From 2c98a7bc80e5c4c225368186718837e53a7f30d3 Mon Sep 17 00:00:00 2001 From: Eli Barzilay Date: Tue, 23 Nov 2021 13:32:38 -0500 Subject: [PATCH] UNINDENT BEFORE CONVERTING TO MODULES --- src/compiler/binder.ts | 6096 +- src/compiler/builder.ts | 2398 +- src/compiler/builderPublic.ts | 304 +- src/compiler/builderState.ts | 1136 +- src/compiler/builderStatePublic.ts | 22 +- src/compiler/checker.ts | 75718 ++++++++-------- src/compiler/commandLineParser.ts | 6542 +- src/compiler/core.ts | 3780 +- src/compiler/corePublic.ts | 276 +- src/compiler/debug.ts | 1212 +- src/compiler/emitter.ts | 9846 +- src/compiler/factory/baseNodeFactory.ts | 92 +- src/compiler/factory/emitHelpers.ts | 1538 +- src/compiler/factory/emitNode.ts | 530 +- src/compiler/factory/nodeConverters.ts | 224 +- src/compiler/factory/nodeFactory.ts | 12074 +-- src/compiler/factory/nodeTests.ts | 1422 +- src/compiler/factory/parenthesizerRules.ts | 776 +- src/compiler/factory/utilities.ts | 2078 +- src/compiler/factory/utilitiesPublic.ts | 8 +- src/compiler/moduleNameResolver.ts | 3720 +- src/compiler/moduleSpecifiers.ts | 1590 +- src/compiler/parser.ts | 16242 ++-- src/compiler/path.ts | 1620 +- src/compiler/perfLogger.ts | 74 +- src/compiler/performance.ts | 236 +- src/compiler/performanceCore.ts | 214 +- src/compiler/program.ts | 7086 +- src/compiler/resolutionCache.ts | 1660 +- src/compiler/scanner.ts | 4540 +- src/compiler/semver.ts | 674 +- src/compiler/sourcemap.ts | 1256 +- src/compiler/symbolWalker.ts | 328 +- src/compiler/sys.ts | 3314 +- src/compiler/tracing.ts | 560 +- src/compiler/transformer.ts | 1006 +- src/compiler/transformers/classFields.ts | 3484 +- src/compiler/transformers/declarations.ts | 3026 +- .../transformers/declarations/diagnostics.ts | 822 +- src/compiler/transformers/destructuring.ts | 932 +- src/compiler/transformers/es2015.ts | 7560 +- src/compiler/transformers/es2016.ts | 178 +- src/compiler/transformers/es2017.ts | 1398 +- src/compiler/transformers/es2018.ts | 2054 +- src/compiler/transformers/es2019.ts | 56 +- src/compiler/transformers/es2020.ts | 358 +- src/compiler/transformers/es2021.ts | 150 +- src/compiler/transformers/es5.ts | 200 +- src/compiler/transformers/esnext.ts | 32 +- src/compiler/transformers/generators.ts | 5354 +- src/compiler/transformers/jsx.ts | 1410 +- .../transformers/module/esnextAnd2015.ts | 476 +- src/compiler/transformers/module/module.ts | 3426 +- src/compiler/transformers/module/node.ts | 118 +- src/compiler/transformers/module/system.ts | 3344 +- src/compiler/transformers/taggedTemplate.ts | 168 +- src/compiler/transformers/ts.ts | 5942 +- src/compiler/transformers/utilities.ts | 642 +- src/compiler/tsbuild.ts | 264 +- src/compiler/tsbuildPublic.ts | 3750 +- src/compiler/types.ts | 17094 ++-- src/compiler/utilities.ts | 12652 +-- src/compiler/utilitiesPublic.ts | 3408 +- src/compiler/visitorPublic.ts | 2478 +- src/compiler/watch.ts | 1196 +- src/compiler/watchPublic.ts | 1784 +- src/compiler/watchUtilities.ts | 1160 +- src/debug/dbg.ts | 874 +- src/deprecatedCompat/deprecations.ts | 2124 +- src/deprecatedCompat/tsconfig.json | 2 +- src/executeCommandLine/executeCommandLine.ts | 1778 +- src/harness/client.ts | 1436 +- src/harness/collectionsImpl.ts | 512 +- src/harness/compilerImpl.ts | 476 +- src/harness/documentsUtil.ts | 322 +- src/harness/evaluatorImpl.ts | 750 +- src/harness/fakesHosts.ts | 914 +- src/harness/findUpDir.ts | 30 +- src/harness/fourslashImpl.ts | 7376 +- src/harness/fourslashInterfaceImpl.ts | 3038 +- src/harness/harnessGlobals.ts | 2 +- src/harness/harnessIO.ts | 2472 +- src/harness/harnessLanguageService.ts | 1754 +- src/harness/harnessUtils.ts | 648 +- src/harness/runnerbase.ts | 102 +- src/harness/sourceMapRecorder.ts | 582 +- src/harness/typeWriter.ts | 322 +- src/harness/util.ts | 170 +- src/harness/vfsUtil.ts | 2810 +- src/harness/virtualFileSystemWithWatch.ts | 2060 +- src/harness/vpathUtil.ts | 248 +- src/instrumenter/tsconfig.json | 2 +- src/jsTyping/jsTyping.ts | 684 +- src/jsTyping/shared.ts | 112 +- src/jsTyping/types.ts | 198 +- src/loggedIO/loggedIO.ts | 762 +- src/server/editorServices.ts | 7282 +- src/server/moduleSpecifierCache.ts | 160 +- src/server/packageJsonCache.ts | 98 +- src/server/project.ts | 4152 +- src/server/protocol.ts | 5944 +- src/server/scriptInfo.ts | 1150 +- src/server/scriptVersionCache.ts | 1322 +- src/server/session.ts | 5556 +- src/server/types.ts | 34 +- src/server/typesMap.json | 2 +- src/server/typingsCache.ts | 232 +- src/server/utilities.ts | 162 +- src/server/utilitiesPublic.ts | 220 +- src/server/watchType.ts | 32 +- src/services/breakpoints.ts | 1184 +- src/services/callHierarchy.ts | 942 +- src/services/classifier.ts | 1874 +- src/services/classifier2020.ts | 402 +- src/services/codeFixProvider.ts | 148 +- ...dConvertToUnknownForNonOverlappingTypes.ts | 38 +- .../codefixes/addEmptyExportDeclaration.ts | 42 +- src/services/codefixes/addMissingAsync.ts | 144 +- src/services/codefixes/addMissingAwait.ts | 474 +- src/services/codefixes/addMissingConst.ts | 176 +- .../codefixes/addMissingDeclareProperty.ts | 56 +- .../addMissingInvocationForDecorator.ts | 36 +- .../codefixes/addNameToNamelessParameter.ts | 62 +- .../codefixes/addOptionalPropertyUndefined.ts | 164 +- .../codefixes/annotateWithTypeFromJSDoc.ts | 294 +- src/services/codefixes/convertConstToLet.ts | 50 +- .../codefixes/convertFunctionToEs6Class.ts | 444 +- .../convertLiteralTypeToMappedType.ts | 100 +- .../codefixes/convertToAsyncFunction.ts | 1396 +- src/services/codefixes/convertToEsModule.ts | 1044 +- .../codefixes/convertToMappedObjectType.ts | 100 +- .../codefixes/convertToTypeOnlyExport.ts | 138 +- .../codefixes/convertToTypeOnlyImport.ts | 88 +- ...correctQualifiedNameToIndexedAccessType.ts | 62 +- .../codefixes/disableJsDiagnostics.ts | 92 +- src/services/codefixes/fixAddMissingMember.ts | 992 +- .../codefixes/fixAddMissingNewOperator.ts | 50 +- .../fixAddModuleReferTypeMissingTypeof.ts | 50 +- src/services/codefixes/fixAddVoidToPromise.ts | 128 +- .../codefixes/fixAwaitInSyncFunction.ts | 140 +- src/services/codefixes/fixCannotFindModule.ts | 98 +- ...sDoesntImplementInheritedAbstractMember.ts | 94 +- .../fixClassIncorrectlyImplementsInterface.ts | 158 +- .../fixClassSuperMustPrecedeThisAccess.ts | 90 +- .../fixConstructorForDerivedNeedSuperCall.ts | 46 +- .../fixEnableExperimentalDecorators.ts | 52 +- src/services/codefixes/fixEnableJsxFlag.ts | 52 +- src/services/codefixes/fixExpectedComma.ts | 72 +- .../fixExtendsInterfaceBecomesImplements.ts | 84 +- .../fixForgottenThisPropertyAccess.ts | 76 +- src/services/codefixes/fixImplicitThis.ts | 92 +- .../codefixes/fixIncorrectNamedTupleSyntax.ts | 86 +- .../codefixes/fixInvalidImportSyntax.ts | 160 +- .../codefixes/fixInvalidJsxCharacters.ts | 76 +- src/services/codefixes/fixJSDocTypes.ts | 148 +- .../codefixes/fixMissingCallParentheses.ts | 70 +- .../codefixes/fixModuleAndTargetOptions.ts | 76 +- .../fixNoPropertyAccessFromIndexSignature.ts | 60 +- src/services/codefixes/fixOverrideModifier.ts | 356 +- .../codefixes/fixPropertyAssignment.ts | 44 +- .../codefixes/fixPropertyOverrideAccessor.ts | 96 +- .../codefixes/fixReturnTypeInAsyncFunction.ts | 104 +- src/services/codefixes/fixSpelling.ts | 242 +- .../codefixes/fixStrictClassInitialization.ts | 250 +- src/services/codefixes/fixUnreachableCode.ts | 112 +- src/services/codefixes/fixUnusedIdentifier.ts | 568 +- src/services/codefixes/fixUnusedLabel.ts | 42 +- src/services/codefixes/generateAccessors.ts | 452 +- src/services/codefixes/helpers.ts | 980 +- src/services/codefixes/importFixes.ts | 2022 +- src/services/codefixes/inferFromUsage.ts | 1906 +- .../removeAccidentalCallParentheses.ts | 36 +- .../codefixes/removeUnnecessaryAwait.ts | 68 +- src/services/codefixes/requireInTs.ts | 120 +- src/services/codefixes/returnValueCorrect.ts | 416 +- src/services/codefixes/splitTypeOnlyImport.ts | 78 +- src/services/codefixes/useBigintLiteral.ts | 52 +- src/services/codefixes/useDefaultImport.ts | 70 +- src/services/codefixes/wrapJsxInFragment.ts | 112 +- src/services/completions.ts | 6868 +- src/services/documentHighlights.ts | 818 +- src/services/documentRegistry.ts | 552 +- src/services/exportAsModule.ts | 2 +- src/services/exportInfoMap.ts | 722 +- src/services/findAllReferences.ts | 3972 +- src/services/formatting/formatting.ts | 2298 +- src/services/formatting/formattingContext.ts | 152 +- src/services/formatting/formattingScanner.ts | 500 +- src/services/formatting/rule.ts | 58 +- src/services/formatting/rules.ts | 1592 +- src/services/formatting/rulesMap.ts | 230 +- src/services/formatting/smartIndenter.ts | 1178 +- src/services/getEditsForFileRename.ts | 430 +- src/services/globalThisShim.ts | 2 +- src/services/goToDefinition.ts | 746 +- src/services/importTracker.ts | 1120 +- src/services/inlayHints.ts | 488 +- src/services/jsDoc.ts | 830 +- src/services/navigateTo.ts | 212 +- src/services/navigationBar.ts | 1674 +- src/services/organizeImports.ts | 822 +- src/services/outliningElementsCollector.ts | 552 +- src/services/patternMatcher.ts | 998 +- src/services/preProcess.ts | 604 +- src/services/refactorProvider.ts | 34 +- .../addOrRemoveBracesToArrowFunction.ts | 202 +- ...onvertArrowFunctionOrFunctionExpression.ts | 416 +- src/services/refactors/convertExport.ts | 436 +- src/services/refactors/convertImport.ts | 344 +- .../convertOverloadListToSingleSignature.ts | 406 +- .../convertParamsToDestructuredObject.ts | 1106 +- .../convertStringOrTemplateLiteral.ts | 426 +- .../convertToOptionalChainExpression.ts | 494 +- src/services/refactors/extractSymbol.ts | 3388 +- src/services/refactors/extractType.ts | 412 +- .../generateGetAccessorAndSetAccessor.ts | 84 +- src/services/refactors/helpers.ts | 40 +- .../refactors/inferFunctionReturnType.ts | 188 +- src/services/refactors/moveToNewFile.ts | 1436 +- src/services/rename.ts | 210 +- src/services/services.ts | 4728 +- src/services/shims.ts | 2318 +- src/services/signatureHelp.ts | 1144 +- src/services/smartSelection.ts | 512 +- src/services/sourcemaps.ts | 322 +- src/services/stringCompletions.ts | 1346 +- src/services/suggestionDiagnostics.ts | 390 +- src/services/symbolDisplay.ts | 1200 +- src/services/textChanges.ts | 2652 +- src/services/transform.ts | 30 +- src/services/transpile.ts | 246 +- src/services/types.ts | 2832 +- src/services/utilities.ts | 5488 +- src/shims/collectionShims.ts | 436 +- src/testRunner/compilerRef.ts | 2 +- src/testRunner/compilerRunner.ts | 566 +- src/testRunner/documentsRef.ts | 2 +- src/testRunner/evaluatorRef.ts | 2 +- src/testRunner/externalCompileRunner.ts | 582 +- src/testRunner/fakesRef.ts | 2 +- src/testRunner/fourslashRef.ts | 2 +- src/testRunner/fourslashRunner.ts | 114 +- src/testRunner/parallel/host.ts | 1072 +- src/testRunner/parallel/shared.ts | 172 +- src/testRunner/parallel/worker.ts | 530 +- src/testRunner/playbackRef.ts | 2 +- src/testRunner/projectsRunner.ts | 766 +- src/testRunner/runner.ts | 484 +- src/testRunner/rwcRunner.ts | 400 +- src/testRunner/test262Runner.ts | 184 +- src/testRunner/unittests/asserts.ts | 18 +- src/testRunner/unittests/base64.ts | 36 +- src/testRunner/unittests/builder.ts | 248 +- src/testRunner/unittests/comments.ts | 40 +- src/testRunner/unittests/compilerCore.ts | 58 +- .../unittests/config/commandLineParsing.ts | 1814 +- .../config/configurationExtension.ts | 654 +- .../config/convertCompilerOptionsFromJson.ts | 1226 +- .../config/convertTypeAcquisitionFromJson.ts | 418 +- .../unittests/config/initializeTSConfig.ts | 42 +- src/testRunner/unittests/config/matchFiles.ts | 2516 +- .../unittests/config/projectReferences.ts | 590 +- src/testRunner/unittests/config/showConfig.ts | 330 +- .../unittests/config/tsconfigParsing.ts | 586 +- .../config/tsconfigParsingWatchOptions.ts | 408 +- src/testRunner/unittests/convertToBase64.ts | 48 +- src/testRunner/unittests/createMapShim.ts | 568 +- src/testRunner/unittests/createSetShim.ts | 538 +- src/testRunner/unittests/customTransforms.ts | 276 +- src/testRunner/unittests/debugDeprecation.ts | 170 +- .../unittests/evaluation/externalModules.ts | 2 +- src/testRunner/unittests/evaluation/forOf.ts | 2 +- .../evaluation/superInStaticInitializer.ts | 2 +- .../evaluation/updateExpressionInModule.ts | 2 +- src/testRunner/unittests/factory.ts | 140 +- src/testRunner/unittests/incrementalParser.ts | 1416 +- src/testRunner/unittests/jsDocParsing.ts | 442 +- .../unittests/jsonParserRecovery.ts | 42 +- src/testRunner/unittests/moduleResolution.ts | 2788 +- src/testRunner/unittests/parsePseudoBigInt.ts | 118 +- src/testRunner/unittests/printer.ts | 506 +- src/testRunner/unittests/programApi.ts | 398 +- .../unittests/reuseProgramStructure.ts | 2000 +- src/testRunner/unittests/semver.ts | 472 +- .../cancellableLanguageServiceOperations.ts | 158 +- .../services/convertToAsyncFunction.ts | 774 +- .../unittests/services/extract/constants.ts | 144 +- .../unittests/services/extract/functions.ts | 260 +- .../unittests/services/extract/helpers.ts | 306 +- .../unittests/services/extract/ranges.ts | 276 +- .../services/extract/symbolWalker.ts | 64 +- .../unittests/services/hostNewLineSupport.ts | 112 +- .../unittests/services/languageService.ts | 490 +- .../unittests/services/organizeImports.ts | 1578 +- .../unittests/services/textChanges.ts | 1100 +- .../unittests/services/transpile.ts | 764 +- src/testRunner/unittests/transform.ts | 992 +- .../unittests/tsbuild/amdModulesWithOut.ts | 272 +- src/testRunner/unittests/tsbuild/clean.ts | 26 +- .../unittests/tsbuild/configFileErrors.ts | 84 +- .../unittests/tsbuild/configFileExtends.ts | 98 +- .../tsbuild/containerOnlyReferenced.ts | 16 +- .../unittests/tsbuild/declarationEmit.ts | 172 +- src/testRunner/unittests/tsbuild/demo.ts | 74 +- .../unittests/tsbuild/emitDeclarationOnly.ts | 84 +- .../unittests/tsbuild/emptyFiles.ts | 40 +- .../unittests/tsbuild/exitCodeOnBogusFile.ts | 16 +- .../unittests/tsbuild/graphOrdering.ts | 152 +- src/testRunner/unittests/tsbuild/helpers.ts | 1282 +- .../inferredTypeFromTransitiveModule.ts | 156 +- .../tsbuild/javascriptProjectEmit.ts | 102 +- .../unittests/tsbuild/lateBoundSymbol.ts | 34 +- .../unittests/tsbuild/moduleResolution.ts | 610 +- .../unittests/tsbuild/moduleSpecifiers.ts | 88 +- .../unittests/tsbuild/noEmitOnError.ts | 76 +- src/testRunner/unittests/tsbuild/outFile.ts | 1168 +- .../unittests/tsbuild/outputPaths.ts | 212 +- src/testRunner/unittests/tsbuild/publicApi.ts | 204 +- .../tsbuild/referencesWithRootDirInParent.ts | 106 +- .../unittests/tsbuild/resolveJsonModule.ts | 134 +- src/testRunner/unittests/tsbuild/sample.ts | 884 +- .../unittests/tsbuild/transitiveReferences.ts | 78 +- .../tsbuildWatch/configFileErrors.ts | 96 +- src/testRunner/unittests/tsbuildWatch/demo.ts | 152 +- .../tsbuildWatch/moduleResolution.ts | 104 +- .../unittests/tsbuildWatch/noEmit.ts | 62 +- .../unittests/tsbuildWatch/noEmitOnError.ts | 72 +- .../unittests/tsbuildWatch/programUpdates.ts | 1390 +- .../unittests/tsbuildWatch/publicApi.ts | 202 +- .../unittests/tsbuildWatch/reexport.ts | 74 +- .../tsbuildWatch/watchEnvironment.ts | 224 +- src/testRunner/unittests/tsc/composite.ts | 74 +- .../unittests/tsc/declarationEmit.ts | 354 +- src/testRunner/unittests/tsc/helpers.ts | 304 +- src/testRunner/unittests/tsc/incremental.ts | 656 +- src/testRunner/unittests/tsc/listFilesOnly.ts | 34 +- .../unittests/tsc/projectReferences.ts | 72 +- .../unittests/tsc/runWithoutArgs.ts | 44 +- .../unittests/tscWatch/consoleClearing.ts | 100 +- src/testRunner/unittests/tscWatch/emit.ts | 966 +- .../unittests/tscWatch/emitAndErrorUpdates.ts | 578 +- .../forceConsistentCasingInFileNames.ts | 424 +- src/testRunner/unittests/tscWatch/helpers.ts | 972 +- .../unittests/tscWatch/incremental.ts | 610 +- .../unittests/tscWatch/nodeNextWatch.ts | 102 +- .../unittests/tscWatch/programUpdates.ts | 3180 +- .../tscWatch/projectsWithReferences.ts | 736 +- .../unittests/tscWatch/resolutionCache.ts | 910 +- .../sourceOfProjectReferenceRedirect.ts | 268 +- src/testRunner/unittests/tscWatch/watchApi.ts | 672 +- .../unittests/tscWatch/watchEnvironment.ts | 1022 +- .../tsserver/applyChangesToOpenFiles.ts | 324 +- .../unittests/tsserver/autoImportProvider.ts | 648 +- .../tsserver/cachingFileSystemInformation.ts | 1284 +- .../unittests/tsserver/cancellationToken.ts | 474 +- .../unittests/tsserver/compileOnSave.ts | 1870 +- .../unittests/tsserver/completions.ts | 466 +- .../tsserver/completionsIncomplete.ts | 438 +- .../unittests/tsserver/configFileSearch.ts | 334 +- .../unittests/tsserver/configuredProjects.ts | 2522 +- .../unittests/tsserver/declarationFileMaps.ts | 1396 +- .../unittests/tsserver/documentRegistry.ts | 152 +- .../unittests/tsserver/duplicatePackages.ts | 92 +- .../unittests/tsserver/dynamicFiles.ts | 448 +- .../tsserver/events/largeFileReferenced.ts | 122 +- .../events/projectLanguageServiceState.ts | 146 +- .../tsserver/events/projectLoading.ts | 396 +- .../events/projectUpdatedInBackground.ts | 918 +- .../unittests/tsserver/exportMapCache.ts | 248 +- .../unittests/tsserver/externalProjects.ts | 1686 +- .../forceConsistentCasingInFileNames.ts | 236 +- .../unittests/tsserver/formatSettings.ts | 58 +- .../tsserver/getApplicableRefactors.ts | 18 +- .../tsserver/getEditsForFileRename.ts | 182 +- .../unittests/tsserver/getExportReferences.ts | 352 +- .../unittests/tsserver/getFileReferences.ts | 98 +- src/testRunner/unittests/tsserver/helpers.ts | 1396 +- .../unittests/tsserver/importHelpers.ts | 30 +- .../unittests/tsserver/inferredProjects.ts | 830 +- .../unittests/tsserver/inlayHints.ts | 112 +- src/testRunner/unittests/tsserver/jsdocTag.ts | 1126 +- .../unittests/tsserver/languageService.ts | 32 +- .../tsserver/maxNodeModuleJsDepth.ts | 104 +- .../unittests/tsserver/metadataInResponse.ts | 182 +- .../tsserver/moduleSpecifierCache.ts | 254 +- src/testRunner/unittests/tsserver/navTo.ts | 202 +- .../unittests/tsserver/occurences.ts | 80 +- src/testRunner/unittests/tsserver/openFile.ts | 306 +- .../unittests/tsserver/packageJsonInfo.ts | 196 +- .../tsserver/partialSemanticServer.ts | 474 +- src/testRunner/unittests/tsserver/plugins.ts | 184 +- .../unittests/tsserver/projectErrors.ts | 1558 +- .../tsserver/projectReferenceCompileOnSave.ts | 4412 +- .../tsserver/projectReferenceErrors.ts | 198 +- .../unittests/tsserver/projectReferences.ts | 2522 +- .../tsserver/projectReferencesSourcemap.ts | 8322 +- src/testRunner/unittests/tsserver/projects.ts | 2864 +- .../tsserver/projectsWithReferences.ts | 976 +- .../unittests/tsserver/refactors.ts | 280 +- src/testRunner/unittests/tsserver/reload.ts | 292 +- .../unittests/tsserver/reloadProjects.ts | 254 +- src/testRunner/unittests/tsserver/rename.ts | 488 +- .../unittests/tsserver/resolutionCache.ts | 1596 +- src/testRunner/unittests/tsserver/session.ts | 1300 +- .../unittests/tsserver/skipLibCheck.ts | 394 +- .../unittests/tsserver/smartSelection.ts | 90 +- src/testRunner/unittests/tsserver/symLinks.ts | 336 +- .../unittests/tsserver/symlinkCache.ts | 120 +- .../unittests/tsserver/syntacticServer.ts | 334 +- .../unittests/tsserver/syntaxOperations.ts | 130 +- .../unittests/tsserver/telemetry.ts | 520 +- .../unittests/tsserver/textStorage.ts | 208 +- .../unittests/tsserver/typeAquisition.ts | 90 +- .../tsserver/typeOnlyImportChains.ts | 320 +- .../tsserver/typeReferenceDirectives.ts | 120 +- .../unittests/tsserver/typingsInstaller.ts | 3778 +- .../unittests/tsserver/versionCache.ts | 592 +- .../unittests/tsserver/watchEnvironment.ts | 1444 +- .../unittests/tsserver/webServer.ts | 274 +- src/testRunner/utilsRef.ts | 2 +- src/testRunner/vfsRef.ts | 2 +- src/testRunner/vpathRef.ts | 2 +- src/tsconfig.json | 2 +- src/tsserver/nodeServer.ts | 1402 +- src/tsserver/server.ts | 150 +- src/tsserver/webServer.ts | 214 +- src/typingsInstaller/nodeTypingsInstaller.ts | 430 +- src/typingsInstallerCore/typingsInstaller.ts | 900 +- src/webServer/webServer.ts | 374 +- 429 files changed, 252234 insertions(+), 252234 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 8ff6712ad30ac..f680633200820 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1,3536 +1,3536 @@ /* @internal */ namespace ts { - export const enum ModuleInstanceState { - NonInstantiated = 0, - Instantiated = 1, - ConstEnumOnly = 2 - } +export const enum ModuleInstanceState { + NonInstantiated = 0, + Instantiated = 1, + ConstEnumOnly = 2 +} - interface ActiveLabel { - next: ActiveLabel | undefined; - name: __String; - breakTarget: FlowLabel; - continueTarget: FlowLabel | undefined; - referenced: boolean; - } +interface ActiveLabel { + next: ActiveLabel | undefined; + name: __String; + breakTarget: FlowLabel; + continueTarget: FlowLabel | undefined; + referenced: boolean; +} - export function getModuleInstanceState(node: ModuleDeclaration, visited?: ESMap): ModuleInstanceState { - if (node.body && !node.body.parent) { - // getModuleInstanceStateForAliasTarget needs to walk up the parent chain, so parent pointers must be set on this tree already - setParent(node.body, node); - setParentRecursive(node.body, /*incremental*/ false); - } - return node.body ? getModuleInstanceStateCached(node.body, visited) : ModuleInstanceState.Instantiated; +export function getModuleInstanceState(node: ModuleDeclaration, visited?: ESMap): ModuleInstanceState { + if (node.body && !node.body.parent) { + // getModuleInstanceStateForAliasTarget needs to walk up the parent chain, so parent pointers must be set on this tree already + setParent(node.body, node); + setParentRecursive(node.body, /*incremental*/ false); } + return node.body ? getModuleInstanceStateCached(node.body, visited) : ModuleInstanceState.Instantiated; +} - function getModuleInstanceStateCached(node: Node, visited = new Map()) { - const nodeId = getNodeId(node); - if (visited.has(nodeId)) { - return visited.get(nodeId) || ModuleInstanceState.NonInstantiated; - } - visited.set(nodeId, undefined); - const result = getModuleInstanceStateWorker(node, visited); - visited.set(nodeId, result); - return result; +function getModuleInstanceStateCached(node: Node, visited = new Map()) { + const nodeId = getNodeId(node); + if (visited.has(nodeId)) { + return visited.get(nodeId) || ModuleInstanceState.NonInstantiated; } + visited.set(nodeId, undefined); + const result = getModuleInstanceStateWorker(node, visited); + visited.set(nodeId, result); + return result; +} - function getModuleInstanceStateWorker(node: Node, visited: ESMap): ModuleInstanceState { - // A module is uninstantiated if it contains only - switch (node.kind) { - // 1. interface declarations, type alias declarations - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: +function getModuleInstanceStateWorker(node: Node, visited: ESMap): ModuleInstanceState { + // A module is uninstantiated if it contains only + switch (node.kind) { + // 1. interface declarations, type alias declarations + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return ModuleInstanceState.NonInstantiated; + // 2. const enum declarations + case SyntaxKind.EnumDeclaration: + if (isEnumConst(node as EnumDeclaration)) { + return ModuleInstanceState.ConstEnumOnly; + } + break; + // 3. non-exported import declarations + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + if (!(hasSyntacticModifier(node, ModifierFlags.Export))) { return ModuleInstanceState.NonInstantiated; - // 2. const enum declarations - case SyntaxKind.EnumDeclaration: - if (isEnumConst(node as EnumDeclaration)) { - return ModuleInstanceState.ConstEnumOnly; - } - break; - // 3. non-exported import declarations - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - if (!(hasSyntacticModifier(node, ModifierFlags.Export))) { - return ModuleInstanceState.NonInstantiated; - } - break; - // 4. Export alias declarations pointing at only uninstantiated modules or things uninstantiated modules contain - case SyntaxKind.ExportDeclaration: - const exportDeclaration = node as ExportDeclaration; - if (!exportDeclaration.moduleSpecifier && exportDeclaration.exportClause && exportDeclaration.exportClause.kind === SyntaxKind.NamedExports) { - let state = ModuleInstanceState.NonInstantiated; - for (const specifier of exportDeclaration.exportClause.elements) { - const specifierState = getModuleInstanceStateForAliasTarget(specifier, visited); - if (specifierState > state) { - state = specifierState; - } - if (state === ModuleInstanceState.Instantiated) { - return state; - } - } - return state; - } - break; - // 5. other uninstantiated module declarations. - case SyntaxKind.ModuleBlock: { + } + break; + // 4. Export alias declarations pointing at only uninstantiated modules or things uninstantiated modules contain + case SyntaxKind.ExportDeclaration: + const exportDeclaration = node as ExportDeclaration; + if (!exportDeclaration.moduleSpecifier && exportDeclaration.exportClause && exportDeclaration.exportClause.kind === SyntaxKind.NamedExports) { let state = ModuleInstanceState.NonInstantiated; - forEachChild(node, n => { - const childState = getModuleInstanceStateCached(n, visited); - switch (childState) { - case ModuleInstanceState.NonInstantiated: - // child is non-instantiated - continue searching - return; - case ModuleInstanceState.ConstEnumOnly: - // child is const enum only - record state and continue searching - state = ModuleInstanceState.ConstEnumOnly; - return; - case ModuleInstanceState.Instantiated: - // child is instantiated - record state and stop - state = ModuleInstanceState.Instantiated; - return true; - default: - Debug.assertNever(childState); + for (const specifier of exportDeclaration.exportClause.elements) { + const specifierState = getModuleInstanceStateForAliasTarget(specifier, visited); + if (specifierState > state) { + state = specifierState; + } + if (state === ModuleInstanceState.Instantiated) { + return state; } - }); + } return state; } - case SyntaxKind.ModuleDeclaration: - return getModuleInstanceState(node as ModuleDeclaration, visited); - case SyntaxKind.Identifier: - // Only jsdoc typedef definition can exist in jsdoc namespace, and it should - // be considered the same as type alias - if ((node as Identifier).isInJSDocNamespace) { - return ModuleInstanceState.NonInstantiated; + break; + // 5. other uninstantiated module declarations. + case SyntaxKind.ModuleBlock: { + let state = ModuleInstanceState.NonInstantiated; + forEachChild(node, n => { + const childState = getModuleInstanceStateCached(n, visited); + switch (childState) { + case ModuleInstanceState.NonInstantiated: + // child is non-instantiated - continue searching + return; + case ModuleInstanceState.ConstEnumOnly: + // child is const enum only - record state and continue searching + state = ModuleInstanceState.ConstEnumOnly; + return; + case ModuleInstanceState.Instantiated: + // child is instantiated - record state and stop + state = ModuleInstanceState.Instantiated; + return true; + default: + Debug.assertNever(childState); } + }); + return state; } - return ModuleInstanceState.Instantiated; + case SyntaxKind.ModuleDeclaration: + return getModuleInstanceState(node as ModuleDeclaration, visited); + case SyntaxKind.Identifier: + // Only jsdoc typedef definition can exist in jsdoc namespace, and it should + // be considered the same as type alias + if ((node as Identifier).isInJSDocNamespace) { + return ModuleInstanceState.NonInstantiated; + } } + return ModuleInstanceState.Instantiated; +} - function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visited: ESMap) { - const name = specifier.propertyName || specifier.name; - let p: Node | undefined = specifier.parent; - while (p) { - if (isBlock(p) || isModuleBlock(p) || isSourceFile(p)) { - const statements = p.statements; - let found: ModuleInstanceState | undefined; - for (const statement of statements) { - if (nodeHasName(statement, name)) { - if (!statement.parent) { - setParent(statement, p); - setParentRecursive(statement, /*incremental*/ false); - } - const state = getModuleInstanceStateCached(statement, visited); - if (found === undefined || state > found) { - found = state; - } - if (found === ModuleInstanceState.Instantiated) { - return found; - } +function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visited: ESMap) { + const name = specifier.propertyName || specifier.name; + let p: Node | undefined = specifier.parent; + while (p) { + if (isBlock(p) || isModuleBlock(p) || isSourceFile(p)) { + const statements = p.statements; + let found: ModuleInstanceState | undefined; + for (const statement of statements) { + if (nodeHasName(statement, name)) { + if (!statement.parent) { + setParent(statement, p); + setParentRecursive(statement, /*incremental*/ false); + } + const state = getModuleInstanceStateCached(statement, visited); + if (found === undefined || state > found) { + found = state; + } + if (found === ModuleInstanceState.Instantiated) { + return found; } } - if (found !== undefined) { - return found; - } } - p = p.parent; + if (found !== undefined) { + return found; + } } - return ModuleInstanceState.Instantiated; // Couldn't locate, assume could refer to a value + p = p.parent; } + return ModuleInstanceState.Instantiated; // Couldn't locate, assume could refer to a value +} - const enum ContainerFlags { - // The current node is not a container, and no container manipulation should happen before - // recursing into it. - None = 0, +const enum ContainerFlags { + // The current node is not a container, and no container manipulation should happen before + // recursing into it. + None = 0, + + // The current node is a container. It should be set as the current container (and block- + // container) before recursing into it. The current node does not have locals. Examples: + // + // Classes, ObjectLiterals, TypeLiterals, Interfaces... + IsContainer = 1 << 0, + + // The current node is a block-scoped-container. It should be set as the current block- + // container before recursing into it. Examples: + // + // Blocks (when not parented by functions), Catch clauses, For/For-in/For-of statements... + IsBlockScopedContainer = 1 << 1, + + // The current node is the container of a control flow path. The current control flow should + // be saved and restored, and a new control flow initialized within the container. + IsControlFlowContainer = 1 << 2, + + IsFunctionLike = 1 << 3, + IsFunctionExpression = 1 << 4, + HasLocals = 1 << 5, + IsInterface = 1 << 6, + IsObjectLiteralOrClassExpressionMethodOrAccessor = 1 << 7, +} - // The current node is a container. It should be set as the current container (and block- - // container) before recursing into it. The current node does not have locals. Examples: - // - // Classes, ObjectLiterals, TypeLiterals, Interfaces... - IsContainer = 1 << 0, +function initFlowNode(node: T) { + Debug.attachFlowNodeDebugInfo(node); + return node; +} - // The current node is a block-scoped-container. It should be set as the current block- - // container before recursing into it. Examples: - // - // Blocks (when not parented by functions), Catch clauses, For/For-in/For-of statements... - IsBlockScopedContainer = 1 << 1, - - // The current node is the container of a control flow path. The current control flow should - // be saved and restored, and a new control flow initialized within the container. - IsControlFlowContainer = 1 << 2, - - IsFunctionLike = 1 << 3, - IsFunctionExpression = 1 << 4, - HasLocals = 1 << 5, - IsInterface = 1 << 6, - IsObjectLiteralOrClassExpressionMethodOrAccessor = 1 << 7, - } - - function initFlowNode(node: T) { - Debug.attachFlowNodeDebugInfo(node); - return node; - } - - const binder = createBinder(); - - export function bindSourceFile(file: SourceFile, options: CompilerOptions) { - tracing?.push(tracing.Phase.Bind, "bindSourceFile", { path: file.path }, /*separateBeginAndEnd*/ true); - performance.mark("beforeBind"); - perfLogger.logStartBindFile("" + file.fileName); - binder(file, options); - perfLogger.logStopBindFile(); - performance.mark("afterBind"); - performance.measure("Bind", "beforeBind", "afterBind"); - tracing?.pop(); - } - - function createBinder(): (file: SourceFile, options: CompilerOptions) => void { - let file: SourceFile; - let options: CompilerOptions; - let languageVersion: ScriptTarget; - let parent: Node; - let container: Node; - let thisParentContainer: Node; // Container one level up - let blockScopeContainer: Node; - let lastContainer: Node; - let delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag)[]; - let seenThisKeyword: boolean; - - // state used by control flow analysis - let currentFlow: FlowNode; - let currentBreakTarget: FlowLabel | undefined; - let currentContinueTarget: FlowLabel | undefined; - let currentReturnTarget: FlowLabel | undefined; - let currentTrueTarget: FlowLabel | undefined; - let currentFalseTarget: FlowLabel | undefined; - let currentExceptionTarget: FlowLabel | undefined; - let preSwitchCaseFlow: FlowNode | undefined; - let activeLabelList: ActiveLabel | undefined; - let hasExplicitReturn: boolean; - - // state used for emit helpers - let emitFlags: NodeFlags; - - // If this file is an external module, then it is automatically in strict-mode according to - // ES6. If it is not an external module, then we'll determine if it is in strict mode or - // not depending on if we see "use strict" in certain places or if we hit a class/namespace - // or if compiler options contain alwaysStrict. - let inStrictMode: boolean; - - // If we are binding an assignment pattern, we will bind certain expressions differently. - let inAssignmentPattern = false; - - let symbolCount = 0; - - let Symbol: new (flags: SymbolFlags, name: __String) => Symbol; - let classifiableNames: Set<__String>; - - const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; - const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; - const bindBinaryExpressionFlow = createBindBinaryExpressionFlow(); - - /** - * Inside the binder, we may create a diagnostic for an as-yet unbound node (with potentially no parent pointers, implying no accessible source file) - * If so, the node _must_ be in the current file (as that's the only way anything could have traversed to it to yield it as the error node) - * This version of `createDiagnosticForNode` uses the binder's context to account for this, and always yields correct diagnostics even in these situations. - */ - function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { - return createDiagnosticForNodeInSourceFile(getSourceFileOfNode(node) || file, node, message, arg0, arg1, arg2); - } - - function bindSourceFile(f: SourceFile, opts: CompilerOptions) { - file = f; - options = opts; - languageVersion = getEmitScriptTarget(options); - inStrictMode = bindInStrictMode(file, opts); - classifiableNames = new Set(); - symbolCount = 0; - - Symbol = objectAllocator.getSymbolConstructor(); - - // Attach debugging information if necessary - Debug.attachFlowNodeDebugInfo(unreachableFlow); - Debug.attachFlowNodeDebugInfo(reportedUnreachableFlow); - - if (!file.locals) { - bind(file); - file.symbolCount = symbolCount; - file.classifiableNames = classifiableNames; - delayedBindJSDocTypedefTag(); - } - - file = undefined!; - options = undefined!; - languageVersion = undefined!; - parent = undefined!; - container = undefined!; - thisParentContainer = undefined!; - blockScopeContainer = undefined!; - lastContainer = undefined!; - delayedTypeAliases = undefined!; - seenThisKeyword = false; - currentFlow = undefined!; - currentBreakTarget = undefined; - currentContinueTarget = undefined; - currentReturnTarget = undefined; - currentTrueTarget = undefined; - currentFalseTarget = undefined; - currentExceptionTarget = undefined; - activeLabelList = undefined; - hasExplicitReturn = false; - inAssignmentPattern = false; - emitFlags = NodeFlags.None; - } +const binder = createBinder(); + +export function bindSourceFile(file: SourceFile, options: CompilerOptions) { + tracing?.push(tracing.Phase.Bind, "bindSourceFile", { path: file.path }, /*separateBeginAndEnd*/ true); + performance.mark("beforeBind"); + perfLogger.logStartBindFile("" + file.fileName); + binder(file, options); + perfLogger.logStopBindFile(); + performance.mark("afterBind"); + performance.measure("Bind", "beforeBind", "afterBind"); + tracing?.pop(); +} - return bindSourceFile; +function createBinder(): (file: SourceFile, options: CompilerOptions) => void { + let file: SourceFile; + let options: CompilerOptions; + let languageVersion: ScriptTarget; + let parent: Node; + let container: Node; + let thisParentContainer: Node; // Container one level up + let blockScopeContainer: Node; + let lastContainer: Node; + let delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag)[]; + let seenThisKeyword: boolean; + + // state used by control flow analysis + let currentFlow: FlowNode; + let currentBreakTarget: FlowLabel | undefined; + let currentContinueTarget: FlowLabel | undefined; + let currentReturnTarget: FlowLabel | undefined; + let currentTrueTarget: FlowLabel | undefined; + let currentFalseTarget: FlowLabel | undefined; + let currentExceptionTarget: FlowLabel | undefined; + let preSwitchCaseFlow: FlowNode | undefined; + let activeLabelList: ActiveLabel | undefined; + let hasExplicitReturn: boolean; + + // state used for emit helpers + let emitFlags: NodeFlags; + + // If this file is an external module, then it is automatically in strict-mode according to + // ES6. If it is not an external module, then we'll determine if it is in strict mode or + // not depending on if we see "use strict" in certain places or if we hit a class/namespace + // or if compiler options contain alwaysStrict. + let inStrictMode: boolean; + + // If we are binding an assignment pattern, we will bind certain expressions differently. + let inAssignmentPattern = false; + + let symbolCount = 0; + + let Symbol: new (flags: SymbolFlags, name: __String) => Symbol; + let classifiableNames: Set<__String>; + + const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; + const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; + const bindBinaryExpressionFlow = createBindBinaryExpressionFlow(); + + /** + * Inside the binder, we may create a diagnostic for an as-yet unbound node (with potentially no parent pointers, implying no accessible source file) + * If so, the node _must_ be in the current file (as that's the only way anything could have traversed to it to yield it as the error node) + * This version of `createDiagnosticForNode` uses the binder's context to account for this, and always yields correct diagnostics even in these situations. + */ + function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { + return createDiagnosticForNodeInSourceFile(getSourceFileOfNode(node) || file, node, message, arg0, arg1, arg2); + } - function bindInStrictMode(file: SourceFile, opts: CompilerOptions): boolean { - if (getStrictOptionValue(opts, "alwaysStrict") && !file.isDeclarationFile) { - // bind in strict mode source files with alwaysStrict option - return true; - } - else { - return !!file.externalModuleIndicator; - } - } + function bindSourceFile(f: SourceFile, opts: CompilerOptions) { + file = f; + options = opts; + languageVersion = getEmitScriptTarget(options); + inStrictMode = bindInStrictMode(file, opts); + classifiableNames = new Set(); + symbolCount = 0; + + Symbol = objectAllocator.getSymbolConstructor(); + + // Attach debugging information if necessary + Debug.attachFlowNodeDebugInfo(unreachableFlow); + Debug.attachFlowNodeDebugInfo(reportedUnreachableFlow); + + if (!file.locals) { + bind(file); + file.symbolCount = symbolCount; + file.classifiableNames = classifiableNames; + delayedBindJSDocTypedefTag(); + } + + file = undefined!; + options = undefined!; + languageVersion = undefined!; + parent = undefined!; + container = undefined!; + thisParentContainer = undefined!; + blockScopeContainer = undefined!; + lastContainer = undefined!; + delayedTypeAliases = undefined!; + seenThisKeyword = false; + currentFlow = undefined!; + currentBreakTarget = undefined; + currentContinueTarget = undefined; + currentReturnTarget = undefined; + currentTrueTarget = undefined; + currentFalseTarget = undefined; + currentExceptionTarget = undefined; + activeLabelList = undefined; + hasExplicitReturn = false; + inAssignmentPattern = false; + emitFlags = NodeFlags.None; + } - function createSymbol(flags: SymbolFlags, name: __String): Symbol { - symbolCount++; - return new Symbol(flags, name); + return bindSourceFile; + + function bindInStrictMode(file: SourceFile, opts: CompilerOptions): boolean { + if (getStrictOptionValue(opts, "alwaysStrict") && !file.isDeclarationFile) { + // bind in strict mode source files with alwaysStrict option + return true; } + else { + return !!file.externalModuleIndicator; + } + } - function addDeclarationToSymbol(symbol: Symbol, node: Declaration, symbolFlags: SymbolFlags) { - symbol.flags |= symbolFlags; + function createSymbol(flags: SymbolFlags, name: __String): Symbol { + symbolCount++; + return new Symbol(flags, name); + } - node.symbol = symbol; - symbol.declarations = appendIfUnique(symbol.declarations, node); + function addDeclarationToSymbol(symbol: Symbol, node: Declaration, symbolFlags: SymbolFlags) { + symbol.flags |= symbolFlags; - if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.Module | SymbolFlags.Variable) && !symbol.exports) { - symbol.exports = createSymbolTable(); - } + node.symbol = symbol; + symbol.declarations = appendIfUnique(symbol.declarations, node); - if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && !symbol.members) { - symbol.members = createSymbolTable(); - } + if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.Module | SymbolFlags.Variable) && !symbol.exports) { + symbol.exports = createSymbolTable(); + } - // On merge of const enum module with class or function, reset const enum only flag (namespaces will already recalculate) - if (symbol.constEnumOnlyModule && (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) { - symbol.constEnumOnlyModule = false; - } + if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && !symbol.members) { + symbol.members = createSymbolTable(); + } - if (symbolFlags & SymbolFlags.Value) { - setValueDeclaration(symbol, node); - } + // On merge of const enum module with class or function, reset const enum only flag (namespaces will already recalculate) + if (symbol.constEnumOnlyModule && (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) { + symbol.constEnumOnlyModule = false; } - // Should not be called on a declaration with a computed property name, - // unless it is a well known Symbol. - function getDeclarationName(node: Declaration): __String | undefined { - if (node.kind === SyntaxKind.ExportAssignment) { - return (node as ExportAssignment).isExportEquals ? InternalSymbolName.ExportEquals : InternalSymbolName.Default; - } + if (symbolFlags & SymbolFlags.Value) { + setValueDeclaration(symbol, node); + } + } + + // Should not be called on a declaration with a computed property name, + // unless it is a well known Symbol. + function getDeclarationName(node: Declaration): __String | undefined { + if (node.kind === SyntaxKind.ExportAssignment) { + return (node as ExportAssignment).isExportEquals ? InternalSymbolName.ExportEquals : InternalSymbolName.Default; + } - const name = getNameOfDeclaration(node); - if (name) { - if (isAmbientModule(node)) { - const moduleName = getTextOfIdentifierOrLiteral(name as Identifier | StringLiteral); - return (isGlobalScopeAugmentation(node as ModuleDeclaration) ? "__global" : `"${moduleName}"`) as __String; + const name = getNameOfDeclaration(node); + if (name) { + if (isAmbientModule(node)) { + const moduleName = getTextOfIdentifierOrLiteral(name as Identifier | StringLiteral); + return (isGlobalScopeAugmentation(node as ModuleDeclaration) ? "__global" : `"${moduleName}"`) as __String; + } + if (name.kind === SyntaxKind.ComputedPropertyName) { + const nameExpression = name.expression; + // treat computed property names where expression is string/numeric literal as just string/numeric literal + if (isStringOrNumericLiteralLike(nameExpression)) { + return escapeLeadingUnderscores(nameExpression.text); } - if (name.kind === SyntaxKind.ComputedPropertyName) { - const nameExpression = name.expression; - // treat computed property names where expression is string/numeric literal as just string/numeric literal - if (isStringOrNumericLiteralLike(nameExpression)) { - return escapeLeadingUnderscores(nameExpression.text); - } - if (isSignedNumericLiteral(nameExpression)) { - return tokenToString(nameExpression.operator) + nameExpression.operand.text as __String; - } - else { - Debug.fail("Only computed properties with literal names have declaration names"); - } + if (isSignedNumericLiteral(nameExpression)) { + return tokenToString(nameExpression.operator) + nameExpression.operand.text as __String; } - if (isPrivateIdentifier(name)) { - // containingClass exists because private names only allowed inside classes - const containingClass = getContainingClass(node); - if (!containingClass) { - // we can get here in cases where there is already a parse error. - return undefined; - } - const containingClassSymbol = containingClass.symbol; - return getSymbolNameForPrivateIdentifier(containingClassSymbol, name.escapedText); - } - return isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; - } - switch (node.kind) { - case SyntaxKind.Constructor: - return InternalSymbolName.Constructor; - case SyntaxKind.FunctionType: - case SyntaxKind.CallSignature: - case SyntaxKind.JSDocSignature: - return InternalSymbolName.Call; - case SyntaxKind.ConstructorType: - case SyntaxKind.ConstructSignature: - return InternalSymbolName.New; - case SyntaxKind.IndexSignature: - return InternalSymbolName.Index; - case SyntaxKind.ExportDeclaration: - return InternalSymbolName.ExportStar; - case SyntaxKind.SourceFile: - // json file should behave as + else { + Debug.fail("Only computed properties with literal names have declaration names"); + } + } + if (isPrivateIdentifier(name)) { + // containingClass exists because private names only allowed inside classes + const containingClass = getContainingClass(node); + if (!containingClass) { + // we can get here in cases where there is already a parse error. + return undefined; + } + const containingClassSymbol = containingClass.symbol; + return getSymbolNameForPrivateIdentifier(containingClassSymbol, name.escapedText); + } + return isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; + } + switch (node.kind) { + case SyntaxKind.Constructor: + return InternalSymbolName.Constructor; + case SyntaxKind.FunctionType: + case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: + return InternalSymbolName.Call; + case SyntaxKind.ConstructorType: + case SyntaxKind.ConstructSignature: + return InternalSymbolName.New; + case SyntaxKind.IndexSignature: + return InternalSymbolName.Index; + case SyntaxKind.ExportDeclaration: + return InternalSymbolName.ExportStar; + case SyntaxKind.SourceFile: + // json file should behave as + // module.exports = ... + return InternalSymbolName.ExportEquals; + case SyntaxKind.BinaryExpression: + if (getAssignmentDeclarationKind(node as BinaryExpression) === AssignmentDeclarationKind.ModuleExports) { // module.exports = ... return InternalSymbolName.ExportEquals; - case SyntaxKind.BinaryExpression: - if (getAssignmentDeclarationKind(node as BinaryExpression) === AssignmentDeclarationKind.ModuleExports) { - // module.exports = ... - return InternalSymbolName.ExportEquals; - } - Debug.fail("Unknown binary declaration kind"); - break; - case SyntaxKind.JSDocFunctionType: - return (isJSDocConstructSignature(node) ? InternalSymbolName.New : InternalSymbolName.Call); - case SyntaxKind.Parameter: - // Parameters with names are handled at the top of this function. Parameters - // without names can only come from JSDocFunctionTypes. - Debug.assert(node.parent.kind === SyntaxKind.JSDocFunctionType, "Impossible parameter parent kind", () => `parent is: ${(ts as any).SyntaxKind ? (ts as any).SyntaxKind[node.parent.kind] : node.parent.kind}, expected JSDocFunctionType`); - const functionType = node.parent as JSDocFunctionType; - const index = functionType.parameters.indexOf(node as ParameterDeclaration); - return "arg" + index as __String; - } + } + Debug.fail("Unknown binary declaration kind"); + break; + case SyntaxKind.JSDocFunctionType: + return (isJSDocConstructSignature(node) ? InternalSymbolName.New : InternalSymbolName.Call); + case SyntaxKind.Parameter: + // Parameters with names are handled at the top of this function. Parameters + // without names can only come from JSDocFunctionTypes. + Debug.assert(node.parent.kind === SyntaxKind.JSDocFunctionType, "Impossible parameter parent kind", () => `parent is: ${(ts as any).SyntaxKind ? (ts as any).SyntaxKind[node.parent.kind] : node.parent.kind}, expected JSDocFunctionType`); + const functionType = node.parent as JSDocFunctionType; + const index = functionType.parameters.indexOf(node as ParameterDeclaration); + return "arg" + index as __String; } + } - function getDisplayName(node: Declaration): string { - return isNamedDeclaration(node) ? declarationNameToString(node.name) : unescapeLeadingUnderscores(Debug.checkDefined(getDeclarationName(node))); - } + function getDisplayName(node: Declaration): string { + return isNamedDeclaration(node) ? declarationNameToString(node.name) : unescapeLeadingUnderscores(Debug.checkDefined(getDeclarationName(node))); + } - /** - * Declares a Symbol for the node and adds it to symbols. Reports errors for conflicting identifier names. - * @param symbolTable - The symbol table which node will be added to. - * @param parent - node's parent declaration. - * @param node - The declaration to be added to the symbol table - * @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.) - * @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations. - */ - function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean, isComputedName?: boolean): Symbol { - Debug.assert(isComputedName || !hasDynamicName(node)); + /** + * Declares a Symbol for the node and adds it to symbols. Reports errors for conflicting identifier names. + * @param symbolTable - The symbol table which node will be added to. + * @param parent - node's parent declaration. + * @param node - The declaration to be added to the symbol table + * @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.) + * @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations. + */ + function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean, isComputedName?: boolean): Symbol { + Debug.assert(isComputedName || !hasDynamicName(node)); - const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && node.name.escapedText === "default"; + const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && node.name.escapedText === "default"; - // The exported symbol for an export default function/class node is always named "default" - const name = isComputedName ? InternalSymbolName.Computed - : isDefaultExport && parent ? InternalSymbolName.Default - : getDeclarationName(node); + // The exported symbol for an export default function/class node is always named "default" + const name = isComputedName ? InternalSymbolName.Computed + : isDefaultExport && parent ? InternalSymbolName.Default + : getDeclarationName(node); - let symbol: Symbol | undefined; - if (name === undefined) { - symbol = createSymbol(SymbolFlags.None, InternalSymbolName.Missing); - } - else { - // Check and see if the symbol table already has a symbol with this name. If not, - // create a new symbol with this name and add it to the table. Note that we don't - // give the new symbol any flags *yet*. This ensures that it will not conflict - // with the 'excludes' flags we pass in. - // - // If we do get an existing symbol, see if it conflicts with the new symbol we're - // creating. For example, a 'var' symbol and a 'class' symbol will conflict within - // the same symbol table. If we have a conflict, report the issue on each - // declaration we have for this symbol, and then create a new symbol for this - // declaration. - // - // Note that when properties declared in Javascript constructors - // (marked by isReplaceableByMethod) conflict with another symbol, the property loses. - // Always. This allows the common Javascript pattern of overwriting a prototype method - // with an bound instance method of the same type: `this.method = this.method.bind(this)` - // - // If we created a new symbol, either because we didn't have a symbol with this name - // in the symbol table, or we conflicted with an existing symbol, then just add this - // node as the sole declaration of the new symbol. - // - // Otherwise, we'll be merging into a compatible existing symbol (for example when - // you have multiple 'vars' with the same name in the same container). In this case - // just add this node into the declarations list of the symbol. - symbol = symbolTable.get(name); + let symbol: Symbol | undefined; + if (name === undefined) { + symbol = createSymbol(SymbolFlags.None, InternalSymbolName.Missing); + } + else { + // Check and see if the symbol table already has a symbol with this name. If not, + // create a new symbol with this name and add it to the table. Note that we don't + // give the new symbol any flags *yet*. This ensures that it will not conflict + // with the 'excludes' flags we pass in. + // + // If we do get an existing symbol, see if it conflicts with the new symbol we're + // creating. For example, a 'var' symbol and a 'class' symbol will conflict within + // the same symbol table. If we have a conflict, report the issue on each + // declaration we have for this symbol, and then create a new symbol for this + // declaration. + // + // Note that when properties declared in Javascript constructors + // (marked by isReplaceableByMethod) conflict with another symbol, the property loses. + // Always. This allows the common Javascript pattern of overwriting a prototype method + // with an bound instance method of the same type: `this.method = this.method.bind(this)` + // + // If we created a new symbol, either because we didn't have a symbol with this name + // in the symbol table, or we conflicted with an existing symbol, then just add this + // node as the sole declaration of the new symbol. + // + // Otherwise, we'll be merging into a compatible existing symbol (for example when + // you have multiple 'vars' with the same name in the same container). In this case + // just add this node into the declarations list of the symbol. + symbol = symbolTable.get(name); - if (includes & SymbolFlags.Classifiable) { - classifiableNames.add(name); - } + if (includes & SymbolFlags.Classifiable) { + classifiableNames.add(name); + } - if (!symbol) { + if (!symbol) { + symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name)); + if (isReplaceableByMethod) symbol.isReplaceableByMethod = true; + } + else if (isReplaceableByMethod && !symbol.isReplaceableByMethod) { + // A symbol already exists, so don't add this as a declaration. + return symbol; + } + else if (symbol.flags & excludes) { + if (symbol.isReplaceableByMethod) { + // Javascript constructor-declared symbols can be discarded in favor of + // prototype symbols like methods. symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name)); - if (isReplaceableByMethod) symbol.isReplaceableByMethod = true; - } - else if (isReplaceableByMethod && !symbol.isReplaceableByMethod) { - // A symbol already exists, so don't add this as a declaration. - return symbol; } - else if (symbol.flags & excludes) { - if (symbol.isReplaceableByMethod) { - // Javascript constructor-declared symbols can be discarded in favor of - // prototype symbols like methods. - symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name)); - } - else if (!(includes & SymbolFlags.Variable && symbol.flags & SymbolFlags.Assignment)) { - // Assignment declarations are allowed to merge with variables, no matter what other flags they have. - if (isNamedDeclaration(node)) { - setParent(node.name, node); - } - // Report errors every position with duplicate declaration - // Report errors on previous encountered declarations - let message = symbol.flags & SymbolFlags.BlockScopedVariable - ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 - : Diagnostics.Duplicate_identifier_0; - let messageNeedsName = true; - - if (symbol.flags & SymbolFlags.Enum || includes & SymbolFlags.Enum) { - message = Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations; + else if (!(includes & SymbolFlags.Variable && symbol.flags & SymbolFlags.Assignment)) { + // Assignment declarations are allowed to merge with variables, no matter what other flags they have. + if (isNamedDeclaration(node)) { + setParent(node.name, node); + } + // Report errors every position with duplicate declaration + // Report errors on previous encountered declarations + let message = symbol.flags & SymbolFlags.BlockScopedVariable + ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 + : Diagnostics.Duplicate_identifier_0; + let messageNeedsName = true; + + if (symbol.flags & SymbolFlags.Enum || includes & SymbolFlags.Enum) { + message = Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations; + messageNeedsName = false; + } + + let multipleDefaultExports = false; + if (length(symbol.declarations)) { + // If the current node is a default export of some sort, then check if + // there are any other default exports that we need to error on. + // We'll know whether we have other default exports depending on if `symbol` already has a declaration list set. + if (isDefaultExport) { + message = Diagnostics.A_module_cannot_have_multiple_default_exports; messageNeedsName = false; + multipleDefaultExports = true; } - - let multipleDefaultExports = false; - if (length(symbol.declarations)) { - // If the current node is a default export of some sort, then check if - // there are any other default exports that we need to error on. - // We'll know whether we have other default exports depending on if `symbol` already has a declaration list set. - if (isDefaultExport) { + else { + // This is to properly report an error in the case "export default { }" is after export default of class declaration or function declaration. + // Error on multiple export default in the following case: + // 1. multiple export default of class declaration or function declaration by checking NodeFlags.Default + // 2. multiple export default of export assignment. This one doesn't have NodeFlags.Default on (as export default doesn't considered as modifiers) + if (symbol.declarations && symbol.declarations.length && + (node.kind === SyntaxKind.ExportAssignment && !(node as ExportAssignment).isExportEquals)) { message = Diagnostics.A_module_cannot_have_multiple_default_exports; messageNeedsName = false; multipleDefaultExports = true; } - else { - // This is to properly report an error in the case "export default { }" is after export default of class declaration or function declaration. - // Error on multiple export default in the following case: - // 1. multiple export default of class declaration or function declaration by checking NodeFlags.Default - // 2. multiple export default of export assignment. This one doesn't have NodeFlags.Default on (as export default doesn't considered as modifiers) - if (symbol.declarations && symbol.declarations.length && - (node.kind === SyntaxKind.ExportAssignment && !(node as ExportAssignment).isExportEquals)) { - message = Diagnostics.A_module_cannot_have_multiple_default_exports; - messageNeedsName = false; - multipleDefaultExports = true; - } - } } + } - const relatedInformation: DiagnosticRelatedInformation[] = []; - if (isTypeAliasDeclaration(node) && nodeIsMissing(node.type) && hasSyntacticModifier(node, ModifierFlags.Export) && symbol.flags & (SymbolFlags.Alias | SymbolFlags.Type | SymbolFlags.Namespace)) { - // export type T; - may have meant export type { T }? - relatedInformation.push(createDiagnosticForNode(node, Diagnostics.Did_you_mean_0, `export type { ${unescapeLeadingUnderscores(node.name.escapedText)} }`)); - } + const relatedInformation: DiagnosticRelatedInformation[] = []; + if (isTypeAliasDeclaration(node) && nodeIsMissing(node.type) && hasSyntacticModifier(node, ModifierFlags.Export) && symbol.flags & (SymbolFlags.Alias | SymbolFlags.Type | SymbolFlags.Namespace)) { + // export type T; - may have meant export type { T }? + relatedInformation.push(createDiagnosticForNode(node, Diagnostics.Did_you_mean_0, `export type { ${unescapeLeadingUnderscores(node.name.escapedText)} }`)); + } - const declarationName = getNameOfDeclaration(node) || node; - forEach(symbol.declarations, (declaration, index) => { - const decl = getNameOfDeclaration(declaration) || declaration; - const diag = createDiagnosticForNode(decl, message, messageNeedsName ? getDisplayName(declaration) : undefined); - file.bindDiagnostics.push( - multipleDefaultExports ? addRelatedInfo(diag, createDiagnosticForNode(declarationName, index === 0 ? Diagnostics.Another_export_default_is_here : Diagnostics.and_here)) : diag - ); - if (multipleDefaultExports) { - relatedInformation.push(createDiagnosticForNode(decl, Diagnostics.The_first_export_default_is_here)); - } - }); + const declarationName = getNameOfDeclaration(node) || node; + forEach(symbol.declarations, (declaration, index) => { + const decl = getNameOfDeclaration(declaration) || declaration; + const diag = createDiagnosticForNode(decl, message, messageNeedsName ? getDisplayName(declaration) : undefined); + file.bindDiagnostics.push( + multipleDefaultExports ? addRelatedInfo(diag, createDiagnosticForNode(declarationName, index === 0 ? Diagnostics.Another_export_default_is_here : Diagnostics.and_here)) : diag + ); + if (multipleDefaultExports) { + relatedInformation.push(createDiagnosticForNode(decl, Diagnostics.The_first_export_default_is_here)); + } + }); - const diag = createDiagnosticForNode(declarationName, message, messageNeedsName ? getDisplayName(node) : undefined); - file.bindDiagnostics.push(addRelatedInfo(diag, ...relatedInformation)); + const diag = createDiagnosticForNode(declarationName, message, messageNeedsName ? getDisplayName(node) : undefined); + file.bindDiagnostics.push(addRelatedInfo(diag, ...relatedInformation)); - symbol = createSymbol(SymbolFlags.None, name); - } + symbol = createSymbol(SymbolFlags.None, name); } } + } + + addDeclarationToSymbol(symbol, node, includes); + if (symbol.parent) { + Debug.assert(symbol.parent === parent, "Existing symbol parent should match new one"); + } + else { + symbol.parent = parent; + } + + return symbol; + } - addDeclarationToSymbol(symbol, node, includes); - if (symbol.parent) { - Debug.assert(symbol.parent === parent, "Existing symbol parent should match new one"); + function declareModuleMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol { + const hasExportModifier = !!(getCombinedModifierFlags(node) & ModifierFlags.Export) || jsdocTreatAsExported(node); + if (symbolFlags & SymbolFlags.Alias) { + if (node.kind === SyntaxKind.ExportSpecifier || (node.kind === SyntaxKind.ImportEqualsDeclaration && hasExportModifier)) { + return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); } else { - symbol.parent = parent; + return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } - - return symbol; } - - function declareModuleMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol { - const hasExportModifier = !!(getCombinedModifierFlags(node) & ModifierFlags.Export) || jsdocTreatAsExported(node); - if (symbolFlags & SymbolFlags.Alias) { - if (node.kind === SyntaxKind.ExportSpecifier || (node.kind === SyntaxKind.ImportEqualsDeclaration && hasExportModifier)) { - return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); - } - else { - return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } + else { + // Exported module members are given 2 symbols: A local symbol that is classified with an ExportValue flag, + // and an associated export symbol with all the correct flags set on it. There are 2 main reasons: + // + // 1. We treat locals and exports of the same name as mutually exclusive within a container. + // That means the binder will issue a Duplicate Identifier error if you mix locals and exports + // with the same name in the same container. + // TODO: Make this a more specific error and decouple it from the exclusion logic. + // 2. When we checkIdentifier in the checker, we set its resolved symbol to the local symbol, + // but return the export symbol (by calling getExportSymbolOfValueSymbolIfExported). That way + // when the emitter comes back to it, it knows not to qualify the name if it was found in a containing scope. + + // NOTE: Nested ambient modules always should go to to 'locals' table to prevent their automatic merge + // during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation + // and this case is specially handled. Module augmentations should only be merged with original module definition + // and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed. + if (isJSDocTypeAlias(node)) Debug.assert(isInJSFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file. + if (!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) { + if (!container.locals || (hasSyntacticModifier(node, ModifierFlags.Default) && !getDeclarationName(node))) { + return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); // No local symbol for an unnamed default! + } + const exportKind = symbolFlags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0; + const local = declareSymbol(container.locals, /*parent*/ undefined, node, exportKind, symbolExcludes); + local.exportSymbol = declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); + node.localSymbol = local; + return local; } else { - // Exported module members are given 2 symbols: A local symbol that is classified with an ExportValue flag, - // and an associated export symbol with all the correct flags set on it. There are 2 main reasons: - // - // 1. We treat locals and exports of the same name as mutually exclusive within a container. - // That means the binder will issue a Duplicate Identifier error if you mix locals and exports - // with the same name in the same container. - // TODO: Make this a more specific error and decouple it from the exclusion logic. - // 2. When we checkIdentifier in the checker, we set its resolved symbol to the local symbol, - // but return the export symbol (by calling getExportSymbolOfValueSymbolIfExported). That way - // when the emitter comes back to it, it knows not to qualify the name if it was found in a containing scope. - - // NOTE: Nested ambient modules always should go to to 'locals' table to prevent their automatic merge - // during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation - // and this case is specially handled. Module augmentations should only be merged with original module definition - // and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed. - if (isJSDocTypeAlias(node)) Debug.assert(isInJSFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file. - if (!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) { - if (!container.locals || (hasSyntacticModifier(node, ModifierFlags.Default) && !getDeclarationName(node))) { - return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); // No local symbol for an unnamed default! - } - const exportKind = symbolFlags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0; - const local = declareSymbol(container.locals, /*parent*/ undefined, node, exportKind, symbolExcludes); - local.exportSymbol = declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); - node.localSymbol = local; - return local; - } - else { - return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } + return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } } + } - function jsdocTreatAsExported(node: Node) { - if (node.parent && isModuleDeclaration(node)) { - node = node.parent; - } - if (!isJSDocTypeAlias(node)) return false; - // jsdoc typedef handling is a bit of a doozy, but to summarize, treat the typedef as exported if: - // 1. It has an explicit name (since by default typedefs are always directly exported, either at the top level or in a container), or - if (!isJSDocEnumTag(node) && !!node.fullName) return true; - // 2. The thing a nameless typedef pulls its name from is implicitly a direct export (either by assignment or actual export flag). - const declName = getNameOfDeclaration(node); - if (!declName) return false; - if (isPropertyAccessEntityNameExpression(declName.parent) && isTopLevelNamespaceAssignment(declName.parent)) return true; - if (isDeclaration(declName.parent) && getCombinedModifierFlags(declName.parent) & ModifierFlags.Export) return true; - // This could potentially be simplified by having `delayedBindJSDocTypedefTag` pass in an override for `hasExportModifier`, since it should - // already have calculated and branched on most of this. - return false; - } + function jsdocTreatAsExported(node: Node) { + if (node.parent && isModuleDeclaration(node)) { + node = node.parent; + } + if (!isJSDocTypeAlias(node)) return false; + // jsdoc typedef handling is a bit of a doozy, but to summarize, treat the typedef as exported if: + // 1. It has an explicit name (since by default typedefs are always directly exported, either at the top level or in a container), or + if (!isJSDocEnumTag(node) && !!node.fullName) return true; + // 2. The thing a nameless typedef pulls its name from is implicitly a direct export (either by assignment or actual export flag). + const declName = getNameOfDeclaration(node); + if (!declName) return false; + if (isPropertyAccessEntityNameExpression(declName.parent) && isTopLevelNamespaceAssignment(declName.parent)) return true; + if (isDeclaration(declName.parent) && getCombinedModifierFlags(declName.parent) & ModifierFlags.Export) return true; + // This could potentially be simplified by having `delayedBindJSDocTypedefTag` pass in an override for `hasExportModifier`, since it should + // already have calculated and branched on most of this. + return false; + } - // All container nodes are kept on a linked list in declaration order. This list is used by - // the getLocalNameOfContainer function in the type checker to validate that the local name - // used for a container is unique. - function bindContainer(node: Mutable, containerFlags: ContainerFlags) { - // Before we recurse into a node's children, we first save the existing parent, container - // and block-container. Then after we pop out of processing the children, we restore - // these saved values. - const saveContainer = container; - const saveThisParentContainer = thisParentContainer; - const savedBlockScopeContainer = blockScopeContainer; - - // Depending on what kind of node this is, we may have to adjust the current container - // and block-container. If the current node is a container, then it is automatically - // considered the current block-container as well. Also, for containers that we know - // may contain locals, we eagerly initialize the .locals field. We do this because - // it's highly likely that the .locals will be needed to place some child in (for example, - // a parameter, or variable declaration). - // - // However, we do not proactively create the .locals for block-containers because it's - // totally normal and common for block-containers to never actually have a block-scoped - // variable in them. We don't want to end up allocating an object for every 'block' we - // run into when most of them won't be necessary. - // - // Finally, if this is a block-container, then we clear out any existing .locals object - // it may contain within it. This happens in incremental scenarios. Because we can be - // reusing a node from a previous compilation, that node may have had 'locals' created - // for it. We must clear this so we don't accidentally move any stale data forward from - // a previous compilation. - if (containerFlags & ContainerFlags.IsContainer) { - if (node.kind !== SyntaxKind.ArrowFunction) { - thisParentContainer = container; - } - container = blockScopeContainer = node; - if (containerFlags & ContainerFlags.HasLocals) { - container.locals = createSymbolTable(); - } - addToContainerChain(container); - } - else if (containerFlags & ContainerFlags.IsBlockScopedContainer) { - blockScopeContainer = node; - blockScopeContainer.locals = undefined; - } - if (containerFlags & ContainerFlags.IsControlFlowContainer) { - const saveCurrentFlow = currentFlow; - const saveBreakTarget = currentBreakTarget; - const saveContinueTarget = currentContinueTarget; - const saveReturnTarget = currentReturnTarget; - const saveExceptionTarget = currentExceptionTarget; - const saveActiveLabelList = activeLabelList; - const saveHasExplicitReturn = hasExplicitReturn; - const isIIFE = containerFlags & ContainerFlags.IsFunctionExpression && !hasSyntacticModifier(node, ModifierFlags.Async) && - !(node as FunctionLikeDeclaration).asteriskToken && !!getImmediatelyInvokedFunctionExpression(node); - // A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave - // similarly to break statements that exit to a label just past the statement body. - if (!isIIFE) { - currentFlow = initFlowNode({ flags: FlowFlags.Start }); - if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) { - currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; - } - } - // We create a return control flow graph for IIFEs and constructors. For constructors - // we use the return control flow graph in strict property initialization checks. - currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor || node.kind === SyntaxKind.ClassStaticBlockDeclaration || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined; - currentExceptionTarget = undefined; - currentBreakTarget = undefined; - currentContinueTarget = undefined; - activeLabelList = undefined; - hasExplicitReturn = false; - bindChildren(node); - // Reset all reachability check related flags on node (for incremental scenarios) - node.flags &= ~NodeFlags.ReachabilityAndEmitFlags; - if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).body)) { - node.flags |= NodeFlags.HasImplicitReturn; - if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn; - (node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).endFlowNode = currentFlow; - } - if (node.kind === SyntaxKind.SourceFile) { - node.flags |= emitFlags; - (node as SourceFile).endFlowNode = currentFlow; - } - - if (currentReturnTarget) { - addAntecedent(currentReturnTarget, currentFlow); - currentFlow = finishFlowLabel(currentReturnTarget); - if (node.kind === SyntaxKind.Constructor || node.kind === SyntaxKind.ClassStaticBlockDeclaration || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression))) { - (node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).returnFlowNode = currentFlow; - } - } - if (!isIIFE) { - currentFlow = saveCurrentFlow; + // All container nodes are kept on a linked list in declaration order. This list is used by + // the getLocalNameOfContainer function in the type checker to validate that the local name + // used for a container is unique. + function bindContainer(node: Mutable, containerFlags: ContainerFlags) { + // Before we recurse into a node's children, we first save the existing parent, container + // and block-container. Then after we pop out of processing the children, we restore + // these saved values. + const saveContainer = container; + const saveThisParentContainer = thisParentContainer; + const savedBlockScopeContainer = blockScopeContainer; + + // Depending on what kind of node this is, we may have to adjust the current container + // and block-container. If the current node is a container, then it is automatically + // considered the current block-container as well. Also, for containers that we know + // may contain locals, we eagerly initialize the .locals field. We do this because + // it's highly likely that the .locals will be needed to place some child in (for example, + // a parameter, or variable declaration). + // + // However, we do not proactively create the .locals for block-containers because it's + // totally normal and common for block-containers to never actually have a block-scoped + // variable in them. We don't want to end up allocating an object for every 'block' we + // run into when most of them won't be necessary. + // + // Finally, if this is a block-container, then we clear out any existing .locals object + // it may contain within it. This happens in incremental scenarios. Because we can be + // reusing a node from a previous compilation, that node may have had 'locals' created + // for it. We must clear this so we don't accidentally move any stale data forward from + // a previous compilation. + if (containerFlags & ContainerFlags.IsContainer) { + if (node.kind !== SyntaxKind.ArrowFunction) { + thisParentContainer = container; + } + container = blockScopeContainer = node; + if (containerFlags & ContainerFlags.HasLocals) { + container.locals = createSymbolTable(); + } + addToContainerChain(container); + } + else if (containerFlags & ContainerFlags.IsBlockScopedContainer) { + blockScopeContainer = node; + blockScopeContainer.locals = undefined; + } + if (containerFlags & ContainerFlags.IsControlFlowContainer) { + const saveCurrentFlow = currentFlow; + const saveBreakTarget = currentBreakTarget; + const saveContinueTarget = currentContinueTarget; + const saveReturnTarget = currentReturnTarget; + const saveExceptionTarget = currentExceptionTarget; + const saveActiveLabelList = activeLabelList; + const saveHasExplicitReturn = hasExplicitReturn; + const isIIFE = containerFlags & ContainerFlags.IsFunctionExpression && !hasSyntacticModifier(node, ModifierFlags.Async) && + !(node as FunctionLikeDeclaration).asteriskToken && !!getImmediatelyInvokedFunctionExpression(node); + // A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave + // similarly to break statements that exit to a label just past the statement body. + if (!isIIFE) { + currentFlow = initFlowNode({ flags: FlowFlags.Start }); + if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) { + currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; } - currentBreakTarget = saveBreakTarget; - currentContinueTarget = saveContinueTarget; - currentReturnTarget = saveReturnTarget; - currentExceptionTarget = saveExceptionTarget; - activeLabelList = saveActiveLabelList; - hasExplicitReturn = saveHasExplicitReturn; } - else if (containerFlags & ContainerFlags.IsInterface) { - seenThisKeyword = false; - bindChildren(node); - node.flags = seenThisKeyword ? node.flags | NodeFlags.ContainsThis : node.flags & ~NodeFlags.ContainsThis; + // We create a return control flow graph for IIFEs and constructors. For constructors + // we use the return control flow graph in strict property initialization checks. + currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor || node.kind === SyntaxKind.ClassStaticBlockDeclaration || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined; + currentExceptionTarget = undefined; + currentBreakTarget = undefined; + currentContinueTarget = undefined; + activeLabelList = undefined; + hasExplicitReturn = false; + bindChildren(node); + // Reset all reachability check related flags on node (for incremental scenarios) + node.flags &= ~NodeFlags.ReachabilityAndEmitFlags; + if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).body)) { + node.flags |= NodeFlags.HasImplicitReturn; + if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn; + (node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).endFlowNode = currentFlow; } - else { - bindChildren(node); + if (node.kind === SyntaxKind.SourceFile) { + node.flags |= emitFlags; + (node as SourceFile).endFlowNode = currentFlow; } - container = saveContainer; - thisParentContainer = saveThisParentContainer; - blockScopeContainer = savedBlockScopeContainer; + if (currentReturnTarget) { + addAntecedent(currentReturnTarget, currentFlow); + currentFlow = finishFlowLabel(currentReturnTarget); + if (node.kind === SyntaxKind.Constructor || node.kind === SyntaxKind.ClassStaticBlockDeclaration || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression))) { + (node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).returnFlowNode = currentFlow; + } + } + if (!isIIFE) { + currentFlow = saveCurrentFlow; + } + currentBreakTarget = saveBreakTarget; + currentContinueTarget = saveContinueTarget; + currentReturnTarget = saveReturnTarget; + currentExceptionTarget = saveExceptionTarget; + activeLabelList = saveActiveLabelList; + hasExplicitReturn = saveHasExplicitReturn; } - - function bindEachFunctionsFirst(nodes: NodeArray | undefined): void { - bindEach(nodes, n => n.kind === SyntaxKind.FunctionDeclaration ? bind(n) : undefined); - bindEach(nodes, n => n.kind !== SyntaxKind.FunctionDeclaration ? bind(n) : undefined); + else if (containerFlags & ContainerFlags.IsInterface) { + seenThisKeyword = false; + bindChildren(node); + node.flags = seenThisKeyword ? node.flags | NodeFlags.ContainsThis : node.flags & ~NodeFlags.ContainsThis; + } + else { + bindChildren(node); } - function bindEach(nodes: NodeArray | undefined, bindFunction: (node: Node) => void = bind): void { - if (nodes === undefined) { - return; - } + container = saveContainer; + thisParentContainer = saveThisParentContainer; + blockScopeContainer = savedBlockScopeContainer; + } - forEach(nodes, bindFunction); - } + function bindEachFunctionsFirst(nodes: NodeArray | undefined): void { + bindEach(nodes, n => n.kind === SyntaxKind.FunctionDeclaration ? bind(n) : undefined); + bindEach(nodes, n => n.kind !== SyntaxKind.FunctionDeclaration ? bind(n) : undefined); + } - function bindEachChild(node: Node) { - forEachChild(node, bind, bindEach); + function bindEach(nodes: NodeArray | undefined, bindFunction: (node: Node) => void = bind): void { + if (nodes === undefined) { + return; } - function bindChildren(node: Node): void { - const saveInAssignmentPattern = inAssignmentPattern; - // Most nodes aren't valid in an assignment pattern, so we clear the value here - // and set it before we descend into nodes that could actually be part of an assignment pattern. - inAssignmentPattern = false; - if (checkUnreachable(node)) { - bindEachChild(node); - bindJSDoc(node); - inAssignmentPattern = saveInAssignmentPattern; - return; - } - if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && !options.allowUnreachableCode) { - node.flowNode = currentFlow; - } - switch (node.kind) { - case SyntaxKind.WhileStatement: - bindWhileStatement(node as WhileStatement); - break; - case SyntaxKind.DoStatement: - bindDoStatement(node as DoStatement); - break; - case SyntaxKind.ForStatement: - bindForStatement(node as ForStatement); - break; - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - bindForInOrForOfStatement(node as ForInOrOfStatement); - break; - case SyntaxKind.IfStatement: - bindIfStatement(node as IfStatement); - break; - case SyntaxKind.ReturnStatement: - case SyntaxKind.ThrowStatement: - bindReturnOrThrow(node as ReturnStatement | ThrowStatement); - break; - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - bindBreakOrContinueStatement(node as BreakOrContinueStatement); - break; - case SyntaxKind.TryStatement: - bindTryStatement(node as TryStatement); - break; - case SyntaxKind.SwitchStatement: - bindSwitchStatement(node as SwitchStatement); - break; - case SyntaxKind.CaseBlock: - bindCaseBlock(node as CaseBlock); - break; - case SyntaxKind.CaseClause: - bindCaseClause(node as CaseClause); - break; - case SyntaxKind.ExpressionStatement: - bindExpressionStatement(node as ExpressionStatement); - break; - case SyntaxKind.LabeledStatement: - bindLabeledStatement(node as LabeledStatement); - break; - case SyntaxKind.PrefixUnaryExpression: - bindPrefixUnaryExpressionFlow(node as PrefixUnaryExpression); - break; - case SyntaxKind.PostfixUnaryExpression: - bindPostfixUnaryExpressionFlow(node as PostfixUnaryExpression); - break; - case SyntaxKind.BinaryExpression: - if (isDestructuringAssignment(node)) { - // Carry over whether we are in an assignment pattern to - // binary expressions that could actually be an initializer - inAssignmentPattern = saveInAssignmentPattern; - bindDestructuringAssignmentFlow(node); - return; - } - bindBinaryExpressionFlow(node as BinaryExpression); - break; - case SyntaxKind.DeleteExpression: - bindDeleteExpressionFlow(node as DeleteExpression); - break; - case SyntaxKind.ConditionalExpression: - bindConditionalExpressionFlow(node as ConditionalExpression); - break; - case SyntaxKind.VariableDeclaration: - bindVariableDeclarationFlow(node as VariableDeclaration); - break; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - bindAccessExpressionFlow(node as AccessExpression); - break; - case SyntaxKind.CallExpression: - bindCallExpressionFlow(node as CallExpression); - break; - case SyntaxKind.NonNullExpression: - bindNonNullExpressionFlow(node as NonNullExpression); - break; - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag); - break; - // In source files and blocks, bind functions first to match hoisting that occurs at runtime - case SyntaxKind.SourceFile: { - bindEachFunctionsFirst((node as SourceFile).statements); - bind((node as SourceFile).endOfFileToken); - break; - } - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - bindEachFunctionsFirst((node as Block).statements); - break; - case SyntaxKind.BindingElement: - bindBindingElementFlow(node as BindingElement); - break; - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.SpreadElement: - // Carry over whether we are in an assignment pattern of Object and Array literals - // as well as their children that are valid assignment targets. - inAssignmentPattern = saveInAssignmentPattern; - // falls through - default: - bindEachChild(node); - break; - } + forEach(nodes, bindFunction); + } + + function bindEachChild(node: Node) { + forEachChild(node, bind, bindEach); + } + + function bindChildren(node: Node): void { + const saveInAssignmentPattern = inAssignmentPattern; + // Most nodes aren't valid in an assignment pattern, so we clear the value here + // and set it before we descend into nodes that could actually be part of an assignment pattern. + inAssignmentPattern = false; + if (checkUnreachable(node)) { + bindEachChild(node); bindJSDoc(node); inAssignmentPattern = saveInAssignmentPattern; + return; } - - function isNarrowingExpression(expr: Expression): boolean { - switch (expr.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PrivateIdentifier: - case SyntaxKind.ThisKeyword: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return containsNarrowableReference(expr); - case SyntaxKind.CallExpression: - return hasNarrowableArgument(expr as CallExpression); - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.NonNullExpression: - return isNarrowingExpression((expr as ParenthesizedExpression | NonNullExpression).expression); - case SyntaxKind.BinaryExpression: - return isNarrowingBinaryExpression(expr as BinaryExpression); - case SyntaxKind.PrefixUnaryExpression: - return (expr as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken && isNarrowingExpression((expr as PrefixUnaryExpression).operand); - case SyntaxKind.TypeOfExpression: - return isNarrowingExpression((expr as TypeOfExpression).expression); + if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && !options.allowUnreachableCode) { + node.flowNode = currentFlow; + } + switch (node.kind) { + case SyntaxKind.WhileStatement: + bindWhileStatement(node as WhileStatement); + break; + case SyntaxKind.DoStatement: + bindDoStatement(node as DoStatement); + break; + case SyntaxKind.ForStatement: + bindForStatement(node as ForStatement); + break; + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + bindForInOrForOfStatement(node as ForInOrOfStatement); + break; + case SyntaxKind.IfStatement: + bindIfStatement(node as IfStatement); + break; + case SyntaxKind.ReturnStatement: + case SyntaxKind.ThrowStatement: + bindReturnOrThrow(node as ReturnStatement | ThrowStatement); + break; + case SyntaxKind.BreakStatement: + case SyntaxKind.ContinueStatement: + bindBreakOrContinueStatement(node as BreakOrContinueStatement); + break; + case SyntaxKind.TryStatement: + bindTryStatement(node as TryStatement); + break; + case SyntaxKind.SwitchStatement: + bindSwitchStatement(node as SwitchStatement); + break; + case SyntaxKind.CaseBlock: + bindCaseBlock(node as CaseBlock); + break; + case SyntaxKind.CaseClause: + bindCaseClause(node as CaseClause); + break; + case SyntaxKind.ExpressionStatement: + bindExpressionStatement(node as ExpressionStatement); + break; + case SyntaxKind.LabeledStatement: + bindLabeledStatement(node as LabeledStatement); + break; + case SyntaxKind.PrefixUnaryExpression: + bindPrefixUnaryExpressionFlow(node as PrefixUnaryExpression); + break; + case SyntaxKind.PostfixUnaryExpression: + bindPostfixUnaryExpressionFlow(node as PostfixUnaryExpression); + break; + case SyntaxKind.BinaryExpression: + if (isDestructuringAssignment(node)) { + // Carry over whether we are in an assignment pattern to + // binary expressions that could actually be an initializer + inAssignmentPattern = saveInAssignmentPattern; + bindDestructuringAssignmentFlow(node); + return; + } + bindBinaryExpressionFlow(node as BinaryExpression); + break; + case SyntaxKind.DeleteExpression: + bindDeleteExpressionFlow(node as DeleteExpression); + break; + case SyntaxKind.ConditionalExpression: + bindConditionalExpressionFlow(node as ConditionalExpression); + break; + case SyntaxKind.VariableDeclaration: + bindVariableDeclarationFlow(node as VariableDeclaration); + break; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + bindAccessExpressionFlow(node as AccessExpression); + break; + case SyntaxKind.CallExpression: + bindCallExpressionFlow(node as CallExpression); + break; + case SyntaxKind.NonNullExpression: + bindNonNullExpressionFlow(node as NonNullExpression); + break; + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag); + break; + // In source files and blocks, bind functions first to match hoisting that occurs at runtime + case SyntaxKind.SourceFile: { + bindEachFunctionsFirst((node as SourceFile).statements); + bind((node as SourceFile).endOfFileToken); + break; } - return false; + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + bindEachFunctionsFirst((node as Block).statements); + break; + case SyntaxKind.BindingElement: + bindBindingElementFlow(node as BindingElement); + break; + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.SpreadElement: + // Carry over whether we are in an assignment pattern of Object and Array literals + // as well as their children that are valid assignment targets. + inAssignmentPattern = saveInAssignmentPattern; + // falls through + default: + bindEachChild(node); + break; } + bindJSDoc(node); + inAssignmentPattern = saveInAssignmentPattern; + } - function isNarrowableReference(expr: Expression): boolean { - return isDottedName(expr) - || (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) - || isBinaryExpression(expr) && expr.operatorToken.kind === SyntaxKind.CommaToken && isNarrowableReference(expr.right) - || isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression) - || isAssignmentExpression(expr) && isNarrowableReference(expr.left); + function isNarrowingExpression(expr: Expression): boolean { + switch (expr.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: + case SyntaxKind.ThisKeyword: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return containsNarrowableReference(expr); + case SyntaxKind.CallExpression: + return hasNarrowableArgument(expr as CallExpression); + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.NonNullExpression: + return isNarrowingExpression((expr as ParenthesizedExpression | NonNullExpression).expression); + case SyntaxKind.BinaryExpression: + return isNarrowingBinaryExpression(expr as BinaryExpression); + case SyntaxKind.PrefixUnaryExpression: + return (expr as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken && isNarrowingExpression((expr as PrefixUnaryExpression).operand); + case SyntaxKind.TypeOfExpression: + return isNarrowingExpression((expr as TypeOfExpression).expression); } + return false; + } - function containsNarrowableReference(expr: Expression): boolean { - return isNarrowableReference(expr) || isOptionalChain(expr) && containsNarrowableReference(expr.expression); - } + function isNarrowableReference(expr: Expression): boolean { + return isDottedName(expr) + || (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) + || isBinaryExpression(expr) && expr.operatorToken.kind === SyntaxKind.CommaToken && isNarrowableReference(expr.right) + || isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression) + || isAssignmentExpression(expr) && isNarrowableReference(expr.left); + } - function hasNarrowableArgument(expr: CallExpression) { - if (expr.arguments) { - for (const argument of expr.arguments) { - if (containsNarrowableReference(argument)) { - return true; - } + function containsNarrowableReference(expr: Expression): boolean { + return isNarrowableReference(expr) || isOptionalChain(expr) && containsNarrowableReference(expr.expression); + } + + function hasNarrowableArgument(expr: CallExpression) { + if (expr.arguments) { + for (const argument of expr.arguments) { + if (containsNarrowableReference(argument)) { + return true; } } - if (expr.expression.kind === SyntaxKind.PropertyAccessExpression && - containsNarrowableReference((expr.expression as PropertyAccessExpression).expression)) { - return true; - } - return false; } - - function isNarrowingTypeofOperands(expr1: Expression, expr2: Expression) { - return isTypeOfExpression(expr1) && isNarrowableOperand(expr1.expression) && isStringLiteralLike(expr2); - } - - function isNarrowingBinaryExpression(expr: BinaryExpression) { - switch (expr.operatorToken.kind) { - case SyntaxKind.EqualsToken: - case SyntaxKind.BarBarEqualsToken: - case SyntaxKind.AmpersandAmpersandEqualsToken: - case SyntaxKind.QuestionQuestionEqualsToken: - return containsNarrowableReference(expr.left); - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - return isNarrowableOperand(expr.left) || isNarrowableOperand(expr.right) || - isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right); - case SyntaxKind.InstanceOfKeyword: - return isNarrowableOperand(expr.left); - case SyntaxKind.InKeyword: - return isNarrowingExpression(expr.right); - case SyntaxKind.CommaToken: - return isNarrowingExpression(expr.right); - } - return false; + if (expr.expression.kind === SyntaxKind.PropertyAccessExpression && + containsNarrowableReference((expr.expression as PropertyAccessExpression).expression)) { + return true; } + return false; + } - function isNarrowableOperand(expr: Expression): boolean { - switch (expr.kind) { - case SyntaxKind.ParenthesizedExpression: - return isNarrowableOperand((expr as ParenthesizedExpression).expression); - case SyntaxKind.BinaryExpression: - switch ((expr as BinaryExpression).operatorToken.kind) { - case SyntaxKind.EqualsToken: - return isNarrowableOperand((expr as BinaryExpression).left); - case SyntaxKind.CommaToken: - return isNarrowableOperand((expr as BinaryExpression).right); - } - } - return containsNarrowableReference(expr); - } + function isNarrowingTypeofOperands(expr1: Expression, expr2: Expression) { + return isTypeOfExpression(expr1) && isNarrowableOperand(expr1.expression) && isStringLiteralLike(expr2); + } - function createBranchLabel(): FlowLabel { - return initFlowNode({ flags: FlowFlags.BranchLabel, antecedents: undefined }); + function isNarrowingBinaryExpression(expr: BinaryExpression) { + switch (expr.operatorToken.kind) { + case SyntaxKind.EqualsToken: + case SyntaxKind.BarBarEqualsToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: + case SyntaxKind.QuestionQuestionEqualsToken: + return containsNarrowableReference(expr.left); + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + return isNarrowableOperand(expr.left) || isNarrowableOperand(expr.right) || + isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right); + case SyntaxKind.InstanceOfKeyword: + return isNarrowableOperand(expr.left); + case SyntaxKind.InKeyword: + return isNarrowingExpression(expr.right); + case SyntaxKind.CommaToken: + return isNarrowingExpression(expr.right); } + return false; + } - function createLoopLabel(): FlowLabel { - return initFlowNode({ flags: FlowFlags.LoopLabel, antecedents: undefined }); + function isNarrowableOperand(expr: Expression): boolean { + switch (expr.kind) { + case SyntaxKind.ParenthesizedExpression: + return isNarrowableOperand((expr as ParenthesizedExpression).expression); + case SyntaxKind.BinaryExpression: + switch ((expr as BinaryExpression).operatorToken.kind) { + case SyntaxKind.EqualsToken: + return isNarrowableOperand((expr as BinaryExpression).left); + case SyntaxKind.CommaToken: + return isNarrowableOperand((expr as BinaryExpression).right); + } } + return containsNarrowableReference(expr); + } - function createReduceLabel(target: FlowLabel, antecedents: FlowNode[], antecedent: FlowNode): FlowReduceLabel { - return initFlowNode({ flags: FlowFlags.ReduceLabel, target, antecedents, antecedent }); - } + function createBranchLabel(): FlowLabel { + return initFlowNode({ flags: FlowFlags.BranchLabel, antecedents: undefined }); + } - function setFlowNodeReferenced(flow: FlowNode) { - // On first reference we set the Referenced flag, thereafter we set the Shared flag - flow.flags |= flow.flags & FlowFlags.Referenced ? FlowFlags.Shared : FlowFlags.Referenced; - } + function createLoopLabel(): FlowLabel { + return initFlowNode({ flags: FlowFlags.LoopLabel, antecedents: undefined }); + } - function addAntecedent(label: FlowLabel, antecedent: FlowNode): void { - if (!(antecedent.flags & FlowFlags.Unreachable) && !contains(label.antecedents, antecedent)) { - (label.antecedents || (label.antecedents = [])).push(antecedent); - setFlowNodeReferenced(antecedent); - } - } + function createReduceLabel(target: FlowLabel, antecedents: FlowNode[], antecedent: FlowNode): FlowReduceLabel { + return initFlowNode({ flags: FlowFlags.ReduceLabel, target, antecedents, antecedent }); + } - function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { - if (antecedent.flags & FlowFlags.Unreachable) { - return antecedent; - } - if (!expression) { - return flags & FlowFlags.TrueCondition ? antecedent : unreachableFlow; - } - if ((expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition || - expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) && - !isExpressionOfOptionalChainRoot(expression) && !isNullishCoalesce(expression.parent)) { - return unreachableFlow; - } - if (!isNarrowingExpression(expression)) { - return antecedent; - } - setFlowNodeReferenced(antecedent); - return initFlowNode({ flags, antecedent, node: expression }); - } + function setFlowNodeReferenced(flow: FlowNode) { + // On first reference we set the Referenced flag, thereafter we set the Shared flag + flow.flags |= flow.flags & FlowFlags.Referenced ? FlowFlags.Shared : FlowFlags.Referenced; + } - function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode { + function addAntecedent(label: FlowLabel, antecedent: FlowNode): void { + if (!(antecedent.flags & FlowFlags.Unreachable) && !contains(label.antecedents, antecedent)) { + (label.antecedents || (label.antecedents = [])).push(antecedent); setFlowNodeReferenced(antecedent); - return initFlowNode({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd }); } + } - function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement): FlowNode { - setFlowNodeReferenced(antecedent); - const result = initFlowNode({ flags, antecedent, node }); - if (currentExceptionTarget) { - addAntecedent(currentExceptionTarget, result); - } - return result; + function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { + if (antecedent.flags & FlowFlags.Unreachable) { + return antecedent; } - - function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode { - setFlowNodeReferenced(antecedent); - return initFlowNode({ flags: FlowFlags.Call, antecedent, node }); + if (!expression) { + return flags & FlowFlags.TrueCondition ? antecedent : unreachableFlow; } - - function finishFlowLabel(flow: FlowLabel): FlowNode { - const antecedents = flow.antecedents; - if (!antecedents) { - return unreachableFlow; - } - if (antecedents.length === 1) { - return antecedents[0]; - } - return flow; + if ((expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition || + expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) && + !isExpressionOfOptionalChainRoot(expression) && !isNullishCoalesce(expression.parent)) { + return unreachableFlow; } - - function isStatementCondition(node: Node) { - const parent = node.parent; - switch (parent.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.DoStatement: - return (parent as IfStatement | WhileStatement | DoStatement).expression === node; - case SyntaxKind.ForStatement: - case SyntaxKind.ConditionalExpression: - return (parent as ForStatement | ConditionalExpression).condition === node; - } - return false; + if (!isNarrowingExpression(expression)) { + return antecedent; } + setFlowNodeReferenced(antecedent); + return initFlowNode({ flags, antecedent, node: expression }); + } - function isLogicalExpression(node: Node) { - while (true) { - if (node.kind === SyntaxKind.ParenthesizedExpression) { - node = (node as ParenthesizedExpression).expression; - } - else if (node.kind === SyntaxKind.PrefixUnaryExpression && (node as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken) { - node = (node as PrefixUnaryExpression).operand; - } - else { - return node.kind === SyntaxKind.BinaryExpression && ( - (node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || - (node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken || - (node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken); - } - } - } + function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode { + setFlowNodeReferenced(antecedent); + return initFlowNode({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd }); + } - function isLogicalAssignmentExpression(node: Node) { - node = skipParentheses(node); - return isBinaryExpression(node) && isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind); + function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement): FlowNode { + setFlowNodeReferenced(antecedent); + const result = initFlowNode({ flags, antecedent, node }); + if (currentExceptionTarget) { + addAntecedent(currentExceptionTarget, result); } + return result; + } - function isTopLevelLogicalExpression(node: Node): boolean { - while (isParenthesizedExpression(node.parent) || - isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.ExclamationToken) { - node = node.parent; - } - return !isStatementCondition(node) && - !isLogicalAssignmentExpression(node.parent) && - !isLogicalExpression(node.parent) && - !(isOptionalChain(node.parent) && node.parent.expression === node); + function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode { + setFlowNodeReferenced(antecedent); + return initFlowNode({ flags: FlowFlags.Call, antecedent, node }); + } + + function finishFlowLabel(flow: FlowLabel): FlowNode { + const antecedents = flow.antecedents; + if (!antecedents) { + return unreachableFlow; + } + if (antecedents.length === 1) { + return antecedents[0]; } + return flow; + } - function doWithConditionalBranches(action: (value: T) => void, value: T, trueTarget: FlowLabel, falseTarget: FlowLabel) { - const savedTrueTarget = currentTrueTarget; - const savedFalseTarget = currentFalseTarget; - currentTrueTarget = trueTarget; - currentFalseTarget = falseTarget; - action(value); - currentTrueTarget = savedTrueTarget; - currentFalseTarget = savedFalseTarget; + function isStatementCondition(node: Node) { + const parent = node.parent; + switch (parent.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.DoStatement: + return (parent as IfStatement | WhileStatement | DoStatement).expression === node; + case SyntaxKind.ForStatement: + case SyntaxKind.ConditionalExpression: + return (parent as ForStatement | ConditionalExpression).condition === node; } + return false; + } - function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) { - doWithConditionalBranches(bind, node, trueTarget, falseTarget); - if (!node || !isLogicalAssignmentExpression(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) { - addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); - addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); + function isLogicalExpression(node: Node) { + while (true) { + if (node.kind === SyntaxKind.ParenthesizedExpression) { + node = (node as ParenthesizedExpression).expression; + } + else if (node.kind === SyntaxKind.PrefixUnaryExpression && (node as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken) { + node = (node as PrefixUnaryExpression).operand; + } + else { + return node.kind === SyntaxKind.BinaryExpression && ( + (node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || + (node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken || + (node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken); } } + } - function bindIterativeStatement(node: Statement, breakTarget: FlowLabel, continueTarget: FlowLabel): void { - const saveBreakTarget = currentBreakTarget; - const saveContinueTarget = currentContinueTarget; - currentBreakTarget = breakTarget; - currentContinueTarget = continueTarget; - bind(node); - currentBreakTarget = saveBreakTarget; - currentContinueTarget = saveContinueTarget; + function isLogicalAssignmentExpression(node: Node) { + node = skipParentheses(node); + return isBinaryExpression(node) && isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind); + } + + function isTopLevelLogicalExpression(node: Node): boolean { + while (isParenthesizedExpression(node.parent) || + isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.ExclamationToken) { + node = node.parent; } + return !isStatementCondition(node) && + !isLogicalAssignmentExpression(node.parent) && + !isLogicalExpression(node.parent) && + !(isOptionalChain(node.parent) && node.parent.expression === node); + } - function setContinueTarget(node: Node, target: FlowLabel) { - let label = activeLabelList; - while (label && node.parent.kind === SyntaxKind.LabeledStatement) { - label.continueTarget = target; - label = label.next; - node = node.parent; - } - return target; - } - - function bindWhileStatement(node: WhileStatement): void { - const preWhileLabel = setContinueTarget(node, createLoopLabel()); - const preBodyLabel = createBranchLabel(); - const postWhileLabel = createBranchLabel(); - addAntecedent(preWhileLabel, currentFlow); - currentFlow = preWhileLabel; - bindCondition(node.expression, preBodyLabel, postWhileLabel); - currentFlow = finishFlowLabel(preBodyLabel); - bindIterativeStatement(node.statement, postWhileLabel, preWhileLabel); - addAntecedent(preWhileLabel, currentFlow); - currentFlow = finishFlowLabel(postWhileLabel); - } - - function bindDoStatement(node: DoStatement): void { - const preDoLabel = createLoopLabel(); - const preConditionLabel = setContinueTarget(node, createBranchLabel()); - const postDoLabel = createBranchLabel(); - addAntecedent(preDoLabel, currentFlow); - currentFlow = preDoLabel; - bindIterativeStatement(node.statement, postDoLabel, preConditionLabel); - addAntecedent(preConditionLabel, currentFlow); - currentFlow = finishFlowLabel(preConditionLabel); - bindCondition(node.expression, preDoLabel, postDoLabel); - currentFlow = finishFlowLabel(postDoLabel); - } - - function bindForStatement(node: ForStatement): void { - const preLoopLabel = setContinueTarget(node, createLoopLabel()); - const preBodyLabel = createBranchLabel(); - const postLoopLabel = createBranchLabel(); - bind(node.initializer); - addAntecedent(preLoopLabel, currentFlow); - currentFlow = preLoopLabel; - bindCondition(node.condition, preBodyLabel, postLoopLabel); - currentFlow = finishFlowLabel(preBodyLabel); - bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel); - bind(node.incrementor); - addAntecedent(preLoopLabel, currentFlow); - currentFlow = finishFlowLabel(postLoopLabel); - } - - function bindForInOrForOfStatement(node: ForInOrOfStatement): void { - const preLoopLabel = setContinueTarget(node, createLoopLabel()); - const postLoopLabel = createBranchLabel(); - bind(node.expression); - addAntecedent(preLoopLabel, currentFlow); - currentFlow = preLoopLabel; - if (node.kind === SyntaxKind.ForOfStatement) { - bind(node.awaitModifier); - } - addAntecedent(postLoopLabel, currentFlow); - bind(node.initializer); - if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) { - bindAssignmentTargetFlow(node.initializer); - } - bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel); - addAntecedent(preLoopLabel, currentFlow); - currentFlow = finishFlowLabel(postLoopLabel); + function doWithConditionalBranches(action: (value: T) => void, value: T, trueTarget: FlowLabel, falseTarget: FlowLabel) { + const savedTrueTarget = currentTrueTarget; + const savedFalseTarget = currentFalseTarget; + currentTrueTarget = trueTarget; + currentFalseTarget = falseTarget; + action(value); + currentTrueTarget = savedTrueTarget; + currentFalseTarget = savedFalseTarget; + } + + function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) { + doWithConditionalBranches(bind, node, trueTarget, falseTarget); + if (!node || !isLogicalAssignmentExpression(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) { + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); } + } - function bindIfStatement(node: IfStatement): void { - const thenLabel = createBranchLabel(); - const elseLabel = createBranchLabel(); - const postIfLabel = createBranchLabel(); - bindCondition(node.expression, thenLabel, elseLabel); - currentFlow = finishFlowLabel(thenLabel); - bind(node.thenStatement); - addAntecedent(postIfLabel, currentFlow); - currentFlow = finishFlowLabel(elseLabel); - bind(node.elseStatement); - addAntecedent(postIfLabel, currentFlow); - currentFlow = finishFlowLabel(postIfLabel); + function bindIterativeStatement(node: Statement, breakTarget: FlowLabel, continueTarget: FlowLabel): void { + const saveBreakTarget = currentBreakTarget; + const saveContinueTarget = currentContinueTarget; + currentBreakTarget = breakTarget; + currentContinueTarget = continueTarget; + bind(node); + currentBreakTarget = saveBreakTarget; + currentContinueTarget = saveContinueTarget; + } + + function setContinueTarget(node: Node, target: FlowLabel) { + let label = activeLabelList; + while (label && node.parent.kind === SyntaxKind.LabeledStatement) { + label.continueTarget = target; + label = label.next; + node = node.parent; } + return target; + } - function bindReturnOrThrow(node: ReturnStatement | ThrowStatement): void { - bind(node.expression); - if (node.kind === SyntaxKind.ReturnStatement) { - hasExplicitReturn = true; - if (currentReturnTarget) { - addAntecedent(currentReturnTarget, currentFlow); - } + function bindWhileStatement(node: WhileStatement): void { + const preWhileLabel = setContinueTarget(node, createLoopLabel()); + const preBodyLabel = createBranchLabel(); + const postWhileLabel = createBranchLabel(); + addAntecedent(preWhileLabel, currentFlow); + currentFlow = preWhileLabel; + bindCondition(node.expression, preBodyLabel, postWhileLabel); + currentFlow = finishFlowLabel(preBodyLabel); + bindIterativeStatement(node.statement, postWhileLabel, preWhileLabel); + addAntecedent(preWhileLabel, currentFlow); + currentFlow = finishFlowLabel(postWhileLabel); + } + + function bindDoStatement(node: DoStatement): void { + const preDoLabel = createLoopLabel(); + const preConditionLabel = setContinueTarget(node, createBranchLabel()); + const postDoLabel = createBranchLabel(); + addAntecedent(preDoLabel, currentFlow); + currentFlow = preDoLabel; + bindIterativeStatement(node.statement, postDoLabel, preConditionLabel); + addAntecedent(preConditionLabel, currentFlow); + currentFlow = finishFlowLabel(preConditionLabel); + bindCondition(node.expression, preDoLabel, postDoLabel); + currentFlow = finishFlowLabel(postDoLabel); + } + + function bindForStatement(node: ForStatement): void { + const preLoopLabel = setContinueTarget(node, createLoopLabel()); + const preBodyLabel = createBranchLabel(); + const postLoopLabel = createBranchLabel(); + bind(node.initializer); + addAntecedent(preLoopLabel, currentFlow); + currentFlow = preLoopLabel; + bindCondition(node.condition, preBodyLabel, postLoopLabel); + currentFlow = finishFlowLabel(preBodyLabel); + bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel); + bind(node.incrementor); + addAntecedent(preLoopLabel, currentFlow); + currentFlow = finishFlowLabel(postLoopLabel); + } + + function bindForInOrForOfStatement(node: ForInOrOfStatement): void { + const preLoopLabel = setContinueTarget(node, createLoopLabel()); + const postLoopLabel = createBranchLabel(); + bind(node.expression); + addAntecedent(preLoopLabel, currentFlow); + currentFlow = preLoopLabel; + if (node.kind === SyntaxKind.ForOfStatement) { + bind(node.awaitModifier); + } + addAntecedent(postLoopLabel, currentFlow); + bind(node.initializer); + if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) { + bindAssignmentTargetFlow(node.initializer); + } + bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel); + addAntecedent(preLoopLabel, currentFlow); + currentFlow = finishFlowLabel(postLoopLabel); + } + + function bindIfStatement(node: IfStatement): void { + const thenLabel = createBranchLabel(); + const elseLabel = createBranchLabel(); + const postIfLabel = createBranchLabel(); + bindCondition(node.expression, thenLabel, elseLabel); + currentFlow = finishFlowLabel(thenLabel); + bind(node.thenStatement); + addAntecedent(postIfLabel, currentFlow); + currentFlow = finishFlowLabel(elseLabel); + bind(node.elseStatement); + addAntecedent(postIfLabel, currentFlow); + currentFlow = finishFlowLabel(postIfLabel); + } + + function bindReturnOrThrow(node: ReturnStatement | ThrowStatement): void { + bind(node.expression); + if (node.kind === SyntaxKind.ReturnStatement) { + hasExplicitReturn = true; + if (currentReturnTarget) { + addAntecedent(currentReturnTarget, currentFlow); } - currentFlow = unreachableFlow; } + currentFlow = unreachableFlow; + } - function findActiveLabel(name: __String) { - for (let label = activeLabelList; label; label = label.next) { - if (label.name === name) { - return label; - } + function findActiveLabel(name: __String) { + for (let label = activeLabelList; label; label = label.next) { + if (label.name === name) { + return label; } - return undefined; } + return undefined; + } - function bindBreakOrContinueFlow(node: BreakOrContinueStatement, breakTarget: FlowLabel | undefined, continueTarget: FlowLabel | undefined) { - const flowLabel = node.kind === SyntaxKind.BreakStatement ? breakTarget : continueTarget; - if (flowLabel) { - addAntecedent(flowLabel, currentFlow); - currentFlow = unreachableFlow; - } + function bindBreakOrContinueFlow(node: BreakOrContinueStatement, breakTarget: FlowLabel | undefined, continueTarget: FlowLabel | undefined) { + const flowLabel = node.kind === SyntaxKind.BreakStatement ? breakTarget : continueTarget; + if (flowLabel) { + addAntecedent(flowLabel, currentFlow); + currentFlow = unreachableFlow; } + } - function bindBreakOrContinueStatement(node: BreakOrContinueStatement): void { - bind(node.label); - if (node.label) { - const activeLabel = findActiveLabel(node.label.escapedText); - if (activeLabel) { - activeLabel.referenced = true; - bindBreakOrContinueFlow(node, activeLabel.breakTarget, activeLabel.continueTarget); - } - } - else { - bindBreakOrContinueFlow(node, currentBreakTarget, currentContinueTarget); + function bindBreakOrContinueStatement(node: BreakOrContinueStatement): void { + bind(node.label); + if (node.label) { + const activeLabel = findActiveLabel(node.label.escapedText); + if (activeLabel) { + activeLabel.referenced = true; + bindBreakOrContinueFlow(node, activeLabel.breakTarget, activeLabel.continueTarget); } } + else { + bindBreakOrContinueFlow(node, currentBreakTarget, currentContinueTarget); + } + } - function bindTryStatement(node: TryStatement): void { - // We conservatively assume that *any* code in the try block can cause an exception, but we only need - // to track code that causes mutations (because only mutations widen the possible control flow type of - // a variable). The exceptionLabel is the target label for control flows that result from exceptions. - // We add all mutation flow nodes as antecedents of this label such that we can analyze them as possible - // antecedents of the start of catch or finally blocks. Furthermore, we add the current control flow to - // represent exceptions that occur before any mutations. - const saveReturnTarget = currentReturnTarget; - const saveExceptionTarget = currentExceptionTarget; - const normalExitLabel = createBranchLabel(); - const returnLabel = createBranchLabel(); - let exceptionLabel = createBranchLabel(); - if (node.finallyBlock) { - currentReturnTarget = returnLabel; - } + function bindTryStatement(node: TryStatement): void { + // We conservatively assume that *any* code in the try block can cause an exception, but we only need + // to track code that causes mutations (because only mutations widen the possible control flow type of + // a variable). The exceptionLabel is the target label for control flows that result from exceptions. + // We add all mutation flow nodes as antecedents of this label such that we can analyze them as possible + // antecedents of the start of catch or finally blocks. Furthermore, we add the current control flow to + // represent exceptions that occur before any mutations. + const saveReturnTarget = currentReturnTarget; + const saveExceptionTarget = currentExceptionTarget; + const normalExitLabel = createBranchLabel(); + const returnLabel = createBranchLabel(); + let exceptionLabel = createBranchLabel(); + if (node.finallyBlock) { + currentReturnTarget = returnLabel; + } + addAntecedent(exceptionLabel, currentFlow); + currentExceptionTarget = exceptionLabel; + bind(node.tryBlock); + addAntecedent(normalExitLabel, currentFlow); + if (node.catchClause) { + // Start of catch clause is the target of exceptions from try block. + currentFlow = finishFlowLabel(exceptionLabel); + // The currentExceptionTarget now represents control flows from exceptions in the catch clause. + // Effectively, in a try-catch-finally, if an exception occurs in the try block, the catch block + // acts like a second try block. + exceptionLabel = createBranchLabel(); addAntecedent(exceptionLabel, currentFlow); currentExceptionTarget = exceptionLabel; - bind(node.tryBlock); + bind(node.catchClause); addAntecedent(normalExitLabel, currentFlow); - if (node.catchClause) { - // Start of catch clause is the target of exceptions from try block. - currentFlow = finishFlowLabel(exceptionLabel); - // The currentExceptionTarget now represents control flows from exceptions in the catch clause. - // Effectively, in a try-catch-finally, if an exception occurs in the try block, the catch block - // acts like a second try block. - exceptionLabel = createBranchLabel(); - addAntecedent(exceptionLabel, currentFlow); - currentExceptionTarget = exceptionLabel; - bind(node.catchClause); - addAntecedent(normalExitLabel, currentFlow); + } + currentReturnTarget = saveReturnTarget; + currentExceptionTarget = saveExceptionTarget; + if (node.finallyBlock) { + // Possible ways control can reach the finally block: + // 1) Normal completion of try block of a try-finally or try-catch-finally + // 2) Normal completion of catch block (following exception in try block) of a try-catch-finally + // 3) Return in try or catch block of a try-finally or try-catch-finally + // 4) Exception in try block of a try-finally + // 5) Exception in catch block of a try-catch-finally + // When analyzing a control flow graph that starts inside a finally block we want to consider all + // five possibilities above. However, when analyzing a control flow graph that starts outside (past) + // the finally block, we only want to consider the first two (if we're past a finally block then it + // must have completed normally). Likewise, when analyzing a control flow graph from return statements + // in try or catch blocks in an IIFE, we only want to consider the third. To make this possible, we + // inject a ReduceLabel node into the control flow graph. This node contains an alternate reduced + // set of antecedents for the pre-finally label. As control flow analysis passes by a ReduceLabel + // node, the pre-finally label is temporarily switched to the reduced antecedent set. + const finallyLabel = createBranchLabel(); + finallyLabel.antecedents = concatenate(concatenate(normalExitLabel.antecedents, exceptionLabel.antecedents), returnLabel.antecedents); + currentFlow = finallyLabel; + bind(node.finallyBlock); + if (currentFlow.flags & FlowFlags.Unreachable) { + // If the end of the finally block is unreachable, the end of the entire try statement is unreachable. + currentFlow = unreachableFlow; } - currentReturnTarget = saveReturnTarget; - currentExceptionTarget = saveExceptionTarget; - if (node.finallyBlock) { - // Possible ways control can reach the finally block: - // 1) Normal completion of try block of a try-finally or try-catch-finally - // 2) Normal completion of catch block (following exception in try block) of a try-catch-finally - // 3) Return in try or catch block of a try-finally or try-catch-finally - // 4) Exception in try block of a try-finally - // 5) Exception in catch block of a try-catch-finally - // When analyzing a control flow graph that starts inside a finally block we want to consider all - // five possibilities above. However, when analyzing a control flow graph that starts outside (past) - // the finally block, we only want to consider the first two (if we're past a finally block then it - // must have completed normally). Likewise, when analyzing a control flow graph from return statements - // in try or catch blocks in an IIFE, we only want to consider the third. To make this possible, we - // inject a ReduceLabel node into the control flow graph. This node contains an alternate reduced - // set of antecedents for the pre-finally label. As control flow analysis passes by a ReduceLabel - // node, the pre-finally label is temporarily switched to the reduced antecedent set. - const finallyLabel = createBranchLabel(); - finallyLabel.antecedents = concatenate(concatenate(normalExitLabel.antecedents, exceptionLabel.antecedents), returnLabel.antecedents); - currentFlow = finallyLabel; - bind(node.finallyBlock); - if (currentFlow.flags & FlowFlags.Unreachable) { - // If the end of the finally block is unreachable, the end of the entire try statement is unreachable. - currentFlow = unreachableFlow; + else { + // If we have an IIFE return target and return statements in the try or catch blocks, add a control + // flow that goes back through the finally block and back through only the return statements. + if (currentReturnTarget && returnLabel.antecedents) { + addAntecedent(currentReturnTarget, createReduceLabel(finallyLabel, returnLabel.antecedents, currentFlow)); } - else { - // If we have an IIFE return target and return statements in the try or catch blocks, add a control - // flow that goes back through the finally block and back through only the return statements. - if (currentReturnTarget && returnLabel.antecedents) { - addAntecedent(currentReturnTarget, createReduceLabel(finallyLabel, returnLabel.antecedents, currentFlow)); - } - // If we have an outer exception target (i.e. a containing try-finally or try-catch-finally), add a - // control flow that goes back through the finally blok and back through each possible exception source. - if (currentExceptionTarget && exceptionLabel.antecedents) { - addAntecedent(currentExceptionTarget, createReduceLabel(finallyLabel, exceptionLabel.antecedents, currentFlow)); - } - // If the end of the finally block is reachable, but the end of the try and catch blocks are not, - // convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should - // result in an unreachable current control flow. - currentFlow = normalExitLabel.antecedents ? createReduceLabel(finallyLabel, normalExitLabel.antecedents, currentFlow) : unreachableFlow; + // If we have an outer exception target (i.e. a containing try-finally or try-catch-finally), add a + // control flow that goes back through the finally blok and back through each possible exception source. + if (currentExceptionTarget && exceptionLabel.antecedents) { + addAntecedent(currentExceptionTarget, createReduceLabel(finallyLabel, exceptionLabel.antecedents, currentFlow)); } - } - else { - currentFlow = finishFlowLabel(normalExitLabel); + // If the end of the finally block is reachable, but the end of the try and catch blocks are not, + // convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should + // result in an unreachable current control flow. + currentFlow = normalExitLabel.antecedents ? createReduceLabel(finallyLabel, normalExitLabel.antecedents, currentFlow) : unreachableFlow; } } - - function bindSwitchStatement(node: SwitchStatement): void { - const postSwitchLabel = createBranchLabel(); - bind(node.expression); - const saveBreakTarget = currentBreakTarget; - const savePreSwitchCaseFlow = preSwitchCaseFlow; - currentBreakTarget = postSwitchLabel; - preSwitchCaseFlow = currentFlow; - bind(node.caseBlock); - addAntecedent(postSwitchLabel, currentFlow); - const hasDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause); - // We mark a switch statement as possibly exhaustive if it has no default clause and if all - // case clauses have unreachable end points (e.g. they all return). Note, we no longer need - // this property in control flow analysis, it's there only for backwards compatibility. - node.possiblyExhaustive = !hasDefault && !postSwitchLabel.antecedents; - if (!hasDefault) { - addAntecedent(postSwitchLabel, createFlowSwitchClause(preSwitchCaseFlow, node, 0, 0)); - } - currentBreakTarget = saveBreakTarget; - preSwitchCaseFlow = savePreSwitchCaseFlow; - currentFlow = finishFlowLabel(postSwitchLabel); + else { + currentFlow = finishFlowLabel(normalExitLabel); } + } - function bindCaseBlock(node: CaseBlock): void { - const clauses = node.clauses; - const isNarrowingSwitch = isNarrowingExpression(node.parent.expression); - let fallthroughFlow = unreachableFlow; - for (let i = 0; i < clauses.length; i++) { - const clauseStart = i; - while (!clauses[i].statements.length && i + 1 < clauses.length) { - bind(clauses[i]); - i++; - } - const preCaseLabel = createBranchLabel(); - addAntecedent(preCaseLabel, isNarrowingSwitch ? createFlowSwitchClause(preSwitchCaseFlow!, node.parent, clauseStart, i + 1) : preSwitchCaseFlow!); - addAntecedent(preCaseLabel, fallthroughFlow); - currentFlow = finishFlowLabel(preCaseLabel); - const clause = clauses[i]; - bind(clause); - fallthroughFlow = currentFlow; - if (!(currentFlow.flags & FlowFlags.Unreachable) && i !== clauses.length - 1 && options.noFallthroughCasesInSwitch) { - clause.fallthroughFlowNode = currentFlow; - } + function bindSwitchStatement(node: SwitchStatement): void { + const postSwitchLabel = createBranchLabel(); + bind(node.expression); + const saveBreakTarget = currentBreakTarget; + const savePreSwitchCaseFlow = preSwitchCaseFlow; + currentBreakTarget = postSwitchLabel; + preSwitchCaseFlow = currentFlow; + bind(node.caseBlock); + addAntecedent(postSwitchLabel, currentFlow); + const hasDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause); + // We mark a switch statement as possibly exhaustive if it has no default clause and if all + // case clauses have unreachable end points (e.g. they all return). Note, we no longer need + // this property in control flow analysis, it's there only for backwards compatibility. + node.possiblyExhaustive = !hasDefault && !postSwitchLabel.antecedents; + if (!hasDefault) { + addAntecedent(postSwitchLabel, createFlowSwitchClause(preSwitchCaseFlow, node, 0, 0)); + } + currentBreakTarget = saveBreakTarget; + preSwitchCaseFlow = savePreSwitchCaseFlow; + currentFlow = finishFlowLabel(postSwitchLabel); + } + + function bindCaseBlock(node: CaseBlock): void { + const clauses = node.clauses; + const isNarrowingSwitch = isNarrowingExpression(node.parent.expression); + let fallthroughFlow = unreachableFlow; + for (let i = 0; i < clauses.length; i++) { + const clauseStart = i; + while (!clauses[i].statements.length && i + 1 < clauses.length) { + bind(clauses[i]); + i++; + } + const preCaseLabel = createBranchLabel(); + addAntecedent(preCaseLabel, isNarrowingSwitch ? createFlowSwitchClause(preSwitchCaseFlow!, node.parent, clauseStart, i + 1) : preSwitchCaseFlow!); + addAntecedent(preCaseLabel, fallthroughFlow); + currentFlow = finishFlowLabel(preCaseLabel); + const clause = clauses[i]; + bind(clause); + fallthroughFlow = currentFlow; + if (!(currentFlow.flags & FlowFlags.Unreachable) && i !== clauses.length - 1 && options.noFallthroughCasesInSwitch) { + clause.fallthroughFlowNode = currentFlow; } } + } - function bindCaseClause(node: CaseClause): void { - const saveCurrentFlow = currentFlow; - currentFlow = preSwitchCaseFlow!; - bind(node.expression); - currentFlow = saveCurrentFlow; - bindEach(node.statements); - } + function bindCaseClause(node: CaseClause): void { + const saveCurrentFlow = currentFlow; + currentFlow = preSwitchCaseFlow!; + bind(node.expression); + currentFlow = saveCurrentFlow; + bindEach(node.statements); + } - function bindExpressionStatement(node: ExpressionStatement): void { - bind(node.expression); - maybeBindExpressionFlowIfCall(node.expression); - } + function bindExpressionStatement(node: ExpressionStatement): void { + bind(node.expression); + maybeBindExpressionFlowIfCall(node.expression); + } - function maybeBindExpressionFlowIfCall(node: Expression) { - // A top level or LHS of comma expression call expression with a dotted function name and at least one argument - // is potentially an assertion and is therefore included in the control flow. - if (node.kind === SyntaxKind.CallExpression) { - const call = node as CallExpression; - if (call.expression.kind !== SyntaxKind.SuperKeyword && isDottedName(call.expression)) { - currentFlow = createFlowCall(currentFlow, call); - } + function maybeBindExpressionFlowIfCall(node: Expression) { + // A top level or LHS of comma expression call expression with a dotted function name and at least one argument + // is potentially an assertion and is therefore included in the control flow. + if (node.kind === SyntaxKind.CallExpression) { + const call = node as CallExpression; + if (call.expression.kind !== SyntaxKind.SuperKeyword && isDottedName(call.expression)) { + currentFlow = createFlowCall(currentFlow, call); } } + } + + function bindLabeledStatement(node: LabeledStatement): void { + const postStatementLabel = createBranchLabel(); + activeLabelList = { + next: activeLabelList, + name: node.label.escapedText, + breakTarget: postStatementLabel, + continueTarget: undefined, + referenced: false + }; + bind(node.label); + bind(node.statement); + if (!activeLabelList.referenced && !options.allowUnusedLabels) { + errorOrSuggestionOnNode(unusedLabelIsError(options), node.label, Diagnostics.Unused_label); + } + activeLabelList = activeLabelList.next; + addAntecedent(postStatementLabel, currentFlow); + currentFlow = finishFlowLabel(postStatementLabel); + } - function bindLabeledStatement(node: LabeledStatement): void { - const postStatementLabel = createBranchLabel(); - activeLabelList = { - next: activeLabelList, - name: node.label.escapedText, - breakTarget: postStatementLabel, - continueTarget: undefined, - referenced: false - }; - bind(node.label); - bind(node.statement); - if (!activeLabelList.referenced && !options.allowUnusedLabels) { - errorOrSuggestionOnNode(unusedLabelIsError(options), node.label, Diagnostics.Unused_label); - } - activeLabelList = activeLabelList.next; - addAntecedent(postStatementLabel, currentFlow); - currentFlow = finishFlowLabel(postStatementLabel); + function bindDestructuringTargetFlow(node: Expression) { + if (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + bindAssignmentTargetFlow((node as BinaryExpression).left); } - - function bindDestructuringTargetFlow(node: Expression) { - if (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - bindAssignmentTargetFlow((node as BinaryExpression).left); - } - else { - bindAssignmentTargetFlow(node); - } + else { + bindAssignmentTargetFlow(node); } + } - function bindAssignmentTargetFlow(node: Expression) { - if (isNarrowableReference(node)) { - currentFlow = createFlowMutation(FlowFlags.Assignment, currentFlow, node); - } - else if (node.kind === SyntaxKind.ArrayLiteralExpression) { - for (const e of (node as ArrayLiteralExpression).elements) { - if (e.kind === SyntaxKind.SpreadElement) { - bindAssignmentTargetFlow((e as SpreadElement).expression); - } - else { - bindDestructuringTargetFlow(e); - } + function bindAssignmentTargetFlow(node: Expression) { + if (isNarrowableReference(node)) { + currentFlow = createFlowMutation(FlowFlags.Assignment, currentFlow, node); + } + else if (node.kind === SyntaxKind.ArrayLiteralExpression) { + for (const e of (node as ArrayLiteralExpression).elements) { + if (e.kind === SyntaxKind.SpreadElement) { + bindAssignmentTargetFlow((e as SpreadElement).expression); + } + else { + bindDestructuringTargetFlow(e); } } - else if (node.kind === SyntaxKind.ObjectLiteralExpression) { - for (const p of (node as ObjectLiteralExpression).properties) { - if (p.kind === SyntaxKind.PropertyAssignment) { - bindDestructuringTargetFlow(p.initializer); - } - else if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { - bindAssignmentTargetFlow(p.name); - } - else if (p.kind === SyntaxKind.SpreadAssignment) { - bindAssignmentTargetFlow(p.expression); - } + } + else if (node.kind === SyntaxKind.ObjectLiteralExpression) { + for (const p of (node as ObjectLiteralExpression).properties) { + if (p.kind === SyntaxKind.PropertyAssignment) { + bindDestructuringTargetFlow(p.initializer); + } + else if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { + bindAssignmentTargetFlow(p.name); + } + else if (p.kind === SyntaxKind.SpreadAssignment) { + bindAssignmentTargetFlow(p.expression); } } } + } - function bindLogicalLikeExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) { - const preRightLabel = createBranchLabel(); - if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) { - bindCondition(node.left, preRightLabel, falseTarget); - } - else { - bindCondition(node.left, trueTarget, preRightLabel); - } - currentFlow = finishFlowLabel(preRightLabel); - bind(node.operatorToken); + function bindLogicalLikeExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) { + const preRightLabel = createBranchLabel(); + if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) { + bindCondition(node.left, preRightLabel, falseTarget); + } + else { + bindCondition(node.left, trueTarget, preRightLabel); + } + currentFlow = finishFlowLabel(preRightLabel); + bind(node.operatorToken); - if (isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind)) { - doWithConditionalBranches(bind, node.right, trueTarget, falseTarget); - bindAssignmentTargetFlow(node.left); + if (isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind)) { + doWithConditionalBranches(bind, node.right, trueTarget, falseTarget); + bindAssignmentTargetFlow(node.left); - addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); - addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); - } - else { - bindCondition(node.right, trueTarget, falseTarget); - } + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); } - - function bindPrefixUnaryExpressionFlow(node: PrefixUnaryExpression) { - if (node.operator === SyntaxKind.ExclamationToken) { - const saveTrueTarget = currentTrueTarget; - currentTrueTarget = currentFalseTarget; - currentFalseTarget = saveTrueTarget; - bindEachChild(node); - currentFalseTarget = currentTrueTarget; - currentTrueTarget = saveTrueTarget; - } - else { - bindEachChild(node); - if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { - bindAssignmentTargetFlow(node.operand); - } - } + else { + bindCondition(node.right, trueTarget, falseTarget); } + } - function bindPostfixUnaryExpressionFlow(node: PostfixUnaryExpression) { + function bindPrefixUnaryExpressionFlow(node: PrefixUnaryExpression) { + if (node.operator === SyntaxKind.ExclamationToken) { + const saveTrueTarget = currentTrueTarget; + currentTrueTarget = currentFalseTarget; + currentFalseTarget = saveTrueTarget; + bindEachChild(node); + currentFalseTarget = currentTrueTarget; + currentTrueTarget = saveTrueTarget; + } + else { bindEachChild(node); if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { bindAssignmentTargetFlow(node.operand); } } + } - function bindDestructuringAssignmentFlow(node: DestructuringAssignment) { - if (inAssignmentPattern) { - inAssignmentPattern = false; - bind(node.operatorToken); - bind(node.right); - inAssignmentPattern = true; - bind(node.left); - } - else { - inAssignmentPattern = true; - bind(node.left); - inAssignmentPattern = false; - bind(node.operatorToken); - bind(node.right); - } - bindAssignmentTargetFlow(node.left); + function bindPostfixUnaryExpressionFlow(node: PostfixUnaryExpression) { + bindEachChild(node); + if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { + bindAssignmentTargetFlow(node.operand); } + } - function createBindBinaryExpressionFlow() { - interface WorkArea { - stackIndex: number; - skip: boolean; - inStrictModeStack: (boolean | undefined)[]; - parentStack: (Node | undefined)[]; - } + function bindDestructuringAssignmentFlow(node: DestructuringAssignment) { + if (inAssignmentPattern) { + inAssignmentPattern = false; + bind(node.operatorToken); + bind(node.right); + inAssignmentPattern = true; + bind(node.left); + } + else { + inAssignmentPattern = true; + bind(node.left); + inAssignmentPattern = false; + bind(node.operatorToken); + bind(node.right); + } + bindAssignmentTargetFlow(node.left); + } - return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined); + function createBindBinaryExpressionFlow() { + interface WorkArea { + stackIndex: number; + skip: boolean; + inStrictModeStack: (boolean | undefined)[]; + parentStack: (Node | undefined)[]; + } + + return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined); - function onEnter(node: BinaryExpression, state: WorkArea | undefined) { - if (state) { - state.stackIndex++; - // Emulate the work that `bind` does before reaching `bindChildren`. A normal call to - // `bindBinaryExpressionFlow` will already have done this work. - setParent(node, parent); - const saveInStrictMode = inStrictMode; - bindWorker(node); - const saveParent = parent; - parent = node; - state.skip = false; - state.inStrictModeStack[state.stackIndex] = saveInStrictMode; - state.parentStack[state.stackIndex] = saveParent; + function onEnter(node: BinaryExpression, state: WorkArea | undefined) { + if (state) { + state.stackIndex++; + // Emulate the work that `bind` does before reaching `bindChildren`. A normal call to + // `bindBinaryExpressionFlow` will already have done this work. + setParent(node, parent); + const saveInStrictMode = inStrictMode; + bindWorker(node); + const saveParent = parent; + parent = node; + state.skip = false; + state.inStrictModeStack[state.stackIndex] = saveInStrictMode; + state.parentStack[state.stackIndex] = saveParent; + } + else { + state = { + stackIndex: 0, + skip: false, + inStrictModeStack: [undefined], + parentStack: [undefined] + }; + } + // TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions + // we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too + // For now, though, since the common cases are chained `+`, leaving it recursive is fine + const operator = node.operatorToken.kind; + if (operator === SyntaxKind.AmpersandAmpersandToken || + operator === SyntaxKind.BarBarToken || + operator === SyntaxKind.QuestionQuestionToken || + isLogicalOrCoalescingAssignmentOperator(operator)) { + if (isTopLevelLogicalExpression(node)) { + const postExpressionLabel = createBranchLabel(); + bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel); + currentFlow = finishFlowLabel(postExpressionLabel); } else { - state = { - stackIndex: 0, - skip: false, - inStrictModeStack: [undefined], - parentStack: [undefined] - }; - } - // TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions - // we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too - // For now, though, since the common cases are chained `+`, leaving it recursive is fine - const operator = node.operatorToken.kind; - if (operator === SyntaxKind.AmpersandAmpersandToken || - operator === SyntaxKind.BarBarToken || - operator === SyntaxKind.QuestionQuestionToken || - isLogicalOrCoalescingAssignmentOperator(operator)) { - if (isTopLevelLogicalExpression(node)) { - const postExpressionLabel = createBranchLabel(); - bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel); - currentFlow = finishFlowLabel(postExpressionLabel); - } - else { - bindLogicalLikeExpression(node, currentTrueTarget!, currentFalseTarget!); - } - state.skip = true; + bindLogicalLikeExpression(node, currentTrueTarget!, currentFalseTarget!); } - return state; + state.skip = true; } + return state; + } - function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) { - if (!state.skip) { - return maybeBind(left); - } + function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) { + if (!state.skip) { + return maybeBind(left); } + } - function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) { - if (!state.skip) { - if (operatorToken.kind === SyntaxKind.CommaToken) { - maybeBindExpressionFlowIfCall(node.left); - } - bind(operatorToken); + function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) { + if (!state.skip) { + if (operatorToken.kind === SyntaxKind.CommaToken) { + maybeBindExpressionFlowIfCall(node.left); } + bind(operatorToken); } + } - function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) { - if (!state.skip) { - return maybeBind(right); - } + function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) { + if (!state.skip) { + return maybeBind(right); } + } - function onExit(node: BinaryExpression, state: WorkArea) { - if (!state.skip) { - const operator = node.operatorToken.kind; - if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) { - bindAssignmentTargetFlow(node.left); - if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) { - const elementAccess = node.left as ElementAccessExpression; - if (isNarrowableOperand(elementAccess.expression)) { - currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); - } + function onExit(node: BinaryExpression, state: WorkArea) { + if (!state.skip) { + const operator = node.operatorToken.kind; + if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) { + bindAssignmentTargetFlow(node.left); + if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) { + const elementAccess = node.left as ElementAccessExpression; + if (isNarrowableOperand(elementAccess.expression)) { + currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); } } } - const savedInStrictMode = state.inStrictModeStack[state.stackIndex]; - const savedParent = state.parentStack[state.stackIndex]; - if (savedInStrictMode !== undefined) { - inStrictMode = savedInStrictMode; - } - if (savedParent !== undefined) { - parent = savedParent; - } - state.skip = false; - state.stackIndex--; } - - function maybeBind(node: Node) { - if (node && isBinaryExpression(node) && !isDestructuringAssignment(node)) { - return node; - } - bind(node); + const savedInStrictMode = state.inStrictModeStack[state.stackIndex]; + const savedParent = state.parentStack[state.stackIndex]; + if (savedInStrictMode !== undefined) { + inStrictMode = savedInStrictMode; + } + if (savedParent !== undefined) { + parent = savedParent; } + state.skip = false; + state.stackIndex--; } - function bindDeleteExpressionFlow(node: DeleteExpression) { - bindEachChild(node); - if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { - bindAssignmentTargetFlow(node.expression); + function maybeBind(node: Node) { + if (node && isBinaryExpression(node) && !isDestructuringAssignment(node)) { + return node; } + bind(node); } + } - function bindConditionalExpressionFlow(node: ConditionalExpression) { - const trueLabel = createBranchLabel(); - const falseLabel = createBranchLabel(); - const postExpressionLabel = createBranchLabel(); - bindCondition(node.condition, trueLabel, falseLabel); - currentFlow = finishFlowLabel(trueLabel); - bind(node.questionToken); - bind(node.whenTrue); - addAntecedent(postExpressionLabel, currentFlow); - currentFlow = finishFlowLabel(falseLabel); - bind(node.colonToken); - bind(node.whenFalse); - addAntecedent(postExpressionLabel, currentFlow); - currentFlow = finishFlowLabel(postExpressionLabel); + function bindDeleteExpressionFlow(node: DeleteExpression) { + bindEachChild(node); + if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { + bindAssignmentTargetFlow(node.expression); } + } - function bindInitializedVariableFlow(node: VariableDeclaration | ArrayBindingElement) { - const name = !isOmittedExpression(node) ? node.name : undefined; - if (isBindingPattern(name)) { - for (const child of name.elements) { - bindInitializedVariableFlow(child); - } - } - else { - currentFlow = createFlowMutation(FlowFlags.Assignment, currentFlow, node); + function bindConditionalExpressionFlow(node: ConditionalExpression) { + const trueLabel = createBranchLabel(); + const falseLabel = createBranchLabel(); + const postExpressionLabel = createBranchLabel(); + bindCondition(node.condition, trueLabel, falseLabel); + currentFlow = finishFlowLabel(trueLabel); + bind(node.questionToken); + bind(node.whenTrue); + addAntecedent(postExpressionLabel, currentFlow); + currentFlow = finishFlowLabel(falseLabel); + bind(node.colonToken); + bind(node.whenFalse); + addAntecedent(postExpressionLabel, currentFlow); + currentFlow = finishFlowLabel(postExpressionLabel); + } + + function bindInitializedVariableFlow(node: VariableDeclaration | ArrayBindingElement) { + const name = !isOmittedExpression(node) ? node.name : undefined; + if (isBindingPattern(name)) { + for (const child of name.elements) { + bindInitializedVariableFlow(child); } } + else { + currentFlow = createFlowMutation(FlowFlags.Assignment, currentFlow, node); + } + } + + function bindVariableDeclarationFlow(node: VariableDeclaration) { + bindEachChild(node); + if (node.initializer || isForInOrOfStatement(node.parent.parent)) { + bindInitializedVariableFlow(node); + } + } - function bindVariableDeclarationFlow(node: VariableDeclaration) { + function bindBindingElementFlow(node: BindingElement) { + if (isBindingPattern(node.name)) { + // When evaluating a binding pattern, the initializer is evaluated before the binding pattern, per: + // - https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization + // - `BindingElement: BindingPattern Initializer?` + // - https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization + // - `BindingElement: BindingPattern Initializer?` + bindEach(node.decorators); + bindEach(node.modifiers); + bind(node.dotDotDotToken); + bind(node.propertyName); + bind(node.initializer); + bind(node.name); + } + else { bindEachChild(node); - if (node.initializer || isForInOrOfStatement(node.parent.parent)) { - bindInitializedVariableFlow(node); - } - } - - function bindBindingElementFlow(node: BindingElement) { - if (isBindingPattern(node.name)) { - // When evaluating a binding pattern, the initializer is evaluated before the binding pattern, per: - // - https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization - // - `BindingElement: BindingPattern Initializer?` - // - https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization - // - `BindingElement: BindingPattern Initializer?` - bindEach(node.decorators); - bindEach(node.modifiers); - bind(node.dotDotDotToken); - bind(node.propertyName); - bind(node.initializer); - bind(node.name); - } - else { - bindEachChild(node); - } } + } - function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag) { - bind(node.tagName); - if (node.kind !== SyntaxKind.JSDocEnumTag && node.fullName) { - // don't bind the type name yet; that's delayed until delayedBindJSDocTypedefTag - setParent(node.fullName, node); - setParentRecursive(node.fullName, /*incremental*/ false); - } - if (typeof node.comment !== "string") { - bindEach(node.comment); - } + function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag) { + bind(node.tagName); + if (node.kind !== SyntaxKind.JSDocEnumTag && node.fullName) { + // don't bind the type name yet; that's delayed until delayedBindJSDocTypedefTag + setParent(node.fullName, node); + setParentRecursive(node.fullName, /*incremental*/ false); } + if (typeof node.comment !== "string") { + bindEach(node.comment); + } + } - function bindJSDocClassTag(node: JSDocClassTag) { - bindEachChild(node); - const host = getHostSignatureFromJSDoc(node); - if (host && host.kind !== SyntaxKind.MethodDeclaration) { - addDeclarationToSymbol(host.symbol, host, SymbolFlags.Class); - } + function bindJSDocClassTag(node: JSDocClassTag) { + bindEachChild(node); + const host = getHostSignatureFromJSDoc(node); + if (host && host.kind !== SyntaxKind.MethodDeclaration) { + addDeclarationToSymbol(host.symbol, host, SymbolFlags.Class); } + } - function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) { - doWithConditionalBranches(bind, node, trueTarget, falseTarget); - if (!isOptionalChain(node) || isOutermostOptionalChain(node)) { - addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); - addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); - } + function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) { + doWithConditionalBranches(bind, node, trueTarget, falseTarget); + if (!isOptionalChain(node) || isOutermostOptionalChain(node)) { + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); } + } - function bindOptionalChainRest(node: OptionalChain) { - switch (node.kind) { - case SyntaxKind.PropertyAccessExpression: - bind(node.questionDotToken); - bind(node.name); - break; - case SyntaxKind.ElementAccessExpression: - bind(node.questionDotToken); - bind(node.argumentExpression); - break; - case SyntaxKind.CallExpression: - bind(node.questionDotToken); - bindEach(node.typeArguments); - bindEach(node.arguments); - break; - } + function bindOptionalChainRest(node: OptionalChain) { + switch (node.kind) { + case SyntaxKind.PropertyAccessExpression: + bind(node.questionDotToken); + bind(node.name); + break; + case SyntaxKind.ElementAccessExpression: + bind(node.questionDotToken); + bind(node.argumentExpression); + break; + case SyntaxKind.CallExpression: + bind(node.questionDotToken); + bindEach(node.typeArguments); + bindEach(node.arguments); + break; } + } - function bindOptionalChain(node: OptionalChain, trueTarget: FlowLabel, falseTarget: FlowLabel) { - // For an optional chain, we emulate the behavior of a logical expression: - // - // a?.b -> a && a.b - // a?.b.c -> a && a.b.c - // a?.b?.c -> a && a.b && a.b.c - // a?.[x = 1] -> a && a[x = 1] - // - // To do this we descend through the chain until we reach the root of a chain (the expression with a `?.`) - // and build it's CFA graph as if it were the first condition (`a && ...`). Then we bind the rest - // of the node as part of the "true" branch, and continue to do so as we ascend back up to the outermost - // chain node. We then treat the entire node as the right side of the expression. - const preChainLabel = isOptionalChainRoot(node) ? createBranchLabel() : undefined; - bindOptionalExpression(node.expression, preChainLabel || trueTarget, falseTarget); - if (preChainLabel) { - currentFlow = finishFlowLabel(preChainLabel); - } - doWithConditionalBranches(bindOptionalChainRest, node, trueTarget, falseTarget); - if (isOutermostOptionalChain(node)) { - addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); - addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); - } + function bindOptionalChain(node: OptionalChain, trueTarget: FlowLabel, falseTarget: FlowLabel) { + // For an optional chain, we emulate the behavior of a logical expression: + // + // a?.b -> a && a.b + // a?.b.c -> a && a.b.c + // a?.b?.c -> a && a.b && a.b.c + // a?.[x = 1] -> a && a[x = 1] + // + // To do this we descend through the chain until we reach the root of a chain (the expression with a `?.`) + // and build it's CFA graph as if it were the first condition (`a && ...`). Then we bind the rest + // of the node as part of the "true" branch, and continue to do so as we ascend back up to the outermost + // chain node. We then treat the entire node as the right side of the expression. + const preChainLabel = isOptionalChainRoot(node) ? createBranchLabel() : undefined; + bindOptionalExpression(node.expression, preChainLabel || trueTarget, falseTarget); + if (preChainLabel) { + currentFlow = finishFlowLabel(preChainLabel); + } + doWithConditionalBranches(bindOptionalChainRest, node, trueTarget, falseTarget); + if (isOutermostOptionalChain(node)) { + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); } + } - function bindOptionalChainFlow(node: OptionalChain) { - if (isTopLevelLogicalExpression(node)) { - const postExpressionLabel = createBranchLabel(); - bindOptionalChain(node, postExpressionLabel, postExpressionLabel); - currentFlow = finishFlowLabel(postExpressionLabel); - } - else { - bindOptionalChain(node, currentTrueTarget!, currentFalseTarget!); - } + function bindOptionalChainFlow(node: OptionalChain) { + if (isTopLevelLogicalExpression(node)) { + const postExpressionLabel = createBranchLabel(); + bindOptionalChain(node, postExpressionLabel, postExpressionLabel); + currentFlow = finishFlowLabel(postExpressionLabel); + } + else { + bindOptionalChain(node, currentTrueTarget!, currentFalseTarget!); } + } - function bindNonNullExpressionFlow(node: NonNullExpression | NonNullChain) { - if (isOptionalChain(node)) { - bindOptionalChainFlow(node); - } - else { - bindEachChild(node); - } + function bindNonNullExpressionFlow(node: NonNullExpression | NonNullChain) { + if (isOptionalChain(node)) { + bindOptionalChainFlow(node); } + else { + bindEachChild(node); + } + } - function bindAccessExpressionFlow(node: AccessExpression | PropertyAccessChain | ElementAccessChain) { - if (isOptionalChain(node)) { - bindOptionalChainFlow(node); - } - else { - bindEachChild(node); - } + function bindAccessExpressionFlow(node: AccessExpression | PropertyAccessChain | ElementAccessChain) { + if (isOptionalChain(node)) { + bindOptionalChainFlow(node); } + else { + bindEachChild(node); + } + } - function bindCallExpressionFlow(node: CallExpression | CallChain) { - if (isOptionalChain(node)) { - bindOptionalChainFlow(node); + function bindCallExpressionFlow(node: CallExpression | CallChain) { + if (isOptionalChain(node)) { + bindOptionalChainFlow(node); + } + else { + // If the target of the call expression is a function expression or arrow function we have + // an immediately invoked function expression (IIFE). Initialize the flowNode property to + // the current control flow (which includes evaluation of the IIFE arguments). + const expr = skipParentheses(node.expression); + if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) { + bindEach(node.typeArguments); + bindEach(node.arguments); + bind(node.expression); } else { - // If the target of the call expression is a function expression or arrow function we have - // an immediately invoked function expression (IIFE). Initialize the flowNode property to - // the current control flow (which includes evaluation of the IIFE arguments). - const expr = skipParentheses(node.expression); - if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) { - bindEach(node.typeArguments); - bindEach(node.arguments); - bind(node.expression); - } - else { - bindEachChild(node); - if (node.expression.kind === SyntaxKind.SuperKeyword) { - currentFlow = createFlowCall(currentFlow, node); - } + bindEachChild(node); + if (node.expression.kind === SyntaxKind.SuperKeyword) { + currentFlow = createFlowCall(currentFlow, node); } } - if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { - const propertyAccess = node.expression as PropertyAccessExpression; - if (isIdentifier(propertyAccess.name) && isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) { - currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); - } + } + if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { + const propertyAccess = node.expression as PropertyAccessExpression; + if (isIdentifier(propertyAccess.name) && isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) { + currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); } } + } - function getContainerFlags(node: Node): ContainerFlags { - switch (node.kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.JsxAttributes: - return ContainerFlags.IsContainer; + function getContainerFlags(node: Node): ContainerFlags { + switch (node.kind) { + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.JsxAttributes: + return ContainerFlags.IsContainer; - case SyntaxKind.InterfaceDeclaration: - return ContainerFlags.IsContainer | ContainerFlags.IsInterface; + case SyntaxKind.InterfaceDeclaration: + return ContainerFlags.IsContainer | ContainerFlags.IsInterface; - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.MappedType: - return ContainerFlags.IsContainer | ContainerFlags.HasLocals; + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.MappedType: + return ContainerFlags.IsContainer | ContainerFlags.HasLocals; + + case SyntaxKind.SourceFile: + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals; + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + if (isObjectLiteralOrClassExpressionMethodOrAccessor(node)) { + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor; + } + // falls through + case SyntaxKind.Constructor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.ConstructorType: + case SyntaxKind.ClassStaticBlockDeclaration: + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike; + + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsFunctionExpression; + + case SyntaxKind.ModuleBlock: + return ContainerFlags.IsControlFlowContainer; + case SyntaxKind.PropertyDeclaration: + return (node as PropertyDeclaration).initializer ? ContainerFlags.IsControlFlowContainer : 0; + + case SyntaxKind.CatchClause: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.CaseBlock: + return ContainerFlags.IsBlockScopedContainer; + + case SyntaxKind.Block: + // do not treat blocks directly inside a function as a block-scoped-container. + // Locals that reside in this block should go to the function locals. Otherwise 'x' + // would not appear to be a redeclaration of a block scoped local in the following + // example: + // + // function foo() { + // var x; + // let x; + // } + // + // If we placed 'var x' into the function locals and 'let x' into the locals of + // the block, then there would be no collision. + // + // By not creating a new block-scoped-container here, we ensure that both 'var x' + // and 'let x' go into the Function-container's locals, and we do get a collision + // conflict. + return isFunctionLike(node.parent) || isClassStaticBlockDeclaration(node.parent) ? ContainerFlags.None : ContainerFlags.IsBlockScopedContainer; + } - case SyntaxKind.SourceFile: - return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals; + return ContainerFlags.None; + } - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - if (isObjectLiteralOrClassExpressionMethodOrAccessor(node)) { - return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor; - } - // falls through - case SyntaxKind.Constructor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.JSDocSignature: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.ConstructorType: - case SyntaxKind.ClassStaticBlockDeclaration: - return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike; - - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsFunctionExpression; - - case SyntaxKind.ModuleBlock: - return ContainerFlags.IsControlFlowContainer; - case SyntaxKind.PropertyDeclaration: - return (node as PropertyDeclaration).initializer ? ContainerFlags.IsControlFlowContainer : 0; - - case SyntaxKind.CatchClause: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.CaseBlock: - return ContainerFlags.IsBlockScopedContainer; - - case SyntaxKind.Block: - // do not treat blocks directly inside a function as a block-scoped-container. - // Locals that reside in this block should go to the function locals. Otherwise 'x' - // would not appear to be a redeclaration of a block scoped local in the following - // example: - // - // function foo() { - // var x; - // let x; - // } - // - // If we placed 'var x' into the function locals and 'let x' into the locals of - // the block, then there would be no collision. - // - // By not creating a new block-scoped-container here, we ensure that both 'var x' - // and 'let x' go into the Function-container's locals, and we do get a collision - // conflict. - return isFunctionLike(node.parent) || isClassStaticBlockDeclaration(node.parent) ? ContainerFlags.None : ContainerFlags.IsBlockScopedContainer; - } - - return ContainerFlags.None; - } - - function addToContainerChain(next: Node) { - if (lastContainer) { - lastContainer.nextContainer = next; - } - - lastContainer = next; - } - - function declareSymbolAndAddToSymbolTable(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol | undefined { - switch (container.kind) { - // Modules, source files, and classes need specialized handling for how their - // members are declared (for example, a member of a class will go into a specific - // symbol table depending on if it is static or not). We defer to specialized - // handlers to take care of declaring these child members. - case SyntaxKind.ModuleDeclaration: - return declareModuleMember(node, symbolFlags, symbolExcludes); - - case SyntaxKind.SourceFile: - return declareSourceFileMember(node, symbolFlags, symbolExcludes); - - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - return declareClassMember(node, symbolFlags, symbolExcludes); - - case SyntaxKind.EnumDeclaration: - return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); - - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.JsxAttributes: - // Interface/Object-types always have their children added to the 'members' of - // their container. They are only accessible through an instance of their - // container, and are never in scope otherwise (even inside the body of the - // object / type / interface declaring them). An exception is type parameters, - // which are in scope without qualification (similar to 'locals'). - return declareSymbol(container.symbol.members!, container.symbol, node, symbolFlags, symbolExcludes); - - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.JSDocSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.ClassStaticBlockDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.MappedType: - // All the children of these container types are never visible through another - // symbol (i.e. through another symbol's 'exports' or 'members'). Instead, - // they're only accessed 'lexically' (i.e. from code that exists underneath - // their container in the tree). To accomplish this, we simply add their declared - // symbol to the 'locals' of the container. These symbols can then be found as - // the type checker walks up the containers, checking them for matching names. - return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } - } - - function declareClassMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - return isStatic(node) - ? declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes) - : declareSymbol(container.symbol.members!, container.symbol, node, symbolFlags, symbolExcludes); - } - - function declareSourceFileMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - return isExternalModule(file) - ? declareModuleMember(node, symbolFlags, symbolExcludes) - : declareSymbol(file.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } - - function hasExportDeclarations(node: ModuleDeclaration | SourceFile): boolean { - const body = isSourceFile(node) ? node : tryCast(node.body, isModuleBlock); - return !!body && body.statements.some(s => isExportDeclaration(s) || isExportAssignment(s)); - } - - function setExportContextFlag(node: Mutable) { - // A declaration source file or ambient module declaration that contains no export declarations (but possibly regular - // declarations with export modifiers) is an export context in which declarations are implicitly exported. - if (node.flags & NodeFlags.Ambient && !hasExportDeclarations(node)) { - node.flags |= NodeFlags.ExportContext; - } - else { - node.flags &= ~NodeFlags.ExportContext; - } + function addToContainerChain(next: Node) { + if (lastContainer) { + lastContainer.nextContainer = next; } - function bindModuleDeclaration(node: ModuleDeclaration) { - setExportContextFlag(node); - if (isAmbientModule(node)) { - if (hasSyntacticModifier(node, ModifierFlags.Export)) { - errorOnFirstToken(node, Diagnostics.export_modifier_cannot_be_applied_to_ambient_modules_and_module_augmentations_since_they_are_always_visible); - } - if (isModuleAugmentationExternal(node)) { - declareModuleSymbol(node); - } - else { - let pattern: string | Pattern | undefined; - if (node.name.kind === SyntaxKind.StringLiteral) { - const { text } = node.name; - pattern = tryParsePattern(text); - if (pattern === undefined) { - errorOnFirstToken(node.name, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, text); - } - } + lastContainer = next; + } - const symbol = declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes)!; - file.patternAmbientModules = append(file.patternAmbientModules, pattern && !isString(pattern) ? { pattern, symbol } : undefined); - } + function declareSymbolAndAddToSymbolTable(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol | undefined { + switch (container.kind) { + // Modules, source files, and classes need specialized handling for how their + // members are declared (for example, a member of a class will go into a specific + // symbol table depending on if it is static or not). We defer to specialized + // handlers to take care of declaring these child members. + case SyntaxKind.ModuleDeclaration: + return declareModuleMember(node, symbolFlags, symbolExcludes); + + case SyntaxKind.SourceFile: + return declareSourceFileMember(node, symbolFlags, symbolExcludes); + + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + return declareClassMember(node, symbolFlags, symbolExcludes); + + case SyntaxKind.EnumDeclaration: + return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); + + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.JsxAttributes: + // Interface/Object-types always have their children added to the 'members' of + // their container. They are only accessible through an instance of their + // container, and are never in scope otherwise (even inside the body of the + // object / type / interface declaring them). An exception is type parameters, + // which are in scope without qualification (similar to 'locals'). + return declareSymbol(container.symbol.members!, container.symbol, node, symbolFlags, symbolExcludes); + + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.JSDocSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.ClassStaticBlockDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.MappedType: + // All the children of these container types are never visible through another + // symbol (i.e. through another symbol's 'exports' or 'members'). Instead, + // they're only accessed 'lexically' (i.e. from code that exists underneath + // their container in the tree). To accomplish this, we simply add their declared + // symbol to the 'locals' of the container. These symbols can then be found as + // the type checker walks up the containers, checking them for matching names. + return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); + } + } + + function declareClassMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + return isStatic(node) + ? declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes) + : declareSymbol(container.symbol.members!, container.symbol, node, symbolFlags, symbolExcludes); + } + + function declareSourceFileMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + return isExternalModule(file) + ? declareModuleMember(node, symbolFlags, symbolExcludes) + : declareSymbol(file.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); + } + + function hasExportDeclarations(node: ModuleDeclaration | SourceFile): boolean { + const body = isSourceFile(node) ? node : tryCast(node.body, isModuleBlock); + return !!body && body.statements.some(s => isExportDeclaration(s) || isExportAssignment(s)); + } + + function setExportContextFlag(node: Mutable) { + // A declaration source file or ambient module declaration that contains no export declarations (but possibly regular + // declarations with export modifiers) is an export context in which declarations are implicitly exported. + if (node.flags & NodeFlags.Ambient && !hasExportDeclarations(node)) { + node.flags |= NodeFlags.ExportContext; + } + else { + node.flags &= ~NodeFlags.ExportContext; + } + } + + function bindModuleDeclaration(node: ModuleDeclaration) { + setExportContextFlag(node); + if (isAmbientModule(node)) { + if (hasSyntacticModifier(node, ModifierFlags.Export)) { + errorOnFirstToken(node, Diagnostics.export_modifier_cannot_be_applied_to_ambient_modules_and_module_augmentations_since_they_are_always_visible); + } + if (isModuleAugmentationExternal(node)) { + declareModuleSymbol(node); } else { - const state = declareModuleSymbol(node); - if (state !== ModuleInstanceState.NonInstantiated) { - const { symbol } = node; - // if module was already merged with some function, class or non-const enum, treat it as non-const-enum-only - symbol.constEnumOnlyModule = (!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) - // Current must be `const enum` only - && state === ModuleInstanceState.ConstEnumOnly - // Can't have been set to 'false' in a previous merged symbol. ('undefined' OK) - && symbol.constEnumOnlyModule !== false; + let pattern: string | Pattern | undefined; + if (node.name.kind === SyntaxKind.StringLiteral) { + const { text } = node.name; + pattern = tryParsePattern(text); + if (pattern === undefined) { + errorOnFirstToken(node.name, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, text); + } } + + const symbol = declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes)!; + file.patternAmbientModules = append(file.patternAmbientModules, pattern && !isString(pattern) ? { pattern, symbol } : undefined); } } - - function declareModuleSymbol(node: ModuleDeclaration): ModuleInstanceState { - const state = getModuleInstanceState(node); - const instantiated = state !== ModuleInstanceState.NonInstantiated; - declareSymbolAndAddToSymbolTable(node, - instantiated ? SymbolFlags.ValueModule : SymbolFlags.NamespaceModule, - instantiated ? SymbolFlags.ValueModuleExcludes : SymbolFlags.NamespaceModuleExcludes); - return state; + else { + const state = declareModuleSymbol(node); + if (state !== ModuleInstanceState.NonInstantiated) { + const { symbol } = node; + // if module was already merged with some function, class or non-const enum, treat it as non-const-enum-only + symbol.constEnumOnlyModule = (!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) + // Current must be `const enum` only + && state === ModuleInstanceState.ConstEnumOnly + // Can't have been set to 'false' in a previous merged symbol. ('undefined' OK) + && symbol.constEnumOnlyModule !== false; + } } + } - function bindFunctionOrConstructorType(node: SignatureDeclaration | JSDocSignature): void { - // For a given function symbol "<...>(...) => T" we want to generate a symbol identical - // to the one we would get for: { <...>(...): T } - // - // We do that by making an anonymous type literal symbol, and then setting the function - // symbol as its sole member. To the rest of the system, this symbol will be indistinguishable - // from an actual type literal symbol you would have gotten had you used the long form. - const symbol = createSymbol(SymbolFlags.Signature, getDeclarationName(node)!); // TODO: GH#18217 - addDeclarationToSymbol(symbol, node, SymbolFlags.Signature); + function declareModuleSymbol(node: ModuleDeclaration): ModuleInstanceState { + const state = getModuleInstanceState(node); + const instantiated = state !== ModuleInstanceState.NonInstantiated; + declareSymbolAndAddToSymbolTable(node, + instantiated ? SymbolFlags.ValueModule : SymbolFlags.NamespaceModule, + instantiated ? SymbolFlags.ValueModuleExcludes : SymbolFlags.NamespaceModuleExcludes); + return state; + } + + function bindFunctionOrConstructorType(node: SignatureDeclaration | JSDocSignature): void { + // For a given function symbol "<...>(...) => T" we want to generate a symbol identical + // to the one we would get for: { <...>(...): T } + // + // We do that by making an anonymous type literal symbol, and then setting the function + // symbol as its sole member. To the rest of the system, this symbol will be indistinguishable + // from an actual type literal symbol you would have gotten had you used the long form. + const symbol = createSymbol(SymbolFlags.Signature, getDeclarationName(node)!); // TODO: GH#18217 + addDeclarationToSymbol(symbol, node, SymbolFlags.Signature); + + const typeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + addDeclarationToSymbol(typeLiteralSymbol, node, SymbolFlags.TypeLiteral); + typeLiteralSymbol.members = createSymbolTable(); + typeLiteralSymbol.members.set(symbol.escapedName, symbol); + } - const typeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); - addDeclarationToSymbol(typeLiteralSymbol, node, SymbolFlags.TypeLiteral); - typeLiteralSymbol.members = createSymbolTable(); - typeLiteralSymbol.members.set(symbol.escapedName, symbol); + function bindObjectLiteralExpression(node: ObjectLiteralExpression) { + const enum ElementKind { + Property = 1, + Accessor = 2 } - function bindObjectLiteralExpression(node: ObjectLiteralExpression) { - const enum ElementKind { - Property = 1, - Accessor = 2 - } + if (inStrictMode && !isAssignmentTarget(node)) { + const seen = new Map<__String, ElementKind>(); + + for (const prop of node.properties) { + if (prop.kind === SyntaxKind.SpreadAssignment || prop.name.kind !== SyntaxKind.Identifier) { + continue; + } - if (inStrictMode && !isAssignmentTarget(node)) { - const seen = new Map<__String, ElementKind>(); + const identifier = prop.name; - for (const prop of node.properties) { - if (prop.kind === SyntaxKind.SpreadAssignment || prop.name.kind !== SyntaxKind.Identifier) { - continue; - } + // ECMA-262 11.1.5 Object Initializer + // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true + // a.This production is contained in strict code and IsDataDescriptor(previous) is true and + // IsDataDescriptor(propId.descriptor) is true. + // b.IsDataDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true. + // c.IsAccessorDescriptor(previous) is true and IsDataDescriptor(propId.descriptor) is true. + // d.IsAccessorDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true + // and either both previous and propId.descriptor have[[Get]] fields or both previous and propId.descriptor have[[Set]] fields + const currentKind = prop.kind === SyntaxKind.PropertyAssignment || prop.kind === SyntaxKind.ShorthandPropertyAssignment || prop.kind === SyntaxKind.MethodDeclaration + ? ElementKind.Property + : ElementKind.Accessor; - const identifier = prop.name; - - // ECMA-262 11.1.5 Object Initializer - // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true - // a.This production is contained in strict code and IsDataDescriptor(previous) is true and - // IsDataDescriptor(propId.descriptor) is true. - // b.IsDataDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true. - // c.IsAccessorDescriptor(previous) is true and IsDataDescriptor(propId.descriptor) is true. - // d.IsAccessorDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true - // and either both previous and propId.descriptor have[[Get]] fields or both previous and propId.descriptor have[[Set]] fields - const currentKind = prop.kind === SyntaxKind.PropertyAssignment || prop.kind === SyntaxKind.ShorthandPropertyAssignment || prop.kind === SyntaxKind.MethodDeclaration - ? ElementKind.Property - : ElementKind.Accessor; - - const existingKind = seen.get(identifier.escapedText); - if (!existingKind) { - seen.set(identifier.escapedText, currentKind); - continue; - } + const existingKind = seen.get(identifier.escapedText); + if (!existingKind) { + seen.set(identifier.escapedText, currentKind); + continue; + } - if (currentKind === ElementKind.Property && existingKind === ElementKind.Property) { - const span = getErrorSpanForNode(file, identifier); - file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, - Diagnostics.An_object_literal_cannot_have_multiple_properties_with_the_same_name_in_strict_mode)); - } + if (currentKind === ElementKind.Property && existingKind === ElementKind.Property) { + const span = getErrorSpanForNode(file, identifier); + file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, + Diagnostics.An_object_literal_cannot_have_multiple_properties_with_the_same_name_in_strict_mode)); } } - - return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, InternalSymbolName.Object); } - function bindJsxAttributes(node: JsxAttributes) { - return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, InternalSymbolName.JSXAttributes); - } + return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, InternalSymbolName.Object); + } - function bindJsxAttribute(node: JsxAttribute, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - return declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); - } + function bindJsxAttributes(node: JsxAttributes) { + return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, InternalSymbolName.JSXAttributes); + } - function bindAnonymousDeclaration(node: Declaration, symbolFlags: SymbolFlags, name: __String) { - const symbol = createSymbol(symbolFlags, name); - if (symbolFlags & (SymbolFlags.EnumMember | SymbolFlags.ClassMember)) { - symbol.parent = container.symbol; - } - addDeclarationToSymbol(symbol, node, symbolFlags); - return symbol; + function bindJsxAttribute(node: JsxAttribute, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + return declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); + } + + function bindAnonymousDeclaration(node: Declaration, symbolFlags: SymbolFlags, name: __String) { + const symbol = createSymbol(symbolFlags, name); + if (symbolFlags & (SymbolFlags.EnumMember | SymbolFlags.ClassMember)) { + symbol.parent = container.symbol; } + addDeclarationToSymbol(symbol, node, symbolFlags); + return symbol; + } - function bindBlockScopedDeclaration(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - switch (blockScopeContainer.kind) { - case SyntaxKind.ModuleDeclaration: + function bindBlockScopedDeclaration(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + switch (blockScopeContainer.kind) { + case SyntaxKind.ModuleDeclaration: + declareModuleMember(node, symbolFlags, symbolExcludes); + break; + case SyntaxKind.SourceFile: + if (isExternalOrCommonJsModule(container as SourceFile)) { declareModuleMember(node, symbolFlags, symbolExcludes); break; - case SyntaxKind.SourceFile: - if (isExternalOrCommonJsModule(container as SourceFile)) { - declareModuleMember(node, symbolFlags, symbolExcludes); - break; - } - // falls through - default: - if (!blockScopeContainer.locals) { - blockScopeContainer.locals = createSymbolTable(); - addToContainerChain(blockScopeContainer); - } - declareSymbol(blockScopeContainer.locals, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } + } + // falls through + default: + if (!blockScopeContainer.locals) { + blockScopeContainer.locals = createSymbolTable(); + addToContainerChain(blockScopeContainer); + } + declareSymbol(blockScopeContainer.locals, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } + } - function delayedBindJSDocTypedefTag() { - if (!delayedTypeAliases) { - return; - } - const saveContainer = container; - const saveLastContainer = lastContainer; - const saveBlockScopeContainer = blockScopeContainer; - const saveParent = parent; - const saveCurrentFlow = currentFlow; - for (const typeAlias of delayedTypeAliases) { - const host = typeAlias.parent.parent; - container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file; - blockScopeContainer = getEnclosingBlockScopeContainer(host) || file; - currentFlow = initFlowNode({ flags: FlowFlags.Start }); - parent = typeAlias; - bind(typeAlias.typeExpression); - const declName = getNameOfDeclaration(typeAlias); - if ((isJSDocEnumTag(typeAlias) || !typeAlias.fullName) && declName && isPropertyAccessEntityNameExpression(declName.parent)) { - // typedef anchored to an A.B.C assignment - we need to bind into B's namespace under name C - const isTopLevel = isTopLevelNamespaceAssignment(declName.parent); - if (isTopLevel) { - bindPotentiallyMissingNamespaces(file.symbol, declName.parent, isTopLevel, - !!findAncestor(declName, d => isPropertyAccessExpression(d) && d.name.escapedText === "prototype"), /*containerIsClass*/ false); - const oldContainer = container; - switch (getAssignmentDeclarationPropertyAccessKind(declName.parent)) { - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.ModuleExports: - if (!isExternalOrCommonJsModule(file)) { - container = undefined!; - } - else { - container = file; - } - break; - case AssignmentDeclarationKind.ThisProperty: - container = declName.parent.expression; - break; - case AssignmentDeclarationKind.PrototypeProperty: - container = (declName.parent.expression as PropertyAccessExpression).name; - break; - case AssignmentDeclarationKind.Property: - container = isExportsOrModuleExportsOrAlias(file, declName.parent.expression) ? file - : isPropertyAccessExpression(declName.parent.expression) ? declName.parent.expression.name - : declName.parent.expression; - break; - case AssignmentDeclarationKind.None: - return Debug.fail("Shouldn't have detected typedef or enum on non-assignment declaration"); - } - if (container) { - declareModuleMember(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - } - container = oldContainer; + function delayedBindJSDocTypedefTag() { + if (!delayedTypeAliases) { + return; + } + const saveContainer = container; + const saveLastContainer = lastContainer; + const saveBlockScopeContainer = blockScopeContainer; + const saveParent = parent; + const saveCurrentFlow = currentFlow; + for (const typeAlias of delayedTypeAliases) { + const host = typeAlias.parent.parent; + container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file; + blockScopeContainer = getEnclosingBlockScopeContainer(host) || file; + currentFlow = initFlowNode({ flags: FlowFlags.Start }); + parent = typeAlias; + bind(typeAlias.typeExpression); + const declName = getNameOfDeclaration(typeAlias); + if ((isJSDocEnumTag(typeAlias) || !typeAlias.fullName) && declName && isPropertyAccessEntityNameExpression(declName.parent)) { + // typedef anchored to an A.B.C assignment - we need to bind into B's namespace under name C + const isTopLevel = isTopLevelNamespaceAssignment(declName.parent); + if (isTopLevel) { + bindPotentiallyMissingNamespaces(file.symbol, declName.parent, isTopLevel, + !!findAncestor(declName, d => isPropertyAccessExpression(d) && d.name.escapedText === "prototype"), /*containerIsClass*/ false); + const oldContainer = container; + switch (getAssignmentDeclarationPropertyAccessKind(declName.parent)) { + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.ModuleExports: + if (!isExternalOrCommonJsModule(file)) { + container = undefined!; + } + else { + container = file; + } + break; + case AssignmentDeclarationKind.ThisProperty: + container = declName.parent.expression; + break; + case AssignmentDeclarationKind.PrototypeProperty: + container = (declName.parent.expression as PropertyAccessExpression).name; + break; + case AssignmentDeclarationKind.Property: + container = isExportsOrModuleExportsOrAlias(file, declName.parent.expression) ? file + : isPropertyAccessExpression(declName.parent.expression) ? declName.parent.expression.name + : declName.parent.expression; + break; + case AssignmentDeclarationKind.None: + return Debug.fail("Shouldn't have detected typedef or enum on non-assignment declaration"); } - } - else if (isJSDocEnumTag(typeAlias) || !typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) { - parent = typeAlias.parent; - bindBlockScopedDeclaration(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - } - else { - bind(typeAlias.fullName); + if (container) { + declareModuleMember(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + } + container = oldContainer; } } - container = saveContainer; - lastContainer = saveLastContainer; - blockScopeContainer = saveBlockScopeContainer; - parent = saveParent; - currentFlow = saveCurrentFlow; + else if (isJSDocEnumTag(typeAlias) || !typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) { + parent = typeAlias.parent; + bindBlockScopedDeclaration(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + } + else { + bind(typeAlias.fullName); + } } + container = saveContainer; + lastContainer = saveLastContainer; + blockScopeContainer = saveBlockScopeContainer; + parent = saveParent; + currentFlow = saveCurrentFlow; + } - // The binder visits every node in the syntax tree so it is a convenient place to perform a single localized - // check for reserved words used as identifiers in strict mode code, as well as `yield` or `await` in - // [Yield] or [Await] contexts, respectively. - function checkContextualIdentifier(node: Identifier) { - // Report error only if there are no parse errors in file - if (!file.parseDiagnostics.length && - !(node.flags & NodeFlags.Ambient) && - !(node.flags & NodeFlags.JSDoc) && - !isIdentifierName(node)) { - - // strict mode identifiers - if (inStrictMode && - node.originalKeywordKind! >= SyntaxKind.FirstFutureReservedWord && - node.originalKeywordKind! <= SyntaxKind.LastFutureReservedWord) { + // The binder visits every node in the syntax tree so it is a convenient place to perform a single localized + // check for reserved words used as identifiers in strict mode code, as well as `yield` or `await` in + // [Yield] or [Await] contexts, respectively. + function checkContextualIdentifier(node: Identifier) { + // Report error only if there are no parse errors in file + if (!file.parseDiagnostics.length && + !(node.flags & NodeFlags.Ambient) && + !(node.flags & NodeFlags.JSDoc) && + !isIdentifierName(node)) { + + // strict mode identifiers + if (inStrictMode && + node.originalKeywordKind! >= SyntaxKind.FirstFutureReservedWord && + node.originalKeywordKind! <= SyntaxKind.LastFutureReservedWord) { + file.bindDiagnostics.push(createDiagnosticForNode(node, + getStrictModeIdentifierMessage(node), declarationNameToString(node))); + } + else if (node.originalKeywordKind === SyntaxKind.AwaitKeyword) { + if (isExternalModule(file) && isInTopLevelContext(node)) { file.bindDiagnostics.push(createDiagnosticForNode(node, - getStrictModeIdentifierMessage(node), declarationNameToString(node))); - } - else if (node.originalKeywordKind === SyntaxKind.AwaitKeyword) { - if (isExternalModule(file) && isInTopLevelContext(node)) { - file.bindDiagnostics.push(createDiagnosticForNode(node, - Diagnostics.Identifier_expected_0_is_a_reserved_word_at_the_top_level_of_a_module, - declarationNameToString(node))); - } - else if (node.flags & NodeFlags.AwaitContext) { - file.bindDiagnostics.push(createDiagnosticForNode(node, - Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here, - declarationNameToString(node))); - } + Diagnostics.Identifier_expected_0_is_a_reserved_word_at_the_top_level_of_a_module, + declarationNameToString(node))); } - else if (node.originalKeywordKind === SyntaxKind.YieldKeyword && node.flags & NodeFlags.YieldContext) { + else if (node.flags & NodeFlags.AwaitContext) { file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here, declarationNameToString(node))); } } - } - - function getStrictModeIdentifierMessage(node: Node) { - // Provide specialized messages to help the user understand why we think they're in - // strict mode. - if (getContainingClass(node)) { - return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Class_definitions_are_automatically_in_strict_mode; + else if (node.originalKeywordKind === SyntaxKind.YieldKeyword && node.flags & NodeFlags.YieldContext) { + file.bindDiagnostics.push(createDiagnosticForNode(node, + Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here, + declarationNameToString(node))); } + } + } - if (file.externalModuleIndicator) { - return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode; - } + function getStrictModeIdentifierMessage(node: Node) { + // Provide specialized messages to help the user understand why we think they're in + // strict mode. + if (getContainingClass(node)) { + return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Class_definitions_are_automatically_in_strict_mode; + } - return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode; + if (file.externalModuleIndicator) { + return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode; } - // The binder visits every node, so this is a good place to check for - // the reserved private name (there is only one) - function checkPrivateIdentifier(node: PrivateIdentifier) { - if (node.escapedText === "#constructor") { - // Report error only if there are no parse errors in file - if (!file.parseDiagnostics.length) { - file.bindDiagnostics.push(createDiagnosticForNode(node, - Diagnostics.constructor_is_a_reserved_word, declarationNameToString(node))); - } + return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode; + } + + // The binder visits every node, so this is a good place to check for + // the reserved private name (there is only one) + function checkPrivateIdentifier(node: PrivateIdentifier) { + if (node.escapedText === "#constructor") { + // Report error only if there are no parse errors in file + if (!file.parseDiagnostics.length) { + file.bindDiagnostics.push(createDiagnosticForNode(node, + Diagnostics.constructor_is_a_reserved_word, declarationNameToString(node))); } } + } - function checkStrictModeBinaryExpression(node: BinaryExpression) { - if (inStrictMode && isLeftHandSideExpression(node.left) && isAssignmentOperator(node.operatorToken.kind)) { - // ECMA 262 (Annex C) The identifier eval or arguments may not appear as the LeftHandSideExpression of an - // Assignment operator(11.13) or of a PostfixExpression(11.3) - checkStrictModeEvalOrArguments(node, node.left as Identifier); - } + function checkStrictModeBinaryExpression(node: BinaryExpression) { + if (inStrictMode && isLeftHandSideExpression(node.left) && isAssignmentOperator(node.operatorToken.kind)) { + // ECMA 262 (Annex C) The identifier eval or arguments may not appear as the LeftHandSideExpression of an + // Assignment operator(11.13) or of a PostfixExpression(11.3) + checkStrictModeEvalOrArguments(node, node.left as Identifier); } + } - function checkStrictModeCatchClause(node: CatchClause) { - // It is a SyntaxError if a TryStatement with a Catch occurs within strict code and the Identifier of the - // Catch production is eval or arguments - if (inStrictMode && node.variableDeclaration) { - checkStrictModeEvalOrArguments(node, node.variableDeclaration.name); - } + function checkStrictModeCatchClause(node: CatchClause) { + // It is a SyntaxError if a TryStatement with a Catch occurs within strict code and the Identifier of the + // Catch production is eval or arguments + if (inStrictMode && node.variableDeclaration) { + checkStrictModeEvalOrArguments(node, node.variableDeclaration.name); + } + } + + function checkStrictModeDeleteExpression(node: DeleteExpression) { + // Grammar checking + if (inStrictMode && node.expression.kind === SyntaxKind.Identifier) { + // When a delete operator occurs within strict mode code, a SyntaxError is thrown if its + // UnaryExpression is a direct reference to a variable, function argument, or function name + const span = getErrorSpanForNode(file, node.expression); + file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, Diagnostics.delete_cannot_be_called_on_an_identifier_in_strict_mode)); } + } + + function isEvalOrArgumentsIdentifier(node: Node): boolean { + return isIdentifier(node) && (node.escapedText === "eval" || node.escapedText === "arguments"); + } - function checkStrictModeDeleteExpression(node: DeleteExpression) { - // Grammar checking - if (inStrictMode && node.expression.kind === SyntaxKind.Identifier) { - // When a delete operator occurs within strict mode code, a SyntaxError is thrown if its - // UnaryExpression is a direct reference to a variable, function argument, or function name - const span = getErrorSpanForNode(file, node.expression); - file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, Diagnostics.delete_cannot_be_called_on_an_identifier_in_strict_mode)); + function checkStrictModeEvalOrArguments(contextNode: Node, name: Node | undefined) { + if (name && name.kind === SyntaxKind.Identifier) { + const identifier = name as Identifier; + if (isEvalOrArgumentsIdentifier(identifier)) { + // We check first if the name is inside class declaration or class expression; if so give explicit message + // otherwise report generic error message. + const span = getErrorSpanForNode(file, name); + file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, + getStrictModeEvalOrArgumentsMessage(contextNode), idText(identifier))); } } + } - function isEvalOrArgumentsIdentifier(node: Node): boolean { - return isIdentifier(node) && (node.escapedText === "eval" || node.escapedText === "arguments"); + function getStrictModeEvalOrArgumentsMessage(node: Node) { + // Provide specialized messages to help the user understand why we think they're in + // strict mode. + if (getContainingClass(node)) { + return Diagnostics.Code_contained_in_a_class_is_evaluated_in_JavaScript_s_strict_mode_which_does_not_allow_this_use_of_0_For_more_information_see_https_Colon_Slash_Slashdeveloper_mozilla_org_Slashen_US_Slashdocs_SlashWeb_SlashJavaScript_SlashReference_SlashStrict_mode; } - function checkStrictModeEvalOrArguments(contextNode: Node, name: Node | undefined) { - if (name && name.kind === SyntaxKind.Identifier) { - const identifier = name as Identifier; - if (isEvalOrArgumentsIdentifier(identifier)) { - // We check first if the name is inside class declaration or class expression; if so give explicit message - // otherwise report generic error message. - const span = getErrorSpanForNode(file, name); - file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, - getStrictModeEvalOrArgumentsMessage(contextNode), idText(identifier))); - } - } + if (file.externalModuleIndicator) { + return Diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode; } - function getStrictModeEvalOrArgumentsMessage(node: Node) { - // Provide specialized messages to help the user understand why we think they're in - // strict mode. - if (getContainingClass(node)) { - return Diagnostics.Code_contained_in_a_class_is_evaluated_in_JavaScript_s_strict_mode_which_does_not_allow_this_use_of_0_For_more_information_see_https_Colon_Slash_Slashdeveloper_mozilla_org_Slashen_US_Slashdocs_SlashWeb_SlashJavaScript_SlashReference_SlashStrict_mode; - } + return Diagnostics.Invalid_use_of_0_in_strict_mode; + } - if (file.externalModuleIndicator) { - return Diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode; - } + function checkStrictModeFunctionName(node: FunctionLikeDeclaration) { + if (inStrictMode) { + // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a strict mode FunctionDeclaration or FunctionExpression (13.1)) + checkStrictModeEvalOrArguments(node, node.name); + } + } - return Diagnostics.Invalid_use_of_0_in_strict_mode; + function getStrictModeBlockScopeFunctionDeclarationMessage(node: Node) { + // Provide specialized messages to help the user understand why we think they're in + // strict mode. + if (getContainingClass(node)) { + return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5_Class_definitions_are_automatically_in_strict_mode; } - function checkStrictModeFunctionName(node: FunctionLikeDeclaration) { - if (inStrictMode) { - // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a strict mode FunctionDeclaration or FunctionExpression (13.1)) - checkStrictModeEvalOrArguments(node, node.name); - } + if (file.externalModuleIndicator) { + return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5_Modules_are_automatically_in_strict_mode; } - function getStrictModeBlockScopeFunctionDeclarationMessage(node: Node) { - // Provide specialized messages to help the user understand why we think they're in - // strict mode. - if (getContainingClass(node)) { - return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5_Class_definitions_are_automatically_in_strict_mode; - } + return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5; + } - if (file.externalModuleIndicator) { - return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5_Modules_are_automatically_in_strict_mode; + function checkStrictModeFunctionDeclaration(node: FunctionDeclaration) { + if (languageVersion < ScriptTarget.ES2015) { + // Report error if function is not top level function declaration + if (blockScopeContainer.kind !== SyntaxKind.SourceFile && + blockScopeContainer.kind !== SyntaxKind.ModuleDeclaration && + !isFunctionLikeOrClassStaticBlockDeclaration(blockScopeContainer)) { + // We check first if the name is inside class declaration or class expression; if so give explicit message + // otherwise report generic error message. + const errorSpan = getErrorSpanForNode(file, node); + file.bindDiagnostics.push(createFileDiagnostic(file, errorSpan.start, errorSpan.length, + getStrictModeBlockScopeFunctionDeclarationMessage(node))); } - - return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5; } + } - function checkStrictModeFunctionDeclaration(node: FunctionDeclaration) { - if (languageVersion < ScriptTarget.ES2015) { - // Report error if function is not top level function declaration - if (blockScopeContainer.kind !== SyntaxKind.SourceFile && - blockScopeContainer.kind !== SyntaxKind.ModuleDeclaration && - !isFunctionLikeOrClassStaticBlockDeclaration(blockScopeContainer)) { - // We check first if the name is inside class declaration or class expression; if so give explicit message - // otherwise report generic error message. - const errorSpan = getErrorSpanForNode(file, node); - file.bindDiagnostics.push(createFileDiagnostic(file, errorSpan.start, errorSpan.length, - getStrictModeBlockScopeFunctionDeclarationMessage(node))); - } - } + function checkStrictModeNumericLiteral(node: NumericLiteral) { + if (inStrictMode && node.numericLiteralFlags & TokenFlags.Octal) { + file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Octal_literals_are_not_allowed_in_strict_mode)); } + } - function checkStrictModeNumericLiteral(node: NumericLiteral) { - if (inStrictMode && node.numericLiteralFlags & TokenFlags.Octal) { - file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Octal_literals_are_not_allowed_in_strict_mode)); - } + function checkStrictModePostfixUnaryExpression(node: PostfixUnaryExpression) { + // Grammar checking + // The identifier eval or arguments may not appear as the LeftHandSideExpression of an + // Assignment operator(11.13) or of a PostfixExpression(11.3) or as the UnaryExpression + // operated upon by a Prefix Increment(11.4.4) or a Prefix Decrement(11.4.5) operator. + if (inStrictMode) { + checkStrictModeEvalOrArguments(node, node.operand as Identifier); } + } - function checkStrictModePostfixUnaryExpression(node: PostfixUnaryExpression) { - // Grammar checking - // The identifier eval or arguments may not appear as the LeftHandSideExpression of an - // Assignment operator(11.13) or of a PostfixExpression(11.3) or as the UnaryExpression - // operated upon by a Prefix Increment(11.4.4) or a Prefix Decrement(11.4.5) operator. - if (inStrictMode) { + function checkStrictModePrefixUnaryExpression(node: PrefixUnaryExpression) { + // Grammar checking + if (inStrictMode) { + if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { checkStrictModeEvalOrArguments(node, node.operand as Identifier); } } + } - function checkStrictModePrefixUnaryExpression(node: PrefixUnaryExpression) { - // Grammar checking - if (inStrictMode) { - if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { - checkStrictModeEvalOrArguments(node, node.operand as Identifier); - } - } + function checkStrictModeWithStatement(node: WithStatement) { + // Grammar checking for withStatement + if (inStrictMode) { + errorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_strict_mode); } + } - function checkStrictModeWithStatement(node: WithStatement) { - // Grammar checking for withStatement - if (inStrictMode) { - errorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_strict_mode); + function checkStrictModeLabeledStatement(node: LabeledStatement) { + // Grammar checking for labeledStatement + if (inStrictMode && getEmitScriptTarget(options) >= ScriptTarget.ES2015) { + if (isDeclarationStatement(node.statement) || isVariableStatement(node.statement)) { + errorOnFirstToken(node.label, Diagnostics.A_label_is_not_allowed_here); } } + } - function checkStrictModeLabeledStatement(node: LabeledStatement) { - // Grammar checking for labeledStatement - if (inStrictMode && getEmitScriptTarget(options) >= ScriptTarget.ES2015) { - if (isDeclarationStatement(node.statement) || isVariableStatement(node.statement)) { - errorOnFirstToken(node.label, Diagnostics.A_label_is_not_allowed_here); - } - } - } + function errorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any) { + const span = getSpanOfTokenAtPosition(file, node.pos); + file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, message, arg0, arg1, arg2)); + } - function errorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any) { - const span = getSpanOfTokenAtPosition(file, node.pos); - file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, message, arg0, arg1, arg2)); - } + function errorOrSuggestionOnNode(isError: boolean, node: Node, message: DiagnosticMessage): void { + errorOrSuggestionOnRange(isError, node, node, message); + } - function errorOrSuggestionOnNode(isError: boolean, node: Node, message: DiagnosticMessage): void { - errorOrSuggestionOnRange(isError, node, node, message); - } + function errorOrSuggestionOnRange(isError: boolean, startNode: Node, endNode: Node, message: DiagnosticMessage): void { + addErrorOrSuggestionDiagnostic(isError, { pos: getTokenPosOfNode(startNode, file), end: endNode.end }, message); + } - function errorOrSuggestionOnRange(isError: boolean, startNode: Node, endNode: Node, message: DiagnosticMessage): void { - addErrorOrSuggestionDiagnostic(isError, { pos: getTokenPosOfNode(startNode, file), end: endNode.end }, message); + function addErrorOrSuggestionDiagnostic(isError: boolean, range: TextRange, message: DiagnosticMessage): void { + const diag = createFileDiagnostic(file, range.pos, range.end - range.pos, message); + if (isError) { + file.bindDiagnostics.push(diag); } - - function addErrorOrSuggestionDiagnostic(isError: boolean, range: TextRange, message: DiagnosticMessage): void { - const diag = createFileDiagnostic(file, range.pos, range.end - range.pos, message); - if (isError) { - file.bindDiagnostics.push(diag); - } - else { - file.bindSuggestionDiagnostics = append(file.bindSuggestionDiagnostics, { ...diag, category: DiagnosticCategory.Suggestion }); - } + else { + file.bindSuggestionDiagnostics = append(file.bindSuggestionDiagnostics, { ...diag, category: DiagnosticCategory.Suggestion }); } + } - function bind(node: Node | undefined): void { - if (!node) { - return; - } - setParent(node, parent); - const saveInStrictMode = inStrictMode; + function bind(node: Node | undefined): void { + if (!node) { + return; + } + setParent(node, parent); + const saveInStrictMode = inStrictMode; - // Even though in the AST the jsdoc @typedef node belongs to the current node, - // its symbol might be in the same scope with the current node's symbol. Consider: - // - // /** @typedef {string | number} MyType */ - // function foo(); - // - // Here the current node is "foo", which is a container, but the scope of "MyType" should - // not be inside "foo". Therefore we always bind @typedef before bind the parent node, - // and skip binding this tag later when binding all the other jsdoc tags. + // Even though in the AST the jsdoc @typedef node belongs to the current node, + // its symbol might be in the same scope with the current node's symbol. Consider: + // + // /** @typedef {string | number} MyType */ + // function foo(); + // + // Here the current node is "foo", which is a container, but the scope of "MyType" should + // not be inside "foo". Therefore we always bind @typedef before bind the parent node, + // and skip binding this tag later when binding all the other jsdoc tags. - // First we bind declaration nodes to a symbol if possible. We'll both create a symbol - // and then potentially add the symbol to an appropriate symbol table. Possible - // destination symbol tables are: - // - // 1) The 'exports' table of the current container's symbol. - // 2) The 'members' table of the current container's symbol. - // 3) The 'locals' table of the current container. - // - // However, not all symbols will end up in any of these tables. 'Anonymous' symbols - // (like TypeLiterals for example) will not be put in any table. - bindWorker(node); - // Then we recurse into the children of the node to bind them as well. For certain - // symbols we do specialized work when we recurse. For example, we'll keep track of - // the current 'container' node when it changes. This helps us know which symbol table - // a local should go into for example. Since terminal nodes are known not to have - // children, as an optimization we don't process those. - if (node.kind > SyntaxKind.LastToken) { - const saveParent = parent; - parent = node; - const containerFlags = getContainerFlags(node); - if (containerFlags === ContainerFlags.None) { - bindChildren(node); - } - else { - bindContainer(node, containerFlags); - } - parent = saveParent; + // First we bind declaration nodes to a symbol if possible. We'll both create a symbol + // and then potentially add the symbol to an appropriate symbol table. Possible + // destination symbol tables are: + // + // 1) The 'exports' table of the current container's symbol. + // 2) The 'members' table of the current container's symbol. + // 3) The 'locals' table of the current container. + // + // However, not all symbols will end up in any of these tables. 'Anonymous' symbols + // (like TypeLiterals for example) will not be put in any table. + bindWorker(node); + // Then we recurse into the children of the node to bind them as well. For certain + // symbols we do specialized work when we recurse. For example, we'll keep track of + // the current 'container' node when it changes. This helps us know which symbol table + // a local should go into for example. Since terminal nodes are known not to have + // children, as an optimization we don't process those. + if (node.kind > SyntaxKind.LastToken) { + const saveParent = parent; + parent = node; + const containerFlags = getContainerFlags(node); + if (containerFlags === ContainerFlags.None) { + bindChildren(node); } else { - const saveParent = parent; - if (node.kind === SyntaxKind.EndOfFileToken) parent = node; - bindJSDoc(node); - parent = saveParent; + bindContainer(node, containerFlags); } - inStrictMode = saveInStrictMode; + parent = saveParent; } + else { + const saveParent = parent; + if (node.kind === SyntaxKind.EndOfFileToken) parent = node; + bindJSDoc(node); + parent = saveParent; + } + inStrictMode = saveInStrictMode; + } - function bindJSDoc(node: Node) { - if (hasJSDocNodes(node)) { - if (isInJSFile(node)) { - for (const j of node.jsDoc!) { - bind(j); - } + function bindJSDoc(node: Node) { + if (hasJSDocNodes(node)) { + if (isInJSFile(node)) { + for (const j of node.jsDoc!) { + bind(j); } - else { - for (const j of node.jsDoc!) { - setParent(j, node); - setParentRecursive(j, /*incremental*/ false); - } + } + else { + for (const j of node.jsDoc!) { + setParent(j, node); + setParentRecursive(j, /*incremental*/ false); } } } + } - function updateStrictModeStatementList(statements: NodeArray) { - if (!inStrictMode) { - for (const statement of statements) { - if (!isPrologueDirective(statement)) { - return; - } + function updateStrictModeStatementList(statements: NodeArray) { + if (!inStrictMode) { + for (const statement of statements) { + if (!isPrologueDirective(statement)) { + return; + } - if (isUseStrictPrologueDirective(statement as ExpressionStatement)) { - inStrictMode = true; - return; - } + if (isUseStrictPrologueDirective(statement as ExpressionStatement)) { + inStrictMode = true; + return; } } } + } - /// Should be called only on prologue directives (isPrologueDirective(node) should be true) - function isUseStrictPrologueDirective(node: ExpressionStatement): boolean { - const nodeText = getSourceTextOfNodeFromSourceFile(file, node.expression); + /// Should be called only on prologue directives (isPrologueDirective(node) should be true) + function isUseStrictPrologueDirective(node: ExpressionStatement): boolean { + const nodeText = getSourceTextOfNodeFromSourceFile(file, node.expression); - // Note: the node text must be exactly "use strict" or 'use strict'. It is not ok for the - // string to contain unicode escapes (as per ES5). - return nodeText === '"use strict"' || nodeText === "'use strict'"; - } + // Note: the node text must be exactly "use strict" or 'use strict'. It is not ok for the + // string to contain unicode escapes (as per ES5). + return nodeText === '"use strict"' || nodeText === "'use strict'"; + } - function bindWorker(node: Node) { - switch (node.kind) { - /* Strict mode checks */ - case SyntaxKind.Identifier: - // for typedef type names with namespaces, bind the new jsdoc type symbol here - // because it requires all containing namespaces to be in effect, namely the - // current "blockScopeContainer" needs to be set to its immediate namespace parent. - if ((node as Identifier).isInJSDocNamespace) { - let parentNode = node.parent; - while (parentNode && !isJSDocTypeAlias(parentNode)) { - parentNode = parentNode.parent; - } - bindBlockScopedDeclaration(parentNode as Declaration, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - break; - } - // falls through - case SyntaxKind.ThisKeyword: - if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) { - node.flowNode = currentFlow; - } - return checkContextualIdentifier(node as Identifier); - case SyntaxKind.QualifiedName: - if (currentFlow && isPartOfTypeQuery(node)) { - node.flowNode = currentFlow; + function bindWorker(node: Node) { + switch (node.kind) { + /* Strict mode checks */ + case SyntaxKind.Identifier: + // for typedef type names with namespaces, bind the new jsdoc type symbol here + // because it requires all containing namespaces to be in effect, namely the + // current "blockScopeContainer" needs to be set to its immediate namespace parent. + if ((node as Identifier).isInJSDocNamespace) { + let parentNode = node.parent; + while (parentNode && !isJSDocTypeAlias(parentNode)) { + parentNode = parentNode.parent; } + bindBlockScopedDeclaration(parentNode as Declaration, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); break; - case SyntaxKind.MetaProperty: - case SyntaxKind.SuperKeyword: + } + // falls through + case SyntaxKind.ThisKeyword: + if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) { node.flowNode = currentFlow; - break; - case SyntaxKind.PrivateIdentifier: - return checkPrivateIdentifier(node as PrivateIdentifier); - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - const expr = node as PropertyAccessExpression | ElementAccessExpression; - if (currentFlow && isNarrowableReference(expr)) { - expr.flowNode = currentFlow; - } - if (isSpecialPropertyDeclaration(expr)) { - bindSpecialPropertyDeclaration(expr); - } - if (isInJSFile(expr) && - file.commonJsModuleIndicator && - isModuleExportsAccessExpression(expr) && - !lookupSymbolForName(blockScopeContainer, "module" as __String)) { - declareSymbol(file.locals!, /*parent*/ undefined, expr.expression, - SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes); - } - break; - case SyntaxKind.BinaryExpression: - const specialKind = getAssignmentDeclarationKind(node as BinaryExpression); - switch (specialKind) { - case AssignmentDeclarationKind.ExportsProperty: - bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression); - break; - case AssignmentDeclarationKind.ModuleExports: - bindModuleExportsAssignment(node as BindablePropertyAssignmentExpression); - break; - case AssignmentDeclarationKind.PrototypeProperty: - bindPrototypePropertyAssignment((node as BindableStaticPropertyAssignmentExpression).left, node); - break; - case AssignmentDeclarationKind.Prototype: - bindPrototypeAssignment(node as BindableStaticPropertyAssignmentExpression); - break; - case AssignmentDeclarationKind.ThisProperty: - bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression); - break; - case AssignmentDeclarationKind.Property: - const expression = ((node as BinaryExpression).left as AccessExpression).expression; - if (isInJSFile(node) && isIdentifier(expression)) { - const symbol = lookupSymbolForName(blockScopeContainer, expression.escapedText); - if (isThisInitializedDeclaration(symbol?.valueDeclaration)) { - bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression); - break; - } - } - bindSpecialPropertyAssignment(node as BindablePropertyAssignmentExpression); - break; - case AssignmentDeclarationKind.None: - // Nothing to do - break; - default: - Debug.fail("Unknown binary expression special property assignment kind"); - } - return checkStrictModeBinaryExpression(node as BinaryExpression); - case SyntaxKind.CatchClause: - return checkStrictModeCatchClause(node as CatchClause); - case SyntaxKind.DeleteExpression: - return checkStrictModeDeleteExpression(node as DeleteExpression); - case SyntaxKind.NumericLiteral: - return checkStrictModeNumericLiteral(node as NumericLiteral); - case SyntaxKind.PostfixUnaryExpression: - return checkStrictModePostfixUnaryExpression(node as PostfixUnaryExpression); - case SyntaxKind.PrefixUnaryExpression: - return checkStrictModePrefixUnaryExpression(node as PrefixUnaryExpression); - case SyntaxKind.WithStatement: - return checkStrictModeWithStatement(node as WithStatement); - case SyntaxKind.LabeledStatement: - return checkStrictModeLabeledStatement(node as LabeledStatement); - case SyntaxKind.ThisType: - seenThisKeyword = true; - return; - case SyntaxKind.TypePredicate: - break; // Binding the children will handle everything - case SyntaxKind.TypeParameter: - return bindTypeParameter(node as TypeParameterDeclaration); - case SyntaxKind.Parameter: - return bindParameter(node as ParameterDeclaration); - case SyntaxKind.VariableDeclaration: - return bindVariableDeclarationOrBindingElement(node as VariableDeclaration); - case SyntaxKind.BindingElement: + } + return checkContextualIdentifier(node as Identifier); + case SyntaxKind.QualifiedName: + if (currentFlow && isPartOfTypeQuery(node)) { node.flowNode = currentFlow; - return bindVariableDeclarationOrBindingElement(node as BindingElement); - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return bindPropertyWorker(node as PropertyDeclaration | PropertySignature); - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.Property, SymbolFlags.PropertyExcludes); - case SyntaxKind.EnumMember: - return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.EnumMember, SymbolFlags.EnumMemberExcludes); - - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - return declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Signature, SymbolFlags.None); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - // If this is an ObjectLiteralExpression method, then it sits in the same space - // as other properties in the object literal. So we use SymbolFlags.PropertyExcludes - // so that it will conflict with any other object literal members with the same - // name. - return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.Method | ((node as MethodDeclaration).questionToken ? SymbolFlags.Optional : SymbolFlags.None), - isObjectLiteralMethod(node) ? SymbolFlags.PropertyExcludes : SymbolFlags.MethodExcludes); - case SyntaxKind.FunctionDeclaration: - return bindFunctionDeclaration(node as FunctionDeclaration); - case SyntaxKind.Constructor: - return declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Constructor, /*symbolExcludes:*/ SymbolFlags.None); - case SyntaxKind.GetAccessor: - return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.GetAccessor, SymbolFlags.GetAccessorExcludes); - case SyntaxKind.SetAccessor: - return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.SetAccessor, SymbolFlags.SetAccessorExcludes); - case SyntaxKind.FunctionType: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.JSDocSignature: - case SyntaxKind.ConstructorType: - return bindFunctionOrConstructorType(node as SignatureDeclaration | JSDocSignature); - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.MappedType: - return bindAnonymousTypeWorker(node as TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral); - case SyntaxKind.JSDocClassTag: - return bindJSDocClassTag(node as JSDocClassTag); - case SyntaxKind.ObjectLiteralExpression: - return bindObjectLiteralExpression(node as ObjectLiteralExpression); - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return bindFunctionExpression(node as FunctionExpression); - - case SyntaxKind.CallExpression: - const assignmentKind = getAssignmentDeclarationKind(node as CallExpression); - switch (assignmentKind) { - case AssignmentDeclarationKind.ObjectDefinePropertyValue: - return bindObjectDefinePropertyAssignment(node as BindableObjectDefinePropertyCall); - case AssignmentDeclarationKind.ObjectDefinePropertyExports: - return bindObjectDefinePropertyExport(node as BindableObjectDefinePropertyCall); - case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: - return bindObjectDefinePrototypeProperty(node as BindableObjectDefinePropertyCall); - case AssignmentDeclarationKind.None: - break; // Nothing to do - default: - return Debug.fail("Unknown call expression assignment declaration kind"); - } - if (isInJSFile(node)) { - bindCallExpression(node as CallExpression); - } - break; + } + break; + case SyntaxKind.MetaProperty: + case SyntaxKind.SuperKeyword: + node.flowNode = currentFlow; + break; + case SyntaxKind.PrivateIdentifier: + return checkPrivateIdentifier(node as PrivateIdentifier); + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + const expr = node as PropertyAccessExpression | ElementAccessExpression; + if (currentFlow && isNarrowableReference(expr)) { + expr.flowNode = currentFlow; + } + if (isSpecialPropertyDeclaration(expr)) { + bindSpecialPropertyDeclaration(expr); + } + if (isInJSFile(expr) && + file.commonJsModuleIndicator && + isModuleExportsAccessExpression(expr) && + !lookupSymbolForName(blockScopeContainer, "module" as __String)) { + declareSymbol(file.locals!, /*parent*/ undefined, expr.expression, + SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes); + } + break; + case SyntaxKind.BinaryExpression: + const specialKind = getAssignmentDeclarationKind(node as BinaryExpression); + switch (specialKind) { + case AssignmentDeclarationKind.ExportsProperty: + bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression); + break; + case AssignmentDeclarationKind.ModuleExports: + bindModuleExportsAssignment(node as BindablePropertyAssignmentExpression); + break; + case AssignmentDeclarationKind.PrototypeProperty: + bindPrototypePropertyAssignment((node as BindableStaticPropertyAssignmentExpression).left, node); + break; + case AssignmentDeclarationKind.Prototype: + bindPrototypeAssignment(node as BindableStaticPropertyAssignmentExpression); + break; + case AssignmentDeclarationKind.ThisProperty: + bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression); + break; + case AssignmentDeclarationKind.Property: + const expression = ((node as BinaryExpression).left as AccessExpression).expression; + if (isInJSFile(node) && isIdentifier(expression)) { + const symbol = lookupSymbolForName(blockScopeContainer, expression.escapedText); + if (isThisInitializedDeclaration(symbol?.valueDeclaration)) { + bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression); + break; + } + } + bindSpecialPropertyAssignment(node as BindablePropertyAssignmentExpression); + break; + case AssignmentDeclarationKind.None: + // Nothing to do + break; + default: + Debug.fail("Unknown binary expression special property assignment kind"); + } + return checkStrictModeBinaryExpression(node as BinaryExpression); + case SyntaxKind.CatchClause: + return checkStrictModeCatchClause(node as CatchClause); + case SyntaxKind.DeleteExpression: + return checkStrictModeDeleteExpression(node as DeleteExpression); + case SyntaxKind.NumericLiteral: + return checkStrictModeNumericLiteral(node as NumericLiteral); + case SyntaxKind.PostfixUnaryExpression: + return checkStrictModePostfixUnaryExpression(node as PostfixUnaryExpression); + case SyntaxKind.PrefixUnaryExpression: + return checkStrictModePrefixUnaryExpression(node as PrefixUnaryExpression); + case SyntaxKind.WithStatement: + return checkStrictModeWithStatement(node as WithStatement); + case SyntaxKind.LabeledStatement: + return checkStrictModeLabeledStatement(node as LabeledStatement); + case SyntaxKind.ThisType: + seenThisKeyword = true; + return; + case SyntaxKind.TypePredicate: + break; // Binding the children will handle everything + case SyntaxKind.TypeParameter: + return bindTypeParameter(node as TypeParameterDeclaration); + case SyntaxKind.Parameter: + return bindParameter(node as ParameterDeclaration); + case SyntaxKind.VariableDeclaration: + return bindVariableDeclarationOrBindingElement(node as VariableDeclaration); + case SyntaxKind.BindingElement: + node.flowNode = currentFlow; + return bindVariableDeclarationOrBindingElement(node as BindingElement); + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return bindPropertyWorker(node as PropertyDeclaration | PropertySignature); + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.Property, SymbolFlags.PropertyExcludes); + case SyntaxKind.EnumMember: + return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.EnumMember, SymbolFlags.EnumMemberExcludes); + + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + return declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Signature, SymbolFlags.None); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + // If this is an ObjectLiteralExpression method, then it sits in the same space + // as other properties in the object literal. So we use SymbolFlags.PropertyExcludes + // so that it will conflict with any other object literal members with the same + // name. + return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.Method | ((node as MethodDeclaration).questionToken ? SymbolFlags.Optional : SymbolFlags.None), + isObjectLiteralMethod(node) ? SymbolFlags.PropertyExcludes : SymbolFlags.MethodExcludes); + case SyntaxKind.FunctionDeclaration: + return bindFunctionDeclaration(node as FunctionDeclaration); + case SyntaxKind.Constructor: + return declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Constructor, /*symbolExcludes:*/ SymbolFlags.None); + case SyntaxKind.GetAccessor: + return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.GetAccessor, SymbolFlags.GetAccessorExcludes); + case SyntaxKind.SetAccessor: + return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.SetAccessor, SymbolFlags.SetAccessorExcludes); + case SyntaxKind.FunctionType: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocSignature: + case SyntaxKind.ConstructorType: + return bindFunctionOrConstructorType(node as SignatureDeclaration | JSDocSignature); + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.MappedType: + return bindAnonymousTypeWorker(node as TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral); + case SyntaxKind.JSDocClassTag: + return bindJSDocClassTag(node as JSDocClassTag); + case SyntaxKind.ObjectLiteralExpression: + return bindObjectLiteralExpression(node as ObjectLiteralExpression); + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return bindFunctionExpression(node as FunctionExpression); + + case SyntaxKind.CallExpression: + const assignmentKind = getAssignmentDeclarationKind(node as CallExpression); + switch (assignmentKind) { + case AssignmentDeclarationKind.ObjectDefinePropertyValue: + return bindObjectDefinePropertyAssignment(node as BindableObjectDefinePropertyCall); + case AssignmentDeclarationKind.ObjectDefinePropertyExports: + return bindObjectDefinePropertyExport(node as BindableObjectDefinePropertyCall); + case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: + return bindObjectDefinePrototypeProperty(node as BindableObjectDefinePropertyCall); + case AssignmentDeclarationKind.None: + break; // Nothing to do + default: + return Debug.fail("Unknown call expression assignment declaration kind"); + } + if (isInJSFile(node)) { + bindCallExpression(node as CallExpression); + } + break; - // Members of classes, interfaces, and modules - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - // All classes are automatically in strict mode in ES6. - inStrictMode = true; - return bindClassLikeDeclaration(node as ClassLikeDeclaration); - case SyntaxKind.InterfaceDeclaration: - return bindBlockScopedDeclaration(node as Declaration, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes); - case SyntaxKind.TypeAliasDeclaration: - return bindBlockScopedDeclaration(node as Declaration, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - case SyntaxKind.EnumDeclaration: - return bindEnumDeclaration(node as EnumDeclaration); - case SyntaxKind.ModuleDeclaration: - return bindModuleDeclaration(node as ModuleDeclaration); - // Jsx-attributes - case SyntaxKind.JsxAttributes: - return bindJsxAttributes(node as JsxAttributes); - case SyntaxKind.JsxAttribute: - return bindJsxAttribute(node as JsxAttribute, SymbolFlags.Property, SymbolFlags.PropertyExcludes); - - // Imports and exports - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - return declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Alias, SymbolFlags.AliasExcludes); - case SyntaxKind.NamespaceExportDeclaration: - return bindNamespaceExportDeclaration(node as NamespaceExportDeclaration); - case SyntaxKind.ImportClause: - return bindImportClause(node as ImportClause); - case SyntaxKind.ExportDeclaration: - return bindExportDeclaration(node as ExportDeclaration); - case SyntaxKind.ExportAssignment: - return bindExportAssignment(node as ExportAssignment); - case SyntaxKind.SourceFile: - updateStrictModeStatementList((node as SourceFile).statements); - return bindSourceFileIfExternalModule(); - case SyntaxKind.Block: - if (!isFunctionLikeOrClassStaticBlockDeclaration(node.parent)) { - return; - } - // falls through - case SyntaxKind.ModuleBlock: - return updateStrictModeStatementList((node as Block | ModuleBlock).statements); + // Members of classes, interfaces, and modules + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + // All classes are automatically in strict mode in ES6. + inStrictMode = true; + return bindClassLikeDeclaration(node as ClassLikeDeclaration); + case SyntaxKind.InterfaceDeclaration: + return bindBlockScopedDeclaration(node as Declaration, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes); + case SyntaxKind.TypeAliasDeclaration: + return bindBlockScopedDeclaration(node as Declaration, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + case SyntaxKind.EnumDeclaration: + return bindEnumDeclaration(node as EnumDeclaration); + case SyntaxKind.ModuleDeclaration: + return bindModuleDeclaration(node as ModuleDeclaration); + // Jsx-attributes + case SyntaxKind.JsxAttributes: + return bindJsxAttributes(node as JsxAttributes); + case SyntaxKind.JsxAttribute: + return bindJsxAttribute(node as JsxAttribute, SymbolFlags.Property, SymbolFlags.PropertyExcludes); + + // Imports and exports + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + return declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Alias, SymbolFlags.AliasExcludes); + case SyntaxKind.NamespaceExportDeclaration: + return bindNamespaceExportDeclaration(node as NamespaceExportDeclaration); + case SyntaxKind.ImportClause: + return bindImportClause(node as ImportClause); + case SyntaxKind.ExportDeclaration: + return bindExportDeclaration(node as ExportDeclaration); + case SyntaxKind.ExportAssignment: + return bindExportAssignment(node as ExportAssignment); + case SyntaxKind.SourceFile: + updateStrictModeStatementList((node as SourceFile).statements); + return bindSourceFileIfExternalModule(); + case SyntaxKind.Block: + if (!isFunctionLikeOrClassStaticBlockDeclaration(node.parent)) { + return; + } + // falls through + case SyntaxKind.ModuleBlock: + return updateStrictModeStatementList((node as Block | ModuleBlock).statements); - case SyntaxKind.JSDocParameterTag: - if (node.parent.kind === SyntaxKind.JSDocSignature) { - return bindParameter(node as JSDocParameterTag); - } - if (node.parent.kind !== SyntaxKind.JSDocTypeLiteral) { - break; - } - // falls through - case SyntaxKind.JSDocPropertyTag: - const propTag = node as JSDocPropertyLikeTag; - const flags = propTag.isBracketed || propTag.typeExpression && propTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType ? - SymbolFlags.Property | SymbolFlags.Optional : - SymbolFlags.Property; - return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes); - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag); - } + case SyntaxKind.JSDocParameterTag: + if (node.parent.kind === SyntaxKind.JSDocSignature) { + return bindParameter(node as JSDocParameterTag); + } + if (node.parent.kind !== SyntaxKind.JSDocTypeLiteral) { + break; + } + // falls through + case SyntaxKind.JSDocPropertyTag: + const propTag = node as JSDocPropertyLikeTag; + const flags = propTag.isBracketed || propTag.typeExpression && propTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType ? + SymbolFlags.Property | SymbolFlags.Optional : + SymbolFlags.Property; + return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes); + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag); } + } - function bindPropertyWorker(node: PropertyDeclaration | PropertySignature) { - return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); - } + function bindPropertyWorker(node: PropertyDeclaration | PropertySignature) { + return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); + } - function bindAnonymousTypeWorker(node: TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral) { - return bindAnonymousDeclaration(node as Declaration, SymbolFlags.TypeLiteral, InternalSymbolName.Type); - } + function bindAnonymousTypeWorker(node: TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral) { + return bindAnonymousDeclaration(node as Declaration, SymbolFlags.TypeLiteral, InternalSymbolName.Type); + } - function bindSourceFileIfExternalModule() { - setExportContextFlag(file); - if (isExternalModule(file)) { - bindSourceFileAsExternalModule(); - } - else if (isJsonSourceFile(file)) { - bindSourceFileAsExternalModule(); - // Create symbol equivalent for the module.exports = {} - const originalSymbol = file.symbol; - declareSymbol(file.symbol.exports!, file.symbol, file, SymbolFlags.Property, SymbolFlags.All); - file.symbol = originalSymbol; - } + function bindSourceFileIfExternalModule() { + setExportContextFlag(file); + if (isExternalModule(file)) { + bindSourceFileAsExternalModule(); } - - function bindSourceFileAsExternalModule() { - bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName)}"` as __String); + else if (isJsonSourceFile(file)) { + bindSourceFileAsExternalModule(); + // Create symbol equivalent for the module.exports = {} + const originalSymbol = file.symbol; + declareSymbol(file.symbol.exports!, file.symbol, file, SymbolFlags.Property, SymbolFlags.All); + file.symbol = originalSymbol; } + } - function bindExportAssignment(node: ExportAssignment) { - if (!container.symbol || !container.symbol.exports) { - // Incorrect export assignment in some sort of block construct - bindAnonymousDeclaration(node, SymbolFlags.Value, getDeclarationName(node)!); - } - else { - const flags = exportAssignmentIsAlias(node) - // An export default clause with an EntityNameExpression or a class expression exports all meanings of that identifier or expression; - ? SymbolFlags.Alias - // An export default clause with any other expression exports a value - : SymbolFlags.Property; - // If there is an `export default x;` alias declaration, can't `export default` anything else. - // (In contrast, you can still have `export default function f() {}` and `export default interface I {}`.) - const symbol = declareSymbol(container.symbol.exports, container.symbol, node, flags, SymbolFlags.All); + function bindSourceFileAsExternalModule() { + bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName)}"` as __String); + } - if (node.isExportEquals) { - // Will be an error later, since the module already has other exports. Just make sure this has a valueDeclaration set. - setValueDeclaration(symbol, node); - } + function bindExportAssignment(node: ExportAssignment) { + if (!container.symbol || !container.symbol.exports) { + // Incorrect export assignment in some sort of block construct + bindAnonymousDeclaration(node, SymbolFlags.Value, getDeclarationName(node)!); + } + else { + const flags = exportAssignmentIsAlias(node) + // An export default clause with an EntityNameExpression or a class expression exports all meanings of that identifier or expression; + ? SymbolFlags.Alias + // An export default clause with any other expression exports a value + : SymbolFlags.Property; + // If there is an `export default x;` alias declaration, can't `export default` anything else. + // (In contrast, you can still have `export default function f() {}` and `export default interface I {}`.) + const symbol = declareSymbol(container.symbol.exports, container.symbol, node, flags, SymbolFlags.All); + + if (node.isExportEquals) { + // Will be an error later, since the module already has other exports. Just make sure this has a valueDeclaration set. + setValueDeclaration(symbol, node); } } + } - function bindNamespaceExportDeclaration(node: NamespaceExportDeclaration) { - if (node.modifiers && node.modifiers.length) { - file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Modifiers_cannot_appear_here)); - } - const diag = !isSourceFile(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_at_top_level - : !isExternalModule(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_in_module_files - : !node.parent.isDeclarationFile ? Diagnostics.Global_module_exports_may_only_appear_in_declaration_files - : undefined; - if (diag) { - file.bindDiagnostics.push(createDiagnosticForNode(node, diag)); - } - else { - file.symbol.globalExports = file.symbol.globalExports || createSymbolTable(); - declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); - } + function bindNamespaceExportDeclaration(node: NamespaceExportDeclaration) { + if (node.modifiers && node.modifiers.length) { + file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Modifiers_cannot_appear_here)); } + const diag = !isSourceFile(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_at_top_level + : !isExternalModule(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_in_module_files + : !node.parent.isDeclarationFile ? Diagnostics.Global_module_exports_may_only_appear_in_declaration_files + : undefined; + if (diag) { + file.bindDiagnostics.push(createDiagnosticForNode(node, diag)); + } + else { + file.symbol.globalExports = file.symbol.globalExports || createSymbolTable(); + declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); + } + } - function bindExportDeclaration(node: ExportDeclaration) { - if (!container.symbol || !container.symbol.exports) { - // Export * in some sort of block construct - bindAnonymousDeclaration(node, SymbolFlags.ExportStar, getDeclarationName(node)!); - } - else if (!node.exportClause) { - // All export * declarations are collected in an __export symbol - declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.ExportStar, SymbolFlags.None); - } - else if (isNamespaceExport(node.exportClause)) { - // declareSymbol walks up parents to find name text, parent _must_ be set - // but won't be set by the normal binder walk until `bindChildren` later on. - setParent(node.exportClause, node); - declareSymbol(container.symbol.exports, container.symbol, node.exportClause, SymbolFlags.Alias, SymbolFlags.AliasExcludes); - } + function bindExportDeclaration(node: ExportDeclaration) { + if (!container.symbol || !container.symbol.exports) { + // Export * in some sort of block construct + bindAnonymousDeclaration(node, SymbolFlags.ExportStar, getDeclarationName(node)!); + } + else if (!node.exportClause) { + // All export * declarations are collected in an __export symbol + declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.ExportStar, SymbolFlags.None); } + else if (isNamespaceExport(node.exportClause)) { + // declareSymbol walks up parents to find name text, parent _must_ be set + // but won't be set by the normal binder walk until `bindChildren` later on. + setParent(node.exportClause, node); + declareSymbol(container.symbol.exports, container.symbol, node.exportClause, SymbolFlags.Alias, SymbolFlags.AliasExcludes); + } + } - function bindImportClause(node: ImportClause) { - if (node.name) { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); - } + function bindImportClause(node: ImportClause) { + if (node.name) { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } + } - function setCommonJsModuleIndicator(node: Node) { - if (file.externalModuleIndicator) { - return false; - } - if (!file.commonJsModuleIndicator) { - file.commonJsModuleIndicator = node; - bindSourceFileAsExternalModule(); - } - return true; + function setCommonJsModuleIndicator(node: Node) { + if (file.externalModuleIndicator) { + return false; + } + if (!file.commonJsModuleIndicator) { + file.commonJsModuleIndicator = node; + bindSourceFileAsExternalModule(); } + return true; + } - function bindObjectDefinePropertyExport(node: BindableObjectDefinePropertyCall) { - if (!setCommonJsModuleIndicator(node)) { - return; - } - const symbol = forEachIdentifierInEntityName(node.arguments[0], /*parent*/ undefined, (id, symbol) => { - if (symbol) { - addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); - } - return symbol; - }); + function bindObjectDefinePropertyExport(node: BindableObjectDefinePropertyCall) { + if (!setCommonJsModuleIndicator(node)) { + return; + } + const symbol = forEachIdentifierInEntityName(node.arguments[0], /*parent*/ undefined, (id, symbol) => { if (symbol) { - const flags = SymbolFlags.Property | SymbolFlags.ExportValue; - declareSymbol(symbol.exports!, symbol, node, flags, SymbolFlags.None); + addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); } + return symbol; + }); + if (symbol) { + const flags = SymbolFlags.Property | SymbolFlags.ExportValue; + declareSymbol(symbol.exports!, symbol, node, flags, SymbolFlags.None); } + } - function bindExportsPropertyAssignment(node: BindableStaticPropertyAssignmentExpression) { - // When we create a property via 'exports.foo = bar', the 'exports.foo' property access - // expression is the declaration - if (!setCommonJsModuleIndicator(node)) { - return; - } - const symbol = forEachIdentifierInEntityName(node.left.expression, /*parent*/ undefined, (id, symbol) => { - if (symbol) { - addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); - } - return symbol; - }); + function bindExportsPropertyAssignment(node: BindableStaticPropertyAssignmentExpression) { + // When we create a property via 'exports.foo = bar', the 'exports.foo' property access + // expression is the declaration + if (!setCommonJsModuleIndicator(node)) { + return; + } + const symbol = forEachIdentifierInEntityName(node.left.expression, /*parent*/ undefined, (id, symbol) => { if (symbol) { - const isAlias = isAliasableExpression(node.right) && (isExportsIdentifier(node.left.expression) || isModuleExportsAccessExpression(node.left.expression)); - const flags = isAlias ? SymbolFlags.Alias : SymbolFlags.Property | SymbolFlags.ExportValue; - setParent(node.left, node); - declareSymbol(symbol.exports!, symbol, node.left, flags, SymbolFlags.None); + addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); } + return symbol; + }); + if (symbol) { + const isAlias = isAliasableExpression(node.right) && (isExportsIdentifier(node.left.expression) || isModuleExportsAccessExpression(node.left.expression)); + const flags = isAlias ? SymbolFlags.Alias : SymbolFlags.Property | SymbolFlags.ExportValue; + setParent(node.left, node); + declareSymbol(symbol.exports!, symbol, node.left, flags, SymbolFlags.None); } + } - function bindModuleExportsAssignment(node: BindablePropertyAssignmentExpression) { - // A common practice in node modules is to set 'export = module.exports = {}', this ensures that 'exports' - // is still pointing to 'module.exports'. - // We do not want to consider this as 'export=' since a module can have only one of these. - // Similarly we do not want to treat 'module.exports = exports' as an 'export='. - if (!setCommonJsModuleIndicator(node)) { - return; - } - const assignedExpression = getRightMostAssignedExpression(node.right); - if (isEmptyObjectLiteral(assignedExpression) || container === file && isExportsOrModuleExportsOrAlias(file, assignedExpression)) { - return; - } - - if (isObjectLiteralExpression(assignedExpression) && every(assignedExpression.properties, isShorthandPropertyAssignment)) { - forEach(assignedExpression.properties, bindExportAssignedObjectMemberAlias); - return; - } - - // 'module.exports = expr' assignment - const flags = exportAssignmentIsAlias(node) - ? SymbolFlags.Alias // An export= with an EntityNameExpression or a ClassExpression exports all meanings of that identifier or class - : SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule; - const symbol = declareSymbol(file.symbol.exports!, file.symbol, node, flags | SymbolFlags.Assignment, SymbolFlags.None); - setValueDeclaration(symbol, node); + function bindModuleExportsAssignment(node: BindablePropertyAssignmentExpression) { + // A common practice in node modules is to set 'export = module.exports = {}', this ensures that 'exports' + // is still pointing to 'module.exports'. + // We do not want to consider this as 'export=' since a module can have only one of these. + // Similarly we do not want to treat 'module.exports = exports' as an 'export='. + if (!setCommonJsModuleIndicator(node)) { + return; + } + const assignedExpression = getRightMostAssignedExpression(node.right); + if (isEmptyObjectLiteral(assignedExpression) || container === file && isExportsOrModuleExportsOrAlias(file, assignedExpression)) { + return; } - function bindExportAssignedObjectMemberAlias(node: ShorthandPropertyAssignment) { - declareSymbol(file.symbol.exports!, file.symbol, node, SymbolFlags.Alias | SymbolFlags.Assignment, SymbolFlags.None); + if (isObjectLiteralExpression(assignedExpression) && every(assignedExpression.properties, isShorthandPropertyAssignment)) { + forEach(assignedExpression.properties, bindExportAssignedObjectMemberAlias); + return; } - function bindThisPropertyAssignment(node: BindablePropertyAssignmentExpression | PropertyAccessExpression | LiteralLikeElementAccessExpression) { - Debug.assert(isInJSFile(node)); - // private identifiers *must* be declared (even in JS files) - const hasPrivateIdentifier = (isBinaryExpression(node) && isPropertyAccessExpression(node.left) && isPrivateIdentifier(node.left.name)) - || (isPropertyAccessExpression(node) && isPrivateIdentifier(node.name)); - if (hasPrivateIdentifier) { - return; - } - const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false); - switch (thisContainer.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - let constructorSymbol: Symbol | undefined = thisContainer.symbol; - // For `f.prototype.m = function() { this.x = 0; }`, `this.x = 0` should modify `f`'s members, not the function expression. - if (isBinaryExpression(thisContainer.parent) && thisContainer.parent.operatorToken.kind === SyntaxKind.EqualsToken) { - const l = thisContainer.parent.left; - if (isBindableStaticAccessExpression(l) && isPrototypeAccess(l.expression)) { - constructorSymbol = lookupSymbolForPropertyAccess(l.expression.expression, thisParentContainer); - } - } + // 'module.exports = expr' assignment + const flags = exportAssignmentIsAlias(node) + ? SymbolFlags.Alias // An export= with an EntityNameExpression or a ClassExpression exports all meanings of that identifier or class + : SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule; + const symbol = declareSymbol(file.symbol.exports!, file.symbol, node, flags | SymbolFlags.Assignment, SymbolFlags.None); + setValueDeclaration(symbol, node); + } - if (constructorSymbol && constructorSymbol.valueDeclaration) { - // Declare a 'member' if the container is an ES5 class or ES6 constructor - constructorSymbol.members = constructorSymbol.members || createSymbolTable(); - // It's acceptable for multiple 'this' assignments of the same identifier to occur - if (hasDynamicName(node)) { - bindDynamicallyNamedThisPropertyAssignment(node, constructorSymbol, constructorSymbol.members); - } - else { - declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); - } - addDeclarationToSymbol(constructorSymbol, constructorSymbol.valueDeclaration, SymbolFlags.Class); - } - break; + function bindExportAssignedObjectMemberAlias(node: ShorthandPropertyAssignment) { + declareSymbol(file.symbol.exports!, file.symbol, node, SymbolFlags.Alias | SymbolFlags.Assignment, SymbolFlags.None); + } - case SyntaxKind.Constructor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.ClassStaticBlockDeclaration: - // this.foo assignment in a JavaScript class - // Bind this property to the containing class - const containingClass = thisContainer.parent; - const symbolTable = isStatic(thisContainer) ? containingClass.symbol.exports! : containingClass.symbol.members!; - if (hasDynamicName(node)) { - bindDynamicallyNamedThisPropertyAssignment(node, containingClass.symbol, symbolTable); - } - else { - declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.None, /*isReplaceableByMethod*/ true); - } - break; - case SyntaxKind.SourceFile: - // this.property = assignment in a source file -- declare symbol in exports for a module, in locals for a script + function bindThisPropertyAssignment(node: BindablePropertyAssignmentExpression | PropertyAccessExpression | LiteralLikeElementAccessExpression) { + Debug.assert(isInJSFile(node)); + // private identifiers *must* be declared (even in JS files) + const hasPrivateIdentifier = (isBinaryExpression(node) && isPropertyAccessExpression(node.left) && isPrivateIdentifier(node.left.name)) + || (isPropertyAccessExpression(node) && isPrivateIdentifier(node.name)); + if (hasPrivateIdentifier) { + return; + } + const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false); + switch (thisContainer.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + let constructorSymbol: Symbol | undefined = thisContainer.symbol; + // For `f.prototype.m = function() { this.x = 0; }`, `this.x = 0` should modify `f`'s members, not the function expression. + if (isBinaryExpression(thisContainer.parent) && thisContainer.parent.operatorToken.kind === SyntaxKind.EqualsToken) { + const l = thisContainer.parent.left; + if (isBindableStaticAccessExpression(l) && isPrototypeAccess(l.expression)) { + constructorSymbol = lookupSymbolForPropertyAccess(l.expression.expression, thisParentContainer); + } + } + + if (constructorSymbol && constructorSymbol.valueDeclaration) { + // Declare a 'member' if the container is an ES5 class or ES6 constructor + constructorSymbol.members = constructorSymbol.members || createSymbolTable(); + // It's acceptable for multiple 'this' assignments of the same identifier to occur if (hasDynamicName(node)) { - break; - } - else if ((thisContainer as SourceFile).commonJsModuleIndicator) { - declareSymbol(thisContainer.symbol.exports!, thisContainer.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None); + bindDynamicallyNamedThisPropertyAssignment(node, constructorSymbol, constructorSymbol.members); } else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes); + declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); } + addDeclarationToSymbol(constructorSymbol, constructorSymbol.valueDeclaration, SymbolFlags.Class); + } + break; + + case SyntaxKind.Constructor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.ClassStaticBlockDeclaration: + // this.foo assignment in a JavaScript class + // Bind this property to the containing class + const containingClass = thisContainer.parent; + const symbolTable = isStatic(thisContainer) ? containingClass.symbol.exports! : containingClass.symbol.members!; + if (hasDynamicName(node)) { + bindDynamicallyNamedThisPropertyAssignment(node, containingClass.symbol, symbolTable); + } + else { + declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.None, /*isReplaceableByMethod*/ true); + } + break; + case SyntaxKind.SourceFile: + // this.property = assignment in a source file -- declare symbol in exports for a module, in locals for a script + if (hasDynamicName(node)) { break; + } + else if ((thisContainer as SourceFile).commonJsModuleIndicator) { + declareSymbol(thisContainer.symbol.exports!, thisContainer.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None); + } + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes); + } + break; - default: - Debug.failBadSyntaxKind(thisContainer); - } + default: + Debug.failBadSyntaxKind(thisContainer); } + } - function bindDynamicallyNamedThisPropertyAssignment(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol, symbolTable: SymbolTable) { - declareSymbol(symbolTable, symbol, node, SymbolFlags.Property, SymbolFlags.None, /*isReplaceableByMethod*/ true, /*isComputedName*/ true); - addLateBoundAssignmentDeclarationToSymbol(node, symbol); - } + function bindDynamicallyNamedThisPropertyAssignment(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol, symbolTable: SymbolTable) { + declareSymbol(symbolTable, symbol, node, SymbolFlags.Property, SymbolFlags.None, /*isReplaceableByMethod*/ true, /*isComputedName*/ true); + addLateBoundAssignmentDeclarationToSymbol(node, symbol); + } - function addLateBoundAssignmentDeclarationToSymbol(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol | undefined) { - if (symbol) { - (symbol.assignmentDeclarationMembers || (symbol.assignmentDeclarationMembers = new Map())).set(getNodeId(node), node); - } + function addLateBoundAssignmentDeclarationToSymbol(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol | undefined) { + if (symbol) { + (symbol.assignmentDeclarationMembers || (symbol.assignmentDeclarationMembers = new Map())).set(getNodeId(node), node); } + } - function bindSpecialPropertyDeclaration(node: PropertyAccessExpression | LiteralLikeElementAccessExpression) { - if (node.expression.kind === SyntaxKind.ThisKeyword) { - bindThisPropertyAssignment(node); + function bindSpecialPropertyDeclaration(node: PropertyAccessExpression | LiteralLikeElementAccessExpression) { + if (node.expression.kind === SyntaxKind.ThisKeyword) { + bindThisPropertyAssignment(node); + } + else if (isBindableStaticAccessExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) { + if (isPrototypeAccess(node.expression)) { + bindPrototypePropertyAssignment(node, node.parent); } - else if (isBindableStaticAccessExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) { - if (isPrototypeAccess(node.expression)) { - bindPrototypePropertyAssignment(node, node.parent); - } - else { - bindStaticPropertyAssignment(node); - } + else { + bindStaticPropertyAssignment(node); } } + } - /** For `x.prototype = { p, ... }`, declare members p,... if `x` is function/class/{}, or not declared. */ - function bindPrototypeAssignment(node: BindableStaticPropertyAssignmentExpression) { - setParent(node.left, node); - setParent(node.right, node); - bindPropertyAssignment(node.left.expression, node.left, /*isPrototypeProperty*/ false, /*containerIsClass*/ true); - } + /** For `x.prototype = { p, ... }`, declare members p,... if `x` is function/class/{}, or not declared. */ + function bindPrototypeAssignment(node: BindableStaticPropertyAssignmentExpression) { + setParent(node.left, node); + setParent(node.right, node); + bindPropertyAssignment(node.left.expression, node.left, /*isPrototypeProperty*/ false, /*containerIsClass*/ true); + } - function bindObjectDefinePrototypeProperty(node: BindableObjectDefinePropertyCall) { - const namespaceSymbol = lookupSymbolForPropertyAccess((node.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression); - if (namespaceSymbol && namespaceSymbol.valueDeclaration) { - // Ensure the namespace symbol becomes class-like - addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); - } - bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ true); + function bindObjectDefinePrototypeProperty(node: BindableObjectDefinePropertyCall) { + const namespaceSymbol = lookupSymbolForPropertyAccess((node.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression); + if (namespaceSymbol && namespaceSymbol.valueDeclaration) { + // Ensure the namespace symbol becomes class-like + addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); } + bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ true); + } - /** - * For `x.prototype.y = z`, declare a member `y` on `x` if `x` is a function or class, or not declared. - * Note that jsdoc preceding an ExpressionStatement like `x.prototype.y;` is also treated as a declaration. - */ - function bindPrototypePropertyAssignment(lhs: BindableStaticAccessExpression, parent: Node) { - // Look up the function in the local scope, since prototype assignments should - // follow the function declaration - const classPrototype = lhs.expression as BindableStaticAccessExpression; - const constructorFunction = classPrototype.expression; + /** + * For `x.prototype.y = z`, declare a member `y` on `x` if `x` is a function or class, or not declared. + * Note that jsdoc preceding an ExpressionStatement like `x.prototype.y;` is also treated as a declaration. + */ + function bindPrototypePropertyAssignment(lhs: BindableStaticAccessExpression, parent: Node) { + // Look up the function in the local scope, since prototype assignments should + // follow the function declaration + const classPrototype = lhs.expression as BindableStaticAccessExpression; + const constructorFunction = classPrototype.expression; + + // Fix up parent pointers since we're going to use these nodes before we bind into them + setParent(constructorFunction, classPrototype); + setParent(classPrototype, lhs); + setParent(lhs, parent); + + bindPropertyAssignment(constructorFunction, lhs, /*isPrototypeProperty*/ true, /*containerIsClass*/ true); + } - // Fix up parent pointers since we're going to use these nodes before we bind into them - setParent(constructorFunction, classPrototype); - setParent(classPrototype, lhs); - setParent(lhs, parent); + function bindObjectDefinePropertyAssignment(node: BindableObjectDefinePropertyCall) { + let namespaceSymbol = lookupSymbolForPropertyAccess(node.arguments[0]); + const isToplevel = node.parent.parent.kind === SyntaxKind.SourceFile; + namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, node.arguments[0], isToplevel, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); + bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ false); + } - bindPropertyAssignment(constructorFunction, lhs, /*isPrototypeProperty*/ true, /*containerIsClass*/ true); + function bindSpecialPropertyAssignment(node: BindablePropertyAssignmentExpression) { + // Class declarations in Typescript do not allow property declarations + const parentSymbol = lookupSymbolForPropertyAccess(node.left.expression, container) || lookupSymbolForPropertyAccess(node.left.expression, blockScopeContainer) ; + if (!isInJSFile(node) && !isFunctionSymbol(parentSymbol)) { + return; + } + const rootExpr = getLeftmostAccessExpression(node.left); + if (isIdentifier(rootExpr) && lookupSymbolForName(container, rootExpr.escapedText)!?.flags & SymbolFlags.Alias) { + return; + } + // Fix up parent pointers since we're going to use these nodes before we bind into them + setParent(node.left, node); + setParent(node.right, node); + if (isIdentifier(node.left.expression) && container === file && isExportsOrModuleExportsOrAlias(file, node.left.expression)) { + // This can be an alias for the 'exports' or 'module.exports' names, e.g. + // var util = module.exports; + // util.property = function ... + bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression); + } + else if (hasDynamicName(node)) { + bindAnonymousDeclaration(node, SymbolFlags.Property | SymbolFlags.Assignment, InternalSymbolName.Computed); + const sym = bindPotentiallyMissingNamespaces(parentSymbol, node.left.expression, isTopLevelNamespaceAssignment(node.left), /*isPrototype*/ false, /*containerIsClass*/ false); + addLateBoundAssignmentDeclarationToSymbol(node, sym); } - - function bindObjectDefinePropertyAssignment(node: BindableObjectDefinePropertyCall) { - let namespaceSymbol = lookupSymbolForPropertyAccess(node.arguments[0]); - const isToplevel = node.parent.parent.kind === SyntaxKind.SourceFile; - namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, node.arguments[0], isToplevel, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); - bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ false); + else { + bindStaticPropertyAssignment(cast(node.left, isBindableStaticNameExpression)); } + } - function bindSpecialPropertyAssignment(node: BindablePropertyAssignmentExpression) { - // Class declarations in Typescript do not allow property declarations - const parentSymbol = lookupSymbolForPropertyAccess(node.left.expression, container) || lookupSymbolForPropertyAccess(node.left.expression, blockScopeContainer) ; - if (!isInJSFile(node) && !isFunctionSymbol(parentSymbol)) { - return; - } - const rootExpr = getLeftmostAccessExpression(node.left); - if (isIdentifier(rootExpr) && lookupSymbolForName(container, rootExpr.escapedText)!?.flags & SymbolFlags.Alias) { - return; - } - // Fix up parent pointers since we're going to use these nodes before we bind into them - setParent(node.left, node); - setParent(node.right, node); - if (isIdentifier(node.left.expression) && container === file && isExportsOrModuleExportsOrAlias(file, node.left.expression)) { - // This can be an alias for the 'exports' or 'module.exports' names, e.g. - // var util = module.exports; - // util.property = function ... - bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression); - } - else if (hasDynamicName(node)) { - bindAnonymousDeclaration(node, SymbolFlags.Property | SymbolFlags.Assignment, InternalSymbolName.Computed); - const sym = bindPotentiallyMissingNamespaces(parentSymbol, node.left.expression, isTopLevelNamespaceAssignment(node.left), /*isPrototype*/ false, /*containerIsClass*/ false); - addLateBoundAssignmentDeclarationToSymbol(node, sym); - } - else { - bindStaticPropertyAssignment(cast(node.left, isBindableStaticNameExpression)); - } + /** + * For nodes like `x.y = z`, declare a member 'y' on 'x' if x is a function (or IIFE) or class or {}, or not declared. + * Also works for expression statements preceded by JSDoc, like / ** @type number * / x.y; + */ + function bindStaticPropertyAssignment(node: BindableStaticNameExpression) { + Debug.assert(!isIdentifier(node)); + setParent(node.expression, node); + bindPropertyAssignment(node.expression, node, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); + } + + function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: BindableStaticNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) { + if (namespaceSymbol?.flags! & SymbolFlags.Alias) { + return namespaceSymbol; + } + if (isToplevel && !isPrototypeProperty) { + // make symbols or add declarations for intermediate containers + const flags = SymbolFlags.Module | SymbolFlags.Assignment; + const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.Assignment; + namespaceSymbol = forEachIdentifierInEntityName(entityName, namespaceSymbol, (id, symbol, parent) => { + if (symbol) { + addDeclarationToSymbol(symbol, id, flags); + return symbol; + } + else { + const table = parent ? parent.exports! : + file.jsGlobalAugmentations || (file.jsGlobalAugmentations = createSymbolTable()); + return declareSymbol(table, parent, id, flags, excludeFlags); + } + }); } + if (containerIsClass && namespaceSymbol && namespaceSymbol.valueDeclaration) { + addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); + } + return namespaceSymbol; + } - /** - * For nodes like `x.y = z`, declare a member 'y' on 'x' if x is a function (or IIFE) or class or {}, or not declared. - * Also works for expression statements preceded by JSDoc, like / ** @type number * / x.y; - */ - function bindStaticPropertyAssignment(node: BindableStaticNameExpression) { - Debug.assert(!isIdentifier(node)); - setParent(node.expression, node); - bindPropertyAssignment(node.expression, node, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); + function bindPotentiallyNewExpandoMemberToNamespace(declaration: BindableStaticAccessExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) { + if (!namespaceSymbol || !isExpandoSymbol(namespaceSymbol)) { + return; } - function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: BindableStaticNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) { - if (namespaceSymbol?.flags! & SymbolFlags.Alias) { - return namespaceSymbol; - } - if (isToplevel && !isPrototypeProperty) { - // make symbols or add declarations for intermediate containers - const flags = SymbolFlags.Module | SymbolFlags.Assignment; - const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.Assignment; - namespaceSymbol = forEachIdentifierInEntityName(entityName, namespaceSymbol, (id, symbol, parent) => { - if (symbol) { - addDeclarationToSymbol(symbol, id, flags); - return symbol; - } - else { - const table = parent ? parent.exports! : - file.jsGlobalAugmentations || (file.jsGlobalAugmentations = createSymbolTable()); - return declareSymbol(table, parent, id, flags, excludeFlags); - } - }); + // Set up the members collection if it doesn't exist already + const symbolTable = isPrototypeProperty ? + (namespaceSymbol.members || (namespaceSymbol.members = createSymbolTable())) : + (namespaceSymbol.exports || (namespaceSymbol.exports = createSymbolTable())); + + let includes = SymbolFlags.None; + let excludes = SymbolFlags.None; + // Method-like + if (isFunctionLikeDeclaration(getAssignedExpandoInitializer(declaration)!)) { + includes = SymbolFlags.Method; + excludes = SymbolFlags.MethodExcludes; + } + // Maybe accessor-like + else if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { + if (some(declaration.arguments[2].properties, p => { + const id = getNameOfDeclaration(p); + return !!id && isIdentifier(id) && idText(id) === "set"; + })) { + // We mix in `SymbolFLags.Property` so in the checker `getTypeOfVariableParameterOrProperty` is used for this + // symbol, instead of `getTypeOfAccessor` (which will assert as there is no real accessor declaration) + includes |= SymbolFlags.SetAccessor | SymbolFlags.Property; + excludes |= SymbolFlags.SetAccessorExcludes; } - if (containerIsClass && namespaceSymbol && namespaceSymbol.valueDeclaration) { - addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); + if (some(declaration.arguments[2].properties, p => { + const id = getNameOfDeclaration(p); + return !!id && isIdentifier(id) && idText(id) === "get"; + })) { + includes |= SymbolFlags.GetAccessor | SymbolFlags.Property; + excludes |= SymbolFlags.GetAccessorExcludes; } - return namespaceSymbol; } - function bindPotentiallyNewExpandoMemberToNamespace(declaration: BindableStaticAccessExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) { - if (!namespaceSymbol || !isExpandoSymbol(namespaceSymbol)) { - return; - } + if (includes === SymbolFlags.None) { + includes = SymbolFlags.Property; + excludes = SymbolFlags.PropertyExcludes; + } - // Set up the members collection if it doesn't exist already - const symbolTable = isPrototypeProperty ? - (namespaceSymbol.members || (namespaceSymbol.members = createSymbolTable())) : - (namespaceSymbol.exports || (namespaceSymbol.exports = createSymbolTable())); - - let includes = SymbolFlags.None; - let excludes = SymbolFlags.None; - // Method-like - if (isFunctionLikeDeclaration(getAssignedExpandoInitializer(declaration)!)) { - includes = SymbolFlags.Method; - excludes = SymbolFlags.MethodExcludes; - } - // Maybe accessor-like - else if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { - if (some(declaration.arguments[2].properties, p => { - const id = getNameOfDeclaration(p); - return !!id && isIdentifier(id) && idText(id) === "set"; - })) { - // We mix in `SymbolFLags.Property` so in the checker `getTypeOfVariableParameterOrProperty` is used for this - // symbol, instead of `getTypeOfAccessor` (which will assert as there is no real accessor declaration) - includes |= SymbolFlags.SetAccessor | SymbolFlags.Property; - excludes |= SymbolFlags.SetAccessorExcludes; - } - if (some(declaration.arguments[2].properties, p => { - const id = getNameOfDeclaration(p); - return !!id && isIdentifier(id) && idText(id) === "get"; - })) { - includes |= SymbolFlags.GetAccessor | SymbolFlags.Property; - excludes |= SymbolFlags.GetAccessorExcludes; - } - } - - if (includes === SymbolFlags.None) { - includes = SymbolFlags.Property; - excludes = SymbolFlags.PropertyExcludes; - } - - declareSymbol(symbolTable, namespaceSymbol, declaration, includes | SymbolFlags.Assignment, excludes & ~SymbolFlags.Assignment); - } - - function isTopLevelNamespaceAssignment(propertyAccess: BindableAccessExpression) { - return isBinaryExpression(propertyAccess.parent) - ? getParentOfBinaryExpression(propertyAccess.parent).parent.kind === SyntaxKind.SourceFile - : propertyAccess.parent.parent.kind === SyntaxKind.SourceFile; - } - - function bindPropertyAssignment(name: BindableStaticNameExpression, propertyAccess: BindableStaticAccessExpression, isPrototypeProperty: boolean, containerIsClass: boolean) { - let namespaceSymbol = lookupSymbolForPropertyAccess(name, container) || lookupSymbolForPropertyAccess(name, blockScopeContainer); - const isToplevel = isTopLevelNamespaceAssignment(propertyAccess); - namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, propertyAccess.expression, isToplevel, isPrototypeProperty, containerIsClass); - bindPotentiallyNewExpandoMemberToNamespace(propertyAccess, namespaceSymbol, isPrototypeProperty); - } - - /** - * Javascript expando values are: - * - Functions - * - classes - * - namespaces - * - variables initialized with function expressions - * - with class expressions - * - with empty object literals - * - with non-empty object literals if assigned to the prototype property - */ - function isExpandoSymbol(symbol: Symbol): boolean { - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.NamespaceModule)) { - return true; - } - const node = symbol.valueDeclaration; - if (node && isCallExpression(node)) { - return !!getAssignedExpandoInitializer(node); - } - let init = !node ? undefined : - isVariableDeclaration(node) ? node.initializer : - isBinaryExpression(node) ? node.right : - isPropertyAccessExpression(node) && isBinaryExpression(node.parent) ? node.parent.right : - undefined; - init = init && getRightMostAssignedExpression(init); - if (init) { - const isPrototypeAssignment = isPrototypeAccess(isVariableDeclaration(node!) ? node.name : isBinaryExpression(node!) ? node.left : node!); - return !!getExpandoInitializer(isBinaryExpression(init) && (init.operatorToken.kind === SyntaxKind.BarBarToken || init.operatorToken.kind === SyntaxKind.QuestionQuestionToken) ? init.right : init, isPrototypeAssignment); - } - return false; + declareSymbol(symbolTable, namespaceSymbol, declaration, includes | SymbolFlags.Assignment, excludes & ~SymbolFlags.Assignment); + } + + function isTopLevelNamespaceAssignment(propertyAccess: BindableAccessExpression) { + return isBinaryExpression(propertyAccess.parent) + ? getParentOfBinaryExpression(propertyAccess.parent).parent.kind === SyntaxKind.SourceFile + : propertyAccess.parent.parent.kind === SyntaxKind.SourceFile; + } + + function bindPropertyAssignment(name: BindableStaticNameExpression, propertyAccess: BindableStaticAccessExpression, isPrototypeProperty: boolean, containerIsClass: boolean) { + let namespaceSymbol = lookupSymbolForPropertyAccess(name, container) || lookupSymbolForPropertyAccess(name, blockScopeContainer); + const isToplevel = isTopLevelNamespaceAssignment(propertyAccess); + namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, propertyAccess.expression, isToplevel, isPrototypeProperty, containerIsClass); + bindPotentiallyNewExpandoMemberToNamespace(propertyAccess, namespaceSymbol, isPrototypeProperty); + } + + /** + * Javascript expando values are: + * - Functions + * - classes + * - namespaces + * - variables initialized with function expressions + * - with class expressions + * - with empty object literals + * - with non-empty object literals if assigned to the prototype property + */ + function isExpandoSymbol(symbol: Symbol): boolean { + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.NamespaceModule)) { + return true; + } + const node = symbol.valueDeclaration; + if (node && isCallExpression(node)) { + return !!getAssignedExpandoInitializer(node); } + let init = !node ? undefined : + isVariableDeclaration(node) ? node.initializer : + isBinaryExpression(node) ? node.right : + isPropertyAccessExpression(node) && isBinaryExpression(node.parent) ? node.parent.right : + undefined; + init = init && getRightMostAssignedExpression(init); + if (init) { + const isPrototypeAssignment = isPrototypeAccess(isVariableDeclaration(node!) ? node.name : isBinaryExpression(node!) ? node.left : node!); + return !!getExpandoInitializer(isBinaryExpression(init) && (init.operatorToken.kind === SyntaxKind.BarBarToken || init.operatorToken.kind === SyntaxKind.QuestionQuestionToken) ? init.right : init, isPrototypeAssignment); + } + return false; + } - function getParentOfBinaryExpression(expr: Node) { - while (isBinaryExpression(expr.parent)) { - expr = expr.parent; - } - return expr.parent; + function getParentOfBinaryExpression(expr: Node) { + while (isBinaryExpression(expr.parent)) { + expr = expr.parent; } + return expr.parent; + } - function lookupSymbolForPropertyAccess(node: BindableStaticNameExpression, lookupContainer: Node = container): Symbol | undefined { - if (isIdentifier(node)) { - return lookupSymbolForName(lookupContainer, node.escapedText); - } - else { - const symbol = lookupSymbolForPropertyAccess(node.expression); - return symbol && symbol.exports && symbol.exports.get(getElementOrPropertyAccessName(node)); - } + function lookupSymbolForPropertyAccess(node: BindableStaticNameExpression, lookupContainer: Node = container): Symbol | undefined { + if (isIdentifier(node)) { + return lookupSymbolForName(lookupContainer, node.escapedText); } + else { + const symbol = lookupSymbolForPropertyAccess(node.expression); + return symbol && symbol.exports && symbol.exports.get(getElementOrPropertyAccessName(node)); + } + } - function forEachIdentifierInEntityName(e: BindableStaticNameExpression, parent: Symbol | undefined, action: (e: Declaration, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined { - if (isExportsOrModuleExportsOrAlias(file, e)) { - return file.symbol; - } - else if (isIdentifier(e)) { - return action(e, lookupSymbolForPropertyAccess(e), parent); - } - else { - const s = forEachIdentifierInEntityName(e.expression, parent, action); - const name = getNameOrArgument(e); - // unreachable - if (isPrivateIdentifier(name)) { - Debug.fail("unexpected PrivateIdentifier"); - } - return action(name, s && s.exports && s.exports.get(getElementOrPropertyAccessName(e)), s); + function forEachIdentifierInEntityName(e: BindableStaticNameExpression, parent: Symbol | undefined, action: (e: Declaration, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined { + if (isExportsOrModuleExportsOrAlias(file, e)) { + return file.symbol; + } + else if (isIdentifier(e)) { + return action(e, lookupSymbolForPropertyAccess(e), parent); + } + else { + const s = forEachIdentifierInEntityName(e.expression, parent, action); + const name = getNameOrArgument(e); + // unreachable + if (isPrivateIdentifier(name)) { + Debug.fail("unexpected PrivateIdentifier"); } + return action(name, s && s.exports && s.exports.get(getElementOrPropertyAccessName(e)), s); } + } - function bindCallExpression(node: CallExpression) { - // We're only inspecting call expressions to detect CommonJS modules, so we can skip - // this check if we've already seen the module indicator - if (!file.commonJsModuleIndicator && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ false)) { - setCommonJsModuleIndicator(node); - } + function bindCallExpression(node: CallExpression) { + // We're only inspecting call expressions to detect CommonJS modules, so we can skip + // this check if we've already seen the module indicator + if (!file.commonJsModuleIndicator && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ false)) { + setCommonJsModuleIndicator(node); } + } - function bindClassLikeDeclaration(node: ClassLikeDeclaration) { - if (node.kind === SyntaxKind.ClassDeclaration) { - bindBlockScopedDeclaration(node, SymbolFlags.Class, SymbolFlags.ClassExcludes); - } - else { - const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Class; - bindAnonymousDeclaration(node, SymbolFlags.Class, bindingName); - // Add name of class expression into the map for semantic classifier - if (node.name) { - classifiableNames.add(node.name.escapedText); - } + function bindClassLikeDeclaration(node: ClassLikeDeclaration) { + if (node.kind === SyntaxKind.ClassDeclaration) { + bindBlockScopedDeclaration(node, SymbolFlags.Class, SymbolFlags.ClassExcludes); + } + else { + const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Class; + bindAnonymousDeclaration(node, SymbolFlags.Class, bindingName); + // Add name of class expression into the map for semantic classifier + if (node.name) { + classifiableNames.add(node.name.escapedText); } + } - const { symbol } = node; + const { symbol } = node; - // TypeScript 1.0 spec (April 2014): 8.4 - // Every class automatically contains a static property member named 'prototype', the - // type of which is an instantiation of the class type with type Any supplied as a type - // argument for each type parameter. It is an error to explicitly declare a static - // property member with the name 'prototype'. - // - // Note: we check for this here because this class may be merging into a module. The - // module might have an exported variable called 'prototype'. We can't allow that as - // that would clash with the built-in 'prototype' for the class. - const prototypeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Prototype, "prototype" as __String); - const symbolExport = symbol.exports!.get(prototypeSymbol.escapedName); - if (symbolExport) { - if (node.name) { - setParent(node.name, node); - } - file.bindDiagnostics.push(createDiagnosticForNode(symbolExport.declarations![0], Diagnostics.Duplicate_identifier_0, symbolName(prototypeSymbol))); + // TypeScript 1.0 spec (April 2014): 8.4 + // Every class automatically contains a static property member named 'prototype', the + // type of which is an instantiation of the class type with type Any supplied as a type + // argument for each type parameter. It is an error to explicitly declare a static + // property member with the name 'prototype'. + // + // Note: we check for this here because this class may be merging into a module. The + // module might have an exported variable called 'prototype'. We can't allow that as + // that would clash with the built-in 'prototype' for the class. + const prototypeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Prototype, "prototype" as __String); + const symbolExport = symbol.exports!.get(prototypeSymbol.escapedName); + if (symbolExport) { + if (node.name) { + setParent(node.name, node); } - symbol.exports!.set(prototypeSymbol.escapedName, prototypeSymbol); - prototypeSymbol.parent = symbol; - } - - function bindEnumDeclaration(node: EnumDeclaration) { - return isEnumConst(node) - ? bindBlockScopedDeclaration(node, SymbolFlags.ConstEnum, SymbolFlags.ConstEnumExcludes) - : bindBlockScopedDeclaration(node, SymbolFlags.RegularEnum, SymbolFlags.RegularEnumExcludes); + file.bindDiagnostics.push(createDiagnosticForNode(symbolExport.declarations![0], Diagnostics.Duplicate_identifier_0, symbolName(prototypeSymbol))); } + symbol.exports!.set(prototypeSymbol.escapedName, prototypeSymbol); + prototypeSymbol.parent = symbol; + } - function bindVariableDeclarationOrBindingElement(node: VariableDeclaration | BindingElement) { - if (inStrictMode) { - checkStrictModeEvalOrArguments(node, node.name); - } + function bindEnumDeclaration(node: EnumDeclaration) { + return isEnumConst(node) + ? bindBlockScopedDeclaration(node, SymbolFlags.ConstEnum, SymbolFlags.ConstEnumExcludes) + : bindBlockScopedDeclaration(node, SymbolFlags.RegularEnum, SymbolFlags.RegularEnumExcludes); + } - if (!isBindingPattern(node.name)) { - if (isInJSFile(node) && isRequireVariableDeclaration(node) && !getJSDocTypeTag(node)) { - declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Alias, SymbolFlags.AliasExcludes); - } - else if (isBlockOrCatchScoped(node)) { - bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes); - } - else if (isParameterDeclaration(node)) { - // It is safe to walk up parent chain to find whether the node is a destructuring parameter declaration - // because its parent chain has already been set up, since parents are set before descending into children. - // - // If node is a binding element in parameter declaration, we need to use ParameterExcludes. - // Using ParameterExcludes flag allows the compiler to report an error on duplicate identifiers in Parameter Declaration - // For example: - // function foo([a,a]) {} // Duplicate Identifier error - // function bar(a,a) {} // Duplicate Identifier error, parameter declaration in this case is handled in bindParameter - // // which correctly set excluded symbols - declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); - } - else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes); - } - } + function bindVariableDeclarationOrBindingElement(node: VariableDeclaration | BindingElement) { + if (inStrictMode) { + checkStrictModeEvalOrArguments(node, node.name); } - function bindParameter(node: ParameterDeclaration | JSDocParameterTag) { - if (node.kind === SyntaxKind.JSDocParameterTag && container.kind !== SyntaxKind.JSDocSignature) { - return; - } - if (inStrictMode && !(node.flags & NodeFlags.Ambient)) { - // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a - // strict mode FunctionLikeDeclaration or FunctionExpression(13.1) - checkStrictModeEvalOrArguments(node, node.name); + if (!isBindingPattern(node.name)) { + if (isInJSFile(node) && isRequireVariableDeclaration(node) && !getJSDocTypeTag(node)) { + declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } - - if (isBindingPattern(node.name)) { - bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, "__" + (node as ParameterDeclaration).parent.parameters.indexOf(node as ParameterDeclaration) as __String); + else if (isBlockOrCatchScoped(node)) { + bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes); } - else { + else if (isParameterDeclaration(node)) { + // It is safe to walk up parent chain to find whether the node is a destructuring parameter declaration + // because its parent chain has already been set up, since parents are set before descending into children. + // + // If node is a binding element in parameter declaration, we need to use ParameterExcludes. + // Using ParameterExcludes flag allows the compiler to report an error on duplicate identifiers in Parameter Declaration + // For example: + // function foo([a,a]) {} // Duplicate Identifier error + // function bar(a,a) {} // Duplicate Identifier error, parameter declaration in this case is handled in bindParameter + // // which correctly set excluded symbols declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); } - - // If this is a property-parameter, then also declare the property symbol into the - // containing class. - if (isParameterPropertyDeclaration(node, node.parent)) { - const classDeclaration = node.parent.parent; - declareSymbol(classDeclaration.symbol.members!, classDeclaration.symbol, node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes); } } + } - function bindFunctionDeclaration(node: FunctionDeclaration) { - if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient)) { - if (isAsyncFunction(node)) { - emitFlags |= NodeFlags.HasAsyncFunctions; - } - } + function bindParameter(node: ParameterDeclaration | JSDocParameterTag) { + if (node.kind === SyntaxKind.JSDocParameterTag && container.kind !== SyntaxKind.JSDocSignature) { + return; + } + if (inStrictMode && !(node.flags & NodeFlags.Ambient)) { + // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a + // strict mode FunctionLikeDeclaration or FunctionExpression(13.1) + checkStrictModeEvalOrArguments(node, node.name); + } - checkStrictModeFunctionName(node); - if (inStrictMode) { - checkStrictModeFunctionDeclaration(node); - bindBlockScopedDeclaration(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes); - } - else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes); - } + if (isBindingPattern(node.name)) { + bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, "__" + (node as ParameterDeclaration).parent.parameters.indexOf(node as ParameterDeclaration) as __String); + } + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); } - function bindFunctionExpression(node: FunctionExpression) { - if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient)) { - if (isAsyncFunction(node)) { - emitFlags |= NodeFlags.HasAsyncFunctions; - } - } - if (currentFlow) { - node.flowNode = currentFlow; - } - checkStrictModeFunctionName(node); - const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Function; - return bindAnonymousDeclaration(node, SymbolFlags.Function, bindingName); + // If this is a property-parameter, then also declare the property symbol into the + // containing class. + if (isParameterPropertyDeclaration(node, node.parent)) { + const classDeclaration = node.parent.parent; + declareSymbol(classDeclaration.symbol.members!, classDeclaration.symbol, node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); } + } - function bindPropertyOrMethodOrAccessor(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient) && isAsyncFunction(node)) { + function bindFunctionDeclaration(node: FunctionDeclaration) { + if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient)) { + if (isAsyncFunction(node)) { emitFlags |= NodeFlags.HasAsyncFunctions; } + } - if (currentFlow && isObjectLiteralOrClassExpressionMethodOrAccessor(node)) { - node.flowNode = currentFlow; + checkStrictModeFunctionName(node); + if (inStrictMode) { + checkStrictModeFunctionDeclaration(node); + bindBlockScopedDeclaration(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes); + } + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes); + } + } + + function bindFunctionExpression(node: FunctionExpression) { + if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient)) { + if (isAsyncFunction(node)) { + emitFlags |= NodeFlags.HasAsyncFunctions; } + } + if (currentFlow) { + node.flowNode = currentFlow; + } + checkStrictModeFunctionName(node); + const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Function; + return bindAnonymousDeclaration(node, SymbolFlags.Function, bindingName); + } - return hasDynamicName(node) - ? bindAnonymousDeclaration(node, symbolFlags, InternalSymbolName.Computed) - : declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); + function bindPropertyOrMethodOrAccessor(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient) && isAsyncFunction(node)) { + emitFlags |= NodeFlags.HasAsyncFunctions; } - function getInferTypeContainer(node: Node): ConditionalTypeNode | undefined { - const extendsType = findAncestor(node, n => n.parent && isConditionalTypeNode(n.parent) && n.parent.extendsType === n); - return extendsType && extendsType.parent as ConditionalTypeNode; + if (currentFlow && isObjectLiteralOrClassExpressionMethodOrAccessor(node)) { + node.flowNode = currentFlow; } - function bindTypeParameter(node: TypeParameterDeclaration) { - if (isJSDocTemplateTag(node.parent)) { - const container = getEffectiveContainerForJSDocTemplateTag(node.parent); - if (container) { - if (!container.locals) { - container.locals = createSymbolTable(); - } - declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); - } - else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); - } - } - else if (node.parent.kind === SyntaxKind.InferType) { - const container = getInferTypeContainer(node.parent); - if (container) { - if (!container.locals) { - container.locals = createSymbolTable(); - } - declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); - } - else { - bindAnonymousDeclaration(node, SymbolFlags.TypeParameter, getDeclarationName(node)!); // TODO: GH#18217 + return hasDynamicName(node) + ? bindAnonymousDeclaration(node, symbolFlags, InternalSymbolName.Computed) + : declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); + } + + function getInferTypeContainer(node: Node): ConditionalTypeNode | undefined { + const extendsType = findAncestor(node, n => n.parent && isConditionalTypeNode(n.parent) && n.parent.extendsType === n); + return extendsType && extendsType.parent as ConditionalTypeNode; + } + + function bindTypeParameter(node: TypeParameterDeclaration) { + if (isJSDocTemplateTag(node.parent)) { + const container = getEffectiveContainerForJSDocTemplateTag(node.parent); + if (container) { + if (!container.locals) { + container.locals = createSymbolTable(); } + declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } else { declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } } - - // reachability checks - - function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean { - const instanceState = getModuleInstanceState(node); - return instanceState === ModuleInstanceState.Instantiated || (instanceState === ModuleInstanceState.ConstEnumOnly && shouldPreserveConstEnums(options)); - } - - function checkUnreachable(node: Node): boolean { - if (!(currentFlow.flags & FlowFlags.Unreachable)) { - return false; - } - if (currentFlow === unreachableFlow) { - const reportError = - // report error on all statements except empty ones - (isStatementButNotDeclaration(node) && node.kind !== SyntaxKind.EmptyStatement) || - // report error on class declarations - node.kind === SyntaxKind.ClassDeclaration || - // report error on instantiated modules or const-enums only modules if preserveConstEnums is set - (node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration(node as ModuleDeclaration)); - - if (reportError) { - currentFlow = reportedUnreachableFlow; - - if (!options.allowUnreachableCode) { - // unreachable code is reported if - // - user has explicitly asked about it AND - // - statement is in not ambient context (statements in ambient context is already an error - // so we should not report extras) AND - // - node is not variable statement OR - // - node is block scoped variable statement OR - // - node is not block scoped variable statement and at least one variable declaration has initializer - // Rationale: we don't want to report errors on non-initialized var's since they are hoisted - // On the other side we do want to report errors on non-initialized 'lets' because of TDZ - const isError = - unreachableCodeIsError(options) && - !(node.flags & NodeFlags.Ambient) && - ( - !isVariableStatement(node) || - !!(getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) || - node.declarationList.declarations.some(d => !!d.initializer) - ); - - eachUnreachableRange(node, (start, end) => errorOrSuggestionOnRange(isError, start, end, Diagnostics.Unreachable_code_detected)); - } + else if (node.parent.kind === SyntaxKind.InferType) { + const container = getInferTypeContainer(node.parent); + if (container) { + if (!container.locals) { + container.locals = createSymbolTable(); } + declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); + } + else { + bindAnonymousDeclaration(node, SymbolFlags.TypeParameter, getDeclarationName(node)!); // TODO: GH#18217 } - return true; + } + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } } - function eachUnreachableRange(node: Node, cb: (start: Node, last: Node) => void): void { - if (isStatement(node) && isExecutableStatement(node) && isBlock(node.parent)) { - const { statements } = node.parent; - const slice = sliceAfter(statements, node); - getRangesWhere(slice, isExecutableStatement, (start, afterEnd) => cb(slice[start], slice[afterEnd - 1])); - } - else { - cb(node, node); + // reachability checks + + function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean { + const instanceState = getModuleInstanceState(node); + return instanceState === ModuleInstanceState.Instantiated || (instanceState === ModuleInstanceState.ConstEnumOnly && shouldPreserveConstEnums(options)); + } + + function checkUnreachable(node: Node): boolean { + if (!(currentFlow.flags & FlowFlags.Unreachable)) { + return false; } + if (currentFlow === unreachableFlow) { + const reportError = + // report error on all statements except empty ones + (isStatementButNotDeclaration(node) && node.kind !== SyntaxKind.EmptyStatement) || + // report error on class declarations + node.kind === SyntaxKind.ClassDeclaration || + // report error on instantiated modules or const-enums only modules if preserveConstEnums is set + (node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration(node as ModuleDeclaration)); + + if (reportError) { + currentFlow = reportedUnreachableFlow; + + if (!options.allowUnreachableCode) { + // unreachable code is reported if + // - user has explicitly asked about it AND + // - statement is in not ambient context (statements in ambient context is already an error + // so we should not report extras) AND + // - node is not variable statement OR + // - node is block scoped variable statement OR + // - node is not block scoped variable statement and at least one variable declaration has initializer + // Rationale: we don't want to report errors on non-initialized var's since they are hoisted + // On the other side we do want to report errors on non-initialized 'lets' because of TDZ + const isError = + unreachableCodeIsError(options) && + !(node.flags & NodeFlags.Ambient) && + ( + !isVariableStatement(node) || + !!(getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) || + node.declarationList.declarations.some(d => !!d.initializer) + ); + + eachUnreachableRange(node, (start, end) => errorOrSuggestionOnRange(isError, start, end, Diagnostics.Unreachable_code_detected)); + } + } + } + return true; } - // As opposed to a pure declaration like an `interface` - function isExecutableStatement(s: Statement): boolean { - // Don't remove statements that can validly be used before they appear. - return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) && !isEnumDeclaration(s) && - // `var x;` may declare a variable used above - !(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.Let | NodeFlags.Const)) && s.declarationList.declarations.some(d => !d.initializer)); +} + +function eachUnreachableRange(node: Node, cb: (start: Node, last: Node) => void): void { + if (isStatement(node) && isExecutableStatement(node) && isBlock(node.parent)) { + const { statements } = node.parent; + const slice = sliceAfter(statements, node); + getRangesWhere(slice, isExecutableStatement, (start, afterEnd) => cb(slice[start], slice[afterEnd - 1])); } + else { + cb(node, node); + } +} +// As opposed to a pure declaration like an `interface` +function isExecutableStatement(s: Statement): boolean { + // Don't remove statements that can validly be used before they appear. + return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) && !isEnumDeclaration(s) && + // `var x;` may declare a variable used above + !(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.Let | NodeFlags.Const)) && s.declarationList.declarations.some(d => !d.initializer)); +} - function isPurelyTypeDeclaration(s: Statement): boolean { - switch (s.kind) { - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - return true; - case SyntaxKind.ModuleDeclaration: - return getModuleInstanceState(s as ModuleDeclaration) !== ModuleInstanceState.Instantiated; - case SyntaxKind.EnumDeclaration: - return hasSyntacticModifier(s, ModifierFlags.Const); - default: - return false; - } - } - - export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean { - let i = 0; - const q = [node]; - while (q.length && i < 100) { - i++; - node = q.shift()!; - if (isExportsIdentifier(node) || isModuleExportsAccessExpression(node)) { - return true; - } - else if (isIdentifier(node)) { - const symbol = lookupSymbolForName(sourceFile, node.escapedText); - if (!!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && !!symbol.valueDeclaration.initializer) { - const init = symbol.valueDeclaration.initializer; - q.push(init); - if (isAssignmentExpression(init, /*excludeCompoundAssignment*/ true)) { - q.push(init.left); - q.push(init.right); - } +function isPurelyTypeDeclaration(s: Statement): boolean { + switch (s.kind) { + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return true; + case SyntaxKind.ModuleDeclaration: + return getModuleInstanceState(s as ModuleDeclaration) !== ModuleInstanceState.Instantiated; + case SyntaxKind.EnumDeclaration: + return hasSyntacticModifier(s, ModifierFlags.Const); + default: + return false; + } +} + +export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean { + let i = 0; + const q = [node]; + while (q.length && i < 100) { + i++; + node = q.shift()!; + if (isExportsIdentifier(node) || isModuleExportsAccessExpression(node)) { + return true; + } + else if (isIdentifier(node)) { + const symbol = lookupSymbolForName(sourceFile, node.escapedText); + if (!!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && !!symbol.valueDeclaration.initializer) { + const init = symbol.valueDeclaration.initializer; + q.push(init); + if (isAssignmentExpression(init, /*excludeCompoundAssignment*/ true)) { + q.push(init.left); + q.push(init.right); } } } - return false; } + return false; +} - function lookupSymbolForName(container: Node, name: __String): Symbol | undefined { - const local = container.locals && container.locals.get(name); - if (local) { - return local.exportSymbol || local; - } - if (isSourceFile(container) && container.jsGlobalAugmentations && container.jsGlobalAugmentations.has(name)) { - return container.jsGlobalAugmentations.get(name); - } - return container.symbol && container.symbol.exports && container.symbol.exports.get(name); +function lookupSymbolForName(container: Node, name: __String): Symbol | undefined { + const local = container.locals && container.locals.get(name); + if (local) { + return local.exportSymbol || local; } + if (isSourceFile(container) && container.jsGlobalAugmentations && container.jsGlobalAugmentations.has(name)) { + return container.jsGlobalAugmentations.get(name); + } + return container.symbol && container.symbol.exports && container.symbol.exports.get(name); +} } diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index aec8cae3cea12..2ffba2bba0aba 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -1,1361 +1,1361 @@ /*@internal*/ namespace ts { - export interface ReusableDiagnostic extends ReusableDiagnosticRelatedInformation { - /** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */ - reportsUnnecessary?: {}; - reportDeprecated?: {} - source?: string; - relatedInformation?: ReusableDiagnosticRelatedInformation[]; - skippedOn?: keyof CompilerOptions; - } - - export interface ReusableDiagnosticRelatedInformation { - category: DiagnosticCategory; - code: number; - file: string | undefined; - start: number | undefined; - length: number | undefined; - messageText: string | ReusableDiagnosticMessageChain; - } +export interface ReusableDiagnostic extends ReusableDiagnosticRelatedInformation { + /** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */ + reportsUnnecessary?: {}; + reportDeprecated?: {} + source?: string; + relatedInformation?: ReusableDiagnosticRelatedInformation[]; + skippedOn?: keyof CompilerOptions; +} - export type ReusableDiagnosticMessageChain = DiagnosticMessageChain; - - export interface ReusableBuilderProgramState extends ReusableBuilderState { - /** - * Cache of bind and check diagnostics for files with their Path being the key - */ - semanticDiagnosticsPerFile?: ReadonlyESMap | undefined; - /** - * The map has key by source file's path that has been changed - */ - changedFilesSet?: ReadonlySet; - /** - * Set of affected files being iterated - */ - affectedFiles?: readonly SourceFile[] | undefined; - /** - * Current changed file for iterating over affected files - */ - currentChangedFilePath?: Path | undefined; - /** - * Map of file signatures, with key being file path, calculated while getting current changed file's affected files - * These will be committed whenever the iteration through affected files of current changed file is complete - */ - currentAffectedFilesSignatures?: ReadonlyESMap | undefined; - /** - * Newly computed visible to outside referencedSet - */ - currentAffectedFilesExportedModulesMap?: BuilderState.ReadonlyManyToManyPathMap | undefined; - /** - * True if the semantic diagnostics were copied from the old state - */ - semanticDiagnosticsFromOldState?: Set; - /** - * program corresponding to this state - */ - program?: Program | undefined; - /** - * compilerOptions for the program - */ - compilerOptions: CompilerOptions; - /** - * Files pending to be emitted - */ - affectedFilesPendingEmit?: readonly Path[] | undefined; - /** - * Files pending to be emitted kind. - */ - affectedFilesPendingEmitKind?: ReadonlyESMap | undefined; - /** - * Current index to retrieve pending affected file - */ - affectedFilesPendingEmitIndex?: number | undefined; - /* - * true if semantic diagnostics are ReusableDiagnostic instead of Diagnostic - */ - hasReusableDiagnostic?: true; - } +export interface ReusableDiagnosticRelatedInformation { + category: DiagnosticCategory; + code: number; + file: string | undefined; + start: number | undefined; + length: number | undefined; + messageText: string | ReusableDiagnosticMessageChain; +} - export const enum BuilderFileEmit { - DtsOnly, - Full - } +export type ReusableDiagnosticMessageChain = DiagnosticMessageChain; +export interface ReusableBuilderProgramState extends ReusableBuilderState { /** - * State to store the changed files, affected files and cache semantic diagnostics + * Cache of bind and check diagnostics for files with their Path being the key */ - // TODO: GH#18217 Properties of this interface are frequently asserted to be defined. - export interface BuilderProgramState extends BuilderState { - /** - * Cache of bind and check diagnostics for files with their Path being the key - */ - semanticDiagnosticsPerFile: ESMap | undefined; - /** - * The map has key by source file's path that has been changed - */ - changedFilesSet: Set; - /** - * Set of affected files being iterated - */ - affectedFiles: readonly SourceFile[] | undefined; - /** - * Current index to retrieve affected file from - */ - affectedFilesIndex: number | undefined; - /** - * Current changed file for iterating over affected files - */ - currentChangedFilePath: Path | undefined; - /** - * Map of file signatures, with key being file path, calculated while getting current changed file's affected files - * These will be committed whenever the iteration through affected files of current changed file is complete - */ - currentAffectedFilesSignatures: ESMap | undefined; - /** - * Newly computed visible to outside referencedSet - * We need to store the updates separately in case the in-progress build is cancelled - * and we need to roll back. - */ - currentAffectedFilesExportedModulesMap: BuilderState.ManyToManyPathMap | undefined; - /** - * Already seen affected files - */ - seenAffectedFiles: Set | undefined; - /** - * whether this program has cleaned semantic diagnostics cache for lib files - */ - cleanedDiagnosticsOfLibFiles?: boolean; - /** - * True if the semantic diagnostics were copied from the old state - */ - semanticDiagnosticsFromOldState?: Set; - /** - * program corresponding to this state - */ - program: Program | undefined; - /** - * compilerOptions for the program - */ - compilerOptions: CompilerOptions; - /** - * Files pending to be emitted - */ - affectedFilesPendingEmit: Path[] | undefined; - /** - * Files pending to be emitted kind. - */ - affectedFilesPendingEmitKind: ESMap | undefined; - /** - * Current index to retrieve pending affected file - */ - affectedFilesPendingEmitIndex: number | undefined; - /** - * true if build info is emitted - */ - buildInfoEmitPending: boolean; - /** - * Already seen emitted files - */ - seenEmittedFiles: ESMap | undefined; - /** - * true if program has been emitted - */ - programEmitComplete?: true; - } + semanticDiagnosticsPerFile?: ReadonlyESMap | undefined; + /** + * The map has key by source file's path that has been changed + */ + changedFilesSet?: ReadonlySet; + /** + * Set of affected files being iterated + */ + affectedFiles?: readonly SourceFile[] | undefined; + /** + * Current changed file for iterating over affected files + */ + currentChangedFilePath?: Path | undefined; + /** + * Map of file signatures, with key being file path, calculated while getting current changed file's affected files + * These will be committed whenever the iteration through affected files of current changed file is complete + */ + currentAffectedFilesSignatures?: ReadonlyESMap | undefined; + /** + * Newly computed visible to outside referencedSet + */ + currentAffectedFilesExportedModulesMap?: BuilderState.ReadonlyManyToManyPathMap | undefined; + /** + * True if the semantic diagnostics were copied from the old state + */ + semanticDiagnosticsFromOldState?: Set; + /** + * program corresponding to this state + */ + program?: Program | undefined; + /** + * compilerOptions for the program + */ + compilerOptions: CompilerOptions; + /** + * Files pending to be emitted + */ + affectedFilesPendingEmit?: readonly Path[] | undefined; + /** + * Files pending to be emitted kind. + */ + affectedFilesPendingEmitKind?: ReadonlyESMap | undefined; + /** + * Current index to retrieve pending affected file + */ + affectedFilesPendingEmitIndex?: number | undefined; + /* + * true if semantic diagnostics are ReusableDiagnostic instead of Diagnostic + */ + hasReusableDiagnostic?: true; +} - function hasSameKeys(map1: ReadonlyCollection | undefined, map2: ReadonlyCollection | undefined): boolean { - // Has same size and every key is present in both maps - return map1 === map2 || map1 !== undefined && map2 !== undefined && map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); - } +export const enum BuilderFileEmit { + DtsOnly, + Full +} +/** + * State to store the changed files, affected files and cache semantic diagnostics + */ +// TODO: GH#18217 Properties of this interface are frequently asserted to be defined. +export interface BuilderProgramState extends BuilderState { /** - * Create the state so that we can iterate on changedFiles/affected files + * Cache of bind and check diagnostics for files with their Path being the key */ - function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState: Readonly | undefined, disableUseFileVersionAsSignature: boolean | undefined): BuilderProgramState { - const state = BuilderState.create(newProgram, getCanonicalFileName, oldState, disableUseFileVersionAsSignature) as BuilderProgramState; - state.program = newProgram; - const compilerOptions = newProgram.getCompilerOptions(); - state.compilerOptions = compilerOptions; - // With --out or --outFile, any change affects all semantic diagnostics so no need to cache them - if (!outFile(compilerOptions)) { - state.semanticDiagnosticsPerFile = new Map(); - } - state.changedFilesSet = new Set(); - - const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState); - const oldCompilerOptions = useOldState ? oldState!.compilerOptions : undefined; - const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile && - !compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldCompilerOptions!); - if (useOldState) { - // Verify the sanity of old state - if (!oldState!.currentChangedFilePath) { - const affectedSignatures = oldState!.currentAffectedFilesSignatures; - Debug.assert(!oldState!.affectedFiles && (!affectedSignatures || !affectedSignatures.size), "Cannot reuse if only few affected files of currentChangedFile were iterated"); - } - const changedFilesSet = oldState!.changedFilesSet; - if (canCopySemanticDiagnostics) { - Debug.assert(!changedFilesSet || !forEachKey(changedFilesSet, path => oldState!.semanticDiagnosticsPerFile!.has(path)), "Semantic diagnostics shouldnt be available for changed files"); - } + semanticDiagnosticsPerFile: ESMap | undefined; + /** + * The map has key by source file's path that has been changed + */ + changedFilesSet: Set; + /** + * Set of affected files being iterated + */ + affectedFiles: readonly SourceFile[] | undefined; + /** + * Current index to retrieve affected file from + */ + affectedFilesIndex: number | undefined; + /** + * Current changed file for iterating over affected files + */ + currentChangedFilePath: Path | undefined; + /** + * Map of file signatures, with key being file path, calculated while getting current changed file's affected files + * These will be committed whenever the iteration through affected files of current changed file is complete + */ + currentAffectedFilesSignatures: ESMap | undefined; + /** + * Newly computed visible to outside referencedSet + * We need to store the updates separately in case the in-progress build is cancelled + * and we need to roll back. + */ + currentAffectedFilesExportedModulesMap: BuilderState.ManyToManyPathMap | undefined; + /** + * Already seen affected files + */ + seenAffectedFiles: Set | undefined; + /** + * whether this program has cleaned semantic diagnostics cache for lib files + */ + cleanedDiagnosticsOfLibFiles?: boolean; + /** + * True if the semantic diagnostics were copied from the old state + */ + semanticDiagnosticsFromOldState?: Set; + /** + * program corresponding to this state + */ + program: Program | undefined; + /** + * compilerOptions for the program + */ + compilerOptions: CompilerOptions; + /** + * Files pending to be emitted + */ + affectedFilesPendingEmit: Path[] | undefined; + /** + * Files pending to be emitted kind. + */ + affectedFilesPendingEmitKind: ESMap | undefined; + /** + * Current index to retrieve pending affected file + */ + affectedFilesPendingEmitIndex: number | undefined; + /** + * true if build info is emitted + */ + buildInfoEmitPending: boolean; + /** + * Already seen emitted files + */ + seenEmittedFiles: ESMap | undefined; + /** + * true if program has been emitted + */ + programEmitComplete?: true; +} - // Copy old state's changed files set - changedFilesSet?.forEach(value => state.changedFilesSet.add(value)); - if (!outFile(compilerOptions) && oldState!.affectedFilesPendingEmit) { - state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit.slice(); - state.affectedFilesPendingEmitKind = oldState!.affectedFilesPendingEmitKind && new Map(oldState!.affectedFilesPendingEmitKind); - state.affectedFilesPendingEmitIndex = oldState!.affectedFilesPendingEmitIndex; - state.seenAffectedFiles = new Set(); - } - } +function hasSameKeys(map1: ReadonlyCollection | undefined, map2: ReadonlyCollection | undefined): boolean { + // Has same size and every key is present in both maps + return map1 === map2 || map1 !== undefined && map2 !== undefined && map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); +} - // Update changed files and copy semantic diagnostics if we can - const referencedMap = state.referencedMap; - const oldReferencedMap = useOldState ? oldState!.referencedMap : undefined; - const copyDeclarationFileDiagnostics = canCopySemanticDiagnostics && !compilerOptions.skipLibCheck === !oldCompilerOptions!.skipLibCheck; - const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck; - state.fileInfos.forEach((info, sourceFilePath) => { - let oldInfo: Readonly | undefined; - let newReferences: ReadonlySet | undefined; - - // if not using old state, every file is changed - if (!useOldState || - // File wasn't present in old state - !(oldInfo = oldState!.fileInfos.get(sourceFilePath)) || - // versions dont match - oldInfo.version !== info.version || - // Referenced files changed - !hasSameKeys(newReferences = referencedMap && referencedMap.getValues(sourceFilePath), oldReferencedMap && oldReferencedMap.getValues(sourceFilePath)) || - // Referenced file was deleted in the new program - newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState!.fileInfos.has(path))) { - // Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated - state.changedFilesSet.add(sourceFilePath); - } - else if (canCopySemanticDiagnostics) { - const sourceFile = newProgram.getSourceFileByPath(sourceFilePath)!; - - if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) return; - if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) return; - - // Unchanged file copy diagnostics - const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath); - if (diagnostics) { - state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram, getCanonicalFileName) : diagnostics as readonly Diagnostic[]); - if (!state.semanticDiagnosticsFromOldState) { - state.semanticDiagnosticsFromOldState = new Set(); - } - state.semanticDiagnosticsFromOldState.add(sourceFilePath); - } - } - }); +/** + * Create the state so that we can iterate on changedFiles/affected files + */ +function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState: Readonly | undefined, disableUseFileVersionAsSignature: boolean | undefined): BuilderProgramState { + const state = BuilderState.create(newProgram, getCanonicalFileName, oldState, disableUseFileVersionAsSignature) as BuilderProgramState; + state.program = newProgram; + const compilerOptions = newProgram.getCompilerOptions(); + state.compilerOptions = compilerOptions; + // With --out or --outFile, any change affects all semantic diagnostics so no need to cache them + if (!outFile(compilerOptions)) { + state.semanticDiagnosticsPerFile = new Map(); + } + state.changedFilesSet = new Set(); + + const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState); + const oldCompilerOptions = useOldState ? oldState!.compilerOptions : undefined; + const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile && + !compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldCompilerOptions!); + if (useOldState) { + // Verify the sanity of old state + if (!oldState!.currentChangedFilePath) { + const affectedSignatures = oldState!.currentAffectedFilesSignatures; + Debug.assert(!oldState!.affectedFiles && (!affectedSignatures || !affectedSignatures.size), "Cannot reuse if only few affected files of currentChangedFile were iterated"); + } + const changedFilesSet = oldState!.changedFilesSet; + if (canCopySemanticDiagnostics) { + Debug.assert(!changedFilesSet || !forEachKey(changedFilesSet, path => oldState!.semanticDiagnosticsPerFile!.has(path)), "Semantic diagnostics shouldnt be available for changed files"); + } - // If the global file is removed, add all files as changed - if (useOldState && forEachEntry(oldState!.fileInfos, (info, sourceFilePath) => info.affectsGlobalScope && !state.fileInfos.has(sourceFilePath))) { - BuilderState.getAllFilesExcludingDefaultLibraryFile(state, newProgram, /*firstSourceFile*/ undefined) - .forEach(file => state.changedFilesSet.add(file.resolvedPath)); + // Copy old state's changed files set + changedFilesSet?.forEach(value => state.changedFilesSet.add(value)); + if (!outFile(compilerOptions) && oldState!.affectedFilesPendingEmit) { + state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit.slice(); + state.affectedFilesPendingEmitKind = oldState!.affectedFilesPendingEmitKind && new Map(oldState!.affectedFilesPendingEmitKind); + state.affectedFilesPendingEmitIndex = oldState!.affectedFilesPendingEmitIndex; + state.seenAffectedFiles = new Set(); } - else if (oldCompilerOptions && !outFile(compilerOptions) && compilerOptionsAffectEmit(compilerOptions, oldCompilerOptions)) { - // Add all files to affectedFilesPendingEmit since emit changed - newProgram.getSourceFiles().forEach(f => addToAffectedFilesPendingEmit(state, f.resolvedPath, BuilderFileEmit.Full)); - Debug.assert(!state.seenAffectedFiles || !state.seenAffectedFiles.size); - state.seenAffectedFiles = state.seenAffectedFiles || new Set(); + } + + // Update changed files and copy semantic diagnostics if we can + const referencedMap = state.referencedMap; + const oldReferencedMap = useOldState ? oldState!.referencedMap : undefined; + const copyDeclarationFileDiagnostics = canCopySemanticDiagnostics && !compilerOptions.skipLibCheck === !oldCompilerOptions!.skipLibCheck; + const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck; + state.fileInfos.forEach((info, sourceFilePath) => { + let oldInfo: Readonly | undefined; + let newReferences: ReadonlySet | undefined; + + // if not using old state, every file is changed + if (!useOldState || + // File wasn't present in old state + !(oldInfo = oldState!.fileInfos.get(sourceFilePath)) || + // versions dont match + oldInfo.version !== info.version || + // Referenced files changed + !hasSameKeys(newReferences = referencedMap && referencedMap.getValues(sourceFilePath), oldReferencedMap && oldReferencedMap.getValues(sourceFilePath)) || + // Referenced file was deleted in the new program + newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState!.fileInfos.has(path))) { + // Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated + state.changedFilesSet.add(sourceFilePath); } - if (useOldState) { - // Any time the interpretation of a source file changes, mark it as changed - forEachEntry(oldState!.fileInfos, (info, sourceFilePath) => { - if (state.fileInfos.has(sourceFilePath) && state.fileInfos.get(sourceFilePath)!.impliedFormat !== info.impliedFormat) { - state.changedFilesSet.add(sourceFilePath); + else if (canCopySemanticDiagnostics) { + const sourceFile = newProgram.getSourceFileByPath(sourceFilePath)!; + + if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) return; + if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) return; + + // Unchanged file copy diagnostics + const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath); + if (diagnostics) { + state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram, getCanonicalFileName) : diagnostics as readonly Diagnostic[]); + if (!state.semanticDiagnosticsFromOldState) { + state.semanticDiagnosticsFromOldState = new Set(); } - }); + state.semanticDiagnosticsFromOldState.add(sourceFilePath); + } } + }); - state.buildInfoEmitPending = !!state.changedFilesSet.size; - return state; + // If the global file is removed, add all files as changed + if (useOldState && forEachEntry(oldState!.fileInfos, (info, sourceFilePath) => info.affectsGlobalScope && !state.fileInfos.has(sourceFilePath))) { + BuilderState.getAllFilesExcludingDefaultLibraryFile(state, newProgram, /*firstSourceFile*/ undefined) + .forEach(file => state.changedFilesSet.add(file.resolvedPath)); } - - function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program, getCanonicalFileName: GetCanonicalFileName): readonly Diagnostic[] { - if (!diagnostics.length) return emptyArray; - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(newProgram.getCompilerOptions())!, newProgram.getCurrentDirectory())); - return diagnostics.map(diagnostic => { - const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath); - result.reportsUnnecessary = diagnostic.reportsUnnecessary; - result.reportsDeprecated = diagnostic.reportDeprecated; - result.source = diagnostic.source; - result.skippedOn = diagnostic.skippedOn; - const { relatedInformation } = diagnostic; - result.relatedInformation = relatedInformation ? - relatedInformation.length ? - relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram, toPath)) : - [] : - undefined; - return result; + else if (oldCompilerOptions && !outFile(compilerOptions) && compilerOptionsAffectEmit(compilerOptions, oldCompilerOptions)) { + // Add all files to affectedFilesPendingEmit since emit changed + newProgram.getSourceFiles().forEach(f => addToAffectedFilesPendingEmit(state, f.resolvedPath, BuilderFileEmit.Full)); + Debug.assert(!state.seenAffectedFiles || !state.seenAffectedFiles.size); + state.seenAffectedFiles = state.seenAffectedFiles || new Set(); + } + if (useOldState) { + // Any time the interpretation of a source file changes, mark it as changed + forEachEntry(oldState!.fileInfos, (info, sourceFilePath) => { + if (state.fileInfos.has(sourceFilePath) && state.fileInfos.get(sourceFilePath)!.impliedFormat !== info.impliedFormat) { + state.changedFilesSet.add(sourceFilePath); + } }); - - function toPath(path: string) { - return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); - } } - function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation { - const { file } = diagnostic; - return { - ...diagnostic, - file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined - }; - } + state.buildInfoEmitPending = !!state.changedFilesSet.size; + return state; +} - /** - * Releases program and other related not needed properties - */ - function releaseCache(state: BuilderProgramState) { - BuilderState.releaseCache(state); - state.program = undefined; - } +function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program, getCanonicalFileName: GetCanonicalFileName): readonly Diagnostic[] { + if (!diagnostics.length) return emptyArray; + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(newProgram.getCompilerOptions())!, newProgram.getCurrentDirectory())); + return diagnostics.map(diagnostic => { + const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath); + result.reportsUnnecessary = diagnostic.reportsUnnecessary; + result.reportsDeprecated = diagnostic.reportDeprecated; + result.source = diagnostic.source; + result.skippedOn = diagnostic.skippedOn; + const { relatedInformation } = diagnostic; + result.relatedInformation = relatedInformation ? + relatedInformation.length ? + relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram, toPath)) : + [] : + undefined; + return result; + }); - /** - * Creates a clone of the state - */ - function cloneBuilderProgramState(state: Readonly): BuilderProgramState { - const newState = BuilderState.clone(state) as BuilderProgramState; - newState.semanticDiagnosticsPerFile = state.semanticDiagnosticsPerFile && new Map(state.semanticDiagnosticsPerFile); - newState.changedFilesSet = new Set(state.changedFilesSet); - newState.affectedFiles = state.affectedFiles; - newState.affectedFilesIndex = state.affectedFilesIndex; - newState.currentChangedFilePath = state.currentChangedFilePath; - newState.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures && new Map(state.currentAffectedFilesSignatures); - newState.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap?.clone(); - newState.seenAffectedFiles = state.seenAffectedFiles && new Set(state.seenAffectedFiles); - newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles; - newState.semanticDiagnosticsFromOldState = state.semanticDiagnosticsFromOldState && new Set(state.semanticDiagnosticsFromOldState); - newState.program = state.program; - newState.compilerOptions = state.compilerOptions; - newState.affectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice(); - newState.affectedFilesPendingEmitKind = state.affectedFilesPendingEmitKind && new Map(state.affectedFilesPendingEmitKind); - newState.affectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex; - newState.seenEmittedFiles = state.seenEmittedFiles && new Map(state.seenEmittedFiles); - newState.programEmitComplete = state.programEmitComplete; - return newState; + function toPath(path: string) { + return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); } +} - /** - * Verifies that source file is ok to be used in calls that arent handled by next - */ - function assertSourceFileOkWithoutNextAffectedCall(state: BuilderProgramState, sourceFile: SourceFile | undefined) { - Debug.assert(!sourceFile || !state.affectedFiles || state.affectedFiles[state.affectedFilesIndex! - 1] !== sourceFile || !state.semanticDiagnosticsPerFile!.has(sourceFile.resolvedPath)); - } +function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation { + const { file } = diagnostic; + return { + ...diagnostic, + file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined + }; +} - /** - * This function returns the next affected file to be processed. - * Note that until doneAffected is called it would keep reporting same result - * This is to allow the callers to be able to actually remove affected file only when the operation is complete - * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained - */ - function getNextAffectedFile(state: BuilderProgramState, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash): SourceFile | Program | undefined { - while (true) { - const { affectedFiles } = state; - if (affectedFiles) { - const seenAffectedFiles = state.seenAffectedFiles!; - let affectedFilesIndex = state.affectedFilesIndex!; // TODO: GH#18217 - while (affectedFilesIndex < affectedFiles.length) { - const affectedFile = affectedFiles[affectedFilesIndex]; - if (!seenAffectedFiles.has(affectedFile.resolvedPath)) { - // Set the next affected file as seen and remove the cached semantic diagnostics - state.affectedFilesIndex = affectedFilesIndex; - handleDtsMayChangeOfAffectedFile(state, affectedFile, cancellationToken, computeHash); - return affectedFile; - } - affectedFilesIndex++; - } +/** + * Releases program and other related not needed properties + */ +function releaseCache(state: BuilderProgramState) { + BuilderState.releaseCache(state); + state.program = undefined; +} - // Remove the changed file from the change set - state.changedFilesSet.delete(state.currentChangedFilePath!); - state.currentChangedFilePath = undefined; - // Commit the changes in file signature - BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures!); - state.currentAffectedFilesSignatures!.clear(); - BuilderState.updateExportedFilesMapFromCache(state, state.currentAffectedFilesExportedModulesMap); - state.affectedFiles = undefined; - } +/** + * Creates a clone of the state + */ +function cloneBuilderProgramState(state: Readonly): BuilderProgramState { + const newState = BuilderState.clone(state) as BuilderProgramState; + newState.semanticDiagnosticsPerFile = state.semanticDiagnosticsPerFile && new Map(state.semanticDiagnosticsPerFile); + newState.changedFilesSet = new Set(state.changedFilesSet); + newState.affectedFiles = state.affectedFiles; + newState.affectedFilesIndex = state.affectedFilesIndex; + newState.currentChangedFilePath = state.currentChangedFilePath; + newState.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures && new Map(state.currentAffectedFilesSignatures); + newState.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap?.clone(); + newState.seenAffectedFiles = state.seenAffectedFiles && new Set(state.seenAffectedFiles); + newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles; + newState.semanticDiagnosticsFromOldState = state.semanticDiagnosticsFromOldState && new Set(state.semanticDiagnosticsFromOldState); + newState.program = state.program; + newState.compilerOptions = state.compilerOptions; + newState.affectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice(); + newState.affectedFilesPendingEmitKind = state.affectedFilesPendingEmitKind && new Map(state.affectedFilesPendingEmitKind); + newState.affectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex; + newState.seenEmittedFiles = state.seenEmittedFiles && new Map(state.seenEmittedFiles); + newState.programEmitComplete = state.programEmitComplete; + return newState; +} - // Get next changed file - const nextKey = state.changedFilesSet.keys().next(); - if (nextKey.done) { - // Done - return undefined; - } +/** + * Verifies that source file is ok to be used in calls that arent handled by next + */ +function assertSourceFileOkWithoutNextAffectedCall(state: BuilderProgramState, sourceFile: SourceFile | undefined) { + Debug.assert(!sourceFile || !state.affectedFiles || state.affectedFiles[state.affectedFilesIndex! - 1] !== sourceFile || !state.semanticDiagnosticsPerFile!.has(sourceFile.resolvedPath)); +} - // With --out or --outFile all outputs go into single file - // so operations are performed directly on program, return program - const program = Debug.checkDefined(state.program); - const compilerOptions = program.getCompilerOptions(); - if (outFile(compilerOptions)) { - Debug.assert(!state.semanticDiagnosticsPerFile); - return program; +/** + * This function returns the next affected file to be processed. + * Note that until doneAffected is called it would keep reporting same result + * This is to allow the callers to be able to actually remove affected file only when the operation is complete + * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained + */ +function getNextAffectedFile(state: BuilderProgramState, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash): SourceFile | Program | undefined { + while (true) { + const { affectedFiles } = state; + if (affectedFiles) { + const seenAffectedFiles = state.seenAffectedFiles!; + let affectedFilesIndex = state.affectedFilesIndex!; // TODO: GH#18217 + while (affectedFilesIndex < affectedFiles.length) { + const affectedFile = affectedFiles[affectedFilesIndex]; + if (!seenAffectedFiles.has(affectedFile.resolvedPath)) { + // Set the next affected file as seen and remove the cached semantic diagnostics + state.affectedFilesIndex = affectedFilesIndex; + handleDtsMayChangeOfAffectedFile(state, affectedFile, cancellationToken, computeHash); + return affectedFile; + } + affectedFilesIndex++; } - // Get next batch of affected files - if (!state.currentAffectedFilesSignatures) state.currentAffectedFilesSignatures = new Map(); - if (state.exportedModulesMap) { - state.currentAffectedFilesExportedModulesMap ||= BuilderState.createManyToManyPathMap(); - } - state.affectedFiles = BuilderState.getFilesAffectedBy(state, program, nextKey.value, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap); - state.currentChangedFilePath = nextKey.value; - state.affectedFilesIndex = 0; - if (!state.seenAffectedFiles) state.seenAffectedFiles = new Set(); + // Remove the changed file from the change set + state.changedFilesSet.delete(state.currentChangedFilePath!); + state.currentChangedFilePath = undefined; + // Commit the changes in file signature + BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures!); + state.currentAffectedFilesSignatures!.clear(); + BuilderState.updateExportedFilesMapFromCache(state, state.currentAffectedFilesExportedModulesMap); + state.affectedFiles = undefined; } + + // Get next changed file + const nextKey = state.changedFilesSet.keys().next(); + if (nextKey.done) { + // Done + return undefined; + } + + // With --out or --outFile all outputs go into single file + // so operations are performed directly on program, return program + const program = Debug.checkDefined(state.program); + const compilerOptions = program.getCompilerOptions(); + if (outFile(compilerOptions)) { + Debug.assert(!state.semanticDiagnosticsPerFile); + return program; + } + + // Get next batch of affected files + if (!state.currentAffectedFilesSignatures) state.currentAffectedFilesSignatures = new Map(); + if (state.exportedModulesMap) { + state.currentAffectedFilesExportedModulesMap ||= BuilderState.createManyToManyPathMap(); + } + state.affectedFiles = BuilderState.getFilesAffectedBy(state, program, nextKey.value, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap); + state.currentChangedFilePath = nextKey.value; + state.affectedFilesIndex = 0; + if (!state.seenAffectedFiles) state.seenAffectedFiles = new Set(); } +} - /** - * Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet - */ - function getNextAffectedFilePendingEmit(state: BuilderProgramState) { - const { affectedFilesPendingEmit } = state; - if (affectedFilesPendingEmit) { - const seenEmittedFiles = (state.seenEmittedFiles || (state.seenEmittedFiles = new Map())); - for (let i = state.affectedFilesPendingEmitIndex!; i < affectedFilesPendingEmit.length; i++) { - const affectedFile = Debug.checkDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[i]); - if (affectedFile) { - const seenKind = seenEmittedFiles.get(affectedFile.resolvedPath); - const emitKind = Debug.checkDefined(Debug.checkDefined(state.affectedFilesPendingEmitKind).get(affectedFile.resolvedPath)); - if (seenKind === undefined || seenKind < emitKind) { - // emit this file - state.affectedFilesPendingEmitIndex = i; - return { affectedFile, emitKind }; - } +/** + * Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet + */ +function getNextAffectedFilePendingEmit(state: BuilderProgramState) { + const { affectedFilesPendingEmit } = state; + if (affectedFilesPendingEmit) { + const seenEmittedFiles = (state.seenEmittedFiles || (state.seenEmittedFiles = new Map())); + for (let i = state.affectedFilesPendingEmitIndex!; i < affectedFilesPendingEmit.length; i++) { + const affectedFile = Debug.checkDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[i]); + if (affectedFile) { + const seenKind = seenEmittedFiles.get(affectedFile.resolvedPath); + const emitKind = Debug.checkDefined(Debug.checkDefined(state.affectedFilesPendingEmitKind).get(affectedFile.resolvedPath)); + if (seenKind === undefined || seenKind < emitKind) { + // emit this file + state.affectedFilesPendingEmitIndex = i; + return { affectedFile, emitKind }; } } - state.affectedFilesPendingEmit = undefined; - state.affectedFilesPendingEmitKind = undefined; - state.affectedFilesPendingEmitIndex = undefined; } - return undefined; + state.affectedFilesPendingEmit = undefined; + state.affectedFilesPendingEmitKind = undefined; + state.affectedFilesPendingEmitIndex = undefined; } + return undefined; +} - /** - * Handles semantic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file - * This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change - */ - function handleDtsMayChangeOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { - removeSemanticDiagnosticsOf(state, affectedFile.resolvedPath); +/** + * Handles semantic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file + * This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change + */ +function handleDtsMayChangeOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { + removeSemanticDiagnosticsOf(state, affectedFile.resolvedPath); + + // If affected files is everything except default library, then nothing more to do + if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) { + if (!state.cleanedDiagnosticsOfLibFiles) { + state.cleanedDiagnosticsOfLibFiles = true; + const program = Debug.checkDefined(state.program); + const options = program.getCompilerOptions(); + forEach(program.getSourceFiles(), f => + program.isSourceFileDefaultLibrary(f) && + !skipTypeChecking(f, options, program) && + removeSemanticDiagnosticsOf(state, f.resolvedPath) + ); + } + // When a change affects the global scope, all files are considered to be affected without updating their signature + // That means when affected file is handled, its signature can be out of date + // To avoid this, ensure that we update the signature for any affected file in this scenario. + BuilderState.updateShapeSignature( + state, + Debug.checkDefined(state.program), + affectedFile, + Debug.checkDefined(state.currentAffectedFilesSignatures), + cancellationToken, + computeHash, + state.currentAffectedFilesExportedModulesMap + ); + return; + } + else { + Debug.assert(state.hasCalledUpdateShapeSignature.has(affectedFile.resolvedPath) || state.currentAffectedFilesSignatures?.has(affectedFile.resolvedPath), `Signature not updated for affected file: ${affectedFile.fileName}`); + } - // If affected files is everything except default library, then nothing more to do - if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) { - if (!state.cleanedDiagnosticsOfLibFiles) { - state.cleanedDiagnosticsOfLibFiles = true; - const program = Debug.checkDefined(state.program); - const options = program.getCompilerOptions(); - forEach(program.getSourceFiles(), f => - program.isSourceFileDefaultLibrary(f) && - !skipTypeChecking(f, options, program) && - removeSemanticDiagnosticsOf(state, f.resolvedPath) - ); - } - // When a change affects the global scope, all files are considered to be affected without updating their signature - // That means when affected file is handled, its signature can be out of date - // To avoid this, ensure that we update the signature for any affected file in this scenario. + if (!state.compilerOptions.assumeChangesOnlyAffectDirectDependencies) { + forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, (state, path) => handleDtsMayChangeOf(state, path, cancellationToken, computeHash)); + } +} + +/** + * Handle the dts may change, so they need to be added to pending emit if dts emit is enabled, + * Also we need to make sure signature is updated for these files + */ +function handleDtsMayChangeOf(state: BuilderProgramState, path: Path, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash): void { + removeSemanticDiagnosticsOf(state, path); + + if (!state.changedFilesSet.has(path)) { + const program = Debug.checkDefined(state.program); + const sourceFile = program.getSourceFileByPath(path); + if (sourceFile) { + // Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics + // we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file + // This ensures that we dont later during incremental builds considering wrong signature. + // Eg where this also is needed to ensure that .tsbuildinfo generated by incremental build should be same as if it was first fresh build + // But we avoid expensive full shape computation, as using file version as shape is enough for correctness. BuilderState.updateShapeSignature( state, - Debug.checkDefined(state.program), - affectedFile, + program, + sourceFile, Debug.checkDefined(state.currentAffectedFilesSignatures), cancellationToken, computeHash, - state.currentAffectedFilesExportedModulesMap + state.currentAffectedFilesExportedModulesMap, + /* useFileVersionAsSignature */ true ); - return; - } - else { - Debug.assert(state.hasCalledUpdateShapeSignature.has(affectedFile.resolvedPath) || state.currentAffectedFilesSignatures?.has(affectedFile.resolvedPath), `Signature not updated for affected file: ${affectedFile.fileName}`); - } - - if (!state.compilerOptions.assumeChangesOnlyAffectDirectDependencies) { - forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, (state, path) => handleDtsMayChangeOf(state, path, cancellationToken, computeHash)); - } - } - - /** - * Handle the dts may change, so they need to be added to pending emit if dts emit is enabled, - * Also we need to make sure signature is updated for these files - */ - function handleDtsMayChangeOf(state: BuilderProgramState, path: Path, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash): void { - removeSemanticDiagnosticsOf(state, path); - - if (!state.changedFilesSet.has(path)) { - const program = Debug.checkDefined(state.program); - const sourceFile = program.getSourceFileByPath(path); - if (sourceFile) { - // Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics - // we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file - // This ensures that we dont later during incremental builds considering wrong signature. - // Eg where this also is needed to ensure that .tsbuildinfo generated by incremental build should be same as if it was first fresh build - // But we avoid expensive full shape computation, as using file version as shape is enough for correctness. - BuilderState.updateShapeSignature( - state, - program, - sourceFile, - Debug.checkDefined(state.currentAffectedFilesSignatures), - cancellationToken, - computeHash, - state.currentAffectedFilesExportedModulesMap, - /* useFileVersionAsSignature */ true - ); - // If not dts emit, nothing more to do - if (getEmitDeclarations(state.compilerOptions)) { - addToAffectedFilesPendingEmit(state, path, BuilderFileEmit.DtsOnly); - } + // If not dts emit, nothing more to do + if (getEmitDeclarations(state.compilerOptions)) { + addToAffectedFilesPendingEmit(state, path, BuilderFileEmit.DtsOnly); } } } +} - /** - * Removes semantic diagnostics for path and - * returns true if there are no more semantic diagnostics from the old state - */ - function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) { - if (!state.semanticDiagnosticsFromOldState) { - return true; - } - state.semanticDiagnosticsFromOldState.delete(path); - state.semanticDiagnosticsPerFile!.delete(path); - return !state.semanticDiagnosticsFromOldState.size; +/** + * Removes semantic diagnostics for path and + * returns true if there are no more semantic diagnostics from the old state + */ +function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) { + if (!state.semanticDiagnosticsFromOldState) { + return true; } + state.semanticDiagnosticsFromOldState.delete(path); + state.semanticDiagnosticsPerFile!.delete(path); + return !state.semanticDiagnosticsFromOldState.size; +} - function isChangedSignature(state: BuilderProgramState, path: Path) { - const newSignature = Debug.checkDefined(state.currentAffectedFilesSignatures).get(path); - const oldSignature = Debug.checkDefined(state.fileInfos.get(path)).signature; - return newSignature !== oldSignature; - } +function isChangedSignature(state: BuilderProgramState, path: Path) { + const newSignature = Debug.checkDefined(state.currentAffectedFilesSignatures).get(path); + const oldSignature = Debug.checkDefined(state.fileInfos.get(path)).signature; + return newSignature !== oldSignature; +} - /** - * Iterate on referencing modules that export entities from affected file - */ - function forEachReferencingModulesOfExportOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, fn: (state: BuilderProgramState, filePath: Path) => void) { - // If there was change in signature (dts output) for the changed file, - // then only we need to handle pending file emit - if (!state.exportedModulesMap || !state.changedFilesSet.has(affectedFile.resolvedPath)) { - return; - } +/** + * Iterate on referencing modules that export entities from affected file + */ +function forEachReferencingModulesOfExportOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, fn: (state: BuilderProgramState, filePath: Path) => void) { + // If there was change in signature (dts output) for the changed file, + // then only we need to handle pending file emit + if (!state.exportedModulesMap || !state.changedFilesSet.has(affectedFile.resolvedPath)) { + return; + } - if (!isChangedSignature(state, affectedFile.resolvedPath)) return; - - // Since isolated modules dont change js files, files affected by change in signature is itself - // But we need to cleanup semantic diagnostics and queue dts emit for affected files - if (state.compilerOptions.isolatedModules) { - const seenFileNamesMap = new Map(); - seenFileNamesMap.set(affectedFile.resolvedPath, true); - const queue = BuilderState.getReferencedByPaths(state, affectedFile.resolvedPath); - while (queue.length > 0) { - const currentPath = queue.pop()!; - if (!seenFileNamesMap.has(currentPath)) { - seenFileNamesMap.set(currentPath, true); - fn(state, currentPath); - if (isChangedSignature(state, currentPath)) { - const currentSourceFile = Debug.checkDefined(state.program).getSourceFileByPath(currentPath)!; - queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath)); - } + if (!isChangedSignature(state, affectedFile.resolvedPath)) return; + + // Since isolated modules dont change js files, files affected by change in signature is itself + // But we need to cleanup semantic diagnostics and queue dts emit for affected files + if (state.compilerOptions.isolatedModules) { + const seenFileNamesMap = new Map(); + seenFileNamesMap.set(affectedFile.resolvedPath, true); + const queue = BuilderState.getReferencedByPaths(state, affectedFile.resolvedPath); + while (queue.length > 0) { + const currentPath = queue.pop()!; + if (!seenFileNamesMap.has(currentPath)) { + seenFileNamesMap.set(currentPath, true); + fn(state, currentPath); + if (isChangedSignature(state, currentPath)) { + const currentSourceFile = Debug.checkDefined(state.program).getSourceFileByPath(currentPath)!; + queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath)); } } } - - Debug.assert(!!state.currentAffectedFilesExportedModulesMap); - - const seenFileAndExportsOfFile = new Set(); - // Go through exported modules from cache first - // If exported modules has path, all files referencing file exported from are affected - state.currentAffectedFilesExportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => - forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn) - ); - - // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected - state.exportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => - // If the cache had an updated value, skip - !state.currentAffectedFilesExportedModulesMap!.hasKey(exportedFromPath) && - !state.currentAffectedFilesExportedModulesMap!.deletedKeys()?.has(exportedFromPath) && - forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn) - ); } - /** - * Iterate on files referencing referencedPath - */ - function forEachFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Set, fn: (state: BuilderProgramState, filePath: Path) => void): void { - state.referencedMap!.getKeys(referencedPath)?.forEach(filePath => - forEachFileAndExportsOfFile(state, filePath, seenFileAndExportsOfFile, fn) - ); - } + Debug.assert(!!state.currentAffectedFilesExportedModulesMap); + + const seenFileAndExportsOfFile = new Set(); + // Go through exported modules from cache first + // If exported modules has path, all files referencing file exported from are affected + state.currentAffectedFilesExportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => + forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn) + ); + + // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected + state.exportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => + // If the cache had an updated value, skip + !state.currentAffectedFilesExportedModulesMap!.hasKey(exportedFromPath) && + !state.currentAffectedFilesExportedModulesMap!.deletedKeys()?.has(exportedFromPath) && + forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn) + ); +} - /** - * fn on file and iterate on anything that exports this file - */ - function forEachFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Set, fn: (state: BuilderProgramState, filePath: Path) => void): void { - if (!tryAddToSet(seenFileAndExportsOfFile, filePath)) { - return; - } +/** + * Iterate on files referencing referencedPath + */ +function forEachFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Set, fn: (state: BuilderProgramState, filePath: Path) => void): void { + state.referencedMap!.getKeys(referencedPath)?.forEach(filePath => + forEachFileAndExportsOfFile(state, filePath, seenFileAndExportsOfFile, fn) + ); +} - fn(state, filePath); +/** + * fn on file and iterate on anything that exports this file + */ +function forEachFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Set, fn: (state: BuilderProgramState, filePath: Path) => void): void { + if (!tryAddToSet(seenFileAndExportsOfFile, filePath)) { + return; + } - Debug.assert(!!state.currentAffectedFilesExportedModulesMap); - // Go through exported modules from cache first - // If exported modules has path, all files referencing file exported from are affected - state.currentAffectedFilesExportedModulesMap.getKeys(filePath)?.forEach(exportedFromPath => - forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn) - ); + fn(state, filePath); + + Debug.assert(!!state.currentAffectedFilesExportedModulesMap); + // Go through exported modules from cache first + // If exported modules has path, all files referencing file exported from are affected + state.currentAffectedFilesExportedModulesMap.getKeys(filePath)?.forEach(exportedFromPath => + forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn) + ); + + // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected + state.exportedModulesMap!.getKeys(filePath)?.forEach(exportedFromPath => + // If the cache had an updated value, skip + !state.currentAffectedFilesExportedModulesMap!.hasKey(exportedFromPath) && + !state.currentAffectedFilesExportedModulesMap!.deletedKeys()?.has(exportedFromPath) && + forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn) + ); + + // Remove diagnostics of files that import this file (without going to exports of referencing files) + state.referencedMap!.getKeys(filePath)?.forEach(referencingFilePath => + !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file + fn(state, referencingFilePath) // Dont add to seen since this is not yet done with the export removal + ); +} - // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected - state.exportedModulesMap!.getKeys(filePath)?.forEach(exportedFromPath => - // If the cache had an updated value, skip - !state.currentAffectedFilesExportedModulesMap!.hasKey(exportedFromPath) && - !state.currentAffectedFilesExportedModulesMap!.deletedKeys()?.has(exportedFromPath) && - forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn) - ); - // Remove diagnostics of files that import this file (without going to exports of referencing files) - state.referencedMap!.getKeys(filePath)?.forEach(referencingFilePath => - !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file - fn(state, referencingFilePath) // Dont add to seen since this is not yet done with the export removal - ); +/** + * This is called after completing operation on the next affected file. + * The operations here are postponed to ensure that cancellation during the iteration is handled correctly + */ +function doneWithAffectedFile( + state: BuilderProgramState, + affected: SourceFile | Program, + emitKind?: BuilderFileEmit, + isPendingEmit?: boolean, + isBuildInfoEmit?: boolean +) { + if (isBuildInfoEmit) { + state.buildInfoEmitPending = false; } - - - /** - * This is called after completing operation on the next affected file. - * The operations here are postponed to ensure that cancellation during the iteration is handled correctly - */ - function doneWithAffectedFile( - state: BuilderProgramState, - affected: SourceFile | Program, - emitKind?: BuilderFileEmit, - isPendingEmit?: boolean, - isBuildInfoEmit?: boolean - ) { - if (isBuildInfoEmit) { - state.buildInfoEmitPending = false; + else if (affected === state.program) { + state.changedFilesSet.clear(); + state.programEmitComplete = true; + } + else { + state.seenAffectedFiles!.add((affected as SourceFile).resolvedPath); + if (emitKind !== undefined) { + (state.seenEmittedFiles || (state.seenEmittedFiles = new Map())).set((affected as SourceFile).resolvedPath, emitKind); } - else if (affected === state.program) { - state.changedFilesSet.clear(); - state.programEmitComplete = true; + if (isPendingEmit) { + state.affectedFilesPendingEmitIndex!++; + state.buildInfoEmitPending = true; } else { - state.seenAffectedFiles!.add((affected as SourceFile).resolvedPath); - if (emitKind !== undefined) { - (state.seenEmittedFiles || (state.seenEmittedFiles = new Map())).set((affected as SourceFile).resolvedPath, emitKind); - } - if (isPendingEmit) { - state.affectedFilesPendingEmitIndex!++; - state.buildInfoEmitPending = true; - } - else { - state.affectedFilesIndex!++; - } + state.affectedFilesIndex!++; } } +} - /** - * Returns the result with affected file - */ - function toAffectedFileResult(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult { - doneWithAffectedFile(state, affected); - return { result, affected }; - } - - /** - * Returns the result with affected file - */ - function toAffectedFileEmitResult( - state: BuilderProgramState, - result: EmitResult, - affected: SourceFile | Program, - emitKind: BuilderFileEmit, - isPendingEmit?: boolean, - isBuildInfoEmit?: boolean - ): AffectedFileResult { - doneWithAffectedFile(state, affected, emitKind, isPendingEmit, isBuildInfoEmit); - return { result, affected }; - } +/** + * Returns the result with affected file + */ +function toAffectedFileResult(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult { + doneWithAffectedFile(state, affected); + return { result, affected }; +} - /** - * Gets semantic diagnostics for the file which are - * bindAndCheckDiagnostics (from cache) and program diagnostics - */ - function getSemanticDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - return concatenate( - getBinderAndCheckerDiagnosticsOfFile(state, sourceFile, cancellationToken), - Debug.checkDefined(state.program).getProgramDiagnostics(sourceFile) - ); - } +/** + * Returns the result with affected file + */ +function toAffectedFileEmitResult( + state: BuilderProgramState, + result: EmitResult, + affected: SourceFile | Program, + emitKind: BuilderFileEmit, + isPendingEmit?: boolean, + isBuildInfoEmit?: boolean +): AffectedFileResult { + doneWithAffectedFile(state, affected, emitKind, isPendingEmit, isBuildInfoEmit); + return { result, affected }; +} - /** - * Gets the binder and checker diagnostics either from cache if present, or otherwise from program and caches it - * Note that it is assumed that when asked about binder and checker diagnostics, the file has been taken out of affected files/changed file set - */ - function getBinderAndCheckerDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - const path = sourceFile.resolvedPath; - if (state.semanticDiagnosticsPerFile) { - const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); - // Report the bind and check diagnostics from the cache if we already have those diagnostics present - if (cachedDiagnostics) { - return filterSemanticDiagnostics(cachedDiagnostics, state.compilerOptions); - } - } +/** + * Gets semantic diagnostics for the file which are + * bindAndCheckDiagnostics (from cache) and program diagnostics + */ +function getSemanticDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + return concatenate( + getBinderAndCheckerDiagnosticsOfFile(state, sourceFile, cancellationToken), + Debug.checkDefined(state.program).getProgramDiagnostics(sourceFile) + ); +} - // Diagnostics werent cached, get them from program, and cache the result - const diagnostics = Debug.checkDefined(state.program).getBindAndCheckDiagnostics(sourceFile, cancellationToken); - if (state.semanticDiagnosticsPerFile) { - state.semanticDiagnosticsPerFile.set(path, diagnostics); +/** + * Gets the binder and checker diagnostics either from cache if present, or otherwise from program and caches it + * Note that it is assumed that when asked about binder and checker diagnostics, the file has been taken out of affected files/changed file set + */ +function getBinderAndCheckerDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + const path = sourceFile.resolvedPath; + if (state.semanticDiagnosticsPerFile) { + const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); + // Report the bind and check diagnostics from the cache if we already have those diagnostics present + if (cachedDiagnostics) { + return filterSemanticDiagnostics(cachedDiagnostics, state.compilerOptions); } - return filterSemanticDiagnostics(diagnostics, state.compilerOptions); } - export type ProgramBuildInfoFileId = number & { __programBuildInfoFileIdBrand: any }; - export type ProgramBuildInfoFileIdListId = number & { __programBuildInfoFileIdListIdBrand: any }; - export type ProgramBuildInfoDiagnostic = ProgramBuildInfoFileId | [fileId: ProgramBuildInfoFileId, diagnostics: readonly ReusableDiagnostic[]]; - export type ProgramBuilderInfoFilePendingEmit = [fileId: ProgramBuildInfoFileId, emitKind: BuilderFileEmit]; - export type ProgramBuildInfoReferencedMap = [fileId: ProgramBuildInfoFileId, fileIdListId: ProgramBuildInfoFileIdListId][]; - export type ProgramBuildInfoBuilderStateFileInfo = Omit & { - /** - * Signature is - * - undefined if FileInfo.version === FileInfo.signature - * - false if FileInfo has signature as undefined (not calculated) - * - string actual signature - */ - signature: string | false | undefined; - }; - /** - * ProgramBuildInfoFileInfo is string if FileInfo.version === FileInfo.signature && !FileInfo.affectsGlobalScope otherwise encoded FileInfo - */ - export type ProgramBuildInfoFileInfo = string | ProgramBuildInfoBuilderStateFileInfo; - export interface ProgramBuildInfo { - fileNames: readonly string[]; - fileInfos: readonly ProgramBuildInfoFileInfo[]; - options: CompilerOptions | undefined; - fileIdsList?: readonly (readonly ProgramBuildInfoFileId[])[]; - referencedMap?: ProgramBuildInfoReferencedMap; - exportedModulesMap?: ProgramBuildInfoReferencedMap; - semanticDiagnosticsPerFile?: ProgramBuildInfoDiagnostic[]; - affectedFilesPendingEmit?: ProgramBuilderInfoFilePendingEmit[]; + // Diagnostics werent cached, get them from program, and cache the result + const diagnostics = Debug.checkDefined(state.program).getBindAndCheckDiagnostics(sourceFile, cancellationToken); + if (state.semanticDiagnosticsPerFile) { + state.semanticDiagnosticsPerFile.set(path, diagnostics); } + return filterSemanticDiagnostics(diagnostics, state.compilerOptions); +} +export type ProgramBuildInfoFileId = number & { __programBuildInfoFileIdBrand: any }; +export type ProgramBuildInfoFileIdListId = number & { __programBuildInfoFileIdListIdBrand: any }; +export type ProgramBuildInfoDiagnostic = ProgramBuildInfoFileId | [fileId: ProgramBuildInfoFileId, diagnostics: readonly ReusableDiagnostic[]]; +export type ProgramBuilderInfoFilePendingEmit = [fileId: ProgramBuildInfoFileId, emitKind: BuilderFileEmit]; +export type ProgramBuildInfoReferencedMap = [fileId: ProgramBuildInfoFileId, fileIdListId: ProgramBuildInfoFileIdListId][]; +export type ProgramBuildInfoBuilderStateFileInfo = Omit & { /** - * Gets the program information to be emitted in buildInfo so that we can use it to create new program + * Signature is + * - undefined if FileInfo.version === FileInfo.signature + * - false if FileInfo has signature as undefined (not calculated) + * - string actual signature */ - function getProgramBuildInfo(state: Readonly, getCanonicalFileName: GetCanonicalFileName): ProgramBuildInfo | undefined { - if (outFile(state.compilerOptions)) return undefined; - const currentDirectory = Debug.checkDefined(state.program).getCurrentDirectory(); - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory)); - const fileNames: string[] = []; - const fileNameToFileId = new Map(); - let fileIdsList: (readonly ProgramBuildInfoFileId[])[] | undefined; - let fileNamesToFileIdListId: ESMap | undefined; - const fileInfos = arrayFrom(state.fileInfos.entries(), ([key, value]): ProgramBuildInfoFileInfo => { - // Ensure fileId - const fileId = toFileId(key); - Debug.assert(fileNames[fileId - 1] === relativeToBuildInfo(key)); - const signature = state.currentAffectedFilesSignatures && state.currentAffectedFilesSignatures.get(key); - const actualSignature = signature ?? value.signature; - return value.version === actualSignature ? - value.affectsGlobalScope ? - { version: value.version, signature: undefined, affectsGlobalScope: true, impliedFormat: value.impliedFormat } : - value.version : - actualSignature !== undefined ? - signature === undefined ? - value : - { version: value.version, signature, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat } : - { version: value.version, signature: false, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat }; - }); + signature: string | false | undefined; +}; +/** + * ProgramBuildInfoFileInfo is string if FileInfo.version === FileInfo.signature && !FileInfo.affectsGlobalScope otherwise encoded FileInfo + */ +export type ProgramBuildInfoFileInfo = string | ProgramBuildInfoBuilderStateFileInfo; +export interface ProgramBuildInfo { + fileNames: readonly string[]; + fileInfos: readonly ProgramBuildInfoFileInfo[]; + options: CompilerOptions | undefined; + fileIdsList?: readonly (readonly ProgramBuildInfoFileId[])[]; + referencedMap?: ProgramBuildInfoReferencedMap; + exportedModulesMap?: ProgramBuildInfoReferencedMap; + semanticDiagnosticsPerFile?: ProgramBuildInfoDiagnostic[]; + affectedFilesPendingEmit?: ProgramBuilderInfoFilePendingEmit[]; +} - let referencedMap: ProgramBuildInfoReferencedMap | undefined; - if (state.referencedMap) { - referencedMap = arrayFrom(state.referencedMap.keys()).sort(compareStringsCaseSensitive).map(key => [ - toFileId(key), - toFileIdListId(state.referencedMap!.getValues(key)!) - ]); - } +/** + * Gets the program information to be emitted in buildInfo so that we can use it to create new program + */ +function getProgramBuildInfo(state: Readonly, getCanonicalFileName: GetCanonicalFileName): ProgramBuildInfo | undefined { + if (outFile(state.compilerOptions)) return undefined; + const currentDirectory = Debug.checkDefined(state.program).getCurrentDirectory(); + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory)); + const fileNames: string[] = []; + const fileNameToFileId = new Map(); + let fileIdsList: (readonly ProgramBuildInfoFileId[])[] | undefined; + let fileNamesToFileIdListId: ESMap | undefined; + const fileInfos = arrayFrom(state.fileInfos.entries(), ([key, value]): ProgramBuildInfoFileInfo => { + // Ensure fileId + const fileId = toFileId(key); + Debug.assert(fileNames[fileId - 1] === relativeToBuildInfo(key)); + const signature = state.currentAffectedFilesSignatures && state.currentAffectedFilesSignatures.get(key); + const actualSignature = signature ?? value.signature; + return value.version === actualSignature ? + value.affectsGlobalScope ? + { version: value.version, signature: undefined, affectsGlobalScope: true, impliedFormat: value.impliedFormat } : + value.version : + actualSignature !== undefined ? + signature === undefined ? + value : + { version: value.version, signature, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat } : + { version: value.version, signature: false, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat }; + }); + + let referencedMap: ProgramBuildInfoReferencedMap | undefined; + if (state.referencedMap) { + referencedMap = arrayFrom(state.referencedMap.keys()).sort(compareStringsCaseSensitive).map(key => [ + toFileId(key), + toFileIdListId(state.referencedMap!.getValues(key)!) + ]); + } - let exportedModulesMap: ProgramBuildInfoReferencedMap | undefined; - if (state.exportedModulesMap) { - exportedModulesMap = mapDefined(arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive), key => { - if (state.currentAffectedFilesExportedModulesMap) { - if (state.currentAffectedFilesExportedModulesMap.deletedKeys()?.has(key)) { - return undefined; - } + let exportedModulesMap: ProgramBuildInfoReferencedMap | undefined; + if (state.exportedModulesMap) { + exportedModulesMap = mapDefined(arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive), key => { + if (state.currentAffectedFilesExportedModulesMap) { + if (state.currentAffectedFilesExportedModulesMap.deletedKeys()?.has(key)) { + return undefined; + } - const newValue = state.currentAffectedFilesExportedModulesMap.getValues(key); - if (newValue) { - return [toFileId(key), toFileIdListId(newValue)]; - } + const newValue = state.currentAffectedFilesExportedModulesMap.getValues(key); + if (newValue) { + return [toFileId(key), toFileIdListId(newValue)]; } + } - // Not in temporary cache, use existing value - return [toFileId(key), toFileIdListId(state.exportedModulesMap!.getValues(key)!)]; - }); - } + // Not in temporary cache, use existing value + return [toFileId(key), toFileIdListId(state.exportedModulesMap!.getValues(key)!)]; + }); + } - let semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] | undefined; - if (state.semanticDiagnosticsPerFile) { - for (const key of arrayFrom(state.semanticDiagnosticsPerFile.keys()).sort(compareStringsCaseSensitive)) { - const value = state.semanticDiagnosticsPerFile.get(key)!; - (semanticDiagnosticsPerFile ||= []).push( - value.length ? - [ - toFileId(key), - state.hasReusableDiagnostic ? - value as readonly ReusableDiagnostic[] : - convertToReusableDiagnostics(value as readonly Diagnostic[], relativeToBuildInfo) - ] : - toFileId(key) - ); - } + let semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] | undefined; + if (state.semanticDiagnosticsPerFile) { + for (const key of arrayFrom(state.semanticDiagnosticsPerFile.keys()).sort(compareStringsCaseSensitive)) { + const value = state.semanticDiagnosticsPerFile.get(key)!; + (semanticDiagnosticsPerFile ||= []).push( + value.length ? + [ + toFileId(key), + state.hasReusableDiagnostic ? + value as readonly ReusableDiagnostic[] : + convertToReusableDiagnostics(value as readonly Diagnostic[], relativeToBuildInfo) + ] : + toFileId(key) + ); } + } - let affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] | undefined; - if (state.affectedFilesPendingEmit) { - const seenFiles = new Set(); - for (const path of state.affectedFilesPendingEmit.slice(state.affectedFilesPendingEmitIndex).sort(compareStringsCaseSensitive)) { - if (tryAddToSet(seenFiles, path)) { - (affectedFilesPendingEmit ||= []).push([toFileId(path), state.affectedFilesPendingEmitKind!.get(path)!]); - } + let affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] | undefined; + if (state.affectedFilesPendingEmit) { + const seenFiles = new Set(); + for (const path of state.affectedFilesPendingEmit.slice(state.affectedFilesPendingEmitIndex).sort(compareStringsCaseSensitive)) { + if (tryAddToSet(seenFiles, path)) { + (affectedFilesPendingEmit ||= []).push([toFileId(path), state.affectedFilesPendingEmitKind!.get(path)!]); } } + } - return { - fileNames, - fileInfos, - options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions, relativeToBuildInfoEnsuringAbsolutePath), - fileIdsList, - referencedMap, - exportedModulesMap, - semanticDiagnosticsPerFile, - affectedFilesPendingEmit, - }; - - function relativeToBuildInfoEnsuringAbsolutePath(path: string) { - return relativeToBuildInfo(getNormalizedAbsolutePath(path, currentDirectory)); - } + return { + fileNames, + fileInfos, + options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions, relativeToBuildInfoEnsuringAbsolutePath), + fileIdsList, + referencedMap, + exportedModulesMap, + semanticDiagnosticsPerFile, + affectedFilesPendingEmit, + }; - function relativeToBuildInfo(path: string) { - return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, getCanonicalFileName)); - } + function relativeToBuildInfoEnsuringAbsolutePath(path: string) { + return relativeToBuildInfo(getNormalizedAbsolutePath(path, currentDirectory)); + } - function toFileId(path: Path): ProgramBuildInfoFileId { - let fileId = fileNameToFileId.get(path); - if (fileId === undefined) { - fileNames.push(relativeToBuildInfo(path)); - fileNameToFileId.set(path, fileId = fileNames.length as ProgramBuildInfoFileId); - } - return fileId; + function relativeToBuildInfo(path: string) { + return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, getCanonicalFileName)); + } + + function toFileId(path: Path): ProgramBuildInfoFileId { + let fileId = fileNameToFileId.get(path); + if (fileId === undefined) { + fileNames.push(relativeToBuildInfo(path)); + fileNameToFileId.set(path, fileId = fileNames.length as ProgramBuildInfoFileId); } + return fileId; + } - function toFileIdListId(set: ReadonlySet): ProgramBuildInfoFileIdListId { - const fileIds = arrayFrom(set.keys(), toFileId).sort(compareValues); - const key = fileIds.join(); - let fileIdListId = fileNamesToFileIdListId?.get(key); - if (fileIdListId === undefined) { - (fileIdsList ||= []).push(fileIds); - (fileNamesToFileIdListId ||= new Map()).set(key, fileIdListId = fileIdsList.length as ProgramBuildInfoFileIdListId); - } - return fileIdListId; + function toFileIdListId(set: ReadonlySet): ProgramBuildInfoFileIdListId { + const fileIds = arrayFrom(set.keys(), toFileId).sort(compareValues); + const key = fileIds.join(); + let fileIdListId = fileNamesToFileIdListId?.get(key); + if (fileIdListId === undefined) { + (fileIdsList ||= []).push(fileIds); + (fileNamesToFileIdListId ||= new Map()).set(key, fileIdListId = fileIdsList.length as ProgramBuildInfoFileIdListId); } + return fileIdListId; } +} - function convertToProgramBuildInfoCompilerOptions(options: CompilerOptions, relativeToBuildInfo: (path: string) => string) { - let result: CompilerOptions | undefined; - const { optionsNameMap } = getOptionsNameMap(); - - for (const name of getOwnKeys(options).sort(compareStringsCaseSensitive)) { - const optionKey = name.toLowerCase(); - const optionInfo = optionsNameMap.get(optionKey); - if (optionInfo?.affectsEmit || optionInfo?.affectsSemanticDiagnostics || - // We need to store `strict`, even though it won't be examined directly, so that the - // flags it controls (e.g. `strictNullChecks`) will be retrieved correctly from the buildinfo - optionKey === "strict" || - // We need to store these to determine whether `lib` files need to be rechecked. - optionKey === "skiplibcheck" || optionKey === "skipdefaultlibcheck") { - (result ||= {})[name] = convertToReusableCompilerOptionValue( - optionInfo, - options[name] as CompilerOptionsValue, - relativeToBuildInfo - ); - } +function convertToProgramBuildInfoCompilerOptions(options: CompilerOptions, relativeToBuildInfo: (path: string) => string) { + let result: CompilerOptions | undefined; + const { optionsNameMap } = getOptionsNameMap(); + + for (const name of getOwnKeys(options).sort(compareStringsCaseSensitive)) { + const optionKey = name.toLowerCase(); + const optionInfo = optionsNameMap.get(optionKey); + if (optionInfo?.affectsEmit || optionInfo?.affectsSemanticDiagnostics || + // We need to store `strict`, even though it won't be examined directly, so that the + // flags it controls (e.g. `strictNullChecks`) will be retrieved correctly from the buildinfo + optionKey === "strict" || + // We need to store these to determine whether `lib` files need to be rechecked. + optionKey === "skiplibcheck" || optionKey === "skipdefaultlibcheck") { + (result ||= {})[name] = convertToReusableCompilerOptionValue( + optionInfo, + options[name] as CompilerOptionsValue, + relativeToBuildInfo + ); } - return result; } + return result; +} - function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) { - if (option) { - if (option.type === "list") { - const values = value as readonly (string | number)[]; - if (option.element.isFilePath && values.length) { - return values.map(relativeToBuildInfo); - } - } - else if (option.isFilePath) { - return relativeToBuildInfo(value as string); +function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) { + if (option) { + if (option.type === "list") { + const values = value as readonly (string | number)[]; + if (option.element.isFilePath && values.length) { + return values.map(relativeToBuildInfo); } } - return value; + else if (option.isFilePath) { + return relativeToBuildInfo(value as string); + } } + return value; +} - function convertToReusableDiagnostics(diagnostics: readonly Diagnostic[], relativeToBuildInfo: (path: string) => string): readonly ReusableDiagnostic[] { - Debug.assert(!!diagnostics.length); - return diagnostics.map(diagnostic => { - const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo); - result.reportsUnnecessary = diagnostic.reportsUnnecessary; - result.reportDeprecated = diagnostic.reportsDeprecated; - result.source = diagnostic.source; - result.skippedOn = diagnostic.skippedOn; - const { relatedInformation } = diagnostic; - result.relatedInformation = relatedInformation ? - relatedInformation.length ? - relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r, relativeToBuildInfo)) : - [] : - undefined; - return result; - }); - } +function convertToReusableDiagnostics(diagnostics: readonly Diagnostic[], relativeToBuildInfo: (path: string) => string): readonly ReusableDiagnostic[] { + Debug.assert(!!diagnostics.length); + return diagnostics.map(diagnostic => { + const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo); + result.reportsUnnecessary = diagnostic.reportsUnnecessary; + result.reportDeprecated = diagnostic.reportsDeprecated; + result.source = diagnostic.source; + result.skippedOn = diagnostic.skippedOn; + const { relatedInformation } = diagnostic; + result.relatedInformation = relatedInformation ? + relatedInformation.length ? + relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r, relativeToBuildInfo)) : + [] : + undefined; + return result; + }); +} - function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation, relativeToBuildInfo: (path: string) => string): ReusableDiagnosticRelatedInformation { - const { file } = diagnostic; - return { - ...diagnostic, - file: file ? relativeToBuildInfo(file.resolvedPath) : undefined - }; - } +function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation, relativeToBuildInfo: (path: string) => string): ReusableDiagnosticRelatedInformation { + const { file } = diagnostic; + return { + ...diagnostic, + file: file ? relativeToBuildInfo(file.resolvedPath) : undefined + }; +} - export enum BuilderProgramKind { - SemanticDiagnosticsBuilderProgram, - EmitAndSemanticDiagnosticsBuilderProgram - } +export enum BuilderProgramKind { + SemanticDiagnosticsBuilderProgram, + EmitAndSemanticDiagnosticsBuilderProgram +} - export interface BuilderCreationParameters { - newProgram: Program; - host: BuilderProgramHost; - oldProgram: BuilderProgram | undefined; - configFileParsingDiagnostics: readonly Diagnostic[]; - } +export interface BuilderCreationParameters { + newProgram: Program; + host: BuilderProgramHost; + oldProgram: BuilderProgram | undefined; + configFileParsingDiagnostics: readonly Diagnostic[]; +} - export function getBuilderCreationParameters(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: BuilderProgram | CompilerHost, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderCreationParameters { - let host: BuilderProgramHost; - let newProgram: Program; - let oldProgram: BuilderProgram; - if (newProgramOrRootNames === undefined) { - Debug.assert(hostOrOptions === undefined); - host = oldProgramOrHost as CompilerHost; - oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram; - Debug.assert(!!oldProgram); - newProgram = oldProgram.getProgram(); - } - else if (isArray(newProgramOrRootNames)) { - oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram; - newProgram = createProgram({ - rootNames: newProgramOrRootNames, - options: hostOrOptions as CompilerOptions, - host: oldProgramOrHost as CompilerHost, - oldProgram: oldProgram && oldProgram.getProgramOrUndefined(), - configFileParsingDiagnostics, - projectReferences - }); - host = oldProgramOrHost as CompilerHost; - } - else { - newProgram = newProgramOrRootNames; - host = hostOrOptions as BuilderProgramHost; - oldProgram = oldProgramOrHost as BuilderProgram; - configFileParsingDiagnostics = configFileParsingDiagnosticsOrOldProgram as readonly Diagnostic[]; - } - return { host, newProgram, oldProgram, configFileParsingDiagnostics: configFileParsingDiagnostics || emptyArray }; +export function getBuilderCreationParameters(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: BuilderProgram | CompilerHost, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderCreationParameters { + let host: BuilderProgramHost; + let newProgram: Program; + let oldProgram: BuilderProgram; + if (newProgramOrRootNames === undefined) { + Debug.assert(hostOrOptions === undefined); + host = oldProgramOrHost as CompilerHost; + oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram; + Debug.assert(!!oldProgram); + newProgram = oldProgram.getProgram(); } + else if (isArray(newProgramOrRootNames)) { + oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram; + newProgram = createProgram({ + rootNames: newProgramOrRootNames, + options: hostOrOptions as CompilerOptions, + host: oldProgramOrHost as CompilerHost, + oldProgram: oldProgram && oldProgram.getProgramOrUndefined(), + configFileParsingDiagnostics, + projectReferences + }); + host = oldProgramOrHost as CompilerHost; + } + else { + newProgram = newProgramOrRootNames; + host = hostOrOptions as BuilderProgramHost; + oldProgram = oldProgramOrHost as BuilderProgram; + configFileParsingDiagnostics = configFileParsingDiagnosticsOrOldProgram as readonly Diagnostic[]; + } + return { host, newProgram, oldProgram, configFileParsingDiagnostics: configFileParsingDiagnostics || emptyArray }; +} - export function createBuilderProgram(kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): SemanticDiagnosticsBuilderProgram; - export function createBuilderProgram(kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): EmitAndSemanticDiagnosticsBuilderProgram; - export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, host, oldProgram, configFileParsingDiagnostics }: BuilderCreationParameters) { - // Return same program if underlying program doesnt change - let oldState = oldProgram && oldProgram.getState(); - if (oldState && newProgram === oldState.program && configFileParsingDiagnostics === newProgram.getConfigFileParsingDiagnostics()) { - newProgram = undefined!; // TODO: GH#18217 - oldState = undefined; - return oldProgram; - } - - /** - * Create the canonical file name for identity - */ - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - /** - * Computing hash to for signature verification - */ - const computeHash = maybeBind(host, host.createHash); - let state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState, host.disableUseFileVersionAsSignature); - let backupState: BuilderProgramState | undefined; - newProgram.getProgramBuildInfo = () => getProgramBuildInfo(state, getCanonicalFileName); - - // To ensure that we arent storing any references to old program or new program without state +export function createBuilderProgram(kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): SemanticDiagnosticsBuilderProgram; +export function createBuilderProgram(kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): EmitAndSemanticDiagnosticsBuilderProgram; +export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, host, oldProgram, configFileParsingDiagnostics }: BuilderCreationParameters) { + // Return same program if underlying program doesnt change + let oldState = oldProgram && oldProgram.getState(); + if (oldState && newProgram === oldState.program && configFileParsingDiagnostics === newProgram.getConfigFileParsingDiagnostics()) { newProgram = undefined!; // TODO: GH#18217 - oldProgram = undefined; oldState = undefined; + return oldProgram; + } - const getState = () => state; - const builderProgram = createRedirectedBuilderProgram(getState, configFileParsingDiagnostics); - builderProgram.getState = getState; - builderProgram.backupState = () => { - Debug.assert(backupState === undefined); - backupState = cloneBuilderProgramState(state); - }; - builderProgram.restoreState = () => { - state = Debug.checkDefined(backupState); - backupState = undefined; - }; - builderProgram.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.checkDefined(state.program), sourceFile); - builderProgram.getSemanticDiagnostics = getSemanticDiagnostics; - builderProgram.emit = emit; - builderProgram.releaseProgram = () => { - releaseCache(state); - backupState = undefined; - }; - - if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { - (builderProgram as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; - } - else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { - (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; - (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile; - builderProgram.emitBuildInfo = emitBuildInfo; - } - else { - notImplemented(); - } + /** + * Create the canonical file name for identity + */ + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + /** + * Computing hash to for signature verification + */ + const computeHash = maybeBind(host, host.createHash); + let state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState, host.disableUseFileVersionAsSignature); + let backupState: BuilderProgramState | undefined; + newProgram.getProgramBuildInfo = () => getProgramBuildInfo(state, getCanonicalFileName); + + // To ensure that we arent storing any references to old program or new program without state + newProgram = undefined!; // TODO: GH#18217 + oldProgram = undefined; + oldState = undefined; + + const getState = () => state; + const builderProgram = createRedirectedBuilderProgram(getState, configFileParsingDiagnostics); + builderProgram.getState = getState; + builderProgram.backupState = () => { + Debug.assert(backupState === undefined); + backupState = cloneBuilderProgramState(state); + }; + builderProgram.restoreState = () => { + state = Debug.checkDefined(backupState); + backupState = undefined; + }; + builderProgram.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.checkDefined(state.program), sourceFile); + builderProgram.getSemanticDiagnostics = getSemanticDiagnostics; + builderProgram.emit = emit; + builderProgram.releaseProgram = () => { + releaseCache(state); + backupState = undefined; + }; - return builderProgram; + if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { + (builderProgram as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; + } + else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; + (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile; + builderProgram.emitBuildInfo = emitBuildInfo; + } + else { + notImplemented(); + } - function emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult { - if (state.buildInfoEmitPending) { - const result = Debug.checkDefined(state.program).emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken); - state.buildInfoEmitPending = false; - return result; - } - return emitSkippedWithNoDiagnostics; + return builderProgram; + + function emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult { + if (state.buildInfoEmitPending) { + const result = Debug.checkDefined(state.program).emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken); + state.buildInfoEmitPending = false; + return result; } + return emitSkippedWithNoDiagnostics; + } - /** - * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete - * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host - * in that order would be used to write the files - */ - function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult { - let affected = getNextAffectedFile(state, cancellationToken, computeHash); - let emitKind = BuilderFileEmit.Full; - let isPendingEmitFile = false; - if (!affected) { - if (!outFile(state.compilerOptions)) { - const pendingAffectedFile = getNextAffectedFilePendingEmit(state); - if (!pendingAffectedFile) { - if (!state.buildInfoEmitPending) { - return undefined; - } - - const affected = Debug.checkDefined(state.program); - return toAffectedFileEmitResult( - state, - // When whole program is affected, do emit only once (eg when --out or --outFile is specified) - // Otherwise just affected file - affected.emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken), - affected, - /*emitKind*/ BuilderFileEmit.Full, - /*isPendingEmitFile*/ false, - /*isBuildInfoEmit*/ true - ); + /** + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult { + let affected = getNextAffectedFile(state, cancellationToken, computeHash); + let emitKind = BuilderFileEmit.Full; + let isPendingEmitFile = false; + if (!affected) { + if (!outFile(state.compilerOptions)) { + const pendingAffectedFile = getNextAffectedFilePendingEmit(state); + if (!pendingAffectedFile) { + if (!state.buildInfoEmitPending) { + return undefined; } - ({ affectedFile: affected, emitKind } = pendingAffectedFile); - isPendingEmitFile = true; - } - else { - const program = Debug.checkDefined(state.program); - if (state.programEmitComplete) return undefined; - affected = program; - } - } - - return toAffectedFileEmitResult( - state, - // When whole program is affected, do emit only once (eg when --out or --outFile is specified) - // Otherwise just affected file - Debug.checkDefined(state.program).emit( - affected === state.program ? undefined : affected as SourceFile, - writeFile || maybeBind(host, host.writeFile), - cancellationToken, - emitOnlyDtsFiles || emitKind === BuilderFileEmit.DtsOnly, - customTransformers - ), - affected, - emitKind, - isPendingEmitFile, - ); - } - /** - * Emits the JavaScript and declaration files. - * When targetSource file is specified, emits the files corresponding to that source file, - * otherwise for the whole program. - * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, - * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, - * it will only emit all the affected files instead of whole program - * - * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host - * in that order would be used to write the files - */ - function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { - let restorePendingEmitOnHandlingNoEmitSuccess = false; - let savedAffectedFilesPendingEmit; - let savedAffectedFilesPendingEmitKind; - let savedAffectedFilesPendingEmitIndex; - // Backup and restore affected pendings emit state for non emit Builder if noEmitOnError is enabled and emitBuildInfo could be written in case there are errors - // This ensures pending files to emit is updated in tsbuildinfo - // Note that when there are no errors, emit proceeds as if everything is emitted as it is callers reponsibility to write the files to disk if at all (because its builder that doesnt track files to emit) - if (kind !== BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram && - !targetSourceFile && - !outFile(state.compilerOptions) && - !state.compilerOptions.noEmit && - state.compilerOptions.noEmitOnError) { - restorePendingEmitOnHandlingNoEmitSuccess = true; - savedAffectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice(); - savedAffectedFilesPendingEmitKind = state.affectedFilesPendingEmitKind && new Map(state.affectedFilesPendingEmitKind); - savedAffectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex; + const affected = Debug.checkDefined(state.program); + return toAffectedFileEmitResult( + state, + // When whole program is affected, do emit only once (eg when --out or --outFile is specified) + // Otherwise just affected file + affected.emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken), + affected, + /*emitKind*/ BuilderFileEmit.Full, + /*isPendingEmitFile*/ false, + /*isBuildInfoEmit*/ true + ); + } + ({ affectedFile: affected, emitKind } = pendingAffectedFile); + isPendingEmitFile = true; } - - if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { - assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile); + else { + const program = Debug.checkDefined(state.program); + if (state.programEmitComplete) return undefined; + affected = program; } - const result = handleNoEmitOptions(builderProgram, targetSourceFile, writeFile, cancellationToken); - if (result) return result; + } - if (restorePendingEmitOnHandlingNoEmitSuccess) { - state.affectedFilesPendingEmit = savedAffectedFilesPendingEmit; - state.affectedFilesPendingEmitKind = savedAffectedFilesPendingEmitKind; - state.affectedFilesPendingEmitIndex = savedAffectedFilesPendingEmitIndex; - } + return toAffectedFileEmitResult( + state, + // When whole program is affected, do emit only once (eg when --out or --outFile is specified) + // Otherwise just affected file + Debug.checkDefined(state.program).emit( + affected === state.program ? undefined : affected as SourceFile, + writeFile || maybeBind(host, host.writeFile), + cancellationToken, + emitOnlyDtsFiles || emitKind === BuilderFileEmit.DtsOnly, + customTransformers + ), + affected, + emitKind, + isPendingEmitFile, + ); + } - // Emit only affected files if using builder for emit - if (!targetSourceFile && kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { - // Emit and report any errors we ran into. - let sourceMaps: SourceMapEmitResult[] = []; - let emitSkipped = false; - let diagnostics: Diagnostic[] | undefined; - let emittedFiles: string[] = []; - - let affectedEmitResult: AffectedFileResult; - while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) { - emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; - diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); - emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); - sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); - } - return { - emitSkipped, - diagnostics: diagnostics || emptyArray, - emittedFiles, - sourceMaps - }; - } - return Debug.checkDefined(state.program).emit(targetSourceFile, writeFile || maybeBind(host, host.writeFile), cancellationToken, emitOnlyDtsFiles, customTransformers); + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { + let restorePendingEmitOnHandlingNoEmitSuccess = false; + let savedAffectedFilesPendingEmit; + let savedAffectedFilesPendingEmitKind; + let savedAffectedFilesPendingEmitIndex; + // Backup and restore affected pendings emit state for non emit Builder if noEmitOnError is enabled and emitBuildInfo could be written in case there are errors + // This ensures pending files to emit is updated in tsbuildinfo + // Note that when there are no errors, emit proceeds as if everything is emitted as it is callers reponsibility to write the files to disk if at all (because its builder that doesnt track files to emit) + if (kind !== BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram && + !targetSourceFile && + !outFile(state.compilerOptions) && + !state.compilerOptions.noEmit && + state.compilerOptions.noEmitOnError) { + restorePendingEmitOnHandlingNoEmitSuccess = true; + savedAffectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice(); + savedAffectedFilesPendingEmitKind = state.affectedFilesPendingEmitKind && new Map(state.affectedFilesPendingEmitKind); + savedAffectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex; } - /** - * Return the semantic diagnostics for the next affected file or undefined if iteration is complete - * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true - */ - function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult { - while (true) { - const affected = getNextAffectedFile(state, cancellationToken, computeHash); - if (!affected) { - // Done - return undefined; - } - else if (affected === state.program) { - // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) - return toAffectedFileResult( - state, - state.program.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), - affected - ); - } + if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile); + } + const result = handleNoEmitOptions(builderProgram, targetSourceFile, writeFile, cancellationToken); + if (result) return result; - // Add file to affected file pending emit to handle for later emit time - // Apart for emit builder do this for tsbuildinfo, do this for non emit builder when noEmit is set as tsbuildinfo is written and reused between emitters - if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram || state.compilerOptions.noEmit || state.compilerOptions.noEmitOnError) { - addToAffectedFilesPendingEmit(state, (affected as SourceFile).resolvedPath, BuilderFileEmit.Full); - } + if (restorePendingEmitOnHandlingNoEmitSuccess) { + state.affectedFilesPendingEmit = savedAffectedFilesPendingEmit; + state.affectedFilesPendingEmitKind = savedAffectedFilesPendingEmitKind; + state.affectedFilesPendingEmitIndex = savedAffectedFilesPendingEmitIndex; + } - // Get diagnostics for the affected file if its not ignored - if (ignoreSourceFile && ignoreSourceFile(affected as SourceFile)) { - // Get next affected file - doneWithAffectedFile(state, affected); - continue; - } + // Emit only affected files if using builder for emit + if (!targetSourceFile && kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + // Emit and report any errors we ran into. + let sourceMaps: SourceMapEmitResult[] = []; + let emitSkipped = false; + let diagnostics: Diagnostic[] | undefined; + let emittedFiles: string[] = []; + + let affectedEmitResult: AffectedFileResult; + while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) { + emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; + diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); + emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); + sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); + } + return { + emitSkipped, + diagnostics: diagnostics || emptyArray, + emittedFiles, + sourceMaps + }; + } + return Debug.checkDefined(state.program).emit(targetSourceFile, writeFile || maybeBind(host, host.writeFile), cancellationToken, emitOnlyDtsFiles, customTransformers); + } + /** + * Return the semantic diagnostics for the next affected file or undefined if iteration is complete + * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true + */ + function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult { + while (true) { + const affected = getNextAffectedFile(state, cancellationToken, computeHash); + if (!affected) { + // Done + return undefined; + } + else if (affected === state.program) { + // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) return toAffectedFileResult( state, - getSemanticDiagnosticsOfFile(state, affected as SourceFile, cancellationToken), + state.program.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), affected ); } - } - - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, - * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics - */ - function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - assertSourceFileOkWithoutNextAffectedCall(state, sourceFile); - const compilerOptions = Debug.checkDefined(state.program).getCompilerOptions(); - if (outFile(compilerOptions)) { - Debug.assert(!state.semanticDiagnosticsPerFile); - // We dont need to cache the diagnostics just return them from program - return Debug.checkDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken); - } - if (sourceFile) { - return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken); + // Add file to affected file pending emit to handle for later emit time + // Apart for emit builder do this for tsbuildinfo, do this for non emit builder when noEmit is set as tsbuildinfo is written and reused between emitters + if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram || state.compilerOptions.noEmit || state.compilerOptions.noEmitOnError) { + addToAffectedFilesPendingEmit(state, (affected as SourceFile).resolvedPath, BuilderFileEmit.Full); } - // When semantic builder asks for diagnostics of the whole program, - // ensure that all the affected files are handled - // eslint-disable-next-line no-empty - while (getSemanticDiagnosticsOfNextAffectedFile(cancellationToken)) { + // Get diagnostics for the affected file if its not ignored + if (ignoreSourceFile && ignoreSourceFile(affected as SourceFile)) { + // Get next affected file + doneWithAffectedFile(state, affected); + continue; } - let diagnostics: Diagnostic[] | undefined; - for (const sourceFile of Debug.checkDefined(state.program).getSourceFiles()) { - diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken)); - } - return diagnostics || emptyArray; + return toAffectedFileResult( + state, + getSemanticDiagnosticsOfFile(state, affected as SourceFile, cancellationToken), + affected + ); } } - function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilePendingEmit: Path, kind: BuilderFileEmit) { - if (!state.affectedFilesPendingEmit) state.affectedFilesPendingEmit = []; - if (!state.affectedFilesPendingEmitKind) state.affectedFilesPendingEmitKind = new Map(); + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics + */ + function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + assertSourceFileOkWithoutNextAffectedCall(state, sourceFile); + const compilerOptions = Debug.checkDefined(state.program).getCompilerOptions(); + if (outFile(compilerOptions)) { + Debug.assert(!state.semanticDiagnosticsPerFile); + // We dont need to cache the diagnostics just return them from program + return Debug.checkDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken); + } + + if (sourceFile) { + return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken); + } - const existingKind = state.affectedFilesPendingEmitKind.get(affectedFilePendingEmit); - state.affectedFilesPendingEmit.push(affectedFilePendingEmit); - state.affectedFilesPendingEmitKind.set(affectedFilePendingEmit, existingKind || kind); + // When semantic builder asks for diagnostics of the whole program, + // ensure that all the affected files are handled + // eslint-disable-next-line no-empty + while (getSemanticDiagnosticsOfNextAffectedFile(cancellationToken)) { + } - // affectedFilesPendingEmitIndex === undefined - // - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files - // so start from 0 as array would be affectedFilesPendingEmit - // else, continue to iterate from existing index, the current set is appended to existing files - if (state.affectedFilesPendingEmitIndex === undefined) { - state.affectedFilesPendingEmitIndex = 0; + let diagnostics: Diagnostic[] | undefined; + for (const sourceFile of Debug.checkDefined(state.program).getSourceFiles()) { + diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken)); } + return diagnostics || emptyArray; } +} + +function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilePendingEmit: Path, kind: BuilderFileEmit) { + if (!state.affectedFilesPendingEmit) state.affectedFilesPendingEmit = []; + if (!state.affectedFilesPendingEmitKind) state.affectedFilesPendingEmitKind = new Map(); - export function toBuilderStateFileInfo(fileInfo: ProgramBuildInfoFileInfo): BuilderState.FileInfo { - return isString(fileInfo) ? - { version: fileInfo, signature: fileInfo, affectsGlobalScope: undefined, impliedFormat: undefined } : - isString(fileInfo.signature) ? - fileInfo as BuilderState.FileInfo : - { version: fileInfo.version, signature: fileInfo.signature === false ? undefined : fileInfo.version, affectsGlobalScope: fileInfo.affectsGlobalScope, impliedFormat: fileInfo.impliedFormat }; + const existingKind = state.affectedFilesPendingEmitKind.get(affectedFilePendingEmit); + state.affectedFilesPendingEmit.push(affectedFilePendingEmit); + state.affectedFilesPendingEmitKind.set(affectedFilePendingEmit, existingKind || kind); + + // affectedFilesPendingEmitIndex === undefined + // - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files + // so start from 0 as array would be affectedFilesPendingEmit + // else, continue to iterate from existing index, the current set is appended to existing files + if (state.affectedFilesPendingEmitIndex === undefined) { + state.affectedFilesPendingEmitIndex = 0; } +} - export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram { - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - - const filePaths = program.fileNames.map(toPath); - const filePathsSetList = program.fileIdsList?.map(fileIds => new Set(fileIds.map(toFilePath))); - const fileInfos = new Map(); - program.fileInfos.forEach((fileInfo, index) => fileInfos.set(toFilePath(index + 1 as ProgramBuildInfoFileId), toBuilderStateFileInfo(fileInfo))); - const state: ReusableBuilderProgramState = { - fileInfos, - compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {}, - referencedMap: toManyToManyPathMap(program.referencedMap), - exportedModulesMap: toManyToManyPathMap(program.exportedModulesMap), - semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]), - hasReusableDiagnostic: true, - affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toFilePath(value[0])), - affectedFilesPendingEmitKind: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toFilePath(value[0]), value => value[1]), - affectedFilesPendingEmitIndex: program.affectedFilesPendingEmit && 0, - }; - return { - getState: () => state, - backupState: noop, - restoreState: noop, - getProgram: notImplemented, - getProgramOrUndefined: returnUndefined, - releaseProgram: noop, - getCompilerOptions: () => state.compilerOptions, - getSourceFile: notImplemented, - getSourceFiles: notImplemented, - getOptionsDiagnostics: notImplemented, - getGlobalDiagnostics: notImplemented, - getConfigFileParsingDiagnostics: notImplemented, - getSyntacticDiagnostics: notImplemented, - getDeclarationDiagnostics: notImplemented, - getSemanticDiagnostics: notImplemented, - emit: notImplemented, - getAllDependencies: notImplemented, - getCurrentDirectory: notImplemented, - emitNextAffectedFile: notImplemented, - getSemanticDiagnosticsOfNextAffectedFile: notImplemented, - emitBuildInfo: notImplemented, - close: noop, - }; - - function toPath(path: string) { - return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); - } +export function toBuilderStateFileInfo(fileInfo: ProgramBuildInfoFileInfo): BuilderState.FileInfo { + return isString(fileInfo) ? + { version: fileInfo, signature: fileInfo, affectsGlobalScope: undefined, impliedFormat: undefined } : + isString(fileInfo.signature) ? + fileInfo as BuilderState.FileInfo : + { version: fileInfo.version, signature: fileInfo.signature === false ? undefined : fileInfo.version, affectsGlobalScope: fileInfo.affectsGlobalScope, impliedFormat: fileInfo.impliedFormat }; +} - function toAbsolutePath(path: string) { - return getNormalizedAbsolutePath(path, buildInfoDirectory); - } +export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram { + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + + const filePaths = program.fileNames.map(toPath); + const filePathsSetList = program.fileIdsList?.map(fileIds => new Set(fileIds.map(toFilePath))); + const fileInfos = new Map(); + program.fileInfos.forEach((fileInfo, index) => fileInfos.set(toFilePath(index + 1 as ProgramBuildInfoFileId), toBuilderStateFileInfo(fileInfo))); + const state: ReusableBuilderProgramState = { + fileInfos, + compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {}, + referencedMap: toManyToManyPathMap(program.referencedMap), + exportedModulesMap: toManyToManyPathMap(program.exportedModulesMap), + semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]), + hasReusableDiagnostic: true, + affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toFilePath(value[0])), + affectedFilesPendingEmitKind: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toFilePath(value[0]), value => value[1]), + affectedFilesPendingEmitIndex: program.affectedFilesPendingEmit && 0, + }; + return { + getState: () => state, + backupState: noop, + restoreState: noop, + getProgram: notImplemented, + getProgramOrUndefined: returnUndefined, + releaseProgram: noop, + getCompilerOptions: () => state.compilerOptions, + getSourceFile: notImplemented, + getSourceFiles: notImplemented, + getOptionsDiagnostics: notImplemented, + getGlobalDiagnostics: notImplemented, + getConfigFileParsingDiagnostics: notImplemented, + getSyntacticDiagnostics: notImplemented, + getDeclarationDiagnostics: notImplemented, + getSemanticDiagnostics: notImplemented, + emit: notImplemented, + getAllDependencies: notImplemented, + getCurrentDirectory: notImplemented, + emitNextAffectedFile: notImplemented, + getSemanticDiagnosticsOfNextAffectedFile: notImplemented, + emitBuildInfo: notImplemented, + close: noop, + }; - function toFilePath(fileId: ProgramBuildInfoFileId) { - return filePaths[fileId - 1]; - } + function toPath(path: string) { + return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); + } - function toFilePathsSet(fileIdsListId: ProgramBuildInfoFileIdListId) { - return filePathsSetList![fileIdsListId - 1]; - } + function toAbsolutePath(path: string) { + return getNormalizedAbsolutePath(path, buildInfoDirectory); + } - function toManyToManyPathMap(referenceMap: ProgramBuildInfoReferencedMap | undefined): BuilderState.ManyToManyPathMap | undefined { - if (!referenceMap) { - return undefined; - } + function toFilePath(fileId: ProgramBuildInfoFileId) { + return filePaths[fileId - 1]; + } - const map = BuilderState.createManyToManyPathMap(); - referenceMap.forEach(([fileId, fileIdListId]) => - map.set(toFilePath(fileId), toFilePathsSet(fileIdListId)) - ); - return map; - } + function toFilePathsSet(fileIdsListId: ProgramBuildInfoFileIdListId) { + return filePathsSetList![fileIdsListId - 1]; } - export function createRedirectedBuilderProgram(getState: () => { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram { - return { - getState: notImplemented, - backupState: noop, - restoreState: noop, - getProgram, - getProgramOrUndefined: () => getState().program, - releaseProgram: () => getState().program = undefined, - getCompilerOptions: () => getState().compilerOptions, - getSourceFile: fileName => getProgram().getSourceFile(fileName), - getSourceFiles: () => getProgram().getSourceFiles(), - getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken), - getGlobalDiagnostics: cancellationToken => getProgram().getGlobalDiagnostics(cancellationToken), - getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics, - getSyntacticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSyntacticDiagnostics(sourceFile, cancellationToken), - getDeclarationDiagnostics: (sourceFile, cancellationToken) => getProgram().getDeclarationDiagnostics(sourceFile, cancellationToken), - getSemanticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSemanticDiagnostics(sourceFile, cancellationToken), - emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => getProgram().emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers), - emitBuildInfo: (writeFile, cancellationToken) => getProgram().emitBuildInfo(writeFile, cancellationToken), - getAllDependencies: notImplemented, - getCurrentDirectory: () => getProgram().getCurrentDirectory(), - close: noop, - }; - - function getProgram() { - return Debug.checkDefined(getState().program); + function toManyToManyPathMap(referenceMap: ProgramBuildInfoReferencedMap | undefined): BuilderState.ManyToManyPathMap | undefined { + if (!referenceMap) { + return undefined; } + + const map = BuilderState.createManyToManyPathMap(); + referenceMap.forEach(([fileId, fileIdListId]) => + map.set(toFilePath(fileId), toFilePathsSet(fileIdListId)) + ); + return map; } } + +export function createRedirectedBuilderProgram(getState: () => { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram { + return { + getState: notImplemented, + backupState: noop, + restoreState: noop, + getProgram, + getProgramOrUndefined: () => getState().program, + releaseProgram: () => getState().program = undefined, + getCompilerOptions: () => getState().compilerOptions, + getSourceFile: fileName => getProgram().getSourceFile(fileName), + getSourceFiles: () => getProgram().getSourceFiles(), + getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken), + getGlobalDiagnostics: cancellationToken => getProgram().getGlobalDiagnostics(cancellationToken), + getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics, + getSyntacticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSyntacticDiagnostics(sourceFile, cancellationToken), + getDeclarationDiagnostics: (sourceFile, cancellationToken) => getProgram().getDeclarationDiagnostics(sourceFile, cancellationToken), + getSemanticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSemanticDiagnostics(sourceFile, cancellationToken), + emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => getProgram().emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers), + emitBuildInfo: (writeFile, cancellationToken) => getProgram().emitBuildInfo(writeFile, cancellationToken), + getAllDependencies: notImplemented, + getCurrentDirectory: () => getProgram().getCurrentDirectory(), + close: noop, + }; + + function getProgram() { + return Debug.checkDefined(getState().program); + } +} +} diff --git a/src/compiler/builderPublic.ts b/src/compiler/builderPublic.ts index 5d5d2ed25dd52..ac10c25319249 100644 --- a/src/compiler/builderPublic.ts +++ b/src/compiler/builderPublic.ts @@ -1,169 +1,169 @@ namespace ts { - export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; - - export interface BuilderProgramHost { - /** - * return true if file names are treated with case sensitivity - */ - useCaseSensitiveFileNames(): boolean; - /** - * If provided this would be used this hash instead of actual file shape text for detecting changes - */ - createHash?: (data: string) => string; - /** - * When emit or emitNextAffectedFile are called without writeFile, - * this callback if present would be used to write files - */ - writeFile?: WriteFileCallback; - /** - * disable using source file version as signature for testing - */ - /*@internal*/ - disableUseFileVersionAsSignature?: boolean; - } +export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; +export interface BuilderProgramHost { /** - * Builder to manage the program state changes - */ - export interface BuilderProgram { - /*@internal*/ - getState(): ReusableBuilderProgramState; - /*@internal*/ - backupState(): void; - /*@internal*/ - restoreState(): void; - /** - * Returns current program - */ - getProgram(): Program; - /** - * Returns current program that could be undefined if the program was released - */ - /*@internal*/ - getProgramOrUndefined(): Program | undefined; - /** - * Releases reference to the program, making all the other operations that need program to fail. - */ - /*@internal*/ - releaseProgram(): void; - /** - * Get compiler options of the program - */ - getCompilerOptions(): CompilerOptions; - /** - * Get the source file in the program with file name - */ - getSourceFile(fileName: string): SourceFile | undefined; - /** - * Get a list of files in the program - */ - getSourceFiles(): readonly SourceFile[]; - /** - * Get the diagnostics for compiler options - */ - getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Get the diagnostics that dont belong to any file - */ - getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Get the diagnostics from config file parsing - */ - getConfigFileParsingDiagnostics(): readonly Diagnostic[]; - /** - * Get the syntax diagnostics, for all source files if source file is not supplied - */ - getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Get the declaration diagnostics, for all source files if source file is not supplied - */ - getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[]; - /** - * Get all the dependencies of the file - */ - getAllDependencies(sourceFile: SourceFile): readonly string[]; - - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, - * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics - */ - getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Emits the JavaScript and declaration files. - * When targetSource file is specified, emits the files corresponding to that source file, - * otherwise for the whole program. - * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, - * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, - * it will only emit all the affected files instead of whole program - * - * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host - * in that order would be used to write the files - */ - emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; - /*@internal*/ - emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult; - /** - * Get the current directory of the program - */ - getCurrentDirectory(): string; - /*@internal*/ - close(): void; - } - + * return true if file names are treated with case sensitivity + */ + useCaseSensitiveFileNames(): boolean; + /** + * If provided this would be used this hash instead of actual file shape text for detecting changes + */ + createHash?: (data: string) => string; /** - * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files + * When emit or emitNextAffectedFile are called without writeFile, + * this callback if present would be used to write files */ - export interface SemanticDiagnosticsBuilderProgram extends BuilderProgram { - /** - * Gets the semantic diagnostics from the program for the next affected file and caches it - * Returns undefined if the iteration is complete - */ - getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult; - } + writeFile?: WriteFileCallback; + /** + * disable using source file version as signature for testing + */ + /*@internal*/ + disableUseFileVersionAsSignature?: boolean; +} +/** + * Builder to manage the program state changes + */ +export interface BuilderProgram { + /*@internal*/ + getState(): ReusableBuilderProgramState; + /*@internal*/ + backupState(): void; + /*@internal*/ + restoreState(): void; + /** + * Returns current program + */ + getProgram(): Program; + /** + * Returns current program that could be undefined if the program was released + */ + /*@internal*/ + getProgramOrUndefined(): Program | undefined; + /** + * Releases reference to the program, making all the other operations that need program to fail. + */ + /*@internal*/ + releaseProgram(): void; + /** + * Get compiler options of the program + */ + getCompilerOptions(): CompilerOptions; + /** + * Get the source file in the program with file name + */ + getSourceFile(fileName: string): SourceFile | undefined; + /** + * Get a list of files in the program + */ + getSourceFiles(): readonly SourceFile[]; + /** + * Get the diagnostics for compiler options + */ + getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; /** - * The builder that can handle the changes in program and iterate through changed file to emit the files - * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files - */ - export interface EmitAndSemanticDiagnosticsBuilderProgram extends SemanticDiagnosticsBuilderProgram { - /** - * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete - * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host - * in that order would be used to write the files - */ - emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult; - } + * Get the diagnostics that dont belong to any file + */ + getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Get the diagnostics from config file parsing + */ + getConfigFileParsingDiagnostics(): readonly Diagnostic[]; + /** + * Get the syntax diagnostics, for all source files if source file is not supplied + */ + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Get the declaration diagnostics, for all source files if source file is not supplied + */ + getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[]; + /** + * Get all the dependencies of the file + */ + getAllDependencies(sourceFile: SourceFile): readonly string[]; /** - * Create the builder to manage semantic diagnostics and cache them + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics + */ + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files */ - export function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): SemanticDiagnosticsBuilderProgram; - export function createSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): SemanticDiagnosticsBuilderProgram; - export function createSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { - return createBuilderProgram(BuilderProgramKind.SemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); - } + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; + /*@internal*/ + emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult; + /** + * Get the current directory of the program + */ + getCurrentDirectory(): string; + /*@internal*/ + close(): void; +} +/** + * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files + */ +export interface SemanticDiagnosticsBuilderProgram extends BuilderProgram { /** - * Create the builder that can handle the changes in program and iterate through changed files - * to emit the those files and manage semantic diagnostics cache as well + * Gets the semantic diagnostics from the program for the next affected file and caches it + * Returns undefined if the iteration is complete */ - export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): EmitAndSemanticDiagnosticsBuilderProgram; - export function createEmitAndSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): EmitAndSemanticDiagnosticsBuilderProgram; - export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { - return createBuilderProgram(BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); - } + getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult; +} +/** + * The builder that can handle the changes in program and iterate through changed file to emit the files + * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files + */ +export interface EmitAndSemanticDiagnosticsBuilderProgram extends SemanticDiagnosticsBuilderProgram { /** - * Creates a builder thats just abstraction over program and can be used with watch + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files */ - export function createAbstractBuilder(newProgram: Program, host: BuilderProgramHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): BuilderProgram; - export function createAbstractBuilder(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram; - export function createAbstractBuilder(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram { - const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences); - return createRedirectedBuilderProgram(() => ({ program: newProgram, compilerOptions: newProgram.getCompilerOptions() }), newConfigFileParsingDiagnostics); - } + emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult; +} + +/** + * Create the builder to manage semantic diagnostics and cache them + */ +export function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): SemanticDiagnosticsBuilderProgram; +export function createSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): SemanticDiagnosticsBuilderProgram; +export function createSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { + return createBuilderProgram(BuilderProgramKind.SemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); +} + +/** + * Create the builder that can handle the changes in program and iterate through changed files + * to emit the those files and manage semantic diagnostics cache as well + */ +export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): EmitAndSemanticDiagnosticsBuilderProgram; +export function createEmitAndSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): EmitAndSemanticDiagnosticsBuilderProgram; +export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { + return createBuilderProgram(BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); +} + +/** + * Creates a builder thats just abstraction over program and can be used with watch + */ +export function createAbstractBuilder(newProgram: Program, host: BuilderProgramHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): BuilderProgram; +export function createAbstractBuilder(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram; +export function createAbstractBuilder(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram { + const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences); + return createRedirectedBuilderProgram(() => ({ program: newProgram, compilerOptions: newProgram.getCompilerOptions() }), newConfigFileParsingDiagnostics); +} } diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index a64a6c13ed1e8..617a5dde8530c 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -1,678 +1,678 @@ /*@internal*/ namespace ts { - export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, - cancellationToken?: CancellationToken, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitOutput { - const outputFiles: OutputFile[] = []; - const { emitSkipped, diagnostics, exportedModulesFromDeclarationEmit } = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, forceDtsEmit); - return { outputFiles, emitSkipped, diagnostics, exportedModulesFromDeclarationEmit }; - - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { - outputFiles.push({ name: fileName, writeByteOrderMark, text }); - } +export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, + cancellationToken?: CancellationToken, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitOutput { + const outputFiles: OutputFile[] = []; + const { emitSkipped, diagnostics, exportedModulesFromDeclarationEmit } = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, forceDtsEmit); + return { outputFiles, emitSkipped, diagnostics, exportedModulesFromDeclarationEmit }; + + function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { + outputFiles.push({ name: fileName, writeByteOrderMark, text }); } +} - export interface ReusableBuilderState { - /** - * Information of the file eg. its version, signature etc - */ - fileInfos: ReadonlyESMap; - /** - * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled - * Otherwise undefined - * Thus non undefined value indicates, module emit - */ - readonly referencedMap?: BuilderState.ReadonlyManyToManyPathMap | undefined; - /** - * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled - * Otherwise undefined - */ - readonly exportedModulesMap?: BuilderState.ReadonlyManyToManyPathMap | undefined; - } +export interface ReusableBuilderState { + /** + * Information of the file eg. its version, signature etc + */ + fileInfos: ReadonlyESMap; + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + * Thus non undefined value indicates, module emit + */ + readonly referencedMap?: BuilderState.ReadonlyManyToManyPathMap | undefined; + /** + * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled + * Otherwise undefined + */ + readonly exportedModulesMap?: BuilderState.ReadonlyManyToManyPathMap | undefined; +} - export interface BuilderState { - /** - * Information of the file eg. its version, signature etc - */ - fileInfos: ESMap; - /** - * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled - * Otherwise undefined - * Thus non undefined value indicates, module emit - */ - readonly referencedMap: BuilderState.ReadonlyManyToManyPathMap | undefined; - /** - * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled - * Otherwise undefined - * - * This is equivalent to referencedMap, but for the emitted .d.ts file. - */ - readonly exportedModulesMap: BuilderState.ManyToManyPathMap | undefined; +export interface BuilderState { + /** + * Information of the file eg. its version, signature etc + */ + fileInfos: ESMap; + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + * Thus non undefined value indicates, module emit + */ + readonly referencedMap: BuilderState.ReadonlyManyToManyPathMap | undefined; + /** + * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled + * Otherwise undefined + * + * This is equivalent to referencedMap, but for the emitted .d.ts file. + */ + readonly exportedModulesMap: BuilderState.ManyToManyPathMap | undefined; + + previousCache?: { + id: number, + version: number, + }; + + /** + * true if file version is used as signature + * This helps in delaying the calculation of the d.ts hash as version for the file till reasonable time + */ + useFileVersionAsSignature: boolean; + /** + * Map of files that have already called update signature. + * That means hence forth these files are assumed to have + * no change in their signature for this version of the program + */ + hasCalledUpdateShapeSignature: Set; + /** + * Cache of all files excluding default library file for the current program + */ + allFilesExcludingDefaultLibraryFile?: readonly SourceFile[]; + /** + * Cache of all the file names + */ + allFileNames?: readonly string[]; +} - previousCache?: { - id: number, - version: number, - }; +export namespace BuilderState { + /** + * Information about the source file: Its version and optional signature from last emit + */ + export interface FileInfo { + readonly version: string; + signature: string | undefined; + affectsGlobalScope: boolean | undefined; + impliedFormat: number | undefined; + } + + export interface ReadonlyManyToManyPathMap { + readonly id: number; + clone(): ManyToManyPathMap; + forEach(action: (v: ReadonlySet, k: Path) => void): void; + getKeys(v: Path): ReadonlySet | undefined; + getValues(k: Path): ReadonlySet | undefined; + hasKey(k: Path): boolean; + keys(): Iterator; /** - * true if file version is used as signature - * This helps in delaying the calculation of the d.ts hash as version for the file till reasonable time - */ - useFileVersionAsSignature: boolean; - /** - * Map of files that have already called update signature. - * That means hence forth these files are assumed to have - * no change in their signature for this version of the program - */ - hasCalledUpdateShapeSignature: Set; - /** - * Cache of all files excluding default library file for the current program - */ - allFilesExcludingDefaultLibraryFile?: readonly SourceFile[]; - /** - * Cache of all the file names + * The set of arguments to {@link deleteKeys} which have not subsequently + * been arguments to {@link set}. Note that a key does not have to have + * ever been in the map to appear in this set. */ - allFileNames?: readonly string[]; + deletedKeys(): ReadonlySet | undefined; } - export namespace BuilderState { - /** - * Information about the source file: Its version and optional signature from last emit - */ - export interface FileInfo { - readonly version: string; - signature: string | undefined; - affectsGlobalScope: boolean | undefined; - impliedFormat: number | undefined; - } - - export interface ReadonlyManyToManyPathMap { - readonly id: number; - clone(): ManyToManyPathMap; - forEach(action: (v: ReadonlySet, k: Path) => void): void; - getKeys(v: Path): ReadonlySet | undefined; - getValues(k: Path): ReadonlySet | undefined; - hasKey(k: Path): boolean; - keys(): Iterator; - - /** - * The set of arguments to {@link deleteKeys} which have not subsequently - * been arguments to {@link set}. Note that a key does not have to have - * ever been in the map to appear in this set. - */ - deletedKeys(): ReadonlySet | undefined; - } - - export interface ManyToManyPathMap extends ReadonlyManyToManyPathMap { - version(): number; // Incremented each time the contents are changed - deleteKey(k: Path): boolean; - set(k: Path, v: ReadonlySet): void; - } - - let manyToManyPathMapCount = 0; - export function createManyToManyPathMap(): ManyToManyPathMap { - function create(forward: ESMap>, reverse: ESMap>, deleted: Set | undefined): ManyToManyPathMap { - let version = 0; - const map: ManyToManyPathMap = { - id: manyToManyPathMapCount++, - version: () => version, - clone: () => create(new Map(forward), new Map(reverse), deleted && new Set(deleted)), - forEach: fn => forward.forEach(fn), - getKeys: v => reverse.get(v), - getValues: k => forward.get(k), - hasKey: k => forward.has(k), - keys: () => forward.keys(), - - deletedKeys: () => deleted, - deleteKey: k => { - (deleted ||= new Set()).add(k); - - const set = forward.get(k); - if (!set) { - return false; + export interface ManyToManyPathMap extends ReadonlyManyToManyPathMap { + version(): number; // Incremented each time the contents are changed + deleteKey(k: Path): boolean; + set(k: Path, v: ReadonlySet): void; + } + + let manyToManyPathMapCount = 0; + export function createManyToManyPathMap(): ManyToManyPathMap { + function create(forward: ESMap>, reverse: ESMap>, deleted: Set | undefined): ManyToManyPathMap { + let version = 0; + const map: ManyToManyPathMap = { + id: manyToManyPathMapCount++, + version: () => version, + clone: () => create(new Map(forward), new Map(reverse), deleted && new Set(deleted)), + forEach: fn => forward.forEach(fn), + getKeys: v => reverse.get(v), + getValues: k => forward.get(k), + hasKey: k => forward.has(k), + keys: () => forward.keys(), + + deletedKeys: () => deleted, + deleteKey: k => { + (deleted ||= new Set()).add(k); + + const set = forward.get(k); + if (!set) { + return false; + } + + set.forEach(v => deleteFromMultimap(reverse, v, k)); + forward.delete(k); + version++; + return true; + }, + set: (k, vSet) => { + let changed = !!deleted?.delete(k); + + const existingVSet = forward.get(k); + forward.set(k, vSet); + + existingVSet?.forEach(v => { + if (!vSet.has(v)) { + changed = true; + deleteFromMultimap(reverse, v, k); } + }); - set.forEach(v => deleteFromMultimap(reverse, v, k)); - forward.delete(k); - version++; - return true; - }, - set: (k, vSet) => { - let changed = !!deleted?.delete(k); - - const existingVSet = forward.get(k); - forward.set(k, vSet); - - existingVSet?.forEach(v => { - if (!vSet.has(v)) { - changed = true; - deleteFromMultimap(reverse, v, k); - } - }); - - vSet.forEach(v => { - if (!existingVSet?.has(v)) { - changed = true; - addToMultimap(reverse, v, k); - } - }); - - if (changed) { - version++; + vSet.forEach(v => { + if (!existingVSet?.has(v)) { + changed = true; + addToMultimap(reverse, v, k); } + }); - return map; - }, - }; + if (changed) { + version++; + } - return map; - } + return map; + }, + }; - return create(new Map>(), new Map>(), /*deleted*/ undefined); + return map; } - function addToMultimap(map: ESMap>, k: K, v: V): void { - let set = map.get(k); - if (!set) { - set = new Set(); - map.set(k, set); - } - set.add(v); + return create(new Map>(), new Map>(), /*deleted*/ undefined); + } + + function addToMultimap(map: ESMap>, k: K, v: V): void { + let set = map.get(k); + if (!set) { + set = new Set(); + map.set(k, set); } + set.add(v); + } - function deleteFromMultimap(map: ESMap>, k: K, v: V, removeEmpty = true): boolean { - const set = map.get(k); + function deleteFromMultimap(map: ESMap>, k: K, v: V, removeEmpty = true): boolean { + const set = map.get(k); - if (set?.delete(v)) { - if (removeEmpty && !set.size) { - map.delete(k); - } - return true; + if (set?.delete(v)) { + if (removeEmpty && !set.size) { + map.delete(k); } - - return false; + return true; } - /** - * Compute the hash to store the shape of the file - */ - export type ComputeHash = ((data: string) => string) | undefined; + return false; + } - function getReferencedFilesFromImportedModuleSymbol(symbol: Symbol): Path[] { - return mapDefined(symbol.declarations, declaration => getSourceFileOfNode(declaration)?.resolvedPath); - } + /** + * Compute the hash to store the shape of the file + */ + export type ComputeHash = ((data: string) => string) | undefined; - /** - * Get the module source file and all augmenting files from the import name node from file - */ - function getReferencedFilesFromImportLiteral(checker: TypeChecker, importName: StringLiteralLike): Path[] | undefined { - const symbol = checker.getSymbolAtLocation(importName); - return symbol && getReferencedFilesFromImportedModuleSymbol(symbol); - } + function getReferencedFilesFromImportedModuleSymbol(symbol: Symbol): Path[] { + return mapDefined(symbol.declarations, declaration => getSourceFileOfNode(declaration)?.resolvedPath); + } - /** - * Gets the path to reference file from file name, it could be resolvedPath if present otherwise path - */ - function getReferencedFileFromFileName(program: Program, fileName: string, sourceFileDirectory: Path, getCanonicalFileName: GetCanonicalFileName): Path { - return toPath(program.getProjectReferenceRedirect(fileName) || fileName, sourceFileDirectory, getCanonicalFileName); - } + /** + * Get the module source file and all augmenting files from the import name node from file + */ + function getReferencedFilesFromImportLiteral(checker: TypeChecker, importName: StringLiteralLike): Path[] | undefined { + const symbol = checker.getSymbolAtLocation(importName); + return symbol && getReferencedFilesFromImportedModuleSymbol(symbol); + } - /** - * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true - */ - function getReferencedFiles(program: Program, sourceFile: SourceFile, getCanonicalFileName: GetCanonicalFileName): Set | undefined { - let referencedFiles: Set | undefined; - - // We need to use a set here since the code can contain the same import twice, - // but that will only be one dependency. - // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - if (sourceFile.imports && sourceFile.imports.length > 0) { - const checker: TypeChecker = program.getTypeChecker(); - for (const importName of sourceFile.imports) { - const declarationSourceFilePaths = getReferencedFilesFromImportLiteral(checker, importName); - declarationSourceFilePaths?.forEach(addReferencedFile); - } - } + /** + * Gets the path to reference file from file name, it could be resolvedPath if present otherwise path + */ + function getReferencedFileFromFileName(program: Program, fileName: string, sourceFileDirectory: Path, getCanonicalFileName: GetCanonicalFileName): Path { + return toPath(program.getProjectReferenceRedirect(fileName) || fileName, sourceFileDirectory, getCanonicalFileName); + } - const sourceFileDirectory = getDirectoryPath(sourceFile.resolvedPath); - // Handle triple slash references - if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { - for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = getReferencedFileFromFileName(program, referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(referencedPath); - } + /** + * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true + */ + function getReferencedFiles(program: Program, sourceFile: SourceFile, getCanonicalFileName: GetCanonicalFileName): Set | undefined { + let referencedFiles: Set | undefined; + + // We need to use a set here since the code can contain the same import twice, + // but that will only be one dependency. + // To avoid invernal conversion, the key of the referencedFiles map must be of type Path + if (sourceFile.imports && sourceFile.imports.length > 0) { + const checker: TypeChecker = program.getTypeChecker(); + for (const importName of sourceFile.imports) { + const declarationSourceFilePaths = getReferencedFilesFromImportLiteral(checker, importName); + declarationSourceFilePaths?.forEach(addReferencedFile); } + } - // Handle type reference directives - if (sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { - if (!resolvedTypeReferenceDirective) { - return; - } - - const fileName = resolvedTypeReferenceDirective.resolvedFileName!; // TODO: GH#18217 - const typeFilePath = getReferencedFileFromFileName(program, fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(typeFilePath); - }); + const sourceFileDirectory = getDirectoryPath(sourceFile.resolvedPath); + // Handle triple slash references + if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { + for (const referencedFile of sourceFile.referencedFiles) { + const referencedPath = getReferencedFileFromFileName(program, referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(referencedPath); } + } - // Add module augmentation as references - if (sourceFile.moduleAugmentations.length) { - const checker = program.getTypeChecker(); - for (const moduleName of sourceFile.moduleAugmentations) { - if (!isStringLiteral(moduleName)) continue; - const symbol = checker.getSymbolAtLocation(moduleName); - if (!symbol) continue; - - // Add any file other than our own as reference - addReferenceFromAmbientModule(symbol); + // Handle type reference directives + if (sourceFile.resolvedTypeReferenceDirectiveNames) { + sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { + if (!resolvedTypeReferenceDirective) { + return; } - } - // From ambient modules - for (const ambientModule of program.getTypeChecker().getAmbientModules()) { - if (ambientModule.declarations && ambientModule.declarations.length > 1) { - addReferenceFromAmbientModule(ambientModule); - } - } + const fileName = resolvedTypeReferenceDirective.resolvedFileName!; // TODO: GH#18217 + const typeFilePath = getReferencedFileFromFileName(program, fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(typeFilePath); + }); + } - return referencedFiles; + // Add module augmentation as references + if (sourceFile.moduleAugmentations.length) { + const checker = program.getTypeChecker(); + for (const moduleName of sourceFile.moduleAugmentations) { + if (!isStringLiteral(moduleName)) continue; + const symbol = checker.getSymbolAtLocation(moduleName); + if (!symbol) continue; - function addReferenceFromAmbientModule(symbol: Symbol) { - if (!symbol.declarations) { - return; - } // Add any file other than our own as reference - for (const declaration of symbol.declarations) { - const declarationSourceFile = getSourceFileOfNode(declaration); - if (declarationSourceFile && - declarationSourceFile !== sourceFile) { - addReferencedFile(declarationSourceFile.resolvedPath); - } - } + addReferenceFromAmbientModule(symbol); } + } - function addReferencedFile(referencedPath: Path) { - (referencedFiles || (referencedFiles = new Set())).add(referencedPath); + // From ambient modules + for (const ambientModule of program.getTypeChecker().getAmbientModules()) { + if (ambientModule.declarations && ambientModule.declarations.length > 1) { + addReferenceFromAmbientModule(ambientModule); } } - /** - * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed - */ - export function canReuseOldState(newReferencedMap: ReadonlyManyToManyPathMap | undefined, oldState: Readonly | undefined) { - return oldState && !oldState.referencedMap === !newReferencedMap; - } + return referencedFiles; - /** - * Creates the state of file references and signature for the new program from oldState if it is safe - */ - export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly, disableUseFileVersionAsSignature?: boolean): BuilderState { - const fileInfos = new Map(); - const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createManyToManyPathMap() : undefined; - const exportedModulesMap = referencedMap ? createManyToManyPathMap() : undefined; - const hasCalledUpdateShapeSignature = new Set(); - const useOldState = canReuseOldState(referencedMap, oldState); - - // Ensure source files have parent pointers set - newProgram.getTypeChecker(); - - // Create the reference map, and set the file infos - for (const sourceFile of newProgram.getSourceFiles()) { - const version = Debug.checkDefined(sourceFile.version, "Program intended to be used with Builder should have source files with versions set"); - const oldInfo = useOldState ? oldState!.fileInfos.get(sourceFile.resolvedPath) : undefined; - if (referencedMap) { - const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); - if (newReferences) { - referencedMap.set(sourceFile.resolvedPath, newReferences); - } - // Copy old visible to outside files map - if (useOldState) { - const exportedModules = oldState!.exportedModulesMap!.getValues(sourceFile.resolvedPath); - if (exportedModules) { - exportedModulesMap!.set(sourceFile.resolvedPath, exportedModules); - } - } + function addReferenceFromAmbientModule(symbol: Symbol) { + if (!symbol.declarations) { + return; + } + // Add any file other than our own as reference + for (const declaration of symbol.declarations) { + const declarationSourceFile = getSourceFileOfNode(declaration); + if (declarationSourceFile && + declarationSourceFile !== sourceFile) { + addReferencedFile(declarationSourceFile.resolvedPath); } - fileInfos.set(sourceFile.resolvedPath, { version, signature: oldInfo && oldInfo.signature, affectsGlobalScope: isFileAffectingGlobalScope(sourceFile) || undefined, impliedFormat: sourceFile.impliedNodeFormat }); } - - return { - fileInfos, - referencedMap, - exportedModulesMap, - hasCalledUpdateShapeSignature, - useFileVersionAsSignature: !disableUseFileVersionAsSignature && !useOldState - }; } - /** - * Releases needed properties - */ - export function releaseCache(state: BuilderState) { - state.allFilesExcludingDefaultLibraryFile = undefined; - state.allFileNames = undefined; + function addReferencedFile(referencedPath: Path) { + (referencedFiles || (referencedFiles = new Set())).add(referencedPath); } + } - /** - * Creates a clone of the state - */ - export function clone(state: Readonly): BuilderState { - // Dont need to backup allFiles info since its cache anyway - return { - fileInfos: new Map(state.fileInfos), - referencedMap: state.referencedMap?.clone(), - exportedModulesMap: state.exportedModulesMap?.clone(), - hasCalledUpdateShapeSignature: new Set(state.hasCalledUpdateShapeSignature), - useFileVersionAsSignature: state.useFileVersionAsSignature, - }; - } + /** + * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed + */ + export function canReuseOldState(newReferencedMap: ReadonlyManyToManyPathMap | undefined, oldState: Readonly | undefined) { + return oldState && !oldState.referencedMap === !newReferencedMap; + } - /** - * Gets the files affected by the path from the program - */ - export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: ESMap, exportedModulesMapCache?: ManyToManyPathMap): readonly SourceFile[] { - // Since the operation could be cancelled, the signatures are always stored in the cache - // They will be committed once it is safe to use them - // eg when calling this api from tsserver, if there is no cancellation of the operation - // In the other cases the affected files signatures are committed only after the iteration through the result is complete - const signatureCache = cacheToUpdateSignature || new Map(); - const sourceFile = programOfThisState.getSourceFileByPath(path); - if (!sourceFile) { - return emptyArray; + /** + * Creates the state of file references and signature for the new program from oldState if it is safe + */ + export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly, disableUseFileVersionAsSignature?: boolean): BuilderState { + const fileInfos = new Map(); + const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createManyToManyPathMap() : undefined; + const exportedModulesMap = referencedMap ? createManyToManyPathMap() : undefined; + const hasCalledUpdateShapeSignature = new Set(); + const useOldState = canReuseOldState(referencedMap, oldState); + + // Ensure source files have parent pointers set + newProgram.getTypeChecker(); + + // Create the reference map, and set the file infos + for (const sourceFile of newProgram.getSourceFiles()) { + const version = Debug.checkDefined(sourceFile.version, "Program intended to be used with Builder should have source files with versions set"); + const oldInfo = useOldState ? oldState!.fileInfos.get(sourceFile.resolvedPath) : undefined; + if (referencedMap) { + const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); + if (newReferences) { + referencedMap.set(sourceFile.resolvedPath, newReferences); + } + // Copy old visible to outside files map + if (useOldState) { + const exportedModules = oldState!.exportedModulesMap!.getValues(sourceFile.resolvedPath); + if (exportedModules) { + exportedModulesMap!.set(sourceFile.resolvedPath, exportedModules); + } + } } + fileInfos.set(sourceFile.resolvedPath, { version, signature: oldInfo && oldInfo.signature, affectsGlobalScope: isFileAffectingGlobalScope(sourceFile) || undefined, impliedFormat: sourceFile.impliedNodeFormat }); + } - if (!updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache)) { - return [sourceFile]; - } + return { + fileInfos, + referencedMap, + exportedModulesMap, + hasCalledUpdateShapeSignature, + useFileVersionAsSignature: !disableUseFileVersionAsSignature && !useOldState + }; + } - const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache); - if (!cacheToUpdateSignature) { - // Commit all the signatures in the signature cache - updateSignaturesFromCache(state, signatureCache); - } - return result; - } + /** + * Releases needed properties + */ + export function releaseCache(state: BuilderState) { + state.allFilesExcludingDefaultLibraryFile = undefined; + state.allFileNames = undefined; + } - /** - * Updates the signatures from the cache into state's fileinfo signatures - * This should be called whenever it is safe to commit the state of the builder - */ - export function updateSignaturesFromCache(state: BuilderState, signatureCache: ESMap) { - signatureCache.forEach((signature, path) => updateSignatureOfFile(state, signature, path)); + /** + * Creates a clone of the state + */ + export function clone(state: Readonly): BuilderState { + // Dont need to backup allFiles info since its cache anyway + return { + fileInfos: new Map(state.fileInfos), + referencedMap: state.referencedMap?.clone(), + exportedModulesMap: state.exportedModulesMap?.clone(), + hasCalledUpdateShapeSignature: new Set(state.hasCalledUpdateShapeSignature), + useFileVersionAsSignature: state.useFileVersionAsSignature, + }; + } + + /** + * Gets the files affected by the path from the program + */ + export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: ESMap, exportedModulesMapCache?: ManyToManyPathMap): readonly SourceFile[] { + // Since the operation could be cancelled, the signatures are always stored in the cache + // They will be committed once it is safe to use them + // eg when calling this api from tsserver, if there is no cancellation of the operation + // In the other cases the affected files signatures are committed only after the iteration through the result is complete + const signatureCache = cacheToUpdateSignature || new Map(); + const sourceFile = programOfThisState.getSourceFileByPath(path); + if (!sourceFile) { + return emptyArray; } - export function updateSignatureOfFile(state: BuilderState, signature: string | undefined, path: Path) { - state.fileInfos.get(path)!.signature = signature; - state.hasCalledUpdateShapeSignature.add(path); + if (!updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache)) { + return [sourceFile]; } - /** - * Returns if the shape of the signature has changed since last emit - */ - export function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: ESMap, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ManyToManyPathMap, useFileVersionAsSignature: boolean = state.useFileVersionAsSignature) { - Debug.assert(!!sourceFile); - Debug.assert(!exportedModulesMapCache || !!state.exportedModulesMap, "Compute visible to outside map only if visibleToOutsideReferencedMap present in the state"); + const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache); + if (!cacheToUpdateSignature) { + // Commit all the signatures in the signature cache + updateSignaturesFromCache(state, signatureCache); + } + return result; + } - // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if (state.hasCalledUpdateShapeSignature.has(sourceFile.resolvedPath) || cacheToUpdateSignature.has(sourceFile.resolvedPath)) { - return false; - } + /** + * Updates the signatures from the cache into state's fileinfo signatures + * This should be called whenever it is safe to commit the state of the builder + */ + export function updateSignaturesFromCache(state: BuilderState, signatureCache: ESMap) { + signatureCache.forEach((signature, path) => updateSignatureOfFile(state, signature, path)); + } - const info = state.fileInfos.get(sourceFile.resolvedPath); - if (!info) return Debug.fail(); - - const prevSignature = info.signature; - let latestSignature: string | undefined; - if (!sourceFile.isDeclarationFile && !useFileVersionAsSignature) { - const emitOutput = getFileEmitOutput( - programOfThisState, - sourceFile, - /*emitOnlyDtsFiles*/ true, - cancellationToken, - /*customTransformers*/ undefined, - /*forceDtsEmit*/ true - ); - const firstDts = firstOrUndefined(emitOutput.outputFiles); - if (firstDts) { - Debug.assert(fileExtensionIsOneOf(firstDts.name, [Extension.Dts, Extension.Dmts, Extension.Dcts]), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`); - latestSignature = (computeHash || generateDjb2Hash)(firstDts.text); - if (exportedModulesMapCache && latestSignature !== prevSignature) { - updateExportedModules(sourceFile, emitOutput.exportedModulesFromDeclarationEmit, exportedModulesMapCache); - } - } - } - // Default is to use file version as signature - if (latestSignature === undefined) { - latestSignature = sourceFile.version; - if (exportedModulesMapCache && latestSignature !== prevSignature) { - // All the references in this file are exported - const references = state.referencedMap ? state.referencedMap.getValues(sourceFile.resolvedPath) : undefined; - if (references) { - exportedModulesMapCache.set(sourceFile.resolvedPath, references); - } - else { - exportedModulesMapCache.deleteKey(sourceFile.resolvedPath); - } - } - } - cacheToUpdateSignature.set(sourceFile.resolvedPath, latestSignature); - return latestSignature !== prevSignature; - } + export function updateSignatureOfFile(state: BuilderState, signature: string | undefined, path: Path) { + state.fileInfos.get(path)!.signature = signature; + state.hasCalledUpdateShapeSignature.add(path); + } - /** - * Coverts the declaration emit result into exported modules map - */ - function updateExportedModules(sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined, exportedModulesMapCache: ManyToManyPathMap) { - if (!exportedModulesFromDeclarationEmit) { - exportedModulesMapCache.deleteKey(sourceFile.resolvedPath); - return; - } + /** + * Returns if the shape of the signature has changed since last emit + */ + export function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: ESMap, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ManyToManyPathMap, useFileVersionAsSignature: boolean = state.useFileVersionAsSignature) { + Debug.assert(!!sourceFile); + Debug.assert(!exportedModulesMapCache || !!state.exportedModulesMap, "Compute visible to outside map only if visibleToOutsideReferencedMap present in the state"); - let exportedModules: Set | undefined; - exportedModulesFromDeclarationEmit.forEach(symbol => addExportedModule(getReferencedFilesFromImportedModuleSymbol(symbol))); - if (exportedModules) { - exportedModulesMapCache.set(sourceFile.resolvedPath, exportedModules); - } - else { - exportedModulesMapCache.deleteKey(sourceFile.resolvedPath); - } + // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate + if (state.hasCalledUpdateShapeSignature.has(sourceFile.resolvedPath) || cacheToUpdateSignature.has(sourceFile.resolvedPath)) { + return false; + } - function addExportedModule(exportedModulePaths: Path[] | undefined) { - if (exportedModulePaths?.length) { - if (!exportedModules) { - exportedModules = new Set(); - } - exportedModulePaths.forEach(path => exportedModules!.add(path)); + const info = state.fileInfos.get(sourceFile.resolvedPath); + if (!info) return Debug.fail(); + + const prevSignature = info.signature; + let latestSignature: string | undefined; + if (!sourceFile.isDeclarationFile && !useFileVersionAsSignature) { + const emitOutput = getFileEmitOutput( + programOfThisState, + sourceFile, + /*emitOnlyDtsFiles*/ true, + cancellationToken, + /*customTransformers*/ undefined, + /*forceDtsEmit*/ true + ); + const firstDts = firstOrUndefined(emitOutput.outputFiles); + if (firstDts) { + Debug.assert(fileExtensionIsOneOf(firstDts.name, [Extension.Dts, Extension.Dmts, Extension.Dcts]), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`); + latestSignature = (computeHash || generateDjb2Hash)(firstDts.text); + if (exportedModulesMapCache && latestSignature !== prevSignature) { + updateExportedModules(sourceFile, emitOutput.exportedModulesFromDeclarationEmit, exportedModulesMapCache); } } } - - /** - * Updates the exported modules from cache into state's exported modules map - * This should be called whenever it is safe to commit the state of the builder - */ - export function updateExportedFilesMapFromCache(state: BuilderState, exportedModulesMapCache: ManyToManyPathMap | undefined) { - if (exportedModulesMapCache) { - Debug.assert(!!state.exportedModulesMap); - - const cacheId = exportedModulesMapCache.id; - const cacheVersion = exportedModulesMapCache.version(); - if (state.previousCache) { - if (state.previousCache.id === cacheId && state.previousCache.version === cacheVersion) { - // If this is the same cache at the same version as last time this BuilderState - // was updated, there's no need to update again - return; - } - state.previousCache.id = cacheId; - state.previousCache.version = cacheVersion; + // Default is to use file version as signature + if (latestSignature === undefined) { + latestSignature = sourceFile.version; + if (exportedModulesMapCache && latestSignature !== prevSignature) { + // All the references in this file are exported + const references = state.referencedMap ? state.referencedMap.getValues(sourceFile.resolvedPath) : undefined; + if (references) { + exportedModulesMapCache.set(sourceFile.resolvedPath, references); } else { - state.previousCache = { id: cacheId, version: cacheVersion }; + exportedModulesMapCache.deleteKey(sourceFile.resolvedPath); } - - exportedModulesMapCache.deletedKeys()?.forEach(path => state.exportedModulesMap!.deleteKey(path)); - exportedModulesMapCache.forEach((exportedModules, path) => state.exportedModulesMap!.set(path, exportedModules)); } } + cacheToUpdateSignature.set(sourceFile.resolvedPath, latestSignature); + return latestSignature !== prevSignature; + } - /** - * Get all the dependencies of the sourceFile - */ - export function getAllDependencies(state: BuilderState, programOfThisState: Program, sourceFile: SourceFile): readonly string[] { - const compilerOptions = programOfThisState.getCompilerOptions(); - // With --out or --outFile all outputs go into single file, all files depend on each other - if (outFile(compilerOptions)) { - return getAllFileNames(state, programOfThisState); - } + /** + * Coverts the declaration emit result into exported modules map + */ + function updateExportedModules(sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined, exportedModulesMapCache: ManyToManyPathMap) { + if (!exportedModulesFromDeclarationEmit) { + exportedModulesMapCache.deleteKey(sourceFile.resolvedPath); + return; + } - // If this is non module emit, or its a global file, it depends on all the source files - if (!state.referencedMap || isFileAffectingGlobalScope(sourceFile)) { - return getAllFileNames(state, programOfThisState); + let exportedModules: Set | undefined; + exportedModulesFromDeclarationEmit.forEach(symbol => addExportedModule(getReferencedFilesFromImportedModuleSymbol(symbol))); + if (exportedModules) { + exportedModulesMapCache.set(sourceFile.resolvedPath, exportedModules); + } + else { + exportedModulesMapCache.deleteKey(sourceFile.resolvedPath); + } + + function addExportedModule(exportedModulePaths: Path[] | undefined) { + if (exportedModulePaths?.length) { + if (!exportedModules) { + exportedModules = new Set(); + } + exportedModulePaths.forEach(path => exportedModules!.add(path)); } + } + } - // Get the references, traversing deep from the referenceMap - const seenMap = new Set(); - const queue = [sourceFile.resolvedPath]; - while (queue.length) { - const path = queue.pop()!; - if (!seenMap.has(path)) { - seenMap.add(path); - const references = state.referencedMap.getValues(path); - if (references) { - const iterator = references.keys(); - for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { - queue.push(iterResult.value); - } - } + /** + * Updates the exported modules from cache into state's exported modules map + * This should be called whenever it is safe to commit the state of the builder + */ + export function updateExportedFilesMapFromCache(state: BuilderState, exportedModulesMapCache: ManyToManyPathMap | undefined) { + if (exportedModulesMapCache) { + Debug.assert(!!state.exportedModulesMap); + + const cacheId = exportedModulesMapCache.id; + const cacheVersion = exportedModulesMapCache.version(); + if (state.previousCache) { + if (state.previousCache.id === cacheId && state.previousCache.version === cacheVersion) { + // If this is the same cache at the same version as last time this BuilderState + // was updated, there's no need to update again + return; } + state.previousCache.id = cacheId; + state.previousCache.version = cacheVersion; + } + else { + state.previousCache = { id: cacheId, version: cacheVersion }; } - return arrayFrom(mapDefinedIterator(seenMap.keys(), path => programOfThisState.getSourceFileByPath(path)?.fileName ?? path)); + exportedModulesMapCache.deletedKeys()?.forEach(path => state.exportedModulesMap!.deleteKey(path)); + exportedModulesMapCache.forEach((exportedModules, path) => state.exportedModulesMap!.set(path, exportedModules)); } + } - /** - * Gets the names of all files from the program - */ - function getAllFileNames(state: BuilderState, programOfThisState: Program): readonly string[] { - if (!state.allFileNames) { - const sourceFiles = programOfThisState.getSourceFiles(); - state.allFileNames = sourceFiles === emptyArray ? emptyArray : sourceFiles.map(file => file.fileName); - } - return state.allFileNames; + /** + * Get all the dependencies of the sourceFile + */ + export function getAllDependencies(state: BuilderState, programOfThisState: Program, sourceFile: SourceFile): readonly string[] { + const compilerOptions = programOfThisState.getCompilerOptions(); + // With --out or --outFile all outputs go into single file, all files depend on each other + if (outFile(compilerOptions)) { + return getAllFileNames(state, programOfThisState); } - /** - * Gets the files referenced by the the file path - */ - export function getReferencedByPaths(state: Readonly, referencedFilePath: Path) { - const keys = state.referencedMap!.getKeys(referencedFilePath); - return keys ? arrayFrom(keys.keys()) : []; + // If this is non module emit, or its a global file, it depends on all the source files + if (!state.referencedMap || isFileAffectingGlobalScope(sourceFile)) { + return getAllFileNames(state, programOfThisState); } - /** - * For script files that contains only ambient external modules, although they are not actually external module files, - * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, - * there are no point to rebuild all script files if these special files have changed. However, if any statement - * in the file is not ambient external module, we treat it as a regular script file. - */ - function containsOnlyAmbientModules(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (!isModuleWithStringLiteralName(statement)) { - return false; + // Get the references, traversing deep from the referenceMap + const seenMap = new Set(); + const queue = [sourceFile.resolvedPath]; + while (queue.length) { + const path = queue.pop()!; + if (!seenMap.has(path)) { + seenMap.add(path); + const references = state.referencedMap.getValues(path); + if (references) { + const iterator = references.keys(); + for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { + queue.push(iterResult.value); + } } } - return true; } - /** - * Return true if file contains anything that augments to global scope we need to build them as if - * they are global files as well as module - */ - function containsGlobalScopeAugmentation(sourceFile: SourceFile) { - return some(sourceFile.moduleAugmentations, augmentation => isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)); - } + return arrayFrom(mapDefinedIterator(seenMap.keys(), path => programOfThisState.getSourceFileByPath(path)?.fileName ?? path)); + } - /** - * Return true if the file will invalidate all files because it affectes global scope - */ - function isFileAffectingGlobalScope(sourceFile: SourceFile) { - return containsGlobalScopeAugmentation(sourceFile) || - !isExternalOrCommonJsModule(sourceFile) && !isJsonSourceFile(sourceFile) && !containsOnlyAmbientModules(sourceFile); + /** + * Gets the names of all files from the program + */ + function getAllFileNames(state: BuilderState, programOfThisState: Program): readonly string[] { + if (!state.allFileNames) { + const sourceFiles = programOfThisState.getSourceFiles(); + state.allFileNames = sourceFiles === emptyArray ? emptyArray : sourceFiles.map(file => file.fileName); } + return state.allFileNames; + } - /** - * Gets all files of the program excluding the default library file - */ - export function getAllFilesExcludingDefaultLibraryFile(state: BuilderState, programOfThisState: Program, firstSourceFile: SourceFile | undefined): readonly SourceFile[] { - // Use cached result - if (state.allFilesExcludingDefaultLibraryFile) { - return state.allFilesExcludingDefaultLibraryFile; - } + /** + * Gets the files referenced by the the file path + */ + export function getReferencedByPaths(state: Readonly, referencedFilePath: Path) { + const keys = state.referencedMap!.getKeys(referencedFilePath); + return keys ? arrayFrom(keys.keys()) : []; + } - let result: SourceFile[] | undefined; - if (firstSourceFile) addSourceFile(firstSourceFile); - for (const sourceFile of programOfThisState.getSourceFiles()) { - if (sourceFile !== firstSourceFile) { - addSourceFile(sourceFile); - } + /** + * For script files that contains only ambient external modules, although they are not actually external module files, + * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, + * there are no point to rebuild all script files if these special files have changed. However, if any statement + * in the file is not ambient external module, we treat it as a regular script file. + */ + function containsOnlyAmbientModules(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (!isModuleWithStringLiteralName(statement)) { + return false; } - state.allFilesExcludingDefaultLibraryFile = result || emptyArray; + } + return true; + } + + /** + * Return true if file contains anything that augments to global scope we need to build them as if + * they are global files as well as module + */ + function containsGlobalScopeAugmentation(sourceFile: SourceFile) { + return some(sourceFile.moduleAugmentations, augmentation => isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)); + } + + /** + * Return true if the file will invalidate all files because it affectes global scope + */ + function isFileAffectingGlobalScope(sourceFile: SourceFile) { + return containsGlobalScopeAugmentation(sourceFile) || + !isExternalOrCommonJsModule(sourceFile) && !isJsonSourceFile(sourceFile) && !containsOnlyAmbientModules(sourceFile); + } + + /** + * Gets all files of the program excluding the default library file + */ + export function getAllFilesExcludingDefaultLibraryFile(state: BuilderState, programOfThisState: Program, firstSourceFile: SourceFile | undefined): readonly SourceFile[] { + // Use cached result + if (state.allFilesExcludingDefaultLibraryFile) { return state.allFilesExcludingDefaultLibraryFile; + } - function addSourceFile(sourceFile: SourceFile) { - if (!programOfThisState.isSourceFileDefaultLibrary(sourceFile)) { - (result || (result = [])).push(sourceFile); - } + let result: SourceFile[] | undefined; + if (firstSourceFile) addSourceFile(firstSourceFile); + for (const sourceFile of programOfThisState.getSourceFiles()) { + if (sourceFile !== firstSourceFile) { + addSourceFile(sourceFile); } } + state.allFilesExcludingDefaultLibraryFile = result || emptyArray; + return state.allFilesExcludingDefaultLibraryFile; - /** - * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { - const compilerOptions = programOfThisState.getCompilerOptions(); - // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, - // so returning the file itself is good enough. - if (compilerOptions && outFile(compilerOptions)) { - return [sourceFileWithUpdatedShape]; + function addSourceFile(sourceFile: SourceFile) { + if (!programOfThisState.isSourceFileDefaultLibrary(sourceFile)) { + (result || (result = [])).push(sourceFile); } - return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); } + } - /** - * When program emits modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: ESMap, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache: ManyToManyPathMap | undefined) { - if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) { - return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); - } + /** + * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + const compilerOptions = programOfThisState.getCompilerOptions(); + // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, + // so returning the file itself is good enough. + if (compilerOptions && outFile(compilerOptions)) { + return [sourceFileWithUpdatedShape]; + } + return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); + } - const compilerOptions = programOfThisState.getCompilerOptions(); - if (compilerOptions && (compilerOptions.isolatedModules || outFile(compilerOptions))) { - return [sourceFileWithUpdatedShape]; - } + /** + * When program emits modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: ESMap, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache: ManyToManyPathMap | undefined) { + if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) { + return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); + } - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - const seenFileNamesMap = new Map(); - - // Start with the paths this file was referenced by - seenFileNamesMap.set(sourceFileWithUpdatedShape.resolvedPath, sourceFileWithUpdatedShape); - const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath); - while (queue.length > 0) { - const currentPath = queue.pop()!; - if (!seenFileNamesMap.has(currentPath)) { - const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!; - seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash, exportedModulesMapCache)) { - queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath)); - } + const compilerOptions = programOfThisState.getCompilerOptions(); + if (compilerOptions && (compilerOptions.isolatedModules || outFile(compilerOptions))) { + return [sourceFileWithUpdatedShape]; + } + + // Now we need to if each file in the referencedBy list has a shape change as well. + // Because if so, its own referencedBy files need to be saved as well to make the + // emitting result consistent with files on disk. + const seenFileNamesMap = new Map(); + + // Start with the paths this file was referenced by + seenFileNamesMap.set(sourceFileWithUpdatedShape.resolvedPath, sourceFileWithUpdatedShape); + const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath); + while (queue.length > 0) { + const currentPath = queue.pop()!; + if (!seenFileNamesMap.has(currentPath)) { + const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!; + seenFileNamesMap.set(currentPath, currentSourceFile); + if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash, exportedModulesMapCache)) { + queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath)); } } - - // Return array of values that needs emit - return arrayFrom(mapDefinedIterator(seenFileNamesMap.values(), value => value)); } + + // Return array of values that needs emit + return arrayFrom(mapDefinedIterator(seenFileNamesMap.values(), value => value)); } } +} diff --git a/src/compiler/builderStatePublic.ts b/src/compiler/builderStatePublic.ts index ce542b0825b80..3d3463f2c2fe1 100644 --- a/src/compiler/builderStatePublic.ts +++ b/src/compiler/builderStatePublic.ts @@ -1,14 +1,14 @@ namespace ts { - export interface EmitOutput { - outputFiles: OutputFile[]; - emitSkipped: boolean; - /* @internal */ diagnostics: readonly Diagnostic[]; - /* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit; - } +export interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + /* @internal */ diagnostics: readonly Diagnostic[]; + /* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit; +} - export interface OutputFile { - name: string; - writeByteOrderMark: boolean; - text: string; - } +export interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; +} } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a90bbb2036e36..dea58ff37df57 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1,44158 +1,44158 @@ /* @internal */ namespace ts { - const ambientModuleSymbolRegex = /^".+"$/; - const anon = "(anonymous)" as __String & string; - - let nextSymbolId = 1; - let nextNodeId = 1; - let nextMergeId = 1; - let nextFlowId = 1; - - const enum IterationUse { - AllowsSyncIterablesFlag = 1 << 0, - AllowsAsyncIterablesFlag = 1 << 1, - AllowsStringInputFlag = 1 << 2, - ForOfFlag = 1 << 3, - YieldStarFlag = 1 << 4, - SpreadFlag = 1 << 5, - DestructuringFlag = 1 << 6, - PossiblyOutOfBounds = 1 << 7, - - // Spread, Destructuring, Array element assignment - Element = AllowsSyncIterablesFlag, - Spread = AllowsSyncIterablesFlag | SpreadFlag, - Destructuring = AllowsSyncIterablesFlag | DestructuringFlag, - - ForOf = AllowsSyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, - ForAwaitOf = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, - - YieldStar = AllowsSyncIterablesFlag | YieldStarFlag, - AsyncYieldStar = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | YieldStarFlag, - - GeneratorReturnType = AllowsSyncIterablesFlag, - AsyncGeneratorReturnType = AllowsAsyncIterablesFlag, - - } - - const enum IterationTypeKind { - Yield, - Return, - Next, - } - - interface IterationTypesResolver { - iterableCacheKey: "iterationTypesOfAsyncIterable" | "iterationTypesOfIterable"; - iteratorCacheKey: "iterationTypesOfAsyncIterator" | "iterationTypesOfIterator"; - iteratorSymbolName: "asyncIterator" | "iterator"; - getGlobalIteratorType: (reportErrors: boolean) => GenericType; - getGlobalIterableType: (reportErrors: boolean) => GenericType; - getGlobalIterableIteratorType: (reportErrors: boolean) => GenericType; - getGlobalGeneratorType: (reportErrors: boolean) => GenericType; - resolveIterationType: (type: Type, errorNode: Node | undefined) => Type | undefined; - mustHaveANextMethodDiagnostic: DiagnosticMessage; - mustBeAMethodDiagnostic: DiagnosticMessage; - mustHaveAValueDiagnostic: DiagnosticMessage; - } - - const enum WideningKind { - Normal, - FunctionReturn, - GeneratorNext, - GeneratorYield, - } - - const enum TypeFacts { - None = 0, - TypeofEQString = 1 << 0, // typeof x === "string" - TypeofEQNumber = 1 << 1, // typeof x === "number" - TypeofEQBigInt = 1 << 2, // typeof x === "bigint" - TypeofEQBoolean = 1 << 3, // typeof x === "boolean" - TypeofEQSymbol = 1 << 4, // typeof x === "symbol" - TypeofEQObject = 1 << 5, // typeof x === "object" - TypeofEQFunction = 1 << 6, // typeof x === "function" - TypeofEQHostObject = 1 << 7, // typeof x === "xxx" - TypeofNEString = 1 << 8, // typeof x !== "string" - TypeofNENumber = 1 << 9, // typeof x !== "number" - TypeofNEBigInt = 1 << 10, // typeof x !== "bigint" - TypeofNEBoolean = 1 << 11, // typeof x !== "boolean" - TypeofNESymbol = 1 << 12, // typeof x !== "symbol" - TypeofNEObject = 1 << 13, // typeof x !== "object" - TypeofNEFunction = 1 << 14, // typeof x !== "function" - TypeofNEHostObject = 1 << 15, // typeof x !== "xxx" - EQUndefined = 1 << 16, // x === undefined - EQNull = 1 << 17, // x === null - EQUndefinedOrNull = 1 << 18, // x === undefined / x === null - NEUndefined = 1 << 19, // x !== undefined - NENull = 1 << 20, // x !== null - NEUndefinedOrNull = 1 << 21, // x != undefined / x != null - Truthy = 1 << 22, // x - Falsy = 1 << 23, // !x - All = (1 << 24) - 1, - // The following members encode facts about particular kinds of types for use in the getTypeFacts function. - // The presence of a particular fact means that the given test is true for some (and possibly all) values - // of that kind of type. - BaseStringStrictFacts = TypeofEQString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, - BaseStringFacts = BaseStringStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - StringStrictFacts = BaseStringStrictFacts | Truthy | Falsy, - StringFacts = BaseStringFacts | Truthy, - EmptyStringStrictFacts = BaseStringStrictFacts | Falsy, - EmptyStringFacts = BaseStringFacts, - NonEmptyStringStrictFacts = BaseStringStrictFacts | Truthy, - NonEmptyStringFacts = BaseStringFacts | Truthy, - BaseNumberStrictFacts = TypeofEQNumber | TypeofNEString | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, - BaseNumberFacts = BaseNumberStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - NumberStrictFacts = BaseNumberStrictFacts | Truthy | Falsy, - NumberFacts = BaseNumberFacts | Truthy, - ZeroNumberStrictFacts = BaseNumberStrictFacts | Falsy, - ZeroNumberFacts = BaseNumberFacts, - NonZeroNumberStrictFacts = BaseNumberStrictFacts | Truthy, - NonZeroNumberFacts = BaseNumberFacts | Truthy, - BaseBigIntStrictFacts = TypeofEQBigInt | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, - BaseBigIntFacts = BaseBigIntStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - BigIntStrictFacts = BaseBigIntStrictFacts | Truthy | Falsy, - BigIntFacts = BaseBigIntFacts | Truthy, - ZeroBigIntStrictFacts = BaseBigIntStrictFacts | Falsy, - ZeroBigIntFacts = BaseBigIntFacts, - NonZeroBigIntStrictFacts = BaseBigIntStrictFacts | Truthy, - NonZeroBigIntFacts = BaseBigIntFacts | Truthy, - BaseBooleanStrictFacts = TypeofEQBoolean | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, - BaseBooleanFacts = BaseBooleanStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - BooleanStrictFacts = BaseBooleanStrictFacts | Truthy | Falsy, - BooleanFacts = BaseBooleanFacts | Truthy, - FalseStrictFacts = BaseBooleanStrictFacts | Falsy, - FalseFacts = BaseBooleanFacts, - TrueStrictFacts = BaseBooleanStrictFacts | Truthy, - TrueFacts = BaseBooleanFacts | Truthy, - SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, - SymbolFacts = SymbolStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy, - ObjectFacts = ObjectStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, - FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy, - NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, - EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull), - AllTypeofNE = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | NEUndefined, - EmptyObjectFacts = All, - } - - const typeofEQFacts: ReadonlyESMap = new Map(getEntries({ - string: TypeFacts.TypeofEQString, - number: TypeFacts.TypeofEQNumber, - bigint: TypeFacts.TypeofEQBigInt, - boolean: TypeFacts.TypeofEQBoolean, - symbol: TypeFacts.TypeofEQSymbol, - undefined: TypeFacts.EQUndefined, - object: TypeFacts.TypeofEQObject, - function: TypeFacts.TypeofEQFunction - })); +const ambientModuleSymbolRegex = /^".+"$/; +const anon = "(anonymous)" as __String & string; + +let nextSymbolId = 1; +let nextNodeId = 1; +let nextMergeId = 1; +let nextFlowId = 1; + +const enum IterationUse { + AllowsSyncIterablesFlag = 1 << 0, + AllowsAsyncIterablesFlag = 1 << 1, + AllowsStringInputFlag = 1 << 2, + ForOfFlag = 1 << 3, + YieldStarFlag = 1 << 4, + SpreadFlag = 1 << 5, + DestructuringFlag = 1 << 6, + PossiblyOutOfBounds = 1 << 7, + + // Spread, Destructuring, Array element assignment + Element = AllowsSyncIterablesFlag, + Spread = AllowsSyncIterablesFlag | SpreadFlag, + Destructuring = AllowsSyncIterablesFlag | DestructuringFlag, + + ForOf = AllowsSyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, + ForAwaitOf = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, + + YieldStar = AllowsSyncIterablesFlag | YieldStarFlag, + AsyncYieldStar = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | YieldStarFlag, + + GeneratorReturnType = AllowsSyncIterablesFlag, + AsyncGeneratorReturnType = AllowsAsyncIterablesFlag, - const typeofNEFacts: ReadonlyESMap = new Map(getEntries({ - string: TypeFacts.TypeofNEString, - number: TypeFacts.TypeofNENumber, - bigint: TypeFacts.TypeofNEBigInt, - boolean: TypeFacts.TypeofNEBoolean, - symbol: TypeFacts.TypeofNESymbol, - undefined: TypeFacts.NEUndefined, - object: TypeFacts.TypeofNEObject, - function: TypeFacts.TypeofNEFunction - })); +} - type TypeSystemEntity = Node | Symbol | Type | Signature; +const enum IterationTypeKind { + Yield, + Return, + Next, +} - const enum TypeSystemPropertyName { - Type, - ResolvedBaseConstructorType, - DeclaredType, - ResolvedReturnType, - ImmediateBaseConstraint, - EnumTagType, - ResolvedTypeArguments, - ResolvedBaseTypes, - } +interface IterationTypesResolver { + iterableCacheKey: "iterationTypesOfAsyncIterable" | "iterationTypesOfIterable"; + iteratorCacheKey: "iterationTypesOfAsyncIterator" | "iterationTypesOfIterator"; + iteratorSymbolName: "asyncIterator" | "iterator"; + getGlobalIteratorType: (reportErrors: boolean) => GenericType; + getGlobalIterableType: (reportErrors: boolean) => GenericType; + getGlobalIterableIteratorType: (reportErrors: boolean) => GenericType; + getGlobalGeneratorType: (reportErrors: boolean) => GenericType; + resolveIterationType: (type: Type, errorNode: Node | undefined) => Type | undefined; + mustHaveANextMethodDiagnostic: DiagnosticMessage; + mustBeAMethodDiagnostic: DiagnosticMessage; + mustHaveAValueDiagnostic: DiagnosticMessage; +} - const enum CheckMode { - Normal = 0, // Normal type checking - Contextual = 1 << 0, // Explicitly assigned contextual type, therefore not cacheable - Inferential = 1 << 1, // Inferential typing - SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions - SkipGenericFunctions = 1 << 3, // Skip single signature generic functions - IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help - } +const enum WideningKind { + Normal, + FunctionReturn, + GeneratorNext, + GeneratorYield, +} - const enum SignatureCheckMode { - BivariantCallback = 1 << 0, - StrictCallback = 1 << 1, - IgnoreReturnTypes = 1 << 2, - StrictArity = 1 << 3, - Callback = BivariantCallback | StrictCallback, - } +const enum TypeFacts { + None = 0, + TypeofEQString = 1 << 0, // typeof x === "string" + TypeofEQNumber = 1 << 1, // typeof x === "number" + TypeofEQBigInt = 1 << 2, // typeof x === "bigint" + TypeofEQBoolean = 1 << 3, // typeof x === "boolean" + TypeofEQSymbol = 1 << 4, // typeof x === "symbol" + TypeofEQObject = 1 << 5, // typeof x === "object" + TypeofEQFunction = 1 << 6, // typeof x === "function" + TypeofEQHostObject = 1 << 7, // typeof x === "xxx" + TypeofNEString = 1 << 8, // typeof x !== "string" + TypeofNENumber = 1 << 9, // typeof x !== "number" + TypeofNEBigInt = 1 << 10, // typeof x !== "bigint" + TypeofNEBoolean = 1 << 11, // typeof x !== "boolean" + TypeofNESymbol = 1 << 12, // typeof x !== "symbol" + TypeofNEObject = 1 << 13, // typeof x !== "object" + TypeofNEFunction = 1 << 14, // typeof x !== "function" + TypeofNEHostObject = 1 << 15, // typeof x !== "xxx" + EQUndefined = 1 << 16, // x === undefined + EQNull = 1 << 17, // x === null + EQUndefinedOrNull = 1 << 18, // x === undefined / x === null + NEUndefined = 1 << 19, // x !== undefined + NENull = 1 << 20, // x !== null + NEUndefinedOrNull = 1 << 21, // x != undefined / x != null + Truthy = 1 << 22, // x + Falsy = 1 << 23, // !x + All = (1 << 24) - 1, + // The following members encode facts about particular kinds of types for use in the getTypeFacts function. + // The presence of a particular fact means that the given test is true for some (and possibly all) values + // of that kind of type. + BaseStringStrictFacts = TypeofEQString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, + BaseStringFacts = BaseStringStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + StringStrictFacts = BaseStringStrictFacts | Truthy | Falsy, + StringFacts = BaseStringFacts | Truthy, + EmptyStringStrictFacts = BaseStringStrictFacts | Falsy, + EmptyStringFacts = BaseStringFacts, + NonEmptyStringStrictFacts = BaseStringStrictFacts | Truthy, + NonEmptyStringFacts = BaseStringFacts | Truthy, + BaseNumberStrictFacts = TypeofEQNumber | TypeofNEString | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, + BaseNumberFacts = BaseNumberStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + NumberStrictFacts = BaseNumberStrictFacts | Truthy | Falsy, + NumberFacts = BaseNumberFacts | Truthy, + ZeroNumberStrictFacts = BaseNumberStrictFacts | Falsy, + ZeroNumberFacts = BaseNumberFacts, + NonZeroNumberStrictFacts = BaseNumberStrictFacts | Truthy, + NonZeroNumberFacts = BaseNumberFacts | Truthy, + BaseBigIntStrictFacts = TypeofEQBigInt | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, + BaseBigIntFacts = BaseBigIntStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + BigIntStrictFacts = BaseBigIntStrictFacts | Truthy | Falsy, + BigIntFacts = BaseBigIntFacts | Truthy, + ZeroBigIntStrictFacts = BaseBigIntStrictFacts | Falsy, + ZeroBigIntFacts = BaseBigIntFacts, + NonZeroBigIntStrictFacts = BaseBigIntStrictFacts | Truthy, + NonZeroBigIntFacts = BaseBigIntFacts | Truthy, + BaseBooleanStrictFacts = TypeofEQBoolean | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, + BaseBooleanFacts = BaseBooleanStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + BooleanStrictFacts = BaseBooleanStrictFacts | Truthy | Falsy, + BooleanFacts = BaseBooleanFacts | Truthy, + FalseStrictFacts = BaseBooleanStrictFacts | Falsy, + FalseFacts = BaseBooleanFacts, + TrueStrictFacts = BaseBooleanStrictFacts | Truthy, + TrueFacts = BaseBooleanFacts | Truthy, + SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + SymbolFacts = SymbolStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + ObjectFacts = ObjectStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy, + NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, + EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull), + AllTypeofNE = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | NEUndefined, + EmptyObjectFacts = All, +} - const enum IntersectionState { - None = 0, - Source = 1 << 0, - Target = 1 << 1, - PropertyCheck = 1 << 2, - UnionIntersectionCheck = 1 << 3, - InPropertyCheck = 1 << 4, - } +const typeofEQFacts: ReadonlyESMap = new Map(getEntries({ + string: TypeFacts.TypeofEQString, + number: TypeFacts.TypeofEQNumber, + bigint: TypeFacts.TypeofEQBigInt, + boolean: TypeFacts.TypeofEQBoolean, + symbol: TypeFacts.TypeofEQSymbol, + undefined: TypeFacts.EQUndefined, + object: TypeFacts.TypeofEQObject, + function: TypeFacts.TypeofEQFunction +})); + +const typeofNEFacts: ReadonlyESMap = new Map(getEntries({ + string: TypeFacts.TypeofNEString, + number: TypeFacts.TypeofNENumber, + bigint: TypeFacts.TypeofNEBigInt, + boolean: TypeFacts.TypeofNEBoolean, + symbol: TypeFacts.TypeofNESymbol, + undefined: TypeFacts.NEUndefined, + object: TypeFacts.TypeofNEObject, + function: TypeFacts.TypeofNEFunction +})); + +type TypeSystemEntity = Node | Symbol | Type | Signature; + +const enum TypeSystemPropertyName { + Type, + ResolvedBaseConstructorType, + DeclaredType, + ResolvedReturnType, + ImmediateBaseConstraint, + EnumTagType, + ResolvedTypeArguments, + ResolvedBaseTypes, +} - const enum RecursionFlags { - None = 0, - Source = 1 << 0, - Target = 1 << 1, - Both = Source | Target, - } +const enum CheckMode { + Normal = 0, // Normal type checking + Contextual = 1 << 0, // Explicitly assigned contextual type, therefore not cacheable + Inferential = 1 << 1, // Inferential typing + SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions + SkipGenericFunctions = 1 << 3, // Skip single signature generic functions + IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help +} - const enum MappedTypeModifiers { - IncludeReadonly = 1 << 0, - ExcludeReadonly = 1 << 1, - IncludeOptional = 1 << 2, - ExcludeOptional = 1 << 3, - } +const enum SignatureCheckMode { + BivariantCallback = 1 << 0, + StrictCallback = 1 << 1, + IgnoreReturnTypes = 1 << 2, + StrictArity = 1 << 3, + Callback = BivariantCallback | StrictCallback, +} - const enum ExpandingFlags { - None = 0, - Source = 1, - Target = 1 << 1, - Both = Source | Target, - } +const enum IntersectionState { + None = 0, + Source = 1 << 0, + Target = 1 << 1, + PropertyCheck = 1 << 2, + UnionIntersectionCheck = 1 << 3, + InPropertyCheck = 1 << 4, +} - const enum MembersOrExportsResolutionKind { - resolvedExports = "resolvedExports", - resolvedMembers = "resolvedMembers" - } +const enum RecursionFlags { + None = 0, + Source = 1 << 0, + Target = 1 << 1, + Both = Source | Target, +} - const enum UnusedKind { - Local, - Parameter, - } +const enum MappedTypeModifiers { + IncludeReadonly = 1 << 0, + ExcludeReadonly = 1 << 1, + IncludeOptional = 1 << 2, + ExcludeOptional = 1 << 3, +} - /** @param containingNode Node to check for parse error */ - type AddUnusedDiagnostic = (containingNode: Node, type: UnusedKind, diagnostic: DiagnosticWithLocation) => void; +const enum ExpandingFlags { + None = 0, + Source = 1, + Target = 1 << 1, + Both = Source | Target, +} - const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor); +const enum MembersOrExportsResolutionKind { + resolvedExports = "resolvedExports", + resolvedMembers = "resolvedMembers" +} - const enum DeclarationMeaning { - GetAccessor = 1, - SetAccessor = 2, - PropertyAssignment = 4, - Method = 8, - PrivateStatic = 16, - GetOrSetAccessor = GetAccessor | SetAccessor, - PropertyAssignmentOrMethod = PropertyAssignment | Method, - } +const enum UnusedKind { + Local, + Parameter, +} - const enum DeclarationSpaces { - None = 0, - ExportValue = 1 << 0, - ExportType = 1 << 1, - ExportNamespace = 1 << 2, - } +/** @param containingNode Node to check for parse error */ +type AddUnusedDiagnostic = (containingNode: Node, type: UnusedKind, diagnostic: DiagnosticWithLocation) => void; - const enum MinArgumentCountFlags { - None = 0, - StrongArityForUntypedJS = 1 << 0, - VoidIsNonOptional = 1 << 1, - } +const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor); - const enum IntrinsicTypeKind { - Uppercase, - Lowercase, - Capitalize, - Uncapitalize - } +const enum DeclarationMeaning { + GetAccessor = 1, + SetAccessor = 2, + PropertyAssignment = 4, + Method = 8, + PrivateStatic = 16, + GetOrSetAccessor = GetAccessor | SetAccessor, + PropertyAssignmentOrMethod = PropertyAssignment | Method, +} - const intrinsicTypeKinds: ReadonlyESMap = new Map(getEntries({ - Uppercase: IntrinsicTypeKind.Uppercase, - Lowercase: IntrinsicTypeKind.Lowercase, - Capitalize: IntrinsicTypeKind.Capitalize, - Uncapitalize: IntrinsicTypeKind.Uncapitalize - })); +const enum DeclarationSpaces { + None = 0, + ExportValue = 1 << 0, + ExportType = 1 << 1, + ExportNamespace = 1 << 2, +} - function SymbolLinks(this: SymbolLinks) { - } +const enum MinArgumentCountFlags { + None = 0, + StrongArityForUntypedJS = 1 << 0, + VoidIsNonOptional = 1 << 1, +} - function NodeLinks(this: NodeLinks) { - this.flags = 0; - } +const enum IntrinsicTypeKind { + Uppercase, + Lowercase, + Capitalize, + Uncapitalize +} - export function getNodeId(node: Node): number { - if (!node.id) { - node.id = nextNodeId; - nextNodeId++; - } - return node.id; - } +const intrinsicTypeKinds: ReadonlyESMap = new Map(getEntries({ + Uppercase: IntrinsicTypeKind.Uppercase, + Lowercase: IntrinsicTypeKind.Lowercase, + Capitalize: IntrinsicTypeKind.Capitalize, + Uncapitalize: IntrinsicTypeKind.Uncapitalize +})); - export function getSymbolId(symbol: Symbol): SymbolId { - if (!symbol.id) { - symbol.id = nextSymbolId; - nextSymbolId++; - } +function SymbolLinks(this: SymbolLinks) { +} - return symbol.id; +function NodeLinks(this: NodeLinks) { + this.flags = 0; +} + +export function getNodeId(node: Node): number { + if (!node.id) { + node.id = nextNodeId; + nextNodeId++; } + return node.id; +} - export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { - const moduleState = getModuleInstanceState(node); - return moduleState === ModuleInstanceState.Instantiated || - (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); +export function getSymbolId(symbol: Symbol): SymbolId { + if (!symbol.id) { + symbol.id = nextSymbolId; + nextSymbolId++; } - export function createTypeChecker(host: TypeCheckerHost, produceDiagnostics: boolean): TypeChecker { - const getPackagesMap = memoize(() => { - // A package name maps to true when we detect it has .d.ts files. - // This is useful as an approximation of whether a package bundles its own types. - // Note: we only look at files already found by module resolution, - // so there may be files we did not consider. - const map = new Map(); - host.getSourceFiles().forEach(sf => { - if (!sf.resolvedModules) return; + return symbol.id; +} + +export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { + const moduleState = getModuleInstanceState(node); + return moduleState === ModuleInstanceState.Instantiated || + (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); +} - sf.resolvedModules.forEach(r => { - if (r && r.packageId) map.set(r.packageId.name, r.extension === Extension.Dts || !!map.get(r.packageId.name)); - }); +export function createTypeChecker(host: TypeCheckerHost, produceDiagnostics: boolean): TypeChecker { + const getPackagesMap = memoize(() => { + // A package name maps to true when we detect it has .d.ts files. + // This is useful as an approximation of whether a package bundles its own types. + // Note: we only look at files already found by module resolution, + // so there may be files we did not consider. + const map = new Map(); + host.getSourceFiles().forEach(sf => { + if (!sf.resolvedModules) return; + + sf.resolvedModules.forEach(r => { + if (r && r.packageId) map.set(r.packageId.name, r.extension === Extension.Dts || !!map.get(r.packageId.name)); }); - return map; }); - - // Cancellation that controls whether or not we can cancel in the middle of type checking. - // In general cancelling is *not* safe for the type checker. We might be in the middle of - // computing something, and we will leave our internals in an inconsistent state. Callers - // who set the cancellation token should catch if a cancellation exception occurs, and - // should throw away and create a new TypeChecker. - // - // Currently we only support setting the cancellation token when getting diagnostics. This - // is because diagnostics can be quite expensive, and we want to allow hosts to bail out if - // they no longer need the information (for example, if the user started editing again). - let cancellationToken: CancellationToken | undefined; - let requestedExternalEmitHelpers: ExternalEmitHelpers; - let externalHelpersModule: Symbol; - - const Symbol = objectAllocator.getSymbolConstructor(); - const Type = objectAllocator.getTypeConstructor(); - const Signature = objectAllocator.getSignatureConstructor(); - - let typeCount = 0; - let symbolCount = 0; - let enumCount = 0; - let totalInstantiationCount = 0; - let instantiationCount = 0; - let instantiationDepth = 0; - let inlineLevel = 0; - let currentNode: Node | undefined; - - const emptySymbols = createSymbolTable(); - const arrayVariances = [VarianceFlags.Covariant]; - - const compilerOptions = host.getCompilerOptions(); - const languageVersion = getEmitScriptTarget(compilerOptions); - const moduleKind = getEmitModuleKind(compilerOptions); - const useDefineForClassFields = getUseDefineForClassFields(compilerOptions); - const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); - const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); - const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); - const strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply"); - const strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); - const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); - const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); - const useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables"); - const keyofStringsOnly = !!compilerOptions.keyofStringsOnly; - const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral; - const exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes; - - const checkBinaryExpression = createCheckBinaryExpression(); - const emitResolver = createResolver(); - const nodeBuilder = createNodeBuilder(); - - const globals = createSymbolTable(); - const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String); - undefinedSymbol.declarations = []; - - const globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); - globalThisSymbol.exports = globals; - globalThisSymbol.declarations = []; - globals.set(globalThisSymbol.escapedName, globalThisSymbol); - - const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String); - const requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String); - - /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ - let apparentArgumentCount: number | undefined; - - // for public members that accept a Node or one of its subtypes, we must guard against - // synthetic nodes created during transformations by calling `getParseTreeNode`. - // for most of these, we perform the guard only on `checker` to avoid any possible - // extra cost of calling `getParseTreeNode` when calling these functions from inside the - // checker. - const checker: TypeChecker = { - getNodeCount: () => sum(host.getSourceFiles(), "nodeCount"), - getIdentifierCount: () => sum(host.getSourceFiles(), "identifierCount"), - getSymbolCount: () => sum(host.getSourceFiles(), "symbolCount") + symbolCount, - getTypeCount: () => typeCount, - getInstantiationCount: () => totalInstantiationCount, - getRelationCacheSizes: () => ({ - assignable: assignableRelation.size, - identity: identityRelation.size, - subtype: subtypeRelation.size, - strictSubtype: strictSubtypeRelation.size, - }), - isUndefinedSymbol: symbol => symbol === undefinedSymbol, - isArgumentsSymbol: symbol => symbol === argumentsSymbol, - isUnknownSymbol: symbol => symbol === unknownSymbol, - getMergedSymbol, - getDiagnostics, - getGlobalDiagnostics, - getRecursionIdentity, - getUnmatchedProperties, - getTypeOfSymbolAtLocation: (symbol, locationIn) => { - const location = getParseTreeNode(locationIn); - return location ? getTypeOfSymbolAtLocation(symbol, location) : errorType; - }, - getSymbolsOfParameterPropertyDeclaration: (parameterIn, parameterName) => { - const parameter = getParseTreeNode(parameterIn, isParameter); - if (parameter === undefined) return Debug.fail("Cannot get symbols of a synthetic parameter that cannot be resolved to a parse-tree node."); - return getSymbolsOfParameterPropertyDeclaration(parameter, escapeLeadingUnderscores(parameterName)); - }, - getDeclaredTypeOfSymbol, - getPropertiesOfType, - getPropertyOfType: (type, name) => getPropertyOfType(type, escapeLeadingUnderscores(name)), - getPrivateIdentifierPropertyOfType: (leftType: Type, name: string, location: Node) => { - const node = getParseTreeNode(location); - if (!node) { - return undefined; - } - const propName = escapeLeadingUnderscores(name); - const lexicallyScopedIdentifier = lookupSymbolForPrivateIdentifierDeclaration(propName, node); - return lexicallyScopedIdentifier ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedIdentifier) : undefined; - }, - getTypeOfPropertyOfType: (type, name) => getTypeOfPropertyOfType(type, escapeLeadingUnderscores(name)), - getIndexInfoOfType: (type, kind) => getIndexInfoOfType(type, kind === IndexKind.String ? stringType : numberType), - getIndexInfosOfType, - getSignaturesOfType, - getIndexTypeOfType: (type, kind) => getIndexTypeOfType(type, kind === IndexKind.String ? stringType : numberType), - getBaseTypes, - getBaseTypeOfLiteralType, - getWidenedType, - getTypeFromTypeNode: nodeIn => { - const node = getParseTreeNode(nodeIn, isTypeNode); - return node ? getTypeFromTypeNode(node) : errorType; - }, - getParameterType: getTypeAtPosition, - getParameterIdentifierNameAtPosition, - getPromisedTypeOfPromise, - getAwaitedType: type => getAwaitedType(type), + return map; + }); + + // Cancellation that controls whether or not we can cancel in the middle of type checking. + // In general cancelling is *not* safe for the type checker. We might be in the middle of + // computing something, and we will leave our internals in an inconsistent state. Callers + // who set the cancellation token should catch if a cancellation exception occurs, and + // should throw away and create a new TypeChecker. + // + // Currently we only support setting the cancellation token when getting diagnostics. This + // is because diagnostics can be quite expensive, and we want to allow hosts to bail out if + // they no longer need the information (for example, if the user started editing again). + let cancellationToken: CancellationToken | undefined; + let requestedExternalEmitHelpers: ExternalEmitHelpers; + let externalHelpersModule: Symbol; + + const Symbol = objectAllocator.getSymbolConstructor(); + const Type = objectAllocator.getTypeConstructor(); + const Signature = objectAllocator.getSignatureConstructor(); + + let typeCount = 0; + let symbolCount = 0; + let enumCount = 0; + let totalInstantiationCount = 0; + let instantiationCount = 0; + let instantiationDepth = 0; + let inlineLevel = 0; + let currentNode: Node | undefined; + + const emptySymbols = createSymbolTable(); + const arrayVariances = [VarianceFlags.Covariant]; + + const compilerOptions = host.getCompilerOptions(); + const languageVersion = getEmitScriptTarget(compilerOptions); + const moduleKind = getEmitModuleKind(compilerOptions); + const useDefineForClassFields = getUseDefineForClassFields(compilerOptions); + const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); + const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); + const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); + const strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply"); + const strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); + const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); + const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); + const useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables"); + const keyofStringsOnly = !!compilerOptions.keyofStringsOnly; + const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral; + const exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes; + + const checkBinaryExpression = createCheckBinaryExpression(); + const emitResolver = createResolver(); + const nodeBuilder = createNodeBuilder(); + + const globals = createSymbolTable(); + const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String); + undefinedSymbol.declarations = []; + + const globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); + globalThisSymbol.exports = globals; + globalThisSymbol.declarations = []; + globals.set(globalThisSymbol.escapedName, globalThisSymbol); + + const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String); + const requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String); + + /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ + let apparentArgumentCount: number | undefined; + + // for public members that accept a Node or one of its subtypes, we must guard against + // synthetic nodes created during transformations by calling `getParseTreeNode`. + // for most of these, we perform the guard only on `checker` to avoid any possible + // extra cost of calling `getParseTreeNode` when calling these functions from inside the + // checker. + const checker: TypeChecker = { + getNodeCount: () => sum(host.getSourceFiles(), "nodeCount"), + getIdentifierCount: () => sum(host.getSourceFiles(), "identifierCount"), + getSymbolCount: () => sum(host.getSourceFiles(), "symbolCount") + symbolCount, + getTypeCount: () => typeCount, + getInstantiationCount: () => totalInstantiationCount, + getRelationCacheSizes: () => ({ + assignable: assignableRelation.size, + identity: identityRelation.size, + subtype: subtypeRelation.size, + strictSubtype: strictSubtypeRelation.size, + }), + isUndefinedSymbol: symbol => symbol === undefinedSymbol, + isArgumentsSymbol: symbol => symbol === argumentsSymbol, + isUnknownSymbol: symbol => symbol === unknownSymbol, + getMergedSymbol, + getDiagnostics, + getGlobalDiagnostics, + getRecursionIdentity, + getUnmatchedProperties, + getTypeOfSymbolAtLocation: (symbol, locationIn) => { + const location = getParseTreeNode(locationIn); + return location ? getTypeOfSymbolAtLocation(symbol, location) : errorType; + }, + getSymbolsOfParameterPropertyDeclaration: (parameterIn, parameterName) => { + const parameter = getParseTreeNode(parameterIn, isParameter); + if (parameter === undefined) return Debug.fail("Cannot get symbols of a synthetic parameter that cannot be resolved to a parse-tree node."); + return getSymbolsOfParameterPropertyDeclaration(parameter, escapeLeadingUnderscores(parameterName)); + }, + getDeclaredTypeOfSymbol, + getPropertiesOfType, + getPropertyOfType: (type, name) => getPropertyOfType(type, escapeLeadingUnderscores(name)), + getPrivateIdentifierPropertyOfType: (leftType: Type, name: string, location: Node) => { + const node = getParseTreeNode(location); + if (!node) { + return undefined; + } + const propName = escapeLeadingUnderscores(name); + const lexicallyScopedIdentifier = lookupSymbolForPrivateIdentifierDeclaration(propName, node); + return lexicallyScopedIdentifier ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedIdentifier) : undefined; + }, + getTypeOfPropertyOfType: (type, name) => getTypeOfPropertyOfType(type, escapeLeadingUnderscores(name)), + getIndexInfoOfType: (type, kind) => getIndexInfoOfType(type, kind === IndexKind.String ? stringType : numberType), + getIndexInfosOfType, + getSignaturesOfType, + getIndexTypeOfType: (type, kind) => getIndexTypeOfType(type, kind === IndexKind.String ? stringType : numberType), + getBaseTypes, + getBaseTypeOfLiteralType, + getWidenedType, + getTypeFromTypeNode: nodeIn => { + const node = getParseTreeNode(nodeIn, isTypeNode); + return node ? getTypeFromTypeNode(node) : errorType; + }, + getParameterType: getTypeAtPosition, + getParameterIdentifierNameAtPosition, + getPromisedTypeOfPromise, + getAwaitedType: type => getAwaitedType(type), + getReturnTypeOfSignature, + isNullableType, + getNullableType, + getNonNullableType, + getNonOptionalType: removeOptionalTypeMarker, + getTypeArguments, + typeToTypeNode: nodeBuilder.typeToTypeNode, + indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration, + signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, + symbolToEntityName: nodeBuilder.symbolToEntityName, + symbolToExpression: nodeBuilder.symbolToExpression, + symbolToTypeParameterDeclarations: nodeBuilder.symbolToTypeParameterDeclarations, + symbolToParameterDeclaration: nodeBuilder.symbolToParameterDeclaration, + typeParameterToDeclaration: nodeBuilder.typeParameterToDeclaration, + getSymbolsInScope: (locationIn, meaning) => { + const location = getParseTreeNode(locationIn); + return location ? getSymbolsInScope(location, meaning) : []; + }, + getSymbolAtLocation: nodeIn => { + const node = getParseTreeNode(nodeIn); + // set ignoreErrors: true because any lookups invoked by the API shouldn't cause any new errors + return node ? getSymbolAtLocation(node, /*ignoreErrors*/ true) : undefined; + }, + getIndexInfosAtLocation: nodeIn => { + const node = getParseTreeNode(nodeIn); + return node ? getIndexInfosAtLocation(node) : undefined; + }, + getShorthandAssignmentValueSymbol: nodeIn => { + const node = getParseTreeNode(nodeIn); + return node ? getShorthandAssignmentValueSymbol(node) : undefined; + }, + getExportSpecifierLocalTargetSymbol: nodeIn => { + const node = getParseTreeNode(nodeIn, isExportSpecifier); + return node ? getExportSpecifierLocalTargetSymbol(node) : undefined; + }, + getExportSymbolOfSymbol(symbol) { + return getMergedSymbol(symbol.exportSymbol || symbol); + }, + getTypeAtLocation: nodeIn => { + const node = getParseTreeNode(nodeIn); + return node ? getTypeOfNode(node) : errorType; + }, + getTypeOfAssignmentPattern: nodeIn => { + const node = getParseTreeNode(nodeIn, isAssignmentPattern); + return node && getTypeOfAssignmentPattern(node) || errorType; + }, + getPropertySymbolOfDestructuringAssignment: locationIn => { + const location = getParseTreeNode(locationIn, isIdentifier); + return location ? getPropertySymbolOfDestructuringAssignment(location) : undefined; + }, + signatureToString: (signature, enclosingDeclaration, flags, kind) => { + return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind); + }, + typeToString: (type, enclosingDeclaration, flags) => { + return typeToString(type, getParseTreeNode(enclosingDeclaration), flags); + }, + symbolToString: (symbol, enclosingDeclaration, meaning, flags) => { + return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags); + }, + typePredicateToString: (predicate, enclosingDeclaration, flags) => { + return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags); + }, + writeSignature: (signature, enclosingDeclaration, flags, kind, writer) => { + return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind, writer); + }, + writeType: (type, enclosingDeclaration, flags, writer) => { + return typeToString(type, getParseTreeNode(enclosingDeclaration), flags, writer); + }, + writeSymbol: (symbol, enclosingDeclaration, meaning, flags, writer) => { + return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags, writer); + }, + writeTypePredicate: (predicate, enclosingDeclaration, flags, writer) => { + return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags, writer); + }, + getAugmentedPropertiesOfType, + getRootSymbols, + getSymbolOfExpando, + getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => { + const node = getParseTreeNode(nodeIn, isExpression); + if (!node) { + return undefined; + } + const containingCall = findAncestor(node, isCallLikeExpression); + const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature; + if (contextFlags! & ContextFlags.Completions && containingCall) { + let toMarkSkip = node as Node; + do { + getNodeLinks(toMarkSkip).skipDirectInference = true; + toMarkSkip = toMarkSkip.parent; + } while (toMarkSkip && toMarkSkip !== containingCall); + getNodeLinks(containingCall).resolvedSignature = undefined; + } + const result = getContextualType(node, contextFlags); + if (contextFlags! & ContextFlags.Completions && containingCall) { + let toMarkSkip = node as Node; + do { + getNodeLinks(toMarkSkip).skipDirectInference = undefined; + toMarkSkip = toMarkSkip.parent; + } while (toMarkSkip && toMarkSkip !== containingCall); + getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature; + } + return result; + }, + getContextualTypeForObjectLiteralElement: nodeIn => { + const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike); + return node ? getContextualTypeForObjectLiteralElement(node) : undefined; + }, + getContextualTypeForArgumentAtIndex: (nodeIn, argIndex) => { + const node = getParseTreeNode(nodeIn, isCallLikeExpression); + return node && getContextualTypeForArgumentAtIndex(node, argIndex); + }, + getContextualTypeForJsxAttribute: (nodeIn) => { + const node = getParseTreeNode(nodeIn, isJsxAttributeLike); + return node && getContextualTypeForJsxAttribute(node); + }, + isContextSensitive, + getTypeOfPropertyOfContextualType, + getFullyQualifiedName, + getResolvedSignature: (node, candidatesOutArray, argumentCount) => + getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), + getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => + getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp), + getExpandedParameters, + hasEffectiveRestParameter, + containsArgumentsReference, + getConstantValue: nodeIn => { + const node = getParseTreeNode(nodeIn, canHaveConstantValue); + return node ? getConstantValue(node) : undefined; + }, + isValidPropertyAccess: (nodeIn, propertyName) => { + const node = getParseTreeNode(nodeIn, isPropertyAccessOrQualifiedNameOrImportTypeNode); + return !!node && isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName)); + }, + isValidPropertyAccessForCompletions: (nodeIn, type, property) => { + const node = getParseTreeNode(nodeIn, isPropertyAccessExpression); + return !!node && isValidPropertyAccessForCompletions(node, type, property); + }, + getSignatureFromDeclaration: declarationIn => { + const declaration = getParseTreeNode(declarationIn, isFunctionLike); + return declaration ? getSignatureFromDeclaration(declaration) : undefined; + }, + isImplementationOfOverload: nodeIn => { + const node = getParseTreeNode(nodeIn, isFunctionLike); + return node ? isImplementationOfOverload(node) : undefined; + }, + getImmediateAliasedSymbol, + getAliasedSymbol: resolveAlias, + getEmitResolver, + getExportsOfModule: getExportsOfModuleAsArray, + getExportsAndPropertiesOfModule, + forEachExportAndPropertyOfModule, + getSymbolWalker: createGetSymbolWalker( + getRestTypeOfSignature, + getTypePredicateOfSignature, getReturnTypeOfSignature, - isNullableType, - getNullableType, - getNonNullableType, - getNonOptionalType: removeOptionalTypeMarker, + getBaseTypes, + resolveStructuredTypeMembers, + getTypeOfSymbol, + getResolvedSymbol, + getConstraintOfTypeParameter, + getFirstIdentifier, getTypeArguments, - typeToTypeNode: nodeBuilder.typeToTypeNode, - indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration, - signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, - symbolToEntityName: nodeBuilder.symbolToEntityName, - symbolToExpression: nodeBuilder.symbolToExpression, - symbolToTypeParameterDeclarations: nodeBuilder.symbolToTypeParameterDeclarations, - symbolToParameterDeclaration: nodeBuilder.symbolToParameterDeclaration, - typeParameterToDeclaration: nodeBuilder.typeParameterToDeclaration, - getSymbolsInScope: (locationIn, meaning) => { - const location = getParseTreeNode(locationIn); - return location ? getSymbolsInScope(location, meaning) : []; - }, - getSymbolAtLocation: nodeIn => { - const node = getParseTreeNode(nodeIn); - // set ignoreErrors: true because any lookups invoked by the API shouldn't cause any new errors - return node ? getSymbolAtLocation(node, /*ignoreErrors*/ true) : undefined; - }, - getIndexInfosAtLocation: nodeIn => { - const node = getParseTreeNode(nodeIn); - return node ? getIndexInfosAtLocation(node) : undefined; - }, - getShorthandAssignmentValueSymbol: nodeIn => { - const node = getParseTreeNode(nodeIn); - return node ? getShorthandAssignmentValueSymbol(node) : undefined; - }, - getExportSpecifierLocalTargetSymbol: nodeIn => { - const node = getParseTreeNode(nodeIn, isExportSpecifier); - return node ? getExportSpecifierLocalTargetSymbol(node) : undefined; - }, - getExportSymbolOfSymbol(symbol) { - return getMergedSymbol(symbol.exportSymbol || symbol); - }, - getTypeAtLocation: nodeIn => { - const node = getParseTreeNode(nodeIn); - return node ? getTypeOfNode(node) : errorType; - }, - getTypeOfAssignmentPattern: nodeIn => { - const node = getParseTreeNode(nodeIn, isAssignmentPattern); - return node && getTypeOfAssignmentPattern(node) || errorType; - }, - getPropertySymbolOfDestructuringAssignment: locationIn => { - const location = getParseTreeNode(locationIn, isIdentifier); - return location ? getPropertySymbolOfDestructuringAssignment(location) : undefined; - }, - signatureToString: (signature, enclosingDeclaration, flags, kind) => { - return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind); - }, - typeToString: (type, enclosingDeclaration, flags) => { - return typeToString(type, getParseTreeNode(enclosingDeclaration), flags); - }, - symbolToString: (symbol, enclosingDeclaration, meaning, flags) => { - return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags); - }, - typePredicateToString: (predicate, enclosingDeclaration, flags) => { - return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags); - }, - writeSignature: (signature, enclosingDeclaration, flags, kind, writer) => { - return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind, writer); - }, - writeType: (type, enclosingDeclaration, flags, writer) => { - return typeToString(type, getParseTreeNode(enclosingDeclaration), flags, writer); - }, - writeSymbol: (symbol, enclosingDeclaration, meaning, flags, writer) => { - return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags, writer); - }, - writeTypePredicate: (predicate, enclosingDeclaration, flags, writer) => { - return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags, writer); - }, - getAugmentedPropertiesOfType, - getRootSymbols, - getSymbolOfExpando, - getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => { - const node = getParseTreeNode(nodeIn, isExpression); - if (!node) { - return undefined; - } - const containingCall = findAncestor(node, isCallLikeExpression); - const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature; - if (contextFlags! & ContextFlags.Completions && containingCall) { - let toMarkSkip = node as Node; - do { - getNodeLinks(toMarkSkip).skipDirectInference = true; - toMarkSkip = toMarkSkip.parent; - } while (toMarkSkip && toMarkSkip !== containingCall); - getNodeLinks(containingCall).resolvedSignature = undefined; - } - const result = getContextualType(node, contextFlags); - if (contextFlags! & ContextFlags.Completions && containingCall) { - let toMarkSkip = node as Node; - do { - getNodeLinks(toMarkSkip).skipDirectInference = undefined; - toMarkSkip = toMarkSkip.parent; - } while (toMarkSkip && toMarkSkip !== containingCall); - getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature; - } - return result; - }, - getContextualTypeForObjectLiteralElement: nodeIn => { - const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike); - return node ? getContextualTypeForObjectLiteralElement(node) : undefined; - }, - getContextualTypeForArgumentAtIndex: (nodeIn, argIndex) => { - const node = getParseTreeNode(nodeIn, isCallLikeExpression); - return node && getContextualTypeForArgumentAtIndex(node, argIndex); - }, - getContextualTypeForJsxAttribute: (nodeIn) => { - const node = getParseTreeNode(nodeIn, isJsxAttributeLike); - return node && getContextualTypeForJsxAttribute(node); - }, - isContextSensitive, - getTypeOfPropertyOfContextualType, - getFullyQualifiedName, - getResolvedSignature: (node, candidatesOutArray, argumentCount) => - getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), - getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => - getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp), - getExpandedParameters, - hasEffectiveRestParameter, - containsArgumentsReference, - getConstantValue: nodeIn => { - const node = getParseTreeNode(nodeIn, canHaveConstantValue); - return node ? getConstantValue(node) : undefined; - }, - isValidPropertyAccess: (nodeIn, propertyName) => { - const node = getParseTreeNode(nodeIn, isPropertyAccessOrQualifiedNameOrImportTypeNode); - return !!node && isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName)); - }, - isValidPropertyAccessForCompletions: (nodeIn, type, property) => { - const node = getParseTreeNode(nodeIn, isPropertyAccessExpression); - return !!node && isValidPropertyAccessForCompletions(node, type, property); - }, - getSignatureFromDeclaration: declarationIn => { - const declaration = getParseTreeNode(declarationIn, isFunctionLike); - return declaration ? getSignatureFromDeclaration(declaration) : undefined; - }, - isImplementationOfOverload: nodeIn => { - const node = getParseTreeNode(nodeIn, isFunctionLike); - return node ? isImplementationOfOverload(node) : undefined; - }, - getImmediateAliasedSymbol, - getAliasedSymbol: resolveAlias, - getEmitResolver, - getExportsOfModule: getExportsOfModuleAsArray, - getExportsAndPropertiesOfModule, - forEachExportAndPropertyOfModule, - getSymbolWalker: createGetSymbolWalker( - getRestTypeOfSignature, - getTypePredicateOfSignature, - getReturnTypeOfSignature, - getBaseTypes, - resolveStructuredTypeMembers, - getTypeOfSymbol, - getResolvedSymbol, - getConstraintOfTypeParameter, - getFirstIdentifier, - getTypeArguments, - ), - getAmbientModules, - getJsxIntrinsicTagNamesAt, - isOptionalParameter: nodeIn => { - const node = getParseTreeNode(nodeIn, isParameter); - return node ? isOptionalParameter(node) : false; - }, - tryGetMemberInModuleExports: (name, symbol) => tryGetMemberInModuleExports(escapeLeadingUnderscores(name), symbol), - tryGetMemberInModuleExportsAndProperties: (name, symbol) => tryGetMemberInModuleExportsAndProperties(escapeLeadingUnderscores(name), symbol), - tryFindAmbientModule: moduleName => tryFindAmbientModule(moduleName, /*withAugmentations*/ true), - tryFindAmbientModuleWithoutAugmentations: moduleName => { - // we deliberately exclude augmentations - // since we are only interested in declarations of the module itself - return tryFindAmbientModule(moduleName, /*withAugmentations*/ false); - }, - getApparentType, - getUnionType, - isTypeAssignableTo, - createAnonymousType, - createSignature, - createSymbol, - createIndexInfo, - getAnyType: () => anyType, - getStringType: () => stringType, - getNumberType: () => numberType, - createPromiseType, - createArrayType, - getElementTypeOfArrayType, - getBooleanType: () => booleanType, - getFalseType: (fresh?) => fresh ? falseType : regularFalseType, - getTrueType: (fresh?) => fresh ? trueType : regularTrueType, - getVoidType: () => voidType, - getUndefinedType: () => undefinedType, - getNullType: () => nullType, - getESSymbolType: () => esSymbolType, - getNeverType: () => neverType, - getOptionalType: () => optionalType, - getPromiseType: () => getGlobalPromiseType(/*reportErrors*/ false), - getPromiseLikeType: () => getGlobalPromiseLikeType(/*reportErrors*/ false), - isSymbolAccessible, - isArrayType, - isTupleType, - isArrayLikeType, - isTypeInvalidDueToUnionDiscriminant, - getExactOptionalProperties, - getAllPossiblePropertiesOfTypes, - getSuggestedSymbolForNonexistentProperty, - getSuggestionForNonexistentProperty, - getSuggestedSymbolForNonexistentJSXAttribute, - getSuggestedSymbolForNonexistentSymbol: (location, name, meaning) => getSuggestedSymbolForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), - getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), - getSuggestedSymbolForNonexistentModule, - getSuggestionForNonexistentExport, - getSuggestedSymbolForNonexistentClassMember, - getBaseConstraintOfType, - getDefaultFromTypeParameter: type => type && type.flags & TypeFlags.TypeParameter ? getDefaultFromTypeParameter(type as TypeParameter) : undefined, - resolveName(name, location, meaning, excludeGlobals) { - return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false, excludeGlobals); - }, - getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)), - getJsxFragmentFactory: n => { - const jsxFragmentFactory = getJsxFragmentFactoryEntity(n); - return jsxFragmentFactory && unescapeLeadingUnderscores(getFirstIdentifier(jsxFragmentFactory).escapedText); - }, - getAccessibleSymbolChain, - getTypePredicateOfSignature, - resolveExternalModuleName: moduleSpecifierIn => { - const moduleSpecifier = getParseTreeNode(moduleSpecifierIn, isExpression); - return moduleSpecifier && resolveExternalModuleName(moduleSpecifier, moduleSpecifier, /*ignoreErrors*/ true); - }, - resolveExternalModuleSymbol, - tryGetThisTypeAt: (nodeIn, includeGlobalThis) => { - const node = getParseTreeNode(nodeIn); - return node && tryGetThisTypeAt(node, includeGlobalThis); - }, - getTypeArgumentConstraint: nodeIn => { - const node = getParseTreeNode(nodeIn, isTypeNode); - return node && getTypeArgumentConstraint(node); - }, - getSuggestionDiagnostics: (fileIn, ct) => { - const file = getParseTreeNode(fileIn, isSourceFile) || Debug.fail("Could not determine parsed source file."); - if (skipTypeChecking(file, compilerOptions, host)) { - return emptyArray; - } - - let diagnostics: DiagnosticWithLocation[] | undefined; - try { - // Record the cancellation token so it can be checked later on during checkSourceElement. - // Do this in a finally block so we can ensure that it gets reset back to nothing after - // this call is done. - cancellationToken = ct; - - // Ensure file is type checked - checkSourceFile(file); - Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked)); - - diagnostics = addRange(diagnostics, suggestionDiagnostics.getDiagnostics(file.fileName)); - checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(file), (containingNode, kind, diag) => { - if (!containsParseError(containingNode) && !unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { - (diagnostics || (diagnostics = [])).push({ ...diag, category: DiagnosticCategory.Suggestion }); - } - }); - - return diagnostics || emptyArray; - } - finally { - cancellationToken = undefined; - } - }, - - runWithCancellationToken: (token, callback) => { - try { - cancellationToken = token; - return callback(checker); - } - finally { - cancellationToken = undefined; - } - }, + ), + getAmbientModules, + getJsxIntrinsicTagNamesAt, + isOptionalParameter: nodeIn => { + const node = getParseTreeNode(nodeIn, isParameter); + return node ? isOptionalParameter(node) : false; + }, + tryGetMemberInModuleExports: (name, symbol) => tryGetMemberInModuleExports(escapeLeadingUnderscores(name), symbol), + tryGetMemberInModuleExportsAndProperties: (name, symbol) => tryGetMemberInModuleExportsAndProperties(escapeLeadingUnderscores(name), symbol), + tryFindAmbientModule: moduleName => tryFindAmbientModule(moduleName, /*withAugmentations*/ true), + tryFindAmbientModuleWithoutAugmentations: moduleName => { + // we deliberately exclude augmentations + // since we are only interested in declarations of the module itself + return tryFindAmbientModule(moduleName, /*withAugmentations*/ false); + }, + getApparentType, + getUnionType, + isTypeAssignableTo, + createAnonymousType, + createSignature, + createSymbol, + createIndexInfo, + getAnyType: () => anyType, + getStringType: () => stringType, + getNumberType: () => numberType, + createPromiseType, + createArrayType, + getElementTypeOfArrayType, + getBooleanType: () => booleanType, + getFalseType: (fresh?) => fresh ? falseType : regularFalseType, + getTrueType: (fresh?) => fresh ? trueType : regularTrueType, + getVoidType: () => voidType, + getUndefinedType: () => undefinedType, + getNullType: () => nullType, + getESSymbolType: () => esSymbolType, + getNeverType: () => neverType, + getOptionalType: () => optionalType, + getPromiseType: () => getGlobalPromiseType(/*reportErrors*/ false), + getPromiseLikeType: () => getGlobalPromiseLikeType(/*reportErrors*/ false), + isSymbolAccessible, + isArrayType, + isTupleType, + isArrayLikeType, + isTypeInvalidDueToUnionDiscriminant, + getExactOptionalProperties, + getAllPossiblePropertiesOfTypes, + getSuggestedSymbolForNonexistentProperty, + getSuggestionForNonexistentProperty, + getSuggestedSymbolForNonexistentJSXAttribute, + getSuggestedSymbolForNonexistentSymbol: (location, name, meaning) => getSuggestedSymbolForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), + getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), + getSuggestedSymbolForNonexistentModule, + getSuggestionForNonexistentExport, + getSuggestedSymbolForNonexistentClassMember, + getBaseConstraintOfType, + getDefaultFromTypeParameter: type => type && type.flags & TypeFlags.TypeParameter ? getDefaultFromTypeParameter(type as TypeParameter) : undefined, + resolveName(name, location, meaning, excludeGlobals) { + return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false, excludeGlobals); + }, + getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)), + getJsxFragmentFactory: n => { + const jsxFragmentFactory = getJsxFragmentFactoryEntity(n); + return jsxFragmentFactory && unescapeLeadingUnderscores(getFirstIdentifier(jsxFragmentFactory).escapedText); + }, + getAccessibleSymbolChain, + getTypePredicateOfSignature, + resolveExternalModuleName: moduleSpecifierIn => { + const moduleSpecifier = getParseTreeNode(moduleSpecifierIn, isExpression); + return moduleSpecifier && resolveExternalModuleName(moduleSpecifier, moduleSpecifier, /*ignoreErrors*/ true); + }, + resolveExternalModuleSymbol, + tryGetThisTypeAt: (nodeIn, includeGlobalThis) => { + const node = getParseTreeNode(nodeIn); + return node && tryGetThisTypeAt(node, includeGlobalThis); + }, + getTypeArgumentConstraint: nodeIn => { + const node = getParseTreeNode(nodeIn, isTypeNode); + return node && getTypeArgumentConstraint(node); + }, + getSuggestionDiagnostics: (fileIn, ct) => { + const file = getParseTreeNode(fileIn, isSourceFile) || Debug.fail("Could not determine parsed source file."); + if (skipTypeChecking(file, compilerOptions, host)) { + return emptyArray; + } - getLocalTypeParametersOfClassOrInterfaceOrTypeAlias, - isDeclarationVisible, - isPropertyAccessible, - getTypeOnlyAliasDeclaration, - getMemberOverrideModifierStatus, - }; + let diagnostics: DiagnosticWithLocation[] | undefined; + try { + // Record the cancellation token so it can be checked later on during checkSourceElement. + // Do this in a finally block so we can ensure that it gets reset back to nothing after + // this call is done. + cancellationToken = ct; - function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined { - const node = getParseTreeNode(nodeIn, isCallLikeExpression); - apparentArgumentCount = argumentCount; - const res = node ? getResolvedSignature(node, candidatesOutArray, checkMode) : undefined; - apparentArgumentCount = undefined; - return res; - } + // Ensure file is type checked + checkSourceFile(file); + Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked)); - const tupleTypes = new Map(); - const unionTypes = new Map(); - const intersectionTypes = new Map(); - const stringLiteralTypes = new Map(); - const numberLiteralTypes = new Map(); - const bigIntLiteralTypes = new Map(); - const enumLiteralTypes = new Map(); - const indexedAccessTypes = new Map(); - const templateLiteralTypes = new Map(); - const stringMappingTypes = new Map(); - const substitutionTypes = new Map(); - const subtypeReductionCache = new Map(); - const evolvingArrayTypes: EvolvingArrayType[] = []; - const undefinedProperties: SymbolTable = new Map(); - - const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); - const resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); - const unresolvedSymbols = new Map(); - const errorTypes = new Map(); - - const anyType = createIntrinsicType(TypeFlags.Any, "any"); - const autoType = createIntrinsicType(TypeFlags.Any, "any"); - const wildcardType = createIntrinsicType(TypeFlags.Any, "any"); - const errorType = createIntrinsicType(TypeFlags.Any, "error"); - const unresolvedType = createIntrinsicType(TypeFlags.Any, "unresolved"); - const nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType); - const intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic"); - const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); - const nonNullUnknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); - const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); - const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); - const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined"); - const missingType = exactOptionalPropertyTypes ? createIntrinsicType(TypeFlags.Undefined, "undefined") : undefinedType; - const nullType = createIntrinsicType(TypeFlags.Null, "null"); - const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); - const stringType = createIntrinsicType(TypeFlags.String, "string"); - const numberType = createIntrinsicType(TypeFlags.Number, "number"); - const bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); - const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; - const regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; - const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; - const regularTrueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; - trueType.regularType = regularTrueType; - trueType.freshType = trueType; - regularTrueType.regularType = regularTrueType; - regularTrueType.freshType = trueType; - falseType.regularType = regularFalseType; - falseType.freshType = falseType; - regularFalseType.regularType = regularFalseType; - regularFalseType.freshType = falseType; - const booleanType = getUnionType([regularFalseType, regularTrueType]); - const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); - const voidType = createIntrinsicType(TypeFlags.Void, "void"); - const neverType = createIntrinsicType(TypeFlags.Never, "never"); - const silentNeverType = createIntrinsicType(TypeFlags.Never, "never"); - const nonInferrableType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType); - const implicitNeverType = createIntrinsicType(TypeFlags.Never, "never"); - const unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never"); - const nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); - const stringOrNumberType = getUnionType([stringType, numberType]); - const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); - const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType; - const numberOrBigIntType = getUnionType([numberType, bigintType]); - const templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType; - - const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t); - const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t); - - const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - const emptyJsxObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; - - const emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); - emptyTypeLiteralSymbol.members = createSymbolTable(); - const emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, emptyArray); - - const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType; - emptyGenericType.instantiations = new Map(); - - const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated - // in getPropagatingFlagsOfTypes, and it is checked in inferFromTypes. - anyFunctionType.objectFlags |= ObjectFlags.NonInferrableType; - - const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - const resolvingDefaultType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - - const markerSuperType = createTypeParameter(); - const markerSubType = createTypeParameter(); - markerSubType.constraint = markerSuperType; - const markerOtherType = createTypeParameter(); - - const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); - - const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - - const enumNumberIndexInfo = createIndexInfo(numberType, stringType, /*isReadonly*/ true); - - const iterationTypesCache = new Map(); // cache for common IterationTypes instances - const noIterationTypes: IterationTypes = { - get yieldType(): Type { return Debug.fail("Not supported"); }, - get returnType(): Type { return Debug.fail("Not supported"); }, - get nextType(): Type { return Debug.fail("Not supported"); }, - }; + diagnostics = addRange(diagnostics, suggestionDiagnostics.getDiagnostics(file.fileName)); + checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(file), (containingNode, kind, diag) => { + if (!containsParseError(containingNode) && !unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { + (diagnostics || (diagnostics = [])).push({ ...diag, category: DiagnosticCategory.Suggestion }); + } + }); - const anyIterationTypes = createIterationTypes(anyType, anyType, anyType); - const anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType); - const defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`. - - const asyncIterationTypesResolver: IterationTypesResolver = { - iterableCacheKey: "iterationTypesOfAsyncIterable", - iteratorCacheKey: "iterationTypesOfAsyncIterator", - iteratorSymbolName: "asyncIterator", - getGlobalIteratorType: getGlobalAsyncIteratorType, - getGlobalIterableType: getGlobalAsyncIterableType, - getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType, - getGlobalGeneratorType: getGlobalAsyncGeneratorType, - resolveIterationType: getAwaitedType, - mustHaveANextMethodDiagnostic: Diagnostics.An_async_iterator_must_have_a_next_method, - mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_async_iterator_must_be_a_method, - mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_async_iterator_must_be_a_promise_for_a_type_with_a_value_property, - }; + return diagnostics || emptyArray; + } + finally { + cancellationToken = undefined; + } + }, - const syncIterationTypesResolver: IterationTypesResolver = { - iterableCacheKey: "iterationTypesOfIterable", - iteratorCacheKey: "iterationTypesOfIterator", - iteratorSymbolName: "iterator", - getGlobalIteratorType, - getGlobalIterableType, - getGlobalIterableIteratorType, - getGlobalGeneratorType, - resolveIterationType: (type, _errorNode) => type, - mustHaveANextMethodDiagnostic: Diagnostics.An_iterator_must_have_a_next_method, - mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_iterator_must_be_a_method, - mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_iterator_must_have_a_value_property, - }; + runWithCancellationToken: (token, callback) => { + try { + cancellationToken = token; + return callback(checker); + } + finally { + cancellationToken = undefined; + } + }, + + getLocalTypeParametersOfClassOrInterfaceOrTypeAlias, + isDeclarationVisible, + isPropertyAccessible, + getTypeOnlyAliasDeclaration, + getMemberOverrideModifierStatus, + }; + + function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined { + const node = getParseTreeNode(nodeIn, isCallLikeExpression); + apparentArgumentCount = argumentCount; + const res = node ? getResolvedSignature(node, candidatesOutArray, checkMode) : undefined; + apparentArgumentCount = undefined; + return res; + } - interface DuplicateInfoForSymbol { - readonly firstFileLocations: Declaration[]; - readonly secondFileLocations: Declaration[]; - readonly isBlockScoped: boolean; - } - interface DuplicateInfoForFiles { - readonly firstFile: SourceFile; - readonly secondFile: SourceFile; - /** Key is symbol name. */ - readonly conflictingSymbols: ESMap; - } - /** Key is "/path/to/a.ts|/path/to/b.ts". */ - let amalgamatedDuplicates: ESMap | undefined; - const reverseMappedCache = new Map(); - let inInferTypeForHomomorphicMappedType = false; - let ambientModulesCache: Symbol[] | undefined; - /** - * List of every ambient module with a "*" wildcard. - * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. - * This is only used if there is no exact match. - */ - let patternAmbientModules: PatternAmbientModule[]; - let patternAmbientModuleAugmentations: ESMap | undefined; - - let globalObjectType: ObjectType; - let globalFunctionType: ObjectType; - let globalCallableFunctionType: ObjectType; - let globalNewableFunctionType: ObjectType; - let globalArrayType: GenericType; - let globalReadonlyArrayType: GenericType; - let globalStringType: ObjectType; - let globalNumberType: ObjectType; - let globalBooleanType: ObjectType; - let globalRegExpType: ObjectType; - let globalThisType: GenericType; - let anyArrayType: Type; - let autoArrayType: Type; - let anyReadonlyArrayType: Type; - let deferredGlobalNonNullableTypeAlias: Symbol; - - // The library files are only loaded when the feature is used. - // This allows users to just specify library files they want to used through --lib - // and they will not get an error from not having unrelated library files - let deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; - let deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined; - let deferredGlobalESSymbolType: ObjectType | undefined; - let deferredGlobalTypedPropertyDescriptorType: GenericType; - let deferredGlobalPromiseType: GenericType | undefined; - let deferredGlobalPromiseLikeType: GenericType | undefined; - let deferredGlobalPromiseConstructorSymbol: Symbol | undefined; - let deferredGlobalPromiseConstructorLikeType: ObjectType | undefined; - let deferredGlobalIterableType: GenericType | undefined; - let deferredGlobalIteratorType: GenericType | undefined; - let deferredGlobalIterableIteratorType: GenericType | undefined; - let deferredGlobalGeneratorType: GenericType | undefined; - let deferredGlobalIteratorYieldResultType: GenericType | undefined; - let deferredGlobalIteratorReturnResultType: GenericType | undefined; - let deferredGlobalAsyncIterableType: GenericType | undefined; - let deferredGlobalAsyncIteratorType: GenericType | undefined; - let deferredGlobalAsyncIterableIteratorType: GenericType | undefined; - let deferredGlobalAsyncGeneratorType: GenericType | undefined; - let deferredGlobalTemplateStringsArrayType: ObjectType | undefined; - let deferredGlobalImportMetaType: ObjectType; - let deferredGlobalImportMetaExpressionType: ObjectType; - let deferredGlobalImportCallOptionsType: ObjectType | undefined; - let deferredGlobalExtractSymbol: Symbol | undefined; - let deferredGlobalOmitSymbol: Symbol | undefined; - let deferredGlobalAwaitedSymbol: Symbol | undefined; - let deferredGlobalBigIntType: ObjectType | undefined; - - const allPotentiallyUnusedIdentifiers = new Map(); // key is file name - - let flowLoopStart = 0; - let flowLoopCount = 0; - let sharedFlowCount = 0; - let flowAnalysisDisabled = false; - let flowInvocationCount = 0; - let lastFlowNode: FlowNode | undefined; - let lastFlowNodeReachable: boolean; - let flowTypeCache: Type[] | undefined; - - const emptyStringType = getStringLiteralType(""); - const zeroType = getNumberLiteralType(0); - const zeroBigIntType = getBigIntLiteralType({ negative: false, base10Value: "0" }); - - const resolutionTargets: TypeSystemEntity[] = []; - const resolutionResults: boolean[] = []; - const resolutionPropertyNames: TypeSystemPropertyName[] = []; - - let suggestionCount = 0; - const maximumSuggestionCount = 10; - const mergedSymbols: Symbol[] = []; - const symbolLinks: SymbolLinks[] = []; - const nodeLinks: NodeLinks[] = []; - const flowLoopCaches: ESMap[] = []; - const flowLoopNodes: FlowNode[] = []; - const flowLoopKeys: string[] = []; - const flowLoopTypes: Type[][] = []; - const sharedFlowNodes: FlowNode[] = []; - const sharedFlowTypes: FlowType[] = []; - const flowNodeReachable: (boolean | undefined)[] = []; - const flowNodePostSuper: (boolean | undefined)[] = []; - const potentialThisCollisions: Node[] = []; - const potentialNewTargetCollisions: Node[] = []; - const potentialWeakMapSetCollisions: Node[] = []; - const potentialReflectCollisions: Node[] = []; - const awaitedTypeStack: number[] = []; - - const diagnostics = createDiagnosticCollection(); - const suggestionDiagnostics = createDiagnosticCollection(); - - const typeofTypesByName: ReadonlyESMap = new Map(getEntries({ - string: stringType, - number: numberType, - bigint: bigintType, - boolean: booleanType, - symbol: esSymbolType, - undefined: undefinedType - })); - const typeofType = createTypeofType(); - - let _jsxNamespace: __String; - let _jsxFactoryEntity: EntityName | undefined; - let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; - - const subtypeRelation = new Map(); - const strictSubtypeRelation = new Map(); - const assignableRelation = new Map(); - const comparableRelation = new Map(); - const identityRelation = new Map(); - const enumRelation = new Map(); - - const builtinGlobals = createSymbolTable(); - builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol); - - // Extensions suggested for path imports when module resolution is node12 or higher. - // The first element of each tuple is the extension a file has. - // The second element of each tuple is the extension that should be used in a path import. - // e.g. if we want to import file `foo.mts`, we should write `import {} from "./foo.mjs". - const suggestedExtensions: [string, string][] = [ - [".mts", ".mjs"], - [".ts", ".js"], - [".cts", ".cjs"], - [".mjs", ".mjs"], - [".js", ".js"], - [".cjs", ".cjs"], - [".tsx", compilerOptions.jsx === JsxEmit.Preserve ? ".jsx" : ".js"], - [".jsx", ".jsx"], - [".json", ".json"], - ]; - - initializeTypeChecker(); - - return checker; - - function getJsxNamespace(location: Node | undefined): __String { - if (location) { - const file = getSourceFileOfNode(location); - if (file) { - if (isJsxOpeningFragment(location)) { - if (file.localJsxFragmentNamespace) { - return file.localJsxFragmentNamespace; - } - const jsxFragmentPragma = file.pragmas.get("jsxfrag"); - if (jsxFragmentPragma) { - const chosenPragma = isArray(jsxFragmentPragma) ? jsxFragmentPragma[0] : jsxFragmentPragma; - file.localJsxFragmentFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); - visitNode(file.localJsxFragmentFactory, markAsSynthetic); - if (file.localJsxFragmentFactory) { - return file.localJsxFragmentNamespace = getFirstIdentifier(file.localJsxFragmentFactory).escapedText; - } - } - const entity = getJsxFragmentFactoryEntity(location); - if (entity) { - file.localJsxFragmentFactory = entity; - return file.localJsxFragmentNamespace = getFirstIdentifier(entity).escapedText; - } + const tupleTypes = new Map(); + const unionTypes = new Map(); + const intersectionTypes = new Map(); + const stringLiteralTypes = new Map(); + const numberLiteralTypes = new Map(); + const bigIntLiteralTypes = new Map(); + const enumLiteralTypes = new Map(); + const indexedAccessTypes = new Map(); + const templateLiteralTypes = new Map(); + const stringMappingTypes = new Map(); + const substitutionTypes = new Map(); + const subtypeReductionCache = new Map(); + const evolvingArrayTypes: EvolvingArrayType[] = []; + const undefinedProperties: SymbolTable = new Map(); + + const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); + const resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); + const unresolvedSymbols = new Map(); + const errorTypes = new Map(); + + const anyType = createIntrinsicType(TypeFlags.Any, "any"); + const autoType = createIntrinsicType(TypeFlags.Any, "any"); + const wildcardType = createIntrinsicType(TypeFlags.Any, "any"); + const errorType = createIntrinsicType(TypeFlags.Any, "error"); + const unresolvedType = createIntrinsicType(TypeFlags.Any, "unresolved"); + const nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType); + const intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic"); + const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); + const nonNullUnknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); + const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); + const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + const missingType = exactOptionalPropertyTypes ? createIntrinsicType(TypeFlags.Undefined, "undefined") : undefinedType; + const nullType = createIntrinsicType(TypeFlags.Null, "null"); + const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); + const stringType = createIntrinsicType(TypeFlags.String, "string"); + const numberType = createIntrinsicType(TypeFlags.Number, "number"); + const bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); + const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; + const regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; + const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; + const regularTrueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; + trueType.regularType = regularTrueType; + trueType.freshType = trueType; + regularTrueType.regularType = regularTrueType; + regularTrueType.freshType = trueType; + falseType.regularType = regularFalseType; + falseType.freshType = falseType; + regularFalseType.regularType = regularFalseType; + regularFalseType.freshType = falseType; + const booleanType = getUnionType([regularFalseType, regularTrueType]); + const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); + const voidType = createIntrinsicType(TypeFlags.Void, "void"); + const neverType = createIntrinsicType(TypeFlags.Never, "never"); + const silentNeverType = createIntrinsicType(TypeFlags.Never, "never"); + const nonInferrableType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType); + const implicitNeverType = createIntrinsicType(TypeFlags.Never, "never"); + const unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never"); + const nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); + const stringOrNumberType = getUnionType([stringType, numberType]); + const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); + const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType; + const numberOrBigIntType = getUnionType([numberType, bigintType]); + const templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType; + + const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t); + const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t); + + const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + const emptyJsxObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; + + const emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + emptyTypeLiteralSymbol.members = createSymbolTable(); + const emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, emptyArray); + + const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType; + emptyGenericType.instantiations = new Map(); + + const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated + // in getPropagatingFlagsOfTypes, and it is checked in inferFromTypes. + anyFunctionType.objectFlags |= ObjectFlags.NonInferrableType; + + const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + const resolvingDefaultType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + + const markerSuperType = createTypeParameter(); + const markerSubType = createTypeParameter(); + markerSubType.constraint = markerSuperType; + const markerOtherType = createTypeParameter(); + + const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); + + const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + + const enumNumberIndexInfo = createIndexInfo(numberType, stringType, /*isReadonly*/ true); + + const iterationTypesCache = new Map(); // cache for common IterationTypes instances + const noIterationTypes: IterationTypes = { + get yieldType(): Type { return Debug.fail("Not supported"); }, + get returnType(): Type { return Debug.fail("Not supported"); }, + get nextType(): Type { return Debug.fail("Not supported"); }, + }; + + const anyIterationTypes = createIterationTypes(anyType, anyType, anyType); + const anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType); + const defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`. + + const asyncIterationTypesResolver: IterationTypesResolver = { + iterableCacheKey: "iterationTypesOfAsyncIterable", + iteratorCacheKey: "iterationTypesOfAsyncIterator", + iteratorSymbolName: "asyncIterator", + getGlobalIteratorType: getGlobalAsyncIteratorType, + getGlobalIterableType: getGlobalAsyncIterableType, + getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType, + getGlobalGeneratorType: getGlobalAsyncGeneratorType, + resolveIterationType: getAwaitedType, + mustHaveANextMethodDiagnostic: Diagnostics.An_async_iterator_must_have_a_next_method, + mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_async_iterator_must_be_a_method, + mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_async_iterator_must_be_a_promise_for_a_type_with_a_value_property, + }; + + const syncIterationTypesResolver: IterationTypesResolver = { + iterableCacheKey: "iterationTypesOfIterable", + iteratorCacheKey: "iterationTypesOfIterator", + iteratorSymbolName: "iterator", + getGlobalIteratorType, + getGlobalIterableType, + getGlobalIterableIteratorType, + getGlobalGeneratorType, + resolveIterationType: (type, _errorNode) => type, + mustHaveANextMethodDiagnostic: Diagnostics.An_iterator_must_have_a_next_method, + mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_iterator_must_be_a_method, + mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_iterator_must_have_a_value_property, + }; + + interface DuplicateInfoForSymbol { + readonly firstFileLocations: Declaration[]; + readonly secondFileLocations: Declaration[]; + readonly isBlockScoped: boolean; + } + interface DuplicateInfoForFiles { + readonly firstFile: SourceFile; + readonly secondFile: SourceFile; + /** Key is symbol name. */ + readonly conflictingSymbols: ESMap; + } + /** Key is "/path/to/a.ts|/path/to/b.ts". */ + let amalgamatedDuplicates: ESMap | undefined; + const reverseMappedCache = new Map(); + let inInferTypeForHomomorphicMappedType = false; + let ambientModulesCache: Symbol[] | undefined; + /** + * List of every ambient module with a "*" wildcard. + * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. + * This is only used if there is no exact match. + */ + let patternAmbientModules: PatternAmbientModule[]; + let patternAmbientModuleAugmentations: ESMap | undefined; + + let globalObjectType: ObjectType; + let globalFunctionType: ObjectType; + let globalCallableFunctionType: ObjectType; + let globalNewableFunctionType: ObjectType; + let globalArrayType: GenericType; + let globalReadonlyArrayType: GenericType; + let globalStringType: ObjectType; + let globalNumberType: ObjectType; + let globalBooleanType: ObjectType; + let globalRegExpType: ObjectType; + let globalThisType: GenericType; + let anyArrayType: Type; + let autoArrayType: Type; + let anyReadonlyArrayType: Type; + let deferredGlobalNonNullableTypeAlias: Symbol; + + // The library files are only loaded when the feature is used. + // This allows users to just specify library files they want to used through --lib + // and they will not get an error from not having unrelated library files + let deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; + let deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined; + let deferredGlobalESSymbolType: ObjectType | undefined; + let deferredGlobalTypedPropertyDescriptorType: GenericType; + let deferredGlobalPromiseType: GenericType | undefined; + let deferredGlobalPromiseLikeType: GenericType | undefined; + let deferredGlobalPromiseConstructorSymbol: Symbol | undefined; + let deferredGlobalPromiseConstructorLikeType: ObjectType | undefined; + let deferredGlobalIterableType: GenericType | undefined; + let deferredGlobalIteratorType: GenericType | undefined; + let deferredGlobalIterableIteratorType: GenericType | undefined; + let deferredGlobalGeneratorType: GenericType | undefined; + let deferredGlobalIteratorYieldResultType: GenericType | undefined; + let deferredGlobalIteratorReturnResultType: GenericType | undefined; + let deferredGlobalAsyncIterableType: GenericType | undefined; + let deferredGlobalAsyncIteratorType: GenericType | undefined; + let deferredGlobalAsyncIterableIteratorType: GenericType | undefined; + let deferredGlobalAsyncGeneratorType: GenericType | undefined; + let deferredGlobalTemplateStringsArrayType: ObjectType | undefined; + let deferredGlobalImportMetaType: ObjectType; + let deferredGlobalImportMetaExpressionType: ObjectType; + let deferredGlobalImportCallOptionsType: ObjectType | undefined; + let deferredGlobalExtractSymbol: Symbol | undefined; + let deferredGlobalOmitSymbol: Symbol | undefined; + let deferredGlobalAwaitedSymbol: Symbol | undefined; + let deferredGlobalBigIntType: ObjectType | undefined; + + const allPotentiallyUnusedIdentifiers = new Map(); // key is file name + + let flowLoopStart = 0; + let flowLoopCount = 0; + let sharedFlowCount = 0; + let flowAnalysisDisabled = false; + let flowInvocationCount = 0; + let lastFlowNode: FlowNode | undefined; + let lastFlowNodeReachable: boolean; + let flowTypeCache: Type[] | undefined; + + const emptyStringType = getStringLiteralType(""); + const zeroType = getNumberLiteralType(0); + const zeroBigIntType = getBigIntLiteralType({ negative: false, base10Value: "0" }); + + const resolutionTargets: TypeSystemEntity[] = []; + const resolutionResults: boolean[] = []; + const resolutionPropertyNames: TypeSystemPropertyName[] = []; + + let suggestionCount = 0; + const maximumSuggestionCount = 10; + const mergedSymbols: Symbol[] = []; + const symbolLinks: SymbolLinks[] = []; + const nodeLinks: NodeLinks[] = []; + const flowLoopCaches: ESMap[] = []; + const flowLoopNodes: FlowNode[] = []; + const flowLoopKeys: string[] = []; + const flowLoopTypes: Type[][] = []; + const sharedFlowNodes: FlowNode[] = []; + const sharedFlowTypes: FlowType[] = []; + const flowNodeReachable: (boolean | undefined)[] = []; + const flowNodePostSuper: (boolean | undefined)[] = []; + const potentialThisCollisions: Node[] = []; + const potentialNewTargetCollisions: Node[] = []; + const potentialWeakMapSetCollisions: Node[] = []; + const potentialReflectCollisions: Node[] = []; + const awaitedTypeStack: number[] = []; + + const diagnostics = createDiagnosticCollection(); + const suggestionDiagnostics = createDiagnosticCollection(); + + const typeofTypesByName: ReadonlyESMap = new Map(getEntries({ + string: stringType, + number: numberType, + bigint: bigintType, + boolean: booleanType, + symbol: esSymbolType, + undefined: undefinedType + })); + const typeofType = createTypeofType(); + + let _jsxNamespace: __String; + let _jsxFactoryEntity: EntityName | undefined; + let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; + + const subtypeRelation = new Map(); + const strictSubtypeRelation = new Map(); + const assignableRelation = new Map(); + const comparableRelation = new Map(); + const identityRelation = new Map(); + const enumRelation = new Map(); + + const builtinGlobals = createSymbolTable(); + builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol); + + // Extensions suggested for path imports when module resolution is node12 or higher. + // The first element of each tuple is the extension a file has. + // The second element of each tuple is the extension that should be used in a path import. + // e.g. if we want to import file `foo.mts`, we should write `import {} from "./foo.mjs". + const suggestedExtensions: [string, string][] = [ + [".mts", ".mjs"], + [".ts", ".js"], + [".cts", ".cjs"], + [".mjs", ".mjs"], + [".js", ".js"], + [".cjs", ".cjs"], + [".tsx", compilerOptions.jsx === JsxEmit.Preserve ? ".jsx" : ".js"], + [".jsx", ".jsx"], + [".json", ".json"], + ]; + + initializeTypeChecker(); + + return checker; + + function getJsxNamespace(location: Node | undefined): __String { + if (location) { + const file = getSourceFileOfNode(location); + if (file) { + if (isJsxOpeningFragment(location)) { + if (file.localJsxFragmentNamespace) { + return file.localJsxFragmentNamespace; } - else { - const localJsxNamespace = getLocalJsxNamespace(file); - if (localJsxNamespace) { - return file.localJsxNamespace = localJsxNamespace; + const jsxFragmentPragma = file.pragmas.get("jsxfrag"); + if (jsxFragmentPragma) { + const chosenPragma = isArray(jsxFragmentPragma) ? jsxFragmentPragma[0] : jsxFragmentPragma; + file.localJsxFragmentFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); + visitNode(file.localJsxFragmentFactory, markAsSynthetic); + if (file.localJsxFragmentFactory) { + return file.localJsxFragmentNamespace = getFirstIdentifier(file.localJsxFragmentFactory).escapedText; } } - } - } - if (!_jsxNamespace) { - _jsxNamespace = "React" as __String; - if (compilerOptions.jsxFactory) { - _jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion); - visitNode(_jsxFactoryEntity, markAsSynthetic); - if (_jsxFactoryEntity) { - _jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText; + const entity = getJsxFragmentFactoryEntity(location); + if (entity) { + file.localJsxFragmentFactory = entity; + return file.localJsxFragmentNamespace = getFirstIdentifier(entity).escapedText; } } - else if (compilerOptions.reactNamespace) { - _jsxNamespace = escapeLeadingUnderscores(compilerOptions.reactNamespace); + else { + const localJsxNamespace = getLocalJsxNamespace(file); + if (localJsxNamespace) { + return file.localJsxNamespace = localJsxNamespace; + } } } - if (!_jsxFactoryEntity) { - _jsxFactoryEntity = factory.createQualifiedName(factory.createIdentifier(unescapeLeadingUnderscores(_jsxNamespace)), "createElement"); - } - return _jsxNamespace; } - - function getLocalJsxNamespace(file: SourceFile): __String | undefined { - if (file.localJsxNamespace) { - return file.localJsxNamespace; - } - const jsxPragma = file.pragmas.get("jsx"); - if (jsxPragma) { - const chosenPragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; - file.localJsxFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); - visitNode(file.localJsxFactory, markAsSynthetic); - if (file.localJsxFactory) { - return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; + if (!_jsxNamespace) { + _jsxNamespace = "React" as __String; + if (compilerOptions.jsxFactory) { + _jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion); + visitNode(_jsxFactoryEntity, markAsSynthetic); + if (_jsxFactoryEntity) { + _jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText; } } + else if (compilerOptions.reactNamespace) { + _jsxNamespace = escapeLeadingUnderscores(compilerOptions.reactNamespace); + } } - - function markAsSynthetic(node: Node): VisitResult { - setTextRangePosEnd(node, -1, -1); - return visitEachChild(node, markAsSynthetic, nullTransformationContext); + if (!_jsxFactoryEntity) { + _jsxFactoryEntity = factory.createQualifiedName(factory.createIdentifier(unescapeLeadingUnderscores(_jsxNamespace)), "createElement"); } + return _jsxNamespace; + } - function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { - // Ensure we have all the type information in place for this file so that all the - // emitter questions of this resolver will return the right information. - getDiagnostics(sourceFile, cancellationToken); - return emitResolver; + function getLocalJsxNamespace(file: SourceFile): __String | undefined { + if (file.localJsxNamespace) { + return file.localJsxNamespace; } - - function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { - const diagnostic = location - ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) - : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); - const existing = diagnostics.lookup(diagnostic); - if (existing) { - return existing; - } - else { - diagnostics.add(diagnostic); - return diagnostic; + const jsxPragma = file.pragmas.get("jsx"); + if (jsxPragma) { + const chosenPragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; + file.localJsxFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); + visitNode(file.localJsxFactory, markAsSynthetic); + if (file.localJsxFactory) { + return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; } } + } - function errorSkippedOn(key: keyof CompilerOptions, location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { - const diagnostic = error(location, message, arg0, arg1, arg2, arg3); - diagnostic.skippedOn = key; - return diagnostic; - } + function markAsSynthetic(node: Node): VisitResult { + setTextRangePosEnd(node, -1, -1); + return visitEachChild(node, markAsSynthetic, nullTransformationContext); + } - function createError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { - return location - ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) - : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); - } + function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { + // Ensure we have all the type information in place for this file so that all the + // emitter questions of this resolver will return the right information. + getDiagnostics(sourceFile, cancellationToken); + return emitResolver; + } - function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { - const diagnostic = createError(location, message, arg0, arg1, arg2, arg3); + function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { + const diagnostic = location + ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) + : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); + const existing = diagnostics.lookup(diagnostic); + if (existing) { + return existing; + } + else { diagnostics.add(diagnostic); return diagnostic; } + } - function addErrorOrSuggestion(isError: boolean, diagnostic: Diagnostic) { - if (isError) { - diagnostics.add(diagnostic); - } - else { - suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); - } + function errorSkippedOn(key: keyof CompilerOptions, location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { + const diagnostic = error(location, message, arg0, arg1, arg2, arg3); + diagnostic.skippedOn = key; + return diagnostic; + } + + function createError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { + return location + ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) + : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); + } + + function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { + const diagnostic = createError(location, message, arg0, arg1, arg2, arg3); + diagnostics.add(diagnostic); + return diagnostic; + } + + function addErrorOrSuggestion(isError: boolean, diagnostic: Diagnostic) { + if (isError) { + diagnostics.add(diagnostic); } - function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { - // Pseudo-synthesized input node - if (location.pos < 0 || location.end < 0) { - if (!isError) { - return; // Drop suggestions (we have no span to suggest on) - } - // Issue errors globally - const file = getSourceFileOfNode(location); - addErrorOrSuggestion(isError, "message" in message ? createFileDiagnostic(file, 0, 0, message, arg0, arg1, arg2, arg3) : createDiagnosticForFileFromMessageChain(file, message)); // eslint-disable-line no-in-operator - return; - } - addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message)); // eslint-disable-line no-in-operator + else { + suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); } + } + function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { + // Pseudo-synthesized input node + if (location.pos < 0 || location.end < 0) { + if (!isError) { + return; // Drop suggestions (we have no span to suggest on) + } + // Issue errors globally + const file = getSourceFileOfNode(location); + addErrorOrSuggestion(isError, "message" in message ? createFileDiagnostic(file, 0, 0, message, arg0, arg1, arg2, arg3) : createDiagnosticForFileFromMessageChain(file, message)); // eslint-disable-line no-in-operator + return; + } + addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message)); // eslint-disable-line no-in-operator + } - function errorAndMaybeSuggestAwait( - location: Node, - maybeMissingAwait: boolean, - message: DiagnosticMessage, - arg0?: string | number | undefined, arg1?: string | number | undefined, arg2?: string | number | undefined, arg3?: string | number | undefined): Diagnostic { - const diagnostic = error(location, message, arg0, arg1, arg2, arg3); - if (maybeMissingAwait) { - const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await); - addRelatedInfo(diagnostic, related); - } - return diagnostic; - } + function errorAndMaybeSuggestAwait( + location: Node, + maybeMissingAwait: boolean, + message: DiagnosticMessage, + arg0?: string | number | undefined, arg1?: string | number | undefined, arg2?: string | number | undefined, arg3?: string | number | undefined): Diagnostic { + const diagnostic = error(location, message, arg0, arg1, arg2, arg3); + if (maybeMissingAwait) { + const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await); + addRelatedInfo(diagnostic, related); + } + return diagnostic; + } - function addDeprecatedSuggestionWorker(declarations: Node | Node[], diagnostic: DiagnosticWithLocation) { - const deprecatedTag = Array.isArray(declarations) ? forEach(declarations, getJSDocDeprecatedTag) : getJSDocDeprecatedTag(declarations); - if (deprecatedTag) { - addRelatedInfo( - diagnostic, - createDiagnosticForNode(deprecatedTag, Diagnostics.The_declaration_was_marked_as_deprecated_here) - ); - } - // We call `addRelatedInfo()` before adding the diagnostic to prevent duplicates. - suggestionDiagnostics.add(diagnostic); - return diagnostic; + function addDeprecatedSuggestionWorker(declarations: Node | Node[], diagnostic: DiagnosticWithLocation) { + const deprecatedTag = Array.isArray(declarations) ? forEach(declarations, getJSDocDeprecatedTag) : getJSDocDeprecatedTag(declarations); + if (deprecatedTag) { + addRelatedInfo( + diagnostic, + createDiagnosticForNode(deprecatedTag, Diagnostics.The_declaration_was_marked_as_deprecated_here) + ); } + // We call `addRelatedInfo()` before adding the diagnostic to prevent duplicates. + suggestionDiagnostics.add(diagnostic); + return diagnostic; + } - function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) { - const diagnostic = createDiagnosticForNode(location, Diagnostics._0_is_deprecated, deprecatedEntity); - return addDeprecatedSuggestionWorker(declarations, diagnostic); - } + function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) { + const diagnostic = createDiagnosticForNode(location, Diagnostics._0_is_deprecated, deprecatedEntity); + return addDeprecatedSuggestionWorker(declarations, diagnostic); + } - function addDeprecatedSuggestionWithSignature(location: Node, declaration: Node, deprecatedEntity: string | undefined, signatureString: string) { - const diagnostic = deprecatedEntity - ? createDiagnosticForNode(location, Diagnostics.The_signature_0_of_1_is_deprecated, signatureString, deprecatedEntity) - : createDiagnosticForNode(location, Diagnostics._0_is_deprecated, signatureString); - return addDeprecatedSuggestionWorker(declaration, diagnostic); - } + function addDeprecatedSuggestionWithSignature(location: Node, declaration: Node, deprecatedEntity: string | undefined, signatureString: string) { + const diagnostic = deprecatedEntity + ? createDiagnosticForNode(location, Diagnostics.The_signature_0_of_1_is_deprecated, signatureString, deprecatedEntity) + : createDiagnosticForNode(location, Diagnostics._0_is_deprecated, signatureString); + return addDeprecatedSuggestionWorker(declaration, diagnostic); + } - function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { - symbolCount++; - const symbol = (new Symbol(flags | SymbolFlags.Transient, name) as TransientSymbol); - symbol.checkFlags = checkFlags || 0; - return symbol; - } + function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { + symbolCount++; + const symbol = (new Symbol(flags | SymbolFlags.Transient, name) as TransientSymbol); + symbol.checkFlags = checkFlags || 0; + return symbol; + } - function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags { - let result: SymbolFlags = 0; - if (flags & SymbolFlags.BlockScopedVariable) result |= SymbolFlags.BlockScopedVariableExcludes; - if (flags & SymbolFlags.FunctionScopedVariable) result |= SymbolFlags.FunctionScopedVariableExcludes; - if (flags & SymbolFlags.Property) result |= SymbolFlags.PropertyExcludes; - if (flags & SymbolFlags.EnumMember) result |= SymbolFlags.EnumMemberExcludes; - if (flags & SymbolFlags.Function) result |= SymbolFlags.FunctionExcludes; - if (flags & SymbolFlags.Class) result |= SymbolFlags.ClassExcludes; - if (flags & SymbolFlags.Interface) result |= SymbolFlags.InterfaceExcludes; - if (flags & SymbolFlags.RegularEnum) result |= SymbolFlags.RegularEnumExcludes; - if (flags & SymbolFlags.ConstEnum) result |= SymbolFlags.ConstEnumExcludes; - if (flags & SymbolFlags.ValueModule) result |= SymbolFlags.ValueModuleExcludes; - if (flags & SymbolFlags.Method) result |= SymbolFlags.MethodExcludes; - if (flags & SymbolFlags.GetAccessor) result |= SymbolFlags.GetAccessorExcludes; - if (flags & SymbolFlags.SetAccessor) result |= SymbolFlags.SetAccessorExcludes; - if (flags & SymbolFlags.TypeParameter) result |= SymbolFlags.TypeParameterExcludes; - if (flags & SymbolFlags.TypeAlias) result |= SymbolFlags.TypeAliasExcludes; - if (flags & SymbolFlags.Alias) result |= SymbolFlags.AliasExcludes; - return result; - } + function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags { + let result: SymbolFlags = 0; + if (flags & SymbolFlags.BlockScopedVariable) result |= SymbolFlags.BlockScopedVariableExcludes; + if (flags & SymbolFlags.FunctionScopedVariable) result |= SymbolFlags.FunctionScopedVariableExcludes; + if (flags & SymbolFlags.Property) result |= SymbolFlags.PropertyExcludes; + if (flags & SymbolFlags.EnumMember) result |= SymbolFlags.EnumMemberExcludes; + if (flags & SymbolFlags.Function) result |= SymbolFlags.FunctionExcludes; + if (flags & SymbolFlags.Class) result |= SymbolFlags.ClassExcludes; + if (flags & SymbolFlags.Interface) result |= SymbolFlags.InterfaceExcludes; + if (flags & SymbolFlags.RegularEnum) result |= SymbolFlags.RegularEnumExcludes; + if (flags & SymbolFlags.ConstEnum) result |= SymbolFlags.ConstEnumExcludes; + if (flags & SymbolFlags.ValueModule) result |= SymbolFlags.ValueModuleExcludes; + if (flags & SymbolFlags.Method) result |= SymbolFlags.MethodExcludes; + if (flags & SymbolFlags.GetAccessor) result |= SymbolFlags.GetAccessorExcludes; + if (flags & SymbolFlags.SetAccessor) result |= SymbolFlags.SetAccessorExcludes; + if (flags & SymbolFlags.TypeParameter) result |= SymbolFlags.TypeParameterExcludes; + if (flags & SymbolFlags.TypeAlias) result |= SymbolFlags.TypeAliasExcludes; + if (flags & SymbolFlags.Alias) result |= SymbolFlags.AliasExcludes; + return result; + } - function recordMergedSymbol(target: Symbol, source: Symbol) { - if (!source.mergeId) { - source.mergeId = nextMergeId; - nextMergeId++; - } - mergedSymbols[source.mergeId] = target; + function recordMergedSymbol(target: Symbol, source: Symbol) { + if (!source.mergeId) { + source.mergeId = nextMergeId; + nextMergeId++; } + mergedSymbols[source.mergeId] = target; + } - function cloneSymbol(symbol: Symbol): Symbol { - const result = createSymbol(symbol.flags, symbol.escapedName); - result.declarations = symbol.declarations ? symbol.declarations.slice() : []; - result.parent = symbol.parent; - if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; - if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; - if (symbol.members) result.members = new Map(symbol.members); - if (symbol.exports) result.exports = new Map(symbol.exports); - recordMergedSymbol(result, symbol); - return result; - } + function cloneSymbol(symbol: Symbol): Symbol { + const result = createSymbol(symbol.flags, symbol.escapedName); + result.declarations = symbol.declarations ? symbol.declarations.slice() : []; + result.parent = symbol.parent; + if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; + if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; + if (symbol.members) result.members = new Map(symbol.members); + if (symbol.exports) result.exports = new Map(symbol.exports); + recordMergedSymbol(result, symbol); + return result; + } - /** - * Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it. - * If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it. - */ - function mergeSymbol(target: Symbol, source: Symbol, unidirectional = false): Symbol { - if (!(target.flags & getExcludedSymbolFlags(source.flags)) || - (source.flags | target.flags) & SymbolFlags.Assignment) { - if (source === target) { - // This can happen when an export assigned namespace exports something also erroneously exported at the top level - // See `declarationFileNoCrashOnExtraExportModifier` for an example - return target; - } - if (!(target.flags & SymbolFlags.Transient)) { - const resolvedTarget = resolveSymbol(target); - if (resolvedTarget === unknownSymbol) { - return source; - } - target = cloneSymbol(resolvedTarget); - } - // Javascript static-property-assignment declarations always merge, even though they are also values - if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) { - // reset flag when merging instantiated module into value module that has only const enums - target.constEnumOnlyModule = false; - } - target.flags |= source.flags; - if (source.valueDeclaration) { - setValueDeclaration(target, source.valueDeclaration); - } - addRange(target.declarations, source.declarations); - if (source.members) { - if (!target.members) target.members = createSymbolTable(); - mergeSymbolTable(target.members, source.members, unidirectional); - } - if (source.exports) { - if (!target.exports) target.exports = createSymbolTable(); - mergeSymbolTable(target.exports, source.exports, unidirectional); - } - if (!unidirectional) { - recordMergedSymbol(target, source); - } - } - else if (target.flags & SymbolFlags.NamespaceModule) { - // Do not report an error when merging `var globalThis` with the built-in `globalThis`, - // as we will already report a "Declaration name conflicts..." error, and this error - // won't make much sense. - if (target !== globalThisSymbol) { - error( - source.declarations && getNameOfDeclaration(source.declarations[0]), - Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, - symbolToString(target)); - } - } - else { // error - const isEitherEnum = !!(target.flags & SymbolFlags.Enum || source.flags & SymbolFlags.Enum); - const isEitherBlockScoped = !!(target.flags & SymbolFlags.BlockScopedVariable || source.flags & SymbolFlags.BlockScopedVariable); - const message = isEitherEnum - ? Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations - : isEitherBlockScoped - ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 - : Diagnostics.Duplicate_identifier_0; - const sourceSymbolFile = source.declarations && getSourceFileOfNode(source.declarations[0]); - const targetSymbolFile = target.declarations && getSourceFileOfNode(target.declarations[0]); - const symbolName = symbolToString(source); - - // Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch - if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) { - const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile; - const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile; - const filesDuplicates = getOrUpdate(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, () => - ({ firstFile, secondFile, conflictingSymbols: new Map() } as DuplicateInfoForFiles)); - const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, () => - ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] } as DuplicateInfoForSymbol)); - addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); - addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); - } - else { - addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target); - addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source); - } + /** + * Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it. + * If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it. + */ + function mergeSymbol(target: Symbol, source: Symbol, unidirectional = false): Symbol { + if (!(target.flags & getExcludedSymbolFlags(source.flags)) || + (source.flags | target.flags) & SymbolFlags.Assignment) { + if (source === target) { + // This can happen when an export assigned namespace exports something also erroneously exported at the top level + // See `declarationFileNoCrashOnExtraExportModifier` for an example + return target; } - return target; - - function addDuplicateLocations(locs: Declaration[], symbol: Symbol): void { - if (symbol.declarations) { - for (const decl of symbol.declarations) { - pushIfUnique(locs, decl); - } + if (!(target.flags & SymbolFlags.Transient)) { + const resolvedTarget = resolveSymbol(target); + if (resolvedTarget === unknownSymbol) { + return source; } + target = cloneSymbol(resolvedTarget); + } + // Javascript static-property-assignment declarations always merge, even though they are also values + if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) { + // reset flag when merging instantiated module into value module that has only const enums + target.constEnumOnlyModule = false; + } + target.flags |= source.flags; + if (source.valueDeclaration) { + setValueDeclaration(target, source.valueDeclaration); + } + addRange(target.declarations, source.declarations); + if (source.members) { + if (!target.members) target.members = createSymbolTable(); + mergeSymbolTable(target.members, source.members, unidirectional); + } + if (source.exports) { + if (!target.exports) target.exports = createSymbolTable(); + mergeSymbolTable(target.exports, source.exports, unidirectional); + } + if (!unidirectional) { + recordMergedSymbol(target, source); } } - - function addDuplicateDeclarationErrorsForSymbols(target: Symbol, message: DiagnosticMessage, symbolName: string, source: Symbol) { - forEach(target.declarations, node => { - addDuplicateDeclarationError(node, message, symbolName, source.declarations); - }); + else if (target.flags & SymbolFlags.NamespaceModule) { + // Do not report an error when merging `var globalThis` with the built-in `globalThis`, + // as we will already report a "Declaration name conflicts..." error, and this error + // won't make much sense. + if (target !== globalThisSymbol) { + error( + source.declarations && getNameOfDeclaration(source.declarations[0]), + Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, + symbolToString(target)); + } + } + else { // error + const isEitherEnum = !!(target.flags & SymbolFlags.Enum || source.flags & SymbolFlags.Enum); + const isEitherBlockScoped = !!(target.flags & SymbolFlags.BlockScopedVariable || source.flags & SymbolFlags.BlockScopedVariable); + const message = isEitherEnum + ? Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations + : isEitherBlockScoped + ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 + : Diagnostics.Duplicate_identifier_0; + const sourceSymbolFile = source.declarations && getSourceFileOfNode(source.declarations[0]); + const targetSymbolFile = target.declarations && getSourceFileOfNode(target.declarations[0]); + const symbolName = symbolToString(source); + + // Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch + if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) { + const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile; + const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile; + const filesDuplicates = getOrUpdate(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, () => + ({ firstFile, secondFile, conflictingSymbols: new Map() } as DuplicateInfoForFiles)); + const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, () => + ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] } as DuplicateInfoForSymbol)); + addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); + addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); + } + else { + addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target); + addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source); + } } + return target; - function addDuplicateDeclarationError(node: Declaration, message: DiagnosticMessage, symbolName: string, relatedNodes: readonly Declaration[] | undefined) { - const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node; - const err = lookupOrIssueError(errorNode, message, symbolName); - for (const relatedNode of relatedNodes || emptyArray) { - const adjustedNode = (getExpandoInitializer(relatedNode, /*isPrototypeAssignment*/ false) ? getNameOfExpando(relatedNode) : getNameOfDeclaration(relatedNode)) || relatedNode; - if (adjustedNode === errorNode) continue; - err.relatedInformation = err.relatedInformation || []; - const leadingMessage = createDiagnosticForNode(adjustedNode, Diagnostics._0_was_also_declared_here, symbolName); - const followOnMessage = createDiagnosticForNode(adjustedNode, Diagnostics.and_here); - if (length(err.relatedInformation) >= 5 || some(err.relatedInformation, r => compareDiagnostics(r, followOnMessage) === Comparison.EqualTo || compareDiagnostics(r, leadingMessage) === Comparison.EqualTo)) continue; - addRelatedInfo(err, !length(err.relatedInformation) ? leadingMessage : followOnMessage); + function addDuplicateLocations(locs: Declaration[], symbol: Symbol): void { + if (symbol.declarations) { + for (const decl of symbol.declarations) { + pushIfUnique(locs, decl); + } } } + } - function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined { - if (!first?.size) return second; - if (!second?.size) return first; - const combined = createSymbolTable(); - mergeSymbolTable(combined, first); - mergeSymbolTable(combined, second); - return combined; + function addDuplicateDeclarationErrorsForSymbols(target: Symbol, message: DiagnosticMessage, symbolName: string, source: Symbol) { + forEach(target.declarations, node => { + addDuplicateDeclarationError(node, message, symbolName, source.declarations); + }); + } + + function addDuplicateDeclarationError(node: Declaration, message: DiagnosticMessage, symbolName: string, relatedNodes: readonly Declaration[] | undefined) { + const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node; + const err = lookupOrIssueError(errorNode, message, symbolName); + for (const relatedNode of relatedNodes || emptyArray) { + const adjustedNode = (getExpandoInitializer(relatedNode, /*isPrototypeAssignment*/ false) ? getNameOfExpando(relatedNode) : getNameOfDeclaration(relatedNode)) || relatedNode; + if (adjustedNode === errorNode) continue; + err.relatedInformation = err.relatedInformation || []; + const leadingMessage = createDiagnosticForNode(adjustedNode, Diagnostics._0_was_also_declared_here, symbolName); + const followOnMessage = createDiagnosticForNode(adjustedNode, Diagnostics.and_here); + if (length(err.relatedInformation) >= 5 || some(err.relatedInformation, r => compareDiagnostics(r, followOnMessage) === Comparison.EqualTo || compareDiagnostics(r, leadingMessage) === Comparison.EqualTo)) continue; + addRelatedInfo(err, !length(err.relatedInformation) ? leadingMessage : followOnMessage); } + } - function mergeSymbolTable(target: SymbolTable, source: SymbolTable, unidirectional = false) { - source.forEach((sourceSymbol, id) => { - const targetSymbol = target.get(id); - target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : sourceSymbol); - }); + function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined { + if (!first?.size) return second; + if (!second?.size) return first; + const combined = createSymbolTable(); + mergeSymbolTable(combined, first); + mergeSymbolTable(combined, second); + return combined; + } + + function mergeSymbolTable(target: SymbolTable, source: SymbolTable, unidirectional = false) { + source.forEach((sourceSymbol, id) => { + const targetSymbol = target.get(id); + target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : sourceSymbol); + }); + } + + function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void { + const moduleAugmentation = moduleName.parent as ModuleDeclaration; + if (moduleAugmentation.symbol.declarations?.[0] !== moduleAugmentation) { + // this is a combined symbol for multiple augmentations within the same file. + // its symbol already has accumulated information for all declarations + // so we need to add it just once - do the work only for first declaration + Debug.assert(moduleAugmentation.symbol.declarations!.length > 1); + return; } - function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void { - const moduleAugmentation = moduleName.parent as ModuleDeclaration; - if (moduleAugmentation.symbol.declarations?.[0] !== moduleAugmentation) { - // this is a combined symbol for multiple augmentations within the same file. - // its symbol already has accumulated information for all declarations - // so we need to add it just once - do the work only for first declaration - Debug.assert(moduleAugmentation.symbol.declarations!.length > 1); + if (isGlobalScopeAugmentation(moduleAugmentation)) { + mergeSymbolTable(globals, moduleAugmentation.symbol.exports!); + } + else { + // find a module that about to be augmented + // do not validate names of augmentations that are defined in ambient context + const moduleNotFoundError = !(moduleName.parent.parent.flags & NodeFlags.Ambient) + ? Diagnostics.Invalid_module_name_in_augmentation_module_0_cannot_be_found + : undefined; + let mainModule = resolveExternalModuleNameWorker(moduleName, moduleName, moduleNotFoundError, /*isForAugmentation*/ true); + if (!mainModule) { return; } - - if (isGlobalScopeAugmentation(moduleAugmentation)) { - mergeSymbolTable(globals, moduleAugmentation.symbol.exports!); - } - else { - // find a module that about to be augmented - // do not validate names of augmentations that are defined in ambient context - const moduleNotFoundError = !(moduleName.parent.parent.flags & NodeFlags.Ambient) - ? Diagnostics.Invalid_module_name_in_augmentation_module_0_cannot_be_found - : undefined; - let mainModule = resolveExternalModuleNameWorker(moduleName, moduleName, moduleNotFoundError, /*isForAugmentation*/ true); - if (!mainModule) { - return; - } - // obtain item referenced by 'export=' - mainModule = resolveExternalModuleSymbol(mainModule); - if (mainModule.flags & SymbolFlags.Namespace) { - // If we're merging an augmentation to a pattern ambient module, we want to - // perform the merge unidirectionally from the augmentation ('a.foo') to - // the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you - // all the exports both from the pattern and from the augmentation, but - // 'getMergedSymbol()' on *.foo only gives you exports from *.foo. - if (some(patternAmbientModules, module => mainModule === module.symbol)) { - const merged = mergeSymbol(moduleAugmentation.symbol, mainModule, /*unidirectional*/ true); - if (!patternAmbientModuleAugmentations) { - patternAmbientModuleAugmentations = new Map(); - } - // moduleName will be a StringLiteral since this is not `declare global`. - patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged); + // obtain item referenced by 'export=' + mainModule = resolveExternalModuleSymbol(mainModule); + if (mainModule.flags & SymbolFlags.Namespace) { + // If we're merging an augmentation to a pattern ambient module, we want to + // perform the merge unidirectionally from the augmentation ('a.foo') to + // the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you + // all the exports both from the pattern and from the augmentation, but + // 'getMergedSymbol()' on *.foo only gives you exports from *.foo. + if (some(patternAmbientModules, module => mainModule === module.symbol)) { + const merged = mergeSymbol(moduleAugmentation.symbol, mainModule, /*unidirectional*/ true); + if (!patternAmbientModuleAugmentations) { + patternAmbientModuleAugmentations = new Map(); } - else { - if (mainModule.exports?.get(InternalSymbolName.ExportStar) && moduleAugmentation.symbol.exports?.size) { - // We may need to merge the module augmentation's exports into the target symbols of the resolved exports - const resolvedExports = getResolvedMembersOrExportsOfSymbol(mainModule, MembersOrExportsResolutionKind.resolvedExports); - for (const [key, value] of arrayFrom(moduleAugmentation.symbol.exports.entries())) { - if (resolvedExports.has(key) && !mainModule.exports.has(key)) { - mergeSymbol(resolvedExports.get(key)!, value); - } + // moduleName will be a StringLiteral since this is not `declare global`. + patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged); + } + else { + if (mainModule.exports?.get(InternalSymbolName.ExportStar) && moduleAugmentation.symbol.exports?.size) { + // We may need to merge the module augmentation's exports into the target symbols of the resolved exports + const resolvedExports = getResolvedMembersOrExportsOfSymbol(mainModule, MembersOrExportsResolutionKind.resolvedExports); + for (const [key, value] of arrayFrom(moduleAugmentation.symbol.exports.entries())) { + if (resolvedExports.has(key) && !mainModule.exports.has(key)) { + mergeSymbol(resolvedExports.get(key)!, value); } } - mergeSymbol(mainModule, moduleAugmentation.symbol); } + mergeSymbol(mainModule, moduleAugmentation.symbol); } - else { - // moduleName will be a StringLiteral since this is not `declare global`. - error(moduleName, Diagnostics.Cannot_augment_module_0_because_it_resolves_to_a_non_module_entity, (moduleName as StringLiteral).text); - } + } + else { + // moduleName will be a StringLiteral since this is not `declare global`. + error(moduleName, Diagnostics.Cannot_augment_module_0_because_it_resolves_to_a_non_module_entity, (moduleName as StringLiteral).text); } } + } - function addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) { - source.forEach((sourceSymbol, id) => { - const targetSymbol = target.get(id); - if (targetSymbol) { - // Error on redeclarations - forEach(targetSymbol.declarations, addDeclarationDiagnostic(unescapeLeadingUnderscores(id), message)); - } - else { - target.set(id, sourceSymbol); - } - }); - - function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) { - return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id)); + function addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) { + source.forEach((sourceSymbol, id) => { + const targetSymbol = target.get(id); + if (targetSymbol) { + // Error on redeclarations + forEach(targetSymbol.declarations, addDeclarationDiagnostic(unescapeLeadingUnderscores(id), message)); } - } + else { + target.set(id, sourceSymbol); + } + }); - function getSymbolLinks(symbol: Symbol): SymbolLinks { - if (symbol.flags & SymbolFlags.Transient) return symbol as TransientSymbol; - const id = getSymbolId(symbol); - return symbolLinks[id] || (symbolLinks[id] = new (SymbolLinks as any)()); + function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) { + return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id)); } + } - function getNodeLinks(node: Node): NodeLinks { - const nodeId = getNodeId(node); - return nodeLinks[nodeId] || (nodeLinks[nodeId] = new (NodeLinks as any)()); - } + function getSymbolLinks(symbol: Symbol): SymbolLinks { + if (symbol.flags & SymbolFlags.Transient) return symbol as TransientSymbol; + const id = getSymbolId(symbol); + return symbolLinks[id] || (symbolLinks[id] = new (SymbolLinks as any)()); + } - function isGlobalSourceFile(node: Node) { - return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(node as SourceFile); - } + function getNodeLinks(node: Node): NodeLinks { + const nodeId = getNodeId(node); + return nodeLinks[nodeId] || (nodeLinks[nodeId] = new (NodeLinks as any)()); + } - function getSymbol(symbols: SymbolTable, name: __String, meaning: SymbolFlags): Symbol | undefined { - if (meaning) { - const symbol = getMergedSymbol(symbols.get(name)); - if (symbol) { - Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (symbol.flags & meaning) { + function isGlobalSourceFile(node: Node) { + return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(node as SourceFile); + } + + function getSymbol(symbols: SymbolTable, name: __String, meaning: SymbolFlags): Symbol | undefined { + if (meaning) { + const symbol = getMergedSymbol(symbols.get(name)); + if (symbol) { + Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); + if (symbol.flags & meaning) { + return symbol; + } + if (symbol.flags & SymbolFlags.Alias) { + const target = resolveAlias(symbol); + // Unknown symbol means an error occurred in alias resolution, treat it as positive answer to avoid cascading errors + if (target === unknownSymbol || target.flags & meaning) { return symbol; } - if (symbol.flags & SymbolFlags.Alias) { - const target = resolveAlias(symbol); - // Unknown symbol means an error occurred in alias resolution, treat it as positive answer to avoid cascading errors - if (target === unknownSymbol || target.flags & meaning) { - return symbol; - } - } } } - // return undefined if we can't find a symbol. } + // return undefined if we can't find a symbol. + } - /** - * Get symbols that represent parameter-property-declaration as parameter and as property declaration - * @param parameter a parameterDeclaration node - * @param parameterName a name of the parameter to get the symbols for. - * @return a tuple of two symbols - */ - function getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: __String): [Symbol, Symbol] { - const constructorDeclaration = parameter.parent; - const classDeclaration = parameter.parent.parent; - - const parameterSymbol = getSymbol(constructorDeclaration.locals!, parameterName, SymbolFlags.Value); - const propertySymbol = getSymbol(getMembersOfSymbol(classDeclaration.symbol), parameterName, SymbolFlags.Value); + /** + * Get symbols that represent parameter-property-declaration as parameter and as property declaration + * @param parameter a parameterDeclaration node + * @param parameterName a name of the parameter to get the symbols for. + * @return a tuple of two symbols + */ + function getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: __String): [Symbol, Symbol] { + const constructorDeclaration = parameter.parent; + const classDeclaration = parameter.parent.parent; - if (parameterSymbol && propertySymbol) { - return [parameterSymbol, propertySymbol]; - } + const parameterSymbol = getSymbol(constructorDeclaration.locals!, parameterName, SymbolFlags.Value); + const propertySymbol = getSymbol(getMembersOfSymbol(classDeclaration.symbol), parameterName, SymbolFlags.Value); - return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration"); + if (parameterSymbol && propertySymbol) { + return [parameterSymbol, propertySymbol]; } - function isBlockScopedNameDeclaredBeforeUse(declaration: Declaration, usage: Node): boolean { - const declarationFile = getSourceFileOfNode(declaration); - const useFile = getSourceFileOfNode(usage); - const declContainer = getEnclosingBlockScopeContainer(declaration); - if (declarationFile !== useFile) { - if ((moduleKind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) || - (!outFile(compilerOptions)) || - isInTypeQuery(usage) || - declaration.flags & NodeFlags.Ambient) { - // nodes are in different files and order cannot be determined - return true; - } - // declaration is after usage - // can be legal if usage is deferred (i.e. inside function or in initializer of instance property) - if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { - return true; - } - const sourceFiles = host.getSourceFiles(); - return sourceFiles.indexOf(declarationFile) <= sourceFiles.indexOf(useFile); - } - - if (declaration.pos <= usage.pos && !(isPropertyDeclaration(declaration) && isThisProperty(usage.parent) && !declaration.initializer && !declaration.exclamationToken)) { - // declaration is before usage - if (declaration.kind === SyntaxKind.BindingElement) { - // still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2]) - const errorBindingElement = getAncestor(usage, SyntaxKind.BindingElement) as BindingElement; - if (errorBindingElement) { - return findAncestor(errorBindingElement, isBindingElement) !== findAncestor(declaration, isBindingElement) || - declaration.pos < errorBindingElement.pos; - } - // or it might be illegal if usage happens before parent variable is declared (eg var [a] = a) - return isBlockScopedNameDeclaredBeforeUse(getAncestor(declaration, SyntaxKind.VariableDeclaration) as Declaration, usage); - } - else if (declaration.kind === SyntaxKind.VariableDeclaration) { - // still might be illegal if usage is in the initializer of the variable declaration (eg var a = a) - return !isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration as VariableDeclaration, usage); - } - else if (isClassDeclaration(declaration)) { - // still might be illegal if the usage is within a computed property name in the class (eg class A { static p = "a"; [A.p]() {} }) - return !findAncestor(usage, n => isComputedPropertyName(n) && n.parent.parent === declaration); - } - else if (isPropertyDeclaration(declaration)) { - // still might be illegal if a self-referencing property initializer (eg private x = this.x) - return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ false); - } - else if (isParameterPropertyDeclaration(declaration, declaration.parent)) { - // foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property - return !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields - && getContainingClass(declaration) === getContainingClass(usage) - && isUsedInFunctionOrInstanceProperty(usage, declaration)); - } - return true; - } - + return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration"); + } - // declaration is after usage, but it can still be legal if usage is deferred: - // 1. inside an export specifier - // 2. inside a function - // 3. inside an instance property initializer, a reference to a non-instance property - // (except when target: "esnext" and useDefineForClassFields: true and the reference is to a parameter property) - // 4. inside a static property initializer, a reference to a static method in the same class - // 5. inside a TS export= declaration (since we will move the export statement during emit to avoid TDZ) - // or if usage is in a type context: - // 1. inside a type query (typeof in type position) - // 2. inside a jsdoc comment - if (usage.parent.kind === SyntaxKind.ExportSpecifier || (usage.parent.kind === SyntaxKind.ExportAssignment && (usage.parent as ExportAssignment).isExportEquals)) { - // export specifiers do not use the variable, they only make it available for use + function isBlockScopedNameDeclaredBeforeUse(declaration: Declaration, usage: Node): boolean { + const declarationFile = getSourceFileOfNode(declaration); + const useFile = getSourceFileOfNode(usage); + const declContainer = getEnclosingBlockScopeContainer(declaration); + if (declarationFile !== useFile) { + if ((moduleKind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) || + (!outFile(compilerOptions)) || + isInTypeQuery(usage) || + declaration.flags & NodeFlags.Ambient) { + // nodes are in different files and order cannot be determined return true; } - // When resolving symbols for exports, the `usage` location passed in can be the export site directly - if (usage.kind === SyntaxKind.ExportAssignment && (usage as ExportAssignment).isExportEquals) { + // declaration is after usage + // can be legal if usage is deferred (i.e. inside function or in initializer of instance property) + if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { return true; } + const sourceFiles = host.getSourceFiles(); + return sourceFiles.indexOf(declarationFile) <= sourceFiles.indexOf(useFile); + } - if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || usageInTypeDeclaration()) { - return true; - } - if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { - if (getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields - && getContainingClass(declaration) - && (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent))) { - return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); - } - else { - return true; + if (declaration.pos <= usage.pos && !(isPropertyDeclaration(declaration) && isThisProperty(usage.parent) && !declaration.initializer && !declaration.exclamationToken)) { + // declaration is before usage + if (declaration.kind === SyntaxKind.BindingElement) { + // still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2]) + const errorBindingElement = getAncestor(usage, SyntaxKind.BindingElement) as BindingElement; + if (errorBindingElement) { + return findAncestor(errorBindingElement, isBindingElement) !== findAncestor(declaration, isBindingElement) || + declaration.pos < errorBindingElement.pos; } + // or it might be illegal if usage happens before parent variable is declared (eg var [a] = a) + return isBlockScopedNameDeclaredBeforeUse(getAncestor(declaration, SyntaxKind.VariableDeclaration) as Declaration, usage); } - return false; - - function usageInTypeDeclaration() { - return !!findAncestor(usage, node => isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)); + else if (declaration.kind === SyntaxKind.VariableDeclaration) { + // still might be illegal if usage is in the initializer of the variable declaration (eg var a = a) + return !isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration as VariableDeclaration, usage); + } + else if (isClassDeclaration(declaration)) { + // still might be illegal if the usage is within a computed property name in the class (eg class A { static p = "a"; [A.p]() {} }) + return !findAncestor(usage, n => isComputedPropertyName(n) && n.parent.parent === declaration); } + else if (isPropertyDeclaration(declaration)) { + // still might be illegal if a self-referencing property initializer (eg private x = this.x) + return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ false); + } + else if (isParameterPropertyDeclaration(declaration, declaration.parent)) { + // foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property + return !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields + && getContainingClass(declaration) === getContainingClass(usage) + && isUsedInFunctionOrInstanceProperty(usage, declaration)); + } + return true; + } - function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { - switch (declaration.parent.parent.kind) { - case SyntaxKind.VariableStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForOfStatement: - // variable statement/for/for-of statement case, - // use site should not be inside variable declaration (initializer of declaration or binding element) - if (isSameScopeDescendentOf(usage, declaration, declContainer)) { - return true; - } - break; - } - // ForIn/ForOf case - use site should not be used in expression part - const grandparent = declaration.parent.parent; - return isForInOrOfStatement(grandparent) && isSameScopeDescendentOf(usage, grandparent.expression, declContainer); + // declaration is after usage, but it can still be legal if usage is deferred: + // 1. inside an export specifier + // 2. inside a function + // 3. inside an instance property initializer, a reference to a non-instance property + // (except when target: "esnext" and useDefineForClassFields: true and the reference is to a parameter property) + // 4. inside a static property initializer, a reference to a static method in the same class + // 5. inside a TS export= declaration (since we will move the export statement during emit to avoid TDZ) + // or if usage is in a type context: + // 1. inside a type query (typeof in type position) + // 2. inside a jsdoc comment + if (usage.parent.kind === SyntaxKind.ExportSpecifier || (usage.parent.kind === SyntaxKind.ExportAssignment && (usage.parent as ExportAssignment).isExportEquals)) { + // export specifiers do not use the variable, they only make it available for use + return true; + } + // When resolving symbols for exports, the `usage` location passed in can be the export site directly + if (usage.kind === SyntaxKind.ExportAssignment && (usage as ExportAssignment).isExportEquals) { + return true; + } + + if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || usageInTypeDeclaration()) { + return true; + } + if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { + if (getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields + && getContainingClass(declaration) + && (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent))) { + return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); } + else { + return true; + } + } + return false; - function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node): boolean { - return !!findAncestor(usage, current => { - if (current === declContainer) { - return "quit"; - } - if (isFunctionLike(current)) { + function usageInTypeDeclaration() { + return !!findAncestor(usage, node => isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)); + } + + function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { + switch (declaration.parent.parent.kind) { + case SyntaxKind.VariableStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForOfStatement: + // variable statement/for/for-of statement case, + // use site should not be inside variable declaration (initializer of declaration or binding element) + if (isSameScopeDescendentOf(usage, declaration, declContainer)) { return true; } - if (isClassStaticBlockDeclaration(current)) { - return declaration.pos < usage.pos; - } + break; + } - const propertyDeclaration = tryCast(current.parent, isPropertyDeclaration); - if (propertyDeclaration) { - const initializerOfProperty = propertyDeclaration.initializer === current; - if (initializerOfProperty) { - if (isStatic(current.parent)) { - if (declaration.kind === SyntaxKind.MethodDeclaration) { - return true; - } - if (isPropertyDeclaration(declaration) && getContainingClass(usage) === getContainingClass(declaration)) { - const propName = declaration.name; - if (isIdentifier(propName) || isPrivateIdentifier(propName)) { - const type = getTypeOfSymbol(getSymbolOfNode(declaration)); - const staticBlocks = filter(declaration.parent.members, isClassStaticBlockDeclaration); - if (isPropertyInitializedInStaticBlocks(propName, type, staticBlocks, declaration.parent.pos, current.pos)) { - return true; - } + // ForIn/ForOf case - use site should not be used in expression part + const grandparent = declaration.parent.parent; + return isForInOrOfStatement(grandparent) && isSameScopeDescendentOf(usage, grandparent.expression, declContainer); + } + + function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node): boolean { + return !!findAncestor(usage, current => { + if (current === declContainer) { + return "quit"; + } + if (isFunctionLike(current)) { + return true; + } + if (isClassStaticBlockDeclaration(current)) { + return declaration.pos < usage.pos; + } + + const propertyDeclaration = tryCast(current.parent, isPropertyDeclaration); + if (propertyDeclaration) { + const initializerOfProperty = propertyDeclaration.initializer === current; + if (initializerOfProperty) { + if (isStatic(current.parent)) { + if (declaration.kind === SyntaxKind.MethodDeclaration) { + return true; + } + if (isPropertyDeclaration(declaration) && getContainingClass(usage) === getContainingClass(declaration)) { + const propName = declaration.name; + if (isIdentifier(propName) || isPrivateIdentifier(propName)) { + const type = getTypeOfSymbol(getSymbolOfNode(declaration)); + const staticBlocks = filter(declaration.parent.members, isClassStaticBlockDeclaration); + if (isPropertyInitializedInStaticBlocks(propName, type, staticBlocks, declaration.parent.pos, current.pos)) { + return true; } } } - else { - const isDeclarationInstanceProperty = declaration.kind === SyntaxKind.PropertyDeclaration && !isStatic(declaration); - if (!isDeclarationInstanceProperty || getContainingClass(usage) !== getContainingClass(declaration)) { - return true; - } + } + else { + const isDeclarationInstanceProperty = declaration.kind === SyntaxKind.PropertyDeclaration && !isStatic(declaration); + if (!isDeclarationInstanceProperty || getContainingClass(usage) !== getContainingClass(declaration)) { + return true; } } } - return false; - }); - } - - /** stopAtAnyPropertyDeclaration is used for detecting ES-standard class field use-before-def errors */ - function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration | ParameterPropertyDeclaration, usage: Node, stopAtAnyPropertyDeclaration: boolean) { - // always legal if usage is after declaration - if (usage.end > declaration.end) { - return false; } - - // still might be legal if usage is deferred (e.g. x: any = () => this.x) - // otherwise illegal if immediately referenced within the declaration (e.g. x: any = this.x) - const ancestorChangingReferenceScope = findAncestor(usage, (node: Node) => { - if (node === declaration) { - return "quit"; - } - - switch (node.kind) { - case SyntaxKind.ArrowFunction: - return true; - case SyntaxKind.PropertyDeclaration: - // even when stopping at any property declaration, they need to come from the same class - return stopAtAnyPropertyDeclaration && - (isPropertyDeclaration(declaration) && node.parent === declaration.parent - || isParameterPropertyDeclaration(declaration, declaration.parent) && node.parent === declaration.parent.parent) - ? "quit": true; - case SyntaxKind.Block: - switch (node.parent.kind) { - case SyntaxKind.GetAccessor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.SetAccessor: - return true; - default: - return false; - } - default: - return false; - } - }); - - return ancestorChangingReferenceScope === undefined; - } + return false; + }); } - function useOuterVariableScopeInParameter(result: Symbol, location: Node, lastLocation: Node) { - const target = getEmitScriptTarget(compilerOptions); - const functionLocation = location as FunctionLikeDeclaration; - if (isParameter(lastLocation) - && functionLocation.body - && result.valueDeclaration - && result.valueDeclaration.pos >= functionLocation.body.pos - && result.valueDeclaration.end <= functionLocation.body.end) { - // check for several cases where we introduce temporaries that require moving the name/initializer of the parameter to the body - // - static field in a class expression - // - optional chaining pre-es2020 - // - nullish coalesce pre-es2020 - // - spread assignment in binding pattern pre-es2017 - if (target >= ScriptTarget.ES2015) { - const links = getNodeLinks(functionLocation); - if (links.declarationRequiresScopeChange === undefined) { - links.declarationRequiresScopeChange = forEach(functionLocation.parameters, requiresScopeChange) || false; - } - return !links.declarationRequiresScopeChange; - } + /** stopAtAnyPropertyDeclaration is used for detecting ES-standard class field use-before-def errors */ + function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration | ParameterPropertyDeclaration, usage: Node, stopAtAnyPropertyDeclaration: boolean) { + // always legal if usage is after declaration + if (usage.end > declaration.end) { + return false; } - return false; - function requiresScopeChange(node: ParameterDeclaration): boolean { - return requiresScopeChangeWorker(node.name) - || !!node.initializer && requiresScopeChangeWorker(node.initializer); - } + // still might be legal if usage is deferred (e.g. x: any = () => this.x) + // otherwise illegal if immediately referenced within the declaration (e.g. x: any = this.x) + const ancestorChangingReferenceScope = findAncestor(usage, (node: Node) => { + if (node === declaration) { + return "quit"; + } - function requiresScopeChangeWorker(node: Node): boolean { switch (node.kind) { case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.Constructor: - // do not descend into these - return false; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.PropertyAssignment: - return requiresScopeChangeWorker((node as MethodDeclaration | AccessorDeclaration | PropertyAssignment).name); + return true; case SyntaxKind.PropertyDeclaration: - // static properties in classes introduce temporary variables - if (hasStaticModifier(node)) { - return target < ScriptTarget.ESNext || !useDefineForClassFields; + // even when stopping at any property declaration, they need to come from the same class + return stopAtAnyPropertyDeclaration && + (isPropertyDeclaration(declaration) && node.parent === declaration.parent + || isParameterPropertyDeclaration(declaration, declaration.parent) && node.parent === declaration.parent.parent) + ? "quit": true; + case SyntaxKind.Block: + switch (node.parent.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.SetAccessor: + return true; + default: + return false; } - return requiresScopeChangeWorker((node as PropertyDeclaration).name); default: - // null coalesce and optional chain pre-es2020 produce temporary variables - if (isNullishCoalesce(node) || isOptionalChain(node)) { - return target < ScriptTarget.ES2020; - } - if (isBindingElement(node) && node.dotDotDotToken && isObjectBindingPattern(node.parent)) { - return target < ScriptTarget.ES2017; - } - if (isTypeNode(node)) return false; - return forEachChild(node, requiresScopeChangeWorker) || false; + return false; } + }); + + return ancestorChangingReferenceScope === undefined; + } + } + + function useOuterVariableScopeInParameter(result: Symbol, location: Node, lastLocation: Node) { + const target = getEmitScriptTarget(compilerOptions); + const functionLocation = location as FunctionLikeDeclaration; + if (isParameter(lastLocation) + && functionLocation.body + && result.valueDeclaration + && result.valueDeclaration.pos >= functionLocation.body.pos + && result.valueDeclaration.end <= functionLocation.body.end) { + // check for several cases where we introduce temporaries that require moving the name/initializer of the parameter to the body + // - static field in a class expression + // - optional chaining pre-es2020 + // - nullish coalesce pre-es2020 + // - spread assignment in binding pattern pre-es2017 + if (target >= ScriptTarget.ES2015) { + const links = getNodeLinks(functionLocation); + if (links.declarationRequiresScopeChange === undefined) { + links.declarationRequiresScopeChange = forEach(functionLocation.parameters, requiresScopeChange) || false; + } + return !links.declarationRequiresScopeChange; } } + return false; - /** - * Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and - * the nameNotFoundMessage argument is not undefined. Returns the resolved symbol, or undefined if no symbol with - * the given name can be found. - * - * @param isUse If true, this will count towards --noUnusedLocals / --noUnusedParameters. - */ - function resolveName( - location: Node | undefined, - name: __String, - meaning: SymbolFlags, - nameNotFoundMessage: DiagnosticMessage | undefined, - nameArg: __String | Identifier | undefined, - isUse: boolean, - excludeGlobals = false): Symbol | undefined { - return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSymbol); - } - - function resolveNameHelper( - location: Node | undefined, - name: __String, - meaning: SymbolFlags, - nameNotFoundMessage: DiagnosticMessage | undefined, - nameArg: __String | Identifier | undefined, - isUse: boolean, - excludeGlobals: boolean, - lookup: typeof getSymbol): Symbol | undefined { - const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location - let result: Symbol | undefined; - let lastLocation: Node | undefined; - let lastSelfReferenceLocation: Node | undefined; - let propertyWithInvalidInitializer: Node | undefined; - let associatedDeclarationForContainingInitializerOrBindingName: ParameterDeclaration | BindingElement | undefined; - let withinDeferredContext = false; - const errorLocation = location; - let grandparent: Node; - let isInExternalModule = false; - - loop: while (location) { - // Locals of a source file are not in scope (because they get merged into the global symbol table) - if (location.locals && !isGlobalSourceFile(location)) { - if (result = lookup(location.locals, name, meaning)) { - let useResult = true; - if (isFunctionLike(location) && lastLocation && lastLocation !== (location as FunctionLikeDeclaration).body) { - // symbol lookup restrictions for function-like declarations - // - Type parameters of a function are in scope in the entire function declaration, including the parameter - // list and return type. However, local types are only in scope in the function body. - // - parameters are only in the scope of function body - // This restriction does not apply to JSDoc comment types because they are parented - // at a higher level than type parameters would normally be - if (meaning & result.flags & SymbolFlags.Type && lastLocation.kind !== SyntaxKind.JSDocComment) { - useResult = result.flags & SymbolFlags.TypeParameter - // type parameters are visible in parameter list, return type and type parameter list - ? lastLocation === (location as FunctionLikeDeclaration).type || + function requiresScopeChange(node: ParameterDeclaration): boolean { + return requiresScopeChangeWorker(node.name) + || !!node.initializer && requiresScopeChangeWorker(node.initializer); + } + + function requiresScopeChangeWorker(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.Constructor: + // do not descend into these + return false; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyAssignment: + return requiresScopeChangeWorker((node as MethodDeclaration | AccessorDeclaration | PropertyAssignment).name); + case SyntaxKind.PropertyDeclaration: + // static properties in classes introduce temporary variables + if (hasStaticModifier(node)) { + return target < ScriptTarget.ESNext || !useDefineForClassFields; + } + return requiresScopeChangeWorker((node as PropertyDeclaration).name); + default: + // null coalesce and optional chain pre-es2020 produce temporary variables + if (isNullishCoalesce(node) || isOptionalChain(node)) { + return target < ScriptTarget.ES2020; + } + if (isBindingElement(node) && node.dotDotDotToken && isObjectBindingPattern(node.parent)) { + return target < ScriptTarget.ES2017; + } + if (isTypeNode(node)) return false; + return forEachChild(node, requiresScopeChangeWorker) || false; + } + } + } + + /** + * Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and + * the nameNotFoundMessage argument is not undefined. Returns the resolved symbol, or undefined if no symbol with + * the given name can be found. + * + * @param isUse If true, this will count towards --noUnusedLocals / --noUnusedParameters. + */ + function resolveName( + location: Node | undefined, + name: __String, + meaning: SymbolFlags, + nameNotFoundMessage: DiagnosticMessage | undefined, + nameArg: __String | Identifier | undefined, + isUse: boolean, + excludeGlobals = false): Symbol | undefined { + return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSymbol); + } + + function resolveNameHelper( + location: Node | undefined, + name: __String, + meaning: SymbolFlags, + nameNotFoundMessage: DiagnosticMessage | undefined, + nameArg: __String | Identifier | undefined, + isUse: boolean, + excludeGlobals: boolean, + lookup: typeof getSymbol): Symbol | undefined { + const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location + let result: Symbol | undefined; + let lastLocation: Node | undefined; + let lastSelfReferenceLocation: Node | undefined; + let propertyWithInvalidInitializer: Node | undefined; + let associatedDeclarationForContainingInitializerOrBindingName: ParameterDeclaration | BindingElement | undefined; + let withinDeferredContext = false; + const errorLocation = location; + let grandparent: Node; + let isInExternalModule = false; + + loop: while (location) { + // Locals of a source file are not in scope (because they get merged into the global symbol table) + if (location.locals && !isGlobalSourceFile(location)) { + if (result = lookup(location.locals, name, meaning)) { + let useResult = true; + if (isFunctionLike(location) && lastLocation && lastLocation !== (location as FunctionLikeDeclaration).body) { + // symbol lookup restrictions for function-like declarations + // - Type parameters of a function are in scope in the entire function declaration, including the parameter + // list and return type. However, local types are only in scope in the function body. + // - parameters are only in the scope of function body + // This restriction does not apply to JSDoc comment types because they are parented + // at a higher level than type parameters would normally be + if (meaning & result.flags & SymbolFlags.Type && lastLocation.kind !== SyntaxKind.JSDocComment) { + useResult = result.flags & SymbolFlags.TypeParameter + // type parameters are visible in parameter list, return type and type parameter list + ? lastLocation === (location as FunctionLikeDeclaration).type || + lastLocation.kind === SyntaxKind.Parameter || + lastLocation.kind === SyntaxKind.TypeParameter + // local types not visible outside the function body + : false; + } + if (meaning & result.flags & SymbolFlags.Variable) { + // expression inside parameter will lookup as normal variable scope when targeting es2015+ + if (useOuterVariableScopeInParameter(result, location, lastLocation)) { + useResult = false; + } + else if (result.flags & SymbolFlags.FunctionScopedVariable) { + // parameters are visible only inside function body, parameter list and return type + // technically for parameter list case here we might mix parameters and variables declared in function, + // however it is detected separately when checking initializers of parameters + // to make sure that they reference no variables declared after them. + useResult = lastLocation.kind === SyntaxKind.Parameter || - lastLocation.kind === SyntaxKind.TypeParameter - // local types not visible outside the function body - : false; - } - if (meaning & result.flags & SymbolFlags.Variable) { - // expression inside parameter will lookup as normal variable scope when targeting es2015+ - if (useOuterVariableScopeInParameter(result, location, lastLocation)) { - useResult = false; - } - else if (result.flags & SymbolFlags.FunctionScopedVariable) { - // parameters are visible only inside function body, parameter list and return type - // technically for parameter list case here we might mix parameters and variables declared in function, - // however it is detected separately when checking initializers of parameters - // to make sure that they reference no variables declared after them. - useResult = - lastLocation.kind === SyntaxKind.Parameter || - ( - lastLocation === (location as FunctionLikeDeclaration).type && - !!findAncestor(result.valueDeclaration, isParameter) - ); - } + ( + lastLocation === (location as FunctionLikeDeclaration).type && + !!findAncestor(result.valueDeclaration, isParameter) + ); } } - else if (location.kind === SyntaxKind.ConditionalType) { - // A type parameter declared using 'infer T' in a conditional type is visible only in - // the true branch of the conditional type. - useResult = lastLocation === (location as ConditionalTypeNode).trueType; - } + } + else if (location.kind === SyntaxKind.ConditionalType) { + // A type parameter declared using 'infer T' in a conditional type is visible only in + // the true branch of the conditional type. + useResult = lastLocation === (location as ConditionalTypeNode).trueType; + } - if (useResult) { - break loop; - } - else { - result = undefined; - } + if (useResult) { + break loop; + } + else { + result = undefined; } } - withinDeferredContext = withinDeferredContext || getIsDeferredContext(location, lastLocation); - switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalOrCommonJsModule(location as SourceFile)) break; - isInExternalModule = true; - // falls through - case SyntaxKind.ModuleDeclaration: - const moduleExports = getSymbolOfNode(location as SourceFile | ModuleDeclaration)?.exports || emptySymbols; - if (location.kind === SyntaxKind.SourceFile || (isModuleDeclaration(location) && location.flags & NodeFlags.Ambient && !isGlobalScopeAugmentation(location))) { - - // It's an external module. First see if the module has an export default and if the local - // name of that export default matches. - if (result = moduleExports.get(InternalSymbolName.Default)) { - const localSymbol = getLocalSymbolForExportDefault(result); - if (localSymbol && (result.flags & meaning) && localSymbol.escapedName === name) { - break loop; - } - result = undefined; + } + withinDeferredContext = withinDeferredContext || getIsDeferredContext(location, lastLocation); + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalOrCommonJsModule(location as SourceFile)) break; + isInExternalModule = true; + // falls through + case SyntaxKind.ModuleDeclaration: + const moduleExports = getSymbolOfNode(location as SourceFile | ModuleDeclaration)?.exports || emptySymbols; + if (location.kind === SyntaxKind.SourceFile || (isModuleDeclaration(location) && location.flags & NodeFlags.Ambient && !isGlobalScopeAugmentation(location))) { + + // It's an external module. First see if the module has an export default and if the local + // name of that export default matches. + if (result = moduleExports.get(InternalSymbolName.Default)) { + const localSymbol = getLocalSymbolForExportDefault(result); + if (localSymbol && (result.flags & meaning) && localSymbol.escapedName === name) { + break loop; } + result = undefined; + } - // Because of module/namespace merging, a module's exports are in scope, - // yet we never want to treat an export specifier as putting a member in scope. - // Therefore, if the name we find is purely an export specifier, it is not actually considered in scope. - // Two things to note about this: - // 1. We have to check this without calling getSymbol. The problem with calling getSymbol - // on an export specifier is that it might find the export specifier itself, and try to - // resolve it as an alias. This will cause the checker to consider the export specifier - // a circular alias reference when it might not be. - // 2. We check === SymbolFlags.Alias in order to check that the symbol is *purely* - // an alias. If we used &, we'd be throwing out symbols that have non alias aspects, - // which is not the desired behavior. - const moduleExport = moduleExports.get(name); - if (moduleExport && - moduleExport.flags === SymbolFlags.Alias && - (getDeclarationOfKind(moduleExport, SyntaxKind.ExportSpecifier) || getDeclarationOfKind(moduleExport, SyntaxKind.NamespaceExport))) { - break; - } + // Because of module/namespace merging, a module's exports are in scope, + // yet we never want to treat an export specifier as putting a member in scope. + // Therefore, if the name we find is purely an export specifier, it is not actually considered in scope. + // Two things to note about this: + // 1. We have to check this without calling getSymbol. The problem with calling getSymbol + // on an export specifier is that it might find the export specifier itself, and try to + // resolve it as an alias. This will cause the checker to consider the export specifier + // a circular alias reference when it might not be. + // 2. We check === SymbolFlags.Alias in order to check that the symbol is *purely* + // an alias. If we used &, we'd be throwing out symbols that have non alias aspects, + // which is not the desired behavior. + const moduleExport = moduleExports.get(name); + if (moduleExport && + moduleExport.flags === SymbolFlags.Alias && + (getDeclarationOfKind(moduleExport, SyntaxKind.ExportSpecifier) || getDeclarationOfKind(moduleExport, SyntaxKind.NamespaceExport))) { + break; } + } - // ES6 exports are also visible locally (except for 'default'), but commonjs exports are not (except typedefs) - if (name !== InternalSymbolName.Default && (result = lookup(moduleExports, name, meaning & SymbolFlags.ModuleMember))) { - if (isSourceFile(location) && location.commonJsModuleIndicator && !result.declarations?.some(isJSDocTypeAlias)) { - result = undefined; - } - else { - break loop; - } + // ES6 exports are also visible locally (except for 'default'), but commonjs exports are not (except typedefs) + if (name !== InternalSymbolName.Default && (result = lookup(moduleExports, name, meaning & SymbolFlags.ModuleMember))) { + if (isSourceFile(location) && location.commonJsModuleIndicator && !result.declarations?.some(isJSDocTypeAlias)) { + result = undefined; } - break; - case SyntaxKind.EnumDeclaration: - if (result = lookup(getSymbolOfNode(location)?.exports || emptySymbols, name, meaning & SymbolFlags.EnumMember)) { + else { break loop; } - break; - case SyntaxKind.PropertyDeclaration: - // TypeScript 1.0 spec (April 2014): 8.4.1 - // Initializer expressions for instance member variables are evaluated in the scope - // of the class constructor body but are not permitted to reference parameters or - // local variables of the constructor. This effectively means that entities from outer scopes - // by the same name as a constructor parameter or local variable are inaccessible - // in initializer expressions for instance member variables. - if (!isStatic(location)) { - const ctor = findConstructorDeclaration(location.parent as ClassLikeDeclaration); - if (ctor && ctor.locals) { - if (lookup(ctor.locals, name, meaning & SymbolFlags.Value)) { - // Remember the property node, it will be used later to report appropriate error - propertyWithInvalidInitializer = location; - } + } + break; + case SyntaxKind.EnumDeclaration: + if (result = lookup(getSymbolOfNode(location)?.exports || emptySymbols, name, meaning & SymbolFlags.EnumMember)) { + break loop; + } + break; + case SyntaxKind.PropertyDeclaration: + // TypeScript 1.0 spec (April 2014): 8.4.1 + // Initializer expressions for instance member variables are evaluated in the scope + // of the class constructor body but are not permitted to reference parameters or + // local variables of the constructor. This effectively means that entities from outer scopes + // by the same name as a constructor parameter or local variable are inaccessible + // in initializer expressions for instance member variables. + if (!isStatic(location)) { + const ctor = findConstructorDeclaration(location.parent as ClassLikeDeclaration); + if (ctor && ctor.locals) { + if (lookup(ctor.locals, name, meaning & SymbolFlags.Value)) { + // Remember the property node, it will be used later to report appropriate error + propertyWithInvalidInitializer = location; } } - break; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals - // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would - // trigger resolving late-bound names, which we may already be in the process of doing while we're here! - if (result = lookup(getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols, name, meaning & SymbolFlags.Type)) { - if (!isTypeParameterSymbolDeclaredInContainer(result, location)) { - // ignore type parameters not declared in this container - result = undefined; - break; - } - if (lastLocation && isStatic(lastLocation)) { - // TypeScript 1.0 spec (April 2014): 3.4.1 - // The scope of a type parameter extends over the entire declaration with which the type - // parameter list is associated, with the exception of static member declarations in classes. - error(errorLocation, Diagnostics.Static_members_cannot_reference_class_type_parameters); - return undefined; - } - break loop; - } - if (location.kind === SyntaxKind.ClassExpression && meaning & SymbolFlags.Class) { - const className = (location as ClassExpression).name; - if (className && name === className.escapedText) { - result = location.symbol; - break loop; - } - } - break; - case SyntaxKind.ExpressionWithTypeArguments: - // The type parameters of a class are not in scope in the base class expression. - if (lastLocation === (location as ExpressionWithTypeArguments).expression && (location.parent as HeritageClause).token === SyntaxKind.ExtendsKeyword) { - const container = location.parent.parent; - if (isClassLike(container) && (result = lookup(getSymbolOfNode(container).members!, name, meaning & SymbolFlags.Type))) { - if (nameNotFoundMessage) { - error(errorLocation, Diagnostics.Base_class_expressions_cannot_reference_class_type_parameters); - } - return undefined; - } - } - break; - // It is not legal to reference a class's own type parameters from a computed property name that - // belongs to the class. For example: - // - // function foo() { return '' } - // class C { // <-- Class's own type parameter T - // [foo()]() { } // <-- Reference to T from class's own computed property - // } - // - case SyntaxKind.ComputedPropertyName: - grandparent = location.parent.parent; - if (isClassLike(grandparent) || grandparent.kind === SyntaxKind.InterfaceDeclaration) { - // A reference to this grandparent's type parameters would be an error - if (result = lookup(getSymbolOfNode(grandparent as ClassLikeDeclaration | InterfaceDeclaration).members!, name, meaning & SymbolFlags.Type)) { - error(errorLocation, Diagnostics.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type); - return undefined; - } - } - break; - case SyntaxKind.ArrowFunction: - // when targeting ES6 or higher there is no 'arguments' in an arrow function - // for lower compile targets the resolved symbol is used to emit an error - if (getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015) { + } + break; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals + // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would + // trigger resolving late-bound names, which we may already be in the process of doing while we're here! + if (result = lookup(getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols, name, meaning & SymbolFlags.Type)) { + if (!isTypeParameterSymbolDeclaredInContainer(result, location)) { + // ignore type parameters not declared in this container + result = undefined; break; } - // falls through - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionDeclaration: - if (meaning & SymbolFlags.Variable && name === "arguments") { - result = argumentsSymbol; - break loop; + if (lastLocation && isStatic(lastLocation)) { + // TypeScript 1.0 spec (April 2014): 3.4.1 + // The scope of a type parameter extends over the entire declaration with which the type + // parameter list is associated, with the exception of static member declarations in classes. + error(errorLocation, Diagnostics.Static_members_cannot_reference_class_type_parameters); + return undefined; } - break; - case SyntaxKind.FunctionExpression: - if (meaning & SymbolFlags.Variable && name === "arguments") { - result = argumentsSymbol; + break loop; + } + if (location.kind === SyntaxKind.ClassExpression && meaning & SymbolFlags.Class) { + const className = (location as ClassExpression).name; + if (className && name === className.escapedText) { + result = location.symbol; break loop; } - - if (meaning & SymbolFlags.Function) { - const functionName = (location as FunctionExpression).name; - if (functionName && name === functionName.escapedText) { - result = location.symbol; - break loop; + } + break; + case SyntaxKind.ExpressionWithTypeArguments: + // The type parameters of a class are not in scope in the base class expression. + if (lastLocation === (location as ExpressionWithTypeArguments).expression && (location.parent as HeritageClause).token === SyntaxKind.ExtendsKeyword) { + const container = location.parent.parent; + if (isClassLike(container) && (result = lookup(getSymbolOfNode(container).members!, name, meaning & SymbolFlags.Type))) { + if (nameNotFoundMessage) { + error(errorLocation, Diagnostics.Base_class_expressions_cannot_reference_class_type_parameters); } + return undefined; } - break; - case SyntaxKind.Decorator: - // Decorators are resolved at the class declaration. Resolving at the parameter - // or member would result in looking up locals in the method. - // - // function y() {} - // class C { - // method(@y x, y) {} // <-- decorator y should be resolved at the class declaration, not the parameter. - // } - // - if (location.parent && location.parent.kind === SyntaxKind.Parameter) { - location = location.parent; - } - // - // function y() {} - // class C { - // @y method(x, y) {} // <-- decorator y should be resolved at the class declaration, not the method. - // } - // - - // class Decorators are resolved outside of the class to avoid referencing type parameters of that class. - // - // type T = number; - // declare function y(x: T): any; - // @param(1 as T) // <-- T should resolve to the type alias outside of class C - // class C {} - if (location.parent && (isClassElement(location.parent) || location.parent.kind === SyntaxKind.ClassDeclaration)) { - location = location.parent; + } + break; + // It is not legal to reference a class's own type parameters from a computed property name that + // belongs to the class. For example: + // + // function foo() { return '' } + // class C { // <-- Class's own type parameter T + // [foo()]() { } // <-- Reference to T from class's own computed property + // } + // + case SyntaxKind.ComputedPropertyName: + grandparent = location.parent.parent; + if (isClassLike(grandparent) || grandparent.kind === SyntaxKind.InterfaceDeclaration) { + // A reference to this grandparent's type parameters would be an error + if (result = lookup(getSymbolOfNode(grandparent as ClassLikeDeclaration | InterfaceDeclaration).members!, name, meaning & SymbolFlags.Type)) { + error(errorLocation, Diagnostics.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type); + return undefined; } + } + break; + case SyntaxKind.ArrowFunction: + // when targeting ES6 or higher there is no 'arguments' in an arrow function + // for lower compile targets the resolved symbol is used to emit an error + if (getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015) { break; - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - // js type aliases do not resolve names from their host, so skip past it - const root = getJSDocRoot(location); - if (root) { - location = root.parent; + } + // falls through + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + if (meaning & SymbolFlags.Variable && name === "arguments") { + result = argumentsSymbol; + break loop; + } + break; + case SyntaxKind.FunctionExpression: + if (meaning & SymbolFlags.Variable && name === "arguments") { + result = argumentsSymbol; + break loop; + } + + if (meaning & SymbolFlags.Function) { + const functionName = (location as FunctionExpression).name; + if (functionName && name === functionName.escapedText) { + result = location.symbol; + break loop; } - break; - case SyntaxKind.Parameter: - if (lastLocation && ( - lastLocation === (location as ParameterDeclaration).initializer || - lastLocation === (location as ParameterDeclaration).name && isBindingPattern(lastLocation))) { - if (!associatedDeclarationForContainingInitializerOrBindingName) { - associatedDeclarationForContainingInitializerOrBindingName = location as ParameterDeclaration; - } + } + break; + case SyntaxKind.Decorator: + // Decorators are resolved at the class declaration. Resolving at the parameter + // or member would result in looking up locals in the method. + // + // function y() {} + // class C { + // method(@y x, y) {} // <-- decorator y should be resolved at the class declaration, not the parameter. + // } + // + if (location.parent && location.parent.kind === SyntaxKind.Parameter) { + location = location.parent; + } + // + // function y() {} + // class C { + // @y method(x, y) {} // <-- decorator y should be resolved at the class declaration, not the method. + // } + // + + // class Decorators are resolved outside of the class to avoid referencing type parameters of that class. + // + // type T = number; + // declare function y(x: T): any; + // @param(1 as T) // <-- T should resolve to the type alias outside of class C + // class C {} + if (location.parent && (isClassElement(location.parent) || location.parent.kind === SyntaxKind.ClassDeclaration)) { + location = location.parent; + } + break; + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + // js type aliases do not resolve names from their host, so skip past it + const root = getJSDocRoot(location); + if (root) { + location = root.parent; + } + break; + case SyntaxKind.Parameter: + if (lastLocation && ( + lastLocation === (location as ParameterDeclaration).initializer || + lastLocation === (location as ParameterDeclaration).name && isBindingPattern(lastLocation))) { + if (!associatedDeclarationForContainingInitializerOrBindingName) { + associatedDeclarationForContainingInitializerOrBindingName = location as ParameterDeclaration; } - break; - case SyntaxKind.BindingElement: - if (lastLocation && ( - lastLocation === (location as BindingElement).initializer || - lastLocation === (location as BindingElement).name && isBindingPattern(lastLocation))) { - if (isParameterDeclaration(location as BindingElement) && !associatedDeclarationForContainingInitializerOrBindingName) { - associatedDeclarationForContainingInitializerOrBindingName = location as BindingElement; - } + } + break; + case SyntaxKind.BindingElement: + if (lastLocation && ( + lastLocation === (location as BindingElement).initializer || + lastLocation === (location as BindingElement).name && isBindingPattern(lastLocation))) { + if (isParameterDeclaration(location as BindingElement) && !associatedDeclarationForContainingInitializerOrBindingName) { + associatedDeclarationForContainingInitializerOrBindingName = location as BindingElement; } - break; - case SyntaxKind.InferType: - if (meaning & SymbolFlags.TypeParameter) { - const parameterName = (location as InferTypeNode).typeParameter.name; - if (parameterName && name === parameterName.escapedText) { - result = (location as InferTypeNode).typeParameter.symbol; - break loop; - } + } + break; + case SyntaxKind.InferType: + if (meaning & SymbolFlags.TypeParameter) { + const parameterName = (location as InferTypeNode).typeParameter.name; + if (parameterName && name === parameterName.escapedText) { + result = (location as InferTypeNode).typeParameter.symbol; + break loop; } - break; - } - if (isSelfReferenceLocation(location)) { - lastSelfReferenceLocation = location; - } - lastLocation = location; - location = isJSDocTemplateTag(location) ? - getEffectiveContainerForJSDocTemplateTag(location) || location.parent : - location.parent; + } + break; } - - // We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`. - // If `result === lastSelfReferenceLocation.symbol`, that means that we are somewhere inside `lastSelfReferenceLocation` looking up a name, and resolving to `lastLocation` itself. - // That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used. - if (isUse && result && (!lastSelfReferenceLocation || result !== lastSelfReferenceLocation.symbol)) { - result.isReferenced! |= meaning; + if (isSelfReferenceLocation(location)) { + lastSelfReferenceLocation = location; } + lastLocation = location; + location = isJSDocTemplateTag(location) ? + getEffectiveContainerForJSDocTemplateTag(location) || location.parent : + location.parent; + } - if (!result) { - if (lastLocation) { - Debug.assert(lastLocation.kind === SyntaxKind.SourceFile); - if ((lastLocation as SourceFile).commonJsModuleIndicator && name === "exports" && meaning & lastLocation.symbol.flags) { - return lastLocation.symbol; - } - } + // We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`. + // If `result === lastSelfReferenceLocation.symbol`, that means that we are somewhere inside `lastSelfReferenceLocation` looking up a name, and resolving to `lastLocation` itself. + // That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used. + if (isUse && result && (!lastSelfReferenceLocation || result !== lastSelfReferenceLocation.symbol)) { + result.isReferenced! |= meaning; + } - if (!excludeGlobals) { - result = lookup(globals, name, meaning); + if (!result) { + if (lastLocation) { + Debug.assert(lastLocation.kind === SyntaxKind.SourceFile); + if ((lastLocation as SourceFile).commonJsModuleIndicator && name === "exports" && meaning & lastLocation.symbol.flags) { + return lastLocation.symbol; } } - if (!result) { - if (originalLocation && isInJSFile(originalLocation) && originalLocation.parent) { - if (isRequireCall(originalLocation.parent, /*checkArgumentIsStringLiteralLike*/ false)) { - return requireSymbol; - } + + if (!excludeGlobals) { + result = lookup(globals, name, meaning); + } + } + if (!result) { + if (originalLocation && isInJSFile(originalLocation) && originalLocation.parent) { + if (isRequireCall(originalLocation.parent, /*checkArgumentIsStringLiteralLike*/ false)) { + return requireSymbol; } } - if (!result) { - if (nameNotFoundMessage) { - if (!errorLocation || - !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg!) && // TODO: GH#18217 - !checkAndReportErrorForExtendingInterface(errorLocation) && - !checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) && - !checkAndReportErrorForExportingPrimitiveType(errorLocation, name) && - !checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) && - !checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning) && - !checkAndReportErrorForUsingValueAsType(errorLocation, name, meaning)) { - let suggestion: Symbol | undefined; - if (suggestionCount < maximumSuggestionCount) { - suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning); - const isGlobalScopeAugmentationDeclaration = suggestion?.valueDeclaration && isAmbientModule(suggestion.valueDeclaration) && isGlobalScopeAugmentation(suggestion.valueDeclaration); - if (isGlobalScopeAugmentationDeclaration) { - suggestion = undefined; - } - if (suggestion) { - const suggestionName = symbolToString(suggestion); - const isUncheckedJS = isUncheckedJSSuggestion(originalLocation, suggestion, /*excludeClasses*/ false); - const message = meaning === SymbolFlags.Namespace || nameArg && typeof nameArg !== "string" && nodeIsSynthesized(nameArg) ? Diagnostics.Cannot_find_namespace_0_Did_you_mean_1 - : isUncheckedJS ? Diagnostics.Could_not_find_name_0_Did_you_mean_1 - : Diagnostics.Cannot_find_name_0_Did_you_mean_1; - const diagnostic = createError(errorLocation, message, diagnosticName(nameArg!), suggestionName); - addErrorOrSuggestion(!isUncheckedJS, diagnostic); - if (suggestion.valueDeclaration) { - addRelatedInfo( - diagnostic, - createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) - ); - } + } + if (!result) { + if (nameNotFoundMessage) { + if (!errorLocation || + !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg!) && // TODO: GH#18217 + !checkAndReportErrorForExtendingInterface(errorLocation) && + !checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) && + !checkAndReportErrorForExportingPrimitiveType(errorLocation, name) && + !checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) && + !checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning) && + !checkAndReportErrorForUsingValueAsType(errorLocation, name, meaning)) { + let suggestion: Symbol | undefined; + if (suggestionCount < maximumSuggestionCount) { + suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning); + const isGlobalScopeAugmentationDeclaration = suggestion?.valueDeclaration && isAmbientModule(suggestion.valueDeclaration) && isGlobalScopeAugmentation(suggestion.valueDeclaration); + if (isGlobalScopeAugmentationDeclaration) { + suggestion = undefined; + } + if (suggestion) { + const suggestionName = symbolToString(suggestion); + const isUncheckedJS = isUncheckedJSSuggestion(originalLocation, suggestion, /*excludeClasses*/ false); + const message = meaning === SymbolFlags.Namespace || nameArg && typeof nameArg !== "string" && nodeIsSynthesized(nameArg) ? Diagnostics.Cannot_find_namespace_0_Did_you_mean_1 + : isUncheckedJS ? Diagnostics.Could_not_find_name_0_Did_you_mean_1 + : Diagnostics.Cannot_find_name_0_Did_you_mean_1; + const diagnostic = createError(errorLocation, message, diagnosticName(nameArg!), suggestionName); + addErrorOrSuggestion(!isUncheckedJS, diagnostic); + if (suggestion.valueDeclaration) { + addRelatedInfo( + diagnostic, + createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) + ); } } - if (!suggestion) { - if (nameArg) { - const lib = getSuggestedLibForNonExistentName(nameArg); - if (lib) { - error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg), lib); - } - else { - error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg)); - } + } + if (!suggestion) { + if (nameArg) { + const lib = getSuggestedLibForNonExistentName(nameArg); + if (lib) { + error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg), lib); + } + else { + error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg)); } } - suggestionCount++; } + suggestionCount++; } - return undefined; } + return undefined; + } - // Perform extra checks only if error reporting was requested - if (nameNotFoundMessage) { - if (propertyWithInvalidInitializer && !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields)) { - // We have a match, but the reference occurred within a property initializer and the identifier also binds - // to a local variable in the constructor where the code will be emitted. Note that this is actually allowed - // with ESNext+useDefineForClassFields because the scope semantics are different. - const propertyName = (propertyWithInvalidInitializer as PropertyDeclaration).name; - error(errorLocation, Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor, - declarationNameToString(propertyName), diagnosticName(nameArg!)); - return undefined; - } + // Perform extra checks only if error reporting was requested + if (nameNotFoundMessage) { + if (propertyWithInvalidInitializer && !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields)) { + // We have a match, but the reference occurred within a property initializer and the identifier also binds + // to a local variable in the constructor where the code will be emitted. Note that this is actually allowed + // with ESNext+useDefineForClassFields because the scope semantics are different. + const propertyName = (propertyWithInvalidInitializer as PropertyDeclaration).name; + error(errorLocation, Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor, + declarationNameToString(propertyName), diagnosticName(nameArg!)); + return undefined; + } - // Only check for block-scoped variable if we have an error location and are looking for the - // name with variable meaning - // For example, - // declare module foo { - // interface bar {} - // } - // const foo/*1*/: foo/*2*/.bar; - // The foo at /*1*/ and /*2*/ will share same symbol with two meanings: - // block-scoped variable and namespace module. However, only when we - // try to resolve name in /*1*/ which is used in variable position, - // we want to check for block-scoped - if (errorLocation && - (meaning & SymbolFlags.BlockScopedVariable || - ((meaning & SymbolFlags.Class || meaning & SymbolFlags.Enum) && (meaning & SymbolFlags.Value) === SymbolFlags.Value))) { - const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result); - if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable || exportOrLocalSymbol.flags & SymbolFlags.Class || exportOrLocalSymbol.flags & SymbolFlags.Enum) { - checkResolvedBlockScopedVariable(exportOrLocalSymbol, errorLocation); - } + // Only check for block-scoped variable if we have an error location and are looking for the + // name with variable meaning + // For example, + // declare module foo { + // interface bar {} + // } + // const foo/*1*/: foo/*2*/.bar; + // The foo at /*1*/ and /*2*/ will share same symbol with two meanings: + // block-scoped variable and namespace module. However, only when we + // try to resolve name in /*1*/ which is used in variable position, + // we want to check for block-scoped + if (errorLocation && + (meaning & SymbolFlags.BlockScopedVariable || + ((meaning & SymbolFlags.Class || meaning & SymbolFlags.Enum) && (meaning & SymbolFlags.Value) === SymbolFlags.Value))) { + const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result); + if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable || exportOrLocalSymbol.flags & SymbolFlags.Class || exportOrLocalSymbol.flags & SymbolFlags.Enum) { + checkResolvedBlockScopedVariable(exportOrLocalSymbol, errorLocation); } + } - // If we're in an external module, we can't reference value symbols created from UMD export declarations - if (result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(originalLocation!.flags & NodeFlags.JSDoc)) { - const merged = getMergedSymbol(result); - if (length(merged.declarations) && every(merged.declarations, d => isNamespaceExportDeclaration(d) || isSourceFile(d) && !!d.symbol.globalExports)) { - errorOrSuggestion(!compilerOptions.allowUmdGlobalAccess, errorLocation!, Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead, unescapeLeadingUnderscores(name)); - } + // If we're in an external module, we can't reference value symbols created from UMD export declarations + if (result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(originalLocation!.flags & NodeFlags.JSDoc)) { + const merged = getMergedSymbol(result); + if (length(merged.declarations) && every(merged.declarations, d => isNamespaceExportDeclaration(d) || isSourceFile(d) && !!d.symbol.globalExports)) { + errorOrSuggestion(!compilerOptions.allowUmdGlobalAccess, errorLocation!, Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead, unescapeLeadingUnderscores(name)); } + } - // If we're in a parameter initializer or binding name, we can't reference the values of the parameter whose initializer we're within or parameters to the right - if (result && associatedDeclarationForContainingInitializerOrBindingName && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { - const candidate = getMergedSymbol(getLateBoundSymbol(result)); - const root = (getRootDeclaration(associatedDeclarationForContainingInitializerOrBindingName) as ParameterDeclaration); - // A parameter initializer or binding pattern initializer within a parameter cannot refer to itself - if (candidate === getSymbolOfNode(associatedDeclarationForContainingInitializerOrBindingName)) { - error(errorLocation, Diagnostics.Parameter_0_cannot_reference_itself, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name)); - } - // And it cannot refer to any declarations which come after it - else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializerOrBindingName.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) { - error(errorLocation, Diagnostics.Parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name), declarationNameToString(errorLocation as Identifier)); - } + // If we're in a parameter initializer or binding name, we can't reference the values of the parameter whose initializer we're within or parameters to the right + if (result && associatedDeclarationForContainingInitializerOrBindingName && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { + const candidate = getMergedSymbol(getLateBoundSymbol(result)); + const root = (getRootDeclaration(associatedDeclarationForContainingInitializerOrBindingName) as ParameterDeclaration); + // A parameter initializer or binding pattern initializer within a parameter cannot refer to itself + if (candidate === getSymbolOfNode(associatedDeclarationForContainingInitializerOrBindingName)) { + error(errorLocation, Diagnostics.Parameter_0_cannot_reference_itself, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name)); } - if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias) { - checkSymbolUsageInExpressionContext(result, name, errorLocation); + // And it cannot refer to any declarations which come after it + else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializerOrBindingName.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) { + error(errorLocation, Diagnostics.Parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name), declarationNameToString(errorLocation as Identifier)); } } - return result; - } - - function checkSymbolUsageInExpressionContext(symbol: Symbol, name: __String, useSite: Node) { - if (!isValidTypeOnlyAliasUseSite(useSite)) { - const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(symbol); - if (typeOnlyDeclaration) { - const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier - ? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type - : Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type; - const unescapedName = unescapeLeadingUnderscores(name); - addTypeOnlyDeclarationRelatedInfo( - error(useSite, message, unescapedName), - typeOnlyDeclaration, - unescapedName); - } + if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias) { + checkSymbolUsageInExpressionContext(result, name, errorLocation); } } + return result; + } - function addTypeOnlyDeclarationRelatedInfo(diagnostic: Diagnostic, typeOnlyDeclaration: TypeOnlyCompatibleAliasDeclaration | undefined, unescapedName: string) { - if (!typeOnlyDeclaration) return diagnostic; - return addRelatedInfo( - diagnostic, - createDiagnosticForNode( + function checkSymbolUsageInExpressionContext(symbol: Symbol, name: __String, useSite: Node) { + if (!isValidTypeOnlyAliasUseSite(useSite)) { + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(symbol); + if (typeOnlyDeclaration) { + const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier + ? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type + : Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type; + const unescapedName = unescapeLeadingUnderscores(name); + addTypeOnlyDeclarationRelatedInfo( + error(useSite, message, unescapedName), typeOnlyDeclaration, - typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here, - unescapedName)); - } - - function getIsDeferredContext(location: Node, lastLocation: Node | undefined): boolean { - if (location.kind !== SyntaxKind.ArrowFunction && location.kind !== SyntaxKind.FunctionExpression) { - // initializers in instance property declaration of class like entities are executed in constructor and thus deferred - return isTypeQueryNode(location) || (( - isFunctionLikeDeclaration(location) || - (location.kind === SyntaxKind.PropertyDeclaration && !isStatic(location)) - ) && (!lastLocation || lastLocation !== (location as SignatureDeclaration | PropertyDeclaration).name)); // A name is evaluated within the enclosing scope - so it shouldn't count as deferred - } - if (lastLocation && lastLocation === (location as FunctionExpression | ArrowFunction).name) { - return false; - } - // generator functions and async functions are not inlined in control flow when immediately invoked - if ((location as FunctionExpression | ArrowFunction).asteriskToken || hasSyntacticModifier(location, ModifierFlags.Async)) { - return true; + unescapedName); } - return !getImmediatelyInvokedFunctionExpression(location); } + } - function isSelfReferenceLocation(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ModuleDeclaration: // For `namespace N { N; }` - return true; - default: - return false; - } + function addTypeOnlyDeclarationRelatedInfo(diagnostic: Diagnostic, typeOnlyDeclaration: TypeOnlyCompatibleAliasDeclaration | undefined, unescapedName: string) { + if (!typeOnlyDeclaration) return diagnostic; + return addRelatedInfo( + diagnostic, + createDiagnosticForNode( + typeOnlyDeclaration, + typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here, + unescapedName)); + } + + function getIsDeferredContext(location: Node, lastLocation: Node | undefined): boolean { + if (location.kind !== SyntaxKind.ArrowFunction && location.kind !== SyntaxKind.FunctionExpression) { + // initializers in instance property declaration of class like entities are executed in constructor and thus deferred + return isTypeQueryNode(location) || (( + isFunctionLikeDeclaration(location) || + (location.kind === SyntaxKind.PropertyDeclaration && !isStatic(location)) + ) && (!lastLocation || lastLocation !== (location as SignatureDeclaration | PropertyDeclaration).name)); // A name is evaluated within the enclosing scope - so it shouldn't count as deferred + } + if (lastLocation && lastLocation === (location as FunctionExpression | ArrowFunction).name) { + return false; } + // generator functions and async functions are not inlined in control flow when immediately invoked + if ((location as FunctionExpression | ArrowFunction).asteriskToken || hasSyntacticModifier(location, ModifierFlags.Async)) { + return true; + } + return !getImmediatelyInvokedFunctionExpression(location); + } - function diagnosticName(nameArg: __String | Identifier | PrivateIdentifier) { - return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); + function isSelfReferenceLocation(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ModuleDeclaration: // For `namespace N { N; }` + return true; + default: + return false; } + } - function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) { - if (symbol.declarations) { - for (const decl of symbol.declarations) { - if (decl.kind === SyntaxKind.TypeParameter) { - const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; - if (parent === container) { - return !(isJSDocTemplateTag(decl.parent) && find((decl.parent.parent as JSDoc).tags!, isJSDocTypeAlias)); // TODO: GH#18217 - } + function diagnosticName(nameArg: __String | Identifier | PrivateIdentifier) { + return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); + } + + function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) { + if (symbol.declarations) { + for (const decl of symbol.declarations) { + if (decl.kind === SyntaxKind.TypeParameter) { + const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; + if (parent === container) { + return !(isJSDocTemplateTag(decl.parent) && find((decl.parent.parent as JSDoc).tags!, isJSDocTypeAlias)); // TODO: GH#18217 } } } + } + + return false; + } + function checkAndReportErrorForMissingPrefix(errorLocation: Node, name: __String, nameArg: __String | Identifier): boolean { + if (!isIdentifier(errorLocation) || errorLocation.escapedText !== name || isTypeReferenceIdentifier(errorLocation) || isInTypeQuery(errorLocation)) { return false; } - function checkAndReportErrorForMissingPrefix(errorLocation: Node, name: __String, nameArg: __String | Identifier): boolean { - if (!isIdentifier(errorLocation) || errorLocation.escapedText !== name || isTypeReferenceIdentifier(errorLocation) || isInTypeQuery(errorLocation)) { - return false; - } + const container = getThisContainer(errorLocation, /*includeArrowFunctions*/ false); + let location = container; + while (location) { + if (isClassLike(location.parent)) { + const classSymbol = getSymbolOfNode(location.parent); + if (!classSymbol) { + break; + } - const container = getThisContainer(errorLocation, /*includeArrowFunctions*/ false); - let location = container; - while (location) { - if (isClassLike(location.parent)) { - const classSymbol = getSymbolOfNode(location.parent); - if (!classSymbol) { - break; - } + // Check to see if a static member exists. + const constructorType = getTypeOfSymbol(classSymbol); + if (getPropertyOfType(constructorType, name)) { + error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0, diagnosticName(nameArg), symbolToString(classSymbol)); + return true; + } - // Check to see if a static member exists. - const constructorType = getTypeOfSymbol(classSymbol); - if (getPropertyOfType(constructorType, name)) { - error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0, diagnosticName(nameArg), symbolToString(classSymbol)); + // No static member is present. + // Check if we're in an instance method and look for a relevant instance member. + if (location === container && !isStatic(location)) { + const instanceType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType!; // TODO: GH#18217 + if (getPropertyOfType(instanceType, name)) { + error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0, diagnosticName(nameArg)); return true; } - - // No static member is present. - // Check if we're in an instance method and look for a relevant instance member. - if (location === container && !isStatic(location)) { - const instanceType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType!; // TODO: GH#18217 - if (getPropertyOfType(instanceType, name)) { - error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0, diagnosticName(nameArg)); - return true; - } - } } - - location = location.parent; } - return false; + + location = location.parent; } + return false; + } - function checkAndReportErrorForExtendingInterface(errorLocation: Node): boolean { - const expression = getEntityNameForExtendingInterface(errorLocation); - if (expression && resolveEntityName(expression, SymbolFlags.Interface, /*ignoreErrors*/ true)) { - error(errorLocation, Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements, getTextOfNode(expression)); - return true; - } - return false; + function checkAndReportErrorForExtendingInterface(errorLocation: Node): boolean { + const expression = getEntityNameForExtendingInterface(errorLocation); + if (expression && resolveEntityName(expression, SymbolFlags.Interface, /*ignoreErrors*/ true)) { + error(errorLocation, Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements, getTextOfNode(expression)); + return true; } - /** - * Climbs up parents to an ExpressionWithTypeArguments, and returns its expression, - * but returns undefined if that expression is not an EntityNameExpression. - */ - function getEntityNameForExtendingInterface(node: Node): EntityNameExpression | undefined { - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: - return node.parent ? getEntityNameForExtendingInterface(node.parent) : undefined; - case SyntaxKind.ExpressionWithTypeArguments: - if (isEntityNameExpression((node as ExpressionWithTypeArguments).expression)) { - return (node as ExpressionWithTypeArguments).expression as EntityNameExpression; - } - // falls through - default: - return undefined; - } + return false; + } + /** + * Climbs up parents to an ExpressionWithTypeArguments, and returns its expression, + * but returns undefined if that expression is not an EntityNameExpression. + */ + function getEntityNameForExtendingInterface(node: Node): EntityNameExpression | undefined { + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: + return node.parent ? getEntityNameForExtendingInterface(node.parent) : undefined; + case SyntaxKind.ExpressionWithTypeArguments: + if (isEntityNameExpression((node as ExpressionWithTypeArguments).expression)) { + return (node as ExpressionWithTypeArguments).expression as EntityNameExpression; + } + // falls through + default: + return undefined; } + } - function checkAndReportErrorForUsingTypeAsNamespace(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(errorLocation) ? SymbolFlags.Value : 0); - if (meaning === namespaceMeaning) { - const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~namespaceMeaning, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - const parent = errorLocation.parent; - if (symbol) { - if (isQualifiedName(parent)) { - Debug.assert(parent.left === errorLocation, "Should only be resolving left side of qualified name as a namespace"); - const propName = parent.right.escapedText; - const propType = getPropertyOfType(getDeclaredTypeOfSymbol(symbol), propName); - if (propType) { - error( - parent, - Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1, - unescapeLeadingUnderscores(name), - unescapeLeadingUnderscores(propName), - ); - return true; - } + function checkAndReportErrorForUsingTypeAsNamespace(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(errorLocation) ? SymbolFlags.Value : 0); + if (meaning === namespaceMeaning) { + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~namespaceMeaning, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); + const parent = errorLocation.parent; + if (symbol) { + if (isQualifiedName(parent)) { + Debug.assert(parent.left === errorLocation, "Should only be resolving left side of qualified name as a namespace"); + const propName = parent.right.escapedText; + const propType = getPropertyOfType(getDeclaredTypeOfSymbol(symbol), propName); + if (propType) { + error( + parent, + Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1, + unescapeLeadingUnderscores(name), + unescapeLeadingUnderscores(propName), + ); + return true; } - error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_namespace_here, unescapeLeadingUnderscores(name)); - return true; } + error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_namespace_here, unescapeLeadingUnderscores(name)); + return true; } - - return false; } - function checkAndReportErrorForUsingValueAsType(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - if (meaning & (SymbolFlags.Type & ~SymbolFlags.Namespace)) { - const symbol = resolveSymbol(resolveName(errorLocation, name, ~SymbolFlags.Type & SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - if (symbol && !(symbol.flags & SymbolFlags.Namespace)) { - error(errorLocation, Diagnostics._0_refers_to_a_value_but_is_being_used_as_a_type_here_Did_you_mean_typeof_0, unescapeLeadingUnderscores(name)); - return true; - } + return false; + } + + function checkAndReportErrorForUsingValueAsType(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & (SymbolFlags.Type & ~SymbolFlags.Namespace)) { + const symbol = resolveSymbol(resolveName(errorLocation, name, ~SymbolFlags.Type & SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); + if (symbol && !(symbol.flags & SymbolFlags.Namespace)) { + error(errorLocation, Diagnostics._0_refers_to_a_value_but_is_being_used_as_a_type_here_Did_you_mean_typeof_0, unescapeLeadingUnderscores(name)); + return true; } - return false; } + return false; + } + + function isPrimitiveTypeName(name: __String) { + return name === "any" || name === "string" || name === "number" || name === "boolean" || name === "never" || name === "unknown"; + } - function isPrimitiveTypeName(name: __String) { - return name === "any" || name === "string" || name === "number" || name === "boolean" || name === "never" || name === "unknown"; + function checkAndReportErrorForExportingPrimitiveType(errorLocation: Node, name: __String): boolean { + if (isPrimitiveTypeName(name) && errorLocation.parent.kind === SyntaxKind.ExportSpecifier) { + error(errorLocation, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, name as string); + return true; } + return false; + } - function checkAndReportErrorForExportingPrimitiveType(errorLocation: Node, name: __String): boolean { - if (isPrimitiveTypeName(name) && errorLocation.parent.kind === SyntaxKind.ExportSpecifier) { - error(errorLocation, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, name as string); + function checkAndReportErrorForUsingTypeAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & (SymbolFlags.Value & ~SymbolFlags.NamespaceModule)) { + if (isPrimitiveTypeName(name)) { + error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, unescapeLeadingUnderscores(name)); return true; } - return false; - } - - function checkAndReportErrorForUsingTypeAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - if (meaning & (SymbolFlags.Value & ~SymbolFlags.NamespaceModule)) { - if (isPrimitiveTypeName(name)) { - error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, unescapeLeadingUnderscores(name)); - return true; + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); + if (symbol && !(symbol.flags & SymbolFlags.NamespaceModule)) { + const rawName = unescapeLeadingUnderscores(name); + if (isES2015OrLaterConstructorName(name)) { + error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later, rawName); } - const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - if (symbol && !(symbol.flags & SymbolFlags.NamespaceModule)) { - const rawName = unescapeLeadingUnderscores(name); - if (isES2015OrLaterConstructorName(name)) { - error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later, rawName); - } - else if (maybeMappedType(errorLocation, symbol)) { - error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Did_you_mean_to_use_1_in_0, rawName, rawName === "K" ? "P" : "K"); - } - else { - error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, rawName); - } - return true; + else if (maybeMappedType(errorLocation, symbol)) { + error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Did_you_mean_to_use_1_in_0, rawName, rawName === "K" ? "P" : "K"); + } + else { + error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, rawName); } + return true; } - return false; } + return false; + } - function maybeMappedType(node: Node, symbol: Symbol) { - const container = findAncestor(node.parent, n => - isComputedPropertyName(n) || isPropertySignature(n) ? false : isTypeLiteralNode(n) || "quit") as TypeLiteralNode | undefined; - if (container && container.members.length === 1) { - const type = getDeclaredTypeOfSymbol(symbol); - return !!(type.flags & TypeFlags.Union) && allTypesAssignableToKind(type, TypeFlags.StringOrNumberLiteral, /*strict*/ true); - } - return false; + function maybeMappedType(node: Node, symbol: Symbol) { + const container = findAncestor(node.parent, n => + isComputedPropertyName(n) || isPropertySignature(n) ? false : isTypeLiteralNode(n) || "quit") as TypeLiteralNode | undefined; + if (container && container.members.length === 1) { + const type = getDeclaredTypeOfSymbol(symbol); + return !!(type.flags & TypeFlags.Union) && allTypesAssignableToKind(type, TypeFlags.StringOrNumberLiteral, /*strict*/ true); } + return false; + } - function isES2015OrLaterConstructorName(n: __String) { - switch (n) { - case "Promise": - case "Symbol": - case "Map": - case "WeakMap": - case "Set": - case "WeakSet": - return true; - } - return false; + function isES2015OrLaterConstructorName(n: __String) { + switch (n) { + case "Promise": + case "Symbol": + case "Map": + case "WeakMap": + case "Set": + case "WeakSet": + return true; } + return false; + } - function checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - if (meaning & (SymbolFlags.Value & ~SymbolFlags.NamespaceModule & ~SymbolFlags.Type)) { - const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.NamespaceModule & ~SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - if (symbol) { - error( - errorLocation, - Diagnostics.Cannot_use_namespace_0_as_a_value, - unescapeLeadingUnderscores(name)); - return true; - } + function checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & (SymbolFlags.Value & ~SymbolFlags.NamespaceModule & ~SymbolFlags.Type)) { + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.NamespaceModule & ~SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); + if (symbol) { + error( + errorLocation, + Diagnostics.Cannot_use_namespace_0_as_a_value, + unescapeLeadingUnderscores(name)); + return true; } - else if (meaning & (SymbolFlags.Type & ~SymbolFlags.NamespaceModule & ~SymbolFlags.Value)) { - const symbol = resolveSymbol(resolveName(errorLocation, name, (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) & ~SymbolFlags.Type, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - if (symbol) { - error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_type, unescapeLeadingUnderscores(name)); - return true; - } + } + else if (meaning & (SymbolFlags.Type & ~SymbolFlags.NamespaceModule & ~SymbolFlags.Value)) { + const symbol = resolveSymbol(resolveName(errorLocation, name, (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) & ~SymbolFlags.Type, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); + if (symbol) { + error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_type, unescapeLeadingUnderscores(name)); + return true; } - return false; } + return false; + } - function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void { - Debug.assert(!!(result.flags & SymbolFlags.BlockScopedVariable || result.flags & SymbolFlags.Class || result.flags & SymbolFlags.Enum)); - if (result.flags & (SymbolFlags.Function | SymbolFlags.FunctionScopedVariable | SymbolFlags.Assignment) && result.flags & SymbolFlags.Class) { - // constructor functions aren't block scoped - return; - } - // Block-scoped variables cannot be used before their definition - const declaration = result.declarations?.find( - d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration)); + function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void { + Debug.assert(!!(result.flags & SymbolFlags.BlockScopedVariable || result.flags & SymbolFlags.Class || result.flags & SymbolFlags.Enum)); + if (result.flags & (SymbolFlags.Function | SymbolFlags.FunctionScopedVariable | SymbolFlags.Assignment) && result.flags & SymbolFlags.Class) { + // constructor functions aren't block scoped + return; + } + // Block-scoped variables cannot be used before their definition + const declaration = result.declarations?.find( + d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration)); - if (declaration === undefined) return Debug.fail("checkResolvedBlockScopedVariable could not find block-scoped declaration"); + if (declaration === undefined) return Debug.fail("checkResolvedBlockScopedVariable could not find block-scoped declaration"); - if (!(declaration.flags & NodeFlags.Ambient) && !isBlockScopedNameDeclaredBeforeUse(declaration, errorLocation)) { - let diagnosticMessage; - const declarationName = declarationNameToString(getNameOfDeclaration(declaration)); - if (result.flags & SymbolFlags.BlockScopedVariable) { - diagnosticMessage = error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationName); - } - else if (result.flags & SymbolFlags.Class) { - diagnosticMessage = error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationName); - } - else if (result.flags & SymbolFlags.RegularEnum) { + if (!(declaration.flags & NodeFlags.Ambient) && !isBlockScopedNameDeclaredBeforeUse(declaration, errorLocation)) { + let diagnosticMessage; + const declarationName = declarationNameToString(getNameOfDeclaration(declaration)); + if (result.flags & SymbolFlags.BlockScopedVariable) { + diagnosticMessage = error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationName); + } + else if (result.flags & SymbolFlags.Class) { + diagnosticMessage = error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationName); + } + else if (result.flags & SymbolFlags.RegularEnum) { + diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); + } + else { + Debug.assert(!!(result.flags & SymbolFlags.ConstEnum)); + if (shouldPreserveConstEnums(compilerOptions)) { diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); } - else { - Debug.assert(!!(result.flags & SymbolFlags.ConstEnum)); - if (shouldPreserveConstEnums(compilerOptions)) { - diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); - } - } + } - if (diagnosticMessage) { - addRelatedInfo(diagnosticMessage, - createDiagnosticForNode(declaration, Diagnostics._0_is_declared_here, declarationName) - ); - } + if (diagnosticMessage) { + addRelatedInfo(diagnosticMessage, + createDiagnosticForNode(declaration, Diagnostics._0_is_declared_here, declarationName) + ); } } + } - /* Starting from 'initial' node walk up the parent chain until 'stopAt' node is reached. - * If at any point current node is equal to 'parent' node - return true. - * Return false if 'stopAt' node is reached or isFunctionLike(current) === true. - */ - function isSameScopeDescendentOf(initial: Node, parent: Node | undefined, stopAt: Node): boolean { - return !!parent && !!findAncestor(initial, n => n === stopAt || isFunctionLike(n) ? "quit" : n === parent); - } + /* Starting from 'initial' node walk up the parent chain until 'stopAt' node is reached. + * If at any point current node is equal to 'parent' node - return true. + * Return false if 'stopAt' node is reached or isFunctionLike(current) === true. + */ + function isSameScopeDescendentOf(initial: Node, parent: Node | undefined, stopAt: Node): boolean { + return !!parent && !!findAncestor(initial, n => n === stopAt || isFunctionLike(n) ? "quit" : n === parent); + } - function getAnyImportSyntax(node: Node): AnyImportSyntax | undefined { - switch (node.kind) { - case SyntaxKind.ImportEqualsDeclaration: - return node as ImportEqualsDeclaration; - case SyntaxKind.ImportClause: - return (node as ImportClause).parent; - case SyntaxKind.NamespaceImport: - return (node as NamespaceImport).parent.parent; - case SyntaxKind.ImportSpecifier: - return (node as ImportSpecifier).parent.parent.parent; - default: - return undefined; - } + function getAnyImportSyntax(node: Node): AnyImportSyntax | undefined { + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return node as ImportEqualsDeclaration; + case SyntaxKind.ImportClause: + return (node as ImportClause).parent; + case SyntaxKind.NamespaceImport: + return (node as NamespaceImport).parent.parent; + case SyntaxKind.ImportSpecifier: + return (node as ImportSpecifier).parent.parent.parent; + default: + return undefined; } + } - function getDeclarationOfAliasSymbol(symbol: Symbol): Declaration | undefined { - return symbol.declarations && findLast(symbol.declarations, isAliasSymbolDeclaration); - } + function getDeclarationOfAliasSymbol(symbol: Symbol): Declaration | undefined { + return symbol.declarations && findLast(symbol.declarations, isAliasSymbolDeclaration); + } - /** - * An alias symbol is created by one of the following declarations: - * import = ... - * import from ... - * import * as from ... - * import { x as } from ... - * export { x as } from ... - * export * as ns from ... - * export = - * export default - * module.exports = - * {} - * {name: } - * const { x } = require ... - */ - function isAliasSymbolDeclaration(node: Node): boolean { - return node.kind === SyntaxKind.ImportEqualsDeclaration - || node.kind === SyntaxKind.NamespaceExportDeclaration - || node.kind === SyntaxKind.ImportClause && !!(node as ImportClause).name - || node.kind === SyntaxKind.NamespaceImport - || node.kind === SyntaxKind.NamespaceExport - || node.kind === SyntaxKind.ImportSpecifier - || node.kind === SyntaxKind.ExportSpecifier - || node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node as ExportAssignment) - || isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports && exportAssignmentIsAlias(node) - || isAccessExpression(node) - && isBinaryExpression(node.parent) - && node.parent.left === node - && node.parent.operatorToken.kind === SyntaxKind.EqualsToken - && isAliasableOrJsExpression(node.parent.right) - || node.kind === SyntaxKind.ShorthandPropertyAssignment - || node.kind === SyntaxKind.PropertyAssignment && isAliasableOrJsExpression((node as PropertyAssignment).initializer) - || isRequireVariableDeclaration(node); - } - - function isAliasableOrJsExpression(e: Expression) { - return isAliasableExpression(e) || isFunctionExpression(e) && isJSConstructor(e); - } - - function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration | VariableDeclaration, dontResolveAlias: boolean): Symbol | undefined { - const commonJSPropertyAccess = getCommonJSPropertyAccess(node); - if (commonJSPropertyAccess) { - const name = (getLeftmostAccessExpression(commonJSPropertyAccess.expression) as CallExpression).arguments[0] as StringLiteral; - return isIdentifier(commonJSPropertyAccess.name) - ? resolveSymbol(getPropertyOfType(resolveExternalModuleTypeByLiteral(name), commonJSPropertyAccess.name.escapedText)) - : undefined; - } - if (isVariableDeclaration(node) || node.moduleReference.kind === SyntaxKind.ExternalModuleReference) { - const immediate = resolveExternalModuleName( - node, - getExternalModuleRequireArgument(node) || getExternalModuleImportEqualsDeclarationExpression(node)); - const resolved = resolveExternalModuleSymbol(immediate); - markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); - return resolved; - } - const resolved = getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); - checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node, resolved); - return resolved; - } + /** + * An alias symbol is created by one of the following declarations: + * import = ... + * import from ... + * import * as from ... + * import { x as } from ... + * export { x as } from ... + * export * as ns from ... + * export = + * export default + * module.exports = + * {} + * {name: } + * const { x } = require ... + */ + function isAliasSymbolDeclaration(node: Node): boolean { + return node.kind === SyntaxKind.ImportEqualsDeclaration + || node.kind === SyntaxKind.NamespaceExportDeclaration + || node.kind === SyntaxKind.ImportClause && !!(node as ImportClause).name + || node.kind === SyntaxKind.NamespaceImport + || node.kind === SyntaxKind.NamespaceExport + || node.kind === SyntaxKind.ImportSpecifier + || node.kind === SyntaxKind.ExportSpecifier + || node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node as ExportAssignment) + || isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports && exportAssignmentIsAlias(node) + || isAccessExpression(node) + && isBinaryExpression(node.parent) + && node.parent.left === node + && node.parent.operatorToken.kind === SyntaxKind.EqualsToken + && isAliasableOrJsExpression(node.parent.right) + || node.kind === SyntaxKind.ShorthandPropertyAssignment + || node.kind === SyntaxKind.PropertyAssignment && isAliasableOrJsExpression((node as PropertyAssignment).initializer) + || isRequireVariableDeclaration(node); + } - function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { - if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) { - const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!; - const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier; - const message = isExport - ? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type - : Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type; - const relatedMessage = isExport - ? Diagnostics._0_was_exported_here - : Diagnostics._0_was_imported_here; + function isAliasableOrJsExpression(e: Expression) { + return isAliasableExpression(e) || isFunctionExpression(e) && isJSConstructor(e); + } - const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText); - addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); - } + function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration | VariableDeclaration, dontResolveAlias: boolean): Symbol | undefined { + const commonJSPropertyAccess = getCommonJSPropertyAccess(node); + if (commonJSPropertyAccess) { + const name = (getLeftmostAccessExpression(commonJSPropertyAccess.expression) as CallExpression).arguments[0] as StringLiteral; + return isIdentifier(commonJSPropertyAccess.name) + ? resolveSymbol(getPropertyOfType(resolveExternalModuleTypeByLiteral(name), commonJSPropertyAccess.name.escapedText)) + : undefined; } - - function resolveExportByName(moduleSymbol: Symbol, name: __String, sourceNode: TypeOnlyCompatibleAliasDeclaration | undefined, dontResolveAlias: boolean) { - const exportValue = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); - const exportSymbol = exportValue ? getPropertyOfType(getTypeOfSymbol(exportValue), name) : moduleSymbol.exports!.get(name); - const resolved = resolveSymbol(exportSymbol, dontResolveAlias); - markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, exportSymbol, resolved, /*overwriteEmpty*/ false); + if (isVariableDeclaration(node) || node.moduleReference.kind === SyntaxKind.ExternalModuleReference) { + const immediate = resolveExternalModuleName( + node, + getExternalModuleRequireArgument(node) || getExternalModuleImportEqualsDeclarationExpression(node)); + const resolved = resolveExternalModuleSymbol(immediate); + markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); return resolved; } + const resolved = getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); + checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node, resolved); + return resolved; + } - function isSyntacticDefault(node: Node) { - return ((isExportAssignment(node) && !node.isExportEquals) || hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node)); - } + function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { + if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) { + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!; + const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier; + const message = isExport + ? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type + : Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type; + const relatedMessage = isExport + ? Diagnostics._0_was_exported_here + : Diagnostics._0_was_imported_here; - function getUsageModeForExpression(usage: Expression) { - return isStringLiteralLike(usage) ? getModeForUsageLocation(getSourceFileOfNode(usage), usage) : undefined; + const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText); + addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); } + } - function isESMFormatImportImportingCommonjsFormatFile(usageMode: SourceFile["impliedNodeFormat"], targetMode: SourceFile["impliedNodeFormat"]) { - return usageMode === ModuleKind.ESNext && targetMode === ModuleKind.CommonJS; - } + function resolveExportByName(moduleSymbol: Symbol, name: __String, sourceNode: TypeOnlyCompatibleAliasDeclaration | undefined, dontResolveAlias: boolean) { + const exportValue = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); + const exportSymbol = exportValue ? getPropertyOfType(getTypeOfSymbol(exportValue), name) : moduleSymbol.exports!.get(name); + const resolved = resolveSymbol(exportSymbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, exportSymbol, resolved, /*overwriteEmpty*/ false); + return resolved; + } - function isOnlyImportedAsDefault(usage: Expression) { - const usageMode = getUsageModeForExpression(usage); - return usageMode === ModuleKind.ESNext && endsWith((usage as StringLiteralLike).text, Extension.Json); - } + function isSyntacticDefault(node: Node) { + return ((isExportAssignment(node) && !node.isExportEquals) || hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node)); + } - function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean, usage: Expression) { - const usageMode = file && getUsageModeForExpression(usage); - if (file && usageMode !== undefined) { - const result = isESMFormatImportImportingCommonjsFormatFile(usageMode, file.impliedNodeFormat); - if (usageMode === ModuleKind.ESNext || result) { - return result; - } - // fallthrough on cjs usages so we imply defaults for interop'd imports, too + function getUsageModeForExpression(usage: Expression) { + return isStringLiteralLike(usage) ? getModeForUsageLocation(getSourceFileOfNode(usage), usage) : undefined; + } + + function isESMFormatImportImportingCommonjsFormatFile(usageMode: SourceFile["impliedNodeFormat"], targetMode: SourceFile["impliedNodeFormat"]) { + return usageMode === ModuleKind.ESNext && targetMode === ModuleKind.CommonJS; + } + + function isOnlyImportedAsDefault(usage: Expression) { + const usageMode = getUsageModeForExpression(usage); + return usageMode === ModuleKind.ESNext && endsWith((usage as StringLiteralLike).text, Extension.Json); + } + + function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean, usage: Expression) { + const usageMode = file && getUsageModeForExpression(usage); + if (file && usageMode !== undefined) { + const result = isESMFormatImportImportingCommonjsFormatFile(usageMode, file.impliedNodeFormat); + if (usageMode === ModuleKind.ESNext || result) { + return result; } - if (!allowSyntheticDefaultImports) { + // fallthrough on cjs usages so we imply defaults for interop'd imports, too + } + if (!allowSyntheticDefaultImports) { + return false; + } + // Declaration files (and ambient modules) + if (!file || file.isDeclarationFile) { + // Definitely cannot have a synthetic default if they have a syntactic default member specified + const defaultExportSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, /*sourceNode*/ undefined, /*dontResolveAlias*/ true); // Dont resolve alias because we want the immediately exported symbol's declaration + if (defaultExportSymbol && some(defaultExportSymbol.declarations, isSyntacticDefault)) { return false; } - // Declaration files (and ambient modules) - if (!file || file.isDeclarationFile) { - // Definitely cannot have a synthetic default if they have a syntactic default member specified - const defaultExportSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, /*sourceNode*/ undefined, /*dontResolveAlias*/ true); // Dont resolve alias because we want the immediately exported symbol's declaration - if (defaultExportSymbol && some(defaultExportSymbol.declarations, isSyntacticDefault)) { - return false; - } - // It _might_ still be incorrect to assume there is no __esModule marker on the import at runtime, even if there is no `default` member - // So we check a bit more, - if (resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias)) { - // If there is an `__esModule` specified in the declaration (meaning someone explicitly added it or wrote it in their code), - // it definitely is a module and does not have a synthetic default - return false; - } - // There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set - // Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member - // as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm - return true; - } - // TypeScript files never have a synthetic default (as they are always emitted with an __esModule marker) _unless_ they contain an export= statement - if (!isSourceFileJS(file)) { - return hasExportAssignmentSymbol(moduleSymbol); + // It _might_ still be incorrect to assume there is no __esModule marker on the import at runtime, even if there is no `default` member + // So we check a bit more, + if (resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias)) { + // If there is an `__esModule` specified in the declaration (meaning someone explicitly added it or wrote it in their code), + // it definitely is a module and does not have a synthetic default + return false; } - // JS files have a synthetic default if they do not contain ES2015+ module syntax (export = is not valid in js) _and_ do not have an __esModule marker - return !file.externalModuleIndicator && !resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias); + // There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set + // Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member + // as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm + return true; } + // TypeScript files never have a synthetic default (as they are always emitted with an __esModule marker) _unless_ they contain an export= statement + if (!isSourceFileJS(file)) { + return hasExportAssignmentSymbol(moduleSymbol); + } + // JS files have a synthetic default if they do not contain ES2015+ module syntax (export = is not valid in js) _and_ do not have an __esModule marker + return !file.externalModuleIndicator && !resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias); + } - function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol | undefined { - const moduleSymbol = resolveExternalModuleName(node, node.parent.moduleSpecifier); - if (moduleSymbol) { - let exportDefaultSymbol: Symbol | undefined; - if (isShorthandAmbientModuleSymbol(moduleSymbol)) { - exportDefaultSymbol = moduleSymbol; - } - else { - exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, node, dontResolveAlias); - } - - const file = moduleSymbol.declarations?.find(isSourceFile); - const hasDefaultOnly = isOnlyImportedAsDefault(node.parent.moduleSpecifier); - const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, node.parent.moduleSpecifier); - if (!exportDefaultSymbol && !hasSyntheticDefault && !hasDefaultOnly) { - if (hasExportAssignmentSymbol(moduleSymbol)) { - const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop"; - const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); - const exportAssignment = exportEqualsSymbol!.valueDeclaration; - const err = error(node.name, Diagnostics.Module_0_can_only_be_default_imported_using_the_1_flag, symbolToString(moduleSymbol), compilerOptionName); - - if (exportAssignment) { - addRelatedInfo(err, createDiagnosticForNode( - exportAssignment, - Diagnostics.This_module_is_declared_with_using_export_and_can_only_be_used_with_a_default_import_when_using_the_0_flag, - compilerOptionName - )); - } - } - else { - reportNonDefaultExport(moduleSymbol, node); + function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol | undefined { + const moduleSymbol = resolveExternalModuleName(node, node.parent.moduleSpecifier); + if (moduleSymbol) { + let exportDefaultSymbol: Symbol | undefined; + if (isShorthandAmbientModuleSymbol(moduleSymbol)) { + exportDefaultSymbol = moduleSymbol; + } + else { + exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, node, dontResolveAlias); + } + + const file = moduleSymbol.declarations?.find(isSourceFile); + const hasDefaultOnly = isOnlyImportedAsDefault(node.parent.moduleSpecifier); + const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, node.parent.moduleSpecifier); + if (!exportDefaultSymbol && !hasSyntheticDefault && !hasDefaultOnly) { + if (hasExportAssignmentSymbol(moduleSymbol)) { + const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop"; + const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); + const exportAssignment = exportEqualsSymbol!.valueDeclaration; + const err = error(node.name, Diagnostics.Module_0_can_only_be_default_imported_using_the_1_flag, symbolToString(moduleSymbol), compilerOptionName); + + if (exportAssignment) { + addRelatedInfo(err, createDiagnosticForNode( + exportAssignment, + Diagnostics.This_module_is_declared_with_using_export_and_can_only_be_used_with_a_default_import_when_using_the_0_flag, + compilerOptionName + )); } } - else if (hasSyntheticDefault || hasDefaultOnly) { - // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present - const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); - markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteTypeOnly*/ false); - return resolved; + else { + reportNonDefaultExport(moduleSymbol, node); } - markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteTypeOnly*/ false); - return exportDefaultSymbol; } + else if (hasSyntheticDefault || hasDefaultOnly) { + // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present + const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteTypeOnly*/ false); + return resolved; + } + markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteTypeOnly*/ false); + return exportDefaultSymbol; } + } - function reportNonDefaultExport(moduleSymbol: Symbol, node: ImportClause) { - if (moduleSymbol.exports?.has(node.symbol.escapedName)) { - error( - node.name, - Diagnostics.Module_0_has_no_default_export_Did_you_mean_to_use_import_1_from_0_instead, - symbolToString(moduleSymbol), - symbolToString(node.symbol), - ); - } - else { - const diagnostic = error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); - const exportStar = moduleSymbol.exports?.get(InternalSymbolName.ExportStar); - if (exportStar) { - const defaultExport = exportStar.declarations?.find(decl => !!( - isExportDeclaration(decl) && decl.moduleSpecifier && - resolveExternalModuleName(decl, decl.moduleSpecifier)?.exports?.has(InternalSymbolName.Default) - )); - if (defaultExport) { - addRelatedInfo(diagnostic, createDiagnosticForNode(defaultExport, Diagnostics.export_Asterisk_does_not_re_export_a_default)); - } + function reportNonDefaultExport(moduleSymbol: Symbol, node: ImportClause) { + if (moduleSymbol.exports?.has(node.symbol.escapedName)) { + error( + node.name, + Diagnostics.Module_0_has_no_default_export_Did_you_mean_to_use_import_1_from_0_instead, + symbolToString(moduleSymbol), + symbolToString(node.symbol), + ); + } + else { + const diagnostic = error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); + const exportStar = moduleSymbol.exports?.get(InternalSymbolName.ExportStar); + if (exportStar) { + const defaultExport = exportStar.declarations?.find(decl => !!( + isExportDeclaration(decl) && decl.moduleSpecifier && + resolveExternalModuleName(decl, decl.moduleSpecifier)?.exports?.has(InternalSymbolName.Default) + )); + if (defaultExport) { + addRelatedInfo(diagnostic, createDiagnosticForNode(defaultExport, Diagnostics.export_Asterisk_does_not_re_export_a_default)); } } } + } - function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { - const moduleSpecifier = node.parent.parent.moduleSpecifier; - const immediate = resolveExternalModuleName(node, moduleSpecifier); - const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); - markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); - return resolved; - } + function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { + const moduleSpecifier = node.parent.parent.moduleSpecifier; + const immediate = resolveExternalModuleName(node, moduleSpecifier); + const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); + markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); + return resolved; + } - function getTargetOfNamespaceExport(node: NamespaceExport, dontResolveAlias: boolean): Symbol | undefined { - const moduleSpecifier = node.parent.moduleSpecifier; - const immediate = moduleSpecifier && resolveExternalModuleName(node, moduleSpecifier); - const resolved = moduleSpecifier && resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); - markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); - return resolved; + function getTargetOfNamespaceExport(node: NamespaceExport, dontResolveAlias: boolean): Symbol | undefined { + const moduleSpecifier = node.parent.moduleSpecifier; + const immediate = moduleSpecifier && resolveExternalModuleName(node, moduleSpecifier); + const resolved = moduleSpecifier && resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); + markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); + return resolved; + } + + // This function creates a synthetic symbol that combines the value side of one symbol with the + // type/namespace side of another symbol. Consider this example: + // + // declare module graphics { + // interface Point { + // x: number; + // y: number; + // } + // } + // declare var graphics: { + // Point: new (x: number, y: number) => graphics.Point; + // } + // declare module "graphics" { + // export = graphics; + // } + // + // An 'import { Point } from "graphics"' needs to create a symbol that combines the value side 'Point' + // property with the type/namespace side interface 'Point'. + function combineValueAndTypeSymbols(valueSymbol: Symbol, typeSymbol: Symbol): Symbol { + if (valueSymbol === unknownSymbol && typeSymbol === unknownSymbol) { + return unknownSymbol; + } + if (valueSymbol.flags & (SymbolFlags.Type | SymbolFlags.Namespace)) { + return valueSymbol; } + const result = createSymbol(valueSymbol.flags | typeSymbol.flags, valueSymbol.escapedName); + result.declarations = deduplicate(concatenate(valueSymbol.declarations, typeSymbol.declarations), equateValues); + result.parent = valueSymbol.parent || typeSymbol.parent; + if (valueSymbol.valueDeclaration) result.valueDeclaration = valueSymbol.valueDeclaration; + if (typeSymbol.members) result.members = new Map(typeSymbol.members); + if (valueSymbol.exports) result.exports = new Map(valueSymbol.exports); + return result; + } - // This function creates a synthetic symbol that combines the value side of one symbol with the - // type/namespace side of another symbol. Consider this example: - // - // declare module graphics { - // interface Point { - // x: number; - // y: number; - // } - // } - // declare var graphics: { - // Point: new (x: number, y: number) => graphics.Point; - // } - // declare module "graphics" { - // export = graphics; - // } - // - // An 'import { Point } from "graphics"' needs to create a symbol that combines the value side 'Point' - // property with the type/namespace side interface 'Point'. - function combineValueAndTypeSymbols(valueSymbol: Symbol, typeSymbol: Symbol): Symbol { - if (valueSymbol === unknownSymbol && typeSymbol === unknownSymbol) { - return unknownSymbol; - } - if (valueSymbol.flags & (SymbolFlags.Type | SymbolFlags.Namespace)) { - return valueSymbol; - } - const result = createSymbol(valueSymbol.flags | typeSymbol.flags, valueSymbol.escapedName); - result.declarations = deduplicate(concatenate(valueSymbol.declarations, typeSymbol.declarations), equateValues); - result.parent = valueSymbol.parent || typeSymbol.parent; - if (valueSymbol.valueDeclaration) result.valueDeclaration = valueSymbol.valueDeclaration; - if (typeSymbol.members) result.members = new Map(typeSymbol.members); - if (valueSymbol.exports) result.exports = new Map(valueSymbol.exports); - return result; + function getExportOfModule(symbol: Symbol, name: Identifier, specifier: Declaration, dontResolveAlias: boolean): Symbol | undefined { + if (symbol.flags & SymbolFlags.Module) { + const exportSymbol = getExportsOfSymbol(symbol).get(name.escapedText); + const resolved = resolveSymbol(exportSymbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false); + return resolved; } + } - function getExportOfModule(symbol: Symbol, name: Identifier, specifier: Declaration, dontResolveAlias: boolean): Symbol | undefined { - if (symbol.flags & SymbolFlags.Module) { - const exportSymbol = getExportsOfSymbol(symbol).get(name.escapedText); - const resolved = resolveSymbol(exportSymbol, dontResolveAlias); - markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false); - return resolved; + function getPropertyOfVariable(symbol: Symbol, name: __String): Symbol | undefined { + if (symbol.flags & SymbolFlags.Variable) { + const typeAnnotation = (symbol.valueDeclaration as VariableDeclaration).type; + if (typeAnnotation) { + return resolveSymbol(getPropertyOfType(getTypeFromTypeNode(typeAnnotation), name)); } } + } - function getPropertyOfVariable(symbol: Symbol, name: __String): Symbol | undefined { - if (symbol.flags & SymbolFlags.Variable) { - const typeAnnotation = (symbol.valueDeclaration as VariableDeclaration).type; - if (typeAnnotation) { - return resolveSymbol(getPropertyOfType(getTypeFromTypeNode(typeAnnotation), name)); - } - } + function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined { + const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration).moduleSpecifier!; + const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217 + const name = !isPropertyAccessExpression(specifier) && specifier.propertyName || specifier.name; + if (!isIdentifier(name)) { + return undefined; } + const suppressInteropError = name.escapedText === InternalSymbolName.Default && !!(compilerOptions.allowSyntheticDefaultImports || getESModuleInterop(compilerOptions)); + const targetSymbol = resolveESModuleSymbol(moduleSymbol, moduleSpecifier, /*dontResolveAlias*/ false, suppressInteropError); + if (targetSymbol) { + if (name.escapedText) { + if (isShorthandAmbientModuleSymbol(moduleSymbol)) { + return moduleSymbol; + } - function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined { - const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration).moduleSpecifier!; - const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217 - const name = !isPropertyAccessExpression(specifier) && specifier.propertyName || specifier.name; - if (!isIdentifier(name)) { - return undefined; - } - const suppressInteropError = name.escapedText === InternalSymbolName.Default && !!(compilerOptions.allowSyntheticDefaultImports || getESModuleInterop(compilerOptions)); - const targetSymbol = resolveESModuleSymbol(moduleSymbol, moduleSpecifier, /*dontResolveAlias*/ false, suppressInteropError); - if (targetSymbol) { - if (name.escapedText) { - if (isShorthandAmbientModuleSymbol(moduleSymbol)) { - return moduleSymbol; - } + let symbolFromVariable: Symbol | undefined; + // First check if module was specified with "export=". If so, get the member from the resolved type + if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports.get(InternalSymbolName.ExportEquals)) { + symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.escapedText, /*skipObjectFunctionPropertyAugment*/ true); + } + else { + symbolFromVariable = getPropertyOfVariable(targetSymbol, name.escapedText); + } + // if symbolFromVariable is export - get its final target + symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); - let symbolFromVariable: Symbol | undefined; - // First check if module was specified with "export=". If so, get the member from the resolved type - if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports.get(InternalSymbolName.ExportEquals)) { - symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.escapedText, /*skipObjectFunctionPropertyAugment*/ true); + let symbolFromModule = getExportOfModule(targetSymbol, name, specifier, dontResolveAlias); + if (symbolFromModule === undefined && name.escapedText === InternalSymbolName.Default) { + const file = moduleSymbol.declarations?.find(isSourceFile); + if (isOnlyImportedAsDefault(moduleSpecifier) || canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) { + symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); } - else { - symbolFromVariable = getPropertyOfVariable(targetSymbol, name.escapedText); - } - // if symbolFromVariable is export - get its final target - symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); + } - let symbolFromModule = getExportOfModule(targetSymbol, name, specifier, dontResolveAlias); - if (symbolFromModule === undefined && name.escapedText === InternalSymbolName.Default) { - const file = moduleSymbol.declarations?.find(isSourceFile); - if (isOnlyImportedAsDefault(moduleSpecifier) || canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) { - symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); + const symbol = symbolFromModule && symbolFromVariable && symbolFromModule !== symbolFromVariable ? + combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) : + symbolFromModule || symbolFromVariable; + if (!symbol) { + const moduleName = getFullyQualifiedName(moduleSymbol, node); + const declarationName = declarationNameToString(name); + const suggestion = getSuggestedSymbolForNonexistentModule(name, targetSymbol); + if (suggestion !== undefined) { + const suggestionName = symbolToString(suggestion); + const diagnostic = error(name, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, moduleName, declarationName, suggestionName); + if (suggestion.valueDeclaration) { + addRelatedInfo(diagnostic, + createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) + ); } } - - const symbol = symbolFromModule && symbolFromVariable && symbolFromModule !== symbolFromVariable ? - combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) : - symbolFromModule || symbolFromVariable; - if (!symbol) { - const moduleName = getFullyQualifiedName(moduleSymbol, node); - const declarationName = declarationNameToString(name); - const suggestion = getSuggestedSymbolForNonexistentModule(name, targetSymbol); - if (suggestion !== undefined) { - const suggestionName = symbolToString(suggestion); - const diagnostic = error(name, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, moduleName, declarationName, suggestionName); - if (suggestion.valueDeclaration) { - addRelatedInfo(diagnostic, - createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) - ); - } + else { + if (moduleSymbol.exports?.has(InternalSymbolName.Default)) { + error( + name, + Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_to_use_import_1_from_0_instead, + moduleName, + declarationName + ); } else { - if (moduleSymbol.exports?.has(InternalSymbolName.Default)) { - error( - name, - Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_to_use_import_1_from_0_instead, - moduleName, - declarationName - ); - } - else { - reportNonExportedMember(node, name, declarationName, moduleSymbol, moduleName); - } + reportNonExportedMember(node, name, declarationName, moduleSymbol, moduleName); } } - return symbol; } + return symbol; } } + } - function reportNonExportedMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, name: Identifier, declarationName: string, moduleSymbol: Symbol, moduleName: string): void { - const localSymbol = moduleSymbol.valueDeclaration?.locals?.get(name.escapedText); - const exports = moduleSymbol.exports; - if (localSymbol) { - const exportedEqualsSymbol = exports?.get(InternalSymbolName.ExportEquals); - if (exportedEqualsSymbol) { - getSymbolIfSameReference(exportedEqualsSymbol, localSymbol) ? reportInvalidImportEqualsExportMember(node, name, declarationName, moduleName) : - error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); - } - else { - const exportedSymbol = exports ? find(symbolsToArray(exports), symbol => !!getSymbolIfSameReference(symbol, localSymbol)) : undefined; - const diagnostic = exportedSymbol ? error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_exported_as_2, moduleName, declarationName, symbolToString(exportedSymbol)) : - error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_not_exported, moduleName, declarationName); - if (localSymbol.declarations) { - addRelatedInfo(diagnostic, - ...map(localSymbol.declarations, (decl, index) => - createDiagnosticForNode(decl, index === 0 ? Diagnostics._0_is_declared_here : Diagnostics.and_here, declarationName))); - } - } + function reportNonExportedMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, name: Identifier, declarationName: string, moduleSymbol: Symbol, moduleName: string): void { + const localSymbol = moduleSymbol.valueDeclaration?.locals?.get(name.escapedText); + const exports = moduleSymbol.exports; + if (localSymbol) { + const exportedEqualsSymbol = exports?.get(InternalSymbolName.ExportEquals); + if (exportedEqualsSymbol) { + getSymbolIfSameReference(exportedEqualsSymbol, localSymbol) ? reportInvalidImportEqualsExportMember(node, name, declarationName, moduleName) : + error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); } else { - error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); + const exportedSymbol = exports ? find(symbolsToArray(exports), symbol => !!getSymbolIfSameReference(symbol, localSymbol)) : undefined; + const diagnostic = exportedSymbol ? error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_exported_as_2, moduleName, declarationName, symbolToString(exportedSymbol)) : + error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_not_exported, moduleName, declarationName); + if (localSymbol.declarations) { + addRelatedInfo(diagnostic, + ...map(localSymbol.declarations, (decl, index) => + createDiagnosticForNode(decl, index === 0 ? Diagnostics._0_is_declared_here : Diagnostics.and_here, declarationName))); + } } } + else { + error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); + } + } - function reportInvalidImportEqualsExportMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, name: Identifier, declarationName: string, moduleName: string) { - if (moduleKind >= ModuleKind.ES2015) { - const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_a_default_import : - Diagnostics._0_can_only_be_imported_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; + function reportInvalidImportEqualsExportMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, name: Identifier, declarationName: string, moduleName: string) { + if (moduleKind >= ModuleKind.ES2015) { + const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_a_default_import : + Diagnostics._0_can_only_be_imported_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; + error(name, message, declarationName); + } + else { + if (isInJSFile(node)) { + const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_a_require_call_or_by_using_a_default_import : + Diagnostics._0_can_only_be_imported_by_using_a_require_call_or_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; error(name, message, declarationName); } else { - if (isInJSFile(node)) { - const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_a_require_call_or_by_using_a_default_import : - Diagnostics._0_can_only_be_imported_by_using_a_require_call_or_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; - error(name, message, declarationName); - } - else { - const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_import_1_require_2_or_a_default_import : - Diagnostics._0_can_only_be_imported_by_using_import_1_require_2_or_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; - error(name, message, declarationName, declarationName, moduleName); - } + const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_import_1_require_2_or_a_default_import : + Diagnostics._0_can_only_be_imported_by_using_import_1_require_2_or_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; + error(name, message, declarationName, declarationName, moduleName); } } + } - function getTargetOfImportSpecifier(node: ImportSpecifier | BindingElement, dontResolveAlias: boolean): Symbol | undefined { - const root = isBindingElement(node) ? getRootDeclaration(node) as VariableDeclaration : node.parent.parent.parent; - const commonJSPropertyAccess = getCommonJSPropertyAccess(root); - const resolved = getExternalModuleMember(root, commonJSPropertyAccess || node, dontResolveAlias); - const name = node.propertyName || node.name; - if (commonJSPropertyAccess && resolved && isIdentifier(name)) { - return resolveSymbol(getPropertyOfType(getTypeOfSymbol(resolved), name.escapedText), dontResolveAlias); - } - markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); - return resolved; + function getTargetOfImportSpecifier(node: ImportSpecifier | BindingElement, dontResolveAlias: boolean): Symbol | undefined { + const root = isBindingElement(node) ? getRootDeclaration(node) as VariableDeclaration : node.parent.parent.parent; + const commonJSPropertyAccess = getCommonJSPropertyAccess(root); + const resolved = getExternalModuleMember(root, commonJSPropertyAccess || node, dontResolveAlias); + const name = node.propertyName || node.name; + if (commonJSPropertyAccess && resolved && isIdentifier(name)) { + return resolveSymbol(getPropertyOfType(getTypeOfSymbol(resolved), name.escapedText), dontResolveAlias); } + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } - function getCommonJSPropertyAccess(node: Node) { - if (isVariableDeclaration(node) && node.initializer && isPropertyAccessExpression(node.initializer)) { - return node.initializer; - } + function getCommonJSPropertyAccess(node: Node) { + if (isVariableDeclaration(node) && node.initializer && isPropertyAccessExpression(node.initializer)) { + return node.initializer; } + } - function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol { - const resolved = resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias); - markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); - return resolved; - } + function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol { + const resolved = resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } - function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { - const resolved = node.parent.parent.moduleSpecifier ? - getExternalModuleMember(node.parent.parent, node, dontResolveAlias) : - resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias); - markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); - return resolved; - } + function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { + const resolved = node.parent.parent.moduleSpecifier ? + getExternalModuleMember(node.parent.parent, node, dontResolveAlias) : + resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } - function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined { - const expression = isExportAssignment(node) ? node.expression : node.right; - const resolved = getTargetOfAliasLikeExpression(expression, dontResolveAlias); - markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); - return resolved; - } + function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined { + const expression = isExportAssignment(node) ? node.expression : node.right; + const resolved = getTargetOfAliasLikeExpression(expression, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } - function getTargetOfAliasLikeExpression(expression: Expression, dontResolveAlias: boolean) { - if (isClassExpression(expression)) { - return checkExpressionCached(expression).symbol; - } - if (!isEntityName(expression) && !isEntityNameExpression(expression)) { - return undefined; - } - const aliasLike = resolveEntityName(expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontResolveAlias); - if (aliasLike) { - return aliasLike; - } - checkExpressionCached(expression); - return getNodeLinks(expression).resolvedSymbol; + function getTargetOfAliasLikeExpression(expression: Expression, dontResolveAlias: boolean) { + if (isClassExpression(expression)) { + return checkExpressionCached(expression).symbol; } - - function getTargetOfPropertyAssignment(node: PropertyAssignment, dontRecursivelyResolve: boolean): Symbol | undefined { - const expression = node.initializer; - return getTargetOfAliasLikeExpression(expression, dontRecursivelyResolve); + if (!isEntityName(expression) && !isEntityNameExpression(expression)) { + return undefined; + } + const aliasLike = resolveEntityName(expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontResolveAlias); + if (aliasLike) { + return aliasLike; } + checkExpressionCached(expression); + return getNodeLinks(expression).resolvedSymbol; + } - function getTargetOfAccessExpression(node: AccessExpression, dontRecursivelyResolve: boolean): Symbol | undefined { - if (!(isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken)) { - return undefined; - } + function getTargetOfPropertyAssignment(node: PropertyAssignment, dontRecursivelyResolve: boolean): Symbol | undefined { + const expression = node.initializer; + return getTargetOfAliasLikeExpression(expression, dontRecursivelyResolve); + } - return getTargetOfAliasLikeExpression(node.parent.right, dontRecursivelyResolve); + function getTargetOfAccessExpression(node: AccessExpression, dontRecursivelyResolve: boolean): Symbol | undefined { + if (!(isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken)) { + return undefined; } - function getTargetOfAliasDeclaration(node: Declaration, dontRecursivelyResolve = false): Symbol | undefined { - switch (node.kind) { - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.VariableDeclaration: - return getTargetOfImportEqualsDeclaration(node as ImportEqualsDeclaration | VariableDeclaration, dontRecursivelyResolve); - case SyntaxKind.ImportClause: - return getTargetOfImportClause(node as ImportClause, dontRecursivelyResolve); - case SyntaxKind.NamespaceImport: - return getTargetOfNamespaceImport(node as NamespaceImport, dontRecursivelyResolve); - case SyntaxKind.NamespaceExport: - return getTargetOfNamespaceExport(node as NamespaceExport, dontRecursivelyResolve); - case SyntaxKind.ImportSpecifier: - case SyntaxKind.BindingElement: - return getTargetOfImportSpecifier(node as ImportSpecifier | BindingElement, dontRecursivelyResolve); - case SyntaxKind.ExportSpecifier: - return getTargetOfExportSpecifier(node as ExportSpecifier, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, dontRecursivelyResolve); - case SyntaxKind.ExportAssignment: - case SyntaxKind.BinaryExpression: - return getTargetOfExportAssignment((node as ExportAssignment | BinaryExpression), dontRecursivelyResolve); - case SyntaxKind.NamespaceExportDeclaration: - return getTargetOfNamespaceExportDeclaration(node as NamespaceExportDeclaration, dontRecursivelyResolve); - case SyntaxKind.ShorthandPropertyAssignment: - return resolveEntityName((node as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontRecursivelyResolve); - case SyntaxKind.PropertyAssignment: - return getTargetOfPropertyAssignment(node as PropertyAssignment, dontRecursivelyResolve); - case SyntaxKind.ElementAccessExpression: - case SyntaxKind.PropertyAccessExpression: - return getTargetOfAccessExpression(node as AccessExpression, dontRecursivelyResolve); - default: - return Debug.fail(); - } - } + return getTargetOfAliasLikeExpression(node.parent.right, dontRecursivelyResolve); + } - /** - * Indicates that a symbol is an alias that does not merge with a local declaration. - * OR Is a JSContainer which may merge an alias with a local declaration - */ - function isNonLocalAlias(symbol: Symbol | undefined, excludes = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace): symbol is Symbol { - if (!symbol) return false; - return (symbol.flags & (SymbolFlags.Alias | excludes)) === SymbolFlags.Alias || !!(symbol.flags & SymbolFlags.Alias && symbol.flags & SymbolFlags.Assignment); + function getTargetOfAliasDeclaration(node: Declaration, dontRecursivelyResolve = false): Symbol | undefined { + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.VariableDeclaration: + return getTargetOfImportEqualsDeclaration(node as ImportEqualsDeclaration | VariableDeclaration, dontRecursivelyResolve); + case SyntaxKind.ImportClause: + return getTargetOfImportClause(node as ImportClause, dontRecursivelyResolve); + case SyntaxKind.NamespaceImport: + return getTargetOfNamespaceImport(node as NamespaceImport, dontRecursivelyResolve); + case SyntaxKind.NamespaceExport: + return getTargetOfNamespaceExport(node as NamespaceExport, dontRecursivelyResolve); + case SyntaxKind.ImportSpecifier: + case SyntaxKind.BindingElement: + return getTargetOfImportSpecifier(node as ImportSpecifier | BindingElement, dontRecursivelyResolve); + case SyntaxKind.ExportSpecifier: + return getTargetOfExportSpecifier(node as ExportSpecifier, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, dontRecursivelyResolve); + case SyntaxKind.ExportAssignment: + case SyntaxKind.BinaryExpression: + return getTargetOfExportAssignment((node as ExportAssignment | BinaryExpression), dontRecursivelyResolve); + case SyntaxKind.NamespaceExportDeclaration: + return getTargetOfNamespaceExportDeclaration(node as NamespaceExportDeclaration, dontRecursivelyResolve); + case SyntaxKind.ShorthandPropertyAssignment: + return resolveEntityName((node as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontRecursivelyResolve); + case SyntaxKind.PropertyAssignment: + return getTargetOfPropertyAssignment(node as PropertyAssignment, dontRecursivelyResolve); + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: + return getTargetOfAccessExpression(node as AccessExpression, dontRecursivelyResolve); + default: + return Debug.fail(); } + } - function resolveSymbol(symbol: Symbol, dontResolveAlias?: boolean): Symbol; - function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; - function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined { - return !dontResolveAlias && isNonLocalAlias(symbol) ? resolveAlias(symbol) : symbol; - } + /** + * Indicates that a symbol is an alias that does not merge with a local declaration. + * OR Is a JSContainer which may merge an alias with a local declaration + */ + function isNonLocalAlias(symbol: Symbol | undefined, excludes = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace): symbol is Symbol { + if (!symbol) return false; + return (symbol.flags & (SymbolFlags.Alias | excludes)) === SymbolFlags.Alias || !!(symbol.flags & SymbolFlags.Alias && symbol.flags & SymbolFlags.Assignment); + } - function resolveAlias(symbol: Symbol): Symbol { - Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); - const links = getSymbolLinks(symbol); - if (!links.target) { - links.target = resolvingSymbol; - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - const target = getTargetOfAliasDeclaration(node); - if (links.target === resolvingSymbol) { - links.target = target || unknownSymbol; - } - else { - error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol)); - } + function resolveSymbol(symbol: Symbol, dontResolveAlias?: boolean): Symbol; + function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; + function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined { + return !dontResolveAlias && isNonLocalAlias(symbol) ? resolveAlias(symbol) : symbol; + } + + function resolveAlias(symbol: Symbol): Symbol { + Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); + const links = getSymbolLinks(symbol); + if (!links.target) { + links.target = resolvingSymbol; + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) return Debug.fail(); + const target = getTargetOfAliasDeclaration(node); + if (links.target === resolvingSymbol) { + links.target = target || unknownSymbol; } - else if (links.target === resolvingSymbol) { - links.target = unknownSymbol; + else { + error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol)); } - return links.target; } + else if (links.target === resolvingSymbol) { + links.target = unknownSymbol; + } + return links.target; + } - function tryResolveAlias(symbol: Symbol): Symbol | undefined { - const links = getSymbolLinks(symbol); - if (links.target !== resolvingSymbol) { - return resolveAlias(symbol); - } - - return undefined; + function tryResolveAlias(symbol: Symbol): Symbol | undefined { + const links = getSymbolLinks(symbol); + if (links.target !== resolvingSymbol) { + return resolveAlias(symbol); } - /** - * Marks a symbol as type-only if its declaration is syntactically type-only. - * If it is not itself marked type-only, but resolves to a type-only alias - * somewhere in its resolution chain, save a reference to the type-only alias declaration - * so the alias _not_ marked type-only can be identified as _transitively_ type-only. - * - * This function is called on each alias declaration that could be type-only or resolve to - * another type-only alias during `resolveAlias`, so that later, when an alias is used in a - * JS-emitting expression, we can quickly determine if that symbol is effectively type-only - * and issue an error if so. - * - * @param aliasDeclaration The alias declaration not marked as type-only - * @param immediateTarget The symbol to which the alias declaration immediately resolves - * @param finalTarget The symbol to which the alias declaration ultimately resolves - * @param overwriteEmpty Checks `resolvesToSymbol` for type-only declarations even if `aliasDeclaration` - * has already been marked as not resolving to a type-only alias. Used when recursively resolving qualified - * names of import aliases, e.g. `import C = a.b.C`. If namespace `a` is not found to be type-only, the - * import declaration will initially be marked as not resolving to a type-only symbol. But, namespace `b` - * must still be checked for a type-only marker, overwriting the previous negative result if found. - */ - function markSymbolOfAliasDeclarationIfTypeOnly( - aliasDeclaration: Declaration | undefined, - immediateTarget: Symbol | undefined, - finalTarget: Symbol | undefined, - overwriteEmpty: boolean, - ): boolean { - if (!aliasDeclaration || isPropertyAccessExpression(aliasDeclaration)) return false; - - // If the declaration itself is type-only, mark it and return. - // No need to check what it resolves to. - const sourceSymbol = getSymbolOfNode(aliasDeclaration); - if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) { - const links = getSymbolLinks(sourceSymbol); - links.typeOnlyDeclaration = aliasDeclaration; - return true; - } + return undefined; + } + /** + * Marks a symbol as type-only if its declaration is syntactically type-only. + * If it is not itself marked type-only, but resolves to a type-only alias + * somewhere in its resolution chain, save a reference to the type-only alias declaration + * so the alias _not_ marked type-only can be identified as _transitively_ type-only. + * + * This function is called on each alias declaration that could be type-only or resolve to + * another type-only alias during `resolveAlias`, so that later, when an alias is used in a + * JS-emitting expression, we can quickly determine if that symbol is effectively type-only + * and issue an error if so. + * + * @param aliasDeclaration The alias declaration not marked as type-only + * @param immediateTarget The symbol to which the alias declaration immediately resolves + * @param finalTarget The symbol to which the alias declaration ultimately resolves + * @param overwriteEmpty Checks `resolvesToSymbol` for type-only declarations even if `aliasDeclaration` + * has already been marked as not resolving to a type-only alias. Used when recursively resolving qualified + * names of import aliases, e.g. `import C = a.b.C`. If namespace `a` is not found to be type-only, the + * import declaration will initially be marked as not resolving to a type-only symbol. But, namespace `b` + * must still be checked for a type-only marker, overwriting the previous negative result if found. + */ + function markSymbolOfAliasDeclarationIfTypeOnly( + aliasDeclaration: Declaration | undefined, + immediateTarget: Symbol | undefined, + finalTarget: Symbol | undefined, + overwriteEmpty: boolean, + ): boolean { + if (!aliasDeclaration || isPropertyAccessExpression(aliasDeclaration)) return false; + + // If the declaration itself is type-only, mark it and return. + // No need to check what it resolves to. + const sourceSymbol = getSymbolOfNode(aliasDeclaration); + if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) { const links = getSymbolLinks(sourceSymbol); - return markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, immediateTarget, overwriteEmpty) - || markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, finalTarget, overwriteEmpty); + links.typeOnlyDeclaration = aliasDeclaration; + return true; } - function markSymbolOfAliasDeclarationIfTypeOnlyWorker(aliasDeclarationLinks: SymbolLinks, target: Symbol | undefined, overwriteEmpty: boolean): boolean { - if (target && (aliasDeclarationLinks.typeOnlyDeclaration === undefined || overwriteEmpty && aliasDeclarationLinks.typeOnlyDeclaration === false)) { - const exportSymbol = target.exports?.get(InternalSymbolName.ExportEquals) ?? target; - const typeOnly = exportSymbol.declarations && find(exportSymbol.declarations, isTypeOnlyImportOrExportDeclaration); - aliasDeclarationLinks.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(exportSymbol).typeOnlyDeclaration ?? false; - } - return !!aliasDeclarationLinks.typeOnlyDeclaration; + const links = getSymbolLinks(sourceSymbol); + return markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, immediateTarget, overwriteEmpty) + || markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, finalTarget, overwriteEmpty); + } + + function markSymbolOfAliasDeclarationIfTypeOnlyWorker(aliasDeclarationLinks: SymbolLinks, target: Symbol | undefined, overwriteEmpty: boolean): boolean { + if (target && (aliasDeclarationLinks.typeOnlyDeclaration === undefined || overwriteEmpty && aliasDeclarationLinks.typeOnlyDeclaration === false)) { + const exportSymbol = target.exports?.get(InternalSymbolName.ExportEquals) ?? target; + const typeOnly = exportSymbol.declarations && find(exportSymbol.declarations, isTypeOnlyImportOrExportDeclaration); + aliasDeclarationLinks.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(exportSymbol).typeOnlyDeclaration ?? false; } + return !!aliasDeclarationLinks.typeOnlyDeclaration; + } - /** Indicates that a symbol directly or indirectly resolves to a type-only import or export. */ - function getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyAliasDeclaration | undefined { - if (!(symbol.flags & SymbolFlags.Alias)) { - return undefined; - } - const links = getSymbolLinks(symbol); - return links.typeOnlyDeclaration || undefined; + /** Indicates that a symbol directly or indirectly resolves to a type-only import or export. */ + function getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyAliasDeclaration | undefined { + if (!(symbol.flags & SymbolFlags.Alias)) { + return undefined; } + const links = getSymbolLinks(symbol); + return links.typeOnlyDeclaration || undefined; + } - function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { - const symbol = getSymbolOfNode(node); - const target = resolveAlias(symbol); - if (target) { - const markAlias = target === unknownSymbol || - ((target.flags & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target) && !getTypeOnlyAliasDeclaration(symbol)); + function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { + const symbol = getSymbolOfNode(node); + const target = resolveAlias(symbol); + if (target) { + const markAlias = target === unknownSymbol || + ((target.flags & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target) && !getTypeOnlyAliasDeclaration(symbol)); - if (markAlias) { - markAliasSymbolAsReferenced(symbol); - } + if (markAlias) { + markAliasSymbolAsReferenced(symbol); } } + } - // When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until - // we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of - // the alias as an expression (which recursively takes us back here if the target references another alias). - function markAliasSymbolAsReferenced(symbol: Symbol) { - const links = getSymbolLinks(symbol); - if (!links.referenced) { - links.referenced = true; - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - // We defer checking of the reference of an `import =` until the import itself is referenced, - // This way a chain of imports can be elided if ultimately the final input is only used in a type - // position. - if (isInternalModuleImportEqualsDeclaration(node)) { - const target = resolveSymbol(symbol); - if (target === unknownSymbol || target.flags & SymbolFlags.Value) { - // import foo = - checkExpressionCached(node.moduleReference as Expression); - } + // When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until + // we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of + // the alias as an expression (which recursively takes us back here if the target references another alias). + function markAliasSymbolAsReferenced(symbol: Symbol) { + const links = getSymbolLinks(symbol); + if (!links.referenced) { + links.referenced = true; + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) return Debug.fail(); + // We defer checking of the reference of an `import =` until the import itself is referenced, + // This way a chain of imports can be elided if ultimately the final input is only used in a type + // position. + if (isInternalModuleImportEqualsDeclaration(node)) { + const target = resolveSymbol(symbol); + if (target === unknownSymbol || target.flags & SymbolFlags.Value) { + // import foo = + checkExpressionCached(node.moduleReference as Expression); } } } + } - // Aliases that resolve to const enums are not marked as referenced because they are not emitted, - // but their usage in value positions must be tracked to determine if the import can be type-only. - function markConstEnumAliasAsReferenced(symbol: Symbol) { - const links = getSymbolLinks(symbol); - if (!links.constEnumReferenced) { - links.constEnumReferenced = true; - } + // Aliases that resolve to const enums are not marked as referenced because they are not emitted, + // but their usage in value positions must be tracked to determine if the import can be type-only. + function markConstEnumAliasAsReferenced(symbol: Symbol) { + const links = getSymbolLinks(symbol); + if (!links.constEnumReferenced) { + links.constEnumReferenced = true; } + } - // This function is only for imports with entity names - function getSymbolOfPartOfRightHandSideOfImportEquals(entityName: EntityName, dontResolveAlias?: boolean): Symbol | undefined { - // There are three things we might try to look for. In the following examples, - // the search term is enclosed in |...|: - // - // import a = |b|; // Namespace - // import a = |b.c|; // Value, type, namespace - // import a = |b.c|.d; // Namespace - if (entityName.kind === SyntaxKind.Identifier && isRightSideOfQualifiedNameOrPropertyAccess(entityName)) { - entityName = entityName.parent as QualifiedName; - } - // Check for case 1 and 3 in the above example - if (entityName.kind === SyntaxKind.Identifier || entityName.parent.kind === SyntaxKind.QualifiedName) { - return resolveEntityName(entityName, SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); - } - else { - // Case 2 in above example - // entityName.kind could be a QualifiedName or a Missing identifier - Debug.assert(entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration); - return resolveEntityName(entityName, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); - } + // This function is only for imports with entity names + function getSymbolOfPartOfRightHandSideOfImportEquals(entityName: EntityName, dontResolveAlias?: boolean): Symbol | undefined { + // There are three things we might try to look for. In the following examples, + // the search term is enclosed in |...|: + // + // import a = |b|; // Namespace + // import a = |b.c|; // Value, type, namespace + // import a = |b.c|.d; // Namespace + if (entityName.kind === SyntaxKind.Identifier && isRightSideOfQualifiedNameOrPropertyAccess(entityName)) { + entityName = entityName.parent as QualifiedName; + } + // Check for case 1 and 3 in the above example + if (entityName.kind === SyntaxKind.Identifier || entityName.parent.kind === SyntaxKind.QualifiedName) { + return resolveEntityName(entityName, SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); + } + else { + // Case 2 in above example + // entityName.kind could be a QualifiedName or a Missing identifier + Debug.assert(entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration); + return resolveEntityName(entityName, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); } + } - function getFullyQualifiedName(symbol: Symbol, containingLocation?: Node): string { - return symbol.parent ? getFullyQualifiedName(symbol.parent, containingLocation) + "." + symbolToString(symbol) : symbolToString(symbol, containingLocation, /*meaning*/ undefined, SymbolFormatFlags.DoNotIncludeSymbolChain | SymbolFormatFlags.AllowAnyNodeKind); - } + function getFullyQualifiedName(symbol: Symbol, containingLocation?: Node): string { + return symbol.parent ? getFullyQualifiedName(symbol.parent, containingLocation) + "." + symbolToString(symbol) : symbolToString(symbol, containingLocation, /*meaning*/ undefined, SymbolFormatFlags.DoNotIncludeSymbolChain | SymbolFormatFlags.AllowAnyNodeKind); + } - function getContainingQualifiedNameNode(node: QualifiedName) { - while (isQualifiedName(node.parent)) { - node = node.parent; - } - return node; + function getContainingQualifiedNameNode(node: QualifiedName) { + while (isQualifiedName(node.parent)) { + node = node.parent; } + return node; + } - function tryGetQualifiedNameAsValue(node: QualifiedName) { - let left: Identifier | QualifiedName = getFirstIdentifier(node); - let symbol = resolveName(left, left.escapedText, SymbolFlags.Value, undefined, left, /*isUse*/ true); + function tryGetQualifiedNameAsValue(node: QualifiedName) { + let left: Identifier | QualifiedName = getFirstIdentifier(node); + let symbol = resolveName(left, left.escapedText, SymbolFlags.Value, undefined, left, /*isUse*/ true); + if (!symbol) { + return undefined; + } + while (isQualifiedName(left.parent)) { + const type = getTypeOfSymbol(symbol); + symbol = getPropertyOfType(type, left.parent.right.escapedText); if (!symbol) { return undefined; } - while (isQualifiedName(left.parent)) { - const type = getTypeOfSymbol(symbol); - symbol = getPropertyOfType(type, left.parent.right.escapedText); - if (!symbol) { - return undefined; - } - left = left.parent; - } - return symbol; + left = left.parent; } + return symbol; + } - /** - * Resolves a qualified name and any involved aliases. - */ - function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean, location?: Node): Symbol | undefined { - if (nodeIsMissing(name)) { + /** + * Resolves a qualified name and any involved aliases. + */ + function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean, location?: Node): Symbol | undefined { + if (nodeIsMissing(name)) { + return undefined; + } + + const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(name) ? meaning & SymbolFlags.Value : 0); + let symbol: Symbol | undefined; + if (name.kind === SyntaxKind.Identifier) { + const message = meaning === namespaceMeaning || nodeIsSynthesized(name) ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name)); + const symbolFromJSPrototype = isInJSFile(name) && !nodeIsSynthesized(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined; + symbol = getMergedSymbol(resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true, false)); + if (!symbol) { + return getMergedSymbol(symbolFromJSPrototype); + } + } + else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) { + const left = name.kind === SyntaxKind.QualifiedName ? name.left : name.expression; + const right = name.kind === SyntaxKind.QualifiedName ? name.right : name.name; + let namespace = resolveEntityName(left, namespaceMeaning, ignoreErrors, /*dontResolveAlias*/ false, location); + if (!namespace || nodeIsMissing(right)) { return undefined; } - - const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(name) ? meaning & SymbolFlags.Value : 0); - let symbol: Symbol | undefined; - if (name.kind === SyntaxKind.Identifier) { - const message = meaning === namespaceMeaning || nodeIsSynthesized(name) ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name)); - const symbolFromJSPrototype = isInJSFile(name) && !nodeIsSynthesized(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined; - symbol = getMergedSymbol(resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true, false)); - if (!symbol) { - return getMergedSymbol(symbolFromJSPrototype); - } + else if (namespace === unknownSymbol) { + return namespace; } - else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) { - const left = name.kind === SyntaxKind.QualifiedName ? name.left : name.expression; - const right = name.kind === SyntaxKind.QualifiedName ? name.right : name.name; - let namespace = resolveEntityName(left, namespaceMeaning, ignoreErrors, /*dontResolveAlias*/ false, location); - if (!namespace || nodeIsMissing(right)) { - return undefined; - } - else if (namespace === unknownSymbol) { - return namespace; - } - if ( - namespace.valueDeclaration && - isInJSFile(namespace.valueDeclaration) && - isVariableDeclaration(namespace.valueDeclaration) && - namespace.valueDeclaration.initializer && - isCommonJsRequire(namespace.valueDeclaration.initializer) - ) { - const moduleName = (namespace.valueDeclaration.initializer as CallExpression).arguments[0] as StringLiteral; - const moduleSym = resolveExternalModuleName(moduleName, moduleName); - if (moduleSym) { - const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); - if (resolvedModuleSymbol) { - namespace = resolvedModuleSymbol; - } + if ( + namespace.valueDeclaration && + isInJSFile(namespace.valueDeclaration) && + isVariableDeclaration(namespace.valueDeclaration) && + namespace.valueDeclaration.initializer && + isCommonJsRequire(namespace.valueDeclaration.initializer) + ) { + const moduleName = (namespace.valueDeclaration.initializer as CallExpression).arguments[0] as StringLiteral; + const moduleSym = resolveExternalModuleName(moduleName, moduleName); + if (moduleSym) { + const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); + if (resolvedModuleSymbol) { + namespace = resolvedModuleSymbol; } } - symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(namespace), right.escapedText, meaning)); - if (!symbol) { - if (!ignoreErrors) { - const namespaceName = getFullyQualifiedName(namespace); - const declarationName = declarationNameToString(right); - const suggestionForNonexistentModule = getSuggestedSymbolForNonexistentModule(right, namespace); - if (suggestionForNonexistentModule) { - error(right, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, namespaceName, declarationName, symbolToString(suggestionForNonexistentModule)); - return undefined; - } + } + symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(namespace), right.escapedText, meaning)); + if (!symbol) { + if (!ignoreErrors) { + const namespaceName = getFullyQualifiedName(namespace); + const declarationName = declarationNameToString(right); + const suggestionForNonexistentModule = getSuggestedSymbolForNonexistentModule(right, namespace); + if (suggestionForNonexistentModule) { + error(right, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, namespaceName, declarationName, symbolToString(suggestionForNonexistentModule)); + return undefined; + } + + const containingQualifiedName = isQualifiedName(name) && getContainingQualifiedNameNode(name); + const canSuggestTypeof = globalObjectType // <-- can't pull on types if global types aren't initialized yet + && (meaning & SymbolFlags.Type) + && containingQualifiedName + && !isTypeOfExpression(containingQualifiedName.parent) + && tryGetQualifiedNameAsValue(containingQualifiedName); + if (canSuggestTypeof) { + error( + containingQualifiedName, + Diagnostics._0_refers_to_a_value_but_is_being_used_as_a_type_here_Did_you_mean_typeof_0, + entityNameToString(containingQualifiedName) + ); + return undefined; + } - const containingQualifiedName = isQualifiedName(name) && getContainingQualifiedNameNode(name); - const canSuggestTypeof = globalObjectType // <-- can't pull on types if global types aren't initialized yet - && (meaning & SymbolFlags.Type) - && containingQualifiedName - && !isTypeOfExpression(containingQualifiedName.parent) - && tryGetQualifiedNameAsValue(containingQualifiedName); - if (canSuggestTypeof) { + if (meaning & SymbolFlags.Namespace && isQualifiedName(name.parent)) { + const exportedTypeSymbol = getMergedSymbol(getSymbol(getExportsOfSymbol(namespace), right.escapedText, SymbolFlags.Type)); + if (exportedTypeSymbol) { error( - containingQualifiedName, - Diagnostics._0_refers_to_a_value_but_is_being_used_as_a_type_here_Did_you_mean_typeof_0, - entityNameToString(containingQualifiedName) + name.parent.right, + Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1, + symbolToString(exportedTypeSymbol), + unescapeLeadingUnderscores(name.parent.right.escapedText) ); return undefined; } - - if (meaning & SymbolFlags.Namespace && isQualifiedName(name.parent)) { - const exportedTypeSymbol = getMergedSymbol(getSymbol(getExportsOfSymbol(namespace), right.escapedText, SymbolFlags.Type)); - if (exportedTypeSymbol) { - error( - name.parent.right, - Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1, - symbolToString(exportedTypeSymbol), - unescapeLeadingUnderscores(name.parent.right.escapedText) - ); - return undefined; - } - } - - error(right, Diagnostics.Namespace_0_has_no_exported_member_1, namespaceName, declarationName); } - return undefined; + + error(right, Diagnostics.Namespace_0_has_no_exported_member_1, namespaceName, declarationName); } + return undefined; } - else { - throw Debug.assertNever(name, "Unknown entity name kind."); - } - Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (!nodeIsSynthesized(name) && isEntityName(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) { - markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), symbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ true); - } - return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); } + else { + throw Debug.assertNever(name, "Unknown entity name kind."); + } + Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); + if (!nodeIsSynthesized(name) && isEntityName(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) { + markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), symbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ true); + } + return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); + } - /** - * 1. For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too. - * Note that prototype-property assignment to locations outside the current file (eg globals) doesn't work, so - * name resolution won't work either. - * 2. For property assignments like `{ x: function f () { } }`, try to resolve names in the scope of `f` too. - */ - function resolveEntityNameFromAssignmentDeclaration(name: Identifier, meaning: SymbolFlags) { - if (isJSDocTypeReference(name.parent)) { - const secondaryLocation = getAssignmentDeclarationLocation(name.parent); - if (secondaryLocation) { - return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true); - } + /** + * 1. For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too. + * Note that prototype-property assignment to locations outside the current file (eg globals) doesn't work, so + * name resolution won't work either. + * 2. For property assignments like `{ x: function f () { } }`, try to resolve names in the scope of `f` too. + */ + function resolveEntityNameFromAssignmentDeclaration(name: Identifier, meaning: SymbolFlags) { + if (isJSDocTypeReference(name.parent)) { + const secondaryLocation = getAssignmentDeclarationLocation(name.parent); + if (secondaryLocation) { + return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true); } } + } - function getAssignmentDeclarationLocation(node: TypeReferenceNode): Node | undefined { - const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node)); - if (typeAlias) { - return; - } - const host = getJSDocHost(node); - if (host && - isExpressionStatement(host) && - isBinaryExpression(host.expression) && - getAssignmentDeclarationKind(host.expression) === AssignmentDeclarationKind.PrototypeProperty) { - // X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration - const symbol = getSymbolOfNode(host.expression.left); - if (symbol) { - return getDeclarationOfJSPrototypeContainer(symbol); - } - } - if (host && (isObjectLiteralMethod(host) || isPropertyAssignment(host)) && - isBinaryExpression(host.parent.parent) && - getAssignmentDeclarationKind(host.parent.parent) === AssignmentDeclarationKind.Prototype) { - // X.prototype = { /** @param {K} p */m() { } } <-- look for K on X's declaration - const symbol = getSymbolOfNode(host.parent.parent.left); - if (symbol) { - return getDeclarationOfJSPrototypeContainer(symbol); - } + function getAssignmentDeclarationLocation(node: TypeReferenceNode): Node | undefined { + const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node)); + if (typeAlias) { + return; + } + const host = getJSDocHost(node); + if (host && + isExpressionStatement(host) && + isBinaryExpression(host.expression) && + getAssignmentDeclarationKind(host.expression) === AssignmentDeclarationKind.PrototypeProperty) { + // X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration + const symbol = getSymbolOfNode(host.expression.left); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); } - const sig = getEffectiveJSDocHost(node); - if (sig && isFunctionLike(sig)) { - const symbol = getSymbolOfNode(sig); - return symbol && symbol.valueDeclaration; + } + if (host && (isObjectLiteralMethod(host) || isPropertyAssignment(host)) && + isBinaryExpression(host.parent.parent) && + getAssignmentDeclarationKind(host.parent.parent) === AssignmentDeclarationKind.Prototype) { + // X.prototype = { /** @param {K} p */m() { } } <-- look for K on X's declaration + const symbol = getSymbolOfNode(host.parent.parent.left); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); } } + const sig = getEffectiveJSDocHost(node); + if (sig && isFunctionLike(sig)) { + const symbol = getSymbolOfNode(sig); + return symbol && symbol.valueDeclaration; + } + } - function getDeclarationOfJSPrototypeContainer(symbol: Symbol) { - const decl = symbol.parent!.valueDeclaration; - if (!decl) { - return undefined; - } - const initializer = isAssignmentDeclaration(decl) ? getAssignedExpandoInitializer(decl) : - hasOnlyExpressionInitializer(decl) ? getDeclaredExpandoInitializer(decl) : - undefined; - return initializer || decl; + function getDeclarationOfJSPrototypeContainer(symbol: Symbol) { + const decl = symbol.parent!.valueDeclaration; + if (!decl) { + return undefined; } + const initializer = isAssignmentDeclaration(decl) ? getAssignedExpandoInitializer(decl) : + hasOnlyExpressionInitializer(decl) ? getDeclaredExpandoInitializer(decl) : + undefined; + return initializer || decl; + } - /** - * Get the real symbol of a declaration with an expando initializer. - * - * Normally, declarations have an associated symbol, but when a declaration has an expando - * initializer, the expando's symbol is the one that has all the members merged into it. - */ - function getExpandoSymbol(symbol: Symbol): Symbol | undefined { - const decl = symbol.valueDeclaration; - if (!decl || !isInJSFile(decl) || symbol.flags & SymbolFlags.TypeAlias || getExpandoInitializer(decl, /*isPrototypeAssignment*/ false)) { - return undefined; - } - const init = isVariableDeclaration(decl) ? getDeclaredExpandoInitializer(decl) : getAssignedExpandoInitializer(decl); - if (init) { - const initSymbol = getSymbolOfNode(init); - if (initSymbol) { - return mergeJSSymbols(initSymbol, symbol); - } + /** + * Get the real symbol of a declaration with an expando initializer. + * + * Normally, declarations have an associated symbol, but when a declaration has an expando + * initializer, the expando's symbol is the one that has all the members merged into it. + */ + function getExpandoSymbol(symbol: Symbol): Symbol | undefined { + const decl = symbol.valueDeclaration; + if (!decl || !isInJSFile(decl) || symbol.flags & SymbolFlags.TypeAlias || getExpandoInitializer(decl, /*isPrototypeAssignment*/ false)) { + return undefined; + } + const init = isVariableDeclaration(decl) ? getDeclaredExpandoInitializer(decl) : getAssignedExpandoInitializer(decl); + if (init) { + const initSymbol = getSymbolOfNode(init); + if (initSymbol) { + return mergeJSSymbols(initSymbol, symbol); } } + } - function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression, ignoreErrors?: boolean): Symbol | undefined { - const isClassic = getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic; - const errorMessage = isClassic? - Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option - : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; - return resolveExternalModuleNameWorker(location, moduleReferenceExpression, ignoreErrors ? undefined : errorMessage); - } + function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression, ignoreErrors?: boolean): Symbol | undefined { + const isClassic = getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic; + const errorMessage = isClassic? + Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option + : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; + return resolveExternalModuleNameWorker(location, moduleReferenceExpression, ignoreErrors ? undefined : errorMessage); + } - function resolveExternalModuleNameWorker(location: Node, moduleReferenceExpression: Expression, moduleNotFoundError: DiagnosticMessage | undefined, isForAugmentation = false): Symbol | undefined { - return isStringLiteralLike(moduleReferenceExpression) - ? resolveExternalModule(location, moduleReferenceExpression.text, moduleNotFoundError, moduleReferenceExpression, isForAugmentation) - : undefined; - } + function resolveExternalModuleNameWorker(location: Node, moduleReferenceExpression: Expression, moduleNotFoundError: DiagnosticMessage | undefined, isForAugmentation = false): Symbol | undefined { + return isStringLiteralLike(moduleReferenceExpression) + ? resolveExternalModule(location, moduleReferenceExpression.text, moduleNotFoundError, moduleReferenceExpression, isForAugmentation) + : undefined; + } - function resolveExternalModule(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage | undefined, errorNode: Node, isForAugmentation = false): Symbol | undefined { - if (startsWith(moduleReference, "@types/")) { - const diag = Diagnostics.Cannot_import_type_declaration_files_Consider_importing_0_instead_of_1; - const withoutAtTypePrefix = removePrefix(moduleReference, "@types/"); - error(errorNode, diag, withoutAtTypePrefix, moduleReference); + function resolveExternalModule(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage | undefined, errorNode: Node, isForAugmentation = false): Symbol | undefined { + if (startsWith(moduleReference, "@types/")) { + const diag = Diagnostics.Cannot_import_type_declaration_files_Consider_importing_0_instead_of_1; + const withoutAtTypePrefix = removePrefix(moduleReference, "@types/"); + error(errorNode, diag, withoutAtTypePrefix, moduleReference); + } + + const ambientModule = tryFindAmbientModule(moduleReference, /*withAugmentations*/ true); + if (ambientModule) { + return ambientModule; + } + const currentSourceFile = getSourceFileOfNode(location); + const contextSpecifier = isStringLiteralLike(location) + ? location + : findAncestor(location, isImportCall)?.arguments[0] || + findAncestor(location, isImportDeclaration)?.moduleSpecifier || + findAncestor(location, isExternalModuleImportEqualsDeclaration)?.moduleReference.expression || + findAncestor(location, isExportDeclaration)?.moduleSpecifier || + (isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name || + (isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal; + const mode = contextSpecifier && isStringLiteralLike(contextSpecifier) ? getModeForUsageLocation(currentSourceFile, contextSpecifier) : currentSourceFile.impliedNodeFormat; + const resolvedModule = getResolvedModule(currentSourceFile, moduleReference, mode); + const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule); + const sourceFile = resolvedModule && !resolutionDiagnostic && host.getSourceFile(resolvedModule.resolvedFileName); + if (sourceFile) { + if (sourceFile.symbol) { + if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) { + errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference); + } + if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) { + const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration); + if (isSyncImport && sourceFile.impliedNodeFormat === ModuleKind.ESNext) { + error(errorNode, Diagnostics.Module_0_cannot_be_imported_using_this_construct_The_specifier_only_resolves_to_an_ES_module_which_cannot_be_imported_synchronously_Use_dynamic_import_instead, moduleReference); + } + if (mode === ModuleKind.ESNext && compilerOptions.resolveJsonModule && resolvedModule.extension === Extension.Json) { + error(errorNode, Diagnostics.JSON_imports_are_experimental_in_ES_module_mode_imports); + } + } + // merged symbol is module declaration symbol combined with all augmentations + return getMergedSymbol(sourceFile.symbol); } - - const ambientModule = tryFindAmbientModule(moduleReference, /*withAugmentations*/ true); - if (ambientModule) { - return ambientModule; + if (moduleNotFoundError) { + // report errors only if it was requested + error(errorNode, Diagnostics.File_0_is_not_a_module, sourceFile.fileName); } - const currentSourceFile = getSourceFileOfNode(location); - const contextSpecifier = isStringLiteralLike(location) - ? location - : findAncestor(location, isImportCall)?.arguments[0] || - findAncestor(location, isImportDeclaration)?.moduleSpecifier || - findAncestor(location, isExternalModuleImportEqualsDeclaration)?.moduleReference.expression || - findAncestor(location, isExportDeclaration)?.moduleSpecifier || - (isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name || - (isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal; - const mode = contextSpecifier && isStringLiteralLike(contextSpecifier) ? getModeForUsageLocation(currentSourceFile, contextSpecifier) : currentSourceFile.impliedNodeFormat; - const resolvedModule = getResolvedModule(currentSourceFile, moduleReference, mode); - const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule); - const sourceFile = resolvedModule && !resolutionDiagnostic && host.getSourceFile(resolvedModule.resolvedFileName); - if (sourceFile) { - if (sourceFile.symbol) { - if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) { - errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference); - } - if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) { - const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration); - if (isSyncImport && sourceFile.impliedNodeFormat === ModuleKind.ESNext) { - error(errorNode, Diagnostics.Module_0_cannot_be_imported_using_this_construct_The_specifier_only_resolves_to_an_ES_module_which_cannot_be_imported_synchronously_Use_dynamic_import_instead, moduleReference); - } - if (mode === ModuleKind.ESNext && compilerOptions.resolveJsonModule && resolvedModule.extension === Extension.Json) { - error(errorNode, Diagnostics.JSON_imports_are_experimental_in_ES_module_mode_imports); - } - } - // merged symbol is module declaration symbol combined with all augmentations - return getMergedSymbol(sourceFile.symbol); - } - if (moduleNotFoundError) { - // report errors only if it was requested - error(errorNode, Diagnostics.File_0_is_not_a_module, sourceFile.fileName); + return undefined; + } + + if (patternAmbientModules) { + const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference); + if (pattern) { + // If the module reference matched a pattern ambient module ('*.foo') but there's also a + // module augmentation by the specific name requested ('a.foo'), we store the merged symbol + // by the augmentation name ('a.foo'), because asking for *.foo should not give you exports + // from a.foo. + const augmentation = patternAmbientModuleAugmentations && patternAmbientModuleAugmentations.get(moduleReference); + if (augmentation) { + return getMergedSymbol(augmentation); } - return undefined; + return getMergedSymbol(pattern.symbol); } + } - if (patternAmbientModules) { - const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference); - if (pattern) { - // If the module reference matched a pattern ambient module ('*.foo') but there's also a - // module augmentation by the specific name requested ('a.foo'), we store the merged symbol - // by the augmentation name ('a.foo'), because asking for *.foo should not give you exports - // from a.foo. - const augmentation = patternAmbientModuleAugmentations && patternAmbientModuleAugmentations.get(moduleReference); - if (augmentation) { - return getMergedSymbol(augmentation); - } - return getMergedSymbol(pattern.symbol); - } + // May be an untyped module. If so, ignore resolutionDiagnostic. + if (resolvedModule && !resolutionExtensionIsTSOrJson(resolvedModule.extension) && resolutionDiagnostic === undefined || resolutionDiagnostic === Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type) { + if (isForAugmentation) { + const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented; + error(errorNode, diag, moduleReference, resolvedModule!.resolvedFileName); + } + else { + errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule!, moduleReference); } + // Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first. + return undefined; + } - // May be an untyped module. If so, ignore resolutionDiagnostic. - if (resolvedModule && !resolutionExtensionIsTSOrJson(resolvedModule.extension) && resolutionDiagnostic === undefined || resolutionDiagnostic === Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type) { - if (isForAugmentation) { - const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented; - error(errorNode, diag, moduleReference, resolvedModule!.resolvedFileName); - } - else { - errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule!, moduleReference); + if (moduleNotFoundError) { + // See if this was possibly a projectReference redirect + if (resolvedModule) { + const redirect = host.getProjectReferenceRedirect(resolvedModule.resolvedFileName); + if (redirect) { + error(errorNode, Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, resolvedModule.resolvedFileName); + return undefined; } - // Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first. - return undefined; } - if (moduleNotFoundError) { - // See if this was possibly a projectReference redirect - if (resolvedModule) { - const redirect = host.getProjectReferenceRedirect(resolvedModule.resolvedFileName); - if (redirect) { - error(errorNode, Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, resolvedModule.resolvedFileName); - return undefined; + if (resolutionDiagnostic) { + error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); + } + else { + const tsExtension = tryExtractTSExtension(moduleReference); + const isExtensionlessRelativePathImport = pathIsRelative(moduleReference) && !hasExtension(moduleReference); + const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions); + const resolutionIsNode12OrNext = moduleResolutionKind === ModuleResolutionKind.Node12 || + moduleResolutionKind === ModuleResolutionKind.NodeNext; + if (tsExtension) { + const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead; + const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension); + let replacedImportSource = importSourceWithoutExtension; + /** + * Direct users to import source with .js extension if outputting an ES module. + * @see https://github.com/microsoft/TypeScript/issues/42151 + */ + if (moduleKind >= ModuleKind.ES2015) { + replacedImportSource += tsExtension === Extension.Mts ? ".mjs" : tsExtension === Extension.Cts ? ".cjs" : ".js"; } + error(errorNode, diag, tsExtension, replacedImportSource); } - - if (resolutionDiagnostic) { - error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); + else if (!compilerOptions.resolveJsonModule && + fileExtensionIs(moduleReference, Extension.Json) && + getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && + hasJsonModuleEmitEnabled(compilerOptions)) { + error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference); } - else { - const tsExtension = tryExtractTSExtension(moduleReference); - const isExtensionlessRelativePathImport = pathIsRelative(moduleReference) && !hasExtension(moduleReference); - const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions); - const resolutionIsNode12OrNext = moduleResolutionKind === ModuleResolutionKind.Node12 || - moduleResolutionKind === ModuleResolutionKind.NodeNext; - if (tsExtension) { - const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead; - const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension); - let replacedImportSource = importSourceWithoutExtension; - /** - * Direct users to import source with .js extension if outputting an ES module. - * @see https://github.com/microsoft/TypeScript/issues/42151 - */ - if (moduleKind >= ModuleKind.ES2015) { - replacedImportSource += tsExtension === Extension.Mts ? ".mjs" : tsExtension === Extension.Cts ? ".cjs" : ".js"; - } - error(errorNode, diag, tsExtension, replacedImportSource); - } - else if (!compilerOptions.resolveJsonModule && - fileExtensionIs(moduleReference, Extension.Json) && - getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && - hasJsonModuleEmitEnabled(compilerOptions)) { - error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference); - } - else if (mode === ModuleKind.ESNext && resolutionIsNode12OrNext && isExtensionlessRelativePathImport) { - const absoluteRef = getNormalizedAbsolutePath(moduleReference, getDirectoryPath(currentSourceFile.path)); - const suggestedExt = suggestedExtensions.find(([actualExt, _importExt]) => host.fileExists(absoluteRef + actualExt))?.[1]; - if (suggestedExt) { - error(errorNode, - Diagnostics.Relative_import_paths_need_explicit_file_extensions_in_EcmaScript_imports_when_moduleResolution_is_node12_or_nodenext_Did_you_mean_0, - moduleReference + suggestedExt); - } - else { - error(errorNode, Diagnostics.Relative_import_paths_need_explicit_file_extensions_in_EcmaScript_imports_when_moduleResolution_is_node12_or_nodenext_Consider_adding_an_extension_to_the_import_path); - } + else if (mode === ModuleKind.ESNext && resolutionIsNode12OrNext && isExtensionlessRelativePathImport) { + const absoluteRef = getNormalizedAbsolutePath(moduleReference, getDirectoryPath(currentSourceFile.path)); + const suggestedExt = suggestedExtensions.find(([actualExt, _importExt]) => host.fileExists(absoluteRef + actualExt))?.[1]; + if (suggestedExt) { + error(errorNode, + Diagnostics.Relative_import_paths_need_explicit_file_extensions_in_EcmaScript_imports_when_moduleResolution_is_node12_or_nodenext_Did_you_mean_0, + moduleReference + suggestedExt); } else { - error(errorNode, moduleNotFoundError, moduleReference); + error(errorNode, Diagnostics.Relative_import_paths_need_explicit_file_extensions_in_EcmaScript_imports_when_moduleResolution_is_node12_or_nodenext_Consider_adding_an_extension_to_the_import_path); } } + else { + error(errorNode, moduleNotFoundError, moduleReference); + } } - return undefined; } + return undefined; + } - function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void { - const errorInfo = !isExternalModuleNameRelative(moduleReference) && packageId - ? typesPackageExists(packageId.name) + function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void { + const errorInfo = !isExternalModuleNameRelative(moduleReference) && packageId + ? typesPackageExists(packageId.name) + ? chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1, + packageId.name, mangleScopedPackageName(packageId.name)) + : packageBundlesTypes(packageId.name) ? chainDiagnosticMessages( /*details*/ undefined, - Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1, - packageId.name, mangleScopedPackageName(packageId.name)) - : packageBundlesTypes(packageId.name) - ? chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1, - packageId.name, - moduleReference) - : chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0, - moduleReference, - mangleScopedPackageName(packageId.name)) - : undefined; - errorOrSuggestion(isError, errorNode, chainDiagnosticMessages( - errorInfo, - Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, - moduleReference, - resolvedFileName)); + Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1, + packageId.name, + moduleReference) + : chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0, + moduleReference, + mangleScopedPackageName(packageId.name)) + : undefined; + errorOrSuggestion(isError, errorNode, chainDiagnosticMessages( + errorInfo, + Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, + moduleReference, + resolvedFileName)); + } + function typesPackageExists(packageName: string): boolean { + return getPackagesMap().has(getTypesPackageName(packageName)); + } + function packageBundlesTypes(packageName: string): boolean { + return !!getPackagesMap().get(packageName); + } + + function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol; + function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; + function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol | undefined { + if (moduleSymbol?.exports) { + const exportEquals = resolveSymbol(moduleSymbol.exports.get(InternalSymbolName.ExportEquals), dontResolveAlias); + const exported = getCommonJsExportEquals(getMergedSymbol(exportEquals), getMergedSymbol(moduleSymbol)); + return getMergedSymbol(exported) || moduleSymbol; } - function typesPackageExists(packageName: string): boolean { - return getPackagesMap().has(getTypesPackageName(packageName)); + return undefined; + } + + function getCommonJsExportEquals(exported: Symbol | undefined, moduleSymbol: Symbol): Symbol | undefined { + if (!exported || exported === unknownSymbol || exported === moduleSymbol || moduleSymbol.exports!.size === 1 || exported.flags & SymbolFlags.Alias) { + return exported; } - function packageBundlesTypes(packageName: string): boolean { - return !!getPackagesMap().get(packageName); + const links = getSymbolLinks(exported); + if (links.cjsExportMerged) { + return links.cjsExportMerged; } - - function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol; - function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; - function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol | undefined { - if (moduleSymbol?.exports) { - const exportEquals = resolveSymbol(moduleSymbol.exports.get(InternalSymbolName.ExportEquals), dontResolveAlias); - const exported = getCommonJsExportEquals(getMergedSymbol(exportEquals), getMergedSymbol(moduleSymbol)); - return getMergedSymbol(exported) || moduleSymbol; - } - return undefined; + const merged = exported.flags & SymbolFlags.Transient ? exported : cloneSymbol(exported); + merged.flags = merged.flags | SymbolFlags.ValueModule; + if (merged.exports === undefined) { + merged.exports = createSymbolTable(); } + moduleSymbol.exports!.forEach((s, name) => { + if (name === InternalSymbolName.ExportEquals) return; + merged.exports!.set(name, merged.exports!.has(name) ? mergeSymbol(merged.exports!.get(name)!, s) : s); + }); + getSymbolLinks(merged).cjsExportMerged = merged; + return links.cjsExportMerged = merged; + } - function getCommonJsExportEquals(exported: Symbol | undefined, moduleSymbol: Symbol): Symbol | undefined { - if (!exported || exported === unknownSymbol || exported === moduleSymbol || moduleSymbol.exports!.size === 1 || exported.flags & SymbolFlags.Alias) { - return exported; - } - const links = getSymbolLinks(exported); - if (links.cjsExportMerged) { - return links.cjsExportMerged; - } - const merged = exported.flags & SymbolFlags.Transient ? exported : cloneSymbol(exported); - merged.flags = merged.flags | SymbolFlags.ValueModule; - if (merged.exports === undefined) { - merged.exports = createSymbolTable(); - } - moduleSymbol.exports!.forEach((s, name) => { - if (name === InternalSymbolName.ExportEquals) return; - merged.exports!.set(name, merged.exports!.has(name) ? mergeSymbol(merged.exports!.get(name)!, s) : s); - }); - getSymbolLinks(merged).cjsExportMerged = merged; - return links.cjsExportMerged = merged; - } + // An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export =' + // references a symbol that is at least declared as a module or a variable. The target of the 'export =' may + // combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable). + function resolveESModuleSymbol(moduleSymbol: Symbol | undefined, referencingLocation: Node, dontResolveAlias: boolean, suppressInteropError: boolean): Symbol | undefined { + const symbol = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias); - // An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export =' - // references a symbol that is at least declared as a module or a variable. The target of the 'export =' may - // combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable). - function resolveESModuleSymbol(moduleSymbol: Symbol | undefined, referencingLocation: Node, dontResolveAlias: boolean, suppressInteropError: boolean): Symbol | undefined { - const symbol = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias); + if (!dontResolveAlias && symbol) { + if (!suppressInteropError && !(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable)) && !getDeclarationOfKind(symbol, SyntaxKind.SourceFile)) { + const compilerOptionName = moduleKind >= ModuleKind.ES2015 + ? "allowSyntheticDefaultImports" + : "esModuleInterop"; - if (!dontResolveAlias && symbol) { - if (!suppressInteropError && !(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable)) && !getDeclarationOfKind(symbol, SyntaxKind.SourceFile)) { - const compilerOptionName = moduleKind >= ModuleKind.ES2015 - ? "allowSyntheticDefaultImports" - : "esModuleInterop"; + error(referencingLocation, Diagnostics.This_module_can_only_be_referenced_with_ECMAScript_imports_Slashexports_by_turning_on_the_0_flag_and_referencing_its_default_export, compilerOptionName); - error(referencingLocation, Diagnostics.This_module_can_only_be_referenced_with_ECMAScript_imports_Slashexports_by_turning_on_the_0_flag_and_referencing_its_default_export, compilerOptionName); + return symbol; + } - return symbol; + const referenceParent = referencingLocation.parent; + if ( + (isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || + isImportCall(referenceParent) + ) { + const reference = isImportCall(referenceParent) ? referenceParent.arguments[0] : referenceParent.moduleSpecifier; + const type = getTypeOfSymbol(symbol); + const defaultOnlyType = getTypeWithSyntheticDefaultOnly(type, symbol, moduleSymbol!, reference); + if (defaultOnlyType) { + return cloneTypeAsModuleType(symbol, defaultOnlyType, referenceParent); } - const referenceParent = referencingLocation.parent; - if ( - (isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || - isImportCall(referenceParent) - ) { - const reference = isImportCall(referenceParent) ? referenceParent.arguments[0] : referenceParent.moduleSpecifier; - const type = getTypeOfSymbol(symbol); - const defaultOnlyType = getTypeWithSyntheticDefaultOnly(type, symbol, moduleSymbol!, reference); - if (defaultOnlyType) { - return cloneTypeAsModuleType(symbol, defaultOnlyType, referenceParent); + if (getESModuleInterop(compilerOptions)) { + let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call); + if (!sigs || !sigs.length) { + sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct); } - - if (getESModuleInterop(compilerOptions)) { - let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call); - if (!sigs || !sigs.length) { - sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct); - } - if ((sigs && sigs.length) || getPropertyOfType(type, InternalSymbolName.Default)) { - const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!, reference); - return cloneTypeAsModuleType(symbol, moduleType, referenceParent); - } + if ((sigs && sigs.length) || getPropertyOfType(type, InternalSymbolName.Default)) { + const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!, reference); + return cloneTypeAsModuleType(symbol, moduleType, referenceParent); } } } - return symbol; } + return symbol; + } - /** - * Create a new symbol which has the module's type less the call and construct signatures - */ - function cloneTypeAsModuleType(symbol: Symbol, moduleType: Type, referenceParent: ImportDeclaration | ImportCall) { - const result = createSymbol(symbol.flags, symbol.escapedName); - result.declarations = symbol.declarations ? symbol.declarations.slice() : []; - result.parent = symbol.parent; - result.target = symbol; - result.originatingImport = referenceParent; - if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; - if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; - if (symbol.members) result.members = new Map(symbol.members); - if (symbol.exports) result.exports = new Map(symbol.exports); - const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above - result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); - return result; - } + /** + * Create a new symbol which has the module's type less the call and construct signatures + */ + function cloneTypeAsModuleType(symbol: Symbol, moduleType: Type, referenceParent: ImportDeclaration | ImportCall) { + const result = createSymbol(symbol.flags, symbol.escapedName); + result.declarations = symbol.declarations ? symbol.declarations.slice() : []; + result.parent = symbol.parent; + result.target = symbol; + result.originatingImport = referenceParent; + if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; + if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; + if (symbol.members) result.members = new Map(symbol.members); + if (symbol.exports) result.exports = new Map(symbol.exports); + const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above + result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); + return result; + } - function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { - return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== undefined; - } + function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { + return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== undefined; + } - function getExportsOfModuleAsArray(moduleSymbol: Symbol): Symbol[] { - return symbolsToArray(getExportsOfModule(moduleSymbol)); - } + function getExportsOfModuleAsArray(moduleSymbol: Symbol): Symbol[] { + return symbolsToArray(getExportsOfModule(moduleSymbol)); + } - function getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[] { - const exports = getExportsOfModuleAsArray(moduleSymbol); - const exportEquals = resolveExternalModuleSymbol(moduleSymbol); - if (exportEquals !== moduleSymbol) { - const type = getTypeOfSymbol(exportEquals); - if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { - addRange(exports, getPropertiesOfType(type)); - } + function getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[] { + const exports = getExportsOfModuleAsArray(moduleSymbol); + const exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals !== moduleSymbol) { + const type = getTypeOfSymbol(exportEquals); + if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { + addRange(exports, getPropertiesOfType(type)); } - return exports; } + return exports; + } - function forEachExportAndPropertyOfModule(moduleSymbol: Symbol, cb: (symbol: Symbol, key: __String) => void): void { - const exports = getExportsOfModule(moduleSymbol); - exports.forEach((symbol, key) => { - if (!isReservedMemberName(key)) { - cb(symbol, key); - } - }); - const exportEquals = resolveExternalModuleSymbol(moduleSymbol); - if (exportEquals !== moduleSymbol) { - const type = getTypeOfSymbol(exportEquals); - if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { - forEachPropertyOfType(type, (symbol, escapedName) => { - cb(symbol, escapedName); - }); - } + function forEachExportAndPropertyOfModule(moduleSymbol: Symbol, cb: (symbol: Symbol, key: __String) => void): void { + const exports = getExportsOfModule(moduleSymbol); + exports.forEach((symbol, key) => { + if (!isReservedMemberName(key)) { + cb(symbol, key); } - } - - function tryGetMemberInModuleExports(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { - const symbolTable = getExportsOfModule(moduleSymbol); - if (symbolTable) { - return symbolTable.get(memberName); + }); + const exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals !== moduleSymbol) { + const type = getTypeOfSymbol(exportEquals); + if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { + forEachPropertyOfType(type, (symbol, escapedName) => { + cb(symbol, escapedName); + }); } } + } - function tryGetMemberInModuleExportsAndProperties(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { - const symbol = tryGetMemberInModuleExports(memberName, moduleSymbol); - if (symbol) { - return symbol; - } - - const exportEquals = resolveExternalModuleSymbol(moduleSymbol); - if (exportEquals === moduleSymbol) { - return undefined; - } - - const type = getTypeOfSymbol(exportEquals); - return shouldTreatPropertiesOfExternalModuleAsExports(type) ? getPropertyOfType(type, memberName) : undefined; + function tryGetMemberInModuleExports(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { + const symbolTable = getExportsOfModule(moduleSymbol); + if (symbolTable) { + return symbolTable.get(memberName); } + } - function shouldTreatPropertiesOfExternalModuleAsExports(resolvedExternalModuleType: Type) { - return !(resolvedExternalModuleType.flags & TypeFlags.Primitive || - getObjectFlags(resolvedExternalModuleType) & ObjectFlags.Class || - // `isArrayOrTupleLikeType` is too expensive to use in this auto-imports hot path - isArrayType(resolvedExternalModuleType) || - isTupleType(resolvedExternalModuleType)); + function tryGetMemberInModuleExportsAndProperties(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { + const symbol = tryGetMemberInModuleExports(memberName, moduleSymbol); + if (symbol) { + return symbol; } - function getExportsOfSymbol(symbol: Symbol): SymbolTable { - return symbol.flags & SymbolFlags.LateBindingContainer ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedExports) : - symbol.flags & SymbolFlags.Module ? getExportsOfModule(symbol) : - symbol.exports || emptySymbols; + const exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals === moduleSymbol) { + return undefined; } - function getExportsOfModule(moduleSymbol: Symbol): SymbolTable { - const links = getSymbolLinks(moduleSymbol); - return links.resolvedExports || (links.resolvedExports = getExportsOfModuleWorker(moduleSymbol)); - } + const type = getTypeOfSymbol(exportEquals); + return shouldTreatPropertiesOfExternalModuleAsExports(type) ? getPropertyOfType(type, memberName) : undefined; + } - interface ExportCollisionTracker { - specifierText: string; - exportsWithDuplicate: ExportDeclaration[]; - } + function shouldTreatPropertiesOfExternalModuleAsExports(resolvedExternalModuleType: Type) { + return !(resolvedExternalModuleType.flags & TypeFlags.Primitive || + getObjectFlags(resolvedExternalModuleType) & ObjectFlags.Class || + // `isArrayOrTupleLikeType` is too expensive to use in this auto-imports hot path + isArrayType(resolvedExternalModuleType) || + isTupleType(resolvedExternalModuleType)); + } - type ExportCollisionTrackerTable = UnderscoreEscapedMap; + function getExportsOfSymbol(symbol: Symbol): SymbolTable { + return symbol.flags & SymbolFlags.LateBindingContainer ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedExports) : + symbol.flags & SymbolFlags.Module ? getExportsOfModule(symbol) : + symbol.exports || emptySymbols; + } - /** - * Extends one symbol table with another while collecting information on name collisions for error message generation into the `lookupTable` argument - * Not passing `lookupTable` and `exportNode` disables this collection, and just extends the tables - */ - function extendExportSymbols(target: SymbolTable, source: SymbolTable | undefined, lookupTable?: ExportCollisionTrackerTable, exportNode?: ExportDeclaration) { - if (!source) return; - source.forEach((sourceSymbol, id) => { - if (id === InternalSymbolName.Default) return; + function getExportsOfModule(moduleSymbol: Symbol): SymbolTable { + const links = getSymbolLinks(moduleSymbol); + return links.resolvedExports || (links.resolvedExports = getExportsOfModuleWorker(moduleSymbol)); + } - const targetSymbol = target.get(id); - if (!targetSymbol) { - target.set(id, sourceSymbol); - if (lookupTable && exportNode) { - lookupTable.set(id, { - specifierText: getTextOfNode(exportNode.moduleSpecifier!) - } as ExportCollisionTracker); - } + interface ExportCollisionTracker { + specifierText: string; + exportsWithDuplicate: ExportDeclaration[]; + } + + type ExportCollisionTrackerTable = UnderscoreEscapedMap; + + /** + * Extends one symbol table with another while collecting information on name collisions for error message generation into the `lookupTable` argument + * Not passing `lookupTable` and `exportNode` disables this collection, and just extends the tables + */ + function extendExportSymbols(target: SymbolTable, source: SymbolTable | undefined, lookupTable?: ExportCollisionTrackerTable, exportNode?: ExportDeclaration) { + if (!source) return; + source.forEach((sourceSymbol, id) => { + if (id === InternalSymbolName.Default) return; + + const targetSymbol = target.get(id); + if (!targetSymbol) { + target.set(id, sourceSymbol); + if (lookupTable && exportNode) { + lookupTable.set(id, { + specifierText: getTextOfNode(exportNode.moduleSpecifier!) + } as ExportCollisionTracker); } - else if (lookupTable && exportNode && targetSymbol && resolveSymbol(targetSymbol) !== resolveSymbol(sourceSymbol)) { - const collisionTracker = lookupTable.get(id)!; - if (!collisionTracker.exportsWithDuplicate) { - collisionTracker.exportsWithDuplicate = [exportNode]; - } - else { - collisionTracker.exportsWithDuplicate.push(exportNode); - } + } + else if (lookupTable && exportNode && targetSymbol && resolveSymbol(targetSymbol) !== resolveSymbol(sourceSymbol)) { + const collisionTracker = lookupTable.get(id)!; + if (!collisionTracker.exportsWithDuplicate) { + collisionTracker.exportsWithDuplicate = [exportNode]; } - }); - } + else { + collisionTracker.exportsWithDuplicate.push(exportNode); + } + } + }); + } - function getExportsOfModuleWorker(moduleSymbol: Symbol): SymbolTable { - const visitedSymbols: Symbol[] = []; + function getExportsOfModuleWorker(moduleSymbol: Symbol): SymbolTable { + const visitedSymbols: Symbol[] = []; - // A module defined by an 'export=' consists of one export that needs to be resolved - moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); + // A module defined by an 'export=' consists of one export that needs to be resolved + moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); - return visit(moduleSymbol) || emptySymbols; + return visit(moduleSymbol) || emptySymbols; - // The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example, - // module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error. - function visit(symbol: Symbol | undefined): SymbolTable | undefined { - if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) { - return; - } - const symbols = new Map(symbol.exports); - // All export * declarations are collected in an __export symbol by the binder - const exportStars = symbol.exports.get(InternalSymbolName.ExportStar); - if (exportStars) { - const nestedSymbols = createSymbolTable(); - const lookupTable: ExportCollisionTrackerTable = new Map(); - if (exportStars.declarations) { - for (const node of exportStars.declarations) { - const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); - const exportedSymbols = visit(resolvedModule); - extendExportSymbols( - nestedSymbols, - exportedSymbols, - lookupTable, - node as ExportDeclaration - ); - } + // The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example, + // module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error. + function visit(symbol: Symbol | undefined): SymbolTable | undefined { + if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) { + return; + } + const symbols = new Map(symbol.exports); + // All export * declarations are collected in an __export symbol by the binder + const exportStars = symbol.exports.get(InternalSymbolName.ExportStar); + if (exportStars) { + const nestedSymbols = createSymbolTable(); + const lookupTable: ExportCollisionTrackerTable = new Map(); + if (exportStars.declarations) { + for (const node of exportStars.declarations) { + const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); + const exportedSymbols = visit(resolvedModule); + extendExportSymbols( + nestedSymbols, + exportedSymbols, + lookupTable, + node as ExportDeclaration + ); } - lookupTable.forEach(({ exportsWithDuplicate }, id) => { - // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself - if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || symbols.has(id)) { - return; - } - for (const node of exportsWithDuplicate) { - diagnostics.add(createDiagnosticForNode( - node, - Diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, - lookupTable.get(id)!.specifierText, - unescapeLeadingUnderscores(id) - )); - } - }); - extendExportSymbols(symbols, nestedSymbols); } - return symbols; + lookupTable.forEach(({ exportsWithDuplicate }, id) => { + // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself + if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || symbols.has(id)) { + return; + } + for (const node of exportsWithDuplicate) { + diagnostics.add(createDiagnosticForNode( + node, + Diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, + lookupTable.get(id)!.specifierText, + unescapeLeadingUnderscores(id) + )); + } + }); + extendExportSymbols(symbols, nestedSymbols); } + return symbols; } + } - function getMergedSymbol(symbol: Symbol): Symbol; - function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined; - function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined { - let merged: Symbol; - return symbol && symbol.mergeId && (merged = mergedSymbols[symbol.mergeId]) ? merged : symbol; - } + function getMergedSymbol(symbol: Symbol): Symbol; + function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined; + function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined { + let merged: Symbol; + return symbol && symbol.mergeId && (merged = mergedSymbols[symbol.mergeId]) ? merged : symbol; + } - function getSymbolOfNode(node: Declaration): Symbol; - function getSymbolOfNode(node: Node): Symbol | undefined; - function getSymbolOfNode(node: Node): Symbol | undefined { - return getMergedSymbol(node.symbol && getLateBoundSymbol(node.symbol)); - } + function getSymbolOfNode(node: Declaration): Symbol; + function getSymbolOfNode(node: Node): Symbol | undefined; + function getSymbolOfNode(node: Node): Symbol | undefined { + return getMergedSymbol(node.symbol && getLateBoundSymbol(node.symbol)); + } - function getParentOfSymbol(symbol: Symbol): Symbol | undefined { - return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent)); - } + function getParentOfSymbol(symbol: Symbol): Symbol | undefined { + return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent)); + } - function getAlternativeContainingModules(symbol: Symbol, enclosingDeclaration: Node): Symbol[] { - const containingFile = getSourceFileOfNode(enclosingDeclaration); - const id = getNodeId(containingFile); - const links = getSymbolLinks(symbol); - let results: Symbol[] | undefined; - if (links.extendedContainersByFile && (results = links.extendedContainersByFile.get(id))) { - return results; - } - if (containingFile && containingFile.imports) { - // Try to make an import using an import already in the enclosing file, if possible - for (const importRef of containingFile.imports) { - if (nodeIsSynthesized(importRef)) continue; // Synthetic names can't be resolved by `resolveExternalModuleName` - they'll cause a debug assert if they error - const resolvedModule = resolveExternalModuleName(enclosingDeclaration, importRef, /*ignoreErrors*/ true); - if (!resolvedModule) continue; - const ref = getAliasForSymbolInContainer(resolvedModule, symbol); - if (!ref) continue; - results = append(results, resolvedModule); - } - if (length(results)) { - (links.extendedContainersByFile || (links.extendedContainersByFile = new Map())).set(id, results!); - return results!; - } - } - if (links.extendedContainers) { - return links.extendedContainers; - } - // No results from files already being imported by this file - expand search (expensive, but not location-specific, so cached) - const otherFiles = host.getSourceFiles(); - for (const file of otherFiles) { - if (!isExternalModule(file)) continue; - const sym = getSymbolOfNode(file); - const ref = getAliasForSymbolInContainer(sym, symbol); + function getAlternativeContainingModules(symbol: Symbol, enclosingDeclaration: Node): Symbol[] { + const containingFile = getSourceFileOfNode(enclosingDeclaration); + const id = getNodeId(containingFile); + const links = getSymbolLinks(symbol); + let results: Symbol[] | undefined; + if (links.extendedContainersByFile && (results = links.extendedContainersByFile.get(id))) { + return results; + } + if (containingFile && containingFile.imports) { + // Try to make an import using an import already in the enclosing file, if possible + for (const importRef of containingFile.imports) { + if (nodeIsSynthesized(importRef)) continue; // Synthetic names can't be resolved by `resolveExternalModuleName` - they'll cause a debug assert if they error + const resolvedModule = resolveExternalModuleName(enclosingDeclaration, importRef, /*ignoreErrors*/ true); + if (!resolvedModule) continue; + const ref = getAliasForSymbolInContainer(resolvedModule, symbol); if (!ref) continue; - results = append(results, sym); + results = append(results, resolvedModule); } - return links.extendedContainers = results || emptyArray; + if (length(results)) { + (links.extendedContainersByFile || (links.extendedContainersByFile = new Map())).set(id, results!); + return results!; + } + } + if (links.extendedContainers) { + return links.extendedContainers; } + // No results from files already being imported by this file - expand search (expensive, but not location-specific, so cached) + const otherFiles = host.getSourceFiles(); + for (const file of otherFiles) { + if (!isExternalModule(file)) continue; + const sym = getSymbolOfNode(file); + const ref = getAliasForSymbolInContainer(sym, symbol); + if (!ref) continue; + results = append(results, sym); + } + return links.extendedContainers = results || emptyArray; + } - /** - * Attempts to find the symbol corresponding to the container a symbol is in - usually this - * is just its' `.parent`, but for locals, this value is `undefined` - */ - function getContainersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): Symbol[] | undefined { - const container = getParentOfSymbol(symbol); - // Type parameters end up in the `members` lists but are not externally visible - if (container && !(symbol.flags & SymbolFlags.TypeParameter)) { - const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer); - const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration); - const objectLiteralContainer = getVariableDeclarationOfObjectLiteral(container, meaning); - if ( - enclosingDeclaration && - container.flags & getQualifiedLeftMeaning(meaning) && - getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false) - ) { - return append(concatenate(concatenate([container], additionalContainers), reexportContainers), objectLiteralContainer); // This order expresses a preference for the real container if it is in scope - } - // we potentially have a symbol which is a member of the instance side of something - look for a variable in scope with the container's type - // which may be acting like a namespace (eg, `Symbol` acts like a namespace when looking up `Symbol.toStringTag`) - const firstVariableMatch = !(container.flags & getQualifiedLeftMeaning(meaning)) - && container.flags & SymbolFlags.Type - && getDeclaredTypeOfSymbol(container).flags & TypeFlags.Object - && meaning === SymbolFlags.Value - ? forEachSymbolTableInScope(enclosingDeclaration, t => { - return forEachEntry(t, s => { - if (s.flags & getQualifiedLeftMeaning(meaning) && getTypeOfSymbol(s) === getDeclaredTypeOfSymbol(container)) { - return s; - } - }); - }) : undefined; - let res = firstVariableMatch ? [firstVariableMatch, ...additionalContainers, container] : [...additionalContainers, container]; - res = append(res, objectLiteralContainer); - res = addRange(res, reexportContainers); - return res; - } - const candidates = mapDefined(symbol.declarations, d => { - if (!isAmbientModule(d) && d.parent && hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { - return getSymbolOfNode(d.parent); - } - if (isClassExpression(d) && isBinaryExpression(d.parent) && d.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAccessExpression(d.parent.left) && isEntityNameExpression(d.parent.left.expression)) { - if (isModuleExportsAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) { - return getSymbolOfNode(getSourceFileOfNode(d)); + /** + * Attempts to find the symbol corresponding to the container a symbol is in - usually this + * is just its' `.parent`, but for locals, this value is `undefined` + */ + function getContainersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): Symbol[] | undefined { + const container = getParentOfSymbol(symbol); + // Type parameters end up in the `members` lists but are not externally visible + if (container && !(symbol.flags & SymbolFlags.TypeParameter)) { + const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer); + const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration); + const objectLiteralContainer = getVariableDeclarationOfObjectLiteral(container, meaning); + if ( + enclosingDeclaration && + container.flags & getQualifiedLeftMeaning(meaning) && + getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false) + ) { + return append(concatenate(concatenate([container], additionalContainers), reexportContainers), objectLiteralContainer); // This order expresses a preference for the real container if it is in scope + } + // we potentially have a symbol which is a member of the instance side of something - look for a variable in scope with the container's type + // which may be acting like a namespace (eg, `Symbol` acts like a namespace when looking up `Symbol.toStringTag`) + const firstVariableMatch = !(container.flags & getQualifiedLeftMeaning(meaning)) + && container.flags & SymbolFlags.Type + && getDeclaredTypeOfSymbol(container).flags & TypeFlags.Object + && meaning === SymbolFlags.Value + ? forEachSymbolTableInScope(enclosingDeclaration, t => { + return forEachEntry(t, s => { + if (s.flags & getQualifiedLeftMeaning(meaning) && getTypeOfSymbol(s) === getDeclaredTypeOfSymbol(container)) { + return s; } - checkExpressionCached(d.parent.left.expression); - return getNodeLinks(d.parent.left.expression).resolvedSymbol; - } - }); - if (!length(candidates)) { - return undefined; - } - return mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined); - - function fileSymbolIfFileSymbolExportEqualsContainer(d: Declaration) { - return container && getFileSymbolIfFileSymbolExportEqualsContainer(d, container); - } + }); + }) : undefined; + let res = firstVariableMatch ? [firstVariableMatch, ...additionalContainers, container] : [...additionalContainers, container]; + res = append(res, objectLiteralContainer); + res = addRange(res, reexportContainers); + return res; } - - function getVariableDeclarationOfObjectLiteral(symbol: Symbol, meaning: SymbolFlags) { - // If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct - // from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however, - // we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal. - const firstDecl: Node | false = !!length(symbol.declarations) && first(symbol.declarations!); - if (meaning & SymbolFlags.Value && firstDecl && firstDecl.parent && isVariableDeclaration(firstDecl.parent)) { - if (isObjectLiteralExpression(firstDecl) && firstDecl === firstDecl.parent.initializer || isTypeLiteralNode(firstDecl) && firstDecl === firstDecl.parent.type) { - return getSymbolOfNode(firstDecl.parent); + const candidates = mapDefined(symbol.declarations, d => { + if (!isAmbientModule(d) && d.parent && hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { + return getSymbolOfNode(d.parent); + } + if (isClassExpression(d) && isBinaryExpression(d.parent) && d.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAccessExpression(d.parent.left) && isEntityNameExpression(d.parent.left.expression)) { + if (isModuleExportsAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) { + return getSymbolOfNode(getSourceFileOfNode(d)); } + checkExpressionCached(d.parent.left.expression); + return getNodeLinks(d.parent.left.expression).resolvedSymbol; } + }); + if (!length(candidates)) { + return undefined; } + return mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined); - function getFileSymbolIfFileSymbolExportEqualsContainer(d: Declaration, container: Symbol) { - const fileSymbol = getExternalModuleContainer(d); - const exported = fileSymbol && fileSymbol.exports && fileSymbol.exports.get(InternalSymbolName.ExportEquals); - return exported && getSymbolIfSameReference(exported, container) ? fileSymbol : undefined; + function fileSymbolIfFileSymbolExportEqualsContainer(d: Declaration) { + return container && getFileSymbolIfFileSymbolExportEqualsContainer(d, container); } + } - function getAliasForSymbolInContainer(container: Symbol, symbol: Symbol) { - if (container === getParentOfSymbol(symbol)) { - // fast path, `symbol` is either already the alias or isn't aliased - return symbol; - } - // Check if container is a thing with an `export=` which points directly at `symbol`, and if so, return - // the container itself as the alias for the symbol - const exportEquals = container.exports && container.exports.get(InternalSymbolName.ExportEquals); - if (exportEquals && getSymbolIfSameReference(exportEquals, symbol)) { - return container; + function getVariableDeclarationOfObjectLiteral(symbol: Symbol, meaning: SymbolFlags) { + // If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct + // from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however, + // we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal. + const firstDecl: Node | false = !!length(symbol.declarations) && first(symbol.declarations!); + if (meaning & SymbolFlags.Value && firstDecl && firstDecl.parent && isVariableDeclaration(firstDecl.parent)) { + if (isObjectLiteralExpression(firstDecl) && firstDecl === firstDecl.parent.initializer || isTypeLiteralNode(firstDecl) && firstDecl === firstDecl.parent.type) { + return getSymbolOfNode(firstDecl.parent); } - const exports = getExportsOfSymbol(container); - const quick = exports.get(symbol.escapedName); - if (quick && getSymbolIfSameReference(quick, symbol)) { - return quick; - } - return forEachEntry(exports, exported => { - if (getSymbolIfSameReference(exported, symbol)) { - return exported; - } - }); } + } - /** - * Checks if two symbols, through aliasing and/or merging, refer to the same thing - */ - function getSymbolIfSameReference(s1: Symbol, s2: Symbol) { - if (getMergedSymbol(resolveSymbol(getMergedSymbol(s1))) === getMergedSymbol(resolveSymbol(getMergedSymbol(s2)))) { - return s1; - } - } + function getFileSymbolIfFileSymbolExportEqualsContainer(d: Declaration, container: Symbol) { + const fileSymbol = getExternalModuleContainer(d); + const exported = fileSymbol && fileSymbol.exports && fileSymbol.exports.get(InternalSymbolName.ExportEquals); + return exported && getSymbolIfSameReference(exported, container) ? fileSymbol : undefined; + } - function getExportSymbolOfValueSymbolIfExported(symbol: Symbol): Symbol; - function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined; - function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined { - return getMergedSymbol(symbol && (symbol.flags & SymbolFlags.ExportValue) !== 0 ? symbol.exportSymbol : symbol); + function getAliasForSymbolInContainer(container: Symbol, symbol: Symbol) { + if (container === getParentOfSymbol(symbol)) { + // fast path, `symbol` is either already the alias or isn't aliased + return symbol; } - - function symbolIsValue(symbol: Symbol): boolean { - return !!(symbol.flags & SymbolFlags.Value || symbol.flags & SymbolFlags.Alias && resolveAlias(symbol).flags & SymbolFlags.Value && !getTypeOnlyAliasDeclaration(symbol)); + // Check if container is a thing with an `export=` which points directly at `symbol`, and if so, return + // the container itself as the alias for the symbol + const exportEquals = container.exports && container.exports.get(InternalSymbolName.ExportEquals); + if (exportEquals && getSymbolIfSameReference(exportEquals, symbol)) { + return container; } - - function findConstructorDeclaration(node: ClassLikeDeclaration): ConstructorDeclaration | undefined { - const members = node.members; - for (const member of members) { - if (member.kind === SyntaxKind.Constructor && nodeIsPresent((member as ConstructorDeclaration).body)) { - return member as ConstructorDeclaration; - } + const exports = getExportsOfSymbol(container); + const quick = exports.get(symbol.escapedName); + if (quick && getSymbolIfSameReference(quick, symbol)) { + return quick; + } + return forEachEntry(exports, exported => { + if (getSymbolIfSameReference(exported, symbol)) { + return exported; } + }); + } + + /** + * Checks if two symbols, through aliasing and/or merging, refer to the same thing + */ + function getSymbolIfSameReference(s1: Symbol, s2: Symbol) { + if (getMergedSymbol(resolveSymbol(getMergedSymbol(s1))) === getMergedSymbol(resolveSymbol(getMergedSymbol(s2)))) { + return s1; } + } + + function getExportSymbolOfValueSymbolIfExported(symbol: Symbol): Symbol; + function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined; + function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined { + return getMergedSymbol(symbol && (symbol.flags & SymbolFlags.ExportValue) !== 0 ? symbol.exportSymbol : symbol); + } + + function symbolIsValue(symbol: Symbol): boolean { + return !!(symbol.flags & SymbolFlags.Value || symbol.flags & SymbolFlags.Alias && resolveAlias(symbol).flags & SymbolFlags.Value && !getTypeOnlyAliasDeclaration(symbol)); + } - function createType(flags: TypeFlags): Type { - const result = new Type(checker, flags); - typeCount++; - result.id = typeCount; - if (produceDiagnostics) { // Only record types from one checker - tracing?.recordType(result); + function findConstructorDeclaration(node: ClassLikeDeclaration): ConstructorDeclaration | undefined { + const members = node.members; + for (const member of members) { + if (member.kind === SyntaxKind.Constructor && nodeIsPresent((member as ConstructorDeclaration).body)) { + return member as ConstructorDeclaration; } - return result; } + } - function createOriginType(flags: TypeFlags): Type { - return new Type(checker, flags); + function createType(flags: TypeFlags): Type { + const result = new Type(checker, flags); + typeCount++; + result.id = typeCount; + if (produceDiagnostics) { // Only record types from one checker + tracing?.recordType(result); } + return result; + } - function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags: ObjectFlags = 0): IntrinsicType { - const type = createType(kind) as IntrinsicType; - type.intrinsicName = intrinsicName; - type.objectFlags = objectFlags; - return type; - } + function createOriginType(flags: TypeFlags): Type { + return new Type(checker, flags); + } - function createObjectType(objectFlags: ObjectFlags, symbol?: Symbol): ObjectType { - const type = createType(TypeFlags.Object) as ObjectType; - type.objectFlags = objectFlags; - type.symbol = symbol!; - type.members = undefined; - type.properties = undefined; - type.callSignatures = undefined; - type.constructSignatures = undefined; - type.indexInfos = undefined; - return type; - } + function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags: ObjectFlags = 0): IntrinsicType { + const type = createType(kind) as IntrinsicType; + type.intrinsicName = intrinsicName; + type.objectFlags = objectFlags; + return type; + } - function createTypeofType() { - return getUnionType(arrayFrom(typeofEQFacts.keys(), getStringLiteralType)); - } + function createObjectType(objectFlags: ObjectFlags, symbol?: Symbol): ObjectType { + const type = createType(TypeFlags.Object) as ObjectType; + type.objectFlags = objectFlags; + type.symbol = symbol!; + type.members = undefined; + type.properties = undefined; + type.callSignatures = undefined; + type.constructSignatures = undefined; + type.indexInfos = undefined; + return type; + } - function createTypeParameter(symbol?: Symbol) { - const type = createType(TypeFlags.TypeParameter) as TypeParameter; - if (symbol) type.symbol = symbol; - return type; - } + function createTypeofType() { + return getUnionType(arrayFrom(typeofEQFacts.keys(), getStringLiteralType)); + } - // A reserved member name starts with two underscores, but the third character cannot be an underscore, - // @, or #. A third underscore indicates an escaped form of an identifier that started - // with at least two underscores. The @ character indicates that the name is denoted by a well known ES - // Symbol instance and the # character indicates that the name is a PrivateIdentifier. - function isReservedMemberName(name: __String) { - return (name as string).charCodeAt(0) === CharacterCodes._ && - (name as string).charCodeAt(1) === CharacterCodes._ && - (name as string).charCodeAt(2) !== CharacterCodes._ && - (name as string).charCodeAt(2) !== CharacterCodes.at && - (name as string).charCodeAt(2) !== CharacterCodes.hash; - } + function createTypeParameter(symbol?: Symbol) { + const type = createType(TypeFlags.TypeParameter) as TypeParameter; + if (symbol) type.symbol = symbol; + return type; + } - function getNamedMembers(members: SymbolTable): Symbol[] { - let result: Symbol[] | undefined; - members.forEach((symbol, id) => { - if (isNamedMember(symbol, id)) { - (result || (result = [])).push(symbol); - } - }); - return result || emptyArray; - } + // A reserved member name starts with two underscores, but the third character cannot be an underscore, + // @, or #. A third underscore indicates an escaped form of an identifier that started + // with at least two underscores. The @ character indicates that the name is denoted by a well known ES + // Symbol instance and the # character indicates that the name is a PrivateIdentifier. + function isReservedMemberName(name: __String) { + return (name as string).charCodeAt(0) === CharacterCodes._ && + (name as string).charCodeAt(1) === CharacterCodes._ && + (name as string).charCodeAt(2) !== CharacterCodes._ && + (name as string).charCodeAt(2) !== CharacterCodes.at && + (name as string).charCodeAt(2) !== CharacterCodes.hash; + } - function isNamedMember(member: Symbol, escapedName: __String) { - return !isReservedMemberName(escapedName) && symbolIsValue(member); - } + function getNamedMembers(members: SymbolTable): Symbol[] { + let result: Symbol[] | undefined; + members.forEach((symbol, id) => { + if (isNamedMember(symbol, id)) { + (result || (result = [])).push(symbol); + } + }); + return result || emptyArray; + } - function getNamedOrIndexSignatureMembers(members: SymbolTable): Symbol[] { - const result = getNamedMembers(members); - const index = getIndexSymbolFromSymbolTable(members); - return index ? concatenate(result, [index]) : result; - } + function isNamedMember(member: Symbol, escapedName: __String) { + return !isReservedMemberName(escapedName) && symbolIsValue(member); + } - function setStructuredTypeMembers(type: StructuredType, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], indexInfos: readonly IndexInfo[]): ResolvedType { - const resolved = type as ResolvedType; - resolved.members = members; - resolved.properties = emptyArray; - resolved.callSignatures = callSignatures; - resolved.constructSignatures = constructSignatures; - resolved.indexInfos = indexInfos; - // This can loop back to getPropertyOfType() which would crash if `callSignatures` & `constructSignatures` are not initialized. - if (members !== emptySymbols) resolved.properties = getNamedMembers(members); - return resolved; - } + function getNamedOrIndexSignatureMembers(members: SymbolTable): Symbol[] { + const result = getNamedMembers(members); + const index = getIndexSymbolFromSymbolTable(members); + return index ? concatenate(result, [index]) : result; + } - function createAnonymousType(symbol: Symbol | undefined, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], indexInfos: readonly IndexInfo[]): ResolvedType { - return setStructuredTypeMembers(createObjectType(ObjectFlags.Anonymous, symbol), - members, callSignatures, constructSignatures, indexInfos); - } + function setStructuredTypeMembers(type: StructuredType, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], indexInfos: readonly IndexInfo[]): ResolvedType { + const resolved = type as ResolvedType; + resolved.members = members; + resolved.properties = emptyArray; + resolved.callSignatures = callSignatures; + resolved.constructSignatures = constructSignatures; + resolved.indexInfos = indexInfos; + // This can loop back to getPropertyOfType() which would crash if `callSignatures` & `constructSignatures` are not initialized. + if (members !== emptySymbols) resolved.properties = getNamedMembers(members); + return resolved; + } - function getResolvedTypeWithoutAbstractConstructSignatures(type: ResolvedType) { - if (type.constructSignatures.length === 0) return type; - if (type.objectTypeWithoutAbstractConstructSignatures) return type.objectTypeWithoutAbstractConstructSignatures; - const constructSignatures = filter(type.constructSignatures, signature => !(signature.flags & SignatureFlags.Abstract)); - if (type.constructSignatures === constructSignatures) return type; - const typeCopy = createAnonymousType( - type.symbol, - type.members, - type.callSignatures, - some(constructSignatures) ? constructSignatures : emptyArray, - type.indexInfos); - type.objectTypeWithoutAbstractConstructSignatures = typeCopy; - typeCopy.objectTypeWithoutAbstractConstructSignatures = typeCopy; - return typeCopy; - } + function createAnonymousType(symbol: Symbol | undefined, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], indexInfos: readonly IndexInfo[]): ResolvedType { + return setStructuredTypeMembers(createObjectType(ObjectFlags.Anonymous, symbol), + members, callSignatures, constructSignatures, indexInfos); + } - function forEachSymbolTableInScope(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable, ignoreQualification?: boolean, isLocalNameLookup?: boolean, scopeNode?: Node) => T): T { - let result: T; - for (let location = enclosingDeclaration; location; location = location.parent) { - // Locals of a source file are not in scope (because they get merged into the global symbol table) - if (location.locals && !isGlobalSourceFile(location)) { - if (result = callback(location.locals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true, location)) { - return result; - } + function getResolvedTypeWithoutAbstractConstructSignatures(type: ResolvedType) { + if (type.constructSignatures.length === 0) return type; + if (type.objectTypeWithoutAbstractConstructSignatures) return type.objectTypeWithoutAbstractConstructSignatures; + const constructSignatures = filter(type.constructSignatures, signature => !(signature.flags & SignatureFlags.Abstract)); + if (type.constructSignatures === constructSignatures) return type; + const typeCopy = createAnonymousType( + type.symbol, + type.members, + type.callSignatures, + some(constructSignatures) ? constructSignatures : emptyArray, + type.indexInfos); + type.objectTypeWithoutAbstractConstructSignatures = typeCopy; + typeCopy.objectTypeWithoutAbstractConstructSignatures = typeCopy; + return typeCopy; + } + + function forEachSymbolTableInScope(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable, ignoreQualification?: boolean, isLocalNameLookup?: boolean, scopeNode?: Node) => T): T { + let result: T; + for (let location = enclosingDeclaration; location; location = location.parent) { + // Locals of a source file are not in scope (because they get merged into the global symbol table) + if (location.locals && !isGlobalSourceFile(location)) { + if (result = callback(location.locals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true, location)) { + return result; } - switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalOrCommonJsModule(location as SourceFile)) { - break; - } - // falls through - case SyntaxKind.ModuleDeclaration: - const sym = getSymbolOfNode(location as ModuleDeclaration); - // `sym` may not have exports if this module declaration is backed by the symbol for a `const` that's being rewritten - // into a namespace - in such cases, it's best to just let the namespace appear empty (the const members couldn't have referred - // to one another anyway) - if (result = callback(sym?.exports || emptySymbols, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true, location)) { - return result; - } + } + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalOrCommonJsModule(location as SourceFile)) { break; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - // Type parameters are bound into `members` lists so they can merge across declarations - // This is troublesome, since in all other respects, they behave like locals :cries: - // TODO: the below is shared with similar code in `resolveName` - in fact, rephrasing all this symbol - // lookup logic in terms of `resolveName` would be nice - // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals - // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would - // trigger resolving late-bound names, which we may already be in the process of doing while we're here! - let table: UnderscoreEscapedMap | undefined; - // TODO: Should this filtered table be cached in some way? - (getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols).forEach((memberSymbol, key) => { - if (memberSymbol.flags & (SymbolFlags.Type & ~SymbolFlags.Assignment)) { - (table || (table = createSymbolTable())).set(key, memberSymbol); - } - }); - if (table && (result = callback(table, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ false, location))) { - return result; + } + // falls through + case SyntaxKind.ModuleDeclaration: + const sym = getSymbolOfNode(location as ModuleDeclaration); + // `sym` may not have exports if this module declaration is backed by the symbol for a `const` that's being rewritten + // into a namespace - in such cases, it's best to just let the namespace appear empty (the const members couldn't have referred + // to one another anyway) + if (result = callback(sym?.exports || emptySymbols, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true, location)) { + return result; + } + break; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + // Type parameters are bound into `members` lists so they can merge across declarations + // This is troublesome, since in all other respects, they behave like locals :cries: + // TODO: the below is shared with similar code in `resolveName` - in fact, rephrasing all this symbol + // lookup logic in terms of `resolveName` would be nice + // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals + // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would + // trigger resolving late-bound names, which we may already be in the process of doing while we're here! + let table: UnderscoreEscapedMap | undefined; + // TODO: Should this filtered table be cached in some way? + (getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols).forEach((memberSymbol, key) => { + if (memberSymbol.flags & (SymbolFlags.Type & ~SymbolFlags.Assignment)) { + (table || (table = createSymbolTable())).set(key, memberSymbol); } - break; - } + }); + if (table && (result = callback(table, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ false, location))) { + return result; + } + break; } + } + + return callback(globals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true); + } + + function getQualifiedLeftMeaning(rightMeaning: SymbolFlags) { + // If we are looking in value space, the parent meaning is value, other wise it is namespace + return rightMeaning === SymbolFlags.Value ? SymbolFlags.Value : SymbolFlags.Namespace; + } - return callback(globals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true); + function getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap: ESMap = new Map()): Symbol[] | undefined { + if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) { + return undefined; + } + const links = getSymbolLinks(symbol); + const cache = (links.accessibleChainCache ||= new Map()); + // Go from enclosingDeclaration to the first scope we check, so the cache is keyed off the scope and thus shared more + const firstRelevantLocation = forEachSymbolTableInScope(enclosingDeclaration, (_, __, ___, node) => node); + const key = `${useOnlyExternalAliasing ? 0 : 1}|${firstRelevantLocation && getNodeId(firstRelevantLocation)}|${meaning}`; + if (cache.has(key)) { + return cache.get(key); } - function getQualifiedLeftMeaning(rightMeaning: SymbolFlags) { - // If we are looking in value space, the parent meaning is value, other wise it is namespace - return rightMeaning === SymbolFlags.Value ? SymbolFlags.Value : SymbolFlags.Namespace; + const id = getSymbolId(symbol); + let visitedSymbolTables = visitedSymbolTablesMap.get(id); + if (!visitedSymbolTables) { + visitedSymbolTablesMap.set(id, visitedSymbolTables = []); } + const result = forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable); + cache.set(key, result); + return result; - function getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap: ESMap = new Map()): Symbol[] | undefined { - if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) { + /** + * @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already) + */ + function getAccessibleSymbolChainFromSymbolTable(symbols: SymbolTable, ignoreQualification?: boolean, isLocalNameLookup?: boolean): Symbol[] | undefined { + if (!pushIfUnique(visitedSymbolTables!, symbols)) { return undefined; } - const links = getSymbolLinks(symbol); - const cache = (links.accessibleChainCache ||= new Map()); - // Go from enclosingDeclaration to the first scope we check, so the cache is keyed off the scope and thus shared more - const firstRelevantLocation = forEachSymbolTableInScope(enclosingDeclaration, (_, __, ___, node) => node); - const key = `${useOnlyExternalAliasing ? 0 : 1}|${firstRelevantLocation && getNodeId(firstRelevantLocation)}|${meaning}`; - if (cache.has(key)) { - return cache.get(key); - } - const id = getSymbolId(symbol); - let visitedSymbolTables = visitedSymbolTablesMap.get(id); - if (!visitedSymbolTables) { - visitedSymbolTablesMap.set(id, visitedSymbolTables = []); - } - const result = forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable); - cache.set(key, result); + const result = trySymbolTable(symbols, ignoreQualification, isLocalNameLookup); + visitedSymbolTables!.pop(); return result; + } - /** - * @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already) - */ - function getAccessibleSymbolChainFromSymbolTable(symbols: SymbolTable, ignoreQualification?: boolean, isLocalNameLookup?: boolean): Symbol[] | undefined { - if (!pushIfUnique(visitedSymbolTables!, symbols)) { - return undefined; - } - - const result = trySymbolTable(symbols, ignoreQualification, isLocalNameLookup); - visitedSymbolTables!.pop(); - return result; - } - - function canQualifySymbol(symbolFromSymbolTable: Symbol, meaning: SymbolFlags) { - // If the symbol is equivalent and doesn't need further qualification, this symbol is accessible - return !needsQualification(symbolFromSymbolTable, enclosingDeclaration, meaning) || - // If symbol needs qualification, make sure that parent is accessible, if it is then this symbol is accessible too - !!getAccessibleSymbolChain(symbolFromSymbolTable.parent, enclosingDeclaration, getQualifiedLeftMeaning(meaning), useOnlyExternalAliasing, visitedSymbolTablesMap); - } - - function isAccessible(symbolFromSymbolTable: Symbol, resolvedAliasSymbol?: Symbol, ignoreQualification?: boolean) { - return (symbol === (resolvedAliasSymbol || symbolFromSymbolTable) || getMergedSymbol(symbol) === getMergedSymbol(resolvedAliasSymbol || symbolFromSymbolTable)) && - // if the symbolFromSymbolTable is not external module (it could be if it was determined as ambient external module and would be in globals table) - // and if symbolFromSymbolTable or alias resolution matches the symbol, - // check the symbol can be qualified, it is only then this symbol is accessible - !some(symbolFromSymbolTable.declarations, hasNonGlobalAugmentationExternalModuleSymbol) && - (ignoreQualification || canQualifySymbol(getMergedSymbol(symbolFromSymbolTable), meaning)); - } - - function trySymbolTable(symbols: SymbolTable, ignoreQualification: boolean | undefined, isLocalNameLookup: boolean | undefined): Symbol[] | undefined { - // If symbol is directly available by its name in the symbol table - if (isAccessible(symbols.get(symbol!.escapedName)!, /*resolvedAliasSymbol*/ undefined, ignoreQualification)) { - return [symbol!]; - } - - // Check if symbol is any of the aliases in scope - const result = forEachEntry(symbols, symbolFromSymbolTable => { - if (symbolFromSymbolTable.flags & SymbolFlags.Alias - && symbolFromSymbolTable.escapedName !== InternalSymbolName.ExportEquals - && symbolFromSymbolTable.escapedName !== InternalSymbolName.Default - && !(isUMDExportSymbol(symbolFromSymbolTable) && enclosingDeclaration && isExternalModule(getSourceFileOfNode(enclosingDeclaration))) - // If `!useOnlyExternalAliasing`, we can use any type of alias to get the name - && (!useOnlyExternalAliasing || some(symbolFromSymbolTable.declarations, isExternalModuleImportEqualsDeclaration)) - // If we're looking up a local name to reference directly, omit namespace reexports, otherwise when we're trawling through an export list to make a dotted name, we can keep it - && (isLocalNameLookup ? !some(symbolFromSymbolTable.declarations, isNamespaceReexportDeclaration) : true) - // While exports are generally considered to be in scope, export-specifier declared symbols are _not_ - // See similar comment in `resolveName` for details - && (ignoreQualification || !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) - ) { + function canQualifySymbol(symbolFromSymbolTable: Symbol, meaning: SymbolFlags) { + // If the symbol is equivalent and doesn't need further qualification, this symbol is accessible + return !needsQualification(symbolFromSymbolTable, enclosingDeclaration, meaning) || + // If symbol needs qualification, make sure that parent is accessible, if it is then this symbol is accessible too + !!getAccessibleSymbolChain(symbolFromSymbolTable.parent, enclosingDeclaration, getQualifiedLeftMeaning(meaning), useOnlyExternalAliasing, visitedSymbolTablesMap); + } + + function isAccessible(symbolFromSymbolTable: Symbol, resolvedAliasSymbol?: Symbol, ignoreQualification?: boolean) { + return (symbol === (resolvedAliasSymbol || symbolFromSymbolTable) || getMergedSymbol(symbol) === getMergedSymbol(resolvedAliasSymbol || symbolFromSymbolTable)) && + // if the symbolFromSymbolTable is not external module (it could be if it was determined as ambient external module and would be in globals table) + // and if symbolFromSymbolTable or alias resolution matches the symbol, + // check the symbol can be qualified, it is only then this symbol is accessible + !some(symbolFromSymbolTable.declarations, hasNonGlobalAugmentationExternalModuleSymbol) && + (ignoreQualification || canQualifySymbol(getMergedSymbol(symbolFromSymbolTable), meaning)); + } + + function trySymbolTable(symbols: SymbolTable, ignoreQualification: boolean | undefined, isLocalNameLookup: boolean | undefined): Symbol[] | undefined { + // If symbol is directly available by its name in the symbol table + if (isAccessible(symbols.get(symbol!.escapedName)!, /*resolvedAliasSymbol*/ undefined, ignoreQualification)) { + return [symbol!]; + } + + // Check if symbol is any of the aliases in scope + const result = forEachEntry(symbols, symbolFromSymbolTable => { + if (symbolFromSymbolTable.flags & SymbolFlags.Alias + && symbolFromSymbolTable.escapedName !== InternalSymbolName.ExportEquals + && symbolFromSymbolTable.escapedName !== InternalSymbolName.Default + && !(isUMDExportSymbol(symbolFromSymbolTable) && enclosingDeclaration && isExternalModule(getSourceFileOfNode(enclosingDeclaration))) + // If `!useOnlyExternalAliasing`, we can use any type of alias to get the name + && (!useOnlyExternalAliasing || some(symbolFromSymbolTable.declarations, isExternalModuleImportEqualsDeclaration)) + // If we're looking up a local name to reference directly, omit namespace reexports, otherwise when we're trawling through an export list to make a dotted name, we can keep it + && (isLocalNameLookup ? !some(symbolFromSymbolTable.declarations, isNamespaceReexportDeclaration) : true) + // While exports are generally considered to be in scope, export-specifier declared symbols are _not_ + // See similar comment in `resolveName` for details + && (ignoreQualification || !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) + ) { - const resolvedImportedSymbol = resolveAlias(symbolFromSymbolTable); - const candidate = getCandidateListForSymbol(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification); - if (candidate) { - return candidate; - } + const resolvedImportedSymbol = resolveAlias(symbolFromSymbolTable); + const candidate = getCandidateListForSymbol(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification); + if (candidate) { + return candidate; } - if (symbolFromSymbolTable.escapedName === symbol!.escapedName && symbolFromSymbolTable.exportSymbol) { - if (isAccessible(getMergedSymbol(symbolFromSymbolTable.exportSymbol), /*aliasSymbol*/ undefined, ignoreQualification)) { - return [symbol!]; - } + } + if (symbolFromSymbolTable.escapedName === symbol!.escapedName && symbolFromSymbolTable.exportSymbol) { + if (isAccessible(getMergedSymbol(symbolFromSymbolTable.exportSymbol), /*aliasSymbol*/ undefined, ignoreQualification)) { + return [symbol!]; } - }); - - // If there's no result and we're looking at the global symbol table, treat `globalThis` like an alias and try to lookup thru that - return result || (symbols === globals ? getCandidateListForSymbol(globalThisSymbol, globalThisSymbol, ignoreQualification) : undefined); - } - - function getCandidateListForSymbol(symbolFromSymbolTable: Symbol, resolvedImportedSymbol: Symbol, ignoreQualification: boolean | undefined) { - if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) { - return [symbolFromSymbolTable]; } + }); - // Look in the exported members, if we can find accessibleSymbolChain, symbol is accessible using this chain - // but only if the symbolFromSymbolTable can be qualified - const candidateTable = getExportsOfSymbol(resolvedImportedSymbol); - const accessibleSymbolsFromExports = candidateTable && getAccessibleSymbolChainFromSymbolTable(candidateTable, /*ignoreQualification*/ true); - if (accessibleSymbolsFromExports && canQualifySymbol(symbolFromSymbolTable, getQualifiedLeftMeaning(meaning))) { - return [symbolFromSymbolTable].concat(accessibleSymbolsFromExports); - } - } + // If there's no result and we're looking at the global symbol table, treat `globalThis` like an alias and try to lookup thru that + return result || (symbols === globals ? getCandidateListForSymbol(globalThisSymbol, globalThisSymbol, ignoreQualification) : undefined); } - function needsQualification(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags) { - let qualify = false; - forEachSymbolTableInScope(enclosingDeclaration, symbolTable => { - // If symbol of this name is not available in the symbol table we are ok - let symbolFromSymbolTable = getMergedSymbol(symbolTable.get(symbol.escapedName)); - if (!symbolFromSymbolTable) { - // Continue to the next symbol table - return false; - } - // If the symbol with this name is present it should refer to the symbol - if (symbolFromSymbolTable === symbol) { - // No need to qualify - return true; - } + function getCandidateListForSymbol(symbolFromSymbolTable: Symbol, resolvedImportedSymbol: Symbol, ignoreQualification: boolean | undefined) { + if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) { + return [symbolFromSymbolTable]; + } - // Qualify if the symbol from symbol table has same meaning as expected - symbolFromSymbolTable = (symbolFromSymbolTable.flags & SymbolFlags.Alias && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) ? resolveAlias(symbolFromSymbolTable) : symbolFromSymbolTable; - if (symbolFromSymbolTable.flags & meaning) { - qualify = true; - return true; - } + // Look in the exported members, if we can find accessibleSymbolChain, symbol is accessible using this chain + // but only if the symbolFromSymbolTable can be qualified + const candidateTable = getExportsOfSymbol(resolvedImportedSymbol); + const accessibleSymbolsFromExports = candidateTable && getAccessibleSymbolChainFromSymbolTable(candidateTable, /*ignoreQualification*/ true); + if (accessibleSymbolsFromExports && canQualifySymbol(symbolFromSymbolTable, getQualifiedLeftMeaning(meaning))) { + return [symbolFromSymbolTable].concat(accessibleSymbolsFromExports); + } + } + } + function needsQualification(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags) { + let qualify = false; + forEachSymbolTableInScope(enclosingDeclaration, symbolTable => { + // If symbol of this name is not available in the symbol table we are ok + let symbolFromSymbolTable = getMergedSymbol(symbolTable.get(symbol.escapedName)); + if (!symbolFromSymbolTable) { // Continue to the next symbol table return false; - }); - - return qualify; - } + } + // If the symbol with this name is present it should refer to the symbol + if (symbolFromSymbolTable === symbol) { + // No need to qualify + return true; + } - function isPropertyOrMethodDeclarationSymbol(symbol: Symbol) { - if (symbol.declarations && symbol.declarations.length) { - for (const declaration of symbol.declarations) { - switch (declaration.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - continue; - default: - return false; - } - } + // Qualify if the symbol from symbol table has same meaning as expected + symbolFromSymbolTable = (symbolFromSymbolTable.flags & SymbolFlags.Alias && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) ? resolveAlias(symbolFromSymbolTable) : symbolFromSymbolTable; + if (symbolFromSymbolTable.flags & meaning) { + qualify = true; return true; } + + // Continue to the next symbol table return false; - } + }); - function isTypeSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { - const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ true); - return access.accessibility === SymbolAccessibility.Accessible; - } + return qualify; + } - function isValueSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { - const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, SymbolFlags.Value, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ true); - return access.accessibility === SymbolAccessibility.Accessible; - } - - function isSymbolAccessibleByFlags(typeSymbol: Symbol, enclosingDeclaration: Node | undefined, flags: SymbolFlags): boolean { - const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, flags, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ false); - return access.accessibility === SymbolAccessibility.Accessible; + function isPropertyOrMethodDeclarationSymbol(symbol: Symbol) { + if (symbol.declarations && symbol.declarations.length) { + for (const declaration of symbol.declarations) { + switch (declaration.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + continue; + default: + return false; + } + } + return true; } + return false; + } - function isAnySymbolAccessible(symbols: Symbol[] | undefined, enclosingDeclaration: Node | undefined, initialSymbol: Symbol, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean, allowModules: boolean): SymbolAccessibilityResult | undefined { - if (!length(symbols)) return; + function isTypeSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { + const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ true); + return access.accessibility === SymbolAccessibility.Accessible; + } - let hadAccessibleChain: Symbol | undefined; - let earlyModuleBail = false; - for (const symbol of symbols!) { - // Symbol is accessible if it by itself is accessible - const accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/ false); - if (accessibleSymbolChain) { - hadAccessibleChain = symbol; - const hasAccessibleDeclarations = hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible); - if (hasAccessibleDeclarations) { - return hasAccessibleDeclarations; - } - } - if (allowModules) { - if (some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { - if (shouldComputeAliasesToMakeVisible) { - earlyModuleBail = true; - // Generally speaking, we want to use the aliases that already exist to refer to a module, if present - // In order to do so, we need to find those aliases in order to retain them in declaration emit; so - // if we are in declaration emit, we cannot use the fast path for module visibility until we've exhausted - // all other visibility options (in order to capture the possible aliases used to reference the module) - continue; - } - // Any meaning of a module symbol is always accessible via an `import` type - return { - accessibility: SymbolAccessibility.Accessible - }; - } - } + function isValueSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { + const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, SymbolFlags.Value, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ true); + return access.accessibility === SymbolAccessibility.Accessible; + } - // If we haven't got the accessible symbol, it doesn't mean the symbol is actually inaccessible. - // It could be a qualified symbol and hence verify the path - // e.g.: - // module m { - // export class c { - // } - // } - // const x: typeof m.c - // In the above example when we start with checking if typeof m.c symbol is accessible, - // we are going to see if c can be accessed in scope directly. - // But it can't, hence the accessible is going to be undefined, but that doesn't mean m.c is inaccessible - // It is accessible if the parent m is accessible because then m.c can be accessed through qualification + function isSymbolAccessibleByFlags(typeSymbol: Symbol, enclosingDeclaration: Node | undefined, flags: SymbolFlags): boolean { + const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, flags, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ false); + return access.accessibility === SymbolAccessibility.Accessible; + } - const containers = getContainersOfSymbol(symbol, enclosingDeclaration, meaning); - const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible, allowModules); - if (parentResult) { - return parentResult; + function isAnySymbolAccessible(symbols: Symbol[] | undefined, enclosingDeclaration: Node | undefined, initialSymbol: Symbol, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean, allowModules: boolean): SymbolAccessibilityResult | undefined { + if (!length(symbols)) return; + + let hadAccessibleChain: Symbol | undefined; + let earlyModuleBail = false; + for (const symbol of symbols!) { + // Symbol is accessible if it by itself is accessible + const accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/ false); + if (accessibleSymbolChain) { + hadAccessibleChain = symbol; + const hasAccessibleDeclarations = hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible); + if (hasAccessibleDeclarations) { + return hasAccessibleDeclarations; + } + } + if (allowModules) { + if (some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { + if (shouldComputeAliasesToMakeVisible) { + earlyModuleBail = true; + // Generally speaking, we want to use the aliases that already exist to refer to a module, if present + // In order to do so, we need to find those aliases in order to retain them in declaration emit; so + // if we are in declaration emit, we cannot use the fast path for module visibility until we've exhausted + // all other visibility options (in order to capture the possible aliases used to reference the module) + continue; + } + // Any meaning of a module symbol is always accessible via an `import` type + return { + accessibility: SymbolAccessibility.Accessible + }; } } - if (earlyModuleBail) { - return { - accessibility: SymbolAccessibility.Accessible - }; - } + // If we haven't got the accessible symbol, it doesn't mean the symbol is actually inaccessible. + // It could be a qualified symbol and hence verify the path + // e.g.: + // module m { + // export class c { + // } + // } + // const x: typeof m.c + // In the above example when we start with checking if typeof m.c symbol is accessible, + // we are going to see if c can be accessed in scope directly. + // But it can't, hence the accessible is going to be undefined, but that doesn't mean m.c is inaccessible + // It is accessible if the parent m is accessible because then m.c can be accessed through qualification - if (hadAccessibleChain) { - return { - accessibility: SymbolAccessibility.NotAccessible, - errorSymbolName: symbolToString(initialSymbol, enclosingDeclaration, meaning), - errorModuleName: hadAccessibleChain !== initialSymbol ? symbolToString(hadAccessibleChain, enclosingDeclaration, SymbolFlags.Namespace) : undefined, - }; + const containers = getContainersOfSymbol(symbol, enclosingDeclaration, meaning); + const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible, allowModules); + if (parentResult) { + return parentResult; } } - /** - * Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested - * - * @param symbol a Symbol to check if accessible - * @param enclosingDeclaration a Node containing reference to the symbol - * @param meaning a SymbolFlags to check if such meaning of the symbol is accessible - * @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible - */ - function isSymbolAccessible(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult { - return isSymbolAccessibleWorker(symbol, enclosingDeclaration, meaning, shouldComputeAliasesToMakeVisible, /*allowModules*/ true); + if (earlyModuleBail) { + return { + accessibility: SymbolAccessibility.Accessible + }; } - function isSymbolAccessibleWorker(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean, allowModules: boolean): SymbolAccessibilityResult { - if (symbol && enclosingDeclaration) { - const result = isAnySymbolAccessible([symbol], enclosingDeclaration, symbol, meaning, shouldComputeAliasesToMakeVisible, allowModules); - if (result) { - return result; - } + if (hadAccessibleChain) { + return { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: symbolToString(initialSymbol, enclosingDeclaration, meaning), + errorModuleName: hadAccessibleChain !== initialSymbol ? symbolToString(hadAccessibleChain, enclosingDeclaration, SymbolFlags.Namespace) : undefined, + }; + } + } - // This could be a symbol that is not exported in the external module - // or it could be a symbol from different external module that is not aliased and hence cannot be named - const symbolExternalModule = forEach(symbol.declarations, getExternalModuleContainer); - if (symbolExternalModule) { - const enclosingExternalModule = getExternalModuleContainer(enclosingDeclaration); - if (symbolExternalModule !== enclosingExternalModule) { - // name from different external module that is not visible - return { - accessibility: SymbolAccessibility.CannotBeNamed, - errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), - errorModuleName: symbolToString(symbolExternalModule), - errorNode: isInJSFile(enclosingDeclaration) ? enclosingDeclaration : undefined, - }; - } - } + /** + * Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested + * + * @param symbol a Symbol to check if accessible + * @param enclosingDeclaration a Node containing reference to the symbol + * @param meaning a SymbolFlags to check if such meaning of the symbol is accessible + * @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible + */ + function isSymbolAccessible(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult { + return isSymbolAccessibleWorker(symbol, enclosingDeclaration, meaning, shouldComputeAliasesToMakeVisible, /*allowModules*/ true); + } - // Just a local name that is not accessible - return { - accessibility: SymbolAccessibility.NotAccessible, - errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), - }; + function isSymbolAccessibleWorker(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean, allowModules: boolean): SymbolAccessibilityResult { + if (symbol && enclosingDeclaration) { + const result = isAnySymbolAccessible([symbol], enclosingDeclaration, symbol, meaning, shouldComputeAliasesToMakeVisible, allowModules); + if (result) { + return result; } - return { accessibility: SymbolAccessibility.Accessible }; - } - - function getExternalModuleContainer(declaration: Node) { - const node = findAncestor(declaration, hasExternalModuleSymbol); - return node && getSymbolOfNode(node); - } + // This could be a symbol that is not exported in the external module + // or it could be a symbol from different external module that is not aliased and hence cannot be named + const symbolExternalModule = forEach(symbol.declarations, getExternalModuleContainer); + if (symbolExternalModule) { + const enclosingExternalModule = getExternalModuleContainer(enclosingDeclaration); + if (symbolExternalModule !== enclosingExternalModule) { + // name from different external module that is not visible + return { + accessibility: SymbolAccessibility.CannotBeNamed, + errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), + errorModuleName: symbolToString(symbolExternalModule), + errorNode: isInJSFile(enclosingDeclaration) ? enclosingDeclaration : undefined, + }; + } + } - function hasExternalModuleSymbol(declaration: Node) { - return isAmbientModule(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration as SourceFile)); + // Just a local name that is not accessible + return { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), + }; } - function hasNonGlobalAugmentationExternalModuleSymbol(declaration: Node) { - return isModuleWithStringLiteralName(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration as SourceFile)); - } + return { accessibility: SymbolAccessibility.Accessible }; + } - function hasVisibleDeclarations(symbol: Symbol, shouldComputeAliasToMakeVisible: boolean): SymbolVisibilityResult | undefined { - let aliasesToMakeVisible: LateVisibilityPaintedStatement[] | undefined; - if (!every(filter(symbol.declarations, d => d.kind !== SyntaxKind.Identifier), getIsDeclarationVisible)) { - return undefined; - } - return { accessibility: SymbolAccessibility.Accessible, aliasesToMakeVisible }; + function getExternalModuleContainer(declaration: Node) { + const node = findAncestor(declaration, hasExternalModuleSymbol); + return node && getSymbolOfNode(node); + } - function getIsDeclarationVisible(declaration: Declaration) { - if (!isDeclarationVisible(declaration)) { - // Mark the unexported alias as visible if its parent is visible - // because these kind of aliases can be used to name types in declaration file + function hasExternalModuleSymbol(declaration: Node) { + return isAmbientModule(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration as SourceFile)); + } - const anyImportSyntax = getAnyImportSyntax(declaration); - if (anyImportSyntax && - !hasSyntacticModifier(anyImportSyntax, ModifierFlags.Export) && // import clause without export - isDeclarationVisible(anyImportSyntax.parent)) { - return addVisibleAlias(declaration, anyImportSyntax); - } - else if (isVariableDeclaration(declaration) && isVariableStatement(declaration.parent.parent) && - !hasSyntacticModifier(declaration.parent.parent, ModifierFlags.Export) && // unexported variable statement - isDeclarationVisible(declaration.parent.parent.parent)) { - return addVisibleAlias(declaration, declaration.parent.parent); - } - else if (isLateVisibilityPaintedStatement(declaration) // unexported top-level statement - && !hasSyntacticModifier(declaration, ModifierFlags.Export) - && isDeclarationVisible(declaration.parent)) { - return addVisibleAlias(declaration, declaration); - } - else if (symbol.flags & SymbolFlags.Alias && isBindingElement(declaration) && isInJSFile(declaration) && declaration.parent?.parent // exported import-like top-level JS require statement - && isVariableDeclaration(declaration.parent.parent) - && declaration.parent.parent.parent?.parent && isVariableStatement(declaration.parent.parent.parent.parent) - && !hasSyntacticModifier(declaration.parent.parent.parent.parent, ModifierFlags.Export) - && declaration.parent.parent.parent.parent.parent // check if the thing containing the variable statement is visible (ie, the file) - && isDeclarationVisible(declaration.parent.parent.parent.parent.parent)) { - return addVisibleAlias(declaration, declaration.parent.parent.parent.parent); - } + function hasNonGlobalAugmentationExternalModuleSymbol(declaration: Node) { + return isModuleWithStringLiteralName(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration as SourceFile)); + } - // Declaration is not visible - return false; - } + function hasVisibleDeclarations(symbol: Symbol, shouldComputeAliasToMakeVisible: boolean): SymbolVisibilityResult | undefined { + let aliasesToMakeVisible: LateVisibilityPaintedStatement[] | undefined; + if (!every(filter(symbol.declarations, d => d.kind !== SyntaxKind.Identifier), getIsDeclarationVisible)) { + return undefined; + } + return { accessibility: SymbolAccessibility.Accessible, aliasesToMakeVisible }; - return true; - } + function getIsDeclarationVisible(declaration: Declaration) { + if (!isDeclarationVisible(declaration)) { + // Mark the unexported alias as visible if its parent is visible + // because these kind of aliases can be used to name types in declaration file - function addVisibleAlias(declaration: Declaration, aliasingStatement: LateVisibilityPaintedStatement) { - // In function "buildTypeDisplay" where we decide whether to write type-alias or serialize types, - // we want to just check if type- alias is accessible or not but we don't care about emitting those alias at that time - // since we will do the emitting later in trackSymbol. - if (shouldComputeAliasToMakeVisible) { - getNodeLinks(declaration).isVisible = true; - aliasesToMakeVisible = appendIfUnique(aliasesToMakeVisible, aliasingStatement); + const anyImportSyntax = getAnyImportSyntax(declaration); + if (anyImportSyntax && + !hasSyntacticModifier(anyImportSyntax, ModifierFlags.Export) && // import clause without export + isDeclarationVisible(anyImportSyntax.parent)) { + return addVisibleAlias(declaration, anyImportSyntax); + } + else if (isVariableDeclaration(declaration) && isVariableStatement(declaration.parent.parent) && + !hasSyntacticModifier(declaration.parent.parent, ModifierFlags.Export) && // unexported variable statement + isDeclarationVisible(declaration.parent.parent.parent)) { + return addVisibleAlias(declaration, declaration.parent.parent); + } + else if (isLateVisibilityPaintedStatement(declaration) // unexported top-level statement + && !hasSyntacticModifier(declaration, ModifierFlags.Export) + && isDeclarationVisible(declaration.parent)) { + return addVisibleAlias(declaration, declaration); + } + else if (symbol.flags & SymbolFlags.Alias && isBindingElement(declaration) && isInJSFile(declaration) && declaration.parent?.parent // exported import-like top-level JS require statement + && isVariableDeclaration(declaration.parent.parent) + && declaration.parent.parent.parent?.parent && isVariableStatement(declaration.parent.parent.parent.parent) + && !hasSyntacticModifier(declaration.parent.parent.parent.parent, ModifierFlags.Export) + && declaration.parent.parent.parent.parent.parent // check if the thing containing the variable statement is visible (ie, the file) + && isDeclarationVisible(declaration.parent.parent.parent.parent.parent)) { + return addVisibleAlias(declaration, declaration.parent.parent.parent.parent); } - return true; - } - } - - function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult { - // get symbol of the first identifier of the entityName - let meaning: SymbolFlags; - if (entityName.parent.kind === SyntaxKind.TypeQuery || - isExpressionWithTypeArgumentsInClassExtendsClause(entityName.parent) || - entityName.parent.kind === SyntaxKind.ComputedPropertyName) { - // Typeof value - meaning = SymbolFlags.Value | SymbolFlags.ExportValue; - } - else if (entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression || - entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration) { - // Left identifier from type reference or TypeAlias - // Entity name of the import declaration - meaning = SymbolFlags.Namespace; - } - else { - // Type Reference or TypeAlias entity = Identifier - meaning = SymbolFlags.Type; - } - const firstIdentifier = getFirstIdentifier(entityName); - const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); - if (symbol && symbol.flags & SymbolFlags.TypeParameter && meaning & SymbolFlags.Type) { - return { accessibility: SymbolAccessibility.Accessible }; + // Declaration is not visible + return false; } - // Verify if the symbol is accessible - return (symbol && hasVisibleDeclarations(symbol, /*shouldComputeAliasToMakeVisible*/ true)) || { - accessibility: SymbolAccessibility.NotAccessible, - errorSymbolName: getTextOfNode(firstIdentifier), - errorNode: firstIdentifier - }; + return true; } - function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.AllowAnyNodeKind, writer?: EmitTextWriter): string { - let nodeFlags = NodeBuilderFlags.IgnoreErrors; - if (flags & SymbolFormatFlags.UseOnlyExternalAliasing) { - nodeFlags |= NodeBuilderFlags.UseOnlyExternalAliasing; + function addVisibleAlias(declaration: Declaration, aliasingStatement: LateVisibilityPaintedStatement) { + // In function "buildTypeDisplay" where we decide whether to write type-alias or serialize types, + // we want to just check if type- alias is accessible or not but we don't care about emitting those alias at that time + // since we will do the emitting later in trackSymbol. + if (shouldComputeAliasToMakeVisible) { + getNodeLinks(declaration).isVisible = true; + aliasesToMakeVisible = appendIfUnique(aliasesToMakeVisible, aliasingStatement); } - if (flags & SymbolFormatFlags.WriteTypeParametersOrArguments) { - nodeFlags |= NodeBuilderFlags.WriteTypeParametersInQualifiedName; - } - if (flags & SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope) { - nodeFlags |= NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; - } - if (flags & SymbolFormatFlags.DoNotIncludeSymbolChain) { - nodeFlags |= NodeBuilderFlags.DoNotIncludeSymbolChain; - } - const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToExpression : nodeBuilder.symbolToEntityName; - return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker); + return true; + } + } - function symbolToStringWorker(writer: EmitTextWriter) { - const entity = builder(symbol, meaning!, enclosingDeclaration, nodeFlags)!; // TODO: GH#18217 - // add neverAsciiEscape for GH#39027 - const printer = enclosingDeclaration?.kind === SyntaxKind.SourceFile ? createPrinter({ removeComments: true, neverAsciiEscape: true }) : createPrinter({ removeComments: true }); - const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, entity, /*sourceFile*/ sourceFile, writer); - return writer; - } + function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult { + // get symbol of the first identifier of the entityName + let meaning: SymbolFlags; + if (entityName.parent.kind === SyntaxKind.TypeQuery || + isExpressionWithTypeArgumentsInClassExtendsClause(entityName.parent) || + entityName.parent.kind === SyntaxKind.ComputedPropertyName) { + // Typeof value + meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + } + else if (entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression || + entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration) { + // Left identifier from type reference or TypeAlias + // Entity name of the import declaration + meaning = SymbolFlags.Namespace; + } + else { + // Type Reference or TypeAlias entity = Identifier + meaning = SymbolFlags.Type; + } + + const firstIdentifier = getFirstIdentifier(entityName); + const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + if (symbol && symbol.flags & SymbolFlags.TypeParameter && meaning & SymbolFlags.Type) { + return { accessibility: SymbolAccessibility.Accessible }; } - function signatureToString(signature: Signature, enclosingDeclaration?: Node, flags = TypeFormatFlags.None, kind?: SignatureKind, writer?: EmitTextWriter): string { - return writer ? signatureToStringWorker(writer).getText() : usingSingleLineStringWriter(signatureToStringWorker); + // Verify if the symbol is accessible + return (symbol && hasVisibleDeclarations(symbol, /*shouldComputeAliasToMakeVisible*/ true)) || { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: getTextOfNode(firstIdentifier), + errorNode: firstIdentifier + }; + } - function signatureToStringWorker(writer: EmitTextWriter) { - let sigOutput: SyntaxKind; - if (flags & TypeFormatFlags.WriteArrowStyleSignature) { - sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructorType : SyntaxKind.FunctionType; - } - else { - sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructSignature : SyntaxKind.CallSignature; - } - const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); - const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); - const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217 - return writer; - } + function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.AllowAnyNodeKind, writer?: EmitTextWriter): string { + let nodeFlags = NodeBuilderFlags.IgnoreErrors; + if (flags & SymbolFormatFlags.UseOnlyExternalAliasing) { + nodeFlags |= NodeBuilderFlags.UseOnlyExternalAliasing; + } + if (flags & SymbolFormatFlags.WriteTypeParametersOrArguments) { + nodeFlags |= NodeBuilderFlags.WriteTypeParametersInQualifiedName; + } + if (flags & SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope) { + nodeFlags |= NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; } + if (flags & SymbolFormatFlags.DoNotIncludeSymbolChain) { + nodeFlags |= NodeBuilderFlags.DoNotIncludeSymbolChain; + } + const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToExpression : nodeBuilder.symbolToEntityName; + return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker); - function typeToString(type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer: EmitTextWriter = createTextWriter("")): string { - const noTruncation = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation; - const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0), writer); - if (typeNode === undefined) return Debug.fail("should always get typenode"); - // The unresolved type gets a synthesized comment on `any` to hint to users that it's not a plain `any`. - // Otherwise, we always strip comments out. - const options = { removeComments: type !== unresolvedType }; - const printer = createPrinter(options); + function symbolToStringWorker(writer: EmitTextWriter) { + const entity = builder(symbol, meaning!, enclosingDeclaration, nodeFlags)!; // TODO: GH#18217 + // add neverAsciiEscape for GH#39027 + const printer = enclosingDeclaration?.kind === SyntaxKind.SourceFile ? createPrinter({ removeComments: true, neverAsciiEscape: true }) : createPrinter({ removeComments: true }); const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer); - const result = writer.getText(); - - const maxLength = noTruncation ? noTruncationMaximumTruncationLength * 2 : defaultMaximumTruncationLength * 2; - if (maxLength && result && result.length >= maxLength) { - return result.substr(0, maxLength - "...".length) + "..."; - } - return result; + printer.writeNode(EmitHint.Unspecified, entity, /*sourceFile*/ sourceFile, writer); + return writer; } + } + + function signatureToString(signature: Signature, enclosingDeclaration?: Node, flags = TypeFormatFlags.None, kind?: SignatureKind, writer?: EmitTextWriter): string { + return writer ? signatureToStringWorker(writer).getText() : usingSingleLineStringWriter(signatureToStringWorker); - function getTypeNamesForErrorDisplay(left: Type, right: Type): [string, string] { - let leftStr = symbolValueDeclarationIsContextSensitive(left.symbol) ? typeToString(left, left.symbol.valueDeclaration) : typeToString(left); - let rightStr = symbolValueDeclarationIsContextSensitive(right.symbol) ? typeToString(right, right.symbol.valueDeclaration) : typeToString(right); - if (leftStr === rightStr) { - leftStr = getTypeNameForErrorDisplay(left); - rightStr = getTypeNameForErrorDisplay(right); + function signatureToStringWorker(writer: EmitTextWriter) { + let sigOutput: SyntaxKind; + if (flags & TypeFormatFlags.WriteArrowStyleSignature) { + sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructorType : SyntaxKind.FunctionType; + } + else { + sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructSignature : SyntaxKind.CallSignature; } - return [leftStr, rightStr]; + const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); + const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217 + return writer; } + } - function getTypeNameForErrorDisplay(type: Type) { - return typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType); - } + function typeToString(type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer: EmitTextWriter = createTextWriter("")): string { + const noTruncation = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation; + const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0), writer); + if (typeNode === undefined) return Debug.fail("should always get typenode"); + // The unresolved type gets a synthesized comment on `any` to hint to users that it's not a plain `any`. + // Otherwise, we always strip comments out. + const options = { removeComments: type !== unresolvedType }; + const printer = createPrinter(options); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer); + const result = writer.getText(); + + const maxLength = noTruncation ? noTruncationMaximumTruncationLength * 2 : defaultMaximumTruncationLength * 2; + if (maxLength && result && result.length >= maxLength) { + return result.substr(0, maxLength - "...".length) + "..."; + } + return result; + } - function symbolValueDeclarationIsContextSensitive(symbol: Symbol): boolean { - return symbol && !!symbol.valueDeclaration && isExpression(symbol.valueDeclaration) && !isContextSensitive(symbol.valueDeclaration); + function getTypeNamesForErrorDisplay(left: Type, right: Type): [string, string] { + let leftStr = symbolValueDeclarationIsContextSensitive(left.symbol) ? typeToString(left, left.symbol.valueDeclaration) : typeToString(left); + let rightStr = symbolValueDeclarationIsContextSensitive(right.symbol) ? typeToString(right, right.symbol.valueDeclaration) : typeToString(right); + if (leftStr === rightStr) { + leftStr = getTypeNameForErrorDisplay(left); + rightStr = getTypeNameForErrorDisplay(right); } + return [leftStr, rightStr]; + } - function toNodeBuilderFlags(flags = TypeFormatFlags.None): NodeBuilderFlags { - return flags & TypeFormatFlags.NodeBuilderFlagsMask; - } + function getTypeNameForErrorDisplay(type: Type) { + return typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType); + } + + function symbolValueDeclarationIsContextSensitive(symbol: Symbol): boolean { + return symbol && !!symbol.valueDeclaration && isExpression(symbol.valueDeclaration) && !isContextSensitive(symbol.valueDeclaration); + } + + function toNodeBuilderFlags(flags = TypeFormatFlags.None): NodeBuilderFlags { + return flags & TypeFormatFlags.NodeBuilderFlagsMask; + } + + function isClassInstanceSide(type: Type) { + return !!type.symbol && !!(type.symbol.flags & SymbolFlags.Class) && (type === getDeclaredTypeOfClassOrInterface(type.symbol) || (!!(type.flags & TypeFlags.Object) && !!(getObjectFlags(type) & ObjectFlags.IsClassInstanceClone))); + } + + function createNodeBuilder() { + return { + typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)), + indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => indexInfoToIndexSignatureDeclarationHelper(indexInfo, context, /*typeNode*/ undefined)), + signatureToSignatureDeclaration: (signature: Signature, kind: SignatureDeclaration["kind"], enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => signatureToSignatureDeclarationHelper(signature, kind, context)), + symbolToEntityName: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => symbolToName(symbol, context, meaning, /*expectsIdentifier*/ false)), + symbolToExpression: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => symbolToExpression(symbol, context, meaning)), + symbolToTypeParameterDeclarations: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => typeParametersToTypeParameterDeclarations(symbol, context)), + symbolToParameterDeclaration: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => symbolToParameterDeclaration(symbol, context)), + typeParameterToDeclaration: (parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => typeParameterToDeclaration(parameter, context)), + symbolTableToDeclarationStatements: (symbolTable: SymbolTable, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker, bundled?: boolean) => + withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context, bundled)), + }; - function isClassInstanceSide(type: Type) { - return !!type.symbol && !!(type.symbol.flags & SymbolFlags.Class) && (type === getDeclaredTypeOfClassOrInterface(type.symbol) || (!!(type.flags & TypeFlags.Object) && !!(getObjectFlags(type) & ObjectFlags.IsClassInstanceClone))); + function withContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, cb: (context: NodeBuilderContext) => T): T | undefined { + Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); + const context: NodeBuilderContext = { + enclosingDeclaration, + flags: flags || NodeBuilderFlags.None, + // If no full tracker is provided, fake up a dummy one with a basic limited-functionality moduleResolverHost + tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: () => false, moduleResolverHost: flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? { + getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", + getCurrentDirectory: () => host.getCurrentDirectory(), + getSymlinkCache: maybeBind(host, host.getSymlinkCache), + useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), + redirectTargetsMap: host.redirectTargetsMap, + getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName), + isSourceOfProjectReferenceRedirect: fileName => host.isSourceOfProjectReferenceRedirect(fileName), + fileExists: fileName => host.fileExists(fileName), + getFileIncludeReasons: () => host.getFileIncludeReasons(), + readFile: host.readFile ? (fileName => host.readFile!(fileName)) : undefined, + } : undefined }, + encounteredError: false, + reportedDiagnostic: false, + visitedTypes: undefined, + symbolDepth: undefined, + inferTypeParameters: undefined, + approximateLength: 0 + }; + context.tracker = wrapSymbolTrackerToReportForContext(context, context.tracker); + const resultingNode = cb(context); + if (context.truncating && context.flags & NodeBuilderFlags.NoTruncation) { + context.tracker?.reportTruncationError?.(); + } + return context.encounteredError ? undefined : resultingNode; } - function createNodeBuilder() { + function wrapSymbolTrackerToReportForContext(context: NodeBuilderContext, tracker: SymbolTracker): SymbolTracker { + const oldTrackSymbol = tracker.trackSymbol; return { - typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)), - indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => indexInfoToIndexSignatureDeclarationHelper(indexInfo, context, /*typeNode*/ undefined)), - signatureToSignatureDeclaration: (signature: Signature, kind: SignatureDeclaration["kind"], enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => signatureToSignatureDeclarationHelper(signature, kind, context)), - symbolToEntityName: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => symbolToName(symbol, context, meaning, /*expectsIdentifier*/ false)), - symbolToExpression: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => symbolToExpression(symbol, context, meaning)), - symbolToTypeParameterDeclarations: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => typeParametersToTypeParameterDeclarations(symbol, context)), - symbolToParameterDeclaration: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => symbolToParameterDeclaration(symbol, context)), - typeParameterToDeclaration: (parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => typeParameterToDeclaration(parameter, context)), - symbolTableToDeclarationStatements: (symbolTable: SymbolTable, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker, bundled?: boolean) => - withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context, bundled)), + ...tracker, + reportCyclicStructureError: wrapReportedDiagnostic(tracker.reportCyclicStructureError), + reportInaccessibleThisError: wrapReportedDiagnostic(tracker.reportInaccessibleThisError), + reportInaccessibleUniqueSymbolError: wrapReportedDiagnostic(tracker.reportInaccessibleUniqueSymbolError), + reportLikelyUnsafeImportRequiredError: wrapReportedDiagnostic(tracker.reportLikelyUnsafeImportRequiredError), + reportNonlocalAugmentation: wrapReportedDiagnostic(tracker.reportNonlocalAugmentation), + reportPrivateInBaseOfClassExpression: wrapReportedDiagnostic(tracker.reportPrivateInBaseOfClassExpression), + reportNonSerializableProperty: wrapReportedDiagnostic(tracker.reportNonSerializableProperty), + trackSymbol: oldTrackSymbol && ((...args) => { + const result = oldTrackSymbol(...args); + if (result) { + context.reportedDiagnostic = true; + } + return result; + }), }; - function withContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, cb: (context: NodeBuilderContext) => T): T | undefined { - Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); - const context: NodeBuilderContext = { - enclosingDeclaration, - flags: flags || NodeBuilderFlags.None, - // If no full tracker is provided, fake up a dummy one with a basic limited-functionality moduleResolverHost - tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: () => false, moduleResolverHost: flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? { - getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", - getCurrentDirectory: () => host.getCurrentDirectory(), - getSymlinkCache: maybeBind(host, host.getSymlinkCache), - useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), - redirectTargetsMap: host.redirectTargetsMap, - getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName), - isSourceOfProjectReferenceRedirect: fileName => host.isSourceOfProjectReferenceRedirect(fileName), - fileExists: fileName => host.fileExists(fileName), - getFileIncludeReasons: () => host.getFileIncludeReasons(), - readFile: host.readFile ? (fileName => host.readFile!(fileName)) : undefined, - } : undefined }, - encounteredError: false, - reportedDiagnostic: false, - visitedTypes: undefined, - symbolDepth: undefined, - inferTypeParameters: undefined, - approximateLength: 0 - }; - context.tracker = wrapSymbolTrackerToReportForContext(context, context.tracker); - const resultingNode = cb(context); - if (context.truncating && context.flags & NodeBuilderFlags.NoTruncation) { - context.tracker?.reportTruncationError?.(); + function wrapReportedDiagnostic any>(method: T | undefined): T | undefined { + if (!method) { + return method; } - return context.encounteredError ? undefined : resultingNode; + return (((...args) => { + context.reportedDiagnostic = true; + return method(...args); + }) as T); } + } - function wrapSymbolTrackerToReportForContext(context: NodeBuilderContext, tracker: SymbolTracker): SymbolTracker { - const oldTrackSymbol = tracker.trackSymbol; - return { - ...tracker, - reportCyclicStructureError: wrapReportedDiagnostic(tracker.reportCyclicStructureError), - reportInaccessibleThisError: wrapReportedDiagnostic(tracker.reportInaccessibleThisError), - reportInaccessibleUniqueSymbolError: wrapReportedDiagnostic(tracker.reportInaccessibleUniqueSymbolError), - reportLikelyUnsafeImportRequiredError: wrapReportedDiagnostic(tracker.reportLikelyUnsafeImportRequiredError), - reportNonlocalAugmentation: wrapReportedDiagnostic(tracker.reportNonlocalAugmentation), - reportPrivateInBaseOfClassExpression: wrapReportedDiagnostic(tracker.reportPrivateInBaseOfClassExpression), - reportNonSerializableProperty: wrapReportedDiagnostic(tracker.reportNonSerializableProperty), - trackSymbol: oldTrackSymbol && ((...args) => { - const result = oldTrackSymbol(...args); - if (result) { - context.reportedDiagnostic = true; - } - return result; - }), - }; + function checkTruncationLength(context: NodeBuilderContext): boolean { + if (context.truncating) return context.truncating; + return context.truncating = context.approximateLength > ((context.flags & NodeBuilderFlags.NoTruncation) ? noTruncationMaximumTruncationLength : defaultMaximumTruncationLength); + } - function wrapReportedDiagnostic any>(method: T | undefined): T | undefined { - if (!method) { - return method; - } - return (((...args) => { - context.reportedDiagnostic = true; - return method(...args); - }) as T); + function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode { + if (cancellationToken && cancellationToken.throwIfCancellationRequested) { + cancellationToken.throwIfCancellationRequested(); + } + const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias; + context.flags &= ~NodeBuilderFlags.InTypeAlias; + + if (!type) { + if (!(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { + context.encounteredError = true; + return undefined!; // TODO: GH#18217 } + context.approximateLength += 3; + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } - function checkTruncationLength(context: NodeBuilderContext): boolean { - if (context.truncating) return context.truncating; - return context.truncating = context.approximateLength > ((context.flags & NodeBuilderFlags.NoTruncation) ? noTruncationMaximumTruncationLength : defaultMaximumTruncationLength); + if (!(context.flags & NodeBuilderFlags.NoTypeReduction)) { + type = getReducedType(type); } - function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode { - if (cancellationToken && cancellationToken.throwIfCancellationRequested) { - cancellationToken.throwIfCancellationRequested(); + if (type.flags & TypeFlags.Any) { + if (type.aliasSymbol) { + return factory.createTypeReferenceNode(symbolToEntityNameNode(type.aliasSymbol), mapToTypeNodes(type.aliasTypeArguments, context)); } - const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias; - context.flags &= ~NodeBuilderFlags.InTypeAlias; - - if (!type) { - if (!(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { - context.encounteredError = true; - return undefined!; // TODO: GH#18217 - } - context.approximateLength += 3; - return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + if (type === unresolvedType) { + return addSyntheticLeadingComment(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), SyntaxKind.MultiLineCommentTrivia, "unresolved"); } - - if (!(context.flags & NodeBuilderFlags.NoTypeReduction)) { - type = getReducedType(type); + context.approximateLength += 3; + return factory.createKeywordTypeNode(type === intrinsicMarkerType ? SyntaxKind.IntrinsicKeyword : SyntaxKind.AnyKeyword); + } + if (type.flags & TypeFlags.Unknown) { + return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword); + } + if (type.flags & TypeFlags.String) { + context.approximateLength += 6; + return factory.createKeywordTypeNode(SyntaxKind.StringKeyword); + } + if (type.flags & TypeFlags.Number) { + context.approximateLength += 6; + return factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + } + if (type.flags & TypeFlags.BigInt) { + context.approximateLength += 6; + return factory.createKeywordTypeNode(SyntaxKind.BigIntKeyword); + } + if (type.flags & TypeFlags.Boolean && !type.aliasSymbol) { + context.approximateLength += 7; + return factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword); + } + if (type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union)) { + const parentSymbol = getParentOfSymbol(type.symbol)!; + const parentName = symbolToTypeNode(parentSymbol, context, SymbolFlags.Type); + if (getDeclaredTypeOfSymbol(parentSymbol) === type) { + return parentName; + } + const memberName = symbolName(type.symbol); + if (isIdentifierText(memberName, ScriptTarget.ES3)) { + return appendReferenceToType( + parentName as TypeReferenceNode | ImportTypeNode, + factory.createTypeReferenceNode(memberName, /*typeArguments*/ undefined) + ); } - - if (type.flags & TypeFlags.Any) { - if (type.aliasSymbol) { - return factory.createTypeReferenceNode(symbolToEntityNameNode(type.aliasSymbol), mapToTypeNodes(type.aliasTypeArguments, context)); - } - if (type === unresolvedType) { - return addSyntheticLeadingComment(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), SyntaxKind.MultiLineCommentTrivia, "unresolved"); - } - context.approximateLength += 3; - return factory.createKeywordTypeNode(type === intrinsicMarkerType ? SyntaxKind.IntrinsicKeyword : SyntaxKind.AnyKeyword); + if (isImportTypeNode(parentName)) { + (parentName as any).isTypeOf = true; // mutably update, node is freshly manufactured anyhow + return factory.createIndexedAccessTypeNode(parentName, factory.createLiteralTypeNode(factory.createStringLiteral(memberName))); } - if (type.flags & TypeFlags.Unknown) { - return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword); + else if (isTypeReferenceNode(parentName)) { + return factory.createIndexedAccessTypeNode(factory.createTypeQueryNode(parentName.typeName), factory.createLiteralTypeNode(factory.createStringLiteral(memberName))); } - if (type.flags & TypeFlags.String) { - context.approximateLength += 6; - return factory.createKeywordTypeNode(SyntaxKind.StringKeyword); - } - if (type.flags & TypeFlags.Number) { - context.approximateLength += 6; - return factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); - } - if (type.flags & TypeFlags.BigInt) { - context.approximateLength += 6; - return factory.createKeywordTypeNode(SyntaxKind.BigIntKeyword); - } - if (type.flags & TypeFlags.Boolean && !type.aliasSymbol) { - context.approximateLength += 7; - return factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword); - } - if (type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union)) { - const parentSymbol = getParentOfSymbol(type.symbol)!; - const parentName = symbolToTypeNode(parentSymbol, context, SymbolFlags.Type); - if (getDeclaredTypeOfSymbol(parentSymbol) === type) { - return parentName; - } - const memberName = symbolName(type.symbol); - if (isIdentifierText(memberName, ScriptTarget.ES3)) { - return appendReferenceToType( - parentName as TypeReferenceNode | ImportTypeNode, - factory.createTypeReferenceNode(memberName, /*typeArguments*/ undefined) - ); + else { + return Debug.fail("Unhandled type node kind returned from `symbolToTypeNode`."); + } + } + if (type.flags & TypeFlags.EnumLike) { + return symbolToTypeNode(type.symbol, context, SymbolFlags.Type); + } + if (type.flags & TypeFlags.StringLiteral) { + context.approximateLength += ((type as StringLiteralType).value.length + 2); + return factory.createLiteralTypeNode(setEmitFlags(factory.createStringLiteral((type as StringLiteralType).value, !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType)), EmitFlags.NoAsciiEscaping)); + } + if (type.flags & TypeFlags.NumberLiteral) { + const value = (type as NumberLiteralType).value; + context.approximateLength += ("" + value).length; + return factory.createLiteralTypeNode(value < 0 ? factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-value)) : factory.createNumericLiteral(value)); + } + if (type.flags & TypeFlags.BigIntLiteral) { + context.approximateLength += (pseudoBigIntToString((type as BigIntLiteralType).value).length) + 1; + return factory.createLiteralTypeNode((factory.createBigIntLiteral((type as BigIntLiteralType).value))); + } + if (type.flags & TypeFlags.BooleanLiteral) { + context.approximateLength += (type as IntrinsicType).intrinsicName.length; + return factory.createLiteralTypeNode((type as IntrinsicType).intrinsicName === "true" ? factory.createTrue() : factory.createFalse()); + } + if (type.flags & TypeFlags.UniqueESSymbol) { + if (!(context.flags & NodeBuilderFlags.AllowUniqueESSymbolType)) { + if (isValueSymbolAccessible(type.symbol, context.enclosingDeclaration)) { + context.approximateLength += 6; + return symbolToTypeNode(type.symbol, context, SymbolFlags.Value); } - if (isImportTypeNode(parentName)) { - (parentName as any).isTypeOf = true; // mutably update, node is freshly manufactured anyhow - return factory.createIndexedAccessTypeNode(parentName, factory.createLiteralTypeNode(factory.createStringLiteral(memberName))); + if (context.tracker.reportInaccessibleUniqueSymbolError) { + context.tracker.reportInaccessibleUniqueSymbolError(); } - else if (isTypeReferenceNode(parentName)) { - return factory.createIndexedAccessTypeNode(factory.createTypeQueryNode(parentName.typeName), factory.createLiteralTypeNode(factory.createStringLiteral(memberName))); + } + context.approximateLength += 13; + return factory.createTypeOperatorNode(SyntaxKind.UniqueKeyword, factory.createKeywordTypeNode(SyntaxKind.SymbolKeyword)); + } + if (type.flags & TypeFlags.Void) { + context.approximateLength += 4; + return factory.createKeywordTypeNode(SyntaxKind.VoidKeyword); + } + if (type.flags & TypeFlags.Undefined) { + context.approximateLength += 9; + return factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword); + } + if (type.flags & TypeFlags.Null) { + context.approximateLength += 4; + return factory.createLiteralTypeNode(factory.createNull()); + } + if (type.flags & TypeFlags.Never) { + context.approximateLength += 5; + return factory.createKeywordTypeNode(SyntaxKind.NeverKeyword); + } + if (type.flags & TypeFlags.ESSymbol) { + context.approximateLength += 6; + return factory.createKeywordTypeNode(SyntaxKind.SymbolKeyword); + } + if (type.flags & TypeFlags.NonPrimitive) { + context.approximateLength += 6; + return factory.createKeywordTypeNode(SyntaxKind.ObjectKeyword); + } + if (isThisTypeParameter(type)) { + if (context.flags & NodeBuilderFlags.InObjectTypeLiteral) { + if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowThisInObjectLiteral)) { + context.encounteredError = true; } - else { - return Debug.fail("Unhandled type node kind returned from `symbolToTypeNode`."); + if (context.tracker.reportInaccessibleThisError) { + context.tracker.reportInaccessibleThisError(); } } - if (type.flags & TypeFlags.EnumLike) { - return symbolToTypeNode(type.symbol, context, SymbolFlags.Type); + context.approximateLength += 4; + return factory.createThisTypeNode(); + } + + if (!inTypeAlias && type.aliasSymbol && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration))) { + const typeArgumentNodes = mapToTypeNodes(type.aliasTypeArguments, context); + if (isReservedMemberName(type.aliasSymbol.escapedName) && !(type.aliasSymbol.flags & SymbolFlags.Class)) return factory.createTypeReferenceNode(factory.createIdentifier(""), typeArgumentNodes); + return symbolToTypeNode(type.aliasSymbol, context, SymbolFlags.Type, typeArgumentNodes); + } + + const objectFlags = getObjectFlags(type); + + if (objectFlags & ObjectFlags.Reference) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + return (type as TypeReference).node ? visitAndTransformType(type, typeReferenceToTypeNode) : typeReferenceToTypeNode(type as TypeReference); + } + if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) { + if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) { + context.approximateLength += (symbolName(type.symbol).length + 6); + return factory.createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, /*constraintNode*/ undefined)); } - if (type.flags & TypeFlags.StringLiteral) { - context.approximateLength += ((type as StringLiteralType).value.length + 2); - return factory.createLiteralTypeNode(setEmitFlags(factory.createStringLiteral((type as StringLiteralType).value, !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType)), EmitFlags.NoAsciiEscaping)); + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && + type.flags & TypeFlags.TypeParameter && + !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration)) { + const name = typeParameterToName(type, context); + context.approximateLength += idText(name).length; + return factory.createTypeReferenceNode(factory.createIdentifier(idText(name)), /*typeArguments*/ undefined); } - if (type.flags & TypeFlags.NumberLiteral) { - const value = (type as NumberLiteralType).value; - context.approximateLength += ("" + value).length; - return factory.createLiteralTypeNode(value < 0 ? factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-value)) : factory.createNumericLiteral(value)); + // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. + return type.symbol + ? symbolToTypeNode(type.symbol, context, SymbolFlags.Type) + : factory.createTypeReferenceNode(factory.createIdentifier("?"), /*typeArguments*/ undefined); + } + if (type.flags & TypeFlags.Union && (type as UnionType).origin) { + type = (type as UnionType).origin!; + } + if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) { + const types = type.flags & TypeFlags.Union ? formatUnionTypes((type as UnionType).types) : (type as IntersectionType).types; + if (length(types) === 1) { + return typeToTypeNodeHelper(types[0], context); } - if (type.flags & TypeFlags.BigIntLiteral) { - context.approximateLength += (pseudoBigIntToString((type as BigIntLiteralType).value).length) + 1; - return factory.createLiteralTypeNode((factory.createBigIntLiteral((type as BigIntLiteralType).value))); + const typeNodes = mapToTypeNodes(types, context, /*isBareList*/ true); + if (typeNodes && typeNodes.length > 0) { + return type.flags & TypeFlags.Union ? factory.createUnionTypeNode(typeNodes) : factory.createIntersectionTypeNode(typeNodes); } - if (type.flags & TypeFlags.BooleanLiteral) { - context.approximateLength += (type as IntrinsicType).intrinsicName.length; - return factory.createLiteralTypeNode((type as IntrinsicType).intrinsicName === "true" ? factory.createTrue() : factory.createFalse()); + else { + if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { + context.encounteredError = true; + } + return undefined!; // TODO: GH#18217 } - if (type.flags & TypeFlags.UniqueESSymbol) { - if (!(context.flags & NodeBuilderFlags.AllowUniqueESSymbolType)) { - if (isValueSymbolAccessible(type.symbol, context.enclosingDeclaration)) { - context.approximateLength += 6; - return symbolToTypeNode(type.symbol, context, SymbolFlags.Value); - } - if (context.tracker.reportInaccessibleUniqueSymbolError) { - context.tracker.reportInaccessibleUniqueSymbolError(); + } + if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // The type is an object literal type. + return createAnonymousTypeNode(type as ObjectType); + } + if (type.flags & TypeFlags.Index) { + const indexedType = (type as IndexType).type; + context.approximateLength += 6; + const indexTypeNode = typeToTypeNodeHelper(indexedType, context); + return factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, indexTypeNode); + } + if (type.flags & TypeFlags.TemplateLiteral) { + const texts = (type as TemplateLiteralType).texts; + const types = (type as TemplateLiteralType).types; + const templateHead = factory.createTemplateHead(texts[0]); + const templateSpans = factory.createNodeArray( + map(types, (t, i) => factory.createTemplateLiteralTypeSpan( + typeToTypeNodeHelper(t, context), + (i < types.length - 1 ? factory.createTemplateMiddle : factory.createTemplateTail)(texts[i + 1])))); + context.approximateLength += 2; + return factory.createTemplateLiteralType(templateHead, templateSpans); + } + if (type.flags & TypeFlags.StringMapping) { + const typeNode = typeToTypeNodeHelper((type as StringMappingType).type, context); + return symbolToTypeNode((type as StringMappingType).symbol, context, SymbolFlags.Type, [typeNode]); + } + if (type.flags & TypeFlags.IndexedAccess) { + const objectTypeNode = typeToTypeNodeHelper((type as IndexedAccessType).objectType, context); + const indexTypeNode = typeToTypeNodeHelper((type as IndexedAccessType).indexType, context); + context.approximateLength += 2; + return factory.createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); + } + if (type.flags & TypeFlags.Conditional) { + return visitAndTransformType(type, type => conditionalTypeToTypeNode(type as ConditionalType)); + } + if (type.flags & TypeFlags.Substitution) { + return typeToTypeNodeHelper((type as SubstitutionType).baseType, context); + } + + return Debug.fail("Should be unreachable."); + + + function conditionalTypeToTypeNode(type: ConditionalType) { + const checkTypeNode = typeToTypeNodeHelper(type.checkType, context); + const saveInferTypeParameters = context.inferTypeParameters; + context.inferTypeParameters = type.root.inferTypeParameters; + const extendsTypeNode = typeToTypeNodeHelper(type.extendsType, context); + context.inferTypeParameters = saveInferTypeParameters; + const trueTypeNode = typeToTypeNodeOrCircularityElision(getTrueTypeFromConditionalType(type)); + const falseTypeNode = typeToTypeNodeOrCircularityElision(getFalseTypeFromConditionalType(type)); + context.approximateLength += 15; + return factory.createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode); + } + + function typeToTypeNodeOrCircularityElision(type: Type) { + if (type.flags & TypeFlags.Union) { + if (context.visitedTypes?.has(getTypeId(type))) { + if (!(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { + context.encounteredError = true; + context.tracker?.reportCyclicStructureError?.(); } + return createElidedInformationPlaceholder(context); } - context.approximateLength += 13; - return factory.createTypeOperatorNode(SyntaxKind.UniqueKeyword, factory.createKeywordTypeNode(SyntaxKind.SymbolKeyword)); - } - if (type.flags & TypeFlags.Void) { - context.approximateLength += 4; - return factory.createKeywordTypeNode(SyntaxKind.VoidKeyword); - } - if (type.flags & TypeFlags.Undefined) { - context.approximateLength += 9; - return factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword); - } - if (type.flags & TypeFlags.Null) { - context.approximateLength += 4; - return factory.createLiteralTypeNode(factory.createNull()); - } - if (type.flags & TypeFlags.Never) { - context.approximateLength += 5; - return factory.createKeywordTypeNode(SyntaxKind.NeverKeyword); + return visitAndTransformType(type, type => typeToTypeNodeHelper(type, context)); } - if (type.flags & TypeFlags.ESSymbol) { - context.approximateLength += 6; - return factory.createKeywordTypeNode(SyntaxKind.SymbolKeyword); + return typeToTypeNodeHelper(type, context); + } + + function createMappedTypeNodeFromType(type: MappedType) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + const readonlyToken = type.declaration.readonlyToken ? factory.createToken(type.declaration.readonlyToken.kind) as ReadonlyKeyword | PlusToken | MinusToken : undefined; + const questionToken = type.declaration.questionToken ? factory.createToken(type.declaration.questionToken.kind) as QuestionToken | PlusToken | MinusToken : undefined; + let appropriateConstraintTypeNode: TypeNode; + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // We have a { [P in keyof T]: X } + // We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType` + appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)); } - if (type.flags & TypeFlags.NonPrimitive) { - context.approximateLength += 6; - return factory.createKeywordTypeNode(SyntaxKind.ObjectKeyword); + else { + appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context); } - if (isThisTypeParameter(type)) { - if (context.flags & NodeBuilderFlags.InObjectTypeLiteral) { - if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowThisInObjectLiteral)) { - context.encounteredError = true; + const typeParameterNode = typeParameterToDeclarationWithConstraint(getTypeParameterFromMappedType(type), context, appropriateConstraintTypeNode); + const nameTypeNode = type.declaration.nameType ? typeToTypeNodeHelper(getNameTypeFromMappedType(type)!, context) : undefined; + const templateTypeNode = typeToTypeNodeHelper(removeMissingType(getTemplateTypeFromMappedType(type), !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), context); + const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode, /*members*/ undefined); + context.approximateLength += 10; + return setEmitFlags(mappedTypeNode, EmitFlags.SingleLine); + } + + function createAnonymousTypeNode(type: ObjectType): TypeNode { + const typeId = type.id; + const symbol = type.symbol; + if (symbol) { + const isInstanceType = isClassInstanceSide(type) ? SymbolFlags.Type : SymbolFlags.Value; + if (isJSConstructor(symbol.valueDeclaration)) { + // Instance and static types share the same symbol; only add 'typeof' for the static side. + return symbolToTypeNode(symbol, context, isInstanceType); + } + // Always use 'typeof T' for type of class, enum, and module objects + else if (symbol.flags & SymbolFlags.Class + && !getBaseTypeVariableOfClass(symbol) + && !(symbol.valueDeclaration && symbol.valueDeclaration.kind === SyntaxKind.ClassExpression && context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) || + symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || + shouldWriteTypeOfFunctionSymbol()) { + return symbolToTypeNode(symbol, context, isInstanceType); + } + else if (context.visitedTypes?.has(typeId)) { + // If type is an anonymous type literal in a type alias declaration, use type alias name + const typeAlias = getTypeAliasForTypeLiteral(type); + if (typeAlias) { + // The specified symbol flags need to be reinterpreted as type flags + return symbolToTypeNode(typeAlias, context, SymbolFlags.Type); } - if (context.tracker.reportInaccessibleThisError) { - context.tracker.reportInaccessibleThisError(); + else { + return createElidedInformationPlaceholder(context); } } - context.approximateLength += 4; - return factory.createThisTypeNode(); + else { + return visitAndTransformType(type, createTypeNodeFromObjectType); + } } - - if (!inTypeAlias && type.aliasSymbol && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration))) { - const typeArgumentNodes = mapToTypeNodes(type.aliasTypeArguments, context); - if (isReservedMemberName(type.aliasSymbol.escapedName) && !(type.aliasSymbol.flags & SymbolFlags.Class)) return factory.createTypeReferenceNode(factory.createIdentifier(""), typeArgumentNodes); - return symbolToTypeNode(type.aliasSymbol, context, SymbolFlags.Type, typeArgumentNodes); + else { + // Anonymous types without a symbol are never circular. + return createTypeNodeFromObjectType(type); + } + function shouldWriteTypeOfFunctionSymbol() { + const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method) && // typeof static method + some(symbol.declarations, declaration => isStatic(declaration)); + const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && + (symbol.parent || // is exported function symbol + forEach(symbol.declarations, declaration => + declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); + if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { + // typeof is allowed only for static/non local functions + return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || (context.visitedTypes?.has(typeId))) && // it is type of the symbol uses itself recursively + (!(context.flags & NodeBuilderFlags.UseStructuralFallback) || isValueSymbolAccessible(symbol, context.enclosingDeclaration)); // And the build is going to succeed without visibility error or there is no structural fallback allowed + } + } + } + + function visitAndTransformType(type: Type, transform: (type: Type) => T) { + const typeId = type.id; + const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; + const id = getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).node ? "N" + getNodeId((type as TypeReference).node!) : + type.flags & TypeFlags.Conditional ? "N" + getNodeId((type as ConditionalType).root.node) : + type.symbol ? (isConstructorObject ? "+" : "") + getSymbolId(type.symbol) : + undefined; + // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead + // of types allows us to catch circular references to instantiations of the same anonymous type + if (!context.visitedTypes) { + context.visitedTypes = new Set(); + } + if (id && !context.symbolDepth) { + context.symbolDepth = new Map(); } - const objectFlags = getObjectFlags(type); - - if (objectFlags & ObjectFlags.Reference) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - return (type as TypeReference).node ? visitAndTransformType(type, typeReferenceToTypeNode) : typeReferenceToTypeNode(type as TypeReference); + const links = context.enclosingDeclaration && getNodeLinks(context.enclosingDeclaration); + const key = `${getTypeId(type)}|${context.flags}`; + if (links) { + links.serializedTypes ||= new Map(); } - if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) { - if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) { - context.approximateLength += (symbolName(type.symbol).length + 6); - return factory.createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, /*constraintNode*/ undefined)); - } - if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && - type.flags & TypeFlags.TypeParameter && - !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration)) { - const name = typeParameterToName(type, context); - context.approximateLength += idText(name).length; - return factory.createTypeReferenceNode(factory.createIdentifier(idText(name)), /*typeArguments*/ undefined); + const cachedResult = links?.serializedTypes?.get(key); + if (cachedResult) { + if (cachedResult.truncating) { + context.truncating = true; } - // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. - return type.symbol - ? symbolToTypeNode(type.symbol, context, SymbolFlags.Type) - : factory.createTypeReferenceNode(factory.createIdentifier("?"), /*typeArguments*/ undefined); + context.approximateLength += cachedResult.addedLength; + return deepCloneOrReuseNode(cachedResult) as TypeNode as T; } - if (type.flags & TypeFlags.Union && (type as UnionType).origin) { - type = (type as UnionType).origin!; + + let depth: number | undefined; + if (id) { + depth = context.symbolDepth!.get(id) || 0; + if (depth > 10) { + return createElidedInformationPlaceholder(context); + } + context.symbolDepth!.set(id, depth + 1); } - if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) { - const types = type.flags & TypeFlags.Union ? formatUnionTypes((type as UnionType).types) : (type as IntersectionType).types; - if (length(types) === 1) { - return typeToTypeNodeHelper(types[0], context); + context.visitedTypes.add(typeId); + const startLength = context.approximateLength; + const result = transform(type); + const addedLength = context.approximateLength - startLength; + if (!context.reportedDiagnostic && !context.encounteredError) { + if (context.truncating) { + (result as any).truncating = true; } - const typeNodes = mapToTypeNodes(types, context, /*isBareList*/ true); - if (typeNodes && typeNodes.length > 0) { - return type.flags & TypeFlags.Union ? factory.createUnionTypeNode(typeNodes) : factory.createIntersectionTypeNode(typeNodes); - } - else { - if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { - context.encounteredError = true; - } - return undefined!; // TODO: GH#18217 - } - } - if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // The type is an object literal type. - return createAnonymousTypeNode(type as ObjectType); - } - if (type.flags & TypeFlags.Index) { - const indexedType = (type as IndexType).type; - context.approximateLength += 6; - const indexTypeNode = typeToTypeNodeHelper(indexedType, context); - return factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, indexTypeNode); - } - if (type.flags & TypeFlags.TemplateLiteral) { - const texts = (type as TemplateLiteralType).texts; - const types = (type as TemplateLiteralType).types; - const templateHead = factory.createTemplateHead(texts[0]); - const templateSpans = factory.createNodeArray( - map(types, (t, i) => factory.createTemplateLiteralTypeSpan( - typeToTypeNodeHelper(t, context), - (i < types.length - 1 ? factory.createTemplateMiddle : factory.createTemplateTail)(texts[i + 1])))); - context.approximateLength += 2; - return factory.createTemplateLiteralType(templateHead, templateSpans); - } - if (type.flags & TypeFlags.StringMapping) { - const typeNode = typeToTypeNodeHelper((type as StringMappingType).type, context); - return symbolToTypeNode((type as StringMappingType).symbol, context, SymbolFlags.Type, [typeNode]); - } - if (type.flags & TypeFlags.IndexedAccess) { - const objectTypeNode = typeToTypeNodeHelper((type as IndexedAccessType).objectType, context); - const indexTypeNode = typeToTypeNodeHelper((type as IndexedAccessType).indexType, context); - context.approximateLength += 2; - return factory.createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); - } - if (type.flags & TypeFlags.Conditional) { - return visitAndTransformType(type, type => conditionalTypeToTypeNode(type as ConditionalType)); - } - if (type.flags & TypeFlags.Substitution) { - return typeToTypeNodeHelper((type as SubstitutionType).baseType, context); - } - - return Debug.fail("Should be unreachable."); - - - function conditionalTypeToTypeNode(type: ConditionalType) { - const checkTypeNode = typeToTypeNodeHelper(type.checkType, context); - const saveInferTypeParameters = context.inferTypeParameters; - context.inferTypeParameters = type.root.inferTypeParameters; - const extendsTypeNode = typeToTypeNodeHelper(type.extendsType, context); - context.inferTypeParameters = saveInferTypeParameters; - const trueTypeNode = typeToTypeNodeOrCircularityElision(getTrueTypeFromConditionalType(type)); - const falseTypeNode = typeToTypeNodeOrCircularityElision(getFalseTypeFromConditionalType(type)); - context.approximateLength += 15; - return factory.createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode); - } - - function typeToTypeNodeOrCircularityElision(type: Type) { - if (type.flags & TypeFlags.Union) { - if (context.visitedTypes?.has(getTypeId(type))) { - if (!(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { - context.encounteredError = true; - context.tracker?.reportCyclicStructureError?.(); - } - return createElidedInformationPlaceholder(context); - } - return visitAndTransformType(type, type => typeToTypeNodeHelper(type, context)); - } - return typeToTypeNodeHelper(type, context); + (result as any).addedLength = addedLength; + links?.serializedTypes?.set(key, result as TypeNode as TypeNode & {truncating?: boolean, addedLength: number}); } + context.visitedTypes.delete(typeId); + if (id) { + context.symbolDepth!.set(id, depth!); + } + return result; - function createMappedTypeNodeFromType(type: MappedType) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - const readonlyToken = type.declaration.readonlyToken ? factory.createToken(type.declaration.readonlyToken.kind) as ReadonlyKeyword | PlusToken | MinusToken : undefined; - const questionToken = type.declaration.questionToken ? factory.createToken(type.declaration.questionToken.kind) as QuestionToken | PlusToken | MinusToken : undefined; - let appropriateConstraintTypeNode: TypeNode; - if (isMappedTypeWithKeyofConstraintDeclaration(type)) { - // We have a { [P in keyof T]: X } - // We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType` - appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)); - } - else { - appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context); - } - const typeParameterNode = typeParameterToDeclarationWithConstraint(getTypeParameterFromMappedType(type), context, appropriateConstraintTypeNode); - const nameTypeNode = type.declaration.nameType ? typeToTypeNodeHelper(getNameTypeFromMappedType(type)!, context) : undefined; - const templateTypeNode = typeToTypeNodeHelper(removeMissingType(getTemplateTypeFromMappedType(type), !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), context); - const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode, /*members*/ undefined); - context.approximateLength += 10; - return setEmitFlags(mappedTypeNode, EmitFlags.SingleLine); - } - - function createAnonymousTypeNode(type: ObjectType): TypeNode { - const typeId = type.id; - const symbol = type.symbol; - if (symbol) { - const isInstanceType = isClassInstanceSide(type) ? SymbolFlags.Type : SymbolFlags.Value; - if (isJSConstructor(symbol.valueDeclaration)) { - // Instance and static types share the same symbol; only add 'typeof' for the static side. - return symbolToTypeNode(symbol, context, isInstanceType); - } - // Always use 'typeof T' for type of class, enum, and module objects - else if (symbol.flags & SymbolFlags.Class - && !getBaseTypeVariableOfClass(symbol) - && !(symbol.valueDeclaration && symbol.valueDeclaration.kind === SyntaxKind.ClassExpression && context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) || - symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || - shouldWriteTypeOfFunctionSymbol()) { - return symbolToTypeNode(symbol, context, isInstanceType); - } - else if (context.visitedTypes?.has(typeId)) { - // If type is an anonymous type literal in a type alias declaration, use type alias name - const typeAlias = getTypeAliasForTypeLiteral(type); - if (typeAlias) { - // The specified symbol flags need to be reinterpreted as type flags - return symbolToTypeNode(typeAlias, context, SymbolFlags.Type); + function deepCloneOrReuseNode(node: Node): Node { + if (!nodeIsSynthesized(node) && getParseTreeNode(node) === node) { + return node; + } + return setTextRange(factory.cloneNode(visitEachChild(node, deepCloneOrReuseNode, nullTransformationContext)), node); + } + } + + function createTypeNodeFromObjectType(type: ObjectType): TypeNode { + if (isGenericMappedType(type) || (type as MappedType).containsError) { + return createMappedTypeNodeFromType(type as MappedType); + } + + const resolved = resolveStructuredTypeMembers(type); + if (!resolved.properties.length && !resolved.indexInfos.length) { + if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { + context.approximateLength += 2; + return setEmitFlags(factory.createTypeLiteralNode(/*members*/ undefined), EmitFlags.SingleLine); + } + + if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { + const signature = resolved.callSignatures[0]; + const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType, context) as FunctionTypeNode; + return signatureNode; + + } + + if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { + const signature = resolved.constructSignatures[0]; + const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType, context) as ConstructorTypeNode; + return signatureNode; + } + } + + const abstractSignatures = filter(resolved.constructSignatures, signature => !!(signature.flags & SignatureFlags.Abstract)); + if (some(abstractSignatures)) { + const types = map(abstractSignatures, getOrCreateTypeFromSignature); + // count the number of type elements excluding abstract constructors + const typeElementCount = + resolved.callSignatures.length + + (resolved.constructSignatures.length - abstractSignatures.length) + + resolved.indexInfos.length + + // exclude `prototype` when writing a class expression as a type literal, as per + // the logic in `createTypeNodesFromResolvedType`. + (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral ? + countWhere(resolved.properties, p => !(p.flags & SymbolFlags.Prototype)) : + length(resolved.properties)); + // don't include an empty object literal if there were no other static-side + // properties to write, i.e. `abstract class C { }` becomes `abstract new () => {}` + // and not `(abstract new () => {}) & {}` + if (typeElementCount) { + // create a copy of the object type without any abstract construct signatures. + types.push(getResolvedTypeWithoutAbstractConstructSignatures(resolved)); + } + return typeToTypeNodeHelper(getIntersectionType(types), context); + } + + const savedFlags = context.flags; + context.flags |= NodeBuilderFlags.InObjectTypeLiteral; + const members = createTypeNodesFromResolvedType(resolved); + context.flags = savedFlags; + const typeLiteralNode = factory.createTypeLiteralNode(members); + context.approximateLength += 2; + setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine); + return typeLiteralNode; + } + + function typeReferenceToTypeNode(type: TypeReference) { + let typeArguments: readonly Type[] = getTypeArguments(type); + if (type.target === globalArrayType || type.target === globalReadonlyArrayType) { + if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) { + const typeArgumentNode = typeToTypeNodeHelper(typeArguments[0], context); + return factory.createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]); + } + const elementType = typeToTypeNodeHelper(typeArguments[0], context); + const arrayType = factory.createArrayTypeNode(elementType); + return type.target === globalArrayType ? arrayType : factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType); + } + else if (type.target.objectFlags & ObjectFlags.Tuple) { + typeArguments = sameMap(typeArguments, (t, i) => removeMissingType(t, !!((type.target as TupleType).elementFlags[i] & ElementFlags.Optional))); + if (typeArguments.length > 0) { + const arity = getTypeReferenceArity(type); + const tupleConstituentNodes = mapToTypeNodes(typeArguments.slice(0, arity), context); + if (tupleConstituentNodes) { + if ((type.target as TupleType).labeledElementDeclarations) { + for (let i = 0; i < tupleConstituentNodes.length; i++) { + const flags = (type.target as TupleType).elementFlags[i]; + tupleConstituentNodes[i] = factory.createNamedTupleMember( + flags & ElementFlags.Variable ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined, + factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel((type.target as TupleType).labeledElementDeclarations![i]))), + flags & ElementFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : + tupleConstituentNodes[i] + ); + } } else { - return createElidedInformationPlaceholder(context); - } - } - else { - return visitAndTransformType(type, createTypeNodeFromObjectType); - } - } - else { - // Anonymous types without a symbol are never circular. - return createTypeNodeFromObjectType(type); - } - function shouldWriteTypeOfFunctionSymbol() { - const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method) && // typeof static method - some(symbol.declarations, declaration => isStatic(declaration)); - const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && - (symbol.parent || // is exported function symbol - forEach(symbol.declarations, declaration => - declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); - if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { - // typeof is allowed only for static/non local functions - return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || (context.visitedTypes?.has(typeId))) && // it is type of the symbol uses itself recursively - (!(context.flags & NodeBuilderFlags.UseStructuralFallback) || isValueSymbolAccessible(symbol, context.enclosingDeclaration)); // And the build is going to succeed without visibility error or there is no structural fallback allowed - } - } - } - - function visitAndTransformType(type: Type, transform: (type: Type) => T) { - const typeId = type.id; - const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; - const id = getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).node ? "N" + getNodeId((type as TypeReference).node!) : - type.flags & TypeFlags.Conditional ? "N" + getNodeId((type as ConditionalType).root.node) : - type.symbol ? (isConstructorObject ? "+" : "") + getSymbolId(type.symbol) : - undefined; - // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead - // of types allows us to catch circular references to instantiations of the same anonymous type - if (!context.visitedTypes) { - context.visitedTypes = new Set(); - } - if (id && !context.symbolDepth) { - context.symbolDepth = new Map(); - } - - const links = context.enclosingDeclaration && getNodeLinks(context.enclosingDeclaration); - const key = `${getTypeId(type)}|${context.flags}`; - if (links) { - links.serializedTypes ||= new Map(); - } - const cachedResult = links?.serializedTypes?.get(key); - if (cachedResult) { - if (cachedResult.truncating) { - context.truncating = true; - } - context.approximateLength += cachedResult.addedLength; - return deepCloneOrReuseNode(cachedResult) as TypeNode as T; - } - - let depth: number | undefined; - if (id) { - depth = context.symbolDepth!.get(id) || 0; - if (depth > 10) { - return createElidedInformationPlaceholder(context); - } - context.symbolDepth!.set(id, depth + 1); - } - context.visitedTypes.add(typeId); - const startLength = context.approximateLength; - const result = transform(type); - const addedLength = context.approximateLength - startLength; - if (!context.reportedDiagnostic && !context.encounteredError) { - if (context.truncating) { - (result as any).truncating = true; - } - (result as any).addedLength = addedLength; - links?.serializedTypes?.set(key, result as TypeNode as TypeNode & {truncating?: boolean, addedLength: number}); - } - context.visitedTypes.delete(typeId); - if (id) { - context.symbolDepth!.set(id, depth!); - } - return result; - - function deepCloneOrReuseNode(node: Node): Node { - if (!nodeIsSynthesized(node) && getParseTreeNode(node) === node) { - return node; - } - return setTextRange(factory.cloneNode(visitEachChild(node, deepCloneOrReuseNode, nullTransformationContext)), node); - } - } - - function createTypeNodeFromObjectType(type: ObjectType): TypeNode { - if (isGenericMappedType(type) || (type as MappedType).containsError) { - return createMappedTypeNodeFromType(type as MappedType); - } - - const resolved = resolveStructuredTypeMembers(type); - if (!resolved.properties.length && !resolved.indexInfos.length) { - if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { - context.approximateLength += 2; - return setEmitFlags(factory.createTypeLiteralNode(/*members*/ undefined), EmitFlags.SingleLine); - } - - if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { - const signature = resolved.callSignatures[0]; - const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType, context) as FunctionTypeNode; - return signatureNode; - - } - - if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { - const signature = resolved.constructSignatures[0]; - const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType, context) as ConstructorTypeNode; - return signatureNode; - } - } - - const abstractSignatures = filter(resolved.constructSignatures, signature => !!(signature.flags & SignatureFlags.Abstract)); - if (some(abstractSignatures)) { - const types = map(abstractSignatures, getOrCreateTypeFromSignature); - // count the number of type elements excluding abstract constructors - const typeElementCount = - resolved.callSignatures.length + - (resolved.constructSignatures.length - abstractSignatures.length) + - resolved.indexInfos.length + - // exclude `prototype` when writing a class expression as a type literal, as per - // the logic in `createTypeNodesFromResolvedType`. - (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral ? - countWhere(resolved.properties, p => !(p.flags & SymbolFlags.Prototype)) : - length(resolved.properties)); - // don't include an empty object literal if there were no other static-side - // properties to write, i.e. `abstract class C { }` becomes `abstract new () => {}` - // and not `(abstract new () => {}) & {}` - if (typeElementCount) { - // create a copy of the object type without any abstract construct signatures. - types.push(getResolvedTypeWithoutAbstractConstructSignatures(resolved)); - } - return typeToTypeNodeHelper(getIntersectionType(types), context); - } - - const savedFlags = context.flags; - context.flags |= NodeBuilderFlags.InObjectTypeLiteral; - const members = createTypeNodesFromResolvedType(resolved); - context.flags = savedFlags; - const typeLiteralNode = factory.createTypeLiteralNode(members); - context.approximateLength += 2; - setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine); - return typeLiteralNode; - } - - function typeReferenceToTypeNode(type: TypeReference) { - let typeArguments: readonly Type[] = getTypeArguments(type); - if (type.target === globalArrayType || type.target === globalReadonlyArrayType) { - if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) { - const typeArgumentNode = typeToTypeNodeHelper(typeArguments[0], context); - return factory.createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]); - } - const elementType = typeToTypeNodeHelper(typeArguments[0], context); - const arrayType = factory.createArrayTypeNode(elementType); - return type.target === globalArrayType ? arrayType : factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType); - } - else if (type.target.objectFlags & ObjectFlags.Tuple) { - typeArguments = sameMap(typeArguments, (t, i) => removeMissingType(t, !!((type.target as TupleType).elementFlags[i] & ElementFlags.Optional))); - if (typeArguments.length > 0) { - const arity = getTypeReferenceArity(type); - const tupleConstituentNodes = mapToTypeNodes(typeArguments.slice(0, arity), context); - if (tupleConstituentNodes) { - if ((type.target as TupleType).labeledElementDeclarations) { - for (let i = 0; i < tupleConstituentNodes.length; i++) { - const flags = (type.target as TupleType).elementFlags[i]; - tupleConstituentNodes[i] = factory.createNamedTupleMember( - flags & ElementFlags.Variable ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined, - factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel((type.target as TupleType).labeledElementDeclarations![i]))), - flags & ElementFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, - flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : - tupleConstituentNodes[i] - ); - } - } - else { - for (let i = 0; i < Math.min(arity, tupleConstituentNodes.length); i++) { - const flags = (type.target as TupleType).elementFlags[i]; - tupleConstituentNodes[i] = - flags & ElementFlags.Variable ? factory.createRestTypeNode(flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : tupleConstituentNodes[i]) : - flags & ElementFlags.Optional ? factory.createOptionalTypeNode(tupleConstituentNodes[i]) : - tupleConstituentNodes[i]; - } + for (let i = 0; i < Math.min(arity, tupleConstituentNodes.length); i++) { + const flags = (type.target as TupleType).elementFlags[i]; + tupleConstituentNodes[i] = + flags & ElementFlags.Variable ? factory.createRestTypeNode(flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : tupleConstituentNodes[i]) : + flags & ElementFlags.Optional ? factory.createOptionalTypeNode(tupleConstituentNodes[i]) : + tupleConstituentNodes[i]; } - const tupleTypeNode = setEmitFlags(factory.createTupleTypeNode(tupleConstituentNodes), EmitFlags.SingleLine); - return (type.target as TupleType).readonly ? factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; } - } - if (context.encounteredError || (context.flags & NodeBuilderFlags.AllowEmptyTuple)) { - const tupleTypeNode = setEmitFlags(factory.createTupleTypeNode([]), EmitFlags.SingleLine); + const tupleTypeNode = setEmitFlags(factory.createTupleTypeNode(tupleConstituentNodes), EmitFlags.SingleLine); return (type.target as TupleType).readonly ? factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; } - context.encounteredError = true; - return undefined!; // TODO: GH#18217 } - else if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral && - type.symbol.valueDeclaration && - isClassLike(type.symbol.valueDeclaration) && - !isValueSymbolAccessible(type.symbol, context.enclosingDeclaration) - ) { - return createAnonymousTypeNode(type); + if (context.encounteredError || (context.flags & NodeBuilderFlags.AllowEmptyTuple)) { + const tupleTypeNode = setEmitFlags(factory.createTupleTypeNode([]), EmitFlags.SingleLine); + return (type.target as TupleType).readonly ? factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; } - else { - const outerTypeParameters = type.target.outerTypeParameters; - let i = 0; - let resultType: TypeReferenceNode | ImportTypeNode | undefined; - if (outerTypeParameters) { - const length = outerTypeParameters.length; - while (i < length) { - // Find group of type arguments for type parameters with the same declaring container. - const start = i; - const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i])!; - do { - i++; - } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); - // When type parameters are their own type arguments for the whole group (i.e. we have - // the default outer type arguments), we don't show the group. - if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { - const typeArgumentSlice = mapToTypeNodes(typeArguments.slice(start, i), context); - const flags = context.flags; - context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; - const ref = symbolToTypeNode(parent, context, SymbolFlags.Type, typeArgumentSlice) as TypeReferenceNode | ImportTypeNode; - context.flags = flags; - resultType = !resultType ? ref : appendReferenceToType(resultType, ref as TypeReferenceNode); - } - } + context.encounteredError = true; + return undefined!; // TODO: GH#18217 + } + else if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral && + type.symbol.valueDeclaration && + isClassLike(type.symbol.valueDeclaration) && + !isValueSymbolAccessible(type.symbol, context.enclosingDeclaration) + ) { + return createAnonymousTypeNode(type); + } + else { + const outerTypeParameters = type.target.outerTypeParameters; + let i = 0; + let resultType: TypeReferenceNode | ImportTypeNode | undefined; + if (outerTypeParameters) { + const length = outerTypeParameters.length; + while (i < length) { + // Find group of type arguments for type parameters with the same declaring container. + const start = i; + const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i])!; + do { + i++; + } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); + // When type parameters are their own type arguments for the whole group (i.e. we have + // the default outer type arguments), we don't show the group. + if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { + const typeArgumentSlice = mapToTypeNodes(typeArguments.slice(start, i), context); + const flags = context.flags; + context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; + const ref = symbolToTypeNode(parent, context, SymbolFlags.Type, typeArgumentSlice) as TypeReferenceNode | ImportTypeNode; + context.flags = flags; + resultType = !resultType ? ref : appendReferenceToType(resultType, ref as TypeReferenceNode); + } + } + } + let typeArgumentNodes: readonly TypeNode[] | undefined; + if (typeArguments.length > 0) { + const typeParameterCount = (type.target.typeParameters || emptyArray).length; + typeArgumentNodes = mapToTypeNodes(typeArguments.slice(i, typeParameterCount), context); + } + const flags = context.flags; + context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; + const finalRef = symbolToTypeNode(type.symbol, context, SymbolFlags.Type, typeArgumentNodes); + context.flags = flags; + return !resultType ? finalRef : appendReferenceToType(resultType, finalRef as TypeReferenceNode); + } + } + + + function appendReferenceToType(root: TypeReferenceNode | ImportTypeNode, ref: TypeReferenceNode): TypeReferenceNode | ImportTypeNode { + if (isImportTypeNode(root)) { + // first shift type arguments + let typeArguments = root.typeArguments; + let qualifier = root.qualifier; + if (qualifier) { + if (isIdentifier(qualifier)) { + qualifier = factory.updateIdentifier(qualifier, typeArguments); } - let typeArgumentNodes: readonly TypeNode[] | undefined; - if (typeArguments.length > 0) { - const typeParameterCount = (type.target.typeParameters || emptyArray).length; - typeArgumentNodes = mapToTypeNodes(typeArguments.slice(i, typeParameterCount), context); + else { + qualifier = factory.updateQualifiedName(qualifier, + qualifier.left, + factory.updateIdentifier(qualifier.right, typeArguments)); } - const flags = context.flags; - context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; - const finalRef = symbolToTypeNode(type.symbol, context, SymbolFlags.Type, typeArgumentNodes); - context.flags = flags; - return !resultType ? finalRef : appendReferenceToType(resultType, finalRef as TypeReferenceNode); } + typeArguments = ref.typeArguments; + // then move qualifiers + const ids = getAccessStack(ref); + for (const id of ids) { + qualifier = qualifier ? factory.createQualifiedName(qualifier, id) : id; + } + return factory.updateImportTypeNode( + root, + root.argument, + qualifier, + typeArguments, + root.isTypeOf); } - - - function appendReferenceToType(root: TypeReferenceNode | ImportTypeNode, ref: TypeReferenceNode): TypeReferenceNode | ImportTypeNode { - if (isImportTypeNode(root)) { - // first shift type arguments - let typeArguments = root.typeArguments; - let qualifier = root.qualifier; - if (qualifier) { - if (isIdentifier(qualifier)) { - qualifier = factory.updateIdentifier(qualifier, typeArguments); - } - else { - qualifier = factory.updateQualifiedName(qualifier, - qualifier.left, - factory.updateIdentifier(qualifier.right, typeArguments)); - } - } - typeArguments = ref.typeArguments; - // then move qualifiers - const ids = getAccessStack(ref); - for (const id of ids) { - qualifier = qualifier ? factory.createQualifiedName(qualifier, id) : id; - } - return factory.updateImportTypeNode( - root, - root.argument, - qualifier, - typeArguments, - root.isTypeOf); + else { + // first shift type arguments + let typeArguments = root.typeArguments; + let typeName = root.typeName; + if (isIdentifier(typeName)) { + typeName = factory.updateIdentifier(typeName, typeArguments); } else { - // first shift type arguments - let typeArguments = root.typeArguments; - let typeName = root.typeName; - if (isIdentifier(typeName)) { - typeName = factory.updateIdentifier(typeName, typeArguments); - } - else { - typeName = factory.updateQualifiedName(typeName, - typeName.left, - factory.updateIdentifier(typeName.right, typeArguments)); - } - typeArguments = ref.typeArguments; - // then move qualifiers - const ids = getAccessStack(ref); - for (const id of ids) { - typeName = factory.createQualifiedName(typeName, id); - } - return factory.updateTypeReferenceNode( - root, - typeName, - typeArguments); + typeName = factory.updateQualifiedName(typeName, + typeName.left, + factory.updateIdentifier(typeName.right, typeArguments)); + } + typeArguments = ref.typeArguments; + // then move qualifiers + const ids = getAccessStack(ref); + for (const id of ids) { + typeName = factory.createQualifiedName(typeName, id); } + return factory.updateTypeReferenceNode( + root, + typeName, + typeArguments); } + } - function getAccessStack(ref: TypeReferenceNode): Identifier[] { - let state = ref.typeName; - const ids = []; - while (!isIdentifier(state)) { - ids.unshift(state.right); - state = state.left; - } - ids.unshift(state); - return ids; + function getAccessStack(ref: TypeReferenceNode): Identifier[] { + let state = ref.typeName; + const ids = []; + while (!isIdentifier(state)) { + ids.unshift(state.right); + state = state.left; } + ids.unshift(state); + return ids; + } - function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] | undefined { - if (checkTruncationLength(context)) { - return [factory.createPropertySignature(/*modifiers*/ undefined, "...", /*questionToken*/ undefined, /*type*/ undefined)]; - } - const typeElements: TypeElement[] = []; - for (const signature of resolvedType.callSignatures) { - typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, context) as CallSignatureDeclaration); - } - for (const signature of resolvedType.constructSignatures) { - if (signature.flags & SignatureFlags.Abstract) continue; - typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context) as ConstructSignatureDeclaration); - } - for (const info of resolvedType.indexInfos) { - typeElements.push(indexInfoToIndexSignatureDeclarationHelper(info, context, resolvedType.objectFlags & ObjectFlags.ReverseMapped ? createElidedInformationPlaceholder(context) : undefined)); - } + function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] | undefined { + if (checkTruncationLength(context)) { + return [factory.createPropertySignature(/*modifiers*/ undefined, "...", /*questionToken*/ undefined, /*type*/ undefined)]; + } + const typeElements: TypeElement[] = []; + for (const signature of resolvedType.callSignatures) { + typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, context) as CallSignatureDeclaration); + } + for (const signature of resolvedType.constructSignatures) { + if (signature.flags & SignatureFlags.Abstract) continue; + typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context) as ConstructSignatureDeclaration); + } + for (const info of resolvedType.indexInfos) { + typeElements.push(indexInfoToIndexSignatureDeclarationHelper(info, context, resolvedType.objectFlags & ObjectFlags.ReverseMapped ? createElidedInformationPlaceholder(context) : undefined)); + } - const properties = resolvedType.properties; - if (!properties) { - return typeElements; - } + const properties = resolvedType.properties; + if (!properties) { + return typeElements; + } - let i = 0; - for (const propertySymbol of properties) { - i++; - if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { - if (propertySymbol.flags & SymbolFlags.Prototype) { - continue; - } - if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { - context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); - } + let i = 0; + for (const propertySymbol of properties) { + i++; + if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { + if (propertySymbol.flags & SymbolFlags.Prototype) { + continue; } - if (checkTruncationLength(context) && (i + 2 < properties.length - 1)) { - typeElements.push(factory.createPropertySignature(/*modifiers*/ undefined, `... ${properties.length - i} more ...`, /*questionToken*/ undefined, /*type*/ undefined)); - addPropertyToElementList(properties[properties.length - 1], context, typeElements); - break; + if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { + context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); } - addPropertyToElementList(propertySymbol, context, typeElements); - } - return typeElements.length ? typeElements : undefined; - } - } + if (checkTruncationLength(context) && (i + 2 < properties.length - 1)) { + typeElements.push(factory.createPropertySignature(/*modifiers*/ undefined, `... ${properties.length - i} more ...`, /*questionToken*/ undefined, /*type*/ undefined)); + addPropertyToElementList(properties[properties.length - 1], context, typeElements); + break; + } + addPropertyToElementList(propertySymbol, context, typeElements); - function createElidedInformationPlaceholder(context: NodeBuilderContext) { - context.approximateLength += 3; - if (!(context.flags & NodeBuilderFlags.NoTruncation)) { - return factory.createTypeReferenceNode(factory.createIdentifier("..."), /*typeArguments*/ undefined); } - return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + return typeElements.length ? typeElements : undefined; } + } - function shouldUsePlaceholderForProperty(propertySymbol: Symbol, context: NodeBuilderContext) { - // Use placeholders for reverse mapped types we've either already descended into, or which - // are nested reverse mappings within a mapping over a non-anonymous type. The later is a restriction mostly just to - // reduce the blowup in printback size from doing, eg, a deep reverse mapping over `Window`. - // Since anonymous types usually come from expressions, this allows us to preserve the output - // for deep mappings which likely come from expressions, while truncating those parts which - // come from mappings over library functions. - return !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped) - && ( - contains(context.reverseMappedStack, propertySymbol as ReverseMappedSymbol) - || ( - context.reverseMappedStack?.[0] - && !(getObjectFlags(last(context.reverseMappedStack).propertyType) & ObjectFlags.Anonymous) - ) - ); + function createElidedInformationPlaceholder(context: NodeBuilderContext) { + context.approximateLength += 3; + if (!(context.flags & NodeBuilderFlags.NoTruncation)) { + return factory.createTypeReferenceNode(factory.createIdentifier("..."), /*typeArguments*/ undefined); } + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } - function addPropertyToElementList(propertySymbol: Symbol, context: NodeBuilderContext, typeElements: TypeElement[]) { - const propertyIsReverseMapped = !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped); - const propertyType = shouldUsePlaceholderForProperty(propertySymbol, context) ? - anyType : getNonMissingTypeOfSymbol(propertySymbol); - const saveEnclosingDeclaration = context.enclosingDeclaration; - context.enclosingDeclaration = undefined; - if (context.tracker.trackSymbol && getCheckFlags(propertySymbol) & CheckFlags.Late && isLateBoundName(propertySymbol.escapedName)) { - if (propertySymbol.declarations) { - const decl = first(propertySymbol.declarations); - if (hasLateBindableName(decl)) { - if (isBinaryExpression(decl)) { - const name = getNameOfDeclaration(decl); - if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) { - trackComputedName(name.argumentExpression, saveEnclosingDeclaration, context); - } - } - else { - trackComputedName(decl.name.expression, saveEnclosingDeclaration, context); + function shouldUsePlaceholderForProperty(propertySymbol: Symbol, context: NodeBuilderContext) { + // Use placeholders for reverse mapped types we've either already descended into, or which + // are nested reverse mappings within a mapping over a non-anonymous type. The later is a restriction mostly just to + // reduce the blowup in printback size from doing, eg, a deep reverse mapping over `Window`. + // Since anonymous types usually come from expressions, this allows us to preserve the output + // for deep mappings which likely come from expressions, while truncating those parts which + // come from mappings over library functions. + return !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped) + && ( + contains(context.reverseMappedStack, propertySymbol as ReverseMappedSymbol) + || ( + context.reverseMappedStack?.[0] + && !(getObjectFlags(last(context.reverseMappedStack).propertyType) & ObjectFlags.Anonymous) + ) + ); + } + + function addPropertyToElementList(propertySymbol: Symbol, context: NodeBuilderContext, typeElements: TypeElement[]) { + const propertyIsReverseMapped = !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped); + const propertyType = shouldUsePlaceholderForProperty(propertySymbol, context) ? + anyType : getNonMissingTypeOfSymbol(propertySymbol); + const saveEnclosingDeclaration = context.enclosingDeclaration; + context.enclosingDeclaration = undefined; + if (context.tracker.trackSymbol && getCheckFlags(propertySymbol) & CheckFlags.Late && isLateBoundName(propertySymbol.escapedName)) { + if (propertySymbol.declarations) { + const decl = first(propertySymbol.declarations); + if (hasLateBindableName(decl)) { + if (isBinaryExpression(decl)) { + const name = getNameOfDeclaration(decl); + if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) { + trackComputedName(name.argumentExpression, saveEnclosingDeclaration, context); } } - } - else if (context.tracker?.reportNonSerializableProperty) { - context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol)); + else { + trackComputedName(decl.name.expression, saveEnclosingDeclaration, context); + } } } - context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration; - const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context); - context.enclosingDeclaration = saveEnclosingDeclaration; - context.approximateLength += (symbolName(propertySymbol).length + 1); - const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined; - if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length && !isReadonlySymbol(propertySymbol)) { - const signatures = getSignaturesOfType(filterType(propertyType, t => !(t.flags & TypeFlags.Undefined)), SignatureKind.Call); - for (const signature of signatures) { - const methodDeclaration = signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature, context, { name: propertyName, questionToken: optionalToken }) as MethodSignature; - typeElements.push(preserveCommentsOn(methodDeclaration)); - } + else if (context.tracker?.reportNonSerializableProperty) { + context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol)); + } + } + context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration; + const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context); + context.enclosingDeclaration = saveEnclosingDeclaration; + context.approximateLength += (symbolName(propertySymbol).length + 1); + const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined; + if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length && !isReadonlySymbol(propertySymbol)) { + const signatures = getSignaturesOfType(filterType(propertyType, t => !(t.flags & TypeFlags.Undefined)), SignatureKind.Call); + for (const signature of signatures) { + const methodDeclaration = signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature, context, { name: propertyName, questionToken: optionalToken }) as MethodSignature; + typeElements.push(preserveCommentsOn(methodDeclaration)); + } + } + else { + let propertyTypeNode: TypeNode; + if (shouldUsePlaceholderForProperty(propertySymbol, context)) { + propertyTypeNode = createElidedInformationPlaceholder(context); } else { - let propertyTypeNode: TypeNode; - if (shouldUsePlaceholderForProperty(propertySymbol, context)) { - propertyTypeNode = createElidedInformationPlaceholder(context); - } - else { - if (propertyIsReverseMapped) { - context.reverseMappedStack ||= []; - context.reverseMappedStack.push(propertySymbol as ReverseMappedSymbol); - } - propertyTypeNode = propertyType ? serializeTypeForDeclaration(context, propertyType, propertySymbol, saveEnclosingDeclaration) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - if (propertyIsReverseMapped) { - context.reverseMappedStack!.pop(); - } + if (propertyIsReverseMapped) { + context.reverseMappedStack ||= []; + context.reverseMappedStack.push(propertySymbol as ReverseMappedSymbol); } - - const modifiers = isReadonlySymbol(propertySymbol) ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined; - if (modifiers) { - context.approximateLength += 9; + propertyTypeNode = propertyType ? serializeTypeForDeclaration(context, propertyType, propertySymbol, saveEnclosingDeclaration) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + if (propertyIsReverseMapped) { + context.reverseMappedStack!.pop(); } - const propertySignature = factory.createPropertySignature( - modifiers, - propertyName, - optionalToken, - propertyTypeNode); + } - typeElements.push(preserveCommentsOn(propertySignature)); + const modifiers = isReadonlySymbol(propertySymbol) ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined; + if (modifiers) { + context.approximateLength += 9; } + const propertySignature = factory.createPropertySignature( + modifiers, + propertyName, + optionalToken, + propertyTypeNode); - function preserveCommentsOn(node: T) { - if (some(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)) { - const d = propertySymbol.declarations?.find(d => d.kind === SyntaxKind.JSDocPropertyTag)! as JSDocPropertyTag; - const commentText = getTextOfJSDocComment(d.comment); - if (commentText) { - setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]); - } - } - else if (propertySymbol.valueDeclaration) { - // Copy comments to node for declaration emit - setCommentRange(node, propertySymbol.valueDeclaration); + typeElements.push(preserveCommentsOn(propertySignature)); + } + + function preserveCommentsOn(node: T) { + if (some(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)) { + const d = propertySymbol.declarations?.find(d => d.kind === SyntaxKind.JSDocPropertyTag)! as JSDocPropertyTag; + const commentText = getTextOfJSDocComment(d.comment); + if (commentText) { + setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]); } - return node; } + else if (propertySymbol.valueDeclaration) { + // Copy comments to node for declaration emit + setCommentRange(node, propertySymbol.valueDeclaration); + } + return node; } + } - function mapToTypeNodes(types: readonly Type[] | undefined, context: NodeBuilderContext, isBareList?: boolean): TypeNode[] | undefined { - if (some(types)) { - if (checkTruncationLength(context)) { - if (!isBareList) { - return [factory.createTypeReferenceNode("...", /*typeArguments*/ undefined)]; - } - else if (types.length > 2) { - return [ - typeToTypeNodeHelper(types[0], context), - factory.createTypeReferenceNode(`... ${types.length - 2} more ...`, /*typeArguments*/ undefined), - typeToTypeNodeHelper(types[types.length - 1], context) - ]; - } + function mapToTypeNodes(types: readonly Type[] | undefined, context: NodeBuilderContext, isBareList?: boolean): TypeNode[] | undefined { + if (some(types)) { + if (checkTruncationLength(context)) { + if (!isBareList) { + return [factory.createTypeReferenceNode("...", /*typeArguments*/ undefined)]; } - const mayHaveNameCollisions = !(context.flags & NodeBuilderFlags.UseFullyQualifiedType); - /** Map from type reference identifier text to [type, index in `result` where the type node is] */ - const seenNames = mayHaveNameCollisions ? createUnderscoreEscapedMultiMap<[Type, number]>() : undefined; - const result: TypeNode[] = []; - let i = 0; - for (const type of types) { - i++; - if (checkTruncationLength(context) && (i + 2 < types.length - 1)) { - result.push(factory.createTypeReferenceNode(`... ${types.length - i} more ...`, /*typeArguments*/ undefined)); - const typeNode = typeToTypeNodeHelper(types[types.length - 1], context); - if (typeNode) { - result.push(typeNode); - } - break; - } - context.approximateLength += 2; // Account for whitespace + separator - const typeNode = typeToTypeNodeHelper(type, context); + else if (types.length > 2) { + return [ + typeToTypeNodeHelper(types[0], context), + factory.createTypeReferenceNode(`... ${types.length - 2} more ...`, /*typeArguments*/ undefined), + typeToTypeNodeHelper(types[types.length - 1], context) + ]; + } + } + const mayHaveNameCollisions = !(context.flags & NodeBuilderFlags.UseFullyQualifiedType); + /** Map from type reference identifier text to [type, index in `result` where the type node is] */ + const seenNames = mayHaveNameCollisions ? createUnderscoreEscapedMultiMap<[Type, number]>() : undefined; + const result: TypeNode[] = []; + let i = 0; + for (const type of types) { + i++; + if (checkTruncationLength(context) && (i + 2 < types.length - 1)) { + result.push(factory.createTypeReferenceNode(`... ${types.length - i} more ...`, /*typeArguments*/ undefined)); + const typeNode = typeToTypeNodeHelper(types[types.length - 1], context); if (typeNode) { result.push(typeNode); - if (seenNames && isIdentifierTypeReference(typeNode)) { - seenNames.add(typeNode.typeName.escapedText, [type, result.length - 1]); - } } + break; } - - if (seenNames) { - // To avoid printing types like `[Foo, Foo]` or `Bar & Bar` where - // occurrences of the same name actually come from different - // namespaces, go through the single-identifier type reference nodes - // we just generated, and see if any names were generated more than - // once while referring to different types. If so, regenerate the - // type node for each entry by that name with the - // `UseFullyQualifiedType` flag enabled. - const saveContextFlags = context.flags; - context.flags |= NodeBuilderFlags.UseFullyQualifiedType; - seenNames.forEach(types => { - if (!arrayIsHomogeneous(types, ([a], [b]) => typesAreSameReference(a, b))) { - for (const [type, resultIndex] of types) { - result[resultIndex] = typeToTypeNodeHelper(type, context); - } - } - }); - context.flags = saveContextFlags; + context.approximateLength += 2; // Account for whitespace + separator + const typeNode = typeToTypeNodeHelper(type, context); + if (typeNode) { + result.push(typeNode); + if (seenNames && isIdentifierTypeReference(typeNode)) { + seenNames.add(typeNode.typeName.escapedText, [type, result.length - 1]); + } } + } - return result; + if (seenNames) { + // To avoid printing types like `[Foo, Foo]` or `Bar & Bar` where + // occurrences of the same name actually come from different + // namespaces, go through the single-identifier type reference nodes + // we just generated, and see if any names were generated more than + // once while referring to different types. If so, regenerate the + // type node for each entry by that name with the + // `UseFullyQualifiedType` flag enabled. + const saveContextFlags = context.flags; + context.flags |= NodeBuilderFlags.UseFullyQualifiedType; + seenNames.forEach(types => { + if (!arrayIsHomogeneous(types, ([a], [b]) => typesAreSameReference(a, b))) { + for (const [type, resultIndex] of types) { + result[resultIndex] = typeToTypeNodeHelper(type, context); + } + } + }); + context.flags = saveContextFlags; } + + return result; } + } - function typesAreSameReference(a: Type, b: Type): boolean { - return a === b - || !!a.symbol && a.symbol === b.symbol - || !!a.aliasSymbol && a.aliasSymbol === b.aliasSymbol; + function typesAreSameReference(a: Type, b: Type): boolean { + return a === b + || !!a.symbol && a.symbol === b.symbol + || !!a.aliasSymbol && a.aliasSymbol === b.aliasSymbol; + } + + function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, context: NodeBuilderContext, typeNode: TypeNode | undefined): IndexSignatureDeclaration { + const name = getNameFromIndexInfo(indexInfo) || "x"; + const indexerTypeNode = typeToTypeNodeHelper(indexInfo.keyType, context); + + const indexingParameter = factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + name, + /*questionToken*/ undefined, + indexerTypeNode, + /*initializer*/ undefined); + if (!typeNode) { + typeNode = typeToTypeNodeHelper(indexInfo.type || anyType, context); + } + if (!indexInfo.type && !(context.flags & NodeBuilderFlags.AllowEmptyIndexInfoType)) { + context.encounteredError = true; } + context.approximateLength += (name.length + 4); + return factory.createIndexSignature( + /*decorators*/ undefined, + indexInfo.isReadonly ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined, + [indexingParameter], + typeNode); + } - function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, context: NodeBuilderContext, typeNode: TypeNode | undefined): IndexSignatureDeclaration { - const name = getNameFromIndexInfo(indexInfo) || "x"; - const indexerTypeNode = typeToTypeNodeHelper(indexInfo.keyType, context); + interface SignatureToSignatureDeclarationOptions { + modifiers?: readonly Modifier[]; + name?: PropertyName; + questionToken?: QuestionToken; + privateSymbolVisitor?: (s: Symbol) => void; + bundledImports?: boolean; + } - const indexingParameter = factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - name, - /*questionToken*/ undefined, - indexerTypeNode, - /*initializer*/ undefined); - if (!typeNode) { - typeNode = typeToTypeNodeHelper(indexInfo.type || anyType, context); - } - if (!indexInfo.type && !(context.flags & NodeBuilderFlags.AllowEmptyIndexInfoType)) { - context.encounteredError = true; - } - context.approximateLength += (name.length + 4); - return factory.createIndexSignature( - /*decorators*/ undefined, - indexInfo.isReadonly ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined, - [indexingParameter], - typeNode); + function signatureToSignatureDeclarationHelper(signature: Signature, kind: SignatureDeclaration["kind"], context: NodeBuilderContext, options?: SignatureToSignatureDeclarationOptions): SignatureDeclaration { + const suppressAny = context.flags & NodeBuilderFlags.SuppressAnyReturnType; + if (suppressAny) context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // suppress only toplevel `any`s + context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum + let typeParameters: TypeParameterDeclaration[] | undefined; + let typeArguments: TypeNode[] | undefined; + if (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature && signature.target && signature.mapper && signature.target.typeParameters) { + typeArguments = signature.target.typeParameters.map(parameter => typeToTypeNodeHelper(instantiateType(parameter, signature.mapper), context)); + } + else { + typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context)); } - interface SignatureToSignatureDeclarationOptions { - modifiers?: readonly Modifier[]; - name?: PropertyName; - questionToken?: QuestionToken; - privateSymbolVisitor?: (s: Symbol) => void; - bundledImports?: boolean; + const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0]; + // If the expanded parameter list had a variadic in a non-trailing position, don't expand it + const parameters = (some(expandedParams, p => p !== expandedParams[expandedParams.length - 1] && !!(getCheckFlags(p) & CheckFlags.RestParameter)) ? signature.parameters : expandedParams).map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor, options?.privateSymbolVisitor, options?.bundledImports)); + const thisParameter = tryGetThisParameterDeclaration(signature, context); + if (thisParameter) { + parameters.unshift(thisParameter); } - function signatureToSignatureDeclarationHelper(signature: Signature, kind: SignatureDeclaration["kind"], context: NodeBuilderContext, options?: SignatureToSignatureDeclarationOptions): SignatureDeclaration { - const suppressAny = context.flags & NodeBuilderFlags.SuppressAnyReturnType; - if (suppressAny) context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // suppress only toplevel `any`s - context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum - let typeParameters: TypeParameterDeclaration[] | undefined; - let typeArguments: TypeNode[] | undefined; - if (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature && signature.target && signature.mapper && signature.target.typeParameters) { - typeArguments = signature.target.typeParameters.map(parameter => typeToTypeNodeHelper(instantiateType(parameter, signature.mapper), context)); + let returnTypeNode: TypeNode | undefined; + const typePredicate = getTypePredicateOfSignature(signature); + if (typePredicate) { + const assertsModifier = typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? + factory.createToken(SyntaxKind.AssertsKeyword) : + undefined; + const parameterName = typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? + setEmitFlags(factory.createIdentifier(typePredicate.parameterName), EmitFlags.NoAsciiEscaping) : + factory.createThisTypeNode(); + const typeNode = typePredicate.type && typeToTypeNodeHelper(typePredicate.type, context); + returnTypeNode = factory.createTypePredicateNode(assertsModifier, parameterName, typeNode); + } + else { + const returnType = getReturnTypeOfSignature(signature); + if (returnType && !(suppressAny && isTypeAny(returnType))) { + returnTypeNode = serializeReturnTypeForSignature(context, returnType, signature, options?.privateSymbolVisitor, options?.bundledImports); } - else { - typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context)); + else if (!suppressAny) { + returnTypeNode = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } + } + let modifiers = options?.modifiers; + if ((kind === SyntaxKind.ConstructorType) && signature.flags & SignatureFlags.Abstract) { + const flags = modifiersToFlags(modifiers); + modifiers = factory.createModifiersFromModifierFlags(flags | ModifierFlags.Abstract); + } - const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0]; - // If the expanded parameter list had a variadic in a non-trailing position, don't expand it - const parameters = (some(expandedParams, p => p !== expandedParams[expandedParams.length - 1] && !!(getCheckFlags(p) & CheckFlags.RestParameter)) ? signature.parameters : expandedParams).map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor, options?.privateSymbolVisitor, options?.bundledImports)); - const thisParameter = tryGetThisParameterDeclaration(signature, context); - if (thisParameter) { - parameters.unshift(thisParameter); - } + const node = + kind === SyntaxKind.CallSignature ? factory.createCallSignature(typeParameters, parameters, returnTypeNode) : + kind === SyntaxKind.ConstructSignature ? factory.createConstructSignature(typeParameters, parameters, returnTypeNode) : + kind === SyntaxKind.MethodSignature ? factory.createMethodSignature(modifiers, options?.name ?? factory.createIdentifier(""), options?.questionToken, typeParameters, parameters, returnTypeNode) : + kind === SyntaxKind.MethodDeclaration ? factory.createMethodDeclaration(/*decorators*/ undefined, modifiers, /*asteriskToken*/ undefined, options?.name ?? factory.createIdentifier(""), /*questionToken*/ undefined, typeParameters, parameters, returnTypeNode, /*body*/ undefined) : + kind === SyntaxKind.Constructor ? factory.createConstructorDeclaration(/*decorators*/ undefined, modifiers, parameters, /*body*/ undefined) : + kind === SyntaxKind.GetAccessor ? factory.createGetAccessorDeclaration(/*decorators*/ undefined, modifiers, options?.name ?? factory.createIdentifier(""), parameters, returnTypeNode, /*body*/ undefined) : + kind === SyntaxKind.SetAccessor ? factory.createSetAccessorDeclaration(/*decorators*/ undefined, modifiers, options?.name ?? factory.createIdentifier(""), parameters, /*body*/ undefined) : + kind === SyntaxKind.IndexSignature ? factory.createIndexSignature(/*decorators*/ undefined, modifiers, parameters, returnTypeNode) : + kind === SyntaxKind.JSDocFunctionType ? factory.createJSDocFunctionType(parameters, returnTypeNode) : + kind === SyntaxKind.FunctionType ? factory.createFunctionTypeNode(typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) : + kind === SyntaxKind.ConstructorType ? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) : + kind === SyntaxKind.FunctionDeclaration ? factory.createFunctionDeclaration(/*decorators*/ undefined, modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, /*body*/ undefined) : + kind === SyntaxKind.FunctionExpression ? factory.createFunctionExpression(modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, factory.createBlock([])) : + kind === SyntaxKind.ArrowFunction ? factory.createArrowFunction(modifiers, typeParameters, parameters, returnTypeNode, /*equalsGreaterThanToken*/ undefined, factory.createBlock([])) : + Debug.assertNever(kind); - let returnTypeNode: TypeNode | undefined; - const typePredicate = getTypePredicateOfSignature(signature); - if (typePredicate) { - const assertsModifier = typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? - factory.createToken(SyntaxKind.AssertsKeyword) : - undefined; - const parameterName = typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? - setEmitFlags(factory.createIdentifier(typePredicate.parameterName), EmitFlags.NoAsciiEscaping) : - factory.createThisTypeNode(); - const typeNode = typePredicate.type && typeToTypeNodeHelper(typePredicate.type, context); - returnTypeNode = factory.createTypePredicateNode(assertsModifier, parameterName, typeNode); - } - else { - const returnType = getReturnTypeOfSignature(signature); - if (returnType && !(suppressAny && isTypeAny(returnType))) { - returnTypeNode = serializeReturnTypeForSignature(context, returnType, signature, options?.privateSymbolVisitor, options?.bundledImports); - } - else if (!suppressAny) { - returnTypeNode = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - } - let modifiers = options?.modifiers; - if ((kind === SyntaxKind.ConstructorType) && signature.flags & SignatureFlags.Abstract) { - const flags = modifiersToFlags(modifiers); - modifiers = factory.createModifiersFromModifierFlags(flags | ModifierFlags.Abstract); - } + if (typeArguments) { + node.typeArguments = factory.createNodeArray(typeArguments); + } - const node = - kind === SyntaxKind.CallSignature ? factory.createCallSignature(typeParameters, parameters, returnTypeNode) : - kind === SyntaxKind.ConstructSignature ? factory.createConstructSignature(typeParameters, parameters, returnTypeNode) : - kind === SyntaxKind.MethodSignature ? factory.createMethodSignature(modifiers, options?.name ?? factory.createIdentifier(""), options?.questionToken, typeParameters, parameters, returnTypeNode) : - kind === SyntaxKind.MethodDeclaration ? factory.createMethodDeclaration(/*decorators*/ undefined, modifiers, /*asteriskToken*/ undefined, options?.name ?? factory.createIdentifier(""), /*questionToken*/ undefined, typeParameters, parameters, returnTypeNode, /*body*/ undefined) : - kind === SyntaxKind.Constructor ? factory.createConstructorDeclaration(/*decorators*/ undefined, modifiers, parameters, /*body*/ undefined) : - kind === SyntaxKind.GetAccessor ? factory.createGetAccessorDeclaration(/*decorators*/ undefined, modifiers, options?.name ?? factory.createIdentifier(""), parameters, returnTypeNode, /*body*/ undefined) : - kind === SyntaxKind.SetAccessor ? factory.createSetAccessorDeclaration(/*decorators*/ undefined, modifiers, options?.name ?? factory.createIdentifier(""), parameters, /*body*/ undefined) : - kind === SyntaxKind.IndexSignature ? factory.createIndexSignature(/*decorators*/ undefined, modifiers, parameters, returnTypeNode) : - kind === SyntaxKind.JSDocFunctionType ? factory.createJSDocFunctionType(parameters, returnTypeNode) : - kind === SyntaxKind.FunctionType ? factory.createFunctionTypeNode(typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) : - kind === SyntaxKind.ConstructorType ? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) : - kind === SyntaxKind.FunctionDeclaration ? factory.createFunctionDeclaration(/*decorators*/ undefined, modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, /*body*/ undefined) : - kind === SyntaxKind.FunctionExpression ? factory.createFunctionExpression(modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, factory.createBlock([])) : - kind === SyntaxKind.ArrowFunction ? factory.createArrowFunction(modifiers, typeParameters, parameters, returnTypeNode, /*equalsGreaterThanToken*/ undefined, factory.createBlock([])) : - Debug.assertNever(kind); + return node; + } - if (typeArguments) { - node.typeArguments = factory.createNodeArray(typeArguments); + function tryGetThisParameterDeclaration(signature: Signature, context: NodeBuilderContext) { + if (signature.thisParameter) { + return symbolToParameterDeclaration(signature.thisParameter, context); + } + if (signature.declaration) { + const thisTag = getJSDocThisTag(signature.declaration); + if (thisTag && thisTag.typeExpression) { + return factory.createParameterDeclaration( + /* decorators */ undefined, + /* modifiers */ undefined, + /* dotDotDotToken */ undefined, + "this", + /* questionToken */ undefined, + typeToTypeNodeHelper(getTypeFromTypeNode(thisTag.typeExpression), context) + ); } - - return node; } + } - function tryGetThisParameterDeclaration(signature: Signature, context: NodeBuilderContext) { - if (signature.thisParameter) { - return symbolToParameterDeclaration(signature.thisParameter, context); - } - if (signature.declaration) { - const thisTag = getJSDocThisTag(signature.declaration); - if (thisTag && thisTag.typeExpression) { - return factory.createParameterDeclaration( - /* decorators */ undefined, - /* modifiers */ undefined, - /* dotDotDotToken */ undefined, - "this", - /* questionToken */ undefined, - typeToTypeNodeHelper(getTypeFromTypeNode(thisTag.typeExpression), context) - ); - } - } - } + function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode | undefined): TypeParameterDeclaration { + const savedContextFlags = context.flags; + context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic + const name = typeParameterToName(type, context); + const defaultParameter = getDefaultFromTypeParameter(type); + const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context); + context.flags = savedContextFlags; + return factory.createTypeParameterDeclaration(name, constraintNode, defaultParameterNode); + } - function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode | undefined): TypeParameterDeclaration { - const savedContextFlags = context.flags; - context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic - const name = typeParameterToName(type, context); - const defaultParameter = getDefaultFromTypeParameter(type); - const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context); - context.flags = savedContextFlags; - return factory.createTypeParameterDeclaration(name, constraintNode, defaultParameterNode); - } + function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { + const constraintNode = constraint && typeToTypeNodeHelper(constraint, context); + return typeParameterToDeclarationWithConstraint(type, context, constraintNode); + } - function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { - const constraintNode = constraint && typeToTypeNodeHelper(constraint, context); - return typeParameterToDeclarationWithConstraint(type, context, constraintNode); + function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean, privateSymbolVisitor?: (s: Symbol) => void, bundledImports?: boolean): ParameterDeclaration { + let parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); + if (!parameterDeclaration && !isTransientSymbol(parameterSymbol)) { + parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); } - function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean, privateSymbolVisitor?: (s: Symbol) => void, bundledImports?: boolean): ParameterDeclaration { - let parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); - if (!parameterDeclaration && !isTransientSymbol(parameterSymbol)) { - parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); - } - - let parameterType = getTypeOfSymbol(parameterSymbol); - if (parameterDeclaration && isRequiredInitializedParameter(parameterDeclaration)) { - parameterType = getOptionalType(parameterType); - } - if ((context.flags & NodeBuilderFlags.NoUndefinedOptionalParameterType) && parameterDeclaration && !isJSDocParameterTag(parameterDeclaration) && isOptionalUninitializedParameter(parameterDeclaration)) { - parameterType = getTypeWithFacts(parameterType, TypeFacts.NEUndefined); - } - const parameterTypeNode = serializeTypeForDeclaration(context, parameterType, parameterSymbol, context.enclosingDeclaration, privateSymbolVisitor, bundledImports); + let parameterType = getTypeOfSymbol(parameterSymbol); + if (parameterDeclaration && isRequiredInitializedParameter(parameterDeclaration)) { + parameterType = getOptionalType(parameterType); + } + if ((context.flags & NodeBuilderFlags.NoUndefinedOptionalParameterType) && parameterDeclaration && !isJSDocParameterTag(parameterDeclaration) && isOptionalUninitializedParameter(parameterDeclaration)) { + parameterType = getTypeWithFacts(parameterType, TypeFacts.NEUndefined); + } + const parameterTypeNode = serializeTypeForDeclaration(context, parameterType, parameterSymbol, context.enclosingDeclaration, privateSymbolVisitor, bundledImports); - const modifiers = !(context.flags & NodeBuilderFlags.OmitParameterModifiers) && preserveModifierFlags && parameterDeclaration && parameterDeclaration.modifiers ? parameterDeclaration.modifiers.map(factory.cloneNode) : undefined; - const isRest = parameterDeclaration && isRestParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.RestParameter; - const dotDotDotToken = isRest ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined; - const name = parameterDeclaration ? parameterDeclaration.name ? - parameterDeclaration.name.kind === SyntaxKind.Identifier ? setEmitFlags(factory.cloneNode(parameterDeclaration.name), EmitFlags.NoAsciiEscaping) : - parameterDeclaration.name.kind === SyntaxKind.QualifiedName ? setEmitFlags(factory.cloneNode(parameterDeclaration.name.right), EmitFlags.NoAsciiEscaping) : - cloneBindingName(parameterDeclaration.name) : - symbolName(parameterSymbol) : - symbolName(parameterSymbol); - const isOptional = parameterDeclaration && isOptionalParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.OptionalParameter; - const questionToken = isOptional ? factory.createToken(SyntaxKind.QuestionToken) : undefined; - const parameterNode = factory.createParameterDeclaration( - /*decorators*/ undefined, - modifiers, - dotDotDotToken, - name, - questionToken, - parameterTypeNode, - /*initializer*/ undefined); - context.approximateLength += symbolName(parameterSymbol).length + 3; - return parameterNode; + const modifiers = !(context.flags & NodeBuilderFlags.OmitParameterModifiers) && preserveModifierFlags && parameterDeclaration && parameterDeclaration.modifiers ? parameterDeclaration.modifiers.map(factory.cloneNode) : undefined; + const isRest = parameterDeclaration && isRestParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.RestParameter; + const dotDotDotToken = isRest ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined; + const name = parameterDeclaration ? parameterDeclaration.name ? + parameterDeclaration.name.kind === SyntaxKind.Identifier ? setEmitFlags(factory.cloneNode(parameterDeclaration.name), EmitFlags.NoAsciiEscaping) : + parameterDeclaration.name.kind === SyntaxKind.QualifiedName ? setEmitFlags(factory.cloneNode(parameterDeclaration.name.right), EmitFlags.NoAsciiEscaping) : + cloneBindingName(parameterDeclaration.name) : + symbolName(parameterSymbol) : + symbolName(parameterSymbol); + const isOptional = parameterDeclaration && isOptionalParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.OptionalParameter; + const questionToken = isOptional ? factory.createToken(SyntaxKind.QuestionToken) : undefined; + const parameterNode = factory.createParameterDeclaration( + /*decorators*/ undefined, + modifiers, + dotDotDotToken, + name, + questionToken, + parameterTypeNode, + /*initializer*/ undefined); + context.approximateLength += symbolName(parameterSymbol).length + 3; + return parameterNode; - function cloneBindingName(node: BindingName): BindingName { - return elideInitializerAndSetEmitFlags(node) as BindingName; - function elideInitializerAndSetEmitFlags(node: Node): Node { - if (context.tracker.trackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { - trackComputedName(node.expression, context.enclosingDeclaration, context); - } - let visited = visitEachChild(node, elideInitializerAndSetEmitFlags, nullTransformationContext, /*nodesVisitor*/ undefined, elideInitializerAndSetEmitFlags)!; - if (isBindingElement(visited)) { - visited = factory.updateBindingElement( - visited, - visited.dotDotDotToken, - visited.propertyName, - visited.name, - /*initializer*/ undefined); - } - if (!nodeIsSynthesized(visited)) { - visited = factory.cloneNode(visited); - } - return setEmitFlags(visited, EmitFlags.SingleLine | EmitFlags.NoAsciiEscaping); + function cloneBindingName(node: BindingName): BindingName { + return elideInitializerAndSetEmitFlags(node) as BindingName; + function elideInitializerAndSetEmitFlags(node: Node): Node { + if (context.tracker.trackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { + trackComputedName(node.expression, context.enclosingDeclaration, context); } + let visited = visitEachChild(node, elideInitializerAndSetEmitFlags, nullTransformationContext, /*nodesVisitor*/ undefined, elideInitializerAndSetEmitFlags)!; + if (isBindingElement(visited)) { + visited = factory.updateBindingElement( + visited, + visited.dotDotDotToken, + visited.propertyName, + visited.name, + /*initializer*/ undefined); + } + if (!nodeIsSynthesized(visited)) { + visited = factory.cloneNode(visited); + } + return setEmitFlags(visited, EmitFlags.SingleLine | EmitFlags.NoAsciiEscaping); } } + } - function trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { - if (!context.tracker.trackSymbol) return; - // get symbol of the first identifier of the entityName - const firstIdentifier = getFirstIdentifier(accessExpression); - const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); - if (name) { - context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value); - } + function trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { + if (!context.tracker.trackSymbol) return; + // get symbol of the first identifier of the entityName + const firstIdentifier = getFirstIdentifier(accessExpression); + const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + if (name) { + context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value); } + } - function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { - context.tracker.trackSymbol!(symbol, context.enclosingDeclaration, meaning); // TODO: GH#18217 - return lookupSymbolChainWorker(symbol, context, meaning, yieldModuleSymbol); - } + function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { + context.tracker.trackSymbol!(symbol, context.enclosingDeclaration, meaning); // TODO: GH#18217 + return lookupSymbolChainWorker(symbol, context, meaning, yieldModuleSymbol); + } - function lookupSymbolChainWorker(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { - // Try to get qualified name if the symbol is not a type parameter and there is an enclosing declaration. - let chain: Symbol[]; - const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; - if (!isTypeParameter && (context.enclosingDeclaration || context.flags & NodeBuilderFlags.UseFullyQualifiedType) && !(context.flags & NodeBuilderFlags.DoNotIncludeSymbolChain)) { - chain = Debug.checkDefined(getSymbolChain(symbol, meaning, /*endOfChain*/ true)); - Debug.assert(chain && chain.length > 0); - } - else { - chain = [symbol]; - } - return chain; - - /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ - function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { - let accessibleSymbolChain = getAccessibleSymbolChain(symbol, context.enclosingDeclaration, meaning, !!(context.flags & NodeBuilderFlags.UseOnlyExternalAliasing)); - let parentSpecifiers: (string | undefined)[]; - if (!accessibleSymbolChain || - needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { - - // Go up and add our parent. - const parents = getContainersOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol, context.enclosingDeclaration, meaning); - if (length(parents)) { - parentSpecifiers = parents!.map(symbol => - some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol) - ? getSpecifierForModuleSymbol(symbol, context) - : undefined); - const indices = parents!.map((_, i) => i); - indices.sort(sortByBestName); - const sortedParents = indices.map(i => parents![i]); - for (const parent of sortedParents) { - const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); - if (parentChain) { - if (parent.exports && parent.exports.get(InternalSymbolName.ExportEquals) && - getSymbolIfSameReference(parent.exports.get(InternalSymbolName.ExportEquals)!, symbol)) { - // parentChain root _is_ symbol - symbol is a module export=, so it kinda looks like it's own parent - // No need to lookup an alias for the symbol in itself - accessibleSymbolChain = parentChain; - break; - } - accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [getAliasForSymbolInContainer(parent, symbol) || symbol]); + function lookupSymbolChainWorker(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { + // Try to get qualified name if the symbol is not a type parameter and there is an enclosing declaration. + let chain: Symbol[]; + const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; + if (!isTypeParameter && (context.enclosingDeclaration || context.flags & NodeBuilderFlags.UseFullyQualifiedType) && !(context.flags & NodeBuilderFlags.DoNotIncludeSymbolChain)) { + chain = Debug.checkDefined(getSymbolChain(symbol, meaning, /*endOfChain*/ true)); + Debug.assert(chain && chain.length > 0); + } + else { + chain = [symbol]; + } + return chain; + + /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ + function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { + let accessibleSymbolChain = getAccessibleSymbolChain(symbol, context.enclosingDeclaration, meaning, !!(context.flags & NodeBuilderFlags.UseOnlyExternalAliasing)); + let parentSpecifiers: (string | undefined)[]; + if (!accessibleSymbolChain || + needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { + + // Go up and add our parent. + const parents = getContainersOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol, context.enclosingDeclaration, meaning); + if (length(parents)) { + parentSpecifiers = parents!.map(symbol => + some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol) + ? getSpecifierForModuleSymbol(symbol, context) + : undefined); + const indices = parents!.map((_, i) => i); + indices.sort(sortByBestName); + const sortedParents = indices.map(i => parents![i]); + for (const parent of sortedParents) { + const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); + if (parentChain) { + if (parent.exports && parent.exports.get(InternalSymbolName.ExportEquals) && + getSymbolIfSameReference(parent.exports.get(InternalSymbolName.ExportEquals)!, symbol)) { + // parentChain root _is_ symbol - symbol is a module export=, so it kinda looks like it's own parent + // No need to lookup an alias for the symbol in itself + accessibleSymbolChain = parentChain; break; } + accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [getAliasForSymbolInContainer(parent, symbol) || symbol]); + break; } } } + } - if (accessibleSymbolChain) { - return accessibleSymbolChain; - } - if ( - // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. - endOfChain || - // If a parent symbol is an anonymous type, don't write it. - !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { - // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) - if (!endOfChain && !yieldModuleSymbol && !!forEach(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { - return; - } - return [symbol]; + if (accessibleSymbolChain) { + return accessibleSymbolChain; + } + if ( + // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. + endOfChain || + // If a parent symbol is an anonymous type, don't write it. + !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { + // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) + if (!endOfChain && !yieldModuleSymbol && !!forEach(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { + return; } + return [symbol]; + } - function sortByBestName(a: number, b: number) { - const specifierA = parentSpecifiers[a]; - const specifierB = parentSpecifiers[b]; - if (specifierA && specifierB) { - const isBRelative = pathIsRelative(specifierB); - if (pathIsRelative(specifierA) === isBRelative) { - // Both relative or both non-relative, sort by number of parts - return moduleSpecifiers.countPathComponents(specifierA) - moduleSpecifiers.countPathComponents(specifierB); - } - if (isBRelative) { - // A is non-relative, B is relative: prefer A - return -1; - } - // A is relative, B is non-relative: prefer B - return 1; + function sortByBestName(a: number, b: number) { + const specifierA = parentSpecifiers[a]; + const specifierB = parentSpecifiers[b]; + if (specifierA && specifierB) { + const isBRelative = pathIsRelative(specifierB); + if (pathIsRelative(specifierA) === isBRelative) { + // Both relative or both non-relative, sort by number of parts + return moduleSpecifiers.countPathComponents(specifierA) - moduleSpecifiers.countPathComponents(specifierB); + } + if (isBRelative) { + // A is non-relative, B is relative: prefer A + return -1; } - return 0; + // A is relative, B is non-relative: prefer B + return 1; } + return 0; } } + } - function typeParametersToTypeParameterDeclarations(symbol: Symbol, context: NodeBuilderContext) { - let typeParameterNodes: NodeArray | undefined; - const targetSymbol = getTargetSymbol(symbol); - if (targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { - typeParameterNodes = factory.createNodeArray(map(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol), tp => typeParameterToDeclaration(tp, context))); - } - return typeParameterNodes; + function typeParametersToTypeParameterDeclarations(symbol: Symbol, context: NodeBuilderContext) { + let typeParameterNodes: NodeArray | undefined; + const targetSymbol = getTargetSymbol(symbol); + if (targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { + typeParameterNodes = factory.createNodeArray(map(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol), tp => typeParameterToDeclaration(tp, context))); } + return typeParameterNodes; + } - function lookupTypeParameterNodes(chain: Symbol[], index: number, context: NodeBuilderContext) { - Debug.assert(chain && 0 <= index && index < chain.length); - const symbol = chain[index]; - const symbolId = getSymbolId(symbol); - if (context.typeParameterSymbolList?.has(symbolId)) { - return undefined; + function lookupTypeParameterNodes(chain: Symbol[], index: number, context: NodeBuilderContext) { + Debug.assert(chain && 0 <= index && index < chain.length); + const symbol = chain[index]; + const symbolId = getSymbolId(symbol); + if (context.typeParameterSymbolList?.has(symbolId)) { + return undefined; + } + (context.typeParameterSymbolList || (context.typeParameterSymbolList = new Set())).add(symbolId); + let typeParameterNodes: readonly TypeNode[] | readonly TypeParameterDeclaration[] | undefined; + if (context.flags & NodeBuilderFlags.WriteTypeParametersInQualifiedName && index < (chain.length - 1)) { + const parentSymbol = symbol; + const nextSymbol = chain[index + 1]; + if (getCheckFlags(nextSymbol) & CheckFlags.Instantiated) { + const params = getTypeParametersOfClassOrInterface( + parentSymbol.flags & SymbolFlags.Alias ? resolveAlias(parentSymbol) : parentSymbol + ); + typeParameterNodes = mapToTypeNodes(map(params, t => getMappedType(t, (nextSymbol as TransientSymbol).mapper!)), context); } - (context.typeParameterSymbolList || (context.typeParameterSymbolList = new Set())).add(symbolId); - let typeParameterNodes: readonly TypeNode[] | readonly TypeParameterDeclaration[] | undefined; - if (context.flags & NodeBuilderFlags.WriteTypeParametersInQualifiedName && index < (chain.length - 1)) { - const parentSymbol = symbol; - const nextSymbol = chain[index + 1]; - if (getCheckFlags(nextSymbol) & CheckFlags.Instantiated) { - const params = getTypeParametersOfClassOrInterface( - parentSymbol.flags & SymbolFlags.Alias ? resolveAlias(parentSymbol) : parentSymbol - ); - typeParameterNodes = mapToTypeNodes(map(params, t => getMappedType(t, (nextSymbol as TransientSymbol).mapper!)), context); - } - else { - typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context); - } + else { + typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context); } - return typeParameterNodes; } + return typeParameterNodes; + } - /** - * Given A[B][C][D], finds A[B] - */ - function getTopmostIndexedAccessType(top: IndexedAccessTypeNode): IndexedAccessTypeNode { - if (isIndexedAccessTypeNode(top.objectType)) { - return getTopmostIndexedAccessType(top.objectType); - } - return top; + /** + * Given A[B][C][D], finds A[B] + */ + function getTopmostIndexedAccessType(top: IndexedAccessTypeNode): IndexedAccessTypeNode { + if (isIndexedAccessTypeNode(top.objectType)) { + return getTopmostIndexedAccessType(top.objectType); } + return top; + } - function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext) { - let file = getDeclarationOfKind(symbol, SyntaxKind.SourceFile); - if (!file) { - const equivalentFileSymbol = firstDefined(symbol.declarations, d => getFileSymbolIfFileSymbolExportEqualsContainer(d, symbol)); - if (equivalentFileSymbol) { - file = getDeclarationOfKind(equivalentFileSymbol, SyntaxKind.SourceFile); - } - } - if (file && file.moduleName !== undefined) { - // Use the amd name if it is available - return file.moduleName; + function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext) { + let file = getDeclarationOfKind(symbol, SyntaxKind.SourceFile); + if (!file) { + const equivalentFileSymbol = firstDefined(symbol.declarations, d => getFileSymbolIfFileSymbolExportEqualsContainer(d, symbol)); + if (equivalentFileSymbol) { + file = getDeclarationOfKind(equivalentFileSymbol, SyntaxKind.SourceFile); } - if (!file) { - if (context.tracker.trackReferencedAmbientModule) { - const ambientDecls = filter(symbol.declarations, isAmbientModule); - if (length(ambientDecls)) { - for (const decl of ambientDecls!) { - context.tracker.trackReferencedAmbientModule(decl, symbol); - } + } + if (file && file.moduleName !== undefined) { + // Use the amd name if it is available + return file.moduleName; + } + if (!file) { + if (context.tracker.trackReferencedAmbientModule) { + const ambientDecls = filter(symbol.declarations, isAmbientModule); + if (length(ambientDecls)) { + for (const decl of ambientDecls!) { + context.tracker.trackReferencedAmbientModule(decl, symbol); } } - if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { - return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); + } + if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { + return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); + } + } + if (!context.enclosingDeclaration || !context.tracker.moduleResolverHost) { + // If there's no context declaration, we can't lookup a non-ambient specifier, so we just use the symbol name + if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { + return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); + } + return getSourceFileOfNode(getNonAugmentationDeclaration(symbol)!).fileName; // A resolver may not be provided for baselines and errors - in those cases we use the fileName in full + } + const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration)); + const links = getSymbolLinks(symbol); + let specifier = links.specifierCache && links.specifierCache.get(contextFile.path); + if (!specifier) { + const isBundle = !!outFile(compilerOptions); + // For declaration bundles, we need to generate absolute paths relative to the common source dir for imports, + // just like how the declaration emitter does for the ambient module declarations - we can easily accomplish this + // using the `baseUrl` compiler option (which we would otherwise never use in declaration emit) and a non-relative + // specifier preference + const { moduleResolverHost } = context.tracker; + const specifierCompilerOptions = isBundle ? { ...compilerOptions, baseUrl: moduleResolverHost.getCommonSourceDirectory() } : compilerOptions; + specifier = first(moduleSpecifiers.getModuleSpecifiers( + symbol, + checker, + specifierCompilerOptions, + contextFile, + moduleResolverHost, + { importModuleSpecifierPreference: isBundle ? "non-relative" : "project-relative", importModuleSpecifierEnding: isBundle ? "minimal" : undefined }, + )); + links.specifierCache ??= new Map(); + links.specifierCache.set(contextFile.path, specifier); + } + return specifier; + } + + function symbolToEntityNameNode(symbol: Symbol): EntityName { + const identifier = factory.createIdentifier(unescapeLeadingUnderscores(symbol.escapedName)); + return symbol.parent ? factory.createQualifiedName(symbolToEntityNameNode(symbol.parent), identifier) : identifier; + } + + function symbolToTypeNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, overrideTypeArguments?: readonly TypeNode[]): TypeNode { + const chain = lookupSymbolChain(symbol, context, meaning, !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope)); // If we're using aliases outside the current scope, dont bother with the module + + const isTypeOf = meaning === SymbolFlags.Value; + if (some(chain[0].declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { + // module is root, must use `ImportTypeNode` + const nonRootParts = chain.length > 1 ? createAccessFromSymbolChain(chain, chain.length - 1, 1) : undefined; + const typeParameterNodes = overrideTypeArguments || lookupTypeParameterNodes(chain, 0, context); + const specifier = getSpecifierForModuleSymbol(chain[0], context); + if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && specifier.indexOf("/node_modules/") >= 0) { + // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error + // since declaration files with these kinds of references are liable to fail when published :( + context.encounteredError = true; + if (context.tracker.reportLikelyUnsafeImportRequiredError) { + context.tracker.reportLikelyUnsafeImportRequiredError(specifier); } } - if (!context.enclosingDeclaration || !context.tracker.moduleResolverHost) { - // If there's no context declaration, we can't lookup a non-ambient specifier, so we just use the symbol name - if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { - return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); + const lit = factory.createLiteralTypeNode(factory.createStringLiteral(specifier)); + if (context.tracker.trackExternalModuleSymbolOfImportTypeNode) context.tracker.trackExternalModuleSymbolOfImportTypeNode(chain[0]); + context.approximateLength += specifier.length + 10; // specifier + import("") + if (!nonRootParts || isEntityName(nonRootParts)) { + if (nonRootParts) { + const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right; + lastId.typeArguments = undefined; } - return getSourceFileOfNode(getNonAugmentationDeclaration(symbol)!).fileName; // A resolver may not be provided for baselines and errors - in those cases we use the fileName in full + return factory.createImportTypeNode(lit, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf); } - const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration)); - const links = getSymbolLinks(symbol); - let specifier = links.specifierCache && links.specifierCache.get(contextFile.path); - if (!specifier) { - const isBundle = !!outFile(compilerOptions); - // For declaration bundles, we need to generate absolute paths relative to the common source dir for imports, - // just like how the declaration emitter does for the ambient module declarations - we can easily accomplish this - // using the `baseUrl` compiler option (which we would otherwise never use in declaration emit) and a non-relative - // specifier preference - const { moduleResolverHost } = context.tracker; - const specifierCompilerOptions = isBundle ? { ...compilerOptions, baseUrl: moduleResolverHost.getCommonSourceDirectory() } : compilerOptions; - specifier = first(moduleSpecifiers.getModuleSpecifiers( - symbol, - checker, - specifierCompilerOptions, - contextFile, - moduleResolverHost, - { importModuleSpecifierPreference: isBundle ? "non-relative" : "project-relative", importModuleSpecifierEnding: isBundle ? "minimal" : undefined }, - )); - links.specifierCache ??= new Map(); - links.specifierCache.set(contextFile.path, specifier); + else { + const splitNode = getTopmostIndexedAccessType(nonRootParts); + const qualifier = (splitNode.objectType as TypeReferenceNode).typeName; + return factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType); } - return specifier; } - function symbolToEntityNameNode(symbol: Symbol): EntityName { - const identifier = factory.createIdentifier(unescapeLeadingUnderscores(symbol.escapedName)); - return symbol.parent ? factory.createQualifiedName(symbolToEntityNameNode(symbol.parent), identifier) : identifier; + const entityName = createAccessFromSymbolChain(chain, chain.length - 1, 0); + if (isIndexedAccessTypeNode(entityName)) { + return entityName; // Indexed accesses can never be `typeof` + } + if (isTypeOf) { + return factory.createTypeQueryNode(entityName); + } + else { + const lastId = isIdentifier(entityName) ? entityName : entityName.right; + const lastTypeArgs = lastId.typeArguments; + lastId.typeArguments = undefined; + return factory.createTypeReferenceNode(entityName, lastTypeArgs as NodeArray); } - function symbolToTypeNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, overrideTypeArguments?: readonly TypeNode[]): TypeNode { - const chain = lookupSymbolChain(symbol, context, meaning, !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope)); // If we're using aliases outside the current scope, dont bother with the module - - const isTypeOf = meaning === SymbolFlags.Value; - if (some(chain[0].declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { - // module is root, must use `ImportTypeNode` - const nonRootParts = chain.length > 1 ? createAccessFromSymbolChain(chain, chain.length - 1, 1) : undefined; - const typeParameterNodes = overrideTypeArguments || lookupTypeParameterNodes(chain, 0, context); - const specifier = getSpecifierForModuleSymbol(chain[0], context); - if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && specifier.indexOf("/node_modules/") >= 0) { - // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error - // since declaration files with these kinds of references are liable to fail when published :( - context.encounteredError = true; - if (context.tracker.reportLikelyUnsafeImportRequiredError) { - context.tracker.reportLikelyUnsafeImportRequiredError(specifier); - } - } - const lit = factory.createLiteralTypeNode(factory.createStringLiteral(specifier)); - if (context.tracker.trackExternalModuleSymbolOfImportTypeNode) context.tracker.trackExternalModuleSymbolOfImportTypeNode(chain[0]); - context.approximateLength += specifier.length + 10; // specifier + import("") - if (!nonRootParts || isEntityName(nonRootParts)) { - if (nonRootParts) { - const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right; - lastId.typeArguments = undefined; - } - return factory.createImportTypeNode(lit, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf); - } - else { - const splitNode = getTopmostIndexedAccessType(nonRootParts); - const qualifier = (splitNode.objectType as TypeReferenceNode).typeName; - return factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType); - } - } + function createAccessFromSymbolChain(chain: Symbol[], index: number, stopper: number): EntityName | IndexedAccessTypeNode { + const typeParameterNodes = index === (chain.length - 1) ? overrideTypeArguments : lookupTypeParameterNodes(chain, index, context); + const symbol = chain[index]; - const entityName = createAccessFromSymbolChain(chain, chain.length - 1, 0); - if (isIndexedAccessTypeNode(entityName)) { - return entityName; // Indexed accesses can never be `typeof` - } - if (isTypeOf) { - return factory.createTypeQueryNode(entityName); + const parent = chain[index - 1]; + let symbolName: string | undefined; + if (index === 0) { + context.flags |= NodeBuilderFlags.InInitialEntityName; + symbolName = getNameOfSymbolAsWritten(symbol, context); + context.approximateLength += (symbolName ? symbolName.length : 0) + 1; + context.flags ^= NodeBuilderFlags.InInitialEntityName; } else { - const lastId = isIdentifier(entityName) ? entityName : entityName.right; - const lastTypeArgs = lastId.typeArguments; - lastId.typeArguments = undefined; - return factory.createTypeReferenceNode(entityName, lastTypeArgs as NodeArray); + if (parent && getExportsOfSymbol(parent)) { + const exports = getExportsOfSymbol(parent); + forEachEntry(exports, (ex, name) => { + if (getSymbolIfSameReference(ex, symbol) && !isLateBoundName(name) && name !== InternalSymbolName.ExportEquals) { + symbolName = unescapeLeadingUnderscores(name); + return true; + } + }); + } } + if (!symbolName) { + symbolName = getNameOfSymbolAsWritten(symbol, context); + } + context.approximateLength += symbolName.length + 1; - function createAccessFromSymbolChain(chain: Symbol[], index: number, stopper: number): EntityName | IndexedAccessTypeNode { - const typeParameterNodes = index === (chain.length - 1) ? overrideTypeArguments : lookupTypeParameterNodes(chain, index, context); - const symbol = chain[index]; - - const parent = chain[index - 1]; - let symbolName: string | undefined; - if (index === 0) { - context.flags |= NodeBuilderFlags.InInitialEntityName; - symbolName = getNameOfSymbolAsWritten(symbol, context); - context.approximateLength += (symbolName ? symbolName.length : 0) + 1; - context.flags ^= NodeBuilderFlags.InInitialEntityName; + if (!(context.flags & NodeBuilderFlags.ForbidIndexedAccessSymbolReferences) && parent && + getMembersOfSymbol(parent) && getMembersOfSymbol(parent).get(symbol.escapedName) && + getSymbolIfSameReference(getMembersOfSymbol(parent).get(symbol.escapedName)!, symbol)) { + // Should use an indexed access + const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); + if (isIndexedAccessTypeNode(LHS)) { + return factory.createIndexedAccessTypeNode(LHS, factory.createLiteralTypeNode(factory.createStringLiteral(symbolName))); } else { - if (parent && getExportsOfSymbol(parent)) { - const exports = getExportsOfSymbol(parent); - forEachEntry(exports, (ex, name) => { - if (getSymbolIfSameReference(ex, symbol) && !isLateBoundName(name) && name !== InternalSymbolName.ExportEquals) { - symbolName = unescapeLeadingUnderscores(name); - return true; - } - }); - } - } - if (!symbolName) { - symbolName = getNameOfSymbolAsWritten(symbol, context); - } - context.approximateLength += symbolName.length + 1; - - if (!(context.flags & NodeBuilderFlags.ForbidIndexedAccessSymbolReferences) && parent && - getMembersOfSymbol(parent) && getMembersOfSymbol(parent).get(symbol.escapedName) && - getSymbolIfSameReference(getMembersOfSymbol(parent).get(symbol.escapedName)!, symbol)) { - // Should use an indexed access - const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); - if (isIndexedAccessTypeNode(LHS)) { - return factory.createIndexedAccessTypeNode(LHS, factory.createLiteralTypeNode(factory.createStringLiteral(symbolName))); - } - else { - return factory.createIndexedAccessTypeNode(factory.createTypeReferenceNode(LHS, typeParameterNodes as readonly TypeNode[]), factory.createLiteralTypeNode(factory.createStringLiteral(symbolName))); - } + return factory.createIndexedAccessTypeNode(factory.createTypeReferenceNode(LHS, typeParameterNodes as readonly TypeNode[]), factory.createLiteralTypeNode(factory.createStringLiteral(symbolName))); } + } - const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); - identifier.symbol = symbol; + const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + identifier.symbol = symbol; - if (index > stopper) { - const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); - if (!isEntityName(LHS)) { - return Debug.fail("Impossible construct - an export of an indexed access cannot be reachable"); - } - return factory.createQualifiedName(LHS, identifier); + if (index > stopper) { + const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); + if (!isEntityName(LHS)) { + return Debug.fail("Impossible construct - an export of an indexed access cannot be reachable"); } - return identifier; + return factory.createQualifiedName(LHS, identifier); } + return identifier; } + } - function typeParameterShadowsNameInScope(escapedName: __String, context: NodeBuilderContext, type: TypeParameter) { - const result = resolveName(context.enclosingDeclaration, escapedName, SymbolFlags.Type, /*nameNotFoundArg*/ undefined, escapedName, /*isUse*/ false); - if (result) { - if (result.flags & SymbolFlags.TypeParameter && result === type.symbol) { - return false; - } - return true; + function typeParameterShadowsNameInScope(escapedName: __String, context: NodeBuilderContext, type: TypeParameter) { + const result = resolveName(context.enclosingDeclaration, escapedName, SymbolFlags.Type, /*nameNotFoundArg*/ undefined, escapedName, /*isUse*/ false); + if (result) { + if (result.flags & SymbolFlags.TypeParameter && result === type.symbol) { + return false; } - return false; + return true; } + return false; + } - function typeParameterToName(type: TypeParameter, context: NodeBuilderContext) { - if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) { - const cached = context.typeParameterNames.get(getTypeId(type)); - if (cached) { - return cached; - } + function typeParameterToName(type: TypeParameter, context: NodeBuilderContext) { + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) { + const cached = context.typeParameterNames.get(getTypeId(type)); + if (cached) { + return cached; } - let result = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true); - if (!(result.kind & SyntaxKind.Identifier)) { - return factory.createIdentifier("(Missing type parameter)"); + } + let result = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true); + if (!(result.kind & SyntaxKind.Identifier)) { + return factory.createIdentifier("(Missing type parameter)"); + } + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { + const rawtext = result.escapedText as string; + let i = context.typeParameterNamesByTextNextNameCount?.get(rawtext) || 0; + let text = rawtext; + while (context.typeParameterNamesByText?.has(text) || typeParameterShadowsNameInScope(text as __String, context, type)) { + i++; + text = `${rawtext}_${i}`; } - if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { - const rawtext = result.escapedText as string; - let i = context.typeParameterNamesByTextNextNameCount?.get(rawtext) || 0; - let text = rawtext; - while (context.typeParameterNamesByText?.has(text) || typeParameterShadowsNameInScope(text as __String, context, type)) { - i++; - text = `${rawtext}_${i}`; - } - if (text !== rawtext) { - result = factory.createIdentifier(text, result.typeArguments); - } - // avoiding iterations of the above loop turns out to be worth it when `i` starts to get large, so we cache the max - // `i` we've used thus far, to save work later - (context.typeParameterNamesByTextNextNameCount ||= new Map()).set(rawtext, i); - (context.typeParameterNames ||= new Map()).set(getTypeId(type), result); - (context.typeParameterNamesByText ||= new Set()).add(rawtext); + if (text !== rawtext) { + result = factory.createIdentifier(text, result.typeArguments); } - return result; + // avoiding iterations of the above loop turns out to be worth it when `i` starts to get large, so we cache the max + // `i` we've used thus far, to save work later + (context.typeParameterNamesByTextNextNameCount ||= new Map()).set(rawtext, i); + (context.typeParameterNames ||= new Map()).set(getTypeId(type), result); + (context.typeParameterNamesByText ||= new Set()).add(rawtext); } + return result; + } - function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: true): Identifier; - function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: false): EntityName; - function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: boolean): EntityName { - const chain = lookupSymbolChain(symbol, context, meaning); + function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: true): Identifier; + function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: false): EntityName; + function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: boolean): EntityName { + const chain = lookupSymbolChain(symbol, context, meaning); - if (expectsIdentifier && chain.length !== 1 - && !context.encounteredError - && !(context.flags & NodeBuilderFlags.AllowQualifiedNameInPlaceOfIdentifier)) { - context.encounteredError = true; - } - return createEntityNameFromSymbolChain(chain, chain.length - 1); + if (expectsIdentifier && chain.length !== 1 + && !context.encounteredError + && !(context.flags & NodeBuilderFlags.AllowQualifiedNameInPlaceOfIdentifier)) { + context.encounteredError = true; + } + return createEntityNameFromSymbolChain(chain, chain.length - 1); - function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { - const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); - const symbol = chain[index]; + function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { + const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); + const symbol = chain[index]; - if (index === 0) { - context.flags |= NodeBuilderFlags.InInitialEntityName; - } - const symbolName = getNameOfSymbolAsWritten(symbol, context); - if (index === 0) { - context.flags ^= NodeBuilderFlags.InInitialEntityName; - } + if (index === 0) { + context.flags |= NodeBuilderFlags.InInitialEntityName; + } + const symbolName = getNameOfSymbolAsWritten(symbol, context); + if (index === 0) { + context.flags ^= NodeBuilderFlags.InInitialEntityName; + } - const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); - identifier.symbol = symbol; + const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + identifier.symbol = symbol; - return index > 0 ? factory.createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; - } + return index > 0 ? factory.createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; } + } + + function symbolToExpression(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { + const chain = lookupSymbolChain(symbol, context, meaning); - function symbolToExpression(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { - const chain = lookupSymbolChain(symbol, context, meaning); + return createExpressionFromSymbolChain(chain, chain.length - 1); - return createExpressionFromSymbolChain(chain, chain.length - 1); + function createExpressionFromSymbolChain(chain: Symbol[], index: number): Expression { + const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); + const symbol = chain[index]; - function createExpressionFromSymbolChain(chain: Symbol[], index: number): Expression { - const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); - const symbol = chain[index]; + if (index === 0) { + context.flags |= NodeBuilderFlags.InInitialEntityName; + } + let symbolName = getNameOfSymbolAsWritten(symbol, context); + if (index === 0) { + context.flags ^= NodeBuilderFlags.InInitialEntityName; + } + let firstChar = symbolName.charCodeAt(0); - if (index === 0) { - context.flags |= NodeBuilderFlags.InInitialEntityName; - } - let symbolName = getNameOfSymbolAsWritten(symbol, context); - if (index === 0) { - context.flags ^= NodeBuilderFlags.InInitialEntityName; - } - let firstChar = symbolName.charCodeAt(0); + if (isSingleOrDoubleQuote(firstChar) && some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { + return factory.createStringLiteral(getSpecifierForModuleSymbol(symbol, context)); + } + const canUsePropertyAccess = firstChar === CharacterCodes.hash ? + symbolName.length > 1 && isIdentifierStart(symbolName.charCodeAt(1), languageVersion) : + isIdentifierStart(firstChar, languageVersion); + if (index === 0 || canUsePropertyAccess) { + const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + identifier.symbol = symbol; - if (isSingleOrDoubleQuote(firstChar) && some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { - return factory.createStringLiteral(getSpecifierForModuleSymbol(symbol, context)); + return index > 0 ? factory.createPropertyAccessExpression(createExpressionFromSymbolChain(chain, index - 1), identifier) : identifier; + } + else { + if (firstChar === CharacterCodes.openBracket) { + symbolName = symbolName.substring(1, symbolName.length - 1); + firstChar = symbolName.charCodeAt(0); } - const canUsePropertyAccess = firstChar === CharacterCodes.hash ? - symbolName.length > 1 && isIdentifierStart(symbolName.charCodeAt(1), languageVersion) : - isIdentifierStart(firstChar, languageVersion); - if (index === 0 || canUsePropertyAccess) { - const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); - identifier.symbol = symbol; - - return index > 0 ? factory.createPropertyAccessExpression(createExpressionFromSymbolChain(chain, index - 1), identifier) : identifier; + let expression: Expression | undefined; + if (isSingleOrDoubleQuote(firstChar) && !(symbol.flags & SymbolFlags.EnumMember)) { + expression = factory.createStringLiteral(stripQuotes(symbolName).replace(/\\./g, s => s.substring(1)), firstChar === CharacterCodes.singleQuote); } - else { - if (firstChar === CharacterCodes.openBracket) { - symbolName = symbolName.substring(1, symbolName.length - 1); - firstChar = symbolName.charCodeAt(0); - } - let expression: Expression | undefined; - if (isSingleOrDoubleQuote(firstChar) && !(symbol.flags & SymbolFlags.EnumMember)) { - expression = factory.createStringLiteral(stripQuotes(symbolName).replace(/\\./g, s => s.substring(1)), firstChar === CharacterCodes.singleQuote); - } - else if (("" + +symbolName) === symbolName) { - expression = factory.createNumericLiteral(+symbolName); - } - if (!expression) { - expression = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); - expression.symbol = symbol; - } - return factory.createElementAccessExpression(createExpressionFromSymbolChain(chain, index - 1), expression); + else if (("" + +symbolName) === symbolName) { + expression = factory.createNumericLiteral(+symbolName); } + if (!expression) { + expression = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + expression.symbol = symbol; + } + return factory.createElementAccessExpression(createExpressionFromSymbolChain(chain, index - 1), expression); } } + } - function isStringNamed(d: Declaration) { - const name = getNameOfDeclaration(d); - return !!name && isStringLiteral(name); - } + function isStringNamed(d: Declaration) { + const name = getNameOfDeclaration(d); + return !!name && isStringLiteral(name); + } - function isSingleQuotedStringNamed(d: Declaration) { - const name = getNameOfDeclaration(d); - return !!(name && isStringLiteral(name) && (name.singleQuote || !nodeIsSynthesized(name) && startsWith(getTextOfNode(name, /*includeTrivia*/ false), "'"))); - } + function isSingleQuotedStringNamed(d: Declaration) { + const name = getNameOfDeclaration(d); + return !!(name && isStringLiteral(name) && (name.singleQuote || !nodeIsSynthesized(name) && startsWith(getTextOfNode(name, /*includeTrivia*/ false), "'"))); + } - function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) { - const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed); - const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote); - if (fromNameType) { - return fromNameType; - } - const rawName = unescapeLeadingUnderscores(symbol.escapedName); - const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed); - return createPropertyNameNodeForIdentifierOrLiteral(rawName, stringNamed, singleQuote); + function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) { + const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed); + const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote); + if (fromNameType) { + return fromNameType; } + const rawName = unescapeLeadingUnderscores(symbol.escapedName); + const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed); + return createPropertyNameNodeForIdentifierOrLiteral(rawName, stringNamed, singleQuote); + } - // See getNameForSymbolFromNameType for a stringy equivalent - function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext, singleQuote?: boolean) { - const nameType = getSymbolLinks(symbol).nameType; - if (nameType) { - if (nameType.flags & TypeFlags.StringOrNumberLiteral) { - const name = "" + (nameType as StringLiteralType | NumberLiteralType).value; - if (!isIdentifierText(name, getEmitScriptTarget(compilerOptions)) && !isNumericLiteralName(name)) { - return factory.createStringLiteral(name, !!singleQuote); - } - if (isNumericLiteralName(name) && startsWith(name, "-")) { - return factory.createComputedPropertyName(factory.createNumericLiteral(+name)); - } - return createPropertyNameNodeForIdentifierOrLiteral(name); + // See getNameForSymbolFromNameType for a stringy equivalent + function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext, singleQuote?: boolean) { + const nameType = getSymbolLinks(symbol).nameType; + if (nameType) { + if (nameType.flags & TypeFlags.StringOrNumberLiteral) { + const name = "" + (nameType as StringLiteralType | NumberLiteralType).value; + if (!isIdentifierText(name, getEmitScriptTarget(compilerOptions)) && !isNumericLiteralName(name)) { + return factory.createStringLiteral(name, !!singleQuote); } - if (nameType.flags & TypeFlags.UniqueESSymbol) { - return factory.createComputedPropertyName(symbolToExpression((nameType as UniqueESSymbolType).symbol, context, SymbolFlags.Value)); + if (isNumericLiteralName(name) && startsWith(name, "-")) { + return factory.createComputedPropertyName(factory.createNumericLiteral(+name)); } + return createPropertyNameNodeForIdentifierOrLiteral(name); + } + if (nameType.flags & TypeFlags.UniqueESSymbol) { + return factory.createComputedPropertyName(symbolToExpression((nameType as UniqueESSymbolType).symbol, context, SymbolFlags.Value)); } } + } - function createPropertyNameNodeForIdentifierOrLiteral(name: string, stringNamed?: boolean, singleQuote?: boolean) { - return isIdentifierText(name, getEmitScriptTarget(compilerOptions)) ? factory.createIdentifier(name) : - !stringNamed && isNumericLiteralName(name) && +name >= 0 ? factory.createNumericLiteral(+name) : - factory.createStringLiteral(name, !!singleQuote); - } + function createPropertyNameNodeForIdentifierOrLiteral(name: string, stringNamed?: boolean, singleQuote?: boolean) { + return isIdentifierText(name, getEmitScriptTarget(compilerOptions)) ? factory.createIdentifier(name) : + !stringNamed && isNumericLiteralName(name) && +name >= 0 ? factory.createNumericLiteral(+name) : + factory.createStringLiteral(name, !!singleQuote); + } - function cloneNodeBuilderContext(context: NodeBuilderContext): NodeBuilderContext { - const initial: NodeBuilderContext = { ...context }; - // Make type parameters created within this context not consume the name outside this context - // The symbol serializer ends up creating many sibling scopes that all need "separate" contexts when - // it comes to naming things - within a normal `typeToTypeNode` call, the node builder only ever descends - // through the type tree, so the only cases where we could have used distinct sibling scopes was when there - // were multiple generic overloads with similar generated type parameter names - // The effect: - // When we write out - // export const x: (x: T) => T - // export const y: (x: T) => T - // we write it out like that, rather than as - // export const x: (x: T) => T - // export const y: (x: T_1) => T_1 - if (initial.typeParameterNames) { - initial.typeParameterNames = new Map(initial.typeParameterNames); - } - if (initial.typeParameterNamesByText) { - initial.typeParameterNamesByText = new Set(initial.typeParameterNamesByText); - } - if (initial.typeParameterSymbolList) { - initial.typeParameterSymbolList = new Set(initial.typeParameterSymbolList); - } - initial.tracker = wrapSymbolTrackerToReportForContext(initial, initial.tracker); - return initial; + function cloneNodeBuilderContext(context: NodeBuilderContext): NodeBuilderContext { + const initial: NodeBuilderContext = { ...context }; + // Make type parameters created within this context not consume the name outside this context + // The symbol serializer ends up creating many sibling scopes that all need "separate" contexts when + // it comes to naming things - within a normal `typeToTypeNode` call, the node builder only ever descends + // through the type tree, so the only cases where we could have used distinct sibling scopes was when there + // were multiple generic overloads with similar generated type parameter names + // The effect: + // When we write out + // export const x: (x: T) => T + // export const y: (x: T) => T + // we write it out like that, rather than as + // export const x: (x: T) => T + // export const y: (x: T_1) => T_1 + if (initial.typeParameterNames) { + initial.typeParameterNames = new Map(initial.typeParameterNames); + } + if (initial.typeParameterNamesByText) { + initial.typeParameterNamesByText = new Set(initial.typeParameterNamesByText); } + if (initial.typeParameterSymbolList) { + initial.typeParameterSymbolList = new Set(initial.typeParameterSymbolList); + } + initial.tracker = wrapSymbolTrackerToReportForContext(initial, initial.tracker); + return initial; + } - function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration: Node | undefined) { - return symbol.declarations && find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration))); - } + function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration: Node | undefined) { + return symbol.declarations && find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration))); + } - function existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing: TypeNode, type: Type) { - return !(getObjectFlags(type) & ObjectFlags.Reference) || !isTypeReferenceNode(existing) || length(existing.typeArguments) >= getMinTypeArgumentCount((type as TypeReference).target.typeParameters); - } + function existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing: TypeNode, type: Type) { + return !(getObjectFlags(type) & ObjectFlags.Reference) || !isTypeReferenceNode(existing) || length(existing.typeArguments) >= getMinTypeArgumentCount((type as TypeReference).target.typeParameters); + } - /** - * Unlike `typeToTypeNodeHelper`, this handles setting up the `AllowUniqueESSymbolType` flag - * so a `unique symbol` is returned when appropriate for the input symbol, rather than `typeof sym` - */ - function serializeTypeForDeclaration(context: NodeBuilderContext, type: Type, symbol: Symbol, enclosingDeclaration: Node | undefined, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { - if (!isErrorType(type) && enclosingDeclaration) { - const declWithExistingAnnotation = getDeclarationWithTypeAnnotation(symbol, enclosingDeclaration); - if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation) && !isGetAccessorDeclaration(declWithExistingAnnotation)) { - // try to reuse the existing annotation - const existing = getEffectiveTypeAnnotationNode(declWithExistingAnnotation)!; - if (getTypeFromTypeNode(existing) === type && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) { - const result = serializeExistingTypeNode(context, existing, includePrivateSymbol, bundled); - if (result) { - return result; - } + /** + * Unlike `typeToTypeNodeHelper`, this handles setting up the `AllowUniqueESSymbolType` flag + * so a `unique symbol` is returned when appropriate for the input symbol, rather than `typeof sym` + */ + function serializeTypeForDeclaration(context: NodeBuilderContext, type: Type, symbol: Symbol, enclosingDeclaration: Node | undefined, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { + if (!isErrorType(type) && enclosingDeclaration) { + const declWithExistingAnnotation = getDeclarationWithTypeAnnotation(symbol, enclosingDeclaration); + if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation) && !isGetAccessorDeclaration(declWithExistingAnnotation)) { + // try to reuse the existing annotation + const existing = getEffectiveTypeAnnotationNode(declWithExistingAnnotation)!; + if (getTypeFromTypeNode(existing) === type && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) { + const result = serializeExistingTypeNode(context, existing, includePrivateSymbol, bundled); + if (result) { + return result; } } } - const oldFlags = context.flags; - if (type.flags & TypeFlags.UniqueESSymbol && - type.symbol === symbol && (!context.enclosingDeclaration || some(symbol.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!)))) { - context.flags |= NodeBuilderFlags.AllowUniqueESSymbolType; - } - const result = typeToTypeNodeHelper(type, context); - context.flags = oldFlags; - return result; } + const oldFlags = context.flags; + if (type.flags & TypeFlags.UniqueESSymbol && + type.symbol === symbol && (!context.enclosingDeclaration || some(symbol.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!)))) { + context.flags |= NodeBuilderFlags.AllowUniqueESSymbolType; + } + const result = typeToTypeNodeHelper(type, context); + context.flags = oldFlags; + return result; + } - function serializeReturnTypeForSignature(context: NodeBuilderContext, type: Type, signature: Signature, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { - if (!isErrorType(type) && context.enclosingDeclaration) { - const annotation = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); - if (!!findAncestor(annotation, n => n === context.enclosingDeclaration) && annotation) { - const annotated = getTypeFromTypeNode(annotation); - const thisInstantiated = annotated.flags & TypeFlags.TypeParameter && (annotated as TypeParameter).isThisType ? instantiateType(annotated, signature.mapper) : annotated; - if (thisInstantiated === type && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(annotation, type)) { - const result = serializeExistingTypeNode(context, annotation, includePrivateSymbol, bundled); - if (result) { - return result; - } + function serializeReturnTypeForSignature(context: NodeBuilderContext, type: Type, signature: Signature, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { + if (!isErrorType(type) && context.enclosingDeclaration) { + const annotation = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); + if (!!findAncestor(annotation, n => n === context.enclosingDeclaration) && annotation) { + const annotated = getTypeFromTypeNode(annotation); + const thisInstantiated = annotated.flags & TypeFlags.TypeParameter && (annotated as TypeParameter).isThisType ? instantiateType(annotated, signature.mapper) : annotated; + if (thisInstantiated === type && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(annotation, type)) { + const result = serializeExistingTypeNode(context, annotation, includePrivateSymbol, bundled); + if (result) { + return result; } } } - return typeToTypeNodeHelper(type, context); } + return typeToTypeNodeHelper(type, context); + } - function trackExistingEntityName(node: T, context: NodeBuilderContext, includePrivateSymbol?: (s: Symbol) => void) { - let introducesError = false; - const leftmost = getFirstIdentifier(node); - if (isInJSFile(node) && (isExportsIdentifier(leftmost) || isModuleExportsAccessExpression(leftmost.parent) || (isQualifiedName(leftmost.parent) && isModuleIdentifier(leftmost.parent.left) && isExportsIdentifier(leftmost.parent.right)))) { + function trackExistingEntityName(node: T, context: NodeBuilderContext, includePrivateSymbol?: (s: Symbol) => void) { + let introducesError = false; + const leftmost = getFirstIdentifier(node); + if (isInJSFile(node) && (isExportsIdentifier(leftmost) || isModuleExportsAccessExpression(leftmost.parent) || (isQualifiedName(leftmost.parent) && isModuleIdentifier(leftmost.parent.left) && isExportsIdentifier(leftmost.parent.right)))) { + introducesError = true; + return { introducesError, node }; + } + const sym = resolveEntityName(leftmost, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveALias*/ true); + if (sym) { + if (isSymbolAccessible(sym, context.enclosingDeclaration, SymbolFlags.All, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { introducesError = true; - return { introducesError, node }; } - const sym = resolveEntityName(leftmost, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveALias*/ true); - if (sym) { - if (isSymbolAccessible(sym, context.enclosingDeclaration, SymbolFlags.All, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { - introducesError = true; - } - else { - context.tracker?.trackSymbol?.(sym, context.enclosingDeclaration, SymbolFlags.All); - includePrivateSymbol?.(sym); - } - if (isIdentifier(node)) { - const name = sym.flags & SymbolFlags.TypeParameter ? typeParameterToName(getDeclaredTypeOfSymbol(sym), context) : factory.cloneNode(node); - name.symbol = sym; // for quickinfo, which uses identifier symbol information - return { introducesError, node: setEmitFlags(setOriginalNode(name, node), EmitFlags.NoAsciiEscaping) }; - } + else { + context.tracker?.trackSymbol?.(sym, context.enclosingDeclaration, SymbolFlags.All); + includePrivateSymbol?.(sym); + } + if (isIdentifier(node)) { + const name = sym.flags & SymbolFlags.TypeParameter ? typeParameterToName(getDeclaredTypeOfSymbol(sym), context) : factory.cloneNode(node); + name.symbol = sym; // for quickinfo, which uses identifier symbol information + return { introducesError, node: setEmitFlags(setOriginalNode(name, node), EmitFlags.NoAsciiEscaping) }; } + } - return { introducesError, node }; + return { introducesError, node }; + } + + function serializeExistingTypeNode(context: NodeBuilderContext, existing: TypeNode, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { + if (cancellationToken && cancellationToken.throwIfCancellationRequested) { + cancellationToken.throwIfCancellationRequested(); + } + let hadError = false; + const file = getSourceFileOfNode(existing); + const transformed = visitNode(existing, visitExistingNodeTreeSymbols); + if (hadError) { + return undefined; } + return transformed === existing ? setTextRange(factory.cloneNode(existing), existing) : transformed; - function serializeExistingTypeNode(context: NodeBuilderContext, existing: TypeNode, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { - if (cancellationToken && cancellationToken.throwIfCancellationRequested) { - cancellationToken.throwIfCancellationRequested(); + function visitExistingNodeTreeSymbols(node: T): Node { + // We don't _actually_ support jsdoc namepath types, emit `any` instead + if (isJSDocAllType(node) || node.kind === SyntaxKind.JSDocNamepathType) { + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } - let hadError = false; - const file = getSourceFileOfNode(existing); - const transformed = visitNode(existing, visitExistingNodeTreeSymbols); - if (hadError) { - return undefined; + if (isJSDocUnknownType(node)) { + return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword); } - return transformed === existing ? setTextRange(factory.cloneNode(existing), existing) : transformed; - - function visitExistingNodeTreeSymbols(node: T): Node { - // We don't _actually_ support jsdoc namepath types, emit `any` instead - if (isJSDocAllType(node) || node.kind === SyntaxKind.JSDocNamepathType) { - return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - if (isJSDocUnknownType(node)) { - return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword); - } - if (isJSDocNullableType(node)) { - return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), factory.createLiteralTypeNode(factory.createNull())]); - } - if (isJSDocOptionalType(node)) { - return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); - } - if (isJSDocNonNullableType(node)) { - return visitNode(node.type, visitExistingNodeTreeSymbols); - } - if (isJSDocVariadicType(node)) { - return factory.createArrayTypeNode(visitNode((node as JSDocVariadicType).type, visitExistingNodeTreeSymbols)); - } - if (isJSDocTypeLiteral(node)) { - return factory.createTypeLiteralNode(map(node.jsDocPropertyTags, t => { - const name = isIdentifier(t.name) ? t.name : t.name.right; - const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(node), name.escapedText); - const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined; + if (isJSDocNullableType(node)) { + return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), factory.createLiteralTypeNode(factory.createNull())]); + } + if (isJSDocOptionalType(node)) { + return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + } + if (isJSDocNonNullableType(node)) { + return visitNode(node.type, visitExistingNodeTreeSymbols); + } + if (isJSDocVariadicType(node)) { + return factory.createArrayTypeNode(visitNode((node as JSDocVariadicType).type, visitExistingNodeTreeSymbols)); + } + if (isJSDocTypeLiteral(node)) { + return factory.createTypeLiteralNode(map(node.jsDocPropertyTags, t => { + const name = isIdentifier(t.name) ? t.name : t.name.right; + const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(node), name.escapedText); + const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined; - return factory.createPropertySignature( - /*modifiers*/ undefined, - name, - t.isBracketed || t.typeExpression && isJSDocOptionalType(t.typeExpression.type) ? factory.createToken(SyntaxKind.QuestionToken) : undefined, - overrideTypeNode || (t.typeExpression && visitNode(t.typeExpression.type, visitExistingNodeTreeSymbols)) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ); - })); - } - if (isTypeReferenceNode(node) && isIdentifier(node.typeName) && node.typeName.escapedText === "") { - return setOriginalNode(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), node); - } - if ((isExpressionWithTypeArguments(node) || isTypeReferenceNode(node)) && isJSDocIndexSignature(node)) { - return factory.createTypeLiteralNode([factory.createIndexSignature( + return factory.createPropertySignature( + /*modifiers*/ undefined, + name, + t.isBracketed || t.typeExpression && isJSDocOptionalType(t.typeExpression.type) ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + overrideTypeNode || (t.typeExpression && visitNode(t.typeExpression.type, visitExistingNodeTreeSymbols)) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ); + })); + } + if (isTypeReferenceNode(node) && isIdentifier(node.typeName) && node.typeName.escapedText === "") { + return setOriginalNode(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), node); + } + if ((isExpressionWithTypeArguments(node) || isTypeReferenceNode(node)) && isJSDocIndexSignature(node)) { + return factory.createTypeLiteralNode([factory.createIndexSignature( + /*decorators*/ undefined, + /*modifiers*/ undefined, + [factory.createParameterDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, - [factory.createParameterDeclaration( + /*dotdotdotToken*/ undefined, + "x", + /*questionToken*/ undefined, + visitNode(node.typeArguments![0], visitExistingNodeTreeSymbols) + )], + visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols) + )]); + } + if (isJSDocFunctionType(node)) { + if (isJSDocConstructSignature(node)) { + let newTypeNode: TypeNode | undefined; + return factory.createConstructorTypeNode( + node.modifiers, + visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), + mapDefined(node.parameters, (p, i) => p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : factory.createParameterDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, - /*dotdotdotToken*/ undefined, - "x", - /*questionToken*/ undefined, - visitNode(node.typeArguments![0], visitExistingNodeTreeSymbols) - )], - visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols) - )]); - } - if (isJSDocFunctionType(node)) { - if (isJSDocConstructSignature(node)) { - let newTypeNode: TypeNode | undefined; - return factory.createConstructorTypeNode( - node.modifiers, - visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), - mapDefined(node.parameters, (p, i) => p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - getEffectiveDotDotDotForParameter(p), - getNameForJSDocFunctionParameter(p, i), - p.questionToken, - visitNode(p.type, visitExistingNodeTreeSymbols), - /*initializer*/ undefined - )), - visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ); - } - else { - return factory.createFunctionTypeNode( - visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), - map(node.parameters, (p, i) => factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - getEffectiveDotDotDotForParameter(p), - getNameForJSDocFunctionParameter(p, i), - p.questionToken, - visitNode(p.type, visitExistingNodeTreeSymbols), - /*initializer*/ undefined - )), - visitNode(node.type, visitExistingNodeTreeSymbols) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ); - } - } - if (isTypeReferenceNode(node) && isInJSDoc(node) && (!existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(node, getTypeFromTypeNode(node)) || getIntendedTypeFromJSDocTypeReference(node) || unknownSymbol === resolveTypeReferenceName(node, SymbolFlags.Type, /*ignoreErrors*/ true))) { - return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node); + getEffectiveDotDotDotForParameter(p), + getNameForJSDocFunctionParameter(p, i), + p.questionToken, + visitNode(p.type, visitExistingNodeTreeSymbols), + /*initializer*/ undefined + )), + visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ); } - if (isLiteralImportTypeNode(node)) { - const nodeSymbol = getNodeLinks(node).resolvedSymbol; - if (isInJSDoc(node) && - nodeSymbol && - ( - // The import type resolved using jsdoc fallback logic - (!node.isTypeOf && !(nodeSymbol.flags & SymbolFlags.Type)) || - // The import type had type arguments autofilled by js fallback logic - !(length(node.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol))) - ) - ) { - return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node); - } - return factory.updateImportTypeNode( - node, - factory.updateLiteralTypeNode(node.argument, rewriteModuleSpecifier(node, node.argument.literal)), - node.qualifier, - visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), - node.isTypeOf + else { + return factory.createFunctionTypeNode( + visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), + map(node.parameters, (p, i) => factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + getEffectiveDotDotDotForParameter(p), + getNameForJSDocFunctionParameter(p, i), + p.questionToken, + visitNode(p.type, visitExistingNodeTreeSymbols), + /*initializer*/ undefined + )), + visitNode(node.type, visitExistingNodeTreeSymbols) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) ); } - - if (isEntityName(node) || isEntityNameExpression(node)) { - const { introducesError, node: result } = trackExistingEntityName(node, context, includePrivateSymbol); - hadError = hadError || introducesError; - if (result !== node) { - return result; - } + } + if (isTypeReferenceNode(node) && isInJSDoc(node) && (!existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(node, getTypeFromTypeNode(node)) || getIntendedTypeFromJSDocTypeReference(node) || unknownSymbol === resolveTypeReferenceName(node, SymbolFlags.Type, /*ignoreErrors*/ true))) { + return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node); + } + if (isLiteralImportTypeNode(node)) { + const nodeSymbol = getNodeLinks(node).resolvedSymbol; + if (isInJSDoc(node) && + nodeSymbol && + ( + // The import type resolved using jsdoc fallback logic + (!node.isTypeOf && !(nodeSymbol.flags & SymbolFlags.Type)) || + // The import type had type arguments autofilled by js fallback logic + !(length(node.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol))) + ) + ) { + return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node); } + return factory.updateImportTypeNode( + node, + factory.updateLiteralTypeNode(node.argument, rewriteModuleSpecifier(node, node.argument.literal)), + node.qualifier, + visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), + node.isTypeOf + ); + } - if (file && isTupleTypeNode(node) && (getLineAndCharacterOfPosition(file, node.pos).line === getLineAndCharacterOfPosition(file, node.end).line)) { - setEmitFlags(node, EmitFlags.SingleLine); + if (isEntityName(node) || isEntityNameExpression(node)) { + const { introducesError, node: result } = trackExistingEntityName(node, context, includePrivateSymbol); + hadError = hadError || introducesError; + if (result !== node) { + return result; } + } - return visitEachChild(node, visitExistingNodeTreeSymbols, nullTransformationContext); + if (file && isTupleTypeNode(node) && (getLineAndCharacterOfPosition(file, node.pos).line === getLineAndCharacterOfPosition(file, node.end).line)) { + setEmitFlags(node, EmitFlags.SingleLine); + } - function getEffectiveDotDotDotForParameter(p: ParameterDeclaration) { - return p.dotDotDotToken || (p.type && isJSDocVariadicType(p.type) ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined); - } + return visitEachChild(node, visitExistingNodeTreeSymbols, nullTransformationContext); - /** Note that `new:T` parameters are not handled, but should be before calling this function. */ - function getNameForJSDocFunctionParameter(p: ParameterDeclaration, index: number) { - return p.name && isIdentifier(p.name) && p.name.escapedText === "this" ? "this" - : getEffectiveDotDotDotForParameter(p) ? `args` - : `arg${index}`; - } + function getEffectiveDotDotDotForParameter(p: ParameterDeclaration) { + return p.dotDotDotToken || (p.type && isJSDocVariadicType(p.type) ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined); + } - function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) { - if (bundled) { - if (context.tracker && context.tracker.moduleResolverHost) { - const targetFile = getExternalModuleFileFromDeclaration(parent); - if (targetFile) { - const getCanonicalFileName = createGetCanonicalFileName(!!host.useCaseSensitiveFileNames); - const resolverHost = { - getCanonicalFileName, - getCurrentDirectory: () => context.tracker.moduleResolverHost!.getCurrentDirectory(), - getCommonSourceDirectory: () => context.tracker.moduleResolverHost!.getCommonSourceDirectory() - }; - const newName = getResolvedExternalModuleName(resolverHost, targetFile); - return factory.createStringLiteral(newName); - } + /** Note that `new:T` parameters are not handled, but should be before calling this function. */ + function getNameForJSDocFunctionParameter(p: ParameterDeclaration, index: number) { + return p.name && isIdentifier(p.name) && p.name.escapedText === "this" ? "this" + : getEffectiveDotDotDotForParameter(p) ? `args` + : `arg${index}`; + } + + function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) { + if (bundled) { + if (context.tracker && context.tracker.moduleResolverHost) { + const targetFile = getExternalModuleFileFromDeclaration(parent); + if (targetFile) { + const getCanonicalFileName = createGetCanonicalFileName(!!host.useCaseSensitiveFileNames); + const resolverHost = { + getCanonicalFileName, + getCurrentDirectory: () => context.tracker.moduleResolverHost!.getCurrentDirectory(), + getCommonSourceDirectory: () => context.tracker.moduleResolverHost!.getCommonSourceDirectory() + }; + const newName = getResolvedExternalModuleName(resolverHost, targetFile); + return factory.createStringLiteral(newName); } } - else { - if (context.tracker && context.tracker.trackExternalModuleSymbolOfImportTypeNode) { - const moduleSym = resolveExternalModuleNameWorker(lit, lit, /*moduleNotFoundError*/ undefined); - if (moduleSym) { - context.tracker.trackExternalModuleSymbolOfImportTypeNode(moduleSym); - } + } + else { + if (context.tracker && context.tracker.trackExternalModuleSymbolOfImportTypeNode) { + const moduleSym = resolveExternalModuleNameWorker(lit, lit, /*moduleNotFoundError*/ undefined); + if (moduleSym) { + context.tracker.trackExternalModuleSymbolOfImportTypeNode(moduleSym); } } - return lit; - } - } - } - - function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext, bundled?: boolean): Statement[] { - const serializePropertySymbolForClass = makeSerializePropertySymbol(factory.createPropertyDeclaration, SyntaxKind.MethodDeclaration, /*useAcessors*/ true); - const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((_decorators, mods, name, question, type) => factory.createPropertySignature(mods, name, question, type), SyntaxKind.MethodSignature, /*useAcessors*/ false); - - // TODO: Use `setOriginalNode` on original declaration names where possible so these declarations see some kind of - // declaration mapping - - // We save the enclosing declaration off here so it's not adjusted by well-meaning declaration - // emit codepaths which want to apply more specific contexts (so we can still refer to the root real declaration - // we're trying to emit from later on) - const enclosingDeclaration = context.enclosingDeclaration!; - let results: Statement[] = []; - const visitedSymbols = new Set(); - const deferredPrivatesStack: ESMap[] = []; - const oldcontext = context; - context = { - ...oldcontext, - usedSymbolNames: new Set(oldcontext.usedSymbolNames), - remappedSymbolNames: new Map(), - tracker: { - ...oldcontext.tracker, - trackSymbol: (sym, decl, meaning) => { - const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*computeAliases*/ false); - if (accessibleResult.accessibility === SymbolAccessibility.Accessible) { - // Lookup the root symbol of the chain of refs we'll use to access it and serialize it - const chain = lookupSymbolChainWorker(sym, context, meaning); - if (!(sym.flags & SymbolFlags.Property)) { - includePrivateSymbol(chain[0]); - } - } - else if (oldcontext.tracker && oldcontext.tracker.trackSymbol) { - return oldcontext.tracker.trackSymbol(sym, decl, meaning); + } + return lit; + } + } + } + + function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext, bundled?: boolean): Statement[] { + const serializePropertySymbolForClass = makeSerializePropertySymbol(factory.createPropertyDeclaration, SyntaxKind.MethodDeclaration, /*useAcessors*/ true); + const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((_decorators, mods, name, question, type) => factory.createPropertySignature(mods, name, question, type), SyntaxKind.MethodSignature, /*useAcessors*/ false); + + // TODO: Use `setOriginalNode` on original declaration names where possible so these declarations see some kind of + // declaration mapping + + // We save the enclosing declaration off here so it's not adjusted by well-meaning declaration + // emit codepaths which want to apply more specific contexts (so we can still refer to the root real declaration + // we're trying to emit from later on) + const enclosingDeclaration = context.enclosingDeclaration!; + let results: Statement[] = []; + const visitedSymbols = new Set(); + const deferredPrivatesStack: ESMap[] = []; + const oldcontext = context; + context = { + ...oldcontext, + usedSymbolNames: new Set(oldcontext.usedSymbolNames), + remappedSymbolNames: new Map(), + tracker: { + ...oldcontext.tracker, + trackSymbol: (sym, decl, meaning) => { + const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*computeAliases*/ false); + if (accessibleResult.accessibility === SymbolAccessibility.Accessible) { + // Lookup the root symbol of the chain of refs we'll use to access it and serialize it + const chain = lookupSymbolChainWorker(sym, context, meaning); + if (!(sym.flags & SymbolFlags.Property)) { + includePrivateSymbol(chain[0]); } - return false; - }, - }, - }; - context.tracker = wrapSymbolTrackerToReportForContext(context, context.tracker); - forEachEntry(symbolTable, (symbol, name) => { - const baseName = unescapeLeadingUnderscores(name); - void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` - }); - let addingDeclare = !bundled; - const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals); - if (exportEquals && symbolTable.size > 1 && exportEquals.flags & SymbolFlags.Alias) { - symbolTable = createSymbolTable(); - // Remove extraneous elements from root symbol table (they'll be mixed back in when the target of the `export=` is looked up) - symbolTable.set(InternalSymbolName.ExportEquals, exportEquals); - } - - visitSymbolTable(symbolTable); - return mergeRedundantStatements(results); - - function isIdentifierAndNotUndefined(node: Node | undefined): node is Identifier { - return !!node && node.kind === SyntaxKind.Identifier; - } - - function getNamesOfDeclaration(statement: Statement): Identifier[] { - if (isVariableStatement(statement)) { - return filter(map(statement.declarationList.declarations, getNameOfDeclaration), isIdentifierAndNotUndefined); - } - return filter([getNameOfDeclaration(statement as DeclarationStatement)], isIdentifierAndNotUndefined); - } - - function flattenExportAssignedNamespace(statements: Statement[]) { - const exportAssignment = find(statements, isExportAssignment); - const nsIndex = findIndex(statements, isModuleDeclaration); - let ns = nsIndex !== -1 ? statements[nsIndex] as ModuleDeclaration : undefined; - if (ns && exportAssignment && exportAssignment.isExportEquals && - isIdentifier(exportAssignment.expression) && isIdentifier(ns.name) && idText(ns.name) === idText(exportAssignment.expression) && - ns.body && isModuleBlock(ns.body)) { - // Pass 0: Correct situations where a module has both an `export = ns` and multiple top-level exports by stripping the export modifiers from - // the top-level exports and exporting them in the targeted ns, as can occur when a js file has both typedefs and `module.export` assignments - const excessExports = filter(statements, s => !!(getEffectiveModifierFlags(s) & ModifierFlags.Export)); - const name = ns.name; - let body = ns.body; - if (length(excessExports)) { - ns = factory.updateModuleDeclaration( - ns, - ns.decorators, - ns.modifiers, - ns.name, - body = factory.updateModuleBlock( - body, - factory.createNodeArray([...ns.body.statements, factory.createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => factory.createExportSpecifier(/*isTypeOnly*/ false, /*alias*/ undefined, id))), - /*moduleSpecifier*/ undefined - )]) - ) - ); - statements = [...statements.slice(0, nsIndex), ns, ...statements.slice(nsIndex + 1)]; } - - // Pass 1: Flatten `export namespace _exports {} export = _exports;` so long as the `export=` only points at a single namespace declaration - if (!find(statements, s => s !== ns && nodeHasName(s, name))) { - results = []; - // If the namespace contains no export assignments or declarations, and no declarations flagged with `export`, then _everything_ is exported - - // to respect this as the top level, we need to add an `export` modifier to everything - const mixinExportFlag = !some(body.statements, s => hasSyntacticModifier(s, ModifierFlags.Export) || isExportAssignment(s) || isExportDeclaration(s)); - forEach(body.statements, s => { - addResult(s, mixinExportFlag ? ModifierFlags.Export : ModifierFlags.None); // Recalculates the ambient (and export, if applicable from above) flag - }); - statements = [...filter(statements, s => s !== ns && s !== exportAssignment), ...results]; + else if (oldcontext.tracker && oldcontext.tracker.trackSymbol) { + return oldcontext.tracker.trackSymbol(sym, decl, meaning); } + return false; + }, + }, + }; + context.tracker = wrapSymbolTrackerToReportForContext(context, context.tracker); + forEachEntry(symbolTable, (symbol, name) => { + const baseName = unescapeLeadingUnderscores(name); + void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` + }); + let addingDeclare = !bundled; + const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals); + if (exportEquals && symbolTable.size > 1 && exportEquals.flags & SymbolFlags.Alias) { + symbolTable = createSymbolTable(); + // Remove extraneous elements from root symbol table (they'll be mixed back in when the target of the `export=` is looked up) + symbolTable.set(InternalSymbolName.ExportEquals, exportEquals); + } + + visitSymbolTable(symbolTable); + return mergeRedundantStatements(results); + + function isIdentifierAndNotUndefined(node: Node | undefined): node is Identifier { + return !!node && node.kind === SyntaxKind.Identifier; + } + + function getNamesOfDeclaration(statement: Statement): Identifier[] { + if (isVariableStatement(statement)) { + return filter(map(statement.declarationList.declarations, getNameOfDeclaration), isIdentifierAndNotUndefined); + } + return filter([getNameOfDeclaration(statement as DeclarationStatement)], isIdentifierAndNotUndefined); + } + + function flattenExportAssignedNamespace(statements: Statement[]) { + const exportAssignment = find(statements, isExportAssignment); + const nsIndex = findIndex(statements, isModuleDeclaration); + let ns = nsIndex !== -1 ? statements[nsIndex] as ModuleDeclaration : undefined; + if (ns && exportAssignment && exportAssignment.isExportEquals && + isIdentifier(exportAssignment.expression) && isIdentifier(ns.name) && idText(ns.name) === idText(exportAssignment.expression) && + ns.body && isModuleBlock(ns.body)) { + // Pass 0: Correct situations where a module has both an `export = ns` and multiple top-level exports by stripping the export modifiers from + // the top-level exports and exporting them in the targeted ns, as can occur when a js file has both typedefs and `module.export` assignments + const excessExports = filter(statements, s => !!(getEffectiveModifierFlags(s) & ModifierFlags.Export)); + const name = ns.name; + let body = ns.body; + if (length(excessExports)) { + ns = factory.updateModuleDeclaration( + ns, + ns.decorators, + ns.modifiers, + ns.name, + body = factory.updateModuleBlock( + body, + factory.createNodeArray([...ns.body.statements, factory.createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => factory.createExportSpecifier(/*isTypeOnly*/ false, /*alias*/ undefined, id))), + /*moduleSpecifier*/ undefined + )]) + ) + ); + statements = [...statements.slice(0, nsIndex), ns, ...statements.slice(nsIndex + 1)]; + } + + // Pass 1: Flatten `export namespace _exports {} export = _exports;` so long as the `export=` only points at a single namespace declaration + if (!find(statements, s => s !== ns && nodeHasName(s, name))) { + results = []; + // If the namespace contains no export assignments or declarations, and no declarations flagged with `export`, then _everything_ is exported - + // to respect this as the top level, we need to add an `export` modifier to everything + const mixinExportFlag = !some(body.statements, s => hasSyntacticModifier(s, ModifierFlags.Export) || isExportAssignment(s) || isExportDeclaration(s)); + forEach(body.statements, s => { + addResult(s, mixinExportFlag ? ModifierFlags.Export : ModifierFlags.None); // Recalculates the ambient (and export, if applicable from above) flag + }); + statements = [...filter(statements, s => s !== ns && s !== exportAssignment), ...results]; } - return statements; } + return statements; + } - function mergeExportDeclarations(statements: Statement[]) { - // Pass 2: Combine all `export {}` declarations - const exports = filter(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause)) as ExportDeclaration[]; - if (length(exports) > 1) { - const nonExports = filter(statements, d => !isExportDeclaration(d) || !!d.moduleSpecifier || !d.exportClause); - statements = [...nonExports, factory.createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports(flatMap(exports, e => cast(e.exportClause, isNamedExports).elements)), - /*moduleSpecifier*/ undefined - )]; - } - // Pass 2b: Also combine all `export {} from "..."` declarations as needed - const reexports = filter(statements, d => isExportDeclaration(d) && !!d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause)) as ExportDeclaration[]; - if (length(reexports) > 1) { - const groups = group(reexports, decl => isStringLiteral(decl.moduleSpecifier!) ? ">" + decl.moduleSpecifier.text : ">"); - if (groups.length !== reexports.length) { - for (const group of groups) { - if (group.length > 1) { - // remove group members from statements and then merge group members and add back to statements - statements = [ - ...filter(statements, s => group.indexOf(s as ExportDeclaration) === -1), - factory.createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports(flatMap(group, e => cast(e.exportClause, isNamedExports).elements)), - group[0].moduleSpecifier - ) - ]; - } + function mergeExportDeclarations(statements: Statement[]) { + // Pass 2: Combine all `export {}` declarations + const exports = filter(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause)) as ExportDeclaration[]; + if (length(exports) > 1) { + const nonExports = filter(statements, d => !isExportDeclaration(d) || !!d.moduleSpecifier || !d.exportClause); + statements = [...nonExports, factory.createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(flatMap(exports, e => cast(e.exportClause, isNamedExports).elements)), + /*moduleSpecifier*/ undefined + )]; + } + // Pass 2b: Also combine all `export {} from "..."` declarations as needed + const reexports = filter(statements, d => isExportDeclaration(d) && !!d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause)) as ExportDeclaration[]; + if (length(reexports) > 1) { + const groups = group(reexports, decl => isStringLiteral(decl.moduleSpecifier!) ? ">" + decl.moduleSpecifier.text : ">"); + if (groups.length !== reexports.length) { + for (const group of groups) { + if (group.length > 1) { + // remove group members from statements and then merge group members and add back to statements + statements = [ + ...filter(statements, s => group.indexOf(s as ExportDeclaration) === -1), + factory.createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(flatMap(group, e => cast(e.exportClause, isNamedExports).elements)), + group[0].moduleSpecifier + ) + ]; } } } - return statements; } + return statements; + } - function inlineExportModifiers(statements: Statement[]) { - // Pass 3: Move all `export {}`'s to `export` modifiers where possible - const index = findIndex(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !d.assertClause && !!d.exportClause && isNamedExports(d.exportClause)); - if (index >= 0) { - const exportDecl = statements[index] as ExportDeclaration & { readonly exportClause: NamedExports }; - const replacements = mapDefined(exportDecl.exportClause.elements, e => { - if (!e.propertyName) { - // export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it - const indices = indicesOf(statements); - const associatedIndices = filter(indices, i => nodeHasName(statements[i], e.name)); - if (length(associatedIndices) && every(associatedIndices, i => canHaveExportModifier(statements[i]))) { - for (const index of associatedIndices) { - statements[index] = addExportModifier(statements[index] as Extract); - } - return undefined; + function inlineExportModifiers(statements: Statement[]) { + // Pass 3: Move all `export {}`'s to `export` modifiers where possible + const index = findIndex(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !d.assertClause && !!d.exportClause && isNamedExports(d.exportClause)); + if (index >= 0) { + const exportDecl = statements[index] as ExportDeclaration & { readonly exportClause: NamedExports }; + const replacements = mapDefined(exportDecl.exportClause.elements, e => { + if (!e.propertyName) { + // export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it + const indices = indicesOf(statements); + const associatedIndices = filter(indices, i => nodeHasName(statements[i], e.name)); + if (length(associatedIndices) && every(associatedIndices, i => canHaveExportModifier(statements[i]))) { + for (const index of associatedIndices) { + statements[index] = addExportModifier(statements[index] as Extract); } + return undefined; } - return e; - }); - if (!length(replacements)) { - // all clauses removed, remove the export declaration - orderedRemoveItemAt(statements, index); - } - else { - // some items filtered, others not - update the export declaration - statements[index] = factory.updateExportDeclaration( - exportDecl, - exportDecl.decorators, - exportDecl.modifiers, - exportDecl.isTypeOnly, - factory.updateNamedExports( - exportDecl.exportClause, - replacements - ), - exportDecl.moduleSpecifier, - exportDecl.assertClause - ); } + return e; + }); + if (!length(replacements)) { + // all clauses removed, remove the export declaration + orderedRemoveItemAt(statements, index); + } + else { + // some items filtered, others not - update the export declaration + statements[index] = factory.updateExportDeclaration( + exportDecl, + exportDecl.decorators, + exportDecl.modifiers, + exportDecl.isTypeOnly, + factory.updateNamedExports( + exportDecl.exportClause, + replacements + ), + exportDecl.moduleSpecifier, + exportDecl.assertClause + ); } - return statements; } + return statements; + } - function mergeRedundantStatements(statements: Statement[]) { - statements = flattenExportAssignedNamespace(statements); - statements = mergeExportDeclarations(statements); - statements = inlineExportModifiers(statements); + function mergeRedundantStatements(statements: Statement[]) { + statements = flattenExportAssignedNamespace(statements); + statements = mergeExportDeclarations(statements); + statements = inlineExportModifiers(statements); - // Not a cleanup, but as a final step: If there is a mix of `export` and non-`export` declarations, but no `export =` or `export {}` add a `export {};` so - // declaration privacy is respected. - if (enclosingDeclaration && - ((isSourceFile(enclosingDeclaration) && isExternalOrCommonJsModule(enclosingDeclaration)) || isModuleDeclaration(enclosingDeclaration)) && - (!some(statements, isExternalModuleIndicator) || (!hasScopeMarker(statements) && some(statements, needsScopeMarker)))) { - statements.push(createEmptyExports(factory)); - } - return statements; + // Not a cleanup, but as a final step: If there is a mix of `export` and non-`export` declarations, but no `export =` or `export {}` add a `export {};` so + // declaration privacy is respected. + if (enclosingDeclaration && + ((isSourceFile(enclosingDeclaration) && isExternalOrCommonJsModule(enclosingDeclaration)) || isModuleDeclaration(enclosingDeclaration)) && + (!some(statements, isExternalModuleIndicator) || (!hasScopeMarker(statements) && some(statements, needsScopeMarker)))) { + statements.push(createEmptyExports(factory)); } + return statements; + } - function canHaveExportModifier(node: Statement): node is Extract { - return isEnumDeclaration(node) || - isVariableStatement(node) || - isFunctionDeclaration(node) || - isClassDeclaration(node) || - (isModuleDeclaration(node) && !isExternalModuleAugmentation(node) && !isGlobalScopeAugmentation(node)) || - isInterfaceDeclaration(node) || - isTypeDeclaration(node); - } + function canHaveExportModifier(node: Statement): node is Extract { + return isEnumDeclaration(node) || + isVariableStatement(node) || + isFunctionDeclaration(node) || + isClassDeclaration(node) || + (isModuleDeclaration(node) && !isExternalModuleAugmentation(node) && !isGlobalScopeAugmentation(node)) || + isInterfaceDeclaration(node) || + isTypeDeclaration(node); + } - function addExportModifier(node: Extract) { - const flags = (getEffectiveModifierFlags(node) | ModifierFlags.Export) & ~ModifierFlags.Ambient; - return factory.updateModifiers(node, flags); - } + function addExportModifier(node: Extract) { + const flags = (getEffectiveModifierFlags(node) | ModifierFlags.Export) & ~ModifierFlags.Ambient; + return factory.updateModifiers(node, flags); + } - function removeExportModifier(node: Extract) { - const flags = getEffectiveModifierFlags(node) & ~ModifierFlags.Export; - return factory.updateModifiers(node, flags); - } + function removeExportModifier(node: Extract) { + const flags = getEffectiveModifierFlags(node) & ~ModifierFlags.Export; + return factory.updateModifiers(node, flags); + } - function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) { - if (!suppressNewPrivateContext) { - deferredPrivatesStack.push(new Map()); - } - symbolTable.forEach((symbol: Symbol) => { - serializeSymbol(symbol, /*isPrivate*/ false, !!propertyAsAlias); + function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) { + if (!suppressNewPrivateContext) { + deferredPrivatesStack.push(new Map()); + } + symbolTable.forEach((symbol: Symbol) => { + serializeSymbol(symbol, /*isPrivate*/ false, !!propertyAsAlias); + }); + if (!suppressNewPrivateContext) { + // deferredPrivates will be filled up by visiting the symbol table + // And will continue to iterate as elements are added while visited `deferredPrivates` + // (As that's how a map iterator is defined to work) + deferredPrivatesStack[deferredPrivatesStack.length - 1].forEach((symbol: Symbol) => { + serializeSymbol(symbol, /*isPrivate*/ true, !!propertyAsAlias); }); - if (!suppressNewPrivateContext) { - // deferredPrivates will be filled up by visiting the symbol table - // And will continue to iterate as elements are added while visited `deferredPrivates` - // (As that's how a map iterator is defined to work) - deferredPrivatesStack[deferredPrivatesStack.length - 1].forEach((symbol: Symbol) => { - serializeSymbol(symbol, /*isPrivate*/ true, !!propertyAsAlias); - }); - deferredPrivatesStack.pop(); - } + deferredPrivatesStack.pop(); } + } - function serializeSymbol(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean) { - // cache visited list based on merged symbol, since we want to use the unmerged top-level symbol, but - // still skip reserializing it if we encounter the merged product later on - const visitedSym = getMergedSymbol(symbol); - if (visitedSymbols.has(getSymbolId(visitedSym))) { - return; // Already printed - } - visitedSymbols.add(getSymbolId(visitedSym)); - // Only actually serialize symbols within the correct enclosing declaration, otherwise do nothing with the out-of-context symbol - const skipMembershipCheck = !isPrivate; // We only call this on exported symbols when we know they're in the correct scope - if (skipMembershipCheck || (!!length(symbol.declarations) && some(symbol.declarations, d => !!findAncestor(d, n => n === enclosingDeclaration)))) { - const oldContext = context; - context = cloneNodeBuilderContext(context); - const result = serializeSymbolWorker(symbol, isPrivate, propertyAsAlias); - if (context.reportedDiagnostic) { - oldcontext.reportedDiagnostic = context.reportedDiagnostic; // hoist diagnostic result into outer context - } - context = oldContext; - return result; + function serializeSymbol(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean) { + // cache visited list based on merged symbol, since we want to use the unmerged top-level symbol, but + // still skip reserializing it if we encounter the merged product later on + const visitedSym = getMergedSymbol(symbol); + if (visitedSymbols.has(getSymbolId(visitedSym))) { + return; // Already printed + } + visitedSymbols.add(getSymbolId(visitedSym)); + // Only actually serialize symbols within the correct enclosing declaration, otherwise do nothing with the out-of-context symbol + const skipMembershipCheck = !isPrivate; // We only call this on exported symbols when we know they're in the correct scope + if (skipMembershipCheck || (!!length(symbol.declarations) && some(symbol.declarations, d => !!findAncestor(d, n => n === enclosingDeclaration)))) { + const oldContext = context; + context = cloneNodeBuilderContext(context); + const result = serializeSymbolWorker(symbol, isPrivate, propertyAsAlias); + if (context.reportedDiagnostic) { + oldcontext.reportedDiagnostic = context.reportedDiagnostic; // hoist diagnostic result into outer context } + context = oldContext; + return result; } + } - // Synthesize declarations for a symbol - might be an Interface, a Class, a Namespace, a Type, a Variable (const, let, or var), an Alias - // or a merge of some number of those. - // An interesting challenge is ensuring that when classes merge with namespaces and interfaces, is keeping - // each symbol in only one of the representations - // Also, synthesizing a default export of some kind - // If it's an alias: emit `export default ref` - // If it's a property: emit `export default _default` with a `_default` prop - // If it's a class/interface/function: emit a class/interface/function with a `default` modifier - // These forms can merge, eg (`export default 12; export default interface A {}`) - function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean) { - const symbolName = unescapeLeadingUnderscores(symbol.escapedName); - const isDefault = symbol.escapedName === InternalSymbolName.Default; - if (isPrivate && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier) && isStringANonContextualKeyword(symbolName) && !isDefault) { - // Oh no. We cannot use this symbol's name as it's name... It's likely some jsdoc had an invalid name like `export` or `default` :( - context.encounteredError = true; - // TODO: Issue error via symbol tracker? - return; // If we need to emit a private with a keyword name, we're done for, since something else will try to refer to it by that name - } - let needsPostExportDefault = isDefault && !!( - symbol.flags & SymbolFlags.ExportDoesNotSupportDefaultModifier - || (symbol.flags & SymbolFlags.Function && length(getPropertiesOfType(getTypeOfSymbol(symbol)))) - ) && !(symbol.flags & SymbolFlags.Alias); // An alias symbol should preclude needing to make an alias ourselves - let needsExportDeclaration = !needsPostExportDefault && !isPrivate && isStringANonContextualKeyword(symbolName) && !isDefault; - // `serializeVariableOrProperty` will handle adding the export declaration if it is run (since `getInternalSymbolName` will create the name mapping), so we need to ensuer we unset `needsExportDeclaration` if it is - if (needsPostExportDefault || needsExportDeclaration) { - isPrivate = true; - } - const modifierFlags = (!isPrivate ? ModifierFlags.Export : 0) | (isDefault && !needsPostExportDefault ? ModifierFlags.Default : 0); - const isConstMergedWithNS = symbol.flags & SymbolFlags.Module && - symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) && - symbol.escapedName !== InternalSymbolName.ExportEquals; - const isConstMergedWithNSPrintableAsSignatureMerge = isConstMergedWithNS && isTypeRepresentableAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol); - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) || isConstMergedWithNSPrintableAsSignatureMerge) { - serializeAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); - } - if (symbol.flags & SymbolFlags.TypeAlias) { - serializeTypeAlias(symbol, symbolName, modifierFlags); - } - // Need to skip over export= symbols below - json source files get a single `Property` flagged - // symbol of name `export=` which needs to be handled like an alias. It's not great, but it is what it is. - if (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) - && symbol.escapedName !== InternalSymbolName.ExportEquals - && !(symbol.flags & SymbolFlags.Prototype) - && !(symbol.flags & SymbolFlags.Class) - && !isConstMergedWithNSPrintableAsSignatureMerge) { - if (propertyAsAlias) { - const createdExport = serializeMaybeAliasAssignment(symbol); - if (createdExport) { - needsExportDeclaration = false; - needsPostExportDefault = false; - } + // Synthesize declarations for a symbol - might be an Interface, a Class, a Namespace, a Type, a Variable (const, let, or var), an Alias + // or a merge of some number of those. + // An interesting challenge is ensuring that when classes merge with namespaces and interfaces, is keeping + // each symbol in only one of the representations + // Also, synthesizing a default export of some kind + // If it's an alias: emit `export default ref` + // If it's a property: emit `export default _default` with a `_default` prop + // If it's a class/interface/function: emit a class/interface/function with a `default` modifier + // These forms can merge, eg (`export default 12; export default interface A {}`) + function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean) { + const symbolName = unescapeLeadingUnderscores(symbol.escapedName); + const isDefault = symbol.escapedName === InternalSymbolName.Default; + if (isPrivate && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier) && isStringANonContextualKeyword(symbolName) && !isDefault) { + // Oh no. We cannot use this symbol's name as it's name... It's likely some jsdoc had an invalid name like `export` or `default` :( + context.encounteredError = true; + // TODO: Issue error via symbol tracker? + return; // If we need to emit a private with a keyword name, we're done for, since something else will try to refer to it by that name + } + let needsPostExportDefault = isDefault && !!( + symbol.flags & SymbolFlags.ExportDoesNotSupportDefaultModifier + || (symbol.flags & SymbolFlags.Function && length(getPropertiesOfType(getTypeOfSymbol(symbol)))) + ) && !(symbol.flags & SymbolFlags.Alias); // An alias symbol should preclude needing to make an alias ourselves + let needsExportDeclaration = !needsPostExportDefault && !isPrivate && isStringANonContextualKeyword(symbolName) && !isDefault; + // `serializeVariableOrProperty` will handle adding the export declaration if it is run (since `getInternalSymbolName` will create the name mapping), so we need to ensuer we unset `needsExportDeclaration` if it is + if (needsPostExportDefault || needsExportDeclaration) { + isPrivate = true; + } + const modifierFlags = (!isPrivate ? ModifierFlags.Export : 0) | (isDefault && !needsPostExportDefault ? ModifierFlags.Default : 0); + const isConstMergedWithNS = symbol.flags & SymbolFlags.Module && + symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) && + symbol.escapedName !== InternalSymbolName.ExportEquals; + const isConstMergedWithNSPrintableAsSignatureMerge = isConstMergedWithNS && isTypeRepresentableAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol); + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) || isConstMergedWithNSPrintableAsSignatureMerge) { + serializeAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); + } + if (symbol.flags & SymbolFlags.TypeAlias) { + serializeTypeAlias(symbol, symbolName, modifierFlags); + } + // Need to skip over export= symbols below - json source files get a single `Property` flagged + // symbol of name `export=` which needs to be handled like an alias. It's not great, but it is what it is. + if (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) + && symbol.escapedName !== InternalSymbolName.ExportEquals + && !(symbol.flags & SymbolFlags.Prototype) + && !(symbol.flags & SymbolFlags.Class) + && !isConstMergedWithNSPrintableAsSignatureMerge) { + if (propertyAsAlias) { + const createdExport = serializeMaybeAliasAssignment(symbol); + if (createdExport) { + needsExportDeclaration = false; + needsPostExportDefault = false; + } + } + else { + const type = getTypeOfSymbol(symbol); + const localName = getInternalSymbolName(symbol, symbolName); + if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) { + // If the type looks like a function declaration + ns could represent it, and it's type is sourced locally, rewrite it into a function declaration + ns + serializeAsFunctionNamespaceMerge(type, symbol, localName, modifierFlags); } else { - const type = getTypeOfSymbol(symbol); - const localName = getInternalSymbolName(symbol, symbolName); - if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) { - // If the type looks like a function declaration + ns could represent it, and it's type is sourced locally, rewrite it into a function declaration + ns - serializeAsFunctionNamespaceMerge(type, symbol, localName, modifierFlags); + // A Class + Property merge is made for a `module.exports.Member = class {}`, and it doesn't serialize well as either a class _or_ a property symbol - in fact, _it behaves like an alias!_ + // `var` is `FunctionScopedVariable`, `const` and `let` are `BlockScopedVariable`, and `module.exports.thing =` is `Property` + const flags = !(symbol.flags & SymbolFlags.BlockScopedVariable) ? undefined + : isConstVariable(symbol) ? NodeFlags.Const + : NodeFlags.Let; + const name = (needsPostExportDefault || !(symbol.flags & SymbolFlags.Property)) ? localName : getUnusedName(localName, symbol); + let textRange: Node | undefined = symbol.declarations && find(symbol.declarations, d => isVariableDeclaration(d)); + if (textRange && isVariableDeclarationList(textRange.parent) && textRange.parent.declarations.length === 1) { + textRange = textRange.parent.parent; + } + const propertyAccessRequire = symbol.declarations?.find(isPropertyAccessExpression); + if (propertyAccessRequire && isBinaryExpression(propertyAccessRequire.parent) && isIdentifier(propertyAccessRequire.parent.right) + && type.symbol?.valueDeclaration && isSourceFile(type.symbol.valueDeclaration)) { + const alias = localName === propertyAccessRequire.parent.right.escapedText ? undefined : propertyAccessRequire.parent.right; + addResult( + factory.createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, alias, localName)]) + ), + ModifierFlags.None + ); + context.tracker.trackSymbol!(type.symbol, context.enclosingDeclaration, SymbolFlags.Value); } else { - // A Class + Property merge is made for a `module.exports.Member = class {}`, and it doesn't serialize well as either a class _or_ a property symbol - in fact, _it behaves like an alias!_ - // `var` is `FunctionScopedVariable`, `const` and `let` are `BlockScopedVariable`, and `module.exports.thing =` is `Property` - const flags = !(symbol.flags & SymbolFlags.BlockScopedVariable) ? undefined - : isConstVariable(symbol) ? NodeFlags.Const - : NodeFlags.Let; - const name = (needsPostExportDefault || !(symbol.flags & SymbolFlags.Property)) ? localName : getUnusedName(localName, symbol); - let textRange: Node | undefined = symbol.declarations && find(symbol.declarations, d => isVariableDeclaration(d)); - if (textRange && isVariableDeclarationList(textRange.parent) && textRange.parent.declarations.length === 1) { - textRange = textRange.parent.parent; - } - const propertyAccessRequire = symbol.declarations?.find(isPropertyAccessExpression); - if (propertyAccessRequire && isBinaryExpression(propertyAccessRequire.parent) && isIdentifier(propertyAccessRequire.parent.right) - && type.symbol?.valueDeclaration && isSourceFile(type.symbol.valueDeclaration)) { - const alias = localName === propertyAccessRequire.parent.right.escapedText ? undefined : propertyAccessRequire.parent.right; + const statement = setTextRange(factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ + factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, type, symbol, enclosingDeclaration, includePrivateSymbol, bundled)) + ], flags)), textRange); + addResult(statement, name !== localName ? modifierFlags & ~ModifierFlags.Export : modifierFlags); + if (name !== localName && !isPrivate) { + // We rename the variable declaration we generate for Property symbols since they may have a name which + // conflicts with a local declaration. For example, given input: + // ``` + // function g() {} + // module.exports.g = g + // ``` + // In such a situation, we have a local variable named `g`, and a separate exported variable named `g`. + // Naively, we would emit + // ``` + // function g() {} + // export const g: typeof g; + // ``` + // That's obviously incorrect - the `g` in the type annotation needs to refer to the local `g`, but + // the export declaration shadows it. + // To work around that, we instead write + // ``` + // function g() {} + // const g_1: typeof g; + // export { g_1 as g }; + // ``` + // To create an export named `g` that does _not_ shadow the local `g` addResult( factory.createExportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, alias, localName)]) + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, name, localName)]) ), ModifierFlags.None ); - context.tracker.trackSymbol!(type.symbol, context.enclosingDeclaration, SymbolFlags.Value); - } - else { - const statement = setTextRange(factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ - factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, type, symbol, enclosingDeclaration, includePrivateSymbol, bundled)) - ], flags)), textRange); - addResult(statement, name !== localName ? modifierFlags & ~ModifierFlags.Export : modifierFlags); - if (name !== localName && !isPrivate) { - // We rename the variable declaration we generate for Property symbols since they may have a name which - // conflicts with a local declaration. For example, given input: - // ``` - // function g() {} - // module.exports.g = g - // ``` - // In such a situation, we have a local variable named `g`, and a separate exported variable named `g`. - // Naively, we would emit - // ``` - // function g() {} - // export const g: typeof g; - // ``` - // That's obviously incorrect - the `g` in the type annotation needs to refer to the local `g`, but - // the export declaration shadows it. - // To work around that, we instead write - // ``` - // function g() {} - // const g_1: typeof g; - // export { g_1 as g }; - // ``` - // To create an export named `g` that does _not_ shadow the local `g` - addResult( - factory.createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, name, localName)]) - ), - ModifierFlags.None - ); - needsExportDeclaration = false; - needsPostExportDefault = false; - } + needsExportDeclaration = false; + needsPostExportDefault = false; } } } } - if (symbol.flags & SymbolFlags.Enum) { - serializeEnum(symbol, symbolName, modifierFlags); - } - if (symbol.flags & SymbolFlags.Class) { - if (symbol.flags & SymbolFlags.Property - && symbol.valueDeclaration - && isBinaryExpression(symbol.valueDeclaration.parent) - && isClassExpression(symbol.valueDeclaration.parent.right)) { - // Looks like a `module.exports.Sub = class {}` - if we serialize `symbol` as a class, the result will have no members, - // since the classiness is actually from the target of the effective alias the symbol is. yes. A BlockScopedVariable|Class|Property - // _really_ acts like an Alias, and none of a BlockScopedVariable, Class, or Property. This is the travesty of JS binding today. - serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); - } - else { - serializeAsClass(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); - } - } - if ((symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && (!isConstMergedWithNS || isTypeOnlyNamespace(symbol))) || isConstMergedWithNSPrintableAsSignatureMerge) { - serializeModule(symbol, symbolName, modifierFlags); - } - // The class meaning serialization should handle serializing all interface members - if (symbol.flags & SymbolFlags.Interface && !(symbol.flags & SymbolFlags.Class)) { - serializeInterface(symbol, symbolName, modifierFlags); - } - if (symbol.flags & SymbolFlags.Alias) { + } + if (symbol.flags & SymbolFlags.Enum) { + serializeEnum(symbol, symbolName, modifierFlags); + } + if (symbol.flags & SymbolFlags.Class) { + if (symbol.flags & SymbolFlags.Property + && symbol.valueDeclaration + && isBinaryExpression(symbol.valueDeclaration.parent) + && isClassExpression(symbol.valueDeclaration.parent.right)) { + // Looks like a `module.exports.Sub = class {}` - if we serialize `symbol` as a class, the result will have no members, + // since the classiness is actually from the target of the effective alias the symbol is. yes. A BlockScopedVariable|Class|Property + // _really_ acts like an Alias, and none of a BlockScopedVariable, Class, or Property. This is the travesty of JS binding today. serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); } - if (symbol.flags & SymbolFlags.Property && symbol.escapedName === InternalSymbolName.ExportEquals) { - serializeMaybeAliasAssignment(symbol); + else { + serializeAsClass(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); } - if (symbol.flags & SymbolFlags.ExportStar) { - // synthesize export * from "moduleReference" - // Straightforward - only one thing to do - make an export declaration - if (symbol.declarations) { - for (const node of symbol.declarations) { - const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); - if (!resolvedModule) continue; - addResult(factory.createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None); - } + } + if ((symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && (!isConstMergedWithNS || isTypeOnlyNamespace(symbol))) || isConstMergedWithNSPrintableAsSignatureMerge) { + serializeModule(symbol, symbolName, modifierFlags); + } + // The class meaning serialization should handle serializing all interface members + if (symbol.flags & SymbolFlags.Interface && !(symbol.flags & SymbolFlags.Class)) { + serializeInterface(symbol, symbolName, modifierFlags); + } + if (symbol.flags & SymbolFlags.Alias) { + serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); + } + if (symbol.flags & SymbolFlags.Property && symbol.escapedName === InternalSymbolName.ExportEquals) { + serializeMaybeAliasAssignment(symbol); + } + if (symbol.flags & SymbolFlags.ExportStar) { + // synthesize export * from "moduleReference" + // Straightforward - only one thing to do - make an export declaration + if (symbol.declarations) { + for (const node of symbol.declarations) { + const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); + if (!resolvedModule) continue; + addResult(factory.createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None); } } - if (needsPostExportDefault) { - addResult(factory.createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, /*isExportAssignment*/ false, factory.createIdentifier(getInternalSymbolName(symbol, symbolName))), ModifierFlags.None); - } - else if (needsExportDeclaration) { - addResult(factory.createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, getInternalSymbolName(symbol, symbolName), symbolName)]) - ), ModifierFlags.None); - } } - - function includePrivateSymbol(symbol: Symbol) { - if (some(symbol.declarations, isParameterDeclaration)) return; - Debug.assertIsDefined(deferredPrivatesStack[deferredPrivatesStack.length - 1]); - getUnusedName(unescapeLeadingUnderscores(symbol.escapedName), symbol); // Call to cache unique name for symbol - // Blanket moving (import) aliases into the root private context should work, since imports are not valid within namespaces - // (so they must have been in the root to begin with if they were real imports) cjs `require` aliases (an upcoming feature) - // will throw a wrench in this, since those may have been nested, but we'll need to synthesize them in the outer scope - // anyway, as that's the only place the import they translate to is valid. In such a case, we might need to use a unique name - // for the moved import; which hopefully the above `getUnusedName` call should produce. - const isExternalImportAlias = !!(symbol.flags & SymbolFlags.Alias) && !some(symbol.declarations, d => - !!findAncestor(d, isExportDeclaration) || - isNamespaceExport(d) || - (isImportEqualsDeclaration(d) && !isExternalModuleReference(d.moduleReference)) - ); - deferredPrivatesStack[isExternalImportAlias ? 0 : (deferredPrivatesStack.length - 1)].set(getSymbolId(symbol), symbol); - } - - function isExportingScope(enclosingDeclaration: Node) { - return ((isSourceFile(enclosingDeclaration) && (isExternalOrCommonJsModule(enclosingDeclaration) || isJsonSourceFile(enclosingDeclaration))) || - (isAmbientModule(enclosingDeclaration) && !isGlobalScopeAugmentation(enclosingDeclaration))); - } - - // Prepends a `declare` and/or `export` modifier if the context requires it, and then adds `node` to `result` and returns `node` - function addResult(node: Statement, additionalModifierFlags: ModifierFlags) { - if (canHaveModifiers(node)) { - let newModifierFlags: ModifierFlags = ModifierFlags.None; - const enclosingDeclaration = context.enclosingDeclaration && - (isJSDocTypeAlias(context.enclosingDeclaration) ? getSourceFileOfNode(context.enclosingDeclaration) : context.enclosingDeclaration); - if (additionalModifierFlags & ModifierFlags.Export && - enclosingDeclaration && (isExportingScope(enclosingDeclaration) || isModuleDeclaration(enclosingDeclaration)) && - canHaveExportModifier(node) - ) { - // Classes, namespaces, variables, functions, interfaces, and types should all be `export`ed in a module context if not private - newModifierFlags |= ModifierFlags.Export; - } - if (addingDeclare && !(newModifierFlags & ModifierFlags.Export) && - (!enclosingDeclaration || !(enclosingDeclaration.flags & NodeFlags.Ambient)) && - (isEnumDeclaration(node) || isVariableStatement(node) || isFunctionDeclaration(node) || isClassDeclaration(node) || isModuleDeclaration(node))) { - // Classes, namespaces, variables, enums, and functions all need `declare` modifiers to be valid in a declaration file top-level scope - newModifierFlags |= ModifierFlags.Ambient; - } - if ((additionalModifierFlags & ModifierFlags.Default) && (isClassDeclaration(node) || isInterfaceDeclaration(node) || isFunctionDeclaration(node))) { - newModifierFlags |= ModifierFlags.Default; - } - if (newModifierFlags) { - node = factory.updateModifiers(node, newModifierFlags | getEffectiveModifierFlags(node)); - } - } - results.push(node); - } - - function serializeTypeAlias(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { - const aliasType = getDeclaredTypeOfTypeAlias(symbol); - const typeParams = getSymbolLinks(symbol).typeParameters; - const typeParamDecls = map(typeParams, p => typeParameterToDeclaration(p, context)); - const jsdocAliasDecl = symbol.declarations?.find(isJSDocTypeAlias); - const commentText = getTextOfJSDocComment(jsdocAliasDecl ? jsdocAliasDecl.comment || jsdocAliasDecl.parent.comment : undefined); - const oldFlags = context.flags; - context.flags |= NodeBuilderFlags.InTypeAlias; - const oldEnclosingDecl = context.enclosingDeclaration; - context.enclosingDeclaration = jsdocAliasDecl; - const typeNode = jsdocAliasDecl && jsdocAliasDecl.typeExpression - && isJSDocTypeExpression(jsdocAliasDecl.typeExpression) - && serializeExistingTypeNode(context, jsdocAliasDecl.typeExpression.type, includePrivateSymbol, bundled) - || typeToTypeNodeHelper(aliasType, context); - addResult(setSyntheticLeadingComments( - factory.createTypeAliasDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, getInternalSymbolName(symbol, symbolName), typeParamDecls, typeNode), - !commentText ? [] : [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }] - ), modifierFlags); - context.flags = oldFlags; - context.enclosingDeclaration = oldEnclosingDecl; - } - - function serializeInterface(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { - const interfaceType = getDeclaredTypeOfClassOrInterface(symbol); - const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); - const baseTypes = getBaseTypes(interfaceType); - const baseType = length(baseTypes) ? getIntersectionType(baseTypes) : undefined; - const members = flatMap(getPropertiesOfType(interfaceType), p => serializePropertySymbolForInterface(p, baseType)); - const callSignatures = serializeSignatures(SignatureKind.Call, interfaceType, baseType, SyntaxKind.CallSignature) as CallSignatureDeclaration[]; - const constructSignatures = serializeSignatures(SignatureKind.Construct, interfaceType, baseType, SyntaxKind.ConstructSignature) as ConstructSignatureDeclaration[]; - const indexSignatures = serializeIndexSignatures(interfaceType, baseType); - - const heritageClauses = !length(baseTypes) ? undefined : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, mapDefined(baseTypes, b => trySerializeAsTypeReference(b, SymbolFlags.Value)))]; - addResult(factory.createInterfaceDeclaration( + if (needsPostExportDefault) { + addResult(factory.createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, /*isExportAssignment*/ false, factory.createIdentifier(getInternalSymbolName(symbol, symbolName))), ModifierFlags.None); + } + else if (needsExportDeclaration) { + addResult(factory.createExportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, - getInternalSymbolName(symbol, symbolName), - typeParamDecls, - heritageClauses, - [...indexSignatures, ...constructSignatures, ...callSignatures, ...members] - ), modifierFlags); + /*isTypeOnly*/ false, + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, getInternalSymbolName(symbol, symbolName), symbolName)]) + ), ModifierFlags.None); } + } - function getNamespaceMembersForSerialization(symbol: Symbol) { - return !symbol.exports ? [] : filter(arrayFrom(symbol.exports.values()), isNamespaceMember); - } + function includePrivateSymbol(symbol: Symbol) { + if (some(symbol.declarations, isParameterDeclaration)) return; + Debug.assertIsDefined(deferredPrivatesStack[deferredPrivatesStack.length - 1]); + getUnusedName(unescapeLeadingUnderscores(symbol.escapedName), symbol); // Call to cache unique name for symbol + // Blanket moving (import) aliases into the root private context should work, since imports are not valid within namespaces + // (so they must have been in the root to begin with if they were real imports) cjs `require` aliases (an upcoming feature) + // will throw a wrench in this, since those may have been nested, but we'll need to synthesize them in the outer scope + // anyway, as that's the only place the import they translate to is valid. In such a case, we might need to use a unique name + // for the moved import; which hopefully the above `getUnusedName` call should produce. + const isExternalImportAlias = !!(symbol.flags & SymbolFlags.Alias) && !some(symbol.declarations, d => + !!findAncestor(d, isExportDeclaration) || + isNamespaceExport(d) || + (isImportEqualsDeclaration(d) && !isExternalModuleReference(d.moduleReference)) + ); + deferredPrivatesStack[isExternalImportAlias ? 0 : (deferredPrivatesStack.length - 1)].set(getSymbolId(symbol), symbol); + } - function isTypeOnlyNamespace(symbol: Symbol) { - return every(getNamespaceMembersForSerialization(symbol), m => !(resolveSymbol(m).flags & SymbolFlags.Value)); - } + function isExportingScope(enclosingDeclaration: Node) { + return ((isSourceFile(enclosingDeclaration) && (isExternalOrCommonJsModule(enclosingDeclaration) || isJsonSourceFile(enclosingDeclaration))) || + (isAmbientModule(enclosingDeclaration) && !isGlobalScopeAugmentation(enclosingDeclaration))); + } - function serializeModule(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { - const members = getNamespaceMembersForSerialization(symbol); - // Split NS members up by declaration - members whose parent symbol is the ns symbol vs those whose is not (but were added in later via merging) - const locationMap = arrayToMultiMap(members, m => m.parent && m.parent === symbol ? "real" : "merged"); - const realMembers = locationMap.get("real") || emptyArray; - const mergedMembers = locationMap.get("merged") || emptyArray; - // TODO: `suppressNewPrivateContext` is questionable -we need to simply be emitting privates in whatever scope they were declared in, rather - // than whatever scope we traverse to them in. That's a bit of a complex rewrite, since we're not _actually_ tracking privates at all in advance, - // so we don't even have placeholders to fill in. - if (length(realMembers)) { - const localName = getInternalSymbolName(symbol, symbolName); - serializeAsNamespaceDeclaration(realMembers, localName, modifierFlags, !!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Assignment))); + // Prepends a `declare` and/or `export` modifier if the context requires it, and then adds `node` to `result` and returns `node` + function addResult(node: Statement, additionalModifierFlags: ModifierFlags) { + if (canHaveModifiers(node)) { + let newModifierFlags: ModifierFlags = ModifierFlags.None; + const enclosingDeclaration = context.enclosingDeclaration && + (isJSDocTypeAlias(context.enclosingDeclaration) ? getSourceFileOfNode(context.enclosingDeclaration) : context.enclosingDeclaration); + if (additionalModifierFlags & ModifierFlags.Export && + enclosingDeclaration && (isExportingScope(enclosingDeclaration) || isModuleDeclaration(enclosingDeclaration)) && + canHaveExportModifier(node) + ) { + // Classes, namespaces, variables, functions, interfaces, and types should all be `export`ed in a module context if not private + newModifierFlags |= ModifierFlags.Export; } - if (length(mergedMembers)) { - const containingFile = getSourceFileOfNode(context.enclosingDeclaration); - const localName = getInternalSymbolName(symbol, symbolName); - const nsBody = factory.createModuleBlock([factory.createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports(mapDefined(filter(mergedMembers, n => n.escapedName !== InternalSymbolName.ExportEquals), s => { - const name = unescapeLeadingUnderscores(s.escapedName); - const localName = getInternalSymbolName(s, name); - const aliasDecl = s.declarations && getDeclarationOfAliasSymbol(s); - if (containingFile && (aliasDecl ? containingFile !== getSourceFileOfNode(aliasDecl) : !some(s.declarations, d => getSourceFileOfNode(d) === containingFile))) { - context.tracker?.reportNonlocalAugmentation?.(containingFile, symbol, s); - return undefined; - } - const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); - includePrivateSymbol(target || s); - const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName; - return factory.createExportSpecifier(/*isTypeOnly*/ false, name === targetName ? undefined : targetName, name); - })) - )]); - addResult(factory.createModuleDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - factory.createIdentifier(localName), - nsBody, - NodeFlags.Namespace - ), ModifierFlags.None); + if (addingDeclare && !(newModifierFlags & ModifierFlags.Export) && + (!enclosingDeclaration || !(enclosingDeclaration.flags & NodeFlags.Ambient)) && + (isEnumDeclaration(node) || isVariableStatement(node) || isFunctionDeclaration(node) || isClassDeclaration(node) || isModuleDeclaration(node))) { + // Classes, namespaces, variables, enums, and functions all need `declare` modifiers to be valid in a declaration file top-level scope + newModifierFlags |= ModifierFlags.Ambient; + } + if ((additionalModifierFlags & ModifierFlags.Default) && (isClassDeclaration(node) || isInterfaceDeclaration(node) || isFunctionDeclaration(node))) { + newModifierFlags |= ModifierFlags.Default; + } + if (newModifierFlags) { + node = factory.updateModifiers(node, newModifierFlags | getEffectiveModifierFlags(node)); } } + results.push(node); + } - function serializeEnum(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { - addResult(factory.createEnumDeclaration( + function serializeTypeAlias(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { + const aliasType = getDeclaredTypeOfTypeAlias(symbol); + const typeParams = getSymbolLinks(symbol).typeParameters; + const typeParamDecls = map(typeParams, p => typeParameterToDeclaration(p, context)); + const jsdocAliasDecl = symbol.declarations?.find(isJSDocTypeAlias); + const commentText = getTextOfJSDocComment(jsdocAliasDecl ? jsdocAliasDecl.comment || jsdocAliasDecl.parent.comment : undefined); + const oldFlags = context.flags; + context.flags |= NodeBuilderFlags.InTypeAlias; + const oldEnclosingDecl = context.enclosingDeclaration; + context.enclosingDeclaration = jsdocAliasDecl; + const typeNode = jsdocAliasDecl && jsdocAliasDecl.typeExpression + && isJSDocTypeExpression(jsdocAliasDecl.typeExpression) + && serializeExistingTypeNode(context, jsdocAliasDecl.typeExpression.type, includePrivateSymbol, bundled) + || typeToTypeNodeHelper(aliasType, context); + addResult(setSyntheticLeadingComments( + factory.createTypeAliasDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, getInternalSymbolName(symbol, symbolName), typeParamDecls, typeNode), + !commentText ? [] : [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }] + ), modifierFlags); + context.flags = oldFlags; + context.enclosingDeclaration = oldEnclosingDecl; + } + + function serializeInterface(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { + const interfaceType = getDeclaredTypeOfClassOrInterface(symbol); + const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); + const baseTypes = getBaseTypes(interfaceType); + const baseType = length(baseTypes) ? getIntersectionType(baseTypes) : undefined; + const members = flatMap(getPropertiesOfType(interfaceType), p => serializePropertySymbolForInterface(p, baseType)); + const callSignatures = serializeSignatures(SignatureKind.Call, interfaceType, baseType, SyntaxKind.CallSignature) as CallSignatureDeclaration[]; + const constructSignatures = serializeSignatures(SignatureKind.Construct, interfaceType, baseType, SyntaxKind.ConstructSignature) as ConstructSignatureDeclaration[]; + const indexSignatures = serializeIndexSignatures(interfaceType, baseType); + + const heritageClauses = !length(baseTypes) ? undefined : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, mapDefined(baseTypes, b => trySerializeAsTypeReference(b, SymbolFlags.Value)))]; + addResult(factory.createInterfaceDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + getInternalSymbolName(symbol, symbolName), + typeParamDecls, + heritageClauses, + [...indexSignatures, ...constructSignatures, ...callSignatures, ...members] + ), modifierFlags); + } + + function getNamespaceMembersForSerialization(symbol: Symbol) { + return !symbol.exports ? [] : filter(arrayFrom(symbol.exports.values()), isNamespaceMember); + } + + function isTypeOnlyNamespace(symbol: Symbol) { + return every(getNamespaceMembersForSerialization(symbol), m => !(resolveSymbol(m).flags & SymbolFlags.Value)); + } + + function serializeModule(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { + const members = getNamespaceMembersForSerialization(symbol); + // Split NS members up by declaration - members whose parent symbol is the ns symbol vs those whose is not (but were added in later via merging) + const locationMap = arrayToMultiMap(members, m => m.parent && m.parent === symbol ? "real" : "merged"); + const realMembers = locationMap.get("real") || emptyArray; + const mergedMembers = locationMap.get("merged") || emptyArray; + // TODO: `suppressNewPrivateContext` is questionable -we need to simply be emitting privates in whatever scope they were declared in, rather + // than whatever scope we traverse to them in. That's a bit of a complex rewrite, since we're not _actually_ tracking privates at all in advance, + // so we don't even have placeholders to fill in. + if (length(realMembers)) { + const localName = getInternalSymbolName(symbol, symbolName); + serializeAsNamespaceDeclaration(realMembers, localName, modifierFlags, !!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Assignment))); + } + if (length(mergedMembers)) { + const containingFile = getSourceFileOfNode(context.enclosingDeclaration); + const localName = getInternalSymbolName(symbol, symbolName); + const nsBody = factory.createModuleBlock([factory.createExportDeclaration( /*decorators*/ undefined, - factory.createModifiersFromModifierFlags(isConstEnumSymbol(symbol) ? ModifierFlags.Const : 0), - getInternalSymbolName(symbol, symbolName), - map(filter(getPropertiesOfType(getTypeOfSymbol(symbol)), p => !!(p.flags & SymbolFlags.EnumMember)), p => { - // TODO: Handle computed names - // I hate that to get the initialized value we need to walk back to the declarations here; but there's no - // other way to get the possible const value of an enum member that I'm aware of, as the value is cached - // _on the declaration_, not on the declaration's symbol... - const initializedValue = p.declarations && p.declarations[0] && isEnumMember(p.declarations[0]) ? getConstantValue(p.declarations[0]) : undefined; - return factory.createEnumMember(unescapeLeadingUnderscores(p.escapedName), initializedValue === undefined ? undefined : - typeof initializedValue === "string" ? factory.createStringLiteral(initializedValue) : - factory.createNumericLiteral(initializedValue)); - }) - ), modifierFlags); - } - - function serializeAsFunctionNamespaceMerge(type: Type, symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { - const signatures = getSignaturesOfType(type, SignatureKind.Call); - for (const sig of signatures) { - // Each overload becomes a separate function declaration, in order - const decl = signatureToSignatureDeclarationHelper(sig, SyntaxKind.FunctionDeclaration, context, { name: factory.createIdentifier(localName), privateSymbolVisitor: includePrivateSymbol, bundledImports: bundled }) as FunctionDeclaration; - addResult(setTextRange(decl, getSignatureTextRangeLocation(sig)), modifierFlags); - } - // Module symbol emit will take care of module-y members, provided it has exports - if (!(symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && !!symbol.exports && !!symbol.exports.size)) { - const props = filter(getPropertiesOfType(type), isNamespaceMember); - serializeAsNamespaceDeclaration(props, localName, modifierFlags, /*suppressNewPrivateContext*/ true); - } - } - - function getSignatureTextRangeLocation(signature: Signature) { - if (signature.declaration && signature.declaration.parent) { - if (isBinaryExpression(signature.declaration.parent) && getAssignmentDeclarationKind(signature.declaration.parent) === AssignmentDeclarationKind.Property) { - return signature.declaration.parent; - } - // for expressions assigned to `var`s, use the `var` as the text range - if (isVariableDeclaration(signature.declaration.parent) && signature.declaration.parent.parent) { - return signature.declaration.parent.parent; - } - } - return signature.declaration; - } - - function serializeAsNamespaceDeclaration(props: readonly Symbol[], localName: string, modifierFlags: ModifierFlags, suppressNewPrivateContext: boolean) { - if (length(props)) { - const localVsRemoteMap = arrayToMultiMap(props, p => - !length(p.declarations) || some(p.declarations, d => - getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!) - ) ? "local" : "remote" - ); - const localProps = localVsRemoteMap.get("local") || emptyArray; - // handle remote props first - we need to make an `import` declaration that points at the module containing each remote - // prop in the outermost scope (TODO: a namespace within a namespace would need to be appropriately handled by this) - // Example: - // import Foo_1 = require("./exporter"); - // export namespace ns { - // import Foo = Foo_1.Foo; - // export { Foo }; - // export const c: number; - // } - // This is needed because in JS, statements like `const x = require("./f")` support both type and value lookup, even if they're - // normally just value lookup (so it functions kinda like an alias even when it's not an alias) - // _Usually_, we'll simply print the top-level as an alias instead of a `var` in such situations, however is is theoretically - // possible to encounter a situation where a type has members from both the current file and other files - in those situations, - // emit akin to the above would be needed. - - // Add a namespace - // Create namespace as non-synthetic so it is usable as an enclosing declaration - let fakespace = parseNodeFactory.createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createIdentifier(localName), factory.createModuleBlock([]), NodeFlags.Namespace); - setParent(fakespace, enclosingDeclaration as SourceFile | NamespaceDeclaration); - fakespace.locals = createSymbolTable(props); - fakespace.symbol = props[0].parent!; - - const oldResults = results; - results = []; - const oldAddingDeclare = addingDeclare; - addingDeclare = false; - const subcontext = { ...context, enclosingDeclaration: fakespace }; - const oldContext = context; - context = subcontext; - // TODO: implement handling for the localVsRemoteMap.get("remote") - should be difficult to trigger (see comment above), as only interesting cross-file js merges should make this possible - visitSymbolTable(createSymbolTable(localProps), suppressNewPrivateContext, /*propertyAsAlias*/ true); - context = oldContext; - addingDeclare = oldAddingDeclare; - const declarations = results; - results = oldResults; - // replace namespace with synthetic version - const defaultReplaced = map(declarations, d => isExportAssignment(d) && !d.isExportEquals && isIdentifier(d.expression) ? factory.createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, d.expression, factory.createIdentifier(InternalSymbolName.Default))]) - ) : d); - const exportModifierStripped = every(defaultReplaced, d => hasSyntacticModifier(d, ModifierFlags.Export)) ? map(defaultReplaced, removeExportModifier) : defaultReplaced; - fakespace = factory.updateModuleDeclaration( - fakespace, - fakespace.decorators, - fakespace.modifiers, - fakespace.name, - factory.createModuleBlock(exportModifierStripped)); - addResult(fakespace, modifierFlags); // namespaces can never be default exported - } - } - - function isNamespaceMember(p: Symbol) { - return !!(p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias)) || - !(p.flags & SymbolFlags.Prototype || p.escapedName === "prototype" || p.valueDeclaration && isStatic(p.valueDeclaration) && isClassLike(p.valueDeclaration.parent)); - } - - function sanitizeJSDocImplements(clauses: readonly ExpressionWithTypeArguments[]): ExpressionWithTypeArguments[] | undefined { - const result = mapDefined(clauses, e => { - const oldEnclosing = context.enclosingDeclaration; - context.enclosingDeclaration = e; - let expr = e.expression; - if (isEntityNameExpression(expr)) { - if (isIdentifier(expr) && idText(expr) === "") { - return cleanup(/*result*/ undefined); // Empty heritage clause, should be an error, but prefer emitting no heritage clauses to reemitting the empty one - } - let introducesError: boolean; - ({ introducesError, node: expr } = trackExistingEntityName(expr, context, includePrivateSymbol)); - if (introducesError) { - return cleanup(/*result*/ undefined); + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(mapDefined(filter(mergedMembers, n => n.escapedName !== InternalSymbolName.ExportEquals), s => { + const name = unescapeLeadingUnderscores(s.escapedName); + const localName = getInternalSymbolName(s, name); + const aliasDecl = s.declarations && getDeclarationOfAliasSymbol(s); + if (containingFile && (aliasDecl ? containingFile !== getSourceFileOfNode(aliasDecl) : !some(s.declarations, d => getSourceFileOfNode(d) === containingFile))) { + context.tracker?.reportNonlocalAugmentation?.(containingFile, symbol, s); + return undefined; } - } - return cleanup(factory.createExpressionWithTypeArguments(expr, - map(e.typeArguments, a => - serializeExistingTypeNode(context, a, includePrivateSymbol, bundled) - || typeToTypeNodeHelper(getTypeFromTypeNode(a), context) - ) - )); + const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); + includePrivateSymbol(target || s); + const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName; + return factory.createExportSpecifier(/*isTypeOnly*/ false, name === targetName ? undefined : targetName, name); + })) + )]); + addResult(factory.createModuleDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createIdentifier(localName), + nsBody, + NodeFlags.Namespace + ), ModifierFlags.None); + } + } - function cleanup(result: T): T { - context.enclosingDeclaration = oldEnclosing; - return result; + function serializeEnum(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { + addResult(factory.createEnumDeclaration( + /*decorators*/ undefined, + factory.createModifiersFromModifierFlags(isConstEnumSymbol(symbol) ? ModifierFlags.Const : 0), + getInternalSymbolName(symbol, symbolName), + map(filter(getPropertiesOfType(getTypeOfSymbol(symbol)), p => !!(p.flags & SymbolFlags.EnumMember)), p => { + // TODO: Handle computed names + // I hate that to get the initialized value we need to walk back to the declarations here; but there's no + // other way to get the possible const value of an enum member that I'm aware of, as the value is cached + // _on the declaration_, not on the declaration's symbol... + const initializedValue = p.declarations && p.declarations[0] && isEnumMember(p.declarations[0]) ? getConstantValue(p.declarations[0]) : undefined; + return factory.createEnumMember(unescapeLeadingUnderscores(p.escapedName), initializedValue === undefined ? undefined : + typeof initializedValue === "string" ? factory.createStringLiteral(initializedValue) : + factory.createNumericLiteral(initializedValue)); + }) + ), modifierFlags); + } + + function serializeAsFunctionNamespaceMerge(type: Type, symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { + const signatures = getSignaturesOfType(type, SignatureKind.Call); + for (const sig of signatures) { + // Each overload becomes a separate function declaration, in order + const decl = signatureToSignatureDeclarationHelper(sig, SyntaxKind.FunctionDeclaration, context, { name: factory.createIdentifier(localName), privateSymbolVisitor: includePrivateSymbol, bundledImports: bundled }) as FunctionDeclaration; + addResult(setTextRange(decl, getSignatureTextRangeLocation(sig)), modifierFlags); + } + // Module symbol emit will take care of module-y members, provided it has exports + if (!(symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && !!symbol.exports && !!symbol.exports.size)) { + const props = filter(getPropertiesOfType(type), isNamespaceMember); + serializeAsNamespaceDeclaration(props, localName, modifierFlags, /*suppressNewPrivateContext*/ true); + } + } + + function getSignatureTextRangeLocation(signature: Signature) { + if (signature.declaration && signature.declaration.parent) { + if (isBinaryExpression(signature.declaration.parent) && getAssignmentDeclarationKind(signature.declaration.parent) === AssignmentDeclarationKind.Property) { + return signature.declaration.parent; + } + // for expressions assigned to `var`s, use the `var` as the text range + if (isVariableDeclaration(signature.declaration.parent) && signature.declaration.parent.parent) { + return signature.declaration.parent.parent; + } + } + return signature.declaration; + } + + function serializeAsNamespaceDeclaration(props: readonly Symbol[], localName: string, modifierFlags: ModifierFlags, suppressNewPrivateContext: boolean) { + if (length(props)) { + const localVsRemoteMap = arrayToMultiMap(props, p => + !length(p.declarations) || some(p.declarations, d => + getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!) + ) ? "local" : "remote" + ); + const localProps = localVsRemoteMap.get("local") || emptyArray; + // handle remote props first - we need to make an `import` declaration that points at the module containing each remote + // prop in the outermost scope (TODO: a namespace within a namespace would need to be appropriately handled by this) + // Example: + // import Foo_1 = require("./exporter"); + // export namespace ns { + // import Foo = Foo_1.Foo; + // export { Foo }; + // export const c: number; + // } + // This is needed because in JS, statements like `const x = require("./f")` support both type and value lookup, even if they're + // normally just value lookup (so it functions kinda like an alias even when it's not an alias) + // _Usually_, we'll simply print the top-level as an alias instead of a `var` in such situations, however is is theoretically + // possible to encounter a situation where a type has members from both the current file and other files - in those situations, + // emit akin to the above would be needed. + + // Add a namespace + // Create namespace as non-synthetic so it is usable as an enclosing declaration + let fakespace = parseNodeFactory.createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createIdentifier(localName), factory.createModuleBlock([]), NodeFlags.Namespace); + setParent(fakespace, enclosingDeclaration as SourceFile | NamespaceDeclaration); + fakespace.locals = createSymbolTable(props); + fakespace.symbol = props[0].parent!; + + const oldResults = results; + results = []; + const oldAddingDeclare = addingDeclare; + addingDeclare = false; + const subcontext = { ...context, enclosingDeclaration: fakespace }; + const oldContext = context; + context = subcontext; + // TODO: implement handling for the localVsRemoteMap.get("remote") - should be difficult to trigger (see comment above), as only interesting cross-file js merges should make this possible + visitSymbolTable(createSymbolTable(localProps), suppressNewPrivateContext, /*propertyAsAlias*/ true); + context = oldContext; + addingDeclare = oldAddingDeclare; + const declarations = results; + results = oldResults; + // replace namespace with synthetic version + const defaultReplaced = map(declarations, d => isExportAssignment(d) && !d.isExportEquals && isIdentifier(d.expression) ? factory.createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, d.expression, factory.createIdentifier(InternalSymbolName.Default))]) + ) : d); + const exportModifierStripped = every(defaultReplaced, d => hasSyntacticModifier(d, ModifierFlags.Export)) ? map(defaultReplaced, removeExportModifier) : defaultReplaced; + fakespace = factory.updateModuleDeclaration( + fakespace, + fakespace.decorators, + fakespace.modifiers, + fakespace.name, + factory.createModuleBlock(exportModifierStripped)); + addResult(fakespace, modifierFlags); // namespaces can never be default exported + } + } + + function isNamespaceMember(p: Symbol) { + return !!(p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias)) || + !(p.flags & SymbolFlags.Prototype || p.escapedName === "prototype" || p.valueDeclaration && isStatic(p.valueDeclaration) && isClassLike(p.valueDeclaration.parent)); + } + + function sanitizeJSDocImplements(clauses: readonly ExpressionWithTypeArguments[]): ExpressionWithTypeArguments[] | undefined { + const result = mapDefined(clauses, e => { + const oldEnclosing = context.enclosingDeclaration; + context.enclosingDeclaration = e; + let expr = e.expression; + if (isEntityNameExpression(expr)) { + if (isIdentifier(expr) && idText(expr) === "") { + return cleanup(/*result*/ undefined); // Empty heritage clause, should be an error, but prefer emitting no heritage clauses to reemitting the empty one } - }); - if (result.length === clauses.length) { + let introducesError: boolean; + ({ introducesError, node: expr } = trackExistingEntityName(expr, context, includePrivateSymbol)); + if (introducesError) { + return cleanup(/*result*/ undefined); + } + } + return cleanup(factory.createExpressionWithTypeArguments(expr, + map(e.typeArguments, a => + serializeExistingTypeNode(context, a, includePrivateSymbol, bundled) + || typeToTypeNodeHelper(getTypeFromTypeNode(a), context) + ) + )); + + function cleanup(result: T): T { + context.enclosingDeclaration = oldEnclosing; return result; } - return undefined; + }); + if (result.length === clauses.length) { + return result; } + return undefined; + } - function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { - const originalDecl = symbol.declarations?.find(isClassLike); - const oldEnclosing = context.enclosingDeclaration; - context.enclosingDeclaration = originalDecl || oldEnclosing; - const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); - const classType = getDeclaredTypeOfClassOrInterface(symbol); - const baseTypes = getBaseTypes(classType); - const originalImplements = originalDecl && getEffectiveImplementsTypeNodes(originalDecl); - const implementsExpressions = originalImplements && sanitizeJSDocImplements(originalImplements) - || mapDefined(getImplementsTypes(classType), serializeImplementedType); - const staticType = getTypeOfSymbol(symbol); - const isClass = !!staticType.symbol?.valueDeclaration && isClassLike(staticType.symbol.valueDeclaration); - const staticBaseType = isClass - ? getBaseConstructorTypeOfClass(staticType as InterfaceType) - : anyType; - const heritageClauses = [ - ...!length(baseTypes) ? [] : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))], - ...!length(implementsExpressions) ? [] : [factory.createHeritageClause(SyntaxKind.ImplementsKeyword, implementsExpressions)] - ]; - const symbolProps = getNonInterhitedProperties(classType, baseTypes, getPropertiesOfType(classType)); - const publicSymbolProps = filter(symbolProps, s => { - // `valueDeclaration` could be undefined if inherited from - // a union/intersection base type, but inherited properties - // don't matter here. - const valueDecl = s.valueDeclaration; - return !!valueDecl && !(isNamedDeclaration(valueDecl) && isPrivateIdentifier(valueDecl.name)); - }); - const hasPrivateIdentifier = some(symbolProps, s => { - // `valueDeclaration` could be undefined if inherited from - // a union/intersection base type, but inherited properties - // don't matter here. - const valueDecl = s.valueDeclaration; - return !!valueDecl && isNamedDeclaration(valueDecl) && isPrivateIdentifier(valueDecl.name); - }); - // Boil down all private properties into a single one. - const privateProperties = hasPrivateIdentifier ? - [factory.createPropertyDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - factory.createPrivateIdentifier("#private"), - /*questionOrExclamationToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined, - )] : - emptyArray; - const publicProperties = flatMap(publicSymbolProps, p => serializePropertySymbolForClass(p, /*isStatic*/ false, baseTypes[0])); - // Consider static members empty if symbol also has function or module meaning - function namespacey emit will handle statics - const staticMembers = flatMap( - filter(getPropertiesOfType(staticType), p => !(p.flags & SymbolFlags.Prototype) && p.escapedName !== "prototype" && !isNamespaceMember(p)), - p => serializePropertySymbolForClass(p, /*isStatic*/ true, staticBaseType)); - // When we encounter an `X.prototype.y` assignment in a JS file, we bind `X` as a class regardless as to whether - // the value is ever initialized with a class or function-like value. For cases where `X` could never be - // created via `new`, we will inject a `private constructor()` declaration to indicate it is not createable. - const isNonConstructableClassLikeInJsFile = - !isClass && - !!symbol.valueDeclaration && - isInJSFile(symbol.valueDeclaration) && - !some(getSignaturesOfType(staticType, SignatureKind.Construct)); - const constructors = isNonConstructableClassLikeInJsFile ? - [factory.createConstructorDeclaration(/*decorators*/ undefined, factory.createModifiersFromModifierFlags(ModifierFlags.Private), [], /*body*/ undefined)] : - serializeSignatures(SignatureKind.Construct, staticType, staticBaseType, SyntaxKind.Constructor) as ConstructorDeclaration[]; - const indexSignatures = serializeIndexSignatures(classType, baseTypes[0]); - context.enclosingDeclaration = oldEnclosing; - addResult(setTextRange(factory.createClassDeclaration( + function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { + const originalDecl = symbol.declarations?.find(isClassLike); + const oldEnclosing = context.enclosingDeclaration; + context.enclosingDeclaration = originalDecl || oldEnclosing; + const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); + const classType = getDeclaredTypeOfClassOrInterface(symbol); + const baseTypes = getBaseTypes(classType); + const originalImplements = originalDecl && getEffectiveImplementsTypeNodes(originalDecl); + const implementsExpressions = originalImplements && sanitizeJSDocImplements(originalImplements) + || mapDefined(getImplementsTypes(classType), serializeImplementedType); + const staticType = getTypeOfSymbol(symbol); + const isClass = !!staticType.symbol?.valueDeclaration && isClassLike(staticType.symbol.valueDeclaration); + const staticBaseType = isClass + ? getBaseConstructorTypeOfClass(staticType as InterfaceType) + : anyType; + const heritageClauses = [ + ...!length(baseTypes) ? [] : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))], + ...!length(implementsExpressions) ? [] : [factory.createHeritageClause(SyntaxKind.ImplementsKeyword, implementsExpressions)] + ]; + const symbolProps = getNonInterhitedProperties(classType, baseTypes, getPropertiesOfType(classType)); + const publicSymbolProps = filter(symbolProps, s => { + // `valueDeclaration` could be undefined if inherited from + // a union/intersection base type, but inherited properties + // don't matter here. + const valueDecl = s.valueDeclaration; + return !!valueDecl && !(isNamedDeclaration(valueDecl) && isPrivateIdentifier(valueDecl.name)); + }); + const hasPrivateIdentifier = some(symbolProps, s => { + // `valueDeclaration` could be undefined if inherited from + // a union/intersection base type, but inherited properties + // don't matter here. + const valueDecl = s.valueDeclaration; + return !!valueDecl && isNamedDeclaration(valueDecl) && isPrivateIdentifier(valueDecl.name); + }); + // Boil down all private properties into a single one. + const privateProperties = hasPrivateIdentifier ? + [factory.createPropertyDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, - localName, - typeParamDecls, - heritageClauses, - [...indexSignatures, ...staticMembers, ...constructors, ...publicProperties, ...privateProperties] - ), symbol.declarations && filter(symbol.declarations, d => isClassDeclaration(d) || isClassExpression(d))[0]), modifierFlags); - } - - function getSomeTargetNameFromDeclarations(declarations: Declaration[] | undefined) { - return firstDefined(declarations, d => { - if (isImportSpecifier(d) || isExportSpecifier(d)) { - return idText(d.propertyName || d.name); - } - if (isBinaryExpression(d) || isExportAssignment(d)) { - const expression = isExportAssignment(d) ? d.expression : d.right; - if (isPropertyAccessExpression(expression)) { - return idText(expression.name); - } - } - if (isAliasSymbolDeclaration(d)) { - // This is... heuristic, at best. But it's probably better than always printing the name of the shorthand ambient module. - const name = getNameOfDeclaration(d); - if (name && isIdentifier(name)) { - return idText(name); - } - } - return undefined; - }); - } + factory.createPrivateIdentifier("#private"), + /*questionOrExclamationToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined, + )] : + emptyArray; + const publicProperties = flatMap(publicSymbolProps, p => serializePropertySymbolForClass(p, /*isStatic*/ false, baseTypes[0])); + // Consider static members empty if symbol also has function or module meaning - function namespacey emit will handle statics + const staticMembers = flatMap( + filter(getPropertiesOfType(staticType), p => !(p.flags & SymbolFlags.Prototype) && p.escapedName !== "prototype" && !isNamespaceMember(p)), + p => serializePropertySymbolForClass(p, /*isStatic*/ true, staticBaseType)); + // When we encounter an `X.prototype.y` assignment in a JS file, we bind `X` as a class regardless as to whether + // the value is ever initialized with a class or function-like value. For cases where `X` could never be + // created via `new`, we will inject a `private constructor()` declaration to indicate it is not createable. + const isNonConstructableClassLikeInJsFile = + !isClass && + !!symbol.valueDeclaration && + isInJSFile(symbol.valueDeclaration) && + !some(getSignaturesOfType(staticType, SignatureKind.Construct)); + const constructors = isNonConstructableClassLikeInJsFile ? + [factory.createConstructorDeclaration(/*decorators*/ undefined, factory.createModifiersFromModifierFlags(ModifierFlags.Private), [], /*body*/ undefined)] : + serializeSignatures(SignatureKind.Construct, staticType, staticBaseType, SyntaxKind.Constructor) as ConstructorDeclaration[]; + const indexSignatures = serializeIndexSignatures(classType, baseTypes[0]); + context.enclosingDeclaration = oldEnclosing; + addResult(setTextRange(factory.createClassDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + localName, + typeParamDecls, + heritageClauses, + [...indexSignatures, ...staticMembers, ...constructors, ...publicProperties, ...privateProperties] + ), symbol.declarations && filter(symbol.declarations, d => isClassDeclaration(d) || isClassExpression(d))[0]), modifierFlags); + } - function serializeAsAlias(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { - // synthesize an alias, eg `export { symbolName as Name }` - // need to mark the alias `symbol` points at - // as something we need to serialize as a private declaration as well - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - const target = getMergedSymbol(getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true)); - if (!target) { - return; + function getSomeTargetNameFromDeclarations(declarations: Declaration[] | undefined) { + return firstDefined(declarations, d => { + if (isImportSpecifier(d) || isExportSpecifier(d)) { + return idText(d.propertyName || d.name); } - // If `target` refers to a shorthand module symbol, the name we're trying to pull out isn;t recoverable from the target symbol - // In such a scenario, we must fall back to looking for an alias declaration on `symbol` and pulling the target name from that - let verbatimTargetName = isShorthandAmbientModuleSymbol(target) && getSomeTargetNameFromDeclarations(symbol.declarations) || unescapeLeadingUnderscores(target.escapedName); - if (verbatimTargetName === InternalSymbolName.ExportEquals && (getESModuleInterop(compilerOptions) || compilerOptions.allowSyntheticDefaultImports)) { - // target refers to an `export=` symbol that was hoisted into a synthetic default - rename here to match - verbatimTargetName = InternalSymbolName.Default; + if (isBinaryExpression(d) || isExportAssignment(d)) { + const expression = isExportAssignment(d) ? d.expression : d.right; + if (isPropertyAccessExpression(expression)) { + return idText(expression.name); + } } - const targetName = getInternalSymbolName(target, verbatimTargetName); - includePrivateSymbol(target); // the target may be within the same scope - attempt to serialize it first - switch (node.kind) { - case SyntaxKind.BindingElement: - if (node.parent?.parent?.kind === SyntaxKind.VariableDeclaration) { - // const { SomeClass } = require('./lib'); - const specifier = getSpecifierForModuleSymbol(target.parent || target, context); // './lib' - const { propertyName } = node as BindingElement; - addResult(factory.createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamedImports([factory.createImportSpecifier( - /*isTypeOnly*/ false, - propertyName && isIdentifier(propertyName) ? factory.createIdentifier(idText(propertyName)) : undefined, - factory.createIdentifier(localName) - )])), - factory.createStringLiteral(specifier), - /*importClause*/ undefined - ), ModifierFlags.None); - break; - } - // We don't know how to serialize this (nested?) binding element - Debug.failBadSyntaxKind(node.parent?.parent || node, "Unhandled binding element grandparent kind in declaration serialization"); - break; - case SyntaxKind.ShorthandPropertyAssignment: - if (node.parent?.parent?.kind === SyntaxKind.BinaryExpression) { - // module.exports = { SomeClass } - serializeExportSpecifier( - unescapeLeadingUnderscores(symbol.escapedName), - targetName - ); - } - break; - case SyntaxKind.VariableDeclaration: - // commonjs require: const x = require('y') - if (isPropertyAccessExpression((node as VariableDeclaration).initializer!)) { - // const x = require('y').z - const initializer = (node as VariableDeclaration).initializer! as PropertyAccessExpression; // require('y').z - const uniqueName = factory.createUniqueName(localName); // _x - const specifier = getSpecifierForModuleSymbol(target.parent || target, context); // 'y' - // import _x = require('y'); - addResult(factory.createImportEqualsDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - uniqueName, - factory.createExternalModuleReference(factory.createStringLiteral(specifier)) - ), ModifierFlags.None); - // import x = _x.z - addResult(factory.createImportEqualsDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createIdentifier(localName), - factory.createQualifiedName(uniqueName, initializer.name as Identifier), - ), modifierFlags); - break; - } - // else fall through and treat commonjs require just like import= - case SyntaxKind.ImportEqualsDeclaration: - // This _specifically_ only exists to handle json declarations - where we make aliases, but since - // we emit no declarations for the json document, must not refer to it in the declarations - if (target.escapedName === InternalSymbolName.ExportEquals && some(target.declarations, isJsonSourceFile)) { - serializeMaybeAliasAssignment(symbol); - break; - } - // Could be a local `import localName = ns.member` or - // an external `import localName = require("whatever")` - const isLocalImport = !(target.flags & SymbolFlags.ValueModule) && !isVariableDeclaration(node); - addResult(factory.createImportEqualsDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createIdentifier(localName), - isLocalImport - ? symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) - : factory.createExternalModuleReference(factory.createStringLiteral(getSpecifierForModuleSymbol(target, context))) - ), isLocalImport ? modifierFlags : ModifierFlags.None); - break; - case SyntaxKind.NamespaceExportDeclaration: - // export as namespace foo - // TODO: Not part of a file's local or export symbol tables - // Is bound into file.symbol.globalExports instead, which we don't currently traverse - addResult(factory.createNamespaceExportDeclaration(idText((node as NamespaceExportDeclaration).name)), ModifierFlags.None); - break; - case SyntaxKind.ImportClause: + if (isAliasSymbolDeclaration(d)) { + // This is... heuristic, at best. But it's probably better than always printing the name of the shorthand ambient module. + const name = getNameOfDeclaration(d); + if (name && isIdentifier(name)) { + return idText(name); + } + } + return undefined; + }); + } + + function serializeAsAlias(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { + // synthesize an alias, eg `export { symbolName as Name }` + // need to mark the alias `symbol` points at + // as something we need to serialize as a private declaration as well + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) return Debug.fail(); + const target = getMergedSymbol(getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true)); + if (!target) { + return; + } + // If `target` refers to a shorthand module symbol, the name we're trying to pull out isn;t recoverable from the target symbol + // In such a scenario, we must fall back to looking for an alias declaration on `symbol` and pulling the target name from that + let verbatimTargetName = isShorthandAmbientModuleSymbol(target) && getSomeTargetNameFromDeclarations(symbol.declarations) || unescapeLeadingUnderscores(target.escapedName); + if (verbatimTargetName === InternalSymbolName.ExportEquals && (getESModuleInterop(compilerOptions) || compilerOptions.allowSyntheticDefaultImports)) { + // target refers to an `export=` symbol that was hoisted into a synthetic default - rename here to match + verbatimTargetName = InternalSymbolName.Default; + } + const targetName = getInternalSymbolName(target, verbatimTargetName); + includePrivateSymbol(target); // the target may be within the same scope - attempt to serialize it first + switch (node.kind) { + case SyntaxKind.BindingElement: + if (node.parent?.parent?.kind === SyntaxKind.VariableDeclaration) { + // const { SomeClass } = require('./lib'); + const specifier = getSpecifierForModuleSymbol(target.parent || target, context); // './lib' + const { propertyName } = node as BindingElement; addResult(factory.createImportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, - factory.createImportClause(/*isTypeOnly*/ false, factory.createIdentifier(localName), /*namedBindings*/ undefined), - // We use `target.parent || target` below as `target.parent` is unset when the target is a module which has been export assigned - // And then made into a default by the `esModuleInterop` or `allowSyntheticDefaultImports` flag - // In such cases, the `target` refers to the module itself already - factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)), - /*assertClause*/ undefined + factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamedImports([factory.createImportSpecifier( + /*isTypeOnly*/ false, + propertyName && isIdentifier(propertyName) ? factory.createIdentifier(idText(propertyName)) : undefined, + factory.createIdentifier(localName) + )])), + factory.createStringLiteral(specifier), + /*importClause*/ undefined ), ModifierFlags.None); break; - case SyntaxKind.NamespaceImport: - addResult(factory.createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - factory.createImportClause(/*isTypeOnly*/ false, /*importClause*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))), - factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)), - /*assertClause*/ undefined - ), ModifierFlags.None); - break; - case SyntaxKind.NamespaceExport: - addResult(factory.createExportDeclaration( + } + // We don't know how to serialize this (nested?) binding element + Debug.failBadSyntaxKind(node.parent?.parent || node, "Unhandled binding element grandparent kind in declaration serialization"); + break; + case SyntaxKind.ShorthandPropertyAssignment: + if (node.parent?.parent?.kind === SyntaxKind.BinaryExpression) { + // module.exports = { SomeClass } + serializeExportSpecifier( + unescapeLeadingUnderscores(symbol.escapedName), + targetName + ); + } + break; + case SyntaxKind.VariableDeclaration: + // commonjs require: const x = require('y') + if (isPropertyAccessExpression((node as VariableDeclaration).initializer!)) { + // const x = require('y').z + const initializer = (node as VariableDeclaration).initializer! as PropertyAccessExpression; // require('y').z + const uniqueName = factory.createUniqueName(localName); // _x + const specifier = getSpecifierForModuleSymbol(target.parent || target, context); // 'y' + // import _x = require('y'); + addResult(factory.createImportEqualsDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamespaceExport(factory.createIdentifier(localName)), - factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)) + uniqueName, + factory.createExternalModuleReference(factory.createStringLiteral(specifier)) ), ModifierFlags.None); - break; - case SyntaxKind.ImportSpecifier: - addResult(factory.createImportDeclaration( + // import x = _x.z + addResult(factory.createImportEqualsDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, - factory.createImportClause( - /*isTypeOnly*/ false, - /*importClause*/ undefined, - factory.createNamedImports([ - factory.createImportSpecifier( - /*isTypeOnly*/ false, - localName !== verbatimTargetName ? factory.createIdentifier(verbatimTargetName) : undefined, - factory.createIdentifier(localName) - ) - ])), - factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)), - /*assertClause*/ undefined - ), ModifierFlags.None); - break; - case SyntaxKind.ExportSpecifier: - // does not use localName because the symbol name in this case refers to the name in the exports table, - // which we must exactly preserve - const specifier = (node.parent.parent as ExportDeclaration).moduleSpecifier; - // targetName is only used when the target is local, as otherwise the target is an alias that points at - // another file - serializeExportSpecifier( - unescapeLeadingUnderscores(symbol.escapedName), - specifier ? verbatimTargetName : targetName, - specifier && isStringLiteralLike(specifier) ? factory.createStringLiteral(specifier.text) : undefined - ); + /*isTypeOnly*/ false, + factory.createIdentifier(localName), + factory.createQualifiedName(uniqueName, initializer.name as Identifier), + ), modifierFlags); break; - case SyntaxKind.ExportAssignment: + } + // else fall through and treat commonjs require just like import= + case SyntaxKind.ImportEqualsDeclaration: + // This _specifically_ only exists to handle json declarations - where we make aliases, but since + // we emit no declarations for the json document, must not refer to it in the declarations + if (target.escapedName === InternalSymbolName.ExportEquals && some(target.declarations, isJsonSourceFile)) { serializeMaybeAliasAssignment(symbol); break; - case SyntaxKind.BinaryExpression: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - // Could be best encoded as though an export specifier or as though an export assignment - // If name is default or export=, do an export assignment - // Otherwise do an export specifier - if (symbol.escapedName === InternalSymbolName.Default || symbol.escapedName === InternalSymbolName.ExportEquals) { - serializeMaybeAliasAssignment(symbol); - } - else { - serializeExportSpecifier(localName, targetName); - } - break; - default: - return Debug.failBadSyntaxKind(node, "Unhandled alias declaration kind in symbol serializer!"); - } - } - - function serializeExportSpecifier(localName: string, targetName: string, specifier?: Expression) { - addResult(factory.createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, localName !== targetName ? targetName : undefined, localName)]), - specifier - ), ModifierFlags.None); - } - - /** - * Returns `true` if an export assignment or declaration was produced for the symbol - */ - function serializeMaybeAliasAssignment(symbol: Symbol): boolean { - if (symbol.flags & SymbolFlags.Prototype) { - return false; - } - const name = unescapeLeadingUnderscores(symbol.escapedName); - const isExportEquals = name === InternalSymbolName.ExportEquals; - const isDefault = name === InternalSymbolName.Default; - const isExportAssignmentCompatibleSymbolName = isExportEquals || isDefault; - // synthesize export = ref - // ref should refer to either be a locally scoped symbol which we need to emit, or - // a reference to another namespace/module which we may need to emit an `import` statement for - const aliasDecl = symbol.declarations && getDeclarationOfAliasSymbol(symbol); - // serialize what the alias points to, preserve the declaration's initializer - const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); - // If the target resolves and resolves to a thing defined in this file, emit as an alias, otherwise emit as a const - if (target && length(target.declarations) && some(target.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(enclosingDeclaration))) { - // In case `target` refers to a namespace member, look at the declaration and serialize the leftmost symbol in it - // eg, `namespace A { export class B {} }; exports = A.B;` - // Technically, this is all that's required in the case where the assignment is an entity name expression - const expr = aliasDecl && ((isExportAssignment(aliasDecl) || isBinaryExpression(aliasDecl)) ? getExportAssignmentExpression(aliasDecl) : getPropertyAssignmentAliasLikeExpression(aliasDecl as ShorthandPropertyAssignment | PropertyAssignment | PropertyAccessExpression)); - const first = expr && isEntityNameExpression(expr) ? getFirstNonModuleExportsIdentifier(expr) : undefined; - const referenced = first && resolveEntityName(first, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, enclosingDeclaration); - if (referenced || target) { - includePrivateSymbol(referenced || target); - } - - // We disable the context's symbol tracker for the duration of this name serialization - // as, by virtue of being here, the name is required to print something, and we don't want to - // issue a visibility error on it. Only anonymous classes that an alias points at _would_ issue - // a visibility error here (as they're not visible within any scope), but we want to hoist them - // into the containing scope anyway, so we want to skip the visibility checks. - const oldTrack = context.tracker.trackSymbol; - context.tracker.trackSymbol = () => false; - if (isExportAssignmentCompatibleSymbolName) { - results.push(factory.createExportAssignment( - /*decorators*/ undefined, - /*modifiers*/ undefined, - isExportEquals, - symbolToExpression(target, context, SymbolFlags.All) - )); + } + // Could be a local `import localName = ns.member` or + // an external `import localName = require("whatever")` + const isLocalImport = !(target.flags & SymbolFlags.ValueModule) && !isVariableDeclaration(node); + addResult(factory.createImportEqualsDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createIdentifier(localName), + isLocalImport + ? symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) + : factory.createExternalModuleReference(factory.createStringLiteral(getSpecifierForModuleSymbol(target, context))) + ), isLocalImport ? modifierFlags : ModifierFlags.None); + break; + case SyntaxKind.NamespaceExportDeclaration: + // export as namespace foo + // TODO: Not part of a file's local or export symbol tables + // Is bound into file.symbol.globalExports instead, which we don't currently traverse + addResult(factory.createNamespaceExportDeclaration(idText((node as NamespaceExportDeclaration).name)), ModifierFlags.None); + break; + case SyntaxKind.ImportClause: + addResult(factory.createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createImportClause(/*isTypeOnly*/ false, factory.createIdentifier(localName), /*namedBindings*/ undefined), + // We use `target.parent || target` below as `target.parent` is unset when the target is a module which has been export assigned + // And then made into a default by the `esModuleInterop` or `allowSyntheticDefaultImports` flag + // In such cases, the `target` refers to the module itself already + factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)), + /*assertClause*/ undefined + ), ModifierFlags.None); + break; + case SyntaxKind.NamespaceImport: + addResult(factory.createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createImportClause(/*isTypeOnly*/ false, /*importClause*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))), + factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)), + /*assertClause*/ undefined + ), ModifierFlags.None); + break; + case SyntaxKind.NamespaceExport: + addResult(factory.createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamespaceExport(factory.createIdentifier(localName)), + factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)) + ), ModifierFlags.None); + break; + case SyntaxKind.ImportSpecifier: + addResult(factory.createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createImportClause( + /*isTypeOnly*/ false, + /*importClause*/ undefined, + factory.createNamedImports([ + factory.createImportSpecifier( + /*isTypeOnly*/ false, + localName !== verbatimTargetName ? factory.createIdentifier(verbatimTargetName) : undefined, + factory.createIdentifier(localName) + ) + ])), + factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)), + /*assertClause*/ undefined + ), ModifierFlags.None); + break; + case SyntaxKind.ExportSpecifier: + // does not use localName because the symbol name in this case refers to the name in the exports table, + // which we must exactly preserve + const specifier = (node.parent.parent as ExportDeclaration).moduleSpecifier; + // targetName is only used when the target is local, as otherwise the target is an alias that points at + // another file + serializeExportSpecifier( + unescapeLeadingUnderscores(symbol.escapedName), + specifier ? verbatimTargetName : targetName, + specifier && isStringLiteralLike(specifier) ? factory.createStringLiteral(specifier.text) : undefined + ); + break; + case SyntaxKind.ExportAssignment: + serializeMaybeAliasAssignment(symbol); + break; + case SyntaxKind.BinaryExpression: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + // Could be best encoded as though an export specifier or as though an export assignment + // If name is default or export=, do an export assignment + // Otherwise do an export specifier + if (symbol.escapedName === InternalSymbolName.Default || symbol.escapedName === InternalSymbolName.ExportEquals) { + serializeMaybeAliasAssignment(symbol); } else { - if (first === expr && first) { - // serialize as `export {target as name}` - serializeExportSpecifier(name, idText(first)); - } - else if (expr && isClassExpression(expr)) { - serializeExportSpecifier(name, getInternalSymbolName(target, symbolName(target))); - } - else { - // serialize as `import _Ref = t.arg.et; export { _Ref as name }` - const varName = getUnusedName(name, symbol); - addResult(factory.createImportEqualsDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createIdentifier(varName), - symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) - ), ModifierFlags.None); - serializeExportSpecifier(name, varName); - } + serializeExportSpecifier(localName, targetName); } - context.tracker.trackSymbol = oldTrack; - return true; + break; + default: + return Debug.failBadSyntaxKind(node, "Unhandled alias declaration kind in symbol serializer!"); + } + } + + function serializeExportSpecifier(localName: string, targetName: string, specifier?: Expression) { + addResult(factory.createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, localName !== targetName ? targetName : undefined, localName)]), + specifier + ), ModifierFlags.None); + } + + /** + * Returns `true` if an export assignment or declaration was produced for the symbol + */ + function serializeMaybeAliasAssignment(symbol: Symbol): boolean { + if (symbol.flags & SymbolFlags.Prototype) { + return false; + } + const name = unescapeLeadingUnderscores(symbol.escapedName); + const isExportEquals = name === InternalSymbolName.ExportEquals; + const isDefault = name === InternalSymbolName.Default; + const isExportAssignmentCompatibleSymbolName = isExportEquals || isDefault; + // synthesize export = ref + // ref should refer to either be a locally scoped symbol which we need to emit, or + // a reference to another namespace/module which we may need to emit an `import` statement for + const aliasDecl = symbol.declarations && getDeclarationOfAliasSymbol(symbol); + // serialize what the alias points to, preserve the declaration's initializer + const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); + // If the target resolves and resolves to a thing defined in this file, emit as an alias, otherwise emit as a const + if (target && length(target.declarations) && some(target.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(enclosingDeclaration))) { + // In case `target` refers to a namespace member, look at the declaration and serialize the leftmost symbol in it + // eg, `namespace A { export class B {} }; exports = A.B;` + // Technically, this is all that's required in the case where the assignment is an entity name expression + const expr = aliasDecl && ((isExportAssignment(aliasDecl) || isBinaryExpression(aliasDecl)) ? getExportAssignmentExpression(aliasDecl) : getPropertyAssignmentAliasLikeExpression(aliasDecl as ShorthandPropertyAssignment | PropertyAssignment | PropertyAccessExpression)); + const first = expr && isEntityNameExpression(expr) ? getFirstNonModuleExportsIdentifier(expr) : undefined; + const referenced = first && resolveEntityName(first, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, enclosingDeclaration); + if (referenced || target) { + includePrivateSymbol(referenced || target); + } + + // We disable the context's symbol tracker for the duration of this name serialization + // as, by virtue of being here, the name is required to print something, and we don't want to + // issue a visibility error on it. Only anonymous classes that an alias points at _would_ issue + // a visibility error here (as they're not visible within any scope), but we want to hoist them + // into the containing scope anyway, so we want to skip the visibility checks. + const oldTrack = context.tracker.trackSymbol; + context.tracker.trackSymbol = () => false; + if (isExportAssignmentCompatibleSymbolName) { + results.push(factory.createExportAssignment( + /*decorators*/ undefined, + /*modifiers*/ undefined, + isExportEquals, + symbolToExpression(target, context, SymbolFlags.All) + )); } else { - // serialize as an anonymous property declaration - const varName = getUnusedName(name, symbol); - // We have to use `getWidenedType` here since the object within a json file is unwidened within the file - // (Unwidened types can only exist in expression contexts and should never be serialized) - const typeToSerialize = getWidenedType(getTypeOfSymbol(getMergedSymbol(symbol))); - if (isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize, symbol)) { - // If there are no index signatures and `typeToSerialize` is an object type, emit as a namespace instead of a const - serializeAsFunctionNamespaceMerge(typeToSerialize, symbol, varName, isExportAssignmentCompatibleSymbolName ? ModifierFlags.None : ModifierFlags.Export); + if (first === expr && first) { + // serialize as `export {target as name}` + serializeExportSpecifier(name, idText(first)); + } + else if (expr && isClassExpression(expr)) { + serializeExportSpecifier(name, getInternalSymbolName(target, symbolName(target))); } else { - const statement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ - factory.createVariableDeclaration(varName, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, typeToSerialize, symbol, enclosingDeclaration, includePrivateSymbol, bundled)) - ], NodeFlags.Const)); - // Inlined JSON types exported with [module.]exports= will already emit an export=, so should use `declare`. - // Otherwise, the type itself should be exported. - addResult(statement, - target && target.flags & SymbolFlags.Property && target.escapedName === InternalSymbolName.ExportEquals ? ModifierFlags.Ambient - : name === varName ? ModifierFlags.Export - : ModifierFlags.None); - } - if (isExportAssignmentCompatibleSymbolName) { - results.push(factory.createExportAssignment( + // serialize as `import _Ref = t.arg.et; export { _Ref as name }` + const varName = getUnusedName(name, symbol); + addResult(factory.createImportEqualsDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, - isExportEquals, - factory.createIdentifier(varName) - )); - return true; - } - else if (name !== varName) { + /*isTypeOnly*/ false, + factory.createIdentifier(varName), + symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) + ), ModifierFlags.None); serializeExportSpecifier(name, varName); - return true; } - return false; } + context.tracker.trackSymbol = oldTrack; + return true; + } + else { + // serialize as an anonymous property declaration + const varName = getUnusedName(name, symbol); + // We have to use `getWidenedType` here since the object within a json file is unwidened within the file + // (Unwidened types can only exist in expression contexts and should never be serialized) + const typeToSerialize = getWidenedType(getTypeOfSymbol(getMergedSymbol(symbol))); + if (isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize, symbol)) { + // If there are no index signatures and `typeToSerialize` is an object type, emit as a namespace instead of a const + serializeAsFunctionNamespaceMerge(typeToSerialize, symbol, varName, isExportAssignmentCompatibleSymbolName ? ModifierFlags.None : ModifierFlags.Export); + } + else { + const statement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ + factory.createVariableDeclaration(varName, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, typeToSerialize, symbol, enclosingDeclaration, includePrivateSymbol, bundled)) + ], NodeFlags.Const)); + // Inlined JSON types exported with [module.]exports= will already emit an export=, so should use `declare`. + // Otherwise, the type itself should be exported. + addResult(statement, + target && target.flags & SymbolFlags.Property && target.escapedName === InternalSymbolName.ExportEquals ? ModifierFlags.Ambient + : name === varName ? ModifierFlags.Export + : ModifierFlags.None); + } + if (isExportAssignmentCompatibleSymbolName) { + results.push(factory.createExportAssignment( + /*decorators*/ undefined, + /*modifiers*/ undefined, + isExportEquals, + factory.createIdentifier(varName) + )); + return true; + } + else if (name !== varName) { + serializeExportSpecifier(name, varName); + return true; + } + return false; } + } - function isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize: Type, hostSymbol: Symbol) { - // Only object types which are not constructable, or indexable, whose members all come from the - // context source file, and whose property names are all valid identifiers and not late-bound, _and_ - // whose input is not type annotated (if the input symbol has an annotation we can reuse, we should prefer it) - const ctxSrc = getSourceFileOfNode(context.enclosingDeclaration); - return getObjectFlags(typeToSerialize) & (ObjectFlags.Anonymous | ObjectFlags.Mapped) && - !length(getIndexInfosOfType(typeToSerialize)) && - !isClassInstanceSide(typeToSerialize) && // While a class instance is potentially representable as a NS, prefer printing a reference to the instance type and serializing the class - !!(length(filter(getPropertiesOfType(typeToSerialize), isNamespaceMember)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) && - !length(getSignaturesOfType(typeToSerialize, SignatureKind.Construct)) && // TODO: could probably serialize as function + ns + class, now that that's OK - !getDeclarationWithTypeAnnotation(hostSymbol, enclosingDeclaration) && - !(typeToSerialize.symbol && some(typeToSerialize.symbol.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && - !some(getPropertiesOfType(typeToSerialize), p => isLateBoundName(p.escapedName)) && - !some(getPropertiesOfType(typeToSerialize), p => some(p.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && - every(getPropertiesOfType(typeToSerialize), p => isIdentifierText(symbolName(p), languageVersion)); - } - - function makeSerializePropertySymbol(createProperty: ( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: true): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]); - function makeSerializePropertySymbol(createProperty: ( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: false): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | T[]); - function makeSerializePropertySymbol(createProperty: ( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: boolean): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]) { - return function serializePropertySymbol(p: Symbol, isStatic: boolean, baseType: Type | undefined): (T | AccessorDeclaration | (T | AccessorDeclaration)[]) { - const modifierFlags = getDeclarationModifierFlagsFromSymbol(p); - const isPrivate = !!(modifierFlags & ModifierFlags.Private); - if (isStatic && (p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias))) { - // Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols - // need to be merged namespace members - return []; - } - if (p.flags & SymbolFlags.Prototype || - (baseType && getPropertyOfType(baseType, p.escapedName) - && isReadonlySymbol(getPropertyOfType(baseType, p.escapedName)!) === isReadonlySymbol(p) - && (p.flags & SymbolFlags.Optional) === (getPropertyOfType(baseType, p.escapedName)!.flags & SymbolFlags.Optional) - && isTypeIdenticalTo(getTypeOfSymbol(p), getTypeOfPropertyOfType(baseType, p.escapedName)!))) { - return []; - } - const flag = (modifierFlags & ~ModifierFlags.Async) | (isStatic ? ModifierFlags.Static : 0); - const name = getPropertyNameNodeForSymbol(p, context); - const firstPropertyLikeDecl = p.declarations?.find(or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression)); - if (p.flags & SymbolFlags.Accessor && useAccessors) { - const result: AccessorDeclaration[] = []; - if (p.flags & SymbolFlags.SetAccessor) { - result.push(setTextRange(factory.createSetAccessorDeclaration( - /*decorators*/ undefined, - factory.createModifiersFromModifierFlags(flag), - name, - [factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - "arg", - /*questionToken*/ undefined, - isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled) - )], - /*body*/ undefined - ), p.declarations?.find(isSetAccessor) || firstPropertyLikeDecl)); - } - if (p.flags & SymbolFlags.GetAccessor) { - const isPrivate = modifierFlags & ModifierFlags.Private; - result.push(setTextRange(factory.createGetAccessorDeclaration( + function isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize: Type, hostSymbol: Symbol) { + // Only object types which are not constructable, or indexable, whose members all come from the + // context source file, and whose property names are all valid identifiers and not late-bound, _and_ + // whose input is not type annotated (if the input symbol has an annotation we can reuse, we should prefer it) + const ctxSrc = getSourceFileOfNode(context.enclosingDeclaration); + return getObjectFlags(typeToSerialize) & (ObjectFlags.Anonymous | ObjectFlags.Mapped) && + !length(getIndexInfosOfType(typeToSerialize)) && + !isClassInstanceSide(typeToSerialize) && // While a class instance is potentially representable as a NS, prefer printing a reference to the instance type and serializing the class + !!(length(filter(getPropertiesOfType(typeToSerialize), isNamespaceMember)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) && + !length(getSignaturesOfType(typeToSerialize, SignatureKind.Construct)) && // TODO: could probably serialize as function + ns + class, now that that's OK + !getDeclarationWithTypeAnnotation(hostSymbol, enclosingDeclaration) && + !(typeToSerialize.symbol && some(typeToSerialize.symbol.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && + !some(getPropertiesOfType(typeToSerialize), p => isLateBoundName(p.escapedName)) && + !some(getPropertiesOfType(typeToSerialize), p => some(p.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && + every(getPropertiesOfType(typeToSerialize), p => isIdentifierText(symbolName(p), languageVersion)); + } + + function makeSerializePropertySymbol(createProperty: ( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: true): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]); + function makeSerializePropertySymbol(createProperty: ( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: false): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | T[]); + function makeSerializePropertySymbol(createProperty: ( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: boolean): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]) { + return function serializePropertySymbol(p: Symbol, isStatic: boolean, baseType: Type | undefined): (T | AccessorDeclaration | (T | AccessorDeclaration)[]) { + const modifierFlags = getDeclarationModifierFlagsFromSymbol(p); + const isPrivate = !!(modifierFlags & ModifierFlags.Private); + if (isStatic && (p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias))) { + // Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols + // need to be merged namespace members + return []; + } + if (p.flags & SymbolFlags.Prototype || + (baseType && getPropertyOfType(baseType, p.escapedName) + && isReadonlySymbol(getPropertyOfType(baseType, p.escapedName)!) === isReadonlySymbol(p) + && (p.flags & SymbolFlags.Optional) === (getPropertyOfType(baseType, p.escapedName)!.flags & SymbolFlags.Optional) + && isTypeIdenticalTo(getTypeOfSymbol(p), getTypeOfPropertyOfType(baseType, p.escapedName)!))) { + return []; + } + const flag = (modifierFlags & ~ModifierFlags.Async) | (isStatic ? ModifierFlags.Static : 0); + const name = getPropertyNameNodeForSymbol(p, context); + const firstPropertyLikeDecl = p.declarations?.find(or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression)); + if (p.flags & SymbolFlags.Accessor && useAccessors) { + const result: AccessorDeclaration[] = []; + if (p.flags & SymbolFlags.SetAccessor) { + result.push(setTextRange(factory.createSetAccessorDeclaration( + /*decorators*/ undefined, + factory.createModifiersFromModifierFlags(flag), + name, + [factory.createParameterDeclaration( /*decorators*/ undefined, - factory.createModifiersFromModifierFlags(flag), - name, - [], - isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled), - /*body*/ undefined - ), p.declarations?.find(isGetAccessor) || firstPropertyLikeDecl)); - } - return result; + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + "arg", + /*questionToken*/ undefined, + isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled) + )], + /*body*/ undefined + ), p.declarations?.find(isSetAccessor) || firstPropertyLikeDecl)); + } + if (p.flags & SymbolFlags.GetAccessor) { + const isPrivate = modifierFlags & ModifierFlags.Private; + result.push(setTextRange(factory.createGetAccessorDeclaration( + /*decorators*/ undefined, + factory.createModifiersFromModifierFlags(flag), + name, + [], + isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled), + /*body*/ undefined + ), p.declarations?.find(isGetAccessor) || firstPropertyLikeDecl)); } - // This is an else/if as accessors and properties can't merge in TS, but might in JS - // If this happens, we assume the accessor takes priority, as it imposes more constraints - else if (p.flags & (SymbolFlags.Property | SymbolFlags.Variable | SymbolFlags.Accessor)) { + return result; + } + // This is an else/if as accessors and properties can't merge in TS, but might in JS + // If this happens, we assume the accessor takes priority, as it imposes more constraints + else if (p.flags & (SymbolFlags.Property | SymbolFlags.Variable | SymbolFlags.Accessor)) { + return setTextRange(createProperty( + /*decorators*/ undefined, + factory.createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag), + name, + p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled), + // TODO: https://github.com/microsoft/TypeScript/pull/32372#discussion_r328386357 + // interface members can't have initializers, however class members _can_ + /*initializer*/ undefined + ), p.declarations?.find(or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl); + } + if (p.flags & (SymbolFlags.Method | SymbolFlags.Function)) { + const type = getTypeOfSymbol(p); + const signatures = getSignaturesOfType(type, SignatureKind.Call); + if (flag & ModifierFlags.Private) { return setTextRange(createProperty( /*decorators*/ undefined, factory.createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag), name, p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, - isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled), - // TODO: https://github.com/microsoft/TypeScript/pull/32372#discussion_r328386357 - // interface members can't have initializers, however class members _can_ + /*type*/ undefined, /*initializer*/ undefined - ), p.declarations?.find(or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl); + ), p.declarations?.find(isFunctionLikeDeclaration) || signatures[0] && signatures[0].declaration || p.declarations && p.declarations[0]); } - if (p.flags & (SymbolFlags.Method | SymbolFlags.Function)) { - const type = getTypeOfSymbol(p); - const signatures = getSignaturesOfType(type, SignatureKind.Call); - if (flag & ModifierFlags.Private) { - return setTextRange(createProperty( - /*decorators*/ undefined, - factory.createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag), - name, - p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, - /*type*/ undefined, - /*initializer*/ undefined - ), p.declarations?.find(isFunctionLikeDeclaration) || signatures[0] && signatures[0].declaration || p.declarations && p.declarations[0]); - } - const results = []; - for (const sig of signatures) { - // Each overload becomes a separate method declaration, in order - const decl = signatureToSignatureDeclarationHelper( - sig, - methodKind, - context, - { - name, - questionToken: p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, - modifiers: flag ? factory.createModifiersFromModifierFlags(flag) : undefined - } - ); - const location = sig.declaration && isPrototypePropertyAssignment(sig.declaration.parent) ? sig.declaration.parent : sig.declaration; - results.push(setTextRange(decl, location)); - } - return results as unknown as T[]; + const results = []; + for (const sig of signatures) { + // Each overload becomes a separate method declaration, in order + const decl = signatureToSignatureDeclarationHelper( + sig, + methodKind, + context, + { + name, + questionToken: p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + modifiers: flag ? factory.createModifiersFromModifierFlags(flag) : undefined + } + ); + const location = sig.declaration && isPrototypePropertyAssignment(sig.declaration.parent) ? sig.declaration.parent : sig.declaration; + results.push(setTextRange(decl, location)); } - // The `Constructor`'s symbol isn't in the class's properties lists, obviously, since it's a signature on the static - return Debug.fail(`Unhandled class member kind! ${(p as any).__debugFlags || p.flags}`); - }; - } + return results as unknown as T[]; + } + // The `Constructor`'s symbol isn't in the class's properties lists, obviously, since it's a signature on the static + return Debug.fail(`Unhandled class member kind! ${(p as any).__debugFlags || p.flags}`); + }; + } - function serializePropertySymbolForInterface(p: Symbol, baseType: Type | undefined) { - return serializePropertySymbolForInterfaceWorker(p, /*isStatic*/ false, baseType); - } + function serializePropertySymbolForInterface(p: Symbol, baseType: Type | undefined) { + return serializePropertySymbolForInterfaceWorker(p, /*isStatic*/ false, baseType); + } - function serializeSignatures(kind: SignatureKind, input: Type, baseType: Type | undefined, outputKind: SignatureDeclaration["kind"]) { - const signatures = getSignaturesOfType(input, kind); - if (kind === SignatureKind.Construct) { - if (!baseType && every(signatures, s => length(s.parameters) === 0)) { - return []; // No base type, every constructor is empty - elide the extraneous `constructor()` + function serializeSignatures(kind: SignatureKind, input: Type, baseType: Type | undefined, outputKind: SignatureDeclaration["kind"]) { + const signatures = getSignaturesOfType(input, kind); + if (kind === SignatureKind.Construct) { + if (!baseType && every(signatures, s => length(s.parameters) === 0)) { + return []; // No base type, every constructor is empty - elide the extraneous `constructor()` + } + if (baseType) { + // If there is a base type, if every signature in the class is identical to a signature in the baseType, elide all the declarations + const baseSigs = getSignaturesOfType(baseType, SignatureKind.Construct); + if (!length(baseSigs) && every(signatures, s => length(s.parameters) === 0)) { + return []; // Base had no explicit signatures, if all our signatures are also implicit, return an empty list } - if (baseType) { - // If there is a base type, if every signature in the class is identical to a signature in the baseType, elide all the declarations - const baseSigs = getSignaturesOfType(baseType, SignatureKind.Construct); - if (!length(baseSigs) && every(signatures, s => length(s.parameters) === 0)) { - return []; // Base had no explicit signatures, if all our signatures are also implicit, return an empty list - } - if (baseSigs.length === signatures.length) { - let failed = false; - for (let i = 0; i < baseSigs.length; i++) { - if (!compareSignaturesIdentical(signatures[i], baseSigs[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { - failed = true; - break; - } - } - if (!failed) { - return []; // Every signature was identical - elide constructor list as it is inherited + if (baseSigs.length === signatures.length) { + let failed = false; + for (let i = 0; i < baseSigs.length; i++) { + if (!compareSignaturesIdentical(signatures[i], baseSigs[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { + failed = true; + break; } } - } - let privateProtected: ModifierFlags = 0; - for (const s of signatures) { - if (s.declaration) { - privateProtected |= getSelectedEffectiveModifierFlags(s.declaration, ModifierFlags.Private | ModifierFlags.Protected); + if (!failed) { + return []; // Every signature was identical - elide constructor list as it is inherited } } - if (privateProtected) { - return [setTextRange(factory.createConstructorDeclaration( - /*decorators*/ undefined, - factory.createModifiersFromModifierFlags(privateProtected), - /*parameters*/ [], - /*body*/ undefined, - ), signatures[0].declaration)]; + } + let privateProtected: ModifierFlags = 0; + for (const s of signatures) { + if (s.declaration) { + privateProtected |= getSelectedEffectiveModifierFlags(s.declaration, ModifierFlags.Private | ModifierFlags.Protected); } } - - const results = []; - for (const sig of signatures) { - // Each overload becomes a separate constructor declaration, in order - const decl = signatureToSignatureDeclarationHelper(sig, outputKind, context); - results.push(setTextRange(decl, sig.declaration)); + if (privateProtected) { + return [setTextRange(factory.createConstructorDeclaration( + /*decorators*/ undefined, + factory.createModifiersFromModifierFlags(privateProtected), + /*parameters*/ [], + /*body*/ undefined, + ), signatures[0].declaration)]; } - return results; } - function serializeIndexSignatures(input: Type, baseType: Type | undefined) { - const results: IndexSignatureDeclaration[] = []; - for (const info of getIndexInfosOfType(input)) { - if (baseType) { - const baseInfo = getIndexInfoOfType(baseType, info.keyType); - if (baseInfo) { - if (isTypeIdenticalTo(info.type, baseInfo.type)) { - continue; // elide identical index signatures - } + const results = []; + for (const sig of signatures) { + // Each overload becomes a separate constructor declaration, in order + const decl = signatureToSignatureDeclarationHelper(sig, outputKind, context); + results.push(setTextRange(decl, sig.declaration)); + } + return results; + } + + function serializeIndexSignatures(input: Type, baseType: Type | undefined) { + const results: IndexSignatureDeclaration[] = []; + for (const info of getIndexInfosOfType(input)) { + if (baseType) { + const baseInfo = getIndexInfoOfType(baseType, info.keyType); + if (baseInfo) { + if (isTypeIdenticalTo(info.type, baseInfo.type)) { + continue; // elide identical index signatures } } - results.push(indexInfoToIndexSignatureDeclarationHelper(info, context, /*typeNode*/ undefined)); } - return results; + results.push(indexInfoToIndexSignatureDeclarationHelper(info, context, /*typeNode*/ undefined)); } + return results; + } - function serializeBaseType(t: Type, staticType: Type, rootName: string) { - const ref = trySerializeAsTypeReference(t, SymbolFlags.Value); - if (ref) { - return ref; - } - const tempName = getUnusedName(`${rootName}_base`); - const statement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ - factory.createVariableDeclaration(tempName, /*exclamationToken*/ undefined, typeToTypeNodeHelper(staticType, context)) - ], NodeFlags.Const)); - addResult(statement, ModifierFlags.None); - return factory.createExpressionWithTypeArguments(factory.createIdentifier(tempName), /*typeArgs*/ undefined); + function serializeBaseType(t: Type, staticType: Type, rootName: string) { + const ref = trySerializeAsTypeReference(t, SymbolFlags.Value); + if (ref) { + return ref; } + const tempName = getUnusedName(`${rootName}_base`); + const statement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ + factory.createVariableDeclaration(tempName, /*exclamationToken*/ undefined, typeToTypeNodeHelper(staticType, context)) + ], NodeFlags.Const)); + addResult(statement, ModifierFlags.None); + return factory.createExpressionWithTypeArguments(factory.createIdentifier(tempName), /*typeArgs*/ undefined); + } - function trySerializeAsTypeReference(t: Type, flags: SymbolFlags) { - let typeArgs: TypeNode[] | undefined; - let reference: Expression | undefined; + function trySerializeAsTypeReference(t: Type, flags: SymbolFlags) { + let typeArgs: TypeNode[] | undefined; + let reference: Expression | undefined; - // We don't use `isValueSymbolAccessible` below. since that considers alternative containers (like modules) - // which we can't write out in a syntactically valid way as an expression - if ((t as TypeReference).target && isSymbolAccessibleByFlags((t as TypeReference).target.symbol, enclosingDeclaration, flags)) { - typeArgs = map(getTypeArguments(t as TypeReference), t => typeToTypeNodeHelper(t, context)); - reference = symbolToExpression((t as TypeReference).target.symbol, context, SymbolFlags.Type); - } - else if (t.symbol && isSymbolAccessibleByFlags(t.symbol, enclosingDeclaration, flags)) { - reference = symbolToExpression(t.symbol, context, SymbolFlags.Type); - } - if (reference) { - return factory.createExpressionWithTypeArguments(reference, typeArgs); - } + // We don't use `isValueSymbolAccessible` below. since that considers alternative containers (like modules) + // which we can't write out in a syntactically valid way as an expression + if ((t as TypeReference).target && isSymbolAccessibleByFlags((t as TypeReference).target.symbol, enclosingDeclaration, flags)) { + typeArgs = map(getTypeArguments(t as TypeReference), t => typeToTypeNodeHelper(t, context)); + reference = symbolToExpression((t as TypeReference).target.symbol, context, SymbolFlags.Type); } - - function serializeImplementedType(t: Type) { - const ref = trySerializeAsTypeReference(t, SymbolFlags.Type); - if (ref) { - return ref; - } - if (t.symbol) { - return factory.createExpressionWithTypeArguments(symbolToExpression(t.symbol, context, SymbolFlags.Type), /*typeArgs*/ undefined); - } + else if (t.symbol && isSymbolAccessibleByFlags(t.symbol, enclosingDeclaration, flags)) { + reference = symbolToExpression(t.symbol, context, SymbolFlags.Type); } - - function getUnusedName(input: string, symbol?: Symbol): string { - const id = symbol ? getSymbolId(symbol) : undefined; - if (id) { - if (context.remappedSymbolNames!.has(id)) { - return context.remappedSymbolNames!.get(id)!; - } - } - if (symbol) { - input = getNameCandidateWorker(symbol, input); - } - let i = 0; - const original = input; - while (context.usedSymbolNames?.has(input)) { - i++; - input = `${original}_${i}`; - } - context.usedSymbolNames?.add(input); - if (id) { - context.remappedSymbolNames!.set(id, input); - } - return input; + if (reference) { + return factory.createExpressionWithTypeArguments(reference, typeArgs); } + } - function getNameCandidateWorker(symbol: Symbol, localName: string) { - if (localName === InternalSymbolName.Default || localName === InternalSymbolName.Class || localName === InternalSymbolName.Function) { - const flags = context.flags; - context.flags |= NodeBuilderFlags.InInitialEntityName; - const nameCandidate = getNameOfSymbolAsWritten(symbol, context); - context.flags = flags; - localName = nameCandidate.length > 0 && isSingleOrDoubleQuote(nameCandidate.charCodeAt(0)) ? stripQuotes(nameCandidate) : nameCandidate; - } - if (localName === InternalSymbolName.Default) { - localName = "_default"; - } - else if (localName === InternalSymbolName.ExportEquals) { - localName = "_exports"; - } - localName = isIdentifierText(localName, languageVersion) && !isStringANonContextualKeyword(localName) ? localName : "_" + localName.replace(/[^a-zA-Z0-9]/g, "_"); - return localName; + function serializeImplementedType(t: Type) { + const ref = trySerializeAsTypeReference(t, SymbolFlags.Type); + if (ref) { + return ref; } + if (t.symbol) { + return factory.createExpressionWithTypeArguments(symbolToExpression(t.symbol, context, SymbolFlags.Type), /*typeArgs*/ undefined); + } + } - function getInternalSymbolName(symbol: Symbol, localName: string) { - const id = getSymbolId(symbol); + function getUnusedName(input: string, symbol?: Symbol): string { + const id = symbol ? getSymbolId(symbol) : undefined; + if (id) { if (context.remappedSymbolNames!.has(id)) { return context.remappedSymbolNames!.get(id)!; } - localName = getNameCandidateWorker(symbol, localName); - // The result of this is going to be used as the symbol's name - lock it in, so `getUnusedName` will also pick it up - context.remappedSymbolNames!.set(id, localName); - return localName; } + if (symbol) { + input = getNameCandidateWorker(symbol, input); + } + let i = 0; + const original = input; + while (context.usedSymbolNames?.has(input)) { + i++; + input = `${original}_${i}`; + } + context.usedSymbolNames?.add(input); + if (id) { + context.remappedSymbolNames!.set(id, input); + } + return input; } - } - function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer?: EmitTextWriter): string { - return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker); + function getNameCandidateWorker(symbol: Symbol, localName: string) { + if (localName === InternalSymbolName.Default || localName === InternalSymbolName.Class || localName === InternalSymbolName.Function) { + const flags = context.flags; + context.flags |= NodeBuilderFlags.InInitialEntityName; + const nameCandidate = getNameOfSymbolAsWritten(symbol, context); + context.flags = flags; + localName = nameCandidate.length > 0 && isSingleOrDoubleQuote(nameCandidate.charCodeAt(0)) ? stripQuotes(nameCandidate) : nameCandidate; + } + if (localName === InternalSymbolName.Default) { + localName = "_default"; + } + else if (localName === InternalSymbolName.ExportEquals) { + localName = "_exports"; + } + localName = isIdentifierText(localName, languageVersion) && !isStringANonContextualKeyword(localName) ? localName : "_" + localName.replace(/[^a-zA-Z0-9]/g, "_"); + return localName; + } - function typePredicateToStringWorker(writer: EmitTextWriter) { - const predicate = factory.createTypePredicateNode( - typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createToken(SyntaxKind.AssertsKeyword) : undefined, - typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createIdentifier(typePredicate.parameterName) : factory.createThisTypeNode(), - typePredicate.type && nodeBuilder.typeToTypeNode(typePredicate.type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName)! // TODO: GH#18217 - ); - const printer = createPrinter({ removeComments: true }); - const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, predicate, /*sourceFile*/ sourceFile, writer); - return writer; + function getInternalSymbolName(symbol: Symbol, localName: string) { + const id = getSymbolId(symbol); + if (context.remappedSymbolNames!.has(id)) { + return context.remappedSymbolNames!.get(id)!; + } + localName = getNameCandidateWorker(symbol, localName); + // The result of this is going to be used as the symbol's name - lock it in, so `getUnusedName` will also pick it up + context.remappedSymbolNames!.set(id, localName); + return localName; } } + } - function formatUnionTypes(types: readonly Type[]): Type[] { - const result: Type[] = []; - let flags: TypeFlags = 0; - for (let i = 0; i < types.length; i++) { - const t = types[i]; - flags |= t.flags; - if (!(t.flags & TypeFlags.Nullable)) { - if (t.flags & (TypeFlags.BooleanLiteral | TypeFlags.EnumLiteral)) { - const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLiteralType(t as LiteralType); - if (baseType.flags & TypeFlags.Union) { - const count = (baseType as UnionType).types.length; - if (i + count <= types.length && getRegularTypeOfLiteralType(types[i + count - 1]) === getRegularTypeOfLiteralType((baseType as UnionType).types[count - 1])) { - result.push(baseType); - i += count - 1; - continue; - } + function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer?: EmitTextWriter): string { + return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker); + + function typePredicateToStringWorker(writer: EmitTextWriter) { + const predicate = factory.createTypePredicateNode( + typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createToken(SyntaxKind.AssertsKeyword) : undefined, + typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createIdentifier(typePredicate.parameterName) : factory.createThisTypeNode(), + typePredicate.type && nodeBuilder.typeToTypeNode(typePredicate.type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName)! // TODO: GH#18217 + ); + const printer = createPrinter({ removeComments: true }); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, predicate, /*sourceFile*/ sourceFile, writer); + return writer; + } + } + + function formatUnionTypes(types: readonly Type[]): Type[] { + const result: Type[] = []; + let flags: TypeFlags = 0; + for (let i = 0; i < types.length; i++) { + const t = types[i]; + flags |= t.flags; + if (!(t.flags & TypeFlags.Nullable)) { + if (t.flags & (TypeFlags.BooleanLiteral | TypeFlags.EnumLiteral)) { + const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLiteralType(t as LiteralType); + if (baseType.flags & TypeFlags.Union) { + const count = (baseType as UnionType).types.length; + if (i + count <= types.length && getRegularTypeOfLiteralType(types[i + count - 1]) === getRegularTypeOfLiteralType((baseType as UnionType).types[count - 1])) { + result.push(baseType); + i += count - 1; + continue; } } - result.push(t); } + result.push(t); } - if (flags & TypeFlags.Null) result.push(nullType); - if (flags & TypeFlags.Undefined) result.push(undefinedType); - return result || types; } + if (flags & TypeFlags.Null) result.push(nullType); + if (flags & TypeFlags.Undefined) result.push(undefinedType); + return result || types; + } - function visibilityToString(flags: ModifierFlags): string | undefined { - if (flags === ModifierFlags.Private) { - return "private"; - } - if (flags === ModifierFlags.Protected) { - return "protected"; - } - return "public"; + function visibilityToString(flags: ModifierFlags): string | undefined { + if (flags === ModifierFlags.Private) { + return "private"; + } + if (flags === ModifierFlags.Protected) { + return "protected"; } + return "public"; + } - function getTypeAliasForTypeLiteral(type: Type): Symbol | undefined { - if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral && type.symbol.declarations) { - const node = walkUpParenthesizedTypes(type.symbol.declarations[0].parent); - if (node.kind === SyntaxKind.TypeAliasDeclaration) { - return getSymbolOfNode(node); - } + function getTypeAliasForTypeLiteral(type: Type): Symbol | undefined { + if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral && type.symbol.declarations) { + const node = walkUpParenthesizedTypes(type.symbol.declarations[0].parent); + if (node.kind === SyntaxKind.TypeAliasDeclaration) { + return getSymbolOfNode(node); } - return undefined; } + return undefined; + } - function isTopLevelInExternalModuleAugmentation(node: Node): boolean { - return node && node.parent && - node.parent.kind === SyntaxKind.ModuleBlock && - isExternalModuleAugmentation(node.parent.parent); - } + function isTopLevelInExternalModuleAugmentation(node: Node): boolean { + return node && node.parent && + node.parent.kind === SyntaxKind.ModuleBlock && + isExternalModuleAugmentation(node.parent.parent); + } - interface NodeBuilderContext { - enclosingDeclaration: Node | undefined; - flags: NodeBuilderFlags; - tracker: SymbolTracker; + interface NodeBuilderContext { + enclosingDeclaration: Node | undefined; + flags: NodeBuilderFlags; + tracker: SymbolTracker; + + // State + encounteredError: boolean; + reportedDiagnostic: boolean; + visitedTypes: Set | undefined; + symbolDepth: ESMap | undefined; + inferTypeParameters: TypeParameter[] | undefined; + approximateLength: number; + truncating?: boolean; + typeParameterSymbolList?: Set; + typeParameterNames?: ESMap; + typeParameterNamesByText?: Set; + typeParameterNamesByTextNextNameCount?: ESMap; + usedSymbolNames?: Set; + remappedSymbolNames?: ESMap; + reverseMappedStack?: ReverseMappedSymbol[]; + } - // State - encounteredError: boolean; - reportedDiagnostic: boolean; - visitedTypes: Set | undefined; - symbolDepth: ESMap | undefined; - inferTypeParameters: TypeParameter[] | undefined; - approximateLength: number; - truncating?: boolean; - typeParameterSymbolList?: Set; - typeParameterNames?: ESMap; - typeParameterNamesByText?: Set; - typeParameterNamesByTextNextNameCount?: ESMap; - usedSymbolNames?: Set; - remappedSymbolNames?: ESMap; - reverseMappedStack?: ReverseMappedSymbol[]; - } + function isDefaultBindingContext(location: Node) { + return location.kind === SyntaxKind.SourceFile || isAmbientModule(location); + } - function isDefaultBindingContext(location: Node) { - return location.kind === SyntaxKind.SourceFile || isAmbientModule(location); + function getNameOfSymbolFromNameType(symbol: Symbol, context?: NodeBuilderContext) { + const nameType = getSymbolLinks(symbol).nameType; + if (nameType) { + if (nameType.flags & TypeFlags.StringOrNumberLiteral) { + const name = "" + (nameType as StringLiteralType | NumberLiteralType).value; + if (!isIdentifierText(name, getEmitScriptTarget(compilerOptions)) && !isNumericLiteralName(name)) { + return `"${escapeString(name, CharacterCodes.doubleQuote)}"`; + } + if (isNumericLiteralName(name) && startsWith(name, "-")) { + return `[${name}]`; + } + return name; + } + if (nameType.flags & TypeFlags.UniqueESSymbol) { + return `[${getNameOfSymbolAsWritten((nameType as UniqueESSymbolType).symbol, context)}]`; + } } + } - function getNameOfSymbolFromNameType(symbol: Symbol, context?: NodeBuilderContext) { - const nameType = getSymbolLinks(symbol).nameType; - if (nameType) { - if (nameType.flags & TypeFlags.StringOrNumberLiteral) { - const name = "" + (nameType as StringLiteralType | NumberLiteralType).value; - if (!isIdentifierText(name, getEmitScriptTarget(compilerOptions)) && !isNumericLiteralName(name)) { - return `"${escapeString(name, CharacterCodes.doubleQuote)}"`; - } - if (isNumericLiteralName(name) && startsWith(name, "-")) { - return `[${name}]`; + /** + * Gets a human-readable name for a symbol. + * Should *not* be used for the right-hand side of a `.` -- use `symbolName(symbol)` for that instead. + * + * Unlike `symbolName(symbol)`, this will include quotes if the name is from a string literal. + * It will also use a representation of a number as written instead of a decimal form, e.g. `0o11` instead of `9`. + */ + function getNameOfSymbolAsWritten(symbol: Symbol, context?: NodeBuilderContext): string { + if (context && symbol.escapedName === InternalSymbolName.Default && !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) && + // If it's not the first part of an entity name, it must print as `default` + (!(context.flags & NodeBuilderFlags.InInitialEntityName) || + // if the symbol is synthesized, it will only be referenced externally it must print as `default` + !symbol.declarations || + // if not in the same binding context (source file, module declaration), it must print as `default` + (context.enclosingDeclaration && findAncestor(symbol.declarations[0], isDefaultBindingContext) !== findAncestor(context.enclosingDeclaration, isDefaultBindingContext)))) { + return "default"; + } + if (symbol.declarations && symbol.declarations.length) { + let declaration = firstDefined(symbol.declarations, d => getNameOfDeclaration(d) ? d : undefined); // Try using a declaration with a name, first + const name = declaration && getNameOfDeclaration(declaration); + if (declaration && name) { + if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { + return symbolName(symbol); + } + if (isComputedPropertyName(name) && !(getCheckFlags(symbol) & CheckFlags.Late)) { + const nameType = getSymbolLinks(symbol).nameType; + if (nameType && nameType.flags & TypeFlags.StringOrNumberLiteral) { + // Computed property name isn't late bound, but has a well-known name type - use name type to generate a symbol name + const result = getNameOfSymbolFromNameType(symbol, context); + if (result !== undefined) { + return result; + } } - return name; - } - if (nameType.flags & TypeFlags.UniqueESSymbol) { - return `[${getNameOfSymbolAsWritten((nameType as UniqueESSymbolType).symbol, context)}]`; } + return declarationNameToString(name); } - } - - /** - * Gets a human-readable name for a symbol. - * Should *not* be used for the right-hand side of a `.` -- use `symbolName(symbol)` for that instead. - * - * Unlike `symbolName(symbol)`, this will include quotes if the name is from a string literal. - * It will also use a representation of a number as written instead of a decimal form, e.g. `0o11` instead of `9`. - */ - function getNameOfSymbolAsWritten(symbol: Symbol, context?: NodeBuilderContext): string { - if (context && symbol.escapedName === InternalSymbolName.Default && !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) && - // If it's not the first part of an entity name, it must print as `default` - (!(context.flags & NodeBuilderFlags.InInitialEntityName) || - // if the symbol is synthesized, it will only be referenced externally it must print as `default` - !symbol.declarations || - // if not in the same binding context (source file, module declaration), it must print as `default` - (context.enclosingDeclaration && findAncestor(symbol.declarations[0], isDefaultBindingContext) !== findAncestor(context.enclosingDeclaration, isDefaultBindingContext)))) { - return "default"; - } - if (symbol.declarations && symbol.declarations.length) { - let declaration = firstDefined(symbol.declarations, d => getNameOfDeclaration(d) ? d : undefined); // Try using a declaration with a name, first - const name = declaration && getNameOfDeclaration(declaration); - if (declaration && name) { - if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { - return symbolName(symbol); - } - if (isComputedPropertyName(name) && !(getCheckFlags(symbol) & CheckFlags.Late)) { - const nameType = getSymbolLinks(symbol).nameType; - if (nameType && nameType.flags & TypeFlags.StringOrNumberLiteral) { - // Computed property name isn't late bound, but has a well-known name type - use name type to generate a symbol name - const result = getNameOfSymbolFromNameType(symbol, context); - if (result !== undefined) { - return result; - } - } + if (!declaration) { + declaration = symbol.declarations[0]; // Declaration may be nameless, but we'll try anyway + } + if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { + return declarationNameToString((declaration.parent as VariableDeclaration).name); + } + switch (declaration.kind) { + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + if (context && !context.encounteredError && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { + context.encounteredError = true; } - return declarationNameToString(name); - } - if (!declaration) { - declaration = symbol.declarations[0]; // Declaration may be nameless, but we'll try anyway - } - if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { - return declarationNameToString((declaration.parent as VariableDeclaration).name); - } - switch (declaration.kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - if (context && !context.encounteredError && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { - context.encounteredError = true; - } - return declaration.kind === SyntaxKind.ClassExpression ? "(Anonymous class)" : "(Anonymous function)"; - } + return declaration.kind === SyntaxKind.ClassExpression ? "(Anonymous class)" : "(Anonymous function)"; } - const name = getNameOfSymbolFromNameType(symbol, context); - return name !== undefined ? name : symbolName(symbol); } + const name = getNameOfSymbolFromNameType(symbol, context); + return name !== undefined ? name : symbolName(symbol); + } - function isDeclarationVisible(node: Node): boolean { - if (node) { - const links = getNodeLinks(node); - if (links.isVisible === undefined) { - links.isVisible = !!determineIfDeclarationIsVisible(); - } - return links.isVisible; + function isDeclarationVisible(node: Node): boolean { + if (node) { + const links = getNodeLinks(node); + if (links.isVisible === undefined) { + links.isVisible = !!determineIfDeclarationIsVisible(); } + return links.isVisible; + } - return false; - - function determineIfDeclarationIsVisible() { - switch (node.kind) { - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocEnumTag: - // Top-level jsdoc type aliases are considered exported - // First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file - return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent)); - case SyntaxKind.BindingElement: - return isDeclarationVisible(node.parent.parent); - case SyntaxKind.VariableDeclaration: - if (isBindingPattern((node as VariableDeclaration).name) && - !((node as VariableDeclaration).name as BindingPattern).elements.length) { - // If the binding pattern is empty, this variable declaration is not visible - return false; - } - // falls through - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - // external module augmentation is always visible - if (isExternalModuleAugmentation(node)) { - return true; - } - const parent = getDeclarationContainer(node); - // If the node is not exported or it is not ambient module element (except import declaration) - if (!(getCombinedModifierFlags(node as Declaration) & ModifierFlags.Export) && - !(node.kind !== SyntaxKind.ImportEqualsDeclaration && parent.kind !== SyntaxKind.SourceFile && parent.flags & NodeFlags.Ambient)) { - return isGlobalSourceFile(parent); - } - // Exported members/ambient module elements (exception import declaration) are visible if parent is visible - return isDeclarationVisible(parent); - - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (hasEffectiveModifier(node, ModifierFlags.Private | ModifierFlags.Protected)) { - // Private/protected properties/methods are not visible - return false; - } - // Public properties/methods are visible if its parents are visible, so: - // falls through + return false; - case SyntaxKind.Constructor: - case SyntaxKind.ConstructSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.Parameter: - case SyntaxKind.ModuleBlock: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.TypeLiteral: - case SyntaxKind.TypeReference: - case SyntaxKind.ArrayType: - case SyntaxKind.TupleType: - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - case SyntaxKind.ParenthesizedType: - case SyntaxKind.NamedTupleMember: - return isDeclarationVisible(node.parent); - - // Default binding, import specifier and namespace import is visible - // only on demand so by default it is not visible - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportSpecifier: + function determineIfDeclarationIsVisible() { + switch (node.kind) { + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocEnumTag: + // Top-level jsdoc type aliases are considered exported + // First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file + return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent)); + case SyntaxKind.BindingElement: + return isDeclarationVisible(node.parent.parent); + case SyntaxKind.VariableDeclaration: + if (isBindingPattern((node as VariableDeclaration).name) && + !((node as VariableDeclaration).name as BindingPattern).elements.length) { + // If the binding pattern is empty, this variable declaration is not visible return false; - - // Type parameters are always visible - case SyntaxKind.TypeParameter: - - // Source file and namespace export are always visible + } // falls through - case SyntaxKind.SourceFile: - case SyntaxKind.NamespaceExportDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + // external module augmentation is always visible + if (isExternalModuleAugmentation(node)) { return true; + } + const parent = getDeclarationContainer(node); + // If the node is not exported or it is not ambient module element (except import declaration) + if (!(getCombinedModifierFlags(node as Declaration) & ModifierFlags.Export) && + !(node.kind !== SyntaxKind.ImportEqualsDeclaration && parent.kind !== SyntaxKind.SourceFile && parent.flags & NodeFlags.Ambient)) { + return isGlobalSourceFile(parent); + } + // Exported members/ambient module elements (exception import declaration) are visible if parent is visible + return isDeclarationVisible(parent); - // Export assignments do not create name bindings outside the module - case SyntaxKind.ExportAssignment: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + if (hasEffectiveModifier(node, ModifierFlags.Private | ModifierFlags.Protected)) { + // Private/protected properties/methods are not visible return false; + } + // Public properties/methods are visible if its parents are visible, so: + // falls through - default: - return false; - } + case SyntaxKind.Constructor: + case SyntaxKind.ConstructSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.Parameter: + case SyntaxKind.ModuleBlock: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.TypeLiteral: + case SyntaxKind.TypeReference: + case SyntaxKind.ArrayType: + case SyntaxKind.TupleType: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.ParenthesizedType: + case SyntaxKind.NamedTupleMember: + return isDeclarationVisible(node.parent); + + // Default binding, import specifier and namespace import is visible + // only on demand so by default it is not visible + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: + return false; + + // Type parameters are always visible + case SyntaxKind.TypeParameter: + + // Source file and namespace export are always visible + // falls through + case SyntaxKind.SourceFile: + case SyntaxKind.NamespaceExportDeclaration: + return true; + + // Export assignments do not create name bindings outside the module + case SyntaxKind.ExportAssignment: + return false; + + default: + return false; } } + } - function collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined { - let exportSymbol: Symbol | undefined; - if (node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { - exportSymbol = resolveName(node, node.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, node, /*isUse*/ false); - } - else if (node.parent.kind === SyntaxKind.ExportSpecifier) { - exportSymbol = getTargetOfExportSpecifier(node.parent as ExportSpecifier, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); - } - let result: Node[] | undefined; - let visited: Set | undefined; - if (exportSymbol) { - visited = new Set(); - visited.add(getSymbolId(exportSymbol)); - buildVisibleNodeList(exportSymbol.declarations); - } - return result; + function collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined { + let exportSymbol: Symbol | undefined; + if (node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { + exportSymbol = resolveName(node, node.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, node, /*isUse*/ false); + } + else if (node.parent.kind === SyntaxKind.ExportSpecifier) { + exportSymbol = getTargetOfExportSpecifier(node.parent as ExportSpecifier, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); + } + let result: Node[] | undefined; + let visited: Set | undefined; + if (exportSymbol) { + visited = new Set(); + visited.add(getSymbolId(exportSymbol)); + buildVisibleNodeList(exportSymbol.declarations); + } + return result; - function buildVisibleNodeList(declarations: Declaration[] | undefined) { - forEach(declarations, declaration => { - const resultNode = getAnyImportSyntax(declaration) || declaration; - if (setVisibility) { - getNodeLinks(declaration).isVisible = true; - } - else { - result = result || []; - pushIfUnique(result, resultNode); - } - - if (isInternalModuleImportEqualsDeclaration(declaration)) { - // Add the referenced top container visible - const internalModuleReference = declaration.moduleReference as Identifier | QualifiedName; - const firstIdentifier = getFirstIdentifier(internalModuleReference); - const importSymbol = resolveName(declaration, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, - undefined, undefined, /*isUse*/ false); - if (importSymbol && visited) { - if (tryAddToSet(visited, getSymbolId(importSymbol))) { - buildVisibleNodeList(importSymbol.declarations); - } + function buildVisibleNodeList(declarations: Declaration[] | undefined) { + forEach(declarations, declaration => { + const resultNode = getAnyImportSyntax(declaration) || declaration; + if (setVisibility) { + getNodeLinks(declaration).isVisible = true; + } + else { + result = result || []; + pushIfUnique(result, resultNode); + } + + if (isInternalModuleImportEqualsDeclaration(declaration)) { + // Add the referenced top container visible + const internalModuleReference = declaration.moduleReference as Identifier | QualifiedName; + const firstIdentifier = getFirstIdentifier(internalModuleReference); + const importSymbol = resolveName(declaration, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, + undefined, undefined, /*isUse*/ false); + if (importSymbol && visited) { + if (tryAddToSet(visited, getSymbolId(importSymbol))) { + buildVisibleNodeList(importSymbol.declarations); } } - }); - } + } + }); } + } - /** - * Push an entry on the type resolution stack. If an entry with the given target and the given property name - * is already on the stack, and no entries in between already have a type, then a circularity has occurred. - * In this case, the result values of the existing entry and all entries pushed after it are changed to false, - * and the value false is returned. Otherwise, the new entry is just pushed onto the stack, and true is returned. - * In order to see if the same query has already been done before, the target object and the propertyName both - * must match the one passed in. - * - * @param target The symbol, type, or signature whose type is being queried - * @param propertyName The property name that should be used to query the target for its type - */ - function pushTypeResolution(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { - const resolutionCycleStartIndex = findResolutionCycleStartIndex(target, propertyName); - if (resolutionCycleStartIndex >= 0) { - // A cycle was found - const { length } = resolutionTargets; - for (let i = resolutionCycleStartIndex; i < length; i++) { - resolutionResults[i] = false; - } - return false; + /** + * Push an entry on the type resolution stack. If an entry with the given target and the given property name + * is already on the stack, and no entries in between already have a type, then a circularity has occurred. + * In this case, the result values of the existing entry and all entries pushed after it are changed to false, + * and the value false is returned. Otherwise, the new entry is just pushed onto the stack, and true is returned. + * In order to see if the same query has already been done before, the target object and the propertyName both + * must match the one passed in. + * + * @param target The symbol, type, or signature whose type is being queried + * @param propertyName The property name that should be used to query the target for its type + */ + function pushTypeResolution(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { + const resolutionCycleStartIndex = findResolutionCycleStartIndex(target, propertyName); + if (resolutionCycleStartIndex >= 0) { + // A cycle was found + const { length } = resolutionTargets; + for (let i = resolutionCycleStartIndex; i < length; i++) { + resolutionResults[i] = false; } - resolutionTargets.push(target); - resolutionResults.push(/*items*/ true); - resolutionPropertyNames.push(propertyName); - return true; + return false; } + resolutionTargets.push(target); + resolutionResults.push(/*items*/ true); + resolutionPropertyNames.push(propertyName); + return true; + } - function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number { - for (let i = resolutionTargets.length - 1; i >= 0; i--) { - if (hasType(resolutionTargets[i], resolutionPropertyNames[i])) { - return -1; - } - if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) { - return i; - } + function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number { + for (let i = resolutionTargets.length - 1; i >= 0; i--) { + if (hasType(resolutionTargets[i], resolutionPropertyNames[i])) { + return -1; + } + if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) { + return i; } - return -1; } + return -1; + } + + function hasType(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { + switch (propertyName) { + case TypeSystemPropertyName.Type: + return !!getSymbolLinks(target as Symbol).type; + case TypeSystemPropertyName.EnumTagType: + return !!(getNodeLinks(target as JSDocEnumTag).resolvedEnumType); + case TypeSystemPropertyName.DeclaredType: + return !!getSymbolLinks(target as Symbol).declaredType; + case TypeSystemPropertyName.ResolvedBaseConstructorType: + return !!(target as InterfaceType).resolvedBaseConstructorType; + case TypeSystemPropertyName.ResolvedReturnType: + return !!(target as Signature).resolvedReturnType; + case TypeSystemPropertyName.ImmediateBaseConstraint: + return !!(target as Type).immediateBaseConstraint; + case TypeSystemPropertyName.ResolvedTypeArguments: + return !!(target as TypeReference).resolvedTypeArguments; + case TypeSystemPropertyName.ResolvedBaseTypes: + return !!(target as InterfaceType).baseTypesResolved; + } + return Debug.assertNever(propertyName); + } + + /** + * Pop an entry from the type resolution stack and return its associated result value. The result value will + * be true if no circularities were detected, or false if a circularity was found. + */ + function popTypeResolution(): boolean { + resolutionTargets.pop(); + resolutionPropertyNames.pop(); + return resolutionResults.pop()!; + } - function hasType(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { - switch (propertyName) { - case TypeSystemPropertyName.Type: - return !!getSymbolLinks(target as Symbol).type; - case TypeSystemPropertyName.EnumTagType: - return !!(getNodeLinks(target as JSDocEnumTag).resolvedEnumType); - case TypeSystemPropertyName.DeclaredType: - return !!getSymbolLinks(target as Symbol).declaredType; - case TypeSystemPropertyName.ResolvedBaseConstructorType: - return !!(target as InterfaceType).resolvedBaseConstructorType; - case TypeSystemPropertyName.ResolvedReturnType: - return !!(target as Signature).resolvedReturnType; - case TypeSystemPropertyName.ImmediateBaseConstraint: - return !!(target as Type).immediateBaseConstraint; - case TypeSystemPropertyName.ResolvedTypeArguments: - return !!(target as TypeReference).resolvedTypeArguments; - case TypeSystemPropertyName.ResolvedBaseTypes: - return !!(target as InterfaceType).baseTypesResolved; + function getDeclarationContainer(node: Node): Node { + return findAncestor(getRootDeclaration(node), node => { + switch (node.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.VariableDeclarationList: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.NamedImports: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportClause: + return false; + default: + return true; } - return Debug.assertNever(propertyName); - } + })!.parent; + } - /** - * Pop an entry from the type resolution stack and return its associated result value. The result value will - * be true if no circularities were detected, or false if a circularity was found. - */ - function popTypeResolution(): boolean { - resolutionTargets.pop(); - resolutionPropertyNames.pop(); - return resolutionResults.pop()!; - } + function getTypeOfPrototypeProperty(prototype: Symbol): Type { + // TypeScript 1.0 spec (April 2014): 8.4 + // Every class automatically contains a static property member named 'prototype', + // the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter. + // It is an error to explicitly declare a static property member with the name 'prototype'. + const classType = getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!) as InterfaceType; + return classType.typeParameters ? createTypeReference(classType as GenericType, map(classType.typeParameters, _ => anyType)) : classType; + } - function getDeclarationContainer(node: Node): Node { - return findAncestor(getRootDeclaration(node), node => { - switch (node.kind) { - case SyntaxKind.VariableDeclaration: - case SyntaxKind.VariableDeclarationList: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.NamedImports: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportClause: - return false; - default: - return true; - } - })!.parent; - } + // Return the type of the given property in the given type, or undefined if no such property exists + function getTypeOfPropertyOfType(type: Type, name: __String): Type | undefined { + const prop = getPropertyOfType(type, name); + return prop ? getTypeOfSymbol(prop) : undefined; + } - function getTypeOfPrototypeProperty(prototype: Symbol): Type { - // TypeScript 1.0 spec (April 2014): 8.4 - // Every class automatically contains a static property member named 'prototype', - // the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter. - // It is an error to explicitly declare a static property member with the name 'prototype'. - const classType = getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!) as InterfaceType; - return classType.typeParameters ? createTypeReference(classType as GenericType, map(classType.typeParameters, _ => anyType)) : classType; - } + function getTypeOfPropertyOrIndexSignature(type: Type, name: __String): Type { + return getTypeOfPropertyOfType(type, name) || getApplicableIndexInfoForName(type, name)?.type || unknownType; + } - // Return the type of the given property in the given type, or undefined if no such property exists - function getTypeOfPropertyOfType(type: Type, name: __String): Type | undefined { - const prop = getPropertyOfType(type, name); - return prop ? getTypeOfSymbol(prop) : undefined; - } + function isTypeAny(type: Type | undefined) { + return type && (type.flags & TypeFlags.Any) !== 0; + } - function getTypeOfPropertyOrIndexSignature(type: Type, name: __String): Type { - return getTypeOfPropertyOfType(type, name) || getApplicableIndexInfoForName(type, name)?.type || unknownType; - } + function isErrorType(type: Type) { + // The only 'any' types that have alias symbols are those manufactured by getTypeFromTypeAliasReference for + // a reference to an unresolved symbol. We want those to behave like the errorType. + return type === errorType || !!(type.flags & TypeFlags.Any && type.aliasSymbol); + } - function isTypeAny(type: Type | undefined) { - return type && (type.flags & TypeFlags.Any) !== 0; - } + // Return the type of a binding element parent. We check SymbolLinks first to see if a type has been + // assigned by contextual typing. + function getTypeForBindingElementParent(node: BindingElementGrandparent) { + const symbol = getSymbolOfNode(node); + return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false); + } - function isErrorType(type: Type) { - // The only 'any' types that have alias symbols are those manufactured by getTypeFromTypeAliasReference for - // a reference to an unresolved symbol. We want those to behave like the errorType. - return type === errorType || !!(type.flags & TypeFlags.Any && type.aliasSymbol); + function getRestType(source: Type, properties: PropertyName[], symbol: Symbol | undefined): Type { + source = filterType(source, t => !(t.flags & TypeFlags.Nullable)); + if (source.flags & TypeFlags.Never) { + return emptyObjectType; } - - // Return the type of a binding element parent. We check SymbolLinks first to see if a type has been - // assigned by contextual typing. - function getTypeForBindingElementParent(node: BindingElementGrandparent) { - const symbol = getSymbolOfNode(node); - return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false); + if (source.flags & TypeFlags.Union) { + return mapType(source, t => getRestType(t, properties, symbol)); } - - function getRestType(source: Type, properties: PropertyName[], symbol: Symbol | undefined): Type { - source = filterType(source, t => !(t.flags & TypeFlags.Nullable)); - if (source.flags & TypeFlags.Never) { - return emptyObjectType; + const omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName)); + if (isGenericObjectType(source) || isGenericIndexType(omitKeyType)) { + if (omitKeyType.flags & TypeFlags.Never) { + return source; } - if (source.flags & TypeFlags.Union) { - return mapType(source, t => getRestType(t, properties, symbol)); - } - const omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName)); - if (isGenericObjectType(source) || isGenericIndexType(omitKeyType)) { - if (omitKeyType.flags & TypeFlags.Never) { - return source; - } - const omitTypeAlias = getGlobalOmitSymbol(); - if (!omitTypeAlias) { - return errorType; - } - return getTypeAliasInstantiation(omitTypeAlias, [source, omitKeyType]); + const omitTypeAlias = getGlobalOmitSymbol(); + if (!omitTypeAlias) { + return errorType; } - const members = createSymbolTable(); - for (const prop of getPropertiesOfType(source)) { - if (!isTypeAssignableTo(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), omitKeyType) - && !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) - && isSpreadableProperty(prop)) { - members.set(prop.escapedName, getSpreadSymbol(prop, /*readonly*/ false)); - } + return getTypeAliasInstantiation(omitTypeAlias, [source, omitKeyType]); + } + const members = createSymbolTable(); + for (const prop of getPropertiesOfType(source)) { + if (!isTypeAssignableTo(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), omitKeyType) + && !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) + && isSpreadableProperty(prop)) { + members.set(prop.escapedName, getSpreadSymbol(prop, /*readonly*/ false)); } - const result = createAnonymousType(symbol, members, emptyArray, emptyArray, getIndexInfosOfType(source)); - result.objectFlags |= ObjectFlags.ObjectRestType; - return result; } + const result = createAnonymousType(symbol, members, emptyArray, emptyArray, getIndexInfosOfType(source)); + result.objectFlags |= ObjectFlags.ObjectRestType; + return result; + } - function isGenericTypeWithUndefinedConstraint(type: Type) { - return !!(type.flags & TypeFlags.Instantiable) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Undefined); - } + function isGenericTypeWithUndefinedConstraint(type: Type) { + return !!(type.flags & TypeFlags.Instantiable) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Undefined); + } - function getNonUndefinedType(type: Type) { - const typeOrConstraint = someType(type, isGenericTypeWithUndefinedConstraint) ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; - return getTypeWithFacts(typeOrConstraint, TypeFacts.NEUndefined); - } + function getNonUndefinedType(type: Type) { + const typeOrConstraint = someType(type, isGenericTypeWithUndefinedConstraint) ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; + return getTypeWithFacts(typeOrConstraint, TypeFacts.NEUndefined); + } - // Determine the control flow type associated with a destructuring declaration or assignment. The following - // forms of destructuring are possible: - // let { x } = obj; // BindingElement - // let [ x ] = obj; // BindingElement - // { x } = obj; // ShorthandPropertyAssignment - // { x: v } = obj; // PropertyAssignment - // [ x ] = obj; // Expression - // We construct a synthetic element access expression corresponding to 'obj.x' such that the control - // flow analyzer doesn't have to handle all the different syntactic forms. - function getFlowTypeOfDestructuring(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression, declaredType: Type) { - const reference = getSyntheticElementAccess(node); - return reference ? getFlowTypeOfReference(reference, declaredType) : declaredType; - } + // Determine the control flow type associated with a destructuring declaration or assignment. The following + // forms of destructuring are possible: + // let { x } = obj; // BindingElement + // let [ x ] = obj; // BindingElement + // { x } = obj; // ShorthandPropertyAssignment + // { x: v } = obj; // PropertyAssignment + // [ x ] = obj; // Expression + // We construct a synthetic element access expression corresponding to 'obj.x' such that the control + // flow analyzer doesn't have to handle all the different syntactic forms. + function getFlowTypeOfDestructuring(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression, declaredType: Type) { + const reference = getSyntheticElementAccess(node); + return reference ? getFlowTypeOfReference(reference, declaredType) : declaredType; + } - function getSyntheticElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression): ElementAccessExpression | undefined { - const parentAccess = getParentElementAccess(node); - if (parentAccess && parentAccess.flowNode) { - const propName = getDestructuringPropertyName(node); - if (propName) { - const literal = setTextRange(parseNodeFactory.createStringLiteral(propName), node); - const lhsExpr = isLeftHandSideExpression(parentAccess) ? parentAccess : parseNodeFactory.createParenthesizedExpression(parentAccess); - const result = setTextRange(parseNodeFactory.createElementAccessExpression(lhsExpr, literal), node); - setParent(literal, result); - setParent(result, node); - if (lhsExpr !== parentAccess) { - setParent(lhsExpr, result); - } - result.flowNode = parentAccess.flowNode; - return result; - } + function getSyntheticElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression): ElementAccessExpression | undefined { + const parentAccess = getParentElementAccess(node); + if (parentAccess && parentAccess.flowNode) { + const propName = getDestructuringPropertyName(node); + if (propName) { + const literal = setTextRange(parseNodeFactory.createStringLiteral(propName), node); + const lhsExpr = isLeftHandSideExpression(parentAccess) ? parentAccess : parseNodeFactory.createParenthesizedExpression(parentAccess); + const result = setTextRange(parseNodeFactory.createElementAccessExpression(lhsExpr, literal), node); + setParent(literal, result); + setParent(result, node); + if (lhsExpr !== parentAccess) { + setParent(lhsExpr, result); + } + result.flowNode = parentAccess.flowNode; + return result; } } + } - function getParentElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { - const ancestor = node.parent.parent; - switch (ancestor.kind) { - case SyntaxKind.BindingElement: - case SyntaxKind.PropertyAssignment: - return getSyntheticElementAccess(ancestor as BindingElement | PropertyAssignment); - case SyntaxKind.ArrayLiteralExpression: - return getSyntheticElementAccess(node.parent as Expression); - case SyntaxKind.VariableDeclaration: - return (ancestor as VariableDeclaration).initializer; - case SyntaxKind.BinaryExpression: - return (ancestor as BinaryExpression).right; - } + function getParentElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { + const ancestor = node.parent.parent; + switch (ancestor.kind) { + case SyntaxKind.BindingElement: + case SyntaxKind.PropertyAssignment: + return getSyntheticElementAccess(ancestor as BindingElement | PropertyAssignment); + case SyntaxKind.ArrayLiteralExpression: + return getSyntheticElementAccess(node.parent as Expression); + case SyntaxKind.VariableDeclaration: + return (ancestor as VariableDeclaration).initializer; + case SyntaxKind.BinaryExpression: + return (ancestor as BinaryExpression).right; } + } - function getDestructuringPropertyName(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { - const parent = node.parent; - if (node.kind === SyntaxKind.BindingElement && parent.kind === SyntaxKind.ObjectBindingPattern) { - return getLiteralPropertyNameText((node as BindingElement).propertyName || (node as BindingElement).name as Identifier); - } - if (node.kind === SyntaxKind.PropertyAssignment || node.kind === SyntaxKind.ShorthandPropertyAssignment) { - return getLiteralPropertyNameText((node as PropertyAssignment | ShorthandPropertyAssignment).name); - } - return "" + ((parent as BindingPattern | ArrayLiteralExpression).elements as NodeArray).indexOf(node); + function getDestructuringPropertyName(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { + const parent = node.parent; + if (node.kind === SyntaxKind.BindingElement && parent.kind === SyntaxKind.ObjectBindingPattern) { + return getLiteralPropertyNameText((node as BindingElement).propertyName || (node as BindingElement).name as Identifier); } - - function getLiteralPropertyNameText(name: PropertyName) { - const type = getLiteralTypeFromPropertyName(name); - return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (type as StringLiteralType | NumberLiteralType).value : undefined; + if (node.kind === SyntaxKind.PropertyAssignment || node.kind === SyntaxKind.ShorthandPropertyAssignment) { + return getLiteralPropertyNameText((node as PropertyAssignment | ShorthandPropertyAssignment).name); } + return "" + ((parent as BindingPattern | ArrayLiteralExpression).elements as NodeArray).indexOf(node); + } + + function getLiteralPropertyNameText(name: PropertyName) { + const type = getLiteralTypeFromPropertyName(name); + return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (type as StringLiteralType | NumberLiteralType).value : undefined; + } - /** Return the inferred type for a binding element */ - function getTypeForBindingElement(declaration: BindingElement): Type | undefined { - const parentType = getTypeForBindingElementParent(declaration.parent.parent); - return parentType && getBindingElementTypeFromParentType(declaration, parentType); + /** Return the inferred type for a binding element */ + function getTypeForBindingElement(declaration: BindingElement): Type | undefined { + const parentType = getTypeForBindingElementParent(declaration.parent.parent); + return parentType && getBindingElementTypeFromParentType(declaration, parentType); + } + + function getBindingElementTypeFromParentType(declaration: BindingElement, parentType: Type): Type { + // If an any type was inferred for parent, infer that for the binding element + if (isTypeAny(parentType)) { + return parentType; + } + const pattern = declaration.parent; + // Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation + if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) { + parentType = getNonNullableType(parentType); + } + // Filter `undefined` from the type we check against if the parent has an initializer and that initializer is not possibly `undefined` + else if (strictNullChecks && pattern.parent.initializer && !(getTypeFacts(getTypeOfInitializer(pattern.parent.initializer)) & TypeFacts.EQUndefined)) { + parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined); } - function getBindingElementTypeFromParentType(declaration: BindingElement, parentType: Type): Type { - // If an any type was inferred for parent, infer that for the binding element - if (isTypeAny(parentType)) { - return parentType; - } - const pattern = declaration.parent; - // Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation - if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) { - parentType = getNonNullableType(parentType); - } - // Filter `undefined` from the type we check against if the parent has an initializer and that initializer is not possibly `undefined` - else if (strictNullChecks && pattern.parent.initializer && !(getTypeFacts(getTypeOfInitializer(pattern.parent.initializer)) & TypeFacts.EQUndefined)) { - parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined); - } - - let type: Type | undefined; - if (pattern.kind === SyntaxKind.ObjectBindingPattern) { - if (declaration.dotDotDotToken) { - parentType = getReducedType(parentType); - if (parentType.flags & TypeFlags.Unknown || !isValidSpreadType(parentType)) { - error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types); - return errorType; - } - const literalMembers: PropertyName[] = []; - for (const element of pattern.elements) { - if (!element.dotDotDotToken) { - literalMembers.push(element.propertyName || element.name as Identifier); - } - } - type = getRestType(parentType, literalMembers, declaration.symbol); - } - else { - // Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) - const name = declaration.propertyName || declaration.name as Identifier; - const indexType = getLiteralTypeFromPropertyName(name); - const declaredType = getIndexedAccessType(parentType, indexType, AccessFlags.ExpressionPosition, name); - type = getFlowTypeOfDestructuring(declaration, declaredType); - } + let type: Type | undefined; + if (pattern.kind === SyntaxKind.ObjectBindingPattern) { + if (declaration.dotDotDotToken) { + parentType = getReducedType(parentType); + if (parentType.flags & TypeFlags.Unknown || !isValidSpreadType(parentType)) { + error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types); + return errorType; + } + const literalMembers: PropertyName[] = []; + for (const element of pattern.elements) { + if (!element.dotDotDotToken) { + literalMembers.push(element.propertyName || element.name as Identifier); + } + } + type = getRestType(parentType, literalMembers, declaration.symbol); } else { - // This elementType will be used if the specific property corresponding to this index is not - // present (aka the tuple element property). This call also checks that the parentType is in - // fact an iterable or array (depending on target language). - const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring | (declaration.dotDotDotToken ? 0 : IterationUse.PossiblyOutOfBounds), parentType, undefinedType, pattern); - const index = pattern.elements.indexOf(declaration); - if (declaration.dotDotDotToken) { - // If the parent is a tuple type, the rest element has a tuple type of the - // remaining tuple element types. Otherwise, the rest element has an array type with same - // element type as the parent type. - type = everyType(parentType, isTupleType) ? - mapType(parentType, t => sliceTupleType(t as TupleTypeReference, index)) : - createArrayType(elementType); - } - else if (isArrayLikeType(parentType)) { - const indexType = getNumberLiteralType(index); - const accessFlags = AccessFlags.ExpressionPosition | (hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0); - const declaredType = getIndexedAccessTypeOrUndefined(parentType, indexType, accessFlags, declaration.name) || errorType; - type = getFlowTypeOfDestructuring(declaration, declaredType); - } - else { - type = elementType; - } + // Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) + const name = declaration.propertyName || declaration.name as Identifier; + const indexType = getLiteralTypeFromPropertyName(name); + const declaredType = getIndexedAccessType(parentType, indexType, AccessFlags.ExpressionPosition, name); + type = getFlowTypeOfDestructuring(declaration, declaredType); } - if (!declaration.initializer) { - return type; + } + else { + // This elementType will be used if the specific property corresponding to this index is not + // present (aka the tuple element property). This call also checks that the parentType is in + // fact an iterable or array (depending on target language). + const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring | (declaration.dotDotDotToken ? 0 : IterationUse.PossiblyOutOfBounds), parentType, undefinedType, pattern); + const index = pattern.elements.indexOf(declaration); + if (declaration.dotDotDotToken) { + // If the parent is a tuple type, the rest element has a tuple type of the + // remaining tuple element types. Otherwise, the rest element has an array type with same + // element type as the parent type. + type = everyType(parentType, isTupleType) ? + mapType(parentType, t => sliceTupleType(t as TupleTypeReference, index)) : + createArrayType(elementType); + } + else if (isArrayLikeType(parentType)) { + const indexType = getNumberLiteralType(index); + const accessFlags = AccessFlags.ExpressionPosition | (hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0); + const declaredType = getIndexedAccessTypeOrUndefined(parentType, indexType, accessFlags, declaration.name) || errorType; + type = getFlowTypeOfDestructuring(declaration, declaredType); } - if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) { - // In strict null checking mode, if a default value of a non-undefined type is specified, remove - // undefined from the final type. - return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ? getNonUndefinedType(type) : type; + else { + type = elementType; } - return widenTypeInferredFromInitializer(declaration, getUnionType([getNonUndefinedType(type), checkDeclarationInitializer(declaration)], UnionReduction.Subtype)); } - - function getTypeForDeclarationFromJSDocComment(declaration: Node) { - const jsdocType = getJSDocType(declaration); - if (jsdocType) { - return getTypeFromTypeNode(jsdocType); - } - return undefined; + if (!declaration.initializer) { + return type; } - - function isNullOrUndefined(node: Expression) { - const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); - return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr as Identifier) === undefinedSymbol; + if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) { + // In strict null checking mode, if a default value of a non-undefined type is specified, remove + // undefined from the final type. + return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ? getNonUndefinedType(type) : type; } + return widenTypeInferredFromInitializer(declaration, getUnionType([getNonUndefinedType(type), checkDeclarationInitializer(declaration)], UnionReduction.Subtype)); + } - function isEmptyArrayLiteral(node: Expression) { - const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); - return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr as ArrayLiteralExpression).elements.length === 0; + function getTypeForDeclarationFromJSDocComment(declaration: Node) { + const jsdocType = getJSDocType(declaration); + if (jsdocType) { + return getTypeFromTypeNode(jsdocType); } + return undefined; + } + + function isNullOrUndefined(node: Expression) { + const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr as Identifier) === undefinedSymbol; + } + + function isEmptyArrayLiteral(node: Expression) { + const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr as ArrayLiteralExpression).elements.length === 0; + } + + function addOptionality(type: Type, isProperty = false, isOptional = true): Type { + return strictNullChecks && isOptional ? getOptionalType(type, isProperty) : type; + } - function addOptionality(type: Type, isProperty = false, isOptional = true): Type { - return strictNullChecks && isOptional ? getOptionalType(type, isProperty) : type; + // Return the inferred type for a variable, parameter, or property declaration + function getTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, includeOptionality: boolean): Type | undefined { + // A variable declared in a for..in statement is of type string, or of type keyof T when the + // right hand expression is of a type parameter type. + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) { + const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression))); + return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType; } - // Return the inferred type for a variable, parameter, or property declaration - function getTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, includeOptionality: boolean): Type | undefined { - // A variable declared in a for..in statement is of type string, or of type keyof T when the - // right hand expression is of a type parameter type. - if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) { - const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression))); - return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType; - } + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { + // checkRightHandSideOfForOf will return undefined if the for-of expression type was + // missing properties/signatures required to get its iteratedType (like + // [Symbol.iterator] or next). This may be because we accessed properties from anyType, + // or it may have led to an error inside getElementTypeOfIterable. + const forOfStatement = declaration.parent.parent; + return checkRightHandSideOfForOf(forOfStatement) || anyType; + } - if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { - // checkRightHandSideOfForOf will return undefined if the for-of expression type was - // missing properties/signatures required to get its iteratedType (like - // [Symbol.iterator] or next). This may be because we accessed properties from anyType, - // or it may have led to an error inside getElementTypeOfIterable. - const forOfStatement = declaration.parent.parent; - return checkRightHandSideOfForOf(forOfStatement) || anyType; - } + if (isBindingPattern(declaration.parent)) { + return getTypeForBindingElement(declaration as BindingElement); + } - if (isBindingPattern(declaration.parent)) { - return getTypeForBindingElement(declaration as BindingElement); - } + const isProperty = isPropertyDeclaration(declaration) || isPropertySignature(declaration); + const isOptional = includeOptionality && ( + isProperty && !!(declaration as PropertyDeclaration | PropertySignature).questionToken || + isParameter(declaration) && (!!declaration.questionToken || isJSDocOptionalParameter(declaration)) || + isOptionalJSDocPropertyLikeTag(declaration)); - const isProperty = isPropertyDeclaration(declaration) || isPropertySignature(declaration); - const isOptional = includeOptionality && ( - isProperty && !!(declaration as PropertyDeclaration | PropertySignature).questionToken || - isParameter(declaration) && (!!declaration.questionToken || isJSDocOptionalParameter(declaration)) || - isOptionalJSDocPropertyLikeTag(declaration)); + // Use type from type annotation if one is present + const declaredType = tryGetTypeFromEffectiveTypeNode(declaration); + if (declaredType) { + return addOptionality(declaredType, isProperty, isOptional); + } - // Use type from type annotation if one is present - const declaredType = tryGetTypeFromEffectiveTypeNode(declaration); - if (declaredType) { - return addOptionality(declaredType, isProperty, isOptional); + if ((noImplicitAny || isInJSFile(declaration)) && + isVariableDeclaration(declaration) && !isBindingPattern(declaration.name) && + !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) && !(declaration.flags & NodeFlags.Ambient)) { + // If --noImplicitAny is on or the declaration is in a Javascript file, + // use control flow tracked 'any' type for non-ambient, non-exported var or let variables with no + // initializer or a 'null' or 'undefined' initializer. + if (!(getCombinedNodeFlags(declaration) & NodeFlags.Const) && (!declaration.initializer || isNullOrUndefined(declaration.initializer))) { + return autoType; } - - if ((noImplicitAny || isInJSFile(declaration)) && - isVariableDeclaration(declaration) && !isBindingPattern(declaration.name) && - !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) && !(declaration.flags & NodeFlags.Ambient)) { - // If --noImplicitAny is on or the declaration is in a Javascript file, - // use control flow tracked 'any' type for non-ambient, non-exported var or let variables with no - // initializer or a 'null' or 'undefined' initializer. - if (!(getCombinedNodeFlags(declaration) & NodeFlags.Const) && (!declaration.initializer || isNullOrUndefined(declaration.initializer))) { - return autoType; - } - // Use control flow tracked 'any[]' type for non-ambient, non-exported variables with an empty array - // literal initializer. - if (declaration.initializer && isEmptyArrayLiteral(declaration.initializer)) { - return autoArrayType; - } + // Use control flow tracked 'any[]' type for non-ambient, non-exported variables with an empty array + // literal initializer. + if (declaration.initializer && isEmptyArrayLiteral(declaration.initializer)) { + return autoArrayType; } + } - if (isParameter(declaration)) { - const func = declaration.parent as FunctionLikeDeclaration; - // For a parameter of a set accessor, use the type of the get accessor if one is present - if (func.kind === SyntaxKind.SetAccessor && hasBindableName(func)) { - const getter = getDeclarationOfKind(getSymbolOfNode(declaration.parent), SyntaxKind.GetAccessor); - if (getter) { - const getterSignature = getSignatureFromDeclaration(getter); - const thisParameter = getAccessorThisParameter(func as AccessorDeclaration); - if (thisParameter && declaration === thisParameter) { - // Use the type from the *getter* - Debug.assert(!thisParameter.type); - return getTypeOfSymbol(getterSignature.thisParameter!); - } - return getReturnTypeOfSignature(getterSignature); - } - } - if (isInJSFile(declaration)) { - const typeTag = getJSDocType(func); - if (typeTag && isFunctionTypeNode(typeTag)) { - const signature = getSignatureFromDeclaration(typeTag); - const pos = func.parameters.indexOf(declaration); - return declaration.dotDotDotToken ? getRestTypeAtPosition(signature, pos) : getTypeAtPosition(signature, pos); + if (isParameter(declaration)) { + const func = declaration.parent as FunctionLikeDeclaration; + // For a parameter of a set accessor, use the type of the get accessor if one is present + if (func.kind === SyntaxKind.SetAccessor && hasBindableName(func)) { + const getter = getDeclarationOfKind(getSymbolOfNode(declaration.parent), SyntaxKind.GetAccessor); + if (getter) { + const getterSignature = getSignatureFromDeclaration(getter); + const thisParameter = getAccessorThisParameter(func as AccessorDeclaration); + if (thisParameter && declaration === thisParameter) { + // Use the type from the *getter* + Debug.assert(!thisParameter.type); + return getTypeOfSymbol(getterSignature.thisParameter!); } - } - // Use contextual parameter type if one is available - const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration); - if (type) { - return addOptionality(type, /*isProperty*/ false, isOptional); + return getReturnTypeOfSignature(getterSignature); } } - - // Use the type of the initializer expression if one is present and the declaration is - // not a parameter of a contextually typed function - if (hasOnlyExpressionInitializer(declaration) && !!declaration.initializer) { - if (isInJSFile(declaration) && !isParameter(declaration)) { - const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfNode(declaration), getDeclaredExpandoInitializer(declaration)); - if (containerObjectType) { - return containerObjectType; - } + if (isInJSFile(declaration)) { + const typeTag = getJSDocType(func); + if (typeTag && isFunctionTypeNode(typeTag)) { + const signature = getSignatureFromDeclaration(typeTag); + const pos = func.parameters.indexOf(declaration); + return declaration.dotDotDotToken ? getRestTypeAtPosition(signature, pos) : getTypeAtPosition(signature, pos); } - const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration)); - return addOptionality(type, isProperty, isOptional); } + // Use contextual parameter type if one is available + const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration); + if (type) { + return addOptionality(type, /*isProperty*/ false, isOptional); + } + } - if (isPropertyDeclaration(declaration) && (noImplicitAny || isInJSFile(declaration))) { - // We have a property declaration with no type annotation or initializer, in noImplicitAny mode or a .js file. - // Use control flow analysis of this.xxx assignments in the constructor or static block to determine the type of the property. - if (!hasStaticModifier(declaration)) { - const constructor = findConstructorDeclaration(declaration.parent); - const type = constructor ? getFlowTypeInConstructor(declaration.symbol, constructor) : - getEffectiveModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : - undefined; - return type && addOptionality(type, /*isProperty*/ true, isOptional); - } - else { - const staticBlocks = filter(declaration.parent.members, isClassStaticBlockDeclaration); - const type = staticBlocks.length ? getFlowTypeInStaticBlocks(declaration.symbol, staticBlocks) : - getEffectiveModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : - undefined; - return type && addOptionality(type, /*isProperty*/ true, isOptional); + // Use the type of the initializer expression if one is present and the declaration is + // not a parameter of a contextually typed function + if (hasOnlyExpressionInitializer(declaration) && !!declaration.initializer) { + if (isInJSFile(declaration) && !isParameter(declaration)) { + const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfNode(declaration), getDeclaredExpandoInitializer(declaration)); + if (containerObjectType) { + return containerObjectType; } } + const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration)); + return addOptionality(type, isProperty, isOptional); + } - if (isJsxAttribute(declaration)) { - // if JSX attribute doesn't have initializer, by default the attribute will have boolean value of true. - // I.e is sugar for - return trueType; + if (isPropertyDeclaration(declaration) && (noImplicitAny || isInJSFile(declaration))) { + // We have a property declaration with no type annotation or initializer, in noImplicitAny mode or a .js file. + // Use control flow analysis of this.xxx assignments in the constructor or static block to determine the type of the property. + if (!hasStaticModifier(declaration)) { + const constructor = findConstructorDeclaration(declaration.parent); + const type = constructor ? getFlowTypeInConstructor(declaration.symbol, constructor) : + getEffectiveModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : + undefined; + return type && addOptionality(type, /*isProperty*/ true, isOptional); } - - // If the declaration specifies a binding pattern and is not a parameter of a contextually - // typed function, use the type implied by the binding pattern - if (isBindingPattern(declaration.name)) { - return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true); + else { + const staticBlocks = filter(declaration.parent.members, isClassStaticBlockDeclaration); + const type = staticBlocks.length ? getFlowTypeInStaticBlocks(declaration.symbol, staticBlocks) : + getEffectiveModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : + undefined; + return type && addOptionality(type, /*isProperty*/ true, isOptional); } - - // No type specified and nothing can be inferred - return undefined; } - function isConstructorDeclaredProperty(symbol: Symbol) { - // A property is considered a constructor declared property when all declaration sites are this.xxx assignments, - // when no declaration sites have JSDoc type annotations, and when at least one declaration site is in the body of - // a class constructor. - if (symbol.valueDeclaration && isBinaryExpression(symbol.valueDeclaration)) { - const links = getSymbolLinks(symbol); - if (links.isConstructorDeclaredProperty === undefined) { - links.isConstructorDeclaredProperty = false; - links.isConstructorDeclaredProperty = !!getDeclaringConstructor(symbol) && every(symbol.declarations, declaration => - isBinaryExpression(declaration) && - isPossiblyAliasedThisProperty(declaration) && - (declaration.left.kind !== SyntaxKind.ElementAccessExpression || isStringOrNumericLiteralLike((declaration.left as ElementAccessExpression).argumentExpression)) && - !getAnnotatedTypeForAssignmentDeclaration(/*declaredType*/ undefined, declaration, symbol, declaration)); - } - return links.isConstructorDeclaredProperty; - } - return false; + if (isJsxAttribute(declaration)) { + // if JSX attribute doesn't have initializer, by default the attribute will have boolean value of true. + // I.e is sugar for + return trueType; } - function isAutoTypedProperty(symbol: Symbol) { - // A property is auto-typed when its declaration has no type annotation or initializer and we're in - // noImplicitAny mode or a .js file. - const declaration = symbol.valueDeclaration; - return declaration && isPropertyDeclaration(declaration) && !getEffectiveTypeAnnotationNode(declaration) && - !declaration.initializer && (noImplicitAny || isInJSFile(declaration)); + // If the declaration specifies a binding pattern and is not a parameter of a contextually + // typed function, use the type implied by the binding pattern + if (isBindingPattern(declaration.name)) { + return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true); } - function getDeclaringConstructor(symbol: Symbol) { - if (!symbol.declarations) { - return; - } - for (const declaration of symbol.declarations) { - const container = getThisContainer(declaration, /*includeArrowFunctions*/ false); - if (container && (container.kind === SyntaxKind.Constructor || isJSConstructor(container))) { - return container as ConstructorDeclaration; - } - }; - } + // No type specified and nothing can be inferred + return undefined; + } - /** Create a synthetic property access flow node after the last statement of the file */ - function getFlowTypeFromCommonJSExport(symbol: Symbol) { - const file = getSourceFileOfNode(symbol.declarations![0]); - const accessName = unescapeLeadingUnderscores(symbol.escapedName); - const areAllModuleExports = symbol.declarations!.every(d => isInJSFile(d) && isAccessExpression(d) && isModuleExportsAccessExpression(d.expression)); - const reference = areAllModuleExports - ? factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier("module"), factory.createIdentifier("exports")), accessName) - : factory.createPropertyAccessExpression(factory.createIdentifier("exports"), accessName); - if (areAllModuleExports) { - setParent((reference.expression as PropertyAccessExpression).expression, reference.expression); + function isConstructorDeclaredProperty(symbol: Symbol) { + // A property is considered a constructor declared property when all declaration sites are this.xxx assignments, + // when no declaration sites have JSDoc type annotations, and when at least one declaration site is in the body of + // a class constructor. + if (symbol.valueDeclaration && isBinaryExpression(symbol.valueDeclaration)) { + const links = getSymbolLinks(symbol); + if (links.isConstructorDeclaredProperty === undefined) { + links.isConstructorDeclaredProperty = false; + links.isConstructorDeclaredProperty = !!getDeclaringConstructor(symbol) && every(symbol.declarations, declaration => + isBinaryExpression(declaration) && + isPossiblyAliasedThisProperty(declaration) && + (declaration.left.kind !== SyntaxKind.ElementAccessExpression || isStringOrNumericLiteralLike((declaration.left as ElementAccessExpression).argumentExpression)) && + !getAnnotatedTypeForAssignmentDeclaration(/*declaredType*/ undefined, declaration, symbol, declaration)); } - setParent(reference.expression, reference); - setParent(reference, file); - reference.flowNode = file.endFlowNode; - return getFlowTypeOfReference(reference, autoType, undefinedType); + return links.isConstructorDeclaredProperty; } + return false; + } - function getFlowTypeInStaticBlocks(symbol: Symbol, staticBlocks: readonly ClassStaticBlockDeclaration[]) { - const accessName = startsWith(symbol.escapedName as string, "__#") - ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) - : unescapeLeadingUnderscores(symbol.escapedName); - for (const staticBlock of staticBlocks) { - const reference = factory.createPropertyAccessExpression(factory.createThis(), accessName); - setParent(reference.expression, reference); - setParent(reference, staticBlock); - reference.flowNode = staticBlock.returnFlowNode; - const flowType = getFlowTypeOfProperty(reference, symbol); - if (noImplicitAny && (flowType === autoType || flowType === autoArrayType)) { - error(symbol.valueDeclaration, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); - } - // We don't infer a type if assignments are only null or undefined. - if (everyType(flowType, isNullableType)) { - continue; - } - return convertAutoToAny(flowType); - } + function isAutoTypedProperty(symbol: Symbol) { + // A property is auto-typed when its declaration has no type annotation or initializer and we're in + // noImplicitAny mode or a .js file. + const declaration = symbol.valueDeclaration; + return declaration && isPropertyDeclaration(declaration) && !getEffectiveTypeAnnotationNode(declaration) && + !declaration.initializer && (noImplicitAny || isInJSFile(declaration)); + } + + function getDeclaringConstructor(symbol: Symbol) { + if (!symbol.declarations) { + return; } + for (const declaration of symbol.declarations) { + const container = getThisContainer(declaration, /*includeArrowFunctions*/ false); + if (container && (container.kind === SyntaxKind.Constructor || isJSConstructor(container))) { + return container as ConstructorDeclaration; + } + }; + } + + /** Create a synthetic property access flow node after the last statement of the file */ + function getFlowTypeFromCommonJSExport(symbol: Symbol) { + const file = getSourceFileOfNode(symbol.declarations![0]); + const accessName = unescapeLeadingUnderscores(symbol.escapedName); + const areAllModuleExports = symbol.declarations!.every(d => isInJSFile(d) && isAccessExpression(d) && isModuleExportsAccessExpression(d.expression)); + const reference = areAllModuleExports + ? factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier("module"), factory.createIdentifier("exports")), accessName) + : factory.createPropertyAccessExpression(factory.createIdentifier("exports"), accessName); + if (areAllModuleExports) { + setParent((reference.expression as PropertyAccessExpression).expression, reference.expression); + } + setParent(reference.expression, reference); + setParent(reference, file); + reference.flowNode = file.endFlowNode; + return getFlowTypeOfReference(reference, autoType, undefinedType); + } - function getFlowTypeInConstructor(symbol: Symbol, constructor: ConstructorDeclaration) { - const accessName = startsWith(symbol.escapedName as string, "__#") - ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) - : unescapeLeadingUnderscores(symbol.escapedName); + function getFlowTypeInStaticBlocks(symbol: Symbol, staticBlocks: readonly ClassStaticBlockDeclaration[]) { + const accessName = startsWith(symbol.escapedName as string, "__#") + ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) + : unescapeLeadingUnderscores(symbol.escapedName); + for (const staticBlock of staticBlocks) { const reference = factory.createPropertyAccessExpression(factory.createThis(), accessName); setParent(reference.expression, reference); - setParent(reference, constructor); - reference.flowNode = constructor.returnFlowNode; + setParent(reference, staticBlock); + reference.flowNode = staticBlock.returnFlowNode; const flowType = getFlowTypeOfProperty(reference, symbol); if (noImplicitAny && (flowType === autoType || flowType === autoArrayType)) { error(symbol.valueDeclaration, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); } // We don't infer a type if assignments are only null or undefined. - return everyType(flowType, isNullableType) ? undefined : convertAutoToAny(flowType); + if (everyType(flowType, isNullableType)) { + continue; + } + return convertAutoToAny(flowType); } + } - function getFlowTypeOfProperty(reference: Node, prop: Symbol | undefined) { - const initialType = prop?.valueDeclaration - && (!isAutoTypedProperty(prop) || getEffectiveModifierFlags(prop.valueDeclaration) & ModifierFlags.Ambient) - && getTypeOfPropertyInBaseClass(prop) - || undefinedType; - return getFlowTypeOfReference(reference, autoType, initialType); - } + function getFlowTypeInConstructor(symbol: Symbol, constructor: ConstructorDeclaration) { + const accessName = startsWith(symbol.escapedName as string, "__#") + ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) + : unescapeLeadingUnderscores(symbol.escapedName); + const reference = factory.createPropertyAccessExpression(factory.createThis(), accessName); + setParent(reference.expression, reference); + setParent(reference, constructor); + reference.flowNode = constructor.returnFlowNode; + const flowType = getFlowTypeOfProperty(reference, symbol); + if (noImplicitAny && (flowType === autoType || flowType === autoArrayType)) { + error(symbol.valueDeclaration, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); + } + // We don't infer a type if assignments are only null or undefined. + return everyType(flowType, isNullableType) ? undefined : convertAutoToAny(flowType); + } - function getWidenedTypeForAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) { - // function/class/{} initializers are themselves containers, so they won't merge in the same way as other initializers - const container = getAssignedExpandoInitializer(symbol.valueDeclaration); - if (container) { - const tag = getJSDocTypeTag(container); - if (tag && tag.typeExpression) { - return getTypeFromTypeNode(tag.typeExpression); - } - const containerObjectType = symbol.valueDeclaration && getJSContainerObjectType(symbol.valueDeclaration, symbol, container); - return containerObjectType || getWidenedLiteralType(checkExpressionCached(container)); - } - let type; - let definedInConstructor = false; - let definedInMethod = false; - // We use control flow analysis to determine the type of the property if the property qualifies as a constructor - // declared property and the resulting control flow type isn't just undefined or null. - if (isConstructorDeclaredProperty(symbol)) { - type = getFlowTypeInConstructor(symbol, getDeclaringConstructor(symbol)!); - } - if (!type) { - let types: Type[] | undefined; - if (symbol.declarations) { - let jsdocType: Type | undefined; - for (const declaration of symbol.declarations) { - const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration : - isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : - undefined; - if (!expression) { - continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere - } - - const kind = isAccessExpression(expression) - ? getAssignmentDeclarationPropertyAccessKind(expression) - : getAssignmentDeclarationKind(expression); - if (kind === AssignmentDeclarationKind.ThisProperty || isBinaryExpression(expression) && isPossiblyAliasedThisProperty(expression, kind)) { - if (isDeclarationInConstructor(expression)) { - definedInConstructor = true; - } - else { - definedInMethod = true; - } - } - if (!isCallExpression(expression)) { - jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration); - } - if (!jsdocType) { - (types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType); - } + function getFlowTypeOfProperty(reference: Node, prop: Symbol | undefined) { + const initialType = prop?.valueDeclaration + && (!isAutoTypedProperty(prop) || getEffectiveModifierFlags(prop.valueDeclaration) & ModifierFlags.Ambient) + && getTypeOfPropertyInBaseClass(prop) + || undefinedType; + return getFlowTypeOfReference(reference, autoType, initialType); + } + + function getWidenedTypeForAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) { + // function/class/{} initializers are themselves containers, so they won't merge in the same way as other initializers + const container = getAssignedExpandoInitializer(symbol.valueDeclaration); + if (container) { + const tag = getJSDocTypeTag(container); + if (tag && tag.typeExpression) { + return getTypeFromTypeNode(tag.typeExpression); + } + const containerObjectType = symbol.valueDeclaration && getJSContainerObjectType(symbol.valueDeclaration, symbol, container); + return containerObjectType || getWidenedLiteralType(checkExpressionCached(container)); + } + let type; + let definedInConstructor = false; + let definedInMethod = false; + // We use control flow analysis to determine the type of the property if the property qualifies as a constructor + // declared property and the resulting control flow type isn't just undefined or null. + if (isConstructorDeclaredProperty(symbol)) { + type = getFlowTypeInConstructor(symbol, getDeclaringConstructor(symbol)!); + } + if (!type) { + let types: Type[] | undefined; + if (symbol.declarations) { + let jsdocType: Type | undefined; + for (const declaration of symbol.declarations) { + const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration : + isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : + undefined; + if (!expression) { + continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere } - type = jsdocType; - } - if (!type) { - if (!length(types)) { - return errorType; // No types from any declarations :( - } - let constructorTypes = definedInConstructor && symbol.declarations ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined; - // use only the constructor types unless they were only assigned null | undefined (including widening variants) - if (definedInMethod) { - const propType = getTypeOfPropertyInBaseClass(symbol); - if (propType) { - (constructorTypes || (constructorTypes = [])).push(propType); + + const kind = isAccessExpression(expression) + ? getAssignmentDeclarationPropertyAccessKind(expression) + : getAssignmentDeclarationKind(expression); + if (kind === AssignmentDeclarationKind.ThisProperty || isBinaryExpression(expression) && isPossiblyAliasedThisProperty(expression, kind)) { + if (isDeclarationInConstructor(expression)) { definedInConstructor = true; } + else { + definedInMethod = true; + } + } + if (!isCallExpression(expression)) { + jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration); + } + if (!jsdocType) { + (types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType); } - const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217 - type = getUnionType(sourceTypes!, UnionReduction.Subtype); } + type = jsdocType; } - const widened = getWidenedType(addOptionality(type, /*isProperty*/ false, definedInMethod && !definedInConstructor)); - if (symbol.valueDeclaration && filterType(widened, t => !!(t.flags & ~TypeFlags.Nullable)) === neverType) { - reportImplicitAny(symbol.valueDeclaration, anyType); - return anyType; + if (!type) { + if (!length(types)) { + return errorType; // No types from any declarations :( + } + let constructorTypes = definedInConstructor && symbol.declarations ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined; + // use only the constructor types unless they were only assigned null | undefined (including widening variants) + if (definedInMethod) { + const propType = getTypeOfPropertyInBaseClass(symbol); + if (propType) { + (constructorTypes || (constructorTypes = [])).push(propType); + definedInConstructor = true; + } + } + const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217 + type = getUnionType(sourceTypes!, UnionReduction.Subtype); } - return widened; } + const widened = getWidenedType(addOptionality(type, /*isProperty*/ false, definedInMethod && !definedInConstructor)); + if (symbol.valueDeclaration && filterType(widened, t => !!(t.flags & ~TypeFlags.Nullable)) === neverType) { + reportImplicitAny(symbol.valueDeclaration, anyType); + return anyType; + } + return widened; + } - function getJSContainerObjectType(decl: Node, symbol: Symbol, init: Expression | undefined): Type | undefined { - if (!isInJSFile(decl) || !init || !isObjectLiteralExpression(init) || init.properties.length) { - return undefined; - } - const exports = createSymbolTable(); - while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) { - const s = getSymbolOfNode(decl); - if (s?.exports?.size) { - mergeSymbolTable(exports, s.exports); - } - decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent; - } + function getJSContainerObjectType(decl: Node, symbol: Symbol, init: Expression | undefined): Type | undefined { + if (!isInJSFile(decl) || !init || !isObjectLiteralExpression(init) || init.properties.length) { + return undefined; + } + const exports = createSymbolTable(); + while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) { const s = getSymbolOfNode(decl); if (s?.exports?.size) { mergeSymbolTable(exports, s.exports); } - const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, emptyArray); - type.objectFlags |= ObjectFlags.JSLiteral; - return type; + decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent; + } + const s = getSymbolOfNode(decl); + if (s?.exports?.size) { + mergeSymbolTable(exports, s.exports); } + const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, emptyArray); + type.objectFlags |= ObjectFlags.JSLiteral; + return type; + } - function getAnnotatedTypeForAssignmentDeclaration(declaredType: Type | undefined, expression: Expression, symbol: Symbol, declaration: Declaration) { - const typeNode = getEffectiveTypeAnnotationNode(expression.parent); - if (typeNode) { - const type = getWidenedType(getTypeFromTypeNode(typeNode)); - if (!declaredType) { - return type; - } - else if (!isErrorType(declaredType) && !isErrorType(type) && !isTypeIdenticalTo(declaredType, type)) { - errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type); - } + function getAnnotatedTypeForAssignmentDeclaration(declaredType: Type | undefined, expression: Expression, symbol: Symbol, declaration: Declaration) { + const typeNode = getEffectiveTypeAnnotationNode(expression.parent); + if (typeNode) { + const type = getWidenedType(getTypeFromTypeNode(typeNode)); + if (!declaredType) { + return type; } - if (symbol.parent?.valueDeclaration) { - const typeNode = getEffectiveTypeAnnotationNode(symbol.parent.valueDeclaration); - if (typeNode) { - const annotationSymbol = getPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName); - if (annotationSymbol) { - return getNonMissingTypeOfSymbol(annotationSymbol); - } + else if (!isErrorType(declaredType) && !isErrorType(type) && !isTypeIdenticalTo(declaredType, type)) { + errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type); + } + } + if (symbol.parent?.valueDeclaration) { + const typeNode = getEffectiveTypeAnnotationNode(symbol.parent.valueDeclaration); + if (typeNode) { + const annotationSymbol = getPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName); + if (annotationSymbol) { + return getNonMissingTypeOfSymbol(annotationSymbol); } } - - return declaredType; } - /** If we don't have an explicit JSDoc type, get the type from the initializer. */ - function getInitializerTypeFromAssignmentDeclaration(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: BinaryExpression | CallExpression, kind: AssignmentDeclarationKind) { - if (isCallExpression(expression)) { - if (resolvedSymbol) { - return getTypeOfSymbol(resolvedSymbol); // This shouldn't happen except under some hopefully forbidden merges of export assignments and object define assignments - } - const objectLitType = checkExpressionCached((expression as BindableObjectDefinePropertyCall).arguments[2]); - const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String); - if (valueType) { - return valueType; - } - const getFunc = getTypeOfPropertyOfType(objectLitType, "get" as __String); - if (getFunc) { - const getSig = getSingleCallSignature(getFunc); - if (getSig) { - return getReturnTypeOfSignature(getSig); - } - } - const setFunc = getTypeOfPropertyOfType(objectLitType, "set" as __String); - if (setFunc) { - const setSig = getSingleCallSignature(setFunc); - if (setSig) { - return getTypeOfFirstParameterOfSignature(setSig); - } + return declaredType; + } + + /** If we don't have an explicit JSDoc type, get the type from the initializer. */ + function getInitializerTypeFromAssignmentDeclaration(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: BinaryExpression | CallExpression, kind: AssignmentDeclarationKind) { + if (isCallExpression(expression)) { + if (resolvedSymbol) { + return getTypeOfSymbol(resolvedSymbol); // This shouldn't happen except under some hopefully forbidden merges of export assignments and object define assignments + } + const objectLitType = checkExpressionCached((expression as BindableObjectDefinePropertyCall).arguments[2]); + const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String); + if (valueType) { + return valueType; + } + const getFunc = getTypeOfPropertyOfType(objectLitType, "get" as __String); + if (getFunc) { + const getSig = getSingleCallSignature(getFunc); + if (getSig) { + return getReturnTypeOfSignature(getSig); } - return anyType; } - if (containsSameNamedThisProperty(expression.left, expression.right)) { - return anyType; + const setFunc = getTypeOfPropertyOfType(objectLitType, "set" as __String); + if (setFunc) { + const setSig = getSingleCallSignature(setFunc); + if (setSig) { + return getTypeOfFirstParameterOfSignature(setSig); + } } - const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right)); - if (type.flags & TypeFlags.Object && - kind === AssignmentDeclarationKind.ModuleExports && - symbol.escapedName === InternalSymbolName.ExportEquals) { - const exportedType = resolveStructuredTypeMembers(type as ObjectType); - const members = createSymbolTable(); - copyEntries(exportedType.members, members); - const initialSize = members.size; - if (resolvedSymbol && !resolvedSymbol.exports) { - resolvedSymbol.exports = createSymbolTable(); - } - (resolvedSymbol || symbol).exports!.forEach((s, name) => { - const exportedMember = members.get(name)!; - if (exportedMember && exportedMember !== s) { - if (s.flags & SymbolFlags.Value && exportedMember.flags & SymbolFlags.Value) { - // If the member has an additional value-like declaration, union the types from the two declarations, - // but issue an error if they occurred in two different files. The purpose is to support a JS file with - // a pattern like: - // - // module.exports = { a: true }; - // module.exports.a = 3; - // - // but we may have a JS file with `module.exports = { a: true }` along with a TypeScript module augmentation - // declaring an `export const a: number`. In that case, we issue a duplicate identifier error, because - // it's unclear what that's supposed to mean, so it's probably a mistake. - if (s.valueDeclaration && exportedMember.valueDeclaration && getSourceFileOfNode(s.valueDeclaration) !== getSourceFileOfNode(exportedMember.valueDeclaration)) { - const unescapedName = unescapeLeadingUnderscores(s.escapedName); - const exportedMemberName = tryCast(exportedMember.valueDeclaration, isNamedDeclaration)?.name || exportedMember.valueDeclaration; - addRelatedInfo( - error(s.valueDeclaration, Diagnostics.Duplicate_identifier_0, unescapedName), - createDiagnosticForNode(exportedMemberName, Diagnostics._0_was_also_declared_here, unescapedName)); - addRelatedInfo( - error(exportedMemberName, Diagnostics.Duplicate_identifier_0, unescapedName), - createDiagnosticForNode(s.valueDeclaration, Diagnostics._0_was_also_declared_here, unescapedName)); - } - const union = createSymbol(s.flags | exportedMember.flags, name); - union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]); - union.valueDeclaration = exportedMember.valueDeclaration; - union.declarations = concatenate(exportedMember.declarations, s.declarations); - members.set(name, union); - } - else { - members.set(name, mergeSymbol(s, exportedMember)); + return anyType; + } + if (containsSameNamedThisProperty(expression.left, expression.right)) { + return anyType; + } + const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right)); + if (type.flags & TypeFlags.Object && + kind === AssignmentDeclarationKind.ModuleExports && + symbol.escapedName === InternalSymbolName.ExportEquals) { + const exportedType = resolveStructuredTypeMembers(type as ObjectType); + const members = createSymbolTable(); + copyEntries(exportedType.members, members); + const initialSize = members.size; + if (resolvedSymbol && !resolvedSymbol.exports) { + resolvedSymbol.exports = createSymbolTable(); + } + (resolvedSymbol || symbol).exports!.forEach((s, name) => { + const exportedMember = members.get(name)!; + if (exportedMember && exportedMember !== s) { + if (s.flags & SymbolFlags.Value && exportedMember.flags & SymbolFlags.Value) { + // If the member has an additional value-like declaration, union the types from the two declarations, + // but issue an error if they occurred in two different files. The purpose is to support a JS file with + // a pattern like: + // + // module.exports = { a: true }; + // module.exports.a = 3; + // + // but we may have a JS file with `module.exports = { a: true }` along with a TypeScript module augmentation + // declaring an `export const a: number`. In that case, we issue a duplicate identifier error, because + // it's unclear what that's supposed to mean, so it's probably a mistake. + if (s.valueDeclaration && exportedMember.valueDeclaration && getSourceFileOfNode(s.valueDeclaration) !== getSourceFileOfNode(exportedMember.valueDeclaration)) { + const unescapedName = unescapeLeadingUnderscores(s.escapedName); + const exportedMemberName = tryCast(exportedMember.valueDeclaration, isNamedDeclaration)?.name || exportedMember.valueDeclaration; + addRelatedInfo( + error(s.valueDeclaration, Diagnostics.Duplicate_identifier_0, unescapedName), + createDiagnosticForNode(exportedMemberName, Diagnostics._0_was_also_declared_here, unescapedName)); + addRelatedInfo( + error(exportedMemberName, Diagnostics.Duplicate_identifier_0, unescapedName), + createDiagnosticForNode(s.valueDeclaration, Diagnostics._0_was_also_declared_here, unescapedName)); } + const union = createSymbol(s.flags | exportedMember.flags, name); + union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]); + union.valueDeclaration = exportedMember.valueDeclaration; + union.declarations = concatenate(exportedMember.declarations, s.declarations); + members.set(name, union); } else { - members.set(name, s); + members.set(name, mergeSymbol(s, exportedMember)); } - }); - const result = createAnonymousType( - initialSize !== members.size ? undefined : exportedType.symbol, // Only set the type's symbol if it looks to be the same as the original type - members, - exportedType.callSignatures, - exportedType.constructSignatures, - exportedType.indexInfos); - result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag - if (result.symbol && result.symbol.flags & SymbolFlags.Class && type === getDeclaredTypeOfClassOrInterface(result.symbol)) { - result.objectFlags |= ObjectFlags.IsClassInstanceClone; // Propagate the knowledge that this type is equivalent to the symbol's class instance type } - return result; - } - if (isEmptyArrayLiteralType(type)) { - reportImplicitAny(expression, anyArrayType); - return anyArrayType; + else { + members.set(name, s); + } + }); + const result = createAnonymousType( + initialSize !== members.size ? undefined : exportedType.symbol, // Only set the type's symbol if it looks to be the same as the original type + members, + exportedType.callSignatures, + exportedType.constructSignatures, + exportedType.indexInfos); + result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag + if (result.symbol && result.symbol.flags & SymbolFlags.Class && type === getDeclaredTypeOfClassOrInterface(result.symbol)) { + result.objectFlags |= ObjectFlags.IsClassInstanceClone; // Propagate the knowledge that this type is equivalent to the symbol's class instance type } - return type; + return result; } - - function containsSameNamedThisProperty(thisProperty: Expression, expression: Expression) { - return isPropertyAccessExpression(thisProperty) - && thisProperty.expression.kind === SyntaxKind.ThisKeyword - && forEachChildRecursively(expression, n => isMatchingReference(thisProperty, n)); + if (isEmptyArrayLiteralType(type)) { + reportImplicitAny(expression, anyArrayType); + return anyArrayType; } + return type; + } - function isDeclarationInConstructor(expression: Expression) { - const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false); - // Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added. - // Function expressions that are assigned to the prototype count as methods. - return thisContainer.kind === SyntaxKind.Constructor || - thisContainer.kind === SyntaxKind.FunctionDeclaration || - (thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent)); - } + function containsSameNamedThisProperty(thisProperty: Expression, expression: Expression) { + return isPropertyAccessExpression(thisProperty) + && thisProperty.expression.kind === SyntaxKind.ThisKeyword + && forEachChildRecursively(expression, n => isMatchingReference(thisProperty, n)); + } - function getConstructorDefinedThisAssignmentTypes(types: Type[], declarations: Declaration[]): Type[] | undefined { - Debug.assert(types.length === declarations.length); - return types.filter((_, i) => { - const declaration = declarations[i]; - const expression = isBinaryExpression(declaration) ? declaration : - isBinaryExpression(declaration.parent) ? declaration.parent : undefined; - return expression && isDeclarationInConstructor(expression); - }); - } + function isDeclarationInConstructor(expression: Expression) { + const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false); + // Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added. + // Function expressions that are assigned to the prototype count as methods. + return thisContainer.kind === SyntaxKind.Constructor || + thisContainer.kind === SyntaxKind.FunctionDeclaration || + (thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent)); + } - // Return the type implied by a binding pattern element. This is the type of the initializer of the element if - // one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding - // pattern. Otherwise, it is the type any. - function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type { - if (element.initializer) { - // The type implied by a binding pattern is independent of context, so we check the initializer with no - // contextual type or, if the element itself is a binding pattern, with the type implied by that binding - // pattern. - const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType; - return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, contextualType))); - } - if (isBindingPattern(element.name)) { - return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors); - } - if (reportErrors && !declarationBelongsToPrivateAmbientMember(element)) { - reportImplicitAny(element, anyType); - } - // When we're including the pattern in the type (an indication we're obtaining a contextual type), we - // use the non-inferrable any type. Inference will never directly infer this type, but it is possible - // to infer a type that contains it, e.g. for a binding pattern like [foo] or { foo }. In such cases, - // widening of the binding pattern type substitutes a regular any for the non-inferrable any. - return includePatternInType ? nonInferrableAnyType : anyType; - } + function getConstructorDefinedThisAssignmentTypes(types: Type[], declarations: Declaration[]): Type[] | undefined { + Debug.assert(types.length === declarations.length); + return types.filter((_, i) => { + const declaration = declarations[i]; + const expression = isBinaryExpression(declaration) ? declaration : + isBinaryExpression(declaration.parent) ? declaration.parent : undefined; + return expression && isDeclarationInConstructor(expression); + }); + } - // Return the type implied by an object binding pattern - function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { - const members = createSymbolTable(); - let stringIndexInfo: IndexInfo | undefined; - let objectFlags = ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - forEach(pattern.elements, e => { - const name = e.propertyName || e.name as Identifier; - if (e.dotDotDotToken) { - stringIndexInfo = createIndexInfo(stringType, anyType, /*isReadonly*/ false); - return; - } + // Return the type implied by a binding pattern element. This is the type of the initializer of the element if + // one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding + // pattern. Otherwise, it is the type any. + function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type { + if (element.initializer) { + // The type implied by a binding pattern is independent of context, so we check the initializer with no + // contextual type or, if the element itself is a binding pattern, with the type implied by that binding + // pattern. + const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType; + return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, contextualType))); + } + if (isBindingPattern(element.name)) { + return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors); + } + if (reportErrors && !declarationBelongsToPrivateAmbientMember(element)) { + reportImplicitAny(element, anyType); + } + // When we're including the pattern in the type (an indication we're obtaining a contextual type), we + // use the non-inferrable any type. Inference will never directly infer this type, but it is possible + // to infer a type that contains it, e.g. for a binding pattern like [foo] or { foo }. In such cases, + // widening of the binding pattern type substitutes a regular any for the non-inferrable any. + return includePatternInType ? nonInferrableAnyType : anyType; + } - const exprType = getLiteralTypeFromPropertyName(name); - if (!isTypeUsableAsPropertyName(exprType)) { - // do not include computed properties in the implied type - objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; - return; - } - const text = getPropertyNameFromType(exprType); - const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0); - const symbol = createSymbol(flags, text); - symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); - symbol.bindingElement = e; - members.set(symbol.escapedName, symbol); - }); - const result = createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo ? [stringIndexInfo] : emptyArray); - result.objectFlags |= objectFlags; - if (includePatternInType) { - result.pattern = pattern; - result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; + // Return the type implied by an object binding pattern + function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { + const members = createSymbolTable(); + let stringIndexInfo: IndexInfo | undefined; + let objectFlags = ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + forEach(pattern.elements, e => { + const name = e.propertyName || e.name as Identifier; + if (e.dotDotDotToken) { + stringIndexInfo = createIndexInfo(stringType, anyType, /*isReadonly*/ false); + return; } - return result; - } - // Return the type implied by an array binding pattern - function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { - const elements = pattern.elements; - const lastElement = lastOrUndefined(elements); - const restElement = lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken ? lastElement : undefined; - if (elements.length === 0 || elements.length === 1 && restElement) { - return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType; - } - const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors)); - const minLength = findLastIndex(elements, e => !(e === restElement || isOmittedExpression(e) || hasDefaultValue(e)), elements.length - 1) + 1; - const elementFlags = map(elements, (e, i) => e === restElement ? ElementFlags.Rest : i >= minLength ? ElementFlags.Optional : ElementFlags.Required); - let result = createTupleType(elementTypes, elementFlags) as TypeReference; - if (includePatternInType) { - result = cloneTypeReference(result); - result.pattern = pattern; - result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; + const exprType = getLiteralTypeFromPropertyName(name); + if (!isTypeUsableAsPropertyName(exprType)) { + // do not include computed properties in the implied type + objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; + return; } - return result; - } - - // Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself - // and without regard to its context (i.e. without regard any type annotation or initializer associated with the - // declaration in which the binding pattern is contained). For example, the implied type of [x, y] is [any, any] - // and the implied type of { x, y: z = 1 } is { x: any; y: number; }. The type implied by a binding pattern is - // used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring - // parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of - // the parameter. - function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false): Type { - return pattern.kind === SyntaxKind.ObjectBindingPattern - ? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors) - : getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors); + const text = getPropertyNameFromType(exprType); + const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0); + const symbol = createSymbol(flags, text); + symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); + symbol.bindingElement = e; + members.set(symbol.escapedName, symbol); + }); + const result = createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo ? [stringIndexInfo] : emptyArray); + result.objectFlags |= objectFlags; + if (includePatternInType) { + result.pattern = pattern; + result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; } + return result; + } - // Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type - // specified in a type annotation or inferred from an initializer. However, in the case of a destructuring declaration it - // is a bit more involved. For example: - // - // var [x, s = ""] = [1, "one"]; - // - // Here, the array literal [1, "one"] is contextually typed by the type [any, string], which is the implied type of the - // binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the - // tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string. - function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, reportErrors?: boolean): Type { - return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true), declaration, reportErrors); - } + // Return the type implied by an array binding pattern + function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { + const elements = pattern.elements; + const lastElement = lastOrUndefined(elements); + const restElement = lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken ? lastElement : undefined; + if (elements.length === 0 || elements.length === 1 && restElement) { + return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType; + } + const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors)); + const minLength = findLastIndex(elements, e => !(e === restElement || isOmittedExpression(e) || hasDefaultValue(e)), elements.length - 1) + 1; + const elementFlags = map(elements, (e, i) => e === restElement ? ElementFlags.Rest : i >= minLength ? ElementFlags.Optional : ElementFlags.Required); + let result = createTupleType(elementTypes, elementFlags) as TypeReference; + if (includePatternInType) { + result = cloneTypeReference(result); + result.pattern = pattern; + result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; + } + return result; + } - function isGlobalSymbolConstructor(node: Node) { - const symbol = getSymbolOfNode(node); - const globalSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false); - return globalSymbol && symbol && symbol === globalSymbol; - } + // Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself + // and without regard to its context (i.e. without regard any type annotation or initializer associated with the + // declaration in which the binding pattern is contained). For example, the implied type of [x, y] is [any, any] + // and the implied type of { x, y: z = 1 } is { x: any; y: number; }. The type implied by a binding pattern is + // used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring + // parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of + // the parameter. + function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false): Type { + return pattern.kind === SyntaxKind.ObjectBindingPattern + ? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors) + : getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors); + } - function widenTypeForVariableLikeDeclaration(type: Type | undefined, declaration: any, reportErrors?: boolean) { - if (type) { - // TODO: If back compat with pre-3.0/4.0 libs isn't required, remove the following SymbolConstructor special case transforming `symbol` into `unique symbol` - if (type.flags & TypeFlags.ESSymbol && isGlobalSymbolConstructor(declaration.parent)) { - type = getESSymbolLikeTypeForNode(declaration); - } - if (reportErrors) { - reportErrorsFromWidening(declaration, type); - } + // Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type + // specified in a type annotation or inferred from an initializer. However, in the case of a destructuring declaration it + // is a bit more involved. For example: + // + // var [x, s = ""] = [1, "one"]; + // + // Here, the array literal [1, "one"] is contextually typed by the type [any, string], which is the implied type of the + // binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the + // tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string. + function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, reportErrors?: boolean): Type { + return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true), declaration, reportErrors); + } - // always widen a 'unique symbol' type if the type was created for a different declaration. - if (type.flags & TypeFlags.UniqueESSymbol && (isBindingElement(declaration) || !declaration.type) && type.symbol !== getSymbolOfNode(declaration)) { - type = esSymbolType; - } + function isGlobalSymbolConstructor(node: Node) { + const symbol = getSymbolOfNode(node); + const globalSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false); + return globalSymbol && symbol && symbol === globalSymbol; + } - return getWidenedType(type); + function widenTypeForVariableLikeDeclaration(type: Type | undefined, declaration: any, reportErrors?: boolean) { + if (type) { + // TODO: If back compat with pre-3.0/4.0 libs isn't required, remove the following SymbolConstructor special case transforming `symbol` into `unique symbol` + if (type.flags & TypeFlags.ESSymbol && isGlobalSymbolConstructor(declaration.parent)) { + type = getESSymbolLikeTypeForNode(declaration); } - - // Rest parameters default to type any[], other parameters default to type any - type = isParameter(declaration) && declaration.dotDotDotToken ? anyArrayType : anyType; - - // Report implicit any errors unless this is a private property within an ambient declaration if (reportErrors) { - if (!declarationBelongsToPrivateAmbientMember(declaration)) { - reportImplicitAny(declaration, type); - } + reportErrorsFromWidening(declaration, type); + } + + // always widen a 'unique symbol' type if the type was created for a different declaration. + if (type.flags & TypeFlags.UniqueESSymbol && (isBindingElement(declaration) || !declaration.type) && type.symbol !== getSymbolOfNode(declaration)) { + type = esSymbolType; } - return type; - } - function declarationBelongsToPrivateAmbientMember(declaration: VariableLikeDeclaration) { - const root = getRootDeclaration(declaration); - const memberDeclaration = root.kind === SyntaxKind.Parameter ? root.parent : root; - return isPrivateWithinAmbient(memberDeclaration); + return getWidenedType(type); } - function tryGetTypeFromEffectiveTypeNode(declaration: Declaration) { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - return getTypeFromTypeNode(typeNode); + // Rest parameters default to type any[], other parameters default to type any + type = isParameter(declaration) && declaration.dotDotDotToken ? anyArrayType : anyType; + + // Report implicit any errors unless this is a private property within an ambient declaration + if (reportErrors) { + if (!declarationBelongsToPrivateAmbientMember(declaration)) { + reportImplicitAny(declaration, type); } } + return type; + } - function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); + function declarationBelongsToPrivateAmbientMember(declaration: VariableLikeDeclaration) { + const root = getRootDeclaration(declaration); + const memberDeclaration = root.kind === SyntaxKind.Parameter ? root.parent : root; + return isPrivateWithinAmbient(memberDeclaration); + } + + function tryGetTypeFromEffectiveTypeNode(declaration: Declaration) { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); + } + } + + function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol); + // For a contextually typed parameter it is possible that a type has already + // been assigned (in assignTypeToParameterAndFixTypeParameters), and we want + // to preserve this type. if (!links.type) { - const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol); - // For a contextually typed parameter it is possible that a type has already - // been assigned (in assignTypeToParameterAndFixTypeParameters), and we want - // to preserve this type. - if (!links.type) { - links.type = type; - } + links.type = type; } - return links.type; } + return links.type; + } - function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol): Type { - // Handle prototype property - if (symbol.flags & SymbolFlags.Prototype) { - return getTypeOfPrototypeProperty(symbol); - } - // CommonsJS require and module both have type any. - if (symbol === requireSymbol) { - return anyType; - } - if (symbol.flags & SymbolFlags.ModuleExports && symbol.valueDeclaration) { - const fileSymbol = getSymbolOfNode(getSourceFileOfNode(symbol.valueDeclaration)); - const result = createSymbol(fileSymbol.flags, "exports" as __String); - result.declarations = fileSymbol.declarations ? fileSymbol.declarations.slice() : []; - result.parent = symbol; - result.target = fileSymbol; - if (fileSymbol.valueDeclaration) result.valueDeclaration = fileSymbol.valueDeclaration; - if (fileSymbol.members) result.members = new Map(fileSymbol.members); - if (fileSymbol.exports) result.exports = new Map(fileSymbol.exports); - const members = createSymbolTable(); - members.set("exports" as __String, result); - return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); - } - // Handle catch clause variables - Debug.assertIsDefined(symbol.valueDeclaration); - const declaration = symbol.valueDeclaration; - if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode === undefined) { - return useUnknownInCatchVariables ? unknownType : anyType; - } - const type = getTypeOfNode(typeNode); - // an errorType will make `checkTryStatement` issue an error - return isTypeAny(type) || type === unknownType ? type : errorType; + function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol): Type { + // Handle prototype property + if (symbol.flags & SymbolFlags.Prototype) { + return getTypeOfPrototypeProperty(symbol); + } + // CommonsJS require and module both have type any. + if (symbol === requireSymbol) { + return anyType; + } + if (symbol.flags & SymbolFlags.ModuleExports && symbol.valueDeclaration) { + const fileSymbol = getSymbolOfNode(getSourceFileOfNode(symbol.valueDeclaration)); + const result = createSymbol(fileSymbol.flags, "exports" as __String); + result.declarations = fileSymbol.declarations ? fileSymbol.declarations.slice() : []; + result.parent = symbol; + result.target = fileSymbol; + if (fileSymbol.valueDeclaration) result.valueDeclaration = fileSymbol.valueDeclaration; + if (fileSymbol.members) result.members = new Map(fileSymbol.members); + if (fileSymbol.exports) result.exports = new Map(fileSymbol.exports); + const members = createSymbolTable(); + members.set("exports" as __String, result); + return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + } + // Handle catch clause variables + Debug.assertIsDefined(symbol.valueDeclaration); + const declaration = symbol.valueDeclaration; + if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode === undefined) { + return useUnknownInCatchVariables ? unknownType : anyType; } - // Handle export default expressions - if (isSourceFile(declaration) && isJsonSourceFile(declaration)) { - if (!declaration.statements.length) { - return emptyObjectType; - } - return getWidenedType(getWidenedLiteralType(checkExpression(declaration.statements[0].expression))); + const type = getTypeOfNode(typeNode); + // an errorType will make `checkTryStatement` issue an error + return isTypeAny(type) || type === unknownType ? type : errorType; + } + // Handle export default expressions + if (isSourceFile(declaration) && isJsonSourceFile(declaration)) { + if (!declaration.statements.length) { + return emptyObjectType; } + return getWidenedType(getWidenedLiteralType(checkExpression(declaration.statements[0].expression))); + } - // Handle variable, parameter or property - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` - if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { - return getTypeOfFuncClassEnumModule(symbol); - } - return reportCircularityError(symbol); - } - let type: Type; - if (declaration.kind === SyntaxKind.ExportAssignment) { - type = widenTypeForVariableLikeDeclaration(tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionCached((declaration as ExportAssignment).expression), declaration); - } - else if ( - isBinaryExpression(declaration) || - (isInJSFile(declaration) && - (isCallExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableStaticElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent)))) { - type = getWidenedTypeForAssignmentDeclaration(symbol); - } - else if (isPropertyAccessExpression(declaration) - || isElementAccessExpression(declaration) - || isIdentifier(declaration) - || isStringLiteralLike(declaration) - || isNumericLiteral(declaration) - || isClassDeclaration(declaration) - || isFunctionDeclaration(declaration) - || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) - || isMethodSignature(declaration) - || isSourceFile(declaration)) { - // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { - return getTypeOfFuncClassEnumModule(symbol); - } - type = isBinaryExpression(declaration.parent) ? - getWidenedTypeForAssignmentDeclaration(symbol) : - tryGetTypeFromEffectiveTypeNode(declaration) || anyType; - } - else if (isPropertyAssignment(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration); - } - else if (isJsxAttribute(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration); - } - else if (isShorthandPropertyAssignment(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal); - } - else if (isObjectLiteralMethod(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal); - } - else if (isParameter(declaration) - || isPropertyDeclaration(declaration) - || isPropertySignature(declaration) - || isVariableDeclaration(declaration) - || isBindingElement(declaration) - || isJSDocPropertyLikeTag(declaration)) { - type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); - } - // getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive. - // Re-dispatch based on valueDeclaration.kind instead. - else if (isEnumDeclaration(declaration)) { - type = getTypeOfFuncClassEnumModule(symbol); - } - else if (isEnumMember(declaration)) { - type = getTypeOfEnumMember(symbol); - } - else if (isAccessor(declaration)) { - type = resolveTypeOfAccessors(symbol) || Debug.fail("Non-write accessor resolution must always produce a type"); - } - else { - return Debug.fail("Unhandled declaration kind! " + Debug.formatSyntaxKind(declaration.kind) + " for " + Debug.formatSymbol(symbol)); + // Handle variable, parameter or property + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` + if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { + return getTypeOfFuncClassEnumModule(symbol); } - - if (!popTypeResolution()) { - // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` - if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { - return getTypeOfFuncClassEnumModule(symbol); - } - return reportCircularityError(symbol); + return reportCircularityError(symbol); + } + let type: Type; + if (declaration.kind === SyntaxKind.ExportAssignment) { + type = widenTypeForVariableLikeDeclaration(tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionCached((declaration as ExportAssignment).expression), declaration); + } + else if ( + isBinaryExpression(declaration) || + (isInJSFile(declaration) && + (isCallExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableStaticElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent)))) { + type = getWidenedTypeForAssignmentDeclaration(symbol); + } + else if (isPropertyAccessExpression(declaration) + || isElementAccessExpression(declaration) + || isIdentifier(declaration) + || isStringLiteralLike(declaration) + || isNumericLiteral(declaration) + || isClassDeclaration(declaration) + || isFunctionDeclaration(declaration) + || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) + || isMethodSignature(declaration) + || isSourceFile(declaration)) { + // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { + return getTypeOfFuncClassEnumModule(symbol); } - return type; + type = isBinaryExpression(declaration.parent) ? + getWidenedTypeForAssignmentDeclaration(symbol) : + tryGetTypeFromEffectiveTypeNode(declaration) || anyType; } - - function getAnnotatedAccessorTypeNode(accessor: AccessorDeclaration | undefined): TypeNode | undefined { - if (accessor) { - if (accessor.kind === SyntaxKind.GetAccessor) { - const getterTypeAnnotation = getEffectiveReturnTypeNode(accessor); - return getterTypeAnnotation; - } - else { - const setterTypeAnnotation = getEffectiveSetAccessorTypeAnnotationNode(accessor); - return setterTypeAnnotation; - } - } - return undefined; + else if (isPropertyAssignment(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration); } - - function getAnnotatedAccessorType(accessor: AccessorDeclaration | undefined): Type | undefined { - const node = getAnnotatedAccessorTypeNode(accessor); - return node && getTypeFromTypeNode(node); + else if (isJsxAttribute(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration); } - - function getAnnotatedAccessorThisParameter(accessor: AccessorDeclaration): Symbol | undefined { - const parameter = getAccessorThisParameter(accessor); - return parameter && parameter.symbol; + else if (isShorthandPropertyAssignment(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal); } - - function getThisTypeOfDeclaration(declaration: SignatureDeclaration): Type | undefined { - return getThisTypeOfSignature(getSignatureFromDeclaration(declaration)); + else if (isObjectLiteralMethod(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal); } - - function getTypeOfAccessors(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - return links.type || (links.type = getTypeOfAccessorsWorker(symbol) || Debug.fail("Read type of accessor must always produce a type")); + else if (isParameter(declaration) + || isPropertyDeclaration(declaration) + || isPropertySignature(declaration) + || isVariableDeclaration(declaration) + || isBindingElement(declaration) + || isJSDocPropertyLikeTag(declaration)) { + type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); } - - function getTypeOfSetAccessor(symbol: Symbol): Type | undefined { - const links = getSymbolLinks(symbol); - return links.writeType || (links.writeType = getTypeOfAccessorsWorker(symbol, /*writing*/ true)); + // getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive. + // Re-dispatch based on valueDeclaration.kind instead. + else if (isEnumDeclaration(declaration)) { + type = getTypeOfFuncClassEnumModule(symbol); + } + else if (isEnumMember(declaration)) { + type = getTypeOfEnumMember(symbol); + } + else if (isAccessor(declaration)) { + type = resolveTypeOfAccessors(symbol) || Debug.fail("Non-write accessor resolution must always produce a type"); + } + else { + return Debug.fail("Unhandled declaration kind! " + Debug.formatSyntaxKind(declaration.kind) + " for " + Debug.formatSymbol(symbol)); } - function getTypeOfAccessorsWorker(symbol: Symbol, writing = false): Type | undefined { - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return errorType; + if (!popTypeResolution()) { + // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` + if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { + return getTypeOfFuncClassEnumModule(symbol); } + return reportCircularityError(symbol); + } + return type; + } - let type = resolveTypeOfAccessors(symbol, writing); - - if (!popTypeResolution()) { - type = anyType; - if (noImplicitAny) { - const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); - error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol)); - } + function getAnnotatedAccessorTypeNode(accessor: AccessorDeclaration | undefined): TypeNode | undefined { + if (accessor) { + if (accessor.kind === SyntaxKind.GetAccessor) { + const getterTypeAnnotation = getEffectiveReturnTypeNode(accessor); + return getterTypeAnnotation; + } + else { + const setterTypeAnnotation = getEffectiveSetAccessorTypeAnnotationNode(accessor); + return setterTypeAnnotation; } - return type; } + return undefined; + } - function resolveTypeOfAccessors(symbol: Symbol, writing = false) { - const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); - const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); + function getAnnotatedAccessorType(accessor: AccessorDeclaration | undefined): Type | undefined { + const node = getAnnotatedAccessorTypeNode(accessor); + return node && getTypeFromTypeNode(node); + } - const setterType = getAnnotatedAccessorType(setter); + function getAnnotatedAccessorThisParameter(accessor: AccessorDeclaration): Symbol | undefined { + const parameter = getAccessorThisParameter(accessor); + return parameter && parameter.symbol; + } - // For write operations, prioritize type annotations on the setter - if (writing && setterType) { - return instantiateTypeIfNeeded(setterType, symbol); - } - // Else defer to the getter type + function getThisTypeOfDeclaration(declaration: SignatureDeclaration): Type | undefined { + return getThisTypeOfSignature(getSignatureFromDeclaration(declaration)); + } - if (getter && isInJSFile(getter)) { - const jsDocType = getTypeForDeclarationFromJSDocComment(getter); - if (jsDocType) { - return instantiateTypeIfNeeded(jsDocType, symbol); - } - } + function getTypeOfAccessors(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + return links.type || (links.type = getTypeOfAccessorsWorker(symbol) || Debug.fail("Read type of accessor must always produce a type")); + } - // Try to see if the user specified a return type on the get-accessor. - const getterType = getAnnotatedAccessorType(getter); - if (getterType) { - return instantiateTypeIfNeeded(getterType, symbol); - } + function getTypeOfSetAccessor(symbol: Symbol): Type | undefined { + const links = getSymbolLinks(symbol); + return links.writeType || (links.writeType = getTypeOfAccessorsWorker(symbol, /*writing*/ true)); + } - // If the user didn't specify a return type, try to use the set-accessor's parameter type. - if (setterType) { - return setterType; - } + function getTypeOfAccessorsWorker(symbol: Symbol, writing = false): Type | undefined { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return errorType; + } - // If there are no specified types, try to infer it from the body of the get accessor if it exists. - if (getter && getter.body) { - const returnTypeFromBody = getReturnTypeFromBody(getter); - return instantiateTypeIfNeeded(returnTypeFromBody, symbol); - } + let type = resolveTypeOfAccessors(symbol, writing); - // Otherwise, fall back to 'any'. - if (setter) { - if (!isPrivateWithinAmbient(setter)) { - errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol)); - } - return anyType; - } - else if (getter) { - Debug.assert(!!getter, "there must exist a getter as we are current checking either setter or getter in this function"); - if (!isPrivateWithinAmbient(getter)) { - errorOrSuggestion(noImplicitAny, getter, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol)); - } - return anyType; + if (!popTypeResolution()) { + type = anyType; + if (noImplicitAny) { + const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); + error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol)); } - return undefined; + } + return type; + } - function instantiateTypeIfNeeded(type: Type, symbol: Symbol) { - if (getCheckFlags(symbol) & CheckFlags.Instantiated) { - const links = getSymbolLinks(symbol); - return instantiateType(type, links.mapper); - } + function resolveTypeOfAccessors(symbol: Symbol, writing = false) { + const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); + const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); - return type; - } - } + const setterType = getAnnotatedAccessorType(setter); - function getBaseTypeVariableOfClass(symbol: Symbol) { - const baseConstructorType = getBaseConstructorTypeOfClass(getDeclaredTypeOfClassOrInterface(symbol)); - return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType : - baseConstructorType.flags & TypeFlags.Intersection ? find((baseConstructorType as IntersectionType).types, t => !!(t.flags & TypeFlags.TypeVariable)) : - undefined; + // For write operations, prioritize type annotations on the setter + if (writing && setterType) { + return instantiateTypeIfNeeded(setterType, symbol); } + // Else defer to the getter type - function getTypeOfFuncClassEnumModule(symbol: Symbol): Type { - let links = getSymbolLinks(symbol); - const originalLinks = links; - if (!links.type) { - const expando = symbol.valueDeclaration && getSymbolOfExpando(symbol.valueDeclaration, /*allowDeclaration*/ false); - if (expando) { - const merged = mergeJSSymbols(symbol, expando); - if (merged) { - // note:we overwrite links because we just cloned the symbol - symbol = links = merged; - } - } - originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol); + if (getter && isInJSFile(getter)) { + const jsDocType = getTypeForDeclarationFromJSDocComment(getter); + if (jsDocType) { + return instantiateTypeIfNeeded(jsDocType, symbol); } - return links.type; } - function getTypeOfFuncClassEnumModuleWorker(symbol: Symbol): Type { - const declaration = symbol.valueDeclaration; - if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) { - return anyType; - } - else if (declaration && (declaration.kind === SyntaxKind.BinaryExpression || - isAccessExpression(declaration) && - declaration.parent.kind === SyntaxKind.BinaryExpression)) { - return getWidenedTypeForAssignmentDeclaration(symbol); - } - else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) { - const resolvedModule = resolveExternalModuleSymbol(symbol); - if (resolvedModule !== symbol) { - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return errorType; - } - const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!); - const type = getWidenedTypeForAssignmentDeclaration(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule); - if (!popTypeResolution()) { - return reportCircularityError(symbol); - } - return type; - } - } - const type = createObjectType(ObjectFlags.Anonymous, symbol); - if (symbol.flags & SymbolFlags.Class) { - const baseTypeVariable = getBaseTypeVariableOfClass(symbol); - return baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type; - } - else { - return strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type) : type; - } + // Try to see if the user specified a return type on the get-accessor. + const getterType = getAnnotatedAccessorType(getter); + if (getterType) { + return instantiateTypeIfNeeded(getterType, symbol); } - function getTypeOfEnumMember(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - return links.type || (links.type = getDeclaredTypeOfEnumMember(symbol)); + // If the user didn't specify a return type, try to use the set-accessor's parameter type. + if (setterType) { + return setterType; } - function getTypeOfAlias(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.type) { - const targetSymbol = resolveAlias(symbol); - const exportSymbol = symbol.declarations && getTargetOfAliasDeclaration(getDeclarationOfAliasSymbol(symbol)!, /*dontResolveAlias*/ true); - const declaredType = firstDefined(exportSymbol?.declarations, d => isExportAssignment(d) ? tryGetTypeFromEffectiveTypeNode(d) : undefined); - // It only makes sense to get the type of a value symbol. If the result of resolving - // the alias is not a value, then it has no type. To get the type associated with a - // type symbol, call getDeclaredTypeOfSymbol. - // This check is important because without it, a call to getTypeOfSymbol could end - // up recursively calling getTypeOfAlias, causing a stack overflow. - links.type = exportSymbol?.declarations && isDuplicatedCommonJSExport(exportSymbol.declarations) && symbol.declarations!.length ? getFlowTypeFromCommonJSExport(exportSymbol) - : isDuplicatedCommonJSExport(symbol.declarations) ? autoType - : declaredType ? declaredType - : targetSymbol.flags & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol) - : errorType; - } - return links.type; - } - - function getTypeOfInstantiatedSymbol(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.type) { - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return links.type = errorType; - } - let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper); - if (!popTypeResolution()) { - type = reportCircularityError(symbol); - } - links.type = type; - } - return links.type; + // If there are no specified types, try to infer it from the body of the get accessor if it exists. + if (getter && getter.body) { + const returnTypeFromBody = getReturnTypeFromBody(getter); + return instantiateTypeIfNeeded(returnTypeFromBody, symbol); } - function reportCircularityError(symbol: Symbol) { - const declaration = symbol.valueDeclaration as VariableLikeDeclaration; - // Check if variable has type annotation that circularly references the variable itself - if (getEffectiveTypeAnnotationNode(declaration)) { - error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, - symbolToString(symbol)); - return errorType; + // Otherwise, fall back to 'any'. + if (setter) { + if (!isPrivateWithinAmbient(setter)) { + errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol)); } - // Check if variable has initializer that circularly references the variable itself - if (noImplicitAny && (declaration.kind !== SyntaxKind.Parameter || (declaration as HasInitializer).initializer)) { - error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer, - symbolToString(symbol)); - } - // Circularities could also result from parameters in function expressions that end up - // having themselves as contextual types following type argument inference. In those cases - // we have already reported an implicit any error so we don't report anything here. return anyType; } - - function getTypeOfSymbolWithDeferredType(symbol: Symbol) { - const links = getSymbolLinks(symbol); - if (!links.type) { - Debug.assertIsDefined(links.deferralParent); - Debug.assertIsDefined(links.deferralConstituents); - links.type = links.deferralParent.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents) : getIntersectionType(links.deferralConstituents); - } - return links.type; - } - - function getSetAccessorTypeOfSymbol(symbol: Symbol): Type { - if (symbol.flags & SymbolFlags.Accessor) { - const type = getTypeOfSetAccessor(symbol); - if (type) { - return type; - } + else if (getter) { + Debug.assert(!!getter, "there must exist a getter as we are current checking either setter or getter in this function"); + if (!isPrivateWithinAmbient(getter)) { + errorOrSuggestion(noImplicitAny, getter, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol)); } - return getTypeOfSymbol(symbol); + return anyType; } + return undefined; - function getTypeOfSymbol(symbol: Symbol): Type { - const checkFlags = getCheckFlags(symbol); - if (checkFlags & CheckFlags.DeferredType) { - return getTypeOfSymbolWithDeferredType(symbol); - } - if (checkFlags & CheckFlags.Instantiated) { - return getTypeOfInstantiatedSymbol(symbol); - } - if (checkFlags & CheckFlags.Mapped) { - return getTypeOfMappedSymbol(symbol as MappedSymbol); - } - if (checkFlags & CheckFlags.ReverseMapped) { - return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol); - } - if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { - return getTypeOfVariableOrParameterOrProperty(symbol); - } - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { - return getTypeOfFuncClassEnumModule(symbol); - } - if (symbol.flags & SymbolFlags.EnumMember) { - return getTypeOfEnumMember(symbol); - } - if (symbol.flags & SymbolFlags.Accessor) { - return getTypeOfAccessors(symbol); - } - if (symbol.flags & SymbolFlags.Alias) { - return getTypeOfAlias(symbol); + function instantiateTypeIfNeeded(type: Type, symbol: Symbol) { + if (getCheckFlags(symbol) & CheckFlags.Instantiated) { + const links = getSymbolLinks(symbol); + return instantiateType(type, links.mapper); } - return errorType; - } - - function getNonMissingTypeOfSymbol(symbol: Symbol) { - return removeMissingType(getTypeOfSymbol(symbol), !!(symbol.flags & SymbolFlags.Optional)); - } - function isReferenceToType(type: Type, target: Type) { - return type !== undefined - && target !== undefined - && (getObjectFlags(type) & ObjectFlags.Reference) !== 0 - && (type as TypeReference).target === target; + return type; } + } - function getTargetType(type: Type): Type { - return getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target : type; - } + function getBaseTypeVariableOfClass(symbol: Symbol) { + const baseConstructorType = getBaseConstructorTypeOfClass(getDeclaredTypeOfClassOrInterface(symbol)); + return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType : + baseConstructorType.flags & TypeFlags.Intersection ? find((baseConstructorType as IntersectionType).types, t => !!(t.flags & TypeFlags.TypeVariable)) : + undefined; + } - // TODO: GH#18217 If `checkBase` is undefined, we should not call this because this will always return false. - function hasBaseType(type: Type, checkBase: Type | undefined) { - return check(type); - function check(type: Type): boolean { - if (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) { - const target = getTargetType(type) as InterfaceType; - return target === checkBase || some(getBaseTypes(target), check); - } - else if (type.flags & TypeFlags.Intersection) { - return some((type as IntersectionType).types, check); + function getTypeOfFuncClassEnumModule(symbol: Symbol): Type { + let links = getSymbolLinks(symbol); + const originalLinks = links; + if (!links.type) { + const expando = symbol.valueDeclaration && getSymbolOfExpando(symbol.valueDeclaration, /*allowDeclaration*/ false); + if (expando) { + const merged = mergeJSSymbols(symbol, expando); + if (merged) { + // note:we overwrite links because we just cloned the symbol + symbol = links = merged; } - return false; } + originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol); } + return links.type; + } - // Appends the type parameters given by a list of declarations to a set of type parameters and returns the resulting set. - // The function allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set - // in-place and returns the same array. - function appendTypeParameters(typeParameters: TypeParameter[] | undefined, declarations: readonly TypeParameterDeclaration[]): TypeParameter[] | undefined { - for (const declaration of declarations) { - typeParameters = appendIfUnique(typeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration))); - } - return typeParameters; + function getTypeOfFuncClassEnumModuleWorker(symbol: Symbol): Type { + const declaration = symbol.valueDeclaration; + if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) { + return anyType; } - - // Return the outer type parameters of a node or undefined if the node has no outer type parameters. - function getOuterTypeParameters(node: Node, includeThisTypes?: boolean): TypeParameter[] | undefined { - while (true) { - node = node.parent; // TODO: GH#18217 Use SourceFile kind check instead - if (node && isBinaryExpression(node)) { - // prototype assignments get the outer type parameters of their constructor function - const assignmentKind = getAssignmentDeclarationKind(node); - if (assignmentKind === AssignmentDeclarationKind.Prototype || assignmentKind === AssignmentDeclarationKind.PrototypeProperty) { - const symbol = getSymbolOfNode(node.left); - if (symbol && symbol.parent && !findAncestor(symbol.parent.valueDeclaration, d => node === d)) { - node = symbol.parent.valueDeclaration!; - } - } - } - if (!node) { - return undefined; + else if (declaration && (declaration.kind === SyntaxKind.BinaryExpression || + isAccessExpression(declaration) && + declaration.parent.kind === SyntaxKind.BinaryExpression)) { + return getWidenedTypeForAssignmentDeclaration(symbol); + } + else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) { + const resolvedModule = resolveExternalModuleSymbol(symbol); + if (resolvedModule !== symbol) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return errorType; } - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.JSDocTemplateTag: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocEnumTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.MappedType: - case SyntaxKind.ConditionalType: { - const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); - if (node.kind === SyntaxKind.MappedType) { - return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((node as MappedTypeNode).typeParameter))); - } - else if (node.kind === SyntaxKind.ConditionalType) { - return concatenate(outerTypeParameters, getInferTypeParameters(node as ConditionalTypeNode)); - } - const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(node as DeclarationWithTypeParameters)); - const thisType = includeThisTypes && - (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) && - getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node as ClassLikeDeclaration | InterfaceDeclaration)).thisType; - return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters; - } - case SyntaxKind.JSDocParameterTag: - const paramSymbol = getParameterSymbolFromJSDoc(node as JSDocParameterTag); - if (paramSymbol) { - node = paramSymbol.valueDeclaration!; - } - break; - case SyntaxKind.JSDocComment: { - const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); - return (node as JSDoc).tags - ? appendTypeParameters(outerTypeParameters, flatMap((node as JSDoc).tags, t => isJSDocTemplateTag(t) ? t.typeParameters : undefined)) - : outerTypeParameters; - } + const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!); + const type = getWidenedTypeForAssignmentDeclaration(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule); + if (!popTypeResolution()) { + return reportCircularityError(symbol); } + return type; } } + const type = createObjectType(ObjectFlags.Anonymous, symbol); + if (symbol.flags & SymbolFlags.Class) { + const baseTypeVariable = getBaseTypeVariableOfClass(symbol); + return baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type; + } + else { + return strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type) : type; + } + } + + function getTypeOfEnumMember(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + return links.type || (links.type = getDeclaredTypeOfEnumMember(symbol)); + } - // The outer type parameters are those defined by enclosing generic classes, methods, or functions. - function getOuterTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { - const declaration = symbol.flags & SymbolFlags.Class ? symbol.valueDeclaration : getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration)!; - Debug.assert(!!declaration, "Class was missing valueDeclaration -OR- non-class had no interface declarations"); - return getOuterTypeParameters(declaration); + function getTypeOfAlias(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + const targetSymbol = resolveAlias(symbol); + const exportSymbol = symbol.declarations && getTargetOfAliasDeclaration(getDeclarationOfAliasSymbol(symbol)!, /*dontResolveAlias*/ true); + const declaredType = firstDefined(exportSymbol?.declarations, d => isExportAssignment(d) ? tryGetTypeFromEffectiveTypeNode(d) : undefined); + // It only makes sense to get the type of a value symbol. If the result of resolving + // the alias is not a value, then it has no type. To get the type associated with a + // type symbol, call getDeclaredTypeOfSymbol. + // This check is important because without it, a call to getTypeOfSymbol could end + // up recursively calling getTypeOfAlias, causing a stack overflow. + links.type = exportSymbol?.declarations && isDuplicatedCommonJSExport(exportSymbol.declarations) && symbol.declarations!.length ? getFlowTypeFromCommonJSExport(exportSymbol) + : isDuplicatedCommonJSExport(symbol.declarations) ? autoType + : declaredType ? declaredType + : targetSymbol.flags & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol) + : errorType; } + return links.type; + } - // The local type parameters are the combined set of type parameters from all declarations of the class, - // interface, or type alias. - function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] | undefined { - if (!symbol.declarations) { - return; + function getTypeOfInstantiatedSymbol(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return links.type = errorType; } - let result: TypeParameter[] | undefined; - for (const node of symbol.declarations) { - if (node.kind === SyntaxKind.InterfaceDeclaration || - node.kind === SyntaxKind.ClassDeclaration || - node.kind === SyntaxKind.ClassExpression || - isJSConstructor(node) || - isTypeAlias(node)) { - const declaration = node as InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag | JSDocCallbackTag; - result = appendTypeParameters(result, getEffectiveTypeParameterDeclarations(declaration)); - } + let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper); + if (!popTypeResolution()) { + type = reportCircularityError(symbol); } - return result; + links.type = type; } + return links.type; + } - // The full set of type parameters for a generic class or interface type consists of its outer type parameters plus - // its locally declared type parameters. - function getTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { - return concatenate(getOuterTypeParametersOfClassOrInterface(symbol), getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol)); + function reportCircularityError(symbol: Symbol) { + const declaration = symbol.valueDeclaration as VariableLikeDeclaration; + // Check if variable has type annotation that circularly references the variable itself + if (getEffectiveTypeAnnotationNode(declaration)) { + error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, + symbolToString(symbol)); + return errorType; + } + // Check if variable has initializer that circularly references the variable itself + if (noImplicitAny && (declaration.kind !== SyntaxKind.Parameter || (declaration as HasInitializer).initializer)) { + error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer, + symbolToString(symbol)); } + // Circularities could also result from parameters in function expressions that end up + // having themselves as contextual types following type argument inference. In those cases + // we have already reported an implicit any error so we don't report anything here. + return anyType; + } - // A type is a mixin constructor if it has a single construct signature taking no type parameters and a single - // rest parameter of type any[]. - function isMixinConstructorType(type: Type) { - const signatures = getSignaturesOfType(type, SignatureKind.Construct); - if (signatures.length === 1) { - const s = signatures[0]; - if (!s.typeParameters && s.parameters.length === 1 && signatureHasRestParameter(s)) { - const paramType = getTypeOfParameter(s.parameters[0]); - return isTypeAny(paramType) || getElementTypeOfArrayType(paramType) === anyType; - } - } - return false; + function getTypeOfSymbolWithDeferredType(symbol: Symbol) { + const links = getSymbolLinks(symbol); + if (!links.type) { + Debug.assertIsDefined(links.deferralParent); + Debug.assertIsDefined(links.deferralConstituents); + links.type = links.deferralParent.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents) : getIntersectionType(links.deferralConstituents); } + return links.type; + } - function isConstructorType(type: Type): boolean { - if (getSignaturesOfType(type, SignatureKind.Construct).length > 0) { - return true; - } - if (type.flags & TypeFlags.TypeVariable) { - const constraint = getBaseConstraintOfType(type); - return !!constraint && isMixinConstructorType(constraint); + function getSetAccessorTypeOfSymbol(symbol: Symbol): Type { + if (symbol.flags & SymbolFlags.Accessor) { + const type = getTypeOfSetAccessor(symbol); + if (type) { + return type; } - return false; } + return getTypeOfSymbol(symbol); + } - function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined { - return getEffectiveBaseTypeNode(type.symbol.valueDeclaration as ClassLikeDeclaration); + function getTypeOfSymbol(symbol: Symbol): Type { + const checkFlags = getCheckFlags(symbol); + if (checkFlags & CheckFlags.DeferredType) { + return getTypeOfSymbolWithDeferredType(symbol); + } + if (checkFlags & CheckFlags.Instantiated) { + return getTypeOfInstantiatedSymbol(symbol); + } + if (checkFlags & CheckFlags.Mapped) { + return getTypeOfMappedSymbol(symbol as MappedSymbol); + } + if (checkFlags & CheckFlags.ReverseMapped) { + return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol); + } + if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { + return getTypeOfVariableOrParameterOrProperty(symbol); } + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { + return getTypeOfFuncClassEnumModule(symbol); + } + if (symbol.flags & SymbolFlags.EnumMember) { + return getTypeOfEnumMember(symbol); + } + if (symbol.flags & SymbolFlags.Accessor) { + return getTypeOfAccessors(symbol); + } + if (symbol.flags & SymbolFlags.Alias) { + return getTypeOfAlias(symbol); + } + return errorType; + } + + function getNonMissingTypeOfSymbol(symbol: Symbol) { + return removeMissingType(getTypeOfSymbol(symbol), !!(symbol.flags & SymbolFlags.Optional)); + } + + function isReferenceToType(type: Type, target: Type) { + return type !== undefined + && target !== undefined + && (getObjectFlags(type) & ObjectFlags.Reference) !== 0 + && (type as TypeReference).target === target; + } + + function getTargetType(type: Type): Type { + return getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target : type; + } - function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { - const typeArgCount = length(typeArgumentNodes); - const isJavascript = isInJSFile(location); - return filter(getSignaturesOfType(type, SignatureKind.Construct), - sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters)); + // TODO: GH#18217 If `checkBase` is undefined, we should not call this because this will always return false. + function hasBaseType(type: Type, checkBase: Type | undefined) { + return check(type); + function check(type: Type): boolean { + if (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) { + const target = getTargetType(type) as InterfaceType; + return target === checkBase || some(getBaseTypes(target), check); + } + else if (type.flags & TypeFlags.Intersection) { + return some((type as IntersectionType).types, check); + } + return false; } + } - function getInstantiatedConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { - const signatures = getConstructorsForTypeArguments(type, typeArgumentNodes, location); - const typeArguments = map(typeArgumentNodes, getTypeFromTypeNode); - return sameMap(signatures, sig => some(sig.typeParameters) ? getSignatureInstantiation(sig, typeArguments, isInJSFile(location)) : sig); + // Appends the type parameters given by a list of declarations to a set of type parameters and returns the resulting set. + // The function allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set + // in-place and returns the same array. + function appendTypeParameters(typeParameters: TypeParameter[] | undefined, declarations: readonly TypeParameterDeclaration[]): TypeParameter[] | undefined { + for (const declaration of declarations) { + typeParameters = appendIfUnique(typeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration))); } + return typeParameters; + } - /** - * The base constructor of a class can resolve to - * * undefinedType if the class has no extends clause, - * * unknownType if an error occurred during resolution of the extends expression, - * * nullType if the extends expression is the null value, - * * anyType if the extends expression has type any, or - * * an object type with at least one construct signature. - */ - function getBaseConstructorTypeOfClass(type: InterfaceType): Type { - if (!type.resolvedBaseConstructorType) { - const decl = type.symbol.valueDeclaration as ClassLikeDeclaration; - const extended = getEffectiveBaseTypeNode(decl); - const baseTypeNode = getBaseTypeNodeOfClass(type); - if (!baseTypeNode) { - return type.resolvedBaseConstructorType = undefinedType; - } - if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseConstructorType)) { - return errorType; - } - const baseConstructorType = checkExpression(baseTypeNode.expression); - if (extended && baseTypeNode !== extended) { - Debug.assert(!extended.typeArguments); // Because this is in a JS file, and baseTypeNode is in an @extends tag - checkExpression(extended.expression); - } - if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection)) { - // Resolving the members of a class requires us to resolve the base class of that class. - // We force resolution here such that we catch circularities now. - resolveStructuredTypeMembers(baseConstructorType as ObjectType); - } - if (!popTypeResolution()) { - error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol)); - return type.resolvedBaseConstructorType = errorType; - } - if (!(baseConstructorType.flags & TypeFlags.Any) && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) { - const err = error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType)); - if (baseConstructorType.flags & TypeFlags.TypeParameter) { - const constraint = getConstraintFromTypeParameter(baseConstructorType); - let ctorReturn: Type = unknownType; - if (constraint) { - const ctorSig = getSignaturesOfType(constraint, SignatureKind.Construct); - if (ctorSig[0]) { - ctorReturn = getReturnTypeOfSignature(ctorSig[0]); - } - } - if (baseConstructorType.symbol.declarations) { - addRelatedInfo(err, createDiagnosticForNode(baseConstructorType.symbol.declarations[0], Diagnostics.Did_you_mean_for_0_to_be_constrained_to_type_new_args_Colon_any_1, symbolToString(baseConstructorType.symbol), typeToString(ctorReturn))); - } + // Return the outer type parameters of a node or undefined if the node has no outer type parameters. + function getOuterTypeParameters(node: Node, includeThisTypes?: boolean): TypeParameter[] | undefined { + while (true) { + node = node.parent; // TODO: GH#18217 Use SourceFile kind check instead + if (node && isBinaryExpression(node)) { + // prototype assignments get the outer type parameters of their constructor function + const assignmentKind = getAssignmentDeclarationKind(node); + if (assignmentKind === AssignmentDeclarationKind.Prototype || assignmentKind === AssignmentDeclarationKind.PrototypeProperty) { + const symbol = getSymbolOfNode(node.left); + if (symbol && symbol.parent && !findAncestor(symbol.parent.valueDeclaration, d => node === d)) { + node = symbol.parent.valueDeclaration!; } - return type.resolvedBaseConstructorType = errorType; } - type.resolvedBaseConstructorType = baseConstructorType; } - return type.resolvedBaseConstructorType; - } - - function getImplementsTypes(type: InterfaceType): BaseType[] { - let resolvedImplementsTypes: BaseType[] = emptyArray; - if (type.symbol.declarations) { - for (const declaration of type.symbol.declarations) { - const implementsTypeNodes = getEffectiveImplementsTypeNodes(declaration as ClassLikeDeclaration); - if (!implementsTypeNodes) continue; - for (const node of implementsTypeNodes) { - const implementsType = getTypeFromTypeNode(node); - if (!isErrorType(implementsType)) { - if (resolvedImplementsTypes === emptyArray) { - resolvedImplementsTypes = [implementsType as ObjectType]; - } - else { - resolvedImplementsTypes.push(implementsType); - } - } + if (!node) { + return undefined; + } + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocTemplateTag: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocEnumTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.MappedType: + case SyntaxKind.ConditionalType: { + const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); + if (node.kind === SyntaxKind.MappedType) { + return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((node as MappedTypeNode).typeParameter))); } + else if (node.kind === SyntaxKind.ConditionalType) { + return concatenate(outerTypeParameters, getInferTypeParameters(node as ConditionalTypeNode)); + } + const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(node as DeclarationWithTypeParameters)); + const thisType = includeThisTypes && + (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) && + getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node as ClassLikeDeclaration | InterfaceDeclaration)).thisType; + return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters; + } + case SyntaxKind.JSDocParameterTag: + const paramSymbol = getParameterSymbolFromJSDoc(node as JSDocParameterTag); + if (paramSymbol) { + node = paramSymbol.valueDeclaration!; + } + break; + case SyntaxKind.JSDocComment: { + const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); + return (node as JSDoc).tags + ? appendTypeParameters(outerTypeParameters, flatMap((node as JSDoc).tags, t => isJSDocTemplateTag(t) ? t.typeParameters : undefined)) + : outerTypeParameters; } } - return resolvedImplementsTypes; } + } + + // The outer type parameters are those defined by enclosing generic classes, methods, or functions. + function getOuterTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { + const declaration = symbol.flags & SymbolFlags.Class ? symbol.valueDeclaration : getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration)!; + Debug.assert(!!declaration, "Class was missing valueDeclaration -OR- non-class had no interface declarations"); + return getOuterTypeParameters(declaration); + } - function reportCircularBaseType(node: Node, type: Type) { - error(node, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); + // The local type parameters are the combined set of type parameters from all declarations of the class, + // interface, or type alias. + function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] | undefined { + if (!symbol.declarations) { + return; } + let result: TypeParameter[] | undefined; + for (const node of symbol.declarations) { + if (node.kind === SyntaxKind.InterfaceDeclaration || + node.kind === SyntaxKind.ClassDeclaration || + node.kind === SyntaxKind.ClassExpression || + isJSConstructor(node) || + isTypeAlias(node)) { + const declaration = node as InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag | JSDocCallbackTag; + result = appendTypeParameters(result, getEffectiveTypeParameterDeclarations(declaration)); + } + } + return result; + } - function getBaseTypes(type: InterfaceType): BaseType[] { - if (!type.baseTypesResolved) { - if (pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseTypes)) { - if (type.objectFlags & ObjectFlags.Tuple) { - type.resolvedBaseTypes = [getTupleBaseType(type as TupleType)]; - } - else if (type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - if (type.symbol.flags & SymbolFlags.Class) { - resolveBaseTypesOfClass(type); - } - if (type.symbol.flags & SymbolFlags.Interface) { - resolveBaseTypesOfInterface(type); - } - } - else { - Debug.fail("type must be class or interface"); - } - if (!popTypeResolution() && type.symbol.declarations) { - for (const declaration of type.symbol.declarations) { - if (declaration.kind === SyntaxKind.ClassDeclaration || declaration.kind === SyntaxKind.InterfaceDeclaration) { - reportCircularBaseType(declaration, type); - } - } - } - } - type.baseTypesResolved = true; + // The full set of type parameters for a generic class or interface type consists of its outer type parameters plus + // its locally declared type parameters. + function getTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { + return concatenate(getOuterTypeParametersOfClassOrInterface(symbol), getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol)); + } + + // A type is a mixin constructor if it has a single construct signature taking no type parameters and a single + // rest parameter of type any[]. + function isMixinConstructorType(type: Type) { + const signatures = getSignaturesOfType(type, SignatureKind.Construct); + if (signatures.length === 1) { + const s = signatures[0]; + if (!s.typeParameters && s.parameters.length === 1 && signatureHasRestParameter(s)) { + const paramType = getTypeOfParameter(s.parameters[0]); + return isTypeAny(paramType) || getElementTypeOfArrayType(paramType) === anyType; } - return type.resolvedBaseTypes; } + return false; + } - function getTupleBaseType(type: TupleType) { - const elementTypes = sameMap(type.typeParameters, (t, i) => type.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t); - return createArrayType(getUnionType(elementTypes || emptyArray), type.readonly); + function isConstructorType(type: Type): boolean { + if (getSignaturesOfType(type, SignatureKind.Construct).length > 0) { + return true; } + if (type.flags & TypeFlags.TypeVariable) { + const constraint = getBaseConstraintOfType(type); + return !!constraint && isMixinConstructorType(constraint); + } + return false; + } - function resolveBaseTypesOfClass(type: InterfaceType) { - type.resolvedBaseTypes = resolvingEmptyArray; - const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type)); - if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) { - return type.resolvedBaseTypes = emptyArray; + function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined { + return getEffectiveBaseTypeNode(type.symbol.valueDeclaration as ClassLikeDeclaration); + } + + function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { + const typeArgCount = length(typeArgumentNodes); + const isJavascript = isInJSFile(location); + return filter(getSignaturesOfType(type, SignatureKind.Construct), + sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters)); + } + + function getInstantiatedConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { + const signatures = getConstructorsForTypeArguments(type, typeArgumentNodes, location); + const typeArguments = map(typeArgumentNodes, getTypeFromTypeNode); + return sameMap(signatures, sig => some(sig.typeParameters) ? getSignatureInstantiation(sig, typeArguments, isInJSFile(location)) : sig); + } + + /** + * The base constructor of a class can resolve to + * * undefinedType if the class has no extends clause, + * * unknownType if an error occurred during resolution of the extends expression, + * * nullType if the extends expression is the null value, + * * anyType if the extends expression has type any, or + * * an object type with at least one construct signature. + */ + function getBaseConstructorTypeOfClass(type: InterfaceType): Type { + if (!type.resolvedBaseConstructorType) { + const decl = type.symbol.valueDeclaration as ClassLikeDeclaration; + const extended = getEffectiveBaseTypeNode(decl); + const baseTypeNode = getBaseTypeNodeOfClass(type); + if (!baseTypeNode) { + return type.resolvedBaseConstructorType = undefinedType; + } + if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseConstructorType)) { + return errorType; } - const baseTypeNode = getBaseTypeNodeOfClass(type)!; - let baseType: Type; - const originalBaseType = baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : undefined; - if (baseConstructorType.symbol && baseConstructorType.symbol.flags & SymbolFlags.Class && - areAllOuterTypeParametersApplied(originalBaseType!)) { - // When base constructor type is a class with no captured type arguments we know that the constructors all have the same type parameters as the - // class and all return the instance type of the class. There is no need for further checks and we can apply the - // type arguments in the same manner as a type reference to get the same error reporting experience. - baseType = getTypeFromClassOrInterfaceReference(baseTypeNode, baseConstructorType.symbol); + const baseConstructorType = checkExpression(baseTypeNode.expression); + if (extended && baseTypeNode !== extended) { + Debug.assert(!extended.typeArguments); // Because this is in a JS file, and baseTypeNode is in an @extends tag + checkExpression(extended.expression); } - else if (baseConstructorType.flags & TypeFlags.Any) { - baseType = baseConstructorType; + if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection)) { + // Resolving the members of a class requires us to resolve the base class of that class. + // We force resolution here such that we catch circularities now. + resolveStructuredTypeMembers(baseConstructorType as ObjectType); } - else { - // The class derives from a "class-like" constructor function, check that we have at least one construct signature - // with a matching number of type parameters and use the return type of the first instantiated signature. Elsewhere - // we check that all instantiated signatures return the same type. - const constructors = getInstantiatedConstructorsForTypeArguments(baseConstructorType, baseTypeNode.typeArguments, baseTypeNode); - if (!constructors.length) { - error(baseTypeNode.expression, Diagnostics.No_base_constructor_has_the_specified_number_of_type_arguments); - return type.resolvedBaseTypes = emptyArray; + if (!popTypeResolution()) { + error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol)); + return type.resolvedBaseConstructorType = errorType; + } + if (!(baseConstructorType.flags & TypeFlags.Any) && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) { + const err = error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType)); + if (baseConstructorType.flags & TypeFlags.TypeParameter) { + const constraint = getConstraintFromTypeParameter(baseConstructorType); + let ctorReturn: Type = unknownType; + if (constraint) { + const ctorSig = getSignaturesOfType(constraint, SignatureKind.Construct); + if (ctorSig[0]) { + ctorReturn = getReturnTypeOfSignature(ctorSig[0]); + } + } + if (baseConstructorType.symbol.declarations) { + addRelatedInfo(err, createDiagnosticForNode(baseConstructorType.symbol.declarations[0], Diagnostics.Did_you_mean_for_0_to_be_constrained_to_type_new_args_Colon_any_1, symbolToString(baseConstructorType.symbol), typeToString(ctorReturn))); + } } - baseType = getReturnTypeOfSignature(constructors[0]); + return type.resolvedBaseConstructorType = errorType; } + type.resolvedBaseConstructorType = baseConstructorType; + } + return type.resolvedBaseConstructorType; + } - if (isErrorType(baseType)) { - return type.resolvedBaseTypes = emptyArray; + function getImplementsTypes(type: InterfaceType): BaseType[] { + let resolvedImplementsTypes: BaseType[] = emptyArray; + if (type.symbol.declarations) { + for (const declaration of type.symbol.declarations) { + const implementsTypeNodes = getEffectiveImplementsTypeNodes(declaration as ClassLikeDeclaration); + if (!implementsTypeNodes) continue; + for (const node of implementsTypeNodes) { + const implementsType = getTypeFromTypeNode(node); + if (!isErrorType(implementsType)) { + if (resolvedImplementsTypes === emptyArray) { + resolvedImplementsTypes = [implementsType as ObjectType]; + } + else { + resolvedImplementsTypes.push(implementsType); + } + } + } } - const reducedBaseType = getReducedType(baseType); - if (!isValidBaseType(reducedBaseType)) { - const elaboration = elaborateNeverIntersection(/*errorInfo*/ undefined, baseType); - const diagnostic = chainDiagnosticMessages(elaboration, Diagnostics.Base_constructor_return_type_0_is_not_an_object_type_or_intersection_of_object_types_with_statically_known_members, typeToString(reducedBaseType)); - diagnostics.add(createDiagnosticForNodeFromMessageChain(baseTypeNode.expression, diagnostic)); - return type.resolvedBaseTypes = emptyArray; + } + return resolvedImplementsTypes; + } + + function reportCircularBaseType(node: Node, type: Type) { + error(node, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); + } + + function getBaseTypes(type: InterfaceType): BaseType[] { + if (!type.baseTypesResolved) { + if (pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseTypes)) { + if (type.objectFlags & ObjectFlags.Tuple) { + type.resolvedBaseTypes = [getTupleBaseType(type as TupleType)]; + } + else if (type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + if (type.symbol.flags & SymbolFlags.Class) { + resolveBaseTypesOfClass(type); + } + if (type.symbol.flags & SymbolFlags.Interface) { + resolveBaseTypesOfInterface(type); + } + } + else { + Debug.fail("type must be class or interface"); + } + if (!popTypeResolution() && type.symbol.declarations) { + for (const declaration of type.symbol.declarations) { + if (declaration.kind === SyntaxKind.ClassDeclaration || declaration.kind === SyntaxKind.InterfaceDeclaration) { + reportCircularBaseType(declaration, type); + } + } + } } - if (type === reducedBaseType || hasBaseType(reducedBaseType, type)) { - error(type.symbol.valueDeclaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, - typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); + type.baseTypesResolved = true; + } + return type.resolvedBaseTypes; + } + + function getTupleBaseType(type: TupleType) { + const elementTypes = sameMap(type.typeParameters, (t, i) => type.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t); + return createArrayType(getUnionType(elementTypes || emptyArray), type.readonly); + } + + function resolveBaseTypesOfClass(type: InterfaceType) { + type.resolvedBaseTypes = resolvingEmptyArray; + const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type)); + if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) { + return type.resolvedBaseTypes = emptyArray; + } + const baseTypeNode = getBaseTypeNodeOfClass(type)!; + let baseType: Type; + const originalBaseType = baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : undefined; + if (baseConstructorType.symbol && baseConstructorType.symbol.flags & SymbolFlags.Class && + areAllOuterTypeParametersApplied(originalBaseType!)) { + // When base constructor type is a class with no captured type arguments we know that the constructors all have the same type parameters as the + // class and all return the instance type of the class. There is no need for further checks and we can apply the + // type arguments in the same manner as a type reference to get the same error reporting experience. + baseType = getTypeFromClassOrInterfaceReference(baseTypeNode, baseConstructorType.symbol); + } + else if (baseConstructorType.flags & TypeFlags.Any) { + baseType = baseConstructorType; + } + else { + // The class derives from a "class-like" constructor function, check that we have at least one construct signature + // with a matching number of type parameters and use the return type of the first instantiated signature. Elsewhere + // we check that all instantiated signatures return the same type. + const constructors = getInstantiatedConstructorsForTypeArguments(baseConstructorType, baseTypeNode.typeArguments, baseTypeNode); + if (!constructors.length) { + error(baseTypeNode.expression, Diagnostics.No_base_constructor_has_the_specified_number_of_type_arguments); return type.resolvedBaseTypes = emptyArray; } - if (type.resolvedBaseTypes === resolvingEmptyArray) { - // Circular reference, likely through instantiation of default parameters - // (otherwise there'd be an error from hasBaseType) - this is fine, but `.members` should be reset - // as `getIndexedAccessType` via `instantiateType` via `getTypeFromClassOrInterfaceReference` forces a - // partial instantiation of the members without the base types fully resolved - type.members = undefined; - } - return type.resolvedBaseTypes = [reducedBaseType]; + baseType = getReturnTypeOfSignature(constructors[0]); } - function areAllOuterTypeParametersApplied(type: Type): boolean { // TODO: GH#18217 Shouldn't this take an InterfaceType? - // An unapplied type parameter has its symbol still the same as the matching argument symbol. - // Since parameters are applied outer-to-inner, only the last outer parameter needs to be checked. - const outerTypeParameters = (type as InterfaceType).outerTypeParameters; - if (outerTypeParameters) { - const last = outerTypeParameters.length - 1; - const typeArguments = getTypeArguments(type as TypeReference); - return outerTypeParameters[last].symbol !== typeArguments[last].symbol; + if (isErrorType(baseType)) { + return type.resolvedBaseTypes = emptyArray; + } + const reducedBaseType = getReducedType(baseType); + if (!isValidBaseType(reducedBaseType)) { + const elaboration = elaborateNeverIntersection(/*errorInfo*/ undefined, baseType); + const diagnostic = chainDiagnosticMessages(elaboration, Diagnostics.Base_constructor_return_type_0_is_not_an_object_type_or_intersection_of_object_types_with_statically_known_members, typeToString(reducedBaseType)); + diagnostics.add(createDiagnosticForNodeFromMessageChain(baseTypeNode.expression, diagnostic)); + return type.resolvedBaseTypes = emptyArray; + } + if (type === reducedBaseType || hasBaseType(reducedBaseType, type)) { + error(type.symbol.valueDeclaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, + typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); + return type.resolvedBaseTypes = emptyArray; + } + if (type.resolvedBaseTypes === resolvingEmptyArray) { + // Circular reference, likely through instantiation of default parameters + // (otherwise there'd be an error from hasBaseType) - this is fine, but `.members` should be reset + // as `getIndexedAccessType` via `instantiateType` via `getTypeFromClassOrInterfaceReference` forces a + // partial instantiation of the members without the base types fully resolved + type.members = undefined; + } + return type.resolvedBaseTypes = [reducedBaseType]; + } + + function areAllOuterTypeParametersApplied(type: Type): boolean { // TODO: GH#18217 Shouldn't this take an InterfaceType? + // An unapplied type parameter has its symbol still the same as the matching argument symbol. + // Since parameters are applied outer-to-inner, only the last outer parameter needs to be checked. + const outerTypeParameters = (type as InterfaceType).outerTypeParameters; + if (outerTypeParameters) { + const last = outerTypeParameters.length - 1; + const typeArguments = getTypeArguments(type as TypeReference); + return outerTypeParameters[last].symbol !== typeArguments[last].symbol; + } + return true; + } + + // A valid base type is `any`, an object type or intersection of object types. + function isValidBaseType(type: Type): type is BaseType { + if (type.flags & TypeFlags.TypeParameter) { + const constraint = getBaseConstraintOfType(type); + if (constraint) { + return isValidBaseType(constraint); } - return true; } + // TODO: Given that we allow type parmeters here now, is this `!isGenericMappedType(type)` check really needed? + // There's no reason a `T` should be allowed while a `Readonly` should not. + return !!(type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any) && !isGenericMappedType(type) || + type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isValidBaseType)); + } - // A valid base type is `any`, an object type or intersection of object types. - function isValidBaseType(type: Type): type is BaseType { - if (type.flags & TypeFlags.TypeParameter) { - const constraint = getBaseConstraintOfType(type); - if (constraint) { - return isValidBaseType(constraint); - } - } - // TODO: Given that we allow type parmeters here now, is this `!isGenericMappedType(type)` check really needed? - // There's no reason a `T` should be allowed while a `Readonly` should not. - return !!(type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any) && !isGenericMappedType(type) || - type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isValidBaseType)); - } - - function resolveBaseTypesOfInterface(type: InterfaceType): void { - type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray; - if (type.symbol.declarations) { - for (const declaration of type.symbol.declarations) { - if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration)) { - for (const node of getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration)!) { - const baseType = getReducedType(getTypeFromTypeNode(node)); - if (!isErrorType(baseType)) { - if (isValidBaseType(baseType)) { - if (type !== baseType && !hasBaseType(baseType, type)) { - if (type.resolvedBaseTypes === emptyArray) { - type.resolvedBaseTypes = [baseType as ObjectType]; - } - else { - type.resolvedBaseTypes.push(baseType); - } + function resolveBaseTypesOfInterface(type: InterfaceType): void { + type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray; + if (type.symbol.declarations) { + for (const declaration of type.symbol.declarations) { + if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration)) { + for (const node of getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration)!) { + const baseType = getReducedType(getTypeFromTypeNode(node)); + if (!isErrorType(baseType)) { + if (isValidBaseType(baseType)) { + if (type !== baseType && !hasBaseType(baseType, type)) { + if (type.resolvedBaseTypes === emptyArray) { + type.resolvedBaseTypes = [baseType as ObjectType]; } else { - reportCircularBaseType(declaration, type); + type.resolvedBaseTypes.push(baseType); } } else { - error(node, Diagnostics.An_interface_can_only_extend_an_object_type_or_intersection_of_object_types_with_statically_known_members); + reportCircularBaseType(declaration, type); } } + else { + error(node, Diagnostics.An_interface_can_only_extend_an_object_type_or_intersection_of_object_types_with_statically_known_members); + } } } } } } + } - /** - * Returns true if the interface given by the symbol is free of "this" references. - * - * Specifically, the result is true if the interface itself contains no references - * to "this" in its body, if all base types are interfaces, - * and if none of the base interfaces have a "this" type. - */ - function isThislessInterface(symbol: Symbol): boolean { - if (!symbol.declarations) { - return true; - } - for (const declaration of symbol.declarations) { - if (declaration.kind === SyntaxKind.InterfaceDeclaration) { - if (declaration.flags & NodeFlags.ContainsThis) { - return false; - } - const baseTypeNodes = getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration); - if (baseTypeNodes) { - for (const node of baseTypeNodes) { - if (isEntityNameExpression(node.expression)) { - const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true); - if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) { - return false; - } + /** + * Returns true if the interface given by the symbol is free of "this" references. + * + * Specifically, the result is true if the interface itself contains no references + * to "this" in its body, if all base types are interfaces, + * and if none of the base interfaces have a "this" type. + */ + function isThislessInterface(symbol: Symbol): boolean { + if (!symbol.declarations) { + return true; + } + for (const declaration of symbol.declarations) { + if (declaration.kind === SyntaxKind.InterfaceDeclaration) { + if (declaration.flags & NodeFlags.ContainsThis) { + return false; + } + const baseTypeNodes = getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration); + if (baseTypeNodes) { + for (const node of baseTypeNodes) { + if (isEntityNameExpression(node.expression)) { + const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true); + if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) { + return false; } } } } } - return true; } + return true; + } - function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType { - let links = getSymbolLinks(symbol); - const originalLinks = links; - if (!links.declaredType) { - const kind = symbol.flags & SymbolFlags.Class ? ObjectFlags.Class : ObjectFlags.Interface; - const merged = mergeJSSymbols(symbol, symbol.valueDeclaration && getAssignedClassSymbol(symbol.valueDeclaration)); - if (merged) { - // note:we overwrite links because we just cloned the symbol - symbol = links = merged; - } + function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType { + let links = getSymbolLinks(symbol); + const originalLinks = links; + if (!links.declaredType) { + const kind = symbol.flags & SymbolFlags.Class ? ObjectFlags.Class : ObjectFlags.Interface; + const merged = mergeJSSymbols(symbol, symbol.valueDeclaration && getAssignedClassSymbol(symbol.valueDeclaration)); + if (merged) { + // note:we overwrite links because we just cloned the symbol + symbol = links = merged; + } + + const type = originalLinks.declaredType = links.declaredType = createObjectType(kind, symbol) as InterfaceType; + const outerTypeParameters = getOuterTypeParametersOfClassOrInterface(symbol); + const localTypeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + // A class or interface is generic if it has type parameters or a "this" type. We always give classes a "this" type + // because it is not feasible to analyze all members to determine if the "this" type escapes the class (in particular, + // property types inferred from initializers and method return types inferred from return statements are very hard + // to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of + // "this" references. + if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface(symbol)) { + type.objectFlags |= ObjectFlags.Reference; + type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); + type.outerTypeParameters = outerTypeParameters; + type.localTypeParameters = localTypeParameters; + (type as GenericType).instantiations = new Map(); + (type as GenericType).instantiations.set(getTypeListId(type.typeParameters), type as GenericType); + (type as GenericType).target = type as GenericType; + (type as GenericType).resolvedTypeArguments = type.typeParameters; + type.thisType = createTypeParameter(symbol); + type.thisType.isThisType = true; + type.thisType.constraint = type; + } + } + return links.declaredType as InterfaceType; + } - const type = originalLinks.declaredType = links.declaredType = createObjectType(kind, symbol) as InterfaceType; - const outerTypeParameters = getOuterTypeParametersOfClassOrInterface(symbol); - const localTypeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - // A class or interface is generic if it has type parameters or a "this" type. We always give classes a "this" type - // because it is not feasible to analyze all members to determine if the "this" type escapes the class (in particular, - // property types inferred from initializers and method return types inferred from return statements are very hard - // to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of - // "this" references. - if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface(symbol)) { - type.objectFlags |= ObjectFlags.Reference; - type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); - type.outerTypeParameters = outerTypeParameters; - type.localTypeParameters = localTypeParameters; - (type as GenericType).instantiations = new Map(); - (type as GenericType).instantiations.set(getTypeListId(type.typeParameters), type as GenericType); - (type as GenericType).target = type as GenericType; - (type as GenericType).resolvedTypeArguments = type.typeParameters; - type.thisType = createTypeParameter(symbol); - type.thisType.isThisType = true; - type.thisType.constraint = type; - } - } - return links.declaredType as InterfaceType; - } - - function getDeclaredTypeOfTypeAlias(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.declaredType) { - // Note that we use the links object as the target here because the symbol object is used as the unique - // identity for resolution of the 'type' property in SymbolLinks. - if (!pushTypeResolution(symbol, TypeSystemPropertyName.DeclaredType)) { - return errorType; - } + function getDeclaredTypeOfTypeAlias(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.declaredType) { + // Note that we use the links object as the target here because the symbol object is used as the unique + // identity for resolution of the 'type' property in SymbolLinks. + if (!pushTypeResolution(symbol, TypeSystemPropertyName.DeclaredType)) { + return errorType; + } - const declaration = Debug.checkDefined(symbol.declarations?.find(isTypeAlias), "Type alias symbol with no valid declaration found"); - const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type; - // If typeNode is missing, we will error in checkJSDocTypedefTag. - let type = typeNode ? getTypeFromTypeNode(typeNode) : errorType; + const declaration = Debug.checkDefined(symbol.declarations?.find(isTypeAlias), "Type alias symbol with no valid declaration found"); + const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type; + // If typeNode is missing, we will error in checkJSDocTypedefTag. + let type = typeNode ? getTypeFromTypeNode(typeNode) : errorType; - if (popTypeResolution()) { - const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - if (typeParameters) { - // Initialize the instantiation cache for generic type aliases. The declared type corresponds to - // an instantiation of the type alias with the type parameters supplied as type arguments. - links.typeParameters = typeParameters; - links.instantiations = new Map(); - links.instantiations.set(getTypeListId(typeParameters), type); - } + if (popTypeResolution()) { + const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + if (typeParameters) { + // Initialize the instantiation cache for generic type aliases. The declared type corresponds to + // an instantiation of the type alias with the type parameters supplied as type arguments. + links.typeParameters = typeParameters; + links.instantiations = new Map(); + links.instantiations.set(getTypeListId(typeParameters), type); + } + } + else { + type = errorType; + if (declaration.kind === SyntaxKind.JSDocEnumTag) { + error(declaration.typeExpression.type, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); } else { - type = errorType; - if (declaration.kind === SyntaxKind.JSDocEnumTag) { - error(declaration.typeExpression.type, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); - } - else { - error(isNamedDeclaration(declaration) ? declaration.name : declaration || declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); - } + error(isNamedDeclaration(declaration) ? declaration.name : declaration || declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); } - links.declaredType = type; } - return links.declaredType; + links.declaredType = type; } + return links.declaredType; + } - function isStringConcatExpression(expr: Node): boolean { - if (isStringLiteralLike(expr)) { - return true; - } - else if (expr.kind === SyntaxKind.BinaryExpression) { - return isStringConcatExpression((expr as BinaryExpression).left) && isStringConcatExpression((expr as BinaryExpression).right); - } - return false; + function isStringConcatExpression(expr: Node): boolean { + if (isStringLiteralLike(expr)) { + return true; } + else if (expr.kind === SyntaxKind.BinaryExpression) { + return isStringConcatExpression((expr as BinaryExpression).left) && isStringConcatExpression((expr as BinaryExpression).right); + } + return false; + } - function isLiteralEnumMember(member: EnumMember) { - const expr = member.initializer; - if (!expr) { - return !(member.flags & NodeFlags.Ambient); - } - switch (expr.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return true; - case SyntaxKind.PrefixUnaryExpression: - return (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && - (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.NumericLiteral; - case SyntaxKind.Identifier: - return nodeIsMissing(expr) || !!getSymbolOfNode(member.parent).exports!.get((expr as Identifier).escapedText); - case SyntaxKind.BinaryExpression: - return isStringConcatExpression(expr); - default: - return false; - } + function isLiteralEnumMember(member: EnumMember) { + const expr = member.initializer; + if (!expr) { + return !(member.flags & NodeFlags.Ambient); } + switch (expr.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return true; + case SyntaxKind.PrefixUnaryExpression: + return (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && + (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.NumericLiteral; + case SyntaxKind.Identifier: + return nodeIsMissing(expr) || !!getSymbolOfNode(member.parent).exports!.get((expr as Identifier).escapedText); + case SyntaxKind.BinaryExpression: + return isStringConcatExpression(expr); + default: + return false; + } + } - function getEnumKind(symbol: Symbol): EnumKind { - const links = getSymbolLinks(symbol); - if (links.enumKind !== undefined) { - return links.enumKind; - } - let hasNonLiteralMember = false; - if (symbol.declarations) { - for (const declaration of symbol.declarations) { - if (declaration.kind === SyntaxKind.EnumDeclaration) { - for (const member of (declaration as EnumDeclaration).members) { - if (member.initializer && isStringLiteralLike(member.initializer)) { - return links.enumKind = EnumKind.Literal; - } - if (!isLiteralEnumMember(member)) { - hasNonLiteralMember = true; - } + function getEnumKind(symbol: Symbol): EnumKind { + const links = getSymbolLinks(symbol); + if (links.enumKind !== undefined) { + return links.enumKind; + } + let hasNonLiteralMember = false; + if (symbol.declarations) { + for (const declaration of symbol.declarations) { + if (declaration.kind === SyntaxKind.EnumDeclaration) { + for (const member of (declaration as EnumDeclaration).members) { + if (member.initializer && isStringLiteralLike(member.initializer)) { + return links.enumKind = EnumKind.Literal; + } + if (!isLiteralEnumMember(member)) { + hasNonLiteralMember = true; } } } } - return links.enumKind = hasNonLiteralMember ? EnumKind.Numeric : EnumKind.Literal; } + return links.enumKind = hasNonLiteralMember ? EnumKind.Numeric : EnumKind.Literal; + } - function getBaseTypeOfEnumLiteralType(type: Type) { - return type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union) ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)!) : type; - } + function getBaseTypeOfEnumLiteralType(type: Type) { + return type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union) ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)!) : type; + } - function getDeclaredTypeOfEnum(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (links.declaredType) { - return links.declaredType; - } - if (getEnumKind(symbol) === EnumKind.Literal) { - enumCount++; - const memberTypeList: Type[] = []; - if (symbol.declarations) { - for (const declaration of symbol.declarations) { - if (declaration.kind === SyntaxKind.EnumDeclaration) { - for (const member of (declaration as EnumDeclaration).members) { - const value = getEnumMemberValue(member); - const memberType = getFreshTypeOfLiteralType(getEnumLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member))); - getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType; - memberTypeList.push(getRegularTypeOfLiteralType(memberType)); - } + function getDeclaredTypeOfEnum(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (links.declaredType) { + return links.declaredType; + } + if (getEnumKind(symbol) === EnumKind.Literal) { + enumCount++; + const memberTypeList: Type[] = []; + if (symbol.declarations) { + for (const declaration of symbol.declarations) { + if (declaration.kind === SyntaxKind.EnumDeclaration) { + for (const member of (declaration as EnumDeclaration).members) { + const value = getEnumMemberValue(member); + const memberType = getFreshTypeOfLiteralType(getEnumLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member))); + getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType; + memberTypeList.push(getRegularTypeOfLiteralType(memberType)); } } } - if (memberTypeList.length) { - const enumType = getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined); - if (enumType.flags & TypeFlags.Union) { - enumType.flags |= TypeFlags.EnumLiteral; - enumType.symbol = symbol; - } - return links.declaredType = enumType; + } + if (memberTypeList.length) { + const enumType = getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined); + if (enumType.flags & TypeFlags.Union) { + enumType.flags |= TypeFlags.EnumLiteral; + enumType.symbol = symbol; } + return links.declaredType = enumType; } - const enumType = createType(TypeFlags.Enum); - enumType.symbol = symbol; - return links.declaredType = enumType; } + const enumType = createType(TypeFlags.Enum); + enumType.symbol = symbol; + return links.declaredType = enumType; + } - function getDeclaredTypeOfEnumMember(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); + function getDeclaredTypeOfEnumMember(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.declaredType) { + const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)!); if (!links.declaredType) { - const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)!); - if (!links.declaredType) { - links.declaredType = enumType; - } + links.declaredType = enumType; } - return links.declaredType; } + return links.declaredType; + } - function getDeclaredTypeOfTypeParameter(symbol: Symbol): TypeParameter { - const links = getSymbolLinks(symbol); - return links.declaredType || (links.declaredType = createTypeParameter(symbol)); - } + function getDeclaredTypeOfTypeParameter(symbol: Symbol): TypeParameter { + const links = getSymbolLinks(symbol); + return links.declaredType || (links.declaredType = createTypeParameter(symbol)); + } - function getDeclaredTypeOfAlias(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - return links.declaredType || (links.declaredType = getDeclaredTypeOfSymbol(resolveAlias(symbol))); - } + function getDeclaredTypeOfAlias(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + return links.declaredType || (links.declaredType = getDeclaredTypeOfSymbol(resolveAlias(symbol))); + } - function getDeclaredTypeOfSymbol(symbol: Symbol): Type { - return tryGetDeclaredTypeOfSymbol(symbol) || errorType; - } + function getDeclaredTypeOfSymbol(symbol: Symbol): Type { + return tryGetDeclaredTypeOfSymbol(symbol) || errorType; + } - function tryGetDeclaredTypeOfSymbol(symbol: Symbol): Type | undefined { - if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - return getDeclaredTypeOfClassOrInterface(symbol); - } - if (symbol.flags & SymbolFlags.TypeAlias) { - return getDeclaredTypeOfTypeAlias(symbol); - } - if (symbol.flags & SymbolFlags.TypeParameter) { - return getDeclaredTypeOfTypeParameter(symbol); - } - if (symbol.flags & SymbolFlags.Enum) { - return getDeclaredTypeOfEnum(symbol); - } - if (symbol.flags & SymbolFlags.EnumMember) { - return getDeclaredTypeOfEnumMember(symbol); - } - if (symbol.flags & SymbolFlags.Alias) { - return getDeclaredTypeOfAlias(symbol); - } - return undefined; + function tryGetDeclaredTypeOfSymbol(symbol: Symbol): Type | undefined { + if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + return getDeclaredTypeOfClassOrInterface(symbol); } - - /** - * A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string - * literal type, an array with an element type that is free of this references, or a type reference that is - * free of this references. - */ - function isThislessType(node: TypeNode): boolean { - switch (node.kind) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.UnknownKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.UndefinedKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.LiteralType: - return true; - case SyntaxKind.ArrayType: - return isThislessType((node as ArrayTypeNode).elementType); - case SyntaxKind.TypeReference: - return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments!.every(isThislessType); - } - return false; + if (symbol.flags & SymbolFlags.TypeAlias) { + return getDeclaredTypeOfTypeAlias(symbol); } - - /** A type parameter is thisless if its constraint is thisless, or if it has no constraint. */ - function isThislessTypeParameter(node: TypeParameterDeclaration) { - const constraint = getEffectiveConstraintOfTypeParameter(node); - return !constraint || isThislessType(constraint); + if (symbol.flags & SymbolFlags.TypeParameter) { + return getDeclaredTypeOfTypeParameter(symbol); } - - /** - * A variable-like declaration is free of this references if it has a type annotation - * that is thisless, or if it has no type annotation and no initializer (and is thus of type any). - */ - function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean { - const typeNode = getEffectiveTypeAnnotationNode(node); - return typeNode ? isThislessType(typeNode) : !hasInitializer(node); + if (symbol.flags & SymbolFlags.Enum) { + return getDeclaredTypeOfEnum(symbol); } - - /** - * A function-like declaration is considered free of `this` references if it has a return type - * annotation that is free of this references and if each parameter is thisless and if - * each type parameter (if present) is thisless. - */ - function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { - const returnType = getEffectiveReturnTypeNode(node); - const typeParameters = getEffectiveTypeParameterDeclarations(node); - return (node.kind === SyntaxKind.Constructor || (!!returnType && isThislessType(returnType))) && - node.parameters.every(isThislessVariableLikeDeclaration) && - typeParameters.every(isThislessTypeParameter); + if (symbol.flags & SymbolFlags.EnumMember) { + return getDeclaredTypeOfEnumMember(symbol); } - - /** - * Returns true if the class or interface member given by the symbol is free of "this" references. The - * function may return false for symbols that are actually free of "this" references because it is not - * feasible to perform a complete analysis in all cases. In particular, property members with types - * inferred from their initializers and function members with inferred return types are conservatively - * assumed not to be free of "this" references. - */ - function isThisless(symbol: Symbol): boolean { - if (symbol.declarations && symbol.declarations.length === 1) { - const declaration = symbol.declarations[0]; - if (declaration) { - switch (declaration.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return isThislessVariableLikeDeclaration(declaration as VariableLikeDeclaration); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return isThislessFunctionLikeDeclaration(declaration as FunctionLikeDeclaration | AccessorDeclaration); - } - } - } - return false; + if (symbol.flags & SymbolFlags.Alias) { + return getDeclaredTypeOfAlias(symbol); } + return undefined; + } - // The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true, - // we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation. - function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable { - const result = createSymbolTable(); - for (const symbol of symbols) { - result.set(symbol.escapedName, mappingThisOnly && isThisless(symbol) ? symbol : instantiateSymbol(symbol, mapper)); - } - return result; + /** + * A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string + * literal type, an array with an element type that is free of this references, or a type reference that is + * free of this references. + */ + function isThislessType(node: TypeNode): boolean { + switch (node.kind) { + case SyntaxKind.AnyKeyword: + case SyntaxKind.UnknownKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.LiteralType: + return true; + case SyntaxKind.ArrayType: + return isThislessType((node as ArrayTypeNode).elementType); + case SyntaxKind.TypeReference: + return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments!.every(isThislessType); } + return false; + } - function addInheritedMembers(symbols: SymbolTable, baseSymbols: Symbol[]) { - for (const s of baseSymbols) { - if (!symbols.has(s.escapedName) && !isStaticPrivateIdentifierProperty(s)) { - symbols.set(s.escapedName, s); - } - } - } + /** A type parameter is thisless if its constraint is thisless, or if it has no constraint. */ + function isThislessTypeParameter(node: TypeParameterDeclaration) { + const constraint = getEffectiveConstraintOfTypeParameter(node); + return !constraint || isThislessType(constraint); + } - function isStaticPrivateIdentifierProperty(s: Symbol): boolean { - return !!s.valueDeclaration && isPrivateIdentifierClassElementDeclaration(s.valueDeclaration) && isStatic(s.valueDeclaration); - } + /** + * A variable-like declaration is free of this references if it has a type annotation + * that is thisless, or if it has no type annotation and no initializer (and is thus of type any). + */ + function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean { + const typeNode = getEffectiveTypeAnnotationNode(node); + return typeNode ? isThislessType(typeNode) : !hasInitializer(node); + } - function resolveDeclaredMembers(type: InterfaceType): InterfaceTypeWithDeclaredMembers { - if (!(type as InterfaceTypeWithDeclaredMembers).declaredProperties) { - const symbol = type.symbol; - const members = getMembersOfSymbol(symbol); - (type as InterfaceTypeWithDeclaredMembers).declaredProperties = getNamedMembers(members); - // Start with signatures at empty array in case of recursive types - (type as InterfaceTypeWithDeclaredMembers).declaredCallSignatures = emptyArray; - (type as InterfaceTypeWithDeclaredMembers).declaredConstructSignatures = emptyArray; - (type as InterfaceTypeWithDeclaredMembers).declaredIndexInfos = emptyArray; + /** + * A function-like declaration is considered free of `this` references if it has a return type + * annotation that is free of this references and if each parameter is thisless and if + * each type parameter (if present) is thisless. + */ + function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { + const returnType = getEffectiveReturnTypeNode(node); + const typeParameters = getEffectiveTypeParameterDeclarations(node); + return (node.kind === SyntaxKind.Constructor || (!!returnType && isThislessType(returnType))) && + node.parameters.every(isThislessVariableLikeDeclaration) && + typeParameters.every(isThislessTypeParameter); + } - (type as InterfaceTypeWithDeclaredMembers).declaredCallSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); - (type as InterfaceTypeWithDeclaredMembers).declaredConstructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); - (type as InterfaceTypeWithDeclaredMembers).declaredIndexInfos = getIndexInfosOfSymbol(symbol); + /** + * Returns true if the class or interface member given by the symbol is free of "this" references. The + * function may return false for symbols that are actually free of "this" references because it is not + * feasible to perform a complete analysis in all cases. In particular, property members with types + * inferred from their initializers and function members with inferred return types are conservatively + * assumed not to be free of "this" references. + */ + function isThisless(symbol: Symbol): boolean { + if (symbol.declarations && symbol.declarations.length === 1) { + const declaration = symbol.declarations[0]; + if (declaration) { + switch (declaration.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return isThislessVariableLikeDeclaration(declaration as VariableLikeDeclaration); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return isThislessFunctionLikeDeclaration(declaration as FunctionLikeDeclaration | AccessorDeclaration); + } } - return type as InterfaceTypeWithDeclaredMembers; } + return false; + } - /** - * Indicates whether a type can be used as a property name. - */ - function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType { - return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique); + // The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true, + // we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation. + function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable { + const result = createSymbolTable(); + for (const symbol of symbols) { + result.set(symbol.escapedName, mappingThisOnly && isThisless(symbol) ? symbol : instantiateSymbol(symbol, mapper)); } + return result; + } - /** - * Indicates whether a declaration name is definitely late-bindable. - * A declaration name is only late-bindable if: - * - It is a `ComputedPropertyName`. - * - Its expression is an `Identifier` or either a `PropertyAccessExpression` an - * `ElementAccessExpression` consisting only of these same three types of nodes. - * - The type of its expression is a string or numeric literal type, or is a `unique symbol` type. - */ - function isLateBindableName(node: DeclarationName): node is LateBoundName { - if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) { - return false; + function addInheritedMembers(symbols: SymbolTable, baseSymbols: Symbol[]) { + for (const s of baseSymbols) { + if (!symbols.has(s.escapedName) && !isStaticPrivateIdentifierProperty(s)) { + symbols.set(s.escapedName, s); } - const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression; - return isEntityNameExpression(expr) - && isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached(expr)); } + } - function isLateBoundName(name: __String): boolean { - return (name as string).charCodeAt(0) === CharacterCodes._ && - (name as string).charCodeAt(1) === CharacterCodes._ && - (name as string).charCodeAt(2) === CharacterCodes.at; - } + function isStaticPrivateIdentifierProperty(s: Symbol): boolean { + return !!s.valueDeclaration && isPrivateIdentifierClassElementDeclaration(s.valueDeclaration) && isStatic(s.valueDeclaration); + } - /** - * Indicates whether a declaration has a late-bindable dynamic name. - */ - function hasLateBindableName(node: Declaration): node is LateBoundDeclaration | LateBoundBinaryExpressionDeclaration { - const name = getNameOfDeclaration(node); - return !!name && isLateBindableName(name); - } + function resolveDeclaredMembers(type: InterfaceType): InterfaceTypeWithDeclaredMembers { + if (!(type as InterfaceTypeWithDeclaredMembers).declaredProperties) { + const symbol = type.symbol; + const members = getMembersOfSymbol(symbol); + (type as InterfaceTypeWithDeclaredMembers).declaredProperties = getNamedMembers(members); + // Start with signatures at empty array in case of recursive types + (type as InterfaceTypeWithDeclaredMembers).declaredCallSignatures = emptyArray; + (type as InterfaceTypeWithDeclaredMembers).declaredConstructSignatures = emptyArray; + (type as InterfaceTypeWithDeclaredMembers).declaredIndexInfos = emptyArray; + + (type as InterfaceTypeWithDeclaredMembers).declaredCallSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); + (type as InterfaceTypeWithDeclaredMembers).declaredConstructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); + (type as InterfaceTypeWithDeclaredMembers).declaredIndexInfos = getIndexInfosOfSymbol(symbol); + } + return type as InterfaceTypeWithDeclaredMembers; + } - /** - * Indicates whether a declaration has an early-bound name or a dynamic name that can be late-bound. - */ - function hasBindableName(node: Declaration) { - return !hasDynamicName(node) || hasLateBindableName(node); - } + /** + * Indicates whether a type can be used as a property name. + */ + function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType { + return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique); + } - /** - * Indicates whether a declaration name is a dynamic name that cannot be late-bound. - */ - function isNonBindableDynamicName(node: DeclarationName) { - return isDynamicName(node) && !isLateBindableName(node); + /** + * Indicates whether a declaration name is definitely late-bindable. + * A declaration name is only late-bindable if: + * - It is a `ComputedPropertyName`. + * - Its expression is an `Identifier` or either a `PropertyAccessExpression` an + * `ElementAccessExpression` consisting only of these same three types of nodes. + * - The type of its expression is a string or numeric literal type, or is a `unique symbol` type. + */ + function isLateBindableName(node: DeclarationName): node is LateBoundName { + if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) { + return false; } + const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression; + return isEntityNameExpression(expr) + && isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached(expr)); + } - /** - * Gets the symbolic name for a member from its type. - */ - function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String { - if (type.flags & TypeFlags.UniqueESSymbol) { - return (type as UniqueESSymbolType).escapedName; - } - if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { - return escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value); - } - return Debug.fail(); + function isLateBoundName(name: __String): boolean { + return (name as string).charCodeAt(0) === CharacterCodes._ && + (name as string).charCodeAt(1) === CharacterCodes._ && + (name as string).charCodeAt(2) === CharacterCodes.at; + } + + /** + * Indicates whether a declaration has a late-bindable dynamic name. + */ + function hasLateBindableName(node: Declaration): node is LateBoundDeclaration | LateBoundBinaryExpressionDeclaration { + const name = getNameOfDeclaration(node); + return !!name && isLateBindableName(name); + } + + /** + * Indicates whether a declaration has an early-bound name or a dynamic name that can be late-bound. + */ + function hasBindableName(node: Declaration) { + return !hasDynamicName(node) || hasLateBindableName(node); + } + + /** + * Indicates whether a declaration name is a dynamic name that cannot be late-bound. + */ + function isNonBindableDynamicName(node: DeclarationName) { + return isDynamicName(node) && !isLateBindableName(node); + } + + /** + * Gets the symbolic name for a member from its type. + */ + function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String { + if (type.flags & TypeFlags.UniqueESSymbol) { + return (type as UniqueESSymbolType).escapedName; } + if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { + return escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value); + } + return Debug.fail(); + } - /** - * Adds a declaration to a late-bound dynamic member. This performs the same function for - * late-bound members that `addDeclarationToSymbol` in binder.ts performs for early-bound - * members. - */ - function addDeclarationToLateBoundSymbol(symbol: Symbol, member: LateBoundDeclaration | BinaryExpression, symbolFlags: SymbolFlags) { - Debug.assert(!!(getCheckFlags(symbol) & CheckFlags.Late), "Expected a late-bound symbol."); - symbol.flags |= symbolFlags; - getSymbolLinks(member.symbol).lateSymbol = symbol; - if (!symbol.declarations) { - symbol.declarations = [member]; - } - else if(!member.symbol.isReplaceableByMethod) { - symbol.declarations.push(member); - } - if (symbolFlags & SymbolFlags.Value) { - if (!symbol.valueDeclaration || symbol.valueDeclaration.kind !== member.kind) { - symbol.valueDeclaration = member; - } + /** + * Adds a declaration to a late-bound dynamic member. This performs the same function for + * late-bound members that `addDeclarationToSymbol` in binder.ts performs for early-bound + * members. + */ + function addDeclarationToLateBoundSymbol(symbol: Symbol, member: LateBoundDeclaration | BinaryExpression, symbolFlags: SymbolFlags) { + Debug.assert(!!(getCheckFlags(symbol) & CheckFlags.Late), "Expected a late-bound symbol."); + symbol.flags |= symbolFlags; + getSymbolLinks(member.symbol).lateSymbol = symbol; + if (!symbol.declarations) { + symbol.declarations = [member]; + } + else if(!member.symbol.isReplaceableByMethod) { + symbol.declarations.push(member); + } + if (symbolFlags & SymbolFlags.Value) { + if (!symbol.valueDeclaration || symbol.valueDeclaration.kind !== member.kind) { + symbol.valueDeclaration = member; } } + } - /** - * Performs late-binding of a dynamic member. This performs the same function for - * late-bound members that `declareSymbol` in binder.ts performs for early-bound - * members. - * - * If a symbol is a dynamic name from a computed property, we perform an additional "late" - * binding phase to attempt to resolve the name for the symbol from the type of the computed - * property's expression. If the type of the expression is a string-literal, numeric-literal, - * or unique symbol type, we can use that type as the name of the symbol. - * - * For example, given: - * - * const x = Symbol(); - * - * interface I { - * [x]: number; - * } - * - * The binder gives the property `[x]: number` a special symbol with the name "__computed". - * In the late-binding phase we can type-check the expression `x` and see that it has a - * unique symbol type which we can then use as the name of the member. This allows users - * to define custom symbols that can be used in the members of an object type. - * - * @param parent The containing symbol for the member. - * @param earlySymbols The early-bound symbols of the parent. - * @param lateSymbols The late-bound symbols of the parent. - * @param decl The member to bind. - */ - function lateBindMember(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: UnderscoreEscapedMap, decl: LateBoundDeclaration | LateBoundBinaryExpressionDeclaration) { - Debug.assert(!!decl.symbol, "The member is expected to have a symbol."); - const links = getNodeLinks(decl); - if (!links.resolvedSymbol) { - // In the event we attempt to resolve the late-bound name of this member recursively, - // fall back to the early-bound name of this member. - links.resolvedSymbol = decl.symbol; - const declName = isBinaryExpression(decl) ? decl.left : decl.name; - const type = isElementAccessExpression(declName) ? checkExpressionCached(declName.argumentExpression) : checkComputedPropertyName(declName); - if (isTypeUsableAsPropertyName(type)) { - const memberName = getPropertyNameFromType(type); - const symbolFlags = decl.symbol.flags; - - // Get or add a late-bound symbol for the member. This allows us to merge late-bound accessor declarations. - let lateSymbol = lateSymbols.get(memberName); - if (!lateSymbol) lateSymbols.set(memberName, lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late)); - - // Report an error if a late-bound member has the same name as an early-bound member, - // or if we have another early-bound symbol declaration with the same name and - // conflicting flags. - const earlySymbol = earlySymbols && earlySymbols.get(memberName); - if (lateSymbol.flags & getExcludedSymbolFlags(symbolFlags) || earlySymbol) { - // If we have an existing early-bound member, combine its declarations so that we can - // report an error at each declaration. - const declarations = earlySymbol ? concatenate(earlySymbol.declarations, lateSymbol.declarations) : lateSymbol.declarations; - const name = !(type.flags & TypeFlags.UniqueESSymbol) && unescapeLeadingUnderscores(memberName) || declarationNameToString(declName); - forEach(declarations, declaration => error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Property_0_was_also_declared_here, name)); - error(declName || decl, Diagnostics.Duplicate_property_0, name); - lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late); - } - lateSymbol.nameType = type; - addDeclarationToLateBoundSymbol(lateSymbol, decl, symbolFlags); - if (lateSymbol.parent) { - Debug.assert(lateSymbol.parent === parent, "Existing symbol parent should match new one"); - } - else { - lateSymbol.parent = parent; - } - return links.resolvedSymbol = lateSymbol; + /** + * Performs late-binding of a dynamic member. This performs the same function for + * late-bound members that `declareSymbol` in binder.ts performs for early-bound + * members. + * + * If a symbol is a dynamic name from a computed property, we perform an additional "late" + * binding phase to attempt to resolve the name for the symbol from the type of the computed + * property's expression. If the type of the expression is a string-literal, numeric-literal, + * or unique symbol type, we can use that type as the name of the symbol. + * + * For example, given: + * + * const x = Symbol(); + * + * interface I { + * [x]: number; + * } + * + * The binder gives the property `[x]: number` a special symbol with the name "__computed". + * In the late-binding phase we can type-check the expression `x` and see that it has a + * unique symbol type which we can then use as the name of the member. This allows users + * to define custom symbols that can be used in the members of an object type. + * + * @param parent The containing symbol for the member. + * @param earlySymbols The early-bound symbols of the parent. + * @param lateSymbols The late-bound symbols of the parent. + * @param decl The member to bind. + */ + function lateBindMember(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: UnderscoreEscapedMap, decl: LateBoundDeclaration | LateBoundBinaryExpressionDeclaration) { + Debug.assert(!!decl.symbol, "The member is expected to have a symbol."); + const links = getNodeLinks(decl); + if (!links.resolvedSymbol) { + // In the event we attempt to resolve the late-bound name of this member recursively, + // fall back to the early-bound name of this member. + links.resolvedSymbol = decl.symbol; + const declName = isBinaryExpression(decl) ? decl.left : decl.name; + const type = isElementAccessExpression(declName) ? checkExpressionCached(declName.argumentExpression) : checkComputedPropertyName(declName); + if (isTypeUsableAsPropertyName(type)) { + const memberName = getPropertyNameFromType(type); + const symbolFlags = decl.symbol.flags; + + // Get or add a late-bound symbol for the member. This allows us to merge late-bound accessor declarations. + let lateSymbol = lateSymbols.get(memberName); + if (!lateSymbol) lateSymbols.set(memberName, lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late)); + + // Report an error if a late-bound member has the same name as an early-bound member, + // or if we have another early-bound symbol declaration with the same name and + // conflicting flags. + const earlySymbol = earlySymbols && earlySymbols.get(memberName); + if (lateSymbol.flags & getExcludedSymbolFlags(symbolFlags) || earlySymbol) { + // If we have an existing early-bound member, combine its declarations so that we can + // report an error at each declaration. + const declarations = earlySymbol ? concatenate(earlySymbol.declarations, lateSymbol.declarations) : lateSymbol.declarations; + const name = !(type.flags & TypeFlags.UniqueESSymbol) && unescapeLeadingUnderscores(memberName) || declarationNameToString(declName); + forEach(declarations, declaration => error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Property_0_was_also_declared_here, name)); + error(declName || decl, Diagnostics.Duplicate_property_0, name); + lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late); + } + lateSymbol.nameType = type; + addDeclarationToLateBoundSymbol(lateSymbol, decl, symbolFlags); + if (lateSymbol.parent) { + Debug.assert(lateSymbol.parent === parent, "Existing symbol parent should match new one"); + } + else { + lateSymbol.parent = parent; } + return links.resolvedSymbol = lateSymbol; } - return links.resolvedSymbol; } + return links.resolvedSymbol; + } - function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): UnderscoreEscapedMap { - const links = getSymbolLinks(symbol); - if (!links[resolutionKind]) { - const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports; - const earlySymbols = !isStatic ? symbol.members : - symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol) : - symbol.exports; - - // In the event we recursively resolve the members/exports of the symbol, we - // set the initial value of resolvedMembers/resolvedExports to the early-bound - // members/exports of the symbol. - links[resolutionKind] = earlySymbols || emptySymbols; - - // fill in any as-yet-unresolved late-bound members. - const lateSymbols = createSymbolTable() as UnderscoreEscapedMap; - for (const decl of symbol.declarations || emptyArray) { - const members = getMembersOfDeclaration(decl); - if (members) { - for (const member of members) { - if (isStatic === hasStaticModifier(member) && hasLateBindableName(member)) { - lateBindMember(symbol, earlySymbols, lateSymbols, member); - } + function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): UnderscoreEscapedMap { + const links = getSymbolLinks(symbol); + if (!links[resolutionKind]) { + const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports; + const earlySymbols = !isStatic ? symbol.members : + symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol) : + symbol.exports; + + // In the event we recursively resolve the members/exports of the symbol, we + // set the initial value of resolvedMembers/resolvedExports to the early-bound + // members/exports of the symbol. + links[resolutionKind] = earlySymbols || emptySymbols; + + // fill in any as-yet-unresolved late-bound members. + const lateSymbols = createSymbolTable() as UnderscoreEscapedMap; + for (const decl of symbol.declarations || emptyArray) { + const members = getMembersOfDeclaration(decl); + if (members) { + for (const member of members) { + if (isStatic === hasStaticModifier(member) && hasLateBindableName(member)) { + lateBindMember(symbol, earlySymbols, lateSymbols, member); } } } - const assignments = symbol.assignmentDeclarationMembers; - if (assignments) { - const decls = arrayFrom(assignments.values()); - for (const member of decls) { - const assignmentKind = getAssignmentDeclarationKind(member as BinaryExpression | CallExpression); - const isInstanceMember = assignmentKind === AssignmentDeclarationKind.PrototypeProperty - || isBinaryExpression(member) && isPossiblyAliasedThisProperty(member, assignmentKind) - || assignmentKind === AssignmentDeclarationKind.ObjectDefinePrototypeProperty - || assignmentKind === AssignmentDeclarationKind.Prototype; // A straight `Prototype` assignment probably can never have a computed name - if (isStatic === !isInstanceMember && hasLateBindableName(member)) { - lateBindMember(symbol, earlySymbols, lateSymbols, member); - } + } + const assignments = symbol.assignmentDeclarationMembers; + if (assignments) { + const decls = arrayFrom(assignments.values()); + for (const member of decls) { + const assignmentKind = getAssignmentDeclarationKind(member as BinaryExpression | CallExpression); + const isInstanceMember = assignmentKind === AssignmentDeclarationKind.PrototypeProperty + || isBinaryExpression(member) && isPossiblyAliasedThisProperty(member, assignmentKind) + || assignmentKind === AssignmentDeclarationKind.ObjectDefinePrototypeProperty + || assignmentKind === AssignmentDeclarationKind.Prototype; // A straight `Prototype` assignment probably can never have a computed name + if (isStatic === !isInstanceMember && hasLateBindableName(member)) { + lateBindMember(symbol, earlySymbols, lateSymbols, member); } } - - links[resolutionKind] = combineSymbolTables(earlySymbols, lateSymbols) || emptySymbols; } - return links[resolutionKind]!; + links[resolutionKind] = combineSymbolTables(earlySymbols, lateSymbols) || emptySymbols; } - /** - * Gets a SymbolTable containing both the early- and late-bound members of a symbol. - * - * For a description of late-binding, see `lateBindMember`. - */ - function getMembersOfSymbol(symbol: Symbol) { - return symbol.flags & SymbolFlags.LateBindingContainer - ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedMembers) - : symbol.members || emptySymbols; - } + return links[resolutionKind]!; + } - /** - * If a symbol is the dynamic name of the member of an object type, get the late-bound - * symbol of the member. - * - * For a description of late-binding, see `lateBindMember`. - */ - function getLateBoundSymbol(symbol: Symbol): Symbol { - if (symbol.flags & SymbolFlags.ClassMember && symbol.escapedName === InternalSymbolName.Computed) { - const links = getSymbolLinks(symbol); - if (!links.lateSymbol && some(symbol.declarations, hasLateBindableName)) { - // force late binding of members/exports. This will set the late-bound symbol - const parent = getMergedSymbol(symbol.parent)!; - if (some(symbol.declarations, hasStaticModifier)) { - getExportsOfSymbol(parent); - } - else { - getMembersOfSymbol(parent); - } + /** + * Gets a SymbolTable containing both the early- and late-bound members of a symbol. + * + * For a description of late-binding, see `lateBindMember`. + */ + function getMembersOfSymbol(symbol: Symbol) { + return symbol.flags & SymbolFlags.LateBindingContainer + ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedMembers) + : symbol.members || emptySymbols; + } + + /** + * If a symbol is the dynamic name of the member of an object type, get the late-bound + * symbol of the member. + * + * For a description of late-binding, see `lateBindMember`. + */ + function getLateBoundSymbol(symbol: Symbol): Symbol { + if (symbol.flags & SymbolFlags.ClassMember && symbol.escapedName === InternalSymbolName.Computed) { + const links = getSymbolLinks(symbol); + if (!links.lateSymbol && some(symbol.declarations, hasLateBindableName)) { + // force late binding of members/exports. This will set the late-bound symbol + const parent = getMergedSymbol(symbol.parent)!; + if (some(symbol.declarations, hasStaticModifier)) { + getExportsOfSymbol(parent); + } + else { + getMembersOfSymbol(parent); } - return links.lateSymbol || (links.lateSymbol = symbol); } - return symbol; + return links.lateSymbol || (links.lateSymbol = symbol); } + return symbol; + } - function getTypeWithThisArgument(type: Type, thisArgument?: Type, needApparentType?: boolean): Type { - if (getObjectFlags(type) & ObjectFlags.Reference) { - const target = (type as TypeReference).target; - const typeArguments = getTypeArguments(type as TypeReference); - if (length(target.typeParameters) === length(typeArguments)) { - const ref = createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!])); - return needApparentType ? getApparentType(ref) : ref; - } - } - else if (type.flags & TypeFlags.Intersection) { - const types = sameMap((type as IntersectionType).types, t => getTypeWithThisArgument(t, thisArgument, needApparentType)); - return types !== (type as IntersectionType).types ? getIntersectionType(types) : type; + function getTypeWithThisArgument(type: Type, thisArgument?: Type, needApparentType?: boolean): Type { + if (getObjectFlags(type) & ObjectFlags.Reference) { + const target = (type as TypeReference).target; + const typeArguments = getTypeArguments(type as TypeReference); + if (length(target.typeParameters) === length(typeArguments)) { + const ref = createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!])); + return needApparentType ? getApparentType(ref) : ref; } - return needApparentType ? getApparentType(type) : type; } + else if (type.flags & TypeFlags.Intersection) { + const types = sameMap((type as IntersectionType).types, t => getTypeWithThisArgument(t, thisArgument, needApparentType)); + return types !== (type as IntersectionType).types ? getIntersectionType(types) : type; + } + return needApparentType ? getApparentType(type) : type; + } - function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: readonly TypeParameter[], typeArguments: readonly Type[]) { - let mapper: TypeMapper | undefined; - let members: SymbolTable; - let callSignatures: readonly Signature[]; - let constructSignatures: readonly Signature[]; - let indexInfos: readonly IndexInfo[]; - if (rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) { - members = source.symbol ? getMembersOfSymbol(source.symbol) : createSymbolTable(source.declaredProperties); - callSignatures = source.declaredCallSignatures; - constructSignatures = source.declaredConstructSignatures; - indexInfos = source.declaredIndexInfos; - } - else { - mapper = createTypeMapper(typeParameters, typeArguments); - members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1); - callSignatures = instantiateSignatures(source.declaredCallSignatures, mapper); - constructSignatures = instantiateSignatures(source.declaredConstructSignatures, mapper); - indexInfos = instantiateIndexInfos(source.declaredIndexInfos, mapper); - } - const baseTypes = getBaseTypes(source); - if (baseTypes.length) { - if (source.symbol && members === getMembersOfSymbol(source.symbol)) { - members = createSymbolTable(source.declaredProperties); - } - setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); - const thisArgument = lastOrUndefined(typeArguments); - for (const baseType of baseTypes) { - const instantiatedBaseType = thisArgument ? getTypeWithThisArgument(instantiateType(baseType, mapper), thisArgument) : baseType; - addInheritedMembers(members, getPropertiesOfType(instantiatedBaseType)); - callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call)); - constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct)); - const inheritedIndexInfos = instantiatedBaseType !== anyType ? getIndexInfosOfType(instantiatedBaseType) : [createIndexInfo(stringType, anyType, /*isReadonly*/ false)]; - indexInfos = concatenate(indexInfos, filter(inheritedIndexInfos, info => !findIndexInfo(indexInfos, info.keyType))); - } + function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: readonly TypeParameter[], typeArguments: readonly Type[]) { + let mapper: TypeMapper | undefined; + let members: SymbolTable; + let callSignatures: readonly Signature[]; + let constructSignatures: readonly Signature[]; + let indexInfos: readonly IndexInfo[]; + if (rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) { + members = source.symbol ? getMembersOfSymbol(source.symbol) : createSymbolTable(source.declaredProperties); + callSignatures = source.declaredCallSignatures; + constructSignatures = source.declaredConstructSignatures; + indexInfos = source.declaredIndexInfos; + } + else { + mapper = createTypeMapper(typeParameters, typeArguments); + members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1); + callSignatures = instantiateSignatures(source.declaredCallSignatures, mapper); + constructSignatures = instantiateSignatures(source.declaredConstructSignatures, mapper); + indexInfos = instantiateIndexInfos(source.declaredIndexInfos, mapper); + } + const baseTypes = getBaseTypes(source); + if (baseTypes.length) { + if (source.symbol && members === getMembersOfSymbol(source.symbol)) { + members = createSymbolTable(source.declaredProperties); } setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); + const thisArgument = lastOrUndefined(typeArguments); + for (const baseType of baseTypes) { + const instantiatedBaseType = thisArgument ? getTypeWithThisArgument(instantiateType(baseType, mapper), thisArgument) : baseType; + addInheritedMembers(members, getPropertiesOfType(instantiatedBaseType)); + callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call)); + constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct)); + const inheritedIndexInfos = instantiatedBaseType !== anyType ? getIndexInfosOfType(instantiatedBaseType) : [createIndexInfo(stringType, anyType, /*isReadonly*/ false)]; + indexInfos = concatenate(indexInfos, filter(inheritedIndexInfos, info => !findIndexInfo(indexInfos, info.keyType))); + } } + setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); + } - function resolveClassOrInterfaceMembers(type: InterfaceType): void { - resolveObjectTypeMembers(type, resolveDeclaredMembers(type), emptyArray, emptyArray); - } + function resolveClassOrInterfaceMembers(type: InterfaceType): void { + resolveObjectTypeMembers(type, resolveDeclaredMembers(type), emptyArray, emptyArray); + } - function resolveTypeReferenceMembers(type: TypeReference): void { - const source = resolveDeclaredMembers(type.target); - const typeParameters = concatenate(source.typeParameters!, [source.thisType!]); - const typeArguments = getTypeArguments(type); - const paddedTypeArguments = typeArguments.length === typeParameters.length ? typeArguments : concatenate(typeArguments, [type]); - resolveObjectTypeMembers(type, source, typeParameters, paddedTypeArguments); - } - - function createSignature( - declaration: SignatureDeclaration | JSDocSignature | undefined, - typeParameters: readonly TypeParameter[] | undefined, - thisParameter: Symbol | undefined, - parameters: readonly Symbol[], - resolvedReturnType: Type | undefined, - resolvedTypePredicate: TypePredicate | undefined, - minArgumentCount: number, - flags: SignatureFlags - ): Signature { - const sig = new Signature(checker, flags); - sig.declaration = declaration; - sig.typeParameters = typeParameters; - sig.parameters = parameters; - sig.thisParameter = thisParameter; - sig.resolvedReturnType = resolvedReturnType; - sig.resolvedTypePredicate = resolvedTypePredicate; - sig.minArgumentCount = minArgumentCount; - sig.resolvedMinArgumentCount = undefined; - sig.target = undefined; - sig.mapper = undefined; - sig.compositeSignatures = undefined; - sig.compositeKind = undefined; - return sig; - } - - function cloneSignature(sig: Signature): Signature { - const result = createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, /*resolvedReturnType*/ undefined, - /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags); - result.target = sig.target; - result.mapper = sig.mapper; - result.compositeSignatures = sig.compositeSignatures; - result.compositeKind = sig.compositeKind; - return result; - } + function resolveTypeReferenceMembers(type: TypeReference): void { + const source = resolveDeclaredMembers(type.target); + const typeParameters = concatenate(source.typeParameters!, [source.thisType!]); + const typeArguments = getTypeArguments(type); + const paddedTypeArguments = typeArguments.length === typeParameters.length ? typeArguments : concatenate(typeArguments, [type]); + resolveObjectTypeMembers(type, source, typeParameters, paddedTypeArguments); + } - function createUnionSignature(signature: Signature, unionSignatures: Signature[]) { - const result = cloneSignature(signature); - result.compositeSignatures = unionSignatures; - result.compositeKind = TypeFlags.Union; - result.target = undefined; - result.mapper = undefined; - return result; - } + function createSignature( + declaration: SignatureDeclaration | JSDocSignature | undefined, + typeParameters: readonly TypeParameter[] | undefined, + thisParameter: Symbol | undefined, + parameters: readonly Symbol[], + resolvedReturnType: Type | undefined, + resolvedTypePredicate: TypePredicate | undefined, + minArgumentCount: number, + flags: SignatureFlags + ): Signature { + const sig = new Signature(checker, flags); + sig.declaration = declaration; + sig.typeParameters = typeParameters; + sig.parameters = parameters; + sig.thisParameter = thisParameter; + sig.resolvedReturnType = resolvedReturnType; + sig.resolvedTypePredicate = resolvedTypePredicate; + sig.minArgumentCount = minArgumentCount; + sig.resolvedMinArgumentCount = undefined; + sig.target = undefined; + sig.mapper = undefined; + sig.compositeSignatures = undefined; + sig.compositeKind = undefined; + return sig; + } - function getOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags): Signature { - if ((signature.flags & SignatureFlags.CallChainFlags) === callChainFlags) { - return signature; - } - if (!signature.optionalCallSignatureCache) { - signature.optionalCallSignatureCache = {}; - } - const key = callChainFlags === SignatureFlags.IsInnerCallChain ? "inner" : "outer"; - return signature.optionalCallSignatureCache[key] - || (signature.optionalCallSignatureCache[key] = createOptionalCallSignature(signature, callChainFlags)); - } + function cloneSignature(sig: Signature): Signature { + const result = createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags); + result.target = sig.target; + result.mapper = sig.mapper; + result.compositeSignatures = sig.compositeSignatures; + result.compositeKind = sig.compositeKind; + return result; + } - function createOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags) { - Debug.assert(callChainFlags === SignatureFlags.IsInnerCallChain || callChainFlags === SignatureFlags.IsOuterCallChain, - "An optional call signature can either be for an inner call chain or an outer call chain, but not both."); - const result = cloneSignature(signature); - result.flags |= callChainFlags; - return result; + function createUnionSignature(signature: Signature, unionSignatures: Signature[]) { + const result = cloneSignature(signature); + result.compositeSignatures = unionSignatures; + result.compositeKind = TypeFlags.Union; + result.target = undefined; + result.mapper = undefined; + return result; + } + + function getOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags): Signature { + if ((signature.flags & SignatureFlags.CallChainFlags) === callChainFlags) { + return signature; } + if (!signature.optionalCallSignatureCache) { + signature.optionalCallSignatureCache = {}; + } + const key = callChainFlags === SignatureFlags.IsInnerCallChain ? "inner" : "outer"; + return signature.optionalCallSignatureCache[key] + || (signature.optionalCallSignatureCache[key] = createOptionalCallSignature(signature, callChainFlags)); + } - function getExpandedParameters(sig: Signature, skipUnionExpanding?: boolean): readonly (readonly Symbol[])[] { - if (signatureHasRestParameter(sig)) { - const restIndex = sig.parameters.length - 1; - const restType = getTypeOfSymbol(sig.parameters[restIndex]); - if (isTupleType(restType)) { - return [expandSignatureParametersWithTupleMembers(restType, restIndex)]; - } - else if (!skipUnionExpanding && restType.flags & TypeFlags.Union && every((restType as UnionType).types, isTupleType)) { - return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex)); - } - } - return [sig.parameters]; - - function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number) { - const elementTypes = getTypeArguments(restType); - const associatedNames = restType.target.labeledElementDeclarations; - const restParams = map(elementTypes, (t, i) => { - // Lookup the label from the individual tuple passed in before falling back to the signature `rest` parameter name - const tupleLabelName = !!associatedNames && getTupleElementLabel(associatedNames[i]); - const name = tupleLabelName || getParameterNameAtPosition(sig, restIndex + i, restType); - const flags = restType.target.elementFlags[i]; - const checkFlags = flags & ElementFlags.Variable ? CheckFlags.RestParameter : - flags & ElementFlags.Optional ? CheckFlags.OptionalParameter : 0; - const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name, checkFlags); - symbol.type = flags & ElementFlags.Rest ? createArrayType(t) : t; - return symbol; - }); - return concatenate(sig.parameters.slice(0, restIndex), restParams); - } - } - - function getDefaultConstructSignatures(classType: InterfaceType): Signature[] { - const baseConstructorType = getBaseConstructorTypeOfClass(classType); - const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); - const declaration = getClassLikeDeclarationOfSymbol(classType.symbol); - const isAbstract = !!declaration && hasSyntacticModifier(declaration, ModifierFlags.Abstract); - if (baseSignatures.length === 0) { - return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, isAbstract ? SignatureFlags.Abstract : SignatureFlags.None)]; - } - const baseTypeNode = getBaseTypeNodeOfClass(classType)!; - const isJavaScript = isInJSFile(baseTypeNode); - const typeArguments = typeArgumentsFromTypeReferenceNode(baseTypeNode); - const typeArgCount = length(typeArguments); - const result: Signature[] = []; - for (const baseSig of baseSignatures) { - const minTypeArgumentCount = getMinTypeArgumentCount(baseSig.typeParameters); - const typeParamCount = length(baseSig.typeParameters); - if (isJavaScript || typeArgCount >= minTypeArgumentCount && typeArgCount <= typeParamCount) { - const sig = typeParamCount ? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, minTypeArgumentCount, isJavaScript)) : cloneSignature(baseSig); - sig.typeParameters = classType.localTypeParameters; - sig.resolvedReturnType = classType; - sig.flags = isAbstract ? sig.flags | SignatureFlags.Abstract : sig.flags & ~SignatureFlags.Abstract; - result.push(sig); - } - } - return result; + function createOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags) { + Debug.assert(callChainFlags === SignatureFlags.IsInnerCallChain || callChainFlags === SignatureFlags.IsOuterCallChain, + "An optional call signature can either be for an inner call chain or an outer call chain, but not both."); + const result = cloneSignature(signature); + result.flags |= callChainFlags; + return result; + } + + function getExpandedParameters(sig: Signature, skipUnionExpanding?: boolean): readonly (readonly Symbol[])[] { + if (signatureHasRestParameter(sig)) { + const restIndex = sig.parameters.length - 1; + const restType = getTypeOfSymbol(sig.parameters[restIndex]); + if (isTupleType(restType)) { + return [expandSignatureParametersWithTupleMembers(restType, restIndex)]; + } + else if (!skipUnionExpanding && restType.flags & TypeFlags.Union && every((restType as UnionType).types, isTupleType)) { + return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex)); + } + } + return [sig.parameters]; + + function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number) { + const elementTypes = getTypeArguments(restType); + const associatedNames = restType.target.labeledElementDeclarations; + const restParams = map(elementTypes, (t, i) => { + // Lookup the label from the individual tuple passed in before falling back to the signature `rest` parameter name + const tupleLabelName = !!associatedNames && getTupleElementLabel(associatedNames[i]); + const name = tupleLabelName || getParameterNameAtPosition(sig, restIndex + i, restType); + const flags = restType.target.elementFlags[i]; + const checkFlags = flags & ElementFlags.Variable ? CheckFlags.RestParameter : + flags & ElementFlags.Optional ? CheckFlags.OptionalParameter : 0; + const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name, checkFlags); + symbol.type = flags & ElementFlags.Rest ? createArrayType(t) : t; + return symbol; + }); + return concatenate(sig.parameters.slice(0, restIndex), restParams); } + } - function findMatchingSignature(signatureList: readonly Signature[], signature: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean): Signature | undefined { - for (const s of signatureList) { - if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, partialMatch ? compareTypesSubtypeOf : compareTypesIdentical)) { - return s; - } + function getDefaultConstructSignatures(classType: InterfaceType): Signature[] { + const baseConstructorType = getBaseConstructorTypeOfClass(classType); + const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); + const declaration = getClassLikeDeclarationOfSymbol(classType.symbol); + const isAbstract = !!declaration && hasSyntacticModifier(declaration, ModifierFlags.Abstract); + if (baseSignatures.length === 0) { + return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, isAbstract ? SignatureFlags.Abstract : SignatureFlags.None)]; + } + const baseTypeNode = getBaseTypeNodeOfClass(classType)!; + const isJavaScript = isInJSFile(baseTypeNode); + const typeArguments = typeArgumentsFromTypeReferenceNode(baseTypeNode); + const typeArgCount = length(typeArguments); + const result: Signature[] = []; + for (const baseSig of baseSignatures) { + const minTypeArgumentCount = getMinTypeArgumentCount(baseSig.typeParameters); + const typeParamCount = length(baseSig.typeParameters); + if (isJavaScript || typeArgCount >= minTypeArgumentCount && typeArgCount <= typeParamCount) { + const sig = typeParamCount ? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, minTypeArgumentCount, isJavaScript)) : cloneSignature(baseSig); + sig.typeParameters = classType.localTypeParameters; + sig.resolvedReturnType = classType; + sig.flags = isAbstract ? sig.flags | SignatureFlags.Abstract : sig.flags & ~SignatureFlags.Abstract; + result.push(sig); + } + } + return result; + } + + function findMatchingSignature(signatureList: readonly Signature[], signature: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean): Signature | undefined { + for (const s of signatureList) { + if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, partialMatch ? compareTypesSubtypeOf : compareTypesIdentical)) { + return s; } } + } - function findMatchingSignatures(signatureLists: readonly (readonly Signature[])[], signature: Signature, listIndex: number): Signature[] | undefined { - if (signature.typeParameters) { - // We require an exact match for generic signatures, so we only return signatures from the first - // signature list and only if they have exact matches in the other signature lists. - if (listIndex > 0) { - return undefined; - } - for (let i = 1; i < signatureLists.length; i++) { - if (!findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false)) { - return undefined; - } - } - return [signature]; + function findMatchingSignatures(signatureLists: readonly (readonly Signature[])[], signature: Signature, listIndex: number): Signature[] | undefined { + if (signature.typeParameters) { + // We require an exact match for generic signatures, so we only return signatures from the first + // signature list and only if they have exact matches in the other signature lists. + if (listIndex > 0) { + return undefined; } - let result: Signature[] | undefined; - for (let i = 0; i < signatureLists.length; i++) { - // Allow matching non-generic signatures to have excess parameters and different return types. - // Prefer matching this types if possible. - const match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true); - if (!match) { + for (let i = 1; i < signatureLists.length; i++) { + if (!findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false)) { return undefined; } - result = appendIfUnique(result, match); } - return result; + return [signature]; } - - // The signatures of a union type are those signatures that are present in each of the constituent types. - // Generic signatures must match exactly, but non-generic signatures are allowed to have extra optional - // parameters and may differ in return types. When signatures differ in return types, the resulting return - // type is the union of the constituent return types. - function getUnionSignatures(signatureLists: readonly (readonly Signature[])[]): Signature[] { - let result: Signature[] | undefined; - let indexWithLengthOverOne: number | undefined; - for (let i = 0; i < signatureLists.length; i++) { - if (signatureLists[i].length === 0) return emptyArray; - if (signatureLists[i].length > 1) { - indexWithLengthOverOne = indexWithLengthOverOne === undefined ? i : -1; // -1 is a signal there are multiple overload sets - } - for (const signature of signatureLists[i]) { - // Only process signatures with parameter lists that aren't already in the result list - if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true)) { - const unionSignatures = findMatchingSignatures(signatureLists, signature, i); - if (unionSignatures) { - let s = signature; - // Union the result types when more than one signature matches - if (unionSignatures.length > 1) { - let thisParameter = signature.thisParameter; - const firstThisParameterOfUnionSignatures = forEach(unionSignatures, sig => sig.thisParameter); - if (firstThisParameterOfUnionSignatures) { - const thisType = getIntersectionType(mapDefined(unionSignatures, sig => sig.thisParameter && getTypeOfSymbol(sig.thisParameter))); - thisParameter = createSymbolWithType(firstThisParameterOfUnionSignatures, thisType); - } - s = createUnionSignature(signature, unionSignatures); - s.thisParameter = thisParameter; - } - (result || (result = [])).push(s); - } - } - } + let result: Signature[] | undefined; + for (let i = 0; i < signatureLists.length; i++) { + // Allow matching non-generic signatures to have excess parameters and different return types. + // Prefer matching this types if possible. + const match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true); + if (!match) { + return undefined; } - if (!length(result) && indexWithLengthOverOne !== -1) { - // No sufficiently similar signature existed to subsume all the other signatures in the union - time to see if we can make a single - // signature that handles all over them. We only do this when there are overloads in only one constituent. - // (Overloads are conditional in nature and having overloads in multiple constituents would necessitate making a power set of - // signatures from the type, whose ordering would be non-obvious) - const masterList = signatureLists[indexWithLengthOverOne !== undefined ? indexWithLengthOverOne : 0]; - let results: Signature[] | undefined = masterList.slice(); - for (const signatures of signatureLists) { - if (signatures !== masterList) { - const signature = signatures[0]; - Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass"); - results = !!signature.typeParameters && some(results, s => !!s.typeParameters && !compareTypeParametersIdentical(signature.typeParameters, s.typeParameters)) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature)); - if (!results) { - break; - } + result = appendIfUnique(result, match); + } + return result; + } + + // The signatures of a union type are those signatures that are present in each of the constituent types. + // Generic signatures must match exactly, but non-generic signatures are allowed to have extra optional + // parameters and may differ in return types. When signatures differ in return types, the resulting return + // type is the union of the constituent return types. + function getUnionSignatures(signatureLists: readonly (readonly Signature[])[]): Signature[] { + let result: Signature[] | undefined; + let indexWithLengthOverOne: number | undefined; + for (let i = 0; i < signatureLists.length; i++) { + if (signatureLists[i].length === 0) return emptyArray; + if (signatureLists[i].length > 1) { + indexWithLengthOverOne = indexWithLengthOverOne === undefined ? i : -1; // -1 is a signal there are multiple overload sets + } + for (const signature of signatureLists[i]) { + // Only process signatures with parameter lists that aren't already in the result list + if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true)) { + const unionSignatures = findMatchingSignatures(signatureLists, signature, i); + if (unionSignatures) { + let s = signature; + // Union the result types when more than one signature matches + if (unionSignatures.length > 1) { + let thisParameter = signature.thisParameter; + const firstThisParameterOfUnionSignatures = forEach(unionSignatures, sig => sig.thisParameter); + if (firstThisParameterOfUnionSignatures) { + const thisType = getIntersectionType(mapDefined(unionSignatures, sig => sig.thisParameter && getTypeOfSymbol(sig.thisParameter))); + thisParameter = createSymbolWithType(firstThisParameterOfUnionSignatures, thisType); + } + s = createUnionSignature(signature, unionSignatures); + s.thisParameter = thisParameter; + } + (result || (result = [])).push(s); + } + } + } + } + if (!length(result) && indexWithLengthOverOne !== -1) { + // No sufficiently similar signature existed to subsume all the other signatures in the union - time to see if we can make a single + // signature that handles all over them. We only do this when there are overloads in only one constituent. + // (Overloads are conditional in nature and having overloads in multiple constituents would necessitate making a power set of + // signatures from the type, whose ordering would be non-obvious) + const masterList = signatureLists[indexWithLengthOverOne !== undefined ? indexWithLengthOverOne : 0]; + let results: Signature[] | undefined = masterList.slice(); + for (const signatures of signatureLists) { + if (signatures !== masterList) { + const signature = signatures[0]; + Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass"); + results = !!signature.typeParameters && some(results, s => !!s.typeParameters && !compareTypeParametersIdentical(signature.typeParameters, s.typeParameters)) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature)); + if (!results) { + break; } } - result = results; } - return result || emptyArray; + result = results; } + return result || emptyArray; + } - function compareTypeParametersIdentical(sourceParams: readonly TypeParameter[] | undefined, targetParams: readonly TypeParameter[] | undefined): boolean { - if (length(sourceParams) !== length(targetParams)) { - return false; - } - if (!sourceParams || !targetParams) { - return true; - } + function compareTypeParametersIdentical(sourceParams: readonly TypeParameter[] | undefined, targetParams: readonly TypeParameter[] | undefined): boolean { + if (length(sourceParams) !== length(targetParams)) { + return false; + } + if (!sourceParams || !targetParams) { + return true; + } - const mapper = createTypeMapper(targetParams, sourceParams); - for (let i = 0; i < sourceParams.length; i++) { - const source = sourceParams[i]; - const target = targetParams[i]; - if (source === target) continue; - // We instantiate the target type parameter constraints into the source types so we can recognize `` as the same as `` - if (!isTypeIdenticalTo(getConstraintFromTypeParameter(source) || unknownType, instantiateType(getConstraintFromTypeParameter(target) || unknownType, mapper))) return false; - // We don't compare defaults - we just use the type parameter defaults from the first signature that seems to match. - // It might make sense to combine these defaults in the future, but doing so intelligently requires knowing - // if the parameter is used covariantly or contravariantly (so we intersect if it's used like a parameter or union if used like a return type) - // and, since it's just an inference _default_, just picking one arbitrarily works OK. - } + const mapper = createTypeMapper(targetParams, sourceParams); + for (let i = 0; i < sourceParams.length; i++) { + const source = sourceParams[i]; + const target = targetParams[i]; + if (source === target) continue; + // We instantiate the target type parameter constraints into the source types so we can recognize `` as the same as `` + if (!isTypeIdenticalTo(getConstraintFromTypeParameter(source) || unknownType, instantiateType(getConstraintFromTypeParameter(target) || unknownType, mapper))) return false; + // We don't compare defaults - we just use the type parameter defaults from the first signature that seems to match. + // It might make sense to combine these defaults in the future, but doing so intelligently requires knowing + // if the parameter is used covariantly or contravariantly (so we intersect if it's used like a parameter or union if used like a return type) + // and, since it's just an inference _default_, just picking one arbitrarily works OK. + } - return true; + return true; + } + + function combineUnionThisParam(left: Symbol | undefined, right: Symbol | undefined, mapper: TypeMapper | undefined): Symbol | undefined { + if (!left || !right) { + return left || right; } + // A signature `this` type might be a read or a write position... It's very possible that it should be invariant + // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be + // permissive when calling, for now, we'll intersect the `this` types just like we do for param types in union signatures. + const thisType = getIntersectionType([getTypeOfSymbol(left), instantiateType(getTypeOfSymbol(right), mapper)]); + return createSymbolWithType(left, thisType); + } - function combineUnionThisParam(left: Symbol | undefined, right: Symbol | undefined, mapper: TypeMapper | undefined): Symbol | undefined { - if (!left || !right) { - return left || right; - } - // A signature `this` type might be a read or a write position... It's very possible that it should be invariant - // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be - // permissive when calling, for now, we'll intersect the `this` types just like we do for param types in union signatures. - const thisType = getIntersectionType([getTypeOfSymbol(left), instantiateType(getTypeOfSymbol(right), mapper)]); - return createSymbolWithType(left, thisType); - } - - function combineUnionParameters(left: Signature, right: Signature, mapper: TypeMapper | undefined) { - const leftCount = getParameterCount(left); - const rightCount = getParameterCount(right); - const longest = leftCount >= rightCount ? left : right; - const shorter = longest === left ? right : left; - const longestCount = longest === left ? leftCount : rightCount; - const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); - const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); - const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0)); - for (let i = 0; i < longestCount; i++) { - let longestParamType = tryGetTypeAtPosition(longest, i)!; - if (longest === right) { - longestParamType = instantiateType(longestParamType, mapper); - } - let shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; - if (shorter === right) { - shorterParamType = instantiateType(shorterParamType, mapper); - } - const unionParamType = getIntersectionType([longestParamType, shorterParamType]); - const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); - const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); - const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i); - const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i); - - const paramName = leftName === rightName ? leftName : - !leftName ? rightName : - !rightName ? leftName : - undefined; - const paramSymbol = createSymbol( - SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), - paramName || `arg${i}` as __String - ); - paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; - params[i] = paramSymbol; - } - if (needsExtraRestElement) { - const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); - restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); - if (shorter === right) { - restParamSymbol.type = instantiateType(restParamSymbol.type, mapper); - } - params[longestCount] = restParamSymbol; - } - return params; - } - - function combineSignaturesOfUnionMembers(left: Signature, right: Signature): Signature { - const typeParams = left.typeParameters || right.typeParameters; - let paramMapper: TypeMapper | undefined; - if (left.typeParameters && right.typeParameters) { - paramMapper = createTypeMapper(right.typeParameters, left.typeParameters); - // We just use the type parameter defaults from the first signature - } - const declaration = left.declaration; - const params = combineUnionParameters(left, right, paramMapper); - const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter, paramMapper); - const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); - const result = createSignature( - declaration, - typeParams, - thisParam, - params, - /*resolvedReturnType*/ undefined, - /*resolvedTypePredicate*/ undefined, - minArgCount, - (left.flags | right.flags) & SignatureFlags.PropagatingFlags + function combineUnionParameters(left: Signature, right: Signature, mapper: TypeMapper | undefined) { + const leftCount = getParameterCount(left); + const rightCount = getParameterCount(right); + const longest = leftCount >= rightCount ? left : right; + const shorter = longest === left ? right : left; + const longestCount = longest === left ? leftCount : rightCount; + const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); + const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); + const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0)); + for (let i = 0; i < longestCount; i++) { + let longestParamType = tryGetTypeAtPosition(longest, i)!; + if (longest === right) { + longestParamType = instantiateType(longestParamType, mapper); + } + let shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; + if (shorter === right) { + shorterParamType = instantiateType(shorterParamType, mapper); + } + const unionParamType = getIntersectionType([longestParamType, shorterParamType]); + const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); + const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); + const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i); + const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i); + + const paramName = leftName === rightName ? leftName : + !leftName ? rightName : + !rightName ? leftName : + undefined; + const paramSymbol = createSymbol( + SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), + paramName || `arg${i}` as __String ); - result.compositeKind = TypeFlags.Union; - result.compositeSignatures = concatenate(left.compositeKind !== TypeFlags.Intersection && left.compositeSignatures || [left], [right]); - if (paramMapper) { - result.mapper = left.compositeKind !== TypeFlags.Intersection && left.mapper && left.compositeSignatures ? combineTypeMappers(left.mapper, paramMapper) : paramMapper; + paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; + params[i] = paramSymbol; + } + if (needsExtraRestElement) { + const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); + restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); + if (shorter === right) { + restParamSymbol.type = instantiateType(restParamSymbol.type, mapper); } - return result; + params[longestCount] = restParamSymbol; } + return params; + } - function getUnionIndexInfos(types: readonly Type[]): IndexInfo[] { - const sourceInfos = getIndexInfosOfType(types[0]); - if (sourceInfos) { - const result = []; - for (const info of sourceInfos) { - const indexType = info.keyType; - if (every(types, t => !!getIndexInfoOfType(t, indexType))) { - result.push(createIndexInfo(indexType, getUnionType(map(types, t => getIndexTypeOfType(t, indexType)!)), - some(types, t => getIndexInfoOfType(t, indexType)!.isReadonly))); - } + function combineSignaturesOfUnionMembers(left: Signature, right: Signature): Signature { + const typeParams = left.typeParameters || right.typeParameters; + let paramMapper: TypeMapper | undefined; + if (left.typeParameters && right.typeParameters) { + paramMapper = createTypeMapper(right.typeParameters, left.typeParameters); + // We just use the type parameter defaults from the first signature + } + const declaration = left.declaration; + const params = combineUnionParameters(left, right, paramMapper); + const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter, paramMapper); + const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); + const result = createSignature( + declaration, + typeParams, + thisParam, + params, + /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, + minArgCount, + (left.flags | right.flags) & SignatureFlags.PropagatingFlags + ); + result.compositeKind = TypeFlags.Union; + result.compositeSignatures = concatenate(left.compositeKind !== TypeFlags.Intersection && left.compositeSignatures || [left], [right]); + if (paramMapper) { + result.mapper = left.compositeKind !== TypeFlags.Intersection && left.mapper && left.compositeSignatures ? combineTypeMappers(left.mapper, paramMapper) : paramMapper; + } + return result; + } + + function getUnionIndexInfos(types: readonly Type[]): IndexInfo[] { + const sourceInfos = getIndexInfosOfType(types[0]); + if (sourceInfos) { + const result = []; + for (const info of sourceInfos) { + const indexType = info.keyType; + if (every(types, t => !!getIndexInfoOfType(t, indexType))) { + result.push(createIndexInfo(indexType, getUnionType(map(types, t => getIndexTypeOfType(t, indexType)!)), + some(types, t => getIndexInfoOfType(t, indexType)!.isReadonly))); } - return result; } - return emptyArray; + return result; } + return emptyArray; + } - function resolveUnionTypeMembers(type: UnionType) { - // The members and properties collections are empty for union types. To get all properties of a union - // type use getPropertiesOfType (only the language service uses this). - const callSignatures = getUnionSignatures(map(type.types, t => t === globalFunctionType ? [unknownSignature] : getSignaturesOfType(t, SignatureKind.Call))); - const constructSignatures = getUnionSignatures(map(type.types, t => getSignaturesOfType(t, SignatureKind.Construct))); - const indexInfos = getUnionIndexInfos(type.types); - setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, indexInfos); - } + function resolveUnionTypeMembers(type: UnionType) { + // The members and properties collections are empty for union types. To get all properties of a union + // type use getPropertiesOfType (only the language service uses this). + const callSignatures = getUnionSignatures(map(type.types, t => t === globalFunctionType ? [unknownSignature] : getSignaturesOfType(t, SignatureKind.Call))); + const constructSignatures = getUnionSignatures(map(type.types, t => getSignaturesOfType(t, SignatureKind.Construct))); + const indexInfos = getUnionIndexInfos(type.types); + setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, indexInfos); + } - function intersectTypes(type1: Type, type2: Type): Type; - function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined; - function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined { - return !type1 ? type2 : !type2 ? type1 : getIntersectionType([type1, type2]); - } + function intersectTypes(type1: Type, type2: Type): Type; + function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined; + function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined { + return !type1 ? type2 : !type2 ? type1 : getIntersectionType([type1, type2]); + } - function findMixins(types: readonly Type[]): readonly boolean[] { - const constructorTypeCount = countWhere(types, (t) => getSignaturesOfType(t, SignatureKind.Construct).length > 0); - const mixinFlags = map(types, isMixinConstructorType); - if (constructorTypeCount > 0 && constructorTypeCount === countWhere(mixinFlags, (b) => b)) { - const firstMixinIndex = mixinFlags.indexOf(/*searchElement*/ true); - mixinFlags[firstMixinIndex] = false; - } - return mixinFlags; + function findMixins(types: readonly Type[]): readonly boolean[] { + const constructorTypeCount = countWhere(types, (t) => getSignaturesOfType(t, SignatureKind.Construct).length > 0); + const mixinFlags = map(types, isMixinConstructorType); + if (constructorTypeCount > 0 && constructorTypeCount === countWhere(mixinFlags, (b) => b)) { + const firstMixinIndex = mixinFlags.indexOf(/*searchElement*/ true); + mixinFlags[firstMixinIndex] = false; } + return mixinFlags; + } - function includeMixinType(type: Type, types: readonly Type[], mixinFlags: readonly boolean[], index: number): Type { - const mixedTypes: Type[] = []; - for (let i = 0; i < types.length; i++) { - if (i === index) { - mixedTypes.push(type); - } - else if (mixinFlags[i]) { - mixedTypes.push(getReturnTypeOfSignature(getSignaturesOfType(types[i], SignatureKind.Construct)[0])); - } + function includeMixinType(type: Type, types: readonly Type[], mixinFlags: readonly boolean[], index: number): Type { + const mixedTypes: Type[] = []; + for (let i = 0; i < types.length; i++) { + if (i === index) { + mixedTypes.push(type); + } + else if (mixinFlags[i]) { + mixedTypes.push(getReturnTypeOfSignature(getSignaturesOfType(types[i], SignatureKind.Construct)[0])); } - return getIntersectionType(mixedTypes); } + return getIntersectionType(mixedTypes); + } - function resolveIntersectionTypeMembers(type: IntersectionType) { - // The members and properties collections are empty for intersection types. To get all properties of an - // intersection type use getPropertiesOfType (only the language service uses this). - let callSignatures: Signature[] | undefined; - let constructSignatures: Signature[] | undefined; - let indexInfos: IndexInfo[] | undefined; - const types = type.types; - const mixinFlags = findMixins(types); - const mixinCount = countWhere(mixinFlags, (b) => b); - for (let i = 0; i < types.length; i++) { - const t = type.types[i]; - // When an intersection type contains mixin constructor types, the construct signatures from - // those types are discarded and their return types are mixed into the return types of all - // other construct signatures in the intersection type. For example, the intersection type - // '{ new(...args: any[]) => A } & { new(s: string) => B }' has a single construct signature - // 'new(s: string) => A & B'. - if (!mixinFlags[i]) { - let signatures = getSignaturesOfType(t, SignatureKind.Construct); - if (signatures.length && mixinCount > 0) { - signatures = map(signatures, s => { - const clone = cloneSignature(s); - clone.resolvedReturnType = includeMixinType(getReturnTypeOfSignature(s), types, mixinFlags, i); - return clone; - }); - } - constructSignatures = appendSignatures(constructSignatures, signatures); + function resolveIntersectionTypeMembers(type: IntersectionType) { + // The members and properties collections are empty for intersection types. To get all properties of an + // intersection type use getPropertiesOfType (only the language service uses this). + let callSignatures: Signature[] | undefined; + let constructSignatures: Signature[] | undefined; + let indexInfos: IndexInfo[] | undefined; + const types = type.types; + const mixinFlags = findMixins(types); + const mixinCount = countWhere(mixinFlags, (b) => b); + for (let i = 0; i < types.length; i++) { + const t = type.types[i]; + // When an intersection type contains mixin constructor types, the construct signatures from + // those types are discarded and their return types are mixed into the return types of all + // other construct signatures in the intersection type. For example, the intersection type + // '{ new(...args: any[]) => A } & { new(s: string) => B }' has a single construct signature + // 'new(s: string) => A & B'. + if (!mixinFlags[i]) { + let signatures = getSignaturesOfType(t, SignatureKind.Construct); + if (signatures.length && mixinCount > 0) { + signatures = map(signatures, s => { + const clone = cloneSignature(s); + clone.resolvedReturnType = includeMixinType(getReturnTypeOfSignature(s), types, mixinFlags, i); + return clone; + }); } - callSignatures = appendSignatures(callSignatures, getSignaturesOfType(t, SignatureKind.Call)); - indexInfos = reduceLeft(getIndexInfosOfType(t), (infos, newInfo) => appendIndexInfo(infos, newInfo, /*union*/ false), indexInfos); + constructSignatures = appendSignatures(constructSignatures, signatures); } - setStructuredTypeMembers(type, emptySymbols, callSignatures || emptyArray, constructSignatures || emptyArray, indexInfos || emptyArray); + callSignatures = appendSignatures(callSignatures, getSignaturesOfType(t, SignatureKind.Call)); + indexInfos = reduceLeft(getIndexInfosOfType(t), (infos, newInfo) => appendIndexInfo(infos, newInfo, /*union*/ false), indexInfos); } + setStructuredTypeMembers(type, emptySymbols, callSignatures || emptyArray, constructSignatures || emptyArray, indexInfos || emptyArray); + } - function appendSignatures(signatures: Signature[] | undefined, newSignatures: readonly Signature[]) { - for (const sig of newSignatures) { - if (!signatures || every(signatures, s => !compareSignaturesIdentical(s, sig, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, compareTypesIdentical))) { - signatures = append(signatures, sig); - } + function appendSignatures(signatures: Signature[] | undefined, newSignatures: readonly Signature[]) { + for (const sig of newSignatures) { + if (!signatures || every(signatures, s => !compareSignaturesIdentical(s, sig, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, compareTypesIdentical))) { + signatures = append(signatures, sig); } - return signatures; } + return signatures; + } - function appendIndexInfo(indexInfos: IndexInfo[] | undefined, newInfo: IndexInfo, union: boolean) { - if (indexInfos) { - for (let i = 0; i < indexInfos.length; i++) { - const info = indexInfos[i]; - if (info.keyType === newInfo.keyType) { - indexInfos[i] = createIndexInfo(info.keyType, - union ? getUnionType([info.type, newInfo.type]) : getIntersectionType([info.type, newInfo.type]), - union ? info.isReadonly || newInfo.isReadonly : info.isReadonly && newInfo.isReadonly); - return indexInfos; - } + function appendIndexInfo(indexInfos: IndexInfo[] | undefined, newInfo: IndexInfo, union: boolean) { + if (indexInfos) { + for (let i = 0; i < indexInfos.length; i++) { + const info = indexInfos[i]; + if (info.keyType === newInfo.keyType) { + indexInfos[i] = createIndexInfo(info.keyType, + union ? getUnionType([info.type, newInfo.type]) : getIntersectionType([info.type, newInfo.type]), + union ? info.isReadonly || newInfo.isReadonly : info.isReadonly && newInfo.isReadonly); + return indexInfos; } } - return append(indexInfos, newInfo); } + return append(indexInfos, newInfo); + } - /** - * Converts an AnonymousType to a ResolvedType. - */ - function resolveAnonymousTypeMembers(type: AnonymousType) { - const symbol = getMergedSymbol(type.symbol); - if (type.target) { - setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); - const members = createInstantiatedSymbolTable(getPropertiesOfObjectType(type.target), type.mapper!, /*mappingThisOnly*/ false); - const callSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Call), type.mapper!); - const constructSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Construct), type.mapper!); - const indexInfos = instantiateIndexInfos(getIndexInfosOfType(type.target), type.mapper!); - setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); - } - else if (symbol.flags & SymbolFlags.TypeLiteral) { - setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); - const members = getMembersOfSymbol(symbol); - const callSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); - const constructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); - const indexInfos = getIndexInfosOfSymbol(symbol); - setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); + /** + * Converts an AnonymousType to a ResolvedType. + */ + function resolveAnonymousTypeMembers(type: AnonymousType) { + const symbol = getMergedSymbol(type.symbol); + if (type.target) { + setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); + const members = createInstantiatedSymbolTable(getPropertiesOfObjectType(type.target), type.mapper!, /*mappingThisOnly*/ false); + const callSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Call), type.mapper!); + const constructSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Construct), type.mapper!); + const indexInfos = instantiateIndexInfos(getIndexInfosOfType(type.target), type.mapper!); + setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); + } + else if (symbol.flags & SymbolFlags.TypeLiteral) { + setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); + const members = getMembersOfSymbol(symbol); + const callSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); + const constructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); + const indexInfos = getIndexInfosOfSymbol(symbol); + setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); + } + else { + // Combinations of function, class, enum and module + let members = emptySymbols; + let indexInfos: IndexInfo[] | undefined; + if (symbol.exports) { + members = getExportsOfSymbol(symbol); + if (symbol === globalThisSymbol) { + const varsOnly = new Map() as SymbolTable; + members.forEach(p => { + if (!(p.flags & SymbolFlags.BlockScoped)) { + varsOnly.set(p.escapedName, p); + } + }); + members = varsOnly; + } } - else { - // Combinations of function, class, enum and module - let members = emptySymbols; - let indexInfos: IndexInfo[] | undefined; - if (symbol.exports) { - members = getExportsOfSymbol(symbol); - if (symbol === globalThisSymbol) { - const varsOnly = new Map() as SymbolTable; - members.forEach(p => { - if (!(p.flags & SymbolFlags.BlockScoped)) { - varsOnly.set(p.escapedName, p); - } - }); - members = varsOnly; - } + let baseConstructorIndexInfo: IndexInfo | undefined; + setStructuredTypeMembers(type, members, emptyArray, emptyArray, emptyArray); + if (symbol.flags & SymbolFlags.Class) { + const classType = getDeclaredTypeOfClassOrInterface(symbol); + const baseConstructorType = getBaseConstructorTypeOfClass(classType); + if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) { + members = createSymbolTable(getNamedOrIndexSignatureMembers(members)); + addInheritedMembers(members, getPropertiesOfType(baseConstructorType)); } - let baseConstructorIndexInfo: IndexInfo | undefined; - setStructuredTypeMembers(type, members, emptyArray, emptyArray, emptyArray); - if (symbol.flags & SymbolFlags.Class) { - const classType = getDeclaredTypeOfClassOrInterface(symbol); - const baseConstructorType = getBaseConstructorTypeOfClass(classType); - if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) { - members = createSymbolTable(getNamedOrIndexSignatureMembers(members)); - addInheritedMembers(members, getPropertiesOfType(baseConstructorType)); - } - else if (baseConstructorType === anyType) { - baseConstructorIndexInfo = createIndexInfo(stringType, anyType, /*isReadonly*/ false); - } + else if (baseConstructorType === anyType) { + baseConstructorIndexInfo = createIndexInfo(stringType, anyType, /*isReadonly*/ false); } + } - const indexSymbol = getIndexSymbolFromSymbolTable(members); - if (indexSymbol) { - indexInfos = getIndexInfosOfIndexSymbol(indexSymbol); + const indexSymbol = getIndexSymbolFromSymbolTable(members); + if (indexSymbol) { + indexInfos = getIndexInfosOfIndexSymbol(indexSymbol); + } + else { + if (baseConstructorIndexInfo) { + indexInfos = append(indexInfos, baseConstructorIndexInfo); } - else { - if (baseConstructorIndexInfo) { - indexInfos = append(indexInfos, baseConstructorIndexInfo); - } - if (symbol.flags & SymbolFlags.Enum && (getDeclaredTypeOfSymbol(symbol).flags & TypeFlags.Enum || - some(type.properties, prop => !!(getTypeOfSymbol(prop).flags & TypeFlags.NumberLike)))) { - indexInfos = append(indexInfos, enumNumberIndexInfo); - } + if (symbol.flags & SymbolFlags.Enum && (getDeclaredTypeOfSymbol(symbol).flags & TypeFlags.Enum || + some(type.properties, prop => !!(getTypeOfSymbol(prop).flags & TypeFlags.NumberLike)))) { + indexInfos = append(indexInfos, enumNumberIndexInfo); } - setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos || emptyArray); - // We resolve the members before computing the signatures because a signature may use - // typeof with a qualified name expression that circularly references the type we are - // in the process of resolving (see issue #6072). The temporarily empty signature list - // will never be observed because a qualified name can't reference signatures. - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { - type.callSignatures = getSignaturesOfSymbol(symbol); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos || emptyArray); + // We resolve the members before computing the signatures because a signature may use + // typeof with a qualified name expression that circularly references the type we are + // in the process of resolving (see issue #6072). The temporarily empty signature list + // will never be observed because a qualified name can't reference signatures. + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { + type.callSignatures = getSignaturesOfSymbol(symbol); + } + // And likewise for construct signatures for classes + if (symbol.flags & SymbolFlags.Class) { + const classType = getDeclaredTypeOfClassOrInterface(symbol); + let constructSignatures = symbol.members ? getSignaturesOfSymbol(symbol.members.get(InternalSymbolName.Constructor)) : emptyArray; + if (symbol.flags & SymbolFlags.Function) { + constructSignatures = addRange(constructSignatures.slice(), mapDefined( + type.callSignatures, + sig => isJSConstructor(sig.declaration) ? + createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, classType, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags) : + undefined)); } - // And likewise for construct signatures for classes - if (symbol.flags & SymbolFlags.Class) { - const classType = getDeclaredTypeOfClassOrInterface(symbol); - let constructSignatures = symbol.members ? getSignaturesOfSymbol(symbol.members.get(InternalSymbolName.Constructor)) : emptyArray; - if (symbol.flags & SymbolFlags.Function) { - constructSignatures = addRange(constructSignatures.slice(), mapDefined( - type.callSignatures, - sig => isJSConstructor(sig.declaration) ? - createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, classType, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags) : - undefined)); - } - if (!constructSignatures.length) { - constructSignatures = getDefaultConstructSignatures(classType); - } - type.constructSignatures = constructSignatures; + if (!constructSignatures.length) { + constructSignatures = getDefaultConstructSignatures(classType); } + type.constructSignatures = constructSignatures; } } + } - type ReplaceableIndexedAccessType = IndexedAccessType & { objectType: TypeParameter, indexType: TypeParameter }; - function replaceIndexedAccess(instantiable: Type, type: ReplaceableIndexedAccessType, replacement: Type) { - // map type.indexType to 0 - // map type.objectType to `[TReplacement]` - // thus making the indexed access `[TReplacement][0]` or `TReplacement` - return instantiateType(instantiable, createTypeMapper([type.indexType, type.objectType], [getNumberLiteralType(0), createTupleType([replacement])])); - } + type ReplaceableIndexedAccessType = IndexedAccessType & { objectType: TypeParameter, indexType: TypeParameter }; + function replaceIndexedAccess(instantiable: Type, type: ReplaceableIndexedAccessType, replacement: Type) { + // map type.indexType to 0 + // map type.objectType to `[TReplacement]` + // thus making the indexed access `[TReplacement][0]` or `TReplacement` + return instantiateType(instantiable, createTypeMapper([type.indexType, type.objectType], [getNumberLiteralType(0), createTupleType([replacement])])); + } - function resolveReverseMappedTypeMembers(type: ReverseMappedType) { - const indexInfo = getIndexInfoOfType(type.source, stringType); - const modifiers = getMappedTypeModifiers(type.mappedType); - const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true; - const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional; - const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly)] : emptyArray; - const members = createSymbolTable(); - for (const prop of getPropertiesOfType(type.source)) { - const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0); - const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol; - inferredProp.declarations = prop.declarations; - inferredProp.nameType = getSymbolLinks(prop).nameType; - inferredProp.propertyType = getTypeOfSymbol(prop); - if (type.constraintType.type.flags & TypeFlags.IndexedAccess - && (type.constraintType.type as IndexedAccessType).objectType.flags & TypeFlags.TypeParameter - && (type.constraintType.type as IndexedAccessType).indexType.flags & TypeFlags.TypeParameter) { - // A reverse mapping of `{[K in keyof T[K_1]]: T[K_1]}` is the same as that of `{[K in keyof T]: T}`, since all we care about is - // inferring to the "type parameter" (or indexed access) shared by the constraint and template. So, to reduce the number of - // type identities produced, we simplify such indexed access occurences - const newTypeParam = (type.constraintType.type as IndexedAccessType).objectType; - const newMappedType = replaceIndexedAccess(type.mappedType, type.constraintType.type as ReplaceableIndexedAccessType, newTypeParam); - inferredProp.mappedType = newMappedType as MappedType; - inferredProp.constraintType = getIndexType(newTypeParam) as IndexType; - } - else { - inferredProp.mappedType = type.mappedType; - inferredProp.constraintType = type.constraintType; - } - members.set(prop.escapedName, inferredProp); + function resolveReverseMappedTypeMembers(type: ReverseMappedType) { + const indexInfo = getIndexInfoOfType(type.source, stringType); + const modifiers = getMappedTypeModifiers(type.mappedType); + const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true; + const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional; + const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly)] : emptyArray; + const members = createSymbolTable(); + for (const prop of getPropertiesOfType(type.source)) { + const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0); + const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol; + inferredProp.declarations = prop.declarations; + inferredProp.nameType = getSymbolLinks(prop).nameType; + inferredProp.propertyType = getTypeOfSymbol(prop); + if (type.constraintType.type.flags & TypeFlags.IndexedAccess + && (type.constraintType.type as IndexedAccessType).objectType.flags & TypeFlags.TypeParameter + && (type.constraintType.type as IndexedAccessType).indexType.flags & TypeFlags.TypeParameter) { + // A reverse mapping of `{[K in keyof T[K_1]]: T[K_1]}` is the same as that of `{[K in keyof T]: T}`, since all we care about is + // inferring to the "type parameter" (or indexed access) shared by the constraint and template. So, to reduce the number of + // type identities produced, we simplify such indexed access occurences + const newTypeParam = (type.constraintType.type as IndexedAccessType).objectType; + const newMappedType = replaceIndexedAccess(type.mappedType, type.constraintType.type as ReplaceableIndexedAccessType, newTypeParam); + inferredProp.mappedType = newMappedType as MappedType; + inferredProp.constraintType = getIndexType(newTypeParam) as IndexType; + } + else { + inferredProp.mappedType = type.mappedType; + inferredProp.constraintType = type.constraintType; } - setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos); + members.set(prop.escapedName, inferredProp); } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos); + } - // Return the lower bound of the key type in a mapped type. Intuitively, the lower - // bound includes those keys that are known to always be present, for example because - // because of constraints on type parameters (e.g. 'keyof T' for a constrained T). - function getLowerBoundOfKeyType(type: Type): Type { - if (type.flags & TypeFlags.Index) { - const t = getApparentType((type as IndexType).type); - return isGenericTupleType(t) ? getKnownKeysOfTupleType(t) : getIndexType(t); - } - if (type.flags & TypeFlags.Conditional) { - if ((type as ConditionalType).root.isDistributive) { - const checkType = (type as ConditionalType).checkType; - const constraint = getLowerBoundOfKeyType(checkType); - if (constraint !== checkType) { - return getConditionalTypeInstantiation(type as ConditionalType, prependTypeMapping((type as ConditionalType).root.checkType, constraint, (type as ConditionalType).mapper)); - } + // Return the lower bound of the key type in a mapped type. Intuitively, the lower + // bound includes those keys that are known to always be present, for example because + // because of constraints on type parameters (e.g. 'keyof T' for a constrained T). + function getLowerBoundOfKeyType(type: Type): Type { + if (type.flags & TypeFlags.Index) { + const t = getApparentType((type as IndexType).type); + return isGenericTupleType(t) ? getKnownKeysOfTupleType(t) : getIndexType(t); + } + if (type.flags & TypeFlags.Conditional) { + if ((type as ConditionalType).root.isDistributive) { + const checkType = (type as ConditionalType).checkType; + const constraint = getLowerBoundOfKeyType(checkType); + if (constraint !== checkType) { + return getConditionalTypeInstantiation(type as ConditionalType, prependTypeMapping((type as ConditionalType).root.checkType, constraint, (type as ConditionalType).mapper)); } - return type; - } - if (type.flags & TypeFlags.Union) { - return mapType(type as UnionType, getLowerBoundOfKeyType); - } - if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(sameMap((type as UnionType).types, getLowerBoundOfKeyType)); } return type; } - - function getIsLateCheckFlag(s: Symbol): CheckFlags { - return getCheckFlags(s) & CheckFlags.Late; + if (type.flags & TypeFlags.Union) { + return mapType(type as UnionType, getLowerBoundOfKeyType); + } + if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(sameMap((type as UnionType).types, getLowerBoundOfKeyType)); } + return type; + } - function forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(type: Type, include: TypeFlags, stringsOnly: boolean, cb: (keyType: Type) => void) { - for (const prop of getPropertiesOfType(type)) { - cb(getLiteralTypeFromProperty(prop, include)); - } - if (type.flags & TypeFlags.Any) { - cb(stringType); - } - else { - for (const info of getIndexInfosOfType(type)) { - if (!stringsOnly || info.keyType.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) { - cb(info.keyType); - } + function getIsLateCheckFlag(s: Symbol): CheckFlags { + return getCheckFlags(s) & CheckFlags.Late; + } + + function forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(type: Type, include: TypeFlags, stringsOnly: boolean, cb: (keyType: Type) => void) { + for (const prop of getPropertiesOfType(type)) { + cb(getLiteralTypeFromProperty(prop, include)); + } + if (type.flags & TypeFlags.Any) { + cb(stringType); + } + else { + for (const info of getIndexInfosOfType(type)) { + if (!stringsOnly || info.keyType.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) { + cb(info.keyType); } } } + } - /** Resolve the members of a mapped type { [P in K]: T } */ - function resolveMappedTypeMembers(type: MappedType) { - const members: SymbolTable = createSymbolTable(); - let indexInfos: IndexInfo[] | undefined; - // Resolve upfront such that recursive references see an empty object type. - setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); - // In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type, - // and T as the template type. - const typeParameter = getTypeParameterFromMappedType(type); - const constraintType = getConstraintTypeFromMappedType(type); - const nameType = getNameTypeFromMappedType(type.target as MappedType || type); - const templateType = getTemplateTypeFromMappedType(type.target as MappedType || type); - const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' - const templateModifiers = getMappedTypeModifiers(type); - const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique; - if (isMappedTypeWithKeyofConstraintDeclaration(type)) { - // We have a { [P in keyof T]: X } - forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, include, keyofStringsOnly, addMemberForKeyType); - } - else { - forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); + /** Resolve the members of a mapped type { [P in K]: T } */ + function resolveMappedTypeMembers(type: MappedType) { + const members: SymbolTable = createSymbolTable(); + let indexInfos: IndexInfo[] | undefined; + // Resolve upfront such that recursive references see an empty object type. + setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); + // In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type, + // and T as the template type. + const typeParameter = getTypeParameterFromMappedType(type); + const constraintType = getConstraintTypeFromMappedType(type); + const nameType = getNameTypeFromMappedType(type.target as MappedType || type); + const templateType = getTemplateTypeFromMappedType(type.target as MappedType || type); + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' + const templateModifiers = getMappedTypeModifiers(type); + const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique; + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // We have a { [P in keyof T]: X } + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, include, keyofStringsOnly, addMemberForKeyType); + } + else { + forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos || emptyArray); + + function addMemberForKeyType(keyType: Type) { + const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType; + forEachType(propNameType, t => addMemberForKeyTypeWorker(keyType, t)); + } + + function addMemberForKeyTypeWorker(keyType: Type, propNameType: Type) { + // If the current iteration type constituent is a string literal type, create a property. + // Otherwise, for type string create a string index signature. + if (isTypeUsableAsPropertyName(propNameType)) { + const propName = getPropertyNameFromType(propNameType); + // String enum members from separate enums with identical values + // are distinct types with the same property name. Make the resulting + // property symbol's name type be the union of those enum member types. + const existingProp = members.get(propName) as MappedSymbol | undefined; + if (existingProp) { + existingProp.nameType = getUnionType([existingProp.nameType!, propNameType]); + existingProp.keyType = getUnionType([existingProp.keyType, keyType]); + } + else { + const modifiersProp = isTypeUsableAsPropertyName(keyType) ? getPropertyOfType(modifiersType, getPropertyNameFromType(keyType)) : undefined; + const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional || + !(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional); + const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly || + !(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersProp && isReadonlySymbol(modifiersProp)); + const stripOptional = strictNullChecks && !isOptional && modifiersProp && modifiersProp.flags & SymbolFlags.Optional; + const lateFlag: CheckFlags = modifiersProp ? getIsLateCheckFlag(modifiersProp) : 0; + const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName, + lateFlag | CheckFlags.Mapped | (isReadonly ? CheckFlags.Readonly : 0) | (stripOptional ? CheckFlags.StripOptional : 0)) as MappedSymbol; + prop.mappedType = type; + prop.nameType = propNameType; + prop.keyType = keyType; + if (modifiersProp) { + prop.syntheticOrigin = modifiersProp; + // If the mapped type has an `as XXX` clause, the property name likely won't match the declaration name and + // multiple properties may map to the same name. Thus, we attach no declarations to the symbol. + prop.declarations = nameType ? undefined : modifiersProp.declarations; + } + members.set(propName, prop); + } + } + else if (isValidIndexKeyType(propNameType) || propNameType.flags & (TypeFlags.Any | TypeFlags.Enum)) { + const indexKeyType = propNameType.flags & (TypeFlags.Any | TypeFlags.String) ? stringType : + propNameType.flags & (TypeFlags.Number | TypeFlags.Enum) ? numberType : + propNameType; + const propType = instantiateType(templateType, appendTypeMapping(type.mapper, typeParameter, keyType)); + const indexInfo = createIndexInfo(indexKeyType, propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); + indexInfos = appendIndexInfo(indexInfos, indexInfo, /*union*/ true); } - setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos || emptyArray); + } + } - function addMemberForKeyType(keyType: Type) { - const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType; - forEachType(propNameType, t => addMemberForKeyTypeWorker(keyType, t)); + function getTypeOfMappedSymbol(symbol: MappedSymbol) { + if (!symbol.type) { + const mappedType = symbol.mappedType; + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + mappedType.containsError = true; + return errorType; } - - function addMemberForKeyTypeWorker(keyType: Type, propNameType: Type) { - // If the current iteration type constituent is a string literal type, create a property. - // Otherwise, for type string create a string index signature. - if (isTypeUsableAsPropertyName(propNameType)) { - const propName = getPropertyNameFromType(propNameType); - // String enum members from separate enums with identical values - // are distinct types with the same property name. Make the resulting - // property symbol's name type be the union of those enum member types. - const existingProp = members.get(propName) as MappedSymbol | undefined; - if (existingProp) { - existingProp.nameType = getUnionType([existingProp.nameType!, propNameType]); - existingProp.keyType = getUnionType([existingProp.keyType, keyType]); - } - else { - const modifiersProp = isTypeUsableAsPropertyName(keyType) ? getPropertyOfType(modifiersType, getPropertyNameFromType(keyType)) : undefined; - const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional || - !(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional); - const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly || - !(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersProp && isReadonlySymbol(modifiersProp)); - const stripOptional = strictNullChecks && !isOptional && modifiersProp && modifiersProp.flags & SymbolFlags.Optional; - const lateFlag: CheckFlags = modifiersProp ? getIsLateCheckFlag(modifiersProp) : 0; - const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName, - lateFlag | CheckFlags.Mapped | (isReadonly ? CheckFlags.Readonly : 0) | (stripOptional ? CheckFlags.StripOptional : 0)) as MappedSymbol; - prop.mappedType = type; - prop.nameType = propNameType; - prop.keyType = keyType; - if (modifiersProp) { - prop.syntheticOrigin = modifiersProp; - // If the mapped type has an `as XXX` clause, the property name likely won't match the declaration name and - // multiple properties may map to the same name. Thus, we attach no declarations to the symbol. - prop.declarations = nameType ? undefined : modifiersProp.declarations; - } - members.set(propName, prop); - } - } - else if (isValidIndexKeyType(propNameType) || propNameType.flags & (TypeFlags.Any | TypeFlags.Enum)) { - const indexKeyType = propNameType.flags & (TypeFlags.Any | TypeFlags.String) ? stringType : - propNameType.flags & (TypeFlags.Number | TypeFlags.Enum) ? numberType : - propNameType; - const propType = instantiateType(templateType, appendTypeMapping(type.mapper, typeParameter, keyType)); - const indexInfo = createIndexInfo(indexKeyType, propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); - indexInfos = appendIndexInfo(indexInfos, indexInfo, /*union*/ true); - } - } - } - - function getTypeOfMappedSymbol(symbol: MappedSymbol) { - if (!symbol.type) { - const mappedType = symbol.mappedType; - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - mappedType.containsError = true; - return errorType; - } - const templateType = getTemplateTypeFromMappedType(mappedType.target as MappedType || mappedType); - const mapper = appendTypeMapping(mappedType.mapper, getTypeParameterFromMappedType(mappedType), symbol.keyType); - const propType = instantiateType(templateType, mapper); - // When creating an optional property in strictNullChecks mode, if 'undefined' isn't assignable to the - // type, we include 'undefined' in the type. Similarly, when creating a non-optional property in strictNullChecks - // mode, if the underlying property is optional we remove 'undefined' from the type. - let type = strictNullChecks && symbol.flags & SymbolFlags.Optional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType, /*isProperty*/ true) : - symbol.checkFlags & CheckFlags.StripOptional ? removeMissingOrUndefinedType(propType) : - propType; - if (!popTypeResolution()) { - error(currentNode, Diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, symbolToString(symbol), typeToString(mappedType)); - type = errorType; - } - symbol.type = type; + const templateType = getTemplateTypeFromMappedType(mappedType.target as MappedType || mappedType); + const mapper = appendTypeMapping(mappedType.mapper, getTypeParameterFromMappedType(mappedType), symbol.keyType); + const propType = instantiateType(templateType, mapper); + // When creating an optional property in strictNullChecks mode, if 'undefined' isn't assignable to the + // type, we include 'undefined' in the type. Similarly, when creating a non-optional property in strictNullChecks + // mode, if the underlying property is optional we remove 'undefined' from the type. + let type = strictNullChecks && symbol.flags & SymbolFlags.Optional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType, /*isProperty*/ true) : + symbol.checkFlags & CheckFlags.StripOptional ? removeMissingOrUndefinedType(propType) : + propType; + if (!popTypeResolution()) { + error(currentNode, Diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, symbolToString(symbol), typeToString(mappedType)); + type = errorType; } - return symbol.type; + symbol.type = type; } + return symbol.type; + } - function getTypeParameterFromMappedType(type: MappedType) { - return type.typeParameter || - (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(type.declaration.typeParameter))); - } + function getTypeParameterFromMappedType(type: MappedType) { + return type.typeParameter || + (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(type.declaration.typeParameter))); + } - function getConstraintTypeFromMappedType(type: MappedType) { - return type.constraintType || - (type.constraintType = getConstraintOfTypeParameter(getTypeParameterFromMappedType(type)) || errorType); - } + function getConstraintTypeFromMappedType(type: MappedType) { + return type.constraintType || + (type.constraintType = getConstraintOfTypeParameter(getTypeParameterFromMappedType(type)) || errorType); + } - function getNameTypeFromMappedType(type: MappedType) { - return type.declaration.nameType ? - type.nameType || (type.nameType = instantiateType(getTypeFromTypeNode(type.declaration.nameType), type.mapper)) : - undefined; - } + function getNameTypeFromMappedType(type: MappedType) { + return type.declaration.nameType ? + type.nameType || (type.nameType = instantiateType(getTypeFromTypeNode(type.declaration.nameType), type.mapper)) : + undefined; + } - function getTemplateTypeFromMappedType(type: MappedType) { - return type.templateType || - (type.templateType = type.declaration.type ? - instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), /*isProperty*/ true, !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), type.mapper) : - errorType); - } + function getTemplateTypeFromMappedType(type: MappedType) { + return type.templateType || + (type.templateType = type.declaration.type ? + instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), /*isProperty*/ true, !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), type.mapper) : + errorType); + } - function getConstraintDeclarationForMappedType(type: MappedType) { - return getEffectiveConstraintOfTypeParameter(type.declaration.typeParameter); - } + function getConstraintDeclarationForMappedType(type: MappedType) { + return getEffectiveConstraintOfTypeParameter(type.declaration.typeParameter); + } - function isMappedTypeWithKeyofConstraintDeclaration(type: MappedType) { - const constraintDeclaration = getConstraintDeclarationForMappedType(type)!; // TODO: GH#18217 - return constraintDeclaration.kind === SyntaxKind.TypeOperator && - (constraintDeclaration as TypeOperatorNode).operator === SyntaxKind.KeyOfKeyword; - } + function isMappedTypeWithKeyofConstraintDeclaration(type: MappedType) { + const constraintDeclaration = getConstraintDeclarationForMappedType(type)!; // TODO: GH#18217 + return constraintDeclaration.kind === SyntaxKind.TypeOperator && + (constraintDeclaration as TypeOperatorNode).operator === SyntaxKind.KeyOfKeyword; + } - function getModifiersTypeFromMappedType(type: MappedType) { - if (!type.modifiersType) { - if (isMappedTypeWithKeyofConstraintDeclaration(type)) { - // If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check - // AST nodes here because, when T is a non-generic type, the logic below eagerly resolves - // 'keyof T' to a literal union type and we can't recover T from that type. - type.modifiersType = instantiateType(getTypeFromTypeNode((getConstraintDeclarationForMappedType(type) as TypeOperatorNode).type), type.mapper); - } - else { - // Otherwise, get the declared constraint type, and if the constraint type is a type parameter, - // get the constraint of that type parameter. If the resulting type is an indexed type 'keyof T', - // the modifiers type is T. Otherwise, the modifiers type is unknown. - const declaredType = getTypeFromMappedTypeNode(type.declaration) as MappedType; - const constraint = getConstraintTypeFromMappedType(declaredType); - const extendedConstraint = constraint && constraint.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(constraint as TypeParameter) : constraint; - type.modifiersType = extendedConstraint && extendedConstraint.flags & TypeFlags.Index ? instantiateType((extendedConstraint as IndexType).type, type.mapper) : unknownType; - } + function getModifiersTypeFromMappedType(type: MappedType) { + if (!type.modifiersType) { + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check + // AST nodes here because, when T is a non-generic type, the logic below eagerly resolves + // 'keyof T' to a literal union type and we can't recover T from that type. + type.modifiersType = instantiateType(getTypeFromTypeNode((getConstraintDeclarationForMappedType(type) as TypeOperatorNode).type), type.mapper); + } + else { + // Otherwise, get the declared constraint type, and if the constraint type is a type parameter, + // get the constraint of that type parameter. If the resulting type is an indexed type 'keyof T', + // the modifiers type is T. Otherwise, the modifiers type is unknown. + const declaredType = getTypeFromMappedTypeNode(type.declaration) as MappedType; + const constraint = getConstraintTypeFromMappedType(declaredType); + const extendedConstraint = constraint && constraint.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(constraint as TypeParameter) : constraint; + type.modifiersType = extendedConstraint && extendedConstraint.flags & TypeFlags.Index ? instantiateType((extendedConstraint as IndexType).type, type.mapper) : unknownType; } - return type.modifiersType; } + return type.modifiersType; + } - function getMappedTypeModifiers(type: MappedType): MappedTypeModifiers { - const declaration = type.declaration; - return (declaration.readonlyToken ? declaration.readonlyToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeReadonly : MappedTypeModifiers.IncludeReadonly : 0) | - (declaration.questionToken ? declaration.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0); - } + function getMappedTypeModifiers(type: MappedType): MappedTypeModifiers { + const declaration = type.declaration; + return (declaration.readonlyToken ? declaration.readonlyToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeReadonly : MappedTypeModifiers.IncludeReadonly : 0) | + (declaration.questionToken ? declaration.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0); + } - function getMappedTypeOptionality(type: MappedType): number { - const modifiers = getMappedTypeModifiers(type); - return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; - } + function getMappedTypeOptionality(type: MappedType): number { + const modifiers = getMappedTypeModifiers(type); + return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; + } - function getCombinedMappedTypeOptionality(type: MappedType): number { - const optionality = getMappedTypeOptionality(type); - const modifiersType = getModifiersTypeFromMappedType(type); - return optionality || (isGenericMappedType(modifiersType) ? getMappedTypeOptionality(modifiersType) : 0); - } + function getCombinedMappedTypeOptionality(type: MappedType): number { + const optionality = getMappedTypeOptionality(type); + const modifiersType = getModifiersTypeFromMappedType(type); + return optionality || (isGenericMappedType(modifiersType) ? getMappedTypeOptionality(modifiersType) : 0); + } - function isPartialMappedType(type: Type) { - return !!(getObjectFlags(type) & ObjectFlags.Mapped && getMappedTypeModifiers(type as MappedType) & MappedTypeModifiers.IncludeOptional); - } + function isPartialMappedType(type: Type) { + return !!(getObjectFlags(type) & ObjectFlags.Mapped && getMappedTypeModifiers(type as MappedType) & MappedTypeModifiers.IncludeOptional); + } - function isGenericMappedType(type: Type): type is MappedType { - return !!(getObjectFlags(type) & ObjectFlags.Mapped) && isGenericIndexType(getConstraintTypeFromMappedType(type as MappedType)); - } + function isGenericMappedType(type: Type): type is MappedType { + return !!(getObjectFlags(type) & ObjectFlags.Mapped) && isGenericIndexType(getConstraintTypeFromMappedType(type as MappedType)); + } - function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { - if (!(type as ResolvedType).members) { - if (type.flags & TypeFlags.Object) { - if ((type as ObjectType).objectFlags & ObjectFlags.Reference) { - resolveTypeReferenceMembers(type as TypeReference); - } - else if ((type as ObjectType).objectFlags & ObjectFlags.ClassOrInterface) { - resolveClassOrInterfaceMembers(type as InterfaceType); - } - else if ((type as ReverseMappedType).objectFlags & ObjectFlags.ReverseMapped) { - resolveReverseMappedTypeMembers(type as ReverseMappedType); - } - else if ((type as ObjectType).objectFlags & ObjectFlags.Anonymous) { - resolveAnonymousTypeMembers(type as AnonymousType); - } - else if ((type as MappedType).objectFlags & ObjectFlags.Mapped) { - resolveMappedTypeMembers(type as MappedType); - } + function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { + if (!(type as ResolvedType).members) { + if (type.flags & TypeFlags.Object) { + if ((type as ObjectType).objectFlags & ObjectFlags.Reference) { + resolveTypeReferenceMembers(type as TypeReference); + } + else if ((type as ObjectType).objectFlags & ObjectFlags.ClassOrInterface) { + resolveClassOrInterfaceMembers(type as InterfaceType); } - else if (type.flags & TypeFlags.Union) { - resolveUnionTypeMembers(type as UnionType); + else if ((type as ReverseMappedType).objectFlags & ObjectFlags.ReverseMapped) { + resolveReverseMappedTypeMembers(type as ReverseMappedType); } - else if (type.flags & TypeFlags.Intersection) { - resolveIntersectionTypeMembers(type as IntersectionType); + else if ((type as ObjectType).objectFlags & ObjectFlags.Anonymous) { + resolveAnonymousTypeMembers(type as AnonymousType); } + else if ((type as MappedType).objectFlags & ObjectFlags.Mapped) { + resolveMappedTypeMembers(type as MappedType); + } + } + else if (type.flags & TypeFlags.Union) { + resolveUnionTypeMembers(type as UnionType); + } + else if (type.flags & TypeFlags.Intersection) { + resolveIntersectionTypeMembers(type as IntersectionType); } - return type as ResolvedType; } + return type as ResolvedType; + } - /** Return properties of an object type or an empty array for other types */ - function getPropertiesOfObjectType(type: Type): Symbol[] { - if (type.flags & TypeFlags.Object) { - return resolveStructuredTypeMembers(type as ObjectType).properties; - } - return emptyArray; + /** Return properties of an object type or an empty array for other types */ + function getPropertiesOfObjectType(type: Type): Symbol[] { + if (type.flags & TypeFlags.Object) { + return resolveStructuredTypeMembers(type as ObjectType).properties; } + return emptyArray; + } - /** If the given type is an object type and that type has a property by the given name, - * return the symbol for that property. Otherwise return undefined. - */ - function getPropertyOfObjectType(type: Type, name: __String): Symbol | undefined { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - const symbol = resolved.members.get(name); - if (symbol && symbolIsValue(symbol)) { - return symbol; - } + /** If the given type is an object type and that type has a property by the given name, + * return the symbol for that property. Otherwise return undefined. + */ + function getPropertyOfObjectType(type: Type, name: __String): Symbol | undefined { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + const symbol = resolved.members.get(name); + if (symbol && symbolIsValue(symbol)) { + return symbol; } } + } - function getPropertiesOfUnionOrIntersectionType(type: UnionOrIntersectionType): Symbol[] { - if (!type.resolvedProperties) { - const members = createSymbolTable(); - for (const current of type.types) { - for (const prop of getPropertiesOfType(current)) { - if (!members.has(prop.escapedName)) { - const combinedProp = getPropertyOfUnionOrIntersectionType(type, prop.escapedName); - if (combinedProp) { - members.set(prop.escapedName, combinedProp); - } + function getPropertiesOfUnionOrIntersectionType(type: UnionOrIntersectionType): Symbol[] { + if (!type.resolvedProperties) { + const members = createSymbolTable(); + for (const current of type.types) { + for (const prop of getPropertiesOfType(current)) { + if (!members.has(prop.escapedName)) { + const combinedProp = getPropertyOfUnionOrIntersectionType(type, prop.escapedName); + if (combinedProp) { + members.set(prop.escapedName, combinedProp); } } - // The properties of a union type are those that are present in all constituent types, so - // we only need to check the properties of the first type without index signature - if (type.flags & TypeFlags.Union && getIndexInfosOfType(current).length === 0) { - break; - } } - type.resolvedProperties = getNamedMembers(members); + // The properties of a union type are those that are present in all constituent types, so + // we only need to check the properties of the first type without index signature + if (type.flags & TypeFlags.Union && getIndexInfosOfType(current).length === 0) { + break; + } } - return type.resolvedProperties; - } - - function getPropertiesOfType(type: Type): Symbol[] { - type = getReducedApparentType(type); - return type.flags & TypeFlags.UnionOrIntersection ? - getPropertiesOfUnionOrIntersectionType(type as UnionType) : - getPropertiesOfObjectType(type); + type.resolvedProperties = getNamedMembers(members); } + return type.resolvedProperties; + } - function forEachPropertyOfType(type: Type, action: (symbol: Symbol, escapedName: __String) => void): void { - type = getReducedApparentType(type); - if (type.flags & TypeFlags.StructuredType) { - resolveStructuredTypeMembers(type as StructuredType).members.forEach((symbol, escapedName) => { - if (isNamedMember(symbol, escapedName)) { - action(symbol, escapedName); - } - }); - } - } + function getPropertiesOfType(type: Type): Symbol[] { + type = getReducedApparentType(type); + return type.flags & TypeFlags.UnionOrIntersection ? + getPropertiesOfUnionOrIntersectionType(type as UnionType) : + getPropertiesOfObjectType(type); + } - function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean { - const list = obj.properties as NodeArray; - return list.some(property => { - const nameType = property.name && getLiteralTypeFromPropertyName(property.name); - const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; - const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name); - return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected); + function forEachPropertyOfType(type: Type, action: (symbol: Symbol, escapedName: __String) => void): void { + type = getReducedApparentType(type); + if (type.flags & TypeFlags.StructuredType) { + resolveStructuredTypeMembers(type as StructuredType).members.forEach((symbol, escapedName) => { + if (isNamedMember(symbol, escapedName)) { + action(symbol, escapedName); + } }); } + } - function getAllPossiblePropertiesOfTypes(types: readonly Type[]): Symbol[] { - const unionType = getUnionType(types); - if (!(unionType.flags & TypeFlags.Union)) { - return getAugmentedPropertiesOfType(unionType); - } + function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean { + const list = obj.properties as NodeArray; + return list.some(property => { + const nameType = property.name && getLiteralTypeFromPropertyName(property.name); + const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; + const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name); + return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected); + }); + } - const props = createSymbolTable(); - for (const memberType of types) { - for (const { escapedName } of getAugmentedPropertiesOfType(memberType)) { - if (!props.has(escapedName)) { - const prop = createUnionOrIntersectionProperty(unionType as UnionType, escapedName); - // May be undefined if the property is private - if (prop) props.set(escapedName, prop); - } + function getAllPossiblePropertiesOfTypes(types: readonly Type[]): Symbol[] { + const unionType = getUnionType(types); + if (!(unionType.flags & TypeFlags.Union)) { + return getAugmentedPropertiesOfType(unionType); + } + + const props = createSymbolTable(); + for (const memberType of types) { + for (const { escapedName } of getAugmentedPropertiesOfType(memberType)) { + if (!props.has(escapedName)) { + const prop = createUnionOrIntersectionProperty(unionType as UnionType, escapedName); + // May be undefined if the property is private + if (prop) props.set(escapedName, prop); } } - return arrayFrom(props.values()); } + return arrayFrom(props.values()); + } - function getConstraintOfType(type: InstantiableType | UnionOrIntersectionType): Type | undefined { - return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(type as TypeParameter) : - type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(type as IndexedAccessType) : - type.flags & TypeFlags.Conditional ? getConstraintOfConditionalType(type as ConditionalType) : - getBaseConstraintOfType(type); - } + function getConstraintOfType(type: InstantiableType | UnionOrIntersectionType): Type | undefined { + return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(type as TypeParameter) : + type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(type as IndexedAccessType) : + type.flags & TypeFlags.Conditional ? getConstraintOfConditionalType(type as ConditionalType) : + getBaseConstraintOfType(type); + } - function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type | undefined { - return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined; - } + function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type | undefined { + return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined; + } - function getConstraintOfIndexedAccess(type: IndexedAccessType) { - return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined; - } + function getConstraintOfIndexedAccess(type: IndexedAccessType) { + return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined; + } - function getSimplifiedTypeOrConstraint(type: Type) { - const simplified = getSimplifiedType(type, /*writing*/ false); - return simplified !== type ? simplified : getConstraintOfType(type); - } + function getSimplifiedTypeOrConstraint(type: Type) { + const simplified = getSimplifiedType(type, /*writing*/ false); + return simplified !== type ? simplified : getConstraintOfType(type); + } - function getConstraintFromIndexedAccess(type: IndexedAccessType) { - const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType); - if (indexConstraint && indexConstraint !== type.indexType) { - const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.accessFlags); - if (indexedAccess) { - return indexedAccess; - } + function getConstraintFromIndexedAccess(type: IndexedAccessType) { + const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType); + if (indexConstraint && indexConstraint !== type.indexType) { + const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.accessFlags); + if (indexedAccess) { + return indexedAccess; } - const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType); - if (objectConstraint && objectConstraint !== type.objectType) { - return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType, type.accessFlags); - } - return undefined; } - - function getDefaultConstraintOfConditionalType(type: ConditionalType) { - if (!type.resolvedDefaultConstraint) { - // An `any` branch of a conditional type would normally be viral - specifically, without special handling here, - // a conditional type with a single branch of type `any` would be assignable to anything, since it's constraint would simplify to - // just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type, - // in effect treating `any` like `never` rather than `unknown` in this location. - const trueConstraint = getInferredTrueTypeFromConditionalType(type); - const falseConstraint = getFalseTypeFromConditionalType(type); - type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]); - } - return type.resolvedDefaultConstraint; - } - - function getConstraintOfDistributiveConditionalType(type: ConditionalType): Type | undefined { - // Check if we have a conditional type of the form 'T extends U ? X : Y', where T is a constrained - // type parameter. If so, create an instantiation of the conditional type where T is replaced - // with its constraint. We do this because if the constraint is a union type it will be distributed - // over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T' - // removes 'undefined' from T. - // We skip returning a distributive constraint for a restrictive instantiation of a conditional type - // as the constraint for all type params (check type included) have been replace with `unknown`, which - // is going to produce even more false positive/negative results than the distribute constraint already does. - // Please note: the distributive constraint is a kludge for emulating what a negated type could to do filter - // a union - once negated types exist and are applied to the conditional false branch, this "constraint" - // likely doesn't need to exist. - if (type.root.isDistributive && type.restrictiveInstantiation !== type) { - const simplified = getSimplifiedType(type.checkType, /*writing*/ false); - const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified; - if (constraint && constraint !== type.checkType) { - const instantiated = getConditionalTypeInstantiation(type, prependTypeMapping(type.root.checkType, constraint, type.mapper)); - if (!(instantiated.flags & TypeFlags.Never)) { - return instantiated; - } - } - } - return undefined; + const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType); + if (objectConstraint && objectConstraint !== type.objectType) { + return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType, type.accessFlags); } + return undefined; + } - function getConstraintFromConditionalType(type: ConditionalType) { - return getConstraintOfDistributiveConditionalType(type) || getDefaultConstraintOfConditionalType(type); - } + function getDefaultConstraintOfConditionalType(type: ConditionalType) { + if (!type.resolvedDefaultConstraint) { + // An `any` branch of a conditional type would normally be viral - specifically, without special handling here, + // a conditional type with a single branch of type `any` would be assignable to anything, since it's constraint would simplify to + // just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type, + // in effect treating `any` like `never` rather than `unknown` in this location. + const trueConstraint = getInferredTrueTypeFromConditionalType(type); + const falseConstraint = getFalseTypeFromConditionalType(type); + type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]); + } + return type.resolvedDefaultConstraint; + } - function getConstraintOfConditionalType(type: ConditionalType) { - return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined; - } + function getConstraintOfDistributiveConditionalType(type: ConditionalType): Type | undefined { + // Check if we have a conditional type of the form 'T extends U ? X : Y', where T is a constrained + // type parameter. If so, create an instantiation of the conditional type where T is replaced + // with its constraint. We do this because if the constraint is a union type it will be distributed + // over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T' + // removes 'undefined' from T. + // We skip returning a distributive constraint for a restrictive instantiation of a conditional type + // as the constraint for all type params (check type included) have been replace with `unknown`, which + // is going to produce even more false positive/negative results than the distribute constraint already does. + // Please note: the distributive constraint is a kludge for emulating what a negated type could to do filter + // a union - once negated types exist and are applied to the conditional false branch, this "constraint" + // likely doesn't need to exist. + if (type.root.isDistributive && type.restrictiveInstantiation !== type) { + const simplified = getSimplifiedType(type.checkType, /*writing*/ false); + const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified; + if (constraint && constraint !== type.checkType) { + const instantiated = getConditionalTypeInstantiation(type, prependTypeMapping(type.root.checkType, constraint, type.mapper)); + if (!(instantiated.flags & TypeFlags.Never)) { + return instantiated; + } + } + } + return undefined; + } - function getEffectiveConstraintOfIntersection(types: readonly Type[], targetIsUnion: boolean) { - let constraints: Type[] | undefined; - let hasDisjointDomainType = false; - for (const t of types) { - if (t.flags & TypeFlags.Instantiable) { - // We keep following constraints as long as we have an instantiable type that is known - // not to be circular or infinite (hence we stop on index access types). - let constraint = getConstraintOfType(t); - while (constraint && constraint.flags & (TypeFlags.TypeParameter | TypeFlags.Index | TypeFlags.Conditional)) { - constraint = getConstraintOfType(constraint); - } - if (constraint) { - constraints = append(constraints, constraint); - if (targetIsUnion) { - constraints = append(constraints, t); - } - } + function getConstraintFromConditionalType(type: ConditionalType) { + return getConstraintOfDistributiveConditionalType(type) || getDefaultConstraintOfConditionalType(type); + } + + function getConstraintOfConditionalType(type: ConditionalType) { + return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined; + } + + function getEffectiveConstraintOfIntersection(types: readonly Type[], targetIsUnion: boolean) { + let constraints: Type[] | undefined; + let hasDisjointDomainType = false; + for (const t of types) { + if (t.flags & TypeFlags.Instantiable) { + // We keep following constraints as long as we have an instantiable type that is known + // not to be circular or infinite (hence we stop on index access types). + let constraint = getConstraintOfType(t); + while (constraint && constraint.flags & (TypeFlags.TypeParameter | TypeFlags.Index | TypeFlags.Conditional)) { + constraint = getConstraintOfType(constraint); } - else if (t.flags & TypeFlags.DisjointDomains) { - hasDisjointDomainType = true; + if (constraint) { + constraints = append(constraints, constraint); + if (targetIsUnion) { + constraints = append(constraints, t); + } } } - // If the target is a union type or if we are intersecting with types belonging to one of the - // disjoint domains, we may end up producing a constraint that hasn't been examined before. - if (constraints && (targetIsUnion || hasDisjointDomainType)) { - if (hasDisjointDomainType) { - // We add any types belong to one of the disjoint domains because they might cause the final - // intersection operation to reduce the union constraints. - for (const t of types) { - if (t.flags & TypeFlags.DisjointDomains) { - constraints = append(constraints, t); - } + else if (t.flags & TypeFlags.DisjointDomains) { + hasDisjointDomainType = true; + } + } + // If the target is a union type or if we are intersecting with types belonging to one of the + // disjoint domains, we may end up producing a constraint that hasn't been examined before. + if (constraints && (targetIsUnion || hasDisjointDomainType)) { + if (hasDisjointDomainType) { + // We add any types belong to one of the disjoint domains because they might cause the final + // intersection operation to reduce the union constraints. + for (const t of types) { + if (t.flags & TypeFlags.DisjointDomains) { + constraints = append(constraints, t); } } - return getIntersectionType(constraints); } - return undefined; + return getIntersectionType(constraints); } + return undefined; + } - function getBaseConstraintOfType(type: Type): Type | undefined { - if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) { - const constraint = getResolvedBaseConstraint(type as InstantiableType | UnionOrIntersectionType); - return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined; - } - return type.flags & TypeFlags.Index ? keyofConstraintType : undefined; + function getBaseConstraintOfType(type: Type): Type | undefined { + if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) { + const constraint = getResolvedBaseConstraint(type as InstantiableType | UnionOrIntersectionType); + return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined; } + return type.flags & TypeFlags.Index ? keyofConstraintType : undefined; + } - /** - * This is similar to `getBaseConstraintOfType` except it returns the input type if there's no base constraint, instead of `undefined` - * It also doesn't map indexes to `string`, as where this is used this would be unneeded (and likely undesirable) - */ - function getBaseConstraintOrType(type: Type) { - return getBaseConstraintOfType(type) || type; - } + /** + * This is similar to `getBaseConstraintOfType` except it returns the input type if there's no base constraint, instead of `undefined` + * It also doesn't map indexes to `string`, as where this is used this would be unneeded (and likely undesirable) + */ + function getBaseConstraintOrType(type: Type) { + return getBaseConstraintOfType(type) || type; + } - function hasNonCircularBaseConstraint(type: InstantiableType): boolean { - return getResolvedBaseConstraint(type) !== circularConstraintType; - } + function hasNonCircularBaseConstraint(type: InstantiableType): boolean { + return getResolvedBaseConstraint(type) !== circularConstraintType; + } - /** - * Return the resolved base constraint of a type variable. The noConstraintType singleton is returned if the - * type variable has no constraint, and the circularConstraintType singleton is returned if the constraint - * circularly references the type variable. - */ - function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): Type { - if (type.resolvedBaseConstraint) { - return type.resolvedBaseConstraint; - } - const stack: Type[] = []; - return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type); - - function getImmediateBaseConstraint(t: Type): Type { - if (!t.immediateBaseConstraint) { - if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) { - return circularConstraintType; - } - let result; - // We always explore at least 10 levels of nested constraints. Thereafter, we continue to explore - // up to 50 levels of nested constraints provided there are no "deeply nested" types on the stack - // (i.e. no types for which five instantiations have been recorded on the stack). If we reach 50 - // levels of nesting, we are presumably exploring a repeating pattern with a long cycle that hasn't - // yet triggered the deeply nested limiter. We have no test cases that actually get to 50 levels of - // nesting, so it is effectively just a safety stop. - if (stack.length < 10 || stack.length < 50 && !isDeeplyNestedType(t, stack, stack.length)) { - stack.push(t); - result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false)); - stack.pop(); - } - if (!popTypeResolution()) { - if (t.flags & TypeFlags.TypeParameter) { - const errorNode = getConstraintDeclaration(t as TypeParameter); - if (errorNode) { - const diagnostic = error(errorNode, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(t)); - if (currentNode && !isNodeDescendantOf(errorNode, currentNode) && !isNodeDescendantOf(currentNode, errorNode)) { - addRelatedInfo(diagnostic, createDiagnosticForNode(currentNode, Diagnostics.Circularity_originates_in_type_at_this_location)); - } + /** + * Return the resolved base constraint of a type variable. The noConstraintType singleton is returned if the + * type variable has no constraint, and the circularConstraintType singleton is returned if the constraint + * circularly references the type variable. + */ + function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): Type { + if (type.resolvedBaseConstraint) { + return type.resolvedBaseConstraint; + } + const stack: Type[] = []; + return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type); + + function getImmediateBaseConstraint(t: Type): Type { + if (!t.immediateBaseConstraint) { + if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) { + return circularConstraintType; + } + let result; + // We always explore at least 10 levels of nested constraints. Thereafter, we continue to explore + // up to 50 levels of nested constraints provided there are no "deeply nested" types on the stack + // (i.e. no types for which five instantiations have been recorded on the stack). If we reach 50 + // levels of nesting, we are presumably exploring a repeating pattern with a long cycle that hasn't + // yet triggered the deeply nested limiter. We have no test cases that actually get to 50 levels of + // nesting, so it is effectively just a safety stop. + if (stack.length < 10 || stack.length < 50 && !isDeeplyNestedType(t, stack, stack.length)) { + stack.push(t); + result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false)); + stack.pop(); + } + if (!popTypeResolution()) { + if (t.flags & TypeFlags.TypeParameter) { + const errorNode = getConstraintDeclaration(t as TypeParameter); + if (errorNode) { + const diagnostic = error(errorNode, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(t)); + if (currentNode && !isNodeDescendantOf(errorNode, currentNode) && !isNodeDescendantOf(currentNode, errorNode)) { + addRelatedInfo(diagnostic, createDiagnosticForNode(currentNode, Diagnostics.Circularity_originates_in_type_at_this_location)); } } - result = circularConstraintType; } - t.immediateBaseConstraint = result || noConstraintType; + result = circularConstraintType; } - return t.immediateBaseConstraint; + t.immediateBaseConstraint = result || noConstraintType; } + return t.immediateBaseConstraint; + } - function getBaseConstraint(t: Type): Type | undefined { - const c = getImmediateBaseConstraint(t); - return c !== noConstraintType && c !== circularConstraintType ? c : undefined; - } + function getBaseConstraint(t: Type): Type | undefined { + const c = getImmediateBaseConstraint(t); + return c !== noConstraintType && c !== circularConstraintType ? c : undefined; + } - function computeBaseConstraint(t: Type): Type | undefined { - if (t.flags & TypeFlags.TypeParameter) { - const constraint = getConstraintFromTypeParameter(t as TypeParameter); - return (t as TypeParameter).isThisType || !constraint ? - constraint : - getBaseConstraint(constraint); - } - if (t.flags & TypeFlags.UnionOrIntersection) { - const types = (t as UnionOrIntersectionType).types; - const baseTypes: Type[] = []; - let different = false; - for (const type of types) { - const baseType = getBaseConstraint(type); - if (baseType) { - if (baseType !== type) { - different = true; - } - baseTypes.push(baseType); - } - else { + function computeBaseConstraint(t: Type): Type | undefined { + if (t.flags & TypeFlags.TypeParameter) { + const constraint = getConstraintFromTypeParameter(t as TypeParameter); + return (t as TypeParameter).isThisType || !constraint ? + constraint : + getBaseConstraint(constraint); + } + if (t.flags & TypeFlags.UnionOrIntersection) { + const types = (t as UnionOrIntersectionType).types; + const baseTypes: Type[] = []; + let different = false; + for (const type of types) { + const baseType = getBaseConstraint(type); + if (baseType) { + if (baseType !== type) { different = true; } + baseTypes.push(baseType); } - if (!different) { - return t; + else { + different = true; } - return t.flags & TypeFlags.Union && baseTypes.length === types.length ? getUnionType(baseTypes) : - t.flags & TypeFlags.Intersection && baseTypes.length ? getIntersectionType(baseTypes) : - undefined; - } - if (t.flags & TypeFlags.Index) { - return keyofConstraintType; } - if (t.flags & TypeFlags.TemplateLiteral) { - const types = (t as TemplateLiteralType).types; - const constraints = mapDefined(types, getBaseConstraint); - return constraints.length === types.length ? getTemplateLiteralType((t as TemplateLiteralType).texts, constraints) : stringType; - } - if (t.flags & TypeFlags.StringMapping) { - const constraint = getBaseConstraint((t as StringMappingType).type); - return constraint ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType; - } - if (t.flags & TypeFlags.IndexedAccess) { - const baseObjectType = getBaseConstraint((t as IndexedAccessType).objectType); - const baseIndexType = getBaseConstraint((t as IndexedAccessType).indexType); - const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (t as IndexedAccessType).accessFlags); - return baseIndexedAccess && getBaseConstraint(baseIndexedAccess); - } - if (t.flags & TypeFlags.Conditional) { - const constraint = getConstraintFromConditionalType(t as ConditionalType); - return constraint && getBaseConstraint(constraint); - } - if (t.flags & TypeFlags.Substitution) { - return getBaseConstraint((t as SubstitutionType).substitute); + if (!different) { + return t; } - return t; + return t.flags & TypeFlags.Union && baseTypes.length === types.length ? getUnionType(baseTypes) : + t.flags & TypeFlags.Intersection && baseTypes.length ? getIntersectionType(baseTypes) : + undefined; + } + if (t.flags & TypeFlags.Index) { + return keyofConstraintType; } + if (t.flags & TypeFlags.TemplateLiteral) { + const types = (t as TemplateLiteralType).types; + const constraints = mapDefined(types, getBaseConstraint); + return constraints.length === types.length ? getTemplateLiteralType((t as TemplateLiteralType).texts, constraints) : stringType; + } + if (t.flags & TypeFlags.StringMapping) { + const constraint = getBaseConstraint((t as StringMappingType).type); + return constraint ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType; + } + if (t.flags & TypeFlags.IndexedAccess) { + const baseObjectType = getBaseConstraint((t as IndexedAccessType).objectType); + const baseIndexType = getBaseConstraint((t as IndexedAccessType).indexType); + const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (t as IndexedAccessType).accessFlags); + return baseIndexedAccess && getBaseConstraint(baseIndexedAccess); + } + if (t.flags & TypeFlags.Conditional) { + const constraint = getConstraintFromConditionalType(t as ConditionalType); + return constraint && getBaseConstraint(constraint); + } + if (t.flags & TypeFlags.Substitution) { + return getBaseConstraint((t as SubstitutionType).substitute); + } + return t; } + } - function getApparentTypeOfIntersectionType(type: IntersectionType) { - return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, type, /*apparentType*/ true)); - } + function getApparentTypeOfIntersectionType(type: IntersectionType) { + return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, type, /*apparentType*/ true)); + } - function getResolvedTypeParameterDefault(typeParameter: TypeParameter): Type | undefined { - if (!typeParameter.default) { - if (typeParameter.target) { - const targetDefault = getResolvedTypeParameterDefault(typeParameter.target); - typeParameter.default = targetDefault ? instantiateType(targetDefault, typeParameter.mapper) : noConstraintType; - } - else { - // To block recursion, set the initial value to the resolvingDefaultType. - typeParameter.default = resolvingDefaultType; - const defaultDeclaration = typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default); - const defaultType = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType; - if (typeParameter.default === resolvingDefaultType) { - // If we have not been called recursively, set the correct default type. - typeParameter.default = defaultType; - } - } + function getResolvedTypeParameterDefault(typeParameter: TypeParameter): Type | undefined { + if (!typeParameter.default) { + if (typeParameter.target) { + const targetDefault = getResolvedTypeParameterDefault(typeParameter.target); + typeParameter.default = targetDefault ? instantiateType(targetDefault, typeParameter.mapper) : noConstraintType; } - else if (typeParameter.default === resolvingDefaultType) { - // If we are called recursively for this type parameter, mark the default as circular. - typeParameter.default = circularConstraintType; + else { + // To block recursion, set the initial value to the resolvingDefaultType. + typeParameter.default = resolvingDefaultType; + const defaultDeclaration = typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default); + const defaultType = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType; + if (typeParameter.default === resolvingDefaultType) { + // If we have not been called recursively, set the correct default type. + typeParameter.default = defaultType; + } } - return typeParameter.default; } - - /** - * Gets the default type for a type parameter. - * - * If the type parameter is the result of an instantiation, this gets the instantiated - * default type of its target. If the type parameter has no default type or the default is - * circular, `undefined` is returned. - */ - function getDefaultFromTypeParameter(typeParameter: TypeParameter): Type | undefined { - const defaultType = getResolvedTypeParameterDefault(typeParameter); - return defaultType !== noConstraintType && defaultType !== circularConstraintType ? defaultType : undefined; + else if (typeParameter.default === resolvingDefaultType) { + // If we are called recursively for this type parameter, mark the default as circular. + typeParameter.default = circularConstraintType; } + return typeParameter.default; + } - function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) { - return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType; - } + /** + * Gets the default type for a type parameter. + * + * If the type parameter is the result of an instantiation, this gets the instantiated + * default type of its target. If the type parameter has no default type or the default is + * circular, `undefined` is returned. + */ + function getDefaultFromTypeParameter(typeParameter: TypeParameter): Type | undefined { + const defaultType = getResolvedTypeParameterDefault(typeParameter); + return defaultType !== noConstraintType && defaultType !== circularConstraintType ? defaultType : undefined; + } - /** - * Indicates whether the declaration of a typeParameter has a default type. - */ - function hasTypeParameterDefault(typeParameter: TypeParameter): boolean { - return !!(typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default)); - } + function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) { + return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType; + } - function getApparentTypeOfMappedType(type: MappedType) { - return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type)); - } + /** + * Indicates whether the declaration of a typeParameter has a default type. + */ + function hasTypeParameterDefault(typeParameter: TypeParameter): boolean { + return !!(typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default)); + } - function getResolvedApparentTypeOfMappedType(type: MappedType) { - const typeVariable = getHomomorphicTypeVariable(type); - if (typeVariable && !type.declaration.nameType) { - const constraint = getConstraintOfTypeParameter(typeVariable); - if (constraint && (isArrayType(constraint) || isTupleType(constraint))) { - return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper)); - } + function getApparentTypeOfMappedType(type: MappedType) { + return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type)); + } + + function getResolvedApparentTypeOfMappedType(type: MappedType) { + const typeVariable = getHomomorphicTypeVariable(type); + if (typeVariable && !type.declaration.nameType) { + const constraint = getConstraintOfTypeParameter(typeVariable); + if (constraint && (isArrayType(constraint) || isTupleType(constraint))) { + return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper)); } - return type; } + return type; + } - /** - * For a type parameter, return the base constraint of the type parameter. For the string, number, - * boolean, and symbol primitive types, return the corresponding object types. Otherwise return the - * type itself. - */ - function getApparentType(type: Type): Type { - const t = type.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(type) || unknownType : type; - return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) : - t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType) : - t.flags & TypeFlags.StringLike ? globalStringType : - t.flags & TypeFlags.NumberLike ? globalNumberType : - t.flags & TypeFlags.BigIntLike ? getGlobalBigIntType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2020) : - t.flags & TypeFlags.BooleanLike ? globalBooleanType : - t.flags & TypeFlags.ESSymbolLike ? getGlobalESSymbolType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2015) : - t.flags & TypeFlags.NonPrimitive ? emptyObjectType : - t.flags & TypeFlags.Index ? keyofConstraintType : - t.flags & TypeFlags.Unknown && !strictNullChecks ? emptyObjectType : - t; - } - - function getReducedApparentType(type: Type): Type { - // Since getApparentType may return a non-reduced union or intersection type, we need to perform - // type reduction both before and after obtaining the apparent type. For example, given a type parameter - // 'T extends A | B', the type 'T & X' becomes 'A & X | B & X' after obtaining the apparent type, and - // that type may need further reduction to remove empty intersections. - return getReducedType(getApparentType(getReducedType(type))); - } - - function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { - let singleProp: Symbol | undefined; - let propSet: ESMap | undefined; - let indexTypes: Type[] | undefined; - const isUnion = containingType.flags & TypeFlags.Union; - // Flags we want to propagate to the result if they exist in all source symbols - let optionalFlag = isUnion ? SymbolFlags.None : SymbolFlags.Optional; - let syntheticFlag = CheckFlags.SyntheticMethod; - let checkFlags = isUnion ? 0 : CheckFlags.Readonly; - let mergedInstantiations = false; - for (const current of containingType.types) { - const type = getApparentType(current); - if (!(isErrorType(type) || type.flags & TypeFlags.Never)) { - const prop = getPropertyOfType(type, name, skipObjectFunctionPropertyAugment); - const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0; - if (prop) { - if (isUnion) { - optionalFlag |= (prop.flags & SymbolFlags.Optional); + /** + * For a type parameter, return the base constraint of the type parameter. For the string, number, + * boolean, and symbol primitive types, return the corresponding object types. Otherwise return the + * type itself. + */ + function getApparentType(type: Type): Type { + const t = type.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(type) || unknownType : type; + return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) : + t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType) : + t.flags & TypeFlags.StringLike ? globalStringType : + t.flags & TypeFlags.NumberLike ? globalNumberType : + t.flags & TypeFlags.BigIntLike ? getGlobalBigIntType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2020) : + t.flags & TypeFlags.BooleanLike ? globalBooleanType : + t.flags & TypeFlags.ESSymbolLike ? getGlobalESSymbolType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2015) : + t.flags & TypeFlags.NonPrimitive ? emptyObjectType : + t.flags & TypeFlags.Index ? keyofConstraintType : + t.flags & TypeFlags.Unknown && !strictNullChecks ? emptyObjectType : + t; + } + + function getReducedApparentType(type: Type): Type { + // Since getApparentType may return a non-reduced union or intersection type, we need to perform + // type reduction both before and after obtaining the apparent type. For example, given a type parameter + // 'T extends A | B', the type 'T & X' becomes 'A & X | B & X' after obtaining the apparent type, and + // that type may need further reduction to remove empty intersections. + return getReducedType(getApparentType(getReducedType(type))); + } + + function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { + let singleProp: Symbol | undefined; + let propSet: ESMap | undefined; + let indexTypes: Type[] | undefined; + const isUnion = containingType.flags & TypeFlags.Union; + // Flags we want to propagate to the result if they exist in all source symbols + let optionalFlag = isUnion ? SymbolFlags.None : SymbolFlags.Optional; + let syntheticFlag = CheckFlags.SyntheticMethod; + let checkFlags = isUnion ? 0 : CheckFlags.Readonly; + let mergedInstantiations = false; + for (const current of containingType.types) { + const type = getApparentType(current); + if (!(isErrorType(type) || type.flags & TypeFlags.Never)) { + const prop = getPropertyOfType(type, name, skipObjectFunctionPropertyAugment); + const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0; + if (prop) { + if (isUnion) { + optionalFlag |= (prop.flags & SymbolFlags.Optional); + } + else { + optionalFlag &= prop.flags; + } + if (!singleProp) { + singleProp = prop; + } + else if (prop !== singleProp) { + const isInstantiation = (getTargetSymbol(prop) || prop) === (getTargetSymbol(singleProp) || singleProp); + // If the symbols are instances of one another with identical types - consider the symbols + // equivalent and just use the first one, which thus allows us to avoid eliding private + // members when intersecting a (this-)instantiations of a class with it's raw base or another instance + if (isInstantiation && compareProperties(singleProp, prop, (a, b) => a === b ? Ternary.True : Ternary.False) === Ternary.True) { + // If we merged instantiations of a generic type, we replicate the symbol parent resetting behavior we used + // to do when we recorded multiple distinct symbols so that we still get, eg, `Array.length` printed + // back and not `Array.length` when we're looking at a `.length` access on a `string[] | number[]` + mergedInstantiations = !!singleProp.parent && !!length(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(singleProp.parent)); } else { - optionalFlag &= prop.flags; - } - if (!singleProp) { - singleProp = prop; - } - else if (prop !== singleProp) { - const isInstantiation = (getTargetSymbol(prop) || prop) === (getTargetSymbol(singleProp) || singleProp); - // If the symbols are instances of one another with identical types - consider the symbols - // equivalent and just use the first one, which thus allows us to avoid eliding private - // members when intersecting a (this-)instantiations of a class with it's raw base or another instance - if (isInstantiation && compareProperties(singleProp, prop, (a, b) => a === b ? Ternary.True : Ternary.False) === Ternary.True) { - // If we merged instantiations of a generic type, we replicate the symbol parent resetting behavior we used - // to do when we recorded multiple distinct symbols so that we still get, eg, `Array.length` printed - // back and not `Array.length` when we're looking at a `.length` access on a `string[] | number[]` - mergedInstantiations = !!singleProp.parent && !!length(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(singleProp.parent)); + if (!propSet) { + propSet = new Map(); + propSet.set(getSymbolId(singleProp), singleProp); } - else { - if (!propSet) { - propSet = new Map(); - propSet.set(getSymbolId(singleProp), singleProp); - } - const id = getSymbolId(prop); - if (!propSet.has(id)) { - propSet.set(id, prop); - } + const id = getSymbolId(prop); + if (!propSet.has(id)) { + propSet.set(id, prop); } } - if (isUnion && isReadonlySymbol(prop)) { - checkFlags |= CheckFlags.Readonly; - } - else if (!isUnion && !isReadonlySymbol(prop)) { - checkFlags &= ~CheckFlags.Readonly; - } - checkFlags |= (!(modifiers & ModifierFlags.NonPublicAccessibilityModifier) ? CheckFlags.ContainsPublic : 0) | - (modifiers & ModifierFlags.Protected ? CheckFlags.ContainsProtected : 0) | - (modifiers & ModifierFlags.Private ? CheckFlags.ContainsPrivate : 0) | - (modifiers & ModifierFlags.Static ? CheckFlags.ContainsStatic : 0); - if (!isPrototypeProperty(prop)) { - syntheticFlag = CheckFlags.SyntheticProperty; - } } - else if (isUnion) { - const indexInfo = !isLateBoundName(name) && getApplicableIndexInfoForName(type, name); - if (indexInfo) { - checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0); - indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type); - } - else if (isObjectLiteralType(type) && !(getObjectFlags(type) & ObjectFlags.ContainsSpread)) { - checkFlags |= CheckFlags.WritePartial; - indexTypes = append(indexTypes, undefinedType); - } - else { - checkFlags |= CheckFlags.ReadPartial; - } + if (isUnion && isReadonlySymbol(prop)) { + checkFlags |= CheckFlags.Readonly; + } + else if (!isUnion && !isReadonlySymbol(prop)) { + checkFlags &= ~CheckFlags.Readonly; + } + checkFlags |= (!(modifiers & ModifierFlags.NonPublicAccessibilityModifier) ? CheckFlags.ContainsPublic : 0) | + (modifiers & ModifierFlags.Protected ? CheckFlags.ContainsProtected : 0) | + (modifiers & ModifierFlags.Private ? CheckFlags.ContainsPrivate : 0) | + (modifiers & ModifierFlags.Static ? CheckFlags.ContainsStatic : 0); + if (!isPrototypeProperty(prop)) { + syntheticFlag = CheckFlags.SyntheticProperty; } } - } - if (!singleProp || isUnion && (propSet || checkFlags & CheckFlags.Partial) && checkFlags & (CheckFlags.ContainsPrivate | CheckFlags.ContainsProtected)) { - // No property was found, or, in a union, a property has a private or protected declaration in one - // constituent, but is missing or has a different declaration in another constituent. - return undefined; - } - if (!propSet && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) { - if (mergedInstantiations) { - // No symbol from a union/intersection should have a `.parent` set (since unions/intersections don't act as symbol parents) - // Unless that parent is "reconstituted" from the "first value declaration" on the symbol (which is likely different than its instantiated parent!) - // They also have a `.containingType` set, which affects some services endpoints behavior, like `getRootSymbol` - const clone = createSymbolWithType(singleProp, (singleProp as TransientSymbol).type); - clone.parent = singleProp.valueDeclaration?.symbol?.parent; - clone.containingType = containingType; - clone.mapper = (singleProp as TransientSymbol).mapper; - return clone; - } - else { - return singleProp; + else if (isUnion) { + const indexInfo = !isLateBoundName(name) && getApplicableIndexInfoForName(type, name); + if (indexInfo) { + checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0); + indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type); + } + else if (isObjectLiteralType(type) && !(getObjectFlags(type) & ObjectFlags.ContainsSpread)) { + checkFlags |= CheckFlags.WritePartial; + indexTypes = append(indexTypes, undefinedType); + } + else { + checkFlags |= CheckFlags.ReadPartial; + } } } - const props = propSet ? arrayFrom(propSet.values()) : [singleProp]; - let declarations: Declaration[] | undefined; - let firstType: Type | undefined; - let nameType: Type | undefined; - const propTypes: Type[] = []; - let firstValueDeclaration: Declaration | undefined; - let hasNonUniformValueDeclaration = false; - for (const prop of props) { - if (!firstValueDeclaration) { - firstValueDeclaration = prop.valueDeclaration; - } - else if (prop.valueDeclaration && prop.valueDeclaration !== firstValueDeclaration) { - hasNonUniformValueDeclaration = true; - } - declarations = addRange(declarations, prop.declarations); - const type = getTypeOfSymbol(prop); - if (!firstType) { - firstType = type; - nameType = getSymbolLinks(prop).nameType; - } - else if (type !== firstType) { - checkFlags |= CheckFlags.HasNonUniformType; - } - if (isLiteralType(type) || isPatternLiteralType(type)) { - checkFlags |= CheckFlags.HasLiteralType; - } - if (type.flags & TypeFlags.Never) { - checkFlags |= CheckFlags.HasNeverType; - } - propTypes.push(type); + } + if (!singleProp || isUnion && (propSet || checkFlags & CheckFlags.Partial) && checkFlags & (CheckFlags.ContainsPrivate | CheckFlags.ContainsProtected)) { + // No property was found, or, in a union, a property has a private or protected declaration in one + // constituent, but is missing or has a different declaration in another constituent. + return undefined; + } + if (!propSet && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) { + if (mergedInstantiations) { + // No symbol from a union/intersection should have a `.parent` set (since unions/intersections don't act as symbol parents) + // Unless that parent is "reconstituted" from the "first value declaration" on the symbol (which is likely different than its instantiated parent!) + // They also have a `.containingType` set, which affects some services endpoints behavior, like `getRootSymbol` + const clone = createSymbolWithType(singleProp, (singleProp as TransientSymbol).type); + clone.parent = singleProp.valueDeclaration?.symbol?.parent; + clone.containingType = containingType; + clone.mapper = (singleProp as TransientSymbol).mapper; + return clone; } - addRange(propTypes, indexTypes); - const result = createSymbol(SymbolFlags.Property | optionalFlag, name, syntheticFlag | checkFlags); - result.containingType = containingType; - if (!hasNonUniformValueDeclaration && firstValueDeclaration) { - result.valueDeclaration = firstValueDeclaration; - - // Inherit information about parent type. - if (firstValueDeclaration.symbol.parent) { - result.parent = firstValueDeclaration.symbol.parent; - } + else { + return singleProp; } - - result.declarations = declarations; - result.nameType = nameType; - if (propTypes.length > 2) { - // When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed - result.checkFlags |= CheckFlags.DeferredType; - result.deferralParent = containingType; - result.deferralConstituents = propTypes; + } + const props = propSet ? arrayFrom(propSet.values()) : [singleProp]; + let declarations: Declaration[] | undefined; + let firstType: Type | undefined; + let nameType: Type | undefined; + const propTypes: Type[] = []; + let firstValueDeclaration: Declaration | undefined; + let hasNonUniformValueDeclaration = false; + for (const prop of props) { + if (!firstValueDeclaration) { + firstValueDeclaration = prop.valueDeclaration; } - else { - result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); + else if (prop.valueDeclaration && prop.valueDeclaration !== firstValueDeclaration) { + hasNonUniformValueDeclaration = true; } - return result; + declarations = addRange(declarations, prop.declarations); + const type = getTypeOfSymbol(prop); + if (!firstType) { + firstType = type; + nameType = getSymbolLinks(prop).nameType; + } + else if (type !== firstType) { + checkFlags |= CheckFlags.HasNonUniformType; + } + if (isLiteralType(type) || isPatternLiteralType(type)) { + checkFlags |= CheckFlags.HasLiteralType; + } + if (type.flags & TypeFlags.Never) { + checkFlags |= CheckFlags.HasNeverType; + } + propTypes.push(type); } + addRange(propTypes, indexTypes); + const result = createSymbol(SymbolFlags.Property | optionalFlag, name, syntheticFlag | checkFlags); + result.containingType = containingType; + if (!hasNonUniformValueDeclaration && firstValueDeclaration) { + result.valueDeclaration = firstValueDeclaration; - // Return the symbol for a given property in a union or intersection type, or undefined if the property - // does not exist in any constituent type. Note that the returned property may only be present in some - // constituents, in which case the isPartial flag is set when the containing type is union type. We need - // these partial properties when identifying discriminant properties, but otherwise they are filtered out - // and do not appear to be present in the union type. - function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { - let property = type.propertyCacheWithoutObjectFunctionPropertyAugment?.get(name) || - !skipObjectFunctionPropertyAugment ? type.propertyCache?.get(name) : undefined; - if (!property) { - property = createUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); - if (property) { - const properties = skipObjectFunctionPropertyAugment ? - type.propertyCacheWithoutObjectFunctionPropertyAugment ||= createSymbolTable() : - type.propertyCache ||= createSymbolTable(); - properties.set(name, property); - } + // Inherit information about parent type. + if (firstValueDeclaration.symbol.parent) { + result.parent = firstValueDeclaration.symbol.parent; } - return property; } - function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { - const property = getUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); - // We need to filter out partial properties in union types - return property && !(getCheckFlags(property) & CheckFlags.ReadPartial) ? property : undefined; + result.declarations = declarations; + result.nameType = nameType; + if (propTypes.length > 2) { + // When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed + result.checkFlags |= CheckFlags.DeferredType; + result.deferralParent = containingType; + result.deferralConstituents = propTypes; } - - /** - * Return the reduced form of the given type. For a union type, it is a union of the normalized constituent types. - * For an intersection of types containing one or more mututally exclusive discriminant properties, it is 'never'. - * For all other types, it is simply the type itself. Discriminant properties are considered mutually exclusive when - * no constituent property has type 'never', but the intersection of the constituent property types is 'never'. - */ - function getReducedType(type: Type): Type { - if (type.flags & TypeFlags.Union && (type as UnionType).objectFlags & ObjectFlags.ContainsIntersections) { - return (type as UnionType).resolvedReducedType || ((type as UnionType).resolvedReducedType = getReducedUnionType(type as UnionType)); - } - else if (type.flags & TypeFlags.Intersection) { - if (!((type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersectionComputed)) { - (type as IntersectionType).objectFlags |= ObjectFlags.IsNeverIntersectionComputed | - (some(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isNeverReducedProperty) ? ObjectFlags.IsNeverIntersection : 0); - } - return (type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type; - } - return type; + else { + result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); } + return result; + } - function getReducedUnionType(unionType: UnionType) { - const reducedTypes = sameMap(unionType.types, getReducedType); - if (reducedTypes === unionType.types) { - return unionType; - } - const reduced = getUnionType(reducedTypes); - if (reduced.flags & TypeFlags.Union) { - (reduced as UnionType).resolvedReducedType = reduced; + // Return the symbol for a given property in a union or intersection type, or undefined if the property + // does not exist in any constituent type. Note that the returned property may only be present in some + // constituents, in which case the isPartial flag is set when the containing type is union type. We need + // these partial properties when identifying discriminant properties, but otherwise they are filtered out + // and do not appear to be present in the union type. + function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { + let property = type.propertyCacheWithoutObjectFunctionPropertyAugment?.get(name) || + !skipObjectFunctionPropertyAugment ? type.propertyCache?.get(name) : undefined; + if (!property) { + property = createUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); + if (property) { + const properties = skipObjectFunctionPropertyAugment ? + type.propertyCacheWithoutObjectFunctionPropertyAugment ||= createSymbolTable() : + type.propertyCache ||= createSymbolTable(); + properties.set(name, property); + } + } + return property; + } + + function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { + const property = getUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); + // We need to filter out partial properties in union types + return property && !(getCheckFlags(property) & CheckFlags.ReadPartial) ? property : undefined; + } + + /** + * Return the reduced form of the given type. For a union type, it is a union of the normalized constituent types. + * For an intersection of types containing one or more mututally exclusive discriminant properties, it is 'never'. + * For all other types, it is simply the type itself. Discriminant properties are considered mutually exclusive when + * no constituent property has type 'never', but the intersection of the constituent property types is 'never'. + */ + function getReducedType(type: Type): Type { + if (type.flags & TypeFlags.Union && (type as UnionType).objectFlags & ObjectFlags.ContainsIntersections) { + return (type as UnionType).resolvedReducedType || ((type as UnionType).resolvedReducedType = getReducedUnionType(type as UnionType)); + } + else if (type.flags & TypeFlags.Intersection) { + if (!((type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersectionComputed)) { + (type as IntersectionType).objectFlags |= ObjectFlags.IsNeverIntersectionComputed | + (some(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isNeverReducedProperty) ? ObjectFlags.IsNeverIntersection : 0); } - return reduced; + return (type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type; } + return type; + } - function isNeverReducedProperty(prop: Symbol) { - return isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop); + function getReducedUnionType(unionType: UnionType) { + const reducedTypes = sameMap(unionType.types, getReducedType); + if (reducedTypes === unionType.types) { + return unionType; } - - function isDiscriminantWithNeverType(prop: Symbol) { - // Return true for a synthetic non-optional property with non-uniform types, where at least one is - // a literal type and none is never, that reduces to never. - return !(prop.flags & SymbolFlags.Optional) && - (getCheckFlags(prop) & (CheckFlags.Discriminant | CheckFlags.HasNeverType)) === CheckFlags.Discriminant && - !!(getTypeOfSymbol(prop).flags & TypeFlags.Never); + const reduced = getUnionType(reducedTypes); + if (reduced.flags & TypeFlags.Union) { + (reduced as UnionType).resolvedReducedType = reduced; } + return reduced; + } - function isConflictingPrivateProperty(prop: Symbol) { - // Return true for a synthetic property with multiple declarations, at least one of which is private. - return !prop.valueDeclaration && !!(getCheckFlags(prop) & CheckFlags.ContainsPrivate); - } + function isNeverReducedProperty(prop: Symbol) { + return isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop); + } - function elaborateNeverIntersection(errorInfo: DiagnosticMessageChain | undefined, type: Type) { - if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsNeverIntersection) { - const neverProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isDiscriminantWithNeverType); - if (neverProp) { - return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_has_conflicting_types_in_some_constituents, - typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(neverProp)); - } - const privateProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isConflictingPrivateProperty); - if (privateProp) { - return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_exists_in_multiple_constituents_and_is_private_in_some, - typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(privateProp)); - } + function isDiscriminantWithNeverType(prop: Symbol) { + // Return true for a synthetic non-optional property with non-uniform types, where at least one is + // a literal type and none is never, that reduces to never. + return !(prop.flags & SymbolFlags.Optional) && + (getCheckFlags(prop) & (CheckFlags.Discriminant | CheckFlags.HasNeverType)) === CheckFlags.Discriminant && + !!(getTypeOfSymbol(prop).flags & TypeFlags.Never); + } + + function isConflictingPrivateProperty(prop: Symbol) { + // Return true for a synthetic property with multiple declarations, at least one of which is private. + return !prop.valueDeclaration && !!(getCheckFlags(prop) & CheckFlags.ContainsPrivate); + } + + function elaborateNeverIntersection(errorInfo: DiagnosticMessageChain | undefined, type: Type) { + if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsNeverIntersection) { + const neverProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isDiscriminantWithNeverType); + if (neverProp) { + return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_has_conflicting_types_in_some_constituents, + typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(neverProp)); + } + const privateProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isConflictingPrivateProperty); + if (privateProp) { + return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_exists_in_multiple_constituents_and_is_private_in_some, + typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(privateProp)); } - return errorInfo; } + return errorInfo; + } - /** - * Return the symbol for the property with the given name in the given type. Creates synthetic union properties when - * necessary, maps primitive types and type parameters are to their apparent types, and augments with properties from - * Object and Function as appropriate. - * - * @param type a type to look up property from - * @param name a name of property to look up in a given type - */ - function getPropertyOfType(type: Type, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { - type = getReducedApparentType(type); - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - const symbol = resolved.members.get(name); - if (symbol && symbolIsValue(symbol)) { + /** + * Return the symbol for the property with the given name in the given type. Creates synthetic union properties when + * necessary, maps primitive types and type parameters are to their apparent types, and augments with properties from + * Object and Function as appropriate. + * + * @param type a type to look up property from + * @param name a name of property to look up in a given type + */ + function getPropertyOfType(type: Type, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { + type = getReducedApparentType(type); + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + const symbol = resolved.members.get(name); + if (symbol && symbolIsValue(symbol)) { + return symbol; + } + if (skipObjectFunctionPropertyAugment) return undefined; + const functionType = resolved === anyFunctionType ? globalFunctionType : + resolved.callSignatures.length ? globalCallableFunctionType : + resolved.constructSignatures.length ? globalNewableFunctionType : + undefined; + if (functionType) { + const symbol = getPropertyOfObjectType(functionType, name); + if (symbol) { return symbol; } - if (skipObjectFunctionPropertyAugment) return undefined; - const functionType = resolved === anyFunctionType ? globalFunctionType : - resolved.callSignatures.length ? globalCallableFunctionType : - resolved.constructSignatures.length ? globalNewableFunctionType : - undefined; - if (functionType) { - const symbol = getPropertyOfObjectType(functionType, name); - if (symbol) { - return symbol; - } - } - return getPropertyOfObjectType(globalObjectType, name); - } - if (type.flags & TypeFlags.UnionOrIntersection) { - return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment); } - return undefined; + return getPropertyOfObjectType(globalObjectType, name); } - - function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): readonly Signature[] { - if (type.flags & TypeFlags.StructuredType) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - return kind === SignatureKind.Call ? resolved.callSignatures : resolved.constructSignatures; - } - return emptyArray; + if (type.flags & TypeFlags.UnionOrIntersection) { + return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment); } + return undefined; + } - /** - * Return the signatures of the given kind in the given type. Creates synthetic union signatures when necessary and - * maps primitive types and type parameters are to their apparent types. - */ - function getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] { - return getSignaturesOfStructuredType(getReducedApparentType(type), kind); + function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): readonly Signature[] { + if (type.flags & TypeFlags.StructuredType) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + return kind === SignatureKind.Call ? resolved.callSignatures : resolved.constructSignatures; } + return emptyArray; + } - function findIndexInfo(indexInfos: readonly IndexInfo[], keyType: Type) { - return find(indexInfos, info => info.keyType === keyType); - } + /** + * Return the signatures of the given kind in the given type. Creates synthetic union signatures when necessary and + * maps primitive types and type parameters are to their apparent types. + */ + function getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] { + return getSignaturesOfStructuredType(getReducedApparentType(type), kind); + } - function findApplicableIndexInfo(indexInfos: readonly IndexInfo[], keyType: Type) { - // Index signatures for type 'string' are considered only when no other index signatures apply. - let stringIndexInfo: IndexInfo | undefined; - let applicableInfo: IndexInfo | undefined; - let applicableInfos: IndexInfo[] | undefined; - for (const info of indexInfos) { - if (info.keyType === stringType) { - stringIndexInfo = info; + function findIndexInfo(indexInfos: readonly IndexInfo[], keyType: Type) { + return find(indexInfos, info => info.keyType === keyType); + } + + function findApplicableIndexInfo(indexInfos: readonly IndexInfo[], keyType: Type) { + // Index signatures for type 'string' are considered only when no other index signatures apply. + let stringIndexInfo: IndexInfo | undefined; + let applicableInfo: IndexInfo | undefined; + let applicableInfos: IndexInfo[] | undefined; + for (const info of indexInfos) { + if (info.keyType === stringType) { + stringIndexInfo = info; + } + else if (isApplicableIndexType(keyType, info.keyType)) { + if (!applicableInfo) { + applicableInfo = info; } - else if (isApplicableIndexType(keyType, info.keyType)) { - if (!applicableInfo) { - applicableInfo = info; - } - else { - (applicableInfos || (applicableInfos = [applicableInfo])).push(info); - } + else { + (applicableInfos || (applicableInfos = [applicableInfo])).push(info); } } - // When more than one index signature is applicable we create a synthetic IndexInfo. Instead of computing - // the intersected key type, we just use unknownType for the key type as nothing actually depends on the - // keyType property of the returned IndexInfo. - return applicableInfos ? createIndexInfo(unknownType, getIntersectionType(map(applicableInfos, info => info.type)), - reduceLeft(applicableInfos, (isReadonly, info) => isReadonly && info.isReadonly, /*initial*/ true)) : - applicableInfo ? applicableInfo : - stringIndexInfo && isApplicableIndexType(keyType, stringType) ? stringIndexInfo : - undefined; } + // When more than one index signature is applicable we create a synthetic IndexInfo. Instead of computing + // the intersected key type, we just use unknownType for the key type as nothing actually depends on the + // keyType property of the returned IndexInfo. + return applicableInfos ? createIndexInfo(unknownType, getIntersectionType(map(applicableInfos, info => info.type)), + reduceLeft(applicableInfos, (isReadonly, info) => isReadonly && info.isReadonly, /*initial*/ true)) : + applicableInfo ? applicableInfo : + stringIndexInfo && isApplicableIndexType(keyType, stringType) ? stringIndexInfo : + undefined; + } - function isApplicableIndexType(source: Type, target: Type): boolean { - // A 'string' index signature applies to types assignable to 'string' or 'number', and a 'number' index - // signature applies to types assignable to 'number' and numeric string literal types. - return isTypeAssignableTo(source, target) || - target === stringType && isTypeAssignableTo(source, numberType) || - target === numberType && !!(source.flags & TypeFlags.StringLiteral) && isNumericLiteralName((source as StringLiteralType).value); - } + function isApplicableIndexType(source: Type, target: Type): boolean { + // A 'string' index signature applies to types assignable to 'string' or 'number', and a 'number' index + // signature applies to types assignable to 'number' and numeric string literal types. + return isTypeAssignableTo(source, target) || + target === stringType && isTypeAssignableTo(source, numberType) || + target === numberType && !!(source.flags & TypeFlags.StringLiteral) && isNumericLiteralName((source as StringLiteralType).value); + } - function getIndexInfosOfStructuredType(type: Type): readonly IndexInfo[] { - if (type.flags & TypeFlags.StructuredType) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - return resolved.indexInfos; - } - return emptyArray; + function getIndexInfosOfStructuredType(type: Type): readonly IndexInfo[] { + if (type.flags & TypeFlags.StructuredType) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + return resolved.indexInfos; } + return emptyArray; + } - function getIndexInfosOfType(type: Type): readonly IndexInfo[] { - return getIndexInfosOfStructuredType(getReducedApparentType(type)); - } + function getIndexInfosOfType(type: Type): readonly IndexInfo[] { + return getIndexInfosOfStructuredType(getReducedApparentType(type)); + } - // Return the indexing info of the given kind in the given type. Creates synthetic union index types when necessary and - // maps primitive types and type parameters are to their apparent types. - function getIndexInfoOfType(type: Type, keyType: Type): IndexInfo | undefined { - return findIndexInfo(getIndexInfosOfType(type), keyType); - } + // Return the indexing info of the given kind in the given type. Creates synthetic union index types when necessary and + // maps primitive types and type parameters are to their apparent types. + function getIndexInfoOfType(type: Type, keyType: Type): IndexInfo | undefined { + return findIndexInfo(getIndexInfosOfType(type), keyType); + } - // Return the index type of the given kind in the given type. Creates synthetic union index types when necessary and - // maps primitive types and type parameters are to their apparent types. - function getIndexTypeOfType(type: Type, keyType: Type): Type | undefined { - return getIndexInfoOfType(type, keyType)?.type; - } + // Return the index type of the given kind in the given type. Creates synthetic union index types when necessary and + // maps primitive types and type parameters are to their apparent types. + function getIndexTypeOfType(type: Type, keyType: Type): Type | undefined { + return getIndexInfoOfType(type, keyType)?.type; + } - function getApplicableIndexInfos(type: Type, keyType: Type): IndexInfo[] { - return getIndexInfosOfType(type).filter(info => isApplicableIndexType(keyType, info.keyType)); - } + function getApplicableIndexInfos(type: Type, keyType: Type): IndexInfo[] { + return getIndexInfosOfType(type).filter(info => isApplicableIndexType(keyType, info.keyType)); + } - function getApplicableIndexInfo(type: Type, keyType: Type): IndexInfo | undefined { - return findApplicableIndexInfo(getIndexInfosOfType(type), keyType); - } + function getApplicableIndexInfo(type: Type, keyType: Type): IndexInfo | undefined { + return findApplicableIndexInfo(getIndexInfosOfType(type), keyType); + } + + function getApplicableIndexInfoForName(type: Type, name: __String): IndexInfo | undefined { + return getApplicableIndexInfo(type, isLateBoundName(name) ? esSymbolType : getStringLiteralType(unescapeLeadingUnderscores(name))); + } - function getApplicableIndexInfoForName(type: Type, name: __String): IndexInfo | undefined { - return getApplicableIndexInfo(type, isLateBoundName(name) ? esSymbolType : getStringLiteralType(unescapeLeadingUnderscores(name))); + // Return list of type parameters with duplicates removed (duplicate identifier errors are generated in the actual + // type checking functions). + function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): TypeParameter[] | undefined { + let result: TypeParameter[] | undefined; + for (const node of getEffectiveTypeParameterDeclarations(declaration)) { + result = appendIfUnique(result, getDeclaredTypeOfTypeParameter(node.symbol)); } + return result; + } - // Return list of type parameters with duplicates removed (duplicate identifier errors are generated in the actual - // type checking functions). - function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): TypeParameter[] | undefined { - let result: TypeParameter[] | undefined; - for (const node of getEffectiveTypeParameterDeclarations(declaration)) { - result = appendIfUnique(result, getDeclaredTypeOfTypeParameter(node.symbol)); + function symbolsToArray(symbols: SymbolTable): Symbol[] { + const result: Symbol[] = []; + symbols.forEach((symbol, id) => { + if (!isReservedMemberName(id)) { + result.push(symbol); } - return result; - } + }); + return result; + } - function symbolsToArray(symbols: SymbolTable): Symbol[] { - const result: Symbol[] = []; - symbols.forEach((symbol, id) => { - if (!isReservedMemberName(id)) { - result.push(symbol); - } - }); - return result; + function isJSDocOptionalParameter(node: ParameterDeclaration) { + return isInJSFile(node) && ( + // node.type should only be a JSDocOptionalType when node is a parameter of a JSDocFunctionType + node.type && node.type.kind === SyntaxKind.JSDocOptionalType + || getJSDocParameterTags(node).some(({ isBracketed, typeExpression }) => + isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType)); + } + + function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) { + if (isExternalModuleNameRelative(moduleName)) { + return undefined; } + const symbol = getSymbol(globals, '"' + moduleName + '"' as __String, SymbolFlags.ValueModule); + // merged symbol is module declaration symbol combined with all augmentations + return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol; + } - function isJSDocOptionalParameter(node: ParameterDeclaration) { - return isInJSFile(node) && ( - // node.type should only be a JSDocOptionalType when node is a parameter of a JSDocFunctionType - node.type && node.type.kind === SyntaxKind.JSDocOptionalType - || getJSDocParameterTags(node).some(({ isBracketed, typeExpression }) => - isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType)); + function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { + if (hasQuestionToken(node) || isOptionalJSDocPropertyLikeTag(node) || isJSDocOptionalParameter(node)) { + return true; } - function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) { - if (isExternalModuleNameRelative(moduleName)) { - return undefined; - } - const symbol = getSymbol(globals, '"' + moduleName + '"' as __String, SymbolFlags.ValueModule); - // merged symbol is module declaration symbol combined with all augmentations - return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol; + if (node.initializer) { + const signature = getSignatureFromDeclaration(node.parent); + const parameterIndex = node.parent.parameters.indexOf(node); + Debug.assert(parameterIndex >= 0); + // Only consider syntactic or instantiated parameters as optional, not `void` parameters as this function is used + // in grammar checks and checking for `void` too early results in parameter types widening too early + // and causes some noImplicitAny errors to be lost. + return parameterIndex >= getMinArgumentCount(signature, MinArgumentCountFlags.StrongArityForUntypedJS | MinArgumentCountFlags.VoidIsNonOptional); + } + const iife = getImmediatelyInvokedFunctionExpression(node.parent); + if (iife) { + return !node.type && + !node.dotDotDotToken && + node.parent.parameters.indexOf(node) >= iife.arguments.length; } - function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { - if (hasQuestionToken(node) || isOptionalJSDocPropertyLikeTag(node) || isJSDocOptionalParameter(node)) { - return true; - } + return false; + } - if (node.initializer) { - const signature = getSignatureFromDeclaration(node.parent); - const parameterIndex = node.parent.parameters.indexOf(node); - Debug.assert(parameterIndex >= 0); - // Only consider syntactic or instantiated parameters as optional, not `void` parameters as this function is used - // in grammar checks and checking for `void` too early results in parameter types widening too early - // and causes some noImplicitAny errors to be lost. - return parameterIndex >= getMinArgumentCount(signature, MinArgumentCountFlags.StrongArityForUntypedJS | MinArgumentCountFlags.VoidIsNonOptional); - } - const iife = getImmediatelyInvokedFunctionExpression(node.parent); - if (iife) { - return !node.type && - !node.dotDotDotToken && - node.parent.parameters.indexOf(node) >= iife.arguments.length; - } + function isOptionalPropertyDeclaration(node: Declaration) { + return isPropertyDeclaration(node) && node.questionToken; + } + function isOptionalJSDocPropertyLikeTag(node: Node): node is JSDocPropertyLikeTag { + if (!isJSDocPropertyLikeTag(node)) { return false; } + const { isBracketed, typeExpression } = node; + return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType; + } - function isOptionalPropertyDeclaration(node: Declaration) { - return isPropertyDeclaration(node) && node.questionToken; - } + function createTypePredicate(kind: TypePredicateKind, parameterName: string | undefined, parameterIndex: number | undefined, type: Type | undefined): TypePredicate { + return { kind, parameterName, parameterIndex, type } as TypePredicate; + } - function isOptionalJSDocPropertyLikeTag(node: Node): node is JSDocPropertyLikeTag { - if (!isJSDocPropertyLikeTag(node)) { - return false; + /** + * Gets the minimum number of type arguments needed to satisfy all non-optional type + * parameters. + */ + function getMinTypeArgumentCount(typeParameters: readonly TypeParameter[] | undefined): number { + let minTypeArgumentCount = 0; + if (typeParameters) { + for (let i = 0; i < typeParameters.length; i++) { + if (!hasTypeParameterDefault(typeParameters[i])) { + minTypeArgumentCount = i + 1; + } } - const { isBracketed, typeExpression } = node; - return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType; } + return minTypeArgumentCount; + } - function createTypePredicate(kind: TypePredicateKind, parameterName: string | undefined, parameterIndex: number | undefined, type: Type | undefined): TypePredicate { - return { kind, parameterName, parameterIndex, type } as TypePredicate; + /** + * Fill in default types for unsupplied type arguments. If `typeArguments` is undefined + * when a default type is supplied, a new array will be created and returned. + * + * @param typeArguments The supplied type arguments. + * @param typeParameters The requested type parameters. + * @param minTypeArgumentCount The minimum number of required type arguments. + */ + function fillMissingTypeArguments(typeArguments: readonly Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[]; + function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[] | undefined; + function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) { + const numTypeParameters = length(typeParameters); + if (!numTypeParameters) { + return []; + } + const numTypeArguments = length(typeArguments); + if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) { + const result = typeArguments ? typeArguments.slice() : []; + // Map invalid forward references in default types to the error type + for (let i = numTypeArguments; i < numTypeParameters; i++) { + result[i] = errorType; + } + const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny); + for (let i = numTypeArguments; i < numTypeParameters; i++) { + let defaultType = getDefaultFromTypeParameter(typeParameters![i]); + if (isJavaScriptImplicitAny && defaultType && (isTypeIdenticalTo(defaultType, unknownType) || isTypeIdenticalTo(defaultType, emptyObjectType))) { + defaultType = anyType; + } + result[i] = defaultType ? instantiateType(defaultType, createTypeMapper(typeParameters!, result)) : baseDefaultType; + } + result.length = typeParameters!.length; + return result; } + return typeArguments && typeArguments.slice(); + } - /** - * Gets the minimum number of type arguments needed to satisfy all non-optional type - * parameters. - */ - function getMinTypeArgumentCount(typeParameters: readonly TypeParameter[] | undefined): number { - let minTypeArgumentCount = 0; - if (typeParameters) { - for (let i = 0; i < typeParameters.length; i++) { - if (!hasTypeParameterDefault(typeParameters[i])) { - minTypeArgumentCount = i + 1; - } + function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): Signature { + const links = getNodeLinks(declaration); + if (!links.resolvedSignature) { + const parameters: Symbol[] = []; + let flags = SignatureFlags.None; + let minArgumentCount = 0; + let thisParameter: Symbol | undefined; + let hasThisParameter = false; + const iife = getImmediatelyInvokedFunctionExpression(declaration); + const isJSConstructSignature = isJSDocConstructSignature(declaration); + const isUntypedSignatureInJSFile = !iife && + isInJSFile(declaration) && + isValueSignatureDeclaration(declaration) && + !hasJSDocParameterTags(declaration) && + !getJSDocType(declaration); + if (isUntypedSignatureInJSFile) { + flags |= SignatureFlags.IsUntypedSignatureInJSFile; + } + + // If this is a JSDoc construct signature, then skip the first parameter in the + // parameter list. The first parameter represents the return type of the construct + // signature. + for (let i = isJSConstructSignature ? 1 : 0; i < declaration.parameters.length; i++) { + const param = declaration.parameters[i]; + + let paramSymbol = param.symbol; + const type = isJSDocParameterTag(param) ? (param.typeExpression && param.typeExpression.type) : param.type; + // Include parameter symbol instead of property symbol in the signature + if (paramSymbol && !!(paramSymbol.flags & SymbolFlags.Property) && !isBindingPattern(param.name)) { + const resolvedSymbol = resolveName(param, paramSymbol.escapedName, SymbolFlags.Value, undefined, undefined, /*isUse*/ false); + paramSymbol = resolvedSymbol!; + } + if (i === 0 && paramSymbol.escapedName === InternalSymbolName.This) { + hasThisParameter = true; + thisParameter = param.symbol; } - } - return minTypeArgumentCount; - } - - /** - * Fill in default types for unsupplied type arguments. If `typeArguments` is undefined - * when a default type is supplied, a new array will be created and returned. - * - * @param typeArguments The supplied type arguments. - * @param typeParameters The requested type parameters. - * @param minTypeArgumentCount The minimum number of required type arguments. - */ - function fillMissingTypeArguments(typeArguments: readonly Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[]; - function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[] | undefined; - function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) { - const numTypeParameters = length(typeParameters); - if (!numTypeParameters) { - return []; - } - const numTypeArguments = length(typeArguments); - if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) { - const result = typeArguments ? typeArguments.slice() : []; - // Map invalid forward references in default types to the error type - for (let i = numTypeArguments; i < numTypeParameters; i++) { - result[i] = errorType; - } - const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny); - for (let i = numTypeArguments; i < numTypeParameters; i++) { - let defaultType = getDefaultFromTypeParameter(typeParameters![i]); - if (isJavaScriptImplicitAny && defaultType && (isTypeIdenticalTo(defaultType, unknownType) || isTypeIdenticalTo(defaultType, emptyObjectType))) { - defaultType = anyType; - } - result[i] = defaultType ? instantiateType(defaultType, createTypeMapper(typeParameters!, result)) : baseDefaultType; - } - result.length = typeParameters!.length; - return result; - } - return typeArguments && typeArguments.slice(); - } - - function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): Signature { - const links = getNodeLinks(declaration); - if (!links.resolvedSignature) { - const parameters: Symbol[] = []; - let flags = SignatureFlags.None; - let minArgumentCount = 0; - let thisParameter: Symbol | undefined; - let hasThisParameter = false; - const iife = getImmediatelyInvokedFunctionExpression(declaration); - const isJSConstructSignature = isJSDocConstructSignature(declaration); - const isUntypedSignatureInJSFile = !iife && - isInJSFile(declaration) && - isValueSignatureDeclaration(declaration) && - !hasJSDocParameterTags(declaration) && - !getJSDocType(declaration); - if (isUntypedSignatureInJSFile) { - flags |= SignatureFlags.IsUntypedSignatureInJSFile; - } - - // If this is a JSDoc construct signature, then skip the first parameter in the - // parameter list. The first parameter represents the return type of the construct - // signature. - for (let i = isJSConstructSignature ? 1 : 0; i < declaration.parameters.length; i++) { - const param = declaration.parameters[i]; - - let paramSymbol = param.symbol; - const type = isJSDocParameterTag(param) ? (param.typeExpression && param.typeExpression.type) : param.type; - // Include parameter symbol instead of property symbol in the signature - if (paramSymbol && !!(paramSymbol.flags & SymbolFlags.Property) && !isBindingPattern(param.name)) { - const resolvedSymbol = resolveName(param, paramSymbol.escapedName, SymbolFlags.Value, undefined, undefined, /*isUse*/ false); - paramSymbol = resolvedSymbol!; - } - if (i === 0 && paramSymbol.escapedName === InternalSymbolName.This) { - hasThisParameter = true; - thisParameter = param.symbol; - } - else { - parameters.push(paramSymbol); - } - - if (type && type.kind === SyntaxKind.LiteralType) { - flags |= SignatureFlags.HasLiteralTypes; - } - - // Record a new minimum argument count if this is not an optional parameter - const isOptionalParameter = isOptionalJSDocPropertyLikeTag(param) || - param.initializer || param.questionToken || isRestParameter(param) || - iife && parameters.length > iife.arguments.length && !type || - isJSDocOptionalParameter(param); - if (!isOptionalParameter) { - minArgumentCount = parameters.length; - } + else { + parameters.push(paramSymbol); } - // If only one accessor includes a this-type annotation, the other behaves as if it had the same type annotation - if ((declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) && - hasBindableName(declaration) && - (!hasThisParameter || !thisParameter)) { - const otherKind = declaration.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; - const other = getDeclarationOfKind(getSymbolOfNode(declaration), otherKind); - if (other) { - thisParameter = getAnnotatedAccessorThisParameter(other); - } + if (type && type.kind === SyntaxKind.LiteralType) { + flags |= SignatureFlags.HasLiteralTypes; } - const classType = declaration.kind === SyntaxKind.Constructor ? - getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol)) - : undefined; - const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); - if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) { - flags |= SignatureFlags.HasRestParameter; + // Record a new minimum argument count if this is not an optional parameter + const isOptionalParameter = isOptionalJSDocPropertyLikeTag(param) || + param.initializer || param.questionToken || isRestParameter(param) || + iife && parameters.length > iife.arguments.length && !type || + isJSDocOptionalParameter(param); + if (!isOptionalParameter) { + minArgumentCount = parameters.length; } - if (isConstructorTypeNode(declaration) && hasSyntacticModifier(declaration, ModifierFlags.Abstract) || - isConstructorDeclaration(declaration) && hasSyntacticModifier(declaration.parent, ModifierFlags.Abstract)) { - flags |= SignatureFlags.Abstract; - } - links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, - /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, - minArgumentCount, flags); } - return links.resolvedSignature; - } - /** - * A JS function gets a synthetic rest parameter if it references `arguments` AND: - * 1. It has no parameters but at least one `@param` with a type that starts with `...` - * OR - * 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...` - */ - function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration | JSDocSignature, parameters: Symbol[]): boolean { - if (isJSDocSignature(declaration) || !containsArgumentsReference(declaration)) { - return false; + // If only one accessor includes a this-type annotation, the other behaves as if it had the same type annotation + if ((declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) && + hasBindableName(declaration) && + (!hasThisParameter || !thisParameter)) { + const otherKind = declaration.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const other = getDeclarationOfKind(getSymbolOfNode(declaration), otherKind); + if (other) { + thisParameter = getAnnotatedAccessorThisParameter(other); + } } - const lastParam = lastOrUndefined(declaration.parameters); - const lastParamTags = lastParam ? getJSDocParameterTags(lastParam) : getJSDocTags(declaration).filter(isJSDocParameterTag); - const lastParamVariadicType = firstDefined(lastParamTags, p => - p.typeExpression && isJSDocVariadicType(p.typeExpression.type) ? p.typeExpression.type : undefined); - const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args" as __String, CheckFlags.RestParameter); - syntheticArgsSymbol.type = lastParamVariadicType ? createArrayType(getTypeFromTypeNode(lastParamVariadicType.type)) : anyArrayType; - if (lastParamVariadicType) { - // Replace the last parameter with a rest parameter. - parameters.pop(); + const classType = declaration.kind === SyntaxKind.Constructor ? + getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol)) + : undefined; + const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); + if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) { + flags |= SignatureFlags.HasRestParameter; } - parameters.push(syntheticArgsSymbol); - return true; + if (isConstructorTypeNode(declaration) && hasSyntacticModifier(declaration, ModifierFlags.Abstract) || + isConstructorDeclaration(declaration) && hasSyntacticModifier(declaration.parent, ModifierFlags.Abstract)) { + flags |= SignatureFlags.Abstract; + } + links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, + /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, + minArgumentCount, flags); } + return links.resolvedSignature; + } - function getSignatureOfTypeTag(node: SignatureDeclaration | JSDocSignature) { - // should be attached to a function declaration or expression - if (!(isInJSFile(node) && isFunctionLikeDeclaration(node))) return undefined; - const typeTag = getJSDocTypeTag(node); - return typeTag?.typeExpression && getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression)); + /** + * A JS function gets a synthetic rest parameter if it references `arguments` AND: + * 1. It has no parameters but at least one `@param` with a type that starts with `...` + * OR + * 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...` + */ + function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration | JSDocSignature, parameters: Symbol[]): boolean { + if (isJSDocSignature(declaration) || !containsArgumentsReference(declaration)) { + return false; } + const lastParam = lastOrUndefined(declaration.parameters); + const lastParamTags = lastParam ? getJSDocParameterTags(lastParam) : getJSDocTags(declaration).filter(isJSDocParameterTag); + const lastParamVariadicType = firstDefined(lastParamTags, p => + p.typeExpression && isJSDocVariadicType(p.typeExpression.type) ? p.typeExpression.type : undefined); - function getReturnTypeOfTypeTag(node: SignatureDeclaration | JSDocSignature) { - const signature = getSignatureOfTypeTag(node); - return signature && getReturnTypeOfSignature(signature); + const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args" as __String, CheckFlags.RestParameter); + syntheticArgsSymbol.type = lastParamVariadicType ? createArrayType(getTypeFromTypeNode(lastParamVariadicType.type)) : anyArrayType; + if (lastParamVariadicType) { + // Replace the last parameter with a rest parameter. + parameters.pop(); } + parameters.push(syntheticArgsSymbol); + return true; + } - function containsArgumentsReference(declaration: SignatureDeclaration): boolean { - const links = getNodeLinks(declaration); - if (links.containsArgumentsReference === undefined) { - if (links.flags & NodeCheckFlags.CaptureArguments) { - links.containsArgumentsReference = true; - } - else { - links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body!); - } + function getSignatureOfTypeTag(node: SignatureDeclaration | JSDocSignature) { + // should be attached to a function declaration or expression + if (!(isInJSFile(node) && isFunctionLikeDeclaration(node))) return undefined; + const typeTag = getJSDocTypeTag(node); + return typeTag?.typeExpression && getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression)); + } + + function getReturnTypeOfTypeTag(node: SignatureDeclaration | JSDocSignature) { + const signature = getSignatureOfTypeTag(node); + return signature && getReturnTypeOfSignature(signature); + } + + function containsArgumentsReference(declaration: SignatureDeclaration): boolean { + const links = getNodeLinks(declaration); + if (links.containsArgumentsReference === undefined) { + if (links.flags & NodeCheckFlags.CaptureArguments) { + links.containsArgumentsReference = true; + } + else { + links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body!); } - return links.containsArgumentsReference; + } + return links.containsArgumentsReference; - function traverse(node: Node): boolean { - if (!node) return false; - switch (node.kind) { - case SyntaxKind.Identifier: - return (node as Identifier).escapedText === argumentsSymbol.escapedName && getReferencedValueSymbol(node as Identifier) === argumentsSymbol; + function traverse(node: Node): boolean { + if (!node) return false; + switch (node.kind) { + case SyntaxKind.Identifier: + return (node as Identifier).escapedText === argumentsSymbol.escapedName && getReferencedValueSymbol(node as Identifier) === argumentsSymbol; - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return (node as NamedDeclaration).name!.kind === SyntaxKind.ComputedPropertyName - && traverse((node as NamedDeclaration).name!); + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return (node as NamedDeclaration).name!.kind === SyntaxKind.ComputedPropertyName + && traverse((node as NamedDeclaration).name!); - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return traverse((node as PropertyAccessExpression | ElementAccessExpression).expression); + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return traverse((node as PropertyAccessExpression | ElementAccessExpression).expression); - default: - return !nodeStartsNewLexicalEnvironment(node) && !isPartOfTypeNode(node) && !!forEachChild(node, traverse); - } + default: + return !nodeStartsNewLexicalEnvironment(node) && !isPartOfTypeNode(node) && !!forEachChild(node, traverse); } } + } - function getSignaturesOfSymbol(symbol: Symbol | undefined): Signature[] { - if (!symbol || !symbol.declarations) return emptyArray; - const result: Signature[] = []; - for (let i = 0; i < symbol.declarations.length; i++) { - const decl = symbol.declarations[i]; - if (!isFunctionLike(decl)) continue; - // Don't include signature if node is the implementation of an overloaded function. A node is considered - // an implementation node if it has a body and the previous node is of the same kind and immediately - // precedes the implementation node (i.e. has the same parent and ends where the implementation starts). - if (i > 0 && (decl as FunctionLikeDeclaration).body) { - const previous = symbol.declarations[i - 1]; - if (decl.parent === previous.parent && decl.kind === previous.kind && decl.pos === previous.end) { - continue; - } + function getSignaturesOfSymbol(symbol: Symbol | undefined): Signature[] { + if (!symbol || !symbol.declarations) return emptyArray; + const result: Signature[] = []; + for (let i = 0; i < symbol.declarations.length; i++) { + const decl = symbol.declarations[i]; + if (!isFunctionLike(decl)) continue; + // Don't include signature if node is the implementation of an overloaded function. A node is considered + // an implementation node if it has a body and the previous node is of the same kind and immediately + // precedes the implementation node (i.e. has the same parent and ends where the implementation starts). + if (i > 0 && (decl as FunctionLikeDeclaration).body) { + const previous = symbol.declarations[i - 1]; + if (decl.parent === previous.parent && decl.kind === previous.kind && decl.pos === previous.end) { + continue; } - result.push(getSignatureFromDeclaration(decl)); } - return result; + result.push(getSignatureFromDeclaration(decl)); } + return result; + } - function resolveExternalModuleTypeByLiteral(name: StringLiteral) { - const moduleSym = resolveExternalModuleName(name, name); - if (moduleSym) { - const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); - if (resolvedModuleSymbol) { - return getTypeOfSymbol(resolvedModuleSymbol); - } + function resolveExternalModuleTypeByLiteral(name: StringLiteral) { + const moduleSym = resolveExternalModuleName(name, name); + if (moduleSym) { + const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); + if (resolvedModuleSymbol) { + return getTypeOfSymbol(resolvedModuleSymbol); } - - return anyType; } - function getThisTypeOfSignature(signature: Signature): Type | undefined { - if (signature.thisParameter) { - return getTypeOfSymbol(signature.thisParameter); - } + return anyType; + } + + function getThisTypeOfSignature(signature: Signature): Type | undefined { + if (signature.thisParameter) { + return getTypeOfSymbol(signature.thisParameter); } + } - function getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined { - if (!signature.resolvedTypePredicate) { - if (signature.target) { - const targetTypePredicate = getTypePredicateOfSignature(signature.target); - signature.resolvedTypePredicate = targetTypePredicate ? instantiateTypePredicate(targetTypePredicate, signature.mapper!) : noTypePredicate; - } - else if (signature.compositeSignatures) { - signature.resolvedTypePredicate = getUnionOrIntersectionTypePredicate(signature.compositeSignatures, signature.compositeKind) || noTypePredicate; - } - else { - const type = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); - let jsdocPredicate: TypePredicate | undefined; - if (!type && isInJSFile(signature.declaration)) { - const jsdocSignature = getSignatureOfTypeTag(signature.declaration!); - if (jsdocSignature && signature !== jsdocSignature) { - jsdocPredicate = getTypePredicateOfSignature(jsdocSignature); - } + function getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined { + if (!signature.resolvedTypePredicate) { + if (signature.target) { + const targetTypePredicate = getTypePredicateOfSignature(signature.target); + signature.resolvedTypePredicate = targetTypePredicate ? instantiateTypePredicate(targetTypePredicate, signature.mapper!) : noTypePredicate; + } + else if (signature.compositeSignatures) { + signature.resolvedTypePredicate = getUnionOrIntersectionTypePredicate(signature.compositeSignatures, signature.compositeKind) || noTypePredicate; + } + else { + const type = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); + let jsdocPredicate: TypePredicate | undefined; + if (!type && isInJSFile(signature.declaration)) { + const jsdocSignature = getSignatureOfTypeTag(signature.declaration!); + if (jsdocSignature && signature !== jsdocSignature) { + jsdocPredicate = getTypePredicateOfSignature(jsdocSignature); } - signature.resolvedTypePredicate = type && isTypePredicateNode(type) ? - createTypePredicateFromTypePredicateNode(type, signature) : - jsdocPredicate || noTypePredicate; } - Debug.assert(!!signature.resolvedTypePredicate); + signature.resolvedTypePredicate = type && isTypePredicateNode(type) ? + createTypePredicateFromTypePredicateNode(type, signature) : + jsdocPredicate || noTypePredicate; } - return signature.resolvedTypePredicate === noTypePredicate ? undefined : signature.resolvedTypePredicate; + Debug.assert(!!signature.resolvedTypePredicate); } + return signature.resolvedTypePredicate === noTypePredicate ? undefined : signature.resolvedTypePredicate; + } - function createTypePredicateFromTypePredicateNode(node: TypePredicateNode, signature: Signature): TypePredicate { - const parameterName = node.parameterName; - const type = node.type && getTypeFromTypeNode(node.type); - return parameterName.kind === SyntaxKind.ThisType ? - createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsThis : TypePredicateKind.This, /*parameterName*/ undefined, /*parameterIndex*/ undefined, type) : - createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsIdentifier : TypePredicateKind.Identifier, parameterName.escapedText as string, - findIndex(signature.parameters, p => p.escapedName === parameterName.escapedText), type); - } + function createTypePredicateFromTypePredicateNode(node: TypePredicateNode, signature: Signature): TypePredicate { + const parameterName = node.parameterName; + const type = node.type && getTypeFromTypeNode(node.type); + return parameterName.kind === SyntaxKind.ThisType ? + createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsThis : TypePredicateKind.This, /*parameterName*/ undefined, /*parameterIndex*/ undefined, type) : + createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsIdentifier : TypePredicateKind.Identifier, parameterName.escapedText as string, + findIndex(signature.parameters, p => p.escapedName === parameterName.escapedText), type); + } - function getUnionOrIntersectionType(types: Type[], kind: TypeFlags | undefined, unionReduction?: UnionReduction) { - return kind !== TypeFlags.Intersection ? getUnionType(types, unionReduction) : getIntersectionType(types); - } + function getUnionOrIntersectionType(types: Type[], kind: TypeFlags | undefined, unionReduction?: UnionReduction) { + return kind !== TypeFlags.Intersection ? getUnionType(types, unionReduction) : getIntersectionType(types); + } - function getReturnTypeOfSignature(signature: Signature): Type { - if (!signature.resolvedReturnType) { - if (!pushTypeResolution(signature, TypeSystemPropertyName.ResolvedReturnType)) { - return errorType; - } - let type = signature.target ? instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper) : - signature.compositeSignatures ? instantiateType(getUnionOrIntersectionType(map(signature.compositeSignatures, getReturnTypeOfSignature), signature.compositeKind, UnionReduction.Subtype), signature.mapper) : - getReturnTypeFromAnnotation(signature.declaration!) || - (nodeIsMissing((signature.declaration as FunctionLikeDeclaration).body) ? anyType : getReturnTypeFromBody(signature.declaration as FunctionLikeDeclaration)); - if (signature.flags & SignatureFlags.IsInnerCallChain) { - type = addOptionalTypeMarker(type); - } - else if (signature.flags & SignatureFlags.IsOuterCallChain) { - type = getOptionalType(type); - } - if (!popTypeResolution()) { - if (signature.declaration) { - const typeNode = getEffectiveReturnTypeNode(signature.declaration); - if (typeNode) { - error(typeNode, Diagnostics.Return_type_annotation_circularly_references_itself); + function getReturnTypeOfSignature(signature: Signature): Type { + if (!signature.resolvedReturnType) { + if (!pushTypeResolution(signature, TypeSystemPropertyName.ResolvedReturnType)) { + return errorType; + } + let type = signature.target ? instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper) : + signature.compositeSignatures ? instantiateType(getUnionOrIntersectionType(map(signature.compositeSignatures, getReturnTypeOfSignature), signature.compositeKind, UnionReduction.Subtype), signature.mapper) : + getReturnTypeFromAnnotation(signature.declaration!) || + (nodeIsMissing((signature.declaration as FunctionLikeDeclaration).body) ? anyType : getReturnTypeFromBody(signature.declaration as FunctionLikeDeclaration)); + if (signature.flags & SignatureFlags.IsInnerCallChain) { + type = addOptionalTypeMarker(type); + } + else if (signature.flags & SignatureFlags.IsOuterCallChain) { + type = getOptionalType(type); + } + if (!popTypeResolution()) { + if (signature.declaration) { + const typeNode = getEffectiveReturnTypeNode(signature.declaration); + if (typeNode) { + error(typeNode, Diagnostics.Return_type_annotation_circularly_references_itself); + } + else if (noImplicitAny) { + const declaration = signature.declaration as Declaration; + const name = getNameOfDeclaration(declaration); + if (name) { + error(name, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, declarationNameToString(name)); } - else if (noImplicitAny) { - const declaration = signature.declaration as Declaration; - const name = getNameOfDeclaration(declaration); - if (name) { - error(name, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, declarationNameToString(name)); - } - else { - error(declaration, Diagnostics.Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions); - } + else { + error(declaration, Diagnostics.Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions); } } - type = anyType; } - signature.resolvedReturnType = type; + type = anyType; } - return signature.resolvedReturnType; + signature.resolvedReturnType = type; } + return signature.resolvedReturnType; + } - function getReturnTypeFromAnnotation(declaration: SignatureDeclaration | JSDocSignature) { - if (declaration.kind === SyntaxKind.Constructor) { - return getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol)); - } - if (isJSDocConstructSignature(declaration)) { - return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type!); // TODO: GH#18217 + function getReturnTypeFromAnnotation(declaration: SignatureDeclaration | JSDocSignature) { + if (declaration.kind === SyntaxKind.Constructor) { + return getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol)); + } + if (isJSDocConstructSignature(declaration)) { + return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type!); // TODO: GH#18217 + } + const typeNode = getEffectiveReturnTypeNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); + } + if (declaration.kind === SyntaxKind.GetAccessor && hasBindableName(declaration)) { + const jsDocType = isInJSFile(declaration) && getTypeForDeclarationFromJSDocComment(declaration); + if (jsDocType) { + return jsDocType; } - const typeNode = getEffectiveReturnTypeNode(declaration); - if (typeNode) { - return getTypeFromTypeNode(typeNode); - } - if (declaration.kind === SyntaxKind.GetAccessor && hasBindableName(declaration)) { - const jsDocType = isInJSFile(declaration) && getTypeForDeclarationFromJSDocComment(declaration); - if (jsDocType) { - return jsDocType; - } - const setter = getDeclarationOfKind(getSymbolOfNode(declaration), SyntaxKind.SetAccessor); - const setterType = getAnnotatedAccessorType(setter); - if (setterType) { - return setterType; - } + const setter = getDeclarationOfKind(getSymbolOfNode(declaration), SyntaxKind.SetAccessor); + const setterType = getAnnotatedAccessorType(setter); + if (setterType) { + return setterType; } - return getReturnTypeOfTypeTag(declaration); - } - - function isResolvingReturnTypeOfSignature(signature: Signature) { - return !signature.resolvedReturnType && findResolutionCycleStartIndex(signature, TypeSystemPropertyName.ResolvedReturnType) >= 0; } + return getReturnTypeOfTypeTag(declaration); + } - function getRestTypeOfSignature(signature: Signature): Type { - return tryGetRestTypeOfSignature(signature) || anyType; - } + function isResolvingReturnTypeOfSignature(signature: Signature) { + return !signature.resolvedReturnType && findResolutionCycleStartIndex(signature, TypeSystemPropertyName.ResolvedReturnType) >= 0; + } - function tryGetRestTypeOfSignature(signature: Signature): Type | undefined { - if (signatureHasRestParameter(signature)) { - const sigRestType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - const restType = isTupleType(sigRestType) ? getRestTypeOfTupleType(sigRestType) : sigRestType; - return restType && getIndexTypeOfType(restType, numberType); - } - return undefined; - } + function getRestTypeOfSignature(signature: Signature): Type { + return tryGetRestTypeOfSignature(signature) || anyType; + } - function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: readonly TypeParameter[]): Signature { - const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript)); - if (inferredTypeParameters) { - const returnSignature = getSingleCallOrConstructSignature(getReturnTypeOfSignature(instantiatedSignature)); - if (returnSignature) { - const newReturnSignature = cloneSignature(returnSignature); - newReturnSignature.typeParameters = inferredTypeParameters; - const newInstantiatedSignature = cloneSignature(instantiatedSignature); - newInstantiatedSignature.resolvedReturnType = getOrCreateTypeFromSignature(newReturnSignature); - return newInstantiatedSignature; - } - } - return instantiatedSignature; + function tryGetRestTypeOfSignature(signature: Signature): Type | undefined { + if (signatureHasRestParameter(signature)) { + const sigRestType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + const restType = isTupleType(sigRestType) ? getRestTypeOfTupleType(sigRestType) : sigRestType; + return restType && getIndexTypeOfType(restType, numberType); } + return undefined; + } - function getSignatureInstantiationWithoutFillingInTypeArguments(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { - const instantiations = signature.instantiations || (signature.instantiations = new Map()); - const id = getTypeListId(typeArguments); - let instantiation = instantiations.get(id); - if (!instantiation) { - instantiations.set(id, instantiation = createSignatureInstantiation(signature, typeArguments)); + function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: readonly TypeParameter[]): Signature { + const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript)); + if (inferredTypeParameters) { + const returnSignature = getSingleCallOrConstructSignature(getReturnTypeOfSignature(instantiatedSignature)); + if (returnSignature) { + const newReturnSignature = cloneSignature(returnSignature); + newReturnSignature.typeParameters = inferredTypeParameters; + const newInstantiatedSignature = cloneSignature(instantiatedSignature); + newInstantiatedSignature.resolvedReturnType = getOrCreateTypeFromSignature(newReturnSignature); + return newInstantiatedSignature; } - return instantiation; } + return instantiatedSignature; + } - function createSignatureInstantiation(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { - return instantiateSignature(signature, createSignatureTypeMapper(signature, typeArguments), /*eraseTypeParameters*/ true); + function getSignatureInstantiationWithoutFillingInTypeArguments(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { + const instantiations = signature.instantiations || (signature.instantiations = new Map()); + const id = getTypeListId(typeArguments); + let instantiation = instantiations.get(id); + if (!instantiation) { + instantiations.set(id, instantiation = createSignatureInstantiation(signature, typeArguments)); } + return instantiation; + } - function createSignatureTypeMapper(signature: Signature, typeArguments: readonly Type[] | undefined): TypeMapper { - return createTypeMapper(signature.typeParameters!, typeArguments); - } + function createSignatureInstantiation(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { + return instantiateSignature(signature, createSignatureTypeMapper(signature, typeArguments), /*eraseTypeParameters*/ true); + } - function getErasedSignature(signature: Signature): Signature { - return signature.typeParameters ? - signature.erasedSignatureCache || (signature.erasedSignatureCache = createErasedSignature(signature)) : - signature; - } + function createSignatureTypeMapper(signature: Signature, typeArguments: readonly Type[] | undefined): TypeMapper { + return createTypeMapper(signature.typeParameters!, typeArguments); + } - function createErasedSignature(signature: Signature) { - // Create an instantiation of the signature where all type arguments are the any type. - return instantiateSignature(signature, createTypeEraser(signature.typeParameters!), /*eraseTypeParameters*/ true); - } + function getErasedSignature(signature: Signature): Signature { + return signature.typeParameters ? + signature.erasedSignatureCache || (signature.erasedSignatureCache = createErasedSignature(signature)) : + signature; + } - function getCanonicalSignature(signature: Signature): Signature { - return signature.typeParameters ? - signature.canonicalSignatureCache || (signature.canonicalSignatureCache = createCanonicalSignature(signature)) : - signature; - } + function createErasedSignature(signature: Signature) { + // Create an instantiation of the signature where all type arguments are the any type. + return instantiateSignature(signature, createTypeEraser(signature.typeParameters!), /*eraseTypeParameters*/ true); + } - function createCanonicalSignature(signature: Signature) { - // Create an instantiation of the signature where each unconstrained type parameter is replaced with - // its original. When a generic class or interface is instantiated, each generic method in the class or - // interface is instantiated with a fresh set of cloned type parameters (which we need to handle scenarios - // where different generations of the same type parameter are in scope). This leads to a lot of new type - // identities, and potentially a lot of work comparing those identities, so here we create an instantiation - // that uses the original type identities for all unconstrained type parameters. - return getSignatureInstantiation( - signature, - map(signature.typeParameters, tp => tp.target && !getConstraintOfTypeParameter(tp.target) ? tp.target : tp), - isInJSFile(signature.declaration)); - } + function getCanonicalSignature(signature: Signature): Signature { + return signature.typeParameters ? + signature.canonicalSignatureCache || (signature.canonicalSignatureCache = createCanonicalSignature(signature)) : + signature; + } - function getBaseSignature(signature: Signature) { - const typeParameters = signature.typeParameters; - if (typeParameters) { - if (signature.baseSignatureCache) { - return signature.baseSignatureCache; - } - const typeEraser = createTypeEraser(typeParameters); - const baseConstraintMapper = createTypeMapper(typeParameters, map(typeParameters, tp => getConstraintOfTypeParameter(tp) || unknownType)); - let baseConstraints: readonly Type[] = map(typeParameters, tp => instantiateType(tp, baseConstraintMapper) || unknownType); - // Run N type params thru the immediate constraint mapper up to N times - // This way any noncircular interdependent type parameters are definitely resolved to their external dependencies - for (let i = 0; i < typeParameters.length - 1; i++) { - baseConstraints = instantiateTypes(baseConstraints, baseConstraintMapper); - } - // and then apply a type eraser to remove any remaining circularly dependent type parameters - baseConstraints = instantiateTypes(baseConstraints, typeEraser); - return signature.baseSignatureCache = instantiateSignature(signature, createTypeMapper(typeParameters, baseConstraints), /*eraseTypeParameters*/ true); - } - return signature; - } + function createCanonicalSignature(signature: Signature) { + // Create an instantiation of the signature where each unconstrained type parameter is replaced with + // its original. When a generic class or interface is instantiated, each generic method in the class or + // interface is instantiated with a fresh set of cloned type parameters (which we need to handle scenarios + // where different generations of the same type parameter are in scope). This leads to a lot of new type + // identities, and potentially a lot of work comparing those identities, so here we create an instantiation + // that uses the original type identities for all unconstrained type parameters. + return getSignatureInstantiation( + signature, + map(signature.typeParameters, tp => tp.target && !getConstraintOfTypeParameter(tp.target) ? tp.target : tp), + isInJSFile(signature.declaration)); + } - function getOrCreateTypeFromSignature(signature: Signature): ObjectType { - // There are two ways to declare a construct signature, one is by declaring a class constructor - // using the constructor keyword, and the other is declaring a bare construct signature in an - // object type literal or interface (using the new keyword). Each way of declaring a constructor - // will result in a different declaration kind. - if (!signature.isolatedSignatureType) { - const kind = signature.declaration ? signature.declaration.kind : SyntaxKind.Unknown; - const isConstructor = kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType; - const type = createObjectType(ObjectFlags.Anonymous); - type.members = emptySymbols; - type.properties = emptyArray; - type.callSignatures = !isConstructor ? [signature] : emptyArray; - type.constructSignatures = isConstructor ? [signature] : emptyArray; - type.indexInfos = emptyArray; - signature.isolatedSignatureType = type; - } + function getBaseSignature(signature: Signature) { + const typeParameters = signature.typeParameters; + if (typeParameters) { + if (signature.baseSignatureCache) { + return signature.baseSignatureCache; + } + const typeEraser = createTypeEraser(typeParameters); + const baseConstraintMapper = createTypeMapper(typeParameters, map(typeParameters, tp => getConstraintOfTypeParameter(tp) || unknownType)); + let baseConstraints: readonly Type[] = map(typeParameters, tp => instantiateType(tp, baseConstraintMapper) || unknownType); + // Run N type params thru the immediate constraint mapper up to N times + // This way any noncircular interdependent type parameters are definitely resolved to their external dependencies + for (let i = 0; i < typeParameters.length - 1; i++) { + baseConstraints = instantiateTypes(baseConstraints, baseConstraintMapper); + } + // and then apply a type eraser to remove any remaining circularly dependent type parameters + baseConstraints = instantiateTypes(baseConstraints, typeEraser); + return signature.baseSignatureCache = instantiateSignature(signature, createTypeMapper(typeParameters, baseConstraints), /*eraseTypeParameters*/ true); + } + return signature; + } - return signature.isolatedSignatureType; - } + function getOrCreateTypeFromSignature(signature: Signature): ObjectType { + // There are two ways to declare a construct signature, one is by declaring a class constructor + // using the constructor keyword, and the other is declaring a bare construct signature in an + // object type literal or interface (using the new keyword). Each way of declaring a constructor + // will result in a different declaration kind. + if (!signature.isolatedSignatureType) { + const kind = signature.declaration ? signature.declaration.kind : SyntaxKind.Unknown; + const isConstructor = kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType; + const type = createObjectType(ObjectFlags.Anonymous); + type.members = emptySymbols; + type.properties = emptyArray; + type.callSignatures = !isConstructor ? [signature] : emptyArray; + type.constructSignatures = isConstructor ? [signature] : emptyArray; + type.indexInfos = emptyArray; + signature.isolatedSignatureType = type; + } + + return signature.isolatedSignatureType; + } - function getIndexSymbol(symbol: Symbol): Symbol | undefined { - return symbol.members ? getIndexSymbolFromSymbolTable(symbol.members) : undefined; - } + function getIndexSymbol(symbol: Symbol): Symbol | undefined { + return symbol.members ? getIndexSymbolFromSymbolTable(symbol.members) : undefined; + } - function getIndexSymbolFromSymbolTable(symbolTable: SymbolTable): Symbol | undefined { - return symbolTable.get(InternalSymbolName.Index); - } + function getIndexSymbolFromSymbolTable(symbolTable: SymbolTable): Symbol | undefined { + return symbolTable.get(InternalSymbolName.Index); + } - function createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo { - return { keyType, type, isReadonly, declaration }; - } + function createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo { + return { keyType, type, isReadonly, declaration }; + } - function getIndexInfosOfSymbol(symbol: Symbol): IndexInfo[] { - const indexSymbol = getIndexSymbol(symbol); - return indexSymbol ? getIndexInfosOfIndexSymbol(indexSymbol) : emptyArray; - } + function getIndexInfosOfSymbol(symbol: Symbol): IndexInfo[] { + const indexSymbol = getIndexSymbol(symbol); + return indexSymbol ? getIndexInfosOfIndexSymbol(indexSymbol) : emptyArray; + } - function getIndexInfosOfIndexSymbol(indexSymbol: Symbol): IndexInfo[] { - if (indexSymbol.declarations) { - const indexInfos: IndexInfo[] = []; - for (const declaration of (indexSymbol.declarations as IndexSignatureDeclaration[])) { - if (declaration.parameters.length === 1) { - const parameter = declaration.parameters[0]; - if (parameter.type) { - forEachType(getTypeFromTypeNode(parameter.type), keyType => { - if (isValidIndexKeyType(keyType) && !findIndexInfo(indexInfos, keyType)) { - indexInfos.push(createIndexInfo(keyType, declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, - hasEffectiveModifier(declaration, ModifierFlags.Readonly), declaration)); - } - }); - } + function getIndexInfosOfIndexSymbol(indexSymbol: Symbol): IndexInfo[] { + if (indexSymbol.declarations) { + const indexInfos: IndexInfo[] = []; + for (const declaration of (indexSymbol.declarations as IndexSignatureDeclaration[])) { + if (declaration.parameters.length === 1) { + const parameter = declaration.parameters[0]; + if (parameter.type) { + forEachType(getTypeFromTypeNode(parameter.type), keyType => { + if (isValidIndexKeyType(keyType) && !findIndexInfo(indexInfos, keyType)) { + indexInfos.push(createIndexInfo(keyType, declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, + hasEffectiveModifier(declaration, ModifierFlags.Readonly), declaration)); + } + }); } } - return indexInfos; } - return emptyArray; + return indexInfos; } + return emptyArray; + } - function isValidIndexKeyType(type: Type): boolean { - return !!(type.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.ESSymbol)) || isPatternLiteralType(type) || - !!(type.flags & TypeFlags.Intersection) && !isGenericType(type) && some((type as IntersectionType).types, isValidIndexKeyType); - } - - function getConstraintDeclaration(type: TypeParameter): TypeNode | undefined { - return mapDefined(filter(type.symbol && type.symbol.declarations, isTypeParameterDeclaration), getEffectiveConstraintOfTypeParameter)[0]; - } - - function getInferredTypeParameterConstraint(typeParameter: TypeParameter) { - let inferences: Type[] | undefined; - if (typeParameter.symbol?.declarations) { - for (const declaration of typeParameter.symbol.declarations) { - if (declaration.parent.kind === SyntaxKind.InferType) { - // When an 'infer T' declaration is immediately contained in a type reference node - // (such as 'Foo'), T's constraint is inferred from the constraint of the - // corresponding type parameter in 'Foo'. When multiple 'infer T' declarations are - // present, we form an intersection of the inferred constraint types. - const [childTypeParameter = declaration.parent, grandParent] = walkUpParenthesizedTypesAndGetParentAndChild(declaration.parent.parent); - if (grandParent.kind === SyntaxKind.TypeReference) { - const typeReference = grandParent as TypeReferenceNode; - const typeParameters = getTypeParametersForTypeReference(typeReference); - if (typeParameters) { - const index = typeReference.typeArguments!.indexOf(childTypeParameter as TypeNode); - if (index < typeParameters.length) { - const declaredConstraint = getConstraintOfTypeParameter(typeParameters[index]); - if (declaredConstraint) { - // Type parameter constraints can reference other type parameters so - // constraints need to be instantiated. If instantiation produces the - // type parameter itself, we discard that inference. For example, in - // type Foo = [T, U]; - // type Bar = T extends Foo ? Foo : T; - // the instantiated constraint for U is X, so we discard that inference. - const mapper = createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReference, typeParameters)); - const constraint = instantiateType(declaredConstraint, mapper); - if (constraint !== typeParameter) { - inferences = append(inferences, constraint); - } + function isValidIndexKeyType(type: Type): boolean { + return !!(type.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.ESSymbol)) || isPatternLiteralType(type) || + !!(type.flags & TypeFlags.Intersection) && !isGenericType(type) && some((type as IntersectionType).types, isValidIndexKeyType); + } + + function getConstraintDeclaration(type: TypeParameter): TypeNode | undefined { + return mapDefined(filter(type.symbol && type.symbol.declarations, isTypeParameterDeclaration), getEffectiveConstraintOfTypeParameter)[0]; + } + + function getInferredTypeParameterConstraint(typeParameter: TypeParameter) { + let inferences: Type[] | undefined; + if (typeParameter.symbol?.declarations) { + for (const declaration of typeParameter.symbol.declarations) { + if (declaration.parent.kind === SyntaxKind.InferType) { + // When an 'infer T' declaration is immediately contained in a type reference node + // (such as 'Foo'), T's constraint is inferred from the constraint of the + // corresponding type parameter in 'Foo'. When multiple 'infer T' declarations are + // present, we form an intersection of the inferred constraint types. + const [childTypeParameter = declaration.parent, grandParent] = walkUpParenthesizedTypesAndGetParentAndChild(declaration.parent.parent); + if (grandParent.kind === SyntaxKind.TypeReference) { + const typeReference = grandParent as TypeReferenceNode; + const typeParameters = getTypeParametersForTypeReference(typeReference); + if (typeParameters) { + const index = typeReference.typeArguments!.indexOf(childTypeParameter as TypeNode); + if (index < typeParameters.length) { + const declaredConstraint = getConstraintOfTypeParameter(typeParameters[index]); + if (declaredConstraint) { + // Type parameter constraints can reference other type parameters so + // constraints need to be instantiated. If instantiation produces the + // type parameter itself, we discard that inference. For example, in + // type Foo = [T, U]; + // type Bar = T extends Foo ? Foo : T; + // the instantiated constraint for U is X, so we discard that inference. + const mapper = createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReference, typeParameters)); + const constraint = instantiateType(declaredConstraint, mapper); + if (constraint !== typeParameter) { + inferences = append(inferences, constraint); } } } } - // When an 'infer T' declaration is immediately contained in a rest parameter declaration, a rest type - // or a named rest tuple element, we infer an 'unknown[]' constraint. - else if (grandParent.kind === SyntaxKind.Parameter && (grandParent as ParameterDeclaration).dotDotDotToken || - grandParent.kind === SyntaxKind.RestType || - grandParent.kind === SyntaxKind.NamedTupleMember && (grandParent as NamedTupleMember).dotDotDotToken) { - inferences = append(inferences, createArrayType(unknownType)); - } - // When an 'infer T' declaration is immediately contained in a string template type, we infer a 'string' - // constraint. - else if (grandParent.kind === SyntaxKind.TemplateLiteralTypeSpan) { - inferences = append(inferences, stringType); - } - // When an 'infer T' declaration is in the constraint position of a mapped type, we infer a 'keyof any' - // constraint. - else if (grandParent.kind === SyntaxKind.TypeParameter && grandParent.parent.kind === SyntaxKind.MappedType) { - inferences = append(inferences, keyofConstraintType); - } - // When an 'infer T' declaration is the template of a mapped type, and that mapped type is the extends - // clause of a conditional whose check type is also a mapped type, give it a constraint equal to the template - // of the check type's mapped type - else if (grandParent.kind === SyntaxKind.MappedType && (grandParent as MappedTypeNode).type && - skipParentheses((grandParent as MappedTypeNode).type!) === declaration.parent && grandParent.parent.kind === SyntaxKind.ConditionalType && - (grandParent.parent as ConditionalTypeNode).extendsType === grandParent && (grandParent.parent as ConditionalTypeNode).checkType.kind === SyntaxKind.MappedType && - ((grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode).type) { - const checkMappedType = (grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode; - const nodeType = getTypeFromTypeNode(checkMappedType.type!); - inferences = append(inferences, instantiateType(nodeType, - makeUnaryTypeMapper(getDeclaredTypeOfTypeParameter(getSymbolOfNode(checkMappedType.typeParameter)), checkMappedType.typeParameter.constraint ? getTypeFromTypeNode(checkMappedType.typeParameter.constraint) : keyofConstraintType) - )); - } + } + // When an 'infer T' declaration is immediately contained in a rest parameter declaration, a rest type + // or a named rest tuple element, we infer an 'unknown[]' constraint. + else if (grandParent.kind === SyntaxKind.Parameter && (grandParent as ParameterDeclaration).dotDotDotToken || + grandParent.kind === SyntaxKind.RestType || + grandParent.kind === SyntaxKind.NamedTupleMember && (grandParent as NamedTupleMember).dotDotDotToken) { + inferences = append(inferences, createArrayType(unknownType)); + } + // When an 'infer T' declaration is immediately contained in a string template type, we infer a 'string' + // constraint. + else if (grandParent.kind === SyntaxKind.TemplateLiteralTypeSpan) { + inferences = append(inferences, stringType); + } + // When an 'infer T' declaration is in the constraint position of a mapped type, we infer a 'keyof any' + // constraint. + else if (grandParent.kind === SyntaxKind.TypeParameter && grandParent.parent.kind === SyntaxKind.MappedType) { + inferences = append(inferences, keyofConstraintType); + } + // When an 'infer T' declaration is the template of a mapped type, and that mapped type is the extends + // clause of a conditional whose check type is also a mapped type, give it a constraint equal to the template + // of the check type's mapped type + else if (grandParent.kind === SyntaxKind.MappedType && (grandParent as MappedTypeNode).type && + skipParentheses((grandParent as MappedTypeNode).type!) === declaration.parent && grandParent.parent.kind === SyntaxKind.ConditionalType && + (grandParent.parent as ConditionalTypeNode).extendsType === grandParent && (grandParent.parent as ConditionalTypeNode).checkType.kind === SyntaxKind.MappedType && + ((grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode).type) { + const checkMappedType = (grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode; + const nodeType = getTypeFromTypeNode(checkMappedType.type!); + inferences = append(inferences, instantiateType(nodeType, + makeUnaryTypeMapper(getDeclaredTypeOfTypeParameter(getSymbolOfNode(checkMappedType.typeParameter)), checkMappedType.typeParameter.constraint ? getTypeFromTypeNode(checkMappedType.typeParameter.constraint) : keyofConstraintType) + )); } } } - return inferences && getIntersectionType(inferences); } + return inferences && getIntersectionType(inferences); + } - /** This is a worker function. Use getConstraintOfTypeParameter which guards against circular constraints. */ - function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type | undefined { - if (!typeParameter.constraint) { - if (typeParameter.target) { - const targetConstraint = getConstraintOfTypeParameter(typeParameter.target); - typeParameter.constraint = targetConstraint ? instantiateType(targetConstraint, typeParameter.mapper) : noConstraintType; + /** This is a worker function. Use getConstraintOfTypeParameter which guards against circular constraints. */ + function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type | undefined { + if (!typeParameter.constraint) { + if (typeParameter.target) { + const targetConstraint = getConstraintOfTypeParameter(typeParameter.target); + typeParameter.constraint = targetConstraint ? instantiateType(targetConstraint, typeParameter.mapper) : noConstraintType; + } + else { + const constraintDeclaration = getConstraintDeclaration(typeParameter); + if (!constraintDeclaration) { + typeParameter.constraint = getInferredTypeParameterConstraint(typeParameter) || noConstraintType; } else { - const constraintDeclaration = getConstraintDeclaration(typeParameter); - if (!constraintDeclaration) { - typeParameter.constraint = getInferredTypeParameterConstraint(typeParameter) || noConstraintType; - } - else { - let type = getTypeFromTypeNode(constraintDeclaration); - if (type.flags & TypeFlags.Any && !isErrorType(type)) { // Allow errorType to propegate to keep downstream errors suppressed - // use keyofConstraintType as the base constraint for mapped type key constraints (unknown isn;t assignable to that, but `any` was), - // use unknown otherwise - type = constraintDeclaration.parent.parent.kind === SyntaxKind.MappedType ? keyofConstraintType : unknownType; - } - typeParameter.constraint = type; + let type = getTypeFromTypeNode(constraintDeclaration); + if (type.flags & TypeFlags.Any && !isErrorType(type)) { // Allow errorType to propegate to keep downstream errors suppressed + // use keyofConstraintType as the base constraint for mapped type key constraints (unknown isn;t assignable to that, but `any` was), + // use unknown otherwise + type = constraintDeclaration.parent.parent.kind === SyntaxKind.MappedType ? keyofConstraintType : unknownType; } + typeParameter.constraint = type; } } - return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint; } + return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint; + } - function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined { - const tp = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; - const host = isJSDocTemplateTag(tp.parent) ? getEffectiveContainerForJSDocTemplateTag(tp.parent) : tp.parent; - return host && getSymbolOfNode(host); - } + function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined { + const tp = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; + const host = isJSDocTemplateTag(tp.parent) ? getEffectiveContainerForJSDocTemplateTag(tp.parent) : tp.parent; + return host && getSymbolOfNode(host); + } - function getTypeListId(types: readonly Type[] | undefined) { - let result = ""; - if (types) { - const length = types.length; - let i = 0; - while (i < length) { - const startId = types[i].id; - let count = 1; - while (i + count < length && types[i + count].id === startId + count) { - count++; - } - if (result.length) { - result += ","; - } - result += startId; - if (count > 1) { - result += ":" + count; - } - i += count; + function getTypeListId(types: readonly Type[] | undefined) { + let result = ""; + if (types) { + const length = types.length; + let i = 0; + while (i < length) { + const startId = types[i].id; + let count = 1; + while (i + count < length && types[i + count].id === startId + count) { + count++; } + if (result.length) { + result += ","; + } + result += startId; + if (count > 1) { + result += ":" + count; + } + i += count; } - return result; } + return result; + } - function getAliasId(aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { - return aliasSymbol ? `@${getSymbolId(aliasSymbol)}` + (aliasTypeArguments ? `:${getTypeListId(aliasTypeArguments)}` : "") : ""; - } + function getAliasId(aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { + return aliasSymbol ? `@${getSymbolId(aliasSymbol)}` + (aliasTypeArguments ? `:${getTypeListId(aliasTypeArguments)}` : "") : ""; + } - // This function is used to propagate certain flags when creating new object type references and union types. - // It is only necessary to do so if a constituent type might be the undefined type, the null type, the type - // of an object literal or the anyFunctionType. This is because there are operations in the type checker - // that care about the presence of such types at arbitrary depth in a containing type. - function getPropagatingFlagsOfTypes(types: readonly Type[], excludeKinds: TypeFlags): ObjectFlags { - let result: ObjectFlags = 0; - for (const type of types) { - if (!(type.flags & excludeKinds)) { - result |= getObjectFlags(type); - } + // This function is used to propagate certain flags when creating new object type references and union types. + // It is only necessary to do so if a constituent type might be the undefined type, the null type, the type + // of an object literal or the anyFunctionType. This is because there are operations in the type checker + // that care about the presence of such types at arbitrary depth in a containing type. + function getPropagatingFlagsOfTypes(types: readonly Type[], excludeKinds: TypeFlags): ObjectFlags { + let result: ObjectFlags = 0; + for (const type of types) { + if (!(type.flags & excludeKinds)) { + result |= getObjectFlags(type); } - return result & ObjectFlags.PropagatingFlags; } + return result & ObjectFlags.PropagatingFlags; + } - function createTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined): TypeReference { - const id = getTypeListId(typeArguments); - let type = target.instantiations.get(id); - if (!type) { - type = createObjectType(ObjectFlags.Reference, target.symbol) as TypeReference; - target.instantiations.set(id, type); - type.objectFlags |= typeArguments ? getPropagatingFlagsOfTypes(typeArguments, /*excludeKinds*/ 0) : 0; - type.target = target; - type.resolvedTypeArguments = typeArguments; - } - return type; + function createTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined): TypeReference { + const id = getTypeListId(typeArguments); + let type = target.instantiations.get(id); + if (!type) { + type = createObjectType(ObjectFlags.Reference, target.symbol) as TypeReference; + target.instantiations.set(id, type); + type.objectFlags |= typeArguments ? getPropagatingFlagsOfTypes(typeArguments, /*excludeKinds*/ 0) : 0; + type.target = target; + type.resolvedTypeArguments = typeArguments; } + return type; + } - function cloneTypeReference(source: TypeReference): TypeReference { - const type = createType(source.flags) as TypeReference; - type.symbol = source.symbol; - type.objectFlags = source.objectFlags; - type.target = source.target; - type.resolvedTypeArguments = source.resolvedTypeArguments; - return type; - } + function cloneTypeReference(source: TypeReference): TypeReference { + const type = createType(source.flags) as TypeReference; + type.symbol = source.symbol; + type.objectFlags = source.objectFlags; + type.target = source.target; + type.resolvedTypeArguments = source.resolvedTypeArguments; + return type; + } - function createDeferredTypeReference(target: GenericType, node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, mapper?: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): DeferredTypeReference { - if (!aliasSymbol) { - aliasSymbol = getAliasSymbolForTypeNode(node); - const localAliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); - aliasTypeArguments = mapper ? instantiateTypes(localAliasTypeArguments, mapper) : localAliasTypeArguments; - } - const type = createObjectType(ObjectFlags.Reference, target.symbol) as DeferredTypeReference; - type.target = target; - type.node = node; - type.mapper = mapper; - type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = aliasTypeArguments; - return type; - } + function createDeferredTypeReference(target: GenericType, node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, mapper?: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): DeferredTypeReference { + if (!aliasSymbol) { + aliasSymbol = getAliasSymbolForTypeNode(node); + const localAliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); + aliasTypeArguments = mapper ? instantiateTypes(localAliasTypeArguments, mapper) : localAliasTypeArguments; + } + const type = createObjectType(ObjectFlags.Reference, target.symbol) as DeferredTypeReference; + type.target = target; + type.node = node; + type.mapper = mapper; + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = aliasTypeArguments; + return type; + } - function getTypeArguments(type: TypeReference): readonly Type[] { - if (!type.resolvedTypeArguments) { - if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedTypeArguments)) { - return type.target.localTypeParameters?.map(() => errorType) || emptyArray; - } - const node = type.node; - const typeArguments = !node ? emptyArray : - node.kind === SyntaxKind.TypeReference ? concatenate(type.target.outerTypeParameters, getEffectiveTypeArguments(node, type.target.localTypeParameters!)) : - node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : - map(node.elements, getTypeFromTypeNode); - if (popTypeResolution()) { - type.resolvedTypeArguments = type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments; - } - else { - type.resolvedTypeArguments = type.target.localTypeParameters?.map(() => errorType) || emptyArray; - error( - type.node || currentNode, - type.target.symbol ? Diagnostics.Type_arguments_for_0_circularly_reference_themselves : Diagnostics.Tuple_type_arguments_circularly_reference_themselves, - type.target.symbol && symbolToString(type.target.symbol) - ); - } + function getTypeArguments(type: TypeReference): readonly Type[] { + if (!type.resolvedTypeArguments) { + if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedTypeArguments)) { + return type.target.localTypeParameters?.map(() => errorType) || emptyArray; + } + const node = type.node; + const typeArguments = !node ? emptyArray : + node.kind === SyntaxKind.TypeReference ? concatenate(type.target.outerTypeParameters, getEffectiveTypeArguments(node, type.target.localTypeParameters!)) : + node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : + map(node.elements, getTypeFromTypeNode); + if (popTypeResolution()) { + type.resolvedTypeArguments = type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments; + } + else { + type.resolvedTypeArguments = type.target.localTypeParameters?.map(() => errorType) || emptyArray; + error( + type.node || currentNode, + type.target.symbol ? Diagnostics.Type_arguments_for_0_circularly_reference_themselves : Diagnostics.Tuple_type_arguments_circularly_reference_themselves, + type.target.symbol && symbolToString(type.target.symbol) + ); } - return type.resolvedTypeArguments; } + return type.resolvedTypeArguments; + } - function getTypeReferenceArity(type: TypeReference): number { - return length(type.target.typeParameters); - } + function getTypeReferenceArity(type: TypeReference): number { + return length(type.target.typeParameters); + } - /** - * Get type from type-reference that reference to class or interface - */ - function getTypeFromClassOrInterfaceReference(node: NodeWithTypeArguments, symbol: Symbol): Type { - const type = getDeclaredTypeOfSymbol(getMergedSymbol(symbol)) as InterfaceType; - const typeParameters = type.localTypeParameters; - if (typeParameters) { - const numTypeArguments = length(node.typeArguments); - const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); - const isJs = isInJSFile(node); - const isJsImplicitAny = !noImplicitAny && isJs; - if (!isJsImplicitAny && (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length)) { - const missingAugmentsTag = isJs && isExpressionWithTypeArguments(node) && !isJSDocAugmentsTag(node.parent); - const diag = minTypeArgumentCount === typeParameters.length ? - missingAugmentsTag ? - Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag : - Diagnostics.Generic_type_0_requires_1_type_argument_s : - missingAugmentsTag ? - Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag : - Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments; - - const typeStr = typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType); - error(node, diag, typeStr, minTypeArgumentCount, typeParameters.length); - if (!isJs) { - // TODO: Adopt same permissive behavior in TS as in JS to reduce follow-on editing experience failures (requires editing fillMissingTypeArguments) - return errorType; - } - } - if (node.kind === SyntaxKind.TypeReference && isDeferredTypeReferenceNode(node as TypeReferenceNode, length(node.typeArguments) !== typeParameters.length)) { - return createDeferredTypeReference(type as GenericType, node as TypeReferenceNode, /*mapper*/ undefined); + /** + * Get type from type-reference that reference to class or interface + */ + function getTypeFromClassOrInterfaceReference(node: NodeWithTypeArguments, symbol: Symbol): Type { + const type = getDeclaredTypeOfSymbol(getMergedSymbol(symbol)) as InterfaceType; + const typeParameters = type.localTypeParameters; + if (typeParameters) { + const numTypeArguments = length(node.typeArguments); + const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); + const isJs = isInJSFile(node); + const isJsImplicitAny = !noImplicitAny && isJs; + if (!isJsImplicitAny && (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length)) { + const missingAugmentsTag = isJs && isExpressionWithTypeArguments(node) && !isJSDocAugmentsTag(node.parent); + const diag = minTypeArgumentCount === typeParameters.length ? + missingAugmentsTag ? + Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag : + Diagnostics.Generic_type_0_requires_1_type_argument_s : + missingAugmentsTag ? + Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag : + Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments; + + const typeStr = typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType); + error(node, diag, typeStr, minTypeArgumentCount, typeParameters.length); + if (!isJs) { + // TODO: Adopt same permissive behavior in TS as in JS to reduce follow-on editing experience failures (requires editing fillMissingTypeArguments) + return errorType; } - // In a type reference, the outer type parameters of the referenced class or interface are automatically - // supplied as type arguments and the type reference only specifies arguments for the local type parameters - // of the class or interface. - const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(typeArgumentsFromTypeReferenceNode(node), typeParameters, minTypeArgumentCount, isJs)); - return createTypeReference(type as GenericType, typeArguments); } - return checkNoTypeArguments(node, symbol) ? type : errorType; + if (node.kind === SyntaxKind.TypeReference && isDeferredTypeReferenceNode(node as TypeReferenceNode, length(node.typeArguments) !== typeParameters.length)) { + return createDeferredTypeReference(type as GenericType, node as TypeReferenceNode, /*mapper*/ undefined); + } + // In a type reference, the outer type parameters of the referenced class or interface are automatically + // supplied as type arguments and the type reference only specifies arguments for the local type parameters + // of the class or interface. + const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(typeArgumentsFromTypeReferenceNode(node), typeParameters, minTypeArgumentCount, isJs)); + return createTypeReference(type as GenericType, typeArguments); } + return checkNoTypeArguments(node, symbol) ? type : errorType; + } - function getTypeAliasInstantiation(symbol: Symbol, typeArguments: readonly Type[] | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - const type = getDeclaredTypeOfSymbol(symbol); - if (type === intrinsicMarkerType && intrinsicTypeKinds.has(symbol.escapedName as string) && typeArguments && typeArguments.length === 1) { - return getStringMappingType(symbol, typeArguments[0]); - } - const links = getSymbolLinks(symbol); - const typeParameters = links.typeParameters!; - const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); - let instantiation = links.instantiations!.get(id); - if (!instantiation) { - links.instantiations!.set(id, instantiation = instantiateTypeWithAlias(type, - createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(symbol.valueDeclaration))), - aliasSymbol, aliasTypeArguments)); + function getTypeAliasInstantiation(symbol: Symbol, typeArguments: readonly Type[] | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + const type = getDeclaredTypeOfSymbol(symbol); + if (type === intrinsicMarkerType && intrinsicTypeKinds.has(symbol.escapedName as string) && typeArguments && typeArguments.length === 1) { + return getStringMappingType(symbol, typeArguments[0]); + } + const links = getSymbolLinks(symbol); + const typeParameters = links.typeParameters!; + const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); + let instantiation = links.instantiations!.get(id); + if (!instantiation) { + links.instantiations!.set(id, instantiation = instantiateTypeWithAlias(type, + createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(symbol.valueDeclaration))), + aliasSymbol, aliasTypeArguments)); + } + return instantiation; + } + + /** + * Get type from reference to type alias. When a type alias is generic, the declared type of the type alias may include + * references to the type parameters of the alias. We replace those with the actual type arguments by instantiating the + * declared type. Instantiations are cached using the type identities of the type arguments as the key. + */ + function getTypeFromTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol): Type { + if (getCheckFlags(symbol) & CheckFlags.Unresolved) { + const typeArguments = typeArgumentsFromTypeReferenceNode(node); + const id = getAliasId(symbol, typeArguments); + let errorType = errorTypes.get(id); + if (!errorType) { + errorType = createIntrinsicType(TypeFlags.Any, "error"); + errorType.aliasSymbol = symbol; + errorType.aliasTypeArguments = typeArguments; + errorTypes.set(id, errorType); } - return instantiation; + return errorType; } - - /** - * Get type from reference to type alias. When a type alias is generic, the declared type of the type alias may include - * references to the type parameters of the alias. We replace those with the actual type arguments by instantiating the - * declared type. Instantiations are cached using the type identities of the type arguments as the key. - */ - function getTypeFromTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol): Type { - if (getCheckFlags(symbol) & CheckFlags.Unresolved) { - const typeArguments = typeArgumentsFromTypeReferenceNode(node); - const id = getAliasId(symbol, typeArguments); - let errorType = errorTypes.get(id); - if (!errorType) { - errorType = createIntrinsicType(TypeFlags.Any, "error"); - errorType.aliasSymbol = symbol; - errorType.aliasTypeArguments = typeArguments; - errorTypes.set(id, errorType); - } + const type = getDeclaredTypeOfSymbol(symbol); + const typeParameters = getSymbolLinks(symbol).typeParameters; + if (typeParameters) { + const numTypeArguments = length(node.typeArguments); + const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); + if (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length) { + error(node, + minTypeArgumentCount === typeParameters.length ? + Diagnostics.Generic_type_0_requires_1_type_argument_s : + Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments, + symbolToString(symbol), + minTypeArgumentCount, + typeParameters.length); return errorType; } - const type = getDeclaredTypeOfSymbol(symbol); - const typeParameters = getSymbolLinks(symbol).typeParameters; - if (typeParameters) { - const numTypeArguments = length(node.typeArguments); - const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); - if (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length) { - error(node, - minTypeArgumentCount === typeParameters.length ? - Diagnostics.Generic_type_0_requires_1_type_argument_s : - Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments, - symbolToString(symbol), - minTypeArgumentCount, - typeParameters.length); - return errorType; - } - // We refrain from associating a local type alias with an instantiation of a top-level type alias - // because the local alias may end up being referenced in an inferred return type where it is not - // accessible--which in turn may lead to a large structural expansion of the type when generating - // a .d.ts file. See #43622 for an example. - const aliasSymbol = getAliasSymbolForTypeNode(node); - const newAliasSymbol = aliasSymbol && (isLocalTypeAlias(symbol) || !isLocalTypeAlias(aliasSymbol)) ? aliasSymbol : undefined; - return getTypeAliasInstantiation(symbol, typeArgumentsFromTypeReferenceNode(node), newAliasSymbol, getTypeArgumentsForAliasSymbol(newAliasSymbol)); - } - return checkNoTypeArguments(node, symbol) ? type : errorType; + // We refrain from associating a local type alias with an instantiation of a top-level type alias + // because the local alias may end up being referenced in an inferred return type where it is not + // accessible--which in turn may lead to a large structural expansion of the type when generating + // a .d.ts file. See #43622 for an example. + const aliasSymbol = getAliasSymbolForTypeNode(node); + const newAliasSymbol = aliasSymbol && (isLocalTypeAlias(symbol) || !isLocalTypeAlias(aliasSymbol)) ? aliasSymbol : undefined; + return getTypeAliasInstantiation(symbol, typeArgumentsFromTypeReferenceNode(node), newAliasSymbol, getTypeArgumentsForAliasSymbol(newAliasSymbol)); } + return checkNoTypeArguments(node, symbol) ? type : errorType; + } + + function isLocalTypeAlias(symbol: Symbol) { + const declaration = symbol.declarations?.find(isTypeAlias); + return !!(declaration && getContainingFunction(declaration)); + } - function isLocalTypeAlias(symbol: Symbol) { - const declaration = symbol.declarations?.find(isTypeAlias); - return !!(declaration && getContainingFunction(declaration)); + function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined { + switch (node.kind) { + case SyntaxKind.TypeReference: + return node.typeName; + case SyntaxKind.ExpressionWithTypeArguments: + // We only support expressions that are simple qualified names. For other + // expressions this produces undefined. + const expr = node.expression; + if (isEntityNameExpression(expr)) { + return expr; + } + // fall through; } - function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined { - switch (node.kind) { - case SyntaxKind.TypeReference: - return node.typeName; - case SyntaxKind.ExpressionWithTypeArguments: - // We only support expressions that are simple qualified names. For other - // expressions this produces undefined. - const expr = node.expression; - if (isEntityNameExpression(expr)) { - return expr; - } - // fall through; - } + return undefined; + } - return undefined; - } + function getSymbolPath(symbol: Symbol): string { + return symbol.parent ? `${getSymbolPath(symbol.parent)}.${symbol.escapedName}` : symbol.escapedName as string; + } - function getSymbolPath(symbol: Symbol): string { - return symbol.parent ? `${getSymbolPath(symbol.parent)}.${symbol.escapedName}` : symbol.escapedName as string; + function getUnresolvedSymbolForEntityName(name: EntityNameOrEntityNameExpression) { + const identifier = name.kind === SyntaxKind.QualifiedName ? name.right : + name.kind === SyntaxKind.PropertyAccessExpression ? name.name : + name; + const text = identifier.escapedText; + if (text) { + const parentSymbol = name.kind === SyntaxKind.QualifiedName ? getUnresolvedSymbolForEntityName(name.left) : + name.kind === SyntaxKind.PropertyAccessExpression ? getUnresolvedSymbolForEntityName(name.expression) : + undefined; + const path = parentSymbol ? `${getSymbolPath(parentSymbol)}.${text}` : text as string; + let result = unresolvedSymbols.get(path); + if (!result) { + unresolvedSymbols.set(path, result = createSymbol(SymbolFlags.TypeAlias, text, CheckFlags.Unresolved)); + result.parent = parentSymbol; + result.declaredType = unresolvedType; + } + return result; } + return unknownSymbol; + } - function getUnresolvedSymbolForEntityName(name: EntityNameOrEntityNameExpression) { - const identifier = name.kind === SyntaxKind.QualifiedName ? name.right : - name.kind === SyntaxKind.PropertyAccessExpression ? name.name : - name; - const text = identifier.escapedText; - if (text) { - const parentSymbol = name.kind === SyntaxKind.QualifiedName ? getUnresolvedSymbolForEntityName(name.left) : - name.kind === SyntaxKind.PropertyAccessExpression ? getUnresolvedSymbolForEntityName(name.expression) : - undefined; - const path = parentSymbol ? `${getSymbolPath(parentSymbol)}.${text}` : text as string; - let result = unresolvedSymbols.get(path); - if (!result) { - unresolvedSymbols.set(path, result = createSymbol(SymbolFlags.TypeAlias, text, CheckFlags.Unresolved)); - result.parent = parentSymbol; - result.declaredType = unresolvedType; - } - return result; - } + function resolveTypeReferenceName(typeReference: TypeReferenceType, meaning: SymbolFlags, ignoreErrors?: boolean) { + const name = getTypeReferenceName(typeReference); + if (!name) { return unknownSymbol; } + const symbol = resolveEntityName(name, meaning, ignoreErrors); + return symbol && symbol !== unknownSymbol ? symbol : + ignoreErrors ? unknownSymbol : getUnresolvedSymbolForEntityName(name); + } - function resolveTypeReferenceName(typeReference: TypeReferenceType, meaning: SymbolFlags, ignoreErrors?: boolean) { - const name = getTypeReferenceName(typeReference); - if (!name) { - return unknownSymbol; - } - const symbol = resolveEntityName(name, meaning, ignoreErrors); - return symbol && symbol !== unknownSymbol ? symbol : - ignoreErrors ? unknownSymbol : getUnresolvedSymbolForEntityName(name); + function getTypeReferenceType(node: NodeWithTypeArguments, symbol: Symbol): Type { + if (symbol === unknownSymbol) { + return errorType; } - - function getTypeReferenceType(node: NodeWithTypeArguments, symbol: Symbol): Type { - if (symbol === unknownSymbol) { - return errorType; - } - symbol = getExpandoSymbol(symbol) || symbol; - if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - return getTypeFromClassOrInterfaceReference(node, symbol); - } - if (symbol.flags & SymbolFlags.TypeAlias) { - return getTypeFromTypeAliasReference(node, symbol); - } - // Get type from reference to named type that cannot be generic (enum or type parameter) - const res = tryGetDeclaredTypeOfSymbol(symbol); - if (res) { - return checkNoTypeArguments(node, symbol) ? getRegularTypeOfLiteralType(res) : errorType; + symbol = getExpandoSymbol(symbol) || symbol; + if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + return getTypeFromClassOrInterfaceReference(node, symbol); + } + if (symbol.flags & SymbolFlags.TypeAlias) { + return getTypeFromTypeAliasReference(node, symbol); + } + // Get type from reference to named type that cannot be generic (enum or type parameter) + const res = tryGetDeclaredTypeOfSymbol(symbol); + if (res) { + return checkNoTypeArguments(node, symbol) ? getRegularTypeOfLiteralType(res) : errorType; + } + if (symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node)) { + const jsdocType = getTypeFromJSDocValueReference(node, symbol); + if (jsdocType) { + return jsdocType; } - if (symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node)) { - const jsdocType = getTypeFromJSDocValueReference(node, symbol); - if (jsdocType) { - return jsdocType; - } - else { - // Resolve the type reference as a Type for the purpose of reporting errors. - resolveTypeReferenceName(node, SymbolFlags.Type); - return getTypeOfSymbol(symbol); - } + else { + // Resolve the type reference as a Type for the purpose of reporting errors. + resolveTypeReferenceName(node, SymbolFlags.Type); + return getTypeOfSymbol(symbol); } - return errorType; } + return errorType; + } - /** - * A JSdoc TypeReference may be to a value, but resolve it as a type anyway. - * Example: import('./b').ConstructorFunction - */ - function getTypeFromJSDocValueReference(node: NodeWithTypeArguments, symbol: Symbol): Type | undefined { - const links = getNodeLinks(node); - if (!links.resolvedJSDocType) { - const valueType = getTypeOfSymbol(symbol); - let typeType = valueType; - if (symbol.valueDeclaration) { - const isImportTypeWithQualifier = node.kind === SyntaxKind.ImportType && (node as ImportTypeNode).qualifier; - // valueType might not have a symbol, eg, {import('./b').STRING_LITERAL} - if (valueType.symbol && valueType.symbol !== symbol && isImportTypeWithQualifier) { - typeType = getTypeReferenceType(node, valueType.symbol); - } + /** + * A JSdoc TypeReference may be to a value, but resolve it as a type anyway. + * Example: import('./b').ConstructorFunction + */ + function getTypeFromJSDocValueReference(node: NodeWithTypeArguments, symbol: Symbol): Type | undefined { + const links = getNodeLinks(node); + if (!links.resolvedJSDocType) { + const valueType = getTypeOfSymbol(symbol); + let typeType = valueType; + if (symbol.valueDeclaration) { + const isImportTypeWithQualifier = node.kind === SyntaxKind.ImportType && (node as ImportTypeNode).qualifier; + // valueType might not have a symbol, eg, {import('./b').STRING_LITERAL} + if (valueType.symbol && valueType.symbol !== symbol && isImportTypeWithQualifier) { + typeType = getTypeReferenceType(node, valueType.symbol); } - links.resolvedJSDocType = typeType; } - return links.resolvedJSDocType; + links.resolvedJSDocType = typeType; } + return links.resolvedJSDocType; + } - function getSubstitutionType(baseType: Type, substitute: Type) { - if (substitute.flags & TypeFlags.AnyOrUnknown || substitute === baseType) { - return baseType; - } - const id = `${getTypeId(baseType)}>${getTypeId(substitute)}`; - const cached = substitutionTypes.get(id); - if (cached) { - return cached; - } - const result = createType(TypeFlags.Substitution) as SubstitutionType; - result.baseType = baseType; - result.substitute = substitute; - substitutionTypes.set(id, result); - return result; + function getSubstitutionType(baseType: Type, substitute: Type) { + if (substitute.flags & TypeFlags.AnyOrUnknown || substitute === baseType) { + return baseType; } - - function isUnaryTupleTypeNode(node: TypeNode) { - return node.kind === SyntaxKind.TupleType && (node as TupleTypeNode).elements.length === 1; + const id = `${getTypeId(baseType)}>${getTypeId(substitute)}`; + const cached = substitutionTypes.get(id); + if (cached) { + return cached; } + const result = createType(TypeFlags.Substitution) as SubstitutionType; + result.baseType = baseType; + result.substitute = substitute; + substitutionTypes.set(id, result); + return result; + } - function getImpliedConstraint(type: Type, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined { - return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(type, (checkNode as TupleTypeNode).elements[0], (extendsNode as TupleTypeNode).elements[0]) : - getActualTypeVariable(getTypeFromTypeNode(checkNode)) === type ? getTypeFromTypeNode(extendsNode) : - undefined; - } + function isUnaryTupleTypeNode(node: TypeNode) { + return node.kind === SyntaxKind.TupleType && (node as TupleTypeNode).elements.length === 1; + } - function getConditionalFlowTypeOfType(type: Type, node: Node) { - let constraints: Type[] | undefined; - let covariant = true; - while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDocComment) { - const parent = node.parent; - // only consider variance flipped by parameter locations - `keyof` types would usually be considered variance inverting, but - // often get used in indexed accesses where they behave sortof invariantly, but our checking is lax - if (parent.kind === SyntaxKind.Parameter) { - covariant = !covariant; - } - // Always substitute on type parameters, regardless of variance, since even - // in contravariant positions, they may rely on substituted constraints to be valid - if ((covariant || type.flags & TypeFlags.TypeVariable) && parent.kind === SyntaxKind.ConditionalType && node === (parent as ConditionalTypeNode).trueType) { - const constraint = getImpliedConstraint(type, (parent as ConditionalTypeNode).checkType, (parent as ConditionalTypeNode).extendsType); - if (constraint) { - constraints = append(constraints, constraint); - } + function getImpliedConstraint(type: Type, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined { + return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(type, (checkNode as TupleTypeNode).elements[0], (extendsNode as TupleTypeNode).elements[0]) : + getActualTypeVariable(getTypeFromTypeNode(checkNode)) === type ? getTypeFromTypeNode(extendsNode) : + undefined; + } + + function getConditionalFlowTypeOfType(type: Type, node: Node) { + let constraints: Type[] | undefined; + let covariant = true; + while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDocComment) { + const parent = node.parent; + // only consider variance flipped by parameter locations - `keyof` types would usually be considered variance inverting, but + // often get used in indexed accesses where they behave sortof invariantly, but our checking is lax + if (parent.kind === SyntaxKind.Parameter) { + covariant = !covariant; + } + // Always substitute on type parameters, regardless of variance, since even + // in contravariant positions, they may rely on substituted constraints to be valid + if ((covariant || type.flags & TypeFlags.TypeVariable) && parent.kind === SyntaxKind.ConditionalType && node === (parent as ConditionalTypeNode).trueType) { + const constraint = getImpliedConstraint(type, (parent as ConditionalTypeNode).checkType, (parent as ConditionalTypeNode).extendsType); + if (constraint) { + constraints = append(constraints, constraint); } - node = parent; } - return constraints ? getSubstitutionType(type, getIntersectionType(append(constraints, type))) : type; + node = parent; } + return constraints ? getSubstitutionType(type, getIntersectionType(append(constraints, type))) : type; + } - function isJSDocTypeReference(node: Node): node is TypeReferenceNode { - return !!(node.flags & NodeFlags.JSDoc) && (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ImportType); - } + function isJSDocTypeReference(node: Node): node is TypeReferenceNode { + return !!(node.flags & NodeFlags.JSDoc) && (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ImportType); + } - function checkNoTypeArguments(node: NodeWithTypeArguments, symbol?: Symbol) { - if (node.typeArguments) { - error(node, Diagnostics.Type_0_is_not_generic, symbol ? symbolToString(symbol) : (node as TypeReferenceNode).typeName ? declarationNameToString((node as TypeReferenceNode).typeName) : anon); - return false; - } - return true; + function checkNoTypeArguments(node: NodeWithTypeArguments, symbol?: Symbol) { + if (node.typeArguments) { + error(node, Diagnostics.Type_0_is_not_generic, symbol ? symbolToString(symbol) : (node as TypeReferenceNode).typeName ? declarationNameToString((node as TypeReferenceNode).typeName) : anon); + return false; } + return true; + } - function getIntendedTypeFromJSDocTypeReference(node: TypeReferenceNode): Type | undefined { - if (isIdentifier(node.typeName)) { - const typeArgs = node.typeArguments; - switch (node.typeName.escapedText) { - case "String": - checkNoTypeArguments(node); - return stringType; - case "Number": - checkNoTypeArguments(node); - return numberType; - case "Boolean": - checkNoTypeArguments(node); - return booleanType; - case "Void": - checkNoTypeArguments(node); - return voidType; - case "Undefined": - checkNoTypeArguments(node); - return undefinedType; - case "Null": - checkNoTypeArguments(node); - return nullType; - case "Function": - case "function": - checkNoTypeArguments(node); - return globalFunctionType; - case "array": - return (!typeArgs || !typeArgs.length) && !noImplicitAny ? anyArrayType : undefined; - case "promise": - return (!typeArgs || !typeArgs.length) && !noImplicitAny ? createPromiseType(anyType) : undefined; - case "Object": - if (typeArgs && typeArgs.length === 2) { - if (isJSDocIndexSignature(node)) { - const indexed = getTypeFromTypeNode(typeArgs[0]); - const target = getTypeFromTypeNode(typeArgs[1]); - const indexInfo = indexed === stringType || indexed === numberType ? [createIndexInfo(indexed, target, /*isReadonly*/ false)] : emptyArray; - return createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, indexInfo); - } - return anyType; + function getIntendedTypeFromJSDocTypeReference(node: TypeReferenceNode): Type | undefined { + if (isIdentifier(node.typeName)) { + const typeArgs = node.typeArguments; + switch (node.typeName.escapedText) { + case "String": + checkNoTypeArguments(node); + return stringType; + case "Number": + checkNoTypeArguments(node); + return numberType; + case "Boolean": + checkNoTypeArguments(node); + return booleanType; + case "Void": + checkNoTypeArguments(node); + return voidType; + case "Undefined": + checkNoTypeArguments(node); + return undefinedType; + case "Null": + checkNoTypeArguments(node); + return nullType; + case "Function": + case "function": + checkNoTypeArguments(node); + return globalFunctionType; + case "array": + return (!typeArgs || !typeArgs.length) && !noImplicitAny ? anyArrayType : undefined; + case "promise": + return (!typeArgs || !typeArgs.length) && !noImplicitAny ? createPromiseType(anyType) : undefined; + case "Object": + if (typeArgs && typeArgs.length === 2) { + if (isJSDocIndexSignature(node)) { + const indexed = getTypeFromTypeNode(typeArgs[0]); + const target = getTypeFromTypeNode(typeArgs[1]); + const indexInfo = indexed === stringType || indexed === numberType ? [createIndexInfo(indexed, target, /*isReadonly*/ false)] : emptyArray; + return createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, indexInfo); } - checkNoTypeArguments(node); - return !noImplicitAny ? anyType : undefined; - } + return anyType; + } + checkNoTypeArguments(node); + return !noImplicitAny ? anyType : undefined; } } + } - function getTypeFromJSDocNullableTypeNode(node: JSDocNullableType) { - const type = getTypeFromTypeNode(node.type); - return strictNullChecks ? getNullableType(type, TypeFlags.Null) : type; - } + function getTypeFromJSDocNullableTypeNode(node: JSDocNullableType) { + const type = getTypeFromTypeNode(node.type); + return strictNullChecks ? getNullableType(type, TypeFlags.Null) : type; + } - function getTypeFromTypeReference(node: TypeReferenceType): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - // handle LS queries on the `const` in `x as const` by resolving to the type of `x` - if (isConstTypeReference(node) && isAssertionExpression(node.parent)) { - links.resolvedSymbol = unknownSymbol; - return links.resolvedType = checkExpressionCached(node.parent.expression); - } - let symbol: Symbol | undefined; - let type: Type | undefined; - const meaning = SymbolFlags.Type; - if (isJSDocTypeReference(node)) { - type = getIntendedTypeFromJSDocTypeReference(node); - if (!type) { - symbol = resolveTypeReferenceName(node, meaning, /*ignoreErrors*/ true); - if (symbol === unknownSymbol) { - symbol = resolveTypeReferenceName(node, meaning | SymbolFlags.Value); - } - else { - resolveTypeReferenceName(node, meaning); // Resolve again to mark errors, if any - } - type = getTypeReferenceType(node, symbol); - } - } + function getTypeFromTypeReference(node: TypeReferenceType): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + // handle LS queries on the `const` in `x as const` by resolving to the type of `x` + if (isConstTypeReference(node) && isAssertionExpression(node.parent)) { + links.resolvedSymbol = unknownSymbol; + return links.resolvedType = checkExpressionCached(node.parent.expression); + } + let symbol: Symbol | undefined; + let type: Type | undefined; + const meaning = SymbolFlags.Type; + if (isJSDocTypeReference(node)) { + type = getIntendedTypeFromJSDocTypeReference(node); if (!type) { - symbol = resolveTypeReferenceName(node, meaning); + symbol = resolveTypeReferenceName(node, meaning, /*ignoreErrors*/ true); + if (symbol === unknownSymbol) { + symbol = resolveTypeReferenceName(node, meaning | SymbolFlags.Value); + } + else { + resolveTypeReferenceName(node, meaning); // Resolve again to mark errors, if any + } type = getTypeReferenceType(node, symbol); } - // Cache both the resolved symbol and the resolved type. The resolved symbol is needed when we check the - // type reference in checkTypeReferenceNode. - links.resolvedSymbol = symbol; - links.resolvedType = type; } - return links.resolvedType; + if (!type) { + symbol = resolveTypeReferenceName(node, meaning); + type = getTypeReferenceType(node, symbol); + } + // Cache both the resolved symbol and the resolved type. The resolved symbol is needed when we check the + // type reference in checkTypeReferenceNode. + links.resolvedSymbol = symbol; + links.resolvedType = type; } + return links.resolvedType; + } - function typeArgumentsFromTypeReferenceNode(node: NodeWithTypeArguments): Type[] | undefined { - return map(node.typeArguments, getTypeFromTypeNode); - } + function typeArgumentsFromTypeReferenceNode(node: NodeWithTypeArguments): Type[] | undefined { + return map(node.typeArguments, getTypeFromTypeNode); + } - function getTypeFromTypeQueryNode(node: TypeQueryNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - // TypeScript 1.0 spec (April 2014): 3.6.3 - // The expression is processed as an identifier expression (section 4.3) - // or property access expression(section 4.10), - // the widened type(section 3.9) of which becomes the result. - const type = isThisIdentifier(node.exprName) ? checkThisExpression(node.exprName) : checkExpression(node.exprName); - links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(type)); - } - return links.resolvedType; + function getTypeFromTypeQueryNode(node: TypeQueryNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + // TypeScript 1.0 spec (April 2014): 3.6.3 + // The expression is processed as an identifier expression (section 4.3) + // or property access expression(section 4.10), + // the widened type(section 3.9) of which becomes the result. + const type = isThisIdentifier(node.exprName) ? checkThisExpression(node.exprName) : checkExpression(node.exprName); + links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(type)); } + return links.resolvedType; + } - function getTypeOfGlobalSymbol(symbol: Symbol | undefined, arity: number): ObjectType { + function getTypeOfGlobalSymbol(symbol: Symbol | undefined, arity: number): ObjectType { - function getTypeDeclaration(symbol: Symbol): Declaration | undefined { - const declarations = symbol.declarations; - if (declarations) { - for (const declaration of declarations) { - switch (declaration.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - return declaration; - } + function getTypeDeclaration(symbol: Symbol): Declaration | undefined { + const declarations = symbol.declarations; + if (declarations) { + for (const declaration of declarations) { + switch (declaration.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + return declaration; } } } - - if (!symbol) { - return arity ? emptyGenericType : emptyObjectType; - } - const type = getDeclaredTypeOfSymbol(symbol); - if (!(type.flags & TypeFlags.Object)) { - error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_be_a_class_or_interface_type, symbolName(symbol)); - return arity ? emptyGenericType : emptyObjectType; - } - if (length((type as InterfaceType).typeParameters) !== arity) { - error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); - return arity ? emptyGenericType : emptyObjectType; - } - return type as ObjectType; } - function getGlobalValueSymbol(name: __String, reportErrors: boolean): Symbol | undefined { - return getGlobalSymbol(name, SymbolFlags.Value, reportErrors ? Diagnostics.Cannot_find_global_value_0 : undefined); + if (!symbol) { + return arity ? emptyGenericType : emptyObjectType; } - - function getGlobalTypeSymbol(name: __String, reportErrors: boolean): Symbol | undefined { - return getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); + const type = getDeclaredTypeOfSymbol(symbol); + if (!(type.flags & TypeFlags.Object)) { + error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_be_a_class_or_interface_type, symbolName(symbol)); + return arity ? emptyGenericType : emptyObjectType; } + if (length((type as InterfaceType).typeParameters) !== arity) { + error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); + return arity ? emptyGenericType : emptyObjectType; + } + return type as ObjectType; + } - function getGlobalTypeAliasSymbol(name: __String, arity: number, reportErrors: boolean): Symbol | undefined { - const symbol = getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); - if (symbol) { - // Resolve the declared type of the symbol. This resolves type parameters for the type - // alias so that we can check arity. - getDeclaredTypeOfSymbol(symbol); - if (length(getSymbolLinks(symbol).typeParameters) !== arity) { - const decl = symbol.declarations && find(symbol.declarations, isTypeAliasDeclaration); - error(decl, Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); - return undefined; - } + function getGlobalValueSymbol(name: __String, reportErrors: boolean): Symbol | undefined { + return getGlobalSymbol(name, SymbolFlags.Value, reportErrors ? Diagnostics.Cannot_find_global_value_0 : undefined); + } + + function getGlobalTypeSymbol(name: __String, reportErrors: boolean): Symbol | undefined { + return getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); + } + + function getGlobalTypeAliasSymbol(name: __String, arity: number, reportErrors: boolean): Symbol | undefined { + const symbol = getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); + if (symbol) { + // Resolve the declared type of the symbol. This resolves type parameters for the type + // alias so that we can check arity. + getDeclaredTypeOfSymbol(symbol); + if (length(getSymbolLinks(symbol).typeParameters) !== arity) { + const decl = symbol.declarations && find(symbol.declarations, isTypeAliasDeclaration); + error(decl, Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); + return undefined; } - return symbol; } + return symbol; + } - function getGlobalSymbol(name: __String, meaning: SymbolFlags, diagnostic: DiagnosticMessage | undefined): Symbol | undefined { - // Don't track references for global symbols anyway, so value if `isReference` is arbitrary - return resolveName(undefined, name, meaning, diagnostic, name, /*isUse*/ false); - } + function getGlobalSymbol(name: __String, meaning: SymbolFlags, diagnostic: DiagnosticMessage | undefined): Symbol | undefined { + // Don't track references for global symbols anyway, so value if `isReference` is arbitrary + return resolveName(undefined, name, meaning, diagnostic, name, /*isUse*/ false); + } - function getGlobalType(name: __String, arity: 0, reportErrors: true): ObjectType; - function getGlobalType(name: __String, arity: 0, reportErrors: boolean): ObjectType | undefined; - function getGlobalType(name: __String, arity: number, reportErrors: true): GenericType; - function getGlobalType(name: __String, arity: number, reportErrors: boolean): GenericType | undefined; - function getGlobalType(name: __String, arity: number, reportErrors: boolean): ObjectType | undefined { - const symbol = getGlobalTypeSymbol(name, reportErrors); - return symbol || reportErrors ? getTypeOfGlobalSymbol(symbol, arity) : undefined; - } + function getGlobalType(name: __String, arity: 0, reportErrors: true): ObjectType; + function getGlobalType(name: __String, arity: 0, reportErrors: boolean): ObjectType | undefined; + function getGlobalType(name: __String, arity: number, reportErrors: true): GenericType; + function getGlobalType(name: __String, arity: number, reportErrors: boolean): GenericType | undefined; + function getGlobalType(name: __String, arity: number, reportErrors: boolean): ObjectType | undefined { + const symbol = getGlobalTypeSymbol(name, reportErrors); + return symbol || reportErrors ? getTypeOfGlobalSymbol(symbol, arity) : undefined; + } - function getGlobalTypedPropertyDescriptorType() { - // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times - return deferredGlobalTypedPropertyDescriptorType ||= getGlobalType("TypedPropertyDescriptor" as __String, /*arity*/ 1, /*reportErrors*/ true) || emptyGenericType; - } + function getGlobalTypedPropertyDescriptorType() { + // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times + return deferredGlobalTypedPropertyDescriptorType ||= getGlobalType("TypedPropertyDescriptor" as __String, /*arity*/ 1, /*reportErrors*/ true) || emptyGenericType; + } - function getGlobalTemplateStringsArrayType() { - // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times - return deferredGlobalTemplateStringsArrayType ||= getGlobalType("TemplateStringsArray" as __String, /*arity*/ 0, /*reportErrors*/ true) || emptyObjectType; - } + function getGlobalTemplateStringsArrayType() { + // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times + return deferredGlobalTemplateStringsArrayType ||= getGlobalType("TemplateStringsArray" as __String, /*arity*/ 0, /*reportErrors*/ true) || emptyObjectType; + } - function getGlobalImportMetaType() { - // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times - return deferredGlobalImportMetaType ||= getGlobalType("ImportMeta" as __String, /*arity*/ 0, /*reportErrors*/ true) || emptyObjectType; - } + function getGlobalImportMetaType() { + // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times + return deferredGlobalImportMetaType ||= getGlobalType("ImportMeta" as __String, /*arity*/ 0, /*reportErrors*/ true) || emptyObjectType; + } - function getGlobalImportMetaExpressionType() { - if (!deferredGlobalImportMetaExpressionType) { - // Create a synthetic type `ImportMetaExpression { meta: MetaProperty }` - const symbol = createSymbol(SymbolFlags.None, "ImportMetaExpression" as __String); - const importMetaType = getGlobalImportMetaType(); + function getGlobalImportMetaExpressionType() { + if (!deferredGlobalImportMetaExpressionType) { + // Create a synthetic type `ImportMetaExpression { meta: MetaProperty }` + const symbol = createSymbol(SymbolFlags.None, "ImportMetaExpression" as __String); + const importMetaType = getGlobalImportMetaType(); - const metaPropertySymbol = createSymbol(SymbolFlags.Property, "meta" as __String, CheckFlags.Readonly); - metaPropertySymbol.parent = symbol; - metaPropertySymbol.type = importMetaType; + const metaPropertySymbol = createSymbol(SymbolFlags.Property, "meta" as __String, CheckFlags.Readonly); + metaPropertySymbol.parent = symbol; + metaPropertySymbol.type = importMetaType; - const members = createSymbolTable([metaPropertySymbol]); - symbol.members = members; + const members = createSymbolTable([metaPropertySymbol]); + symbol.members = members; - deferredGlobalImportMetaExpressionType = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); - } - return deferredGlobalImportMetaExpressionType; + deferredGlobalImportMetaExpressionType = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); } + return deferredGlobalImportMetaExpressionType; + } - function getGlobalImportCallOptionsType(reportErrors: boolean) { - return (deferredGlobalImportCallOptionsType ||= getGlobalType("ImportCallOptions" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; - } + function getGlobalImportCallOptionsType(reportErrors: boolean) { + return (deferredGlobalImportCallOptionsType ||= getGlobalType("ImportCallOptions" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } - function getGlobalESSymbolConstructorSymbol(reportErrors: boolean): Symbol | undefined { - return deferredGlobalESSymbolConstructorSymbol ||= getGlobalValueSymbol("Symbol" as __String, reportErrors); - } + function getGlobalESSymbolConstructorSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalESSymbolConstructorSymbol ||= getGlobalValueSymbol("Symbol" as __String, reportErrors); + } - function getGlobalESSymbolConstructorTypeSymbol(reportErrors: boolean): Symbol | undefined { - return deferredGlobalESSymbolConstructorTypeSymbol ||= getGlobalTypeSymbol("SymbolConstructor" as __String, reportErrors); - } + function getGlobalESSymbolConstructorTypeSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalESSymbolConstructorTypeSymbol ||= getGlobalTypeSymbol("SymbolConstructor" as __String, reportErrors); + } - function getGlobalESSymbolType(reportErrors: boolean) { - return (deferredGlobalESSymbolType ||= getGlobalType("Symbol" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; - } + function getGlobalESSymbolType(reportErrors: boolean) { + return (deferredGlobalESSymbolType ||= getGlobalType("Symbol" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } - function getGlobalPromiseType(reportErrors: boolean) { - return (deferredGlobalPromiseType ||= getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalPromiseType(reportErrors: boolean) { + return (deferredGlobalPromiseType ||= getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalPromiseLikeType(reportErrors: boolean) { - return (deferredGlobalPromiseLikeType ||= getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalPromiseLikeType(reportErrors: boolean) { + return (deferredGlobalPromiseLikeType ||= getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined { - return deferredGlobalPromiseConstructorSymbol ||= getGlobalValueSymbol("Promise" as __String, reportErrors); - } + function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalPromiseConstructorSymbol ||= getGlobalValueSymbol("Promise" as __String, reportErrors); + } - function getGlobalPromiseConstructorLikeType(reportErrors: boolean) { - return (deferredGlobalPromiseConstructorLikeType ||= getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; - } + function getGlobalPromiseConstructorLikeType(reportErrors: boolean) { + return (deferredGlobalPromiseConstructorLikeType ||= getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } - function getGlobalAsyncIterableType(reportErrors: boolean) { - return (deferredGlobalAsyncIterableType ||= getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalAsyncIterableType(reportErrors: boolean) { + return (deferredGlobalAsyncIterableType ||= getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalAsyncIteratorType(reportErrors: boolean) { - return (deferredGlobalAsyncIteratorType ||= getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; - } + function getGlobalAsyncIteratorType(reportErrors: boolean) { + return (deferredGlobalAsyncIteratorType ||= getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } - function getGlobalAsyncIterableIteratorType(reportErrors: boolean) { - return (deferredGlobalAsyncIterableIteratorType ||= getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalAsyncIterableIteratorType(reportErrors: boolean) { + return (deferredGlobalAsyncIterableIteratorType ||= getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalAsyncGeneratorType(reportErrors: boolean) { - return (deferredGlobalAsyncGeneratorType ||= getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; - } + function getGlobalAsyncGeneratorType(reportErrors: boolean) { + return (deferredGlobalAsyncGeneratorType ||= getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } - function getGlobalIterableType(reportErrors: boolean) { - return (deferredGlobalIterableType ||= getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalIterableType(reportErrors: boolean) { + return (deferredGlobalIterableType ||= getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalIteratorType(reportErrors: boolean) { - return (deferredGlobalIteratorType ||= getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; - } + function getGlobalIteratorType(reportErrors: boolean) { + return (deferredGlobalIteratorType ||= getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } - function getGlobalIterableIteratorType(reportErrors: boolean) { - return (deferredGlobalIterableIteratorType ||= getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalIterableIteratorType(reportErrors: boolean) { + return (deferredGlobalIterableIteratorType ||= getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalGeneratorType(reportErrors: boolean) { - return (deferredGlobalGeneratorType ||= getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; - } + function getGlobalGeneratorType(reportErrors: boolean) { + return (deferredGlobalGeneratorType ||= getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } - function getGlobalIteratorYieldResultType(reportErrors: boolean) { - return (deferredGlobalIteratorYieldResultType ||= getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalIteratorYieldResultType(reportErrors: boolean) { + return (deferredGlobalIteratorYieldResultType ||= getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalIteratorReturnResultType(reportErrors: boolean) { - return (deferredGlobalIteratorReturnResultType ||= getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalIteratorReturnResultType(reportErrors: boolean) { + return (deferredGlobalIteratorReturnResultType ||= getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined { - const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined); - return symbol && getTypeOfGlobalSymbol(symbol, arity) as GenericType; - } + function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined { + const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined); + return symbol && getTypeOfGlobalSymbol(symbol, arity) as GenericType; + } - function getGlobalExtractSymbol(): Symbol | undefined { - // We always report an error, so cache a result in the event we could not resolve the symbol to prevent reporting it multiple times - deferredGlobalExtractSymbol ||= getGlobalTypeAliasSymbol("Extract" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; - return deferredGlobalExtractSymbol === unknownSymbol ? undefined : deferredGlobalExtractSymbol; - } + function getGlobalExtractSymbol(): Symbol | undefined { + // We always report an error, so cache a result in the event we could not resolve the symbol to prevent reporting it multiple times + deferredGlobalExtractSymbol ||= getGlobalTypeAliasSymbol("Extract" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; + return deferredGlobalExtractSymbol === unknownSymbol ? undefined : deferredGlobalExtractSymbol; + } - function getGlobalOmitSymbol(): Symbol | undefined { - // We always report an error, so cache a result in the event we could not resolve the symbol to prevent reporting it multiple times - deferredGlobalOmitSymbol ||= getGlobalTypeAliasSymbol("Omit" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; - return deferredGlobalOmitSymbol === unknownSymbol ? undefined : deferredGlobalOmitSymbol; - } + function getGlobalOmitSymbol(): Symbol | undefined { + // We always report an error, so cache a result in the event we could not resolve the symbol to prevent reporting it multiple times + deferredGlobalOmitSymbol ||= getGlobalTypeAliasSymbol("Omit" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; + return deferredGlobalOmitSymbol === unknownSymbol ? undefined : deferredGlobalOmitSymbol; + } - function getGlobalAwaitedSymbol(reportErrors: boolean): Symbol | undefined { - // Only cache `unknownSymbol` if we are reporting errors so that we don't report the error more than once. - deferredGlobalAwaitedSymbol ||= getGlobalTypeAliasSymbol("Awaited" as __String, /*arity*/ 1, reportErrors) || (reportErrors ? unknownSymbol : undefined); - return deferredGlobalAwaitedSymbol === unknownSymbol ? undefined : deferredGlobalAwaitedSymbol; - } + function getGlobalAwaitedSymbol(reportErrors: boolean): Symbol | undefined { + // Only cache `unknownSymbol` if we are reporting errors so that we don't report the error more than once. + deferredGlobalAwaitedSymbol ||= getGlobalTypeAliasSymbol("Awaited" as __String, /*arity*/ 1, reportErrors) || (reportErrors ? unknownSymbol : undefined); + return deferredGlobalAwaitedSymbol === unknownSymbol ? undefined : deferredGlobalAwaitedSymbol; + } - function getGlobalBigIntType(reportErrors: boolean) { - return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; - } + function getGlobalBigIntType(reportErrors: boolean) { + return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } - /** - * Instantiates a global type that is generic with some element type, and returns that instantiation. - */ - function createTypeFromGenericGlobalType(genericGlobalType: GenericType, typeArguments: readonly Type[]): ObjectType { - return genericGlobalType !== emptyGenericType ? createTypeReference(genericGlobalType, typeArguments) : emptyObjectType; - } + /** + * Instantiates a global type that is generic with some element type, and returns that instantiation. + */ + function createTypeFromGenericGlobalType(genericGlobalType: GenericType, typeArguments: readonly Type[]): ObjectType { + return genericGlobalType !== emptyGenericType ? createTypeReference(genericGlobalType, typeArguments) : emptyObjectType; + } - function createTypedPropertyDescriptorType(propertyType: Type): Type { - return createTypeFromGenericGlobalType(getGlobalTypedPropertyDescriptorType(), [propertyType]); - } + function createTypedPropertyDescriptorType(propertyType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalTypedPropertyDescriptorType(), [propertyType]); + } - function createIterableType(iteratedType: Type): Type { - return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType]); - } + function createIterableType(iteratedType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType]); + } - function createArrayType(elementType: Type, readonly?: boolean): ObjectType { - return createTypeFromGenericGlobalType(readonly ? globalReadonlyArrayType : globalArrayType, [elementType]); - } + function createArrayType(elementType: Type, readonly?: boolean): ObjectType { + return createTypeFromGenericGlobalType(readonly ? globalReadonlyArrayType : globalArrayType, [elementType]); + } - function getTupleElementFlags(node: TypeNode) { - switch (node.kind) { - case SyntaxKind.OptionalType: - return ElementFlags.Optional; - case SyntaxKind.RestType: - return getRestTypeElementFlags(node as RestTypeNode); - case SyntaxKind.NamedTupleMember: - return (node as NamedTupleMember).questionToken ? ElementFlags.Optional : - (node as NamedTupleMember).dotDotDotToken ? getRestTypeElementFlags(node as NamedTupleMember) : - ElementFlags.Required; - default: - return ElementFlags.Required; - } + function getTupleElementFlags(node: TypeNode) { + switch (node.kind) { + case SyntaxKind.OptionalType: + return ElementFlags.Optional; + case SyntaxKind.RestType: + return getRestTypeElementFlags(node as RestTypeNode); + case SyntaxKind.NamedTupleMember: + return (node as NamedTupleMember).questionToken ? ElementFlags.Optional : + (node as NamedTupleMember).dotDotDotToken ? getRestTypeElementFlags(node as NamedTupleMember) : + ElementFlags.Required; + default: + return ElementFlags.Required; } + } - function getRestTypeElementFlags(node: RestTypeNode | NamedTupleMember) { - return getArrayElementTypeNode(node.type) ? ElementFlags.Rest : ElementFlags.Variadic; - } + function getRestTypeElementFlags(node: RestTypeNode | NamedTupleMember) { + return getArrayElementTypeNode(node.type) ? ElementFlags.Rest : ElementFlags.Variadic; + } - function getArrayOrTupleTargetType(node: ArrayTypeNode | TupleTypeNode): GenericType { - const readonly = isReadonlyTypeOperator(node.parent); - const elementType = getArrayElementTypeNode(node); - if (elementType) { - return readonly ? globalReadonlyArrayType : globalArrayType; - } - const elementFlags = map((node as TupleTypeNode).elements, getTupleElementFlags); - const missingName = some((node as TupleTypeNode).elements, e => e.kind !== SyntaxKind.NamedTupleMember); - return getTupleTargetType(elementFlags, readonly, /*associatedNames*/ missingName ? undefined : (node as TupleTypeNode).elements as readonly NamedTupleMember[]); + function getArrayOrTupleTargetType(node: ArrayTypeNode | TupleTypeNode): GenericType { + const readonly = isReadonlyTypeOperator(node.parent); + const elementType = getArrayElementTypeNode(node); + if (elementType) { + return readonly ? globalReadonlyArrayType : globalArrayType; } + const elementFlags = map((node as TupleTypeNode).elements, getTupleElementFlags); + const missingName = some((node as TupleTypeNode).elements, e => e.kind !== SyntaxKind.NamedTupleMember); + return getTupleTargetType(elementFlags, readonly, /*associatedNames*/ missingName ? undefined : (node as TupleTypeNode).elements as readonly NamedTupleMember[]); + } - // Return true if the given type reference node is directly aliased or if it needs to be deferred - // because it is possibly contained in a circular chain of eagerly resolved types. - function isDeferredTypeReferenceNode(node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, hasDefaultTypeArguments?: boolean) { - return !!getAliasSymbolForTypeNode(node) || isResolvedByTypeAlias(node) && ( - node.kind === SyntaxKind.ArrayType ? mayResolveTypeAlias(node.elementType) : - node.kind === SyntaxKind.TupleType ? some(node.elements, mayResolveTypeAlias) : - hasDefaultTypeArguments || some(node.typeArguments, mayResolveTypeAlias)); - } + // Return true if the given type reference node is directly aliased or if it needs to be deferred + // because it is possibly contained in a circular chain of eagerly resolved types. + function isDeferredTypeReferenceNode(node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, hasDefaultTypeArguments?: boolean) { + return !!getAliasSymbolForTypeNode(node) || isResolvedByTypeAlias(node) && ( + node.kind === SyntaxKind.ArrayType ? mayResolveTypeAlias(node.elementType) : + node.kind === SyntaxKind.TupleType ? some(node.elements, mayResolveTypeAlias) : + hasDefaultTypeArguments || some(node.typeArguments, mayResolveTypeAlias)); + } - // Return true when the given node is transitively contained in type constructs that eagerly - // resolve their constituent types. We include SyntaxKind.TypeReference because type arguments - // of type aliases are eagerly resolved. - function isResolvedByTypeAlias(node: Node): boolean { - const parent = node.parent; - switch (parent.kind) { - case SyntaxKind.ParenthesizedType: - case SyntaxKind.NamedTupleMember: - case SyntaxKind.TypeReference: - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - case SyntaxKind.IndexedAccessType: - case SyntaxKind.ConditionalType: - case SyntaxKind.TypeOperator: - case SyntaxKind.ArrayType: - case SyntaxKind.TupleType: - return isResolvedByTypeAlias(parent); - case SyntaxKind.TypeAliasDeclaration: - return true; - } - return false; + // Return true when the given node is transitively contained in type constructs that eagerly + // resolve their constituent types. We include SyntaxKind.TypeReference because type arguments + // of type aliases are eagerly resolved. + function isResolvedByTypeAlias(node: Node): boolean { + const parent = node.parent; + switch (parent.kind) { + case SyntaxKind.ParenthesizedType: + case SyntaxKind.NamedTupleMember: + case SyntaxKind.TypeReference: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.IndexedAccessType: + case SyntaxKind.ConditionalType: + case SyntaxKind.TypeOperator: + case SyntaxKind.ArrayType: + case SyntaxKind.TupleType: + return isResolvedByTypeAlias(parent); + case SyntaxKind.TypeAliasDeclaration: + return true; } + return false; + } - // Return true if resolving the given node (i.e. getTypeFromTypeNode) possibly causes resolution - // of a type alias. - function mayResolveTypeAlias(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.TypeReference: - return isJSDocTypeReference(node) || !!(resolveTypeReferenceName(node as TypeReferenceNode, SymbolFlags.Type).flags & SymbolFlags.TypeAlias); - case SyntaxKind.TypeQuery: - return true; - case SyntaxKind.TypeOperator: - return (node as TypeOperatorNode).operator !== SyntaxKind.UniqueKeyword && mayResolveTypeAlias((node as TypeOperatorNode).type); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.OptionalType: - case SyntaxKind.NamedTupleMember: - case SyntaxKind.JSDocOptionalType: - case SyntaxKind.JSDocNullableType: - case SyntaxKind.JSDocNonNullableType: - case SyntaxKind.JSDocTypeExpression: - return mayResolveTypeAlias((node as ParenthesizedTypeNode | OptionalTypeNode | JSDocTypeReferencingNode | NamedTupleMember).type); - case SyntaxKind.RestType: - return (node as RestTypeNode).type.kind !== SyntaxKind.ArrayType || mayResolveTypeAlias(((node as RestTypeNode).type as ArrayTypeNode).elementType); - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - return some((node as UnionOrIntersectionTypeNode).types, mayResolveTypeAlias); - case SyntaxKind.IndexedAccessType: - return mayResolveTypeAlias((node as IndexedAccessTypeNode).objectType) || mayResolveTypeAlias((node as IndexedAccessTypeNode).indexType); - case SyntaxKind.ConditionalType: - return mayResolveTypeAlias((node as ConditionalTypeNode).checkType) || mayResolveTypeAlias((node as ConditionalTypeNode).extendsType) || - mayResolveTypeAlias((node as ConditionalTypeNode).trueType) || mayResolveTypeAlias((node as ConditionalTypeNode).falseType); - } - return false; - } + // Return true if resolving the given node (i.e. getTypeFromTypeNode) possibly causes resolution + // of a type alias. + function mayResolveTypeAlias(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.TypeReference: + return isJSDocTypeReference(node) || !!(resolveTypeReferenceName(node as TypeReferenceNode, SymbolFlags.Type).flags & SymbolFlags.TypeAlias); + case SyntaxKind.TypeQuery: + return true; + case SyntaxKind.TypeOperator: + return (node as TypeOperatorNode).operator !== SyntaxKind.UniqueKeyword && mayResolveTypeAlias((node as TypeOperatorNode).type); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.OptionalType: + case SyntaxKind.NamedTupleMember: + case SyntaxKind.JSDocOptionalType: + case SyntaxKind.JSDocNullableType: + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocTypeExpression: + return mayResolveTypeAlias((node as ParenthesizedTypeNode | OptionalTypeNode | JSDocTypeReferencingNode | NamedTupleMember).type); + case SyntaxKind.RestType: + return (node as RestTypeNode).type.kind !== SyntaxKind.ArrayType || mayResolveTypeAlias(((node as RestTypeNode).type as ArrayTypeNode).elementType); + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + return some((node as UnionOrIntersectionTypeNode).types, mayResolveTypeAlias); + case SyntaxKind.IndexedAccessType: + return mayResolveTypeAlias((node as IndexedAccessTypeNode).objectType) || mayResolveTypeAlias((node as IndexedAccessTypeNode).indexType); + case SyntaxKind.ConditionalType: + return mayResolveTypeAlias((node as ConditionalTypeNode).checkType) || mayResolveTypeAlias((node as ConditionalTypeNode).extendsType) || + mayResolveTypeAlias((node as ConditionalTypeNode).trueType) || mayResolveTypeAlias((node as ConditionalTypeNode).falseType); + } + return false; + } - function getTypeFromArrayOrTupleTypeNode(node: ArrayTypeNode | TupleTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const target = getArrayOrTupleTargetType(node); - if (target === emptyGenericType) { - links.resolvedType = emptyObjectType; - } - else if (!(node.kind === SyntaxKind.TupleType && some(node.elements, e => !!(getTupleElementFlags(e) & ElementFlags.Variadic))) && isDeferredTypeReferenceNode(node)) { - links.resolvedType = node.kind === SyntaxKind.TupleType && node.elements.length === 0 ? target : - createDeferredTypeReference(target, node, /*mapper*/ undefined); - } - else { - const elementTypes = node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : map(node.elements, getTypeFromTypeNode); - links.resolvedType = createNormalizedTypeReference(target, elementTypes); - } + function getTypeFromArrayOrTupleTypeNode(node: ArrayTypeNode | TupleTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const target = getArrayOrTupleTargetType(node); + if (target === emptyGenericType) { + links.resolvedType = emptyObjectType; + } + else if (!(node.kind === SyntaxKind.TupleType && some(node.elements, e => !!(getTupleElementFlags(e) & ElementFlags.Variadic))) && isDeferredTypeReferenceNode(node)) { + links.resolvedType = node.kind === SyntaxKind.TupleType && node.elements.length === 0 ? target : + createDeferredTypeReference(target, node, /*mapper*/ undefined); + } + else { + const elementTypes = node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : map(node.elements, getTypeFromTypeNode); + links.resolvedType = createNormalizedTypeReference(target, elementTypes); } - return links.resolvedType; } + return links.resolvedType; + } - function isReadonlyTypeOperator(node: Node) { - return isTypeOperatorNode(node) && node.operator === SyntaxKind.ReadonlyKeyword; - } + function isReadonlyTypeOperator(node: Node) { + return isTypeOperatorNode(node) && node.operator === SyntaxKind.ReadonlyKeyword; + } - function createTupleType(elementTypes: readonly Type[], elementFlags?: readonly ElementFlags[], readonly = false, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]) { - const tupleTarget = getTupleTargetType(elementFlags || map(elementTypes, _ => ElementFlags.Required), readonly, namedMemberDeclarations); - return tupleTarget === emptyGenericType ? emptyObjectType : - elementTypes.length ? createNormalizedTypeReference(tupleTarget, elementTypes) : - tupleTarget; - } + function createTupleType(elementTypes: readonly Type[], elementFlags?: readonly ElementFlags[], readonly = false, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]) { + const tupleTarget = getTupleTargetType(elementFlags || map(elementTypes, _ => ElementFlags.Required), readonly, namedMemberDeclarations); + return tupleTarget === emptyGenericType ? emptyObjectType : + elementTypes.length ? createNormalizedTypeReference(tupleTarget, elementTypes) : + tupleTarget; + } - function getTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]): GenericType { - if (elementFlags.length === 1 && elementFlags[0] & ElementFlags.Rest) { - // [...X[]] is equivalent to just X[] - return readonly ? globalReadonlyArrayType : globalArrayType; - } - const key = map(elementFlags, f => f & ElementFlags.Required ? "#" : f & ElementFlags.Optional ? "?" : f & ElementFlags.Rest ? "." : "*").join() + - (readonly ? "R" : "") + - (namedMemberDeclarations && namedMemberDeclarations.length ? "," + map(namedMemberDeclarations, getNodeId).join(",") : ""); - let type = tupleTypes.get(key); - if (!type) { - tupleTypes.set(key, type = createTupleTargetType(elementFlags, readonly, namedMemberDeclarations)); - } - return type; + function getTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]): GenericType { + if (elementFlags.length === 1 && elementFlags[0] & ElementFlags.Rest) { + // [...X[]] is equivalent to just X[] + return readonly ? globalReadonlyArrayType : globalArrayType; + } + const key = map(elementFlags, f => f & ElementFlags.Required ? "#" : f & ElementFlags.Optional ? "?" : f & ElementFlags.Rest ? "." : "*").join() + + (readonly ? "R" : "") + + (namedMemberDeclarations && namedMemberDeclarations.length ? "," + map(namedMemberDeclarations, getNodeId).join(",") : ""); + let type = tupleTypes.get(key); + if (!type) { + tupleTypes.set(key, type = createTupleTargetType(elementFlags, readonly, namedMemberDeclarations)); } + return type; + } - // We represent tuple types as type references to synthesized generic interface types created by - // this function. The types are of the form: - // - // interface Tuple extends Array { 0: T0, 1: T1, 2: T2, ... } - // - // Note that the generic type created by this function has no symbol associated with it. The same - // is true for each of the synthesized type parameters. - function createTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration)[] | undefined): TupleType { - const arity = elementFlags.length; - const minLength = countWhere(elementFlags, f => !!(f & (ElementFlags.Required | ElementFlags.Variadic))); - let typeParameters: TypeParameter[] | undefined; - const properties: Symbol[] = []; - let combinedFlags: ElementFlags = 0; - if (arity) { - typeParameters = new Array(arity); - for (let i = 0; i < arity; i++) { - const typeParameter = typeParameters[i] = createTypeParameter(); - const flags = elementFlags[i]; - combinedFlags |= flags; - if (!(combinedFlags & ElementFlags.Variable)) { - const property = createSymbol(SymbolFlags.Property | (flags & ElementFlags.Optional ? SymbolFlags.Optional : 0), - "" + i as __String, readonly ? CheckFlags.Readonly : 0); - property.tupleLabelDeclaration = namedMemberDeclarations?.[i]; - property.type = typeParameter; - properties.push(property); - } - } - } - const fixedLength = properties.length; - const lengthSymbol = createSymbol(SymbolFlags.Property, "length" as __String); - if (combinedFlags & ElementFlags.Variable) { - lengthSymbol.type = numberType; + // We represent tuple types as type references to synthesized generic interface types created by + // this function. The types are of the form: + // + // interface Tuple extends Array { 0: T0, 1: T1, 2: T2, ... } + // + // Note that the generic type created by this function has no symbol associated with it. The same + // is true for each of the synthesized type parameters. + function createTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration)[] | undefined): TupleType { + const arity = elementFlags.length; + const minLength = countWhere(elementFlags, f => !!(f & (ElementFlags.Required | ElementFlags.Variadic))); + let typeParameters: TypeParameter[] | undefined; + const properties: Symbol[] = []; + let combinedFlags: ElementFlags = 0; + if (arity) { + typeParameters = new Array(arity); + for (let i = 0; i < arity; i++) { + const typeParameter = typeParameters[i] = createTypeParameter(); + const flags = elementFlags[i]; + combinedFlags |= flags; + if (!(combinedFlags & ElementFlags.Variable)) { + const property = createSymbol(SymbolFlags.Property | (flags & ElementFlags.Optional ? SymbolFlags.Optional : 0), + "" + i as __String, readonly ? CheckFlags.Readonly : 0); + property.tupleLabelDeclaration = namedMemberDeclarations?.[i]; + property.type = typeParameter; + properties.push(property); + } + } + } + const fixedLength = properties.length; + const lengthSymbol = createSymbol(SymbolFlags.Property, "length" as __String); + if (combinedFlags & ElementFlags.Variable) { + lengthSymbol.type = numberType; + } + else { + const literalTypes = []; + for (let i = minLength; i <= arity; i++) literalTypes.push(getNumberLiteralType(i)); + lengthSymbol.type = getUnionType(literalTypes); + } + properties.push(lengthSymbol); + const type = createObjectType(ObjectFlags.Tuple | ObjectFlags.Reference) as TupleType & InterfaceTypeWithDeclaredMembers; + type.typeParameters = typeParameters; + type.outerTypeParameters = undefined; + type.localTypeParameters = typeParameters; + type.instantiations = new Map(); + type.instantiations.set(getTypeListId(type.typeParameters), type as GenericType); + type.target = type as GenericType; + type.resolvedTypeArguments = type.typeParameters; + type.thisType = createTypeParameter(); + type.thisType.isThisType = true; + type.thisType.constraint = type; + type.declaredProperties = properties; + type.declaredCallSignatures = emptyArray; + type.declaredConstructSignatures = emptyArray; + type.declaredIndexInfos = emptyArray; + type.elementFlags = elementFlags; + type.minLength = minLength; + type.fixedLength = fixedLength; + type.hasRestElement = !!(combinedFlags & ElementFlags.Variable); + type.combinedFlags = combinedFlags; + type.readonly = readonly; + type.labeledElementDeclarations = namedMemberDeclarations; + return type; + } + + function createNormalizedTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined) { + return target.objectFlags & ObjectFlags.Tuple ? createNormalizedTupleType(target as TupleType, typeArguments!) : createTypeReference(target, typeArguments); + } + + function createNormalizedTupleType(target: TupleType, elementTypes: readonly Type[]): Type { + if (!(target.combinedFlags & ElementFlags.NonRequired)) { + // No need to normalize when we only have regular required elements + return createTypeReference(target, elementTypes); + } + if (target.combinedFlags & ElementFlags.Variadic) { + // Transform [A, ...(X | Y | Z)] into [A, ...X] | [A, ...Y] | [A, ...Z] + const unionIndex = findIndex(elementTypes, (t, i) => !!(target.elementFlags[i] & ElementFlags.Variadic && t.flags & (TypeFlags.Never | TypeFlags.Union))); + if (unionIndex >= 0) { + return checkCrossProductUnion(map(elementTypes, (t, i) => target.elementFlags[i] & ElementFlags.Variadic ? t : unknownType)) ? + mapType(elementTypes[unionIndex], t => createNormalizedTupleType(target, replaceElement(elementTypes, unionIndex, t))) : + errorType; } - else { - const literalTypes = []; - for (let i = minLength; i <= arity; i++) literalTypes.push(getNumberLiteralType(i)); - lengthSymbol.type = getUnionType(literalTypes); - } - properties.push(lengthSymbol); - const type = createObjectType(ObjectFlags.Tuple | ObjectFlags.Reference) as TupleType & InterfaceTypeWithDeclaredMembers; - type.typeParameters = typeParameters; - type.outerTypeParameters = undefined; - type.localTypeParameters = typeParameters; - type.instantiations = new Map(); - type.instantiations.set(getTypeListId(type.typeParameters), type as GenericType); - type.target = type as GenericType; - type.resolvedTypeArguments = type.typeParameters; - type.thisType = createTypeParameter(); - type.thisType.isThisType = true; - type.thisType.constraint = type; - type.declaredProperties = properties; - type.declaredCallSignatures = emptyArray; - type.declaredConstructSignatures = emptyArray; - type.declaredIndexInfos = emptyArray; - type.elementFlags = elementFlags; - type.minLength = minLength; - type.fixedLength = fixedLength; - type.hasRestElement = !!(combinedFlags & ElementFlags.Variable); - type.combinedFlags = combinedFlags; - type.readonly = readonly; - type.labeledElementDeclarations = namedMemberDeclarations; - return type; } - - function createNormalizedTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined) { - return target.objectFlags & ObjectFlags.Tuple ? createNormalizedTupleType(target as TupleType, typeArguments!) : createTypeReference(target, typeArguments); - } - - function createNormalizedTupleType(target: TupleType, elementTypes: readonly Type[]): Type { - if (!(target.combinedFlags & ElementFlags.NonRequired)) { - // No need to normalize when we only have regular required elements - return createTypeReference(target, elementTypes); - } - if (target.combinedFlags & ElementFlags.Variadic) { - // Transform [A, ...(X | Y | Z)] into [A, ...X] | [A, ...Y] | [A, ...Z] - const unionIndex = findIndex(elementTypes, (t, i) => !!(target.elementFlags[i] & ElementFlags.Variadic && t.flags & (TypeFlags.Never | TypeFlags.Union))); - if (unionIndex >= 0) { - return checkCrossProductUnion(map(elementTypes, (t, i) => target.elementFlags[i] & ElementFlags.Variadic ? t : unknownType)) ? - mapType(elementTypes[unionIndex], t => createNormalizedTupleType(target, replaceElement(elementTypes, unionIndex, t))) : - errorType; - } - } - // We have optional, rest, or variadic elements that may need normalizing. Normalization ensures that all variadic - // elements are generic and that the tuple type has one of the following layouts, disregarding variadic elements: - // (1) Zero or more required elements, followed by zero or more optional elements, followed by zero or one rest element. - // (2) Zero or more required elements, followed by a rest element, followed by zero or more required elements. - // In either layout, zero or more generic variadic elements may be present at any location. - const expandedTypes: Type[] = []; - const expandedFlags: ElementFlags[] = []; - let expandedDeclarations: (NamedTupleMember | ParameterDeclaration)[] | undefined = []; - let lastRequiredIndex = -1; - let firstRestIndex = -1; - let lastOptionalOrRestIndex = -1; - for (let i = 0; i < elementTypes.length; i++) { - const type = elementTypes[i]; - const flags = target.elementFlags[i]; - if (flags & ElementFlags.Variadic) { - if (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type)) { - // Generic variadic elements stay as they are. - addElement(type, ElementFlags.Variadic, target.labeledElementDeclarations?.[i]); - } - else if (isTupleType(type)) { - const elements = getTypeArguments(type); - if (elements.length + expandedTypes.length >= 10_000) { - error(currentNode, isPartOfTypeNode(currentNode!) - ? Diagnostics.Type_produces_a_tuple_type_that_is_too_large_to_represent - : Diagnostics.Expression_produces_a_tuple_type_that_is_too_large_to_represent); - return errorType; - } - // Spread variadic elements with tuple types into the resulting tuple. - forEach(elements, (t, n) => addElement(t, type.target.elementFlags[n], type.target.labeledElementDeclarations?.[n])); - } - else { - // Treat everything else as an array type and create a rest element. - addElement(isArrayLikeType(type) && getIndexTypeOfType(type, numberType) || errorType, ElementFlags.Rest, target.labeledElementDeclarations?.[i]); + // We have optional, rest, or variadic elements that may need normalizing. Normalization ensures that all variadic + // elements are generic and that the tuple type has one of the following layouts, disregarding variadic elements: + // (1) Zero or more required elements, followed by zero or more optional elements, followed by zero or one rest element. + // (2) Zero or more required elements, followed by a rest element, followed by zero or more required elements. + // In either layout, zero or more generic variadic elements may be present at any location. + const expandedTypes: Type[] = []; + const expandedFlags: ElementFlags[] = []; + let expandedDeclarations: (NamedTupleMember | ParameterDeclaration)[] | undefined = []; + let lastRequiredIndex = -1; + let firstRestIndex = -1; + let lastOptionalOrRestIndex = -1; + for (let i = 0; i < elementTypes.length; i++) { + const type = elementTypes[i]; + const flags = target.elementFlags[i]; + if (flags & ElementFlags.Variadic) { + if (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type)) { + // Generic variadic elements stay as they are. + addElement(type, ElementFlags.Variadic, target.labeledElementDeclarations?.[i]); + } + else if (isTupleType(type)) { + const elements = getTypeArguments(type); + if (elements.length + expandedTypes.length >= 10_000) { + error(currentNode, isPartOfTypeNode(currentNode!) + ? Diagnostics.Type_produces_a_tuple_type_that_is_too_large_to_represent + : Diagnostics.Expression_produces_a_tuple_type_that_is_too_large_to_represent); + return errorType; } + // Spread variadic elements with tuple types into the resulting tuple. + forEach(elements, (t, n) => addElement(t, type.target.elementFlags[n], type.target.labeledElementDeclarations?.[n])); } else { - // Copy other element kinds with no change. - addElement(type, flags, target.labeledElementDeclarations?.[i]); + // Treat everything else as an array type and create a rest element. + addElement(isArrayLikeType(type) && getIndexTypeOfType(type, numberType) || errorType, ElementFlags.Rest, target.labeledElementDeclarations?.[i]); } } - // Turn optional elements preceding the last required element into required elements - for (let i = 0; i < lastRequiredIndex; i++) { - if (expandedFlags[i] & ElementFlags.Optional) expandedFlags[i] = ElementFlags.Required; - } - if (firstRestIndex >= 0 && firstRestIndex < lastOptionalOrRestIndex) { - // Turn elements between first rest and last optional/rest into a single rest element - expandedTypes[firstRestIndex] = getUnionType(sameMap(expandedTypes.slice(firstRestIndex, lastOptionalOrRestIndex + 1), - (t, i) => expandedFlags[firstRestIndex + i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t)); - expandedTypes.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); - expandedFlags.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); - expandedDeclarations?.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); + else { + // Copy other element kinds with no change. + addElement(type, flags, target.labeledElementDeclarations?.[i]); } - const tupleTarget = getTupleTargetType(expandedFlags, target.readonly, expandedDeclarations); - return tupleTarget === emptyGenericType ? emptyObjectType : - expandedFlags.length ? createTypeReference(tupleTarget, expandedTypes) : - tupleTarget; + } + // Turn optional elements preceding the last required element into required elements + for (let i = 0; i < lastRequiredIndex; i++) { + if (expandedFlags[i] & ElementFlags.Optional) expandedFlags[i] = ElementFlags.Required; + } + if (firstRestIndex >= 0 && firstRestIndex < lastOptionalOrRestIndex) { + // Turn elements between first rest and last optional/rest into a single rest element + expandedTypes[firstRestIndex] = getUnionType(sameMap(expandedTypes.slice(firstRestIndex, lastOptionalOrRestIndex + 1), + (t, i) => expandedFlags[firstRestIndex + i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t)); + expandedTypes.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); + expandedFlags.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); + expandedDeclarations?.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); + } + const tupleTarget = getTupleTargetType(expandedFlags, target.readonly, expandedDeclarations); + return tupleTarget === emptyGenericType ? emptyObjectType : + expandedFlags.length ? createTypeReference(tupleTarget, expandedTypes) : + tupleTarget; - function addElement(type: Type, flags: ElementFlags, declaration: NamedTupleMember | ParameterDeclaration | undefined) { - if (flags & ElementFlags.Required) { - lastRequiredIndex = expandedFlags.length; - } - if (flags & ElementFlags.Rest && firstRestIndex < 0) { - firstRestIndex = expandedFlags.length; - } - if (flags & (ElementFlags.Optional | ElementFlags.Rest)) { - lastOptionalOrRestIndex = expandedFlags.length; - } - expandedTypes.push(type); - expandedFlags.push(flags); - if (expandedDeclarations && declaration) { - expandedDeclarations.push(declaration); - } - else { - expandedDeclarations = undefined; - } + function addElement(type: Type, flags: ElementFlags, declaration: NamedTupleMember | ParameterDeclaration | undefined) { + if (flags & ElementFlags.Required) { + lastRequiredIndex = expandedFlags.length; + } + if (flags & ElementFlags.Rest && firstRestIndex < 0) { + firstRestIndex = expandedFlags.length; + } + if (flags & (ElementFlags.Optional | ElementFlags.Rest)) { + lastOptionalOrRestIndex = expandedFlags.length; + } + expandedTypes.push(type); + expandedFlags.push(flags); + if (expandedDeclarations && declaration) { + expandedDeclarations.push(declaration); + } + else { + expandedDeclarations = undefined; } } + } - function sliceTupleType(type: TupleTypeReference, index: number, endSkipCount = 0) { - const target = type.target; - const endIndex = getTypeReferenceArity(type) - endSkipCount; - return index > target.fixedLength ? getRestArrayTypeOfTupleType(type) || createTupleType(emptyArray) : - createTupleType(getTypeArguments(type).slice(index, endIndex), target.elementFlags.slice(index, endIndex), - /*readonly*/ false, target.labeledElementDeclarations && target.labeledElementDeclarations.slice(index, endIndex)); - } + function sliceTupleType(type: TupleTypeReference, index: number, endSkipCount = 0) { + const target = type.target; + const endIndex = getTypeReferenceArity(type) - endSkipCount; + return index > target.fixedLength ? getRestArrayTypeOfTupleType(type) || createTupleType(emptyArray) : + createTupleType(getTypeArguments(type).slice(index, endIndex), target.elementFlags.slice(index, endIndex), + /*readonly*/ false, target.labeledElementDeclarations && target.labeledElementDeclarations.slice(index, endIndex)); + } - function getKnownKeysOfTupleType(type: TupleTypeReference) { - return getUnionType(append(arrayOf(type.target.fixedLength, i => getStringLiteralType("" + i)), - getIndexType(type.target.readonly ? globalReadonlyArrayType : globalArrayType))); - } + function getKnownKeysOfTupleType(type: TupleTypeReference) { + return getUnionType(append(arrayOf(type.target.fixedLength, i => getStringLiteralType("" + i)), + getIndexType(type.target.readonly ? globalReadonlyArrayType : globalArrayType))); + } - // Return count of starting consecutive tuple elements of the given kind(s) - function getStartElementCount(type: TupleType, flags: ElementFlags) { - const index = findIndex(type.elementFlags, f => !(f & flags)); - return index >= 0 ? index : type.elementFlags.length; - } + // Return count of starting consecutive tuple elements of the given kind(s) + function getStartElementCount(type: TupleType, flags: ElementFlags) { + const index = findIndex(type.elementFlags, f => !(f & flags)); + return index >= 0 ? index : type.elementFlags.length; + } - // Return count of ending consecutive tuple elements of the given kind(s) - function getEndElementCount(type: TupleType, flags: ElementFlags) { - return type.elementFlags.length - findLastIndex(type.elementFlags, f => !(f & flags)) - 1; - } + // Return count of ending consecutive tuple elements of the given kind(s) + function getEndElementCount(type: TupleType, flags: ElementFlags) { + return type.elementFlags.length - findLastIndex(type.elementFlags, f => !(f & flags)) - 1; + } - function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type { - return addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true); - } + function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type { + return addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true); + } - function getTypeId(type: Type): TypeId { - return type.id; - } + function getTypeId(type: Type): TypeId { + return type.id; + } - function containsType(types: readonly Type[], type: Type): boolean { - return binarySearch(types, type, getTypeId, compareValues) >= 0; - } + function containsType(types: readonly Type[], type: Type): boolean { + return binarySearch(types, type, getTypeId, compareValues) >= 0; + } - function insertType(types: Type[], type: Type): boolean { - const index = binarySearch(types, type, getTypeId, compareValues); - if (index < 0) { - types.splice(~index, 0, type); - return true; - } - return false; + function insertType(types: Type[], type: Type): boolean { + const index = binarySearch(types, type, getTypeId, compareValues); + if (index < 0) { + types.splice(~index, 0, type); + return true; } + return false; + } - function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { - const flags = type.flags; - if (flags & TypeFlags.Union) { - return addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types); + function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { + const flags = type.flags; + if (flags & TypeFlags.Union) { + return addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types); + } + // We ignore 'never' types in unions + if (!(flags & TypeFlags.Never)) { + includes |= flags & TypeFlags.IncludesMask; + if (flags & TypeFlags.Instantiable) includes |= TypeFlags.IncludesInstantiable; + if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; + if (!strictNullChecks && flags & TypeFlags.Nullable) { + if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType; } - // We ignore 'never' types in unions - if (!(flags & TypeFlags.Never)) { - includes |= flags & TypeFlags.IncludesMask; - if (flags & TypeFlags.Instantiable) includes |= TypeFlags.IncludesInstantiable; - if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; - if (!strictNullChecks && flags & TypeFlags.Nullable) { - if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType; - } - else { - const len = typeSet.length; - const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues); - if (index < 0) { - typeSet.splice(~index, 0, type); - } + else { + const len = typeSet.length; + const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues); + if (index < 0) { + typeSet.splice(~index, 0, type); } } - return includes; } + return includes; + } - // Add the given types to the given type set. Order is preserved, duplicates are removed, - // and nested types of the given kind are flattened into the set. - function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: readonly Type[]): TypeFlags { - for (const type of types) { - includes = addTypeToUnion(typeSet, includes, type); - } - return includes; + // Add the given types to the given type set. Order is preserved, duplicates are removed, + // and nested types of the given kind are flattened into the set. + function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: readonly Type[]): TypeFlags { + for (const type of types) { + includes = addTypeToUnion(typeSet, includes, type); } + return includes; + } - function removeSubtypes(types: Type[], hasObjectTypes: boolean): Type[] | undefined { - const id = getTypeListId(types); - const match = subtypeReductionCache.get(id); - if (match) { - return match; - } - // We assume that redundant primitive types have already been removed from the types array and that there - // are no any and unknown types in the array. Thus, the only possible supertypes for primitive types are empty - // object types, and if none of those are present we can exclude primitive types from the subtype check. - const hasEmptyObject = hasObjectTypes && some(types, t => !!(t.flags & TypeFlags.Object) && !isGenericMappedType(t) && isEmptyResolvedType(resolveStructuredTypeMembers(t as ObjectType))); - const len = types.length; - let i = len; - let count = 0; - while (i > 0) { - i--; - const source = types[i]; - if (hasEmptyObject || source.flags & TypeFlags.StructuredOrInstantiable) { - // Find the first property with a unit type, if any. When constituents have a property by the same name - // but of a different unit type, we can quickly disqualify them from subtype checks. This helps subtype - // reduction of large discriminated union types. - const keyProperty = source.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive) ? - find(getPropertiesOfType(source), p => isUnitType(getTypeOfSymbol(p))) : - undefined; - const keyPropertyType = keyProperty && getRegularTypeOfLiteralType(getTypeOfSymbol(keyProperty)); - for (const target of types) { - if (source !== target) { - if (count === 100000) { - // After 100000 subtype checks we estimate the remaining amount of work by assuming the - // same ratio of checks per element. If the estimated number of remaining type checks is - // greater than 1M we deem the union type too complex to represent. This for example - // caps union types at 1000 unique object types. - const estimatedCount = (count / (len - i)) * len; - if (estimatedCount > 1000000) { - tracing?.instant(tracing.Phase.CheckTypes, "removeSubtypes_DepthLimit", { typeIds: types.map(t => t.id) }); - error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); - return undefined; - } - } - count++; - if (keyProperty && target.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive)) { - const t = getTypeOfPropertyOfType(target, keyProperty.escapedName); - if (t && isUnitType(t) && getRegularTypeOfLiteralType(t) !== keyPropertyType) { - continue; - } + function removeSubtypes(types: Type[], hasObjectTypes: boolean): Type[] | undefined { + const id = getTypeListId(types); + const match = subtypeReductionCache.get(id); + if (match) { + return match; + } + // We assume that redundant primitive types have already been removed from the types array and that there + // are no any and unknown types in the array. Thus, the only possible supertypes for primitive types are empty + // object types, and if none of those are present we can exclude primitive types from the subtype check. + const hasEmptyObject = hasObjectTypes && some(types, t => !!(t.flags & TypeFlags.Object) && !isGenericMappedType(t) && isEmptyResolvedType(resolveStructuredTypeMembers(t as ObjectType))); + const len = types.length; + let i = len; + let count = 0; + while (i > 0) { + i--; + const source = types[i]; + if (hasEmptyObject || source.flags & TypeFlags.StructuredOrInstantiable) { + // Find the first property with a unit type, if any. When constituents have a property by the same name + // but of a different unit type, we can quickly disqualify them from subtype checks. This helps subtype + // reduction of large discriminated union types. + const keyProperty = source.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive) ? + find(getPropertiesOfType(source), p => isUnitType(getTypeOfSymbol(p))) : + undefined; + const keyPropertyType = keyProperty && getRegularTypeOfLiteralType(getTypeOfSymbol(keyProperty)); + for (const target of types) { + if (source !== target) { + if (count === 100000) { + // After 100000 subtype checks we estimate the remaining amount of work by assuming the + // same ratio of checks per element. If the estimated number of remaining type checks is + // greater than 1M we deem the union type too complex to represent. This for example + // caps union types at 1000 unique object types. + const estimatedCount = (count / (len - i)) * len; + if (estimatedCount > 1000000) { + tracing?.instant(tracing.Phase.CheckTypes, "removeSubtypes_DepthLimit", { typeIds: types.map(t => t.id) }); + error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); + return undefined; } - if (isTypeRelatedTo(source, target, strictSubtypeRelation) && ( - !(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) || - !(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) || - isTypeDerivedFrom(source, target))) { - orderedRemoveItemAt(types, i); - break; + } + count++; + if (keyProperty && target.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive)) { + const t = getTypeOfPropertyOfType(target, keyProperty.escapedName); + if (t && isUnitType(t) && getRegularTypeOfLiteralType(t) !== keyPropertyType) { + continue; } } + if (isTypeRelatedTo(source, target, strictSubtypeRelation) && ( + !(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) || + !(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) || + isTypeDerivedFrom(source, target))) { + orderedRemoveItemAt(types, i); + break; + } } } } - subtypeReductionCache.set(id, types); - return types; } + subtypeReductionCache.set(id, types); + return types; + } + + function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags, reduceVoidUndefined: boolean) { + let i = types.length; + while (i > 0) { + i--; + const t = types[i]; + const flags = t.flags; + const remove = + flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && includes & TypeFlags.String || + flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number || + flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt || + flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol || + reduceVoidUndefined && flags & TypeFlags.Undefined && includes & TypeFlags.Void || + isFreshLiteralType(t) && containsType(types, (t as LiteralType).regularType); + if (remove) { + orderedRemoveItemAt(types, i); + } + } + } - function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags, reduceVoidUndefined: boolean) { + function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) { + const templates = filter(types, isPatternLiteralType) as TemplateLiteralType[]; + if (templates.length) { let i = types.length; while (i > 0) { i--; const t = types[i]; - const flags = t.flags; - const remove = - flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && includes & TypeFlags.String || - flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number || - flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt || - flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol || - reduceVoidUndefined && flags & TypeFlags.Undefined && includes & TypeFlags.Void || - isFreshLiteralType(t) && containsType(types, (t as LiteralType).regularType); - if (remove) { + if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeMatchedByTemplateLiteralType(t, template))) { orderedRemoveItemAt(types, i); } } } + } - function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) { - const templates = filter(types, isPatternLiteralType) as TemplateLiteralType[]; - if (templates.length) { - let i = types.length; - while (i > 0) { - i--; - const t = types[i]; - if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeMatchedByTemplateLiteralType(t, template))) { - orderedRemoveItemAt(types, i); - } - } - } - } - - function isNamedUnionType(type: Type) { - return !!(type.flags & TypeFlags.Union && (type.aliasSymbol || (type as UnionType).origin)); - } + function isNamedUnionType(type: Type) { + return !!(type.flags & TypeFlags.Union && (type.aliasSymbol || (type as UnionType).origin)); + } - function addNamedUnions(namedUnions: Type[], types: readonly Type[]) { - for (const t of types) { - if (t.flags & TypeFlags.Union) { - const origin = (t as UnionType).origin; - if (t.aliasSymbol || origin && !(origin.flags & TypeFlags.Union)) { - pushIfUnique(namedUnions, t); - } - else if (origin && origin.flags & TypeFlags.Union) { - addNamedUnions(namedUnions, (origin as UnionType).types); - } + function addNamedUnions(namedUnions: Type[], types: readonly Type[]) { + for (const t of types) { + if (t.flags & TypeFlags.Union) { + const origin = (t as UnionType).origin; + if (t.aliasSymbol || origin && !(origin.flags & TypeFlags.Union)) { + pushIfUnique(namedUnions, t); + } + else if (origin && origin.flags & TypeFlags.Union) { + addNamedUnions(namedUnions, (origin as UnionType).types); } } } + } - function createOriginUnionOrIntersectionType(flags: TypeFlags, types: Type[]) { - const result = createOriginType(flags) as UnionOrIntersectionType; - result.types = types; - return result; - } + function createOriginUnionOrIntersectionType(flags: TypeFlags, types: Type[]) { + const result = createOriginType(flags) as UnionOrIntersectionType; + result.types = types; + return result; + } - // We sort and deduplicate the constituent types based on object identity. If the subtypeReduction - // flag is specified we also reduce the constituent type set to only include types that aren't subtypes - // of other types. Subtype reduction is expensive for large union types and is possible only when union - // types are known not to circularly reference themselves (as is the case with union types created by - // expression constructs such as array literals and the || and ?: operators). Named types can - // circularly reference themselves and therefore cannot be subtype reduced during their declaration. - // For example, "type Item = string | (() => Item" is a named type that circularly references itself. - function getUnionType(types: readonly Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { - if (types.length === 0) { - return neverType; - } - if (types.length === 1) { - return types[0]; + // We sort and deduplicate the constituent types based on object identity. If the subtypeReduction + // flag is specified we also reduce the constituent type set to only include types that aren't subtypes + // of other types. Subtype reduction is expensive for large union types and is possible only when union + // types are known not to circularly reference themselves (as is the case with union types created by + // expression constructs such as array literals and the || and ?: operators). Named types can + // circularly reference themselves and therefore cannot be subtype reduced during their declaration. + // For example, "type Item = string | (() => Item" is a named type that circularly references itself. + function getUnionType(types: readonly Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { + if (types.length === 0) { + return neverType; + } + if (types.length === 1) { + return types[0]; + } + let typeSet: Type[] | undefined = []; + const includes = addTypesToUnion(typeSet, 0, types); + if (unionReduction !== UnionReduction.None) { + if (includes & TypeFlags.AnyOrUnknown) { + return includes & TypeFlags.Any ? + includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : + includes & TypeFlags.Null || containsType(typeSet, unknownType) ? unknownType : nonNullUnknownType; } - let typeSet: Type[] | undefined = []; - const includes = addTypesToUnion(typeSet, 0, types); - if (unionReduction !== UnionReduction.None) { - if (includes & TypeFlags.AnyOrUnknown) { - return includes & TypeFlags.Any ? - includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : - includes & TypeFlags.Null || containsType(typeSet, unknownType) ? unknownType : nonNullUnknownType; - } - if (exactOptionalPropertyTypes && includes & TypeFlags.Undefined) { - const missingIndex = binarySearch(typeSet, missingType, getTypeId, compareValues); - if (missingIndex >= 0 && containsType(typeSet, undefinedType)) { - orderedRemoveItemAt(typeSet, missingIndex); - } - } - if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || includes & TypeFlags.Void && includes & TypeFlags.Undefined) { - removeRedundantLiteralTypes(typeSet, includes, !!(unionReduction & UnionReduction.Subtype)); - } - if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) { - removeStringLiteralsMatchedByTemplateLiterals(typeSet); - } - if (unionReduction === UnionReduction.Subtype) { - typeSet = removeSubtypes(typeSet, !!(includes & TypeFlags.Object)); - if (!typeSet) { - return errorType; - } - } - if (typeSet.length === 0) { - return includes & TypeFlags.Null ? includes & TypeFlags.IncludesNonWideningType ? nullType : nullWideningType : - includes & TypeFlags.Undefined ? includes & TypeFlags.IncludesNonWideningType ? undefinedType : undefinedWideningType : - neverType; + if (exactOptionalPropertyTypes && includes & TypeFlags.Undefined) { + const missingIndex = binarySearch(typeSet, missingType, getTypeId, compareValues); + if (missingIndex >= 0 && containsType(typeSet, undefinedType)) { + orderedRemoveItemAt(typeSet, missingIndex); } } - if (!origin && includes & TypeFlags.Union) { - const namedUnions: Type[] = []; - addNamedUnions(namedUnions, types); - const reducedTypes: Type[] = []; - for (const t of typeSet) { - if (!some(namedUnions, union => containsType((union as UnionType).types, t))) { - reducedTypes.push(t); - } + if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || includes & TypeFlags.Void && includes & TypeFlags.Undefined) { + removeRedundantLiteralTypes(typeSet, includes, !!(unionReduction & UnionReduction.Subtype)); + } + if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) { + removeStringLiteralsMatchedByTemplateLiterals(typeSet); + } + if (unionReduction === UnionReduction.Subtype) { + typeSet = removeSubtypes(typeSet, !!(includes & TypeFlags.Object)); + if (!typeSet) { + return errorType; } - if (!aliasSymbol && namedUnions.length === 1 && reducedTypes.length === 0) { - return namedUnions[0]; + } + if (typeSet.length === 0) { + return includes & TypeFlags.Null ? includes & TypeFlags.IncludesNonWideningType ? nullType : nullWideningType : + includes & TypeFlags.Undefined ? includes & TypeFlags.IncludesNonWideningType ? undefinedType : undefinedWideningType : + neverType; + } + } + if (!origin && includes & TypeFlags.Union) { + const namedUnions: Type[] = []; + addNamedUnions(namedUnions, types); + const reducedTypes: Type[] = []; + for (const t of typeSet) { + if (!some(namedUnions, union => containsType((union as UnionType).types, t))) { + reducedTypes.push(t); } - // We create a denormalized origin type only when the union was created from one or more named unions - // (unions with alias symbols or origins) and when there is no overlap between those named unions. - const namedTypesCount = reduceLeft(namedUnions, (sum, union) => sum + (union as UnionType).types.length, 0); - if (namedTypesCount + reducedTypes.length === typeSet.length) { - for (const t of namedUnions) { - insertType(reducedTypes, t); - } - origin = createOriginUnionOrIntersectionType(TypeFlags.Union, reducedTypes); + } + if (!aliasSymbol && namedUnions.length === 1 && reducedTypes.length === 0) { + return namedUnions[0]; + } + // We create a denormalized origin type only when the union was created from one or more named unions + // (unions with alias symbols or origins) and when there is no overlap between those named unions. + const namedTypesCount = reduceLeft(namedUnions, (sum, union) => sum + (union as UnionType).types.length, 0); + if (namedTypesCount + reducedTypes.length === typeSet.length) { + for (const t of namedUnions) { + insertType(reducedTypes, t); } + origin = createOriginUnionOrIntersectionType(TypeFlags.Union, reducedTypes); } - const objectFlags = (includes & TypeFlags.NotPrimitiveUnion ? 0 : ObjectFlags.PrimitiveUnion) | - (includes & TypeFlags.Intersection ? ObjectFlags.ContainsIntersections : 0); - return getUnionTypeFromSortedList(typeSet, objectFlags, aliasSymbol, aliasTypeArguments, origin); } + const objectFlags = (includes & TypeFlags.NotPrimitiveUnion ? 0 : ObjectFlags.PrimitiveUnion) | + (includes & TypeFlags.Intersection ? ObjectFlags.ContainsIntersections : 0); + return getUnionTypeFromSortedList(typeSet, objectFlags, aliasSymbol, aliasTypeArguments, origin); + } - function getUnionOrIntersectionTypePredicate(signatures: readonly Signature[], kind: TypeFlags | undefined): TypePredicate | undefined { - let first: TypePredicate | undefined; - const types: Type[] = []; - for (const sig of signatures) { - const pred = getTypePredicateOfSignature(sig); - if (!pred || pred.kind === TypePredicateKind.AssertsThis || pred.kind === TypePredicateKind.AssertsIdentifier) { - if (kind !== TypeFlags.Intersection) { - continue; - } - else { - return; // intersections demand all members be type predicates for the result to have a predicate - } - } - - if (first) { - if (!typePredicateKindsMatch(first, pred)) { - // No common type predicate. - return undefined; - } + function getUnionOrIntersectionTypePredicate(signatures: readonly Signature[], kind: TypeFlags | undefined): TypePredicate | undefined { + let first: TypePredicate | undefined; + const types: Type[] = []; + for (const sig of signatures) { + const pred = getTypePredicateOfSignature(sig); + if (!pred || pred.kind === TypePredicateKind.AssertsThis || pred.kind === TypePredicateKind.AssertsIdentifier) { + if (kind !== TypeFlags.Intersection) { + continue; } else { - first = pred; + return; // intersections demand all members be type predicates for the result to have a predicate } - types.push(pred.type); } - if (!first) { - // No signatures had a type predicate. - return undefined; + + if (first) { + if (!typePredicateKindsMatch(first, pred)) { + // No common type predicate. + return undefined; + } + } + else { + first = pred; } - const compositeType = getUnionOrIntersectionType(types, kind); - return createTypePredicate(first.kind, first.parameterName, first.parameterIndex, compositeType); + types.push(pred.type); } - - function typePredicateKindsMatch(a: TypePredicate, b: TypePredicate): boolean { - return a.kind === b.kind && a.parameterIndex === b.parameterIndex; + if (!first) { + // No signatures had a type predicate. + return undefined; } + const compositeType = getUnionOrIntersectionType(types, kind); + return createTypePredicate(first.kind, first.parameterName, first.parameterIndex, compositeType); + } - // This function assumes the constituent type list is sorted and deduplicated. - function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { - if (types.length === 0) { - return neverType; - } - if (types.length === 1) { - return types[0]; - } - const typeKey = !origin ? getTypeListId(types) : - origin.flags & TypeFlags.Union ? `|${getTypeListId((origin as UnionType).types)}` : - origin.flags & TypeFlags.Intersection ? `&${getTypeListId((origin as IntersectionType).types)}` : - `#${(origin as IndexType).type.id}|${getTypeListId(types)}`; // origin type id alone is insufficient, as `keyof x` may resolve to multiple WIP values while `x` is still resolving - const id = typeKey + getAliasId(aliasSymbol, aliasTypeArguments); - let type = unionTypes.get(id); - if (!type) { - type = createType(TypeFlags.Union) as UnionType; - type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); - type.types = types; - type.origin = origin; - type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = aliasTypeArguments; - if (types.length === 2 && types[0].flags & TypeFlags.BooleanLiteral && types[1].flags & TypeFlags.BooleanLiteral) { - type.flags |= TypeFlags.Boolean; - (type as UnionType & IntrinsicType).intrinsicName = "boolean"; - } - unionTypes.set(id, type); + function typePredicateKindsMatch(a: TypePredicate, b: TypePredicate): boolean { + return a.kind === b.kind && a.parameterIndex === b.parameterIndex; + } + + // This function assumes the constituent type list is sorted and deduplicated. + function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { + if (types.length === 0) { + return neverType; + } + if (types.length === 1) { + return types[0]; + } + const typeKey = !origin ? getTypeListId(types) : + origin.flags & TypeFlags.Union ? `|${getTypeListId((origin as UnionType).types)}` : + origin.flags & TypeFlags.Intersection ? `&${getTypeListId((origin as IntersectionType).types)}` : + `#${(origin as IndexType).type.id}|${getTypeListId(types)}`; // origin type id alone is insufficient, as `keyof x` may resolve to multiple WIP values while `x` is still resolving + const id = typeKey + getAliasId(aliasSymbol, aliasTypeArguments); + let type = unionTypes.get(id); + if (!type) { + type = createType(TypeFlags.Union) as UnionType; + type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + type.types = types; + type.origin = origin; + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = aliasTypeArguments; + if (types.length === 2 && types[0].flags & TypeFlags.BooleanLiteral && types[1].flags & TypeFlags.BooleanLiteral) { + type.flags |= TypeFlags.Boolean; + (type as UnionType & IntrinsicType).intrinsicName = "boolean"; } - return type; + unionTypes.set(id, type); } + return type; + } - function getTypeFromUnionTypeNode(node: UnionTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const aliasSymbol = getAliasSymbolForTypeNode(node); - links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal, - aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); - } - return links.resolvedType; + function getTypeFromUnionTypeNode(node: UnionTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const aliasSymbol = getAliasSymbolForTypeNode(node); + links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal, + aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); } + return links.resolvedType; + } - function addTypeToIntersection(typeSet: ESMap, includes: TypeFlags, type: Type) { - const flags = type.flags; - if (flags & TypeFlags.Intersection) { - return addTypesToIntersection(typeSet, includes, (type as IntersectionType).types); + function addTypeToIntersection(typeSet: ESMap, includes: TypeFlags, type: Type) { + const flags = type.flags; + if (flags & TypeFlags.Intersection) { + return addTypesToIntersection(typeSet, includes, (type as IntersectionType).types); + } + if (isEmptyAnonymousObjectType(type)) { + if (!(includes & TypeFlags.IncludesEmptyObject)) { + includes |= TypeFlags.IncludesEmptyObject; + typeSet.set(type.id.toString(), type); } - if (isEmptyAnonymousObjectType(type)) { - if (!(includes & TypeFlags.IncludesEmptyObject)) { - includes |= TypeFlags.IncludesEmptyObject; - typeSet.set(type.id.toString(), type); - } + } + else { + if (flags & TypeFlags.AnyOrUnknown) { + if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; } - else { - if (flags & TypeFlags.AnyOrUnknown) { - if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; + else if (strictNullChecks || !(flags & TypeFlags.Nullable)) { + if (exactOptionalPropertyTypes && type === missingType) { + includes |= TypeFlags.IncludesMissingType; + type = undefinedType; } - else if (strictNullChecks || !(flags & TypeFlags.Nullable)) { - if (exactOptionalPropertyTypes && type === missingType) { - includes |= TypeFlags.IncludesMissingType; - type = undefinedType; - } - if (!typeSet.has(type.id.toString())) { - if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) { - // We have seen two distinct unit types which means we should reduce to an - // empty intersection. Adding TypeFlags.NonPrimitive causes that to happen. - includes |= TypeFlags.NonPrimitive; - } - typeSet.set(type.id.toString(), type); + if (!typeSet.has(type.id.toString())) { + if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) { + // We have seen two distinct unit types which means we should reduce to an + // empty intersection. Adding TypeFlags.NonPrimitive causes that to happen. + includes |= TypeFlags.NonPrimitive; } + typeSet.set(type.id.toString(), type); } - includes |= flags & TypeFlags.IncludesMask; } - return includes; + includes |= flags & TypeFlags.IncludesMask; } + return includes; + } - // Add the given types to the given type set. Order is preserved, freshness is removed from literal - // types, duplicates are removed, and nested types of the given kind are flattened into the set. - function addTypesToIntersection(typeSet: ESMap, includes: TypeFlags, types: readonly Type[]) { - for (const type of types) { - includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type)); - } - return includes; + // Add the given types to the given type set. Order is preserved, freshness is removed from literal + // types, duplicates are removed, and nested types of the given kind are flattened into the set. + function addTypesToIntersection(typeSet: ESMap, includes: TypeFlags, types: readonly Type[]) { + for (const type of types) { + includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type)); } + return includes; + } - function removeRedundantPrimitiveTypes(types: Type[], includes: TypeFlags) { - let i = types.length; - while (i > 0) { - i--; - const t = types[i]; - const remove = - t.flags & TypeFlags.String && includes & TypeFlags.StringLiteral || - t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral || - t.flags & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || - t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol; - if (remove) { - orderedRemoveItemAt(types, i); - } + function removeRedundantPrimitiveTypes(types: Type[], includes: TypeFlags) { + let i = types.length; + while (i > 0) { + i--; + const t = types[i]; + const remove = + t.flags & TypeFlags.String && includes & TypeFlags.StringLiteral || + t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral || + t.flags & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || + t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol; + if (remove) { + orderedRemoveItemAt(types, i); } } + } - // Check that the given type has a match in every union. A given type is matched by - // an identical type, and a literal type is additionally matched by its corresponding - // primitive type. - function eachUnionContains(unionTypes: UnionType[], type: Type) { - for (const u of unionTypes) { - if (!containsType(u.types, type)) { - const primitive = type.flags & TypeFlags.StringLiteral ? stringType : - type.flags & TypeFlags.NumberLiteral ? numberType : - type.flags & TypeFlags.BigIntLiteral ? bigintType : - type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : - undefined; - if (!primitive || !containsType(u.types, primitive)) { - return false; - } + // Check that the given type has a match in every union. A given type is matched by + // an identical type, and a literal type is additionally matched by its corresponding + // primitive type. + function eachUnionContains(unionTypes: UnionType[], type: Type) { + for (const u of unionTypes) { + if (!containsType(u.types, type)) { + const primitive = type.flags & TypeFlags.StringLiteral ? stringType : + type.flags & TypeFlags.NumberLiteral ? numberType : + type.flags & TypeFlags.BigIntLiteral ? bigintType : + type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : + undefined; + if (!primitive || !containsType(u.types, primitive)) { + return false; } } - return true; } + return true; + } - /** - * Returns `true` if the intersection of the template literals and string literals is the empty set, eg `get${string}` & "setX", and should reduce to `never` - */ - function extractRedundantTemplateLiterals(types: Type[]): boolean { - let i = types.length; - const literals = filter(types, t => !!(t.flags & TypeFlags.StringLiteral)); - while (i > 0) { - i--; - const t = types[i]; - if (!(t.flags & TypeFlags.TemplateLiteral)) continue; - for (const t2 of literals) { - if (isTypeSubtypeOf(t2, t)) { - // eg, ``get${T}` & "getX"` is just `"getX"` - orderedRemoveItemAt(types, i); - break; - } - else if (isPatternLiteralType(t)) { - return true; - } + /** + * Returns `true` if the intersection of the template literals and string literals is the empty set, eg `get${string}` & "setX", and should reduce to `never` + */ + function extractRedundantTemplateLiterals(types: Type[]): boolean { + let i = types.length; + const literals = filter(types, t => !!(t.flags & TypeFlags.StringLiteral)); + while (i > 0) { + i--; + const t = types[i]; + if (!(t.flags & TypeFlags.TemplateLiteral)) continue; + for (const t2 of literals) { + if (isTypeSubtypeOf(t2, t)) { + // eg, ``get${T}` & "getX"` is just `"getX"` + orderedRemoveItemAt(types, i); + break; + } + else if (isPatternLiteralType(t)) { + return true; } } - return false; } + return false; + } - function eachIsUnionContaining(types: Type[], flag: TypeFlags) { - return every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag))); - } + function eachIsUnionContaining(types: Type[], flag: TypeFlags) { + return every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag))); + } - function removeFromEach(types: Type[], flag: TypeFlags) { - for (let i = 0; i < types.length; i++) { - types[i] = filterType(types[i], t => !(t.flags & flag)); - } + function removeFromEach(types: Type[], flag: TypeFlags) { + for (let i = 0; i < types.length; i++) { + types[i] = filterType(types[i], t => !(t.flags & flag)); } + } - // If the given list of types contains more than one union of primitive types, replace the - // first with a union containing an intersection of those primitive types, then remove the - // other unions and return true. Otherwise, do nothing and return false. - function intersectUnionsOfPrimitiveTypes(types: Type[]) { - let unionTypes: UnionType[] | undefined; - const index = findIndex(types, t => !!(getObjectFlags(t) & ObjectFlags.PrimitiveUnion)); - if (index < 0) { - return false; - } - let i = index + 1; - // Remove all but the first union of primitive types and collect them in - // the unionTypes array. - while (i < types.length) { - const t = types[i]; - if (getObjectFlags(t) & ObjectFlags.PrimitiveUnion) { - (unionTypes || (unionTypes = [types[index] as UnionType])).push(t as UnionType); - orderedRemoveItemAt(types, i); - } - else { - i++; - } + // If the given list of types contains more than one union of primitive types, replace the + // first with a union containing an intersection of those primitive types, then remove the + // other unions and return true. Otherwise, do nothing and return false. + function intersectUnionsOfPrimitiveTypes(types: Type[]) { + let unionTypes: UnionType[] | undefined; + const index = findIndex(types, t => !!(getObjectFlags(t) & ObjectFlags.PrimitiveUnion)); + if (index < 0) { + return false; + } + let i = index + 1; + // Remove all but the first union of primitive types and collect them in + // the unionTypes array. + while (i < types.length) { + const t = types[i]; + if (getObjectFlags(t) & ObjectFlags.PrimitiveUnion) { + (unionTypes || (unionTypes = [types[index] as UnionType])).push(t as UnionType); + orderedRemoveItemAt(types, i); } - // Return false if there was only one union of primitive types - if (!unionTypes) { - return false; + else { + i++; } - // We have more than one union of primitive types, now intersect them. For each - // type in each union we check if the type is matched in every union and if so - // we include it in the result. - const checked: Type[] = []; - const result: Type[] = []; - for (const u of unionTypes) { - for (const t of u.types) { - if (insertType(checked, t)) { - if (eachUnionContains(unionTypes, t)) { - insertType(result, t); - } + } + // Return false if there was only one union of primitive types + if (!unionTypes) { + return false; + } + // We have more than one union of primitive types, now intersect them. For each + // type in each union we check if the type is matched in every union and if so + // we include it in the result. + const checked: Type[] = []; + const result: Type[] = []; + for (const u of unionTypes) { + for (const t of u.types) { + if (insertType(checked, t)) { + if (eachUnionContains(unionTypes, t)) { + insertType(result, t); } } } - // Finally replace the first union with the result - types[index] = getUnionTypeFromSortedList(result, ObjectFlags.PrimitiveUnion); - return true; } + // Finally replace the first union with the result + types[index] = getUnionTypeFromSortedList(result, ObjectFlags.PrimitiveUnion); + return true; + } - function createIntersectionType(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { - const result = createType(TypeFlags.Intersection) as IntersectionType; - result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); - result.types = types; - result.aliasSymbol = aliasSymbol; - result.aliasTypeArguments = aliasTypeArguments; - return result; - } + function createIntersectionType(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { + const result = createType(TypeFlags.Intersection) as IntersectionType; + result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + result.types = types; + result.aliasSymbol = aliasSymbol; + result.aliasTypeArguments = aliasTypeArguments; + return result; + } - // We normalize combinations of intersection and union types based on the distributive property of the '&' - // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection - // types with union type constituents into equivalent union types with intersection type constituents and - // effectively ensure that union types are always at the top level in type representations. - // - // We do not perform structural deduplication on intersection types. Intersection types are created only by the & - // type operator and we can't reduce those because we want to support recursive intersection types. For example, - // a type alias of the form "type List = T & { next: List }" cannot be reduced during its declaration. - // Also, unlike union types, the order of the constituent types is preserved in order that overload resolution - // for intersections of types with signatures can be deterministic. - function getIntersectionType(types: readonly Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - const typeMembershipMap: ESMap = new Map(); - const includes = addTypesToIntersection(typeMembershipMap, 0, types); - const typeSet: Type[] = arrayFrom(typeMembershipMap.values()); - // An intersection type is considered empty if it contains - // the type never, or - // more than one unit type or, - // an object type and a nullable type (null or undefined), or - // a string-like type and a type known to be non-string-like, or - // a number-like type and a type known to be non-number-like, or - // a symbol-like type and a type known to be non-symbol-like, or - // a void-like type and a type known to be non-void-like, or - // a non-primitive type and a type known to be primitive. - if (includes & TypeFlags.Never) { - return contains(typeSet, silentNeverType) ? silentNeverType : neverType; - } - if (strictNullChecks && includes & TypeFlags.Nullable && includes & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.IncludesEmptyObject) || - includes & TypeFlags.NonPrimitive && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NonPrimitive) || - includes & TypeFlags.StringLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.StringLike) || - includes & TypeFlags.NumberLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NumberLike) || - includes & TypeFlags.BigIntLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.BigIntLike) || - includes & TypeFlags.ESSymbolLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.ESSymbolLike) || - includes & TypeFlags.VoidLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.VoidLike)) { - return neverType; - } - if (includes & TypeFlags.TemplateLiteral && includes & TypeFlags.StringLiteral && extractRedundantTemplateLiterals(typeSet)) { - return neverType; - } - if (includes & TypeFlags.Any) { - return includes & TypeFlags.IncludesWildcard ? wildcardType : anyType; - } - if (!strictNullChecks && includes & TypeFlags.Nullable) { - return includes & TypeFlags.Undefined ? undefinedType : nullType; - } - if (includes & TypeFlags.String && includes & TypeFlags.StringLiteral || - includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral || - includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || - includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol) { - removeRedundantPrimitiveTypes(typeSet, includes); - } - if (includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.Object) { - orderedRemoveItemAt(typeSet, findIndex(typeSet, isEmptyAnonymousObjectType)); - } - if (includes & TypeFlags.IncludesMissingType) { - typeSet[typeSet.indexOf(undefinedType)] = missingType; - } - if (typeSet.length === 0) { - return unknownType; - } - if (typeSet.length === 1) { - return typeSet[0]; - } - const id = getTypeListId(typeSet) + getAliasId(aliasSymbol, aliasTypeArguments); - let result = intersectionTypes.get(id); - if (!result) { - if (includes & TypeFlags.Union) { - if (intersectUnionsOfPrimitiveTypes(typeSet)) { - // When the intersection creates a reduced set (which might mean that *all* union types have - // disappeared), we restart the operation to get a new set of combined flags. Once we have - // reduced we'll never reduce again, so this occurs at most once. - result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); - } - else if (eachIsUnionContaining(typeSet, TypeFlags.Undefined)) { - const undefinedOrMissingType = exactOptionalPropertyTypes && some(typeSet, t => containsType((t as UnionType).types, missingType)) ? missingType : undefinedType; - removeFromEach(typeSet, TypeFlags.Undefined); - result = getUnionType([getIntersectionType(typeSet), undefinedOrMissingType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); - } - else if (eachIsUnionContaining(typeSet, TypeFlags.Null)) { - removeFromEach(typeSet, TypeFlags.Null); - result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); - } - else { - // We are attempting to construct a type of the form X & (A | B) & (C | D). Transform this into a type of - // the form X & A & C | X & A & D | X & B & C | X & B & D. If the estimated size of the resulting union type - // exceeds 100000 constituents, report an error. - if (!checkCrossProductUnion(typeSet)) { - return errorType; - } - const constituents = getCrossProductIntersections(typeSet); - // We attach a denormalized origin type when at least one constituent of the cross-product union is an - // intersection (i.e. when the intersection didn't just reduce one or more unions to smaller unions). - const origin = some(constituents, t => !!(t.flags & TypeFlags.Intersection)) ? createOriginUnionOrIntersectionType(TypeFlags.Intersection, typeSet) : undefined; - result = getUnionType(constituents, UnionReduction.Literal, aliasSymbol, aliasTypeArguments, origin); - } + // We normalize combinations of intersection and union types based on the distributive property of the '&' + // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection + // types with union type constituents into equivalent union types with intersection type constituents and + // effectively ensure that union types are always at the top level in type representations. + // + // We do not perform structural deduplication on intersection types. Intersection types are created only by the & + // type operator and we can't reduce those because we want to support recursive intersection types. For example, + // a type alias of the form "type List = T & { next: List }" cannot be reduced during its declaration. + // Also, unlike union types, the order of the constituent types is preserved in order that overload resolution + // for intersections of types with signatures can be deterministic. + function getIntersectionType(types: readonly Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + const typeMembershipMap: ESMap = new Map(); + const includes = addTypesToIntersection(typeMembershipMap, 0, types); + const typeSet: Type[] = arrayFrom(typeMembershipMap.values()); + // An intersection type is considered empty if it contains + // the type never, or + // more than one unit type or, + // an object type and a nullable type (null or undefined), or + // a string-like type and a type known to be non-string-like, or + // a number-like type and a type known to be non-number-like, or + // a symbol-like type and a type known to be non-symbol-like, or + // a void-like type and a type known to be non-void-like, or + // a non-primitive type and a type known to be primitive. + if (includes & TypeFlags.Never) { + return contains(typeSet, silentNeverType) ? silentNeverType : neverType; + } + if (strictNullChecks && includes & TypeFlags.Nullable && includes & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.IncludesEmptyObject) || + includes & TypeFlags.NonPrimitive && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NonPrimitive) || + includes & TypeFlags.StringLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.StringLike) || + includes & TypeFlags.NumberLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NumberLike) || + includes & TypeFlags.BigIntLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.BigIntLike) || + includes & TypeFlags.ESSymbolLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.ESSymbolLike) || + includes & TypeFlags.VoidLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.VoidLike)) { + return neverType; + } + if (includes & TypeFlags.TemplateLiteral && includes & TypeFlags.StringLiteral && extractRedundantTemplateLiterals(typeSet)) { + return neverType; + } + if (includes & TypeFlags.Any) { + return includes & TypeFlags.IncludesWildcard ? wildcardType : anyType; + } + if (!strictNullChecks && includes & TypeFlags.Nullable) { + return includes & TypeFlags.Undefined ? undefinedType : nullType; + } + if (includes & TypeFlags.String && includes & TypeFlags.StringLiteral || + includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral || + includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || + includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol) { + removeRedundantPrimitiveTypes(typeSet, includes); + } + if (includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.Object) { + orderedRemoveItemAt(typeSet, findIndex(typeSet, isEmptyAnonymousObjectType)); + } + if (includes & TypeFlags.IncludesMissingType) { + typeSet[typeSet.indexOf(undefinedType)] = missingType; + } + if (typeSet.length === 0) { + return unknownType; + } + if (typeSet.length === 1) { + return typeSet[0]; + } + const id = getTypeListId(typeSet) + getAliasId(aliasSymbol, aliasTypeArguments); + let result = intersectionTypes.get(id); + if (!result) { + if (includes & TypeFlags.Union) { + if (intersectUnionsOfPrimitiveTypes(typeSet)) { + // When the intersection creates a reduced set (which might mean that *all* union types have + // disappeared), we restart the operation to get a new set of combined flags. Once we have + // reduced we'll never reduce again, so this occurs at most once. + result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); + } + else if (eachIsUnionContaining(typeSet, TypeFlags.Undefined)) { + const undefinedOrMissingType = exactOptionalPropertyTypes && some(typeSet, t => containsType((t as UnionType).types, missingType)) ? missingType : undefinedType; + removeFromEach(typeSet, TypeFlags.Undefined); + result = getUnionType([getIntersectionType(typeSet), undefinedOrMissingType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + else if (eachIsUnionContaining(typeSet, TypeFlags.Null)) { + removeFromEach(typeSet, TypeFlags.Null); + result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } else { - result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); + // We are attempting to construct a type of the form X & (A | B) & (C | D). Transform this into a type of + // the form X & A & C | X & A & D | X & B & C | X & B & D. If the estimated size of the resulting union type + // exceeds 100000 constituents, report an error. + if (!checkCrossProductUnion(typeSet)) { + return errorType; + } + const constituents = getCrossProductIntersections(typeSet); + // We attach a denormalized origin type when at least one constituent of the cross-product union is an + // intersection (i.e. when the intersection didn't just reduce one or more unions to smaller unions). + const origin = some(constituents, t => !!(t.flags & TypeFlags.Intersection)) ? createOriginUnionOrIntersectionType(TypeFlags.Intersection, typeSet) : undefined; + result = getUnionType(constituents, UnionReduction.Literal, aliasSymbol, aliasTypeArguments, origin); } - intersectionTypes.set(id, result); } - return result; + else { + result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); + } + intersectionTypes.set(id, result); } + return result; + } - function getCrossProductUnionSize(types: readonly Type[]) { - return reduceLeft(types, (n, t) => t.flags & TypeFlags.Union ? n * (t as UnionType).types.length : t.flags & TypeFlags.Never ? 0 : n, 1); - } + function getCrossProductUnionSize(types: readonly Type[]) { + return reduceLeft(types, (n, t) => t.flags & TypeFlags.Union ? n * (t as UnionType).types.length : t.flags & TypeFlags.Never ? 0 : n, 1); + } - function checkCrossProductUnion(types: readonly Type[]) { - const size = getCrossProductUnionSize(types); - if (size >= 100000) { - tracing?.instant(tracing.Phase.CheckTypes, "checkCrossProductUnion_DepthLimit", { typeIds: types.map(t => t.id), size }); - error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); - return false; - } - return true; + function checkCrossProductUnion(types: readonly Type[]) { + const size = getCrossProductUnionSize(types); + if (size >= 100000) { + tracing?.instant(tracing.Phase.CheckTypes, "checkCrossProductUnion_DepthLimit", { typeIds: types.map(t => t.id), size }); + error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); + return false; } + return true; + } - function getCrossProductIntersections(types: readonly Type[]) { - const count = getCrossProductUnionSize(types); - const intersections: Type[] = []; - for (let i = 0; i < count; i++) { - const constituents = types.slice(); - let n = i; - for (let j = types.length - 1; j >= 0; j--) { - if (types[j].flags & TypeFlags.Union) { - const sourceTypes = (types[j] as UnionType).types; - const length = sourceTypes.length; - constituents[j] = sourceTypes[n % length]; - n = Math.floor(n / length); - } + function getCrossProductIntersections(types: readonly Type[]) { + const count = getCrossProductUnionSize(types); + const intersections: Type[] = []; + for (let i = 0; i < count; i++) { + const constituents = types.slice(); + let n = i; + for (let j = types.length - 1; j >= 0; j--) { + if (types[j].flags & TypeFlags.Union) { + const sourceTypes = (types[j] as UnionType).types; + const length = sourceTypes.length; + constituents[j] = sourceTypes[n % length]; + n = Math.floor(n / length); } - const t = getIntersectionType(constituents); - if (!(t.flags & TypeFlags.Never)) intersections.push(t); - } - return intersections; - } - - function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const aliasSymbol = getAliasSymbolForTypeNode(node); - links.resolvedType = getIntersectionType(map(node.types, getTypeFromTypeNode), - aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); } - return links.resolvedType; + const t = getIntersectionType(constituents); + if (!(t.flags & TypeFlags.Never)) intersections.push(t); } + return intersections; + } - function createIndexType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { - const result = createType(TypeFlags.Index) as IndexType; - result.type = type; - result.stringsOnly = stringsOnly; - return result; + function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const aliasSymbol = getAliasSymbolForTypeNode(node); + links.resolvedType = getIntersectionType(map(node.types, getTypeFromTypeNode), + aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); } + return links.resolvedType; + } - function createOriginIndexType(type: InstantiableType | UnionOrIntersectionType) { - const result = createOriginType(TypeFlags.Index) as IndexType; - result.type = type; - return result; - } + function createIndexType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { + const result = createType(TypeFlags.Index) as IndexType; + result.type = type; + result.stringsOnly = stringsOnly; + return result; + } - function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { - return stringsOnly ? - type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, /*stringsOnly*/ true)) : - type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false)); - } + function createOriginIndexType(type: InstantiableType | UnionOrIntersectionType) { + const result = createOriginType(TypeFlags.Index) as IndexType; + result.type = type; + return result; + } - /** - * This roughly mirrors `resolveMappedTypeMembers` in the nongeneric case, except only reports a union of the keys calculated, - * rather than manufacturing the properties. We can't just fetch the `constraintType` since that would ignore mappings - * and mapping the `constraintType` directly ignores how mapped types map _properties_ and not keys (thus ignoring subtype - * reduction in the constraintType) when possible. - * @param noIndexSignatures Indicates if _string_ index signatures should be elided. (other index signatures are always reported) - */ - function getIndexTypeForMappedType(type: MappedType, stringsOnly: boolean, noIndexSignatures: boolean | undefined) { - const typeParameter = getTypeParameterFromMappedType(type); - const constraintType = getConstraintTypeFromMappedType(type); - const nameType = getNameTypeFromMappedType(type.target as MappedType || type); - if (!nameType && !noIndexSignatures) { - // no mapping and no filtering required, just quickly bail to returning the constraint in the common case - return constraintType; - } - const keyTypes: Type[] = []; - if (isMappedTypeWithKeyofConstraintDeclaration(type)) { - // We have a { [P in keyof T]: X } + function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { + return stringsOnly ? + type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, /*stringsOnly*/ true)) : + type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false)); + } - // `getApparentType` on the T in a generic mapped type can trigger a circularity - // (conditionals and `infer` types create a circular dependency in the constraint resolution) - // so we only eagerly manifest the keys if the constraint is nongeneric - if (!isGenericIndexType(constraintType)) { - const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' - forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, stringsOnly, addMemberForKeyType); - } - else { - // we have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer the whole `keyof whatever` for later - // since it's not safe to resolve the shape of modifier type - return getIndexTypeForGenericType(type, stringsOnly); - } + /** + * This roughly mirrors `resolveMappedTypeMembers` in the nongeneric case, except only reports a union of the keys calculated, + * rather than manufacturing the properties. We can't just fetch the `constraintType` since that would ignore mappings + * and mapping the `constraintType` directly ignores how mapped types map _properties_ and not keys (thus ignoring subtype + * reduction in the constraintType) when possible. + * @param noIndexSignatures Indicates if _string_ index signatures should be elided. (other index signatures are always reported) + */ + function getIndexTypeForMappedType(type: MappedType, stringsOnly: boolean, noIndexSignatures: boolean | undefined) { + const typeParameter = getTypeParameterFromMappedType(type); + const constraintType = getConstraintTypeFromMappedType(type); + const nameType = getNameTypeFromMappedType(type.target as MappedType || type); + if (!nameType && !noIndexSignatures) { + // no mapping and no filtering required, just quickly bail to returning the constraint in the common case + return constraintType; + } + const keyTypes: Type[] = []; + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // We have a { [P in keyof T]: X } + + // `getApparentType` on the T in a generic mapped type can trigger a circularity + // (conditionals and `infer` types create a circular dependency in the constraint resolution) + // so we only eagerly manifest the keys if the constraint is nongeneric + if (!isGenericIndexType(constraintType)) { + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, stringsOnly, addMemberForKeyType); } else { - forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); - } - if (isGenericIndexType(constraintType)) { // include the generic component in the resulting type - forEachType(constraintType, addMemberForKeyType); - } - // we had to pick apart the constraintType to potentially map/filter it - compare the final resulting list with the original constraintType, - // so we can return the union that preserves aliases/origin data if possible - const result = noIndexSignatures ? filterType(getUnionType(keyTypes), t => !(t.flags & (TypeFlags.Any | TypeFlags.String))) : getUnionType(keyTypes); - if (result.flags & TypeFlags.Union && constraintType.flags & TypeFlags.Union && getTypeListId((result as UnionType).types) === getTypeListId((constraintType as UnionType).types)){ - return constraintType; - } - return result; - - function addMemberForKeyType(keyType: Type) { - const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType; - // `keyof` currently always returns `string | number` for concrete `string` index signatures - the below ternary keeps that behavior for mapped types - // See `getLiteralTypeFromProperties` where there's a similar ternary to cause the same behavior. - keyTypes.push(propNameType === stringType ? stringOrNumberType : propNameType); + // we have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer the whole `keyof whatever` for later + // since it's not safe to resolve the shape of modifier type + return getIndexTypeForGenericType(type, stringsOnly); } } - - // Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N

]: X }, to simply N. This however presumes - // that N distributes over union types, i.e. that N is equivalent to N | N | N. Specifically, we only - // want to perform the reduction when the name type of a mapped type is distributive with respect to the type variable - // introduced by the 'in' clause of the mapped type. Note that non-generic types are considered to be distributive because - // they're the same type regardless of what's being distributed over. - function hasDistributiveNameType(mappedType: MappedType) { - const typeVariable = getTypeParameterFromMappedType(mappedType); - return isDistributive(getNameTypeFromMappedType(mappedType) || typeVariable); - function isDistributive(type: Type): boolean { - return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Primitive | TypeFlags.Never | TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.NonPrimitive) ? true : - type.flags & TypeFlags.Conditional ? (type as ConditionalType).root.isDistributive && (type as ConditionalType).checkType === typeVariable : - type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) ? every((type as UnionOrIntersectionType | TemplateLiteralType).types, isDistributive) : - type.flags & TypeFlags.IndexedAccess ? isDistributive((type as IndexedAccessType).objectType) && isDistributive((type as IndexedAccessType).indexType) : - type.flags & TypeFlags.Substitution ? isDistributive((type as SubstitutionType).substitute) : - type.flags & TypeFlags.StringMapping ? isDistributive((type as StringMappingType).type) : - false; - } + else { + forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); } - - function getLiteralTypeFromPropertyName(name: PropertyName) { - if (isPrivateIdentifier(name)) { - return neverType; - } - return isIdentifier(name) ? getStringLiteralType(unescapeLeadingUnderscores(name.escapedText)) : - getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name)); + if (isGenericIndexType(constraintType)) { // include the generic component in the resulting type + forEachType(constraintType, addMemberForKeyType); } - - function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags, includeNonPublic?: boolean) { - if (includeNonPublic || !(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) { - let type = getSymbolLinks(getLateBoundSymbol(prop)).nameType; - if (!type) { - const name = getNameOfDeclaration(prop.valueDeclaration) as PropertyName; - type = prop.escapedName === InternalSymbolName.Default ? getStringLiteralType("default") : - name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getStringLiteralType(symbolName(prop)) : undefined); - } - if (type && type.flags & include) { - return type; - } - } - return neverType; + // we had to pick apart the constraintType to potentially map/filter it - compare the final resulting list with the original constraintType, + // so we can return the union that preserves aliases/origin data if possible + const result = noIndexSignatures ? filterType(getUnionType(keyTypes), t => !(t.flags & (TypeFlags.Any | TypeFlags.String))) : getUnionType(keyTypes); + if (result.flags & TypeFlags.Union && constraintType.flags & TypeFlags.Union && getTypeListId((result as UnionType).types) === getTypeListId((constraintType as UnionType).types)){ + return constraintType; } + return result; - function isKeyTypeIncluded(keyType: Type, include: TypeFlags): boolean { - return !!(keyType.flags & include || keyType.flags & TypeFlags.Intersection && some((keyType as IntersectionType).types, t => isKeyTypeIncluded(t, include))); + function addMemberForKeyType(keyType: Type) { + const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType; + // `keyof` currently always returns `string | number` for concrete `string` index signatures - the below ternary keeps that behavior for mapped types + // See `getLiteralTypeFromProperties` where there's a similar ternary to cause the same behavior. + keyTypes.push(propNameType === stringType ? stringOrNumberType : propNameType); } + } - function getLiteralTypeFromProperties(type: Type, include: TypeFlags, includeOrigin: boolean) { - const origin = includeOrigin && (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference) || type.aliasSymbol) ? createOriginIndexType(type) : undefined; - const propertyTypes = map(getPropertiesOfType(type), prop => getLiteralTypeFromProperty(prop, include)); - const indexKeyTypes = map(getIndexInfosOfType(type), info => info !== enumNumberIndexInfo && isKeyTypeIncluded(info.keyType, include) ? - info.keyType === stringType && include & TypeFlags.Number ? stringOrNumberType : info.keyType : neverType); - return getUnionType(concatenate(propertyTypes, indexKeyTypes), UnionReduction.Literal, - /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin); + // Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N

]: X }, to simply N. This however presumes + // that N distributes over union types, i.e. that N is equivalent to N | N | N. Specifically, we only + // want to perform the reduction when the name type of a mapped type is distributive with respect to the type variable + // introduced by the 'in' clause of the mapped type. Note that non-generic types are considered to be distributive because + // they're the same type regardless of what's being distributed over. + function hasDistributiveNameType(mappedType: MappedType) { + const typeVariable = getTypeParameterFromMappedType(mappedType); + return isDistributive(getNameTypeFromMappedType(mappedType) || typeVariable); + function isDistributive(type: Type): boolean { + return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Primitive | TypeFlags.Never | TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.NonPrimitive) ? true : + type.flags & TypeFlags.Conditional ? (type as ConditionalType).root.isDistributive && (type as ConditionalType).checkType === typeVariable : + type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) ? every((type as UnionOrIntersectionType | TemplateLiteralType).types, isDistributive) : + type.flags & TypeFlags.IndexedAccess ? isDistributive((type as IndexedAccessType).objectType) && isDistributive((type as IndexedAccessType).indexType) : + type.flags & TypeFlags.Substitution ? isDistributive((type as SubstitutionType).substitute) : + type.flags & TypeFlags.StringMapping ? isDistributive((type as StringMappingType).type) : + false; } + } - function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type { - type = getReducedType(type); - return type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : - type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : - type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && !hasDistributiveNameType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) : - getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, stringsOnly, noIndexSignatures) : - type === wildcardType ? wildcardType : - type.flags & TypeFlags.Unknown ? neverType : - type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType : - getLiteralTypeFromProperties(type, (noIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (stringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), - stringsOnly === keyofStringsOnly && !noIndexSignatures); + function getLiteralTypeFromPropertyName(name: PropertyName) { + if (isPrivateIdentifier(name)) { + return neverType; } + return isIdentifier(name) ? getStringLiteralType(unescapeLeadingUnderscores(name.escapedText)) : + getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name)); + } - function getExtractStringType(type: Type) { - if (keyofStringsOnly) { + function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags, includeNonPublic?: boolean) { + if (includeNonPublic || !(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) { + let type = getSymbolLinks(getLateBoundSymbol(prop)).nameType; + if (!type) { + const name = getNameOfDeclaration(prop.valueDeclaration) as PropertyName; + type = prop.escapedName === InternalSymbolName.Default ? getStringLiteralType("default") : + name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getStringLiteralType(symbolName(prop)) : undefined); + } + if (type && type.flags & include) { return type; } - const extractTypeAlias = getGlobalExtractSymbol(); - return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType; } + return neverType; + } + + function isKeyTypeIncluded(keyType: Type, include: TypeFlags): boolean { + return !!(keyType.flags & include || keyType.flags & TypeFlags.Intersection && some((keyType as IntersectionType).types, t => isKeyTypeIncluded(t, include))); + } + + function getLiteralTypeFromProperties(type: Type, include: TypeFlags, includeOrigin: boolean) { + const origin = includeOrigin && (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference) || type.aliasSymbol) ? createOriginIndexType(type) : undefined; + const propertyTypes = map(getPropertiesOfType(type), prop => getLiteralTypeFromProperty(prop, include)); + const indexKeyTypes = map(getIndexInfosOfType(type), info => info !== enumNumberIndexInfo && isKeyTypeIncluded(info.keyType, include) ? + info.keyType === stringType && include & TypeFlags.Number ? stringOrNumberType : info.keyType : neverType); + return getUnionType(concatenate(propertyTypes, indexKeyTypes), UnionReduction.Literal, + /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin); + } + + function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type { + type = getReducedType(type); + return type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : + type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : + type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && !hasDistributiveNameType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) : + getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, stringsOnly, noIndexSignatures) : + type === wildcardType ? wildcardType : + type.flags & TypeFlags.Unknown ? neverType : + type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType : + getLiteralTypeFromProperties(type, (noIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (stringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), + stringsOnly === keyofStringsOnly && !noIndexSignatures); + } - function getIndexTypeOrString(type: Type): Type { - const indexType = getExtractStringType(getIndexType(type)); - return indexType.flags & TypeFlags.Never ? stringType : indexType; + function getExtractStringType(type: Type) { + if (keyofStringsOnly) { + return type; } + const extractTypeAlias = getGlobalExtractSymbol(); + return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType; + } - function getTypeFromTypeOperatorNode(node: TypeOperatorNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - switch (node.operator) { - case SyntaxKind.KeyOfKeyword: - links.resolvedType = getIndexType(getTypeFromTypeNode(node.type)); - break; - case SyntaxKind.UniqueKeyword: - links.resolvedType = node.type.kind === SyntaxKind.SymbolKeyword - ? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent)) - : errorType; - break; - case SyntaxKind.ReadonlyKeyword: - links.resolvedType = getTypeFromTypeNode(node.type); - break; - default: - throw Debug.assertNever(node.operator); - } + function getIndexTypeOrString(type: Type): Type { + const indexType = getExtractStringType(getIndexType(type)); + return indexType.flags & TypeFlags.Never ? stringType : indexType; + } + + function getTypeFromTypeOperatorNode(node: TypeOperatorNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + switch (node.operator) { + case SyntaxKind.KeyOfKeyword: + links.resolvedType = getIndexType(getTypeFromTypeNode(node.type)); + break; + case SyntaxKind.UniqueKeyword: + links.resolvedType = node.type.kind === SyntaxKind.SymbolKeyword + ? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent)) + : errorType; + break; + case SyntaxKind.ReadonlyKeyword: + links.resolvedType = getTypeFromTypeNode(node.type); + break; + default: + throw Debug.assertNever(node.operator); } - return links.resolvedType; } + return links.resolvedType; + } - function getTypeFromTemplateTypeNode(node: TemplateLiteralTypeNode) { - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = getTemplateLiteralType( - [node.head.text, ...map(node.templateSpans, span => span.literal.text)], - map(node.templateSpans, span => getTypeFromTypeNode(span.type))); - } - return links.resolvedType; + function getTypeFromTemplateTypeNode(node: TemplateLiteralTypeNode) { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getTemplateLiteralType( + [node.head.text, ...map(node.templateSpans, span => span.literal.text)], + map(node.templateSpans, span => getTypeFromTypeNode(span.type))); } + return links.resolvedType; + } - function getTemplateLiteralType(texts: readonly string[], types: readonly Type[]): Type { - const unionIndex = findIndex(types, t => !!(t.flags & (TypeFlags.Never | TypeFlags.Union))); - if (unionIndex >= 0) { - return checkCrossProductUnion(types) ? - mapType(types[unionIndex], t => getTemplateLiteralType(texts, replaceElement(types, unionIndex, t))) : - errorType; - } - if (contains(types, wildcardType)) { - return wildcardType; - } - const newTypes: Type[] = []; - const newTexts: string[] = []; - let text = texts[0]; - if (!addSpans(texts, types)) { - return stringType; - } - if (newTypes.length === 0) { - return getStringLiteralType(text); - } - newTexts.push(text); - if (every(newTexts, t => t === "") && every(newTypes, t => !!(t.flags & TypeFlags.String))) { - return stringType; - } - const id = `${getTypeListId(newTypes)}|${map(newTexts, t => t.length).join(",")}|${newTexts.join("")}`; - let type = templateLiteralTypes.get(id); - if (!type) { - templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newTypes)); - } - return type; + function getTemplateLiteralType(texts: readonly string[], types: readonly Type[]): Type { + const unionIndex = findIndex(types, t => !!(t.flags & (TypeFlags.Never | TypeFlags.Union))); + if (unionIndex >= 0) { + return checkCrossProductUnion(types) ? + mapType(types[unionIndex], t => getTemplateLiteralType(texts, replaceElement(types, unionIndex, t))) : + errorType; + } + if (contains(types, wildcardType)) { + return wildcardType; + } + const newTypes: Type[] = []; + const newTexts: string[] = []; + let text = texts[0]; + if (!addSpans(texts, types)) { + return stringType; + } + if (newTypes.length === 0) { + return getStringLiteralType(text); + } + newTexts.push(text); + if (every(newTexts, t => t === "") && every(newTypes, t => !!(t.flags & TypeFlags.String))) { + return stringType; + } + const id = `${getTypeListId(newTypes)}|${map(newTexts, t => t.length).join(",")}|${newTexts.join("")}`; + let type = templateLiteralTypes.get(id); + if (!type) { + templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newTypes)); + } + return type; - function addSpans(texts: readonly string[], types: readonly Type[]): boolean { - for (let i = 0; i < types.length; i++) { - const t = types[i]; - if (t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) { - text += getTemplateStringForType(t) || ""; - text += texts[i + 1]; - } - else if (t.flags & TypeFlags.TemplateLiteral) { - text += (t as TemplateLiteralType).texts[0]; - if (!addSpans((t as TemplateLiteralType).texts, (t as TemplateLiteralType).types)) return false; - text += texts[i + 1]; - } - else if (isGenericIndexType(t) || isPatternLiteralPlaceholderType(t)) { - newTypes.push(t); - newTexts.push(text); - text = texts[i + 1]; - } - else { - return false; - } + function addSpans(texts: readonly string[], types: readonly Type[]): boolean { + for (let i = 0; i < types.length; i++) { + const t = types[i]; + if (t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) { + text += getTemplateStringForType(t) || ""; + text += texts[i + 1]; + } + else if (t.flags & TypeFlags.TemplateLiteral) { + text += (t as TemplateLiteralType).texts[0]; + if (!addSpans((t as TemplateLiteralType).texts, (t as TemplateLiteralType).types)) return false; + text += texts[i + 1]; + } + else if (isGenericIndexType(t) || isPatternLiteralPlaceholderType(t)) { + newTypes.push(t); + newTexts.push(text); + text = texts[i + 1]; + } + else { + return false; } - return true; } + return true; } + } - function getTemplateStringForType(type: Type) { - return type.flags & TypeFlags.StringLiteral ? (type as StringLiteralType).value : - type.flags & TypeFlags.NumberLiteral ? "" + (type as NumberLiteralType).value : - type.flags & TypeFlags.BigIntLiteral ? pseudoBigIntToString((type as BigIntLiteralType).value) : - type.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) ? (type as IntrinsicType).intrinsicName : - undefined; - } + function getTemplateStringForType(type: Type) { + return type.flags & TypeFlags.StringLiteral ? (type as StringLiteralType).value : + type.flags & TypeFlags.NumberLiteral ? "" + (type as NumberLiteralType).value : + type.flags & TypeFlags.BigIntLiteral ? pseudoBigIntToString((type as BigIntLiteralType).value) : + type.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) ? (type as IntrinsicType).intrinsicName : + undefined; + } - function createTemplateLiteralType(texts: readonly string[], types: readonly Type[]) { - const type = createType(TypeFlags.TemplateLiteral) as TemplateLiteralType; - type.texts = texts; - type.types = types; - return type; - } + function createTemplateLiteralType(texts: readonly string[], types: readonly Type[]) { + const type = createType(TypeFlags.TemplateLiteral) as TemplateLiteralType; + type.texts = texts; + type.types = types; + return type; + } - function getStringMappingType(symbol: Symbol, type: Type): Type { - return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) : - isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) : - type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) : - type; - } + function getStringMappingType(symbol: Symbol, type: Type): Type { + return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) : + isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) : + type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) : + type; + } - function applyStringMapping(symbol: Symbol, str: string) { - switch (intrinsicTypeKinds.get(symbol.escapedName as string)) { - case IntrinsicTypeKind.Uppercase: return str.toUpperCase(); - case IntrinsicTypeKind.Lowercase: return str.toLowerCase(); - case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1); - case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1); - } - return str; + function applyStringMapping(symbol: Symbol, str: string) { + switch (intrinsicTypeKinds.get(symbol.escapedName as string)) { + case IntrinsicTypeKind.Uppercase: return str.toUpperCase(); + case IntrinsicTypeKind.Lowercase: return str.toLowerCase(); + case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1); + case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1); } + return str; + } - function getStringMappingTypeForGenericType(symbol: Symbol, type: Type): Type { - const id = `${getSymbolId(symbol)},${getTypeId(type)}`; - let result = stringMappingTypes.get(id); - if (!result) { - stringMappingTypes.set(id, result = createStringMappingType(symbol, type)); - } - return result; + function getStringMappingTypeForGenericType(symbol: Symbol, type: Type): Type { + const id = `${getSymbolId(symbol)},${getTypeId(type)}`; + let result = stringMappingTypes.get(id); + if (!result) { + stringMappingTypes.set(id, result = createStringMappingType(symbol, type)); } + return result; + } - function createStringMappingType(symbol: Symbol, type: Type) { - const result = createType(TypeFlags.StringMapping) as StringMappingType; - result.symbol = symbol; - result.type = type; - return result; - } + function createStringMappingType(symbol: Symbol, type: Type) { + const result = createType(TypeFlags.StringMapping) as StringMappingType; + result.symbol = symbol; + result.type = type; + return result; + } - function createIndexedAccessType(objectType: Type, indexType: Type, accessFlags: AccessFlags, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { - const type = createType(TypeFlags.IndexedAccess) as IndexedAccessType; - type.objectType = objectType; - type.indexType = indexType; - type.accessFlags = accessFlags; - type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = aliasTypeArguments; - return type; - } + function createIndexedAccessType(objectType: Type, indexType: Type, accessFlags: AccessFlags, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { + const type = createType(TypeFlags.IndexedAccess) as IndexedAccessType; + type.objectType = objectType; + type.indexType = indexType; + type.accessFlags = accessFlags; + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = aliasTypeArguments; + return type; + } - /** - * Returns if a type is or consists of a JSLiteral object type - * In addition to objects which are directly literals, - * * unions where every element is a jsliteral - * * intersections where at least one element is a jsliteral - * * and instantiable types constrained to a jsliteral - * Should all count as literals and not print errors on access or assignment of possibly existing properties. - * This mirrors the behavior of the index signature propagation, to which this behaves similarly (but doesn't affect assignability or inference). - */ - function isJSLiteralType(type: Type): boolean { - if (noImplicitAny) { - return false; // Flag is meaningless under `noImplicitAny` mode - } - if (getObjectFlags(type) & ObjectFlags.JSLiteral) { - return true; - } - if (type.flags & TypeFlags.Union) { - return every((type as UnionType).types, isJSLiteralType); - } - if (type.flags & TypeFlags.Intersection) { - return some((type as IntersectionType).types, isJSLiteralType); - } - if (type.flags & TypeFlags.Instantiable) { - const constraint = getResolvedBaseConstraint(type); - return constraint !== type && isJSLiteralType(constraint); - } - return false; + /** + * Returns if a type is or consists of a JSLiteral object type + * In addition to objects which are directly literals, + * * unions where every element is a jsliteral + * * intersections where at least one element is a jsliteral + * * and instantiable types constrained to a jsliteral + * Should all count as literals and not print errors on access or assignment of possibly existing properties. + * This mirrors the behavior of the index signature propagation, to which this behaves similarly (but doesn't affect assignability or inference). + */ + function isJSLiteralType(type: Type): boolean { + if (noImplicitAny) { + return false; // Flag is meaningless under `noImplicitAny` mode + } + if (getObjectFlags(type) & ObjectFlags.JSLiteral) { + return true; } - - function getPropertyNameFromIndex(indexType: Type, accessNode: StringLiteral | Identifier | PrivateIdentifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) { - return isTypeUsableAsPropertyName(indexType) ? - getPropertyNameFromType(indexType) : - accessNode && isPropertyName(accessNode) ? - // late bound names are handled in the first branch, so here we only need to handle normal names - getPropertyNameForPropertyNameNode(accessNode) : - undefined; + if (type.flags & TypeFlags.Union) { + return every((type as UnionType).types, isJSLiteralType); + } + if (type.flags & TypeFlags.Intersection) { + return some((type as IntersectionType).types, isJSLiteralType); + } + if (type.flags & TypeFlags.Instantiable) { + const constraint = getResolvedBaseConstraint(type); + return constraint !== type && isJSLiteralType(constraint); } + return false; + } - function isUncalledFunctionReference(node: Node, symbol: Symbol) { - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { - const parent = findAncestor(node.parent, n => !isAccessExpression(n)) || node.parent; - if (isCallLikeExpression(parent)) { - return isCallOrNewExpression(parent) && isIdentifier(node) && hasMatchingArgument(parent, node); - } - return every(symbol.declarations, d => !isFunctionLike(d) || !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); + function getPropertyNameFromIndex(indexType: Type, accessNode: StringLiteral | Identifier | PrivateIdentifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) { + return isTypeUsableAsPropertyName(indexType) ? + getPropertyNameFromType(indexType) : + accessNode && isPropertyName(accessNode) ? + // late bound names are handled in the first branch, so here we only need to handle normal names + getPropertyNameForPropertyNameNode(accessNode) : + undefined; + } + + function isUncalledFunctionReference(node: Node, symbol: Symbol) { + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { + const parent = findAncestor(node.parent, n => !isAccessExpression(n)) || node.parent; + if (isCallLikeExpression(parent)) { + return isCallOrNewExpression(parent) && isIdentifier(node) && hasMatchingArgument(parent, node); } - return true; + return every(symbol.declarations, d => !isFunctionLike(d) || !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); } + return true; + } - function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) { - const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; - const propName = accessNode && isPrivateIdentifier(accessNode) ? undefined : getPropertyNameFromIndex(indexType, accessNode); + function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) { + const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; + const propName = accessNode && isPrivateIdentifier(accessNode) ? undefined : getPropertyNameFromIndex(indexType, accessNode); - if (propName !== undefined) { - if (accessFlags & AccessFlags.Contextual) { - return getTypeOfPropertyOfContextualType(objectType, propName) || anyType; + if (propName !== undefined) { + if (accessFlags & AccessFlags.Contextual) { + return getTypeOfPropertyOfContextualType(objectType, propName) || anyType; + } + const prop = getPropertyOfType(objectType, propName); + if (prop) { + if (accessFlags & AccessFlags.ReportDeprecated && accessNode && prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(accessNode, prop)) { + const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode); + addDeprecatedSuggestion(deprecatedNode, prop.declarations, propName as string); } - const prop = getPropertyOfType(objectType, propName); - if (prop) { - if (accessFlags & AccessFlags.ReportDeprecated && accessNode && prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(accessNode, prop)) { - const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode); - addDeprecatedSuggestion(deprecatedNode, prop.declarations, propName as string); + if (accessExpression) { + markPropertyAsReferenced(prop, accessExpression, isSelfTypeAccess(accessExpression.expression, objectType.symbol)); + if (isAssignmentToReadonlyEntity(accessExpression, prop, getAssignmentTargetKind(accessExpression))) { + error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop)); + return undefined; } - if (accessExpression) { - markPropertyAsReferenced(prop, accessExpression, isSelfTypeAccess(accessExpression.expression, objectType.symbol)); - if (isAssignmentToReadonlyEntity(accessExpression, prop, getAssignmentTargetKind(accessExpression))) { - error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop)); - return undefined; - } - if (accessFlags & AccessFlags.CacheSymbol) { - getNodeLinks(accessNode!).resolvedSymbol = prop; - } - if (isThisPropertyAccessInConstructor(accessExpression, prop)) { - return autoType; - } + if (accessFlags & AccessFlags.CacheSymbol) { + getNodeLinks(accessNode!).resolvedSymbol = prop; } - const propType = getTypeOfSymbol(prop); - return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? - getFlowTypeOfReference(accessExpression, propType) : - propType; - } - if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) { - if (accessNode && everyType(objectType, t => !(t as TupleTypeReference).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) { - const indexNode = getIndexNodeForAccessExpression(accessNode); - if (isTupleType(objectType)) { - error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2, - typeToString(objectType), getTypeReferenceArity(objectType), unescapeLeadingUnderscores(propName)); - } - else { - error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); - } + if (isThisPropertyAccessInConstructor(accessExpression, prop)) { + return autoType; } - errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType)); - return mapType(objectType, t => { - const restType = getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType; - return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([restType, undefinedType]) : restType; - }); } + const propType = getTypeOfSymbol(prop); + return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? + getFlowTypeOfReference(accessExpression, propType) : + propType; } - if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) { - if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) { - return objectType; - } - // If no index signature is applicable, we default to the string index signature. In effect, this means the string - // index signature applies even when accessing with a symbol-like type. - const indexInfo = getApplicableIndexInfo(objectType, indexType) || getIndexInfoOfType(objectType, stringType); - if (indexInfo) { - if (accessFlags & AccessFlags.NoIndexSignatures && indexInfo.keyType !== numberType) { - if (accessExpression) { - error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType)); - } - return undefined; + if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) { + if (accessNode && everyType(objectType, t => !(t as TupleTypeReference).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) { + const indexNode = getIndexNodeForAccessExpression(accessNode); + if (isTupleType(objectType)) { + error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2, + typeToString(objectType), getTypeReferenceArity(objectType), unescapeLeadingUnderscores(propName)); } - if (accessNode && indexInfo.keyType === stringType && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { - const indexNode = getIndexNodeForAccessExpression(accessNode); - error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); - return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; + else { + error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); } - errorIfWritingToReadonlyIndex(indexInfo); - return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } - if (indexType.flags & TypeFlags.Never) { - return neverType; + errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType)); + return mapType(objectType, t => { + const restType = getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType; + return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([restType, undefinedType]) : restType; + }); + } + } + if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) { + if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) { + return objectType; + } + // If no index signature is applicable, we default to the string index signature. In effect, this means the string + // index signature applies even when accessing with a symbol-like type. + const indexInfo = getApplicableIndexInfo(objectType, indexType) || getIndexInfoOfType(objectType, stringType); + if (indexInfo) { + if (accessFlags & AccessFlags.NoIndexSignatures && indexInfo.keyType !== numberType) { + if (accessExpression) { + error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType)); + } + return undefined; } - if (isJSLiteralType(objectType)) { - return anyType; + if (accessNode && indexInfo.keyType === stringType && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { + const indexNode = getIndexNodeForAccessExpression(accessNode); + error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); + return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } - if (accessExpression && !isConstEnumObjectType(objectType)) { - if (isObjectLiteralType(objectType)) { - if (noImplicitAny && indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { - diagnostics.add(createDiagnosticForNode(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType))); - return undefinedType; - } - else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) { - const types = map((objectType as ResolvedType).properties, property => { - return getTypeOfSymbol(property); - }); - return getUnionType(append(types, undefinedType)); - } + errorIfWritingToReadonlyIndex(indexInfo); + return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; + } + if (indexType.flags & TypeFlags.Never) { + return neverType; + } + if (isJSLiteralType(objectType)) { + return anyType; + } + if (accessExpression && !isConstEnumObjectType(objectType)) { + if (isObjectLiteralType(objectType)) { + if (noImplicitAny && indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { + diagnostics.add(createDiagnosticForNode(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType))); + return undefinedType; + } + else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) { + const types = map((objectType as ResolvedType).properties, property => { + return getTypeOfSymbol(property); + }); + return getUnionType(append(types, undefinedType)); } + } - if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports!.has(propName) && (globalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) { - error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); + if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports!.has(propName) && (globalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) { + error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); + } + else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !(accessFlags & AccessFlags.SuppressNoImplicitAnyError)) { + if (propName !== undefined && typeHasStaticProperty(propName, objectType)) { + const typeName = typeToString(objectType); + error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName as string, typeName, typeName + "[" + getTextOfNode(accessExpression.argumentExpression) + "]"); } - else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !(accessFlags & AccessFlags.SuppressNoImplicitAnyError)) { - if (propName !== undefined && typeHasStaticProperty(propName, objectType)) { - const typeName = typeToString(objectType); - error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName as string, typeName, typeName + "[" + getTextOfNode(accessExpression.argumentExpression) + "]"); - } - else if (getIndexTypeOfType(objectType, numberType)) { - error(accessExpression.argumentExpression, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number); + else if (getIndexTypeOfType(objectType, numberType)) { + error(accessExpression.argumentExpression, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number); + } + else { + let suggestion: string | undefined; + if (propName !== undefined && (suggestion = getSuggestionForNonexistentProperty(propName as string, objectType))) { + if (suggestion !== undefined) { + error(accessExpression.argumentExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName as string, typeToString(objectType), suggestion); + } } else { - let suggestion: string | undefined; - if (propName !== undefined && (suggestion = getSuggestionForNonexistentProperty(propName as string, objectType))) { - if (suggestion !== undefined) { - error(accessExpression.argumentExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName as string, typeToString(objectType), suggestion); - } + const suggestion = getSuggestionForNonexistentIndexSignature(objectType, accessExpression, indexType); + if (suggestion !== undefined) { + error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature_Did_you_mean_to_call_1, typeToString(objectType), suggestion); } else { - const suggestion = getSuggestionForNonexistentIndexSignature(objectType, accessExpression, indexType); - if (suggestion !== undefined) { - error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature_Did_you_mean_to_call_1, typeToString(objectType), suggestion); + let errorInfo: DiagnosticMessageChain | undefined; + if (indexType.flags & TypeFlags.EnumLiteral) { + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + typeToString(indexType) + "]", typeToString(objectType)); } - else { - let errorInfo: DiagnosticMessageChain | undefined; - if (indexType.flags & TypeFlags.EnumLiteral) { - errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + typeToString(indexType) + "]", typeToString(objectType)); - } - else if (indexType.flags & TypeFlags.UniqueESSymbol) { - const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression); - errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType)); - } - else if (indexType.flags & TypeFlags.StringLiteral) { - errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType)); - } - else if (indexType.flags & TypeFlags.NumberLiteral) { - errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as NumberLiteralType).value, typeToString(objectType)); - } - else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) { - errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.No_index_signature_with_a_parameter_of_type_0_was_found_on_type_1, typeToString(indexType), typeToString(objectType)); - } - - errorInfo = chainDiagnosticMessages( - errorInfo, - Diagnostics.Element_implicitly_has_an_any_type_because_expression_of_type_0_can_t_be_used_to_index_type_1, typeToString(fullIndexType), typeToString(objectType) - ); - diagnostics.add(createDiagnosticForNodeFromMessageChain(accessExpression, errorInfo)); + else if (indexType.flags & TypeFlags.UniqueESSymbol) { + const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression); + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType)); } + else if (indexType.flags & TypeFlags.StringLiteral) { + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType)); + } + else if (indexType.flags & TypeFlags.NumberLiteral) { + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as NumberLiteralType).value, typeToString(objectType)); + } + else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) { + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.No_index_signature_with_a_parameter_of_type_0_was_found_on_type_1, typeToString(indexType), typeToString(objectType)); + } + + errorInfo = chainDiagnosticMessages( + errorInfo, + Diagnostics.Element_implicitly_has_an_any_type_because_expression_of_type_0_can_t_be_used_to_index_type_1, typeToString(fullIndexType), typeToString(objectType) + ); + diagnostics.add(createDiagnosticForNodeFromMessageChain(accessExpression, errorInfo)); } } } - return undefined; } + return undefined; } - if (isJSLiteralType(objectType)) { - return anyType; + } + if (isJSLiteralType(objectType)) { + return anyType; + } + if (accessNode) { + const indexNode = getIndexNodeForAccessExpression(accessNode); + if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { + error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (indexType as StringLiteralType | NumberLiteralType).value, typeToString(objectType)); } - if (accessNode) { - const indexNode = getIndexNodeForAccessExpression(accessNode); - if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { - error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (indexType as StringLiteralType | NumberLiteralType).value, typeToString(objectType)); - } - else if (indexType.flags & (TypeFlags.String | TypeFlags.Number)) { - error(indexNode, Diagnostics.Type_0_has_no_matching_index_signature_for_type_1, typeToString(objectType), typeToString(indexType)); - } - else { - error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); - } + else if (indexType.flags & (TypeFlags.String | TypeFlags.Number)) { + error(indexNode, Diagnostics.Type_0_has_no_matching_index_signature_for_type_1, typeToString(objectType), typeToString(indexType)); } - if (isTypeAny(indexType)) { - return indexType; + else { + error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); } - return undefined; + } + if (isTypeAny(indexType)) { + return indexType; + } + return undefined; - function errorIfWritingToReadonlyIndex(indexInfo: IndexInfo | undefined): void { - if (indexInfo && indexInfo.isReadonly && accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) { - error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); - } + function errorIfWritingToReadonlyIndex(indexInfo: IndexInfo | undefined): void { + if (indexInfo && indexInfo.isReadonly && accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) { + error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); } } + } - function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) { - return accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : - accessNode.kind === SyntaxKind.IndexedAccessType ? accessNode.indexType : - accessNode.kind === SyntaxKind.ComputedPropertyName ? accessNode.expression : - accessNode; - } + function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) { + return accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : + accessNode.kind === SyntaxKind.IndexedAccessType ? accessNode.indexType : + accessNode.kind === SyntaxKind.ComputedPropertyName ? accessNode.expression : + accessNode; + } - function isPatternLiteralPlaceholderType(type: Type) { - return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)); - } + function isPatternLiteralPlaceholderType(type: Type) { + return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)); + } - function isPatternLiteralType(type: Type) { - return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType); - } + function isPatternLiteralType(type: Type) { + return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType); + } - function isGenericType(type: Type): boolean { - return !!getGenericObjectFlags(type); - } + function isGenericType(type: Type): boolean { + return !!getGenericObjectFlags(type); + } - function isGenericObjectType(type: Type): boolean { - return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericObjectType); - } + function isGenericObjectType(type: Type): boolean { + return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericObjectType); + } - function isGenericIndexType(type: Type): boolean { - return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericIndexType); - } + function isGenericIndexType(type: Type): boolean { + return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericIndexType); + } - function getGenericObjectFlags(type: Type): ObjectFlags { - if (type.flags & TypeFlags.UnionOrIntersection) { - if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) { - (type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericTypeComputed | - reduceLeft((type as UnionOrIntersectionType).types, (flags, t) => flags | getGenericObjectFlags(t), 0); - } - return (type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericType; + function getGenericObjectFlags(type: Type): ObjectFlags { + if (type.flags & TypeFlags.UnionOrIntersection) { + if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) { + (type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericTypeComputed | + reduceLeft((type as UnionOrIntersectionType).types, (flags, t) => flags | getGenericObjectFlags(t), 0); } - if (type.flags & TypeFlags.Substitution) { - if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) { - (type as SubstitutionType).objectFlags |= ObjectFlags.IsGenericTypeComputed | - getGenericObjectFlags((type as SubstitutionType).substitute) | getGenericObjectFlags((type as SubstitutionType).baseType); - } - return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType; + return (type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericType; + } + if (type.flags & TypeFlags.Substitution) { + if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) { + (type as SubstitutionType).objectFlags |= ObjectFlags.IsGenericTypeComputed | + getGenericObjectFlags((type as SubstitutionType).substitute) | getGenericObjectFlags((type as SubstitutionType).baseType); } - return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) | - (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0); + return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType; } + return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) | + (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0); + } - function isThisTypeParameter(type: Type): boolean { - return !!(type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType); + function isThisTypeParameter(type: Type): boolean { + return !!(type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType); + } + + function getSimplifiedType(type: Type, writing: boolean): Type { + return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type as IndexedAccessType, writing) : + type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type as ConditionalType, writing) : + type; + } + + function distributeIndexOverObjectType(objectType: Type, indexType: Type, writing: boolean) { + // (T | U)[K] -> T[K] | U[K] (reading) + // (T | U)[K] -> T[K] & U[K] (writing) + // (T & U)[K] -> T[K] & U[K] + if (objectType.flags & TypeFlags.UnionOrIntersection) { + const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType), writing)); + return objectType.flags & TypeFlags.Intersection || writing ? getIntersectionType(types) : getUnionType(types); } + } - function getSimplifiedType(type: Type, writing: boolean): Type { - return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type as IndexedAccessType, writing) : - type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type as ConditionalType, writing) : - type; + function distributeObjectOverIndexType(objectType: Type, indexType: Type, writing: boolean) { + // T[A | B] -> T[A] | T[B] (reading) + // T[A | B] -> T[A] & T[B] (writing) + if (indexType.flags & TypeFlags.Union) { + const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t), writing)); + return writing ? getIntersectionType(types) : getUnionType(types); } + } - function distributeIndexOverObjectType(objectType: Type, indexType: Type, writing: boolean) { + // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return + // the type itself if no transformation is possible. The writing flag indicates that the type is + // the target of an assignment. + function getSimplifiedIndexedAccessType(type: IndexedAccessType, writing: boolean): Type { + const cache = writing ? "simplifiedForWriting" : "simplifiedForReading"; + if (type[cache]) { + return type[cache] === circularConstraintType ? type : type[cache]!; + } + type[cache] = circularConstraintType; + // We recursively simplify the object type as it may in turn be an indexed access type. For example, with + // '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type. + const objectType = getSimplifiedType(type.objectType, writing); + const indexType = getSimplifiedType(type.indexType, writing); + // T[A | B] -> T[A] | T[B] (reading) + // T[A | B] -> T[A] & T[B] (writing) + const distributedOverIndex = distributeObjectOverIndexType(objectType, indexType, writing); + if (distributedOverIndex) { + return type[cache] = distributedOverIndex; + } + // Only do the inner distributions if the index can no longer be instantiated to cause index distribution again + if (!(indexType.flags & TypeFlags.Instantiable)) { // (T | U)[K] -> T[K] | U[K] (reading) // (T | U)[K] -> T[K] & U[K] (writing) // (T & U)[K] -> T[K] & U[K] - if (objectType.flags & TypeFlags.UnionOrIntersection) { - const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType), writing)); - return objectType.flags & TypeFlags.Intersection || writing ? getIntersectionType(types) : getUnionType(types); + const distributedOverObject = distributeIndexOverObjectType(objectType, indexType, writing); + if (distributedOverObject) { + return type[cache] = distributedOverObject; } } + // So ultimately (reading): + // ((A & B) | C)[K1 | K2] -> ((A & B) | C)[K1] | ((A & B) | C)[K2] -> (A & B)[K1] | C[K1] | (A & B)[K2] | C[K2] -> (A[K1] & B[K1]) | C[K1] | (A[K2] & B[K2]) | C[K2] - function distributeObjectOverIndexType(objectType: Type, indexType: Type, writing: boolean) { - // T[A | B] -> T[A] | T[B] (reading) - // T[A | B] -> T[A] & T[B] (writing) - if (indexType.flags & TypeFlags.Union) { - const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t), writing)); - return writing ? getIntersectionType(types) : getUnionType(types); + // A generic tuple type indexed by a number exists only when the index type doesn't select a + // fixed element. We simplify to either the combined type of all elements (when the index type + // the actual number type) or to the combined type of all non-fixed elements. + if (isGenericTupleType(objectType) && indexType.flags & TypeFlags.NumberLike) { + const elementType = getElementTypeOfSliceOfTupleType(objectType, indexType.flags & TypeFlags.Number ? 0 : objectType.target.fixedLength, /*endSkipCount*/ 0, writing); + if (elementType) { + return type[cache] = elementType; } } + // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper + // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we + // construct the type Box. + if (isGenericMappedType(objectType)) { + return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing)); + } + return type[cache] = type; + } - // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return - // the type itself if no transformation is possible. The writing flag indicates that the type is - // the target of an assignment. - function getSimplifiedIndexedAccessType(type: IndexedAccessType, writing: boolean): Type { - const cache = writing ? "simplifiedForWriting" : "simplifiedForReading"; - if (type[cache]) { - return type[cache] === circularConstraintType ? type : type[cache]!; + function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) { + const checkType = type.checkType; + const extendsType = type.extendsType; + const trueType = getTrueTypeFromConditionalType(type); + const falseType = getFalseTypeFromConditionalType(type); + // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`. + if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) { + if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true + return getSimplifiedType(trueType, writing); + } + else if (isIntersectionEmpty(checkType, extendsType)) { // Always false + return neverType; } - type[cache] = circularConstraintType; - // We recursively simplify the object type as it may in turn be an indexed access type. For example, with - // '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type. - const objectType = getSimplifiedType(type.objectType, writing); - const indexType = getSimplifiedType(type.indexType, writing); - // T[A | B] -> T[A] | T[B] (reading) - // T[A | B] -> T[A] & T[B] (writing) - const distributedOverIndex = distributeObjectOverIndexType(objectType, indexType, writing); - if (distributedOverIndex) { - return type[cache] = distributedOverIndex; + } + else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) { + if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true + return neverType; } - // Only do the inner distributions if the index can no longer be instantiated to cause index distribution again - if (!(indexType.flags & TypeFlags.Instantiable)) { - // (T | U)[K] -> T[K] | U[K] (reading) - // (T | U)[K] -> T[K] & U[K] (writing) - // (T & U)[K] -> T[K] & U[K] - const distributedOverObject = distributeIndexOverObjectType(objectType, indexType, writing); - if (distributedOverObject) { - return type[cache] = distributedOverObject; - } + else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false + return getSimplifiedType(falseType, writing); } - // So ultimately (reading): - // ((A & B) | C)[K1 | K2] -> ((A & B) | C)[K1] | ((A & B) | C)[K2] -> (A & B)[K1] | C[K1] | (A & B)[K2] | C[K2] -> (A[K1] & B[K1]) | C[K1] | (A[K2] & B[K2]) | C[K2] + } + return type; + } - // A generic tuple type indexed by a number exists only when the index type doesn't select a - // fixed element. We simplify to either the combined type of all elements (when the index type - // the actual number type) or to the combined type of all non-fixed elements. - if (isGenericTupleType(objectType) && indexType.flags & TypeFlags.NumberLike) { - const elementType = getElementTypeOfSliceOfTupleType(objectType, indexType.flags & TypeFlags.Number ? 0 : objectType.target.fixedLength, /*endSkipCount*/ 0, writing); - if (elementType) { - return type[cache] = elementType; + /** + * Invokes union simplification logic to determine if an intersection is considered empty as a union constituent + */ + function isIntersectionEmpty(type1: Type, type2: Type) { + return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never); + } + + function substituteIndexedMappedType(objectType: MappedType, index: Type) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); + const templateMapper = combineTypeMappers(objectType.mapper, mapper); + return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); + } + + function getIndexedAccessType(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + return getIndexedAccessTypeOrUndefined(objectType, indexType, accessFlags, accessNode, aliasSymbol, aliasTypeArguments) || (accessNode ? errorType : unknownType); + } + + function indexTypeLessThan(indexType: Type, limit: number) { + return everyType(indexType, t => { + if (t.flags & TypeFlags.StringOrNumberLiteral) { + const propName = getPropertyNameFromType(t as StringLiteralType | NumberLiteralType); + if (isNumericLiteralName(propName)) { + const index = +propName; + return index >= 0 && index < limit; } } - // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper - // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we - // construct the type Box. - if (isGenericMappedType(objectType)) { - return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing)); + return false; + }); + } + + function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined { + if (objectType === wildcardType || indexType === wildcardType) { + return wildcardType; + } + // If the object type has a string index signature and no other members we know that the result will + // always be the type of that index signature and we can simplify accordingly. + if (isStringIndexSignatureOnlyType(objectType) && !(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { + indexType = stringType; + } + // In noUncheckedIndexedAccess mode, indexed access operations that occur in an expression in a read position and resolve to + // an index signature have 'undefined' included in their type. + if (compilerOptions.noUncheckedIndexedAccess && accessFlags & AccessFlags.ExpressionPosition) accessFlags |= AccessFlags.IncludeUndefined; + // If the index type is generic, or if the object type is generic and doesn't originate in an expression and + // the operation isn't exclusively indexing the fixed (non-variadic) portion of a tuple type, we are performing + // a higher-order index access where we cannot meaningfully access the properties of the object type. Note that + // for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in an expression. This is to + // preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved + // eagerly using the constraint type of 'this' at the given location. + if (isGenericIndexType(indexType) || (accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType ? + isGenericTupleType(objectType) && !indexTypeLessThan(indexType, objectType.target.fixedLength) : + isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)))) { + if (objectType.flags & TypeFlags.AnyOrUnknown) { + return objectType; + } + // Defer the operation by creating an indexed access type. + const persistentAccessFlags = accessFlags & AccessFlags.Persistent; + const id = objectType.id + "," + indexType.id + "," + persistentAccessFlags + getAliasId(aliasSymbol, aliasTypeArguments); + let type = indexedAccessTypes.get(id); + if (!type) { + indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType, persistentAccessFlags, aliasSymbol, aliasTypeArguments)); } - return type[cache] = type; - } - function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) { - const checkType = type.checkType; - const extendsType = type.extendsType; - const trueType = getTrueTypeFromConditionalType(type); - const falseType = getFalseTypeFromConditionalType(type); - // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`. - if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) { - if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true - return getSimplifiedType(trueType, writing); + return type; + } + // In the following we resolve T[K] to the type of the property in T selected by K. + // We treat boolean as different from other unions to improve errors; + // skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'. + const apparentObjectType = getReducedApparentType(objectType); + if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Boolean)) { + const propTypes: Type[] = []; + let wasMissingProp = false; + for (const t of (indexType as UnionType).types) { + const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, accessNode, accessFlags | (wasMissingProp ? AccessFlags.SuppressNoImplicitAnyError : 0)); + if (propType) { + propTypes.push(propType); + } + else if (!accessNode) { + // If there's no error node, we can immeditely stop, since error reporting is off + return undefined; } - else if (isIntersectionEmpty(checkType, extendsType)) { // Always false - return neverType; + else { + // Otherwise we set a flag and return at the end of the loop so we still mark all errors + wasMissingProp = true; } } - else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) { - if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true - return neverType; - } - else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false - return getSimplifiedType(falseType, writing); - } + if (wasMissingProp) { + return undefined; } - return type; + return accessFlags & AccessFlags.Writing + ? getIntersectionType(propTypes, aliasSymbol, aliasTypeArguments) + : getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } + return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol | AccessFlags.ReportDeprecated); + } - /** - * Invokes union simplification logic to determine if an intersection is considered empty as a union constituent - */ - function isIntersectionEmpty(type1: Type, type2: Type) { - return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never); - } + function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const objectType = getTypeFromTypeNode(node.objectType); + const indexType = getTypeFromTypeNode(node.indexType); + const potentialAlias = getAliasSymbolForTypeNode(node); + const resolved = getIndexedAccessType(objectType, indexType, AccessFlags.None, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias)); + links.resolvedType = resolved.flags & TypeFlags.IndexedAccess && + (resolved as IndexedAccessType).objectType === objectType && + (resolved as IndexedAccessType).indexType === indexType ? + getConditionalFlowTypeOfType(resolved, node) : resolved; + } + return links.resolvedType; + } - function substituteIndexedMappedType(objectType: MappedType, index: Type) { - const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); - const templateMapper = combineTypeMappers(objectType.mapper, mapper); - return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); - } + function getTypeFromMappedTypeNode(node: MappedTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const type = createObjectType(ObjectFlags.Mapped, node.symbol) as MappedType; + type.declaration = node; + type.aliasSymbol = getAliasSymbolForTypeNode(node); + type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol); + links.resolvedType = type; + // Eagerly resolve the constraint type which forces an error if the constraint type circularly + // references itself through one or more type aliases. + getConstraintTypeFromMappedType(type); + } + return links.resolvedType; + } - function getIndexedAccessType(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - return getIndexedAccessTypeOrUndefined(objectType, indexType, accessFlags, accessNode, aliasSymbol, aliasTypeArguments) || (accessNode ? errorType : unknownType); + function getActualTypeVariable(type: Type): Type { + if (type.flags & TypeFlags.Substitution) { + return (type as SubstitutionType).baseType; } - - function indexTypeLessThan(indexType: Type, limit: number) { - return everyType(indexType, t => { - if (t.flags & TypeFlags.StringOrNumberLiteral) { - const propName = getPropertyNameFromType(t as StringLiteralType | NumberLiteralType); - if (isNumericLiteralName(propName)) { - const index = +propName; - return index >= 0 && index < limit; - } - } - return false; - }); + if (type.flags & TypeFlags.IndexedAccess && ( + (type as IndexedAccessType).objectType.flags & TypeFlags.Substitution || + (type as IndexedAccessType).indexType.flags & TypeFlags.Substitution)) { + return getIndexedAccessType(getActualTypeVariable((type as IndexedAccessType).objectType), getActualTypeVariable((type as IndexedAccessType).indexType)); } + return type; + } - function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined { - if (objectType === wildcardType || indexType === wildcardType) { - return wildcardType; - } - // If the object type has a string index signature and no other members we know that the result will - // always be the type of that index signature and we can simplify accordingly. - if (isStringIndexSignatureOnlyType(objectType) && !(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { - indexType = stringType; - } - // In noUncheckedIndexedAccess mode, indexed access operations that occur in an expression in a read position and resolve to - // an index signature have 'undefined' included in their type. - if (compilerOptions.noUncheckedIndexedAccess && accessFlags & AccessFlags.ExpressionPosition) accessFlags |= AccessFlags.IncludeUndefined; - // If the index type is generic, or if the object type is generic and doesn't originate in an expression and - // the operation isn't exclusively indexing the fixed (non-variadic) portion of a tuple type, we are performing - // a higher-order index access where we cannot meaningfully access the properties of the object type. Note that - // for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in an expression. This is to - // preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved - // eagerly using the constraint type of 'this' at the given location. - if (isGenericIndexType(indexType) || (accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType ? - isGenericTupleType(objectType) && !indexTypeLessThan(indexType, objectType.target.fixedLength) : - isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)))) { - if (objectType.flags & TypeFlags.AnyOrUnknown) { - return objectType; - } - // Defer the operation by creating an indexed access type. - const persistentAccessFlags = accessFlags & AccessFlags.Persistent; - const id = objectType.id + "," + indexType.id + "," + persistentAccessFlags + getAliasId(aliasSymbol, aliasTypeArguments); - let type = indexedAccessTypes.get(id); - if (!type) { - indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType, persistentAccessFlags, aliasSymbol, aliasTypeArguments)); - } + function isTypicalNondistributiveConditional(root: ConditionalRoot) { + return !root.isDistributive && isSingletonTupleType(root.node.checkType) && isSingletonTupleType(root.node.extendsType); + } - return type; + function isSingletonTupleType(node: TypeNode) { + return isTupleTypeNode(node) && length(node.elements) === 1 && !isOptionalTypeNode(node.elements[0]) && !isRestTypeNode(node.elements[0]); + } + + /** + * We syntactually check for common nondistributive conditional shapes and unwrap them into + * the intended comparison - we do this so we can check if the unwrapped types are generic or + * not and appropriately defer condition calculation + */ + function unwrapNondistributiveConditionalTuple(root: ConditionalRoot, type: Type) { + return isTypicalNondistributiveConditional(root) && isTupleType(type) ? getTypeArguments(type)[0] : type; + } + + function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + let result; + let extraTypes: Type[] | undefined; + let tailCount = 0; + // We loop here for an immediately nested conditional type in the false position, effectively treating + // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for + // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of + // another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive + // cases we increment the tail recursion counter and stop after 1000 iterations. + while (true) { + if (tailCount === 1000) { + error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); + result = errorType; + break; } - // In the following we resolve T[K] to the type of the property in T selected by K. - // We treat boolean as different from other unions to improve errors; - // skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'. - const apparentObjectType = getReducedApparentType(objectType); - if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Boolean)) { - const propTypes: Type[] = []; - let wasMissingProp = false; - for (const t of (indexType as UnionType).types) { - const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, accessNode, accessFlags | (wasMissingProp ? AccessFlags.SuppressNoImplicitAnyError : 0)); - if (propType) { - propTypes.push(propType); + const isUnwrapped = isTypicalNondistributiveConditional(root); + const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper); + const checkTypeInstantiable = isGenericType(checkType); + const extendsType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), mapper); + if (checkType === wildcardType || extendsType === wildcardType) { + return wildcardType; + } + let combinedMapper: TypeMapper | undefined; + if (root.inferTypeParameters) { + const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None); + if (!checkTypeInstantiable) { + // We don't want inferences from constraints as they may cause us to eagerly resolve the + // conditional type instead of deferring resolution. Also, we always want strict function + // types rules (i.e. proper contravariance) for inferences. + inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + } + // It's possible for 'infer T' type paramteters to be given uninstantiated constraints when the + // those type parameters are used in type references (see getInferredTypeParameterConstraint). For + // that reason we need context.mapper to be first in the combined mapper. See #42636 for examples. + combinedMapper = mapper ? combineTypeMappers(context.mapper, mapper) : context.mapper; + } + // Instantiate the extends type including inferences for 'infer T' type parameters + const inferredExtendsType = combinedMapper ? instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), combinedMapper) : extendsType; + // We attempt to resolve the conditional type only when the check and extends types are non-generic + if (!checkTypeInstantiable && !isGenericType(inferredExtendsType)) { + // Return falseType for a definitely false extends check. We check an instantiations of the two + // types with type parameters mapped to the wildcard type, the most permissive instantiations + // possible (the wildcard type is assignable to and from all types). If those are not related, + // then no instantiations will be and we can just return the false branch type. + if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && ((checkType.flags & TypeFlags.Any && !isUnwrapped) || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { + // Return union of trueType and falseType for 'any' since it matches anything + if (checkType.flags & TypeFlags.Any && !isUnwrapped) { + (extraTypes || (extraTypes = [])).push(instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper)); + } + // If falseType is an immediately nested conditional type that isn't distributive or has an + // identical checkType, switch to that type and loop. + const falseType = getTypeFromTypeNode(root.node.falseType); + if (falseType.flags & TypeFlags.Conditional) { + const newRoot = (falseType as ConditionalType).root; + if (newRoot.node.parent === root.node && (!newRoot.isDistributive || newRoot.checkType === root.checkType)) { + root = newRoot; + continue; + } + if (canTailRecurse(falseType, mapper)) { + continue; + } } - else if (!accessNode) { - // If there's no error node, we can immeditely stop, since error reporting is off - return undefined; - } - else { - // Otherwise we set a flag and return at the end of the loop so we still mark all errors - wasMissingProp = true; - } - } - if (wasMissingProp) { - return undefined; - } - return accessFlags & AccessFlags.Writing - ? getIntersectionType(propTypes, aliasSymbol, aliasTypeArguments) - : getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments); - } - return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol | AccessFlags.ReportDeprecated); - } - - function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const objectType = getTypeFromTypeNode(node.objectType); - const indexType = getTypeFromTypeNode(node.indexType); - const potentialAlias = getAliasSymbolForTypeNode(node); - const resolved = getIndexedAccessType(objectType, indexType, AccessFlags.None, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias)); - links.resolvedType = resolved.flags & TypeFlags.IndexedAccess && - (resolved as IndexedAccessType).objectType === objectType && - (resolved as IndexedAccessType).indexType === indexType ? - getConditionalFlowTypeOfType(resolved, node) : resolved; - } - return links.resolvedType; - } - - function getTypeFromMappedTypeNode(node: MappedTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const type = createObjectType(ObjectFlags.Mapped, node.symbol) as MappedType; - type.declaration = node; - type.aliasSymbol = getAliasSymbolForTypeNode(node); - type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol); - links.resolvedType = type; - // Eagerly resolve the constraint type which forces an error if the constraint type circularly - // references itself through one or more type aliases. - getConstraintTypeFromMappedType(type); - } - return links.resolvedType; - } - - function getActualTypeVariable(type: Type): Type { - if (type.flags & TypeFlags.Substitution) { - return (type as SubstitutionType).baseType; - } - if (type.flags & TypeFlags.IndexedAccess && ( - (type as IndexedAccessType).objectType.flags & TypeFlags.Substitution || - (type as IndexedAccessType).indexType.flags & TypeFlags.Substitution)) { - return getIndexedAccessType(getActualTypeVariable((type as IndexedAccessType).objectType), getActualTypeVariable((type as IndexedAccessType).indexType)); - } - return type; - } - - function isTypicalNondistributiveConditional(root: ConditionalRoot) { - return !root.isDistributive && isSingletonTupleType(root.node.checkType) && isSingletonTupleType(root.node.extendsType); - } - - function isSingletonTupleType(node: TypeNode) { - return isTupleTypeNode(node) && length(node.elements) === 1 && !isOptionalTypeNode(node.elements[0]) && !isRestTypeNode(node.elements[0]); - } - - /** - * We syntactually check for common nondistributive conditional shapes and unwrap them into - * the intended comparison - we do this so we can check if the unwrapped types are generic or - * not and appropriately defer condition calculation - */ - function unwrapNondistributiveConditionalTuple(root: ConditionalRoot, type: Type) { - return isTypicalNondistributiveConditional(root) && isTupleType(type) ? getTypeArguments(type)[0] : type; - } - - function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - let result; - let extraTypes: Type[] | undefined; - let tailCount = 0; - // We loop here for an immediately nested conditional type in the false position, effectively treating - // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for - // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of - // another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive - // cases we increment the tail recursion counter and stop after 1000 iterations. - while (true) { - if (tailCount === 1000) { - error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); - result = errorType; + result = instantiateType(falseType, mapper); break; } - const isUnwrapped = isTypicalNondistributiveConditional(root); - const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper); - const checkTypeInstantiable = isGenericType(checkType); - const extendsType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), mapper); - if (checkType === wildcardType || extendsType === wildcardType) { - return wildcardType; - } - let combinedMapper: TypeMapper | undefined; - if (root.inferTypeParameters) { - const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None); - if (!checkTypeInstantiable) { - // We don't want inferences from constraints as they may cause us to eagerly resolve the - // conditional type instead of deferring resolution. Also, we always want strict function - // types rules (i.e. proper contravariance) for inferences. - inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); - } - // It's possible for 'infer T' type paramteters to be given uninstantiated constraints when the - // those type parameters are used in type references (see getInferredTypeParameterConstraint). For - // that reason we need context.mapper to be first in the combined mapper. See #42636 for examples. - combinedMapper = mapper ? combineTypeMappers(context.mapper, mapper) : context.mapper; - } - // Instantiate the extends type including inferences for 'infer T' type parameters - const inferredExtendsType = combinedMapper ? instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), combinedMapper) : extendsType; - // We attempt to resolve the conditional type only when the check and extends types are non-generic - if (!checkTypeInstantiable && !isGenericType(inferredExtendsType)) { - // Return falseType for a definitely false extends check. We check an instantiations of the two - // types with type parameters mapped to the wildcard type, the most permissive instantiations - // possible (the wildcard type is assignable to and from all types). If those are not related, - // then no instantiations will be and we can just return the false branch type. - if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && ((checkType.flags & TypeFlags.Any && !isUnwrapped) || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { - // Return union of trueType and falseType for 'any' since it matches anything - if (checkType.flags & TypeFlags.Any && !isUnwrapped) { - (extraTypes || (extraTypes = [])).push(instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper)); - } - // If falseType is an immediately nested conditional type that isn't distributive or has an - // identical checkType, switch to that type and loop. - const falseType = getTypeFromTypeNode(root.node.falseType); - if (falseType.flags & TypeFlags.Conditional) { - const newRoot = (falseType as ConditionalType).root; - if (newRoot.node.parent === root.node && (!newRoot.isDistributive || newRoot.checkType === root.checkType)) { - root = newRoot; - continue; - } - if (canTailRecurse(falseType, mapper)) { - continue; - } - } - result = instantiateType(falseType, mapper); - break; - } - // Return trueType for a definitely true extends check. We check instantiations of the two - // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter - // that has no constraint. This ensures that, for example, the type - // type Foo = T extends { x: string } ? string : number - // doesn't immediately resolve to 'string' instead of being deferred. - if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { - const trueType = getTypeFromTypeNode(root.node.trueType); - const trueMapper = combinedMapper || mapper; - if (canTailRecurse(trueType, trueMapper)) { - continue; - } - result = instantiateType(trueType, trueMapper); - break; + // Return trueType for a definitely true extends check. We check instantiations of the two + // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter + // that has no constraint. This ensures that, for example, the type + // type Foo = T extends { x: string } ? string : number + // doesn't immediately resolve to 'string' instead of being deferred. + if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { + const trueType = getTypeFromTypeNode(root.node.trueType); + const trueMapper = combinedMapper || mapper; + if (canTailRecurse(trueType, trueMapper)) { + continue; } + result = instantiateType(trueType, trueMapper); + break; } - // Return a deferred type for a check that is neither definitely true nor definitely false - result = createType(TypeFlags.Conditional) as ConditionalType; - result.root = root; - result.checkType = instantiateType(root.checkType, mapper); - result.extendsType = instantiateType(root.extendsType, mapper); - result.mapper = mapper; - result.combinedMapper = combinedMapper; - result.aliasSymbol = aliasSymbol || root.aliasSymbol; - result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 - break; } - return extraTypes ? getUnionType(append(extraTypes, result)) : result; - // We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and - // (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check - // type. Note that recursion is possible only through aliased conditional types, so we only increment the tail - // recursion counter for those. - function canTailRecurse(newType: Type, newMapper: TypeMapper | undefined) { - if (newType.flags & TypeFlags.Conditional && newMapper) { - const newRoot = (newType as ConditionalType).root; - if (newRoot.outerTypeParameters) { - const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper); - const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)); - const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); - const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined; - if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { - root = newRoot; - mapper = newRootMapper; - aliasSymbol = undefined; - aliasTypeArguments = undefined; - if (newRoot.aliasSymbol) { - tailCount++; - } - return true; + // Return a deferred type for a check that is neither definitely true nor definitely false + result = createType(TypeFlags.Conditional) as ConditionalType; + result.root = root; + result.checkType = instantiateType(root.checkType, mapper); + result.extendsType = instantiateType(root.extendsType, mapper); + result.mapper = mapper; + result.combinedMapper = combinedMapper; + result.aliasSymbol = aliasSymbol || root.aliasSymbol; + result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 + break; + } + return extraTypes ? getUnionType(append(extraTypes, result)) : result; + // We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and + // (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check + // type. Note that recursion is possible only through aliased conditional types, so we only increment the tail + // recursion counter for those. + function canTailRecurse(newType: Type, newMapper: TypeMapper | undefined) { + if (newType.flags & TypeFlags.Conditional && newMapper) { + const newRoot = (newType as ConditionalType).root; + if (newRoot.outerTypeParameters) { + const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper); + const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)); + const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); + const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined; + if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { + root = newRoot; + mapper = newRootMapper; + aliasSymbol = undefined; + aliasTypeArguments = undefined; + if (newRoot.aliasSymbol) { + tailCount++; } + return true; } } - return false; } + return false; } + } - function getTrueTypeFromConditionalType(type: ConditionalType) { - return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.mapper)); - } + function getTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.mapper)); + } - function getFalseTypeFromConditionalType(type: ConditionalType) { - return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(getTypeFromTypeNode(type.root.node.falseType), type.mapper)); - } + function getFalseTypeFromConditionalType(type: ConditionalType) { + return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(getTypeFromTypeNode(type.root.node.falseType), type.mapper)); + } + + function getInferredTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.combinedMapper) : getTrueTypeFromConditionalType(type)); + } - function getInferredTrueTypeFromConditionalType(type: ConditionalType) { - return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.combinedMapper) : getTrueTypeFromConditionalType(type)); + function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined { + let result: TypeParameter[] | undefined; + if (node.locals) { + node.locals.forEach(symbol => { + if (symbol.flags & SymbolFlags.TypeParameter) { + result = append(result, getDeclaredTypeOfSymbol(symbol)); + } + }); } + return result; + } - function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined { - let result: TypeParameter[] | undefined; - if (node.locals) { - node.locals.forEach(symbol => { - if (symbol.flags & SymbolFlags.TypeParameter) { - result = append(result, getDeclaredTypeOfSymbol(symbol)); - } - }); + function isDistributionDependent(root: ConditionalRoot) { + return root.isDistributive && ( + isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.trueType) || + isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.falseType)); + } + + function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const checkType = getTypeFromTypeNode(node.checkType); + const aliasSymbol = getAliasSymbolForTypeNode(node); + const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); + const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); + const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, node)); + const root: ConditionalRoot = { + node, + checkType, + extendsType: getTypeFromTypeNode(node.extendsType), + isDistributive: !!(checkType.flags & TypeFlags.TypeParameter), + inferTypeParameters: getInferTypeParameters(node), + outerTypeParameters, + instantiations: undefined, + aliasSymbol, + aliasTypeArguments + }; + links.resolvedType = getConditionalType(root, /*mapper*/ undefined); + if (outerTypeParameters) { + root.instantiations = new Map(); + root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); } - return result; } + return links.resolvedType; + } - function isDistributionDependent(root: ConditionalRoot) { - return root.isDistributive && ( - isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.trueType) || - isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.falseType)); + function getTypeFromInferTypeNode(node: InferTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node.typeParameter)); } + return links.resolvedType; + } - function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const checkType = getTypeFromTypeNode(node.checkType); - const aliasSymbol = getAliasSymbolForTypeNode(node); - const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); - const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); - const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, node)); - const root: ConditionalRoot = { - node, - checkType, - extendsType: getTypeFromTypeNode(node.extendsType), - isDistributive: !!(checkType.flags & TypeFlags.TypeParameter), - inferTypeParameters: getInferTypeParameters(node), - outerTypeParameters, - instantiations: undefined, - aliasSymbol, - aliasTypeArguments - }; - links.resolvedType = getConditionalType(root, /*mapper*/ undefined); - if (outerTypeParameters) { - root.instantiations = new Map(); - root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); - } - } - return links.resolvedType; + function getIdentifierChain(node: EntityName): Identifier[] { + if (isIdentifier(node)) { + return [node]; } - - function getTypeFromInferTypeNode(node: InferTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node.typeParameter)); - } - return links.resolvedType; + else { + return append(getIdentifierChain(node.left), node.right); } + } - function getIdentifierChain(node: EntityName): Identifier[] { - if (isIdentifier(node)) { - return [node]; + function getTypeFromImportTypeNode(node: ImportTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + if (node.isTypeOf && node.typeArguments) { // Only the non-typeof form can make use of type arguments + error(node, Diagnostics.Type_arguments_cannot_be_used_here); + links.resolvedSymbol = unknownSymbol; + return links.resolvedType = errorType; + } + if (!isLiteralImportTypeNode(node)) { + error(node.argument, Diagnostics.String_literal_expected); + links.resolvedSymbol = unknownSymbol; + return links.resolvedType = errorType; + } + const targetMeaning = node.isTypeOf ? SymbolFlags.Value : node.flags & NodeFlags.JSDoc ? SymbolFlags.Value | SymbolFlags.Type : SymbolFlags.Type; + // TODO: Future work: support unions/generics/whatever via a deferred import-type + const innerModuleSymbol = resolveExternalModuleName(node, node.argument.literal); + if (!innerModuleSymbol) { + links.resolvedSymbol = unknownSymbol; + return links.resolvedType = errorType; + } + const moduleSymbol = resolveExternalModuleSymbol(innerModuleSymbol, /*dontResolveAlias*/ false); + if (!nodeIsMissing(node.qualifier)) { + const nameStack: Identifier[] = getIdentifierChain(node.qualifier!); + let currentNamespace = moduleSymbol; + let current: Identifier | undefined; + while (current = nameStack.shift()) { + const meaning = nameStack.length ? SymbolFlags.Namespace : targetMeaning; + // typeof a.b.c is normally resolved using `checkExpression` which in turn defers to `checkQualifiedName` + // That, in turn, ultimately uses `getPropertyOfType` on the type of the symbol, which differs slightly from + // the `exports` lookup process that only looks up namespace members which is used for most type references + const mergedResolvedSymbol = getMergedSymbol(resolveSymbol(currentNamespace)); + const next = node.isTypeOf + ? getPropertyOfType(getTypeOfSymbol(mergedResolvedSymbol), current.escapedText) + : getSymbol(getExportsOfSymbol(mergedResolvedSymbol), current.escapedText, meaning); + if (!next) { + error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current)); + return links.resolvedType = errorType; + } + getNodeLinks(current).resolvedSymbol = next; + getNodeLinks(current.parent).resolvedSymbol = next; + currentNamespace = next; + } + links.resolvedType = resolveImportSymbolType(node, links, currentNamespace, targetMeaning); } else { - return append(getIdentifierChain(node.left), node.right); - } - } - - function getTypeFromImportTypeNode(node: ImportTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - if (node.isTypeOf && node.typeArguments) { // Only the non-typeof form can make use of type arguments - error(node, Diagnostics.Type_arguments_cannot_be_used_here); - links.resolvedSymbol = unknownSymbol; - return links.resolvedType = errorType; - } - if (!isLiteralImportTypeNode(node)) { - error(node.argument, Diagnostics.String_literal_expected); - links.resolvedSymbol = unknownSymbol; - return links.resolvedType = errorType; - } - const targetMeaning = node.isTypeOf ? SymbolFlags.Value : node.flags & NodeFlags.JSDoc ? SymbolFlags.Value | SymbolFlags.Type : SymbolFlags.Type; - // TODO: Future work: support unions/generics/whatever via a deferred import-type - const innerModuleSymbol = resolveExternalModuleName(node, node.argument.literal); - if (!innerModuleSymbol) { - links.resolvedSymbol = unknownSymbol; - return links.resolvedType = errorType; - } - const moduleSymbol = resolveExternalModuleSymbol(innerModuleSymbol, /*dontResolveAlias*/ false); - if (!nodeIsMissing(node.qualifier)) { - const nameStack: Identifier[] = getIdentifierChain(node.qualifier!); - let currentNamespace = moduleSymbol; - let current: Identifier | undefined; - while (current = nameStack.shift()) { - const meaning = nameStack.length ? SymbolFlags.Namespace : targetMeaning; - // typeof a.b.c is normally resolved using `checkExpression` which in turn defers to `checkQualifiedName` - // That, in turn, ultimately uses `getPropertyOfType` on the type of the symbol, which differs slightly from - // the `exports` lookup process that only looks up namespace members which is used for most type references - const mergedResolvedSymbol = getMergedSymbol(resolveSymbol(currentNamespace)); - const next = node.isTypeOf - ? getPropertyOfType(getTypeOfSymbol(mergedResolvedSymbol), current.escapedText) - : getSymbol(getExportsOfSymbol(mergedResolvedSymbol), current.escapedText, meaning); - if (!next) { - error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current)); - return links.resolvedType = errorType; - } - getNodeLinks(current).resolvedSymbol = next; - getNodeLinks(current.parent).resolvedSymbol = next; - currentNamespace = next; - } - links.resolvedType = resolveImportSymbolType(node, links, currentNamespace, targetMeaning); + if (moduleSymbol.flags & targetMeaning) { + links.resolvedType = resolveImportSymbolType(node, links, moduleSymbol, targetMeaning); } else { - if (moduleSymbol.flags & targetMeaning) { - links.resolvedType = resolveImportSymbolType(node, links, moduleSymbol, targetMeaning); - } - else { - const errorMessage = targetMeaning === SymbolFlags.Value - ? Diagnostics.Module_0_does_not_refer_to_a_value_but_is_used_as_a_value_here - : Diagnostics.Module_0_does_not_refer_to_a_type_but_is_used_as_a_type_here_Did_you_mean_typeof_import_0; + const errorMessage = targetMeaning === SymbolFlags.Value + ? Diagnostics.Module_0_does_not_refer_to_a_value_but_is_used_as_a_value_here + : Diagnostics.Module_0_does_not_refer_to_a_type_but_is_used_as_a_type_here_Did_you_mean_typeof_import_0; - error(node, errorMessage, node.argument.literal.text); + error(node, errorMessage, node.argument.literal.text); - links.resolvedSymbol = unknownSymbol; - links.resolvedType = errorType; - } + links.resolvedSymbol = unknownSymbol; + links.resolvedType = errorType; } } - return links.resolvedType; } + return links.resolvedType; + } - function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) { - const resolvedSymbol = resolveSymbol(symbol); - links.resolvedSymbol = resolvedSymbol; - if (meaning === SymbolFlags.Value) { - return getTypeOfSymbol(symbol); // intentionally doesn't use resolved symbol so type is cached as expected on the alias - } - else { - return getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol - } + function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) { + const resolvedSymbol = resolveSymbol(symbol); + links.resolvedSymbol = resolvedSymbol; + if (meaning === SymbolFlags.Value) { + return getTypeOfSymbol(symbol); // intentionally doesn't use resolved symbol so type is cached as expected on the alias } + else { + return getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol + } + } - function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - // Deferred resolution of members is handled by resolveObjectTypeMembers - const aliasSymbol = getAliasSymbolForTypeNode(node); - if (getMembersOfSymbol(node.symbol).size === 0 && !aliasSymbol) { - links.resolvedType = emptyTypeLiteralType; - } - else { - let type = createObjectType(ObjectFlags.Anonymous, node.symbol); - type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); - if (isJSDocTypeLiteral(node) && node.isArrayType) { - type = createArrayType(type); - } - links.resolvedType = type; + function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + // Deferred resolution of members is handled by resolveObjectTypeMembers + const aliasSymbol = getAliasSymbolForTypeNode(node); + if (getMembersOfSymbol(node.symbol).size === 0 && !aliasSymbol) { + links.resolvedType = emptyTypeLiteralType; + } + else { + let type = createObjectType(ObjectFlags.Anonymous, node.symbol); + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); + if (isJSDocTypeLiteral(node) && node.isArrayType) { + type = createArrayType(type); } + links.resolvedType = type; } - return links.resolvedType; } + return links.resolvedType; + } - function getAliasSymbolForTypeNode(node: Node) { - let host = node.parent; - while (isParenthesizedTypeNode(host) || isJSDocTypeExpression(host) || isTypeOperatorNode(host) && host.operator === SyntaxKind.ReadonlyKeyword) { - host = host.parent; - } - return isTypeAlias(host) ? getSymbolOfNode(host) : undefined; + function getAliasSymbolForTypeNode(node: Node) { + let host = node.parent; + while (isParenthesizedTypeNode(host) || isJSDocTypeExpression(host) || isTypeOperatorNode(host) && host.operator === SyntaxKind.ReadonlyKeyword) { + host = host.parent; } + return isTypeAlias(host) ? getSymbolOfNode(host) : undefined; + } + + function getTypeArgumentsForAliasSymbol(symbol: Symbol | undefined) { + return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined; + } + + function isNonGenericObjectType(type: Type) { + return !!(type.flags & TypeFlags.Object) && !isGenericMappedType(type); + } + + function isEmptyObjectTypeOrSpreadsIntoEmptyObject(type: Type) { + return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)); + } - function getTypeArgumentsForAliasSymbol(symbol: Symbol | undefined) { - return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined; + function tryMergeUnionOfObjectTypeAndEmptyObject(type: Type, readonly: boolean): Type { + if (!(type.flags & TypeFlags.Union)) { + return type; + } + if (every((type as UnionType).types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) { + return find((type as UnionType).types, isEmptyObjectType) || emptyObjectType; + } + const firstType = find((type as UnionType).types, t => !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t)); + if (!firstType) { + return type; + } + const secondType = find((type as UnionType).types, t => t !== firstType && !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t)); + if (secondType) { + return type; } + return getAnonymousPartialType(firstType); - function isNonGenericObjectType(type: Type) { - return !!(type.flags & TypeFlags.Object) && !isGenericMappedType(type); + function getAnonymousPartialType(type: Type) { + // gets the type as if it had been spread, but where everything in the spread is made optional + const members = createSymbolTable(); + for (const prop of getPropertiesOfType(type)) { + if (getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) { + // do nothing, skip privates + } + else if (isSpreadableProperty(prop)) { + const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); + const flags = SymbolFlags.Property | SymbolFlags.Optional; + const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0)); + result.type = isSetonlyAccessor ? undefinedType : addOptionality(getTypeOfSymbol(prop), /*isProperty*/ true); + result.declarations = prop.declarations; + result.nameType = getSymbolLinks(prop).nameType; + result.syntheticOrigin = prop; + members.set(prop.escapedName, result); + } + } + const spread = createAnonymousType(type.symbol, members, emptyArray, emptyArray, getIndexInfosOfType(type)); + spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + return spread; } + } - function isEmptyObjectTypeOrSpreadsIntoEmptyObject(type: Type) { - return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)); + /** + * Since the source of spread types are object literals, which are not binary, + * this function should be called in a left folding style, with left = previous result of getSpreadType + * and right = the new element to be spread. + */ + function getSpreadType(left: Type, right: Type, symbol: Symbol | undefined, objectFlags: ObjectFlags, readonly: boolean): Type { + if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) { + return anyType; + } + if (left.flags & TypeFlags.Unknown || right.flags & TypeFlags.Unknown) { + return unknownType; + } + if (left.flags & TypeFlags.Never) { + return right; + } + if (right.flags & TypeFlags.Never) { + return left; + } + left = tryMergeUnionOfObjectTypeAndEmptyObject(left, readonly); + if (left.flags & TypeFlags.Union) { + return checkCrossProductUnion([left, right]) + ? mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly)) + : errorType; + } + right = tryMergeUnionOfObjectTypeAndEmptyObject(right, readonly); + if (right.flags & TypeFlags.Union) { + return checkCrossProductUnion([left, right]) + ? mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly)) + : errorType; + } + if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) { + return left; } - function tryMergeUnionOfObjectTypeAndEmptyObject(type: Type, readonly: boolean): Type { - if (!(type.flags & TypeFlags.Union)) { - return type; - } - if (every((type as UnionType).types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) { - return find((type as UnionType).types, isEmptyObjectType) || emptyObjectType; - } - const firstType = find((type as UnionType).types, t => !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t)); - if (!firstType) { - return type; - } - const secondType = find((type as UnionType).types, t => t !== firstType && !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t)); - if (secondType) { - return type; + if (isGenericObjectType(left) || isGenericObjectType(right)) { + if (isEmptyObjectType(left)) { + return right; } - return getAnonymousPartialType(firstType); - - function getAnonymousPartialType(type: Type) { - // gets the type as if it had been spread, but where everything in the spread is made optional - const members = createSymbolTable(); - for (const prop of getPropertiesOfType(type)) { - if (getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) { - // do nothing, skip privates - } - else if (isSpreadableProperty(prop)) { - const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); - const flags = SymbolFlags.Property | SymbolFlags.Optional; - const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0)); - result.type = isSetonlyAccessor ? undefinedType : addOptionality(getTypeOfSymbol(prop), /*isProperty*/ true); - result.declarations = prop.declarations; - result.nameType = getSymbolLinks(prop).nameType; - result.syntheticOrigin = prop; - members.set(prop.escapedName, result); - } + // When the left type is an intersection, we may need to merge the last constituent of the + // intersection with the right type. For example when the left type is 'T & { a: string }' + // and the right type is '{ b: string }' we produce 'T & { a: string, b: string }'. + if (left.flags & TypeFlags.Intersection) { + const types = (left as IntersectionType).types; + const lastLeft = types[types.length - 1]; + if (isNonGenericObjectType(lastLeft) && isNonGenericObjectType(right)) { + return getIntersectionType(concatenate(types.slice(0, types.length - 1), [getSpreadType(lastLeft, right, symbol, objectFlags, readonly)])); } - const spread = createAnonymousType(type.symbol, members, emptyArray, emptyArray, getIndexInfosOfType(type)); - spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - return spread; } + return getIntersectionType([left, right]); } - /** - * Since the source of spread types are object literals, which are not binary, - * this function should be called in a left folding style, with left = previous result of getSpreadType - * and right = the new element to be spread. - */ - function getSpreadType(left: Type, right: Type, symbol: Symbol | undefined, objectFlags: ObjectFlags, readonly: boolean): Type { - if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) { - return anyType; - } - if (left.flags & TypeFlags.Unknown || right.flags & TypeFlags.Unknown) { - return unknownType; - } - if (left.flags & TypeFlags.Never) { - return right; - } - if (right.flags & TypeFlags.Never) { - return left; - } - left = tryMergeUnionOfObjectTypeAndEmptyObject(left, readonly); - if (left.flags & TypeFlags.Union) { - return checkCrossProductUnion([left, right]) - ? mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly)) - : errorType; - } - right = tryMergeUnionOfObjectTypeAndEmptyObject(right, readonly); - if (right.flags & TypeFlags.Union) { - return checkCrossProductUnion([left, right]) - ? mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly)) - : errorType; + const members = createSymbolTable(); + const skippedPrivateMembers = new Set<__String>(); + const indexInfos = left === emptyObjectType ? getIndexInfosOfType(right) : getUnionIndexInfos([left, right]); + + for (const rightProp of getPropertiesOfType(right)) { + if (getDeclarationModifierFlagsFromSymbol(rightProp) & (ModifierFlags.Private | ModifierFlags.Protected)) { + skippedPrivateMembers.add(rightProp.escapedName); } - if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) { - return left; + else if (isSpreadableProperty(rightProp)) { + members.set(rightProp.escapedName, getSpreadSymbol(rightProp, readonly)); } + } - if (isGenericObjectType(left) || isGenericObjectType(right)) { - if (isEmptyObjectType(left)) { - return right; - } - // When the left type is an intersection, we may need to merge the last constituent of the - // intersection with the right type. For example when the left type is 'T & { a: string }' - // and the right type is '{ b: string }' we produce 'T & { a: string, b: string }'. - if (left.flags & TypeFlags.Intersection) { - const types = (left as IntersectionType).types; - const lastLeft = types[types.length - 1]; - if (isNonGenericObjectType(lastLeft) && isNonGenericObjectType(right)) { - return getIntersectionType(concatenate(types.slice(0, types.length - 1), [getSpreadType(lastLeft, right, symbol, objectFlags, readonly)])); - } + for (const leftProp of getPropertiesOfType(left)) { + if (skippedPrivateMembers.has(leftProp.escapedName) || !isSpreadableProperty(leftProp)) { + continue; + } + if (members.has(leftProp.escapedName)) { + const rightProp = members.get(leftProp.escapedName)!; + const rightType = getTypeOfSymbol(rightProp); + if (rightProp.flags & SymbolFlags.Optional) { + const declarations = concatenate(leftProp.declarations, rightProp.declarations); + const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional); + const result = createSymbol(flags, leftProp.escapedName); + result.type = getUnionType([getTypeOfSymbol(leftProp), removeMissingOrUndefinedType(rightType)], UnionReduction.Subtype); + result.leftSpread = leftProp; + result.rightSpread = rightProp; + result.declarations = declarations; + result.nameType = getSymbolLinks(leftProp).nameType; + members.set(leftProp.escapedName, result); } - return getIntersectionType([left, right]); } + else { + members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly)); + } + } - const members = createSymbolTable(); - const skippedPrivateMembers = new Set<__String>(); - const indexInfos = left === emptyObjectType ? getIndexInfosOfType(right) : getUnionIndexInfos([left, right]); + const spread = createAnonymousType(symbol, members, emptyArray, emptyArray, sameMap(indexInfos, info => getIndexInfoWithReadonly(info, readonly))); + spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; + return spread; + } - for (const rightProp of getPropertiesOfType(right)) { - if (getDeclarationModifierFlagsFromSymbol(rightProp) & (ModifierFlags.Private | ModifierFlags.Protected)) { - skippedPrivateMembers.add(rightProp.escapedName); - } - else if (isSpreadableProperty(rightProp)) { - members.set(rightProp.escapedName, getSpreadSymbol(rightProp, readonly)); - } - } + /** We approximate own properties as non-methods plus methods that are inside the object literal */ + function isSpreadableProperty(prop: Symbol): boolean { + return !some(prop.declarations, isPrivateIdentifierClassElementDeclaration) && + (!(prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor | SymbolFlags.SetAccessor)) || + !prop.declarations?.some(decl => isClassLike(decl.parent))); + } - for (const leftProp of getPropertiesOfType(left)) { - if (skippedPrivateMembers.has(leftProp.escapedName) || !isSpreadableProperty(leftProp)) { - continue; - } - if (members.has(leftProp.escapedName)) { - const rightProp = members.get(leftProp.escapedName)!; - const rightType = getTypeOfSymbol(rightProp); - if (rightProp.flags & SymbolFlags.Optional) { - const declarations = concatenate(leftProp.declarations, rightProp.declarations); - const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional); - const result = createSymbol(flags, leftProp.escapedName); - result.type = getUnionType([getTypeOfSymbol(leftProp), removeMissingOrUndefinedType(rightType)], UnionReduction.Subtype); - result.leftSpread = leftProp; - result.rightSpread = rightProp; - result.declarations = declarations; - result.nameType = getSymbolLinks(leftProp).nameType; - members.set(leftProp.escapedName, result); - } - } - else { - members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly)); - } - } + function getSpreadSymbol(prop: Symbol, readonly: boolean) { + const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); + if (!isSetonlyAccessor && readonly === isReadonlySymbol(prop)) { + return prop; + } + const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional); + const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0)); + result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); + result.declarations = prop.declarations; + result.nameType = getSymbolLinks(prop).nameType; + result.syntheticOrigin = prop; + return result; + } - const spread = createAnonymousType(symbol, members, emptyArray, emptyArray, sameMap(indexInfos, info => getIndexInfoWithReadonly(info, readonly))); - spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; - return spread; - } + function getIndexInfoWithReadonly(info: IndexInfo, readonly: boolean) { + return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration) : info; + } - /** We approximate own properties as non-methods plus methods that are inside the object literal */ - function isSpreadableProperty(prop: Symbol): boolean { - return !some(prop.declarations, isPrivateIdentifierClassElementDeclaration) && - (!(prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor | SymbolFlags.SetAccessor)) || - !prop.declarations?.some(decl => isClassLike(decl.parent))); - } + function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol?: Symbol, regularType?: LiteralType) { + const type = createType(flags) as LiteralType; + type.symbol = symbol!; + type.value = value; + type.regularType = regularType || type; + return type; + } - function getSpreadSymbol(prop: Symbol, readonly: boolean) { - const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); - if (!isSetonlyAccessor && readonly === isReadonlySymbol(prop)) { - return prop; + function getFreshTypeOfLiteralType(type: Type): Type { + if (type.flags & TypeFlags.Literal) { + if (!(type as LiteralType).freshType) { + const freshType = createLiteralType(type.flags, (type as LiteralType).value, (type as LiteralType).symbol, type as LiteralType); + freshType.freshType = freshType; + (type as LiteralType).freshType = freshType; } - const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional); - const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0)); - result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); - result.declarations = prop.declarations; - result.nameType = getSymbolLinks(prop).nameType; - result.syntheticOrigin = prop; - return result; + return (type as LiteralType).freshType; } + return type; + } - function getIndexInfoWithReadonly(info: IndexInfo, readonly: boolean) { - return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration) : info; - } + function getRegularTypeOfLiteralType(type: Type): Type { + return type.flags & TypeFlags.Literal ? (type as LiteralType).regularType : + type.flags & TypeFlags.Union ? ((type as UnionType).regularType || ((type as UnionType).regularType = mapType(type, getRegularTypeOfLiteralType) as UnionType)) : + type; + } - function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol?: Symbol, regularType?: LiteralType) { - const type = createType(flags) as LiteralType; - type.symbol = symbol!; - type.value = value; - type.regularType = regularType || type; - return type; - } + function isFreshLiteralType(type: Type) { + return !!(type.flags & TypeFlags.Literal) && (type as LiteralType).freshType === type; + } - function getFreshTypeOfLiteralType(type: Type): Type { - if (type.flags & TypeFlags.Literal) { - if (!(type as LiteralType).freshType) { - const freshType = createLiteralType(type.flags, (type as LiteralType).value, (type as LiteralType).symbol, type as LiteralType); - freshType.freshType = freshType; - (type as LiteralType).freshType = freshType; - } - return (type as LiteralType).freshType; - } - return type; - } + function getStringLiteralType(value: string): StringLiteralType { + let type; + return stringLiteralTypes.get(value) || + (stringLiteralTypes.set(value, type = createLiteralType(TypeFlags.StringLiteral, value) as StringLiteralType), type); + } - function getRegularTypeOfLiteralType(type: Type): Type { - return type.flags & TypeFlags.Literal ? (type as LiteralType).regularType : - type.flags & TypeFlags.Union ? ((type as UnionType).regularType || ((type as UnionType).regularType = mapType(type, getRegularTypeOfLiteralType) as UnionType)) : - type; - } + function getNumberLiteralType(value: number): NumberLiteralType { + let type; + return numberLiteralTypes.get(value) || + (numberLiteralTypes.set(value, type = createLiteralType(TypeFlags.NumberLiteral, value) as NumberLiteralType), type); + } - function isFreshLiteralType(type: Type) { - return !!(type.flags & TypeFlags.Literal) && (type as LiteralType).freshType === type; - } + function getBigIntLiteralType(value: PseudoBigInt): BigIntLiteralType { + let type; + const key = pseudoBigIntToString(value); + return bigIntLiteralTypes.get(key) || + (bigIntLiteralTypes.set(key, type = createLiteralType(TypeFlags.BigIntLiteral, value) as BigIntLiteralType), type); + } - function getStringLiteralType(value: string): StringLiteralType { - let type; - return stringLiteralTypes.get(value) || - (stringLiteralTypes.set(value, type = createLiteralType(TypeFlags.StringLiteral, value) as StringLiteralType), type); - } + function getEnumLiteralType(value: string | number, enumId: number, symbol: Symbol): LiteralType { + let type; + const qualifier = typeof value === "string" ? "@" : "#"; + const key = enumId + qualifier + value; + const flags = TypeFlags.EnumLiteral | (typeof value === "string" ? TypeFlags.StringLiteral : TypeFlags.NumberLiteral); + return enumLiteralTypes.get(key) || + (enumLiteralTypes.set(key, type = createLiteralType(flags, value, symbol)), type); + } - function getNumberLiteralType(value: number): NumberLiteralType { - let type; - return numberLiteralTypes.get(value) || - (numberLiteralTypes.set(value, type = createLiteralType(TypeFlags.NumberLiteral, value) as NumberLiteralType), type); + function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { + if (node.literal.kind === SyntaxKind.NullKeyword) { + return nullType; } - - function getBigIntLiteralType(value: PseudoBigInt): BigIntLiteralType { - let type; - const key = pseudoBigIntToString(value); - return bigIntLiteralTypes.get(key) || - (bigIntLiteralTypes.set(key, type = createLiteralType(TypeFlags.BigIntLiteral, value) as BigIntLiteralType), type); + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getRegularTypeOfLiteralType(checkExpression(node.literal)); } + return links.resolvedType; + } + + function createUniqueESSymbolType(symbol: Symbol) { + const type = createType(TypeFlags.UniqueESSymbol) as UniqueESSymbolType; + type.symbol = symbol; + type.escapedName = `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String; + return type; + } - function getEnumLiteralType(value: string | number, enumId: number, symbol: Symbol): LiteralType { - let type; - const qualifier = typeof value === "string" ? "@" : "#"; - const key = enumId + qualifier + value; - const flags = TypeFlags.EnumLiteral | (typeof value === "string" ? TypeFlags.StringLiteral : TypeFlags.NumberLiteral); - return enumLiteralTypes.get(key) || - (enumLiteralTypes.set(key, type = createLiteralType(flags, value, symbol)), type); + function getESSymbolLikeTypeForNode(node: Node) { + if (isValidESSymbolDeclaration(node)) { + const symbol = getSymbolOfNode(node); + const links = getSymbolLinks(symbol); + return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol)); } + return esSymbolType; + } - function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { - if (node.literal.kind === SyntaxKind.NullKeyword) { - return nullType; - } - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = getRegularTypeOfLiteralType(checkExpression(node.literal)); + function getThisType(node: Node): Type { + const container = getThisContainer(node, /*includeArrowFunctions*/ false); + const parent = container && container.parent; + if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { + if (!isStatic(container) && + (!isConstructorDeclaration(container) || isNodeDescendantOf(node, container.body))) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent as ClassLikeDeclaration | InterfaceDeclaration)).thisType!; } - return links.resolvedType; } - function createUniqueESSymbolType(symbol: Symbol) { - const type = createType(TypeFlags.UniqueESSymbol) as UniqueESSymbolType; - type.symbol = symbol; - type.escapedName = `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String; - return type; + // inside x.prototype = { ... } + if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!; } - - function getESSymbolLikeTypeForNode(node: Node) { - if (isValidESSymbolDeclaration(node)) { - const symbol = getSymbolOfNode(node); - const links = getSymbolLinks(symbol); - return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol)); - } - return esSymbolType; + // /** @return {this} */ + // x.prototype.m = function() { ... } + const host = node.flags & NodeFlags.JSDoc ? getHostSignatureFromJSDoc(node) : undefined; + if (host && isFunctionExpression(host) && isBinaryExpression(host.parent) && getAssignmentDeclarationKind(host.parent) === AssignmentDeclarationKind.PrototypeProperty) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(host.parent.left)!.parent!).thisType!; } - - function getThisType(node: Node): Type { - const container = getThisContainer(node, /*includeArrowFunctions*/ false); - const parent = container && container.parent; - if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { - if (!isStatic(container) && - (!isConstructorDeclaration(container) || isNodeDescendantOf(node, container.body))) { - return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent as ClassLikeDeclaration | InterfaceDeclaration)).thisType!; - } - } - - // inside x.prototype = { ... } - if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) { - return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!; - } - // /** @return {this} */ - // x.prototype.m = function() { ... } - const host = node.flags & NodeFlags.JSDoc ? getHostSignatureFromJSDoc(node) : undefined; - if (host && isFunctionExpression(host) && isBinaryExpression(host.parent) && getAssignmentDeclarationKind(host.parent) === AssignmentDeclarationKind.PrototypeProperty) { - return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(host.parent.left)!.parent!).thisType!; - } - // inside constructor function C() { ... } - if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { - return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(container)).thisType!; - } - error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); - return errorType; + // inside constructor function C() { ... } + if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(container)).thisType!; } + error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); + return errorType; + } - function getTypeFromThisTypeNode(node: ThisExpression | ThisTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = getThisType(node); - } - return links.resolvedType; + function getTypeFromThisTypeNode(node: ThisExpression | ThisTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getThisType(node); } + return links.resolvedType; + } - function getTypeFromRestTypeNode(node: RestTypeNode | NamedTupleMember) { - return getTypeFromTypeNode(getArrayElementTypeNode(node.type) || node.type); - } + function getTypeFromRestTypeNode(node: RestTypeNode | NamedTupleMember) { + return getTypeFromTypeNode(getArrayElementTypeNode(node.type) || node.type); + } - function getArrayElementTypeNode(node: TypeNode): TypeNode | undefined { - switch (node.kind) { - case SyntaxKind.ParenthesizedType: - return getArrayElementTypeNode((node as ParenthesizedTypeNode).type); - case SyntaxKind.TupleType: - if ((node as TupleTypeNode).elements.length === 1) { - node = (node as TupleTypeNode).elements[0]; - if (node.kind === SyntaxKind.RestType || node.kind === SyntaxKind.NamedTupleMember && (node as NamedTupleMember).dotDotDotToken) { - return getArrayElementTypeNode((node as RestTypeNode | NamedTupleMember).type); - } + function getArrayElementTypeNode(node: TypeNode): TypeNode | undefined { + switch (node.kind) { + case SyntaxKind.ParenthesizedType: + return getArrayElementTypeNode((node as ParenthesizedTypeNode).type); + case SyntaxKind.TupleType: + if ((node as TupleTypeNode).elements.length === 1) { + node = (node as TupleTypeNode).elements[0]; + if (node.kind === SyntaxKind.RestType || node.kind === SyntaxKind.NamedTupleMember && (node as NamedTupleMember).dotDotDotToken) { + return getArrayElementTypeNode((node as RestTypeNode | NamedTupleMember).type); } - break; - case SyntaxKind.ArrayType: - return (node as ArrayTypeNode).elementType; - } - return undefined; + } + break; + case SyntaxKind.ArrayType: + return (node as ArrayTypeNode).elementType; } + return undefined; + } - function getTypeFromNamedTupleTypeNode(node: NamedTupleMember): Type { - const links = getNodeLinks(node); - return links.resolvedType || (links.resolvedType = - node.dotDotDotToken ? getTypeFromRestTypeNode(node) : - addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true, !!node.questionToken)); - } + function getTypeFromNamedTupleTypeNode(node: NamedTupleMember): Type { + const links = getNodeLinks(node); + return links.resolvedType || (links.resolvedType = + node.dotDotDotToken ? getTypeFromRestTypeNode(node) : + addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true, !!node.questionToken)); + } - function getTypeFromTypeNode(node: TypeNode): Type { - return getConditionalFlowTypeOfType(getTypeFromTypeNodeWorker(node), node); - } + function getTypeFromTypeNode(node: TypeNode): Type { + return getConditionalFlowTypeOfType(getTypeFromTypeNodeWorker(node), node); + } - function getTypeFromTypeNodeWorker(node: TypeNode): Type { - switch (node.kind) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.JSDocAllType: - case SyntaxKind.JSDocUnknownType: - return anyType; - case SyntaxKind.UnknownKeyword: - return unknownType; - case SyntaxKind.StringKeyword: - return stringType; - case SyntaxKind.NumberKeyword: - return numberType; - case SyntaxKind.BigIntKeyword: - return bigintType; - case SyntaxKind.BooleanKeyword: - return booleanType; - case SyntaxKind.SymbolKeyword: - return esSymbolType; - case SyntaxKind.VoidKeyword: - return voidType; - case SyntaxKind.UndefinedKeyword: - return undefinedType; - case SyntaxKind.NullKeyword as TypeNodeSyntaxKind: - // TODO(rbuckton): `NullKeyword` is no longer a `TypeNode`, but we defensively allow it here because of incorrect casts in the Language Service. - return nullType; - case SyntaxKind.NeverKeyword: - return neverType; - case SyntaxKind.ObjectKeyword: - return node.flags & NodeFlags.JavaScriptFile && !noImplicitAny ? anyType : nonPrimitiveType; - case SyntaxKind.IntrinsicKeyword: - return intrinsicMarkerType; - case SyntaxKind.ThisType: - case SyntaxKind.ThisKeyword as TypeNodeSyntaxKind: - // TODO(rbuckton): `ThisKeyword` is no longer a `TypeNode`, but we defensively allow it here because of incorrect casts in the Language Service and because of `isPartOfTypeNode`. - return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode); - case SyntaxKind.LiteralType: - return getTypeFromLiteralTypeNode(node as LiteralTypeNode); - case SyntaxKind.TypeReference: - return getTypeFromTypeReference(node as TypeReferenceNode); - case SyntaxKind.TypePredicate: - return (node as TypePredicateNode).assertsModifier ? voidType : booleanType; - case SyntaxKind.ExpressionWithTypeArguments: - return getTypeFromTypeReference(node as ExpressionWithTypeArguments); - case SyntaxKind.TypeQuery: - return getTypeFromTypeQueryNode(node as TypeQueryNode); - case SyntaxKind.ArrayType: - case SyntaxKind.TupleType: - return getTypeFromArrayOrTupleTypeNode(node as ArrayTypeNode | TupleTypeNode); - case SyntaxKind.OptionalType: - return getTypeFromOptionalTypeNode(node as OptionalTypeNode); - case SyntaxKind.UnionType: - return getTypeFromUnionTypeNode(node as UnionTypeNode); - case SyntaxKind.IntersectionType: - return getTypeFromIntersectionTypeNode(node as IntersectionTypeNode); - case SyntaxKind.JSDocNullableType: - return getTypeFromJSDocNullableTypeNode(node as JSDocNullableType); - case SyntaxKind.JSDocOptionalType: - return addOptionality(getTypeFromTypeNode((node as JSDocOptionalType).type)); - case SyntaxKind.NamedTupleMember: - return getTypeFromNamedTupleTypeNode(node as NamedTupleMember); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.JSDocNonNullableType: - case SyntaxKind.JSDocTypeExpression: - return getTypeFromTypeNode((node as ParenthesizedTypeNode | JSDocTypeReferencingNode | JSDocTypeExpression | NamedTupleMember).type); - case SyntaxKind.RestType: - return getTypeFromRestTypeNode(node as RestTypeNode); - case SyntaxKind.JSDocVariadicType: - return getTypeFromJSDocVariadicType(node as JSDocVariadicType); - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.JSDocSignature: - return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); - case SyntaxKind.TypeOperator: - return getTypeFromTypeOperatorNode(node as TypeOperatorNode); - case SyntaxKind.IndexedAccessType: - return getTypeFromIndexedAccessTypeNode(node as IndexedAccessTypeNode); - case SyntaxKind.MappedType: - return getTypeFromMappedTypeNode(node as MappedTypeNode); - case SyntaxKind.ConditionalType: - return getTypeFromConditionalTypeNode(node as ConditionalTypeNode); - case SyntaxKind.InferType: - return getTypeFromInferTypeNode(node as InferTypeNode); - case SyntaxKind.TemplateLiteralType: - return getTypeFromTemplateTypeNode(node as TemplateLiteralTypeNode); - case SyntaxKind.ImportType: - return getTypeFromImportTypeNode(node as ImportTypeNode); - // This function assumes that an identifier, qualified name, or property access expression is a type expression - // Callers should first ensure this by calling `isPartOfTypeNode` - // TODO(rbuckton): These aren't valid TypeNodes, but we treat them as such because of `isPartOfTypeNode`, which returns `true` for things that aren't `TypeNode`s. - case SyntaxKind.Identifier as TypeNodeSyntaxKind: - case SyntaxKind.QualifiedName as TypeNodeSyntaxKind: - case SyntaxKind.PropertyAccessExpression as TypeNodeSyntaxKind: - const symbol = getSymbolAtLocation(node); - return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; - default: - return errorType; - } + function getTypeFromTypeNodeWorker(node: TypeNode): Type { + switch (node.kind) { + case SyntaxKind.AnyKeyword: + case SyntaxKind.JSDocAllType: + case SyntaxKind.JSDocUnknownType: + return anyType; + case SyntaxKind.UnknownKeyword: + return unknownType; + case SyntaxKind.StringKeyword: + return stringType; + case SyntaxKind.NumberKeyword: + return numberType; + case SyntaxKind.BigIntKeyword: + return bigintType; + case SyntaxKind.BooleanKeyword: + return booleanType; + case SyntaxKind.SymbolKeyword: + return esSymbolType; + case SyntaxKind.VoidKeyword: + return voidType; + case SyntaxKind.UndefinedKeyword: + return undefinedType; + case SyntaxKind.NullKeyword as TypeNodeSyntaxKind: + // TODO(rbuckton): `NullKeyword` is no longer a `TypeNode`, but we defensively allow it here because of incorrect casts in the Language Service. + return nullType; + case SyntaxKind.NeverKeyword: + return neverType; + case SyntaxKind.ObjectKeyword: + return node.flags & NodeFlags.JavaScriptFile && !noImplicitAny ? anyType : nonPrimitiveType; + case SyntaxKind.IntrinsicKeyword: + return intrinsicMarkerType; + case SyntaxKind.ThisType: + case SyntaxKind.ThisKeyword as TypeNodeSyntaxKind: + // TODO(rbuckton): `ThisKeyword` is no longer a `TypeNode`, but we defensively allow it here because of incorrect casts in the Language Service and because of `isPartOfTypeNode`. + return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode); + case SyntaxKind.LiteralType: + return getTypeFromLiteralTypeNode(node as LiteralTypeNode); + case SyntaxKind.TypeReference: + return getTypeFromTypeReference(node as TypeReferenceNode); + case SyntaxKind.TypePredicate: + return (node as TypePredicateNode).assertsModifier ? voidType : booleanType; + case SyntaxKind.ExpressionWithTypeArguments: + return getTypeFromTypeReference(node as ExpressionWithTypeArguments); + case SyntaxKind.TypeQuery: + return getTypeFromTypeQueryNode(node as TypeQueryNode); + case SyntaxKind.ArrayType: + case SyntaxKind.TupleType: + return getTypeFromArrayOrTupleTypeNode(node as ArrayTypeNode | TupleTypeNode); + case SyntaxKind.OptionalType: + return getTypeFromOptionalTypeNode(node as OptionalTypeNode); + case SyntaxKind.UnionType: + return getTypeFromUnionTypeNode(node as UnionTypeNode); + case SyntaxKind.IntersectionType: + return getTypeFromIntersectionTypeNode(node as IntersectionTypeNode); + case SyntaxKind.JSDocNullableType: + return getTypeFromJSDocNullableTypeNode(node as JSDocNullableType); + case SyntaxKind.JSDocOptionalType: + return addOptionality(getTypeFromTypeNode((node as JSDocOptionalType).type)); + case SyntaxKind.NamedTupleMember: + return getTypeFromNamedTupleTypeNode(node as NamedTupleMember); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocTypeExpression: + return getTypeFromTypeNode((node as ParenthesizedTypeNode | JSDocTypeReferencingNode | JSDocTypeExpression | NamedTupleMember).type); + case SyntaxKind.RestType: + return getTypeFromRestTypeNode(node as RestTypeNode); + case SyntaxKind.JSDocVariadicType: + return getTypeFromJSDocVariadicType(node as JSDocVariadicType); + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocSignature: + return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); + case SyntaxKind.TypeOperator: + return getTypeFromTypeOperatorNode(node as TypeOperatorNode); + case SyntaxKind.IndexedAccessType: + return getTypeFromIndexedAccessTypeNode(node as IndexedAccessTypeNode); + case SyntaxKind.MappedType: + return getTypeFromMappedTypeNode(node as MappedTypeNode); + case SyntaxKind.ConditionalType: + return getTypeFromConditionalTypeNode(node as ConditionalTypeNode); + case SyntaxKind.InferType: + return getTypeFromInferTypeNode(node as InferTypeNode); + case SyntaxKind.TemplateLiteralType: + return getTypeFromTemplateTypeNode(node as TemplateLiteralTypeNode); + case SyntaxKind.ImportType: + return getTypeFromImportTypeNode(node as ImportTypeNode); + // This function assumes that an identifier, qualified name, or property access expression is a type expression + // Callers should first ensure this by calling `isPartOfTypeNode` + // TODO(rbuckton): These aren't valid TypeNodes, but we treat them as such because of `isPartOfTypeNode`, which returns `true` for things that aren't `TypeNode`s. + case SyntaxKind.Identifier as TypeNodeSyntaxKind: + case SyntaxKind.QualifiedName as TypeNodeSyntaxKind: + case SyntaxKind.PropertyAccessExpression as TypeNodeSyntaxKind: + const symbol = getSymbolAtLocation(node); + return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; + default: + return errorType; } + } - function instantiateList(items: readonly T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[]; - function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined; - function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined { - if (items && items.length) { - for (let i = 0; i < items.length; i++) { - const item = items[i]; - const mapped = instantiator(item, mapper); - if (item !== mapped) { - const result = i === 0 ? [] : items.slice(0, i); - result.push(mapped); - for (i++; i < items.length; i++) { - result.push(instantiator(items[i], mapper)); - } - return result; + function instantiateList(items: readonly T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[]; + function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined; + function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined { + if (items && items.length) { + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const mapped = instantiator(item, mapper); + if (item !== mapped) { + const result = i === 0 ? [] : items.slice(0, i); + result.push(mapped); + for (i++; i < items.length; i++) { + result.push(instantiator(items[i], mapper)); } + return result; } } - return items; } + return items; + } - function instantiateTypes(types: readonly Type[], mapper: TypeMapper): readonly Type[]; - function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined; - function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined { - return instantiateList(types, mapper, instantiateType); - } + function instantiateTypes(types: readonly Type[], mapper: TypeMapper): readonly Type[]; + function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined; + function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined { + return instantiateList(types, mapper, instantiateType); + } - function instantiateSignatures(signatures: readonly Signature[], mapper: TypeMapper): readonly Signature[] { - return instantiateList(signatures, mapper, instantiateSignature); - } + function instantiateSignatures(signatures: readonly Signature[], mapper: TypeMapper): readonly Signature[] { + return instantiateList(signatures, mapper, instantiateSignature); + } - function instantiateIndexInfos(indexInfos: readonly IndexInfo[], mapper: TypeMapper): readonly IndexInfo[] { - return instantiateList(indexInfos, mapper, instantiateIndexInfo); - } + function instantiateIndexInfos(indexInfos: readonly IndexInfo[], mapper: TypeMapper): readonly IndexInfo[] { + return instantiateList(indexInfos, mapper, instantiateIndexInfo); + } - function createTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { - return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : makeArrayTypeMapper(sources, targets); - } + function createTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { + return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : makeArrayTypeMapper(sources, targets); + } - function getMappedType(type: Type, mapper: TypeMapper): Type { - switch (mapper.kind) { - case TypeMapKind.Simple: - return type === mapper.source ? mapper.target : type; - case TypeMapKind.Array: - const sources = mapper.sources; - const targets = mapper.targets; - for (let i = 0; i < sources.length; i++) { - if (type === sources[i]) { - return targets ? targets[i] : anyType; - } + function getMappedType(type: Type, mapper: TypeMapper): Type { + switch (mapper.kind) { + case TypeMapKind.Simple: + return type === mapper.source ? mapper.target : type; + case TypeMapKind.Array: + const sources = mapper.sources; + const targets = mapper.targets; + for (let i = 0; i < sources.length; i++) { + if (type === sources[i]) { + return targets ? targets[i] : anyType; } - return type; - case TypeMapKind.Function: - return mapper.func(type); - case TypeMapKind.Composite: - case TypeMapKind.Merged: - const t1 = getMappedType(type, mapper.mapper1); - return t1 !== type && mapper.kind === TypeMapKind.Composite ? instantiateType(t1, mapper.mapper2) : getMappedType(t1, mapper.mapper2); - } + } + return type; + case TypeMapKind.Function: + return mapper.func(type); + case TypeMapKind.Composite: + case TypeMapKind.Merged: + const t1 = getMappedType(type, mapper.mapper1); + return t1 !== type && mapper.kind === TypeMapKind.Composite ? instantiateType(t1, mapper.mapper2) : getMappedType(t1, mapper.mapper2); } + } - function makeUnaryTypeMapper(source: Type, target: Type): TypeMapper { - return { kind: TypeMapKind.Simple, source, target }; - } + function makeUnaryTypeMapper(source: Type, target: Type): TypeMapper { + return { kind: TypeMapKind.Simple, source, target }; + } - function makeArrayTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { - return { kind: TypeMapKind.Array, sources, targets }; - } + function makeArrayTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { + return { kind: TypeMapKind.Array, sources, targets }; + } - function makeFunctionTypeMapper(func: (t: Type) => Type): TypeMapper { - return { kind: TypeMapKind.Function, func }; - } + function makeFunctionTypeMapper(func: (t: Type) => Type): TypeMapper { + return { kind: TypeMapKind.Function, func }; + } - function makeCompositeTypeMapper(kind: TypeMapKind.Composite | TypeMapKind.Merged, mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { - return { kind, mapper1, mapper2 }; - } + function makeCompositeTypeMapper(kind: TypeMapKind.Composite | TypeMapKind.Merged, mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { + return { kind, mapper1, mapper2 }; + } - function createTypeEraser(sources: readonly TypeParameter[]): TypeMapper { - return createTypeMapper(sources, /*targets*/ undefined); - } + function createTypeEraser(sources: readonly TypeParameter[]): TypeMapper { + return createTypeMapper(sources, /*targets*/ undefined); + } - /** - * Maps forward-references to later types parameters to the empty object type. - * This is used during inference when instantiating type parameter defaults. - */ - function createBackreferenceMapper(context: InferenceContext, index: number): TypeMapper { - return makeFunctionTypeMapper(t => findIndex(context.inferences, info => info.typeParameter === t) >= index ? unknownType : t); - } + /** + * Maps forward-references to later types parameters to the empty object type. + * This is used during inference when instantiating type parameter defaults. + */ + function createBackreferenceMapper(context: InferenceContext, index: number): TypeMapper { + return makeFunctionTypeMapper(t => findIndex(context.inferences, info => info.typeParameter === t) >= index ? unknownType : t); + } - function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { - return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper2; - } + function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { + return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper2; + } - function mergeTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { - return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Merged, mapper1, mapper2) : mapper2; - } + function mergeTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { + return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Merged, mapper1, mapper2) : mapper2; + } - function prependTypeMapping(source: Type, target: Type, mapper: TypeMapper | undefined) { - return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, makeUnaryTypeMapper(source, target), mapper); - } + function prependTypeMapping(source: Type, target: Type, mapper: TypeMapper | undefined) { + return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, makeUnaryTypeMapper(source, target), mapper); + } - function appendTypeMapping(mapper: TypeMapper | undefined, source: Type, target: Type) { - return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, mapper, makeUnaryTypeMapper(source, target)); - } + function appendTypeMapping(mapper: TypeMapper | undefined, source: Type, target: Type) { + return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, mapper, makeUnaryTypeMapper(source, target)); + } - function getRestrictiveTypeParameter(tp: TypeParameter) { - return tp.constraint === unknownType ? tp : tp.restrictiveInstantiation || ( - tp.restrictiveInstantiation = createTypeParameter(tp.symbol), - (tp.restrictiveInstantiation as TypeParameter).constraint = unknownType, - tp.restrictiveInstantiation - ); - } + function getRestrictiveTypeParameter(tp: TypeParameter) { + return tp.constraint === unknownType ? tp : tp.restrictiveInstantiation || ( + tp.restrictiveInstantiation = createTypeParameter(tp.symbol), + (tp.restrictiveInstantiation as TypeParameter).constraint = unknownType, + tp.restrictiveInstantiation + ); + } - function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter { - const result = createTypeParameter(typeParameter.symbol); - result.target = typeParameter; - return result; - } + function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter { + const result = createTypeParameter(typeParameter.symbol); + result.target = typeParameter; + return result; + } - function instantiateTypePredicate(predicate: TypePredicate, mapper: TypeMapper): TypePredicate { - return createTypePredicate(predicate.kind, predicate.parameterName, predicate.parameterIndex, instantiateType(predicate.type, mapper)); - } - - function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature { - let freshTypeParameters: TypeParameter[] | undefined; - if (signature.typeParameters && !eraseTypeParameters) { - // First create a fresh set of type parameters, then include a mapping from the old to the - // new type parameters in the mapper function. Finally store this mapper in the new type - // parameters such that we can use it when instantiating constraints. - freshTypeParameters = map(signature.typeParameters, cloneTypeParameter); - mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper); - for (const tp of freshTypeParameters) { - tp.mapper = mapper; - } - } - // Don't compute resolvedReturnType and resolvedTypePredicate now, - // because using `mapper` now could trigger inferences to become fixed. (See `createInferenceContext`.) - // See GH#17600. - const result = createSignature(signature.declaration, freshTypeParameters, - signature.thisParameter && instantiateSymbol(signature.thisParameter, mapper), - instantiateList(signature.parameters, mapper, instantiateSymbol), - /*resolvedReturnType*/ undefined, - /*resolvedTypePredicate*/ undefined, - signature.minArgumentCount, - signature.flags & SignatureFlags.PropagatingFlags); - result.target = signature; - result.mapper = mapper; - return result; - } + function instantiateTypePredicate(predicate: TypePredicate, mapper: TypeMapper): TypePredicate { + return createTypePredicate(predicate.kind, predicate.parameterName, predicate.parameterIndex, instantiateType(predicate.type, mapper)); + } - function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol { - const links = getSymbolLinks(symbol); - if (links.type && !couldContainTypeVariables(links.type)) { - // If the type of the symbol is already resolved, and if that type could not possibly - // be affected by instantiation, simply return the symbol itself. - return symbol; - } - if (getCheckFlags(symbol) & CheckFlags.Instantiated) { - // If symbol being instantiated is itself a instantiation, fetch the original target and combine the - // type mappers. This ensures that original type identities are properly preserved and that aliases - // always reference a non-aliases. - symbol = links.target!; - mapper = combineTypeMappers(links.mapper, mapper); - } - // Keep the flags from the symbol we're instantiating. Mark that is instantiated, and - // also transient so that we can just store data on it directly. - const result = createSymbol(symbol.flags, symbol.escapedName, CheckFlags.Instantiated | getCheckFlags(symbol) & (CheckFlags.Readonly | CheckFlags.Late | CheckFlags.OptionalParameter | CheckFlags.RestParameter)); - result.declarations = symbol.declarations; - result.parent = symbol.parent; - result.target = symbol; - result.mapper = mapper; - if (symbol.valueDeclaration) { - result.valueDeclaration = symbol.valueDeclaration; - } - if (links.nameType) { - result.nameType = links.nameType; - } - return result; + function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature { + let freshTypeParameters: TypeParameter[] | undefined; + if (signature.typeParameters && !eraseTypeParameters) { + // First create a fresh set of type parameters, then include a mapping from the old to the + // new type parameters in the mapper function. Finally store this mapper in the new type + // parameters such that we can use it when instantiating constraints. + freshTypeParameters = map(signature.typeParameters, cloneTypeParameter); + mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper); + for (const tp of freshTypeParameters) { + tp.mapper = mapper; + } + } + // Don't compute resolvedReturnType and resolvedTypePredicate now, + // because using `mapper` now could trigger inferences to become fixed. (See `createInferenceContext`.) + // See GH#17600. + const result = createSignature(signature.declaration, freshTypeParameters, + signature.thisParameter && instantiateSymbol(signature.thisParameter, mapper), + instantiateList(signature.parameters, mapper, instantiateSymbol), + /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, + signature.minArgumentCount, + signature.flags & SignatureFlags.PropagatingFlags); + result.target = signature; + result.mapper = mapper; + return result; + } + + function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol { + const links = getSymbolLinks(symbol); + if (links.type && !couldContainTypeVariables(links.type)) { + // If the type of the symbol is already resolved, and if that type could not possibly + // be affected by instantiation, simply return the symbol itself. + return symbol; } + if (getCheckFlags(symbol) & CheckFlags.Instantiated) { + // If symbol being instantiated is itself a instantiation, fetch the original target and combine the + // type mappers. This ensures that original type identities are properly preserved and that aliases + // always reference a non-aliases. + symbol = links.target!; + mapper = combineTypeMappers(links.mapper, mapper); + } + // Keep the flags from the symbol we're instantiating. Mark that is instantiated, and + // also transient so that we can just store data on it directly. + const result = createSymbol(symbol.flags, symbol.escapedName, CheckFlags.Instantiated | getCheckFlags(symbol) & (CheckFlags.Readonly | CheckFlags.Late | CheckFlags.OptionalParameter | CheckFlags.RestParameter)); + result.declarations = symbol.declarations; + result.parent = symbol.parent; + result.target = symbol; + result.mapper = mapper; + if (symbol.valueDeclaration) { + result.valueDeclaration = symbol.valueDeclaration; + } + if (links.nameType) { + result.nameType = links.nameType; + } + return result; + } - function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { - const declaration = type.objectFlags & ObjectFlags.Reference ? (type as TypeReference).node! : type.symbol.declarations![0]; - const links = getNodeLinks(declaration); - const target = type.objectFlags & ObjectFlags.Reference ? links.resolvedType! as DeferredTypeReference : - type.objectFlags & ObjectFlags.Instantiated ? type.target! : type; - let typeParameters = links.outerTypeParameters; - if (!typeParameters) { - // The first time an anonymous type is instantiated we compute and store a list of the type - // parameters that are in scope (and therefore potentially referenced). For type literals that - // aren't the right hand side of a generic type alias declaration we optimize by reducing the - // set of type parameters to those that are possibly referenced in the literal. - let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true); - if (isJSConstructor(declaration)) { - const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters); - outerTypeParameters = addRange(outerTypeParameters, templateTagParameters); - } - typeParameters = outerTypeParameters || emptyArray; - const allDeclarations = type.objectFlags & ObjectFlags.Reference ? [declaration] : type.symbol.declarations!; - typeParameters = (target.objectFlags & ObjectFlags.Reference || target.symbol.flags & SymbolFlags.Method || target.symbol.flags & SymbolFlags.TypeLiteral) && !target.aliasTypeArguments ? - filter(typeParameters, tp => some(allDeclarations, d => isTypeParameterPossiblyReferenced(tp, d))) : - typeParameters; - links.outerTypeParameters = typeParameters; - } - if (typeParameters.length) { - // We are instantiating an anonymous type that has one or more type parameters in scope. Apply the - // mapper to the type parameters to produce the effective list of type arguments, and compute the - // instantiation cache key from the type IDs of the type arguments. - const combinedMapper = combineTypeMappers(type.mapper, mapper); - const typeArguments = map(typeParameters, t => getMappedType(t, combinedMapper)); - const newAliasSymbol = aliasSymbol || type.aliasSymbol; - const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - const id = getTypeListId(typeArguments) + getAliasId(newAliasSymbol, newAliasTypeArguments); - if (!target.instantiations) { - target.instantiations = new Map(); - target.instantiations.set(getTypeListId(typeParameters) + getAliasId(target.aliasSymbol, target.aliasTypeArguments), target); - } - let result = target.instantiations.get(id); - if (!result) { - const newMapper = createTypeMapper(typeParameters, typeArguments); - result = target.objectFlags & ObjectFlags.Reference ? createDeferredTypeReference((type as DeferredTypeReference).target, (type as DeferredTypeReference).node, newMapper, newAliasSymbol, newAliasTypeArguments) : - target.objectFlags & ObjectFlags.Mapped ? instantiateMappedType(target as MappedType, newMapper, newAliasSymbol, newAliasTypeArguments) : - instantiateAnonymousType(target, newMapper, newAliasSymbol, newAliasTypeArguments); - target.instantiations.set(id, result); - } - return result; + function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { + const declaration = type.objectFlags & ObjectFlags.Reference ? (type as TypeReference).node! : type.symbol.declarations![0]; + const links = getNodeLinks(declaration); + const target = type.objectFlags & ObjectFlags.Reference ? links.resolvedType! as DeferredTypeReference : + type.objectFlags & ObjectFlags.Instantiated ? type.target! : type; + let typeParameters = links.outerTypeParameters; + if (!typeParameters) { + // The first time an anonymous type is instantiated we compute and store a list of the type + // parameters that are in scope (and therefore potentially referenced). For type literals that + // aren't the right hand side of a generic type alias declaration we optimize by reducing the + // set of type parameters to those that are possibly referenced in the literal. + let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true); + if (isJSConstructor(declaration)) { + const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters); + outerTypeParameters = addRange(outerTypeParameters, templateTagParameters); + } + typeParameters = outerTypeParameters || emptyArray; + const allDeclarations = type.objectFlags & ObjectFlags.Reference ? [declaration] : type.symbol.declarations!; + typeParameters = (target.objectFlags & ObjectFlags.Reference || target.symbol.flags & SymbolFlags.Method || target.symbol.flags & SymbolFlags.TypeLiteral) && !target.aliasTypeArguments ? + filter(typeParameters, tp => some(allDeclarations, d => isTypeParameterPossiblyReferenced(tp, d))) : + typeParameters; + links.outerTypeParameters = typeParameters; + } + if (typeParameters.length) { + // We are instantiating an anonymous type that has one or more type parameters in scope. Apply the + // mapper to the type parameters to produce the effective list of type arguments, and compute the + // instantiation cache key from the type IDs of the type arguments. + const combinedMapper = combineTypeMappers(type.mapper, mapper); + const typeArguments = map(typeParameters, t => getMappedType(t, combinedMapper)); + const newAliasSymbol = aliasSymbol || type.aliasSymbol; + const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + const id = getTypeListId(typeArguments) + getAliasId(newAliasSymbol, newAliasTypeArguments); + if (!target.instantiations) { + target.instantiations = new Map(); + target.instantiations.set(getTypeListId(typeParameters) + getAliasId(target.aliasSymbol, target.aliasTypeArguments), target); + } + let result = target.instantiations.get(id); + if (!result) { + const newMapper = createTypeMapper(typeParameters, typeArguments); + result = target.objectFlags & ObjectFlags.Reference ? createDeferredTypeReference((type as DeferredTypeReference).target, (type as DeferredTypeReference).node, newMapper, newAliasSymbol, newAliasTypeArguments) : + target.objectFlags & ObjectFlags.Mapped ? instantiateMappedType(target as MappedType, newMapper, newAliasSymbol, newAliasTypeArguments) : + instantiateAnonymousType(target, newMapper, newAliasSymbol, newAliasTypeArguments); + target.instantiations.set(id, result); } - return type; + return result; } + return type; + } - function maybeTypeParameterReference(node: Node) { - return !(node.parent.kind === SyntaxKind.TypeReference && (node.parent as TypeReferenceNode).typeArguments && node === (node.parent as TypeReferenceNode).typeName || - node.parent.kind === SyntaxKind.ImportType && (node.parent as ImportTypeNode).typeArguments && node === (node.parent as ImportTypeNode).qualifier); - } + function maybeTypeParameterReference(node: Node) { + return !(node.parent.kind === SyntaxKind.TypeReference && (node.parent as TypeReferenceNode).typeArguments && node === (node.parent as TypeReferenceNode).typeName || + node.parent.kind === SyntaxKind.ImportType && (node.parent as ImportTypeNode).typeArguments && node === (node.parent as ImportTypeNode).qualifier); + } - function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) { - // If the type parameter doesn't have exactly one declaration, if there are invening statement blocks - // between the node and the type parameter declaration, if the node contains actual references to the - // type parameter, or if the node contains type queries, we consider the type parameter possibly referenced. - if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) { - const container = tp.symbol.declarations[0].parent; - for (let n = node; n !== container; n = n.parent) { - if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n as ConditionalTypeNode).extendsType, containsReference)) { - return true; - } + function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) { + // If the type parameter doesn't have exactly one declaration, if there are invening statement blocks + // between the node and the type parameter declaration, if the node contains actual references to the + // type parameter, or if the node contains type queries, we consider the type parameter possibly referenced. + if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) { + const container = tp.symbol.declarations[0].parent; + for (let n = node; n !== container; n = n.parent) { + if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n as ConditionalTypeNode).extendsType, containsReference)) { + return true; } - return containsReference(node); } - return true; - function containsReference(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ThisType: - return !!tp.isThisType; - case SyntaxKind.Identifier: - return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) && - getTypeFromTypeNodeWorker(node as TypeNode) === tp; // use worker because we're looking for === equality - case SyntaxKind.TypeQuery: - return true; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return !(node as FunctionLikeDeclaration).type && !!(node as FunctionLikeDeclaration).body || - some((node as FunctionLikeDeclaration).typeParameters, containsReference) || - some((node as FunctionLikeDeclaration).parameters, containsReference) || - !!(node as FunctionLikeDeclaration).type && containsReference((node as FunctionLikeDeclaration).type!); - } - return !!forEachChild(node, containsReference); + return containsReference(node); + } + return true; + function containsReference(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ThisType: + return !!tp.isThisType; + case SyntaxKind.Identifier: + return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) && + getTypeFromTypeNodeWorker(node as TypeNode) === tp; // use worker because we're looking for === equality + case SyntaxKind.TypeQuery: + return true; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + return !(node as FunctionLikeDeclaration).type && !!(node as FunctionLikeDeclaration).body || + some((node as FunctionLikeDeclaration).typeParameters, containsReference) || + some((node as FunctionLikeDeclaration).parameters, containsReference) || + !!(node as FunctionLikeDeclaration).type && containsReference((node as FunctionLikeDeclaration).type!); } + return !!forEachChild(node, containsReference); } + } - function getHomomorphicTypeVariable(type: MappedType) { - const constraintType = getConstraintTypeFromMappedType(type); - if (constraintType.flags & TypeFlags.Index) { - const typeVariable = getActualTypeVariable((constraintType as IndexType).type); - if (typeVariable.flags & TypeFlags.TypeParameter) { - return typeVariable as TypeParameter; - } + function getHomomorphicTypeVariable(type: MappedType) { + const constraintType = getConstraintTypeFromMappedType(type); + if (constraintType.flags & TypeFlags.Index) { + const typeVariable = getActualTypeVariable((constraintType as IndexType).type); + if (typeVariable.flags & TypeFlags.TypeParameter) { + return typeVariable as TypeParameter; } - return undefined; } + return undefined; + } - function instantiateMappedType(type: MappedType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - // For a homomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping - // operation depends on T as follows: - // * If T is a primitive type no mapping is performed and the result is simply T. - // * If T is a union type we distribute the mapped type over the union. - // * If T is an array we map to an array where the element type has been transformed. - // * If T is a tuple we map to a tuple where the element types have been transformed. - // * Otherwise we map to an object type where the type of each property has been transformed. - // For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } | - // { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce - // { [P in keyof A]: X } | undefined. - const typeVariable = getHomomorphicTypeVariable(type); - if (typeVariable) { - const mappedTypeVariable = instantiateType(typeVariable, mapper); - if (typeVariable !== mappedTypeVariable) { - return mapTypeWithAlias(getReducedType(mappedTypeVariable), t => { - if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) { - if (!type.declaration.nameType) { - let constraint; - if (isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 && - (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, or(isArrayType, isTupleType))) { - return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper)); - } - if (isGenericTupleType(t)) { - return instantiateMappedGenericTupleType(t, type, typeVariable, mapper); - } - if (isTupleType(t)) { - return instantiateMappedTupleType(t, type, prependTypeMapping(typeVariable, t, mapper)); - } - } - return instantiateAnonymousType(type, prependTypeMapping(typeVariable, t, mapper)); - } - return t; - }, aliasSymbol, aliasTypeArguments); - } + function instantiateMappedType(type: MappedType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + // For a homomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping + // operation depends on T as follows: + // * If T is a primitive type no mapping is performed and the result is simply T. + // * If T is a union type we distribute the mapped type over the union. + // * If T is an array we map to an array where the element type has been transformed. + // * If T is a tuple we map to a tuple where the element types have been transformed. + // * Otherwise we map to an object type where the type of each property has been transformed. + // For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } | + // { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce + // { [P in keyof A]: X } | undefined. + const typeVariable = getHomomorphicTypeVariable(type); + if (typeVariable) { + const mappedTypeVariable = instantiateType(typeVariable, mapper); + if (typeVariable !== mappedTypeVariable) { + return mapTypeWithAlias(getReducedType(mappedTypeVariable), t => { + if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) { + if (!type.declaration.nameType) { + let constraint; + if (isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 && + (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, or(isArrayType, isTupleType))) { + return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper)); + } + if (isGenericTupleType(t)) { + return instantiateMappedGenericTupleType(t, type, typeVariable, mapper); + } + if (isTupleType(t)) { + return instantiateMappedTupleType(t, type, prependTypeMapping(typeVariable, t, mapper)); + } + } + return instantiateAnonymousType(type, prependTypeMapping(typeVariable, t, mapper)); + } + return t; + }, aliasSymbol, aliasTypeArguments); } - // If the constraint type of the instantiation is the wildcard type, return the wildcard type. - return instantiateType(getConstraintTypeFromMappedType(type), mapper) === wildcardType ? wildcardType : instantiateAnonymousType(type, mapper, aliasSymbol, aliasTypeArguments); } + // If the constraint type of the instantiation is the wildcard type, return the wildcard type. + return instantiateType(getConstraintTypeFromMappedType(type), mapper) === wildcardType ? wildcardType : instantiateAnonymousType(type, mapper, aliasSymbol, aliasTypeArguments); + } - function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) { - return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state; - } + function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) { + return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state; + } - function instantiateMappedGenericTupleType(tupleType: TupleTypeReference, mappedType: MappedType, typeVariable: TypeVariable, mapper: TypeMapper) { - // When a tuple type is generic (i.e. when it contains variadic elements), we want to eagerly map the - // non-generic elements and defer mapping the generic elements. In order to facilitate this, we transform - // M<[A, B?, ...T, ...C[]] into [...M<[A]>, ...M<[B?]>, ...M, ...M] and then rely on tuple type - // normalization to resolve the non-generic parts of the resulting tuple. - const elementFlags = tupleType.target.elementFlags; - const elementTypes = map(getTypeArguments(tupleType), (t, i) => { - const singleton = elementFlags[i] & ElementFlags.Variadic ? t : - elementFlags[i] & ElementFlags.Rest ? createArrayType(t) : - createTupleType([t], [elementFlags[i]]); - // The singleton is never a generic tuple type, so it is safe to recurse here. - return instantiateMappedType(mappedType, prependTypeMapping(typeVariable, singleton, mapper)); - }); - const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, getMappedTypeModifiers(mappedType)); - return createTupleType(elementTypes, map(elementTypes, _ => ElementFlags.Variadic), newReadonly); - } - - function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) { - const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper); - return isErrorType(elementType) ? errorType : - createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType))); - } - - function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) { - const elementFlags = tupleType.target.elementFlags; - const elementTypes = map(getTypeArguments(tupleType), (_, i) => - instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(elementFlags[i] & ElementFlags.Optional), mapper)); - const modifiers = getMappedTypeModifiers(mappedType); - const newTupleModifiers = modifiers & MappedTypeModifiers.IncludeOptional ? map(elementFlags, f => f & ElementFlags.Required ? ElementFlags.Optional : f) : - modifiers & MappedTypeModifiers.ExcludeOptional ? map(elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : - elementFlags; - const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, modifiers); - return contains(elementTypes, errorType) ? errorType : - createTupleType(elementTypes, newTupleModifiers, newReadonly, tupleType.target.labeledElementDeclarations); - } - - function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) { - const templateMapper = appendTypeMapping(mapper, getTypeParameterFromMappedType(type), key); - const propType = instantiateType(getTemplateTypeFromMappedType(type.target as MappedType || type), templateMapper); - const modifiers = getMappedTypeModifiers(type); - return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType, /*isProperty*/ true) : - strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : - propType; - } + function instantiateMappedGenericTupleType(tupleType: TupleTypeReference, mappedType: MappedType, typeVariable: TypeVariable, mapper: TypeMapper) { + // When a tuple type is generic (i.e. when it contains variadic elements), we want to eagerly map the + // non-generic elements and defer mapping the generic elements. In order to facilitate this, we transform + // M<[A, B?, ...T, ...C[]] into [...M<[A]>, ...M<[B?]>, ...M, ...M] and then rely on tuple type + // normalization to resolve the non-generic parts of the resulting tuple. + const elementFlags = tupleType.target.elementFlags; + const elementTypes = map(getTypeArguments(tupleType), (t, i) => { + const singleton = elementFlags[i] & ElementFlags.Variadic ? t : + elementFlags[i] & ElementFlags.Rest ? createArrayType(t) : + createTupleType([t], [elementFlags[i]]); + // The singleton is never a generic tuple type, so it is safe to recurse here. + return instantiateMappedType(mappedType, prependTypeMapping(typeVariable, singleton, mapper)); + }); + const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, getMappedTypeModifiers(mappedType)); + return createTupleType(elementTypes, map(elementTypes, _ => ElementFlags.Variadic), newReadonly); + } + + function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) { + const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper); + return isErrorType(elementType) ? errorType : + createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType))); + } + + function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) { + const elementFlags = tupleType.target.elementFlags; + const elementTypes = map(getTypeArguments(tupleType), (_, i) => + instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(elementFlags[i] & ElementFlags.Optional), mapper)); + const modifiers = getMappedTypeModifiers(mappedType); + const newTupleModifiers = modifiers & MappedTypeModifiers.IncludeOptional ? map(elementFlags, f => f & ElementFlags.Required ? ElementFlags.Optional : f) : + modifiers & MappedTypeModifiers.ExcludeOptional ? map(elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : + elementFlags; + const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, modifiers); + return contains(elementTypes, errorType) ? errorType : + createTupleType(elementTypes, newTupleModifiers, newReadonly, tupleType.target.labeledElementDeclarations); + } + + function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) { + const templateMapper = appendTypeMapping(mapper, getTypeParameterFromMappedType(type), key); + const propType = instantiateType(getTemplateTypeFromMappedType(type.target as MappedType || type), templateMapper); + const modifiers = getMappedTypeModifiers(type); + return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType, /*isProperty*/ true) : + strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : + propType; + } - function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): AnonymousType { - const result = createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol) as AnonymousType; - if (type.objectFlags & ObjectFlags.Mapped) { - (result as MappedType).declaration = (type as MappedType).declaration; - // C.f. instantiateSignature - const origTypeParameter = getTypeParameterFromMappedType(type as MappedType); - const freshTypeParameter = cloneTypeParameter(origTypeParameter); - (result as MappedType).typeParameter = freshTypeParameter; - mapper = combineTypeMappers(makeUnaryTypeMapper(origTypeParameter, freshTypeParameter), mapper); - freshTypeParameter.mapper = mapper; + function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): AnonymousType { + const result = createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol) as AnonymousType; + if (type.objectFlags & ObjectFlags.Mapped) { + (result as MappedType).declaration = (type as MappedType).declaration; + // C.f. instantiateSignature + const origTypeParameter = getTypeParameterFromMappedType(type as MappedType); + const freshTypeParameter = cloneTypeParameter(origTypeParameter); + (result as MappedType).typeParameter = freshTypeParameter; + mapper = combineTypeMappers(makeUnaryTypeMapper(origTypeParameter, freshTypeParameter), mapper); + freshTypeParameter.mapper = mapper; + } + result.target = type; + result.mapper = mapper; + result.aliasSymbol = aliasSymbol || type.aliasSymbol; + result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + return result; + } + + function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + const root = type.root; + if (root.outerTypeParameters) { + // We are instantiating a conditional type that has one or more type parameters in scope. Apply the + // mapper to the type parameters to produce the effective list of type arguments, and compute the + // instantiation cache key from the type IDs of the type arguments. + const typeArguments = map(root.outerTypeParameters, t => getMappedType(t, mapper)); + const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); + let result = root.instantiations!.get(id); + if (!result) { + const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); + const checkType = root.checkType; + const distributionType = root.isDistributive ? getMappedType(checkType, newMapper) : undefined; + // Distributive conditional types are distributed over union types. For example, when the + // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the + // result is (A extends U ? X : Y) | (B extends U ? X : Y). + result = distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never) ? + mapTypeWithAlias(distributionType, t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper)), aliasSymbol, aliasTypeArguments) : + getConditionalType(root, newMapper, aliasSymbol, aliasTypeArguments); + root.instantiations!.set(id, result); } - result.target = type; - result.mapper = mapper; - result.aliasSymbol = aliasSymbol || type.aliasSymbol; - result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); return result; } + return type; + } - function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - const root = type.root; - if (root.outerTypeParameters) { - // We are instantiating a conditional type that has one or more type parameters in scope. Apply the - // mapper to the type parameters to produce the effective list of type arguments, and compute the - // instantiation cache key from the type IDs of the type arguments. - const typeArguments = map(root.outerTypeParameters, t => getMappedType(t, mapper)); - const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); - let result = root.instantiations!.get(id); - if (!result) { - const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); - const checkType = root.checkType; - const distributionType = root.isDistributive ? getMappedType(checkType, newMapper) : undefined; - // Distributive conditional types are distributed over union types. For example, when the - // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the - // result is (A extends U ? X : Y) | (B extends U ? X : Y). - result = distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never) ? - mapTypeWithAlias(distributionType, t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper)), aliasSymbol, aliasTypeArguments) : - getConditionalType(root, newMapper, aliasSymbol, aliasTypeArguments); - root.instantiations!.set(id, result); - } - return result; - } + function instantiateType(type: Type, mapper: TypeMapper | undefined): Type; + function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined; + function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined { + return type && mapper ? instantiateTypeWithAlias(type, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : type; + } + + function instantiateTypeWithAlias(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { + if (!couldContainTypeVariables(type)) { return type; } - - function instantiateType(type: Type, mapper: TypeMapper | undefined): Type; - function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined; - function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined { - return type && mapper ? instantiateTypeWithAlias(type, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : type; + if (instantiationDepth === 100 || instantiationCount >= 5000000) { + // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement + // or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types + // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. + tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); + error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); + return errorType; } + totalInstantiationCount++; + instantiationCount++; + instantiationDepth++; + const result = instantiateTypeWorker(type, mapper, aliasSymbol, aliasTypeArguments); + instantiationDepth--; + return result; + } - function instantiateTypeWithAlias(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { - if (!couldContainTypeVariables(type)) { - return type; - } - if (instantiationDepth === 100 || instantiationCount >= 5000000) { - // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement - // or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types - // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. - tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); - error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); - return errorType; - } - totalInstantiationCount++; - instantiationCount++; - instantiationDepth++; - const result = instantiateTypeWorker(type, mapper, aliasSymbol, aliasTypeArguments); - instantiationDepth--; - return result; + function instantiateTypeWorker(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { + const flags = type.flags; + if (flags & TypeFlags.TypeParameter) { + return getMappedType(type, mapper); } - - function instantiateTypeWorker(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { - const flags = type.flags; - if (flags & TypeFlags.TypeParameter) { - return getMappedType(type, mapper); - } - if (flags & TypeFlags.Object) { - const objectFlags = (type as ObjectType).objectFlags; - if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - if (objectFlags & ObjectFlags.Reference && !(type as TypeReference).node) { - const resolvedTypeArguments = (type as TypeReference).resolvedTypeArguments; - const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); - return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type as TypeReference).target, newTypeArguments) : type; - } - if (objectFlags & ObjectFlags.ReverseMapped) { - return instantiateReverseMappedType(type as ReverseMappedType, mapper); - } - return getObjectTypeInstantiation(type as TypeReference | AnonymousType | MappedType, mapper, aliasSymbol, aliasTypeArguments); - } - return type; - } - if (flags & TypeFlags.UnionOrIntersection) { - const origin = type.flags & TypeFlags.Union ? (type as UnionType).origin : undefined; - const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (origin as UnionOrIntersectionType).types : (type as UnionOrIntersectionType).types; - const newTypes = instantiateTypes(types, mapper); - if (newTypes === types && aliasSymbol === type.aliasSymbol) { - return type; - } - const newAliasSymbol = aliasSymbol || type.aliasSymbol; - const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ? - getIntersectionType(newTypes, newAliasSymbol, newAliasTypeArguments) : - getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments); - } - if (flags & TypeFlags.Index) { - return getIndexType(instantiateType((type as IndexType).type, mapper)); - } - if (flags & TypeFlags.TemplateLiteral) { - return getTemplateLiteralType((type as TemplateLiteralType).texts, instantiateTypes((type as TemplateLiteralType).types, mapper)); - } - if (flags & TypeFlags.StringMapping) { - return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper)); - } - if (flags & TypeFlags.IndexedAccess) { - const newAliasSymbol = aliasSymbol || type.aliasSymbol; - const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - return getIndexedAccessType(instantiateType((type as IndexedAccessType).objectType, mapper), instantiateType((type as IndexedAccessType).indexType, mapper), (type as IndexedAccessType).accessFlags, /*accessNode*/ undefined, newAliasSymbol, newAliasTypeArguments); - } - if (flags & TypeFlags.Conditional) { - return getConditionalTypeInstantiation(type as ConditionalType, combineTypeMappers((type as ConditionalType).mapper, mapper), aliasSymbol, aliasTypeArguments); - } - if (flags & TypeFlags.Substitution) { - const maybeVariable = instantiateType((type as SubstitutionType).baseType, mapper); - if (maybeVariable.flags & TypeFlags.TypeVariable) { - return getSubstitutionType(maybeVariable as TypeVariable, instantiateType((type as SubstitutionType).substitute, mapper)); + if (flags & TypeFlags.Object) { + const objectFlags = (type as ObjectType).objectFlags; + if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + if (objectFlags & ObjectFlags.Reference && !(type as TypeReference).node) { + const resolvedTypeArguments = (type as TypeReference).resolvedTypeArguments; + const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); + return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type as TypeReference).target, newTypeArguments) : type; } - else { - const sub = instantiateType((type as SubstitutionType).substitute, mapper); - if (sub.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(maybeVariable), getRestrictiveInstantiation(sub))) { - return maybeVariable; - } - return sub; + if (objectFlags & ObjectFlags.ReverseMapped) { + return instantiateReverseMappedType(type as ReverseMappedType, mapper); } + return getObjectTypeInstantiation(type as TypeReference | AnonymousType | MappedType, mapper, aliasSymbol, aliasTypeArguments); } return type; } - - function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) { - const innerMappedType = instantiateType(type.mappedType, mapper); - if (!(getObjectFlags(innerMappedType) & ObjectFlags.Mapped)) { - return type; - } - const innerIndexType = instantiateType(type.constraintType, mapper); - if (!(innerIndexType.flags & TypeFlags.Index)) { + if (flags & TypeFlags.UnionOrIntersection) { + const origin = type.flags & TypeFlags.Union ? (type as UnionType).origin : undefined; + const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (origin as UnionOrIntersectionType).types : (type as UnionOrIntersectionType).types; + const newTypes = instantiateTypes(types, mapper); + if (newTypes === types && aliasSymbol === type.aliasSymbol) { return type; } - const instantiated = inferTypeForHomomorphicMappedType( - instantiateType(type.source, mapper), - innerMappedType as MappedType, - innerIndexType as IndexType - ); - if (instantiated) { - return instantiated; - } - return type; // Nested invocation of `inferTypeForHomomorphicMappedType` or the `source` instantiated into something unmappable + const newAliasSymbol = aliasSymbol || type.aliasSymbol; + const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ? + getIntersectionType(newTypes, newAliasSymbol, newAliasTypeArguments) : + getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments); } - - function getPermissiveInstantiation(type: Type) { - return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : - type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper)); + if (flags & TypeFlags.Index) { + return getIndexType(instantiateType((type as IndexType).type, mapper)); } - - function getRestrictiveInstantiation(type: Type) { - if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) { - return type; - } - if (type.restrictiveInstantiation) { - return type.restrictiveInstantiation; - } - type.restrictiveInstantiation = instantiateType(type, restrictiveMapper); - // We set the following so we don't attempt to set the restrictive instance of a restrictive instance - // which is redundant - we'll produce new type identities, but all type params have already been mapped. - // This also gives us a way to detect restrictive instances upon comparisons and _disable_ the "distributeive constraint" - // assignability check for them, which is distinctly unsafe, as once you have a restrctive instance, all the type parameters - // are constrained to `unknown` and produce tons of false positives/negatives! - type.restrictiveInstantiation.restrictiveInstantiation = type.restrictiveInstantiation; - return type.restrictiveInstantiation; + if (flags & TypeFlags.TemplateLiteral) { + return getTemplateLiteralType((type as TemplateLiteralType).texts, instantiateTypes((type as TemplateLiteralType).types, mapper)); } - - function instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper) { - return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration); + if (flags & TypeFlags.StringMapping) { + return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper)); } - - // Returns true if the given expression contains (at any level of nesting) a function or arrow expression - // that is subject to contextual typing. - function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - switch (node.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type - return isContextSensitiveFunctionLikeDeclaration(node as FunctionExpression | ArrowFunction | MethodDeclaration); - case SyntaxKind.ObjectLiteralExpression: - return some((node as ObjectLiteralExpression).properties, isContextSensitive); - case SyntaxKind.ArrayLiteralExpression: - return some((node as ArrayLiteralExpression).elements, isContextSensitive); - case SyntaxKind.ConditionalExpression: - return isContextSensitive((node as ConditionalExpression).whenTrue) || - isContextSensitive((node as ConditionalExpression).whenFalse); - case SyntaxKind.BinaryExpression: - return ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken || (node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken) && - (isContextSensitive((node as BinaryExpression).left) || isContextSensitive((node as BinaryExpression).right)); - case SyntaxKind.PropertyAssignment: - return isContextSensitive((node as PropertyAssignment).initializer); - case SyntaxKind.ParenthesizedExpression: - return isContextSensitive((node as ParenthesizedExpression).expression); - case SyntaxKind.JsxAttributes: - return some((node as JsxAttributes).properties, isContextSensitive) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, isContextSensitive); - case SyntaxKind.JsxAttribute: { - // If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive. - const { initializer } = node as JsxAttribute; - return !!initializer && isContextSensitive(initializer); - } - case SyntaxKind.JsxExpression: { - // It is possible to that node.expression is undefined (e.g

) - const { expression } = node as JsxExpression; - return !!expression && isContextSensitive(expression); + if (flags & TypeFlags.IndexedAccess) { + const newAliasSymbol = aliasSymbol || type.aliasSymbol; + const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + return getIndexedAccessType(instantiateType((type as IndexedAccessType).objectType, mapper), instantiateType((type as IndexedAccessType).indexType, mapper), (type as IndexedAccessType).accessFlags, /*accessNode*/ undefined, newAliasSymbol, newAliasTypeArguments); + } + if (flags & TypeFlags.Conditional) { + return getConditionalTypeInstantiation(type as ConditionalType, combineTypeMappers((type as ConditionalType).mapper, mapper), aliasSymbol, aliasTypeArguments); + } + if (flags & TypeFlags.Substitution) { + const maybeVariable = instantiateType((type as SubstitutionType).baseType, mapper); + if (maybeVariable.flags & TypeFlags.TypeVariable) { + return getSubstitutionType(maybeVariable as TypeVariable, instantiateType((type as SubstitutionType).substitute, mapper)); + } + else { + const sub = instantiateType((type as SubstitutionType).substitute, mapper); + if (sub.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(maybeVariable), getRestrictiveInstantiation(sub))) { + return maybeVariable; } + return sub; } - - return false; } + return type; + } - function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { - return (!isFunctionDeclaration(node) || isInJSFile(node) && !!getTypeForDeclarationFromJSDocComment(node)) && - (hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node)); + function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) { + const innerMappedType = instantiateType(type.mappedType, mapper); + if (!(getObjectFlags(innerMappedType) & ObjectFlags.Mapped)) { + return type; } - - function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) { - // TODO(anhans): A block should be context-sensitive if it has a context-sensitive return value. - return !node.typeParameters && !getEffectiveReturnTypeNode(node) && !!node.body && node.body.kind !== SyntaxKind.Block && isContextSensitive(node.body); + const innerIndexType = instantiateType(type.constraintType, mapper); + if (!(innerIndexType.flags & TypeFlags.Index)) { + return type; } - - function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { - return (isInJSFile(func) && isFunctionDeclaration(func) || isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && - isContextSensitiveFunctionLikeDeclaration(func); + const instantiated = inferTypeForHomomorphicMappedType( + instantiateType(type.source, mapper), + innerMappedType as MappedType, + innerIndexType as IndexType + ); + if (instantiated) { + return instantiated; } + return type; // Nested invocation of `inferTypeForHomomorphicMappedType` or the `source` instantiated into something unmappable + } - function getTypeWithoutSignatures(type: Type): Type { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - if (resolved.constructSignatures.length || resolved.callSignatures.length) { - const result = createObjectType(ObjectFlags.Anonymous, type.symbol); - result.members = resolved.members; - result.properties = resolved.properties; - result.callSignatures = emptyArray; - result.constructSignatures = emptyArray; - result.indexInfos = emptyArray; - return result; - } - } - else if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(map((type as IntersectionType).types, getTypeWithoutSignatures)); - } + function getPermissiveInstantiation(type: Type) { + return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : + type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper)); + } + + function getRestrictiveInstantiation(type: Type) { + if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) { return type; } - - // TYPE CHECKING - - function isTypeIdenticalTo(source: Type, target: Type): boolean { - return isTypeRelatedTo(source, target, identityRelation); + if (type.restrictiveInstantiation) { + return type.restrictiveInstantiation; } + type.restrictiveInstantiation = instantiateType(type, restrictiveMapper); + // We set the following so we don't attempt to set the restrictive instance of a restrictive instance + // which is redundant - we'll produce new type identities, but all type params have already been mapped. + // This also gives us a way to detect restrictive instances upon comparisons and _disable_ the "distributeive constraint" + // assignability check for them, which is distinctly unsafe, as once you have a restrctive instance, all the type parameters + // are constrained to `unknown` and produce tons of false positives/negatives! + type.restrictiveInstantiation.restrictiveInstantiation = type.restrictiveInstantiation; + return type.restrictiveInstantiation; + } - function compareTypesIdentical(source: Type, target: Type): Ternary { - return isTypeRelatedTo(source, target, identityRelation) ? Ternary.True : Ternary.False; - } + function instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper) { + return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration); + } - function compareTypesAssignable(source: Type, target: Type): Ternary { - return isTypeRelatedTo(source, target, assignableRelation) ? Ternary.True : Ternary.False; - } + // Returns true if the given expression contains (at any level of nesting) a function or arrow expression + // that is subject to contextual typing. + function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + switch (node.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type + return isContextSensitiveFunctionLikeDeclaration(node as FunctionExpression | ArrowFunction | MethodDeclaration); + case SyntaxKind.ObjectLiteralExpression: + return some((node as ObjectLiteralExpression).properties, isContextSensitive); + case SyntaxKind.ArrayLiteralExpression: + return some((node as ArrayLiteralExpression).elements, isContextSensitive); + case SyntaxKind.ConditionalExpression: + return isContextSensitive((node as ConditionalExpression).whenTrue) || + isContextSensitive((node as ConditionalExpression).whenFalse); + case SyntaxKind.BinaryExpression: + return ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken || (node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken) && + (isContextSensitive((node as BinaryExpression).left) || isContextSensitive((node as BinaryExpression).right)); + case SyntaxKind.PropertyAssignment: + return isContextSensitive((node as PropertyAssignment).initializer); + case SyntaxKind.ParenthesizedExpression: + return isContextSensitive((node as ParenthesizedExpression).expression); + case SyntaxKind.JsxAttributes: + return some((node as JsxAttributes).properties, isContextSensitive) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, isContextSensitive); + case SyntaxKind.JsxAttribute: { + // If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive. + const { initializer } = node as JsxAttribute; + return !!initializer && isContextSensitive(initializer); + } + case SyntaxKind.JsxExpression: { + // It is possible to that node.expression is undefined (e.g
) + const { expression } = node as JsxExpression; + return !!expression && isContextSensitive(expression); + } + } + + return false; + } - function compareTypesSubtypeOf(source: Type, target: Type): Ternary { - return isTypeRelatedTo(source, target, subtypeRelation) ? Ternary.True : Ternary.False; - } + function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { + return (!isFunctionDeclaration(node) || isInJSFile(node) && !!getTypeForDeclarationFromJSDocComment(node)) && + (hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node)); + } - function isTypeSubtypeOf(source: Type, target: Type): boolean { - return isTypeRelatedTo(source, target, subtypeRelation); - } + function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) { + // TODO(anhans): A block should be context-sensitive if it has a context-sensitive return value. + return !node.typeParameters && !getEffectiveReturnTypeNode(node) && !!node.body && node.body.kind !== SyntaxKind.Block && isContextSensitive(node.body); + } - function isTypeAssignableTo(source: Type, target: Type): boolean { - return isTypeRelatedTo(source, target, assignableRelation); - } + function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { + return (isInJSFile(func) && isFunctionDeclaration(func) || isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && + isContextSensitiveFunctionLikeDeclaration(func); + } - // An object type S is considered to be derived from an object type T if - // S is a union type and every constituent of S is derived from T, - // T is a union type and S is derived from at least one constituent of T, or - // S is a type variable with a base constraint that is derived from T, - // T is one of the global types Object and Function and S is a subtype of T, or - // T occurs directly or indirectly in an 'extends' clause of S. - // Note that this check ignores type parameters and only considers the - // inheritance hierarchy. - function isTypeDerivedFrom(source: Type, target: Type): boolean { - return source.flags & TypeFlags.Union ? every((source as UnionType).types, t => isTypeDerivedFrom(t, target)) : - target.flags & TypeFlags.Union ? some((target as UnionType).types, t => isTypeDerivedFrom(source, t)) : - source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) : - target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) : - target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType(source as ObjectType) : - hasBaseType(source, getTargetType(target)) || (isArrayType(target) && !isReadonlyArrayType(target) && isTypeDerivedFrom(source, globalReadonlyArrayType)); + function getTypeWithoutSignatures(type: Type): Type { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + if (resolved.constructSignatures.length || resolved.callSignatures.length) { + const result = createObjectType(ObjectFlags.Anonymous, type.symbol); + result.members = resolved.members; + result.properties = resolved.properties; + result.callSignatures = emptyArray; + result.constructSignatures = emptyArray; + result.indexInfos = emptyArray; + return result; + } } - - /** - * This is *not* a bi-directional relationship. - * If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'. - * - * A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T. - * It is used to check following cases: - * - the types of the left and right sides of equality/inequality operators (`===`, `!==`, `==`, `!=`). - * - the types of `case` clause expressions and their respective `switch` expressions. - * - the type of an expression in a type assertion with the type being asserted. - */ - function isTypeComparableTo(source: Type, target: Type): boolean { - return isTypeRelatedTo(source, target, comparableRelation); + else if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(map((type as IntersectionType).types, getTypeWithoutSignatures)); } + return type; + } - function areTypesComparable(type1: Type, type2: Type): boolean { - return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1); - } + // TYPE CHECKING - function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputObject?: { errors?: Diagnostic[] }): boolean { - return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain, errorOutputObject); - } + function isTypeIdenticalTo(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, identityRelation); + } - /** - * Like `checkTypeAssignableTo`, but if it would issue an error, instead performs structural comparisons of the types using the given expression node to - * attempt to issue more specific errors on, for example, specific object literal properties or tuple members. - */ - function checkTypeAssignableToAndOptionallyElaborate(source: Type, target: Type, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { - return checkTypeRelatedToAndOptionallyElaborate(source, target, assignableRelation, errorNode, expr, headMessage, containingMessageChain, /*errorOutputContainer*/ undefined); - } - - function checkTypeRelatedToAndOptionallyElaborate( - source: Type, - target: Type, - relation: ESMap, - errorNode: Node | undefined, - expr: Expression | undefined, - headMessage: DiagnosticMessage | undefined, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ): boolean { - if (isTypeRelatedTo(source, target, relation)) return true; - if (!errorNode || !elaborateError(expr, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { - return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer); - } - return false; - } + function compareTypesIdentical(source: Type, target: Type): Ternary { + return isTypeRelatedTo(source, target, identityRelation) ? Ternary.True : Ternary.False; + } + + function compareTypesAssignable(source: Type, target: Type): Ternary { + return isTypeRelatedTo(source, target, assignableRelation) ? Ternary.True : Ternary.False; + } + + function compareTypesSubtypeOf(source: Type, target: Type): Ternary { + return isTypeRelatedTo(source, target, subtypeRelation) ? Ternary.True : Ternary.False; + } + + function isTypeSubtypeOf(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, subtypeRelation); + } + + function isTypeAssignableTo(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, assignableRelation); + } + + // An object type S is considered to be derived from an object type T if + // S is a union type and every constituent of S is derived from T, + // T is a union type and S is derived from at least one constituent of T, or + // S is a type variable with a base constraint that is derived from T, + // T is one of the global types Object and Function and S is a subtype of T, or + // T occurs directly or indirectly in an 'extends' clause of S. + // Note that this check ignores type parameters and only considers the + // inheritance hierarchy. + function isTypeDerivedFrom(source: Type, target: Type): boolean { + return source.flags & TypeFlags.Union ? every((source as UnionType).types, t => isTypeDerivedFrom(t, target)) : + target.flags & TypeFlags.Union ? some((target as UnionType).types, t => isTypeDerivedFrom(source, t)) : + source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) : + target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) : + target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType(source as ObjectType) : + hasBaseType(source, getTargetType(target)) || (isArrayType(target) && !isReadonlyArrayType(target) && isTypeDerivedFrom(source, globalReadonlyArrayType)); + } - function isOrHasGenericConditional(type: Type): boolean { - return !!(type.flags & TypeFlags.Conditional || (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isOrHasGenericConditional))); + /** + * This is *not* a bi-directional relationship. + * If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'. + * + * A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T. + * It is used to check following cases: + * - the types of the left and right sides of equality/inequality operators (`===`, `!==`, `==`, `!=`). + * - the types of `case` clause expressions and their respective `switch` expressions. + * - the type of an expression in a type assertion with the type being asserted. + */ + function isTypeComparableTo(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, comparableRelation); + } + + function areTypesComparable(type1: Type, type2: Type): boolean { + return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1); + } + + function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputObject?: { errors?: Diagnostic[] }): boolean { + return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain, errorOutputObject); + } + + /** + * Like `checkTypeAssignableTo`, but if it would issue an error, instead performs structural comparisons of the types using the given expression node to + * attempt to issue more specific errors on, for example, specific object literal properties or tuple members. + */ + function checkTypeAssignableToAndOptionallyElaborate(source: Type, target: Type, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { + return checkTypeRelatedToAndOptionallyElaborate(source, target, assignableRelation, errorNode, expr, headMessage, containingMessageChain, /*errorOutputContainer*/ undefined); + } + + function checkTypeRelatedToAndOptionallyElaborate( + source: Type, + target: Type, + relation: ESMap, + errorNode: Node | undefined, + expr: Expression | undefined, + headMessage: DiagnosticMessage | undefined, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ): boolean { + if (isTypeRelatedTo(source, target, relation)) return true; + if (!errorNode || !elaborateError(expr, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { + return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer); + } + return false; + } + + function isOrHasGenericConditional(type: Type): boolean { + return !!(type.flags & TypeFlags.Conditional || (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isOrHasGenericConditional))); + } + + function elaborateError( + node: Expression | undefined, + source: Type, + target: Type, + relation: ESMap, + headMessage: DiagnosticMessage | undefined, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ): boolean { + if (!node || isOrHasGenericConditional(target)) return false; + if (!checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined) + && elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { + return true; } + switch (node.kind) { + case SyntaxKind.JsxExpression: + case SyntaxKind.ParenthesizedExpression: + return elaborateError((node as ParenthesizedExpression | JsxExpression).expression, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); + case SyntaxKind.BinaryExpression: + switch ((node as BinaryExpression).operatorToken.kind) { + case SyntaxKind.EqualsToken: + case SyntaxKind.CommaToken: + return elaborateError((node as BinaryExpression).right, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); + } + break; + case SyntaxKind.ObjectLiteralExpression: + return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); + case SyntaxKind.ArrayLiteralExpression: + return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); + case SyntaxKind.JsxAttributes: + return elaborateJsxComponents(node as JsxAttributes, source, target, relation, containingMessageChain, errorOutputContainer); + case SyntaxKind.ArrowFunction: + return elaborateArrowFunction(node as ArrowFunction, source, target, relation, containingMessageChain, errorOutputContainer); + } + return false; + } - function elaborateError( - node: Expression | undefined, - source: Type, - target: Type, - relation: ESMap, - headMessage: DiagnosticMessage | undefined, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ): boolean { - if (!node || isOrHasGenericConditional(target)) return false; - if (!checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined) - && elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { + function elaborateDidYouMeanToCallOrConstruct( + node: Expression, + source: Type, + target: Type, + relation: ESMap, + headMessage: DiagnosticMessage | undefined, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ): boolean { + const callSignatures = getSignaturesOfType(source, SignatureKind.Call); + const constructSignatures = getSignaturesOfType(source, SignatureKind.Construct); + for (const signatures of [constructSignatures, callSignatures]) { + if (some(signatures, s => { + const returnType = getReturnTypeOfSignature(s); + return !(returnType.flags & (TypeFlags.Any | TypeFlags.Never)) && checkTypeRelatedTo(returnType, target, relation, /*errorNode*/ undefined); + })) { + const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; + checkTypeAssignableTo(source, target, node, headMessage, containingMessageChain, resultObj); + const diagnostic = resultObj.errors![resultObj.errors!.length - 1]; + addRelatedInfo(diagnostic, createDiagnosticForNode( + node, + signatures === constructSignatures ? Diagnostics.Did_you_mean_to_use_new_with_this_expression : Diagnostics.Did_you_mean_to_call_this_expression + )); return true; } - switch (node.kind) { - case SyntaxKind.JsxExpression: - case SyntaxKind.ParenthesizedExpression: - return elaborateError((node as ParenthesizedExpression | JsxExpression).expression, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); - case SyntaxKind.BinaryExpression: - switch ((node as BinaryExpression).operatorToken.kind) { - case SyntaxKind.EqualsToken: - case SyntaxKind.CommaToken: - return elaborateError((node as BinaryExpression).right, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); - } - break; - case SyntaxKind.ObjectLiteralExpression: - return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); - case SyntaxKind.ArrayLiteralExpression: - return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); - case SyntaxKind.JsxAttributes: - return elaborateJsxComponents(node as JsxAttributes, source, target, relation, containingMessageChain, errorOutputContainer); - case SyntaxKind.ArrowFunction: - return elaborateArrowFunction(node as ArrowFunction, source, target, relation, containingMessageChain, errorOutputContainer); - } - return false; } + return false; + } - function elaborateDidYouMeanToCallOrConstruct( - node: Expression, - source: Type, - target: Type, - relation: ESMap, - headMessage: DiagnosticMessage | undefined, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ): boolean { - const callSignatures = getSignaturesOfType(source, SignatureKind.Call); - const constructSignatures = getSignaturesOfType(source, SignatureKind.Construct); - for (const signatures of [constructSignatures, callSignatures]) { - if (some(signatures, s => { - const returnType = getReturnTypeOfSignature(s); - return !(returnType.flags & (TypeFlags.Any | TypeFlags.Never)) && checkTypeRelatedTo(returnType, target, relation, /*errorNode*/ undefined); - })) { - const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; - checkTypeAssignableTo(source, target, node, headMessage, containingMessageChain, resultObj); - const diagnostic = resultObj.errors![resultObj.errors!.length - 1]; - addRelatedInfo(diagnostic, createDiagnosticForNode( - node, - signatures === constructSignatures ? Diagnostics.Did_you_mean_to_use_new_with_this_expression : Diagnostics.Did_you_mean_to_call_this_expression - )); - return true; - } - } + function elaborateArrowFunction( + node: ArrowFunction, + source: Type, + target: Type, + relation: ESMap, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ): boolean { + // Don't elaborate blocks + if (isBlock(node.body)) { return false; } - - function elaborateArrowFunction( - node: ArrowFunction, - source: Type, - target: Type, - relation: ESMap, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ): boolean { - // Don't elaborate blocks - if (isBlock(node.body)) { - return false; - } - // Or functions with annotated parameter types - if (some(node.parameters, ts.hasType)) { - return false; - } - const sourceSig = getSingleCallSignature(source); - if (!sourceSig) { - return false; - } - const targetSignatures = getSignaturesOfType(target, SignatureKind.Call); - if (!length(targetSignatures)) { - return false; - } - const returnExpression = node.body; - const sourceReturn = getReturnTypeOfSignature(sourceSig); - const targetReturn = getUnionType(map(targetSignatures, getReturnTypeOfSignature)); - if (!checkTypeRelatedTo(sourceReturn, targetReturn, relation, /*errorNode*/ undefined)) { - const elaborated = returnExpression && elaborateError(returnExpression, sourceReturn, targetReturn, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); - if (elaborated) { - return elaborated; + // Or functions with annotated parameter types + if (some(node.parameters, ts.hasType)) { + return false; + } + const sourceSig = getSingleCallSignature(source); + if (!sourceSig) { + return false; + } + const targetSignatures = getSignaturesOfType(target, SignatureKind.Call); + if (!length(targetSignatures)) { + return false; + } + const returnExpression = node.body; + const sourceReturn = getReturnTypeOfSignature(sourceSig); + const targetReturn = getUnionType(map(targetSignatures, getReturnTypeOfSignature)); + if (!checkTypeRelatedTo(sourceReturn, targetReturn, relation, /*errorNode*/ undefined)) { + const elaborated = returnExpression && elaborateError(returnExpression, sourceReturn, targetReturn, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); + if (elaborated) { + return elaborated; + } + const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; + checkTypeRelatedTo(sourceReturn, targetReturn, relation, returnExpression, /*message*/ undefined, containingMessageChain, resultObj); + if (resultObj.errors) { + if (target.symbol && length(target.symbol.declarations)) { + addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( + target.symbol.declarations![0], + Diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature, + )); } - const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; - checkTypeRelatedTo(sourceReturn, targetReturn, relation, returnExpression, /*message*/ undefined, containingMessageChain, resultObj); - if (resultObj.errors) { - if (target.symbol && length(target.symbol.declarations)) { - addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( - target.symbol.declarations![0], - Diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature, - )); - } - if ((getFunctionFlags(node) & FunctionFlags.Async) === 0 - // exclude cases where source itself is promisy - this way we don't make a suggestion when relating - // an IPromise and a Promise that are slightly different - && !getTypeOfPropertyOfType(sourceReturn, "then" as __String) - && checkTypeRelatedTo(createPromiseType(sourceReturn), targetReturn, relation, /*errorNode*/ undefined) - ) { - addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( - node, - Diagnostics.Did_you_mean_to_mark_this_function_as_async - )); - } - return true; + if ((getFunctionFlags(node) & FunctionFlags.Async) === 0 + // exclude cases where source itself is promisy - this way we don't make a suggestion when relating + // an IPromise and a Promise that are slightly different + && !getTypeOfPropertyOfType(sourceReturn, "then" as __String) + && checkTypeRelatedTo(createPromiseType(sourceReturn), targetReturn, relation, /*errorNode*/ undefined) + ) { + addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( + node, + Diagnostics.Did_you_mean_to_mark_this_function_as_async + )); } + return true; } - return false; } + return false; + } - function getBestMatchIndexedAccessTypeOrUndefined(source: Type, target: Type, nameType: Type) { - const idx = getIndexedAccessTypeOrUndefined(target, nameType); - if (idx) { - return idx; - } - if (target.flags & TypeFlags.Union) { - const best = getBestMatchingType(source, target as UnionType); - if (best) { - return getIndexedAccessTypeOrUndefined(best, nameType); - } + function getBestMatchIndexedAccessTypeOrUndefined(source: Type, target: Type, nameType: Type) { + const idx = getIndexedAccessTypeOrUndefined(target, nameType); + if (idx) { + return idx; + } + if (target.flags & TypeFlags.Union) { + const best = getBestMatchingType(source, target as UnionType); + if (best) { + return getIndexedAccessTypeOrUndefined(best, nameType); } } + } - function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) { - next.contextualType = sourcePropType; - try { - return checkExpressionForMutableLocation(next, CheckMode.Contextual, sourcePropType); - } - finally { - next.contextualType = undefined; - } + function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) { + next.contextualType = sourcePropType; + try { + return checkExpressionForMutableLocation(next, CheckMode.Contextual, sourcePropType); } + finally { + next.contextualType = undefined; + } + } - type ElaborationIterator = IterableIterator<{ errorNode: Node, innerExpression: Expression | undefined, nameType: Type, errorMessage?: DiagnosticMessage | undefined }>; - /** - * For every element returned from the iterator, checks that element to issue an error on a property of that element's type - * If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError` - * Otherwise, we issue an error on _every_ element which fail the assignability check - */ - function elaborateElementwise( - iterator: ElaborationIterator, - source: Type, - target: Type, - relation: ESMap, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ) { - // Assignability failure - check each prop individually, and if that fails, fall back on the bad error span - let reportedError = false; - for (let status = iterator.next(); !status.done; status = iterator.next()) { - const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value; - let targetPropType = getBestMatchIndexedAccessTypeOrUndefined(source, target, nameType); - if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables - let sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType); - if (!sourcePropType) continue; - const propName = getPropertyNameFromIndex(nameType, /*accessNode*/ undefined); - if (!checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) { - const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); - reportedError = true; - if (!elaborated) { - // Issue error on the prop itself, since the prop couldn't elaborate the error - const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; - // Use the expression type, if available - const specificSource = next ? checkExpressionForMutableLocationWithContextualType(next, sourcePropType) : sourcePropType; - if (exactOptionalPropertyTypes && isExactOptionalPropertyMismatch(specificSource, targetPropType)) { - const diag = createDiagnosticForNode(prop, Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, typeToString(specificSource), typeToString(targetPropType)); - diagnostics.add(diag); - resultObj.errors = [diag]; + type ElaborationIterator = IterableIterator<{ errorNode: Node, innerExpression: Expression | undefined, nameType: Type, errorMessage?: DiagnosticMessage | undefined }>; + /** + * For every element returned from the iterator, checks that element to issue an error on a property of that element's type + * If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError` + * Otherwise, we issue an error on _every_ element which fail the assignability check + */ + function elaborateElementwise( + iterator: ElaborationIterator, + source: Type, + target: Type, + relation: ESMap, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ) { + // Assignability failure - check each prop individually, and if that fails, fall back on the bad error span + let reportedError = false; + for (let status = iterator.next(); !status.done; status = iterator.next()) { + const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value; + let targetPropType = getBestMatchIndexedAccessTypeOrUndefined(source, target, nameType); + if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables + let sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType); + if (!sourcePropType) continue; + const propName = getPropertyNameFromIndex(nameType, /*accessNode*/ undefined); + if (!checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) { + const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); + reportedError = true; + if (!elaborated) { + // Issue error on the prop itself, since the prop couldn't elaborate the error + const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; + // Use the expression type, if available + const specificSource = next ? checkExpressionForMutableLocationWithContextualType(next, sourcePropType) : sourcePropType; + if (exactOptionalPropertyTypes && isExactOptionalPropertyMismatch(specificSource, targetPropType)) { + const diag = createDiagnosticForNode(prop, Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, typeToString(specificSource), typeToString(targetPropType)); + diagnostics.add(diag); + resultObj.errors = [diag]; + } + else { + const targetIsOptional = !!(propName && (getPropertyOfType(target, propName) || unknownSymbol).flags & SymbolFlags.Optional); + const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional); + targetPropType = removeMissingType(targetPropType, targetIsOptional); + sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional); + const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); + if (result && specificSource !== sourcePropType) { + // If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType + checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); } - else { - const targetIsOptional = !!(propName && (getPropertyOfType(target, propName) || unknownSymbol).flags & SymbolFlags.Optional); - const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional); - targetPropType = removeMissingType(targetPropType, targetIsOptional); - sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional); - const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); - if (result && specificSource !== sourcePropType) { - // If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType - checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); + } + if (resultObj.errors) { + const reportedDiag = resultObj.errors[resultObj.errors.length - 1]; + const propertyName = isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; + const targetProp = propertyName !== undefined ? getPropertyOfType(target, propertyName) : undefined; + + let issuedElaboration = false; + if (!targetProp) { + const indexInfo = getApplicableIndexInfo(target, nameType); + if (indexInfo && indexInfo.declaration && !getSourceFileOfNode(indexInfo.declaration).hasNoDefaultLib) { + issuedElaboration = true; + addRelatedInfo(reportedDiag, createDiagnosticForNode(indexInfo.declaration, Diagnostics.The_expected_type_comes_from_this_index_signature)); } } - if (resultObj.errors) { - const reportedDiag = resultObj.errors[resultObj.errors.length - 1]; - const propertyName = isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; - const targetProp = propertyName !== undefined ? getPropertyOfType(target, propertyName) : undefined; - - let issuedElaboration = false; - if (!targetProp) { - const indexInfo = getApplicableIndexInfo(target, nameType); - if (indexInfo && indexInfo.declaration && !getSourceFileOfNode(indexInfo.declaration).hasNoDefaultLib) { - issuedElaboration = true; - addRelatedInfo(reportedDiag, createDiagnosticForNode(indexInfo.declaration, Diagnostics.The_expected_type_comes_from_this_index_signature)); - } - } - if (!issuedElaboration && (targetProp && length(targetProp.declarations) || target.symbol && length(target.symbol.declarations))) { - const targetNode = targetProp && length(targetProp.declarations) ? targetProp.declarations![0] : target.symbol.declarations![0]; - if (!getSourceFileOfNode(targetNode).hasNoDefaultLib) { - addRelatedInfo(reportedDiag, createDiagnosticForNode( - targetNode, - Diagnostics.The_expected_type_comes_from_property_0_which_is_declared_here_on_type_1, - propertyName && !(nameType.flags & TypeFlags.UniqueESSymbol) ? unescapeLeadingUnderscores(propertyName) : typeToString(nameType), - typeToString(target) - )); - } + if (!issuedElaboration && (targetProp && length(targetProp.declarations) || target.symbol && length(target.symbol.declarations))) { + const targetNode = targetProp && length(targetProp.declarations) ? targetProp.declarations![0] : target.symbol.declarations![0]; + if (!getSourceFileOfNode(targetNode).hasNoDefaultLib) { + addRelatedInfo(reportedDiag, createDiagnosticForNode( + targetNode, + Diagnostics.The_expected_type_comes_from_property_0_which_is_declared_here_on_type_1, + propertyName && !(nameType.flags & TypeFlags.UniqueESSymbol) ? unescapeLeadingUnderscores(propertyName) : typeToString(nameType), + typeToString(target) + )); } } } } } - return reportedError; } + return reportedError; + } - function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator { - if (!length(node.properties)) return; - for (const prop of node.properties) { - if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue; - yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) }; - } + function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator { + if (!length(node.properties)) return; + for (const prop of node.properties) { + if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue; + yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) }; } + } - function *generateJsxChildren(node: JsxElement, getInvalidTextDiagnostic: () => DiagnosticMessage): ElaborationIterator { - if (!length(node.children)) return; - let memberOffset = 0; - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i]; - const nameType = getNumberLiteralType(i - memberOffset); - const elem = getElaborationElementForJsxChild(child, nameType, getInvalidTextDiagnostic); - if (elem) { - yield elem; - } - else { - memberOffset++; - } + function *generateJsxChildren(node: JsxElement, getInvalidTextDiagnostic: () => DiagnosticMessage): ElaborationIterator { + if (!length(node.children)) return; + let memberOffset = 0; + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const nameType = getNumberLiteralType(i - memberOffset); + const elem = getElaborationElementForJsxChild(child, nameType, getInvalidTextDiagnostic); + if (elem) { + yield elem; + } + else { + memberOffset++; } } + } - function getElaborationElementForJsxChild(child: JsxChild, nameType: LiteralType, getInvalidTextDiagnostic: () => DiagnosticMessage) { - switch (child.kind) { - case SyntaxKind.JsxExpression: - // child is of the type of the expression - return { errorNode: child, innerExpression: child.expression, nameType }; - case SyntaxKind.JsxText: - if (child.containsOnlyTriviaWhiteSpaces) { - break; // Whitespace only jsx text isn't real jsx text - } - // child is a string - return { errorNode: child, innerExpression: undefined, nameType, errorMessage: getInvalidTextDiagnostic() }; - case SyntaxKind.JsxElement: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxFragment: - // child is of type JSX.Element - return { errorNode: child, innerExpression: child, nameType }; - default: - return Debug.assertNever(child, "Found invalid jsx child"); - } + function getElaborationElementForJsxChild(child: JsxChild, nameType: LiteralType, getInvalidTextDiagnostic: () => DiagnosticMessage) { + switch (child.kind) { + case SyntaxKind.JsxExpression: + // child is of the type of the expression + return { errorNode: child, innerExpression: child.expression, nameType }; + case SyntaxKind.JsxText: + if (child.containsOnlyTriviaWhiteSpaces) { + break; // Whitespace only jsx text isn't real jsx text + } + // child is a string + return { errorNode: child, innerExpression: undefined, nameType, errorMessage: getInvalidTextDiagnostic() }; + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxFragment: + // child is of type JSX.Element + return { errorNode: child, innerExpression: child, nameType }; + default: + return Debug.assertNever(child, "Found invalid jsx child"); } + } - function elaborateJsxComponents( - node: JsxAttributes, - source: Type, - target: Type, - relation: ESMap, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ) { - let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation, containingMessageChain, errorOutputContainer); - let invalidTextDiagnostic: DiagnosticMessage | undefined; - if (isJsxOpeningElement(node.parent) && isJsxElement(node.parent.parent)) { - const containingElement = node.parent.parent; - const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); - const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); - const childrenNameType = getStringLiteralType(childrenPropName); - const childrenTargetType = getIndexedAccessType(target, childrenNameType); - const validChildren = getSemanticJsxChildren(containingElement.children); - if (!length(validChildren)) { - return result; - } - const moreThanOneRealChildren = length(validChildren) > 1; - const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType); - const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t)); - if (moreThanOneRealChildren) { - if (arrayLikeTargetParts !== neverType) { - const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal)); - const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic); - result = elaborateElementwise(children, realSource, arrayLikeTargetParts, relation, containingMessageChain, errorOutputContainer) || result; - } - else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { - // arity mismatch - result = true; - const diag = error( - containingElement.openingElement.tagName, - Diagnostics.This_JSX_tag_s_0_prop_expects_a_single_child_of_type_1_but_multiple_children_were_provided, - childrenPropName, - typeToString(childrenTargetType) - ); - if (errorOutputContainer && errorOutputContainer.skipLogging) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); - } - } - } - else { - if (nonArrayLikeTargetParts !== neverType) { - const child = validChildren[0]; - const elem = getElaborationElementForJsxChild(child, childrenNameType, getInvalidTextualChildDiagnostic); - if (elem) { - result = elaborateElementwise( - (function*() { yield elem; })(), - source, - target, - relation, - containingMessageChain, - errorOutputContainer - ) || result; - } - } - else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { - // arity mismatch - result = true; - const diag = error( - containingElement.openingElement.tagName, - Diagnostics.This_JSX_tag_s_0_prop_expects_type_1_which_requires_multiple_children_but_only_a_single_child_was_provided, - childrenPropName, - typeToString(childrenTargetType) - ); - if (errorOutputContainer && errorOutputContainer.skipLogging) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); - } - } - } + function elaborateJsxComponents( + node: JsxAttributes, + source: Type, + target: Type, + relation: ESMap, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ) { + let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation, containingMessageChain, errorOutputContainer); + let invalidTextDiagnostic: DiagnosticMessage | undefined; + if (isJsxOpeningElement(node.parent) && isJsxElement(node.parent.parent)) { + const containingElement = node.parent.parent; + const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); + const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); + const childrenNameType = getStringLiteralType(childrenPropName); + const childrenTargetType = getIndexedAccessType(target, childrenNameType); + const validChildren = getSemanticJsxChildren(containingElement.children); + if (!length(validChildren)) { + return result; } - return result; - - function getInvalidTextualChildDiagnostic() { - if (!invalidTextDiagnostic) { - const tagNameText = getTextOfNode(node.parent.tagName); - const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); - const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); - const childrenTargetType = getIndexedAccessType(target, getStringLiteralType(childrenPropName)); - const diagnostic = Diagnostics._0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2; - invalidTextDiagnostic = { ...diagnostic, key: "!!ALREADY FORMATTED!!", message: formatMessage(/*_dummy*/ undefined, diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) }; + const moreThanOneRealChildren = length(validChildren) > 1; + const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType); + const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t)); + if (moreThanOneRealChildren) { + if (arrayLikeTargetParts !== neverType) { + const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal)); + const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic); + result = elaborateElementwise(children, realSource, arrayLikeTargetParts, relation, containingMessageChain, errorOutputContainer) || result; + } + else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { + // arity mismatch + result = true; + const diag = error( + containingElement.openingElement.tagName, + Diagnostics.This_JSX_tag_s_0_prop_expects_a_single_child_of_type_1_but_multiple_children_were_provided, + childrenPropName, + typeToString(childrenTargetType) + ); + if (errorOutputContainer && errorOutputContainer.skipLogging) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } } - return invalidTextDiagnostic; } - } - - function *generateLimitedTupleElements(node: ArrayLiteralExpression, target: Type): ElaborationIterator { - const len = length(node.elements); - if (!len) return; - for (let i = 0; i < len; i++) { - // Skip elements which do not exist in the target - a length error on the tuple overall is likely better than an error on a mismatched index signature - if (isTupleLikeType(target) && !getPropertyOfType(target, ("" + i) as __String)) continue; - const elem = node.elements[i]; - if (isOmittedExpression(elem)) continue; - const nameType = getNumberLiteralType(i); - yield { errorNode: elem, innerExpression: elem, nameType }; - } - } - - function elaborateArrayLiteral( - node: ArrayLiteralExpression, - source: Type, - target: Type, - relation: ESMap, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ) { - if (target.flags & TypeFlags.Primitive) return false; - if (isTupleLikeType(source)) { - return elaborateElementwise(generateLimitedTupleElements(node, target), source, target, relation, containingMessageChain, errorOutputContainer); - } - // recreate a tuple from the elements, if possible - // Since we're re-doing the expression type, we need to reapply the contextual type - const oldContext = node.contextualType; - node.contextualType = target; - try { - const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); - node.contextualType = oldContext; - if (isTupleLikeType(tupleizedType)) { - return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer); + else { + if (nonArrayLikeTargetParts !== neverType) { + const child = validChildren[0]; + const elem = getElaborationElementForJsxChild(child, childrenNameType, getInvalidTextualChildDiagnostic); + if (elem) { + result = elaborateElementwise( + (function*() { yield elem; })(), + source, + target, + relation, + containingMessageChain, + errorOutputContainer + ) || result; + } + } + else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { + // arity mismatch + result = true; + const diag = error( + containingElement.openingElement.tagName, + Diagnostics.This_JSX_tag_s_0_prop_expects_type_1_which_requires_multiple_children_but_only_a_single_child_was_provided, + childrenPropName, + typeToString(childrenTargetType) + ); + if (errorOutputContainer && errorOutputContainer.skipLogging) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } } - return false; - } - finally { - node.contextualType = oldContext; } } + return result; - function *generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator { - if (!length(node.properties)) return; - for (const prop of node.properties) { - if (isSpreadAssignment(prop)) continue; - const type = getLiteralTypeFromProperty(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique); - if (!type || (type.flags & TypeFlags.Never)) { - continue; - } - switch (prop.kind) { - case SyntaxKind.SetAccessor: - case SyntaxKind.GetAccessor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.ShorthandPropertyAssignment: - yield { errorNode: prop.name, innerExpression: undefined, nameType: type }; - break; - case SyntaxKind.PropertyAssignment: - yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: type, errorMessage: isComputedNonLiteralName(prop.name) ? Diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1 : undefined }; - break; - default: - Debug.assertNever(prop); - } + function getInvalidTextualChildDiagnostic() { + if (!invalidTextDiagnostic) { + const tagNameText = getTextOfNode(node.parent.tagName); + const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); + const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); + const childrenTargetType = getIndexedAccessType(target, getStringLiteralType(childrenPropName)); + const diagnostic = Diagnostics._0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2; + invalidTextDiagnostic = { ...diagnostic, key: "!!ALREADY FORMATTED!!", message: formatMessage(/*_dummy*/ undefined, diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) }; } + return invalidTextDiagnostic; } + } - function elaborateObjectLiteral( - node: ObjectLiteralExpression, - source: Type, - target: Type, - relation: ESMap, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ) { - if (target.flags & TypeFlags.Primitive) return false; - return elaborateElementwise(generateObjectLiteralElements(node), source, target, relation, containingMessageChain, errorOutputContainer); - } - - /** - * This is *not* a bi-directional relationship. - * If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'. - */ - function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { - return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain); + function *generateLimitedTupleElements(node: ArrayLiteralExpression, target: Type): ElaborationIterator { + const len = length(node.elements); + if (!len) return; + for (let i = 0; i < len; i++) { + // Skip elements which do not exist in the target - a length error on the tuple overall is likely better than an error on a mismatched index signature + if (isTupleLikeType(target) && !getPropertyOfType(target, ("" + i) as __String)) continue; + const elem = node.elements[i]; + if (isOmittedExpression(elem)) continue; + const nameType = getNumberLiteralType(i); + yield { errorNode: elem, innerExpression: elem, nameType }; } + } - function isSignatureAssignableTo(source: Signature, - target: Signature, - ignoreReturnTypes: boolean): boolean { - return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : 0, /*reportErrors*/ false, - /*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False; + function elaborateArrayLiteral( + node: ArrayLiteralExpression, + source: Type, + target: Type, + relation: ESMap, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ) { + if (target.flags & TypeFlags.Primitive) return false; + if (isTupleLikeType(source)) { + return elaborateElementwise(generateLimitedTupleElements(node, target), source, target, relation, containingMessageChain, errorOutputContainer); + } + // recreate a tuple from the elements, if possible + // Since we're re-doing the expression type, we need to reapply the contextual type + const oldContext = node.contextualType; + node.contextualType = target; + try { + const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); + node.contextualType = oldContext; + if (isTupleLikeType(tupleizedType)) { + return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer); + } + return false; } - - type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void; - - /** - * Returns true if `s` is `(...args: any[]) => any` or `(this: any, ...args: any[]) => any` - */ - function isAnySignature(s: Signature) { - return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && - signatureHasRestParameter(s) && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) && - isTypeAny(getReturnTypeOfSignature(s)); + finally { + node.contextualType = oldContext; } + } - /** - * See signatureRelatedTo, compareSignaturesIdentical - */ - function compareSignaturesRelated(source: Signature, - target: Signature, - checkMode: SignatureCheckMode, - reportErrors: boolean, - errorReporter: ErrorReporter | undefined, - incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined, - compareTypes: TypeComparer, - reportUnreliableMarkers: TypeMapper | undefined): Ternary { - // TODO (drosen): De-duplicate code between related functions. - if (source === target) { - return Ternary.True; + function *generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator { + if (!length(node.properties)) return; + for (const prop of node.properties) { + if (isSpreadAssignment(prop)) continue; + const type = getLiteralTypeFromProperty(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique); + if (!type || (type.flags & TypeFlags.Never)) { + continue; } - - if (isAnySignature(target)) { - return Ternary.True; + switch (prop.kind) { + case SyntaxKind.SetAccessor: + case SyntaxKind.GetAccessor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.ShorthandPropertyAssignment: + yield { errorNode: prop.name, innerExpression: undefined, nameType: type }; + break; + case SyntaxKind.PropertyAssignment: + yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: type, errorMessage: isComputedNonLiteralName(prop.name) ? Diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1 : undefined }; + break; + default: + Debug.assertNever(prop); } + } + } - const targetCount = getParameterCount(target); - const sourceHasMoreParameters = !hasEffectiveRestParameter(target) && - (checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount); - if (sourceHasMoreParameters) { - return Ternary.False; - } + function elaborateObjectLiteral( + node: ObjectLiteralExpression, + source: Type, + target: Type, + relation: ESMap, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ) { + if (target.flags & TypeFlags.Primitive) return false; + return elaborateElementwise(generateObjectLiteralElements(node), source, target, relation, containingMessageChain, errorOutputContainer); + } - if (source.typeParameters && source.typeParameters !== target.typeParameters) { - target = getCanonicalSignature(target); - source = instantiateSignatureInContextOf(source, target, /*inferenceContext*/ undefined, compareTypes); - } + /** + * This is *not* a bi-directional relationship. + * If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'. + */ + function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { + return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain); + } - const sourceCount = getParameterCount(source); - const sourceRestType = getNonArrayRestType(source); - const targetRestType = getNonArrayRestType(target); - if (sourceRestType || targetRestType) { - void instantiateType(sourceRestType || targetRestType, reportUnreliableMarkers); - } - if (sourceRestType && targetRestType && sourceCount !== targetCount) { - // We're not able to relate misaligned complex rest parameters - return Ternary.False; - } + function isSignatureAssignableTo(source: Signature, + target: Signature, + ignoreReturnTypes: boolean): boolean { + return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : 0, /*reportErrors*/ false, + /*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False; + } - const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; - const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration && - kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor; - let result = Ternary.True; + type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void; - const sourceThisType = getThisTypeOfSignature(source); - if (sourceThisType && sourceThisType !== voidType) { - const targetThisType = getThisTypeOfSignature(target); - if (targetThisType) { - // void sources are assignable to anything. - const related = !strictVariance && compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false) - || compareTypes(targetThisType, sourceThisType, reportErrors); - if (!related) { - if (reportErrors) { - errorReporter!(Diagnostics.The_this_types_of_each_signature_are_incompatible); - } - return Ternary.False; - } - result &= related; - } - } + /** + * Returns true if `s` is `(...args: any[]) => any` or `(this: any, ...args: any[]) => any` + */ + function isAnySignature(s: Signature) { + return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && + signatureHasRestParameter(s) && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) && + isTypeAny(getReturnTypeOfSignature(s)); + } - const paramCount = sourceRestType || targetRestType ? Math.min(sourceCount, targetCount) : Math.max(sourceCount, targetCount); - const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1; - - for (let i = 0; i < paramCount; i++) { - const sourceType = i === restIndex ? getRestTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i); - const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i); - if (sourceType && targetType) { - // In order to ensure that any generic type Foo is at least co-variant with respect to T no matter - // how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions, - // they naturally relate only contra-variantly). However, if the source and target parameters both have - // function types with a single call signature, we know we are relating two callback parameters. In - // that case it is sufficient to only relate the parameters of the signatures co-variantly because, - // similar to return values, callback parameters are output positions. This means that a Promise, - // where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) - // with respect to T. - const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); - const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType)); - const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && - (getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable); - let related = callbacks ? - compareSignaturesRelated(targetSig, sourceSig, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : - !(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); - // With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void - if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) { - related = Ternary.False; - } - if (!related) { - if (reportErrors) { - errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, - unescapeLeadingUnderscores(getParameterNameAtPosition(source, i)), - unescapeLeadingUnderscores(getParameterNameAtPosition(target, i))); - } - return Ternary.False; + /** + * See signatureRelatedTo, compareSignaturesIdentical + */ + function compareSignaturesRelated(source: Signature, + target: Signature, + checkMode: SignatureCheckMode, + reportErrors: boolean, + errorReporter: ErrorReporter | undefined, + incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined, + compareTypes: TypeComparer, + reportUnreliableMarkers: TypeMapper | undefined): Ternary { + // TODO (drosen): De-duplicate code between related functions. + if (source === target) { + return Ternary.True; + } + + if (isAnySignature(target)) { + return Ternary.True; + } + + const targetCount = getParameterCount(target); + const sourceHasMoreParameters = !hasEffectiveRestParameter(target) && + (checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount); + if (sourceHasMoreParameters) { + return Ternary.False; + } + + if (source.typeParameters && source.typeParameters !== target.typeParameters) { + target = getCanonicalSignature(target); + source = instantiateSignatureInContextOf(source, target, /*inferenceContext*/ undefined, compareTypes); + } + + const sourceCount = getParameterCount(source); + const sourceRestType = getNonArrayRestType(source); + const targetRestType = getNonArrayRestType(target); + if (sourceRestType || targetRestType) { + void instantiateType(sourceRestType || targetRestType, reportUnreliableMarkers); + } + if (sourceRestType && targetRestType && sourceCount !== targetCount) { + // We're not able to relate misaligned complex rest parameters + return Ternary.False; + } + + const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; + const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration && + kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor; + let result = Ternary.True; + + const sourceThisType = getThisTypeOfSignature(source); + if (sourceThisType && sourceThisType !== voidType) { + const targetThisType = getThisTypeOfSignature(target); + if (targetThisType) { + // void sources are assignable to anything. + const related = !strictVariance && compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false) + || compareTypes(targetThisType, sourceThisType, reportErrors); + if (!related) { + if (reportErrors) { + errorReporter!(Diagnostics.The_this_types_of_each_signature_are_incompatible); } - result &= related; + return Ternary.False; } + result &= related; } + } - if (!(checkMode & SignatureCheckMode.IgnoreReturnTypes)) { - // If a signature resolution is already in-flight, skip issuing a circularity error - // here and just use the `any` type directly - const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType - : target.declaration && isJSConstructor(target.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(target.declaration.symbol)) - : getReturnTypeOfSignature(target); - if (targetReturnType === voidType) { - return result; - } - const sourceReturnType = isResolvingReturnTypeOfSignature(source) ? anyType - : source.declaration && isJSConstructor(source.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(source.declaration.symbol)) - : getReturnTypeOfSignature(source); - - // The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions - const targetTypePredicate = getTypePredicateOfSignature(target); - if (targetTypePredicate) { - const sourceTypePredicate = getTypePredicateOfSignature(source); - if (sourceTypePredicate) { - result &= compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes); - } - else if (isIdentifierTypePredicate(targetTypePredicate)) { - if (reportErrors) { - errorReporter!(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source)); - } - return Ternary.False; - } + const paramCount = sourceRestType || targetRestType ? Math.min(sourceCount, targetCount) : Math.max(sourceCount, targetCount); + const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1; + + for (let i = 0; i < paramCount; i++) { + const sourceType = i === restIndex ? getRestTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i); + const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i); + if (sourceType && targetType) { + // In order to ensure that any generic type Foo is at least co-variant with respect to T no matter + // how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions, + // they naturally relate only contra-variantly). However, if the source and target parameters both have + // function types with a single call signature, we know we are relating two callback parameters. In + // that case it is sufficient to only relate the parameters of the signatures co-variantly because, + // similar to return values, callback parameters are output positions. This means that a Promise, + // where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) + // with respect to T. + const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); + const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType)); + const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && + (getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable); + let related = callbacks ? + compareSignaturesRelated(targetSig, sourceSig, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : + !(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); + // With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void + if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) { + related = Ternary.False; } - else { - // When relating callback signatures, we still need to relate return types bi-variantly as otherwise - // the containing type wouldn't be co-variant. For example, interface Foo { add(cb: () => T): void } - // wouldn't be co-variant for T without this rule. - result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) || - compareTypes(sourceReturnType, targetReturnType, reportErrors); - if (!result && reportErrors && incompatibleErrorReporter) { - incompatibleErrorReporter(sourceReturnType, targetReturnType); + if (!related) { + if (reportErrors) { + errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, + unescapeLeadingUnderscores(getParameterNameAtPosition(source, i)), + unescapeLeadingUnderscores(getParameterNameAtPosition(target, i))); } + return Ternary.False; } - + result &= related; } - - return result; } - function compareTypePredicateRelatedTo( - source: TypePredicate, - target: TypePredicate, - reportErrors: boolean, - errorReporter: ErrorReporter | undefined, - compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary): Ternary { - if (source.kind !== target.kind) { - if (reportErrors) { - errorReporter!(Diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard); - errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); - } - return Ternary.False; + if (!(checkMode & SignatureCheckMode.IgnoreReturnTypes)) { + // If a signature resolution is already in-flight, skip issuing a circularity error + // here and just use the `any` type directly + const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType + : target.declaration && isJSConstructor(target.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(target.declaration.symbol)) + : getReturnTypeOfSignature(target); + if (targetReturnType === voidType) { + return result; } + const sourceReturnType = isResolvingReturnTypeOfSignature(source) ? anyType + : source.declaration && isJSConstructor(source.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(source.declaration.symbol)) + : getReturnTypeOfSignature(source); - if (source.kind === TypePredicateKind.Identifier || source.kind === TypePredicateKind.AssertsIdentifier) { - if (source.parameterIndex !== (target as IdentifierTypePredicate).parameterIndex) { + // The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions + const targetTypePredicate = getTypePredicateOfSignature(target); + if (targetTypePredicate) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + if (sourceTypePredicate) { + result &= compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes); + } + else if (isIdentifierTypePredicate(targetTypePredicate)) { if (reportErrors) { - errorReporter!(Diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, (target as IdentifierTypePredicate).parameterName); - errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); + errorReporter!(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source)); } return Ternary.False; } } - - const related = source.type === target.type ? Ternary.True : - source.type && target.type ? compareTypes(source.type, target.type, reportErrors) : - Ternary.False; - if (related === Ternary.False && reportErrors) { - errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); + else { + // When relating callback signatures, we still need to relate return types bi-variantly as otherwise + // the containing type wouldn't be co-variant. For example, interface Foo { add(cb: () => T): void } + // wouldn't be co-variant for T without this rule. + result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) || + compareTypes(sourceReturnType, targetReturnType, reportErrors); + if (!result && reportErrors && incompatibleErrorReporter) { + incompatibleErrorReporter(sourceReturnType, targetReturnType); + } } - return related; - } - function isImplementationCompatibleWithOverload(implementation: Signature, overload: Signature): boolean { - const erasedSource = getErasedSignature(implementation); - const erasedTarget = getErasedSignature(overload); + } - // First see if the return types are compatible in either direction. - const sourceReturnType = getReturnTypeOfSignature(erasedSource); - const targetReturnType = getReturnTypeOfSignature(erasedTarget); - if (targetReturnType === voidType - || isTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation) - || isTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation)) { + return result; + } - return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true); + function compareTypePredicateRelatedTo( + source: TypePredicate, + target: TypePredicate, + reportErrors: boolean, + errorReporter: ErrorReporter | undefined, + compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary): Ternary { + if (source.kind !== target.kind) { + if (reportErrors) { + errorReporter!(Diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard); + errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } - - return false; + return Ternary.False; } - function isEmptyResolvedType(t: ResolvedType) { - return t !== anyFunctionType && - t.properties.length === 0 && - t.callSignatures.length === 0 && - t.constructSignatures.length === 0 && - t.indexInfos.length === 0; + if (source.kind === TypePredicateKind.Identifier || source.kind === TypePredicateKind.AssertsIdentifier) { + if (source.parameterIndex !== (target as IdentifierTypePredicate).parameterIndex) { + if (reportErrors) { + errorReporter!(Diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, (target as IdentifierTypePredicate).parameterName); + errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); + } + return Ternary.False; + } } - function isEmptyObjectType(type: Type): boolean { - return type.flags & TypeFlags.Object ? !isGenericMappedType(type) && isEmptyResolvedType(resolveStructuredTypeMembers(type as ObjectType)) : - type.flags & TypeFlags.NonPrimitive ? true : - type.flags & TypeFlags.Union ? some((type as UnionType).types, isEmptyObjectType) : - type.flags & TypeFlags.Intersection ? every((type as UnionType).types, isEmptyObjectType) : - false; + const related = source.type === target.type ? Ternary.True : + source.type && target.type ? compareTypes(source.type, target.type, reportErrors) : + Ternary.False; + if (related === Ternary.False && reportErrors) { + errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } + return related; + } - function isEmptyAnonymousObjectType(type: Type) { - return !!(getObjectFlags(type) & ObjectFlags.Anonymous && ( - (type as ResolvedType).members && isEmptyResolvedType(type as ResolvedType) || - type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral && getMembersOfSymbol(type.symbol).size === 0)); - } + function isImplementationCompatibleWithOverload(implementation: Signature, overload: Signature): boolean { + const erasedSource = getErasedSignature(implementation); + const erasedTarget = getErasedSignature(overload); - function isStringIndexSignatureOnlyType(type: Type): boolean { - return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, stringType) || - type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isStringIndexSignatureOnlyType) || - false; + // First see if the return types are compatible in either direction. + const sourceReturnType = getReturnTypeOfSignature(erasedSource); + const targetReturnType = getReturnTypeOfSignature(erasedTarget); + if (targetReturnType === voidType + || isTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation) + || isTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation)) { + + return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true); } - function isEnumTypeRelatedTo(sourceSymbol: Symbol, targetSymbol: Symbol, errorReporter?: ErrorReporter) { - if (sourceSymbol === targetSymbol) { - return true; - } - const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol); - const entry = enumRelation.get(id); - if (entry !== undefined && !(!(entry & RelationComparisonResult.Reported) && entry & RelationComparisonResult.Failed && errorReporter)) { - return !!(entry & RelationComparisonResult.Succeeded); - } - if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) { - enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); - return false; - } - const targetEnumType = getTypeOfSymbol(targetSymbol); - for (const property of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) { - if (property.flags & SymbolFlags.EnumMember) { - const targetProperty = getPropertyOfType(targetEnumType, property.escapedName); - if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) { - if (errorReporter) { - errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property), - typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType)); - enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); - } - else { - enumRelation.set(id, RelationComparisonResult.Failed); - } - return false; + return false; + } + + function isEmptyResolvedType(t: ResolvedType) { + return t !== anyFunctionType && + t.properties.length === 0 && + t.callSignatures.length === 0 && + t.constructSignatures.length === 0 && + t.indexInfos.length === 0; + } + + function isEmptyObjectType(type: Type): boolean { + return type.flags & TypeFlags.Object ? !isGenericMappedType(type) && isEmptyResolvedType(resolveStructuredTypeMembers(type as ObjectType)) : + type.flags & TypeFlags.NonPrimitive ? true : + type.flags & TypeFlags.Union ? some((type as UnionType).types, isEmptyObjectType) : + type.flags & TypeFlags.Intersection ? every((type as UnionType).types, isEmptyObjectType) : + false; + } + + function isEmptyAnonymousObjectType(type: Type) { + return !!(getObjectFlags(type) & ObjectFlags.Anonymous && ( + (type as ResolvedType).members && isEmptyResolvedType(type as ResolvedType) || + type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral && getMembersOfSymbol(type.symbol).size === 0)); + } + + function isStringIndexSignatureOnlyType(type: Type): boolean { + return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, stringType) || + type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isStringIndexSignatureOnlyType) || + false; + } + + function isEnumTypeRelatedTo(sourceSymbol: Symbol, targetSymbol: Symbol, errorReporter?: ErrorReporter) { + if (sourceSymbol === targetSymbol) { + return true; + } + const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol); + const entry = enumRelation.get(id); + if (entry !== undefined && !(!(entry & RelationComparisonResult.Reported) && entry & RelationComparisonResult.Failed && errorReporter)) { + return !!(entry & RelationComparisonResult.Succeeded); + } + if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) { + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); + return false; + } + const targetEnumType = getTypeOfSymbol(targetSymbol); + for (const property of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) { + if (property.flags & SymbolFlags.EnumMember) { + const targetProperty = getPropertyOfType(targetEnumType, property.escapedName); + if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) { + if (errorReporter) { + errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property), + typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType)); + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); } + else { + enumRelation.set(id, RelationComparisonResult.Failed); + } + return false; } } - enumRelation.set(id, RelationComparisonResult.Succeeded); - return true; } + enumRelation.set(id, RelationComparisonResult.Succeeded); + return true; + } - function isSimpleTypeRelatedTo(source: Type, target: Type, relation: ESMap, errorReporter?: ErrorReporter) { - const s = source.flags; - const t = target.flags; - if (t & TypeFlags.AnyOrUnknown || s & TypeFlags.Never || source === wildcardType) return true; - if (t & TypeFlags.Never) return false; - if (s & TypeFlags.StringLike && t & TypeFlags.String) return true; - if (s & TypeFlags.StringLiteral && s & TypeFlags.EnumLiteral && - t & TypeFlags.StringLiteral && !(t & TypeFlags.EnumLiteral) && - (source as StringLiteralType).value === (target as StringLiteralType).value) return true; - if (s & TypeFlags.NumberLike && t & TypeFlags.Number) return true; - if (s & TypeFlags.NumberLiteral && s & TypeFlags.EnumLiteral && - t & TypeFlags.NumberLiteral && !(t & TypeFlags.EnumLiteral) && - (source as NumberLiteralType).value === (target as NumberLiteralType).value) return true; - if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) return true; - if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true; - if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true; - if (s & TypeFlags.Enum && t & TypeFlags.Enum && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; - if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) { - if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; - if (s & TypeFlags.Literal && t & TypeFlags.Literal && - (source as LiteralType).value === (target as LiteralType).value && - isEnumTypeRelatedTo(getParentOfSymbol(source.symbol)!, getParentOfSymbol(target.symbol)!, errorReporter)) return true; - } - if (s & TypeFlags.Undefined && (!strictNullChecks || t & (TypeFlags.Undefined | TypeFlags.Void))) return true; - if (s & TypeFlags.Null && (!strictNullChecks || t & TypeFlags.Null)) return true; - if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive) return true; - if (relation === assignableRelation || relation === comparableRelation) { - if (s & TypeFlags.Any) return true; - // Type number or any numeric literal type is assignable to any numeric enum type or any - // numeric enum literal type. This rule exists for backwards compatibility reasons because - // bit-flag enum types sometimes look like literal enum types with numeric literal values. - if (s & (TypeFlags.Number | TypeFlags.NumberLiteral) && !(s & TypeFlags.EnumLiteral) && ( - t & TypeFlags.Enum || relation === assignableRelation && t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true; - } - return false; - } + function isSimpleTypeRelatedTo(source: Type, target: Type, relation: ESMap, errorReporter?: ErrorReporter) { + const s = source.flags; + const t = target.flags; + if (t & TypeFlags.AnyOrUnknown || s & TypeFlags.Never || source === wildcardType) return true; + if (t & TypeFlags.Never) return false; + if (s & TypeFlags.StringLike && t & TypeFlags.String) return true; + if (s & TypeFlags.StringLiteral && s & TypeFlags.EnumLiteral && + t & TypeFlags.StringLiteral && !(t & TypeFlags.EnumLiteral) && + (source as StringLiteralType).value === (target as StringLiteralType).value) return true; + if (s & TypeFlags.NumberLike && t & TypeFlags.Number) return true; + if (s & TypeFlags.NumberLiteral && s & TypeFlags.EnumLiteral && + t & TypeFlags.NumberLiteral && !(t & TypeFlags.EnumLiteral) && + (source as NumberLiteralType).value === (target as NumberLiteralType).value) return true; + if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) return true; + if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true; + if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true; + if (s & TypeFlags.Enum && t & TypeFlags.Enum && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; + if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) { + if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; + if (s & TypeFlags.Literal && t & TypeFlags.Literal && + (source as LiteralType).value === (target as LiteralType).value && + isEnumTypeRelatedTo(getParentOfSymbol(source.symbol)!, getParentOfSymbol(target.symbol)!, errorReporter)) return true; + } + if (s & TypeFlags.Undefined && (!strictNullChecks || t & (TypeFlags.Undefined | TypeFlags.Void))) return true; + if (s & TypeFlags.Null && (!strictNullChecks || t & TypeFlags.Null)) return true; + if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive) return true; + if (relation === assignableRelation || relation === comparableRelation) { + if (s & TypeFlags.Any) return true; + // Type number or any numeric literal type is assignable to any numeric enum type or any + // numeric enum literal type. This rule exists for backwards compatibility reasons because + // bit-flag enum types sometimes look like literal enum types with numeric literal values. + if (s & (TypeFlags.Number | TypeFlags.NumberLiteral) && !(s & TypeFlags.EnumLiteral) && ( + t & TypeFlags.Enum || relation === assignableRelation && t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true; + } + return false; + } - function isTypeRelatedTo(source: Type, target: Type, relation: ESMap) { - if (isFreshLiteralType(source)) { - source = (source as FreshableType).regularType; - } - if (isFreshLiteralType(target)) { - target = (target as FreshableType).regularType; - } - if (source === target) { + function isTypeRelatedTo(source: Type, target: Type, relation: ESMap) { + if (isFreshLiteralType(source)) { + source = (source as FreshableType).regularType; + } + if (isFreshLiteralType(target)) { + target = (target as FreshableType).regularType; + } + if (source === target) { + return true; + } + if (relation !== identityRelation) { + if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation)) { return true; } - if (relation !== identityRelation) { - if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation)) { - return true; - } - } - else { - if (source.flags !== target.flags) return false; - if (source.flags & TypeFlags.Singleton) return true; - } - if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { - const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation, /*ignoreConstraints*/ false)); - if (related !== undefined) { - return !!(related & RelationComparisonResult.Succeeded); - } - } - if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { - return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined); + } + else { + if (source.flags !== target.flags) return false; + if (source.flags & TypeFlags.Singleton) return true; + } + if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { + const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation, /*ignoreConstraints*/ false)); + if (related !== undefined) { + return !!(related & RelationComparisonResult.Succeeded); } - return false; } - - function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) { - return getObjectFlags(source) & ObjectFlags.JsxAttributes && isHyphenatedJsxName(sourceProp.escapedName); + if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { + return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined); } + return false; + } - function getNormalizedType(type: Type, writing: boolean): Type { - while (true) { - let t = isFreshLiteralType(type) ? (type as FreshableType).regularType : - getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).node ? createTypeReference((type as TypeReference).target, getTypeArguments(type as TypeReference)) : - type.flags & TypeFlags.UnionOrIntersection ? getReducedType(type) : - type.flags & TypeFlags.Substitution ? writing ? (type as SubstitutionType).baseType : (type as SubstitutionType).substitute : - type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) : - type; - t = getSingleBaseForNonAugmentingSubtype(t) || t; - if (t === type) break; - type = t; - } - return type; + function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) { + return getObjectFlags(source) & ObjectFlags.JsxAttributes && isHyphenatedJsxName(sourceProp.escapedName); + } + + function getNormalizedType(type: Type, writing: boolean): Type { + while (true) { + let t = isFreshLiteralType(type) ? (type as FreshableType).regularType : + getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).node ? createTypeReference((type as TypeReference).target, getTypeArguments(type as TypeReference)) : + type.flags & TypeFlags.UnionOrIntersection ? getReducedType(type) : + type.flags & TypeFlags.Substitution ? writing ? (type as SubstitutionType).baseType : (type as SubstitutionType).substitute : + type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) : + type; + t = getSingleBaseForNonAugmentingSubtype(t) || t; + if (t === type) break; + type = t; } + return type; + } - /** - * Checks if 'source' is related to 'target' (e.g.: is a assignable to). - * @param source The left-hand-side of the relation. - * @param target The right-hand-side of the relation. - * @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'. - * Used as both to determine which checks are performed and as a cache of previously computed results. - * @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used. - * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. - * @param containingMessageChain A chain of errors to prepend any new errors found. - * @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy. - */ - function checkTypeRelatedTo( - source: Type, - target: Type, - relation: ESMap, - errorNode: Node | undefined, - headMessage?: DiagnosticMessage, - containingMessageChain?: () => DiagnosticMessageChain | undefined, - errorOutputContainer?: { errors?: Diagnostic[], skipLogging?: boolean }, - ): boolean { - - let errorInfo: DiagnosticMessageChain | undefined; - let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined; - let maybeKeys: string[]; - let sourceStack: Type[]; - let targetStack: Type[]; - let maybeCount = 0; - let sourceDepth = 0; - let targetDepth = 0; - let expandingFlags = ExpandingFlags.None; - let overflow = false; - let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid - let lastSkippedInfo: [Type, Type] | undefined; - let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = []; - let inPropertyCheck = false; - - Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); - - const result = isRelatedTo(source, target, RecursionFlags.Both, /*reportErrors*/ !!errorNode, headMessage); - if (incompatibleStack.length) { - reportIncompatibleStack(); - } - if (overflow) { - tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth }); - const diag = error(errorNode || currentNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target)); - if (errorOutputContainer) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); - } + /** + * Checks if 'source' is related to 'target' (e.g.: is a assignable to). + * @param source The left-hand-side of the relation. + * @param target The right-hand-side of the relation. + * @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'. + * Used as both to determine which checks are performed and as a cache of previously computed results. + * @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used. + * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. + * @param containingMessageChain A chain of errors to prepend any new errors found. + * @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy. + */ + function checkTypeRelatedTo( + source: Type, + target: Type, + relation: ESMap, + errorNode: Node | undefined, + headMessage?: DiagnosticMessage, + containingMessageChain?: () => DiagnosticMessageChain | undefined, + errorOutputContainer?: { errors?: Diagnostic[], skipLogging?: boolean }, + ): boolean { + + let errorInfo: DiagnosticMessageChain | undefined; + let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined; + let maybeKeys: string[]; + let sourceStack: Type[]; + let targetStack: Type[]; + let maybeCount = 0; + let sourceDepth = 0; + let targetDepth = 0; + let expandingFlags = ExpandingFlags.None; + let overflow = false; + let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid + let lastSkippedInfo: [Type, Type] | undefined; + let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = []; + let inPropertyCheck = false; + + Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); + + const result = isRelatedTo(source, target, RecursionFlags.Both, /*reportErrors*/ !!errorNode, headMessage); + if (incompatibleStack.length) { + reportIncompatibleStack(); + } + if (overflow) { + tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth }); + const diag = error(errorNode || currentNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target)); + if (errorOutputContainer) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } + } + else if (errorInfo) { + if (containingMessageChain) { + const chain = containingMessageChain(); + if (chain) { + concatenateDiagnosticMessageChains(chain, errorInfo); + errorInfo = chain; + } + } + + let relatedInformation: DiagnosticRelatedInformation[] | undefined; + // Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement + if (headMessage && errorNode && !result && source.symbol) { + const links = getSymbolLinks(source.symbol); + if (links.originatingImport && !isImportCall(links.originatingImport)) { + const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined); + if (helpfulRetry) { + // Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import + const diag = createDiagnosticForNode(links.originatingImport, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead); + relatedInformation = append(relatedInformation, diag); // Cause the error to appear with the error that triggered it + } + } + } + const diag = createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation); + if (relatedInfo) { + addRelatedInfo(diag, ...relatedInfo); } - else if (errorInfo) { - if (containingMessageChain) { - const chain = containingMessageChain(); - if (chain) { - concatenateDiagnosticMessageChains(chain, errorInfo); - errorInfo = chain; - } - } - - let relatedInformation: DiagnosticRelatedInformation[] | undefined; - // Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement - if (headMessage && errorNode && !result && source.symbol) { - const links = getSymbolLinks(source.symbol); - if (links.originatingImport && !isImportCall(links.originatingImport)) { - const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined); - if (helpfulRetry) { - // Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import - const diag = createDiagnosticForNode(links.originatingImport, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead); - relatedInformation = append(relatedInformation, diag); // Cause the error to appear with the error that triggered it - } - } - } - const diag = createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation); - if (relatedInfo) { - addRelatedInfo(diag, ...relatedInfo); - } - if (errorOutputContainer) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); - } - if (!errorOutputContainer || !errorOutputContainer.skipLogging) { - diagnostics.add(diag); - } + if (errorOutputContainer) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); } - if (errorNode && errorOutputContainer && errorOutputContainer.skipLogging && result === Ternary.False) { - Debug.assert(!!errorOutputContainer.errors, "missed opportunity to interact with error."); + if (!errorOutputContainer || !errorOutputContainer.skipLogging) { + diagnostics.add(diag); } + } + if (errorNode && errorOutputContainer && errorOutputContainer.skipLogging && result === Ternary.False) { + Debug.assert(!!errorOutputContainer.errors, "missed opportunity to interact with error."); + } - return result !== Ternary.False; + return result !== Ternary.False; - function resetErrorInfo(saved: ReturnType) { - errorInfo = saved.errorInfo; - lastSkippedInfo = saved.lastSkippedInfo; - incompatibleStack = saved.incompatibleStack; - overrideNextErrorInfo = saved.overrideNextErrorInfo; - relatedInfo = saved.relatedInfo; - } + function resetErrorInfo(saved: ReturnType) { + errorInfo = saved.errorInfo; + lastSkippedInfo = saved.lastSkippedInfo; + incompatibleStack = saved.incompatibleStack; + overrideNextErrorInfo = saved.overrideNextErrorInfo; + relatedInfo = saved.relatedInfo; + } - function captureErrorCalculationState() { - return { - errorInfo, - lastSkippedInfo, - incompatibleStack: incompatibleStack.slice(), - overrideNextErrorInfo, - relatedInfo: !relatedInfo ? undefined : relatedInfo.slice() as ([DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined) - }; - } + function captureErrorCalculationState() { + return { + errorInfo, + lastSkippedInfo, + incompatibleStack: incompatibleStack.slice(), + overrideNextErrorInfo, + relatedInfo: !relatedInfo ? undefined : relatedInfo.slice() as ([DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined) + }; + } - function reportIncompatibleError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number) { - overrideNextErrorInfo++; // Suppress the next relation error - lastSkippedInfo = undefined; // Reset skipped info cache - incompatibleStack.push([message, arg0, arg1, arg2, arg3]); - } + function reportIncompatibleError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number) { + overrideNextErrorInfo++; // Suppress the next relation error + lastSkippedInfo = undefined; // Reset skipped info cache + incompatibleStack.push([message, arg0, arg1, arg2, arg3]); + } - function reportIncompatibleStack() { - const stack = incompatibleStack; - incompatibleStack = []; - const info = lastSkippedInfo; - lastSkippedInfo = undefined; - if (stack.length === 1) { - reportError(...stack[0]); - if (info) { - // Actually do the last relation error - reportRelationError(/*headMessage*/ undefined, ...info); - } - return; + function reportIncompatibleStack() { + const stack = incompatibleStack; + incompatibleStack = []; + const info = lastSkippedInfo; + lastSkippedInfo = undefined; + if (stack.length === 1) { + reportError(...stack[0]); + if (info) { + // Actually do the last relation error + reportRelationError(/*headMessage*/ undefined, ...info); } - // The first error will be the innermost, while the last will be the outermost - so by popping off the end, - // we can build from left to right - let path = ""; - const secondaryRootErrors: typeof incompatibleStack = []; - while (stack.length) { - const [msg, ...args] = stack.pop()!; - switch (msg.code) { - case Diagnostics.Types_of_property_0_are_incompatible.code: { - // Parenthesize a `new` if there is one - if (path.indexOf("new ") === 0) { - path = `(${path})`; - } - const str = "" + args[0]; - // If leading, just print back the arg (irrespective of if it's a valid identifier) - if (path.length === 0) { - path = `${str}`; - } - // Otherwise write a dotted name if possible - else if (isIdentifierText(str, getEmitScriptTarget(compilerOptions))) { - path = `${path}.${str}`; - } - // Failing that, check if the name is already a computed name - else if (str[0] === "[" && str[str.length - 1] === "]") { - path = `${path}${str}`; - } - // And finally write out a computed name as a last resort - else { - path = `${path}[${str}]`; - } - break; + return; + } + // The first error will be the innermost, while the last will be the outermost - so by popping off the end, + // we can build from left to right + let path = ""; + const secondaryRootErrors: typeof incompatibleStack = []; + while (stack.length) { + const [msg, ...args] = stack.pop()!; + switch (msg.code) { + case Diagnostics.Types_of_property_0_are_incompatible.code: { + // Parenthesize a `new` if there is one + if (path.indexOf("new ") === 0) { + path = `(${path})`; + } + const str = "" + args[0]; + // If leading, just print back the arg (irrespective of if it's a valid identifier) + if (path.length === 0) { + path = `${str}`; + } + // Otherwise write a dotted name if possible + else if (isIdentifierText(str, getEmitScriptTarget(compilerOptions))) { + path = `${path}.${str}`; + } + // Failing that, check if the name is already a computed name + else if (str[0] === "[" && str[str.length - 1] === "]") { + path = `${path}${str}`; + } + // And finally write out a computed name as a last resort + else { + path = `${path}[${str}]`; } - case Diagnostics.Call_signature_return_types_0_and_1_are_incompatible.code: - case Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code: - case Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: - case Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: { - if (path.length === 0) { - // Don't flatten signature compatability errors at the start of a chain - instead prefer - // to unify (the with no arguments bit is excessive for printback) and print them back - let mappedMsg = msg; - if (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { - mappedMsg = Diagnostics.Call_signature_return_types_0_and_1_are_incompatible; - } - else if (msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { - mappedMsg = Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible; - } - secondaryRootErrors.unshift([mappedMsg, args[0], args[1]]); + break; + } + case Diagnostics.Call_signature_return_types_0_and_1_are_incompatible.code: + case Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code: + case Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: + case Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: { + if (path.length === 0) { + // Don't flatten signature compatability errors at the start of a chain - instead prefer + // to unify (the with no arguments bit is excessive for printback) and print them back + let mappedMsg = msg; + if (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { + mappedMsg = Diagnostics.Call_signature_return_types_0_and_1_are_incompatible; } - else { - const prefix = (msg.code === Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code || - msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) - ? "new " - : ""; - const params = (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code || - msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) - ? "" - : "..."; - path = `${prefix}${path}(${params})`; + else if (msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { + mappedMsg = Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible; } - break; - } - case Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target.code: { - secondaryRootErrors.unshift([Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, args[0], args[1]]); - break; + secondaryRootErrors.unshift([mappedMsg, args[0], args[1]]); } - case Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target.code: { - secondaryRootErrors.unshift([Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, args[0], args[1], args[2]]); - break; + else { + const prefix = (msg.code === Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code || + msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) + ? "new " + : ""; + const params = (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code || + msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) + ? "" + : "..."; + path = `${prefix}${path}(${params})`; } - default: - return Debug.fail(`Unhandled Diagnostic: ${msg.code}`); + break; } + case Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target.code: { + secondaryRootErrors.unshift([Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, args[0], args[1]]); + break; + } + case Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target.code: { + secondaryRootErrors.unshift([Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, args[0], args[1], args[2]]); + break; + } + default: + return Debug.fail(`Unhandled Diagnostic: ${msg.code}`); } - if (path) { - reportError(path[path.length - 1] === ")" - ? Diagnostics.The_types_returned_by_0_are_incompatible_between_these_types - : Diagnostics.The_types_of_0_are_incompatible_between_these_types, - path - ); - } - else { - // Remove the innermost secondary error as it will duplicate the error already reported by `reportRelationError` on entry - secondaryRootErrors.shift(); - } - for (const [msg, ...args] of secondaryRootErrors) { - const originalValue = msg.elidedInCompatabilityPyramid; - msg.elidedInCompatabilityPyramid = false; // Temporarily override elision to ensure error is reported - reportError(msg, ...args); - msg.elidedInCompatabilityPyramid = originalValue; - } - if (info) { - // Actually do the last relation error - reportRelationError(/*headMessage*/ undefined, ...info); - } } + if (path) { + reportError(path[path.length - 1] === ")" + ? Diagnostics.The_types_returned_by_0_are_incompatible_between_these_types + : Diagnostics.The_types_of_0_are_incompatible_between_these_types, + path + ); + } + else { + // Remove the innermost secondary error as it will duplicate the error already reported by `reportRelationError` on entry + secondaryRootErrors.shift(); + } + for (const [msg, ...args] of secondaryRootErrors) { + const originalValue = msg.elidedInCompatabilityPyramid; + msg.elidedInCompatabilityPyramid = false; // Temporarily override elision to ensure error is reported + reportError(msg, ...args); + msg.elidedInCompatabilityPyramid = originalValue; + } + if (info) { + // Actually do the last relation error + reportRelationError(/*headMessage*/ undefined, ...info); + } + } + + function reportError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { + Debug.assert(!!errorNode); + if (incompatibleStack.length) reportIncompatibleStack(); + if (message.elidedInCompatabilityPyramid) return; + errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2, arg3); + } + + function associateRelatedInfo(info: DiagnosticRelatedInformation) { + Debug.assert(!!errorInfo); + if (!relatedInfo) { + relatedInfo = [info]; + } + else { + relatedInfo.push(info); + } + } - function reportError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { - Debug.assert(!!errorNode); - if (incompatibleStack.length) reportIncompatibleStack(); - if (message.elidedInCompatabilityPyramid) return; - errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2, arg3); + function reportRelationError(message: DiagnosticMessage | undefined, source: Type, target: Type) { + if (incompatibleStack.length) reportIncompatibleStack(); + const [sourceType, targetType] = getTypeNamesForErrorDisplay(source, target); + let generalizedSource = source; + let generalizedSourceType = sourceType; + + if (isLiteralType(source) && !typeCouldHaveTopLevelSingletonTypes(target)) { + generalizedSource = getBaseTypeOfLiteralType(source); + Debug.assert(!isTypeAssignableTo(generalizedSource, target), "generalized source shouldn't be assignable"); + generalizedSourceType = getTypeNameForErrorDisplay(generalizedSource); } - function associateRelatedInfo(info: DiagnosticRelatedInformation) { - Debug.assert(!!errorInfo); - if (!relatedInfo) { - relatedInfo = [info]; + if (target.flags & TypeFlags.TypeParameter) { + const constraint = getBaseConstraintOfType(target); + let needsOriginalSource; + if (constraint && (isTypeAssignableTo(generalizedSource, constraint) || (needsOriginalSource = isTypeAssignableTo(source, constraint)))) { + reportError( + Diagnostics._0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, + needsOriginalSource ? sourceType : generalizedSourceType, + targetType, + typeToString(constraint), + ); } else { - relatedInfo.push(info); + errorInfo = undefined; + reportError( + Diagnostics._0_could_be_instantiated_with_an_arbitrary_type_which_could_be_unrelated_to_1, + targetType, + generalizedSourceType + ); } } - function reportRelationError(message: DiagnosticMessage | undefined, source: Type, target: Type) { - if (incompatibleStack.length) reportIncompatibleStack(); - const [sourceType, targetType] = getTypeNamesForErrorDisplay(source, target); - let generalizedSource = source; - let generalizedSourceType = sourceType; - - if (isLiteralType(source) && !typeCouldHaveTopLevelSingletonTypes(target)) { - generalizedSource = getBaseTypeOfLiteralType(source); - Debug.assert(!isTypeAssignableTo(generalizedSource, target), "generalized source shouldn't be assignable"); - generalizedSourceType = getTypeNameForErrorDisplay(generalizedSource); + if (!message) { + if (relation === comparableRelation) { + message = Diagnostics.Type_0_is_not_comparable_to_type_1; } - - if (target.flags & TypeFlags.TypeParameter) { - const constraint = getBaseConstraintOfType(target); - let needsOriginalSource; - if (constraint && (isTypeAssignableTo(generalizedSource, constraint) || (needsOriginalSource = isTypeAssignableTo(source, constraint)))) { - reportError( - Diagnostics._0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, - needsOriginalSource ? sourceType : generalizedSourceType, - targetType, - typeToString(constraint), - ); - } - else { - errorInfo = undefined; - reportError( - Diagnostics._0_could_be_instantiated_with_an_arbitrary_type_which_could_be_unrelated_to_1, - targetType, - generalizedSourceType - ); - } + else if (sourceType === targetType) { + message = Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated; } - - if (!message) { - if (relation === comparableRelation) { - message = Diagnostics.Type_0_is_not_comparable_to_type_1; - } - else if (sourceType === targetType) { - message = Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated; - } - else if (exactOptionalPropertyTypes && getExactOptionalUnassignableProperties(source, target).length) { - message = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties; - } - else { - if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.Union) { - const suggestedType = getSuggestedTypeForNonexistentStringLiteralType(source as StringLiteralType, target as UnionType); - if (suggestedType) { - reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, generalizedSourceType, targetType, typeToString(suggestedType)); - return; - } + else if (exactOptionalPropertyTypes && getExactOptionalUnassignableProperties(source, target).length) { + message = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties; + } + else { + if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.Union) { + const suggestedType = getSuggestedTypeForNonexistentStringLiteralType(source as StringLiteralType, target as UnionType); + if (suggestedType) { + reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, generalizedSourceType, targetType, typeToString(suggestedType)); + return; } - message = Diagnostics.Type_0_is_not_assignable_to_type_1; } + message = Diagnostics.Type_0_is_not_assignable_to_type_1; } - else if (message === Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1 - && exactOptionalPropertyTypes - && getExactOptionalUnassignableProperties(source, target).length) { - message = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties; - } - - reportError(message, generalizedSourceType, targetType); + } + else if (message === Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1 + && exactOptionalPropertyTypes + && getExactOptionalUnassignableProperties(source, target).length) { + message = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties; } - function tryElaborateErrorsForPrimitivesAndObjects(source: Type, target: Type) { - const sourceType = symbolValueDeclarationIsContextSensitive(source.symbol) ? typeToString(source, source.symbol.valueDeclaration) : typeToString(source); - const targetType = symbolValueDeclarationIsContextSensitive(target.symbol) ? typeToString(target, target.symbol.valueDeclaration) : typeToString(target); + reportError(message, generalizedSourceType, targetType); + } - if ((globalStringType === source && stringType === target) || - (globalNumberType === source && numberType === target) || - (globalBooleanType === source && booleanType === target) || - (getGlobalESSymbolType(/*reportErrors*/ false) === source && esSymbolType === target)) { - reportError(Diagnostics._0_is_a_primitive_but_1_is_a_wrapper_object_Prefer_using_0_when_possible, targetType, sourceType); - } + function tryElaborateErrorsForPrimitivesAndObjects(source: Type, target: Type) { + const sourceType = symbolValueDeclarationIsContextSensitive(source.symbol) ? typeToString(source, source.symbol.valueDeclaration) : typeToString(source); + const targetType = symbolValueDeclarationIsContextSensitive(target.symbol) ? typeToString(target, target.symbol.valueDeclaration) : typeToString(target); + + if ((globalStringType === source && stringType === target) || + (globalNumberType === source && numberType === target) || + (globalBooleanType === source && booleanType === target) || + (getGlobalESSymbolType(/*reportErrors*/ false) === source && esSymbolType === target)) { + reportError(Diagnostics._0_is_a_primitive_but_1_is_a_wrapper_object_Prefer_using_0_when_possible, targetType, sourceType); } + } + /** + * Try and elaborate array and tuple errors. Returns false + * if we have found an elaboration, or we should ignore + * any other elaborations when relating the `source` and + * `target` types. + */ + function tryElaborateArrayLikeErrors(source: Type, target: Type, reportErrors: boolean): boolean { /** - * Try and elaborate array and tuple errors. Returns false - * if we have found an elaboration, or we should ignore - * any other elaborations when relating the `source` and - * `target` types. + * The spec for elaboration is: + * - If the source is a readonly tuple and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. + * - If the source is a tuple then skip property elaborations if the target is an array or tuple. + * - If the source is a readonly array and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. + * - If the source an array then skip property elaborations if the target is a tuple. */ - function tryElaborateArrayLikeErrors(source: Type, target: Type, reportErrors: boolean): boolean { - /** - * The spec for elaboration is: - * - If the source is a readonly tuple and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. - * - If the source is a tuple then skip property elaborations if the target is an array or tuple. - * - If the source is a readonly array and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. - * - If the source an array then skip property elaborations if the target is a tuple. - */ - if (isTupleType(source)) { - if (source.target.readonly && isMutableArrayOrTuple(target)) { - if (reportErrors) { - reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); - } - return false; - } - return isTupleType(target) || isArrayType(target); - } - if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) { + if (isTupleType(source)) { + if (source.target.readonly && isMutableArrayOrTuple(target)) { if (reportErrors) { reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); } return false; } - if (isTupleType(target)) { - return isArrayType(source); + return isTupleType(target) || isArrayType(target); + } + if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) { + if (reportErrors) { + reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); } - return true; + return false; } - - function isRelatedToWorker(source: Type, target: Type, reportErrors: boolean) { - return isRelatedTo(source, target, RecursionFlags.Both, reportErrors); + if (isTupleType(target)) { + return isArrayType(source); } + return true; + } - /** - * Compare two types and return - * * Ternary.True if they are related with no assumptions, - * * Ternary.Maybe if they are related with assumptions of other relationships, or - * * Ternary.False if they are not related. - */ - function isRelatedTo(originalSource: Type, originalTarget: Type, recursionFlags: RecursionFlags = RecursionFlags.Both, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary { - // Before normalization: if `source` is type an object type, and `target` is primitive, - // skip all the checks we don't need and just return `isSimpleTypeRelatedTo` result - if (originalSource.flags & TypeFlags.Object && originalTarget.flags & TypeFlags.Primitive) { - if (isSimpleTypeRelatedTo(originalSource, originalTarget, relation, reportErrors ? reportError : undefined)) { - return Ternary.True; - } - reportErrorResults(originalSource, originalTarget, Ternary.False, !!(getObjectFlags(originalSource) & ObjectFlags.JsxAttributes)); - return Ternary.False; + function isRelatedToWorker(source: Type, target: Type, reportErrors: boolean) { + return isRelatedTo(source, target, RecursionFlags.Both, reportErrors); + } + + /** + * Compare two types and return + * * Ternary.True if they are related with no assumptions, + * * Ternary.Maybe if they are related with assumptions of other relationships, or + * * Ternary.False if they are not related. + */ + function isRelatedTo(originalSource: Type, originalTarget: Type, recursionFlags: RecursionFlags = RecursionFlags.Both, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary { + // Before normalization: if `source` is type an object type, and `target` is primitive, + // skip all the checks we don't need and just return `isSimpleTypeRelatedTo` result + if (originalSource.flags & TypeFlags.Object && originalTarget.flags & TypeFlags.Primitive) { + if (isSimpleTypeRelatedTo(originalSource, originalTarget, relation, reportErrors ? reportError : undefined)) { + return Ternary.True; } + reportErrorResults(originalSource, originalTarget, Ternary.False, !!(getObjectFlags(originalSource) & ObjectFlags.JsxAttributes)); + return Ternary.False; + } - // Normalize the source and target types: Turn fresh literal types into regular literal types, - // turn deferred type references into regular type references, simplify indexed access and - // conditional types, and resolve substitution types to either the substitution (on the source - // side) or the type variable (on the target side). - const source = getNormalizedType(originalSource, /*writing*/ false); - let target = getNormalizedType(originalTarget, /*writing*/ true); + // Normalize the source and target types: Turn fresh literal types into regular literal types, + // turn deferred type references into regular type references, simplify indexed access and + // conditional types, and resolve substitution types to either the substitution (on the source + // side) or the type variable (on the target side). + const source = getNormalizedType(originalSource, /*writing*/ false); + let target = getNormalizedType(originalTarget, /*writing*/ true); - if (source === target) return Ternary.True; + if (source === target) return Ternary.True; - if (relation === identityRelation) { - return isIdenticalTo(source, target, recursionFlags); - } + if (relation === identityRelation) { + return isIdenticalTo(source, target, recursionFlags); + } - // We fastpath comparing a type parameter to exactly its constraint, as this is _super_ common, - // and otherwise, for type parameters in large unions, causes us to need to compare the union to itself, - // as we break down the _target_ union first, _then_ get the source constraint - so for every - // member of the target, we attempt to find a match in the source. This avoids that in cases where - // the target is exactly the constraint. - if (source.flags & TypeFlags.TypeParameter && getConstraintOfType(source) === target) { - return Ternary.True; - } + // We fastpath comparing a type parameter to exactly its constraint, as this is _super_ common, + // and otherwise, for type parameters in large unions, causes us to need to compare the union to itself, + // as we break down the _target_ union first, _then_ get the source constraint - so for every + // member of the target, we attempt to find a match in the source. This avoids that in cases where + // the target is exactly the constraint. + if (source.flags & TypeFlags.TypeParameter && getConstraintOfType(source) === target) { + return Ternary.True; + } - // Try to see if we're relating something like `Foo` -> `Bar | null | undefined`. - // If so, reporting the `null` and `undefined` in the type is hardly useful. - // First, see if we're even relating an object type to a union. - // Then see if the target is stripped down to a single non-union type. - // Note - // * We actually want to remove null and undefined naively here (rather than using getNonNullableType), - // since we don't want to end up with a worse error like "`Foo` is not assignable to `NonNullable`" - // when dealing with generics. - // * We also don't deal with primitive source types, since we already halt elaboration below. - if (target.flags & TypeFlags.Union && source.flags & TypeFlags.Object && - (target as UnionType).types.length <= 3 && maybeTypeOfKind(target, TypeFlags.Nullable)) { - const nullStrippedTarget = extractTypesOfKind(target, ~TypeFlags.Nullable); - if (!(nullStrippedTarget.flags & (TypeFlags.Union | TypeFlags.Never))) { - target = getNormalizedType(nullStrippedTarget, /*writing*/ true); - } - if (source === nullStrippedTarget) return Ternary.True; + // Try to see if we're relating something like `Foo` -> `Bar | null | undefined`. + // If so, reporting the `null` and `undefined` in the type is hardly useful. + // First, see if we're even relating an object type to a union. + // Then see if the target is stripped down to a single non-union type. + // Note + // * We actually want to remove null and undefined naively here (rather than using getNonNullableType), + // since we don't want to end up with a worse error like "`Foo` is not assignable to `NonNullable`" + // when dealing with generics. + // * We also don't deal with primitive source types, since we already halt elaboration below. + if (target.flags & TypeFlags.Union && source.flags & TypeFlags.Object && + (target as UnionType).types.length <= 3 && maybeTypeOfKind(target, TypeFlags.Nullable)) { + const nullStrippedTarget = extractTypesOfKind(target, ~TypeFlags.Nullable); + if (!(nullStrippedTarget.flags & (TypeFlags.Union | TypeFlags.Never))) { + target = getNormalizedType(nullStrippedTarget, /*writing*/ true); } + if (source === nullStrippedTarget) return Ternary.True; + } - if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || - isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True; - - const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); - const isPerformingExcessPropertyChecks = !(intersectionState & IntersectionState.Target) && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral); - if (isPerformingExcessPropertyChecks) { - if (hasExcessProperties(source as FreshObjectLiteralType, target, reportErrors)) { - if (reportErrors) { - reportRelationError(headMessage, source, originalTarget.aliasSymbol ? originalTarget : target); - } - return Ternary.False; - } - } + if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || + isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True; - const isPerformingCommonPropertyChecks = relation !== comparableRelation && !(intersectionState & IntersectionState.Target) && - source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType && - target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) && - (getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source)); - if (isPerformingCommonPropertyChecks && !hasCommonProperties(source, target, isComparingJsxAttributes)) { + const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); + const isPerformingExcessPropertyChecks = !(intersectionState & IntersectionState.Target) && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral); + if (isPerformingExcessPropertyChecks) { + if (hasExcessProperties(source as FreshObjectLiteralType, target, reportErrors)) { if (reportErrors) { - const sourceString = typeToString(originalSource.aliasSymbol ? originalSource : source); - const targetString = typeToString(originalTarget.aliasSymbol ? originalTarget : target); - const calls = getSignaturesOfType(source, SignatureKind.Call); - const constructs = getSignaturesOfType(source, SignatureKind.Construct); - if (calls.length > 0 && isRelatedTo(getReturnTypeOfSignature(calls[0]), target, RecursionFlags.Source, /*reportErrors*/ false) || - constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, RecursionFlags.Source, /*reportErrors*/ false)) { - reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, sourceString, targetString); - } - else { - reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, sourceString, targetString); - } + reportRelationError(headMessage, source, originalTarget.aliasSymbol ? originalTarget : target); } return Ternary.False; } + } - traceUnionsOrIntersectionsTooLarge(source, target); - - let result = Ternary.False; - const saveErrorInfo = captureErrorCalculationState(); - - if ((source.flags & TypeFlags.Union || target.flags & TypeFlags.Union) && getConstituentCount(source) * getConstituentCount(target) < 4) { - // We skip caching when source or target is a union with no more than three constituents. - result = structuredTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck); - } - else if (source.flags & TypeFlags.UnionOrIntersection || target.flags & TypeFlags.UnionOrIntersection) { - result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck, recursionFlags); - } - if (!result && !(source.flags & TypeFlags.Union) && (source.flags & (TypeFlags.StructuredOrInstantiable) || target.flags & TypeFlags.StructuredOrInstantiable)) { - if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags)) { - resetErrorInfo(saveErrorInfo); + const isPerformingCommonPropertyChecks = relation !== comparableRelation && !(intersectionState & IntersectionState.Target) && + source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType && + target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) && + (getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source)); + if (isPerformingCommonPropertyChecks && !hasCommonProperties(source, target, isComparingJsxAttributes)) { + if (reportErrors) { + const sourceString = typeToString(originalSource.aliasSymbol ? originalSource : source); + const targetString = typeToString(originalTarget.aliasSymbol ? originalTarget : target); + const calls = getSignaturesOfType(source, SignatureKind.Call); + const constructs = getSignaturesOfType(source, SignatureKind.Construct); + if (calls.length > 0 && isRelatedTo(getReturnTypeOfSignature(calls[0]), target, RecursionFlags.Source, /*reportErrors*/ false) || + constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, RecursionFlags.Source, /*reportErrors*/ false)) { + reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, sourceString, targetString); + } + else { + reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, sourceString, targetString); } } - if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) { - // The combined constraint of an intersection type is the intersection of the constraints of - // the constituents. When an intersection type contains instantiable types with union type - // constraints, there are situations where we need to examine the combined constraint. One is - // when the target is a union type. Another is when the intersection contains types belonging - // to one of the disjoint domains. For example, given type variables T and U, each with the - // constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and - // we need to check this constraint against a union on the target side. Also, given a type - // variable V constrained to 'string | number', 'V & number' has a combined constraint of - // 'string & number | number & number' which reduces to just 'number'. - // This also handles type parameters, as a type parameter with a union constraint compared against a union - // needs to have its constraint hoisted into an intersection with said type parameter, this way - // the type param can be compared with itself in the target (with the influence of its constraint to match other parts) - // For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)` - const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union)); - if (constraint && (source.flags & TypeFlags.Intersection || target.flags & TypeFlags.Union)) { - if (everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself - // TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this - if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { - resetErrorInfo(saveErrorInfo); - } + return Ternary.False; + } + + traceUnionsOrIntersectionsTooLarge(source, target); + + let result = Ternary.False; + const saveErrorInfo = captureErrorCalculationState(); + + if ((source.flags & TypeFlags.Union || target.flags & TypeFlags.Union) && getConstituentCount(source) * getConstituentCount(target) < 4) { + // We skip caching when source or target is a union with no more than three constituents. + result = structuredTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck); + } + else if (source.flags & TypeFlags.UnionOrIntersection || target.flags & TypeFlags.UnionOrIntersection) { + result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck, recursionFlags); + } + if (!result && !(source.flags & TypeFlags.Union) && (source.flags & (TypeFlags.StructuredOrInstantiable) || target.flags & TypeFlags.StructuredOrInstantiable)) { + if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags)) { + resetErrorInfo(saveErrorInfo); + } + } + if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) { + // The combined constraint of an intersection type is the intersection of the constraints of + // the constituents. When an intersection type contains instantiable types with union type + // constraints, there are situations where we need to examine the combined constraint. One is + // when the target is a union type. Another is when the intersection contains types belonging + // to one of the disjoint domains. For example, given type variables T and U, each with the + // constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and + // we need to check this constraint against a union on the target side. Also, given a type + // variable V constrained to 'string | number', 'V & number' has a combined constraint of + // 'string & number | number & number' which reduces to just 'number'. + // This also handles type parameters, as a type parameter with a union constraint compared against a union + // needs to have its constraint hoisted into an intersection with said type parameter, this way + // the type param can be compared with itself in the target (with the influence of its constraint to match other parts) + // For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)` + const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union)); + if (constraint && (source.flags & TypeFlags.Intersection || target.flags & TypeFlags.Union)) { + if (everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself + // TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this + if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { + resetErrorInfo(saveErrorInfo); } } } + } - // For certain combinations involving intersections and optional, excess, or mismatched properties we need - // an extra property check where the intersection is viewed as a single object. The following are motivating - // examples that all should be errors, but aren't without this extra property check: - // - // let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property - // - // declare let wrong: { a: { y: string } }; - // let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type - // - // function foo(x: { a?: string }, y: T & { a: boolean }) { - // x = y; // Mismatched property in source intersection - // } - // - // We suppress recursive intersection property checks because they can generate lots of work when relating - // recursive intersections that are structurally similar but not exactly identical. See #37854. - if (result && !inPropertyCheck && ( - target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) || - isNonGenericObjectType(target) && !isArrayType(target) && !isTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { - inPropertyCheck = true; - result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck, recursionFlags); - inPropertyCheck = false; - } + // For certain combinations involving intersections and optional, excess, or mismatched properties we need + // an extra property check where the intersection is viewed as a single object. The following are motivating + // examples that all should be errors, but aren't without this extra property check: + // + // let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property + // + // declare let wrong: { a: { y: string } }; + // let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type + // + // function foo(x: { a?: string }, y: T & { a: boolean }) { + // x = y; // Mismatched property in source intersection + // } + // + // We suppress recursive intersection property checks because they can generate lots of work when relating + // recursive intersections that are structurally similar but not exactly identical. See #37854. + if (result && !inPropertyCheck && ( + target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) || + isNonGenericObjectType(target) && !isArrayType(target) && !isTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { + inPropertyCheck = true; + result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck, recursionFlags); + inPropertyCheck = false; + } - reportErrorResults(source, target, result, isComparingJsxAttributes); - return result; + reportErrorResults(source, target, result, isComparingJsxAttributes); + return result; - function reportErrorResults(source: Type, target: Type, result: Ternary, isComparingJsxAttributes: boolean) { - if (!result && reportErrors) { - const sourceHasBase = !!getSingleBaseForNonAugmentingSubtype(originalSource); - const targetHasBase = !!getSingleBaseForNonAugmentingSubtype(originalTarget); - source = (originalSource.aliasSymbol || sourceHasBase) ? originalSource : source; - target = (originalTarget.aliasSymbol || targetHasBase) ? originalTarget : target; - let maybeSuppress = overrideNextErrorInfo > 0; - if (maybeSuppress) { - overrideNextErrorInfo--; - } - if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { - const currentError = errorInfo; - tryElaborateArrayLikeErrors(source, target, reportErrors); - if (errorInfo !== currentError) { - maybeSuppress = !!errorInfo; - } - } - if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) { - tryElaborateErrorsForPrimitivesAndObjects(source, target); - } - else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) { - reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead); - } - else if (isComparingJsxAttributes && target.flags & TypeFlags.Intersection) { - const targetTypes = (target as IntersectionType).types; - const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode); - const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode); - if (!isErrorType(intrinsicAttributes) && !isErrorType(intrinsicClassAttributes) && - (contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))) { - // do not report top error - return result; - } - } - else { - errorInfo = elaborateNeverIntersection(errorInfo, originalTarget); - } - if (!headMessage && maybeSuppress) { - lastSkippedInfo = [source, target]; - // Used by, eg, missing property checking to replace the top-level message with a more informative one + function reportErrorResults(source: Type, target: Type, result: Ternary, isComparingJsxAttributes: boolean) { + if (!result && reportErrors) { + const sourceHasBase = !!getSingleBaseForNonAugmentingSubtype(originalSource); + const targetHasBase = !!getSingleBaseForNonAugmentingSubtype(originalTarget); + source = (originalSource.aliasSymbol || sourceHasBase) ? originalSource : source; + target = (originalTarget.aliasSymbol || targetHasBase) ? originalTarget : target; + let maybeSuppress = overrideNextErrorInfo > 0; + if (maybeSuppress) { + overrideNextErrorInfo--; + } + if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { + const currentError = errorInfo; + tryElaborateArrayLikeErrors(source, target, reportErrors); + if (errorInfo !== currentError) { + maybeSuppress = !!errorInfo; + } + } + if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) { + tryElaborateErrorsForPrimitivesAndObjects(source, target); + } + else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) { + reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead); + } + else if (isComparingJsxAttributes && target.flags & TypeFlags.Intersection) { + const targetTypes = (target as IntersectionType).types; + const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode); + const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode); + if (!isErrorType(intrinsicAttributes) && !isErrorType(intrinsicClassAttributes) && + (contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))) { + // do not report top error return result; } - reportRelationError(headMessage, source, target); } + else { + errorInfo = elaborateNeverIntersection(errorInfo, originalTarget); + } + if (!headMessage && maybeSuppress) { + lastSkippedInfo = [source, target]; + // Used by, eg, missing property checking to replace the top-level message with a more informative one + return result; + } + reportRelationError(headMessage, source, target); } } + } - function traceUnionsOrIntersectionsTooLarge(source: Type, target: Type): void { - if (!tracing) { - return; - } + function traceUnionsOrIntersectionsTooLarge(source: Type, target: Type): void { + if (!tracing) { + return; + } - if ((source.flags & TypeFlags.UnionOrIntersection) && (target.flags & TypeFlags.UnionOrIntersection)) { - const sourceUnionOrIntersection = source as UnionOrIntersectionType; - const targetUnionOrIntersection = target as UnionOrIntersectionType; + if ((source.flags & TypeFlags.UnionOrIntersection) && (target.flags & TypeFlags.UnionOrIntersection)) { + const sourceUnionOrIntersection = source as UnionOrIntersectionType; + const targetUnionOrIntersection = target as UnionOrIntersectionType; - if (sourceUnionOrIntersection.objectFlags & targetUnionOrIntersection.objectFlags & ObjectFlags.PrimitiveUnion) { - // There's a fast path for comparing primitive unions - return; - } + if (sourceUnionOrIntersection.objectFlags & targetUnionOrIntersection.objectFlags & ObjectFlags.PrimitiveUnion) { + // There's a fast path for comparing primitive unions + return; + } - const sourceSize = sourceUnionOrIntersection.types.length; - const targetSize = targetUnionOrIntersection.types.length; - if (sourceSize * targetSize > 1E6) { - tracing.instant(tracing.Phase.CheckTypes, "traceUnionsOrIntersectionsTooLarge_DepthLimit", { - sourceId: source.id, - sourceSize, - targetId: target.id, - targetSize, - pos: errorNode?.pos, - end: errorNode?.end - }); - } + const sourceSize = sourceUnionOrIntersection.types.length; + const targetSize = targetUnionOrIntersection.types.length; + if (sourceSize * targetSize > 1E6) { + tracing.instant(tracing.Phase.CheckTypes, "traceUnionsOrIntersectionsTooLarge_DepthLimit", { + sourceId: source.id, + sourceSize, + targetId: target.id, + targetSize, + pos: errorNode?.pos, + end: errorNode?.end + }); } } + } - function isIdenticalTo(source: Type, target: Type, recursionFlags: RecursionFlags): Ternary { - if (source.flags !== target.flags) return Ternary.False; - if (source.flags & TypeFlags.Singleton) return Ternary.True; - traceUnionsOrIntersectionsTooLarge(source, target); - if (source.flags & TypeFlags.UnionOrIntersection) { - let result = eachTypeRelatedToSomeType(source as UnionOrIntersectionType, target as UnionOrIntersectionType); - if (result) { - result &= eachTypeRelatedToSomeType(target as UnionOrIntersectionType, source as UnionOrIntersectionType); - } - return result; + function isIdenticalTo(source: Type, target: Type, recursionFlags: RecursionFlags): Ternary { + if (source.flags !== target.flags) return Ternary.False; + if (source.flags & TypeFlags.Singleton) return Ternary.True; + traceUnionsOrIntersectionsTooLarge(source, target); + if (source.flags & TypeFlags.UnionOrIntersection) { + let result = eachTypeRelatedToSomeType(source as UnionOrIntersectionType, target as UnionOrIntersectionType); + if (result) { + result &= eachTypeRelatedToSomeType(target as UnionOrIntersectionType, source as UnionOrIntersectionType); } - return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None, recursionFlags); + return result; } + return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None, recursionFlags); + } - function getTypeOfPropertyInTypes(types: Type[], name: __String) { - const appendPropType = (propTypes: Type[] | undefined, type: Type) => { - type = getApparentType(type); - const prop = type.flags & TypeFlags.UnionOrIntersection ? getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name) : getPropertyOfObjectType(type, name); - const propType = prop && getTypeOfSymbol(prop) || getApplicableIndexInfoForName(type, name)?.type || undefinedType; - return append(propTypes, propType); - }; - return getUnionType(reduceLeft(types, appendPropType, /*initial*/ undefined) || emptyArray); - } + function getTypeOfPropertyInTypes(types: Type[], name: __String) { + const appendPropType = (propTypes: Type[] | undefined, type: Type) => { + type = getApparentType(type); + const prop = type.flags & TypeFlags.UnionOrIntersection ? getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name) : getPropertyOfObjectType(type, name); + const propType = prop && getTypeOfSymbol(prop) || getApplicableIndexInfoForName(type, name)?.type || undefinedType; + return append(propTypes, propType); + }; + return getUnionType(reduceLeft(types, appendPropType, /*initial*/ undefined) || emptyArray); + } - function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean { - if (!isExcessPropertyCheckTarget(target) || !noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) { - return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny - } - const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); - if ((relation === assignableRelation || relation === comparableRelation) && - (isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) { - return false; - } - let reducedTarget = target; - let checkTypes: Type[] | undefined; - if (target.flags & TypeFlags.Union) { - reducedTarget = findMatchingDiscriminantType(source, target as UnionType, isRelatedTo) || filterPrimitivesIfContainsNonPrimitive(target as UnionType); - checkTypes = reducedTarget.flags & TypeFlags.Union ? (reducedTarget as UnionType).types : [reducedTarget]; - } - for (const prop of getPropertiesOfType(source)) { - if (shouldCheckAsExcessProperty(prop, source.symbol) && !isIgnoredJsxProperty(source, prop)) { - if (!isKnownProperty(reducedTarget, prop.escapedName, isComparingJsxAttributes)) { - if (reportErrors) { - // Report error in terms of object types in the target as those are the only ones - // we check in isKnownProperty. - const errorTarget = filterType(reducedTarget, isExcessPropertyCheckTarget); - // We know *exactly* where things went wrong when comparing the types. - // Use this property as the error node as this will be more helpful in - // reasoning about what went wrong. - if (!errorNode) return Debug.fail(); - if (isJsxAttributes(errorNode) || isJsxOpeningLikeElement(errorNode) || isJsxOpeningLikeElement(errorNode.parent)) { - // JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal. - // However, using an object-literal error message will be very confusing to the users so we give different a message. - if (prop.valueDeclaration && isJsxAttribute(prop.valueDeclaration) && getSourceFileOfNode(errorNode) === getSourceFileOfNode(prop.valueDeclaration.name)) { - // Note that extraneous children (as in `extra`) don't pass this check, - // since `children` is a SyntaxKind.PropertySignature instead of a SyntaxKind.JsxAttribute. - errorNode = prop.valueDeclaration.name; - } - const propName = symbolToString(prop); - const suggestionSymbol = getSuggestedSymbolForNonexistentJSXAttribute(propName, errorTarget); - const suggestion = suggestionSymbol ? symbolToString(suggestionSymbol) : undefined; - if (suggestion) { - reportError(Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName, typeToString(errorTarget), suggestion); - } - else { - reportError(Diagnostics.Property_0_does_not_exist_on_type_1, propName, typeToString(errorTarget)); - } + function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean { + if (!isExcessPropertyCheckTarget(target) || !noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) { + return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny + } + const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); + if ((relation === assignableRelation || relation === comparableRelation) && + (isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) { + return false; + } + let reducedTarget = target; + let checkTypes: Type[] | undefined; + if (target.flags & TypeFlags.Union) { + reducedTarget = findMatchingDiscriminantType(source, target as UnionType, isRelatedTo) || filterPrimitivesIfContainsNonPrimitive(target as UnionType); + checkTypes = reducedTarget.flags & TypeFlags.Union ? (reducedTarget as UnionType).types : [reducedTarget]; + } + for (const prop of getPropertiesOfType(source)) { + if (shouldCheckAsExcessProperty(prop, source.symbol) && !isIgnoredJsxProperty(source, prop)) { + if (!isKnownProperty(reducedTarget, prop.escapedName, isComparingJsxAttributes)) { + if (reportErrors) { + // Report error in terms of object types in the target as those are the only ones + // we check in isKnownProperty. + const errorTarget = filterType(reducedTarget, isExcessPropertyCheckTarget); + // We know *exactly* where things went wrong when comparing the types. + // Use this property as the error node as this will be more helpful in + // reasoning about what went wrong. + if (!errorNode) return Debug.fail(); + if (isJsxAttributes(errorNode) || isJsxOpeningLikeElement(errorNode) || isJsxOpeningLikeElement(errorNode.parent)) { + // JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal. + // However, using an object-literal error message will be very confusing to the users so we give different a message. + if (prop.valueDeclaration && isJsxAttribute(prop.valueDeclaration) && getSourceFileOfNode(errorNode) === getSourceFileOfNode(prop.valueDeclaration.name)) { + // Note that extraneous children (as in `extra`) don't pass this check, + // since `children` is a SyntaxKind.PropertySignature instead of a SyntaxKind.JsxAttribute. + errorNode = prop.valueDeclaration.name; + } + const propName = symbolToString(prop); + const suggestionSymbol = getSuggestedSymbolForNonexistentJSXAttribute(propName, errorTarget); + const suggestion = suggestionSymbol ? symbolToString(suggestionSymbol) : undefined; + if (suggestion) { + reportError(Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName, typeToString(errorTarget), suggestion); } else { - // use the property's value declaration if the property is assigned inside the literal itself - const objectLiteralDeclaration = source.symbol?.declarations && firstOrUndefined(source.symbol.declarations); - let suggestion: string | undefined; - if (prop.valueDeclaration && findAncestor(prop.valueDeclaration, d => d === objectLiteralDeclaration) && getSourceFileOfNode(objectLiteralDeclaration) === getSourceFileOfNode(errorNode)) { - const propDeclaration = prop.valueDeclaration as ObjectLiteralElementLike; - Debug.assertNode(propDeclaration, isObjectLiteralElementLike); - - errorNode = propDeclaration; - - const name = propDeclaration.name!; - if (isIdentifier(name)) { - suggestion = getSuggestionForNonexistentProperty(name, errorTarget); - } - } - if (suggestion !== undefined) { - reportError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2, - symbolToString(prop), typeToString(errorTarget), suggestion); - } - else { - reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, - symbolToString(prop), typeToString(errorTarget)); + reportError(Diagnostics.Property_0_does_not_exist_on_type_1, propName, typeToString(errorTarget)); + } + } + else { + // use the property's value declaration if the property is assigned inside the literal itself + const objectLiteralDeclaration = source.symbol?.declarations && firstOrUndefined(source.symbol.declarations); + let suggestion: string | undefined; + if (prop.valueDeclaration && findAncestor(prop.valueDeclaration, d => d === objectLiteralDeclaration) && getSourceFileOfNode(objectLiteralDeclaration) === getSourceFileOfNode(errorNode)) { + const propDeclaration = prop.valueDeclaration as ObjectLiteralElementLike; + Debug.assertNode(propDeclaration, isObjectLiteralElementLike); + + errorNode = propDeclaration; + + const name = propDeclaration.name!; + if (isIdentifier(name)) { + suggestion = getSuggestionForNonexistentProperty(name, errorTarget); } } + if (suggestion !== undefined) { + reportError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2, + symbolToString(prop), typeToString(errorTarget), suggestion); + } + else { + reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, + symbolToString(prop), typeToString(errorTarget)); + } } - return true; } - if (checkTypes && !isRelatedTo(getTypeOfSymbol(prop), getTypeOfPropertyInTypes(checkTypes, prop.escapedName), RecursionFlags.Both, reportErrors)) { - if (reportErrors) { - reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(prop)); - } - return true; + return true; + } + if (checkTypes && !isRelatedTo(getTypeOfSymbol(prop), getTypeOfPropertyInTypes(checkTypes, prop.escapedName), RecursionFlags.Both, reportErrors)) { + if (reportErrors) { + reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(prop)); } + return true; } } - return false; } + return false; + } - function shouldCheckAsExcessProperty(prop: Symbol, container: Symbol) { - return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration; - } + function shouldCheckAsExcessProperty(prop: Symbol, container: Symbol) { + return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration; + } - function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary { - let result = Ternary.True; - const sourceTypes = source.types; - for (const sourceType of sourceTypes) { - const related = typeRelatedToSomeType(sourceType, target, /*reportErrors*/ false); - if (!related) { - return Ternary.False; - } - result &= related; + function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary { + let result = Ternary.True; + const sourceTypes = source.types; + for (const sourceType of sourceTypes) { + const related = typeRelatedToSomeType(sourceType, target, /*reportErrors*/ false); + if (!related) { + return Ternary.False; } - return result; + result &= related; } + return result; + } - function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { - const targetTypes = target.types; - if (target.flags & TypeFlags.Union) { - if (containsType(targetTypes, source)) { - return Ternary.True; - } - const match = getMatchingUnionConstituentForType(target as UnionType, source); - if (match) { - const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false); - if (related) { - return related; - } - } + function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { + const targetTypes = target.types; + if (target.flags & TypeFlags.Union) { + if (containsType(targetTypes, source)) { + return Ternary.True; } - for (const type of targetTypes) { - const related = isRelatedTo(source, type, RecursionFlags.Target, /*reportErrors*/ false); + const match = getMatchingUnionConstituentForType(target as UnionType, source); + if (match) { + const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false); if (related) { return related; } } - if (reportErrors) { - const bestMatchingType = getBestMatchingType(source, target, isRelatedTo); - isRelatedTo(source, bestMatchingType || targetTypes[targetTypes.length - 1], RecursionFlags.Target, /*reportErrors*/ true); + } + for (const type of targetTypes) { + const related = isRelatedTo(source, type, RecursionFlags.Target, /*reportErrors*/ false); + if (related) { + return related; } - return Ternary.False; } + if (reportErrors) { + const bestMatchingType = getBestMatchingType(source, target, isRelatedTo); + isRelatedTo(source, bestMatchingType || targetTypes[targetTypes.length - 1], RecursionFlags.Target, /*reportErrors*/ true); + } + return Ternary.False; + } - function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - let result = Ternary.True; - const targetTypes = target.types; - for (const targetType of targetTypes) { - const related = isRelatedTo(source, targetType, RecursionFlags.Target, reportErrors, /*headMessage*/ undefined, intersectionState); - if (!related) { - return Ternary.False; - } - result &= related; + function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + let result = Ternary.True; + const targetTypes = target.types; + for (const targetType of targetTypes) { + const related = isRelatedTo(source, targetType, RecursionFlags.Target, reportErrors, /*headMessage*/ undefined, intersectionState); + if (!related) { + return Ternary.False; } - return result; + result &= related; } + return result; + } - function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - const sourceTypes = source.types; - if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) { - return Ternary.True; + function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + const sourceTypes = source.types; + if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) { + return Ternary.True; + } + const len = sourceTypes.length; + for (let i = 0; i < len; i++) { + const related = isRelatedTo(sourceTypes[i], target, RecursionFlags.Source, reportErrors && i === len - 1, /*headMessage*/ undefined, intersectionState); + if (related) { + return related; } - const len = sourceTypes.length; - for (let i = 0; i < len; i++) { - const related = isRelatedTo(sourceTypes[i], target, RecursionFlags.Source, reportErrors && i === len - 1, /*headMessage*/ undefined, intersectionState); + } + return Ternary.False; + } + + function getUndefinedStrippedTargetIfNeeded(source: Type, target: Type) { + // As a builtin type, `undefined` is a very low type ID - making it almsot always first, making this a very fast check to see + // if we need to strip `undefined` from the target + if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union && + !((source as UnionType).types[0].flags & TypeFlags.Undefined) && (target as UnionType).types[0].flags & TypeFlags.Undefined) { + return extractTypesOfKind(target, ~TypeFlags.Undefined); + } + return target; + } + + function eachTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + let result = Ternary.True; + const sourceTypes = source.types; + // We strip `undefined` from the target if the `source` trivially doesn't contain it for our correspondence-checking fastpath + // since `undefined` is frequently added by optionality and would otherwise spoil a potentially useful correspondence + const undefinedStrippedTarget = getUndefinedStrippedTargetIfNeeded(source, target as UnionType); + for (let i = 0; i < sourceTypes.length; i++) { + const sourceType = sourceTypes[i]; + if (undefinedStrippedTarget.flags & TypeFlags.Union && sourceTypes.length >= (undefinedStrippedTarget as UnionType).types.length && sourceTypes.length % (undefinedStrippedTarget as UnionType).types.length === 0) { + // many unions are mappings of one another; in such cases, simply comparing members at the same index can shortcut the comparison + // such unions will have identical lengths, and their corresponding elements will match up. Another common scenario is where a large + // union has a union of objects intersected with it. In such cases, if the input was, eg `("a" | "b" | "c") & (string | boolean | {} | {whatever})`, + // the result will have the structure `"a" | "b" | "c" | "a" & {} | "b" & {} | "c" & {} | "a" & {whatever} | "b" & {whatever} | "c" & {whatever}` + // - the resulting union has a length which is a multiple of the original union, and the elements correspond modulo the length of the original union + const related = isRelatedTo(sourceType, (undefinedStrippedTarget as UnionType).types[i % (undefinedStrippedTarget as UnionType).types.length], RecursionFlags.Both, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); if (related) { - return related; + result &= related; + continue; } } - return Ternary.False; - } - - function getUndefinedStrippedTargetIfNeeded(source: Type, target: Type) { - // As a builtin type, `undefined` is a very low type ID - making it almsot always first, making this a very fast check to see - // if we need to strip `undefined` from the target - if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union && - !((source as UnionType).types[0].flags & TypeFlags.Undefined) && (target as UnionType).types[0].flags & TypeFlags.Undefined) { - return extractTypesOfKind(target, ~TypeFlags.Undefined); + const related = isRelatedTo(sourceType, target, RecursionFlags.Source, reportErrors, /*headMessage*/ undefined, intersectionState); + if (!related) { + return Ternary.False; } - return target; + result &= related; } + return result; + } - function eachTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - let result = Ternary.True; - const sourceTypes = source.types; - // We strip `undefined` from the target if the `source` trivially doesn't contain it for our correspondence-checking fastpath - // since `undefined` is frequently added by optionality and would otherwise spoil a potentially useful correspondence - const undefinedStrippedTarget = getUndefinedStrippedTargetIfNeeded(source, target as UnionType); - for (let i = 0; i < sourceTypes.length; i++) { - const sourceType = sourceTypes[i]; - if (undefinedStrippedTarget.flags & TypeFlags.Union && sourceTypes.length >= (undefinedStrippedTarget as UnionType).types.length && sourceTypes.length % (undefinedStrippedTarget as UnionType).types.length === 0) { - // many unions are mappings of one another; in such cases, simply comparing members at the same index can shortcut the comparison - // such unions will have identical lengths, and their corresponding elements will match up. Another common scenario is where a large - // union has a union of objects intersected with it. In such cases, if the input was, eg `("a" | "b" | "c") & (string | boolean | {} | {whatever})`, - // the result will have the structure `"a" | "b" | "c" | "a" & {} | "b" & {} | "c" & {} | "a" & {whatever} | "b" & {whatever} | "c" & {whatever}` - // - the resulting union has a length which is a multiple of the original union, and the elements correspond modulo the length of the original union - const related = isRelatedTo(sourceType, (undefinedStrippedTarget as UnionType).types[i % (undefinedStrippedTarget as UnionType).types.length], RecursionFlags.Both, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); + function typeArgumentsRelatedTo(sources: readonly Type[] = emptyArray, targets: readonly Type[] = emptyArray, variances: readonly VarianceFlags[] = emptyArray, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + if (sources.length !== targets.length && relation === identityRelation) { + return Ternary.False; + } + const length = sources.length <= targets.length ? sources.length : targets.length; + let result = Ternary.True; + for (let i = 0; i < length; i++) { + // When variance information isn't available we default to covariance. This happens + // in the process of computing variance information for recursive types and when + // comparing 'this' type arguments. + const varianceFlags = i < variances.length ? variances[i] : VarianceFlags.Covariant; + const variance = varianceFlags & VarianceFlags.VarianceMask; + // We ignore arguments for independent type parameters (because they're never witnessed). + if (variance !== VarianceFlags.Independent) { + const s = sources[i]; + const t = targets[i]; + let related = Ternary.True; + if (varianceFlags & VarianceFlags.Unmeasurable) { + // Even an `Unmeasurable` variance works out without a structural check if the source and target are _identical_. + // We can't simply assume invariance, because `Unmeasurable` marks nonlinear relations, for example, a relation tained by + // the `-?` modifier in a mapped type (where, no matter how the inputs are related, the outputs still might not be) + related = relation === identityRelation ? isRelatedTo(s, t, RecursionFlags.Both, /*reportErrors*/ false) : compareTypesIdentical(s, t); + } + else if (variance === VarianceFlags.Covariant) { + related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + } + else if (variance === VarianceFlags.Contravariant) { + related = isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + } + else if (variance === VarianceFlags.Bivariant) { + // In the bivariant case we first compare contravariantly without reporting + // errors. Then, if that doesn't succeed, we compare covariantly with error + // reporting. Thus, error elaboration will be based on the the covariant check, + // which is generally easier to reason about. + related = isRelatedTo(t, s, RecursionFlags.Both, /*reportErrors*/ false); + if (!related) { + related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + } + } + else { + // In the invariant case we first compare covariantly, and only when that + // succeeds do we proceed to compare contravariantly. Thus, error elaboration + // will typically be based on the covariant check. + related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); if (related) { - result &= related; - continue; + related &= isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); } } - const related = isRelatedTo(sourceType, target, RecursionFlags.Source, reportErrors, /*headMessage*/ undefined, intersectionState); if (!related) { return Ternary.False; } result &= related; } - return result; } + return result; + } - function typeArgumentsRelatedTo(sources: readonly Type[] = emptyArray, targets: readonly Type[] = emptyArray, variances: readonly VarianceFlags[] = emptyArray, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - if (sources.length !== targets.length && relation === identityRelation) { - return Ternary.False; + // Determine if possibly recursive types are related. First, check if the result is already available in the global cache. + // Second, check if we have already started a comparison of the given two types in which case we assume the result to be true. + // Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are + // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion + // and issue an error. Otherwise, actually compare the structure of the two types. + function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, recursionFlags: RecursionFlags): Ternary { + if (overflow) { + return Ternary.False; + } + const keyIntersectionState = intersectionState | (inPropertyCheck ? IntersectionState.InPropertyCheck : 0); + const id = getRelationKey(source, target, keyIntersectionState, relation, /*ingnoreConstraints*/ false); + const entry = relation.get(id); + if (entry !== undefined) { + if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) { + // We are elaborating errors and the cached result is an unreported failure. The result will be reported + // as a failure, and should be updated as a reported failure by the bottom of this function. } - const length = sources.length <= targets.length ? sources.length : targets.length; - let result = Ternary.True; - for (let i = 0; i < length; i++) { - // When variance information isn't available we default to covariance. This happens - // in the process of computing variance information for recursive types and when - // comparing 'this' type arguments. - const varianceFlags = i < variances.length ? variances[i] : VarianceFlags.Covariant; - const variance = varianceFlags & VarianceFlags.VarianceMask; - // We ignore arguments for independent type parameters (because they're never witnessed). - if (variance !== VarianceFlags.Independent) { - const s = sources[i]; - const t = targets[i]; - let related = Ternary.True; - if (varianceFlags & VarianceFlags.Unmeasurable) { - // Even an `Unmeasurable` variance works out without a structural check if the source and target are _identical_. - // We can't simply assume invariance, because `Unmeasurable` marks nonlinear relations, for example, a relation tained by - // the `-?` modifier in a mapped type (where, no matter how the inputs are related, the outputs still might not be) - related = relation === identityRelation ? isRelatedTo(s, t, RecursionFlags.Both, /*reportErrors*/ false) : compareTypesIdentical(s, t); - } - else if (variance === VarianceFlags.Covariant) { - related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - } - else if (variance === VarianceFlags.Contravariant) { - related = isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - } - else if (variance === VarianceFlags.Bivariant) { - // In the bivariant case we first compare contravariantly without reporting - // errors. Then, if that doesn't succeed, we compare covariantly with error - // reporting. Thus, error elaboration will be based on the the covariant check, - // which is generally easier to reason about. - related = isRelatedTo(t, s, RecursionFlags.Both, /*reportErrors*/ false); - if (!related) { - related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - } - } - else { - // In the invariant case we first compare covariantly, and only when that - // succeeds do we proceed to compare contravariantly. Thus, error elaboration - // will typically be based on the covariant check. - related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - if (related) { - related &= isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - } + else { + if (outofbandVarianceMarkerHandler) { + // We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component + const saved = entry & RelationComparisonResult.ReportsMask; + if (saved & RelationComparisonResult.ReportsUnmeasurable) { + instantiateType(source, makeFunctionTypeMapper(reportUnmeasurableMarkers)); } - if (!related) { - return Ternary.False; + if (saved & RelationComparisonResult.ReportsUnreliable) { + instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers)); } - result &= related; } + return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False; } - return result; } - - // Determine if possibly recursive types are related. First, check if the result is already available in the global cache. - // Second, check if we have already started a comparison of the given two types in which case we assume the result to be true. - // Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are - // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion - // and issue an error. Otherwise, actually compare the structure of the two types. - function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, recursionFlags: RecursionFlags): Ternary { - if (overflow) { + if (!maybeKeys) { + maybeKeys = []; + sourceStack = []; + targetStack = []; + } + else { + // A key that starts with "*" is an indication that we have type references that reference constrained + // type parameters. For such keys we also check against the key we would have gotten if all type parameters + // were unconstrained. + const broadestEquivalentId = id.startsWith("*") ? getRelationKey(source, target, keyIntersectionState, relation, /*ignoreConstraints*/ true) : undefined; + for (let i = 0; i < maybeCount; i++) { + // If source and target are already being compared, consider them related with assumptions + if (id === maybeKeys[i] || broadestEquivalentId && broadestEquivalentId === maybeKeys[i]) { + return Ternary.Maybe; + } + } + if (sourceDepth === 100 || targetDepth === 100) { + overflow = true; return Ternary.False; } - const keyIntersectionState = intersectionState | (inPropertyCheck ? IntersectionState.InPropertyCheck : 0); - const id = getRelationKey(source, target, keyIntersectionState, relation, /*ingnoreConstraints*/ false); - const entry = relation.get(id); - if (entry !== undefined) { - if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) { - // We are elaborating errors and the cached result is an unreported failure. The result will be reported - // as a failure, and should be updated as a reported failure by the bottom of this function. - } - else { - if (outofbandVarianceMarkerHandler) { - // We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component - const saved = entry & RelationComparisonResult.ReportsMask; - if (saved & RelationComparisonResult.ReportsUnmeasurable) { - instantiateType(source, makeFunctionTypeMapper(reportUnmeasurableMarkers)); - } - if (saved & RelationComparisonResult.ReportsUnreliable) { - instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers)); - } + } + const maybeStart = maybeCount; + maybeKeys[maybeCount] = id; + maybeCount++; + const saveExpandingFlags = expandingFlags; + if (recursionFlags & RecursionFlags.Source) { + sourceStack[sourceDepth] = source; + sourceDepth++; + if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, sourceDepth)) expandingFlags |= ExpandingFlags.Source; + } + if (recursionFlags & RecursionFlags.Target) { + targetStack[targetDepth] = target; + targetDepth++; + if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, targetDepth)) expandingFlags |= ExpandingFlags.Target; + } + let originalHandler: typeof outofbandVarianceMarkerHandler; + let propagatingVarianceFlags: RelationComparisonResult = 0; + if (outofbandVarianceMarkerHandler) { + originalHandler = outofbandVarianceMarkerHandler; + outofbandVarianceMarkerHandler = onlyUnreliable => { + propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable; + return originalHandler!(onlyUnreliable); + }; + } + + if (expandingFlags === ExpandingFlags.Both) { + tracing?.instant(tracing.Phase.CheckTypes, "recursiveTypeRelatedTo_DepthLimit", { + sourceId: source.id, + sourceIdStack: sourceStack.map(t => t.id), + targetId: target.id, + targetIdStack: targetStack.map(t => t.id), + depth: sourceDepth, + targetDepth + }); + } + + const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, intersectionState) : Ternary.Maybe; + if (outofbandVarianceMarkerHandler) { + outofbandVarianceMarkerHandler = originalHandler; + } + if (recursionFlags & RecursionFlags.Source) { + sourceDepth--; + } + if (recursionFlags & RecursionFlags.Target) { + targetDepth--; + } + expandingFlags = saveExpandingFlags; + if (result) { + if (result === Ternary.True || (sourceDepth === 0 && targetDepth === 0)) { + if (result === Ternary.True || result === Ternary.Maybe) { + // If result is definitely true, record all maybe keys as having succeeded. Also, record Ternary.Maybe + // results as having succeeded once we reach depth 0, but never record Ternary.Unknown results. + for (let i = maybeStart; i < maybeCount; i++) { + relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags); } - return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False; } + maybeCount = maybeStart; } - if (!maybeKeys) { - maybeKeys = []; - sourceStack = []; - targetStack = []; - } - else { - // A key that starts with "*" is an indication that we have type references that reference constrained - // type parameters. For such keys we also check against the key we would have gotten if all type parameters - // were unconstrained. - const broadestEquivalentId = id.startsWith("*") ? getRelationKey(source, target, keyIntersectionState, relation, /*ignoreConstraints*/ true) : undefined; - for (let i = 0; i < maybeCount; i++) { - // If source and target are already being compared, consider them related with assumptions - if (id === maybeKeys[i] || broadestEquivalentId && broadestEquivalentId === maybeKeys[i]) { - return Ternary.Maybe; - } - } - if (sourceDepth === 100 || targetDepth === 100) { - overflow = true; - return Ternary.False; - } + } + else { + // A false result goes straight into global cache (when something is false under + // assumptions it will also be false without assumptions) + relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags); + maybeCount = maybeStart; + } + return result; + } + + function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + tracing?.push(tracing.Phase.CheckTypes, "structuredTypeRelatedTo", { sourceId: source.id, targetId: target.id }); + const result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState); + tracing?.pop(); + return result; + } + + function structuredTypeRelatedToWorker(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + if (intersectionState & IntersectionState.PropertyCheck) { + return propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None); + } + if (intersectionState & IntersectionState.UnionIntersectionCheck) { + // Note that these checks are specifically ordered to produce correct results. In particular, + // we need to deconstruct unions before intersections (because unions are always at the top), + // and we need to handle "each" relations before "some" relations for the same kind of type. + if (source.flags & TypeFlags.Union) { + return relation === comparableRelation ? + someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck) : + eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck); } - const maybeStart = maybeCount; - maybeKeys[maybeCount] = id; - maybeCount++; - const saveExpandingFlags = expandingFlags; - if (recursionFlags & RecursionFlags.Source) { - sourceStack[sourceDepth] = source; - sourceDepth++; - if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, sourceDepth)) expandingFlags |= ExpandingFlags.Source; - } - if (recursionFlags & RecursionFlags.Target) { - targetStack[targetDepth] = target; - targetDepth++; - if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, targetDepth)) expandingFlags |= ExpandingFlags.Target; - } - let originalHandler: typeof outofbandVarianceMarkerHandler; - let propagatingVarianceFlags: RelationComparisonResult = 0; - if (outofbandVarianceMarkerHandler) { - originalHandler = outofbandVarianceMarkerHandler; - outofbandVarianceMarkerHandler = onlyUnreliable => { - propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable; - return originalHandler!(onlyUnreliable); - }; + if (target.flags & TypeFlags.Union) { + return typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target as UnionType, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive)); } - - if (expandingFlags === ExpandingFlags.Both) { - tracing?.instant(tracing.Phase.CheckTypes, "recursiveTypeRelatedTo_DepthLimit", { - sourceId: source.id, - sourceIdStack: sourceStack.map(t => t.id), - targetId: target.id, - targetIdStack: targetStack.map(t => t.id), - depth: sourceDepth, - targetDepth - }); + if (target.flags & TypeFlags.Intersection) { + return typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors, IntersectionState.Target); } - - const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, intersectionState) : Ternary.Maybe; - if (outofbandVarianceMarkerHandler) { - outofbandVarianceMarkerHandler = originalHandler; + // Source is an intersection. For the comparable relation, if the target is a primitive type we hoist the + // constraints of all non-primitive types in the source into a new intersection. We do this because the + // intersection may further constrain the constraints of the non-primitive types. For example, given a type + // parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't + // appear to be comparable to '2'. + if (relation === comparableRelation && target.flags & TypeFlags.Primitive) { + const constraints = sameMap((source as IntersectionType).types, getBaseConstraintOrType); + if (constraints !== (source as IntersectionType).types) { + source = getIntersectionType(constraints); + if (!(source.flags & TypeFlags.Intersection)) { + return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false); + } + } } - if (recursionFlags & RecursionFlags.Source) { - sourceDepth--; + // Check to see if any constituents of the intersection are immediately related to the target. + // + // Don't report errors though. Checking whether a constituent is related to the source is not actually + // useful and leads to some confusing error messages. Instead it is better to let the below checks + // take care of this, or to not elaborate at all. For instance, + // + // - For an object type (such as 'C = A & B'), users are usually more interested in structural errors. + // + // - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection + // than to report that 'D' is not assignable to 'A' or 'B'. + // + // - For a primitive type or type parameter (such as 'number = A & B') there is no point in + // breaking the intersection apart. + return someTypeRelatedToType(source as IntersectionType, target, /*reportErrors*/ false, IntersectionState.Source); + } + const flags = source.flags & target.flags; + if (relation === identityRelation && !(flags & TypeFlags.Object)) { + if (flags & TypeFlags.Index) { + return isRelatedTo((source as IndexType).type, (target as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false); } - if (recursionFlags & RecursionFlags.Target) { - targetDepth--; + let result = Ternary.False; + if (flags & TypeFlags.IndexedAccess) { + if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, /*reportErrors*/ false)) { + if (result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, /*reportErrors*/ false)) { + return result; + } + } } - expandingFlags = saveExpandingFlags; - if (result) { - if (result === Ternary.True || (sourceDepth === 0 && targetDepth === 0)) { - if (result === Ternary.True || result === Ternary.Maybe) { - // If result is definitely true, record all maybe keys as having succeeded. Also, record Ternary.Maybe - // results as having succeeded once we reach depth 0, but never record Ternary.Unknown results. - for (let i = maybeStart; i < maybeCount; i++) { - relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags); + if (flags & TypeFlags.Conditional) { + if ((source as ConditionalType).root.isDistributive === (target as ConditionalType).root.isDistributive) { + if (result = isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both, /*reportErrors*/ false)) { + if (result &= isRelatedTo((source as ConditionalType).extendsType, (target as ConditionalType).extendsType, RecursionFlags.Both, /*reportErrors*/ false)) { + if (result &= isRelatedTo(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, /*reportErrors*/ false)) { + if (result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, /*reportErrors*/ false)) { + return result; + } + } } } - maybeCount = maybeStart; } } - else { - // A false result goes straight into global cache (when something is false under - // assumptions it will also be false without assumptions) - relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags); - maybeCount = maybeStart; + if (flags & TypeFlags.Substitution) { + return isRelatedTo((source as SubstitutionType).substitute, (target as SubstitutionType).substitute, RecursionFlags.Both, /*reportErrors*/ false); + } + return Ternary.False; + } + + let result: Ternary; + let originalErrorInfo: DiagnosticMessageChain | undefined; + let varianceCheckFailed = false; + const saveErrorInfo = captureErrorCalculationState(); + + // We limit alias variance probing to only object and conditional types since their alias behavior + // is more predictable than other, interned types, which may or may not have an alias depending on + // the order in which things were checked. + if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && + source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol && + !(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) { + const variances = getAliasVariances(source.aliasSymbol); + if (variances === emptyArray) { + return Ternary.Unknown; + } + const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState); + if (varianceResult !== undefined) { + return varianceResult; } - return result; } - function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - tracing?.push(tracing.Phase.CheckTypes, "structuredTypeRelatedTo", { sourceId: source.id, targetId: target.id }); - const result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState); - tracing?.pop(); + // For a generic type T and a type U that is assignable to T, [...U] is assignable to T, U is assignable to readonly [...T], + // and U is assignable to [...T] when U is constrained to a mutable array or tuple type. + if (isSingleElementGenericTupleType(source) && !source.target.readonly && (result = isRelatedTo(getTypeArguments(source)[0], target, RecursionFlags.Source)) || + isSingleElementGenericTupleType(target) && (target.target.readonly || isMutableArrayOrTuple(getBaseConstraintOfType(source) || source)) && (result = isRelatedTo(source, getTypeArguments(target)[0], RecursionFlags.Target))) { return result; } - function structuredTypeRelatedToWorker(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - if (intersectionState & IntersectionState.PropertyCheck) { - return propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None); - } - if (intersectionState & IntersectionState.UnionIntersectionCheck) { - // Note that these checks are specifically ordered to produce correct results. In particular, - // we need to deconstruct unions before intersections (because unions are always at the top), - // and we need to handle "each" relations before "some" relations for the same kind of type. - if (source.flags & TypeFlags.Union) { - return relation === comparableRelation ? - someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck) : - eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck); - } - if (target.flags & TypeFlags.Union) { - return typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target as UnionType, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive)); - } - if (target.flags & TypeFlags.Intersection) { - return typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors, IntersectionState.Target); - } - // Source is an intersection. For the comparable relation, if the target is a primitive type we hoist the - // constraints of all non-primitive types in the source into a new intersection. We do this because the - // intersection may further constrain the constraints of the non-primitive types. For example, given a type - // parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't - // appear to be comparable to '2'. - if (relation === comparableRelation && target.flags & TypeFlags.Primitive) { - const constraints = sameMap((source as IntersectionType).types, getBaseConstraintOrType); - if (constraints !== (source as IntersectionType).types) { - source = getIntersectionType(constraints); - if (!(source.flags & TypeFlags.Intersection)) { - return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false); - } - } - } - // Check to see if any constituents of the intersection are immediately related to the target. - // - // Don't report errors though. Checking whether a constituent is related to the source is not actually - // useful and leads to some confusing error messages. Instead it is better to let the below checks - // take care of this, or to not elaborate at all. For instance, - // - // - For an object type (such as 'C = A & B'), users are usually more interested in structural errors. - // - // - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection - // than to report that 'D' is not assignable to 'A' or 'B'. - // - // - For a primitive type or type parameter (such as 'number = A & B') there is no point in - // breaking the intersection apart. - return someTypeRelatedToType(source as IntersectionType, target, /*reportErrors*/ false, IntersectionState.Source); - } - const flags = source.flags & target.flags; - if (relation === identityRelation && !(flags & TypeFlags.Object)) { - if (flags & TypeFlags.Index) { - return isRelatedTo((source as IndexType).type, (target as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false); - } - let result = Ternary.False; - if (flags & TypeFlags.IndexedAccess) { - if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, /*reportErrors*/ false)) { - if (result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, /*reportErrors*/ false)) { - return result; - } - } - } - if (flags & TypeFlags.Conditional) { - if ((source as ConditionalType).root.isDistributive === (target as ConditionalType).root.isDistributive) { - if (result = isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both, /*reportErrors*/ false)) { - if (result &= isRelatedTo((source as ConditionalType).extendsType, (target as ConditionalType).extendsType, RecursionFlags.Both, /*reportErrors*/ false)) { - if (result &= isRelatedTo(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, /*reportErrors*/ false)) { - if (result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, /*reportErrors*/ false)) { - return result; - } - } - } - } + if (target.flags & TypeFlags.TypeParameter) { + // A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q]. + if (getObjectFlags(source) & ObjectFlags.Mapped && !(source as MappedType).declaration.nameType && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType(source as MappedType), RecursionFlags.Both)) { + + if (!(getMappedTypeModifiers(source as MappedType) & MappedTypeModifiers.IncludeOptional)) { + const templateType = getTemplateTypeFromMappedType(source as MappedType); + const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source as MappedType)); + if (result = isRelatedTo(templateType, indexedAccessType, RecursionFlags.Both, reportErrors)) { + return result; } } - if (flags & TypeFlags.Substitution) { - return isRelatedTo((source as SubstitutionType).substitute, (target as SubstitutionType).substitute, RecursionFlags.Both, /*reportErrors*/ false); - } - return Ternary.False; } - - let result: Ternary; - let originalErrorInfo: DiagnosticMessageChain | undefined; - let varianceCheckFailed = false; - const saveErrorInfo = captureErrorCalculationState(); - - // We limit alias variance probing to only object and conditional types since their alias behavior - // is more predictable than other, interned types, which may or may not have an alias depending on - // the order in which things were checked. - if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && - source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol && - !(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) { - const variances = getAliasVariances(source.aliasSymbol); - if (variances === emptyArray) { - return Ternary.Unknown; - } - const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState); - if (varianceResult !== undefined) { - return varianceResult; + } + else if (target.flags & TypeFlags.Index) { + const targetType = (target as IndexType).type; + // A keyof S is related to a keyof T if T is related to S. + if (source.flags & TypeFlags.Index) { + if (result = isRelatedTo(targetType, (source as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false)) { + return result; } } - - // For a generic type T and a type U that is assignable to T, [...U] is assignable to T, U is assignable to readonly [...T], - // and U is assignable to [...T] when U is constrained to a mutable array or tuple type. - if (isSingleElementGenericTupleType(source) && !source.target.readonly && (result = isRelatedTo(getTypeArguments(source)[0], target, RecursionFlags.Source)) || - isSingleElementGenericTupleType(target) && (target.target.readonly || isMutableArrayOrTuple(getBaseConstraintOfType(source) || source)) && (result = isRelatedTo(source, getTypeArguments(target)[0], RecursionFlags.Target))) { - return result; - } - - if (target.flags & TypeFlags.TypeParameter) { - // A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q]. - if (getObjectFlags(source) & ObjectFlags.Mapped && !(source as MappedType).declaration.nameType && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType(source as MappedType), RecursionFlags.Both)) { - - if (!(getMappedTypeModifiers(source as MappedType) & MappedTypeModifiers.IncludeOptional)) { - const templateType = getTemplateTypeFromMappedType(source as MappedType); - const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source as MappedType)); - if (result = isRelatedTo(templateType, indexedAccessType, RecursionFlags.Both, reportErrors)) { - return result; - } - } + if (isTupleType(targetType)) { + // An index type can have a tuple type target when the tuple type contains variadic elements. + // Check if the source is related to the known keys of the tuple type. + if (result = isRelatedTo(source, getKnownKeysOfTupleType(targetType), RecursionFlags.Target, reportErrors)) { + return result; } } - else if (target.flags & TypeFlags.Index) { - const targetType = (target as IndexType).type; - // A keyof S is related to a keyof T if T is related to S. - if (source.flags & TypeFlags.Index) { - if (result = isRelatedTo(targetType, (source as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false)) { - return result; + else { + // A type S is assignable to keyof T if S is assignable to keyof C, where C is the + // simplified form of T or, if T doesn't simplify, the constraint of T. + const constraint = getSimplifiedTypeOrConstraint(targetType); + if (constraint) { + // We require Ternary.True here such that circular constraints don't cause + // false positives. For example, given 'T extends { [K in keyof T]: string }', + // 'keyof T' has itself as its constraint and produces a Ternary.Maybe when + // related to other types. + if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), RecursionFlags.Target, reportErrors) === Ternary.True) { + return Ternary.True; } } - if (isTupleType(targetType)) { - // An index type can have a tuple type target when the tuple type contains variadic elements. - // Check if the source is related to the known keys of the tuple type. - if (result = isRelatedTo(source, getKnownKeysOfTupleType(targetType), RecursionFlags.Target, reportErrors)) { - return result; + else if (isGenericMappedType(targetType)) { + // generic mapped types that don't simplify or have a constraint still have a very simple set of keys we can compare against + // - their nameType or constraintType. + // In many ways, this comparison is a deferred version of what `getIndexTypeForMappedType` does to actually resolve the keys for _non_-generic types + + const nameType = getNameTypeFromMappedType(targetType); + const constraintType = getConstraintTypeFromMappedType(targetType); + let targetKeys; + if (nameType && isMappedTypeWithKeyofConstraintDeclaration(targetType)) { + // we need to get the apparent mappings and union them with the generic mappings, since some properties may be + // missing from the `constraintType` which will otherwise be mapped in the object + const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType)); + const mappedKeys: Type[] = []; + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType( + modifiersType, + TypeFlags.StringOrNumberLiteralOrUnique, + /*stringsOnly*/ false, + t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(targetType.mapper, getTypeParameterFromMappedType(targetType), t))) + ); + // We still need to include the non-apparent (and thus still generic) keys in the target side of the comparison (in case they're in the source side) + targetKeys = getUnionType([...mappedKeys, nameType]); + } + else { + targetKeys = nameType || constraintType; + } + if (isRelatedTo(source, targetKeys, RecursionFlags.Target, reportErrors) === Ternary.True) { + return Ternary.True; } } - else { - // A type S is assignable to keyof T if S is assignable to keyof C, where C is the - // simplified form of T or, if T doesn't simplify, the constraint of T. - const constraint = getSimplifiedTypeOrConstraint(targetType); + } + } + else if (target.flags & TypeFlags.IndexedAccess) { + if (source.flags & TypeFlags.IndexedAccess) { + // Relate components directly before falling back to constraint relationships + // A type S[K] is related to a type T[J] if S is related to T and K is related to J. + if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, reportErrors)) { + result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, reportErrors); + } + if (result) { + resetErrorInfo(saveErrorInfo); + return result; + } + if (reportErrors) { + originalErrorInfo = errorInfo; + } + } + // A type S is related to a type T[K] if S is related to C, where C is the base + // constraint of T[K] for writing. + if (relation === assignableRelation || relation === comparableRelation) { + const objectType = (target as IndexedAccessType).objectType; + const indexType = (target as IndexedAccessType).indexType; + const baseObjectType = getBaseConstraintOfType(objectType) || objectType; + const baseIndexType = getBaseConstraintOfType(indexType) || indexType; + if (!isGenericObjectType(baseObjectType) && !isGenericIndexType(baseIndexType)) { + const accessFlags = AccessFlags.Writing | (baseObjectType !== objectType ? AccessFlags.NoIndexSignatures : 0); + const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, accessFlags); if (constraint) { - // We require Ternary.True here such that circular constraints don't cause - // false positives. For example, given 'T extends { [K in keyof T]: string }', - // 'keyof T' has itself as its constraint and produces a Ternary.Maybe when - // related to other types. - if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), RecursionFlags.Target, reportErrors) === Ternary.True) { - return Ternary.True; - } - } - else if (isGenericMappedType(targetType)) { - // generic mapped types that don't simplify or have a constraint still have a very simple set of keys we can compare against - // - their nameType or constraintType. - // In many ways, this comparison is a deferred version of what `getIndexTypeForMappedType` does to actually resolve the keys for _non_-generic types - - const nameType = getNameTypeFromMappedType(targetType); - const constraintType = getConstraintTypeFromMappedType(targetType); - let targetKeys; - if (nameType && isMappedTypeWithKeyofConstraintDeclaration(targetType)) { - // we need to get the apparent mappings and union them with the generic mappings, since some properties may be - // missing from the `constraintType` which will otherwise be mapped in the object - const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType)); - const mappedKeys: Type[] = []; - forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType( - modifiersType, - TypeFlags.StringOrNumberLiteralOrUnique, - /*stringsOnly*/ false, - t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(targetType.mapper, getTypeParameterFromMappedType(targetType), t))) - ); - // We still need to include the non-apparent (and thus still generic) keys in the target side of the comparison (in case they're in the source side) - targetKeys = getUnionType([...mappedKeys, nameType]); + if (reportErrors && originalErrorInfo) { + // create a new chain for the constraint error + resetErrorInfo(saveErrorInfo); } - else { - targetKeys = nameType || constraintType; + if (result = isRelatedTo(source, constraint, RecursionFlags.Target, reportErrors)) { + return result; } - if (isRelatedTo(source, targetKeys, RecursionFlags.Target, reportErrors) === Ternary.True) { - return Ternary.True; + // prefer the shorter chain of the constraint comparison chain, and the direct comparison chain + if (reportErrors && originalErrorInfo && errorInfo) { + errorInfo = countMessageChainBreadth([originalErrorInfo]) <= countMessageChainBreadth([errorInfo]) ? originalErrorInfo : errorInfo; } } } } - else if (target.flags & TypeFlags.IndexedAccess) { - if (source.flags & TypeFlags.IndexedAccess) { - // Relate components directly before falling back to constraint relationships - // A type S[K] is related to a type T[J] if S is related to T and K is related to J. - if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, reportErrors)) { - result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, reportErrors); - } - if (result) { - resetErrorInfo(saveErrorInfo); - return result; - } - if (reportErrors) { - originalErrorInfo = errorInfo; - } - } - // A type S is related to a type T[K] if S is related to C, where C is the base - // constraint of T[K] for writing. - if (relation === assignableRelation || relation === comparableRelation) { - const objectType = (target as IndexedAccessType).objectType; - const indexType = (target as IndexedAccessType).indexType; - const baseObjectType = getBaseConstraintOfType(objectType) || objectType; - const baseIndexType = getBaseConstraintOfType(indexType) || indexType; - if (!isGenericObjectType(baseObjectType) && !isGenericIndexType(baseIndexType)) { - const accessFlags = AccessFlags.Writing | (baseObjectType !== objectType ? AccessFlags.NoIndexSignatures : 0); - const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, accessFlags); - if (constraint) { - if (reportErrors && originalErrorInfo) { - // create a new chain for the constraint error - resetErrorInfo(saveErrorInfo); - } - if (result = isRelatedTo(source, constraint, RecursionFlags.Target, reportErrors)) { + if (reportErrors) { + originalErrorInfo = undefined; + } + } + else if (isGenericMappedType(target) && relation !== identityRelation) { + // Check if source type `S` is related to target type `{ [P in Q]: T }` or `{ [P in Q as R]: T}`. + const keysRemapped = !!target.declaration.nameType; + const templateType = getTemplateTypeFromMappedType(target); + const modifiers = getMappedTypeModifiers(target); + if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { + // If the mapped type has shape `{ [P in Q]: T[P] }`, + // source `S` is related to target if `T` = `S`, i.e. `S` is related to `{ [P in Q]: S[P] }`. + if (!keysRemapped && templateType.flags & TypeFlags.IndexedAccess && (templateType as IndexedAccessType).objectType === source && + (templateType as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) { + return Ternary.True; + } + if (!isGenericMappedType(source)) { + // If target has shape `{ [P in Q as R]: T}`, then its keys have type `R`. + // If target has shape `{ [P in Q]: T }`, then its keys have type `Q`. + const targetKeys = keysRemapped ? getNameTypeFromMappedType(target)! : getConstraintTypeFromMappedType(target); + // Type of the keys of source type `S`, i.e. `keyof S`. + const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true); + const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional; + const filteredByApplicability = includeOptional ? intersectTypes(targetKeys, sourceKeys) : undefined; + // A source type `S` is related to a target type `{ [P in Q]: T }` if `Q` is related to `keyof S` and `S[Q]` is related to `T`. + // A source type `S` is related to a target type `{ [P in Q as R]: T }` if `R` is related to `keyof S` and `S[R]` is related to `T. + // A source type `S` is related to a target type `{ [P in Q]?: T }` if some constituent `Q'` of `Q` is related to `keyof S` and `S[Q']` is related to `T`. + // A source type `S` is related to a target type `{ [P in Q as R]?: T }` if some constituent `R'` of `R` is related to `keyof S` and `S[R']` is related to `T`. + if (includeOptional + ? !(filteredByApplicability!.flags & TypeFlags.Never) + : isRelatedTo(targetKeys, sourceKeys, RecursionFlags.Both)) { + const templateType = getTemplateTypeFromMappedType(target); + const typeParameter = getTypeParameterFromMappedType(target); + + // Fastpath: When the template type has the form `Obj[P]` where `P` is the mapped type parameter, directly compare source `S` with `Obj` + // to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `S[P]`. + const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable); + if (!keysRemapped && nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) { + if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, RecursionFlags.Target, reportErrors)) { return result; } - // prefer the shorter chain of the constraint comparison chain, and the direct comparison chain - if (reportErrors && originalErrorInfo && errorInfo) { - errorInfo = countMessageChainBreadth([originalErrorInfo]) <= countMessageChainBreadth([errorInfo]) ? originalErrorInfo : errorInfo; - } } - } - } - if (reportErrors) { - originalErrorInfo = undefined; - } - } - else if (isGenericMappedType(target) && relation !== identityRelation) { - // Check if source type `S` is related to target type `{ [P in Q]: T }` or `{ [P in Q as R]: T}`. - const keysRemapped = !!target.declaration.nameType; - const templateType = getTemplateTypeFromMappedType(target); - const modifiers = getMappedTypeModifiers(target); - if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { - // If the mapped type has shape `{ [P in Q]: T[P] }`, - // source `S` is related to target if `T` = `S`, i.e. `S` is related to `{ [P in Q]: S[P] }`. - if (!keysRemapped && templateType.flags & TypeFlags.IndexedAccess && (templateType as IndexedAccessType).objectType === source && - (templateType as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) { - return Ternary.True; - } - if (!isGenericMappedType(source)) { - // If target has shape `{ [P in Q as R]: T}`, then its keys have type `R`. - // If target has shape `{ [P in Q]: T }`, then its keys have type `Q`. - const targetKeys = keysRemapped ? getNameTypeFromMappedType(target)! : getConstraintTypeFromMappedType(target); - // Type of the keys of source type `S`, i.e. `keyof S`. - const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true); - const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional; - const filteredByApplicability = includeOptional ? intersectTypes(targetKeys, sourceKeys) : undefined; - // A source type `S` is related to a target type `{ [P in Q]: T }` if `Q` is related to `keyof S` and `S[Q]` is related to `T`. - // A source type `S` is related to a target type `{ [P in Q as R]: T }` if `R` is related to `keyof S` and `S[R]` is related to `T. - // A source type `S` is related to a target type `{ [P in Q]?: T }` if some constituent `Q'` of `Q` is related to `keyof S` and `S[Q']` is related to `T`. - // A source type `S` is related to a target type `{ [P in Q as R]?: T }` if some constituent `R'` of `R` is related to `keyof S` and `S[R']` is related to `T`. - if (includeOptional - ? !(filteredByApplicability!.flags & TypeFlags.Never) - : isRelatedTo(targetKeys, sourceKeys, RecursionFlags.Both)) { - const templateType = getTemplateTypeFromMappedType(target); - const typeParameter = getTypeParameterFromMappedType(target); - - // Fastpath: When the template type has the form `Obj[P]` where `P` is the mapped type parameter, directly compare source `S` with `Obj` - // to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `S[P]`. - const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable); - if (!keysRemapped && nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) { - if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, RecursionFlags.Target, reportErrors)) { - return result; - } - } - else { - // We need to compare the type of a property on the source type `S` to the type of the same property on the target type, - // so we need to construct an indexing type representing a property, and then use indexing type to index the source type for comparison. - - // If the target type has shape `{ [P in Q]: T }`, then a property of the target has type `P`. - // If the target type has shape `{ [P in Q]?: T }`, then a property of the target has type `P`, - // but the property is optional, so we only want to compare properties `P` that are common between `keyof S` and `Q`. - // If the target type has shape `{ [P in Q as R]: T }`, then a property of the target has type `R`. - // If the target type has shape `{ [P in Q as R]?: T }`, then a property of the target has type `R`, - // but the property is optional, so we only want to compare properties `R` that are common between `keyof S` and `R`. - const indexingType = keysRemapped - ? (filteredByApplicability || targetKeys) - : filteredByApplicability - ? getIntersectionType([filteredByApplicability, typeParameter]) - : typeParameter; - const indexedAccessType = getIndexedAccessType(source, indexingType); - // Compare `S[indexingType]` to `T`, where `T` is the type of a property of the target type. - if (result = isRelatedTo(indexedAccessType, templateType, RecursionFlags.Both, reportErrors)) { - return result; - } + else { + // We need to compare the type of a property on the source type `S` to the type of the same property on the target type, + // so we need to construct an indexing type representing a property, and then use indexing type to index the source type for comparison. + + // If the target type has shape `{ [P in Q]: T }`, then a property of the target has type `P`. + // If the target type has shape `{ [P in Q]?: T }`, then a property of the target has type `P`, + // but the property is optional, so we only want to compare properties `P` that are common between `keyof S` and `Q`. + // If the target type has shape `{ [P in Q as R]: T }`, then a property of the target has type `R`. + // If the target type has shape `{ [P in Q as R]?: T }`, then a property of the target has type `R`, + // but the property is optional, so we only want to compare properties `R` that are common between `keyof S` and `R`. + const indexingType = keysRemapped + ? (filteredByApplicability || targetKeys) + : filteredByApplicability + ? getIntersectionType([filteredByApplicability, typeParameter]) + : typeParameter; + const indexedAccessType = getIndexedAccessType(source, indexingType); + // Compare `S[indexingType]` to `T`, where `T` is the type of a property of the target type. + if (result = isRelatedTo(indexedAccessType, templateType, RecursionFlags.Both, reportErrors)) { + return result; } } - originalErrorInfo = errorInfo; - resetErrorInfo(saveErrorInfo); } - } - } - else if (target.flags & TypeFlags.Conditional) { - // If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive - // conditional type and bail out with a Ternary.Maybe result. - if (isDeeplyNestedType(target, targetStack, targetDepth, 10)) { + originalErrorInfo = errorInfo; resetErrorInfo(saveErrorInfo); - return Ternary.Maybe; - } - const c = target as ConditionalType; - // We check for a relationship to a conditional type target only when the conditional type has no - // 'infer' positions and is not distributive or is distributive but doesn't reference the check type - // parameter in either of the result types. - if (!c.root.inferTypeParameters && !isDistributionDependent(c.root)) { - // Check if the conditional is always true or always false but still deferred for distribution purposes. - const skipTrue = !isTypeAssignableTo(getPermissiveInstantiation(c.checkType), getPermissiveInstantiation(c.extendsType)); - const skipFalse = !skipTrue && isTypeAssignableTo(getRestrictiveInstantiation(c.checkType), getRestrictiveInstantiation(c.extendsType)); - // TODO: Find a nice way to include potential conditional type breakdowns in error output, if they seem good (they usually don't) - if (result = skipTrue ? Ternary.True : isRelatedTo(source, getTrueTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false)) { - result &= skipFalse ? Ternary.True : isRelatedTo(source, getFalseTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false); - if (result) { - resetErrorInfo(saveErrorInfo); - return result; - } - } } } - else if (target.flags & TypeFlags.TemplateLiteral) { - if (source.flags & TypeFlags.TemplateLiteral) { - if (relation === comparableRelation) { - return templateLiteralTypesDefinitelyUnrelated(source as TemplateLiteralType, target as TemplateLiteralType) ? Ternary.False : Ternary.True; + } + else if (target.flags & TypeFlags.Conditional) { + // If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive + // conditional type and bail out with a Ternary.Maybe result. + if (isDeeplyNestedType(target, targetStack, targetDepth, 10)) { + resetErrorInfo(saveErrorInfo); + return Ternary.Maybe; + } + const c = target as ConditionalType; + // We check for a relationship to a conditional type target only when the conditional type has no + // 'infer' positions and is not distributive or is distributive but doesn't reference the check type + // parameter in either of the result types. + if (!c.root.inferTypeParameters && !isDistributionDependent(c.root)) { + // Check if the conditional is always true or always false but still deferred for distribution purposes. + const skipTrue = !isTypeAssignableTo(getPermissiveInstantiation(c.checkType), getPermissiveInstantiation(c.extendsType)); + const skipFalse = !skipTrue && isTypeAssignableTo(getRestrictiveInstantiation(c.checkType), getRestrictiveInstantiation(c.extendsType)); + // TODO: Find a nice way to include potential conditional type breakdowns in error output, if they seem good (they usually don't) + if (result = skipTrue ? Ternary.True : isRelatedTo(source, getTrueTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false)) { + result &= skipFalse ? Ternary.True : isRelatedTo(source, getFalseTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false); + if (result) { + resetErrorInfo(saveErrorInfo); + return result; } - // Report unreliable variance for type variables referenced in template literal type placeholders. - // For example, `foo-${number}` is related to `foo-${string}` even though number isn't related to string. - instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers)); } - if (isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)) { - return Ternary.True; + } + } + else if (target.flags & TypeFlags.TemplateLiteral) { + if (source.flags & TypeFlags.TemplateLiteral) { + if (relation === comparableRelation) { + return templateLiteralTypesDefinitelyUnrelated(source as TemplateLiteralType, target as TemplateLiteralType) ? Ternary.False : Ternary.True; } + // Report unreliable variance for type variables referenced in template literal type placeholders. + // For example, `foo-${number}` is related to `foo-${string}` even though number isn't related to string. + instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers)); + } + if (isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)) { + return Ternary.True; } + } - if (source.flags & TypeFlags.TypeVariable) { - // IndexedAccess comparisons are handled above in the `target.flags & TypeFlage.IndexedAccess` branch - if (!(source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess)) { - const constraint = getConstraintOfType(source as TypeVariable); - if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) { - // A type variable with no constraint is not related to the non-primitive object type. - if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive), RecursionFlags.Both)) { - resetErrorInfo(saveErrorInfo); - return result; - } - } - // hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed - else if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { - resetErrorInfo(saveErrorInfo); - return result; - } - // slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example - else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, RecursionFlags.Source, reportErrors && !(target.flags & source.flags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState)) { + if (source.flags & TypeFlags.TypeVariable) { + // IndexedAccess comparisons are handled above in the `target.flags & TypeFlage.IndexedAccess` branch + if (!(source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess)) { + const constraint = getConstraintOfType(source as TypeVariable); + if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) { + // A type variable with no constraint is not related to the non-primitive object type. + if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive), RecursionFlags.Both)) { resetErrorInfo(saveErrorInfo); return result; } } + // hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed + else if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { + resetErrorInfo(saveErrorInfo); + return result; + } + // slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example + else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, RecursionFlags.Source, reportErrors && !(target.flags & source.flags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState)) { + resetErrorInfo(saveErrorInfo); + return result; + } + } + } + else if (source.flags & TypeFlags.Index) { + if (result = isRelatedTo(keyofConstraintType, target, RecursionFlags.Source, reportErrors)) { + resetErrorInfo(saveErrorInfo); + return result; } - else if (source.flags & TypeFlags.Index) { - if (result = isRelatedTo(keyofConstraintType, target, RecursionFlags.Source, reportErrors)) { + } + else if (source.flags & TypeFlags.TemplateLiteral && !(target.flags & TypeFlags.Object)) { + if (!(target.flags & TypeFlags.TemplateLiteral)) { + const constraint = getBaseConstraintOfType(source); + if (constraint && constraint !== source && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) { resetErrorInfo(saveErrorInfo); return result; } } - else if (source.flags & TypeFlags.TemplateLiteral && !(target.flags & TypeFlags.Object)) { - if (!(target.flags & TypeFlags.TemplateLiteral)) { - const constraint = getBaseConstraintOfType(source); - if (constraint && constraint !== source && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) { - resetErrorInfo(saveErrorInfo); - return result; - } + } + else if (source.flags & TypeFlags.StringMapping) { + if (target.flags & TypeFlags.StringMapping && (source as StringMappingType).symbol === (target as StringMappingType).symbol) { + if (result = isRelatedTo((source as StringMappingType).type, (target as StringMappingType).type, RecursionFlags.Both, reportErrors)) { + resetErrorInfo(saveErrorInfo); + return result; + } + } + else { + const constraint = getBaseConstraintOfType(source); + if (constraint && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) { + resetErrorInfo(saveErrorInfo); + return result; } } - else if (source.flags & TypeFlags.StringMapping) { - if (target.flags & TypeFlags.StringMapping && (source as StringMappingType).symbol === (target as StringMappingType).symbol) { - if (result = isRelatedTo((source as StringMappingType).type, (target as StringMappingType).type, RecursionFlags.Both, reportErrors)) { + } + else if (source.flags & TypeFlags.Conditional) { + // If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive + // conditional type and bail out with a Ternary.Maybe result. + if (isDeeplyNestedType(source, sourceStack, sourceDepth, 10)) { + resetErrorInfo(saveErrorInfo); + return Ternary.Maybe; + } + if (target.flags & TypeFlags.Conditional) { + // Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if + // one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2, + // and Y1 is related to Y2. + const sourceParams = (source as ConditionalType).root.inferTypeParameters; + let sourceExtends = (source as ConditionalType).extendsType; + let mapper: TypeMapper | undefined; + if (sourceParams) { + // If the source has infer type parameters, we instantiate them in the context of the target + const ctx = createInferenceContext(sourceParams, /*signature*/ undefined, InferenceFlags.None, isRelatedToWorker); + inferTypes(ctx.inferences, (target as ConditionalType).extendsType, sourceExtends, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + sourceExtends = instantiateType(sourceExtends, ctx.mapper); + mapper = ctx.mapper; + } + if (isTypeIdenticalTo(sourceExtends, (target as ConditionalType).extendsType) && + (isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both) || isRelatedTo((target as ConditionalType).checkType, (source as ConditionalType).checkType, RecursionFlags.Both))) { + if (result = isRelatedTo(instantiateType(getTrueTypeFromConditionalType(source as ConditionalType), mapper), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors)) { + result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors); + } + if (result) { resetErrorInfo(saveErrorInfo); return result; } } - else { - const constraint = getBaseConstraintOfType(source); - if (constraint && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) { + } + else { + // conditionals aren't related to one another via distributive constraint as it is much too inaccurate and allows way + // more assignments than are desirable (since it maps the source check type to its constraint, it loses information) + const distributiveConstraint = getConstraintOfDistributiveConditionalType(source as ConditionalType); + if (distributiveConstraint) { + if (result = isRelatedTo(distributiveConstraint, target, RecursionFlags.Source, reportErrors)) { resetErrorInfo(saveErrorInfo); return result; } } } - else if (source.flags & TypeFlags.Conditional) { - // If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive - // conditional type and bail out with a Ternary.Maybe result. - if (isDeeplyNestedType(source, sourceStack, sourceDepth, 10)) { + + // conditionals _can_ be related to one another via normal constraint, as, eg, `A extends B ? O : never` should be assignable to `O` + // when `O` is a conditional (`never` is trivially assignable to `O`, as is `O`!). + const defaultConstraint = getDefaultConstraintOfConditionalType(source as ConditionalType); + if (defaultConstraint) { + if (result = isRelatedTo(defaultConstraint, target, RecursionFlags.Source, reportErrors)) { resetErrorInfo(saveErrorInfo); - return Ternary.Maybe; - } - if (target.flags & TypeFlags.Conditional) { - // Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if - // one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2, - // and Y1 is related to Y2. - const sourceParams = (source as ConditionalType).root.inferTypeParameters; - let sourceExtends = (source as ConditionalType).extendsType; - let mapper: TypeMapper | undefined; - if (sourceParams) { - // If the source has infer type parameters, we instantiate them in the context of the target - const ctx = createInferenceContext(sourceParams, /*signature*/ undefined, InferenceFlags.None, isRelatedToWorker); - inferTypes(ctx.inferences, (target as ConditionalType).extendsType, sourceExtends, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); - sourceExtends = instantiateType(sourceExtends, ctx.mapper); - mapper = ctx.mapper; - } - if (isTypeIdenticalTo(sourceExtends, (target as ConditionalType).extendsType) && - (isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both) || isRelatedTo((target as ConditionalType).checkType, (source as ConditionalType).checkType, RecursionFlags.Both))) { - if (result = isRelatedTo(instantiateType(getTrueTypeFromConditionalType(source as ConditionalType), mapper), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors)) { - result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors); - } - if (result) { - resetErrorInfo(saveErrorInfo); - return result; - } - } - } - else { - // conditionals aren't related to one another via distributive constraint as it is much too inaccurate and allows way - // more assignments than are desirable (since it maps the source check type to its constraint, it loses information) - const distributiveConstraint = getConstraintOfDistributiveConditionalType(source as ConditionalType); - if (distributiveConstraint) { - if (result = isRelatedTo(distributiveConstraint, target, RecursionFlags.Source, reportErrors)) { - resetErrorInfo(saveErrorInfo); - return result; - } - } + return result; } - - // conditionals _can_ be related to one another via normal constraint, as, eg, `A extends B ? O : never` should be assignable to `O` - // when `O` is a conditional (`never` is trivially assignable to `O`, as is `O`!). - const defaultConstraint = getDefaultConstraintOfConditionalType(source as ConditionalType); - if (defaultConstraint) { - if (result = isRelatedTo(defaultConstraint, target, RecursionFlags.Source, reportErrors)) { + } + } + else { + // An empty object type is related to any mapped type that includes a '?' modifier. + if (relation !== subtypeRelation && relation !== strictSubtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) { + return Ternary.True; + } + if (isGenericMappedType(target)) { + if (isGenericMappedType(source)) { + if (result = mappedTypeRelatedTo(source, target, reportErrors)) { resetErrorInfo(saveErrorInfo); return result; } } + return Ternary.False; } - else { - // An empty object type is related to any mapped type that includes a '?' modifier. - if (relation !== subtypeRelation && relation !== strictSubtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) { - return Ternary.True; + const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive); + if (relation !== identityRelation) { + source = getApparentType(source); + } + else if (isGenericMappedType(source)) { + return Ternary.False; + } + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target && + !isTupleType(source) && !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) { + // We have type references to the same generic type, and the type references are not marker + // type references (which are intended by be compared structurally). Obtain the variance + // information for the type parameters and relate the type arguments accordingly. + const variances = getVariances((source as TypeReference).target); + // We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This + // effectively means we measure variance only from type parameter occurrences that aren't nested in + // recursive instantiations of the generic type. + if (variances === emptyArray) { + return Ternary.Unknown; } - if (isGenericMappedType(target)) { - if (isGenericMappedType(source)) { - if (result = mappedTypeRelatedTo(source, target, reportErrors)) { - resetErrorInfo(saveErrorInfo); - return result; - } - } - return Ternary.False; + const varianceResult = relateVariances(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), variances, intersectionState); + if (varianceResult !== undefined) { + return varianceResult; } - const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive); + } + else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { if (relation !== identityRelation) { - source = getApparentType(source); + return isRelatedTo(getIndexTypeOfType(source, numberType) || anyType, getIndexTypeOfType(target, numberType) || anyType, RecursionFlags.Both, reportErrors); } - else if (isGenericMappedType(source)) { + else { + // By flags alone, we know that the `target` is a readonly array while the source is a normal array or tuple + // or `target` is an array and source is a tuple - in both cases the types cannot be identical, by construction return Ternary.False; } - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target && - !isTupleType(source) && !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) { - // We have type references to the same generic type, and the type references are not marker - // type references (which are intended by be compared structurally). Obtain the variance - // information for the type parameters and relate the type arguments accordingly. - const variances = getVariances((source as TypeReference).target); - // We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This - // effectively means we measure variance only from type parameter occurrences that aren't nested in - // recursive instantiations of the generic type. - if (variances === emptyArray) { - return Ternary.Unknown; - } - const varianceResult = relateVariances(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), variances, intersectionState); - if (varianceResult !== undefined) { - return varianceResult; + } + // Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})` + // and not `{} <- fresh({}) <- {[idx: string]: any}` + else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) { + return Ternary.False; + } + // Even if relationship doesn't hold for unions, intersections, or generic type references, + // it may hold in a structural comparison. + // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates + // to X. Failing both of those we want to check if the aggregation of A and B's members structurally + // relates to X. Thus, we include intersection types on the source side here. + if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { + // Report structural errors only if we haven't reported any errors yet + const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; + result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, intersectionState); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors); + if (result) { + result &= indexSignaturesRelatedTo(source, target, sourceIsPrimitive, reportStructuralErrors, intersectionState); + } } } - else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { - if (relation !== identityRelation) { - return isRelatedTo(getIndexTypeOfType(source, numberType) || anyType, getIndexTypeOfType(target, numberType) || anyType, RecursionFlags.Both, reportErrors); - } - else { - // By flags alone, we know that the `target` is a readonly array while the source is a normal array or tuple - // or `target` is an array and source is a tuple - in both cases the types cannot be identical, by construction - return Ternary.False; - } + if (varianceCheckFailed && result) { + errorInfo = originalErrorInfo || errorInfo || saveErrorInfo.errorInfo; // Use variance error (there is no structural one) and return false } - // Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})` - // and not `{} <- fresh({}) <- {[idx: string]: any}` - else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) { - return Ternary.False; + else if (result) { + return result; } - // Even if relationship doesn't hold for unions, intersections, or generic type references, - // it may hold in a structural comparison. - // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates - // to X. Failing both of those we want to check if the aggregation of A and B's members structurally - // relates to X. Thus, we include intersection types on the source side here. - if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { - // Report structural errors only if we haven't reported any errors yet - const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; - result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, intersectionState); + } + // If S is an object type and T is a discriminated union, S may be related to T if + // there exists a constituent of T for every combination of the discriminants of S + // with respect to T. We do not report errors here, as we will use the existing + // error result from checking each constituent of the union. + if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Union) { + const objectOnlyTarget = extractTypesOfKind(target, TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Substitution); + if (objectOnlyTarget.flags & TypeFlags.Union) { + const result = typeRelatedToDiscriminatedType(source, objectOnlyTarget as UnionType); if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); - if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors); - if (result) { - result &= indexSignaturesRelatedTo(source, target, sourceIsPrimitive, reportStructuralErrors, intersectionState); - } - } - } - if (varianceCheckFailed && result) { - errorInfo = originalErrorInfo || errorInfo || saveErrorInfo.errorInfo; // Use variance error (there is no structural one) and return false - } - else if (result) { return result; } } - // If S is an object type and T is a discriminated union, S may be related to T if - // there exists a constituent of T for every combination of the discriminants of S - // with respect to T. We do not report errors here, as we will use the existing - // error result from checking each constituent of the union. - if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Union) { - const objectOnlyTarget = extractTypesOfKind(target, TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Substitution); - if (objectOnlyTarget.flags & TypeFlags.Union) { - const result = typeRelatedToDiscriminatedType(source, objectOnlyTarget as UnionType); - if (result) { - return result; - } - } - } } - return Ternary.False; + } + return Ternary.False; - function countMessageChainBreadth(info: DiagnosticMessageChain[] | undefined): number { - if (!info) return 0; - return reduceLeft(info, (value, chain) => value + 1 + countMessageChainBreadth(chain.next), 0); - } + function countMessageChainBreadth(info: DiagnosticMessageChain[] | undefined): number { + if (!info) return 0; + return reduceLeft(info, (value, chain) => value + 1 + countMessageChainBreadth(chain.next), 0); + } - function relateVariances(sourceTypeArguments: readonly Type[] | undefined, targetTypeArguments: readonly Type[] | undefined, variances: VarianceFlags[], intersectionState: IntersectionState) { - if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState)) { - return result; - } - if (some(variances, v => !!(v & VarianceFlags.AllowsStructuralFallback))) { - // If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we - // have to allow a structural fallback check - // We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially - // be assuming identity of the type parameter. - originalErrorInfo = undefined; - resetErrorInfo(saveErrorInfo); - return undefined; - } - const allowStructuralFallback = targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances); - varianceCheckFailed = !allowStructuralFallback; - // The type arguments did not relate appropriately, but it may be because we have no variance - // information (in which case typeArgumentsRelatedTo defaulted to covariance for all type - // arguments). It might also be the case that the target type has a 'void' type argument for - // a covariant type parameter that is only used in return positions within the generic type - // (in which case any type argument is permitted on the source side). In those cases we proceed - // with a structural comparison. Otherwise, we know for certain the instantiations aren't - // related and we can return here. - if (variances !== emptyArray && !allowStructuralFallback) { - // In some cases generic types that are covariant in regular type checking mode become - // invariant in --strictFunctionTypes mode because one or more type parameters are used in - // both co- and contravariant positions. In order to make it easier to diagnose *why* such - // types are invariant, if any of the type parameters are invariant we reset the reported - // errors and instead force a structural comparison (which will include elaborations that - // reveal the reason). - // We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`, - // we can return `False` early here to skip calculating the structural error message we don't need. - if (varianceCheckFailed && !(reportErrors && some(variances, v => (v & VarianceFlags.VarianceMask) === VarianceFlags.Invariant))) { - return Ternary.False; - } - // We remember the original error information so we can restore it in case the structural - // comparison unexpectedly succeeds. This can happen when the structural comparison result - // is a Ternary.Maybe for example caused by the recursion depth limiter. - originalErrorInfo = errorInfo; - resetErrorInfo(saveErrorInfo); + function relateVariances(sourceTypeArguments: readonly Type[] | undefined, targetTypeArguments: readonly Type[] | undefined, variances: VarianceFlags[], intersectionState: IntersectionState) { + if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState)) { + return result; + } + if (some(variances, v => !!(v & VarianceFlags.AllowsStructuralFallback))) { + // If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we + // have to allow a structural fallback check + // We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially + // be assuming identity of the type parameter. + originalErrorInfo = undefined; + resetErrorInfo(saveErrorInfo); + return undefined; + } + const allowStructuralFallback = targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances); + varianceCheckFailed = !allowStructuralFallback; + // The type arguments did not relate appropriately, but it may be because we have no variance + // information (in which case typeArgumentsRelatedTo defaulted to covariance for all type + // arguments). It might also be the case that the target type has a 'void' type argument for + // a covariant type parameter that is only used in return positions within the generic type + // (in which case any type argument is permitted on the source side). In those cases we proceed + // with a structural comparison. Otherwise, we know for certain the instantiations aren't + // related and we can return here. + if (variances !== emptyArray && !allowStructuralFallback) { + // In some cases generic types that are covariant in regular type checking mode become + // invariant in --strictFunctionTypes mode because one or more type parameters are used in + // both co- and contravariant positions. In order to make it easier to diagnose *why* such + // types are invariant, if any of the type parameters are invariant we reset the reported + // errors and instead force a structural comparison (which will include elaborations that + // reveal the reason). + // We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`, + // we can return `False` early here to skip calculating the structural error message we don't need. + if (varianceCheckFailed && !(reportErrors && some(variances, v => (v & VarianceFlags.VarianceMask) === VarianceFlags.Invariant))) { + return Ternary.False; } + // We remember the original error information so we can restore it in case the structural + // comparison unexpectedly succeeds. This can happen when the structural comparison result + // is a Ternary.Maybe for example caused by the recursion depth limiter. + originalErrorInfo = errorInfo; + resetErrorInfo(saveErrorInfo); } } + } - function reportUnmeasurableMarkers(p: TypeParameter) { - if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) { - outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); - } - return p; + function reportUnmeasurableMarkers(p: TypeParameter) { + if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); } + return p; + } - function reportUnreliableMarkers(p: TypeParameter) { - if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) { - outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); - } - return p; + function reportUnreliableMarkers(p: TypeParameter) { + if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); } + return p; + } - // A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is - // related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice - // that S and T are contra-variant whereas X and Y are co-variant. - function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary { - const modifiersRelated = relation === comparableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) : - getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target)); - if (modifiersRelated) { - let result: Ternary; - const targetConstraint = getConstraintTypeFromMappedType(target); - const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), makeFunctionTypeMapper(getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMarkers : reportUnreliableMarkers)); - if (result = isRelatedTo(targetConstraint, sourceConstraint, RecursionFlags.Both, reportErrors)) { - const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]); - if (instantiateType(getNameTypeFromMappedType(source), mapper) === instantiateType(getNameTypeFromMappedType(target), mapper)) { - return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), RecursionFlags.Both, reportErrors); - } + // A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is + // related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice + // that S and T are contra-variant whereas X and Y are co-variant. + function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary { + const modifiersRelated = relation === comparableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) : + getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target)); + if (modifiersRelated) { + let result: Ternary; + const targetConstraint = getConstraintTypeFromMappedType(target); + const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), makeFunctionTypeMapper(getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMarkers : reportUnreliableMarkers)); + if (result = isRelatedTo(targetConstraint, sourceConstraint, RecursionFlags.Both, reportErrors)) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]); + if (instantiateType(getNameTypeFromMappedType(source), mapper) === instantiateType(getNameTypeFromMappedType(target), mapper)) { + return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), RecursionFlags.Both, reportErrors); } } - return Ternary.False; } + return Ternary.False; + } - function typeRelatedToDiscriminatedType(source: Type, target: UnionType) { - // 1. Generate the combinations of discriminant properties & types 'source' can satisfy. - // a. If the number of combinations is above a set limit, the comparison is too complex. - // 2. Filter 'target' to the subset of types whose discriminants exist in the matrix. - // a. If 'target' does not satisfy all discriminants in the matrix, 'source' is not related. - // 3. For each type in the filtered 'target', determine if all non-discriminant properties of - // 'target' are related to a property in 'source'. - // - // NOTE: See ~/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts - // for examples. - - const sourceProperties = getPropertiesOfType(source); - const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); - if (!sourcePropertiesFiltered) return Ternary.False; - - // Though we could compute the number of combinations as we generate - // the matrix, this would incur additional memory overhead due to - // array allocations. To reduce this overhead, we first compute - // the number of combinations to ensure we will not surpass our - // fixed limit before incurring the cost of any allocations: - let numCombinations = 1; - for (const sourceProperty of sourcePropertiesFiltered) { - numCombinations *= countTypes(getNonMissingTypeOfSymbol(sourceProperty)); - if (numCombinations > 25) { - // We've reached the complexity limit. - tracing?.instant(tracing.Phase.CheckTypes, "typeRelatedToDiscriminatedType_DepthLimit", { sourceId: source.id, targetId: target.id, numCombinations }); - return Ternary.False; - } + function typeRelatedToDiscriminatedType(source: Type, target: UnionType) { + // 1. Generate the combinations of discriminant properties & types 'source' can satisfy. + // a. If the number of combinations is above a set limit, the comparison is too complex. + // 2. Filter 'target' to the subset of types whose discriminants exist in the matrix. + // a. If 'target' does not satisfy all discriminants in the matrix, 'source' is not related. + // 3. For each type in the filtered 'target', determine if all non-discriminant properties of + // 'target' are related to a property in 'source'. + // + // NOTE: See ~/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts + // for examples. + + const sourceProperties = getPropertiesOfType(source); + const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); + if (!sourcePropertiesFiltered) return Ternary.False; + + // Though we could compute the number of combinations as we generate + // the matrix, this would incur additional memory overhead due to + // array allocations. To reduce this overhead, we first compute + // the number of combinations to ensure we will not surpass our + // fixed limit before incurring the cost of any allocations: + let numCombinations = 1; + for (const sourceProperty of sourcePropertiesFiltered) { + numCombinations *= countTypes(getNonMissingTypeOfSymbol(sourceProperty)); + if (numCombinations > 25) { + // We've reached the complexity limit. + tracing?.instant(tracing.Phase.CheckTypes, "typeRelatedToDiscriminatedType_DepthLimit", { sourceId: source.id, targetId: target.id, numCombinations }); + return Ternary.False; } + } - // Compute the set of types for each discriminant property. - const sourceDiscriminantTypes: Type[][] = new Array(sourcePropertiesFiltered.length); - const excludedProperties = new Set<__String>(); - for (let i = 0; i < sourcePropertiesFiltered.length; i++) { - const sourceProperty = sourcePropertiesFiltered[i]; - const sourcePropertyType = getNonMissingTypeOfSymbol(sourceProperty); - sourceDiscriminantTypes[i] = sourcePropertyType.flags & TypeFlags.Union - ? (sourcePropertyType as UnionType).types - : [sourcePropertyType]; - excludedProperties.add(sourceProperty.escapedName); - } - - // Match each combination of the cartesian product of discriminant properties to one or more - // constituents of 'target'. If any combination does not have a match then 'source' is not relatable. - const discriminantCombinations = cartesianProduct(sourceDiscriminantTypes); - const matchingTypes: Type[] = []; - for (const combination of discriminantCombinations) { - let hasMatch = false; - outer: for (const type of target.types) { - for (let i = 0; i < sourcePropertiesFiltered.length; i++) { - const sourceProperty = sourcePropertiesFiltered[i]; - const targetProperty = getPropertyOfType(type, sourceProperty.escapedName); - if (!targetProperty) continue outer; - if (sourceProperty === targetProperty) continue; - // We compare the source property to the target in the context of a single discriminant type. - const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false, IntersectionState.None, /*skipOptional*/ strictNullChecks || relation === comparableRelation); - // If the target property could not be found, or if the properties were not related, - // then this constituent is not a match. - if (!related) { - continue outer; - } + // Compute the set of types for each discriminant property. + const sourceDiscriminantTypes: Type[][] = new Array(sourcePropertiesFiltered.length); + const excludedProperties = new Set<__String>(); + for (let i = 0; i < sourcePropertiesFiltered.length; i++) { + const sourceProperty = sourcePropertiesFiltered[i]; + const sourcePropertyType = getNonMissingTypeOfSymbol(sourceProperty); + sourceDiscriminantTypes[i] = sourcePropertyType.flags & TypeFlags.Union + ? (sourcePropertyType as UnionType).types + : [sourcePropertyType]; + excludedProperties.add(sourceProperty.escapedName); + } + + // Match each combination of the cartesian product of discriminant properties to one or more + // constituents of 'target'. If any combination does not have a match then 'source' is not relatable. + const discriminantCombinations = cartesianProduct(sourceDiscriminantTypes); + const matchingTypes: Type[] = []; + for (const combination of discriminantCombinations) { + let hasMatch = false; + outer: for (const type of target.types) { + for (let i = 0; i < sourcePropertiesFiltered.length; i++) { + const sourceProperty = sourcePropertiesFiltered[i]; + const targetProperty = getPropertyOfType(type, sourceProperty.escapedName); + if (!targetProperty) continue outer; + if (sourceProperty === targetProperty) continue; + // We compare the source property to the target in the context of a single discriminant type. + const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false, IntersectionState.None, /*skipOptional*/ strictNullChecks || relation === comparableRelation); + // If the target property could not be found, or if the properties were not related, + // then this constituent is not a match. + if (!related) { + continue outer; } - pushIfUnique(matchingTypes, type, equateValues); - hasMatch = true; - } - if (!hasMatch) { - // We failed to match any type for this combination. - return Ternary.False; } + pushIfUnique(matchingTypes, type, equateValues); + hasMatch = true; + } + if (!hasMatch) { + // We failed to match any type for this combination. + return Ternary.False; } + } - // Compare the remaining non-discriminant properties of each match. - let result = Ternary.True; - for (const type of matchingTypes) { - result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, IntersectionState.None); + // Compare the remaining non-discriminant properties of each match. + let result = Ternary.True; + for (const type of matchingTypes) { + result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, IntersectionState.None); + if (result) { + result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false); if (result) { - result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false); - if (result) { - result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false); - if (result && !(isTupleType(source) && isTupleType(type))) { - // Comparing numeric index types when both `source` and `type` are tuples is unnecessary as the - // element types should be sufficiently covered by `propertiesRelatedTo`. It also causes problems - // with index type assignability as the types for the excluded discriminants are still included - // in the index type. - result &= indexSignaturesRelatedTo(source, type, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false, IntersectionState.None); - } + result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false); + if (result && !(isTupleType(source) && isTupleType(type))) { + // Comparing numeric index types when both `source` and `type` are tuples is unnecessary as the + // element types should be sufficiently covered by `propertiesRelatedTo`. It also causes problems + // with index type assignability as the types for the excluded discriminants are still included + // in the index type. + result &= indexSignaturesRelatedTo(source, type, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false, IntersectionState.None); } } - if (!result) { - return result; - } } - return result; - } + if (!result) { + return result; + } + } + return result; + } - function excludeProperties(properties: Symbol[], excludedProperties: Set<__String> | undefined) { - if (!excludedProperties || properties.length === 0) return properties; - let result: Symbol[] | undefined; - for (let i = 0; i < properties.length; i++) { - if (!excludedProperties.has(properties[i].escapedName)) { - if (result) { - result.push(properties[i]); - } - } - else if (!result) { - result = properties.slice(0, i); + function excludeProperties(properties: Symbol[], excludedProperties: Set<__String> | undefined) { + if (!excludedProperties || properties.length === 0) return properties; + let result: Symbol[] | undefined; + for (let i = 0; i < properties.length; i++) { + if (!excludedProperties.has(properties[i].escapedName)) { + if (result) { + result.push(properties[i]); } } - return result || properties; + else if (!result) { + result = properties.slice(0, i); + } } + return result || properties; + } - function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial); - const effectiveTarget = addOptionality(getNonMissingTypeOfSymbol(targetProp), /*isProperty*/ false, targetIsOptional); - const effectiveSource = getTypeOfSourceProperty(sourceProp); - return isRelatedTo(effectiveSource, effectiveTarget, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - } + function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial); + const effectiveTarget = addOptionality(getNonMissingTypeOfSymbol(targetProp), /*isProperty*/ false, targetIsOptional); + const effectiveSource = getTypeOfSourceProperty(sourceProp); + return isRelatedTo(effectiveSource, effectiveTarget, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + } - function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState, skipOptional: boolean): Ternary { - const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); - const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); - if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { - if (sourceProp.valueDeclaration !== targetProp.valueDeclaration) { - if (reportErrors) { - if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) { - reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp)); - } - else { - reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp), - typeToString(sourcePropFlags & ModifierFlags.Private ? source : target), - typeToString(sourcePropFlags & ModifierFlags.Private ? target : source)); - } + function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState, skipOptional: boolean): Ternary { + const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); + const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); + if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { + if (sourceProp.valueDeclaration !== targetProp.valueDeclaration) { + if (reportErrors) { + if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) { + reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp)); } - return Ternary.False; - } - } - else if (targetPropFlags & ModifierFlags.Protected) { - if (!isValidOverrideOf(sourceProp, targetProp)) { - if (reportErrors) { - reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp), - typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target)); + else { + reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp), + typeToString(sourcePropFlags & ModifierFlags.Private ? source : target), + typeToString(sourcePropFlags & ModifierFlags.Private ? target : source)); } - return Ternary.False; - } - } - else if (sourcePropFlags & ModifierFlags.Protected) { - if (reportErrors) { - reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, - symbolToString(targetProp), typeToString(source), typeToString(target)); } return Ternary.False; } - // If the target comes from a partial union prop, allow `undefined` in the target type - const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, intersectionState); - if (!related) { + } + else if (targetPropFlags & ModifierFlags.Protected) { + if (!isValidOverrideOf(sourceProp, targetProp)) { if (reportErrors) { - reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); + reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp), + typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target)); } return Ternary.False; } - // When checking for comparability, be more lenient with optional properties. - if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) { - // TypeScript 1.0 spec (April 2014): 3.8.3 - // S is a subtype of a type T, and T is a supertype of S if ... - // S' and T are object types and, for each member M in T.. - // M is a property and S' contains a property N where - // if M is a required property, N is also a required property - // (M - property in T) - // (N - property in S) - if (reportErrors) { - reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, - symbolToString(targetProp), typeToString(source), typeToString(target)); - } - return Ternary.False; + } + else if (sourcePropFlags & ModifierFlags.Protected) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, + symbolToString(targetProp), typeToString(source), typeToString(target)); } - return related; - } - - function reportUnmatchedProperty(source: Type, target: Type, unmatchedProperty: Symbol, requireOptionalProperties: boolean) { - let shouldSkipElaboration = false; - // give specific error in case where private names have the same description - if (unmatchedProperty.valueDeclaration - && isNamedDeclaration(unmatchedProperty.valueDeclaration) - && isPrivateIdentifier(unmatchedProperty.valueDeclaration.name) - && source.symbol - && source.symbol.flags & SymbolFlags.Class) { - const privateIdentifierDescription = unmatchedProperty.valueDeclaration.name.escapedText; - const symbolTableKey = getSymbolNameForPrivateIdentifier(source.symbol, privateIdentifierDescription); - if (symbolTableKey && getPropertyOfType(source, symbolTableKey)) { - const sourceName = factory.getDeclarationName(source.symbol.valueDeclaration); - const targetName = factory.getDeclarationName(target.symbol.valueDeclaration); - reportError( - Diagnostics.Property_0_in_type_1_refers_to_a_different_member_that_cannot_be_accessed_from_within_type_2, - diagnosticName(privateIdentifierDescription), - diagnosticName(sourceName.escapedText === "" ? anon : sourceName), - diagnosticName(targetName.escapedText === "" ? anon : targetName)); - return; - } + return Ternary.False; + } + // If the target comes from a partial union prop, allow `undefined` in the target type + const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, intersectionState); + if (!related) { + if (reportErrors) { + reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); } - const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false)); - if (!headMessage || (headMessage.code !== Diagnostics.Class_0_incorrectly_implements_interface_1.code && - headMessage.code !== Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.code)) { - shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it + return Ternary.False; + } + // When checking for comparability, be more lenient with optional properties. + if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) { + // TypeScript 1.0 spec (April 2014): 3.8.3 + // S is a subtype of a type T, and T is a supertype of S if ... + // S' and T are object types and, for each member M in T.. + // M is a property and S' contains a property N where + // if M is a required property, N is also a required property + // (M - property in T) + // (N - property in S) + if (reportErrors) { + reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, + symbolToString(targetProp), typeToString(source), typeToString(target)); } - if (props.length === 1) { - const propName = symbolToString(unmatchedProperty); - reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target)); - if (length(unmatchedProperty.declarations)) { - associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations![0], Diagnostics._0_is_declared_here, propName)); - } - if (shouldSkipElaboration && errorInfo) { - overrideNextErrorInfo++; - } + return Ternary.False; + } + return related; + } + + function reportUnmatchedProperty(source: Type, target: Type, unmatchedProperty: Symbol, requireOptionalProperties: boolean) { + let shouldSkipElaboration = false; + // give specific error in case where private names have the same description + if (unmatchedProperty.valueDeclaration + && isNamedDeclaration(unmatchedProperty.valueDeclaration) + && isPrivateIdentifier(unmatchedProperty.valueDeclaration.name) + && source.symbol + && source.symbol.flags & SymbolFlags.Class) { + const privateIdentifierDescription = unmatchedProperty.valueDeclaration.name.escapedText; + const symbolTableKey = getSymbolNameForPrivateIdentifier(source.symbol, privateIdentifierDescription); + if (symbolTableKey && getPropertyOfType(source, symbolTableKey)) { + const sourceName = factory.getDeclarationName(source.symbol.valueDeclaration); + const targetName = factory.getDeclarationName(target.symbol.valueDeclaration); + reportError( + Diagnostics.Property_0_in_type_1_refers_to_a_different_member_that_cannot_be_accessed_from_within_type_2, + diagnosticName(privateIdentifierDescription), + diagnosticName(sourceName.escapedText === "" ? anon : sourceName), + diagnosticName(targetName.escapedText === "" ? anon : targetName)); + return; } - else if (tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ false)) { - if (props.length > 5) { // arbitrary cutoff for too-long list form - reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more, typeToString(source), typeToString(target), map(props.slice(0, 4), p => symbolToString(p)).join(", "), props.length - 4); - } - else { - reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2, typeToString(source), typeToString(target), map(props, p => symbolToString(p)).join(", ")); - } - if (shouldSkipElaboration && errorInfo) { - overrideNextErrorInfo++; - } + } + const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false)); + if (!headMessage || (headMessage.code !== Diagnostics.Class_0_incorrectly_implements_interface_1.code && + headMessage.code !== Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.code)) { + shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it + } + if (props.length === 1) { + const propName = symbolToString(unmatchedProperty); + reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target)); + if (length(unmatchedProperty.declarations)) { + associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations![0], Diagnostics._0_is_declared_here, propName)); + } + if (shouldSkipElaboration && errorInfo) { + overrideNextErrorInfo++; } - // No array like or unmatched property error - just issue top level error (errorInfo = undefined) } - - function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, intersectionState: IntersectionState): Ternary { - if (relation === identityRelation) { - return propertiesIdenticalTo(source, target, excludedProperties); + else if (tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ false)) { + if (props.length > 5) { // arbitrary cutoff for too-long list form + reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more, typeToString(source), typeToString(target), map(props.slice(0, 4), p => symbolToString(p)).join(", "), props.length - 4); } - let result = Ternary.True; - if (isTupleType(target)) { - if (isArrayType(source) || isTupleType(source)) { - if (!target.target.readonly && (isReadonlyArrayType(source) || isTupleType(source) && source.target.readonly)) { - return Ternary.False; + else { + reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2, typeToString(source), typeToString(target), map(props, p => symbolToString(p)).join(", ")); + } + if (shouldSkipElaboration && errorInfo) { + overrideNextErrorInfo++; + } + } + // No array like or unmatched property error - just issue top level error (errorInfo = undefined) + } + + function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, intersectionState: IntersectionState): Ternary { + if (relation === identityRelation) { + return propertiesIdenticalTo(source, target, excludedProperties); + } + let result = Ternary.True; + if (isTupleType(target)) { + if (isArrayType(source) || isTupleType(source)) { + if (!target.target.readonly && (isReadonlyArrayType(source) || isTupleType(source) && source.target.readonly)) { + return Ternary.False; + } + const sourceArity = getTypeReferenceArity(source); + const targetArity = getTypeReferenceArity(target); + const sourceRestFlag = isTupleType(source) ? source.target.combinedFlags & ElementFlags.Rest : ElementFlags.Rest; + const targetRestFlag = target.target.combinedFlags & ElementFlags.Rest; + const sourceMinLength = isTupleType(source) ? source.target.minLength : 0; + const targetMinLength = target.target.minLength; + if (!sourceRestFlag && sourceArity < targetMinLength) { + if (reportErrors) { + reportError(Diagnostics.Source_has_0_element_s_but_target_requires_1, sourceArity, targetMinLength); } - const sourceArity = getTypeReferenceArity(source); - const targetArity = getTypeReferenceArity(target); - const sourceRestFlag = isTupleType(source) ? source.target.combinedFlags & ElementFlags.Rest : ElementFlags.Rest; - const targetRestFlag = target.target.combinedFlags & ElementFlags.Rest; - const sourceMinLength = isTupleType(source) ? source.target.minLength : 0; - const targetMinLength = target.target.minLength; - if (!sourceRestFlag && sourceArity < targetMinLength) { + return Ternary.False; + } + if (!targetRestFlag && targetArity < sourceMinLength) { + if (reportErrors) { + reportError(Diagnostics.Source_has_0_element_s_but_target_allows_only_1, sourceMinLength, targetArity); + } + return Ternary.False; + } + if (!targetRestFlag && (sourceRestFlag || targetArity < sourceArity)) { + if (reportErrors) { + if (sourceMinLength < targetMinLength) { + reportError(Diagnostics.Target_requires_0_element_s_but_source_may_have_fewer, targetMinLength); + } + else { + reportError(Diagnostics.Target_allows_only_0_element_s_but_source_may_have_more, targetArity); + } + } + return Ternary.False; + } + const sourceTypeArguments = getTypeArguments(source); + const targetTypeArguments = getTypeArguments(target); + const startCount = Math.min(isTupleType(source) ? getStartElementCount(source.target, ElementFlags.NonRest) : 0, getStartElementCount(target.target, ElementFlags.NonRest)); + const endCount = Math.min(isTupleType(source) ? getEndElementCount(source.target, ElementFlags.NonRest) : 0, targetRestFlag ? getEndElementCount(target.target, ElementFlags.NonRest) : 0); + let canExcludeDiscriminants = !!excludedProperties; + for (let i = 0; i < targetArity; i++) { + const sourceIndex = i < targetArity - endCount ? i : i + sourceArity - targetArity; + const sourceFlags = isTupleType(source) && (i < startCount || i >= targetArity - endCount) ? source.target.elementFlags[sourceIndex] : ElementFlags.Rest; + const targetFlags = target.target.elementFlags[i]; + if (targetFlags & ElementFlags.Variadic && !(sourceFlags & ElementFlags.Variadic)) { if (reportErrors) { - reportError(Diagnostics.Source_has_0_element_s_but_target_requires_1, sourceArity, targetMinLength); + reportError(Diagnostics.Source_provides_no_match_for_variadic_element_at_position_0_in_target, i); } return Ternary.False; } - if (!targetRestFlag && targetArity < sourceMinLength) { + if (sourceFlags & ElementFlags.Variadic && !(targetFlags & ElementFlags.Variable)) { if (reportErrors) { - reportError(Diagnostics.Source_has_0_element_s_but_target_allows_only_1, sourceMinLength, targetArity); + reportError(Diagnostics.Variadic_element_at_position_0_in_source_does_not_match_element_at_position_1_in_target, sourceIndex, i); } return Ternary.False; } - if (!targetRestFlag && (sourceRestFlag || targetArity < sourceArity)) { + if (targetFlags & ElementFlags.Required && !(sourceFlags & ElementFlags.Required)) { if (reportErrors) { - if (sourceMinLength < targetMinLength) { - reportError(Diagnostics.Target_requires_0_element_s_but_source_may_have_fewer, targetMinLength); - } - else { - reportError(Diagnostics.Target_allows_only_0_element_s_but_source_may_have_more, targetArity); - } + reportError(Diagnostics.Source_provides_no_match_for_required_element_at_position_0_in_target, i); } return Ternary.False; } - const sourceTypeArguments = getTypeArguments(source); - const targetTypeArguments = getTypeArguments(target); - const startCount = Math.min(isTupleType(source) ? getStartElementCount(source.target, ElementFlags.NonRest) : 0, getStartElementCount(target.target, ElementFlags.NonRest)); - const endCount = Math.min(isTupleType(source) ? getEndElementCount(source.target, ElementFlags.NonRest) : 0, targetRestFlag ? getEndElementCount(target.target, ElementFlags.NonRest) : 0); - let canExcludeDiscriminants = !!excludedProperties; - for (let i = 0; i < targetArity; i++) { - const sourceIndex = i < targetArity - endCount ? i : i + sourceArity - targetArity; - const sourceFlags = isTupleType(source) && (i < startCount || i >= targetArity - endCount) ? source.target.elementFlags[sourceIndex] : ElementFlags.Rest; - const targetFlags = target.target.elementFlags[i]; - if (targetFlags & ElementFlags.Variadic && !(sourceFlags & ElementFlags.Variadic)) { - if (reportErrors) { - reportError(Diagnostics.Source_provides_no_match_for_variadic_element_at_position_0_in_target, i); - } - return Ternary.False; + // We can only exclude discriminant properties if we have not yet encountered a variable-length element. + if (canExcludeDiscriminants) { + if (sourceFlags & ElementFlags.Variable || targetFlags & ElementFlags.Variable) { + canExcludeDiscriminants = false; } - if (sourceFlags & ElementFlags.Variadic && !(targetFlags & ElementFlags.Variable)) { - if (reportErrors) { - reportError(Diagnostics.Variadic_element_at_position_0_in_source_does_not_match_element_at_position_1_in_target, sourceIndex, i); - } - return Ternary.False; - } - if (targetFlags & ElementFlags.Required && !(sourceFlags & ElementFlags.Required)) { - if (reportErrors) { - reportError(Diagnostics.Source_provides_no_match_for_required_element_at_position_0_in_target, i); - } - return Ternary.False; + if (canExcludeDiscriminants && excludedProperties?.has(("" + i) as __String)) { + continue; } - // We can only exclude discriminant properties if we have not yet encountered a variable-length element. - if (canExcludeDiscriminants) { - if (sourceFlags & ElementFlags.Variable || targetFlags & ElementFlags.Variable) { - canExcludeDiscriminants = false; - } - if (canExcludeDiscriminants && excludedProperties?.has(("" + i) as __String)) { - continue; + } + const sourceType = !isTupleType(source) ? sourceTypeArguments[0] : + i < startCount || i >= targetArity - endCount ? removeMissingType(sourceTypeArguments[sourceIndex], !!(sourceFlags & targetFlags & ElementFlags.Optional)) : + getElementTypeOfSliceOfTupleType(source, startCount, endCount) || neverType; + const targetType = targetTypeArguments[i]; + const targetCheckType = sourceFlags & ElementFlags.Variadic && targetFlags & ElementFlags.Rest ? createArrayType(targetType) : + removeMissingType(targetType, !!(targetFlags & ElementFlags.Optional)); + const related = isRelatedTo(sourceType, targetCheckType, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + if (!related) { + if (reportErrors && (targetArity > 1 || sourceArity > 1)) { + if (i < startCount || i >= targetArity - endCount || sourceArity - startCount - endCount === 1) { + reportIncompatibleError(Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, sourceIndex, i); } - } - const sourceType = !isTupleType(source) ? sourceTypeArguments[0] : - i < startCount || i >= targetArity - endCount ? removeMissingType(sourceTypeArguments[sourceIndex], !!(sourceFlags & targetFlags & ElementFlags.Optional)) : - getElementTypeOfSliceOfTupleType(source, startCount, endCount) || neverType; - const targetType = targetTypeArguments[i]; - const targetCheckType = sourceFlags & ElementFlags.Variadic && targetFlags & ElementFlags.Rest ? createArrayType(targetType) : - removeMissingType(targetType, !!(targetFlags & ElementFlags.Optional)); - const related = isRelatedTo(sourceType, targetCheckType, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - if (!related) { - if (reportErrors && (targetArity > 1 || sourceArity > 1)) { - if (i < startCount || i >= targetArity - endCount || sourceArity - startCount - endCount === 1) { - reportIncompatibleError(Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, sourceIndex, i); - } - else { - reportIncompatibleError(Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, startCount, sourceArity - endCount - 1, i); - } + else { + reportIncompatibleError(Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, startCount, sourceArity - endCount - 1, i); } - return Ternary.False; } - result &= related; + return Ternary.False; } - return result; - } - if (target.target.combinedFlags & ElementFlags.Variable) { - return Ternary.False; + result &= related; } + return result; } - const requireOptionalProperties = (relation === subtypeRelation || relation === strictSubtypeRelation) && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source); - const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false); - if (unmatchedProperty) { - if (reportErrors) { - reportUnmatchedProperty(source, target, unmatchedProperty, requireOptionalProperties); - } + if (target.target.combinedFlags & ElementFlags.Variable) { return Ternary.False; } - if (isObjectLiteralType(target)) { - for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) { - if (!getPropertyOfObjectType(target, sourceProp.escapedName)) { - const sourceType = getTypeOfSymbol(sourceProp); - if (!(sourceType.flags & TypeFlags.Undefined)) { - if (reportErrors) { - reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target)); - } - return Ternary.False; + } + const requireOptionalProperties = (relation === subtypeRelation || relation === strictSubtypeRelation) && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source); + const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false); + if (unmatchedProperty) { + if (reportErrors) { + reportUnmatchedProperty(source, target, unmatchedProperty, requireOptionalProperties); + } + return Ternary.False; + } + if (isObjectLiteralType(target)) { + for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) { + if (!getPropertyOfObjectType(target, sourceProp.escapedName)) { + const sourceType = getTypeOfSymbol(sourceProp); + if (!(sourceType.flags & TypeFlags.Undefined)) { + if (reportErrors) { + reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target)); } + return Ternary.False; } } } - // We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_ - // from the target union, across all members - const properties = getPropertiesOfType(target); - const numericNamesOnly = isTupleType(source) && isTupleType(target); - for (const targetProp of excludeProperties(properties, excludedProperties)) { - const name = targetProp.escapedName; - if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length")) { - const sourceProp = getPropertyOfType(source, name); - if (sourceProp && sourceProp !== targetProp) { - const related = propertyRelatedTo(source, target, sourceProp, targetProp, getNonMissingTypeOfSymbol, reportErrors, intersectionState, relation === comparableRelation); - if (!related) { - return Ternary.False; - } - result &= related; + } + // We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_ + // from the target union, across all members + const properties = getPropertiesOfType(target); + const numericNamesOnly = isTupleType(source) && isTupleType(target); + for (const targetProp of excludeProperties(properties, excludedProperties)) { + const name = targetProp.escapedName; + if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length")) { + const sourceProp = getPropertyOfType(source, name); + if (sourceProp && sourceProp !== targetProp) { + const related = propertyRelatedTo(source, target, sourceProp, targetProp, getNonMissingTypeOfSymbol, reportErrors, intersectionState, relation === comparableRelation); + if (!related) { + return Ternary.False; } + result &= related; } } - return result; } + return result; + } - function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: Set<__String> | undefined): Ternary { - if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) { + function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: Set<__String> | undefined): Ternary { + if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) { + return Ternary.False; + } + const sourceProperties = excludeProperties(getPropertiesOfObjectType(source), excludedProperties); + const targetProperties = excludeProperties(getPropertiesOfObjectType(target), excludedProperties); + if (sourceProperties.length !== targetProperties.length) { + return Ternary.False; + } + let result = Ternary.True; + for (const sourceProp of sourceProperties) { + const targetProp = getPropertyOfObjectType(target, sourceProp.escapedName); + if (!targetProp) { return Ternary.False; } - const sourceProperties = excludeProperties(getPropertiesOfObjectType(source), excludedProperties); - const targetProperties = excludeProperties(getPropertiesOfObjectType(target), excludedProperties); - if (sourceProperties.length !== targetProperties.length) { + const related = compareProperties(sourceProp, targetProp, isRelatedTo); + if (!related) { return Ternary.False; } - let result = Ternary.True; - for (const sourceProp of sourceProperties) { - const targetProp = getPropertyOfObjectType(target, sourceProp.escapedName); - if (!targetProp) { - return Ternary.False; - } - const related = compareProperties(sourceProp, targetProp, isRelatedTo); - if (!related) { - return Ternary.False; - } - result &= related; - } - return result; + result &= related; } + return result; + } - function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean): Ternary { - if (relation === identityRelation) { - return signaturesIdenticalTo(source, target, kind); - } - if (target === anyFunctionType || source === anyFunctionType) { - return Ternary.True; - } + function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean): Ternary { + if (relation === identityRelation) { + return signaturesIdenticalTo(source, target, kind); + } + if (target === anyFunctionType || source === anyFunctionType) { + return Ternary.True; + } - const sourceIsJSConstructor = source.symbol && isJSConstructor(source.symbol.valueDeclaration); - const targetIsJSConstructor = target.symbol && isJSConstructor(target.symbol.valueDeclaration); + const sourceIsJSConstructor = source.symbol && isJSConstructor(source.symbol.valueDeclaration); + const targetIsJSConstructor = target.symbol && isJSConstructor(target.symbol.valueDeclaration); - const sourceSignatures = getSignaturesOfType(source, (sourceIsJSConstructor && kind === SignatureKind.Construct) ? - SignatureKind.Call : kind); - const targetSignatures = getSignaturesOfType(target, (targetIsJSConstructor && kind === SignatureKind.Construct) ? - SignatureKind.Call : kind); + const sourceSignatures = getSignaturesOfType(source, (sourceIsJSConstructor && kind === SignatureKind.Construct) ? + SignatureKind.Call : kind); + const targetSignatures = getSignaturesOfType(target, (targetIsJSConstructor && kind === SignatureKind.Construct) ? + SignatureKind.Call : kind); - if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) { - const sourceIsAbstract = !!(sourceSignatures[0].flags & SignatureFlags.Abstract); - const targetIsAbstract = !!(targetSignatures[0].flags & SignatureFlags.Abstract); - if (sourceIsAbstract && !targetIsAbstract) { - // An abstract constructor type is not assignable to a non-abstract constructor type - // as it would otherwise be possible to new an abstract class. Note that the assignability - // check we perform for an extends clause excludes construct signatures from the target, - // so this check never proceeds. - if (reportErrors) { - reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type); - } - return Ternary.False; - } - if (!constructorVisibilitiesAreCompatible(sourceSignatures[0], targetSignatures[0], reportErrors)) { - return Ternary.False; + if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) { + const sourceIsAbstract = !!(sourceSignatures[0].flags & SignatureFlags.Abstract); + const targetIsAbstract = !!(targetSignatures[0].flags & SignatureFlags.Abstract); + if (sourceIsAbstract && !targetIsAbstract) { + // An abstract constructor type is not assignable to a non-abstract constructor type + // as it would otherwise be possible to new an abstract class. Note that the assignability + // check we perform for an extends clause excludes construct signatures from the target, + // so this check never proceeds. + if (reportErrors) { + reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type); } + return Ternary.False; + } + if (!constructorVisibilitiesAreCompatible(sourceSignatures[0], targetSignatures[0], reportErrors)) { + return Ternary.False; } + } - let result = Ternary.True; - const saveErrorInfo = captureErrorCalculationState(); - const incompatibleReporter = kind === SignatureKind.Construct ? reportIncompatibleConstructSignatureReturn : reportIncompatibleCallSignatureReturn; - const sourceObjectFlags = getObjectFlags(source); - const targetObjectFlags = getObjectFlags(target); - if (sourceObjectFlags & ObjectFlags.Instantiated && targetObjectFlags & ObjectFlags.Instantiated && source.symbol === target.symbol) { - // We have instantiations of the same anonymous type (which typically will be the type of a - // method). Simply do a pairwise comparison of the signatures in the two signature lists instead - // of the much more expensive N * M comparison matrix we explore below. We erase type parameters - // as they are known to always be the same. - for (let i = 0; i < targetSignatures.length; i++) { - const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, incompatibleReporter(sourceSignatures[i], targetSignatures[i])); - if (!related) { - return Ternary.False; - } - result &= related; + let result = Ternary.True; + const saveErrorInfo = captureErrorCalculationState(); + const incompatibleReporter = kind === SignatureKind.Construct ? reportIncompatibleConstructSignatureReturn : reportIncompatibleCallSignatureReturn; + const sourceObjectFlags = getObjectFlags(source); + const targetObjectFlags = getObjectFlags(target); + if (sourceObjectFlags & ObjectFlags.Instantiated && targetObjectFlags & ObjectFlags.Instantiated && source.symbol === target.symbol) { + // We have instantiations of the same anonymous type (which typically will be the type of a + // method). Simply do a pairwise comparison of the signatures in the two signature lists instead + // of the much more expensive N * M comparison matrix we explore below. We erase type parameters + // as they are known to always be the same. + for (let i = 0; i < targetSignatures.length; i++) { + const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, incompatibleReporter(sourceSignatures[i], targetSignatures[i])); + if (!related) { + return Ternary.False; } + result &= related; } - else if (sourceSignatures.length === 1 && targetSignatures.length === 1) { - // For simple functions (functions with a single signature) we only erase type parameters for - // the comparable relation. Otherwise, if the source signature is generic, we instantiate it - // in the context of the target signature before checking the relationship. Ideally we'd do - // this regardless of the number of signatures, but the potential costs are prohibitive due - // to the quadratic nature of the logic below. - const eraseGenerics = relation === comparableRelation || !!compilerOptions.noStrictGenericChecks; - const sourceSignature = first(sourceSignatures); - const targetSignature = first(targetSignatures); - result = signatureRelatedTo(sourceSignature, targetSignature, eraseGenerics, reportErrors, incompatibleReporter(sourceSignature, targetSignature)); - if (!result && reportErrors && kind === SignatureKind.Construct && (sourceObjectFlags & targetObjectFlags) && - (targetSignature.declaration?.kind === SyntaxKind.Constructor || sourceSignature.declaration?.kind === SyntaxKind.Constructor)) { - const constructSignatureToString = (signature: Signature) => - signatureToString(signature, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrowStyleSignature, kind); - reportError(Diagnostics.Type_0_is_not_assignable_to_type_1, constructSignatureToString(sourceSignature), constructSignatureToString(targetSignature)); - reportError(Diagnostics.Types_of_construct_signatures_are_incompatible); - return result; - } + } + else if (sourceSignatures.length === 1 && targetSignatures.length === 1) { + // For simple functions (functions with a single signature) we only erase type parameters for + // the comparable relation. Otherwise, if the source signature is generic, we instantiate it + // in the context of the target signature before checking the relationship. Ideally we'd do + // this regardless of the number of signatures, but the potential costs are prohibitive due + // to the quadratic nature of the logic below. + const eraseGenerics = relation === comparableRelation || !!compilerOptions.noStrictGenericChecks; + const sourceSignature = first(sourceSignatures); + const targetSignature = first(targetSignatures); + result = signatureRelatedTo(sourceSignature, targetSignature, eraseGenerics, reportErrors, incompatibleReporter(sourceSignature, targetSignature)); + if (!result && reportErrors && kind === SignatureKind.Construct && (sourceObjectFlags & targetObjectFlags) && + (targetSignature.declaration?.kind === SyntaxKind.Constructor || sourceSignature.declaration?.kind === SyntaxKind.Constructor)) { + const constructSignatureToString = (signature: Signature) => + signatureToString(signature, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrowStyleSignature, kind); + reportError(Diagnostics.Type_0_is_not_assignable_to_type_1, constructSignatureToString(sourceSignature), constructSignatureToString(targetSignature)); + reportError(Diagnostics.Types_of_construct_signatures_are_incompatible); + return result; } - else { - outer: for (const t of targetSignatures) { - // Only elaborate errors from the first failure - let shouldElaborateErrors = reportErrors; - for (const s of sourceSignatures) { - const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, incompatibleReporter(s, t)); - if (related) { - result &= related; - resetErrorInfo(saveErrorInfo); - continue outer; - } - shouldElaborateErrors = false; + } + else { + outer: for (const t of targetSignatures) { + // Only elaborate errors from the first failure + let shouldElaborateErrors = reportErrors; + for (const s of sourceSignatures) { + const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, incompatibleReporter(s, t)); + if (related) { + result &= related; + resetErrorInfo(saveErrorInfo); + continue outer; } + shouldElaborateErrors = false; + } - if (shouldElaborateErrors) { - reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1, - typeToString(source), - signatureToString(t, /*enclosingDeclaration*/ undefined, /*flags*/ undefined, kind)); - } - return Ternary.False; + if (shouldElaborateErrors) { + reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1, + typeToString(source), + signatureToString(t, /*enclosingDeclaration*/ undefined, /*flags*/ undefined, kind)); } + return Ternary.False; } - return result; } + return result; + } - function reportIncompatibleCallSignatureReturn(siga: Signature, sigb: Signature) { - if (siga.parameters.length === 0 && sigb.parameters.length === 0) { - return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); - } - return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); + function reportIncompatibleCallSignatureReturn(siga: Signature, sigb: Signature) { + if (siga.parameters.length === 0 && sigb.parameters.length === 0) { + return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); } + return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); + } - function reportIncompatibleConstructSignatureReturn(siga: Signature, sigb: Signature) { - if (siga.parameters.length === 0 && sigb.parameters.length === 0) { - return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); - } - return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); + function reportIncompatibleConstructSignatureReturn(siga: Signature, sigb: Signature) { + if (siga.parameters.length === 0 && sigb.parameters.length === 0) { + return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); } + return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); + } - /** - * See signatureAssignableTo, compareSignaturesIdentical - */ - function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { - return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, - relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, makeFunctionTypeMapper(reportUnreliableMarkers)); - } + /** + * See signatureAssignableTo, compareSignaturesIdentical + */ + function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { + return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, + relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, makeFunctionTypeMapper(reportUnreliableMarkers)); + } - function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { - const sourceSignatures = getSignaturesOfType(source, kind); - const targetSignatures = getSignaturesOfType(target, kind); - if (sourceSignatures.length !== targetSignatures.length) { + function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { + const sourceSignatures = getSignaturesOfType(source, kind); + const targetSignatures = getSignaturesOfType(target, kind); + if (sourceSignatures.length !== targetSignatures.length) { + return Ternary.False; + } + let result = Ternary.True; + for (let i = 0; i < sourceSignatures.length; i++) { + const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, isRelatedTo); + if (!related) { return Ternary.False; } - let result = Ternary.True; - for (let i = 0; i < sourceSignatures.length; i++) { - const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, isRelatedTo); - if (!related) { - return Ternary.False; - } - result &= related; - } - return result; + result &= related; } + return result; + } - function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean): Ternary { - let result = Ternary.True; - const keyType = targetInfo.keyType; - const props = source.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(source as IntersectionType) : getPropertiesOfObjectType(source); - for (const prop of props) { - // Skip over ignored JSX and symbol-named members - if (isIgnoredJsxProperty(source, prop)) { - continue; - } - if (isApplicableIndexType(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), keyType)) { - const propType = getNonMissingTypeOfSymbol(prop); - const type = exactOptionalPropertyTypes || propType.flags & TypeFlags.Undefined || keyType === numberType || !(prop.flags & SymbolFlags.Optional) - ? propType - : getTypeWithFacts(propType, TypeFacts.NEUndefined); - const related = isRelatedTo(type, targetInfo.type, RecursionFlags.Both, reportErrors); - if (!related) { - if (reportErrors) { - reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); - } - return Ternary.False; - } - result &= related; - } + function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean): Ternary { + let result = Ternary.True; + const keyType = targetInfo.keyType; + const props = source.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(source as IntersectionType) : getPropertiesOfObjectType(source); + for (const prop of props) { + // Skip over ignored JSX and symbol-named members + if (isIgnoredJsxProperty(source, prop)) { + continue; } - for (const info of getIndexInfosOfType(source)) { - if (isApplicableIndexType(info.keyType, keyType)) { - const related = indexInfoRelatedTo(info, targetInfo, reportErrors); - if (!related) { - return Ternary.False; + if (isApplicableIndexType(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), keyType)) { + const propType = getNonMissingTypeOfSymbol(prop); + const type = exactOptionalPropertyTypes || propType.flags & TypeFlags.Undefined || keyType === numberType || !(prop.flags & SymbolFlags.Optional) + ? propType + : getTypeWithFacts(propType, TypeFacts.NEUndefined); + const related = isRelatedTo(type, targetInfo.type, RecursionFlags.Both, reportErrors); + if (!related) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); } - result &= related; - } - } - return result; - } - - function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean) { - const related = isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both, reportErrors); - if (!related && reportErrors) { - if (sourceInfo.keyType === targetInfo.keyType) { - reportError(Diagnostics._0_index_signatures_are_incompatible, typeToString(sourceInfo.keyType)); - } - else { - reportError(Diagnostics._0_and_1_index_signatures_are_incompatible, typeToString(sourceInfo.keyType), typeToString(targetInfo.keyType)); + return Ternary.False; } + result &= related; } - return related; } - - function indexSignaturesRelatedTo(source: Type, target: Type, sourceIsPrimitive: boolean, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - if (relation === identityRelation) { - return indexSignaturesIdenticalTo(source, target); - } - const indexInfos = getIndexInfosOfType(target); - const targetHasStringIndex = some(indexInfos, info => info.keyType === stringType); - let result = Ternary.True; - for (const targetInfo of indexInfos) { - const related = !sourceIsPrimitive && targetHasStringIndex && targetInfo.type.flags & TypeFlags.Any ? Ternary.True : - isGenericMappedType(source) && targetHasStringIndex ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, RecursionFlags.Both, reportErrors) : - typeRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState); + for (const info of getIndexInfosOfType(source)) { + if (isApplicableIndexType(info.keyType, keyType)) { + const related = indexInfoRelatedTo(info, targetInfo, reportErrors); if (!related) { return Ternary.False; } result &= related; } - return result; } + return result; + } - function typeRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - const sourceInfo = getApplicableIndexInfo(source, targetInfo.keyType); - if (sourceInfo) { - return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors); - } - if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) { - // Intersection constituents are never considered to have an inferred index signature - return membersRelatedToIndexInfo(source, targetInfo, reportErrors); + function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean) { + const related = isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both, reportErrors); + if (!related && reportErrors) { + if (sourceInfo.keyType === targetInfo.keyType) { + reportError(Diagnostics._0_index_signatures_are_incompatible, typeToString(sourceInfo.keyType)); } - if (reportErrors) { - reportError(Diagnostics.Index_signature_for_type_0_is_missing_in_type_1, typeToString(targetInfo.keyType), typeToString(source)); + else { + reportError(Diagnostics._0_and_1_index_signatures_are_incompatible, typeToString(sourceInfo.keyType), typeToString(targetInfo.keyType)); } - return Ternary.False; } + return related; + } - function indexSignaturesIdenticalTo(source: Type, target: Type): Ternary { - const sourceInfos = getIndexInfosOfType(source); - const targetInfos = getIndexInfosOfType(target); - if (sourceInfos.length !== targetInfos.length) { - return Ternary.False; - } - for (const targetInfo of targetInfos) { - const sourceInfo = getIndexInfoOfType(source, targetInfo.keyType); - if (!(sourceInfo && isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both) && sourceInfo.isReadonly === targetInfo.isReadonly)) { - return Ternary.False; - } - } - return Ternary.True; + function indexSignaturesRelatedTo(source: Type, target: Type, sourceIsPrimitive: boolean, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + if (relation === identityRelation) { + return indexSignaturesIdenticalTo(source, target); } - - function constructorVisibilitiesAreCompatible(sourceSignature: Signature, targetSignature: Signature, reportErrors: boolean) { - if (!sourceSignature.declaration || !targetSignature.declaration) { - return true; + const indexInfos = getIndexInfosOfType(target); + const targetHasStringIndex = some(indexInfos, info => info.keyType === stringType); + let result = Ternary.True; + for (const targetInfo of indexInfos) { + const related = !sourceIsPrimitive && targetHasStringIndex && targetInfo.type.flags & TypeFlags.Any ? Ternary.True : + isGenericMappedType(source) && targetHasStringIndex ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, RecursionFlags.Both, reportErrors) : + typeRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState); + if (!related) { + return Ternary.False; } + result &= related; + } + return result; + } - const sourceAccessibility = getSelectedEffectiveModifierFlags(sourceSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); - const targetAccessibility = getSelectedEffectiveModifierFlags(targetSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); - - // A public, protected and private signature is assignable to a private signature. - if (targetAccessibility === ModifierFlags.Private) { - return true; - } + function typeRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + const sourceInfo = getApplicableIndexInfo(source, targetInfo.keyType); + if (sourceInfo) { + return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors); + } + if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) { + // Intersection constituents are never considered to have an inferred index signature + return membersRelatedToIndexInfo(source, targetInfo, reportErrors); + } + if (reportErrors) { + reportError(Diagnostics.Index_signature_for_type_0_is_missing_in_type_1, typeToString(targetInfo.keyType), typeToString(source)); + } + return Ternary.False; + } - // A public and protected signature is assignable to a protected signature. - if (targetAccessibility === ModifierFlags.Protected && sourceAccessibility !== ModifierFlags.Private) { - return true; + function indexSignaturesIdenticalTo(source: Type, target: Type): Ternary { + const sourceInfos = getIndexInfosOfType(source); + const targetInfos = getIndexInfosOfType(target); + if (sourceInfos.length !== targetInfos.length) { + return Ternary.False; + } + for (const targetInfo of targetInfos) { + const sourceInfo = getIndexInfoOfType(source, targetInfo.keyType); + if (!(sourceInfo && isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both) && sourceInfo.isReadonly === targetInfo.isReadonly)) { + return Ternary.False; } + } + return Ternary.True; + } - // Only a public signature is assignable to public signature. - if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) { - return true; - } + function constructorVisibilitiesAreCompatible(sourceSignature: Signature, targetSignature: Signature, reportErrors: boolean) { + if (!sourceSignature.declaration || !targetSignature.declaration) { + return true; + } - if (reportErrors) { - reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility)); - } + const sourceAccessibility = getSelectedEffectiveModifierFlags(sourceSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); + const targetAccessibility = getSelectedEffectiveModifierFlags(targetSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); - return false; + // A public, protected and private signature is assignable to a private signature. + if (targetAccessibility === ModifierFlags.Private) { + return true; } - } - function typeCouldHaveTopLevelSingletonTypes(type: Type): boolean { - // Okay, yes, 'boolean' is a union of 'true | false', but that's not useful - // in error reporting scenarios. If you need to use this function but that detail matters, - // feel free to add a flag. - if (type.flags & TypeFlags.Boolean) { - return false; + // A public and protected signature is assignable to a protected signature. + if (targetAccessibility === ModifierFlags.Protected && sourceAccessibility !== ModifierFlags.Private) { + return true; } - if (type.flags & TypeFlags.UnionOrIntersection) { - return !!forEach((type as IntersectionType).types, typeCouldHaveTopLevelSingletonTypes); + // Only a public signature is assignable to public signature. + if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) { + return true; } - if (type.flags & TypeFlags.Instantiable) { - const constraint = getConstraintOfType(type); - if (constraint && constraint !== type) { - return typeCouldHaveTopLevelSingletonTypes(constraint); - } + if (reportErrors) { + reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility)); } - return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral); + return false; } + } - function getExactOptionalUnassignableProperties(source: Type, target: Type) { - if (isTupleType(source) && isTupleType(target)) return emptyArray; - return getPropertiesOfType(target) - .filter(targetProp => isExactOptionalPropertyMismatch(getTypeOfPropertyOfType(source, targetProp.escapedName), getTypeOfSymbol(targetProp))); + function typeCouldHaveTopLevelSingletonTypes(type: Type): boolean { + // Okay, yes, 'boolean' is a union of 'true | false', but that's not useful + // in error reporting scenarios. If you need to use this function but that detail matters, + // feel free to add a flag. + if (type.flags & TypeFlags.Boolean) { + return false; } - function isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined) { - return !!source && !!target && maybeTypeOfKind(source, TypeFlags.Undefined) && !!containsMissingType(target); + if (type.flags & TypeFlags.UnionOrIntersection) { + return !!forEach((type as IntersectionType).types, typeCouldHaveTopLevelSingletonTypes); } - function getExactOptionalProperties(type: Type) { - return getPropertiesOfType(type).filter(targetProp => containsMissingType(getTypeOfSymbol(targetProp))); + if (type.flags & TypeFlags.Instantiable) { + const constraint = getConstraintOfType(type); + if (constraint && constraint !== type) { + return typeCouldHaveTopLevelSingletonTypes(constraint); + } } - function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { - return findMatchingDiscriminantType(source, target, isRelatedTo, /*skipPartial*/ true) || - findMatchingTypeReferenceOrTypeAliasReference(source, target) || - findBestTypeForObjectLiteral(source, target) || - findBestTypeForInvokable(source, target) || - findMostOverlappyType(source, target); - } + return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral); + } - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: undefined, skipPartial?: boolean): Type | undefined; - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type, skipPartial?: boolean): Type; - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type, skipPartial?: boolean) { - // undefined=unknown, true=discriminated, false=not discriminated - // The state of each type progresses from left to right. Discriminated types stop at 'true'. - const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; - for (const [getDiscriminatingType, propertyName] of discriminators) { - const targetProp = getUnionOrIntersectionProperty(target, propertyName); - if (skipPartial && targetProp && getCheckFlags(targetProp) & CheckFlags.ReadPartial) { - continue; + function getExactOptionalUnassignableProperties(source: Type, target: Type) { + if (isTupleType(source) && isTupleType(target)) return emptyArray; + return getPropertiesOfType(target) + .filter(targetProp => isExactOptionalPropertyMismatch(getTypeOfPropertyOfType(source, targetProp.escapedName), getTypeOfSymbol(targetProp))); + } + + function isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined) { + return !!source && !!target && maybeTypeOfKind(source, TypeFlags.Undefined) && !!containsMissingType(target); + } + + function getExactOptionalProperties(type: Type) { + return getPropertiesOfType(type).filter(targetProp => containsMissingType(getTypeOfSymbol(targetProp))); + } + + function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { + return findMatchingDiscriminantType(source, target, isRelatedTo, /*skipPartial*/ true) || + findMatchingTypeReferenceOrTypeAliasReference(source, target) || + findBestTypeForObjectLiteral(source, target) || + findBestTypeForInvokable(source, target) || + findMostOverlappyType(source, target); + } + + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: undefined, skipPartial?: boolean): Type | undefined; + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type, skipPartial?: boolean): Type; + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type, skipPartial?: boolean) { + // undefined=unknown, true=discriminated, false=not discriminated + // The state of each type progresses from left to right. Discriminated types stop at 'true'. + const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; + for (const [getDiscriminatingType, propertyName] of discriminators) { + const targetProp = getUnionOrIntersectionProperty(target, propertyName); + if (skipPartial && targetProp && getCheckFlags(targetProp) & CheckFlags.ReadPartial) { + continue; + } + let i = 0; + for (const type of target.types) { + const targetType = getTypeOfPropertyOfType(type, propertyName); + if (targetType && related(getDiscriminatingType(), targetType)) { + discriminable[i] = discriminable[i] === undefined ? true : discriminable[i]; } - let i = 0; - for (const type of target.types) { - const targetType = getTypeOfPropertyOfType(type, propertyName); - if (targetType && related(getDiscriminatingType(), targetType)) { - discriminable[i] = discriminable[i] === undefined ? true : discriminable[i]; - } - else { - discriminable[i] = false; - } - i++; + else { + discriminable[i] = false; } + i++; } - const match = discriminable.indexOf(/*searchElement*/ true); - if (match === -1) { + } + const match = discriminable.indexOf(/*searchElement*/ true); + if (match === -1) { + return defaultValue; + } + // make sure exactly 1 matches before returning it + let nextMatch = discriminable.indexOf(/*searchElement*/ true, match + 1); + while (nextMatch !== -1) { + if (!isTypeIdenticalTo(target.types[match], target.types[nextMatch])) { return defaultValue; } - // make sure exactly 1 matches before returning it - let nextMatch = discriminable.indexOf(/*searchElement*/ true, match + 1); - while (nextMatch !== -1) { - if (!isTypeIdenticalTo(target.types[match], target.types[nextMatch])) { - return defaultValue; - } - nextMatch = discriminable.indexOf(/*searchElement*/ true, nextMatch + 1); - } - return target.types[match]; + nextMatch = discriminable.indexOf(/*searchElement*/ true, nextMatch + 1); } + return target.types[match]; + } - /** - * A type is 'weak' if it is an object type with at least one optional property - * and no required properties, call/construct signatures or index signatures - */ - function isWeakType(type: Type): boolean { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 && resolved.indexInfos.length === 0 && - resolved.properties.length > 0 && every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional)); - } - if (type.flags & TypeFlags.Intersection) { - return every((type as IntersectionType).types, isWeakType); - } - return false; + /** + * A type is 'weak' if it is an object type with at least one optional property + * and no required properties, call/construct signatures or index signatures + */ + function isWeakType(type: Type): boolean { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 && resolved.indexInfos.length === 0 && + resolved.properties.length > 0 && every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional)); } + if (type.flags & TypeFlags.Intersection) { + return every((type as IntersectionType).types, isWeakType); + } + return false; + } - function hasCommonProperties(source: Type, target: Type, isComparingJsxAttributes: boolean) { - for (const prop of getPropertiesOfType(source)) { - if (isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) { - return true; - } + function hasCommonProperties(source: Type, target: Type, isComparingJsxAttributes: boolean) { + for (const prop of getPropertiesOfType(source)) { + if (isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) { + return true; } - return false; } + return false; + } - // Return a type reference where the source type parameter is replaced with the target marker - // type, and flag the result as a marker type reference. - function getMarkerTypeReference(type: GenericType, source: TypeParameter, target: Type) { - const result = createTypeReference(type, map(type.typeParameters, t => t === source ? target : t)); - result.objectFlags |= ObjectFlags.MarkerType; - return result; + // Return a type reference where the source type parameter is replaced with the target marker + // type, and flag the result as a marker type reference. + function getMarkerTypeReference(type: GenericType, source: TypeParameter, target: Type) { + const result = createTypeReference(type, map(type.typeParameters, t => t === source ? target : t)); + result.objectFlags |= ObjectFlags.MarkerType; + return result; + } + + function getAliasVariances(symbol: Symbol) { + const links = getSymbolLinks(symbol); + return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => { + const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker))); + type.aliasTypeArgumentsContainsMarker = true; + return type; + }); + } + + // Return an array containing the variance of each type parameter. The variance is effectively + // a digest of the type comparisons that occur for each type argument when instantiations of the + // generic type are structurally compared. We infer the variance information by comparing + // instantiations of the generic type for type arguments with known relations. The function + // returns the emptyArray singleton when invoked recursively for the given generic type. + function getVariancesWorker(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): VarianceFlags[] { + let variances = cache.variances; + if (!variances) { + tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: (cache as any).id ?? (cache as any).declaredType?.id ?? -1 }); + // The emptyArray singleton is used to signal a recursive invocation. + cache.variances = emptyArray; + variances = []; + for (const tp of typeParameters) { + let unmeasurable = false; + let unreliable = false; + const oldHandler = outofbandVarianceMarkerHandler; + outofbandVarianceMarkerHandler = (onlyUnreliable) => onlyUnreliable ? unreliable = true : unmeasurable = true; + // We first compare instantiations where the type parameter is replaced with + // marker types that have a known subtype relationship. From this we can infer + // invariance, covariance, contravariance or bivariance. + const typeWithSuper = createMarkerType(cache, tp, markerSuperType); + const typeWithSub = createMarkerType(cache, tp, markerSubType); + let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) | + (isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0); + // If the instantiations appear to be related bivariantly it may be because the + // type parameter is independent (i.e. it isn't witnessed anywhere in the generic + // type). To determine this we compare instantiations where the type parameter is + // replaced with marker types that are known to be unrelated. + if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) { + variance = VarianceFlags.Independent; + } + outofbandVarianceMarkerHandler = oldHandler; + if (unmeasurable || unreliable) { + if (unmeasurable) { + variance |= VarianceFlags.Unmeasurable; + } + if (unreliable) { + variance |= VarianceFlags.Unreliable; + } + } + variances.push(variance); + } + cache.variances = variances; + tracing?.pop(); } + return variances; + } - function getAliasVariances(symbol: Symbol) { - const links = getSymbolLinks(symbol); - return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => { - const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker))); - type.aliasTypeArgumentsContainsMarker = true; - return type; - }); + function getVariances(type: GenericType): VarianceFlags[] { + // Arrays and tuples are known to be covariant, no need to spend time computing this. + if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) { + return arrayVariances; } + return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference); + } - // Return an array containing the variance of each type parameter. The variance is effectively - // a digest of the type comparisons that occur for each type argument when instantiations of the - // generic type are structurally compared. We infer the variance information by comparing - // instantiations of the generic type for type arguments with known relations. The function - // returns the emptyArray singleton when invoked recursively for the given generic type. - function getVariancesWorker(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): VarianceFlags[] { - let variances = cache.variances; - if (!variances) { - tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: (cache as any).id ?? (cache as any).declaredType?.id ?? -1 }); - // The emptyArray singleton is used to signal a recursive invocation. - cache.variances = emptyArray; - variances = []; - for (const tp of typeParameters) { - let unmeasurable = false; - let unreliable = false; - const oldHandler = outofbandVarianceMarkerHandler; - outofbandVarianceMarkerHandler = (onlyUnreliable) => onlyUnreliable ? unreliable = true : unmeasurable = true; - // We first compare instantiations where the type parameter is replaced with - // marker types that have a known subtype relationship. From this we can infer - // invariance, covariance, contravariance or bivariance. - const typeWithSuper = createMarkerType(cache, tp, markerSuperType); - const typeWithSub = createMarkerType(cache, tp, markerSubType); - let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) | - (isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0); - // If the instantiations appear to be related bivariantly it may be because the - // type parameter is independent (i.e. it isn't witnessed anywhere in the generic - // type). To determine this we compare instantiations where the type parameter is - // replaced with marker types that are known to be unrelated. - if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) { - variance = VarianceFlags.Independent; - } - outofbandVarianceMarkerHandler = oldHandler; - if (unmeasurable || unreliable) { - if (unmeasurable) { - variance |= VarianceFlags.Unmeasurable; - } - if (unreliable) { - variance |= VarianceFlags.Unreliable; - } - } - variances.push(variance); - } - cache.variances = variances; - tracing?.pop(); - } - return variances; - } - - function getVariances(type: GenericType): VarianceFlags[] { - // Arrays and tuples are known to be covariant, no need to spend time computing this. - if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) { - return arrayVariances; - } - return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference); - } - - // Return true if the given type reference has a 'void' type argument for a covariant type parameter. - // See comment at call in recursiveTypeRelatedTo for when this case matters. - function hasCovariantVoidArgument(typeArguments: readonly Type[], variances: VarianceFlags[]): boolean { - for (let i = 0; i < variances.length; i++) { - if ((variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Covariant && typeArguments[i].flags & TypeFlags.Void) { - return true; - } + // Return true if the given type reference has a 'void' type argument for a covariant type parameter. + // See comment at call in recursiveTypeRelatedTo for when this case matters. + function hasCovariantVoidArgument(typeArguments: readonly Type[], variances: VarianceFlags[]): boolean { + for (let i = 0; i < variances.length; i++) { + if ((variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Covariant && typeArguments[i].flags & TypeFlags.Void) { + return true; } - return false; } + return false; + } - function isUnconstrainedTypeParameter(type: Type) { - return type.flags & TypeFlags.TypeParameter && !getConstraintOfTypeParameter(type as TypeParameter); - } + function isUnconstrainedTypeParameter(type: Type) { + return type.flags & TypeFlags.TypeParameter && !getConstraintOfTypeParameter(type as TypeParameter); + } - function isNonDeferredTypeReference(type: Type): type is TypeReference { - return !!(getObjectFlags(type) & ObjectFlags.Reference) && !(type as TypeReference).node; - } + function isNonDeferredTypeReference(type: Type): type is TypeReference { + return !!(getObjectFlags(type) & ObjectFlags.Reference) && !(type as TypeReference).node; + } - function isTypeReferenceWithGenericArguments(type: Type): boolean { - return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => !!(t.flags & TypeFlags.TypeParameter) || isTypeReferenceWithGenericArguments(t)); - } + function isTypeReferenceWithGenericArguments(type: Type): boolean { + return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => !!(t.flags & TypeFlags.TypeParameter) || isTypeReferenceWithGenericArguments(t)); + } - function getGenericTypeReferenceRelationKey(source: TypeReference, target: TypeReference, postFix: string, ignoreConstraints: boolean) { - const typeParameters: Type[] = []; - let constraintMarker = ""; - const sourceId = getTypeReferenceId(source, 0); - const targetId = getTypeReferenceId(target, 0); - return `${constraintMarker}${sourceId},${targetId}${postFix}`; - // getTypeReferenceId(A) returns "111=0-12=1" - // where A.id=111 and number.id=12 - function getTypeReferenceId(type: TypeReference, depth = 0) { - let result = "" + type.target.id; - for (const t of getTypeArguments(type)) { - if (t.flags & TypeFlags.TypeParameter) { - if (ignoreConstraints || isUnconstrainedTypeParameter(t)) { - let index = typeParameters.indexOf(t); - if (index < 0) { - index = typeParameters.length; - typeParameters.push(t); - } - result += "=" + index; - continue; + function getGenericTypeReferenceRelationKey(source: TypeReference, target: TypeReference, postFix: string, ignoreConstraints: boolean) { + const typeParameters: Type[] = []; + let constraintMarker = ""; + const sourceId = getTypeReferenceId(source, 0); + const targetId = getTypeReferenceId(target, 0); + return `${constraintMarker}${sourceId},${targetId}${postFix}`; + // getTypeReferenceId(A) returns "111=0-12=1" + // where A.id=111 and number.id=12 + function getTypeReferenceId(type: TypeReference, depth = 0) { + let result = "" + type.target.id; + for (const t of getTypeArguments(type)) { + if (t.flags & TypeFlags.TypeParameter) { + if (ignoreConstraints || isUnconstrainedTypeParameter(t)) { + let index = typeParameters.indexOf(t); + if (index < 0) { + index = typeParameters.length; + typeParameters.push(t); } - // We mark type references that reference constrained type parameters such that we know to obtain - // and look for a "broadest equivalent key" in the cache. - constraintMarker = "*"; - } - else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) { - result += "<" + getTypeReferenceId(t as TypeReference, depth + 1) + ">"; + result += "=" + index; continue; } - result += "-" + t.id; + // We mark type references that reference constrained type parameters such that we know to obtain + // and look for a "broadest equivalent key" in the cache. + constraintMarker = "*"; } - return result; + else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) { + result += "<" + getTypeReferenceId(t as TypeReference, depth + 1) + ">"; + continue; + } + result += "-" + t.id; } + return result; } + } - /** - * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters. - * For other cases, the types ids are used. - */ - function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: ESMap, ignoreConstraints: boolean) { - if (relation === identityRelation && source.id > target.id) { - const temp = source; - source = target; - target = temp; - } - const postFix = intersectionState ? ":" + intersectionState : ""; - return isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target) ? - getGenericTypeReferenceRelationKey(source as TypeReference, target as TypeReference, postFix, ignoreConstraints) : - `${source.id},${target.id}${postFix}`; - } - - // Invoke the callback for each underlying property symbol of the given symbol and return the first - // value that isn't undefined. - function forEachProperty(prop: Symbol, callback: (p: Symbol) => T): T | undefined { - if (getCheckFlags(prop) & CheckFlags.Synthetic) { - for (const t of (prop as TransientSymbol).containingType!.types) { - const p = getPropertyOfType(t, prop.escapedName); - const result = p && forEachProperty(p, callback); - if (result) { - return result; - } + /** + * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters. + * For other cases, the types ids are used. + */ + function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: ESMap, ignoreConstraints: boolean) { + if (relation === identityRelation && source.id > target.id) { + const temp = source; + source = target; + target = temp; + } + const postFix = intersectionState ? ":" + intersectionState : ""; + return isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target) ? + getGenericTypeReferenceRelationKey(source as TypeReference, target as TypeReference, postFix, ignoreConstraints) : + `${source.id},${target.id}${postFix}`; + } + + // Invoke the callback for each underlying property symbol of the given symbol and return the first + // value that isn't undefined. + function forEachProperty(prop: Symbol, callback: (p: Symbol) => T): T | undefined { + if (getCheckFlags(prop) & CheckFlags.Synthetic) { + for (const t of (prop as TransientSymbol).containingType!.types) { + const p = getPropertyOfType(t, prop.escapedName); + const result = p && forEachProperty(p, callback); + if (result) { + return result; } - return undefined; } - return callback(prop); + return undefined; } + return callback(prop); + } - // Return the declaring class type of a property or undefined if property not declared in class - function getDeclaringClass(prop: Symbol) { - return prop.parent && prop.parent.flags & SymbolFlags.Class ? getDeclaredTypeOfSymbol(getParentOfSymbol(prop)!) as InterfaceType : undefined; - } + // Return the declaring class type of a property or undefined if property not declared in class + function getDeclaringClass(prop: Symbol) { + return prop.parent && prop.parent.flags & SymbolFlags.Class ? getDeclaredTypeOfSymbol(getParentOfSymbol(prop)!) as InterfaceType : undefined; + } - // Return the inherited type of the given property or undefined if property doesn't exist in a base class. - function getTypeOfPropertyInBaseClass(property: Symbol) { - const classType = getDeclaringClass(property); - const baseClassType = classType && getBaseTypes(classType)[0]; - return baseClassType && getTypeOfPropertyOfType(baseClassType, property.escapedName); - } + // Return the inherited type of the given property or undefined if property doesn't exist in a base class. + function getTypeOfPropertyInBaseClass(property: Symbol) { + const classType = getDeclaringClass(property); + const baseClassType = classType && getBaseTypes(classType)[0]; + return baseClassType && getTypeOfPropertyOfType(baseClassType, property.escapedName); + } - // Return true if some underlying source property is declared in a class that derives - // from the given base class. - function isPropertyInClassDerivedFrom(prop: Symbol, baseClass: Type | undefined) { - return forEachProperty(prop, sp => { - const sourceClass = getDeclaringClass(sp); - return sourceClass ? hasBaseType(sourceClass, baseClass) : false; - }); - } + // Return true if some underlying source property is declared in a class that derives + // from the given base class. + function isPropertyInClassDerivedFrom(prop: Symbol, baseClass: Type | undefined) { + return forEachProperty(prop, sp => { + const sourceClass = getDeclaringClass(sp); + return sourceClass ? hasBaseType(sourceClass, baseClass) : false; + }); + } - // Return true if source property is a valid override of protected parts of target property. - function isValidOverrideOf(sourceProp: Symbol, targetProp: Symbol) { - return !forEachProperty(targetProp, tp => getDeclarationModifierFlagsFromSymbol(tp) & ModifierFlags.Protected ? - !isPropertyInClassDerivedFrom(sourceProp, getDeclaringClass(tp)) : false); - } - - // Return true if the given class derives from each of the declaring classes of the protected - // constituents of the given property. - function isClassDerivedFromDeclaringClasses(checkClass: Type, prop: Symbol, writing: boolean) { - return forEachProperty(prop, p => getDeclarationModifierFlagsFromSymbol(p, writing) & ModifierFlags.Protected ? - !hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass; - } - - // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons - // for maxDepth or more occurrences or instantiations of the type have been recorded on the given stack. It is possible, - // though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely - // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least maxDepth - // levels, but unequal at some level beyond that. - // In addition, this will also detect when an indexed access has been chained off of maxDepth more times (which is - // essentially the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding - // false positives for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`). - // It also detects when a recursive type reference has expanded maxDepth or more times, e.g. if the true branch of - // `type A = null extends T ? [A>] : [T]` - // has expanded into `[A>>>>>]`. In such cases we need - // to terminate the expansion, and we do so here. - function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean { - if (depth >= maxDepth) { - const identity = getRecursionIdentity(type); - let count = 0; - let lastTypeId = 0; - for (let i = 0; i < depth; i++) { - const t = stack[i]; - if (getRecursionIdentity(t) === identity) { - // We only count occurrences with a higher type id than the previous occurrence, since higher - // type ids are an indicator of newer instantiations caused by recursion. - if (t.id >= lastTypeId) { - count++; - if (count >= maxDepth) { - return true; - } + // Return true if source property is a valid override of protected parts of target property. + function isValidOverrideOf(sourceProp: Symbol, targetProp: Symbol) { + return !forEachProperty(targetProp, tp => getDeclarationModifierFlagsFromSymbol(tp) & ModifierFlags.Protected ? + !isPropertyInClassDerivedFrom(sourceProp, getDeclaringClass(tp)) : false); + } + + // Return true if the given class derives from each of the declaring classes of the protected + // constituents of the given property. + function isClassDerivedFromDeclaringClasses(checkClass: Type, prop: Symbol, writing: boolean) { + return forEachProperty(prop, p => getDeclarationModifierFlagsFromSymbol(p, writing) & ModifierFlags.Protected ? + !hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass; + } + + // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons + // for maxDepth or more occurrences or instantiations of the type have been recorded on the given stack. It is possible, + // though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely + // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least maxDepth + // levels, but unequal at some level beyond that. + // In addition, this will also detect when an indexed access has been chained off of maxDepth more times (which is + // essentially the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding + // false positives for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`). + // It also detects when a recursive type reference has expanded maxDepth or more times, e.g. if the true branch of + // `type A = null extends T ? [A>] : [T]` + // has expanded into `[A>>>>>]`. In such cases we need + // to terminate the expansion, and we do so here. + function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean { + if (depth >= maxDepth) { + const identity = getRecursionIdentity(type); + let count = 0; + let lastTypeId = 0; + for (let i = 0; i < depth; i++) { + const t = stack[i]; + if (getRecursionIdentity(t) === identity) { + // We only count occurrences with a higher type id than the previous occurrence, since higher + // type ids are an indicator of newer instantiations caused by recursion. + if (t.id >= lastTypeId) { + count++; + if (count >= maxDepth) { + return true; } - lastTypeId = t.id; } + lastTypeId = t.id; } } - return false; } + return false; + } - // The recursion identity of a type is an object identity that is shared among multiple instantiations of the type. - // We track recursion identities in order to identify deeply nested and possibly infinite type instantiations with - // the same origin. For example, when type parameters are in scope in an object type such as { x: T }, all - // instantiations of that type have the same recursion identity. The default recursion identity is the object - // identity of the type, meaning that every type is unique. Generally, types with constituents that could circularly - // reference the type have a recursion identity that differs from the object identity. - function getRecursionIdentity(type: Type): object { - // Object and array literals are known not to contain recursive references and don't need a recursion identity. - if (type.flags & TypeFlags.Object && !isObjectOrArrayLiteralType(type)) { - if (getObjectFlags(type) && ObjectFlags.Reference && (type as TypeReference).node) { - // Deferred type references are tracked through their associated AST node. This gives us finer - // granularity than using their associated target because each manifest type reference has a - // unique AST node. - return (type as TypeReference).node!; - } - if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) { - // We track all object types that have an associated symbol (representing the origin of the type), but - // exclude the static side of classes from this check since it shares its symbol with the instance side. - return type.symbol; - } - if (isTupleType(type)) { - // Tuple types are tracked through their target type - return type.target; - } - } - if (type.flags & TypeFlags.TypeParameter) { + // The recursion identity of a type is an object identity that is shared among multiple instantiations of the type. + // We track recursion identities in order to identify deeply nested and possibly infinite type instantiations with + // the same origin. For example, when type parameters are in scope in an object type such as { x: T }, all + // instantiations of that type have the same recursion identity. The default recursion identity is the object + // identity of the type, meaning that every type is unique. Generally, types with constituents that could circularly + // reference the type have a recursion identity that differs from the object identity. + function getRecursionIdentity(type: Type): object { + // Object and array literals are known not to contain recursive references and don't need a recursion identity. + if (type.flags & TypeFlags.Object && !isObjectOrArrayLiteralType(type)) { + if (getObjectFlags(type) && ObjectFlags.Reference && (type as TypeReference).node) { + // Deferred type references are tracked through their associated AST node. This gives us finer + // granularity than using their associated target because each manifest type reference has a + // unique AST node. + return (type as TypeReference).node!; + } + if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) { + // We track all object types that have an associated symbol (representing the origin of the type), but + // exclude the static side of classes from this check since it shares its symbol with the instance side. return type.symbol; } - if (type.flags & TypeFlags.IndexedAccess) { - // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P][Q] it is A - do { - type = (type as IndexedAccessType).objectType; - } while (type.flags & TypeFlags.IndexedAccess); - return type; - } - if (type.flags & TypeFlags.Conditional) { - // The root object represents the origin of the conditional type - return (type as ConditionalType).root; + if (isTupleType(type)) { + // Tuple types are tracked through their target type + return type.target; } + } + if (type.flags & TypeFlags.TypeParameter) { + return type.symbol; + } + if (type.flags & TypeFlags.IndexedAccess) { + // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P][Q] it is A + do { + type = (type as IndexedAccessType).objectType; + } while (type.flags & TypeFlags.IndexedAccess); return type; } - - function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean { - return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False; + if (type.flags & TypeFlags.Conditional) { + // The root object represents the origin of the conditional type + return (type as ConditionalType).root; } + return type; + } - function compareProperties(sourceProp: Symbol, targetProp: Symbol, compareTypes: (source: Type, target: Type) => Ternary): Ternary { - // Two members are considered identical when - // - they are public properties with identical names, optionality, and types, - // - they are private or protected properties originating in the same declaration and having identical types - if (sourceProp === targetProp) { - return Ternary.True; - } - const sourcePropAccessibility = getDeclarationModifierFlagsFromSymbol(sourceProp) & ModifierFlags.NonPublicAccessibilityModifier; - const targetPropAccessibility = getDeclarationModifierFlagsFromSymbol(targetProp) & ModifierFlags.NonPublicAccessibilityModifier; - if (sourcePropAccessibility !== targetPropAccessibility) { + function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean { + return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False; + } + + function compareProperties(sourceProp: Symbol, targetProp: Symbol, compareTypes: (source: Type, target: Type) => Ternary): Ternary { + // Two members are considered identical when + // - they are public properties with identical names, optionality, and types, + // - they are private or protected properties originating in the same declaration and having identical types + if (sourceProp === targetProp) { + return Ternary.True; + } + const sourcePropAccessibility = getDeclarationModifierFlagsFromSymbol(sourceProp) & ModifierFlags.NonPublicAccessibilityModifier; + const targetPropAccessibility = getDeclarationModifierFlagsFromSymbol(targetProp) & ModifierFlags.NonPublicAccessibilityModifier; + if (sourcePropAccessibility !== targetPropAccessibility) { + return Ternary.False; + } + if (sourcePropAccessibility) { + if (getTargetSymbol(sourceProp) !== getTargetSymbol(targetProp)) { return Ternary.False; } - if (sourcePropAccessibility) { - if (getTargetSymbol(sourceProp) !== getTargetSymbol(targetProp)) { - return Ternary.False; - } - } - else { - if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) { - return Ternary.False; - } - } - if (isReadonlySymbol(sourceProp) !== isReadonlySymbol(targetProp)) { + } + else { + if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) { return Ternary.False; } - return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); - } - - function isMatchingSignature(source: Signature, target: Signature, partialMatch: boolean) { - const sourceParameterCount = getParameterCount(source); - const targetParameterCount = getParameterCount(target); - const sourceMinArgumentCount = getMinArgumentCount(source); - const targetMinArgumentCount = getMinArgumentCount(target); - const sourceHasRestParameter = hasEffectiveRestParameter(source); - const targetHasRestParameter = hasEffectiveRestParameter(target); - // A source signature matches a target signature if the two signatures have the same number of required, - // optional, and rest parameters. - if (sourceParameterCount === targetParameterCount && - sourceMinArgumentCount === targetMinArgumentCount && - sourceHasRestParameter === targetHasRestParameter) { - return true; - } - // A source signature partially matches a target signature if the target signature has no fewer required - // parameters - if (partialMatch && sourceMinArgumentCount <= targetMinArgumentCount) { - return true; - } - return false; } + if (isReadonlySymbol(sourceProp) !== isReadonlySymbol(targetProp)) { + return Ternary.False; + } + return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); + } - /** - * See signatureRelatedTo, compareSignaturesIdentical - */ - function compareSignaturesIdentical(source: Signature, target: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { - // TODO (drosen): De-duplicate code between related functions. - if (source === target) { - return Ternary.True; - } - if (!(isMatchingSignature(source, target, partialMatch))) { - return Ternary.False; - } - // Check that the two signatures have the same number of type parameters. - if (length(source.typeParameters) !== length(target.typeParameters)) { - return Ternary.False; - } - // Check that type parameter constraints and defaults match. If they do, instantiate the source - // signature with the type parameters of the target signature and continue the comparison. - if (target.typeParameters) { - const mapper = createTypeMapper(source.typeParameters!, target.typeParameters); - for (let i = 0; i < target.typeParameters.length; i++) { - const s = source.typeParameters![i]; - const t = target.typeParameters[i]; - if (!(s === t || compareTypes(instantiateType(getConstraintFromTypeParameter(s), mapper) || unknownType, getConstraintFromTypeParameter(t) || unknownType) && - compareTypes(instantiateType(getDefaultFromTypeParameter(s), mapper) || unknownType, getDefaultFromTypeParameter(t) || unknownType))) { - return Ternary.False; - } + function isMatchingSignature(source: Signature, target: Signature, partialMatch: boolean) { + const sourceParameterCount = getParameterCount(source); + const targetParameterCount = getParameterCount(target); + const sourceMinArgumentCount = getMinArgumentCount(source); + const targetMinArgumentCount = getMinArgumentCount(target); + const sourceHasRestParameter = hasEffectiveRestParameter(source); + const targetHasRestParameter = hasEffectiveRestParameter(target); + // A source signature matches a target signature if the two signatures have the same number of required, + // optional, and rest parameters. + if (sourceParameterCount === targetParameterCount && + sourceMinArgumentCount === targetMinArgumentCount && + sourceHasRestParameter === targetHasRestParameter) { + return true; + } + // A source signature partially matches a target signature if the target signature has no fewer required + // parameters + if (partialMatch && sourceMinArgumentCount <= targetMinArgumentCount) { + return true; + } + return false; + } + + /** + * See signatureRelatedTo, compareSignaturesIdentical + */ + function compareSignaturesIdentical(source: Signature, target: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { + // TODO (drosen): De-duplicate code between related functions. + if (source === target) { + return Ternary.True; + } + if (!(isMatchingSignature(source, target, partialMatch))) { + return Ternary.False; + } + // Check that the two signatures have the same number of type parameters. + if (length(source.typeParameters) !== length(target.typeParameters)) { + return Ternary.False; + } + // Check that type parameter constraints and defaults match. If they do, instantiate the source + // signature with the type parameters of the target signature and continue the comparison. + if (target.typeParameters) { + const mapper = createTypeMapper(source.typeParameters!, target.typeParameters); + for (let i = 0; i < target.typeParameters.length; i++) { + const s = source.typeParameters![i]; + const t = target.typeParameters[i]; + if (!(s === t || compareTypes(instantiateType(getConstraintFromTypeParameter(s), mapper) || unknownType, getConstraintFromTypeParameter(t) || unknownType) && + compareTypes(instantiateType(getDefaultFromTypeParameter(s), mapper) || unknownType, getDefaultFromTypeParameter(t) || unknownType))) { + return Ternary.False; } - source = instantiateSignature(source, mapper, /*eraseTypeParameters*/ true); } - let result = Ternary.True; - if (!ignoreThisTypes) { - const sourceThisType = getThisTypeOfSignature(source); - if (sourceThisType) { - const targetThisType = getThisTypeOfSignature(target); - if (targetThisType) { - const related = compareTypes(sourceThisType, targetThisType); - if (!related) { - return Ternary.False; - } - result &= related; + source = instantiateSignature(source, mapper, /*eraseTypeParameters*/ true); + } + let result = Ternary.True; + if (!ignoreThisTypes) { + const sourceThisType = getThisTypeOfSignature(source); + if (sourceThisType) { + const targetThisType = getThisTypeOfSignature(target); + if (targetThisType) { + const related = compareTypes(sourceThisType, targetThisType); + if (!related) { + return Ternary.False; } + result &= related; } } - const targetLen = getParameterCount(target); - for (let i = 0; i < targetLen; i++) { - const s = getTypeAtPosition(source, i); - const t = getTypeAtPosition(target, i); - const related = compareTypes(t, s); - if (!related) { - return Ternary.False; - } - result &= related; - } - if (!ignoreReturnTypes) { - const sourceTypePredicate = getTypePredicateOfSignature(source); - const targetTypePredicate = getTypePredicateOfSignature(target); - result &= sourceTypePredicate || targetTypePredicate ? - compareTypePredicatesIdentical(sourceTypePredicate, targetTypePredicate, compareTypes) : - compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); + } + const targetLen = getParameterCount(target); + for (let i = 0; i < targetLen; i++) { + const s = getTypeAtPosition(source, i); + const t = getTypeAtPosition(target, i); + const related = compareTypes(t, s); + if (!related) { + return Ternary.False; } - return result; + result &= related; } - - function compareTypePredicatesIdentical(source: TypePredicate | undefined, target: TypePredicate | undefined, compareTypes: (s: Type, t: Type) => Ternary): Ternary { - return !(source && target && typePredicateKindsMatch(source, target)) ? Ternary.False : - source.type === target.type ? Ternary.True : - source.type && target.type ? compareTypes(source.type, target.type) : - Ternary.False; + if (!ignoreReturnTypes) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + const targetTypePredicate = getTypePredicateOfSignature(target); + result &= sourceTypePredicate || targetTypePredicate ? + compareTypePredicatesIdentical(sourceTypePredicate, targetTypePredicate, compareTypes) : + compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); } + return result; + } - function literalTypesWithSameBaseType(types: Type[]): boolean { - let commonBaseType: Type | undefined; - for (const t of types) { - const baseType = getBaseTypeOfLiteralType(t); - if (!commonBaseType) { - commonBaseType = baseType; - } - if (baseType === t || baseType !== commonBaseType) { - return false; - } - } - return true; - } + function compareTypePredicatesIdentical(source: TypePredicate | undefined, target: TypePredicate | undefined, compareTypes: (s: Type, t: Type) => Ternary): Ternary { + return !(source && target && typePredicateKindsMatch(source, target)) ? Ternary.False : + source.type === target.type ? Ternary.True : + source.type && target.type ? compareTypes(source.type, target.type) : + Ternary.False; + } - // When the candidate types are all literal types with the same base type, return a union - // of those literal types. Otherwise, return the leftmost type for which no type to the - // right is a supertype. - function getSupertypeOrUnion(types: Type[]): Type { - if (types.length === 1) { - return types[0]; + function literalTypesWithSameBaseType(types: Type[]): boolean { + let commonBaseType: Type | undefined; + for (const t of types) { + const baseType = getBaseTypeOfLiteralType(t); + if (!commonBaseType) { + commonBaseType = baseType; } - return literalTypesWithSameBaseType(types) ? - getUnionType(types) : - reduceLeft(types, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!; - } - - function getCommonSupertype(types: Type[]): Type { - if (!strictNullChecks) { - return getSupertypeOrUnion(types); + if (baseType === t || baseType !== commonBaseType) { + return false; } - const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable)); - return primaryTypes.length ? - getNullableType(getSupertypeOrUnion(primaryTypes), getFalsyFlagsOfTypes(types) & TypeFlags.Nullable) : - getUnionType(types, UnionReduction.Subtype); } + return true; + } - // Return the leftmost type for which no type to the right is a subtype. - function getCommonSubtype(types: Type[]) { - return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s)!; + // When the candidate types are all literal types with the same base type, return a union + // of those literal types. Otherwise, return the leftmost type for which no type to the + // right is a supertype. + function getSupertypeOrUnion(types: Type[]): Type { + if (types.length === 1) { + return types[0]; } + return literalTypesWithSameBaseType(types) ? + getUnionType(types) : + reduceLeft(types, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!; + } - function isArrayType(type: Type): type is TypeReference { - return !!(getObjectFlags(type) & ObjectFlags.Reference) && ((type as TypeReference).target === globalArrayType || (type as TypeReference).target === globalReadonlyArrayType); + function getCommonSupertype(types: Type[]): Type { + if (!strictNullChecks) { + return getSupertypeOrUnion(types); } + const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable)); + return primaryTypes.length ? + getNullableType(getSupertypeOrUnion(primaryTypes), getFalsyFlagsOfTypes(types) & TypeFlags.Nullable) : + getUnionType(types, UnionReduction.Subtype); + } - function isReadonlyArrayType(type: Type): boolean { - return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type as TypeReference).target === globalReadonlyArrayType; - } + // Return the leftmost type for which no type to the right is a subtype. + function getCommonSubtype(types: Type[]) { + return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s)!; + } - function isMutableArrayOrTuple(type: Type): boolean { - return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly; - } + function isArrayType(type: Type): type is TypeReference { + return !!(getObjectFlags(type) & ObjectFlags.Reference) && ((type as TypeReference).target === globalArrayType || (type as TypeReference).target === globalReadonlyArrayType); + } - function getElementTypeOfArrayType(type: Type): Type | undefined { - return isArrayType(type) ? getTypeArguments(type)[0] : undefined; - } + function isReadonlyArrayType(type: Type): boolean { + return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type as TypeReference).target === globalReadonlyArrayType; + } - function isArrayLikeType(type: Type): boolean { - // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, - // or if it is not the undefined or null type and if it is assignable to ReadonlyArray - return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType); - } + function isMutableArrayOrTuple(type: Type): boolean { + return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly; + } + + function getElementTypeOfArrayType(type: Type): Type | undefined { + return isArrayType(type) ? getTypeArguments(type)[0] : undefined; + } + + function isArrayLikeType(type: Type): boolean { + // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, + // or if it is not the undefined or null type and if it is assignable to ReadonlyArray + return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType); + } - function getSingleBaseForNonAugmentingSubtype(type: Type) { - if (!(getObjectFlags(type) & ObjectFlags.Reference) || !(getObjectFlags((type as TypeReference).target) & ObjectFlags.ClassOrInterface)) { + function getSingleBaseForNonAugmentingSubtype(type: Type) { + if (!(getObjectFlags(type) & ObjectFlags.Reference) || !(getObjectFlags((type as TypeReference).target) & ObjectFlags.ClassOrInterface)) { + return undefined; + } + if (getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeCalculated) { + return getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeExists ? (type as TypeReference).cachedEquivalentBaseType : undefined; + } + (type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeCalculated; + const target = (type as TypeReference).target as InterfaceType; + if (getObjectFlags(target) & ObjectFlags.Class) { + const baseTypeNode = getBaseTypeNodeOfClass(target); + // A base type expression may circularly reference the class itself (e.g. as an argument to function call), so we only + // check for base types specified as simple qualified names. + if (baseTypeNode && baseTypeNode.expression.kind !== SyntaxKind.Identifier && baseTypeNode.expression.kind !== SyntaxKind.PropertyAccessExpression) { return undefined; } - if (getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeCalculated) { - return getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeExists ? (type as TypeReference).cachedEquivalentBaseType : undefined; - } - (type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeCalculated; - const target = (type as TypeReference).target as InterfaceType; - if (getObjectFlags(target) & ObjectFlags.Class) { - const baseTypeNode = getBaseTypeNodeOfClass(target); - // A base type expression may circularly reference the class itself (e.g. as an argument to function call), so we only - // check for base types specified as simple qualified names. - if (baseTypeNode && baseTypeNode.expression.kind !== SyntaxKind.Identifier && baseTypeNode.expression.kind !== SyntaxKind.PropertyAccessExpression) { - return undefined; - } - } - const bases = getBaseTypes(target); - if (bases.length !== 1) { - return undefined; - } - if (getMembersOfSymbol(type.symbol).size) { - return undefined; // If the interface has any members, they may subtype members in the base, so we should do a full structural comparison - } - let instantiatedBase = !length(target.typeParameters) ? bases[0] : instantiateType(bases[0], createTypeMapper(target.typeParameters!, getTypeArguments(type as TypeReference).slice(0, target.typeParameters!.length))); - if (length(getTypeArguments(type as TypeReference)) > length(target.typeParameters)) { - instantiatedBase = getTypeWithThisArgument(instantiatedBase, last(getTypeArguments(type as TypeReference))); - } - (type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeExists; - return (type as TypeReference).cachedEquivalentBaseType = instantiatedBase; } - - function isEmptyLiteralType(type: Type): boolean { - return strictNullChecks ? type === implicitNeverType : type === undefinedWideningType; + const bases = getBaseTypes(target); + if (bases.length !== 1) { + return undefined; } - - function isEmptyArrayLiteralType(type: Type): boolean { - const elementType = getElementTypeOfArrayType(type); - return !!elementType && isEmptyLiteralType(elementType); + if (getMembersOfSymbol(type.symbol).size) { + return undefined; // If the interface has any members, they may subtype members in the base, so we should do a full structural comparison } - - function isTupleLikeType(type: Type): boolean { - return isTupleType(type) || !!getPropertyOfType(type, "0" as __String); + let instantiatedBase = !length(target.typeParameters) ? bases[0] : instantiateType(bases[0], createTypeMapper(target.typeParameters!, getTypeArguments(type as TypeReference).slice(0, target.typeParameters!.length))); + if (length(getTypeArguments(type as TypeReference)) > length(target.typeParameters)) { + instantiatedBase = getTypeWithThisArgument(instantiatedBase, last(getTypeArguments(type as TypeReference))); } + (type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeExists; + return (type as TypeReference).cachedEquivalentBaseType = instantiatedBase; + } - function isArrayOrTupleLikeType(type: Type): boolean { - return isArrayLikeType(type) || isTupleLikeType(type); - } + function isEmptyLiteralType(type: Type): boolean { + return strictNullChecks ? type === implicitNeverType : type === undefinedWideningType; + } - function getTupleElementType(type: Type, index: number) { - const propType = getTypeOfPropertyOfType(type, "" + index as __String); - if (propType) { - return propType; - } - if (everyType(type, isTupleType)) { - return mapType(type, t => getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType); - } - return undefined; - } + function isEmptyArrayLiteralType(type: Type): boolean { + const elementType = getElementTypeOfArrayType(type); + return !!elementType && isEmptyLiteralType(elementType); + } - function isNeitherUnitTypeNorNever(type: Type): boolean { - return !(type.flags & (TypeFlags.Unit | TypeFlags.Never)); - } + function isTupleLikeType(type: Type): boolean { + return isTupleType(type) || !!getPropertyOfType(type, "0" as __String); + } - function isUnitType(type: Type): boolean { - return !!(type.flags & TypeFlags.Unit); - } + function isArrayOrTupleLikeType(type: Type): boolean { + return isArrayLikeType(type) || isTupleLikeType(type); + } - function isUnitLikeType(type: Type): boolean { - return type.flags & TypeFlags.Intersection ? some((type as IntersectionType).types, isUnitType) : - !!(type.flags & TypeFlags.Unit); + function getTupleElementType(type: Type, index: number) { + const propType = getTypeOfPropertyOfType(type, "" + index as __String); + if (propType) { + return propType; } - - function extractUnitType(type: Type) { - return type.flags & TypeFlags.Intersection ? find((type as IntersectionType).types, isUnitType) || type : type; + if (everyType(type, isTupleType)) { + return mapType(type, t => getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType); } + return undefined; + } - function isLiteralType(type: Type): boolean { - return type.flags & TypeFlags.Boolean ? true : - type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : every((type as UnionType).types, isUnitType) : - isUnitType(type); - } + function isNeitherUnitTypeNorNever(type: Type): boolean { + return !(type.flags & (TypeFlags.Unit | TypeFlags.Never)); + } - function getBaseTypeOfLiteralType(type: Type): Type { - return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(type as LiteralType) : - type.flags & TypeFlags.StringLiteral ? stringType : - type.flags & TypeFlags.NumberLiteral ? numberType : - type.flags & TypeFlags.BigIntLiteral ? bigintType : - type.flags & TypeFlags.BooleanLiteral ? booleanType : - type.flags & TypeFlags.Union ? mapType(type as UnionType, getBaseTypeOfLiteralType) : - type; - } + function isUnitType(type: Type): boolean { + return !!(type.flags & TypeFlags.Unit); + } - function getWidenedLiteralType(type: Type): Type { - return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(type as LiteralType) : - type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType : - type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType : - type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType : - type.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(type) ? booleanType : - type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedLiteralType) : - type; - } + function isUnitLikeType(type: Type): boolean { + return type.flags & TypeFlags.Intersection ? some((type as IntersectionType).types, isUnitType) : + !!(type.flags & TypeFlags.Unit); + } - function getWidenedUniqueESSymbolType(type: Type): Type { - return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : - type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedUniqueESSymbolType) : - type; - } + function extractUnitType(type: Type) { + return type.flags & TypeFlags.Intersection ? find((type as IntersectionType).types, isUnitType) || type : type; + } - function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) { - if (!isLiteralOfContextualType(type, contextualType)) { - type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); - } - return type; - } + function isLiteralType(type: Type): boolean { + return type.flags & TypeFlags.Boolean ? true : + type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : every((type as UnionType).types, isUnitType) : + isUnitType(type); + } - function getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, isAsync: boolean) { - if (type && isUnitType(type)) { - const contextualType = !contextualSignatureReturnType ? undefined : - isAsync ? getPromisedTypeOfPromise(contextualSignatureReturnType) : - contextualSignatureReturnType; - type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); - } - return type; - } + function getBaseTypeOfLiteralType(type: Type): Type { + return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(type as LiteralType) : + type.flags & TypeFlags.StringLiteral ? stringType : + type.flags & TypeFlags.NumberLiteral ? numberType : + type.flags & TypeFlags.BigIntLiteral ? bigintType : + type.flags & TypeFlags.BooleanLiteral ? booleanType : + type.flags & TypeFlags.Union ? mapType(type as UnionType, getBaseTypeOfLiteralType) : + type; + } - function getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, kind: IterationTypeKind, isAsyncGenerator: boolean) { - if (type && isUnitType(type)) { - const contextualType = !contextualSignatureReturnType ? undefined : - getIterationTypeOfGeneratorFunctionReturnType(kind, contextualSignatureReturnType, isAsyncGenerator); - type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); - } - return type; - } + function getWidenedLiteralType(type: Type): Type { + return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(type as LiteralType) : + type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType : + type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType : + type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType : + type.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(type) ? booleanType : + type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedLiteralType) : + type; + } - /** - * Check if a Type was written as a tuple type literal. - * Prefer using isTupleLikeType() unless the use of `elementTypes`/`getTypeArguments` is required. - */ - function isTupleType(type: Type): type is TupleTypeReference { - return !!(getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target.objectFlags & ObjectFlags.Tuple); - } + function getWidenedUniqueESSymbolType(type: Type): Type { + return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : + type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedUniqueESSymbolType) : + type; + } - function isGenericTupleType(type: Type): type is TupleTypeReference { - return isTupleType(type) && !!(type.target.combinedFlags & ElementFlags.Variadic); + function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) { + if (!isLiteralOfContextualType(type, contextualType)) { + type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); } + return type; + } - function isSingleElementGenericTupleType(type: Type): type is TupleTypeReference { - return isGenericTupleType(type) && type.target.elementFlags.length === 1; + function getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, isAsync: boolean) { + if (type && isUnitType(type)) { + const contextualType = !contextualSignatureReturnType ? undefined : + isAsync ? getPromisedTypeOfPromise(contextualSignatureReturnType) : + contextualSignatureReturnType; + type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); } + return type; + } - function getRestTypeOfTupleType(type: TupleTypeReference) { - return getElementTypeOfSliceOfTupleType(type, type.target.fixedLength); + function getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, kind: IterationTypeKind, isAsyncGenerator: boolean) { + if (type && isUnitType(type)) { + const contextualType = !contextualSignatureReturnType ? undefined : + getIterationTypeOfGeneratorFunctionReturnType(kind, contextualSignatureReturnType, isAsyncGenerator); + type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); } + return type; + } - function getRestArrayTypeOfTupleType(type: TupleTypeReference) { - const restType = getRestTypeOfTupleType(type); - return restType && createArrayType(restType); - } + /** + * Check if a Type was written as a tuple type literal. + * Prefer using isTupleLikeType() unless the use of `elementTypes`/`getTypeArguments` is required. + */ + function isTupleType(type: Type): type is TupleTypeReference { + return !!(getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target.objectFlags & ObjectFlags.Tuple); + } - function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false) { - const length = getTypeReferenceArity(type) - endSkipCount; - if (index < length) { - const typeArguments = getTypeArguments(type); - const elementTypes: Type[] = []; - for (let i = index; i < length; i++) { - const t = typeArguments[i]; - elementTypes.push(type.target.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t); - } - return writing ? getIntersectionType(elementTypes) : getUnionType(elementTypes); - } - return undefined; - } + function isGenericTupleType(type: Type): type is TupleTypeReference { + return isTupleType(type) && !!(type.target.combinedFlags & ElementFlags.Variadic); + } - function isTupleTypeStructureMatching(t1: TupleTypeReference, t2: TupleTypeReference) { - return getTypeReferenceArity(t1) === getTypeReferenceArity(t2) && - every(t1.target.elementFlags, (f, i) => (f & ElementFlags.Variable) === (t2.target.elementFlags[i] & ElementFlags.Variable)); - } + function isSingleElementGenericTupleType(type: Type): type is TupleTypeReference { + return isGenericTupleType(type) && type.target.elementFlags.length === 1; + } - function isZeroBigInt({value}: BigIntLiteralType) { - return value.base10Value === "0"; - } + function getRestTypeOfTupleType(type: TupleTypeReference) { + return getElementTypeOfSliceOfTupleType(type, type.target.fixedLength); + } - function getFalsyFlagsOfTypes(types: Type[]): TypeFlags { - let result: TypeFlags = 0; - for (const t of types) { - result |= getFalsyFlags(t); + function getRestArrayTypeOfTupleType(type: TupleTypeReference) { + const restType = getRestTypeOfTupleType(type); + return restType && createArrayType(restType); + } + + function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false) { + const length = getTypeReferenceArity(type) - endSkipCount; + if (index < length) { + const typeArguments = getTypeArguments(type); + const elementTypes: Type[] = []; + for (let i = index; i < length; i++) { + const t = typeArguments[i]; + elementTypes.push(type.target.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t); } - return result; + return writing ? getIntersectionType(elementTypes) : getUnionType(elementTypes); } + return undefined; + } - // Returns the String, Number, Boolean, StringLiteral, NumberLiteral, BooleanLiteral, Void, Undefined, or Null - // flags for the string, number, boolean, "", 0, false, void, undefined, or null types respectively. Returns - // no flags for all other types (including non-falsy literal types). - function getFalsyFlags(type: Type): TypeFlags { - return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((type as UnionType).types) : - type.flags & TypeFlags.StringLiteral ? (type as StringLiteralType).value === "" ? TypeFlags.StringLiteral : 0 : - type.flags & TypeFlags.NumberLiteral ? (type as NumberLiteralType).value === 0 ? TypeFlags.NumberLiteral : 0 : - type.flags & TypeFlags.BigIntLiteral ? isZeroBigInt(type as BigIntLiteralType) ? TypeFlags.BigIntLiteral : 0 : - type.flags & TypeFlags.BooleanLiteral ? (type === falseType || type === regularFalseType) ? TypeFlags.BooleanLiteral : 0 : - type.flags & TypeFlags.PossiblyFalsy; - } + function isTupleTypeStructureMatching(t1: TupleTypeReference, t2: TupleTypeReference) { + return getTypeReferenceArity(t1) === getTypeReferenceArity(t2) && + every(t1.target.elementFlags, (f, i) => (f & ElementFlags.Variable) === (t2.target.elementFlags[i] & ElementFlags.Variable)); + } - function removeDefinitelyFalsyTypes(type: Type): Type { - return getFalsyFlags(type) & TypeFlags.DefinitelyFalsy ? - filterType(type, t => !(getFalsyFlags(t) & TypeFlags.DefinitelyFalsy)) : - type; - } + function isZeroBigInt({value}: BigIntLiteralType) { + return value.base10Value === "0"; + } - function extractDefinitelyFalsyTypes(type: Type): Type { - return mapType(type, getDefinitelyFalsyPartOfType); + function getFalsyFlagsOfTypes(types: Type[]): TypeFlags { + let result: TypeFlags = 0; + for (const t of types) { + result |= getFalsyFlags(t); } + return result; + } - function getDefinitelyFalsyPartOfType(type: Type): Type { - return type.flags & TypeFlags.String ? emptyStringType : - type.flags & TypeFlags.Number ? zeroType : - type.flags & TypeFlags.BigInt ? zeroBigIntType : - type === regularFalseType || - type === falseType || - type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null | TypeFlags.AnyOrUnknown) || - type.flags & TypeFlags.StringLiteral && (type as StringLiteralType).value === "" || - type.flags & TypeFlags.NumberLiteral && (type as NumberLiteralType).value === 0 || - type.flags & TypeFlags.BigIntLiteral && isZeroBigInt(type as BigIntLiteralType) ? type : - neverType; - } + // Returns the String, Number, Boolean, StringLiteral, NumberLiteral, BooleanLiteral, Void, Undefined, or Null + // flags for the string, number, boolean, "", 0, false, void, undefined, or null types respectively. Returns + // no flags for all other types (including non-falsy literal types). + function getFalsyFlags(type: Type): TypeFlags { + return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((type as UnionType).types) : + type.flags & TypeFlags.StringLiteral ? (type as StringLiteralType).value === "" ? TypeFlags.StringLiteral : 0 : + type.flags & TypeFlags.NumberLiteral ? (type as NumberLiteralType).value === 0 ? TypeFlags.NumberLiteral : 0 : + type.flags & TypeFlags.BigIntLiteral ? isZeroBigInt(type as BigIntLiteralType) ? TypeFlags.BigIntLiteral : 0 : + type.flags & TypeFlags.BooleanLiteral ? (type === falseType || type === regularFalseType) ? TypeFlags.BooleanLiteral : 0 : + type.flags & TypeFlags.PossiblyFalsy; + } - /** - * Add undefined or null or both to a type if they are missing. - * @param type - type to add undefined and/or null to if not present - * @param flags - Either TypeFlags.Undefined or TypeFlags.Null, or both - */ - function getNullableType(type: Type, flags: TypeFlags): Type { - const missing = (flags & ~type.flags) & (TypeFlags.Undefined | TypeFlags.Null); - return missing === 0 ? type : - missing === TypeFlags.Undefined ? getUnionType([type, undefinedType]) : - missing === TypeFlags.Null ? getUnionType([type, nullType]) : - getUnionType([type, undefinedType, nullType]); - } + function removeDefinitelyFalsyTypes(type: Type): Type { + return getFalsyFlags(type) & TypeFlags.DefinitelyFalsy ? + filterType(type, t => !(getFalsyFlags(t) & TypeFlags.DefinitelyFalsy)) : + type; + } - function getOptionalType(type: Type, isProperty = false): Type { - Debug.assert(strictNullChecks); - return type.flags & TypeFlags.Undefined ? type : getUnionType([type, isProperty ? missingType : undefinedType]); - } + function extractDefinitelyFalsyTypes(type: Type): Type { + return mapType(type, getDefinitelyFalsyPartOfType); + } - function getGlobalNonNullableTypeInstantiation(type: Type) { - // First reduce away any constituents that are assignable to 'undefined' or 'null'. This not only eliminates - // 'undefined' and 'null', but also higher-order types such as a type parameter 'U extends undefined | null' - // that isn't eliminated by a NonNullable instantiation. - const reducedType = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); - if (!deferredGlobalNonNullableTypeAlias) { - deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol; - } - // If the NonNullable type is available, return an instantiation. Otherwise just return the reduced type. - return deferredGlobalNonNullableTypeAlias !== unknownSymbol ? - getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [reducedType]) : - reducedType; - } + function getDefinitelyFalsyPartOfType(type: Type): Type { + return type.flags & TypeFlags.String ? emptyStringType : + type.flags & TypeFlags.Number ? zeroType : + type.flags & TypeFlags.BigInt ? zeroBigIntType : + type === regularFalseType || + type === falseType || + type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null | TypeFlags.AnyOrUnknown) || + type.flags & TypeFlags.StringLiteral && (type as StringLiteralType).value === "" || + type.flags & TypeFlags.NumberLiteral && (type as NumberLiteralType).value === 0 || + type.flags & TypeFlags.BigIntLiteral && isZeroBigInt(type as BigIntLiteralType) ? type : + neverType; + } - function getNonNullableType(type: Type): Type { - return strictNullChecks ? getGlobalNonNullableTypeInstantiation(type) : type; - } + /** + * Add undefined or null or both to a type if they are missing. + * @param type - type to add undefined and/or null to if not present + * @param flags - Either TypeFlags.Undefined or TypeFlags.Null, or both + */ + function getNullableType(type: Type, flags: TypeFlags): Type { + const missing = (flags & ~type.flags) & (TypeFlags.Undefined | TypeFlags.Null); + return missing === 0 ? type : + missing === TypeFlags.Undefined ? getUnionType([type, undefinedType]) : + missing === TypeFlags.Null ? getUnionType([type, nullType]) : + getUnionType([type, undefinedType, nullType]); + } - function addOptionalTypeMarker(type: Type) { - return strictNullChecks ? getUnionType([type, optionalType]) : type; - } + function getOptionalType(type: Type, isProperty = false): Type { + Debug.assert(strictNullChecks); + return type.flags & TypeFlags.Undefined ? type : getUnionType([type, isProperty ? missingType : undefinedType]); + } - function removeOptionalTypeMarker(type: Type): Type { - return strictNullChecks ? removeType(type, optionalType) : type; - } + function getGlobalNonNullableTypeInstantiation(type: Type) { + // First reduce away any constituents that are assignable to 'undefined' or 'null'. This not only eliminates + // 'undefined' and 'null', but also higher-order types such as a type parameter 'U extends undefined | null' + // that isn't eliminated by a NonNullable instantiation. + const reducedType = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + if (!deferredGlobalNonNullableTypeAlias) { + deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol; + } + // If the NonNullable type is available, return an instantiation. Otherwise just return the reduced type. + return deferredGlobalNonNullableTypeAlias !== unknownSymbol ? + getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [reducedType]) : + reducedType; + } - function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) { - return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type; - } + function getNonNullableType(type: Type): Type { + return strictNullChecks ? getGlobalNonNullableTypeInstantiation(type) : type; + } - function getOptionalExpressionType(exprType: Type, expression: Expression) { - return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) : - isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) : - exprType; - } + function addOptionalTypeMarker(type: Type) { + return strictNullChecks ? getUnionType([type, optionalType]) : type; + } - function removeMissingType(type: Type, isOptional: boolean) { - return exactOptionalPropertyTypes && isOptional ? removeType(type, missingType) : type; - } + function removeOptionalTypeMarker(type: Type): Type { + return strictNullChecks ? removeType(type, optionalType) : type; + } - function containsMissingType(type: Type) { - return exactOptionalPropertyTypes && (type === missingType || type.flags & TypeFlags.Union && containsType((type as UnionType).types, missingType)); - } + function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) { + return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type; + } - function removeMissingOrUndefinedType(type: Type): Type { - return exactOptionalPropertyTypes ? removeType(type, missingType) : getTypeWithFacts(type, TypeFacts.NEUndefined); - } + function getOptionalExpressionType(exprType: Type, expression: Expression) { + return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) : + isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) : + exprType; + } - /** - * Is source potentially coercible to target type under `==`. - * Assumes that `source` is a constituent of a union, hence - * the boolean literal flag on the LHS, but not on the RHS. - * - * This does not fully replicate the semantics of `==`. The - * intention is to catch cases that are clearly not right. - * - * Comparing (string | number) to number should not remove the - * string element. - * - * Comparing (string | number) to 1 will remove the string - * element, though this is not sound. This is a pragmatic - * choice. - * - * @see narrowTypeByEquality - * - * @param source - * @param target - */ - function isCoercibleUnderDoubleEquals(source: Type, target: Type): boolean { - return ((source.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.BooleanLiteral)) !== 0) - && ((target.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.Boolean)) !== 0); - } + function removeMissingType(type: Type, isOptional: boolean) { + return exactOptionalPropertyTypes && isOptional ? removeType(type, missingType) : type; + } - /** - * Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module - * with no call or construct signatures. - */ - function isObjectTypeWithInferableIndex(type: Type): boolean { - return type.flags & TypeFlags.Intersection ? every((type as IntersectionType).types, isObjectTypeWithInferableIndex) : - !!(type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 && - !typeHasCallOrConstructSignatures(type)) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source)); - } + function containsMissingType(type: Type) { + return exactOptionalPropertyTypes && (type === missingType || type.flags & TypeFlags.Union && containsType((type as UnionType).types, missingType)); + } - function createSymbolWithType(source: Symbol, type: Type | undefined) { - const symbol = createSymbol(source.flags, source.escapedName, getCheckFlags(source) & CheckFlags.Readonly); - symbol.declarations = source.declarations; - symbol.parent = source.parent; - symbol.type = type; - symbol.target = source; - if (source.valueDeclaration) { - symbol.valueDeclaration = source.valueDeclaration; - } - const nameType = getSymbolLinks(source).nameType; - if (nameType) { - symbol.nameType = nameType; - } - return symbol; - } + function removeMissingOrUndefinedType(type: Type): Type { + return exactOptionalPropertyTypes ? removeType(type, missingType) : getTypeWithFacts(type, TypeFacts.NEUndefined); + } - function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { - const members = createSymbolTable(); - for (const property of getPropertiesOfObjectType(type)) { - const original = getTypeOfSymbol(property); - const updated = f(original); - members.set(property.escapedName, updated === original ? property : createSymbolWithType(property, updated)); - } - return members; - } + /** + * Is source potentially coercible to target type under `==`. + * Assumes that `source` is a constituent of a union, hence + * the boolean literal flag on the LHS, but not on the RHS. + * + * This does not fully replicate the semantics of `==`. The + * intention is to catch cases that are clearly not right. + * + * Comparing (string | number) to number should not remove the + * string element. + * + * Comparing (string | number) to 1 will remove the string + * element, though this is not sound. This is a pragmatic + * choice. + * + * @see narrowTypeByEquality + * + * @param source + * @param target + */ + function isCoercibleUnderDoubleEquals(source: Type, target: Type): boolean { + return ((source.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.BooleanLiteral)) !== 0) + && ((target.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.Boolean)) !== 0); + } - /** - * If the the provided object literal is subject to the excess properties check, - * create a new that is exempt. Recursively mark object literal members as exempt. - * Leave signatures alone since they are not subject to the check. - */ - function getRegularTypeOfObjectLiteral(type: Type): Type { - if (!(isObjectLiteralType(type) && getObjectFlags(type) & ObjectFlags.FreshLiteral)) { - return type; - } - const regularType = (type as FreshObjectLiteralType).regularType; - if (regularType) { - return regularType; - } + /** + * Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module + * with no call or construct signatures. + */ + function isObjectTypeWithInferableIndex(type: Type): boolean { + return type.flags & TypeFlags.Intersection ? every((type as IntersectionType).types, isObjectTypeWithInferableIndex) : + !!(type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 && + !typeHasCallOrConstructSignatures(type)) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source)); + } - const resolved = type as ResolvedType; - const members = transformTypeOfMembers(type, getRegularTypeOfObjectLiteral); - const regularNew = createAnonymousType(resolved.symbol, members, resolved.callSignatures, resolved.constructSignatures, resolved.indexInfos); - regularNew.flags = resolved.flags; - regularNew.objectFlags |= resolved.objectFlags & ~ObjectFlags.FreshLiteral; - (type as FreshObjectLiteralType).regularType = regularNew; - return regularNew; + function createSymbolWithType(source: Symbol, type: Type | undefined) { + const symbol = createSymbol(source.flags, source.escapedName, getCheckFlags(source) & CheckFlags.Readonly); + symbol.declarations = source.declarations; + symbol.parent = source.parent; + symbol.type = type; + symbol.target = source; + if (source.valueDeclaration) { + symbol.valueDeclaration = source.valueDeclaration; + } + const nameType = getSymbolLinks(source).nameType; + if (nameType) { + symbol.nameType = nameType; } + return symbol; + } - function createWideningContext(parent: WideningContext | undefined, propertyName: __String | undefined, siblings: Type[] | undefined): WideningContext { - return { parent, propertyName, siblings, resolvedProperties: undefined }; + function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { + const members = createSymbolTable(); + for (const property of getPropertiesOfObjectType(type)) { + const original = getTypeOfSymbol(property); + const updated = f(original); + members.set(property.escapedName, updated === original ? property : createSymbolWithType(property, updated)); } + return members; + } - function getSiblingsOfContext(context: WideningContext): Type[] { - if (!context.siblings) { - const siblings: Type[] = []; - for (const type of getSiblingsOfContext(context.parent!)) { - if (isObjectLiteralType(type)) { - const prop = getPropertyOfObjectType(type, context.propertyName!); - if (prop) { - forEachType(getTypeOfSymbol(prop), t => { - siblings.push(t); - }); - } + /** + * If the the provided object literal is subject to the excess properties check, + * create a new that is exempt. Recursively mark object literal members as exempt. + * Leave signatures alone since they are not subject to the check. + */ + function getRegularTypeOfObjectLiteral(type: Type): Type { + if (!(isObjectLiteralType(type) && getObjectFlags(type) & ObjectFlags.FreshLiteral)) { + return type; + } + const regularType = (type as FreshObjectLiteralType).regularType; + if (regularType) { + return regularType; + } + + const resolved = type as ResolvedType; + const members = transformTypeOfMembers(type, getRegularTypeOfObjectLiteral); + const regularNew = createAnonymousType(resolved.symbol, members, resolved.callSignatures, resolved.constructSignatures, resolved.indexInfos); + regularNew.flags = resolved.flags; + regularNew.objectFlags |= resolved.objectFlags & ~ObjectFlags.FreshLiteral; + (type as FreshObjectLiteralType).regularType = regularNew; + return regularNew; + } + + function createWideningContext(parent: WideningContext | undefined, propertyName: __String | undefined, siblings: Type[] | undefined): WideningContext { + return { parent, propertyName, siblings, resolvedProperties: undefined }; + } + + function getSiblingsOfContext(context: WideningContext): Type[] { + if (!context.siblings) { + const siblings: Type[] = []; + for (const type of getSiblingsOfContext(context.parent!)) { + if (isObjectLiteralType(type)) { + const prop = getPropertyOfObjectType(type, context.propertyName!); + if (prop) { + forEachType(getTypeOfSymbol(prop), t => { + siblings.push(t); + }); } } - context.siblings = siblings; } - return context.siblings; + context.siblings = siblings; } + return context.siblings; + } - function getPropertiesOfContext(context: WideningContext): Symbol[] { - if (!context.resolvedProperties) { - const names = new Map() as UnderscoreEscapedMap; - for (const t of getSiblingsOfContext(context)) { - if (isObjectLiteralType(t) && !(getObjectFlags(t) & ObjectFlags.ContainsSpread)) { - for (const prop of getPropertiesOfType(t)) { - names.set(prop.escapedName, prop); - } + function getPropertiesOfContext(context: WideningContext): Symbol[] { + if (!context.resolvedProperties) { + const names = new Map() as UnderscoreEscapedMap; + for (const t of getSiblingsOfContext(context)) { + if (isObjectLiteralType(t) && !(getObjectFlags(t) & ObjectFlags.ContainsSpread)) { + for (const prop of getPropertiesOfType(t)) { + names.set(prop.escapedName, prop); } } - context.resolvedProperties = arrayFrom(names.values()); } - return context.resolvedProperties; + context.resolvedProperties = arrayFrom(names.values()); } + return context.resolvedProperties; + } - function getWidenedProperty(prop: Symbol, context: WideningContext | undefined): Symbol { - if (!(prop.flags & SymbolFlags.Property)) { - // Since get accessors already widen their return value there is no need to - // widen accessor based properties here. - return prop; - } - const original = getTypeOfSymbol(prop); - const propContext = context && createWideningContext(context, prop.escapedName, /*siblings*/ undefined); - const widened = getWidenedTypeWithContext(original, propContext); - return widened === original ? prop : createSymbolWithType(prop, widened); + function getWidenedProperty(prop: Symbol, context: WideningContext | undefined): Symbol { + if (!(prop.flags & SymbolFlags.Property)) { + // Since get accessors already widen their return value there is no need to + // widen accessor based properties here. + return prop; } + const original = getTypeOfSymbol(prop); + const propContext = context && createWideningContext(context, prop.escapedName, /*siblings*/ undefined); + const widened = getWidenedTypeWithContext(original, propContext); + return widened === original ? prop : createSymbolWithType(prop, widened); + } - function getUndefinedProperty(prop: Symbol) { - const cached = undefinedProperties.get(prop.escapedName); - if (cached) { - return cached; - } - const result = createSymbolWithType(prop, missingType); - result.flags |= SymbolFlags.Optional; - undefinedProperties.set(prop.escapedName, result); - return result; + function getUndefinedProperty(prop: Symbol) { + const cached = undefinedProperties.get(prop.escapedName); + if (cached) { + return cached; } + const result = createSymbolWithType(prop, missingType); + result.flags |= SymbolFlags.Optional; + undefinedProperties.set(prop.escapedName, result); + return result; + } - function getWidenedTypeOfObjectLiteral(type: Type, context: WideningContext | undefined): Type { - const members = createSymbolTable(); - for (const prop of getPropertiesOfObjectType(type)) { - members.set(prop.escapedName, getWidenedProperty(prop, context)); - } - if (context) { - for (const prop of getPropertiesOfContext(context)) { - if (!members.has(prop.escapedName)) { - members.set(prop.escapedName, getUndefinedProperty(prop)); - } + function getWidenedTypeOfObjectLiteral(type: Type, context: WideningContext | undefined): Type { + const members = createSymbolTable(); + for (const prop of getPropertiesOfObjectType(type)) { + members.set(prop.escapedName, getWidenedProperty(prop, context)); + } + if (context) { + for (const prop of getPropertiesOfContext(context)) { + if (!members.has(prop.escapedName)) { + members.set(prop.escapedName, getUndefinedProperty(prop)); } } - const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, - sameMap(getIndexInfosOfType(type), info => createIndexInfo(info.keyType, getWidenedType(info.type), info.isReadonly))); - result.objectFlags |= (getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.NonInferrableType)); // Retain js literal flag through widening - return result; } + const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, + sameMap(getIndexInfosOfType(type), info => createIndexInfo(info.keyType, getWidenedType(info.type), info.isReadonly))); + result.objectFlags |= (getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.NonInferrableType)); // Retain js literal flag through widening + return result; + } - function getWidenedType(type: Type) { - return getWidenedTypeWithContext(type, /*context*/ undefined); - } + function getWidenedType(type: Type) { + return getWidenedTypeWithContext(type, /*context*/ undefined); + } - function getWidenedTypeWithContext(type: Type, context: WideningContext | undefined): Type { - if (getObjectFlags(type) & ObjectFlags.RequiresWidening) { - if (context === undefined && type.widened) { - return type.widened; - } - let result: Type | undefined; - if (type.flags & (TypeFlags.Any | TypeFlags.Nullable)) { - result = anyType; - } - else if (isObjectLiteralType(type)) { - result = getWidenedTypeOfObjectLiteral(type, context); - } - else if (type.flags & TypeFlags.Union) { - const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type as UnionType).types); - const widenedTypes = sameMap((type as UnionType).types, t => t.flags & TypeFlags.Nullable ? t : getWidenedTypeWithContext(t, unionContext)); - // Widening an empty object literal transitions from a highly restrictive type to - // a highly inclusive one. For that reason we perform subtype reduction here if the - // union includes empty object types (e.g. reducing {} | string to just {}). - result = getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal); - } - else if (type.flags & TypeFlags.Intersection) { - result = getIntersectionType(sameMap((type as IntersectionType).types, getWidenedType)); - } - else if (isArrayType(type) || isTupleType(type)) { - result = createTypeReference(type.target, sameMap(getTypeArguments(type), getWidenedType)); - } - if (result && context === undefined) { - type.widened = result; - } - return result || type; + function getWidenedTypeWithContext(type: Type, context: WideningContext | undefined): Type { + if (getObjectFlags(type) & ObjectFlags.RequiresWidening) { + if (context === undefined && type.widened) { + return type.widened; } - return type; + let result: Type | undefined; + if (type.flags & (TypeFlags.Any | TypeFlags.Nullable)) { + result = anyType; + } + else if (isObjectLiteralType(type)) { + result = getWidenedTypeOfObjectLiteral(type, context); + } + else if (type.flags & TypeFlags.Union) { + const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type as UnionType).types); + const widenedTypes = sameMap((type as UnionType).types, t => t.flags & TypeFlags.Nullable ? t : getWidenedTypeWithContext(t, unionContext)); + // Widening an empty object literal transitions from a highly restrictive type to + // a highly inclusive one. For that reason we perform subtype reduction here if the + // union includes empty object types (e.g. reducing {} | string to just {}). + result = getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal); + } + else if (type.flags & TypeFlags.Intersection) { + result = getIntersectionType(sameMap((type as IntersectionType).types, getWidenedType)); + } + else if (isArrayType(type) || isTupleType(type)) { + result = createTypeReference(type.target, sameMap(getTypeArguments(type), getWidenedType)); + } + if (result && context === undefined) { + type.widened = result; + } + return result || type; } + return type; + } - /** - * Reports implicit any errors that occur as a result of widening 'null' and 'undefined' - * to 'any'. A call to reportWideningErrorsInType is normally accompanied by a call to - * getWidenedType. But in some cases getWidenedType is called without reporting errors - * (type argument inference is an example). - * - * The return value indicates whether an error was in fact reported. The particular circumstances - * are on a best effort basis. Currently, if the null or undefined that causes widening is inside - * an object literal property (arbitrarily deeply), this function reports an error. If no error is - * reported, reportImplicitAnyError is a suitable fallback to report a general error. - */ - function reportWideningErrorsInType(type: Type): boolean { - let errorReported = false; - if (getObjectFlags(type) & ObjectFlags.ContainsWideningType) { - if (type.flags & TypeFlags.Union) { - if (some((type as UnionType).types, isEmptyObjectType)) { - errorReported = true; - } - else { - for (const t of (type as UnionType).types) { - if (reportWideningErrorsInType(t)) { - errorReported = true; - } - } - } + /** + * Reports implicit any errors that occur as a result of widening 'null' and 'undefined' + * to 'any'. A call to reportWideningErrorsInType is normally accompanied by a call to + * getWidenedType. But in some cases getWidenedType is called without reporting errors + * (type argument inference is an example). + * + * The return value indicates whether an error was in fact reported. The particular circumstances + * are on a best effort basis. Currently, if the null or undefined that causes widening is inside + * an object literal property (arbitrarily deeply), this function reports an error. If no error is + * reported, reportImplicitAnyError is a suitable fallback to report a general error. + */ + function reportWideningErrorsInType(type: Type): boolean { + let errorReported = false; + if (getObjectFlags(type) & ObjectFlags.ContainsWideningType) { + if (type.flags & TypeFlags.Union) { + if (some((type as UnionType).types, isEmptyObjectType)) { + errorReported = true; } - if (isArrayType(type) || isTupleType(type)) { - for (const t of getTypeArguments(type)) { + else { + for (const t of (type as UnionType).types) { if (reportWideningErrorsInType(t)) { errorReported = true; } } } - if (isObjectLiteralType(type)) { - for (const p of getPropertiesOfObjectType(type)) { - const t = getTypeOfSymbol(p); - if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) { - if (!reportWideningErrorsInType(t)) { - error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t))); - } - errorReported = true; + } + if (isArrayType(type) || isTupleType(type)) { + for (const t of getTypeArguments(type)) { + if (reportWideningErrorsInType(t)) { + errorReported = true; + } + } + } + if (isObjectLiteralType(type)) { + for (const p of getPropertiesOfObjectType(type)) { + const t = getTypeOfSymbol(p); + if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) { + if (!reportWideningErrorsInType(t)) { + error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t))); } + errorReported = true; } } } - return errorReported; } + return errorReported; + } - function reportImplicitAny(declaration: Declaration, type: Type, wideningKind?: WideningKind) { - const typeAsString = typeToString(getWidenedType(type)); - if (isInJSFile(declaration) && !isCheckJsEnabledForFile(getSourceFileOfNode(declaration), compilerOptions)) { - // Only report implicit any errors/suggestions in TS and ts-check JS files - return; - } - let diagnostic: DiagnosticMessage; - switch (declaration.kind) { - case SyntaxKind.BinaryExpression: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - diagnostic = noImplicitAny ? Diagnostics.Member_0_implicitly_has_an_1_type : Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; - break; - case SyntaxKind.Parameter: - const param = declaration as ParameterDeclaration; - if (isIdentifier(param.name) && - (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) && - param.parent.parameters.indexOf(param) > -1 && - (resolveName(param, param.name.escapedText, SymbolFlags.Type, undefined, param.name.escapedText, /*isUse*/ true) || - param.name.originalKeywordKind && isTypeNodeKind(param.name.originalKeywordKind))) { - const newName = "arg" + param.parent.parameters.indexOf(param); - const typeName = declarationNameToString(param.name) + (param.dotDotDotToken ? "[]" : ""); - errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, typeName); - return; - } - diagnostic = (declaration as ParameterDeclaration).dotDotDotToken ? - noImplicitAny ? Diagnostics.Rest_parameter_0_implicitly_has_an_any_type : Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage : - noImplicitAny ? Diagnostics.Parameter_0_implicitly_has_an_1_type : Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; - break; - case SyntaxKind.BindingElement: - diagnostic = Diagnostics.Binding_element_0_implicitly_has_an_1_type; - if (!noImplicitAny) { - // Don't issue a suggestion for binding elements since the codefix doesn't yet support them. - return; - } - break; - case SyntaxKind.JSDocFunctionType: - error(declaration, Diagnostics.Function_type_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); + function reportImplicitAny(declaration: Declaration, type: Type, wideningKind?: WideningKind) { + const typeAsString = typeToString(getWidenedType(type)); + if (isInJSFile(declaration) && !isCheckJsEnabledForFile(getSourceFileOfNode(declaration), compilerOptions)) { + // Only report implicit any errors/suggestions in TS and ts-check JS files + return; + } + let diagnostic: DiagnosticMessage; + switch (declaration.kind) { + case SyntaxKind.BinaryExpression: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + diagnostic = noImplicitAny ? Diagnostics.Member_0_implicitly_has_an_1_type : Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; + break; + case SyntaxKind.Parameter: + const param = declaration as ParameterDeclaration; + if (isIdentifier(param.name) && + (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) && + param.parent.parameters.indexOf(param) > -1 && + (resolveName(param, param.name.escapedText, SymbolFlags.Type, undefined, param.name.escapedText, /*isUse*/ true) || + param.name.originalKeywordKind && isTypeNodeKind(param.name.originalKeywordKind))) { + const newName = "arg" + param.parent.parameters.indexOf(param); + const typeName = declarationNameToString(param.name) + (param.dotDotDotToken ? "[]" : ""); + errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, typeName); return; - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - if (noImplicitAny && !(declaration as NamedDeclaration).name) { - if (wideningKind === WideningKind.GeneratorYield) { - error(declaration, Diagnostics.Generator_implicitly_has_yield_type_0_because_it_does_not_yield_any_values_Consider_supplying_a_return_type_annotation, typeAsString); - } - else { - error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); - } - return; + } + diagnostic = (declaration as ParameterDeclaration).dotDotDotToken ? + noImplicitAny ? Diagnostics.Rest_parameter_0_implicitly_has_an_any_type : Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage : + noImplicitAny ? Diagnostics.Parameter_0_implicitly_has_an_1_type : Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; + break; + case SyntaxKind.BindingElement: + diagnostic = Diagnostics.Binding_element_0_implicitly_has_an_1_type; + if (!noImplicitAny) { + // Don't issue a suggestion for binding elements since the codefix doesn't yet support them. + return; + } + break; + case SyntaxKind.JSDocFunctionType: + error(declaration, Diagnostics.Function_type_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); + return; + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + if (noImplicitAny && !(declaration as NamedDeclaration).name) { + if (wideningKind === WideningKind.GeneratorYield) { + error(declaration, Diagnostics.Generator_implicitly_has_yield_type_0_because_it_does_not_yield_any_values_Consider_supplying_a_return_type_annotation, typeAsString); } - diagnostic = !noImplicitAny ? Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage : - wideningKind === WideningKind.GeneratorYield ? Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_yield_type : - Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type; - break; - case SyntaxKind.MappedType: - if (noImplicitAny) { - error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type); + else { + error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); } return; - default: - diagnostic = noImplicitAny ? Diagnostics.Variable_0_implicitly_has_an_1_type : Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; + } + diagnostic = !noImplicitAny ? Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage : + wideningKind === WideningKind.GeneratorYield ? Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_yield_type : + Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type; + break; + case SyntaxKind.MappedType: + if (noImplicitAny) { + error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type); + } + return; + default: + diagnostic = noImplicitAny ? Diagnostics.Variable_0_implicitly_has_an_1_type : Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; + } + errorOrSuggestion(noImplicitAny, declaration, diagnostic, declarationNameToString(getNameOfDeclaration(declaration)), typeAsString); + } + + function reportErrorsFromWidening(declaration: Declaration, type: Type, wideningKind?: WideningKind) { + if (produceDiagnostics && noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType && (!wideningKind || !getContextualSignatureForFunctionLikeDeclaration(declaration as FunctionLikeDeclaration))) { + // Report implicit any error within type if possible, otherwise report error on declaration + if (!reportWideningErrorsInType(type)) { + reportImplicitAny(declaration, type, wideningKind); } - errorOrSuggestion(noImplicitAny, declaration, diagnostic, declarationNameToString(getNameOfDeclaration(declaration)), typeAsString); } + } - function reportErrorsFromWidening(declaration: Declaration, type: Type, wideningKind?: WideningKind) { - if (produceDiagnostics && noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType && (!wideningKind || !getContextualSignatureForFunctionLikeDeclaration(declaration as FunctionLikeDeclaration))) { - // Report implicit any error within type if possible, otherwise report error on declaration - if (!reportWideningErrorsInType(type)) { - reportImplicitAny(declaration, type, wideningKind); - } + function applyToParameterTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { + const sourceCount = getParameterCount(source); + const targetCount = getParameterCount(target); + const sourceRestType = getEffectiveRestType(source); + const targetRestType = getEffectiveRestType(target); + const targetNonRestCount = targetRestType ? targetCount - 1 : targetCount; + const paramCount = sourceRestType ? targetNonRestCount : Math.min(sourceCount, targetNonRestCount); + const sourceThisType = getThisTypeOfSignature(source); + if (sourceThisType) { + const targetThisType = getThisTypeOfSignature(target); + if (targetThisType) { + callback(sourceThisType, targetThisType); } } + for (let i = 0; i < paramCount; i++) { + callback(getTypeAtPosition(source, i), getTypeAtPosition(target, i)); + } + if (targetRestType) { + callback(getRestTypeAtPosition(source, paramCount), targetRestType); + } + } - function applyToParameterTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { - const sourceCount = getParameterCount(source); - const targetCount = getParameterCount(target); - const sourceRestType = getEffectiveRestType(source); - const targetRestType = getEffectiveRestType(target); - const targetNonRestCount = targetRestType ? targetCount - 1 : targetCount; - const paramCount = sourceRestType ? targetNonRestCount : Math.min(sourceCount, targetNonRestCount); - const sourceThisType = getThisTypeOfSignature(source); - if (sourceThisType) { - const targetThisType = getThisTypeOfSignature(target); - if (targetThisType) { - callback(sourceThisType, targetThisType); - } - } - for (let i = 0; i < paramCount; i++) { - callback(getTypeAtPosition(source, i), getTypeAtPosition(target, i)); - } - if (targetRestType) { - callback(getRestTypeAtPosition(source, paramCount), targetRestType); - } + function applyToReturnTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + const targetTypePredicate = getTypePredicateOfSignature(target); + if (sourceTypePredicate && targetTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) { + callback(sourceTypePredicate.type, targetTypePredicate.type); } - - function applyToReturnTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { - const sourceTypePredicate = getTypePredicateOfSignature(source); - const targetTypePredicate = getTypePredicateOfSignature(target); - if (sourceTypePredicate && targetTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) { - callback(sourceTypePredicate.type, targetTypePredicate.type); - } - else { - callback(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); - } + else { + callback(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); } + } - function createInferenceContext(typeParameters: readonly TypeParameter[], signature: Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer): InferenceContext { - return createInferenceContextWorker(typeParameters.map(createInferenceInfo), signature, flags, compareTypes || compareTypesAssignable); - } + function createInferenceContext(typeParameters: readonly TypeParameter[], signature: Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer): InferenceContext { + return createInferenceContextWorker(typeParameters.map(createInferenceInfo), signature, flags, compareTypes || compareTypesAssignable); + } - function cloneInferenceContext(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined { - return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes); - } + function cloneInferenceContext(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined { + return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes); + } - function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext { - const context: InferenceContext = { - inferences, - signature, - flags, - compareTypes, - mapper: makeFunctionTypeMapper(t => mapToInferredType(context, t, /*fix*/ true)), - nonFixingMapper: makeFunctionTypeMapper(t => mapToInferredType(context, t, /*fix*/ false)), - }; - return context; - } + function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext { + const context: InferenceContext = { + inferences, + signature, + flags, + compareTypes, + mapper: makeFunctionTypeMapper(t => mapToInferredType(context, t, /*fix*/ true)), + nonFixingMapper: makeFunctionTypeMapper(t => mapToInferredType(context, t, /*fix*/ false)), + }; + return context; + } - function mapToInferredType(context: InferenceContext, t: Type, fix: boolean): Type { - const inferences = context.inferences; - for (let i = 0; i < inferences.length; i++) { - const inference = inferences[i]; - if (t === inference.typeParameter) { - if (fix && !inference.isFixed) { - clearCachedInferences(inferences); - inference.isFixed = true; - } - return getInferredType(context, i); + function mapToInferredType(context: InferenceContext, t: Type, fix: boolean): Type { + const inferences = context.inferences; + for (let i = 0; i < inferences.length; i++) { + const inference = inferences[i]; + if (t === inference.typeParameter) { + if (fix && !inference.isFixed) { + clearCachedInferences(inferences); + inference.isFixed = true; } + return getInferredType(context, i); } - return t; } + return t; + } - function clearCachedInferences(inferences: InferenceInfo[]) { - for (const inference of inferences) { - if (!inference.isFixed) { - inference.inferredType = undefined; - } + function clearCachedInferences(inferences: InferenceInfo[]) { + for (const inference of inferences) { + if (!inference.isFixed) { + inference.inferredType = undefined; } } + } - function createInferenceInfo(typeParameter: TypeParameter): InferenceInfo { - return { - typeParameter, - candidates: undefined, - contraCandidates: undefined, - inferredType: undefined, - priority: undefined, - topLevel: true, - isFixed: false, - impliedArity: undefined - }; - } - - function cloneInferenceInfo(inference: InferenceInfo): InferenceInfo { - return { - typeParameter: inference.typeParameter, - candidates: inference.candidates && inference.candidates.slice(), - contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(), - inferredType: inference.inferredType, - priority: inference.priority, - topLevel: inference.topLevel, - isFixed: inference.isFixed, - impliedArity: inference.impliedArity - }; - } + function createInferenceInfo(typeParameter: TypeParameter): InferenceInfo { + return { + typeParameter, + candidates: undefined, + contraCandidates: undefined, + inferredType: undefined, + priority: undefined, + topLevel: true, + isFixed: false, + impliedArity: undefined + }; + } - function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined { - const inferences = filter(context.inferences, hasInferenceCandidates); - return inferences.length ? - createInferenceContextWorker(map(inferences, cloneInferenceInfo), context.signature, context.flags, context.compareTypes) : - undefined; - } + function cloneInferenceInfo(inference: InferenceInfo): InferenceInfo { + return { + typeParameter: inference.typeParameter, + candidates: inference.candidates && inference.candidates.slice(), + contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(), + inferredType: inference.inferredType, + priority: inference.priority, + topLevel: inference.topLevel, + isFixed: inference.isFixed, + impliedArity: inference.impliedArity + }; + } - function getMapperFromContext(context: T): TypeMapper | T & undefined { - return context && context.mapper; - } + function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined { + const inferences = filter(context.inferences, hasInferenceCandidates); + return inferences.length ? + createInferenceContextWorker(map(inferences, cloneInferenceInfo), context.signature, context.flags, context.compareTypes) : + undefined; + } - // Return true if the given type could possibly reference a type parameter for which - // we perform type inference (i.e. a type parameter of a generic function). We cache - // results for union and intersection types for performance reasons. - function couldContainTypeVariables(type: Type): boolean { - const objectFlags = getObjectFlags(type); - if (objectFlags & ObjectFlags.CouldContainTypeVariablesComputed) { - return !!(objectFlags & ObjectFlags.CouldContainTypeVariables); - } - const result = !!(type.flags & TypeFlags.Instantiable || - type.flags & TypeFlags.Object && !isNonGenericTopLevelType(type) && ( - objectFlags & ObjectFlags.Reference && ((type as TypeReference).node || forEach(getTypeArguments(type as TypeReference), couldContainTypeVariables)) || - objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations || - objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType)) || - type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType).types, couldContainTypeVariables)); - if (type.flags & TypeFlags.ObjectFlagsType) { - (type as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (result ? ObjectFlags.CouldContainTypeVariables : 0); - } - return result; - } + function getMapperFromContext(context: T): TypeMapper | T & undefined { + return context && context.mapper; + } - function isNonGenericTopLevelType(type: Type) { - if (type.aliasSymbol && !type.aliasTypeArguments) { - const declaration = getDeclarationOfKind(type.aliasSymbol, SyntaxKind.TypeAliasDeclaration); - return !!(declaration && findAncestor(declaration.parent, n => n.kind === SyntaxKind.SourceFile ? true : n.kind === SyntaxKind.ModuleDeclaration ? false : "quit")); - } - return false; - } + // Return true if the given type could possibly reference a type parameter for which + // we perform type inference (i.e. a type parameter of a generic function). We cache + // results for union and intersection types for performance reasons. + function couldContainTypeVariables(type: Type): boolean { + const objectFlags = getObjectFlags(type); + if (objectFlags & ObjectFlags.CouldContainTypeVariablesComputed) { + return !!(objectFlags & ObjectFlags.CouldContainTypeVariables); + } + const result = !!(type.flags & TypeFlags.Instantiable || + type.flags & TypeFlags.Object && !isNonGenericTopLevelType(type) && ( + objectFlags & ObjectFlags.Reference && ((type as TypeReference).node || forEach(getTypeArguments(type as TypeReference), couldContainTypeVariables)) || + objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations || + objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType)) || + type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType).types, couldContainTypeVariables)); + if (type.flags & TypeFlags.ObjectFlagsType) { + (type as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (result ? ObjectFlags.CouldContainTypeVariables : 0); + } + return result; + } - function isTypeParameterAtTopLevel(type: Type, typeParameter: TypeParameter): boolean { - return !!(type === typeParameter || - type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isTypeParameterAtTopLevel(t, typeParameter)) || - type.flags & TypeFlags.Conditional && (getTrueTypeFromConditionalType(type as ConditionalType) === typeParameter || getFalseTypeFromConditionalType(type as ConditionalType) === typeParameter)); + function isNonGenericTopLevelType(type: Type) { + if (type.aliasSymbol && !type.aliasTypeArguments) { + const declaration = getDeclarationOfKind(type.aliasSymbol, SyntaxKind.TypeAliasDeclaration); + return !!(declaration && findAncestor(declaration.parent, n => n.kind === SyntaxKind.SourceFile ? true : n.kind === SyntaxKind.ModuleDeclaration ? false : "quit")); } + return false; + } - /** Create an object with properties named in the string literal type. Every property has type `any` */ - function createEmptyObjectTypeFromStringLiteral(type: Type) { - const members = createSymbolTable(); - forEachType(type, t => { - if (!(t.flags & TypeFlags.StringLiteral)) { - return; - } - const name = escapeLeadingUnderscores((t as StringLiteralType).value); - const literalProp = createSymbol(SymbolFlags.Property, name); - literalProp.type = anyType; - if (t.symbol) { - literalProp.declarations = t.symbol.declarations; - literalProp.valueDeclaration = t.symbol.valueDeclaration; - } - members.set(name, literalProp); - }); - const indexInfos = type.flags & TypeFlags.String ? [createIndexInfo(stringType, emptyObjectType, /*isReadonly*/ false)] : emptyArray; - return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfos); - } + function isTypeParameterAtTopLevel(type: Type, typeParameter: TypeParameter): boolean { + return !!(type === typeParameter || + type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isTypeParameterAtTopLevel(t, typeParameter)) || + type.flags & TypeFlags.Conditional && (getTrueTypeFromConditionalType(type as ConditionalType) === typeParameter || getFalseTypeFromConditionalType(type as ConditionalType) === typeParameter)); + } - /** - * Infer a suitable input type for a homomorphic mapped type { [P in keyof T]: X }. We construct - * an object type with the same set of properties as the source type, where the type of each - * property is computed by inferring from the source property type to X for the type - * variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). - */ - function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined { - if (inInferTypeForHomomorphicMappedType) { - return undefined; + /** Create an object with properties named in the string literal type. Every property has type `any` */ + function createEmptyObjectTypeFromStringLiteral(type: Type) { + const members = createSymbolTable(); + forEachType(type, t => { + if (!(t.flags & TypeFlags.StringLiteral)) { + return; } - const key = source.id + "," + target.id + "," + constraint.id; - if (reverseMappedCache.has(key)) { - return reverseMappedCache.get(key); + const name = escapeLeadingUnderscores((t as StringLiteralType).value); + const literalProp = createSymbol(SymbolFlags.Property, name); + literalProp.type = anyType; + if (t.symbol) { + literalProp.declarations = t.symbol.declarations; + literalProp.valueDeclaration = t.symbol.valueDeclaration; } - inInferTypeForHomomorphicMappedType = true; - const type = createReverseMappedType(source, target, constraint); - inInferTypeForHomomorphicMappedType = false; - reverseMappedCache.set(key, type); - return type; - } + members.set(name, literalProp); + }); + const indexInfos = type.flags & TypeFlags.String ? [createIndexInfo(stringType, emptyObjectType, /*isReadonly*/ false)] : emptyArray; + return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfos); + } - // We consider a type to be partially inferable if it isn't marked non-inferable or if it is - // an object literal type with at least one property of an inferable type. For example, an object - // literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive - // arrow function, but is considered partially inferable because property 'a' has an inferable type. - function isPartiallyInferableType(type: Type): boolean { - return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) || - isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))) || - isTupleType(type) && some(getTypeArguments(type), isPartiallyInferableType); + /** + * Infer a suitable input type for a homomorphic mapped type { [P in keyof T]: X }. We construct + * an object type with the same set of properties as the source type, where the type of each + * property is computed by inferring from the source property type to X for the type + * variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). + */ + function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined { + if (inInferTypeForHomomorphicMappedType) { + return undefined; } + const key = source.id + "," + target.id + "," + constraint.id; + if (reverseMappedCache.has(key)) { + return reverseMappedCache.get(key); + } + inInferTypeForHomomorphicMappedType = true; + const type = createReverseMappedType(source, target, constraint); + inInferTypeForHomomorphicMappedType = false; + reverseMappedCache.set(key, type); + return type; + } - function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) { - // We consider a source type reverse mappable if it has a string index signature or if - // it has one or more properties and is of a partially inferable type. - if (!(getIndexInfoOfType(source, stringType) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) { - return undefined; - } - // For arrays and tuples we infer new arrays and tuples where the reverse mapping has been - // applied to the element type(s). - if (isArrayType(source)) { - return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source)); - } - if (isTupleType(source)) { - const elementTypes = map(getTypeArguments(source), t => inferReverseMappedType(t, target, constraint)); - const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ? - sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : - source.target.elementFlags; - return createTupleType(elementTypes, elementFlags, source.target.readonly, source.target.labeledElementDeclarations); - } - // For all other object types we infer a new object type where the reverse mapping has been - // applied to the type of each property. - const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType; - reversed.source = source; - reversed.mappedType = target; - reversed.constraintType = constraint; - return reversed; - } - - function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) { - const links = getSymbolLinks(symbol); - if (!links.type) { - links.type = inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType); - } - return links.type; + // We consider a type to be partially inferable if it isn't marked non-inferable or if it is + // an object literal type with at least one property of an inferable type. For example, an object + // literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive + // arrow function, but is considered partially inferable because property 'a' has an inferable type. + function isPartiallyInferableType(type: Type): boolean { + return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) || + isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))) || + isTupleType(type) && some(getTypeArguments(type), isPartiallyInferableType); + } + + function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) { + // We consider a source type reverse mappable if it has a string index signature or if + // it has one or more properties and is of a partially inferable type. + if (!(getIndexInfoOfType(source, stringType) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) { + return undefined; } + // For arrays and tuples we infer new arrays and tuples where the reverse mapping has been + // applied to the element type(s). + if (isArrayType(source)) { + return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source)); + } + if (isTupleType(source)) { + const elementTypes = map(getTypeArguments(source), t => inferReverseMappedType(t, target, constraint)); + const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ? + sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : + source.target.elementFlags; + return createTupleType(elementTypes, elementFlags, source.target.readonly, source.target.labeledElementDeclarations); + } + // For all other object types we infer a new object type where the reverse mapping has been + // applied to the type of each property. + const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType; + reversed.source = source; + reversed.mappedType = target; + reversed.constraintType = constraint; + return reversed; + } - function inferReverseMappedType(sourceType: Type, target: MappedType, constraint: IndexType): Type { - const typeParameter = getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target)) as TypeParameter; - const templateType = getTemplateTypeFromMappedType(target); - const inference = createInferenceInfo(typeParameter); - inferTypes([inference], sourceType, templateType); - return getTypeFromInference(inference) || unknownType; + function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) { + const links = getSymbolLinks(symbol); + if (!links.type) { + links.type = inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType); } + return links.type; + } - function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator { - const properties = getPropertiesOfType(target); - for (const targetProp of properties) { - // TODO: remove this when we support static private identifier fields and find other solutions to get privateNamesAndStaticFields test to pass - if (isStaticPrivateIdentifierProperty(targetProp)) { - continue; + function inferReverseMappedType(sourceType: Type, target: MappedType, constraint: IndexType): Type { + const typeParameter = getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target)) as TypeParameter; + const templateType = getTemplateTypeFromMappedType(target); + const inference = createInferenceInfo(typeParameter); + inferTypes([inference], sourceType, templateType); + return getTypeFromInference(inference) || unknownType; + } + + function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator { + const properties = getPropertiesOfType(target); + for (const targetProp of properties) { + // TODO: remove this when we support static private identifier fields and find other solutions to get privateNamesAndStaticFields test to pass + if (isStaticPrivateIdentifierProperty(targetProp)) { + continue; + } + if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial)) { + const sourceProp = getPropertyOfType(source, targetProp.escapedName); + if (!sourceProp) { + yield targetProp; } - if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial)) { - const sourceProp = getPropertyOfType(source, targetProp.escapedName); - if (!sourceProp) { - yield targetProp; - } - else if (matchDiscriminantProperties) { - const targetType = getTypeOfSymbol(targetProp); - if (targetType.flags & TypeFlags.Unit) { - const sourceType = getTypeOfSymbol(sourceProp); - if (!(sourceType.flags & TypeFlags.Any || getRegularTypeOfLiteralType(sourceType) === getRegularTypeOfLiteralType(targetType))) { - yield targetProp; - } + else if (matchDiscriminantProperties) { + const targetType = getTypeOfSymbol(targetProp); + if (targetType.flags & TypeFlags.Unit) { + const sourceType = getTypeOfSymbol(sourceProp); + if (!(sourceType.flags & TypeFlags.Any || getRegularTypeOfLiteralType(sourceType) === getRegularTypeOfLiteralType(targetType))) { + yield targetProp; } } } } } + } - function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined { - const result = getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties).next(); - if (!result.done) return result.value; - } + function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined { + const result = getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties).next(); + if (!result.done) return result.value; + } - function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) { - return !(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength || - !target.target.hasRestElement && (source.target.hasRestElement || target.target.fixedLength < source.target.fixedLength); - } + function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) { + return !(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength || + !target.target.hasRestElement && (source.target.hasRestElement || target.target.fixedLength < source.target.fixedLength); + } - function typesDefinitelyUnrelated(source: Type, target: Type) { - // Two tuple types with incompatible arities are definitely unrelated. - // Two object types that each have a property that is unmatched in the other are definitely unrelated. - return isTupleType(source) && isTupleType(target) ? tupleTypesDefinitelyUnrelated(source, target) : - !!getUnmatchedProperty(source, target, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true) && - !!getUnmatchedProperty(target, source, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ false); - } + function typesDefinitelyUnrelated(source: Type, target: Type) { + // Two tuple types with incompatible arities are definitely unrelated. + // Two object types that each have a property that is unmatched in the other are definitely unrelated. + return isTupleType(source) && isTupleType(target) ? tupleTypesDefinitelyUnrelated(source, target) : + !!getUnmatchedProperty(source, target, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true) && + !!getUnmatchedProperty(target, source, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ false); + } - function getTypeFromInference(inference: InferenceInfo) { - return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : - inference.contraCandidates ? getIntersectionType(inference.contraCandidates) : - undefined; - } + function getTypeFromInference(inference: InferenceInfo) { + return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : + inference.contraCandidates ? getIntersectionType(inference.contraCandidates) : + undefined; + } - function hasSkipDirectInferenceFlag(node: Node) { - return !!getNodeLinks(node).skipDirectInference; - } + function hasSkipDirectInferenceFlag(node: Node) { + return !!getNodeLinks(node).skipDirectInference; + } - function isFromInferenceBlockedSource(type: Type) { - return !!(type.symbol && some(type.symbol.declarations, hasSkipDirectInferenceFlag)); - } + function isFromInferenceBlockedSource(type: Type) { + return !!(type.symbol && some(type.symbol.declarations, hasSkipDirectInferenceFlag)); + } - function templateLiteralTypesDefinitelyUnrelated(source: TemplateLiteralType, target: TemplateLiteralType) { - // Two template literal types with diffences in their starting or ending text spans are definitely unrelated. - const sourceStart = source.texts[0]; - const targetStart = target.texts[0]; - const sourceEnd = source.texts[source.texts.length - 1]; - const targetEnd = target.texts[target.texts.length - 1]; - const startLen = Math.min(sourceStart.length, targetStart.length); - const endLen = Math.min(sourceEnd.length, targetEnd.length); - return sourceStart.slice(0, startLen) !== targetStart.slice(0, startLen) || - sourceEnd.slice(sourceEnd.length - endLen) !== targetEnd.slice(targetEnd.length - endLen); - } + function templateLiteralTypesDefinitelyUnrelated(source: TemplateLiteralType, target: TemplateLiteralType) { + // Two template literal types with diffences in their starting or ending text spans are definitely unrelated. + const sourceStart = source.texts[0]; + const targetStart = target.texts[0]; + const sourceEnd = source.texts[source.texts.length - 1]; + const targetEnd = target.texts[target.texts.length - 1]; + const startLen = Math.min(sourceStart.length, targetStart.length); + const endLen = Math.min(sourceEnd.length, targetEnd.length); + return sourceStart.slice(0, startLen) !== targetStart.slice(0, startLen) || + sourceEnd.slice(sourceEnd.length - endLen) !== targetEnd.slice(targetEnd.length - endLen); + } - function isValidBigIntString(s: string): boolean { - const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false); - let success = true; - scanner.setOnError(() => success = false); - scanner.setText(s + "n"); - let result = scanner.scan(); - if (result === SyntaxKind.MinusToken) { - result = scanner.scan(); - } - const flags = scanner.getTokenFlags(); - // validate that - // * scanning proceeded without error - // * a bigint can be scanned, and that when it is scanned, it is - // * the full length of the input string (so the scanner is one character beyond the augmented input length) - // * it does not contain a numeric seperator (the `BigInt` constructor does not accept a numeric seperator in its input) - return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator); + function isValidBigIntString(s: string): boolean { + const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false); + let success = true; + scanner.setOnError(() => success = false); + scanner.setText(s + "n"); + let result = scanner.scan(); + if (result === SyntaxKind.MinusToken) { + result = scanner.scan(); + } + const flags = scanner.getTokenFlags(); + // validate that + // * scanning proceeded without error + // * a bigint can be scanned, and that when it is scanned, it is + // * the full length of the input string (so the scanner is one character beyond the augmented input length) + // * it does not contain a numeric seperator (the `BigInt` constructor does not accept a numeric seperator in its input) + return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator); + } + + function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean { + if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) { + return true; + } + if (source.flags & TypeFlags.StringLiteral) { + const value = (source as StringLiteralType).value; + return !!(target.flags & TypeFlags.Number && value !== "" && isFinite(+value) || + target.flags & TypeFlags.BigInt && value !== "" && isValidBigIntString(value) || + target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName); } + if (source.flags & TypeFlags.TemplateLiteral) { + const texts = (source as TemplateLiteralType).texts; + return texts.length === 2 && texts[0] === "" && texts[1] === "" && isTypeAssignableTo((source as TemplateLiteralType).types[0], target); + } + return isTypeAssignableTo(source, target); + } - function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean { - if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) { - return true; + function inferTypesFromTemplateLiteralType(source: Type, target: TemplateLiteralType): Type[] | undefined { + return source.flags & TypeFlags.StringLiteral ? inferFromLiteralPartsToTemplateLiteral([(source as StringLiteralType).value], emptyArray, target) : + source.flags & TypeFlags.TemplateLiteral ? + arraysEqual((source as TemplateLiteralType).texts, target.texts) ? map((source as TemplateLiteralType).types, getStringLikeTypeForType) : + inferFromLiteralPartsToTemplateLiteral((source as TemplateLiteralType).texts, (source as TemplateLiteralType).types, target) : + undefined; + } + + function isTypeMatchedByTemplateLiteralType(source: Type, target: TemplateLiteralType): boolean { + const inferences = inferTypesFromTemplateLiteralType(source, target); + return !!inferences && every(inferences, (r, i) => isValidTypeForTemplateLiteralPlaceholder(r, target.types[i])); + } + + function getStringLikeTypeForType(type: Type) { + return type.flags & (TypeFlags.Any | TypeFlags.StringLike) ? type : getTemplateLiteralType(["", ""], [type]); + } + + // This function infers from the text parts and type parts of a source literal to a target template literal. The number + // of text parts is always one more than the number of type parts, and a source string literal is treated as a source + // with one text part and zero type parts. The function returns an array of inferred string or template literal types + // corresponding to the placeholders in the target template literal, or undefined if the source doesn't match the target. + // + // We first check that the starting source text part matches the starting target text part, and that the ending source + // text part ends matches the ending target text part. We then iterate through the remaining target text parts, finding + // a match for each in the source and inferring string or template literal types created from the segments of the source + // that occur between the matches. During this iteration, seg holds the index of the current text part in the sourceTexts + // array and pos holds the current character position in the current text part. + // + // Consider inference from type `<<${string}>.<${number}-${number}>>` to type `<${string}.${string}>`, i.e. + // sourceTexts = ['<<', '>.<', '-', '>>'] + // sourceTypes = [string, number, number] + // target.texts = ['<', '.', '>'] + // We first match '<' in the target to the start of '<<' in the source and '>' in the target to the end of '>>' in + // the source. The first match for the '.' in target occurs at character 1 in the source text part at index 1, and thus + // the first inference is the template literal type `<${string}>`. The remainder of the source makes up the second + // inference, the template literal type `<${number}-${number}>`. + function inferFromLiteralPartsToTemplateLiteral(sourceTexts: readonly string[], sourceTypes: readonly Type[], target: TemplateLiteralType): Type[] | undefined { + const lastSourceIndex = sourceTexts.length - 1; + const sourceStartText = sourceTexts[0]; + const sourceEndText = sourceTexts[lastSourceIndex]; + const targetTexts = target.texts; + const lastTargetIndex = targetTexts.length - 1; + const targetStartText = targetTexts[0]; + const targetEndText = targetTexts[lastTargetIndex]; + if (lastSourceIndex === 0 && sourceStartText.length < targetStartText.length + targetEndText.length || + !sourceStartText.startsWith(targetStartText) || !sourceEndText.endsWith(targetEndText)) return undefined; + const remainingEndText = sourceEndText.slice(0, sourceEndText.length - targetEndText.length); + const matches: Type[] = []; + let seg = 0; + let pos = targetStartText.length; + for (let i = 1; i < lastTargetIndex; i++) { + const delim = targetTexts[i]; + if (delim.length > 0) { + let s = seg; + let p = pos; + while (true) { + p = getSourceText(s).indexOf(delim, p); + if (p >= 0) break; + s++; + if (s === sourceTexts.length) return undefined; + p = 0; + } + addMatch(s, p); + pos += delim.length; } - if (source.flags & TypeFlags.StringLiteral) { - const value = (source as StringLiteralType).value; - return !!(target.flags & TypeFlags.Number && value !== "" && isFinite(+value) || - target.flags & TypeFlags.BigInt && value !== "" && isValidBigIntString(value) || - target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName); + else if (pos < getSourceText(seg).length) { + addMatch(seg, pos + 1); } - if (source.flags & TypeFlags.TemplateLiteral) { - const texts = (source as TemplateLiteralType).texts; - return texts.length === 2 && texts[0] === "" && texts[1] === "" && isTypeAssignableTo((source as TemplateLiteralType).types[0], target); + else if (seg < lastSourceIndex) { + addMatch(seg + 1, 0); + } + else { + return undefined; } - return isTypeAssignableTo(source, target); - } - - function inferTypesFromTemplateLiteralType(source: Type, target: TemplateLiteralType): Type[] | undefined { - return source.flags & TypeFlags.StringLiteral ? inferFromLiteralPartsToTemplateLiteral([(source as StringLiteralType).value], emptyArray, target) : - source.flags & TypeFlags.TemplateLiteral ? - arraysEqual((source as TemplateLiteralType).texts, target.texts) ? map((source as TemplateLiteralType).types, getStringLikeTypeForType) : - inferFromLiteralPartsToTemplateLiteral((source as TemplateLiteralType).texts, (source as TemplateLiteralType).types, target) : - undefined; } - - function isTypeMatchedByTemplateLiteralType(source: Type, target: TemplateLiteralType): boolean { - const inferences = inferTypesFromTemplateLiteralType(source, target); - return !!inferences && every(inferences, (r, i) => isValidTypeForTemplateLiteralPlaceholder(r, target.types[i])); + addMatch(lastSourceIndex, getSourceText(lastSourceIndex).length); + return matches; + function getSourceText(index: number) { + return index < lastSourceIndex ? sourceTexts[index] : remainingEndText; } - - function getStringLikeTypeForType(type: Type) { - return type.flags & (TypeFlags.Any | TypeFlags.StringLike) ? type : getTemplateLiteralType(["", ""], [type]); + function addMatch(s: number, p: number) { + const matchType = s === seg ? + getStringLiteralType(getSourceText(s).slice(pos, p)) : + getTemplateLiteralType( + [sourceTexts[seg].slice(pos), ...sourceTexts.slice(seg + 1, s), getSourceText(s).slice(0, p)], + sourceTypes.slice(seg, s)); + matches.push(matchType); + seg = s; + pos = p; } + } - // This function infers from the text parts and type parts of a source literal to a target template literal. The number - // of text parts is always one more than the number of type parts, and a source string literal is treated as a source - // with one text part and zero type parts. The function returns an array of inferred string or template literal types - // corresponding to the placeholders in the target template literal, or undefined if the source doesn't match the target. - // - // We first check that the starting source text part matches the starting target text part, and that the ending source - // text part ends matches the ending target text part. We then iterate through the remaining target text parts, finding - // a match for each in the source and inferring string or template literal types created from the segments of the source - // that occur between the matches. During this iteration, seg holds the index of the current text part in the sourceTexts - // array and pos holds the current character position in the current text part. - // - // Consider inference from type `<<${string}>.<${number}-${number}>>` to type `<${string}.${string}>`, i.e. - // sourceTexts = ['<<', '>.<', '-', '>>'] - // sourceTypes = [string, number, number] - // target.texts = ['<', '.', '>'] - // We first match '<' in the target to the start of '<<' in the source and '>' in the target to the end of '>>' in - // the source. The first match for the '.' in target occurs at character 1 in the source text part at index 1, and thus - // the first inference is the template literal type `<${string}>`. The remainder of the source makes up the second - // inference, the template literal type `<${number}-${number}>`. - function inferFromLiteralPartsToTemplateLiteral(sourceTexts: readonly string[], sourceTypes: readonly Type[], target: TemplateLiteralType): Type[] | undefined { - const lastSourceIndex = sourceTexts.length - 1; - const sourceStartText = sourceTexts[0]; - const sourceEndText = sourceTexts[lastSourceIndex]; - const targetTexts = target.texts; - const lastTargetIndex = targetTexts.length - 1; - const targetStartText = targetTexts[0]; - const targetEndText = targetTexts[lastTargetIndex]; - if (lastSourceIndex === 0 && sourceStartText.length < targetStartText.length + targetEndText.length || - !sourceStartText.startsWith(targetStartText) || !sourceEndText.endsWith(targetEndText)) return undefined; - const remainingEndText = sourceEndText.slice(0, sourceEndText.length - targetEndText.length); - const matches: Type[] = []; - let seg = 0; - let pos = targetStartText.length; - for (let i = 1; i < lastTargetIndex; i++) { - const delim = targetTexts[i]; - if (delim.length > 0) { - let s = seg; - let p = pos; - while (true) { - p = getSourceText(s).indexOf(delim, p); - if (p >= 0) break; - s++; - if (s === sourceTexts.length) return undefined; - p = 0; - } - addMatch(s, p); - pos += delim.length; - } - else if (pos < getSourceText(seg).length) { - addMatch(seg, pos + 1); - } - else if (seg < lastSourceIndex) { - addMatch(seg + 1, 0); - } - else { - return undefined; - } + function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) { + let bivariant = false; + let propagationType: Type; + let inferencePriority = InferencePriority.MaxValue; + let allowComplexConstraintInference = true; + let visited: ESMap; + let sourceStack: object[]; + let targetStack: object[]; + let expandingFlags = ExpandingFlags.None; + inferFromTypes(originalSource, originalTarget); + + function inferFromTypes(source: Type, target: Type): void { + if (!couldContainTypeVariables(target)) { + return; } - addMatch(lastSourceIndex, getSourceText(lastSourceIndex).length); - return matches; - function getSourceText(index: number) { - return index < lastSourceIndex ? sourceTexts[index] : remainingEndText; - } - function addMatch(s: number, p: number) { - const matchType = s === seg ? - getStringLiteralType(getSourceText(s).slice(pos, p)) : - getTemplateLiteralType( - [sourceTexts[seg].slice(pos), ...sourceTexts.slice(seg + 1, s), getSourceText(s).slice(0, p)], - sourceTypes.slice(seg, s)); - matches.push(matchType); - seg = s; - pos = p; - } - } - - function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) { - let bivariant = false; - let propagationType: Type; - let inferencePriority = InferencePriority.MaxValue; - let allowComplexConstraintInference = true; - let visited: ESMap; - let sourceStack: object[]; - let targetStack: object[]; - let expandingFlags = ExpandingFlags.None; - inferFromTypes(originalSource, originalTarget); - - function inferFromTypes(source: Type, target: Type): void { - if (!couldContainTypeVariables(target)) { - return; - } - if (source === wildcardType) { - // We are inferring from an 'any' type. We want to infer this type for every type parameter - // referenced in the target type, so we record it as the propagation type and infer from the - // target to itself. Then, as we find candidates we substitute the propagation type. - const savePropagationType = propagationType; - propagationType = source; - inferFromTypes(target, target); - propagationType = savePropagationType; - return; + if (source === wildcardType) { + // We are inferring from an 'any' type. We want to infer this type for every type parameter + // referenced in the target type, so we record it as the propagation type and infer from the + // target to itself. Then, as we find candidates we substitute the propagation type. + const savePropagationType = propagationType; + propagationType = source; + inferFromTypes(target, target); + propagationType = savePropagationType; + return; + } + if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) { + // Source and target are types originating in the same generic type alias declaration. + // Simply infer from source type arguments to target type arguments. + inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol)); + return; + } + if (source === target && source.flags & TypeFlags.UnionOrIntersection) { + // When source and target are the same union or intersection type, just relate each constituent + // type to itself. + for (const t of (source as UnionOrIntersectionType).types) { + inferFromTypes(t, t); } - if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) { - // Source and target are types originating in the same generic type alias declaration. - // Simply infer from source type arguments to target type arguments. - inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol)); + return; + } + if (target.flags & TypeFlags.Union) { + // First, infer between identically matching source and target constituents and remove the + // matching types. + const [tempSources, tempTargets] = inferFromMatchingTypes(source.flags & TypeFlags.Union ? (source as UnionType).types : [source], (target as UnionType).types, isTypeOrBaseIdenticalTo); + // Next, infer between closely matching source and target constituents and remove + // the matching types. Types closely match when they are instantiations of the same + // object type or instantiations of the same type alias. + const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy); + if (targets.length === 0) { return; } - if (source === target && source.flags & TypeFlags.UnionOrIntersection) { - // When source and target are the same union or intersection type, just relate each constituent - // type to itself. - for (const t of (source as UnionOrIntersectionType).types) { - inferFromTypes(t, t); - } + target = getUnionType(targets); + if (sources.length === 0) { + // All source constituents have been matched and there is nothing further to infer from. + // However, simply making no inferences is undesirable because it could ultimately mean + // inferring a type parameter constraint. Instead, make a lower priority inference from + // the full source to whatever remains in the target. For example, when inferring from + // string to 'string | T', make a lower priority inference of string for T. + inferWithPriority(source, target, InferencePriority.NakedTypeVariable); return; } - if (target.flags & TypeFlags.Union) { - // First, infer between identically matching source and target constituents and remove the - // matching types. - const [tempSources, tempTargets] = inferFromMatchingTypes(source.flags & TypeFlags.Union ? (source as UnionType).types : [source], (target as UnionType).types, isTypeOrBaseIdenticalTo); - // Next, infer between closely matching source and target constituents and remove - // the matching types. Types closely match when they are instantiations of the same - // object type or instantiations of the same type alias. - const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy); - if (targets.length === 0) { + source = getUnionType(sources); + } + else if (target.flags & TypeFlags.Intersection && some((target as IntersectionType).types, + t => !!getInferenceInfoForType(t) || (isGenericMappedType(t) && !!getInferenceInfoForType(getHomomorphicTypeVariable(t) || neverType)))) { + // We reduce intersection types only when they contain naked type parameters. For example, when + // inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and + // infer { extra: any } for T. But when inferring to 'string[] & Iterable' we want to keep the + // string[] on the source side and infer string for T. + // Likewise, we consider a homomorphic mapped type constrainted to the target type parameter as similar to a "naked type variable" + // in such scenarios. + if (!(source.flags & TypeFlags.Union)) { + // Infer between identically matching source and target constituents and remove the matching types. + const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types : [source], (target as IntersectionType).types, isTypeIdenticalTo); + if (sources.length === 0 || targets.length === 0) { return; } - target = getUnionType(targets); - if (sources.length === 0) { - // All source constituents have been matched and there is nothing further to infer from. - // However, simply making no inferences is undesirable because it could ultimately mean - // inferring a type parameter constraint. Instead, make a lower priority inference from - // the full source to whatever remains in the target. For example, when inferring from - // string to 'string | T', make a lower priority inference of string for T. - inferWithPriority(source, target, InferencePriority.NakedTypeVariable); - return; - } - source = getUnionType(sources); - } - else if (target.flags & TypeFlags.Intersection && some((target as IntersectionType).types, - t => !!getInferenceInfoForType(t) || (isGenericMappedType(t) && !!getInferenceInfoForType(getHomomorphicTypeVariable(t) || neverType)))) { - // We reduce intersection types only when they contain naked type parameters. For example, when - // inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and - // infer { extra: any } for T. But when inferring to 'string[] & Iterable' we want to keep the - // string[] on the source side and infer string for T. - // Likewise, we consider a homomorphic mapped type constrainted to the target type parameter as similar to a "naked type variable" - // in such scenarios. - if (!(source.flags & TypeFlags.Union)) { - // Infer between identically matching source and target constituents and remove the matching types. - const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types : [source], (target as IntersectionType).types, isTypeIdenticalTo); - if (sources.length === 0 || targets.length === 0) { - return; - } - source = getIntersectionType(sources); - target = getIntersectionType(targets); - } + source = getIntersectionType(sources); + target = getIntersectionType(targets); } - else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { - target = getActualTypeVariable(target); + } + else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { + target = getActualTypeVariable(target); + } + if (target.flags & TypeFlags.TypeVariable) { + // If target is a type parameter, make an inference, unless the source type contains + // the anyFunctionType (the wildcard type that's used to avoid contextually typing functions). + // Because the anyFunctionType is internal, it should not be exposed to the user by adding + // it as an inference candidate. Hopefully, a better candidate will come along that does + // not contain anyFunctionType when we come back to this argument for its second round + // of inference. Also, we exclude inferences for silentNeverType (which is used as a wildcard + // when constructing types from type parameters that had no inference candidates). + if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType || source === silentNeverType || + (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType)) || isFromInferenceBlockedSource(source)) { + return; } - if (target.flags & TypeFlags.TypeVariable) { - // If target is a type parameter, make an inference, unless the source type contains - // the anyFunctionType (the wildcard type that's used to avoid contextually typing functions). - // Because the anyFunctionType is internal, it should not be exposed to the user by adding - // it as an inference candidate. Hopefully, a better candidate will come along that does - // not contain anyFunctionType when we come back to this argument for its second round - // of inference. Also, we exclude inferences for silentNeverType (which is used as a wildcard - // when constructing types from type parameters that had no inference candidates). - if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType || source === silentNeverType || - (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType)) || isFromInferenceBlockedSource(source)) { - return; - } - const inference = getInferenceInfoForType(target); - if (inference) { - if (!inference.isFixed) { - if (inference.priority === undefined || priority < inference.priority) { - inference.candidates = undefined; - inference.contraCandidates = undefined; - inference.topLevel = true; - inference.priority = priority; - } - if (priority === inference.priority) { - const candidate = propagationType || source; - // We make contravariant inferences only if we are in a pure contravariant position, - // i.e. only if we have not descended into a bivariant position. - if (contravariant && !bivariant) { - if (!contains(inference.contraCandidates, candidate)) { - inference.contraCandidates = append(inference.contraCandidates, candidate); - clearCachedInferences(inferences); - } - } - else if (!contains(inference.candidates, candidate)) { - inference.candidates = append(inference.candidates, candidate); + const inference = getInferenceInfoForType(target); + if (inference) { + if (!inference.isFixed) { + if (inference.priority === undefined || priority < inference.priority) { + inference.candidates = undefined; + inference.contraCandidates = undefined; + inference.topLevel = true; + inference.priority = priority; + } + if (priority === inference.priority) { + const candidate = propagationType || source; + // We make contravariant inferences only if we are in a pure contravariant position, + // i.e. only if we have not descended into a bivariant position. + if (contravariant && !bivariant) { + if (!contains(inference.contraCandidates, candidate)) { + inference.contraCandidates = append(inference.contraCandidates, candidate); clearCachedInferences(inferences); } } - if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && inference.topLevel && !isTypeParameterAtTopLevel(originalTarget, target as TypeParameter)) { - inference.topLevel = false; + else if (!contains(inference.candidates, candidate)) { + inference.candidates = append(inference.candidates, candidate); clearCachedInferences(inferences); } } - inferencePriority = Math.min(inferencePriority, priority); - return; - } - else { - // Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine - const simplified = getSimplifiedType(target, /*writing*/ false); - if (simplified !== target) { - invokeOnce(source, simplified, inferFromTypes); - } - else if (target.flags & TypeFlags.IndexedAccess) { - const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false); - // Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider - // that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can. - if (indexType.flags & TypeFlags.Instantiable) { - const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false); - if (simplified && simplified !== target) { - invokeOnce(source, simplified, inferFromTypes); - } - } + if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && inference.topLevel && !isTypeParameterAtTopLevel(originalTarget, target as TypeParameter)) { + inference.topLevel = false; + clearCachedInferences(inferences); } } - } - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( - (source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target)) && - !((source as TypeReference).node && (target as TypeReference).node)) { - // If source and target are references to the same generic type, infer from type arguments - inferFromTypeArguments(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), getVariances((source as TypeReference).target)); - } - else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) { - contravariant = !contravariant; - inferFromTypes((source as IndexType).type, (target as IndexType).type); - contravariant = !contravariant; - } - else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) { - const empty = createEmptyObjectTypeFromStringLiteral(source); - contravariant = !contravariant; - inferWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof); - contravariant = !contravariant; - } - else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { - inferFromTypes((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType); - inferFromTypes((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType); - } - else if (source.flags & TypeFlags.StringMapping && target.flags & TypeFlags.StringMapping) { - if ((source as StringMappingType).symbol === (target as StringMappingType).symbol) { - inferFromTypes((source as StringMappingType).type, (target as StringMappingType).type); - } - } - else if (source.flags & TypeFlags.Substitution) { - inferFromTypes((source as SubstitutionType).baseType, target); - const oldPriority = priority; - priority |= InferencePriority.SubstituteSource; - inferFromTypes((source as SubstitutionType).substitute, target); // Make substitute inference at a lower priority - priority = oldPriority; - } - else if (target.flags & TypeFlags.Conditional) { - invokeOnce(source, target, inferToConditionalType); - } - else if (target.flags & TypeFlags.UnionOrIntersection) { - inferToMultipleTypes(source, (target as UnionOrIntersectionType).types, target.flags); - } - else if (source.flags & TypeFlags.Union) { - // Source is a union or intersection type, infer from each constituent type - const sourceTypes = (source as UnionOrIntersectionType).types; - for (const sourceType of sourceTypes) { - inferFromTypes(sourceType, target); - } - } - else if (target.flags & TypeFlags.TemplateLiteral) { - inferToTemplateLiteralType(source, target as TemplateLiteralType); + inferencePriority = Math.min(inferencePriority, priority); + return; } else { - source = getReducedType(source); - if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) { - const apparentSource = getApparentType(source); - // getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type. - // If that occurs and it doesn't simplify to an object or intersection, we'll need to restart `inferFromTypes` - // with the simplified source. - if (apparentSource !== source && allowComplexConstraintInference && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) { - // TODO: The `allowComplexConstraintInference` flag is a hack! This forbids inference from complex constraints within constraints! - // This isn't required algorithmically, but rather is used to lower the memory burden caused by performing inference - // that is _too good_ in projects with complicated constraints (eg, fp-ts). In such cases, if we did not limit ourselves - // here, we might produce more valid inferences for types, causing us to do more checks and perform more instantiations - // (in addition to the extra stack depth here) which, in turn, can push the already close process over its limit. - // TL;DR: If we ever become generally more memory efficient (or our resource budget ever increases), we should just - // remove this `allowComplexConstraintInference` flag. - allowComplexConstraintInference = false; - return inferFromTypes(apparentSource, target); - } - source = apparentSource; + // Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine + const simplified = getSimplifiedType(target, /*writing*/ false); + if (simplified !== target) { + invokeOnce(source, simplified, inferFromTypes); } - if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { - invokeOnce(source, target, inferFromObjectTypes); + else if (target.flags & TypeFlags.IndexedAccess) { + const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false); + // Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider + // that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can. + if (indexType.flags & TypeFlags.Instantiable) { + const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false); + if (simplified && simplified !== target) { + invokeOnce(source, simplified, inferFromTypes); + } + } } } } - - function inferWithPriority(source: Type, target: Type, newPriority: InferencePriority) { - const savePriority = priority; - priority |= newPriority; - inferFromTypes(source, target); - priority = savePriority; + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( + (source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target)) && + !((source as TypeReference).node && (target as TypeReference).node)) { + // If source and target are references to the same generic type, infer from type arguments + inferFromTypeArguments(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), getVariances((source as TypeReference).target)); } - - function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) { - const key = source.id + "," + target.id; - const status = visited && visited.get(key); - if (status !== undefined) { - inferencePriority = Math.min(inferencePriority, status); - return; - } - (visited || (visited = new Map())).set(key, InferencePriority.Circularity); - const saveInferencePriority = inferencePriority; - inferencePriority = InferencePriority.MaxValue; - // We stop inferring and report a circularity if we encounter duplicate recursion identities on both - // the source side and the target side. - const saveExpandingFlags = expandingFlags; - const sourceIdentity = getRecursionIdentity(source); - const targetIdentity = getRecursionIdentity(target); - if (contains(sourceStack, sourceIdentity)) expandingFlags |= ExpandingFlags.Source; - if (contains(targetStack, targetIdentity)) expandingFlags |= ExpandingFlags.Target; - if (expandingFlags !== ExpandingFlags.Both) { - (sourceStack || (sourceStack = [])).push(sourceIdentity); - (targetStack || (targetStack = [])).push(targetIdentity); - action(source, target); - targetStack.pop(); - sourceStack.pop(); - } - else { - inferencePriority = InferencePriority.Circularity; + else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) { + contravariant = !contravariant; + inferFromTypes((source as IndexType).type, (target as IndexType).type); + contravariant = !contravariant; + } + else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) { + const empty = createEmptyObjectTypeFromStringLiteral(source); + contravariant = !contravariant; + inferWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof); + contravariant = !contravariant; + } + else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { + inferFromTypes((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType); + inferFromTypes((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType); + } + else if (source.flags & TypeFlags.StringMapping && target.flags & TypeFlags.StringMapping) { + if ((source as StringMappingType).symbol === (target as StringMappingType).symbol) { + inferFromTypes((source as StringMappingType).type, (target as StringMappingType).type); } - expandingFlags = saveExpandingFlags; - visited.set(key, inferencePriority); - inferencePriority = Math.min(inferencePriority, saveInferencePriority); } - - function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] { - let matchedSources: Type[] | undefined; - let matchedTargets: Type[] | undefined; - for (const t of targets) { - for (const s of sources) { - if (matches(s, t)) { - inferFromTypes(s, t); - matchedSources = appendIfUnique(matchedSources, s); - matchedTargets = appendIfUnique(matchedTargets, t); - } - } + else if (source.flags & TypeFlags.Substitution) { + inferFromTypes((source as SubstitutionType).baseType, target); + const oldPriority = priority; + priority |= InferencePriority.SubstituteSource; + inferFromTypes((source as SubstitutionType).substitute, target); // Make substitute inference at a lower priority + priority = oldPriority; + } + else if (target.flags & TypeFlags.Conditional) { + invokeOnce(source, target, inferToConditionalType); + } + else if (target.flags & TypeFlags.UnionOrIntersection) { + inferToMultipleTypes(source, (target as UnionOrIntersectionType).types, target.flags); + } + else if (source.flags & TypeFlags.Union) { + // Source is a union or intersection type, infer from each constituent type + const sourceTypes = (source as UnionOrIntersectionType).types; + for (const sourceType of sourceTypes) { + inferFromTypes(sourceType, target); } - return [ - matchedSources ? filter(sources, t => !contains(matchedSources, t)) : sources, - matchedTargets ? filter(targets, t => !contains(matchedTargets, t)) : targets, - ]; } + else if (target.flags & TypeFlags.TemplateLiteral) { + inferToTemplateLiteralType(source, target as TemplateLiteralType); + } + else { + source = getReducedType(source); + if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) { + const apparentSource = getApparentType(source); + // getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type. + // If that occurs and it doesn't simplify to an object or intersection, we'll need to restart `inferFromTypes` + // with the simplified source. + if (apparentSource !== source && allowComplexConstraintInference && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) { + // TODO: The `allowComplexConstraintInference` flag is a hack! This forbids inference from complex constraints within constraints! + // This isn't required algorithmically, but rather is used to lower the memory burden caused by performing inference + // that is _too good_ in projects with complicated constraints (eg, fp-ts). In such cases, if we did not limit ourselves + // here, we might produce more valid inferences for types, causing us to do more checks and perform more instantiations + // (in addition to the extra stack depth here) which, in turn, can push the already close process over its limit. + // TL;DR: If we ever become generally more memory efficient (or our resource budget ever increases), we should just + // remove this `allowComplexConstraintInference` flag. + allowComplexConstraintInference = false; + return inferFromTypes(apparentSource, target); + } + source = apparentSource; + } + if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { + invokeOnce(source, target, inferFromObjectTypes); + } + } + } + + function inferWithPriority(source: Type, target: Type, newPriority: InferencePriority) { + const savePriority = priority; + priority |= newPriority; + inferFromTypes(source, target); + priority = savePriority; + } + + function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) { + const key = source.id + "," + target.id; + const status = visited && visited.get(key); + if (status !== undefined) { + inferencePriority = Math.min(inferencePriority, status); + return; + } + (visited || (visited = new Map())).set(key, InferencePriority.Circularity); + const saveInferencePriority = inferencePriority; + inferencePriority = InferencePriority.MaxValue; + // We stop inferring and report a circularity if we encounter duplicate recursion identities on both + // the source side and the target side. + const saveExpandingFlags = expandingFlags; + const sourceIdentity = getRecursionIdentity(source); + const targetIdentity = getRecursionIdentity(target); + if (contains(sourceStack, sourceIdentity)) expandingFlags |= ExpandingFlags.Source; + if (contains(targetStack, targetIdentity)) expandingFlags |= ExpandingFlags.Target; + if (expandingFlags !== ExpandingFlags.Both) { + (sourceStack || (sourceStack = [])).push(sourceIdentity); + (targetStack || (targetStack = [])).push(targetIdentity); + action(source, target); + targetStack.pop(); + sourceStack.pop(); + } + else { + inferencePriority = InferencePriority.Circularity; + } + expandingFlags = saveExpandingFlags; + visited.set(key, inferencePriority); + inferencePriority = Math.min(inferencePriority, saveInferencePriority); + } - function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) { - const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length; - for (let i = 0; i < count; i++) { - if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) { - inferFromContravariantTypes(sourceTypes[i], targetTypes[i]); - } - else { - inferFromTypes(sourceTypes[i], targetTypes[i]); + function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] { + let matchedSources: Type[] | undefined; + let matchedTargets: Type[] | undefined; + for (const t of targets) { + for (const s of sources) { + if (matches(s, t)) { + inferFromTypes(s, t); + matchedSources = appendIfUnique(matchedSources, s); + matchedTargets = appendIfUnique(matchedTargets, t); } } } + return [ + matchedSources ? filter(sources, t => !contains(matchedSources, t)) : sources, + matchedTargets ? filter(targets, t => !contains(matchedTargets, t)) : targets, + ]; + } - function inferFromContravariantTypes(source: Type, target: Type) { - if (strictFunctionTypes || priority & InferencePriority.AlwaysStrict) { - contravariant = !contravariant; - inferFromTypes(source, target); - contravariant = !contravariant; + function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) { + const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length; + for (let i = 0; i < count; i++) { + if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) { + inferFromContravariantTypes(sourceTypes[i], targetTypes[i]); } else { - inferFromTypes(source, target); + inferFromTypes(sourceTypes[i], targetTypes[i]); } } + } - function getInferenceInfoForType(type: Type) { - if (type.flags & TypeFlags.TypeVariable) { - for (const inference of inferences) { - if (type === inference.typeParameter) { - return inference; - } + function inferFromContravariantTypes(source: Type, target: Type) { + if (strictFunctionTypes || priority & InferencePriority.AlwaysStrict) { + contravariant = !contravariant; + inferFromTypes(source, target); + contravariant = !contravariant; + } + else { + inferFromTypes(source, target); + } + } + + function getInferenceInfoForType(type: Type) { + if (type.flags & TypeFlags.TypeVariable) { + for (const inference of inferences) { + if (type === inference.typeParameter) { + return inference; } } - return undefined; } + return undefined; + } - function getSingleTypeVariableFromIntersectionTypes(types: Type[]) { - let typeVariable: Type | undefined; - for (const type of types) { - const t = type.flags & TypeFlags.Intersection && find((type as IntersectionType).types, t => !!getInferenceInfoForType(t)); - if (!t || typeVariable && t !== typeVariable) { - return undefined; - } - typeVariable = t; + function getSingleTypeVariableFromIntersectionTypes(types: Type[]) { + let typeVariable: Type | undefined; + for (const type of types) { + const t = type.flags & TypeFlags.Intersection && find((type as IntersectionType).types, t => !!getInferenceInfoForType(t)); + if (!t || typeVariable && t !== typeVariable) { + return undefined; } - return typeVariable; + typeVariable = t; } + return typeVariable; + } - function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) { - let typeVariableCount = 0; - if (targetFlags & TypeFlags.Union) { - let nakedTypeVariable: Type | undefined; - const sources = source.flags & TypeFlags.Union ? (source as UnionType).types : [source]; - const matched = new Array(sources.length); - let inferenceCircularity = false; - // First infer to types that are not naked type variables. For each source type we - // track whether inferences were made from that particular type to some target with - // equal priority (i.e. of equal quality) to what we would infer for a naked type - // parameter. - for (const t of targets) { - if (getInferenceInfoForType(t)) { - nakedTypeVariable = t; - typeVariableCount++; - } - else { - for (let i = 0; i < sources.length; i++) { - const saveInferencePriority = inferencePriority; - inferencePriority = InferencePriority.MaxValue; - inferFromTypes(sources[i], t); - if (inferencePriority === priority) matched[i] = true; - inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity; - inferencePriority = Math.min(inferencePriority, saveInferencePriority); - } - } - } - if (typeVariableCount === 0) { - // If every target is an intersection of types containing a single naked type variable, - // make a lower priority inference to that type variable. This handles inferring from - // 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T. - const intersectionTypeVariable = getSingleTypeVariableFromIntersectionTypes(targets); - if (intersectionTypeVariable) { - inferWithPriority(source, intersectionTypeVariable, InferencePriority.NakedTypeVariable); - } - return; + function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) { + let typeVariableCount = 0; + if (targetFlags & TypeFlags.Union) { + let nakedTypeVariable: Type | undefined; + const sources = source.flags & TypeFlags.Union ? (source as UnionType).types : [source]; + const matched = new Array(sources.length); + let inferenceCircularity = false; + // First infer to types that are not naked type variables. For each source type we + // track whether inferences were made from that particular type to some target with + // equal priority (i.e. of equal quality) to what we would infer for a naked type + // parameter. + for (const t of targets) { + if (getInferenceInfoForType(t)) { + nakedTypeVariable = t; + typeVariableCount++; } - // If the target has a single naked type variable and no inference circularities were - // encountered above (meaning we explored the types fully), create a union of the source - // types from which no inferences have been made so far and infer from that union to the - // naked type variable. - if (typeVariableCount === 1 && !inferenceCircularity) { - const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); - if (unmatched.length) { - inferFromTypes(getUnionType(unmatched), nakedTypeVariable!); - return; + else { + for (let i = 0; i < sources.length; i++) { + const saveInferencePriority = inferencePriority; + inferencePriority = InferencePriority.MaxValue; + inferFromTypes(sources[i], t); + if (inferencePriority === priority) matched[i] = true; + inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity; + inferencePriority = Math.min(inferencePriority, saveInferencePriority); } } } - else { - // We infer from types that are not naked type variables first so that inferences we - // make from nested naked type variables and given slightly higher priority by virtue - // of being first in the candidates array. - for (const t of targets) { - if (getInferenceInfoForType(t)) { - typeVariableCount++; - } - else { - inferFromTypes(source, t); - } + if (typeVariableCount === 0) { + // If every target is an intersection of types containing a single naked type variable, + // make a lower priority inference to that type variable. This handles inferring from + // 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T. + const intersectionTypeVariable = getSingleTypeVariableFromIntersectionTypes(targets); + if (intersectionTypeVariable) { + inferWithPriority(source, intersectionTypeVariable, InferencePriority.NakedTypeVariable); } + return; } - // Inferences directly to naked type variables are given lower priority as they are - // less specific. For example, when inferring from Promise to T | Promise, - // we want to infer string for T, not Promise | string. For intersection types - // we only infer to single naked type variables. - if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { - for (const t of targets) { - if (getInferenceInfoForType(t)) { - inferWithPriority(source, t, InferencePriority.NakedTypeVariable); - } + // If the target has a single naked type variable and no inference circularities were + // encountered above (meaning we explored the types fully), create a union of the source + // types from which no inferences have been made so far and infer from that union to the + // naked type variable. + if (typeVariableCount === 1 && !inferenceCircularity) { + const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); + if (unmatched.length) { + inferFromTypes(getUnionType(unmatched), nakedTypeVariable!); + return; } } } - - function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { - if (constraintType.flags & TypeFlags.Union) { - let result = false; - for (const type of (constraintType as UnionType).types) { - result = inferToMappedType(source, target, type) || result; + else { + // We infer from types that are not naked type variables first so that inferences we + // make from nested naked type variables and given slightly higher priority by virtue + // of being first in the candidates array. + for (const t of targets) { + if (getInferenceInfoForType(t)) { + typeVariableCount++; } - return result; - } - if (constraintType.flags & TypeFlags.Index) { - // We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X }, - // where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source - // type and then make a secondary inference from that type to T. We make a secondary inference - // such that direct inferences to T get priority over inferences to Partial, for example. - const inference = getInferenceInfoForType((constraintType as IndexType).type); - if (inference && !inference.isFixed && !isFromInferenceBlockedSource(source)) { - const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType as IndexType); - if (inferredType) { - // We assign a lower priority to inferences made from types containing non-inferrable - // types because we may only have a partial result (i.e. we may have failed to make - // reverse inferences for some properties). - inferWithPriority(inferredType, inference.typeParameter, - getObjectFlags(source) & ObjectFlags.NonInferrableType ? - InferencePriority.PartialHomomorphicMappedType : - InferencePriority.HomomorphicMappedType); - } + else { + inferFromTypes(source, t); } - return true; } - if (constraintType.flags & TypeFlags.TypeParameter) { - // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type - // parameter. First infer from 'keyof S' to K. - inferWithPriority(getIndexType(source), constraintType, InferencePriority.MappedTypeConstraint); - // If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X }, - // where K extends keyof T, we make the same inferences as for a homomorphic mapped type - // { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a - // Pick. - const extendedConstraint = getConstraintOfType(constraintType); - if (extendedConstraint && inferToMappedType(source, target, extendedConstraint)) { - return true; + } + // Inferences directly to naked type variables are given lower priority as they are + // less specific. For example, when inferring from Promise to T | Promise, + // we want to infer string for T, not Promise | string. For intersection types + // we only infer to single naked type variables. + if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { + for (const t of targets) { + if (getInferenceInfoForType(t)) { + inferWithPriority(source, t, InferencePriority.NakedTypeVariable); } - // If no inferences can be made to K's constraint, infer from a union of the property types - // in the source to the template type X. - const propTypes = map(getPropertiesOfType(source), getTypeOfSymbol); - const indexTypes = map(getIndexInfosOfType(source), info => info !== enumNumberIndexInfo ? info.type : neverType); - inferFromTypes(getUnionType(concatenate(propTypes, indexTypes)), getTemplateTypeFromMappedType(target)); - return true; } - return false; } + } - function inferToConditionalType(source: Type, target: ConditionalType) { - if (source.flags & TypeFlags.Conditional) { - inferFromTypes((source as ConditionalType).checkType, target.checkType); - inferFromTypes((source as ConditionalType).extendsType, target.extendsType); - inferFromTypes(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target)); - inferFromTypes(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target)); + function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { + if (constraintType.flags & TypeFlags.Union) { + let result = false; + for (const type of (constraintType as UnionType).types) { + result = inferToMappedType(source, target, type) || result; } - else { - const savePriority = priority; - priority |= contravariant ? InferencePriority.ContravariantConditional : 0; - const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; - inferToMultipleTypes(source, targetTypes, target.flags); - priority = savePriority; + return result; + } + if (constraintType.flags & TypeFlags.Index) { + // We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X }, + // where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source + // type and then make a secondary inference from that type to T. We make a secondary inference + // such that direct inferences to T get priority over inferences to Partial, for example. + const inference = getInferenceInfoForType((constraintType as IndexType).type); + if (inference && !inference.isFixed && !isFromInferenceBlockedSource(source)) { + const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType as IndexType); + if (inferredType) { + // We assign a lower priority to inferences made from types containing non-inferrable + // types because we may only have a partial result (i.e. we may have failed to make + // reverse inferences for some properties). + inferWithPriority(inferredType, inference.typeParameter, + getObjectFlags(source) & ObjectFlags.NonInferrableType ? + InferencePriority.PartialHomomorphicMappedType : + InferencePriority.HomomorphicMappedType); + } + } + return true; + } + if (constraintType.flags & TypeFlags.TypeParameter) { + // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type + // parameter. First infer from 'keyof S' to K. + inferWithPriority(getIndexType(source), constraintType, InferencePriority.MappedTypeConstraint); + // If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X }, + // where K extends keyof T, we make the same inferences as for a homomorphic mapped type + // { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a + // Pick. + const extendedConstraint = getConstraintOfType(constraintType); + if (extendedConstraint && inferToMappedType(source, target, extendedConstraint)) { + return true; } + // If no inferences can be made to K's constraint, infer from a union of the property types + // in the source to the template type X. + const propTypes = map(getPropertiesOfType(source), getTypeOfSymbol); + const indexTypes = map(getIndexInfosOfType(source), info => info !== enumNumberIndexInfo ? info.type : neverType); + inferFromTypes(getUnionType(concatenate(propTypes, indexTypes)), getTemplateTypeFromMappedType(target)); + return true; } + return false; + } - function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) { - const matches = inferTypesFromTemplateLiteralType(source, target); - const types = target.types; - // When the target template literal contains only placeholders (meaning that inference is intended to extract - // single characters and remainder strings) and inference fails to produce matches, we want to infer 'never' for - // each placeholder such that instantiation with the inferred value(s) produces 'never', a type for which an - // assignment check will fail. If we make no inferences, we'll likely end up with the constraint 'string' which, - // upon instantiation, would collapse all the placeholders to just 'string', and an assignment check might - // succeed. That would be a pointless and confusing outcome. - if (matches || every(target.texts, s => s.length === 0)) { - for (let i = 0; i < types.length; i++) { - inferFromTypes(matches ? matches[i] : neverType, types[i]); - } + function inferToConditionalType(source: Type, target: ConditionalType) { + if (source.flags & TypeFlags.Conditional) { + inferFromTypes((source as ConditionalType).checkType, target.checkType); + inferFromTypes((source as ConditionalType).extendsType, target.extendsType); + inferFromTypes(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target)); + inferFromTypes(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target)); + } + else { + const savePriority = priority; + priority |= contravariant ? InferencePriority.ContravariantConditional : 0; + const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; + inferToMultipleTypes(source, targetTypes, target.flags); + priority = savePriority; + } + } + + function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) { + const matches = inferTypesFromTemplateLiteralType(source, target); + const types = target.types; + // When the target template literal contains only placeholders (meaning that inference is intended to extract + // single characters and remainder strings) and inference fails to produce matches, we want to infer 'never' for + // each placeholder such that instantiation with the inferred value(s) produces 'never', a type for which an + // assignment check will fail. If we make no inferences, we'll likely end up with the constraint 'string' which, + // upon instantiation, would collapse all the placeholders to just 'string', and an assignment check might + // succeed. That would be a pointless and confusing outcome. + if (matches || every(target.texts, s => s.length === 0)) { + for (let i = 0; i < types.length; i++) { + inferFromTypes(matches ? matches[i] : neverType, types[i]); } } + } - function inferFromObjectTypes(source: Type, target: Type) { - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( - (source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target))) { - // If source and target are references to the same generic type, infer from type arguments - inferFromTypeArguments(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), getVariances((source as TypeReference).target)); + function inferFromObjectTypes(source: Type, target: Type) { + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( + (source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target))) { + // If source and target are references to the same generic type, infer from type arguments + inferFromTypeArguments(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), getVariances((source as TypeReference).target)); + return; + } + if (isGenericMappedType(source) && isGenericMappedType(target)) { + // The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer + // from S to T and from X to Y. + inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target)); + inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target)); + const sourceNameType = getNameTypeFromMappedType(source); + const targetNameType = getNameTypeFromMappedType(target); + if (sourceNameType && targetNameType) inferFromTypes(sourceNameType, targetNameType); + } + if (getObjectFlags(target) & ObjectFlags.Mapped && !(target as MappedType).declaration.nameType) { + const constraintType = getConstraintTypeFromMappedType(target as MappedType); + if (inferToMappedType(source, target as MappedType, constraintType)) { return; } - if (isGenericMappedType(source) && isGenericMappedType(target)) { - // The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer - // from S to T and from X to Y. - inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target)); - inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target)); - const sourceNameType = getNameTypeFromMappedType(source); - const targetNameType = getNameTypeFromMappedType(target); - if (sourceNameType && targetNameType) inferFromTypes(sourceNameType, targetNameType); - } - if (getObjectFlags(target) & ObjectFlags.Mapped && !(target as MappedType).declaration.nameType) { - const constraintType = getConstraintTypeFromMappedType(target as MappedType); - if (inferToMappedType(source, target as MappedType, constraintType)) { - return; - } - } - // Infer from the members of source and target only if the two types are possibly related - if (!typesDefinitelyUnrelated(source, target)) { - if (isArrayType(source) || isTupleType(source)) { - if (isTupleType(target)) { - const sourceArity = getTypeReferenceArity(source); - const targetArity = getTypeReferenceArity(target); - const elementTypes = getTypeArguments(target); - const elementFlags = target.target.elementFlags; - // When source and target are tuple types with the same structure (fixed, variadic, and rest are matched - // to the same kind in each position), simply infer between the element types. - if (isTupleType(source) && isTupleTypeStructureMatching(source, target)) { - for (let i = 0; i < targetArity; i++) { - inferFromTypes(getTypeArguments(source)[i], elementTypes[i]); - } - return; - } - const startLength = isTupleType(source) ? Math.min(source.target.fixedLength, target.target.fixedLength) : 0; - const endLength = Math.min(isTupleType(source) ? getEndElementCount(source.target, ElementFlags.Fixed) : 0, - target.target.hasRestElement ? getEndElementCount(target.target, ElementFlags.Fixed) : 0); - // Infer between starting fixed elements. - for (let i = 0; i < startLength; i++) { + } + // Infer from the members of source and target only if the two types are possibly related + if (!typesDefinitelyUnrelated(source, target)) { + if (isArrayType(source) || isTupleType(source)) { + if (isTupleType(target)) { + const sourceArity = getTypeReferenceArity(source); + const targetArity = getTypeReferenceArity(target); + const elementTypes = getTypeArguments(target); + const elementFlags = target.target.elementFlags; + // When source and target are tuple types with the same structure (fixed, variadic, and rest are matched + // to the same kind in each position), simply infer between the element types. + if (isTupleType(source) && isTupleTypeStructureMatching(source, target)) { + for (let i = 0; i < targetArity; i++) { inferFromTypes(getTypeArguments(source)[i], elementTypes[i]); } - if (!isTupleType(source) || sourceArity - startLength - endLength === 1 && source.target.elementFlags[startLength] & ElementFlags.Rest) { - // Single rest element remains in source, infer from that to every element in target - const restType = getTypeArguments(source)[startLength]; - for (let i = startLength; i < targetArity - endLength; i++) { - inferFromTypes(elementFlags[i] & ElementFlags.Variadic ? createArrayType(restType) : restType, elementTypes[i]); - } + return; + } + const startLength = isTupleType(source) ? Math.min(source.target.fixedLength, target.target.fixedLength) : 0; + const endLength = Math.min(isTupleType(source) ? getEndElementCount(source.target, ElementFlags.Fixed) : 0, + target.target.hasRestElement ? getEndElementCount(target.target, ElementFlags.Fixed) : 0); + // Infer between starting fixed elements. + for (let i = 0; i < startLength; i++) { + inferFromTypes(getTypeArguments(source)[i], elementTypes[i]); + } + if (!isTupleType(source) || sourceArity - startLength - endLength === 1 && source.target.elementFlags[startLength] & ElementFlags.Rest) { + // Single rest element remains in source, infer from that to every element in target + const restType = getTypeArguments(source)[startLength]; + for (let i = startLength; i < targetArity - endLength; i++) { + inferFromTypes(elementFlags[i] & ElementFlags.Variadic ? createArrayType(restType) : restType, elementTypes[i]); } - else { - const middleLength = targetArity - startLength - endLength; - if (middleLength === 2 && elementFlags[startLength] & elementFlags[startLength + 1] & ElementFlags.Variadic && isTupleType(source)) { - // Middle of target is [...T, ...U] and source is tuple type - const targetInfo = getInferenceInfoForType(elementTypes[startLength]); - if (targetInfo && targetInfo.impliedArity !== undefined) { - // Infer slices from source based on implied arity of T. - inferFromTypes(sliceTupleType(source, startLength, endLength + sourceArity - targetInfo.impliedArity), elementTypes[startLength]); - inferFromTypes(sliceTupleType(source, startLength + targetInfo.impliedArity, endLength), elementTypes[startLength + 1]); - } - } - else if (middleLength === 1 && elementFlags[startLength] & ElementFlags.Variadic) { - // Middle of target is exactly one variadic element. Infer the slice between the fixed parts in the source. - // If target ends in optional element(s), make a lower priority a speculative inference. - const endsInOptional = target.target.elementFlags[targetArity - 1] & ElementFlags.Optional; - const sourceSlice = isTupleType(source) ? sliceTupleType(source, startLength, endLength) : createArrayType(getTypeArguments(source)[0]); - inferWithPriority(sourceSlice, elementTypes[startLength], endsInOptional ? InferencePriority.SpeculativeTuple : 0); - } - else if (middleLength === 1 && elementFlags[startLength] & ElementFlags.Rest) { - // Middle of target is exactly one rest element. If middle of source is not empty, infer union of middle element types. - const restType = isTupleType(source) ? getElementTypeOfSliceOfTupleType(source, startLength, endLength) : getTypeArguments(source)[0]; - if (restType) { - inferFromTypes(restType, elementTypes[startLength]); - } + } + else { + const middleLength = targetArity - startLength - endLength; + if (middleLength === 2 && elementFlags[startLength] & elementFlags[startLength + 1] & ElementFlags.Variadic && isTupleType(source)) { + // Middle of target is [...T, ...U] and source is tuple type + const targetInfo = getInferenceInfoForType(elementTypes[startLength]); + if (targetInfo && targetInfo.impliedArity !== undefined) { + // Infer slices from source based on implied arity of T. + inferFromTypes(sliceTupleType(source, startLength, endLength + sourceArity - targetInfo.impliedArity), elementTypes[startLength]); + inferFromTypes(sliceTupleType(source, startLength + targetInfo.impliedArity, endLength), elementTypes[startLength + 1]); } } - // Infer between ending fixed elements - for (let i = 0; i < endLength; i++) { - inferFromTypes(getTypeArguments(source)[sourceArity - i - 1], elementTypes[targetArity - i - 1]); + else if (middleLength === 1 && elementFlags[startLength] & ElementFlags.Variadic) { + // Middle of target is exactly one variadic element. Infer the slice between the fixed parts in the source. + // If target ends in optional element(s), make a lower priority a speculative inference. + const endsInOptional = target.target.elementFlags[targetArity - 1] & ElementFlags.Optional; + const sourceSlice = isTupleType(source) ? sliceTupleType(source, startLength, endLength) : createArrayType(getTypeArguments(source)[0]); + inferWithPriority(sourceSlice, elementTypes[startLength], endsInOptional ? InferencePriority.SpeculativeTuple : 0); + } + else if (middleLength === 1 && elementFlags[startLength] & ElementFlags.Rest) { + // Middle of target is exactly one rest element. If middle of source is not empty, infer union of middle element types. + const restType = isTupleType(source) ? getElementTypeOfSliceOfTupleType(source, startLength, endLength) : getTypeArguments(source)[0]; + if (restType) { + inferFromTypes(restType, elementTypes[startLength]); + } } - return; } - if (isArrayType(target)) { - inferFromIndexTypes(source, target); - return; + // Infer between ending fixed elements + for (let i = 0; i < endLength; i++) { + inferFromTypes(getTypeArguments(source)[sourceArity - i - 1], elementTypes[targetArity - i - 1]); } + return; + } + if (isArrayType(target)) { + inferFromIndexTypes(source, target); + return; } - inferFromProperties(source, target); - inferFromSignatures(source, target, SignatureKind.Call); - inferFromSignatures(source, target, SignatureKind.Construct); - inferFromIndexTypes(source, target); } + inferFromProperties(source, target); + inferFromSignatures(source, target, SignatureKind.Call); + inferFromSignatures(source, target, SignatureKind.Construct); + inferFromIndexTypes(source, target); } + } - function inferFromProperties(source: Type, target: Type) { - const properties = getPropertiesOfObjectType(target); - for (const targetProp of properties) { - const sourceProp = getPropertyOfType(source, targetProp.escapedName); - if (sourceProp) { - inferFromTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); - } + function inferFromProperties(source: Type, target: Type) { + const properties = getPropertiesOfObjectType(target); + for (const targetProp of properties) { + const sourceProp = getPropertyOfType(source, targetProp.escapedName); + if (sourceProp) { + inferFromTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); } } + } - function inferFromSignatures(source: Type, target: Type, kind: SignatureKind) { - const sourceSignatures = getSignaturesOfType(source, kind); - const targetSignatures = getSignaturesOfType(target, kind); - const sourceLen = sourceSignatures.length; - const targetLen = targetSignatures.length; - const len = sourceLen < targetLen ? sourceLen : targetLen; - const skipParameters = !!(getObjectFlags(source) & ObjectFlags.NonInferrableType); - for (let i = 0; i < len; i++) { - inferFromSignature(getBaseSignature(sourceSignatures[sourceLen - len + i]), getErasedSignature(targetSignatures[targetLen - len + i]), skipParameters); - } + function inferFromSignatures(source: Type, target: Type, kind: SignatureKind) { + const sourceSignatures = getSignaturesOfType(source, kind); + const targetSignatures = getSignaturesOfType(target, kind); + const sourceLen = sourceSignatures.length; + const targetLen = targetSignatures.length; + const len = sourceLen < targetLen ? sourceLen : targetLen; + const skipParameters = !!(getObjectFlags(source) & ObjectFlags.NonInferrableType); + for (let i = 0; i < len; i++) { + inferFromSignature(getBaseSignature(sourceSignatures[sourceLen - len + i]), getErasedSignature(targetSignatures[targetLen - len + i]), skipParameters); } + } - function inferFromSignature(source: Signature, target: Signature, skipParameters: boolean) { - if (!skipParameters) { - const saveBivariant = bivariant; - const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; - // Once we descend into a bivariant signature we remain bivariant for all nested inferences - bivariant = bivariant || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.Constructor; - applyToParameterTypes(source, target, inferFromContravariantTypes); - bivariant = saveBivariant; - } - applyToReturnTypes(source, target, inferFromTypes); + function inferFromSignature(source: Signature, target: Signature, skipParameters: boolean) { + if (!skipParameters) { + const saveBivariant = bivariant; + const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; + // Once we descend into a bivariant signature we remain bivariant for all nested inferences + bivariant = bivariant || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.Constructor; + applyToParameterTypes(source, target, inferFromContravariantTypes); + bivariant = saveBivariant; } + applyToReturnTypes(source, target, inferFromTypes); + } - function inferFromIndexTypes(source: Type, target: Type) { - // Inferences across mapped type index signatures are pretty much the same a inferences to homomorphic variables - const priority = (getObjectFlags(source) & getObjectFlags(target) & ObjectFlags.Mapped) ? InferencePriority.HomomorphicMappedType : 0; - const indexInfos = getIndexInfosOfType(target); - if (isObjectTypeWithInferableIndex(source)) { - for (const targetInfo of indexInfos) { - const propTypes: Type[] = []; - for (const prop of getPropertiesOfType(source)) { - if (isApplicableIndexType(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), targetInfo.keyType)) { - const propType = getTypeOfSymbol(prop); - propTypes.push(prop.flags & SymbolFlags.Optional ? removeMissingOrUndefinedType(propType) : propType); - } - } - for (const info of getIndexInfosOfType(source)) { - if (isApplicableIndexType(info.keyType, targetInfo.keyType)) { - propTypes.push(info.type); - } + function inferFromIndexTypes(source: Type, target: Type) { + // Inferences across mapped type index signatures are pretty much the same a inferences to homomorphic variables + const priority = (getObjectFlags(source) & getObjectFlags(target) & ObjectFlags.Mapped) ? InferencePriority.HomomorphicMappedType : 0; + const indexInfos = getIndexInfosOfType(target); + if (isObjectTypeWithInferableIndex(source)) { + for (const targetInfo of indexInfos) { + const propTypes: Type[] = []; + for (const prop of getPropertiesOfType(source)) { + if (isApplicableIndexType(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), targetInfo.keyType)) { + const propType = getTypeOfSymbol(prop); + propTypes.push(prop.flags & SymbolFlags.Optional ? removeMissingOrUndefinedType(propType) : propType); } - if (propTypes.length) { - inferWithPriority(getUnionType(propTypes), targetInfo.type, priority); + } + for (const info of getIndexInfosOfType(source)) { + if (isApplicableIndexType(info.keyType, targetInfo.keyType)) { + propTypes.push(info.type); } } - } - for (const targetInfo of indexInfos) { - const sourceInfo = getApplicableIndexInfo(source, targetInfo.keyType); - if (sourceInfo) { - inferWithPriority(sourceInfo.type, targetInfo.type, priority); + if (propTypes.length) { + inferWithPriority(getUnionType(propTypes), targetInfo.type, priority); } } } + for (const targetInfo of indexInfos) { + const sourceInfo = getApplicableIndexInfo(source, targetInfo.keyType); + if (sourceInfo) { + inferWithPriority(sourceInfo.type, targetInfo.type, priority); + } + } } + } - function isTypeOrBaseIdenticalTo(s: Type, t: Type) { - return exactOptionalPropertyTypes && t === missingType ? s === t : - (isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral)); - } + function isTypeOrBaseIdenticalTo(s: Type, t: Type) { + return exactOptionalPropertyTypes && t === missingType ? s === t : + (isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral)); + } - function isTypeCloselyMatchedBy(s: Type, t: Type) { - return !!(s.flags & TypeFlags.Object && t.flags & TypeFlags.Object && s.symbol && s.symbol === t.symbol || - s.aliasSymbol && s.aliasTypeArguments && s.aliasSymbol === t.aliasSymbol); - } + function isTypeCloselyMatchedBy(s: Type, t: Type) { + return !!(s.flags & TypeFlags.Object && t.flags & TypeFlags.Object && s.symbol && s.symbol === t.symbol || + s.aliasSymbol && s.aliasTypeArguments && s.aliasSymbol === t.aliasSymbol); + } - function hasPrimitiveConstraint(type: TypeParameter): boolean { - const constraint = getConstraintOfTypeParameter(type); - return !!constraint && maybeTypeOfKind(constraint.flags & TypeFlags.Conditional ? getDefaultConstraintOfConditionalType(constraint as ConditionalType) : constraint, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping); - } + function hasPrimitiveConstraint(type: TypeParameter): boolean { + const constraint = getConstraintOfTypeParameter(type); + return !!constraint && maybeTypeOfKind(constraint.flags & TypeFlags.Conditional ? getDefaultConstraintOfConditionalType(constraint as ConditionalType) : constraint, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping); + } - function isObjectLiteralType(type: Type) { - return !!(getObjectFlags(type) & ObjectFlags.ObjectLiteral); - } + function isObjectLiteralType(type: Type) { + return !!(getObjectFlags(type) & ObjectFlags.ObjectLiteral); + } - function isObjectOrArrayLiteralType(type: Type) { - return !!(getObjectFlags(type) & (ObjectFlags.ObjectLiteral | ObjectFlags.ArrayLiteral)); - } + function isObjectOrArrayLiteralType(type: Type) { + return !!(getObjectFlags(type) & (ObjectFlags.ObjectLiteral | ObjectFlags.ArrayLiteral)); + } - function unionObjectAndArrayLiteralCandidates(candidates: Type[]): Type[] { - if (candidates.length > 1) { - const objectLiterals = filter(candidates, isObjectOrArrayLiteralType); - if (objectLiterals.length) { - const literalsType = getUnionType(objectLiterals, UnionReduction.Subtype); - return concatenate(filter(candidates, t => !isObjectOrArrayLiteralType(t)), [literalsType]); - } + function unionObjectAndArrayLiteralCandidates(candidates: Type[]): Type[] { + if (candidates.length > 1) { + const objectLiterals = filter(candidates, isObjectOrArrayLiteralType); + if (objectLiterals.length) { + const literalsType = getUnionType(objectLiterals, UnionReduction.Subtype); + return concatenate(filter(candidates, t => !isObjectOrArrayLiteralType(t)), [literalsType]); } - return candidates; } + return candidates; + } - function getContravariantInference(inference: InferenceInfo) { - return inference.priority! & InferencePriority.PriorityImpliesCombination ? getIntersectionType(inference.contraCandidates!) : getCommonSubtype(inference.contraCandidates!); - } + function getContravariantInference(inference: InferenceInfo) { + return inference.priority! & InferencePriority.PriorityImpliesCombination ? getIntersectionType(inference.contraCandidates!) : getCommonSubtype(inference.contraCandidates!); + } - function getCovariantInference(inference: InferenceInfo, signature: Signature) { - // Extract all object and array literal types and replace them with a single widened and normalized type. - const candidates = unionObjectAndArrayLiteralCandidates(inference.candidates!); - // We widen inferred literal types if - // all inferences were made to top-level occurrences of the type parameter, and - // the type parameter has no constraint or its constraint includes no primitive or literal types, and - // the type parameter was fixed during inference or does not occur at top-level in the return type. - const primitiveConstraint = hasPrimitiveConstraint(inference.typeParameter); - const widenLiteralTypes = !primitiveConstraint && inference.topLevel && - (inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter)); - const baseCandidates = primitiveConstraint ? sameMap(candidates, getRegularTypeOfLiteralType) : - widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) : - candidates; - // If all inferences were made from a position that implies a combined result, infer a union type. - // Otherwise, infer a common supertype. - const unwidenedType = inference.priority! & InferencePriority.PriorityImpliesCombination ? - getUnionType(baseCandidates, UnionReduction.Subtype) : - getCommonSupertype(baseCandidates); - return getWidenedType(unwidenedType); - } + function getCovariantInference(inference: InferenceInfo, signature: Signature) { + // Extract all object and array literal types and replace them with a single widened and normalized type. + const candidates = unionObjectAndArrayLiteralCandidates(inference.candidates!); + // We widen inferred literal types if + // all inferences were made to top-level occurrences of the type parameter, and + // the type parameter has no constraint or its constraint includes no primitive or literal types, and + // the type parameter was fixed during inference or does not occur at top-level in the return type. + const primitiveConstraint = hasPrimitiveConstraint(inference.typeParameter); + const widenLiteralTypes = !primitiveConstraint && inference.topLevel && + (inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter)); + const baseCandidates = primitiveConstraint ? sameMap(candidates, getRegularTypeOfLiteralType) : + widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) : + candidates; + // If all inferences were made from a position that implies a combined result, infer a union type. + // Otherwise, infer a common supertype. + const unwidenedType = inference.priority! & InferencePriority.PriorityImpliesCombination ? + getUnionType(baseCandidates, UnionReduction.Subtype) : + getCommonSupertype(baseCandidates); + return getWidenedType(unwidenedType); + } - function getInferredType(context: InferenceContext, index: number): Type { - const inference = context.inferences[index]; - if (!inference.inferredType) { - let inferredType: Type | undefined; - const signature = context.signature; - if (signature) { - const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined; - if (inference.contraCandidates) { - // If we have both co- and contra-variant inferences, we prefer the contra-variant inference - // unless the co-variant inference is a subtype of some contra-variant inference and not 'never'. - inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) && - some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) ? - inferredCovariantType : getContravariantInference(inference); - } - else if (inferredCovariantType) { - inferredType = inferredCovariantType; - } - else if (context.flags & InferenceFlags.NoDefault) { - // We use silentNeverType as the wildcard that signals no inferences. - inferredType = silentNeverType; - } - else { - // Infer either the default or the empty object type when no inferences were - // made. It is important to remember that in this case, inference still - // succeeds, meaning there is no error for not having inference candidates. An - // inference error only occurs when there are *conflicting* candidates, i.e. - // candidates with no common supertype. - const defaultType = getDefaultFromTypeParameter(inference.typeParameter); - if (defaultType) { - // Instantiate the default type. Any forward reference to a type - // parameter should be instantiated to the empty object type. - inferredType = instantiateType(defaultType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper)); - } - } + function getInferredType(context: InferenceContext, index: number): Type { + const inference = context.inferences[index]; + if (!inference.inferredType) { + let inferredType: Type | undefined; + const signature = context.signature; + if (signature) { + const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined; + if (inference.contraCandidates) { + // If we have both co- and contra-variant inferences, we prefer the contra-variant inference + // unless the co-variant inference is a subtype of some contra-variant inference and not 'never'. + inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) && + some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) ? + inferredCovariantType : getContravariantInference(inference); + } + else if (inferredCovariantType) { + inferredType = inferredCovariantType; + } + else if (context.flags & InferenceFlags.NoDefault) { + // We use silentNeverType as the wildcard that signals no inferences. + inferredType = silentNeverType; } else { - inferredType = getTypeFromInference(inference); + // Infer either the default or the empty object type when no inferences were + // made. It is important to remember that in this case, inference still + // succeeds, meaning there is no error for not having inference candidates. An + // inference error only occurs when there are *conflicting* candidates, i.e. + // candidates with no common supertype. + const defaultType = getDefaultFromTypeParameter(inference.typeParameter); + if (defaultType) { + // Instantiate the default type. Any forward reference to a type + // parameter should be instantiated to the empty object type. + inferredType = instantiateType(defaultType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper)); + } } + } + else { + inferredType = getTypeFromInference(inference); + } - inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault)); + inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault)); - const constraint = getConstraintOfTypeParameter(inference.typeParameter); - if (constraint) { - const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); - if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { - inference.inferredType = inferredType = instantiatedConstraint; - } + const constraint = getConstraintOfTypeParameter(inference.typeParameter); + if (constraint) { + const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); + if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { + inference.inferredType = inferredType = instantiatedConstraint; } } - - return inference.inferredType; } - function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type { - return isInJavaScriptFile ? anyType : unknownType; - } + return inference.inferredType; + } - function getInferredTypes(context: InferenceContext): Type[] { - const result: Type[] = []; - for (let i = 0; i < context.inferences.length; i++) { - result.push(getInferredType(context, i)); - } - return result; - } + function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type { + return isInJavaScriptFile ? anyType : unknownType; + } - // EXPRESSION TYPE CHECKING - - function getCannotFindNameDiagnosticForName(node: Identifier): DiagnosticMessage { - switch (node.escapedText) { - case "document": - case "console": - return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_include_dom; - case "$": - return compilerOptions.types - ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery_and_then_add_jquery_to_the_types_field_in_your_tsconfig - : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery; - case "describe": - case "suite": - case "it": - case "test": - return compilerOptions.types - ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha_and_then_add_jest_or_mocha_to_the_types_field_in_your_tsconfig - : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha; - case "process": - case "require": - case "Buffer": - case "module": - return compilerOptions.types - ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode_and_then_add_node_to_the_types_field_in_your_tsconfig - : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode; - case "Map": - case "Set": - case "Promise": - case "Symbol": - case "WeakMap": - case "WeakSet": - case "Iterator": - case "AsyncIterator": - case "SharedArrayBuffer": - case "Atomics": - case "AsyncIterable": - case "AsyncIterableIterator": - case "AsyncGenerator": - case "AsyncGeneratorFunction": - case "BigInt": - case "Reflect": - case "BigInt64Array": - case "BigUint64Array": - return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_1_or_later; - default: - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - return Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer; - } - else { - return Diagnostics.Cannot_find_name_0; - } - } + function getInferredTypes(context: InferenceContext): Type[] { + const result: Type[] = []; + for (let i = 0; i < context.inferences.length; i++) { + result.push(getInferredType(context, i)); } + return result; + } - function getResolvedSymbol(node: Identifier): Symbol { - const links = getNodeLinks(node); - if (!links.resolvedSymbol) { - links.resolvedSymbol = !nodeIsMissing(node) && - resolveName( - node, - node.escapedText, - SymbolFlags.Value | SymbolFlags.ExportValue, - getCannotFindNameDiagnosticForName(node), - node, - !isWriteOnlyAccess(node), - /*excludeGlobals*/ false) || unknownSymbol; - } - return links.resolvedSymbol; + // EXPRESSION TYPE CHECKING + + function getCannotFindNameDiagnosticForName(node: Identifier): DiagnosticMessage { + switch (node.escapedText) { + case "document": + case "console": + return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_include_dom; + case "$": + return compilerOptions.types + ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery_and_then_add_jquery_to_the_types_field_in_your_tsconfig + : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery; + case "describe": + case "suite": + case "it": + case "test": + return compilerOptions.types + ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha_and_then_add_jest_or_mocha_to_the_types_field_in_your_tsconfig + : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha; + case "process": + case "require": + case "Buffer": + case "module": + return compilerOptions.types + ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode_and_then_add_node_to_the_types_field_in_your_tsconfig + : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode; + case "Map": + case "Set": + case "Promise": + case "Symbol": + case "WeakMap": + case "WeakSet": + case "Iterator": + case "AsyncIterator": + case "SharedArrayBuffer": + case "Atomics": + case "AsyncIterable": + case "AsyncIterableIterator": + case "AsyncGenerator": + case "AsyncGeneratorFunction": + case "BigInt": + case "Reflect": + case "BigInt64Array": + case "BigUint64Array": + return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_1_or_later; + default: + if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + return Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer; + } + else { + return Diagnostics.Cannot_find_name_0; + } } + } - function isInTypeQuery(node: Node): boolean { - // TypeScript 1.0 spec (April 2014): 3.6.3 - // A type query consists of the keyword typeof followed by an expression. - // The expression is restricted to a single identifier or a sequence of identifiers separated by periods - return !!findAncestor( - node, - n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit"); + function getResolvedSymbol(node: Identifier): Symbol { + const links = getNodeLinks(node); + if (!links.resolvedSymbol) { + links.resolvedSymbol = !nodeIsMissing(node) && + resolveName( + node, + node.escapedText, + SymbolFlags.Value | SymbolFlags.ExportValue, + getCannotFindNameDiagnosticForName(node), + node, + !isWriteOnlyAccess(node), + /*excludeGlobals*/ false) || unknownSymbol; } + return links.resolvedSymbol; + } - // Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers - // separated by dots). The key consists of the id of the symbol referenced by the - // leftmost identifier followed by zero or more property names separated by dots. - // The result is undefined if the reference isn't a dotted name. - function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined { - switch (node.kind) { - case SyntaxKind.Identifier: - if (!isThisInTypeQuery(node)) { - const symbol = getResolvedSymbol(node as Identifier); - return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${getSymbolId(symbol)}` : undefined; - } - // falls through - case SyntaxKind.ThisKeyword: - return `0|${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}`; - case SyntaxKind.NonNullExpression: - case SyntaxKind.ParenthesizedExpression: - return getFlowCacheKey((node as NonNullExpression | ParenthesizedExpression).expression, declaredType, initialType, flowContainer); - case SyntaxKind.QualifiedName: - const left = getFlowCacheKey((node as QualifiedName).left, declaredType, initialType, flowContainer); - return left && left + "." + (node as QualifiedName).right.escapedText; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - const propName = getAccessedPropertyName(node as AccessExpression); - if (propName !== undefined) { - const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); - return key && key + "." + propName; - } - } - return undefined; - } + function isInTypeQuery(node: Node): boolean { + // TypeScript 1.0 spec (April 2014): 3.6.3 + // A type query consists of the keyword typeof followed by an expression. + // The expression is restricted to a single identifier or a sequence of identifiers separated by periods + return !!findAncestor( + node, + n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit"); + } - function isMatchingReference(source: Node, target: Node): boolean { - switch (target.kind) { - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.NonNullExpression: - return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression); - case SyntaxKind.BinaryExpression: - return (isAssignmentExpression(target) && isMatchingReference(source, target.left)) || - (isBinaryExpression(target) && target.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source, target.right)); - } - switch (source.kind) { - case SyntaxKind.MetaProperty: - return target.kind === SyntaxKind.MetaProperty - && (source as MetaProperty).keywordToken === (target as MetaProperty).keywordToken - && (source as MetaProperty).name.escapedText === (target as MetaProperty).name.escapedText; - case SyntaxKind.Identifier: - case SyntaxKind.PrivateIdentifier: - return isThisInTypeQuery(source) ? - target.kind === SyntaxKind.ThisKeyword : - target.kind === SyntaxKind.Identifier && getResolvedSymbol(source as Identifier) === getResolvedSymbol(target as Identifier) || - (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && - getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target); - case SyntaxKind.ThisKeyword: - return target.kind === SyntaxKind.ThisKeyword; - case SyntaxKind.SuperKeyword: - return target.kind === SyntaxKind.SuperKeyword; - case SyntaxKind.NonNullExpression: - case SyntaxKind.ParenthesizedExpression: - return isMatchingReference((source as NonNullExpression | ParenthesizedExpression).expression, target); - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return isAccessExpression(target) && - getAccessedPropertyName(source as AccessExpression) === getAccessedPropertyName(target) && - isMatchingReference((source as AccessExpression).expression, target.expression); - case SyntaxKind.QualifiedName: - return isAccessExpression(target) && - (source as QualifiedName).right.escapedText === getAccessedPropertyName(target) && - isMatchingReference((source as QualifiedName).left, target.expression); - case SyntaxKind.BinaryExpression: - return (isBinaryExpression(source) && source.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source.right, target)); - } - return false; - } + // Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers + // separated by dots). The key consists of the id of the symbol referenced by the + // leftmost identifier followed by zero or more property names separated by dots. + // The result is undefined if the reference isn't a dotted name. + function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined { + switch (node.kind) { + case SyntaxKind.Identifier: + if (!isThisInTypeQuery(node)) { + const symbol = getResolvedSymbol(node as Identifier); + return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${getSymbolId(symbol)}` : undefined; + } + // falls through + case SyntaxKind.ThisKeyword: + return `0|${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}`; + case SyntaxKind.NonNullExpression: + case SyntaxKind.ParenthesizedExpression: + return getFlowCacheKey((node as NonNullExpression | ParenthesizedExpression).expression, declaredType, initialType, flowContainer); + case SyntaxKind.QualifiedName: + const left = getFlowCacheKey((node as QualifiedName).left, declaredType, initialType, flowContainer); + return left && left + "." + (node as QualifiedName).right.escapedText; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + const propName = getAccessedPropertyName(node as AccessExpression); + if (propName !== undefined) { + const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); + return key && key + "." + propName; + } + } + return undefined; + } - function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined { - let propertyName; - return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText : - access.kind === SyntaxKind.ElementAccessExpression && isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) : - access.kind === SyntaxKind.BindingElement && (propertyName = getDestructuringPropertyName(access)) ? escapeLeadingUnderscores(propertyName) : - undefined; - } + function isMatchingReference(source: Node, target: Node): boolean { + switch (target.kind) { + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.NonNullExpression: + return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression); + case SyntaxKind.BinaryExpression: + return (isAssignmentExpression(target) && isMatchingReference(source, target.left)) || + (isBinaryExpression(target) && target.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source, target.right)); + } + switch (source.kind) { + case SyntaxKind.MetaProperty: + return target.kind === SyntaxKind.MetaProperty + && (source as MetaProperty).keywordToken === (target as MetaProperty).keywordToken + && (source as MetaProperty).name.escapedText === (target as MetaProperty).name.escapedText; + case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: + return isThisInTypeQuery(source) ? + target.kind === SyntaxKind.ThisKeyword : + target.kind === SyntaxKind.Identifier && getResolvedSymbol(source as Identifier) === getResolvedSymbol(target as Identifier) || + (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && + getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target); + case SyntaxKind.ThisKeyword: + return target.kind === SyntaxKind.ThisKeyword; + case SyntaxKind.SuperKeyword: + return target.kind === SyntaxKind.SuperKeyword; + case SyntaxKind.NonNullExpression: + case SyntaxKind.ParenthesizedExpression: + return isMatchingReference((source as NonNullExpression | ParenthesizedExpression).expression, target); + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return isAccessExpression(target) && + getAccessedPropertyName(source as AccessExpression) === getAccessedPropertyName(target) && + isMatchingReference((source as AccessExpression).expression, target.expression); + case SyntaxKind.QualifiedName: + return isAccessExpression(target) && + (source as QualifiedName).right.escapedText === getAccessedPropertyName(target) && + isMatchingReference((source as QualifiedName).left, target.expression); + case SyntaxKind.BinaryExpression: + return (isBinaryExpression(source) && source.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source.right, target)); + } + return false; + } - function containsMatchingReference(source: Node, target: Node) { - while (isAccessExpression(source)) { - source = source.expression; - if (isMatchingReference(source, target)) { - return true; - } + function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined { + let propertyName; + return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText : + access.kind === SyntaxKind.ElementAccessExpression && isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) : + access.kind === SyntaxKind.BindingElement && (propertyName = getDestructuringPropertyName(access)) ? escapeLeadingUnderscores(propertyName) : + undefined; + } + + function containsMatchingReference(source: Node, target: Node) { + while (isAccessExpression(source)) { + source = source.expression; + if (isMatchingReference(source, target)) { + return true; } - return false; } + return false; + } - function optionalChainContainsReference(source: Node, target: Node) { - while (isOptionalChain(source)) { - source = source.expression; - if (isMatchingReference(source, target)) { - return true; - } + function optionalChainContainsReference(source: Node, target: Node) { + while (isOptionalChain(source)) { + source = source.expression; + if (isMatchingReference(source, target)) { + return true; } - return false; } + return false; + } - function isDiscriminantProperty(type: Type | undefined, name: __String) { - if (type && type.flags & TypeFlags.Union) { - const prop = getUnionOrIntersectionProperty(type as UnionType, name); - if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { - if ((prop as TransientSymbol).isDiscriminantProperty === undefined) { - (prop as TransientSymbol).isDiscriminantProperty = - ((prop as TransientSymbol).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && - !isGenericType(getTypeOfSymbol(prop)); - } - return !!(prop as TransientSymbol).isDiscriminantProperty; + function isDiscriminantProperty(type: Type | undefined, name: __String) { + if (type && type.flags & TypeFlags.Union) { + const prop = getUnionOrIntersectionProperty(type as UnionType, name); + if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { + if ((prop as TransientSymbol).isDiscriminantProperty === undefined) { + (prop as TransientSymbol).isDiscriminantProperty = + ((prop as TransientSymbol).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && + !isGenericType(getTypeOfSymbol(prop)); } + return !!(prop as TransientSymbol).isDiscriminantProperty; } - return false; } + return false; + } - function findDiscriminantProperties(sourceProperties: Symbol[], target: Type): Symbol[] | undefined { - let result: Symbol[] | undefined; - for (const sourceProperty of sourceProperties) { - if (isDiscriminantProperty(target, sourceProperty.escapedName)) { - if (result) { - result.push(sourceProperty); - continue; - } - result = [sourceProperty]; + function findDiscriminantProperties(sourceProperties: Symbol[], target: Type): Symbol[] | undefined { + let result: Symbol[] | undefined; + for (const sourceProperty of sourceProperties) { + if (isDiscriminantProperty(target, sourceProperty.escapedName)) { + if (result) { + result.push(sourceProperty); + continue; } + result = [sourceProperty]; } - return result; } + return result; + } - // Given a set of constituent types and a property name, create and return a map keyed by the literal - // types of the property by that name in each constituent type. No map is returned if some key property - // has a non-literal type or if less than 10 or less than 50% of the constituents have a unique key. - // Entries with duplicate keys have unknownType as the value. - function mapTypesByKeyProperty(types: Type[], name: __String) { - const map = new Map(); - let count = 0; - for (const type of types) { - if (type.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive)) { - const discriminant = getTypeOfPropertyOfType(type, name); - if (discriminant) { - if (!isLiteralType(discriminant)) { - return undefined; - } - let duplicate = false; - forEachType(discriminant, t => { - const id = getTypeId(getRegularTypeOfLiteralType(t)); - const existing = map.get(id); - if (!existing) { - map.set(id, type); - } - else if (existing !== unknownType) { - map.set(id, unknownType); - duplicate = true; - } - }); - if (!duplicate) count++; + // Given a set of constituent types and a property name, create and return a map keyed by the literal + // types of the property by that name in each constituent type. No map is returned if some key property + // has a non-literal type or if less than 10 or less than 50% of the constituents have a unique key. + // Entries with duplicate keys have unknownType as the value. + function mapTypesByKeyProperty(types: Type[], name: __String) { + const map = new Map(); + let count = 0; + for (const type of types) { + if (type.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive)) { + const discriminant = getTypeOfPropertyOfType(type, name); + if (discriminant) { + if (!isLiteralType(discriminant)) { + return undefined; } + let duplicate = false; + forEachType(discriminant, t => { + const id = getTypeId(getRegularTypeOfLiteralType(t)); + const existing = map.get(id); + if (!existing) { + map.set(id, type); + } + else if (existing !== unknownType) { + map.set(id, unknownType); + duplicate = true; + } + }); + if (!duplicate) count++; } } - return count >= 10 && count * 2 >= types.length ? map : undefined; } + return count >= 10 && count * 2 >= types.length ? map : undefined; + } - // Return the name of a discriminant property for which it was possible and feasible to construct a map of - // constituent types keyed by the literal types of the property by that name in each constituent type. - function getKeyPropertyName(unionType: UnionType): __String | undefined { - const types = unionType.types; - // We only construct maps for unions with many non-primitive constituents. - if (types.length < 10 || getObjectFlags(unionType) & ObjectFlags.PrimitiveUnion || - countWhere(types, t => !!(t.flags & (TypeFlags.Object | TypeFlags.InstantiableNonPrimitive))) < 10) { - return undefined; - } - if (unionType.keyPropertyName === undefined) { - // The candidate key property name is the name of the first property with a unit type in one of the - // constituent types. - const keyPropertyName = forEach(types, t => - t.flags & (TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) ? - forEach(getPropertiesOfType(t), p => isUnitType(getTypeOfSymbol(p)) ? p.escapedName : undefined) : - undefined); - const mapByKeyProperty = keyPropertyName && mapTypesByKeyProperty(types, keyPropertyName); - unionType.keyPropertyName = mapByKeyProperty ? keyPropertyName : "" as __String; - unionType.constituentMap = mapByKeyProperty; - } - return (unionType.keyPropertyName as string).length ? unionType.keyPropertyName : undefined; + // Return the name of a discriminant property for which it was possible and feasible to construct a map of + // constituent types keyed by the literal types of the property by that name in each constituent type. + function getKeyPropertyName(unionType: UnionType): __String | undefined { + const types = unionType.types; + // We only construct maps for unions with many non-primitive constituents. + if (types.length < 10 || getObjectFlags(unionType) & ObjectFlags.PrimitiveUnion || + countWhere(types, t => !!(t.flags & (TypeFlags.Object | TypeFlags.InstantiableNonPrimitive))) < 10) { + return undefined; } + if (unionType.keyPropertyName === undefined) { + // The candidate key property name is the name of the first property with a unit type in one of the + // constituent types. + const keyPropertyName = forEach(types, t => + t.flags & (TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) ? + forEach(getPropertiesOfType(t), p => isUnitType(getTypeOfSymbol(p)) ? p.escapedName : undefined) : + undefined); + const mapByKeyProperty = keyPropertyName && mapTypesByKeyProperty(types, keyPropertyName); + unionType.keyPropertyName = mapByKeyProperty ? keyPropertyName : "" as __String; + unionType.constituentMap = mapByKeyProperty; + } + return (unionType.keyPropertyName as string).length ? unionType.keyPropertyName : undefined; + } - // Given a union type for which getKeyPropertyName returned a non-undefined result, return the constituent - // that corresponds to the given key type for that property name. - function getConstituentTypeForKeyType(unionType: UnionType, keyType: Type) { - const result = unionType.constituentMap?.get(getTypeId(getRegularTypeOfLiteralType(keyType))); - return result !== unknownType ? result : undefined; - } + // Given a union type for which getKeyPropertyName returned a non-undefined result, return the constituent + // that corresponds to the given key type for that property name. + function getConstituentTypeForKeyType(unionType: UnionType, keyType: Type) { + const result = unionType.constituentMap?.get(getTypeId(getRegularTypeOfLiteralType(keyType))); + return result !== unknownType ? result : undefined; + } - function getMatchingUnionConstituentForType(unionType: UnionType, type: Type) { - const keyPropertyName = getKeyPropertyName(unionType); - const propType = keyPropertyName && getTypeOfPropertyOfType(type, keyPropertyName); - return propType && getConstituentTypeForKeyType(unionType, propType); - } + function getMatchingUnionConstituentForType(unionType: UnionType, type: Type) { + const keyPropertyName = getKeyPropertyName(unionType); + const propType = keyPropertyName && getTypeOfPropertyOfType(type, keyPropertyName); + return propType && getConstituentTypeForKeyType(unionType, propType); + } - function getMatchingUnionConstituentForObjectLiteral(unionType: UnionType, node: ObjectLiteralExpression) { - const keyPropertyName = getKeyPropertyName(unionType); - const propNode = keyPropertyName && find(node.properties, p => p.symbol && p.kind === SyntaxKind.PropertyAssignment && - p.symbol.escapedName === keyPropertyName && isPossiblyDiscriminantValue(p.initializer)); - const propType = propNode && getContextFreeTypeOfExpression((propNode as PropertyAssignment).initializer); - return propType && getConstituentTypeForKeyType(unionType, propType); - } + function getMatchingUnionConstituentForObjectLiteral(unionType: UnionType, node: ObjectLiteralExpression) { + const keyPropertyName = getKeyPropertyName(unionType); + const propNode = keyPropertyName && find(node.properties, p => p.symbol && p.kind === SyntaxKind.PropertyAssignment && + p.symbol.escapedName === keyPropertyName && isPossiblyDiscriminantValue(p.initializer)); + const propType = propNode && getContextFreeTypeOfExpression((propNode as PropertyAssignment).initializer); + return propType && getConstituentTypeForKeyType(unionType, propType); + } - function isOrContainsMatchingReference(source: Node, target: Node) { - return isMatchingReference(source, target) || containsMatchingReference(source, target); - } + function isOrContainsMatchingReference(source: Node, target: Node) { + return isMatchingReference(source, target) || containsMatchingReference(source, target); + } - function hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) { - if (expression.arguments) { - for (const argument of expression.arguments) { - if (isOrContainsMatchingReference(reference, argument)) { - return true; - } + function hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) { + if (expression.arguments) { + for (const argument of expression.arguments) { + if (isOrContainsMatchingReference(reference, argument)) { + return true; } } - if (expression.expression.kind === SyntaxKind.PropertyAccessExpression && - isOrContainsMatchingReference(reference, (expression.expression as PropertyAccessExpression).expression)) { - return true; - } - return false; } - - function getFlowNodeId(flow: FlowNode): number { - if (!flow.id || flow.id < 0) { - flow.id = nextFlowId; - nextFlowId++; - } - return flow.id; + if (expression.expression.kind === SyntaxKind.PropertyAccessExpression && + isOrContainsMatchingReference(reference, (expression.expression as PropertyAccessExpression).expression)) { + return true; } + return false; + } - function typeMaybeAssignableTo(source: Type, target: Type) { - if (!(source.flags & TypeFlags.Union)) { - return isTypeAssignableTo(source, target); - } - for (const t of (source as UnionType).types) { - if (isTypeAssignableTo(t, target)) { - return true; - } - } - return false; + function getFlowNodeId(flow: FlowNode): number { + if (!flow.id || flow.id < 0) { + flow.id = nextFlowId; + nextFlowId++; } + return flow.id; + } - // Remove those constituent types of declaredType to which no constituent type of assignedType is assignable. - // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean, - // we remove type string. - function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) { - if (declaredType !== assignedType) { - if (assignedType.flags & TypeFlags.Never) { - return assignedType; - } - let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); - if (assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType)) { - reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types - } - // Our crude heuristic produces an invalid result in some cases: see GH#26130. - // For now, when that happens, we give up and don't narrow at all. (This also - // means we'll never narrow for erroneous assignments where the assigned type - // is not assignable to the declared type.) - if (isTypeAssignableTo(assignedType, reducedType)) { - return reducedType; - } - } - return declaredType; + function typeMaybeAssignableTo(source: Type, target: Type) { + if (!(source.flags & TypeFlags.Union)) { + return isTypeAssignableTo(source, target); } - - function isFunctionObjectType(type: ObjectType): boolean { - // We do a quick check for a "bind" property before performing the more expensive subtype - // check. This gives us a quicker out in the common case where an object type is not a function. - const resolved = resolveStructuredTypeMembers(type); - return !!(resolved.callSignatures.length || resolved.constructSignatures.length || - resolved.members.get("bind" as __String) && isTypeSubtypeOf(type, globalFunctionType)); + for (const t of (source as UnionType).types) { + if (isTypeAssignableTo(t, target)) { + return true; + } } + return false; + } - function getTypeFacts(type: Type, ignoreObjects = false): TypeFacts { - const flags = type.flags; - if (flags & TypeFlags.String) { - return strictNullChecks ? TypeFacts.StringStrictFacts : TypeFacts.StringFacts; - } - if (flags & TypeFlags.StringLiteral) { - const isEmpty = (type as StringLiteralType).value === ""; - return strictNullChecks ? - isEmpty ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts : - isEmpty ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts; - } - if (flags & (TypeFlags.Number | TypeFlags.Enum)) { - return strictNullChecks ? TypeFacts.NumberStrictFacts : TypeFacts.NumberFacts; - } - if (flags & TypeFlags.NumberLiteral) { - const isZero = (type as NumberLiteralType).value === 0; - return strictNullChecks ? - isZero ? TypeFacts.ZeroNumberStrictFacts : TypeFacts.NonZeroNumberStrictFacts : - isZero ? TypeFacts.ZeroNumberFacts : TypeFacts.NonZeroNumberFacts; - } - if (flags & TypeFlags.BigInt) { - return strictNullChecks ? TypeFacts.BigIntStrictFacts : TypeFacts.BigIntFacts; - } - if (flags & TypeFlags.BigIntLiteral) { - const isZero = isZeroBigInt(type as BigIntLiteralType); - return strictNullChecks ? - isZero ? TypeFacts.ZeroBigIntStrictFacts : TypeFacts.NonZeroBigIntStrictFacts : - isZero ? TypeFacts.ZeroBigIntFacts : TypeFacts.NonZeroBigIntFacts; - } - if (flags & TypeFlags.Boolean) { - return strictNullChecks ? TypeFacts.BooleanStrictFacts : TypeFacts.BooleanFacts; - } - if (flags & TypeFlags.BooleanLike) { - return strictNullChecks ? - (type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts : - (type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts; + // Remove those constituent types of declaredType to which no constituent type of assignedType is assignable. + // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean, + // we remove type string. + function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) { + if (declaredType !== assignedType) { + if (assignedType.flags & TypeFlags.Never) { + return assignedType; } - if (flags & TypeFlags.Object && !ignoreObjects) { - return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ? - strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts : - isFunctionObjectType(type as ObjectType) ? - strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts : - strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; + let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); + if (assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType)) { + reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types } - if (flags & (TypeFlags.Void | TypeFlags.Undefined)) { - return TypeFacts.UndefinedFacts; + // Our crude heuristic produces an invalid result in some cases: see GH#26130. + // For now, when that happens, we give up and don't narrow at all. (This also + // means we'll never narrow for erroneous assignments where the assigned type + // is not assignable to the declared type.) + if (isTypeAssignableTo(assignedType, reducedType)) { + return reducedType; } - if (flags & TypeFlags.Null) { - return TypeFacts.NullFacts; - } - if (flags & TypeFlags.ESSymbolLike) { - return strictNullChecks ? TypeFacts.SymbolStrictFacts : TypeFacts.SymbolFacts; - } - if (flags & TypeFlags.NonPrimitive) { - return strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; - } - if (flags & TypeFlags.Never) { - return TypeFacts.None; - } - if (flags & TypeFlags.Instantiable) { - return !isPatternLiteralType(type) ? getTypeFacts(getBaseConstraintOfType(type) || unknownType, ignoreObjects) : - strictNullChecks ? TypeFacts.NonEmptyStringStrictFacts : TypeFacts.NonEmptyStringFacts; - } - if (flags & TypeFlags.Union) { - return reduceLeft((type as UnionType).types, (facts, t) => facts | getTypeFacts(t, ignoreObjects), TypeFacts.None); - } - if (flags & TypeFlags.Intersection) { - // When an intersection contains a primitive type we ignore object type constituents as they are - // presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type. - ignoreObjects ||= maybeTypeOfKind(type, TypeFlags.Primitive); - return reduceLeft((type as UnionType).types, (facts, t) => facts & getTypeFacts(t, ignoreObjects), TypeFacts.All); - } - return TypeFacts.All; - } - - function getTypeWithFacts(type: Type, include: TypeFacts) { - return filterType(type, t => (getTypeFacts(t) & include) !== 0); - } - - function getTypeWithDefault(type: Type, defaultExpression: Expression) { - return defaultExpression ? - getUnionType([getNonUndefinedType(type), getTypeOfExpression(defaultExpression)]) : - type; - } - - function getTypeOfDestructuredProperty(type: Type, name: PropertyName) { - const nameType = getLiteralTypeFromPropertyName(name); - if (!isTypeUsableAsPropertyName(nameType)) return errorType; - const text = getPropertyNameFromType(nameType); - return getTypeOfPropertyOfType(type, text) || includeUndefinedInIndexSignature(getApplicableIndexInfoForName(type, text)?.type) || errorType; - } - - function getTypeOfDestructuredArrayElement(type: Type, index: number) { - return everyType(type, isTupleLikeType) && getTupleElementType(type, index) || - includeUndefinedInIndexSignature(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined)) || - errorType; - } - - function includeUndefinedInIndexSignature(type: Type | undefined): Type | undefined { - if (!type) return type; - return compilerOptions.noUncheckedIndexedAccess ? - getUnionType([type, undefinedType]) : - type; - } - - function getTypeOfDestructuredSpreadExpression(type: Type) { - return createArrayType(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) || errorType); - } - - function getAssignedTypeOfBinaryExpression(node: BinaryExpression): Type { - const isDestructuringDefaultAssignment = - node.parent.kind === SyntaxKind.ArrayLiteralExpression && isDestructuringAssignmentTarget(node.parent) || - node.parent.kind === SyntaxKind.PropertyAssignment && isDestructuringAssignmentTarget(node.parent.parent); - return isDestructuringDefaultAssignment ? - getTypeWithDefault(getAssignedType(node), node.right) : - getTypeOfExpression(node.right); - } - - function isDestructuringAssignmentTarget(parent: Node) { - return parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent as BinaryExpression).left === parent || - parent.parent.kind === SyntaxKind.ForOfStatement && (parent.parent as ForOfStatement).initializer === parent; - } - - function getAssignedTypeOfArrayLiteralElement(node: ArrayLiteralExpression, element: Expression): Type { - return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element)); - } - - function getAssignedTypeOfSpreadExpression(node: SpreadElement): Type { - return getTypeOfDestructuredSpreadExpression(getAssignedType(node.parent as ArrayLiteralExpression)); } + return declaredType; + } - function getAssignedTypeOfPropertyAssignment(node: PropertyAssignment | ShorthandPropertyAssignment): Type { - return getTypeOfDestructuredProperty(getAssignedType(node.parent), node.name); - } + function isFunctionObjectType(type: ObjectType): boolean { + // We do a quick check for a "bind" property before performing the more expensive subtype + // check. This gives us a quicker out in the common case where an object type is not a function. + const resolved = resolveStructuredTypeMembers(type); + return !!(resolved.callSignatures.length || resolved.constructSignatures.length || + resolved.members.get("bind" as __String) && isTypeSubtypeOf(type, globalFunctionType)); + } - function getAssignedTypeOfShorthandPropertyAssignment(node: ShorthandPropertyAssignment): Type { - return getTypeWithDefault(getAssignedTypeOfPropertyAssignment(node), node.objectAssignmentInitializer!); + function getTypeFacts(type: Type, ignoreObjects = false): TypeFacts { + const flags = type.flags; + if (flags & TypeFlags.String) { + return strictNullChecks ? TypeFacts.StringStrictFacts : TypeFacts.StringFacts; } - - function getAssignedType(node: Expression): Type { - const { parent } = node; - switch (parent.kind) { - case SyntaxKind.ForInStatement: - return stringType; - case SyntaxKind.ForOfStatement: - return checkRightHandSideOfForOf(parent as ForOfStatement) || errorType; - case SyntaxKind.BinaryExpression: - return getAssignedTypeOfBinaryExpression(parent as BinaryExpression); - case SyntaxKind.DeleteExpression: - return undefinedType; - case SyntaxKind.ArrayLiteralExpression: - return getAssignedTypeOfArrayLiteralElement(parent as ArrayLiteralExpression, node); - case SyntaxKind.SpreadElement: - return getAssignedTypeOfSpreadExpression(parent as SpreadElement); - case SyntaxKind.PropertyAssignment: - return getAssignedTypeOfPropertyAssignment(parent as PropertyAssignment); - case SyntaxKind.ShorthandPropertyAssignment: - return getAssignedTypeOfShorthandPropertyAssignment(parent as ShorthandPropertyAssignment); - } - return errorType; + if (flags & TypeFlags.StringLiteral) { + const isEmpty = (type as StringLiteralType).value === ""; + return strictNullChecks ? + isEmpty ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts : + isEmpty ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts; } - - function getInitialTypeOfBindingElement(node: BindingElement): Type { - const pattern = node.parent; - const parentType = getInitialType(pattern.parent as VariableDeclaration | BindingElement); - const type = pattern.kind === SyntaxKind.ObjectBindingPattern ? - getTypeOfDestructuredProperty(parentType, node.propertyName || node.name as Identifier) : - !node.dotDotDotToken ? - getTypeOfDestructuredArrayElement(parentType, pattern.elements.indexOf(node)) : - getTypeOfDestructuredSpreadExpression(parentType); - return getTypeWithDefault(type, node.initializer!); + if (flags & (TypeFlags.Number | TypeFlags.Enum)) { + return strictNullChecks ? TypeFacts.NumberStrictFacts : TypeFacts.NumberFacts; } - - function getTypeOfInitializer(node: Expression) { - // Return the cached type if one is available. If the type of the variable was inferred - // from its initializer, we'll already have cached the type. Otherwise we compute it now - // without caching such that transient types are reflected. - const links = getNodeLinks(node); - return links.resolvedType || getTypeOfExpression(node); + if (flags & TypeFlags.NumberLiteral) { + const isZero = (type as NumberLiteralType).value === 0; + return strictNullChecks ? + isZero ? TypeFacts.ZeroNumberStrictFacts : TypeFacts.NonZeroNumberStrictFacts : + isZero ? TypeFacts.ZeroNumberFacts : TypeFacts.NonZeroNumberFacts; } - - function getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { - if (node.initializer) { - return getTypeOfInitializer(node.initializer); - } - if (node.parent.parent.kind === SyntaxKind.ForInStatement) { - return stringType; - } - if (node.parent.parent.kind === SyntaxKind.ForOfStatement) { - return checkRightHandSideOfForOf(node.parent.parent) || errorType; - } - return errorType; + if (flags & TypeFlags.BigInt) { + return strictNullChecks ? TypeFacts.BigIntStrictFacts : TypeFacts.BigIntFacts; } - - function getInitialType(node: VariableDeclaration | BindingElement) { - return node.kind === SyntaxKind.VariableDeclaration ? - getInitialTypeOfVariableDeclaration(node) : - getInitialTypeOfBindingElement(node); + if (flags & TypeFlags.BigIntLiteral) { + const isZero = isZeroBigInt(type as BigIntLiteralType); + return strictNullChecks ? + isZero ? TypeFacts.ZeroBigIntStrictFacts : TypeFacts.NonZeroBigIntStrictFacts : + isZero ? TypeFacts.ZeroBigIntFacts : TypeFacts.NonZeroBigIntFacts; } - - function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) { - return node.kind === SyntaxKind.VariableDeclaration && (node as VariableDeclaration).initializer && - isEmptyArrayLiteral((node as VariableDeclaration).initializer!) || - node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression && - isEmptyArrayLiteral((node.parent as BinaryExpression).right); + if (flags & TypeFlags.Boolean) { + return strictNullChecks ? TypeFacts.BooleanStrictFacts : TypeFacts.BooleanFacts; } - - function getReferenceCandidate(node: Expression): Expression { - switch (node.kind) { - case SyntaxKind.ParenthesizedExpression: - return getReferenceCandidate((node as ParenthesizedExpression).expression); - case SyntaxKind.BinaryExpression: - switch ((node as BinaryExpression).operatorToken.kind) { - case SyntaxKind.EqualsToken: - case SyntaxKind.BarBarEqualsToken: - case SyntaxKind.AmpersandAmpersandEqualsToken: - case SyntaxKind.QuestionQuestionEqualsToken: - return getReferenceCandidate((node as BinaryExpression).left); - case SyntaxKind.CommaToken: - return getReferenceCandidate((node as BinaryExpression).right); - } - } - return node; + if (flags & TypeFlags.BooleanLike) { + return strictNullChecks ? + (type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts : + (type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts; } - - function getReferenceRoot(node: Node): Node { - const { parent } = node; - return parent.kind === SyntaxKind.ParenthesizedExpression || - parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && (parent as BinaryExpression).left === node || - parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken && (parent as BinaryExpression).right === node ? - getReferenceRoot(parent) : node; + if (flags & TypeFlags.Object && !ignoreObjects) { + return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ? + strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts : + isFunctionObjectType(type as ObjectType) ? + strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts : + strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; } - - function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { - if (clause.kind === SyntaxKind.CaseClause) { - return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression)); - } - return neverType; + if (flags & (TypeFlags.Void | TypeFlags.Undefined)) { + return TypeFacts.UndefinedFacts; } - - function getSwitchClauseTypes(switchStatement: SwitchStatement): Type[] { - const links = getNodeLinks(switchStatement); - if (!links.switchTypes) { - links.switchTypes = []; - for (const clause of switchStatement.caseBlock.clauses) { - links.switchTypes.push(getTypeOfSwitchClause(clause)); - } - } - return links.switchTypes; + if (flags & TypeFlags.Null) { + return TypeFacts.NullFacts; } - - // Get the types from all cases in a switch on `typeof`. An - // `undefined` element denotes an explicit `default` clause. - function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: false): string[]; - function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: boolean): (string | undefined)[]; - function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: boolean): (string | undefined)[] { - const witnesses: (string | undefined)[] = []; - for (const clause of switchStatement.caseBlock.clauses) { - if (clause.kind === SyntaxKind.CaseClause) { - if (isStringLiteralLike(clause.expression)) { - witnesses.push(clause.expression.text); - continue; - } - return emptyArray; - } - if (retainDefault) witnesses.push(/*explicitDefaultStatement*/ undefined); - } - return witnesses; + if (flags & TypeFlags.ESSymbolLike) { + return strictNullChecks ? TypeFacts.SymbolStrictFacts : TypeFacts.SymbolFacts; } - - function eachTypeContainedIn(source: Type, types: Type[]) { - return source.flags & TypeFlags.Union ? !forEach((source as UnionType).types, t => !contains(types, t)) : contains(types, source); + if (flags & TypeFlags.NonPrimitive) { + return strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; } - - function isTypeSubsetOf(source: Type, target: Type) { - return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType); + if (flags & TypeFlags.Never) { + return TypeFacts.None; } - - function isTypeSubsetOfUnion(source: Type, target: UnionType) { - if (source.flags & TypeFlags.Union) { - for (const t of (source as UnionType).types) { - if (!containsType(target.types, t)) { - return false; - } - } - return true; - } - if (source.flags & TypeFlags.EnumLiteral && getBaseTypeOfEnumLiteralType(source as LiteralType) === target) { - return true; - } - return containsType(target.types, source); + if (flags & TypeFlags.Instantiable) { + return !isPatternLiteralType(type) ? getTypeFacts(getBaseConstraintOfType(type) || unknownType, ignoreObjects) : + strictNullChecks ? TypeFacts.NonEmptyStringStrictFacts : TypeFacts.NonEmptyStringFacts; } - - function forEachType(type: Type, f: (t: Type) => T | undefined): T | undefined { - return type.flags & TypeFlags.Union ? forEach((type as UnionType).types, f) : f(type); + if (flags & TypeFlags.Union) { + return reduceLeft((type as UnionType).types, (facts, t) => facts | getTypeFacts(t, ignoreObjects), TypeFacts.None); } - - function someType(type: Type, f: (t: Type) => boolean): boolean { - return type.flags & TypeFlags.Union ? some((type as UnionType).types, f) : f(type); + if (flags & TypeFlags.Intersection) { + // When an intersection contains a primitive type we ignore object type constituents as they are + // presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type. + ignoreObjects ||= maybeTypeOfKind(type, TypeFlags.Primitive); + return reduceLeft((type as UnionType).types, (facts, t) => facts & getTypeFacts(t, ignoreObjects), TypeFacts.All); } + return TypeFacts.All; + } - function everyType(type: Type, f: (t: Type) => boolean): boolean { - return type.flags & TypeFlags.Union ? every((type as UnionType).types, f) : f(type); - } + function getTypeWithFacts(type: Type, include: TypeFacts) { + return filterType(type, t => (getTypeFacts(t) & include) !== 0); + } - function everyContainedType(type: Type, f: (t: Type) => boolean): boolean { - return type.flags & TypeFlags.UnionOrIntersection ? every((type as UnionOrIntersectionType).types, f) : f(type); - } + function getTypeWithDefault(type: Type, defaultExpression: Expression) { + return defaultExpression ? + getUnionType([getNonUndefinedType(type), getTypeOfExpression(defaultExpression)]) : + type; + } - function filterType(type: Type, f: (t: Type) => boolean): Type { - if (type.flags & TypeFlags.Union) { - const types = (type as UnionType).types; - const filtered = filter(types, f); - if (filtered === types) { - return type; - } - const origin = (type as UnionType).origin; - let newOrigin: Type | undefined; - if (origin && origin.flags & TypeFlags.Union) { - // If the origin type is a (denormalized) union type, filter its non-union constituents. If that ends - // up removing a smaller number of types than in the normalized constituent set (meaning some of the - // filtered types are within nested unions in the origin), then we can't construct a new origin type. - // Otherwise, if we have exactly one type left in the origin set, return that as the filtered type. - // Otherwise, construct a new filtered origin type. - const originTypes = (origin as UnionType).types; - const originFiltered = filter(originTypes, t => !!(t.flags & TypeFlags.Union) || f(t)); - if (originTypes.length - originFiltered.length === types.length - filtered.length) { - if (originFiltered.length === 1) { - return originFiltered[0]; - } - newOrigin = createOriginUnionOrIntersectionType(TypeFlags.Union, originFiltered); - } - } - return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin); - } - return type.flags & TypeFlags.Never || f(type) ? type : neverType; - } + function getTypeOfDestructuredProperty(type: Type, name: PropertyName) { + const nameType = getLiteralTypeFromPropertyName(name); + if (!isTypeUsableAsPropertyName(nameType)) return errorType; + const text = getPropertyNameFromType(nameType); + return getTypeOfPropertyOfType(type, text) || includeUndefinedInIndexSignature(getApplicableIndexInfoForName(type, text)?.type) || errorType; + } - function removeType(type: Type, targetType: Type) { - return filterType(type, t => t !== targetType); - } + function getTypeOfDestructuredArrayElement(type: Type, index: number) { + return everyType(type, isTupleLikeType) && getTupleElementType(type, index) || + includeUndefinedInIndexSignature(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined)) || + errorType; + } - function countTypes(type: Type) { - return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1; - } + function includeUndefinedInIndexSignature(type: Type | undefined): Type | undefined { + if (!type) return type; + return compilerOptions.noUncheckedIndexedAccess ? + getUnionType([type, undefinedType]) : + type; + } - // Apply a mapping function to a type and return the resulting type. If the source type - // is a union type, the mapping function is applied to each constituent type and a union - // of the resulting types is returned. - function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined { - if (type.flags & TypeFlags.Never) { - return type; - } - if (!(type.flags & TypeFlags.Union)) { - return mapper(type); - } - const origin = (type as UnionType).origin; - const types = origin && origin.flags & TypeFlags.Union ? (origin as UnionType).types : (type as UnionType).types; - let mappedTypes: Type[] | undefined; - let changed = false; - for (const t of types) { - const mapped = t.flags & TypeFlags.Union ? mapType(t, mapper, noReductions) : mapper(t); - changed ||= t !== mapped; - if (mapped) { - if (!mappedTypes) { - mappedTypes = [mapped]; - } - else { - mappedTypes.push(mapped); - } - } - } - return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type; - } + function getTypeOfDestructuredSpreadExpression(type: Type) { + return createArrayType(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) || errorType); + } - function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { - return type.flags & TypeFlags.Union && aliasSymbol ? - getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments) : - mapType(type, mapper); - } + function getAssignedTypeOfBinaryExpression(node: BinaryExpression): Type { + const isDestructuringDefaultAssignment = + node.parent.kind === SyntaxKind.ArrayLiteralExpression && isDestructuringAssignmentTarget(node.parent) || + node.parent.kind === SyntaxKind.PropertyAssignment && isDestructuringAssignmentTarget(node.parent.parent); + return isDestructuringDefaultAssignment ? + getTypeWithDefault(getAssignedType(node), node.right) : + getTypeOfExpression(node.right); + } - function getConstituentCount(type: Type) { - return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1; - } + function isDestructuringAssignmentTarget(parent: Node) { + return parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent as BinaryExpression).left === parent || + parent.parent.kind === SyntaxKind.ForOfStatement && (parent.parent as ForOfStatement).initializer === parent; + } - function extractTypesOfKind(type: Type, kind: TypeFlags) { - return filterType(type, t => (t.flags & kind) !== 0); - } + function getAssignedTypeOfArrayLiteralElement(node: ArrayLiteralExpression, element: Expression): Type { + return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element)); + } - // Return a new type in which occurrences of the string, number and bigint primitives and placeholder template - // literal types in typeWithPrimitives have been replaced with occurrences of compatible and more specific types - // from typeWithLiterals. This is essentially a limited form of intersection between the two types. We avoid a - // true intersection because it is more costly and, when applied to union types, generates a large number of - // types we don't actually care about. - function replacePrimitivesWithLiterals(typeWithPrimitives: Type, typeWithLiterals: Type) { - if (maybeTypeOfKind(typeWithPrimitives, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.Number | TypeFlags.BigInt) && - maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.NumberLiteral | TypeFlags.BigIntLiteral)) { - return mapType(typeWithPrimitives, t => - t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) : - isPatternLiteralType(t) && !maybeTypeOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? extractTypesOfKind(typeWithLiterals, TypeFlags.StringLiteral) : - t.flags & TypeFlags.Number ? extractTypesOfKind(typeWithLiterals, TypeFlags.Number | TypeFlags.NumberLiteral) : - t.flags & TypeFlags.BigInt ? extractTypesOfKind(typeWithLiterals, TypeFlags.BigInt | TypeFlags.BigIntLiteral) : t); - } - return typeWithPrimitives; - } + function getAssignedTypeOfSpreadExpression(node: SpreadElement): Type { + return getTypeOfDestructuredSpreadExpression(getAssignedType(node.parent as ArrayLiteralExpression)); + } - function isIncomplete(flowType: FlowType) { - return flowType.flags === 0; - } + function getAssignedTypeOfPropertyAssignment(node: PropertyAssignment | ShorthandPropertyAssignment): Type { + return getTypeOfDestructuredProperty(getAssignedType(node.parent), node.name); + } - function getTypeFromFlowType(flowType: FlowType) { - return flowType.flags === 0 ? (flowType as IncompleteType).type : flowType as Type; - } + function getAssignedTypeOfShorthandPropertyAssignment(node: ShorthandPropertyAssignment): Type { + return getTypeWithDefault(getAssignedTypeOfPropertyAssignment(node), node.objectAssignmentInitializer!); + } - function createFlowType(type: Type, incomplete: boolean): FlowType { - return incomplete ? { flags: 0, type: type.flags & TypeFlags.Never ? silentNeverType : type } : type; - } + function getAssignedType(node: Expression): Type { + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.ForInStatement: + return stringType; + case SyntaxKind.ForOfStatement: + return checkRightHandSideOfForOf(parent as ForOfStatement) || errorType; + case SyntaxKind.BinaryExpression: + return getAssignedTypeOfBinaryExpression(parent as BinaryExpression); + case SyntaxKind.DeleteExpression: + return undefinedType; + case SyntaxKind.ArrayLiteralExpression: + return getAssignedTypeOfArrayLiteralElement(parent as ArrayLiteralExpression, node); + case SyntaxKind.SpreadElement: + return getAssignedTypeOfSpreadExpression(parent as SpreadElement); + case SyntaxKind.PropertyAssignment: + return getAssignedTypeOfPropertyAssignment(parent as PropertyAssignment); + case SyntaxKind.ShorthandPropertyAssignment: + return getAssignedTypeOfShorthandPropertyAssignment(parent as ShorthandPropertyAssignment); + } + return errorType; + } - // An evolving array type tracks the element types that have so far been seen in an - // 'x.push(value)' or 'x[n] = value' operation along the control flow graph. Evolving - // array types are ultimately converted into manifest array types (using getFinalArrayType) - // and never escape the getFlowTypeOfReference function. - function createEvolvingArrayType(elementType: Type): EvolvingArrayType { - const result = createObjectType(ObjectFlags.EvolvingArray) as EvolvingArrayType; - result.elementType = elementType; - return result; - } + function getInitialTypeOfBindingElement(node: BindingElement): Type { + const pattern = node.parent; + const parentType = getInitialType(pattern.parent as VariableDeclaration | BindingElement); + const type = pattern.kind === SyntaxKind.ObjectBindingPattern ? + getTypeOfDestructuredProperty(parentType, node.propertyName || node.name as Identifier) : + !node.dotDotDotToken ? + getTypeOfDestructuredArrayElement(parentType, pattern.elements.indexOf(node)) : + getTypeOfDestructuredSpreadExpression(parentType); + return getTypeWithDefault(type, node.initializer!); + } - function getEvolvingArrayType(elementType: Type): EvolvingArrayType { - return evolvingArrayTypes[elementType.id] || (evolvingArrayTypes[elementType.id] = createEvolvingArrayType(elementType)); - } + function getTypeOfInitializer(node: Expression) { + // Return the cached type if one is available. If the type of the variable was inferred + // from its initializer, we'll already have cached the type. Otherwise we compute it now + // without caching such that transient types are reflected. + const links = getNodeLinks(node); + return links.resolvedType || getTypeOfExpression(node); + } - // When adding evolving array element types we do not perform subtype reduction. Instead, - // we defer subtype reduction until the evolving array type is finalized into a manifest - // array type. - function addEvolvingArrayElementType(evolvingArrayType: EvolvingArrayType, node: Expression): EvolvingArrayType { - const elementType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(getContextFreeTypeOfExpression(node))); - return isTypeSubsetOf(elementType, evolvingArrayType.elementType) ? evolvingArrayType : getEvolvingArrayType(getUnionType([evolvingArrayType.elementType, elementType])); + function getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { + if (node.initializer) { + return getTypeOfInitializer(node.initializer); } - - function createFinalArrayType(elementType: Type) { - return elementType.flags & TypeFlags.Never ? - autoArrayType : - createArrayType(elementType.flags & TypeFlags.Union ? - getUnionType((elementType as UnionType).types, UnionReduction.Subtype) : - elementType); + if (node.parent.parent.kind === SyntaxKind.ForInStatement) { + return stringType; } - - // We perform subtype reduction upon obtaining the final array type from an evolving array type. - function getFinalArrayType(evolvingArrayType: EvolvingArrayType): Type { - return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createFinalArrayType(evolvingArrayType.elementType)); + if (node.parent.parent.kind === SyntaxKind.ForOfStatement) { + return checkRightHandSideOfForOf(node.parent.parent) || errorType; } + return errorType; + } - function finalizeEvolvingArrayType(type: Type): Type { - return getObjectFlags(type) & ObjectFlags.EvolvingArray ? getFinalArrayType(type as EvolvingArrayType) : type; - } + function getInitialType(node: VariableDeclaration | BindingElement) { + return node.kind === SyntaxKind.VariableDeclaration ? + getInitialTypeOfVariableDeclaration(node) : + getInitialTypeOfBindingElement(node); + } - function getElementTypeOfEvolvingArrayType(type: Type) { - return getObjectFlags(type) & ObjectFlags.EvolvingArray ? (type as EvolvingArrayType).elementType : neverType; - } + function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) { + return node.kind === SyntaxKind.VariableDeclaration && (node as VariableDeclaration).initializer && + isEmptyArrayLiteral((node as VariableDeclaration).initializer!) || + node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression && + isEmptyArrayLiteral((node.parent as BinaryExpression).right); + } - function isEvolvingArrayTypeList(types: Type[]) { - let hasEvolvingArrayType = false; - for (const t of types) { - if (!(t.flags & TypeFlags.Never)) { - if (!(getObjectFlags(t) & ObjectFlags.EvolvingArray)) { - return false; - } - hasEvolvingArrayType = true; + function getReferenceCandidate(node: Expression): Expression { + switch (node.kind) { + case SyntaxKind.ParenthesizedExpression: + return getReferenceCandidate((node as ParenthesizedExpression).expression); + case SyntaxKind.BinaryExpression: + switch ((node as BinaryExpression).operatorToken.kind) { + case SyntaxKind.EqualsToken: + case SyntaxKind.BarBarEqualsToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: + case SyntaxKind.QuestionQuestionEqualsToken: + return getReferenceCandidate((node as BinaryExpression).left); + case SyntaxKind.CommaToken: + return getReferenceCandidate((node as BinaryExpression).right); } - } - return hasEvolvingArrayType; - } - - // Return true if the given node is 'x' in an 'x.length', x.push(value)', 'x.unshift(value)' or - // 'x[n] = value' operation, where 'n' is an expression of type any, undefined, or a number-like type. - function isEvolvingArrayOperationTarget(node: Node) { - const root = getReferenceRoot(node); - const parent = root.parent; - const isLengthPushOrUnshift = isPropertyAccessExpression(parent) && ( - parent.name.escapedText === "length" || - parent.parent.kind === SyntaxKind.CallExpression - && isIdentifier(parent.name) - && isPushOrUnshiftIdentifier(parent.name)); - const isElementAssignment = parent.kind === SyntaxKind.ElementAccessExpression && - (parent as ElementAccessExpression).expression === root && - parent.parent.kind === SyntaxKind.BinaryExpression && - (parent.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && - (parent.parent as BinaryExpression).left === parent && - !isAssignmentTarget(parent.parent) && - isTypeAssignableToKind(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression), TypeFlags.NumberLike); - return isLengthPushOrUnshift || isElementAssignment; } + return node; + } - function isDeclarationWithExplicitTypeAnnotation(node: Declaration) { - return (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isParameter(node)) && - !!(getEffectiveTypeAnnotationNode(node) || - isInJSFile(node) && hasInitializer(node) && node.initializer && isFunctionExpressionOrArrowFunction(node.initializer) && getEffectiveReturnTypeNode(node.initializer)); - } + function getReferenceRoot(node: Node): Node { + const { parent } = node; + return parent.kind === SyntaxKind.ParenthesizedExpression || + parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && (parent as BinaryExpression).left === node || + parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken && (parent as BinaryExpression).right === node ? + getReferenceRoot(parent) : node; + } - function getExplicitTypeOfSymbol(symbol: Symbol, diagnostic?: Diagnostic) { - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule)) { - return getTypeOfSymbol(symbol); - } - if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { - if (getCheckFlags(symbol) & CheckFlags.Mapped) { - const origin = (symbol as MappedSymbol).syntheticOrigin; - if (origin && getExplicitTypeOfSymbol(origin)) { - return getTypeOfSymbol(symbol); - } - } - const declaration = symbol.valueDeclaration; - if (declaration) { - if (isDeclarationWithExplicitTypeAnnotation(declaration)) { - return getTypeOfSymbol(symbol); - } - if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { - const statement = declaration.parent.parent; - const expressionType = getTypeOfDottedName(statement.expression, /*diagnostic*/ undefined); - if (expressionType) { - const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; - return checkIteratedTypeOrElementType(use, expressionType, undefinedType, /*errorNode*/ undefined); - } - } - if (diagnostic) { - addRelatedInfo(diagnostic, createDiagnosticForNode(declaration, Diagnostics._0_needs_an_explicit_type_annotation, symbolToString(symbol))); - } - } - } + function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { + if (clause.kind === SyntaxKind.CaseClause) { + return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression)); } + return neverType; + } - // We require the dotted function name in an assertion expression to be comprised of identifiers - // that reference function, method, class or value module symbols; or variable, property or - // parameter symbols with declarations that have explicit type annotations. Such references are - // resolvable with no possibility of triggering circularities in control flow analysis. - function getTypeOfDottedName(node: Expression, diagnostic: Diagnostic | undefined): Type | undefined { - if (!(node.flags & NodeFlags.InWithStatement)) { - switch (node.kind) { - case SyntaxKind.Identifier: - const symbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(node as Identifier)); - return getExplicitTypeOfSymbol(symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol, diagnostic); - case SyntaxKind.ThisKeyword: - return getExplicitThisType(node); - case SyntaxKind.SuperKeyword: - return checkSuperExpression(node); - case SyntaxKind.PropertyAccessExpression: { - const type = getTypeOfDottedName((node as PropertyAccessExpression).expression, diagnostic); - if (type) { - const name = (node as PropertyAccessExpression).name; - let prop: Symbol | undefined; - if (isPrivateIdentifier(name)) { - if (!type.symbol) { - return undefined; - } - prop = getPropertyOfType(type, getSymbolNameForPrivateIdentifier(type.symbol, name.escapedText)); - } - else { - prop = getPropertyOfType(type, name.escapedText); - } - return prop && getExplicitTypeOfSymbol(prop, diagnostic); - } - return undefined; - } - case SyntaxKind.ParenthesizedExpression: - return getTypeOfDottedName((node as ParenthesizedExpression).expression, diagnostic); - } + function getSwitchClauseTypes(switchStatement: SwitchStatement): Type[] { + const links = getNodeLinks(switchStatement); + if (!links.switchTypes) { + links.switchTypes = []; + for (const clause of switchStatement.caseBlock.clauses) { + links.switchTypes.push(getTypeOfSwitchClause(clause)); } } + return links.switchTypes; + } - function getEffectsSignature(node: CallExpression) { - const links = getNodeLinks(node); - let signature = links.effectsSignature; - if (signature === undefined) { - // A call expression parented by an expression statement is a potential assertion. Other call - // expressions are potential type predicate function calls. In order to avoid triggering - // circularities in control flow analysis, we use getTypeOfDottedName when resolving the call - // target expression of an assertion. - let funcType: Type | undefined; - if (node.parent.kind === SyntaxKind.ExpressionStatement) { - funcType = getTypeOfDottedName(node.expression, /*diagnostic*/ undefined); - } - else if (node.expression.kind !== SyntaxKind.SuperKeyword) { - if (isOptionalChain(node)) { - funcType = checkNonNullType( - getOptionalExpressionType(checkExpression(node.expression), node.expression), - node.expression - ); - } - else { - funcType = checkNonNullExpression(node.expression); - } + // Get the types from all cases in a switch on `typeof`. An + // `undefined` element denotes an explicit `default` clause. + function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: false): string[]; + function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: boolean): (string | undefined)[]; + function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: boolean): (string | undefined)[] { + const witnesses: (string | undefined)[] = []; + for (const clause of switchStatement.caseBlock.clauses) { + if (clause.kind === SyntaxKind.CaseClause) { + if (isStringLiteralLike(clause.expression)) { + witnesses.push(clause.expression.text); + continue; } - const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call); - const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] : - some(signatures, hasTypePredicateOrNeverReturnType) ? getResolvedSignature(node) : - undefined; - signature = links.effectsSignature = candidate && hasTypePredicateOrNeverReturnType(candidate) ? candidate : unknownSignature; - } - return signature === unknownSignature ? undefined : signature; - } - - function hasTypePredicateOrNeverReturnType(signature: Signature) { - return !!(getTypePredicateOfSignature(signature) || - signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never); - } - - function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) { - if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) { - return callExpression.arguments[predicate.parameterIndex]; + return emptyArray; } - const invokedExpression = skipParentheses(callExpression.expression); - return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined; - } - - function reportFlowControlError(node: Node) { - const block = findAncestor(node, isFunctionOrModuleBlock) as Block | ModuleBlock | SourceFile; - const sourceFile = getSourceFileOfNode(node); - const span = getSpanOfTokenAtPosition(sourceFile, block.statements.pos); - diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_containing_function_or_module_body_is_too_large_for_control_flow_analysis)); - } - - function isReachableFlowNode(flow: FlowNode) { - const result = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ false); - lastFlowNode = flow; - lastFlowNodeReachable = result; - return result; + if (retainDefault) witnesses.push(/*explicitDefaultStatement*/ undefined); } + return witnesses; + } - function isFalseExpression(expr: Expression): boolean { - const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); - return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && ( - (node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isFalseExpression((node as BinaryExpression).left) || isFalseExpression((node as BinaryExpression).right)) || - (node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken && isFalseExpression((node as BinaryExpression).left) && isFalseExpression((node as BinaryExpression).right)); - } + function eachTypeContainedIn(source: Type, types: Type[]) { + return source.flags & TypeFlags.Union ? !forEach((source as UnionType).types, t => !contains(types, t)) : contains(types, source); + } - function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean { - while (true) { - if (flow === lastFlowNode) { - return lastFlowNodeReachable; - } - const flags = flow.flags; - if (flags & FlowFlags.Shared) { - if (!noCacheCheck) { - const id = getFlowNodeId(flow); - const reachable = flowNodeReachable[id]; - return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ true)); - } - noCacheCheck = false; - } - if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation)) { - flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation).antecedent; - } - else if (flags & FlowFlags.Call) { - const signature = getEffectsSignature((flow as FlowCall).node); - if (signature) { - const predicate = getTypePredicateOfSignature(signature); - if (predicate && predicate.kind === TypePredicateKind.AssertsIdentifier && !predicate.type) { - const predicateArgument = (flow as FlowCall).node.arguments[predicate.parameterIndex]; - if (predicateArgument && isFalseExpression(predicateArgument)) { - return false; - } - } - if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { - return false; - } - } - flow = (flow as FlowCall).antecedent; - } - else if (flags & FlowFlags.BranchLabel) { - // A branching point is reachable if any branch is reachable. - return some((flow as FlowLabel).antecedents, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); - } - else if (flags & FlowFlags.LoopLabel) { - const antecedents = (flow as FlowLabel).antecedents; - if (antecedents === undefined || antecedents.length === 0) { - return false; - } - // A loop is reachable if the control flow path that leads to the top is reachable. - flow = antecedents[0]; - } - else if (flags & FlowFlags.SwitchClause) { - // The control flow path representing an unmatched value in a switch statement with - // no default clause is unreachable if the switch statement is exhaustive. - if ((flow as FlowSwitchClause).clauseStart === (flow as FlowSwitchClause).clauseEnd && isExhaustiveSwitchStatement((flow as FlowSwitchClause).switchStatement)) { - return false; - } - flow = (flow as FlowSwitchClause).antecedent; - } - else if (flags & FlowFlags.ReduceLabel) { - // Cache is unreliable once we start adjusting labels - lastFlowNode = undefined; - const target = (flow as FlowReduceLabel).target; - const saveAntecedents = target.antecedents; - target.antecedents = (flow as FlowReduceLabel).antecedents; - const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); - target.antecedents = saveAntecedents; - return result; - } - else { - return !(flags & FlowFlags.Unreachable); - } - } - } + function isTypeSubsetOf(source: Type, target: Type) { + return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType); + } - // Return true if the given flow node is preceded by a 'super(...)' call in every possible code path - // leading to the node. - function isPostSuperFlowNode(flow: FlowNode, noCacheCheck: boolean): boolean { - while (true) { - const flags = flow.flags; - if (flags & FlowFlags.Shared) { - if (!noCacheCheck) { - const id = getFlowNodeId(flow); - const postSuper = flowNodePostSuper[id]; - return postSuper !== undefined ? postSuper : (flowNodePostSuper[id] = isPostSuperFlowNode(flow, /*noCacheCheck*/ true)); - } - noCacheCheck = false; - } - if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.SwitchClause)) { - flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation | FlowSwitchClause).antecedent; + function isTypeSubsetOfUnion(source: Type, target: UnionType) { + if (source.flags & TypeFlags.Union) { + for (const t of (source as UnionType).types) { + if (!containsType(target.types, t)) { + return false; } - else if (flags & FlowFlags.Call) { - if ((flow as FlowCall).node.expression.kind === SyntaxKind.SuperKeyword) { - return true; - } - flow = (flow as FlowCall).antecedent; - } - else if (flags & FlowFlags.BranchLabel) { - // A branching point is post-super if every branch is post-super. - return every((flow as FlowLabel).antecedents, f => isPostSuperFlowNode(f, /*noCacheCheck*/ false)); - } - else if (flags & FlowFlags.LoopLabel) { - // A loop is post-super if the control flow path that leads to the top is post-super. - flow = (flow as FlowLabel).antecedents![0]; - } - else if (flags & FlowFlags.ReduceLabel) { - const target = (flow as FlowReduceLabel).target; - const saveAntecedents = target.antecedents; - target.antecedents = (flow as FlowReduceLabel).antecedents; - const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); - target.antecedents = saveAntecedents; - return result; - } - else { - // Unreachable nodes are considered post-super to silence errors - return !!(flags & FlowFlags.Unreachable); - } - } - } - - function isConstantReference(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.Identifier: { - const symbol = getResolvedSymbol(node as Identifier); - return isConstVariable(symbol) || isParameterOrCatchClauseVariable(symbol) && !isSymbolAssigned(symbol); - } - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - // The resolvedSymbol property is initialized by checkPropertyAccess or checkElementAccess before we get here. - return isConstantReference((node as AccessExpression).expression) && isReadonlySymbol(getNodeLinks(node).resolvedSymbol || unknownSymbol); - } - return false; - } - - function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = reference.flowNode) { - let key: string | undefined; - let isKeySet = false; - let flowDepth = 0; - if (flowAnalysisDisabled) { - return errorType; - } - if (!flowNode) { - return declaredType; - } - flowInvocationCount++; - const sharedFlowStart = sharedFlowCount; - const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode)); - sharedFlowCount = sharedFlowStart; - // When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation, - // we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations - // on empty arrays are possible without implicit any errors and new element types can be inferred without - // type mismatch errors. - const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType); - if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && !(resultType.flags & TypeFlags.Never) && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { - return declaredType; - } - // The non-null unknown type should never escape control flow analysis. - return resultType === nonNullUnknownType ? unknownType : resultType; - - function getOrSetCacheKey() { - if (isKeySet) { - return key; - } - isKeySet = true; - return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer); - } - - function getTypeAtFlowNode(flow: FlowNode): FlowType { - if (flowDepth === 2000) { - // We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error - // and disable further control flow analysis in the containing function or module body. - tracing?.instant(tracing.Phase.CheckTypes, "getTypeAtFlowNode_DepthLimit", { flowId: flow.id }); - flowAnalysisDisabled = true; - reportFlowControlError(reference); - return errorType; - } - flowDepth++; - let sharedFlow: FlowNode | undefined; - while (true) { - const flags = flow.flags; - if (flags & FlowFlags.Shared) { - // We cache results of flow type resolution for shared nodes that were previously visited in - // the same getFlowTypeOfReference invocation. A node is considered shared when it is the - // antecedent of more than one node. - for (let i = sharedFlowStart; i < sharedFlowCount; i++) { - if (sharedFlowNodes[i] === flow) { - flowDepth--; - return sharedFlowTypes[i]; - } - } - sharedFlow = flow; - } - let type: FlowType | undefined; - if (flags & FlowFlags.Assignment) { - type = getTypeAtFlowAssignment(flow as FlowAssignment); - if (!type) { - flow = (flow as FlowAssignment).antecedent; - continue; - } - } - else if (flags & FlowFlags.Call) { - type = getTypeAtFlowCall(flow as FlowCall); - if (!type) { - flow = (flow as FlowCall).antecedent; - continue; - } - } - else if (flags & FlowFlags.Condition) { - type = getTypeAtFlowCondition(flow as FlowCondition); - } - else if (flags & FlowFlags.SwitchClause) { - type = getTypeAtSwitchClause(flow as FlowSwitchClause); - } - else if (flags & FlowFlags.Label) { - if ((flow as FlowLabel).antecedents!.length === 1) { - flow = (flow as FlowLabel).antecedents![0]; - continue; - } - type = flags & FlowFlags.BranchLabel ? - getTypeAtFlowBranchLabel(flow as FlowLabel) : - getTypeAtFlowLoopLabel(flow as FlowLabel); - } - else if (flags & FlowFlags.ArrayMutation) { - type = getTypeAtFlowArrayMutation(flow as FlowArrayMutation); - if (!type) { - flow = (flow as FlowArrayMutation).antecedent; - continue; - } - } - else if (flags & FlowFlags.ReduceLabel) { - const target = (flow as FlowReduceLabel).target; - const saveAntecedents = target.antecedents; - target.antecedents = (flow as FlowReduceLabel).antecedents; - type = getTypeAtFlowNode((flow as FlowReduceLabel).antecedent); - target.antecedents = saveAntecedents; - } - else if (flags & FlowFlags.Start) { - // Check if we should continue with the control flow of the containing function. - const container = (flow as FlowStart).node; - if (container && container !== flowContainer && - reference.kind !== SyntaxKind.PropertyAccessExpression && - reference.kind !== SyntaxKind.ElementAccessExpression && - reference.kind !== SyntaxKind.ThisKeyword) { - flow = container.flowNode!; - continue; - } - // At the top of the flow we have the initial type. - type = initialType; - } - else { - // Unreachable code errors are reported in the binding phase. Here we - // simply return the non-auto declared type to reduce follow-on errors. - type = convertAutoToAny(declaredType); - } - if (sharedFlow) { - // Record visited node and the associated type in the cache. - sharedFlowNodes[sharedFlowCount] = sharedFlow; - sharedFlowTypes[sharedFlowCount] = type; - sharedFlowCount++; - } - flowDepth--; - return type; - } - } - - function getInitialOrAssignedType(flow: FlowAssignment) { - const node = flow.node; - return getNarrowableTypeForReference(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? - getInitialType(node as VariableDeclaration | BindingElement) : - getAssignedType(node), reference); - } - - function getTypeAtFlowAssignment(flow: FlowAssignment) { - const node = flow.node; - // Assignments only narrow the computed type if the declared type is a union type. Thus, we - // only need to evaluate the assigned type if the declared type is a union type. - if (isMatchingReference(reference, node)) { - if (!isReachableFlowNode(flow)) { - return unreachableNeverType; - } - if (getAssignmentTargetKind(node) === AssignmentKind.Compound) { - const flowType = getTypeAtFlowNode(flow.antecedent); - return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType)); - } - if (declaredType === autoType || declaredType === autoArrayType) { - if (isEmptyArrayAssignment(node)) { - return getEvolvingArrayType(neverType); - } - const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow)); - return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; - } - if (declaredType.flags & TypeFlags.Union) { - return getAssignmentReducedType(declaredType as UnionType, getInitialOrAssignedType(flow)); - } - return declaredType; - } - // We didn't have a direct match. However, if the reference is a dotted name, this - // may be an assignment to a left hand part of the reference. For example, for a - // reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case, - // return the declared type. - if (containsMatchingReference(reference, node)) { - if (!isReachableFlowNode(flow)) { - return unreachableNeverType; - } - // A matching dotted name might also be an expando property on a function *expression*, - // in which case we continue control flow analysis back to the function's declaration - if (isVariableDeclaration(node) && (isInJSFile(node) || isVarConst(node))) { - const init = getDeclaredExpandoInitializer(node); - if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) { - return getTypeAtFlowNode(flow.antecedent); - } - } - return declaredType; - } - // for (const _ in ref) acts as a nonnull on ref - if (isVariableDeclaration(node) && node.parent.parent.kind === SyntaxKind.ForInStatement && isMatchingReference(reference, node.parent.parent.expression)) { - return getNonNullableTypeIfNeeded(getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent))); - } - // Assignment doesn't affect reference - return undefined; - } - - function narrowTypeByAssertion(type: Type, expr: Expression): Type { - const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); - if (node.kind === SyntaxKind.FalseKeyword) { - return unreachableNeverType; - } - if (node.kind === SyntaxKind.BinaryExpression) { - if ((node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { - return narrowTypeByAssertion(narrowTypeByAssertion(type, (node as BinaryExpression).left), (node as BinaryExpression).right); - } - if ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken) { - return getUnionType([narrowTypeByAssertion(type, (node as BinaryExpression).left), narrowTypeByAssertion(type, (node as BinaryExpression).right)]); - } - } - return narrowType(type, node, /*assumeTrue*/ true); - } - - function getTypeAtFlowCall(flow: FlowCall): FlowType | undefined { - const signature = getEffectsSignature(flow.node); - if (signature) { - const predicate = getTypePredicateOfSignature(signature); - if (predicate && (predicate.kind === TypePredicateKind.AssertsThis || predicate.kind === TypePredicateKind.AssertsIdentifier)) { - const flowType = getTypeAtFlowNode(flow.antecedent); - const type = finalizeEvolvingArrayType(getTypeFromFlowType(flowType)); - const narrowedType = predicate.type ? narrowTypeByTypePredicate(type, predicate, flow.node, /*assumeTrue*/ true) : - predicate.kind === TypePredicateKind.AssertsIdentifier && predicate.parameterIndex >= 0 && predicate.parameterIndex < flow.node.arguments.length ? narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]) : - type; - return narrowedType === type ? flowType : createFlowType(narrowedType, isIncomplete(flowType)); - } - if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { - return unreachableNeverType; - } - } - return undefined; - } - - function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType | undefined { - if (declaredType === autoType || declaredType === autoArrayType) { - const node = flow.node; - const expr = node.kind === SyntaxKind.CallExpression ? - (node.expression as PropertyAccessExpression).expression : - (node.left as ElementAccessExpression).expression; - if (isMatchingReference(reference, getReferenceCandidate(expr))) { - const flowType = getTypeAtFlowNode(flow.antecedent); - const type = getTypeFromFlowType(flowType); - if (getObjectFlags(type) & ObjectFlags.EvolvingArray) { - let evolvedType = type as EvolvingArrayType; - if (node.kind === SyntaxKind.CallExpression) { - for (const arg of node.arguments) { - evolvedType = addEvolvingArrayElementType(evolvedType, arg); - } - } - else { - // We must get the context free expression type so as to not recur in an uncached fashion on the LHS (which causes exponential blowup in compile time) - const indexType = getContextFreeTypeOfExpression((node.left as ElementAccessExpression).argumentExpression); - if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { - evolvedType = addEvolvingArrayElementType(evolvedType, node.right); - } - } - return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); - } - return flowType; - } - } - return undefined; - } - - function getTypeAtFlowCondition(flow: FlowCondition): FlowType { - const flowType = getTypeAtFlowNode(flow.antecedent); - const type = getTypeFromFlowType(flowType); - if (type.flags & TypeFlags.Never) { - return flowType; - } - // If we have an antecedent type (meaning we're reachable in some way), we first - // attempt to narrow the antecedent type. If that produces the never type, and if - // the antecedent type is incomplete (i.e. a transient type in a loop), then we - // take the type guard as an indication that control *could* reach here once we - // have the complete type. We proceed by switching to the silent never type which - // doesn't report errors when operators are applied to it. Note that this is the - // *only* place a silent never type is ever generated. - const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0; - const nonEvolvingType = finalizeEvolvingArrayType(type); - const narrowedType = narrowType(nonEvolvingType, flow.node, assumeTrue); - if (narrowedType === nonEvolvingType) { - return flowType; - } - return createFlowType(narrowedType, isIncomplete(flowType)); - } - - function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { - const expr = flow.switchStatement.expression; - const flowType = getTypeAtFlowNode(flow.antecedent); - let type = getTypeFromFlowType(flowType); - if (isMatchingReference(reference, expr)) { - type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); - } - else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { - type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); - } - else { - if (strictNullChecks) { - if (optionalChainContainsReference(expr, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, - t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); - } - else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, - t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined")); - } - } - const access = getDiscriminantPropertyAccess(expr, type); - if (access) { - type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.switchStatement, flow.clauseStart, flow.clauseEnd); - } - } - return createFlowType(type, isIncomplete(flowType)); - } - - function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType { - const antecedentTypes: Type[] = []; - let subtypeReduction = false; - let seenIncomplete = false; - let bypassFlow: FlowSwitchClause | undefined; - for (const antecedent of flow.antecedents!) { - if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).clauseStart === (antecedent as FlowSwitchClause).clauseEnd) { - // The antecedent is the bypass branch of a potentially exhaustive switch statement. - bypassFlow = antecedent as FlowSwitchClause; - continue; - } - const flowType = getTypeAtFlowNode(antecedent); - const type = getTypeFromFlowType(flowType); - // If the type at a particular antecedent path is the declared type and the - // reference is known to always be assigned (i.e. when declared and initial types - // are the same), there is no reason to process more antecedents since the only - // possible outcome is subtypes that will be removed in the final union type anyway. - if (type === declaredType && declaredType === initialType) { - return type; - } - pushIfUnique(antecedentTypes, type); - // If an antecedent type is not a subset of the declared type, we need to perform - // subtype reduction. This happens when a "foreign" type is injected into the control - // flow using the instanceof operator or a user defined type predicate. - if (!isTypeSubsetOf(type, declaredType)) { - subtypeReduction = true; - } - if (isIncomplete(flowType)) { - seenIncomplete = true; - } - } - if (bypassFlow) { - const flowType = getTypeAtFlowNode(bypassFlow); - const type = getTypeFromFlowType(flowType); - // If the bypass flow contributes a type we haven't seen yet and the switch statement - // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase - // the risk of circularities, we only want to perform them when they make a difference. - if (!contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.switchStatement)) { - if (type === declaredType && declaredType === initialType) { - return type; - } - antecedentTypes.push(type); - if (!isTypeSubsetOf(type, declaredType)) { - subtypeReduction = true; - } - if (isIncomplete(flowType)) { - seenIncomplete = true; - } - } - } - return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); - } - - function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType { - // If we have previously computed the control flow type for the reference at - // this flow loop junction, return the cached type. - const id = getFlowNodeId(flow); - const cache = flowLoopCaches[id] || (flowLoopCaches[id] = new Map()); - const key = getOrSetCacheKey(); - if (!key) { - // No cache key is generated when binding patterns are in unnarrowable situations - return declaredType; - } - const cached = cache.get(key); - if (cached) { - return cached; - } - // If this flow loop junction and reference are already being processed, return - // the union of the types computed for each branch so far, marked as incomplete. - // It is possible to see an empty array in cases where loops are nested and the - // back edge of the outer loop reaches an inner loop that is already being analyzed. - // In such cases we restart the analysis of the inner loop, which will then see - // a non-empty in-process array for the outer loop and eventually terminate because - // the first antecedent of a loop junction is always the non-looping control flow - // path that leads to the top. - for (let i = flowLoopStart; i < flowLoopCount; i++) { - if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) { - return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], UnionReduction.Literal), /*incomplete*/ true); - } - } - // Add the flow loop junction and reference to the in-process stack and analyze - // each antecedent code path. - const antecedentTypes: Type[] = []; - let subtypeReduction = false; - let firstAntecedentType: FlowType | undefined; - for (const antecedent of flow.antecedents!) { - let flowType; - if (!firstAntecedentType) { - // The first antecedent of a loop junction is always the non-looping control - // flow path that leads to the top. - flowType = firstAntecedentType = getTypeAtFlowNode(antecedent); - } - else { - // All but the first antecedent are the looping control flow paths that lead - // back to the loop junction. We track these on the flow loop stack. - flowLoopNodes[flowLoopCount] = flow; - flowLoopKeys[flowLoopCount] = key; - flowLoopTypes[flowLoopCount] = antecedentTypes; - flowLoopCount++; - const saveFlowTypeCache = flowTypeCache; - flowTypeCache = undefined; - flowType = getTypeAtFlowNode(antecedent); - flowTypeCache = saveFlowTypeCache; - flowLoopCount--; - // If we see a value appear in the cache it is a sign that control flow analysis - // was restarted and completed by checkExpressionCached. We can simply pick up - // the resulting type and bail out. - const cached = cache.get(key); - if (cached) { - return cached; - } - } - const type = getTypeFromFlowType(flowType); - pushIfUnique(antecedentTypes, type); - // If an antecedent type is not a subset of the declared type, we need to perform - // subtype reduction. This happens when a "foreign" type is injected into the control - // flow using the instanceof operator or a user defined type predicate. - if (!isTypeSubsetOf(type, declaredType)) { - subtypeReduction = true; - } - // If the type at a particular antecedent path is the declared type there is no - // reason to process more antecedents since the only possible outcome is subtypes - // that will be removed in the final union type anyway. - if (type === declaredType) { - break; - } - } - // The result is incomplete if the first antecedent (the non-looping control flow path) - // is incomplete. - const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal); - if (isIncomplete(firstAntecedentType!)) { - return createFlowType(result, /*incomplete*/ true); - } - cache.set(key, result); - return result; - } - - // At flow control branch or loop junctions, if the type along every antecedent code path - // is an evolving array type, we construct a combined evolving array type. Otherwise we - // finalize all evolving array types. - function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: UnionReduction) { - if (isEvolvingArrayTypeList(types)) { - return getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))); - } - const result = getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction); - if (result !== declaredType && result.flags & declaredType.flags & TypeFlags.Union && arraysEqual((result as UnionType).types, (declaredType as UnionType).types)) { - return declaredType; - } - return result; - } - - function getCandidateDiscriminantPropertyAccess(expr: Expression) { - if (isBindingPattern(reference)) { - // When the reference is a binding pattern, we are narrowing a pesudo-reference in getNarrowedTypeOfSymbol. - // An identifier for a destructuring variable declared in the same binding pattern is a candidate. - if (isIdentifier(expr)) { - const symbol = getResolvedSymbol(expr); - const declaration = symbol.valueDeclaration; - if (declaration && isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && reference === declaration.parent) { - return declaration; - } - } - } - else if (isAccessExpression(expr)) { - // An access expression is a candidate if the reference matches the left hand expression. - if (isMatchingReference(reference, expr.expression)) { - return expr; - } - } - else if (isIdentifier(expr)) { - const symbol = getResolvedSymbol(expr); - if (isConstVariable(symbol)) { - const declaration = symbol.valueDeclaration!; - // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind' - if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) && - isMatchingReference(reference, declaration.initializer.expression)) { - return declaration.initializer; - } - // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind' - if (isBindingElement(declaration) && !declaration.initializer) { - const parent = declaration.parent.parent; - if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) && - isMatchingReference(reference, parent.initializer)) { - return declaration; - } - } - } - } - return undefined; - } - - function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { - const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType; - if (type.flags & TypeFlags.Union) { - const access = getCandidateDiscriminantPropertyAccess(expr); - if (access) { - const name = getAccessedPropertyName(access); - if (name && isDiscriminantProperty(type, name)) { - return access; - } - } - } - return undefined; - } - - function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement, narrowType: (t: Type) => Type): Type { - const propName = getAccessedPropertyName(access); - if (propName === undefined) { - return type; - } - const removeNullable = strictNullChecks && isOptionalChain(access) && maybeTypeOfKind(type, TypeFlags.Nullable); - let propType = getTypeOfPropertyOfType(removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type, propName); - if (!propType) { - return type; - } - propType = removeNullable ? getOptionalType(propType) : propType; - const narrowedPropType = narrowType(propType); - return filterType(type, t => { - const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName); - return !(narrowedPropType.flags & TypeFlags.Never) && isTypeComparableTo(narrowedPropType, discriminantType); - }); - } - - function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, operator: SyntaxKind, value: Expression, assumeTrue: boolean) { - if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && type.flags & TypeFlags.Union) { - const keyPropertyName = getKeyPropertyName(type as UnionType); - if (keyPropertyName && keyPropertyName === getAccessedPropertyName(access)) { - const candidate = getConstituentTypeForKeyType(type as UnionType, getTypeOfExpression(value)); - if (candidate) { - return operator === (assumeTrue ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken) ? candidate : - isUnitType(getTypeOfPropertyOfType(candidate, keyPropertyName) || unknownType) ? removeType(type, candidate) : - type; - } - } - } - return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); - } - - function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { - if (clauseStart < clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) { - const clauseTypes = getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd); - const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType)); - if (candidate !== unknownType) { - return candidate; - } - } - return narrowTypeByDiscriminant(type, access, t => narrowTypeBySwitchOnDiscriminant(t, switchStatement, clauseStart, clauseEnd)); - } - - function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { - if (isMatchingReference(reference, expr)) { - return type.flags & TypeFlags.Unknown && assumeTrue ? nonNullUnknownType : - getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); - } - if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) { - type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); - } - const access = getDiscriminantPropertyAccess(expr, type); - if (access) { - return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); - } - return type; - } - - function isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) { - const prop = getPropertyOfType(type, propName); - if (prop) { - return prop.flags & SymbolFlags.Optional ? true : assumeTrue; - } - return getApplicableIndexInfoForName(type, propName) ? true : !assumeTrue; - } - - function narrowByInKeyword(type: Type, name: __String, assumeTrue: boolean) { - if (type.flags & TypeFlags.Union - || type.flags & TypeFlags.Object && declaredType !== type - || isThisTypeParameter(type) - || type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, t => t.symbol !== globalThisSymbol)) { - return filterType(type, t => isTypePresencePossible(t, name, assumeTrue)); - } - return type; - } - - function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - switch (expr.operatorToken.kind) { - case SyntaxKind.EqualsToken: - case SyntaxKind.BarBarEqualsToken: - case SyntaxKind.AmpersandAmpersandEqualsToken: - case SyntaxKind.QuestionQuestionEqualsToken: - return narrowTypeByTruthiness(narrowType(type, expr.right, assumeTrue), expr.left, assumeTrue); - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - const operator = expr.operatorToken.kind; - const left = getReferenceCandidate(expr.left); - const right = getReferenceCandidate(expr.right); - if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) { - return narrowTypeByTypeof(type, left as TypeOfExpression, operator, right, assumeTrue); - } - if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) { - return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue); - } - if (isMatchingReference(reference, left)) { - return narrowTypeByEquality(type, operator, right, assumeTrue); - } - if (isMatchingReference(reference, right)) { - return narrowTypeByEquality(type, operator, left, assumeTrue); - } - if (strictNullChecks) { - if (optionalChainContainsReference(left, reference)) { - type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue); - } - else if (optionalChainContainsReference(right, reference)) { - type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue); - } - } - const leftAccess = getDiscriminantPropertyAccess(left, type); - if (leftAccess) { - return narrowTypeByDiscriminantProperty(type, leftAccess, operator, right, assumeTrue); - } - const rightAccess = getDiscriminantPropertyAccess(right, type); - if (rightAccess) { - return narrowTypeByDiscriminantProperty(type, rightAccess, operator, left, assumeTrue); - } - if (isMatchingConstructorReference(left)) { - return narrowTypeByConstructor(type, operator, right, assumeTrue); - } - if (isMatchingConstructorReference(right)) { - return narrowTypeByConstructor(type, operator, left, assumeTrue); - } - break; - case SyntaxKind.InstanceOfKeyword: - return narrowTypeByInstanceof(type, expr, assumeTrue); - case SyntaxKind.InKeyword: - if (isPrivateIdentifier(expr.left)) { - return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue); - } - const target = getReferenceCandidate(expr.right); - const leftType = getTypeOfNode(expr.left); - if (leftType.flags & TypeFlags.StringLiteral) { - const name = escapeLeadingUnderscores((leftType as StringLiteralType).value); - if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target) && - getAccessedPropertyName(reference) === name) { - return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); - } - if (isMatchingReference(reference, target)) { - return narrowByInKeyword(type, name, assumeTrue); - } - } - break; - case SyntaxKind.CommaToken: - return narrowType(type, expr.right, assumeTrue); - // Ordinarily we won't see && and || expressions in control flow analysis because the Binder breaks those - // expressions down to individual conditional control flows. However, we may encounter them when analyzing - // aliased conditional expressions. - case SyntaxKind.AmpersandAmpersandToken: - return assumeTrue ? - narrowType(narrowType(type, expr.left, /*assumeTrue*/ true), expr.right, /*assumeTrue*/ true) : - getUnionType([narrowType(type, expr.left, /*assumeTrue*/ false), narrowType(type, expr.right, /*assumeTrue*/ false)]); - case SyntaxKind.BarBarToken: - return assumeTrue ? - getUnionType([narrowType(type, expr.left, /*assumeTrue*/ true), narrowType(type, expr.right, /*assumeTrue*/ true)]) : - narrowType(narrowType(type, expr.left, /*assumeTrue*/ false), expr.right, /*assumeTrue*/ false); - } - return type; - } - - function narrowTypeByPrivateIdentifierInInExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - const target = getReferenceCandidate(expr.right); - if (!isMatchingReference(reference, target)) { - return type; - } - - Debug.assertNode(expr.left, isPrivateIdentifier); - const symbol = getSymbolForPrivateIdentifierExpression(expr.left); - if (symbol === undefined) { - return type; - } - const classSymbol = symbol.parent!; - const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration")) - ? getTypeOfSymbol(classSymbol) as InterfaceType - : getDeclaredTypeOfSymbol(classSymbol); - return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom); - } - - function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { - // We are in a branch of obj?.foo === value (or any one of the other equality operators). We narrow obj as follows: - // When operator is === and type of value excludes undefined, null and undefined is removed from type of obj in true branch. - // When operator is !== and type of value excludes undefined, null and undefined is removed from type of obj in false branch. - // When operator is == and type of value excludes null and undefined, null and undefined is removed from type of obj in true branch. - // When operator is != and type of value excludes null and undefined, null and undefined is removed from type of obj in false branch. - // When operator is === and type of value is undefined, null and undefined is removed from type of obj in false branch. - // When operator is !== and type of value is undefined, null and undefined is removed from type of obj in true branch. - // When operator is == and type of value is null or undefined, null and undefined is removed from type of obj in false branch. - // When operator is != and type of value is null or undefined, null and undefined is removed from type of obj in true branch. - const equalsOperator = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; - const nullableFlags = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? TypeFlags.Nullable : TypeFlags.Undefined; - const valueType = getTypeOfExpression(value); - // Note that we include any and unknown in the exclusion test because their domain includes null and undefined. - const removeNullable = equalsOperator !== assumeTrue && everyType(valueType, t => !!(t.flags & nullableFlags)) || - equalsOperator === assumeTrue && everyType(valueType, t => !(t.flags & (TypeFlags.AnyOrUnknown | nullableFlags))); - return removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; - } - - function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { - if (type.flags & TypeFlags.Any) { - return type; - } - if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { - assumeTrue = !assumeTrue; - } - const valueType = getTypeOfExpression(value); - if (assumeTrue && (type.flags & TypeFlags.Unknown) && (operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken) && (valueType.flags & TypeFlags.Null)) { - return getUnionType([nullType, undefinedType]); - } - if ((type.flags & TypeFlags.Unknown) && assumeTrue && (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) { - if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { - return valueType; - } - if (valueType.flags & TypeFlags.Object) { - return nonPrimitiveType; - } - return type; - } - if (valueType.flags & TypeFlags.Nullable) { - if (!strictNullChecks) { - return type; - } - const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; - const facts = doubleEquals ? - assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull : - valueType.flags & TypeFlags.Null ? - assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull : - assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; - return type.flags & TypeFlags.Unknown && facts & (TypeFacts.NENull | TypeFacts.NEUndefinedOrNull) ? nonNullUnknownType : getTypeWithFacts(type, facts); - } - if (assumeTrue) { - const filterFn: (t: Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ? - t => areTypesComparable(t, valueType) || isCoercibleUnderDoubleEquals(t, valueType) : - t => areTypesComparable(t, valueType); - return replacePrimitivesWithLiterals(filterType(type, filterFn), valueType); - } - if (isUnitType(valueType)) { - return filterType(type, t => !(isUnitLikeType(t) && areTypesComparable(t, valueType))); - } - return type; - } - - function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { - // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands - if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { - assumeTrue = !assumeTrue; - } - const target = getReferenceCandidate(typeOfExpr.expression); - if (!isMatchingReference(reference, target)) { - if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { - return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); - } - return type; - } - if (type.flags & TypeFlags.Any && literal.text === "function") { - return type; - } - if (assumeTrue && type.flags & TypeFlags.Unknown && literal.text === "object") { - // The non-null unknown type is used to track whether a previous narrowing operation has removed the null type - // from the unknown type. For example, the expression `x && typeof x === 'object'` first narrows x to the non-null - // unknown type, and then narrows that to the non-primitive type. - return type === nonNullUnknownType ? nonPrimitiveType : getUnionType([nonPrimitiveType, nullType]); - } - const facts = assumeTrue ? - typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject : - typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject; - const impliedType = getImpliedTypeFromTypeofGuard(type, literal.text); - return getTypeWithFacts(assumeTrue && impliedType ? mapType(type, narrowUnionMemberByTypeof(impliedType)) : type, facts); - } - - function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: Type) => boolean) { - const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck); - return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; - } - - function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { - // We only narrow if all case expressions specify - // values with unit types, except for the case where - // `type` is unknown. In this instance we map object - // types to the nonPrimitive type and narrow with that. - const switchTypes = getSwitchClauseTypes(switchStatement); - if (!switchTypes.length) { - return type; - } - const clauseTypes = switchTypes.slice(clauseStart, clauseEnd); - const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType); - if ((type.flags & TypeFlags.Unknown) && !hasDefaultClause) { - let groundClauseTypes: Type[] | undefined; - for (let i = 0; i < clauseTypes.length; i += 1) { - const t = clauseTypes[i]; - if (t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { - if (groundClauseTypes !== undefined) { - groundClauseTypes.push(t); - } - } - else if (t.flags & TypeFlags.Object) { - if (groundClauseTypes === undefined) { - groundClauseTypes = clauseTypes.slice(0, i); - } - groundClauseTypes.push(nonPrimitiveType); - } - else { - return type; - } - } - return getUnionType(groundClauseTypes === undefined ? clauseTypes : groundClauseTypes); - } - const discriminantType = getUnionType(clauseTypes); - const caseType = - discriminantType.flags & TypeFlags.Never ? neverType : - replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType); - if (!hasDefaultClause) { - return caseType; - } - const defaultType = filterType(type, t => !(isUnitLikeType(t) && contains(switchTypes, getRegularTypeOfLiteralType(extractUnitType(t))))); - return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]); - } - - function getImpliedTypeFromTypeofGuard(type: Type, text: string) { - switch (text) { - case "function": - return type.flags & TypeFlags.Any ? type : globalFunctionType; - case "object": - return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type; - default: - return typeofTypesByName.get(text); - } - } - - // When narrowing a union type by a `typeof` guard using type-facts alone, constituent types that are - // super-types of the implied guard will be retained in the final type: this is because type-facts only - // filter. Instead, we would like to replace those union constituents with the more precise type implied by - // the guard. For example: narrowing `{} | undefined` by `"boolean"` should produce the type `boolean`, not - // the filtered type `{}`. For this reason we narrow constituents of the union individually, in addition to - // filtering by type-facts. - function narrowUnionMemberByTypeof(candidate: Type) { - return (type: Type) => { - if (isTypeSubtypeOf(type, candidate)) { - return type; - } - if (isTypeSubtypeOf(candidate, type)) { - return candidate; - } - if (type.flags & TypeFlags.Instantiable) { - const constraint = getBaseConstraintOfType(type) || anyType; - if (isTypeSubtypeOf(candidate, constraint)) { - return getIntersectionType([type, candidate]); - } - } - return type; - }; - } - - function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type { - const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement, /*retainDefault*/ true); - if (!switchWitnesses.length) { - return type; - } - // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause - const defaultCaseLocation = findIndex(switchWitnesses, elem => elem === undefined); - const hasDefaultClause = clauseStart === clauseEnd || (defaultCaseLocation >= clauseStart && defaultCaseLocation < clauseEnd); - let clauseWitnesses: string[]; - let switchFacts: TypeFacts; - if (defaultCaseLocation > -1) { - // We no longer need the undefined denoting an explicit default case. Remove the undefined and - // fix-up clauseStart and clauseEnd. This means that we don't have to worry about undefined in the - // witness array. - const witnesses = switchWitnesses.filter(witness => witness !== undefined) as string[]; - // The adjusted clause start and end after removing the `default` statement. - const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart; - const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd; - clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd); - switchFacts = getFactsFromTypeofSwitch(fixedClauseStart, fixedClauseEnd, witnesses, hasDefaultClause); - } - else { - clauseWitnesses = switchWitnesses.slice(clauseStart, clauseEnd) as string[]; - switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, switchWitnesses as string[], hasDefaultClause); - } - if (hasDefaultClause) { - return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts); - } - /* - The implied type is the raw type suggested by a - value being caught in this clause. - - When the clause contains a default case we ignore - the implied type and try to narrow using any facts - we can learn: see `switchFacts`. - - Example: - switch (typeof x) { - case 'number': - case 'string': break; - default: break; - case 'number': - case 'boolean': break - } - - In the first clause (case `number` and `string`) the - implied type is number | string. - - In the default clause we de not compute an implied type. - - In the third clause (case `number` and `boolean`) - the naive implied type is number | boolean, however - we use the type facts to narrow the implied type to - boolean. We know that number cannot be selected - because it is caught in the first clause. - */ - const impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofGuard(type, text) || type)), switchFacts); - return getTypeWithFacts(mapType(type, narrowUnionMemberByTypeof(impliedType)), switchFacts); - } - - function isMatchingConstructorReference(expr: Expression) { - return (isPropertyAccessExpression(expr) && idText(expr.name) === "constructor" || - isElementAccessExpression(expr) && isStringLiteralLike(expr.argumentExpression) && expr.argumentExpression.text === "constructor") && - isMatchingReference(reference, expr.expression); - } - - function narrowTypeByConstructor(type: Type, operator: SyntaxKind, identifier: Expression, assumeTrue: boolean): Type { - // Do not narrow when checking inequality. - if (assumeTrue ? (operator !== SyntaxKind.EqualsEqualsToken && operator !== SyntaxKind.EqualsEqualsEqualsToken) : (operator !== SyntaxKind.ExclamationEqualsToken && operator !== SyntaxKind.ExclamationEqualsEqualsToken)) { - return type; - } - - // Get the type of the constructor identifier expression, if it is not a function then do not narrow. - const identifierType = getTypeOfExpression(identifier); - if (!isFunctionType(identifierType) && !isConstructorType(identifierType)) { - return type; - } - - // Get the prototype property of the type identifier so we can find out its type. - const prototypeProperty = getPropertyOfType(identifierType, "prototype" as __String); - if (!prototypeProperty) { - return type; - } - - // Get the type of the prototype, if it is undefined, or the global `Object` or `Function` types then do not narrow. - const prototypeType = getTypeOfSymbol(prototypeProperty); - const candidate = !isTypeAny(prototypeType) ? prototypeType : undefined; - if (!candidate || candidate === globalObjectType || candidate === globalFunctionType) { - return type; - } - - // If the type that is being narrowed is `any` then just return the `candidate` type since every type is a subtype of `any`. - if (isTypeAny(type)) { - return candidate; - } - - // Filter out types that are not considered to be "constructed by" the `candidate` type. - return filterType(type, t => isConstructedBy(t, candidate)); - - function isConstructedBy(source: Type, target: Type) { - // If either the source or target type are a class type then we need to check that they are the same exact type. - // This is because you may have a class `A` that defines some set of properties, and another class `B` - // that defines the same set of properties as class `A`, in that case they are structurally the same - // type, but when you do something like `instanceOfA.constructor === B` it will return false. - if (source.flags & TypeFlags.Object && getObjectFlags(source) & ObjectFlags.Class || - target.flags & TypeFlags.Object && getObjectFlags(target) & ObjectFlags.Class) { - return source.symbol === target.symbol; - } - - // For all other types just check that the `source` type is a subtype of the `target` type. - return isTypeSubtypeOf(source, target); - } - } - - function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - const left = getReferenceCandidate(expr.left); - if (!isMatchingReference(reference, left)) { - if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) { - return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); - } - return type; - } - - // Check that right operand is a function type with a prototype property - const rightType = getTypeOfExpression(expr.right); - if (!isTypeDerivedFrom(rightType, globalFunctionType)) { - return type; - } - - let targetType: Type | undefined; - const prototypeProperty = getPropertyOfType(rightType, "prototype" as __String); - if (prototypeProperty) { - // Target type is type of the prototype property - const prototypePropertyType = getTypeOfSymbol(prototypeProperty); - if (!isTypeAny(prototypePropertyType)) { - targetType = prototypePropertyType; - } - } - - // Don't narrow from 'any' if the target type is exactly 'Object' or 'Function' - if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) { - return type; - } - - if (!targetType) { - const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct); - targetType = constructSignatures.length ? - getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) : - emptyObjectType; - } - - // We can't narrow a union based off instanceof without negated types see #31576 for more info - if (!assumeTrue && rightType.flags & TypeFlags.Union) { - const nonConstructorTypeInUnion = find((rightType as UnionType).types, (t) => !isConstructorType(t)); - if (!nonConstructorTypeInUnion) return type; - } - - return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom); - } - - function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) { - if (!assumeTrue) { - return filterType(type, t => !isRelated(t, candidate)); - } - // If the current type is a union type, remove all constituents that couldn't be instances of - // the candidate type. If one or more constituents remain, return a union of those. - if (type.flags & TypeFlags.Union) { - const assignableType = filterType(type, t => isRelated(t, candidate)); - if (!(assignableType.flags & TypeFlags.Never)) { - return assignableType; - } - } - - // If the candidate type is a subtype of the target type, narrow to the candidate type. - // Otherwise, if the target type is assignable to the candidate type, keep the target type. - // Otherwise, if the candidate type is assignable to the target type, narrow to the candidate - // type. Otherwise, the types are completely unrelated, so narrow to an intersection of the - // two types. - return isTypeSubtypeOf(candidate, type) ? candidate : - isTypeAssignableTo(type, candidate) ? type : - isTypeAssignableTo(candidate, type) ? candidate : - getIntersectionType([type, candidate]); - } - - function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { - if (hasMatchingArgument(callExpression, reference)) { - const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined; - const predicate = signature && getTypePredicateOfSignature(signature); - if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) { - return narrowTypeByTypePredicate(type, predicate, callExpression, assumeTrue); - } - } - if (containsMissingType(type) && isAccessExpression(reference) && isPropertyAccessExpression(callExpression.expression)) { - const callAccess = callExpression.expression; - if (isMatchingReference(reference.expression, getReferenceCandidate(callAccess.expression)) && - isIdentifier(callAccess.name) && callAccess.name.escapedText === "hasOwnProperty" && callExpression.arguments.length === 1) { - const argument = callExpression.arguments[0]; - if (isStringLiteralLike(argument) && getAccessedPropertyName(reference) === escapeLeadingUnderscores(argument.text)) { - return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); - } - } - } - return type; - } - - function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type { - // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function' - if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) { - const predicateArgument = getTypePredicateArgument(predicate, callExpression); - if (predicateArgument) { - if (isMatchingReference(reference, predicateArgument)) { - return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf); - } - if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) && - !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) { - type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); - } - const access = getDiscriminantPropertyAccess(predicateArgument, type); - if (access) { - return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, isTypeSubtypeOf)); - } - } - } - return type; - } - - // Narrow the given type based on the given expression having the assumed boolean value. The returned type - // will be a subtype or the same type as the argument. - function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { - // for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a` - if (isExpressionOfOptionalChainRoot(expr) || - isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) { - return narrowTypeByOptionality(type, expr, assumeTrue); - } - switch (expr.kind) { - case SyntaxKind.Identifier: - // When narrowing a reference to a const variable, non-assigned parameter, or readonly property, we inline - // up to five levels of aliased conditional expressions that are themselves declared as const variables. - if (!isMatchingReference(reference, expr) && inlineLevel < 5) { - const symbol = getResolvedSymbol(expr as Identifier); - if (isConstVariable(symbol)) { - const declaration = symbol.valueDeclaration; - if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isConstantReference(reference)) { - inlineLevel++; - const result = narrowType(type, declaration.initializer, assumeTrue); - inlineLevel--; - return result; - } - } - } - // falls through - case SyntaxKind.ThisKeyword: - case SyntaxKind.SuperKeyword: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return narrowTypeByTruthiness(type, expr, assumeTrue); - case SyntaxKind.CallExpression: - return narrowTypeByCallExpression(type, expr as CallExpression, assumeTrue); - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.NonNullExpression: - return narrowType(type, (expr as ParenthesizedExpression | NonNullExpression).expression, assumeTrue); - case SyntaxKind.BinaryExpression: - return narrowTypeByBinaryExpression(type, expr as BinaryExpression, assumeTrue); - case SyntaxKind.PrefixUnaryExpression: - if ((expr as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken) { - return narrowType(type, (expr as PrefixUnaryExpression).operand, !assumeTrue); - } - break; - } - return type; - } - - function narrowTypeByOptionality(type: Type, expr: Expression, assumePresent: boolean): Type { - if (isMatchingReference(reference, expr)) { - return getTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull); - } - const access = getDiscriminantPropertyAccess(expr, type); - if (access) { - return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull)); - } - return type; } + return true; + } + if (source.flags & TypeFlags.EnumLiteral && getBaseTypeOfEnumLiteralType(source as LiteralType) === target) { + return true; } + return containsType(target.types, source); + } - function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { - symbol = symbol.exportSymbol || symbol; + function forEachType(type: Type, f: (t: Type) => T | undefined): T | undefined { + return type.flags & TypeFlags.Union ? forEach((type as UnionType).types, f) : f(type); + } - // If we have an identifier or a property access at the given location, if the location is - // an dotted name expression, and if the location is not an assignment target, obtain the type - // of the expression (which will reflect control flow analysis). If the expression indeed - // resolved to the given symbol, return the narrowed type. - if (location.kind === SyntaxKind.Identifier || location.kind === SyntaxKind.PrivateIdentifier) { - if (isRightSideOfQualifiedNameOrPropertyAccess(location)) { - location = location.parent; - } - if (isExpressionNode(location) && (!isAssignmentTarget(location) || isWriteAccess(location))) { - const type = getTypeOfExpression(location as Expression); - if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { - return type; + function someType(type: Type, f: (t: Type) => boolean): boolean { + return type.flags & TypeFlags.Union ? some((type as UnionType).types, f) : f(type); + } + + function everyType(type: Type, f: (t: Type) => boolean): boolean { + return type.flags & TypeFlags.Union ? every((type as UnionType).types, f) : f(type); + } + + function everyContainedType(type: Type, f: (t: Type) => boolean): boolean { + return type.flags & TypeFlags.UnionOrIntersection ? every((type as UnionOrIntersectionType).types, f) : f(type); + } + + function filterType(type: Type, f: (t: Type) => boolean): Type { + if (type.flags & TypeFlags.Union) { + const types = (type as UnionType).types; + const filtered = filter(types, f); + if (filtered === types) { + return type; + } + const origin = (type as UnionType).origin; + let newOrigin: Type | undefined; + if (origin && origin.flags & TypeFlags.Union) { + // If the origin type is a (denormalized) union type, filter its non-union constituents. If that ends + // up removing a smaller number of types than in the normalized constituent set (meaning some of the + // filtered types are within nested unions in the origin), then we can't construct a new origin type. + // Otherwise, if we have exactly one type left in the origin set, return that as the filtered type. + // Otherwise, construct a new filtered origin type. + const originTypes = (origin as UnionType).types; + const originFiltered = filter(originTypes, t => !!(t.flags & TypeFlags.Union) || f(t)); + if (originTypes.length - originFiltered.length === types.length - filtered.length) { + if (originFiltered.length === 1) { + return originFiltered[0]; } + newOrigin = createOriginUnionOrIntersectionType(TypeFlags.Union, originFiltered); } } - if (isDeclarationName(location) && isSetAccessor(location.parent) && getAnnotatedAccessorTypeNode(location.parent)) { - return resolveTypeOfAccessors(location.parent.symbol, /*writing*/ true)!; - } - // The location isn't a reference to the given symbol, meaning we're being asked - // a hypothetical question of what type the symbol would have if there was a reference - // to it at the given location. Since we have no control flow information for the - // hypothetical reference (control flow information is created and attached by the - // binder), we simply return the declared type of the symbol. - return getNonMissingTypeOfSymbol(symbol); + return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin); } + return type.flags & TypeFlags.Never || f(type) ? type : neverType; + } - function getControlFlowContainer(node: Node): Node { - return findAncestor(node.parent, node => - isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || - node.kind === SyntaxKind.ModuleBlock || - node.kind === SyntaxKind.SourceFile || - node.kind === SyntaxKind.PropertyDeclaration)!; - } + function removeType(type: Type, targetType: Type) { + return filterType(type, t => t !== targetType); + } - // Check if a parameter or catch variable is assigned anywhere - function isSymbolAssigned(symbol: Symbol) { - if (!symbol.valueDeclaration) { - return false; - } - const parent = getRootDeclaration(symbol.valueDeclaration).parent; - const links = getNodeLinks(parent); - if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { - links.flags |= NodeCheckFlags.AssignmentsMarked; - if (!hasParentWithAssignmentsMarked(parent)) { - markNodeAssignments(parent); + function countTypes(type: Type) { + return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1; + } + + // Apply a mapping function to a type and return the resulting type. If the source type + // is a union type, the mapping function is applied to each constituent type and a union + // of the resulting types is returned. + function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined { + if (type.flags & TypeFlags.Never) { + return type; + } + if (!(type.flags & TypeFlags.Union)) { + return mapper(type); + } + const origin = (type as UnionType).origin; + const types = origin && origin.flags & TypeFlags.Union ? (origin as UnionType).types : (type as UnionType).types; + let mappedTypes: Type[] | undefined; + let changed = false; + for (const t of types) { + const mapped = t.flags & TypeFlags.Union ? mapType(t, mapper, noReductions) : mapper(t); + changed ||= t !== mapped; + if (mapped) { + if (!mappedTypes) { + mappedTypes = [mapped]; + } + else { + mappedTypes.push(mapped); } } - return symbol.isAssigned || false; } + return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type; + } - function hasParentWithAssignmentsMarked(node: Node) { - return !!findAncestor(node.parent, node => - (isFunctionLike(node) || isCatchClause(node)) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); - } + function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { + return type.flags & TypeFlags.Union && aliasSymbol ? + getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments) : + mapType(type, mapper); + } - function markNodeAssignments(node: Node) { - if (node.kind === SyntaxKind.Identifier) { - if (isAssignmentTarget(node)) { - const symbol = getResolvedSymbol(node as Identifier); - if (isParameterOrCatchClauseVariable(symbol)) { - symbol.isAssigned = true; - } + function getConstituentCount(type: Type) { + return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1; + } + + function extractTypesOfKind(type: Type, kind: TypeFlags) { + return filterType(type, t => (t.flags & kind) !== 0); + } + + // Return a new type in which occurrences of the string, number and bigint primitives and placeholder template + // literal types in typeWithPrimitives have been replaced with occurrences of compatible and more specific types + // from typeWithLiterals. This is essentially a limited form of intersection between the two types. We avoid a + // true intersection because it is more costly and, when applied to union types, generates a large number of + // types we don't actually care about. + function replacePrimitivesWithLiterals(typeWithPrimitives: Type, typeWithLiterals: Type) { + if (maybeTypeOfKind(typeWithPrimitives, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.Number | TypeFlags.BigInt) && + maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.NumberLiteral | TypeFlags.BigIntLiteral)) { + return mapType(typeWithPrimitives, t => + t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) : + isPatternLiteralType(t) && !maybeTypeOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? extractTypesOfKind(typeWithLiterals, TypeFlags.StringLiteral) : + t.flags & TypeFlags.Number ? extractTypesOfKind(typeWithLiterals, TypeFlags.Number | TypeFlags.NumberLiteral) : + t.flags & TypeFlags.BigInt ? extractTypesOfKind(typeWithLiterals, TypeFlags.BigInt | TypeFlags.BigIntLiteral) : t); + } + return typeWithPrimitives; + } + + function isIncomplete(flowType: FlowType) { + return flowType.flags === 0; + } + + function getTypeFromFlowType(flowType: FlowType) { + return flowType.flags === 0 ? (flowType as IncompleteType).type : flowType as Type; + } + + function createFlowType(type: Type, incomplete: boolean): FlowType { + return incomplete ? { flags: 0, type: type.flags & TypeFlags.Never ? silentNeverType : type } : type; + } + + // An evolving array type tracks the element types that have so far been seen in an + // 'x.push(value)' or 'x[n] = value' operation along the control flow graph. Evolving + // array types are ultimately converted into manifest array types (using getFinalArrayType) + // and never escape the getFlowTypeOfReference function. + function createEvolvingArrayType(elementType: Type): EvolvingArrayType { + const result = createObjectType(ObjectFlags.EvolvingArray) as EvolvingArrayType; + result.elementType = elementType; + return result; + } + + function getEvolvingArrayType(elementType: Type): EvolvingArrayType { + return evolvingArrayTypes[elementType.id] || (evolvingArrayTypes[elementType.id] = createEvolvingArrayType(elementType)); + } + + // When adding evolving array element types we do not perform subtype reduction. Instead, + // we defer subtype reduction until the evolving array type is finalized into a manifest + // array type. + function addEvolvingArrayElementType(evolvingArrayType: EvolvingArrayType, node: Expression): EvolvingArrayType { + const elementType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(getContextFreeTypeOfExpression(node))); + return isTypeSubsetOf(elementType, evolvingArrayType.elementType) ? evolvingArrayType : getEvolvingArrayType(getUnionType([evolvingArrayType.elementType, elementType])); + } + + function createFinalArrayType(elementType: Type) { + return elementType.flags & TypeFlags.Never ? + autoArrayType : + createArrayType(elementType.flags & TypeFlags.Union ? + getUnionType((elementType as UnionType).types, UnionReduction.Subtype) : + elementType); + } + + // We perform subtype reduction upon obtaining the final array type from an evolving array type. + function getFinalArrayType(evolvingArrayType: EvolvingArrayType): Type { + return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createFinalArrayType(evolvingArrayType.elementType)); + } + + function finalizeEvolvingArrayType(type: Type): Type { + return getObjectFlags(type) & ObjectFlags.EvolvingArray ? getFinalArrayType(type as EvolvingArrayType) : type; + } + + function getElementTypeOfEvolvingArrayType(type: Type) { + return getObjectFlags(type) & ObjectFlags.EvolvingArray ? (type as EvolvingArrayType).elementType : neverType; + } + + function isEvolvingArrayTypeList(types: Type[]) { + let hasEvolvingArrayType = false; + for (const t of types) { + if (!(t.flags & TypeFlags.Never)) { + if (!(getObjectFlags(t) & ObjectFlags.EvolvingArray)) { + return false; } - } - else { - forEachChild(node, markNodeAssignments); + hasEvolvingArrayType = true; } } + return hasEvolvingArrayType; + } - function isConstVariable(symbol: Symbol) { - return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0; - } + // Return true if the given node is 'x' in an 'x.length', x.push(value)', 'x.unshift(value)' or + // 'x[n] = value' operation, where 'n' is an expression of type any, undefined, or a number-like type. + function isEvolvingArrayOperationTarget(node: Node) { + const root = getReferenceRoot(node); + const parent = root.parent; + const isLengthPushOrUnshift = isPropertyAccessExpression(parent) && ( + parent.name.escapedText === "length" || + parent.parent.kind === SyntaxKind.CallExpression + && isIdentifier(parent.name) + && isPushOrUnshiftIdentifier(parent.name)); + const isElementAssignment = parent.kind === SyntaxKind.ElementAccessExpression && + (parent as ElementAccessExpression).expression === root && + parent.parent.kind === SyntaxKind.BinaryExpression && + (parent.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && + (parent.parent as BinaryExpression).left === parent && + !isAssignmentTarget(parent.parent) && + isTypeAssignableToKind(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression), TypeFlags.NumberLike); + return isLengthPushOrUnshift || isElementAssignment; + } - /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ - function removeOptionalityFromDeclaredType(declaredType: Type, declaration: VariableLikeDeclaration): Type { - if (pushTypeResolution(declaration.symbol, TypeSystemPropertyName.DeclaredType)) { - const annotationIncludesUndefined = strictNullChecks && - declaration.kind === SyntaxKind.Parameter && - declaration.initializer && - getFalsyFlags(declaredType) & TypeFlags.Undefined && - !(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined); - popTypeResolution(); + function isDeclarationWithExplicitTypeAnnotation(node: Declaration) { + return (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isParameter(node)) && + !!(getEffectiveTypeAnnotationNode(node) || + isInJSFile(node) && hasInitializer(node) && node.initializer && isFunctionExpressionOrArrowFunction(node.initializer) && getEffectiveReturnTypeNode(node.initializer)); + } - return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType; - } - else { - reportCircularityError(declaration.symbol); - return declaredType; - } + function getExplicitTypeOfSymbol(symbol: Symbol, diagnostic?: Diagnostic) { + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule)) { + return getTypeOfSymbol(symbol); } - - function isConstraintPosition(type: Type, node: Node) { - const parent = node.parent; - // In an element access obj[x], we consider obj to be in a constraint position, except when obj is of - // a generic type without a nullable constraint and x is a generic type. This is because when both obj - // and x are of generic types T and K, we want the resulting type to be T[K]. - return parent.kind === SyntaxKind.PropertyAccessExpression || - parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === node || - parent.kind === SyntaxKind.ElementAccessExpression && (parent as ElementAccessExpression).expression === node && - !(someType(type, isGenericTypeWithoutNullableConstraint) && isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression))); - } - - function isGenericTypeWithUnionConstraint(type: Type) { - return !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Nullable | TypeFlags.Union)); - } - - function isGenericTypeWithoutNullableConstraint(type: Type) { - return !!(type.flags & TypeFlags.Instantiable && !maybeTypeOfKind(getBaseConstraintOrType(type), TypeFlags.Nullable)); - } - - function hasNonBindingPatternContextualTypeWithNoGenericTypes(node: Node) { - // Computing the contextual type for a child of a JSX element involves resolving the type of the - // element's tag name, so we exclude that here to avoid circularities. - const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) && - !((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) && - getContextualType(node, ContextFlags.SkipBindingPatterns); - return contextualType && !isGenericType(contextualType); - } - - function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) { - // When the type of a reference is or contains an instantiable type with a union type constraint, and - // when the reference is in a constraint position (where it is known we'll obtain the apparent type) or - // has a contextual type containing no top-level instantiables (meaning constraints will determine - // assignability), we substitute constraints for all instantiables in the type of the reference to give - // control flow analysis an opportunity to narrow it further. For example, for a reference of a type - // parameter type 'T extends string | undefined' with a contextual type 'string', we substitute - // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'. - const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) && - someType(type, isGenericTypeWithUnionConstraint) && - (isConstraintPosition(type, reference) || hasNonBindingPatternContextualTypeWithNoGenericTypes(reference)); - return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; - } - - function isExportOrExportExpression(location: Node) { - return !!findAncestor(location, n => { - const parent = n.parent; - if (parent === undefined) { - return "quit"; - } - if (isExportAssignment(parent)) { - return parent.expression === n && isEntityNameExpression(n); + if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { + if (getCheckFlags(symbol) & CheckFlags.Mapped) { + const origin = (symbol as MappedSymbol).syntheticOrigin; + if (origin && getExplicitTypeOfSymbol(origin)) { + return getTypeOfSymbol(symbol); } - if (isExportSpecifier(parent)) { - return parent.name === n || parent.propertyName === n; + } + const declaration = symbol.valueDeclaration; + if (declaration) { + if (isDeclarationWithExplicitTypeAnnotation(declaration)) { + return getTypeOfSymbol(symbol); } - return false; - }); - } - - function markAliasReferenced(symbol: Symbol, location: Node) { - if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && !getTypeOnlyAliasDeclaration(symbol)) { - const target = resolveAlias(symbol); - if (target.flags & SymbolFlags.Value) { - // An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled - // (because the const enum value will not be inlined), or if (2) the alias is an export - // of a const enum declaration that will be preserved. - if (compilerOptions.isolatedModules || - shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) || - !isConstEnumOrConstEnumOnlyModule(target) - ) { - markAliasSymbolAsReferenced(symbol); - } - else { - markConstEnumAliasAsReferenced(symbol); + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { + const statement = declaration.parent.parent; + const expressionType = getTypeOfDottedName(statement.expression, /*diagnostic*/ undefined); + if (expressionType) { + const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; + return checkIteratedTypeOrElementType(use, expressionType, undefinedType, /*errorNode*/ undefined); } } + if (diagnostic) { + addRelatedInfo(diagnostic, createDiagnosticForNode(declaration, Diagnostics._0_needs_an_explicit_type_annotation, symbolToString(symbol))); + } } } + } - function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier) { - // If we have a non-rest binding element with no initializer declared as a const variable or a const-like - // parameter (a parameter for which there are no assignments in the function body), and if the parent type - // for the destructuring is a union type, one or more of the binding elements may represent discriminant - // properties, and we want the effects of conditional checks on such discriminants to affect the types of - // other binding elements from the same destructuring. Consider: - // - // type Action = - // | { kind: 'A', payload: number } - // | { kind: 'B', payload: string }; - // - // function f1({ kind, payload }: Action) { - // if (kind === 'A') { - // payload.toFixed(); - // } - // if (kind === 'B') { - // payload.toUpperCase(); - // } - // } - // - // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use - // the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference - // as if it occurred in the specified location. We then recompute the narrowed binding element type by - // destructuring from the narrowed parent type. - const declaration = symbol.valueDeclaration; - if (declaration && isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) { - const parent = declaration.parent.parent; - if (parent.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlags(declaration) & NodeFlags.Const || parent.kind === SyntaxKind.Parameter) { - const links = getNodeLinks(location); - if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) { - links.flags |= NodeCheckFlags.InCheckIdentifier; - const parentType = getTypeForBindingElementParent(parent); - links.flags &= ~NodeCheckFlags.InCheckIdentifier; - if (parentType && parentType.flags & TypeFlags.Union && !(parent.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) { - const pattern = declaration.parent; - const narrowedType = getFlowTypeOfReference(pattern, parentType, parentType, /*flowContainer*/ undefined, location.flowNode); - return getBindingElementTypeFromParentType(declaration, narrowedType); + // We require the dotted function name in an assertion expression to be comprised of identifiers + // that reference function, method, class or value module symbols; or variable, property or + // parameter symbols with declarations that have explicit type annotations. Such references are + // resolvable with no possibility of triggering circularities in control flow analysis. + function getTypeOfDottedName(node: Expression, diagnostic: Diagnostic | undefined): Type | undefined { + if (!(node.flags & NodeFlags.InWithStatement)) { + switch (node.kind) { + case SyntaxKind.Identifier: + const symbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(node as Identifier)); + return getExplicitTypeOfSymbol(symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol, diagnostic); + case SyntaxKind.ThisKeyword: + return getExplicitThisType(node); + case SyntaxKind.SuperKeyword: + return checkSuperExpression(node); + case SyntaxKind.PropertyAccessExpression: { + const type = getTypeOfDottedName((node as PropertyAccessExpression).expression, diagnostic); + if (type) { + const name = (node as PropertyAccessExpression).name; + let prop: Symbol | undefined; + if (isPrivateIdentifier(name)) { + if (!type.symbol) { + return undefined; + } + prop = getPropertyOfType(type, getSymbolNameForPrivateIdentifier(type.symbol, name.escapedText)); + } + else { + prop = getPropertyOfType(type, name.escapedText); } + return prop && getExplicitTypeOfSymbol(prop, diagnostic); } + return undefined; } + case SyntaxKind.ParenthesizedExpression: + return getTypeOfDottedName((node as ParenthesizedExpression).expression, diagnostic); } - return getTypeOfSymbol(symbol); } + } - function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type { - const symbol = getResolvedSymbol(node); - if (symbol === unknownSymbol) { - return errorType; - } - - // As noted in ECMAScript 6 language spec, arrow functions never have an arguments objects. - // Although in down-level emit of arrow function, we emit it using function expression which means that - // arguments objects will be bound to the inner object; emitting arrow function natively in ES6, arguments objects - // will be bound to non-arrow function that contain this arrow function. This results in inconsistent behavior. - // To avoid that we will give an error to users if they use arguments objects in arrow function so that they - // can explicitly bound arguments objects - if (symbol === argumentsSymbol) { - if (isInPropertyInitializerOrClassStaticBlock(node)) { - error(node, Diagnostics.arguments_cannot_be_referenced_in_property_initializers); - return errorType; + function getEffectsSignature(node: CallExpression) { + const links = getNodeLinks(node); + let signature = links.effectsSignature; + if (signature === undefined) { + // A call expression parented by an expression statement is a potential assertion. Other call + // expressions are potential type predicate function calls. In order to avoid triggering + // circularities in control flow analysis, we use getTypeOfDottedName when resolving the call + // target expression of an assertion. + let funcType: Type | undefined; + if (node.parent.kind === SyntaxKind.ExpressionStatement) { + funcType = getTypeOfDottedName(node.expression, /*diagnostic*/ undefined); + } + else if (node.expression.kind !== SyntaxKind.SuperKeyword) { + if (isOptionalChain(node)) { + funcType = checkNonNullType( + getOptionalExpressionType(checkExpression(node.expression), node.expression), + node.expression + ); } - - const container = getContainingFunction(node)!; - if (languageVersion < ScriptTarget.ES2015) { - if (container.kind === SyntaxKind.ArrowFunction) { - error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_ES3_and_ES5_Consider_using_a_standard_function_expression); - } - else if (hasSyntacticModifier(container, ModifierFlags.Async)) { - error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_async_function_or_method_in_ES3_and_ES5_Consider_using_a_standard_function_or_method); - } + else { + funcType = checkNonNullExpression(node.expression); } - - getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; - return getTypeOfSymbol(symbol); - } - - // We should only mark aliases as referenced if there isn't a local value declaration - // for the symbol. Also, don't mark any property access expression LHS - checkPropertyAccessExpression will handle that - if (!(node.parent && isPropertyAccessExpression(node.parent) && node.parent.expression === node)) { - markAliasReferenced(symbol, node); - } - - const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); - const sourceSymbol = localOrExportSymbol.flags & SymbolFlags.Alias ? resolveAlias(localOrExportSymbol) : localOrExportSymbol; - if (sourceSymbol.declarations && getDeclarationNodeFlagsFromSymbol(sourceSymbol) & NodeFlags.Deprecated && isUncalledFunctionReference(node, sourceSymbol)) { - addDeprecatedSuggestion(node, sourceSymbol.declarations, node.escapedText as string); } + const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call); + const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] : + some(signatures, hasTypePredicateOrNeverReturnType) ? getResolvedSignature(node) : + undefined; + signature = links.effectsSignature = candidate && hasTypePredicateOrNeverReturnType(candidate) ? candidate : unknownSignature; + } + return signature === unknownSignature ? undefined : signature; + } - let declaration = localOrExportSymbol.valueDeclaration; - if (declaration && localOrExportSymbol.flags & SymbolFlags.Class) { - // Due to the emit for class decorators, any reference to the class from inside of the class body - // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind - // behavior of class names in ES6. - if (declaration.kind === SyntaxKind.ClassDeclaration - && nodeIsDecorated(declaration as ClassDeclaration)) { - let container = getContainingClass(node); - while (container !== undefined) { - if (container === declaration && container.name !== node) { - getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; - getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; - break; - } - - container = getContainingClass(container); - } - } - else if (declaration.kind === SyntaxKind.ClassExpression) { - // When we emit a class expression with static members that contain a reference - // to the constructor in the initializer, we will need to substitute that - // binding with an alias as the class name is not in scope. - let container = getThisContainer(node, /*includeArrowFunctions*/ false); - while (container.kind !== SyntaxKind.SourceFile) { - if (container.parent === declaration) { - if (isPropertyDeclaration(container) && isStatic(container) || isClassStaticBlockDeclaration(container)) { - getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; - getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; - } - break; - } + function hasTypePredicateOrNeverReturnType(signature: Signature) { + return !!(getTypePredicateOfSignature(signature) || + signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never); + } - container = getThisContainer(container, /*includeArrowFunctions*/ false); - } - } - } + function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) { + if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) { + return callExpression.arguments[predicate.parameterIndex]; + } + const invokedExpression = skipParentheses(callExpression.expression); + return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined; + } - checkNestedBlockScopedBinding(node, symbol); + function reportFlowControlError(node: Node) { + const block = findAncestor(node, isFunctionOrModuleBlock) as Block | ModuleBlock | SourceFile; + const sourceFile = getSourceFileOfNode(node); + const span = getSpanOfTokenAtPosition(sourceFile, block.statements.pos); + diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_containing_function_or_module_body_is_too_large_for_control_flow_analysis)); + } - let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node); - const assignmentKind = getAssignmentTargetKind(node); + function isReachableFlowNode(flow: FlowNode) { + const result = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ false); + lastFlowNode = flow; + lastFlowNodeReachable = result; + return result; + } - if (assignmentKind) { - if (!(localOrExportSymbol.flags & SymbolFlags.Variable) && - !(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule)) { - const assignmentError = localOrExportSymbol.flags & SymbolFlags.Enum ? Diagnostics.Cannot_assign_to_0_because_it_is_an_enum - : localOrExportSymbol.flags & SymbolFlags.Class ? Diagnostics.Cannot_assign_to_0_because_it_is_a_class - : localOrExportSymbol.flags & SymbolFlags.Module ? Diagnostics.Cannot_assign_to_0_because_it_is_a_namespace - : localOrExportSymbol.flags & SymbolFlags.Function ? Diagnostics.Cannot_assign_to_0_because_it_is_a_function - : localOrExportSymbol.flags & SymbolFlags.Alias ? Diagnostics.Cannot_assign_to_0_because_it_is_an_import - : Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable; + function isFalseExpression(expr: Expression): boolean { + const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && ( + (node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isFalseExpression((node as BinaryExpression).left) || isFalseExpression((node as BinaryExpression).right)) || + (node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken && isFalseExpression((node as BinaryExpression).left) && isFalseExpression((node as BinaryExpression).right)); + } - error(node, assignmentError, symbolToString(symbol)); - return errorType; + function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean { + while (true) { + if (flow === lastFlowNode) { + return lastFlowNodeReachable; + } + const flags = flow.flags; + if (flags & FlowFlags.Shared) { + if (!noCacheCheck) { + const id = getFlowNodeId(flow); + const reachable = flowNodeReachable[id]; + return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ true)); } - if (isReadonlySymbol(localOrExportSymbol)) { - if (localOrExportSymbol.flags & SymbolFlags.Variable) { - error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant, symbolToString(symbol)); + noCacheCheck = false; + } + if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation)) { + flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation).antecedent; + } + else if (flags & FlowFlags.Call) { + const signature = getEffectsSignature((flow as FlowCall).node); + if (signature) { + const predicate = getTypePredicateOfSignature(signature); + if (predicate && predicate.kind === TypePredicateKind.AssertsIdentifier && !predicate.type) { + const predicateArgument = (flow as FlowCall).node.arguments[predicate.parameterIndex]; + if (predicateArgument && isFalseExpression(predicateArgument)) { + return false; + } } - else { - error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(symbol)); + if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { + return false; } - return errorType; } + flow = (flow as FlowCall).antecedent; } - - const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias; - - // We only narrow variables and parameters occurring in a non-assignment position. For all other - // entities we simply return the declared type. - if (localOrExportSymbol.flags & SymbolFlags.Variable) { - if (assignmentKind === AssignmentKind.Definite) { - return type; + else if (flags & FlowFlags.BranchLabel) { + // A branching point is reachable if any branch is reachable. + return some((flow as FlowLabel).antecedents, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); + } + else if (flags & FlowFlags.LoopLabel) { + const antecedents = (flow as FlowLabel).antecedents; + if (antecedents === undefined || antecedents.length === 0) { + return false; + } + // A loop is reachable if the control flow path that leads to the top is reachable. + flow = antecedents[0]; + } + else if (flags & FlowFlags.SwitchClause) { + // The control flow path representing an unmatched value in a switch statement with + // no default clause is unreachable if the switch statement is exhaustive. + if ((flow as FlowSwitchClause).clauseStart === (flow as FlowSwitchClause).clauseEnd && isExhaustiveSwitchStatement((flow as FlowSwitchClause).switchStatement)) { + return false; } + flow = (flow as FlowSwitchClause).antecedent; } - else if (isAlias) { - declaration = getDeclarationOfAliasSymbol(symbol); + else if (flags & FlowFlags.ReduceLabel) { + // Cache is unreliable once we start adjusting labels + lastFlowNode = undefined; + const target = (flow as FlowReduceLabel).target; + const saveAntecedents = target.antecedents; + target.antecedents = (flow as FlowReduceLabel).antecedents; + const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + target.antecedents = saveAntecedents; + return result; } else { - return type; + return !(flags & FlowFlags.Unreachable); } + } + } - if (!declaration) { - return type; + // Return true if the given flow node is preceded by a 'super(...)' call in every possible code path + // leading to the node. + function isPostSuperFlowNode(flow: FlowNode, noCacheCheck: boolean): boolean { + while (true) { + const flags = flow.flags; + if (flags & FlowFlags.Shared) { + if (!noCacheCheck) { + const id = getFlowNodeId(flow); + const postSuper = flowNodePostSuper[id]; + return postSuper !== undefined ? postSuper : (flowNodePostSuper[id] = isPostSuperFlowNode(flow, /*noCacheCheck*/ true)); + } + noCacheCheck = false; } - - type = getNarrowableTypeForReference(type, node, checkMode); - - // The declaration container is the innermost function that encloses the declaration of the variable - // or parameter. The flow container is the innermost function starting with which we analyze the control - // flow graph to determine the control flow based type. - const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter; - const declarationContainer = getControlFlowContainer(declaration); - let flowContainer = getControlFlowContainer(node); - const isOuterVariable = flowContainer !== declarationContainer; - const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent); - const isModuleExports = symbol.flags & SymbolFlags.ModuleExports; - // When the control flow originates in a function expression or arrow function and we are referencing - // a const variable or parameter from an outer function, we extend the origin of the control flow - // analysis to include the immediately enclosing function. - while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || - flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)) && - (isConstVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isSymbolAssigned(localOrExportSymbol))) { - flowContainer = getControlFlowContainer(flowContainer); - } - // We only look for uninitialized variables in strict null checking mode, and only when we can analyze - // the entire control flow graph from the variable's declaration (i.e. when the flow container and - // declaration container are the same). - const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isBindingElement(declaration) || - type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 || - isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) || - node.parent.kind === SyntaxKind.NonNullExpression || - declaration.kind === SyntaxKind.VariableDeclaration && (declaration as VariableDeclaration).exclamationToken || - declaration.flags & NodeFlags.Ambient; - const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : - type === autoType || type === autoArrayType ? undefinedType : - getOptionalType(type); - const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer); - // A variable is considered uninitialized when it is possible to analyze the entire control flow graph - // from declaration to use, and when the variable's declared type doesn't include undefined but the - // control flow based type does include undefined. - if (!isEvolvingArrayOperationTarget(node) && (type === autoType || type === autoArrayType)) { - if (flowType === autoType || flowType === autoArrayType) { - if (noImplicitAny) { - error(getNameOfDeclaration(declaration), Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(flowType)); - error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); - } - return convertAutoToAny(flowType); - } - } - else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { - error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); - // Return the declared type to reduce follow-on errors - return type; + if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.SwitchClause)) { + flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation | FlowSwitchClause).antecedent; + } + else if (flags & FlowFlags.Call) { + if ((flow as FlowCall).node.expression.kind === SyntaxKind.SuperKeyword) { + return true; + } + flow = (flow as FlowCall).antecedent; + } + else if (flags & FlowFlags.BranchLabel) { + // A branching point is post-super if every branch is post-super. + return every((flow as FlowLabel).antecedents, f => isPostSuperFlowNode(f, /*noCacheCheck*/ false)); + } + else if (flags & FlowFlags.LoopLabel) { + // A loop is post-super if the control flow path that leads to the top is post-super. + flow = (flow as FlowLabel).antecedents![0]; + } + else if (flags & FlowFlags.ReduceLabel) { + const target = (flow as FlowReduceLabel).target; + const saveAntecedents = target.antecedents; + target.antecedents = (flow as FlowReduceLabel).antecedents; + const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + target.antecedents = saveAntecedents; + return result; + } + else { + // Unreachable nodes are considered post-super to silence errors + return !!(flags & FlowFlags.Unreachable); } - return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; } + } - function isInsideFunctionOrInstancePropertyInitializer(node: Node, threshold: Node): boolean { - return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n) || ( - n.parent && isPropertyDeclaration(n.parent) && !hasStaticModifier(n.parent) && n.parent.initializer === n - )); + function isConstantReference(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.Identifier: { + const symbol = getResolvedSymbol(node as Identifier); + return isConstVariable(symbol) || isParameterOrCatchClauseVariable(symbol) && !isSymbolAssigned(symbol); + } + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + // The resolvedSymbol property is initialized by checkPropertyAccess or checkElementAccess before we get here. + return isConstantReference((node as AccessExpression).expression) && isReadonlySymbol(getNodeLinks(node).resolvedSymbol || unknownSymbol); } + return false; + } - function getPartOfForStatementContainingNode(node: Node, container: ForStatement) { - return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement); + function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = reference.flowNode) { + let key: string | undefined; + let isKeySet = false; + let flowDepth = 0; + if (flowAnalysisDisabled) { + return errorType; } - - function getEnclosingIterationStatement(node: Node): Node | undefined { - return findAncestor(node, n => (!n || nodeStartsNewLexicalEnvironment(n)) ? "quit" : isIterationStatement(n, /*lookInLabeledStatements*/ false)); + if (!flowNode) { + return declaredType; + } + flowInvocationCount++; + const sharedFlowStart = sharedFlowCount; + const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode)); + sharedFlowCount = sharedFlowStart; + // When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation, + // we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations + // on empty arrays are possible without implicit any errors and new element types can be inferred without + // type mismatch errors. + const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType); + if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && !(resultType.flags & TypeFlags.Never) && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { + return declaredType; } + // The non-null unknown type should never escape control flow analysis. + return resultType === nonNullUnknownType ? unknownType : resultType; - function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void { - if (languageVersion >= ScriptTarget.ES2015 || - (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 || - !symbol.valueDeclaration || - isSourceFile(symbol.valueDeclaration) || - symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause) { - return; + function getOrSetCacheKey() { + if (isKeySet) { + return key; } + isKeySet = true; + return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer); + } - // 1. walk from the use site up to the declaration and check - // if there is anything function like between declaration and use-site (is binding/class is captured in function). - // 2. walk from the declaration up to the boundary of lexical environment and check - // if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement) - - const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); - const isCaptured = isInsideFunctionOrInstancePropertyInitializer(node, container); - - const enclosingIterationStatement = getEnclosingIterationStatement(container); - if (enclosingIterationStatement) { - if (isCaptured) { - // mark iteration statement as containing block-scoped binding captured in some function - let capturesBlockScopeBindingInLoopBody = true; - if (isForStatement(container)) { - const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList); - if (varDeclList && varDeclList.parent === container) { - const part = getPartOfForStatementContainingNode(node.parent, container); - if (part) { - const links = getNodeLinks(part); - links.flags |= NodeCheckFlags.ContainsCapturedBlockScopeBinding; - - const capturedBindings = links.capturedBlockScopeBindings || (links.capturedBlockScopeBindings = []); - pushIfUnique(capturedBindings, symbol); - - if (part === container.initializer) { - capturesBlockScopeBindingInLoopBody = false; // Initializer is outside of loop body - } - } + function getTypeAtFlowNode(flow: FlowNode): FlowType { + if (flowDepth === 2000) { + // We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error + // and disable further control flow analysis in the containing function or module body. + tracing?.instant(tracing.Phase.CheckTypes, "getTypeAtFlowNode_DepthLimit", { flowId: flow.id }); + flowAnalysisDisabled = true; + reportFlowControlError(reference); + return errorType; + } + flowDepth++; + let sharedFlow: FlowNode | undefined; + while (true) { + const flags = flow.flags; + if (flags & FlowFlags.Shared) { + // We cache results of flow type resolution for shared nodes that were previously visited in + // the same getFlowTypeOfReference invocation. A node is considered shared when it is the + // antecedent of more than one node. + for (let i = sharedFlowStart; i < sharedFlowCount; i++) { + if (sharedFlowNodes[i] === flow) { + flowDepth--; + return sharedFlowTypes[i]; } } - if (capturesBlockScopeBindingInLoopBody) { - getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + sharedFlow = flow; + } + let type: FlowType | undefined; + if (flags & FlowFlags.Assignment) { + type = getTypeAtFlowAssignment(flow as FlowAssignment); + if (!type) { + flow = (flow as FlowAssignment).antecedent; + continue; } } - - // mark variables that are declared in loop initializer and reassigned inside the body of ForStatement. - // if body of ForStatement will be converted to function then we'll need a extra machinery to propagate reassigned values back. - if (isForStatement(container)) { - const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList); - if (varDeclList && varDeclList.parent === container && isAssignedInBodyOfForStatement(node, container)) { - getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.NeedsLoopOutParameter; + else if (flags & FlowFlags.Call) { + type = getTypeAtFlowCall(flow as FlowCall); + if (!type) { + flow = (flow as FlowCall).antecedent; + continue; } } - - // set 'declared inside loop' bit on the block-scoped binding - getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; - } - - if (isCaptured) { - getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; - } - } - - function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { - const links = getNodeLinks(node); - return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfNode(decl)); - } - - function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean { - // skip parenthesized nodes - let current: Node = node; - while (current.parent.kind === SyntaxKind.ParenthesizedExpression) { - current = current.parent; - } - - // check if node is used as LHS in some assignment expression - let isAssigned = false; - if (isAssignmentTarget(current)) { - isAssigned = true; - } - else if ((current.parent.kind === SyntaxKind.PrefixUnaryExpression || current.parent.kind === SyntaxKind.PostfixUnaryExpression)) { - const expr = current.parent as PrefixUnaryExpression | PostfixUnaryExpression; - isAssigned = expr.operator === SyntaxKind.PlusPlusToken || expr.operator === SyntaxKind.MinusMinusToken; - } - - if (!isAssigned) { - return false; - } - - // at this point we know that node is the target of assignment - // now check that modification happens inside the statement part of the ForStatement - return !!findAncestor(current, n => n === container ? "quit" : n === container.statement); - } - - function captureLexicalThis(node: Node, container: Node): void { - getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis; - if (container.kind === SyntaxKind.PropertyDeclaration || container.kind === SyntaxKind.Constructor) { - const classNode = container.parent; - getNodeLinks(classNode).flags |= NodeCheckFlags.CaptureThis; - } - else { - getNodeLinks(container).flags |= NodeCheckFlags.CaptureThis; + else if (flags & FlowFlags.Condition) { + type = getTypeAtFlowCondition(flow as FlowCondition); + } + else if (flags & FlowFlags.SwitchClause) { + type = getTypeAtSwitchClause(flow as FlowSwitchClause); + } + else if (flags & FlowFlags.Label) { + if ((flow as FlowLabel).antecedents!.length === 1) { + flow = (flow as FlowLabel).antecedents![0]; + continue; + } + type = flags & FlowFlags.BranchLabel ? + getTypeAtFlowBranchLabel(flow as FlowLabel) : + getTypeAtFlowLoopLabel(flow as FlowLabel); + } + else if (flags & FlowFlags.ArrayMutation) { + type = getTypeAtFlowArrayMutation(flow as FlowArrayMutation); + if (!type) { + flow = (flow as FlowArrayMutation).antecedent; + continue; + } + } + else if (flags & FlowFlags.ReduceLabel) { + const target = (flow as FlowReduceLabel).target; + const saveAntecedents = target.antecedents; + target.antecedents = (flow as FlowReduceLabel).antecedents; + type = getTypeAtFlowNode((flow as FlowReduceLabel).antecedent); + target.antecedents = saveAntecedents; + } + else if (flags & FlowFlags.Start) { + // Check if we should continue with the control flow of the containing function. + const container = (flow as FlowStart).node; + if (container && container !== flowContainer && + reference.kind !== SyntaxKind.PropertyAccessExpression && + reference.kind !== SyntaxKind.ElementAccessExpression && + reference.kind !== SyntaxKind.ThisKeyword) { + flow = container.flowNode!; + continue; + } + // At the top of the flow we have the initial type. + type = initialType; + } + else { + // Unreachable code errors are reported in the binding phase. Here we + // simply return the non-auto declared type to reduce follow-on errors. + type = convertAutoToAny(declaredType); + } + if (sharedFlow) { + // Record visited node and the associated type in the cache. + sharedFlowNodes[sharedFlowCount] = sharedFlow; + sharedFlowTypes[sharedFlowCount] = type; + sharedFlowCount++; + } + flowDepth--; + return type; } } - function findFirstSuperCall(node: Node): SuperCall | undefined { - return isSuperCall(node) ? node : - isFunctionLike(node) ? undefined : - forEachChild(node, findFirstSuperCall); - } - - /** - * Check if the given class-declaration extends null then return true. - * Otherwise, return false - * @param classDecl a class declaration to check if it extends null - */ - function classDeclarationExtendsNull(classDecl: ClassDeclaration): boolean { - const classSymbol = getSymbolOfNode(classDecl); - const classInstanceType = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType; - const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType); - - return baseConstructorType === nullWideningType; + function getInitialOrAssignedType(flow: FlowAssignment) { + const node = flow.node; + return getNarrowableTypeForReference(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? + getInitialType(node as VariableDeclaration | BindingElement) : + getAssignedType(node), reference); } - function checkThisBeforeSuper(node: Node, container: Node, diagnosticMessage: DiagnosticMessage) { - const containingClassDecl = container.parent as ClassDeclaration; - const baseTypeNode = getClassExtendsHeritageElement(containingClassDecl); - - // If a containing class does not have extends clause or the class extends null - // skip checking whether super statement is called before "this" accessing. - if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) { - if (node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) { - error(node, diagnosticMessage); + function getTypeAtFlowAssignment(flow: FlowAssignment) { + const node = flow.node; + // Assignments only narrow the computed type if the declared type is a union type. Thus, we + // only need to evaluate the assigned type if the declared type is a union type. + if (isMatchingReference(reference, node)) { + if (!isReachableFlowNode(flow)) { + return unreachableNeverType; + } + if (getAssignmentTargetKind(node) === AssignmentKind.Compound) { + const flowType = getTypeAtFlowNode(flow.antecedent); + return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType)); + } + if (declaredType === autoType || declaredType === autoArrayType) { + if (isEmptyArrayAssignment(node)) { + return getEvolvingArrayType(neverType); + } + const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow)); + return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; + } + if (declaredType.flags & TypeFlags.Union) { + return getAssignmentReducedType(declaredType as UnionType, getInitialOrAssignedType(flow)); + } + return declaredType; + } + // We didn't have a direct match. However, if the reference is a dotted name, this + // may be an assignment to a left hand part of the reference. For example, for a + // reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case, + // return the declared type. + if (containsMatchingReference(reference, node)) { + if (!isReachableFlowNode(flow)) { + return unreachableNeverType; + } + // A matching dotted name might also be an expando property on a function *expression*, + // in which case we continue control flow analysis back to the function's declaration + if (isVariableDeclaration(node) && (isInJSFile(node) || isVarConst(node))) { + const init = getDeclaredExpandoInitializer(node); + if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) { + return getTypeAtFlowNode(flow.antecedent); + } } + return declaredType; } - } - - function checkThisInStaticClassFieldInitializerInDecoratedClass(thisExpression: Node, container: Node) { - if (isPropertyDeclaration(container) && hasStaticModifier(container) && - container.initializer && textRangeContainsPositionInclusive(container.initializer, thisExpression.pos) && length(container.parent.decorators)) { - error(thisExpression, Diagnostics.Cannot_use_this_in_a_static_property_initializer_of_a_decorated_class); + // for (const _ in ref) acts as a nonnull on ref + if (isVariableDeclaration(node) && node.parent.parent.kind === SyntaxKind.ForInStatement && isMatchingReference(reference, node.parent.parent.expression)) { + return getNonNullableTypeIfNeeded(getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent))); } + // Assignment doesn't affect reference + return undefined; } - function checkThisExpression(node: Node): Type { - const isNodeInTypeQuery = isInTypeQuery(node); - // Stop at the first arrow function so that we can - // tell whether 'this' needs to be captured. - let container = getThisContainer(node, /* includeArrowFunctions */ true); - let capturedByArrowFunction = false; - - if (container.kind === SyntaxKind.Constructor) { - checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); - } - - // Now skip arrow functions to get the "real" owner of 'this'. - if (container.kind === SyntaxKind.ArrowFunction) { - container = getThisContainer(container, /* includeArrowFunctions */ false); - capturedByArrowFunction = true; + function narrowTypeByAssertion(type: Type, expr: Expression): Type { + const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + if (node.kind === SyntaxKind.FalseKeyword) { + return unreachableNeverType; } - - checkThisInStaticClassFieldInitializerInDecoratedClass(node, container); - switch (container.kind) { - case SyntaxKind.ModuleDeclaration: - error(node, Diagnostics.this_cannot_be_referenced_in_a_module_or_namespace_body); - // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks - break; - case SyntaxKind.EnumDeclaration: - error(node, Diagnostics.this_cannot_be_referenced_in_current_location); - // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks - break; - case SyntaxKind.Constructor: - if (isInConstructorArgumentInitializer(node, container)) { - error(node, Diagnostics.this_cannot_be_referenced_in_constructor_arguments); - // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks - } - break; - case SyntaxKind.ComputedPropertyName: - error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name); - break; + if (node.kind === SyntaxKind.BinaryExpression) { + if ((node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + return narrowTypeByAssertion(narrowTypeByAssertion(type, (node as BinaryExpression).left), (node as BinaryExpression).right); + } + if ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken) { + return getUnionType([narrowTypeByAssertion(type, (node as BinaryExpression).left), narrowTypeByAssertion(type, (node as BinaryExpression).right)]); + } } + return narrowType(type, node, /*assumeTrue*/ true); + } - // When targeting es6, mark that we'll need to capture `this` in its lexically bound scope. - if (!isNodeInTypeQuery && capturedByArrowFunction && languageVersion < ScriptTarget.ES2015) { - captureLexicalThis(node, container); + function getTypeAtFlowCall(flow: FlowCall): FlowType | undefined { + const signature = getEffectsSignature(flow.node); + if (signature) { + const predicate = getTypePredicateOfSignature(signature); + if (predicate && (predicate.kind === TypePredicateKind.AssertsThis || predicate.kind === TypePredicateKind.AssertsIdentifier)) { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = finalizeEvolvingArrayType(getTypeFromFlowType(flowType)); + const narrowedType = predicate.type ? narrowTypeByTypePredicate(type, predicate, flow.node, /*assumeTrue*/ true) : + predicate.kind === TypePredicateKind.AssertsIdentifier && predicate.parameterIndex >= 0 && predicate.parameterIndex < flow.node.arguments.length ? narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]) : + type; + return narrowedType === type ? flowType : createFlowType(narrowedType, isIncomplete(flowType)); + } + if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { + return unreachableNeverType; + } } + return undefined; + } - const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container); - if (noImplicitThis) { - const globalThisType = getTypeOfSymbol(globalThisSymbol); - if (type === globalThisType && capturedByArrowFunction) { - error(node, Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this); - } - else if (!type) { - // With noImplicitThis, functions may not reference 'this' if it has type 'any' - const diag = error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation); - if (!isSourceFile(container)) { - const outsideThis = tryGetThisTypeAt(container); - if (outsideThis && outsideThis !== globalThisType) { - addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container)); + function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType | undefined { + if (declaredType === autoType || declaredType === autoArrayType) { + const node = flow.node; + const expr = node.kind === SyntaxKind.CallExpression ? + (node.expression as PropertyAccessExpression).expression : + (node.left as ElementAccessExpression).expression; + if (isMatchingReference(reference, getReferenceCandidate(expr))) { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = getTypeFromFlowType(flowType); + if (getObjectFlags(type) & ObjectFlags.EvolvingArray) { + let evolvedType = type as EvolvingArrayType; + if (node.kind === SyntaxKind.CallExpression) { + for (const arg of node.arguments) { + evolvedType = addEvolvingArrayElementType(evolvedType, arg); + } + } + else { + // We must get the context free expression type so as to not recur in an uncached fashion on the LHS (which causes exponential blowup in compile time) + const indexType = getContextFreeTypeOfExpression((node.left as ElementAccessExpression).argumentExpression); + if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { + evolvedType = addEvolvingArrayElementType(evolvedType, node.right); + } } + return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); } + return flowType; } } - return type || anyType; + return undefined; } - function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined { - const isInJS = isInJSFile(node); - if (isFunctionLike(container) && - (!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) { - let thisType = getThisTypeOfDeclaration(container) || isInJS && getTypeForThisExpressionFromJSDoc(container); - // Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated. - // If this is a function in a JS file, it might be a class method. - if (!thisType) { - const className = getClassNameFromPrototypeMethod(container); - if (isInJS && className) { - const classSymbol = checkExpression(className).symbol; - if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) { - thisType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType; - } + function getTypeAtFlowCondition(flow: FlowCondition): FlowType { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = getTypeFromFlowType(flowType); + if (type.flags & TypeFlags.Never) { + return flowType; + } + // If we have an antecedent type (meaning we're reachable in some way), we first + // attempt to narrow the antecedent type. If that produces the never type, and if + // the antecedent type is incomplete (i.e. a transient type in a loop), then we + // take the type guard as an indication that control *could* reach here once we + // have the complete type. We proceed by switching to the silent never type which + // doesn't report errors when operators are applied to it. Note that this is the + // *only* place a silent never type is ever generated. + const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0; + const nonEvolvingType = finalizeEvolvingArrayType(type); + const narrowedType = narrowType(nonEvolvingType, flow.node, assumeTrue); + if (narrowedType === nonEvolvingType) { + return flowType; + } + return createFlowType(narrowedType, isIncomplete(flowType)); + } + + function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { + const expr = flow.switchStatement.expression; + const flowType = getTypeAtFlowNode(flow.antecedent); + let type = getTypeFromFlowType(flowType); + if (isMatchingReference(reference, expr)) { + type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + } + else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { + type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + } + else { + if (strictNullChecks) { + if (optionalChainContainsReference(expr, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, + t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); } - else if (isJSConstructor(container)) { - thisType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType; + else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, + t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined")); } - thisType ||= getContextualThisParameterType(container); } - - if (thisType) { - return getFlowTypeOfReference(node, thisType); + const access = getDiscriminantPropertyAccess(expr, type); + if (access) { + type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.switchStatement, flow.clauseStart, flow.clauseEnd); } } + return createFlowType(type, isIncomplete(flowType)); + } - if (isClassLike(container.parent)) { - const symbol = getSymbolOfNode(container.parent); - const type = isStatic(container) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; - return getFlowTypeOfReference(node, type); - } - - if (isSourceFile(container)) { - // look up in the source file's locals or exports - if (container.commonJsModuleIndicator) { - const fileSymbol = getSymbolOfNode(container); - return fileSymbol && getTypeOfSymbol(fileSymbol); + function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType { + const antecedentTypes: Type[] = []; + let subtypeReduction = false; + let seenIncomplete = false; + let bypassFlow: FlowSwitchClause | undefined; + for (const antecedent of flow.antecedents!) { + if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).clauseStart === (antecedent as FlowSwitchClause).clauseEnd) { + // The antecedent is the bypass branch of a potentially exhaustive switch statement. + bypassFlow = antecedent as FlowSwitchClause; + continue; } - else if (container.externalModuleIndicator) { - // TODO: Maybe issue a better error than 'object is possibly undefined' - return undefinedType; + const flowType = getTypeAtFlowNode(antecedent); + const type = getTypeFromFlowType(flowType); + // If the type at a particular antecedent path is the declared type and the + // reference is known to always be assigned (i.e. when declared and initial types + // are the same), there is no reason to process more antecedents since the only + // possible outcome is subtypes that will be removed in the final union type anyway. + if (type === declaredType && declaredType === initialType) { + return type; } - else if (includeGlobalThis) { - return getTypeOfSymbol(globalThisSymbol); + pushIfUnique(antecedentTypes, type); + // If an antecedent type is not a subset of the declared type, we need to perform + // subtype reduction. This happens when a "foreign" type is injected into the control + // flow using the instanceof operator or a user defined type predicate. + if (!isTypeSubsetOf(type, declaredType)) { + subtypeReduction = true; } - } - } - - function getExplicitThisType(node: Expression) { - const container = getThisContainer(node, /*includeArrowFunctions*/ false); - if (isFunctionLike(container)) { - const signature = getSignatureFromDeclaration(container); - if (signature.thisParameter) { - return getExplicitTypeOfSymbol(signature.thisParameter); + if (isIncomplete(flowType)) { + seenIncomplete = true; } } - if (isClassLike(container.parent)) { - const symbol = getSymbolOfNode(container.parent); - return isStatic(container) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; + if (bypassFlow) { + const flowType = getTypeAtFlowNode(bypassFlow); + const type = getTypeFromFlowType(flowType); + // If the bypass flow contributes a type we haven't seen yet and the switch statement + // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase + // the risk of circularities, we only want to perform them when they make a difference. + if (!contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.switchStatement)) { + if (type === declaredType && declaredType === initialType) { + return type; + } + antecedentTypes.push(type); + if (!isTypeSubsetOf(type, declaredType)) { + subtypeReduction = true; + } + if (isIncomplete(flowType)) { + seenIncomplete = true; + } + } } + return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); } - function getClassNameFromPrototypeMethod(container: Node) { - // Check if it's the RHS of a x.prototype.y = function [name]() { .... } - if (container.kind === SyntaxKind.FunctionExpression && - isBinaryExpression(container.parent) && - getAssignmentDeclarationKind(container.parent) === AssignmentDeclarationKind.PrototypeProperty) { - // Get the 'x' of 'x.prototype.y = container' - return ((container.parent // x.prototype.y = container - .left as PropertyAccessExpression) // x.prototype.y - .expression as PropertyAccessExpression) // x.prototype - .expression; // x - } - // x.prototype = { method() { } } - else if (container.kind === SyntaxKind.MethodDeclaration && - container.parent.kind === SyntaxKind.ObjectLiteralExpression && - isBinaryExpression(container.parent.parent) && - getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.Prototype) { - return (container.parent.parent.left as PropertyAccessExpression).expression; + function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType { + // If we have previously computed the control flow type for the reference at + // this flow loop junction, return the cached type. + const id = getFlowNodeId(flow); + const cache = flowLoopCaches[id] || (flowLoopCaches[id] = new Map()); + const key = getOrSetCacheKey(); + if (!key) { + // No cache key is generated when binding patterns are in unnarrowable situations + return declaredType; } - // x.prototype = { method: function() { } } - else if (container.kind === SyntaxKind.FunctionExpression && - container.parent.kind === SyntaxKind.PropertyAssignment && - container.parent.parent.kind === SyntaxKind.ObjectLiteralExpression && - isBinaryExpression(container.parent.parent.parent) && - getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.Prototype) { - return (container.parent.parent.parent.left as PropertyAccessExpression).expression; + const cached = cache.get(key); + if (cached) { + return cached; } - // Object.defineProperty(x, "method", { value: function() { } }); - // Object.defineProperty(x, "method", { set: (x: () => void) => void }); - // Object.defineProperty(x, "method", { get: () => function() { }) }); - else if (container.kind === SyntaxKind.FunctionExpression && - isPropertyAssignment(container.parent) && - isIdentifier(container.parent.name) && - (container.parent.name.escapedText === "value" || container.parent.name.escapedText === "get" || container.parent.name.escapedText === "set") && - isObjectLiteralExpression(container.parent.parent) && - isCallExpression(container.parent.parent.parent) && - container.parent.parent.parent.arguments[2] === container.parent.parent && - getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { - return (container.parent.parent.parent.arguments[0] as PropertyAccessExpression).expression; + // If this flow loop junction and reference are already being processed, return + // the union of the types computed for each branch so far, marked as incomplete. + // It is possible to see an empty array in cases where loops are nested and the + // back edge of the outer loop reaches an inner loop that is already being analyzed. + // In such cases we restart the analysis of the inner loop, which will then see + // a non-empty in-process array for the outer loop and eventually terminate because + // the first antecedent of a loop junction is always the non-looping control flow + // path that leads to the top. + for (let i = flowLoopStart; i < flowLoopCount; i++) { + if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) { + return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], UnionReduction.Literal), /*incomplete*/ true); + } + } + // Add the flow loop junction and reference to the in-process stack and analyze + // each antecedent code path. + const antecedentTypes: Type[] = []; + let subtypeReduction = false; + let firstAntecedentType: FlowType | undefined; + for (const antecedent of flow.antecedents!) { + let flowType; + if (!firstAntecedentType) { + // The first antecedent of a loop junction is always the non-looping control + // flow path that leads to the top. + flowType = firstAntecedentType = getTypeAtFlowNode(antecedent); + } + else { + // All but the first antecedent are the looping control flow paths that lead + // back to the loop junction. We track these on the flow loop stack. + flowLoopNodes[flowLoopCount] = flow; + flowLoopKeys[flowLoopCount] = key; + flowLoopTypes[flowLoopCount] = antecedentTypes; + flowLoopCount++; + const saveFlowTypeCache = flowTypeCache; + flowTypeCache = undefined; + flowType = getTypeAtFlowNode(antecedent); + flowTypeCache = saveFlowTypeCache; + flowLoopCount--; + // If we see a value appear in the cache it is a sign that control flow analysis + // was restarted and completed by checkExpressionCached. We can simply pick up + // the resulting type and bail out. + const cached = cache.get(key); + if (cached) { + return cached; + } + } + const type = getTypeFromFlowType(flowType); + pushIfUnique(antecedentTypes, type); + // If an antecedent type is not a subset of the declared type, we need to perform + // subtype reduction. This happens when a "foreign" type is injected into the control + // flow using the instanceof operator or a user defined type predicate. + if (!isTypeSubsetOf(type, declaredType)) { + subtypeReduction = true; + } + // If the type at a particular antecedent path is the declared type there is no + // reason to process more antecedents since the only possible outcome is subtypes + // that will be removed in the final union type anyway. + if (type === declaredType) { + break; + } } - // Object.defineProperty(x, "method", { value() { } }); - // Object.defineProperty(x, "method", { set(x: () => void) {} }); - // Object.defineProperty(x, "method", { get() { return () => {} } }); - else if (isMethodDeclaration(container) && - isIdentifier(container.name) && - (container.name.escapedText === "value" || container.name.escapedText === "get" || container.name.escapedText === "set") && - isObjectLiteralExpression(container.parent) && - isCallExpression(container.parent.parent) && - container.parent.parent.arguments[2] === container.parent && - getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { - return (container.parent.parent.arguments[0] as PropertyAccessExpression).expression; + // The result is incomplete if the first antecedent (the non-looping control flow path) + // is incomplete. + const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal); + if (isIncomplete(firstAntecedentType!)) { + return createFlowType(result, /*incomplete*/ true); } + cache.set(key, result); + return result; } - function getTypeForThisExpressionFromJSDoc(node: Node) { - const jsdocType = getJSDocType(node); - if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) { - const jsDocFunctionType = jsdocType as JSDocFunctionType; - if (jsDocFunctionType.parameters.length > 0 && - jsDocFunctionType.parameters[0].name && - (jsDocFunctionType.parameters[0].name as Identifier).escapedText === InternalSymbolName.This) { - return getTypeFromTypeNode(jsDocFunctionType.parameters[0].type!); - } + // At flow control branch or loop junctions, if the type along every antecedent code path + // is an evolving array type, we construct a combined evolving array type. Otherwise we + // finalize all evolving array types. + function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: UnionReduction) { + if (isEvolvingArrayTypeList(types)) { + return getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))); } - const thisTag = getJSDocThisTag(node); - if (thisTag && thisTag.typeExpression) { - return getTypeFromTypeNode(thisTag.typeExpression); + const result = getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction); + if (result !== declaredType && result.flags & declaredType.flags & TypeFlags.Union && arraysEqual((result as UnionType).types, (declaredType as UnionType).types)) { + return declaredType; } + return result; } - function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { - return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl); - } - - function checkSuperExpression(node: Node): Type { - const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent as CallExpression).expression === node; - - const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true); - let container = immediateContainer; - let needToCaptureLexicalThis = false; - - // adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting - if (!isCallExpression) { - while (container && container.kind === SyntaxKind.ArrowFunction) { - container = getSuperContainer(container, /*stopOnFunctions*/ true); - needToCaptureLexicalThis = languageVersion < ScriptTarget.ES2015; - } - } - - const canUseSuperExpression = isLegalUsageOfSuperExpression(container); - let nodeCheckFlag: NodeCheckFlags = 0; - - if (!canUseSuperExpression) { - // issue more specific error if super is used in computed property name - // class A { foo() { return "1" }} - // class B { - // [super.foo()]() {} - // } - const current = findAncestor(node, n => n === container ? "quit" : n.kind === SyntaxKind.ComputedPropertyName); - if (current && current.kind === SyntaxKind.ComputedPropertyName) { - error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name); - } - else if (isCallExpression) { - error(node, Diagnostics.Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors); - } - else if (!container || !container.parent || !(isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression)) { - error(node, Diagnostics.super_can_only_be_referenced_in_members_of_derived_classes_or_object_literal_expressions); - } - else { - error(node, Diagnostics.super_property_access_is_permitted_only_in_a_constructor_member_function_or_member_accessor_of_a_derived_class); + function getCandidateDiscriminantPropertyAccess(expr: Expression) { + if (isBindingPattern(reference)) { + // When the reference is a binding pattern, we are narrowing a pesudo-reference in getNarrowedTypeOfSymbol. + // An identifier for a destructuring variable declared in the same binding pattern is a candidate. + if (isIdentifier(expr)) { + const symbol = getResolvedSymbol(expr); + const declaration = symbol.valueDeclaration; + if (declaration && isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && reference === declaration.parent) { + return declaration; + } } - return errorType; } - - if (!isCallExpression && immediateContainer.kind === SyntaxKind.Constructor) { - checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class); + else if (isAccessExpression(expr)) { + // An access expression is a candidate if the reference matches the left hand expression. + if (isMatchingReference(reference, expr.expression)) { + return expr; + } } - - if (isStatic(container) || isCallExpression) { - nodeCheckFlag = NodeCheckFlags.SuperStatic; - if (!isCallExpression && - languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021 && - (isPropertyDeclaration(container) || isClassStaticBlockDeclaration(container))) { - // for `super.x` or `super[x]` in a static initializer, mark all enclosing - // block scope containers so that we can report potential collisions with - // `Reflect`. - forEachEnclosingBlockScopeContainer(node.parent, current => { - if (!isSourceFile(current) || isExternalOrCommonJsModule(current)) { - getNodeLinks(current).flags |= NodeCheckFlags.ContainsSuperPropertyInStaticInitializer; + else if (isIdentifier(expr)) { + const symbol = getResolvedSymbol(expr); + if (isConstVariable(symbol)) { + const declaration = symbol.valueDeclaration!; + // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind' + if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) && + isMatchingReference(reference, declaration.initializer.expression)) { + return declaration.initializer; + } + // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind' + if (isBindingElement(declaration) && !declaration.initializer) { + const parent = declaration.parent.parent; + if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) && + isMatchingReference(reference, parent.initializer)) { + return declaration; } - }); + } } } - else { - nodeCheckFlag = NodeCheckFlags.SuperInstance; - } - - getNodeLinks(node).flags |= nodeCheckFlag; + return undefined; + } - // Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference. - // This is due to the fact that we emit the body of an async function inside of a generator function. As generator - // functions cannot reference `super`, we emit a helper inside of the method body, but outside of the generator. This helper - // uses an arrow function, which is permitted to reference `super`. - // - // There are two primary ways we can access `super` from within an async method. The first is getting the value of a property - // or indexed access on super, either as part of a right-hand-side expression or call expression. The second is when setting the value - // of a property or indexed access, either as part of an assignment expression or destructuring assignment. - // - // The simplest case is reading a value, in which case we will emit something like the following: - // - // // ts - // ... - // async asyncMethod() { - // let x = await super.asyncMethod(); - // return x; - // } - // ... - // - // // js - // ... - // asyncMethod() { - // const _super = Object.create(null, { - // asyncMethod: { get: () => super.asyncMethod }, - // }); - // return __awaiter(this, arguments, Promise, function *() { - // let x = yield _super.asyncMethod.call(this); - // return x; - // }); - // } - // ... - // - // The more complex case is when we wish to assign a value, especially as part of a destructuring assignment. As both cases - // are legal in ES6, but also likely less frequent, we only emit setters if there is an assignment: - // - // // ts - // ... - // async asyncMethod(ar: Promise) { - // [super.a, super.b] = await ar; - // } - // ... - // - // // js - // ... - // asyncMethod(ar) { - // const _super = Object.create(null, { - // a: { get: () => super.a, set: (v) => super.a = v }, - // b: { get: () => super.b, set: (v) => super.b = v } - // }; - // return __awaiter(this, arguments, Promise, function *() { - // [_super.a, _super.b] = yield ar; - // }); - // } - // ... - // - // Creating an object that has getter and setters instead of just an accessor function is required for destructuring assignments - // as a call expression cannot be used as the target of a destructuring assignment while a property access can. - // - // For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations. - if (container.kind === SyntaxKind.MethodDeclaration && hasSyntacticModifier(container, ModifierFlags.Async)) { - if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) { - getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding; - } - else { - getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuper; + function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { + const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType; + if (type.flags & TypeFlags.Union) { + const access = getCandidateDiscriminantPropertyAccess(expr); + if (access) { + const name = getAccessedPropertyName(access); + if (name && isDiscriminantProperty(type, name)) { + return access; + } } } + return undefined; + } - if (needToCaptureLexicalThis) { - // call expressions are allowed only in constructors so they should always capture correct 'this' - // super property access expressions can also appear in arrow functions - - // in this case they should also use correct lexical this - captureLexicalThis(node.parent, container); + function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement, narrowType: (t: Type) => Type): Type { + const propName = getAccessedPropertyName(access); + if (propName === undefined) { + return type; + } + const removeNullable = strictNullChecks && isOptionalChain(access) && maybeTypeOfKind(type, TypeFlags.Nullable); + let propType = getTypeOfPropertyOfType(removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type, propName); + if (!propType) { + return type; } + propType = removeNullable ? getOptionalType(propType) : propType; + const narrowedPropType = narrowType(propType); + return filterType(type, t => { + const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName); + return !(narrowedPropType.flags & TypeFlags.Never) && isTypeComparableTo(narrowedPropType, discriminantType); + }); + } - if (container.parent.kind === SyntaxKind.ObjectLiteralExpression) { - if (languageVersion < ScriptTarget.ES2015) { - error(node, Diagnostics.super_is_only_allowed_in_members_of_object_literal_expressions_when_option_target_is_ES2015_or_higher); - return errorType; - } - else { - // for object literal assume that type of 'super' is 'any' - return anyType; + function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, operator: SyntaxKind, value: Expression, assumeTrue: boolean) { + if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && type.flags & TypeFlags.Union) { + const keyPropertyName = getKeyPropertyName(type as UnionType); + if (keyPropertyName && keyPropertyName === getAccessedPropertyName(access)) { + const candidate = getConstituentTypeForKeyType(type as UnionType, getTypeOfExpression(value)); + if (candidate) { + return operator === (assumeTrue ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken) ? candidate : + isUnitType(getTypeOfPropertyOfType(candidate, keyPropertyName) || unknownType) ? removeType(type, candidate) : + type; + } } } + return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); + } - // at this point the only legal case for parent is ClassLikeDeclaration - const classLikeDeclaration = container.parent as ClassLikeDeclaration; - if (!getClassExtendsHeritageElement(classLikeDeclaration)) { - error(node, Diagnostics.super_can_only_be_referenced_in_a_derived_class); - return errorType; + function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { + if (clauseStart < clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) { + const clauseTypes = getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd); + const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType)); + if (candidate !== unknownType) { + return candidate; + } } + return narrowTypeByDiscriminant(type, access, t => narrowTypeBySwitchOnDiscriminant(t, switchStatement, clauseStart, clauseEnd)); + } - const classType = getDeclaredTypeOfSymbol(getSymbolOfNode(classLikeDeclaration)) as InterfaceType; - const baseClassType = classType && getBaseTypes(classType)[0]; - if (!baseClassType) { - return errorType; + function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { + if (isMatchingReference(reference, expr)) { + return type.flags & TypeFlags.Unknown && assumeTrue ? nonNullUnknownType : + getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); } - - if (container.kind === SyntaxKind.Constructor && isInConstructorArgumentInitializer(node, container)) { - // issue custom error message for super property access in constructor arguments (to be aligned with old compiler) - error(node, Diagnostics.super_cannot_be_referenced_in_constructor_arguments); - return errorType; + if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) { + type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } - - return nodeCheckFlag === NodeCheckFlags.SuperStatic - ? getBaseConstructorTypeOfClass(classType) - : getTypeWithThisArgument(baseClassType, classType.thisType); - - function isLegalUsageOfSuperExpression(container: Node): boolean { - if (!container) { - return false; - } - - if (isCallExpression) { - // TS 1.0 SPEC (April 2014): 4.8.1 - // Super calls are only permitted in constructors of derived classes - return container.kind === SyntaxKind.Constructor; - } - else { - // TS 1.0 SPEC (April 2014) - // 'super' property access is allowed - // - In a constructor, instance member function, instance member accessor, or instance member variable initializer where this references a derived class instance - // - In a static member function or static member accessor - - // topmost container must be something that is directly nested in the class declaration\object literal expression - if (isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression) { - if (isStatic(container)) { - return container.kind === SyntaxKind.MethodDeclaration || - container.kind === SyntaxKind.MethodSignature || - container.kind === SyntaxKind.GetAccessor || - container.kind === SyntaxKind.SetAccessor || - container.kind === SyntaxKind.PropertyDeclaration || - container.kind === SyntaxKind.ClassStaticBlockDeclaration; - } - else { - return container.kind === SyntaxKind.MethodDeclaration || - container.kind === SyntaxKind.MethodSignature || - container.kind === SyntaxKind.GetAccessor || - container.kind === SyntaxKind.SetAccessor || - container.kind === SyntaxKind.PropertyDeclaration || - container.kind === SyntaxKind.PropertySignature || - container.kind === SyntaxKind.Constructor; - } - } - } - - return false; + const access = getDiscriminantPropertyAccess(expr, type); + if (access) { + return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); } + return type; } - function getContainingObjectLiteral(func: SignatureDeclaration): ObjectLiteralExpression | undefined { - return (func.kind === SyntaxKind.MethodDeclaration || - func.kind === SyntaxKind.GetAccessor || - func.kind === SyntaxKind.SetAccessor) && func.parent.kind === SyntaxKind.ObjectLiteralExpression ? func.parent : - func.kind === SyntaxKind.FunctionExpression && func.parent.kind === SyntaxKind.PropertyAssignment ? func.parent.parent as ObjectLiteralExpression : - undefined; - } - - function getThisTypeArgument(type: Type): Type | undefined { - return getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target === globalThisType ? getTypeArguments(type as TypeReference)[0] : undefined; + function isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) { + const prop = getPropertyOfType(type, propName); + if (prop) { + return prop.flags & SymbolFlags.Optional ? true : assumeTrue; + } + return getApplicableIndexInfoForName(type, propName) ? true : !assumeTrue; } - function getThisTypeFromContextualType(type: Type): Type | undefined { - return mapType(type, t => { - return t.flags & TypeFlags.Intersection ? forEach((t as IntersectionType).types, getThisTypeArgument) : getThisTypeArgument(t); - }); + function narrowByInKeyword(type: Type, name: __String, assumeTrue: boolean) { + if (type.flags & TypeFlags.Union + || type.flags & TypeFlags.Object && declaredType !== type + || isThisTypeParameter(type) + || type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, t => t.symbol !== globalThisSymbol)) { + return filterType(type, t => isTypePresencePossible(t, name, assumeTrue)); + } + return type; } - function getContextualThisParameterType(func: SignatureDeclaration): Type | undefined { - if (func.kind === SyntaxKind.ArrowFunction) { - return undefined; - } - if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { - const contextualSignature = getContextualSignature(func); - if (contextualSignature) { - const thisParameter = contextualSignature.thisParameter; - if (thisParameter) { - return getTypeOfSymbol(thisParameter); - } - } - } - const inJs = isInJSFile(func); - if (noImplicitThis || inJs) { - const containingLiteral = getContainingObjectLiteral(func); - if (containingLiteral) { - // We have an object literal method. Check if the containing object literal has a contextual type - // that includes a ThisType. If so, T is the contextual type for 'this'. We continue looking in - // any directly enclosing object literals. - const contextualType = getApparentTypeOfContextualType(containingLiteral); - let literal = containingLiteral; - let type = contextualType; - while (type) { - const thisType = getThisTypeFromContextualType(type); - if (thisType) { - return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral))); - } - if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { - break; + function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + switch (expr.operatorToken.kind) { + case SyntaxKind.EqualsToken: + case SyntaxKind.BarBarEqualsToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: + case SyntaxKind.QuestionQuestionEqualsToken: + return narrowTypeByTruthiness(narrowType(type, expr.right, assumeTrue), expr.left, assumeTrue); + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + const operator = expr.operatorToken.kind; + const left = getReferenceCandidate(expr.left); + const right = getReferenceCandidate(expr.right); + if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) { + return narrowTypeByTypeof(type, left as TypeOfExpression, operator, right, assumeTrue); + } + if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) { + return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue); + } + if (isMatchingReference(reference, left)) { + return narrowTypeByEquality(type, operator, right, assumeTrue); + } + if (isMatchingReference(reference, right)) { + return narrowTypeByEquality(type, operator, left, assumeTrue); + } + if (strictNullChecks) { + if (optionalChainContainsReference(left, reference)) { + type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue); } - literal = literal.parent.parent as ObjectLiteralExpression; - type = getApparentTypeOfContextualType(literal); - } - // There was no contextual ThisType for the containing object literal, so the contextual type - // for 'this' is the non-null form of the contextual type for the containing object literal or - // the type of the object literal itself. - return getWidenedType(contextualType ? getNonNullableType(contextualType) : checkExpressionCached(containingLiteral)); - } - // In an assignment of the form 'obj.xxx = function(...)' or 'obj[xxx] = function(...)', the - // contextual type for 'this' is 'obj'. - const parent = walkUpParenthesizedExpressions(func.parent); - if (parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - const target = (parent as BinaryExpression).left; - if (isAccessExpression(target)) { - const { expression } = target; - // Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }` - if (inJs && isIdentifier(expression)) { - const sourceFile = getSourceFileOfNode(parent); - if (sourceFile.commonJsModuleIndicator && getResolvedSymbol(expression) === sourceFile.symbol) { - return undefined; - } + else if (optionalChainContainsReference(right, reference)) { + type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue); } - - return getWidenedType(checkExpressionCached(expression)); } - } + const leftAccess = getDiscriminantPropertyAccess(left, type); + if (leftAccess) { + return narrowTypeByDiscriminantProperty(type, leftAccess, operator, right, assumeTrue); + } + const rightAccess = getDiscriminantPropertyAccess(right, type); + if (rightAccess) { + return narrowTypeByDiscriminantProperty(type, rightAccess, operator, left, assumeTrue); + } + if (isMatchingConstructorReference(left)) { + return narrowTypeByConstructor(type, operator, right, assumeTrue); + } + if (isMatchingConstructorReference(right)) { + return narrowTypeByConstructor(type, operator, left, assumeTrue); + } + break; + case SyntaxKind.InstanceOfKeyword: + return narrowTypeByInstanceof(type, expr, assumeTrue); + case SyntaxKind.InKeyword: + if (isPrivateIdentifier(expr.left)) { + return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue); + } + const target = getReferenceCandidate(expr.right); + const leftType = getTypeOfNode(expr.left); + if (leftType.flags & TypeFlags.StringLiteral) { + const name = escapeLeadingUnderscores((leftType as StringLiteralType).value); + if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target) && + getAccessedPropertyName(reference) === name) { + return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); + } + if (isMatchingReference(reference, target)) { + return narrowByInKeyword(type, name, assumeTrue); + } + } + break; + case SyntaxKind.CommaToken: + return narrowType(type, expr.right, assumeTrue); + // Ordinarily we won't see && and || expressions in control flow analysis because the Binder breaks those + // expressions down to individual conditional control flows. However, we may encounter them when analyzing + // aliased conditional expressions. + case SyntaxKind.AmpersandAmpersandToken: + return assumeTrue ? + narrowType(narrowType(type, expr.left, /*assumeTrue*/ true), expr.right, /*assumeTrue*/ true) : + getUnionType([narrowType(type, expr.left, /*assumeTrue*/ false), narrowType(type, expr.right, /*assumeTrue*/ false)]); + case SyntaxKind.BarBarToken: + return assumeTrue ? + getUnionType([narrowType(type, expr.left, /*assumeTrue*/ true), narrowType(type, expr.right, /*assumeTrue*/ true)]) : + narrowType(narrowType(type, expr.left, /*assumeTrue*/ false), expr.right, /*assumeTrue*/ false); } - return undefined; + return type; } - // Return contextual type of parameter or undefined if no contextual type is available - function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined { - const func = parameter.parent; - if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) { - return undefined; + function narrowTypeByPrivateIdentifierInInExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + const target = getReferenceCandidate(expr.right); + if (!isMatchingReference(reference, target)) { + return type; } - const iife = getImmediatelyInvokedFunctionExpression(func); - if (iife && iife.arguments) { - const args = getEffectiveCallArguments(iife); - const indexOfParameter = func.parameters.indexOf(parameter); - if (parameter.dotDotDotToken) { - return getSpreadArgumentType(args, indexOfParameter, args.length, anyType, /*context*/ undefined, CheckMode.Normal); - } - const links = getNodeLinks(iife); - const cached = links.resolvedSignature; - links.resolvedSignature = anySignature; - const type = indexOfParameter < args.length ? - getWidenedLiteralType(checkExpression(args[indexOfParameter])) : - parameter.initializer ? undefined : undefinedWideningType; - links.resolvedSignature = cached; + + Debug.assertNode(expr.left, isPrivateIdentifier); + const symbol = getSymbolForPrivateIdentifierExpression(expr.left); + if (symbol === undefined) { return type; } - const contextualSignature = getContextualSignature(func); - if (contextualSignature) { - const index = func.parameters.indexOf(parameter) - (getThisParameter(func) ? 1 : 0); - return parameter.dotDotDotToken && lastOrUndefined(func.parameters) === parameter ? - getRestTypeAtPosition(contextualSignature, index) : - tryGetTypeAtPosition(contextualSignature, index); + const classSymbol = symbol.parent!; + const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration")) + ? getTypeOfSymbol(classSymbol) as InterfaceType + : getDeclaredTypeOfSymbol(classSymbol); + return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom); + } + + function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { + // We are in a branch of obj?.foo === value (or any one of the other equality operators). We narrow obj as follows: + // When operator is === and type of value excludes undefined, null and undefined is removed from type of obj in true branch. + // When operator is !== and type of value excludes undefined, null and undefined is removed from type of obj in false branch. + // When operator is == and type of value excludes null and undefined, null and undefined is removed from type of obj in true branch. + // When operator is != and type of value excludes null and undefined, null and undefined is removed from type of obj in false branch. + // When operator is === and type of value is undefined, null and undefined is removed from type of obj in false branch. + // When operator is !== and type of value is undefined, null and undefined is removed from type of obj in true branch. + // When operator is == and type of value is null or undefined, null and undefined is removed from type of obj in false branch. + // When operator is != and type of value is null or undefined, null and undefined is removed from type of obj in true branch. + const equalsOperator = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; + const nullableFlags = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? TypeFlags.Nullable : TypeFlags.Undefined; + const valueType = getTypeOfExpression(value); + // Note that we include any and unknown in the exclusion test because their domain includes null and undefined. + const removeNullable = equalsOperator !== assumeTrue && everyType(valueType, t => !!(t.flags & nullableFlags)) || + equalsOperator === assumeTrue && everyType(valueType, t => !(t.flags & (TypeFlags.AnyOrUnknown | nullableFlags))); + return removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + } + + function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { + if (type.flags & TypeFlags.Any) { + return type; } - } - - function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration): Type | undefined { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - return getTypeFromTypeNode(typeNode); + if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { + assumeTrue = !assumeTrue; } - switch (declaration.kind) { - case SyntaxKind.Parameter: - return getContextuallyTypedParameterType(declaration); - case SyntaxKind.BindingElement: - return getContextualTypeForBindingElement(declaration); - case SyntaxKind.PropertyDeclaration: - if (isStatic(declaration)) { - return getContextualTypeForStaticPropertyDeclaration(declaration); - } - // By default, do nothing and return undefined - only the above cases have context implied by a parent + const valueType = getTypeOfExpression(value); + if (assumeTrue && (type.flags & TypeFlags.Unknown) && (operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken) && (valueType.flags & TypeFlags.Null)) { + return getUnionType([nullType, undefinedType]); + } + if ((type.flags & TypeFlags.Unknown) && assumeTrue && (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) { + if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { + return valueType; + } + if (valueType.flags & TypeFlags.Object) { + return nonPrimitiveType; + } + return type; + } + if (valueType.flags & TypeFlags.Nullable) { + if (!strictNullChecks) { + return type; + } + const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; + const facts = doubleEquals ? + assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull : + valueType.flags & TypeFlags.Null ? + assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull : + assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; + return type.flags & TypeFlags.Unknown && facts & (TypeFacts.NENull | TypeFacts.NEUndefinedOrNull) ? nonNullUnknownType : getTypeWithFacts(type, facts); + } + if (assumeTrue) { + const filterFn: (t: Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ? + t => areTypesComparable(t, valueType) || isCoercibleUnderDoubleEquals(t, valueType) : + t => areTypesComparable(t, valueType); + return replacePrimitivesWithLiterals(filterType(type, filterFn), valueType); } + if (isUnitType(valueType)) { + return filterType(type, t => !(isUnitLikeType(t) && areTypesComparable(t, valueType))); + } + return type; } - function getContextualTypeForBindingElement(declaration: BindingElement): Type | undefined { - const parent = declaration.parent.parent; - const name = declaration.propertyName || declaration.name; - const parentType = getContextualTypeForVariableLikeDeclaration(parent) || - parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent); - if (!parentType || isBindingPattern(name) || isComputedNonLiteralName(name)) return undefined; - if (parent.name.kind === SyntaxKind.ArrayBindingPattern) { - const index = indexOfNode(declaration.parent.elements, declaration); - if (index < 0) return undefined; - return getContextualTypeForElementExpression(parentType, index); - } - const nameType = getLiteralTypeFromPropertyName(name); - if (isTypeUsableAsPropertyName(nameType)) { - const text = getPropertyNameFromType(nameType); - return getTypeOfPropertyOfType(parentType, text); - } - } - - function getContextualTypeForStaticPropertyDeclaration(declaration: PropertyDeclaration): Type | undefined { - const parentType = isExpression(declaration.parent) && getContextualType(declaration.parent); - if (!parentType) return undefined; - return getTypeOfPropertyOfContextualType(parentType, getSymbolOfNode(declaration).escapedName); - } - - // In a variable, parameter or property declaration with a type annotation, - // the contextual type of an initializer expression is the type of the variable, parameter or property. - // Otherwise, in a parameter declaration of a contextually typed function expression, - // the contextual type of an initializer expression is the contextual type of the parameter. - // Otherwise, in a variable or parameter declaration with a binding pattern name, - // the contextual type of an initializer expression is the type implied by the binding pattern. - // Otherwise, in a binding pattern inside a variable or parameter declaration, - // the contextual type of an initializer expression is the type annotation of the containing declaration, if present. - function getContextualTypeForInitializerExpression(node: Expression, contextFlags?: ContextFlags): Type | undefined { - const declaration = node.parent as VariableLikeDeclaration; - if (hasInitializer(declaration) && node === declaration.initializer) { - const result = getContextualTypeForVariableLikeDeclaration(declaration); - if (result) { - return result; - } - if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable - return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false); + function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { + // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands + if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { + assumeTrue = !assumeTrue; + } + const target = getReferenceCandidate(typeOfExpr.expression); + if (!isMatchingReference(reference, target)) { + if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { + return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } + return type; } - return undefined; + if (type.flags & TypeFlags.Any && literal.text === "function") { + return type; + } + if (assumeTrue && type.flags & TypeFlags.Unknown && literal.text === "object") { + // The non-null unknown type is used to track whether a previous narrowing operation has removed the null type + // from the unknown type. For example, the expression `x && typeof x === 'object'` first narrows x to the non-null + // unknown type, and then narrows that to the non-primitive type. + return type === nonNullUnknownType ? nonPrimitiveType : getUnionType([nonPrimitiveType, nullType]); + } + const facts = assumeTrue ? + typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject : + typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject; + const impliedType = getImpliedTypeFromTypeofGuard(type, literal.text); + return getTypeWithFacts(assumeTrue && impliedType ? mapType(type, narrowUnionMemberByTypeof(impliedType)) : type, facts); } - function getContextualTypeForReturnExpression(node: Expression): Type | undefined { - const func = getContainingFunction(node); - if (func) { - let contextualReturnType = getContextualReturnType(func); - if (contextualReturnType) { - const functionFlags = getFunctionFlags(func); - if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function - const use = functionFlags & FunctionFlags.Async ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType; - const iterationTypes = getIterationTypesOfIterable(contextualReturnType, use, /*errorNode*/ undefined); - if (!iterationTypes) { - return undefined; + function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: Type) => boolean) { + const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck); + return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + } + + function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { + // We only narrow if all case expressions specify + // values with unit types, except for the case where + // `type` is unknown. In this instance we map object + // types to the nonPrimitive type and narrow with that. + const switchTypes = getSwitchClauseTypes(switchStatement); + if (!switchTypes.length) { + return type; + } + const clauseTypes = switchTypes.slice(clauseStart, clauseEnd); + const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType); + if ((type.flags & TypeFlags.Unknown) && !hasDefaultClause) { + let groundClauseTypes: Type[] | undefined; + for (let i = 0; i < clauseTypes.length; i += 1) { + const t = clauseTypes[i]; + if (t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { + if (groundClauseTypes !== undefined) { + groundClauseTypes.push(t); } - contextualReturnType = iterationTypes.returnType; - // falls through to unwrap Promise for AsyncGenerators } - - if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function - // Get the awaited type without the `Awaited` alias - const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeNoAlias); - return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); + else if (t.flags & TypeFlags.Object) { + if (groundClauseTypes === undefined) { + groundClauseTypes = clauseTypes.slice(0, i); + } + groundClauseTypes.push(nonPrimitiveType); + } + else { + return type; } - - return contextualReturnType; // Regular function or Generator function } + return getUnionType(groundClauseTypes === undefined ? clauseTypes : groundClauseTypes); } - return undefined; - } - - function getContextualTypeForAwaitOperand(node: AwaitExpression, contextFlags?: ContextFlags): Type | undefined { - const contextualType = getContextualType(node, contextFlags); - if (contextualType) { - const contextualAwaitedType = getAwaitedTypeNoAlias(contextualType); - return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); + const discriminantType = getUnionType(clauseTypes); + const caseType = + discriminantType.flags & TypeFlags.Never ? neverType : + replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType); + if (!hasDefaultClause) { + return caseType; } - return undefined; + const defaultType = filterType(type, t => !(isUnitLikeType(t) && contains(switchTypes, getRegularTypeOfLiteralType(extractUnitType(t))))); + return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]); } - function getContextualTypeForYieldOperand(node: YieldExpression): Type | undefined { - const func = getContainingFunction(node); - if (func) { - const functionFlags = getFunctionFlags(func); - const contextualReturnType = getContextualReturnType(func); - if (contextualReturnType) { - return node.asteriskToken - ? contextualReturnType - : getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, (functionFlags & FunctionFlags.Async) !== 0); - } + function getImpliedTypeFromTypeofGuard(type: Type, text: string) { + switch (text) { + case "function": + return type.flags & TypeFlags.Any ? type : globalFunctionType; + case "object": + return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type; + default: + return typeofTypesByName.get(text); } - - return undefined; } - function isInParameterInitializerBeforeContainingFunction(node: Node) { - let inBindingInitializer = false; - while (node.parent && !isFunctionLike(node.parent)) { - if (isParameter(node.parent) && (inBindingInitializer || node.parent.initializer === node)) { - return true; + // When narrowing a union type by a `typeof` guard using type-facts alone, constituent types that are + // super-types of the implied guard will be retained in the final type: this is because type-facts only + // filter. Instead, we would like to replace those union constituents with the more precise type implied by + // the guard. For example: narrowing `{} | undefined` by `"boolean"` should produce the type `boolean`, not + // the filtered type `{}`. For this reason we narrow constituents of the union individually, in addition to + // filtering by type-facts. + function narrowUnionMemberByTypeof(candidate: Type) { + return (type: Type) => { + if (isTypeSubtypeOf(type, candidate)) { + return type; } - if (isBindingElement(node.parent) && node.parent.initializer === node) { - inBindingInitializer = true; + if (isTypeSubtypeOf(candidate, type)) { + return candidate; } - - node = node.parent; - } - - return false; + if (type.flags & TypeFlags.Instantiable) { + const constraint = getBaseConstraintOfType(type) || anyType; + if (isTypeSubtypeOf(candidate, constraint)) { + return getIntersectionType([type, candidate]); + } + } + return type; + }; } - function getContextualIterationType(kind: IterationTypeKind, functionDecl: SignatureDeclaration): Type | undefined { - const isAsync = !!(getFunctionFlags(functionDecl) & FunctionFlags.Async); - const contextualReturnType = getContextualReturnType(functionDecl); - if (contextualReturnType) { - return getIterationTypeOfGeneratorFunctionReturnType(kind, contextualReturnType, isAsync) - || undefined; + function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type { + const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement, /*retainDefault*/ true); + if (!switchWitnesses.length) { + return type; } - - return undefined; - } - - function getContextualReturnType(functionDecl: SignatureDeclaration): Type | undefined { - // If the containing function has a return type annotation, is a constructor, or is a get accessor whose - // corresponding set accessor has a type annotation, return statements in the function are contextually typed - const returnType = getReturnTypeFromAnnotation(functionDecl); - if (returnType) { - return returnType; + // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause + const defaultCaseLocation = findIndex(switchWitnesses, elem => elem === undefined); + const hasDefaultClause = clauseStart === clauseEnd || (defaultCaseLocation >= clauseStart && defaultCaseLocation < clauseEnd); + let clauseWitnesses: string[]; + let switchFacts: TypeFacts; + if (defaultCaseLocation > -1) { + // We no longer need the undefined denoting an explicit default case. Remove the undefined and + // fix-up clauseStart and clauseEnd. This means that we don't have to worry about undefined in the + // witness array. + const witnesses = switchWitnesses.filter(witness => witness !== undefined) as string[]; + // The adjusted clause start and end after removing the `default` statement. + const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart; + const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd; + clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd); + switchFacts = getFactsFromTypeofSwitch(fixedClauseStart, fixedClauseEnd, witnesses, hasDefaultClause); } - // Otherwise, if the containing function is contextually typed by a function type with exactly one call signature - // and that call signature is non-generic, return statements are contextually typed by the return type of the signature - const signature = getContextualSignatureForFunctionLikeDeclaration(functionDecl as FunctionExpression); - if (signature && !isResolvingReturnTypeOfSignature(signature)) { - return getReturnTypeOfSignature(signature); + else { + clauseWitnesses = switchWitnesses.slice(clauseStart, clauseEnd) as string[]; + switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, switchWitnesses as string[], hasDefaultClause); } - const iife = getImmediatelyInvokedFunctionExpression(functionDecl); - if (iife) { - return getContextualType(iife); + if (hasDefaultClause) { + return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts); } - return undefined; - } - - // In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter. - function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type | undefined { - const args = getEffectiveCallArguments(callTarget); - const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression - return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex); - } + /* + The implied type is the raw type suggested by a + value being caught in this clause. - function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number): Type { - if (isImportCall(callTarget)) { - return argIndex === 0 ? stringType : - argIndex === 1 ? getGlobalImportCallOptionsType(/*reportErrors*/ false) : - anyType; - } + When the clause contains a default case we ignore + the implied type and try to narrow using any facts + we can learn: see `switchFacts`. - // If we're already in the process of resolving the given signature, don't resolve again as - // that could cause infinite recursion. Instead, return anySignature. - const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget); + Example: + switch (typeof x) { + case 'number': + case 'string': break; + default: break; + case 'number': + case 'boolean': break + } - if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { - return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); - } - const restIndex = signature.parameters.length - 1; - return signatureHasRestParameter(signature) && argIndex >= restIndex ? - getIndexedAccessType(getTypeOfSymbol(signature.parameters[restIndex]), getNumberLiteralType(argIndex - restIndex), AccessFlags.Contextual) : - getTypeAtPosition(signature, argIndex); - } + In the first clause (case `number` and `string`) the + implied type is number | string. - function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) { - if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) { - return getContextualTypeForArgument(template.parent as TaggedTemplateExpression, substitutionExpression); - } + In the default clause we de not compute an implied type. - return undefined; + In the third clause (case `number` and `boolean`) + the naive implied type is number | boolean, however + we use the type facts to narrow the implied type to + boolean. We know that number cannot be selected + because it is caught in the first clause. + */ + const impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofGuard(type, text) || type)), switchFacts); + return getTypeWithFacts(mapType(type, narrowUnionMemberByTypeof(impliedType)), switchFacts); } - function getContextualTypeForBinaryOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined { - const binaryExpression = node.parent as BinaryExpression; - const { left, operatorToken, right } = binaryExpression; - switch (operatorToken.kind) { - case SyntaxKind.EqualsToken: - case SyntaxKind.AmpersandAmpersandEqualsToken: - case SyntaxKind.BarBarEqualsToken: - case SyntaxKind.QuestionQuestionEqualsToken: - return node === right ? getContextualTypeForAssignmentDeclaration(binaryExpression) : undefined; - case SyntaxKind.BarBarToken: - case SyntaxKind.QuestionQuestionToken: - // When an || expression has a contextual type, the operands are contextually typed by that type, except - // when that type originates in a binding pattern, the right operand is contextually typed by the type of - // the left operand. When an || expression has no contextual type, the right operand is contextually typed - // by the type of the left operand, except for the special case of Javascript declarations of the form - // `namespace.prop = namespace.prop || {}`. - const type = getContextualType(binaryExpression, contextFlags); - return node === right && (type && type.pattern || !type && !isDefaultedExpandoInitializer(binaryExpression)) ? - getTypeOfExpression(left) : type; - case SyntaxKind.AmpersandAmpersandToken: - case SyntaxKind.CommaToken: - return node === right ? getContextualType(binaryExpression, contextFlags) : undefined; - default: - return undefined; - } + function isMatchingConstructorReference(expr: Expression) { + return (isPropertyAccessExpression(expr) && idText(expr.name) === "constructor" || + isElementAccessExpression(expr) && isStringLiteralLike(expr.argumentExpression) && expr.argumentExpression.text === "constructor") && + isMatchingReference(reference, expr.expression); } - /** - * Try to find a resolved symbol for an expression without also resolving its type, as - * getSymbolAtLocation would (as that could be reentrant into contextual typing) - */ - function getSymbolForExpression(e: Expression) { - if (e.symbol) { - return e.symbol; - } - if (isIdentifier(e)) { - return getResolvedSymbol(e); + function narrowTypeByConstructor(type: Type, operator: SyntaxKind, identifier: Expression, assumeTrue: boolean): Type { + // Do not narrow when checking inequality. + if (assumeTrue ? (operator !== SyntaxKind.EqualsEqualsToken && operator !== SyntaxKind.EqualsEqualsEqualsToken) : (operator !== SyntaxKind.ExclamationEqualsToken && operator !== SyntaxKind.ExclamationEqualsEqualsToken)) { + return type; } - if (isPropertyAccessExpression(e)) { - const lhsType = getTypeOfExpression(e.expression); - return isPrivateIdentifier(e.name) ? tryGetPrivateIdentifierPropertyOfType(lhsType, e.name) : getPropertyOfType(lhsType, e.name.escapedText); + + // Get the type of the constructor identifier expression, if it is not a function then do not narrow. + const identifierType = getTypeOfExpression(identifier); + if (!isFunctionType(identifierType) && !isConstructorType(identifierType)) { + return type; } - return undefined; - function tryGetPrivateIdentifierPropertyOfType(type: Type, id: PrivateIdentifier) { - const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(id.escapedText, id); - return lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(type, lexicallyScopedSymbol); + // Get the prototype property of the type identifier so we can find out its type. + const prototypeProperty = getPropertyOfType(identifierType, "prototype" as __String); + if (!prototypeProperty) { + return type; } - } - // In an assignment expression, the right operand is contextually typed by the type of the left operand. - // Don't do this for assignment declarations unless there is a type tag on the assignment, to avoid circularity from checking the right operand. - function getContextualTypeForAssignmentDeclaration(binaryExpression: BinaryExpression): Type | undefined { - const kind = getAssignmentDeclarationKind(binaryExpression); - switch (kind) { - case AssignmentDeclarationKind.None: - case AssignmentDeclarationKind.ThisProperty: - const lhsSymbol = getSymbolForExpression(binaryExpression.left); - const decl = lhsSymbol && lhsSymbol.valueDeclaration; - // Unannotated, uninitialized property declarations have a type implied by their usage in the constructor. - // We avoid calling back into `getTypeOfExpression` and reentering contextual typing to avoid a bogus circularity error in that case. - if (decl && (isPropertyDeclaration(decl) || isPropertySignature(decl))) { - const overallAnnotation = getEffectiveTypeAnnotationNode(decl); - return (overallAnnotation && instantiateType(getTypeFromTypeNode(overallAnnotation), getSymbolLinks(lhsSymbol).mapper)) || - (decl.initializer && getTypeOfExpression(binaryExpression.left)); - } - if (kind === AssignmentDeclarationKind.None) { - return getTypeOfExpression(binaryExpression.left); - } - return getContextualTypeForThisPropertyAssignment(binaryExpression); - case AssignmentDeclarationKind.Property: - if (isPossiblyAliasedThisProperty(binaryExpression, kind)) { - return getContextualTypeForThisPropertyAssignment(binaryExpression); - } - // If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration. - // See `bindStaticPropertyAssignment` in `binder.ts`. - else if (!binaryExpression.left.symbol) { - return getTypeOfExpression(binaryExpression.left); - } - else { - const decl = binaryExpression.left.symbol.valueDeclaration; - if (!decl) { - return undefined; - } - const lhs = cast(binaryExpression.left, isAccessExpression); - const overallAnnotation = getEffectiveTypeAnnotationNode(decl); - if (overallAnnotation) { - return getTypeFromTypeNode(overallAnnotation); - } - else if (isIdentifier(lhs.expression)) { - const id = lhs.expression; - const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true); - if (parentSymbol) { - const annotated = parentSymbol.valueDeclaration && getEffectiveTypeAnnotationNode(parentSymbol.valueDeclaration); - if (annotated) { - const nameStr = getElementOrPropertyAccessName(lhs); - if (nameStr !== undefined) { - return getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr); - } - } - return undefined; - } - } - return isInJSFile(decl) ? undefined : getTypeOfExpression(binaryExpression.left); - } - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.Prototype: - case AssignmentDeclarationKind.PrototypeProperty: - let valueDeclaration = binaryExpression.left.symbol?.valueDeclaration; - // falls through - case AssignmentDeclarationKind.ModuleExports: - valueDeclaration ||= binaryExpression.symbol?.valueDeclaration; - const annotated = valueDeclaration && getEffectiveTypeAnnotationNode(valueDeclaration); - return annotated ? getTypeFromTypeNode(annotated) : undefined; - case AssignmentDeclarationKind.ObjectDefinePropertyValue: - case AssignmentDeclarationKind.ObjectDefinePropertyExports: - case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: - return Debug.fail("Does not apply"); - default: - return Debug.assertNever(kind); + // Get the type of the prototype, if it is undefined, or the global `Object` or `Function` types then do not narrow. + const prototypeType = getTypeOfSymbol(prototypeProperty); + const candidate = !isTypeAny(prototypeType) ? prototypeType : undefined; + if (!candidate || candidate === globalObjectType || candidate === globalFunctionType) { + return type; } - } - function isPossiblyAliasedThisProperty(declaration: BinaryExpression, kind = getAssignmentDeclarationKind(declaration)) { - if (kind === AssignmentDeclarationKind.ThisProperty) { - return true; + // If the type that is being narrowed is `any` then just return the `candidate` type since every type is a subtype of `any`. + if (isTypeAny(type)) { + return candidate; } - if (!isInJSFile(declaration) || kind !== AssignmentDeclarationKind.Property || !isIdentifier((declaration.left as AccessExpression).expression)) { - return false; + + // Filter out types that are not considered to be "constructed by" the `candidate` type. + return filterType(type, t => isConstructedBy(t, candidate)); + + function isConstructedBy(source: Type, target: Type) { + // If either the source or target type are a class type then we need to check that they are the same exact type. + // This is because you may have a class `A` that defines some set of properties, and another class `B` + // that defines the same set of properties as class `A`, in that case they are structurally the same + // type, but when you do something like `instanceOfA.constructor === B` it will return false. + if (source.flags & TypeFlags.Object && getObjectFlags(source) & ObjectFlags.Class || + target.flags & TypeFlags.Object && getObjectFlags(target) & ObjectFlags.Class) { + return source.symbol === target.symbol; + } + + // For all other types just check that the `source` type is a subtype of the `target` type. + return isTypeSubtypeOf(source, target); } - const name = ((declaration.left as AccessExpression).expression as Identifier).escapedText; - const symbol = resolveName(declaration.left, name, SymbolFlags.Value, undefined, undefined, /*isUse*/ true, /*excludeGlobals*/ true); - return isThisInitializedDeclaration(symbol?.valueDeclaration); } - function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression): Type | undefined { - if (!binaryExpression.symbol) return getTypeOfExpression(binaryExpression.left); - if (binaryExpression.symbol.valueDeclaration) { - const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration); - if (annotated) { - const type = getTypeFromTypeNode(annotated); - if (type) { - return type; - } + function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + const left = getReferenceCandidate(expr.left); + if (!isMatchingReference(reference, left)) { + if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) { + return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } + return type; } - const thisAccess = cast(binaryExpression.left, isAccessExpression); - if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) { - return undefined; - } - const thisType = checkThisExpression(thisAccess.expression); - const nameStr = getElementOrPropertyAccessName(thisAccess); - return nameStr !== undefined && getTypeOfPropertyOfContextualType(thisType, nameStr) || undefined; - - } - function isCircularMappedProperty(symbol: Symbol) { - return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0); - } + // Check that right operand is a function type with a prototype property + const rightType = getTypeOfExpression(expr.right); + if (!isTypeDerivedFrom(rightType, globalFunctionType)) { + return type; + } - function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) { - return mapType(type, t => { - if (isGenericMappedType(t)) { - const constraint = getConstraintTypeFromMappedType(t); - const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint; - const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name)); - if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) { - return substituteIndexedMappedType(t, propertyNameType); - } - } - else if (t.flags & TypeFlags.StructuredType) { - const prop = getPropertyOfType(t, name); - if (prop) { - return isCircularMappedProperty(prop) ? undefined : getTypeOfSymbol(prop); - } - if (isTupleType(t)) { - const restType = getRestTypeOfTupleType(t); - if (restType && isNumericLiteralName(name) && +name >= 0) { - return restType; - } - } - return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type; + let targetType: Type | undefined; + const prototypeProperty = getPropertyOfType(rightType, "prototype" as __String); + if (prototypeProperty) { + // Target type is type of the prototype property + const prototypePropertyType = getTypeOfSymbol(prototypeProperty); + if (!isTypeAny(prototypePropertyType)) { + targetType = prototypePropertyType; } - return undefined; - }, /*noReductions*/ true); - } - - // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of - // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one - // exists. Otherwise, it is the type of the string index signature in T, if one exists. - function getContextualTypeForObjectLiteralMethod(node: MethodDeclaration, contextFlags?: ContextFlags): Type | undefined { - Debug.assert(isObjectLiteralMethod(node)); - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; } - return getContextualTypeForObjectLiteralElement(node, contextFlags); - } - function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike, contextFlags?: ContextFlags) { - const objectLiteral = element.parent as ObjectLiteralExpression; - const propertyAssignmentType = isPropertyAssignment(element) && getContextualTypeForVariableLikeDeclaration(element); - if (propertyAssignmentType) { - return propertyAssignmentType; + // Don't narrow from 'any' if the target type is exactly 'Object' or 'Function' + if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) { + return type; } - const type = getApparentTypeOfContextualType(objectLiteral, contextFlags); - if (type) { - if (hasBindableName(element)) { - // For a (non-symbol) computed property, there is no reason to look up the name - // in the type. It will just be "__computed", which does not appear in any - // SymbolTable. - const symbol = getSymbolOfNode(element); - return getTypeOfPropertyOfContextualType(type, symbol.escapedName, getSymbolLinks(symbol).nameType); - } - if (element.name) { - const nameType = getLiteralTypeFromPropertyName(element.name); - // We avoid calling getApplicableIndexInfo here because it performs potentially expensive intersection reduction. - return mapType(type, t => findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType)?.type, /*noReductions*/ true); - } + + if (!targetType) { + const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct); + targetType = constructSignatures.length ? + getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) : + emptyObjectType; } - return undefined; - } - // In an array literal contextually typed by a type T, the contextual type of an element expression at index N is - // the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature, - // it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated - // type of T. - function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined { - return arrayContextualType && ( - getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String) - || mapType( - arrayContextualType, - t => getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false), - /*noReductions*/ true)); - } + // We can't narrow a union based off instanceof without negated types see #31576 for more info + if (!assumeTrue && rightType.flags & TypeFlags.Union) { + const nonConstructorTypeInUnion = find((rightType as UnionType).types, (t) => !isConstructorType(t)); + if (!nonConstructorTypeInUnion) return type; + } - // In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type. - function getContextualTypeForConditionalOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined { - const conditional = node.parent as ConditionalExpression; - return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional, contextFlags) : undefined; + return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom); } - function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild) { - const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName); - // JSX expression is in children of JSX Element, we will look for an "children" attribute (we get the name from JSX.ElementAttributesProperty) - const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); - if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) { - return undefined; + function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) { + if (!assumeTrue) { + return filterType(type, t => !isRelated(t, candidate)); } - const realChildren = getSemanticJsxChildren(node.children); - const childIndex = realChildren.indexOf(child); - const childFieldType = getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName); - return childFieldType && (realChildren.length === 1 ? childFieldType : mapType(childFieldType, t => { - if (isArrayLikeType(t)) { - return getIndexedAccessType(t, getNumberLiteralType(childIndex)); - } - else { - return t; + // If the current type is a union type, remove all constituents that couldn't be instances of + // the candidate type. If one or more constituents remain, return a union of those. + if (type.flags & TypeFlags.Union) { + const assignableType = filterType(type, t => isRelated(t, candidate)); + if (!(assignableType.flags & TypeFlags.Never)) { + return assignableType; } - }, /*noReductions*/ true)); - } + } - function getContextualTypeForJsxExpression(node: JsxExpression): Type | undefined { - const exprParent = node.parent; - return isJsxAttributeLike(exprParent) - ? getContextualType(node) - : isJsxElement(exprParent) - ? getContextualTypeForChildJsxExpression(exprParent, node) - : undefined; + // If the candidate type is a subtype of the target type, narrow to the candidate type. + // Otherwise, if the target type is assignable to the candidate type, keep the target type. + // Otherwise, if the candidate type is assignable to the target type, narrow to the candidate + // type. Otherwise, the types are completely unrelated, so narrow to an intersection of the + // two types. + return isTypeSubtypeOf(candidate, type) ? candidate : + isTypeAssignableTo(type, candidate) ? type : + isTypeAssignableTo(candidate, type) ? candidate : + getIntersectionType([type, candidate]); } - function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined { - // When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type - // which is a type of the parameter of the signature we are trying out. - // If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName - if (isJsxAttribute(attribute)) { - const attributesType = getApparentTypeOfContextualType(attribute.parent); - if (!attributesType || isTypeAny(attributesType)) { - return undefined; + function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { + if (hasMatchingArgument(callExpression, reference)) { + const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined; + const predicate = signature && getTypePredicateOfSignature(signature); + if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) { + return narrowTypeByTypePredicate(type, predicate, callExpression, assumeTrue); } - return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText); } - else { - return getContextualType(attribute.parent); + if (containsMissingType(type) && isAccessExpression(reference) && isPropertyAccessExpression(callExpression.expression)) { + const callAccess = callExpression.expression; + if (isMatchingReference(reference.expression, getReferenceCandidate(callAccess.expression)) && + isIdentifier(callAccess.name) && callAccess.name.escapedText === "hasOwnProperty" && callExpression.arguments.length === 1) { + const argument = callExpression.arguments[0]; + if (isStringLiteralLike(argument) && getAccessedPropertyName(reference) === escapeLeadingUnderscores(argument.text)) { + return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); + } + } } + return type; } - // Return true if the given expression is possibly a discriminant value. We limit the kinds of - // expressions we check to those that don't depend on their contextual type in order not to cause - // recursive (and possibly infinite) invocations of getContextualType. - function isPossiblyDiscriminantValue(node: Expression): boolean { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.Identifier: - case SyntaxKind.UndefinedKeyword: - return true; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ParenthesizedExpression: - return isPossiblyDiscriminantValue((node as PropertyAccessExpression | ParenthesizedExpression).expression); - case SyntaxKind.JsxExpression: - return !(node as JsxExpression).expression || isPossiblyDiscriminantValue((node as JsxExpression).expression!); + function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type { + // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function' + if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) { + const predicateArgument = getTypePredicateArgument(predicate, callExpression); + if (predicateArgument) { + if (isMatchingReference(reference, predicateArgument)) { + return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf); + } + if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) && + !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) { + type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } + const access = getDiscriminantPropertyAccess(predicateArgument, type); + if (access) { + return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, isTypeSubtypeOf)); + } + } } - return false; - } - - function discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) { - return getMatchingUnionConstituentForObjectLiteral(contextualType, node) || discriminateTypeByDiscriminableItems(contextualType, - concatenate( - map( - filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)), - prop => ([() => getContextFreeTypeOfExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String]) - ), - map( - filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)), - s => [() => undefinedType, s.escapedName] as [() => Type, __String] - ) - ), - isTypeAssignableTo, - contextualType - ); - } - - function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) { - return discriminateTypeByDiscriminableItems(contextualType, - concatenate( - map( - filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.JsxAttribute && isDiscriminantProperty(contextualType, p.symbol.escapedName) && (!p.initializer || isPossiblyDiscriminantValue(p.initializer))), - prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => getContextFreeTypeOfExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String]) - ), - map( - filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)), - s => [() => undefinedType, s.escapedName] as [() => Type, __String] - ) - ), - isTypeAssignableTo, - contextualType - ); + return type; } - // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily - // be "pushed" onto a node using the contextualType property. - function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags?: ContextFlags): Type | undefined { - const contextualType = isObjectLiteralMethod(node) ? - getContextualTypeForObjectLiteralMethod(node, contextFlags) : - getContextualType(node, contextFlags); - const instantiatedType = instantiateContextualType(contextualType, node, contextFlags); - if (instantiatedType && !(contextFlags && contextFlags & ContextFlags.NoConstraints && instantiatedType.flags & TypeFlags.TypeVariable)) { - const apparentType = mapType(instantiatedType, getApparentType, /*noReductions*/ true); - return apparentType.flags & TypeFlags.Union && isObjectLiteralExpression(node) ? discriminateContextualTypeByObjectMembers(node, apparentType as UnionType) : - apparentType.flags & TypeFlags.Union && isJsxAttributes(node) ? discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType) : - apparentType; + // Narrow the given type based on the given expression having the assumed boolean value. The returned type + // will be a subtype or the same type as the argument. + function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { + // for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a` + if (isExpressionOfOptionalChainRoot(expr) || + isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) { + return narrowTypeByOptionality(type, expr, assumeTrue); } - } - - // If the given contextual type contains instantiable types and if a mapper representing - // return type inferences is available, instantiate those types using that mapper. - function instantiateContextualType(contextualType: Type | undefined, node: Node, contextFlags?: ContextFlags): Type | undefined { - if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) { - const inferenceContext = getInferenceContext(node); - // If no inferences have been made, nothing is gained from instantiating as type parameters - // would just be replaced with their defaults similar to the apparent type. - if (inferenceContext && some(inferenceContext.inferences, hasInferenceCandidates)) { - // For contextual signatures we incorporate all inferences made so far, e.g. from return - // types as well as arguments to the left in a function call. - if (contextFlags && contextFlags & ContextFlags.Signature) { - return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper); + switch (expr.kind) { + case SyntaxKind.Identifier: + // When narrowing a reference to a const variable, non-assigned parameter, or readonly property, we inline + // up to five levels of aliased conditional expressions that are themselves declared as const variables. + if (!isMatchingReference(reference, expr) && inlineLevel < 5) { + const symbol = getResolvedSymbol(expr as Identifier); + if (isConstVariable(symbol)) { + const declaration = symbol.valueDeclaration; + if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isConstantReference(reference)) { + inlineLevel++; + const result = narrowType(type, declaration.initializer, assumeTrue); + inlineLevel--; + return result; + } + } } - // For other purposes (e.g. determining whether to produce literal types) we only - // incorporate inferences made from the return type in a function call. - if (inferenceContext.returnMapper) { - return instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); + // falls through + case SyntaxKind.ThisKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return narrowTypeByTruthiness(type, expr, assumeTrue); + case SyntaxKind.CallExpression: + return narrowTypeByCallExpression(type, expr as CallExpression, assumeTrue); + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.NonNullExpression: + return narrowType(type, (expr as ParenthesizedExpression | NonNullExpression).expression, assumeTrue); + case SyntaxKind.BinaryExpression: + return narrowTypeByBinaryExpression(type, expr as BinaryExpression, assumeTrue); + case SyntaxKind.PrefixUnaryExpression: + if ((expr as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken) { + return narrowType(type, (expr as PrefixUnaryExpression).operand, !assumeTrue); } - } + break; } - return contextualType; + return type; } - // This function is similar to instantiateType, except that (a) it only instantiates types that - // are classified as instantiable (i.e. it doesn't instantiate object types), and (b) it performs - // no reductions on instantiated union types. - function instantiateInstantiableTypes(type: Type, mapper: TypeMapper): Type { - if (type.flags & TypeFlags.Instantiable) { - return instantiateType(type, mapper); - } - if (type.flags & TypeFlags.Union) { - return getUnionType(map((type as UnionType).types, t => instantiateInstantiableTypes(t, mapper)), UnionReduction.None); + function narrowTypeByOptionality(type: Type, expr: Expression, assumePresent: boolean): Type { + if (isMatchingReference(reference, expr)) { + return getTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull); } - if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(map((type as IntersectionType).types, t => instantiateInstantiableTypes(t, mapper))); + const access = getDiscriminantPropertyAccess(expr, type); + if (access) { + return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull)); } return type; } + } - /** - * Whoa! Do you really want to use this function? - * - * Unless you're trying to get the *non-apparent* type for a - * value-literal type or you're authoring relevant portions of this algorithm, - * you probably meant to use 'getApparentTypeOfContextualType'. - * Otherwise this may not be very useful. - * - * In cases where you *are* working on this function, you should understand - * when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'. - * - * - Use 'getContextualType' when you are simply going to propagate the result to the expression. - * - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type. - * - * @param node the expression whose contextual type will be returned. - * @returns the contextual type of an expression. - */ - function getContextualType(node: Expression, contextFlags?: ContextFlags): Type | undefined { - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; - } - if (node.contextualType) { - return node.contextualType; + function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { + symbol = symbol.exportSymbol || symbol; + + // If we have an identifier or a property access at the given location, if the location is + // an dotted name expression, and if the location is not an assignment target, obtain the type + // of the expression (which will reflect control flow analysis). If the expression indeed + // resolved to the given symbol, return the narrowed type. + if (location.kind === SyntaxKind.Identifier || location.kind === SyntaxKind.PrivateIdentifier) { + if (isRightSideOfQualifiedNameOrPropertyAccess(location)) { + location = location.parent; } - const { parent } = node; - switch (parent.kind) { - case SyntaxKind.VariableDeclaration: - case SyntaxKind.Parameter: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.BindingElement: - return getContextualTypeForInitializerExpression(node, contextFlags); - case SyntaxKind.ArrowFunction: - case SyntaxKind.ReturnStatement: - return getContextualTypeForReturnExpression(node); - case SyntaxKind.YieldExpression: - return getContextualTypeForYieldOperand(parent as YieldExpression); - case SyntaxKind.AwaitExpression: - return getContextualTypeForAwaitOperand(parent as AwaitExpression, contextFlags); - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - return getContextualTypeForArgument(parent as CallExpression | NewExpression, node); - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - return isConstTypeReference((parent as AssertionExpression).type) ? tryFindWhenConstTypeReference(parent as AssertionExpression) : getTypeFromTypeNode((parent as AssertionExpression).type); - case SyntaxKind.BinaryExpression: - return getContextualTypeForBinaryOperand(node, contextFlags); - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - return getContextualTypeForObjectLiteralElement(parent as PropertyAssignment | ShorthandPropertyAssignment, contextFlags); - case SyntaxKind.SpreadAssignment: - return getContextualType(parent.parent as ObjectLiteralExpression, contextFlags); - case SyntaxKind.ArrayLiteralExpression: { - const arrayLiteral = parent as ArrayLiteralExpression; - const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags); - return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node)); - } - case SyntaxKind.ConditionalExpression: - return getContextualTypeForConditionalOperand(node, contextFlags); - case SyntaxKind.TemplateSpan: - Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); - return getContextualTypeForSubstitutionExpression(parent.parent as TemplateExpression, node); - case SyntaxKind.ParenthesizedExpression: { - // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. - const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined; - return !tag ? getContextualType(parent as ParenthesizedExpression, contextFlags) : - isJSDocTypeTag(tag) && isConstTypeReference(tag.typeExpression.type) ? tryFindWhenConstTypeReference(parent as ParenthesizedExpression) : - getTypeFromTypeNode(tag.typeExpression.type); + if (isExpressionNode(location) && (!isAssignmentTarget(location) || isWriteAccess(location))) { + const type = getTypeOfExpression(location as Expression); + if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { + return type; } - case SyntaxKind.NonNullExpression: - return getContextualType(parent as NonNullExpression, contextFlags); - case SyntaxKind.JsxExpression: - return getContextualTypeForJsxExpression(parent as JsxExpression); - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxSpreadAttribute: - return getContextualTypeForJsxAttribute(parent as JsxAttribute | JsxSpreadAttribute); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxSelfClosingElement: - return getContextualJsxElementAttributesType(parent as JsxOpeningLikeElement, contextFlags); - } - return undefined; - - function tryFindWhenConstTypeReference(node: Expression) { - return getContextualType(node); } } - - function getInferenceContext(node: Node) { - const ancestor = findAncestor(node, n => !!n.inferenceContext); - return ancestor && ancestor.inferenceContext!; + if (isDeclarationName(location) && isSetAccessor(location.parent) && getAnnotatedAccessorTypeNode(location.parent)) { + return resolveTypeOfAccessors(location.parent.symbol, /*writing*/ true)!; } + // The location isn't a reference to the given symbol, meaning we're being asked + // a hypothetical question of what type the symbol would have if there was a reference + // to it at the given location. Since we have no control flow information for the + // hypothetical reference (control flow information is created and attached by the + // binder), we simply return the declared type of the symbol. + return getNonMissingTypeOfSymbol(symbol); + } + + function getControlFlowContainer(node: Node): Node { + return findAncestor(node.parent, node => + isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || + node.kind === SyntaxKind.ModuleBlock || + node.kind === SyntaxKind.SourceFile || + node.kind === SyntaxKind.PropertyDeclaration)!; + } - function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags?: ContextFlags) { - if (isJsxOpeningElement(node) && node.parent.contextualType && contextFlags !== ContextFlags.Completions) { - // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit - // _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type - // (as below) instead! - return node.parent.contextualType; + // Check if a parameter or catch variable is assigned anywhere + function isSymbolAssigned(symbol: Symbol) { + if (!symbol.valueDeclaration) { + return false; + } + const parent = getRootDeclaration(symbol.valueDeclaration).parent; + const links = getNodeLinks(parent); + if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { + links.flags |= NodeCheckFlags.AssignmentsMarked; + if (!hasParentWithAssignmentsMarked(parent)) { + markNodeAssignments(parent); } - return getContextualTypeForArgumentAtIndex(node, 0); } + return symbol.isAssigned || false; + } - function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) { - return getJsxReferenceKind(node) !== JsxReferenceKind.Component - ? getJsxPropsTypeFromCallSignature(signature, node) - : getJsxPropsTypeFromClassType(signature, node); - } + function hasParentWithAssignmentsMarked(node: Node) { + return !!findAncestor(node.parent, node => + (isFunctionLike(node) || isCatchClause(node)) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); + } - function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) { - let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType); - propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType); - const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); - if (!isErrorType(intrinsicAttribs)) { - propsType = intersectTypes(intrinsicAttribs, propsType); - } - return propsType; - } - - function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) { - if (sig.compositeSignatures) { - // JSX Elements using the legacy `props`-field based lookup (eg, react class components) need to treat the `props` member as an input - // instead of an output position when resolving the signature. We need to go back to the input signatures of the composite signature, - // get the type of `props` on each return type individually, and then _intersect them_, rather than union them (as would normally occur - // for a union signature). It's an unfortunate quirk of looking in the output of the signature for the type we want to use for the input. - // The default behavior of `getTypeOfFirstParameterOfSignatureWithFallback` when no `props` member name is defined is much more sane. - const results: Type[] = []; - for (const signature of sig.compositeSignatures) { - const instance = getReturnTypeOfSignature(signature); - if (isTypeAny(instance)) { - return instance; - } - const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation); - if (!propType) { - return; - } - results.push(propType); + function markNodeAssignments(node: Node) { + if (node.kind === SyntaxKind.Identifier) { + if (isAssignmentTarget(node)) { + const symbol = getResolvedSymbol(node as Identifier); + if (isParameterOrCatchClauseVariable(symbol)) { + symbol.isAssigned = true; } - return getIntersectionType(results); // Same result for both union and intersection signatures } - const instanceType = getReturnTypeOfSignature(sig); - return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); } + else { + forEachChild(node, markNodeAssignments); + } + } + + function isConstVariable(symbol: Symbol) { + return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0; + } + + /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ + function removeOptionalityFromDeclaredType(declaredType: Type, declaration: VariableLikeDeclaration): Type { + if (pushTypeResolution(declaration.symbol, TypeSystemPropertyName.DeclaredType)) { + const annotationIncludesUndefined = strictNullChecks && + declaration.kind === SyntaxKind.Parameter && + declaration.initializer && + getFalsyFlags(declaredType) & TypeFlags.Undefined && + !(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined); + popTypeResolution(); + + return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType; + } + else { + reportCircularityError(declaration.symbol); + return declaredType; + } + } + + function isConstraintPosition(type: Type, node: Node) { + const parent = node.parent; + // In an element access obj[x], we consider obj to be in a constraint position, except when obj is of + // a generic type without a nullable constraint and x is a generic type. This is because when both obj + // and x are of generic types T and K, we want the resulting type to be T[K]. + return parent.kind === SyntaxKind.PropertyAccessExpression || + parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === node || + parent.kind === SyntaxKind.ElementAccessExpression && (parent as ElementAccessExpression).expression === node && + !(someType(type, isGenericTypeWithoutNullableConstraint) && isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression))); + } + + function isGenericTypeWithUnionConstraint(type: Type) { + return !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Nullable | TypeFlags.Union)); + } + + function isGenericTypeWithoutNullableConstraint(type: Type) { + return !!(type.flags & TypeFlags.Instantiable && !maybeTypeOfKind(getBaseConstraintOrType(type), TypeFlags.Nullable)); + } + + function hasNonBindingPatternContextualTypeWithNoGenericTypes(node: Node) { + // Computing the contextual type for a child of a JSX element involves resolving the type of the + // element's tag name, so we exclude that here to avoid circularities. + const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) && + !((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) && + getContextualType(node, ContextFlags.SkipBindingPatterns); + return contextualType && !isGenericType(contextualType); + } - function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) { - if (isJsxIntrinsicIdentifier(context.tagName)) { - const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context); - const fakeSignature = createSignatureForJSXIntrinsic(context, result); - return getOrCreateTypeFromSignature(fakeSignature); + function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) { + // When the type of a reference is or contains an instantiable type with a union type constraint, and + // when the reference is in a constraint position (where it is known we'll obtain the apparent type) or + // has a contextual type containing no top-level instantiables (meaning constraints will determine + // assignability), we substitute constraints for all instantiables in the type of the reference to give + // control flow analysis an opportunity to narrow it further. For example, for a reference of a type + // parameter type 'T extends string | undefined' with a contextual type 'string', we substitute + // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'. + const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) && + someType(type, isGenericTypeWithUnionConstraint) && + (isConstraintPosition(type, reference) || hasNonBindingPatternContextualTypeWithNoGenericTypes(reference)); + return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; + } + + function isExportOrExportExpression(location: Node) { + return !!findAncestor(location, n => { + const parent = n.parent; + if (parent === undefined) { + return "quit"; } - const tagType = checkExpressionCached(context.tagName); - if (tagType.flags & TypeFlags.StringLiteral) { - const result = getIntrinsicAttributesTypeFromStringLiteralType(tagType as StringLiteralType, context); - if (!result) { - return errorType; - } - const fakeSignature = createSignatureForJSXIntrinsic(context, result); - return getOrCreateTypeFromSignature(fakeSignature); + if (isExportAssignment(parent)) { + return parent.expression === n && isEntityNameExpression(n); } - return tagType; - } + if (isExportSpecifier(parent)) { + return parent.name === n || parent.propertyName === n; + } + return false; + }); + } - function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) { - const managedSym = getJsxLibraryManagedAttributes(ns); - if (managedSym) { - const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); // fetches interface type, or initializes symbol links type parmaeters - const ctorType = getStaticTypeOfReferencedJsxConstructor(context); - if (managedSym.flags & SymbolFlags.TypeAlias) { - const params = getSymbolLinks(managedSym).typeParameters; - if (length(params) >= 2) { - const args = fillMissingTypeArguments([ctorType, attributesType], params, 2, isInJSFile(context)); - return getTypeAliasInstantiation(managedSym, args); - } + function markAliasReferenced(symbol: Symbol, location: Node) { + if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && !getTypeOnlyAliasDeclaration(symbol)) { + const target = resolveAlias(symbol); + if (target.flags & SymbolFlags.Value) { + // An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled + // (because the const enum value will not be inlined), or if (2) the alias is an export + // of a const enum declaration that will be preserved. + if (compilerOptions.isolatedModules || + shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) || + !isConstEnumOrConstEnumOnlyModule(target) + ) { + markAliasSymbolAsReferenced(symbol); } - if (length((declaredManagedType as GenericType).typeParameters) >= 2) { - const args = fillMissingTypeArguments([ctorType, attributesType], (declaredManagedType as GenericType).typeParameters, 2, isInJSFile(context)); - return createTypeReference((declaredManagedType as GenericType), args); + else { + markConstEnumAliasAsReferenced(symbol); } } - return attributesType; } + } - function getJsxPropsTypeFromClassType(sig: Signature, context: JsxOpeningLikeElement) { - const ns = getJsxNamespaceAt(context); - const forcedLookupLocation = getJsxElementPropertiesName(ns); - let attributesType = forcedLookupLocation === undefined - // If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type - ? getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType) - : forcedLookupLocation === "" - // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead - ? getReturnTypeOfSignature(sig) - // Otherwise get the type of the property on the signature return type - : getJsxPropsTypeForSignatureFromMember(sig, forcedLookupLocation); - - if (!attributesType) { - // There is no property named 'props' on this instance type - if (!!forcedLookupLocation && !!length(context.attributes.properties)) { - error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(forcedLookupLocation)); + function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier) { + // If we have a non-rest binding element with no initializer declared as a const variable or a const-like + // parameter (a parameter for which there are no assignments in the function body), and if the parent type + // for the destructuring is a union type, one or more of the binding elements may represent discriminant + // properties, and we want the effects of conditional checks on such discriminants to affect the types of + // other binding elements from the same destructuring. Consider: + // + // type Action = + // | { kind: 'A', payload: number } + // | { kind: 'B', payload: string }; + // + // function f1({ kind, payload }: Action) { + // if (kind === 'A') { + // payload.toFixed(); + // } + // if (kind === 'B') { + // payload.toUpperCase(); + // } + // } + // + // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use + // the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference + // as if it occurred in the specified location. We then recompute the narrowed binding element type by + // destructuring from the narrowed parent type. + const declaration = symbol.valueDeclaration; + if (declaration && isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) { + const parent = declaration.parent.parent; + if (parent.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlags(declaration) & NodeFlags.Const || parent.kind === SyntaxKind.Parameter) { + const links = getNodeLinks(location); + if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) { + links.flags |= NodeCheckFlags.InCheckIdentifier; + const parentType = getTypeForBindingElementParent(parent); + links.flags &= ~NodeCheckFlags.InCheckIdentifier; + if (parentType && parentType.flags & TypeFlags.Union && !(parent.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) { + const pattern = declaration.parent; + const narrowedType = getFlowTypeOfReference(pattern, parentType, parentType, /*flowContainer*/ undefined, location.flowNode); + return getBindingElementTypeFromParentType(declaration, narrowedType); + } } - return unknownType; } + } + return getTypeOfSymbol(symbol); + } - attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType); + function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type { + const symbol = getResolvedSymbol(node); + if (symbol === unknownSymbol) { + return errorType; + } - if (isTypeAny(attributesType)) { - // Props is of type 'any' or unknown - return attributesType; + // As noted in ECMAScript 6 language spec, arrow functions never have an arguments objects. + // Although in down-level emit of arrow function, we emit it using function expression which means that + // arguments objects will be bound to the inner object; emitting arrow function natively in ES6, arguments objects + // will be bound to non-arrow function that contain this arrow function. This results in inconsistent behavior. + // To avoid that we will give an error to users if they use arguments objects in arrow function so that they + // can explicitly bound arguments objects + if (symbol === argumentsSymbol) { + if (isInPropertyInitializerOrClassStaticBlock(node)) { + error(node, Diagnostics.arguments_cannot_be_referenced_in_property_initializers); + return errorType; } - else { - // Normal case -- add in IntrinsicClassElements and IntrinsicElements - let apparentAttributesType = attributesType; - const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context); - if (!isErrorType(intrinsicClassAttribs)) { - const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); - const hostClassType = getReturnTypeOfSignature(sig); - apparentAttributesType = intersectTypes( - typeParams - ? createTypeReference(intrinsicClassAttribs as GenericType, fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isInJSFile(context))) - : intrinsicClassAttribs, - apparentAttributesType - ); - } - const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); - if (!isErrorType(intrinsicAttribs)) { - apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); + const container = getContainingFunction(node)!; + if (languageVersion < ScriptTarget.ES2015) { + if (container.kind === SyntaxKind.ArrowFunction) { + error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_ES3_and_ES5_Consider_using_a_standard_function_expression); + } + else if (hasSyntacticModifier(container, ModifierFlags.Async)) { + error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_async_function_or_method_in_ES3_and_ES5_Consider_using_a_standard_function_or_method); } - - return apparentAttributesType; } - } - - function getIntersectedSignatures(signatures: readonly Signature[]) { - return getStrictOptionValue(compilerOptions, "noImplicitAny") - ? reduceLeft( - signatures, - (left, right) => - left === right || !left ? left - : compareTypeParametersIdentical(left.typeParameters, right.typeParameters) ? combineSignaturesOfIntersectionMembers(left, right) - : undefined) - : undefined; - } - function combineIntersectionThisParam(left: Symbol | undefined, right: Symbol | undefined, mapper: TypeMapper | undefined): Symbol | undefined { - if (!left || !right) { - return left || right; - } - // A signature `this` type might be a read or a write position... It's very possible that it should be invariant - // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be - // pessimistic when contextual typing, for now, we'll union the `this` types. - const thisType = getUnionType([getTypeOfSymbol(left), instantiateType(getTypeOfSymbol(right), mapper)]); - return createSymbolWithType(left, thisType); - } - - function combineIntersectionParameters(left: Signature, right: Signature, mapper: TypeMapper | undefined) { - const leftCount = getParameterCount(left); - const rightCount = getParameterCount(right); - const longest = leftCount >= rightCount ? left : right; - const shorter = longest === left ? right : left; - const longestCount = longest === left ? leftCount : rightCount; - const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); - const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); - const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0)); - for (let i = 0; i < longestCount; i++) { - let longestParamType = tryGetTypeAtPosition(longest, i)!; - if (longest === right) { - longestParamType = instantiateType(longestParamType, mapper); - } - let shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; - if (shorter === right) { - shorterParamType = instantiateType(shorterParamType, mapper); - } - const unionParamType = getUnionType([longestParamType, shorterParamType]); - const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); - const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); - const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i); - const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i); - - const paramName = leftName === rightName ? leftName : - !leftName ? rightName : - !rightName ? leftName : - undefined; - const paramSymbol = createSymbol( - SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), - paramName || `arg${i}` as __String - ); - paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; - params[i] = paramSymbol; - } - if (needsExtraRestElement) { - const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); - restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); - if (shorter === right) { - restParamSymbol.type = instantiateType(restParamSymbol.type, mapper); - } - params[longestCount] = restParamSymbol; - } - return params; - } - - function combineSignaturesOfIntersectionMembers(left: Signature, right: Signature): Signature { - const typeParams = left.typeParameters || right.typeParameters; - let paramMapper: TypeMapper | undefined; - if (left.typeParameters && right.typeParameters) { - paramMapper = createTypeMapper(right.typeParameters, left.typeParameters); - // We just use the type parameter defaults from the first signature - } - const declaration = left.declaration; - const params = combineIntersectionParameters(left, right, paramMapper); - const thisParam = combineIntersectionThisParam(left.thisParameter, right.thisParameter, paramMapper); - const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); - const result = createSignature( - declaration, - typeParams, - thisParam, - params, - /*resolvedReturnType*/ undefined, - /*resolvedTypePredicate*/ undefined, - minArgCount, - (left.flags | right.flags) & SignatureFlags.PropagatingFlags - ); - result.compositeKind = TypeFlags.Intersection; - result.compositeSignatures = concatenate(left.compositeKind === TypeFlags.Intersection && left.compositeSignatures || [left], [right]); - if (paramMapper) { - result.mapper = left.compositeKind === TypeFlags.Intersection && left.mapper && left.compositeSignatures ? combineTypeMappers(left.mapper, paramMapper) : paramMapper; - } - return result; + getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; + return getTypeOfSymbol(symbol); } - // If the given type is an object or union type with a single signature, and if that signature has at - // least as many parameters as the given function, return the signature. Otherwise return undefined. - function getContextualCallSignature(type: Type, node: SignatureDeclaration): Signature | undefined { - const signatures = getSignaturesOfType(type, SignatureKind.Call); - const applicableByArity = filter(signatures, s => !isAritySmaller(s, node)); - return applicableByArity.length === 1 ? applicableByArity[0] : getIntersectedSignatures(applicableByArity); + // We should only mark aliases as referenced if there isn't a local value declaration + // for the symbol. Also, don't mark any property access expression LHS - checkPropertyAccessExpression will handle that + if (!(node.parent && isPropertyAccessExpression(node.parent) && node.parent.expression === node)) { + markAliasReferenced(symbol, node); } - /** If the contextual signature has fewer parameters than the function expression, do not use it */ - function isAritySmaller(signature: Signature, target: SignatureDeclaration) { - let targetParameterCount = 0; - for (; targetParameterCount < target.parameters.length; targetParameterCount++) { - const param = target.parameters[targetParameterCount]; - if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { - break; - } - } - if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { - targetParameterCount--; - } - return !hasEffectiveRestParameter(signature) && getParameterCount(signature) < targetParameterCount; + const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); + const sourceSymbol = localOrExportSymbol.flags & SymbolFlags.Alias ? resolveAlias(localOrExportSymbol) : localOrExportSymbol; + if (sourceSymbol.declarations && getDeclarationNodeFlagsFromSymbol(sourceSymbol) & NodeFlags.Deprecated && isUncalledFunctionReference(node, sourceSymbol)) { + addDeprecatedSuggestion(node, sourceSymbol.declarations, node.escapedText as string); } - function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): Signature | undefined { - // Only function expressions, arrow functions, and object literal methods are contextually typed. - return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node) - ? getContextualSignature(node as FunctionExpression) - : undefined; - } + let declaration = localOrExportSymbol.valueDeclaration; + if (declaration && localOrExportSymbol.flags & SymbolFlags.Class) { + // Due to the emit for class decorators, any reference to the class from inside of the class body + // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind + // behavior of class names in ES6. + if (declaration.kind === SyntaxKind.ClassDeclaration + && nodeIsDecorated(declaration as ClassDeclaration)) { + let container = getContainingClass(node); + while (container !== undefined) { + if (container === declaration && container.name !== node) { + getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; + getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; + break; + } - // Return the contextual signature for a given expression node. A contextual type provides a - // contextual signature if it has a single call signature and if that call signature is non-generic. - // If the contextual type is a union type, get the signature from each type possible and if they are - // all identical ignoring their return type, the result is same signature but with return type as - // union type of return types from these signatures - function getContextualSignature(node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature | undefined { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - const typeTagSignature = getSignatureOfTypeTag(node); - if (typeTagSignature) { - return typeTagSignature; - } - const type = getApparentTypeOfContextualType(node, ContextFlags.Signature); - if (!type) { - return undefined; - } - if (!(type.flags & TypeFlags.Union)) { - return getContextualCallSignature(type, node); + container = getContainingClass(container); + } } - let signatureList: Signature[] | undefined; - const types = (type as UnionType).types; - for (const current of types) { - const signature = getContextualCallSignature(current, node); - if (signature) { - if (!signatureList) { - // This signature will contribute to contextual union signature - signatureList = [signature]; - } - else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { - // Signatures aren't identical, do not use - return undefined; - } - else { - // Use this signature for contextual union signature - signatureList.push(signature); + else if (declaration.kind === SyntaxKind.ClassExpression) { + // When we emit a class expression with static members that contain a reference + // to the constructor in the initializer, we will need to substitute that + // binding with an alias as the class name is not in scope. + let container = getThisContainer(node, /*includeArrowFunctions*/ false); + while (container.kind !== SyntaxKind.SourceFile) { + if (container.parent === declaration) { + if (isPropertyDeclaration(container) && isStatic(container) || isClassStaticBlockDeclaration(container)) { + getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; + getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; + } + break; } + + container = getThisContainer(container, /*includeArrowFunctions*/ false); } } - // Result is union of signatures collected (return type is union of return types of this signature set) - if (signatureList) { - return signatureList.length === 1 ? signatureList[0] : createUnionSignature(signatureList[0], signatureList); - } } - function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type { - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray); - } - - const arrayOrIterableType = checkExpression(node.expression, checkMode); - return checkIteratedTypeOrElementType(IterationUse.Spread, arrayOrIterableType, undefinedType, node.expression); - } + checkNestedBlockScopedBinding(node, symbol); - function checkSyntheticExpression(node: SyntheticExpression): Type { - return node.isSpread ? getIndexedAccessType(node.type, numberType) : node.type; - } + let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node); + const assignmentKind = getAssignmentTargetKind(node); - function hasDefaultValue(node: BindingElement | Expression): boolean { - return (node.kind === SyntaxKind.BindingElement && !!(node as BindingElement).initializer) || - (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken); - } + if (assignmentKind) { + if (!(localOrExportSymbol.flags & SymbolFlags.Variable) && + !(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule)) { + const assignmentError = localOrExportSymbol.flags & SymbolFlags.Enum ? Diagnostics.Cannot_assign_to_0_because_it_is_an_enum + : localOrExportSymbol.flags & SymbolFlags.Class ? Diagnostics.Cannot_assign_to_0_because_it_is_a_class + : localOrExportSymbol.flags & SymbolFlags.Module ? Diagnostics.Cannot_assign_to_0_because_it_is_a_namespace + : localOrExportSymbol.flags & SymbolFlags.Function ? Diagnostics.Cannot_assign_to_0_because_it_is_a_function + : localOrExportSymbol.flags & SymbolFlags.Alias ? Diagnostics.Cannot_assign_to_0_because_it_is_an_import + : Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable; - function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined, forceTuple: boolean | undefined): Type { - const elements = node.elements; - const elementCount = elements.length; - const elementTypes: Type[] = []; - const elementFlags: ElementFlags[] = []; - const contextualType = getApparentTypeOfContextualType(node); - const inDestructuringPattern = isAssignmentTarget(node); - const inConstContext = isConstContext(node); - let hasOmittedExpression = false; - for (let i = 0; i < elementCount; i++) { - const e = elements[i]; - if (e.kind === SyntaxKind.SpreadElement) { - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(e, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray); - } - const spreadType = checkExpression((e as SpreadElement).expression, checkMode, forceTuple); - if (isArrayLikeType(spreadType)) { - elementTypes.push(spreadType); - elementFlags.push(ElementFlags.Variadic); - } - else if (inDestructuringPattern) { - // Given the following situation: - // var c: {}; - // [...c] = ["", 0]; - // - // c is represented in the tree as a spread element in an array literal. - // But c really functions as a rest element, and its purpose is to provide - // a contextual type for the right hand side of the assignment. Therefore, - // instead of calling checkExpression on "...c", which will give an error - // if c is not iterable/array-like, we need to act as if we are trying to - // get the contextual element type from it. So we do something similar to - // getContextualTypeForElementExpression, which will crucially not error - // if there is no index type / iterated type. - const restElementType = getIndexTypeOfType(spreadType, numberType) || - getIteratedTypeOrElementType(IterationUse.Destructuring, spreadType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false) || - unknownType; - elementTypes.push(restElementType); - elementFlags.push(ElementFlags.Rest); - } - else { - elementTypes.push(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, (e as SpreadElement).expression)); - elementFlags.push(ElementFlags.Rest); - } - } - else if (exactOptionalPropertyTypes && e.kind === SyntaxKind.OmittedExpression) { - hasOmittedExpression = true; - elementTypes.push(missingType); - elementFlags.push(ElementFlags.Optional); + error(node, assignmentError, symbolToString(symbol)); + return errorType; + } + if (isReadonlySymbol(localOrExportSymbol)) { + if (localOrExportSymbol.flags & SymbolFlags.Variable) { + error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant, symbolToString(symbol)); } else { - const elementContextualType = getContextualTypeForElementExpression(contextualType, elementTypes.length); - const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple); - elementTypes.push(addOptionality(type, /*isProperty*/ true, hasOmittedExpression)); - elementFlags.push(hasOmittedExpression ? ElementFlags.Optional : ElementFlags.Required); + error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(symbol)); } + return errorType; } - if (inDestructuringPattern) { - return createTupleType(elementTypes, elementFlags); - } - if (forceTuple || inConstContext || contextualType && someType(contextualType, isTupleLikeType)) { - return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext)); - } - return createArrayLiteralType(createArrayType(elementTypes.length ? - getUnionType(sameMap(elementTypes, (t, i) => elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessTypeOrUndefined(t, numberType) || anyType : t), UnionReduction.Subtype) : - strictNullChecks ? implicitNeverType : undefinedWideningType, inConstContext)); } - function createArrayLiteralType(type: Type) { - if (!(getObjectFlags(type) & ObjectFlags.Reference)) { + const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias; + + // We only narrow variables and parameters occurring in a non-assignment position. For all other + // entities we simply return the declared type. + if (localOrExportSymbol.flags & SymbolFlags.Variable) { + if (assignmentKind === AssignmentKind.Definite) { return type; } - let literalType = (type as TypeReference).literalType; - if (!literalType) { - literalType = (type as TypeReference).literalType = cloneTypeReference(type as TypeReference); - literalType.objectFlags |= ObjectFlags.ArrayLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - } - return literalType; } - - function isNumericName(name: DeclarationName): boolean { - switch (name.kind) { - case SyntaxKind.ComputedPropertyName: - return isNumericComputedName(name); - case SyntaxKind.Identifier: - return isNumericLiteralName(name.escapedText); - case SyntaxKind.NumericLiteral: - case SyntaxKind.StringLiteral: - return isNumericLiteralName(name.text); - default: - return false; - } + else if (isAlias) { + declaration = getDeclarationOfAliasSymbol(symbol); } - - function isNumericComputedName(name: ComputedPropertyName): boolean { - // It seems odd to consider an expression of type Any to result in a numeric name, - // but this behavior is consistent with checkIndexedAccess - return isTypeAssignableToKind(checkComputedPropertyName(name), TypeFlags.NumberLike); + else { + return type; } - function isNumericLiteralName(name: string | __String) { - // The intent of numeric names is that - // - they are names with text in a numeric form, and that - // - setting properties/indexing with them is always equivalent to doing so with the numeric literal 'numLit', - // acquired by applying the abstract 'ToNumber' operation on the name's text. - // - // The subtlety is in the latter portion, as we cannot reliably say that anything that looks like a numeric literal is a numeric name. - // In fact, it is the case that the text of the name must be equal to 'ToString(numLit)' for this to hold. - // - // Consider the property name '"0xF00D"'. When one indexes with '0xF00D', they are actually indexing with the value of 'ToString(0xF00D)' - // according to the ECMAScript specification, so it is actually as if the user indexed with the string '"61453"'. - // Thus, the text of all numeric literals equivalent to '61543' such as '0xF00D', '0xf00D', '0170015', etc. are not valid numeric names - // because their 'ToString' representation is not equal to their original text. - // This is motivated by ECMA-262 sections 9.3.1, 9.8.1, 11.1.5, and 11.2.1. - // - // Here, we test whether 'ToString(ToNumber(name))' is exactly equal to 'name'. - // The '+' prefix operator is equivalent here to applying the abstract ToNumber operation. - // Applying the 'toString()' method on a number gives us the abstract ToString operation on a number. - // - // Note that this accepts the values 'Infinity', '-Infinity', and 'NaN', and that this is intentional. - // This is desired behavior, because when indexing with them as numeric entities, you are indexing - // with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively. - return (+name).toString() === name; + if (!declaration) { + return type; } - function checkComputedPropertyName(node: ComputedPropertyName): Type { - const links = getNodeLinks(node.expression); - if (!links.resolvedType) { - if ((isTypeLiteralNode(node.parent.parent) || isClassLike(node.parent.parent) || isInterfaceDeclaration(node.parent.parent)) - && isBinaryExpression(node.expression) && node.expression.operatorToken.kind === SyntaxKind.InKeyword) { - return links.resolvedType = errorType; - } - links.resolvedType = checkExpression(node.expression); - // The computed property name of a non-static class field within a loop must be stored in a block-scoped binding. - // (It needs to be bound at class evaluation time.) - if (isPropertyDeclaration(node.parent) && !hasStaticModifier(node.parent) && isClassExpression(node.parent.parent)) { - const container = getEnclosingBlockScopeContainer(node.parent.parent); - const enclosingIterationStatement = getEnclosingIterationStatement(container); - if (enclosingIterationStatement) { - // The computed field name will use a block scoped binding which can be unique for each iteration of the loop. - getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; - // The generated variable which stores the computed field name must be block-scoped. - getNodeLinks(node).flags |= NodeCheckFlags.BlockScopedBindingInLoop; - // The generated variable which stores the class must be block-scoped. - getNodeLinks(node.parent.parent).flags |= NodeCheckFlags.BlockScopedBindingInLoop; - } - } - // This will allow types number, string, symbol or any. It will also allow enums, the unknown - // type, and any union of these types (like string | number). - if (links.resolvedType.flags & TypeFlags.Nullable || - !isTypeAssignableToKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) && - !isTypeAssignableTo(links.resolvedType, stringNumberSymbolType)) { - error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any); + type = getNarrowableTypeForReference(type, node, checkMode); + + // The declaration container is the innermost function that encloses the declaration of the variable + // or parameter. The flow container is the innermost function starting with which we analyze the control + // flow graph to determine the control flow based type. + const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter; + const declarationContainer = getControlFlowContainer(declaration); + let flowContainer = getControlFlowContainer(node); + const isOuterVariable = flowContainer !== declarationContainer; + const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent); + const isModuleExports = symbol.flags & SymbolFlags.ModuleExports; + // When the control flow originates in a function expression or arrow function and we are referencing + // a const variable or parameter from an outer function, we extend the origin of the control flow + // analysis to include the immediately enclosing function. + while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || + flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)) && + (isConstVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isSymbolAssigned(localOrExportSymbol))) { + flowContainer = getControlFlowContainer(flowContainer); + } + // We only look for uninitialized variables in strict null checking mode, and only when we can analyze + // the entire control flow graph from the variable's declaration (i.e. when the flow container and + // declaration container are the same). + const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isBindingElement(declaration) || + type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 || + isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) || + node.parent.kind === SyntaxKind.NonNullExpression || + declaration.kind === SyntaxKind.VariableDeclaration && (declaration as VariableDeclaration).exclamationToken || + declaration.flags & NodeFlags.Ambient; + const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : + type === autoType || type === autoArrayType ? undefinedType : + getOptionalType(type); + const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer); + // A variable is considered uninitialized when it is possible to analyze the entire control flow graph + // from declaration to use, and when the variable's declared type doesn't include undefined but the + // control flow based type does include undefined. + if (!isEvolvingArrayOperationTarget(node) && (type === autoType || type === autoArrayType)) { + if (flowType === autoType || flowType === autoArrayType) { + if (noImplicitAny) { + error(getNameOfDeclaration(declaration), Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(flowType)); + error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); } + return convertAutoToAny(flowType); } - - return links.resolvedType; } - - function isSymbolWithNumericName(symbol: Symbol) { - const firstDecl = symbol.declarations?.[0]; - return isNumericLiteralName(symbol.escapedName) || (firstDecl && isNamedDeclaration(firstDecl) && isNumericName(firstDecl.name)); + else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { + error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); + // Return the declared type to reduce follow-on errors + return type; } + return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + } - function isSymbolWithSymbolName(symbol: Symbol) { - const firstDecl = symbol.declarations?.[0]; - return isKnownSymbol(symbol) || (firstDecl && isNamedDeclaration(firstDecl) && isComputedPropertyName(firstDecl.name) && - isTypeAssignableToKind(checkComputedPropertyName(firstDecl.name), TypeFlags.ESSymbol)); - } + function isInsideFunctionOrInstancePropertyInitializer(node: Node, threshold: Node): boolean { + return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n) || ( + n.parent && isPropertyDeclaration(n.parent) && !hasStaticModifier(n.parent) && n.parent.initializer === n + )); + } - function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, offset: number, properties: Symbol[], keyType: Type): IndexInfo { - const propTypes: Type[] = []; - for (let i = offset; i < properties.length; i++) { - const prop = properties[i]; - if (keyType === stringType && !isSymbolWithSymbolName(prop) || - keyType === numberType && isSymbolWithNumericName(prop) || - keyType === esSymbolType && isSymbolWithSymbolName(prop)) { - propTypes.push(getTypeOfSymbol(properties[i])); - } - } - const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType; - return createIndexInfo(keyType, unionType, isConstContext(node)); - } + function getPartOfForStatementContainingNode(node: Node, container: ForStatement) { + return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement); + } - function getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined { - Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); - const links = getSymbolLinks(symbol); - if (!links.immediateTarget) { - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - links.immediateTarget = getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true); - } + function getEnclosingIterationStatement(node: Node): Node | undefined { + return findAncestor(node, n => (!n || nodeStartsNewLexicalEnvironment(n)) ? "quit" : isIterationStatement(n, /*lookInLabeledStatements*/ false)); + } - return links.immediateTarget; + function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void { + if (languageVersion >= ScriptTarget.ES2015 || + (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 || + !symbol.valueDeclaration || + isSourceFile(symbol.valueDeclaration) || + symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause) { + return; } - function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type { - const inDestructuringPattern = isAssignmentTarget(node); - // Grammar checking - checkGrammarObjectLiteralExpression(node, inDestructuringPattern); - - const allPropertiesTable = strictNullChecks ? createSymbolTable() : undefined; - let propertiesTable = createSymbolTable(); - let propertiesArray: Symbol[] = []; - let spread: Type = emptyObjectType; - - const contextualType = getApparentTypeOfContextualType(node); - const contextualTypeHasPattern = contextualType && contextualType.pattern && - (contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression); - const inConstContext = isConstContext(node); - const checkFlags = inConstContext ? CheckFlags.Readonly : 0; - const isInJavascript = isInJSFile(node) && !isInJsonFile(node); - const enumTag = getJSDocEnumTag(node); - const isJSObjectLiteral = !contextualType && isInJavascript && !enumTag; - let objectFlags: ObjectFlags = freshObjectLiteralFlag; - let patternWithComputedProperties = false; - let hasComputedStringProperty = false; - let hasComputedNumberProperty = false; - let hasComputedSymbolProperty = false; - - // Spreads may cause an early bail; ensure computed names are always checked (this is cached) - // As otherwise they may not be checked until exports for the type at this position are retrieved, - // which may never occur. - for (const elem of node.properties) { - if (elem.name && isComputedPropertyName(elem.name)) { - checkComputedPropertyName(elem.name); - } - } - - let offset = 0; - for (const memberDecl of node.properties) { - let member = getSymbolOfNode(memberDecl); - const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName ? - checkComputedPropertyName(memberDecl.name) : undefined; - if (memberDecl.kind === SyntaxKind.PropertyAssignment || - memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment || - isObjectLiteralMethod(memberDecl)) { - let type = memberDecl.kind === SyntaxKind.PropertyAssignment ? checkPropertyAssignment(memberDecl, checkMode) : - // avoid resolving the left side of the ShorthandPropertyAssignment outside of the destructuring - // for error recovery purposes. For example, if a user wrote `{ a = 100 }` instead of `{ a: 100 }`. - // we don't want to say "could not find 'a'". - memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ? checkExpressionForMutableLocation(!inDestructuringPattern && memberDecl.objectAssignmentInitializer ? memberDecl.objectAssignmentInitializer : memberDecl.name, checkMode) : - checkObjectLiteralMethod(memberDecl, checkMode); - if (isInJavascript) { - const jsDocType = getTypeForDeclarationFromJSDocComment(memberDecl); - if (jsDocType) { - checkTypeAssignableTo(type, jsDocType, memberDecl); - type = jsDocType; - } - else if (enumTag && enumTag.typeExpression) { - checkTypeAssignableTo(type, getTypeFromTypeNode(enumTag.typeExpression), memberDecl); - } - } - objectFlags |= getObjectFlags(type) & ObjectFlags.PropagatingFlags; - const nameType = computedNameType && isTypeUsableAsPropertyName(computedNameType) ? computedNameType : undefined; - const prop = nameType ? - createSymbol(SymbolFlags.Property | member.flags, getPropertyNameFromType(nameType), checkFlags | CheckFlags.Late) : - createSymbol(SymbolFlags.Property | member.flags, member.escapedName, checkFlags); - if (nameType) { - prop.nameType = nameType; - } - - if (inDestructuringPattern) { - // If object literal is an assignment pattern and if the assignment pattern specifies a default value - // for the property, make the property optional. - const isOptional = - (memberDecl.kind === SyntaxKind.PropertyAssignment && hasDefaultValue(memberDecl.initializer)) || - (memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment && memberDecl.objectAssignmentInitializer); - if (isOptional) { - prop.flags |= SymbolFlags.Optional; - } - } - else if (contextualTypeHasPattern && !(getObjectFlags(contextualType) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) { - // If object literal is contextually typed by the implied type of a binding pattern, and if the - // binding pattern specifies a default value for the property, make the property optional. - const impliedProp = getPropertyOfType(contextualType, member.escapedName); - if (impliedProp) { - prop.flags |= impliedProp.flags & SymbolFlags.Optional; - } + // 1. walk from the use site up to the declaration and check + // if there is anything function like between declaration and use-site (is binding/class is captured in function). + // 2. walk from the declaration up to the boundary of lexical environment and check + // if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement) - else if (!compilerOptions.suppressExcessPropertyErrors && !getIndexInfoOfType(contextualType, stringType)) { - error(memberDecl.name, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, - symbolToString(member), typeToString(contextualType)); - } - } + const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); + const isCaptured = isInsideFunctionOrInstancePropertyInitializer(node, container); - prop.declarations = member.declarations; - prop.parent = member.parent; - if (member.valueDeclaration) { - prop.valueDeclaration = member.valueDeclaration; - } + const enclosingIterationStatement = getEnclosingIterationStatement(container); + if (enclosingIterationStatement) { + if (isCaptured) { + // mark iteration statement as containing block-scoped binding captured in some function + let capturesBlockScopeBindingInLoopBody = true; + if (isForStatement(container)) { + const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList); + if (varDeclList && varDeclList.parent === container) { + const part = getPartOfForStatementContainingNode(node.parent, container); + if (part) { + const links = getNodeLinks(part); + links.flags |= NodeCheckFlags.ContainsCapturedBlockScopeBinding; - prop.type = type; - prop.target = member; - member = prop; - allPropertiesTable?.set(prop.escapedName, prop); - } - else if (memberDecl.kind === SyntaxKind.SpreadAssignment) { - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(memberDecl, ExternalEmitHelpers.Assign); - } - if (propertiesArray.length > 0) { - spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); - propertiesArray = []; - propertiesTable = createSymbolTable(); - hasComputedStringProperty = false; - hasComputedNumberProperty = false; - hasComputedSymbolProperty = false; - } - const type = getReducedType(checkExpression(memberDecl.expression)); - if (isValidSpreadType(type)) { - const mergedType = tryMergeUnionOfObjectTypeAndEmptyObject(type, inConstContext); - if (allPropertiesTable) { - checkSpreadPropOverrides(mergedType, allPropertiesTable, memberDecl); - } - offset = propertiesArray.length; - if (isErrorType(spread)) { - continue; - } - spread = getSpreadType(spread, mergedType, node.symbol, objectFlags, inConstContext); - } - else { - error(memberDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types); - spread = errorType; - } - continue; - } - else { - // TypeScript 1.0 spec (April 2014) - // A get accessor declaration is processed in the same manner as - // an ordinary function declaration(section 6.1) with no parameters. - // A set accessor declaration is processed in the same manner - // as an ordinary function declaration with a single parameter and a Void return type. - Debug.assert(memberDecl.kind === SyntaxKind.GetAccessor || memberDecl.kind === SyntaxKind.SetAccessor); - checkNodeDeferred(memberDecl); - } + const capturedBindings = links.capturedBlockScopeBindings || (links.capturedBlockScopeBindings = []); + pushIfUnique(capturedBindings, symbol); - if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) { - if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) { - if (isTypeAssignableTo(computedNameType, numberType)) { - hasComputedNumberProperty = true; - } - else if (isTypeAssignableTo(computedNameType, esSymbolType)) { - hasComputedSymbolProperty = true; - } - else { - hasComputedStringProperty = true; - } - if (inDestructuringPattern) { - patternWithComputedProperties = true; + if (part === container.initializer) { + capturesBlockScopeBindingInLoopBody = false; // Initializer is outside of loop body + } } } } - else { - propertiesTable.set(member.escapedName, member); - } - propertiesArray.push(member); - } - - // If object literal is contextually typed by the implied type of a binding pattern, augment the result - // type with those properties for which the binding pattern specifies a default value. - // If the object literal is spread into another object literal, skip this step and let the top-level object - // literal handle it instead. - if (contextualTypeHasPattern && node.parent.kind !== SyntaxKind.SpreadAssignment) { - for (const prop of getPropertiesOfType(contextualType)) { - if (!propertiesTable.get(prop.escapedName) && !getPropertyOfType(spread, prop.escapedName)) { - if (!(prop.flags & SymbolFlags.Optional)) { - error(prop.valueDeclaration || (prop as TransientSymbol).bindingElement, - Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value); - } - propertiesTable.set(prop.escapedName, prop); - propertiesArray.push(prop); - } + if (capturesBlockScopeBindingInLoopBody) { + getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; } } - if (isErrorType(spread)) { - return errorType; - } - - if (spread !== emptyObjectType) { - if (propertiesArray.length > 0) { - spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); - propertiesArray = []; - propertiesTable = createSymbolTable(); - hasComputedStringProperty = false; - hasComputedNumberProperty = false; + // mark variables that are declared in loop initializer and reassigned inside the body of ForStatement. + // if body of ForStatement will be converted to function then we'll need a extra machinery to propagate reassigned values back. + if (isForStatement(container)) { + const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList); + if (varDeclList && varDeclList.parent === container && isAssignedInBodyOfForStatement(node, container)) { + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.NeedsLoopOutParameter; } - // remap the raw emptyObjectType fed in at the top into a fresh empty object literal type, unique to this use site - return mapType(spread, t => t === emptyObjectType ? createObjectLiteralType() : t); } - return createObjectLiteralType(); - - function createObjectLiteralType() { - const indexInfos = []; - if (hasComputedStringProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, stringType)); - if (hasComputedNumberProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, numberType)); - if (hasComputedSymbolProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, esSymbolType)); - const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, indexInfos); - result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - if (isJSObjectLiteral) { - result.objectFlags |= ObjectFlags.JSLiteral; - } - if (patternWithComputedProperties) { - result.objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; - } - if (inDestructuringPattern) { - result.pattern = node; - } - return result; - } + // set 'declared inside loop' bit on the block-scoped binding + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; } - function isValidSpreadType(type: Type): boolean { - if (type.flags & TypeFlags.Instantiable) { - const constraint = getBaseConstraintOfType(type); - if (constraint !== undefined) { - return isValidSpreadType(constraint); - } - } - return !!(type.flags & (TypeFlags.Any | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) || - getFalsyFlags(type) & TypeFlags.DefinitelyFalsy && isValidSpreadType(removeDefinitelyFalsyTypes(type)) || - type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isValidSpreadType)); + if (isCaptured) { + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; } + } - function checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) { - checkJsxOpeningLikeElementOrOpeningFragment(node); - } + function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { + const links = getNodeLinks(node); + return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfNode(decl)); + } - function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type { - checkNodeDeferred(node); - return getJsxElementTypeAt(node) || anyType; + function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean { + // skip parenthesized nodes + let current: Node = node; + while (current.parent.kind === SyntaxKind.ParenthesizedExpression) { + current = current.parent; } - function checkJsxElementDeferred(node: JsxElement) { - // Check attributes - checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement); - - // Perform resolution on the closing tag so that rename/go to definition/etc work - if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) { - getIntrinsicTagSymbol(node.closingElement); - } - else { - checkExpression(node.closingElement.tagName); - } - - checkJsxChildren(node); + // check if node is used as LHS in some assignment expression + let isAssigned = false; + if (isAssignmentTarget(current)) { + isAssigned = true; } - - function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type { - checkNodeDeferred(node); - - return getJsxElementTypeAt(node) || anyType; + else if ((current.parent.kind === SyntaxKind.PrefixUnaryExpression || current.parent.kind === SyntaxKind.PostfixUnaryExpression)) { + const expr = current.parent as PrefixUnaryExpression | PostfixUnaryExpression; + isAssigned = expr.operator === SyntaxKind.PlusPlusToken || expr.operator === SyntaxKind.MinusMinusToken; } - function checkJsxFragment(node: JsxFragment): Type { - checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment); - - // by default, jsx:'react' will use jsxFactory = React.createElement and jsxFragmentFactory = React.Fragment - // if jsxFactory compiler option is provided, ensure jsxFragmentFactory compiler option or @jsxFrag pragma is provided too - const nodeSourceFile = getSourceFileOfNode(node); - if (getJSXTransformEnabled(compilerOptions) && (compilerOptions.jsxFactory || nodeSourceFile.pragmas.has("jsx")) - && !compilerOptions.jsxFragmentFactory && !nodeSourceFile.pragmas.has("jsxfrag")) { - error(node, compilerOptions.jsxFactory - ? Diagnostics.The_jsxFragmentFactory_compiler_option_must_be_provided_to_use_JSX_fragments_with_the_jsxFactory_compiler_option - : Diagnostics.An_jsxFrag_pragma_is_required_when_using_an_jsx_pragma_with_JSX_fragments); - } - - checkJsxChildren(node); - return getJsxElementTypeAt(node) || anyType; + if (!isAssigned) { + return false; } - function isHyphenatedJsxName(name: string | __String) { - return stringContains(name as string, "-"); - } + // at this point we know that node is the target of assignment + // now check that modification happens inside the statement part of the ForStatement + return !!findAncestor(current, n => n === container ? "quit" : n === container.statement); + } - /** - * Returns true iff React would emit this tag name as a string rather than an identifier or qualified name - */ - function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): boolean { - return tagName.kind === SyntaxKind.Identifier && isIntrinsicJsxName(tagName.escapedText); + function captureLexicalThis(node: Node, container: Node): void { + getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis; + if (container.kind === SyntaxKind.PropertyDeclaration || container.kind === SyntaxKind.Constructor) { + const classNode = container.parent; + getNodeLinks(classNode).flags |= NodeCheckFlags.CaptureThis; } - - function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) { - return node.initializer - ? checkExpressionForMutableLocation(node.initializer, checkMode) - : trueType; // is sugar for + else { + getNodeLinks(container).flags |= NodeCheckFlags.CaptureThis; } + } - /** - * Get attributes type of the JSX opening-like element. The result is from resolving "attributes" property of the opening-like element. - * - * @param openingLikeElement a JSX opening-like element - * @param filter a function to remove attributes that will not participate in checking whether attributes are assignable - * @return an anonymous type (similar to the one returned by checkObjectLiteral) in which its properties are attributes property. - * @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral, - * which also calls getSpreadType. - */ - function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode | undefined) { - const attributes = openingLikeElement.attributes; - const allAttributesTable = strictNullChecks ? createSymbolTable() : undefined; - let attributesTable = createSymbolTable(); - let spread: Type = emptyJsxObjectType; - let hasSpreadAnyType = false; - let typeToIntersect: Type | undefined; - let explicitlySpecifyChildrenAttribute = false; - let objectFlags: ObjectFlags = ObjectFlags.JsxAttributes; - const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(openingLikeElement)); - - for (const attributeDecl of attributes.properties) { - const member = attributeDecl.symbol; - if (isJsxAttribute(attributeDecl)) { - const exprType = checkJsxAttribute(attributeDecl, checkMode); - objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags; - - const attributeSymbol = createSymbol(SymbolFlags.Property | member.flags, member.escapedName); - attributeSymbol.declarations = member.declarations; - attributeSymbol.parent = member.parent; - if (member.valueDeclaration) { - attributeSymbol.valueDeclaration = member.valueDeclaration; - } - attributeSymbol.type = exprType; - attributeSymbol.target = member; - attributesTable.set(attributeSymbol.escapedName, attributeSymbol); - allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol); - if (attributeDecl.name.escapedText === jsxChildrenPropertyName) { - explicitlySpecifyChildrenAttribute = true; - } - } - else { - Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute); - if (attributesTable.size > 0) { - spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); - attributesTable = createSymbolTable(); - } - const exprType = getReducedType(checkExpressionCached(attributeDecl.expression, checkMode)); - if (isTypeAny(exprType)) { - hasSpreadAnyType = true; - } - if (isValidSpreadType(exprType)) { - spread = getSpreadType(spread, exprType, attributes.symbol, objectFlags, /*readonly*/ false); - if (allAttributesTable) { - checkSpreadPropOverrides(exprType, allAttributesTable, attributeDecl); - } - } - else { - typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType; - } - } - } - - if (!hasSpreadAnyType) { - if (attributesTable.size > 0) { - spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); - } - } - - // Handle children attribute - const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ? openingLikeElement.parent as JsxElement : undefined; - // We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement - if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) { - const childrenTypes: Type[] = checkJsxChildren(parent, checkMode); - - if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") { - // Error if there is a attribute named "children" explicitly specified and children element. - // This is because children element will overwrite the value from attributes. - // Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread. - if (explicitlySpecifyChildrenAttribute) { - error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName)); - } + function findFirstSuperCall(node: Node): SuperCall | undefined { + return isSuperCall(node) ? node : + isFunctionLike(node) ? undefined : + forEachChild(node, findFirstSuperCall); + } - const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes); - const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName); - // If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process - const childrenPropSymbol = createSymbol(SymbolFlags.Property, jsxChildrenPropertyName); - childrenPropSymbol.type = childrenTypes.length === 1 ? childrenTypes[0] : - childrenContextualType && someType(childrenContextualType, isTupleLikeType) ? createTupleType(childrenTypes) : - createArrayType(getUnionType(childrenTypes)); - // Fake up a property declaration for the children - childrenPropSymbol.valueDeclaration = factory.createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined); - setParent(childrenPropSymbol.valueDeclaration, attributes); - childrenPropSymbol.valueDeclaration.symbol = childrenPropSymbol; - const childPropMap = createSymbolTable(); - childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol); - spread = getSpreadType(spread, createAnonymousType(attributes.symbol, childPropMap, emptyArray, emptyArray, emptyArray), - attributes.symbol, objectFlags, /*readonly*/ false); + /** + * Check if the given class-declaration extends null then return true. + * Otherwise, return false + * @param classDecl a class declaration to check if it extends null + */ + function classDeclarationExtendsNull(classDecl: ClassDeclaration): boolean { + const classSymbol = getSymbolOfNode(classDecl); + const classInstanceType = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType; + const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType); + + return baseConstructorType === nullWideningType; + } - } - } + function checkThisBeforeSuper(node: Node, container: Node, diagnosticMessage: DiagnosticMessage) { + const containingClassDecl = container.parent as ClassDeclaration; + const baseTypeNode = getClassExtendsHeritageElement(containingClassDecl); - if (hasSpreadAnyType) { - return anyType; - } - if (typeToIntersect && spread !== emptyJsxObjectType) { - return getIntersectionType([typeToIntersect, spread]); + // If a containing class does not have extends clause or the class extends null + // skip checking whether super statement is called before "this" accessing. + if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) { + if (node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) { + error(node, diagnosticMessage); } - return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread); + } + } - /** - * Create anonymous type from given attributes symbol table. - * @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable - * @param attributesTable a symbol table of attributes property - */ - function createJsxAttributesType() { - objectFlags |= freshObjectLiteralFlag; - const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, emptyArray); - result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - return result; - } + function checkThisInStaticClassFieldInitializerInDecoratedClass(thisExpression: Node, container: Node) { + if (isPropertyDeclaration(container) && hasStaticModifier(container) && + container.initializer && textRangeContainsPositionInclusive(container.initializer, thisExpression.pos) && length(container.parent.decorators)) { + error(thisExpression, Diagnostics.Cannot_use_this_in_a_static_property_initializer_of_a_decorated_class); } + } - function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode) { - const childrenTypes: Type[] = []; - for (const child of node.children) { - // In React, JSX text that contains only whitespaces will be ignored so we don't want to type-check that - // because then type of children property will have constituent of string type. - if (child.kind === SyntaxKind.JsxText) { - if (!child.containsOnlyTriviaWhiteSpaces) { - childrenTypes.push(stringType); - } - } - else if (child.kind === SyntaxKind.JsxExpression && !child.expression) { - continue; // empty jsx expressions don't *really* count as present children - } - else { - childrenTypes.push(checkExpressionForMutableLocation(child, checkMode)); - } - } - return childrenTypes; + function checkThisExpression(node: Node): Type { + const isNodeInTypeQuery = isInTypeQuery(node); + // Stop at the first arrow function so that we can + // tell whether 'this' needs to be captured. + let container = getThisContainer(node, /* includeArrowFunctions */ true); + let capturedByArrowFunction = false; + + if (container.kind === SyntaxKind.Constructor) { + checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); } - function checkSpreadPropOverrides(type: Type, props: SymbolTable, spread: SpreadAssignment | JsxSpreadAttribute) { - for (const right of getPropertiesOfType(type)) { - if (!(right.flags & SymbolFlags.Optional)) { - const left = props.get(right.escapedName); - if (left) { - const diagnostic = error(left.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(left.escapedName)); - addRelatedInfo(diagnostic, createDiagnosticForNode(spread, Diagnostics.This_spread_always_overwrites_this_property)); - } - } - } + // Now skip arrow functions to get the "real" owner of 'this'. + if (container.kind === SyntaxKind.ArrowFunction) { + container = getThisContainer(container, /* includeArrowFunctions */ false); + capturedByArrowFunction = true; } - /** - * Check attributes property of opening-like element. This function is called during chooseOverload to get call signature of a JSX opening-like element. - * (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used) - * @param node a JSXAttributes to be resolved of its type - */ - function checkJsxAttributes(node: JsxAttributes, checkMode: CheckMode | undefined) { - return createJsxAttributesTypeFromAttributesProperty(node.parent, checkMode); + checkThisInStaticClassFieldInitializerInDecoratedClass(node, container); + switch (container.kind) { + case SyntaxKind.ModuleDeclaration: + error(node, Diagnostics.this_cannot_be_referenced_in_a_module_or_namespace_body); + // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks + break; + case SyntaxKind.EnumDeclaration: + error(node, Diagnostics.this_cannot_be_referenced_in_current_location); + // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks + break; + case SyntaxKind.Constructor: + if (isInConstructorArgumentInitializer(node, container)) { + error(node, Diagnostics.this_cannot_be_referenced_in_constructor_arguments); + // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks + } + break; + case SyntaxKind.ComputedPropertyName: + error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name); + break; } - function getJsxType(name: __String, location: Node | undefined) { - const namespace = getJsxNamespaceAt(location); - const exports = namespace && getExportsOfSymbol(namespace); - const typeSymbol = exports && getSymbol(exports, name, SymbolFlags.Type); - return typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType; + // When targeting es6, mark that we'll need to capture `this` in its lexically bound scope. + if (!isNodeInTypeQuery && capturedByArrowFunction && languageVersion < ScriptTarget.ES2015) { + captureLexicalThis(node, container); } - /** - * Looks up an intrinsic tag name and returns a symbol that either points to an intrinsic - * property (in which case nodeLinks.jsxFlags will be IntrinsicNamedElement) or an intrinsic - * string index signature (in which case nodeLinks.jsxFlags will be IntrinsicIndexedElement). - * May also return unknownSymbol if both of these lookups fail. - */ - function getIntrinsicTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): Symbol { - const links = getNodeLinks(node); - if (!links.resolvedSymbol) { - const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, node); - if (!isErrorType(intrinsicElementsType)) { - // Property case - if (!isIdentifier(node.tagName)) return Debug.fail(); - const intrinsicProp = getPropertyOfType(intrinsicElementsType, node.tagName.escapedText); - if (intrinsicProp) { - links.jsxFlags |= JsxFlags.IntrinsicNamedElement; - return links.resolvedSymbol = intrinsicProp; + const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container); + if (noImplicitThis) { + const globalThisType = getTypeOfSymbol(globalThisSymbol); + if (type === globalThisType && capturedByArrowFunction) { + error(node, Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this); + } + else if (!type) { + // With noImplicitThis, functions may not reference 'this' if it has type 'any' + const diag = error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation); + if (!isSourceFile(container)) { + const outsideThis = tryGetThisTypeAt(container); + if (outsideThis && outsideThis !== globalThisType) { + addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container)); } + } + } + } + return type || anyType; + } - // Intrinsic string indexer case - const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, stringType); - if (indexSignatureType) { - links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; - return links.resolvedSymbol = intrinsicElementsType.symbol; + function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined { + const isInJS = isInJSFile(node); + if (isFunctionLike(container) && + (!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) { + let thisType = getThisTypeOfDeclaration(container) || isInJS && getTypeForThisExpressionFromJSDoc(container); + // Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated. + // If this is a function in a JS file, it might be a class method. + if (!thisType) { + const className = getClassNameFromPrototypeMethod(container); + if (isInJS && className) { + const classSymbol = checkExpression(className).symbol; + if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) { + thisType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType; } - - // Wasn't found - error(node, Diagnostics.Property_0_does_not_exist_on_type_1, idText(node.tagName), "JSX." + JsxNames.IntrinsicElements); - return links.resolvedSymbol = unknownSymbol; } - else { - if (noImplicitAny) { - error(node, Diagnostics.JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists, unescapeLeadingUnderscores(JsxNames.IntrinsicElements)); - } - return links.resolvedSymbol = unknownSymbol; + else if (isJSConstructor(container)) { + thisType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType; } + thisType ||= getContextualThisParameterType(container); } - return links.resolvedSymbol; - } - function getJsxNamespaceContainerForImplicitImport(location: Node | undefined): Symbol | undefined { - const file = location && getSourceFileOfNode(location); - const links = file && getNodeLinks(file); - if (links && links.jsxImplicitImportContainer === false) { - return undefined; - } - if (links && links.jsxImplicitImportContainer) { - return links.jsxImplicitImportContainer; - } - const runtimeImportSpecifier = getJSXRuntimeImport(getJSXImplicitImportBase(compilerOptions, file), compilerOptions); - if (!runtimeImportSpecifier) { - return undefined; - } - const isClassic = getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic; - const errorMessage = isClassic - ? Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option - : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; - const mod = resolveExternalModule(location!, runtimeImportSpecifier, errorMessage, location!); - const result = mod && mod !== unknownSymbol ? getMergedSymbol(resolveSymbol(mod)) : undefined; - if (links) { - links.jsxImplicitImportContainer = result || false; + if (thisType) { + return getFlowTypeOfReference(node, thisType); } - return result; } - function getJsxNamespaceAt(location: Node | undefined): Symbol { - const links = location && getNodeLinks(location); - if (links && links.jsxNamespace) { - return links.jsxNamespace; - } - if (!links || links.jsxNamespace !== false) { - let resolvedNamespace = getJsxNamespaceContainerForImplicitImport(location); - - if (!resolvedNamespace || resolvedNamespace === unknownSymbol) { - const namespaceName = getJsxNamespace(location); - resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined, namespaceName, /*isUse*/ false); - } + if (isClassLike(container.parent)) { + const symbol = getSymbolOfNode(container.parent); + const type = isStatic(container) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; + return getFlowTypeOfReference(node, type); + } - if (resolvedNamespace) { - const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace)); - if (candidate && candidate !== unknownSymbol) { - if (links) { - links.jsxNamespace = candidate; - } - return candidate; - } - } - if (links) { - links.jsxNamespace = false; - } + if (isSourceFile(container)) { + // look up in the source file's locals or exports + if (container.commonJsModuleIndicator) { + const fileSymbol = getSymbolOfNode(container); + return fileSymbol && getTypeOfSymbol(fileSymbol); + } + else if (container.externalModuleIndicator) { + // TODO: Maybe issue a better error than 'object is possibly undefined' + return undefinedType; } - // JSX global fallback - const s = resolveSymbol(getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined)); - if (s === unknownSymbol) { - return undefined!; // TODO: GH#18217 + else if (includeGlobalThis) { + return getTypeOfSymbol(globalThisSymbol); } - return s!; // TODO: GH#18217 } + } - /** - * Look into JSX namespace and then look for container with matching name as nameOfAttribPropContainer. - * Get a single property from that container if existed. Report an error if there are more than one property. - * - * @param nameOfAttribPropContainer a string of value JsxNames.ElementAttributesPropertyNameContainer or JsxNames.ElementChildrenAttributeNameContainer - * if other string is given or the container doesn't exist, return undefined. - */ - function getNameFromJsxElementAttributesContainer(nameOfAttribPropContainer: __String, jsxNamespace: Symbol): __String | undefined { - // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [symbol] - const jsxElementAttribPropInterfaceSym = jsxNamespace && getSymbol(jsxNamespace.exports!, nameOfAttribPropContainer, SymbolFlags.Type); - // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [type] - const jsxElementAttribPropInterfaceType = jsxElementAttribPropInterfaceSym && getDeclaredTypeOfSymbol(jsxElementAttribPropInterfaceSym); - // The properties of JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute - const propertiesOfJsxElementAttribPropInterface = jsxElementAttribPropInterfaceType && getPropertiesOfType(jsxElementAttribPropInterfaceType); - if (propertiesOfJsxElementAttribPropInterface) { - // Element Attributes has zero properties, so the element attributes type will be the class instance type - if (propertiesOfJsxElementAttribPropInterface.length === 0) { - return "" as __String; - } - // Element Attributes has one property, so the element attributes type will be the type of the corresponding - // property of the class instance type - else if (propertiesOfJsxElementAttribPropInterface.length === 1) { - return propertiesOfJsxElementAttribPropInterface[0].escapedName; - } - else if (propertiesOfJsxElementAttribPropInterface.length > 1 && jsxElementAttribPropInterfaceSym.declarations) { - // More than one property on ElementAttributesProperty is an error - error(jsxElementAttribPropInterfaceSym.declarations[0], Diagnostics.The_global_type_JSX_0_may_not_have_more_than_one_property, unescapeLeadingUnderscores(nameOfAttribPropContainer)); - } + function getExplicitThisType(node: Expression) { + const container = getThisContainer(node, /*includeArrowFunctions*/ false); + if (isFunctionLike(container)) { + const signature = getSignatureFromDeclaration(container); + if (signature.thisParameter) { + return getExplicitTypeOfSymbol(signature.thisParameter); } - return undefined; } + if (isClassLike(container.parent)) { + const symbol = getSymbolOfNode(container.parent); + return isStatic(container) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; + } + } - function getJsxLibraryManagedAttributes(jsxNamespace: Symbol) { - // JSX.LibraryManagedAttributes [symbol] - return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.LibraryManagedAttributes, SymbolFlags.Type); + function getClassNameFromPrototypeMethod(container: Node) { + // Check if it's the RHS of a x.prototype.y = function [name]() { .... } + if (container.kind === SyntaxKind.FunctionExpression && + isBinaryExpression(container.parent) && + getAssignmentDeclarationKind(container.parent) === AssignmentDeclarationKind.PrototypeProperty) { + // Get the 'x' of 'x.prototype.y = container' + return ((container.parent // x.prototype.y = container + .left as PropertyAccessExpression) // x.prototype.y + .expression as PropertyAccessExpression) // x.prototype + .expression; // x + } + // x.prototype = { method() { } } + else if (container.kind === SyntaxKind.MethodDeclaration && + container.parent.kind === SyntaxKind.ObjectLiteralExpression && + isBinaryExpression(container.parent.parent) && + getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.Prototype) { + return (container.parent.parent.left as PropertyAccessExpression).expression; + } + // x.prototype = { method: function() { } } + else if (container.kind === SyntaxKind.FunctionExpression && + container.parent.kind === SyntaxKind.PropertyAssignment && + container.parent.parent.kind === SyntaxKind.ObjectLiteralExpression && + isBinaryExpression(container.parent.parent.parent) && + getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.Prototype) { + return (container.parent.parent.parent.left as PropertyAccessExpression).expression; + } + // Object.defineProperty(x, "method", { value: function() { } }); + // Object.defineProperty(x, "method", { set: (x: () => void) => void }); + // Object.defineProperty(x, "method", { get: () => function() { }) }); + else if (container.kind === SyntaxKind.FunctionExpression && + isPropertyAssignment(container.parent) && + isIdentifier(container.parent.name) && + (container.parent.name.escapedText === "value" || container.parent.name.escapedText === "get" || container.parent.name.escapedText === "set") && + isObjectLiteralExpression(container.parent.parent) && + isCallExpression(container.parent.parent.parent) && + container.parent.parent.parent.arguments[2] === container.parent.parent && + getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { + return (container.parent.parent.parent.arguments[0] as PropertyAccessExpression).expression; + } + // Object.defineProperty(x, "method", { value() { } }); + // Object.defineProperty(x, "method", { set(x: () => void) {} }); + // Object.defineProperty(x, "method", { get() { return () => {} } }); + else if (isMethodDeclaration(container) && + isIdentifier(container.name) && + (container.name.escapedText === "value" || container.name.escapedText === "get" || container.name.escapedText === "set") && + isObjectLiteralExpression(container.parent) && + isCallExpression(container.parent.parent) && + container.parent.parent.arguments[2] === container.parent && + getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { + return (container.parent.parent.arguments[0] as PropertyAccessExpression).expression; } + } - /// e.g. "props" for React.d.ts, - /// or 'undefined' if ElementAttributesProperty doesn't exist (which means all - /// non-intrinsic elements' attributes type is 'any'), - /// or '' if it has 0 properties (which means every - /// non-intrinsic elements' attributes type is the element instance type) - function getJsxElementPropertiesName(jsxNamespace: Symbol) { - return getNameFromJsxElementAttributesContainer(JsxNames.ElementAttributesPropertyNameContainer, jsxNamespace); + function getTypeForThisExpressionFromJSDoc(node: Node) { + const jsdocType = getJSDocType(node); + if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) { + const jsDocFunctionType = jsdocType as JSDocFunctionType; + if (jsDocFunctionType.parameters.length > 0 && + jsDocFunctionType.parameters[0].name && + (jsDocFunctionType.parameters[0].name as Identifier).escapedText === InternalSymbolName.This) { + return getTypeFromTypeNode(jsDocFunctionType.parameters[0].type!); + } + } + const thisTag = getJSDocThisTag(node); + if (thisTag && thisTag.typeExpression) { + return getTypeFromTypeNode(thisTag.typeExpression); } + } + + function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { + return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl); + } + + function checkSuperExpression(node: Node): Type { + const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent as CallExpression).expression === node; - function getJsxElementChildrenPropertyName(jsxNamespace: Symbol): __String | undefined { - return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace); + const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true); + let container = immediateContainer; + let needToCaptureLexicalThis = false; + + // adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting + if (!isCallExpression) { + while (container && container.kind === SyntaxKind.ArrowFunction) { + container = getSuperContainer(container, /*stopOnFunctions*/ true); + needToCaptureLexicalThis = languageVersion < ScriptTarget.ES2015; + } } - function getUninstantiatedJsxSignaturesOfType(elementType: Type, caller: JsxOpeningLikeElement): readonly Signature[] { - if (elementType.flags & TypeFlags.String) { - return [anySignature]; + const canUseSuperExpression = isLegalUsageOfSuperExpression(container); + let nodeCheckFlag: NodeCheckFlags = 0; + + if (!canUseSuperExpression) { + // issue more specific error if super is used in computed property name + // class A { foo() { return "1" }} + // class B { + // [super.foo()]() {} + // } + const current = findAncestor(node, n => n === container ? "quit" : n.kind === SyntaxKind.ComputedPropertyName); + if (current && current.kind === SyntaxKind.ComputedPropertyName) { + error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name); } - else if (elementType.flags & TypeFlags.StringLiteral) { - const intrinsicType = getIntrinsicAttributesTypeFromStringLiteralType(elementType as StringLiteralType, caller); - if (!intrinsicType) { - error(caller, Diagnostics.Property_0_does_not_exist_on_type_1, (elementType as StringLiteralType).value, "JSX." + JsxNames.IntrinsicElements); - return emptyArray; - } - else { - const fakeSignature = createSignatureForJSXIntrinsic(caller, intrinsicType); - return [fakeSignature]; - } + else if (isCallExpression) { + error(node, Diagnostics.Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors); } - const apparentElemType = getApparentType(elementType); - // Resolve the signatures, preferring constructor - let signatures = getSignaturesOfType(apparentElemType, SignatureKind.Construct); - if (signatures.length === 0) { - // No construct signatures, try call signatures - signatures = getSignaturesOfType(apparentElemType, SignatureKind.Call); + else if (!container || !container.parent || !(isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression)) { + error(node, Diagnostics.super_can_only_be_referenced_in_members_of_derived_classes_or_object_literal_expressions); } - if (signatures.length === 0 && apparentElemType.flags & TypeFlags.Union) { - // If each member has some combination of new/call signatures; make a union signature list for those - signatures = getUnionSignatures(map((apparentElemType as UnionType).types, t => getUninstantiatedJsxSignaturesOfType(t, caller))); + else { + error(node, Diagnostics.super_property_access_is_permitted_only_in_a_constructor_member_function_or_member_accessor_of_a_derived_class); } - return signatures; + return errorType; } - function getIntrinsicAttributesTypeFromStringLiteralType(type: StringLiteralType, location: Node): Type | undefined { - // If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type - // For example: - // var CustomTag: "h1" = "h1"; - // Hello World - const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, location); - if (!isErrorType(intrinsicElementsType)) { - const stringLiteralTypeName = type.value; - const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName)); - if (intrinsicProp) { - return getTypeOfSymbol(intrinsicProp); - } - const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, stringType); - if (indexSignatureType) { - return indexSignatureType; - } - return undefined; - } - // If we need to report an error, we already done so here. So just return any to prevent any more error downstream - return anyType; + if (!isCallExpression && immediateContainer.kind === SyntaxKind.Constructor) { + checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class); } - function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: JsxOpeningLikeElement) { - if (refKind === JsxReferenceKind.Function) { - const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); - if (sfcReturnConstraint) { - checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_return_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); - } + if (isStatic(container) || isCallExpression) { + nodeCheckFlag = NodeCheckFlags.SuperStatic; + if (!isCallExpression && + languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021 && + (isPropertyDeclaration(container) || isClassStaticBlockDeclaration(container))) { + // for `super.x` or `super[x]` in a static initializer, mark all enclosing + // block scope containers so that we can report potential collisions with + // `Reflect`. + forEachEnclosingBlockScopeContainer(node.parent, current => { + if (!isSourceFile(current) || isExternalOrCommonJsModule(current)) { + getNodeLinks(current).flags |= NodeCheckFlags.ContainsSuperPropertyInStaticInitializer; + } + }); } - else if (refKind === JsxReferenceKind.Component) { - const classConstraint = getJsxElementClassTypeAt(openingLikeElement); - if (classConstraint) { - // Issue an error if this return type isn't assignable to JSX.ElementClass, failing that - checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_instance_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); - } + } + else { + nodeCheckFlag = NodeCheckFlags.SuperInstance; + } + + getNodeLinks(node).flags |= nodeCheckFlag; + + // Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference. + // This is due to the fact that we emit the body of an async function inside of a generator function. As generator + // functions cannot reference `super`, we emit a helper inside of the method body, but outside of the generator. This helper + // uses an arrow function, which is permitted to reference `super`. + // + // There are two primary ways we can access `super` from within an async method. The first is getting the value of a property + // or indexed access on super, either as part of a right-hand-side expression or call expression. The second is when setting the value + // of a property or indexed access, either as part of an assignment expression or destructuring assignment. + // + // The simplest case is reading a value, in which case we will emit something like the following: + // + // // ts + // ... + // async asyncMethod() { + // let x = await super.asyncMethod(); + // return x; + // } + // ... + // + // // js + // ... + // asyncMethod() { + // const _super = Object.create(null, { + // asyncMethod: { get: () => super.asyncMethod }, + // }); + // return __awaiter(this, arguments, Promise, function *() { + // let x = yield _super.asyncMethod.call(this); + // return x; + // }); + // } + // ... + // + // The more complex case is when we wish to assign a value, especially as part of a destructuring assignment. As both cases + // are legal in ES6, but also likely less frequent, we only emit setters if there is an assignment: + // + // // ts + // ... + // async asyncMethod(ar: Promise) { + // [super.a, super.b] = await ar; + // } + // ... + // + // // js + // ... + // asyncMethod(ar) { + // const _super = Object.create(null, { + // a: { get: () => super.a, set: (v) => super.a = v }, + // b: { get: () => super.b, set: (v) => super.b = v } + // }; + // return __awaiter(this, arguments, Promise, function *() { + // [_super.a, _super.b] = yield ar; + // }); + // } + // ... + // + // Creating an object that has getter and setters instead of just an accessor function is required for destructuring assignments + // as a call expression cannot be used as the target of a destructuring assignment while a property access can. + // + // For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations. + if (container.kind === SyntaxKind.MethodDeclaration && hasSyntacticModifier(container, ModifierFlags.Async)) { + if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) { + getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding; } - else { // Mixed - const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); - const classConstraint = getJsxElementClassTypeAt(openingLikeElement); - if (!sfcReturnConstraint || !classConstraint) { - return; - } - const combined = getUnionType([sfcReturnConstraint, classConstraint]); - checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_element_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); + else { + getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuper; } + } - function generateInitialErrorChain(): DiagnosticMessageChain { - const componentName = getTextOfNode(openingLikeElement.tagName); - return chainDiagnosticMessages(/* details */ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName); - } + if (needToCaptureLexicalThis) { + // call expressions are allowed only in constructors so they should always capture correct 'this' + // super property access expressions can also appear in arrow functions - + // in this case they should also use correct lexical this + captureLexicalThis(node.parent, container); } - /** - * Get attributes type of the given intrinsic opening-like Jsx element by resolving the tag name. - * The function is intended to be called from a function which has checked that the opening element is an intrinsic element. - * @param node an intrinsic JSX opening-like element - */ - function getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type { - Debug.assert(isJsxIntrinsicIdentifier(node.tagName)); - const links = getNodeLinks(node); - if (!links.resolvedJsxElementAttributesType) { - const symbol = getIntrinsicTagSymbol(node); - if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) { - return links.resolvedJsxElementAttributesType = getTypeOfSymbol(symbol) || errorType; - } - else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) { - return links.resolvedJsxElementAttributesType = - getIndexTypeOfType(getJsxType(JsxNames.IntrinsicElements, node), stringType) || errorType; - } - else { - return links.resolvedJsxElementAttributesType = errorType; - } + if (container.parent.kind === SyntaxKind.ObjectLiteralExpression) { + if (languageVersion < ScriptTarget.ES2015) { + error(node, Diagnostics.super_is_only_allowed_in_members_of_object_literal_expressions_when_option_target_is_ES2015_or_higher); + return errorType; + } + else { + // for object literal assume that type of 'super' is 'any' + return anyType; } - return links.resolvedJsxElementAttributesType; } - function getJsxElementClassTypeAt(location: Node): Type | undefined { - const type = getJsxType(JsxNames.ElementClass, location); - if (isErrorType(type)) return undefined; - return type; + // at this point the only legal case for parent is ClassLikeDeclaration + const classLikeDeclaration = container.parent as ClassLikeDeclaration; + if (!getClassExtendsHeritageElement(classLikeDeclaration)) { + error(node, Diagnostics.super_can_only_be_referenced_in_a_derived_class); + return errorType; } - function getJsxElementTypeAt(location: Node): Type { - return getJsxType(JsxNames.Element, location); + const classType = getDeclaredTypeOfSymbol(getSymbolOfNode(classLikeDeclaration)) as InterfaceType; + const baseClassType = classType && getBaseTypes(classType)[0]; + if (!baseClassType) { + return errorType; } - function getJsxStatelessElementTypeAt(location: Node): Type | undefined { - const jsxElementType = getJsxElementTypeAt(location); - if (jsxElementType) { - return getUnionType([jsxElementType, nullType]); - } + if (container.kind === SyntaxKind.Constructor && isInConstructorArgumentInitializer(node, container)) { + // issue custom error message for super property access in constructor arguments (to be aligned with old compiler) + error(node, Diagnostics.super_cannot_be_referenced_in_constructor_arguments); + return errorType; } - /** - * Returns all the properties of the Jsx.IntrinsicElements interface - */ - function getJsxIntrinsicTagNamesAt(location: Node): Symbol[] { - const intrinsics = getJsxType(JsxNames.IntrinsicElements, location); - return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray; - } + return nodeCheckFlag === NodeCheckFlags.SuperStatic + ? getBaseConstructorTypeOfClass(classType) + : getTypeWithThisArgument(baseClassType, classType.thisType); - function checkJsxPreconditions(errorNode: Node) { - // Preconditions for using JSX - if ((compilerOptions.jsx || JsxEmit.None) === JsxEmit.None) { - error(errorNode, Diagnostics.Cannot_use_JSX_unless_the_jsx_flag_is_provided); + function isLegalUsageOfSuperExpression(container: Node): boolean { + if (!container) { + return false; } - if (getJsxElementTypeAt(errorNode) === undefined) { - if (noImplicitAny) { - error(errorNode, Diagnostics.JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist); - } + if (isCallExpression) { + // TS 1.0 SPEC (April 2014): 4.8.1 + // Super calls are only permitted in constructors of derived classes + return container.kind === SyntaxKind.Constructor; } - } - - function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) { - const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node); + else { + // TS 1.0 SPEC (April 2014) + // 'super' property access is allowed + // - In a constructor, instance member function, instance member accessor, or instance member variable initializer where this references a derived class instance + // - In a static member function or static member accessor - if (isNodeOpeningLikeElement) { - checkGrammarJsxElement(node as JsxOpeningLikeElement); + // topmost container must be something that is directly nested in the class declaration\object literal expression + if (isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression) { + if (isStatic(container)) { + return container.kind === SyntaxKind.MethodDeclaration || + container.kind === SyntaxKind.MethodSignature || + container.kind === SyntaxKind.GetAccessor || + container.kind === SyntaxKind.SetAccessor || + container.kind === SyntaxKind.PropertyDeclaration || + container.kind === SyntaxKind.ClassStaticBlockDeclaration; + } + else { + return container.kind === SyntaxKind.MethodDeclaration || + container.kind === SyntaxKind.MethodSignature || + container.kind === SyntaxKind.GetAccessor || + container.kind === SyntaxKind.SetAccessor || + container.kind === SyntaxKind.PropertyDeclaration || + container.kind === SyntaxKind.PropertySignature || + container.kind === SyntaxKind.Constructor; + } + } } - checkJsxPreconditions(node); + return false; + } + } - if (!getJsxNamespaceContainerForImplicitImport(node)) { - // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. - // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. - const jsxFactoryRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; - const jsxFactoryNamespace = getJsxNamespace(node); - const jsxFactoryLocation = isNodeOpeningLikeElement ? (node as JsxOpeningLikeElement).tagName : node; + function getContainingObjectLiteral(func: SignatureDeclaration): ObjectLiteralExpression | undefined { + return (func.kind === SyntaxKind.MethodDeclaration || + func.kind === SyntaxKind.GetAccessor || + func.kind === SyntaxKind.SetAccessor) && func.parent.kind === SyntaxKind.ObjectLiteralExpression ? func.parent : + func.kind === SyntaxKind.FunctionExpression && func.parent.kind === SyntaxKind.PropertyAssignment ? func.parent.parent as ObjectLiteralExpression : + undefined; + } - // allow null as jsxFragmentFactory - let jsxFactorySym: Symbol | undefined; - if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { - jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, jsxFactoryNamespace, /*isUse*/ true); - } + function getThisTypeArgument(type: Type): Type | undefined { + return getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target === globalThisType ? getTypeArguments(type as TypeReference)[0] : undefined; + } - if (jsxFactorySym) { - // Mark local symbol as referenced here because it might not have been marked - // if jsx emit was not jsxFactory as there wont be error being emitted - jsxFactorySym.isReferenced = SymbolFlags.All; + function getThisTypeFromContextualType(type: Type): Type | undefined { + return mapType(type, t => { + return t.flags & TypeFlags.Intersection ? forEach((t as IntersectionType).types, getThisTypeArgument) : getThisTypeArgument(t); + }); + } - // If react/jsxFactory symbol is alias, mark it as refereced - if (jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { - markAliasSymbolAsReferenced(jsxFactorySym); + function getContextualThisParameterType(func: SignatureDeclaration): Type | undefined { + if (func.kind === SyntaxKind.ArrowFunction) { + return undefined; + } + if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { + const contextualSignature = getContextualSignature(func); + if (contextualSignature) { + const thisParameter = contextualSignature.thisParameter; + if (thisParameter) { + return getTypeOfSymbol(thisParameter); + } + } + } + const inJs = isInJSFile(func); + if (noImplicitThis || inJs) { + const containingLiteral = getContainingObjectLiteral(func); + if (containingLiteral) { + // We have an object literal method. Check if the containing object literal has a contextual type + // that includes a ThisType. If so, T is the contextual type for 'this'. We continue looking in + // any directly enclosing object literals. + const contextualType = getApparentTypeOfContextualType(containingLiteral); + let literal = containingLiteral; + let type = contextualType; + while (type) { + const thisType = getThisTypeFromContextualType(type); + if (thisType) { + return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral))); + } + if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { + break; } - } - - // For JsxFragment, mark jsx pragma as referenced via resolveName - if (isJsxOpeningFragment(node)) { - const file = getSourceFileOfNode(node); - const localJsxNamespace = getLocalJsxNamespace(file); - if (localJsxNamespace) { - resolveName(jsxFactoryLocation, localJsxNamespace, SymbolFlags.Value, jsxFactoryRefErr, localJsxNamespace, /*isUse*/ true); + literal = literal.parent.parent as ObjectLiteralExpression; + type = getApparentTypeOfContextualType(literal); + } + // There was no contextual ThisType for the containing object literal, so the contextual type + // for 'this' is the non-null form of the contextual type for the containing object literal or + // the type of the object literal itself. + return getWidenedType(contextualType ? getNonNullableType(contextualType) : checkExpressionCached(containingLiteral)); + } + // In an assignment of the form 'obj.xxx = function(...)' or 'obj[xxx] = function(...)', the + // contextual type for 'this' is 'obj'. + const parent = walkUpParenthesizedExpressions(func.parent); + if (parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + const target = (parent as BinaryExpression).left; + if (isAccessExpression(target)) { + const { expression } = target; + // Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }` + if (inJs && isIdentifier(expression)) { + const sourceFile = getSourceFileOfNode(parent); + if (sourceFile.commonJsModuleIndicator && getResolvedSymbol(expression) === sourceFile.symbol) { + return undefined; + } } + + return getWidenedType(checkExpressionCached(expression)); } } + } + return undefined; + } - if (isNodeOpeningLikeElement) { - const jsxOpeningLikeNode = node as JsxOpeningLikeElement; - const sig = getResolvedSignature(jsxOpeningLikeNode); - checkDeprecatedSignature(sig, node as JsxOpeningLikeElement); - checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode); + // Return contextual type of parameter or undefined if no contextual type is available + function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined { + const func = parameter.parent; + if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) { + return undefined; + } + const iife = getImmediatelyInvokedFunctionExpression(func); + if (iife && iife.arguments) { + const args = getEffectiveCallArguments(iife); + const indexOfParameter = func.parameters.indexOf(parameter); + if (parameter.dotDotDotToken) { + return getSpreadArgumentType(args, indexOfParameter, args.length, anyType, /*context*/ undefined, CheckMode.Normal); } + const links = getNodeLinks(iife); + const cached = links.resolvedSignature; + links.resolvedSignature = anySignature; + const type = indexOfParameter < args.length ? + getWidenedLiteralType(checkExpression(args[indexOfParameter])) : + parameter.initializer ? undefined : undefinedWideningType; + links.resolvedSignature = cached; + return type; } + const contextualSignature = getContextualSignature(func); + if (contextualSignature) { + const index = func.parameters.indexOf(parameter) - (getThisParameter(func) ? 1 : 0); + return parameter.dotDotDotToken && lastOrUndefined(func.parameters) === parameter ? + getRestTypeAtPosition(contextualSignature, index) : + tryGetTypeAtPosition(contextualSignature, index); + } + } - /** - * Check if a property with the given name is known anywhere in the given type. In an object type, a property - * is considered known if - * 1. the object type is empty and the check is for assignability, or - * 2. if the object type has index signatures, or - * 3. if the property is actually declared in the object type - * (this means that 'toString', for example, is not usually a known property). - * 4. In a union or intersection type, - * a property is considered known if it is known in any constituent type. - * @param targetType a type to search a given name in - * @param name a property name to search - * @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType - */ - function isKnownProperty(targetType: Type, name: __String, isComparingJsxAttributes: boolean): boolean { - if (targetType.flags & TypeFlags.Object) { - // For backwards compatibility a symbol-named property is satisfied by a string index signature. This - // is incorrect and inconsistent with element access expressions, where it is an error, so eventually - // we should remove this exception. - if (getPropertyOfObjectType(targetType, name) || - getApplicableIndexInfoForName(targetType, name) || - isLateBoundName(name) && getIndexInfoOfType(targetType, stringType) || - isComparingJsxAttributes && isHyphenatedJsxName(name)) { - // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known. - return true; + function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration): Type | undefined { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); + } + switch (declaration.kind) { + case SyntaxKind.Parameter: + return getContextuallyTypedParameterType(declaration); + case SyntaxKind.BindingElement: + return getContextualTypeForBindingElement(declaration); + case SyntaxKind.PropertyDeclaration: + if (isStatic(declaration)) { + return getContextualTypeForStaticPropertyDeclaration(declaration); } + // By default, do nothing and return undefined - only the above cases have context implied by a parent + } + } + + function getContextualTypeForBindingElement(declaration: BindingElement): Type | undefined { + const parent = declaration.parent.parent; + const name = declaration.propertyName || declaration.name; + const parentType = getContextualTypeForVariableLikeDeclaration(parent) || + parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent); + if (!parentType || isBindingPattern(name) || isComputedNonLiteralName(name)) return undefined; + if (parent.name.kind === SyntaxKind.ArrayBindingPattern) { + const index = indexOfNode(declaration.parent.elements, declaration); + if (index < 0) return undefined; + return getContextualTypeForElementExpression(parentType, index); + } + const nameType = getLiteralTypeFromPropertyName(name); + if (isTypeUsableAsPropertyName(nameType)) { + const text = getPropertyNameFromType(nameType); + return getTypeOfPropertyOfType(parentType, text); + } + } + + function getContextualTypeForStaticPropertyDeclaration(declaration: PropertyDeclaration): Type | undefined { + const parentType = isExpression(declaration.parent) && getContextualType(declaration.parent); + if (!parentType) return undefined; + return getTypeOfPropertyOfContextualType(parentType, getSymbolOfNode(declaration).escapedName); + } + + // In a variable, parameter or property declaration with a type annotation, + // the contextual type of an initializer expression is the type of the variable, parameter or property. + // Otherwise, in a parameter declaration of a contextually typed function expression, + // the contextual type of an initializer expression is the contextual type of the parameter. + // Otherwise, in a variable or parameter declaration with a binding pattern name, + // the contextual type of an initializer expression is the type implied by the binding pattern. + // Otherwise, in a binding pattern inside a variable or parameter declaration, + // the contextual type of an initializer expression is the type annotation of the containing declaration, if present. + function getContextualTypeForInitializerExpression(node: Expression, contextFlags?: ContextFlags): Type | undefined { + const declaration = node.parent as VariableLikeDeclaration; + if (hasInitializer(declaration) && node === declaration.initializer) { + const result = getContextualTypeForVariableLikeDeclaration(declaration); + if (result) { + return result; } - else if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) { - for (const t of (targetType as UnionOrIntersectionType).types) { - if (isKnownProperty(t, name, isComparingJsxAttributes)) { - return true; + if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable + return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false); + } + } + return undefined; + } + + function getContextualTypeForReturnExpression(node: Expression): Type | undefined { + const func = getContainingFunction(node); + if (func) { + let contextualReturnType = getContextualReturnType(func); + if (contextualReturnType) { + const functionFlags = getFunctionFlags(func); + if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function + const use = functionFlags & FunctionFlags.Async ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType; + const iterationTypes = getIterationTypesOfIterable(contextualReturnType, use, /*errorNode*/ undefined); + if (!iterationTypes) { + return undefined; } + contextualReturnType = iterationTypes.returnType; + // falls through to unwrap Promise for AsyncGenerators + } + + if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function + // Get the awaited type without the `Awaited` alias + const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeNoAlias); + return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } + + return contextualReturnType; // Regular function or Generator function } - return false; } + return undefined; + } - function isExcessPropertyCheckTarget(type: Type): boolean { - return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) || - type.flags & TypeFlags.NonPrimitive || - type.flags & TypeFlags.Union && some((type as UnionType).types, isExcessPropertyCheckTarget) || - type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isExcessPropertyCheckTarget)); + function getContextualTypeForAwaitOperand(node: AwaitExpression, contextFlags?: ContextFlags): Type | undefined { + const contextualType = getContextualType(node, contextFlags); + if (contextualType) { + const contextualAwaitedType = getAwaitedTypeNoAlias(contextualType); + return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } + return undefined; + } - function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) { - checkGrammarJsxExpression(node); - if (node.expression) { - const type = checkExpression(node.expression, checkMode); - if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) { - error(node, Diagnostics.JSX_spread_child_must_be_an_array_type); - } - return type; - } - else { - return errorType; + function getContextualTypeForYieldOperand(node: YieldExpression): Type | undefined { + const func = getContainingFunction(node); + if (func) { + const functionFlags = getFunctionFlags(func); + const contextualReturnType = getContextualReturnType(func); + if (contextualReturnType) { + return node.asteriskToken + ? contextualReturnType + : getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, (functionFlags & FunctionFlags.Async) !== 0); } } - function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { - return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0; - } + return undefined; + } - /** - * Return whether this symbol is a member of a prototype somewhere - * Note that this is not tracked well within the compiler, so the answer may be incorrect. - */ - function isPrototypeProperty(symbol: Symbol) { - if (symbol.flags & SymbolFlags.Method || getCheckFlags(symbol) & CheckFlags.SyntheticMethod) { + function isInParameterInitializerBeforeContainingFunction(node: Node) { + let inBindingInitializer = false; + while (node.parent && !isFunctionLike(node.parent)) { + if (isParameter(node.parent) && (inBindingInitializer || node.parent.initializer === node)) { return true; } - if (isInJSFile(symbol.valueDeclaration)) { - const parent = symbol.valueDeclaration!.parent; - return parent && isBinaryExpression(parent) && - getAssignmentDeclarationKind(parent) === AssignmentDeclarationKind.PrototypeProperty; + if (isBindingElement(node.parent) && node.parent.initializer === node) { + inBindingInitializer = true; } + + node = node.parent; } - /** - * Check whether the requested property access is valid. - * Returns true if node is a valid property access, and false otherwise. - * @param node The node to be checked. - * @param isSuper True if the access is from `super.`. - * @param type The type of the object whose property is being accessed. (Not the type of the property.) - * @param prop The symbol for the property being accessed. - */ - function checkPropertyAccessibility( - node: PropertyAccessExpression | QualifiedName | PropertyAccessExpression | VariableDeclaration | ParameterDeclaration | ImportTypeNode | PropertyAssignment | ShorthandPropertyAssignment | BindingElement, - isSuper: boolean, writing: boolean, type: Type, prop: Symbol, reportError = true): boolean { + return false; + } + + function getContextualIterationType(kind: IterationTypeKind, functionDecl: SignatureDeclaration): Type | undefined { + const isAsync = !!(getFunctionFlags(functionDecl) & FunctionFlags.Async); + const contextualReturnType = getContextualReturnType(functionDecl); + if (contextualReturnType) { + return getIterationTypeOfGeneratorFunctionReturnType(kind, contextualReturnType, isAsync) + || undefined; + } - const errorNode = !reportError ? undefined : - node.kind === SyntaxKind.QualifiedName ? node.right : - node.kind === SyntaxKind.ImportType ? node : - node.kind === SyntaxKind.BindingElement && node.propertyName ? node.propertyName : node.name; + return undefined; + } - return checkPropertyAccessibilityAtLocation(node, isSuper, writing, type, prop, errorNode); + function getContextualReturnType(functionDecl: SignatureDeclaration): Type | undefined { + // If the containing function has a return type annotation, is a constructor, or is a get accessor whose + // corresponding set accessor has a type annotation, return statements in the function are contextually typed + const returnType = getReturnTypeFromAnnotation(functionDecl); + if (returnType) { + return returnType; + } + // Otherwise, if the containing function is contextually typed by a function type with exactly one call signature + // and that call signature is non-generic, return statements are contextually typed by the return type of the signature + const signature = getContextualSignatureForFunctionLikeDeclaration(functionDecl as FunctionExpression); + if (signature && !isResolvingReturnTypeOfSignature(signature)) { + return getReturnTypeOfSignature(signature); } + const iife = getImmediatelyInvokedFunctionExpression(functionDecl); + if (iife) { + return getContextualType(iife); + } + return undefined; + } - /** - * Check whether the requested property can be accessed at the requested location. - * Returns true if node is a valid property access, and false otherwise. - * @param location The location node where we want to check if the property is accessible. - * @param isSuper True if the access is from `super.`. - * @param writing True if this is a write property access, false if it is a read property access. - * @param containingType The type of the object whose property is being accessed. (Not the type of the property.) - * @param prop The symbol for the property being accessed. - * @param errorNode The node where we should report an invalid property access error, or undefined if we should not report errors. - */ - function checkPropertyAccessibilityAtLocation(location: Node, - isSuper: boolean, writing: boolean, - containingType: Type, prop: Symbol, errorNode?: Node): boolean { + // In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter. + function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type | undefined { + const args = getEffectiveCallArguments(callTarget); + const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression + return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex); + } - const flags = getDeclarationModifierFlagsFromSymbol(prop, writing); + function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number): Type { + if (isImportCall(callTarget)) { + return argIndex === 0 ? stringType : + argIndex === 1 ? getGlobalImportCallOptionsType(/*reportErrors*/ false) : + anyType; + } - if (isSuper) { - // TS 1.0 spec (April 2014): 4.8.2 - // - In a constructor, instance member function, instance member accessor, or - // instance member variable initializer where this references a derived class instance, - // a super property access is permitted and must specify a public instance member function of the base class. - // - In a static member function or static member accessor - // where this references the constructor function object of a derived class, - // a super property access is permitted and must specify a public static member function of the base class. - if (languageVersion < ScriptTarget.ES2015) { - if (symbolHasNonMethodDeclaration(prop)) { - if (errorNode) { - error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword); - } - return false; - } + // If we're already in the process of resolving the given signature, don't resolve again as + // that could cause infinite recursion. Instead, return anySignature. + const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget); + + if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { + return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); + } + const restIndex = signature.parameters.length - 1; + return signatureHasRestParameter(signature) && argIndex >= restIndex ? + getIndexedAccessType(getTypeOfSymbol(signature.parameters[restIndex]), getNumberLiteralType(argIndex - restIndex), AccessFlags.Contextual) : + getTypeAtPosition(signature, argIndex); + } + + function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) { + if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) { + return getContextualTypeForArgument(template.parent as TaggedTemplateExpression, substitutionExpression); + } + + return undefined; + } + + function getContextualTypeForBinaryOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined { + const binaryExpression = node.parent as BinaryExpression; + const { left, operatorToken, right } = binaryExpression; + switch (operatorToken.kind) { + case SyntaxKind.EqualsToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: + case SyntaxKind.BarBarEqualsToken: + case SyntaxKind.QuestionQuestionEqualsToken: + return node === right ? getContextualTypeForAssignmentDeclaration(binaryExpression) : undefined; + case SyntaxKind.BarBarToken: + case SyntaxKind.QuestionQuestionToken: + // When an || expression has a contextual type, the operands are contextually typed by that type, except + // when that type originates in a binding pattern, the right operand is contextually typed by the type of + // the left operand. When an || expression has no contextual type, the right operand is contextually typed + // by the type of the left operand, except for the special case of Javascript declarations of the form + // `namespace.prop = namespace.prop || {}`. + const type = getContextualType(binaryExpression, contextFlags); + return node === right && (type && type.pattern || !type && !isDefaultedExpandoInitializer(binaryExpression)) ? + getTypeOfExpression(left) : type; + case SyntaxKind.AmpersandAmpersandToken: + case SyntaxKind.CommaToken: + return node === right ? getContextualType(binaryExpression, contextFlags) : undefined; + default: + return undefined; + } + } + + /** + * Try to find a resolved symbol for an expression without also resolving its type, as + * getSymbolAtLocation would (as that could be reentrant into contextual typing) + */ + function getSymbolForExpression(e: Expression) { + if (e.symbol) { + return e.symbol; + } + if (isIdentifier(e)) { + return getResolvedSymbol(e); + } + if (isPropertyAccessExpression(e)) { + const lhsType = getTypeOfExpression(e.expression); + return isPrivateIdentifier(e.name) ? tryGetPrivateIdentifierPropertyOfType(lhsType, e.name) : getPropertyOfType(lhsType, e.name.escapedText); + } + return undefined; + + function tryGetPrivateIdentifierPropertyOfType(type: Type, id: PrivateIdentifier) { + const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(id.escapedText, id); + return lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(type, lexicallyScopedSymbol); + } + } + + // In an assignment expression, the right operand is contextually typed by the type of the left operand. + // Don't do this for assignment declarations unless there is a type tag on the assignment, to avoid circularity from checking the right operand. + function getContextualTypeForAssignmentDeclaration(binaryExpression: BinaryExpression): Type | undefined { + const kind = getAssignmentDeclarationKind(binaryExpression); + switch (kind) { + case AssignmentDeclarationKind.None: + case AssignmentDeclarationKind.ThisProperty: + const lhsSymbol = getSymbolForExpression(binaryExpression.left); + const decl = lhsSymbol && lhsSymbol.valueDeclaration; + // Unannotated, uninitialized property declarations have a type implied by their usage in the constructor. + // We avoid calling back into `getTypeOfExpression` and reentering contextual typing to avoid a bogus circularity error in that case. + if (decl && (isPropertyDeclaration(decl) || isPropertySignature(decl))) { + const overallAnnotation = getEffectiveTypeAnnotationNode(decl); + return (overallAnnotation && instantiateType(getTypeFromTypeNode(overallAnnotation), getSymbolLinks(lhsSymbol).mapper)) || + (decl.initializer && getTypeOfExpression(binaryExpression.left)); + } + if (kind === AssignmentDeclarationKind.None) { + return getTypeOfExpression(binaryExpression.left); + } + return getContextualTypeForThisPropertyAssignment(binaryExpression); + case AssignmentDeclarationKind.Property: + if (isPossiblyAliasedThisProperty(binaryExpression, kind)) { + return getContextualTypeForThisPropertyAssignment(binaryExpression); } - if (flags & ModifierFlags.Abstract) { - // A method cannot be accessed in a super property access if the method is abstract. - // This error could mask a private property access error. But, a member - // cannot simultaneously be private and abstract, so this will trigger an - // additional error elsewhere. - if (errorNode) { - error(errorNode, - Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, - symbolToString(prop), - typeToString(getDeclaringClass(prop)!)); - } - return false; + // If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration. + // See `bindStaticPropertyAssignment` in `binder.ts`. + else if (!binaryExpression.left.symbol) { + return getTypeOfExpression(binaryExpression.left); } - } - - // Referencing abstract properties within their own constructors is not allowed - if ((flags & ModifierFlags.Abstract) && symbolHasNonMethodDeclaration(prop) && - (isThisProperty(location) || isThisInitializedObjectBindingExpression(location) || isObjectBindingPattern(location.parent) && isThisInitializedDeclaration(location.parent.parent))) { - const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!); - if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(location)) { - if (errorNode) { - error(errorNode, - Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, - symbolToString(prop), - getTextOfIdentifierOrLiteral(declaringClassDeclaration.name!)); + else { + const decl = binaryExpression.left.symbol.valueDeclaration; + if (!decl) { + return undefined; } - return false; + const lhs = cast(binaryExpression.left, isAccessExpression); + const overallAnnotation = getEffectiveTypeAnnotationNode(decl); + if (overallAnnotation) { + return getTypeFromTypeNode(overallAnnotation); + } + else if (isIdentifier(lhs.expression)) { + const id = lhs.expression; + const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true); + if (parentSymbol) { + const annotated = parentSymbol.valueDeclaration && getEffectiveTypeAnnotationNode(parentSymbol.valueDeclaration); + if (annotated) { + const nameStr = getElementOrPropertyAccessName(lhs); + if (nameStr !== undefined) { + return getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr); + } + } + return undefined; + } + } + return isInJSFile(decl) ? undefined : getTypeOfExpression(binaryExpression.left); + } + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.Prototype: + case AssignmentDeclarationKind.PrototypeProperty: + let valueDeclaration = binaryExpression.left.symbol?.valueDeclaration; + // falls through + case AssignmentDeclarationKind.ModuleExports: + valueDeclaration ||= binaryExpression.symbol?.valueDeclaration; + const annotated = valueDeclaration && getEffectiveTypeAnnotationNode(valueDeclaration); + return annotated ? getTypeFromTypeNode(annotated) : undefined; + case AssignmentDeclarationKind.ObjectDefinePropertyValue: + case AssignmentDeclarationKind.ObjectDefinePropertyExports: + case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: + return Debug.fail("Does not apply"); + default: + return Debug.assertNever(kind); + } + } + + function isPossiblyAliasedThisProperty(declaration: BinaryExpression, kind = getAssignmentDeclarationKind(declaration)) { + if (kind === AssignmentDeclarationKind.ThisProperty) { + return true; + } + if (!isInJSFile(declaration) || kind !== AssignmentDeclarationKind.Property || !isIdentifier((declaration.left as AccessExpression).expression)) { + return false; + } + const name = ((declaration.left as AccessExpression).expression as Identifier).escapedText; + const symbol = resolveName(declaration.left, name, SymbolFlags.Value, undefined, undefined, /*isUse*/ true, /*excludeGlobals*/ true); + return isThisInitializedDeclaration(symbol?.valueDeclaration); + } + + function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression): Type | undefined { + if (!binaryExpression.symbol) return getTypeOfExpression(binaryExpression.left); + if (binaryExpression.symbol.valueDeclaration) { + const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration); + if (annotated) { + const type = getTypeFromTypeNode(annotated); + if (type) { + return type; } } + } + const thisAccess = cast(binaryExpression.left, isAccessExpression); + if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) { + return undefined; + } + const thisType = checkThisExpression(thisAccess.expression); + const nameStr = getElementOrPropertyAccessName(thisAccess); + return nameStr !== undefined && getTypeOfPropertyOfContextualType(thisType, nameStr) || undefined; - // Public properties are otherwise accessible. - if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) { - return true; - } + } - // Property is known to be private or protected at this point + function isCircularMappedProperty(symbol: Symbol) { + return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0); + } - // Private property is accessible if the property is within the declaring class - if (flags & ModifierFlags.Private) { - const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!)!; - if (!isNodeWithinClass(location, declaringClassDeclaration)) { - if (errorNode) { - error(errorNode, - Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, - symbolToString(prop), - typeToString(getDeclaringClass(prop)!)); + function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) { + return mapType(type, t => { + if (isGenericMappedType(t)) { + const constraint = getConstraintTypeFromMappedType(t); + const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint; + const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name)); + if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) { + return substituteIndexedMappedType(t, propertyNameType); + } + } + else if (t.flags & TypeFlags.StructuredType) { + const prop = getPropertyOfType(t, name); + if (prop) { + return isCircularMappedProperty(prop) ? undefined : getTypeOfSymbol(prop); + } + if (isTupleType(t)) { + const restType = getRestTypeOfTupleType(t); + if (restType && isNumericLiteralName(name) && +name >= 0) { + return restType; } - return false; } - return true; + return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type; } + return undefined; + }, /*noReductions*/ true); + } - // Property is known to be protected at this point + // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of + // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one + // exists. Otherwise, it is the type of the string index signature in T, if one exists. + function getContextualTypeForObjectLiteralMethod(node: MethodDeclaration, contextFlags?: ContextFlags): Type | undefined { + Debug.assert(isObjectLiteralMethod(node)); + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return undefined; + } + return getContextualTypeForObjectLiteralElement(node, contextFlags); + } - // All protected properties of a supertype are accessible in a super access - if (isSuper) { - return true; + function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike, contextFlags?: ContextFlags) { + const objectLiteral = element.parent as ObjectLiteralExpression; + const propertyAssignmentType = isPropertyAssignment(element) && getContextualTypeForVariableLikeDeclaration(element); + if (propertyAssignmentType) { + return propertyAssignmentType; + } + const type = getApparentTypeOfContextualType(objectLiteral, contextFlags); + if (type) { + if (hasBindableName(element)) { + // For a (non-symbol) computed property, there is no reason to look up the name + // in the type. It will just be "__computed", which does not appear in any + // SymbolTable. + const symbol = getSymbolOfNode(element); + return getTypeOfPropertyOfContextualType(type, symbol.escapedName, getSymbolLinks(symbol).nameType); + } + if (element.name) { + const nameType = getLiteralTypeFromPropertyName(element.name); + // We avoid calling getApplicableIndexInfo here because it performs potentially expensive intersection reduction. + return mapType(type, t => findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType)?.type, /*noReductions*/ true); } + } + return undefined; + } - // Find the first enclosing class that has the declaring classes of the protected constituents - // of the property as base classes - let enclosingClass = forEachEnclosingClass(location, enclosingDeclaration => { - const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!) as InterfaceType; - return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing) ? enclosingClass : undefined; - }); - // A protected property is accessible if the property is within the declaring class or classes derived from it - if (!enclosingClass) { - // allow PropertyAccessibility if context is in function with this parameter - // static member access is disallow - let thisParameter: ParameterDeclaration | undefined; - if (flags & ModifierFlags.Static || !(thisParameter = getThisParameterFromNodeContext(location)) || !thisParameter.type) { - if (errorNode) { - error(errorNode, - Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, - symbolToString(prop), - typeToString(getDeclaringClass(prop) || containingType)); - } - return false; - } + // In an array literal contextually typed by a type T, the contextual type of an element expression at index N is + // the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature, + // it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated + // type of T. + function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined { + return arrayContextualType && ( + getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String) + || mapType( + arrayContextualType, + t => getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false), + /*noReductions*/ true)); + } - const thisType = getTypeFromTypeNode(thisParameter.type); - enclosingClass = (((thisType.flags & TypeFlags.TypeParameter) ? getConstraintOfTypeParameter(thisType as TypeParameter) : thisType) as TypeReference).target; - } - // No further restrictions for static properties - if (flags & ModifierFlags.Static) { - return true; - } - if (containingType.flags & TypeFlags.TypeParameter) { - // get the original type -- represented as the type constraint of the 'this' type - containingType = (containingType as TypeParameter).isThisType ? getConstraintOfTypeParameter(containingType as TypeParameter)! : getBaseConstraintOfType(containingType as TypeParameter)!; // TODO: GH#18217 Use a different variable that's allowed to be undefined + // In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type. + function getContextualTypeForConditionalOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined { + const conditional = node.parent as ConditionalExpression; + return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional, contextFlags) : undefined; + } + + function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild) { + const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName); + // JSX expression is in children of JSX Element, we will look for an "children" attribute (we get the name from JSX.ElementAttributesProperty) + const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); + if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) { + return undefined; + } + const realChildren = getSemanticJsxChildren(node.children); + const childIndex = realChildren.indexOf(child); + const childFieldType = getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName); + return childFieldType && (realChildren.length === 1 ? childFieldType : mapType(childFieldType, t => { + if (isArrayLikeType(t)) { + return getIndexedAccessType(t, getNumberLiteralType(childIndex)); } - if (!containingType || !hasBaseType(containingType, enclosingClass)) { - if (errorNode) { - error(errorNode, - Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1_This_is_an_instance_of_class_2, - symbolToString(prop), typeToString(enclosingClass), typeToString(containingType)); - } - return false; + else { + return t; } - return true; - } + }, /*noReductions*/ true)); + } - function getThisParameterFromNodeContext(node: Node) { - const thisContainer = getThisContainer(node, /* includeArrowFunctions */ false); - return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined; - } + function getContextualTypeForJsxExpression(node: JsxExpression): Type | undefined { + const exprParent = node.parent; + return isJsxAttributeLike(exprParent) + ? getContextualType(node) + : isJsxElement(exprParent) + ? getContextualTypeForChildJsxExpression(exprParent, node) + : undefined; + } - function symbolHasNonMethodDeclaration(symbol: Symbol) { - return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method)); + function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined { + // When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type + // which is a type of the parameter of the signature we are trying out. + // If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName + if (isJsxAttribute(attribute)) { + const attributesType = getApparentTypeOfContextualType(attribute.parent); + if (!attributesType || isTypeAny(attributesType)) { + return undefined; + } + return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText); } - - function checkNonNullExpression(node: Expression | QualifiedName) { - return checkNonNullType(checkExpression(node), node); + else { + return getContextualType(attribute.parent); } + } - function isNullableType(type: Type) { - return !!((strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable); + // Return true if the given expression is possibly a discriminant value. We limit the kinds of + // expressions we check to those that don't depend on their contextual type in order not to cause + // recursive (and possibly infinite) invocations of getContextualType. + function isPossiblyDiscriminantValue(node: Expression): boolean { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.Identifier: + case SyntaxKind.UndefinedKeyword: + return true; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ParenthesizedExpression: + return isPossiblyDiscriminantValue((node as PropertyAccessExpression | ParenthesizedExpression).expression); + case SyntaxKind.JsxExpression: + return !(node as JsxExpression).expression || isPossiblyDiscriminantValue((node as JsxExpression).expression!); } + return false; + } - function getNonNullableTypeIfNeeded(type: Type) { - return isNullableType(type) ? getNonNullableType(type) : type; - } + function discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) { + return getMatchingUnionConstituentForObjectLiteral(contextualType, node) || discriminateTypeByDiscriminableItems(contextualType, + concatenate( + map( + filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)), + prop => ([() => getContextFreeTypeOfExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String]) + ), + map( + filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)), + s => [() => undefinedType, s.escapedName] as [() => Type, __String] + ) + ), + isTypeAssignableTo, + contextualType + ); + } - function reportObjectPossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) { - error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ? - Diagnostics.Object_is_possibly_null_or_undefined : - Diagnostics.Object_is_possibly_undefined : - Diagnostics.Object_is_possibly_null - ); - } + function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) { + return discriminateTypeByDiscriminableItems(contextualType, + concatenate( + map( + filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.JsxAttribute && isDiscriminantProperty(contextualType, p.symbol.escapedName) && (!p.initializer || isPossiblyDiscriminantValue(p.initializer))), + prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => getContextFreeTypeOfExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String]) + ), + map( + filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)), + s => [() => undefinedType, s.escapedName] as [() => Type, __String] + ) + ), + isTypeAssignableTo, + contextualType + ); + } - function reportCannotInvokePossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) { - error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ? - Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined : - Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined : - Diagnostics.Cannot_invoke_an_object_which_is_possibly_null - ); + // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily + // be "pushed" onto a node using the contextualType property. + function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags?: ContextFlags): Type | undefined { + const contextualType = isObjectLiteralMethod(node) ? + getContextualTypeForObjectLiteralMethod(node, contextFlags) : + getContextualType(node, contextFlags); + const instantiatedType = instantiateContextualType(contextualType, node, contextFlags); + if (instantiatedType && !(contextFlags && contextFlags & ContextFlags.NoConstraints && instantiatedType.flags & TypeFlags.TypeVariable)) { + const apparentType = mapType(instantiatedType, getApparentType, /*noReductions*/ true); + return apparentType.flags & TypeFlags.Union && isObjectLiteralExpression(node) ? discriminateContextualTypeByObjectMembers(node, apparentType as UnionType) : + apparentType.flags & TypeFlags.Union && isJsxAttributes(node) ? discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType) : + apparentType; } + } - function checkNonNullTypeWithReporter( - type: Type, - node: Node, - reportError: (node: Node, kind: TypeFlags) => void - ): Type { - if (strictNullChecks && type.flags & TypeFlags.Unknown) { - error(node, Diagnostics.Object_is_of_type_unknown); - return errorType; - } - const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable; - if (kind) { - reportError(node, kind); - const t = getNonNullableType(type); - return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t; + // If the given contextual type contains instantiable types and if a mapper representing + // return type inferences is available, instantiate those types using that mapper. + function instantiateContextualType(contextualType: Type | undefined, node: Node, contextFlags?: ContextFlags): Type | undefined { + if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) { + const inferenceContext = getInferenceContext(node); + // If no inferences have been made, nothing is gained from instantiating as type parameters + // would just be replaced with their defaults similar to the apparent type. + if (inferenceContext && some(inferenceContext.inferences, hasInferenceCandidates)) { + // For contextual signatures we incorporate all inferences made so far, e.g. from return + // types as well as arguments to the left in a function call. + if (contextFlags && contextFlags & ContextFlags.Signature) { + return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper); + } + // For other purposes (e.g. determining whether to produce literal types) we only + // incorporate inferences made from the return type in a function call. + if (inferenceContext.returnMapper) { + return instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); + } } - return type; } + return contextualType; + } - function checkNonNullType(type: Type, node: Node) { - return checkNonNullTypeWithReporter(type, node, reportObjectPossiblyNullOrUndefinedError); + // This function is similar to instantiateType, except that (a) it only instantiates types that + // are classified as instantiable (i.e. it doesn't instantiate object types), and (b) it performs + // no reductions on instantiated union types. + function instantiateInstantiableTypes(type: Type, mapper: TypeMapper): Type { + if (type.flags & TypeFlags.Instantiable) { + return instantiateType(type, mapper); } - - function checkNonNullNonVoidType(type: Type, node: Node): Type { - const nonNullType = checkNonNullType(type, node); - if (nonNullType.flags & TypeFlags.Void) { - error(node, Diagnostics.Object_is_possibly_undefined); - } - return nonNullType; + if (type.flags & TypeFlags.Union) { + return getUnionType(map((type as UnionType).types, t => instantiateInstantiableTypes(t, mapper)), UnionReduction.None); } - - function checkPropertyAccessExpression(node: PropertyAccessExpression, checkMode: CheckMode | undefined) { - return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain, checkMode) : - checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name, checkMode); + if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(map((type as IntersectionType).types, t => instantiateInstantiableTypes(t, mapper))); } + return type; + } - function checkPropertyAccessChain(node: PropertyAccessChain, checkMode: CheckMode | undefined) { - const leftType = checkExpression(node.expression); - const nonOptionalType = getOptionalExpressionType(leftType, node.expression); - return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name, checkMode), node, nonOptionalType !== leftType); + /** + * Whoa! Do you really want to use this function? + * + * Unless you're trying to get the *non-apparent* type for a + * value-literal type or you're authoring relevant portions of this algorithm, + * you probably meant to use 'getApparentTypeOfContextualType'. + * Otherwise this may not be very useful. + * + * In cases where you *are* working on this function, you should understand + * when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'. + * + * - Use 'getContextualType' when you are simply going to propagate the result to the expression. + * - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type. + * + * @param node the expression whose contextual type will be returned. + * @returns the contextual type of an expression. + */ + function getContextualType(node: Expression, contextFlags?: ContextFlags): Type | undefined { + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return undefined; + } + if (node.contextualType) { + return node.contextualType; + } + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.BindingElement: + return getContextualTypeForInitializerExpression(node, contextFlags); + case SyntaxKind.ArrowFunction: + case SyntaxKind.ReturnStatement: + return getContextualTypeForReturnExpression(node); + case SyntaxKind.YieldExpression: + return getContextualTypeForYieldOperand(parent as YieldExpression); + case SyntaxKind.AwaitExpression: + return getContextualTypeForAwaitOperand(parent as AwaitExpression, contextFlags); + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + return getContextualTypeForArgument(parent as CallExpression | NewExpression, node); + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return isConstTypeReference((parent as AssertionExpression).type) ? tryFindWhenConstTypeReference(parent as AssertionExpression) : getTypeFromTypeNode((parent as AssertionExpression).type); + case SyntaxKind.BinaryExpression: + return getContextualTypeForBinaryOperand(node, contextFlags); + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + return getContextualTypeForObjectLiteralElement(parent as PropertyAssignment | ShorthandPropertyAssignment, contextFlags); + case SyntaxKind.SpreadAssignment: + return getContextualType(parent.parent as ObjectLiteralExpression, contextFlags); + case SyntaxKind.ArrayLiteralExpression: { + const arrayLiteral = parent as ArrayLiteralExpression; + const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags); + return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node)); + } + case SyntaxKind.ConditionalExpression: + return getContextualTypeForConditionalOperand(node, contextFlags); + case SyntaxKind.TemplateSpan: + Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); + return getContextualTypeForSubstitutionExpression(parent.parent as TemplateExpression, node); + case SyntaxKind.ParenthesizedExpression: { + // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. + const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined; + return !tag ? getContextualType(parent as ParenthesizedExpression, contextFlags) : + isJSDocTypeTag(tag) && isConstTypeReference(tag.typeExpression.type) ? tryFindWhenConstTypeReference(parent as ParenthesizedExpression) : + getTypeFromTypeNode(tag.typeExpression.type); + } + case SyntaxKind.NonNullExpression: + return getContextualType(parent as NonNullExpression, contextFlags); + case SyntaxKind.JsxExpression: + return getContextualTypeForJsxExpression(parent as JsxExpression); + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxSpreadAttribute: + return getContextualTypeForJsxAttribute(parent as JsxAttribute | JsxSpreadAttribute); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + return getContextualJsxElementAttributesType(parent as JsxOpeningLikeElement, contextFlags); + } + return undefined; + + function tryFindWhenConstTypeReference(node: Expression) { + return getContextualType(node); } + } + + function getInferenceContext(node: Node) { + const ancestor = findAncestor(node, n => !!n.inferenceContext); + return ancestor && ancestor.inferenceContext!; + } - function checkQualifiedName(node: QualifiedName, checkMode: CheckMode | undefined) { - const leftType = isPartOfTypeQuery(node) && isThisIdentifier(node.left) ? checkNonNullType(checkThisExpression(node.left), node.left) : checkNonNullExpression(node.left); - return checkPropertyAccessExpressionOrQualifiedName(node, node.left, leftType, node.right, checkMode); + function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags?: ContextFlags) { + if (isJsxOpeningElement(node) && node.parent.contextualType && contextFlags !== ContextFlags.Completions) { + // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit + // _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type + // (as below) instead! + return node.parent.contextualType; } + return getContextualTypeForArgumentAtIndex(node, 0); + } - function isMethodAccessForCall(node: Node) { - while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { - node = node.parent; - } - return isCallOrNewExpression(node.parent) && node.parent.expression === node; + function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) { + return getJsxReferenceKind(node) !== JsxReferenceKind.Component + ? getJsxPropsTypeFromCallSignature(signature, node) + : getJsxPropsTypeFromClassType(signature, node); + } + + function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) { + let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType); + propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType); + const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); + if (!isErrorType(intrinsicAttribs)) { + propsType = intersectTypes(intrinsicAttribs, propsType); } + return propsType; + } - // Lookup the private identifier lexically. - function lookupSymbolForPrivateIdentifierDeclaration(propName: __String, location: Node): Symbol | undefined { - for (let containingClass = getContainingClass(location); !!containingClass; containingClass = getContainingClass(containingClass)) { - const { symbol } = containingClass; - const name = getSymbolNameForPrivateIdentifier(symbol, propName); - const prop = (symbol.members && symbol.members.get(name)) || (symbol.exports && symbol.exports.get(name)); - if (prop) { - return prop; + function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) { + if (sig.compositeSignatures) { + // JSX Elements using the legacy `props`-field based lookup (eg, react class components) need to treat the `props` member as an input + // instead of an output position when resolving the signature. We need to go back to the input signatures of the composite signature, + // get the type of `props` on each return type individually, and then _intersect them_, rather than union them (as would normally occur + // for a union signature). It's an unfortunate quirk of looking in the output of the signature for the type we want to use for the input. + // The default behavior of `getTypeOfFirstParameterOfSignatureWithFallback` when no `props` member name is defined is much more sane. + const results: Type[] = []; + for (const signature of sig.compositeSignatures) { + const instance = getReturnTypeOfSignature(signature); + if (isTypeAny(instance)) { + return instance; + } + const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation); + if (!propType) { + return; } + results.push(propType); } + return getIntersectionType(results); // Same result for both union and intersection signatures } + const instanceType = getReturnTypeOfSignature(sig); + return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); + } - function checkGrammarPrivateIdentifierExpression(privId: PrivateIdentifier): boolean { - if (!getContainingClass(privId)) { - return grammarErrorOnNode(privId, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) { + if (isJsxIntrinsicIdentifier(context.tagName)) { + const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context); + const fakeSignature = createSignatureForJSXIntrinsic(context, result); + return getOrCreateTypeFromSignature(fakeSignature); + } + const tagType = checkExpressionCached(context.tagName); + if (tagType.flags & TypeFlags.StringLiteral) { + const result = getIntrinsicAttributesTypeFromStringLiteralType(tagType as StringLiteralType, context); + if (!result) { + return errorType; } - if (!isExpressionNode(privId)) { - return grammarErrorOnNode(privId, Diagnostics.Private_identifiers_are_only_allowed_in_class_bodies_and_may_only_be_used_as_part_of_a_class_member_declaration_property_access_or_on_the_left_hand_side_of_an_in_expression); + const fakeSignature = createSignatureForJSXIntrinsic(context, result); + return getOrCreateTypeFromSignature(fakeSignature); + } + return tagType; + } + + function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) { + const managedSym = getJsxLibraryManagedAttributes(ns); + if (managedSym) { + const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); // fetches interface type, or initializes symbol links type parmaeters + const ctorType = getStaticTypeOfReferencedJsxConstructor(context); + if (managedSym.flags & SymbolFlags.TypeAlias) { + const params = getSymbolLinks(managedSym).typeParameters; + if (length(params) >= 2) { + const args = fillMissingTypeArguments([ctorType, attributesType], params, 2, isInJSFile(context)); + return getTypeAliasInstantiation(managedSym, args); + } } - if (!getSymbolForPrivateIdentifierExpression(privId)) { - return grammarErrorOnNode(privId, Diagnostics.Cannot_find_name_0, idText(privId)); + if (length((declaredManagedType as GenericType).typeParameters) >= 2) { + const args = fillMissingTypeArguments([ctorType, attributesType], (declaredManagedType as GenericType).typeParameters, 2, isInJSFile(context)); + return createTypeReference((declaredManagedType as GenericType), args); } - return false; } + return attributesType; + } - function checkPrivateIdentifierExpression(privId: PrivateIdentifier): Type { - checkGrammarPrivateIdentifierExpression(privId); - const symbol = getSymbolForPrivateIdentifierExpression(privId); - if (symbol) { - markPropertyAsReferenced(symbol, /* nodeForCheckWriteOnly: */ undefined, /* isThisAccess: */ false); + function getJsxPropsTypeFromClassType(sig: Signature, context: JsxOpeningLikeElement) { + const ns = getJsxNamespaceAt(context); + const forcedLookupLocation = getJsxElementPropertiesName(ns); + let attributesType = forcedLookupLocation === undefined + // If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type + ? getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType) + : forcedLookupLocation === "" + // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead + ? getReturnTypeOfSignature(sig) + // Otherwise get the type of the property on the signature return type + : getJsxPropsTypeForSignatureFromMember(sig, forcedLookupLocation); + + if (!attributesType) { + // There is no property named 'props' on this instance type + if (!!forcedLookupLocation && !!length(context.attributes.properties)) { + error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(forcedLookupLocation)); } - return anyType; + return unknownType; } - function getSymbolForPrivateIdentifierExpression(privId: PrivateIdentifier): Symbol | undefined { - if (!isExpressionNode(privId)) { - return undefined; + attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType); + + if (isTypeAny(attributesType)) { + // Props is of type 'any' or unknown + return attributesType; + } + else { + // Normal case -- add in IntrinsicClassElements and IntrinsicElements + let apparentAttributesType = attributesType; + const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context); + if (!isErrorType(intrinsicClassAttribs)) { + const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); + const hostClassType = getReturnTypeOfSignature(sig); + apparentAttributesType = intersectTypes( + typeParams + ? createTypeReference(intrinsicClassAttribs as GenericType, fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isInJSFile(context))) + : intrinsicClassAttribs, + apparentAttributesType + ); } - const links = getNodeLinks(privId); - if (links.resolvedSymbol === undefined) { - links.resolvedSymbol = lookupSymbolForPrivateIdentifierDeclaration(privId.escapedText, privId); + const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); + if (!isErrorType(intrinsicAttribs)) { + apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); } - return links.resolvedSymbol; - } - function getPrivateIdentifierPropertyOfType(leftType: Type, lexicallyScopedIdentifier: Symbol): Symbol | undefined { - return getPropertyOfType(leftType, lexicallyScopedIdentifier.escapedName); + return apparentAttributesType; } + } - function checkPrivateIdentifierPropertyAccess(leftType: Type, right: PrivateIdentifier, lexicallyScopedIdentifier: Symbol | undefined): boolean { - // Either the identifier could not be looked up in the lexical scope OR the lexically scoped identifier did not exist on the type. - // Find a private identifier with the same description on the type. - let propertyOnType: Symbol | undefined; - const properties = getPropertiesOfType(leftType); - if (properties) { - forEach(properties, (symbol: Symbol) => { - const decl = symbol.valueDeclaration; - if (decl && isNamedDeclaration(decl) && isPrivateIdentifier(decl.name) && decl.name.escapedText === right.escapedText) { - propertyOnType = symbol; - return true; - } - }); - } - const diagName = diagnosticName(right); - if (propertyOnType) { - const typeValueDecl = Debug.checkDefined(propertyOnType.valueDeclaration); - const typeClass = Debug.checkDefined(getContainingClass(typeValueDecl)); - // We found a private identifier property with the same description. - // Either: - // - There is a lexically scoped private identifier AND it shadows the one we found on the type. - // - It is an attempt to access the private identifier outside of the class. - if (lexicallyScopedIdentifier?.valueDeclaration) { - const lexicalValueDecl = lexicallyScopedIdentifier.valueDeclaration; - const lexicalClass = getContainingClass(lexicalValueDecl); - Debug.assert(!!lexicalClass); - if (findAncestor(lexicalClass, n => typeClass === n)) { - const diagnostic = error( - right, - Diagnostics.The_property_0_cannot_be_accessed_on_type_1_within_this_class_because_it_is_shadowed_by_another_private_identifier_with_the_same_spelling, - diagName, - typeToString(leftType) - ); + function getIntersectedSignatures(signatures: readonly Signature[]) { + return getStrictOptionValue(compilerOptions, "noImplicitAny") + ? reduceLeft( + signatures, + (left, right) => + left === right || !left ? left + : compareTypeParametersIdentical(left.typeParameters, right.typeParameters) ? combineSignaturesOfIntersectionMembers(left, right) + : undefined) + : undefined; + } - addRelatedInfo( - diagnostic, - createDiagnosticForNode( - lexicalValueDecl, - Diagnostics.The_shadowing_declaration_of_0_is_defined_here, - diagName - ), - createDiagnosticForNode( - typeValueDecl, - Diagnostics.The_declaration_of_0_that_you_probably_intended_to_use_is_defined_here, - diagName - ) - ); - return true; - } - } - error( - right, - Diagnostics.Property_0_is_not_accessible_outside_class_1_because_it_has_a_private_identifier, - diagName, - diagnosticName(typeClass.name || anon) - ); - return true; - } - return false; + function combineIntersectionThisParam(left: Symbol | undefined, right: Symbol | undefined, mapper: TypeMapper | undefined): Symbol | undefined { + if (!left || !right) { + return left || right; } + // A signature `this` type might be a read or a write position... It's very possible that it should be invariant + // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be + // pessimistic when contextual typing, for now, we'll union the `this` types. + const thisType = getUnionType([getTypeOfSymbol(left), instantiateType(getTypeOfSymbol(right), mapper)]); + return createSymbolWithType(left, thisType); + } - function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) { - return (isConstructorDeclaredProperty(prop) || isThisProperty(node) && isAutoTypedProperty(prop)) - && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop); + function combineIntersectionParameters(left: Signature, right: Signature, mapper: TypeMapper | undefined) { + const leftCount = getParameterCount(left); + const rightCount = getParameterCount(right); + const longest = leftCount >= rightCount ? left : right; + const shorter = longest === left ? right : left; + const longestCount = longest === left ? leftCount : rightCount; + const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); + const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); + const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0)); + for (let i = 0; i < longestCount; i++) { + let longestParamType = tryGetTypeAtPosition(longest, i)!; + if (longest === right) { + longestParamType = instantiateType(longestParamType, mapper); + } + let shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; + if (shorter === right) { + shorterParamType = instantiateType(shorterParamType, mapper); + } + const unionParamType = getUnionType([longestParamType, shorterParamType]); + const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); + const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); + const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i); + const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i); + + const paramName = leftName === rightName ? leftName : + !leftName ? rightName : + !rightName ? leftName : + undefined; + const paramSymbol = createSymbol( + SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), + paramName || `arg${i}` as __String + ); + paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; + params[i] = paramSymbol; } + if (needsExtraRestElement) { + const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); + restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); + if (shorter === right) { + restParamSymbol.type = instantiateType(restParamSymbol.type, mapper); + } + params[longestCount] = restParamSymbol; + } + return params; + } - function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier, checkMode: CheckMode | undefined) { - const parentSymbol = getNodeLinks(left).resolvedSymbol; - const assignmentKind = getAssignmentTargetKind(node); - const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); - const isAnyLike = isTypeAny(apparentType) || apparentType === silentNeverType; - let prop: Symbol | undefined; - if (isPrivateIdentifier(right)) { - if (languageVersion < ScriptTarget.ESNext) { - if (assignmentKind !== AssignmentKind.None) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldSet); - } - if (assignmentKind !== AssignmentKind.Definite) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldGet); - } - } + function combineSignaturesOfIntersectionMembers(left: Signature, right: Signature): Signature { + const typeParams = left.typeParameters || right.typeParameters; + let paramMapper: TypeMapper | undefined; + if (left.typeParameters && right.typeParameters) { + paramMapper = createTypeMapper(right.typeParameters, left.typeParameters); + // We just use the type parameter defaults from the first signature + } + const declaration = left.declaration; + const params = combineIntersectionParameters(left, right, paramMapper); + const thisParam = combineIntersectionThisParam(left.thisParameter, right.thisParameter, paramMapper); + const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); + const result = createSignature( + declaration, + typeParams, + thisParam, + params, + /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, + minArgCount, + (left.flags | right.flags) & SignatureFlags.PropagatingFlags + ); + result.compositeKind = TypeFlags.Intersection; + result.compositeSignatures = concatenate(left.compositeKind === TypeFlags.Intersection && left.compositeSignatures || [left], [right]); + if (paramMapper) { + result.mapper = left.compositeKind === TypeFlags.Intersection && left.mapper && left.compositeSignatures ? combineTypeMappers(left.mapper, paramMapper) : paramMapper; + } + return result; + } - const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); - if (assignmentKind && lexicallyScopedSymbol && lexicallyScopedSymbol.valueDeclaration && isMethodDeclaration(lexicallyScopedSymbol.valueDeclaration)) { - grammarErrorOnNode(right, Diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable, idText(right)); - } + // If the given type is an object or union type with a single signature, and if that signature has at + // least as many parameters as the given function, return the signature. Otherwise return undefined. + function getContextualCallSignature(type: Type, node: SignatureDeclaration): Signature | undefined { + const signatures = getSignaturesOfType(type, SignatureKind.Call); + const applicableByArity = filter(signatures, s => !isAritySmaller(s, node)); + return applicableByArity.length === 1 ? applicableByArity[0] : getIntersectedSignatures(applicableByArity); + } - if (lexicallyScopedSymbol?.valueDeclaration && (getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && !useDefineForClassFields)) { - const lexicalClass = getContainingClass(lexicallyScopedSymbol.valueDeclaration); - const parentStaticFieldInitializer = findAncestor(node, (n) => { - if (n === lexicalClass) return "quit"; - if (isPropertyDeclaration(n.parent) && hasStaticModifier(n.parent) && n.parent.initializer === n && n.parent.parent === lexicalClass) { - return true; - } - return false; - }); - if (parentStaticFieldInitializer) { - const parentStaticFieldInitializerSymbol = getSymbolOfNode(parentStaticFieldInitializer.parent); - Debug.assert(parentStaticFieldInitializerSymbol, "Initializer without declaration symbol"); - const diagnostic = error(node, - Diagnostics.Property_0_may_not_be_used_in_a_static_property_s_initializer_in_the_same_class_when_target_is_esnext_and_useDefineForClassFields_is_false, - symbolName(lexicallyScopedSymbol)); - addRelatedInfo(diagnostic, - createDiagnosticForNode(parentStaticFieldInitializer.parent, - Diagnostics.Initializer_for_property_0, - symbolName(parentStaticFieldInitializerSymbol)) - ); - } - } + /** If the contextual signature has fewer parameters than the function expression, do not use it */ + function isAritySmaller(signature: Signature, target: SignatureDeclaration) { + let targetParameterCount = 0; + for (; targetParameterCount < target.parameters.length; targetParameterCount++) { + const param = target.parameters[targetParameterCount]; + if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { + break; + } + } + if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { + targetParameterCount--; + } + return !hasEffectiveRestParameter(signature) && getParameterCount(signature) < targetParameterCount; + } - if (isAnyLike) { - if (lexicallyScopedSymbol) { - return isErrorType(apparentType) ? errorType : apparentType; - } - if (!getContainingClass(right)) { - grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); - return anyType; - } + function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): Signature | undefined { + // Only function expressions, arrow functions, and object literal methods are contextually typed. + return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node) + ? getContextualSignature(node as FunctionExpression) + : undefined; + } + + // Return the contextual signature for a given expression node. A contextual type provides a + // contextual signature if it has a single call signature and if that call signature is non-generic. + // If the contextual type is a union type, get the signature from each type possible and if they are + // all identical ignoring their return type, the result is same signature but with return type as + // union type of return types from these signatures + function getContextualSignature(node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature | undefined { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + const typeTagSignature = getSignatureOfTypeTag(node); + if (typeTagSignature) { + return typeTagSignature; + } + const type = getApparentTypeOfContextualType(node, ContextFlags.Signature); + if (!type) { + return undefined; + } + if (!(type.flags & TypeFlags.Union)) { + return getContextualCallSignature(type, node); + } + let signatureList: Signature[] | undefined; + const types = (type as UnionType).types; + for (const current of types) { + const signature = getContextualCallSignature(current, node); + if (signature) { + if (!signatureList) { + // This signature will contribute to contextual union signature + signatureList = [signature]; } - prop = lexicallyScopedSymbol ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol) : undefined; - // Check for private-identifier-specific shadowing and lexical-scoping errors. - if (!prop && checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) { - return errorType; + else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { + // Signatures aren't identical, do not use + return undefined; } else { - const isSetonlyAccessor = prop && prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); - if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite) { - error(node, Diagnostics.Private_accessor_was_defined_without_a_getter); - } - } - } - else { - if (isAnyLike) { - if (isIdentifier(left) && parentSymbol) { - markAliasReferenced(parentSymbol, node); - } - return isErrorType(apparentType) ? errorType : apparentType;; + // Use this signature for contextual union signature + signatureList.push(signature); } - prop = getPropertyOfType(apparentType, right.escapedText); - } - // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums. - // The exceptions are: - // 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and - // 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`. - if (isIdentifier(left) && parentSymbol && (compilerOptions.isolatedModules || !(prop && isConstEnumOrConstEnumOnlyModule(prop)) || shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(node))) { - markAliasReferenced(parentSymbol, node); } + } + // Result is union of signatures collected (return type is union of return types of this signature set) + if (signatureList) { + return signatureList.length === 1 ? signatureList[0] : createUnionSignature(signatureList[0], signatureList); + } + } - let propType: Type; - if (!prop) { - const indexInfo = !isPrivateIdentifier(right) && (assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType)) ? - getApplicableIndexInfoForName(apparentType, right.escapedText) : undefined; - if (!(indexInfo && indexInfo.type)) { - const isUncheckedJS = isUncheckedJSSuggestion(node, leftType.symbol, /*excludeClasses*/ true); - if (!isUncheckedJS && isJSLiteralType(leftType)) { - return anyType; - } - if (leftType.symbol === globalThisSymbol) { - if (globalThisSymbol.exports!.has(right.escapedText) && (globalThisSymbol.exports!.get(right.escapedText)!.flags & SymbolFlags.BlockScoped)) { - error(right, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(right.escapedText), typeToString(leftType)); - } - else if (noImplicitAny) { - error(right, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(leftType)); - } - return anyType; - } - if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { - reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType, isUncheckedJS); - } - return errorType; + function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type { + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray); + } + + const arrayOrIterableType = checkExpression(node.expression, checkMode); + return checkIteratedTypeOrElementType(IterationUse.Spread, arrayOrIterableType, undefinedType, node.expression); + } + + function checkSyntheticExpression(node: SyntheticExpression): Type { + return node.isSpread ? getIndexedAccessType(node.type, numberType) : node.type; + } + + function hasDefaultValue(node: BindingElement | Expression): boolean { + return (node.kind === SyntaxKind.BindingElement && !!(node as BindingElement).initializer) || + (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken); + } + + function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined, forceTuple: boolean | undefined): Type { + const elements = node.elements; + const elementCount = elements.length; + const elementTypes: Type[] = []; + const elementFlags: ElementFlags[] = []; + const contextualType = getApparentTypeOfContextualType(node); + const inDestructuringPattern = isAssignmentTarget(node); + const inConstContext = isConstContext(node); + let hasOmittedExpression = false; + for (let i = 0; i < elementCount; i++) { + const e = elements[i]; + if (e.kind === SyntaxKind.SpreadElement) { + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(e, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray); } - if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) { - error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType)); + const spreadType = checkExpression((e as SpreadElement).expression, checkMode, forceTuple); + if (isArrayLikeType(spreadType)) { + elementTypes.push(spreadType); + elementFlags.push(ElementFlags.Variadic); } - - propType = (compilerOptions.noUncheckedIndexedAccess && !isAssignmentTarget(node)) ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; - if (compilerOptions.noPropertyAccessFromIndexSignature && isPropertyAccessExpression(node)) { - error(right, Diagnostics.Property_0_comes_from_an_index_signature_so_it_must_be_accessed_with_0, unescapeLeadingUnderscores(right.escapedText)); + else if (inDestructuringPattern) { + // Given the following situation: + // var c: {}; + // [...c] = ["", 0]; + // + // c is represented in the tree as a spread element in an array literal. + // But c really functions as a rest element, and its purpose is to provide + // a contextual type for the right hand side of the assignment. Therefore, + // instead of calling checkExpression on "...c", which will give an error + // if c is not iterable/array-like, we need to act as if we are trying to + // get the contextual element type from it. So we do something similar to + // getContextualTypeForElementExpression, which will crucially not error + // if there is no index type / iterated type. + const restElementType = getIndexTypeOfType(spreadType, numberType) || + getIteratedTypeOrElementType(IterationUse.Destructuring, spreadType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false) || + unknownType; + elementTypes.push(restElementType); + elementFlags.push(ElementFlags.Rest); + } + else { + elementTypes.push(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, (e as SpreadElement).expression)); + elementFlags.push(ElementFlags.Rest); } } + else if (exactOptionalPropertyTypes && e.kind === SyntaxKind.OmittedExpression) { + hasOmittedExpression = true; + elementTypes.push(missingType); + elementFlags.push(ElementFlags.Optional); + } else { - if (prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(node, prop)) { - addDeprecatedSuggestion(right, prop.declarations, right.escapedText as string); - } - checkPropertyNotUsedBeforeDeclaration(prop, node, right); - markPropertyAsReferenced(prop, node, isSelfTypeAccess(left, parentSymbol)); - getNodeLinks(node).resolvedSymbol = prop; - const writing = isWriteAccess(node); - checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, writing, apparentType, prop); - if (isAssignmentToReadonlyEntity(node as Expression, prop, assignmentKind)) { - error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right)); - return errorType; - } - - propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writing ? getSetAccessorTypeOfSymbol(prop) : getTypeOfSymbol(prop); + const elementContextualType = getContextualTypeForElementExpression(contextualType, elementTypes.length); + const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple); + elementTypes.push(addOptionality(type, /*isProperty*/ true, hasOmittedExpression)); + elementFlags.push(hasOmittedExpression ? ElementFlags.Optional : ElementFlags.Required); } - - return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode); } + if (inDestructuringPattern) { + return createTupleType(elementTypes, elementFlags); + } + if (forceTuple || inConstContext || contextualType && someType(contextualType, isTupleLikeType)) { + return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext)); + } + return createArrayLiteralType(createArrayType(elementTypes.length ? + getUnionType(sameMap(elementTypes, (t, i) => elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessTypeOrUndefined(t, numberType) || anyType : t), UnionReduction.Subtype) : + strictNullChecks ? implicitNeverType : undefinedWideningType, inConstContext)); + } - /** - * Determines whether a did-you-mean error should be a suggestion in an unchecked JS file. - * Only applies to unchecked JS files without checkJS, // @ts-check or // @ts-nocheck - * It does not suggest when the suggestion: - * - Is from a global file that is different from the reference file, or - * - (optionally) Is a class, or is a this.x property access expression - */ - function isUncheckedJSSuggestion(node: Node | undefined, suggestion: Symbol | undefined, excludeClasses: boolean): boolean { - const file = getSourceFileOfNode(node); - if (file) { - if (compilerOptions.checkJs === undefined && file.checkJsDirective === undefined && (file.scriptKind === ScriptKind.JS || file.scriptKind === ScriptKind.JSX)) { - const declarationFile = forEach(suggestion?.declarations, getSourceFileOfNode); - return !(file !== declarationFile && !!declarationFile && isGlobalSourceFile(declarationFile)) - && !(excludeClasses && suggestion && suggestion.flags & SymbolFlags.Class) - && !(!!node && excludeClasses && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword); - } - } - return false; + function createArrayLiteralType(type: Type) { + if (!(getObjectFlags(type) & ObjectFlags.Reference)) { + return type; } + let literalType = (type as TypeReference).literalType; + if (!literalType) { + literalType = (type as TypeReference).literalType = cloneTypeReference(type as TypeReference); + literalType.objectFlags |= ObjectFlags.ArrayLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + } + return literalType; + } - function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node, checkMode: CheckMode | undefined) { - // Only compute control flow type if this is a property access expression that isn't an - // assignment target, and the referenced property was declared as a variable, property, - // accessor, or optional method. - const assignmentKind = getAssignmentTargetKind(node); - if (assignmentKind === AssignmentKind.Definite) { - return removeMissingType(propType, !!(prop && prop.flags & SymbolFlags.Optional)); - } - if (prop && - !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) - && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union) - && !isDuplicatedCommonJSExport(prop.declarations)) { - return propType; - } - if (propType === autoType) { - return getFlowTypeOfProperty(node, prop); - } - propType = getNarrowableTypeForReference(propType, node, checkMode); - // If strict null checks and strict property initialization checks are enabled, if we have - // a this.xxx property access, if the property is an instance property without an initializer, - // and if we are in a constructor of the same class as the property declaration, assume that - // the property is uninitialized at the top of the control flow. - let assumeUninitialized = false; - if (strictNullChecks && strictPropertyInitialization && isAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword) { - const declaration = prop && prop.valueDeclaration; - if (declaration && isPropertyWithoutInitializer(declaration)) { - if (!isStatic(declaration)) { - const flowContainer = getControlFlowContainer(node); - if (flowContainer.kind === SyntaxKind.Constructor && flowContainer.parent === declaration.parent && !(declaration.flags & NodeFlags.Ambient)) { - assumeUninitialized = true; - } - } - } - } - else if (strictNullChecks && prop && prop.valueDeclaration && - isPropertyAccessExpression(prop.valueDeclaration) && - getAssignmentDeclarationPropertyAccessKind(prop.valueDeclaration) && - getControlFlowContainer(node) === getControlFlowContainer(prop.valueDeclaration)) { - assumeUninitialized = true; - } - const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); - if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { - error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 - // Return the declared type to reduce follow-on errors - return propType; - } - return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + function isNumericName(name: DeclarationName): boolean { + switch (name.kind) { + case SyntaxKind.ComputedPropertyName: + return isNumericComputedName(name); + case SyntaxKind.Identifier: + return isNumericLiteralName(name.escapedText); + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + return isNumericLiteralName(name.text); + default: + return false; } + } - function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier | PrivateIdentifier): void { - const { valueDeclaration } = prop; - if (!valueDeclaration || getSourceFileOfNode(node).isDeclarationFile) { - return; - } + function isNumericComputedName(name: ComputedPropertyName): boolean { + // It seems odd to consider an expression of type Any to result in a numeric name, + // but this behavior is consistent with checkIndexedAccess + return isTypeAssignableToKind(checkComputedPropertyName(name), TypeFlags.NumberLike); + } - let diagnosticMessage; - const declarationName = idText(right); - if (isInPropertyInitializerOrClassStaticBlock(node) - && !isOptionalPropertyDeclaration(valueDeclaration) - && !(isAccessExpression(node) && isAccessExpression(node.expression)) - && !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right) - && (compilerOptions.useDefineForClassFields || !isPropertyDeclaredInAncestorClass(prop))) { - diagnosticMessage = error(right, Diagnostics.Property_0_is_used_before_its_initialization, declarationName); - } - else if (valueDeclaration.kind === SyntaxKind.ClassDeclaration && - node.parent.kind !== SyntaxKind.TypeReference && - !(valueDeclaration.flags & NodeFlags.Ambient) && - !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)) { - diagnosticMessage = error(right, Diagnostics.Class_0_used_before_its_declaration, declarationName); - } + function isNumericLiteralName(name: string | __String) { + // The intent of numeric names is that + // - they are names with text in a numeric form, and that + // - setting properties/indexing with them is always equivalent to doing so with the numeric literal 'numLit', + // acquired by applying the abstract 'ToNumber' operation on the name's text. + // + // The subtlety is in the latter portion, as we cannot reliably say that anything that looks like a numeric literal is a numeric name. + // In fact, it is the case that the text of the name must be equal to 'ToString(numLit)' for this to hold. + // + // Consider the property name '"0xF00D"'. When one indexes with '0xF00D', they are actually indexing with the value of 'ToString(0xF00D)' + // according to the ECMAScript specification, so it is actually as if the user indexed with the string '"61453"'. + // Thus, the text of all numeric literals equivalent to '61543' such as '0xF00D', '0xf00D', '0170015', etc. are not valid numeric names + // because their 'ToString' representation is not equal to their original text. + // This is motivated by ECMA-262 sections 9.3.1, 9.8.1, 11.1.5, and 11.2.1. + // + // Here, we test whether 'ToString(ToNumber(name))' is exactly equal to 'name'. + // The '+' prefix operator is equivalent here to applying the abstract ToNumber operation. + // Applying the 'toString()' method on a number gives us the abstract ToString operation on a number. + // + // Note that this accepts the values 'Infinity', '-Infinity', and 'NaN', and that this is intentional. + // This is desired behavior, because when indexing with them as numeric entities, you are indexing + // with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively. + return (+name).toString() === name; + } - if (diagnosticMessage) { - addRelatedInfo(diagnosticMessage, - createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName) - ); + function checkComputedPropertyName(node: ComputedPropertyName): Type { + const links = getNodeLinks(node.expression); + if (!links.resolvedType) { + if ((isTypeLiteralNode(node.parent.parent) || isClassLike(node.parent.parent) || isInterfaceDeclaration(node.parent.parent)) + && isBinaryExpression(node.expression) && node.expression.operatorToken.kind === SyntaxKind.InKeyword) { + return links.resolvedType = errorType; + } + links.resolvedType = checkExpression(node.expression); + // The computed property name of a non-static class field within a loop must be stored in a block-scoped binding. + // (It needs to be bound at class evaluation time.) + if (isPropertyDeclaration(node.parent) && !hasStaticModifier(node.parent) && isClassExpression(node.parent.parent)) { + const container = getEnclosingBlockScopeContainer(node.parent.parent); + const enclosingIterationStatement = getEnclosingIterationStatement(container); + if (enclosingIterationStatement) { + // The computed field name will use a block scoped binding which can be unique for each iteration of the loop. + getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + // The generated variable which stores the computed field name must be block-scoped. + getNodeLinks(node).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + // The generated variable which stores the class must be block-scoped. + getNodeLinks(node.parent.parent).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + } + } + // This will allow types number, string, symbol or any. It will also allow enums, the unknown + // type, and any union of these types (like string | number). + if (links.resolvedType.flags & TypeFlags.Nullable || + !isTypeAssignableToKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) && + !isTypeAssignableTo(links.resolvedType, stringNumberSymbolType)) { + error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any); + } + } + + return links.resolvedType; + } + + function isSymbolWithNumericName(symbol: Symbol) { + const firstDecl = symbol.declarations?.[0]; + return isNumericLiteralName(symbol.escapedName) || (firstDecl && isNamedDeclaration(firstDecl) && isNumericName(firstDecl.name)); + } + + function isSymbolWithSymbolName(symbol: Symbol) { + const firstDecl = symbol.declarations?.[0]; + return isKnownSymbol(symbol) || (firstDecl && isNamedDeclaration(firstDecl) && isComputedPropertyName(firstDecl.name) && + isTypeAssignableToKind(checkComputedPropertyName(firstDecl.name), TypeFlags.ESSymbol)); + } + + function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, offset: number, properties: Symbol[], keyType: Type): IndexInfo { + const propTypes: Type[] = []; + for (let i = offset; i < properties.length; i++) { + const prop = properties[i]; + if (keyType === stringType && !isSymbolWithSymbolName(prop) || + keyType === numberType && isSymbolWithNumericName(prop) || + keyType === esSymbolType && isSymbolWithSymbolName(prop)) { + propTypes.push(getTypeOfSymbol(properties[i])); } } + const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType; + return createIndexInfo(keyType, unionType, isConstContext(node)); + } - function isInPropertyInitializerOrClassStaticBlock(node: Node): boolean { - return !!findAncestor(node, node => { - switch (node.kind) { - case SyntaxKind.PropertyDeclaration: - return true; - case SyntaxKind.PropertyAssignment: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.SpreadAssignment: - case SyntaxKind.ComputedPropertyName: - case SyntaxKind.TemplateSpan: - case SyntaxKind.JsxExpression: - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxAttributes: - case SyntaxKind.JsxSpreadAttribute: - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.ExpressionWithTypeArguments: - case SyntaxKind.HeritageClause: - return false; - case SyntaxKind.ArrowFunction: - case SyntaxKind.ExpressionStatement: - return isBlock(node.parent) && isClassStaticBlockDeclaration(node.parent.parent) ? true : "quit"; - default: - return isExpressionNode(node) ? false : "quit"; - } - }); + function getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined { + Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); + const links = getSymbolLinks(symbol); + if (!links.immediateTarget) { + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) return Debug.fail(); + links.immediateTarget = getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true); } - /** - * It's possible that "prop.valueDeclaration" is a local declaration, but the property was also declared in a superclass. - * In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration. - */ - function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean { - if (!(prop.parent!.flags & SymbolFlags.Class)) { - return false; - } - let classType: InterfaceType | undefined = getTypeOfSymbol(prop.parent!) as InterfaceType; - while (true) { - classType = classType.symbol && getSuperClass(classType) as InterfaceType | undefined; - if (!classType) { - return false; - } - const superProperty = getPropertyOfType(classType, prop.escapedName); - if (superProperty && superProperty.valueDeclaration) { - return true; + return links.immediateTarget; + } + + function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type { + const inDestructuringPattern = isAssignmentTarget(node); + // Grammar checking + checkGrammarObjectLiteralExpression(node, inDestructuringPattern); + + const allPropertiesTable = strictNullChecks ? createSymbolTable() : undefined; + let propertiesTable = createSymbolTable(); + let propertiesArray: Symbol[] = []; + let spread: Type = emptyObjectType; + + const contextualType = getApparentTypeOfContextualType(node); + const contextualTypeHasPattern = contextualType && contextualType.pattern && + (contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression); + const inConstContext = isConstContext(node); + const checkFlags = inConstContext ? CheckFlags.Readonly : 0; + const isInJavascript = isInJSFile(node) && !isInJsonFile(node); + const enumTag = getJSDocEnumTag(node); + const isJSObjectLiteral = !contextualType && isInJavascript && !enumTag; + let objectFlags: ObjectFlags = freshObjectLiteralFlag; + let patternWithComputedProperties = false; + let hasComputedStringProperty = false; + let hasComputedNumberProperty = false; + let hasComputedSymbolProperty = false; + + // Spreads may cause an early bail; ensure computed names are always checked (this is cached) + // As otherwise they may not be checked until exports for the type at this position are retrieved, + // which may never occur. + for (const elem of node.properties) { + if (elem.name && isComputedPropertyName(elem.name)) { + checkComputedPropertyName(elem.name); + } + } + + let offset = 0; + for (const memberDecl of node.properties) { + let member = getSymbolOfNode(memberDecl); + const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName ? + checkComputedPropertyName(memberDecl.name) : undefined; + if (memberDecl.kind === SyntaxKind.PropertyAssignment || + memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment || + isObjectLiteralMethod(memberDecl)) { + let type = memberDecl.kind === SyntaxKind.PropertyAssignment ? checkPropertyAssignment(memberDecl, checkMode) : + // avoid resolving the left side of the ShorthandPropertyAssignment outside of the destructuring + // for error recovery purposes. For example, if a user wrote `{ a = 100 }` instead of `{ a: 100 }`. + // we don't want to say "could not find 'a'". + memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ? checkExpressionForMutableLocation(!inDestructuringPattern && memberDecl.objectAssignmentInitializer ? memberDecl.objectAssignmentInitializer : memberDecl.name, checkMode) : + checkObjectLiteralMethod(memberDecl, checkMode); + if (isInJavascript) { + const jsDocType = getTypeForDeclarationFromJSDocComment(memberDecl); + if (jsDocType) { + checkTypeAssignableTo(type, jsDocType, memberDecl); + type = jsDocType; + } + else if (enumTag && enumTag.typeExpression) { + checkTypeAssignableTo(type, getTypeFromTypeNode(enumTag.typeExpression), memberDecl); + } + } + objectFlags |= getObjectFlags(type) & ObjectFlags.PropagatingFlags; + const nameType = computedNameType && isTypeUsableAsPropertyName(computedNameType) ? computedNameType : undefined; + const prop = nameType ? + createSymbol(SymbolFlags.Property | member.flags, getPropertyNameFromType(nameType), checkFlags | CheckFlags.Late) : + createSymbol(SymbolFlags.Property | member.flags, member.escapedName, checkFlags); + if (nameType) { + prop.nameType = nameType; } - } - } - function getSuperClass(classType: InterfaceType): Type | undefined { - const x = getBaseTypes(classType); - if (x.length === 0) { - return undefined; - } - return getIntersectionType(x); - } + if (inDestructuringPattern) { + // If object literal is an assignment pattern and if the assignment pattern specifies a default value + // for the property, make the property optional. + const isOptional = + (memberDecl.kind === SyntaxKind.PropertyAssignment && hasDefaultValue(memberDecl.initializer)) || + (memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment && memberDecl.objectAssignmentInitializer); + if (isOptional) { + prop.flags |= SymbolFlags.Optional; + } + } + else if (contextualTypeHasPattern && !(getObjectFlags(contextualType) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) { + // If object literal is contextually typed by the implied type of a binding pattern, and if the + // binding pattern specifies a default value for the property, make the property optional. + const impliedProp = getPropertyOfType(contextualType, member.escapedName); + if (impliedProp) { + prop.flags |= impliedProp.flags & SymbolFlags.Optional; + } - function reportNonexistentProperty(propNode: Identifier | PrivateIdentifier, containingType: Type, isUncheckedJS: boolean) { - let errorInfo: DiagnosticMessageChain | undefined; - let relatedInfo: Diagnostic | undefined; - if (!isPrivateIdentifier(propNode) && containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) { - for (const subtype of (containingType as UnionType).types) { - if (!getPropertyOfType(subtype, propNode.escapedText) && !getApplicableIndexInfoForName(subtype, propNode.escapedText)) { - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(subtype)); - break; + else if (!compilerOptions.suppressExcessPropertyErrors && !getIndexInfoOfType(contextualType, stringType)) { + error(memberDecl.name, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, + symbolToString(member), typeToString(contextualType)); } } + + prop.declarations = member.declarations; + prop.parent = member.parent; + if (member.valueDeclaration) { + prop.valueDeclaration = member.valueDeclaration; + } + + prop.type = type; + prop.target = member; + member = prop; + allPropertiesTable?.set(prop.escapedName, prop); } - if (typeHasStaticProperty(propNode.escapedText, containingType)) { - const propName = declarationNameToString(propNode); - const typeName = typeToString(containingType); - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName, typeName, typeName + "." + propName); - } - else { - const promisedType = getPromisedTypeOfPromise(containingType); - if (promisedType && getPropertyOfType(promisedType, propNode.escapedText)) { - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType)); - relatedInfo = createDiagnosticForNode(propNode, Diagnostics.Did_you_forget_to_use_await); + else if (memberDecl.kind === SyntaxKind.SpreadAssignment) { + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(memberDecl, ExternalEmitHelpers.Assign); + } + if (propertiesArray.length > 0) { + spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); + propertiesArray = []; + propertiesTable = createSymbolTable(); + hasComputedStringProperty = false; + hasComputedNumberProperty = false; + hasComputedSymbolProperty = false; + } + const type = getReducedType(checkExpression(memberDecl.expression)); + if (isValidSpreadType(type)) { + const mergedType = tryMergeUnionOfObjectTypeAndEmptyObject(type, inConstContext); + if (allPropertiesTable) { + checkSpreadPropOverrides(mergedType, allPropertiesTable, memberDecl); + } + offset = propertiesArray.length; + if (isErrorType(spread)) { + continue; + } + spread = getSpreadType(spread, mergedType, node.symbol, objectFlags, inConstContext); } else { - const missingProperty = declarationNameToString(propNode); - const container = typeToString(containingType); - const libSuggestion = getSuggestedLibForNonExistentProperty(missingProperty, containingType); - if (libSuggestion !== undefined) { - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_2_or_later, missingProperty, container, libSuggestion); + error(memberDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types); + spread = errorType; + } + continue; + } + else { + // TypeScript 1.0 spec (April 2014) + // A get accessor declaration is processed in the same manner as + // an ordinary function declaration(section 6.1) with no parameters. + // A set accessor declaration is processed in the same manner + // as an ordinary function declaration with a single parameter and a Void return type. + Debug.assert(memberDecl.kind === SyntaxKind.GetAccessor || memberDecl.kind === SyntaxKind.SetAccessor); + checkNodeDeferred(memberDecl); + } + + if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) { + if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) { + if (isTypeAssignableTo(computedNameType, numberType)) { + hasComputedNumberProperty = true; + } + else if (isTypeAssignableTo(computedNameType, esSymbolType)) { + hasComputedSymbolProperty = true; } else { - const suggestion = getSuggestedSymbolForNonexistentProperty(propNode, containingType); - if (suggestion !== undefined) { - const suggestedName = symbolName(suggestion); - const message = isUncheckedJS ? Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2 : Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2; - errorInfo = chainDiagnosticMessages(errorInfo, message, missingProperty, container, suggestedName); - relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName); - } - else { - const diagnostic = containerSeemsToBeEmptyDomElement(containingType) - ? Diagnostics.Property_0_does_not_exist_on_type_1_Try_changing_the_lib_compiler_option_to_include_dom - : Diagnostics.Property_0_does_not_exist_on_type_1; - errorInfo = chainDiagnosticMessages(elaborateNeverIntersection(errorInfo, containingType), diagnostic, missingProperty, container); - } + hasComputedStringProperty = true; + } + if (inDestructuringPattern) { + patternWithComputedProperties = true; } } } - const resultDiagnostic = createDiagnosticForNodeFromMessageChain(propNode, errorInfo); - if (relatedInfo) { - addRelatedInfo(resultDiagnostic, relatedInfo); + else { + propertiesTable.set(member.escapedName, member); } - addErrorOrSuggestion(!isUncheckedJS || errorInfo.code !== Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2.code, resultDiagnostic); + propertiesArray.push(member); } - function containerSeemsToBeEmptyDomElement(containingType: Type) { - return (compilerOptions.lib && !compilerOptions.lib.includes("dom")) && - everyContainedType(containingType, type => type.symbol && /^(EventTarget|Node|((HTML[a-zA-Z]*)?Element))$/.test(unescapeLeadingUnderscores(type.symbol.escapedName))) && - isEmptyObjectType(containingType); + // If object literal is contextually typed by the implied type of a binding pattern, augment the result + // type with those properties for which the binding pattern specifies a default value. + // If the object literal is spread into another object literal, skip this step and let the top-level object + // literal handle it instead. + if (contextualTypeHasPattern && node.parent.kind !== SyntaxKind.SpreadAssignment) { + for (const prop of getPropertiesOfType(contextualType)) { + if (!propertiesTable.get(prop.escapedName) && !getPropertyOfType(spread, prop.escapedName)) { + if (!(prop.flags & SymbolFlags.Optional)) { + error(prop.valueDeclaration || (prop as TransientSymbol).bindingElement, + Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value); + } + propertiesTable.set(prop.escapedName, prop); + propertiesArray.push(prop); + } + } } - function typeHasStaticProperty(propName: __String, containingType: Type): boolean { - const prop = containingType.symbol && getPropertyOfType(getTypeOfSymbol(containingType.symbol), propName); - return prop !== undefined && !!prop.valueDeclaration && isStatic(prop.valueDeclaration); + if (isErrorType(spread)) { + return errorType; } - function getSuggestedLibForNonExistentName(name: __String | Identifier) { - const missingName = diagnosticName(name); - const allFeatures = getScriptTargetFeatures(); - const libTargets = getOwnKeys(allFeatures); - for (const libTarget of libTargets) { - const containingTypes = getOwnKeys(allFeatures[libTarget]); - if (containingTypes !== undefined && contains(containingTypes, missingName)) { - return libTarget; - } + if (spread !== emptyObjectType) { + if (propertiesArray.length > 0) { + spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); + propertiesArray = []; + propertiesTable = createSymbolTable(); + hasComputedStringProperty = false; + hasComputedNumberProperty = false; } + // remap the raw emptyObjectType fed in at the top into a fresh empty object literal type, unique to this use site + return mapType(spread, t => t === emptyObjectType ? createObjectLiteralType() : t); } - function getSuggestedLibForNonExistentProperty(missingProperty: string, containingType: Type) { - const container = getApparentType(containingType).symbol; - if (!container) { - return undefined; + return createObjectLiteralType(); + + function createObjectLiteralType() { + const indexInfos = []; + if (hasComputedStringProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, stringType)); + if (hasComputedNumberProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, numberType)); + if (hasComputedSymbolProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, esSymbolType)); + const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, indexInfos); + result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + if (isJSObjectLiteral) { + result.objectFlags |= ObjectFlags.JSLiteral; } - const allFeatures = getScriptTargetFeatures(); - const libTargets = getOwnKeys(allFeatures); - for (const libTarget of libTargets) { - const featuresOfLib = allFeatures[libTarget]; - const featuresOfContainingType = featuresOfLib[symbolName(container)]; - if (featuresOfContainingType !== undefined && contains(featuresOfContainingType, missingProperty)) { - return libTarget; - } + if (patternWithComputedProperties) { + result.objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; } + if (inDestructuringPattern) { + result.pattern = node; + } + return result; } + } - function getSuggestedSymbolForNonexistentClassMember(name: string, baseType: Type): Symbol | undefined { - return getSpellingSuggestionForName(name, getPropertiesOfType(baseType), SymbolFlags.ClassMember); - } - - function getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined { - let props = getPropertiesOfType(containingType); - if (typeof name !== "string") { - const parent = name.parent; - if (isPropertyAccessExpression(parent)) { - props = filter(props, prop => isValidPropertyAccessForCompletions(parent, containingType, prop)); - } - name = idText(name); + function isValidSpreadType(type: Type): boolean { + if (type.flags & TypeFlags.Instantiable) { + const constraint = getBaseConstraintOfType(type); + if (constraint !== undefined) { + return isValidSpreadType(constraint); } - return getSpellingSuggestionForName(name, props, SymbolFlags.Value); } + return !!(type.flags & (TypeFlags.Any | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) || + getFalsyFlags(type) & TypeFlags.DefinitelyFalsy && isValidSpreadType(removeDefinitelyFalsyTypes(type)) || + type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isValidSpreadType)); + } - function getSuggestedSymbolForNonexistentJSXAttribute(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined { - const strName = isString(name) ? name : idText(name); - const properties = getPropertiesOfType(containingType); - const jsxSpecific = strName === "for" ? find(properties, x => symbolName(x) === "htmlFor") - : strName === "class" ? find(properties, x => symbolName(x) === "className") - : undefined; - return jsxSpecific ?? getSpellingSuggestionForName(strName, properties, SymbolFlags.Value); - } - - function getSuggestionForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): string | undefined { - const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType); - return suggestion && symbolName(suggestion); - } - - function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined { - Debug.assert(outerName !== undefined, "outername should always be defined"); - const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, (symbols, name, meaning) => { - Debug.assertEqual(outerName, name, "name should equal outerName"); - const symbol = getSymbol(symbols, name, meaning); - // Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function - // So the table *contains* `x` but `x` isn't actually in scope. - // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion. - if (symbol) return symbol; - let candidates: Symbol[]; - if (symbols === globals) { - const primitives = mapDefined( - ["string", "number", "boolean", "object", "bigint", "symbol"], - s => symbols.has((s.charAt(0).toUpperCase() + s.slice(1)) as __String) - ? createSymbol(SymbolFlags.TypeAlias, s as __String) as Symbol - : undefined); - candidates = primitives.concat(arrayFrom(symbols.values())); - } - else { - candidates = arrayFrom(symbols.values()); - } - return getSpellingSuggestionForName(unescapeLeadingUnderscores(name), candidates, meaning); - }); - return result; - } + function checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) { + checkJsxOpeningLikeElementOrOpeningFragment(node); + } - function getSuggestionForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): string | undefined { - const symbolResult = getSuggestedSymbolForNonexistentSymbol(location, outerName, meaning); - return symbolResult && symbolName(symbolResult); - } + function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type { + checkNodeDeferred(node); + return getJsxElementTypeAt(node) || anyType; + } - function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined { - return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); - } + function checkJsxElementDeferred(node: JsxElement) { + // Check attributes + checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement); - function getSuggestionForNonexistentExport(name: Identifier, targetModule: Symbol): string | undefined { - const suggestion = getSuggestedSymbolForNonexistentModule(name, targetModule); - return suggestion && symbolName(suggestion); + // Perform resolution on the closing tag so that rename/go to definition/etc work + if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) { + getIntrinsicTagSymbol(node.closingElement); + } + else { + checkExpression(node.closingElement.tagName); } - function getSuggestionForNonexistentIndexSignature(objectType: Type, expr: ElementAccessExpression, keyedType: Type): string | undefined { - // check if object type has setter or getter - function hasProp(name: "set" | "get") { - const prop = getPropertyOfObjectType(objectType, name as __String); - if (prop) { - const s = getSingleCallSignature(getTypeOfSymbol(prop)); - return !!s && getMinArgumentCount(s) >= 1 && isTypeAssignableTo(keyedType, getTypeAtPosition(s, 0)); - } - return false; - }; + checkJsxChildren(node); + } - const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get"; - if (!hasProp(suggestedMethod)) { - return undefined; - } + function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type { + checkNodeDeferred(node); - let suggestion = tryGetPropertyAccessOrIdentifierToString(expr.expression); - if (suggestion === undefined) { - suggestion = suggestedMethod; - } - else { - suggestion += "." + suggestedMethod; - } + return getJsxElementTypeAt(node) || anyType; + } - return suggestion; - } + function checkJsxFragment(node: JsxFragment): Type { + checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment); - function getSuggestedTypeForNonexistentStringLiteralType(source: StringLiteralType, target: UnionType): StringLiteralType | undefined { - const candidates = target.types.filter((type): type is StringLiteralType => !!(type.flags & TypeFlags.StringLiteral)); - return getSpellingSuggestion(source.value, candidates, type => type.value); + // by default, jsx:'react' will use jsxFactory = React.createElement and jsxFragmentFactory = React.Fragment + // if jsxFactory compiler option is provided, ensure jsxFragmentFactory compiler option or @jsxFrag pragma is provided too + const nodeSourceFile = getSourceFileOfNode(node); + if (getJSXTransformEnabled(compilerOptions) && (compilerOptions.jsxFactory || nodeSourceFile.pragmas.has("jsx")) + && !compilerOptions.jsxFragmentFactory && !nodeSourceFile.pragmas.has("jsxfrag")) { + error(node, compilerOptions.jsxFactory + ? Diagnostics.The_jsxFragmentFactory_compiler_option_must_be_provided_to_use_JSX_fragments_with_the_jsxFactory_compiler_option + : Diagnostics.An_jsxFrag_pragma_is_required_when_using_an_jsx_pragma_with_JSX_fragments); } - /** - * Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough. - * Names less than length 3 only check for case-insensitive equality, not levenshtein distance. - * - * If there is a candidate that's the same except for case, return that. - * If there is a candidate that's within one edit of the name, return that. - * Otherwise, return the candidate with the smallest Levenshtein distance, - * except for candidates: - * * With no name - * * Whose meaning doesn't match the `meaning` parameter. - * * Whose length differs from the target name by more than 0.34 of the length of the name. - * * Whose levenshtein distance is more than 0.4 of the length of the name - * (0.4 allows 1 substitution/transposition for every 5 characters, - * and 1 insertion/deletion at 3 characters) - */ - function getSpellingSuggestionForName(name: string, symbols: Symbol[], meaning: SymbolFlags): Symbol | undefined { - return getSpellingSuggestion(name, symbols, getCandidateName); - - function getCandidateName(candidate: Symbol) { - const candidateName = symbolName(candidate); - if (startsWith(candidateName, "\"")) { - return undefined; - } + checkJsxChildren(node); + return getJsxElementTypeAt(node) || anyType; + } - if (candidate.flags & meaning) { - return candidateName; - } + function isHyphenatedJsxName(name: string | __String) { + return stringContains(name as string, "-"); + } - if (candidate.flags & SymbolFlags.Alias) { - const alias = tryResolveAlias(candidate); - if (alias && alias.flags & meaning) { - return candidateName; - } - } + /** + * Returns true iff React would emit this tag name as a string rather than an identifier or qualified name + */ + function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): boolean { + return tagName.kind === SyntaxKind.Identifier && isIntrinsicJsxName(tagName.escapedText); + } - return undefined; - } - } + function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) { + return node.initializer + ? checkExpressionForMutableLocation(node.initializer, checkMode) + : trueType; // is sugar for + } - function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isSelfTypeAccess: boolean) { - const valueDeclaration = prop && (prop.flags & SymbolFlags.ClassMember) && prop.valueDeclaration; - if (!valueDeclaration) { - return; - } - const hasPrivateModifier = hasEffectiveModifier(valueDeclaration, ModifierFlags.Private); - const hasPrivateIdentifier = prop.valueDeclaration && isNamedDeclaration(prop.valueDeclaration) && isPrivateIdentifier(prop.valueDeclaration.name); - if (!hasPrivateModifier && !hasPrivateIdentifier) { - return; - } - if (nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly) && !(prop.flags & SymbolFlags.SetAccessor)) { - return; + /** + * Get attributes type of the JSX opening-like element. The result is from resolving "attributes" property of the opening-like element. + * + * @param openingLikeElement a JSX opening-like element + * @param filter a function to remove attributes that will not participate in checking whether attributes are assignable + * @return an anonymous type (similar to the one returned by checkObjectLiteral) in which its properties are attributes property. + * @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral, + * which also calls getSpreadType. + */ + function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode | undefined) { + const attributes = openingLikeElement.attributes; + const allAttributesTable = strictNullChecks ? createSymbolTable() : undefined; + let attributesTable = createSymbolTable(); + let spread: Type = emptyJsxObjectType; + let hasSpreadAnyType = false; + let typeToIntersect: Type | undefined; + let explicitlySpecifyChildrenAttribute = false; + let objectFlags: ObjectFlags = ObjectFlags.JsxAttributes; + const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(openingLikeElement)); + + for (const attributeDecl of attributes.properties) { + const member = attributeDecl.symbol; + if (isJsxAttribute(attributeDecl)) { + const exprType = checkJsxAttribute(attributeDecl, checkMode); + objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags; + + const attributeSymbol = createSymbol(SymbolFlags.Property | member.flags, member.escapedName); + attributeSymbol.declarations = member.declarations; + attributeSymbol.parent = member.parent; + if (member.valueDeclaration) { + attributeSymbol.valueDeclaration = member.valueDeclaration; + } + attributeSymbol.type = exprType; + attributeSymbol.target = member; + attributesTable.set(attributeSymbol.escapedName, attributeSymbol); + allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol); + if (attributeDecl.name.escapedText === jsxChildrenPropertyName) { + explicitlySpecifyChildrenAttribute = true; + } } - if (isSelfTypeAccess) { - // Find any FunctionLikeDeclaration because those create a new 'this' binding. But this should only matter for methods (or getters/setters). - const containingMethod = findAncestor(nodeForCheckWriteOnly, isFunctionLikeDeclaration); - if (containingMethod && containingMethod.symbol === prop) { - return; + else { + Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute); + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); + attributesTable = createSymbolTable(); + } + const exprType = getReducedType(checkExpressionCached(attributeDecl.expression, checkMode)); + if (isTypeAny(exprType)) { + hasSpreadAnyType = true; + } + if (isValidSpreadType(exprType)) { + spread = getSpreadType(spread, exprType, attributes.symbol, objectFlags, /*readonly*/ false); + if (allAttributesTable) { + checkSpreadPropOverrides(exprType, allAttributesTable, attributeDecl); + } + } + else { + typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType; } } - - (getCheckFlags(prop) & CheckFlags.Instantiated ? getSymbolLinks(prop).target : prop)!.isReferenced = SymbolFlags.All; - } - - function isSelfTypeAccess(name: Expression | QualifiedName, parent: Symbol | undefined) { - return name.kind === SyntaxKind.ThisKeyword - || !!parent && isEntityNameExpression(name) && parent === getResolvedSymbol(getFirstIdentifier(name)); } - function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: __String): boolean { - switch (node.kind) { - case SyntaxKind.PropertyAccessExpression: - return isValidPropertyAccessWithType(node, node.expression.kind === SyntaxKind.SuperKeyword, propertyName, getWidenedType(checkExpression(node.expression))); - case SyntaxKind.QualifiedName: - return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getWidenedType(checkExpression(node.left))); - case SyntaxKind.ImportType: - return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getTypeFromTypeNode(node)); + if (!hasSpreadAnyType) { + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); } } - /** - * Checks if an existing property access is valid for completions purposes. - * @param node a property access-like node where we want to check if we can access a property. - * This node does not need to be an access of the property we are checking. - * e.g. in completions, this node will often be an incomplete property access node, as in `foo.`. - * Besides providing a location (i.e. scope) used to check property accessibility, we use this node for - * computing whether this is a `super` property access. - * @param type the type whose property we are checking. - * @param property the accessed property's symbol. - */ - function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean { - return isPropertyAccessible(node, - node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, - /* isWrite */ false, - type, - property); - // Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context. - } + // Handle children attribute + const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ? openingLikeElement.parent as JsxElement : undefined; + // We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement + if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) { + const childrenTypes: Type[] = checkJsxChildren(parent, checkMode); - function isValidPropertyAccessWithType( - node: PropertyAccessExpression | QualifiedName | ImportTypeNode, - isSuper: boolean, - propertyName: __String, - type: Type): boolean { + if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") { + // Error if there is a attribute named "children" explicitly specified and children element. + // This is because children element will overwrite the value from attributes. + // Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread. + if (explicitlySpecifyChildrenAttribute) { + error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName)); + } + + const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes); + const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName); + // If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process + const childrenPropSymbol = createSymbol(SymbolFlags.Property, jsxChildrenPropertyName); + childrenPropSymbol.type = childrenTypes.length === 1 ? childrenTypes[0] : + childrenContextualType && someType(childrenContextualType, isTupleLikeType) ? createTupleType(childrenTypes) : + createArrayType(getUnionType(childrenTypes)); + // Fake up a property declaration for the children + childrenPropSymbol.valueDeclaration = factory.createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined); + setParent(childrenPropSymbol.valueDeclaration, attributes); + childrenPropSymbol.valueDeclaration.symbol = childrenPropSymbol; + const childPropMap = createSymbolTable(); + childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol); + spread = getSpreadType(spread, createAnonymousType(attributes.symbol, childPropMap, emptyArray, emptyArray, emptyArray), + attributes.symbol, objectFlags, /*readonly*/ false); - // Short-circuiting for improved performance. - if (isTypeAny(type)) { - return true; } + } - const prop = getPropertyOfType(type, propertyName); - return !!prop && isPropertyAccessible(node, isSuper, /* isWrite */ false, type, prop); + if (hasSpreadAnyType) { + return anyType; + } + if (typeToIntersect && spread !== emptyJsxObjectType) { + return getIntersectionType([typeToIntersect, spread]); } + return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread); /** - * Checks if a property can be accessed in a location. - * The location is given by the `node` parameter. - * The node does not need to be a property access. - * @param node location where to check property accessibility - * @param isSuper whether to consider this a `super` property access, e.g. `super.foo`. - * @param isWrite whether this is a write access, e.g. `++foo.x`. - * @param containingType type where the property comes from. - * @param property property symbol. + * Create anonymous type from given attributes symbol table. + * @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable + * @param attributesTable a symbol table of attributes property */ - function isPropertyAccessible( - node: Node, - isSuper: boolean, - isWrite: boolean, - containingType: Type, - property: Symbol): boolean { - - // Short-circuiting for improved performance. - if (isTypeAny(containingType)) { - return true; - } - - // A #private property access in an optional chain is an error dealt with by the parser. - // The checker does not check for it, so we need to do our own check here. - if (property.valueDeclaration && isPrivateIdentifierClassElementDeclaration(property.valueDeclaration)) { - const declClass = getContainingClass(property.valueDeclaration); - return !isOptionalChain(node) && !!findAncestor(node, parent => parent === declClass); - } - - return checkPropertyAccessibilityAtLocation(node, isSuper, isWrite, containingType, property); + function createJsxAttributesType() { + objectFlags |= freshObjectLiteralFlag; + const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, emptyArray); + result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + return result; } + } - /** - * Return the symbol of the for-in variable declared or referenced by the given for-in statement. - */ - function getForInVariableSymbol(node: ForInStatement): Symbol | undefined { - const initializer = node.initializer; - if (initializer.kind === SyntaxKind.VariableDeclarationList) { - const variable = (initializer as VariableDeclarationList).declarations[0]; - if (variable && !isBindingPattern(variable.name)) { - return getSymbolOfNode(variable); + function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode) { + const childrenTypes: Type[] = []; + for (const child of node.children) { + // In React, JSX text that contains only whitespaces will be ignored so we don't want to type-check that + // because then type of children property will have constituent of string type. + if (child.kind === SyntaxKind.JsxText) { + if (!child.containsOnlyTriviaWhiteSpaces) { + childrenTypes.push(stringType); } } - else if (initializer.kind === SyntaxKind.Identifier) { - return getResolvedSymbol(initializer as Identifier); + else if (child.kind === SyntaxKind.JsxExpression && !child.expression) { + continue; // empty jsx expressions don't *really* count as present children + } + else { + childrenTypes.push(checkExpressionForMutableLocation(child, checkMode)); } - return undefined; - } - - /** - * Return true if the given type is considered to have numeric property names. - */ - function hasNumericPropertyNames(type: Type) { - return getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, numberType); } + return childrenTypes; + } - /** - * Return true if given node is an expression consisting of an identifier (possibly parenthesized) - * that references a for-in variable for an object with numeric property names. - */ - function isForInVariableForNumericPropertyNames(expr: Expression) { - const e = skipParentheses(expr); - if (e.kind === SyntaxKind.Identifier) { - const symbol = getResolvedSymbol(e as Identifier); - if (symbol.flags & SymbolFlags.Variable) { - let child: Node = expr; - let node = expr.parent; - while (node) { - if (node.kind === SyntaxKind.ForInStatement && - child === (node as ForInStatement).statement && - getForInVariableSymbol(node as ForInStatement) === symbol && - hasNumericPropertyNames(getTypeOfExpression((node as ForInStatement).expression))) { - return true; - } - child = node; - node = node.parent; - } + function checkSpreadPropOverrides(type: Type, props: SymbolTable, spread: SpreadAssignment | JsxSpreadAttribute) { + for (const right of getPropertiesOfType(type)) { + if (!(right.flags & SymbolFlags.Optional)) { + const left = props.get(right.escapedName); + if (left) { + const diagnostic = error(left.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(left.escapedName)); + addRelatedInfo(diagnostic, createDiagnosticForNode(spread, Diagnostics.This_spread_always_overwrites_this_property)); } } - return false; } + } - function checkIndexedAccess(node: ElementAccessExpression, checkMode: CheckMode | undefined): Type { - return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain, checkMode) : - checkElementAccessExpression(node, checkNonNullExpression(node.expression), checkMode); - } + /** + * Check attributes property of opening-like element. This function is called during chooseOverload to get call signature of a JSX opening-like element. + * (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used) + * @param node a JSXAttributes to be resolved of its type + */ + function checkJsxAttributes(node: JsxAttributes, checkMode: CheckMode | undefined) { + return createJsxAttributesTypeFromAttributesProperty(node.parent, checkMode); + } - function checkElementAccessChain(node: ElementAccessChain, checkMode: CheckMode | undefined) { - const exprType = checkExpression(node.expression); - const nonOptionalType = getOptionalExpressionType(exprType, node.expression); - return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression), checkMode), node, nonOptionalType !== exprType); - } + function getJsxType(name: __String, location: Node | undefined) { + const namespace = getJsxNamespaceAt(location); + const exports = namespace && getExportsOfSymbol(namespace); + const typeSymbol = exports && getSymbol(exports, name, SymbolFlags.Type); + return typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType; + } - function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type, checkMode: CheckMode | undefined): Type { - const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType; - const indexExpression = node.argumentExpression; - const indexType = checkExpression(indexExpression); + /** + * Looks up an intrinsic tag name and returns a symbol that either points to an intrinsic + * property (in which case nodeLinks.jsxFlags will be IntrinsicNamedElement) or an intrinsic + * string index signature (in which case nodeLinks.jsxFlags will be IntrinsicIndexedElement). + * May also return unknownSymbol if both of these lookups fail. + */ + function getIntrinsicTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): Symbol { + const links = getNodeLinks(node); + if (!links.resolvedSymbol) { + const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, node); + if (!isErrorType(intrinsicElementsType)) { + // Property case + if (!isIdentifier(node.tagName)) return Debug.fail(); + const intrinsicProp = getPropertyOfType(intrinsicElementsType, node.tagName.escapedText); + if (intrinsicProp) { + links.jsxFlags |= JsxFlags.IntrinsicNamedElement; + return links.resolvedSymbol = intrinsicProp; + } - if (isErrorType(objectType) || objectType === silentNeverType) { - return objectType; - } + // Intrinsic string indexer case + const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, stringType); + if (indexSignatureType) { + links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; + return links.resolvedSymbol = intrinsicElementsType.symbol; + } - if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) { - error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal); - return errorType; + // Wasn't found + error(node, Diagnostics.Property_0_does_not_exist_on_type_1, idText(node.tagName), "JSX." + JsxNames.IntrinsicElements); + return links.resolvedSymbol = unknownSymbol; + } + else { + if (noImplicitAny) { + error(node, Diagnostics.JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists, unescapeLeadingUnderscores(JsxNames.IntrinsicElements)); + } + return links.resolvedSymbol = unknownSymbol; } + } + return links.resolvedSymbol; + } - const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; - const accessFlags = isAssignmentTarget(node) ? - AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : - AccessFlags.ExpressionPosition; - const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, accessFlags, node) || errorType; - return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node); + function getJsxNamespaceContainerForImplicitImport(location: Node | undefined): Symbol | undefined { + const file = location && getSourceFileOfNode(location); + const links = file && getNodeLinks(file); + if (links && links.jsxImplicitImportContainer === false) { + return undefined; + } + if (links && links.jsxImplicitImportContainer) { + return links.jsxImplicitImportContainer; + } + const runtimeImportSpecifier = getJSXRuntimeImport(getJSXImplicitImportBase(compilerOptions, file), compilerOptions); + if (!runtimeImportSpecifier) { + return undefined; + } + const isClassic = getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic; + const errorMessage = isClassic + ? Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option + : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; + const mod = resolveExternalModule(location!, runtimeImportSpecifier, errorMessage, location!); + const result = mod && mod !== unknownSymbol ? getMergedSymbol(resolveSymbol(mod)) : undefined; + if (links) { + links.jsxImplicitImportContainer = result || false; } + return result; + } - function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement { - return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node); + function getJsxNamespaceAt(location: Node | undefined): Symbol { + const links = location && getNodeLinks(location); + if (links && links.jsxNamespace) { + return links.jsxNamespace; } + if (!links || links.jsxNamespace !== false) { + let resolvedNamespace = getJsxNamespaceContainerForImplicitImport(location); - function resolveUntypedCall(node: CallLikeExpression): Signature { - if (callLikeExpressionMayHaveTypeArguments(node)) { - // Check type arguments even though we will give an error that untyped calls may not accept type arguments. - // This gets us diagnostics for the type arguments and marks them as referenced. - forEach(node.typeArguments, checkSourceElement); + if (!resolvedNamespace || resolvedNamespace === unknownSymbol) { + const namespaceName = getJsxNamespace(location); + resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined, namespaceName, /*isUse*/ false); } - if (node.kind === SyntaxKind.TaggedTemplateExpression) { - checkExpression(node.template); - } - else if (isJsxOpeningLikeElement(node)) { - checkExpression(node.attributes); - } - else if (node.kind !== SyntaxKind.Decorator) { - forEach((node as CallExpression).arguments, argument => { - checkExpression(argument); - }); - } - return anySignature; - } - - function resolveErrorCall(node: CallLikeExpression): Signature { - resolveUntypedCall(node); - return unknownSignature; - } - - // Re-order candidate signatures into the result array. Assumes the result array to be empty. - // The candidate list orders groups in reverse, but within a group signatures are kept in declaration order - // A nit here is that we reorder only signatures that belong to the same symbol, - // so order how inherited signatures are processed is still preserved. - // interface A { (x: string): void } - // interface B extends A { (x: 'foo'): string } - // const b: B; - // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void] - function reorderCandidates(signatures: readonly Signature[], result: Signature[], callChainFlags: SignatureFlags): void { - let lastParent: Node | undefined; - let lastSymbol: Symbol | undefined; - let cutoffIndex = 0; - let index: number | undefined; - let specializedIndex = -1; - let spliceIndex: number; - Debug.assert(!result.length); - for (const signature of signatures) { - const symbol = signature.declaration && getSymbolOfNode(signature.declaration); - const parent = signature.declaration && signature.declaration.parent; - if (!lastSymbol || symbol === lastSymbol) { - if (lastParent && parent === lastParent) { - index = index! + 1; - } - else { - lastParent = parent; - index = cutoffIndex; + if (resolvedNamespace) { + const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace)); + if (candidate && candidate !== unknownSymbol) { + if (links) { + links.jsxNamespace = candidate; } + return candidate; } - else { - // current declaration belongs to a different symbol - // set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex - index = cutoffIndex = result.length; - lastParent = parent; - } - lastSymbol = symbol; - - // specialized signatures always need to be placed before non-specialized signatures regardless - // of the cutoff position; see GH#1133 - if (signatureHasLiteralTypes(signature)) { - specializedIndex++; - spliceIndex = specializedIndex; - // The cutoff index always needs to be greater than or equal to the specialized signature index - // in order to prevent non-specialized signatures from being added before a specialized - // signature. - cutoffIndex++; - } - else { - spliceIndex = index; - } - - result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature); + } + if (links) { + links.jsxNamespace = false; } } - - function isSpreadArgument(arg: Expression | undefined): arg is Expression { - return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).isSpread); + // JSX global fallback + const s = resolveSymbol(getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined)); + if (s === unknownSymbol) { + return undefined!; // TODO: GH#18217 } + return s!; // TODO: GH#18217 + } - function getSpreadArgumentIndex(args: readonly Expression[]): number { - return findIndex(args, isSpreadArgument); - } + /** + * Look into JSX namespace and then look for container with matching name as nameOfAttribPropContainer. + * Get a single property from that container if existed. Report an error if there are more than one property. + * + * @param nameOfAttribPropContainer a string of value JsxNames.ElementAttributesPropertyNameContainer or JsxNames.ElementChildrenAttributeNameContainer + * if other string is given or the container doesn't exist, return undefined. + */ + function getNameFromJsxElementAttributesContainer(nameOfAttribPropContainer: __String, jsxNamespace: Symbol): __String | undefined { + // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [symbol] + const jsxElementAttribPropInterfaceSym = jsxNamespace && getSymbol(jsxNamespace.exports!, nameOfAttribPropContainer, SymbolFlags.Type); + // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [type] + const jsxElementAttribPropInterfaceType = jsxElementAttribPropInterfaceSym && getDeclaredTypeOfSymbol(jsxElementAttribPropInterfaceSym); + // The properties of JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute + const propertiesOfJsxElementAttribPropInterface = jsxElementAttribPropInterfaceType && getPropertiesOfType(jsxElementAttribPropInterfaceType); + if (propertiesOfJsxElementAttribPropInterface) { + // Element Attributes has zero properties, so the element attributes type will be the class instance type + if (propertiesOfJsxElementAttribPropInterface.length === 0) { + return "" as __String; + } + // Element Attributes has one property, so the element attributes type will be the type of the corresponding + // property of the class instance type + else if (propertiesOfJsxElementAttribPropInterface.length === 1) { + return propertiesOfJsxElementAttribPropInterface[0].escapedName; + } + else if (propertiesOfJsxElementAttribPropInterface.length > 1 && jsxElementAttribPropInterfaceSym.declarations) { + // More than one property on ElementAttributesProperty is an error + error(jsxElementAttribPropInterfaceSym.declarations[0], Diagnostics.The_global_type_JSX_0_may_not_have_more_than_one_property, unescapeLeadingUnderscores(nameOfAttribPropContainer)); + } + } + return undefined; + } - function acceptsVoid(t: Type): boolean { - return !!(t.flags & TypeFlags.Void); - } + function getJsxLibraryManagedAttributes(jsxNamespace: Symbol) { + // JSX.LibraryManagedAttributes [symbol] + return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.LibraryManagedAttributes, SymbolFlags.Type); + } - function acceptsVoidUndefinedUnknownOrAny(t: Type): boolean { - return !!(t.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Unknown | TypeFlags.Any)); - } + /// e.g. "props" for React.d.ts, + /// or 'undefined' if ElementAttributesProperty doesn't exist (which means all + /// non-intrinsic elements' attributes type is 'any'), + /// or '' if it has 0 properties (which means every + /// non-intrinsic elements' attributes type is the element instance type) + function getJsxElementPropertiesName(jsxNamespace: Symbol) { + return getNameFromJsxElementAttributesContainer(JsxNames.ElementAttributesPropertyNameContainer, jsxNamespace); + } - function hasCorrectArity(node: CallLikeExpression, args: readonly Expression[], signature: Signature, signatureHelpTrailingComma = false) { - let argCount: number; - let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments - let effectiveParameterCount = getParameterCount(signature); - let effectiveMinimumArguments = getMinArgumentCount(signature); + function getJsxElementChildrenPropertyName(jsxNamespace: Symbol): __String | undefined { + return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace); + } - if (node.kind === SyntaxKind.TaggedTemplateExpression) { - argCount = args.length; - if (node.template.kind === SyntaxKind.TemplateExpression) { - // If a tagged template expression lacks a tail literal, the call is incomplete. - // Specifically, a template only can end in a TemplateTail or a Missing literal. - const lastSpan = last(node.template.templateSpans); // we should always have at least one span. - callIsIncomplete = nodeIsMissing(lastSpan.literal) || !!lastSpan.literal.isUnterminated; - } - else { - // If the template didn't end in a backtick, or its beginning occurred right prior to EOF, - // then this might actually turn out to be a TemplateHead in the future; - // so we consider the call to be incomplete. - const templateLiteral = node.template as LiteralExpression; - Debug.assert(templateLiteral.kind === SyntaxKind.NoSubstitutionTemplateLiteral); - callIsIncomplete = !!templateLiteral.isUnterminated; - } - } - else if (node.kind === SyntaxKind.Decorator) { - argCount = getDecoratorArgumentCount(node, signature); - } - else if (isJsxOpeningLikeElement(node)) { - callIsIncomplete = node.attributes.end === node.end; - if (callIsIncomplete) { - return true; - } - argCount = effectiveMinimumArguments === 0 ? args.length : 1; - effectiveParameterCount = args.length === 0 ? effectiveParameterCount : 1; // class may have argumentless ctor functions - still resolve ctor and compare vs props member type - effectiveMinimumArguments = Math.min(effectiveMinimumArguments, 1); // sfc may specify context argument - handled by framework and not typechecked - } - else if (!node.arguments) { - // This only happens when we have something of the form: 'new C' - Debug.assert(node.kind === SyntaxKind.NewExpression); - return getMinArgumentCount(signature) === 0; + function getUninstantiatedJsxSignaturesOfType(elementType: Type, caller: JsxOpeningLikeElement): readonly Signature[] { + if (elementType.flags & TypeFlags.String) { + return [anySignature]; + } + else if (elementType.flags & TypeFlags.StringLiteral) { + const intrinsicType = getIntrinsicAttributesTypeFromStringLiteralType(elementType as StringLiteralType, caller); + if (!intrinsicType) { + error(caller, Diagnostics.Property_0_does_not_exist_on_type_1, (elementType as StringLiteralType).value, "JSX." + JsxNames.IntrinsicElements); + return emptyArray; } else { - argCount = signatureHelpTrailingComma ? args.length + 1 : args.length; - - // If we are missing the close parenthesis, the call is incomplete. - callIsIncomplete = node.arguments.end === node.end; - - // If a spread argument is present, check that it corresponds to a rest parameter or at least that it's in the valid range. - const spreadArgIndex = getSpreadArgumentIndex(args); - if (spreadArgIndex >= 0) { - return spreadArgIndex >= getMinArgumentCount(signature) && (hasEffectiveRestParameter(signature) || spreadArgIndex < getParameterCount(signature)); - } + const fakeSignature = createSignatureForJSXIntrinsic(caller, intrinsicType); + return [fakeSignature]; } + } + const apparentElemType = getApparentType(elementType); + // Resolve the signatures, preferring constructor + let signatures = getSignaturesOfType(apparentElemType, SignatureKind.Construct); + if (signatures.length === 0) { + // No construct signatures, try call signatures + signatures = getSignaturesOfType(apparentElemType, SignatureKind.Call); + } + if (signatures.length === 0 && apparentElemType.flags & TypeFlags.Union) { + // If each member has some combination of new/call signatures; make a union signature list for those + signatures = getUnionSignatures(map((apparentElemType as UnionType).types, t => getUninstantiatedJsxSignaturesOfType(t, caller))); + } + return signatures; + } - // Too many arguments implies incorrect arity. - if (!hasEffectiveRestParameter(signature) && argCount > effectiveParameterCount) { - return false; + function getIntrinsicAttributesTypeFromStringLiteralType(type: StringLiteralType, location: Node): Type | undefined { + // If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type + // For example: + // var CustomTag: "h1" = "h1"; + // Hello World + const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, location); + if (!isErrorType(intrinsicElementsType)) { + const stringLiteralTypeName = type.value; + const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName)); + if (intrinsicProp) { + return getTypeOfSymbol(intrinsicProp); + } + const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, stringType); + if (indexSignatureType) { + return indexSignatureType; } + return undefined; + } + // If we need to report an error, we already done so here. So just return any to prevent any more error downstream + return anyType; + } - // If the call is incomplete, we should skip the lower bound check. - // JSX signatures can have extra parameters provided by the library which we don't check - if (callIsIncomplete || argCount >= effectiveMinimumArguments) { - return true; + function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: JsxOpeningLikeElement) { + if (refKind === JsxReferenceKind.Function) { + const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); + if (sfcReturnConstraint) { + checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_return_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); } - for (let i = argCount; i < effectiveMinimumArguments; i++) { - const type = getTypeAtPosition(signature, i); - if (filterType(type, isInJSFile(node) && !strictNullChecks ? acceptsVoidUndefinedUnknownOrAny : acceptsVoid).flags & TypeFlags.Never) { - return false; - } + } + else if (refKind === JsxReferenceKind.Component) { + const classConstraint = getJsxElementClassTypeAt(openingLikeElement); + if (classConstraint) { + // Issue an error if this return type isn't assignable to JSX.ElementClass, failing that + checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_instance_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); + } + } + else { // Mixed + const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); + const classConstraint = getJsxElementClassTypeAt(openingLikeElement); + if (!sfcReturnConstraint || !classConstraint) { + return; } - return true; + const combined = getUnionType([sfcReturnConstraint, classConstraint]); + checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_element_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); } - function hasCorrectTypeArgumentArity(signature: Signature, typeArguments: NodeArray | undefined) { - // If the user supplied type arguments, but the number of type arguments does not match - // the declared number of type parameters, the call has an incorrect arity. - const numTypeParameters = length(signature.typeParameters); - const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters); - return !some(typeArguments) || - (typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters); + function generateInitialErrorChain(): DiagnosticMessageChain { + const componentName = getTextOfNode(openingLikeElement.tagName); + return chainDiagnosticMessages(/* details */ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName); } + } - // If type has a single call signature and no other members, return that signature. Otherwise, return undefined. - function getSingleCallSignature(type: Type): Signature | undefined { - return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false); + /** + * Get attributes type of the given intrinsic opening-like Jsx element by resolving the tag name. + * The function is intended to be called from a function which has checked that the opening element is an intrinsic element. + * @param node an intrinsic JSX opening-like element + */ + function getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type { + Debug.assert(isJsxIntrinsicIdentifier(node.tagName)); + const links = getNodeLinks(node); + if (!links.resolvedJsxElementAttributesType) { + const symbol = getIntrinsicTagSymbol(node); + if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) { + return links.resolvedJsxElementAttributesType = getTypeOfSymbol(symbol) || errorType; + } + else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) { + return links.resolvedJsxElementAttributesType = + getIndexTypeOfType(getJsxType(JsxNames.IntrinsicElements, node), stringType) || errorType; + } + else { + return links.resolvedJsxElementAttributesType = errorType; + } } + return links.resolvedJsxElementAttributesType; + } - function getSingleCallOrConstructSignature(type: Type): Signature | undefined { - return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) || - getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false); + function getJsxElementClassTypeAt(location: Node): Type | undefined { + const type = getJsxType(JsxNames.ElementClass, location); + if (isErrorType(type)) return undefined; + return type; + } + + function getJsxElementTypeAt(location: Node): Type { + return getJsxType(JsxNames.Element, location); + } + + function getJsxStatelessElementTypeAt(location: Node): Type | undefined { + const jsxElementType = getJsxElementTypeAt(location); + if (jsxElementType) { + return getUnionType([jsxElementType, nullType]); } + } - function getSingleSignature(type: Type, kind: SignatureKind, allowMembers: boolean): Signature | undefined { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - if (allowMembers || resolved.properties.length === 0 && resolved.indexInfos.length === 0) { - if (kind === SignatureKind.Call && resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0) { - return resolved.callSignatures[0]; - } - if (kind === SignatureKind.Construct && resolved.constructSignatures.length === 1 && resolved.callSignatures.length === 0) { - return resolved.constructSignatures[0]; - } - } - } - return undefined; + /** + * Returns all the properties of the Jsx.IntrinsicElements interface + */ + function getJsxIntrinsicTagNamesAt(location: Node): Symbol[] { + const intrinsics = getJsxType(JsxNames.IntrinsicElements, location); + return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray; + } + + function checkJsxPreconditions(errorNode: Node) { + // Preconditions for using JSX + if ((compilerOptions.jsx || JsxEmit.None) === JsxEmit.None) { + error(errorNode, Diagnostics.Cannot_use_JSX_unless_the_jsx_flag_is_provided); } - // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec) - function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, inferenceContext?: InferenceContext, compareTypes?: TypeComparer): Signature { - const context = createInferenceContext(signature.typeParameters!, signature, InferenceFlags.None, compareTypes); - // We clone the inferenceContext to avoid fixing. For example, when the source signature is (x: T) => T[] and - // the contextual signature is (...args: A) => B, we want to infer the element type of A's constraint (say 'any') - // for T but leave it possible to later infer '[any]' back to A. - const restType = getEffectiveRestType(contextualSignature); - const mapper = inferenceContext && (restType && restType.flags & TypeFlags.TypeParameter ? inferenceContext.nonFixingMapper : inferenceContext.mapper); - const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature; - applyToParameterTypes(sourceSignature, signature, (source, target) => { - // Type parameters from outer context referenced by source type are fixed by instantiation of the source type - inferTypes(context.inferences, source, target); - }); - if (!inferenceContext) { - applyToReturnTypes(contextualSignature, signature, (source, target) => { - inferTypes(context.inferences, source, target, InferencePriority.ReturnType); - }); + if (getJsxElementTypeAt(errorNode) === undefined) { + if (noImplicitAny) { + error(errorNode, Diagnostics.JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist); } - return getSignatureInstantiation(signature, getInferredTypes(context), isInJSFile(contextualSignature.declaration)); } + } - function inferJsxTypeArguments(node: JsxOpeningLikeElement, signature: Signature, checkMode: CheckMode, context: InferenceContext): Type[] { - const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); - const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context, checkMode); - inferTypes(context.inferences, checkAttrType, paramType); - return getInferredTypes(context); - } + function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) { + const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node); - function getThisArgumentType(thisArgumentNode: LeftHandSideExpression | undefined) { - if (!thisArgumentNode) { - return voidType; - } - const thisArgumentType = checkExpression(thisArgumentNode); - return isOptionalChainRoot(thisArgumentNode.parent) ? getNonNullableType(thisArgumentType) : - isOptionalChain(thisArgumentNode.parent) ? removeOptionalTypeMarker(thisArgumentType) : - thisArgumentType; + if (isNodeOpeningLikeElement) { + checkGrammarJsxElement(node as JsxOpeningLikeElement); } - function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): Type[] { - if (isJsxOpeningLikeElement(node)) { - return inferJsxTypeArguments(node, signature, checkMode, context); - } + checkJsxPreconditions(node); - // If a contextual type is available, infer from that type to the return type of the call expression. For - // example, given a 'function wrap(cb: (x: T) => U): (x: T) => U' and a call expression - // 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the - // return type of 'wrap'. - if (node.kind !== SyntaxKind.Decorator) { - const contextualType = getContextualType(node, every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)) ? ContextFlags.SkipBindingPatterns : ContextFlags.None); - if (contextualType) { - // We clone the inference context to avoid disturbing a resolution in progress for an - // outer call expression. Effectively we just want a snapshot of whatever has been - // inferred for any outer call expression so far. - const outerContext = getInferenceContext(node); - const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault)); - const instantiatedType = instantiateType(contextualType, outerMapper); - // If the contextual type is a generic function type with a single call signature, we - // instantiate the type with its own type parameters and type arguments. This ensures that - // the type parameters are not erased to type any during type inference such that they can - // be inferred as actual types from the contextual type. For example: - // declare function arrayMap(f: (x: T) => U): (a: T[]) => U[]; - // const boxElements: (a: A[]) => { value: A }[] = arrayMap(value => ({ value })); - // Above, the type of the 'value' parameter is inferred to be 'A'. - const contextualSignature = getSingleCallSignature(instantiatedType); - const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ? - getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) : - instantiatedType; - const inferenceTargetType = getReturnTypeOfSignature(signature); - // Inferences made from return types have lower priority than all other inferences. - inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); - // Create a type mapper for instantiating generic contextual types using the inferences made - // from the return type. We need a separate inference pass here because (a) instantiation of - // the source type uses the outer context's return mapper (which excludes inferences made from - // outer arguments), and (b) we don't want any further inferences going into this context. - const returnContext = createInferenceContext(signature.typeParameters!, signature, context.flags); - const returnSourceType = instantiateType(contextualType, outerContext && outerContext.returnMapper); - inferTypes(returnContext.inferences, returnSourceType, inferenceTargetType); - context.returnMapper = some(returnContext.inferences, hasInferenceCandidates) ? getMapperFromContext(cloneInferredPartOfContext(returnContext)) : undefined; - } - } - - const restType = getNonArrayRestType(signature); - const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; - if (restType && restType.flags & TypeFlags.TypeParameter) { - const info = find(context.inferences, info => info.typeParameter === restType); - if (info) { - info.impliedArity = findIndex(args, isSpreadArgument, argCount) < 0 ? args.length - argCount : undefined; - } - } + if (!getJsxNamespaceContainerForImplicitImport(node)) { + // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. + // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. + const jsxFactoryRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; + const jsxFactoryNamespace = getJsxNamespace(node); + const jsxFactoryLocation = isNodeOpeningLikeElement ? (node as JsxOpeningLikeElement).tagName : node; - const thisType = getThisTypeOfSignature(signature); - if (thisType) { - const thisArgumentNode = getThisArgumentOfCall(node); - inferTypes(context.inferences, getThisArgumentType(thisArgumentNode), thisType); + // allow null as jsxFragmentFactory + let jsxFactorySym: Symbol | undefined; + if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { + jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, jsxFactoryNamespace, /*isUse*/ true); } - for (let i = 0; i < argCount; i++) { - const arg = args[i]; - if (arg.kind !== SyntaxKind.OmittedExpression) { - const paramType = getTypeAtPosition(signature, i); - const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode); - inferTypes(context.inferences, argType, paramType); + if (jsxFactorySym) { + // Mark local symbol as referenced here because it might not have been marked + // if jsx emit was not jsxFactory as there wont be error being emitted + jsxFactorySym.isReferenced = SymbolFlags.All; + + // If react/jsxFactory symbol is alias, mark it as refereced + if (jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { + markAliasSymbolAsReferenced(jsxFactorySym); } } - if (restType) { - const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, context, checkMode); - inferTypes(context.inferences, spreadType, restType); + // For JsxFragment, mark jsx pragma as referenced via resolveName + if (isJsxOpeningFragment(node)) { + const file = getSourceFileOfNode(node); + const localJsxNamespace = getLocalJsxNamespace(file); + if (localJsxNamespace) { + resolveName(jsxFactoryLocation, localJsxNamespace, SymbolFlags.Value, jsxFactoryRefErr, localJsxNamespace, /*isUse*/ true); + } } - - return getInferredTypes(context); } - function getMutableArrayOrTupleType(type: Type) { - return type.flags & TypeFlags.Union ? mapType(type, getMutableArrayOrTupleType) : - type.flags & TypeFlags.Any || isMutableArrayOrTuple(getBaseConstraintOfType(type) || type) ? type : - isTupleType(type) ? createTupleType(getTypeArguments(type), type.target.elementFlags, /*readonly*/ false, type.target.labeledElementDeclarations) : - createTupleType([type], [ElementFlags.Variadic]); + if (isNodeOpeningLikeElement) { + const jsxOpeningLikeNode = node as JsxOpeningLikeElement; + const sig = getResolvedSignature(jsxOpeningLikeNode); + checkDeprecatedSignature(sig, node as JsxOpeningLikeElement); + checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode); } + } - function getSpreadArgumentType(args: readonly Expression[], index: number, argCount: number, restType: Type, context: InferenceContext | undefined, checkMode: CheckMode) { - if (index >= argCount - 1) { - const arg = args[argCount - 1]; - if (isSpreadArgument(arg)) { - // We are inferring from a spread expression in the last argument position, i.e. both the parameter - // and the argument are ...x forms. - return getMutableArrayOrTupleType(arg.kind === SyntaxKind.SyntheticExpression ? (arg as SyntheticExpression).type : - checkExpressionWithContextualType((arg as SpreadElement).expression, restType, context, checkMode)); - } - } - const types = []; - const flags = []; - const names = []; - for (let i = index; i < argCount; i++) { - const arg = args[i]; - if (isSpreadArgument(arg)) { - const spreadType = arg.kind === SyntaxKind.SyntheticExpression ? (arg as SyntheticExpression).type : checkExpression((arg as SpreadElement).expression); - if (isArrayLikeType(spreadType)) { - types.push(spreadType); - flags.push(ElementFlags.Variadic); - } - else { - types.push(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, arg.kind === SyntaxKind.SpreadElement ? (arg as SpreadElement).expression : arg)); - flags.push(ElementFlags.Rest); - } - } - else { - const contextualType = getIndexedAccessType(restType, getNumberLiteralType(i - index), AccessFlags.Contextual); - const argType = checkExpressionWithContextualType(arg, contextualType, context, checkMode); - const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping); - types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType)); - flags.push(ElementFlags.Required); - } - if (arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).tupleNameSource) { - names.push((arg as SyntheticExpression).tupleNameSource!); - } + /** + * Check if a property with the given name is known anywhere in the given type. In an object type, a property + * is considered known if + * 1. the object type is empty and the check is for assignability, or + * 2. if the object type has index signatures, or + * 3. if the property is actually declared in the object type + * (this means that 'toString', for example, is not usually a known property). + * 4. In a union or intersection type, + * a property is considered known if it is known in any constituent type. + * @param targetType a type to search a given name in + * @param name a property name to search + * @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType + */ + function isKnownProperty(targetType: Type, name: __String, isComparingJsxAttributes: boolean): boolean { + if (targetType.flags & TypeFlags.Object) { + // For backwards compatibility a symbol-named property is satisfied by a string index signature. This + // is incorrect and inconsistent with element access expressions, where it is an error, so eventually + // we should remove this exception. + if (getPropertyOfObjectType(targetType, name) || + getApplicableIndexInfoForName(targetType, name) || + isLateBoundName(name) && getIndexInfoOfType(targetType, stringType) || + isComparingJsxAttributes && isHyphenatedJsxName(name)) { + // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known. + return true; } - return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined); } - - function checkTypeArguments(signature: Signature, typeArgumentNodes: readonly TypeNode[], reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined { - const isJavascript = isInJSFile(signature.declaration); - const typeParameters = signature.typeParameters!; - const typeArgumentTypes = fillMissingTypeArguments(map(typeArgumentNodes, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isJavascript); - let mapper: TypeMapper | undefined; - for (let i = 0; i < typeArgumentNodes.length; i++) { - Debug.assert(typeParameters[i] !== undefined, "Should not call checkTypeArguments with too many type arguments"); - const constraint = getConstraintOfTypeParameter(typeParameters[i]); - if (constraint) { - const errorInfo = reportErrors && headMessage ? (() => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1)) : undefined; - const typeArgumentHeadMessage = headMessage || Diagnostics.Type_0_does_not_satisfy_the_constraint_1; - if (!mapper) { - mapper = createTypeMapper(typeParameters, typeArgumentTypes); - } - const typeArgument = typeArgumentTypes[i]; - if (!checkTypeAssignableTo( - typeArgument, - getTypeWithThisArgument(instantiateType(constraint, mapper), typeArgument), - reportErrors ? typeArgumentNodes[i] : undefined, - typeArgumentHeadMessage, - errorInfo)) { - return undefined; - } + else if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) { + for (const t of (targetType as UnionOrIntersectionType).types) { + if (isKnownProperty(t, name, isComparingJsxAttributes)) { + return true; } } - return typeArgumentTypes; } + return false; + } - function getJsxReferenceKind(node: JsxOpeningLikeElement): JsxReferenceKind { - if (isJsxIntrinsicIdentifier(node.tagName)) { - return JsxReferenceKind.Mixed; - } - const tagType = getApparentType(checkExpression(node.tagName)); - if (length(getSignaturesOfType(tagType, SignatureKind.Construct))) { - return JsxReferenceKind.Component; - } - if (length(getSignaturesOfType(tagType, SignatureKind.Call))) { - return JsxReferenceKind.Function; + function isExcessPropertyCheckTarget(type: Type): boolean { + return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) || + type.flags & TypeFlags.NonPrimitive || + type.flags & TypeFlags.Union && some((type as UnionType).types, isExcessPropertyCheckTarget) || + type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isExcessPropertyCheckTarget)); + } + + function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) { + checkGrammarJsxExpression(node); + if (node.expression) { + const type = checkExpression(node.expression, checkMode); + if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) { + error(node, Diagnostics.JSX_spread_child_must_be_an_array_type); } - return JsxReferenceKind.Mixed; + return type; + } + else { + return errorType; } + } - /** - * Check if the given signature can possibly be a signature called by the JSX opening-like element. - * @param node a JSX opening-like element we are trying to figure its call signature - * @param signature a candidate signature we are trying whether it is a call signature - * @param relation a relationship to check parameter and argument type - */ - function checkApplicableSignatureForJsxOpeningLikeElement( - node: JsxOpeningLikeElement, - signature: Signature, - relation: ESMap, - checkMode: CheckMode, - reportErrors: boolean, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } - ) { - // Stateless function components can have maximum of three arguments: "props", "context", and "updater". - // However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props, - // can be specified by users through attributes property. - const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); - const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode); - return checkTagNameDoesNotExpectTooManyArguments() && checkTypeRelatedToAndOptionallyElaborate( - attributesType, - paramType, - relation, - reportErrors ? node.tagName : undefined, - node.attributes, - /*headMessage*/ undefined, - containingMessageChain, - errorOutputContainer); - - function checkTagNameDoesNotExpectTooManyArguments(): boolean { - if (getJsxNamespaceContainerForImplicitImport(node)) { - return true; // factory is implicitly jsx/jsxdev - assume it fits the bill, since we don't strongly look for the jsx/jsxs/jsxDEV factory APIs anywhere else (at least not yet) - } - const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicIdentifier(node.tagName) ? checkExpression(node.tagName) : undefined; - if (!tagType) { - return true; - } - const tagCallSignatures = getSignaturesOfType(tagType, SignatureKind.Call); - if (!length(tagCallSignatures)) { - return true; - } - const factory = getJsxFactoryEntity(node); - if (!factory) { - return true; - } - const factorySymbol = resolveEntityName(factory, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, node); - if (!factorySymbol) { - return true; - } + function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { + return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0; + } - const factoryType = getTypeOfSymbol(factorySymbol); - const callSignatures = getSignaturesOfType(factoryType, SignatureKind.Call); - if (!length(callSignatures)) { - return true; - } + /** + * Return whether this symbol is a member of a prototype somewhere + * Note that this is not tracked well within the compiler, so the answer may be incorrect. + */ + function isPrototypeProperty(symbol: Symbol) { + if (symbol.flags & SymbolFlags.Method || getCheckFlags(symbol) & CheckFlags.SyntheticMethod) { + return true; + } + if (isInJSFile(symbol.valueDeclaration)) { + const parent = symbol.valueDeclaration!.parent; + return parent && isBinaryExpression(parent) && + getAssignmentDeclarationKind(parent) === AssignmentDeclarationKind.PrototypeProperty; + } + } - let hasFirstParamSignatures = false; - let maxParamCount = 0; - // Check that _some_ first parameter expects a FC-like thing, and that some overload of the SFC expects an acceptable number of arguments - for (const sig of callSignatures) { - const firstparam = getTypeAtPosition(sig, 0); - const signaturesOfParam = getSignaturesOfType(firstparam, SignatureKind.Call); - if (!length(signaturesOfParam)) continue; - for (const paramSig of signaturesOfParam) { - hasFirstParamSignatures = true; - if (hasEffectiveRestParameter(paramSig)) { - return true; // some signature has a rest param, so function components can have an arbitrary number of arguments - } - const paramCount = getParameterCount(paramSig); - if (paramCount > maxParamCount) { - maxParamCount = paramCount; - } - } - } - if (!hasFirstParamSignatures) { - // Not a single signature had a first parameter which expected a signature - for back compat, and - // to guard against generic factories which won't have signatures directly, do not error - return true; - } - let absoluteMinArgCount = Infinity; - for (const tagSig of tagCallSignatures) { - const tagRequiredArgCount = getMinArgumentCount(tagSig); - if (tagRequiredArgCount < absoluteMinArgCount) { - absoluteMinArgCount = tagRequiredArgCount; + /** + * Check whether the requested property access is valid. + * Returns true if node is a valid property access, and false otherwise. + * @param node The node to be checked. + * @param isSuper True if the access is from `super.`. + * @param type The type of the object whose property is being accessed. (Not the type of the property.) + * @param prop The symbol for the property being accessed. + */ + function checkPropertyAccessibility( + node: PropertyAccessExpression | QualifiedName | PropertyAccessExpression | VariableDeclaration | ParameterDeclaration | ImportTypeNode | PropertyAssignment | ShorthandPropertyAssignment | BindingElement, + isSuper: boolean, writing: boolean, type: Type, prop: Symbol, reportError = true): boolean { + + const errorNode = !reportError ? undefined : + node.kind === SyntaxKind.QualifiedName ? node.right : + node.kind === SyntaxKind.ImportType ? node : + node.kind === SyntaxKind.BindingElement && node.propertyName ? node.propertyName : node.name; + + return checkPropertyAccessibilityAtLocation(node, isSuper, writing, type, prop, errorNode); + } + + /** + * Check whether the requested property can be accessed at the requested location. + * Returns true if node is a valid property access, and false otherwise. + * @param location The location node where we want to check if the property is accessible. + * @param isSuper True if the access is from `super.`. + * @param writing True if this is a write property access, false if it is a read property access. + * @param containingType The type of the object whose property is being accessed. (Not the type of the property.) + * @param prop The symbol for the property being accessed. + * @param errorNode The node where we should report an invalid property access error, or undefined if we should not report errors. + */ + function checkPropertyAccessibilityAtLocation(location: Node, + isSuper: boolean, writing: boolean, + containingType: Type, prop: Symbol, errorNode?: Node): boolean { + + const flags = getDeclarationModifierFlagsFromSymbol(prop, writing); + + if (isSuper) { + // TS 1.0 spec (April 2014): 4.8.2 + // - In a constructor, instance member function, instance member accessor, or + // instance member variable initializer where this references a derived class instance, + // a super property access is permitted and must specify a public instance member function of the base class. + // - In a static member function or static member accessor + // where this references the constructor function object of a derived class, + // a super property access is permitted and must specify a public static member function of the base class. + if (languageVersion < ScriptTarget.ES2015) { + if (symbolHasNonMethodDeclaration(prop)) { + if (errorNode) { + error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword); } + return false; } - if (absoluteMinArgCount <= maxParamCount) { - return true; // some signature accepts the number of arguments the function component provides + } + if (flags & ModifierFlags.Abstract) { + // A method cannot be accessed in a super property access if the method is abstract. + // This error could mask a private property access error. But, a member + // cannot simultaneously be private and abstract, so this will trigger an + // additional error elsewhere. + if (errorNode) { + error(errorNode, + Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, + symbolToString(prop), + typeToString(getDeclaringClass(prop)!)); } + return false; + } + } - if (reportErrors) { - const diag = createDiagnosticForNode(node.tagName, Diagnostics.Tag_0_expects_at_least_1_arguments_but_the_JSX_factory_2_provides_at_most_3, entityNameToString(node.tagName), absoluteMinArgCount, entityNameToString(factory), maxParamCount); - const tagNameDeclaration = getSymbolAtLocation(node.tagName)?.valueDeclaration; - if (tagNameDeclaration) { - addRelatedInfo(diag, createDiagnosticForNode(tagNameDeclaration, Diagnostics._0_is_declared_here, entityNameToString(node.tagName))); - } - if (errorOutputContainer && errorOutputContainer.skipLogging) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); - } - if (!errorOutputContainer.skipLogging) { - diagnostics.add(diag); - } + // Referencing abstract properties within their own constructors is not allowed + if ((flags & ModifierFlags.Abstract) && symbolHasNonMethodDeclaration(prop) && + (isThisProperty(location) || isThisInitializedObjectBindingExpression(location) || isObjectBindingPattern(location.parent) && isThisInitializedDeclaration(location.parent.parent))) { + const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!); + if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(location)) { + if (errorNode) { + error(errorNode, + Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, + symbolToString(prop), + getTextOfIdentifierOrLiteral(declaringClassDeclaration.name!)); } return false; } } - function getSignatureApplicabilityError( - node: CallLikeExpression, - args: readonly Expression[], - signature: Signature, - relation: ESMap, - checkMode: CheckMode, - reportErrors: boolean, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - ): readonly Diagnostic[] | undefined { + // Public properties are otherwise accessible. + if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) { + return true; + } + + // Property is known to be private or protected at this point - const errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } = { errors: undefined, skipLogging: true }; - if (isJsxOpeningLikeElement(node)) { - if (!checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors, containingMessageChain, errorOutputContainer)) { - Debug.assert(!reportErrors || !!errorOutputContainer.errors, "jsx should have errors when reporting errors"); - return errorOutputContainer.errors || emptyArray; - } - return undefined; - } - const thisType = getThisTypeOfSignature(signature); - if (thisType && thisType !== voidType && node.kind !== SyntaxKind.NewExpression) { - // If the called expression is not of the form `x.f` or `x["f"]`, then sourceType = voidType - // If the signature's 'this' type is voidType, then the check is skipped -- anything is compatible. - // If the expression is a new expression, then the check is skipped. - const thisArgumentNode = getThisArgumentOfCall(node); - const thisArgumentType = getThisArgumentType(thisArgumentNode); - const errorNode = reportErrors ? (thisArgumentNode || node) : undefined; - const headMessage = Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1; - if (!checkTypeRelatedTo(thisArgumentType, thisType, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer)) { - Debug.assert(!reportErrors || !!errorOutputContainer.errors, "this parameter should have errors when reporting errors"); - return errorOutputContainer.errors || emptyArray; + // Private property is accessible if the property is within the declaring class + if (flags & ModifierFlags.Private) { + const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!)!; + if (!isNodeWithinClass(location, declaringClassDeclaration)) { + if (errorNode) { + error(errorNode, + Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, + symbolToString(prop), + typeToString(getDeclaringClass(prop)!)); } + return false; } - const headMessage = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1; - const restType = getNonArrayRestType(signature); - const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; - for (let i = 0; i < argCount; i++) { - const arg = args[i]; - if (arg.kind !== SyntaxKind.OmittedExpression) { - const paramType = getTypeAtPosition(signature, i); - const argType = checkExpressionWithContextualType(arg, paramType, /*inferenceContext*/ undefined, checkMode); - // If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive), - // we obtain the regular type of any object literal arguments because we may not have inferred complete - // parameter types yet and therefore excess property checks may yield false positives (see #17041). - const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; - if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? arg : undefined, arg, headMessage, containingMessageChain, errorOutputContainer)) { - Debug.assert(!reportErrors || !!errorOutputContainer.errors, "parameter should have errors when reporting errors"); - maybeAddMissingAwaitInfo(arg, checkArgType, paramType); - return errorOutputContainer.errors || emptyArray; - } - } - } - if (restType) { - const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, /*context*/ undefined, checkMode); - const restArgCount = args.length - argCount; - const errorNode = !reportErrors ? undefined : - restArgCount === 0 ? node : - restArgCount === 1 ? args[argCount] : - setTextRangePosEnd(createSyntheticExpression(node, spreadType), args[argCount].pos, args[args.length - 1].end); - if (!checkTypeRelatedTo(spreadType, restType, relation, errorNode, headMessage, /*containingMessageChain*/ undefined, errorOutputContainer)) { - Debug.assert(!reportErrors || !!errorOutputContainer.errors, "rest parameter should have errors when reporting errors"); - maybeAddMissingAwaitInfo(errorNode, spreadType, restType); - return errorOutputContainer.errors || emptyArray; + return true; + } + + // Property is known to be protected at this point + + // All protected properties of a supertype are accessible in a super access + if (isSuper) { + return true; + } + + // Find the first enclosing class that has the declaring classes of the protected constituents + // of the property as base classes + let enclosingClass = forEachEnclosingClass(location, enclosingDeclaration => { + const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!) as InterfaceType; + return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing) ? enclosingClass : undefined; + }); + // A protected property is accessible if the property is within the declaring class or classes derived from it + if (!enclosingClass) { + // allow PropertyAccessibility if context is in function with this parameter + // static member access is disallow + let thisParameter: ParameterDeclaration | undefined; + if (flags & ModifierFlags.Static || !(thisParameter = getThisParameterFromNodeContext(location)) || !thisParameter.type) { + if (errorNode) { + error(errorNode, + Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, + symbolToString(prop), + typeToString(getDeclaringClass(prop) || containingType)); } + return false; } - return undefined; - function maybeAddMissingAwaitInfo(errorNode: Node | undefined, source: Type, target: Type) { - if (errorNode && reportErrors && errorOutputContainer.errors && errorOutputContainer.errors.length) { - // Bail if target is Promise-like---something else is wrong - if (getAwaitedTypeOfPromise(target)) { - return; - } - const awaitedTypeOfSource = getAwaitedTypeOfPromise(source); - if (awaitedTypeOfSource && isTypeRelatedTo(awaitedTypeOfSource, target, relation)) { - addRelatedInfo(errorOutputContainer.errors[0], createDiagnosticForNode(errorNode, Diagnostics.Did_you_forget_to_use_await)); - } - } + const thisType = getTypeFromTypeNode(thisParameter.type); + enclosingClass = (((thisType.flags & TypeFlags.TypeParameter) ? getConstraintOfTypeParameter(thisType as TypeParameter) : thisType) as TypeReference).target; + } + // No further restrictions for static properties + if (flags & ModifierFlags.Static) { + return true; + } + if (containingType.flags & TypeFlags.TypeParameter) { + // get the original type -- represented as the type constraint of the 'this' type + containingType = (containingType as TypeParameter).isThisType ? getConstraintOfTypeParameter(containingType as TypeParameter)! : getBaseConstraintOfType(containingType as TypeParameter)!; // TODO: GH#18217 Use a different variable that's allowed to be undefined + } + if (!containingType || !hasBaseType(containingType, enclosingClass)) { + if (errorNode) { + error(errorNode, + Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1_This_is_an_instance_of_class_2, + symbolToString(prop), typeToString(enclosingClass), typeToString(containingType)); } + return false; } + return true; + } - /** - * Returns the this argument in calls like x.f(...) and x[f](...). Undefined otherwise. - */ - function getThisArgumentOfCall(node: CallLikeExpression): LeftHandSideExpression | undefined { - const expression = node.kind === SyntaxKind.CallExpression ? node.expression : - node.kind === SyntaxKind.TaggedTemplateExpression ? node.tag : undefined; - if (expression) { - const callee = skipOuterExpressions(expression); - if (isAccessExpression(callee)) { - return callee.expression; - } - } + function getThisParameterFromNodeContext(node: Node) { + const thisContainer = getThisContainer(node, /* includeArrowFunctions */ false); + return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined; + } + + function symbolHasNonMethodDeclaration(symbol: Symbol) { + return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method)); + } + + function checkNonNullExpression(node: Expression | QualifiedName) { + return checkNonNullType(checkExpression(node), node); + } + + function isNullableType(type: Type) { + return !!((strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable); + } + + function getNonNullableTypeIfNeeded(type: Type) { + return isNullableType(type) ? getNonNullableType(type) : type; + } + + function reportObjectPossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) { + error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ? + Diagnostics.Object_is_possibly_null_or_undefined : + Diagnostics.Object_is_possibly_undefined : + Diagnostics.Object_is_possibly_null + ); + } + + function reportCannotInvokePossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) { + error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ? + Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined : + Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined : + Diagnostics.Cannot_invoke_an_object_which_is_possibly_null + ); + } + + function checkNonNullTypeWithReporter( + type: Type, + node: Node, + reportError: (node: Node, kind: TypeFlags) => void + ): Type { + if (strictNullChecks && type.flags & TypeFlags.Unknown) { + error(node, Diagnostics.Object_is_of_type_unknown); + return errorType; + } + const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable; + if (kind) { + reportError(node, kind); + const t = getNonNullableType(type); + return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t; } + return type; + } - function createSyntheticExpression(parent: Node, type: Type, isSpread?: boolean, tupleNameSource?: ParameterDeclaration | NamedTupleMember) { - const result = parseNodeFactory.createSyntheticExpression(type, isSpread, tupleNameSource); - setTextRange(result, parent); - setParent(result, parent); - return result; + function checkNonNullType(type: Type, node: Node) { + return checkNonNullTypeWithReporter(type, node, reportObjectPossiblyNullOrUndefinedError); + } + + function checkNonNullNonVoidType(type: Type, node: Node): Type { + const nonNullType = checkNonNullType(type, node); + if (nonNullType.flags & TypeFlags.Void) { + error(node, Diagnostics.Object_is_possibly_undefined); } + return nonNullType; + } - /** - * Returns the effective arguments for an expression that works like a function invocation. - */ - function getEffectiveCallArguments(node: CallLikeExpression): readonly Expression[] { - if (node.kind === SyntaxKind.TaggedTemplateExpression) { - const template = node.template; - const args: Expression[] = [createSyntheticExpression(template, getGlobalTemplateStringsArrayType())]; - if (template.kind === SyntaxKind.TemplateExpression) { - forEach(template.templateSpans, span => { - args.push(span.expression); - }); - } - return args; - } - if (node.kind === SyntaxKind.Decorator) { - return getEffectiveDecoratorArguments(node); - } - if (isJsxOpeningLikeElement(node)) { - return node.attributes.properties.length > 0 || (isJsxOpeningElement(node) && node.parent.children.length > 0) ? [node.attributes] : emptyArray; - } - const args = node.arguments || emptyArray; - const spreadIndex = getSpreadArgumentIndex(args); - if (spreadIndex >= 0) { - // Create synthetic arguments from spreads of tuple types. - const effectiveArgs = args.slice(0, spreadIndex); - for (let i = spreadIndex; i < args.length; i++) { - const arg = args[i]; - // We can call checkExpressionCached because spread expressions never have a contextual type. - const spreadType = arg.kind === SyntaxKind.SpreadElement && (flowLoopCount ? checkExpression((arg as SpreadElement).expression) : checkExpressionCached((arg as SpreadElement).expression)); - if (spreadType && isTupleType(spreadType)) { - forEach(getTypeArguments(spreadType), (t, i) => { - const flags = spreadType.target.elementFlags[i]; - const syntheticArg = createSyntheticExpression(arg, flags & ElementFlags.Rest ? createArrayType(t) : t, - !!(flags & ElementFlags.Variable), spreadType.target.labeledElementDeclarations?.[i]); - effectiveArgs.push(syntheticArg); - }); - } - else { - effectiveArgs.push(arg); - } - } - return effectiveArgs; - } - return args; + function checkPropertyAccessExpression(node: PropertyAccessExpression, checkMode: CheckMode | undefined) { + return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain, checkMode) : + checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name, checkMode); + } + + function checkPropertyAccessChain(node: PropertyAccessChain, checkMode: CheckMode | undefined) { + const leftType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(leftType, node.expression); + return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name, checkMode), node, nonOptionalType !== leftType); + } + + function checkQualifiedName(node: QualifiedName, checkMode: CheckMode | undefined) { + const leftType = isPartOfTypeQuery(node) && isThisIdentifier(node.left) ? checkNonNullType(checkThisExpression(node.left), node.left) : checkNonNullExpression(node.left); + return checkPropertyAccessExpressionOrQualifiedName(node, node.left, leftType, node.right, checkMode); + } + + function isMethodAccessForCall(node: Node) { + while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { + node = node.parent; } + return isCallOrNewExpression(node.parent) && node.parent.expression === node; + } - /** - * Returns the synthetic argument list for a decorator invocation. - */ - function getEffectiveDecoratorArguments(node: Decorator): readonly Expression[] { - const parent = node.parent; - const expr = node.expression; - switch (parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - // For a class decorator, the `target` is the type of the class (e.g. the - // "static" or "constructor" side of the class). - return [ - createSyntheticExpression(expr, getTypeOfSymbol(getSymbolOfNode(parent))) - ]; - case SyntaxKind.Parameter: - // A parameter declaration decorator will have three arguments (see - // `ParameterDecorator` in core.d.ts). - const func = parent.parent as FunctionLikeDeclaration; - return [ - createSyntheticExpression(expr, parent.parent.kind === SyntaxKind.Constructor ? getTypeOfSymbol(getSymbolOfNode(func)) : errorType), - createSyntheticExpression(expr, anyType), - createSyntheticExpression(expr, numberType) - ]; - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // A method or accessor declaration decorator will have two or three arguments (see - // `PropertyDecorator` and `MethodDecorator` in core.d.ts). If we are emitting decorators - // for ES3, we will only pass two arguments. - const hasPropDesc = parent.kind !== SyntaxKind.PropertyDeclaration && languageVersion !== ScriptTarget.ES3; - return [ - createSyntheticExpression(expr, getParentTypeOfClassElement(parent as ClassElement)), - createSyntheticExpression(expr, getClassElementPropertyKeyType(parent as ClassElement)), - createSyntheticExpression(expr, hasPropDesc ? createTypedPropertyDescriptorType(getTypeOfNode(parent)) : anyType) - ]; + // Lookup the private identifier lexically. + function lookupSymbolForPrivateIdentifierDeclaration(propName: __String, location: Node): Symbol | undefined { + for (let containingClass = getContainingClass(location); !!containingClass; containingClass = getContainingClass(containingClass)) { + const { symbol } = containingClass; + const name = getSymbolNameForPrivateIdentifier(symbol, propName); + const prop = (symbol.members && symbol.members.get(name)) || (symbol.exports && symbol.exports.get(name)); + if (prop) { + return prop; } - return Debug.fail(); } + } - /** - * Returns the argument count for a decorator node that works like a function invocation. - */ - function getDecoratorArgumentCount(node: Decorator, signature: Signature) { - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return 1; - case SyntaxKind.PropertyDeclaration: - return 2; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // For ES3 or decorators with only two parameters we supply only two arguments - return languageVersion === ScriptTarget.ES3 || signature.parameters.length <= 2 ? 2 : 3; - case SyntaxKind.Parameter: - return 3; - default: - return Debug.fail(); - } + function checkGrammarPrivateIdentifierExpression(privId: PrivateIdentifier): boolean { + if (!getContainingClass(privId)) { + return grammarErrorOnNode(privId, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); } - function getDiagnosticSpanForCallNode(node: CallExpression, doNotIncludeArguments?: boolean) { - let start: number; - let length: number; - const sourceFile = getSourceFileOfNode(node); + if (!isExpressionNode(privId)) { + return grammarErrorOnNode(privId, Diagnostics.Private_identifiers_are_only_allowed_in_class_bodies_and_may_only_be_used_as_part_of_a_class_member_declaration_property_access_or_on_the_left_hand_side_of_an_in_expression); + } + if (!getSymbolForPrivateIdentifierExpression(privId)) { + return grammarErrorOnNode(privId, Diagnostics.Cannot_find_name_0, idText(privId)); + } + return false; + } - if (isPropertyAccessExpression(node.expression)) { - const nameSpan = getErrorSpanForNode(sourceFile, node.expression.name); - start = nameSpan.start; - length = doNotIncludeArguments ? nameSpan.length : node.end - start; - } - else { - const expressionSpan = getErrorSpanForNode(sourceFile, node.expression); - start = expressionSpan.start; - length = doNotIncludeArguments ? expressionSpan.length : node.end - start; - } - return { start, length, sourceFile }; + function checkPrivateIdentifierExpression(privId: PrivateIdentifier): Type { + checkGrammarPrivateIdentifierExpression(privId); + const symbol = getSymbolForPrivateIdentifierExpression(privId); + if (symbol) { + markPropertyAsReferenced(symbol, /* nodeForCheckWriteOnly: */ undefined, /* isThisAccess: */ false); } - function getDiagnosticForCallNode(node: CallLikeExpression, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation { - if (isCallExpression(node)) { - const { sourceFile, start, length } = getDiagnosticSpanForCallNode(node); - return createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2, arg3); - } - else { - return createDiagnosticForNode(node, message, arg0, arg1, arg2, arg3); - } + return anyType; + } + + function getSymbolForPrivateIdentifierExpression(privId: PrivateIdentifier): Symbol | undefined { + if (!isExpressionNode(privId)) { + return undefined; } - function isPromiseResolveArityError(node: CallLikeExpression) { - if (!isCallExpression(node) || !isIdentifier(node.expression)) return false; + const links = getNodeLinks(privId); + if (links.resolvedSymbol === undefined) { + links.resolvedSymbol = lookupSymbolForPrivateIdentifierDeclaration(privId.escapedText, privId); + } + return links.resolvedSymbol; + } - const symbol = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, undefined, undefined, false); - const decl = symbol?.valueDeclaration; - if (!decl || !isParameter(decl) || !isFunctionExpressionOrArrowFunction(decl.parent) || !isNewExpression(decl.parent.parent) || !isIdentifier(decl.parent.parent.expression)) { - return false; - } + function getPrivateIdentifierPropertyOfType(leftType: Type, lexicallyScopedIdentifier: Symbol): Symbol | undefined { + return getPropertyOfType(leftType, lexicallyScopedIdentifier.escapedName); + } - const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); - if (!globalPromiseSymbol) return false; - - const constructorSymbol = getSymbolAtLocation(decl.parent.parent.expression, /*ignoreErrors*/ true); - return constructorSymbol === globalPromiseSymbol; - } - - function getArgumentArityError(node: CallLikeExpression, signatures: readonly Signature[], args: readonly Expression[]) { - const spreadIndex = getSpreadArgumentIndex(args); - if (spreadIndex > -1) { - return createDiagnosticForNode(args[spreadIndex], Diagnostics.A_spread_argument_must_either_have_a_tuple_type_or_be_passed_to_a_rest_parameter); - } - let min = Number.POSITIVE_INFINITY; // smallest parameter count - let max = Number.NEGATIVE_INFINITY; // largest parameter count - let maxBelow = Number.NEGATIVE_INFINITY; // largest parameter count that is smaller than the number of arguments - let minAbove = Number.POSITIVE_INFINITY; // smallest parameter count that is larger than the number of arguments - - let closestSignature: Signature | undefined; - for (const sig of signatures) { - const minParameter = getMinArgumentCount(sig); - const maxParameter = getParameterCount(sig); - // smallest/largest parameter counts - if (minParameter < min) { - min = minParameter; - closestSignature = sig; - } - max = Math.max(max, maxParameter); - // shortest parameter count *longer than the call*/longest parameter count *shorter than the call* - if (minParameter < args.length && minParameter > maxBelow) maxBelow = minParameter; - if (args.length < maxParameter && maxParameter < minAbove) minAbove = maxParameter; - } - const hasRestParameter = some(signatures, hasEffectiveRestParameter); - const parameterRange = hasRestParameter ? min - : min < max ? min + "-" + max - : min; - const error = hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 - : parameterRange === 1 && args.length === 0 && isPromiseResolveArityError(node) ? Diagnostics.Expected_0_arguments_but_got_1_Did_you_forget_to_include_void_in_your_type_argument_to_Promise - : Diagnostics.Expected_0_arguments_but_got_1; - if (min < args.length && args.length < max) { - // between min and max, but with no matching overload - return getDiagnosticForCallNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, args.length, maxBelow, minAbove); - } - else if (args.length < min) { - // too short: put the error span on the call expression, not any of the args - const diagnostic = getDiagnosticForCallNode(node, error, parameterRange, args.length); - const parameter = closestSignature?.declaration?.parameters[closestSignature.thisParameter ? args.length + 1 : args.length]; - if (parameter) { - const parameterError = createDiagnosticForNode( - parameter, - isBindingPattern(parameter.name) ? Diagnostics.An_argument_matching_this_binding_pattern_was_not_provided - : isRestParameter(parameter) ? Diagnostics.Arguments_for_the_rest_parameter_0_were_not_provided - : Diagnostics.An_argument_for_0_was_not_provided, - !parameter.name ? args.length : !isBindingPattern(parameter.name) ? idText(getFirstIdentifier(parameter.name)) : undefined - ); - return addRelatedInfo(diagnostic, parameterError); - } - return diagnostic; - } - else { - // too long; error goes on the excess parameters - const errorSpan = factory.createNodeArray(args.slice(max)); - const pos = first(errorSpan).pos; - let end = last(errorSpan).end; - if (end === pos) { - end++; + function checkPrivateIdentifierPropertyAccess(leftType: Type, right: PrivateIdentifier, lexicallyScopedIdentifier: Symbol | undefined): boolean { + // Either the identifier could not be looked up in the lexical scope OR the lexically scoped identifier did not exist on the type. + // Find a private identifier with the same description on the type. + let propertyOnType: Symbol | undefined; + const properties = getPropertiesOfType(leftType); + if (properties) { + forEach(properties, (symbol: Symbol) => { + const decl = symbol.valueDeclaration; + if (decl && isNamedDeclaration(decl) && isPrivateIdentifier(decl.name) && decl.name.escapedText === right.escapedText) { + propertyOnType = symbol; + return true; } - setTextRangePosEnd(errorSpan, pos, end); - return createDiagnosticForNodeArray(getSourceFileOfNode(node), errorSpan, error, parameterRange, args.length); - } + }); } + const diagName = diagnosticName(right); + if (propertyOnType) { + const typeValueDecl = Debug.checkDefined(propertyOnType.valueDeclaration); + const typeClass = Debug.checkDefined(getContainingClass(typeValueDecl)); + // We found a private identifier property with the same description. + // Either: + // - There is a lexically scoped private identifier AND it shadows the one we found on the type. + // - It is an attempt to access the private identifier outside of the class. + if (lexicallyScopedIdentifier?.valueDeclaration) { + const lexicalValueDecl = lexicallyScopedIdentifier.valueDeclaration; + const lexicalClass = getContainingClass(lexicalValueDecl); + Debug.assert(!!lexicalClass); + if (findAncestor(lexicalClass, n => typeClass === n)) { + const diagnostic = error( + right, + Diagnostics.The_property_0_cannot_be_accessed_on_type_1_within_this_class_because_it_is_shadowed_by_another_private_identifier_with_the_same_spelling, + diagName, + typeToString(leftType) + ); - function getTypeArgumentArityError(node: Node, signatures: readonly Signature[], typeArguments: NodeArray) { - const argCount = typeArguments.length; - // No overloads exist - if (signatures.length === 1) { - const sig = signatures[0]; - const min = getMinTypeArgumentCount(sig.typeParameters); - const max = length(sig.typeParameters); - return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, min < max ? min + "-" + max : min , argCount); - } - // Overloads exist - let belowArgCount = -Infinity; - let aboveArgCount = Infinity; - for (const sig of signatures) { - const min = getMinTypeArgumentCount(sig.typeParameters); - const max = length(sig.typeParameters); - if (min > argCount) { - aboveArgCount = Math.min(aboveArgCount, min); - } - else if (max < argCount) { - belowArgCount = Math.max(belowArgCount, max); + addRelatedInfo( + diagnostic, + createDiagnosticForNode( + lexicalValueDecl, + Diagnostics.The_shadowing_declaration_of_0_is_defined_here, + diagName + ), + createDiagnosticForNode( + typeValueDecl, + Diagnostics.The_declaration_of_0_that_you_probably_intended_to_use_is_defined_here, + diagName + ) + ); + return true; } } - if (belowArgCount !== -Infinity && aboveArgCount !== Infinity) { - return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.No_overload_expects_0_type_arguments_but_overloads_do_exist_that_expect_either_1_or_2_type_arguments, argCount, belowArgCount, aboveArgCount); - } - return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); + error( + right, + Diagnostics.Property_0_is_not_accessible_outside_class_1_because_it_has_a_private_identifier, + diagName, + diagnosticName(typeClass.name || anon) + ); + return true; } + return false; + } - function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, fallbackError?: DiagnosticMessage): Signature { - const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; - const isDecorator = node.kind === SyntaxKind.Decorator; - const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); - const reportErrors = !candidatesOutArray && produceDiagnostics; - - let typeArguments: NodeArray | undefined; - - if (!isDecorator) { - typeArguments = (node as CallExpression).typeArguments; + function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) { + return (isConstructorDeclaredProperty(prop) || isThisProperty(node) && isAutoTypedProperty(prop)) + && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop); + } - // We already perform checking on the type arguments on the class declaration itself. - if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node as CallExpression).expression.kind !== SyntaxKind.SuperKeyword) { - forEach(typeArguments, checkSourceElement); + function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier, checkMode: CheckMode | undefined) { + const parentSymbol = getNodeLinks(left).resolvedSymbol; + const assignmentKind = getAssignmentTargetKind(node); + const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); + const isAnyLike = isTypeAny(apparentType) || apparentType === silentNeverType; + let prop: Symbol | undefined; + if (isPrivateIdentifier(right)) { + if (languageVersion < ScriptTarget.ESNext) { + if (assignmentKind !== AssignmentKind.None) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldSet); } - } - - const candidates = candidatesOutArray || []; - // reorderCandidates fills up the candidates array directly - reorderCandidates(signatures, candidates, callChainFlags); - if (!candidates.length) { - if (reportErrors) { - diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures)); + if (assignmentKind !== AssignmentKind.Definite) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldGet); } - return resolveErrorCall(node); } - const args = getEffectiveCallArguments(node); - - // The excludeArgument array contains true for each context sensitive argument (an argument - // is context sensitive it is susceptible to a one-time permanent contextual typing). - // - // The idea is that we will perform type argument inference & assignability checking once - // without using the susceptible parameters that are functions, and once more for those - // parameters, contextually typing each as we go along. - // - // For a tagged template, then the first argument be 'undefined' if necessary because it - // represents a TemplateStringsArray. - // - // For a decorator, no arguments are susceptible to contextual typing due to the fact - // decorators are applied to a declaration by the emitter, and not to an expression. - const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters; - let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal; - - // The following variables are captured and modified by calls to chooseOverload. - // If overload resolution or type argument inference fails, we want to report the - // best error possible. The best error is one which says that an argument was not - // assignable to a parameter. This implies that everything else about the overload - // was fine. So if there is any overload that is only incorrect because of an - // argument, we will report an error on that one. - // - // function foo(s: string): void; - // function foo(n: number): void; // Report argument error on this overload - // function foo(): void; - // foo(true); - // - // If none of the overloads even made it that far, there are two possibilities. - // There was a problem with type arguments for some overload, in which case - // report an error on that. Or none of the overloads even had correct arity, - // in which case give an arity error. - // - // function foo(x: T): void; // Report type argument error - // function foo(): void; - // foo(0); - // - let candidatesForArgumentError: Signature[] | undefined; - let candidateForArgumentArityError: Signature | undefined; - let candidateForTypeArgumentError: Signature | undefined; - let result: Signature | undefined; - - // If we are in signature help, a trailing comma indicates that we intend to provide another argument, - // so we will only accept overloads with arity at least 1 higher than the current number of provided arguments. - const signatureHelpTrailingComma = - !!(checkMode & CheckMode.IsForSignatureHelp) && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma; - - // Section 4.12.1: - // if the candidate list contains one or more signatures for which the type of each argument - // expression is a subtype of each corresponding parameter type, the return type of the first - // of those signatures becomes the return type of the function call. - // Otherwise, the return type of the first signature in the candidate list becomes the return - // type of the function call. - // - // Whether the call is an error is determined by assignability of the arguments. The subtype pass - // is just important for choosing the best signature. So in the case where there is only one - // signature, the subtype pass is useless. So skipping it is an optimization. - if (candidates.length > 1) { - result = chooseOverload(candidates, subtypeRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); - } - if (!result) { - result = chooseOverload(candidates, assignableRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); - } - if (result) { - return result; + const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); + if (assignmentKind && lexicallyScopedSymbol && lexicallyScopedSymbol.valueDeclaration && isMethodDeclaration(lexicallyScopedSymbol.valueDeclaration)) { + grammarErrorOnNode(right, Diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable, idText(right)); } - // No signatures were applicable. Now report errors based on the last applicable signature with - // no arguments excluded from assignability checks. - // If candidate is undefined, it means that no candidates had a suitable arity. In that case, - // skip the checkApplicableSignature check. - if (reportErrors) { - if (candidatesForArgumentError) { - if (candidatesForArgumentError.length === 1 || candidatesForArgumentError.length > 3) { - const last = candidatesForArgumentError[candidatesForArgumentError.length - 1]; - let chain: DiagnosticMessageChain | undefined; - if (candidatesForArgumentError.length > 3) { - chain = chainDiagnosticMessages(chain, Diagnostics.The_last_overload_gave_the_following_error); - chain = chainDiagnosticMessages(chain, Diagnostics.No_overload_matches_this_call); - } - const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain); - if (diags) { - for (const d of diags) { - if (last.declaration && candidatesForArgumentError.length > 3) { - addRelatedInfo(d, createDiagnosticForNode(last.declaration, Diagnostics.The_last_overload_is_declared_here)); - } - addImplementationSuccessElaboration(last, d); - diagnostics.add(d); - } - } - else { - Debug.fail("No error for last overload signature"); - } - } - else { - const allDiagnostics: (readonly DiagnosticRelatedInformation[])[] = []; - let max = 0; - let min = Number.MAX_VALUE; - let minIndex = 0; - let i = 0; - for (const c of candidatesForArgumentError) { - const chain = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Overload_0_of_1_2_gave_the_following_error, i + 1, candidates.length, signatureToString(c)); - const diags = getSignatureApplicabilityError(node, args, c, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, chain); - if (diags) { - if (diags.length <= min) { - min = diags.length; - minIndex = i; - } - max = Math.max(max, diags.length); - allDiagnostics.push(diags); - } - else { - Debug.fail("No error for 3 or fewer overload signatures"); - } - i++; - } - - const diags = max > 1 ? allDiagnostics[minIndex] : flatten(allDiagnostics); - Debug.assert(diags.length > 0, "No errors reported for 3 or fewer overload signatures"); - const chain = chainDiagnosticMessages( - map(diags, d => typeof d.messageText === "string" ? (d as DiagnosticMessageChain) : d.messageText), - Diagnostics.No_overload_matches_this_call); - // The below is a spread to guarantee we get a new (mutable) array - our `flatMap` helper tries to do "smart" optimizations where it reuses input - // arrays and the emptyArray singleton where possible, which is decidedly not what we want while we're still constructing this diagnostic - const related = [...flatMap(diags, d => (d as Diagnostic).relatedInformation) as DiagnosticRelatedInformation[]]; - let diag: Diagnostic; - if (every(diags, d => d.start === diags[0].start && d.length === diags[0].length && d.file === diags[0].file)) { - const { file, start, length } = diags[0]; - diag = { file, start, length, code: chain.code, category: chain.category, messageText: chain, relatedInformation: related }; - } - else { - diag = createDiagnosticForNodeFromMessageChain(node, chain, related); - } - addImplementationSuccessElaboration(candidatesForArgumentError[0], diag); - diagnostics.add(diag); + if (lexicallyScopedSymbol?.valueDeclaration && (getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && !useDefineForClassFields)) { + const lexicalClass = getContainingClass(lexicallyScopedSymbol.valueDeclaration); + const parentStaticFieldInitializer = findAncestor(node, (n) => { + if (n === lexicalClass) return "quit"; + if (isPropertyDeclaration(n.parent) && hasStaticModifier(n.parent) && n.parent.initializer === n && n.parent.parent === lexicalClass) { + return true; } + return false; + }); + if (parentStaticFieldInitializer) { + const parentStaticFieldInitializerSymbol = getSymbolOfNode(parentStaticFieldInitializer.parent); + Debug.assert(parentStaticFieldInitializerSymbol, "Initializer without declaration symbol"); + const diagnostic = error(node, + Diagnostics.Property_0_may_not_be_used_in_a_static_property_s_initializer_in_the_same_class_when_target_is_esnext_and_useDefineForClassFields_is_false, + symbolName(lexicallyScopedSymbol)); + addRelatedInfo(diagnostic, + createDiagnosticForNode(parentStaticFieldInitializer.parent, + Diagnostics.Initializer_for_property_0, + symbolName(parentStaticFieldInitializerSymbol)) + ); } - else if (candidateForArgumentArityError) { - diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args)); + } + + if (isAnyLike) { + if (lexicallyScopedSymbol) { + return isErrorType(apparentType) ? errorType : apparentType; } - else if (candidateForTypeArgumentError) { - checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, fallbackError); + if (!getContainingClass(right)) { + grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + return anyType; } - else { - const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); - if (signaturesWithCorrectTypeArgumentArity.length === 0) { - diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!)); - } - else if (!isDecorator) { - diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args)); - } - else if (fallbackError) { - diagnostics.add(getDiagnosticForCallNode(node, fallbackError)); - } + } + prop = lexicallyScopedSymbol ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol) : undefined; + // Check for private-identifier-specific shadowing and lexical-scoping errors. + if (!prop && checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) { + return errorType; + } + else { + const isSetonlyAccessor = prop && prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); + if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite) { + error(node, Diagnostics.Private_accessor_was_defined_without_a_getter); } } - - return getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray); - - function addImplementationSuccessElaboration(failed: Signature, diagnostic: Diagnostic) { - const oldCandidatesForArgumentError = candidatesForArgumentError; - const oldCandidateForArgumentArityError = candidateForArgumentArityError; - const oldCandidateForTypeArgumentError = candidateForTypeArgumentError; - - const failedSignatureDeclarations = failed.declaration?.symbol?.declarations || emptyArray; - const isOverload = failedSignatureDeclarations.length > 1; - const implDecl = isOverload ? find(failedSignatureDeclarations, d => isFunctionLikeDeclaration(d) && nodeIsPresent(d.body)) : undefined; - if (implDecl) { - const candidate = getSignatureFromDeclaration(implDecl as FunctionLikeDeclaration); - const isSingleNonGenericCandidate = !candidate.typeParameters; - if (chooseOverload([candidate], assignableRelation, isSingleNonGenericCandidate)) { - addRelatedInfo(diagnostic, createDiagnosticForNode(implDecl, Diagnostics.The_call_would_have_succeeded_against_this_implementation_but_implementation_signatures_of_overloads_are_not_externally_visible)); - } + } + else { + if (isAnyLike) { + if (isIdentifier(left) && parentSymbol) { + markAliasReferenced(parentSymbol, node); } - - candidatesForArgumentError = oldCandidatesForArgumentError; - candidateForArgumentArityError = oldCandidateForArgumentArityError; - candidateForTypeArgumentError = oldCandidateForTypeArgumentError; + return isErrorType(apparentType) ? errorType : apparentType;; } + prop = getPropertyOfType(apparentType, right.escapedText); + } + // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums. + // The exceptions are: + // 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and + // 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`. + if (isIdentifier(left) && parentSymbol && (compilerOptions.isolatedModules || !(prop && isConstEnumOrConstEnumOnlyModule(prop)) || shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(node))) { + markAliasReferenced(parentSymbol, node); + } - function chooseOverload(candidates: Signature[], relation: ESMap, isSingleNonGenericCandidate: boolean, signatureHelpTrailingComma = false) { - candidatesForArgumentError = undefined; - candidateForArgumentArityError = undefined; - candidateForTypeArgumentError = undefined; - - if (isSingleNonGenericCandidate) { - const candidate = candidates[0]; - if (some(typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { - return undefined; - } - if (getSignatureApplicabilityError(node, args, candidate, relation, CheckMode.Normal, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { - candidatesForArgumentError = [candidate]; - return undefined; - } - return candidate; + let propType: Type; + if (!prop) { + const indexInfo = !isPrivateIdentifier(right) && (assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType)) ? + getApplicableIndexInfoForName(apparentType, right.escapedText) : undefined; + if (!(indexInfo && indexInfo.type)) { + const isUncheckedJS = isUncheckedJSSuggestion(node, leftType.symbol, /*excludeClasses*/ true); + if (!isUncheckedJS && isJSLiteralType(leftType)) { + return anyType; } - - for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { - const candidate = candidates[candidateIndex]; - if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { - continue; - } - - let checkCandidate: Signature; - let inferenceContext: InferenceContext | undefined; - - if (candidate.typeParameters) { - let typeArgumentTypes: Type[] | undefined; - if (some(typeArguments)) { - typeArgumentTypes = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false); - if (!typeArgumentTypes) { - candidateForTypeArgumentError = candidate; - continue; - } - } - else { - inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); - typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext); - argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal; - } - checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); - // If the original signature has a generic rest type, instantiation may produce a - // signature with different arity and we need to perform another arity check. - if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { - candidateForArgumentArityError = checkCandidate; - continue; - } - } - else { - checkCandidate = candidate; - } - if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { - // Give preference to error candidates that have no rest parameters (as they are more specific) - (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); - continue; + if (leftType.symbol === globalThisSymbol) { + if (globalThisSymbol.exports!.has(right.escapedText) && (globalThisSymbol.exports!.get(right.escapedText)!.flags & SymbolFlags.BlockScoped)) { + error(right, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(right.escapedText), typeToString(leftType)); } - if (argCheckMode) { - // If one or more context sensitive arguments were excluded, we start including - // them now (and keeping do so for any subsequent candidates) and perform a second - // round of type inference and applicability checking for this particular candidate. - argCheckMode = CheckMode.Normal; - if (inferenceContext) { - const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext); - checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); - // If the original signature has a generic rest type, instantiation may produce a - // signature with different arity and we need to perform another arity check. - if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { - candidateForArgumentArityError = checkCandidate; - continue; - } - } - if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { - // Give preference to error candidates that have no rest parameters (as they are more specific) - (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); - continue; - } + else if (noImplicitAny) { + error(right, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(leftType)); } - candidates[candidateIndex] = checkCandidate; - return checkCandidate; + return anyType; } - - return undefined; + if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { + reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType, isUncheckedJS); + } + return errorType; + } + if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) { + error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType)); } - } - - // No signature was applicable. We have already reported the errors for the invalid signature. - function getCandidateForOverloadFailure( - node: CallLikeExpression, - candidates: Signature[], - args: readonly Expression[], - hasCandidatesOutArray: boolean, - ): Signature { - Debug.assert(candidates.length > 0); // Else should not have called this. - checkNodeDeferred(node); - // Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine. - // Don't do this if there is a `candidatesOutArray`, - // because then we want the chosen best candidate to be one of the overloads, not a combination. - return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters) - ? pickLongestCandidateSignature(node, candidates, args) - : createUnionOfSignaturesForOverloadFailure(candidates); - } - function createUnionOfSignaturesForOverloadFailure(candidates: readonly Signature[]): Signature { - const thisParameters = mapDefined(candidates, c => c.thisParameter); - let thisParameter: Symbol | undefined; - if (thisParameters.length) { - thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter)); + propType = (compilerOptions.noUncheckedIndexedAccess && !isAssignmentTarget(node)) ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; + if (compilerOptions.noPropertyAccessFromIndexSignature && isPropertyAccessExpression(node)) { + error(right, Diagnostics.Property_0_comes_from_an_index_signature_so_it_must_be_accessed_with_0, unescapeLeadingUnderscores(right.escapedText)); } - const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters); - const parameters: Symbol[] = []; - for (let i = 0; i < maxNonRestParam; i++) { - const symbols = mapDefined(candidates, s => signatureHasRestParameter(s) ? - i < s.parameters.length - 1 ? s.parameters[i] : last(s.parameters) : - i < s.parameters.length ? s.parameters[i] : undefined); - Debug.assert(symbols.length !== 0); - parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i)))); - } - const restParameterSymbols = mapDefined(candidates, c => signatureHasRestParameter(c) ? last(c.parameters) : undefined); - let flags = SignatureFlags.None; - if (restParameterSymbols.length !== 0) { - const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype)); - parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type)); - flags |= SignatureFlags.HasRestParameter; + } + else { + if (prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(node, prop)) { + addDeprecatedSuggestion(right, prop.declarations, right.escapedText as string); } - if (candidates.some(signatureHasLiteralTypes)) { - flags |= SignatureFlags.HasLiteralTypes; + checkPropertyNotUsedBeforeDeclaration(prop, node, right); + markPropertyAsReferenced(prop, node, isSelfTypeAccess(left, parentSymbol)); + getNodeLinks(node).resolvedSymbol = prop; + const writing = isWriteAccess(node); + checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, writing, apparentType, prop); + if (isAssignmentToReadonlyEntity(node as Expression, prop, assignmentKind)) { + error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right)); + return errorType; } - return createSignature( - candidates[0].declaration, - /*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`. - thisParameter, - parameters, - /*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)), - /*typePredicate*/ undefined, - minArgumentCount, - flags); + + propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writing ? getSetAccessorTypeOfSymbol(prop) : getTypeOfSymbol(prop); } - function getNumNonRestParameters(signature: Signature): number { - const numParams = signature.parameters.length; - return signatureHasRestParameter(signature) ? numParams - 1 : numParams; + return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode); + } + + /** + * Determines whether a did-you-mean error should be a suggestion in an unchecked JS file. + * Only applies to unchecked JS files without checkJS, // @ts-check or // @ts-nocheck + * It does not suggest when the suggestion: + * - Is from a global file that is different from the reference file, or + * - (optionally) Is a class, or is a this.x property access expression + */ + function isUncheckedJSSuggestion(node: Node | undefined, suggestion: Symbol | undefined, excludeClasses: boolean): boolean { + const file = getSourceFileOfNode(node); + if (file) { + if (compilerOptions.checkJs === undefined && file.checkJsDirective === undefined && (file.scriptKind === ScriptKind.JS || file.scriptKind === ScriptKind.JSX)) { + const declarationFile = forEach(suggestion?.declarations, getSourceFileOfNode); + return !(file !== declarationFile && !!declarationFile && isGlobalSourceFile(declarationFile)) + && !(excludeClasses && suggestion && suggestion.flags & SymbolFlags.Class) + && !(!!node && excludeClasses && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword); + } + } + return false; + } + + function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node, checkMode: CheckMode | undefined) { + // Only compute control flow type if this is a property access expression that isn't an + // assignment target, and the referenced property was declared as a variable, property, + // accessor, or optional method. + const assignmentKind = getAssignmentTargetKind(node); + if (assignmentKind === AssignmentKind.Definite) { + return removeMissingType(propType, !!(prop && prop.flags & SymbolFlags.Optional)); + } + if (prop && + !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) + && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union) + && !isDuplicatedCommonJSExport(prop.declarations)) { + return propType; + } + if (propType === autoType) { + return getFlowTypeOfProperty(node, prop); + } + propType = getNarrowableTypeForReference(propType, node, checkMode); + // If strict null checks and strict property initialization checks are enabled, if we have + // a this.xxx property access, if the property is an instance property without an initializer, + // and if we are in a constructor of the same class as the property declaration, assume that + // the property is uninitialized at the top of the control flow. + let assumeUninitialized = false; + if (strictNullChecks && strictPropertyInitialization && isAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword) { + const declaration = prop && prop.valueDeclaration; + if (declaration && isPropertyWithoutInitializer(declaration)) { + if (!isStatic(declaration)) { + const flowContainer = getControlFlowContainer(node); + if (flowContainer.kind === SyntaxKind.Constructor && flowContainer.parent === declaration.parent && !(declaration.flags & NodeFlags.Ambient)) { + assumeUninitialized = true; + } + } + } + } + else if (strictNullChecks && prop && prop.valueDeclaration && + isPropertyAccessExpression(prop.valueDeclaration) && + getAssignmentDeclarationPropertyAccessKind(prop.valueDeclaration) && + getControlFlowContainer(node) === getControlFlowContainer(prop.valueDeclaration)) { + assumeUninitialized = true; + } + const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); + if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { + error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 + // Return the declared type to reduce follow-on errors + return propType; + } + return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + } + + function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier | PrivateIdentifier): void { + const { valueDeclaration } = prop; + if (!valueDeclaration || getSourceFileOfNode(node).isDeclarationFile) { + return; } - function createCombinedSymbolFromTypes(sources: readonly Symbol[], types: Type[]): Symbol { - return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype)); + let diagnosticMessage; + const declarationName = idText(right); + if (isInPropertyInitializerOrClassStaticBlock(node) + && !isOptionalPropertyDeclaration(valueDeclaration) + && !(isAccessExpression(node) && isAccessExpression(node.expression)) + && !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right) + && (compilerOptions.useDefineForClassFields || !isPropertyDeclaredInAncestorClass(prop))) { + diagnosticMessage = error(right, Diagnostics.Property_0_is_used_before_its_initialization, declarationName); + } + else if (valueDeclaration.kind === SyntaxKind.ClassDeclaration && + node.parent.kind !== SyntaxKind.TypeReference && + !(valueDeclaration.flags & NodeFlags.Ambient) && + !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)) { + diagnosticMessage = error(right, Diagnostics.Class_0_used_before_its_declaration, declarationName); } - function createCombinedSymbolForOverloadFailure(sources: readonly Symbol[], type: Type): Symbol { - // This function is currently only used for erroneous overloads, so it's good enough to just use the first source. - return createSymbolWithType(first(sources), type); + if (diagnosticMessage) { + addRelatedInfo(diagnosticMessage, + createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName) + ); } + } - function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: readonly Expression[]): Signature { - // Pick the longest signature. This way we can get a contextual type for cases like: - // declare function f(a: { xa: number; xb: number; }, b: number); - // f({ | - // Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like: - // declare function f(k: keyof T); - // f(" - const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount); - const candidate = candidates[bestIndex]; - const { typeParameters } = candidate; - if (!typeParameters) { - return candidate; + function isInPropertyInitializerOrClassStaticBlock(node: Node): boolean { + return !!findAncestor(node, node => { + switch (node.kind) { + case SyntaxKind.PropertyDeclaration: + return true; + case SyntaxKind.PropertyAssignment: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.SpreadAssignment: + case SyntaxKind.ComputedPropertyName: + case SyntaxKind.TemplateSpan: + case SyntaxKind.JsxExpression: + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxAttributes: + case SyntaxKind.JsxSpreadAttribute: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.HeritageClause: + return false; + case SyntaxKind.ArrowFunction: + case SyntaxKind.ExpressionStatement: + return isBlock(node.parent) && isClassStaticBlockDeclaration(node.parent.parent) ? true : "quit"; + default: + return isExpressionNode(node) ? false : "quit"; } + }); + } - const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined; - const instantiated = typeArgumentNodes - ? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node))) - : inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args); - candidates[bestIndex] = instantiated; - return instantiated; + /** + * It's possible that "prop.valueDeclaration" is a local declaration, but the property was also declared in a superclass. + * In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration. + */ + function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean { + if (!(prop.parent!.flags & SymbolFlags.Class)) { + return false; } - - function getTypeArgumentsFromNodes(typeArgumentNodes: readonly TypeNode[], typeParameters: readonly TypeParameter[], isJs: boolean): readonly Type[] { - const typeArguments = typeArgumentNodes.map(getTypeOfNode); - while (typeArguments.length > typeParameters.length) { - typeArguments.pop(); + let classType: InterfaceType | undefined = getTypeOfSymbol(prop.parent!) as InterfaceType; + while (true) { + classType = classType.symbol && getSuperClass(classType) as InterfaceType | undefined; + if (!classType) { + return false; } - while (typeArguments.length < typeParameters.length) { - typeArguments.push(getConstraintOfTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isJs)); + const superProperty = getPropertyOfType(classType, prop.escapedName); + if (superProperty && superProperty.valueDeclaration) { + return true; } - return typeArguments; } + } - function inferSignatureInstantiationForOverloadFailure(node: CallLikeExpression, typeParameters: readonly TypeParameter[], candidate: Signature, args: readonly Expression[]): Signature { - const inferenceContext = createInferenceContext(typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); - const typeArgumentTypes = inferTypeArguments(node, candidate, args, CheckMode.SkipContextSensitive | CheckMode.SkipGenericFunctions, inferenceContext); - return createSignatureInstantiation(candidate, typeArgumentTypes); + function getSuperClass(classType: InterfaceType): Type | undefined { + const x = getBaseTypes(classType); + if (x.length === 0) { + return undefined; } + return getIntersectionType(x); + } - function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number { - let maxParamsIndex = -1; - let maxParams = -1; - - for (let i = 0; i < candidates.length; i++) { - const candidate = candidates[i]; - const paramCount = getParameterCount(candidate); - if (hasEffectiveRestParameter(candidate) || paramCount >= argsCount) { - return i; - } - if (paramCount > maxParams) { - maxParams = paramCount; - maxParamsIndex = i; + function reportNonexistentProperty(propNode: Identifier | PrivateIdentifier, containingType: Type, isUncheckedJS: boolean) { + let errorInfo: DiagnosticMessageChain | undefined; + let relatedInfo: Diagnostic | undefined; + if (!isPrivateIdentifier(propNode) && containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) { + for (const subtype of (containingType as UnionType).types) { + if (!getPropertyOfType(subtype, propNode.escapedText) && !getApplicableIndexInfoForName(subtype, propNode.escapedText)) { + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(subtype)); + break; } } - - return maxParamsIndex; } - - function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - if (node.expression.kind === SyntaxKind.SuperKeyword) { - const superType = checkSuperExpression(node.expression); - if (isTypeAny(superType)) { - for (const arg of node.arguments) { - checkExpression(arg); // Still visit arguments so they get marked for visibility, etc - } - return anySignature; + if (typeHasStaticProperty(propNode.escapedText, containingType)) { + const propName = declarationNameToString(propNode); + const typeName = typeToString(containingType); + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName, typeName, typeName + "." + propName); + } + else { + const promisedType = getPromisedTypeOfPromise(containingType); + if (promisedType && getPropertyOfType(promisedType, propNode.escapedText)) { + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType)); + relatedInfo = createDiagnosticForNode(propNode, Diagnostics.Did_you_forget_to_use_await); + } + else { + const missingProperty = declarationNameToString(propNode); + const container = typeToString(containingType); + const libSuggestion = getSuggestedLibForNonExistentProperty(missingProperty, containingType); + if (libSuggestion !== undefined) { + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_2_or_later, missingProperty, container, libSuggestion); } - if (!isErrorType(superType)) { - // In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated - // with the type arguments specified in the extends clause. - const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!); - if (baseTypeNode) { - const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode); - return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlags.None); + else { + const suggestion = getSuggestedSymbolForNonexistentProperty(propNode, containingType); + if (suggestion !== undefined) { + const suggestedName = symbolName(suggestion); + const message = isUncheckedJS ? Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2 : Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2; + errorInfo = chainDiagnosticMessages(errorInfo, message, missingProperty, container, suggestedName); + relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName); + } + else { + const diagnostic = containerSeemsToBeEmptyDomElement(containingType) + ? Diagnostics.Property_0_does_not_exist_on_type_1_Try_changing_the_lib_compiler_option_to_include_dom + : Diagnostics.Property_0_does_not_exist_on_type_1; + errorInfo = chainDiagnosticMessages(elaborateNeverIntersection(errorInfo, containingType), diagnostic, missingProperty, container); } } - return resolveUntypedCall(node); } + } + const resultDiagnostic = createDiagnosticForNodeFromMessageChain(propNode, errorInfo); + if (relatedInfo) { + addRelatedInfo(resultDiagnostic, relatedInfo); + } + addErrorOrSuggestion(!isUncheckedJS || errorInfo.code !== Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2.code, resultDiagnostic); + } + + function containerSeemsToBeEmptyDomElement(containingType: Type) { + return (compilerOptions.lib && !compilerOptions.lib.includes("dom")) && + everyContainedType(containingType, type => type.symbol && /^(EventTarget|Node|((HTML[a-zA-Z]*)?Element))$/.test(unescapeLeadingUnderscores(type.symbol.escapedName))) && + isEmptyObjectType(containingType); + } + + function typeHasStaticProperty(propName: __String, containingType: Type): boolean { + const prop = containingType.symbol && getPropertyOfType(getTypeOfSymbol(containingType.symbol), propName); + return prop !== undefined && !!prop.valueDeclaration && isStatic(prop.valueDeclaration); + } - let callChainFlags: SignatureFlags; - let funcType = checkExpression(node.expression); - if (isCallChain(node)) { - const nonOptionalType = getOptionalExpressionType(funcType, node.expression); - callChainFlags = nonOptionalType === funcType ? SignatureFlags.None : - isOutermostOptionalChain(node) ? SignatureFlags.IsOuterCallChain : - SignatureFlags.IsInnerCallChain; - funcType = nonOptionalType; + function getSuggestedLibForNonExistentName(name: __String | Identifier) { + const missingName = diagnosticName(name); + const allFeatures = getScriptTargetFeatures(); + const libTargets = getOwnKeys(allFeatures); + for (const libTarget of libTargets) { + const containingTypes = getOwnKeys(allFeatures[libTarget]); + if (containingTypes !== undefined && contains(containingTypes, missingName)) { + return libTarget; } - else { - callChainFlags = SignatureFlags.None; + } + } + + function getSuggestedLibForNonExistentProperty(missingProperty: string, containingType: Type) { + const container = getApparentType(containingType).symbol; + if (!container) { + return undefined; + } + const allFeatures = getScriptTargetFeatures(); + const libTargets = getOwnKeys(allFeatures); + for (const libTarget of libTargets) { + const featuresOfLib = allFeatures[libTarget]; + const featuresOfContainingType = featuresOfLib[symbolName(container)]; + if (featuresOfContainingType !== undefined && contains(featuresOfContainingType, missingProperty)) { + return libTarget; } + } + } - funcType = checkNonNullTypeWithReporter( - funcType, - node.expression, - reportCannotInvokePossiblyNullOrUndefinedError - ); + function getSuggestedSymbolForNonexistentClassMember(name: string, baseType: Type): Symbol | undefined { + return getSpellingSuggestionForName(name, getPropertiesOfType(baseType), SymbolFlags.ClassMember); + } - if (funcType === silentNeverType) { - return silentNeverSignature; + function getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined { + let props = getPropertiesOfType(containingType); + if (typeof name !== "string") { + const parent = name.parent; + if (isPropertyAccessExpression(parent)) { + props = filter(props, prop => isValidPropertyAccessForCompletions(parent, containingType, prop)); } + name = idText(name); + } + return getSpellingSuggestionForName(name, props, SymbolFlags.Value); + } - const apparentType = getApparentType(funcType); - if (isErrorType(apparentType)) { - // Another error has already been reported - return resolveErrorCall(node); - } + function getSuggestedSymbolForNonexistentJSXAttribute(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined { + const strName = isString(name) ? name : idText(name); + const properties = getPropertiesOfType(containingType); + const jsxSpecific = strName === "for" ? find(properties, x => symbolName(x) === "htmlFor") + : strName === "class" ? find(properties, x => symbolName(x) === "className") + : undefined; + return jsxSpecific ?? getSpellingSuggestionForName(strName, properties, SymbolFlags.Value); + } - // Technically, this signatures list may be incomplete. We are taking the apparent type, - // but we are not including call signatures that may have been added to the Object or - // Function interface, since they have none by default. This is a bit of a leap of faith - // that the user will not add any. - const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + function getSuggestionForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): string | undefined { + const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType); + return suggestion && symbolName(suggestion); + } - // TS 1.0 Spec: 4.12 - // In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual - // types are provided for the argument expressions, and the result is always of type Any. - if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { - // The unknownType indicates that an error already occurred (and was reported). No - // need to report another error in this case. - if (!isErrorType(funcType) && node.typeArguments) { - error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); - } - return resolveUntypedCall(node); + function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined { + Debug.assert(outerName !== undefined, "outername should always be defined"); + const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, (symbols, name, meaning) => { + Debug.assertEqual(outerName, name, "name should equal outerName"); + const symbol = getSymbol(symbols, name, meaning); + // Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function + // So the table *contains* `x` but `x` isn't actually in scope. + // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion. + if (symbol) return symbol; + let candidates: Symbol[]; + if (symbols === globals) { + const primitives = mapDefined( + ["string", "number", "boolean", "object", "bigint", "symbol"], + s => symbols.has((s.charAt(0).toUpperCase() + s.slice(1)) as __String) + ? createSymbol(SymbolFlags.TypeAlias, s as __String) as Symbol + : undefined); + candidates = primitives.concat(arrayFrom(symbols.values())); } - // If FuncExpr's apparent type(section 3.8.1) is a function type, the call is a typed function call. - // TypeScript employs overload resolution in typed function calls in order to support functions - // with multiple call signatures. - if (!callSignatures.length) { - if (numConstructSignatures) { - error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); - } - else { - let relatedInformation: DiagnosticRelatedInformation | undefined; - if (node.arguments.length === 1) { - const text = getSourceFileOfNode(node).text; - if (isLineBreak(text.charCodeAt(skipTrivia(text, node.expression.end, /* stopAfterLineBreak */ true) - 1))) { - relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.Are_you_missing_a_semicolon); - } - } - invocationError(node.expression, apparentType, SignatureKind.Call, relatedInformation); - } - return resolveErrorCall(node); + else { + candidates = arrayFrom(symbols.values()); } - // When a call to a generic function is an argument to an outer call to a generic function for which - // inference is in process, we have a choice to make. If the inner call relies on inferences made from - // its contextual type to its return type, deferring the inner call processing allows the best possible - // contextual type to accumulate. But if the outer call relies on inferences made from the return type of - // the inner call, the inner call should be processed early. There's no sure way to know which choice is - // right (only a full unification algorithm can determine that), so we resort to the following heuristic: - // If no type arguments are specified in the inner call and at least one call signature is generic and - // returns a function type, we choose to defer processing. This narrowly permits function composition - // operators to flow inferences through return types, but otherwise processes calls right away. We - // use the resolvingSignature singleton to indicate that we deferred processing. This result will be - // propagated out and eventually turned into nonInferrableType (a type that is assignable to anything and - // from which we never make inferences). - if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) { - skippedGenericFunction(node, checkMode); - return resolvingSignature; - } - // If the function is explicitly marked with `@class`, then it must be constructed. - if (callSignatures.some(sig => isInJSFile(sig.declaration) && !!getJSDocClassTag(sig.declaration!))) { - error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); - return resolveErrorCall(node); + return getSpellingSuggestionForName(unescapeLeadingUnderscores(name), candidates, meaning); + }); + return result; + } + + function getSuggestionForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): string | undefined { + const symbolResult = getSuggestedSymbolForNonexistentSymbol(location, outerName, meaning); + return symbolResult && symbolName(symbolResult); + } + + function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined { + return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); + } + + function getSuggestionForNonexistentExport(name: Identifier, targetModule: Symbol): string | undefined { + const suggestion = getSuggestedSymbolForNonexistentModule(name, targetModule); + return suggestion && symbolName(suggestion); + } + + function getSuggestionForNonexistentIndexSignature(objectType: Type, expr: ElementAccessExpression, keyedType: Type): string | undefined { + // check if object type has setter or getter + function hasProp(name: "set" | "get") { + const prop = getPropertyOfObjectType(objectType, name as __String); + if (prop) { + const s = getSingleCallSignature(getTypeOfSymbol(prop)); + return !!s && getMinArgumentCount(s) >= 1 && isTypeAssignableTo(keyedType, getTypeAtPosition(s, 0)); } + return false; + }; - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags); + const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get"; + if (!hasProp(suggestedMethod)) { + return undefined; } - function isGenericFunctionReturningFunction(signature: Signature) { - return !!(signature.typeParameters && isFunctionType(getReturnTypeOfSignature(signature))); + let suggestion = tryGetPropertyAccessOrIdentifierToString(expr.expression); + if (suggestion === undefined) { + suggestion = suggestedMethod; } - - /** - * TS 1.0 spec: 4.12 - * If FuncExpr is of type Any, or of an object type that has no call or construct signatures - * but is a subtype of the Function interface, the call is an untyped function call. - */ - function isUntypedFunctionCall(funcType: Type, apparentFuncType: Type, numCallSignatures: number, numConstructSignatures: number): boolean { - // We exclude union types because we may have a union of function types that happen to have no common signatures. - return isTypeAny(funcType) || isTypeAny(apparentFuncType) && !!(funcType.flags & TypeFlags.TypeParameter) || - !numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & TypeFlags.Union) && !(getReducedType(apparentFuncType).flags & TypeFlags.Never) && isTypeAssignableTo(funcType, globalFunctionType); + else { + suggestion += "." + suggestedMethod; } - function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - if (node.arguments && languageVersion < ScriptTarget.ES5) { - const spreadIndex = getSpreadArgumentIndex(node.arguments); - if (spreadIndex >= 0) { - error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_5_and_higher); - } - } + return suggestion; + } + + function getSuggestedTypeForNonexistentStringLiteralType(source: StringLiteralType, target: UnionType): StringLiteralType | undefined { + const candidates = target.types.filter((type): type is StringLiteralType => !!(type.flags & TypeFlags.StringLiteral)); + return getSpellingSuggestion(source.value, candidates, type => type.value); + } - let expressionType = checkNonNullExpression(node.expression); - if (expressionType === silentNeverType) { - return silentNeverSignature; + /** + * Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough. + * Names less than length 3 only check for case-insensitive equality, not levenshtein distance. + * + * If there is a candidate that's the same except for case, return that. + * If there is a candidate that's within one edit of the name, return that. + * Otherwise, return the candidate with the smallest Levenshtein distance, + * except for candidates: + * * With no name + * * Whose meaning doesn't match the `meaning` parameter. + * * Whose length differs from the target name by more than 0.34 of the length of the name. + * * Whose levenshtein distance is more than 0.4 of the length of the name + * (0.4 allows 1 substitution/transposition for every 5 characters, + * and 1 insertion/deletion at 3 characters) + */ + function getSpellingSuggestionForName(name: string, symbols: Symbol[], meaning: SymbolFlags): Symbol | undefined { + return getSpellingSuggestion(name, symbols, getCandidateName); + + function getCandidateName(candidate: Symbol) { + const candidateName = symbolName(candidate); + if (startsWith(candidateName, "\"")) { + return undefined; } - // If expressionType's apparent type(section 3.8.1) is an object type with one or - // more construct signatures, the expression is processed in the same manner as a - // function call, but using the construct signatures as the initial set of candidate - // signatures for overload resolution. The result type of the function call becomes - // the result type of the operation. - expressionType = getApparentType(expressionType); - if (isErrorType(expressionType)) { - // Another error has already been reported - return resolveErrorCall(node); + if (candidate.flags & meaning) { + return candidateName; } - // TS 1.0 spec: 4.11 - // If expressionType is of type Any, Args can be any argument - // list and the result of the operation is of type Any. - if (isTypeAny(expressionType)) { - if (node.typeArguments) { - error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); + if (candidate.flags & SymbolFlags.Alias) { + const alias = tryResolveAlias(candidate); + if (alias && alias.flags & meaning) { + return candidateName; } - return resolveUntypedCall(node); } - // Technically, this signatures list may be incomplete. We are taking the apparent type, - // but we are not including construct signatures that may have been added to the Object or - // Function interface, since they have none by default. This is a bit of a leap of faith - // that the user will not add any. - const constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct); - if (constructSignatures.length) { - if (!isConstructorAccessible(node, constructSignatures[0])) { - return resolveErrorCall(node); - } - // If the expression is a class of abstract type, or an abstract construct signature, - // then it cannot be instantiated. - // In the case of a merged class-module or class-interface declaration, - // only the class declaration node will have the Abstract flag set. - if (constructSignatures.some(signature => signature.flags & SignatureFlags.Abstract)) { - error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class); - return resolveErrorCall(node); - } - const valueDecl = expressionType.symbol && getClassLikeDeclarationOfSymbol(expressionType.symbol); - if (valueDecl && hasSyntacticModifier(valueDecl, ModifierFlags.Abstract)) { - error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class); - return resolveErrorCall(node); - } + return undefined; + } + } - return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isSelfTypeAccess: boolean) { + const valueDeclaration = prop && (prop.flags & SymbolFlags.ClassMember) && prop.valueDeclaration; + if (!valueDeclaration) { + return; + } + const hasPrivateModifier = hasEffectiveModifier(valueDeclaration, ModifierFlags.Private); + const hasPrivateIdentifier = prop.valueDeclaration && isNamedDeclaration(prop.valueDeclaration) && isPrivateIdentifier(prop.valueDeclaration.name); + if (!hasPrivateModifier && !hasPrivateIdentifier) { + return; + } + if (nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly) && !(prop.flags & SymbolFlags.SetAccessor)) { + return; + } + if (isSelfTypeAccess) { + // Find any FunctionLikeDeclaration because those create a new 'this' binding. But this should only matter for methods (or getters/setters). + const containingMethod = findAncestor(nodeForCheckWriteOnly, isFunctionLikeDeclaration); + if (containingMethod && containingMethod.symbol === prop) { + return; } + } - // If expressionType's apparent type is an object type with no construct signatures but - // one or more call signatures, the expression is processed as a function call. A compile-time - // error occurs if the result of the function call is not Void. The type of the result of the - // operation is Any. It is an error to have a Void this type. - const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); - if (callSignatures.length) { - const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); - if (!noImplicitAny) { - if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { - error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); - } - if (getThisTypeOfSignature(signature) === voidType) { - error(node, Diagnostics.A_function_that_is_called_with_the_new_keyword_cannot_have_a_this_type_that_is_void); - } - } - return signature; - } + (getCheckFlags(prop) & CheckFlags.Instantiated ? getSymbolLinks(prop).target : prop)!.isReferenced = SymbolFlags.All; + } - invocationError(node.expression, expressionType, SignatureKind.Construct); - return resolveErrorCall(node); - } + function isSelfTypeAccess(name: Expression | QualifiedName, parent: Symbol | undefined) { + return name.kind === SyntaxKind.ThisKeyword + || !!parent && isEntityNameExpression(name) && parent === getResolvedSymbol(getFirstIdentifier(name)); + } - function typeHasProtectedAccessibleBase(target: Symbol, type: InterfaceType): boolean { - const baseTypes = getBaseTypes(type); - if (!length(baseTypes)) { - return false; - } - const firstBase = baseTypes[0]; - if (firstBase.flags & TypeFlags.Intersection) { - const types = (firstBase as IntersectionType).types; - const mixinFlags = findMixins(types); - let i = 0; - for (const intersectionMember of (firstBase as IntersectionType).types) { - // We want to ignore mixin ctors - if (!mixinFlags[i]) { - if (getObjectFlags(intersectionMember) & (ObjectFlags.Class | ObjectFlags.Interface)) { - if (intersectionMember.symbol === target) { - return true; - } - if (typeHasProtectedAccessibleBase(target, intersectionMember as InterfaceType)) { - return true; - } - } - } - i++; - } - return false; - } - if (firstBase.symbol === target) { - return true; - } - return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType); + function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: __String): boolean { + switch (node.kind) { + case SyntaxKind.PropertyAccessExpression: + return isValidPropertyAccessWithType(node, node.expression.kind === SyntaxKind.SuperKeyword, propertyName, getWidenedType(checkExpression(node.expression))); + case SyntaxKind.QualifiedName: + return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getWidenedType(checkExpression(node.left))); + case SyntaxKind.ImportType: + return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getTypeFromTypeNode(node)); + } + } + + /** + * Checks if an existing property access is valid for completions purposes. + * @param node a property access-like node where we want to check if we can access a property. + * This node does not need to be an access of the property we are checking. + * e.g. in completions, this node will often be an incomplete property access node, as in `foo.`. + * Besides providing a location (i.e. scope) used to check property accessibility, we use this node for + * computing whether this is a `super` property access. + * @param type the type whose property we are checking. + * @param property the accessed property's symbol. + */ + function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean { + return isPropertyAccessible(node, + node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, + /* isWrite */ false, + type, + property); + // Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context. + } + + function isValidPropertyAccessWithType( + node: PropertyAccessExpression | QualifiedName | ImportTypeNode, + isSuper: boolean, + propertyName: __String, + type: Type): boolean { + + // Short-circuiting for improved performance. + if (isTypeAny(type)) { + return true; } - function isConstructorAccessible(node: NewExpression, signature: Signature) { - if (!signature || !signature.declaration) { - return true; - } + const prop = getPropertyOfType(type, propertyName); + return !!prop && isPropertyAccessible(node, isSuper, /* isWrite */ false, type, prop); + } - const declaration = signature.declaration; - const modifiers = getSelectedEffectiveModifierFlags(declaration, ModifierFlags.NonPublicAccessibilityModifier); + /** + * Checks if a property can be accessed in a location. + * The location is given by the `node` parameter. + * The node does not need to be a property access. + * @param node location where to check property accessibility + * @param isSuper whether to consider this a `super` property access, e.g. `super.foo`. + * @param isWrite whether this is a write access, e.g. `++foo.x`. + * @param containingType type where the property comes from. + * @param property property symbol. + */ + function isPropertyAccessible( + node: Node, + isSuper: boolean, + isWrite: boolean, + containingType: Type, + property: Symbol): boolean { + + // Short-circuiting for improved performance. + if (isTypeAny(containingType)) { + return true; + } - // (1) Public constructors and (2) constructor functions are always accessible. - if (!modifiers || declaration.kind !== SyntaxKind.Constructor) { - return true; + // A #private property access in an optional chain is an error dealt with by the parser. + // The checker does not check for it, so we need to do our own check here. + if (property.valueDeclaration && isPrivateIdentifierClassElementDeclaration(property.valueDeclaration)) { + const declClass = getContainingClass(property.valueDeclaration); + return !isOptionalChain(node) && !!findAncestor(node, parent => parent === declClass); + } + + return checkPropertyAccessibilityAtLocation(node, isSuper, isWrite, containingType, property); + } + + /** + * Return the symbol of the for-in variable declared or referenced by the given for-in statement. + */ + function getForInVariableSymbol(node: ForInStatement): Symbol | undefined { + const initializer = node.initializer; + if (initializer.kind === SyntaxKind.VariableDeclarationList) { + const variable = (initializer as VariableDeclarationList).declarations[0]; + if (variable && !isBindingPattern(variable.name)) { + return getSymbolOfNode(variable); } + } + else if (initializer.kind === SyntaxKind.Identifier) { + return getResolvedSymbol(initializer as Identifier); + } + return undefined; + } - const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(declaration.parent.symbol)!; - const declaringClass = getDeclaredTypeOfSymbol(declaration.parent.symbol) as InterfaceType; + /** + * Return true if the given type is considered to have numeric property names. + */ + function hasNumericPropertyNames(type: Type) { + return getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, numberType); + } - // A private or protected constructor can only be instantiated within its own class (or a subclass, for protected) - if (!isNodeWithinClass(node, declaringClassDeclaration)) { - const containingClass = getContainingClass(node); - if (containingClass && modifiers & ModifierFlags.Protected) { - const containingType = getTypeOfNode(containingClass); - if (typeHasProtectedAccessibleBase(declaration.parent.symbol, containingType as InterfaceType)) { + /** + * Return true if given node is an expression consisting of an identifier (possibly parenthesized) + * that references a for-in variable for an object with numeric property names. + */ + function isForInVariableForNumericPropertyNames(expr: Expression) { + const e = skipParentheses(expr); + if (e.kind === SyntaxKind.Identifier) { + const symbol = getResolvedSymbol(e as Identifier); + if (symbol.flags & SymbolFlags.Variable) { + let child: Node = expr; + let node = expr.parent; + while (node) { + if (node.kind === SyntaxKind.ForInStatement && + child === (node as ForInStatement).statement && + getForInVariableSymbol(node as ForInStatement) === symbol && + hasNumericPropertyNames(getTypeOfExpression((node as ForInStatement).expression))) { return true; } + child = node; + node = node.parent; } - if (modifiers & ModifierFlags.Private) { - error(node, Diagnostics.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); - } - if (modifiers & ModifierFlags.Protected) { - error(node, Diagnostics.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); - } - return false; } - - return true; } + return false; + } - function invocationErrorDetails(errorTarget: Node, apparentType: Type, kind: SignatureKind): { messageChain: DiagnosticMessageChain, relatedMessage: DiagnosticMessage | undefined } { - let errorInfo: DiagnosticMessageChain | undefined; - const isCall = kind === SignatureKind.Call; - const awaitedType = getAwaitedType(apparentType); - const maybeMissingAwait = awaitedType && getSignaturesOfType(awaitedType, kind).length > 0; - if (apparentType.flags & TypeFlags.Union) { - const types = (apparentType as UnionType).types; - let hasSignatures = false; - for (const constituent of types) { - const signatures = getSignaturesOfType(constituent, kind); - if (signatures.length !== 0) { - hasSignatures = true; - if (errorInfo) { - // Bail early if we already have an error, no chance of "No constituent of type is callable" - break; - } - } - else { - // Error on the first non callable constituent only - if (!errorInfo) { - errorInfo = chainDiagnosticMessages( - errorInfo, - isCall ? - Diagnostics.Type_0_has_no_call_signatures : - Diagnostics.Type_0_has_no_construct_signatures, - typeToString(constituent) - ); - errorInfo = chainDiagnosticMessages( - errorInfo, - isCall ? - Diagnostics.Not_all_constituents_of_type_0_are_callable : - Diagnostics.Not_all_constituents_of_type_0_are_constructable, - typeToString(apparentType) - ); - } - if (hasSignatures) { - // Bail early if we already found a siganture, no chance of "No constituent of type is callable" - break; - } - } - } - if (!hasSignatures) { - errorInfo = chainDiagnosticMessages( - /* detials */ undefined, - isCall ? - Diagnostics.No_constituent_of_type_0_is_callable : - Diagnostics.No_constituent_of_type_0_is_constructable, - typeToString(apparentType) - ); - } - if (!errorInfo) { - errorInfo = chainDiagnosticMessages( - errorInfo, - isCall ? - Diagnostics.Each_member_of_the_union_type_0_has_signatures_but_none_of_those_signatures_are_compatible_with_each_other : - Diagnostics.Each_member_of_the_union_type_0_has_construct_signatures_but_none_of_those_signatures_are_compatible_with_each_other, - typeToString(apparentType) - ); - } - } - else { - errorInfo = chainDiagnosticMessages( - errorInfo, - isCall ? - Diagnostics.Type_0_has_no_call_signatures : - Diagnostics.Type_0_has_no_construct_signatures, - typeToString(apparentType) - ); - } + function checkIndexedAccess(node: ElementAccessExpression, checkMode: CheckMode | undefined): Type { + return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain, checkMode) : + checkElementAccessExpression(node, checkNonNullExpression(node.expression), checkMode); + } - let headMessage = isCall ? Diagnostics.This_expression_is_not_callable : Diagnostics.This_expression_is_not_constructable; + function checkElementAccessChain(node: ElementAccessChain, checkMode: CheckMode | undefined) { + const exprType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(exprType, node.expression); + return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression), checkMode), node, nonOptionalType !== exprType); + } - // Diagnose get accessors incorrectly called as functions - if (isCallExpression(errorTarget.parent) && errorTarget.parent.arguments.length === 0) { - const { resolvedSymbol } = getNodeLinks(errorTarget); - if (resolvedSymbol && resolvedSymbol.flags & SymbolFlags.GetAccessor) { - headMessage = Diagnostics.This_expression_is_not_callable_because_it_is_a_get_accessor_Did_you_mean_to_use_it_without; - } - } + function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type, checkMode: CheckMode | undefined): Type { + const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType; + const indexExpression = node.argumentExpression; + const indexType = checkExpression(indexExpression); - return { - messageChain: chainDiagnosticMessages(errorInfo, headMessage), - relatedMessage: maybeMissingAwait ? Diagnostics.Did_you_forget_to_use_await : undefined, - }; + if (isErrorType(objectType) || objectType === silentNeverType) { + return objectType; } - function invocationError(errorTarget: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) { - const { messageChain, relatedMessage: relatedInfo } = invocationErrorDetails(errorTarget, apparentType, kind); - const diagnostic = createDiagnosticForNodeFromMessageChain(errorTarget, messageChain); - if (relatedInfo) { - addRelatedInfo(diagnostic, createDiagnosticForNode(errorTarget, relatedInfo)); - } - if (isCallExpression(errorTarget.parent)) { - const { start, length } = getDiagnosticSpanForCallNode(errorTarget.parent, /* doNotIncludeArguments */ true); - diagnostic.start = start; - diagnostic.length = length; - } - diagnostics.add(diagnostic); - invocationErrorRecovery(apparentType, kind, relatedInformation ? addRelatedInfo(diagnostic, relatedInformation) : diagnostic); - } - - function invocationErrorRecovery(apparentType: Type, kind: SignatureKind, diagnostic: Diagnostic) { - if (!apparentType.symbol) { - return; - } - const importNode = getSymbolLinks(apparentType.symbol).originatingImport; - // Create a diagnostic on the originating import if possible onto which we can attach a quickfix - // An import call expression cannot be rewritten into another form to correct the error - the only solution is to use `.default` at the use-site - if (importNode && !isImportCall(importNode)) { - const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target!), kind); - if (!sigs || !sigs.length) return; - addRelatedInfo(diagnostic, - createDiagnosticForNode(importNode, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead) - ); - } + if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) { + error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal); + return errorType; } - function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - const tagType = checkExpression(node.tag); - const apparentType = getApparentType(tagType); + const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; + const accessFlags = isAssignmentTarget(node) ? + AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : + AccessFlags.ExpressionPosition; + const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, accessFlags, node) || errorType; + return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node); + } - if (isErrorType(apparentType)) { - // Another error has already been reported - return resolveErrorCall(node); - } + function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement { + return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node); + } - const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + function resolveUntypedCall(node: CallLikeExpression): Signature { + if (callLikeExpressionMayHaveTypeArguments(node)) { + // Check type arguments even though we will give an error that untyped calls may not accept type arguments. + // This gets us diagnostics for the type arguments and marks them as referenced. + forEach(node.typeArguments, checkSourceElement); + } - if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) { - return resolveUntypedCall(node); - } + if (node.kind === SyntaxKind.TaggedTemplateExpression) { + checkExpression(node.template); + } + else if (isJsxOpeningLikeElement(node)) { + checkExpression(node.attributes); + } + else if (node.kind !== SyntaxKind.Decorator) { + forEach((node as CallExpression).arguments, argument => { + checkExpression(argument); + }); + } + return anySignature; + } - if (!callSignatures.length) { - if (isArrayLiteralExpression(node.parent)) { - const diagnostic = createDiagnosticForNode(node.tag, Diagnostics.It_is_likely_that_you_are_missing_a_comma_to_separate_these_two_template_expressions_They_form_a_tagged_template_expression_which_cannot_be_invoked); - diagnostics.add(diagnostic); - return resolveErrorCall(node); - } + function resolveErrorCall(node: CallLikeExpression): Signature { + resolveUntypedCall(node); + return unknownSignature; + } - invocationError(node.tag, apparentType, SignatureKind.Call); - return resolveErrorCall(node); + // Re-order candidate signatures into the result array. Assumes the result array to be empty. + // The candidate list orders groups in reverse, but within a group signatures are kept in declaration order + // A nit here is that we reorder only signatures that belong to the same symbol, + // so order how inherited signatures are processed is still preserved. + // interface A { (x: string): void } + // interface B extends A { (x: 'foo'): string } + // const b: B; + // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void] + function reorderCandidates(signatures: readonly Signature[], result: Signature[], callChainFlags: SignatureFlags): void { + let lastParent: Node | undefined; + let lastSymbol: Symbol | undefined; + let cutoffIndex = 0; + let index: number | undefined; + let specializedIndex = -1; + let spliceIndex: number; + Debug.assert(!result.length); + for (const signature of signatures) { + const symbol = signature.declaration && getSymbolOfNode(signature.declaration); + const parent = signature.declaration && signature.declaration.parent; + if (!lastSymbol || symbol === lastSymbol) { + if (lastParent && parent === lastParent) { + index = index! + 1; + } + else { + lastParent = parent; + index = cutoffIndex; + } + } + else { + // current declaration belongs to a different symbol + // set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex + index = cutoffIndex = result.length; + lastParent = parent; + } + lastSymbol = symbol; + + // specialized signatures always need to be placed before non-specialized signatures regardless + // of the cutoff position; see GH#1133 + if (signatureHasLiteralTypes(signature)) { + specializedIndex++; + spliceIndex = specializedIndex; + // The cutoff index always needs to be greater than or equal to the specialized signature index + // in order to prevent non-specialized signatures from being added before a specialized + // signature. + cutoffIndex++; + } + else { + spliceIndex = index; } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature); } + } - /** - * Gets the localized diagnostic head message to use for errors when resolving a decorator as a call expression. - */ - function getDiagnosticHeadMessageForDecoratorResolution(node: Decorator) { - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return Diagnostics.Unable_to_resolve_signature_of_class_decorator_when_called_as_an_expression; + function isSpreadArgument(arg: Expression | undefined): arg is Expression { + return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).isSpread); + } - case SyntaxKind.Parameter: - return Diagnostics.Unable_to_resolve_signature_of_parameter_decorator_when_called_as_an_expression; + function getSpreadArgumentIndex(args: readonly Expression[]): number { + return findIndex(args, isSpreadArgument); + } - case SyntaxKind.PropertyDeclaration: - return Diagnostics.Unable_to_resolve_signature_of_property_decorator_when_called_as_an_expression; + function acceptsVoid(t: Type): boolean { + return !!(t.flags & TypeFlags.Void); + } - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return Diagnostics.Unable_to_resolve_signature_of_method_decorator_when_called_as_an_expression; + function acceptsVoidUndefinedUnknownOrAny(t: Type): boolean { + return !!(t.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Unknown | TypeFlags.Any)); + } - default: - return Debug.fail(); + function hasCorrectArity(node: CallLikeExpression, args: readonly Expression[], signature: Signature, signatureHelpTrailingComma = false) { + let argCount: number; + let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments + let effectiveParameterCount = getParameterCount(signature); + let effectiveMinimumArguments = getMinArgumentCount(signature); + + if (node.kind === SyntaxKind.TaggedTemplateExpression) { + argCount = args.length; + if (node.template.kind === SyntaxKind.TemplateExpression) { + // If a tagged template expression lacks a tail literal, the call is incomplete. + // Specifically, a template only can end in a TemplateTail or a Missing literal. + const lastSpan = last(node.template.templateSpans); // we should always have at least one span. + callIsIncomplete = nodeIsMissing(lastSpan.literal) || !!lastSpan.literal.isUnterminated; + } + else { + // If the template didn't end in a backtick, or its beginning occurred right prior to EOF, + // then this might actually turn out to be a TemplateHead in the future; + // so we consider the call to be incomplete. + const templateLiteral = node.template as LiteralExpression; + Debug.assert(templateLiteral.kind === SyntaxKind.NoSubstitutionTemplateLiteral); + callIsIncomplete = !!templateLiteral.isUnterminated; } } - - /** - * Resolves a decorator as if it were a call expression. - */ - function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - const funcType = checkExpression(node.expression); - const apparentType = getApparentType(funcType); - if (isErrorType(apparentType)) { - return resolveErrorCall(node); + else if (node.kind === SyntaxKind.Decorator) { + argCount = getDecoratorArgumentCount(node, signature); + } + else if (isJsxOpeningLikeElement(node)) { + callIsIncomplete = node.attributes.end === node.end; + if (callIsIncomplete) { + return true; } + argCount = effectiveMinimumArguments === 0 ? args.length : 1; + effectiveParameterCount = args.length === 0 ? effectiveParameterCount : 1; // class may have argumentless ctor functions - still resolve ctor and compare vs props member type + effectiveMinimumArguments = Math.min(effectiveMinimumArguments, 1); // sfc may specify context argument - handled by framework and not typechecked + } + else if (!node.arguments) { + // This only happens when we have something of the form: 'new C' + Debug.assert(node.kind === SyntaxKind.NewExpression); + return getMinArgumentCount(signature) === 0; + } + else { + argCount = signatureHelpTrailingComma ? args.length + 1 : args.length; + + // If we are missing the close parenthesis, the call is incomplete. + callIsIncomplete = node.arguments.end === node.end; - const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; - if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { - return resolveUntypedCall(node); + // If a spread argument is present, check that it corresponds to a rest parameter or at least that it's in the valid range. + const spreadArgIndex = getSpreadArgumentIndex(args); + if (spreadArgIndex >= 0) { + return spreadArgIndex >= getMinArgumentCount(signature) && (hasEffectiveRestParameter(signature) || spreadArgIndex < getParameterCount(signature)); } + } - if (isPotentiallyUncalledDecorator(node, callSignatures)) { - const nodeStr = getTextOfNode(node.expression, /*includeTrivia*/ false); - error(node, Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0, nodeStr); - return resolveErrorCall(node); + // Too many arguments implies incorrect arity. + if (!hasEffectiveRestParameter(signature) && argCount > effectiveParameterCount) { + return false; + } + + // If the call is incomplete, we should skip the lower bound check. + // JSX signatures can have extra parameters provided by the library which we don't check + if (callIsIncomplete || argCount >= effectiveMinimumArguments) { + return true; + } + for (let i = argCount; i < effectiveMinimumArguments; i++) { + const type = getTypeAtPosition(signature, i); + if (filterType(type, isInJSFile(node) && !strictNullChecks ? acceptsVoidUndefinedUnknownOrAny : acceptsVoid).flags & TypeFlags.Never) { + return false; } + } + return true; + } - const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); - if (!callSignatures.length) { - const errorDetails = invocationErrorDetails(node.expression, apparentType, SignatureKind.Call); - const messageChain = chainDiagnosticMessages(errorDetails.messageChain, headMessage); - const diag = createDiagnosticForNodeFromMessageChain(node.expression, messageChain); - if (errorDetails.relatedMessage) { - addRelatedInfo(diag, createDiagnosticForNode(node.expression, errorDetails.relatedMessage)); + function hasCorrectTypeArgumentArity(signature: Signature, typeArguments: NodeArray | undefined) { + // If the user supplied type arguments, but the number of type arguments does not match + // the declared number of type parameters, the call has an incorrect arity. + const numTypeParameters = length(signature.typeParameters); + const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters); + return !some(typeArguments) || + (typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters); + } + + // If type has a single call signature and no other members, return that signature. Otherwise, return undefined. + function getSingleCallSignature(type: Type): Signature | undefined { + return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false); + } + + function getSingleCallOrConstructSignature(type: Type): Signature | undefined { + return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) || + getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false); + } + + function getSingleSignature(type: Type, kind: SignatureKind, allowMembers: boolean): Signature | undefined { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + if (allowMembers || resolved.properties.length === 0 && resolved.indexInfos.length === 0) { + if (kind === SignatureKind.Call && resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0) { + return resolved.callSignatures[0]; + } + if (kind === SignatureKind.Construct && resolved.constructSignatures.length === 1 && resolved.callSignatures.length === 0) { + return resolved.constructSignatures[0]; } - diagnostics.add(diag); - invocationErrorRecovery(apparentType, SignatureKind.Call, diag); - return resolveErrorCall(node); } + } + return undefined; + } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage); + // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec) + function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, inferenceContext?: InferenceContext, compareTypes?: TypeComparer): Signature { + const context = createInferenceContext(signature.typeParameters!, signature, InferenceFlags.None, compareTypes); + // We clone the inferenceContext to avoid fixing. For example, when the source signature is (x: T) => T[] and + // the contextual signature is (...args: A) => B, we want to infer the element type of A's constraint (say 'any') + // for T but leave it possible to later infer '[any]' back to A. + const restType = getEffectiveRestType(contextualSignature); + const mapper = inferenceContext && (restType && restType.flags & TypeFlags.TypeParameter ? inferenceContext.nonFixingMapper : inferenceContext.mapper); + const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature; + applyToParameterTypes(sourceSignature, signature, (source, target) => { + // Type parameters from outer context referenced by source type are fixed by instantiation of the source type + inferTypes(context.inferences, source, target); + }); + if (!inferenceContext) { + applyToReturnTypes(contextualSignature, signature, (source, target) => { + inferTypes(context.inferences, source, target, InferencePriority.ReturnType); + }); } + return getSignatureInstantiation(signature, getInferredTypes(context), isInJSFile(contextualSignature.declaration)); + } - function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature { - const namespace = getJsxNamespaceAt(node); - const exports = namespace && getExportsOfSymbol(namespace); - // We fake up a SFC signature for each intrinsic, however a more specific per-element signature drawn from the JSX declaration - // file would probably be preferable. - const typeSymbol = exports && getSymbol(exports, JsxNames.Element, SymbolFlags.Type); - const returnNode = typeSymbol && nodeBuilder.symbolToEntityName(typeSymbol, SymbolFlags.Type, node); - const declaration = factory.createFunctionTypeNode(/*typeParameters*/ undefined, - [factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotdotdot*/ undefined, "props", /*questionMark*/ undefined, nodeBuilder.typeToTypeNode(result, node))], - returnNode ? factory.createTypeReferenceNode(returnNode, /*typeArguments*/ undefined) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ); - const parameterSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "props" as __String); - parameterSymbol.type = result; - return createSignature( - declaration, - /*typeParameters*/ undefined, - /*thisParameter*/ undefined, - [parameterSymbol], - typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType, - /*returnTypePredicate*/ undefined, - 1, - SignatureFlags.None - ); + function inferJsxTypeArguments(node: JsxOpeningLikeElement, signature: Signature, checkMode: CheckMode, context: InferenceContext): Type[] { + const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); + const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context, checkMode); + inferTypes(context.inferences, checkAttrType, paramType); + return getInferredTypes(context); + } + + function getThisArgumentType(thisArgumentNode: LeftHandSideExpression | undefined) { + if (!thisArgumentNode) { + return voidType; + } + const thisArgumentType = checkExpression(thisArgumentNode); + return isOptionalChainRoot(thisArgumentNode.parent) ? getNonNullableType(thisArgumentType) : + isOptionalChain(thisArgumentNode.parent) ? removeOptionalTypeMarker(thisArgumentType) : + thisArgumentType; + } + + function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): Type[] { + if (isJsxOpeningLikeElement(node)) { + return inferJsxTypeArguments(node, signature, checkMode, context); } - function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - if (isJsxIntrinsicIdentifier(node.tagName)) { - const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); - const fakeSignature = createSignatureForJSXIntrinsic(node, result); - checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*mapper*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes); - if (length(node.typeArguments)) { - forEach(node.typeArguments, checkSourceElement); - diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), node.typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(node.typeArguments))); + // If a contextual type is available, infer from that type to the return type of the call expression. For + // example, given a 'function wrap(cb: (x: T) => U): (x: T) => U' and a call expression + // 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the + // return type of 'wrap'. + if (node.kind !== SyntaxKind.Decorator) { + const contextualType = getContextualType(node, every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)) ? ContextFlags.SkipBindingPatterns : ContextFlags.None); + if (contextualType) { + // We clone the inference context to avoid disturbing a resolution in progress for an + // outer call expression. Effectively we just want a snapshot of whatever has been + // inferred for any outer call expression so far. + const outerContext = getInferenceContext(node); + const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault)); + const instantiatedType = instantiateType(contextualType, outerMapper); + // If the contextual type is a generic function type with a single call signature, we + // instantiate the type with its own type parameters and type arguments. This ensures that + // the type parameters are not erased to type any during type inference such that they can + // be inferred as actual types from the contextual type. For example: + // declare function arrayMap(f: (x: T) => U): (a: T[]) => U[]; + // const boxElements: (a: A[]) => { value: A }[] = arrayMap(value => ({ value })); + // Above, the type of the 'value' parameter is inferred to be 'A'. + const contextualSignature = getSingleCallSignature(instantiatedType); + const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ? + getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) : + instantiatedType; + const inferenceTargetType = getReturnTypeOfSignature(signature); + // Inferences made from return types have lower priority than all other inferences. + inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); + // Create a type mapper for instantiating generic contextual types using the inferences made + // from the return type. We need a separate inference pass here because (a) instantiation of + // the source type uses the outer context's return mapper (which excludes inferences made from + // outer arguments), and (b) we don't want any further inferences going into this context. + const returnContext = createInferenceContext(signature.typeParameters!, signature, context.flags); + const returnSourceType = instantiateType(contextualType, outerContext && outerContext.returnMapper); + inferTypes(returnContext.inferences, returnSourceType, inferenceTargetType); + context.returnMapper = some(returnContext.inferences, hasInferenceCandidates) ? getMapperFromContext(cloneInferredPartOfContext(returnContext)) : undefined; + } + } + + const restType = getNonArrayRestType(signature); + const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; + if (restType && restType.flags & TypeFlags.TypeParameter) { + const info = find(context.inferences, info => info.typeParameter === restType); + if (info) { + info.impliedArity = findIndex(args, isSpreadArgument, argCount) < 0 ? args.length - argCount : undefined; + } + } + + const thisType = getThisTypeOfSignature(signature); + if (thisType) { + const thisArgumentNode = getThisArgumentOfCall(node); + inferTypes(context.inferences, getThisArgumentType(thisArgumentNode), thisType); + } + + for (let i = 0; i < argCount; i++) { + const arg = args[i]; + if (arg.kind !== SyntaxKind.OmittedExpression) { + const paramType = getTypeAtPosition(signature, i); + const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode); + inferTypes(context.inferences, argType, paramType); + } + } + + if (restType) { + const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, context, checkMode); + inferTypes(context.inferences, spreadType, restType); + } + + return getInferredTypes(context); + } + + function getMutableArrayOrTupleType(type: Type) { + return type.flags & TypeFlags.Union ? mapType(type, getMutableArrayOrTupleType) : + type.flags & TypeFlags.Any || isMutableArrayOrTuple(getBaseConstraintOfType(type) || type) ? type : + isTupleType(type) ? createTupleType(getTypeArguments(type), type.target.elementFlags, /*readonly*/ false, type.target.labeledElementDeclarations) : + createTupleType([type], [ElementFlags.Variadic]); + } + + function getSpreadArgumentType(args: readonly Expression[], index: number, argCount: number, restType: Type, context: InferenceContext | undefined, checkMode: CheckMode) { + if (index >= argCount - 1) { + const arg = args[argCount - 1]; + if (isSpreadArgument(arg)) { + // We are inferring from a spread expression in the last argument position, i.e. both the parameter + // and the argument are ...x forms. + return getMutableArrayOrTupleType(arg.kind === SyntaxKind.SyntheticExpression ? (arg as SyntheticExpression).type : + checkExpressionWithContextualType((arg as SpreadElement).expression, restType, context, checkMode)); + } + } + const types = []; + const flags = []; + const names = []; + for (let i = index; i < argCount; i++) { + const arg = args[i]; + if (isSpreadArgument(arg)) { + const spreadType = arg.kind === SyntaxKind.SyntheticExpression ? (arg as SyntheticExpression).type : checkExpression((arg as SpreadElement).expression); + if (isArrayLikeType(spreadType)) { + types.push(spreadType); + flags.push(ElementFlags.Variadic); + } + else { + types.push(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, arg.kind === SyntaxKind.SpreadElement ? (arg as SpreadElement).expression : arg)); + flags.push(ElementFlags.Rest); } - return fakeSignature; } - const exprTypes = checkExpression(node.tagName); - const apparentType = getApparentType(exprTypes); - if (isErrorType(apparentType)) { - return resolveErrorCall(node); + else { + const contextualType = getIndexedAccessType(restType, getNumberLiteralType(i - index), AccessFlags.Contextual); + const argType = checkExpressionWithContextualType(arg, contextualType, context, checkMode); + const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping); + types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType)); + flags.push(ElementFlags.Required); } - - const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node); - if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { - return resolveUntypedCall(node); + if (arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).tupleNameSource) { + names.push((arg as SyntheticExpression).tupleNameSource!); } + } + return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined); + } - if (signatures.length === 0) { - // We found no signatures at all, which is an error - error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); - return resolveErrorCall(node); + function checkTypeArguments(signature: Signature, typeArgumentNodes: readonly TypeNode[], reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined { + const isJavascript = isInJSFile(signature.declaration); + const typeParameters = signature.typeParameters!; + const typeArgumentTypes = fillMissingTypeArguments(map(typeArgumentNodes, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isJavascript); + let mapper: TypeMapper | undefined; + for (let i = 0; i < typeArgumentNodes.length; i++) { + Debug.assert(typeParameters[i] !== undefined, "Should not call checkTypeArguments with too many type arguments"); + const constraint = getConstraintOfTypeParameter(typeParameters[i]); + if (constraint) { + const errorInfo = reportErrors && headMessage ? (() => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1)) : undefined; + const typeArgumentHeadMessage = headMessage || Diagnostics.Type_0_does_not_satisfy_the_constraint_1; + if (!mapper) { + mapper = createTypeMapper(typeParameters, typeArgumentTypes); + } + const typeArgument = typeArgumentTypes[i]; + if (!checkTypeAssignableTo( + typeArgument, + getTypeWithThisArgument(instantiateType(constraint, mapper), typeArgument), + reportErrors ? typeArgumentNodes[i] : undefined, + typeArgumentHeadMessage, + errorInfo)) { + return undefined; + } } - - return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None); } + return typeArgumentTypes; + } - /** - * Sometimes, we have a decorator that could accept zero arguments, - * but is receiving too many arguments as part of the decorator invocation. - * In those cases, a user may have meant to *call* the expression before using it as a decorator. - */ - function isPotentiallyUncalledDecorator(decorator: Decorator, signatures: readonly Signature[]) { - return signatures.length && every(signatures, signature => - signature.minArgumentCount === 0 && - !signatureHasRestParameter(signature) && - signature.parameters.length < getDecoratorArgumentCount(decorator, signature)); + function getJsxReferenceKind(node: JsxOpeningLikeElement): JsxReferenceKind { + if (isJsxIntrinsicIdentifier(node.tagName)) { + return JsxReferenceKind.Mixed; } - - function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - switch (node.kind) { - case SyntaxKind.CallExpression: - return resolveCallExpression(node, candidatesOutArray, checkMode); - case SyntaxKind.NewExpression: - return resolveNewExpression(node, candidatesOutArray, checkMode); - case SyntaxKind.TaggedTemplateExpression: - return resolveTaggedTemplateExpression(node, candidatesOutArray, checkMode); - case SyntaxKind.Decorator: - return resolveDecorator(node, candidatesOutArray, checkMode); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxSelfClosingElement: - return resolveJsxOpeningLikeElement(node, candidatesOutArray, checkMode); - } - throw Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable."); + const tagType = getApparentType(checkExpression(node.tagName)); + if (length(getSignaturesOfType(tagType, SignatureKind.Construct))) { + return JsxReferenceKind.Component; } + if (length(getSignaturesOfType(tagType, SignatureKind.Call))) { + return JsxReferenceKind.Function; + } + return JsxReferenceKind.Mixed; + } - /** - * Resolve a signature of a given call-like expression. - * @param node a call-like expression to try resolve a signature for - * @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service; - * the function will fill it up with appropriate candidate signatures - * @return a signature of the call-like expression or undefined if one can't be found - */ - function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, checkMode?: CheckMode): Signature { - const links = getNodeLinks(node); - // If getResolvedSignature has already been called, we will have cached the resolvedSignature. - // However, it is possible that either candidatesOutArray was not passed in the first time, - // or that a different candidatesOutArray was passed in. Therefore, we need to redo the work - // to correctly fill the candidatesOutArray. - const cached = links.resolvedSignature; - if (cached && cached !== resolvingSignature && !candidatesOutArray) { - return cached; + /** + * Check if the given signature can possibly be a signature called by the JSX opening-like element. + * @param node a JSX opening-like element we are trying to figure its call signature + * @param signature a candidate signature we are trying whether it is a call signature + * @param relation a relationship to check parameter and argument type + */ + function checkApplicableSignatureForJsxOpeningLikeElement( + node: JsxOpeningLikeElement, + signature: Signature, + relation: ESMap, + checkMode: CheckMode, + reportErrors: boolean, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } + ) { + // Stateless function components can have maximum of three arguments: "props", "context", and "updater". + // However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props, + // can be specified by users through attributes property. + const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); + const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode); + return checkTagNameDoesNotExpectTooManyArguments() && checkTypeRelatedToAndOptionallyElaborate( + attributesType, + paramType, + relation, + reportErrors ? node.tagName : undefined, + node.attributes, + /*headMessage*/ undefined, + containingMessageChain, + errorOutputContainer); + + function checkTagNameDoesNotExpectTooManyArguments(): boolean { + if (getJsxNamespaceContainerForImplicitImport(node)) { + return true; // factory is implicitly jsx/jsxdev - assume it fits the bill, since we don't strongly look for the jsx/jsxs/jsxDEV factory APIs anywhere else (at least not yet) + } + const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicIdentifier(node.tagName) ? checkExpression(node.tagName) : undefined; + if (!tagType) { + return true; } - links.resolvedSignature = resolvingSignature; - const result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal); - // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call - // resolution should be deferred. - if (result !== resolvingSignature) { - // If signature resolution originated in control flow type analysis (for example to compute the - // assigned type in a flow assignment) we don't cache the result as it may be based on temporary - // types from the control flow analysis. - links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached; + const tagCallSignatures = getSignaturesOfType(tagType, SignatureKind.Call); + if (!length(tagCallSignatures)) { + return true; } - return result; - } - - /** - * Indicates whether a declaration can be treated as a constructor in a JavaScript - * file. - */ - function isJSConstructor(node: Node | undefined): node is FunctionDeclaration | FunctionExpression { - if (!node || !isInJSFile(node)) { - return false; + const factory = getJsxFactoryEntity(node); + if (!factory) { + return true; + } + const factorySymbol = resolveEntityName(factory, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, node); + if (!factorySymbol) { + return true; } - const func = isFunctionDeclaration(node) || isFunctionExpression(node) ? node : - isVariableDeclaration(node) && node.initializer && isFunctionExpression(node.initializer) ? node.initializer : - undefined; - if (func) { - // If the node has a @class tag, treat it like a constructor. - if (getJSDocClassTag(node)) return true; - // If the symbol of the node has members, treat it like a constructor. - const symbol = getSymbolOfNode(func); - return !!symbol?.members?.size; + const factoryType = getTypeOfSymbol(factorySymbol); + const callSignatures = getSignaturesOfType(factoryType, SignatureKind.Call); + if (!length(callSignatures)) { + return true; } - return false; - } - function mergeJSSymbols(target: Symbol, source: Symbol | undefined) { - if (source) { - const links = getSymbolLinks(source); - if (!links.inferredClassSymbol || !links.inferredClassSymbol.has(getSymbolId(target))) { - const inferred = isTransientSymbol(target) ? target : cloneSymbol(target) as TransientSymbol; - inferred.exports = inferred.exports || createSymbolTable(); - inferred.members = inferred.members || createSymbolTable(); - inferred.flags |= source.flags & SymbolFlags.Class; - if (source.exports?.size) { - mergeSymbolTable(inferred.exports, source.exports); + let hasFirstParamSignatures = false; + let maxParamCount = 0; + // Check that _some_ first parameter expects a FC-like thing, and that some overload of the SFC expects an acceptable number of arguments + for (const sig of callSignatures) { + const firstparam = getTypeAtPosition(sig, 0); + const signaturesOfParam = getSignaturesOfType(firstparam, SignatureKind.Call); + if (!length(signaturesOfParam)) continue; + for (const paramSig of signaturesOfParam) { + hasFirstParamSignatures = true; + if (hasEffectiveRestParameter(paramSig)) { + return true; // some signature has a rest param, so function components can have an arbitrary number of arguments } - if (source.members?.size) { - mergeSymbolTable(inferred.members, source.members); + const paramCount = getParameterCount(paramSig); + if (paramCount > maxParamCount) { + maxParamCount = paramCount; } - (links.inferredClassSymbol || (links.inferredClassSymbol = new Map())).set(getSymbolId(inferred), inferred); - return inferred; } - return links.inferredClassSymbol.get(getSymbolId(target)); - } - } - - function getAssignedClassSymbol(decl: Declaration): Symbol | undefined { - const assignmentSymbol = decl && getSymbolOfExpando(decl, /*allowDeclaration*/ true); - const prototype = assignmentSymbol?.exports?.get("prototype" as __String); - const init = prototype?.valueDeclaration && getAssignedJSPrototype(prototype.valueDeclaration); - return init ? getSymbolOfNode(init) : undefined; - } - - function getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined { - if (!node.parent) { - return undefined; } - let name: Expression | BindingName | undefined; - let decl: Node | undefined; - if (isVariableDeclaration(node.parent) && node.parent.initializer === node) { - if (!isInJSFile(node) && !(isVarConst(node.parent) && isFunctionLikeDeclaration(node))) { - return undefined; - } - name = node.parent.name; - decl = node.parent; + if (!hasFirstParamSignatures) { + // Not a single signature had a first parameter which expected a signature - for back compat, and + // to guard against generic factories which won't have signatures directly, do not error + return true; } - else if (isBinaryExpression(node.parent)) { - const parentNode = node.parent; - const parentNodeOperator = node.parent.operatorToken.kind; - if (parentNodeOperator === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.right === node)) { - name = parentNode.left; - decl = name; - } - else if (parentNodeOperator === SyntaxKind.BarBarToken || parentNodeOperator === SyntaxKind.QuestionQuestionToken) { - if (isVariableDeclaration(parentNode.parent) && parentNode.parent.initializer === parentNode) { - name = parentNode.parent.name; - decl = parentNode.parent; - } - else if (isBinaryExpression(parentNode.parent) && parentNode.parent.operatorToken.kind === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.parent.right === parentNode)) { - name = parentNode.parent.left; - decl = name; - } - - if (!name || !isBindableStaticNameExpression(name) || !isSameEntityName(name, parentNode.left)) { - return undefined; - } + let absoluteMinArgCount = Infinity; + for (const tagSig of tagCallSignatures) { + const tagRequiredArgCount = getMinArgumentCount(tagSig); + if (tagRequiredArgCount < absoluteMinArgCount) { + absoluteMinArgCount = tagRequiredArgCount; } } - else if (allowDeclaration && isFunctionDeclaration(node)) { - name = node.name; - decl = node; + if (absoluteMinArgCount <= maxParamCount) { + return true; // some signature accepts the number of arguments the function component provides } - if (!decl || !name || (!allowDeclaration && !getExpandoInitializer(node, isPrototypeAccess(name)))) { - return undefined; + if (reportErrors) { + const diag = createDiagnosticForNode(node.tagName, Diagnostics.Tag_0_expects_at_least_1_arguments_but_the_JSX_factory_2_provides_at_most_3, entityNameToString(node.tagName), absoluteMinArgCount, entityNameToString(factory), maxParamCount); + const tagNameDeclaration = getSymbolAtLocation(node.tagName)?.valueDeclaration; + if (tagNameDeclaration) { + addRelatedInfo(diag, createDiagnosticForNode(tagNameDeclaration, Diagnostics._0_is_declared_here, entityNameToString(node.tagName))); + } + if (errorOutputContainer && errorOutputContainer.skipLogging) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } + if (!errorOutputContainer.skipLogging) { + diagnostics.add(diag); + } } - return getSymbolOfNode(decl); + return false; } + } - - function getAssignedJSPrototype(node: Node) { - if (!node.parent) { - return false; + function getSignatureApplicabilityError( + node: CallLikeExpression, + args: readonly Expression[], + signature: Signature, + relation: ESMap, + checkMode: CheckMode, + reportErrors: boolean, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + ): readonly Diagnostic[] | undefined { + + const errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } = { errors: undefined, skipLogging: true }; + if (isJsxOpeningLikeElement(node)) { + if (!checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors, containingMessageChain, errorOutputContainer)) { + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "jsx should have errors when reporting errors"); + return errorOutputContainer.errors || emptyArray; } - let parent: Node = node.parent; - while (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { - parent = parent.parent; + return undefined; + } + const thisType = getThisTypeOfSignature(signature); + if (thisType && thisType !== voidType && node.kind !== SyntaxKind.NewExpression) { + // If the called expression is not of the form `x.f` or `x["f"]`, then sourceType = voidType + // If the signature's 'this' type is voidType, then the check is skipped -- anything is compatible. + // If the expression is a new expression, then the check is skipped. + const thisArgumentNode = getThisArgumentOfCall(node); + const thisArgumentType = getThisArgumentType(thisArgumentNode); + const errorNode = reportErrors ? (thisArgumentNode || node) : undefined; + const headMessage = Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1; + if (!checkTypeRelatedTo(thisArgumentType, thisType, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer)) { + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "this parameter should have errors when reporting errors"); + return errorOutputContainer.errors || emptyArray; + } + } + const headMessage = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1; + const restType = getNonArrayRestType(signature); + const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; + for (let i = 0; i < argCount; i++) { + const arg = args[i]; + if (arg.kind !== SyntaxKind.OmittedExpression) { + const paramType = getTypeAtPosition(signature, i); + const argType = checkExpressionWithContextualType(arg, paramType, /*inferenceContext*/ undefined, checkMode); + // If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive), + // we obtain the regular type of any object literal arguments because we may not have inferred complete + // parameter types yet and therefore excess property checks may yield false positives (see #17041). + const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; + if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? arg : undefined, arg, headMessage, containingMessageChain, errorOutputContainer)) { + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "parameter should have errors when reporting errors"); + maybeAddMissingAwaitInfo(arg, checkArgType, paramType); + return errorOutputContainer.errors || emptyArray; + } } - if (parent && isBinaryExpression(parent) && isPrototypeAccess(parent.left) && parent.operatorToken.kind === SyntaxKind.EqualsToken) { - const right = getInitializerOfBinaryExpression(parent); - return isObjectLiteralExpression(right) && right; + } + if (restType) { + const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, /*context*/ undefined, checkMode); + const restArgCount = args.length - argCount; + const errorNode = !reportErrors ? undefined : + restArgCount === 0 ? node : + restArgCount === 1 ? args[argCount] : + setTextRangePosEnd(createSyntheticExpression(node, spreadType), args[argCount].pos, args[args.length - 1].end); + if (!checkTypeRelatedTo(spreadType, restType, relation, errorNode, headMessage, /*containingMessageChain*/ undefined, errorOutputContainer)) { + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "rest parameter should have errors when reporting errors"); + maybeAddMissingAwaitInfo(errorNode, spreadType, restType); + return errorOutputContainer.errors || emptyArray; } } + return undefined; - /** - * Syntactically and semantically checks a call or new expression. - * @param node The call/new expression to be checked. - * @returns On success, the expression's signature's return type. On failure, anyType. - */ - function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type { - if (!checkGrammarTypeArguments(node, node.typeArguments)) checkGrammarArguments(node.arguments); - - const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode); - if (signature === resolvingSignature) { - // CheckMode.SkipGenericFunctions is enabled and this is a call to a generic function that - // returns a function type. We defer checking and return nonInferrableType. - return nonInferrableType; + function maybeAddMissingAwaitInfo(errorNode: Node | undefined, source: Type, target: Type) { + if (errorNode && reportErrors && errorOutputContainer.errors && errorOutputContainer.errors.length) { + // Bail if target is Promise-like---something else is wrong + if (getAwaitedTypeOfPromise(target)) { + return; + } + const awaitedTypeOfSource = getAwaitedTypeOfPromise(source); + if (awaitedTypeOfSource && isTypeRelatedTo(awaitedTypeOfSource, target, relation)) { + addRelatedInfo(errorOutputContainer.errors[0], createDiagnosticForNode(errorNode, Diagnostics.Did_you_forget_to_use_await)); + } } + } + } - checkDeprecatedSignature(signature, node); - - if (node.expression.kind === SyntaxKind.SuperKeyword) { - return voidType; + /** + * Returns the this argument in calls like x.f(...) and x[f](...). Undefined otherwise. + */ + function getThisArgumentOfCall(node: CallLikeExpression): LeftHandSideExpression | undefined { + const expression = node.kind === SyntaxKind.CallExpression ? node.expression : + node.kind === SyntaxKind.TaggedTemplateExpression ? node.tag : undefined; + if (expression) { + const callee = skipOuterExpressions(expression); + if (isAccessExpression(callee)) { + return callee.expression; } + } + } - if (node.kind === SyntaxKind.NewExpression) { - const declaration = signature.declaration; - - if (declaration && - declaration.kind !== SyntaxKind.Constructor && - declaration.kind !== SyntaxKind.ConstructSignature && - declaration.kind !== SyntaxKind.ConstructorType && - !isJSDocConstructSignature(declaration) && - !isJSConstructor(declaration)) { + function createSyntheticExpression(parent: Node, type: Type, isSpread?: boolean, tupleNameSource?: ParameterDeclaration | NamedTupleMember) { + const result = parseNodeFactory.createSyntheticExpression(type, isSpread, tupleNameSource); + setTextRange(result, parent); + setParent(result, parent); + return result; + } - // When resolved signature is a call signature (and not a construct signature) the result type is any - if (noImplicitAny) { - error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); - } - return anyType; + /** + * Returns the effective arguments for an expression that works like a function invocation. + */ + function getEffectiveCallArguments(node: CallLikeExpression): readonly Expression[] { + if (node.kind === SyntaxKind.TaggedTemplateExpression) { + const template = node.template; + const args: Expression[] = [createSyntheticExpression(template, getGlobalTemplateStringsArrayType())]; + if (template.kind === SyntaxKind.TemplateExpression) { + forEach(template.templateSpans, span => { + args.push(span.expression); + }); + } + return args; + } + if (node.kind === SyntaxKind.Decorator) { + return getEffectiveDecoratorArguments(node); + } + if (isJsxOpeningLikeElement(node)) { + return node.attributes.properties.length > 0 || (isJsxOpeningElement(node) && node.parent.children.length > 0) ? [node.attributes] : emptyArray; + } + const args = node.arguments || emptyArray; + const spreadIndex = getSpreadArgumentIndex(args); + if (spreadIndex >= 0) { + // Create synthetic arguments from spreads of tuple types. + const effectiveArgs = args.slice(0, spreadIndex); + for (let i = spreadIndex; i < args.length; i++) { + const arg = args[i]; + // We can call checkExpressionCached because spread expressions never have a contextual type. + const spreadType = arg.kind === SyntaxKind.SpreadElement && (flowLoopCount ? checkExpression((arg as SpreadElement).expression) : checkExpressionCached((arg as SpreadElement).expression)); + if (spreadType && isTupleType(spreadType)) { + forEach(getTypeArguments(spreadType), (t, i) => { + const flags = spreadType.target.elementFlags[i]; + const syntheticArg = createSyntheticExpression(arg, flags & ElementFlags.Rest ? createArrayType(t) : t, + !!(flags & ElementFlags.Variable), spreadType.target.labeledElementDeclarations?.[i]); + effectiveArgs.push(syntheticArg); + }); + } + else { + effectiveArgs.push(arg); } } + return effectiveArgs; + } + return args; + } - // In JavaScript files, calls to any identifier 'require' are treated as external module imports - if (isInJSFile(node) && isCommonJsRequire(node)) { - return resolveExternalModuleTypeByLiteral(node.arguments![0] as StringLiteral); - } + /** + * Returns the synthetic argument list for a decorator invocation. + */ + function getEffectiveDecoratorArguments(node: Decorator): readonly Expression[] { + const parent = node.parent; + const expr = node.expression; + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + // For a class decorator, the `target` is the type of the class (e.g. the + // "static" or "constructor" side of the class). + return [ + createSyntheticExpression(expr, getTypeOfSymbol(getSymbolOfNode(parent))) + ]; + case SyntaxKind.Parameter: + // A parameter declaration decorator will have three arguments (see + // `ParameterDecorator` in core.d.ts). + const func = parent.parent as FunctionLikeDeclaration; + return [ + createSyntheticExpression(expr, parent.parent.kind === SyntaxKind.Constructor ? getTypeOfSymbol(getSymbolOfNode(func)) : errorType), + createSyntheticExpression(expr, anyType), + createSyntheticExpression(expr, numberType) + ]; + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // A method or accessor declaration decorator will have two or three arguments (see + // `PropertyDecorator` and `MethodDecorator` in core.d.ts). If we are emitting decorators + // for ES3, we will only pass two arguments. + const hasPropDesc = parent.kind !== SyntaxKind.PropertyDeclaration && languageVersion !== ScriptTarget.ES3; + return [ + createSyntheticExpression(expr, getParentTypeOfClassElement(parent as ClassElement)), + createSyntheticExpression(expr, getClassElementPropertyKeyType(parent as ClassElement)), + createSyntheticExpression(expr, hasPropDesc ? createTypedPropertyDescriptorType(getTypeOfNode(parent)) : anyType) + ]; + } + return Debug.fail(); + } - const returnType = getReturnTypeOfSignature(signature); - // Treat any call to the global 'Symbol' function that is part of a const variable or readonly property - // as a fresh unique symbol literal type. - if (returnType.flags & TypeFlags.ESSymbolLike && isSymbolOrSymbolForCall(node)) { - return getESSymbolLikeTypeForNode(walkUpParenthesizedExpressions(node.parent)); - } - if (node.kind === SyntaxKind.CallExpression && !node.questionDotToken && node.parent.kind === SyntaxKind.ExpressionStatement && - returnType.flags & TypeFlags.Void && getTypePredicateOfSignature(signature)) { - if (!isDottedName(node.expression)) { - error(node.expression, Diagnostics.Assertions_require_the_call_target_to_be_an_identifier_or_qualified_name); - } - else if (!getEffectsSignature(node)) { - const diagnostic = error(node.expression, Diagnostics.Assertions_require_every_name_in_the_call_target_to_be_declared_with_an_explicit_type_annotation); - getTypeOfDottedName(node.expression, diagnostic); - } - } + /** + * Returns the argument count for a decorator node that works like a function invocation. + */ + function getDecoratorArgumentCount(node: Decorator, signature: Signature) { + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return 1; + case SyntaxKind.PropertyDeclaration: + return 2; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // For ES3 or decorators with only two parameters we supply only two arguments + return languageVersion === ScriptTarget.ES3 || signature.parameters.length <= 2 ? 2 : 3; + case SyntaxKind.Parameter: + return 3; + default: + return Debug.fail(); + } + } + function getDiagnosticSpanForCallNode(node: CallExpression, doNotIncludeArguments?: boolean) { + let start: number; + let length: number; + const sourceFile = getSourceFileOfNode(node); + + if (isPropertyAccessExpression(node.expression)) { + const nameSpan = getErrorSpanForNode(sourceFile, node.expression.name); + start = nameSpan.start; + length = doNotIncludeArguments ? nameSpan.length : node.end - start; + } + else { + const expressionSpan = getErrorSpanForNode(sourceFile, node.expression); + start = expressionSpan.start; + length = doNotIncludeArguments ? expressionSpan.length : node.end - start; + } + return { start, length, sourceFile }; + } + function getDiagnosticForCallNode(node: CallLikeExpression, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation { + if (isCallExpression(node)) { + const { sourceFile, start, length } = getDiagnosticSpanForCallNode(node); + return createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2, arg3); + } + else { + return createDiagnosticForNode(node, message, arg0, arg1, arg2, arg3); + } + } - if (isInJSFile(node)) { - const jsSymbol = getSymbolOfExpando(node, /*allowDeclaration*/ false); - if (jsSymbol?.exports?.size) { - const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, emptyArray); - jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral; - return getIntersectionType([returnType, jsAssignmentType]); - } - } + function isPromiseResolveArityError(node: CallLikeExpression) { + if (!isCallExpression(node) || !isIdentifier(node.expression)) return false; - return returnType; + const symbol = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, undefined, undefined, false); + const decl = symbol?.valueDeclaration; + if (!decl || !isParameter(decl) || !isFunctionExpressionOrArrowFunction(decl.parent) || !isNewExpression(decl.parent.parent) || !isIdentifier(decl.parent.parent.expression)) { + return false; } - function checkDeprecatedSignature(signature: Signature, node: CallLikeExpression) { - if (signature.declaration && signature.declaration.flags & NodeFlags.Deprecated) { - const suggestionNode = getDeprecatedSuggestionNode(node); - const name = tryGetPropertyAccessOrIdentifierToString(getInvokedExpression(node)); - addDeprecatedSuggestionWithSignature(suggestionNode, signature.declaration, name, signatureToString(signature)); + const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); + if (!globalPromiseSymbol) return false; + + const constructorSymbol = getSymbolAtLocation(decl.parent.parent.expression, /*ignoreErrors*/ true); + return constructorSymbol === globalPromiseSymbol; + } + + function getArgumentArityError(node: CallLikeExpression, signatures: readonly Signature[], args: readonly Expression[]) { + const spreadIndex = getSpreadArgumentIndex(args); + if (spreadIndex > -1) { + return createDiagnosticForNode(args[spreadIndex], Diagnostics.A_spread_argument_must_either_have_a_tuple_type_or_be_passed_to_a_rest_parameter); + } + let min = Number.POSITIVE_INFINITY; // smallest parameter count + let max = Number.NEGATIVE_INFINITY; // largest parameter count + let maxBelow = Number.NEGATIVE_INFINITY; // largest parameter count that is smaller than the number of arguments + let minAbove = Number.POSITIVE_INFINITY; // smallest parameter count that is larger than the number of arguments + + let closestSignature: Signature | undefined; + for (const sig of signatures) { + const minParameter = getMinArgumentCount(sig); + const maxParameter = getParameterCount(sig); + // smallest/largest parameter counts + if (minParameter < min) { + min = minParameter; + closestSignature = sig; + } + max = Math.max(max, maxParameter); + // shortest parameter count *longer than the call*/longest parameter count *shorter than the call* + if (minParameter < args.length && minParameter > maxBelow) maxBelow = minParameter; + if (args.length < maxParameter && maxParameter < minAbove) minAbove = maxParameter; + } + const hasRestParameter = some(signatures, hasEffectiveRestParameter); + const parameterRange = hasRestParameter ? min + : min < max ? min + "-" + max + : min; + const error = hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 + : parameterRange === 1 && args.length === 0 && isPromiseResolveArityError(node) ? Diagnostics.Expected_0_arguments_but_got_1_Did_you_forget_to_include_void_in_your_type_argument_to_Promise + : Diagnostics.Expected_0_arguments_but_got_1; + if (min < args.length && args.length < max) { + // between min and max, but with no matching overload + return getDiagnosticForCallNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, args.length, maxBelow, minAbove); + } + else if (args.length < min) { + // too short: put the error span on the call expression, not any of the args + const diagnostic = getDiagnosticForCallNode(node, error, parameterRange, args.length); + const parameter = closestSignature?.declaration?.parameters[closestSignature.thisParameter ? args.length + 1 : args.length]; + if (parameter) { + const parameterError = createDiagnosticForNode( + parameter, + isBindingPattern(parameter.name) ? Diagnostics.An_argument_matching_this_binding_pattern_was_not_provided + : isRestParameter(parameter) ? Diagnostics.Arguments_for_the_rest_parameter_0_were_not_provided + : Diagnostics.An_argument_for_0_was_not_provided, + !parameter.name ? args.length : !isBindingPattern(parameter.name) ? idText(getFirstIdentifier(parameter.name)) : undefined + ); + return addRelatedInfo(diagnostic, parameterError); } + return diagnostic; } - - function getDeprecatedSuggestionNode(node: Node): Node { - node = skipParentheses(node); - switch (node.kind) { - case SyntaxKind.CallExpression: - case SyntaxKind.Decorator: - case SyntaxKind.NewExpression: - return getDeprecatedSuggestionNode((node as Decorator | CallExpression | NewExpression).expression); - case SyntaxKind.TaggedTemplateExpression: - return getDeprecatedSuggestionNode((node as TaggedTemplateExpression).tag); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxSelfClosingElement: - return getDeprecatedSuggestionNode((node as JsxOpeningLikeElement).tagName); - case SyntaxKind.ElementAccessExpression: - return (node as ElementAccessExpression).argumentExpression; - case SyntaxKind.PropertyAccessExpression: - return (node as PropertyAccessExpression).name; - case SyntaxKind.TypeReference: - const typeReference = node as TypeReferenceNode; - return isQualifiedName(typeReference.typeName) ? typeReference.typeName.right : typeReference; - default: - return node; + else { + // too long; error goes on the excess parameters + const errorSpan = factory.createNodeArray(args.slice(max)); + const pos = first(errorSpan).pos; + let end = last(errorSpan).end; + if (end === pos) { + end++; } + setTextRangePosEnd(errorSpan, pos, end); + return createDiagnosticForNodeArray(getSourceFileOfNode(node), errorSpan, error, parameterRange, args.length); } + } - function isSymbolOrSymbolForCall(node: Node) { - if (!isCallExpression(node)) return false; - let left = node.expression; - if (isPropertyAccessExpression(left) && left.name.escapedText === "for") { - left = left.expression; - } - if (!isIdentifier(left) || left.escapedText !== "Symbol") { - return false; + function getTypeArgumentArityError(node: Node, signatures: readonly Signature[], typeArguments: NodeArray) { + const argCount = typeArguments.length; + // No overloads exist + if (signatures.length === 1) { + const sig = signatures[0]; + const min = getMinTypeArgumentCount(sig.typeParameters); + const max = length(sig.typeParameters); + return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, min < max ? min + "-" + max : min , argCount); + } + // Overloads exist + let belowArgCount = -Infinity; + let aboveArgCount = Infinity; + for (const sig of signatures) { + const min = getMinTypeArgumentCount(sig.typeParameters); + const max = length(sig.typeParameters); + if (min > argCount) { + aboveArgCount = Math.min(aboveArgCount, min); } - - // make sure `Symbol` is the global symbol - const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); - if (!globalESSymbol) { - return false; + else if (max < argCount) { + belowArgCount = Math.max(belowArgCount, max); } - - return globalESSymbol === resolveName(left, "Symbol" as __String, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); } + if (belowArgCount !== -Infinity && aboveArgCount !== Infinity) { + return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.No_overload_expects_0_type_arguments_but_overloads_do_exist_that_expect_either_1_or_2_type_arguments, argCount, belowArgCount, aboveArgCount); + } + return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); + } - function checkImportCallExpression(node: ImportCall): Type { - // Check grammar of dynamic import - if (!checkGrammarArguments(node.arguments)) checkGrammarImportCallExpression(node); - - if (node.arguments.length === 0) { - return createPromiseReturnType(node, anyType); - } + function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, fallbackError?: DiagnosticMessage): Signature { + const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; + const isDecorator = node.kind === SyntaxKind.Decorator; + const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); + const reportErrors = !candidatesOutArray && produceDiagnostics; - const specifier = node.arguments[0]; - const specifierType = checkExpressionCached(specifier); - const optionsType = node.arguments.length > 1 ? checkExpressionCached(node.arguments[1]) : undefined; - // Even though multiple arguments is grammatically incorrect, type-check extra arguments for completion - for (let i = 2; i < node.arguments.length; ++i) { - checkExpressionCached(node.arguments[i]); - } + let typeArguments: NodeArray | undefined; - if (specifierType.flags & TypeFlags.Undefined || specifierType.flags & TypeFlags.Null || !isTypeAssignableTo(specifierType, stringType)) { - error(specifier, Diagnostics.Dynamic_import_s_specifier_must_be_of_type_string_but_here_has_type_0, typeToString(specifierType)); - } + if (!isDecorator) { + typeArguments = (node as CallExpression).typeArguments; - if (optionsType) { - const importCallOptionsType = getGlobalImportCallOptionsType(/*reportErrors*/ true); - if (importCallOptionsType !== emptyObjectType) { - checkTypeAssignableTo(optionsType, getNullableType(importCallOptionsType, TypeFlags.Undefined), node.arguments[1]); - } + // We already perform checking on the type arguments on the class declaration itself. + if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node as CallExpression).expression.kind !== SyntaxKind.SuperKeyword) { + forEach(typeArguments, checkSourceElement); } + } - // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal - const moduleSymbol = resolveExternalModuleName(node, specifier); - if (moduleSymbol) { - const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true, /*suppressUsageError*/ false); - if (esModuleSymbol) { - return createPromiseReturnType(node, - getTypeWithSyntheticDefaultOnly(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) || - getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) - ); - } + const candidates = candidatesOutArray || []; + // reorderCandidates fills up the candidates array directly + reorderCandidates(signatures, candidates, callChainFlags); + if (!candidates.length) { + if (reportErrors) { + diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures)); } - return createPromiseReturnType(node, anyType); + return resolveErrorCall(node); } - function createDefaultPropertyWrapperForModule(symbol: Symbol, originalSymbol: Symbol, anonymousSymbol?: Symbol | undefined) { - const memberTable = createSymbolTable(); - const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); - newSymbol.parent = originalSymbol; - newSymbol.nameType = getStringLiteralType("default"); - newSymbol.target = resolveSymbol(symbol); - memberTable.set(InternalSymbolName.Default, newSymbol); - return createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, emptyArray); - } + const args = getEffectiveCallArguments(node); - function getTypeWithSyntheticDefaultOnly(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression) { - const hasDefaultOnly = isOnlyImportedAsDefault(moduleSpecifier); - if (hasDefaultOnly && type && !isErrorType(type)) { - const synthType = type as SyntheticDefaultModuleType; - if (!synthType.defaultOnlyType) { - const type = createDefaultPropertyWrapperForModule(symbol, originalSymbol); - synthType.defaultOnlyType = type; - } - return synthType.defaultOnlyType; - } - return undefined; + // The excludeArgument array contains true for each context sensitive argument (an argument + // is context sensitive it is susceptible to a one-time permanent contextual typing). + // + // The idea is that we will perform type argument inference & assignability checking once + // without using the susceptible parameters that are functions, and once more for those + // parameters, contextually typing each as we go along. + // + // For a tagged template, then the first argument be 'undefined' if necessary because it + // represents a TemplateStringsArray. + // + // For a decorator, no arguments are susceptible to contextual typing due to the fact + // decorators are applied to a declaration by the emitter, and not to an expression. + const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters; + let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal; + + // The following variables are captured and modified by calls to chooseOverload. + // If overload resolution or type argument inference fails, we want to report the + // best error possible. The best error is one which says that an argument was not + // assignable to a parameter. This implies that everything else about the overload + // was fine. So if there is any overload that is only incorrect because of an + // argument, we will report an error on that one. + // + // function foo(s: string): void; + // function foo(n: number): void; // Report argument error on this overload + // function foo(): void; + // foo(true); + // + // If none of the overloads even made it that far, there are two possibilities. + // There was a problem with type arguments for some overload, in which case + // report an error on that. Or none of the overloads even had correct arity, + // in which case give an arity error. + // + // function foo(x: T): void; // Report type argument error + // function foo(): void; + // foo(0); + // + let candidatesForArgumentError: Signature[] | undefined; + let candidateForArgumentArityError: Signature | undefined; + let candidateForTypeArgumentError: Signature | undefined; + let result: Signature | undefined; + + // If we are in signature help, a trailing comma indicates that we intend to provide another argument, + // so we will only accept overloads with arity at least 1 higher than the current number of provided arguments. + const signatureHelpTrailingComma = + !!(checkMode & CheckMode.IsForSignatureHelp) && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma; + + // Section 4.12.1: + // if the candidate list contains one or more signatures for which the type of each argument + // expression is a subtype of each corresponding parameter type, the return type of the first + // of those signatures becomes the return type of the function call. + // Otherwise, the return type of the first signature in the candidate list becomes the return + // type of the function call. + // + // Whether the call is an error is determined by assignability of the arguments. The subtype pass + // is just important for choosing the best signature. So in the case where there is only one + // signature, the subtype pass is useless. So skipping it is an optimization. + if (candidates.length > 1) { + result = chooseOverload(candidates, subtypeRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); + } + if (!result) { + result = chooseOverload(candidates, assignableRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); + } + if (result) { + return result; } - function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression): Type { - if (allowSyntheticDefaultImports && type && !isErrorType(type)) { - const synthType = type as SyntheticDefaultModuleType; - if (!synthType.syntheticType) { - const file = originalSymbol.declarations?.find(isSourceFile); - const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false, moduleSpecifier); - if (hasSyntheticDefault) { - const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); - const defaultContainingObject = createDefaultPropertyWrapperForModule(symbol, originalSymbol, anonymousSymbol); - anonymousSymbol.type = defaultContainingObject; - synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; + // No signatures were applicable. Now report errors based on the last applicable signature with + // no arguments excluded from assignability checks. + // If candidate is undefined, it means that no candidates had a suitable arity. In that case, + // skip the checkApplicableSignature check. + if (reportErrors) { + if (candidatesForArgumentError) { + if (candidatesForArgumentError.length === 1 || candidatesForArgumentError.length > 3) { + const last = candidatesForArgumentError[candidatesForArgumentError.length - 1]; + let chain: DiagnosticMessageChain | undefined; + if (candidatesForArgumentError.length > 3) { + chain = chainDiagnosticMessages(chain, Diagnostics.The_last_overload_gave_the_following_error); + chain = chainDiagnosticMessages(chain, Diagnostics.No_overload_matches_this_call); + } + const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain); + if (diags) { + for (const d of diags) { + if (last.declaration && candidatesForArgumentError.length > 3) { + addRelatedInfo(d, createDiagnosticForNode(last.declaration, Diagnostics.The_last_overload_is_declared_here)); + } + addImplementationSuccessElaboration(last, d); + diagnostics.add(d); + } } else { - synthType.syntheticType = type; + Debug.fail("No error for last overload signature"); } } - return synthType.syntheticType; - } - return type; - } + else { + const allDiagnostics: (readonly DiagnosticRelatedInformation[])[] = []; + let max = 0; + let min = Number.MAX_VALUE; + let minIndex = 0; + let i = 0; + for (const c of candidatesForArgumentError) { + const chain = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Overload_0_of_1_2_gave_the_following_error, i + 1, candidates.length, signatureToString(c)); + const diags = getSignatureApplicabilityError(node, args, c, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, chain); + if (diags) { + if (diags.length <= min) { + min = diags.length; + minIndex = i; + } + max = Math.max(max, diags.length); + allDiagnostics.push(diags); + } + else { + Debug.fail("No error for 3 or fewer overload signatures"); + } + i++; + } - function isCommonJsRequire(node: Node): boolean { - if (!isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { - return false; + const diags = max > 1 ? allDiagnostics[minIndex] : flatten(allDiagnostics); + Debug.assert(diags.length > 0, "No errors reported for 3 or fewer overload signatures"); + const chain = chainDiagnosticMessages( + map(diags, d => typeof d.messageText === "string" ? (d as DiagnosticMessageChain) : d.messageText), + Diagnostics.No_overload_matches_this_call); + // The below is a spread to guarantee we get a new (mutable) array - our `flatMap` helper tries to do "smart" optimizations where it reuses input + // arrays and the emptyArray singleton where possible, which is decidedly not what we want while we're still constructing this diagnostic + const related = [...flatMap(diags, d => (d as Diagnostic).relatedInformation) as DiagnosticRelatedInformation[]]; + let diag: Diagnostic; + if (every(diags, d => d.start === diags[0].start && d.length === diags[0].length && d.file === diags[0].file)) { + const { file, start, length } = diags[0]; + diag = { file, start, length, code: chain.code, category: chain.category, messageText: chain, relatedInformation: related }; + } + else { + diag = createDiagnosticForNodeFromMessageChain(node, chain, related); + } + addImplementationSuccessElaboration(candidatesForArgumentError[0], diag); + diagnostics.add(diag); + } } - - // Make sure require is not a local function - if (!isIdentifier(node.expression)) return Debug.fail(); - const resolvedRequire = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true)!; // TODO: GH#18217 - if (resolvedRequire === requireSymbol) { - return true; + else if (candidateForArgumentArityError) { + diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args)); } - // project includes symbol named 'require' - make sure that it is ambient and local non-alias - if (resolvedRequire.flags & SymbolFlags.Alias) { - return false; + else if (candidateForTypeArgumentError) { + checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, fallbackError); } - - const targetDeclarationKind = resolvedRequire.flags & SymbolFlags.Function - ? SyntaxKind.FunctionDeclaration - : resolvedRequire.flags & SymbolFlags.Variable - ? SyntaxKind.VariableDeclaration - : SyntaxKind.Unknown; - if (targetDeclarationKind !== SyntaxKind.Unknown) { - const decl = getDeclarationOfKind(resolvedRequire, targetDeclarationKind)!; - // function/variable declaration should be ambient - return !!decl && !!(decl.flags & NodeFlags.Ambient); + else { + const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); + if (signaturesWithCorrectTypeArgumentArity.length === 0) { + diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!)); + } + else if (!isDecorator) { + diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args)); + } + else if (fallbackError) { + diagnostics.add(getDiagnosticForCallNode(node, fallbackError)); + } } - return false; } - function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { - if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments); - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); - } - const signature = getResolvedSignature(node); - checkDeprecatedSignature(signature, node); - return getReturnTypeOfSignature(signature); - } + return getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray); - function checkAssertion(node: AssertionExpression) { - if (node.kind === SyntaxKind.TypeAssertionExpression) { - const file = getSourceFileOfNode(node); - if (file && fileExtensionIsOneOf(file.fileName, [Extension.Cts, Extension.Mts])) { - grammarErrorOnNode(node, Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Use_an_as_expression_instead); + function addImplementationSuccessElaboration(failed: Signature, diagnostic: Diagnostic) { + const oldCandidatesForArgumentError = candidatesForArgumentError; + const oldCandidateForArgumentArityError = candidateForArgumentArityError; + const oldCandidateForTypeArgumentError = candidateForTypeArgumentError; + + const failedSignatureDeclarations = failed.declaration?.symbol?.declarations || emptyArray; + const isOverload = failedSignatureDeclarations.length > 1; + const implDecl = isOverload ? find(failedSignatureDeclarations, d => isFunctionLikeDeclaration(d) && nodeIsPresent(d.body)) : undefined; + if (implDecl) { + const candidate = getSignatureFromDeclaration(implDecl as FunctionLikeDeclaration); + const isSingleNonGenericCandidate = !candidate.typeParameters; + if (chooseOverload([candidate], assignableRelation, isSingleNonGenericCandidate)) { + addRelatedInfo(diagnostic, createDiagnosticForNode(implDecl, Diagnostics.The_call_would_have_succeeded_against_this_implementation_but_implementation_signatures_of_overloads_are_not_externally_visible)); } } - return checkAssertionWorker(node, node.type, node.expression); - } - function isValidConstAssertionArgument(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.TemplateExpression: - return true; - case SyntaxKind.ParenthesizedExpression: - return isValidConstAssertionArgument((node as ParenthesizedExpression).expression); - case SyntaxKind.PrefixUnaryExpression: - const op = (node as PrefixUnaryExpression).operator; - const arg = (node as PrefixUnaryExpression).operand; - return op === SyntaxKind.MinusToken && (arg.kind === SyntaxKind.NumericLiteral || arg.kind === SyntaxKind.BigIntLiteral) || - op === SyntaxKind.PlusToken && arg.kind === SyntaxKind.NumericLiteral; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - const expr = (node as PropertyAccessExpression | ElementAccessExpression).expression; - let symbol = getTypeOfNode(expr).symbol; - if (symbol && symbol.flags & SymbolFlags.Alias) { - symbol = resolveAlias(symbol); - } - return !!(symbol && (symbol.flags & SymbolFlags.Enum) && getEnumKind(symbol) === EnumKind.Literal); - } - return false; + candidatesForArgumentError = oldCandidatesForArgumentError; + candidateForArgumentArityError = oldCandidateForArgumentArityError; + candidateForTypeArgumentError = oldCandidateForTypeArgumentError; } - function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) { - let exprType = checkExpression(expression, checkMode); - if (isConstTypeReference(type)) { - if (!isValidConstAssertionArgument(expression)) { - error(expression, Diagnostics.A_const_assertions_can_only_be_applied_to_references_to_enum_members_or_string_number_boolean_array_or_object_literals); + function chooseOverload(candidates: Signature[], relation: ESMap, isSingleNonGenericCandidate: boolean, signatureHelpTrailingComma = false) { + candidatesForArgumentError = undefined; + candidateForArgumentArityError = undefined; + candidateForTypeArgumentError = undefined; + + if (isSingleNonGenericCandidate) { + const candidate = candidates[0]; + if (some(typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { + return undefined; } - return getRegularTypeOfLiteralType(exprType); + if (getSignatureApplicabilityError(node, args, candidate, relation, CheckMode.Normal, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { + candidatesForArgumentError = [candidate]; + return undefined; + } + return candidate; } - checkSourceElement(type); - exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType)); - const targetType = getTypeFromTypeNode(type); - if (produceDiagnostics && !isErrorType(targetType)) { - const widenedType = getWidenedType(exprType); - if (!isTypeComparableTo(targetType, widenedType)) { - checkTypeComparableTo(exprType, targetType, errNode, - Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first); + + for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { + const candidate = candidates[candidateIndex]; + if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { + continue; + } + + let checkCandidate: Signature; + let inferenceContext: InferenceContext | undefined; + + if (candidate.typeParameters) { + let typeArgumentTypes: Type[] | undefined; + if (some(typeArguments)) { + typeArgumentTypes = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false); + if (!typeArgumentTypes) { + candidateForTypeArgumentError = candidate; + continue; + } + } + else { + inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); + typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext); + argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal; + } + checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); + // If the original signature has a generic rest type, instantiation may produce a + // signature with different arity and we need to perform another arity check. + if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { + candidateForArgumentArityError = checkCandidate; + continue; + } + } + else { + checkCandidate = candidate; + } + if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { + // Give preference to error candidates that have no rest parameters (as they are more specific) + (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); + continue; + } + if (argCheckMode) { + // If one or more context sensitive arguments were excluded, we start including + // them now (and keeping do so for any subsequent candidates) and perform a second + // round of type inference and applicability checking for this particular candidate. + argCheckMode = CheckMode.Normal; + if (inferenceContext) { + const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext); + checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); + // If the original signature has a generic rest type, instantiation may produce a + // signature with different arity and we need to perform another arity check. + if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { + candidateForArgumentArityError = checkCandidate; + continue; + } + } + if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { + // Give preference to error candidates that have no rest parameters (as they are more specific) + (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); + continue; + } } + candidates[candidateIndex] = checkCandidate; + return checkCandidate; } - return targetType; - } - function checkNonNullChain(node: NonNullChain) { - const leftType = checkExpression(node.expression); - const nonOptionalType = getOptionalExpressionType(leftType, node.expression); - return propagateOptionalTypeMarker(getNonNullableType(nonOptionalType), node, nonOptionalType !== leftType); + return undefined; } + } - function checkNonNullAssertion(node: NonNullExpression) { - return node.flags & NodeFlags.OptionalChain ? checkNonNullChain(node as NonNullChain) : - getNonNullableType(checkExpression(node.expression)); - } + // No signature was applicable. We have already reported the errors for the invalid signature. + function getCandidateForOverloadFailure( + node: CallLikeExpression, + candidates: Signature[], + args: readonly Expression[], + hasCandidatesOutArray: boolean, + ): Signature { + Debug.assert(candidates.length > 0); // Else should not have called this. + checkNodeDeferred(node); + // Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine. + // Don't do this if there is a `candidatesOutArray`, + // because then we want the chosen best candidate to be one of the overloads, not a combination. + return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters) + ? pickLongestCandidateSignature(node, candidates, args) + : createUnionOfSignaturesForOverloadFailure(candidates); + } - function checkMetaProperty(node: MetaProperty): Type { - checkGrammarMetaProperty(node); + function createUnionOfSignaturesForOverloadFailure(candidates: readonly Signature[]): Signature { + const thisParameters = mapDefined(candidates, c => c.thisParameter); + let thisParameter: Symbol | undefined; + if (thisParameters.length) { + thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter)); + } + const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters); + const parameters: Symbol[] = []; + for (let i = 0; i < maxNonRestParam; i++) { + const symbols = mapDefined(candidates, s => signatureHasRestParameter(s) ? + i < s.parameters.length - 1 ? s.parameters[i] : last(s.parameters) : + i < s.parameters.length ? s.parameters[i] : undefined); + Debug.assert(symbols.length !== 0); + parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i)))); + } + const restParameterSymbols = mapDefined(candidates, c => signatureHasRestParameter(c) ? last(c.parameters) : undefined); + let flags = SignatureFlags.None; + if (restParameterSymbols.length !== 0) { + const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype)); + parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type)); + flags |= SignatureFlags.HasRestParameter; + } + if (candidates.some(signatureHasLiteralTypes)) { + flags |= SignatureFlags.HasLiteralTypes; + } + return createSignature( + candidates[0].declaration, + /*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`. + thisParameter, + parameters, + /*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)), + /*typePredicate*/ undefined, + minArgumentCount, + flags); + } - if (node.keywordToken === SyntaxKind.NewKeyword) { - return checkNewTargetMetaProperty(node); - } + function getNumNonRestParameters(signature: Signature): number { + const numParams = signature.parameters.length; + return signatureHasRestParameter(signature) ? numParams - 1 : numParams; + } - if (node.keywordToken === SyntaxKind.ImportKeyword) { - return checkImportMetaProperty(node); - } + function createCombinedSymbolFromTypes(sources: readonly Symbol[], types: Type[]): Symbol { + return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype)); + } - return Debug.assertNever(node.keywordToken); - } + function createCombinedSymbolForOverloadFailure(sources: readonly Symbol[], type: Type): Symbol { + // This function is currently only used for erroneous overloads, so it's good enough to just use the first source. + return createSymbolWithType(first(sources), type); + } - function checkMetaPropertyKeyword(node: MetaProperty): Type { - switch (node.keywordToken) { - case SyntaxKind.ImportKeyword: - return getGlobalImportMetaExpressionType(); - case SyntaxKind.NewKeyword: - const type = checkNewTargetMetaProperty(node); - return isErrorType(type) ? errorType : createNewTargetExpressionType(type); - default: - Debug.assertNever(node.keywordToken); - } + function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: readonly Expression[]): Signature { + // Pick the longest signature. This way we can get a contextual type for cases like: + // declare function f(a: { xa: number; xb: number; }, b: number); + // f({ | + // Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like: + // declare function f(k: keyof T); + // f(" + const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount); + const candidate = candidates[bestIndex]; + const { typeParameters } = candidate; + if (!typeParameters) { + return candidate; + } + + const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined; + const instantiated = typeArgumentNodes + ? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node))) + : inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args); + candidates[bestIndex] = instantiated; + return instantiated; + } + + function getTypeArgumentsFromNodes(typeArgumentNodes: readonly TypeNode[], typeParameters: readonly TypeParameter[], isJs: boolean): readonly Type[] { + const typeArguments = typeArgumentNodes.map(getTypeOfNode); + while (typeArguments.length > typeParameters.length) { + typeArguments.pop(); } + while (typeArguments.length < typeParameters.length) { + typeArguments.push(getConstraintOfTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isJs)); + } + return typeArguments; + } - function checkNewTargetMetaProperty(node: MetaProperty) { - const container = getNewTargetContainer(node); - if (!container) { - error(node, Diagnostics.Meta_property_0_is_only_allowed_in_the_body_of_a_function_declaration_function_expression_or_constructor, "new.target"); - return errorType; - } - else if (container.kind === SyntaxKind.Constructor) { - const symbol = getSymbolOfNode(container.parent as ClassLikeDeclaration); - return getTypeOfSymbol(symbol); + function inferSignatureInstantiationForOverloadFailure(node: CallLikeExpression, typeParameters: readonly TypeParameter[], candidate: Signature, args: readonly Expression[]): Signature { + const inferenceContext = createInferenceContext(typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); + const typeArgumentTypes = inferTypeArguments(node, candidate, args, CheckMode.SkipContextSensitive | CheckMode.SkipGenericFunctions, inferenceContext); + return createSignatureInstantiation(candidate, typeArgumentTypes); + } + + function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number { + let maxParamsIndex = -1; + let maxParams = -1; + + for (let i = 0; i < candidates.length; i++) { + const candidate = candidates[i]; + const paramCount = getParameterCount(candidate); + if (hasEffectiveRestParameter(candidate) || paramCount >= argsCount) { + return i; } - else { - const symbol = getSymbolOfNode(container)!; - return getTypeOfSymbol(symbol); + if (paramCount > maxParams) { + maxParams = paramCount; + maxParamsIndex = i; } } - function checkImportMetaProperty(node: MetaProperty) { - if (moduleKind === ModuleKind.Node12 || moduleKind === ModuleKind.NodeNext) { - if (getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.ESNext) { - error(node, Diagnostics.The_import_meta_meta_property_is_not_allowed_in_files_which_will_build_into_CommonJS_output); + return maxParamsIndex; + } + + function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + if (node.expression.kind === SyntaxKind.SuperKeyword) { + const superType = checkSuperExpression(node.expression); + if (isTypeAny(superType)) { + for (const arg of node.arguments) { + checkExpression(arg); // Still visit arguments so they get marked for visibility, etc } + return anySignature; } - else if (moduleKind < ModuleKind.ES2020 && moduleKind !== ModuleKind.System) { - error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_when_the_module_option_is_es2020_es2022_esnext_system_node12_or_nodenext); + if (!isErrorType(superType)) { + // In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated + // with the type arguments specified in the extends clause. + const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!); + if (baseTypeNode) { + const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode); + return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlags.None); + } } - const file = getSourceFileOfNode(node); - Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag."); - return node.name.escapedText === "meta" ? getGlobalImportMetaType() : errorType; + return resolveUntypedCall(node); } - function getTypeOfParameter(symbol: Symbol) { - const type = getTypeOfSymbol(symbol); - if (strictNullChecks) { - const declaration = symbol.valueDeclaration; - if (declaration && hasInitializer(declaration)) { - return getOptionalType(type); - } - } - return type; + let callChainFlags: SignatureFlags; + let funcType = checkExpression(node.expression); + if (isCallChain(node)) { + const nonOptionalType = getOptionalExpressionType(funcType, node.expression); + callChainFlags = nonOptionalType === funcType ? SignatureFlags.None : + isOutermostOptionalChain(node) ? SignatureFlags.IsOuterCallChain : + SignatureFlags.IsInnerCallChain; + funcType = nonOptionalType; + } + else { + callChainFlags = SignatureFlags.None; + } + + funcType = checkNonNullTypeWithReporter( + funcType, + node.expression, + reportCannotInvokePossiblyNullOrUndefinedError + ); + + if (funcType === silentNeverType) { + return silentNeverSignature; } - function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember) { - Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names - return d.name.escapedText; + const apparentType = getApparentType(funcType); + if (isErrorType(apparentType)) { + // Another error has already been reported + return resolveErrorCall(node); } - function getParameterNameAtPosition(signature: Signature, pos: number, overrideRestType?: Type) { - const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - if (pos < paramCount) { - return signature.parameters[pos].escapedName; + // Technically, this signatures list may be incomplete. We are taking the apparent type, + // but we are not including call signatures that may have been added to the Object or + // Function interface, since they have none by default. This is a bit of a leap of faith + // that the user will not add any. + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + + // TS 1.0 Spec: 4.12 + // In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual + // types are provided for the argument expressions, and the result is always of type Any. + if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { + // The unknownType indicates that an error already occurred (and was reported). No + // need to report another error in this case. + if (!isErrorType(funcType) && node.typeArguments) { + error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); + } + return resolveUntypedCall(node); + } + // If FuncExpr's apparent type(section 3.8.1) is a function type, the call is a typed function call. + // TypeScript employs overload resolution in typed function calls in order to support functions + // with multiple call signatures. + if (!callSignatures.length) { + if (numConstructSignatures) { + error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); } - const restParameter = signature.parameters[paramCount] || unknownSymbol; - const restType = overrideRestType || getTypeOfSymbol(restParameter); - if (isTupleType(restType)) { - const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; - const index = pos - paramCount; - return associatedNames && getTupleElementLabel(associatedNames[index]) || restParameter.escapedName + "_" + index as __String; + else { + let relatedInformation: DiagnosticRelatedInformation | undefined; + if (node.arguments.length === 1) { + const text = getSourceFileOfNode(node).text; + if (isLineBreak(text.charCodeAt(skipTrivia(text, node.expression.end, /* stopAfterLineBreak */ true) - 1))) { + relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.Are_you_missing_a_semicolon); + } + } + invocationError(node.expression, apparentType, SignatureKind.Call, relatedInformation); } - return restParameter.escapedName; + return resolveErrorCall(node); + } + // When a call to a generic function is an argument to an outer call to a generic function for which + // inference is in process, we have a choice to make. If the inner call relies on inferences made from + // its contextual type to its return type, deferring the inner call processing allows the best possible + // contextual type to accumulate. But if the outer call relies on inferences made from the return type of + // the inner call, the inner call should be processed early. There's no sure way to know which choice is + // right (only a full unification algorithm can determine that), so we resort to the following heuristic: + // If no type arguments are specified in the inner call and at least one call signature is generic and + // returns a function type, we choose to defer processing. This narrowly permits function composition + // operators to flow inferences through return types, but otherwise processes calls right away. We + // use the resolvingSignature singleton to indicate that we deferred processing. This result will be + // propagated out and eventually turned into nonInferrableType (a type that is assignable to anything and + // from which we never make inferences). + if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) { + skippedGenericFunction(node, checkMode); + return resolvingSignature; + } + // If the function is explicitly marked with `@class`, then it must be constructed. + if (callSignatures.some(sig => isInJSFile(sig.declaration) && !!getJSDocClassTag(sig.declaration!))) { + error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); + return resolveErrorCall(node); } - function getParameterIdentifierNameAtPosition(signature: Signature, pos: number): [parameterName: __String, isRestParameter: boolean] | undefined { - const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - if (pos < paramCount) { - const param = signature.parameters[pos]; - return isParameterDeclarationWithIdentifierName(param) ? [param.escapedName, false] : undefined; - } + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags); + } - const restParameter = signature.parameters[paramCount] || unknownSymbol; - if (!isParameterDeclarationWithIdentifierName(restParameter)) { - return undefined; - } + function isGenericFunctionReturningFunction(signature: Signature) { + return !!(signature.typeParameters && isFunctionType(getReturnTypeOfSignature(signature))); + } - const restType = getTypeOfSymbol(restParameter); - if (isTupleType(restType)) { - const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; - const index = pos - paramCount; - const associatedName = associatedNames?.[index]; - const isRestTupleElement = !!associatedName?.dotDotDotToken; - return associatedName ? [ - getTupleElementLabel(associatedName), - isRestTupleElement - ] : undefined; - } + /** + * TS 1.0 spec: 4.12 + * If FuncExpr is of type Any, or of an object type that has no call or construct signatures + * but is a subtype of the Function interface, the call is an untyped function call. + */ + function isUntypedFunctionCall(funcType: Type, apparentFuncType: Type, numCallSignatures: number, numConstructSignatures: number): boolean { + // We exclude union types because we may have a union of function types that happen to have no common signatures. + return isTypeAny(funcType) || isTypeAny(apparentFuncType) && !!(funcType.flags & TypeFlags.TypeParameter) || + !numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & TypeFlags.Union) && !(getReducedType(apparentFuncType).flags & TypeFlags.Never) && isTypeAssignableTo(funcType, globalFunctionType); + } - if (pos === paramCount) { - return [restParameter.escapedName, true]; + function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + if (node.arguments && languageVersion < ScriptTarget.ES5) { + const spreadIndex = getSpreadArgumentIndex(node.arguments); + if (spreadIndex >= 0) { + error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_5_and_higher); } - return undefined; } - function isParameterDeclarationWithIdentifierName(symbol: Symbol) { - return symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isIdentifier(symbol.valueDeclaration.name); - } - function isValidDeclarationForTupleLabel(d: Declaration): d is NamedTupleMember | (ParameterDeclaration & { name: Identifier }) { - return d.kind === SyntaxKind.NamedTupleMember || (isParameter(d) && d.name && isIdentifier(d.name)); + let expressionType = checkNonNullExpression(node.expression); + if (expressionType === silentNeverType) { + return silentNeverSignature; } - function getNameableDeclarationAtPosition(signature: Signature, pos: number) { - const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - if (pos < paramCount) { - const decl = signature.parameters[pos].valueDeclaration; - return decl && isValidDeclarationForTupleLabel(decl) ? decl : undefined; - } - const restParameter = signature.parameters[paramCount] || unknownSymbol; - const restType = getTypeOfSymbol(restParameter); - if (isTupleType(restType)) { - const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; - const index = pos - paramCount; - return associatedNames && associatedNames[index]; - } - return restParameter.valueDeclaration && isValidDeclarationForTupleLabel(restParameter.valueDeclaration) ? restParameter.valueDeclaration : undefined; + // If expressionType's apparent type(section 3.8.1) is an object type with one or + // more construct signatures, the expression is processed in the same manner as a + // function call, but using the construct signatures as the initial set of candidate + // signatures for overload resolution. The result type of the function call becomes + // the result type of the operation. + expressionType = getApparentType(expressionType); + if (isErrorType(expressionType)) { + // Another error has already been reported + return resolveErrorCall(node); } - function getTypeAtPosition(signature: Signature, pos: number): Type { - return tryGetTypeAtPosition(signature, pos) || anyType; + // TS 1.0 spec: 4.11 + // If expressionType is of type Any, Args can be any argument + // list and the result of the operation is of type Any. + if (isTypeAny(expressionType)) { + if (node.typeArguments) { + error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); + } + return resolveUntypedCall(node); } - function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined { - const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - if (pos < paramCount) { - return getTypeOfParameter(signature.parameters[pos]); + // Technically, this signatures list may be incomplete. We are taking the apparent type, + // but we are not including construct signatures that may have been added to the Object or + // Function interface, since they have none by default. This is a bit of a leap of faith + // that the user will not add any. + const constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct); + if (constructSignatures.length) { + if (!isConstructorAccessible(node, constructSignatures[0])) { + return resolveErrorCall(node); } - if (signatureHasRestParameter(signature)) { - // We want to return the value undefined for an out of bounds parameter position, - // so we need to check bounds here before calling getIndexedAccessType (which - // otherwise would return the type 'undefined'). - const restType = getTypeOfSymbol(signature.parameters[paramCount]); - const index = pos - paramCount; - if (!isTupleType(restType) || restType.target.hasRestElement || index < restType.target.fixedLength) { - return getIndexedAccessType(restType, getNumberLiteralType(index)); - } + // If the expression is a class of abstract type, or an abstract construct signature, + // then it cannot be instantiated. + // In the case of a merged class-module or class-interface declaration, + // only the class declaration node will have the Abstract flag set. + if (constructSignatures.some(signature => signature.flags & SignatureFlags.Abstract)) { + error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class); + return resolveErrorCall(node); } - return undefined; + const valueDecl = expressionType.symbol && getClassLikeDeclarationOfSymbol(expressionType.symbol); + if (valueDecl && hasSyntacticModifier(valueDecl, ModifierFlags.Abstract)) { + error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class); + return resolveErrorCall(node); + } + + return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, SignatureFlags.None); } - function getRestTypeAtPosition(source: Signature, pos: number): Type { - const parameterCount = getParameterCount(source); - const minArgumentCount = getMinArgumentCount(source); - const restType = getEffectiveRestType(source); - if (restType && pos >= parameterCount - 1) { - return pos === parameterCount - 1 ? restType : createArrayType(getIndexedAccessType(restType, numberType)); - } - const types = []; - const flags = []; - const names = []; - for (let i = pos; i < parameterCount; i++) { - if (!restType || i < parameterCount - 1) { - types.push(getTypeAtPosition(source, i)); - flags.push(i < minArgumentCount ? ElementFlags.Required : ElementFlags.Optional); - } - else { - types.push(restType); - flags.push(ElementFlags.Variadic); + // If expressionType's apparent type is an object type with no construct signatures but + // one or more call signatures, the expression is processed as a function call. A compile-time + // error occurs if the result of the function call is not Void. The type of the result of the + // operation is Any. It is an error to have a Void this type. + const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); + if (callSignatures.length) { + const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + if (!noImplicitAny) { + if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { + error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); } - const name = getNameableDeclarationAtPosition(source, i); - if (name) { - names.push(name); + if (getThisTypeOfSignature(signature) === voidType) { + error(node, Diagnostics.A_function_that_is_called_with_the_new_keyword_cannot_have_a_this_type_that_is_void); } } - return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined); + return signature; } - // Return the number of parameters in a signature. The rest parameter, if present, counts as one - // parameter. For example, the parameter count of (x: number, y: number, ...z: string[]) is 3 and - // the parameter count of (x: number, ...args: [number, ...string[], boolean])) is also 3. In the - // latter example, the effective rest type is [...string[], boolean]. - function getParameterCount(signature: Signature) { - const length = signature.parameters.length; - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[length - 1]); - if (isTupleType(restType)) { - return length + restType.target.fixedLength - (restType.target.hasRestElement ? 0 : 1); - } - } - return length; - } + invocationError(node.expression, expressionType, SignatureKind.Construct); + return resolveErrorCall(node); + } - function getMinArgumentCount(signature: Signature, flags?: MinArgumentCountFlags) { - const strongArityForUntypedJS = flags! & MinArgumentCountFlags.StrongArityForUntypedJS; - const voidIsNonOptional = flags! & MinArgumentCountFlags.VoidIsNonOptional; - if (voidIsNonOptional || signature.resolvedMinArgumentCount === undefined) { - let minArgumentCount: number | undefined; - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - if (isTupleType(restType)) { - const firstOptionalIndex = findIndex(restType.target.elementFlags, f => !(f & ElementFlags.Required)); - const requiredCount = firstOptionalIndex < 0 ? restType.target.fixedLength : firstOptionalIndex; - if (requiredCount > 0) { - minArgumentCount = signature.parameters.length - 1 + requiredCount; + function typeHasProtectedAccessibleBase(target: Symbol, type: InterfaceType): boolean { + const baseTypes = getBaseTypes(type); + if (!length(baseTypes)) { + return false; + } + const firstBase = baseTypes[0]; + if (firstBase.flags & TypeFlags.Intersection) { + const types = (firstBase as IntersectionType).types; + const mixinFlags = findMixins(types); + let i = 0; + for (const intersectionMember of (firstBase as IntersectionType).types) { + // We want to ignore mixin ctors + if (!mixinFlags[i]) { + if (getObjectFlags(intersectionMember) & (ObjectFlags.Class | ObjectFlags.Interface)) { + if (intersectionMember.symbol === target) { + return true; + } + if (typeHasProtectedAccessibleBase(target, intersectionMember as InterfaceType)) { + return true; } } } - if (minArgumentCount === undefined) { - if (!strongArityForUntypedJS && signature.flags & SignatureFlags.IsUntypedSignatureInJSFile) { - return 0; - } - minArgumentCount = signature.minArgumentCount; - } - if (voidIsNonOptional) { - return minArgumentCount; - } - for (let i = minArgumentCount - 1; i >= 0; i--) { - const type = getTypeAtPosition(signature, i); - if (filterType(type, acceptsVoid).flags & TypeFlags.Never) { - break; - } - minArgumentCount = i; - } - signature.resolvedMinArgumentCount = minArgumentCount; - } - return signature.resolvedMinArgumentCount; - } - - function hasEffectiveRestParameter(signature: Signature) { - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - return !isTupleType(restType) || restType.target.hasRestElement; + i++; } return false; } - - function getEffectiveRestType(signature: Signature) { - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - if (!isTupleType(restType)) { - return restType; - } - if (restType.target.hasRestElement) { - return sliceTupleType(restType, restType.target.fixedLength); - } - } - return undefined; + if (firstBase.symbol === target) { + return true; } + return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType); + } - function getNonArrayRestType(signature: Signature) { - const restType = getEffectiveRestType(signature); - return restType && !isArrayType(restType) && !isTypeAny(restType) && (getReducedType(restType).flags & TypeFlags.Never) === 0 ? restType : undefined; + function isConstructorAccessible(node: NewExpression, signature: Signature) { + if (!signature || !signature.declaration) { + return true; } - function getTypeOfFirstParameterOfSignature(signature: Signature) { - return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType); - } + const declaration = signature.declaration; + const modifiers = getSelectedEffectiveModifierFlags(declaration, ModifierFlags.NonPublicAccessibilityModifier); - function getTypeOfFirstParameterOfSignatureWithFallback(signature: Signature, fallbackType: Type) { - return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : fallbackType; + // (1) Public constructors and (2) constructor functions are always accessible. + if (!modifiers || declaration.kind !== SyntaxKind.Constructor) { + return true; } - function inferFromAnnotatedParameters(signature: Signature, context: Signature, inferenceContext: InferenceContext) { - const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - for (let i = 0; i < len; i++) { - const declaration = signature.parameters[i].valueDeclaration as ParameterDeclaration; - if (declaration.type) { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - inferTypes(inferenceContext.inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i)); - } + const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(declaration.parent.symbol)!; + const declaringClass = getDeclaredTypeOfSymbol(declaration.parent.symbol) as InterfaceType; + + // A private or protected constructor can only be instantiated within its own class (or a subclass, for protected) + if (!isNodeWithinClass(node, declaringClassDeclaration)) { + const containingClass = getContainingClass(node); + if (containingClass && modifiers & ModifierFlags.Protected) { + const containingType = getTypeOfNode(containingClass); + if (typeHasProtectedAccessibleBase(declaration.parent.symbol, containingType as InterfaceType)) { + return true; } } - const restType = getEffectiveRestType(context); - if (restType && restType.flags & TypeFlags.TypeParameter) { - // The contextual signature has a generic rest parameter. We first instantiate the contextual - // signature (without fixing type parameters) and assign types to contextually typed parameters. - const instantiatedContext = instantiateSignature(context, inferenceContext.nonFixingMapper); - assignContextualParameterTypes(signature, instantiatedContext); - // We then infer from a tuple type representing the parameters that correspond to the contextual - // rest parameter. - const restPos = getParameterCount(context) - 1; - inferTypes(inferenceContext.inferences, getRestTypeAtPosition(signature, restPos), restType); + if (modifiers & ModifierFlags.Private) { + error(node, Diagnostics.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); } + if (modifiers & ModifierFlags.Protected) { + error(node, Diagnostics.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); + } + return false; } - function assignContextualParameterTypes(signature: Signature, context: Signature) { - if (context.typeParameters) { - if (!signature.typeParameters) { - signature.typeParameters = context.typeParameters; + return true; + } + + function invocationErrorDetails(errorTarget: Node, apparentType: Type, kind: SignatureKind): { messageChain: DiagnosticMessageChain, relatedMessage: DiagnosticMessage | undefined } { + let errorInfo: DiagnosticMessageChain | undefined; + const isCall = kind === SignatureKind.Call; + const awaitedType = getAwaitedType(apparentType); + const maybeMissingAwait = awaitedType && getSignaturesOfType(awaitedType, kind).length > 0; + if (apparentType.flags & TypeFlags.Union) { + const types = (apparentType as UnionType).types; + let hasSignatures = false; + for (const constituent of types) { + const signatures = getSignaturesOfType(constituent, kind); + if (signatures.length !== 0) { + hasSignatures = true; + if (errorInfo) { + // Bail early if we already have an error, no chance of "No constituent of type is callable" + break; + } } else { - return; // This signature has already has a contextual inference performed and cached on it! - } - } - if (context.thisParameter) { - const parameter = signature.thisParameter; - if (!parameter || parameter.valueDeclaration && !(parameter.valueDeclaration as ParameterDeclaration).type) { - if (!parameter) { - signature.thisParameter = createSymbolWithType(context.thisParameter, /*type*/ undefined); + // Error on the first non callable constituent only + if (!errorInfo) { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Type_0_has_no_call_signatures : + Diagnostics.Type_0_has_no_construct_signatures, + typeToString(constituent) + ); + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Not_all_constituents_of_type_0_are_callable : + Diagnostics.Not_all_constituents_of_type_0_are_constructable, + typeToString(apparentType) + ); + } + if (hasSignatures) { + // Bail early if we already found a siganture, no chance of "No constituent of type is callable" + break; } - assignParameterType(signature.thisParameter!, getTypeOfSymbol(context.thisParameter)); } } - const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - for (let i = 0; i < len; i++) { - const parameter = signature.parameters[i]; - if (!getEffectiveTypeAnnotationNode(parameter.valueDeclaration as ParameterDeclaration)) { - const contextualParameterType = tryGetTypeAtPosition(context, i); - assignParameterType(parameter, contextualParameterType); - } + if (!hasSignatures) { + errorInfo = chainDiagnosticMessages( + /* detials */ undefined, + isCall ? + Diagnostics.No_constituent_of_type_0_is_callable : + Diagnostics.No_constituent_of_type_0_is_constructable, + typeToString(apparentType) + ); } - if (signatureHasRestParameter(signature)) { - // parameter might be a transient symbol generated by use of `arguments` in the function body. - const parameter = last(signature.parameters); - if (isTransientSymbol(parameter) || !getEffectiveTypeAnnotationNode(parameter.valueDeclaration as ParameterDeclaration)) { - const contextualParameterType = getRestTypeAtPosition(context, len); - assignParameterType(parameter, contextualParameterType); - } + if (!errorInfo) { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Each_member_of_the_union_type_0_has_signatures_but_none_of_those_signatures_are_compatible_with_each_other : + Diagnostics.Each_member_of_the_union_type_0_has_construct_signatures_but_none_of_those_signatures_are_compatible_with_each_other, + typeToString(apparentType) + ); } } + else { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Type_0_has_no_call_signatures : + Diagnostics.Type_0_has_no_construct_signatures, + typeToString(apparentType) + ); + } - function assignNonContextualParameterTypes(signature: Signature) { - if (signature.thisParameter) { - assignParameterType(signature.thisParameter); - } - for (const parameter of signature.parameters) { - assignParameterType(parameter); + let headMessage = isCall ? Diagnostics.This_expression_is_not_callable : Diagnostics.This_expression_is_not_constructable; + + // Diagnose get accessors incorrectly called as functions + if (isCallExpression(errorTarget.parent) && errorTarget.parent.arguments.length === 0) { + const { resolvedSymbol } = getNodeLinks(errorTarget); + if (resolvedSymbol && resolvedSymbol.flags & SymbolFlags.GetAccessor) { + headMessage = Diagnostics.This_expression_is_not_callable_because_it_is_a_get_accessor_Did_you_mean_to_use_it_without; } } - function assignParameterType(parameter: Symbol, type?: Type) { - const links = getSymbolLinks(parameter); - if (!links.type) { - const declaration = parameter.valueDeclaration as ParameterDeclaration; - links.type = type || getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); - if (declaration.name.kind !== SyntaxKind.Identifier) { - // if inference didn't come up with anything but unknown, fall back to the binding pattern if present. - if (links.type === unknownType) { - links.type = getTypeFromBindingPattern(declaration.name); - } - assignBindingElementTypes(declaration.name); - } - } + return { + messageChain: chainDiagnosticMessages(errorInfo, headMessage), + relatedMessage: maybeMissingAwait ? Diagnostics.Did_you_forget_to_use_await : undefined, + }; + } + function invocationError(errorTarget: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) { + const { messageChain, relatedMessage: relatedInfo } = invocationErrorDetails(errorTarget, apparentType, kind); + const diagnostic = createDiagnosticForNodeFromMessageChain(errorTarget, messageChain); + if (relatedInfo) { + addRelatedInfo(diagnostic, createDiagnosticForNode(errorTarget, relatedInfo)); + } + if (isCallExpression(errorTarget.parent)) { + const { start, length } = getDiagnosticSpanForCallNode(errorTarget.parent, /* doNotIncludeArguments */ true); + diagnostic.start = start; + diagnostic.length = length; + } + diagnostics.add(diagnostic); + invocationErrorRecovery(apparentType, kind, relatedInformation ? addRelatedInfo(diagnostic, relatedInformation) : diagnostic); + } + + function invocationErrorRecovery(apparentType: Type, kind: SignatureKind, diagnostic: Diagnostic) { + if (!apparentType.symbol) { + return; } + const importNode = getSymbolLinks(apparentType.symbol).originatingImport; + // Create a diagnostic on the originating import if possible onto which we can attach a quickfix + // An import call expression cannot be rewritten into another form to correct the error - the only solution is to use `.default` at the use-site + if (importNode && !isImportCall(importNode)) { + const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target!), kind); + if (!sigs || !sigs.length) return; - // When contextual typing assigns a type to a parameter that contains a binding pattern, we also need to push - // the destructured type into the contained binding elements. - function assignBindingElementTypes(pattern: BindingPattern) { - for (const element of pattern.elements) { - if (!isOmittedExpression(element)) { - if (element.name.kind === SyntaxKind.Identifier) { - getSymbolLinks(getSymbolOfNode(element)).type = getTypeForBindingElement(element); - } - else { - assignBindingElementTypes(element.name); - } - } - } + addRelatedInfo(diagnostic, + createDiagnosticForNode(importNode, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead) + ); } + } - function createPromiseType(promisedType: Type): Type { - // creates a `Promise` type where `T` is the promisedType argument - const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); - if (globalPromiseType !== emptyGenericType) { - // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type - // Unwrap an `Awaited` to `T` to improve inference. - promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; - return createTypeReference(globalPromiseType, [promisedType]); - } + function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + const tagType = checkExpression(node.tag); + const apparentType = getApparentType(tagType); - return unknownType; + if (isErrorType(apparentType)) { + // Another error has already been reported + return resolveErrorCall(node); } - function createPromiseLikeType(promisedType: Type): Type { - // creates a `PromiseLike` type where `T` is the promisedType argument - const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ true); - if (globalPromiseLikeType !== emptyGenericType) { - // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type - // Unwrap an `Awaited` to `T` to improve inference. - promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; - return createTypeReference(globalPromiseLikeType, [promisedType]); - } + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; - return unknownType; + if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) { + return resolveUntypedCall(node); } - function createPromiseReturnType(func: FunctionLikeDeclaration | ImportCall, promisedType: Type) { - const promiseType = createPromiseType(promisedType); - if (promiseType === unknownType) { - error(func, isImportCall(func) ? - Diagnostics.A_dynamic_import_call_returns_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option : - Diagnostics.An_async_function_or_method_must_return_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option); - return errorType; - } - else if (!getGlobalPromiseConstructorSymbol(/*reportErrors*/ true)) { - error(func, isImportCall(func) ? - Diagnostics.A_dynamic_import_call_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option : - Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); + if (!callSignatures.length) { + if (isArrayLiteralExpression(node.parent)) { + const diagnostic = createDiagnosticForNode(node.tag, Diagnostics.It_is_likely_that_you_are_missing_a_comma_to_separate_these_two_template_expressions_They_form_a_tagged_template_expression_which_cannot_be_invoked); + diagnostics.add(diagnostic); + return resolveErrorCall(node); } - return promiseType; + invocationError(node.tag, apparentType, SignatureKind.Call); + return resolveErrorCall(node); } - function createNewTargetExpressionType(targetType: Type): Type { - // Create a synthetic type `NewTargetExpression { target: TargetType; }` - const symbol = createSymbol(SymbolFlags.None, "NewTargetExpression" as __String); + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + } - const targetPropertySymbol = createSymbol(SymbolFlags.Property, "target" as __String, CheckFlags.Readonly); - targetPropertySymbol.parent = symbol; - targetPropertySymbol.type = targetType; + /** + * Gets the localized diagnostic head message to use for errors when resolving a decorator as a call expression. + */ + function getDiagnosticHeadMessageForDecoratorResolution(node: Decorator) { + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return Diagnostics.Unable_to_resolve_signature_of_class_decorator_when_called_as_an_expression; - const members = createSymbolTable([targetPropertySymbol]); - symbol.members = members; - return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + case SyntaxKind.Parameter: + return Diagnostics.Unable_to_resolve_signature_of_parameter_decorator_when_called_as_an_expression; + + case SyntaxKind.PropertyDeclaration: + return Diagnostics.Unable_to_resolve_signature_of_property_decorator_when_called_as_an_expression; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return Diagnostics.Unable_to_resolve_signature_of_method_decorator_when_called_as_an_expression; + + default: + return Debug.fail(); } + } - function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { - if (!func.body) { - return errorType; - } + /** + * Resolves a decorator as if it were a call expression. + */ + function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + const funcType = checkExpression(node.expression); + const apparentType = getApparentType(funcType); + if (isErrorType(apparentType)) { + return resolveErrorCall(node); + } - const functionFlags = getFunctionFlags(func); - const isAsync = (functionFlags & FunctionFlags.Async) !== 0; - const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0; + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { + return resolveUntypedCall(node); + } - let returnType: Type | undefined; - let yieldType: Type | undefined; - let nextType: Type | undefined; - let fallbackReturnType: Type = voidType; - if (func.body.kind !== SyntaxKind.Block) { // Async or normal arrow function - returnType = checkExpressionCached(func.body, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); - if (isAsync) { - // From within an async function you can return either a non-promise value or a promise. Any - // Promise/A+ compatible implementation will always assimilate any foreign promise, so the - // return type of the body should be unwrapped to its awaited type, which we will wrap in - // the native Promise type later in this function. - returnType = unwrapAwaitedType(checkAwaitedType(returnType, /*withAlias*/ false, /*errorNode*/ func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)); - } - } - else if (isGenerator) { // Generator or AsyncGenerator function - const returnTypes = checkAndAggregateReturnExpressionTypes(func, checkMode); - if (!returnTypes) { - fallbackReturnType = neverType; - } - else if (returnTypes.length > 0) { - returnType = getUnionType(returnTypes, UnionReduction.Subtype); - } - const { yieldTypes, nextTypes } = checkAndAggregateYieldOperandTypes(func, checkMode); - yieldType = some(yieldTypes) ? getUnionType(yieldTypes, UnionReduction.Subtype) : undefined; - nextType = some(nextTypes) ? getIntersectionType(nextTypes) : undefined; - } - else { // Async or normal function - const types = checkAndAggregateReturnExpressionTypes(func, checkMode); - if (!types) { - // For an async function, the return type will not be never, but rather a Promise for never. - return functionFlags & FunctionFlags.Async - ? createPromiseReturnType(func, neverType) // Async function - : neverType; // Normal function - } - if (types.length === 0) { - // For an async function, the return type will not be void, but rather a Promise for void. - return functionFlags & FunctionFlags.Async - ? createPromiseReturnType(func, voidType) // Async function - : voidType; // Normal function - } - - // Return a union of the return expression types. - returnType = getUnionType(types, UnionReduction.Subtype); - } - - if (returnType || yieldType || nextType) { - if (yieldType) reportErrorsFromWidening(func, yieldType, WideningKind.GeneratorYield); - if (returnType) reportErrorsFromWidening(func, returnType, WideningKind.FunctionReturn); - if (nextType) reportErrorsFromWidening(func, nextType, WideningKind.GeneratorNext); - if (returnType && isUnitType(returnType) || - yieldType && isUnitType(yieldType) || - nextType && isUnitType(nextType)) { - const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); - const contextualType = !contextualSignature ? undefined : - contextualSignature === getSignatureFromDeclaration(func) ? isGenerator ? undefined : returnType : - instantiateContextualType(getReturnTypeOfSignature(contextualSignature), func); - if (isGenerator) { - yieldType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(yieldType, contextualType, IterationTypeKind.Yield, isAsync); - returnType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(returnType, contextualType, IterationTypeKind.Return, isAsync); - nextType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(nextType, contextualType, IterationTypeKind.Next, isAsync); - } - else { - returnType = getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(returnType, contextualType, isAsync); - } - } + if (isPotentiallyUncalledDecorator(node, callSignatures)) { + const nodeStr = getTextOfNode(node.expression, /*includeTrivia*/ false); + error(node, Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0, nodeStr); + return resolveErrorCall(node); + } - if (yieldType) yieldType = getWidenedType(yieldType); - if (returnType) returnType = getWidenedType(returnType); - if (nextType) nextType = getWidenedType(nextType); + const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); + if (!callSignatures.length) { + const errorDetails = invocationErrorDetails(node.expression, apparentType, SignatureKind.Call); + const messageChain = chainDiagnosticMessages(errorDetails.messageChain, headMessage); + const diag = createDiagnosticForNodeFromMessageChain(node.expression, messageChain); + if (errorDetails.relatedMessage) { + addRelatedInfo(diag, createDiagnosticForNode(node.expression, errorDetails.relatedMessage)); } + diagnostics.add(diag); + invocationErrorRecovery(apparentType, SignatureKind.Call, diag); + return resolveErrorCall(node); + } - if (isGenerator) { - return createGeneratorReturnType( - yieldType || neverType, - returnType || fallbackReturnType, - nextType || getContextualIterationType(IterationTypeKind.Next, func) || unknownType, - isAsync); - } - else { - // From within an async function you can return either a non-promise value or a promise. Any - // Promise/A+ compatible implementation will always assimilate any foreign promise, so the - // return type of the body is awaited type of the body, wrapped in a native Promise type. - return isAsync - ? createPromiseType(returnType || fallbackReturnType) - : returnType || fallbackReturnType; + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage); + } + + function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature { + const namespace = getJsxNamespaceAt(node); + const exports = namespace && getExportsOfSymbol(namespace); + // We fake up a SFC signature for each intrinsic, however a more specific per-element signature drawn from the JSX declaration + // file would probably be preferable. + const typeSymbol = exports && getSymbol(exports, JsxNames.Element, SymbolFlags.Type); + const returnNode = typeSymbol && nodeBuilder.symbolToEntityName(typeSymbol, SymbolFlags.Type, node); + const declaration = factory.createFunctionTypeNode(/*typeParameters*/ undefined, + [factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotdotdot*/ undefined, "props", /*questionMark*/ undefined, nodeBuilder.typeToTypeNode(result, node))], + returnNode ? factory.createTypeReferenceNode(returnNode, /*typeArguments*/ undefined) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ); + const parameterSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "props" as __String); + parameterSymbol.type = result; + return createSignature( + declaration, + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [parameterSymbol], + typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType, + /*returnTypePredicate*/ undefined, + 1, + SignatureFlags.None + ); + } + + function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + if (isJsxIntrinsicIdentifier(node.tagName)) { + const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); + const fakeSignature = createSignatureForJSXIntrinsic(node, result); + checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*mapper*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes); + if (length(node.typeArguments)) { + forEach(node.typeArguments, checkSourceElement); + diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), node.typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(node.typeArguments))); } + return fakeSignature; + } + const exprTypes = checkExpression(node.tagName); + const apparentType = getApparentType(exprTypes); + if (isErrorType(apparentType)) { + return resolveErrorCall(node); } - function createGeneratorReturnType(yieldType: Type, returnType: Type, nextType: Type, isAsyncGenerator: boolean) { - const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; - const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false); - yieldType = resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || unknownType; - returnType = resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || unknownType; - nextType = resolver.resolveIterationType(nextType, /*errorNode*/ undefined) || unknownType; - if (globalGeneratorType === emptyGenericType) { - // Fall back to the global IterableIterator if returnType is assignable to the expected return iteration - // type of IterableIterator, and the expected next iteration type of IterableIterator is assignable to - // nextType. - const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); - const iterationTypes = globalType !== emptyGenericType ? getIterationTypesOfGlobalIterableType(globalType, resolver) : undefined; - const iterableIteratorReturnType = iterationTypes ? iterationTypes.returnType : anyType; - const iterableIteratorNextType = iterationTypes ? iterationTypes.nextType : undefinedType; - if (isTypeAssignableTo(returnType, iterableIteratorReturnType) && - isTypeAssignableTo(iterableIteratorNextType, nextType)) { - if (globalType !== emptyGenericType) { - return createTypeFromGenericGlobalType(globalType, [yieldType]); - } - - // The global IterableIterator type doesn't exist, so report an error - resolver.getGlobalIterableIteratorType(/*reportErrors*/ true); - return emptyObjectType; - } - - // The global Generator type doesn't exist, so report an error - resolver.getGlobalGeneratorType(/*reportErrors*/ true); - return emptyObjectType; - } + const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node); + if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { + return resolveUntypedCall(node); + } - return createTypeFromGenericGlobalType(globalGeneratorType, [yieldType, returnType, nextType]); + if (signatures.length === 0) { + // We found no signatures at all, which is an error + error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); + return resolveErrorCall(node); } - function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined) { - const yieldTypes: Type[] = []; - const nextTypes: Type[] = []; - const isAsync = (getFunctionFlags(func) & FunctionFlags.Async) !== 0; - forEachYieldExpression(func.body as Block, yieldExpression => { - const yieldExpressionType = yieldExpression.expression ? checkExpression(yieldExpression.expression, checkMode) : undefinedWideningType; - pushIfUnique(yieldTypes, getYieldedTypeOfYieldExpression(yieldExpression, yieldExpressionType, anyType, isAsync)); - let nextType: Type | undefined; - if (yieldExpression.asteriskToken) { - const iterationTypes = getIterationTypesOfIterable( - yieldExpressionType, - isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, - yieldExpression.expression); - nextType = iterationTypes && iterationTypes.nextType; - } - else { - nextType = getContextualType(yieldExpression); - } - if (nextType) pushIfUnique(nextTypes, nextType); - }); - return { yieldTypes, nextTypes }; + return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + + /** + * Sometimes, we have a decorator that could accept zero arguments, + * but is receiving too many arguments as part of the decorator invocation. + * In those cases, a user may have meant to *call* the expression before using it as a decorator. + */ + function isPotentiallyUncalledDecorator(decorator: Decorator, signatures: readonly Signature[]) { + return signatures.length && every(signatures, signature => + signature.minArgumentCount === 0 && + !signatureHasRestParameter(signature) && + signature.parameters.length < getDecoratorArgumentCount(decorator, signature)); + } + + function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + switch (node.kind) { + case SyntaxKind.CallExpression: + return resolveCallExpression(node, candidatesOutArray, checkMode); + case SyntaxKind.NewExpression: + return resolveNewExpression(node, candidatesOutArray, checkMode); + case SyntaxKind.TaggedTemplateExpression: + return resolveTaggedTemplateExpression(node, candidatesOutArray, checkMode); + case SyntaxKind.Decorator: + return resolveDecorator(node, candidatesOutArray, checkMode); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + return resolveJsxOpeningLikeElement(node, candidatesOutArray, checkMode); + } + throw Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable."); + } + + /** + * Resolve a signature of a given call-like expression. + * @param node a call-like expression to try resolve a signature for + * @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service; + * the function will fill it up with appropriate candidate signatures + * @return a signature of the call-like expression or undefined if one can't be found + */ + function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, checkMode?: CheckMode): Signature { + const links = getNodeLinks(node); + // If getResolvedSignature has already been called, we will have cached the resolvedSignature. + // However, it is possible that either candidatesOutArray was not passed in the first time, + // or that a different candidatesOutArray was passed in. Therefore, we need to redo the work + // to correctly fill the candidatesOutArray. + const cached = links.resolvedSignature; + if (cached && cached !== resolvingSignature && !candidatesOutArray) { + return cached; + } + links.resolvedSignature = resolvingSignature; + const result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal); + // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call + // resolution should be deferred. + if (result !== resolvingSignature) { + // If signature resolution originated in control flow type analysis (for example to compute the + // assigned type in a flow assignment) we don't cache the result as it may be based on temporary + // types from the control flow analysis. + links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached; + } + return result; + } + + /** + * Indicates whether a declaration can be treated as a constructor in a JavaScript + * file. + */ + function isJSConstructor(node: Node | undefined): node is FunctionDeclaration | FunctionExpression { + if (!node || !isInJSFile(node)) { + return false; } + const func = isFunctionDeclaration(node) || isFunctionExpression(node) ? node : + isVariableDeclaration(node) && node.initializer && isFunctionExpression(node.initializer) ? node.initializer : + undefined; + if (func) { + // If the node has a @class tag, treat it like a constructor. + if (getJSDocClassTag(node)) return true; - function getYieldedTypeOfYieldExpression(node: YieldExpression, expressionType: Type, sentType: Type, isAsync: boolean): Type | undefined { - const errorNode = node.expression || node; - // A `yield*` expression effectively yields everything that its operand yields - const yieldedType = node.asteriskToken ? checkIteratedTypeOrElementType(isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, expressionType, sentType, errorNode) : expressionType; - return !isAsync ? yieldedType : getAwaitedType(yieldedType, errorNode, node.asteriskToken - ? Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member - : Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + // If the symbol of the node has members, treat it like a constructor. + const symbol = getSymbolOfNode(func); + return !!symbol?.members?.size; } + return false; + } - /** - * Collect the TypeFacts learned from a typeof switch with - * total clauses `witnesses`, and the active clause ranging - * from `start` to `end`. Parameter `hasDefault` denotes - * whether the active clause contains a default clause. - */ - function getFactsFromTypeofSwitch(start: number, end: number, witnesses: string[], hasDefault: boolean): TypeFacts { - let facts: TypeFacts = TypeFacts.None; - // When in the default we only collect inequality facts - // because default is 'in theory' a set of infinite - // equalities. - if (hasDefault) { - // Value is not equal to any types after the active clause. - for (let i = end; i < witnesses.length; i++) { - facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject; - } - // Remove inequalities for types that appear in the - // active clause because they appear before other - // types collected so far. - for (let i = start; i < end; i++) { - facts &= ~(typeofNEFacts.get(witnesses[i]) || 0); - } - // Add inequalities for types before the active clause unconditionally. - for (let i = 0; i < start; i++) { - facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject; - } - } - // When in an active clause without default the set of - // equalities is finite. - else { - // Add equalities for all types in the active clause. - for (let i = start; i < end; i++) { - facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject; + function mergeJSSymbols(target: Symbol, source: Symbol | undefined) { + if (source) { + const links = getSymbolLinks(source); + if (!links.inferredClassSymbol || !links.inferredClassSymbol.has(getSymbolId(target))) { + const inferred = isTransientSymbol(target) ? target : cloneSymbol(target) as TransientSymbol; + inferred.exports = inferred.exports || createSymbolTable(); + inferred.members = inferred.members || createSymbolTable(); + inferred.flags |= source.flags & SymbolFlags.Class; + if (source.exports?.size) { + mergeSymbolTable(inferred.exports, source.exports); } - // Remove equalities for types that appear before the - // active clause. - for (let i = 0; i < start; i++) { - facts &= ~(typeofEQFacts.get(witnesses[i]) || 0); + if (source.members?.size) { + mergeSymbolTable(inferred.members, source.members); } + (links.inferredClassSymbol || (links.inferredClassSymbol = new Map())).set(getSymbolId(inferred), inferred); + return inferred; } - return facts; + return links.inferredClassSymbol.get(getSymbolId(target)); } + } - function isExhaustiveSwitchStatement(node: SwitchStatement): boolean { - const links = getNodeLinks(node); - return links.isExhaustive !== undefined ? links.isExhaustive : (links.isExhaustive = computeExhaustiveSwitchStatement(node)); - } + function getAssignedClassSymbol(decl: Declaration): Symbol | undefined { + const assignmentSymbol = decl && getSymbolOfExpando(decl, /*allowDeclaration*/ true); + const prototype = assignmentSymbol?.exports?.get("prototype" as __String); + const init = prototype?.valueDeclaration && getAssignedJSPrototype(prototype.valueDeclaration); + return init ? getSymbolOfNode(init) : undefined; + } - function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean { - if (node.expression.kind === SyntaxKind.TypeOfExpression) { - const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression); - const witnesses = getSwitchClauseTypeOfWitnesses(node, /*retainDefault*/ false); - // notEqualFacts states that the type of the switched value is not equal to every type in the switch. - const notEqualFacts = getFactsFromTypeofSwitch(0, 0, witnesses, /*hasDefault*/ true); - const type = getBaseConstraintOfType(operandType) || operandType; - // Take any/unknown as a special condition. Or maybe we could change `type` to a union containing all primitive types. - if (type.flags & TypeFlags.AnyOrUnknown) { - return (TypeFacts.AllTypeofNE & notEqualFacts) === TypeFacts.AllTypeofNE; - } - return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never); + function getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined { + if (!node.parent) { + return undefined; + } + let name: Expression | BindingName | undefined; + let decl: Node | undefined; + if (isVariableDeclaration(node.parent) && node.parent.initializer === node) { + if (!isInJSFile(node) && !(isVarConst(node.parent) && isFunctionLikeDeclaration(node))) { + return undefined; } - const type = getTypeOfExpression(node.expression); - if (!isLiteralType(type)) { - return false; + name = node.parent.name; + decl = node.parent; + } + else if (isBinaryExpression(node.parent)) { + const parentNode = node.parent; + const parentNodeOperator = node.parent.operatorToken.kind; + if (parentNodeOperator === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.right === node)) { + name = parentNode.left; + decl = name; } - const switchTypes = getSwitchClauseTypes(node); - if (!switchTypes.length || some(switchTypes, isNeitherUnitTypeNorNever)) { - return false; + else if (parentNodeOperator === SyntaxKind.BarBarToken || parentNodeOperator === SyntaxKind.QuestionQuestionToken) { + if (isVariableDeclaration(parentNode.parent) && parentNode.parent.initializer === parentNode) { + name = parentNode.parent.name; + decl = parentNode.parent; + } + else if (isBinaryExpression(parentNode.parent) && parentNode.parent.operatorToken.kind === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.parent.right === parentNode)) { + name = parentNode.parent.left; + decl = name; + } + + if (!name || !isBindableStaticNameExpression(name) || !isSameEntityName(name, parentNode.left)) { + return undefined; + } } - return eachTypeContainedIn(mapType(type, getRegularTypeOfLiteralType), switchTypes); + } + else if (allowDeclaration && isFunctionDeclaration(node)) { + name = node.name; + decl = node; } - function functionHasImplicitReturn(func: FunctionLikeDeclaration) { - return func.endFlowNode && isReachableFlowNode(func.endFlowNode); + if (!decl || !name || (!allowDeclaration && !getExpandoInitializer(node, isPrototypeAccess(name)))) { + return undefined; } + return getSymbolOfNode(decl); + } - /** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */ - function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined): Type[] | undefined { - const functionFlags = getFunctionFlags(func); - const aggregatedTypes: Type[] = []; - let hasReturnWithNoExpression = functionHasImplicitReturn(func); - let hasReturnOfTypeNever = false; - forEachReturnStatement(func.body as Block, returnStatement => { - const expr = returnStatement.expression; - if (expr) { - let type = checkExpressionCached(expr, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); - if (functionFlags & FunctionFlags.Async) { - // From within an async function you can return either a non-promise value or a promise. Any - // Promise/A+ compatible implementation will always assimilate any foreign promise, so the - // return type of the body should be unwrapped to its awaited type, which should be wrapped in - // the native Promise type by the caller. - type = unwrapAwaitedType(checkAwaitedType(type, /*withAlias*/ false, func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)); - } - if (type.flags & TypeFlags.Never) { - hasReturnOfTypeNever = true; - } - pushIfUnique(aggregatedTypes, type); - } - else { - hasReturnWithNoExpression = true; - } - }); - if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) { - return undefined; - } - if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression && - !(isJSConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) { - // Javascript "callable constructors", containing eg `if (!(this instanceof A)) return new A()` should not add undefined - pushIfUnique(aggregatedTypes, undefinedType); - } - return aggregatedTypes; + + function getAssignedJSPrototype(node: Node) { + if (!node.parent) { + return false; } - function mayReturnNever(func: FunctionLikeDeclaration): boolean { - switch (func.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return true; - case SyntaxKind.MethodDeclaration: - return func.parent.kind === SyntaxKind.ObjectLiteralExpression; - default: - return false; - } + let parent: Node = node.parent; + while (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { + parent = parent.parent; + } + if (parent && isBinaryExpression(parent) && isPrototypeAccess(parent.left) && parent.operatorToken.kind === SyntaxKind.EqualsToken) { + const right = getInitializerOfBinaryExpression(parent); + return isObjectLiteralExpression(right) && right; } + } - /** - * TypeScript Specification 1.0 (6.3) - July 2014 - * An explicitly typed function whose return type isn't the Void type, - * the Any type, or a union type containing the Void or Any type as a constituent - * must have at least one return statement somewhere in its body. - * An exception to this rule is if the function implementation consists of a single 'throw' statement. - * - * @param returnType - return type of the function, can be undefined if return type is not explicitly specified - */ - function checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration | MethodSignature, returnType: Type | undefined): void { - if (!produceDiagnostics) { - return; - } + /** + * Syntactically and semantically checks a call or new expression. + * @param node The call/new expression to be checked. + * @returns On success, the expression's signature's return type. On failure, anyType. + */ + function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type { + if (!checkGrammarTypeArguments(node, node.typeArguments)) checkGrammarArguments(node.arguments); - const functionFlags = getFunctionFlags(func); - const type = returnType && unwrapReturnType(returnType, functionFlags); + const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode); + if (signature === resolvingSignature) { + // CheckMode.SkipGenericFunctions is enabled and this is a call to a generic function that + // returns a function type. We defer checking and return nonInferrableType. + return nonInferrableType; + } - // Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions. - if (type && maybeTypeOfKind(type, TypeFlags.Any | TypeFlags.Void)) { - return; - } + checkDeprecatedSignature(signature, node); - // If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check. - // also if HasImplicitReturn flag is not set this means that all codepaths in function body end with return or throw - if (func.kind === SyntaxKind.MethodSignature || nodeIsMissing(func.body) || func.body!.kind !== SyntaxKind.Block || !functionHasImplicitReturn(func)) { - return; - } + if (node.expression.kind === SyntaxKind.SuperKeyword) { + return voidType; + } - const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; - const errorNode = getEffectiveReturnTypeNode(func) || func; + if (node.kind === SyntaxKind.NewExpression) { + const declaration = signature.declaration; - if (type && type.flags & TypeFlags.Never) { - error(errorNode, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); - } - else if (type && !hasExplicitReturn) { - // minimal check: function has syntactic return type annotation and no explicit return statements in the body - // this function does not conform to the specification. - error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); - } - else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) { - error(errorNode, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); - } - else if (compilerOptions.noImplicitReturns) { - if (!type) { - // If return type annotation is omitted check if function has any explicit return statements. - // If it does not have any - its inferred return type is void - don't do any checks. - // Otherwise get inferred return type from function body and report error only if it is not void / anytype - if (!hasExplicitReturn) { - return; - } - const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func)); - if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) { - return; - } + if (declaration && + declaration.kind !== SyntaxKind.Constructor && + declaration.kind !== SyntaxKind.ConstructSignature && + declaration.kind !== SyntaxKind.ConstructorType && + !isJSDocConstructSignature(declaration) && + !isJSConstructor(declaration)) { + + // When resolved signature is a call signature (and not a construct signature) the result type is any + if (noImplicitAny) { + error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); } - error(errorNode, Diagnostics.Not_all_code_paths_return_a_value); + return anyType; } } - function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode): Type { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - checkNodeDeferred(node); + // In JavaScript files, calls to any identifier 'require' are treated as external module imports + if (isInJSFile(node) && isCommonJsRequire(node)) { + return resolveExternalModuleTypeByLiteral(node.arguments![0] as StringLiteral); + } - if (isFunctionExpression(node)) { - checkCollisionsForDeclarationName(node, node.name); + const returnType = getReturnTypeOfSignature(signature); + // Treat any call to the global 'Symbol' function that is part of a const variable or readonly property + // as a fresh unique symbol literal type. + if (returnType.flags & TypeFlags.ESSymbolLike && isSymbolOrSymbolForCall(node)) { + return getESSymbolLikeTypeForNode(walkUpParenthesizedExpressions(node.parent)); + } + if (node.kind === SyntaxKind.CallExpression && !node.questionDotToken && node.parent.kind === SyntaxKind.ExpressionStatement && + returnType.flags & TypeFlags.Void && getTypePredicateOfSignature(signature)) { + if (!isDottedName(node.expression)) { + error(node.expression, Diagnostics.Assertions_require_the_call_target_to_be_an_identifier_or_qualified_name); } - - // The identityMapper object is used to indicate that function expressions are wildcards - if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) { - // Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage - if (!getEffectiveReturnTypeNode(node) && !hasContextSensitiveParameters(node)) { - // Return plain anyFunctionType if there is no possibility we'll make inferences from the return type - const contextualSignature = getContextualSignature(node); - if (contextualSignature && couldContainTypeVariables(getReturnTypeOfSignature(contextualSignature))) { - const links = getNodeLinks(node); - if (links.contextFreeType) { - return links.contextFreeType; - } - const returnType = getReturnTypeFromBody(node, checkMode); - const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, emptyArray); - returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; - return links.contextFreeType = returnOnlyType; - } - } - return anyFunctionType; + else if (!getEffectsSignature(node)) { + const diagnostic = error(node.expression, Diagnostics.Assertions_require_every_name_in_the_call_target_to_be_declared_with_an_explicit_type_annotation); + getTypeOfDottedName(node.expression, diagnostic); } + } - // Grammar checking - const hasGrammarError = checkGrammarFunctionLikeDeclaration(node); - if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { - checkGrammarForGenerator(node); + if (isInJSFile(node)) { + const jsSymbol = getSymbolOfExpando(node, /*allowDeclaration*/ false); + if (jsSymbol?.exports?.size) { + const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, emptyArray); + jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral; + return getIntersectionType([returnType, jsAssignmentType]); } + } - contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); + return returnType; + } - return getTypeOfSymbol(getSymbolOfNode(node)); + function checkDeprecatedSignature(signature: Signature, node: CallLikeExpression) { + if (signature.declaration && signature.declaration.flags & NodeFlags.Deprecated) { + const suggestionNode = getDeprecatedSuggestionNode(node); + const name = tryGetPropertyAccessOrIdentifierToString(getInvokedExpression(node)); + addDeprecatedSuggestionWithSignature(suggestionNode, signature.declaration, name, signatureToString(signature)); } + } - function contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode) { - const links = getNodeLinks(node); - // Check if function expression is contextually typed and assign parameter types if so. - if (!(links.flags & NodeCheckFlags.ContextChecked)) { - const contextualSignature = getContextualSignature(node); - // If a type check is started at a function expression that is an argument of a function call, obtaining the - // contextual type may recursively get back to here during overload resolution of the call. If so, we will have - // already assigned contextual types. - if (!(links.flags & NodeCheckFlags.ContextChecked)) { - links.flags |= NodeCheckFlags.ContextChecked; - const signature = firstOrUndefined(getSignaturesOfType(getTypeOfSymbol(getSymbolOfNode(node)), SignatureKind.Call)); - if (!signature) { - return; - } - if (isContextSensitive(node)) { - if (contextualSignature) { - const inferenceContext = getInferenceContext(node); - if (checkMode && checkMode & CheckMode.Inferential) { - inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); - } - const instantiatedContextualSignature = inferenceContext ? - instantiateSignature(contextualSignature, inferenceContext.mapper) : contextualSignature; - assignContextualParameterTypes(signature, instantiatedContextualSignature); - } - else { - // Force resolution of all parameter types such that the absence of a contextual type is consistently reflected. - assignNonContextualParameterTypes(signature); - } - } - if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { - const returnType = getReturnTypeFromBody(node, checkMode); - if (!signature.resolvedReturnType) { - signature.resolvedReturnType = returnType; - } - } - checkSignatureDeclaration(node); - } - } + function getDeprecatedSuggestionNode(node: Node): Node { + node = skipParentheses(node); + switch (node.kind) { + case SyntaxKind.CallExpression: + case SyntaxKind.Decorator: + case SyntaxKind.NewExpression: + return getDeprecatedSuggestionNode((node as Decorator | CallExpression | NewExpression).expression); + case SyntaxKind.TaggedTemplateExpression: + return getDeprecatedSuggestionNode((node as TaggedTemplateExpression).tag); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + return getDeprecatedSuggestionNode((node as JsxOpeningLikeElement).tagName); + case SyntaxKind.ElementAccessExpression: + return (node as ElementAccessExpression).argumentExpression; + case SyntaxKind.PropertyAccessExpression: + return (node as PropertyAccessExpression).name; + case SyntaxKind.TypeReference: + const typeReference = node as TypeReferenceNode; + return isQualifiedName(typeReference.typeName) ? typeReference.typeName.right : typeReference; + default: + return node; } + } - function checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + function isSymbolOrSymbolForCall(node: Node) { + if (!isCallExpression(node)) return false; + let left = node.expression; + if (isPropertyAccessExpression(left) && left.name.escapedText === "for") { + left = left.expression; + } + if (!isIdentifier(left) || left.escapedText !== "Symbol") { + return false; + } - const functionFlags = getFunctionFlags(node); - const returnType = getReturnTypeFromAnnotation(node); - checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); + // make sure `Symbol` is the global symbol + const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); + if (!globalESSymbol) { + return false; + } - if (node.body) { - if (!getEffectiveReturnTypeNode(node)) { - // There are some checks that are only performed in getReturnTypeFromBody, that may produce errors - // we need. An example is the noImplicitAny errors resulting from widening the return expression - // of a function. Because checking of function expression bodies is deferred, there was never an - // appropriate time to do this during the main walk of the file (see the comment at the top of - // checkFunctionExpressionBodies). So it must be done now. - getReturnTypeOfSignature(getSignatureFromDeclaration(node)); - } + return globalESSymbol === resolveName(left, "Symbol" as __String, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + } - if (node.body.kind === SyntaxKind.Block) { - checkSourceElement(node.body); - } - else { - // From within an async function you can return either a non-promise value or a promise. Any - // Promise/A+ compatible implementation will always assimilate any foreign promise, so we - // should not be checking assignability of a promise to the return type. Instead, we need to - // check assignability of the awaited type of the expression body against the promised type of - // its return type annotation. - const exprType = checkExpression(node.body); - const returnOrPromisedType = returnType && unwrapReturnType(returnType, functionFlags); - if (returnOrPromisedType) { - if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function - const awaitedType = checkAwaitedType(exprType, /*withAlias*/ false, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); - checkTypeAssignableToAndOptionallyElaborate(awaitedType, returnOrPromisedType, node.body, node.body); - } - else { // Normal function - checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, node.body, node.body); - } - } - } + function checkImportCallExpression(node: ImportCall): Type { + // Check grammar of dynamic import + if (!checkGrammarArguments(node.arguments)) checkGrammarImportCallExpression(node); + + if (node.arguments.length === 0) { + return createPromiseReturnType(node, anyType); + } + + const specifier = node.arguments[0]; + const specifierType = checkExpressionCached(specifier); + const optionsType = node.arguments.length > 1 ? checkExpressionCached(node.arguments[1]) : undefined; + // Even though multiple arguments is grammatically incorrect, type-check extra arguments for completion + for (let i = 2; i < node.arguments.length; ++i) { + checkExpressionCached(node.arguments[i]); + } + + if (specifierType.flags & TypeFlags.Undefined || specifierType.flags & TypeFlags.Null || !isTypeAssignableTo(specifierType, stringType)) { + error(specifier, Diagnostics.Dynamic_import_s_specifier_must_be_of_type_string_but_here_has_type_0, typeToString(specifierType)); + } + + if (optionsType) { + const importCallOptionsType = getGlobalImportCallOptionsType(/*reportErrors*/ true); + if (importCallOptionsType !== emptyObjectType) { + checkTypeAssignableTo(optionsType, getNullableType(importCallOptionsType, TypeFlags.Undefined), node.arguments[1]); + } + } + + // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal + const moduleSymbol = resolveExternalModuleName(node, specifier); + if (moduleSymbol) { + const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true, /*suppressUsageError*/ false); + if (esModuleSymbol) { + return createPromiseReturnType(node, + getTypeWithSyntheticDefaultOnly(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) || + getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) + ); } } + return createPromiseReturnType(node, anyType); + } - function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage, isAwaitValid = false): boolean { - if (!isTypeAssignableTo(type, numberOrBigIntType)) { - const awaitedType = isAwaitValid && getAwaitedTypeOfPromise(type); - errorAndMaybeSuggestAwait( - operand, - !!awaitedType && isTypeAssignableTo(awaitedType, numberOrBigIntType), - diagnostic); - return false; + function createDefaultPropertyWrapperForModule(symbol: Symbol, originalSymbol: Symbol, anonymousSymbol?: Symbol | undefined) { + const memberTable = createSymbolTable(); + const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); + newSymbol.parent = originalSymbol; + newSymbol.nameType = getStringLiteralType("default"); + newSymbol.target = resolveSymbol(symbol); + memberTable.set(InternalSymbolName.Default, newSymbol); + return createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, emptyArray); + } + + function getTypeWithSyntheticDefaultOnly(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression) { + const hasDefaultOnly = isOnlyImportedAsDefault(moduleSpecifier); + if (hasDefaultOnly && type && !isErrorType(type)) { + const synthType = type as SyntheticDefaultModuleType; + if (!synthType.defaultOnlyType) { + const type = createDefaultPropertyWrapperForModule(symbol, originalSymbol); + synthType.defaultOnlyType = type; } - return true; + return synthType.defaultOnlyType; } + return undefined; + } - function isReadonlyAssignmentDeclaration(d: Declaration) { - if (!isCallExpression(d)) { - return false; - } - if (!isBindableObjectDefinePropertyCall(d)) { - return false; - } - const objectLitType = checkExpressionCached(d.arguments[2]); - const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String); - if (valueType) { - const writableProp = getPropertyOfType(objectLitType, "writable" as __String); - const writableType = writableProp && getTypeOfSymbol(writableProp); - if (!writableType || writableType === falseType || writableType === regularFalseType) { - return true; + function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression): Type { + if (allowSyntheticDefaultImports && type && !isErrorType(type)) { + const synthType = type as SyntheticDefaultModuleType; + if (!synthType.syntheticType) { + const file = originalSymbol.declarations?.find(isSourceFile); + const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false, moduleSpecifier); + if (hasSyntheticDefault) { + const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + const defaultContainingObject = createDefaultPropertyWrapperForModule(symbol, originalSymbol, anonymousSymbol); + anonymousSymbol.type = defaultContainingObject; + synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; } - // We include this definition whereupon we walk back and check the type at the declaration because - // The usual definition of `Object.defineProperty` will _not_ cause literal types to be preserved in the - // argument types, should the type be contextualized by the call itself. - if (writableProp && writableProp.valueDeclaration && isPropertyAssignment(writableProp.valueDeclaration)) { - const initializer = writableProp.valueDeclaration.initializer; - const rawOriginalType = checkExpression(initializer); - if (rawOriginalType === falseType || rawOriginalType === regularFalseType) { - return true; - } + else { + synthType.syntheticType = type; } - return false; } - const setProp = getPropertyOfType(objectLitType, "set" as __String); - return !setProp; - } - - function isReadonlySymbol(symbol: Symbol): boolean { - // The following symbols are considered read-only: - // Properties with a 'readonly' modifier - // Variables declared with 'const' - // Get accessors without matching set accessors - // Enum members - // Object.defineProperty assignments with writable false or no setter - // Unions and intersections of the above (unions and intersections eagerly set isReadonly on creation) - return !!(getCheckFlags(symbol) & CheckFlags.Readonly || - symbol.flags & SymbolFlags.Property && getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.Readonly || - symbol.flags & SymbolFlags.Variable && getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const || - symbol.flags & SymbolFlags.Accessor && !(symbol.flags & SymbolFlags.SetAccessor) || - symbol.flags & SymbolFlags.EnumMember || - some(symbol.declarations, isReadonlyAssignmentDeclaration) - ); + return synthType.syntheticType; } + return type; + } - function isAssignmentToReadonlyEntity(expr: Expression, symbol: Symbol, assignmentKind: AssignmentKind) { - if (assignmentKind === AssignmentKind.None) { - // no assigment means it doesn't matter whether the entity is readonly - return false; - } - if (isReadonlySymbol(symbol)) { - // Allow assignments to readonly properties within constructors of the same class declaration. - if (symbol.flags & SymbolFlags.Property && - isAccessExpression(expr) && - expr.expression.kind === SyntaxKind.ThisKeyword) { - // Look for if this is the constructor for the class that `symbol` is a property of. - const ctor = getContainingFunction(expr); - if (!(ctor && (ctor.kind === SyntaxKind.Constructor || isJSConstructor(ctor)))) { - return true; - } - if (symbol.valueDeclaration) { - const isAssignmentDeclaration = isBinaryExpression(symbol.valueDeclaration); - const isLocalPropertyDeclaration = ctor.parent === symbol.valueDeclaration.parent; - const isLocalParameterProperty = ctor === symbol.valueDeclaration.parent; - const isLocalThisPropertyAssignment = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor.parent; - const isLocalThisPropertyAssignmentConstructorFunction = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor; - const isWriteableSymbol = - isLocalPropertyDeclaration - || isLocalParameterProperty - || isLocalThisPropertyAssignment - || isLocalThisPropertyAssignmentConstructorFunction; - return !isWriteableSymbol; - } - } - return true; - } - if (isAccessExpression(expr)) { - // references through namespace import should be readonly - const node = skipParentheses(expr.expression); - if (node.kind === SyntaxKind.Identifier) { - const symbol = getNodeLinks(node).resolvedSymbol!; - if (symbol.flags & SymbolFlags.Alias) { - const declaration = getDeclarationOfAliasSymbol(symbol); - return !!declaration && declaration.kind === SyntaxKind.NamespaceImport; - } - } - } + function isCommonJsRequire(node: Node): boolean { + if (!isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { return false; } - function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage, invalidOptionalChainMessage: DiagnosticMessage): boolean { - // References are combinations of identifiers, parentheses, and property accesses. - const node = skipOuterExpressions(expr, OuterExpressionKinds.Assertions | OuterExpressionKinds.Parentheses); - if (node.kind !== SyntaxKind.Identifier && !isAccessExpression(node)) { - error(expr, invalidReferenceMessage); - return false; - } - if (node.flags & NodeFlags.OptionalChain) { - error(expr, invalidOptionalChainMessage); - return false; - } + // Make sure require is not a local function + if (!isIdentifier(node.expression)) return Debug.fail(); + const resolvedRequire = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true)!; // TODO: GH#18217 + if (resolvedRequire === requireSymbol) { return true; } + // project includes symbol named 'require' - make sure that it is ambient and local non-alias + if (resolvedRequire.flags & SymbolFlags.Alias) { + return false; + } - function checkDeleteExpression(node: DeleteExpression): Type { - checkExpression(node.expression); - const expr = skipParentheses(node.expression); - if (!isAccessExpression(expr)) { - error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_a_property_reference); - return booleanType; - } - if (isPropertyAccessExpression(expr) && isPrivateIdentifier(expr.name)) { - error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_private_identifier); - } - const links = getNodeLinks(expr); - const symbol = getExportSymbolOfValueSymbolIfExported(links.resolvedSymbol); - if (symbol) { - if (isReadonlySymbol(symbol)) { - error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_read_only_property); - } - checkDeleteExpressionMustBeOptional(expr, symbol); - } - return booleanType; + const targetDeclarationKind = resolvedRequire.flags & SymbolFlags.Function + ? SyntaxKind.FunctionDeclaration + : resolvedRequire.flags & SymbolFlags.Variable + ? SyntaxKind.VariableDeclaration + : SyntaxKind.Unknown; + if (targetDeclarationKind !== SyntaxKind.Unknown) { + const decl = getDeclarationOfKind(resolvedRequire, targetDeclarationKind)!; + // function/variable declaration should be ambient + return !!decl && !!(decl.flags & NodeFlags.Ambient); + } + return false; + } + + function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { + if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments); + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); } + const signature = getResolvedSignature(node); + checkDeprecatedSignature(signature, node); + return getReturnTypeOfSignature(signature); + } - function checkDeleteExpressionMustBeOptional(expr: AccessExpression, symbol: Symbol) { - const type = getTypeOfSymbol(symbol); - if (strictNullChecks && - !(type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Never)) && - !(exactOptionalPropertyTypes ? symbol.flags & SymbolFlags.Optional : getFalsyFlags(type) & TypeFlags.Undefined)) { - error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_optional); + function checkAssertion(node: AssertionExpression) { + if (node.kind === SyntaxKind.TypeAssertionExpression) { + const file = getSourceFileOfNode(node); + if (file && fileExtensionIsOneOf(file.fileName, [Extension.Cts, Extension.Mts])) { + grammarErrorOnNode(node, Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Use_an_as_expression_instead); } } + return checkAssertionWorker(node, node.type, node.expression); + } - function checkTypeOfExpression(node: TypeOfExpression): Type { - checkExpression(node.expression); - return typeofType; + function isValidConstAssertionArgument(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.TemplateExpression: + return true; + case SyntaxKind.ParenthesizedExpression: + return isValidConstAssertionArgument((node as ParenthesizedExpression).expression); + case SyntaxKind.PrefixUnaryExpression: + const op = (node as PrefixUnaryExpression).operator; + const arg = (node as PrefixUnaryExpression).operand; + return op === SyntaxKind.MinusToken && (arg.kind === SyntaxKind.NumericLiteral || arg.kind === SyntaxKind.BigIntLiteral) || + op === SyntaxKind.PlusToken && arg.kind === SyntaxKind.NumericLiteral; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + const expr = (node as PropertyAccessExpression | ElementAccessExpression).expression; + let symbol = getTypeOfNode(expr).symbol; + if (symbol && symbol.flags & SymbolFlags.Alias) { + symbol = resolveAlias(symbol); + } + return !!(symbol && (symbol.flags & SymbolFlags.Enum) && getEnumKind(symbol) === EnumKind.Literal); + } + return false; + } + + function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) { + let exprType = checkExpression(expression, checkMode); + if (isConstTypeReference(type)) { + if (!isValidConstAssertionArgument(expression)) { + error(expression, Diagnostics.A_const_assertions_can_only_be_applied_to_references_to_enum_members_or_string_number_boolean_array_or_object_literals); + } + return getRegularTypeOfLiteralType(exprType); + } + checkSourceElement(type); + exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType)); + const targetType = getTypeFromTypeNode(type); + if (produceDiagnostics && !isErrorType(targetType)) { + const widenedType = getWidenedType(exprType); + if (!isTypeComparableTo(targetType, widenedType)) { + checkTypeComparableTo(exprType, targetType, errNode, + Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first); + } } + return targetType; + } - function checkVoidExpression(node: VoidExpression): Type { - checkExpression(node.expression); - return undefinedWideningType; + function checkNonNullChain(node: NonNullChain) { + const leftType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(leftType, node.expression); + return propagateOptionalTypeMarker(getNonNullableType(nonOptionalType), node, nonOptionalType !== leftType); + } + + function checkNonNullAssertion(node: NonNullExpression) { + return node.flags & NodeFlags.OptionalChain ? checkNonNullChain(node as NonNullChain) : + getNonNullableType(checkExpression(node.expression)); + } + + function checkMetaProperty(node: MetaProperty): Type { + checkGrammarMetaProperty(node); + + if (node.keywordToken === SyntaxKind.NewKeyword) { + return checkNewTargetMetaProperty(node); } - function checkAwaitExpression(node: AwaitExpression): Type { - // Grammar checking - if (produceDiagnostics) { - const container = getContainingFunctionOrClassStaticBlock(node); - if (container && isClassStaticBlockDeclaration(container)) { - error(node, Diagnostics.Await_expression_cannot_be_used_inside_a_class_static_block); - } - else if (!(node.flags & NodeFlags.AwaitContext)) { - if (isInTopLevelContext(node)) { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - let span: TextSpan | undefined; - if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { - if (!span) span = getSpanOfTokenAtPosition(sourceFile, node.pos); - const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, - Diagnostics.await_expressions_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module); - diagnostics.add(diagnostic); - } - if ((moduleKind !== ModuleKind.ES2022 && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System && !(moduleKind === ModuleKind.NodeNext && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.ESNext)) || languageVersion < ScriptTarget.ES2017) { - span = getSpanOfTokenAtPosition(sourceFile, node.pos); - const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, - Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher); - diagnostics.add(diagnostic); - } - } - } - else { - // use of 'await' in non-async function - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const span = getSpanOfTokenAtPosition(sourceFile, node.pos); - const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); - if (container && container.kind !== SyntaxKind.Constructor && (getFunctionFlags(container) & FunctionFlags.Async) === 0) { - const relatedInfo = createDiagnosticForNode(container, Diagnostics.Did_you_mean_to_mark_this_function_as_async); - addRelatedInfo(diagnostic, relatedInfo); - } - diagnostics.add(diagnostic); - } - } - } + if (node.keywordToken === SyntaxKind.ImportKeyword) { + return checkImportMetaProperty(node); + } - if (isInParameterInitializerBeforeContainingFunction(node)) { - error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); - } - } + return Debug.assertNever(node.keywordToken); + } - const operandType = checkExpression(node.expression); - const awaitedType = checkAwaitedType(operandType, /*withAlias*/ true, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); - if (awaitedType === operandType && !isErrorType(awaitedType) && !(operandType.flags & TypeFlags.AnyOrUnknown)) { - addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.await_has_no_effect_on_the_type_of_this_expression)); - } - return awaitedType; + function checkMetaPropertyKeyword(node: MetaProperty): Type { + switch (node.keywordToken) { + case SyntaxKind.ImportKeyword: + return getGlobalImportMetaExpressionType(); + case SyntaxKind.NewKeyword: + const type = checkNewTargetMetaProperty(node); + return isErrorType(type) ? errorType : createNewTargetExpressionType(type); + default: + Debug.assertNever(node.keywordToken); } + } - function checkPrefixUnaryExpression(node: PrefixUnaryExpression): Type { - const operandType = checkExpression(node.operand); - if (operandType === silentNeverType) { - return silentNeverType; - } - switch (node.operand.kind) { - case SyntaxKind.NumericLiteral: - switch (node.operator) { - case SyntaxKind.MinusToken: - return getFreshTypeOfLiteralType(getNumberLiteralType(-(node.operand as NumericLiteral).text)); - case SyntaxKind.PlusToken: - return getFreshTypeOfLiteralType(getNumberLiteralType(+(node.operand as NumericLiteral).text)); - } - break; - case SyntaxKind.BigIntLiteral: - if (node.operator === SyntaxKind.MinusToken) { - return getFreshTypeOfLiteralType(getBigIntLiteralType({ - negative: true, - base10Value: parsePseudoBigInt((node.operand as BigIntLiteral).text) - })); - } - } - switch (node.operator) { - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - checkNonNullType(operandType, node.operand); - if (maybeTypeOfKind(operandType, TypeFlags.ESSymbolLike)) { - error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator)); - } - if (node.operator === SyntaxKind.PlusToken) { - if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { - error(node.operand, Diagnostics.Operator_0_cannot_be_applied_to_type_1, tokenToString(node.operator), typeToString(getBaseTypeOfLiteralType(operandType))); - } - return numberType; - } - return getUnaryResultType(operandType); - case SyntaxKind.ExclamationToken: - checkTruthinessExpression(node.operand); - const facts = getTypeFacts(operandType) & (TypeFacts.Truthy | TypeFacts.Falsy); - return facts === TypeFacts.Truthy ? falseType : - facts === TypeFacts.Falsy ? trueType : - booleanType; - case SyntaxKind.PlusPlusToken: - case SyntaxKind.MinusMinusToken: - const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand), - Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); - if (ok) { - // run check only if former checks succeeded to avoid reporting cascading errors - checkReferenceExpression( - node.operand, - Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, - Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); - } - return getUnaryResultType(operandType); - } + function checkNewTargetMetaProperty(node: MetaProperty) { + const container = getNewTargetContainer(node); + if (!container) { + error(node, Diagnostics.Meta_property_0_is_only_allowed_in_the_body_of_a_function_declaration_function_expression_or_constructor, "new.target"); return errorType; } + else if (container.kind === SyntaxKind.Constructor) { + const symbol = getSymbolOfNode(container.parent as ClassLikeDeclaration); + return getTypeOfSymbol(symbol); + } + else { + const symbol = getSymbolOfNode(container)!; + return getTypeOfSymbol(symbol); + } + } - function checkPostfixUnaryExpression(node: PostfixUnaryExpression): Type { - const operandType = checkExpression(node.operand); - if (operandType === silentNeverType) { - return silentNeverType; - } - const ok = checkArithmeticOperandType( - node.operand, - checkNonNullType(operandType, node.operand), - Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); - if (ok) { - // run check only if former checks succeeded to avoid reporting cascading errors - checkReferenceExpression( - node.operand, - Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, - Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); + function checkImportMetaProperty(node: MetaProperty) { + if (moduleKind === ModuleKind.Node12 || moduleKind === ModuleKind.NodeNext) { + if (getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.ESNext) { + error(node, Diagnostics.The_import_meta_meta_property_is_not_allowed_in_files_which_will_build_into_CommonJS_output); } - return getUnaryResultType(operandType); } + else if (moduleKind < ModuleKind.ES2020 && moduleKind !== ModuleKind.System) { + error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_when_the_module_option_is_es2020_es2022_esnext_system_node12_or_nodenext); + } + const file = getSourceFileOfNode(node); + Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag."); + return node.name.escapedText === "meta" ? getGlobalImportMetaType() : errorType; + } - function getUnaryResultType(operandType: Type): Type { - if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { - return isTypeAssignableToKind(operandType, TypeFlags.AnyOrUnknown) || maybeTypeOfKind(operandType, TypeFlags.NumberLike) - ? numberOrBigIntType - : bigintType; + function getTypeOfParameter(symbol: Symbol) { + const type = getTypeOfSymbol(symbol); + if (strictNullChecks) { + const declaration = symbol.valueDeclaration; + if (declaration && hasInitializer(declaration)) { + return getOptionalType(type); } - // If it's not a bigint type, implicit coercion will result in a number - return numberType; } + return type; + } - // Return true if type might be of the given kind. A union or intersection type might be of a given - // kind if at least one constituent type is of the given kind. - function maybeTypeOfKind(type: Type, kind: TypeFlags): boolean { - if (type.flags & kind) { - return true; - } - if (type.flags & TypeFlags.UnionOrIntersection) { - const types = (type as UnionOrIntersectionType).types; - for (const t of types) { - if (maybeTypeOfKind(t, kind)) { - return true; - } - } - } - return false; + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember) { + Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names + return d.name.escapedText; + } + + function getParameterNameAtPosition(signature: Signature, pos: number, overrideRestType?: Type) { + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + if (pos < paramCount) { + return signature.parameters[pos].escapedName; } + const restParameter = signature.parameters[paramCount] || unknownSymbol; + const restType = overrideRestType || getTypeOfSymbol(restParameter); + if (isTupleType(restType)) { + const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; + const index = pos - paramCount; + return associatedNames && getTupleElementLabel(associatedNames[index]) || restParameter.escapedName + "_" + index as __String; + } + return restParameter.escapedName; + } - function isTypeAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean { - if (source.flags & kind) { - return true; - } - if (strict && source.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null)) { - return false; - } - return !!(kind & TypeFlags.NumberLike) && isTypeAssignableTo(source, numberType) || - !!(kind & TypeFlags.BigIntLike) && isTypeAssignableTo(source, bigintType) || - !!(kind & TypeFlags.StringLike) && isTypeAssignableTo(source, stringType) || - !!(kind & TypeFlags.BooleanLike) && isTypeAssignableTo(source, booleanType) || - !!(kind & TypeFlags.Void) && isTypeAssignableTo(source, voidType) || - !!(kind & TypeFlags.Never) && isTypeAssignableTo(source, neverType) || - !!(kind & TypeFlags.Null) && isTypeAssignableTo(source, nullType) || - !!(kind & TypeFlags.Undefined) && isTypeAssignableTo(source, undefinedType) || - !!(kind & TypeFlags.ESSymbol) && isTypeAssignableTo(source, esSymbolType) || - !!(kind & TypeFlags.NonPrimitive) && isTypeAssignableTo(source, nonPrimitiveType); + function getParameterIdentifierNameAtPosition(signature: Signature, pos: number): [parameterName: __String, isRestParameter: boolean] | undefined { + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + if (pos < paramCount) { + const param = signature.parameters[pos]; + return isParameterDeclarationWithIdentifierName(param) ? [param.escapedName, false] : undefined; } - function allTypesAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean { - return source.flags & TypeFlags.Union ? - every((source as UnionType).types, subType => allTypesAssignableToKind(subType, kind, strict)) : - isTypeAssignableToKind(source, kind, strict); + const restParameter = signature.parameters[paramCount] || unknownSymbol; + if (!isParameterDeclarationWithIdentifierName(restParameter)) { + return undefined; } - function isConstEnumObjectType(type: Type): boolean { - return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isConstEnumSymbol(type.symbol); + const restType = getTypeOfSymbol(restParameter); + if (isTupleType(restType)) { + const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; + const index = pos - paramCount; + const associatedName = associatedNames?.[index]; + const isRestTupleElement = !!associatedName?.dotDotDotToken; + return associatedName ? [ + getTupleElementLabel(associatedName), + isRestTupleElement + ] : undefined; } - function isConstEnumSymbol(symbol: Symbol): boolean { - return (symbol.flags & SymbolFlags.ConstEnum) !== 0; + if (pos === paramCount) { + return [restParameter.escapedName, true]; } + return undefined; + } - function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; - } - // TypeScript 1.0 spec (April 2014): 4.15.4 - // The instanceof operator requires the left operand to be of type Any, an object type, or a type parameter type, - // and the right operand to be of type Any, a subtype of the 'Function' interface type, or have a call or construct signature. - // The result is always of the Boolean primitive type. - // NOTE: do not raise error if leftType is unknown as related error was already reported - if (!isTypeAny(leftType) && - allTypesAssignableToKind(leftType, TypeFlags.Primitive)) { - error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); - } - // NOTE: do not raise error if right is unknown as related error was already reported - if (!(isTypeAny(rightType) || typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) { - error(right, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_of_type_any_or_of_a_type_assignable_to_the_Function_interface_type); - } - return booleanType; + function isParameterDeclarationWithIdentifierName(symbol: Symbol) { + return symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isIdentifier(symbol.valueDeclaration.name); + } + function isValidDeclarationForTupleLabel(d: Declaration): d is NamedTupleMember | (ParameterDeclaration & { name: Identifier }) { + return d.kind === SyntaxKind.NamedTupleMember || (isParameter(d) && d.name && isIdentifier(d.name)); + } + + function getNameableDeclarationAtPosition(signature: Signature, pos: number) { + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + if (pos < paramCount) { + const decl = signature.parameters[pos].valueDeclaration; + return decl && isValidDeclarationForTupleLabel(decl) ? decl : undefined; + } + const restParameter = signature.parameters[paramCount] || unknownSymbol; + const restType = getTypeOfSymbol(restParameter); + if (isTupleType(restType)) { + const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; + const index = pos - paramCount; + return associatedNames && associatedNames[index]; } + return restParameter.valueDeclaration && isValidDeclarationForTupleLabel(restParameter.valueDeclaration) ? restParameter.valueDeclaration : undefined; + } + + function getTypeAtPosition(signature: Signature, pos: number): Type { + return tryGetTypeAtPosition(signature, pos) || anyType; + } - function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; + function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined { + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + if (pos < paramCount) { + return getTypeOfParameter(signature.parameters[pos]); + } + if (signatureHasRestParameter(signature)) { + // We want to return the value undefined for an out of bounds parameter position, + // so we need to check bounds here before calling getIndexedAccessType (which + // otherwise would return the type 'undefined'). + const restType = getTypeOfSymbol(signature.parameters[paramCount]); + const index = pos - paramCount; + if (!isTupleType(restType) || restType.target.hasRestElement || index < restType.target.fixedLength) { + return getIndexedAccessType(restType, getNumberLiteralType(index)); } - if (isPrivateIdentifier(left)) { - if (languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(left, ExternalEmitHelpers.ClassPrivateFieldIn); - } - // Unlike in 'checkPrivateIdentifierExpression' we now have access to the RHS type - // which provides us with the opportunity to emit more detailed errors - if (!getNodeLinks(left).resolvedSymbol && getContainingClass(left)) { - const isUncheckedJS = isUncheckedJSSuggestion(left, rightType.symbol, /*excludeClasses*/ true); - reportNonexistentProperty(left, rightType, isUncheckedJS); - } + } + return undefined; + } + + function getRestTypeAtPosition(source: Signature, pos: number): Type { + const parameterCount = getParameterCount(source); + const minArgumentCount = getMinArgumentCount(source); + const restType = getEffectiveRestType(source); + if (restType && pos >= parameterCount - 1) { + return pos === parameterCount - 1 ? restType : createArrayType(getIndexedAccessType(restType, numberType)); + } + const types = []; + const flags = []; + const names = []; + for (let i = pos; i < parameterCount; i++) { + if (!restType || i < parameterCount - 1) { + types.push(getTypeAtPosition(source, i)); + flags.push(i < minArgumentCount ? ElementFlags.Required : ElementFlags.Optional); } else { - leftType = checkNonNullType(leftType, left); - // TypeScript 1.0 spec (April 2014): 4.15.5 - // Require the left operand to be of type Any, the String primitive type, or the Number primitive type. - if (!(allTypesAssignableToKind(leftType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) || - isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.TypeParameter))) { - error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_a_private_identifier_or_of_type_any_string_number_or_symbol); - } + types.push(restType); + flags.push(ElementFlags.Variadic); } - rightType = checkNonNullType(rightType, right); - // TypeScript 1.0 spec (April 2014): 4.15.5 - // The in operator requires the right operand to be - // - // 1. assignable to the non-primitive type, - // 2. an unconstrained type parameter, - // 3. a union or intersection including one or more type parameters, whose constituents are all assignable to the - // the non-primitive type, or are unconstrainted type parameters, or have constraints assignable to the - // non-primitive type, or - // 4. a type parameter whose constraint is - // i. an object type, - // ii. the non-primitive type, or - // iii. a union or intersection with at least one constituent assignable to an object or non-primitive type. - // - // The divergent behavior for type parameters and unions containing type parameters is a workaround for type - // parameters not being narrowable. If the right operand is a concrete type, we can error if there is any chance - // it is a primitive. But if the operand is a type parameter, it cannot be narrowed, so we don't issue an error - // unless *all* instantiations would result in an error. - // - // The result is always of the Boolean primitive type. - const rightTypeConstraint = getConstraintOfType(rightType); - if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || - rightTypeConstraint && ( - isTypeAssignableToKind(rightType, TypeFlags.UnionOrIntersection) && !allTypesAssignableToKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || - !maybeTypeOfKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object) - ) - ) { - error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_not_be_a_primitive); + const name = getNameableDeclarationAtPosition(source, i); + if (name) { + names.push(name); } - return booleanType; } + return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined); + } - function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type, rightIsThis?: boolean): Type { - const properties = node.properties; - if (strictNullChecks && properties.length === 0) { - return checkNonNullType(sourceType, node); - } - for (let i = 0; i < properties.length; i++) { - checkObjectLiteralDestructuringPropertyAssignment(node, sourceType, i, properties, rightIsThis); + // Return the number of parameters in a signature. The rest parameter, if present, counts as one + // parameter. For example, the parameter count of (x: number, y: number, ...z: string[]) is 3 and + // the parameter count of (x: number, ...args: [number, ...string[], boolean])) is also 3. In the + // latter example, the effective rest type is [...string[], boolean]. + function getParameterCount(signature: Signature) { + const length = signature.parameters.length; + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[length - 1]); + if (isTupleType(restType)) { + return length + restType.target.fixedLength - (restType.target.hasRestElement ? 0 : 1); } - return sourceType; } + return length; + } - /** Note: If property cannot be a SpreadAssignment, then allProperties does not need to be provided */ - function checkObjectLiteralDestructuringPropertyAssignment(node: ObjectLiteralExpression, objectLiteralType: Type, propertyIndex: number, allProperties?: NodeArray, rightIsThis = false) { - const properties = node.properties; - const property = properties[propertyIndex]; - if (property.kind === SyntaxKind.PropertyAssignment || property.kind === SyntaxKind.ShorthandPropertyAssignment) { - const name = property.name; - const exprType = getLiteralTypeFromPropertyName(name); - if (isTypeUsableAsPropertyName(exprType)) { - const text = getPropertyNameFromType(exprType); - const prop = getPropertyOfType(objectLiteralType, text); - if (prop) { - markPropertyAsReferenced(prop, property, rightIsThis); - checkPropertyAccessibility(property, /*isSuper*/ false, /*writing*/ true, objectLiteralType, prop); + function getMinArgumentCount(signature: Signature, flags?: MinArgumentCountFlags) { + const strongArityForUntypedJS = flags! & MinArgumentCountFlags.StrongArityForUntypedJS; + const voidIsNonOptional = flags! & MinArgumentCountFlags.VoidIsNonOptional; + if (voidIsNonOptional || signature.resolvedMinArgumentCount === undefined) { + let minArgumentCount: number | undefined; + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + if (isTupleType(restType)) { + const firstOptionalIndex = findIndex(restType.target.elementFlags, f => !(f & ElementFlags.Required)); + const requiredCount = firstOptionalIndex < 0 ? restType.target.fixedLength : firstOptionalIndex; + if (requiredCount > 0) { + minArgumentCount = signature.parameters.length - 1 + requiredCount; } } - const elementType = getIndexedAccessType(objectLiteralType, exprType, AccessFlags.ExpressionPosition, name); - const type = getFlowTypeOfDestructuring(property, elementType); - return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); } - else if (property.kind === SyntaxKind.SpreadAssignment) { - if (propertyIndex < properties.length - 1) { - error(property, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); - } - else { - if (languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest); - } - const nonRestNames: PropertyName[] = []; - if (allProperties) { - for (const otherProperty of allProperties) { - if (!isSpreadAssignment(otherProperty)) { - nonRestNames.push(otherProperty.name); - } - } - } - const type = getRestType(objectLiteralType, nonRestNames, objectLiteralType.symbol); - checkGrammarForDisallowedTrailingComma(allProperties, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - return checkDestructuringAssignment(property.expression, type); + if (minArgumentCount === undefined) { + if (!strongArityForUntypedJS && signature.flags & SignatureFlags.IsUntypedSignatureInJSFile) { + return 0; } + minArgumentCount = signature.minArgumentCount; } - else { - error(property, Diagnostics.Property_assignment_expected); + if (voidIsNonOptional) { + return minArgumentCount; } + for (let i = minArgumentCount - 1; i >= 0; i--) { + const type = getTypeAtPosition(signature, i); + if (filterType(type, acceptsVoid).flags & TypeFlags.Never) { + break; + } + minArgumentCount = i; + } + signature.resolvedMinArgumentCount = minArgumentCount; } + return signature.resolvedMinArgumentCount; + } - function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type { - const elements = node.elements; - if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); + function hasEffectiveRestParameter(signature: Signature) { + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + return !isTupleType(restType) || restType.target.hasRestElement; + } + return false; + } + + function getEffectiveRestType(signature: Signature) { + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + if (!isTupleType(restType)) { + return restType; } - // This elementType will be used if the specific property corresponding to this index is not - // present (aka the tuple element property). This call also checks that the parentType is in - // fact an iterable or array (depending on target language). - const possiblyOutOfBoundsType = checkIteratedTypeOrElementType(IterationUse.Destructuring | IterationUse.PossiblyOutOfBounds, sourceType, undefinedType, node) || errorType; - let inBoundsType: Type | undefined = compilerOptions.noUncheckedIndexedAccess ? undefined: possiblyOutOfBoundsType; - for (let i = 0; i < elements.length; i++) { - let type = possiblyOutOfBoundsType; - if (node.elements[i].kind === SyntaxKind.SpreadElement) { - type = inBoundsType = inBoundsType ?? (checkIteratedTypeOrElementType(IterationUse.Destructuring, sourceType, undefinedType, node) || errorType); - } - checkArrayLiteralDestructuringElementAssignment(node, sourceType, i, type, checkMode); - } - return sourceType; - } - - function checkArrayLiteralDestructuringElementAssignment(node: ArrayLiteralExpression, sourceType: Type, - elementIndex: number, elementType: Type, checkMode?: CheckMode) { - const elements = node.elements; - const element = elements[elementIndex]; - if (element.kind !== SyntaxKind.OmittedExpression) { - if (element.kind !== SyntaxKind.SpreadElement) { - const indexType = getNumberLiteralType(elementIndex); - if (isArrayLikeType(sourceType)) { - // We create a synthetic expression so that getIndexedAccessType doesn't get confused - // when the element is a SyntaxKind.ElementAccessExpression. - const accessFlags = AccessFlags.ExpressionPosition | (hasDefaultValue(element) ? AccessFlags.NoTupleBoundsCheck : 0); - const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, accessFlags, createSyntheticExpression(element, indexType)) || errorType; - const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType; - const type = getFlowTypeOfDestructuring(element, assignedType); - return checkDestructuringAssignment(element, type, checkMode); - } - return checkDestructuringAssignment(element, elementType, checkMode); - } - if (elementIndex < elements.length - 1) { - error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); - } - else { - const restExpression = (element as SpreadElement).expression; - if (restExpression.kind === SyntaxKind.BinaryExpression && (restExpression as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - error((restExpression as BinaryExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer); - } - else { - checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - const type = everyType(sourceType, isTupleType) ? - mapType(sourceType, t => sliceTupleType(t as TupleTypeReference, elementIndex)) : - createArrayType(elementType); - return checkDestructuringAssignment(restExpression, type, checkMode); - } - } + if (restType.target.hasRestElement) { + return sliceTupleType(restType, restType.target.fixedLength); } - return undefined; } + return undefined; + } - function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: Type, checkMode?: CheckMode, rightIsThis?: boolean): Type { - let target: Expression; - if (exprOrAssignment.kind === SyntaxKind.ShorthandPropertyAssignment) { - const prop = exprOrAssignment as ShorthandPropertyAssignment; - if (prop.objectAssignmentInitializer) { - // In strict null checking mode, if a default value of a non-undefined type is specified, remove - // undefined from the final type. - if (strictNullChecks && - !(getFalsyFlags(checkExpression(prop.objectAssignmentInitializer)) & TypeFlags.Undefined)) { - sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); - } - checkBinaryLikeExpression(prop.name, prop.equalsToken!, prop.objectAssignmentInitializer, checkMode); + function getNonArrayRestType(signature: Signature) { + const restType = getEffectiveRestType(signature); + return restType && !isArrayType(restType) && !isTypeAny(restType) && (getReducedType(restType).flags & TypeFlags.Never) === 0 ? restType : undefined; + } + + function getTypeOfFirstParameterOfSignature(signature: Signature) { + return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType); + } + + function getTypeOfFirstParameterOfSignatureWithFallback(signature: Signature, fallbackType: Type) { + return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : fallbackType; + } + + function inferFromAnnotatedParameters(signature: Signature, context: Signature, inferenceContext: InferenceContext) { + const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + for (let i = 0; i < len; i++) { + const declaration = signature.parameters[i].valueDeclaration as ParameterDeclaration; + if (declaration.type) { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + inferTypes(inferenceContext.inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i)); } - target = (exprOrAssignment as ShorthandPropertyAssignment).name; + } + } + const restType = getEffectiveRestType(context); + if (restType && restType.flags & TypeFlags.TypeParameter) { + // The contextual signature has a generic rest parameter. We first instantiate the contextual + // signature (without fixing type parameters) and assign types to contextually typed parameters. + const instantiatedContext = instantiateSignature(context, inferenceContext.nonFixingMapper); + assignContextualParameterTypes(signature, instantiatedContext); + // We then infer from a tuple type representing the parameters that correspond to the contextual + // rest parameter. + const restPos = getParameterCount(context) - 1; + inferTypes(inferenceContext.inferences, getRestTypeAtPosition(signature, restPos), restType); + } + } + + function assignContextualParameterTypes(signature: Signature, context: Signature) { + if (context.typeParameters) { + if (!signature.typeParameters) { + signature.typeParameters = context.typeParameters; } else { - target = exprOrAssignment; + return; // This signature has already has a contextual inference performed and cached on it! } - - if (target.kind === SyntaxKind.BinaryExpression && (target as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - checkBinaryExpression(target as BinaryExpression, checkMode); - target = (target as BinaryExpression).left; + } + if (context.thisParameter) { + const parameter = signature.thisParameter; + if (!parameter || parameter.valueDeclaration && !(parameter.valueDeclaration as ParameterDeclaration).type) { + if (!parameter) { + signature.thisParameter = createSymbolWithType(context.thisParameter, /*type*/ undefined); + } + assignParameterType(signature.thisParameter!, getTypeOfSymbol(context.thisParameter)); } - if (target.kind === SyntaxKind.ObjectLiteralExpression) { - return checkObjectLiteralAssignment(target as ObjectLiteralExpression, sourceType, rightIsThis); + } + const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + for (let i = 0; i < len; i++) { + const parameter = signature.parameters[i]; + if (!getEffectiveTypeAnnotationNode(parameter.valueDeclaration as ParameterDeclaration)) { + const contextualParameterType = tryGetTypeAtPosition(context, i); + assignParameterType(parameter, contextualParameterType); } - if (target.kind === SyntaxKind.ArrayLiteralExpression) { - return checkArrayLiteralAssignment(target as ArrayLiteralExpression, sourceType, checkMode); + } + if (signatureHasRestParameter(signature)) { + // parameter might be a transient symbol generated by use of `arguments` in the function body. + const parameter = last(signature.parameters); + if (isTransientSymbol(parameter) || !getEffectiveTypeAnnotationNode(parameter.valueDeclaration as ParameterDeclaration)) { + const contextualParameterType = getRestTypeAtPosition(context, len); + assignParameterType(parameter, contextualParameterType); } - return checkReferenceAssignment(target, sourceType, checkMode); } + } + + function assignNonContextualParameterTypes(signature: Signature) { + if (signature.thisParameter) { + assignParameterType(signature.thisParameter); + } + for (const parameter of signature.parameters) { + assignParameterType(parameter); + } + } - function checkReferenceAssignment(target: Expression, sourceType: Type, checkMode?: CheckMode): Type { - const targetType = checkExpression(target, checkMode); - const error = target.parent.kind === SyntaxKind.SpreadAssignment ? - Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access : - Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access; - const optionalError = target.parent.kind === SyntaxKind.SpreadAssignment ? - Diagnostics.The_target_of_an_object_rest_assignment_may_not_be_an_optional_property_access : - Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access; - if (checkReferenceExpression(target, error, optionalError)) { - checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, target, target); + function assignParameterType(parameter: Symbol, type?: Type) { + const links = getSymbolLinks(parameter); + if (!links.type) { + const declaration = parameter.valueDeclaration as ParameterDeclaration; + links.type = type || getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); + if (declaration.name.kind !== SyntaxKind.Identifier) { + // if inference didn't come up with anything but unknown, fall back to the binding pattern if present. + if (links.type === unknownType) { + links.type = getTypeFromBindingPattern(declaration.name); + } + assignBindingElementTypes(declaration.name); } - if (isPrivateIdentifierPropertyAccessExpression(target)) { - checkExternalEmitHelpers(target.parent, ExternalEmitHelpers.ClassPrivateFieldSet); + } + } + + // When contextual typing assigns a type to a parameter that contains a binding pattern, we also need to push + // the destructured type into the contained binding elements. + function assignBindingElementTypes(pattern: BindingPattern) { + for (const element of pattern.elements) { + if (!isOmittedExpression(element)) { + if (element.name.kind === SyntaxKind.Identifier) { + getSymbolLinks(getSymbolOfNode(element)).type = getTypeForBindingElement(element); + } + else { + assignBindingElementTypes(element.name); + } } - return sourceType; } + } - /** - * This is a *shallow* check: An expression is side-effect-free if the - * evaluation of the expression *itself* cannot produce side effects. - * For example, x++ / 3 is side-effect free because the / operator - * does not have side effects. - * The intent is to "smell test" an expression for correctness in positions where - * its value is discarded (e.g. the left side of the comma operator). - */ - function isSideEffectFree(node: Node): boolean { - node = skipParentheses(node); - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.StringLiteral: - case SyntaxKind.RegularExpressionLiteral: - case SyntaxKind.TaggedTemplateExpression: - case SyntaxKind.TemplateExpression: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.UndefinedKeyword: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ClassExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.TypeOfExpression: - case SyntaxKind.NonNullExpression: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxElement: - return true; + function createPromiseType(promisedType: Type): Type { + // creates a `Promise` type where `T` is the promisedType argument + const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); + if (globalPromiseType !== emptyGenericType) { + // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type + // Unwrap an `Awaited` to `T` to improve inference. + promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; + return createTypeReference(globalPromiseType, [promisedType]); + } - case SyntaxKind.ConditionalExpression: - return isSideEffectFree((node as ConditionalExpression).whenTrue) && - isSideEffectFree((node as ConditionalExpression).whenFalse); + return unknownType; + } - case SyntaxKind.BinaryExpression: - if (isAssignmentOperator((node as BinaryExpression).operatorToken.kind)) { - return false; - } - return isSideEffectFree((node as BinaryExpression).left) && - isSideEffectFree((node as BinaryExpression).right); + function createPromiseLikeType(promisedType: Type): Type { + // creates a `PromiseLike` type where `T` is the promisedType argument + const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ true); + if (globalPromiseLikeType !== emptyGenericType) { + // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type + // Unwrap an `Awaited` to `T` to improve inference. + promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; + return createTypeReference(globalPromiseLikeType, [promisedType]); + } - case SyntaxKind.PrefixUnaryExpression: - case SyntaxKind.PostfixUnaryExpression: - // Unary operators ~, !, +, and - have no side effects. - // The rest do. - switch ((node as PrefixUnaryExpression).operator) { - case SyntaxKind.ExclamationToken: - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - return true; - } - return false; + return unknownType; + } + + function createPromiseReturnType(func: FunctionLikeDeclaration | ImportCall, promisedType: Type) { + const promiseType = createPromiseType(promisedType); + if (promiseType === unknownType) { + error(func, isImportCall(func) ? + Diagnostics.A_dynamic_import_call_returns_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option : + Diagnostics.An_async_function_or_method_must_return_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option); + return errorType; + } + else if (!getGlobalPromiseConstructorSymbol(/*reportErrors*/ true)) { + error(func, isImportCall(func) ? + Diagnostics.A_dynamic_import_call_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option : + Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); + } + + return promiseType; + } + + function createNewTargetExpressionType(targetType: Type): Type { + // Create a synthetic type `NewTargetExpression { target: TargetType; }` + const symbol = createSymbol(SymbolFlags.None, "NewTargetExpression" as __String); + + const targetPropertySymbol = createSymbol(SymbolFlags.Property, "target" as __String, CheckFlags.Readonly); + targetPropertySymbol.parent = symbol; + targetPropertySymbol.type = targetType; + + const members = createSymbolTable([targetPropertySymbol]); + symbol.members = members; + return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + } + + function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { + if (!func.body) { + return errorType; + } - // Some forms listed here for clarity - case SyntaxKind.VoidExpression: // Explicit opt-out - case SyntaxKind.TypeAssertionExpression: // Not SEF, but can produce useful type warnings - case SyntaxKind.AsExpression: // Not SEF, but can produce useful type warnings - default: - return false; + const functionFlags = getFunctionFlags(func); + const isAsync = (functionFlags & FunctionFlags.Async) !== 0; + const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0; + + let returnType: Type | undefined; + let yieldType: Type | undefined; + let nextType: Type | undefined; + let fallbackReturnType: Type = voidType; + if (func.body.kind !== SyntaxKind.Block) { // Async or normal arrow function + returnType = checkExpressionCached(func.body, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); + if (isAsync) { + // From within an async function you can return either a non-promise value or a promise. Any + // Promise/A+ compatible implementation will always assimilate any foreign promise, so the + // return type of the body should be unwrapped to its awaited type, which we will wrap in + // the native Promise type later in this function. + returnType = unwrapAwaitedType(checkAwaitedType(returnType, /*withAlias*/ false, /*errorNode*/ func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)); } } - - function isTypeEqualityComparableTo(source: Type, target: Type) { - return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target); + else if (isGenerator) { // Generator or AsyncGenerator function + const returnTypes = checkAndAggregateReturnExpressionTypes(func, checkMode); + if (!returnTypes) { + fallbackReturnType = neverType; + } + else if (returnTypes.length > 0) { + returnType = getUnionType(returnTypes, UnionReduction.Subtype); + } + const { yieldTypes, nextTypes } = checkAndAggregateYieldOperandTypes(func, checkMode); + yieldType = some(yieldTypes) ? getUnionType(yieldTypes, UnionReduction.Subtype) : undefined; + nextType = some(nextTypes) ? getIntersectionType(nextTypes) : undefined; } - - function createCheckBinaryExpression() { - interface WorkArea { - readonly checkMode: CheckMode | undefined; - skip: boolean; - stackIndex: number; - /** - * Holds the types from the left-side of an expression from [0..stackIndex]. - * Holds the type of the result at stackIndex+1. This allows us to reuse existing stack entries - * and avoid storing an extra property on the object (i.e., `lastResult`). - */ - typeStack: (Type | undefined)[]; + else { // Async or normal function + const types = checkAndAggregateReturnExpressionTypes(func, checkMode); + if (!types) { + // For an async function, the return type will not be never, but rather a Promise for never. + return functionFlags & FunctionFlags.Async + ? createPromiseReturnType(func, neverType) // Async function + : neverType; // Normal function } - - const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState); - - return (node: BinaryExpression, checkMode: CheckMode | undefined) => { - const result = trampoline(node, checkMode); - Debug.assertIsDefined(result); - return result; - }; - - function onEnter(node: BinaryExpression, state: WorkArea | undefined, checkMode: CheckMode | undefined) { - if (state) { - state.stackIndex++; - state.skip = false; - setLeftType(state, /*type*/ undefined); - setLastResult(state, /*type*/ undefined); + if (types.length === 0) { + // For an async function, the return type will not be void, but rather a Promise for void. + return functionFlags & FunctionFlags.Async + ? createPromiseReturnType(func, voidType) // Async function + : voidType; // Normal function + } + + // Return a union of the return expression types. + returnType = getUnionType(types, UnionReduction.Subtype); + } + + if (returnType || yieldType || nextType) { + if (yieldType) reportErrorsFromWidening(func, yieldType, WideningKind.GeneratorYield); + if (returnType) reportErrorsFromWidening(func, returnType, WideningKind.FunctionReturn); + if (nextType) reportErrorsFromWidening(func, nextType, WideningKind.GeneratorNext); + if (returnType && isUnitType(returnType) || + yieldType && isUnitType(yieldType) || + nextType && isUnitType(nextType)) { + const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); + const contextualType = !contextualSignature ? undefined : + contextualSignature === getSignatureFromDeclaration(func) ? isGenerator ? undefined : returnType : + instantiateContextualType(getReturnTypeOfSignature(contextualSignature), func); + if (isGenerator) { + yieldType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(yieldType, contextualType, IterationTypeKind.Yield, isAsync); + returnType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(returnType, contextualType, IterationTypeKind.Return, isAsync); + nextType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(nextType, contextualType, IterationTypeKind.Next, isAsync); } else { - state = { - checkMode, - skip: false, - stackIndex: 0, - typeStack: [undefined, undefined], - }; + returnType = getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(returnType, contextualType, isAsync); } + } - if (isInJSFile(node) && getAssignedExpandoInitializer(node)) { - state.skip = true; - setLastResult(state, checkExpression(node.right, checkMode)); - return state; - } + if (yieldType) yieldType = getWidenedType(yieldType); + if (returnType) returnType = getWidenedType(returnType); + if (nextType) nextType = getWidenedType(nextType); + } - checkGrammarNullishCoalesceWithLogicalExpression(node); + if (isGenerator) { + return createGeneratorReturnType( + yieldType || neverType, + returnType || fallbackReturnType, + nextType || getContextualIterationType(IterationTypeKind.Next, func) || unknownType, + isAsync); + } + else { + // From within an async function you can return either a non-promise value or a promise. Any + // Promise/A+ compatible implementation will always assimilate any foreign promise, so the + // return type of the body is awaited type of the body, wrapped in a native Promise type. + return isAsync + ? createPromiseType(returnType || fallbackReturnType) + : returnType || fallbackReturnType; + } + } - const operator = node.operatorToken.kind; - if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) { - state.skip = true; - setLastResult(state, checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword)); - return state; + function createGeneratorReturnType(yieldType: Type, returnType: Type, nextType: Type, isAsyncGenerator: boolean) { + const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; + const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false); + yieldType = resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || unknownType; + returnType = resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || unknownType; + nextType = resolver.resolveIterationType(nextType, /*errorNode*/ undefined) || unknownType; + if (globalGeneratorType === emptyGenericType) { + // Fall back to the global IterableIterator if returnType is assignable to the expected return iteration + // type of IterableIterator, and the expected next iteration type of IterableIterator is assignable to + // nextType. + const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); + const iterationTypes = globalType !== emptyGenericType ? getIterationTypesOfGlobalIterableType(globalType, resolver) : undefined; + const iterableIteratorReturnType = iterationTypes ? iterationTypes.returnType : anyType; + const iterableIteratorNextType = iterationTypes ? iterationTypes.nextType : undefinedType; + if (isTypeAssignableTo(returnType, iterableIteratorReturnType) && + isTypeAssignableTo(iterableIteratorNextType, nextType)) { + if (globalType !== emptyGenericType) { + return createTypeFromGenericGlobalType(globalType, [yieldType]); } - return state; + // The global IterableIterator type doesn't exist, so report an error + resolver.getGlobalIterableIteratorType(/*reportErrors*/ true); + return emptyObjectType; } - function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) { - if (!state.skip) { - return maybeCheckExpression(state, left); - } - } + // The global Generator type doesn't exist, so report an error + resolver.getGlobalGeneratorType(/*reportErrors*/ true); + return emptyObjectType; + } - function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) { - if (!state.skip) { - const leftType = getLastResult(state); - Debug.assertIsDefined(leftType); - setLeftType(state, leftType); - setLastResult(state, /*type*/ undefined); - const operator = operatorToken.kind; - if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { - if (operator === SyntaxKind.AmpersandAmpersandToken) { - const parent = walkUpParenthesizedExpressions(node.parent); - checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined); - } - checkTruthinessOfType(leftType, node.left); - } - } - } + return createTypeFromGenericGlobalType(globalGeneratorType, [yieldType, returnType, nextType]); + } - function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) { - if (!state.skip) { - return maybeCheckExpression(state, right); - } + function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined) { + const yieldTypes: Type[] = []; + const nextTypes: Type[] = []; + const isAsync = (getFunctionFlags(func) & FunctionFlags.Async) !== 0; + forEachYieldExpression(func.body as Block, yieldExpression => { + const yieldExpressionType = yieldExpression.expression ? checkExpression(yieldExpression.expression, checkMode) : undefinedWideningType; + pushIfUnique(yieldTypes, getYieldedTypeOfYieldExpression(yieldExpression, yieldExpressionType, anyType, isAsync)); + let nextType: Type | undefined; + if (yieldExpression.asteriskToken) { + const iterationTypes = getIterationTypesOfIterable( + yieldExpressionType, + isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, + yieldExpression.expression); + nextType = iterationTypes && iterationTypes.nextType; + } + else { + nextType = getContextualType(yieldExpression); } + if (nextType) pushIfUnique(nextTypes, nextType); + }); + return { yieldTypes, nextTypes }; + } - function onExit(node: BinaryExpression, state: WorkArea): Type | undefined { - let result: Type | undefined; - if (state.skip) { - result = getLastResult(state); - } - else { - const leftType = getLeftType(state); - Debug.assertIsDefined(leftType); + function getYieldedTypeOfYieldExpression(node: YieldExpression, expressionType: Type, sentType: Type, isAsync: boolean): Type | undefined { + const errorNode = node.expression || node; + // A `yield*` expression effectively yields everything that its operand yields + const yieldedType = node.asteriskToken ? checkIteratedTypeOrElementType(isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, expressionType, sentType, errorNode) : expressionType; + return !isAsync ? yieldedType : getAwaitedType(yieldedType, errorNode, node.asteriskToken + ? Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member + : Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + } - const rightType = getLastResult(state); - Debug.assertIsDefined(rightType); + /** + * Collect the TypeFacts learned from a typeof switch with + * total clauses `witnesses`, and the active clause ranging + * from `start` to `end`. Parameter `hasDefault` denotes + * whether the active clause contains a default clause. + */ + function getFactsFromTypeofSwitch(start: number, end: number, witnesses: string[], hasDefault: boolean): TypeFacts { + let facts: TypeFacts = TypeFacts.None; + // When in the default we only collect inequality facts + // because default is 'in theory' a set of infinite + // equalities. + if (hasDefault) { + // Value is not equal to any types after the active clause. + for (let i = end; i < witnesses.length; i++) { + facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject; + } + // Remove inequalities for types that appear in the + // active clause because they appear before other + // types collected so far. + for (let i = start; i < end; i++) { + facts &= ~(typeofNEFacts.get(witnesses[i]) || 0); + } + // Add inequalities for types before the active clause unconditionally. + for (let i = 0; i < start; i++) { + facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject; + } + } + // When in an active clause without default the set of + // equalities is finite. + else { + // Add equalities for all types in the active clause. + for (let i = start; i < end; i++) { + facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject; + } + // Remove equalities for types that appear before the + // active clause. + for (let i = 0; i < start; i++) { + facts &= ~(typeofEQFacts.get(witnesses[i]) || 0); + } + } + return facts; + } - result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node); - } + function isExhaustiveSwitchStatement(node: SwitchStatement): boolean { + const links = getNodeLinks(node); + return links.isExhaustive !== undefined ? links.isExhaustive : (links.isExhaustive = computeExhaustiveSwitchStatement(node)); + } - state.skip = false; - setLeftType(state, /*type*/ undefined); - setLastResult(state, /*type*/ undefined); - state.stackIndex--; - return result; - } + function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean { + if (node.expression.kind === SyntaxKind.TypeOfExpression) { + const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression); + const witnesses = getSwitchClauseTypeOfWitnesses(node, /*retainDefault*/ false); + // notEqualFacts states that the type of the switched value is not equal to every type in the switch. + const notEqualFacts = getFactsFromTypeofSwitch(0, 0, witnesses, /*hasDefault*/ true); + const type = getBaseConstraintOfType(operandType) || operandType; + // Take any/unknown as a special condition. Or maybe we could change `type` to a union containing all primitive types. + if (type.flags & TypeFlags.AnyOrUnknown) { + return (TypeFacts.AllTypeofNE & notEqualFacts) === TypeFacts.AllTypeofNE; + } + return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never); + } + const type = getTypeOfExpression(node.expression); + if (!isLiteralType(type)) { + return false; + } + const switchTypes = getSwitchClauseTypes(node); + if (!switchTypes.length || some(switchTypes, isNeitherUnitTypeNorNever)) { + return false; + } + return eachTypeContainedIn(mapType(type, getRegularTypeOfLiteralType), switchTypes); + } - function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") { - setLastResult(state, result); - return state; - } + function functionHasImplicitReturn(func: FunctionLikeDeclaration) { + return func.endFlowNode && isReachableFlowNode(func.endFlowNode); + } - function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined { - if (isBinaryExpression(node)) { - return node; + /** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */ + function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined): Type[] | undefined { + const functionFlags = getFunctionFlags(func); + const aggregatedTypes: Type[] = []; + let hasReturnWithNoExpression = functionHasImplicitReturn(func); + let hasReturnOfTypeNever = false; + forEachReturnStatement(func.body as Block, returnStatement => { + const expr = returnStatement.expression; + if (expr) { + let type = checkExpressionCached(expr, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); + if (functionFlags & FunctionFlags.Async) { + // From within an async function you can return either a non-promise value or a promise. Any + // Promise/A+ compatible implementation will always assimilate any foreign promise, so the + // return type of the body should be unwrapped to its awaited type, which should be wrapped in + // the native Promise type by the caller. + type = unwrapAwaitedType(checkAwaitedType(type, /*withAlias*/ false, func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)); + } + if (type.flags & TypeFlags.Never) { + hasReturnOfTypeNever = true; } - setLastResult(state, checkExpression(node, state.checkMode)); + pushIfUnique(aggregatedTypes, type); } - - function getLeftType(state: WorkArea) { - return state.typeStack[state.stackIndex]; + else { + hasReturnWithNoExpression = true; } + }); + if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) { + return undefined; + } + if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression && + !(isJSConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) { + // Javascript "callable constructors", containing eg `if (!(this instanceof A)) return new A()` should not add undefined + pushIfUnique(aggregatedTypes, undefinedType); + } + return aggregatedTypes; + } + function mayReturnNever(func: FunctionLikeDeclaration): boolean { + switch (func.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return true; + case SyntaxKind.MethodDeclaration: + return func.parent.kind === SyntaxKind.ObjectLiteralExpression; + default: + return false; + } + } - function setLeftType(state: WorkArea, type: Type | undefined) { - state.typeStack[state.stackIndex] = type; - } + /** + * TypeScript Specification 1.0 (6.3) - July 2014 + * An explicitly typed function whose return type isn't the Void type, + * the Any type, or a union type containing the Void or Any type as a constituent + * must have at least one return statement somewhere in its body. + * An exception to this rule is if the function implementation consists of a single 'throw' statement. + * + * @param returnType - return type of the function, can be undefined if return type is not explicitly specified + */ + function checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration | MethodSignature, returnType: Type | undefined): void { + if (!produceDiagnostics) { + return; + } - function getLastResult(state: WorkArea) { - return state.typeStack[state.stackIndex + 1]; - } + const functionFlags = getFunctionFlags(func); + const type = returnType && unwrapReturnType(returnType, functionFlags); - function setLastResult(state: WorkArea, type: Type | undefined) { - // To reduce overhead, reuse the next stack entry to store the - // last result. This avoids the overhead of an additional property - // on `WorkArea` and reuses empty stack entries as we walk back up - // the stack. - state.typeStack[state.stackIndex + 1] = type; - } + // Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions. + if (type && maybeTypeOfKind(type, TypeFlags.Any | TypeFlags.Void)) { + return; } - function checkGrammarNullishCoalesceWithLogicalExpression(node: BinaryExpression) { - const { left, operatorToken, right } = node; - if (operatorToken.kind === SyntaxKind.QuestionQuestionToken) { - if (isBinaryExpression(left) && (left.operatorToken.kind === SyntaxKind.BarBarToken || left.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { - grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(left.operatorToken.kind), tokenToString(operatorToken.kind)); + // If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check. + // also if HasImplicitReturn flag is not set this means that all codepaths in function body end with return or throw + if (func.kind === SyntaxKind.MethodSignature || nodeIsMissing(func.body) || func.body!.kind !== SyntaxKind.Block || !functionHasImplicitReturn(func)) { + return; + } + + const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; + const errorNode = getEffectiveReturnTypeNode(func) || func; + + if (type && type.flags & TypeFlags.Never) { + error(errorNode, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); + } + else if (type && !hasExplicitReturn) { + // minimal check: function has syntactic return type annotation and no explicit return statements in the body + // this function does not conform to the specification. + error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); + } + else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) { + error(errorNode, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); + } + else if (compilerOptions.noImplicitReturns) { + if (!type) { + // If return type annotation is omitted check if function has any explicit return statements. + // If it does not have any - its inferred return type is void - don't do any checks. + // Otherwise get inferred return type from function body and report error only if it is not void / anytype + if (!hasExplicitReturn) { + return; } - if (isBinaryExpression(right) && (right.operatorToken.kind === SyntaxKind.BarBarToken || right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { - grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind)); + const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func)); + if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) { + return; } } + error(errorNode, Diagnostics.Not_all_code_paths_return_a_value); } + } - // Note that this and `checkBinaryExpression` above should behave mostly the same, except this elides some - // expression-wide checks and does not use a work stack to fold nested binary expressions into the same callstack frame - function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, errorNode?: Node): Type { - const operator = operatorToken.kind; - if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) { - return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode, right.kind === SyntaxKind.ThisKeyword); - } - let leftType: Type; - if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { - leftType = checkTruthinessExpression(left, checkMode); - } - else { - leftType = checkExpression(left, checkMode); - } + function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode): Type { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + checkNodeDeferred(node); - const rightType = checkExpression(right, checkMode); - return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, errorNode); + if (isFunctionExpression(node)) { + checkCollisionsForDeclarationName(node, node.name); } - function checkBinaryLikeExpressionWorker( - left: Expression, - operatorToken: Node, - right: Expression, - leftType: Type, - rightType: Type, - errorNode?: Node - ): Type { - const operator = operatorToken.kind; - switch (operator) { - case SyntaxKind.AsteriskToken: - case SyntaxKind.AsteriskAsteriskToken: - case SyntaxKind.AsteriskEqualsToken: - case SyntaxKind.AsteriskAsteriskEqualsToken: - case SyntaxKind.SlashToken: - case SyntaxKind.SlashEqualsToken: - case SyntaxKind.PercentToken: - case SyntaxKind.PercentEqualsToken: - case SyntaxKind.MinusToken: - case SyntaxKind.MinusEqualsToken: - case SyntaxKind.LessThanLessThanToken: - case SyntaxKind.LessThanLessThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanToken: - case SyntaxKind.GreaterThanGreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - case SyntaxKind.BarToken: - case SyntaxKind.BarEqualsToken: - case SyntaxKind.CaretToken: - case SyntaxKind.CaretEqualsToken: - case SyntaxKind.AmpersandToken: - case SyntaxKind.AmpersandEqualsToken: - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; - } - - leftType = checkNonNullType(leftType, left); - rightType = checkNonNullType(rightType, right); - - let suggestedOperator: SyntaxKind | undefined; - // if a user tries to apply a bitwise operator to 2 boolean operands - // try and return them a helpful suggestion - if ((leftType.flags & TypeFlags.BooleanLike) && - (rightType.flags & TypeFlags.BooleanLike) && - (suggestedOperator = getSuggestedBooleanOperator(operatorToken.kind)) !== undefined) { - error(errorNode || operatorToken, Diagnostics.The_0_operator_is_not_allowed_for_boolean_types_Consider_using_1_instead, tokenToString(operatorToken.kind), tokenToString(suggestedOperator)); - return numberType; - } - else { - // otherwise just check each operand separately and report errors as normal - const leftOk = checkArithmeticOperandType(left, leftType, Diagnostics.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); - const rightOk = checkArithmeticOperandType(right, rightType, Diagnostics.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); - let resultType: Type; - // If both are any or unknown, allow operation; assume it will resolve to number - if ((isTypeAssignableToKind(leftType, TypeFlags.AnyOrUnknown) && isTypeAssignableToKind(rightType, TypeFlags.AnyOrUnknown)) || - // Or, if neither could be bigint, implicit coercion results in a number result - !(maybeTypeOfKind(leftType, TypeFlags.BigIntLike) || maybeTypeOfKind(rightType, TypeFlags.BigIntLike)) - ) { - resultType = numberType; - } - // At least one is assignable to bigint, so check that both are - else if (bothAreBigIntLike(leftType, rightType)) { - switch (operator) { - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - reportOperatorError(); - break; - case SyntaxKind.AsteriskAsteriskToken: - case SyntaxKind.AsteriskAsteriskEqualsToken: - if (languageVersion < ScriptTarget.ES2016) { - error(errorNode, Diagnostics.Exponentiation_cannot_be_performed_on_bigint_values_unless_the_target_option_is_set_to_es2016_or_later); - } - } - resultType = bigintType; - } - // Exactly one of leftType/rightType is assignable to bigint - else { - reportOperatorError(bothAreBigIntLike); - resultType = errorType; - } - if (leftOk && rightOk) { - checkAssignmentOperator(resultType); - } - return resultType; - } - case SyntaxKind.PlusToken: - case SyntaxKind.PlusEqualsToken: - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; - } - - if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) { - leftType = checkNonNullType(leftType, left); - rightType = checkNonNullType(rightType, right); - } - - let resultType: Type | undefined; - if (isTypeAssignableToKind(leftType, TypeFlags.NumberLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.NumberLike, /*strict*/ true)) { - // Operands of an enum type are treated as having the primitive type Number. - // If both operands are of the Number primitive type, the result is of the Number primitive type. - resultType = numberType; - } - else if (isTypeAssignableToKind(leftType, TypeFlags.BigIntLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.BigIntLike, /*strict*/ true)) { - // If both operands are of the BigInt primitive type, the result is of the BigInt primitive type. - resultType = bigintType; - } - else if (isTypeAssignableToKind(leftType, TypeFlags.StringLike, /*strict*/ true) || isTypeAssignableToKind(rightType, TypeFlags.StringLike, /*strict*/ true)) { - // If one or both operands are of the String primitive type, the result is of the String primitive type. - resultType = stringType; - } - else if (isTypeAny(leftType) || isTypeAny(rightType)) { - // Otherwise, the result is of type Any. - // NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we. - resultType = isErrorType(leftType) || isErrorType(rightType) ? errorType : anyType; + // The identityMapper object is used to indicate that function expressions are wildcards + if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) { + // Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage + if (!getEffectiveReturnTypeNode(node) && !hasContextSensitiveParameters(node)) { + // Return plain anyFunctionType if there is no possibility we'll make inferences from the return type + const contextualSignature = getContextualSignature(node); + if (contextualSignature && couldContainTypeVariables(getReturnTypeOfSignature(contextualSignature))) { + const links = getNodeLinks(node); + if (links.contextFreeType) { + return links.contextFreeType; } + const returnType = getReturnTypeFromBody(node, checkMode); + const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, emptyArray); + returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; + return links.contextFreeType = returnOnlyType; + } + } + return anyFunctionType; + } - // Symbols are not allowed at all in arithmetic expressions - if (resultType && !checkForDisallowedESSymbolOperand(operator)) { - return resultType; - } + // Grammar checking + const hasGrammarError = checkGrammarFunctionLikeDeclaration(node); + if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { + checkGrammarForGenerator(node); + } - if (!resultType) { - // Types that have a reasonably good chance of being a valid operand type. - // If both types have an awaited type of one of these, we'll assume the user - // might be missing an await without doing an exhaustive check that inserting - // await(s) will actually be a completely valid binary expression. - const closeEnoughKind = TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.AnyOrUnknown; - reportOperatorError((left, right) => - isTypeAssignableToKind(left, closeEnoughKind) && - isTypeAssignableToKind(right, closeEnoughKind)); - return anyType; - } + contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); - if (operator === SyntaxKind.PlusEqualsToken) { - checkAssignmentOperator(resultType); - } - return resultType; - case SyntaxKind.LessThanToken: - case SyntaxKind.GreaterThanToken: - case SyntaxKind.LessThanEqualsToken: - case SyntaxKind.GreaterThanEqualsToken: - if (checkForDisallowedESSymbolOperand(operator)) { - leftType = getBaseTypeOfLiteralType(checkNonNullType(leftType, left)); - rightType = getBaseTypeOfLiteralType(checkNonNullType(rightType, right)); - reportOperatorErrorUnless((left, right) => - isTypeComparableTo(left, right) || isTypeComparableTo(right, left) || ( - isTypeAssignableTo(left, numberOrBigIntType) && isTypeAssignableTo(right, numberOrBigIntType))); - } - return booleanType; - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left)); - return booleanType; + return getTypeOfSymbol(getSymbolOfNode(node)); + } - case SyntaxKind.InstanceOfKeyword: - return checkInstanceOfExpression(left, right, leftType, rightType); - case SyntaxKind.InKeyword: - return checkInExpression(left, right, leftType, rightType); - case SyntaxKind.AmpersandAmpersandToken: - case SyntaxKind.AmpersandAmpersandEqualsToken: { - const resultType = getTypeFacts(leftType) & TypeFacts.Truthy ? - getUnionType([extractDefinitelyFalsyTypes(strictNullChecks ? leftType : getBaseTypeOfLiteralType(rightType)), rightType]) : - leftType; - if (operator === SyntaxKind.AmpersandAmpersandEqualsToken) { - checkAssignmentOperator(rightType); - } - return resultType; - } - case SyntaxKind.BarBarToken: - case SyntaxKind.BarBarEqualsToken: { - const resultType = getTypeFacts(leftType) & TypeFacts.Falsy ? - getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) : - leftType; - if (operator === SyntaxKind.BarBarEqualsToken) { - checkAssignmentOperator(rightType); - } - return resultType; - } - case SyntaxKind.QuestionQuestionToken: - case SyntaxKind.QuestionQuestionEqualsToken: { - const resultType = getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ? - getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) : - leftType; - if (operator === SyntaxKind.QuestionQuestionEqualsToken) { - checkAssignmentOperator(rightType); - } - return resultType; + function contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode) { + const links = getNodeLinks(node); + // Check if function expression is contextually typed and assign parameter types if so. + if (!(links.flags & NodeCheckFlags.ContextChecked)) { + const contextualSignature = getContextualSignature(node); + // If a type check is started at a function expression that is an argument of a function call, obtaining the + // contextual type may recursively get back to here during overload resolution of the call. If so, we will have + // already assigned contextual types. + if (!(links.flags & NodeCheckFlags.ContextChecked)) { + links.flags |= NodeCheckFlags.ContextChecked; + const signature = firstOrUndefined(getSignaturesOfType(getTypeOfSymbol(getSymbolOfNode(node)), SignatureKind.Call)); + if (!signature) { + return; } - case SyntaxKind.EqualsToken: - const declKind = isBinaryExpression(left.parent) ? getAssignmentDeclarationKind(left.parent) : AssignmentDeclarationKind.None; - checkAssignmentDeclaration(declKind, rightType); - if (isAssignmentDeclaration(declKind)) { - if (!(rightType.flags & TypeFlags.Object) || - declKind !== AssignmentDeclarationKind.ModuleExports && - declKind !== AssignmentDeclarationKind.Prototype && - !isEmptyObjectType(rightType) && - !isFunctionObjectType(rightType as ObjectType) && - !(getObjectFlags(rightType) & ObjectFlags.Class)) { - // don't check assignability of module.exports=, C.prototype=, or expando types because they will necessarily be incomplete - checkAssignmentOperator(rightType); - } - return leftType; + if (isContextSensitive(node)) { + if (contextualSignature) { + const inferenceContext = getInferenceContext(node); + if (checkMode && checkMode & CheckMode.Inferential) { + inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); + } + const instantiatedContextualSignature = inferenceContext ? + instantiateSignature(contextualSignature, inferenceContext.mapper) : contextualSignature; + assignContextualParameterTypes(signature, instantiatedContextualSignature); } else { - checkAssignmentOperator(rightType); - return getRegularTypeOfObjectLiteral(rightType); + // Force resolution of all parameter types such that the absence of a contextual type is consistently reflected. + assignNonContextualParameterTypes(signature); } - case SyntaxKind.CommaToken: - if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isEvalNode(right)) { - const sf = getSourceFileOfNode(left); - const sourceText = sf.text; - const start = skipTrivia(sourceText, left.pos); - const isInDiag2657 = sf.parseDiagnostics.some(diag => { - if (diag.code !== Diagnostics.JSX_expressions_must_have_one_parent_element.code) return false; - return textSpanContainsPosition(diag, start); - }); - if (!isInDiag2657) error(left, Diagnostics.Left_side_of_comma_operator_is_unused_and_has_no_side_effects); + } + if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { + const returnType = getReturnTypeFromBody(node, checkMode); + if (!signature.resolvedReturnType) { + signature.resolvedReturnType = returnType; } - return rightType; - - default: - return Debug.fail(); + } + checkSignatureDeclaration(node); } + } + } + + function checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + + const functionFlags = getFunctionFlags(node); + const returnType = getReturnTypeFromAnnotation(node); + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); - function bothAreBigIntLike(left: Type, right: Type): boolean { - return isTypeAssignableToKind(left, TypeFlags.BigIntLike) && isTypeAssignableToKind(right, TypeFlags.BigIntLike); + if (node.body) { + if (!getEffectiveReturnTypeNode(node)) { + // There are some checks that are only performed in getReturnTypeFromBody, that may produce errors + // we need. An example is the noImplicitAny errors resulting from widening the return expression + // of a function. Because checking of function expression bodies is deferred, there was never an + // appropriate time to do this during the main walk of the file (see the comment at the top of + // checkFunctionExpressionBodies). So it must be done now. + getReturnTypeOfSignature(getSignatureFromDeclaration(node)); } - function checkAssignmentDeclaration(kind: AssignmentDeclarationKind, rightType: Type) { - if (kind === AssignmentDeclarationKind.ModuleExports) { - for (const prop of getPropertiesOfObjectType(rightType)) { - const propType = getTypeOfSymbol(prop); - if (propType.symbol && propType.symbol.flags & SymbolFlags.Class) { - const name = prop.escapedName; - const symbol = resolveName(prop.valueDeclaration, name, SymbolFlags.Type, undefined, name, /*isUse*/ false); - if (symbol?.declarations && symbol.declarations.some(isJSDocTypedefTag)) { - addDuplicateDeclarationErrorsForSymbols(symbol, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), prop); - addDuplicateDeclarationErrorsForSymbols(prop, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), symbol); - } - } + if (node.body.kind === SyntaxKind.Block) { + checkSourceElement(node.body); + } + else { + // From within an async function you can return either a non-promise value or a promise. Any + // Promise/A+ compatible implementation will always assimilate any foreign promise, so we + // should not be checking assignability of a promise to the return type. Instead, we need to + // check assignability of the awaited type of the expression body against the promised type of + // its return type annotation. + const exprType = checkExpression(node.body); + const returnOrPromisedType = returnType && unwrapReturnType(returnType, functionFlags); + if (returnOrPromisedType) { + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function + const awaitedType = checkAwaitedType(exprType, /*withAlias*/ false, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + checkTypeAssignableToAndOptionallyElaborate(awaitedType, returnOrPromisedType, node.body, node.body); + } + else { // Normal function + checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, node.body, node.body); } } } + } + } - function isEvalNode(node: Expression) { - return node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "eval"; - } - - // Return true if there was no error, false if there was an error. - function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean { - const offendingSymbolOperand = - maybeTypeOfKind(leftType, TypeFlags.ESSymbolLike) ? left : - maybeTypeOfKind(rightType, TypeFlags.ESSymbolLike) ? right : - undefined; - - if (offendingSymbolOperand) { - error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); - return false; - } + function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage, isAwaitValid = false): boolean { + if (!isTypeAssignableTo(type, numberOrBigIntType)) { + const awaitedType = isAwaitValid && getAwaitedTypeOfPromise(type); + errorAndMaybeSuggestAwait( + operand, + !!awaitedType && isTypeAssignableTo(awaitedType, numberOrBigIntType), + diagnostic); + return false; + } + return true; + } + function isReadonlyAssignmentDeclaration(d: Declaration) { + if (!isCallExpression(d)) { + return false; + } + if (!isBindableObjectDefinePropertyCall(d)) { + return false; + } + const objectLitType = checkExpressionCached(d.arguments[2]); + const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String); + if (valueType) { + const writableProp = getPropertyOfType(objectLitType, "writable" as __String); + const writableType = writableProp && getTypeOfSymbol(writableProp); + if (!writableType || writableType === falseType || writableType === regularFalseType) { return true; } - - function getSuggestedBooleanOperator(operator: SyntaxKind): SyntaxKind | undefined { - switch (operator) { - case SyntaxKind.BarToken: - case SyntaxKind.BarEqualsToken: - return SyntaxKind.BarBarToken; - case SyntaxKind.CaretToken: - case SyntaxKind.CaretEqualsToken: - return SyntaxKind.ExclamationEqualsEqualsToken; - case SyntaxKind.AmpersandToken: - case SyntaxKind.AmpersandEqualsToken: - return SyntaxKind.AmpersandAmpersandToken; - default: - return undefined; + // We include this definition whereupon we walk back and check the type at the declaration because + // The usual definition of `Object.defineProperty` will _not_ cause literal types to be preserved in the + // argument types, should the type be contextualized by the call itself. + if (writableProp && writableProp.valueDeclaration && isPropertyAssignment(writableProp.valueDeclaration)) { + const initializer = writableProp.valueDeclaration.initializer; + const rawOriginalType = checkExpression(initializer); + if (rawOriginalType === falseType || rawOriginalType === regularFalseType) { + return true; } } + return false; + } + const setProp = getPropertyOfType(objectLitType, "set" as __String); + return !setProp; + } - function checkAssignmentOperator(valueType: Type): void { - if (produceDiagnostics && isAssignmentOperator(operator)) { - // TypeScript 1.0 spec (April 2014): 4.17 - // An assignment of the form - // VarExpr = ValueExpr - // requires VarExpr to be classified as a reference - // A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1) - // and the type of the non-compound operation to be assignable to the type of VarExpr. - - if (checkReferenceExpression(left, - Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access, - Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access) - && (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) { + function isReadonlySymbol(symbol: Symbol): boolean { + // The following symbols are considered read-only: + // Properties with a 'readonly' modifier + // Variables declared with 'const' + // Get accessors without matching set accessors + // Enum members + // Object.defineProperty assignments with writable false or no setter + // Unions and intersections of the above (unions and intersections eagerly set isReadonly on creation) + return !!(getCheckFlags(symbol) & CheckFlags.Readonly || + symbol.flags & SymbolFlags.Property && getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.Readonly || + symbol.flags & SymbolFlags.Variable && getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const || + symbol.flags & SymbolFlags.Accessor && !(symbol.flags & SymbolFlags.SetAccessor) || + symbol.flags & SymbolFlags.EnumMember || + some(symbol.declarations, isReadonlyAssignmentDeclaration) + ); + } - let headMessage: DiagnosticMessage | undefined; - if (exactOptionalPropertyTypes && isPropertyAccessExpression(left) && maybeTypeOfKind(valueType, TypeFlags.Undefined)) { - const target = getTypeOfPropertyOfType(getTypeOfExpression(left.expression), left.name.escapedText); - if (isExactOptionalPropertyMismatch(valueType, target)) { - headMessage = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target; - } - } - // to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported - checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right, headMessage); - } + function isAssignmentToReadonlyEntity(expr: Expression, symbol: Symbol, assignmentKind: AssignmentKind) { + if (assignmentKind === AssignmentKind.None) { + // no assigment means it doesn't matter whether the entity is readonly + return false; + } + if (isReadonlySymbol(symbol)) { + // Allow assignments to readonly properties within constructors of the same class declaration. + if (symbol.flags & SymbolFlags.Property && + isAccessExpression(expr) && + expr.expression.kind === SyntaxKind.ThisKeyword) { + // Look for if this is the constructor for the class that `symbol` is a property of. + const ctor = getContainingFunction(expr); + if (!(ctor && (ctor.kind === SyntaxKind.Constructor || isJSConstructor(ctor)))) { + return true; } - } - - function isAssignmentDeclaration(kind: AssignmentDeclarationKind) { - switch (kind) { - case AssignmentDeclarationKind.ModuleExports: - return true; - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.Property: - case AssignmentDeclarationKind.Prototype: - case AssignmentDeclarationKind.PrototypeProperty: - case AssignmentDeclarationKind.ThisProperty: - const symbol = getSymbolOfNode(left); - const init = getAssignedExpandoInitializer(right); - return !!init && isObjectLiteralExpression(init) && - !!symbol?.exports?.size; - default: - return false; + if (symbol.valueDeclaration) { + const isAssignmentDeclaration = isBinaryExpression(symbol.valueDeclaration); + const isLocalPropertyDeclaration = ctor.parent === symbol.valueDeclaration.parent; + const isLocalParameterProperty = ctor === symbol.valueDeclaration.parent; + const isLocalThisPropertyAssignment = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor.parent; + const isLocalThisPropertyAssignmentConstructorFunction = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor; + const isWriteableSymbol = + isLocalPropertyDeclaration + || isLocalParameterProperty + || isLocalThisPropertyAssignment + || isLocalThisPropertyAssignmentConstructorFunction; + return !isWriteableSymbol; } } - - /** - * Returns true if an error is reported - */ - function reportOperatorErrorUnless(typesAreCompatible: (left: Type, right: Type) => boolean): boolean { - if (!typesAreCompatible(leftType, rightType)) { - reportOperatorError(typesAreCompatible); - return true; + return true; + } + if (isAccessExpression(expr)) { + // references through namespace import should be readonly + const node = skipParentheses(expr.expression); + if (node.kind === SyntaxKind.Identifier) { + const symbol = getNodeLinks(node).resolvedSymbol!; + if (symbol.flags & SymbolFlags.Alias) { + const declaration = getDeclarationOfAliasSymbol(symbol); + return !!declaration && declaration.kind === SyntaxKind.NamespaceImport; } - return false; } + } + return false; + } - function reportOperatorError(isRelated?: (left: Type, right: Type) => boolean) { - let wouldWorkWithAwait = false; - const errNode = errorNode || operatorToken; - if (isRelated) { - const awaitedLeftType = getAwaitedTypeNoAlias(leftType); - const awaitedRightType = getAwaitedTypeNoAlias(rightType); - wouldWorkWithAwait = !(awaitedLeftType === leftType && awaitedRightType === rightType) - && !!(awaitedLeftType && awaitedRightType) - && isRelated(awaitedLeftType, awaitedRightType); - } - - let effectiveLeft = leftType; - let effectiveRight = rightType; - if (!wouldWorkWithAwait && isRelated) { - [effectiveLeft, effectiveRight] = getBaseTypesIfUnrelated(leftType, rightType, isRelated); - } - const [leftStr, rightStr] = getTypeNamesForErrorDisplay(effectiveLeft, effectiveRight); - if (!tryGiveBetterPrimaryError(errNode, wouldWorkWithAwait, leftStr, rightStr)) { - errorAndMaybeSuggestAwait( - errNode, - wouldWorkWithAwait, - Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2, - tokenToString(operatorToken.kind), - leftStr, - rightStr, - ); - } + function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage, invalidOptionalChainMessage: DiagnosticMessage): boolean { + // References are combinations of identifiers, parentheses, and property accesses. + const node = skipOuterExpressions(expr, OuterExpressionKinds.Assertions | OuterExpressionKinds.Parentheses); + if (node.kind !== SyntaxKind.Identifier && !isAccessExpression(node)) { + error(expr, invalidReferenceMessage); + return false; + } + if (node.flags & NodeFlags.OptionalChain) { + error(expr, invalidOptionalChainMessage); + return false; + } + return true; + } + + function checkDeleteExpression(node: DeleteExpression): Type { + checkExpression(node.expression); + const expr = skipParentheses(node.expression); + if (!isAccessExpression(expr)) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_a_property_reference); + return booleanType; + } + if (isPropertyAccessExpression(expr) && isPrivateIdentifier(expr.name)) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_private_identifier); + } + const links = getNodeLinks(expr); + const symbol = getExportSymbolOfValueSymbolIfExported(links.resolvedSymbol); + if (symbol) { + if (isReadonlySymbol(symbol)) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_read_only_property); } + checkDeleteExpressionMustBeOptional(expr, symbol); + } + return booleanType; + } - function tryGiveBetterPrimaryError(errNode: Node, maybeMissingAwait: boolean, leftStr: string, rightStr: string) { - let typeName: string | undefined; - switch (operatorToken.kind) { - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.EqualsEqualsToken: - typeName = "false"; - break; - case SyntaxKind.ExclamationEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - typeName = "true"; + function checkDeleteExpressionMustBeOptional(expr: AccessExpression, symbol: Symbol) { + const type = getTypeOfSymbol(symbol); + if (strictNullChecks && + !(type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Never)) && + !(exactOptionalPropertyTypes ? symbol.flags & SymbolFlags.Optional : getFalsyFlags(type) & TypeFlags.Undefined)) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_optional); + } + } + + function checkTypeOfExpression(node: TypeOfExpression): Type { + checkExpression(node.expression); + return typeofType; + } + + function checkVoidExpression(node: VoidExpression): Type { + checkExpression(node.expression); + return undefinedWideningType; + } + + function checkAwaitExpression(node: AwaitExpression): Type { + // Grammar checking + if (produceDiagnostics) { + const container = getContainingFunctionOrClassStaticBlock(node); + if (container && isClassStaticBlockDeclaration(container)) { + error(node, Diagnostics.Await_expression_cannot_be_used_inside_a_class_static_block); + } + else if (!(node.flags & NodeFlags.AwaitContext)) { + if (isInTopLevelContext(node)) { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + let span: TextSpan | undefined; + if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { + if (!span) span = getSpanOfTokenAtPosition(sourceFile, node.pos); + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, + Diagnostics.await_expressions_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module); + diagnostics.add(diagnostic); + } + if ((moduleKind !== ModuleKind.ES2022 && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System && !(moduleKind === ModuleKind.NodeNext && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.ESNext)) || languageVersion < ScriptTarget.ES2017) { + span = getSpanOfTokenAtPosition(sourceFile, node.pos); + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, + Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher); + diagnostics.add(diagnostic); + } + } } - - if (typeName) { - return errorAndMaybeSuggestAwait( - errNode, - maybeMissingAwait, - Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap, - typeName, leftStr, rightStr); + else { + // use of 'await' in non-async function + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); + if (container && container.kind !== SyntaxKind.Constructor && (getFunctionFlags(container) & FunctionFlags.Async) === 0) { + const relatedInfo = createDiagnosticForNode(container, Diagnostics.Did_you_mean_to_mark_this_function_as_async); + addRelatedInfo(diagnostic, relatedInfo); + } + diagnostics.add(diagnostic); + } } - - return undefined; } - } - function getBaseTypesIfUnrelated(leftType: Type, rightType: Type, isRelated: (left: Type, right: Type) => boolean): [Type, Type] { - let effectiveLeft = leftType; - let effectiveRight = rightType; - const leftBase = getBaseTypeOfLiteralType(leftType); - const rightBase = getBaseTypeOfLiteralType(rightType); - if (!isRelated(leftBase, rightBase)) { - effectiveLeft = leftBase; - effectiveRight = rightBase; + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); } - return [ effectiveLeft, effectiveRight ]; } - function checkYieldExpression(node: YieldExpression): Type { - // Grammar checking - if (produceDiagnostics) { - if (!(node.flags & NodeFlags.YieldContext)) { - grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); - } + const operandType = checkExpression(node.expression); + const awaitedType = checkAwaitedType(operandType, /*withAlias*/ true, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + if (awaitedType === operandType && !isErrorType(awaitedType) && !(operandType.flags & TypeFlags.AnyOrUnknown)) { + addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.await_has_no_effect_on_the_type_of_this_expression)); + } + return awaitedType; + } - if (isInParameterInitializerBeforeContainingFunction(node)) { - error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); + function checkPrefixUnaryExpression(node: PrefixUnaryExpression): Type { + const operandType = checkExpression(node.operand); + if (operandType === silentNeverType) { + return silentNeverType; + } + switch (node.operand.kind) { + case SyntaxKind.NumericLiteral: + switch (node.operator) { + case SyntaxKind.MinusToken: + return getFreshTypeOfLiteralType(getNumberLiteralType(-(node.operand as NumericLiteral).text)); + case SyntaxKind.PlusToken: + return getFreshTypeOfLiteralType(getNumberLiteralType(+(node.operand as NumericLiteral).text)); } - } - - const func = getContainingFunction(node); - if (!func) return anyType; - const functionFlags = getFunctionFlags(func); - - if (!(functionFlags & FunctionFlags.Generator)) { - // If the user's code is syntactically correct, the func should always have a star. After all, we are in a yield context. - return anyType; - } - - const isAsync = (functionFlags & FunctionFlags.Async) !== 0; - if (node.asteriskToken) { - // Async generator functions prior to ESNext require the __await, __asyncDelegator, - // and __asyncValues helpers - if (isAsync && languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes); + break; + case SyntaxKind.BigIntLiteral: + if (node.operator === SyntaxKind.MinusToken) { + return getFreshTypeOfLiteralType(getBigIntLiteralType({ + negative: true, + base10Value: parsePseudoBigInt((node.operand as BigIntLiteral).text) + })); } - - // Generator functions prior to ES2015 require the __values helper - if (!isAsync && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Values); + } + switch (node.operator) { + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.TildeToken: + checkNonNullType(operandType, node.operand); + if (maybeTypeOfKind(operandType, TypeFlags.ESSymbolLike)) { + error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator)); } - } - - // There is no point in doing an assignability check if the function - // has no explicit return type because the return type is directly computed - // from the yield expressions. - const returnType = getReturnTypeFromAnnotation(func); - const iterationTypes = returnType && getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsync); - const signatureYieldType = iterationTypes && iterationTypes.yieldType || anyType; - const signatureNextType = iterationTypes && iterationTypes.nextType || anyType; - const resolvedSignatureNextType = isAsync ? getAwaitedType(signatureNextType) || anyType : signatureNextType; - const yieldExpressionType = node.expression ? checkExpression(node.expression) : undefinedWideningType; - const yieldedType = getYieldedTypeOfYieldExpression(node, yieldExpressionType, resolvedSignatureNextType, isAsync); - if (returnType && yieldedType) { - checkTypeAssignableToAndOptionallyElaborate(yieldedType, signatureYieldType, node.expression || node, node.expression); - } - - if (node.asteriskToken) { - const use = isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar; - return getIterationTypeOfIterable(use, IterationTypeKind.Return, yieldExpressionType, node.expression) - || anyType; - } - else if (returnType) { - return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, isAsync) - || anyType; - } - let type = getContextualIterationType(IterationTypeKind.Next, func); - if (!type) { - type = anyType; - if (produceDiagnostics && noImplicitAny && !expressionResultIsUnused(node)) { - const contextualType = getContextualType(node); - if (!contextualType || isTypeAny(contextualType)) { - error(node, Diagnostics.yield_expression_implicitly_results_in_an_any_type_because_its_containing_generator_lacks_a_return_type_annotation); + if (node.operator === SyntaxKind.PlusToken) { + if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { + error(node.operand, Diagnostics.Operator_0_cannot_be_applied_to_type_1, tokenToString(node.operator), typeToString(getBaseTypeOfLiteralType(operandType))); } + return numberType; } - } - return type; + return getUnaryResultType(operandType); + case SyntaxKind.ExclamationToken: + checkTruthinessExpression(node.operand); + const facts = getTypeFacts(operandType) & (TypeFacts.Truthy | TypeFacts.Falsy); + return facts === TypeFacts.Truthy ? falseType : + facts === TypeFacts.Falsy ? trueType : + booleanType; + case SyntaxKind.PlusPlusToken: + case SyntaxKind.MinusMinusToken: + const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand), + Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); + if (ok) { + // run check only if former checks succeeded to avoid reporting cascading errors + checkReferenceExpression( + node.operand, + Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, + Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); + } + return getUnaryResultType(operandType); } + return errorType; + } - function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { - const type = checkTruthinessExpression(node.condition); - checkTestingKnownTruthyCallableOrAwaitableType(node.condition, type, node.whenTrue); - const type1 = checkExpression(node.whenTrue, checkMode); - const type2 = checkExpression(node.whenFalse, checkMode); - return getUnionType([type1, type2], UnionReduction.Subtype); + function checkPostfixUnaryExpression(node: PostfixUnaryExpression): Type { + const operandType = checkExpression(node.operand); + if (operandType === silentNeverType) { + return silentNeverType; + } + const ok = checkArithmeticOperandType( + node.operand, + checkNonNullType(operandType, node.operand), + Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); + if (ok) { + // run check only if former checks succeeded to avoid reporting cascading errors + checkReferenceExpression( + node.operand, + Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, + Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); } + return getUnaryResultType(operandType); + } - function isTemplateLiteralContext(node: Node): boolean { - const parent = node.parent; - return isParenthesizedExpression(parent) && isTemplateLiteralContext(parent) || - isElementAccessExpression(parent) && parent.argumentExpression === node; + function getUnaryResultType(operandType: Type): Type { + if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { + return isTypeAssignableToKind(operandType, TypeFlags.AnyOrUnknown) || maybeTypeOfKind(operandType, TypeFlags.NumberLike) + ? numberOrBigIntType + : bigintType; } + // If it's not a bigint type, implicit coercion will result in a number + return numberType; + } - function checkTemplateExpression(node: TemplateExpression): Type { - const texts = [node.head.text]; - const types = []; - for (const span of node.templateSpans) { - const type = checkExpression(span.expression); - if (maybeTypeOfKind(type, TypeFlags.ESSymbolLike)) { - error(span.expression, Diagnostics.Implicit_conversion_of_a_symbol_to_a_string_will_fail_at_runtime_Consider_wrapping_this_expression_in_String); + // Return true if type might be of the given kind. A union or intersection type might be of a given + // kind if at least one constituent type is of the given kind. + function maybeTypeOfKind(type: Type, kind: TypeFlags): boolean { + if (type.flags & kind) { + return true; + } + if (type.flags & TypeFlags.UnionOrIntersection) { + const types = (type as UnionOrIntersectionType).types; + for (const t of types) { + if (maybeTypeOfKind(t, kind)) { + return true; } - texts.push(span.literal.text); - types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType); } - return isConstContext(node) || isTemplateLiteralContext(node) || someType(getContextualType(node) || unknownType, isTemplateLiteralContextualType) ? getTemplateLiteralType(texts, types) : stringType; } + return false; + } - function isTemplateLiteralContextualType(type: Type): boolean { - return !!(type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral) || - type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.StringLike)); + function isTypeAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean { + if (source.flags & kind) { + return true; } - - function getContextNode(node: Expression): Node { - if (node.kind === SyntaxKind.JsxAttributes && !isJsxSelfClosingElement(node.parent)) { - return node.parent.parent; // Needs to be the root JsxElement, so it encompasses the attributes _and_ the children (which are essentially part of the attributes) - } - return node; + if (strict && source.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null)) { + return false; } + return !!(kind & TypeFlags.NumberLike) && isTypeAssignableTo(source, numberType) || + !!(kind & TypeFlags.BigIntLike) && isTypeAssignableTo(source, bigintType) || + !!(kind & TypeFlags.StringLike) && isTypeAssignableTo(source, stringType) || + !!(kind & TypeFlags.BooleanLike) && isTypeAssignableTo(source, booleanType) || + !!(kind & TypeFlags.Void) && isTypeAssignableTo(source, voidType) || + !!(kind & TypeFlags.Never) && isTypeAssignableTo(source, neverType) || + !!(kind & TypeFlags.Null) && isTypeAssignableTo(source, nullType) || + !!(kind & TypeFlags.Undefined) && isTypeAssignableTo(source, undefinedType) || + !!(kind & TypeFlags.ESSymbol) && isTypeAssignableTo(source, esSymbolType) || + !!(kind & TypeFlags.NonPrimitive) && isTypeAssignableTo(source, nonPrimitiveType); + } - function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { - const context = getContextNode(node); - const saveContextualType = context.contextualType; - const saveInferenceContext = context.inferenceContext; - try { - context.contextualType = contextualType; - context.inferenceContext = inferenceContext; - const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); - // We strip literal freshness when an appropriate contextual type is present such that contextually typed - // literals always preserve their literal types (otherwise they might widen during type inference). An alternative - // here would be to not mark contextually typed literals as fresh in the first place. - const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node)) ? - getRegularTypeOfLiteralType(type) : type; - return result; - } - finally { - // In the event our operation is canceled or some other exception occurs, reset the contextual type - // so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer - // may hold onto the checker that created it. - context.contextualType = saveContextualType; - context.inferenceContext = saveInferenceContext; - } - } + function allTypesAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean { + return source.flags & TypeFlags.Union ? + every((source as UnionType).types, subType => allTypesAssignableToKind(subType, kind, strict)) : + isTypeAssignableToKind(source, kind, strict); + } - function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - if (checkMode && checkMode !== CheckMode.Normal) { - return checkExpression(node, checkMode); - } - // When computing a type that we're going to cache, we need to ignore any ongoing control flow - // analysis because variables may have transient types in indeterminable states. Moving flowLoopStart - // to the top of the stack ensures all transient types are computed from a known point. - const saveFlowLoopStart = flowLoopStart; - const saveFlowTypeCache = flowTypeCache; - flowLoopStart = flowLoopCount; - flowTypeCache = undefined; - links.resolvedType = checkExpression(node, checkMode); - flowTypeCache = saveFlowTypeCache; - flowLoopStart = saveFlowLoopStart; - } - return links.resolvedType; - } + function isConstEnumObjectType(type: Type): boolean { + return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isConstEnumSymbol(type.symbol); + } - function isTypeAssertion(node: Expression) { - node = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); - return node.kind === SyntaxKind.TypeAssertionExpression || - node.kind === SyntaxKind.AsExpression || - isJSDocTypeAssertion(node); - } + function isConstEnumSymbol(symbol: Symbol): boolean { + return (symbol.flags & SymbolFlags.ConstEnum) !== 0; + } - function checkDeclarationInitializer(declaration: HasExpressionInitializer, contextualType?: Type | undefined) { - const initializer = getEffectiveInitializer(declaration)!; - const type = getQuickTypeOfExpression(initializer) || - (contextualType ? checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, CheckMode.Normal) : checkExpressionCached(initializer)); - return isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern && - isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ? - padTupleType(type, declaration.name) : type; + function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; } - - function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) { - const patternElements = pattern.elements; - const elementTypes = getTypeArguments(type).slice(); - const elementFlags = type.target.elementFlags.slice(); - for (let i = getTypeReferenceArity(type); i < patternElements.length; i++) { - const e = patternElements[i]; - if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && e.dotDotDotToken)) { - elementTypes.push(!isOmittedExpression(e) && hasDefaultValue(e) ? getTypeFromBindingElement(e, /*includePatternInType*/ false, /*reportErrors*/ false) : anyType); - elementFlags.push(ElementFlags.Optional); - if (!isOmittedExpression(e) && !hasDefaultValue(e)) { - reportImplicitAny(e, anyType); - } - } - } - return createTupleType(elementTypes, elementFlags, type.target.readonly); + // TypeScript 1.0 spec (April 2014): 4.15.4 + // The instanceof operator requires the left operand to be of type Any, an object type, or a type parameter type, + // and the right operand to be of type Any, a subtype of the 'Function' interface type, or have a call or construct signature. + // The result is always of the Boolean primitive type. + // NOTE: do not raise error if leftType is unknown as related error was already reported + if (!isTypeAny(leftType) && + allTypesAssignableToKind(leftType, TypeFlags.Primitive)) { + error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); + } + // NOTE: do not raise error if right is unknown as related error was already reported + if (!(isTypeAny(rightType) || typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) { + error(right, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_of_type_any_or_of_a_type_assignable_to_the_Function_interface_type); } + return booleanType; + } - function widenTypeInferredFromInitializer(declaration: HasExpressionInitializer, type: Type) { - const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const || isDeclarationReadonly(declaration) ? type : getWidenedLiteralType(type); - if (isInJSFile(declaration)) { - if (isEmptyLiteralType(widened)) { - reportImplicitAny(declaration, anyType); - return anyType; - } - else if (isEmptyArrayLiteralType(widened)) { - reportImplicitAny(declaration, anyArrayType); - return anyArrayType; - } + function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; + } + if (isPrivateIdentifier(left)) { + if (languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(left, ExternalEmitHelpers.ClassPrivateFieldIn); + } + // Unlike in 'checkPrivateIdentifierExpression' we now have access to the RHS type + // which provides us with the opportunity to emit more detailed errors + if (!getNodeLinks(left).resolvedSymbol && getContainingClass(left)) { + const isUncheckedJS = isUncheckedJSSuggestion(left, rightType.symbol, /*excludeClasses*/ true); + reportNonexistentProperty(left, rightType, isUncheckedJS); } - return widened; } - - function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean { - if (contextualType) { - if (contextualType.flags & TypeFlags.UnionOrIntersection) { - const types = (contextualType as UnionType).types; - return some(types, t => isLiteralOfContextualType(candidateType, t)); - } - if (contextualType.flags & TypeFlags.InstantiableNonPrimitive) { - // If the contextual type is a type variable constrained to a primitive type, consider - // this a literal context for literals of that primitive type. For example, given a - // type parameter 'T extends string', infer string literal types for T. - const constraint = getBaseConstraintOfType(contextualType) || unknownType; - return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || - maybeTypeOfKind(constraint, TypeFlags.Number) && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || - maybeTypeOfKind(constraint, TypeFlags.BigInt) && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || - maybeTypeOfKind(constraint, TypeFlags.ESSymbol) && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) || - isLiteralOfContextualType(candidateType, constraint); - } - // If the contextual type is a literal of a particular primitive type, we consider this a - // literal context for all literals of that primitive type. - return !!(contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || - contextualType.flags & TypeFlags.NumberLiteral && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || - contextualType.flags & TypeFlags.BigIntLiteral && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || - contextualType.flags & TypeFlags.BooleanLiteral && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) || - contextualType.flags & TypeFlags.UniqueESSymbol && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol)); + else { + leftType = checkNonNullType(leftType, left); + // TypeScript 1.0 spec (April 2014): 4.15.5 + // Require the left operand to be of type Any, the String primitive type, or the Number primitive type. + if (!(allTypesAssignableToKind(leftType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) || + isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.TypeParameter))) { + error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_a_private_identifier_or_of_type_any_string_number_or_symbol); } - return false; } - - function isConstContext(node: Expression): boolean { - const parent = node.parent; - return isAssertionExpression(parent) && isConstTypeReference(parent.type) || - isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) || - (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || - (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); + rightType = checkNonNullType(rightType, right); + // TypeScript 1.0 spec (April 2014): 4.15.5 + // The in operator requires the right operand to be + // + // 1. assignable to the non-primitive type, + // 2. an unconstrained type parameter, + // 3. a union or intersection including one or more type parameters, whose constituents are all assignable to the + // the non-primitive type, or are unconstrainted type parameters, or have constraints assignable to the + // non-primitive type, or + // 4. a type parameter whose constraint is + // i. an object type, + // ii. the non-primitive type, or + // iii. a union or intersection with at least one constituent assignable to an object or non-primitive type. + // + // The divergent behavior for type parameters and unions containing type parameters is a workaround for type + // parameters not being narrowable. If the right operand is a concrete type, we can error if there is any chance + // it is a primitive. But if the operand is a type parameter, it cannot be narrowed, so we don't issue an error + // unless *all* instantiations would result in an error. + // + // The result is always of the Boolean primitive type. + const rightTypeConstraint = getConstraintOfType(rightType); + if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || + rightTypeConstraint && ( + isTypeAssignableToKind(rightType, TypeFlags.UnionOrIntersection) && !allTypesAssignableToKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || + !maybeTypeOfKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object) + ) + ) { + error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_not_be_a_primitive); } + return booleanType; + } - function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, contextualType?: Type, forceTuple?: boolean): Type { - const type = checkExpression(node, checkMode, forceTuple); - return isConstContext(node) ? getRegularTypeOfLiteralType(type) : - isTypeAssertion(node) ? type : - getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node) : contextualType, node)); + function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type, rightIsThis?: boolean): Type { + const properties = node.properties; + if (strictNullChecks && properties.length === 0) { + return checkNonNullType(sourceType, node); } - - function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type { - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); - } - - return checkExpressionForMutableLocation(node.initializer, checkMode); + for (let i = 0; i < properties.length; i++) { + checkObjectLiteralDestructuringPropertyAssignment(node, sourceType, i, properties, rightIsThis); } + return sourceType; + } - function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { - // Grammar checking - checkGrammarMethod(node); - - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); + /** Note: If property cannot be a SpreadAssignment, then allProperties does not need to be provided */ + function checkObjectLiteralDestructuringPropertyAssignment(node: ObjectLiteralExpression, objectLiteralType: Type, propertyIndex: number, allProperties?: NodeArray, rightIsThis = false) { + const properties = node.properties; + const property = properties[propertyIndex]; + if (property.kind === SyntaxKind.PropertyAssignment || property.kind === SyntaxKind.ShorthandPropertyAssignment) { + const name = property.name; + const exprType = getLiteralTypeFromPropertyName(name); + if (isTypeUsableAsPropertyName(exprType)) { + const text = getPropertyNameFromType(exprType); + const prop = getPropertyOfType(objectLiteralType, text); + if (prop) { + markPropertyAsReferenced(prop, property, rightIsThis); + checkPropertyAccessibility(property, /*isSuper*/ false, /*writing*/ true, objectLiteralType, prop); + } } - - const uninstantiatedType = checkFunctionExpressionOrObjectLiteralMethod(node, checkMode); - return instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); + const elementType = getIndexedAccessType(objectLiteralType, exprType, AccessFlags.ExpressionPosition, name); + const type = getFlowTypeOfDestructuring(property, elementType); + return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); } - - function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: Type, checkMode?: CheckMode) { - if (checkMode && checkMode & (CheckMode.Inferential | CheckMode.SkipGenericFunctions)) { - const callSignature = getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ true); - const constructSignature = getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ true); - const signature = callSignature || constructSignature; - if (signature && signature.typeParameters) { - const contextualType = getApparentTypeOfContextualType(node as Expression, ContextFlags.NoConstraints); - if (contextualType) { - const contextualSignature = getSingleSignature(getNonNullableType(contextualType), callSignature ? SignatureKind.Call : SignatureKind.Construct, /*allowMembers*/ false); - if (contextualSignature && !contextualSignature.typeParameters) { - if (checkMode & CheckMode.SkipGenericFunctions) { - skippedGenericFunction(node, checkMode); - return anyFunctionType; - } - const context = getInferenceContext(node)!; - // We have an expression that is an argument of a generic function for which we are performing - // type argument inference. The expression is of a function type with a single generic call - // signature and a contextual function type with a single non-generic call signature. Now check - // if the outer function returns a function type with a single non-generic call signature and - // if some of the outer function type parameters have no inferences so far. If so, we can - // potentially add inferred type parameters to the outer function return type. - const returnType = context.signature && getReturnTypeOfSignature(context.signature); - const returnSignature = returnType && getSingleCallOrConstructSignature(returnType); - if (returnSignature && !returnSignature.typeParameters && !every(context.inferences, hasInferenceCandidates)) { - // Instantiate the signature with its own type parameters as type arguments, possibly - // renaming the type parameters to ensure they have unique names. - const uniqueTypeParameters = getUniqueTypeParameters(context, signature.typeParameters); - const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, uniqueTypeParameters); - // Infer from the parameters of the instantiated signature to the parameters of the - // contextual signature starting with an empty set of inference candidates. - const inferences = map(context.inferences, info => createInferenceInfo(info.typeParameter)); - applyToParameterTypes(instantiatedSignature, contextualSignature, (source, target) => { - inferTypes(inferences, source, target, /*priority*/ 0, /*contravariant*/ true); - }); - if (some(inferences, hasInferenceCandidates)) { - // We have inference candidates, indicating that one or more type parameters are referenced - // in the parameter types of the contextual signature. Now also infer from the return type. - applyToReturnTypes(instantiatedSignature, contextualSignature, (source, target) => { - inferTypes(inferences, source, target); - }); - // If the type parameters for which we produced candidates do not have any inferences yet, - // we adopt the new inference candidates and add the type parameters of the expression type - // to the set of inferred type parameters for the outer function return type. - if (!hasOverlappingInferences(context.inferences, inferences)) { - mergeInferences(context.inferences, inferences); - context.inferredTypeParameters = concatenate(context.inferredTypeParameters, uniqueTypeParameters); - return getOrCreateTypeFromSignature(instantiatedSignature); - } - } - } - return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context)); + else if (property.kind === SyntaxKind.SpreadAssignment) { + if (propertyIndex < properties.length - 1) { + error(property, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); + } + else { + if (languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest); + } + const nonRestNames: PropertyName[] = []; + if (allProperties) { + for (const otherProperty of allProperties) { + if (!isSpreadAssignment(otherProperty)) { + nonRestNames.push(otherProperty.name); } } } + const type = getRestType(objectLiteralType, nonRestNames, objectLiteralType.symbol); + checkGrammarForDisallowedTrailingComma(allProperties, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + return checkDestructuringAssignment(property.expression, type); } - return type; } - - function skippedGenericFunction(node: Node, checkMode: CheckMode) { - if (checkMode & CheckMode.Inferential) { - // We have skipped a generic function during inferential typing. Obtain the inference context and - // indicate this has occurred such that we know a second pass of inference is be needed. - const context = getInferenceContext(node)!; - context.flags |= InferenceFlags.SkippedGenericFunction; - } + else { + error(property, Diagnostics.Property_assignment_expected); } + } - function hasInferenceCandidates(info: InferenceInfo) { - return !!(info.candidates || info.contraCandidates); + function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type { + const elements = node.elements; + if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); } - - function hasOverlappingInferences(a: InferenceInfo[], b: InferenceInfo[]) { - for (let i = 0; i < a.length; i++) { - if (hasInferenceCandidates(a[i]) && hasInferenceCandidates(b[i])) { - return true; - } + // This elementType will be used if the specific property corresponding to this index is not + // present (aka the tuple element property). This call also checks that the parentType is in + // fact an iterable or array (depending on target language). + const possiblyOutOfBoundsType = checkIteratedTypeOrElementType(IterationUse.Destructuring | IterationUse.PossiblyOutOfBounds, sourceType, undefinedType, node) || errorType; + let inBoundsType: Type | undefined = compilerOptions.noUncheckedIndexedAccess ? undefined: possiblyOutOfBoundsType; + for (let i = 0; i < elements.length; i++) { + let type = possiblyOutOfBoundsType; + if (node.elements[i].kind === SyntaxKind.SpreadElement) { + type = inBoundsType = inBoundsType ?? (checkIteratedTypeOrElementType(IterationUse.Destructuring, sourceType, undefinedType, node) || errorType); } - return false; + checkArrayLiteralDestructuringElementAssignment(node, sourceType, i, type, checkMode); } + return sourceType; + } - function mergeInferences(target: InferenceInfo[], source: InferenceInfo[]) { - for (let i = 0; i < target.length; i++) { - if (!hasInferenceCandidates(target[i]) && hasInferenceCandidates(source[i])) { - target[i] = source[i]; - } + function checkArrayLiteralDestructuringElementAssignment(node: ArrayLiteralExpression, sourceType: Type, + elementIndex: number, elementType: Type, checkMode?: CheckMode) { + const elements = node.elements; + const element = elements[elementIndex]; + if (element.kind !== SyntaxKind.OmittedExpression) { + if (element.kind !== SyntaxKind.SpreadElement) { + const indexType = getNumberLiteralType(elementIndex); + if (isArrayLikeType(sourceType)) { + // We create a synthetic expression so that getIndexedAccessType doesn't get confused + // when the element is a SyntaxKind.ElementAccessExpression. + const accessFlags = AccessFlags.ExpressionPosition | (hasDefaultValue(element) ? AccessFlags.NoTupleBoundsCheck : 0); + const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, accessFlags, createSyntheticExpression(element, indexType)) || errorType; + const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType; + const type = getFlowTypeOfDestructuring(element, assignedType); + return checkDestructuringAssignment(element, type, checkMode); + } + return checkDestructuringAssignment(element, elementType, checkMode); + } + if (elementIndex < elements.length - 1) { + error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); } - } - - function getUniqueTypeParameters(context: InferenceContext, typeParameters: readonly TypeParameter[]): readonly TypeParameter[] { - const result: TypeParameter[] = []; - let oldTypeParameters: TypeParameter[] | undefined; - let newTypeParameters: TypeParameter[] | undefined; - for (const tp of typeParameters) { - const name = tp.symbol.escapedName; - if (hasTypeParameterByName(context.inferredTypeParameters, name) || hasTypeParameterByName(result, name)) { - const newName = getUniqueTypeParameterName(concatenate(context.inferredTypeParameters, result), name); - const symbol = createSymbol(SymbolFlags.TypeParameter, newName); - const newTypeParameter = createTypeParameter(symbol); - newTypeParameter.target = tp; - oldTypeParameters = append(oldTypeParameters, tp); - newTypeParameters = append(newTypeParameters, newTypeParameter); - result.push(newTypeParameter); + else { + const restExpression = (element as SpreadElement).expression; + if (restExpression.kind === SyntaxKind.BinaryExpression && (restExpression as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + error((restExpression as BinaryExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer); } else { - result.push(tp); - } - } - if (newTypeParameters) { - const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters); - for (const tp of newTypeParameters) { - tp.mapper = mapper; + checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + const type = everyType(sourceType, isTupleType) ? + mapType(sourceType, t => sliceTupleType(t as TupleTypeReference, elementIndex)) : + createArrayType(elementType); + return checkDestructuringAssignment(restExpression, type, checkMode); } } - return result; - } - - function hasTypeParameterByName(typeParameters: readonly TypeParameter[] | undefined, name: __String) { - return some(typeParameters, tp => tp.symbol.escapedName === name); } + return undefined; + } - function getUniqueTypeParameterName(typeParameters: readonly TypeParameter[], baseName: __String) { - let len = (baseName as string).length; - while (len > 1 && (baseName as string).charCodeAt(len - 1) >= CharacterCodes._0 && (baseName as string).charCodeAt(len - 1) <= CharacterCodes._9) len--; - const s = (baseName as string).slice(0, len); - for (let index = 1; true; index++) { - const augmentedName = (s + index as __String); - if (!hasTypeParameterByName(typeParameters, augmentedName)) { - return augmentedName; + function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: Type, checkMode?: CheckMode, rightIsThis?: boolean): Type { + let target: Expression; + if (exprOrAssignment.kind === SyntaxKind.ShorthandPropertyAssignment) { + const prop = exprOrAssignment as ShorthandPropertyAssignment; + if (prop.objectAssignmentInitializer) { + // In strict null checking mode, if a default value of a non-undefined type is specified, remove + // undefined from the final type. + if (strictNullChecks && + !(getFalsyFlags(checkExpression(prop.objectAssignmentInitializer)) & TypeFlags.Undefined)) { + sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); } + checkBinaryLikeExpression(prop.name, prop.equalsToken!, prop.objectAssignmentInitializer, checkMode); } + target = (exprOrAssignment as ShorthandPropertyAssignment).name; } - - function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) { - const signature = getSingleCallSignature(funcType); - if (signature && !signature.typeParameters) { - return getReturnTypeOfSignature(signature); - } + else { + target = exprOrAssignment; } - function getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr: CallChain) { - const funcType = checkExpression(expr.expression); - const nonOptionalType = getOptionalExpressionType(funcType, expr.expression); - const returnType = getReturnTypeOfSingleNonGenericCallSignature(funcType); - return returnType && propagateOptionalTypeMarker(returnType, expr, nonOptionalType !== funcType); + if (target.kind === SyntaxKind.BinaryExpression && (target as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + checkBinaryExpression(target as BinaryExpression, checkMode); + target = (target as BinaryExpression).left; } - - /** - * Returns the type of an expression. Unlike checkExpression, this function is simply concerned - * with computing the type and may not fully check all contained sub-expressions for errors. - */ - function getTypeOfExpression(node: Expression) { - // Don't bother caching types that require no flow analysis and are quick to compute. - const quickType = getQuickTypeOfExpression(node); - if (quickType) { - return quickType; - } - // If a type has been cached for the node, return it. - if (node.flags & NodeFlags.TypeCached && flowTypeCache) { - const cachedType = flowTypeCache[getNodeId(node)]; - if (cachedType) { - return cachedType; - } - } - const startInvocationCount = flowInvocationCount; - const type = checkExpression(node); - // If control flow analysis was required to determine the type, it is worth caching. - if (flowInvocationCount !== startInvocationCount) { - const cache = flowTypeCache || (flowTypeCache = []); - cache[getNodeId(node)] = type; - setNodeFlags(node, node.flags | NodeFlags.TypeCached); - } - return type; + if (target.kind === SyntaxKind.ObjectLiteralExpression) { + return checkObjectLiteralAssignment(target as ObjectLiteralExpression, sourceType, rightIsThis); } + if (target.kind === SyntaxKind.ArrayLiteralExpression) { + return checkArrayLiteralAssignment(target as ArrayLiteralExpression, sourceType, checkMode); + } + return checkReferenceAssignment(target, sourceType, checkMode); + } + + function checkReferenceAssignment(target: Expression, sourceType: Type, checkMode?: CheckMode): Type { + const targetType = checkExpression(target, checkMode); + const error = target.parent.kind === SyntaxKind.SpreadAssignment ? + Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access : + Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access; + const optionalError = target.parent.kind === SyntaxKind.SpreadAssignment ? + Diagnostics.The_target_of_an_object_rest_assignment_may_not_be_an_optional_property_access : + Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access; + if (checkReferenceExpression(target, error, optionalError)) { + checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, target, target); + } + if (isPrivateIdentifierPropertyAccessExpression(target)) { + checkExternalEmitHelpers(target.parent, ExternalEmitHelpers.ClassPrivateFieldSet); + } + return sourceType; + } + + /** + * This is a *shallow* check: An expression is side-effect-free if the + * evaluation of the expression *itself* cannot produce side effects. + * For example, x++ / 3 is side-effect free because the / operator + * does not have side effects. + * The intent is to "smell test" an expression for correctness in positions where + * its value is discarded (e.g. the left side of the comma operator). + */ + function isSideEffectFree(node: Node): boolean { + node = skipParentheses(node); + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.StringLiteral: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.TemplateExpression: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ClassExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.TypeOfExpression: + case SyntaxKind.NonNullExpression: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxElement: + return true; - function getQuickTypeOfExpression(node: Expression) { - let expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); - if (isJSDocTypeAssertion(expr)) { - const type = getJSDocTypeAssertionType(expr); - if (!isConstTypeReference(type)) { - return getTypeFromTypeNode(type); + case SyntaxKind.ConditionalExpression: + return isSideEffectFree((node as ConditionalExpression).whenTrue) && + isSideEffectFree((node as ConditionalExpression).whenFalse); + + case SyntaxKind.BinaryExpression: + if (isAssignmentOperator((node as BinaryExpression).operatorToken.kind)) { + return false; } - } - expr = skipParentheses(node); - // Optimize for the common case of a call to a function with a single non-generic call - // signature where we can just fetch the return type without checking the arguments. - if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { - const type = isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : - getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); - if (type) { - return type; + return isSideEffectFree((node as BinaryExpression).left) && + isSideEffectFree((node as BinaryExpression).right); + + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.PostfixUnaryExpression: + // Unary operators ~, !, +, and - have no side effects. + // The rest do. + switch ((node as PrefixUnaryExpression).operator) { + case SyntaxKind.ExclamationToken: + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.TildeToken: + return true; } - } - else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { - return getTypeFromTypeNode((expr as TypeAssertion).type); - } - else if (node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral || - node.kind === SyntaxKind.TrueKeyword || node.kind === SyntaxKind.FalseKeyword) { - return checkExpression(node); - } - return undefined; + return false; + + // Some forms listed here for clarity + case SyntaxKind.VoidExpression: // Explicit opt-out + case SyntaxKind.TypeAssertionExpression: // Not SEF, but can produce useful type warnings + case SyntaxKind.AsExpression: // Not SEF, but can produce useful type warnings + default: + return false; } + } - /** - * Returns the type of an expression. Unlike checkExpression, this function is simply concerned - * with computing the type and may not fully check all contained sub-expressions for errors. - * It is intended for uses where you know there is no contextual type, - * and requesting the contextual type might cause a circularity or other bad behaviour. - * It sets the contextual type of the node to any before calling getTypeOfExpression. - */ - function getContextFreeTypeOfExpression(node: Expression) { - const links = getNodeLinks(node); - if (links.contextFreeType) { - return links.contextFreeType; - } - const saveContextualType = node.contextualType; - node.contextualType = anyType; - try { - const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); - return type; + function isTypeEqualityComparableTo(source: Type, target: Type) { + return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target); + } + + function createCheckBinaryExpression() { + interface WorkArea { + readonly checkMode: CheckMode | undefined; + skip: boolean; + stackIndex: number; + /** + * Holds the types from the left-side of an expression from [0..stackIndex]. + * Holds the type of the result at stackIndex+1. This allows us to reuse existing stack entries + * and avoid storing an extra property on the object (i.e., `lastResult`). + */ + typeStack: (Type | undefined)[]; + } + + const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState); + + return (node: BinaryExpression, checkMode: CheckMode | undefined) => { + const result = trampoline(node, checkMode); + Debug.assertIsDefined(result); + return result; + }; + + function onEnter(node: BinaryExpression, state: WorkArea | undefined, checkMode: CheckMode | undefined) { + if (state) { + state.stackIndex++; + state.skip = false; + setLeftType(state, /*type*/ undefined); + setLastResult(state, /*type*/ undefined); } - finally { - // In the event our operation is canceled or some other exception occurs, reset the contextual type - // so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer - // may hold onto the checker that created it. - node.contextualType = saveContextualType; + else { + state = { + checkMode, + skip: false, + stackIndex: 0, + typeStack: [undefined, undefined], + }; } - } - function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, forceTuple?: boolean): Type { - tracing?.push(tracing.Phase.Check, "checkExpression", { kind: node.kind, pos: node.pos, end: node.end }); - const saveCurrentNode = currentNode; - currentNode = node; - instantiationCount = 0; - const uninstantiatedType = checkExpressionWorker(node, checkMode, forceTuple); - const type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); - if (isConstEnumObjectType(type)) { - checkConstEnumAccess(node, type); + if (isInJSFile(node) && getAssignedExpandoInitializer(node)) { + state.skip = true; + setLastResult(state, checkExpression(node.right, checkMode)); + return state; } - currentNode = saveCurrentNode; - tracing?.pop(); - return type; - } - function checkConstEnumAccess(node: Expression | QualifiedName, type: Type) { - // enum object type for const enums are only permitted in: - // - 'left' in property access - // - 'object' in indexed access - // - target in rhs of import statement - const ok = - (node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent as PropertyAccessExpression).expression === node) || - (node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent as ElementAccessExpression).expression === node) || - ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(node as Identifier) || - (node.parent.kind === SyntaxKind.TypeQuery && (node.parent as TypeQueryNode).exprName === node)) || - (node.parent.kind === SyntaxKind.ExportSpecifier); // We allow reexporting const enums + checkGrammarNullishCoalesceWithLogicalExpression(node); - if (!ok) { - error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query); + const operator = node.operatorToken.kind; + if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) { + state.skip = true; + setLastResult(state, checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword)); + return state; } - if (compilerOptions.isolatedModules) { - Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum)); - const constEnumDeclaration = type.symbol.valueDeclaration as EnumDeclaration; - if (constEnumDeclaration.flags & NodeFlags.Ambient) { - error(node, Diagnostics.Cannot_access_ambient_const_enums_when_the_isolatedModules_flag_is_provided); - } - } + return state; } - function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { - if (hasJSDocNodes(node) && isJSDocTypeAssertion(node)) { - const type = getJSDocTypeAssertionType(node); - return checkAssertionWorker(type, type, node.expression, checkMode); + function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) { + if (!state.skip) { + return maybeCheckExpression(state, left); } - return checkExpression(node.expression, checkMode); } - function checkExpressionWorker(node: Expression | QualifiedName, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { - const kind = node.kind; - if (cancellationToken) { - // Only bother checking on a few construct kinds. We don't want to be excessively - // hitting the cancellation token on every node we check. - switch (kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - cancellationToken.throwIfCancellationRequested(); - } - } - switch (kind) { - case SyntaxKind.Identifier: - return checkIdentifier(node as Identifier, checkMode); - case SyntaxKind.PrivateIdentifier: - return checkPrivateIdentifierExpression(node as PrivateIdentifier); - case SyntaxKind.ThisKeyword: - return checkThisExpression(node); - case SyntaxKind.SuperKeyword: - return checkSuperExpression(node); - case SyntaxKind.NullKeyword: - return nullWideningType; - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.StringLiteral: - return getFreshTypeOfLiteralType(getStringLiteralType((node as StringLiteralLike).text)); - case SyntaxKind.NumericLiteral: - checkGrammarNumericLiteral(node as NumericLiteral); - return getFreshTypeOfLiteralType(getNumberLiteralType(+(node as NumericLiteral).text)); - case SyntaxKind.BigIntLiteral: - checkGrammarBigIntLiteral(node as BigIntLiteral); - return getFreshTypeOfLiteralType(getBigIntLiteralType({ - negative: false, - base10Value: parsePseudoBigInt((node as BigIntLiteral).text) - })); - case SyntaxKind.TrueKeyword: - return trueType; - case SyntaxKind.FalseKeyword: - return falseType; - case SyntaxKind.TemplateExpression: - return checkTemplateExpression(node as TemplateExpression); - case SyntaxKind.RegularExpressionLiteral: - return globalRegExpType; - case SyntaxKind.ArrayLiteralExpression: - return checkArrayLiteral(node as ArrayLiteralExpression, checkMode, forceTuple); - case SyntaxKind.ObjectLiteralExpression: - return checkObjectLiteral(node as ObjectLiteralExpression, checkMode); - case SyntaxKind.PropertyAccessExpression: - return checkPropertyAccessExpression(node as PropertyAccessExpression, checkMode); - case SyntaxKind.QualifiedName: - return checkQualifiedName(node as QualifiedName, checkMode); - case SyntaxKind.ElementAccessExpression: - return checkIndexedAccess(node as ElementAccessExpression, checkMode); - case SyntaxKind.CallExpression: - if ((node as CallExpression).expression.kind === SyntaxKind.ImportKeyword) { - return checkImportCallExpression(node as ImportCall); + function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) { + if (!state.skip) { + const leftType = getLastResult(state); + Debug.assertIsDefined(leftType); + setLeftType(state, leftType); + setLastResult(state, /*type*/ undefined); + const operator = operatorToken.kind; + if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { + if (operator === SyntaxKind.AmpersandAmpersandToken) { + const parent = walkUpParenthesizedExpressions(node.parent); + checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined); } - // falls through - case SyntaxKind.NewExpression: - return checkCallExpression(node as CallExpression, checkMode); - case SyntaxKind.TaggedTemplateExpression: - return checkTaggedTemplateExpression(node as TaggedTemplateExpression); - case SyntaxKind.ParenthesizedExpression: - return checkParenthesizedExpression(node as ParenthesizedExpression, checkMode); - case SyntaxKind.ClassExpression: - return checkClassExpression(node as ClassExpression); - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return checkFunctionExpressionOrObjectLiteralMethod(node as FunctionExpression | ArrowFunction, checkMode); - case SyntaxKind.TypeOfExpression: - return checkTypeOfExpression(node as TypeOfExpression); - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - return checkAssertion(node as AssertionExpression); - case SyntaxKind.NonNullExpression: - return checkNonNullAssertion(node as NonNullExpression); - case SyntaxKind.MetaProperty: - return checkMetaProperty(node as MetaProperty); - case SyntaxKind.DeleteExpression: - return checkDeleteExpression(node as DeleteExpression); - case SyntaxKind.VoidExpression: - return checkVoidExpression(node as VoidExpression); - case SyntaxKind.AwaitExpression: - return checkAwaitExpression(node as AwaitExpression); - case SyntaxKind.PrefixUnaryExpression: - return checkPrefixUnaryExpression(node as PrefixUnaryExpression); - case SyntaxKind.PostfixUnaryExpression: - return checkPostfixUnaryExpression(node as PostfixUnaryExpression); - case SyntaxKind.BinaryExpression: - return checkBinaryExpression(node as BinaryExpression, checkMode); - case SyntaxKind.ConditionalExpression: - return checkConditionalExpression(node as ConditionalExpression, checkMode); - case SyntaxKind.SpreadElement: - return checkSpreadExpression(node as SpreadElement, checkMode); - case SyntaxKind.OmittedExpression: - return undefinedWideningType; - case SyntaxKind.YieldExpression: - return checkYieldExpression(node as YieldExpression); - case SyntaxKind.SyntheticExpression: - return checkSyntheticExpression(node as SyntheticExpression); - case SyntaxKind.JsxExpression: - return checkJsxExpression(node as JsxExpression, checkMode); - case SyntaxKind.JsxElement: - return checkJsxElement(node as JsxElement, checkMode); - case SyntaxKind.JsxSelfClosingElement: - return checkJsxSelfClosingElement(node as JsxSelfClosingElement, checkMode); - case SyntaxKind.JsxFragment: - return checkJsxFragment(node as JsxFragment); - case SyntaxKind.JsxAttributes: - return checkJsxAttributes(node as JsxAttributes, checkMode); - case SyntaxKind.JsxOpeningElement: - Debug.fail("Shouldn't ever directly check a JsxOpeningElement"); + checkTruthinessOfType(leftType, node.left); + } } - return errorType; } - // DECLARATION AND STATEMENT TYPE CHECKING - - function checkTypeParameter(node: TypeParameterDeclaration) { - // Grammar Checking - if (node.expression) { - grammarErrorOnFirstToken(node.expression, Diagnostics.Type_expected); - } - - checkSourceElement(node.constraint); - checkSourceElement(node.default); - const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)); - // Resolve base constraint to reveal circularity errors - getBaseConstraintOfType(typeParameter); - if (!hasNonCircularTypeParameterDefault(typeParameter)) { - error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter)); - } - const constraintType = getConstraintOfTypeParameter(typeParameter); - const defaultType = getDefaultFromTypeParameter(typeParameter); - if (constraintType && defaultType) { - checkTypeAssignableTo(defaultType, getTypeWithThisArgument(instantiateType(constraintType, makeUnaryTypeMapper(typeParameter, defaultType)), defaultType), node.default, Diagnostics.Type_0_does_not_satisfy_the_constraint_1); - } - if (produceDiagnostics) { - checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0); + function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) { + if (!state.skip) { + return maybeCheckExpression(state, right); } } - function checkParameter(node: ParameterDeclaration) { - // Grammar checking - // It is a SyntaxError if the Identifier "eval" or the Identifier "arguments" occurs as the - // Identifier in a PropertySetParameterList of a PropertyAssignment that is contained in strict code - // or if its FunctionBody is strict code(11.1.5). - checkGrammarDecoratorsAndModifiers(node); - - checkVariableLikeDeclaration(node); - const func = getContainingFunction(node)!; - if (hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) { - if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) { - error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); - } - if (func.kind === SyntaxKind.Constructor && isIdentifier(node.name) && node.name.escapedText === "constructor") { - error(node.name, Diagnostics.constructor_cannot_be_used_as_a_parameter_property_name); - } - } - if (node.questionToken && isBindingPattern(node.name) && (func as FunctionLikeDeclaration).body) { - error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature); - } - if (node.name && isIdentifier(node.name) && (node.name.escapedText === "this" || node.name.escapedText === "new")) { - if (func.parameters.indexOf(node) !== 0) { - error(node, Diagnostics.A_0_parameter_must_be_the_first_parameter, node.name.escapedText as string); - } - if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) { - error(node, Diagnostics.A_constructor_cannot_have_a_this_parameter); - } - if (func.kind === SyntaxKind.ArrowFunction) { - error(node, Diagnostics.An_arrow_function_cannot_have_a_this_parameter); - } - if (func.kind === SyntaxKind.GetAccessor || func.kind === SyntaxKind.SetAccessor) { - error(node, Diagnostics.get_and_set_accessors_cannot_declare_this_parameters); - } + function onExit(node: BinaryExpression, state: WorkArea): Type | undefined { + let result: Type | undefined; + if (state.skip) { + result = getLastResult(state); } + else { + const leftType = getLeftType(state); + Debug.assertIsDefined(leftType); + + const rightType = getLastResult(state); + Debug.assertIsDefined(rightType); - // Only check rest parameter type if it's not a binding pattern. Since binding patterns are - // not allowed in a rest parameter, we already have an error from checkGrammarParameterList. - if (node.dotDotDotToken && !isBindingPattern(node.name) && !isTypeAssignableTo(getReducedType(getTypeOfSymbol(node.symbol)), anyReadonlyArrayType)) { - error(node, Diagnostics.A_rest_parameter_must_be_of_an_array_type); + result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node); } + + state.skip = false; + setLeftType(state, /*type*/ undefined); + setLastResult(state, /*type*/ undefined); + state.stackIndex--; + return result; } - function checkTypePredicate(node: TypePredicateNode): void { - const parent = getTypePredicateParent(node); - if (!parent) { - // The parent must not be valid. - error(node, Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); - return; - } + function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") { + setLastResult(state, result); + return state; + } - const signature = getSignatureFromDeclaration(parent); - const typePredicate = getTypePredicateOfSignature(signature); - if (!typePredicate) { - return; + function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined { + if (isBinaryExpression(node)) { + return node; } + setLastResult(state, checkExpression(node, state.checkMode)); + } - checkSourceElement(node.type); + function getLeftType(state: WorkArea) { + return state.typeStack[state.stackIndex]; + } + + function setLeftType(state: WorkArea, type: Type | undefined) { + state.typeStack[state.stackIndex] = type; + } + + function getLastResult(state: WorkArea) { + return state.typeStack[state.stackIndex + 1]; + } + + function setLastResult(state: WorkArea, type: Type | undefined) { + // To reduce overhead, reuse the next stack entry to store the + // last result. This avoids the overhead of an additional property + // on `WorkArea` and reuses empty stack entries as we walk back up + // the stack. + state.typeStack[state.stackIndex + 1] = type; + } + } - const { parameterName } = node; - if (typePredicate.kind === TypePredicateKind.This || typePredicate.kind === TypePredicateKind.AssertsThis) { - getTypeFromThisTypeNode(parameterName as ThisTypeNode); + function checkGrammarNullishCoalesceWithLogicalExpression(node: BinaryExpression) { + const { left, operatorToken, right } = node; + if (operatorToken.kind === SyntaxKind.QuestionQuestionToken) { + if (isBinaryExpression(left) && (left.operatorToken.kind === SyntaxKind.BarBarToken || left.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { + grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(left.operatorToken.kind), tokenToString(operatorToken.kind)); } - else { - if (typePredicate.parameterIndex >= 0) { - if (signatureHasRestParameter(signature) && typePredicate.parameterIndex === signature.parameters.length - 1) { - error(parameterName, Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter); - } - else { - if (typePredicate.type) { - const leadingError = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.A_type_predicate_s_type_must_be_assignable_to_its_parameter_s_type); - checkTypeAssignableTo(typePredicate.type, - getTypeOfSymbol(signature.parameters[typePredicate.parameterIndex]), - node.type, - /*headMessage*/ undefined, - leadingError); - } - } - } - else if (parameterName) { - let hasReportedError = false; - for (const { name } of parent.parameters) { - if (isBindingPattern(name) && - checkIfTypePredicateVariableIsDeclaredInBindingPattern(name, parameterName, typePredicate.parameterName)) { - hasReportedError = true; - break; - } - } - if (!hasReportedError) { - error(node.parameterName, Diagnostics.Cannot_find_parameter_0, typePredicate.parameterName); - } - } + if (isBinaryExpression(right) && (right.operatorToken.kind === SyntaxKind.BarBarToken || right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { + grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind)); } } + } - function getTypePredicateParent(node: Node): SignatureDeclaration | undefined { - switch (node.parent.kind) { - case SyntaxKind.ArrowFunction: - case SyntaxKind.CallSignature: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionType: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - const parent = node.parent as SignatureDeclaration; - if (node === parent.type) { - return parent; - } - } + // Note that this and `checkBinaryExpression` above should behave mostly the same, except this elides some + // expression-wide checks and does not use a work stack to fold nested binary expressions into the same callstack frame + function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, errorNode?: Node): Type { + const operator = operatorToken.kind; + if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) { + return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode, right.kind === SyntaxKind.ThisKeyword); + } + let leftType: Type; + if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { + leftType = checkTruthinessExpression(left, checkMode); + } + else { + leftType = checkExpression(left, checkMode); } - function checkIfTypePredicateVariableIsDeclaredInBindingPattern( - pattern: BindingPattern, - predicateVariableNode: Node, - predicateVariableName: string) { - for (const element of pattern.elements) { - if (isOmittedExpression(element)) { - continue; + const rightType = checkExpression(right, checkMode); + return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, errorNode); + } + + function checkBinaryLikeExpressionWorker( + left: Expression, + operatorToken: Node, + right: Expression, + leftType: Type, + rightType: Type, + errorNode?: Node + ): Type { + const operator = operatorToken.kind; + switch (operator) { + case SyntaxKind.AsteriskToken: + case SyntaxKind.AsteriskAsteriskToken: + case SyntaxKind.AsteriskEqualsToken: + case SyntaxKind.AsteriskAsteriskEqualsToken: + case SyntaxKind.SlashToken: + case SyntaxKind.SlashEqualsToken: + case SyntaxKind.PercentToken: + case SyntaxKind.PercentEqualsToken: + case SyntaxKind.MinusToken: + case SyntaxKind.MinusEqualsToken: + case SyntaxKind.LessThanLessThanToken: + case SyntaxKind.LessThanLessThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + case SyntaxKind.BarToken: + case SyntaxKind.BarEqualsToken: + case SyntaxKind.CaretToken: + case SyntaxKind.CaretEqualsToken: + case SyntaxKind.AmpersandToken: + case SyntaxKind.AmpersandEqualsToken: + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; } - const name = element.name; - if (name.kind === SyntaxKind.Identifier && name.escapedText === predicateVariableName) { - error(predicateVariableNode, - Diagnostics.A_type_predicate_cannot_reference_element_0_in_a_binding_pattern, - predicateVariableName); - return true; + leftType = checkNonNullType(leftType, left); + rightType = checkNonNullType(rightType, right); + + let suggestedOperator: SyntaxKind | undefined; + // if a user tries to apply a bitwise operator to 2 boolean operands + // try and return them a helpful suggestion + if ((leftType.flags & TypeFlags.BooleanLike) && + (rightType.flags & TypeFlags.BooleanLike) && + (suggestedOperator = getSuggestedBooleanOperator(operatorToken.kind)) !== undefined) { + error(errorNode || operatorToken, Diagnostics.The_0_operator_is_not_allowed_for_boolean_types_Consider_using_1_instead, tokenToString(operatorToken.kind), tokenToString(suggestedOperator)); + return numberType; } - else if (name.kind === SyntaxKind.ArrayBindingPattern || name.kind === SyntaxKind.ObjectBindingPattern) { - if (checkIfTypePredicateVariableIsDeclaredInBindingPattern( - name, - predicateVariableNode, - predicateVariableName)) { - return true; + else { + // otherwise just check each operand separately and report errors as normal + const leftOk = checkArithmeticOperandType(left, leftType, Diagnostics.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); + const rightOk = checkArithmeticOperandType(right, rightType, Diagnostics.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); + let resultType: Type; + // If both are any or unknown, allow operation; assume it will resolve to number + if ((isTypeAssignableToKind(leftType, TypeFlags.AnyOrUnknown) && isTypeAssignableToKind(rightType, TypeFlags.AnyOrUnknown)) || + // Or, if neither could be bigint, implicit coercion results in a number result + !(maybeTypeOfKind(leftType, TypeFlags.BigIntLike) || maybeTypeOfKind(rightType, TypeFlags.BigIntLike)) + ) { + resultType = numberType; + } + // At least one is assignable to bigint, so check that both are + else if (bothAreBigIntLike(leftType, rightType)) { + switch (operator) { + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + reportOperatorError(); + break; + case SyntaxKind.AsteriskAsteriskToken: + case SyntaxKind.AsteriskAsteriskEqualsToken: + if (languageVersion < ScriptTarget.ES2016) { + error(errorNode, Diagnostics.Exponentiation_cannot_be_performed_on_bigint_values_unless_the_target_option_is_set_to_es2016_or_later); + } + } + resultType = bigintType; + } + // Exactly one of leftType/rightType is assignable to bigint + else { + reportOperatorError(bothAreBigIntLike); + resultType = errorType; + } + if (leftOk && rightOk) { + checkAssignmentOperator(resultType); } + return resultType; } - } - } - - function checkSignatureDeclaration(node: SignatureDeclaration) { - // Grammar checking - if (node.kind === SyntaxKind.IndexSignature) { - checkGrammarIndexSignature(node as SignatureDeclaration); - } - // TODO (yuisu): Remove this check in else-if when SyntaxKind.Construct is moved and ambient context is handled - else if (node.kind === SyntaxKind.FunctionType || node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.ConstructorType || - node.kind === SyntaxKind.CallSignature || node.kind === SyntaxKind.Constructor || - node.kind === SyntaxKind.ConstructSignature) { - checkGrammarFunctionLikeDeclaration(node as FunctionLikeDeclaration); - } - - const functionFlags = getFunctionFlags(node as FunctionLikeDeclaration); - if (!(functionFlags & FunctionFlags.Invalid)) { - // Async generators prior to ESNext require the __await and __asyncGenerator helpers - if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator && languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncGeneratorIncludes); + case SyntaxKind.PlusToken: + case SyntaxKind.PlusEqualsToken: + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; } - // Async functions prior to ES2017 require the __awaiter helper - if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async && languageVersion < ScriptTarget.ES2017) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Awaiter); + if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) { + leftType = checkNonNullType(leftType, left); + rightType = checkNonNullType(rightType, right); } - // Generator functions, Async functions, and Async Generator functions prior to - // ES2015 require the __generator helper - if ((functionFlags & FunctionFlags.AsyncGenerator) !== FunctionFlags.Normal && languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Generator); + let resultType: Type | undefined; + if (isTypeAssignableToKind(leftType, TypeFlags.NumberLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.NumberLike, /*strict*/ true)) { + // Operands of an enum type are treated as having the primitive type Number. + // If both operands are of the Number primitive type, the result is of the Number primitive type. + resultType = numberType; + } + else if (isTypeAssignableToKind(leftType, TypeFlags.BigIntLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.BigIntLike, /*strict*/ true)) { + // If both operands are of the BigInt primitive type, the result is of the BigInt primitive type. + resultType = bigintType; + } + else if (isTypeAssignableToKind(leftType, TypeFlags.StringLike, /*strict*/ true) || isTypeAssignableToKind(rightType, TypeFlags.StringLike, /*strict*/ true)) { + // If one or both operands are of the String primitive type, the result is of the String primitive type. + resultType = stringType; + } + else if (isTypeAny(leftType) || isTypeAny(rightType)) { + // Otherwise, the result is of type Any. + // NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we. + resultType = isErrorType(leftType) || isErrorType(rightType) ? errorType : anyType; } - } - - checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); - forEach(node.parameters, checkParameter); + // Symbols are not allowed at all in arithmetic expressions + if (resultType && !checkForDisallowedESSymbolOperand(operator)) { + return resultType; + } - // TODO(rbuckton): Should we start checking JSDoc types? - if (node.type) { - checkSourceElement(node.type); - } + if (!resultType) { + // Types that have a reasonably good chance of being a valid operand type. + // If both types have an awaited type of one of these, we'll assume the user + // might be missing an await without doing an exhaustive check that inserting + // await(s) will actually be a completely valid binary expression. + const closeEnoughKind = TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.AnyOrUnknown; + reportOperatorError((left, right) => + isTypeAssignableToKind(left, closeEnoughKind) && + isTypeAssignableToKind(right, closeEnoughKind)); + return anyType; + } - if (produceDiagnostics) { - checkCollisionWithArgumentsInGeneratedCode(node); - const returnTypeNode = getEffectiveReturnTypeNode(node); - if (noImplicitAny && !returnTypeNode) { - switch (node.kind) { - case SyntaxKind.ConstructSignature: - error(node, Diagnostics.Construct_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); - break; - case SyntaxKind.CallSignature: - error(node, Diagnostics.Call_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); - break; - } + if (operator === SyntaxKind.PlusEqualsToken) { + checkAssignmentOperator(resultType); + } + return resultType; + case SyntaxKind.LessThanToken: + case SyntaxKind.GreaterThanToken: + case SyntaxKind.LessThanEqualsToken: + case SyntaxKind.GreaterThanEqualsToken: + if (checkForDisallowedESSymbolOperand(operator)) { + leftType = getBaseTypeOfLiteralType(checkNonNullType(leftType, left)); + rightType = getBaseTypeOfLiteralType(checkNonNullType(rightType, right)); + reportOperatorErrorUnless((left, right) => + isTypeComparableTo(left, right) || isTypeComparableTo(right, left) || ( + isTypeAssignableTo(left, numberOrBigIntType) && isTypeAssignableTo(right, numberOrBigIntType))); } + return booleanType; + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left)); + return booleanType; - if (returnTypeNode) { - const functionFlags = getFunctionFlags(node as FunctionDeclaration); - if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) { - const returnType = getTypeFromTypeNode(returnTypeNode); - if (returnType === voidType) { - error(returnTypeNode, Diagnostics.A_generator_cannot_have_a_void_type_annotation); - } - else { - // Naively, one could check that Generator is assignable to the return type annotation. - // However, that would not catch the error in the following case. - // - // interface BadGenerator extends Iterable, Iterator { } - // function* g(): BadGenerator { } // Iterable and Iterator have different types! - // - const generatorYieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType; - const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType; - const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType; - const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async)); - checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeNode); - } - } - else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { - checkAsyncFunctionReturnType(node as FunctionLikeDeclaration, returnTypeNode); + case SyntaxKind.InstanceOfKeyword: + return checkInstanceOfExpression(left, right, leftType, rightType); + case SyntaxKind.InKeyword: + return checkInExpression(left, right, leftType, rightType); + case SyntaxKind.AmpersandAmpersandToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: { + const resultType = getTypeFacts(leftType) & TypeFacts.Truthy ? + getUnionType([extractDefinitelyFalsyTypes(strictNullChecks ? leftType : getBaseTypeOfLiteralType(rightType)), rightType]) : + leftType; + if (operator === SyntaxKind.AmpersandAmpersandEqualsToken) { + checkAssignmentOperator(rightType); + } + return resultType; + } + case SyntaxKind.BarBarToken: + case SyntaxKind.BarBarEqualsToken: { + const resultType = getTypeFacts(leftType) & TypeFacts.Falsy ? + getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) : + leftType; + if (operator === SyntaxKind.BarBarEqualsToken) { + checkAssignmentOperator(rightType); + } + return resultType; + } + case SyntaxKind.QuestionQuestionToken: + case SyntaxKind.QuestionQuestionEqualsToken: { + const resultType = getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ? + getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) : + leftType; + if (operator === SyntaxKind.QuestionQuestionEqualsToken) { + checkAssignmentOperator(rightType); + } + return resultType; + } + case SyntaxKind.EqualsToken: + const declKind = isBinaryExpression(left.parent) ? getAssignmentDeclarationKind(left.parent) : AssignmentDeclarationKind.None; + checkAssignmentDeclaration(declKind, rightType); + if (isAssignmentDeclaration(declKind)) { + if (!(rightType.flags & TypeFlags.Object) || + declKind !== AssignmentDeclarationKind.ModuleExports && + declKind !== AssignmentDeclarationKind.Prototype && + !isEmptyObjectType(rightType) && + !isFunctionObjectType(rightType as ObjectType) && + !(getObjectFlags(rightType) & ObjectFlags.Class)) { + // don't check assignability of module.exports=, C.prototype=, or expando types because they will necessarily be incomplete + checkAssignmentOperator(rightType); } + return leftType; } - if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) { - registerForUnusedIdentifiersCheck(node); + else { + checkAssignmentOperator(rightType); + return getRegularTypeOfObjectLiteral(rightType); + } + case SyntaxKind.CommaToken: + if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isEvalNode(right)) { + const sf = getSourceFileOfNode(left); + const sourceText = sf.text; + const start = skipTrivia(sourceText, left.pos); + const isInDiag2657 = sf.parseDiagnostics.some(diag => { + if (diag.code !== Diagnostics.JSX_expressions_must_have_one_parent_element.code) return false; + return textSpanContainsPosition(diag, start); + }); + if (!isInDiag2657) error(left, Diagnostics.Left_side_of_comma_operator_is_unused_and_has_no_side_effects); } - } + return rightType; + + default: + return Debug.fail(); } - function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { - const instanceNames = new Map<__String, DeclarationMeaning>(); - const staticNames = new Map<__String, DeclarationMeaning>(); - // instance and static private identifiers share the same scope - const privateIdentifiers = new Map<__String, DeclarationMeaning>(); - for (const member of node.members) { - if (member.kind === SyntaxKind.Constructor) { - for (const param of (member as ConstructorDeclaration).parameters) { - if (isParameterPropertyDeclaration(param, member) && !isBindingPattern(param.name)) { - addName(instanceNames, param.name, param.name.escapedText, DeclarationMeaning.GetOrSetAccessor); + function bothAreBigIntLike(left: Type, right: Type): boolean { + return isTypeAssignableToKind(left, TypeFlags.BigIntLike) && isTypeAssignableToKind(right, TypeFlags.BigIntLike); + } + + function checkAssignmentDeclaration(kind: AssignmentDeclarationKind, rightType: Type) { + if (kind === AssignmentDeclarationKind.ModuleExports) { + for (const prop of getPropertiesOfObjectType(rightType)) { + const propType = getTypeOfSymbol(prop); + if (propType.symbol && propType.symbol.flags & SymbolFlags.Class) { + const name = prop.escapedName; + const symbol = resolveName(prop.valueDeclaration, name, SymbolFlags.Type, undefined, name, /*isUse*/ false); + if (symbol?.declarations && symbol.declarations.some(isJSDocTypedefTag)) { + addDuplicateDeclarationErrorsForSymbols(symbol, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), prop); + addDuplicateDeclarationErrorsForSymbols(prop, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), symbol); } } } - else { - const isStaticMember = isStatic(member); - const name = member.name; - if (!name) { - continue; - } - const isPrivate = isPrivateIdentifier(name); - const privateStaticFlags = isPrivate && isStaticMember ? DeclarationMeaning.PrivateStatic : 0; - const names = - isPrivate ? privateIdentifiers : - isStaticMember ? staticNames : - instanceNames; + } + } - const memberName = name && getPropertyNameForPropertyNameNode(name); - if (memberName) { - switch (member.kind) { - case SyntaxKind.GetAccessor: - addName(names, name, memberName, DeclarationMeaning.GetAccessor | privateStaticFlags); - break; + function isEvalNode(node: Expression) { + return node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "eval"; + } - case SyntaxKind.SetAccessor: - addName(names, name, memberName, DeclarationMeaning.SetAccessor | privateStaticFlags); - break; + // Return true if there was no error, false if there was an error. + function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean { + const offendingSymbolOperand = + maybeTypeOfKind(leftType, TypeFlags.ESSymbolLike) ? left : + maybeTypeOfKind(rightType, TypeFlags.ESSymbolLike) ? right : + undefined; - case SyntaxKind.PropertyDeclaration: - addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor | privateStaticFlags); - break; + if (offendingSymbolOperand) { + error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); + return false; + } - case SyntaxKind.MethodDeclaration: - addName(names, name, memberName, DeclarationMeaning.Method | privateStaticFlags); - break; - } - } - } + return true; + } + + function getSuggestedBooleanOperator(operator: SyntaxKind): SyntaxKind | undefined { + switch (operator) { + case SyntaxKind.BarToken: + case SyntaxKind.BarEqualsToken: + return SyntaxKind.BarBarToken; + case SyntaxKind.CaretToken: + case SyntaxKind.CaretEqualsToken: + return SyntaxKind.ExclamationEqualsEqualsToken; + case SyntaxKind.AmpersandToken: + case SyntaxKind.AmpersandEqualsToken: + return SyntaxKind.AmpersandAmpersandToken; + default: + return undefined; } + } - function addName(names: UnderscoreEscapedMap, location: Node, name: __String, meaning: DeclarationMeaning) { - const prev = names.get(name); - if (prev) { - // For private identifiers, do not allow mixing of static and instance members with the same name - if ((prev & DeclarationMeaning.PrivateStatic) !== (meaning & DeclarationMeaning.PrivateStatic)) { - error(location, Diagnostics.Duplicate_identifier_0_Static_and_instance_elements_cannot_share_the_same_private_name, getTextOfNode(location)); - } - else { - const prevIsMethod = !!(prev & DeclarationMeaning.Method); - const isMethod = !!(meaning & DeclarationMeaning.Method); - if (prevIsMethod || isMethod) { - if (prevIsMethod !== isMethod) { - error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); - } - // If this is a method/method duplication is might be an overload, so this will be handled when overloads are considered - } - else if (prev & meaning & ~DeclarationMeaning.PrivateStatic) { - error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); - } - else { - names.set(name, prev | meaning); + function checkAssignmentOperator(valueType: Type): void { + if (produceDiagnostics && isAssignmentOperator(operator)) { + // TypeScript 1.0 spec (April 2014): 4.17 + // An assignment of the form + // VarExpr = ValueExpr + // requires VarExpr to be classified as a reference + // A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1) + // and the type of the non-compound operation to be assignable to the type of VarExpr. + + if (checkReferenceExpression(left, + Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access, + Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access) + && (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) { + + let headMessage: DiagnosticMessage | undefined; + if (exactOptionalPropertyTypes && isPropertyAccessExpression(left) && maybeTypeOfKind(valueType, TypeFlags.Undefined)) { + const target = getTypeOfPropertyOfType(getTypeOfExpression(left.expression), left.name.escapedText); + if (isExactOptionalPropertyMismatch(valueType, target)) { + headMessage = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target; } } + // to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported + checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right, headMessage); } - else { - names.set(name, meaning); - } + } + } + + function isAssignmentDeclaration(kind: AssignmentDeclarationKind) { + switch (kind) { + case AssignmentDeclarationKind.ModuleExports: + return true; + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.Property: + case AssignmentDeclarationKind.Prototype: + case AssignmentDeclarationKind.PrototypeProperty: + case AssignmentDeclarationKind.ThisProperty: + const symbol = getSymbolOfNode(left); + const init = getAssignedExpandoInitializer(right); + return !!init && isObjectLiteralExpression(init) && + !!symbol?.exports?.size; + default: + return false; } } /** - * Static members being set on a constructor function may conflict with built-in properties - * of Function. Esp. in ECMAScript 5 there are non-configurable and non-writable - * built-in properties. This check issues a transpile error when a class has a static - * member with the same name as a non-writable built-in property. - * - * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.3 - * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.5 - * @see http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-function-constructor - * @see http://www.ecma-international.org/ecma-262/6.0/#sec-function-instances + * Returns true if an error is reported */ - function checkClassForStaticPropertyNameConflicts(node: ClassLikeDeclaration) { - for (const member of node.members) { - const memberNameNode = member.name; - const isStaticMember = isStatic(member); - if (isStaticMember && memberNameNode) { - const memberName = getPropertyNameForPropertyNameNode(memberNameNode); - switch (memberName) { - case "name": - case "length": - case "caller": - case "arguments": - case "prototype": - const message = Diagnostics.Static_property_0_conflicts_with_built_in_property_Function_0_of_constructor_function_1; - const className = getNameOfSymbolAsWritten(getSymbolOfNode(node)); - error(memberNameNode, message, memberName, className); - break; - } - } + function reportOperatorErrorUnless(typesAreCompatible: (left: Type, right: Type) => boolean): boolean { + if (!typesAreCompatible(leftType, rightType)) { + reportOperatorError(typesAreCompatible); + return true; } + return false; } - function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) { - const names = new Map(); - for (const member of node.members) { - if (member.kind === SyntaxKind.PropertySignature) { - let memberName: string; - const name = member.name!; - switch (name.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - memberName = name.text; - break; - case SyntaxKind.Identifier: - memberName = idText(name); - break; - default: - continue; - } + function reportOperatorError(isRelated?: (left: Type, right: Type) => boolean) { + let wouldWorkWithAwait = false; + const errNode = errorNode || operatorToken; + if (isRelated) { + const awaitedLeftType = getAwaitedTypeNoAlias(leftType); + const awaitedRightType = getAwaitedTypeNoAlias(rightType); + wouldWorkWithAwait = !(awaitedLeftType === leftType && awaitedRightType === rightType) + && !!(awaitedLeftType && awaitedRightType) + && isRelated(awaitedLeftType, awaitedRightType); + } - if (names.get(memberName)) { - error(getNameOfDeclaration(member.symbol.valueDeclaration), Diagnostics.Duplicate_identifier_0, memberName); - error(member.name, Diagnostics.Duplicate_identifier_0, memberName); - } - else { - names.set(memberName, true); - } - } + let effectiveLeft = leftType; + let effectiveRight = rightType; + if (!wouldWorkWithAwait && isRelated) { + [effectiveLeft, effectiveRight] = getBaseTypesIfUnrelated(leftType, rightType, isRelated); + } + const [leftStr, rightStr] = getTypeNamesForErrorDisplay(effectiveLeft, effectiveRight); + if (!tryGiveBetterPrimaryError(errNode, wouldWorkWithAwait, leftStr, rightStr)) { + errorAndMaybeSuggestAwait( + errNode, + wouldWorkWithAwait, + Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2, + tokenToString(operatorToken.kind), + leftStr, + rightStr, + ); } } - function checkTypeForDuplicateIndexSignatures(node: Node) { - if (node.kind === SyntaxKind.InterfaceDeclaration) { - const nodeSymbol = getSymbolOfNode(node as InterfaceDeclaration); - // in case of merging interface declaration it is possible that we'll enter this check procedure several times for every declaration - // to prevent this run check only for the first declaration of a given kind - if (nodeSymbol.declarations && nodeSymbol.declarations.length > 0 && nodeSymbol.declarations[0] !== node) { - return; - } + function tryGiveBetterPrimaryError(errNode: Node, maybeMissingAwait: boolean, leftStr: string, rightStr: string) { + let typeName: string | undefined; + switch (operatorToken.kind) { + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.EqualsEqualsToken: + typeName = "false"; + break; + case SyntaxKind.ExclamationEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + typeName = "true"; } - // TypeScript 1.0 spec (April 2014) - // 3.7.4: An object type can contain at most one string index signature and one numeric index signature. - // 8.5: A class declaration can have at most one string index member declaration and one numeric index member declaration - const indexSymbol = getIndexSymbol(getSymbolOfNode(node)!); - if (indexSymbol?.declarations) { - const indexSignatureMap = new Map(); - for (const declaration of (indexSymbol.declarations as IndexSignatureDeclaration[])) { - if (declaration.parameters.length === 1 && declaration.parameters[0].type) { - forEachType(getTypeFromTypeNode(declaration.parameters[0].type), type => { - const entry = indexSignatureMap.get(getTypeId(type)); - if (entry) { - entry.declarations.push(declaration); - } - else { - indexSignatureMap.set(getTypeId(type), { type, declarations: [declaration] }); - } - }); - } - } - indexSignatureMap.forEach(entry => { - if (entry.declarations.length > 1) { - for (const declaration of entry.declarations) { - error(declaration, Diagnostics.Duplicate_index_signature_for_type_0, typeToString(entry.type)); - } - } - }); + if (typeName) { + return errorAndMaybeSuggestAwait( + errNode, + maybeMissingAwait, + Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap, + typeName, leftStr, rightStr); } + + return undefined; } + } - function checkPropertyDeclaration(node: PropertyDeclaration | PropertySignature) { - // Grammar checking - if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name); - checkVariableLikeDeclaration(node); + function getBaseTypesIfUnrelated(leftType: Type, rightType: Type, isRelated: (left: Type, right: Type) => boolean): [Type, Type] { + let effectiveLeft = leftType; + let effectiveRight = rightType; + const leftBase = getBaseTypeOfLiteralType(leftType); + const rightBase = getBaseTypeOfLiteralType(rightType); + if (!isRelated(leftBase, rightBase)) { + effectiveLeft = leftBase; + effectiveRight = rightBase; + } + return [ effectiveLeft, effectiveRight ]; + } - setNodeLinksForPrivateIdentifierScope(node); - if (isPrivateIdentifier(node.name) && hasStaticModifier(node) && node.initializer && languageVersion === ScriptTarget.ESNext && !compilerOptions.useDefineForClassFields) { - error(node.initializer, Diagnostics.Static_fields_with_private_names_can_t_have_initializers_when_the_useDefineForClassFields_flag_is_not_specified_with_a_target_of_esnext_Consider_adding_the_useDefineForClassFields_flag); - } - // property signatures already report "initializer not allowed in ambient context" elsewhere - if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.PropertyDeclaration && node.initializer) { - error(node, Diagnostics.Property_0_cannot_have_an_initializer_because_it_is_marked_abstract, declarationNameToString(node.name)); + function checkYieldExpression(node: YieldExpression): Type { + // Grammar checking + if (produceDiagnostics) { + if (!(node.flags & NodeFlags.YieldContext)) { + grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); } - } - function checkPropertySignature(node: PropertySignature) { - if (isPrivateIdentifier(node.name)) { - error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); } - return checkPropertyDeclaration(node); } - function checkMethodDeclaration(node: MethodDeclaration | MethodSignature) { - // Grammar checking - if (!checkGrammarMethod(node)) checkGrammarComputedPropertyName(node.name); + const func = getContainingFunction(node); + if (!func) return anyType; + const functionFlags = getFunctionFlags(func); - // Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration - checkFunctionOrMethodDeclaration(node); + if (!(functionFlags & FunctionFlags.Generator)) { + // If the user's code is syntactically correct, the func should always have a star. After all, we are in a yield context. + return anyType; + } - // method signatures already report "implementation not allowed in ambient context" elsewhere - if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.MethodDeclaration && node.body) { - error(node, Diagnostics.Method_0_cannot_have_an_implementation_because_it_is_marked_abstract, declarationNameToString(node.name)); + const isAsync = (functionFlags & FunctionFlags.Async) !== 0; + if (node.asteriskToken) { + // Async generator functions prior to ESNext require the __await, __asyncDelegator, + // and __asyncValues helpers + if (isAsync && languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes); } - // Private named methods are only allowed in class declarations - if (isPrivateIdentifier(node.name) && !getContainingClass(node)) { - error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + // Generator functions prior to ES2015 require the __values helper + if (!isAsync && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Values); } + } - setNodeLinksForPrivateIdentifierScope(node); + // There is no point in doing an assignability check if the function + // has no explicit return type because the return type is directly computed + // from the yield expressions. + const returnType = getReturnTypeFromAnnotation(func); + const iterationTypes = returnType && getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsync); + const signatureYieldType = iterationTypes && iterationTypes.yieldType || anyType; + const signatureNextType = iterationTypes && iterationTypes.nextType || anyType; + const resolvedSignatureNextType = isAsync ? getAwaitedType(signatureNextType) || anyType : signatureNextType; + const yieldExpressionType = node.expression ? checkExpression(node.expression) : undefinedWideningType; + const yieldedType = getYieldedTypeOfYieldExpression(node, yieldExpressionType, resolvedSignatureNextType, isAsync); + if (returnType && yieldedType) { + checkTypeAssignableToAndOptionallyElaborate(yieldedType, signatureYieldType, node.expression || node, node.expression); } - function setNodeLinksForPrivateIdentifierScope(node: PropertyDeclaration | PropertySignature | MethodDeclaration | MethodSignature | AccessorDeclaration) { - if (isPrivateIdentifier(node.name) && languageVersion < ScriptTarget.ESNext) { - for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) { - getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers; + if (node.asteriskToken) { + const use = isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar; + return getIterationTypeOfIterable(use, IterationTypeKind.Return, yieldExpressionType, node.expression) + || anyType; + } + else if (returnType) { + return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, isAsync) + || anyType; + } + let type = getContextualIterationType(IterationTypeKind.Next, func); + if (!type) { + type = anyType; + if (produceDiagnostics && noImplicitAny && !expressionResultIsUnused(node)) { + const contextualType = getContextualType(node); + if (!contextualType || isTypeAny(contextualType)) { + error(node, Diagnostics.yield_expression_implicitly_results_in_an_any_type_because_its_containing_generator_lacks_a_return_type_annotation); } + } + } + return type; + } - // If this is a private element in a class expression inside the body of a loop, - // then we must use a block-scoped binding to store the additional variables required - // to transform private elements. - if (isClassExpression(node.parent)) { - const enclosingIterationStatement = getEnclosingIterationStatement(node.parent); - if (enclosingIterationStatement) { - getNodeLinks(node.name).flags |= NodeCheckFlags.BlockScopedBindingInLoop; - getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; - } - } + function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { + const type = checkTruthinessExpression(node.condition); + checkTestingKnownTruthyCallableOrAwaitableType(node.condition, type, node.whenTrue); + const type1 = checkExpression(node.whenTrue, checkMode); + const type2 = checkExpression(node.whenFalse, checkMode); + return getUnionType([type1, type2], UnionReduction.Subtype); + } + + function isTemplateLiteralContext(node: Node): boolean { + const parent = node.parent; + return isParenthesizedExpression(parent) && isTemplateLiteralContext(parent) || + isElementAccessExpression(parent) && parent.argumentExpression === node; + } + + function checkTemplateExpression(node: TemplateExpression): Type { + const texts = [node.head.text]; + const types = []; + for (const span of node.templateSpans) { + const type = checkExpression(span.expression); + if (maybeTypeOfKind(type, TypeFlags.ESSymbolLike)) { + error(span.expression, Diagnostics.Implicit_conversion_of_a_symbol_to_a_string_will_fail_at_runtime_Consider_wrapping_this_expression_in_String); } + texts.push(span.literal.text); + types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType); } + return isConstContext(node) || isTemplateLiteralContext(node) || someType(getContextualType(node) || unknownType, isTemplateLiteralContextualType) ? getTemplateLiteralType(texts, types) : stringType; + } - function checkClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { - checkGrammarDecoratorsAndModifiers(node); + function isTemplateLiteralContextualType(type: Type): boolean { + return !!(type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral) || + type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.StringLike)); + } - forEachChild(node, checkSourceElement); + function getContextNode(node: Expression): Node { + if (node.kind === SyntaxKind.JsxAttributes && !isJsxSelfClosingElement(node.parent)) { + return node.parent.parent; // Needs to be the root JsxElement, so it encompasses the attributes _and_ the children (which are essentially part of the attributes) } + return node; + } - function checkConstructorDeclaration(node: ConstructorDeclaration) { - // Grammar check on signature of constructor and modifier of the constructor is done in checkSignatureDeclaration function. - checkSignatureDeclaration(node); - // Grammar check for checking only related to constructorDeclaration - if (!checkGrammarConstructorTypeParameters(node)) checkGrammarConstructorTypeAnnotation(node); + function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { + const context = getContextNode(node); + const saveContextualType = context.contextualType; + const saveInferenceContext = context.inferenceContext; + try { + context.contextualType = contextualType; + context.inferenceContext = inferenceContext; + const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); + // We strip literal freshness when an appropriate contextual type is present such that contextually typed + // literals always preserve their literal types (otherwise they might widen during type inference). An alternative + // here would be to not mark contextually typed literals as fresh in the first place. + const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node)) ? + getRegularTypeOfLiteralType(type) : type; + return result; + } + finally { + // In the event our operation is canceled or some other exception occurs, reset the contextual type + // so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer + // may hold onto the checker that created it. + context.contextualType = saveContextualType; + context.inferenceContext = saveInferenceContext; + } + } - checkSourceElement(node.body); + function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + if (checkMode && checkMode !== CheckMode.Normal) { + return checkExpression(node, checkMode); + } + // When computing a type that we're going to cache, we need to ignore any ongoing control flow + // analysis because variables may have transient types in indeterminable states. Moving flowLoopStart + // to the top of the stack ensures all transient types are computed from a known point. + const saveFlowLoopStart = flowLoopStart; + const saveFlowTypeCache = flowTypeCache; + flowLoopStart = flowLoopCount; + flowTypeCache = undefined; + links.resolvedType = checkExpression(node, checkMode); + flowTypeCache = saveFlowTypeCache; + flowLoopStart = saveFlowLoopStart; + } + return links.resolvedType; + } - const symbol = getSymbolOfNode(node); - const firstDeclaration = getDeclarationOfKind(symbol, node.kind); + function isTypeAssertion(node: Expression) { + node = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + return node.kind === SyntaxKind.TypeAssertionExpression || + node.kind === SyntaxKind.AsExpression || + isJSDocTypeAssertion(node); + } - // Only type check the symbol once - if (node === firstDeclaration) { - checkFunctionOrConstructorSymbol(symbol); - } + function checkDeclarationInitializer(declaration: HasExpressionInitializer, contextualType?: Type | undefined) { + const initializer = getEffectiveInitializer(declaration)!; + const type = getQuickTypeOfExpression(initializer) || + (contextualType ? checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, CheckMode.Normal) : checkExpressionCached(initializer)); + return isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern && + isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ? + padTupleType(type, declaration.name) : type; + } - // exit early in the case of signature - super checks are not relevant to them - if (nodeIsMissing(node.body)) { - return; + function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) { + const patternElements = pattern.elements; + const elementTypes = getTypeArguments(type).slice(); + const elementFlags = type.target.elementFlags.slice(); + for (let i = getTypeReferenceArity(type); i < patternElements.length; i++) { + const e = patternElements[i]; + if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && e.dotDotDotToken)) { + elementTypes.push(!isOmittedExpression(e) && hasDefaultValue(e) ? getTypeFromBindingElement(e, /*includePatternInType*/ false, /*reportErrors*/ false) : anyType); + elementFlags.push(ElementFlags.Optional); + if (!isOmittedExpression(e) && !hasDefaultValue(e)) { + reportImplicitAny(e, anyType); + } } + } + return createTupleType(elementTypes, elementFlags, type.target.readonly); + } - if (!produceDiagnostics) { - return; + function widenTypeInferredFromInitializer(declaration: HasExpressionInitializer, type: Type) { + const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const || isDeclarationReadonly(declaration) ? type : getWidenedLiteralType(type); + if (isInJSFile(declaration)) { + if (isEmptyLiteralType(widened)) { + reportImplicitAny(declaration, anyType); + return anyType; } - - function isInstancePropertyWithInitializerOrPrivateIdentifierProperty(n: Node): boolean { - if (isPrivateIdentifierClassElementDeclaration(n)) { - return true; - } - return n.kind === SyntaxKind.PropertyDeclaration && - !isStatic(n) && - !!(n as PropertyDeclaration).initializer; - } - - // TS 1.0 spec (April 2014): 8.3.2 - // Constructors of classes with no extends clause may not contain super calls, whereas - // constructors of derived classes must contain at least one super call somewhere in their function body. - const containingClassDecl = node.parent as ClassDeclaration; - if (getClassExtendsHeritageElement(containingClassDecl)) { - captureLexicalThis(node.parent, containingClassDecl); - const classExtendsNull = classDeclarationExtendsNull(containingClassDecl); - const superCall = findFirstSuperCall(node.body!); - if (superCall) { - if (classExtendsNull) { - error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); - } - - // The first statement in the body of a constructor (excluding prologue directives) must be a super call - // if both of the following are true: - // - The containing class is a derived class. - // - The constructor declares parameter properties - // or the containing class declares instance member variables with initializers. - const superCallShouldBeFirst = - (getEmitScriptTarget(compilerOptions) !== ScriptTarget.ESNext || !useDefineForClassFields) && - (some((node.parent as ClassDeclaration).members, isInstancePropertyWithInitializerOrPrivateIdentifierProperty) || - some(node.parameters, p => hasSyntacticModifier(p, ModifierFlags.ParameterPropertyModifier))); - - // Skip past any prologue directives to find the first statement - // to ensure that it was a super call. - if (superCallShouldBeFirst) { - const statements = node.body!.statements; - let superCallStatement: ExpressionStatement | undefined; - - for (const statement of statements) { - if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((statement as ExpressionStatement).expression)) { - superCallStatement = statement as ExpressionStatement; - break; - } - if (!isPrologueDirective(statement)) { - break; - } - } - if (!superCallStatement) { - error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_when_a_class_contains_initialized_properties_parameter_properties_or_private_identifiers); - } - } - } - else if (!classExtendsNull) { - error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); - } + else if (isEmptyArrayLiteralType(widened)) { + reportImplicitAny(declaration, anyArrayType); + return anyArrayType; } } + return widened; + } + + function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean { + if (contextualType) { + if (contextualType.flags & TypeFlags.UnionOrIntersection) { + const types = (contextualType as UnionType).types; + return some(types, t => isLiteralOfContextualType(candidateType, t)); + } + if (contextualType.flags & TypeFlags.InstantiableNonPrimitive) { + // If the contextual type is a type variable constrained to a primitive type, consider + // this a literal context for literals of that primitive type. For example, given a + // type parameter 'T extends string', infer string literal types for T. + const constraint = getBaseConstraintOfType(contextualType) || unknownType; + return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || + maybeTypeOfKind(constraint, TypeFlags.Number) && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || + maybeTypeOfKind(constraint, TypeFlags.BigInt) && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || + maybeTypeOfKind(constraint, TypeFlags.ESSymbol) && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) || + isLiteralOfContextualType(candidateType, constraint); + } + // If the contextual type is a literal of a particular primitive type, we consider this a + // literal context for all literals of that primitive type. + return !!(contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || + contextualType.flags & TypeFlags.NumberLiteral && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || + contextualType.flags & TypeFlags.BigIntLiteral && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || + contextualType.flags & TypeFlags.BooleanLiteral && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) || + contextualType.flags & TypeFlags.UniqueESSymbol && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol)); + } + return false; + } + + function isConstContext(node: Expression): boolean { + const parent = node.parent; + return isAssertionExpression(parent) && isConstTypeReference(parent.type) || + isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) || + (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || + (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); + } + + function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, contextualType?: Type, forceTuple?: boolean): Type { + const type = checkExpression(node, checkMode, forceTuple); + return isConstContext(node) ? getRegularTypeOfLiteralType(type) : + isTypeAssertion(node) ? type : + getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node) : contextualType, node)); + } + + function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type { + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); + } - function checkAccessorDeclaration(node: AccessorDeclaration) { - if (produceDiagnostics) { - // Grammar checking accessors - if (!checkGrammarFunctionLikeDeclaration(node) && !checkGrammarAccessor(node)) checkGrammarComputedPropertyName(node.name); + return checkExpressionForMutableLocation(node.initializer, checkMode); + } - checkDecorators(node); - checkSignatureDeclaration(node); - if (node.kind === SyntaxKind.GetAccessor) { - if (!(node.flags & NodeFlags.Ambient) && nodeIsPresent(node.body) && (node.flags & NodeFlags.HasImplicitReturn)) { - if (!(node.flags & NodeFlags.HasExplicitReturn)) { - error(node.name, Diagnostics.A_get_accessor_must_return_a_value); - } - } - } - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); - } + function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { + // Grammar checking + checkGrammarMethod(node); - if (hasBindableName(node)) { - // TypeScript 1.0 spec (April 2014): 8.4.3 - // Accessors for the same member name must specify the same accessibility. - const symbol = getSymbolOfNode(node); - const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); - const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); - if (getter && setter && !(getNodeCheckFlags(getter) & NodeCheckFlags.TypeChecked)) { - getNodeLinks(getter).flags |= NodeCheckFlags.TypeChecked; - const getterFlags = getEffectiveModifierFlags(getter); - const setterFlags = getEffectiveModifierFlags(setter); - if ((getterFlags & ModifierFlags.Abstract) !== (setterFlags & ModifierFlags.Abstract)) { - error(getter.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); - error(setter.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); - } - if (((getterFlags & ModifierFlags.Protected) && !(setterFlags & (ModifierFlags.Protected | ModifierFlags.Private))) || - ((getterFlags & ModifierFlags.Private) && !(setterFlags & ModifierFlags.Private))) { - error(getter.name, Diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter); - error(setter.name, Diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter); - } + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); + } + + const uninstantiatedType = checkFunctionExpressionOrObjectLiteralMethod(node, checkMode); + return instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); + } - const getterType = getAnnotatedAccessorType(getter); - const setterType = getAnnotatedAccessorType(setter); - if (getterType && setterType) { - checkTypeAssignableTo(getterType, setterType, getter, Diagnostics.The_return_type_of_a_get_accessor_must_be_assignable_to_its_set_accessor_type); + function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: Type, checkMode?: CheckMode) { + if (checkMode && checkMode & (CheckMode.Inferential | CheckMode.SkipGenericFunctions)) { + const callSignature = getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ true); + const constructSignature = getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ true); + const signature = callSignature || constructSignature; + if (signature && signature.typeParameters) { + const contextualType = getApparentTypeOfContextualType(node as Expression, ContextFlags.NoConstraints); + if (contextualType) { + const contextualSignature = getSingleSignature(getNonNullableType(contextualType), callSignature ? SignatureKind.Call : SignatureKind.Construct, /*allowMembers*/ false); + if (contextualSignature && !contextualSignature.typeParameters) { + if (checkMode & CheckMode.SkipGenericFunctions) { + skippedGenericFunction(node, checkMode); + return anyFunctionType; + } + const context = getInferenceContext(node)!; + // We have an expression that is an argument of a generic function for which we are performing + // type argument inference. The expression is of a function type with a single generic call + // signature and a contextual function type with a single non-generic call signature. Now check + // if the outer function returns a function type with a single non-generic call signature and + // if some of the outer function type parameters have no inferences so far. If so, we can + // potentially add inferred type parameters to the outer function return type. + const returnType = context.signature && getReturnTypeOfSignature(context.signature); + const returnSignature = returnType && getSingleCallOrConstructSignature(returnType); + if (returnSignature && !returnSignature.typeParameters && !every(context.inferences, hasInferenceCandidates)) { + // Instantiate the signature with its own type parameters as type arguments, possibly + // renaming the type parameters to ensure they have unique names. + const uniqueTypeParameters = getUniqueTypeParameters(context, signature.typeParameters); + const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, uniqueTypeParameters); + // Infer from the parameters of the instantiated signature to the parameters of the + // contextual signature starting with an empty set of inference candidates. + const inferences = map(context.inferences, info => createInferenceInfo(info.typeParameter)); + applyToParameterTypes(instantiatedSignature, contextualSignature, (source, target) => { + inferTypes(inferences, source, target, /*priority*/ 0, /*contravariant*/ true); + }); + if (some(inferences, hasInferenceCandidates)) { + // We have inference candidates, indicating that one or more type parameters are referenced + // in the parameter types of the contextual signature. Now also infer from the return type. + applyToReturnTypes(instantiatedSignature, contextualSignature, (source, target) => { + inferTypes(inferences, source, target); + }); + // If the type parameters for which we produced candidates do not have any inferences yet, + // we adopt the new inference candidates and add the type parameters of the expression type + // to the set of inferred type parameters for the outer function return type. + if (!hasOverlappingInferences(context.inferences, inferences)) { + mergeInferences(context.inferences, inferences); + context.inferredTypeParameters = concatenate(context.inferredTypeParameters, uniqueTypeParameters); + return getOrCreateTypeFromSignature(instantiatedSignature); + } + } } + return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context)); } } - const returnType = getTypeOfAccessors(getSymbolOfNode(node)); - if (node.kind === SyntaxKind.GetAccessor) { - checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); - } } - checkSourceElement(node.body); - setNodeLinksForPrivateIdentifierScope(node); } + return type; + } - function checkMissingDeclaration(node: Node) { - checkDecorators(node); + function skippedGenericFunction(node: Node, checkMode: CheckMode) { + if (checkMode & CheckMode.Inferential) { + // We have skipped a generic function during inferential typing. Obtain the inference context and + // indicate this has occurred such that we know a second pass of inference is be needed. + const context = getInferenceContext(node)!; + context.flags |= InferenceFlags.SkippedGenericFunction; } + } - function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] { - return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, - getMinTypeArgumentCount(typeParameters), isInJSFile(node)); - } + function hasInferenceCandidates(info: InferenceInfo) { + return !!(info.candidates || info.contraCandidates); + } - function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean { - let typeArguments: Type[] | undefined; - let mapper: TypeMapper | undefined; - let result = true; - for (let i = 0; i < typeParameters.length; i++) { - const constraint = getConstraintOfTypeParameter(typeParameters[i]); - if (constraint) { - if (!typeArguments) { - typeArguments = getEffectiveTypeArguments(node, typeParameters); - mapper = createTypeMapper(typeParameters, typeArguments); - } - result = result && checkTypeAssignableTo( - typeArguments[i], - instantiateType(constraint, mapper), - node.typeArguments![i], - Diagnostics.Type_0_does_not_satisfy_the_constraint_1); - } + function hasOverlappingInferences(a: InferenceInfo[], b: InferenceInfo[]) { + for (let i = 0; i < a.length; i++) { + if (hasInferenceCandidates(a[i]) && hasInferenceCandidates(b[i])) { + return true; } - return result; } + return false; + } - function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) { - const type = getTypeFromTypeReference(node); - if (!isErrorType(type)) { - const symbol = getNodeLinks(node).resolvedSymbol; - if (symbol) { - return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || - (getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined); - } + function mergeInferences(target: InferenceInfo[], source: InferenceInfo[]) { + for (let i = 0; i < target.length; i++) { + if (!hasInferenceCandidates(target[i]) && hasInferenceCandidates(source[i])) { + target[i] = source[i]; } - return undefined; } + } - function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { - checkGrammarTypeArguments(node, node.typeArguments); - if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDotPos !== undefined && !isInJSFile(node) && !isInJSDoc(node)) { - grammarErrorAtPos(node, node.typeName.jsdocDotPos, 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + function getUniqueTypeParameters(context: InferenceContext, typeParameters: readonly TypeParameter[]): readonly TypeParameter[] { + const result: TypeParameter[] = []; + let oldTypeParameters: TypeParameter[] | undefined; + let newTypeParameters: TypeParameter[] | undefined; + for (const tp of typeParameters) { + const name = tp.symbol.escapedName; + if (hasTypeParameterByName(context.inferredTypeParameters, name) || hasTypeParameterByName(result, name)) { + const newName = getUniqueTypeParameterName(concatenate(context.inferredTypeParameters, result), name); + const symbol = createSymbol(SymbolFlags.TypeParameter, newName); + const newTypeParameter = createTypeParameter(symbol); + newTypeParameter.target = tp; + oldTypeParameters = append(oldTypeParameters, tp); + newTypeParameters = append(newTypeParameters, newTypeParameter); + result.push(newTypeParameter); } - forEach(node.typeArguments, checkSourceElement); - const type = getTypeFromTypeReference(node); - if (!isErrorType(type)) { - if (node.typeArguments && produceDiagnostics) { - const typeParameters = getTypeParametersForTypeReference(node); - if (typeParameters) { - checkTypeArgumentConstraints(node, typeParameters); - } - } - const symbol = getNodeLinks(node).resolvedSymbol; - if (symbol) { - if (some(symbol.declarations, d => isTypeDeclaration(d) && !!(d.flags & NodeFlags.Deprecated))) { - addDeprecatedSuggestion( - getDeprecatedSuggestionNode(node), - symbol.declarations!, - symbol.escapedName as string - ); - } - if (type.flags & TypeFlags.Enum && symbol.flags & SymbolFlags.EnumMember) { - error(node, Diagnostics.Enum_type_0_has_members_with_initializers_that_are_not_literals, typeToString(type)); - } - } + else { + result.push(tp); } } - - function getTypeArgumentConstraint(node: TypeNode): Type | undefined { - const typeReferenceNode = tryCast(node.parent, isTypeReferenceType); - if (!typeReferenceNode) return undefined; - const typeParameters = getTypeParametersForTypeReference(typeReferenceNode); - if (!typeParameters) return undefined; - const constraint = getConstraintOfTypeParameter(typeParameters[typeReferenceNode.typeArguments!.indexOf(node)]); - return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters))); + if (newTypeParameters) { + const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters); + for (const tp of newTypeParameters) { + tp.mapper = mapper; + } } + return result; + } - function checkTypeQuery(node: TypeQueryNode) { - getTypeFromTypeQueryNode(node); - } + function hasTypeParameterByName(typeParameters: readonly TypeParameter[] | undefined, name: __String) { + return some(typeParameters, tp => tp.symbol.escapedName === name); + } - function checkTypeLiteral(node: TypeLiteralNode) { - forEach(node.members, checkSourceElement); - if (produceDiagnostics) { - const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); - checkIndexConstraints(type, type.symbol); - checkTypeForDuplicateIndexSignatures(node); - checkObjectTypeForDuplicateDeclarations(node); + function getUniqueTypeParameterName(typeParameters: readonly TypeParameter[], baseName: __String) { + let len = (baseName as string).length; + while (len > 1 && (baseName as string).charCodeAt(len - 1) >= CharacterCodes._0 && (baseName as string).charCodeAt(len - 1) <= CharacterCodes._9) len--; + const s = (baseName as string).slice(0, len); + for (let index = 1; true; index++) { + const augmentedName = (s + index as __String); + if (!hasTypeParameterByName(typeParameters, augmentedName)) { + return augmentedName; } } + } - function checkArrayType(node: ArrayTypeNode) { - checkSourceElement(node.elementType); + function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) { + const signature = getSingleCallSignature(funcType); + if (signature && !signature.typeParameters) { + return getReturnTypeOfSignature(signature); } + } - function checkTupleType(node: TupleTypeNode) { - const elementTypes = node.elements; - let seenOptionalElement = false; - let seenRestElement = false; - const hasNamedElement = some(elementTypes, isNamedTupleMember); - for (const e of elementTypes) { - if (e.kind !== SyntaxKind.NamedTupleMember && hasNamedElement) { - grammarErrorOnNode(e, Diagnostics.Tuple_members_must_all_have_names_or_all_not_have_names); - break; - } - const flags = getTupleElementFlags(e); - if (flags & ElementFlags.Variadic) { - const type = getTypeFromTypeNode((e as RestTypeNode | NamedTupleMember).type); - if (!isArrayLikeType(type)) { - error(e, Diagnostics.A_rest_element_type_must_be_an_array_type); - break; - } - if (isArrayType(type) || isTupleType(type) && type.target.combinedFlags & ElementFlags.Rest) { - seenRestElement = true; - } - } - else if (flags & ElementFlags.Rest) { - if (seenRestElement) { - grammarErrorOnNode(e, Diagnostics.A_rest_element_cannot_follow_another_rest_element); - break; - } - seenRestElement = true; - } - else if (flags & ElementFlags.Optional) { - if (seenRestElement) { - grammarErrorOnNode(e, Diagnostics.An_optional_element_cannot_follow_a_rest_element); - break; - } - seenOptionalElement = true; - } - else if (seenOptionalElement) { - grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); - break; - } - } - forEach(node.elements, checkSourceElement); - getTypeFromTypeNode(node); - } + function getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr: CallChain) { + const funcType = checkExpression(expr.expression); + const nonOptionalType = getOptionalExpressionType(funcType, expr.expression); + const returnType = getReturnTypeOfSingleNonGenericCallSignature(funcType); + return returnType && propagateOptionalTypeMarker(returnType, expr, nonOptionalType !== funcType); + } - function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) { - forEach(node.types, checkSourceElement); - getTypeFromTypeNode(node); - } + /** + * Returns the type of an expression. Unlike checkExpression, this function is simply concerned + * with computing the type and may not fully check all contained sub-expressions for errors. + */ + function getTypeOfExpression(node: Expression) { + // Don't bother caching types that require no flow analysis and are quick to compute. + const quickType = getQuickTypeOfExpression(node); + if (quickType) { + return quickType; + } + // If a type has been cached for the node, return it. + if (node.flags & NodeFlags.TypeCached && flowTypeCache) { + const cachedType = flowTypeCache[getNodeId(node)]; + if (cachedType) { + return cachedType; + } + } + const startInvocationCount = flowInvocationCount; + const type = checkExpression(node); + // If control flow analysis was required to determine the type, it is worth caching. + if (flowInvocationCount !== startInvocationCount) { + const cache = flowTypeCache || (flowTypeCache = []); + cache[getNodeId(node)] = type; + setNodeFlags(node, node.flags | NodeFlags.TypeCached); + } + return type; + } - function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) { - if (!(type.flags & TypeFlags.IndexedAccess)) { - return type; + function getQuickTypeOfExpression(node: Expression) { + let expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + if (isJSDocTypeAssertion(expr)) { + const type = getJSDocTypeAssertionType(expr); + if (!isConstTypeReference(type)) { + return getTypeFromTypeNode(type); } - // Check if the index type is assignable to 'keyof T' for the object type. - const objectType = (type as IndexedAccessType).objectType; - const indexType = (type as IndexedAccessType).indexType; - if (isTypeAssignableTo(indexType, getIndexType(objectType, /*stringsOnly*/ false))) { - if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && - getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.IncludeReadonly) { - error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); - } - return type; - } - // Check if we're indexing with a numeric type and if either object or index types - // is a generic type with a constraint that has a numeric index signature. - const apparentObjectType = getApparentType(objectType); - if (getIndexInfoOfType(apparentObjectType, numberType) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { + } + expr = skipParentheses(node); + // Optimize for the common case of a call to a function with a single non-generic call + // signature where we can just fetch the return type without checking the arguments. + if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { + const type = isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : + getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); + if (type) { return type; } - if (isGenericObjectType(objectType)) { - const propertyName = getPropertyNameFromIndex(indexType, accessNode); - if (propertyName) { - const propertySymbol = forEachType(apparentObjectType, t => getPropertyOfType(t, propertyName)); - if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { - error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName)); - return errorType; - } - } - } - error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); - return errorType; } + else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { + return getTypeFromTypeNode((expr as TypeAssertion).type); + } + else if (node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral || + node.kind === SyntaxKind.TrueKeyword || node.kind === SyntaxKind.FalseKeyword) { + return checkExpression(node); + } + return undefined; + } - function checkIndexedAccessType(node: IndexedAccessTypeNode) { - checkSourceElement(node.objectType); - checkSourceElement(node.indexType); - checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); + /** + * Returns the type of an expression. Unlike checkExpression, this function is simply concerned + * with computing the type and may not fully check all contained sub-expressions for errors. + * It is intended for uses where you know there is no contextual type, + * and requesting the contextual type might cause a circularity or other bad behaviour. + * It sets the contextual type of the node to any before calling getTypeOfExpression. + */ + function getContextFreeTypeOfExpression(node: Expression) { + const links = getNodeLinks(node); + if (links.contextFreeType) { + return links.contextFreeType; + } + const saveContextualType = node.contextualType; + node.contextualType = anyType; + try { + const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); + return type; } + finally { + // In the event our operation is canceled or some other exception occurs, reset the contextual type + // so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer + // may hold onto the checker that created it. + node.contextualType = saveContextualType; + } + } - function checkMappedType(node: MappedTypeNode) { - checkGrammarMappedType(node); - checkSourceElement(node.typeParameter); - checkSourceElement(node.nameType); - checkSourceElement(node.type); + function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, forceTuple?: boolean): Type { + tracing?.push(tracing.Phase.Check, "checkExpression", { kind: node.kind, pos: node.pos, end: node.end }); + const saveCurrentNode = currentNode; + currentNode = node; + instantiationCount = 0; + const uninstantiatedType = checkExpressionWorker(node, checkMode, forceTuple); + const type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); + if (isConstEnumObjectType(type)) { + checkConstEnumAccess(node, type); + } + currentNode = saveCurrentNode; + tracing?.pop(); + return type; + } - if (!node.type) { - reportImplicitAny(node, anyType); - } + function checkConstEnumAccess(node: Expression | QualifiedName, type: Type) { + // enum object type for const enums are only permitted in: + // - 'left' in property access + // - 'object' in indexed access + // - target in rhs of import statement + const ok = + (node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent as PropertyAccessExpression).expression === node) || + (node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent as ElementAccessExpression).expression === node) || + ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(node as Identifier) || + (node.parent.kind === SyntaxKind.TypeQuery && (node.parent as TypeQueryNode).exprName === node)) || + (node.parent.kind === SyntaxKind.ExportSpecifier); // We allow reexporting const enums - const type = getTypeFromMappedTypeNode(node) as MappedType; - const nameType = getNameTypeFromMappedType(type); - if (nameType) { - checkTypeAssignableTo(nameType, keyofConstraintType, node.nameType); - } - else { - const constraintType = getConstraintTypeFromMappedType(type); - checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); - } + if (!ok) { + error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query); } - function checkGrammarMappedType(node: MappedTypeNode) { - if (node.members?.length) { - return grammarErrorOnNode(node.members[0], Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); + if (compilerOptions.isolatedModules) { + Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum)); + const constEnumDeclaration = type.symbol.valueDeclaration as EnumDeclaration; + if (constEnumDeclaration.flags & NodeFlags.Ambient) { + error(node, Diagnostics.Cannot_access_ambient_const_enums_when_the_isolatedModules_flag_is_provided); } } + } - function checkThisType(node: ThisTypeNode) { - getTypeFromThisTypeNode(node); + function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { + if (hasJSDocNodes(node) && isJSDocTypeAssertion(node)) { + const type = getJSDocTypeAssertionType(node); + return checkAssertionWorker(type, type, node.expression, checkMode); } + return checkExpression(node.expression, checkMode); + } - function checkTypeOperator(node: TypeOperatorNode) { - checkGrammarTypeOperatorNode(node); - checkSourceElement(node.type); + function checkExpressionWorker(node: Expression | QualifiedName, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { + const kind = node.kind; + if (cancellationToken) { + // Only bother checking on a few construct kinds. We don't want to be excessively + // hitting the cancellation token on every node we check. + switch (kind) { + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + cancellationToken.throwIfCancellationRequested(); + } } + switch (kind) { + case SyntaxKind.Identifier: + return checkIdentifier(node as Identifier, checkMode); + case SyntaxKind.PrivateIdentifier: + return checkPrivateIdentifierExpression(node as PrivateIdentifier); + case SyntaxKind.ThisKeyword: + return checkThisExpression(node); + case SyntaxKind.SuperKeyword: + return checkSuperExpression(node); + case SyntaxKind.NullKeyword: + return nullWideningType; + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.StringLiteral: + return getFreshTypeOfLiteralType(getStringLiteralType((node as StringLiteralLike).text)); + case SyntaxKind.NumericLiteral: + checkGrammarNumericLiteral(node as NumericLiteral); + return getFreshTypeOfLiteralType(getNumberLiteralType(+(node as NumericLiteral).text)); + case SyntaxKind.BigIntLiteral: + checkGrammarBigIntLiteral(node as BigIntLiteral); + return getFreshTypeOfLiteralType(getBigIntLiteralType({ + negative: false, + base10Value: parsePseudoBigInt((node as BigIntLiteral).text) + })); + case SyntaxKind.TrueKeyword: + return trueType; + case SyntaxKind.FalseKeyword: + return falseType; + case SyntaxKind.TemplateExpression: + return checkTemplateExpression(node as TemplateExpression); + case SyntaxKind.RegularExpressionLiteral: + return globalRegExpType; + case SyntaxKind.ArrayLiteralExpression: + return checkArrayLiteral(node as ArrayLiteralExpression, checkMode, forceTuple); + case SyntaxKind.ObjectLiteralExpression: + return checkObjectLiteral(node as ObjectLiteralExpression, checkMode); + case SyntaxKind.PropertyAccessExpression: + return checkPropertyAccessExpression(node as PropertyAccessExpression, checkMode); + case SyntaxKind.QualifiedName: + return checkQualifiedName(node as QualifiedName, checkMode); + case SyntaxKind.ElementAccessExpression: + return checkIndexedAccess(node as ElementAccessExpression, checkMode); + case SyntaxKind.CallExpression: + if ((node as CallExpression).expression.kind === SyntaxKind.ImportKeyword) { + return checkImportCallExpression(node as ImportCall); + } + // falls through + case SyntaxKind.NewExpression: + return checkCallExpression(node as CallExpression, checkMode); + case SyntaxKind.TaggedTemplateExpression: + return checkTaggedTemplateExpression(node as TaggedTemplateExpression); + case SyntaxKind.ParenthesizedExpression: + return checkParenthesizedExpression(node as ParenthesizedExpression, checkMode); + case SyntaxKind.ClassExpression: + return checkClassExpression(node as ClassExpression); + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return checkFunctionExpressionOrObjectLiteralMethod(node as FunctionExpression | ArrowFunction, checkMode); + case SyntaxKind.TypeOfExpression: + return checkTypeOfExpression(node as TypeOfExpression); + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return checkAssertion(node as AssertionExpression); + case SyntaxKind.NonNullExpression: + return checkNonNullAssertion(node as NonNullExpression); + case SyntaxKind.MetaProperty: + return checkMetaProperty(node as MetaProperty); + case SyntaxKind.DeleteExpression: + return checkDeleteExpression(node as DeleteExpression); + case SyntaxKind.VoidExpression: + return checkVoidExpression(node as VoidExpression); + case SyntaxKind.AwaitExpression: + return checkAwaitExpression(node as AwaitExpression); + case SyntaxKind.PrefixUnaryExpression: + return checkPrefixUnaryExpression(node as PrefixUnaryExpression); + case SyntaxKind.PostfixUnaryExpression: + return checkPostfixUnaryExpression(node as PostfixUnaryExpression); + case SyntaxKind.BinaryExpression: + return checkBinaryExpression(node as BinaryExpression, checkMode); + case SyntaxKind.ConditionalExpression: + return checkConditionalExpression(node as ConditionalExpression, checkMode); + case SyntaxKind.SpreadElement: + return checkSpreadExpression(node as SpreadElement, checkMode); + case SyntaxKind.OmittedExpression: + return undefinedWideningType; + case SyntaxKind.YieldExpression: + return checkYieldExpression(node as YieldExpression); + case SyntaxKind.SyntheticExpression: + return checkSyntheticExpression(node as SyntheticExpression); + case SyntaxKind.JsxExpression: + return checkJsxExpression(node as JsxExpression, checkMode); + case SyntaxKind.JsxElement: + return checkJsxElement(node as JsxElement, checkMode); + case SyntaxKind.JsxSelfClosingElement: + return checkJsxSelfClosingElement(node as JsxSelfClosingElement, checkMode); + case SyntaxKind.JsxFragment: + return checkJsxFragment(node as JsxFragment); + case SyntaxKind.JsxAttributes: + return checkJsxAttributes(node as JsxAttributes, checkMode); + case SyntaxKind.JsxOpeningElement: + Debug.fail("Shouldn't ever directly check a JsxOpeningElement"); + } + return errorType; + } + + // DECLARATION AND STATEMENT TYPE CHECKING - function checkConditionalType(node: ConditionalTypeNode) { - forEachChild(node, checkSourceElement); + function checkTypeParameter(node: TypeParameterDeclaration) { + // Grammar Checking + if (node.expression) { + grammarErrorOnFirstToken(node.expression, Diagnostics.Type_expected); } - function checkInferType(node: InferTypeNode) { - if (!findAncestor(node, n => n.parent && n.parent.kind === SyntaxKind.ConditionalType && (n.parent as ConditionalTypeNode).extendsType === n)) { - grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type); - } - checkSourceElement(node.typeParameter); - registerForUnusedIdentifiersCheck(node); + checkSourceElement(node.constraint); + checkSourceElement(node.default); + const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)); + // Resolve base constraint to reveal circularity errors + getBaseConstraintOfType(typeParameter); + if (!hasNonCircularTypeParameterDefault(typeParameter)) { + error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter)); + } + const constraintType = getConstraintOfTypeParameter(typeParameter); + const defaultType = getDefaultFromTypeParameter(typeParameter); + if (constraintType && defaultType) { + checkTypeAssignableTo(defaultType, getTypeWithThisArgument(instantiateType(constraintType, makeUnaryTypeMapper(typeParameter, defaultType)), defaultType), node.default, Diagnostics.Type_0_does_not_satisfy_the_constraint_1); + } + if (produceDiagnostics) { + checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0); } + } + + function checkParameter(node: ParameterDeclaration) { + // Grammar checking + // It is a SyntaxError if the Identifier "eval" or the Identifier "arguments" occurs as the + // Identifier in a PropertySetParameterList of a PropertyAssignment that is contained in strict code + // or if its FunctionBody is strict code(11.1.5). + checkGrammarDecoratorsAndModifiers(node); - function checkTemplateLiteralType(node: TemplateLiteralTypeNode) { - for (const span of node.templateSpans) { - checkSourceElement(span.type); - const type = getTypeFromTypeNode(span.type); - checkTypeAssignableTo(type, templateConstraintType, span.type); + checkVariableLikeDeclaration(node); + const func = getContainingFunction(node)!; + if (hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) { + if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) { + error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); + } + if (func.kind === SyntaxKind.Constructor && isIdentifier(node.name) && node.name.escapedText === "constructor") { + error(node.name, Diagnostics.constructor_cannot_be_used_as_a_parameter_property_name); } - getTypeFromTypeNode(node); } - - function checkImportType(node: ImportTypeNode) { - checkSourceElement(node.argument); - getTypeFromTypeNode(node); + if (node.questionToken && isBindingPattern(node.name) && (func as FunctionLikeDeclaration).body) { + error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature); } - - function checkNamedTupleMember(node: NamedTupleMember) { - if (node.dotDotDotToken && node.questionToken) { - grammarErrorOnNode(node, Diagnostics.A_tuple_member_cannot_be_both_optional_and_rest); + if (node.name && isIdentifier(node.name) && (node.name.escapedText === "this" || node.name.escapedText === "new")) { + if (func.parameters.indexOf(node) !== 0) { + error(node, Diagnostics.A_0_parameter_must_be_the_first_parameter, node.name.escapedText as string); + } + if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) { + error(node, Diagnostics.A_constructor_cannot_have_a_this_parameter); } - if (node.type.kind === SyntaxKind.OptionalType) { - grammarErrorOnNode(node.type, Diagnostics.A_labeled_tuple_element_is_declared_as_optional_with_a_question_mark_after_the_name_and_before_the_colon_rather_than_after_the_type); + if (func.kind === SyntaxKind.ArrowFunction) { + error(node, Diagnostics.An_arrow_function_cannot_have_a_this_parameter); } - if (node.type.kind === SyntaxKind.RestType) { - grammarErrorOnNode(node.type, Diagnostics.A_labeled_tuple_element_is_declared_as_rest_with_a_before_the_name_rather_than_before_the_type); + if (func.kind === SyntaxKind.GetAccessor || func.kind === SyntaxKind.SetAccessor) { + error(node, Diagnostics.get_and_set_accessors_cannot_declare_this_parameters); } - checkSourceElement(node.type); - getTypeFromTypeNode(node); } - function isPrivateWithinAmbient(node: Node): boolean { - return (hasEffectiveModifier(node, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(node)) && !!(node.flags & NodeFlags.Ambient); + // Only check rest parameter type if it's not a binding pattern. Since binding patterns are + // not allowed in a rest parameter, we already have an error from checkGrammarParameterList. + if (node.dotDotDotToken && !isBindingPattern(node.name) && !isTypeAssignableTo(getReducedType(getTypeOfSymbol(node.symbol)), anyReadonlyArrayType)) { + error(node, Diagnostics.A_rest_parameter_must_be_of_an_array_type); } + } - function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags { - let flags = getCombinedModifierFlags(n); + function checkTypePredicate(node: TypePredicateNode): void { + const parent = getTypePredicateParent(node); + if (!parent) { + // The parent must not be valid. + error(node, Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); + return; + } - // children of classes (even ambient classes) should not be marked as ambient or export - // because those flags have no useful semantics there. - if (n.parent.kind !== SyntaxKind.InterfaceDeclaration && - n.parent.kind !== SyntaxKind.ClassDeclaration && - n.parent.kind !== SyntaxKind.ClassExpression && - n.flags & NodeFlags.Ambient) { - if (!(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) { - // It is nested in an ambient context, which means it is automatically exported - flags |= ModifierFlags.Export; + const signature = getSignatureFromDeclaration(parent); + const typePredicate = getTypePredicateOfSignature(signature); + if (!typePredicate) { + return; + } + + checkSourceElement(node.type); + + const { parameterName } = node; + if (typePredicate.kind === TypePredicateKind.This || typePredicate.kind === TypePredicateKind.AssertsThis) { + getTypeFromThisTypeNode(parameterName as ThisTypeNode); + } + else { + if (typePredicate.parameterIndex >= 0) { + if (signatureHasRestParameter(signature) && typePredicate.parameterIndex === signature.parameters.length - 1) { + error(parameterName, Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter); + } + else { + if (typePredicate.type) { + const leadingError = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.A_type_predicate_s_type_must_be_assignable_to_its_parameter_s_type); + checkTypeAssignableTo(typePredicate.type, + getTypeOfSymbol(signature.parameters[typePredicate.parameterIndex]), + node.type, + /*headMessage*/ undefined, + leadingError); + } + } + } + else if (parameterName) { + let hasReportedError = false; + for (const { name } of parent.parameters) { + if (isBindingPattern(name) && + checkIfTypePredicateVariableIsDeclaredInBindingPattern(name, parameterName, typePredicate.parameterName)) { + hasReportedError = true; + break; + } + } + if (!hasReportedError) { + error(node.parameterName, Diagnostics.Cannot_find_parameter_0, typePredicate.parameterName); } - flags |= ModifierFlags.Ambient; } + } + } - return flags & flagsToCheck; + function getTypePredicateParent(node: Node): SignatureDeclaration | undefined { + switch (node.parent.kind) { + case SyntaxKind.ArrowFunction: + case SyntaxKind.CallSignature: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionType: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + const parent = node.parent as SignatureDeclaration; + if (node === parent.type) { + return parent; + } } + } - function checkFunctionOrConstructorSymbol(symbol: Symbol): void { - if (!produceDiagnostics) { - return; + function checkIfTypePredicateVariableIsDeclaredInBindingPattern( + pattern: BindingPattern, + predicateVariableNode: Node, + predicateVariableName: string) { + for (const element of pattern.elements) { + if (isOmittedExpression(element)) { + continue; } - function getCanonicalOverload(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined): Declaration { - // Consider the canonical set of flags to be the flags of the bodyDeclaration or the first declaration - // Error on all deviations from this canonical set of flags - // The caveat is that if some overloads are defined in lib.d.ts, we don't want to - // report the errors on those. To achieve this, we will say that the implementation is - // the canonical signature only if it is in the same container as the first overload - const implementationSharesContainerWithFirstOverload = implementation !== undefined && implementation.parent === overloads[0].parent; - return implementationSharesContainerWithFirstOverload ? implementation : overloads[0]; + const name = element.name; + if (name.kind === SyntaxKind.Identifier && name.escapedText === predicateVariableName) { + error(predicateVariableNode, + Diagnostics.A_type_predicate_cannot_reference_element_0_in_a_binding_pattern, + predicateVariableName); + return true; } - - function checkFlagAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, flagsToCheck: ModifierFlags, someOverloadFlags: ModifierFlags, allOverloadFlags: ModifierFlags): void { - // Error if some overloads have a flag that is not shared by all overloads. To find the - // deviations, we XOR someOverloadFlags with allOverloadFlags - const someButNotAllOverloadFlags = someOverloadFlags ^ allOverloadFlags; - if (someButNotAllOverloadFlags !== 0) { - const canonicalFlags = getEffectiveDeclarationFlags(getCanonicalOverload(overloads, implementation), flagsToCheck); - forEach(overloads, o => { - const deviation = getEffectiveDeclarationFlags(o, flagsToCheck) ^ canonicalFlags; - if (deviation & ModifierFlags.Export) { - error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_exported_or_non_exported); - } - else if (deviation & ModifierFlags.Ambient) { - error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_ambient_or_non_ambient); - } - else if (deviation & (ModifierFlags.Private | ModifierFlags.Protected)) { - error(getNameOfDeclaration(o) || o, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected); - } - else if (deviation & ModifierFlags.Abstract) { - error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_abstract_or_non_abstract); - } - }); + else if (name.kind === SyntaxKind.ArrayBindingPattern || name.kind === SyntaxKind.ObjectBindingPattern) { + if (checkIfTypePredicateVariableIsDeclaredInBindingPattern( + name, + predicateVariableNode, + predicateVariableName)) { + return true; } } + } + } - function checkQuestionTokenAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, someHaveQuestionToken: boolean, allHaveQuestionToken: boolean): void { - if (someHaveQuestionToken !== allHaveQuestionToken) { - const canonicalHasQuestionToken = hasQuestionToken(getCanonicalOverload(overloads, implementation)); - forEach(overloads, o => { - const deviation = hasQuestionToken(o) !== canonicalHasQuestionToken; - if (deviation) { - error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_optional_or_required); - } - }); - } + function checkSignatureDeclaration(node: SignatureDeclaration) { + // Grammar checking + if (node.kind === SyntaxKind.IndexSignature) { + checkGrammarIndexSignature(node as SignatureDeclaration); + } + // TODO (yuisu): Remove this check in else-if when SyntaxKind.Construct is moved and ambient context is handled + else if (node.kind === SyntaxKind.FunctionType || node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.ConstructorType || + node.kind === SyntaxKind.CallSignature || node.kind === SyntaxKind.Constructor || + node.kind === SyntaxKind.ConstructSignature) { + checkGrammarFunctionLikeDeclaration(node as FunctionLikeDeclaration); + } + + const functionFlags = getFunctionFlags(node as FunctionLikeDeclaration); + if (!(functionFlags & FunctionFlags.Invalid)) { + // Async generators prior to ESNext require the __await and __asyncGenerator helpers + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator && languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncGeneratorIncludes); } - const flagsToCheck: ModifierFlags = ModifierFlags.Export | ModifierFlags.Ambient | ModifierFlags.Private | ModifierFlags.Protected | ModifierFlags.Abstract; - let someNodeFlags: ModifierFlags = ModifierFlags.None; - let allNodeFlags = flagsToCheck; - let someHaveQuestionToken = false; - let allHaveQuestionToken = true; - let hasOverloads = false; - let bodyDeclaration: FunctionLikeDeclaration | undefined; - let lastSeenNonAmbientDeclaration: FunctionLikeDeclaration | undefined; - let previousDeclaration: SignatureDeclaration | undefined; + // Async functions prior to ES2017 require the __awaiter helper + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async && languageVersion < ScriptTarget.ES2017) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Awaiter); + } - const declarations = symbol.declarations; - const isConstructor = (symbol.flags & SymbolFlags.Constructor) !== 0; + // Generator functions, Async functions, and Async Generator functions prior to + // ES2015 require the __generator helper + if ((functionFlags & FunctionFlags.AsyncGenerator) !== FunctionFlags.Normal && languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Generator); + } + } - function reportImplementationExpectedError(node: SignatureDeclaration): void { - if (node.name && nodeIsMissing(node.name)) { - return; + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + + forEach(node.parameters, checkParameter); + + // TODO(rbuckton): Should we start checking JSDoc types? + if (node.type) { + checkSourceElement(node.type); + } + + if (produceDiagnostics) { + checkCollisionWithArgumentsInGeneratedCode(node); + const returnTypeNode = getEffectiveReturnTypeNode(node); + if (noImplicitAny && !returnTypeNode) { + switch (node.kind) { + case SyntaxKind.ConstructSignature: + error(node, Diagnostics.Construct_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); + break; + case SyntaxKind.CallSignature: + error(node, Diagnostics.Call_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); + break; } + } - let seen = false; - const subsequentNode = forEachChild(node.parent, c => { - if (seen) { - return c; + if (returnTypeNode) { + const functionFlags = getFunctionFlags(node as FunctionDeclaration); + if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) { + const returnType = getTypeFromTypeNode(returnTypeNode); + if (returnType === voidType) { + error(returnTypeNode, Diagnostics.A_generator_cannot_have_a_void_type_annotation); } else { - seen = c === node; - } - }); - // We may be here because of some extra nodes between overloads that could not be parsed into a valid node. - // In this case the subsequent node is not really consecutive (.pos !== node.end), and we must ignore it here. - if (subsequentNode && subsequentNode.pos === node.end) { - if (subsequentNode.kind === node.kind) { - const errorNode: Node = (subsequentNode as FunctionLikeDeclaration).name || subsequentNode; - const subsequentName = (subsequentNode as FunctionLikeDeclaration).name; - if (node.name && subsequentName && ( - // both are private identifiers - isPrivateIdentifier(node.name) && isPrivateIdentifier(subsequentName) && node.name.escapedText === subsequentName.escapedText || - // Both are computed property names - // TODO: GH#17345: These are methods, so handle computed name case. (`Always allowing computed property names is *not* the correct behavior!) - isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) || - // Both are literal property names that are the same. - isPropertyNameLiteral(node.name) && isPropertyNameLiteral(subsequentName) && - getEscapedTextOfIdentifierOrLiteral(node.name) === getEscapedTextOfIdentifierOrLiteral(subsequentName) - )) { - const reportError = - (node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature) && - isStatic(node) !== isStatic(subsequentNode); - // we can get here in two cases - // 1. mixed static and instance class members - // 2. something with the same name was defined before the set of overloads that prevents them from merging - // here we'll report error only for the first case since for second we should already report error in binder - if (reportError) { - const diagnostic = isStatic(node) ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static; - error(errorNode, diagnostic); - } - return; - } - if (nodeIsPresent((subsequentNode as FunctionLikeDeclaration).body)) { - error(errorNode, Diagnostics.Function_implementation_name_must_be_0, declarationNameToString(node.name)); - return; - } + // Naively, one could check that Generator is assignable to the return type annotation. + // However, that would not catch the error in the following case. + // + // interface BadGenerator extends Iterable, Iterator { } + // function* g(): BadGenerator { } // Iterable and Iterator have different types! + // + const generatorYieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType; + const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType; + const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType; + const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async)); + checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeNode); } } - const errorNode: Node = node.name || node; - if (isConstructor) { - error(errorNode, Diagnostics.Constructor_implementation_is_missing); - } - else { - // Report different errors regarding non-consecutive blocks of declarations depending on whether - // the node in question is abstract. - if (hasSyntacticModifier(node, ModifierFlags.Abstract)) { - error(errorNode, Diagnostics.All_declarations_of_an_abstract_method_must_be_consecutive); - } - else { - error(errorNode, Diagnostics.Function_implementation_is_missing_or_not_immediately_following_the_declaration); - } + else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { + checkAsyncFunctionReturnType(node as FunctionLikeDeclaration, returnTypeNode); } } + if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) { + registerForUnusedIdentifiersCheck(node); + } + } + } - let duplicateFunctionDeclaration = false; - let multipleConstructorImplementation = false; - let hasNonAmbientClass = false; - const functionDeclarations = [] as Declaration[]; - if (declarations) { - for (const current of declarations) { - const node = current as SignatureDeclaration | ClassDeclaration | ClassExpression; - const inAmbientContext = node.flags & NodeFlags.Ambient; - const inAmbientContextOrInterface = node.parent && (node.parent.kind === SyntaxKind.InterfaceDeclaration || node.parent.kind === SyntaxKind.TypeLiteral) || inAmbientContext; - if (inAmbientContextOrInterface) { - // check if declarations are consecutive only if they are non-ambient - // 1. ambient declarations can be interleaved - // i.e. this is legal - // declare function foo(); - // declare function bar(); - // declare function foo(); - // 2. mixing ambient and non-ambient declarations is a separate error that will be reported - do not want to report an extra one - previousDeclaration = undefined; - } - - if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && !inAmbientContext) { - hasNonAmbientClass = true; - } - - if (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature || node.kind === SyntaxKind.Constructor) { - functionDeclarations.push(node); - const currentNodeFlags = getEffectiveDeclarationFlags(node, flagsToCheck); - someNodeFlags |= currentNodeFlags; - allNodeFlags &= currentNodeFlags; - someHaveQuestionToken = someHaveQuestionToken || hasQuestionToken(node); - allHaveQuestionToken = allHaveQuestionToken && hasQuestionToken(node); - const bodyIsPresent = nodeIsPresent((node as FunctionLikeDeclaration).body); - - if (bodyIsPresent && bodyDeclaration) { - if (isConstructor) { - multipleConstructorImplementation = true; - } - else { - duplicateFunctionDeclaration = true; - } - } - else if (previousDeclaration?.parent === node.parent && previousDeclaration.end !== node.pos) { - reportImplementationExpectedError(previousDeclaration); - } - - if (bodyIsPresent) { - if (!bodyDeclaration) { - bodyDeclaration = node as FunctionLikeDeclaration; - } - } - else { - hasOverloads = true; - } - - previousDeclaration = node; - - if (!inAmbientContextOrInterface) { - lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; - } + function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { + const instanceNames = new Map<__String, DeclarationMeaning>(); + const staticNames = new Map<__String, DeclarationMeaning>(); + // instance and static private identifiers share the same scope + const privateIdentifiers = new Map<__String, DeclarationMeaning>(); + for (const member of node.members) { + if (member.kind === SyntaxKind.Constructor) { + for (const param of (member as ConstructorDeclaration).parameters) { + if (isParameterPropertyDeclaration(param, member) && !isBindingPattern(param.name)) { + addName(instanceNames, param.name, param.name.escapedText, DeclarationMeaning.GetOrSetAccessor); } } } + else { + const isStaticMember = isStatic(member); + const name = member.name; + if (!name) { + continue; + } + const isPrivate = isPrivateIdentifier(name); + const privateStaticFlags = isPrivate && isStaticMember ? DeclarationMeaning.PrivateStatic : 0; + const names = + isPrivate ? privateIdentifiers : + isStaticMember ? staticNames : + instanceNames; - if (multipleConstructorImplementation) { - forEach(functionDeclarations, declaration => { - error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed); - }); - } + const memberName = name && getPropertyNameForPropertyNameNode(name); + if (memberName) { + switch (member.kind) { + case SyntaxKind.GetAccessor: + addName(names, name, memberName, DeclarationMeaning.GetAccessor | privateStaticFlags); + break; - if (duplicateFunctionDeclaration) { - forEach(functionDeclarations, declaration => { - error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_function_implementation); - }); - } + case SyntaxKind.SetAccessor: + addName(names, name, memberName, DeclarationMeaning.SetAccessor | privateStaticFlags); + break; - if (hasNonAmbientClass && !isConstructor && symbol.flags & SymbolFlags.Function && declarations) { - const relatedDiagnostics = filter(declarations, d => d.kind === SyntaxKind.ClassDeclaration) - .map(d => createDiagnosticForNode(d, Diagnostics.Consider_adding_a_declare_modifier_to_this_class)); + case SyntaxKind.PropertyDeclaration: + addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor | privateStaticFlags); + break; - forEach(declarations, declaration => { - const diagnostic = declaration.kind === SyntaxKind.ClassDeclaration - ? Diagnostics.Class_declaration_cannot_implement_overload_list_for_0 - : declaration.kind === SyntaxKind.FunctionDeclaration - ? Diagnostics.Function_with_bodies_can_only_merge_with_classes_that_are_ambient - : undefined; - if (diagnostic) { - addRelatedInfo( - error(getNameOfDeclaration(declaration) || declaration, diagnostic, symbolName(symbol)), - ...relatedDiagnostics - ); + case SyntaxKind.MethodDeclaration: + addName(names, name, memberName, DeclarationMeaning.Method | privateStaticFlags); + break; } - }); - } - - // Abstract methods can't have an implementation -- in particular, they don't need one. - if (lastSeenNonAmbientDeclaration && !lastSeenNonAmbientDeclaration.body && - !hasSyntacticModifier(lastSeenNonAmbientDeclaration, ModifierFlags.Abstract) && !lastSeenNonAmbientDeclaration.questionToken) { - reportImplementationExpectedError(lastSeenNonAmbientDeclaration); + } } + } - if (hasOverloads) { - if (declarations) { - checkFlagAgreementBetweenOverloads(declarations, bodyDeclaration, flagsToCheck, someNodeFlags, allNodeFlags); - checkQuestionTokenAgreementBetweenOverloads(declarations, bodyDeclaration, someHaveQuestionToken, allHaveQuestionToken); + function addName(names: UnderscoreEscapedMap, location: Node, name: __String, meaning: DeclarationMeaning) { + const prev = names.get(name); + if (prev) { + // For private identifiers, do not allow mixing of static and instance members with the same name + if ((prev & DeclarationMeaning.PrivateStatic) !== (meaning & DeclarationMeaning.PrivateStatic)) { + error(location, Diagnostics.Duplicate_identifier_0_Static_and_instance_elements_cannot_share_the_same_private_name, getTextOfNode(location)); } - - if (bodyDeclaration) { - const signatures = getSignaturesOfSymbol(symbol); - const bodySignature = getSignatureFromDeclaration(bodyDeclaration); - for (const signature of signatures) { - if (!isImplementationCompatibleWithOverload(bodySignature, signature)) { - addRelatedInfo( - error(signature.declaration, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), - createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here) - ); - break; + else { + const prevIsMethod = !!(prev & DeclarationMeaning.Method); + const isMethod = !!(meaning & DeclarationMeaning.Method); + if (prevIsMethod || isMethod) { + if (prevIsMethod !== isMethod) { + error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); } + // If this is a method/method duplication is might be an overload, so this will be handled when overloads are considered + } + else if (prev & meaning & ~DeclarationMeaning.PrivateStatic) { + error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); + } + else { + names.set(name, prev | meaning); } } } + else { + names.set(name, meaning); + } } + } - function checkExportsOnMergedDeclarations(node: Declaration): void { - if (!produceDiagnostics) { - return; + /** + * Static members being set on a constructor function may conflict with built-in properties + * of Function. Esp. in ECMAScript 5 there are non-configurable and non-writable + * built-in properties. This check issues a transpile error when a class has a static + * member with the same name as a non-writable built-in property. + * + * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.3 + * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.5 + * @see http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-function-constructor + * @see http://www.ecma-international.org/ecma-262/6.0/#sec-function-instances + */ + function checkClassForStaticPropertyNameConflicts(node: ClassLikeDeclaration) { + for (const member of node.members) { + const memberNameNode = member.name; + const isStaticMember = isStatic(member); + if (isStaticMember && memberNameNode) { + const memberName = getPropertyNameForPropertyNameNode(memberNameNode); + switch (memberName) { + case "name": + case "length": + case "caller": + case "arguments": + case "prototype": + const message = Diagnostics.Static_property_0_conflicts_with_built_in_property_Function_0_of_constructor_function_1; + const className = getNameOfSymbolAsWritten(getSymbolOfNode(node)); + error(memberNameNode, message, memberName, className); + break; + } } + } + } + + function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) { + const names = new Map(); + for (const member of node.members) { + if (member.kind === SyntaxKind.PropertySignature) { + let memberName: string; + const name = member.name!; + switch (name.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + memberName = name.text; + break; + case SyntaxKind.Identifier: + memberName = idText(name); + break; + default: + continue; + } - // if localSymbol is defined on node then node itself is exported - check is required - let symbol = node.localSymbol; - if (!symbol) { - // local symbol is undefined => this declaration is non-exported. - // however symbol might contain other declarations that are exported - symbol = getSymbolOfNode(node)!; - if (!symbol.exportSymbol) { - // this is a pure local symbol (all declarations are non-exported) - no need to check anything - return; + if (names.get(memberName)) { + error(getNameOfDeclaration(member.symbol.valueDeclaration), Diagnostics.Duplicate_identifier_0, memberName); + error(member.name, Diagnostics.Duplicate_identifier_0, memberName); + } + else { + names.set(memberName, true); } } + } + } - // run the check only for the first declaration in the list - if (getDeclarationOfKind(symbol, node.kind) !== node) { + function checkTypeForDuplicateIndexSignatures(node: Node) { + if (node.kind === SyntaxKind.InterfaceDeclaration) { + const nodeSymbol = getSymbolOfNode(node as InterfaceDeclaration); + // in case of merging interface declaration it is possible that we'll enter this check procedure several times for every declaration + // to prevent this run check only for the first declaration of a given kind + if (nodeSymbol.declarations && nodeSymbol.declarations.length > 0 && nodeSymbol.declarations[0] !== node) { return; } + } - let exportedDeclarationSpaces = DeclarationSpaces.None; - let nonExportedDeclarationSpaces = DeclarationSpaces.None; - let defaultExportedDeclarationSpaces = DeclarationSpaces.None; - for (const d of symbol.declarations!) { - const declarationSpaces = getDeclarationSpaces(d); - const effectiveDeclarationFlags = getEffectiveDeclarationFlags(d, ModifierFlags.Export | ModifierFlags.Default); - - if (effectiveDeclarationFlags & ModifierFlags.Export) { - if (effectiveDeclarationFlags & ModifierFlags.Default) { - defaultExportedDeclarationSpaces |= declarationSpaces; - } - else { - exportedDeclarationSpaces |= declarationSpaces; - } - } - else { - nonExportedDeclarationSpaces |= declarationSpaces; + // TypeScript 1.0 spec (April 2014) + // 3.7.4: An object type can contain at most one string index signature and one numeric index signature. + // 8.5: A class declaration can have at most one string index member declaration and one numeric index member declaration + const indexSymbol = getIndexSymbol(getSymbolOfNode(node)!); + if (indexSymbol?.declarations) { + const indexSignatureMap = new Map(); + for (const declaration of (indexSymbol.declarations as IndexSignatureDeclaration[])) { + if (declaration.parameters.length === 1 && declaration.parameters[0].type) { + forEachType(getTypeFromTypeNode(declaration.parameters[0].type), type => { + const entry = indexSignatureMap.get(getTypeId(type)); + if (entry) { + entry.declarations.push(declaration); + } + else { + indexSignatureMap.set(getTypeId(type), { type, declarations: [declaration] }); + } + }); } } - - // Spaces for anything not declared a 'default export'. - const nonDefaultExportedDeclarationSpaces = exportedDeclarationSpaces | nonExportedDeclarationSpaces; - - const commonDeclarationSpacesForExportsAndLocals = exportedDeclarationSpaces & nonExportedDeclarationSpaces; - const commonDeclarationSpacesForDefaultAndNonDefault = defaultExportedDeclarationSpaces & nonDefaultExportedDeclarationSpaces; - - if (commonDeclarationSpacesForExportsAndLocals || commonDeclarationSpacesForDefaultAndNonDefault) { - // declaration spaces for exported and non-exported declarations intersect - for (const d of symbol.declarations!) { - const declarationSpaces = getDeclarationSpaces(d); - - const name = getNameOfDeclaration(d); - // Only error on the declarations that contributed to the intersecting spaces. - if (declarationSpaces & commonDeclarationSpacesForDefaultAndNonDefault) { - error(name, Diagnostics.Merged_declaration_0_cannot_include_a_default_export_declaration_Consider_adding_a_separate_export_default_0_declaration_instead, declarationNameToString(name)); - } - else if (declarationSpaces & commonDeclarationSpacesForExportsAndLocals) { - error(name, Diagnostics.Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local, declarationNameToString(name)); + indexSignatureMap.forEach(entry => { + if (entry.declarations.length > 1) { + for (const declaration of entry.declarations) { + error(declaration, Diagnostics.Duplicate_index_signature_for_type_0, typeToString(entry.type)); } } - } - - function getDeclarationSpaces(decl: Declaration): DeclarationSpaces { - let d = decl as Node; - switch (d.kind) { - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: + }); + } + } - // A jsdoc typedef and callback are, by definition, type aliases. - // falls through - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - return DeclarationSpaces.ExportType; - case SyntaxKind.ModuleDeclaration: - return isAmbientModule(d as ModuleDeclaration) || getModuleInstanceState(d as ModuleDeclaration) !== ModuleInstanceState.NonInstantiated - ? DeclarationSpaces.ExportNamespace | DeclarationSpaces.ExportValue - : DeclarationSpaces.ExportNamespace; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.EnumMember: - return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue; - case SyntaxKind.SourceFile: - return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue | DeclarationSpaces.ExportNamespace; - case SyntaxKind.ExportAssignment: - case SyntaxKind.BinaryExpression: - const node = d as ExportAssignment | BinaryExpression; - const expression = isExportAssignment(node) ? node.expression : node.right; - // Export assigned entity name expressions act as aliases and should fall through, otherwise they export values - if (!isEntityNameExpression(expression)) { - return DeclarationSpaces.ExportValue; - } - d = expression; + function checkPropertyDeclaration(node: PropertyDeclaration | PropertySignature) { + // Grammar checking + if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name); + checkVariableLikeDeclaration(node); - // The below options all declare an Alias, which is allowed to merge with other values within the importing module. - // falls through - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportClause: - let result = DeclarationSpaces.None; - const target = resolveAlias(getSymbolOfNode(d)!); - forEach(target.declarations, d => { - result |= getDeclarationSpaces(d); - }); - return result; - case SyntaxKind.VariableDeclaration: - case SyntaxKind.BindingElement: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ImportSpecifier: // https://github.com/Microsoft/TypeScript/pull/7591 - case SyntaxKind.Identifier: // https://github.com/microsoft/TypeScript/issues/36098 - // Identifiers are used as declarations of assignment declarations whose parents may be - // SyntaxKind.CallExpression - `Object.defineProperty(thing, "aField", {value: 42});` - // SyntaxKind.ElementAccessExpression - `thing["aField"] = 42;` or `thing["aField"];` (with a doc comment on it) - // or SyntaxKind.PropertyAccessExpression - `thing.aField = 42;` - // all of which are pretty much always values, or at least imply a value meaning. - // It may be apprpriate to treat these as aliases in the future. - return DeclarationSpaces.ExportValue; - default: - return Debug.failBadSyntaxKind(d); - } - } + setNodeLinksForPrivateIdentifierScope(node); + if (isPrivateIdentifier(node.name) && hasStaticModifier(node) && node.initializer && languageVersion === ScriptTarget.ESNext && !compilerOptions.useDefineForClassFields) { + error(node.initializer, Diagnostics.Static_fields_with_private_names_can_t_have_initializers_when_the_useDefineForClassFields_flag_is_not_specified_with_a_target_of_esnext_Consider_adding_the_useDefineForClassFields_flag); } + // property signatures already report "initializer not allowed in ambient context" elsewhere + if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.PropertyDeclaration && node.initializer) { + error(node, Diagnostics.Property_0_cannot_have_an_initializer_because_it_is_marked_abstract, declarationNameToString(node.name)); + } + } - function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { - const promisedType = getPromisedTypeOfPromise(type, errorNode); - return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0); + function checkPropertySignature(node: PropertySignature) { + if (isPrivateIdentifier(node.name)) { + error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); } + return checkPropertyDeclaration(node); + } - /** - * Gets the "promised type" of a promise. - * @param type The type of the promise. - * @remarks The "promised type" of a type is the type of the "value" parameter of the "onfulfilled" callback. - */ - function getPromisedTypeOfPromise(type: Type, errorNode?: Node): Type | undefined { - // - // { // type - // then( // thenFunction - // onfulfilled: ( // onfulfilledParameterType - // value: T // valueParameterType - // ) => any - // ): any; - // } - // + function checkMethodDeclaration(node: MethodDeclaration | MethodSignature) { + // Grammar checking + if (!checkGrammarMethod(node)) checkGrammarComputedPropertyName(node.name); - if (isTypeAny(type)) { - return undefined; - } + // Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration + checkFunctionOrMethodDeclaration(node); - const typeAsPromise = type as PromiseOrAwaitableType; - if (typeAsPromise.promisedTypeOfPromise) { - return typeAsPromise.promisedTypeOfPromise; - } + // method signatures already report "implementation not allowed in ambient context" elsewhere + if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.MethodDeclaration && node.body) { + error(node, Diagnostics.Method_0_cannot_have_an_implementation_because_it_is_marked_abstract, declarationNameToString(node.name)); + } - if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false))) { - return typeAsPromise.promisedTypeOfPromise = getTypeArguments(type as GenericType)[0]; - } + // Private named methods are only allowed in class declarations + if (isPrivateIdentifier(node.name) && !getContainingClass(node)) { + error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + } - // primitives with a `{ then() }` won't be unwrapped/adopted. - if (allTypesAssignableToKind(type, TypeFlags.Primitive | TypeFlags.Never)) { - return undefined; - } + setNodeLinksForPrivateIdentifierScope(node); + } - const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217 - if (isTypeAny(thenFunction)) { - return undefined; + function setNodeLinksForPrivateIdentifierScope(node: PropertyDeclaration | PropertySignature | MethodDeclaration | MethodSignature | AccessorDeclaration) { + if (isPrivateIdentifier(node.name) && languageVersion < ScriptTarget.ESNext) { + for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) { + getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers; } - const thenSignatures = thenFunction ? getSignaturesOfType(thenFunction, SignatureKind.Call) : emptyArray; - if (thenSignatures.length === 0) { - if (errorNode) { - error(errorNode, Diagnostics.A_promise_must_have_a_then_method); + // If this is a private element in a class expression inside the body of a loop, + // then we must use a block-scoped binding to store the additional variables required + // to transform private elements. + if (isClassExpression(node.parent)) { + const enclosingIterationStatement = getEnclosingIterationStatement(node.parent); + if (enclosingIterationStatement) { + getNodeLinks(node.name).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; } - return undefined; } + } + } - const onfulfilledParameterType = getTypeWithFacts(getUnionType(map(thenSignatures, getTypeOfFirstParameterOfSignature)), TypeFacts.NEUndefinedOrNull); - if (isTypeAny(onfulfilledParameterType)) { - return undefined; - } + function checkClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { + checkGrammarDecoratorsAndModifiers(node); - const onfulfilledParameterSignatures = getSignaturesOfType(onfulfilledParameterType, SignatureKind.Call); - if (onfulfilledParameterSignatures.length === 0) { - if (errorNode) { - error(errorNode, Diagnostics.The_first_parameter_of_the_then_method_of_a_promise_must_be_a_callback); - } - return undefined; - } + forEachChild(node, checkSourceElement); + } - return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), UnionReduction.Subtype); - } + function checkConstructorDeclaration(node: ConstructorDeclaration) { + // Grammar check on signature of constructor and modifier of the constructor is done in checkSignatureDeclaration function. + checkSignatureDeclaration(node); + // Grammar check for checking only related to constructorDeclaration + if (!checkGrammarConstructorTypeParameters(node)) checkGrammarConstructorTypeAnnotation(node); - /** - * Gets the "awaited type" of a type. - * @param type The type to await. - * @param withAlias When `true`, wraps the "awaited type" in `Awaited` if needed. - * @remarks The "awaited type" of an expression is its "promised type" if the expression is a - * Promise-like type; otherwise, it is the type of the expression. This is used to reflect - * The runtime behavior of the `await` keyword. - */ - function checkAwaitedType(type: Type, withAlias: boolean, errorNode: Node, diagnosticMessage: DiagnosticMessage, arg0?: string | number): Type { - const awaitedType = withAlias ? - getAwaitedType(type, errorNode, diagnosticMessage, arg0) : - getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, arg0); - return awaitedType || errorType; - } + checkSourceElement(node.body); - /** - * Determines whether a type is an object with a callable `then` member. - */ - function isThenableType(type: Type): boolean { - if (allTypesAssignableToKind(type, TypeFlags.Primitive | TypeFlags.Never)) { - // primitive types cannot be considered "thenable" since they are not objects. - return false; - } + const symbol = getSymbolOfNode(node); + const firstDeclaration = getDeclarationOfKind(symbol, node.kind); - const thenFunction = getTypeOfPropertyOfType(type, "then" as __String); - return !!thenFunction && getSignaturesOfType(getTypeWithFacts(thenFunction, TypeFacts.NEUndefinedOrNull), SignatureKind.Call).length > 0; + // Only type check the symbol once + if (node === firstDeclaration) { + checkFunctionOrConstructorSymbol(symbol); } - interface AwaitedTypeInstantiation extends Type { - _awaitedTypeBrand: never; - aliasSymbol: Symbol; - aliasTypeArguments: readonly Type[]; + // exit early in the case of signature - super checks are not relevant to them + if (nodeIsMissing(node.body)) { + return; } - function isAwaitedTypeInstantiation(type: Type): type is AwaitedTypeInstantiation { - if (type.flags & TypeFlags.Conditional) { - const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ false); - return !!awaitedSymbol && type.aliasSymbol === awaitedSymbol && type.aliasTypeArguments?.length === 1; - } - return false; + if (!produceDiagnostics) { + return; } - /** - * For a generic `Awaited`, gets `T`. - */ - function unwrapAwaitedType(type: Type) { - return type.flags & TypeFlags.Union ? mapType(type, unwrapAwaitedType) : - isAwaitedTypeInstantiation(type) ? type.aliasTypeArguments[0] : - type; + function isInstancePropertyWithInitializerOrPrivateIdentifierProperty(n: Node): boolean { + if (isPrivateIdentifierClassElementDeclaration(n)) { + return true; + } + return n.kind === SyntaxKind.PropertyDeclaration && + !isStatic(n) && + !!(n as PropertyDeclaration).initializer; + } + + // TS 1.0 spec (April 2014): 8.3.2 + // Constructors of classes with no extends clause may not contain super calls, whereas + // constructors of derived classes must contain at least one super call somewhere in their function body. + const containingClassDecl = node.parent as ClassDeclaration; + if (getClassExtendsHeritageElement(containingClassDecl)) { + captureLexicalThis(node.parent, containingClassDecl); + const classExtendsNull = classDeclarationExtendsNull(containingClassDecl); + const superCall = findFirstSuperCall(node.body!); + if (superCall) { + if (classExtendsNull) { + error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); + } + + // The first statement in the body of a constructor (excluding prologue directives) must be a super call + // if both of the following are true: + // - The containing class is a derived class. + // - The constructor declares parameter properties + // or the containing class declares instance member variables with initializers. + const superCallShouldBeFirst = + (getEmitScriptTarget(compilerOptions) !== ScriptTarget.ESNext || !useDefineForClassFields) && + (some((node.parent as ClassDeclaration).members, isInstancePropertyWithInitializerOrPrivateIdentifierProperty) || + some(node.parameters, p => hasSyntacticModifier(p, ModifierFlags.ParameterPropertyModifier))); + + // Skip past any prologue directives to find the first statement + // to ensure that it was a super call. + if (superCallShouldBeFirst) { + const statements = node.body!.statements; + let superCallStatement: ExpressionStatement | undefined; + + for (const statement of statements) { + if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((statement as ExpressionStatement).expression)) { + superCallStatement = statement as ExpressionStatement; + break; + } + if (!isPrologueDirective(statement)) { + break; + } + } + if (!superCallStatement) { + error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_when_a_class_contains_initialized_properties_parameter_properties_or_private_identifiers); + } + } + } + else if (!classExtendsNull) { + error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); + } } + } - function createAwaitedTypeIfNeeded(type: Type): Type { - // We wrap type `T` in `Awaited` based on the following conditions: - // - `T` is not already an `Awaited`, and - // - `T` is generic, and - // - One of the following applies: - // - `T` has no base constraint, or - // - The base constraint of `T` is `any`, `unknown`, `object`, or `{}`, or - // - The base constraint of `T` is an object type with a callable `then` method. + function checkAccessorDeclaration(node: AccessorDeclaration) { + if (produceDiagnostics) { + // Grammar checking accessors + if (!checkGrammarFunctionLikeDeclaration(node) && !checkGrammarAccessor(node)) checkGrammarComputedPropertyName(node.name); - if (isTypeAny(type)) { - return type; + checkDecorators(node); + checkSignatureDeclaration(node); + if (node.kind === SyntaxKind.GetAccessor) { + if (!(node.flags & NodeFlags.Ambient) && nodeIsPresent(node.body) && (node.flags & NodeFlags.HasImplicitReturn)) { + if (!(node.flags & NodeFlags.HasExplicitReturn)) { + error(node.name, Diagnostics.A_get_accessor_must_return_a_value); + } + } } - - // If this is already an `Awaited`, just return it. This helps to avoid `Awaited>` in higher-order. - if (isAwaitedTypeInstantiation(type)) { - return type; + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); } - // Only instantiate `Awaited` if `T` contains possibly non-primitive types. - if (isGenericObjectType(type)) { - const baseConstraint = getBaseConstraintOfType(type); - // Only instantiate `Awaited` if `T` has no base constraint, or the base constraint of `T` is `any`, `unknown`, `{}`, `object`, - // or is promise-like. - if (!baseConstraint || (baseConstraint.flags & TypeFlags.AnyOrUnknown) || isEmptyObjectType(baseConstraint) || isThenableType(baseConstraint)) { - // Nothing to do if `Awaited` doesn't exist - const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ true); - if (awaitedSymbol) { - // Unwrap unions that may contain `Awaited`, otherwise its possible to manufacture an `Awaited | U>` where - // an `Awaited` would suffice. - return getTypeAliasInstantiation(awaitedSymbol, [unwrapAwaitedType(type)]); + if (hasBindableName(node)) { + // TypeScript 1.0 spec (April 2014): 8.4.3 + // Accessors for the same member name must specify the same accessibility. + const symbol = getSymbolOfNode(node); + const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); + const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); + if (getter && setter && !(getNodeCheckFlags(getter) & NodeCheckFlags.TypeChecked)) { + getNodeLinks(getter).flags |= NodeCheckFlags.TypeChecked; + const getterFlags = getEffectiveModifierFlags(getter); + const setterFlags = getEffectiveModifierFlags(setter); + if ((getterFlags & ModifierFlags.Abstract) !== (setterFlags & ModifierFlags.Abstract)) { + error(getter.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); + error(setter.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); + } + if (((getterFlags & ModifierFlags.Protected) && !(setterFlags & (ModifierFlags.Protected | ModifierFlags.Private))) || + ((getterFlags & ModifierFlags.Private) && !(setterFlags & ModifierFlags.Private))) { + error(getter.name, Diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter); + error(setter.name, Diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter); + } + + const getterType = getAnnotatedAccessorType(getter); + const setterType = getAnnotatedAccessorType(setter); + if (getterType && setterType) { + checkTypeAssignableTo(getterType, setterType, getter, Diagnostics.The_return_type_of_a_get_accessor_must_be_assignable_to_its_set_accessor_type); } } } - - Debug.assert(getPromisedTypeOfPromise(type) === undefined, "type provided should not be a non-generic 'promise'-like."); - return type; + const returnType = getTypeOfAccessors(getSymbolOfNode(node)); + if (node.kind === SyntaxKind.GetAccessor) { + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); + } } + checkSourceElement(node.body); + setNodeLinksForPrivateIdentifierScope(node); + } - /** - * Gets the "awaited type" of a type. - * - * The "awaited type" of an expression is its "promised type" if the expression is a - * Promise-like type; otherwise, it is the type of the expression. If the "promised - * type" is itself a Promise-like, the "promised type" is recursively unwrapped until a - * non-promise type is found. - * - * This is used to reflect the runtime behavior of the `await` keyword. - */ - function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { - const awaitedType = getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, arg0); - return awaitedType && createAwaitedTypeIfNeeded(awaitedType); - } + function checkMissingDeclaration(node: Node) { + checkDecorators(node); + } - /** - * Gets the "awaited type" of a type without introducing an `Awaited` wrapper. - * - * @see {@link getAwaitedType} - */ - function getAwaitedTypeNoAlias(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { - if (isTypeAny(type)) { - return type; - } + function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] { + return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, + getMinTypeArgumentCount(typeParameters), isInJSFile(node)); + } - // If this is already an `Awaited`, just return it. This avoids `Awaited>` in higher-order - if (isAwaitedTypeInstantiation(type)) { - return type; - } + function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean { + let typeArguments: Type[] | undefined; + let mapper: TypeMapper | undefined; + let result = true; + for (let i = 0; i < typeParameters.length; i++) { + const constraint = getConstraintOfTypeParameter(typeParameters[i]); + if (constraint) { + if (!typeArguments) { + typeArguments = getEffectiveTypeArguments(node, typeParameters); + mapper = createTypeMapper(typeParameters, typeArguments); + } + result = result && checkTypeAssignableTo( + typeArguments[i], + instantiateType(constraint, mapper), + node.typeArguments![i], + Diagnostics.Type_0_does_not_satisfy_the_constraint_1); + } + } + return result; + } - // If we've already cached an awaited type, return a possible `Awaited` for it. - const typeAsAwaitable = type as PromiseOrAwaitableType; - if (typeAsAwaitable.awaitedTypeOfType) { - return typeAsAwaitable.awaitedTypeOfType; + function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) { + const type = getTypeFromTypeReference(node); + if (!isErrorType(type)) { + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol) { + return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || + (getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined); } + } + return undefined; + } - // For a union, get a union of the awaited types of each constituent. - if (type.flags & TypeFlags.Union) { - const mapper = errorNode ? (constituentType: Type) => getAwaitedTypeNoAlias(constituentType, errorNode, diagnosticMessage, arg0) : getAwaitedTypeNoAlias; - return typeAsAwaitable.awaitedTypeOfType = mapType(type, mapper); + function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { + checkGrammarTypeArguments(node, node.typeArguments); + if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDotPos !== undefined && !isInJSFile(node) && !isInJSDoc(node)) { + grammarErrorAtPos(node, node.typeName.jsdocDotPos, 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } + forEach(node.typeArguments, checkSourceElement); + const type = getTypeFromTypeReference(node); + if (!isErrorType(type)) { + if (node.typeArguments && produceDiagnostics) { + const typeParameters = getTypeParametersForTypeReference(node); + if (typeParameters) { + checkTypeArgumentConstraints(node, typeParameters); + } } - - const promisedType = getPromisedTypeOfPromise(type); - if (promisedType) { - if (type.id === promisedType.id || awaitedTypeStack.lastIndexOf(promisedType.id) >= 0) { - // Verify that we don't have a bad actor in the form of a promise whose - // promised type is the same as the promise type, or a mutually recursive - // promise. If so, we return undefined as we cannot guess the shape. If this - // were the actual case in the JavaScript, this Promise would never resolve. - // - // An example of a bad actor with a singly-recursive promise type might - // be: - // - // interface BadPromise { - // then( - // onfulfilled: (value: BadPromise) => any, - // onrejected: (error: any) => any): BadPromise; - // } - // - // The above interface will pass the PromiseLike check, and return a - // promised type of `BadPromise`. Since this is a self reference, we - // don't want to keep recursing ad infinitum. - // - // An example of a bad actor in the form of a mutually-recursive - // promise type might be: - // - // interface BadPromiseA { - // then( - // onfulfilled: (value: BadPromiseB) => any, - // onrejected: (error: any) => any): BadPromiseB; - // } - // - // interface BadPromiseB { - // then( - // onfulfilled: (value: BadPromiseA) => any, - // onrejected: (error: any) => any): BadPromiseA; - // } - // - if (errorNode) { - error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method); - } - return undefined; + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol) { + if (some(symbol.declarations, d => isTypeDeclaration(d) && !!(d.flags & NodeFlags.Deprecated))) { + addDeprecatedSuggestion( + getDeprecatedSuggestionNode(node), + symbol.declarations!, + symbol.escapedName as string + ); } - - // Keep track of the type we're about to unwrap to avoid bad recursive promise types. - // See the comments above for more information. - awaitedTypeStack.push(type.id); - const awaitedType = getAwaitedTypeNoAlias(promisedType, errorNode, diagnosticMessage, arg0); - awaitedTypeStack.pop(); - - if (!awaitedType) { - return undefined; + if (type.flags & TypeFlags.Enum && symbol.flags & SymbolFlags.EnumMember) { + error(node, Diagnostics.Enum_type_0_has_members_with_initializers_that_are_not_literals, typeToString(type)); } - - return typeAsAwaitable.awaitedTypeOfType = awaitedType; } + } + } - // The type was not a promise, so it could not be unwrapped any further. - // As long as the type does not have a callable "then" property, it is - // safe to return the type; otherwise, an error is reported and we return - // undefined. - // - // An example of a non-promise "thenable" might be: - // - // await { then(): void {} } - // - // The "thenable" does not match the minimal definition for a promise. When - // a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise - // will never settle. We treat this as an error to help flag an early indicator - // of a runtime problem. If the user wants to return this value from an async - // function, they would need to wrap it in some other value. If they want it to - // be treated as a promise, they can cast to . - if (isThenableType(type)) { - if (errorNode) { - Debug.assertIsDefined(diagnosticMessage); - error(errorNode, diagnosticMessage, arg0); - } - return undefined; - } + function getTypeArgumentConstraint(node: TypeNode): Type | undefined { + const typeReferenceNode = tryCast(node.parent, isTypeReferenceType); + if (!typeReferenceNode) return undefined; + const typeParameters = getTypeParametersForTypeReference(typeReferenceNode); + if (!typeParameters) return undefined; + const constraint = getConstraintOfTypeParameter(typeParameters[typeReferenceNode.typeArguments!.indexOf(node)]); + return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters))); + } + + function checkTypeQuery(node: TypeQueryNode) { + getTypeFromTypeQueryNode(node); + } - return typeAsAwaitable.awaitedTypeOfType = type; + function checkTypeLiteral(node: TypeLiteralNode) { + forEach(node.members, checkSourceElement); + if (produceDiagnostics) { + const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); + checkIndexConstraints(type, type.symbol); + checkTypeForDuplicateIndexSignatures(node); + checkObjectTypeForDuplicateDeclarations(node); } + } - /** - * Checks the return type of an async function to ensure it is a compatible - * Promise implementation. - * - * This checks that an async function has a valid Promise-compatible return type. - * An async function has a valid Promise-compatible return type if the resolved value - * of the return type has a construct signature that takes in an `initializer` function - * that in turn supplies a `resolve` function as one of its arguments and results in an - * object with a callable `then` signature. - * - * @param node The signature to check - */ - function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode) { - // As part of our emit for an async function, we will need to emit the entity name of - // the return type annotation as an expression. To meet the necessary runtime semantics - // for __awaiter, we must also check that the type of the declaration (e.g. the static - // side or "constructor" of the promise type) is compatible `PromiseConstructorLike`. - // - // An example might be (from lib.es6.d.ts): - // - // interface Promise { ... } - // interface PromiseConstructor { - // new (...): Promise; - // } - // declare var Promise: PromiseConstructor; - // - // When an async function declares a return type annotation of `Promise`, we - // need to get the type of the `Promise` variable declaration above, which would - // be `PromiseConstructor`. - // - // The same case applies to a class: - // - // declare class Promise { - // constructor(...); - // then(...): Promise; - // } - // - const returnType = getTypeFromTypeNode(returnTypeNode); + function checkArrayType(node: ArrayTypeNode) { + checkSourceElement(node.elementType); + } - if (languageVersion >= ScriptTarget.ES2015) { - if (isErrorType(returnType)) { - return; - } - const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); - if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) { - // The promise type was not a valid type reference to the global promise type, so we - // report an error and return the unknown type. - error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, typeToString(getAwaitedTypeNoAlias(returnType) || voidType)); - return; - } + function checkTupleType(node: TupleTypeNode) { + const elementTypes = node.elements; + let seenOptionalElement = false; + let seenRestElement = false; + const hasNamedElement = some(elementTypes, isNamedTupleMember); + for (const e of elementTypes) { + if (e.kind !== SyntaxKind.NamedTupleMember && hasNamedElement) { + grammarErrorOnNode(e, Diagnostics.Tuple_members_must_all_have_names_or_all_not_have_names); + break; } - else { - // Always mark the type node as referenced if it points to a value - markTypeNodeAsReferenced(returnTypeNode); - - if (isErrorType(returnType)) { - return; + const flags = getTupleElementFlags(e); + if (flags & ElementFlags.Variadic) { + const type = getTypeFromTypeNode((e as RestTypeNode | NamedTupleMember).type); + if (!isArrayLikeType(type)) { + error(e, Diagnostics.A_rest_element_type_must_be_an_array_type); + break; } - - const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode); - if (promiseConstructorName === undefined) { - error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(returnType)); - return; + if (isArrayType(type) || isTupleType(type) && type.target.combinedFlags & ElementFlags.Rest) { + seenRestElement = true; } - - const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true); - const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType; - if (isErrorType(promiseConstructorType)) { - if (promiseConstructorName.kind === SyntaxKind.Identifier && promiseConstructorName.escapedText === "Promise" && getTargetType(returnType) === getGlobalPromiseType(/*reportErrors*/ false)) { - error(returnTypeNode, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); - } - else { - error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); - } - return; + } + else if (flags & ElementFlags.Rest) { + if (seenRestElement) { + grammarErrorOnNode(e, Diagnostics.A_rest_element_cannot_follow_another_rest_element); + break; } - - const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(/*reportErrors*/ true); - if (globalPromiseConstructorLikeType === emptyObjectType) { - // If we couldn't resolve the global PromiseConstructorLike type we cannot verify - // compatibility with __awaiter. - error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); - return; + seenRestElement = true; + } + else if (flags & ElementFlags.Optional) { + if (seenRestElement) { + grammarErrorOnNode(e, Diagnostics.An_optional_element_cannot_follow_a_rest_element); + break; } + seenOptionalElement = true; + } + else if (seenOptionalElement) { + grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); + break; + } + } + forEach(node.elements, checkSourceElement); + getTypeFromTypeNode(node); + } - if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeNode, - Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value)) { - return; - } + function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) { + forEach(node.types, checkSourceElement); + getTypeFromTypeNode(node); + } - // Verify there is no local declaration that could collide with the promise constructor. - const rootName = promiseConstructorName && getFirstIdentifier(promiseConstructorName); - const collidingSymbol = getSymbol(node.locals!, rootName.escapedText, SymbolFlags.Value); - if (collidingSymbol) { - error(collidingSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, - idText(rootName), - entityNameToString(promiseConstructorName)); - return; + function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) { + if (!(type.flags & TypeFlags.IndexedAccess)) { + return type; + } + // Check if the index type is assignable to 'keyof T' for the object type. + const objectType = (type as IndexedAccessType).objectType; + const indexType = (type as IndexedAccessType).indexType; + if (isTypeAssignableTo(indexType, getIndexType(objectType, /*stringsOnly*/ false))) { + if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && + getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.IncludeReadonly) { + error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); + } + return type; + } + // Check if we're indexing with a numeric type and if either object or index types + // is a generic type with a constraint that has a numeric index signature. + const apparentObjectType = getApparentType(objectType); + if (getIndexInfoOfType(apparentObjectType, numberType) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { + return type; + } + if (isGenericObjectType(objectType)) { + const propertyName = getPropertyNameFromIndex(indexType, accessNode); + if (propertyName) { + const propertySymbol = forEachType(apparentObjectType, t => getPropertyOfType(t, propertyName)); + if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { + error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName)); + return errorType; } } - checkAwaitedType(returnType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); } + error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); + return errorType; + } - /** Check a decorator */ - function checkDecorator(node: Decorator): void { - const signature = getResolvedSignature(node); - checkDeprecatedSignature(signature, node); - const returnType = getReturnTypeOfSignature(signature); - if (returnType.flags & TypeFlags.Any) { - return; - } + function checkIndexedAccessType(node: IndexedAccessTypeNode) { + checkSourceElement(node.objectType); + checkSourceElement(node.indexType); + checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); + } - let expectedReturnType: Type; - const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); - let errorInfo: DiagnosticMessageChain | undefined; - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - const classSymbol = getSymbolOfNode(node.parent); - const classConstructorType = getTypeOfSymbol(classSymbol); - expectedReturnType = getUnionType([classConstructorType, voidType]); - break; + function checkMappedType(node: MappedTypeNode) { + checkGrammarMappedType(node); + checkSourceElement(node.typeParameter); + checkSourceElement(node.nameType); + checkSourceElement(node.type); - case SyntaxKind.Parameter: - expectedReturnType = voidType; - errorInfo = chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.The_return_type_of_a_parameter_decorator_function_must_be_either_void_or_any); + if (!node.type) { + reportImplicitAny(node, anyType); + } - break; + const type = getTypeFromMappedTypeNode(node) as MappedType; + const nameType = getNameTypeFromMappedType(type); + if (nameType) { + checkTypeAssignableTo(nameType, keyofConstraintType, node.nameType); + } + else { + const constraintType = getConstraintTypeFromMappedType(type); + checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); + } + } - case SyntaxKind.PropertyDeclaration: - expectedReturnType = voidType; - errorInfo = chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.The_return_type_of_a_property_decorator_function_must_be_either_void_or_any); - break; + function checkGrammarMappedType(node: MappedTypeNode) { + if (node.members?.length) { + return grammarErrorOnNode(node.members[0], Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); + } + } + + function checkThisType(node: ThisTypeNode) { + getTypeFromThisTypeNode(node); + } - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - const methodType = getTypeOfNode(node.parent); - const descriptorType = createTypedPropertyDescriptorType(methodType); - expectedReturnType = getUnionType([descriptorType, voidType]); - break; + function checkTypeOperator(node: TypeOperatorNode) { + checkGrammarTypeOperatorNode(node); + checkSourceElement(node.type); + } - default: - return Debug.fail(); - } + function checkConditionalType(node: ConditionalTypeNode) { + forEachChild(node, checkSourceElement); + } - checkTypeAssignableTo( - returnType, - expectedReturnType, - node, - headMessage, - () => errorInfo); + function checkInferType(node: InferTypeNode) { + if (!findAncestor(node, n => n.parent && n.parent.kind === SyntaxKind.ConditionalType && (n.parent as ConditionalTypeNode).extendsType === n)) { + grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type); } + checkSourceElement(node.typeParameter); + registerForUnusedIdentifiersCheck(node); + } - /** - * If a TypeNode can be resolved to a value symbol imported from an external module, it is - * marked as referenced to prevent import elision. - */ - function markTypeNodeAsReferenced(node: TypeNode) { - markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node)); + function checkTemplateLiteralType(node: TemplateLiteralTypeNode) { + for (const span of node.templateSpans) { + checkSourceElement(span.type); + const type = getTypeFromTypeNode(span.type); + checkTypeAssignableTo(type, templateConstraintType, span.type); } + getTypeFromTypeNode(node); + } - function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined) { - if (!typeName) return; + function checkImportType(node: ImportTypeNode) { + checkSourceElement(node.argument); + getTypeFromTypeNode(node); + } - const rootName = getFirstIdentifier(typeName); - const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; - const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isReference*/ true); - if (rootSymbol - && rootSymbol.flags & SymbolFlags.Alias - && symbolIsValue(rootSymbol) - && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) - && !getTypeOnlyAliasDeclaration(rootSymbol)) { - markAliasSymbolAsReferenced(rootSymbol); - } + function checkNamedTupleMember(node: NamedTupleMember) { + if (node.dotDotDotToken && node.questionToken) { + grammarErrorOnNode(node, Diagnostics.A_tuple_member_cannot_be_both_optional_and_rest); + } + if (node.type.kind === SyntaxKind.OptionalType) { + grammarErrorOnNode(node.type, Diagnostics.A_labeled_tuple_element_is_declared_as_optional_with_a_question_mark_after_the_name_and_before_the_colon_rather_than_after_the_type); } + if (node.type.kind === SyntaxKind.RestType) { + grammarErrorOnNode(node.type, Diagnostics.A_labeled_tuple_element_is_declared_as_rest_with_a_before_the_name_rather_than_before_the_type); + } + checkSourceElement(node.type); + getTypeFromTypeNode(node); + } - /** - * This function marks the type used for metadata decorator as referenced if it is import - * from external module. - * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in - * union and intersection type - * @param node - */ - function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { - const entityName = getEntityNameForDecoratorMetadata(node); - if (entityName && isEntityName(entityName)) { - markEntityNameOrEntityExpressionAsReference(entityName); + function isPrivateWithinAmbient(node: Node): boolean { + return (hasEffectiveModifier(node, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(node)) && !!(node.flags & NodeFlags.Ambient); + } + + function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags { + let flags = getCombinedModifierFlags(n); + + // children of classes (even ambient classes) should not be marked as ambient or export + // because those flags have no useful semantics there. + if (n.parent.kind !== SyntaxKind.InterfaceDeclaration && + n.parent.kind !== SyntaxKind.ClassDeclaration && + n.parent.kind !== SyntaxKind.ClassExpression && + n.flags & NodeFlags.Ambient) { + if (!(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) { + // It is nested in an ambient context, which means it is automatically exported + flags |= ModifierFlags.Export; } + flags |= ModifierFlags.Ambient; } - function getEntityNameForDecoratorMetadata(node: TypeNode | undefined): EntityName | undefined { - if (node) { - switch (node.kind) { - case SyntaxKind.IntersectionType: - case SyntaxKind.UnionType: - return getEntityNameForDecoratorMetadataFromTypeList((node as UnionOrIntersectionTypeNode).types); + return flags & flagsToCheck; + } - case SyntaxKind.ConditionalType: - return getEntityNameForDecoratorMetadataFromTypeList([(node as ConditionalTypeNode).trueType, (node as ConditionalTypeNode).falseType]); + function checkFunctionOrConstructorSymbol(symbol: Symbol): void { + if (!produceDiagnostics) { + return; + } - case SyntaxKind.ParenthesizedType: - case SyntaxKind.NamedTupleMember: - return getEntityNameForDecoratorMetadata((node as ParenthesizedTypeNode).type); + function getCanonicalOverload(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined): Declaration { + // Consider the canonical set of flags to be the flags of the bodyDeclaration or the first declaration + // Error on all deviations from this canonical set of flags + // The caveat is that if some overloads are defined in lib.d.ts, we don't want to + // report the errors on those. To achieve this, we will say that the implementation is + // the canonical signature only if it is in the same container as the first overload + const implementationSharesContainerWithFirstOverload = implementation !== undefined && implementation.parent === overloads[0].parent; + return implementationSharesContainerWithFirstOverload ? implementation : overloads[0]; + } - case SyntaxKind.TypeReference: - return (node as TypeReferenceNode).typeName; - } + function checkFlagAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, flagsToCheck: ModifierFlags, someOverloadFlags: ModifierFlags, allOverloadFlags: ModifierFlags): void { + // Error if some overloads have a flag that is not shared by all overloads. To find the + // deviations, we XOR someOverloadFlags with allOverloadFlags + const someButNotAllOverloadFlags = someOverloadFlags ^ allOverloadFlags; + if (someButNotAllOverloadFlags !== 0) { + const canonicalFlags = getEffectiveDeclarationFlags(getCanonicalOverload(overloads, implementation), flagsToCheck); + forEach(overloads, o => { + const deviation = getEffectiveDeclarationFlags(o, flagsToCheck) ^ canonicalFlags; + if (deviation & ModifierFlags.Export) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_exported_or_non_exported); + } + else if (deviation & ModifierFlags.Ambient) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_ambient_or_non_ambient); + } + else if (deviation & (ModifierFlags.Private | ModifierFlags.Protected)) { + error(getNameOfDeclaration(o) || o, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected); + } + else if (deviation & ModifierFlags.Abstract) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_abstract_or_non_abstract); + } + }); } } - function getEntityNameForDecoratorMetadataFromTypeList(types: readonly TypeNode[]): EntityName | undefined { - let commonEntityName: EntityName | undefined; - for (let typeNode of types) { - while (typeNode.kind === SyntaxKind.ParenthesizedType || typeNode.kind === SyntaxKind.NamedTupleMember) { - typeNode = (typeNode as ParenthesizedTypeNode | NamedTupleMember).type; // Skip parens if need be - } - if (typeNode.kind === SyntaxKind.NeverKeyword) { - continue; // Always elide `never` from the union/intersection if possible - } - if (!strictNullChecks && (typeNode.kind === SyntaxKind.LiteralType && (typeNode as LiteralTypeNode).literal.kind === SyntaxKind.NullKeyword || typeNode.kind === SyntaxKind.UndefinedKeyword)) { - continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks - } - const individualEntityName = getEntityNameForDecoratorMetadata(typeNode); - if (!individualEntityName) { - // Individual is something like string number - // So it would be serialized to either that type or object - // Safe to return here - return undefined; - } - - if (commonEntityName) { - // Note this is in sync with the transformation that happens for type node. - // Keep this in sync with serializeUnionOrIntersectionType - // Verify if they refer to same entity and is identifier - // return undefined if they dont match because we would emit object - if (!isIdentifier(commonEntityName) || - !isIdentifier(individualEntityName) || - commonEntityName.escapedText !== individualEntityName.escapedText) { - return undefined; + function checkQuestionTokenAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, someHaveQuestionToken: boolean, allHaveQuestionToken: boolean): void { + if (someHaveQuestionToken !== allHaveQuestionToken) { + const canonicalHasQuestionToken = hasQuestionToken(getCanonicalOverload(overloads, implementation)); + forEach(overloads, o => { + const deviation = hasQuestionToken(o) !== canonicalHasQuestionToken; + if (deviation) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_optional_or_required); } - } - else { - commonEntityName = individualEntityName; - } + }); } - return commonEntityName; } - function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined { - const typeNode = getEffectiveTypeAnnotationNode(node); - return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; - } + const flagsToCheck: ModifierFlags = ModifierFlags.Export | ModifierFlags.Ambient | ModifierFlags.Private | ModifierFlags.Protected | ModifierFlags.Abstract; + let someNodeFlags: ModifierFlags = ModifierFlags.None; + let allNodeFlags = flagsToCheck; + let someHaveQuestionToken = false; + let allHaveQuestionToken = true; + let hasOverloads = false; + let bodyDeclaration: FunctionLikeDeclaration | undefined; + let lastSeenNonAmbientDeclaration: FunctionLikeDeclaration | undefined; + let previousDeclaration: SignatureDeclaration | undefined; - /** Check the decorators of a node */ - function checkDecorators(node: Node): void { - if (!node.decorators) { - return; - } + const declarations = symbol.declarations; + const isConstructor = (symbol.flags & SymbolFlags.Constructor) !== 0; - // skip this check for nodes that cannot have decorators. These should have already had an error reported by - // checkGrammarDecorators. - if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { + function reportImplementationExpectedError(node: SignatureDeclaration): void { + if (node.name && nodeIsMissing(node.name)) { return; } - if (!compilerOptions.experimentalDecorators) { - error(node, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning); + let seen = false; + const subsequentNode = forEachChild(node.parent, c => { + if (seen) { + return c; + } + else { + seen = c === node; + } + }); + // We may be here because of some extra nodes between overloads that could not be parsed into a valid node. + // In this case the subsequent node is not really consecutive (.pos !== node.end), and we must ignore it here. + if (subsequentNode && subsequentNode.pos === node.end) { + if (subsequentNode.kind === node.kind) { + const errorNode: Node = (subsequentNode as FunctionLikeDeclaration).name || subsequentNode; + const subsequentName = (subsequentNode as FunctionLikeDeclaration).name; + if (node.name && subsequentName && ( + // both are private identifiers + isPrivateIdentifier(node.name) && isPrivateIdentifier(subsequentName) && node.name.escapedText === subsequentName.escapedText || + // Both are computed property names + // TODO: GH#17345: These are methods, so handle computed name case. (`Always allowing computed property names is *not* the correct behavior!) + isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) || + // Both are literal property names that are the same. + isPropertyNameLiteral(node.name) && isPropertyNameLiteral(subsequentName) && + getEscapedTextOfIdentifierOrLiteral(node.name) === getEscapedTextOfIdentifierOrLiteral(subsequentName) + )) { + const reportError = + (node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature) && + isStatic(node) !== isStatic(subsequentNode); + // we can get here in two cases + // 1. mixed static and instance class members + // 2. something with the same name was defined before the set of overloads that prevents them from merging + // here we'll report error only for the first case since for second we should already report error in binder + if (reportError) { + const diagnostic = isStatic(node) ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static; + error(errorNode, diagnostic); + } + return; + } + if (nodeIsPresent((subsequentNode as FunctionLikeDeclaration).body)) { + error(errorNode, Diagnostics.Function_implementation_name_must_be_0, declarationNameToString(node.name)); + return; + } + } } - - const firstDecorator = node.decorators[0]; - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate); - if (node.kind === SyntaxKind.Parameter) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param); + const errorNode: Node = node.name || node; + if (isConstructor) { + error(errorNode, Diagnostics.Constructor_implementation_is_missing); } + else { + // Report different errors regarding non-consecutive blocks of declarations depending on whether + // the node in question is abstract. + if (hasSyntacticModifier(node, ModifierFlags.Abstract)) { + error(errorNode, Diagnostics.All_declarations_of_an_abstract_method_must_be_consecutive); + } + else { + error(errorNode, Diagnostics.Function_implementation_is_missing_or_not_immediately_following_the_declaration); + } + } + } - if (compilerOptions.emitDecoratorMetadata) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); - - // we only need to perform these checks if we are emitting serialized type metadata for the target of a decorator. - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - const constructor = getFirstConstructorWithBody(node as ClassDeclaration); - if (constructor) { - for (const parameter of constructor.parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); - } + let duplicateFunctionDeclaration = false; + let multipleConstructorImplementation = false; + let hasNonAmbientClass = false; + const functionDeclarations = [] as Declaration[]; + if (declarations) { + for (const current of declarations) { + const node = current as SignatureDeclaration | ClassDeclaration | ClassExpression; + const inAmbientContext = node.flags & NodeFlags.Ambient; + const inAmbientContextOrInterface = node.parent && (node.parent.kind === SyntaxKind.InterfaceDeclaration || node.parent.kind === SyntaxKind.TypeLiteral) || inAmbientContext; + if (inAmbientContextOrInterface) { + // check if declarations are consecutive only if they are non-ambient + // 1. ambient declarations can be interleaved + // i.e. this is legal + // declare function foo(); + // declare function bar(); + // declare function foo(); + // 2. mixing ambient and non-ambient declarations is a separate error that will be reported - do not want to report an extra one + previousDeclaration = undefined; + } + + if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && !inAmbientContext) { + hasNonAmbientClass = true; + } + + if (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature || node.kind === SyntaxKind.Constructor) { + functionDeclarations.push(node); + const currentNodeFlags = getEffectiveDeclarationFlags(node, flagsToCheck); + someNodeFlags |= currentNodeFlags; + allNodeFlags &= currentNodeFlags; + someHaveQuestionToken = someHaveQuestionToken || hasQuestionToken(node); + allHaveQuestionToken = allHaveQuestionToken && hasQuestionToken(node); + const bodyIsPresent = nodeIsPresent((node as FunctionLikeDeclaration).body); + + if (bodyIsPresent && bodyDeclaration) { + if (isConstructor) { + multipleConstructorImplementation = true; } - break; - - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; - const otherAccessor = getDeclarationOfKind(getSymbolOfNode(node as AccessorDeclaration), otherKind); - markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node as AccessorDeclaration) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); - break; - case SyntaxKind.MethodDeclaration: - for (const parameter of (node as FunctionLikeDeclaration).parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + else { + duplicateFunctionDeclaration = true; } + } + else if (previousDeclaration?.parent === node.parent && previousDeclaration.end !== node.pos) { + reportImplementationExpectedError(previousDeclaration); + } - markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node as FunctionLikeDeclaration)); - break; + if (bodyIsPresent) { + if (!bodyDeclaration) { + bodyDeclaration = node as FunctionLikeDeclaration; + } + } + else { + hasOverloads = true; + } - case SyntaxKind.PropertyDeclaration: - markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node as ParameterDeclaration)); - break; + previousDeclaration = node; - case SyntaxKind.Parameter: - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node as ParameterDeclaration)); - const containingSignature = (node as ParameterDeclaration).parent; - for (const parameter of containingSignature.parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); - } - break; + if (!inAmbientContextOrInterface) { + lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; + } } } + } - forEach(node.decorators, checkDecorator); + if (multipleConstructorImplementation) { + forEach(functionDeclarations, declaration => { + error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed); + }); } - function checkFunctionDeclaration(node: FunctionDeclaration): void { - if (produceDiagnostics) { - checkFunctionOrMethodDeclaration(node); - checkGrammarForGenerator(node); - checkCollisionsForDeclarationName(node, node.name); - } + if (duplicateFunctionDeclaration) { + forEach(functionDeclarations, declaration => { + error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_function_implementation); + }); } - function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) { - if (!node.typeExpression) { - // If the node had `@property` tags, `typeExpression` would have been set to the first property tag. - error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags); - } + if (hasNonAmbientClass && !isConstructor && symbol.flags & SymbolFlags.Function && declarations) { + const relatedDiagnostics = filter(declarations, d => d.kind === SyntaxKind.ClassDeclaration) + .map(d => createDiagnosticForNode(d, Diagnostics.Consider_adding_a_declare_modifier_to_this_class)); - if (node.name) { - checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); - } - checkSourceElement(node.typeExpression); - checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + forEach(declarations, declaration => { + const diagnostic = declaration.kind === SyntaxKind.ClassDeclaration + ? Diagnostics.Class_declaration_cannot_implement_overload_list_for_0 + : declaration.kind === SyntaxKind.FunctionDeclaration + ? Diagnostics.Function_with_bodies_can_only_merge_with_classes_that_are_ambient + : undefined; + if (diagnostic) { + addRelatedInfo( + error(getNameOfDeclaration(declaration) || declaration, diagnostic, symbolName(symbol)), + ...relatedDiagnostics + ); + } + }); } - function checkJSDocTemplateTag(node: JSDocTemplateTag): void { - checkSourceElement(node.constraint); - for (const tp of node.typeParameters) { - checkSourceElement(tp); - } + // Abstract methods can't have an implementation -- in particular, they don't need one. + if (lastSeenNonAmbientDeclaration && !lastSeenNonAmbientDeclaration.body && + !hasSyntacticModifier(lastSeenNonAmbientDeclaration, ModifierFlags.Abstract) && !lastSeenNonAmbientDeclaration.questionToken) { + reportImplementationExpectedError(lastSeenNonAmbientDeclaration); } - function checkJSDocTypeTag(node: JSDocTypeTag) { - checkSourceElement(node.typeExpression); - } + if (hasOverloads) { + if (declarations) { + checkFlagAgreementBetweenOverloads(declarations, bodyDeclaration, flagsToCheck, someNodeFlags, allNodeFlags); + checkQuestionTokenAgreementBetweenOverloads(declarations, bodyDeclaration, someHaveQuestionToken, allHaveQuestionToken); + } - function checkJSDocParameterTag(node: JSDocParameterTag) { - checkSourceElement(node.typeExpression); - if (!getParameterSymbolFromJSDoc(node)) { - const decl = getHostSignatureFromJSDoc(node); - // don't issue an error for invalid hosts -- just functions -- - // and give a better error message when the host function mentions `arguments` - // but the tag doesn't have an array type - if (decl) { - const i = getJSDocTags(decl).filter(isJSDocParameterTag).indexOf(node); - if (i > -1 && i < decl.parameters.length && isBindingPattern(decl.parameters[i].name)) { - return; - } - if (!containsArgumentsReference(decl)) { - if (isQualifiedName(node.name)) { - error(node.name, - Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, - entityNameToString(node.name), - entityNameToString(node.name.left)); - } - else { - error(node.name, - Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name, - idText(node.name)); - } - } - else if (findLast(getJSDocTags(decl), isJSDocParameterTag) === node && - node.typeExpression && node.typeExpression.type && - !isArrayType(getTypeFromTypeNode(node.typeExpression.type))) { - error(node.name, - Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type, - idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name)); + if (bodyDeclaration) { + const signatures = getSignaturesOfSymbol(symbol); + const bodySignature = getSignatureFromDeclaration(bodyDeclaration); + for (const signature of signatures) { + if (!isImplementationCompatibleWithOverload(bodySignature, signature)) { + addRelatedInfo( + error(signature.declaration, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), + createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here) + ); + break; } } } } + } - function checkJSDocPropertyTag(node: JSDocPropertyTag) { - checkSourceElement(node.typeExpression); + function checkExportsOnMergedDeclarations(node: Declaration): void { + if (!produceDiagnostics) { + return; } - function checkJSDocFunctionType(node: JSDocFunctionType): void { - if (produceDiagnostics && !node.type && !isJSDocConstructSignature(node)) { - reportImplicitAny(node, anyType); + // if localSymbol is defined on node then node itself is exported - check is required + let symbol = node.localSymbol; + if (!symbol) { + // local symbol is undefined => this declaration is non-exported. + // however symbol might contain other declarations that are exported + symbol = getSymbolOfNode(node)!; + if (!symbol.exportSymbol) { + // this is a pure local symbol (all declarations are non-exported) - no need to check anything + return; } - checkSignatureDeclaration(node); } - function checkJSDocImplementsTag(node: JSDocImplementsTag): void { - const classLike = getEffectiveJSDocHost(node); - if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) { - error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName)); - } + // run the check only for the first declaration in the list + if (getDeclarationOfKind(symbol, node.kind) !== node) { + return; } - function checkJSDocAugmentsTag(node: JSDocAugmentsTag): void { - const classLike = getEffectiveJSDocHost(node); - if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) { - error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName)); - return; - } - - const augmentsTags = getJSDocTags(classLike).filter(isJSDocAugmentsTag); - Debug.assert(augmentsTags.length > 0); - if (augmentsTags.length > 1) { - error(augmentsTags[1], Diagnostics.Class_declarations_cannot_have_more_than_one_augments_or_extends_tag); - } + let exportedDeclarationSpaces = DeclarationSpaces.None; + let nonExportedDeclarationSpaces = DeclarationSpaces.None; + let defaultExportedDeclarationSpaces = DeclarationSpaces.None; + for (const d of symbol.declarations!) { + const declarationSpaces = getDeclarationSpaces(d); + const effectiveDeclarationFlags = getEffectiveDeclarationFlags(d, ModifierFlags.Export | ModifierFlags.Default); - const name = getIdentifierFromEntityNameExpression(node.class.expression); - const extend = getClassExtendsHeritageElement(classLike); - if (extend) { - const className = getIdentifierFromEntityNameExpression(extend.expression); - if (className && name.escapedText !== className.escapedText) { - error(name, Diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, idText(node.tagName), idText(name), idText(className)); + if (effectiveDeclarationFlags & ModifierFlags.Export) { + if (effectiveDeclarationFlags & ModifierFlags.Default) { + defaultExportedDeclarationSpaces |= declarationSpaces; + } + else { + exportedDeclarationSpaces |= declarationSpaces; } } - } - - function checkJSDocAccessibilityModifiers(node: JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag): void { - const host = getJSDocHost(node); - if (host && isPrivateIdentifierClassElementDeclaration(host)) { - error(node, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier); - } - } - - function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier | PrivateIdentifier; - function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined; - function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined { - switch (node.kind) { - case SyntaxKind.Identifier: - return node as Identifier; - case SyntaxKind.PropertyAccessExpression: - return (node as PropertyAccessExpression).name; - default: - return undefined; + else { + nonExportedDeclarationSpaces |= declarationSpaces; } } - function checkFunctionOrMethodDeclaration(node: FunctionDeclaration | MethodDeclaration | MethodSignature): void { - checkDecorators(node); - checkSignatureDeclaration(node); - const functionFlags = getFunctionFlags(node); - - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name && node.name.kind === SyntaxKind.ComputedPropertyName) { - // This check will account for methods in class/interface declarations, - // as well as accessors in classes/object literals - checkComputedPropertyName(node.name); - } + // Spaces for anything not declared a 'default export'. + const nonDefaultExportedDeclarationSpaces = exportedDeclarationSpaces | nonExportedDeclarationSpaces; - if (hasBindableName(node)) { - // first we want to check the local symbol that contain this declaration - // - if node.localSymbol !== undefined - this is current declaration is exported and localSymbol points to the local symbol - // - if node.localSymbol === undefined - this node is non-exported so we can just pick the result of getSymbolOfNode - const symbol = getSymbolOfNode(node); - const localSymbol = node.localSymbol || symbol; + const commonDeclarationSpacesForExportsAndLocals = exportedDeclarationSpaces & nonExportedDeclarationSpaces; + const commonDeclarationSpacesForDefaultAndNonDefault = defaultExportedDeclarationSpaces & nonDefaultExportedDeclarationSpaces; - // Since the javascript won't do semantic analysis like typescript, - // if the javascript file comes before the typescript file and both contain same name functions, - // checkFunctionOrConstructorSymbol wouldn't be called if we didnt ignore javascript function. - const firstDeclaration = localSymbol.declarations?.find( - // Get first non javascript function declaration - declaration => declaration.kind === node.kind && !(declaration.flags & NodeFlags.JavaScriptFile)); + if (commonDeclarationSpacesForExportsAndLocals || commonDeclarationSpacesForDefaultAndNonDefault) { + // declaration spaces for exported and non-exported declarations intersect + for (const d of symbol.declarations!) { + const declarationSpaces = getDeclarationSpaces(d); - // Only type check the symbol once - if (node === firstDeclaration) { - checkFunctionOrConstructorSymbol(localSymbol); + const name = getNameOfDeclaration(d); + // Only error on the declarations that contributed to the intersecting spaces. + if (declarationSpaces & commonDeclarationSpacesForDefaultAndNonDefault) { + error(name, Diagnostics.Merged_declaration_0_cannot_include_a_default_export_declaration_Consider_adding_a_separate_export_default_0_declaration_instead, declarationNameToString(name)); } - - if (symbol.parent) { - // run check on export symbol to check that modifiers agree across all exported declarations - checkFunctionOrConstructorSymbol(symbol); + else if (declarationSpaces & commonDeclarationSpacesForExportsAndLocals) { + error(name, Diagnostics.Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local, declarationNameToString(name)); } } + } - const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body; - checkSourceElement(body); - checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node)); - - if (produceDiagnostics && !getEffectiveReturnTypeNode(node)) { - // Report an implicit any error if there is no body, no explicit return type, and node is not a private method - // in an ambient context - if (nodeIsMissing(body) && !isPrivateWithinAmbient(node)) { - reportImplicitAny(node, anyType); - } + function getDeclarationSpaces(decl: Declaration): DeclarationSpaces { + let d = decl as Node; + switch (d.kind) { + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: - if (functionFlags & FunctionFlags.Generator && nodeIsPresent(body)) { - // A generator with a body and no type annotation can still cause errors. It can error if the - // yielded values have no common supertype, or it can give an implicit any error if it has no - // yielded values. The only way to trigger these errors is to try checking its return type. - getReturnTypeOfSignature(getSignatureFromDeclaration(node)); - } - } + // A jsdoc typedef and callback are, by definition, type aliases. + // falls through + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + return DeclarationSpaces.ExportType; + case SyntaxKind.ModuleDeclaration: + return isAmbientModule(d as ModuleDeclaration) || getModuleInstanceState(d as ModuleDeclaration) !== ModuleInstanceState.NonInstantiated + ? DeclarationSpaces.ExportNamespace | DeclarationSpaces.ExportValue + : DeclarationSpaces.ExportNamespace; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.EnumMember: + return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue; + case SyntaxKind.SourceFile: + return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue | DeclarationSpaces.ExportNamespace; + case SyntaxKind.ExportAssignment: + case SyntaxKind.BinaryExpression: + const node = d as ExportAssignment | BinaryExpression; + const expression = isExportAssignment(node) ? node.expression : node.right; + // Export assigned entity name expressions act as aliases and should fall through, otherwise they export values + if (!isEntityNameExpression(expression)) { + return DeclarationSpaces.ExportValue; + } + d = expression; - // A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature - if (isInJSFile(node)) { - const typeTag = getJSDocTypeTag(node); - if (typeTag && typeTag.typeExpression && !getContextualCallSignature(getTypeFromTypeNode(typeTag.typeExpression), node)) { - error(typeTag.typeExpression.type, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature); - } + // The below options all declare an Alias, which is allowed to merge with other values within the importing module. + // falls through + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportClause: + let result = DeclarationSpaces.None; + const target = resolveAlias(getSymbolOfNode(d)!); + forEach(target.declarations, d => { + result |= getDeclarationSpaces(d); + }); + return result; + case SyntaxKind.VariableDeclaration: + case SyntaxKind.BindingElement: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ImportSpecifier: // https://github.com/Microsoft/TypeScript/pull/7591 + case SyntaxKind.Identifier: // https://github.com/microsoft/TypeScript/issues/36098 + // Identifiers are used as declarations of assignment declarations whose parents may be + // SyntaxKind.CallExpression - `Object.defineProperty(thing, "aField", {value: 42});` + // SyntaxKind.ElementAccessExpression - `thing["aField"] = 42;` or `thing["aField"];` (with a doc comment on it) + // or SyntaxKind.PropertyAccessExpression - `thing.aField = 42;` + // all of which are pretty much always values, or at least imply a value meaning. + // It may be apprpriate to treat these as aliases in the future. + return DeclarationSpaces.ExportValue; + default: + return Debug.failBadSyntaxKind(d); } } + } - function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void { - // May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`. - if (produceDiagnostics) { - const sourceFile = getSourceFileOfNode(node); - let potentiallyUnusedIdentifiers = allPotentiallyUnusedIdentifiers.get(sourceFile.path); - if (!potentiallyUnusedIdentifiers) { - potentiallyUnusedIdentifiers = []; - allPotentiallyUnusedIdentifiers.set(sourceFile.path, potentiallyUnusedIdentifiers); - } - // TODO: GH#22580 - // Debug.assert(addToSeen(seenPotentiallyUnusedIdentifiers, getNodeId(node)), "Adding potentially-unused identifier twice"); - potentiallyUnusedIdentifiers.push(node); - } + function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { + const promisedType = getPromisedTypeOfPromise(type, errorNode); + return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0); + } + + /** + * Gets the "promised type" of a promise. + * @param type The type of the promise. + * @remarks The "promised type" of a type is the type of the "value" parameter of the "onfulfilled" callback. + */ + function getPromisedTypeOfPromise(type: Type, errorNode?: Node): Type | undefined { + // + // { // type + // then( // thenFunction + // onfulfilled: ( // onfulfilledParameterType + // value: T // valueParameterType + // ) => any + // ): any; + // } + // + + if (isTypeAny(type)) { + return undefined; } - type PotentiallyUnusedIdentifier = - | SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration - | Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement - | Exclude | TypeAliasDeclaration - | InferTypeNode; + const typeAsPromise = type as PromiseOrAwaitableType; + if (typeAsPromise.promisedTypeOfPromise) { + return typeAsPromise.promisedTypeOfPromise; + } - function checkUnusedIdentifiers(potentiallyUnusedIdentifiers: readonly PotentiallyUnusedIdentifier[], addDiagnostic: AddUnusedDiagnostic) { - for (const node of potentiallyUnusedIdentifiers) { - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - checkUnusedClassMembers(node, addDiagnostic); - checkUnusedTypeParameters(node, addDiagnostic); - break; - case SyntaxKind.SourceFile: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.Block: - case SyntaxKind.CaseBlock: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - checkUnusedLocalsAndParameters(node, addDiagnostic); - break; - case SyntaxKind.Constructor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - if (node.body) { // Don't report unused parameters in overloads - checkUnusedLocalsAndParameters(node, addDiagnostic); - } - checkUnusedTypeParameters(node, addDiagnostic); - break; - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - checkUnusedTypeParameters(node, addDiagnostic); - break; - case SyntaxKind.InferType: - checkUnusedInferTypeParameter(node, addDiagnostic); - break; - default: - Debug.assertNever(node, "Node should not have been registered for unused identifiers check"); - } - } + if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false))) { + return typeAsPromise.promisedTypeOfPromise = getTypeArguments(type as GenericType)[0]; } - function errorUnusedLocal(declaration: Declaration, name: string, addDiagnostic: AddUnusedDiagnostic) { - const node = getNameOfDeclaration(declaration) || declaration; - const message = isTypeDeclaration(declaration) ? Diagnostics._0_is_declared_but_never_used : Diagnostics._0_is_declared_but_its_value_is_never_read; - addDiagnostic(declaration, UnusedKind.Local, createDiagnosticForNode(node, message, name)); + // primitives with a `{ then() }` won't be unwrapped/adopted. + if (allTypesAssignableToKind(type, TypeFlags.Primitive | TypeFlags.Never)) { + return undefined; } - function isIdentifierThatStartsWithUnderscore(node: Node) { - return isIdentifier(node) && idText(node).charCodeAt(0) === CharacterCodes._; + const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217 + if (isTypeAny(thenFunction)) { + return undefined; } - function checkUnusedClassMembers(node: ClassDeclaration | ClassExpression, addDiagnostic: AddUnusedDiagnostic): void { - for (const member of node.members) { - switch (member.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - if (member.kind === SyntaxKind.SetAccessor && member.symbol.flags & SymbolFlags.GetAccessor) { - // Already would have reported an error on the getter. - break; - } - const symbol = getSymbolOfNode(member); - if (!symbol.isReferenced - && (hasEffectiveModifier(member, ModifierFlags.Private) || isNamedDeclaration(member) && isPrivateIdentifier(member.name)) - && !(member.flags & NodeFlags.Ambient)) { - addDiagnostic(member, UnusedKind.Local, createDiagnosticForNode(member.name!, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolToString(symbol))); - } - break; - case SyntaxKind.Constructor: - for (const parameter of (member as ConstructorDeclaration).parameters) { - if (!parameter.symbol.isReferenced && hasSyntacticModifier(parameter, ModifierFlags.Private)) { - addDiagnostic(parameter, UnusedKind.Local, createDiagnosticForNode(parameter.name, Diagnostics.Property_0_is_declared_but_its_value_is_never_read, symbolName(parameter.symbol))); - } - } - break; - case SyntaxKind.IndexSignature: - case SyntaxKind.SemicolonClassElement: - case SyntaxKind.ClassStaticBlockDeclaration: - // Can't be private - break; - default: - Debug.fail("Unexpected class member"); - } + const thenSignatures = thenFunction ? getSignaturesOfType(thenFunction, SignatureKind.Call) : emptyArray; + if (thenSignatures.length === 0) { + if (errorNode) { + error(errorNode, Diagnostics.A_promise_must_have_a_then_method); } + return undefined; } - function checkUnusedInferTypeParameter(node: InferTypeNode, addDiagnostic: AddUnusedDiagnostic): void { - const { typeParameter } = node; - if (isTypeParameterUnused(typeParameter)) { - addDiagnostic(node, UnusedKind.Parameter, createDiagnosticForNode(node, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(typeParameter.name))); - } + const onfulfilledParameterType = getTypeWithFacts(getUnionType(map(thenSignatures, getTypeOfFirstParameterOfSignature)), TypeFacts.NEUndefinedOrNull); + if (isTypeAny(onfulfilledParameterType)) { + return undefined; } - function checkUnusedTypeParameters(node: ClassLikeDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration, addDiagnostic: AddUnusedDiagnostic): void { - // Only report errors on the last declaration for the type parameter container; - // this ensures that all uses have been accounted for. - const declarations = getSymbolOfNode(node).declarations; - if (!declarations || last(declarations) !== node) return; + const onfulfilledParameterSignatures = getSignaturesOfType(onfulfilledParameterType, SignatureKind.Call); + if (onfulfilledParameterSignatures.length === 0) { + if (errorNode) { + error(errorNode, Diagnostics.The_first_parameter_of_the_then_method_of_a_promise_must_be_a_callback); + } + return undefined; + } - const typeParameters = getEffectiveTypeParameterDeclarations(node); - const seenParentsWithEveryUnused = new Set(); + return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), UnionReduction.Subtype); + } - for (const typeParameter of typeParameters) { - if (!isTypeParameterUnused(typeParameter)) continue; + /** + * Gets the "awaited type" of a type. + * @param type The type to await. + * @param withAlias When `true`, wraps the "awaited type" in `Awaited` if needed. + * @remarks The "awaited type" of an expression is its "promised type" if the expression is a + * Promise-like type; otherwise, it is the type of the expression. This is used to reflect + * The runtime behavior of the `await` keyword. + */ + function checkAwaitedType(type: Type, withAlias: boolean, errorNode: Node, diagnosticMessage: DiagnosticMessage, arg0?: string | number): Type { + const awaitedType = withAlias ? + getAwaitedType(type, errorNode, diagnosticMessage, arg0) : + getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, arg0); + return awaitedType || errorType; + } - const name = idText(typeParameter.name); - const { parent } = typeParameter; - if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) { - if (tryAddToSet(seenParentsWithEveryUnused, parent)) { - const sourceFile = getSourceFileOfNode(parent); - const range = isJSDocTemplateTag(parent) - // Whole @template tag - ? rangeOfNode(parent) - // Include the `<>` in the error message - : rangeOfTypeParameters(sourceFile, parent.typeParameters!); - const only = parent.typeParameters!.length === 1; - //TODO: following line is possible reason for bug #41974, unusedTypeParameters_TemplateTag - const message = only ? Diagnostics._0_is_declared_but_its_value_is_never_read : Diagnostics.All_type_parameters_are_unused; - const arg0 = only ? name : undefined; - addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, message, arg0)); - } - } - else { - //TODO: following line is possible reason for bug #41974, unusedTypeParameters_TemplateTag - addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name)); - } - } + /** + * Determines whether a type is an object with a callable `then` member. + */ + function isThenableType(type: Type): boolean { + if (allTypesAssignableToKind(type, TypeFlags.Primitive | TypeFlags.Never)) { + // primitive types cannot be considered "thenable" since they are not objects. + return false; } - function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean { - return !(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name); + + const thenFunction = getTypeOfPropertyOfType(type, "then" as __String); + return !!thenFunction && getSignaturesOfType(getTypeWithFacts(thenFunction, TypeFacts.NEUndefinedOrNull), SignatureKind.Call).length > 0; + } + + interface AwaitedTypeInstantiation extends Type { + _awaitedTypeBrand: never; + aliasSymbol: Symbol; + aliasTypeArguments: readonly Type[]; + } + + function isAwaitedTypeInstantiation(type: Type): type is AwaitedTypeInstantiation { + if (type.flags & TypeFlags.Conditional) { + const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ false); + return !!awaitedSymbol && type.aliasSymbol === awaitedSymbol && type.aliasTypeArguments?.length === 1; } + return false; + } - function addToGroup(map: ESMap, key: K, value: V, getKey: (key: K) => number | string): void { - const keyString = String(getKey(key)); - const group = map.get(keyString); - if (group) { - group[1].push(value); - } - else { - map.set(keyString, [key, [value]]); - } + /** + * For a generic `Awaited`, gets `T`. + */ + function unwrapAwaitedType(type: Type) { + return type.flags & TypeFlags.Union ? mapType(type, unwrapAwaitedType) : + isAwaitedTypeInstantiation(type) ? type.aliasTypeArguments[0] : + type; + } + + function createAwaitedTypeIfNeeded(type: Type): Type { + // We wrap type `T` in `Awaited` based on the following conditions: + // - `T` is not already an `Awaited`, and + // - `T` is generic, and + // - One of the following applies: + // - `T` has no base constraint, or + // - The base constraint of `T` is `any`, `unknown`, `object`, or `{}`, or + // - The base constraint of `T` is an object type with a callable `then` method. + + if (isTypeAny(type)) { + return type; } - function tryGetRootParameterDeclaration(node: Node): ParameterDeclaration | undefined { - return tryCast(getRootDeclaration(node), isParameter); + // If this is already an `Awaited`, just return it. This helps to avoid `Awaited>` in higher-order. + if (isAwaitedTypeInstantiation(type)) { + return type; } - function isValidUnusedLocalDeclaration(declaration: Declaration): boolean { - if (isBindingElement(declaration)) { - if (isObjectBindingPattern(declaration.parent)) { - /** - * ignore starts with underscore names _ - * const { a: _a } = { a: 1 } - */ - return !!(declaration.propertyName && isIdentifierThatStartsWithUnderscore(declaration.name)); + // Only instantiate `Awaited` if `T` contains possibly non-primitive types. + if (isGenericObjectType(type)) { + const baseConstraint = getBaseConstraintOfType(type); + // Only instantiate `Awaited` if `T` has no base constraint, or the base constraint of `T` is `any`, `unknown`, `{}`, `object`, + // or is promise-like. + if (!baseConstraint || (baseConstraint.flags & TypeFlags.AnyOrUnknown) || isEmptyObjectType(baseConstraint) || isThenableType(baseConstraint)) { + // Nothing to do if `Awaited` doesn't exist + const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ true); + if (awaitedSymbol) { + // Unwrap unions that may contain `Awaited`, otherwise its possible to manufacture an `Awaited | U>` where + // an `Awaited` would suffice. + return getTypeAliasInstantiation(awaitedSymbol, [unwrapAwaitedType(type)]); } - return isIdentifierThatStartsWithUnderscore(declaration.name); } - return isAmbientModule(declaration) || - (isVariableDeclaration(declaration) && isForInOrOfStatement(declaration.parent.parent) || isImportedDeclaration(declaration)) && isIdentifierThatStartsWithUnderscore(declaration.name!); } - function checkUnusedLocalsAndParameters(nodeWithLocals: Node, addDiagnostic: AddUnusedDiagnostic): void { - // Ideally we could use the ImportClause directly as a key, but must wait until we have full ES6 maps. So must store key along with value. - const unusedImports = new Map(); - const unusedDestructures = new Map(); - const unusedVariables = new Map(); - nodeWithLocals.locals!.forEach(local => { - // If it's purely a type parameter, ignore, will be checked in `checkUnusedTypeParameters`. - // If it's a type parameter merged with a parameter, check if the parameter-side is used. - if (local.flags & SymbolFlags.TypeParameter ? !(local.flags & SymbolFlags.Variable && !(local.isReferenced! & SymbolFlags.Variable)) : local.isReferenced || local.exportSymbol) { - return; - } + Debug.assert(getPromisedTypeOfPromise(type) === undefined, "type provided should not be a non-generic 'promise'-like."); + return type; + } - if (local.declarations) { - for (const declaration of local.declarations) { - if (isValidUnusedLocalDeclaration(declaration)) { - continue; - } + /** + * Gets the "awaited type" of a type. + * + * The "awaited type" of an expression is its "promised type" if the expression is a + * Promise-like type; otherwise, it is the type of the expression. If the "promised + * type" is itself a Promise-like, the "promised type" is recursively unwrapped until a + * non-promise type is found. + * + * This is used to reflect the runtime behavior of the `await` keyword. + */ + function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { + const awaitedType = getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, arg0); + return awaitedType && createAwaitedTypeIfNeeded(awaitedType); + } - if (isImportedDeclaration(declaration)) { - addToGroup(unusedImports, importClauseFromImported(declaration), declaration, getNodeId); - } - else if (isBindingElement(declaration) && isObjectBindingPattern(declaration.parent)) { - // In `{ a, ...b }, `a` is considered used since it removes a property from `b`. `b` may still be unused though. - const lastElement = last(declaration.parent.elements); - if (declaration === lastElement || !last(declaration.parent.elements).dotDotDotToken) { - addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); - } - } - else if (isVariableDeclaration(declaration)) { - addToGroup(unusedVariables, declaration.parent, declaration, getNodeId); - } - else { - const parameter = local.valueDeclaration && tryGetRootParameterDeclaration(local.valueDeclaration); - const name = local.valueDeclaration && getNameOfDeclaration(local.valueDeclaration); - if (parameter && name) { - if (!isParameterPropertyDeclaration(parameter, parameter.parent) && !parameterIsThisKeyword(parameter) && !isIdentifierThatStartsWithUnderscore(name)) { - if (isBindingElement(declaration) && isArrayBindingPattern(declaration.parent)) { - addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); - } - else { - addDiagnostic(parameter, UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local))); - } - } - } - else { - errorUnusedLocal(declaration, symbolName(local), addDiagnostic); - } - } - } - } - }); - unusedImports.forEach(([importClause, unuseds]) => { - const importDecl = importClause.parent; - const nDeclarations = (importClause.name ? 1 : 0) + - (importClause.namedBindings ? - (importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? 1 : importClause.namedBindings.elements.length) - : 0); - if (nDeclarations === unuseds.length) { - addDiagnostic(importDecl, UnusedKind.Local, unuseds.length === 1 - ? createDiagnosticForNode(importDecl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(first(unuseds).name!)) - : createDiagnosticForNode(importDecl, Diagnostics.All_imports_in_import_declaration_are_unused)); - } - else { - for (const unused of unuseds) errorUnusedLocal(unused, idText(unused.name!), addDiagnostic); - } - }); - unusedDestructures.forEach(([bindingPattern, bindingElements]) => { - const kind = tryGetRootParameterDeclaration(bindingPattern.parent) ? UnusedKind.Parameter : UnusedKind.Local; - if (bindingPattern.elements.length === bindingElements.length) { - if (bindingElements.length === 1 && bindingPattern.parent.kind === SyntaxKind.VariableDeclaration && bindingPattern.parent.parent.kind === SyntaxKind.VariableDeclarationList) { - addToGroup(unusedVariables, bindingPattern.parent.parent, bindingPattern.parent, getNodeId); - } - else { - addDiagnostic(bindingPattern, kind, bindingElements.length === 1 - ? createDiagnosticForNode(bindingPattern, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(bindingElements).name)) - : createDiagnosticForNode(bindingPattern, Diagnostics.All_destructured_elements_are_unused)); - } - } - else { - for (const e of bindingElements) { - addDiagnostic(e, kind, createDiagnosticForNode(e, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(e.name))); - } - } - }); - unusedVariables.forEach(([declarationList, declarations]) => { - if (declarationList.declarations.length === declarations.length) { - addDiagnostic(declarationList, UnusedKind.Local, declarations.length === 1 - ? createDiagnosticForNode(first(declarations).name, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(declarations).name)) - : createDiagnosticForNode(declarationList.parent.kind === SyntaxKind.VariableStatement ? declarationList.parent : declarationList, Diagnostics.All_variables_are_unused)); - } - else { - for (const decl of declarations) { - addDiagnostic(decl, UnusedKind.Local, createDiagnosticForNode(decl, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(decl.name))); - } - } - }); + /** + * Gets the "awaited type" of a type without introducing an `Awaited` wrapper. + * + * @see {@link getAwaitedType} + */ + function getAwaitedTypeNoAlias(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { + if (isTypeAny(type)) { + return type; } - function bindingNameText(name: BindingName): string { - switch (name.kind) { - case SyntaxKind.Identifier: - return idText(name); - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ObjectBindingPattern: - return bindingNameText(cast(first(name.elements), isBindingElement).name); - default: - return Debug.assertNever(name); - } + // If this is already an `Awaited`, just return it. This avoids `Awaited>` in higher-order + if (isAwaitedTypeInstantiation(type)) { + return type; } - type ImportedDeclaration = ImportClause | ImportSpecifier | NamespaceImport; - function isImportedDeclaration(node: Node): node is ImportedDeclaration { - return node.kind === SyntaxKind.ImportClause || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.NamespaceImport; - } - function importClauseFromImported(decl: ImportedDeclaration): ImportClause { - return decl.kind === SyntaxKind.ImportClause ? decl : decl.kind === SyntaxKind.NamespaceImport ? decl.parent : decl.parent.parent; + // If we've already cached an awaited type, return a possible `Awaited` for it. + const typeAsAwaitable = type as PromiseOrAwaitableType; + if (typeAsAwaitable.awaitedTypeOfType) { + return typeAsAwaitable.awaitedTypeOfType; } - function checkBlock(node: Block) { - // Grammar checking for SyntaxKind.Block - if (node.kind === SyntaxKind.Block) { - checkGrammarStatementInAmbientContext(node); - } - if (isFunctionOrModuleBlock(node)) { - const saveFlowAnalysisDisabled = flowAnalysisDisabled; - forEach(node.statements, checkSourceElement); - flowAnalysisDisabled = saveFlowAnalysisDisabled; - } - else { - forEach(node.statements, checkSourceElement); - } - if (node.locals) { - registerForUnusedIdentifiersCheck(node); - } + // For a union, get a union of the awaited types of each constituent. + if (type.flags & TypeFlags.Union) { + const mapper = errorNode ? (constituentType: Type) => getAwaitedTypeNoAlias(constituentType, errorNode, diagnosticMessage, arg0) : getAwaitedTypeNoAlias; + return typeAsAwaitable.awaitedTypeOfType = mapType(type, mapper); } - function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) { - // no rest parameters \ declaration context \ overload - no codegen impact - if (languageVersion >= ScriptTarget.ES2015 || !hasRestParameter(node) || node.flags & NodeFlags.Ambient || nodeIsMissing((node as FunctionLikeDeclaration).body)) { - return; - } - - forEach(node.parameters, p => { - if (p.name && !isBindingPattern(p.name) && p.name.escapedText === argumentsSymbol.escapedName) { - errorSkippedOn("noEmit", p, Diagnostics.Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters); + const promisedType = getPromisedTypeOfPromise(type); + if (promisedType) { + if (type.id === promisedType.id || awaitedTypeStack.lastIndexOf(promisedType.id) >= 0) { + // Verify that we don't have a bad actor in the form of a promise whose + // promised type is the same as the promise type, or a mutually recursive + // promise. If so, we return undefined as we cannot guess the shape. If this + // were the actual case in the JavaScript, this Promise would never resolve. + // + // An example of a bad actor with a singly-recursive promise type might + // be: + // + // interface BadPromise { + // then( + // onfulfilled: (value: BadPromise) => any, + // onrejected: (error: any) => any): BadPromise; + // } + // + // The above interface will pass the PromiseLike check, and return a + // promised type of `BadPromise`. Since this is a self reference, we + // don't want to keep recursing ad infinitum. + // + // An example of a bad actor in the form of a mutually-recursive + // promise type might be: + // + // interface BadPromiseA { + // then( + // onfulfilled: (value: BadPromiseB) => any, + // onrejected: (error: any) => any): BadPromiseB; + // } + // + // interface BadPromiseB { + // then( + // onfulfilled: (value: BadPromiseA) => any, + // onrejected: (error: any) => any): BadPromiseA; + // } + // + if (errorNode) { + error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method); } - }); - } - - /** - * Checks whether an {@link Identifier}, in the context of another {@link Node}, would collide with a runtime value - * of {@link name} in an outer scope. This is used to check for collisions for downlevel transformations that - * require names like `Object`, `Promise`, `Reflect`, `require`, `exports`, etc. - */ - function needCollisionCheckForIdentifier(node: Node, identifier: Identifier | undefined, name: string): boolean { - if (identifier?.escapedText !== name) { - return false; + return undefined; } - if (node.kind === SyntaxKind.PropertyDeclaration || - node.kind === SyntaxKind.PropertySignature || - node.kind === SyntaxKind.MethodDeclaration || - node.kind === SyntaxKind.MethodSignature || - node.kind === SyntaxKind.GetAccessor || - node.kind === SyntaxKind.SetAccessor || - node.kind === SyntaxKind.PropertyAssignment) { - // it is ok to have member named '_super', '_this', `Promise`, etc. - member access is always qualified - return false; - } + // Keep track of the type we're about to unwrap to avoid bad recursive promise types. + // See the comments above for more information. + awaitedTypeStack.push(type.id); + const awaitedType = getAwaitedTypeNoAlias(promisedType, errorNode, diagnosticMessage, arg0); + awaitedTypeStack.pop(); - if (node.flags & NodeFlags.Ambient) { - // ambient context - no codegen impact - return false; + if (!awaitedType) { + return undefined; } - if (isImportClause(node) || isImportEqualsDeclaration(node) || isImportSpecifier(node)) { - // type-only imports do not require collision checks against runtime values. - if (isTypeOnlyImportOrExportDeclaration(node)) { - return false; - } - } + return typeAsAwaitable.awaitedTypeOfType = awaitedType; + } - const root = getRootDeclaration(node); - if (isParameter(root) && nodeIsMissing((root.parent as FunctionLikeDeclaration).body)) { - // just an overload - no codegen impact - return false; + // The type was not a promise, so it could not be unwrapped any further. + // As long as the type does not have a callable "then" property, it is + // safe to return the type; otherwise, an error is reported and we return + // undefined. + // + // An example of a non-promise "thenable" might be: + // + // await { then(): void {} } + // + // The "thenable" does not match the minimal definition for a promise. When + // a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise + // will never settle. We treat this as an error to help flag an early indicator + // of a runtime problem. If the user wants to return this value from an async + // function, they would need to wrap it in some other value. If they want it to + // be treated as a promise, they can cast to . + if (isThenableType(type)) { + if (errorNode) { + Debug.assertIsDefined(diagnosticMessage); + error(errorNode, diagnosticMessage, arg0); } - - return true; + return undefined; } - // this function will run after checking the source file so 'CaptureThis' is correct for all nodes - function checkIfThisIsCapturedInEnclosingScope(node: Node): void { - findAncestor(node, current => { - if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureThis) { - const isDeclaration = node.kind !== SyntaxKind.Identifier; - if (isDeclaration) { - error(getNameOfDeclaration(node as Declaration), Diagnostics.Duplicate_identifier_this_Compiler_uses_variable_declaration_this_to_capture_this_reference); - } - else { - error(node, Diagnostics.Expression_resolves_to_variable_declaration_this_that_compiler_uses_to_capture_this_reference); - } - return true; - } - return false; - }); - } + return typeAsAwaitable.awaitedTypeOfType = type; + } - function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void { - findAncestor(node, current => { - if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) { - const isDeclaration = node.kind !== SyntaxKind.Identifier; - if (isDeclaration) { - error(getNameOfDeclaration(node as Declaration), Diagnostics.Duplicate_identifier_newTarget_Compiler_uses_variable_declaration_newTarget_to_capture_new_target_meta_property_reference); - } - else { - error(node, Diagnostics.Expression_resolves_to_variable_declaration_newTarget_that_compiler_uses_to_capture_new_target_meta_property_reference); - } - return true; - } - return false; - }); - } + /** + * Checks the return type of an async function to ensure it is a compatible + * Promise implementation. + * + * This checks that an async function has a valid Promise-compatible return type. + * An async function has a valid Promise-compatible return type if the resolved value + * of the return type has a construct signature that takes in an `initializer` function + * that in turn supplies a `resolve` function as one of its arguments and results in an + * object with a callable `then` signature. + * + * @param node The signature to check + */ + function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode) { + // As part of our emit for an async function, we will need to emit the entity name of + // the return type annotation as an expression. To meet the necessary runtime semantics + // for __awaiter, we must also check that the type of the declaration (e.g. the static + // side or "constructor" of the promise type) is compatible `PromiseConstructorLike`. + // + // An example might be (from lib.es6.d.ts): + // + // interface Promise { ... } + // interface PromiseConstructor { + // new (...): Promise; + // } + // declare var Promise: PromiseConstructor; + // + // When an async function declares a return type annotation of `Promise`, we + // need to get the type of the `Promise` variable declaration above, which would + // be `PromiseConstructor`. + // + // The same case applies to a class: + // + // declare class Promise { + // constructor(...); + // then(...): Promise; + // } + // + const returnType = getTypeFromTypeNode(returnTypeNode); - function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier | undefined) { - // No need to check for require or exports for ES6 modules and later - if (moduleKind >= ModuleKind.ES2015 && !(moduleKind >= ModuleKind.Node12 && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { + if (languageVersion >= ScriptTarget.ES2015) { + if (isErrorType(returnType)) { return; } - - if (!name || !needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) { + const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); + if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) { + // The promise type was not a valid type reference to the global promise type, so we + // report an error and return the unknown type. + error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, typeToString(getAwaitedTypeNoAlias(returnType) || voidType)); return; } + } + else { + // Always mark the type node as referenced if it points to a value + markTypeNodeAsReferenced(returnTypeNode); - // Uninstantiated modules shouldnt do this check - if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { + if (isErrorType(returnType)) { return; } - // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent - const parent = getDeclarationContainer(node); - if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(parent as SourceFile)) { - // If the declaration happens to be in external module, report error that require and exports are reserved keywords - errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module, - declarationNameToString(name), declarationNameToString(name)); + const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode); + if (promiseConstructorName === undefined) { + error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(returnType)); + return; } - } - function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier | undefined): void { - if (!name || languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) { + const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true); + const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType; + if (isErrorType(promiseConstructorType)) { + if (promiseConstructorName.kind === SyntaxKind.Identifier && promiseConstructorName.escapedText === "Promise" && getTargetType(returnType) === getGlobalPromiseType(/*reportErrors*/ false)) { + error(returnTypeNode, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); + } + else { + error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); + } return; } - // Uninstantiated modules shouldnt do this check - if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { + const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(/*reportErrors*/ true); + if (globalPromiseConstructorLikeType === emptyObjectType) { + // If we couldn't resolve the global PromiseConstructorLike type we cannot verify + // compatibility with __awaiter. + error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); return; } - // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent - const parent = getDeclarationContainer(node); - if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(parent as SourceFile) && parent.flags & NodeFlags.HasAsyncFunctions) { - // If the declaration happens to be in external module, report error that Promise is a reserved identifier. - errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module_containing_async_functions, - declarationNameToString(name), declarationNameToString(name)); + if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeNode, + Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value)) { + return; } - } - function recordPotentialCollisionWithWeakMapSetInGeneratedCode(node: Node, name: Identifier): void { - if (languageVersion <= ScriptTarget.ES2021 - && (needCollisionCheckForIdentifier(node, name, "WeakMap") || needCollisionCheckForIdentifier(node, name, "WeakSet"))) { - potentialWeakMapSetCollisions.push(node); + // Verify there is no local declaration that could collide with the promise constructor. + const rootName = promiseConstructorName && getFirstIdentifier(promiseConstructorName); + const collidingSymbol = getSymbol(node.locals!, rootName.escapedText, SymbolFlags.Value); + if (collidingSymbol) { + error(collidingSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, + idText(rootName), + entityNameToString(promiseConstructorName)); + return; } } + checkAwaitedType(returnType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + } - function checkWeakMapSetCollision(node: Node) { - const enclosingBlockScope = getEnclosingBlockScopeContainer(node); - if (getNodeCheckFlags(enclosingBlockScope) & NodeCheckFlags.ContainsClassWithPrivateIdentifiers) { - Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name) && typeof node.name.escapedText === "string", "The target of a WeakMap/WeakSet collision check should be an identifier"); - errorSkippedOn("noEmit", node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, node.name.escapedText); - } - } + /** Check a decorator */ + function checkDecorator(node: Decorator): void { + const signature = getResolvedSignature(node); + checkDeprecatedSignature(signature, node); + const returnType = getReturnTypeOfSignature(signature); + if (returnType.flags & TypeFlags.Any) { + return; + } + + let expectedReturnType: Type; + const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); + let errorInfo: DiagnosticMessageChain | undefined; + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + const classSymbol = getSymbolOfNode(node.parent); + const classConstructorType = getTypeOfSymbol(classSymbol); + expectedReturnType = getUnionType([classConstructorType, voidType]); + break; - function recordPotentialCollisionWithReflectInGeneratedCode(node: Node, name: Identifier | undefined): void { - if (name && languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021 - && needCollisionCheckForIdentifier(node, name, "Reflect")) { - potentialReflectCollisions.push(node); - } + case SyntaxKind.Parameter: + expectedReturnType = voidType; + errorInfo = chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.The_return_type_of_a_parameter_decorator_function_must_be_either_void_or_any); + + break; + + case SyntaxKind.PropertyDeclaration: + expectedReturnType = voidType; + errorInfo = chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.The_return_type_of_a_property_decorator_function_must_be_either_void_or_any); + break; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const methodType = getTypeOfNode(node.parent); + const descriptorType = createTypedPropertyDescriptorType(methodType); + expectedReturnType = getUnionType([descriptorType, voidType]); + break; + + default: + return Debug.fail(); } - function checkReflectCollision(node: Node) { - let hasCollision = false; - if (isClassExpression(node)) { - // ClassExpression names don't contribute to their containers, but do matter for any of their block-scoped members. - for (const member of node.members) { - if (getNodeCheckFlags(member) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { - hasCollision = true; - break; - } - } - } - else if (isFunctionExpression(node)) { - // FunctionExpression names don't contribute to their containers, but do matter for their contents - if (getNodeCheckFlags(node) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { - hasCollision = true; - } - } - else { - const container = getEnclosingBlockScopeContainer(node); - if (container && getNodeCheckFlags(container) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { - hasCollision = true; - } - } - if (hasCollision) { - Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name), "The target of a Reflect collision check should be an identifier"); - errorSkippedOn("noEmit", node, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_when_emitting_super_references_in_static_initializers, - declarationNameToString(node.name), - "Reflect"); - } + checkTypeAssignableTo( + returnType, + expectedReturnType, + node, + headMessage, + () => errorInfo); + } + + /** + * If a TypeNode can be resolved to a value symbol imported from an external module, it is + * marked as referenced to prevent import elision. + */ + function markTypeNodeAsReferenced(node: TypeNode) { + markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node)); + } + + function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined) { + if (!typeName) return; + + const rootName = getFirstIdentifier(typeName); + const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; + const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isReference*/ true); + if (rootSymbol + && rootSymbol.flags & SymbolFlags.Alias + && symbolIsValue(rootSymbol) + && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) + && !getTypeOnlyAliasDeclaration(rootSymbol)) { + markAliasSymbolAsReferenced(rootSymbol); } + } - function checkCollisionsForDeclarationName(node: Node, name: Identifier | undefined) { - if (!name) return; - checkCollisionWithRequireExportsInGeneratedCode(node, name); - checkCollisionWithGlobalPromiseInGeneratedCode(node, name); - recordPotentialCollisionWithWeakMapSetInGeneratedCode(node, name); - recordPotentialCollisionWithReflectInGeneratedCode(node, name); - if (isClassLike(node)) { - checkTypeNameIsReserved(name, Diagnostics.Class_name_cannot_be_0); - if (!(node.flags & NodeFlags.Ambient)) { - checkClassNameCollisionWithObject(name); - } - } - else if (isEnumDeclaration(node)) { - checkTypeNameIsReserved(name, Diagnostics.Enum_name_cannot_be_0); - } + /** + * This function marks the type used for metadata decorator as referenced if it is import + * from external module. + * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in + * union and intersection type + * @param node + */ + function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { + const entityName = getEntityNameForDecoratorMetadata(node); + if (entityName && isEntityName(entityName)) { + markEntityNameOrEntityExpressionAsReference(entityName); } + } - function checkVarDeclaredNamesNotShadowed(node: VariableDeclaration | BindingElement) { - // - ScriptBody : StatementList - // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList - // also occurs in the VarDeclaredNames of StatementList. + function getEntityNameForDecoratorMetadata(node: TypeNode | undefined): EntityName | undefined { + if (node) { + switch (node.kind) { + case SyntaxKind.IntersectionType: + case SyntaxKind.UnionType: + return getEntityNameForDecoratorMetadataFromTypeList((node as UnionOrIntersectionTypeNode).types); - // - Block : { StatementList } - // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList - // also occurs in the VarDeclaredNames of StatementList. + case SyntaxKind.ConditionalType: + return getEntityNameForDecoratorMetadataFromTypeList([(node as ConditionalTypeNode).trueType, (node as ConditionalTypeNode).falseType]); - // Variable declarations are hoisted to the top of their function scope. They can shadow - // block scoped declarations, which bind tighter. this will not be flagged as duplicate definition - // by the binder as the declaration scope is different. - // A non-initialized declaration is a no-op as the block declaration will resolve before the var - // declaration. the problem is if the declaration has an initializer. this will act as a write to the - // block declared value. this is fine for let, but not const. - // Only consider declarations with initializers, uninitialized const declarations will not - // step on a let/const variable. - // Do not consider const and const declarations, as duplicate block-scoped declarations - // are handled by the binder. - // We are only looking for const declarations that step on let\const declarations from a - // different scope. e.g.: - // { - // const x = 0; // localDeclarationSymbol obtained after name resolution will correspond to this declaration - // const x = 0; // symbol for this declaration will be 'symbol' - // } + case SyntaxKind.ParenthesizedType: + case SyntaxKind.NamedTupleMember: + return getEntityNameForDecoratorMetadata((node as ParenthesizedTypeNode).type); - // skip block-scoped variables and parameters - if ((getCombinedNodeFlags(node) & NodeFlags.BlockScoped) !== 0 || isParameterDeclaration(node)) { - return; + case SyntaxKind.TypeReference: + return (node as TypeReferenceNode).typeName; } + } + } - // skip variable declarations that don't have initializers - // NOTE: in ES6 spec initializer is required in variable declarations where name is binding pattern - // so we'll always treat binding elements as initialized - if (node.kind === SyntaxKind.VariableDeclaration && !node.initializer) { - return; + function getEntityNameForDecoratorMetadataFromTypeList(types: readonly TypeNode[]): EntityName | undefined { + let commonEntityName: EntityName | undefined; + for (let typeNode of types) { + while (typeNode.kind === SyntaxKind.ParenthesizedType || typeNode.kind === SyntaxKind.NamedTupleMember) { + typeNode = (typeNode as ParenthesizedTypeNode | NamedTupleMember).type; // Skip parens if need be + } + if (typeNode.kind === SyntaxKind.NeverKeyword) { + continue; // Always elide `never` from the union/intersection if possible + } + if (!strictNullChecks && (typeNode.kind === SyntaxKind.LiteralType && (typeNode as LiteralTypeNode).literal.kind === SyntaxKind.NullKeyword || typeNode.kind === SyntaxKind.UndefinedKeyword)) { + continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks + } + const individualEntityName = getEntityNameForDecoratorMetadata(typeNode); + if (!individualEntityName) { + // Individual is something like string number + // So it would be serialized to either that type or object + // Safe to return here + return undefined; } - const symbol = getSymbolOfNode(node); - if (symbol.flags & SymbolFlags.FunctionScopedVariable) { - if (!isIdentifier(node.name)) return Debug.fail(); - const localDeclarationSymbol = resolveName(node, node.name.escapedText, SymbolFlags.Variable, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); - if (localDeclarationSymbol && - localDeclarationSymbol !== symbol && - localDeclarationSymbol.flags & SymbolFlags.BlockScopedVariable) { - if (getDeclarationNodeFlagsFromSymbol(localDeclarationSymbol) & NodeFlags.BlockScoped) { - const varDeclList = getAncestor(localDeclarationSymbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!; - const container = - varDeclList.parent.kind === SyntaxKind.VariableStatement && varDeclList.parent.parent - ? varDeclList.parent.parent - : undefined; - - // names of block-scoped and function scoped variables can collide only - // if block scoped variable is defined in the function\module\source file scope (because of variable hoisting) - const namesShareScope = - container && - (container.kind === SyntaxKind.Block && isFunctionLike(container.parent) || - container.kind === SyntaxKind.ModuleBlock || - container.kind === SyntaxKind.ModuleDeclaration || - container.kind === SyntaxKind.SourceFile); - - // here we know that function scoped variable is shadowed by block scoped one - // if they are defined in the same scope - binder has already reported redeclaration error - // otherwise if variable has an initializer - show error that initialization will fail - // since LHS will be block scoped name instead of function scoped - if (!namesShareScope) { - const name = symbolToString(localDeclarationSymbol); - error(node, Diagnostics.Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1, name, name); - } - } + if (commonEntityName) { + // Note this is in sync with the transformation that happens for type node. + // Keep this in sync with serializeUnionOrIntersectionType + // Verify if they refer to same entity and is identifier + // return undefined if they dont match because we would emit object + if (!isIdentifier(commonEntityName) || + !isIdentifier(individualEntityName) || + commonEntityName.escapedText !== individualEntityName.escapedText) { + return undefined; } } + else { + commonEntityName = individualEntityName; + } } + return commonEntityName; + } - function convertAutoToAny(type: Type) { - return type === autoType ? anyType : type === autoArrayType ? anyArrayType : type; + function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined { + const typeNode = getEffectiveTypeAnnotationNode(node); + return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; + } + + /** Check the decorators of a node */ + function checkDecorators(node: Node): void { + if (!node.decorators) { + return; } - // Check variable, parameter, or property declaration - function checkVariableLikeDeclaration(node: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement) { - checkDecorators(node); - if (!isBindingElement(node)) { - checkSourceElement(node.type); - } + // skip this check for nodes that cannot have decorators. These should have already had an error reported by + // checkGrammarDecorators. + if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { + return; + } - // JSDoc `function(string, string): string` syntax results in parameters with no name - if (!node.name) { - return; - } + if (!compilerOptions.experimentalDecorators) { + error(node, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning); + } - // For a computed property, just check the initializer and exit - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); - if (node.initializer) { - checkExpressionCached(node.initializer); - } - } + const firstDecorator = node.decorators[0]; + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate); + if (node.kind === SyntaxKind.Parameter) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param); + } - if (isBindingElement(node)) { - if (isObjectBindingPattern(node.parent) && node.dotDotDotToken && languageVersion < ScriptTarget.ES2018) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Rest); - } - // check computed properties inside property names of binding elements - if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.propertyName); - } + if (compilerOptions.emitDecoratorMetadata) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); - // check private/protected variable access - const parent = node.parent.parent; - const parentType = getTypeForBindingElementParent(parent); - const name = node.propertyName || node.name; - if (parentType && !isBindingPattern(name)) { - const exprType = getLiteralTypeFromPropertyName(name); - if (isTypeUsableAsPropertyName(exprType)) { - const nameText = getPropertyNameFromType(exprType); - const property = getPropertyOfType(parentType, nameText); - if (property) { - markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isSelfTypeAccess*/ false); // A destructuring is never a write-only reference. - checkPropertyAccessibility(node, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, /*writing*/ false, parentType, property); + // we only need to perform these checks if we are emitting serialized type metadata for the target of a decorator. + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + const constructor = getFirstConstructorWithBody(node as ClassDeclaration); + if (constructor) { + for (const parameter of constructor.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); } } - } - } - - // For a binding pattern, check contained binding elements - if (isBindingPattern(node.name)) { - if (node.name.kind === SyntaxKind.ArrayBindingPattern && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); - } + break; - forEach(node.name.elements, checkSourceElement); - } - // For a parameter declaration with an initializer, error and exit if the containing function doesn't have a body - if (node.initializer && isParameterDeclaration(node) && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { - error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation); - return; - } - // For a binding pattern, validate the initializer and exit - if (isBindingPattern(node.name)) { - const needCheckInitializer = node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement; - const needCheckWidenedType = node.name.elements.length === 0; - if (needCheckInitializer || needCheckWidenedType) { - // Don't validate for-in initializer as it is already an error - const widenedType = getWidenedTypeForVariableLikeDeclaration(node); - if (needCheckInitializer) { - const initializerType = checkExpressionCached(node.initializer!); - if (strictNullChecks && needCheckWidenedType) { - checkNonNullNonVoidType(initializerType, node); - } - else { - checkTypeAssignableToAndOptionallyElaborate(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, node.initializer); - } - } - // check the binding pattern with empty elements - if (needCheckWidenedType) { - if (isArrayBindingPattern(node.name)) { - checkIteratedTypeOrElementType(IterationUse.Destructuring, widenedType, undefinedType, node); - } - else if (strictNullChecks) { - checkNonNullNonVoidType(widenedType, node); - } + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfNode(node as AccessorDeclaration), otherKind); + markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node as AccessorDeclaration) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); + break; + case SyntaxKind.MethodDeclaration: + for (const parameter of (node as FunctionLikeDeclaration).parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); } - } - return; - } - // For a commonjs `const x = require`, validate the alias and exit - const symbol = getSymbolOfNode(node); - if (symbol.flags & SymbolFlags.Alias && isRequireVariableDeclaration(node)) { - checkAliasSymbol(node); - return; - } - const type = convertAutoToAny(getTypeOfSymbol(symbol)); - if (node === symbol.valueDeclaration) { - // Node is the primary declaration of the symbol, just validate the initializer - // Don't validate for-in initializer as it is already an error - const initializer = getEffectiveInitializer(node); - if (initializer) { - const isJSObjectLiteralInitializer = isInJSFile(node) && - isObjectLiteralExpression(initializer) && - (initializer.properties.length === 0 || isPrototypeAccess(node.name)) && - !!symbol.exports?.size; - if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { - checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined); - } - } - if (symbol.declarations && symbol.declarations.length > 1) { - if (some(symbol.declarations, d => d !== node && isVariableLike(d) && !areDeclarationFlagsIdentical(d, node))) { - error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node as FunctionLikeDeclaration)); + break; + + case SyntaxKind.PropertyDeclaration: + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node as ParameterDeclaration)); + break; + + case SyntaxKind.Parameter: + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node as ParameterDeclaration)); + const containingSignature = (node as ParameterDeclaration).parent; + for (const parameter of containingSignature.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); } - } + break; } - else { - // Node is a secondary declaration, check that type is identical to primary declaration and check that - // initializer is consistent with type associated with the node - const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node)); + } - if (!isErrorType(type) && !isErrorType(declarationType) && - !isTypeIdenticalTo(type, declarationType) && - !(symbol.flags & SymbolFlags.Assignment)) { - errorNextVariableOrPropertyDeclarationMustHaveSameType(symbol.valueDeclaration, type, node, declarationType); - } - if (node.initializer) { - checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined); + forEach(node.decorators, checkDecorator); + } + + function checkFunctionDeclaration(node: FunctionDeclaration): void { + if (produceDiagnostics) { + checkFunctionOrMethodDeclaration(node); + checkGrammarForGenerator(node); + checkCollisionsForDeclarationName(node, node.name); + } + } + + function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) { + if (!node.typeExpression) { + // If the node had `@property` tags, `typeExpression` would have been set to the first property tag. + error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags); + } + + if (node.name) { + checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); + } + checkSourceElement(node.typeExpression); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + } + + function checkJSDocTemplateTag(node: JSDocTemplateTag): void { + checkSourceElement(node.constraint); + for (const tp of node.typeParameters) { + checkSourceElement(tp); + } + } + + function checkJSDocTypeTag(node: JSDocTypeTag) { + checkSourceElement(node.typeExpression); + } + + function checkJSDocParameterTag(node: JSDocParameterTag) { + checkSourceElement(node.typeExpression); + if (!getParameterSymbolFromJSDoc(node)) { + const decl = getHostSignatureFromJSDoc(node); + // don't issue an error for invalid hosts -- just functions -- + // and give a better error message when the host function mentions `arguments` + // but the tag doesn't have an array type + if (decl) { + const i = getJSDocTags(decl).filter(isJSDocParameterTag).indexOf(node); + if (i > -1 && i < decl.parameters.length && isBindingPattern(decl.parameters[i].name)) { + return; } - if (symbol.valueDeclaration && !areDeclarationFlagsIdentical(node, symbol.valueDeclaration)) { - error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); + if (!containsArgumentsReference(decl)) { + if (isQualifiedName(node.name)) { + error(node.name, + Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, + entityNameToString(node.name), + entityNameToString(node.name.left)); + } + else { + error(node.name, + Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name, + idText(node.name)); + } } - } - if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature) { - // We know we don't have a binding pattern or computed name here - checkExportsOnMergedDeclarations(node); - if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { - checkVarDeclaredNamesNotShadowed(node); + else if (findLast(getJSDocTags(decl), isJSDocParameterTag) === node && + node.typeExpression && node.typeExpression.type && + !isArrayType(getTypeFromTypeNode(node.typeExpression.type))) { + error(node.name, + Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type, + idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name)); } - checkCollisionsForDeclarationName(node, node.name); } } + } - function errorNextVariableOrPropertyDeclarationMustHaveSameType(firstDeclaration: Declaration | undefined, firstType: Type, nextDeclaration: Declaration, nextType: Type): void { - const nextDeclarationName = getNameOfDeclaration(nextDeclaration); - const message = nextDeclaration.kind === SyntaxKind.PropertyDeclaration || nextDeclaration.kind === SyntaxKind.PropertySignature - ? Diagnostics.Subsequent_property_declarations_must_have_the_same_type_Property_0_must_be_of_type_1_but_here_has_type_2 - : Diagnostics.Subsequent_variable_declarations_must_have_the_same_type_Variable_0_must_be_of_type_1_but_here_has_type_2; - const declName = declarationNameToString(nextDeclarationName); - const err = error( - nextDeclarationName, - message, - declName, - typeToString(firstType), - typeToString(nextType) - ); - if (firstDeclaration) { - addRelatedInfo(err, - createDiagnosticForNode(firstDeclaration, Diagnostics._0_was_also_declared_here, declName) - ); - } - } + function checkJSDocPropertyTag(node: JSDocPropertyTag) { + checkSourceElement(node.typeExpression); + } - function areDeclarationFlagsIdentical(left: Declaration, right: Declaration) { - if ((left.kind === SyntaxKind.Parameter && right.kind === SyntaxKind.VariableDeclaration) || - (left.kind === SyntaxKind.VariableDeclaration && right.kind === SyntaxKind.Parameter)) { - // Differences in optionality between parameters and variables are allowed. - return true; - } + function checkJSDocFunctionType(node: JSDocFunctionType): void { + if (produceDiagnostics && !node.type && !isJSDocConstructSignature(node)) { + reportImplicitAny(node, anyType); + } + checkSignatureDeclaration(node); + } - if (hasQuestionToken(left) !== hasQuestionToken(right)) { - return false; - } + function checkJSDocImplementsTag(node: JSDocImplementsTag): void { + const classLike = getEffectiveJSDocHost(node); + if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) { + error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName)); + } + } - const interestingFlags = ModifierFlags.Private | - ModifierFlags.Protected | - ModifierFlags.Async | - ModifierFlags.Abstract | - ModifierFlags.Readonly | - ModifierFlags.Static; + function checkJSDocAugmentsTag(node: JSDocAugmentsTag): void { + const classLike = getEffectiveJSDocHost(node); + if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) { + error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName)); + return; + } - return getSelectedEffectiveModifierFlags(left, interestingFlags) === getSelectedEffectiveModifierFlags(right, interestingFlags); + const augmentsTags = getJSDocTags(classLike).filter(isJSDocAugmentsTag); + Debug.assert(augmentsTags.length > 0); + if (augmentsTags.length > 1) { + error(augmentsTags[1], Diagnostics.Class_declarations_cannot_have_more_than_one_augments_or_extends_tag); } - function checkVariableDeclaration(node: VariableDeclaration) { - tracing?.push(tracing.Phase.Check, "checkVariableDeclaration", { kind: node.kind, pos: node.pos, end: node.end }); - checkGrammarVariableDeclaration(node); - checkVariableLikeDeclaration(node); - tracing?.pop(); + const name = getIdentifierFromEntityNameExpression(node.class.expression); + const extend = getClassExtendsHeritageElement(classLike); + if (extend) { + const className = getIdentifierFromEntityNameExpression(extend.expression); + if (className && name.escapedText !== className.escapedText) { + error(name, Diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, idText(node.tagName), idText(name), idText(className)); + } } + } - function checkBindingElement(node: BindingElement) { - checkGrammarBindingElement(node); - return checkVariableLikeDeclaration(node); + function checkJSDocAccessibilityModifiers(node: JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag): void { + const host = getJSDocHost(node); + if (host && isPrivateIdentifierClassElementDeclaration(host)) { + error(node, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier); } + } - function checkVariableStatement(node: VariableStatement) { - // Grammar checking - if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarVariableDeclarationList(node.declarationList)) checkGrammarForDisallowedLetOrConstStatement(node); - forEach(node.declarationList.declarations, checkSourceElement); + function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier | PrivateIdentifier; + function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined; + function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined { + switch (node.kind) { + case SyntaxKind.Identifier: + return node as Identifier; + case SyntaxKind.PropertyAccessExpression: + return (node as PropertyAccessExpression).name; + default: + return undefined; } + } - function checkExpressionStatement(node: ExpressionStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); + function checkFunctionOrMethodDeclaration(node: FunctionDeclaration | MethodDeclaration | MethodSignature): void { + checkDecorators(node); + checkSignatureDeclaration(node); + const functionFlags = getFunctionFlags(node); - checkExpression(node.expression); + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name && node.name.kind === SyntaxKind.ComputedPropertyName) { + // This check will account for methods in class/interface declarations, + // as well as accessors in classes/object literals + checkComputedPropertyName(node.name); } - function checkIfStatement(node: IfStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); - const type = checkTruthinessExpression(node.expression); - checkTestingKnownTruthyCallableOrAwaitableType(node.expression, type, node.thenStatement); - checkSourceElement(node.thenStatement); + if (hasBindableName(node)) { + // first we want to check the local symbol that contain this declaration + // - if node.localSymbol !== undefined - this is current declaration is exported and localSymbol points to the local symbol + // - if node.localSymbol === undefined - this node is non-exported so we can just pick the result of getSymbolOfNode + const symbol = getSymbolOfNode(node); + const localSymbol = node.localSymbol || symbol; + + // Since the javascript won't do semantic analysis like typescript, + // if the javascript file comes before the typescript file and both contain same name functions, + // checkFunctionOrConstructorSymbol wouldn't be called if we didnt ignore javascript function. + const firstDeclaration = localSymbol.declarations?.find( + // Get first non javascript function declaration + declaration => declaration.kind === node.kind && !(declaration.flags & NodeFlags.JavaScriptFile)); - if (node.thenStatement.kind === SyntaxKind.EmptyStatement) { - error(node.thenStatement, Diagnostics.The_body_of_an_if_statement_cannot_be_the_empty_statement); + // Only type check the symbol once + if (node === firstDeclaration) { + checkFunctionOrConstructorSymbol(localSymbol); } - checkSourceElement(node.elseStatement); + if (symbol.parent) { + // run check on export symbol to check that modifiers agree across all exported declarations + checkFunctionOrConstructorSymbol(symbol); + } } - function checkTestingKnownTruthyCallableOrAwaitableType(condExpr: Expression, type: Type, body?: Statement | Expression) { - if (!strictNullChecks) return; - if (getFalsyFlags(type)) return; + const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body; + checkSourceElement(body); + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node)); - const location = isBinaryExpression(condExpr) ? condExpr.right : condExpr; - if (isPropertyAccessExpression(location) && isTypeAssertion(location.expression)) { - return; + if (produceDiagnostics && !getEffectiveReturnTypeNode(node)) { + // Report an implicit any error if there is no body, no explicit return type, and node is not a private method + // in an ambient context + if (nodeIsMissing(body) && !isPrivateWithinAmbient(node)) { + reportImplicitAny(node, anyType); } - const testedNode = isIdentifier(location) ? location - : isPropertyAccessExpression(location) ? location.name - : isBinaryExpression(location) && isIdentifier(location.right) ? location.right - : undefined; - - // While it technically should be invalid for any known-truthy value - // to be tested, we de-scope to functions and Promises unreferenced in - // the block as a heuristic to identify the most common bugs. There - // are too many false positives for values sourced from type - // definitions without strictNullChecks otherwise. - const callSignatures = getSignaturesOfType(type, SignatureKind.Call); - const isPromise = !!getAwaitedTypeOfPromise(type); - if (callSignatures.length === 0 && !isPromise) { - return; + if (functionFlags & FunctionFlags.Generator && nodeIsPresent(body)) { + // A generator with a body and no type annotation can still cause errors. It can error if the + // yielded values have no common supertype, or it can give an implicit any error if it has no + // yielded values. The only way to trigger these errors is to try checking its return type. + getReturnTypeOfSignature(getSignatureFromDeclaration(node)); } + } - const testedSymbol = testedNode && getSymbolAtLocation(testedNode); - if (!testedSymbol && !isPromise) { - return; + // A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature + if (isInJSFile(node)) { + const typeTag = getJSDocTypeTag(node); + if (typeTag && typeTag.typeExpression && !getContextualCallSignature(getTypeFromTypeNode(typeTag.typeExpression), node)) { + error(typeTag.typeExpression.type, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature); } + } + } - const isUsed = testedSymbol && isBinaryExpression(condExpr.parent) && isSymbolUsedInBinaryExpressionChain(condExpr.parent, testedSymbol) - || testedSymbol && body && isSymbolUsedInConditionBody(condExpr, body, testedNode, testedSymbol); - if (!isUsed) { - if (isPromise) { - errorAndMaybeSuggestAwait( - location, - /*maybeMissingAwait*/ true, - Diagnostics.This_condition_will_always_return_true_since_this_0_is_always_defined, - getTypeNameForErrorDisplay(type)); - } - else { - error(location, Diagnostics.This_condition_will_always_return_true_since_this_function_is_always_defined_Did_you_mean_to_call_it_instead); - } + function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void { + // May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`. + if (produceDiagnostics) { + const sourceFile = getSourceFileOfNode(node); + let potentiallyUnusedIdentifiers = allPotentiallyUnusedIdentifiers.get(sourceFile.path); + if (!potentiallyUnusedIdentifiers) { + potentiallyUnusedIdentifiers = []; + allPotentiallyUnusedIdentifiers.set(sourceFile.path, potentiallyUnusedIdentifiers); } + // TODO: GH#22580 + // Debug.assert(addToSeen(seenPotentiallyUnusedIdentifiers, getNodeId(node)), "Adding potentially-unused identifier twice"); + potentiallyUnusedIdentifiers.push(node); } + } - function isSymbolUsedInConditionBody(expr: Expression, body: Statement | Expression, testedNode: Node, testedSymbol: Symbol): boolean { - return !!forEachChild(body, function check(childNode): boolean | undefined { - if (isIdentifier(childNode)) { - const childSymbol = getSymbolAtLocation(childNode); - if (childSymbol && childSymbol === testedSymbol) { - // If the test was a simple identifier, the above check is sufficient - if (isIdentifier(expr)) { - return true; - } - // Otherwise we need to ensure the symbol is called on the same target - let testedExpression = testedNode.parent; - let childExpression = childNode.parent; - while (testedExpression && childExpression) { - if (isIdentifier(testedExpression) && isIdentifier(childExpression) || - testedExpression.kind === SyntaxKind.ThisKeyword && childExpression.kind === SyntaxKind.ThisKeyword) { - return getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression); - } - else if (isPropertyAccessExpression(testedExpression) && isPropertyAccessExpression(childExpression)) { - if (getSymbolAtLocation(testedExpression.name) !== getSymbolAtLocation(childExpression.name)) { - return false; - } - childExpression = childExpression.expression; - testedExpression = testedExpression.expression; - } - else if (isCallExpression(testedExpression) && isCallExpression(childExpression)) { - childExpression = childExpression.expression; - testedExpression = testedExpression.expression; - } - else { - return false; - } - } - } - } - return forEachChild(childNode, check); - }); - } + type PotentiallyUnusedIdentifier = + | SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration + | Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement + | Exclude | TypeAliasDeclaration + | InferTypeNode; - function isSymbolUsedInBinaryExpressionChain(node: Node, testedSymbol: Symbol): boolean { - while (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { - const isUsed = forEachChild(node.right, function visit(child): boolean | undefined { - if (isIdentifier(child)) { - const symbol = getSymbolAtLocation(child); - if (symbol && symbol === testedSymbol) { - return true; - } + function checkUnusedIdentifiers(potentiallyUnusedIdentifiers: readonly PotentiallyUnusedIdentifier[], addDiagnostic: AddUnusedDiagnostic) { + for (const node of potentiallyUnusedIdentifiers) { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + checkUnusedClassMembers(node, addDiagnostic); + checkUnusedTypeParameters(node, addDiagnostic); + break; + case SyntaxKind.SourceFile: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.Block: + case SyntaxKind.CaseBlock: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + checkUnusedLocalsAndParameters(node, addDiagnostic); + break; + case SyntaxKind.Constructor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + if (node.body) { // Don't report unused parameters in overloads + checkUnusedLocalsAndParameters(node, addDiagnostic); } - return forEachChild(child, visit); - }); - if (isUsed) { - return true; - } - node = node.parent; + checkUnusedTypeParameters(node, addDiagnostic); + break; + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + checkUnusedTypeParameters(node, addDiagnostic); + break; + case SyntaxKind.InferType: + checkUnusedInferTypeParameter(node, addDiagnostic); + break; + default: + Debug.assertNever(node, "Node should not have been registered for unused identifiers check"); } - return false; } + } - function checkDoStatement(node: DoStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); + function errorUnusedLocal(declaration: Declaration, name: string, addDiagnostic: AddUnusedDiagnostic) { + const node = getNameOfDeclaration(declaration) || declaration; + const message = isTypeDeclaration(declaration) ? Diagnostics._0_is_declared_but_never_used : Diagnostics._0_is_declared_but_its_value_is_never_read; + addDiagnostic(declaration, UnusedKind.Local, createDiagnosticForNode(node, message, name)); + } - checkSourceElement(node.statement); - checkTruthinessExpression(node.expression); - } + function isIdentifierThatStartsWithUnderscore(node: Node) { + return isIdentifier(node) && idText(node).charCodeAt(0) === CharacterCodes._; + } - function checkWhileStatement(node: WhileStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); + function checkUnusedClassMembers(node: ClassDeclaration | ClassExpression, addDiagnostic: AddUnusedDiagnostic): void { + for (const member of node.members) { + switch (member.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + if (member.kind === SyntaxKind.SetAccessor && member.symbol.flags & SymbolFlags.GetAccessor) { + // Already would have reported an error on the getter. + break; + } + const symbol = getSymbolOfNode(member); + if (!symbol.isReferenced + && (hasEffectiveModifier(member, ModifierFlags.Private) || isNamedDeclaration(member) && isPrivateIdentifier(member.name)) + && !(member.flags & NodeFlags.Ambient)) { + addDiagnostic(member, UnusedKind.Local, createDiagnosticForNode(member.name!, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolToString(symbol))); + } + break; + case SyntaxKind.Constructor: + for (const parameter of (member as ConstructorDeclaration).parameters) { + if (!parameter.symbol.isReferenced && hasSyntacticModifier(parameter, ModifierFlags.Private)) { + addDiagnostic(parameter, UnusedKind.Local, createDiagnosticForNode(parameter.name, Diagnostics.Property_0_is_declared_but_its_value_is_never_read, symbolName(parameter.symbol))); + } + } + break; + case SyntaxKind.IndexSignature: + case SyntaxKind.SemicolonClassElement: + case SyntaxKind.ClassStaticBlockDeclaration: + // Can't be private + break; + default: + Debug.fail("Unexpected class member"); + } + } + } - checkTruthinessExpression(node.expression); - checkSourceElement(node.statement); + function checkUnusedInferTypeParameter(node: InferTypeNode, addDiagnostic: AddUnusedDiagnostic): void { + const { typeParameter } = node; + if (isTypeParameterUnused(typeParameter)) { + addDiagnostic(node, UnusedKind.Parameter, createDiagnosticForNode(node, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(typeParameter.name))); } + } - function checkTruthinessOfType(type: Type, node: Node) { - if (type.flags & TypeFlags.Void) { - error(node, Diagnostics.An_expression_of_type_void_cannot_be_tested_for_truthiness); + function checkUnusedTypeParameters(node: ClassLikeDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration, addDiagnostic: AddUnusedDiagnostic): void { + // Only report errors on the last declaration for the type parameter container; + // this ensures that all uses have been accounted for. + const declarations = getSymbolOfNode(node).declarations; + if (!declarations || last(declarations) !== node) return; + + const typeParameters = getEffectiveTypeParameterDeclarations(node); + const seenParentsWithEveryUnused = new Set(); + + for (const typeParameter of typeParameters) { + if (!isTypeParameterUnused(typeParameter)) continue; + + const name = idText(typeParameter.name); + const { parent } = typeParameter; + if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) { + if (tryAddToSet(seenParentsWithEveryUnused, parent)) { + const sourceFile = getSourceFileOfNode(parent); + const range = isJSDocTemplateTag(parent) + // Whole @template tag + ? rangeOfNode(parent) + // Include the `<>` in the error message + : rangeOfTypeParameters(sourceFile, parent.typeParameters!); + const only = parent.typeParameters!.length === 1; + //TODO: following line is possible reason for bug #41974, unusedTypeParameters_TemplateTag + const message = only ? Diagnostics._0_is_declared_but_its_value_is_never_read : Diagnostics.All_type_parameters_are_unused; + const arg0 = only ? name : undefined; + addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, message, arg0)); + } + } + else { + //TODO: following line is possible reason for bug #41974, unusedTypeParameters_TemplateTag + addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name)); } - return type; } + } + function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean { + return !(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name); + } - function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) { - return checkTruthinessOfType(checkExpression(node, checkMode), node); + function addToGroup(map: ESMap, key: K, value: V, getKey: (key: K) => number | string): void { + const keyString = String(getKey(key)); + const group = map.get(keyString); + if (group) { + group[1].push(value); + } + else { + map.set(keyString, [key, [value]]); } + } - function checkForStatement(node: ForStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) { - checkGrammarVariableDeclarationList(node.initializer as VariableDeclarationList); - } - } + function tryGetRootParameterDeclaration(node: Node): ParameterDeclaration | undefined { + return tryCast(getRootDeclaration(node), isParameter); + } - if (node.initializer) { - if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { - forEach((node.initializer as VariableDeclarationList).declarations, checkVariableDeclaration); - } - else { - checkExpression(node.initializer); - } + function isValidUnusedLocalDeclaration(declaration: Declaration): boolean { + if (isBindingElement(declaration)) { + if (isObjectBindingPattern(declaration.parent)) { + /** + * ignore starts with underscore names _ + * const { a: _a } = { a: 1 } + */ + return !!(declaration.propertyName && isIdentifierThatStartsWithUnderscore(declaration.name)); } + return isIdentifierThatStartsWithUnderscore(declaration.name); + } + return isAmbientModule(declaration) || + (isVariableDeclaration(declaration) && isForInOrOfStatement(declaration.parent.parent) || isImportedDeclaration(declaration)) && isIdentifierThatStartsWithUnderscore(declaration.name!); + } - if (node.condition) checkTruthinessExpression(node.condition); - if (node.incrementor) checkExpression(node.incrementor); - checkSourceElement(node.statement); - if (node.locals) { - registerForUnusedIdentifiersCheck(node); + function checkUnusedLocalsAndParameters(nodeWithLocals: Node, addDiagnostic: AddUnusedDiagnostic): void { + // Ideally we could use the ImportClause directly as a key, but must wait until we have full ES6 maps. So must store key along with value. + const unusedImports = new Map(); + const unusedDestructures = new Map(); + const unusedVariables = new Map(); + nodeWithLocals.locals!.forEach(local => { + // If it's purely a type parameter, ignore, will be checked in `checkUnusedTypeParameters`. + // If it's a type parameter merged with a parameter, check if the parameter-side is used. + if (local.flags & SymbolFlags.TypeParameter ? !(local.flags & SymbolFlags.Variable && !(local.isReferenced! & SymbolFlags.Variable)) : local.isReferenced || local.exportSymbol) { + return; } - } - function checkForOfStatement(node: ForOfStatement): void { - checkGrammarForInOrForOfStatement(node); + if (local.declarations) { + for (const declaration of local.declarations) { + if (isValidUnusedLocalDeclaration(declaration)) { + continue; + } - const container = getContainingFunctionOrClassStaticBlock(node); - if (node.awaitModifier) { - if (container && isClassStaticBlockDeclaration(container)) { - grammarErrorOnNode(node.awaitModifier, Diagnostics.For_await_loops_cannot_be_used_inside_a_class_static_block); - } - else { - const functionFlags = getFunctionFlags(container); - if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Async)) === FunctionFlags.Async && languageVersion < ScriptTarget.ESNext) { - // for..await..of in an async function or async generator function prior to ESNext requires the __asyncValues helper - checkExternalEmitHelpers(node, ExternalEmitHelpers.ForAwaitOfIncludes); + if (isImportedDeclaration(declaration)) { + addToGroup(unusedImports, importClauseFromImported(declaration), declaration, getNodeId); + } + else if (isBindingElement(declaration) && isObjectBindingPattern(declaration.parent)) { + // In `{ a, ...b }, `a` is considered used since it removes a property from `b`. `b` may still be unused though. + const lastElement = last(declaration.parent.elements); + if (declaration === lastElement || !last(declaration.parent.elements).dotDotDotToken) { + addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); + } + } + else if (isVariableDeclaration(declaration)) { + addToGroup(unusedVariables, declaration.parent, declaration, getNodeId); + } + else { + const parameter = local.valueDeclaration && tryGetRootParameterDeclaration(local.valueDeclaration); + const name = local.valueDeclaration && getNameOfDeclaration(local.valueDeclaration); + if (parameter && name) { + if (!isParameterPropertyDeclaration(parameter, parameter.parent) && !parameterIsThisKeyword(parameter) && !isIdentifierThatStartsWithUnderscore(name)) { + if (isBindingElement(declaration) && isArrayBindingPattern(declaration.parent)) { + addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); + } + else { + addDiagnostic(parameter, UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local))); + } + } + } + else { + errorUnusedLocal(declaration, symbolName(local), addDiagnostic); + } } } } - else if (compilerOptions.downlevelIteration && languageVersion < ScriptTarget.ES2015) { - // for..of prior to ES2015 requires the __values helper when downlevelIteration is enabled - checkExternalEmitHelpers(node, ExternalEmitHelpers.ForOfIncludes); - } - - // Check the LHS and RHS - // If the LHS is a declaration, just check it as a variable declaration, which will in turn check the RHS - // via checkRightHandSideOfForOf. - // If the LHS is an expression, check the LHS, as a destructuring assignment or as a reference. - // Then check that the RHS is assignable to it. - if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { - checkForInOrForOfVariableDeclaration(node); + }); + unusedImports.forEach(([importClause, unuseds]) => { + const importDecl = importClause.parent; + const nDeclarations = (importClause.name ? 1 : 0) + + (importClause.namedBindings ? + (importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? 1 : importClause.namedBindings.elements.length) + : 0); + if (nDeclarations === unuseds.length) { + addDiagnostic(importDecl, UnusedKind.Local, unuseds.length === 1 + ? createDiagnosticForNode(importDecl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(first(unuseds).name!)) + : createDiagnosticForNode(importDecl, Diagnostics.All_imports_in_import_declaration_are_unused)); } else { - const varExpr = node.initializer; - const iteratedType = checkRightHandSideOfForOf(node); - - // There may be a destructuring assignment on the left side - if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { - // iteratedType may be undefined. In this case, we still want to check the structure of - // varExpr, in particular making sure it's a valid LeftHandSideExpression. But we'd like - // to short circuit the type relation checking as much as possible, so we pass the unknownType. - checkDestructuringAssignment(varExpr, iteratedType || errorType); + for (const unused of unuseds) errorUnusedLocal(unused, idText(unused.name!), addDiagnostic); + } + }); + unusedDestructures.forEach(([bindingPattern, bindingElements]) => { + const kind = tryGetRootParameterDeclaration(bindingPattern.parent) ? UnusedKind.Parameter : UnusedKind.Local; + if (bindingPattern.elements.length === bindingElements.length) { + if (bindingElements.length === 1 && bindingPattern.parent.kind === SyntaxKind.VariableDeclaration && bindingPattern.parent.parent.kind === SyntaxKind.VariableDeclarationList) { + addToGroup(unusedVariables, bindingPattern.parent.parent, bindingPattern.parent, getNodeId); } else { - const leftType = checkExpression(varExpr); - checkReferenceExpression( - varExpr, - Diagnostics.The_left_hand_side_of_a_for_of_statement_must_be_a_variable_or_a_property_access, - Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_an_optional_property_access); - - // iteratedType will be undefined if the rightType was missing properties/signatures - // required to get its iteratedType (like [Symbol.iterator] or next). This may be - // because we accessed properties from anyType, or it may have led to an error inside - // getElementTypeOfIterable. - if (iteratedType) { - checkTypeAssignableToAndOptionallyElaborate(iteratedType, leftType, varExpr, node.expression); - } + addDiagnostic(bindingPattern, kind, bindingElements.length === 1 + ? createDiagnosticForNode(bindingPattern, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(bindingElements).name)) + : createDiagnosticForNode(bindingPattern, Diagnostics.All_destructured_elements_are_unused)); } } - - checkSourceElement(node.statement); - if (node.locals) { - registerForUnusedIdentifiersCheck(node); - } - } - - function checkForInStatement(node: ForInStatement) { - // Grammar checking - checkGrammarForInOrForOfStatement(node); - - const rightType = getNonNullableTypeIfNeeded(checkExpression(node.expression)); - // TypeScript 1.0 spec (April 2014): 5.4 - // In a 'for-in' statement of the form - // for (let VarDecl in Expr) Statement - // VarDecl must be a variable declaration without a type annotation that declares a variable of type Any, - // and Expr must be an expression of type Any, an object type, or a type parameter type. - if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { - const variable = (node.initializer as VariableDeclarationList).declarations[0]; - if (variable && isBindingPattern(variable.name)) { - error(variable.name, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); + else { + for (const e of bindingElements) { + addDiagnostic(e, kind, createDiagnosticForNode(e, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(e.name))); } - checkForInOrForOfVariableDeclaration(node); + } + }); + unusedVariables.forEach(([declarationList, declarations]) => { + if (declarationList.declarations.length === declarations.length) { + addDiagnostic(declarationList, UnusedKind.Local, declarations.length === 1 + ? createDiagnosticForNode(first(declarations).name, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(declarations).name)) + : createDiagnosticForNode(declarationList.parent.kind === SyntaxKind.VariableStatement ? declarationList.parent : declarationList, Diagnostics.All_variables_are_unused)); } else { - // In a 'for-in' statement of the form - // for (Var in Expr) Statement - // Var must be an expression classified as a reference of type Any or the String primitive type, - // and Expr must be an expression of type Any, an object type, or a type parameter type. - const varExpr = node.initializer; - const leftType = checkExpression(varExpr); - if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { - error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); - } - else if (!isTypeAssignableTo(getIndexTypeOrString(rightType), leftType)) { - error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_of_type_string_or_any); + for (const decl of declarations) { + addDiagnostic(decl, UnusedKind.Local, createDiagnosticForNode(decl, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(decl.name))); } - else { - // run check only former check succeeded to avoid cascading errors - checkReferenceExpression( - varExpr, - Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_a_variable_or_a_property_access, - Diagnostics.The_left_hand_side_of_a_for_in_statement_may_not_be_an_optional_property_access); - } - } - - // unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved - // in this case error about missing name is already reported - do not report extra one - if (rightType === neverType || !isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { - error(node.expression, Diagnostics.The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter_but_here_has_type_0, typeToString(rightType)); } + }); + } - checkSourceElement(node.statement); - if (node.locals) { - registerForUnusedIdentifiersCheck(node); - } + function bindingNameText(name: BindingName): string { + switch (name.kind) { + case SyntaxKind.Identifier: + return idText(name); + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ObjectBindingPattern: + return bindingNameText(cast(first(name.elements), isBindingElement).name); + default: + return Debug.assertNever(name); } + } - function checkForInOrForOfVariableDeclaration(iterationStatement: ForInOrOfStatement): void { - const variableDeclarationList = iterationStatement.initializer as VariableDeclarationList; - // checkGrammarForInOrForOfStatement will check that there is exactly one declaration. - if (variableDeclarationList.declarations.length >= 1) { - const decl = variableDeclarationList.declarations[0]; - checkVariableDeclaration(decl); - } + type ImportedDeclaration = ImportClause | ImportSpecifier | NamespaceImport; + function isImportedDeclaration(node: Node): node is ImportedDeclaration { + return node.kind === SyntaxKind.ImportClause || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.NamespaceImport; + } + function importClauseFromImported(decl: ImportedDeclaration): ImportClause { + return decl.kind === SyntaxKind.ImportClause ? decl : decl.kind === SyntaxKind.NamespaceImport ? decl.parent : decl.parent.parent; + } + + function checkBlock(node: Block) { + // Grammar checking for SyntaxKind.Block + if (node.kind === SyntaxKind.Block) { + checkGrammarStatementInAmbientContext(node); } + if (isFunctionOrModuleBlock(node)) { + const saveFlowAnalysisDisabled = flowAnalysisDisabled; + forEach(node.statements, checkSourceElement); + flowAnalysisDisabled = saveFlowAnalysisDisabled; + } + else { + forEach(node.statements, checkSourceElement); + } + if (node.locals) { + registerForUnusedIdentifiersCheck(node); + } + } - function checkRightHandSideOfForOf(statement: ForOfStatement): Type { - const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; - return checkIteratedTypeOrElementType(use, checkNonNullExpression(statement.expression), undefinedType, statement.expression); + function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) { + // no rest parameters \ declaration context \ overload - no codegen impact + if (languageVersion >= ScriptTarget.ES2015 || !hasRestParameter(node) || node.flags & NodeFlags.Ambient || nodeIsMissing((node as FunctionLikeDeclaration).body)) { + return; } - function checkIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined): Type { - if (isTypeAny(inputType)) { - return inputType; + forEach(node.parameters, p => { + if (p.name && !isBindingPattern(p.name) && p.name.escapedText === argumentsSymbol.escapedName) { + errorSkippedOn("noEmit", p, Diagnostics.Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters); } - return getIteratedTypeOrElementType(use, inputType, sentType, errorNode, /*checkAssignability*/ true) || anyType; + }); + } + + /** + * Checks whether an {@link Identifier}, in the context of another {@link Node}, would collide with a runtime value + * of {@link name} in an outer scope. This is used to check for collisions for downlevel transformations that + * require names like `Object`, `Promise`, `Reflect`, `require`, `exports`, etc. + */ + function needCollisionCheckForIdentifier(node: Node, identifier: Identifier | undefined, name: string): boolean { + if (identifier?.escapedText !== name) { + return false; } - /** - * When consuming an iterable type in a for..of, spread, or iterator destructuring assignment - * we want to get the iterated type of an iterable for ES2015 or later, or the iterated type - * of a iterable (if defined globally) or element type of an array like for ES2015 or earlier. - */ - function getIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined, checkAssignability: boolean): Type | undefined { - const allowAsyncIterables = (use & IterationUse.AllowsAsyncIterablesFlag) !== 0; - if (inputType === neverType) { - reportTypeNotIterableError(errorNode!, inputType, allowAsyncIterables); // TODO: GH#18217 - return undefined; - } + if (node.kind === SyntaxKind.PropertyDeclaration || + node.kind === SyntaxKind.PropertySignature || + node.kind === SyntaxKind.MethodDeclaration || + node.kind === SyntaxKind.MethodSignature || + node.kind === SyntaxKind.GetAccessor || + node.kind === SyntaxKind.SetAccessor || + node.kind === SyntaxKind.PropertyAssignment) { + // it is ok to have member named '_super', '_this', `Promise`, etc. - member access is always qualified + return false; + } - const uplevelIteration = languageVersion >= ScriptTarget.ES2015; - const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration; - const possibleOutOfBounds = compilerOptions.noUncheckedIndexedAccess && !!(use & IterationUse.PossiblyOutOfBounds); + if (node.flags & NodeFlags.Ambient) { + // ambient context - no codegen impact + return false; + } - // Get the iterated type of an `Iterable` or `IterableIterator` only in ES2015 - // or higher, when inside of an async generator or for-await-if, or when - // downlevelIteration is requested. - if (uplevelIteration || downlevelIteration || allowAsyncIterables) { - // We only report errors for an invalid iterable type in ES2015 or higher. - const iterationTypes = getIterationTypesOfIterable(inputType, use, uplevelIteration ? errorNode : undefined); - if (checkAssignability) { - if (iterationTypes) { - const diagnostic = - use & IterationUse.ForOfFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_for_of_will_always_send_0 : - use & IterationUse.SpreadFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_spread_will_always_send_0 : - use & IterationUse.DestructuringFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_destructuring_will_always_send_0 : - use & IterationUse.YieldStarFlag ? Diagnostics.Cannot_delegate_iteration_to_value_because_the_next_method_of_its_iterator_expects_type_1_but_the_containing_generator_will_always_send_0 : - undefined; - if (diagnostic) { - checkTypeAssignableTo(sentType, iterationTypes.nextType, errorNode, diagnostic); - } - } - } - if (iterationTypes || uplevelIteration) { - return possibleOutOfBounds ? includeUndefinedInIndexSignature(iterationTypes && iterationTypes.yieldType) : (iterationTypes && iterationTypes.yieldType); - } + if (isImportClause(node) || isImportEqualsDeclaration(node) || isImportSpecifier(node)) { + // type-only imports do not require collision checks against runtime values. + if (isTypeOnlyImportOrExportDeclaration(node)) { + return false; } + } - let arrayType = inputType; - let reportedError = false; - let hasStringConstituent = false; - - // If strings are permitted, remove any string-like constituents from the array type. - // This allows us to find other non-string element types from an array unioned with - // a string. - if (use & IterationUse.AllowsStringInputFlag) { - if (arrayType.flags & TypeFlags.Union) { - // After we remove all types that are StringLike, we will know if there was a string constituent - // based on whether the result of filter is a new array. - const arrayTypes = (inputType as UnionType).types; - const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike)); - if (filteredTypes !== arrayTypes) { - arrayType = getUnionType(filteredTypes, UnionReduction.Subtype); - } - } - else if (arrayType.flags & TypeFlags.StringLike) { - arrayType = neverType; - } + const root = getRootDeclaration(node); + if (isParameter(root) && nodeIsMissing((root.parent as FunctionLikeDeclaration).body)) { + // just an overload - no codegen impact + return false; + } - hasStringConstituent = arrayType !== inputType; - if (hasStringConstituent) { - if (languageVersion < ScriptTarget.ES5) { - if (errorNode) { - error(errorNode, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher); - reportedError = true; - } - } + return true; + } - // Now that we've removed all the StringLike types, if no constituents remain, then the entire - // arrayOrStringType was a string. - if (arrayType.flags & TypeFlags.Never) { - return possibleOutOfBounds ? includeUndefinedInIndexSignature(stringType) : stringType; - } + // this function will run after checking the source file so 'CaptureThis' is correct for all nodes + function checkIfThisIsCapturedInEnclosingScope(node: Node): void { + findAncestor(node, current => { + if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureThis) { + const isDeclaration = node.kind !== SyntaxKind.Identifier; + if (isDeclaration) { + error(getNameOfDeclaration(node as Declaration), Diagnostics.Duplicate_identifier_this_Compiler_uses_variable_declaration_this_to_capture_this_reference); } - } - - if (!isArrayLikeType(arrayType)) { - if (errorNode && !reportedError) { - // Which error we report depends on whether we allow strings or if there was a - // string constituent. For example, if the input type is number | string, we - // want to say that number is not an array type. But if the input was just - // number and string input is allowed, we want to say that number is not an - // array type or a string type. - const allowsStrings = !!(use & IterationUse.AllowsStringInputFlag) && !hasStringConstituent; - const [defaultDiagnostic, maybeMissingAwait] = getIterationDiagnosticDetails(allowsStrings, downlevelIteration); - errorAndMaybeSuggestAwait( - errorNode, - maybeMissingAwait && !!getAwaitedTypeOfPromise(arrayType), - defaultDiagnostic, - typeToString(arrayType)); + else { + error(node, Diagnostics.Expression_resolves_to_variable_declaration_this_that_compiler_uses_to_capture_this_reference); } - return hasStringConstituent ? possibleOutOfBounds ? includeUndefinedInIndexSignature(stringType) : stringType : undefined; + return true; } + return false; + }); + } - const arrayElementType = getIndexTypeOfType(arrayType, numberType); - if (hasStringConstituent && arrayElementType) { - // This is just an optimization for the case where arrayOrStringType is string | string[] - if (arrayElementType.flags & TypeFlags.StringLike && !compilerOptions.noUncheckedIndexedAccess) { - return stringType; + function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void { + findAncestor(node, current => { + if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) { + const isDeclaration = node.kind !== SyntaxKind.Identifier; + if (isDeclaration) { + error(getNameOfDeclaration(node as Declaration), Diagnostics.Duplicate_identifier_newTarget_Compiler_uses_variable_declaration_newTarget_to_capture_new_target_meta_property_reference); } - - return getUnionType(possibleOutOfBounds ? [arrayElementType, stringType, undefinedType] : [arrayElementType, stringType], UnionReduction.Subtype); + else { + error(node, Diagnostics.Expression_resolves_to_variable_declaration_newTarget_that_compiler_uses_to_capture_new_target_meta_property_reference); + } + return true; } + return false; + }); + } - return (use & IterationUse.PossiblyOutOfBounds) ? includeUndefinedInIndexSignature(arrayElementType) : arrayElementType; + function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier | undefined) { + // No need to check for require or exports for ES6 modules and later + if (moduleKind >= ModuleKind.ES2015 && !(moduleKind >= ModuleKind.Node12 && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { + return; + } - function getIterationDiagnosticDetails(allowsStrings: boolean, downlevelIteration: boolean | undefined): [DiagnosticMessage, boolean] { - if (downlevelIteration) { - return allowsStrings - ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true] - : [Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true]; - } + if (!name || !needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) { + return; + } - const yieldType = getIterationTypeOfIterable(use, IterationTypeKind.Yield, inputType, /*errorNode*/ undefined); + // Uninstantiated modules shouldnt do this check + if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { + return; + } - if (yieldType) { - return [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_Use_compiler_option_downlevelIteration_to_allow_iterating_of_iterators, false]; - } + // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent + const parent = getDeclarationContainer(node); + if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(parent as SourceFile)) { + // If the declaration happens to be in external module, report error that require and exports are reserved keywords + errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module, + declarationNameToString(name), declarationNameToString(name)); + } + } - if (isES2015OrLaterIterable(inputType.symbol?.escapedName)) { - return [Diagnostics.Type_0_can_only_be_iterated_through_when_using_the_downlevelIteration_flag_or_with_a_target_of_es2015_or_higher, true]; - } + function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier | undefined): void { + if (!name || languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) { + return; + } - return allowsStrings - ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type, true] - : [Diagnostics.Type_0_is_not_an_array_type, true]; - } - } - - function isES2015OrLaterIterable(n: __String) { - switch (n) { - case "Float32Array": - case "Float64Array": - case "Int16Array": - case "Int32Array": - case "Int8Array": - case "NodeList": - case "Uint16Array": - case "Uint32Array": - case "Uint8Array": - case "Uint8ClampedArray": - return true; - } - return false; + // Uninstantiated modules shouldnt do this check + if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { + return; } - /** - * Gets the requested "iteration type" from an `Iterable`-like or `AsyncIterable`-like type. - */ - function getIterationTypeOfIterable(use: IterationUse, typeKind: IterationTypeKind, inputType: Type, errorNode: Node | undefined): Type | undefined { - if (isTypeAny(inputType)) { - return undefined; - } + // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent + const parent = getDeclarationContainer(node); + if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(parent as SourceFile) && parent.flags & NodeFlags.HasAsyncFunctions) { + // If the declaration happens to be in external module, report error that Promise is a reserved identifier. + errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module_containing_async_functions, + declarationNameToString(name), declarationNameToString(name)); + } + } - const iterationTypes = getIterationTypesOfIterable(inputType, use, errorNode); - return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(typeKind)]; + function recordPotentialCollisionWithWeakMapSetInGeneratedCode(node: Node, name: Identifier): void { + if (languageVersion <= ScriptTarget.ES2021 + && (needCollisionCheckForIdentifier(node, name, "WeakMap") || needCollisionCheckForIdentifier(node, name, "WeakSet"))) { + potentialWeakMapSetCollisions.push(node); } + } - function createIterationTypes(yieldType: Type = neverType, returnType: Type = neverType, nextType: Type = unknownType): IterationTypes { - // `yieldType` and `returnType` are defaulted to `neverType` they each will be combined - // via `getUnionType` when merging iteration types. `nextType` is defined as `unknownType` - // as it is combined via `getIntersectionType` when merging iteration types. + function checkWeakMapSetCollision(node: Node) { + const enclosingBlockScope = getEnclosingBlockScopeContainer(node); + if (getNodeCheckFlags(enclosingBlockScope) & NodeCheckFlags.ContainsClassWithPrivateIdentifiers) { + Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name) && typeof node.name.escapedText === "string", "The target of a WeakMap/WeakSet collision check should be an identifier"); + errorSkippedOn("noEmit", node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, node.name.escapedText); + } + } - // Use the cache only for intrinsic types to keep it small as they are likely to be - // more frequently created (i.e. `Iterator`). Iteration types - // are also cached on the type they are requested for, so we shouldn't need to maintain - // the cache for less-frequently used types. - if (yieldType.flags & TypeFlags.Intrinsic && - returnType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined) && - nextType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined)) { - const id = getTypeListId([yieldType, returnType, nextType]); - let iterationTypes = iterationTypesCache.get(id); - if (!iterationTypes) { - iterationTypes = { yieldType, returnType, nextType }; - iterationTypesCache.set(id, iterationTypes); - } - return iterationTypes; - } - return { yieldType, returnType, nextType }; + function recordPotentialCollisionWithReflectInGeneratedCode(node: Node, name: Identifier | undefined): void { + if (name && languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021 + && needCollisionCheckForIdentifier(node, name, "Reflect")) { + potentialReflectCollisions.push(node); } + } - /** - * Combines multiple `IterationTypes` records. - * - * If `array` is empty or all elements are missing or are references to `noIterationTypes`, - * then `noIterationTypes` is returned. Otherwise, an `IterationTypes` record is returned - * for the combined iteration types. - */ - function combineIterationTypes(array: (IterationTypes | undefined)[]) { - let yieldTypes: Type[] | undefined; - let returnTypes: Type[] | undefined; - let nextTypes: Type[] | undefined; - for (const iterationTypes of array) { - if (iterationTypes === undefined || iterationTypes === noIterationTypes) { - continue; - } - if (iterationTypes === anyIterationTypes) { - return anyIterationTypes; + function checkReflectCollision(node: Node) { + let hasCollision = false; + if (isClassExpression(node)) { + // ClassExpression names don't contribute to their containers, but do matter for any of their block-scoped members. + for (const member of node.members) { + if (getNodeCheckFlags(member) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { + hasCollision = true; + break; } - yieldTypes = append(yieldTypes, iterationTypes.yieldType); - returnTypes = append(returnTypes, iterationTypes.returnType); - nextTypes = append(nextTypes, iterationTypes.nextType); } - if (yieldTypes || returnTypes || nextTypes) { - return createIterationTypes( - yieldTypes && getUnionType(yieldTypes), - returnTypes && getUnionType(returnTypes), - nextTypes && getIntersectionType(nextTypes)); + } + else if (isFunctionExpression(node)) { + // FunctionExpression names don't contribute to their containers, but do matter for their contents + if (getNodeCheckFlags(node) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { + hasCollision = true; } - return noIterationTypes; } - - function getCachedIterationTypes(type: Type, cacheKey: MatchingKeys) { - return (type as IterableOrIteratorType)[cacheKey]; + else { + const container = getEnclosingBlockScopeContainer(node); + if (container && getNodeCheckFlags(container) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { + hasCollision = true; + } } - - function setCachedIterationTypes(type: Type, cacheKey: MatchingKeys, cachedTypes: IterationTypes) { - return (type as IterableOrIteratorType)[cacheKey] = cachedTypes; + if (hasCollision) { + Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name), "The target of a Reflect collision check should be an identifier"); + errorSkippedOn("noEmit", node, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_when_emitting_super_references_in_static_initializers, + declarationNameToString(node.name), + "Reflect"); } + } - /** - * Gets the *yield*, *return*, and *next* types from an `Iterable`-like or `AsyncIterable`-like type. - * - * At every level that involves analyzing return types of signatures, we union the return types of all the signatures. - * - * Another thing to note is that at any step of this process, we could run into a dead end, - * meaning either the property is missing, or we run into the anyType. If either of these things - * happens, we return `undefined` to signal that we could not find the iteration type. If a property - * is missing, and the previous step did not result in `any`, then we also give an error if the - * caller requested it. Then the caller can decide what to do in the case where there is no iterated - * type. - * - * For a **for-of** statement, `yield*` (in a normal generator), spread, array - * destructuring, or normal generator we will only ever look for a `[Symbol.iterator]()` - * method. - * - * For an async generator we will only ever look at the `[Symbol.asyncIterator]()` method. - * - * For a **for-await-of** statement or a `yield*` in an async generator we will look for - * the `[Symbol.asyncIterator]()` method first, and then the `[Symbol.iterator]()` method. - */ - function getIterationTypesOfIterable(type: Type, use: IterationUse, errorNode: Node | undefined) { - if (isTypeAny(type)) { - return anyIterationTypes; + function checkCollisionsForDeclarationName(node: Node, name: Identifier | undefined) { + if (!name) return; + checkCollisionWithRequireExportsInGeneratedCode(node, name); + checkCollisionWithGlobalPromiseInGeneratedCode(node, name); + recordPotentialCollisionWithWeakMapSetInGeneratedCode(node, name); + recordPotentialCollisionWithReflectInGeneratedCode(node, name); + if (isClassLike(node)) { + checkTypeNameIsReserved(name, Diagnostics.Class_name_cannot_be_0); + if (!(node.flags & NodeFlags.Ambient)) { + checkClassNameCollisionWithObject(name); } + } + else if (isEnumDeclaration(node)) { + checkTypeNameIsReserved(name, Diagnostics.Enum_name_cannot_be_0); + } + } - if (!(type.flags & TypeFlags.Union)) { - const iterationTypes = getIterationTypesOfIterableWorker(type, use, errorNode); - if (iterationTypes === noIterationTypes) { - if (errorNode) { - reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); - } - return undefined; - } - return iterationTypes; - } + function checkVarDeclaredNamesNotShadowed(node: VariableDeclaration | BindingElement) { + // - ScriptBody : StatementList + // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList + // also occurs in the VarDeclaredNames of StatementList. + + // - Block : { StatementList } + // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList + // also occurs in the VarDeclaredNames of StatementList. + + // Variable declarations are hoisted to the top of their function scope. They can shadow + // block scoped declarations, which bind tighter. this will not be flagged as duplicate definition + // by the binder as the declaration scope is different. + // A non-initialized declaration is a no-op as the block declaration will resolve before the var + // declaration. the problem is if the declaration has an initializer. this will act as a write to the + // block declared value. this is fine for let, but not const. + // Only consider declarations with initializers, uninitialized const declarations will not + // step on a let/const variable. + // Do not consider const and const declarations, as duplicate block-scoped declarations + // are handled by the binder. + // We are only looking for const declarations that step on let\const declarations from a + // different scope. e.g.: + // { + // const x = 0; // localDeclarationSymbol obtained after name resolution will correspond to this declaration + // const x = 0; // symbol for this declaration will be 'symbol' + // } + + // skip block-scoped variables and parameters + if ((getCombinedNodeFlags(node) & NodeFlags.BlockScoped) !== 0 || isParameterDeclaration(node)) { + return; + } + + // skip variable declarations that don't have initializers + // NOTE: in ES6 spec initializer is required in variable declarations where name is binding pattern + // so we'll always treat binding elements as initialized + if (node.kind === SyntaxKind.VariableDeclaration && !node.initializer) { + return; + } + + const symbol = getSymbolOfNode(node); + if (symbol.flags & SymbolFlags.FunctionScopedVariable) { + if (!isIdentifier(node.name)) return Debug.fail(); + const localDeclarationSymbol = resolveName(node, node.name.escapedText, SymbolFlags.Variable, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + if (localDeclarationSymbol && + localDeclarationSymbol !== symbol && + localDeclarationSymbol.flags & SymbolFlags.BlockScopedVariable) { + if (getDeclarationNodeFlagsFromSymbol(localDeclarationSymbol) & NodeFlags.BlockScoped) { + const varDeclList = getAncestor(localDeclarationSymbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!; + const container = + varDeclList.parent.kind === SyntaxKind.VariableStatement && varDeclList.parent.parent + ? varDeclList.parent.parent + : undefined; - const cacheKey = use & IterationUse.AllowsAsyncIterablesFlag ? "iterationTypesOfAsyncIterable" : "iterationTypesOfIterable"; - const cachedTypes = getCachedIterationTypes(type, cacheKey); - if (cachedTypes) return cachedTypes === noIterationTypes ? undefined : cachedTypes; + // names of block-scoped and function scoped variables can collide only + // if block scoped variable is defined in the function\module\source file scope (because of variable hoisting) + const namesShareScope = + container && + (container.kind === SyntaxKind.Block && isFunctionLike(container.parent) || + container.kind === SyntaxKind.ModuleBlock || + container.kind === SyntaxKind.ModuleDeclaration || + container.kind === SyntaxKind.SourceFile); - let allIterationTypes: IterationTypes[] | undefined; - for (const constituent of (type as UnionType).types) { - const iterationTypes = getIterationTypesOfIterableWorker(constituent, use, errorNode); - if (iterationTypes === noIterationTypes) { - if (errorNode) { - reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); + // here we know that function scoped variable is shadowed by block scoped one + // if they are defined in the same scope - binder has already reported redeclaration error + // otherwise if variable has an initializer - show error that initialization will fail + // since LHS will be block scoped name instead of function scoped + if (!namesShareScope) { + const name = symbolToString(localDeclarationSymbol); + error(node, Diagnostics.Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1, name, name); } - setCachedIterationTypes(type, cacheKey, noIterationTypes); - return undefined; - } - else { - allIterationTypes = append(allIterationTypes, iterationTypes); } } + } + } - const iterationTypes = allIterationTypes ? combineIterationTypes(allIterationTypes) : noIterationTypes; - setCachedIterationTypes(type, cacheKey, iterationTypes); - return iterationTypes === noIterationTypes ? undefined : iterationTypes; + function convertAutoToAny(type: Type) { + return type === autoType ? anyType : type === autoArrayType ? anyArrayType : type; + } + + // Check variable, parameter, or property declaration + function checkVariableLikeDeclaration(node: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement) { + checkDecorators(node); + if (!isBindingElement(node)) { + checkSourceElement(node.type); } - function getAsyncFromSyncIterationTypes(iterationTypes: IterationTypes, errorNode: Node | undefined) { - if (iterationTypes === noIterationTypes) return noIterationTypes; - if (iterationTypes === anyIterationTypes) return anyIterationTypes; - const { yieldType, returnType, nextType } = iterationTypes; - // if we're requesting diagnostics, report errors for a missing `Awaited`. - if (errorNode) { - getGlobalAwaitedSymbol(/*reportErrors*/ true); + // JSDoc `function(string, string): string` syntax results in parameters with no name + if (!node.name) { + return; + } + + // For a computed property, just check the initializer and exit + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); + if (node.initializer) { + checkExpressionCached(node.initializer); } - return createIterationTypes( - getAwaitedType(yieldType, errorNode) || anyType, - getAwaitedType(returnType, errorNode) || anyType, - nextType); } - /** - * Gets the *yield*, *return*, and *next* types from a non-union type. - * - * If we are unable to find the *yield*, *return*, and *next* types, `noIterationTypes` is - * returned to indicate to the caller that it should report an error. Otherwise, an - * `IterationTypes` record is returned. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterable` instead. - */ - function getIterationTypesOfIterableWorker(type: Type, use: IterationUse, errorNode: Node | undefined) { - if (isTypeAny(type)) { - return anyIterationTypes; + if (isBindingElement(node)) { + if (isObjectBindingPattern(node.parent) && node.dotDotDotToken && languageVersion < ScriptTarget.ES2018) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Rest); + } + // check computed properties inside property names of binding elements + if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.propertyName); } - if (use & IterationUse.AllowsAsyncIterablesFlag) { - const iterationTypes = - getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) || - getIterationTypesOfIterableFast(type, asyncIterationTypesResolver); - if (iterationTypes) { - return use & IterationUse.ForOfFlag ? - getAsyncFromSyncIterationTypes(iterationTypes, errorNode) : - iterationTypes; + // check private/protected variable access + const parent = node.parent.parent; + const parentType = getTypeForBindingElementParent(parent); + const name = node.propertyName || node.name; + if (parentType && !isBindingPattern(name)) { + const exprType = getLiteralTypeFromPropertyName(name); + if (isTypeUsableAsPropertyName(exprType)) { + const nameText = getPropertyNameFromType(exprType); + const property = getPropertyOfType(parentType, nameText); + if (property) { + markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isSelfTypeAccess*/ false); // A destructuring is never a write-only reference. + checkPropertyAccessibility(node, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, /*writing*/ false, parentType, property); + } } } + } - if (use & IterationUse.AllowsSyncIterablesFlag) { - const iterationTypes = - getIterationTypesOfIterableCached(type, syncIterationTypesResolver) || - getIterationTypesOfIterableFast(type, syncIterationTypesResolver); - if (iterationTypes) { - if (use & IterationUse.AllowsAsyncIterablesFlag) { - // for a sync iterable in an async context, only use the cached types if they are valid. - if (iterationTypes !== noIterationTypes) { - return setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", getAsyncFromSyncIterationTypes(iterationTypes, errorNode)); - } + // For a binding pattern, check contained binding elements + if (isBindingPattern(node.name)) { + if (node.name.kind === SyntaxKind.ArrayBindingPattern && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); + } + + forEach(node.name.elements, checkSourceElement); + } + // For a parameter declaration with an initializer, error and exit if the containing function doesn't have a body + if (node.initializer && isParameterDeclaration(node) && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { + error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation); + return; + } + // For a binding pattern, validate the initializer and exit + if (isBindingPattern(node.name)) { + const needCheckInitializer = node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement; + const needCheckWidenedType = node.name.elements.length === 0; + if (needCheckInitializer || needCheckWidenedType) { + // Don't validate for-in initializer as it is already an error + const widenedType = getWidenedTypeForVariableLikeDeclaration(node); + if (needCheckInitializer) { + const initializerType = checkExpressionCached(node.initializer!); + if (strictNullChecks && needCheckWidenedType) { + checkNonNullNonVoidType(initializerType, node); } else { - return iterationTypes; + checkTypeAssignableToAndOptionallyElaborate(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, node.initializer); + } + } + // check the binding pattern with empty elements + if (needCheckWidenedType) { + if (isArrayBindingPattern(node.name)) { + checkIteratedTypeOrElementType(IterationUse.Destructuring, widenedType, undefinedType, node); + } + else if (strictNullChecks) { + checkNonNullNonVoidType(widenedType, node); } } } + return; + } + // For a commonjs `const x = require`, validate the alias and exit + const symbol = getSymbolOfNode(node); + if (symbol.flags & SymbolFlags.Alias && isRequireVariableDeclaration(node)) { + checkAliasSymbol(node); + return; + } - if (use & IterationUse.AllowsAsyncIterablesFlag) { - const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode); - if (iterationTypes !== noIterationTypes) { - return iterationTypes; + const type = convertAutoToAny(getTypeOfSymbol(symbol)); + if (node === symbol.valueDeclaration) { + // Node is the primary declaration of the symbol, just validate the initializer + // Don't validate for-in initializer as it is already an error + const initializer = getEffectiveInitializer(node); + if (initializer) { + const isJSObjectLiteralInitializer = isInJSFile(node) && + isObjectLiteralExpression(initializer) && + (initializer.properties.length === 0 || isPrototypeAccess(node.name)) && + !!symbol.exports?.size; + if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { + checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined); } } - - if (use & IterationUse.AllowsSyncIterablesFlag) { - const iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode); - if (iterationTypes !== noIterationTypes) { - if (use & IterationUse.AllowsAsyncIterablesFlag) { - return setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", iterationTypes - ? getAsyncFromSyncIterationTypes(iterationTypes, errorNode) - : noIterationTypes); - } - else { - return iterationTypes; - } + if (symbol.declarations && symbol.declarations.length > 1) { + if (some(symbol.declarations, d => d !== node && isVariableLike(d) && !areDeclarationFlagsIdentical(d, node))) { + error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); } } + } + else { + // Node is a secondary declaration, check that type is identical to primary declaration and check that + // initializer is consistent with type associated with the node + const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node)); - return noIterationTypes; + if (!isErrorType(type) && !isErrorType(declarationType) && + !isTypeIdenticalTo(type, declarationType) && + !(symbol.flags & SymbolFlags.Assignment)) { + errorNextVariableOrPropertyDeclarationMustHaveSameType(symbol.valueDeclaration, type, node, declarationType); + } + if (node.initializer) { + checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined); + } + if (symbol.valueDeclaration && !areDeclarationFlagsIdentical(node, symbol.valueDeclaration)) { + error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); + } + } + if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature) { + // We know we don't have a binding pattern or computed name here + checkExportsOnMergedDeclarations(node); + if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { + checkVarDeclaredNamesNotShadowed(node); + } + checkCollisionsForDeclarationName(node, node.name); } + } - /** - * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or - * `AsyncIterable`-like type from the cache. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterable` instead. - */ - function getIterationTypesOfIterableCached(type: Type, resolver: IterationTypesResolver) { - return getCachedIterationTypes(type, resolver.iterableCacheKey); + function errorNextVariableOrPropertyDeclarationMustHaveSameType(firstDeclaration: Declaration | undefined, firstType: Type, nextDeclaration: Declaration, nextType: Type): void { + const nextDeclarationName = getNameOfDeclaration(nextDeclaration); + const message = nextDeclaration.kind === SyntaxKind.PropertyDeclaration || nextDeclaration.kind === SyntaxKind.PropertySignature + ? Diagnostics.Subsequent_property_declarations_must_have_the_same_type_Property_0_must_be_of_type_1_but_here_has_type_2 + : Diagnostics.Subsequent_variable_declarations_must_have_the_same_type_Variable_0_must_be_of_type_1_but_here_has_type_2; + const declName = declarationNameToString(nextDeclarationName); + const err = error( + nextDeclarationName, + message, + declName, + typeToString(firstType), + typeToString(nextType) + ); + if (firstDeclaration) { + addRelatedInfo(err, + createDiagnosticForNode(firstDeclaration, Diagnostics._0_was_also_declared_here, declName) + ); } + } - function getIterationTypesOfGlobalIterableType(globalType: Type, resolver: IterationTypesResolver) { - const globalIterationTypes = - getIterationTypesOfIterableCached(globalType, resolver) || - getIterationTypesOfIterableSlow(globalType, resolver, /*errorNode*/ undefined); - return globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; + function areDeclarationFlagsIdentical(left: Declaration, right: Declaration) { + if ((left.kind === SyntaxKind.Parameter && right.kind === SyntaxKind.VariableDeclaration) || + (left.kind === SyntaxKind.VariableDeclaration && right.kind === SyntaxKind.Parameter)) { + // Differences in optionality between parameters and variables are allowed. + return true; } - /** - * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like - * type from from common heuristics. - * - * If we previously analyzed this type and found no iteration types, `noIterationTypes` is - * returned. If we found iteration types, an `IterationTypes` record is returned. - * Otherwise, we return `undefined` to indicate to the caller it should perform a more - * exhaustive analysis. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterable` instead. - */ - function getIterationTypesOfIterableFast(type: Type, resolver: IterationTypesResolver) { - // As an optimization, if the type is an instantiation of one of the following global types, then - // just grab its related type argument: - // - `Iterable` or `AsyncIterable` - // - `IterableIterator` or `AsyncIterableIterator` - let globalType: Type; - if (isReferenceToType(type, globalType = resolver.getGlobalIterableType(/*reportErrors*/ false)) || - isReferenceToType(type, globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false))) { - const [yieldType] = getTypeArguments(type as GenericType); - // The "return" and "next" types of `Iterable` and `IterableIterator` are defined by the - // iteration types of their `[Symbol.iterator]()` method. The same is true for their async cousins. - // While we define these as `any` and `undefined` in our libs by default, a custom lib *could* use - // different definitions. - const { returnType, nextType } = getIterationTypesOfGlobalIterableType(globalType, resolver); - return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); - } - - // As an optimization, if the type is an instantiation of the following global type, then - // just grab its related type arguments: - // - `Generator` or `AsyncGenerator` - if (isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { - const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); - return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); - } - } - - function getPropertyNameForKnownSymbolName(symbolName: string): __String { - const ctorType = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); - const uniqueType = ctorType && getTypeOfPropertyOfType(getTypeOfSymbol(ctorType), escapeLeadingUnderscores(symbolName)); - return uniqueType && isTypeUsableAsPropertyName(uniqueType) ? getPropertyNameFromType(uniqueType) : `__@${symbolName}` as __String; + if (hasQuestionToken(left) !== hasQuestionToken(right)) { + return false; } - /** - * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like - * type from its members. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, `noIterationTypes` is returned. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterable` instead. - */ - function getIterationTypesOfIterableSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { - const method = getPropertyOfType(type, getPropertyNameForKnownSymbolName(resolver.iteratorSymbolName)); - const methodType = method && !(method.flags & SymbolFlags.Optional) ? getTypeOfSymbol(method) : undefined; - if (isTypeAny(methodType)) { - return setCachedIterationTypes(type, resolver.iterableCacheKey, anyIterationTypes); - } + const interestingFlags = ModifierFlags.Private | + ModifierFlags.Protected | + ModifierFlags.Async | + ModifierFlags.Abstract | + ModifierFlags.Readonly | + ModifierFlags.Static; - const signatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : undefined; - if (!some(signatures)) { - return setCachedIterationTypes(type, resolver.iterableCacheKey, noIterationTypes); - } + return getSelectedEffectiveModifierFlags(left, interestingFlags) === getSelectedEffectiveModifierFlags(right, interestingFlags); + } + + function checkVariableDeclaration(node: VariableDeclaration) { + tracing?.push(tracing.Phase.Check, "checkVariableDeclaration", { kind: node.kind, pos: node.pos, end: node.end }); + checkGrammarVariableDeclaration(node); + checkVariableLikeDeclaration(node); + tracing?.pop(); + } + + function checkBindingElement(node: BindingElement) { + checkGrammarBindingElement(node); + return checkVariableLikeDeclaration(node); + } - const iteratorType = getIntersectionType(map(signatures, getReturnTypeOfSignature)); - const iterationTypes = getIterationTypesOfIterator(iteratorType, resolver, errorNode) ?? noIterationTypes; - return setCachedIterationTypes(type, resolver.iterableCacheKey, iterationTypes); + function checkVariableStatement(node: VariableStatement) { + // Grammar checking + if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarVariableDeclarationList(node.declarationList)) checkGrammarForDisallowedLetOrConstStatement(node); + forEach(node.declarationList.declarations, checkSourceElement); + } + + function checkExpressionStatement(node: ExpressionStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + checkExpression(node.expression); + } + + function checkIfStatement(node: IfStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + const type = checkTruthinessExpression(node.expression); + checkTestingKnownTruthyCallableOrAwaitableType(node.expression, type, node.thenStatement); + checkSourceElement(node.thenStatement); + + if (node.thenStatement.kind === SyntaxKind.EmptyStatement) { + error(node.thenStatement, Diagnostics.The_body_of_an_if_statement_cannot_be_the_empty_statement); } - function reportTypeNotIterableError(errorNode: Node, type: Type, allowAsyncIterables: boolean): void { - const message = allowAsyncIterables - ? Diagnostics.Type_0_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator - : Diagnostics.Type_0_must_have_a_Symbol_iterator_method_that_returns_an_iterator; - errorAndMaybeSuggestAwait(errorNode, !!getAwaitedTypeOfPromise(type), message, typeToString(type)); + checkSourceElement(node.elseStatement); + } + + function checkTestingKnownTruthyCallableOrAwaitableType(condExpr: Expression, type: Type, body?: Statement | Expression) { + if (!strictNullChecks) return; + if (getFalsyFlags(type)) return; + + const location = isBinaryExpression(condExpr) ? condExpr.right : condExpr; + if (isPropertyAccessExpression(location) && isTypeAssertion(location.expression)) { + return; } - /** - * Gets the *yield*, *return*, and *next* types from an `Iterator`-like or `AsyncIterator`-like type. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, `undefined` is returned. - */ - function getIterationTypesOfIterator(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { - if (isTypeAny(type)) { - return anyIterationTypes; - } + const testedNode = isIdentifier(location) ? location + : isPropertyAccessExpression(location) ? location.name + : isBinaryExpression(location) && isIdentifier(location.right) ? location.right + : undefined; - const iterationTypes = - getIterationTypesOfIteratorCached(type, resolver) || - getIterationTypesOfIteratorFast(type, resolver) || - getIterationTypesOfIteratorSlow(type, resolver, errorNode); - return iterationTypes === noIterationTypes ? undefined : iterationTypes; + // While it technically should be invalid for any known-truthy value + // to be tested, we de-scope to functions and Promises unreferenced in + // the block as a heuristic to identify the most common bugs. There + // are too many false positives for values sourced from type + // definitions without strictNullChecks otherwise. + const callSignatures = getSignaturesOfType(type, SignatureKind.Call); + const isPromise = !!getAwaitedTypeOfPromise(type); + if (callSignatures.length === 0 && !isPromise) { + return; } - /** - * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the - * cache. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterator` instead. - */ - function getIterationTypesOfIteratorCached(type: Type, resolver: IterationTypesResolver) { - return getCachedIterationTypes(type, resolver.iteratorCacheKey); + const testedSymbol = testedNode && getSymbolAtLocation(testedNode); + if (!testedSymbol && !isPromise) { + return; } - /** - * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the - * cache or from common heuristics. - * - * If we previously analyzed this type and found no iteration types, `noIterationTypes` is - * returned. If we found iteration types, an `IterationTypes` record is returned. - * Otherwise, we return `undefined` to indicate to the caller it should perform a more - * exhaustive analysis. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterator` instead. - */ - function getIterationTypesOfIteratorFast(type: Type, resolver: IterationTypesResolver) { - // As an optimization, if the type is an instantiation of one of the following global types, - // then just grab its related type argument: - // - `IterableIterator` or `AsyncIterableIterator` - // - `Iterator` or `AsyncIterator` - // - `Generator` or `AsyncGenerator` - const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); - if (isReferenceToType(type, globalType)) { - const [yieldType] = getTypeArguments(type as GenericType); - // The "return" and "next" types of `IterableIterator` and `AsyncIterableIterator` are defined by the - // iteration types of their `next`, `return`, and `throw` methods. While we define these as `any` - // and `undefined` in our libs by default, a custom lib *could* use different definitions. - const globalIterationTypes = - getIterationTypesOfIteratorCached(globalType, resolver) || - getIterationTypesOfIteratorSlow(globalType, resolver, /*errorNode*/ undefined); - const { returnType, nextType } = globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; - return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType)); + const isUsed = testedSymbol && isBinaryExpression(condExpr.parent) && isSymbolUsedInBinaryExpressionChain(condExpr.parent, testedSymbol) + || testedSymbol && body && isSymbolUsedInConditionBody(condExpr, body, testedNode, testedSymbol); + if (!isUsed) { + if (isPromise) { + errorAndMaybeSuggestAwait( + location, + /*maybeMissingAwait*/ true, + Diagnostics.This_condition_will_always_return_true_since_this_0_is_always_defined, + getTypeNameForErrorDisplay(type)); } - if (isReferenceToType(type, resolver.getGlobalIteratorType(/*reportErrors*/ false)) || - isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { - const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); - return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType)); + else { + error(location, Diagnostics.This_condition_will_always_return_true_since_this_function_is_always_defined_Did_you_mean_to_call_it_instead); } } + } - function isIteratorResult(type: Type, kind: IterationTypeKind.Yield | IterationTypeKind.Return) { - // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface: - // > [done] is the result status of an iterator `next` method call. If the end of the iterator was reached `done` is `true`. - // > If the end was not reached `done` is `false` and a value is available. - // > If a `done` property (either own or inherited) does not exist, it is consider to have the value `false`. - const doneType = getTypeOfPropertyOfType(type, "done" as __String) || falseType; - return isTypeAssignableTo(kind === IterationTypeKind.Yield ? falseType : trueType, doneType); - } + function isSymbolUsedInConditionBody(expr: Expression, body: Statement | Expression, testedNode: Node, testedSymbol: Symbol): boolean { + return !!forEachChild(body, function check(childNode): boolean | undefined { + if (isIdentifier(childNode)) { + const childSymbol = getSymbolAtLocation(childNode); + if (childSymbol && childSymbol === testedSymbol) { + // If the test was a simple identifier, the above check is sufficient + if (isIdentifier(expr)) { + return true; + } + // Otherwise we need to ensure the symbol is called on the same target + let testedExpression = testedNode.parent; + let childExpression = childNode.parent; + while (testedExpression && childExpression) { + if (isIdentifier(testedExpression) && isIdentifier(childExpression) || + testedExpression.kind === SyntaxKind.ThisKeyword && childExpression.kind === SyntaxKind.ThisKeyword) { + return getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression); + } + else if (isPropertyAccessExpression(testedExpression) && isPropertyAccessExpression(childExpression)) { + if (getSymbolAtLocation(testedExpression.name) !== getSymbolAtLocation(childExpression.name)) { + return false; + } + childExpression = childExpression.expression; + testedExpression = testedExpression.expression; + } + else if (isCallExpression(testedExpression) && isCallExpression(childExpression)) { + childExpression = childExpression.expression; + testedExpression = testedExpression.expression; + } + else { + return false; + } + } + } + } + return forEachChild(childNode, check); + }); + } - function isYieldIteratorResult(type: Type) { - return isIteratorResult(type, IterationTypeKind.Yield); + function isSymbolUsedInBinaryExpressionChain(node: Node, testedSymbol: Symbol): boolean { + while (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + const isUsed = forEachChild(node.right, function visit(child): boolean | undefined { + if (isIdentifier(child)) { + const symbol = getSymbolAtLocation(child); + if (symbol && symbol === testedSymbol) { + return true; + } + } + return forEachChild(child, visit); + }); + if (isUsed) { + return true; + } + node = node.parent; } + return false; + } + + function checkDoStatement(node: DoStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); - function isReturnIteratorResult(type: Type) { - return isIteratorResult(type, IterationTypeKind.Return); + checkSourceElement(node.statement); + checkTruthinessExpression(node.expression); + } + + function checkWhileStatement(node: WhileStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + checkTruthinessExpression(node.expression); + checkSourceElement(node.statement); + } + + function checkTruthinessOfType(type: Type, node: Node) { + if (type.flags & TypeFlags.Void) { + error(node, Diagnostics.An_expression_of_type_void_cannot_be_tested_for_truthiness); } + return type; + } - /** - * Gets the *yield* and *return* types of an `IteratorResult`-like type. - * - * If we are unable to determine a *yield* or a *return* type, `noIterationTypes` is - * returned to indicate to the caller that it should handle the error. Otherwise, an - * `IterationTypes` record is returned. - */ - function getIterationTypesOfIteratorResult(type: Type) { - if (isTypeAny(type)) { - return anyIterationTypes; - } + function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) { + return checkTruthinessOfType(checkExpression(node, checkMode), node); + } - const cachedTypes = getCachedIterationTypes(type, "iterationTypesOfIteratorResult"); - if (cachedTypes) { - return cachedTypes; + function checkForStatement(node: ForStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) { + checkGrammarVariableDeclarationList(node.initializer as VariableDeclarationList); } + } - // As an optimization, if the type is an instantiation of one of the global `IteratorYieldResult` - // or `IteratorReturnResult` types, then just grab its type argument. - if (isReferenceToType(type, getGlobalIteratorYieldResultType(/*reportErrors*/ false))) { - const yieldType = getTypeArguments(type as GenericType)[0]; - return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(yieldType, /*returnType*/ undefined, /*nextType*/ undefined)); + if (node.initializer) { + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + forEach((node.initializer as VariableDeclarationList).declarations, checkVariableDeclaration); } - if (isReferenceToType(type, getGlobalIteratorReturnResultType(/*reportErrors*/ false))) { - const returnType = getTypeArguments(type as GenericType)[0]; - return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(/*yieldType*/ undefined, returnType, /*nextType*/ undefined)); + else { + checkExpression(node.initializer); } + } - // Choose any constituents that can produce the requested iteration type. - const yieldIteratorResult = filterType(type, isYieldIteratorResult); - const yieldType = yieldIteratorResult !== neverType ? getTypeOfPropertyOfType(yieldIteratorResult, "value" as __String) : undefined; + if (node.condition) checkTruthinessExpression(node.condition); + if (node.incrementor) checkExpression(node.incrementor); + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); + } + } - const returnIteratorResult = filterType(type, isReturnIteratorResult); - const returnType = returnIteratorResult !== neverType ? getTypeOfPropertyOfType(returnIteratorResult, "value" as __String) : undefined; + function checkForOfStatement(node: ForOfStatement): void { + checkGrammarForInOrForOfStatement(node); - if (!yieldType && !returnType) { - return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", noIterationTypes); + const container = getContainingFunctionOrClassStaticBlock(node); + if (node.awaitModifier) { + if (container && isClassStaticBlockDeclaration(container)) { + grammarErrorOnNode(node.awaitModifier, Diagnostics.For_await_loops_cannot_be_used_inside_a_class_static_block); + } + else { + const functionFlags = getFunctionFlags(container); + if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Async)) === FunctionFlags.Async && languageVersion < ScriptTarget.ESNext) { + // for..await..of in an async function or async generator function prior to ESNext requires the __asyncValues helper + checkExternalEmitHelpers(node, ExternalEmitHelpers.ForAwaitOfIncludes); + } } + } + else if (compilerOptions.downlevelIteration && languageVersion < ScriptTarget.ES2015) { + // for..of prior to ES2015 requires the __values helper when downlevelIteration is enabled + checkExternalEmitHelpers(node, ExternalEmitHelpers.ForOfIncludes); + } - // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface - // > ... If the iterator does not have a return value, `value` is `undefined`. In that case, the - // > `value` property may be absent from the conforming object if it does not inherit an explicit - // > `value` property. - return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(yieldType, returnType || voidType, /*nextType*/ undefined)); + // Check the LHS and RHS + // If the LHS is a declaration, just check it as a variable declaration, which will in turn check the RHS + // via checkRightHandSideOfForOf. + // If the LHS is an expression, check the LHS, as a destructuring assignment or as a reference. + // Then check that the RHS is assignable to it. + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + checkForInOrForOfVariableDeclaration(node); } + else { + const varExpr = node.initializer; + const iteratedType = checkRightHandSideOfForOf(node); - /** - * Gets the *yield*, *return*, and *next* types of a the `next()`, `return()`, or - * `throw()` method of an `Iterator`-like or `AsyncIterator`-like type. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, we return `undefined`. - */ - function getIterationTypesOfMethod(type: Type, resolver: IterationTypesResolver, methodName: "next" | "return" | "throw", errorNode: Node | undefined): IterationTypes | undefined { - const method = getPropertyOfType(type, methodName as __String); + // There may be a destructuring assignment on the left side + if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { + // iteratedType may be undefined. In this case, we still want to check the structure of + // varExpr, in particular making sure it's a valid LeftHandSideExpression. But we'd like + // to short circuit the type relation checking as much as possible, so we pass the unknownType. + checkDestructuringAssignment(varExpr, iteratedType || errorType); + } + else { + const leftType = checkExpression(varExpr); + checkReferenceExpression( + varExpr, + Diagnostics.The_left_hand_side_of_a_for_of_statement_must_be_a_variable_or_a_property_access, + Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_an_optional_property_access); - // Ignore 'return' or 'throw' if they are missing. - if (!method && methodName !== "next") { - return undefined; + // iteratedType will be undefined if the rightType was missing properties/signatures + // required to get its iteratedType (like [Symbol.iterator] or next). This may be + // because we accessed properties from anyType, or it may have led to an error inside + // getElementTypeOfIterable. + if (iteratedType) { + checkTypeAssignableToAndOptionallyElaborate(iteratedType, leftType, varExpr, node.expression); + } } + } - const methodType = method && !(methodName === "next" && (method.flags & SymbolFlags.Optional)) - ? methodName === "next" ? getTypeOfSymbol(method) : getTypeWithFacts(getTypeOfSymbol(method), TypeFacts.NEUndefinedOrNull) - : undefined; + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); + } + } - if (isTypeAny(methodType)) { - // `return()` and `throw()` don't provide a *next* type. - return methodName === "next" ? anyIterationTypes : anyIterationTypesExceptNext; - } + function checkForInStatement(node: ForInStatement) { + // Grammar checking + checkGrammarForInOrForOfStatement(node); - // Both async and non-async iterators *must* have a `next` method. - const methodSignatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : emptyArray; - if (methodSignatures.length === 0) { - if (errorNode) { - const diagnostic = methodName === "next" - ? resolver.mustHaveANextMethodDiagnostic - : resolver.mustBeAMethodDiagnostic; - error(errorNode, diagnostic, methodName); - } - return methodName === "next" ? anyIterationTypes : undefined; - } - - // If the method signature comes exclusively from the global iterator or generator type, - // create iteration types from its type arguments like `getIterationTypesOfIteratorFast` - // does (so as to remove `undefined` from the next and return types). We arrive here when - // a contextual type for a generator was not a direct reference to one of those global types, - // but looking up `methodType` referred to one of them (and nothing else). E.g., in - // `interface SpecialIterator extends Iterator {}`, `SpecialIterator` is not a - // reference to `Iterator`, but its `next` member derives exclusively from `Iterator`. - if (methodType?.symbol && methodSignatures.length === 1) { - const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false); - const globalIteratorType = resolver.getGlobalIteratorType(/*reportErrors*/ false); - const isGeneratorMethod = globalGeneratorType.symbol?.members?.get(methodName as __String) === methodType.symbol; - const isIteratorMethod = !isGeneratorMethod && globalIteratorType.symbol?.members?.get(methodName as __String) === methodType.symbol; - if (isGeneratorMethod || isIteratorMethod) { - const globalType = isGeneratorMethod ? globalGeneratorType : globalIteratorType; - const { mapper } = methodType as AnonymousType; - return createIterationTypes( - getMappedType(globalType.typeParameters![0], mapper!), - getMappedType(globalType.typeParameters![1], mapper!), - methodName === "next" ? getMappedType(globalType.typeParameters![2], mapper!) : undefined); - } - } - - // Extract the first parameter and return type of each signature. - let methodParameterTypes: Type[] | undefined; - let methodReturnTypes: Type[] | undefined; - for (const signature of methodSignatures) { - if (methodName !== "throw" && some(signature.parameters)) { - methodParameterTypes = append(methodParameterTypes, getTypeAtPosition(signature, 0)); - } - methodReturnTypes = append(methodReturnTypes, getReturnTypeOfSignature(signature)); - } - - // Resolve the *next* or *return* type from the first parameter of a `next()` or - // `return()` method, respectively. - let returnTypes: Type[] | undefined; - let nextType: Type | undefined; - if (methodName !== "throw") { - const methodParameterType = methodParameterTypes ? getUnionType(methodParameterTypes) : unknownType; - if (methodName === "next") { - // The value of `next(value)` is *not* awaited by async generators - nextType = methodParameterType; - } - else if (methodName === "return") { - // The value of `return(value)` *is* awaited by async generators - const resolvedMethodParameterType = resolver.resolveIterationType(methodParameterType, errorNode) || anyType; - returnTypes = append(returnTypes, resolvedMethodParameterType); - } + const rightType = getNonNullableTypeIfNeeded(checkExpression(node.expression)); + // TypeScript 1.0 spec (April 2014): 5.4 + // In a 'for-in' statement of the form + // for (let VarDecl in Expr) Statement + // VarDecl must be a variable declaration without a type annotation that declares a variable of type Any, + // and Expr must be an expression of type Any, an object type, or a type parameter type. + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + const variable = (node.initializer as VariableDeclarationList).declarations[0]; + if (variable && isBindingPattern(variable.name)) { + error(variable.name, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); } - - // Resolve the *yield* and *return* types from the return type of the method (i.e. `IteratorResult`) - let yieldType: Type; - const methodReturnType = methodReturnTypes ? getIntersectionType(methodReturnTypes) : neverType; - const resolvedMethodReturnType = resolver.resolveIterationType(methodReturnType, errorNode) || anyType; - const iterationTypes = getIterationTypesOfIteratorResult(resolvedMethodReturnType); - if (iterationTypes === noIterationTypes) { - if (errorNode) { - error(errorNode, resolver.mustHaveAValueDiagnostic, methodName); - } - yieldType = anyType; - returnTypes = append(returnTypes, anyType); + checkForInOrForOfVariableDeclaration(node); + } + else { + // In a 'for-in' statement of the form + // for (Var in Expr) Statement + // Var must be an expression classified as a reference of type Any or the String primitive type, + // and Expr must be an expression of type Any, an object type, or a type parameter type. + const varExpr = node.initializer; + const leftType = checkExpression(varExpr); + if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { + error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); + } + else if (!isTypeAssignableTo(getIndexTypeOrString(rightType), leftType)) { + error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_of_type_string_or_any); } else { - yieldType = iterationTypes.yieldType; - returnTypes = append(returnTypes, iterationTypes.returnType); + // run check only former check succeeded to avoid cascading errors + checkReferenceExpression( + varExpr, + Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_a_variable_or_a_property_access, + Diagnostics.The_left_hand_side_of_a_for_in_statement_may_not_be_an_optional_property_access); } - - return createIterationTypes(yieldType, getUnionType(returnTypes), nextType); } - /** - * Gets the *yield*, *return*, and *next* types of an `Iterator`-like or `AsyncIterator`-like - * type from its members. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, `noIterationTypes` is returned. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterator` instead. - */ - function getIterationTypesOfIteratorSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { - const iterationTypes = combineIterationTypes([ - getIterationTypesOfMethod(type, resolver, "next", errorNode), - getIterationTypesOfMethod(type, resolver, "return", errorNode), - getIterationTypesOfMethod(type, resolver, "throw", errorNode), - ]); - return setCachedIterationTypes(type, resolver.iteratorCacheKey, iterationTypes); + // unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved + // in this case error about missing name is already reported - do not report extra one + if (rightType === neverType || !isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { + error(node.expression, Diagnostics.The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter_but_here_has_type_0, typeToString(rightType)); } - /** - * Gets the requested "iteration type" from a type that is either `Iterable`-like, `Iterator`-like, - * `IterableIterator`-like, or `Generator`-like (for a non-async generator); or `AsyncIterable`-like, - * `AsyncIterator`-like, `AsyncIterableIterator`-like, or `AsyncGenerator`-like (for an async generator). - */ - function getIterationTypeOfGeneratorFunctionReturnType(kind: IterationTypeKind, returnType: Type, isAsyncGenerator: boolean): Type | undefined { - if (isTypeAny(returnType)) { - return undefined; - } - - const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsyncGenerator); - return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(kind)]; + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); } + } - function getIterationTypesOfGeneratorFunctionReturnType(type: Type, isAsyncGenerator: boolean) { - if (isTypeAny(type)) { - return anyIterationTypes; - } - - const use = isAsyncGenerator ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType; - const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; - return getIterationTypesOfIterable(type, use, /*errorNode*/ undefined) || - getIterationTypesOfIterator(type, resolver, /*errorNode*/ undefined); + function checkForInOrForOfVariableDeclaration(iterationStatement: ForInOrOfStatement): void { + const variableDeclarationList = iterationStatement.initializer as VariableDeclarationList; + // checkGrammarForInOrForOfStatement will check that there is exactly one declaration. + if (variableDeclarationList.declarations.length >= 1) { + const decl = variableDeclarationList.declarations[0]; + checkVariableDeclaration(decl); } + } - function checkBreakOrContinueStatement(node: BreakOrContinueStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) checkGrammarBreakOrContinueStatement(node); + function checkRightHandSideOfForOf(statement: ForOfStatement): Type { + const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; + return checkIteratedTypeOrElementType(use, checkNonNullExpression(statement.expression), undefinedType, statement.expression); + } - // TODO: Check that target label is valid + function checkIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined): Type { + if (isTypeAny(inputType)) { + return inputType; } + return getIteratedTypeOrElementType(use, inputType, sentType, errorNode, /*checkAssignability*/ true) || anyType; + } - function unwrapReturnType(returnType: Type, functionFlags: FunctionFlags) { - const isGenerator = !!(functionFlags & FunctionFlags.Generator); - const isAsync = !!(functionFlags & FunctionFlags.Async); - return isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync) || errorType : - isAsync ? getAwaitedTypeNoAlias(returnType) || errorType : - returnType; + /** + * When consuming an iterable type in a for..of, spread, or iterator destructuring assignment + * we want to get the iterated type of an iterable for ES2015 or later, or the iterated type + * of a iterable (if defined globally) or element type of an array like for ES2015 or earlier. + */ + function getIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined, checkAssignability: boolean): Type | undefined { + const allowAsyncIterables = (use & IterationUse.AllowsAsyncIterablesFlag) !== 0; + if (inputType === neverType) { + reportTypeNotIterableError(errorNode!, inputType, allowAsyncIterables); // TODO: GH#18217 + return undefined; } - function isUnwrappedReturnTypeVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean { - const unwrappedReturnType = unwrapReturnType(returnType, getFunctionFlags(func)); - return !!unwrappedReturnType && maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.AnyOrUnknown); - } + const uplevelIteration = languageVersion >= ScriptTarget.ES2015; + const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration; + const possibleOutOfBounds = compilerOptions.noUncheckedIndexedAccess && !!(use & IterationUse.PossiblyOutOfBounds); - function checkReturnStatement(node: ReturnStatement) { - // Grammar checking - if (checkGrammarStatementInAmbientContext(node)) { - return; + // Get the iterated type of an `Iterable` or `IterableIterator` only in ES2015 + // or higher, when inside of an async generator or for-await-if, or when + // downlevelIteration is requested. + if (uplevelIteration || downlevelIteration || allowAsyncIterables) { + // We only report errors for an invalid iterable type in ES2015 or higher. + const iterationTypes = getIterationTypesOfIterable(inputType, use, uplevelIteration ? errorNode : undefined); + if (checkAssignability) { + if (iterationTypes) { + const diagnostic = + use & IterationUse.ForOfFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_for_of_will_always_send_0 : + use & IterationUse.SpreadFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_spread_will_always_send_0 : + use & IterationUse.DestructuringFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_destructuring_will_always_send_0 : + use & IterationUse.YieldStarFlag ? Diagnostics.Cannot_delegate_iteration_to_value_because_the_next_method_of_its_iterator_expects_type_1_but_the_containing_generator_will_always_send_0 : + undefined; + if (diagnostic) { + checkTypeAssignableTo(sentType, iterationTypes.nextType, errorNode, diagnostic); + } + } } - - const container = getContainingFunctionOrClassStaticBlock(node); - if(container && isClassStaticBlockDeclaration(container)) { - grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_cannot_be_used_inside_a_class_static_block); - return; + if (iterationTypes || uplevelIteration) { + return possibleOutOfBounds ? includeUndefinedInIndexSignature(iterationTypes && iterationTypes.yieldType) : (iterationTypes && iterationTypes.yieldType); } + } - if (!container) { - grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body); - return; - } + let arrayType = inputType; + let reportedError = false; + let hasStringConstituent = false; - const signature = getSignatureFromDeclaration(container); - const returnType = getReturnTypeOfSignature(signature); - const functionFlags = getFunctionFlags(container); - if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { - const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; - if (container.kind === SyntaxKind.SetAccessor) { - if (node.expression) { - error(node, Diagnostics.Setters_cannot_return_a_value); - } + // If strings are permitted, remove any string-like constituents from the array type. + // This allows us to find other non-string element types from an array unioned with + // a string. + if (use & IterationUse.AllowsStringInputFlag) { + if (arrayType.flags & TypeFlags.Union) { + // After we remove all types that are StringLike, we will know if there was a string constituent + // based on whether the result of filter is a new array. + const arrayTypes = (inputType as UnionType).types; + const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike)); + if (filteredTypes !== arrayTypes) { + arrayType = getUnionType(filteredTypes, UnionReduction.Subtype); } - else if (container.kind === SyntaxKind.Constructor) { - if (node.expression && !checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression)) { - error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); + } + else if (arrayType.flags & TypeFlags.StringLike) { + arrayType = neverType; + } + + hasStringConstituent = arrayType !== inputType; + if (hasStringConstituent) { + if (languageVersion < ScriptTarget.ES5) { + if (errorNode) { + error(errorNode, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher); + reportedError = true; } } - else if (getReturnTypeFromAnnotation(container)) { - const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; - const unwrappedExprType = functionFlags & FunctionFlags.Async - ? checkAwaitedType(exprType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) - : exprType; - if (unwrappedReturnType) { - // If the function has a return type, but promisedType is - // undefined, an error will be reported in checkAsyncFunctionReturnType - // so we don't need to report one here. - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); - } + + // Now that we've removed all the StringLike types, if no constituents remain, then the entire + // arrayOrStringType was a string. + if (arrayType.flags & TypeFlags.Never) { + return possibleOutOfBounds ? includeUndefinedInIndexSignature(stringType) : stringType; } } - else if (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(container, returnType)) { - // The function has a return type, but the return statement doesn't have an expression. - error(node, Diagnostics.Not_all_code_paths_return_a_value); - } } - function checkWithStatement(node: WithStatement) { - // Grammar checking for withStatement - if (!checkGrammarStatementInAmbientContext(node)) { - if (node.flags & NodeFlags.AwaitContext) { - grammarErrorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_an_async_function_block); - } + if (!isArrayLikeType(arrayType)) { + if (errorNode && !reportedError) { + // Which error we report depends on whether we allow strings or if there was a + // string constituent. For example, if the input type is number | string, we + // want to say that number is not an array type. But if the input was just + // number and string input is allowed, we want to say that number is not an + // array type or a string type. + const allowsStrings = !!(use & IterationUse.AllowsStringInputFlag) && !hasStringConstituent; + const [defaultDiagnostic, maybeMissingAwait] = getIterationDiagnosticDetails(allowsStrings, downlevelIteration); + errorAndMaybeSuggestAwait( + errorNode, + maybeMissingAwait && !!getAwaitedTypeOfPromise(arrayType), + defaultDiagnostic, + typeToString(arrayType)); } + return hasStringConstituent ? possibleOutOfBounds ? includeUndefinedInIndexSignature(stringType) : stringType : undefined; + } - checkExpression(node.expression); - - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const start = getSpanOfTokenAtPosition(sourceFile, node.pos).start; - const end = node.statement.pos; - grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.The_with_statement_is_not_supported_All_symbols_in_a_with_block_will_have_type_any); + const arrayElementType = getIndexTypeOfType(arrayType, numberType); + if (hasStringConstituent && arrayElementType) { + // This is just an optimization for the case where arrayOrStringType is string | string[] + if (arrayElementType.flags & TypeFlags.StringLike && !compilerOptions.noUncheckedIndexedAccess) { + return stringType; } + + return getUnionType(possibleOutOfBounds ? [arrayElementType, stringType, undefinedType] : [arrayElementType, stringType], UnionReduction.Subtype); } - function checkSwitchStatement(node: SwitchStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); + return (use & IterationUse.PossiblyOutOfBounds) ? includeUndefinedInIndexSignature(arrayElementType) : arrayElementType; - let firstDefaultClause: CaseOrDefaultClause; - let hasDuplicateDefaultClause = false; + function getIterationDiagnosticDetails(allowsStrings: boolean, downlevelIteration: boolean | undefined): [DiagnosticMessage, boolean] { + if (downlevelIteration) { + return allowsStrings + ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true] + : [Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true]; + } - const expressionType = checkExpression(node.expression); - const expressionIsLiteral = isLiteralType(expressionType); - forEach(node.caseBlock.clauses, clause => { - // Grammar check for duplicate default clauses, skip if we already report duplicate default clause - if (clause.kind === SyntaxKind.DefaultClause && !hasDuplicateDefaultClause) { - if (firstDefaultClause === undefined) { - firstDefaultClause = clause; - } - else { - grammarErrorOnNode(clause, Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement); - hasDuplicateDefaultClause = true; - } - } + const yieldType = getIterationTypeOfIterable(use, IterationTypeKind.Yield, inputType, /*errorNode*/ undefined); - if (produceDiagnostics && clause.kind === SyntaxKind.CaseClause) { - // TypeScript 1.0 spec (April 2014): 5.9 - // In a 'switch' statement, each 'case' expression must be of a type that is comparable - // to or from the type of the 'switch' expression. - let caseType = checkExpression(clause.expression); - const caseIsLiteral = isLiteralType(caseType); - let comparedExpressionType = expressionType; - if (!caseIsLiteral || !expressionIsLiteral) { - caseType = caseIsLiteral ? getBaseTypeOfLiteralType(caseType) : caseType; - comparedExpressionType = getBaseTypeOfLiteralType(expressionType); - } - if (!isTypeEqualityComparableTo(comparedExpressionType, caseType)) { - // expressionType is not comparable to caseType, try the reversed check and report errors if it fails - checkTypeComparableTo(caseType, comparedExpressionType, clause.expression, /*headMessage*/ undefined); - } - } - forEach(clause.statements, checkSourceElement); - if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) { - error(clause, Diagnostics.Fallthrough_case_in_switch); - } - }); - if (node.caseBlock.locals) { - registerForUnusedIdentifiersCheck(node.caseBlock); + if (yieldType) { + return [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_Use_compiler_option_downlevelIteration_to_allow_iterating_of_iterators, false]; } - } - function checkLabeledStatement(node: LabeledStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - findAncestor(node.parent, current => { - if (isFunctionLike(current)) { - return "quit"; - } - if (current.kind === SyntaxKind.LabeledStatement && (current as LabeledStatement).label.escapedText === node.label.escapedText) { - grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNode(node.label)); - return true; - } - return false; - }); + if (isES2015OrLaterIterable(inputType.symbol?.escapedName)) { + return [Diagnostics.Type_0_can_only_be_iterated_through_when_using_the_downlevelIteration_flag_or_with_a_target_of_es2015_or_higher, true]; } - // ensure that label is unique - checkSourceElement(node.statement); + return allowsStrings + ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type, true] + : [Diagnostics.Type_0_is_not_an_array_type, true]; } + } - function checkThrowStatement(node: ThrowStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - if (isIdentifier(node.expression) && !node.expression.escapedText) { - grammarErrorAfterFirstToken(node, Diagnostics.Line_break_not_permitted_here); - } - } - - if (node.expression) { - checkExpression(node.expression); - } + function isES2015OrLaterIterable(n: __String) { + switch (n) { + case "Float32Array": + case "Float64Array": + case "Int16Array": + case "Int32Array": + case "Int8Array": + case "NodeList": + case "Uint16Array": + case "Uint32Array": + case "Uint8Array": + case "Uint8ClampedArray": + return true; } + return false; + } - function checkTryStatement(node: TryStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); + /** + * Gets the requested "iteration type" from an `Iterable`-like or `AsyncIterable`-like type. + */ + function getIterationTypeOfIterable(use: IterationUse, typeKind: IterationTypeKind, inputType: Type, errorNode: Node | undefined): Type | undefined { + if (isTypeAny(inputType)) { + return undefined; + } - checkBlock(node.tryBlock); - const catchClause = node.catchClause; - if (catchClause) { - // Grammar checking - if (catchClause.variableDeclaration) { - const declaration = catchClause.variableDeclaration; - const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration)); - if (typeNode) { - const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false); - if (type && !(type.flags & TypeFlags.AnyOrUnknown)) { - grammarErrorOnFirstToken(typeNode, Diagnostics.Catch_clause_variable_type_annotation_must_be_any_or_unknown_if_specified); - } - } - else if (declaration.initializer) { - grammarErrorOnFirstToken(declaration.initializer, Diagnostics.Catch_clause_variable_cannot_have_an_initializer); - } - else { - const blockLocals = catchClause.block.locals; - if (blockLocals) { - forEachKey(catchClause.locals!, caughtName => { - const blockLocal = blockLocals.get(caughtName); - if (blockLocal?.valueDeclaration && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) { - grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, caughtName); - } - }); - } - } - } + const iterationTypes = getIterationTypesOfIterable(inputType, use, errorNode); + return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(typeKind)]; + } - checkBlock(catchClause.block); - } + function createIterationTypes(yieldType: Type = neverType, returnType: Type = neverType, nextType: Type = unknownType): IterationTypes { + // `yieldType` and `returnType` are defaulted to `neverType` they each will be combined + // via `getUnionType` when merging iteration types. `nextType` is defined as `unknownType` + // as it is combined via `getIntersectionType` when merging iteration types. + + // Use the cache only for intrinsic types to keep it small as they are likely to be + // more frequently created (i.e. `Iterator`). Iteration types + // are also cached on the type they are requested for, so we shouldn't need to maintain + // the cache for less-frequently used types. + if (yieldType.flags & TypeFlags.Intrinsic && + returnType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined) && + nextType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined)) { + const id = getTypeListId([yieldType, returnType, nextType]); + let iterationTypes = iterationTypesCache.get(id); + if (!iterationTypes) { + iterationTypes = { yieldType, returnType, nextType }; + iterationTypesCache.set(id, iterationTypes); + } + return iterationTypes; + } + return { yieldType, returnType, nextType }; + } - if (node.finallyBlock) { - checkBlock(node.finallyBlock); + /** + * Combines multiple `IterationTypes` records. + * + * If `array` is empty or all elements are missing or are references to `noIterationTypes`, + * then `noIterationTypes` is returned. Otherwise, an `IterationTypes` record is returned + * for the combined iteration types. + */ + function combineIterationTypes(array: (IterationTypes | undefined)[]) { + let yieldTypes: Type[] | undefined; + let returnTypes: Type[] | undefined; + let nextTypes: Type[] | undefined; + for (const iterationTypes of array) { + if (iterationTypes === undefined || iterationTypes === noIterationTypes) { + continue; + } + if (iterationTypes === anyIterationTypes) { + return anyIterationTypes; } + yieldTypes = append(yieldTypes, iterationTypes.yieldType); + returnTypes = append(returnTypes, iterationTypes.returnType); + nextTypes = append(nextTypes, iterationTypes.nextType); + } + if (yieldTypes || returnTypes || nextTypes) { + return createIterationTypes( + yieldTypes && getUnionType(yieldTypes), + returnTypes && getUnionType(returnTypes), + nextTypes && getIntersectionType(nextTypes)); } + return noIterationTypes; + } - function checkIndexConstraints(type: Type, symbol: Symbol, isStaticIndex?: boolean) { - const indexInfos = getIndexInfosOfType(type); - if (indexInfos.length === 0) { - return; - } - for (const prop of getPropertiesOfObjectType(type)) { - if (!(isStaticIndex && prop.flags & SymbolFlags.Prototype)) { - checkIndexConstraintForProperty(type, prop, getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique, /*includeNonPublic*/ true), getNonMissingTypeOfSymbol(prop)); - } - } - const typeDeclaration = symbol.valueDeclaration; - if (typeDeclaration && isClassLike(typeDeclaration)) { - for (const member of typeDeclaration.members) { - // Only process instance properties with computed names here. Static properties cannot be in conflict with indexers, - // and properties with literal names were already checked. - if (!isStatic(member) && !hasBindableName(member)) { - const symbol = getSymbolOfNode(member); - checkIndexConstraintForProperty(type, symbol, getTypeOfExpression((member as DynamicNamedDeclaration).name.expression), getNonMissingTypeOfSymbol(symbol)); - } - } - } - if (indexInfos.length > 1) { - for (const info of indexInfos) { - checkIndexConstraintForIndexSignature(type, info); + function getCachedIterationTypes(type: Type, cacheKey: MatchingKeys) { + return (type as IterableOrIteratorType)[cacheKey]; + } + + function setCachedIterationTypes(type: Type, cacheKey: MatchingKeys, cachedTypes: IterationTypes) { + return (type as IterableOrIteratorType)[cacheKey] = cachedTypes; + } + + /** + * Gets the *yield*, *return*, and *next* types from an `Iterable`-like or `AsyncIterable`-like type. + * + * At every level that involves analyzing return types of signatures, we union the return types of all the signatures. + * + * Another thing to note is that at any step of this process, we could run into a dead end, + * meaning either the property is missing, or we run into the anyType. If either of these things + * happens, we return `undefined` to signal that we could not find the iteration type. If a property + * is missing, and the previous step did not result in `any`, then we also give an error if the + * caller requested it. Then the caller can decide what to do in the case where there is no iterated + * type. + * + * For a **for-of** statement, `yield*` (in a normal generator), spread, array + * destructuring, or normal generator we will only ever look for a `[Symbol.iterator]()` + * method. + * + * For an async generator we will only ever look at the `[Symbol.asyncIterator]()` method. + * + * For a **for-await-of** statement or a `yield*` in an async generator we will look for + * the `[Symbol.asyncIterator]()` method first, and then the `[Symbol.iterator]()` method. + */ + function getIterationTypesOfIterable(type: Type, use: IterationUse, errorNode: Node | undefined) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + + if (!(type.flags & TypeFlags.Union)) { + const iterationTypes = getIterationTypesOfIterableWorker(type, use, errorNode); + if (iterationTypes === noIterationTypes) { + if (errorNode) { + reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); } + return undefined; } + return iterationTypes; } - function checkIndexConstraintForProperty(type: Type, prop: Symbol, propNameType: Type, propType: Type) { - const declaration = prop.valueDeclaration; - const name = getNameOfDeclaration(declaration); - if (name && isPrivateIdentifier(name)) { - return; - } - const indexInfos = getApplicableIndexInfos(type, propNameType); - const interfaceDeclaration = getObjectFlags(type) & ObjectFlags.Interface ? getDeclarationOfKind(type.symbol, SyntaxKind.InterfaceDeclaration) : undefined; - const localPropDeclaration = declaration && declaration.kind === SyntaxKind.BinaryExpression || - name && name.kind === SyntaxKind.ComputedPropertyName || getParentOfSymbol(prop) === type.symbol ? declaration : undefined; - for (const info of indexInfos) { - const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfNode(info.declaration)) === type.symbol ? info.declaration : undefined; - // We check only when (a) the property is declared in the containing type, or (b) the applicable index signature is declared - // in the containing type, or (c) the containing type is an interface and no base interface contains both the property and - // the index signature (i.e. property and index signature are declared in separate inherited interfaces). - const errorNode = localPropDeclaration || localIndexDeclaration || - (interfaceDeclaration && !some(getBaseTypes(type as InterfaceType), base => !!getPropertyOfObjectType(base, prop.escapedName) && !!getIndexTypeOfType(base, info.keyType)) ? interfaceDeclaration : undefined); - if (errorNode && !isTypeAssignableTo(propType, info.type)) { - error(errorNode, Diagnostics.Property_0_of_type_1_is_not_assignable_to_2_index_type_3, - symbolToString(prop), typeToString(propType), typeToString(info.keyType), typeToString(info.type)); + const cacheKey = use & IterationUse.AllowsAsyncIterablesFlag ? "iterationTypesOfAsyncIterable" : "iterationTypesOfIterable"; + const cachedTypes = getCachedIterationTypes(type, cacheKey); + if (cachedTypes) return cachedTypes === noIterationTypes ? undefined : cachedTypes; + + let allIterationTypes: IterationTypes[] | undefined; + for (const constituent of (type as UnionType).types) { + const iterationTypes = getIterationTypesOfIterableWorker(constituent, use, errorNode); + if (iterationTypes === noIterationTypes) { + if (errorNode) { + reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); } + setCachedIterationTypes(type, cacheKey, noIterationTypes); + return undefined; } - } - - function checkIndexConstraintForIndexSignature(type: Type, checkInfo: IndexInfo) { - const declaration = checkInfo.declaration; - const indexInfos = getApplicableIndexInfos(type, checkInfo.keyType); - const interfaceDeclaration = getObjectFlags(type) & ObjectFlags.Interface ? getDeclarationOfKind(type.symbol, SyntaxKind.InterfaceDeclaration) : undefined; - const localCheckDeclaration = declaration && getParentOfSymbol(getSymbolOfNode(declaration)) === type.symbol ? declaration : undefined; - for (const info of indexInfos) { - if (info === checkInfo) continue; - const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfNode(info.declaration)) === type.symbol ? info.declaration : undefined; - // We check only when (a) the check index signature is declared in the containing type, or (b) the applicable index - // signature is declared in the containing type, or (c) the containing type is an interface and no base interface contains - // both index signatures (i.e. the index signatures are declared in separate inherited interfaces). - const errorNode = localCheckDeclaration || localIndexDeclaration || - (interfaceDeclaration && !some(getBaseTypes(type as InterfaceType), base => !!getIndexInfoOfType(base, checkInfo.keyType) && !!getIndexTypeOfType(base, info.keyType)) ? interfaceDeclaration : undefined); - if (errorNode && !isTypeAssignableTo(checkInfo.type, info.type)) { - error(errorNode, Diagnostics._0_index_type_1_is_not_assignable_to_2_index_type_3, - typeToString(checkInfo.keyType), typeToString(checkInfo.type), typeToString(info.keyType), typeToString(info.type)); - } - } - } - - function checkTypeNameIsReserved(name: Identifier, message: DiagnosticMessage): void { - // TS 1.0 spec (April 2014): 3.6.1 - // The predefined type keywords are reserved and cannot be used as names of user defined types. - switch (name.escapedText) { - case "any": - case "unknown": - case "never": - case "number": - case "bigint": - case "boolean": - case "string": - case "symbol": - case "void": - case "object": - error(name, message, name.escapedText as string); + else { + allIterationTypes = append(allIterationTypes, iterationTypes); } } - /** - * The name cannot be used as 'Object' of user defined types with special target. - */ - function checkClassNameCollisionWithObject(name: Identifier): void { - if (languageVersion >= ScriptTarget.ES5 && name.escapedText === "Object" - && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(name).impliedNodeFormat === ModuleKind.CommonJS)) { - error(name, Diagnostics.Class_name_cannot_be_Object_when_targeting_ES5_with_module_0, ModuleKind[moduleKind]); // https://github.com/Microsoft/TypeScript/issues/17494 - } - } + const iterationTypes = allIterationTypes ? combineIterationTypes(allIterationTypes) : noIterationTypes; + setCachedIterationTypes(type, cacheKey, iterationTypes); + return iterationTypes === noIterationTypes ? undefined : iterationTypes; + } - /** - * Check each type parameter and check that type parameters have no duplicate type parameter declarations - */ - function checkTypeParameters(typeParameterDeclarations: readonly TypeParameterDeclaration[] | undefined) { - if (typeParameterDeclarations) { - let seenDefault = false; - for (let i = 0; i < typeParameterDeclarations.length; i++) { - const node = typeParameterDeclarations[i]; - checkTypeParameter(node); - - if (produceDiagnostics) { - if (node.default) { - seenDefault = true; - checkTypeParametersNotReferenced(node.default, typeParameterDeclarations, i); - } - else if (seenDefault) { - error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters); - } - for (let j = 0; j < i; j++) { - if (typeParameterDeclarations[j].symbol === node.symbol) { - error(node.name, Diagnostics.Duplicate_identifier_0, declarationNameToString(node.name)); - } - } - } - } + function getAsyncFromSyncIterationTypes(iterationTypes: IterationTypes, errorNode: Node | undefined) { + if (iterationTypes === noIterationTypes) return noIterationTypes; + if (iterationTypes === anyIterationTypes) return anyIterationTypes; + const { yieldType, returnType, nextType } = iterationTypes; + // if we're requesting diagnostics, report errors for a missing `Awaited`. + if (errorNode) { + getGlobalAwaitedSymbol(/*reportErrors*/ true); + } + return createIterationTypes( + getAwaitedType(yieldType, errorNode) || anyType, + getAwaitedType(returnType, errorNode) || anyType, + nextType); + } + + /** + * Gets the *yield*, *return*, and *next* types from a non-union type. + * + * If we are unable to find the *yield*, *return*, and *next* types, `noIterationTypes` is + * returned to indicate to the caller that it should report an error. Otherwise, an + * `IterationTypes` record is returned. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterable` instead. + */ + function getIterationTypesOfIterableWorker(type: Type, use: IterationUse, errorNode: Node | undefined) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + + if (use & IterationUse.AllowsAsyncIterablesFlag) { + const iterationTypes = + getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) || + getIterationTypesOfIterableFast(type, asyncIterationTypesResolver); + if (iterationTypes) { + return use & IterationUse.ForOfFlag ? + getAsyncFromSyncIterationTypes(iterationTypes, errorNode) : + iterationTypes; } } - /** Check that type parameter defaults only reference previously declared type parameters */ - function checkTypeParametersNotReferenced(root: TypeNode, typeParameters: readonly TypeParameterDeclaration[], index: number) { - visit(root); - function visit(node: Node) { - if (node.kind === SyntaxKind.TypeReference) { - const type = getTypeFromTypeReference(node as TypeReferenceNode); - if (type.flags & TypeFlags.TypeParameter) { - for (let i = index; i < typeParameters.length; i++) { - if (type.symbol === getSymbolOfNode(typeParameters[i])) { - error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters); - } - } + if (use & IterationUse.AllowsSyncIterablesFlag) { + const iterationTypes = + getIterationTypesOfIterableCached(type, syncIterationTypesResolver) || + getIterationTypesOfIterableFast(type, syncIterationTypesResolver); + if (iterationTypes) { + if (use & IterationUse.AllowsAsyncIterablesFlag) { + // for a sync iterable in an async context, only use the cached types if they are valid. + if (iterationTypes !== noIterationTypes) { + return setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", getAsyncFromSyncIterationTypes(iterationTypes, errorNode)); } } - forEachChild(node, visit); + else { + return iterationTypes; + } } } - /** Check that type parameter lists are identical across multiple declarations */ - function checkTypeParameterListsIdentical(symbol: Symbol) { - if (symbol.declarations && symbol.declarations.length === 1) { - return; + if (use & IterationUse.AllowsAsyncIterablesFlag) { + const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode); + if (iterationTypes !== noIterationTypes) { + return iterationTypes; } + } - const links = getSymbolLinks(symbol); - if (!links.typeParametersChecked) { - links.typeParametersChecked = true; - const declarations = getClassOrInterfaceDeclarationsOfSymbol(symbol); - if (!declarations || declarations.length <= 1) { - return; + if (use & IterationUse.AllowsSyncIterablesFlag) { + const iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode); + if (iterationTypes !== noIterationTypes) { + if (use & IterationUse.AllowsAsyncIterablesFlag) { + return setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", iterationTypes + ? getAsyncFromSyncIterationTypes(iterationTypes, errorNode) + : noIterationTypes); } - - const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; - if (!areTypeParametersIdentical(declarations, type.localTypeParameters!)) { - // Report an error on every conflicting declaration. - const name = symbolToString(symbol); - for (const declaration of declarations) { - error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_type_parameters, name); - } + else { + return iterationTypes; } } } - function areTypeParametersIdentical(declarations: readonly (ClassDeclaration | InterfaceDeclaration)[], targetParameters: TypeParameter[]) { - const maxTypeArgumentCount = length(targetParameters); - const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters); + return noIterationTypes; + } - for (const declaration of declarations) { - // If this declaration has too few or too many type parameters, we report an error - const sourceParameters = getEffectiveTypeParameterDeclarations(declaration); - const numTypeParameters = sourceParameters.length; - if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) { - return false; - } + /** + * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or + * `AsyncIterable`-like type from the cache. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterable` instead. + */ + function getIterationTypesOfIterableCached(type: Type, resolver: IterationTypesResolver) { + return getCachedIterationTypes(type, resolver.iterableCacheKey); + } - for (let i = 0; i < numTypeParameters; i++) { - const source = sourceParameters[i]; - const target = targetParameters[i]; + function getIterationTypesOfGlobalIterableType(globalType: Type, resolver: IterationTypesResolver) { + const globalIterationTypes = + getIterationTypesOfIterableCached(globalType, resolver) || + getIterationTypesOfIterableSlow(globalType, resolver, /*errorNode*/ undefined); + return globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; + } - // If the type parameter node does not have the same as the resolved type - // parameter at this position, we report an error. - if (source.name.escapedText !== target.symbol.escapedName) { - return false; - } + /** + * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like + * type from from common heuristics. + * + * If we previously analyzed this type and found no iteration types, `noIterationTypes` is + * returned. If we found iteration types, an `IterationTypes` record is returned. + * Otherwise, we return `undefined` to indicate to the caller it should perform a more + * exhaustive analysis. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterable` instead. + */ + function getIterationTypesOfIterableFast(type: Type, resolver: IterationTypesResolver) { + // As an optimization, if the type is an instantiation of one of the following global types, then + // just grab its related type argument: + // - `Iterable` or `AsyncIterable` + // - `IterableIterator` or `AsyncIterableIterator` + let globalType: Type; + if (isReferenceToType(type, globalType = resolver.getGlobalIterableType(/*reportErrors*/ false)) || + isReferenceToType(type, globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false))) { + const [yieldType] = getTypeArguments(type as GenericType); + // The "return" and "next" types of `Iterable` and `IterableIterator` are defined by the + // iteration types of their `[Symbol.iterator]()` method. The same is true for their async cousins. + // While we define these as `any` and `undefined` in our libs by default, a custom lib *could* use + // different definitions. + const { returnType, nextType } = getIterationTypesOfGlobalIterableType(globalType, resolver); + return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); + } + + // As an optimization, if the type is an instantiation of the following global type, then + // just grab its related type arguments: + // - `Generator` or `AsyncGenerator` + if (isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { + const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); + return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); + } + } - // If the type parameter node does not have an identical constraint as the resolved - // type parameter at this position, we report an error. - const constraint = getEffectiveConstraintOfTypeParameter(source); - const sourceConstraint = constraint && getTypeFromTypeNode(constraint); - const targetConstraint = getConstraintOfTypeParameter(target); - // relax check if later interface augmentation has no constraint, it's more broad and is OK to merge with - // a more constrained interface (this could be generalized to a full hierarchy check, but that's maybe overkill) - if (sourceConstraint && targetConstraint && !isTypeIdenticalTo(sourceConstraint, targetConstraint)) { - return false; - } + function getPropertyNameForKnownSymbolName(symbolName: string): __String { + const ctorType = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); + const uniqueType = ctorType && getTypeOfPropertyOfType(getTypeOfSymbol(ctorType), escapeLeadingUnderscores(symbolName)); + return uniqueType && isTypeUsableAsPropertyName(uniqueType) ? getPropertyNameFromType(uniqueType) : `__@${symbolName}` as __String; + } - // If the type parameter node has a default and it is not identical to the default - // for the type parameter at this position, we report an error. - const sourceDefault = source.default && getTypeFromTypeNode(source.default); - const targetDefault = getDefaultFromTypeParameter(target); - if (sourceDefault && targetDefault && !isTypeIdenticalTo(sourceDefault, targetDefault)) { - return false; - } - } - } + /** + * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like + * type from its members. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, `noIterationTypes` is returned. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterable` instead. + */ + function getIterationTypesOfIterableSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { + const method = getPropertyOfType(type, getPropertyNameForKnownSymbolName(resolver.iteratorSymbolName)); + const methodType = method && !(method.flags & SymbolFlags.Optional) ? getTypeOfSymbol(method) : undefined; + if (isTypeAny(methodType)) { + return setCachedIterationTypes(type, resolver.iterableCacheKey, anyIterationTypes); + } + + const signatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : undefined; + if (!some(signatures)) { + return setCachedIterationTypes(type, resolver.iterableCacheKey, noIterationTypes); + } + + const iteratorType = getIntersectionType(map(signatures, getReturnTypeOfSignature)); + const iterationTypes = getIterationTypesOfIterator(iteratorType, resolver, errorNode) ?? noIterationTypes; + return setCachedIterationTypes(type, resolver.iterableCacheKey, iterationTypes); + } - return true; + function reportTypeNotIterableError(errorNode: Node, type: Type, allowAsyncIterables: boolean): void { + const message = allowAsyncIterables + ? Diagnostics.Type_0_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator + : Diagnostics.Type_0_must_have_a_Symbol_iterator_method_that_returns_an_iterator; + errorAndMaybeSuggestAwait(errorNode, !!getAwaitedTypeOfPromise(type), message, typeToString(type)); + } + + /** + * Gets the *yield*, *return*, and *next* types from an `Iterator`-like or `AsyncIterator`-like type. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, `undefined` is returned. + */ + function getIterationTypesOfIterator(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + + const iterationTypes = + getIterationTypesOfIteratorCached(type, resolver) || + getIterationTypesOfIteratorFast(type, resolver) || + getIterationTypesOfIteratorSlow(type, resolver, errorNode); + return iterationTypes === noIterationTypes ? undefined : iterationTypes; + } + + /** + * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the + * cache. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterator` instead. + */ + function getIterationTypesOfIteratorCached(type: Type, resolver: IterationTypesResolver) { + return getCachedIterationTypes(type, resolver.iteratorCacheKey); + } + + /** + * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the + * cache or from common heuristics. + * + * If we previously analyzed this type and found no iteration types, `noIterationTypes` is + * returned. If we found iteration types, an `IterationTypes` record is returned. + * Otherwise, we return `undefined` to indicate to the caller it should perform a more + * exhaustive analysis. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterator` instead. + */ + function getIterationTypesOfIteratorFast(type: Type, resolver: IterationTypesResolver) { + // As an optimization, if the type is an instantiation of one of the following global types, + // then just grab its related type argument: + // - `IterableIterator` or `AsyncIterableIterator` + // - `Iterator` or `AsyncIterator` + // - `Generator` or `AsyncGenerator` + const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); + if (isReferenceToType(type, globalType)) { + const [yieldType] = getTypeArguments(type as GenericType); + // The "return" and "next" types of `IterableIterator` and `AsyncIterableIterator` are defined by the + // iteration types of their `next`, `return`, and `throw` methods. While we define these as `any` + // and `undefined` in our libs by default, a custom lib *could* use different definitions. + const globalIterationTypes = + getIterationTypesOfIteratorCached(globalType, resolver) || + getIterationTypesOfIteratorSlow(globalType, resolver, /*errorNode*/ undefined); + const { returnType, nextType } = globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; + return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType)); + } + if (isReferenceToType(type, resolver.getGlobalIteratorType(/*reportErrors*/ false)) || + isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { + const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); + return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType)); + } + } + + function isIteratorResult(type: Type, kind: IterationTypeKind.Yield | IterationTypeKind.Return) { + // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface: + // > [done] is the result status of an iterator `next` method call. If the end of the iterator was reached `done` is `true`. + // > If the end was not reached `done` is `false` and a value is available. + // > If a `done` property (either own or inherited) does not exist, it is consider to have the value `false`. + const doneType = getTypeOfPropertyOfType(type, "done" as __String) || falseType; + return isTypeAssignableTo(kind === IterationTypeKind.Yield ? falseType : trueType, doneType); + } + + function isYieldIteratorResult(type: Type) { + return isIteratorResult(type, IterationTypeKind.Yield); + } + + function isReturnIteratorResult(type: Type) { + return isIteratorResult(type, IterationTypeKind.Return); + } + + /** + * Gets the *yield* and *return* types of an `IteratorResult`-like type. + * + * If we are unable to determine a *yield* or a *return* type, `noIterationTypes` is + * returned to indicate to the caller that it should handle the error. Otherwise, an + * `IterationTypes` record is returned. + */ + function getIterationTypesOfIteratorResult(type: Type) { + if (isTypeAny(type)) { + return anyIterationTypes; } - function checkClassExpression(node: ClassExpression): Type { - checkClassLikeDeclaration(node); - checkNodeDeferred(node); - return getTypeOfSymbol(getSymbolOfNode(node)); + const cachedTypes = getCachedIterationTypes(type, "iterationTypesOfIteratorResult"); + if (cachedTypes) { + return cachedTypes; } - function checkClassExpressionDeferred(node: ClassExpression) { - forEach(node.members, checkSourceElement); - registerForUnusedIdentifiersCheck(node); + // As an optimization, if the type is an instantiation of one of the global `IteratorYieldResult` + // or `IteratorReturnResult` types, then just grab its type argument. + if (isReferenceToType(type, getGlobalIteratorYieldResultType(/*reportErrors*/ false))) { + const yieldType = getTypeArguments(type as GenericType)[0]; + return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(yieldType, /*returnType*/ undefined, /*nextType*/ undefined)); + } + if (isReferenceToType(type, getGlobalIteratorReturnResultType(/*reportErrors*/ false))) { + const returnType = getTypeArguments(type as GenericType)[0]; + return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(/*yieldType*/ undefined, returnType, /*nextType*/ undefined)); } - function checkClassDeclaration(node: ClassDeclaration) { - if (some(node.decorators) && some(node.members, p => hasStaticModifier(p) && isPrivateIdentifierClassElementDeclaration(p))) { - grammarErrorOnNode(node.decorators[0], Diagnostics.Class_decorators_can_t_be_used_with_static_private_identifier_Consider_removing_the_experimental_decorator); - } - if (!node.name && !hasSyntacticModifier(node, ModifierFlags.Default)) { - grammarErrorOnFirstToken(node, Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name); - } - checkClassLikeDeclaration(node); - forEach(node.members, checkSourceElement); + // Choose any constituents that can produce the requested iteration type. + const yieldIteratorResult = filterType(type, isYieldIteratorResult); + const yieldType = yieldIteratorResult !== neverType ? getTypeOfPropertyOfType(yieldIteratorResult, "value" as __String) : undefined; - registerForUnusedIdentifiersCheck(node); + const returnIteratorResult = filterType(type, isReturnIteratorResult); + const returnType = returnIteratorResult !== neverType ? getTypeOfPropertyOfType(returnIteratorResult, "value" as __String) : undefined; + + if (!yieldType && !returnType) { + return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", noIterationTypes); } - function checkClassLikeDeclaration(node: ClassLikeDeclaration) { - checkGrammarClassLikeDeclaration(node); - checkDecorators(node); - checkCollisionsForDeclarationName(node, node.name); - checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); - checkExportsOnMergedDeclarations(node); - const symbol = getSymbolOfNode(node); - const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; - const typeWithThis = getTypeWithThisArgument(type); - const staticType = getTypeOfSymbol(symbol) as ObjectType; - checkTypeParameterListsIdentical(symbol); - checkFunctionOrConstructorSymbol(symbol); - checkClassForDuplicateDeclarations(node); + // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface + // > ... If the iterator does not have a return value, `value` is `undefined`. In that case, the + // > `value` property may be absent from the conforming object if it does not inherit an explicit + // > `value` property. + return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(yieldType, returnType || voidType, /*nextType*/ undefined)); + } - // Only check for reserved static identifiers on non-ambient context. - const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); - if (!nodeInAmbientContext) { - checkClassForStaticPropertyNameConflicts(node); - } + /** + * Gets the *yield*, *return*, and *next* types of a the `next()`, `return()`, or + * `throw()` method of an `Iterator`-like or `AsyncIterator`-like type. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, we return `undefined`. + */ + function getIterationTypesOfMethod(type: Type, resolver: IterationTypesResolver, methodName: "next" | "return" | "throw", errorNode: Node | undefined): IterationTypes | undefined { + const method = getPropertyOfType(type, methodName as __String); + + // Ignore 'return' or 'throw' if they are missing. + if (!method && methodName !== "next") { + return undefined; + } - const baseTypeNode = getEffectiveBaseTypeNode(node); - if (baseTypeNode) { - forEach(baseTypeNode.typeArguments, checkSourceElement); - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(baseTypeNode.parent, ExternalEmitHelpers.Extends); - } - // check both @extends and extends if both are specified. - const extendsNode = getClassExtendsHeritageElement(node); - if (extendsNode && extendsNode !== baseTypeNode) { - checkExpression(extendsNode.expression); - } - - const baseTypes = getBaseTypes(type); - if (baseTypes.length && produceDiagnostics) { - const baseType = baseTypes[0]; - const baseConstructorType = getBaseConstructorTypeOfClass(type); - const staticBaseType = getApparentType(baseConstructorType); - checkBaseTypeAccessibility(staticBaseType, baseTypeNode); - checkSourceElement(baseTypeNode.expression); - if (some(baseTypeNode.typeArguments)) { - forEach(baseTypeNode.typeArguments, checkSourceElement); - for (const constructor of getConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode)) { - if (!checkTypeArgumentConstraints(baseTypeNode, constructor.typeParameters!)) { - break; - } - } - } - const baseWithThis = getTypeWithThisArgument(baseType, type.thisType); - if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { - issueMemberSpecificError(node, typeWithThis, baseWithThis, Diagnostics.Class_0_incorrectly_extends_base_class_1); - } - else { - // Report static side error only when instance type is assignable - checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node, - Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1); - } - if (baseConstructorType.flags & TypeFlags.TypeVariable) { - if (!isMixinConstructorType(staticType)) { - error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any); - } - else { - const constructSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); - if (constructSignatures.some(signature => signature.flags & SignatureFlags.Abstract) && !hasSyntacticModifier(node, ModifierFlags.Abstract)) { - error(node.name || node, Diagnostics.A_mixin_class_that_extends_from_a_type_variable_containing_an_abstract_construct_signature_must_also_be_declared_abstract); - } - } - } + const methodType = method && !(methodName === "next" && (method.flags & SymbolFlags.Optional)) + ? methodName === "next" ? getTypeOfSymbol(method) : getTypeWithFacts(getTypeOfSymbol(method), TypeFacts.NEUndefinedOrNull) + : undefined; - if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class) && !(baseConstructorType.flags & TypeFlags.TypeVariable)) { - // When the static base type is a "class-like" constructor function (but not actually a class), we verify - // that all instantiated base constructor signatures return the same type. - const constructors = getInstantiatedConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode); - if (forEach(constructors, sig => !isJSConstructor(sig.declaration) && !isTypeIdenticalTo(getReturnTypeOfSignature(sig), baseType))) { - error(baseTypeNode.expression, Diagnostics.Base_constructors_must_all_have_the_same_return_type); - } - } - checkKindsOfPropertyMemberOverrides(type, baseType); - } + if (isTypeAny(methodType)) { + // `return()` and `throw()` don't provide a *next* type. + return methodName === "next" ? anyIterationTypes : anyIterationTypesExceptNext; + } + + // Both async and non-async iterators *must* have a `next` method. + const methodSignatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : emptyArray; + if (methodSignatures.length === 0) { + if (errorNode) { + const diagnostic = methodName === "next" + ? resolver.mustHaveANextMethodDiagnostic + : resolver.mustBeAMethodDiagnostic; + error(errorNode, diagnostic, methodName); + } + return methodName === "next" ? anyIterationTypes : undefined; + } + + // If the method signature comes exclusively from the global iterator or generator type, + // create iteration types from its type arguments like `getIterationTypesOfIteratorFast` + // does (so as to remove `undefined` from the next and return types). We arrive here when + // a contextual type for a generator was not a direct reference to one of those global types, + // but looking up `methodType` referred to one of them (and nothing else). E.g., in + // `interface SpecialIterator extends Iterator {}`, `SpecialIterator` is not a + // reference to `Iterator`, but its `next` member derives exclusively from `Iterator`. + if (methodType?.symbol && methodSignatures.length === 1) { + const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false); + const globalIteratorType = resolver.getGlobalIteratorType(/*reportErrors*/ false); + const isGeneratorMethod = globalGeneratorType.symbol?.members?.get(methodName as __String) === methodType.symbol; + const isIteratorMethod = !isGeneratorMethod && globalIteratorType.symbol?.members?.get(methodName as __String) === methodType.symbol; + if (isGeneratorMethod || isIteratorMethod) { + const globalType = isGeneratorMethod ? globalGeneratorType : globalIteratorType; + const { mapper } = methodType as AnonymousType; + return createIterationTypes( + getMappedType(globalType.typeParameters![0], mapper!), + getMappedType(globalType.typeParameters![1], mapper!), + methodName === "next" ? getMappedType(globalType.typeParameters![2], mapper!) : undefined); } + } - checkMembersForOverrideModifier(node, type, typeWithThis, staticType); + // Extract the first parameter and return type of each signature. + let methodParameterTypes: Type[] | undefined; + let methodReturnTypes: Type[] | undefined; + for (const signature of methodSignatures) { + if (methodName !== "throw" && some(signature.parameters)) { + methodParameterTypes = append(methodParameterTypes, getTypeAtPosition(signature, 0)); + } + methodReturnTypes = append(methodReturnTypes, getReturnTypeOfSignature(signature)); + } - const implementedTypeNodes = getEffectiveImplementsTypeNodes(node); - if (implementedTypeNodes) { - for (const typeRefNode of implementedTypeNodes) { - if (!isEntityNameExpression(typeRefNode.expression) || isOptionalChain(typeRefNode.expression)) { - error(typeRefNode.expression, Diagnostics.A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments); - } - checkTypeReferenceNode(typeRefNode); - if (produceDiagnostics) { - const t = getReducedType(getTypeFromTypeNode(typeRefNode)); - if (!isErrorType(t)) { - if (isValidBaseType(t)) { - const genericDiag = t.symbol && t.symbol.flags & SymbolFlags.Class ? - Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass : - Diagnostics.Class_0_incorrectly_implements_interface_1; - const baseWithThis = getTypeWithThisArgument(t, type.thisType); - if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { - issueMemberSpecificError(node, typeWithThis, baseWithThis, genericDiag); - } - } - else { - error(typeRefNode, Diagnostics.A_class_can_only_implement_an_object_type_or_intersection_of_object_types_with_statically_known_members); - } - } - } - } + // Resolve the *next* or *return* type from the first parameter of a `next()` or + // `return()` method, respectively. + let returnTypes: Type[] | undefined; + let nextType: Type | undefined; + if (methodName !== "throw") { + const methodParameterType = methodParameterTypes ? getUnionType(methodParameterTypes) : unknownType; + if (methodName === "next") { + // The value of `next(value)` is *not* awaited by async generators + nextType = methodParameterType; + } + else if (methodName === "return") { + // The value of `return(value)` *is* awaited by async generators + const resolvedMethodParameterType = resolver.resolveIterationType(methodParameterType, errorNode) || anyType; + returnTypes = append(returnTypes, resolvedMethodParameterType); + } + } + + // Resolve the *yield* and *return* types from the return type of the method (i.e. `IteratorResult`) + let yieldType: Type; + const methodReturnType = methodReturnTypes ? getIntersectionType(methodReturnTypes) : neverType; + const resolvedMethodReturnType = resolver.resolveIterationType(methodReturnType, errorNode) || anyType; + const iterationTypes = getIterationTypesOfIteratorResult(resolvedMethodReturnType); + if (iterationTypes === noIterationTypes) { + if (errorNode) { + error(errorNode, resolver.mustHaveAValueDiagnostic, methodName); } + yieldType = anyType; + returnTypes = append(returnTypes, anyType); + } + else { + yieldType = iterationTypes.yieldType; + returnTypes = append(returnTypes, iterationTypes.returnType); + } + + return createIterationTypes(yieldType, getUnionType(returnTypes), nextType); + } + + /** + * Gets the *yield*, *return*, and *next* types of an `Iterator`-like or `AsyncIterator`-like + * type from its members. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, `noIterationTypes` is returned. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterator` instead. + */ + function getIterationTypesOfIteratorSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { + const iterationTypes = combineIterationTypes([ + getIterationTypesOfMethod(type, resolver, "next", errorNode), + getIterationTypesOfMethod(type, resolver, "return", errorNode), + getIterationTypesOfMethod(type, resolver, "throw", errorNode), + ]); + return setCachedIterationTypes(type, resolver.iteratorCacheKey, iterationTypes); + } - if (produceDiagnostics) { - checkIndexConstraints(type, symbol); - checkIndexConstraints(staticType, symbol, /*isStaticIndex*/ true); - checkTypeForDuplicateIndexSignatures(node); - checkPropertyInitialization(node); - } + /** + * Gets the requested "iteration type" from a type that is either `Iterable`-like, `Iterator`-like, + * `IterableIterator`-like, or `Generator`-like (for a non-async generator); or `AsyncIterable`-like, + * `AsyncIterator`-like, `AsyncIterableIterator`-like, or `AsyncGenerator`-like (for an async generator). + */ + function getIterationTypeOfGeneratorFunctionReturnType(kind: IterationTypeKind, returnType: Type, isAsyncGenerator: boolean): Type | undefined { + if (isTypeAny(returnType)) { + return undefined; } - function checkMembersForOverrideModifier(node: ClassLikeDeclaration, type: InterfaceType, typeWithThis: Type, staticType: ObjectType) { - const baseTypeNode = getEffectiveBaseTypeNode(node); - const baseTypes = baseTypeNode && getBaseTypes(type); - const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; - const baseStaticType = getBaseConstructorTypeOfClass(type); + const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsyncGenerator); + return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(kind)]; + } - for (const member of node.members) { - if (hasAmbientModifier(member)) { - continue; - } + function getIterationTypesOfGeneratorFunctionReturnType(type: Type, isAsyncGenerator: boolean) { + if (isTypeAny(type)) { + return anyIterationTypes; + } - if (isConstructorDeclaration(member)) { - forEach(member.parameters, param => { - if (isParameterPropertyDeclaration(param, member)) { - checkExistingMemberForOverrideModifier( - node, - staticType, - baseStaticType, - baseWithThis, - type, - typeWithThis, - param, - /* memberIsParameterProperty */ true - ); - } - }); - } - checkExistingMemberForOverrideModifier( - node, - staticType, - baseStaticType, - baseWithThis, - type, - typeWithThis, - member, - /* memberIsParameterProperty */ false, - ); - } + const use = isAsyncGenerator ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType; + const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; + return getIterationTypesOfIterable(type, use, /*errorNode*/ undefined) || + getIterationTypesOfIterator(type, resolver, /*errorNode*/ undefined); + } + + function checkBreakOrContinueStatement(node: BreakOrContinueStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) checkGrammarBreakOrContinueStatement(node); + + // TODO: Check that target label is valid + } + + function unwrapReturnType(returnType: Type, functionFlags: FunctionFlags) { + const isGenerator = !!(functionFlags & FunctionFlags.Generator); + const isAsync = !!(functionFlags & FunctionFlags.Async); + return isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync) || errorType : + isAsync ? getAwaitedTypeNoAlias(returnType) || errorType : + returnType; + } + + function isUnwrappedReturnTypeVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean { + const unwrappedReturnType = unwrapReturnType(returnType, getFunctionFlags(func)); + return !!unwrappedReturnType && maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.AnyOrUnknown); + } + + function checkReturnStatement(node: ReturnStatement) { + // Grammar checking + if (checkGrammarStatementInAmbientContext(node)) { + return; } - /** - * @param member Existing member node to be checked. - * Note: `member` cannot be a synthetic node. - */ - function checkExistingMemberForOverrideModifier( - node: ClassLikeDeclaration, - staticType: ObjectType, - baseStaticType: Type, - baseWithThis: Type | undefined, - type: InterfaceType, - typeWithThis: Type, - member: ClassElement | ParameterPropertyDeclaration, - memberIsParameterProperty: boolean, - reportErrors = true, - ): MemberOverrideStatus { - const declaredProp = member.name - && getSymbolAtLocation(member.name) - || getSymbolAtLocation(member); - if (!declaredProp) { - return MemberOverrideStatus.Ok; - } - - return checkMemberForOverrideModifier( - node, - staticType, - baseStaticType, - baseWithThis, - type, - typeWithThis, - hasOverrideModifier(member), - hasAbstractModifier(member), - isStatic(member), - memberIsParameterProperty, - symbolName(declaredProp), - reportErrors ? member : undefined, - ); + const container = getContainingFunctionOrClassStaticBlock(node); + if(container && isClassStaticBlockDeclaration(container)) { + grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_cannot_be_used_inside_a_class_static_block); + return; } - /** - * Checks a class member declaration for either a missing or an invalid `override` modifier. - * Note: this function can be used for speculative checking, - * i.e. checking a member that does not yet exist in the program. - * An example of that would be to call this function in a completions scenario, - * when offering a method declaration as completion. - * @param errorNode The node where we should report an error, or undefined if we should not report errors. - */ - function checkMemberForOverrideModifier( - node: ClassLikeDeclaration, - staticType: ObjectType, - baseStaticType: Type, - baseWithThis: Type | undefined, - type: InterfaceType, - typeWithThis: Type, - memberHasOverrideModifier: boolean, - memberHasAbstractModifier: boolean, - memberIsStatic: boolean, - memberIsParameterProperty: boolean, - memberName: string, - errorNode?: Node, - ): MemberOverrideStatus { - const isJs = isInJSFile(node); - const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); - if (baseWithThis && (memberHasOverrideModifier || compilerOptions.noImplicitOverride)) { - const memberEscapedName = escapeLeadingUnderscores(memberName); - const thisType = memberIsStatic ? staticType : typeWithThis; - const baseType = memberIsStatic ? baseStaticType : baseWithThis; - const prop = getPropertyOfType(thisType, memberEscapedName); - const baseProp = getPropertyOfType(baseType, memberEscapedName); - - const baseClassName = typeToString(baseWithThis); - if (prop && !baseProp && memberHasOverrideModifier) { - if (errorNode) { - const suggestion = getSuggestedSymbolForNonexistentClassMember(memberName, baseType); // Again, using symbol name: note that's different from `symbol.escapedName` - suggestion ? - error( - errorNode, - isJs ? - Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1 : - Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1, - baseClassName, - symbolToString(suggestion)) : - error( - errorNode, - isJs ? - Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0 : - Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0, - baseClassName); - } - return MemberOverrideStatus.HasInvalidOverride; - } - else if (prop && baseProp?.declarations && compilerOptions.noImplicitOverride && !nodeInAmbientContext) { - const baseHasAbstract = some(baseProp.declarations, hasAbstractModifier); - if (memberHasOverrideModifier) { - return MemberOverrideStatus.Ok; - } + if (!container) { + grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body); + return; + } - if (!baseHasAbstract) { - if (errorNode) { - const diag = memberIsParameterProperty ? - isJs ? - Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 : - Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0 : - isJs ? - Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 : - Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0; - error(errorNode, diag, baseClassName); - } - return MemberOverrideStatus.NeedsOverride; - } - else if (memberHasAbstractModifier && baseHasAbstract) { - if (errorNode) { - error(errorNode, Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0, baseClassName); - } - return MemberOverrideStatus.NeedsOverride; - } + const signature = getSignatureFromDeclaration(container); + const returnType = getReturnTypeOfSignature(signature); + const functionFlags = getFunctionFlags(container); + if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { + const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; + if (container.kind === SyntaxKind.SetAccessor) { + if (node.expression) { + error(node, Diagnostics.Setters_cannot_return_a_value); } } - else if (memberHasOverrideModifier) { - if (errorNode) { - const className = typeToString(type); - error( - errorNode, - isJs ? - Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class : - Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class, - className); + else if (container.kind === SyntaxKind.Constructor) { + if (node.expression && !checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression)) { + error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); } - return MemberOverrideStatus.HasInvalidOverride; } - - return MemberOverrideStatus.Ok; - } - - function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: Type, baseWithThis: Type, broadDiag: DiagnosticMessage) { - // iterate over all implemented properties and issue errors on each one which isn't compatible, rather than the class as a whole, if possible - let issuedMemberError = false; - for (const member of node.members) { - if (isStatic(member)) { - continue; - } - const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member); - if (declaredProp) { - const prop = getPropertyOfType(typeWithThis, declaredProp.escapedName); - const baseProp = getPropertyOfType(baseWithThis, declaredProp.escapedName); - if (prop && baseProp) { - const rootChain = () => chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2, - symbolToString(declaredProp), - typeToString(typeWithThis), - typeToString(baseWithThis) - ); - if (!checkTypeAssignableTo(getTypeOfSymbol(prop), getTypeOfSymbol(baseProp), member.name || member, /*message*/ undefined, rootChain)) { - issuedMemberError = true; - } - } + else if (getReturnTypeFromAnnotation(container)) { + const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; + const unwrappedExprType = functionFlags & FunctionFlags.Async + ? checkAwaitedType(exprType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) + : exprType; + if (unwrappedReturnType) { + // If the function has a return type, but promisedType is + // undefined, an error will be reported in checkAsyncFunctionReturnType + // so we don't need to report one here. + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); } } - if (!issuedMemberError) { - // check again with diagnostics to generate a less-specific error - checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag); - } } + else if (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(container, returnType)) { + // The function has a return type, but the return statement doesn't have an expression. + error(node, Diagnostics.Not_all_code_paths_return_a_value); + } + } - function checkBaseTypeAccessibility(type: Type, node: ExpressionWithTypeArguments) { - const signatures = getSignaturesOfType(type, SignatureKind.Construct); - if (signatures.length) { - const declaration = signatures[0].declaration; - if (declaration && hasEffectiveModifier(declaration, ModifierFlags.Private)) { - const typeClassDeclaration = getClassLikeDeclarationOfSymbol(type.symbol)!; - if (!isNodeWithinClass(node, typeClassDeclaration)) { - error(node, Diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, getFullyQualifiedName(type.symbol)); - } - } + function checkWithStatement(node: WithStatement) { + // Grammar checking for withStatement + if (!checkGrammarStatementInAmbientContext(node)) { + if (node.flags & NodeFlags.AwaitContext) { + grammarErrorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_an_async_function_block); } } - /** - * Checks a member declaration node to see if has a missing or invalid `override` modifier. - * @param node Class-like node where the member is declared. - * @param member Member declaration node. - * Note: `member` can be a synthetic node without a parent. - */ - function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus { - if (!member.name) { - return MemberOverrideStatus.Ok; - } + checkExpression(node.expression); - const symbol = getSymbolOfNode(node); - const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; - const typeWithThis = getTypeWithThisArgument(type); - const staticType = getTypeOfSymbol(symbol) as ObjectType; + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const start = getSpanOfTokenAtPosition(sourceFile, node.pos).start; + const end = node.statement.pos; + grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.The_with_statement_is_not_supported_All_symbols_in_a_with_block_will_have_type_any); + } + } - const baseTypeNode = getEffectiveBaseTypeNode(node); - const baseTypes = baseTypeNode && getBaseTypes(type); - const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; - const baseStaticType = getBaseConstructorTypeOfClass(type); + function checkSwitchStatement(node: SwitchStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); - const memberHasOverrideModifier = member.parent - ? hasOverrideModifier(member) - : hasSyntacticModifier(member, ModifierFlags.Override); + let firstDefaultClause: CaseOrDefaultClause; + let hasDuplicateDefaultClause = false; - const memberName = unescapeLeadingUnderscores(getTextOfPropertyName(member.name)); + const expressionType = checkExpression(node.expression); + const expressionIsLiteral = isLiteralType(expressionType); + forEach(node.caseBlock.clauses, clause => { + // Grammar check for duplicate default clauses, skip if we already report duplicate default clause + if (clause.kind === SyntaxKind.DefaultClause && !hasDuplicateDefaultClause) { + if (firstDefaultClause === undefined) { + firstDefaultClause = clause; + } + else { + grammarErrorOnNode(clause, Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement); + hasDuplicateDefaultClause = true; + } + } - return checkMemberForOverrideModifier( - node, - staticType, - baseStaticType, - baseWithThis, - type, - typeWithThis, - memberHasOverrideModifier, - hasAbstractModifier(member), - isStatic(member), - /* memberIsParameterProperty */ false, - memberName, - ); + if (produceDiagnostics && clause.kind === SyntaxKind.CaseClause) { + // TypeScript 1.0 spec (April 2014): 5.9 + // In a 'switch' statement, each 'case' expression must be of a type that is comparable + // to or from the type of the 'switch' expression. + let caseType = checkExpression(clause.expression); + const caseIsLiteral = isLiteralType(caseType); + let comparedExpressionType = expressionType; + if (!caseIsLiteral || !expressionIsLiteral) { + caseType = caseIsLiteral ? getBaseTypeOfLiteralType(caseType) : caseType; + comparedExpressionType = getBaseTypeOfLiteralType(expressionType); + } + if (!isTypeEqualityComparableTo(comparedExpressionType, caseType)) { + // expressionType is not comparable to caseType, try the reversed check and report errors if it fails + checkTypeComparableTo(caseType, comparedExpressionType, clause.expression, /*headMessage*/ undefined); + } + } + forEach(clause.statements, checkSourceElement); + if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) { + error(clause, Diagnostics.Fallthrough_case_in_switch); + } + }); + if (node.caseBlock.locals) { + registerForUnusedIdentifiersCheck(node.caseBlock); } + } - function getTargetSymbol(s: Symbol) { - // if symbol is instantiated its flags are not copied from the 'target' - // so we'll need to get back original 'target' symbol to work with correct set of flags - return getCheckFlags(s) & CheckFlags.Instantiated ? (s as TransientSymbol).target! : s; + function checkLabeledStatement(node: LabeledStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + findAncestor(node.parent, current => { + if (isFunctionLike(current)) { + return "quit"; + } + if (current.kind === SyntaxKind.LabeledStatement && (current as LabeledStatement).label.escapedText === node.label.escapedText) { + grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNode(node.label)); + return true; + } + return false; + }); } - function getClassOrInterfaceDeclarationsOfSymbol(symbol: Symbol) { - return filter(symbol.declarations, (d: Declaration): d is ClassDeclaration | InterfaceDeclaration => - d.kind === SyntaxKind.ClassDeclaration || d.kind === SyntaxKind.InterfaceDeclaration); + // ensure that label is unique + checkSourceElement(node.statement); + } + + function checkThrowStatement(node: ThrowStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + if (isIdentifier(node.expression) && !node.expression.escapedText) { + grammarErrorAfterFirstToken(node, Diagnostics.Line_break_not_permitted_here); + } } - function checkKindsOfPropertyMemberOverrides(type: InterfaceType, baseType: BaseType): void { - // TypeScript 1.0 spec (April 2014): 8.2.3 - // A derived class inherits all members from its base class it doesn't override. - // Inheritance means that a derived class implicitly contains all non - overridden members of the base class. - // Both public and private property members are inherited, but only public property members can be overridden. - // A property member in a derived class is said to override a property member in a base class - // when the derived class property member has the same name and kind(instance or static) - // as the base class property member. - // The type of an overriding property member must be assignable(section 3.8.4) - // to the type of the overridden property member, or otherwise a compile - time error occurs. - // Base class instance member functions can be overridden by derived class instance member functions, - // but not by other kinds of members. - // Base class instance member variables and accessors can be overridden by - // derived class instance member variables and accessors, but not by other kinds of members. + if (node.expression) { + checkExpression(node.expression); + } + } - // NOTE: assignability is checked in checkClassDeclaration - const baseProperties = getPropertiesOfType(baseType); - basePropertyCheck: for (const baseProperty of baseProperties) { - const base = getTargetSymbol(baseProperty); + function checkTryStatement(node: TryStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); - if (base.flags & SymbolFlags.Prototype) { - continue; + checkBlock(node.tryBlock); + const catchClause = node.catchClause; + if (catchClause) { + // Grammar checking + if (catchClause.variableDeclaration) { + const declaration = catchClause.variableDeclaration; + const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration)); + if (typeNode) { + const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false); + if (type && !(type.flags & TypeFlags.AnyOrUnknown)) { + grammarErrorOnFirstToken(typeNode, Diagnostics.Catch_clause_variable_type_annotation_must_be_any_or_unknown_if_specified); + } } - const baseSymbol = getPropertyOfObjectType(type, base.escapedName); - if (!baseSymbol) { - continue; + else if (declaration.initializer) { + grammarErrorOnFirstToken(declaration.initializer, Diagnostics.Catch_clause_variable_cannot_have_an_initializer); } - const derived = getTargetSymbol(baseSymbol); - const baseDeclarationFlags = getDeclarationModifierFlagsFromSymbol(base); - - Debug.assert(!!derived, "derived should point to something, even if it is the base class' declaration."); - - // In order to resolve whether the inherited method was overridden in the base class or not, - // we compare the Symbols obtained. Since getTargetSymbol returns the symbol on the *uninstantiated* - // type declaration, derived and base resolve to the same symbol even in the case of generic classes. - if (derived === base) { - // derived class inherits base without override/redeclaration - const derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol)!; - - // It is an error to inherit an abstract member without implementing it or being declared abstract. - // If there is no declaration for the derived class (as in the case of class expressions), - // then the class cannot be declared abstract. - if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasSyntacticModifier(derivedClassDecl, ModifierFlags.Abstract))) { - // Searches other base types for a declaration that would satisfy the inherited abstract member. - // (The class may have more than one base type via declaration merging with an interface with the - // same name.) - for (const otherBaseType of getBaseTypes(type)) { - if (otherBaseType === baseType) continue; - const baseSymbol = getPropertyOfObjectType(otherBaseType, base.escapedName); - const derivedElsewhere = baseSymbol && getTargetSymbol(baseSymbol); - if (derivedElsewhere && derivedElsewhere !== base) { - continue basePropertyCheck; + else { + const blockLocals = catchClause.block.locals; + if (blockLocals) { + forEachKey(catchClause.locals!, caughtName => { + const blockLocal = blockLocals.get(caughtName); + if (blockLocal?.valueDeclaration && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) { + grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, caughtName); } - } - - if (derivedClassDecl.kind === SyntaxKind.ClassExpression) { - error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, - symbolToString(baseProperty), typeToString(baseType)); - } - else { - error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, - typeToString(type), symbolToString(baseProperty), typeToString(baseType)); - } + }); } } - else { - // derived overrides base. - const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived); - if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) { - // either base or derived property is private - not override, skip it - continue; - } - - let errorMessage: DiagnosticMessage; - const basePropertyFlags = base.flags & SymbolFlags.PropertyOrAccessor; - const derivedPropertyFlags = derived.flags & SymbolFlags.PropertyOrAccessor; - if (basePropertyFlags && derivedPropertyFlags) { - // property/accessor is overridden with property/accessor - if (baseDeclarationFlags & ModifierFlags.Abstract && !(base.valueDeclaration && isPropertyDeclaration(base.valueDeclaration) && base.valueDeclaration.initializer) - || base.valueDeclaration && base.valueDeclaration.parent.kind === SyntaxKind.InterfaceDeclaration - || derived.valueDeclaration && isBinaryExpression(derived.valueDeclaration)) { - // when the base property is abstract or from an interface, base/derived flags don't need to match - // same when the derived property is from an assignment - continue; - } + } - const overriddenInstanceProperty = basePropertyFlags !== SymbolFlags.Property && derivedPropertyFlags === SymbolFlags.Property; - const overriddenInstanceAccessor = basePropertyFlags === SymbolFlags.Property && derivedPropertyFlags !== SymbolFlags.Property; - if (overriddenInstanceProperty || overriddenInstanceAccessor) { - const errorMessage = overriddenInstanceProperty ? - Diagnostics._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property : - Diagnostics._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor; - error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType), typeToString(type)); - } - else if (useDefineForClassFields) { - const uninitialized = derived.declarations?.find(d => d.kind === SyntaxKind.PropertyDeclaration && !(d as PropertyDeclaration).initializer); - if (uninitialized - && !(derived.flags & SymbolFlags.Transient) - && !(baseDeclarationFlags & ModifierFlags.Abstract) - && !(derivedDeclarationFlags & ModifierFlags.Abstract) - && !derived.declarations?.some(d => !!(d.flags & NodeFlags.Ambient))) { - const constructor = findConstructorDeclaration(getClassLikeDeclarationOfSymbol(type.symbol)!); - const propName = (uninitialized as PropertyDeclaration).name; - if ((uninitialized as PropertyDeclaration).exclamationToken - || !constructor - || !isIdentifier(propName) - || !strictNullChecks - || !isPropertyInitializedInConstructor(propName, type, constructor)) { - const errorMessage = Diagnostics.Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_add_a_declare_modifier_or_remove_the_redundant_declaration; - error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType)); - } - } - } + checkBlock(catchClause.block); + } - // correct case - continue; - } - else if (isPrototypeProperty(base)) { - if (isPrototypeProperty(derived) || derived.flags & SymbolFlags.Property) { - // method is overridden with method or property -- correct case - continue; - } - else { - Debug.assert(!!(derived.flags & SymbolFlags.Accessor)); - errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor; - } - } - else if (base.flags & SymbolFlags.Accessor) { - errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function; - } - else { - errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function; - } + if (node.finallyBlock) { + checkBlock(node.finallyBlock); + } + } - error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type)); + function checkIndexConstraints(type: Type, symbol: Symbol, isStaticIndex?: boolean) { + const indexInfos = getIndexInfosOfType(type); + if (indexInfos.length === 0) { + return; + } + for (const prop of getPropertiesOfObjectType(type)) { + if (!(isStaticIndex && prop.flags & SymbolFlags.Prototype)) { + checkIndexConstraintForProperty(type, prop, getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique, /*includeNonPublic*/ true), getNonMissingTypeOfSymbol(prop)); + } + } + const typeDeclaration = symbol.valueDeclaration; + if (typeDeclaration && isClassLike(typeDeclaration)) { + for (const member of typeDeclaration.members) { + // Only process instance properties with computed names here. Static properties cannot be in conflict with indexers, + // and properties with literal names were already checked. + if (!isStatic(member) && !hasBindableName(member)) { + const symbol = getSymbolOfNode(member); + checkIndexConstraintForProperty(type, symbol, getTypeOfExpression((member as DynamicNamedDeclaration).name.expression), getNonMissingTypeOfSymbol(symbol)); } } } - - function getNonInterhitedProperties(type: InterfaceType, baseTypes: BaseType[], properties: Symbol[]) { - if (!length(baseTypes)) { - return properties; + if (indexInfos.length > 1) { + for (const info of indexInfos) { + checkIndexConstraintForIndexSignature(type, info); } - const seen = new Map<__String, Symbol>(); - forEach(properties, p => { - seen.set(p.escapedName, p); - }); + } + } - for (const base of baseTypes) { - const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); - for (const prop of properties) { - const existing = seen.get(prop.escapedName); - if (existing && !isPropertyIdenticalTo(existing, prop)) { - seen.delete(prop.escapedName); - } - } + function checkIndexConstraintForProperty(type: Type, prop: Symbol, propNameType: Type, propType: Type) { + const declaration = prop.valueDeclaration; + const name = getNameOfDeclaration(declaration); + if (name && isPrivateIdentifier(name)) { + return; + } + const indexInfos = getApplicableIndexInfos(type, propNameType); + const interfaceDeclaration = getObjectFlags(type) & ObjectFlags.Interface ? getDeclarationOfKind(type.symbol, SyntaxKind.InterfaceDeclaration) : undefined; + const localPropDeclaration = declaration && declaration.kind === SyntaxKind.BinaryExpression || + name && name.kind === SyntaxKind.ComputedPropertyName || getParentOfSymbol(prop) === type.symbol ? declaration : undefined; + for (const info of indexInfos) { + const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfNode(info.declaration)) === type.symbol ? info.declaration : undefined; + // We check only when (a) the property is declared in the containing type, or (b) the applicable index signature is declared + // in the containing type, or (c) the containing type is an interface and no base interface contains both the property and + // the index signature (i.e. property and index signature are declared in separate inherited interfaces). + const errorNode = localPropDeclaration || localIndexDeclaration || + (interfaceDeclaration && !some(getBaseTypes(type as InterfaceType), base => !!getPropertyOfObjectType(base, prop.escapedName) && !!getIndexTypeOfType(base, info.keyType)) ? interfaceDeclaration : undefined); + if (errorNode && !isTypeAssignableTo(propType, info.type)) { + error(errorNode, Diagnostics.Property_0_of_type_1_is_not_assignable_to_2_index_type_3, + symbolToString(prop), typeToString(propType), typeToString(info.keyType), typeToString(info.type)); } - - return arrayFrom(seen.values()); } + } - function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean { - const baseTypes = getBaseTypes(type); - if (baseTypes.length < 2) { - return true; + function checkIndexConstraintForIndexSignature(type: Type, checkInfo: IndexInfo) { + const declaration = checkInfo.declaration; + const indexInfos = getApplicableIndexInfos(type, checkInfo.keyType); + const interfaceDeclaration = getObjectFlags(type) & ObjectFlags.Interface ? getDeclarationOfKind(type.symbol, SyntaxKind.InterfaceDeclaration) : undefined; + const localCheckDeclaration = declaration && getParentOfSymbol(getSymbolOfNode(declaration)) === type.symbol ? declaration : undefined; + for (const info of indexInfos) { + if (info === checkInfo) continue; + const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfNode(info.declaration)) === type.symbol ? info.declaration : undefined; + // We check only when (a) the check index signature is declared in the containing type, or (b) the applicable index + // signature is declared in the containing type, or (c) the containing type is an interface and no base interface contains + // both index signatures (i.e. the index signatures are declared in separate inherited interfaces). + const errorNode = localCheckDeclaration || localIndexDeclaration || + (interfaceDeclaration && !some(getBaseTypes(type as InterfaceType), base => !!getIndexInfoOfType(base, checkInfo.keyType) && !!getIndexTypeOfType(base, info.keyType)) ? interfaceDeclaration : undefined); + if (errorNode && !isTypeAssignableTo(checkInfo.type, info.type)) { + error(errorNode, Diagnostics._0_index_type_1_is_not_assignable_to_2_index_type_3, + typeToString(checkInfo.keyType), typeToString(checkInfo.type), typeToString(info.keyType), typeToString(info.type)); } + } + } - interface InheritanceInfoMap { prop: Symbol; containingType: Type; } - const seen = new Map<__String, InheritanceInfoMap>(); - forEach(resolveDeclaredMembers(type).declaredProperties, p => { - seen.set(p.escapedName, { prop: p, containingType: type }); - }); - let ok = true; + function checkTypeNameIsReserved(name: Identifier, message: DiagnosticMessage): void { + // TS 1.0 spec (April 2014): 3.6.1 + // The predefined type keywords are reserved and cannot be used as names of user defined types. + switch (name.escapedText) { + case "any": + case "unknown": + case "never": + case "number": + case "bigint": + case "boolean": + case "string": + case "symbol": + case "void": + case "object": + error(name, message, name.escapedText as string); + } + } - for (const base of baseTypes) { - const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); - for (const prop of properties) { - const existing = seen.get(prop.escapedName); - if (!existing) { - seen.set(prop.escapedName, { prop, containingType: base }); - } - else { - const isInheritedProperty = existing.containingType !== type; - if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) { - ok = false; + /** + * The name cannot be used as 'Object' of user defined types with special target. + */ + function checkClassNameCollisionWithObject(name: Identifier): void { + if (languageVersion >= ScriptTarget.ES5 && name.escapedText === "Object" + && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(name).impliedNodeFormat === ModuleKind.CommonJS)) { + error(name, Diagnostics.Class_name_cannot_be_Object_when_targeting_ES5_with_module_0, ModuleKind[moduleKind]); // https://github.com/Microsoft/TypeScript/issues/17494 + } + } - const typeName1 = typeToString(existing.containingType); - const typeName2 = typeToString(base); + /** + * Check each type parameter and check that type parameters have no duplicate type parameter declarations + */ + function checkTypeParameters(typeParameterDeclarations: readonly TypeParameterDeclaration[] | undefined) { + if (typeParameterDeclarations) { + let seenDefault = false; + for (let i = 0; i < typeParameterDeclarations.length; i++) { + const node = typeParameterDeclarations[i]; + checkTypeParameter(node); - let errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Named_property_0_of_types_1_and_2_are_not_identical, symbolToString(prop), typeName1, typeName2); - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Interface_0_cannot_simultaneously_extend_types_1_and_2, typeToString(type), typeName1, typeName2); - diagnostics.add(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo)); + if (produceDiagnostics) { + if (node.default) { + seenDefault = true; + checkTypeParametersNotReferenced(node.default, typeParameterDeclarations, i); + } + else if (seenDefault) { + error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters); + } + for (let j = 0; j < i; j++) { + if (typeParameterDeclarations[j].symbol === node.symbol) { + error(node.name, Diagnostics.Duplicate_identifier_0, declarationNameToString(node.name)); } } } } - - return ok; } + } - function checkPropertyInitialization(node: ClassLikeDeclaration) { - if (!strictNullChecks || !strictPropertyInitialization || node.flags & NodeFlags.Ambient) { - return; - } - const constructor = findConstructorDeclaration(node); - for (const member of node.members) { - if (getEffectiveModifierFlags(member) & ModifierFlags.Ambient) { - continue; - } - if (!isStatic(member) && isPropertyWithoutInitializer(member)) { - const propName = (member as PropertyDeclaration).name; - if (isIdentifier(propName) || isPrivateIdentifier(propName)) { - const type = getTypeOfSymbol(getSymbolOfNode(member)); - if (!(type.flags & TypeFlags.AnyOrUnknown || getFalsyFlags(type) & TypeFlags.Undefined)) { - if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) { - error(member.name, Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor, declarationNameToString(propName)); - } + /** Check that type parameter defaults only reference previously declared type parameters */ + function checkTypeParametersNotReferenced(root: TypeNode, typeParameters: readonly TypeParameterDeclaration[], index: number) { + visit(root); + function visit(node: Node) { + if (node.kind === SyntaxKind.TypeReference) { + const type = getTypeFromTypeReference(node as TypeReferenceNode); + if (type.flags & TypeFlags.TypeParameter) { + for (let i = index; i < typeParameters.length; i++) { + if (type.symbol === getSymbolOfNode(typeParameters[i])) { + error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters); } } } } + forEachChild(node, visit); } + } - function isPropertyWithoutInitializer(node: Node) { - return node.kind === SyntaxKind.PropertyDeclaration && - !hasAbstractModifier(node) && - !(node as PropertyDeclaration).exclamationToken && - !(node as PropertyDeclaration).initializer; + /** Check that type parameter lists are identical across multiple declarations */ + function checkTypeParameterListsIdentical(symbol: Symbol) { + if (symbol.declarations && symbol.declarations.length === 1) { + return; } - function isPropertyInitializedInStaticBlocks(propName: Identifier | PrivateIdentifier, propType: Type, staticBlocks: readonly ClassStaticBlockDeclaration[], startPos: number, endPos: number) { - for (const staticBlock of staticBlocks) { - // static block must be within the provided range as they are evaluated in document order (unlike constructors) - if (staticBlock.pos >= startPos && staticBlock.pos <= endPos) { - const reference = factory.createPropertyAccessExpression(factory.createThis(), propName); - setParent(reference.expression, reference); - setParent(reference, staticBlock); - reference.flowNode = staticBlock.returnFlowNode; - const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); - if (!(getFalsyFlags(flowType) & TypeFlags.Undefined)) { - return true; - } - } + const links = getSymbolLinks(symbol); + if (!links.typeParametersChecked) { + links.typeParametersChecked = true; + const declarations = getClassOrInterfaceDeclarationsOfSymbol(symbol); + if (!declarations || declarations.length <= 1) { + return; } - return false; - } - function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier, propType: Type, constructor: ConstructorDeclaration) { - const reference = factory.createPropertyAccessExpression(factory.createThis(), propName); - setParent(reference.expression, reference); - setParent(reference, constructor); - reference.flowNode = constructor.returnFlowNode; - const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); - return !(getFalsyFlags(flowType) & TypeFlags.Undefined); + const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + if (!areTypeParametersIdentical(declarations, type.localTypeParameters!)) { + // Report an error on every conflicting declaration. + const name = symbolToString(symbol); + for (const declaration of declarations) { + error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_type_parameters, name); + } + } } + } - function checkInterfaceDeclaration(node: InterfaceDeclaration) { - // Grammar checking - if (!checkGrammarDecoratorsAndModifiers(node)) checkGrammarInterfaceDeclaration(node); - - checkTypeParameters(node.typeParameters); - if (produceDiagnostics) { - checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); - - checkExportsOnMergedDeclarations(node); - const symbol = getSymbolOfNode(node); - checkTypeParameterListsIdentical(symbol); + function areTypeParametersIdentical(declarations: readonly (ClassDeclaration | InterfaceDeclaration)[], targetParameters: TypeParameter[]) { + const maxTypeArgumentCount = length(targetParameters); + const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters); - // Only check this symbol once - const firstInterfaceDecl = getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration); - if (node === firstInterfaceDecl) { - const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; - const typeWithThis = getTypeWithThisArgument(type); - // run subsequent checks only if first set succeeded - if (checkInheritedPropertiesAreIdentical(type, node.name)) { - for (const baseType of getBaseTypes(type)) { - checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(baseType, type.thisType), node.name, Diagnostics.Interface_0_incorrectly_extends_interface_1); - } - checkIndexConstraints(type, symbol); - } - } - checkObjectTypeForDuplicateDeclarations(node); + for (const declaration of declarations) { + // If this declaration has too few or too many type parameters, we report an error + const sourceParameters = getEffectiveTypeParameterDeclarations(declaration); + const numTypeParameters = sourceParameters.length; + if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) { + return false; } - forEach(getInterfaceBaseTypeNodes(node), heritageElement => { - if (!isEntityNameExpression(heritageElement.expression) || isOptionalChain(heritageElement.expression)) { - error(heritageElement.expression, Diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments); - } - checkTypeReferenceNode(heritageElement); - }); - forEach(node.members, checkSourceElement); + for (let i = 0; i < numTypeParameters; i++) { + const source = sourceParameters[i]; + const target = targetParameters[i]; - if (produceDiagnostics) { - checkTypeForDuplicateIndexSignatures(node); - registerForUnusedIdentifiersCheck(node); - } - } + // If the type parameter node does not have the same as the resolved type + // parameter at this position, we report an error. + if (source.name.escapedText !== target.symbol.escapedName) { + return false; + } - function checkTypeAliasDeclaration(node: TypeAliasDeclaration) { - // Grammar checking - checkGrammarDecoratorsAndModifiers(node); - checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); - checkExportsOnMergedDeclarations(node); - checkTypeParameters(node.typeParameters); - if (node.type.kind === SyntaxKind.IntrinsicKeyword) { - if (!intrinsicTypeKinds.has(node.name.escapedText as string) || length(node.typeParameters) !== 1) { - error(node.type, Diagnostics.The_intrinsic_keyword_can_only_be_used_to_declare_compiler_provided_intrinsic_types); + // If the type parameter node does not have an identical constraint as the resolved + // type parameter at this position, we report an error. + const constraint = getEffectiveConstraintOfTypeParameter(source); + const sourceConstraint = constraint && getTypeFromTypeNode(constraint); + const targetConstraint = getConstraintOfTypeParameter(target); + // relax check if later interface augmentation has no constraint, it's more broad and is OK to merge with + // a more constrained interface (this could be generalized to a full hierarchy check, but that's maybe overkill) + if (sourceConstraint && targetConstraint && !isTypeIdenticalTo(sourceConstraint, targetConstraint)) { + return false; } - } - else { - checkSourceElement(node.type); - registerForUnusedIdentifiersCheck(node); - } - } - function computeEnumMemberValues(node: EnumDeclaration) { - const nodeLinks = getNodeLinks(node); - if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) { - nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; - let autoValue: number | undefined = 0; - for (const member of node.members) { - const value = computeMemberValue(member, autoValue); - getNodeLinks(member).enumMemberValue = value; - autoValue = typeof value === "number" ? value + 1 : undefined; + // If the type parameter node has a default and it is not identical to the default + // for the type parameter at this position, we report an error. + const sourceDefault = source.default && getTypeFromTypeNode(source.default); + const targetDefault = getDefaultFromTypeParameter(target); + if (sourceDefault && targetDefault && !isTypeIdenticalTo(sourceDefault, targetDefault)) { + return false; } } } - function computeMemberValue(member: EnumMember, autoValue: number | undefined) { - if (isComputedNonLiteralName(member.name)) { - error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums); - } - else { - const text = getTextOfPropertyName(member.name); - if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) { - error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name); - } - } - if (member.initializer) { - return computeConstantValue(member); - } - // In ambient non-const numeric enum declarations, enum members without initializers are - // considered computed members (as opposed to having auto-incremented values). - if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent) && getEnumKind(getSymbolOfNode(member.parent)) === EnumKind.Numeric) { - return undefined; - } - // If the member declaration specifies no value, the member is considered a constant enum member. - // If the member is the first member in the enum declaration, it is assigned the value zero. - // Otherwise, it is assigned the value of the immediately preceding member plus one, and an error - // occurs if the immediately preceding member is not a constant enum member. - if (autoValue !== undefined) { - return autoValue; - } - error(member.name, Diagnostics.Enum_member_must_have_initializer); - return undefined; + return true; + } + + function checkClassExpression(node: ClassExpression): Type { + checkClassLikeDeclaration(node); + checkNodeDeferred(node); + return getTypeOfSymbol(getSymbolOfNode(node)); + } + + function checkClassExpressionDeferred(node: ClassExpression) { + forEach(node.members, checkSourceElement); + registerForUnusedIdentifiersCheck(node); + } + + function checkClassDeclaration(node: ClassDeclaration) { + if (some(node.decorators) && some(node.members, p => hasStaticModifier(p) && isPrivateIdentifierClassElementDeclaration(p))) { + grammarErrorOnNode(node.decorators[0], Diagnostics.Class_decorators_can_t_be_used_with_static_private_identifier_Consider_removing_the_experimental_decorator); } + if (!node.name && !hasSyntacticModifier(node, ModifierFlags.Default)) { + grammarErrorOnFirstToken(node, Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name); + } + checkClassLikeDeclaration(node); + forEach(node.members, checkSourceElement); - function computeConstantValue(member: EnumMember): string | number | undefined { - const enumKind = getEnumKind(getSymbolOfNode(member.parent)); - const isConstEnum = isEnumConst(member.parent); - const initializer = member.initializer!; - const value = enumKind === EnumKind.Literal && !isLiteralEnumMember(member) ? undefined : evaluate(initializer); - if (value !== undefined) { - if (isConstEnum && typeof value === "number" && !isFinite(value)) { - error(initializer, isNaN(value) ? - Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN : - Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value); - } - } - else if (enumKind === EnumKind.Literal) { - error(initializer, Diagnostics.Computed_values_are_not_permitted_in_an_enum_with_string_valued_members); - return 0; - } - else if (isConstEnum) { - error(initializer, Diagnostics.const_enum_member_initializers_can_only_contain_literal_values_and_other_computed_enum_values); - } - else if (member.parent.flags & NodeFlags.Ambient) { - error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression); + registerForUnusedIdentifiersCheck(node); + } + + function checkClassLikeDeclaration(node: ClassLikeDeclaration) { + checkGrammarClassLikeDeclaration(node); + checkDecorators(node); + checkCollisionsForDeclarationName(node, node.name); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfNode(node); + const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + const typeWithThis = getTypeWithThisArgument(type); + const staticType = getTypeOfSymbol(symbol) as ObjectType; + checkTypeParameterListsIdentical(symbol); + checkFunctionOrConstructorSymbol(symbol); + checkClassForDuplicateDeclarations(node); + + // Only check for reserved static identifiers on non-ambient context. + const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); + if (!nodeInAmbientContext) { + checkClassForStaticPropertyNameConflicts(node); + } + + const baseTypeNode = getEffectiveBaseTypeNode(node); + if (baseTypeNode) { + forEach(baseTypeNode.typeArguments, checkSourceElement); + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(baseTypeNode.parent, ExternalEmitHelpers.Extends); } - else { - // Only here do we need to check that the initializer is assignable to the enum type. - const source = checkExpression(initializer); - if (!isTypeAssignableToKind(source, TypeFlags.NumberLike)) { - error(initializer, Diagnostics.Only_numeric_enums_can_have_computed_members_but_this_expression_has_type_0_If_you_do_not_need_exhaustiveness_checks_consider_using_an_object_literal_instead, typeToString(source)); - } - else { - checkTypeAssignableTo(source, getDeclaredTypeOfSymbol(getSymbolOfNode(member.parent)), initializer, /*headMessage*/ undefined); - } + // check both @extends and extends if both are specified. + const extendsNode = getClassExtendsHeritageElement(node); + if (extendsNode && extendsNode !== baseTypeNode) { + checkExpression(extendsNode.expression); } - return value; - function evaluate(expr: Expression): string | number | undefined { - switch (expr.kind) { - case SyntaxKind.PrefixUnaryExpression: - const value = evaluate((expr as PrefixUnaryExpression).operand); - if (typeof value === "number") { - switch ((expr as PrefixUnaryExpression).operator) { - case SyntaxKind.PlusToken: return value; - case SyntaxKind.MinusToken: return -value; - case SyntaxKind.TildeToken: return ~value; - } - } - break; - case SyntaxKind.BinaryExpression: - const left = evaluate((expr as BinaryExpression).left); - const right = evaluate((expr as BinaryExpression).right); - if (typeof left === "number" && typeof right === "number") { - switch ((expr as BinaryExpression).operatorToken.kind) { - case SyntaxKind.BarToken: return left | right; - case SyntaxKind.AmpersandToken: return left & right; - case SyntaxKind.GreaterThanGreaterThanToken: return left >> right; - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right; - case SyntaxKind.LessThanLessThanToken: return left << right; - case SyntaxKind.CaretToken: return left ^ right; - case SyntaxKind.AsteriskToken: return left * right; - case SyntaxKind.SlashToken: return left / right; - case SyntaxKind.PlusToken: return left + right; - case SyntaxKind.MinusToken: return left - right; - case SyntaxKind.PercentToken: return left % right; - case SyntaxKind.AsteriskAsteriskToken: return left ** right; - } - } - else if (typeof left === "string" && typeof right === "string" && (expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken) { - return left + right; - } - break; - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return (expr as StringLiteralLike).text; - case SyntaxKind.NumericLiteral: - checkGrammarNumericLiteral(expr as NumericLiteral); - return +(expr as NumericLiteral).text; - case SyntaxKind.ParenthesizedExpression: - return evaluate((expr as ParenthesizedExpression).expression); - case SyntaxKind.Identifier: - const identifier = expr as Identifier; - if (isInfinityOrNaNString(identifier.escapedText)) { - return +(identifier.escapedText); + const baseTypes = getBaseTypes(type); + if (baseTypes.length && produceDiagnostics) { + const baseType = baseTypes[0]; + const baseConstructorType = getBaseConstructorTypeOfClass(type); + const staticBaseType = getApparentType(baseConstructorType); + checkBaseTypeAccessibility(staticBaseType, baseTypeNode); + checkSourceElement(baseTypeNode.expression); + if (some(baseTypeNode.typeArguments)) { + forEach(baseTypeNode.typeArguments, checkSourceElement); + for (const constructor of getConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode)) { + if (!checkTypeArgumentConstraints(baseTypeNode, constructor.typeParameters!)) { + break; } - return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), identifier.escapedText); - case SyntaxKind.ElementAccessExpression: - case SyntaxKind.PropertyAccessExpression: - const ex = expr as AccessExpression; - if (isConstantMemberAccess(ex)) { - const type = getTypeOfExpression(ex.expression); - if (type.symbol && type.symbol.flags & SymbolFlags.Enum) { - let name: __String; - if (ex.kind === SyntaxKind.PropertyAccessExpression) { - name = ex.name.escapedText; - } - else { - name = escapeLeadingUnderscores(cast(ex.argumentExpression, isLiteralExpression).text); - } - return evaluateEnumMember(expr, type.symbol, name); - } + } + } + const baseWithThis = getTypeWithThisArgument(baseType, type.thisType); + if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { + issueMemberSpecificError(node, typeWithThis, baseWithThis, Diagnostics.Class_0_incorrectly_extends_base_class_1); + } + else { + // Report static side error only when instance type is assignable + checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node, + Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1); + } + if (baseConstructorType.flags & TypeFlags.TypeVariable) { + if (!isMixinConstructorType(staticType)) { + error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any); + } + else { + const constructSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); + if (constructSignatures.some(signature => signature.flags & SignatureFlags.Abstract) && !hasSyntacticModifier(node, ModifierFlags.Abstract)) { + error(node.name || node, Diagnostics.A_mixin_class_that_extends_from_a_type_variable_containing_an_abstract_construct_signature_must_also_be_declared_abstract); } - break; + } } - return undefined; + + if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class) && !(baseConstructorType.flags & TypeFlags.TypeVariable)) { + // When the static base type is a "class-like" constructor function (but not actually a class), we verify + // that all instantiated base constructor signatures return the same type. + const constructors = getInstantiatedConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode); + if (forEach(constructors, sig => !isJSConstructor(sig.declaration) && !isTypeIdenticalTo(getReturnTypeOfSignature(sig), baseType))) { + error(baseTypeNode.expression, Diagnostics.Base_constructors_must_all_have_the_same_return_type); + } + } + checkKindsOfPropertyMemberOverrides(type, baseType); } + } + + checkMembersForOverrideModifier(node, type, typeWithThis, staticType); - function evaluateEnumMember(expr: Expression, enumSymbol: Symbol, name: __String) { - const memberSymbol = enumSymbol.exports!.get(name); - if (memberSymbol) { - const declaration = memberSymbol.valueDeclaration; - if (declaration !== member) { - if (declaration && isBlockScopedNameDeclaredBeforeUse(declaration, member)) { - return getEnumMemberValue(declaration as EnumMember); + const implementedTypeNodes = getEffectiveImplementsTypeNodes(node); + if (implementedTypeNodes) { + for (const typeRefNode of implementedTypeNodes) { + if (!isEntityNameExpression(typeRefNode.expression) || isOptionalChain(typeRefNode.expression)) { + error(typeRefNode.expression, Diagnostics.A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments); + } + checkTypeReferenceNode(typeRefNode); + if (produceDiagnostics) { + const t = getReducedType(getTypeFromTypeNode(typeRefNode)); + if (!isErrorType(t)) { + if (isValidBaseType(t)) { + const genericDiag = t.symbol && t.symbol.flags & SymbolFlags.Class ? + Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass : + Diagnostics.Class_0_incorrectly_implements_interface_1; + const baseWithThis = getTypeWithThisArgument(t, type.thisType); + if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { + issueMemberSpecificError(node, typeWithThis, baseWithThis, genericDiag); + } + } + else { + error(typeRefNode, Diagnostics.A_class_can_only_implement_an_object_type_or_intersection_of_object_types_with_statically_known_members); } - error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); - return 0; - } - else { - error(expr, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(memberSymbol)); } } - return undefined; } } - function isConstantMemberAccess(node: Expression): boolean { - return node.kind === SyntaxKind.Identifier || - node.kind === SyntaxKind.PropertyAccessExpression && isConstantMemberAccess((node as PropertyAccessExpression).expression) || - node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((node as ElementAccessExpression).expression) && - isStringLiteralLike((node as ElementAccessExpression).argumentExpression); + if (produceDiagnostics) { + checkIndexConstraints(type, symbol); + checkIndexConstraints(staticType, symbol, /*isStaticIndex*/ true); + checkTypeForDuplicateIndexSignatures(node); + checkPropertyInitialization(node); } + } - function checkEnumDeclaration(node: EnumDeclaration) { - if (!produceDiagnostics) { - return; + function checkMembersForOverrideModifier(node: ClassLikeDeclaration, type: InterfaceType, typeWithThis: Type, staticType: ObjectType) { + const baseTypeNode = getEffectiveBaseTypeNode(node); + const baseTypes = baseTypeNode && getBaseTypes(type); + const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; + const baseStaticType = getBaseConstructorTypeOfClass(type); + + for (const member of node.members) { + if (hasAmbientModifier(member)) { + continue; } - // Grammar checking - checkGrammarDecoratorsAndModifiers(node); + if (isConstructorDeclaration(member)) { + forEach(member.parameters, param => { + if (isParameterPropertyDeclaration(param, member)) { + checkExistingMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + param, + /* memberIsParameterProperty */ true + ); + } + }); + } + checkExistingMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + member, + /* memberIsParameterProperty */ false, + ); + } + } - checkCollisionsForDeclarationName(node, node.name); - checkExportsOnMergedDeclarations(node); - node.members.forEach(checkEnumMember); + /** + * @param member Existing member node to be checked. + * Note: `member` cannot be a synthetic node. + */ + function checkExistingMemberForOverrideModifier( + node: ClassLikeDeclaration, + staticType: ObjectType, + baseStaticType: Type, + baseWithThis: Type | undefined, + type: InterfaceType, + typeWithThis: Type, + member: ClassElement | ParameterPropertyDeclaration, + memberIsParameterProperty: boolean, + reportErrors = true, + ): MemberOverrideStatus { + const declaredProp = member.name + && getSymbolAtLocation(member.name) + || getSymbolAtLocation(member); + if (!declaredProp) { + return MemberOverrideStatus.Ok; + } - computeEnumMemberValues(node); + return checkMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + hasOverrideModifier(member), + hasAbstractModifier(member), + isStatic(member), + memberIsParameterProperty, + symbolName(declaredProp), + reportErrors ? member : undefined, + ); + } - // Spec 2014 - Section 9.3: - // It isn't possible for one enum declaration to continue the automatic numbering sequence of another, - // and when an enum type has multiple declarations, only one declaration is permitted to omit a value - // for the first member. - // - // Only perform this check once per symbol - const enumSymbol = getSymbolOfNode(node); - const firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind); - if (node === firstDeclaration) { - if (enumSymbol.declarations && enumSymbol.declarations.length > 1) { - const enumIsConst = isEnumConst(node); - // check that const is placed\omitted on all enum declarations - forEach(enumSymbol.declarations, decl => { - if (isEnumDeclaration(decl) && isEnumConst(decl) !== enumIsConst) { - error(getNameOfDeclaration(decl), Diagnostics.Enum_declarations_must_all_be_const_or_non_const); - } - }); + /** + * Checks a class member declaration for either a missing or an invalid `override` modifier. + * Note: this function can be used for speculative checking, + * i.e. checking a member that does not yet exist in the program. + * An example of that would be to call this function in a completions scenario, + * when offering a method declaration as completion. + * @param errorNode The node where we should report an error, or undefined if we should not report errors. + */ + function checkMemberForOverrideModifier( + node: ClassLikeDeclaration, + staticType: ObjectType, + baseStaticType: Type, + baseWithThis: Type | undefined, + type: InterfaceType, + typeWithThis: Type, + memberHasOverrideModifier: boolean, + memberHasAbstractModifier: boolean, + memberIsStatic: boolean, + memberIsParameterProperty: boolean, + memberName: string, + errorNode?: Node, + ): MemberOverrideStatus { + const isJs = isInJSFile(node); + const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); + if (baseWithThis && (memberHasOverrideModifier || compilerOptions.noImplicitOverride)) { + const memberEscapedName = escapeLeadingUnderscores(memberName); + const thisType = memberIsStatic ? staticType : typeWithThis; + const baseType = memberIsStatic ? baseStaticType : baseWithThis; + const prop = getPropertyOfType(thisType, memberEscapedName); + const baseProp = getPropertyOfType(baseType, memberEscapedName); + + const baseClassName = typeToString(baseWithThis); + if (prop && !baseProp && memberHasOverrideModifier) { + if (errorNode) { + const suggestion = getSuggestedSymbolForNonexistentClassMember(memberName, baseType); // Again, using symbol name: note that's different from `symbol.escapedName` + suggestion ? + error( + errorNode, + isJs ? + Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1 : + Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1, + baseClassName, + symbolToString(suggestion)) : + error( + errorNode, + isJs ? + Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0 : + Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0, + baseClassName); + } + return MemberOverrideStatus.HasInvalidOverride; + } + else if (prop && baseProp?.declarations && compilerOptions.noImplicitOverride && !nodeInAmbientContext) { + const baseHasAbstract = some(baseProp.declarations, hasAbstractModifier); + if (memberHasOverrideModifier) { + return MemberOverrideStatus.Ok; } - let seenEnumMissingInitialInitializer = false; - forEach(enumSymbol.declarations, declaration => { - // return true if we hit a violation of the rule, false otherwise - if (declaration.kind !== SyntaxKind.EnumDeclaration) { - return false; - } - - const enumDeclaration = declaration as EnumDeclaration; - if (!enumDeclaration.members.length) { - return false; - } - - const firstEnumMember = enumDeclaration.members[0]; - if (!firstEnumMember.initializer) { - if (seenEnumMissingInitialInitializer) { - error(firstEnumMember.name, Diagnostics.In_an_enum_with_multiple_declarations_only_one_declaration_can_omit_an_initializer_for_its_first_enum_element); - } - else { - seenEnumMissingInitialInitializer = true; - } + if (!baseHasAbstract) { + if (errorNode) { + const diag = memberIsParameterProperty ? + isJs ? + Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 : + Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0 : + isJs ? + Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 : + Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0; + error(errorNode, diag, baseClassName); + } + return MemberOverrideStatus.NeedsOverride; + } + else if (memberHasAbstractModifier && baseHasAbstract) { + if (errorNode) { + error(errorNode, Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0, baseClassName); } - }); + return MemberOverrideStatus.NeedsOverride; + } } } - - function checkEnumMember(node: EnumMember) { - if (isPrivateIdentifier(node.name)) { - error(node, Diagnostics.An_enum_member_cannot_be_named_with_a_private_identifier); + else if (memberHasOverrideModifier) { + if (errorNode) { + const className = typeToString(type); + error( + errorNode, + isJs ? + Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class : + Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class, + className); } + return MemberOverrideStatus.HasInvalidOverride; } - function getFirstNonAmbientClassOrFunctionDeclaration(symbol: Symbol): Declaration | undefined { - const declarations = symbol.declarations; - if (declarations) { - for (const declaration of declarations) { - if ((declaration.kind === SyntaxKind.ClassDeclaration || - (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((declaration as FunctionLikeDeclaration).body))) && - !(declaration.flags & NodeFlags.Ambient)) { - return declaration; + return MemberOverrideStatus.Ok; + } + + function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: Type, baseWithThis: Type, broadDiag: DiagnosticMessage) { + // iterate over all implemented properties and issue errors on each one which isn't compatible, rather than the class as a whole, if possible + let issuedMemberError = false; + for (const member of node.members) { + if (isStatic(member)) { + continue; + } + const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member); + if (declaredProp) { + const prop = getPropertyOfType(typeWithThis, declaredProp.escapedName); + const baseProp = getPropertyOfType(baseWithThis, declaredProp.escapedName); + if (prop && baseProp) { + const rootChain = () => chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2, + symbolToString(declaredProp), + typeToString(typeWithThis), + typeToString(baseWithThis) + ); + if (!checkTypeAssignableTo(getTypeOfSymbol(prop), getTypeOfSymbol(baseProp), member.name || member, /*message*/ undefined, rootChain)) { + issuedMemberError = true; } } } - return undefined; } + if (!issuedMemberError) { + // check again with diagnostics to generate a less-specific error + checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag); + } + } - function inSameLexicalScope(node1: Node, node2: Node) { - const container1 = getEnclosingBlockScopeContainer(node1); - const container2 = getEnclosingBlockScopeContainer(node2); - if (isGlobalSourceFile(container1)) { - return isGlobalSourceFile(container2); - } - else if (isGlobalSourceFile(container2)) { - return false; - } - else { - return container1 === container2; + function checkBaseTypeAccessibility(type: Type, node: ExpressionWithTypeArguments) { + const signatures = getSignaturesOfType(type, SignatureKind.Construct); + if (signatures.length) { + const declaration = signatures[0].declaration; + if (declaration && hasEffectiveModifier(declaration, ModifierFlags.Private)) { + const typeClassDeclaration = getClassLikeDeclarationOfSymbol(type.symbol)!; + if (!isNodeWithinClass(node, typeClassDeclaration)) { + error(node, Diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, getFullyQualifiedName(type.symbol)); + } } } + } - function checkModuleDeclaration(node: ModuleDeclaration) { - if (produceDiagnostics) { - // Grammar checking - const isGlobalAugmentation = isGlobalScopeAugmentation(node); - const inAmbientContext = node.flags & NodeFlags.Ambient; - if (isGlobalAugmentation && !inAmbientContext) { - error(node.name, Diagnostics.Augmentations_for_the_global_scope_should_have_declare_modifier_unless_they_appear_in_already_ambient_context); - } + /** + * Checks a member declaration node to see if has a missing or invalid `override` modifier. + * @param node Class-like node where the member is declared. + * @param member Member declaration node. + * Note: `member` can be a synthetic node without a parent. + */ + function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus { + if (!member.name) { + return MemberOverrideStatus.Ok; + } - const isAmbientExternalModule: boolean = isAmbientModule(node); - const contextErrorMessage = isAmbientExternalModule - ? Diagnostics.An_ambient_module_declaration_is_only_allowed_at_the_top_level_in_a_file - : Diagnostics.A_namespace_declaration_is_only_allowed_in_a_namespace_or_module; - if (checkGrammarModuleElementContext(node, contextErrorMessage)) { - // If we hit a module declaration in an illegal context, just bail out to avoid cascading errors. - return; - } + const symbol = getSymbolOfNode(node); + const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + const typeWithThis = getTypeWithThisArgument(type); + const staticType = getTypeOfSymbol(symbol) as ObjectType; + + const baseTypeNode = getEffectiveBaseTypeNode(node); + const baseTypes = baseTypeNode && getBaseTypes(type); + const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; + const baseStaticType = getBaseConstructorTypeOfClass(type); + + const memberHasOverrideModifier = member.parent + ? hasOverrideModifier(member) + : hasSyntacticModifier(member, ModifierFlags.Override); + + const memberName = unescapeLeadingUnderscores(getTextOfPropertyName(member.name)); + + return checkMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + memberHasOverrideModifier, + hasAbstractModifier(member), + isStatic(member), + /* memberIsParameterProperty */ false, + memberName, + ); + } + + function getTargetSymbol(s: Symbol) { + // if symbol is instantiated its flags are not copied from the 'target' + // so we'll need to get back original 'target' symbol to work with correct set of flags + return getCheckFlags(s) & CheckFlags.Instantiated ? (s as TransientSymbol).target! : s; + } + + function getClassOrInterfaceDeclarationsOfSymbol(symbol: Symbol) { + return filter(symbol.declarations, (d: Declaration): d is ClassDeclaration | InterfaceDeclaration => + d.kind === SyntaxKind.ClassDeclaration || d.kind === SyntaxKind.InterfaceDeclaration); + } - if (!checkGrammarDecoratorsAndModifiers(node)) { - if (!inAmbientContext && node.name.kind === SyntaxKind.StringLiteral) { - grammarErrorOnNode(node.name, Diagnostics.Only_ambient_modules_can_use_quoted_names); + function checkKindsOfPropertyMemberOverrides(type: InterfaceType, baseType: BaseType): void { + // TypeScript 1.0 spec (April 2014): 8.2.3 + // A derived class inherits all members from its base class it doesn't override. + // Inheritance means that a derived class implicitly contains all non - overridden members of the base class. + // Both public and private property members are inherited, but only public property members can be overridden. + // A property member in a derived class is said to override a property member in a base class + // when the derived class property member has the same name and kind(instance or static) + // as the base class property member. + // The type of an overriding property member must be assignable(section 3.8.4) + // to the type of the overridden property member, or otherwise a compile - time error occurs. + // Base class instance member functions can be overridden by derived class instance member functions, + // but not by other kinds of members. + // Base class instance member variables and accessors can be overridden by + // derived class instance member variables and accessors, but not by other kinds of members. + + // NOTE: assignability is checked in checkClassDeclaration + const baseProperties = getPropertiesOfType(baseType); + basePropertyCheck: for (const baseProperty of baseProperties) { + const base = getTargetSymbol(baseProperty); + + if (base.flags & SymbolFlags.Prototype) { + continue; + } + const baseSymbol = getPropertyOfObjectType(type, base.escapedName); + if (!baseSymbol) { + continue; + } + const derived = getTargetSymbol(baseSymbol); + const baseDeclarationFlags = getDeclarationModifierFlagsFromSymbol(base); + + Debug.assert(!!derived, "derived should point to something, even if it is the base class' declaration."); + + // In order to resolve whether the inherited method was overridden in the base class or not, + // we compare the Symbols obtained. Since getTargetSymbol returns the symbol on the *uninstantiated* + // type declaration, derived and base resolve to the same symbol even in the case of generic classes. + if (derived === base) { + // derived class inherits base without override/redeclaration + const derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol)!; + + // It is an error to inherit an abstract member without implementing it or being declared abstract. + // If there is no declaration for the derived class (as in the case of class expressions), + // then the class cannot be declared abstract. + if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasSyntacticModifier(derivedClassDecl, ModifierFlags.Abstract))) { + // Searches other base types for a declaration that would satisfy the inherited abstract member. + // (The class may have more than one base type via declaration merging with an interface with the + // same name.) + for (const otherBaseType of getBaseTypes(type)) { + if (otherBaseType === baseType) continue; + const baseSymbol = getPropertyOfObjectType(otherBaseType, base.escapedName); + const derivedElsewhere = baseSymbol && getTargetSymbol(baseSymbol); + if (derivedElsewhere && derivedElsewhere !== base) { + continue basePropertyCheck; + } + } + + if (derivedClassDecl.kind === SyntaxKind.ClassExpression) { + error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, + symbolToString(baseProperty), typeToString(baseType)); + } + else { + error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, + typeToString(type), symbolToString(baseProperty), typeToString(baseType)); } } - - if (isIdentifier(node.name)) { - checkCollisionsForDeclarationName(node, node.name); + } + else { + // derived overrides base. + const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived); + if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) { + // either base or derived property is private - not override, skip it + continue; } - checkExportsOnMergedDeclarations(node); - const symbol = getSymbolOfNode(node); - - // The following checks only apply on a non-ambient instantiated module declaration. - if (symbol.flags & SymbolFlags.ValueModule - && !inAmbientContext - && symbol.declarations - && symbol.declarations.length > 1 - && isInstantiatedModule(node, shouldPreserveConstEnums(compilerOptions))) { - const firstNonAmbientClassOrFunc = getFirstNonAmbientClassOrFunctionDeclaration(symbol); - if (firstNonAmbientClassOrFunc) { - if (getSourceFileOfNode(node) !== getSourceFileOfNode(firstNonAmbientClassOrFunc)) { - error(node.name, Diagnostics.A_namespace_declaration_cannot_be_in_a_different_file_from_a_class_or_function_with_which_it_is_merged); - } - else if (node.pos < firstNonAmbientClassOrFunc.pos) { - error(node.name, Diagnostics.A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged); - } + let errorMessage: DiagnosticMessage; + const basePropertyFlags = base.flags & SymbolFlags.PropertyOrAccessor; + const derivedPropertyFlags = derived.flags & SymbolFlags.PropertyOrAccessor; + if (basePropertyFlags && derivedPropertyFlags) { + // property/accessor is overridden with property/accessor + if (baseDeclarationFlags & ModifierFlags.Abstract && !(base.valueDeclaration && isPropertyDeclaration(base.valueDeclaration) && base.valueDeclaration.initializer) + || base.valueDeclaration && base.valueDeclaration.parent.kind === SyntaxKind.InterfaceDeclaration + || derived.valueDeclaration && isBinaryExpression(derived.valueDeclaration)) { + // when the base property is abstract or from an interface, base/derived flags don't need to match + // same when the derived property is from an assignment + continue; } - // if the module merges with a class declaration in the same lexical scope, - // we need to track this to ensure the correct emit. - const mergedClass = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration); - if (mergedClass && - inSameLexicalScope(node, mergedClass)) { - getNodeLinks(node).flags |= NodeCheckFlags.LexicalModuleMergesWithClass; + const overriddenInstanceProperty = basePropertyFlags !== SymbolFlags.Property && derivedPropertyFlags === SymbolFlags.Property; + const overriddenInstanceAccessor = basePropertyFlags === SymbolFlags.Property && derivedPropertyFlags !== SymbolFlags.Property; + if (overriddenInstanceProperty || overriddenInstanceAccessor) { + const errorMessage = overriddenInstanceProperty ? + Diagnostics._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property : + Diagnostics._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor; + error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType), typeToString(type)); } - } - - if (isAmbientExternalModule) { - if (isExternalModuleAugmentation(node)) { - // body of the augmentation should be checked for consistency only if augmentation was applied to its target (either global scope or module) - // otherwise we'll be swamped in cascading errors. - // We can detect if augmentation was applied using following rules: - // - augmentation for a global scope is always applied - // - augmentation for some external module is applied if symbol for augmentation is merged (it was combined with target module). - const checkBody = isGlobalAugmentation || (getSymbolOfNode(node).flags & SymbolFlags.Transient); - if (checkBody && node.body) { - for (const statement of node.body.statements) { - checkModuleAugmentationElement(statement, isGlobalAugmentation); + else if (useDefineForClassFields) { + const uninitialized = derived.declarations?.find(d => d.kind === SyntaxKind.PropertyDeclaration && !(d as PropertyDeclaration).initializer); + if (uninitialized + && !(derived.flags & SymbolFlags.Transient) + && !(baseDeclarationFlags & ModifierFlags.Abstract) + && !(derivedDeclarationFlags & ModifierFlags.Abstract) + && !derived.declarations?.some(d => !!(d.flags & NodeFlags.Ambient))) { + const constructor = findConstructorDeclaration(getClassLikeDeclarationOfSymbol(type.symbol)!); + const propName = (uninitialized as PropertyDeclaration).name; + if ((uninitialized as PropertyDeclaration).exclamationToken + || !constructor + || !isIdentifier(propName) + || !strictNullChecks + || !isPropertyInitializedInConstructor(propName, type, constructor)) { + const errorMessage = Diagnostics.Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_add_a_declare_modifier_or_remove_the_redundant_declaration; + error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType)); } } } - else if (isGlobalSourceFile(node.parent)) { - if (isGlobalAugmentation) { - error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); - } - else if (isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(node.name))) { - error(node.name, Diagnostics.Ambient_module_declaration_cannot_specify_relative_module_name); - } + + // correct case + continue; + } + else if (isPrototypeProperty(base)) { + if (isPrototypeProperty(derived) || derived.flags & SymbolFlags.Property) { + // method is overridden with method or property -- correct case + continue; } else { - if (isGlobalAugmentation) { - error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); - } - else { - // Node is not an augmentation and is not located on the script level. - // This means that this is declaration of ambient module that is located in other module or namespace which is prohibited. - error(node.name, Diagnostics.Ambient_modules_cannot_be_nested_in_other_modules_or_namespaces); - } + Debug.assert(!!(derived.flags & SymbolFlags.Accessor)); + errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor; } } + else if (base.flags & SymbolFlags.Accessor) { + errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function; + } + else { + errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function; + } + + error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type)); } + } + } - if (node.body) { - checkSourceElement(node.body); - if (!isGlobalScopeAugmentation(node)) { - registerForUnusedIdentifiersCheck(node); + function getNonInterhitedProperties(type: InterfaceType, baseTypes: BaseType[], properties: Symbol[]) { + if (!length(baseTypes)) { + return properties; + } + const seen = new Map<__String, Symbol>(); + forEach(properties, p => { + seen.set(p.escapedName, p); + }); + + for (const base of baseTypes) { + const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); + for (const prop of properties) { + const existing = seen.get(prop.escapedName); + if (existing && !isPropertyIdenticalTo(existing, prop)) { + seen.delete(prop.escapedName); } } } - function checkModuleAugmentationElement(node: Node, isGlobalAugmentation: boolean): void { - switch (node.kind) { - case SyntaxKind.VariableStatement: - // error each individual name in variable statement instead of marking the entire variable statement - for (const decl of (node as VariableStatement).declarationList.declarations) { - checkModuleAugmentationElement(decl, isGlobalAugmentation); - } - break; - case SyntaxKind.ExportAssignment: - case SyntaxKind.ExportDeclaration: - grammarErrorOnFirstToken(node, Diagnostics.Exports_and_export_assignments_are_not_permitted_in_module_augmentations); - break; - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportDeclaration: - grammarErrorOnFirstToken(node, Diagnostics.Imports_are_not_permitted_in_module_augmentations_Consider_moving_them_to_the_enclosing_external_module); - break; - case SyntaxKind.BindingElement: - case SyntaxKind.VariableDeclaration: - const name = (node as VariableDeclaration | BindingElement).name; - if (isBindingPattern(name)) { - for (const el of name.elements) { - // mark individual names in binding pattern - checkModuleAugmentationElement(el, isGlobalAugmentation); - } - break; - } - // falls through - case SyntaxKind.ClassDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.TypeAliasDeclaration: - if (isGlobalAugmentation) { - return; - } - const symbol = getSymbolOfNode(node); - if (symbol) { - // module augmentations cannot introduce new names on the top level scope of the module - // this is done it two steps - // 1. quick check - if symbol for node is not merged - this is local symbol to this augmentation - report error - // 2. main check - report error if value declaration of the parent symbol is module augmentation) - let reportError = !(symbol.flags & SymbolFlags.Transient); - if (!reportError) { - // symbol should not originate in augmentation - reportError = !!symbol.parent?.declarations && isExternalModuleAugmentation(symbol.parent.declarations[0]); - } + return arrayFrom(seen.values()); + } + + function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean { + const baseTypes = getBaseTypes(type); + if (baseTypes.length < 2) { + return true; + } + + interface InheritanceInfoMap { prop: Symbol; containingType: Type; } + const seen = new Map<__String, InheritanceInfoMap>(); + forEach(resolveDeclaredMembers(type).declaredProperties, p => { + seen.set(p.escapedName, { prop: p, containingType: type }); + }); + let ok = true; + + for (const base of baseTypes) { + const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); + for (const prop of properties) { + const existing = seen.get(prop.escapedName); + if (!existing) { + seen.set(prop.escapedName, { prop, containingType: base }); + } + else { + const isInheritedProperty = existing.containingType !== type; + if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) { + ok = false; + + const typeName1 = typeToString(existing.containingType); + const typeName2 = typeToString(base); + + let errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Named_property_0_of_types_1_and_2_are_not_identical, symbolToString(prop), typeName1, typeName2); + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Interface_0_cannot_simultaneously_extend_types_1_and_2, typeToString(type), typeName1, typeName2); + diagnostics.add(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo)); } - break; + } } } - function getFirstNonModuleExportsIdentifier(node: EntityNameOrEntityNameExpression): Identifier { - switch (node.kind) { - case SyntaxKind.Identifier: - return node; - case SyntaxKind.QualifiedName: - do { - node = node.left; - } while (node.kind !== SyntaxKind.Identifier); - return node; - case SyntaxKind.PropertyAccessExpression: - do { - if (isModuleExportsAccessExpression(node.expression) && !isPrivateIdentifier(node.name)) { - return node.name; + return ok; + } + + function checkPropertyInitialization(node: ClassLikeDeclaration) { + if (!strictNullChecks || !strictPropertyInitialization || node.flags & NodeFlags.Ambient) { + return; + } + const constructor = findConstructorDeclaration(node); + for (const member of node.members) { + if (getEffectiveModifierFlags(member) & ModifierFlags.Ambient) { + continue; + } + if (!isStatic(member) && isPropertyWithoutInitializer(member)) { + const propName = (member as PropertyDeclaration).name; + if (isIdentifier(propName) || isPrivateIdentifier(propName)) { + const type = getTypeOfSymbol(getSymbolOfNode(member)); + if (!(type.flags & TypeFlags.AnyOrUnknown || getFalsyFlags(type) & TypeFlags.Undefined)) { + if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) { + error(member.name, Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor, declarationNameToString(propName)); } - node = node.expression; - } while (node.kind !== SyntaxKind.Identifier); - return node; + } + } } } + } - function checkExternalImportOrExportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean { - const moduleName = getExternalModuleName(node); - if (!moduleName || nodeIsMissing(moduleName)) { - // Should be a parse error. - return false; - } - if (!isStringLiteral(moduleName)) { - error(moduleName, Diagnostics.String_literal_expected); - return false; - } - const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); - if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule) { - error(moduleName, node.kind === SyntaxKind.ExportDeclaration ? - Diagnostics.Export_declarations_are_not_permitted_in_a_namespace : - Diagnostics.Import_declarations_in_a_namespace_cannot_reference_a_module); - return false; - } - if (inAmbientExternalModule && isExternalModuleNameRelative(moduleName.text)) { - // we have already reported errors on top level imports/exports in external module augmentations in checkModuleDeclaration - // no need to do this again. - if (!isTopLevelInExternalModuleAugmentation(node)) { - // TypeScript 1.0 spec (April 2013): 12.1.6 - // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference - // other external modules only through top - level external module names. - // Relative external module names are not permitted. - error(node, Diagnostics.Import_or_export_declaration_in_an_ambient_module_declaration_cannot_reference_module_through_relative_module_name); - return false; + function isPropertyWithoutInitializer(node: Node) { + return node.kind === SyntaxKind.PropertyDeclaration && + !hasAbstractModifier(node) && + !(node as PropertyDeclaration).exclamationToken && + !(node as PropertyDeclaration).initializer; + } + + function isPropertyInitializedInStaticBlocks(propName: Identifier | PrivateIdentifier, propType: Type, staticBlocks: readonly ClassStaticBlockDeclaration[], startPos: number, endPos: number) { + for (const staticBlock of staticBlocks) { + // static block must be within the provided range as they are evaluated in document order (unlike constructors) + if (staticBlock.pos >= startPos && staticBlock.pos <= endPos) { + const reference = factory.createPropertyAccessExpression(factory.createThis(), propName); + setParent(reference.expression, reference); + setParent(reference, staticBlock); + reference.flowNode = staticBlock.returnFlowNode; + const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); + if (!(getFalsyFlags(flowType) & TypeFlags.Undefined)) { + return true; } } - return true; } + return false; + } - function checkAliasSymbol(node: ImportEqualsDeclaration | VariableDeclaration | ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier | NamespaceExport) { - let symbol = getSymbolOfNode(node); - const target = resolveAlias(symbol); + function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier, propType: Type, constructor: ConstructorDeclaration) { + const reference = factory.createPropertyAccessExpression(factory.createThis(), propName); + setParent(reference.expression, reference); + setParent(reference, constructor); + reference.flowNode = constructor.returnFlowNode; + const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); + return !(getFalsyFlags(flowType) & TypeFlags.Undefined); + } - if (target !== unknownSymbol) { - // For external modules, `symbol` represents the local symbol for an alias. - // This local symbol will merge any other local declarations (excluding other aliases) - // and symbol.flags will contains combined representation for all merged declaration. - // Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have, - // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* - // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). - symbol = getMergedSymbol(symbol.exportSymbol || symbol); - const excludedMeanings = - (symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) | - (symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) | - (symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0); - if (target.flags & excludedMeanings) { - const message = node.kind === SyntaxKind.ExportSpecifier ? - Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 : - Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0; - error(node, message, symbolToString(symbol)); - } - - if (compilerOptions.isolatedModules - && !isTypeOnlyImportOrExportDeclaration(node) - && !(node.flags & NodeFlags.Ambient)) { - const typeOnlyAlias = getTypeOnlyAliasDeclaration(symbol); - const isType = !(target.flags & SymbolFlags.Value); - if (isType || typeOnlyAlias) { - switch (node.kind) { - case SyntaxKind.ImportClause: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportEqualsDeclaration: { - if (compilerOptions.preserveValueImports) { - Debug.assertIsDefined(node.name, "An ImportClause with a symbol should have a name"); - const message = isType - ? Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled - : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled; - const name = idText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name); - addTypeOnlyDeclarationRelatedInfo( - error(node, message, name), - isType ? undefined : typeOnlyAlias, - name - ); - } - break; - } - case SyntaxKind.ExportSpecifier: { - // Don't allow re-exporting an export that will be elided when `--isolatedModules` is set. - // The exception is that `import type { A } from './a'; export { A }` is allowed - // because single-file analysis can determine that the export should be dropped. - if (getSourceFileOfNode(typeOnlyAlias) !== getSourceFileOfNode(node)) { - const message = isType - ? Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type - : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_isolatedModules_is_enabled; - const name = idText(node.propertyName || node.name); - addTypeOnlyDeclarationRelatedInfo( - error(node, message, name), - isType ? undefined : typeOnlyAlias, - name - ); - return; - } - } - } - } - } + function checkInterfaceDeclaration(node: InterfaceDeclaration) { + // Grammar checking + if (!checkGrammarDecoratorsAndModifiers(node)) checkGrammarInterfaceDeclaration(node); + + checkTypeParameters(node.typeParameters); + if (produceDiagnostics) { + checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); + + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfNode(node); + checkTypeParameterListsIdentical(symbol); - if (isImportSpecifier(node) && target.declarations?.every(d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated))) { - addDeprecatedSuggestion(node.name, target.declarations, symbol.escapedName as string); + // Only check this symbol once + const firstInterfaceDecl = getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration); + if (node === firstInterfaceDecl) { + const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + const typeWithThis = getTypeWithThisArgument(type); + // run subsequent checks only if first set succeeded + if (checkInheritedPropertiesAreIdentical(type, node.name)) { + for (const baseType of getBaseTypes(type)) { + checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(baseType, type.thisType), node.name, Diagnostics.Interface_0_incorrectly_extends_interface_1); + } + checkIndexConstraints(type, symbol); } } + checkObjectTypeForDuplicateDeclarations(node); } + forEach(getInterfaceBaseTypeNodes(node), heritageElement => { + if (!isEntityNameExpression(heritageElement.expression) || isOptionalChain(heritageElement.expression)) { + error(heritageElement.expression, Diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments); + } + checkTypeReferenceNode(heritageElement); + }); - function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) { - checkCollisionsForDeclarationName(node, node.name); - checkAliasSymbol(node); - if (node.kind === SyntaxKind.ImportSpecifier && - idText(node.propertyName || node.name) === "default" && - getESModuleInterop(compilerOptions) && - moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); + forEach(node.members, checkSourceElement); + + if (produceDiagnostics) { + checkTypeForDuplicateIndexSignatures(node); + registerForUnusedIdentifiersCheck(node); + } + } + + function checkTypeAliasDeclaration(node: TypeAliasDeclaration) { + // Grammar checking + checkGrammarDecoratorsAndModifiers(node); + checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); + checkExportsOnMergedDeclarations(node); + checkTypeParameters(node.typeParameters); + if (node.type.kind === SyntaxKind.IntrinsicKeyword) { + if (!intrinsicTypeKinds.has(node.name.escapedText as string) || length(node.typeParameters) !== 1) { + error(node.type, Diagnostics.The_intrinsic_keyword_can_only_be_used_to_declare_compiler_provided_intrinsic_types); } } + else { + checkSourceElement(node.type); + registerForUnusedIdentifiersCheck(node); + } + } - function checkAssertClause(declaration: ImportDeclaration | ExportDeclaration) { - if (declaration.assertClause) { - const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier); - if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext) { - return grammarErrorOnNode(declaration.assertClause, - moduleKind === ModuleKind.NodeNext - ? Diagnostics.Import_assertions_are_not_allowed_on_statements_that_transpile_to_commonjs_require_calls - : Diagnostics.Import_assertions_are_only_supported_when_the_module_option_is_set_to_esnext_or_nodenext); - } + function computeEnumMemberValues(node: EnumDeclaration) { + const nodeLinks = getNodeLinks(node); + if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) { + nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; + let autoValue: number | undefined = 0; + for (const member of node.members) { + const value = computeMemberValue(member, autoValue); + getNodeLinks(member).enumMemberValue = value; + autoValue = typeof value === "number" ? value + 1 : undefined; + } + } + } - if (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly) { - return grammarErrorOnNode(declaration.assertClause, Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports); - } + function computeMemberValue(member: EnumMember, autoValue: number | undefined) { + if (isComputedNonLiteralName(member.name)) { + error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums); + } + else { + const text = getTextOfPropertyName(member.name); + if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) { + error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name); } } + if (member.initializer) { + return computeConstantValue(member); + } + // In ambient non-const numeric enum declarations, enum members without initializers are + // considered computed members (as opposed to having auto-incremented values). + if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent) && getEnumKind(getSymbolOfNode(member.parent)) === EnumKind.Numeric) { + return undefined; + } + // If the member declaration specifies no value, the member is considered a constant enum member. + // If the member is the first member in the enum declaration, it is assigned the value zero. + // Otherwise, it is assigned the value of the immediately preceding member plus one, and an error + // occurs if the immediately preceding member is not a constant enum member. + if (autoValue !== undefined) { + return autoValue; + } + error(member.name, Diagnostics.Enum_member_must_have_initializer); + return undefined; + } - function checkImportDeclaration(node: ImportDeclaration) { - if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) { - // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. - return; + function computeConstantValue(member: EnumMember): string | number | undefined { + const enumKind = getEnumKind(getSymbolOfNode(member.parent)); + const isConstEnum = isEnumConst(member.parent); + const initializer = member.initializer!; + const value = enumKind === EnumKind.Literal && !isLiteralEnumMember(member) ? undefined : evaluate(initializer); + if (value !== undefined) { + if (isConstEnum && typeof value === "number" && !isFinite(value)) { + error(initializer, isNaN(value) ? + Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN : + Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value); + } + } + else if (enumKind === EnumKind.Literal) { + error(initializer, Diagnostics.Computed_values_are_not_permitted_in_an_enum_with_string_valued_members); + return 0; + } + else if (isConstEnum) { + error(initializer, Diagnostics.const_enum_member_initializers_can_only_contain_literal_values_and_other_computed_enum_values); + } + else if (member.parent.flags & NodeFlags.Ambient) { + error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression); + } + else { + // Only here do we need to check that the initializer is assignable to the enum type. + const source = checkExpression(initializer); + if (!isTypeAssignableToKind(source, TypeFlags.NumberLike)) { + error(initializer, Diagnostics.Only_numeric_enums_can_have_computed_members_but_this_expression_has_type_0_If_you_do_not_need_exhaustiveness_checks_consider_using_an_object_literal_instead, typeToString(source)); + } + else { + checkTypeAssignableTo(source, getDeclaredTypeOfSymbol(getSymbolOfNode(member.parent)), initializer, /*headMessage*/ undefined); } - if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { - grammarErrorOnFirstToken(node, Diagnostics.An_import_declaration_cannot_have_modifiers); - } - if (checkExternalImportOrExportDeclaration(node)) { - const importClause = node.importClause; - if (importClause && !checkGrammarImportClause(importClause)) { - if (importClause.name) { - checkImportBinding(importClause); - } - if (importClause.namedBindings) { - if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - checkImportBinding(importClause.namedBindings); - if (moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && getESModuleInterop(compilerOptions)) { - // import * as ns from "foo"; - checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); + } + return value; + + function evaluate(expr: Expression): string | number | undefined { + switch (expr.kind) { + case SyntaxKind.PrefixUnaryExpression: + const value = evaluate((expr as PrefixUnaryExpression).operand); + if (typeof value === "number") { + switch ((expr as PrefixUnaryExpression).operator) { + case SyntaxKind.PlusToken: return value; + case SyntaxKind.MinusToken: return -value; + case SyntaxKind.TildeToken: return ~value; + } + } + break; + case SyntaxKind.BinaryExpression: + const left = evaluate((expr as BinaryExpression).left); + const right = evaluate((expr as BinaryExpression).right); + if (typeof left === "number" && typeof right === "number") { + switch ((expr as BinaryExpression).operatorToken.kind) { + case SyntaxKind.BarToken: return left | right; + case SyntaxKind.AmpersandToken: return left & right; + case SyntaxKind.GreaterThanGreaterThanToken: return left >> right; + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right; + case SyntaxKind.LessThanLessThanToken: return left << right; + case SyntaxKind.CaretToken: return left ^ right; + case SyntaxKind.AsteriskToken: return left * right; + case SyntaxKind.SlashToken: return left / right; + case SyntaxKind.PlusToken: return left + right; + case SyntaxKind.MinusToken: return left - right; + case SyntaxKind.PercentToken: return left % right; + case SyntaxKind.AsteriskAsteriskToken: return left ** right; + } + } + else if (typeof left === "string" && typeof right === "string" && (expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken) { + return left + right; + } + break; + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return (expr as StringLiteralLike).text; + case SyntaxKind.NumericLiteral: + checkGrammarNumericLiteral(expr as NumericLiteral); + return +(expr as NumericLiteral).text; + case SyntaxKind.ParenthesizedExpression: + return evaluate((expr as ParenthesizedExpression).expression); + case SyntaxKind.Identifier: + const identifier = expr as Identifier; + if (isInfinityOrNaNString(identifier.escapedText)) { + return +(identifier.escapedText); + } + return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), identifier.escapedText); + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: + const ex = expr as AccessExpression; + if (isConstantMemberAccess(ex)) { + const type = getTypeOfExpression(ex.expression); + if (type.symbol && type.symbol.flags & SymbolFlags.Enum) { + let name: __String; + if (ex.kind === SyntaxKind.PropertyAccessExpression) { + name = ex.name.escapedText; } - } - else { - const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier); - if (moduleExisted) { - forEach(importClause.namedBindings.elements, checkImportBinding); + else { + name = escapeLeadingUnderscores(cast(ex.argumentExpression, isLiteralExpression).text); } + return evaluateEnumMember(expr, type.symbol, name); } } - } + break; } - checkAssertClause(node); + return undefined; } - function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) { - if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) { - // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. - return; - } - - checkGrammarDecoratorsAndModifiers(node); - if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { - checkImportBinding(node); - if (hasSyntacticModifier(node, ModifierFlags.Export)) { - markExportAsReferenced(node); - } - if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { - const target = resolveAlias(getSymbolOfNode(node)); - if (target !== unknownSymbol) { - if (target.flags & SymbolFlags.Value) { - // Target is a value symbol, check that it is not hidden by a local declaration with the same name - const moduleName = getFirstIdentifier(node.moduleReference); - if (!(resolveEntityName(moduleName, SymbolFlags.Value | SymbolFlags.Namespace)!.flags & SymbolFlags.Namespace)) { - error(moduleName, Diagnostics.Module_0_is_hidden_by_a_local_declaration_with_the_same_name, declarationNameToString(moduleName)); - } - } - if (target.flags & SymbolFlags.Type) { - checkTypeNameIsReserved(node.name, Diagnostics.Import_name_cannot_be_0); - } - } - if (node.isTypeOnly) { - grammarErrorOnNode(node, Diagnostics.An_import_alias_cannot_use_import_type); + function evaluateEnumMember(expr: Expression, enumSymbol: Symbol, name: __String) { + const memberSymbol = enumSymbol.exports!.get(name); + if (memberSymbol) { + const declaration = memberSymbol.valueDeclaration; + if (declaration !== member) { + if (declaration && isBlockScopedNameDeclaredBeforeUse(declaration, member)) { + return getEnumMemberValue(declaration as EnumMember); } + error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); + return 0; } else { - if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat === undefined && !node.isTypeOnly && !(node.flags & NodeFlags.Ambient)) { - // Import equals declaration is deprecated in es6 or above - grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead); - } + error(expr, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(memberSymbol)); } } + return undefined; } + } - function checkExportDeclaration(node: ExportDeclaration) { - if (checkGrammarModuleElementContext(node, Diagnostics.An_export_declaration_can_only_be_used_in_a_module)) { - // If we hit an export in an illegal context, just bail out to avoid cascading errors. - return; - } + function isConstantMemberAccess(node: Expression): boolean { + return node.kind === SyntaxKind.Identifier || + node.kind === SyntaxKind.PropertyAccessExpression && isConstantMemberAccess((node as PropertyAccessExpression).expression) || + node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((node as ElementAccessExpression).expression) && + isStringLiteralLike((node as ElementAccessExpression).argumentExpression); + } - if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { - grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers); - } + function checkEnumDeclaration(node: EnumDeclaration) { + if (!produceDiagnostics) { + return; + } - if (node.moduleSpecifier && node.exportClause && isNamedExports(node.exportClause) && length(node.exportClause.elements) && languageVersion === ScriptTarget.ES3) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.CreateBinding); - } + // Grammar checking + checkGrammarDecoratorsAndModifiers(node); + + checkCollisionsForDeclarationName(node, node.name); + checkExportsOnMergedDeclarations(node); + node.members.forEach(checkEnumMember); - checkGrammarExportDeclaration(node); - if (!node.moduleSpecifier || checkExternalImportOrExportDeclaration(node)) { - if (node.exportClause && !isNamespaceExport(node.exportClause)) { - // export { x, y } - // export { x, y } from "foo" - forEach(node.exportClause.elements, checkExportSpecifier); - const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); - const inAmbientNamespaceDeclaration = !inAmbientExternalModule && node.parent.kind === SyntaxKind.ModuleBlock && - !node.moduleSpecifier && node.flags & NodeFlags.Ambient; - if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule && !inAmbientNamespaceDeclaration) { - error(node, Diagnostics.Export_declarations_are_not_permitted_in_a_namespace); + computeEnumMemberValues(node); + + // Spec 2014 - Section 9.3: + // It isn't possible for one enum declaration to continue the automatic numbering sequence of another, + // and when an enum type has multiple declarations, only one declaration is permitted to omit a value + // for the first member. + // + // Only perform this check once per symbol + const enumSymbol = getSymbolOfNode(node); + const firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind); + if (node === firstDeclaration) { + if (enumSymbol.declarations && enumSymbol.declarations.length > 1) { + const enumIsConst = isEnumConst(node); + // check that const is placed\omitted on all enum declarations + forEach(enumSymbol.declarations, decl => { + if (isEnumDeclaration(decl) && isEnumConst(decl) !== enumIsConst) { + error(getNameOfDeclaration(decl), Diagnostics.Enum_declarations_must_all_be_const_or_non_const); } + }); + } + + let seenEnumMissingInitialInitializer = false; + forEach(enumSymbol.declarations, declaration => { + // return true if we hit a violation of the rule, false otherwise + if (declaration.kind !== SyntaxKind.EnumDeclaration) { + return false; } - else { - // export * from "foo" - // export * as ns from "foo"; - const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!); - if (moduleSymbol && hasExportAssignmentSymbol(moduleSymbol)) { - error(node.moduleSpecifier, Diagnostics.Module_0_uses_export_and_cannot_be_used_with_export_Asterisk, symbolToString(moduleSymbol)); - } - else if (node.exportClause) { - checkAliasSymbol(node.exportClause); - } - if (moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { - if (node.exportClause) { - // export * as ns from "foo"; - // For ES2015 modules, we emit it as a pair of `import * as a_1 ...; export { a_1 as ns }` and don't need the helper. - // We only use the helper here when in esModuleInterop - if (getESModuleInterop(compilerOptions)) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); - } - } - else { - // export * from "foo" - checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar); - } + + const enumDeclaration = declaration as EnumDeclaration; + if (!enumDeclaration.members.length) { + return false; + } + + const firstEnumMember = enumDeclaration.members[0]; + if (!firstEnumMember.initializer) { + if (seenEnumMissingInitialInitializer) { + error(firstEnumMember.name, Diagnostics.In_an_enum_with_multiple_declarations_only_one_declaration_can_omit_an_initializer_for_its_first_enum_element); + } + else { + seenEnumMissingInitialInitializer = true; } } - } - checkAssertClause(node); + }); } + } - function checkGrammarExportDeclaration(node: ExportDeclaration): boolean { - if (node.isTypeOnly) { - if (node.exportClause?.kind === SyntaxKind.NamedExports) { - return checkGrammarNamedImportsOrExports(node.exportClause); - } - else { - return grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type); - } - } - return false; + function checkEnumMember(node: EnumMember) { + if (isPrivateIdentifier(node.name)) { + error(node, Diagnostics.An_enum_member_cannot_be_named_with_a_private_identifier); } + } - function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean { - const isInAppropriateContext = node.parent.kind === SyntaxKind.SourceFile || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.ModuleDeclaration; - if (!isInAppropriateContext) { - grammarErrorOnFirstToken(node, errorMessage); + function getFirstNonAmbientClassOrFunctionDeclaration(symbol: Symbol): Declaration | undefined { + const declarations = symbol.declarations; + if (declarations) { + for (const declaration of declarations) { + if ((declaration.kind === SyntaxKind.ClassDeclaration || + (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((declaration as FunctionLikeDeclaration).body))) && + !(declaration.flags & NodeFlags.Ambient)) { + return declaration; + } } - return !isInAppropriateContext; } + return undefined; + } - function importClauseContainsReferencedImport(importClause: ImportClause) { - return forEachImportClauseDeclaration(importClause, declaration => { - return !!getSymbolOfNode(declaration).isReferenced; - }); + function inSameLexicalScope(node1: Node, node2: Node) { + const container1 = getEnclosingBlockScopeContainer(node1); + const container2 = getEnclosingBlockScopeContainer(node2); + if (isGlobalSourceFile(container1)) { + return isGlobalSourceFile(container2); } - - function importClauseContainsConstEnumUsedAsValue(importClause: ImportClause) { - return forEachImportClauseDeclaration(importClause, declaration => { - return !!getSymbolLinks(getSymbolOfNode(declaration)).constEnumReferenced; - }); + else if (isGlobalSourceFile(container2)) { + return false; } - - function canConvertImportDeclarationToTypeOnly(statement: Statement) { - return isImportDeclaration(statement) && - statement.importClause && - !statement.importClause.isTypeOnly && - importClauseContainsReferencedImport(statement.importClause) && - !isReferencedAliasDeclaration(statement.importClause, /*checkChildren*/ true) && - !importClauseContainsConstEnumUsedAsValue(statement.importClause); + else { + return container1 === container2; } + } - function canConvertImportEqualsDeclarationToTypeOnly(statement: Statement) { - return isImportEqualsDeclaration(statement) && - isExternalModuleReference(statement.moduleReference) && - !statement.isTypeOnly && - getSymbolOfNode(statement).isReferenced && - !isReferencedAliasDeclaration(statement, /*checkChildren*/ false) && - !getSymbolLinks(getSymbolOfNode(statement)).constEnumReferenced; - } + function checkModuleDeclaration(node: ModuleDeclaration) { + if (produceDiagnostics) { + // Grammar checking + const isGlobalAugmentation = isGlobalScopeAugmentation(node); + const inAmbientContext = node.flags & NodeFlags.Ambient; + if (isGlobalAugmentation && !inAmbientContext) { + error(node.name, Diagnostics.Augmentations_for_the_global_scope_should_have_declare_modifier_unless_they_appear_in_already_ambient_context); + } + + const isAmbientExternalModule: boolean = isAmbientModule(node); + const contextErrorMessage = isAmbientExternalModule + ? Diagnostics.An_ambient_module_declaration_is_only_allowed_at_the_top_level_in_a_file + : Diagnostics.A_namespace_declaration_is_only_allowed_in_a_namespace_or_module; + if (checkGrammarModuleElementContext(node, contextErrorMessage)) { + // If we hit a module declaration in an illegal context, just bail out to avoid cascading errors. + return; + } - function checkImportsForTypeOnlyConversion(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (canConvertImportDeclarationToTypeOnly(statement) || canConvertImportEqualsDeclarationToTypeOnly(statement)) { - error( - statement, - Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_importsNotUsedAsValues_is_set_to_error); + if (!checkGrammarDecoratorsAndModifiers(node)) { + if (!inAmbientContext && node.name.kind === SyntaxKind.StringLiteral) { + grammarErrorOnNode(node.name, Diagnostics.Only_ambient_modules_can_use_quoted_names); } } - } - function checkExportSpecifier(node: ExportSpecifier) { - checkAliasSymbol(node); - if (getEmitDeclarations(compilerOptions)) { - collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); + if (isIdentifier(node.name)) { + checkCollisionsForDeclarationName(node, node.name); } - if (!node.parent.parent.moduleSpecifier) { - const exportedName = node.propertyName || node.name; - // find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases) - const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, - /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); - if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { - error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); - } - else { - markExportAsReferenced(node); - const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); - if (!target || target === unknownSymbol || target.flags & SymbolFlags.Value) { - checkExpressionCached(node.propertyName || node.name); + + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfNode(node); + + // The following checks only apply on a non-ambient instantiated module declaration. + if (symbol.flags & SymbolFlags.ValueModule + && !inAmbientContext + && symbol.declarations + && symbol.declarations.length > 1 + && isInstantiatedModule(node, shouldPreserveConstEnums(compilerOptions))) { + const firstNonAmbientClassOrFunc = getFirstNonAmbientClassOrFunctionDeclaration(symbol); + if (firstNonAmbientClassOrFunc) { + if (getSourceFileOfNode(node) !== getSourceFileOfNode(firstNonAmbientClassOrFunc)) { + error(node.name, Diagnostics.A_namespace_declaration_cannot_be_in_a_different_file_from_a_class_or_function_with_which_it_is_merged); + } + else if (node.pos < firstNonAmbientClassOrFunc.pos) { + error(node.name, Diagnostics.A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged); } } - } - else { - if (getESModuleInterop(compilerOptions) && - moduleKind !== ModuleKind.System && - (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && - idText(node.propertyName || node.name) === "default") { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); - } - } - } - function checkExportAssignment(node: ExportAssignment) { - const illegalContextMessage = node.isExportEquals - ? Diagnostics.An_export_assignment_must_be_at_the_top_level_of_a_file_or_module_declaration - : Diagnostics.A_default_export_must_be_at_the_top_level_of_a_file_or_module_declaration; - if (checkGrammarModuleElementContext(node, illegalContextMessage)) { - // If we hit an export assignment in an illegal context, just bail out to avoid cascading errors. - return; + // if the module merges with a class declaration in the same lexical scope, + // we need to track this to ensure the correct emit. + const mergedClass = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration); + if (mergedClass && + inSameLexicalScope(node, mergedClass)) { + getNodeLinks(node).flags |= NodeCheckFlags.LexicalModuleMergesWithClass; + } } - const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent as ModuleDeclaration; - if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { - if (node.isExportEquals) { - error(node, Diagnostics.An_export_assignment_cannot_be_used_in_a_namespace); + if (isAmbientExternalModule) { + if (isExternalModuleAugmentation(node)) { + // body of the augmentation should be checked for consistency only if augmentation was applied to its target (either global scope or module) + // otherwise we'll be swamped in cascading errors. + // We can detect if augmentation was applied using following rules: + // - augmentation for a global scope is always applied + // - augmentation for some external module is applied if symbol for augmentation is merged (it was combined with target module). + const checkBody = isGlobalAugmentation || (getSymbolOfNode(node).flags & SymbolFlags.Transient); + if (checkBody && node.body) { + for (const statement of node.body.statements) { + checkModuleAugmentationElement(statement, isGlobalAugmentation); + } + } + } + else if (isGlobalSourceFile(node.parent)) { + if (isGlobalAugmentation) { + error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); + } + else if (isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(node.name))) { + error(node.name, Diagnostics.Ambient_module_declaration_cannot_specify_relative_module_name); + } } else { - error(node, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); + if (isGlobalAugmentation) { + error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); + } + else { + // Node is not an augmentation and is not located on the script level. + // This means that this is declaration of ambient module that is located in other module or namespace which is prohibited. + error(node.name, Diagnostics.Ambient_modules_cannot_be_nested_in_other_modules_or_namespaces); + } } - - return; - } - // Grammar checking - if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { - grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers); } + } - const typeAnnotationNode = getEffectiveTypeAnnotationNode(node); - if (typeAnnotationNode) { - checkTypeAssignableTo(checkExpressionCached(node.expression), getTypeFromTypeNode(typeAnnotationNode), node.expression); + if (node.body) { + checkSourceElement(node.body); + if (!isGlobalScopeAugmentation(node)) { + registerForUnusedIdentifiersCheck(node); } + } + } - if (node.expression.kind === SyntaxKind.Identifier) { - const id = node.expression as Identifier; - const sym = resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node); - if (sym) { - markAliasReferenced(sym, id); - // If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`) - const target = sym.flags & SymbolFlags.Alias ? resolveAlias(sym) : sym; - if (target === unknownSymbol || target.flags & SymbolFlags.Value) { - // However if it is a value, we need to check it's being used correctly - checkExpressionCached(node.expression); + function checkModuleAugmentationElement(node: Node, isGlobalAugmentation: boolean): void { + switch (node.kind) { + case SyntaxKind.VariableStatement: + // error each individual name in variable statement instead of marking the entire variable statement + for (const decl of (node as VariableStatement).declarationList.declarations) { + checkModuleAugmentationElement(decl, isGlobalAugmentation); + } + break; + case SyntaxKind.ExportAssignment: + case SyntaxKind.ExportDeclaration: + grammarErrorOnFirstToken(node, Diagnostics.Exports_and_export_assignments_are_not_permitted_in_module_augmentations); + break; + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportDeclaration: + grammarErrorOnFirstToken(node, Diagnostics.Imports_are_not_permitted_in_module_augmentations_Consider_moving_them_to_the_enclosing_external_module); + break; + case SyntaxKind.BindingElement: + case SyntaxKind.VariableDeclaration: + const name = (node as VariableDeclaration | BindingElement).name; + if (isBindingPattern(name)) { + for (const el of name.elements) { + // mark individual names in binding pattern + checkModuleAugmentationElement(el, isGlobalAugmentation); } + break; } - else { - checkExpressionCached(node.expression); // doesn't resolve, check as expression to mark as error + // falls through + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.TypeAliasDeclaration: + if (isGlobalAugmentation) { + return; + } + const symbol = getSymbolOfNode(node); + if (symbol) { + // module augmentations cannot introduce new names on the top level scope of the module + // this is done it two steps + // 1. quick check - if symbol for node is not merged - this is local symbol to this augmentation - report error + // 2. main check - report error if value declaration of the parent symbol is module augmentation) + let reportError = !(symbol.flags & SymbolFlags.Transient); + if (!reportError) { + // symbol should not originate in augmentation + reportError = !!symbol.parent?.declarations && isExternalModuleAugmentation(symbol.parent.declarations[0]); + } } + break; + } + } + + function getFirstNonModuleExportsIdentifier(node: EntityNameOrEntityNameExpression): Identifier { + switch (node.kind) { + case SyntaxKind.Identifier: + return node; + case SyntaxKind.QualifiedName: + do { + node = node.left; + } while (node.kind !== SyntaxKind.Identifier); + return node; + case SyntaxKind.PropertyAccessExpression: + do { + if (isModuleExportsAccessExpression(node.expression) && !isPrivateIdentifier(node.name)) { + return node.name; + } + node = node.expression; + } while (node.kind !== SyntaxKind.Identifier); + return node; + } + } + + function checkExternalImportOrExportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean { + const moduleName = getExternalModuleName(node); + if (!moduleName || nodeIsMissing(moduleName)) { + // Should be a parse error. + return false; + } + if (!isStringLiteral(moduleName)) { + error(moduleName, Diagnostics.String_literal_expected); + return false; + } + const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); + if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule) { + error(moduleName, node.kind === SyntaxKind.ExportDeclaration ? + Diagnostics.Export_declarations_are_not_permitted_in_a_namespace : + Diagnostics.Import_declarations_in_a_namespace_cannot_reference_a_module); + return false; + } + if (inAmbientExternalModule && isExternalModuleNameRelative(moduleName.text)) { + // we have already reported errors on top level imports/exports in external module augmentations in checkModuleDeclaration + // no need to do this again. + if (!isTopLevelInExternalModuleAugmentation(node)) { + // TypeScript 1.0 spec (April 2013): 12.1.6 + // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference + // other external modules only through top - level external module names. + // Relative external module names are not permitted. + error(node, Diagnostics.Import_or_export_declaration_in_an_ambient_module_declaration_cannot_reference_module_through_relative_module_name); + return false; + } + } + return true; + } - if (getEmitDeclarations(compilerOptions)) { - collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true); + function checkAliasSymbol(node: ImportEqualsDeclaration | VariableDeclaration | ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier | NamespaceExport) { + let symbol = getSymbolOfNode(node); + const target = resolveAlias(symbol); + + if (target !== unknownSymbol) { + // For external modules, `symbol` represents the local symbol for an alias. + // This local symbol will merge any other local declarations (excluding other aliases) + // and symbol.flags will contains combined representation for all merged declaration. + // Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have, + // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* + // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). + symbol = getMergedSymbol(symbol.exportSymbol || symbol); + const excludedMeanings = + (symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) | + (symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) | + (symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0); + if (target.flags & excludedMeanings) { + const message = node.kind === SyntaxKind.ExportSpecifier ? + Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 : + Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0; + error(node, message, symbolToString(symbol)); + } + + if (compilerOptions.isolatedModules + && !isTypeOnlyImportOrExportDeclaration(node) + && !(node.flags & NodeFlags.Ambient)) { + const typeOnlyAlias = getTypeOnlyAliasDeclaration(symbol); + const isType = !(target.flags & SymbolFlags.Value); + if (isType || typeOnlyAlias) { + switch (node.kind) { + case SyntaxKind.ImportClause: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportEqualsDeclaration: { + if (compilerOptions.preserveValueImports) { + Debug.assertIsDefined(node.name, "An ImportClause with a symbol should have a name"); + const message = isType + ? Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled + : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled; + const name = idText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name); + addTypeOnlyDeclarationRelatedInfo( + error(node, message, name), + isType ? undefined : typeOnlyAlias, + name + ); + } + break; + } + case SyntaxKind.ExportSpecifier: { + // Don't allow re-exporting an export that will be elided when `--isolatedModules` is set. + // The exception is that `import type { A } from './a'; export { A }` is allowed + // because single-file analysis can determine that the export should be dropped. + if (getSourceFileOfNode(typeOnlyAlias) !== getSourceFileOfNode(node)) { + const message = isType + ? Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type + : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_isolatedModules_is_enabled; + const name = idText(node.propertyName || node.name); + addTypeOnlyDeclarationRelatedInfo( + error(node, message, name), + isType ? undefined : typeOnlyAlias, + name + ); + return; + } + } + } } } - else { - checkExpressionCached(node.expression); + + if (isImportSpecifier(node) && target.declarations?.every(d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated))) { + addDeprecatedSuggestion(node.name, target.declarations, symbol.escapedName as string); } + } + } - checkExternalModuleExports(container); + function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) { + checkCollisionsForDeclarationName(node, node.name); + checkAliasSymbol(node); + if (node.kind === SyntaxKind.ImportSpecifier && + idText(node.propertyName || node.name) === "default" && + getESModuleInterop(compilerOptions) && + moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); + } + } - if ((node.flags & NodeFlags.Ambient) && !isEntityNameExpression(node.expression)) { - grammarErrorOnNode(node.expression, Diagnostics.The_expression_of_an_export_assignment_must_be_an_identifier_or_qualified_name_in_an_ambient_context); + function checkAssertClause(declaration: ImportDeclaration | ExportDeclaration) { + if (declaration.assertClause) { + const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier); + if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext) { + return grammarErrorOnNode(declaration.assertClause, + moduleKind === ModuleKind.NodeNext + ? Diagnostics.Import_assertions_are_not_allowed_on_statements_that_transpile_to_commonjs_require_calls + : Diagnostics.Import_assertions_are_only_supported_when_the_module_option_is_set_to_esnext_or_nodenext); } - if (node.isExportEquals && !(node.flags & NodeFlags.Ambient)) { - if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.CommonJS) { - // export assignment is not supported in es6 modules - grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_export_default_or_another_module_format_instead); - } - else if (moduleKind === ModuleKind.System) { - // system modules does not support export assignment - grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system); - } + if (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly) { + return grammarErrorOnNode(declaration.assertClause, Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports); } } + } - function hasExportedMembers(moduleSymbol: Symbol) { - return forEachEntry(moduleSymbol.exports!, (_, id) => id !== "export="); + function checkImportDeclaration(node: ImportDeclaration) { + if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) { + // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. + return; } - - function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { - const moduleSymbol = getSymbolOfNode(node); - const links = getSymbolLinks(moduleSymbol); - if (!links.exportsChecked) { - const exportEqualsSymbol = moduleSymbol.exports!.get("export=" as __String); - if (exportEqualsSymbol && hasExportedMembers(moduleSymbol)) { - const declaration = getDeclarationOfAliasSymbol(exportEqualsSymbol) || exportEqualsSymbol.valueDeclaration; - if (declaration && !isTopLevelInExternalModuleAugmentation(declaration) && !isInJSFile(declaration)) { - error(declaration, Diagnostics.An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements); - } + if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_import_declaration_cannot_have_modifiers); + } + if (checkExternalImportOrExportDeclaration(node)) { + const importClause = node.importClause; + if (importClause && !checkGrammarImportClause(importClause)) { + if (importClause.name) { + checkImportBinding(importClause); } - // Checks for export * conflicts - const exports = getExportsOfModule(moduleSymbol); - if (exports) { - exports.forEach(({ declarations, flags }, id) => { - if (id === "__export") { - return; - } - // ECMA262: 15.2.1.1 It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. - // (TS Exceptions: namespaces, function overloads, enums, and interfaces) - if (flags & (SymbolFlags.Namespace | SymbolFlags.Interface | SymbolFlags.Enum)) { - return; - } - const exportedDeclarationsCount = countWhere(declarations, isNotOverloadAndNotAccessor); - if (flags & SymbolFlags.TypeAlias && exportedDeclarationsCount <= 2) { - // it is legal to merge type alias with other values - // so count should be either 1 (just type alias) or 2 (type alias + merged value) - return; + if (importClause.namedBindings) { + if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + checkImportBinding(importClause.namedBindings); + if (moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && getESModuleInterop(compilerOptions)) { + // import * as ns from "foo"; + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); } - if (exportedDeclarationsCount > 1) { - if (!isDuplicatedCommonJSExport(declarations)) { - for (const declaration of declarations!) { - if (isNotOverload(declaration)) { - diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Cannot_redeclare_exported_variable_0, unescapeLeadingUnderscores(id))); - } - } - } + } + else { + const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier); + if (moduleExisted) { + forEach(importClause.namedBindings.elements, checkImportBinding); } - }); + } } - links.exportsChecked = true; } } + checkAssertClause(node); + } - function isDuplicatedCommonJSExport(declarations: Declaration[] | undefined) { - return declarations - && declarations.length > 1 - && declarations.every(d => isInJSFile(d) && isAccessExpression(d) && (isExportsIdentifier(d.expression) || isModuleExportsAccessExpression(d.expression))); - } - - function checkSourceElement(node: Node | undefined): void { - if (node) { - const saveCurrentNode = currentNode; - currentNode = node; - instantiationCount = 0; - checkSourceElementWorker(node); - currentNode = saveCurrentNode; - } + function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) { + if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) { + // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. + return; } - function checkSourceElementWorker(node: Node): void { - if (isInJSFile(node)) { - forEach((node as JSDocContainer).jsDoc, ({ tags }) => forEach(tags, checkSourceElement)); + checkGrammarDecoratorsAndModifiers(node); + if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { + checkImportBinding(node); + if (hasSyntacticModifier(node, ModifierFlags.Export)) { + markExportAsReferenced(node); } - - const kind = node.kind; - if (cancellationToken) { - // Only bother checking on a few construct kinds. We don't want to be excessively - // hitting the cancellation token on every node we check. - switch (kind) { - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.FunctionDeclaration: - cancellationToken.throwIfCancellationRequested(); + if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { + const target = resolveAlias(getSymbolOfNode(node)); + if (target !== unknownSymbol) { + if (target.flags & SymbolFlags.Value) { + // Target is a value symbol, check that it is not hidden by a local declaration with the same name + const moduleName = getFirstIdentifier(node.moduleReference); + if (!(resolveEntityName(moduleName, SymbolFlags.Value | SymbolFlags.Namespace)!.flags & SymbolFlags.Namespace)) { + error(moduleName, Diagnostics.Module_0_is_hidden_by_a_local_declaration_with_the_same_name, declarationNameToString(moduleName)); + } + } + if (target.flags & SymbolFlags.Type) { + checkTypeNameIsReserved(node.name, Diagnostics.Import_name_cannot_be_0); + } + } + if (node.isTypeOnly) { + grammarErrorOnNode(node, Diagnostics.An_import_alias_cannot_use_import_type); } } - if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && node.flowNode && !isReachableFlowNode(node.flowNode)) { - errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); + else { + if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat === undefined && !node.isTypeOnly && !(node.flags & NodeFlags.Ambient)) { + // Import equals declaration is deprecated in es6 or above + grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead); + } } + } + } - switch (kind) { - case SyntaxKind.TypeParameter: - return checkTypeParameter(node as TypeParameterDeclaration); - case SyntaxKind.Parameter: - return checkParameter(node as ParameterDeclaration); - case SyntaxKind.PropertyDeclaration: - return checkPropertyDeclaration(node as PropertyDeclaration); - case SyntaxKind.PropertySignature: - return checkPropertySignature(node as PropertySignature); - case SyntaxKind.ConstructorType: - case SyntaxKind.FunctionType: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - return checkSignatureDeclaration(node as SignatureDeclaration); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return checkMethodDeclaration(node as MethodDeclaration | MethodSignature); - case SyntaxKind.ClassStaticBlockDeclaration: - return checkClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); - case SyntaxKind.Constructor: - return checkConstructorDeclaration(node as ConstructorDeclaration); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return checkAccessorDeclaration(node as AccessorDeclaration); - case SyntaxKind.TypeReference: - return checkTypeReferenceNode(node as TypeReferenceNode); - case SyntaxKind.TypePredicate: - return checkTypePredicate(node as TypePredicateNode); - case SyntaxKind.TypeQuery: - return checkTypeQuery(node as TypeQueryNode); - case SyntaxKind.TypeLiteral: - return checkTypeLiteral(node as TypeLiteralNode); - case SyntaxKind.ArrayType: - return checkArrayType(node as ArrayTypeNode); - case SyntaxKind.TupleType: - return checkTupleType(node as TupleTypeNode); - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - return checkUnionOrIntersectionType(node as UnionOrIntersectionTypeNode); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.OptionalType: - case SyntaxKind.RestType: - return checkSourceElement((node as ParenthesizedTypeNode | OptionalTypeNode | RestTypeNode).type); - case SyntaxKind.ThisType: - return checkThisType(node as ThisTypeNode); - case SyntaxKind.TypeOperator: - return checkTypeOperator(node as TypeOperatorNode); - case SyntaxKind.ConditionalType: - return checkConditionalType(node as ConditionalTypeNode); - case SyntaxKind.InferType: - return checkInferType(node as InferTypeNode); - case SyntaxKind.TemplateLiteralType: - return checkTemplateLiteralType(node as TemplateLiteralTypeNode); - case SyntaxKind.ImportType: - return checkImportType(node as ImportTypeNode); - case SyntaxKind.NamedTupleMember: - return checkNamedTupleMember(node as NamedTupleMember); - case SyntaxKind.JSDocAugmentsTag: - return checkJSDocAugmentsTag(node as JSDocAugmentsTag); - case SyntaxKind.JSDocImplementsTag: - return checkJSDocImplementsTag(node as JSDocImplementsTag); - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - return checkJSDocTypeAliasTag(node as JSDocTypedefTag); - case SyntaxKind.JSDocTemplateTag: - return checkJSDocTemplateTag(node as JSDocTemplateTag); - case SyntaxKind.JSDocTypeTag: - return checkJSDocTypeTag(node as JSDocTypeTag); - case SyntaxKind.JSDocParameterTag: - return checkJSDocParameterTag(node as JSDocParameterTag); - case SyntaxKind.JSDocPropertyTag: - return checkJSDocPropertyTag(node as JSDocPropertyTag); - case SyntaxKind.JSDocFunctionType: - checkJSDocFunctionType(node as JSDocFunctionType); - // falls through - case SyntaxKind.JSDocNonNullableType: - case SyntaxKind.JSDocNullableType: - case SyntaxKind.JSDocAllType: - case SyntaxKind.JSDocUnknownType: - case SyntaxKind.JSDocTypeLiteral: - checkJSDocTypeIsInJsFile(node); - forEachChild(node, checkSourceElement); - return; - case SyntaxKind.JSDocVariadicType: - checkJSDocVariadicType(node as JSDocVariadicType); - return; - case SyntaxKind.JSDocTypeExpression: - return checkSourceElement((node as JSDocTypeExpression).type); - case SyntaxKind.JSDocPublicTag: - case SyntaxKind.JSDocProtectedTag: - case SyntaxKind.JSDocPrivateTag: - return checkJSDocAccessibilityModifiers(node as JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag); - case SyntaxKind.IndexedAccessType: - return checkIndexedAccessType(node as IndexedAccessTypeNode); - case SyntaxKind.MappedType: - return checkMappedType(node as MappedTypeNode); - case SyntaxKind.FunctionDeclaration: - return checkFunctionDeclaration(node as FunctionDeclaration); - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - return checkBlock(node as Block); - case SyntaxKind.VariableStatement: - return checkVariableStatement(node as VariableStatement); - case SyntaxKind.ExpressionStatement: - return checkExpressionStatement(node as ExpressionStatement); - case SyntaxKind.IfStatement: - return checkIfStatement(node as IfStatement); - case SyntaxKind.DoStatement: - return checkDoStatement(node as DoStatement); - case SyntaxKind.WhileStatement: - return checkWhileStatement(node as WhileStatement); - case SyntaxKind.ForStatement: - return checkForStatement(node as ForStatement); - case SyntaxKind.ForInStatement: - return checkForInStatement(node as ForInStatement); - case SyntaxKind.ForOfStatement: - return checkForOfStatement(node as ForOfStatement); - case SyntaxKind.ContinueStatement: - case SyntaxKind.BreakStatement: - return checkBreakOrContinueStatement(node as BreakOrContinueStatement); - case SyntaxKind.ReturnStatement: - return checkReturnStatement(node as ReturnStatement); - case SyntaxKind.WithStatement: - return checkWithStatement(node as WithStatement); - case SyntaxKind.SwitchStatement: - return checkSwitchStatement(node as SwitchStatement); - case SyntaxKind.LabeledStatement: - return checkLabeledStatement(node as LabeledStatement); - case SyntaxKind.ThrowStatement: - return checkThrowStatement(node as ThrowStatement); - case SyntaxKind.TryStatement: - return checkTryStatement(node as TryStatement); - case SyntaxKind.VariableDeclaration: - return checkVariableDeclaration(node as VariableDeclaration); - case SyntaxKind.BindingElement: - return checkBindingElement(node as BindingElement); - case SyntaxKind.ClassDeclaration: - return checkClassDeclaration(node as ClassDeclaration); - case SyntaxKind.InterfaceDeclaration: - return checkInterfaceDeclaration(node as InterfaceDeclaration); - case SyntaxKind.TypeAliasDeclaration: - return checkTypeAliasDeclaration(node as TypeAliasDeclaration); - case SyntaxKind.EnumDeclaration: - return checkEnumDeclaration(node as EnumDeclaration); - case SyntaxKind.ModuleDeclaration: - return checkModuleDeclaration(node as ModuleDeclaration); - case SyntaxKind.ImportDeclaration: - return checkImportDeclaration(node as ImportDeclaration); - case SyntaxKind.ImportEqualsDeclaration: - return checkImportEqualsDeclaration(node as ImportEqualsDeclaration); - case SyntaxKind.ExportDeclaration: - return checkExportDeclaration(node as ExportDeclaration); - case SyntaxKind.ExportAssignment: - return checkExportAssignment(node as ExportAssignment); - case SyntaxKind.EmptyStatement: - case SyntaxKind.DebuggerStatement: - checkGrammarStatementInAmbientContext(node); - return; - case SyntaxKind.MissingDeclaration: - return checkMissingDeclaration(node); + function checkExportDeclaration(node: ExportDeclaration) { + if (checkGrammarModuleElementContext(node, Diagnostics.An_export_declaration_can_only_be_used_in_a_module)) { + // If we hit an export in an illegal context, just bail out to avoid cascading errors. + return; + } + + if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers); + } + + if (node.moduleSpecifier && node.exportClause && isNamedExports(node.exportClause) && length(node.exportClause.elements) && languageVersion === ScriptTarget.ES3) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.CreateBinding); + } + + checkGrammarExportDeclaration(node); + if (!node.moduleSpecifier || checkExternalImportOrExportDeclaration(node)) { + if (node.exportClause && !isNamespaceExport(node.exportClause)) { + // export { x, y } + // export { x, y } from "foo" + forEach(node.exportClause.elements, checkExportSpecifier); + const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); + const inAmbientNamespaceDeclaration = !inAmbientExternalModule && node.parent.kind === SyntaxKind.ModuleBlock && + !node.moduleSpecifier && node.flags & NodeFlags.Ambient; + if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule && !inAmbientNamespaceDeclaration) { + error(node, Diagnostics.Export_declarations_are_not_permitted_in_a_namespace); + } + } + else { + // export * from "foo" + // export * as ns from "foo"; + const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!); + if (moduleSymbol && hasExportAssignmentSymbol(moduleSymbol)) { + error(node.moduleSpecifier, Diagnostics.Module_0_uses_export_and_cannot_be_used_with_export_Asterisk, symbolToString(moduleSymbol)); + } + else if (node.exportClause) { + checkAliasSymbol(node.exportClause); + } + if (moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { + if (node.exportClause) { + // export * as ns from "foo"; + // For ES2015 modules, we emit it as a pair of `import * as a_1 ...; export { a_1 as ns }` and don't need the helper. + // We only use the helper here when in esModuleInterop + if (getESModuleInterop(compilerOptions)) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); + } + } + else { + // export * from "foo" + checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar); + } + } } } + checkAssertClause(node); + } - function checkJSDocTypeIsInJsFile(node: Node): void { - if (!isInJSFile(node)) { - grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + function checkGrammarExportDeclaration(node: ExportDeclaration): boolean { + if (node.isTypeOnly) { + if (node.exportClause?.kind === SyntaxKind.NamedExports) { + return checkGrammarNamedImportsOrExports(node.exportClause); + } + else { + return grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type); } } + return false; + } - function checkJSDocVariadicType(node: JSDocVariadicType): void { - checkJSDocTypeIsInJsFile(node); - checkSourceElement(node.type); + function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean { + const isInAppropriateContext = node.parent.kind === SyntaxKind.SourceFile || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.ModuleDeclaration; + if (!isInAppropriateContext) { + grammarErrorOnFirstToken(node, errorMessage); + } + return !isInAppropriateContext; + } - // Only legal location is in the *last* parameter tag or last parameter of a JSDoc function. - const { parent } = node; - if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { - if (last(parent.parent.parameters) !== parent) { - error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); - } - return; + function importClauseContainsReferencedImport(importClause: ImportClause) { + return forEachImportClauseDeclaration(importClause, declaration => { + return !!getSymbolOfNode(declaration).isReferenced; + }); + } + + function importClauseContainsConstEnumUsedAsValue(importClause: ImportClause) { + return forEachImportClauseDeclaration(importClause, declaration => { + return !!getSymbolLinks(getSymbolOfNode(declaration)).constEnumReferenced; + }); + } + + function canConvertImportDeclarationToTypeOnly(statement: Statement) { + return isImportDeclaration(statement) && + statement.importClause && + !statement.importClause.isTypeOnly && + importClauseContainsReferencedImport(statement.importClause) && + !isReferencedAliasDeclaration(statement.importClause, /*checkChildren*/ true) && + !importClauseContainsConstEnumUsedAsValue(statement.importClause); + } + + function canConvertImportEqualsDeclarationToTypeOnly(statement: Statement) { + return isImportEqualsDeclaration(statement) && + isExternalModuleReference(statement.moduleReference) && + !statement.isTypeOnly && + getSymbolOfNode(statement).isReferenced && + !isReferencedAliasDeclaration(statement, /*checkChildren*/ false) && + !getSymbolLinks(getSymbolOfNode(statement)).constEnumReferenced; + } + + function checkImportsForTypeOnlyConversion(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (canConvertImportDeclarationToTypeOnly(statement) || canConvertImportEqualsDeclarationToTypeOnly(statement)) { + error( + statement, + Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_importsNotUsedAsValues_is_set_to_error); } + } + } - if (!isJSDocTypeExpression(parent)) { - error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); + function checkExportSpecifier(node: ExportSpecifier) { + checkAliasSymbol(node); + if (getEmitDeclarations(compilerOptions)) { + collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); + } + if (!node.parent.parent.moduleSpecifier) { + const exportedName = node.propertyName || node.name; + // find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases) + const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, + /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { + error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); + } + else { + markExportAsReferenced(node); + const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); + if (!target || target === unknownSymbol || target.flags & SymbolFlags.Value) { + checkExpressionCached(node.propertyName || node.name); + } + } + } + else { + if (getESModuleInterop(compilerOptions) && + moduleKind !== ModuleKind.System && + (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && + idText(node.propertyName || node.name) === "default") { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); } + } + } - const paramTag = node.parent.parent; - if (!isJSDocParameterTag(paramTag)) { - error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); - return; + function checkExportAssignment(node: ExportAssignment) { + const illegalContextMessage = node.isExportEquals + ? Diagnostics.An_export_assignment_must_be_at_the_top_level_of_a_file_or_module_declaration + : Diagnostics.A_default_export_must_be_at_the_top_level_of_a_file_or_module_declaration; + if (checkGrammarModuleElementContext(node, illegalContextMessage)) { + // If we hit an export assignment in an illegal context, just bail out to avoid cascading errors. + return; + } + + const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent as ModuleDeclaration; + if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { + if (node.isExportEquals) { + error(node, Diagnostics.An_export_assignment_cannot_be_used_in_a_namespace); + } + else { + error(node, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); } - const param = getParameterSymbolFromJSDoc(paramTag); - if (!param) { - // We will error in `checkJSDocParameterTag`. - return; + return; + } + // Grammar checking + if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers); + } + + const typeAnnotationNode = getEffectiveTypeAnnotationNode(node); + if (typeAnnotationNode) { + checkTypeAssignableTo(checkExpressionCached(node.expression), getTypeFromTypeNode(typeAnnotationNode), node.expression); + } + + if (node.expression.kind === SyntaxKind.Identifier) { + const id = node.expression as Identifier; + const sym = resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node); + if (sym) { + markAliasReferenced(sym, id); + // If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`) + const target = sym.flags & SymbolFlags.Alias ? resolveAlias(sym) : sym; + if (target === unknownSymbol || target.flags & SymbolFlags.Value) { + // However if it is a value, we need to check it's being used correctly + checkExpressionCached(node.expression); + } + } + else { + checkExpressionCached(node.expression); // doesn't resolve, check as expression to mark as error } - const host = getHostSignatureFromJSDoc(paramTag); - if (!host || last(host.parameters).symbol !== param) { - error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); + if (getEmitDeclarations(compilerOptions)) { + collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true); } } + else { + checkExpressionCached(node.expression); + } - function getTypeFromJSDocVariadicType(node: JSDocVariadicType): Type { - const type = getTypeFromTypeNode(node.type); - const { parent } = node; - const paramTag = node.parent.parent; - if (isJSDocTypeExpression(node.parent) && isJSDocParameterTag(paramTag)) { - // Else we will add a diagnostic, see `checkJSDocVariadicType`. - const host = getHostSignatureFromJSDoc(paramTag); - const isCallbackTag = isJSDocCallbackTag(paramTag.parent.parent); - if (host || isCallbackTag) { - /* - Only return an array type if the corresponding parameter is marked as a rest parameter, or if there are no parameters. - So in the following situation we will not create an array type: - /** @param {...number} a * / - function f(a) {} - Because `a` will just be of type `number | undefined`. A synthetic `...args` will also be added, which *will* get an array type. - */ - const lastParamDeclaration = isCallbackTag - ? lastOrUndefined((paramTag.parent.parent as unknown as JSDocCallbackTag).typeExpression.parameters) - : lastOrUndefined(host!.parameters); - const symbol = getParameterSymbolFromJSDoc(paramTag); - if (!lastParamDeclaration || - symbol && lastParamDeclaration.symbol === symbol && isRestParameter(lastParamDeclaration)) { - return createArrayType(type); - } - } - } - if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { - return createArrayType(type); - } - return addOptionality(type); - } - - // Function and class expression bodies are checked after all statements in the enclosing body. This is - // to ensure constructs like the following are permitted: - // const foo = function () { - // const s = foo(); - // return "hello"; - // } - // Here, performing a full type check of the body of the function expression whilst in the process of - // determining the type of foo would cause foo to be given type any because of the recursive reference. - // Delaying the type check of the body ensures foo has been assigned a type. - function checkNodeDeferred(node: Node) { - const enclosingFile = getSourceFileOfNode(node); - const links = getNodeLinks(enclosingFile); - if (!(links.flags & NodeCheckFlags.TypeChecked)) { - links.deferredNodes = links.deferredNodes || new Map(); - const id = getNodeId(node); - links.deferredNodes.set(id, node); + checkExternalModuleExports(container); + + if ((node.flags & NodeFlags.Ambient) && !isEntityNameExpression(node.expression)) { + grammarErrorOnNode(node.expression, Diagnostics.The_expression_of_an_export_assignment_must_be_an_identifier_or_qualified_name_in_an_ambient_context); + } + + if (node.isExportEquals && !(node.flags & NodeFlags.Ambient)) { + if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.CommonJS) { + // export assignment is not supported in es6 modules + grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_export_default_or_another_module_format_instead); + } + else if (moduleKind === ModuleKind.System) { + // system modules does not support export assignment + grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system); } } + } + + function hasExportedMembers(moduleSymbol: Symbol) { + return forEachEntry(moduleSymbol.exports!, (_, id) => id !== "export="); + } - function checkDeferredNodes(context: SourceFile) { - const links = getNodeLinks(context); - if (links.deferredNodes) { - links.deferredNodes.forEach(checkDeferredNode); + function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { + const moduleSymbol = getSymbolOfNode(node); + const links = getSymbolLinks(moduleSymbol); + if (!links.exportsChecked) { + const exportEqualsSymbol = moduleSymbol.exports!.get("export=" as __String); + if (exportEqualsSymbol && hasExportedMembers(moduleSymbol)) { + const declaration = getDeclarationOfAliasSymbol(exportEqualsSymbol) || exportEqualsSymbol.valueDeclaration; + if (declaration && !isTopLevelInExternalModuleAugmentation(declaration) && !isInJSFile(declaration)) { + error(declaration, Diagnostics.An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements); + } + } + // Checks for export * conflicts + const exports = getExportsOfModule(moduleSymbol); + if (exports) { + exports.forEach(({ declarations, flags }, id) => { + if (id === "__export") { + return; + } + // ECMA262: 15.2.1.1 It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. + // (TS Exceptions: namespaces, function overloads, enums, and interfaces) + if (flags & (SymbolFlags.Namespace | SymbolFlags.Interface | SymbolFlags.Enum)) { + return; + } + const exportedDeclarationsCount = countWhere(declarations, isNotOverloadAndNotAccessor); + if (flags & SymbolFlags.TypeAlias && exportedDeclarationsCount <= 2) { + // it is legal to merge type alias with other values + // so count should be either 1 (just type alias) or 2 (type alias + merged value) + return; + } + if (exportedDeclarationsCount > 1) { + if (!isDuplicatedCommonJSExport(declarations)) { + for (const declaration of declarations!) { + if (isNotOverload(declaration)) { + diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Cannot_redeclare_exported_variable_0, unescapeLeadingUnderscores(id))); + } + } + } + } + }); } + links.exportsChecked = true; } + } + + function isDuplicatedCommonJSExport(declarations: Declaration[] | undefined) { + return declarations + && declarations.length > 1 + && declarations.every(d => isInJSFile(d) && isAccessExpression(d) && (isExportsIdentifier(d.expression) || isModuleExportsAccessExpression(d.expression))); + } - function checkDeferredNode(node: Node) { - tracing?.push(tracing.Phase.Check, "checkDeferredNode", { kind: node.kind, pos: node.pos, end: node.end }); + function checkSourceElement(node: Node | undefined): void { + if (node) { const saveCurrentNode = currentNode; currentNode = node; instantiationCount = 0; - switch (node.kind) { - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.TaggedTemplateExpression: - case SyntaxKind.Decorator: - case SyntaxKind.JsxOpeningElement: - // These node kinds are deferred checked when overload resolution fails - // To save on work, we ensure the arguments are checked just once, in - // a deferred way - resolveUntypedCall(node as CallLikeExpression); - break; - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - checkFunctionExpressionOrObjectLiteralMethodDeferred(node as FunctionExpression); - break; - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - checkAccessorDeclaration(node as AccessorDeclaration); - break; - case SyntaxKind.ClassExpression: - checkClassExpressionDeferred(node as ClassExpression); - break; - case SyntaxKind.JsxSelfClosingElement: - checkJsxSelfClosingElementDeferred(node as JsxSelfClosingElement); - break; - case SyntaxKind.JsxElement: - checkJsxElementDeferred(node as JsxElement); - break; - } + checkSourceElementWorker(node); currentNode = saveCurrentNode; - tracing?.pop(); } + } - function checkSourceFile(node: SourceFile) { - tracing?.push(tracing.Phase.Check, "checkSourceFile", { path: node.path }, /*separateBeginAndEnd*/ true); - performance.mark("beforeCheck"); - checkSourceFileWorker(node); - performance.mark("afterCheck"); - performance.measure("Check", "beforeCheck", "afterCheck"); - tracing?.pop(); + function checkSourceElementWorker(node: Node): void { + if (isInJSFile(node)) { + forEach((node as JSDocContainer).jsDoc, ({ tags }) => forEach(tags, checkSourceElement)); } - function unusedIsError(kind: UnusedKind, isAmbient: boolean): boolean { - if (isAmbient) { - return false; - } + const kind = node.kind; + if (cancellationToken) { + // Only bother checking on a few construct kinds. We don't want to be excessively + // hitting the cancellation token on every node we check. switch (kind) { - case UnusedKind.Local: - return !!compilerOptions.noUnusedLocals; - case UnusedKind.Parameter: - return !!compilerOptions.noUnusedParameters; - default: - return Debug.assertNever(kind); + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.FunctionDeclaration: + cancellationToken.throwIfCancellationRequested(); } } + if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && node.flowNode && !isReachableFlowNode(node.flowNode)) { + errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); + } + + switch (kind) { + case SyntaxKind.TypeParameter: + return checkTypeParameter(node as TypeParameterDeclaration); + case SyntaxKind.Parameter: + return checkParameter(node as ParameterDeclaration); + case SyntaxKind.PropertyDeclaration: + return checkPropertyDeclaration(node as PropertyDeclaration); + case SyntaxKind.PropertySignature: + return checkPropertySignature(node as PropertySignature); + case SyntaxKind.ConstructorType: + case SyntaxKind.FunctionType: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + return checkSignatureDeclaration(node as SignatureDeclaration); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + return checkMethodDeclaration(node as MethodDeclaration | MethodSignature); + case SyntaxKind.ClassStaticBlockDeclaration: + return checkClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); + case SyntaxKind.Constructor: + return checkConstructorDeclaration(node as ConstructorDeclaration); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return checkAccessorDeclaration(node as AccessorDeclaration); + case SyntaxKind.TypeReference: + return checkTypeReferenceNode(node as TypeReferenceNode); + case SyntaxKind.TypePredicate: + return checkTypePredicate(node as TypePredicateNode); + case SyntaxKind.TypeQuery: + return checkTypeQuery(node as TypeQueryNode); + case SyntaxKind.TypeLiteral: + return checkTypeLiteral(node as TypeLiteralNode); + case SyntaxKind.ArrayType: + return checkArrayType(node as ArrayTypeNode); + case SyntaxKind.TupleType: + return checkTupleType(node as TupleTypeNode); + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + return checkUnionOrIntersectionType(node as UnionOrIntersectionTypeNode); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.OptionalType: + case SyntaxKind.RestType: + return checkSourceElement((node as ParenthesizedTypeNode | OptionalTypeNode | RestTypeNode).type); + case SyntaxKind.ThisType: + return checkThisType(node as ThisTypeNode); + case SyntaxKind.TypeOperator: + return checkTypeOperator(node as TypeOperatorNode); + case SyntaxKind.ConditionalType: + return checkConditionalType(node as ConditionalTypeNode); + case SyntaxKind.InferType: + return checkInferType(node as InferTypeNode); + case SyntaxKind.TemplateLiteralType: + return checkTemplateLiteralType(node as TemplateLiteralTypeNode); + case SyntaxKind.ImportType: + return checkImportType(node as ImportTypeNode); + case SyntaxKind.NamedTupleMember: + return checkNamedTupleMember(node as NamedTupleMember); + case SyntaxKind.JSDocAugmentsTag: + return checkJSDocAugmentsTag(node as JSDocAugmentsTag); + case SyntaxKind.JSDocImplementsTag: + return checkJSDocImplementsTag(node as JSDocImplementsTag); + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + return checkJSDocTypeAliasTag(node as JSDocTypedefTag); + case SyntaxKind.JSDocTemplateTag: + return checkJSDocTemplateTag(node as JSDocTemplateTag); + case SyntaxKind.JSDocTypeTag: + return checkJSDocTypeTag(node as JSDocTypeTag); + case SyntaxKind.JSDocParameterTag: + return checkJSDocParameterTag(node as JSDocParameterTag); + case SyntaxKind.JSDocPropertyTag: + return checkJSDocPropertyTag(node as JSDocPropertyTag); + case SyntaxKind.JSDocFunctionType: + checkJSDocFunctionType(node as JSDocFunctionType); + // falls through + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocNullableType: + case SyntaxKind.JSDocAllType: + case SyntaxKind.JSDocUnknownType: + case SyntaxKind.JSDocTypeLiteral: + checkJSDocTypeIsInJsFile(node); + forEachChild(node, checkSourceElement); + return; + case SyntaxKind.JSDocVariadicType: + checkJSDocVariadicType(node as JSDocVariadicType); + return; + case SyntaxKind.JSDocTypeExpression: + return checkSourceElement((node as JSDocTypeExpression).type); + case SyntaxKind.JSDocPublicTag: + case SyntaxKind.JSDocProtectedTag: + case SyntaxKind.JSDocPrivateTag: + return checkJSDocAccessibilityModifiers(node as JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag); + case SyntaxKind.IndexedAccessType: + return checkIndexedAccessType(node as IndexedAccessTypeNode); + case SyntaxKind.MappedType: + return checkMappedType(node as MappedTypeNode); + case SyntaxKind.FunctionDeclaration: + return checkFunctionDeclaration(node as FunctionDeclaration); + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + return checkBlock(node as Block); + case SyntaxKind.VariableStatement: + return checkVariableStatement(node as VariableStatement); + case SyntaxKind.ExpressionStatement: + return checkExpressionStatement(node as ExpressionStatement); + case SyntaxKind.IfStatement: + return checkIfStatement(node as IfStatement); + case SyntaxKind.DoStatement: + return checkDoStatement(node as DoStatement); + case SyntaxKind.WhileStatement: + return checkWhileStatement(node as WhileStatement); + case SyntaxKind.ForStatement: + return checkForStatement(node as ForStatement); + case SyntaxKind.ForInStatement: + return checkForInStatement(node as ForInStatement); + case SyntaxKind.ForOfStatement: + return checkForOfStatement(node as ForOfStatement); + case SyntaxKind.ContinueStatement: + case SyntaxKind.BreakStatement: + return checkBreakOrContinueStatement(node as BreakOrContinueStatement); + case SyntaxKind.ReturnStatement: + return checkReturnStatement(node as ReturnStatement); + case SyntaxKind.WithStatement: + return checkWithStatement(node as WithStatement); + case SyntaxKind.SwitchStatement: + return checkSwitchStatement(node as SwitchStatement); + case SyntaxKind.LabeledStatement: + return checkLabeledStatement(node as LabeledStatement); + case SyntaxKind.ThrowStatement: + return checkThrowStatement(node as ThrowStatement); + case SyntaxKind.TryStatement: + return checkTryStatement(node as TryStatement); + case SyntaxKind.VariableDeclaration: + return checkVariableDeclaration(node as VariableDeclaration); + case SyntaxKind.BindingElement: + return checkBindingElement(node as BindingElement); + case SyntaxKind.ClassDeclaration: + return checkClassDeclaration(node as ClassDeclaration); + case SyntaxKind.InterfaceDeclaration: + return checkInterfaceDeclaration(node as InterfaceDeclaration); + case SyntaxKind.TypeAliasDeclaration: + return checkTypeAliasDeclaration(node as TypeAliasDeclaration); + case SyntaxKind.EnumDeclaration: + return checkEnumDeclaration(node as EnumDeclaration); + case SyntaxKind.ModuleDeclaration: + return checkModuleDeclaration(node as ModuleDeclaration); + case SyntaxKind.ImportDeclaration: + return checkImportDeclaration(node as ImportDeclaration); + case SyntaxKind.ImportEqualsDeclaration: + return checkImportEqualsDeclaration(node as ImportEqualsDeclaration); + case SyntaxKind.ExportDeclaration: + return checkExportDeclaration(node as ExportDeclaration); + case SyntaxKind.ExportAssignment: + return checkExportAssignment(node as ExportAssignment); + case SyntaxKind.EmptyStatement: + case SyntaxKind.DebuggerStatement: + checkGrammarStatementInAmbientContext(node); + return; + case SyntaxKind.MissingDeclaration: + return checkMissingDeclaration(node); + } + } - function getPotentiallyUnusedIdentifiers(sourceFile: SourceFile): readonly PotentiallyUnusedIdentifier[] { - return allPotentiallyUnusedIdentifiers.get(sourceFile.path) || emptyArray; + function checkJSDocTypeIsInJsFile(node: Node): void { + if (!isInJSFile(node)) { + grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); } + } - // Fully type check a source file and collect the relevant diagnostics. - function checkSourceFileWorker(node: SourceFile) { - const links = getNodeLinks(node); - if (!(links.flags & NodeCheckFlags.TypeChecked)) { - if (skipTypeChecking(node, compilerOptions, host)) { - return; - } + function checkJSDocVariadicType(node: JSDocVariadicType): void { + checkJSDocTypeIsInJsFile(node); + checkSourceElement(node.type); - // Grammar checking - checkGrammarSourceFile(node); + // Only legal location is in the *last* parameter tag or last parameter of a JSDoc function. + const { parent } = node; + if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { + if (last(parent.parent.parameters) !== parent) { + error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); + } + return; + } - clear(potentialThisCollisions); - clear(potentialNewTargetCollisions); - clear(potentialWeakMapSetCollisions); - clear(potentialReflectCollisions); + if (!isJSDocTypeExpression(parent)) { + error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); + } - forEach(node.statements, checkSourceElement); - checkSourceElement(node.endOfFileToken); + const paramTag = node.parent.parent; + if (!isJSDocParameterTag(paramTag)) { + error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); + return; + } - checkDeferredNodes(node); + const param = getParameterSymbolFromJSDoc(paramTag); + if (!param) { + // We will error in `checkJSDocParameterTag`. + return; + } - if (isExternalOrCommonJsModule(node)) { - registerForUnusedIdentifiersCheck(node); - } + const host = getHostSignatureFromJSDoc(paramTag); + if (!host || last(host.parameters).symbol !== param) { + error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); + } + } - if (!node.isDeclarationFile && (compilerOptions.noUnusedLocals || compilerOptions.noUnusedParameters)) { - checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(node), (containingNode, kind, diag) => { - if (!containsParseError(containingNode) && unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { - diagnostics.add(diag); - } - }); + function getTypeFromJSDocVariadicType(node: JSDocVariadicType): Type { + const type = getTypeFromTypeNode(node.type); + const { parent } = node; + const paramTag = node.parent.parent; + if (isJSDocTypeExpression(node.parent) && isJSDocParameterTag(paramTag)) { + // Else we will add a diagnostic, see `checkJSDocVariadicType`. + const host = getHostSignatureFromJSDoc(paramTag); + const isCallbackTag = isJSDocCallbackTag(paramTag.parent.parent); + if (host || isCallbackTag) { + /* + Only return an array type if the corresponding parameter is marked as a rest parameter, or if there are no parameters. + So in the following situation we will not create an array type: + /** @param {...number} a * / + function f(a) {} + Because `a` will just be of type `number | undefined`. A synthetic `...args` will also be added, which *will* get an array type. + */ + const lastParamDeclaration = isCallbackTag + ? lastOrUndefined((paramTag.parent.parent as unknown as JSDocCallbackTag).typeExpression.parameters) + : lastOrUndefined(host!.parameters); + const symbol = getParameterSymbolFromJSDoc(paramTag); + if (!lastParamDeclaration || + symbol && lastParamDeclaration.symbol === symbol && isRestParameter(lastParamDeclaration)) { + return createArrayType(type); } + } + } + if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { + return createArrayType(type); + } + return addOptionality(type); + } - if (compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && - !node.isDeclarationFile && - isExternalModule(node) - ) { - checkImportsForTypeOnlyConversion(node); - } + // Function and class expression bodies are checked after all statements in the enclosing body. This is + // to ensure constructs like the following are permitted: + // const foo = function () { + // const s = foo(); + // return "hello"; + // } + // Here, performing a full type check of the body of the function expression whilst in the process of + // determining the type of foo would cause foo to be given type any because of the recursive reference. + // Delaying the type check of the body ensures foo has been assigned a type. + function checkNodeDeferred(node: Node) { + const enclosingFile = getSourceFileOfNode(node); + const links = getNodeLinks(enclosingFile); + if (!(links.flags & NodeCheckFlags.TypeChecked)) { + links.deferredNodes = links.deferredNodes || new Map(); + const id = getNodeId(node); + links.deferredNodes.set(id, node); + } + } - if (isExternalOrCommonJsModule(node)) { - checkExternalModuleExports(node); - } + function checkDeferredNodes(context: SourceFile) { + const links = getNodeLinks(context); + if (links.deferredNodes) { + links.deferredNodes.forEach(checkDeferredNode); + } + } - if (potentialThisCollisions.length) { - forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope); - clear(potentialThisCollisions); - } + function checkDeferredNode(node: Node) { + tracing?.push(tracing.Phase.Check, "checkDeferredNode", { kind: node.kind, pos: node.pos, end: node.end }); + const saveCurrentNode = currentNode; + currentNode = node; + instantiationCount = 0; + switch (node.kind) { + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.Decorator: + case SyntaxKind.JsxOpeningElement: + // These node kinds are deferred checked when overload resolution fails + // To save on work, we ensure the arguments are checked just once, in + // a deferred way + resolveUntypedCall(node as CallLikeExpression); + break; + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + checkFunctionExpressionOrObjectLiteralMethodDeferred(node as FunctionExpression); + break; + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + checkAccessorDeclaration(node as AccessorDeclaration); + break; + case SyntaxKind.ClassExpression: + checkClassExpressionDeferred(node as ClassExpression); + break; + case SyntaxKind.JsxSelfClosingElement: + checkJsxSelfClosingElementDeferred(node as JsxSelfClosingElement); + break; + case SyntaxKind.JsxElement: + checkJsxElementDeferred(node as JsxElement); + break; + } + currentNode = saveCurrentNode; + tracing?.pop(); + } - if (potentialNewTargetCollisions.length) { - forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope); - clear(potentialNewTargetCollisions); - } + function checkSourceFile(node: SourceFile) { + tracing?.push(tracing.Phase.Check, "checkSourceFile", { path: node.path }, /*separateBeginAndEnd*/ true); + performance.mark("beforeCheck"); + checkSourceFileWorker(node); + performance.mark("afterCheck"); + performance.measure("Check", "beforeCheck", "afterCheck"); + tracing?.pop(); + } - if (potentialWeakMapSetCollisions.length) { - forEach(potentialWeakMapSetCollisions, checkWeakMapSetCollision); - clear(potentialWeakMapSetCollisions); - } + function unusedIsError(kind: UnusedKind, isAmbient: boolean): boolean { + if (isAmbient) { + return false; + } + switch (kind) { + case UnusedKind.Local: + return !!compilerOptions.noUnusedLocals; + case UnusedKind.Parameter: + return !!compilerOptions.noUnusedParameters; + default: + return Debug.assertNever(kind); + } + } - if (potentialReflectCollisions.length) { - forEach(potentialReflectCollisions, checkReflectCollision); - clear(potentialReflectCollisions); - } + function getPotentiallyUnusedIdentifiers(sourceFile: SourceFile): readonly PotentiallyUnusedIdentifier[] { + return allPotentiallyUnusedIdentifiers.get(sourceFile.path) || emptyArray; + } - links.flags |= NodeCheckFlags.TypeChecked; + // Fully type check a source file and collect the relevant diagnostics. + function checkSourceFileWorker(node: SourceFile) { + const links = getNodeLinks(node); + if (!(links.flags & NodeCheckFlags.TypeChecked)) { + if (skipTypeChecking(node, compilerOptions, host)) { + return; } - } - function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] { - try { - // Record the cancellation token so it can be checked later on during checkSourceElement. - // Do this in a finally block so we can ensure that it gets reset back to nothing after - // this call is done. - cancellationToken = ct; - return getDiagnosticsWorker(sourceFile); + // Grammar checking + checkGrammarSourceFile(node); + + clear(potentialThisCollisions); + clear(potentialNewTargetCollisions); + clear(potentialWeakMapSetCollisions); + clear(potentialReflectCollisions); + + forEach(node.statements, checkSourceElement); + checkSourceElement(node.endOfFileToken); + + checkDeferredNodes(node); + + if (isExternalOrCommonJsModule(node)) { + registerForUnusedIdentifiersCheck(node); } - finally { - cancellationToken = undefined; + + if (!node.isDeclarationFile && (compilerOptions.noUnusedLocals || compilerOptions.noUnusedParameters)) { + checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(node), (containingNode, kind, diag) => { + if (!containsParseError(containingNode) && unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { + diagnostics.add(diag); + } + }); } - } - function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] { - throwIfNonDiagnosticsProducing(); - if (sourceFile) { - // Some global diagnostics are deferred until they are needed and - // may not be reported in the first call to getGlobalDiagnostics. - // We should catch these changes and report them. - const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); - const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length; + if (compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && + !node.isDeclarationFile && + isExternalModule(node) + ) { + checkImportsForTypeOnlyConversion(node); + } - checkSourceFile(sourceFile); + if (isExternalOrCommonJsModule(node)) { + checkExternalModuleExports(node); + } - const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName); - const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); - if (currentGlobalDiagnostics !== previousGlobalDiagnostics) { - // If the arrays are not the same reference, new diagnostics were added. - const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics); - return concatenate(deferredGlobalDiagnostics, semanticDiagnostics); - } - else if (previousGlobalDiagnosticsSize === 0 && currentGlobalDiagnostics.length > 0) { - // If the arrays are the same reference, but the length has changed, a single - // new diagnostic was added as DiagnosticCollection attempts to reuse the - // same array. - return concatenate(currentGlobalDiagnostics, semanticDiagnostics); - } + if (potentialThisCollisions.length) { + forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope); + clear(potentialThisCollisions); + } + + if (potentialNewTargetCollisions.length) { + forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope); + clear(potentialNewTargetCollisions); + } - return semanticDiagnostics; + if (potentialWeakMapSetCollisions.length) { + forEach(potentialWeakMapSetCollisions, checkWeakMapSetCollision); + clear(potentialWeakMapSetCollisions); } - // Global diagnostics are always added when a file is not provided to - // getDiagnostics - forEach(host.getSourceFiles(), checkSourceFile); - return diagnostics.getDiagnostics(); - } + if (potentialReflectCollisions.length) { + forEach(potentialReflectCollisions, checkReflectCollision); + clear(potentialReflectCollisions); + } - function getGlobalDiagnostics(): Diagnostic[] { - throwIfNonDiagnosticsProducing(); - return diagnostics.getGlobalDiagnostics(); + links.flags |= NodeCheckFlags.TypeChecked; } + } - function throwIfNonDiagnosticsProducing() { - if (!produceDiagnostics) { - throw new Error("Trying to get diagnostics from a type checker that does not produce them."); - } + function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] { + try { + // Record the cancellation token so it can be checked later on during checkSourceElement. + // Do this in a finally block so we can ensure that it gets reset back to nothing after + // this call is done. + cancellationToken = ct; + return getDiagnosticsWorker(sourceFile); } + finally { + cancellationToken = undefined; + } + } - // Language service support + function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] { + throwIfNonDiagnosticsProducing(); + if (sourceFile) { + // Some global diagnostics are deferred until they are needed and + // may not be reported in the first call to getGlobalDiagnostics. + // We should catch these changes and report them. + const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); + const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length; - function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { - if (location.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return []; + checkSourceFile(sourceFile); + + const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName); + const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); + if (currentGlobalDiagnostics !== previousGlobalDiagnostics) { + // If the arrays are not the same reference, new diagnostics were added. + const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics); + return concatenate(deferredGlobalDiagnostics, semanticDiagnostics); + } + else if (previousGlobalDiagnosticsSize === 0 && currentGlobalDiagnostics.length > 0) { + // If the arrays are the same reference, but the length has changed, a single + // new diagnostic was added as DiagnosticCollection attempts to reuse the + // same array. + return concatenate(currentGlobalDiagnostics, semanticDiagnostics); } - const symbols = createSymbolTable(); - let isStaticSymbol = false; + return semanticDiagnostics; + } - populateSymbols(); + // Global diagnostics are always added when a file is not provided to + // getDiagnostics + forEach(host.getSourceFiles(), checkSourceFile); + return diagnostics.getDiagnostics(); + } - symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword - return symbolsToArray(symbols); + function getGlobalDiagnostics(): Diagnostic[] { + throwIfNonDiagnosticsProducing(); + return diagnostics.getGlobalDiagnostics(); + } - function populateSymbols() { - while (location) { - if (location.locals && !isGlobalSourceFile(location)) { - copySymbols(location.locals, meaning); - } + function throwIfNonDiagnosticsProducing() { + if (!produceDiagnostics) { + throw new Error("Trying to get diagnostics from a type checker that does not produce them."); + } + } - switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalModule(location as SourceFile)) break; - // falls through - case SyntaxKind.ModuleDeclaration: - copyLocallyVisibleExportSymbols(getSymbolOfNode(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); - break; - case SyntaxKind.EnumDeclaration: - copySymbols(getSymbolOfNode(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember); - break; - case SyntaxKind.ClassExpression: - const className = (location as ClassExpression).name; - if (className) { - copySymbol(location.symbol, meaning); - } + // Language service support - // this fall-through is necessary because we would like to handle - // type parameter inside class expression similar to how we handle it in classDeclaration and interface Declaration. - // falls through - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - // If we didn't come from static member of class or interface, - // add the type parameters into the symbol table - // (type parameters of classDeclaration/classExpression and interface are in member property of the symbol. - // Note: that the memberFlags come from previous iteration. - if (!isStaticSymbol) { - copySymbols(getMembersOfSymbol(getSymbolOfNode(location as ClassDeclaration | InterfaceDeclaration)), meaning & SymbolFlags.Type); - } - break; - case SyntaxKind.FunctionExpression: - const funcName = (location as FunctionExpression).name; - if (funcName) { - copySymbol(location.symbol, meaning); - } - break; - } + function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { + if (location.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return []; + } - if (introducesArgumentsExoticObject(location)) { - copySymbol(argumentsSymbol, meaning); - } + const symbols = createSymbolTable(); + let isStaticSymbol = false; - isStaticSymbol = isStatic(location); - location = location.parent; + populateSymbols(); + + symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword + return symbolsToArray(symbols); + + function populateSymbols() { + while (location) { + if (location.locals && !isGlobalSourceFile(location)) { + copySymbols(location.locals, meaning); } - copySymbols(globals, meaning); - } + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalModule(location as SourceFile)) break; + // falls through + case SyntaxKind.ModuleDeclaration: + copyLocallyVisibleExportSymbols(getSymbolOfNode(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); + break; + case SyntaxKind.EnumDeclaration: + copySymbols(getSymbolOfNode(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember); + break; + case SyntaxKind.ClassExpression: + const className = (location as ClassExpression).name; + if (className) { + copySymbol(location.symbol, meaning); + } - /** - * Copy the given symbol into symbol tables if the symbol has the given meaning - * and it doesn't already existed in the symbol table - * @param key a key for storing in symbol table; if undefined, use symbol.name - * @param symbol the symbol to be added into symbol table - * @param meaning meaning of symbol to filter by before adding to symbol table - */ - function copySymbol(symbol: Symbol, meaning: SymbolFlags): void { - if (getCombinedLocalAndExportSymbolFlags(symbol) & meaning) { - const id = symbol.escapedName; - // We will copy all symbol regardless of its reserved name because - // symbolsToArray will check whether the key is a reserved name and - // it will not copy symbol with reserved name to the array - if (!symbols.has(id)) { - symbols.set(id, symbol); - } + // this fall-through is necessary because we would like to handle + // type parameter inside class expression similar to how we handle it in classDeclaration and interface Declaration. + // falls through + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + // If we didn't come from static member of class or interface, + // add the type parameters into the symbol table + // (type parameters of classDeclaration/classExpression and interface are in member property of the symbol. + // Note: that the memberFlags come from previous iteration. + if (!isStaticSymbol) { + copySymbols(getMembersOfSymbol(getSymbolOfNode(location as ClassDeclaration | InterfaceDeclaration)), meaning & SymbolFlags.Type); + } + break; + case SyntaxKind.FunctionExpression: + const funcName = (location as FunctionExpression).name; + if (funcName) { + copySymbol(location.symbol, meaning); + } + break; } - } - function copySymbols(source: SymbolTable, meaning: SymbolFlags): void { - if (meaning) { - source.forEach(symbol => { - copySymbol(symbol, meaning); - }); + if (introducesArgumentsExoticObject(location)) { + copySymbol(argumentsSymbol, meaning); } - } - function copyLocallyVisibleExportSymbols(source: SymbolTable, meaning: SymbolFlags): void { - if (meaning) { - source.forEach(symbol => { - // Similar condition as in `resolveNameHelper` - if (!getDeclarationOfKind(symbol, SyntaxKind.ExportSpecifier) && !getDeclarationOfKind(symbol, SyntaxKind.NamespaceExport)) { - copySymbol(symbol, meaning); - } - }); - } + isStaticSymbol = isStatic(location); + location = location.parent; } - } - function isTypeDeclarationName(name: Node): boolean { - return name.kind === SyntaxKind.Identifier && - isTypeDeclaration(name.parent) && - getNameOfDeclaration(name.parent) === name; + copySymbols(globals, meaning); } - function isTypeDeclaration(node: Node): node is TypeParameterDeclaration | ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag | EnumDeclaration | ImportClause | ImportSpecifier | ExportSpecifier { - switch (node.kind) { - case SyntaxKind.TypeParameter: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - return true; - case SyntaxKind.ImportClause: - return (node as ImportClause).isTypeOnly; - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - return (node as ImportSpecifier | ExportSpecifier).parent.parent.isTypeOnly; - default: - return false; + /** + * Copy the given symbol into symbol tables if the symbol has the given meaning + * and it doesn't already existed in the symbol table + * @param key a key for storing in symbol table; if undefined, use symbol.name + * @param symbol the symbol to be added into symbol table + * @param meaning meaning of symbol to filter by before adding to symbol table + */ + function copySymbol(symbol: Symbol, meaning: SymbolFlags): void { + if (getCombinedLocalAndExportSymbolFlags(symbol) & meaning) { + const id = symbol.escapedName; + // We will copy all symbol regardless of its reserved name because + // symbolsToArray will check whether the key is a reserved name and + // it will not copy symbol with reserved name to the array + if (!symbols.has(id)) { + symbols.set(id, symbol); + } } } - // True if the given identifier is part of a type reference - function isTypeReferenceIdentifier(node: EntityName): boolean { - while (node.parent.kind === SyntaxKind.QualifiedName) { - node = node.parent as QualifiedName; + function copySymbols(source: SymbolTable, meaning: SymbolFlags): void { + if (meaning) { + source.forEach(symbol => { + copySymbol(symbol, meaning); + }); } - - return node.parent.kind === SyntaxKind.TypeReference; } - function isHeritageClauseElementIdentifier(node: Node): boolean { - while (node.parent.kind === SyntaxKind.PropertyAccessExpression) { - node = node.parent; + function copyLocallyVisibleExportSymbols(source: SymbolTable, meaning: SymbolFlags): void { + if (meaning) { + source.forEach(symbol => { + // Similar condition as in `resolveNameHelper` + if (!getDeclarationOfKind(symbol, SyntaxKind.ExportSpecifier) && !getDeclarationOfKind(symbol, SyntaxKind.NamespaceExport)) { + copySymbol(symbol, meaning); + } + }); } - - return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments; } + } - function forEachEnclosingClass(node: Node, callback: (node: Node) => T | undefined): T | undefined { - let result: T | undefined; + function isTypeDeclarationName(name: Node): boolean { + return name.kind === SyntaxKind.Identifier && + isTypeDeclaration(name.parent) && + getNameOfDeclaration(name.parent) === name; + } - while (true) { - node = getContainingClass(node)!; - if (!node) break; - if (result = callback(node)) break; - } + function isTypeDeclaration(node: Node): node is TypeParameterDeclaration | ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag | EnumDeclaration | ImportClause | ImportSpecifier | ExportSpecifier { + switch (node.kind) { + case SyntaxKind.TypeParameter: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + return true; + case SyntaxKind.ImportClause: + return (node as ImportClause).isTypeOnly; + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + return (node as ImportSpecifier | ExportSpecifier).parent.parent.isTypeOnly; + default: + return false; + } + } - return result; + // True if the given identifier is part of a type reference + function isTypeReferenceIdentifier(node: EntityName): boolean { + while (node.parent.kind === SyntaxKind.QualifiedName) { + node = node.parent as QualifiedName; } - function isNodeUsedDuringClassInitialization(node: Node) { - return !!findAncestor(node, element => { - if (isConstructorDeclaration(element) && nodeIsPresent(element.body) || isPropertyDeclaration(element)) { - return true; - } - else if (isClassLike(element) || isFunctionLikeDeclaration(element)) { - return "quit"; - } + return node.parent.kind === SyntaxKind.TypeReference; + } - return false; - }); + function isHeritageClauseElementIdentifier(node: Node): boolean { + while (node.parent.kind === SyntaxKind.PropertyAccessExpression) { + node = node.parent; } - function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) { - return !!forEachEnclosingClass(node, n => n === classDeclaration); + return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments; + } + + function forEachEnclosingClass(node: Node, callback: (node: Node) => T | undefined): T | undefined { + let result: T | undefined; + + while (true) { + node = getContainingClass(node)!; + if (!node) break; + if (result = callback(node)) break; } - function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment | undefined { - while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) { - nodeOnRightSide = nodeOnRightSide.parent as QualifiedName; - } + return result; + } - if (nodeOnRightSide.parent.kind === SyntaxKind.ImportEqualsDeclaration) { - return (nodeOnRightSide.parent as ImportEqualsDeclaration).moduleReference === nodeOnRightSide ? nodeOnRightSide.parent as ImportEqualsDeclaration : undefined; + function isNodeUsedDuringClassInitialization(node: Node) { + return !!findAncestor(node, element => { + if (isConstructorDeclaration(element) && nodeIsPresent(element.body) || isPropertyDeclaration(element)) { + return true; } - - if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) { - return (nodeOnRightSide.parent as ExportAssignment).expression === nodeOnRightSide as Node ? nodeOnRightSide.parent as ExportAssignment : undefined; + else if (isClassLike(element) || isFunctionLikeDeclaration(element)) { + return "quit"; } - return undefined; + return false; + }); + } + + function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) { + return !!forEachEnclosingClass(node, n => n === classDeclaration); + } + + function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment | undefined { + while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) { + nodeOnRightSide = nodeOnRightSide.parent as QualifiedName; } - function isInRightSideOfImportOrExportAssignment(node: EntityName) { - return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined; + if (nodeOnRightSide.parent.kind === SyntaxKind.ImportEqualsDeclaration) { + return (nodeOnRightSide.parent as ImportEqualsDeclaration).moduleReference === nodeOnRightSide ? nodeOnRightSide.parent as ImportEqualsDeclaration : undefined; } - function getSpecialPropertyAssignmentSymbolFromEntityName(entityName: EntityName | PropertyAccessExpression) { - const specialPropertyAssignmentKind = getAssignmentDeclarationKind(entityName.parent.parent as BinaryExpression); - switch (specialPropertyAssignmentKind) { - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.PrototypeProperty: - return getSymbolOfNode(entityName.parent); - case AssignmentDeclarationKind.ThisProperty: - case AssignmentDeclarationKind.ModuleExports: - case AssignmentDeclarationKind.Property: - return getSymbolOfNode(entityName.parent.parent); - } + if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) { + return (nodeOnRightSide.parent as ExportAssignment).expression === nodeOnRightSide as Node ? nodeOnRightSide.parent as ExportAssignment : undefined; } - function isImportTypeQualifierPart(node: EntityName): ImportTypeNode | undefined { - let parent = node.parent; - while (isQualifiedName(parent)) { - node = parent; - parent = parent.parent; - } - if (parent && parent.kind === SyntaxKind.ImportType && (parent as ImportTypeNode).qualifier === node) { - return parent as ImportTypeNode; - } - return undefined; + return undefined; + } + + function isInRightSideOfImportOrExportAssignment(node: EntityName) { + return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined; + } + + function getSpecialPropertyAssignmentSymbolFromEntityName(entityName: EntityName | PropertyAccessExpression) { + const specialPropertyAssignmentKind = getAssignmentDeclarationKind(entityName.parent.parent as BinaryExpression); + switch (specialPropertyAssignmentKind) { + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.PrototypeProperty: + return getSymbolOfNode(entityName.parent); + case AssignmentDeclarationKind.ThisProperty: + case AssignmentDeclarationKind.ModuleExports: + case AssignmentDeclarationKind.Property: + return getSymbolOfNode(entityName.parent.parent); } + } - function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression | JSDocMemberName): Symbol | undefined { - if (isDeclarationName(name)) { - return getSymbolOfNode(name.parent); - } + function isImportTypeQualifierPart(node: EntityName): ImportTypeNode | undefined { + let parent = node.parent; + while (isQualifiedName(parent)) { + node = parent; + parent = parent.parent; + } + if (parent && parent.kind === SyntaxKind.ImportType && (parent as ImportTypeNode).qualifier === node) { + return parent as ImportTypeNode; + } + return undefined; + } - if (isInJSFile(name) && - name.parent.kind === SyntaxKind.PropertyAccessExpression && - name.parent === (name.parent.parent as BinaryExpression).left) { - // Check if this is a special property assignment - if (!isPrivateIdentifier(name) && !isJSDocMemberName(name)) { - const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(name); - if (specialPropertyAssignmentSymbol) { - return specialPropertyAssignmentSymbol; - } - } - } + function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression | JSDocMemberName): Symbol | undefined { + if (isDeclarationName(name)) { + return getSymbolOfNode(name.parent); + } - if (name.parent.kind === SyntaxKind.ExportAssignment && isEntityNameExpression(name)) { - // Even an entity name expression that doesn't resolve as an entityname may still typecheck as a property access expression - const success = resolveEntityName(name, - /*all meanings*/ SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*ignoreErrors*/ true); - if (success && success !== unknownSymbol) { - return success; + if (isInJSFile(name) && + name.parent.kind === SyntaxKind.PropertyAccessExpression && + name.parent === (name.parent.parent as BinaryExpression).left) { + // Check if this is a special property assignment + if (!isPrivateIdentifier(name) && !isJSDocMemberName(name)) { + const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(name); + if (specialPropertyAssignmentSymbol) { + return specialPropertyAssignmentSymbol; } } - else if (isEntityName(name) && isInRightSideOfImportOrExportAssignment(name)) { - // Since we already checked for ExportAssignment, this really could only be an Import - const importEqualsDeclaration = getAncestor(name, SyntaxKind.ImportEqualsDeclaration); - Debug.assert(importEqualsDeclaration !== undefined); - return getSymbolOfPartOfRightHandSideOfImportEquals(name, /*dontResolveAlias*/ true); - } + } - if (isEntityName(name)) { - const possibleImportNode = isImportTypeQualifierPart(name); - if (possibleImportNode) { - getTypeFromTypeNode(possibleImportNode); - const sym = getNodeLinks(name).resolvedSymbol; - return sym === unknownSymbol ? undefined : sym; - } + if (name.parent.kind === SyntaxKind.ExportAssignment && isEntityNameExpression(name)) { + // Even an entity name expression that doesn't resolve as an entityname may still typecheck as a property access expression + const success = resolveEntityName(name, + /*all meanings*/ SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*ignoreErrors*/ true); + if (success && success !== unknownSymbol) { + return success; } + } + else if (isEntityName(name) && isInRightSideOfImportOrExportAssignment(name)) { + // Since we already checked for ExportAssignment, this really could only be an Import + const importEqualsDeclaration = getAncestor(name, SyntaxKind.ImportEqualsDeclaration); + Debug.assert(importEqualsDeclaration !== undefined); + return getSymbolOfPartOfRightHandSideOfImportEquals(name, /*dontResolveAlias*/ true); + } - while (isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName(name)) { - name = name.parent as QualifiedName | PropertyAccessEntityNameExpression | JSDocMemberName; + if (isEntityName(name)) { + const possibleImportNode = isImportTypeQualifierPart(name); + if (possibleImportNode) { + getTypeFromTypeNode(possibleImportNode); + const sym = getNodeLinks(name).resolvedSymbol; + return sym === unknownSymbol ? undefined : sym; } + } - if (isHeritageClauseElementIdentifier(name)) { - let meaning = SymbolFlags.None; - // In an interface or class, we're definitely interested in a type. - if (name.parent.kind === SyntaxKind.ExpressionWithTypeArguments) { - meaning = SymbolFlags.Type; + while (isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName(name)) { + name = name.parent as QualifiedName | PropertyAccessEntityNameExpression | JSDocMemberName; + } - // In a class 'extends' clause we are also looking for a value. - if (isExpressionWithTypeArgumentsInClassExtendsClause(name.parent)) { - meaning |= SymbolFlags.Value; - } - } - else { - meaning = SymbolFlags.Namespace; - } + if (isHeritageClauseElementIdentifier(name)) { + let meaning = SymbolFlags.None; + // In an interface or class, we're definitely interested in a type. + if (name.parent.kind === SyntaxKind.ExpressionWithTypeArguments) { + meaning = SymbolFlags.Type; - meaning |= SymbolFlags.Alias; - const entityNameSymbol = isEntityNameExpression(name) ? resolveEntityName(name, meaning) : undefined; - if (entityNameSymbol) { - return entityNameSymbol; + // In a class 'extends' clause we are also looking for a value. + if (isExpressionWithTypeArgumentsInClassExtendsClause(name.parent)) { + meaning |= SymbolFlags.Value; } } + else { + meaning = SymbolFlags.Namespace; + } - if (name.parent.kind === SyntaxKind.JSDocParameterTag) { - return getParameterSymbolFromJSDoc(name.parent as JSDocParameterTag); + meaning |= SymbolFlags.Alias; + const entityNameSymbol = isEntityNameExpression(name) ? resolveEntityName(name, meaning) : undefined; + if (entityNameSymbol) { + return entityNameSymbol; } + } + + if (name.parent.kind === SyntaxKind.JSDocParameterTag) { + return getParameterSymbolFromJSDoc(name.parent as JSDocParameterTag); + } + + if (name.parent.kind === SyntaxKind.TypeParameter && name.parent.parent.kind === SyntaxKind.JSDocTemplateTag) { + Debug.assert(!isInJSFile(name)); // Otherwise `isDeclarationName` would have been true. + const typeParameter = getTypeParameterFromJsDoc(name.parent as TypeParameterDeclaration & { parent: JSDocTemplateTag }); + return typeParameter && typeParameter.symbol; + } - if (name.parent.kind === SyntaxKind.TypeParameter && name.parent.parent.kind === SyntaxKind.JSDocTemplateTag) { - Debug.assert(!isInJSFile(name)); // Otherwise `isDeclarationName` would have been true. - const typeParameter = getTypeParameterFromJsDoc(name.parent as TypeParameterDeclaration & { parent: JSDocTemplateTag }); - return typeParameter && typeParameter.symbol; + if (isExpressionNode(name)) { + if (nodeIsMissing(name)) { + // Missing entity name. + return undefined; } - if (isExpressionNode(name)) { - if (nodeIsMissing(name)) { - // Missing entity name. - return undefined; + const isJSDoc = findAncestor(name, or(isJSDocLinkLike, isJSDocNameReference, isJSDocMemberName)); + const meaning = isJSDoc ? SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value : SymbolFlags.Value; + if (name.kind === SyntaxKind.Identifier) { + if (isJSXTagName(name) && isJsxIntrinsicIdentifier(name)) { + const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement); + return symbol === unknownSymbol ? undefined : symbol; } - - const isJSDoc = findAncestor(name, or(isJSDocLinkLike, isJSDocNameReference, isJSDocMemberName)); - const meaning = isJSDoc ? SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value : SymbolFlags.Value; - if (name.kind === SyntaxKind.Identifier) { - if (isJSXTagName(name) && isJsxIntrinsicIdentifier(name)) { - const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement); - return symbol === unknownSymbol ? undefined : symbol; + const result = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ !isJSDoc, getHostSignatureFromJSDoc(name)); + if (!result && isJSDoc) { + const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration)); + if (container) { + return resolveJSDocMemberName(name, getSymbolOfNode(container)); } - const result = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ !isJSDoc, getHostSignatureFromJSDoc(name)); - if (!result && isJSDoc) { - const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration)); - if (container) { - return resolveJSDocMemberName(name, getSymbolOfNode(container)); - } - } - return result; } - else if (isPrivateIdentifier(name)) { - return getSymbolForPrivateIdentifierExpression(name); + return result; + } + else if (isPrivateIdentifier(name)) { + return getSymbolForPrivateIdentifierExpression(name); + } + else if (name.kind === SyntaxKind.PropertyAccessExpression || name.kind === SyntaxKind.QualifiedName) { + const links = getNodeLinks(name); + if (links.resolvedSymbol) { + return links.resolvedSymbol; } - else if (name.kind === SyntaxKind.PropertyAccessExpression || name.kind === SyntaxKind.QualifiedName) { - const links = getNodeLinks(name); - if (links.resolvedSymbol) { - return links.resolvedSymbol; - } - if (name.kind === SyntaxKind.PropertyAccessExpression) { - checkPropertyAccessExpression(name, CheckMode.Normal); - } - else { - checkQualifiedName(name, CheckMode.Normal); - } - if (!links.resolvedSymbol && isJSDoc && isQualifiedName(name)) { - return resolveJSDocMemberName(name); - } - return links.resolvedSymbol; + if (name.kind === SyntaxKind.PropertyAccessExpression) { + checkPropertyAccessExpression(name, CheckMode.Normal); + } + else { + checkQualifiedName(name, CheckMode.Normal); } - else if (isJSDocMemberName(name)) { + if (!links.resolvedSymbol && isJSDoc && isQualifiedName(name)) { return resolveJSDocMemberName(name); } + return links.resolvedSymbol; } - else if (isTypeReferenceIdentifier(name as EntityName)) { - const meaning = name.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace; - const symbol = resolveEntityName(name as EntityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); - return symbol && symbol !== unknownSymbol ? symbol : getUnresolvedSymbolForEntityName(name as EntityName); - } - if (name.parent.kind === SyntaxKind.TypePredicate) { - return resolveEntityName(name as Identifier, /*meaning*/ SymbolFlags.FunctionScopedVariable); + else if (isJSDocMemberName(name)) { + return resolveJSDocMemberName(name); } - - return undefined; + } + else if (isTypeReferenceIdentifier(name as EntityName)) { + const meaning = name.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace; + const symbol = resolveEntityName(name as EntityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); + return symbol && symbol !== unknownSymbol ? symbol : getUnresolvedSymbolForEntityName(name as EntityName); + } + if (name.parent.kind === SyntaxKind.TypePredicate) { + return resolveEntityName(name as Identifier, /*meaning*/ SymbolFlags.FunctionScopedVariable); } - /** - * Recursively resolve entity names and jsdoc instance references: - * 1. K#m as K.prototype.m for a class (or other value) K - * 2. K.m as K.prototype.m - * 3. I.m as I.m for a type I, or any other I.m that fails to resolve in (1) or (2) - * - * For unqualified names, a container K may be provided as a second argument. - */ - function resolveJSDocMemberName(name: EntityName | JSDocMemberName, container?: Symbol): Symbol | undefined { - if (isEntityName(name)) { - // resolve static values first - const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value; - let symbol = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); - if (!symbol && isIdentifier(name) && container) { - symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(container), name.escapedText, meaning)); - } - if (symbol) { - return symbol; - } + return undefined; + } + + /** + * Recursively resolve entity names and jsdoc instance references: + * 1. K#m as K.prototype.m for a class (or other value) K + * 2. K.m as K.prototype.m + * 3. I.m as I.m for a type I, or any other I.m that fails to resolve in (1) or (2) + * + * For unqualified names, a container K may be provided as a second argument. + */ + function resolveJSDocMemberName(name: EntityName | JSDocMemberName, container?: Symbol): Symbol | undefined { + if (isEntityName(name)) { + // resolve static values first + const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value; + let symbol = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); + if (!symbol && isIdentifier(name) && container) { + symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(container), name.escapedText, meaning)); } - const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left); - const right = isIdentifier(name) ? name.escapedText : name.right.escapedText; - if (left) { - const proto = left.flags & SymbolFlags.Value && getPropertyOfType(getTypeOfSymbol(left), "prototype" as __String); - const t = proto ? getTypeOfSymbol(proto) : getDeclaredTypeOfSymbol(left); - return getPropertyOfType(t, right); + if (symbol) { + return symbol; } } + const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left); + const right = isIdentifier(name) ? name.escapedText : name.right.escapedText; + if (left) { + const proto = left.flags & SymbolFlags.Value && getPropertyOfType(getTypeOfSymbol(left), "prototype" as __String); + const t = proto ? getTypeOfSymbol(proto) : getDeclaredTypeOfSymbol(left); + return getPropertyOfType(t, right); + } + } - function getSymbolAtLocation(node: Node, ignoreErrors?: boolean): Symbol | undefined { - if (node.kind === SyntaxKind.SourceFile) { - return isExternalModule(node as SourceFile) ? getMergedSymbol(node.symbol) : undefined; - } - const { parent } = node; - const grandParent = parent.parent; + function getSymbolAtLocation(node: Node, ignoreErrors?: boolean): Symbol | undefined { + if (node.kind === SyntaxKind.SourceFile) { + return isExternalModule(node as SourceFile) ? getMergedSymbol(node.symbol) : undefined; + } + const { parent } = node; + const grandParent = parent.parent; - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; - } + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return undefined; + } - if (isDeclarationNameOrImportPropertyName(node)) { - // This is a declaration, call getSymbolOfNode - const parentSymbol = getSymbolOfNode(parent)!; - return isImportOrExportSpecifier(node.parent) && node.parent.propertyName === node - ? getImmediateAliasedSymbol(parentSymbol) - : parentSymbol; - } - else if (isLiteralComputedPropertyDeclarationName(node)) { - return getSymbolOfNode(parent.parent); + if (isDeclarationNameOrImportPropertyName(node)) { + // This is a declaration, call getSymbolOfNode + const parentSymbol = getSymbolOfNode(parent)!; + return isImportOrExportSpecifier(node.parent) && node.parent.propertyName === node + ? getImmediateAliasedSymbol(parentSymbol) + : parentSymbol; + } + else if (isLiteralComputedPropertyDeclarationName(node)) { + return getSymbolOfNode(parent.parent); + } + + if (node.kind === SyntaxKind.Identifier) { + if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { + return getSymbolOfNameOrPropertyAccessExpression(node as Identifier); } + else if (parent.kind === SyntaxKind.BindingElement && + grandParent.kind === SyntaxKind.ObjectBindingPattern && + node === (parent as BindingElement).propertyName) { + const typeOfPattern = getTypeOfNode(grandParent); + const propertyDeclaration = getPropertyOfType(typeOfPattern, (node as Identifier).escapedText); - if (node.kind === SyntaxKind.Identifier) { - if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { - return getSymbolOfNameOrPropertyAccessExpression(node as Identifier); + if (propertyDeclaration) { + return propertyDeclaration; } - else if (parent.kind === SyntaxKind.BindingElement && - grandParent.kind === SyntaxKind.ObjectBindingPattern && - node === (parent as BindingElement).propertyName) { - const typeOfPattern = getTypeOfNode(grandParent); - const propertyDeclaration = getPropertyOfType(typeOfPattern, (node as Identifier).escapedText); - - if (propertyDeclaration) { - return propertyDeclaration; - } + } + else if (isMetaProperty(parent)) { + const parentType = getTypeOfNode(parent); + const propertyDeclaration = getPropertyOfType(parentType, (node as Identifier).escapedText); + if (propertyDeclaration) { + return propertyDeclaration; } - else if (isMetaProperty(parent)) { - const parentType = getTypeOfNode(parent); - const propertyDeclaration = getPropertyOfType(parentType, (node as Identifier).escapedText); - if (propertyDeclaration) { - return propertyDeclaration; - } - if (parent.keywordToken === SyntaxKind.NewKeyword) { - return checkNewTargetMetaProperty(parent).symbol; - } + if (parent.keywordToken === SyntaxKind.NewKeyword) { + return checkNewTargetMetaProperty(parent).symbol; } } + } - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PrivateIdentifier: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.QualifiedName: - return getSymbolOfNameOrPropertyAccessExpression(node as EntityName | PrivateIdentifier | PropertyAccessExpression); + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.QualifiedName: + return getSymbolOfNameOrPropertyAccessExpression(node as EntityName | PrivateIdentifier | PropertyAccessExpression); - case SyntaxKind.ThisKeyword: - const container = getThisContainer(node, /*includeArrowFunctions*/ false); - if (isFunctionLike(container)) { - const sig = getSignatureFromDeclaration(container); - if (sig.thisParameter) { - return sig.thisParameter; - } - } - if (isInExpressionContext(node)) { - return checkExpression(node as Expression).symbol; + case SyntaxKind.ThisKeyword: + const container = getThisContainer(node, /*includeArrowFunctions*/ false); + if (isFunctionLike(container)) { + const sig = getSignatureFromDeclaration(container); + if (sig.thisParameter) { + return sig.thisParameter; } - // falls through - - case SyntaxKind.ThisType: - return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode).symbol; - - case SyntaxKind.SuperKeyword: + } + if (isInExpressionContext(node)) { return checkExpression(node as Expression).symbol; + } + // falls through - case SyntaxKind.ConstructorKeyword: - // constructor keyword for an overload, should take us to the definition if it exist - const constructorDeclaration = node.parent; - if (constructorDeclaration && constructorDeclaration.kind === SyntaxKind.Constructor) { - return (constructorDeclaration.parent as ClassDeclaration).symbol; - } - return undefined; - - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - // 1). import x = require("./mo/*gotToDefinitionHere*/d") - // 2). External module name in an import declaration - // 3). Dynamic import call or require in javascript - // 4). type A = import("./f/*gotToDefinitionHere*/oo") - if ((isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node) || - ((node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration) && (node.parent as ImportDeclaration).moduleSpecifier === node) || - ((isInJSFile(node) && isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false)) || isImportCall(node.parent)) || - (isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent) - ) { - return resolveExternalModuleName(node, node as LiteralExpression, ignoreErrors); - } - if (isCallExpression(parent) && isBindableObjectDefinePropertyCall(parent) && parent.arguments[1] === node) { - return getSymbolOfNode(parent); - } - // falls through - - case SyntaxKind.NumericLiteral: - // index access - const objectType = isElementAccessExpression(parent) - ? parent.argumentExpression === node ? getTypeOfExpression(parent.expression) : undefined - : isLiteralTypeNode(parent) && isIndexedAccessTypeNode(grandParent) - ? getTypeFromTypeNode(grandParent.objectType) - : undefined; - return objectType && getPropertyOfType(objectType, escapeLeadingUnderscores((node as StringLiteral | NumericLiteral).text)); + case SyntaxKind.ThisType: + return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode).symbol; - case SyntaxKind.DefaultKeyword: - case SyntaxKind.FunctionKeyword: - case SyntaxKind.EqualsGreaterThanToken: - case SyntaxKind.ClassKeyword: - return getSymbolOfNode(node.parent); - case SyntaxKind.ImportType: - return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal, ignoreErrors) : undefined; + case SyntaxKind.SuperKeyword: + return checkExpression(node as Expression).symbol; - case SyntaxKind.ExportKeyword: - return isExportAssignment(node.parent) ? Debug.checkDefined(node.parent.symbol) : undefined; + case SyntaxKind.ConstructorKeyword: + // constructor keyword for an overload, should take us to the definition if it exist + const constructorDeclaration = node.parent; + if (constructorDeclaration && constructorDeclaration.kind === SyntaxKind.Constructor) { + return (constructorDeclaration.parent as ClassDeclaration).symbol; + } + return undefined; - case SyntaxKind.ImportKeyword: - case SyntaxKind.NewKeyword: - return isMetaProperty(node.parent) ? checkMetaPropertyKeyword(node.parent).symbol : undefined; - case SyntaxKind.MetaProperty: - return checkExpression(node as Expression).symbol; + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + // 1). import x = require("./mo/*gotToDefinitionHere*/d") + // 2). External module name in an import declaration + // 3). Dynamic import call or require in javascript + // 4). type A = import("./f/*gotToDefinitionHere*/oo") + if ((isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node) || + ((node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration) && (node.parent as ImportDeclaration).moduleSpecifier === node) || + ((isInJSFile(node) && isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false)) || isImportCall(node.parent)) || + (isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent) + ) { + return resolveExternalModuleName(node, node as LiteralExpression, ignoreErrors); + } + if (isCallExpression(parent) && isBindableObjectDefinePropertyCall(parent) && parent.arguments[1] === node) { + return getSymbolOfNode(parent); + } + // falls through + + case SyntaxKind.NumericLiteral: + // index access + const objectType = isElementAccessExpression(parent) + ? parent.argumentExpression === node ? getTypeOfExpression(parent.expression) : undefined + : isLiteralTypeNode(parent) && isIndexedAccessTypeNode(grandParent) + ? getTypeFromTypeNode(grandParent.objectType) + : undefined; + return objectType && getPropertyOfType(objectType, escapeLeadingUnderscores((node as StringLiteral | NumericLiteral).text)); + + case SyntaxKind.DefaultKeyword: + case SyntaxKind.FunctionKeyword: + case SyntaxKind.EqualsGreaterThanToken: + case SyntaxKind.ClassKeyword: + return getSymbolOfNode(node.parent); + case SyntaxKind.ImportType: + return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal, ignoreErrors) : undefined; + + case SyntaxKind.ExportKeyword: + return isExportAssignment(node.parent) ? Debug.checkDefined(node.parent.symbol) : undefined; + + case SyntaxKind.ImportKeyword: + case SyntaxKind.NewKeyword: + return isMetaProperty(node.parent) ? checkMetaPropertyKeyword(node.parent).symbol : undefined; + case SyntaxKind.MetaProperty: + return checkExpression(node as Expression).symbol; - default: - return undefined; - } + default: + return undefined; } + } - function getIndexInfosAtLocation(node: Node): readonly IndexInfo[] | undefined { - if (isIdentifier(node) && isPropertyAccessExpression(node.parent) && node.parent.name === node) { - const keyType = getLiteralTypeFromPropertyName(node); - const objectType = getTypeOfExpression(node.parent.expression); - const objectTypes = objectType.flags & TypeFlags.Union ? (objectType as UnionType).types : [objectType]; - return flatMap(objectTypes, t => filter(getIndexInfosOfType(t), info => isApplicableIndexType(keyType, info.keyType))); - } - return undefined; + function getIndexInfosAtLocation(node: Node): readonly IndexInfo[] | undefined { + if (isIdentifier(node) && isPropertyAccessExpression(node.parent) && node.parent.name === node) { + const keyType = getLiteralTypeFromPropertyName(node); + const objectType = getTypeOfExpression(node.parent.expression); + const objectTypes = objectType.flags & TypeFlags.Union ? (objectType as UnionType).types : [objectType]; + return flatMap(objectTypes, t => filter(getIndexInfosOfType(t), info => isApplicableIndexType(keyType, info.keyType))); } + return undefined; + } - function getShorthandAssignmentValueSymbol(location: Node | undefined): Symbol | undefined { - if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) { - return resolveEntityName((location as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Alias); - } - return undefined; + function getShorthandAssignmentValueSymbol(location: Node | undefined): Symbol | undefined { + if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) { + return resolveEntityName((location as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Alias); } + return undefined; + } - /** Returns the target of an export specifier without following aliases */ - function getExportSpecifierLocalTargetSymbol(node: ExportSpecifier | Identifier): Symbol | undefined { - if (isExportSpecifier(node)) { - return node.parent.parent.moduleSpecifier ? - getExternalModuleMember(node.parent.parent, node) : - resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); - } - else { - return resolveEntityName(node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); - } + /** Returns the target of an export specifier without following aliases */ + function getExportSpecifierLocalTargetSymbol(node: ExportSpecifier | Identifier): Symbol | undefined { + if (isExportSpecifier(node)) { + return node.parent.parent.moduleSpecifier ? + getExternalModuleMember(node.parent.parent, node) : + resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); } + else { + return resolveEntityName(node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); + } + } - function getTypeOfNode(node: Node): Type { - if (isSourceFile(node) && !isExternalModule(node)) { - return errorType; - } - - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return errorType; - } - - const classDecl = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node); - const classType = classDecl && getDeclaredTypeOfClassOrInterface(getSymbolOfNode(classDecl.class)); - if (isPartOfTypeNode(node)) { - const typeFromTypeNode = getTypeFromTypeNode(node as TypeNode); - return classType ? getTypeWithThisArgument(typeFromTypeNode, classType.thisType) : typeFromTypeNode; - } - - if (isExpressionNode(node)) { - return getRegularTypeOfExpression(node as Expression); - } + function getTypeOfNode(node: Node): Type { + if (isSourceFile(node) && !isExternalModule(node)) { + return errorType; + } - if (classType && !classDecl.isImplements) { - // A SyntaxKind.ExpressionWithTypeArguments is considered a type node, except when it occurs in the - // extends clause of a class. We handle that case here. - const baseType = firstOrUndefined(getBaseTypes(classType)); - return baseType ? getTypeWithThisArgument(baseType, classType.thisType) : errorType; - } + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return errorType; + } - if (isTypeDeclaration(node)) { - // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration - const symbol = getSymbolOfNode(node); - return getDeclaredTypeOfSymbol(symbol); - } + const classDecl = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node); + const classType = classDecl && getDeclaredTypeOfClassOrInterface(getSymbolOfNode(classDecl.class)); + if (isPartOfTypeNode(node)) { + const typeFromTypeNode = getTypeFromTypeNode(node as TypeNode); + return classType ? getTypeWithThisArgument(typeFromTypeNode, classType.thisType) : typeFromTypeNode; + } - if (isTypeDeclarationName(node)) { - const symbol = getSymbolAtLocation(node); - return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; - } + if (isExpressionNode(node)) { + return getRegularTypeOfExpression(node as Expression); + } - if (isDeclaration(node)) { - // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration - const symbol = getSymbolOfNode(node); - return getTypeOfSymbol(symbol); - } + if (classType && !classDecl.isImplements) { + // A SyntaxKind.ExpressionWithTypeArguments is considered a type node, except when it occurs in the + // extends clause of a class. We handle that case here. + const baseType = firstOrUndefined(getBaseTypes(classType)); + return baseType ? getTypeWithThisArgument(baseType, classType.thisType) : errorType; + } - if (isDeclarationNameOrImportPropertyName(node)) { - const symbol = getSymbolAtLocation(node); - if (symbol) { - return getTypeOfSymbol(symbol); - } - return errorType; - } + if (isTypeDeclaration(node)) { + // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration + const symbol = getSymbolOfNode(node); + return getDeclaredTypeOfSymbol(symbol); + } - if (isBindingPattern(node)) { - return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true) || errorType; - } + if (isTypeDeclarationName(node)) { + const symbol = getSymbolAtLocation(node); + return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; + } - if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { - const symbol = getSymbolAtLocation(node); - if (symbol) { - const declaredType = getDeclaredTypeOfSymbol(symbol); - return !isErrorType(declaredType) ? declaredType : getTypeOfSymbol(symbol); - } - } + if (isDeclaration(node)) { + // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration + const symbol = getSymbolOfNode(node); + return getTypeOfSymbol(symbol); + } - if (isMetaProperty(node.parent) && node.parent.keywordToken === node.kind) { - return checkMetaPropertyKeyword(node.parent); + if (isDeclarationNameOrImportPropertyName(node)) { + const symbol = getSymbolAtLocation(node); + if (symbol) { + return getTypeOfSymbol(symbol); } - return errorType; } - // Gets the type of object literal or array literal of destructuring assignment. - // { a } from - // for ( { a } of elems) { - // } - // [ a ] from - // [a] = [ some array ...] - function getTypeOfAssignmentPattern(expr: AssignmentPattern): Type | undefined { - Debug.assert(expr.kind === SyntaxKind.ObjectLiteralExpression || expr.kind === SyntaxKind.ArrayLiteralExpression); - // If this is from "for of" - // for ( { a } of elems) { - // } - if (expr.parent.kind === SyntaxKind.ForOfStatement) { - const iteratedType = checkRightHandSideOfForOf(expr.parent as ForOfStatement); - return checkDestructuringAssignment(expr, iteratedType || errorType); - } - // If this is from "for" initializer - // for ({a } = elems[0];.....) { } - if (expr.parent.kind === SyntaxKind.BinaryExpression) { - const iteratedType = getTypeOfExpression((expr.parent as BinaryExpression).right); - return checkDestructuringAssignment(expr, iteratedType || errorType); - } - // If this is from nested object binding pattern - // for ({ skills: { primary, secondary } } = multiRobot, i = 0; i < 1; i++) { - if (expr.parent.kind === SyntaxKind.PropertyAssignment) { - const node = cast(expr.parent.parent, isObjectLiteralExpression); - const typeOfParentObjectLiteral = getTypeOfAssignmentPattern(node) || errorType; - const propertyIndex = indexOfNode(node.properties, expr.parent); - return checkObjectLiteralDestructuringPropertyAssignment(node, typeOfParentObjectLiteral, propertyIndex); - } - // Array literal assignment - array destructuring pattern - const node = cast(expr.parent, isArrayLiteralExpression); - // [{ property1: p1, property2 }] = elems; - const typeOfArrayLiteral = getTypeOfAssignmentPattern(node) || errorType; - const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, typeOfArrayLiteral, undefinedType, expr.parent) || errorType; - return checkArrayLiteralDestructuringElementAssignment(node, typeOfArrayLiteral, node.elements.indexOf(expr), elementType); - } - - // Gets the property symbol corresponding to the property in destructuring assignment - // 'property1' from - // for ( { property1: a } of elems) { - // } - // 'property1' at location 'a' from: - // [a] = [ property1, property2 ] - function getPropertySymbolOfDestructuringAssignment(location: Identifier) { - // Get the type of the object or array literal and then look for property of given name in the type - const typeOfObjectLiteral = getTypeOfAssignmentPattern(cast(location.parent.parent, isAssignmentPattern)); - return typeOfObjectLiteral && getPropertyOfType(typeOfObjectLiteral, location.escapedText); + if (isBindingPattern(node)) { + return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true) || errorType; } - function getRegularTypeOfExpression(expr: Expression): Type { - if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) { - expr = expr.parent as Expression; + if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { + const symbol = getSymbolAtLocation(node); + if (symbol) { + const declaredType = getDeclaredTypeOfSymbol(symbol); + return !isErrorType(declaredType) ? declaredType : getTypeOfSymbol(symbol); } - return getRegularTypeOfLiteralType(getTypeOfExpression(expr)); } - /** - * Gets either the static or instance type of a class element, based on - * whether the element is declared as "static". - */ - function getParentTypeOfClassElement(node: ClassElement) { - const classSymbol = getSymbolOfNode(node.parent)!; - return isStatic(node) - ? getTypeOfSymbol(classSymbol) - : getDeclaredTypeOfSymbol(classSymbol); + if (isMetaProperty(node.parent) && node.parent.keywordToken === node.kind) { + return checkMetaPropertyKeyword(node.parent); } - function getClassElementPropertyKeyType(element: ClassElement) { - const name = element.name!; - switch (name.kind) { - case SyntaxKind.Identifier: - return getStringLiteralType(idText(name)); - case SyntaxKind.NumericLiteral: - case SyntaxKind.StringLiteral: - return getStringLiteralType(name.text); - case SyntaxKind.ComputedPropertyName: - const nameType = checkComputedPropertyName(name); - return isTypeAssignableToKind(nameType, TypeFlags.ESSymbolLike) ? nameType : stringType; - default: - return Debug.fail("Unsupported property name."); - } + return errorType; + } + + // Gets the type of object literal or array literal of destructuring assignment. + // { a } from + // for ( { a } of elems) { + // } + // [ a ] from + // [a] = [ some array ...] + function getTypeOfAssignmentPattern(expr: AssignmentPattern): Type | undefined { + Debug.assert(expr.kind === SyntaxKind.ObjectLiteralExpression || expr.kind === SyntaxKind.ArrayLiteralExpression); + // If this is from "for of" + // for ( { a } of elems) { + // } + if (expr.parent.kind === SyntaxKind.ForOfStatement) { + const iteratedType = checkRightHandSideOfForOf(expr.parent as ForOfStatement); + return checkDestructuringAssignment(expr, iteratedType || errorType); + } + // If this is from "for" initializer + // for ({a } = elems[0];.....) { } + if (expr.parent.kind === SyntaxKind.BinaryExpression) { + const iteratedType = getTypeOfExpression((expr.parent as BinaryExpression).right); + return checkDestructuringAssignment(expr, iteratedType || errorType); + } + // If this is from nested object binding pattern + // for ({ skills: { primary, secondary } } = multiRobot, i = 0; i < 1; i++) { + if (expr.parent.kind === SyntaxKind.PropertyAssignment) { + const node = cast(expr.parent.parent, isObjectLiteralExpression); + const typeOfParentObjectLiteral = getTypeOfAssignmentPattern(node) || errorType; + const propertyIndex = indexOfNode(node.properties, expr.parent); + return checkObjectLiteralDestructuringPropertyAssignment(node, typeOfParentObjectLiteral, propertyIndex); + } + // Array literal assignment - array destructuring pattern + const node = cast(expr.parent, isArrayLiteralExpression); + // [{ property1: p1, property2 }] = elems; + const typeOfArrayLiteral = getTypeOfAssignmentPattern(node) || errorType; + const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, typeOfArrayLiteral, undefinedType, expr.parent) || errorType; + return checkArrayLiteralDestructuringElementAssignment(node, typeOfArrayLiteral, node.elements.indexOf(expr), elementType); + } + + // Gets the property symbol corresponding to the property in destructuring assignment + // 'property1' from + // for ( { property1: a } of elems) { + // } + // 'property1' at location 'a' from: + // [a] = [ property1, property2 ] + function getPropertySymbolOfDestructuringAssignment(location: Identifier) { + // Get the type of the object or array literal and then look for property of given name in the type + const typeOfObjectLiteral = getTypeOfAssignmentPattern(cast(location.parent.parent, isAssignmentPattern)); + return typeOfObjectLiteral && getPropertyOfType(typeOfObjectLiteral, location.escapedText); + } + + function getRegularTypeOfExpression(expr: Expression): Type { + if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) { + expr = expr.parent as Expression; } + return getRegularTypeOfLiteralType(getTypeOfExpression(expr)); + } - // Return the list of properties of the given type, augmented with properties from Function - // if the type has call or construct signatures - function getAugmentedPropertiesOfType(type: Type): Symbol[] { - type = getApparentType(type); - const propsByName = createSymbolTable(getPropertiesOfType(type)); - const functionType = getSignaturesOfType(type, SignatureKind.Call).length ? globalCallableFunctionType : - getSignaturesOfType(type, SignatureKind.Construct).length ? globalNewableFunctionType : - undefined; - if (functionType) { - forEach(getPropertiesOfType(functionType), p => { - if (!propsByName.has(p.escapedName)) { - propsByName.set(p.escapedName, p); - } - }); - } - return getNamedMembers(propsByName); + /** + * Gets either the static or instance type of a class element, based on + * whether the element is declared as "static". + */ + function getParentTypeOfClassElement(node: ClassElement) { + const classSymbol = getSymbolOfNode(node.parent)!; + return isStatic(node) + ? getTypeOfSymbol(classSymbol) + : getDeclaredTypeOfSymbol(classSymbol); + } + + function getClassElementPropertyKeyType(element: ClassElement) { + const name = element.name!; + switch (name.kind) { + case SyntaxKind.Identifier: + return getStringLiteralType(idText(name)); + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + return getStringLiteralType(name.text); + case SyntaxKind.ComputedPropertyName: + const nameType = checkComputedPropertyName(name); + return isTypeAssignableToKind(nameType, TypeFlags.ESSymbolLike) ? nameType : stringType; + default: + return Debug.fail("Unsupported property name."); } + } - function typeHasCallOrConstructSignatures(type: Type): boolean { - return ts.typeHasCallOrConstructSignatures(type, checker); + // Return the list of properties of the given type, augmented with properties from Function + // if the type has call or construct signatures + function getAugmentedPropertiesOfType(type: Type): Symbol[] { + type = getApparentType(type); + const propsByName = createSymbolTable(getPropertiesOfType(type)); + const functionType = getSignaturesOfType(type, SignatureKind.Call).length ? globalCallableFunctionType : + getSignaturesOfType(type, SignatureKind.Construct).length ? globalNewableFunctionType : + undefined; + if (functionType) { + forEach(getPropertiesOfType(functionType), p => { + if (!propsByName.has(p.escapedName)) { + propsByName.set(p.escapedName, p); + } + }); } + return getNamedMembers(propsByName); + } + + function typeHasCallOrConstructSignatures(type: Type): boolean { + return ts.typeHasCallOrConstructSignatures(type, checker); + } - function getRootSymbols(symbol: Symbol): readonly Symbol[] { - const roots = getImmediateRootSymbols(symbol); - return roots ? flatMap(roots, getRootSymbols) : [symbol]; + function getRootSymbols(symbol: Symbol): readonly Symbol[] { + const roots = getImmediateRootSymbols(symbol); + return roots ? flatMap(roots, getRootSymbols) : [symbol]; + } + function getImmediateRootSymbols(symbol: Symbol): readonly Symbol[] | undefined { + if (getCheckFlags(symbol) & CheckFlags.Synthetic) { + return mapDefined(getSymbolLinks(symbol).containingType!.types, type => getPropertyOfType(type, symbol.escapedName)); } - function getImmediateRootSymbols(symbol: Symbol): readonly Symbol[] | undefined { - if (getCheckFlags(symbol) & CheckFlags.Synthetic) { - return mapDefined(getSymbolLinks(symbol).containingType!.types, type => getPropertyOfType(type, symbol.escapedName)); - } - else if (symbol.flags & SymbolFlags.Transient) { - const { leftSpread, rightSpread, syntheticOrigin } = symbol as TransientSymbol; - return leftSpread ? [leftSpread, rightSpread!] - : syntheticOrigin ? [syntheticOrigin] - : singleElementArray(tryGetAliasTarget(symbol)); - } - return undefined; + else if (symbol.flags & SymbolFlags.Transient) { + const { leftSpread, rightSpread, syntheticOrigin } = symbol as TransientSymbol; + return leftSpread ? [leftSpread, rightSpread!] + : syntheticOrigin ? [syntheticOrigin] + : singleElementArray(tryGetAliasTarget(symbol)); } - function tryGetAliasTarget(symbol: Symbol): Symbol | undefined { - let target: Symbol | undefined; - let next: Symbol | undefined = symbol; - while (next = getSymbolLinks(next).target) { - target = next; - } - return target; + return undefined; + } + function tryGetAliasTarget(symbol: Symbol): Symbol | undefined { + let target: Symbol | undefined; + let next: Symbol | undefined = symbol; + while (next = getSymbolLinks(next).target) { + target = next; } + return target; + } - // Emitter support + // Emitter support + + function isArgumentsLocalBinding(nodeIn: Identifier): boolean { + // Note: does not handle isShorthandPropertyAssignment (and probably a few more) + if (isGeneratedIdentifier(nodeIn)) return false; + const node = getParseTreeNode(nodeIn, isIdentifier); + if (!node) return false; + const parent = node.parent; + if (!parent) return false; + const isPropertyName = ((isPropertyAccessExpression(parent) + || isPropertyAssignment(parent)) + && parent.name === node); + return !isPropertyName && getReferencedValueSymbol(node) === argumentsSymbol; + } - function isArgumentsLocalBinding(nodeIn: Identifier): boolean { - // Note: does not handle isShorthandPropertyAssignment (and probably a few more) - if (isGeneratedIdentifier(nodeIn)) return false; - const node = getParseTreeNode(nodeIn, isIdentifier); - if (!node) return false; - const parent = node.parent; - if (!parent) return false; - const isPropertyName = ((isPropertyAccessExpression(parent) - || isPropertyAssignment(parent)) - && parent.name === node); - return !isPropertyName && getReferencedValueSymbol(node) === argumentsSymbol; + function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean { + let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression); + if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) { + // If the module is not found or is shorthand, assume that it may export a value. + return true; } - function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean { - let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression); - if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) { - // If the module is not found or is shorthand, assume that it may export a value. - return true; - } + const hasExportAssignment = hasExportAssignmentSymbol(moduleSymbol); + // if module has export assignment then 'resolveExternalModuleSymbol' will return resolved symbol for export assignment + // otherwise it will return moduleSymbol itself + moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); - const hasExportAssignment = hasExportAssignmentSymbol(moduleSymbol); - // if module has export assignment then 'resolveExternalModuleSymbol' will return resolved symbol for export assignment - // otherwise it will return moduleSymbol itself - moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); + const symbolLinks = getSymbolLinks(moduleSymbol); + if (symbolLinks.exportsSomeValue === undefined) { + // for export assignments - check if resolved symbol for RHS is itself a value + // otherwise - check if at least one export is value + symbolLinks.exportsSomeValue = hasExportAssignment + ? !!(moduleSymbol.flags & SymbolFlags.Value) + : forEachEntry(getExportsOfModule(moduleSymbol), isValue); + } - const symbolLinks = getSymbolLinks(moduleSymbol); - if (symbolLinks.exportsSomeValue === undefined) { - // for export assignments - check if resolved symbol for RHS is itself a value - // otherwise - check if at least one export is value - symbolLinks.exportsSomeValue = hasExportAssignment - ? !!(moduleSymbol.flags & SymbolFlags.Value) - : forEachEntry(getExportsOfModule(moduleSymbol), isValue); - } + return symbolLinks.exportsSomeValue!; + + function isValue(s: Symbol): boolean { + s = resolveSymbol(s); + return s && !!(s.flags & SymbolFlags.Value); + } + } - return symbolLinks.exportsSomeValue!; + function isNameOfModuleOrEnumDeclaration(node: Identifier) { + return isModuleOrEnumDeclaration(node.parent) && node === node.parent.name; + } - function isValue(s: Symbol): boolean { - s = resolveSymbol(s); - return s && !!(s.flags & SymbolFlags.Value); + // When resolved as an expression identifier, if the given node references an exported entity, return the declaration + // node of the exported entity's container. Otherwise, return undefined. + function getReferencedExportContainer(nodeIn: Identifier, prefixLocals?: boolean): SourceFile | ModuleDeclaration | EnumDeclaration | undefined { + const node = getParseTreeNode(nodeIn, isIdentifier); + if (node) { + // When resolving the export container for the name of a module or enum + // declaration, we need to start resolution at the declaration's container. + // Otherwise, we could incorrectly resolve the export container as the + // declaration if it contains an exported member with the same name. + let symbol = getReferencedValueSymbol(node, /*startInDeclarationContainer*/ isNameOfModuleOrEnumDeclaration(node)); + if (symbol) { + if (symbol.flags & SymbolFlags.ExportValue) { + // If we reference an exported entity within the same module declaration, then whether + // we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the + // kinds that we do NOT prefix. + const exportSymbol = getMergedSymbol(symbol.exportSymbol!); + if (!prefixLocals && exportSymbol.flags & SymbolFlags.ExportHasLocal && !(exportSymbol.flags & SymbolFlags.Variable)) { + return undefined; + } + symbol = exportSymbol; + } + const parentSymbol = getParentOfSymbol(symbol); + if (parentSymbol) { + if (parentSymbol.flags & SymbolFlags.ValueModule && parentSymbol.valueDeclaration?.kind === SyntaxKind.SourceFile) { + const symbolFile = parentSymbol.valueDeclaration as SourceFile; + const referenceFile = getSourceFileOfNode(node); + // If `node` accesses an export and that export isn't in the same file, then symbol is a namespace export, so return undefined. + const symbolIsUmdExport = symbolFile !== referenceFile; + return symbolIsUmdExport ? undefined : symbolFile; + } + return findAncestor(node.parent, (n): n is ModuleDeclaration | EnumDeclaration => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol); + } } } + } - function isNameOfModuleOrEnumDeclaration(node: Identifier) { - return isModuleOrEnumDeclaration(node.parent) && node === node.parent.name; + // When resolved as an expression identifier, if the given node references an import, return the declaration of + // that import. Otherwise, return undefined. + function getReferencedImportDeclaration(nodeIn: Identifier): Declaration | undefined { + if (nodeIn.generatedImportReference) { + return nodeIn.generatedImportReference; + } + const node = getParseTreeNode(nodeIn, isIdentifier); + if (node) { + const symbol = getReferencedValueSymbol(node); + // We should only get the declaration of an alias if there isn't a local value + // declaration for the symbol + if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !getTypeOnlyAliasDeclaration(symbol)) { + return getDeclarationOfAliasSymbol(symbol); + } } - // When resolved as an expression identifier, if the given node references an exported entity, return the declaration - // node of the exported entity's container. Otherwise, return undefined. - function getReferencedExportContainer(nodeIn: Identifier, prefixLocals?: boolean): SourceFile | ModuleDeclaration | EnumDeclaration | undefined { - const node = getParseTreeNode(nodeIn, isIdentifier); - if (node) { - // When resolving the export container for the name of a module or enum - // declaration, we need to start resolution at the declaration's container. - // Otherwise, we could incorrectly resolve the export container as the - // declaration if it contains an exported member with the same name. - let symbol = getReferencedValueSymbol(node, /*startInDeclarationContainer*/ isNameOfModuleOrEnumDeclaration(node)); - if (symbol) { - if (symbol.flags & SymbolFlags.ExportValue) { - // If we reference an exported entity within the same module declaration, then whether - // we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the - // kinds that we do NOT prefix. - const exportSymbol = getMergedSymbol(symbol.exportSymbol!); - if (!prefixLocals && exportSymbol.flags & SymbolFlags.ExportHasLocal && !(exportSymbol.flags & SymbolFlags.Variable)) { - return undefined; - } - symbol = exportSymbol; + return undefined; + } + + function isSymbolOfDestructuredElementOfCatchBinding(symbol: Symbol) { + return symbol.valueDeclaration + && isBindingElement(symbol.valueDeclaration) + && walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause; + } + + function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean { + if (symbol.flags & SymbolFlags.BlockScoped && symbol.valueDeclaration && !isSourceFile(symbol.valueDeclaration)) { + const links = getSymbolLinks(symbol); + if (links.isDeclarationWithCollidingName === undefined) { + const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); + if (isStatementWithLocals(container) || isSymbolOfDestructuredElementOfCatchBinding(symbol)) { + const nodeLinks = getNodeLinks(symbol.valueDeclaration); + if (resolveName(container.parent, symbol.escapedName, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)) { + // redeclaration - always should be renamed + links.isDeclarationWithCollidingName = true; + } + else if (nodeLinks.flags & NodeCheckFlags.CapturedBlockScopedBinding) { + // binding is captured in the function + // should be renamed if: + // - binding is not top level - top level bindings never collide with anything + // AND + // - binding is not declared in loop, should be renamed to avoid name reuse across siblings + // let a, b + // { let x = 1; a = () => x; } + // { let x = 100; b = () => x; } + // console.log(a()); // should print '1' + // console.log(b()); // should print '100' + // OR + // - binding is declared inside loop but not in inside initializer of iteration statement or directly inside loop body + // * variables from initializer are passed to rewritten loop body as parameters so they are not captured directly + // * variables that are declared immediately in loop body will become top level variable after loop is rewritten and thus + // they will not collide with anything + const isDeclaredInLoop = nodeLinks.flags & NodeCheckFlags.BlockScopedBindingInLoop; + const inLoopInitializer = isIterationStatement(container, /*lookInLabeledStatements*/ false); + const inLoopBodyBlock = container.kind === SyntaxKind.Block && isIterationStatement(container.parent, /*lookInLabeledStatements*/ false); + + links.isDeclarationWithCollidingName = !isBlockScopedContainerTopLevel(container) && (!isDeclaredInLoop || (!inLoopInitializer && !inLoopBodyBlock)); } - const parentSymbol = getParentOfSymbol(symbol); - if (parentSymbol) { - if (parentSymbol.flags & SymbolFlags.ValueModule && parentSymbol.valueDeclaration?.kind === SyntaxKind.SourceFile) { - const symbolFile = parentSymbol.valueDeclaration as SourceFile; - const referenceFile = getSourceFileOfNode(node); - // If `node` accesses an export and that export isn't in the same file, then symbol is a namespace export, so return undefined. - const symbolIsUmdExport = symbolFile !== referenceFile; - return symbolIsUmdExport ? undefined : symbolFile; - } - return findAncestor(node.parent, (n): n is ModuleDeclaration | EnumDeclaration => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol); + else { + links.isDeclarationWithCollidingName = false; } } } + return links.isDeclarationWithCollidingName!; } + return false; + } - // When resolved as an expression identifier, if the given node references an import, return the declaration of - // that import. Otherwise, return undefined. - function getReferencedImportDeclaration(nodeIn: Identifier): Declaration | undefined { - if (nodeIn.generatedImportReference) { - return nodeIn.generatedImportReference; - } + // When resolved as an expression identifier, if the given node references a nested block scoped entity with + // a name that either hides an existing name or might hide it when compiled downlevel, + // return the declaration of that entity. Otherwise, return undefined. + function getReferencedDeclarationWithCollidingName(nodeIn: Identifier): Declaration | undefined { + if (!isGeneratedIdentifier(nodeIn)) { const node = getParseTreeNode(nodeIn, isIdentifier); if (node) { const symbol = getReferencedValueSymbol(node); - // We should only get the declaration of an alias if there isn't a local value - // declaration for the symbol - if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !getTypeOnlyAliasDeclaration(symbol)) { - return getDeclarationOfAliasSymbol(symbol); + if (symbol && isSymbolOfDeclarationWithCollidingName(symbol)) { + return symbol.valueDeclaration; } } - - return undefined; } - function isSymbolOfDestructuredElementOfCatchBinding(symbol: Symbol) { - return symbol.valueDeclaration - && isBindingElement(symbol.valueDeclaration) - && walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause; - } + return undefined; + } - function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean { - if (symbol.flags & SymbolFlags.BlockScoped && symbol.valueDeclaration && !isSourceFile(symbol.valueDeclaration)) { - const links = getSymbolLinks(symbol); - if (links.isDeclarationWithCollidingName === undefined) { - const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); - if (isStatementWithLocals(container) || isSymbolOfDestructuredElementOfCatchBinding(symbol)) { - const nodeLinks = getNodeLinks(symbol.valueDeclaration); - if (resolveName(container.parent, symbol.escapedName, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)) { - // redeclaration - always should be renamed - links.isDeclarationWithCollidingName = true; - } - else if (nodeLinks.flags & NodeCheckFlags.CapturedBlockScopedBinding) { - // binding is captured in the function - // should be renamed if: - // - binding is not top level - top level bindings never collide with anything - // AND - // - binding is not declared in loop, should be renamed to avoid name reuse across siblings - // let a, b - // { let x = 1; a = () => x; } - // { let x = 100; b = () => x; } - // console.log(a()); // should print '1' - // console.log(b()); // should print '100' - // OR - // - binding is declared inside loop but not in inside initializer of iteration statement or directly inside loop body - // * variables from initializer are passed to rewritten loop body as parameters so they are not captured directly - // * variables that are declared immediately in loop body will become top level variable after loop is rewritten and thus - // they will not collide with anything - const isDeclaredInLoop = nodeLinks.flags & NodeCheckFlags.BlockScopedBindingInLoop; - const inLoopInitializer = isIterationStatement(container, /*lookInLabeledStatements*/ false); - const inLoopBodyBlock = container.kind === SyntaxKind.Block && isIterationStatement(container.parent, /*lookInLabeledStatements*/ false); - - links.isDeclarationWithCollidingName = !isBlockScopedContainerTopLevel(container) && (!isDeclaredInLoop || (!inLoopInitializer && !inLoopBodyBlock)); - } - else { - links.isDeclarationWithCollidingName = false; - } - } - } - return links.isDeclarationWithCollidingName!; + // Return true if the given node is a declaration of a nested block scoped entity with a name that either hides an + // existing name or might hide a name when compiled downlevel + function isDeclarationWithCollidingName(nodeIn: Declaration): boolean { + const node = getParseTreeNode(nodeIn, isDeclaration); + if (node) { + const symbol = getSymbolOfNode(node); + if (symbol) { + return isSymbolOfDeclarationWithCollidingName(symbol); } - return false; } - // When resolved as an expression identifier, if the given node references a nested block scoped entity with - // a name that either hides an existing name or might hide it when compiled downlevel, - // return the declaration of that entity. Otherwise, return undefined. - function getReferencedDeclarationWithCollidingName(nodeIn: Identifier): Declaration | undefined { - if (!isGeneratedIdentifier(nodeIn)) { - const node = getParseTreeNode(nodeIn, isIdentifier); - if (node) { - const symbol = getReferencedValueSymbol(node); - if (symbol && isSymbolOfDeclarationWithCollidingName(symbol)) { - return symbol.valueDeclaration; - } - } - } - - return undefined; - } + return false; + } - // Return true if the given node is a declaration of a nested block scoped entity with a name that either hides an - // existing name or might hide a name when compiled downlevel - function isDeclarationWithCollidingName(nodeIn: Declaration): boolean { - const node = getParseTreeNode(nodeIn, isDeclaration); - if (node) { + function isValueAliasDeclaration(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return isAliasResolvedToValue(getSymbolOfNode(node)); + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: const symbol = getSymbolOfNode(node); - if (symbol) { - return isSymbolOfDeclarationWithCollidingName(symbol); - } - } - - return false; + return !!symbol && isAliasResolvedToValue(symbol) && !getTypeOnlyAliasDeclaration(symbol); + case SyntaxKind.ExportDeclaration: + const exportClause = (node as ExportDeclaration).exportClause; + return !!exportClause && ( + isNamespaceExport(exportClause) || + some(exportClause.elements, isValueAliasDeclaration) + ); + case SyntaxKind.ExportAssignment: + return (node as ExportAssignment).expression && (node as ExportAssignment).expression.kind === SyntaxKind.Identifier ? + isAliasResolvedToValue(getSymbolOfNode(node)) : + true; } + return false; + } - function isValueAliasDeclaration(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ImportEqualsDeclaration: - return isAliasResolvedToValue(getSymbolOfNode(node)); - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - const symbol = getSymbolOfNode(node); - return !!symbol && isAliasResolvedToValue(symbol) && !getTypeOnlyAliasDeclaration(symbol); - case SyntaxKind.ExportDeclaration: - const exportClause = (node as ExportDeclaration).exportClause; - return !!exportClause && ( - isNamespaceExport(exportClause) || - some(exportClause.elements, isValueAliasDeclaration) - ); - case SyntaxKind.ExportAssignment: - return (node as ExportAssignment).expression && (node as ExportAssignment).expression.kind === SyntaxKind.Identifier ? - isAliasResolvedToValue(getSymbolOfNode(node)) : - true; - } + function isTopLevelValueImportEqualsWithEntityName(nodeIn: ImportEqualsDeclaration): boolean { + const node = getParseTreeNode(nodeIn, isImportEqualsDeclaration); + if (node === undefined || node.parent.kind !== SyntaxKind.SourceFile || !isInternalModuleImportEqualsDeclaration(node)) { + // parent is not source file or it is not reference to internal module return false; } - function isTopLevelValueImportEqualsWithEntityName(nodeIn: ImportEqualsDeclaration): boolean { - const node = getParseTreeNode(nodeIn, isImportEqualsDeclaration); - if (node === undefined || node.parent.kind !== SyntaxKind.SourceFile || !isInternalModuleImportEqualsDeclaration(node)) { - // parent is not source file or it is not reference to internal module - return false; - } + const isValue = isAliasResolvedToValue(getSymbolOfNode(node)); + return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference); + } - const isValue = isAliasResolvedToValue(getSymbolOfNode(node)); - return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference); + function isAliasResolvedToValue(symbol: Symbol | undefined): boolean { + if (!symbol) { + return false; + } + const target = getExportSymbolOfValueSymbolIfExported(resolveAlias(symbol)); + if (target === unknownSymbol) { + return true; } + // const enums and modules that contain only const enums are not considered values from the emit perspective + // unless 'preserveConstEnums' option is set to true + return !!(target.flags & SymbolFlags.Value) && + (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target)); + } - function isAliasResolvedToValue(symbol: Symbol | undefined): boolean { - if (!symbol) { - return false; + function isConstEnumOrConstEnumOnlyModule(s: Symbol): boolean { + return isConstEnumSymbol(s) || !!s.constEnumOnlyModule; + } + + function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { + if (isAliasSymbolDeclaration(node)) { + const symbol = getSymbolOfNode(node); + const links = symbol && getSymbolLinks(symbol); + if (links?.referenced) { + return true; } - const target = getExportSymbolOfValueSymbolIfExported(resolveAlias(symbol)); - if (target === unknownSymbol) { + const target = getSymbolLinks(symbol!).target; // TODO: GH#18217 + if (target && getEffectiveModifierFlags(node) & ModifierFlags.Export && + target.flags & SymbolFlags.Value && + (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target))) { + // An `export import ... =` of a value symbol is always considered referenced return true; } - // const enums and modules that contain only const enums are not considered values from the emit perspective - // unless 'preserveConstEnums' option is set to true - return !!(target.flags & SymbolFlags.Value) && - (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target)); } - function isConstEnumOrConstEnumOnlyModule(s: Symbol): boolean { - return isConstEnumSymbol(s) || !!s.constEnumOnlyModule; - } - - function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { - if (isAliasSymbolDeclaration(node)) { - const symbol = getSymbolOfNode(node); - const links = symbol && getSymbolLinks(symbol); - if (links?.referenced) { - return true; - } - const target = getSymbolLinks(symbol!).target; // TODO: GH#18217 - if (target && getEffectiveModifierFlags(node) & ModifierFlags.Export && - target.flags & SymbolFlags.Value && - (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target))) { - // An `export import ... =` of a value symbol is always considered referenced - return true; - } - } - - if (checkChildren) { - return !!forEachChild(node, node => isReferencedAliasDeclaration(node, checkChildren)); - } - return false; + if (checkChildren) { + return !!forEachChild(node, node => isReferencedAliasDeclaration(node, checkChildren)); } + return false; + } - function isImplementationOfOverload(node: SignatureDeclaration) { - if (nodeIsPresent((node as FunctionLikeDeclaration).body)) { - if (isGetAccessor(node) || isSetAccessor(node)) return false; // Get or set accessors can never be overload implementations, but can have up to 2 signatures - const symbol = getSymbolOfNode(node); - const signaturesOfSymbol = getSignaturesOfSymbol(symbol); - // If this function body corresponds to function with multiple signature, it is implementation of overload + function isImplementationOfOverload(node: SignatureDeclaration) { + if (nodeIsPresent((node as FunctionLikeDeclaration).body)) { + if (isGetAccessor(node) || isSetAccessor(node)) return false; // Get or set accessors can never be overload implementations, but can have up to 2 signatures + const symbol = getSymbolOfNode(node); + const signaturesOfSymbol = getSignaturesOfSymbol(symbol); + // If this function body corresponds to function with multiple signature, it is implementation of overload + // e.g.: function foo(a: string): string; + // function foo(a: number): number; + // function foo(a: any) { // This is implementation of the overloads + // return a; + // } + return signaturesOfSymbol.length > 1 || + // If there is single signature for the symbol, it is overload if that signature isn't coming from the node // e.g.: function foo(a: string): string; - // function foo(a: number): number; // function foo(a: any) { // This is implementation of the overloads // return a; // } - return signaturesOfSymbol.length > 1 || - // If there is single signature for the symbol, it is overload if that signature isn't coming from the node - // e.g.: function foo(a: string): string; - // function foo(a: any) { // This is implementation of the overloads - // return a; - // } - (signaturesOfSymbol.length === 1 && signaturesOfSymbol[0].declaration !== node); - } - return false; + (signaturesOfSymbol.length === 1 && signaturesOfSymbol[0].declaration !== node); } + return false; + } - function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean { - return !!strictNullChecks && - !isOptionalParameter(parameter) && - !isJSDocParameterTag(parameter) && - !!parameter.initializer && - !hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); - } + function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean { + return !!strictNullChecks && + !isOptionalParameter(parameter) && + !isJSDocParameterTag(parameter) && + !!parameter.initializer && + !hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); + } - function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration) { - return strictNullChecks && - isOptionalParameter(parameter) && - !parameter.initializer && - hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); - } + function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration) { + return strictNullChecks && + isOptionalParameter(parameter) && + !parameter.initializer && + hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); + } - function isOptionalUninitializedParameter(parameter: ParameterDeclaration) { - return !!strictNullChecks && - isOptionalParameter(parameter) && - !parameter.initializer; - } + function isOptionalUninitializedParameter(parameter: ParameterDeclaration) { + return !!strictNullChecks && + isOptionalParameter(parameter) && + !parameter.initializer; + } - function isExpandoFunctionDeclaration(node: Declaration): boolean { - const declaration = getParseTreeNode(node, isFunctionDeclaration); - if (!declaration) { - return false; - } - const symbol = getSymbolOfNode(declaration); - if (!symbol || !(symbol.flags & SymbolFlags.Function)) { - return false; - } - return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && p.valueDeclaration && isPropertyAccessExpression(p.valueDeclaration)); + function isExpandoFunctionDeclaration(node: Declaration): boolean { + const declaration = getParseTreeNode(node, isFunctionDeclaration); + if (!declaration) { + return false; + } + const symbol = getSymbolOfNode(declaration); + if (!symbol || !(symbol.flags & SymbolFlags.Function)) { + return false; } + return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && p.valueDeclaration && isPropertyAccessExpression(p.valueDeclaration)); + } - function getPropertiesOfContainerFunction(node: Declaration): Symbol[] { - const declaration = getParseTreeNode(node, isFunctionDeclaration); - if (!declaration) { - return emptyArray; - } - const symbol = getSymbolOfNode(declaration); - return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray; + function getPropertiesOfContainerFunction(node: Declaration): Symbol[] { + const declaration = getParseTreeNode(node, isFunctionDeclaration); + if (!declaration) { + return emptyArray; } + const symbol = getSymbolOfNode(declaration); + return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray; + } + + function getNodeCheckFlags(node: Node): NodeCheckFlags { + const nodeId = node.id || 0; + if (nodeId < 0 || nodeId >= nodeLinks.length) return 0; + return nodeLinks[nodeId]?.flags || 0; + } - function getNodeCheckFlags(node: Node): NodeCheckFlags { - const nodeId = node.id || 0; - if (nodeId < 0 || nodeId >= nodeLinks.length) return 0; - return nodeLinks[nodeId]?.flags || 0; + function getEnumMemberValue(node: EnumMember): string | number | undefined { + computeEnumMemberValues(node.parent); + return getNodeLinks(node).enumMemberValue; + } + + function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression { + switch (node.kind) { + case SyntaxKind.EnumMember: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return true; } + return false; + } - function getEnumMemberValue(node: EnumMember): string | number | undefined { - computeEnumMemberValues(node.parent); - return getNodeLinks(node).enumMemberValue; + function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { + if (node.kind === SyntaxKind.EnumMember) { + return getEnumMemberValue(node); } - function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression { - switch (node.kind) { - case SyntaxKind.EnumMember: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return true; + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol && (symbol.flags & SymbolFlags.EnumMember)) { + // inline property\index accesses only for const enums + const member = symbol.valueDeclaration as EnumMember; + if (isEnumConst(member.parent)) { + return getEnumMemberValue(member); } - return false; } - function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { - if (node.kind === SyntaxKind.EnumMember) { - return getEnumMemberValue(node); - } + return undefined; + } - const symbol = getNodeLinks(node).resolvedSymbol; - if (symbol && (symbol.flags & SymbolFlags.EnumMember)) { - // inline property\index accesses only for const enums - const member = symbol.valueDeclaration as EnumMember; - if (isEnumConst(member.parent)) { - return getEnumMemberValue(member); - } - } + function isFunctionType(type: Type): boolean { + return !!(type.flags & TypeFlags.Object) && getSignaturesOfType(type, SignatureKind.Call).length > 0; + } - return undefined; - } + function getTypeReferenceSerializationKind(typeNameIn: EntityName, location?: Node): TypeReferenceSerializationKind { + // ensure both `typeName` and `location` are parse tree nodes. + const typeName = getParseTreeNode(typeNameIn, isEntityName); + if (!typeName) return TypeReferenceSerializationKind.Unknown; - function isFunctionType(type: Type): boolean { - return !!(type.flags & TypeFlags.Object) && getSignaturesOfType(type, SignatureKind.Call).length > 0; + if (location) { + location = getParseTreeNode(location); + if (!location) return TypeReferenceSerializationKind.Unknown; } - function getTypeReferenceSerializationKind(typeNameIn: EntityName, location?: Node): TypeReferenceSerializationKind { - // ensure both `typeName` and `location` are parse tree nodes. - const typeName = getParseTreeNode(typeNameIn, isEntityName); - if (!typeName) return TypeReferenceSerializationKind.Unknown; + // Resolve the symbol as a value to ensure the type can be reached at runtime during emit. + let isTypeOnly = false; + if (isQualifiedName(typeName)) { + const rootValueSymbol = resolveEntityName(getFirstIdentifier(typeName), SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); + isTypeOnly = !!rootValueSymbol?.declarations?.every(isTypeOnlyImportOrExportDeclaration); + } + const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); + const resolvedSymbol = valueSymbol && valueSymbol.flags & SymbolFlags.Alias ? resolveAlias(valueSymbol) : valueSymbol; + isTypeOnly ||= !!valueSymbol?.declarations?.every(isTypeOnlyImportOrExportDeclaration); - if (location) { - location = getParseTreeNode(location); - if (!location) return TypeReferenceSerializationKind.Unknown; + // Resolve the symbol as a type so that we can provide a more useful hint for the type serializer. + const typeSymbol = resolveEntityName(typeName, SymbolFlags.Type, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location); + if (resolvedSymbol && resolvedSymbol === typeSymbol) { + const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); + if (globalPromiseSymbol && resolvedSymbol === globalPromiseSymbol) { + return TypeReferenceSerializationKind.Promise; } - // Resolve the symbol as a value to ensure the type can be reached at runtime during emit. - let isTypeOnly = false; - if (isQualifiedName(typeName)) { - const rootValueSymbol = resolveEntityName(getFirstIdentifier(typeName), SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); - isTypeOnly = !!rootValueSymbol?.declarations?.every(isTypeOnlyImportOrExportDeclaration); + const constructorType = getTypeOfSymbol(resolvedSymbol); + if (constructorType && isConstructorType(constructorType)) { + return isTypeOnly ? TypeReferenceSerializationKind.TypeWithCallSignature : TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; } - const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); - const resolvedSymbol = valueSymbol && valueSymbol.flags & SymbolFlags.Alias ? resolveAlias(valueSymbol) : valueSymbol; - isTypeOnly ||= !!valueSymbol?.declarations?.every(isTypeOnlyImportOrExportDeclaration); + } - // Resolve the symbol as a type so that we can provide a more useful hint for the type serializer. - const typeSymbol = resolveEntityName(typeName, SymbolFlags.Type, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location); - if (resolvedSymbol && resolvedSymbol === typeSymbol) { - const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); - if (globalPromiseSymbol && resolvedSymbol === globalPromiseSymbol) { - return TypeReferenceSerializationKind.Promise; - } + // We might not be able to resolve type symbol so use unknown type in that case (eg error case) + if (!typeSymbol) { + return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; + } + const type = getDeclaredTypeOfSymbol(typeSymbol); + if (isErrorType(type)) { + return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; + } + else if (type.flags & TypeFlags.AnyOrUnknown) { + return TypeReferenceSerializationKind.ObjectType; + } + else if (isTypeAssignableToKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) { + return TypeReferenceSerializationKind.VoidNullableOrNeverType; + } + else if (isTypeAssignableToKind(type, TypeFlags.BooleanLike)) { + return TypeReferenceSerializationKind.BooleanType; + } + else if (isTypeAssignableToKind(type, TypeFlags.NumberLike)) { + return TypeReferenceSerializationKind.NumberLikeType; + } + else if (isTypeAssignableToKind(type, TypeFlags.BigIntLike)) { + return TypeReferenceSerializationKind.BigIntLikeType; + } + else if (isTypeAssignableToKind(type, TypeFlags.StringLike)) { + return TypeReferenceSerializationKind.StringLikeType; + } + else if (isTupleType(type)) { + return TypeReferenceSerializationKind.ArrayLikeType; + } + else if (isTypeAssignableToKind(type, TypeFlags.ESSymbolLike)) { + return TypeReferenceSerializationKind.ESSymbolType; + } + else if (isFunctionType(type)) { + return TypeReferenceSerializationKind.TypeWithCallSignature; + } + else if (isArrayType(type)) { + return TypeReferenceSerializationKind.ArrayLikeType; + } + else { + return TypeReferenceSerializationKind.ObjectType; + } + } - const constructorType = getTypeOfSymbol(resolvedSymbol); - if (constructorType && isConstructorType(constructorType)) { - return isTypeOnly ? TypeReferenceSerializationKind.TypeWithCallSignature : TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; - } - } + function createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean) { + const declaration = getParseTreeNode(declarationIn, isVariableLikeOrAccessor); + if (!declaration) { + return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + } + // Get type of the symbol if this is the valid symbol otherwise get type at location + const symbol = getSymbolOfNode(declaration); + let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) + ? getWidenedLiteralType(getTypeOfSymbol(symbol)) + : errorType; + if (type.flags & TypeFlags.UniqueESSymbol && + type.symbol === symbol) { + flags |= NodeBuilderFlags.AllowUniqueESSymbolType; + } + if (addUndefined) { + type = getOptionalType(type); + } + return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } - // We might not be able to resolve type symbol so use unknown type in that case (eg error case) - if (!typeSymbol) { - return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; - } - const type = getDeclaredTypeOfSymbol(typeSymbol); - if (isErrorType(type)) { - return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; - } - else if (type.flags & TypeFlags.AnyOrUnknown) { - return TypeReferenceSerializationKind.ObjectType; - } - else if (isTypeAssignableToKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) { - return TypeReferenceSerializationKind.VoidNullableOrNeverType; - } - else if (isTypeAssignableToKind(type, TypeFlags.BooleanLike)) { - return TypeReferenceSerializationKind.BooleanType; - } - else if (isTypeAssignableToKind(type, TypeFlags.NumberLike)) { - return TypeReferenceSerializationKind.NumberLikeType; - } - else if (isTypeAssignableToKind(type, TypeFlags.BigIntLike)) { - return TypeReferenceSerializationKind.BigIntLikeType; - } - else if (isTypeAssignableToKind(type, TypeFlags.StringLike)) { - return TypeReferenceSerializationKind.StringLikeType; - } - else if (isTupleType(type)) { - return TypeReferenceSerializationKind.ArrayLikeType; - } - else if (isTypeAssignableToKind(type, TypeFlags.ESSymbolLike)) { - return TypeReferenceSerializationKind.ESSymbolType; - } - else if (isFunctionType(type)) { - return TypeReferenceSerializationKind.TypeWithCallSignature; - } - else if (isArrayType(type)) { - return TypeReferenceSerializationKind.ArrayLikeType; - } - else { - return TypeReferenceSerializationKind.ObjectType; - } + function createReturnTypeOfSignatureDeclaration(signatureDeclarationIn: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { + const signatureDeclaration = getParseTreeNode(signatureDeclarationIn, isFunctionLike); + if (!signatureDeclaration) { + return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; } + const signature = getSignatureFromDeclaration(signatureDeclaration); + return nodeBuilder.typeToTypeNode(getReturnTypeOfSignature(signature), enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } - function createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean) { - const declaration = getParseTreeNode(declarationIn, isVariableLikeOrAccessor); - if (!declaration) { - return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; - } - // Get type of the symbol if this is the valid symbol otherwise get type at location - const symbol = getSymbolOfNode(declaration); - let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) - ? getWidenedLiteralType(getTypeOfSymbol(symbol)) - : errorType; - if (type.flags & TypeFlags.UniqueESSymbol && - type.symbol === symbol) { - flags |= NodeBuilderFlags.AllowUniqueESSymbolType; - } - if (addUndefined) { - type = getOptionalType(type); - } - return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + function createTypeOfExpression(exprIn: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { + const expr = getParseTreeNode(exprIn, isExpression); + if (!expr) { + return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + } + const type = getWidenedType(getRegularTypeOfExpression(expr)); + return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } + + function hasGlobalName(name: string): boolean { + return globals.has(escapeLeadingUnderscores(name)); + } + + function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol | undefined { + const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; + if (resolvedSymbol) { + return resolvedSymbol; } - function createReturnTypeOfSignatureDeclaration(signatureDeclarationIn: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { - const signatureDeclaration = getParseTreeNode(signatureDeclarationIn, isFunctionLike); - if (!signatureDeclaration) { - return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + let location: Node = reference; + if (startInDeclarationContainer) { + // When resolving the name of a declaration as a value, we need to start resolution + // at a point outside of the declaration. + const parent = reference.parent; + if (isDeclaration(parent) && reference === parent.name) { + location = getDeclarationContainer(parent); } - const signature = getSignatureFromDeclaration(signatureDeclaration); - return nodeBuilder.typeToTypeNode(getReturnTypeOfSignature(signature), enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); } - function createTypeOfExpression(exprIn: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { - const expr = getParseTreeNode(exprIn, isExpression); - if (!expr) { - return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + return resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + } + + function getReferencedValueDeclaration(referenceIn: Identifier): Declaration | undefined { + if (!isGeneratedIdentifier(referenceIn)) { + const reference = getParseTreeNode(referenceIn, isIdentifier); + if (reference) { + const symbol = getReferencedValueSymbol(reference); + if (symbol) { + return getExportSymbolOfValueSymbolIfExported(symbol).valueDeclaration; + } } - const type = getWidenedType(getRegularTypeOfExpression(expr)); - return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); } - function hasGlobalName(name: string): boolean { - return globals.has(escapeLeadingUnderscores(name)); + return undefined; + } + + function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean { + if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) { + return isFreshLiteralType(getTypeOfSymbol(getSymbolOfNode(node))); } + return false; + } + + function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression { + const enumResult = type.flags & TypeFlags.EnumLiteral ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing, /*flags*/ undefined, tracker) + : type === trueType ? factory.createTrue() : type === falseType && factory.createFalse(); + if (enumResult) return enumResult; + const literalValue = (type as LiteralType).value; + return typeof literalValue === "object" ? factory.createBigIntLiteral(literalValue) : + typeof literalValue === "number" ? factory.createNumericLiteral(literalValue) : + factory.createStringLiteral(literalValue); + } + + function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) { + const type = getTypeOfSymbol(getSymbolOfNode(node)); + return literalTypeToNode(type as FreshableType, node, tracker); + } - function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol | undefined { - const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; - if (resolvedSymbol) { - return resolvedSymbol; - } + function getJsxFactoryEntity(location: Node): EntityName | undefined { + return location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity; + } - let location: Node = reference; - if (startInDeclarationContainer) { - // When resolving the name of a declaration as a value, we need to start resolution - // at a point outside of the declaration. - const parent = reference.parent; - if (isDeclaration(parent) && reference === parent.name) { - location = getDeclarationContainer(parent); + function getJsxFragmentFactoryEntity(location: Node): EntityName | undefined { + if (location) { + const file = getSourceFileOfNode(location); + if (file) { + if (file.localJsxFragmentFactory) { + return file.localJsxFragmentFactory; + } + const jsxFragPragmas = file.pragmas.get("jsxfrag"); + const jsxFragPragma = isArray(jsxFragPragmas) ? jsxFragPragmas[0] : jsxFragPragmas; + if (jsxFragPragma) { + file.localJsxFragmentFactory = parseIsolatedEntityName(jsxFragPragma.arguments.factory, languageVersion); + return file.localJsxFragmentFactory; } } + } - return resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + if (compilerOptions.jsxFragmentFactory) { + return parseIsolatedEntityName(compilerOptions.jsxFragmentFactory, languageVersion); } + } - function getReferencedValueDeclaration(referenceIn: Identifier): Declaration | undefined { - if (!isGeneratedIdentifier(referenceIn)) { - const reference = getParseTreeNode(referenceIn, isIdentifier); - if (reference) { - const symbol = getReferencedValueSymbol(reference); - if (symbol) { - return getExportSymbolOfValueSymbolIfExported(symbol).valueDeclaration; - } + function createResolver(): EmitResolver { + // this variable and functions that use it are deliberately moved here from the outer scope + // to avoid scope pollution + const resolvedTypeReferenceDirectives = host.getResolvedTypeReferenceDirectives(); + let fileToDirective: ESMap; + if (resolvedTypeReferenceDirectives) { + // populate reverse mapping: file path -> type reference directive that was resolved to this file + fileToDirective = new Map(); + resolvedTypeReferenceDirectives.forEach((resolvedDirective, key) => { + if (!resolvedDirective || !resolvedDirective.resolvedFileName) { + return; } - } - - return undefined; + const file = host.getSourceFile(resolvedDirective.resolvedFileName); + if (file) { + // Add the transitive closure of path references loaded by this file (as long as they are not) + // part of an existing type reference. + addReferencedFilesToTypeDirective(file, key); + } + }); } - function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean { - if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) { - return isFreshLiteralType(getTypeOfSymbol(getSymbolOfNode(node))); + return { + getReferencedExportContainer, + getReferencedImportDeclaration, + getReferencedDeclarationWithCollidingName, + isDeclarationWithCollidingName, + isValueAliasDeclaration: nodeIn => { + const node = getParseTreeNode(nodeIn); + // Synthesized nodes are always treated like values. + return node ? isValueAliasDeclaration(node) : true; + }, + hasGlobalName, + isReferencedAliasDeclaration: (nodeIn, checkChildren?) => { + const node = getParseTreeNode(nodeIn); + // Synthesized nodes are always treated as referenced. + return node ? isReferencedAliasDeclaration(node, checkChildren) : true; + }, + getNodeCheckFlags: nodeIn => { + const node = getParseTreeNode(nodeIn); + return node ? getNodeCheckFlags(node) : 0; + }, + isTopLevelValueImportEqualsWithEntityName, + isDeclarationVisible, + isImplementationOfOverload, + isRequiredInitializedParameter, + isOptionalUninitializedParameterProperty, + isExpandoFunctionDeclaration, + getPropertiesOfContainerFunction, + createTypeOfDeclaration, + createReturnTypeOfSignatureDeclaration, + createTypeOfExpression, + createLiteralConstValue, + isSymbolAccessible, + isEntityNameVisible, + getConstantValue: nodeIn => { + const node = getParseTreeNode(nodeIn, canHaveConstantValue); + return node ? getConstantValue(node) : undefined; + }, + collectLinkedAliases, + getReferencedValueDeclaration, + getTypeReferenceSerializationKind, + isOptionalParameter, + moduleExportsSomeValue, + isArgumentsLocalBinding, + getExternalModuleFileFromDeclaration: nodeIn => { + const node = getParseTreeNode(nodeIn, hasPossibleExternalModuleReference); + return node && getExternalModuleFileFromDeclaration(node); + }, + getTypeReferenceDirectivesForEntityName, + getTypeReferenceDirectivesForSymbol, + isLiteralConstDeclaration, + isLateBound: (nodeIn: Declaration): nodeIn is LateBoundDeclaration => { + const node = getParseTreeNode(nodeIn, isDeclaration); + const symbol = node && getSymbolOfNode(node); + return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late); + }, + getJsxFactoryEntity, + getJsxFragmentFactoryEntity, + getAllAccessorDeclarations(accessor: AccessorDeclaration): AllAccessorDeclarations { + accessor = getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!; // TODO: GH#18217 + const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfNode(accessor), otherKind); + const firstAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? otherAccessor : accessor; + const secondAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? accessor : otherAccessor; + const setAccessor = accessor.kind === SyntaxKind.SetAccessor ? accessor : otherAccessor as SetAccessorDeclaration; + const getAccessor = accessor.kind === SyntaxKind.GetAccessor ? accessor : otherAccessor as GetAccessorDeclaration; + return { + firstAccessor, + secondAccessor, + setAccessor, + getAccessor + }; + }, + getSymbolOfExternalModuleSpecifier: moduleName => resolveExternalModuleNameWorker(moduleName, moduleName, /*moduleNotFoundError*/ undefined), + isBindingCapturedByNode: (node, decl) => { + const parseNode = getParseTreeNode(node); + const parseDecl = getParseTreeNode(decl); + return !!parseNode && !!parseDecl && (isVariableDeclaration(parseDecl) || isBindingElement(parseDecl)) && isBindingCapturedByNode(parseNode, parseDecl); + }, + getDeclarationStatementsForSourceFile: (node, flags, tracker, bundled) => { + const n = getParseTreeNode(node) as SourceFile; + Debug.assert(n && n.kind === SyntaxKind.SourceFile, "Non-sourcefile node passed into getDeclarationsForSourceFile"); + const sym = getSymbolOfNode(node); + if (!sym) { + return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker, bundled); + } + return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker, bundled); + }, + isImportRequiredByAugmentation, + }; + + function isImportRequiredByAugmentation(node: ImportDeclaration) { + const file = getSourceFileOfNode(node); + if (!file.symbol) return false; + const importTarget = getExternalModuleFileFromDeclaration(node); + if (!importTarget) return false; + if (importTarget === file) return false; + const exports = getExportsOfModule(file.symbol); + for (const s of arrayFrom(exports.values())) { + if (s.mergeId) { + const merged = getMergedSymbol(s); + if (merged.declarations) { + for (const d of merged.declarations) { + const declFile = getSourceFileOfNode(d); + if (declFile === importTarget) { + return true; + } + } + } + } } return false; } - function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression { - const enumResult = type.flags & TypeFlags.EnumLiteral ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing, /*flags*/ undefined, tracker) - : type === trueType ? factory.createTrue() : type === falseType && factory.createFalse(); - if (enumResult) return enumResult; - const literalValue = (type as LiteralType).value; - return typeof literalValue === "object" ? factory.createBigIntLiteral(literalValue) : - typeof literalValue === "number" ? factory.createNumericLiteral(literalValue) : - factory.createStringLiteral(literalValue); + function isInHeritageClause(node: PropertyAccessEntityNameExpression) { + return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && node.parent.parent && node.parent.parent.kind === SyntaxKind.HeritageClause; } - function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) { - const type = getTypeOfSymbol(getSymbolOfNode(node)); - return literalTypeToNode(type as FreshableType, node, tracker); - } + // defined here to avoid outer scope pollution + function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): string[] | undefined { + // program does not have any files with type reference directives - bail out + if (!fileToDirective) { + return undefined; + } + // property access can only be used as values, or types when within an expression with type arguments inside a heritage clause + // qualified names can only be used as types\namespaces + // identifiers are treated as values only if they appear in type queries + let meaning = SymbolFlags.Type | SymbolFlags.Namespace; + if ((node.kind === SyntaxKind.Identifier && isInTypeQuery(node)) || (node.kind === SyntaxKind.PropertyAccessExpression && !isInHeritageClause(node))) { + meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + } - function getJsxFactoryEntity(location: Node): EntityName | undefined { - return location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity; + const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true); + return symbol && symbol !== unknownSymbol ? getTypeReferenceDirectivesForSymbol(symbol, meaning) : undefined; } - function getJsxFragmentFactoryEntity(location: Node): EntityName | undefined { - if (location) { - const file = getSourceFileOfNode(location); - if (file) { - if (file.localJsxFragmentFactory) { - return file.localJsxFragmentFactory; + // defined here to avoid outer scope pollution + function getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[] | undefined { + // program does not have any files with type reference directives - bail out + if (!fileToDirective || !isSymbolFromTypeDeclarationFile(symbol)) { + return undefined; + } + // check what declarations in the symbol can contribute to the target meaning + let typeReferenceDirectives: string[] | undefined; + for (const decl of symbol.declarations!) { + // check meaning of the local symbol to see if declaration needs to be analyzed further + if (decl.symbol && decl.symbol.flags & meaning!) { + const file = getSourceFileOfNode(decl); + const typeReferenceDirective = fileToDirective.get(file.path); + if (typeReferenceDirective) { + (typeReferenceDirectives || (typeReferenceDirectives = [])).push(typeReferenceDirective); } - const jsxFragPragmas = file.pragmas.get("jsxfrag"); - const jsxFragPragma = isArray(jsxFragPragmas) ? jsxFragPragmas[0] : jsxFragPragmas; - if (jsxFragPragma) { - file.localJsxFragmentFactory = parseIsolatedEntityName(jsxFragPragma.arguments.factory, languageVersion); - return file.localJsxFragmentFactory; + else { + // found at least one entry that does not originate from type reference directive + return undefined; } } } - - if (compilerOptions.jsxFragmentFactory) { - return parseIsolatedEntityName(compilerOptions.jsxFragmentFactory, languageVersion); - } + return typeReferenceDirectives; } - function createResolver(): EmitResolver { - // this variable and functions that use it are deliberately moved here from the outer scope - // to avoid scope pollution - const resolvedTypeReferenceDirectives = host.getResolvedTypeReferenceDirectives(); - let fileToDirective: ESMap; - if (resolvedTypeReferenceDirectives) { - // populate reverse mapping: file path -> type reference directive that was resolved to this file - fileToDirective = new Map(); - resolvedTypeReferenceDirectives.forEach((resolvedDirective, key) => { - if (!resolvedDirective || !resolvedDirective.resolvedFileName) { - return; - } - const file = host.getSourceFile(resolvedDirective.resolvedFileName); - if (file) { - // Add the transitive closure of path references loaded by this file (as long as they are not) - // part of an existing type reference. - addReferencedFilesToTypeDirective(file, key); - } - }); + function isSymbolFromTypeDeclarationFile(symbol: Symbol): boolean { + // bail out if symbol does not have associated declarations (i.e. this is transient symbol created for property in binding pattern) + if (!symbol.declarations) { + return false; } - return { - getReferencedExportContainer, - getReferencedImportDeclaration, - getReferencedDeclarationWithCollidingName, - isDeclarationWithCollidingName, - isValueAliasDeclaration: nodeIn => { - const node = getParseTreeNode(nodeIn); - // Synthesized nodes are always treated like values. - return node ? isValueAliasDeclaration(node) : true; - }, - hasGlobalName, - isReferencedAliasDeclaration: (nodeIn, checkChildren?) => { - const node = getParseTreeNode(nodeIn); - // Synthesized nodes are always treated as referenced. - return node ? isReferencedAliasDeclaration(node, checkChildren) : true; - }, - getNodeCheckFlags: nodeIn => { - const node = getParseTreeNode(nodeIn); - return node ? getNodeCheckFlags(node) : 0; - }, - isTopLevelValueImportEqualsWithEntityName, - isDeclarationVisible, - isImplementationOfOverload, - isRequiredInitializedParameter, - isOptionalUninitializedParameterProperty, - isExpandoFunctionDeclaration, - getPropertiesOfContainerFunction, - createTypeOfDeclaration, - createReturnTypeOfSignatureDeclaration, - createTypeOfExpression, - createLiteralConstValue, - isSymbolAccessible, - isEntityNameVisible, - getConstantValue: nodeIn => { - const node = getParseTreeNode(nodeIn, canHaveConstantValue); - return node ? getConstantValue(node) : undefined; - }, - collectLinkedAliases, - getReferencedValueDeclaration, - getTypeReferenceSerializationKind, - isOptionalParameter, - moduleExportsSomeValue, - isArgumentsLocalBinding, - getExternalModuleFileFromDeclaration: nodeIn => { - const node = getParseTreeNode(nodeIn, hasPossibleExternalModuleReference); - return node && getExternalModuleFileFromDeclaration(node); - }, - getTypeReferenceDirectivesForEntityName, - getTypeReferenceDirectivesForSymbol, - isLiteralConstDeclaration, - isLateBound: (nodeIn: Declaration): nodeIn is LateBoundDeclaration => { - const node = getParseTreeNode(nodeIn, isDeclaration); - const symbol = node && getSymbolOfNode(node); - return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late); - }, - getJsxFactoryEntity, - getJsxFragmentFactoryEntity, - getAllAccessorDeclarations(accessor: AccessorDeclaration): AllAccessorDeclarations { - accessor = getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!; // TODO: GH#18217 - const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor; - const otherAccessor = getDeclarationOfKind(getSymbolOfNode(accessor), otherKind); - const firstAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? otherAccessor : accessor; - const secondAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? accessor : otherAccessor; - const setAccessor = accessor.kind === SyntaxKind.SetAccessor ? accessor : otherAccessor as SetAccessorDeclaration; - const getAccessor = accessor.kind === SyntaxKind.GetAccessor ? accessor : otherAccessor as GetAccessorDeclaration; - return { - firstAccessor, - secondAccessor, - setAccessor, - getAccessor - }; - }, - getSymbolOfExternalModuleSpecifier: moduleName => resolveExternalModuleNameWorker(moduleName, moduleName, /*moduleNotFoundError*/ undefined), - isBindingCapturedByNode: (node, decl) => { - const parseNode = getParseTreeNode(node); - const parseDecl = getParseTreeNode(decl); - return !!parseNode && !!parseDecl && (isVariableDeclaration(parseDecl) || isBindingElement(parseDecl)) && isBindingCapturedByNode(parseNode, parseDecl); - }, - getDeclarationStatementsForSourceFile: (node, flags, tracker, bundled) => { - const n = getParseTreeNode(node) as SourceFile; - Debug.assert(n && n.kind === SyntaxKind.SourceFile, "Non-sourcefile node passed into getDeclarationsForSourceFile"); - const sym = getSymbolOfNode(node); - if (!sym) { - return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker, bundled); - } - return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker, bundled); - }, - isImportRequiredByAugmentation, - }; - - function isImportRequiredByAugmentation(node: ImportDeclaration) { - const file = getSourceFileOfNode(node); - if (!file.symbol) return false; - const importTarget = getExternalModuleFileFromDeclaration(node); - if (!importTarget) return false; - if (importTarget === file) return false; - const exports = getExportsOfModule(file.symbol); - for (const s of arrayFrom(exports.values())) { - if (s.mergeId) { - const merged = getMergedSymbol(s); - if (merged.declarations) { - for (const d of merged.declarations) { - const declFile = getSourceFileOfNode(d); - if (declFile === importTarget) { - return true; - } - } - } - } + // walk the parent chain for symbols to make sure that top level parent symbol is in the global scope + // external modules cannot define or contribute to type declaration files + let current = symbol; + while (true) { + const parent = getParentOfSymbol(current); + if (parent) { + current = parent; + } + else { + break; } - return false; } - function isInHeritageClause(node: PropertyAccessEntityNameExpression) { - return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && node.parent.parent && node.parent.parent.kind === SyntaxKind.HeritageClause; + if (current.valueDeclaration && current.valueDeclaration.kind === SyntaxKind.SourceFile && current.flags & SymbolFlags.ValueModule) { + return false; } - // defined here to avoid outer scope pollution - function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): string[] | undefined { - // program does not have any files with type reference directives - bail out - if (!fileToDirective) { - return undefined; - } - // property access can only be used as values, or types when within an expression with type arguments inside a heritage clause - // qualified names can only be used as types\namespaces - // identifiers are treated as values only if they appear in type queries - let meaning = SymbolFlags.Type | SymbolFlags.Namespace; - if ((node.kind === SyntaxKind.Identifier && isInTypeQuery(node)) || (node.kind === SyntaxKind.PropertyAccessExpression && !isInHeritageClause(node))) { - meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + // check that at least one declaration of top level symbol originates from type declaration file + for (const decl of symbol.declarations) { + const file = getSourceFileOfNode(decl); + if (fileToDirective.has(file.path)) { + return true; } - - const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true); - return symbol && symbol !== unknownSymbol ? getTypeReferenceDirectivesForSymbol(symbol, meaning) : undefined; } + return false; + } - // defined here to avoid outer scope pollution - function getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[] | undefined { - // program does not have any files with type reference directives - bail out - if (!fileToDirective || !isSymbolFromTypeDeclarationFile(symbol)) { - return undefined; - } - // check what declarations in the symbol can contribute to the target meaning - let typeReferenceDirectives: string[] | undefined; - for (const decl of symbol.declarations!) { - // check meaning of the local symbol to see if declaration needs to be analyzed further - if (decl.symbol && decl.symbol.flags & meaning!) { - const file = getSourceFileOfNode(decl); - const typeReferenceDirective = fileToDirective.get(file.path); - if (typeReferenceDirective) { - (typeReferenceDirectives || (typeReferenceDirectives = [])).push(typeReferenceDirective); - } - else { - // found at least one entry that does not originate from type reference directive - return undefined; - } - } + function addReferencedFilesToTypeDirective(file: SourceFile, key: string) { + if (fileToDirective.has(file.path)) return; + fileToDirective.set(file.path, key); + for (const { fileName } of file.referencedFiles) { + const resolvedFile = resolveTripleslashReference(fileName, file.fileName); + const referencedFile = host.getSourceFile(resolvedFile); + if (referencedFile) { + addReferencedFilesToTypeDirective(referencedFile, key); } - return typeReferenceDirectives; } + } + } - function isSymbolFromTypeDeclarationFile(symbol: Symbol): boolean { - // bail out if symbol does not have associated declarations (i.e. this is transient symbol created for property in binding pattern) - if (!symbol.declarations) { - return false; - } + function getExternalModuleFileFromDeclaration(declaration: AnyImportOrReExport | ModuleDeclaration | ImportTypeNode | ImportCall): SourceFile | undefined { + const specifier = declaration.kind === SyntaxKind.ModuleDeclaration ? tryCast(declaration.name, isStringLiteral) : getExternalModuleName(declaration); + const moduleSymbol = resolveExternalModuleNameWorker(specifier!, specifier!, /*moduleNotFoundError*/ undefined); // TODO: GH#18217 + if (!moduleSymbol) { + return undefined; + } + return getDeclarationOfKind(moduleSymbol, SyntaxKind.SourceFile); + } - // walk the parent chain for symbols to make sure that top level parent symbol is in the global scope - // external modules cannot define or contribute to type declaration files - let current = symbol; - while (true) { - const parent = getParentOfSymbol(current); - if (parent) { - current = parent; - } - else { - break; - } - } + function initializeTypeChecker() { + // Bind all source files and propagate errors + for (const file of host.getSourceFiles()) { + bindSourceFile(file, compilerOptions); + } - if (current.valueDeclaration && current.valueDeclaration.kind === SyntaxKind.SourceFile && current.flags & SymbolFlags.ValueModule) { - return false; - } + amalgamatedDuplicates = new Map(); - // check that at least one declaration of top level symbol originates from type declaration file - for (const decl of symbol.declarations) { - const file = getSourceFileOfNode(decl); - if (fileToDirective.has(file.path)) { - return true; - } - } - return false; + // Initialize global symbol table + let augmentations: (readonly (StringLiteral | Identifier)[])[] | undefined; + for (const file of host.getSourceFiles()) { + if (file.redirectInfo) { + continue; } - - function addReferencedFilesToTypeDirective(file: SourceFile, key: string) { - if (fileToDirective.has(file.path)) return; - fileToDirective.set(file.path, key); - for (const { fileName } of file.referencedFiles) { - const resolvedFile = resolveTripleslashReference(fileName, file.fileName); - const referencedFile = host.getSourceFile(resolvedFile); - if (referencedFile) { - addReferencedFilesToTypeDirective(referencedFile, key); + if (!isExternalOrCommonJsModule(file)) { + // It is an error for a non-external-module (i.e. script) to declare its own `globalThis`. + // We can't use `builtinGlobals` for this due to synthetic expando-namespace generation in JS files. + const fileGlobalThisSymbol = file.locals!.get("globalThis" as __String); + if (fileGlobalThisSymbol?.declarations) { + for (const declaration of fileGlobalThisSymbol.declarations) { + diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis")); } } + mergeSymbolTable(globals, file.locals!); } - } - - function getExternalModuleFileFromDeclaration(declaration: AnyImportOrReExport | ModuleDeclaration | ImportTypeNode | ImportCall): SourceFile | undefined { - const specifier = declaration.kind === SyntaxKind.ModuleDeclaration ? tryCast(declaration.name, isStringLiteral) : getExternalModuleName(declaration); - const moduleSymbol = resolveExternalModuleNameWorker(specifier!, specifier!, /*moduleNotFoundError*/ undefined); // TODO: GH#18217 - if (!moduleSymbol) { - return undefined; + if (file.jsGlobalAugmentations) { + mergeSymbolTable(globals, file.jsGlobalAugmentations); } - return getDeclarationOfKind(moduleSymbol, SyntaxKind.SourceFile); - } - - function initializeTypeChecker() { - // Bind all source files and propagate errors - for (const file of host.getSourceFiles()) { - bindSourceFile(file, compilerOptions); + if (file.patternAmbientModules && file.patternAmbientModules.length) { + patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules); } - - amalgamatedDuplicates = new Map(); - - // Initialize global symbol table - let augmentations: (readonly (StringLiteral | Identifier)[])[] | undefined; - for (const file of host.getSourceFiles()) { - if (file.redirectInfo) { - continue; - } - if (!isExternalOrCommonJsModule(file)) { - // It is an error for a non-external-module (i.e. script) to declare its own `globalThis`. - // We can't use `builtinGlobals` for this due to synthetic expando-namespace generation in JS files. - const fileGlobalThisSymbol = file.locals!.get("globalThis" as __String); - if (fileGlobalThisSymbol?.declarations) { - for (const declaration of fileGlobalThisSymbol.declarations) { - diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis")); - } + if (file.moduleAugmentations.length) { + (augmentations || (augmentations = [])).push(file.moduleAugmentations); + } + if (file.symbol && file.symbol.globalExports) { + // Merge in UMD exports with first-in-wins semantics (see #9771) + const source = file.symbol.globalExports; + source.forEach((sourceSymbol, id) => { + if (!globals.has(id)) { + globals.set(id, sourceSymbol); } - mergeSymbolTable(globals, file.locals!); - } - if (file.jsGlobalAugmentations) { - mergeSymbolTable(globals, file.jsGlobalAugmentations); - } - if (file.patternAmbientModules && file.patternAmbientModules.length) { - patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules); - } - if (file.moduleAugmentations.length) { - (augmentations || (augmentations = [])).push(file.moduleAugmentations); - } - if (file.symbol && file.symbol.globalExports) { - // Merge in UMD exports with first-in-wins semantics (see #9771) - const source = file.symbol.globalExports; - source.forEach((sourceSymbol, id) => { - if (!globals.has(id)) { - globals.set(id, sourceSymbol); - } - }); - } + }); } + } - // We do global augmentations separately from module augmentations (and before creating global types) because they - // 1. Affect global types. We won't have the correct global types until global augmentations are merged. Also, - // 2. Module augmentation instantiation requires creating the type of a module, which, in turn, can require - // checking for an export or property on the module (if export=) which, in turn, can fall back to the - // apparent type of the module - either globalObjectType or globalFunctionType - which wouldn't exist if we - // did module augmentations prior to finalizing the global types. - if (augmentations) { - // merge _global_ module augmentations. - // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed - for (const list of augmentations) { - for (const augmentation of list) { - if (!isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue; - mergeModuleAugmentation(augmentation); - } + // We do global augmentations separately from module augmentations (and before creating global types) because they + // 1. Affect global types. We won't have the correct global types until global augmentations are merged. Also, + // 2. Module augmentation instantiation requires creating the type of a module, which, in turn, can require + // checking for an export or property on the module (if export=) which, in turn, can fall back to the + // apparent type of the module - either globalObjectType or globalFunctionType - which wouldn't exist if we + // did module augmentations prior to finalizing the global types. + if (augmentations) { + // merge _global_ module augmentations. + // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed + for (const list of augmentations) { + for (const augmentation of list) { + if (!isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue; + mergeModuleAugmentation(augmentation); } } + } - // Setup global builtins - addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0); + // Setup global builtins + addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0); - getSymbolLinks(undefinedSymbol).type = undefinedWideningType; - getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments" as __String, /*arity*/ 0, /*reportErrors*/ true); - getSymbolLinks(unknownSymbol).type = errorType; - getSymbolLinks(globalThisSymbol).type = createObjectType(ObjectFlags.Anonymous, globalThisSymbol); + getSymbolLinks(undefinedSymbol).type = undefinedWideningType; + getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments" as __String, /*arity*/ 0, /*reportErrors*/ true); + getSymbolLinks(unknownSymbol).type = errorType; + getSymbolLinks(globalThisSymbol).type = createObjectType(ObjectFlags.Anonymous, globalThisSymbol); - // Initialize special types - globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true); - globalObjectType = getGlobalType("Object" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalFunctionType = getGlobalType("Function" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalCallableFunctionType = strictBindCallApply && getGlobalType("CallableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; - globalNewableFunctionType = strictBindCallApply && getGlobalType("NewableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; - globalStringType = getGlobalType("String" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalNumberType = getGlobalType("Number" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalBooleanType = getGlobalType("Boolean" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalRegExpType = getGlobalType("RegExp" as __String, /*arity*/ 0, /*reportErrors*/ true); - anyArrayType = createArrayType(anyType); + // Initialize special types + globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true); + globalObjectType = getGlobalType("Object" as __String, /*arity*/ 0, /*reportErrors*/ true); + globalFunctionType = getGlobalType("Function" as __String, /*arity*/ 0, /*reportErrors*/ true); + globalCallableFunctionType = strictBindCallApply && getGlobalType("CallableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; + globalNewableFunctionType = strictBindCallApply && getGlobalType("NewableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; + globalStringType = getGlobalType("String" as __String, /*arity*/ 0, /*reportErrors*/ true); + globalNumberType = getGlobalType("Number" as __String, /*arity*/ 0, /*reportErrors*/ true); + globalBooleanType = getGlobalType("Boolean" as __String, /*arity*/ 0, /*reportErrors*/ true); + globalRegExpType = getGlobalType("RegExp" as __String, /*arity*/ 0, /*reportErrors*/ true); + anyArrayType = createArrayType(anyType); - autoArrayType = createArrayType(autoType); - if (autoArrayType === emptyObjectType) { - // autoArrayType is used as a marker, so even if global Array type is not defined, it needs to be a unique type - autoArrayType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - } + autoArrayType = createArrayType(autoType); + if (autoArrayType === emptyObjectType) { + // autoArrayType is used as a marker, so even if global Array type is not defined, it needs to be a unique type + autoArrayType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + } - globalReadonlyArrayType = getGlobalTypeOrUndefined("ReadonlyArray" as __String, /*arity*/ 1) as GenericType || globalArrayType; - anyReadonlyArrayType = globalReadonlyArrayType ? createTypeFromGenericGlobalType(globalReadonlyArrayType, [anyType]) : anyArrayType; - globalThisType = getGlobalTypeOrUndefined("ThisType" as __String, /*arity*/ 1) as GenericType; + globalReadonlyArrayType = getGlobalTypeOrUndefined("ReadonlyArray" as __String, /*arity*/ 1) as GenericType || globalArrayType; + anyReadonlyArrayType = globalReadonlyArrayType ? createTypeFromGenericGlobalType(globalReadonlyArrayType, [anyType]) : anyArrayType; + globalThisType = getGlobalTypeOrUndefined("ThisType" as __String, /*arity*/ 1) as GenericType; - if (augmentations) { - // merge _nonglobal_ module augmentations. - // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed - for (const list of augmentations) { - for (const augmentation of list) { - if (isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue; - mergeModuleAugmentation(augmentation); - } + if (augmentations) { + // merge _nonglobal_ module augmentations. + // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed + for (const list of augmentations) { + for (const augmentation of list) { + if (isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue; + mergeModuleAugmentation(augmentation); } } + } - amalgamatedDuplicates.forEach(({ firstFile, secondFile, conflictingSymbols }) => { - // If not many things conflict, issue individual errors - if (conflictingSymbols.size < 8) { - conflictingSymbols.forEach(({ isBlockScoped, firstFileLocations, secondFileLocations }, symbolName) => { - const message = isBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 : Diagnostics.Duplicate_identifier_0; - for (const node of firstFileLocations) { - addDuplicateDeclarationError(node, message, symbolName, secondFileLocations); - } - for (const node of secondFileLocations) { - addDuplicateDeclarationError(node, message, symbolName, firstFileLocations); - } - }); - } - else { - // Otherwise issue top-level error since the files appear very identical in terms of what they contain - const list = arrayFrom(conflictingSymbols.keys()).join(", "); - diagnostics.add(addRelatedInfo( - createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), - createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file) - )); - diagnostics.add(addRelatedInfo( - createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), - createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file) - )); - } - }); - amalgamatedDuplicates = undefined; - } - - function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) { - if ((requestedExternalEmitHelpers & helpers) !== helpers && compilerOptions.importHelpers) { - const sourceFile = getSourceFileOfNode(location); - if (isEffectiveExternalModule(sourceFile, compilerOptions) && !(location.flags & NodeFlags.Ambient)) { - const helpersModule = resolveHelpersModule(sourceFile, location); - if (helpersModule !== unknownSymbol) { - const uncheckedHelpers = helpers & ~requestedExternalEmitHelpers; - for (let helper = ExternalEmitHelpers.FirstEmitHelper; helper <= ExternalEmitHelpers.LastEmitHelper; helper <<= 1) { - if (uncheckedHelpers & helper) { - const name = getHelperName(helper); - const symbol = getSymbol(helpersModule.exports!, escapeLeadingUnderscores(name), SymbolFlags.Value); - if (!symbol) { - error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_which_does_not_exist_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name); - } - else if (helper & ExternalEmitHelpers.ClassPrivateFieldGet) { - if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 3)) { - error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 4); - } + amalgamatedDuplicates.forEach(({ firstFile, secondFile, conflictingSymbols }) => { + // If not many things conflict, issue individual errors + if (conflictingSymbols.size < 8) { + conflictingSymbols.forEach(({ isBlockScoped, firstFileLocations, secondFileLocations }, symbolName) => { + const message = isBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 : Diagnostics.Duplicate_identifier_0; + for (const node of firstFileLocations) { + addDuplicateDeclarationError(node, message, symbolName, secondFileLocations); + } + for (const node of secondFileLocations) { + addDuplicateDeclarationError(node, message, symbolName, firstFileLocations); + } + }); + } + else { + // Otherwise issue top-level error since the files appear very identical in terms of what they contain + const list = arrayFrom(conflictingSymbols.keys()).join(", "); + diagnostics.add(addRelatedInfo( + createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), + createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file) + )); + diagnostics.add(addRelatedInfo( + createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), + createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file) + )); + } + }); + amalgamatedDuplicates = undefined; + } + + function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) { + if ((requestedExternalEmitHelpers & helpers) !== helpers && compilerOptions.importHelpers) { + const sourceFile = getSourceFileOfNode(location); + if (isEffectiveExternalModule(sourceFile, compilerOptions) && !(location.flags & NodeFlags.Ambient)) { + const helpersModule = resolveHelpersModule(sourceFile, location); + if (helpersModule !== unknownSymbol) { + const uncheckedHelpers = helpers & ~requestedExternalEmitHelpers; + for (let helper = ExternalEmitHelpers.FirstEmitHelper; helper <= ExternalEmitHelpers.LastEmitHelper; helper <<= 1) { + if (uncheckedHelpers & helper) { + const name = getHelperName(helper); + const symbol = getSymbol(helpersModule.exports!, escapeLeadingUnderscores(name), SymbolFlags.Value); + if (!symbol) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_which_does_not_exist_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name); + } + else if (helper & ExternalEmitHelpers.ClassPrivateFieldGet) { + if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 3)) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 4); } - else if (helper & ExternalEmitHelpers.ClassPrivateFieldSet) { - if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 4)) { - error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 5); - } + } + else if (helper & ExternalEmitHelpers.ClassPrivateFieldSet) { + if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 4)) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 5); } - else if (helper & ExternalEmitHelpers.SpreadArray) { - if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 2)) { - error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 3); - } + } + else if (helper & ExternalEmitHelpers.SpreadArray) { + if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 2)) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 3); } } } } - requestedExternalEmitHelpers |= helpers; } + requestedExternalEmitHelpers |= helpers; } } + } - function getHelperName(helper: ExternalEmitHelpers) { - switch (helper) { - case ExternalEmitHelpers.Extends: return "__extends"; - case ExternalEmitHelpers.Assign: return "__assign"; - case ExternalEmitHelpers.Rest: return "__rest"; - case ExternalEmitHelpers.Decorate: return "__decorate"; - case ExternalEmitHelpers.Metadata: return "__metadata"; - case ExternalEmitHelpers.Param: return "__param"; - case ExternalEmitHelpers.Awaiter: return "__awaiter"; - case ExternalEmitHelpers.Generator: return "__generator"; - case ExternalEmitHelpers.Values: return "__values"; - case ExternalEmitHelpers.Read: return "__read"; - case ExternalEmitHelpers.SpreadArray: return "__spreadArray"; - case ExternalEmitHelpers.Await: return "__await"; - case ExternalEmitHelpers.AsyncGenerator: return "__asyncGenerator"; - case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator"; - case ExternalEmitHelpers.AsyncValues: return "__asyncValues"; - case ExternalEmitHelpers.ExportStar: return "__exportStar"; - case ExternalEmitHelpers.ImportStar: return "__importStar"; - case ExternalEmitHelpers.ImportDefault: return "__importDefault"; - case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject"; - case ExternalEmitHelpers.ClassPrivateFieldGet: return "__classPrivateFieldGet"; - case ExternalEmitHelpers.ClassPrivateFieldSet: return "__classPrivateFieldSet"; - case ExternalEmitHelpers.ClassPrivateFieldIn: return "__classPrivateFieldIn"; - case ExternalEmitHelpers.CreateBinding: return "__createBinding"; - default: return Debug.fail("Unrecognized helper"); - } + function getHelperName(helper: ExternalEmitHelpers) { + switch (helper) { + case ExternalEmitHelpers.Extends: return "__extends"; + case ExternalEmitHelpers.Assign: return "__assign"; + case ExternalEmitHelpers.Rest: return "__rest"; + case ExternalEmitHelpers.Decorate: return "__decorate"; + case ExternalEmitHelpers.Metadata: return "__metadata"; + case ExternalEmitHelpers.Param: return "__param"; + case ExternalEmitHelpers.Awaiter: return "__awaiter"; + case ExternalEmitHelpers.Generator: return "__generator"; + case ExternalEmitHelpers.Values: return "__values"; + case ExternalEmitHelpers.Read: return "__read"; + case ExternalEmitHelpers.SpreadArray: return "__spreadArray"; + case ExternalEmitHelpers.Await: return "__await"; + case ExternalEmitHelpers.AsyncGenerator: return "__asyncGenerator"; + case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator"; + case ExternalEmitHelpers.AsyncValues: return "__asyncValues"; + case ExternalEmitHelpers.ExportStar: return "__exportStar"; + case ExternalEmitHelpers.ImportStar: return "__importStar"; + case ExternalEmitHelpers.ImportDefault: return "__importDefault"; + case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject"; + case ExternalEmitHelpers.ClassPrivateFieldGet: return "__classPrivateFieldGet"; + case ExternalEmitHelpers.ClassPrivateFieldSet: return "__classPrivateFieldSet"; + case ExternalEmitHelpers.ClassPrivateFieldIn: return "__classPrivateFieldIn"; + case ExternalEmitHelpers.CreateBinding: return "__createBinding"; + default: return Debug.fail("Unrecognized helper"); } + } - function resolveHelpersModule(node: SourceFile, errorNode: Node) { - if (!externalHelpersModule) { - externalHelpersModule = resolveExternalModule(node, externalHelpersModuleNameText, Diagnostics.This_syntax_requires_an_imported_helper_but_module_0_cannot_be_found, errorNode) || unknownSymbol; - } - return externalHelpersModule; + function resolveHelpersModule(node: SourceFile, errorNode: Node) { + if (!externalHelpersModule) { + externalHelpersModule = resolveExternalModule(node, externalHelpersModuleNameText, Diagnostics.This_syntax_requires_an_imported_helper_but_module_0_cannot_be_found, errorNode) || unknownSymbol; } + return externalHelpersModule; + } - // GRAMMAR CHECKING - function checkGrammarDecoratorsAndModifiers(node: Node): boolean { - return checkGrammarDecorators(node) || checkGrammarModifiers(node); - } + // GRAMMAR CHECKING + function checkGrammarDecoratorsAndModifiers(node: Node): boolean { + return checkGrammarDecorators(node) || checkGrammarModifiers(node); + } - function checkGrammarDecorators(node: Node): boolean { - if (!node.decorators) { - return false; + function checkGrammarDecorators(node: Node): boolean { + if (!node.decorators) { + return false; + } + if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { + if (node.kind === SyntaxKind.MethodDeclaration && !nodeIsPresent((node as MethodDeclaration).body)) { + return grammarErrorOnFirstToken(node, Diagnostics.A_decorator_can_only_decorate_a_method_implementation_not_an_overload); } - if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { - if (node.kind === SyntaxKind.MethodDeclaration && !nodeIsPresent((node as MethodDeclaration).body)) { - return grammarErrorOnFirstToken(node, Diagnostics.A_decorator_can_only_decorate_a_method_implementation_not_an_overload); - } - else { - return grammarErrorOnFirstToken(node, Diagnostics.Decorators_are_not_valid_here); - } + else { + return grammarErrorOnFirstToken(node, Diagnostics.Decorators_are_not_valid_here); } - else if (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor) { - const accessors = getAllAccessorDeclarations((node.parent as ClassDeclaration).members, node as AccessorDeclaration); - if (accessors.firstAccessor.decorators && node === accessors.secondAccessor) { - return grammarErrorOnFirstToken(node, Diagnostics.Decorators_cannot_be_applied_to_multiple_get_Slashset_accessors_of_the_same_name); - } + } + else if (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor) { + const accessors = getAllAccessorDeclarations((node.parent as ClassDeclaration).members, node as AccessorDeclaration); + if (accessors.firstAccessor.decorators && node === accessors.secondAccessor) { + return grammarErrorOnFirstToken(node, Diagnostics.Decorators_cannot_be_applied_to_multiple_get_Slashset_accessors_of_the_same_name); } - return false; } + return false; + } - function checkGrammarModifiers(node: Node): boolean { - const quickResult = reportObviousModifierErrors(node); - if (quickResult !== undefined) { - return quickResult; - } + function checkGrammarModifiers(node: Node): boolean { + const quickResult = reportObviousModifierErrors(node); + if (quickResult !== undefined) { + return quickResult; + } - let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastReadonly: Node | undefined, lastOverride: Node | undefined; - let flags = ModifierFlags.None; - for (const modifier of node.modifiers!) { - if (modifier.kind !== SyntaxKind.ReadonlyKeyword) { - if (node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_member, tokenToString(modifier.kind)); + let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastReadonly: Node | undefined, lastOverride: Node | undefined; + let flags = ModifierFlags.None; + for (const modifier of node.modifiers!) { + if (modifier.kind !== SyntaxKind.ReadonlyKeyword) { + if (node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_member, tokenToString(modifier.kind)); + } + if (node.kind === SyntaxKind.IndexSignature && (modifier.kind !== SyntaxKind.StaticKeyword || !isClassLike(node.parent))) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind)); + } + } + switch (modifier.kind) { + case SyntaxKind.ConstKeyword: + if (node.kind !== SyntaxKind.EnumDeclaration) { + return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword)); } - if (node.kind === SyntaxKind.IndexSignature && (modifier.kind !== SyntaxKind.StaticKeyword || !isClassLike(node.parent))) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind)); + break; + case SyntaxKind.OverrideKeyword: + // If node.kind === SyntaxKind.Parameter, checkParameter reports an error if it's not a parameter property. + if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "override"); } - } - switch (modifier.kind) { - case SyntaxKind.ConstKeyword: - if (node.kind !== SyntaxKind.EnumDeclaration) { - return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword)); - } - break; - case SyntaxKind.OverrideKeyword: - // If node.kind === SyntaxKind.Parameter, checkParameter reports an error if it's not a parameter property. - if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "override"); - } - else if (flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "override", "declare"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "readonly"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "async"); - } - flags |= ModifierFlags.Override; - lastOverride = modifier; - break; + else if (flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "override", "declare"); + } + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "readonly"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "async"); + } + flags |= ModifierFlags.Override; + lastOverride = modifier; + break; - case SyntaxKind.PublicKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.PrivateKeyword: - const text = visibilityToString(modifierToFlag(modifier.kind)); + case SyntaxKind.PublicKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PrivateKeyword: + const text = visibilityToString(modifierToFlag(modifier.kind)); - if (flags & ModifierFlags.AccessibilityModifier) { - return grammarErrorOnNode(modifier, Diagnostics.Accessibility_modifier_already_seen); - } - else if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "override"); - } - else if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "readonly"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "async"); - } - else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, text); - } - else if (flags & ModifierFlags.Abstract) { - if (modifier.kind === SyntaxKind.PrivateKeyword) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, text, "abstract"); - } - else { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "abstract"); - } + if (flags & ModifierFlags.AccessibilityModifier) { + return grammarErrorOnNode(modifier, Diagnostics.Accessibility_modifier_already_seen); + } + else if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "override"); + } + else if (flags & ModifierFlags.Static) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static"); + } + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "readonly"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "async"); + } + else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, text); + } + else if (flags & ModifierFlags.Abstract) { + if (modifier.kind === SyntaxKind.PrivateKeyword) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, text, "abstract"); } - else if (isPrivateIdentifierClassElementDeclaration(node)) { - return grammarErrorOnNode(modifier, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier); + else { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "abstract"); } - flags |= modifierToFlag(modifier.kind); - break; + } + else if (isPrivateIdentifierClassElementDeclaration(node)) { + return grammarErrorOnNode(modifier, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier); + } + flags |= modifierToFlag(modifier.kind); + break; - case SyntaxKind.StaticKeyword: - if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "static"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async"); - } - else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, "static"); - } - else if (node.kind === SyntaxKind.Parameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "static"); - } - else if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); - } - else if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "override"); - } - flags |= ModifierFlags.Static; - lastStatic = modifier; - break; + case SyntaxKind.StaticKeyword: + if (flags & ModifierFlags.Static) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "static"); + } + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async"); + } + else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, "static"); + } + else if (node.kind === SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "static"); + } + else if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); + } + else if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "override"); + } + flags |= ModifierFlags.Static; + lastStatic = modifier; + break; - case SyntaxKind.ReadonlyKeyword: - if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly"); - } - else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.Parameter) { - // If node.kind === SyntaxKind.Parameter, checkParameter reports an error if it's not a parameter property. - return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature); - } - flags |= ModifierFlags.Readonly; - lastReadonly = modifier; - break; + case SyntaxKind.ReadonlyKeyword: + if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly"); + } + else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.Parameter) { + // If node.kind === SyntaxKind.Parameter, checkParameter reports an error if it's not a parameter property. + return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature); + } + flags |= ModifierFlags.Readonly; + lastReadonly = modifier; + break; - case SyntaxKind.ExportKeyword: - if (flags & ModifierFlags.Export) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "export"); - } - else if (flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "declare"); - } - else if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "abstract"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "async"); - } - else if (isClassLike(node.parent)) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind, "export"); - } - else if (node.kind === SyntaxKind.Parameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "export"); - } - flags |= ModifierFlags.Export; - break; - case SyntaxKind.DefaultKeyword: - const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent; - if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { - return grammarErrorOnNode(modifier, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); - } - else if (!(flags & ModifierFlags.Export)) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "default"); - } + case SyntaxKind.ExportKeyword: + if (flags & ModifierFlags.Export) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "export"); + } + else if (flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "declare"); + } + else if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "abstract"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "async"); + } + else if (isClassLike(node.parent)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind, "export"); + } + else if (node.kind === SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "export"); + } + flags |= ModifierFlags.Export; + break; + case SyntaxKind.DefaultKeyword: + const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent; + if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { + return grammarErrorOnNode(modifier, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); + } + else if (!(flags & ModifierFlags.Export)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "default"); + } - flags |= ModifierFlags.Default; - break; - case SyntaxKind.DeclareKeyword: - if (flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "declare"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); - } - else if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "override"); - } - else if (isClassLike(node.parent) && !isPropertyDeclaration(node)) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind, "declare"); + flags |= ModifierFlags.Default; + break; + case SyntaxKind.DeclareKeyword: + if (flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "declare"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); + } + else if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "override"); + } + else if (isClassLike(node.parent) && !isPropertyDeclaration(node)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind, "declare"); + } + else if (node.kind === SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "declare"); + } + else if ((node.parent.flags & NodeFlags.Ambient) && node.parent.kind === SyntaxKind.ModuleBlock) { + return grammarErrorOnNode(modifier, Diagnostics.A_declare_modifier_cannot_be_used_in_an_already_ambient_context); + } + else if (isPrivateIdentifierClassElementDeclaration(node)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "declare"); + } + flags |= ModifierFlags.Ambient; + lastDeclare = modifier; + break; + + case SyntaxKind.AbstractKeyword: + if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract"); + } + if (node.kind !== SyntaxKind.ClassDeclaration && + node.kind !== SyntaxKind.ConstructorType) { + if (node.kind !== SyntaxKind.MethodDeclaration && + node.kind !== SyntaxKind.PropertyDeclaration && + node.kind !== SyntaxKind.GetAccessor && + node.kind !== SyntaxKind.SetAccessor) { + return grammarErrorOnNode(modifier, Diagnostics.abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration); } - else if (node.kind === SyntaxKind.Parameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "declare"); + if (!(node.parent.kind === SyntaxKind.ClassDeclaration && hasSyntacticModifier(node.parent, ModifierFlags.Abstract))) { + return grammarErrorOnNode(modifier, Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class); } - else if ((node.parent.flags & NodeFlags.Ambient) && node.parent.kind === SyntaxKind.ModuleBlock) { - return grammarErrorOnNode(modifier, Diagnostics.A_declare_modifier_cannot_be_used_in_an_already_ambient_context); + if (flags & ModifierFlags.Static) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); } - else if (isPrivateIdentifierClassElementDeclaration(node)) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "declare"); + if (flags & ModifierFlags.Private) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "private", "abstract"); } - flags |= ModifierFlags.Ambient; - lastDeclare = modifier; - break; - - case SyntaxKind.AbstractKeyword: - if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract"); - } - if (node.kind !== SyntaxKind.ClassDeclaration && - node.kind !== SyntaxKind.ConstructorType) { - if (node.kind !== SyntaxKind.MethodDeclaration && - node.kind !== SyntaxKind.PropertyDeclaration && - node.kind !== SyntaxKind.GetAccessor && - node.kind !== SyntaxKind.SetAccessor) { - return grammarErrorOnNode(modifier, Diagnostics.abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration); - } - if (!(node.parent.kind === SyntaxKind.ClassDeclaration && hasSyntacticModifier(node.parent, ModifierFlags.Abstract))) { - return grammarErrorOnNode(modifier, Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class); - } - if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); - } - if (flags & ModifierFlags.Private) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "private", "abstract"); - } - if (flags & ModifierFlags.Async && lastAsync) { - return grammarErrorOnNode(lastAsync, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "async", "abstract"); - } - if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "abstract", "override"); - } + if (flags & ModifierFlags.Async && lastAsync) { + return grammarErrorOnNode(lastAsync, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "async", "abstract"); } - if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.PrivateIdentifier) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "abstract"); + if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "abstract", "override"); } + } + if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.PrivateIdentifier) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "abstract"); + } - flags |= ModifierFlags.Abstract; - break; + flags |= ModifierFlags.Abstract; + break; - case SyntaxKind.AsyncKeyword: - if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "async"); - } - else if (flags & ModifierFlags.Ambient || node.parent.flags & NodeFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); - } - else if (node.kind === SyntaxKind.Parameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "async"); - } - if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "async", "abstract"); - } - flags |= ModifierFlags.Async; - lastAsync = modifier; - break; - } + case SyntaxKind.AsyncKeyword: + if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "async"); + } + else if (flags & ModifierFlags.Ambient || node.parent.flags & NodeFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); + } + else if (node.kind === SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "async"); + } + if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "async", "abstract"); + } + flags |= ModifierFlags.Async; + lastAsync = modifier; + break; } + } - if (node.kind === SyntaxKind.Constructor) { - if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "static"); - } - if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "abstract"); // TODO: GH#18217 - } - if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(lastOverride!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "override"); // TODO: GH#18217 - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(lastAsync!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(lastReadonly!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "readonly"); - } - return false; + if (node.kind === SyntaxKind.Constructor) { + if (flags & ModifierFlags.Static) { + return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "static"); } - else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(lastDeclare!, Diagnostics.A_0_modifier_cannot_be_used_with_an_import_declaration, "declare"); + if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "abstract"); // TODO: GH#18217 } - else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && isBindingPattern((node as ParameterDeclaration).name)) { - return grammarErrorOnNode(node, Diagnostics.A_parameter_property_may_not_be_declared_using_a_binding_pattern); + if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(lastOverride!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "override"); // TODO: GH#18217 } - else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && (node as ParameterDeclaration).dotDotDotToken) { - return grammarErrorOnNode(node, Diagnostics.A_parameter_property_cannot_be_declared_using_a_rest_parameter); + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(lastAsync!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); } - if (flags & ModifierFlags.Async) { - return checkGrammarAsyncModifier(node, lastAsync!); + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(lastReadonly!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "readonly"); } return false; } - - /** - * true | false: Early return this value from checkGrammarModifiers. - * undefined: Need to do full checking on the modifiers. - */ - function reportObviousModifierErrors(node: Node): boolean | undefined { - return !node.modifiers - ? false - : shouldReportBadModifier(node) - ? grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here) - : undefined; - } - function shouldReportBadModifier(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.Constructor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExportAssignment: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.Parameter: - return false; - default: - if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - return false; - } - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - return nodeHasAnyModifiersExcept(node, SyntaxKind.AsyncKeyword); - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ConstructorType: - return nodeHasAnyModifiersExcept(node, SyntaxKind.AbstractKeyword); - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.VariableStatement: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ClassStaticBlockDeclaration: - return true; - case SyntaxKind.EnumDeclaration: - return nodeHasAnyModifiersExcept(node, SyntaxKind.ConstKeyword); - default: - Debug.fail(); - } - } + else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(lastDeclare!, Diagnostics.A_0_modifier_cannot_be_used_with_an_import_declaration, "declare"); + } + else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && isBindingPattern((node as ParameterDeclaration).name)) { + return grammarErrorOnNode(node, Diagnostics.A_parameter_property_may_not_be_declared_using_a_binding_pattern); } - function nodeHasAnyModifiersExcept(node: Node, allowedModifier: SyntaxKind): boolean { - return node.modifiers!.length > 1 || node.modifiers![0].kind !== allowedModifier; + else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && (node as ParameterDeclaration).dotDotDotToken) { + return grammarErrorOnNode(node, Diagnostics.A_parameter_property_cannot_be_declared_using_a_rest_parameter); } + if (flags & ModifierFlags.Async) { + return checkGrammarAsyncModifier(node, lastAsync!); + } + return false; + } - function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean { - switch (node.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: + /** + * true | false: Early return this value from checkGrammarModifiers. + * undefined: Need to do full checking on the modifiers. + */ + function reportObviousModifierErrors(node: Node): boolean | undefined { + return !node.modifiers + ? false + : shouldReportBadModifier(node) + ? grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here) + : undefined; + } + function shouldReportBadModifier(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.Constructor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExportAssignment: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.Parameter: + return false; + default: + if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { return false; - } - - return grammarErrorOnNode(asyncModifier, Diagnostics._0_modifier_cannot_be_used_here, "async"); + } + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return nodeHasAnyModifiersExcept(node, SyntaxKind.AsyncKeyword); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ConstructorType: + return nodeHasAnyModifiersExcept(node, SyntaxKind.AbstractKeyword); + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.VariableStatement: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ClassStaticBlockDeclaration: + return true; + case SyntaxKind.EnumDeclaration: + return nodeHasAnyModifiersExcept(node, SyntaxKind.ConstKeyword); + default: + Debug.fail(); + } } + } + function nodeHasAnyModifiersExcept(node: Node, allowedModifier: SyntaxKind): boolean { + return node.modifiers!.length > 1 || node.modifiers![0].kind !== allowedModifier; + } - function checkGrammarForDisallowedTrailingComma(list: NodeArray | undefined, diag = Diagnostics.Trailing_comma_not_allowed): boolean { - if (list && list.hasTrailingComma) { - return grammarErrorAtPos(list[0], list.end - ",".length, ",".length, diag); - } - return false; + function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean { + switch (node.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return false; } - function checkGrammarTypeParameterList(typeParameters: NodeArray | undefined, file: SourceFile): boolean { - if (typeParameters && typeParameters.length === 0) { - const start = typeParameters.pos - "<".length; - const end = skipTrivia(file.text, typeParameters.end) + ">".length; - return grammarErrorAtPos(file, start, end - start, Diagnostics.Type_parameter_list_cannot_be_empty); - } - return false; + return grammarErrorOnNode(asyncModifier, Diagnostics._0_modifier_cannot_be_used_here, "async"); + } + + function checkGrammarForDisallowedTrailingComma(list: NodeArray | undefined, diag = Diagnostics.Trailing_comma_not_allowed): boolean { + if (list && list.hasTrailingComma) { + return grammarErrorAtPos(list[0], list.end - ",".length, ",".length, diag); } + return false; + } - function checkGrammarParameterList(parameters: NodeArray) { - let seenOptionalParameter = false; - const parameterCount = parameters.length; + function checkGrammarTypeParameterList(typeParameters: NodeArray | undefined, file: SourceFile): boolean { + if (typeParameters && typeParameters.length === 0) { + const start = typeParameters.pos - "<".length; + const end = skipTrivia(file.text, typeParameters.end) + ">".length; + return grammarErrorAtPos(file, start, end - start, Diagnostics.Type_parameter_list_cannot_be_empty); + } + return false; + } - for (let i = 0; i < parameterCount; i++) { - const parameter = parameters[i]; - if (parameter.dotDotDotToken) { - if (i !== (parameterCount - 1)) { - return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); - } - if (!(parameter.flags & NodeFlags.Ambient)) { // Allow `...foo,` in ambient declarations; see GH#23070 - checkGrammarForDisallowedTrailingComma(parameters, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - } + function checkGrammarParameterList(parameters: NodeArray) { + let seenOptionalParameter = false; + const parameterCount = parameters.length; - if (parameter.questionToken) { - return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional); - } + for (let i = 0; i < parameterCount; i++) { + const parameter = parameters[i]; + if (parameter.dotDotDotToken) { + if (i !== (parameterCount - 1)) { + return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); + } + if (!(parameter.flags & NodeFlags.Ambient)) { // Allow `...foo,` in ambient declarations; see GH#23070 + checkGrammarForDisallowedTrailingComma(parameters, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + } - if (parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_cannot_have_an_initializer); - } + if (parameter.questionToken) { + return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional); } - else if (isOptionalParameter(parameter)) { - seenOptionalParameter = true; - if (parameter.questionToken && parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.Parameter_cannot_have_question_mark_and_initializer); - } + + if (parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_cannot_have_an_initializer); } - else if (seenOptionalParameter && !parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.A_required_parameter_cannot_follow_an_optional_parameter); + } + else if (isOptionalParameter(parameter)) { + seenOptionalParameter = true; + if (parameter.questionToken && parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.Parameter_cannot_have_question_mark_and_initializer); } } + else if (seenOptionalParameter && !parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.A_required_parameter_cannot_follow_an_optional_parameter); + } } + } - function getNonSimpleParameters(parameters: readonly ParameterDeclaration[]): readonly ParameterDeclaration[] { - return filter(parameters, parameter => !!parameter.initializer || isBindingPattern(parameter.name) || isRestParameter(parameter)); - } + function getNonSimpleParameters(parameters: readonly ParameterDeclaration[]): readonly ParameterDeclaration[] { + return filter(parameters, parameter => !!parameter.initializer || isBindingPattern(parameter.name) || isRestParameter(parameter)); + } - function checkGrammarForUseStrictSimpleParameterList(node: FunctionLikeDeclaration): boolean { - if (languageVersion >= ScriptTarget.ES2016) { - const useStrictDirective = node.body && isBlock(node.body) && findUseStrictPrologue(node.body.statements); - if (useStrictDirective) { - const nonSimpleParameters = getNonSimpleParameters(node.parameters); - if (length(nonSimpleParameters)) { - forEach(nonSimpleParameters, parameter => { - addRelatedInfo( - error(parameter, Diagnostics.This_parameter_is_not_allowed_with_use_strict_directive), - createDiagnosticForNode(useStrictDirective, Diagnostics.use_strict_directive_used_here) - ); - }); + function checkGrammarForUseStrictSimpleParameterList(node: FunctionLikeDeclaration): boolean { + if (languageVersion >= ScriptTarget.ES2016) { + const useStrictDirective = node.body && isBlock(node.body) && findUseStrictPrologue(node.body.statements); + if (useStrictDirective) { + const nonSimpleParameters = getNonSimpleParameters(node.parameters); + if (length(nonSimpleParameters)) { + forEach(nonSimpleParameters, parameter => { + addRelatedInfo( + error(parameter, Diagnostics.This_parameter_is_not_allowed_with_use_strict_directive), + createDiagnosticForNode(useStrictDirective, Diagnostics.use_strict_directive_used_here) + ); + }); - const diagnostics = nonSimpleParameters.map((parameter, index) => ( - index === 0 ? createDiagnosticForNode(parameter, Diagnostics.Non_simple_parameter_declared_here) : createDiagnosticForNode(parameter, Diagnostics.and_here) - )) as [DiagnosticWithLocation, ...DiagnosticWithLocation[]]; - addRelatedInfo(error(useStrictDirective, Diagnostics.use_strict_directive_cannot_be_used_with_non_simple_parameter_list), ...diagnostics); - return true; - } + const diagnostics = nonSimpleParameters.map((parameter, index) => ( + index === 0 ? createDiagnosticForNode(parameter, Diagnostics.Non_simple_parameter_declared_here) : createDiagnosticForNode(parameter, Diagnostics.and_here) + )) as [DiagnosticWithLocation, ...DiagnosticWithLocation[]]; + addRelatedInfo(error(useStrictDirective, Diagnostics.use_strict_directive_cannot_be_used_with_non_simple_parameter_list), ...diagnostics); + return true; } } - return false; } + return false; + } - function checkGrammarFunctionLikeDeclaration(node: FunctionLikeDeclaration | MethodSignature): boolean { - // Prevent cascading error by short-circuit - const file = getSourceFileOfNode(node); - return checkGrammarDecoratorsAndModifiers(node) || - checkGrammarTypeParameterList(node.typeParameters, file) || - checkGrammarParameterList(node.parameters) || - checkGrammarArrowFunction(node, file) || - (isFunctionLikeDeclaration(node) && checkGrammarForUseStrictSimpleParameterList(node)); - } + function checkGrammarFunctionLikeDeclaration(node: FunctionLikeDeclaration | MethodSignature): boolean { + // Prevent cascading error by short-circuit + const file = getSourceFileOfNode(node); + return checkGrammarDecoratorsAndModifiers(node) || + checkGrammarTypeParameterList(node.typeParameters, file) || + checkGrammarParameterList(node.parameters) || + checkGrammarArrowFunction(node, file) || + (isFunctionLikeDeclaration(node) && checkGrammarForUseStrictSimpleParameterList(node)); + } - function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean { - const file = getSourceFileOfNode(node); - return checkGrammarClassDeclarationHeritageClauses(node) || - checkGrammarTypeParameterList(node.typeParameters, file); - } + function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean { + const file = getSourceFileOfNode(node); + return checkGrammarClassDeclarationHeritageClauses(node) || + checkGrammarTypeParameterList(node.typeParameters, file); + } - function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean { - if (!isArrowFunction(node)) { - return false; - } + function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean { + if (!isArrowFunction(node)) { + return false; + } - if (node.typeParameters && !(length(node.typeParameters) > 1 || node.typeParameters.hasTrailingComma || node.typeParameters[0].constraint)) { - if (file && fileExtensionIsOneOf(file.fileName, [Extension.Mts, Extension.Cts])) { - grammarErrorOnNode(node.typeParameters[0], Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Add_a_trailing_comma_or_explicit_constraint); - } + if (node.typeParameters && !(length(node.typeParameters) > 1 || node.typeParameters.hasTrailingComma || node.typeParameters[0].constraint)) { + if (file && fileExtensionIsOneOf(file.fileName, [Extension.Mts, Extension.Cts])) { + grammarErrorOnNode(node.typeParameters[0], Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Add_a_trailing_comma_or_explicit_constraint); } - - const { equalsGreaterThanToken } = node; - const startLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.pos).line; - const endLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.end).line; - return startLine !== endLine && grammarErrorOnNode(equalsGreaterThanToken, Diagnostics.Line_terminator_not_permitted_before_arrow); } - function checkGrammarIndexSignatureParameters(node: SignatureDeclaration): boolean { - const parameter = node.parameters[0]; - if (node.parameters.length !== 1) { - if (parameter) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_must_have_exactly_one_parameter); - } - else { - return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_exactly_one_parameter); - } - } - checkGrammarForDisallowedTrailingComma(node.parameters, Diagnostics.An_index_signature_cannot_have_a_trailing_comma); - if (parameter.dotDotDotToken) { - return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.An_index_signature_cannot_have_a_rest_parameter); - } - if (hasEffectiveModifiers(parameter)) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_accessibility_modifier); - } - if (parameter.questionToken) { - return grammarErrorOnNode(parameter.questionToken, Diagnostics.An_index_signature_parameter_cannot_have_a_question_mark); - } - if (parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_initializer); - } - if (!parameter.type) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_must_have_a_type_annotation); - } - const type = getTypeFromTypeNode(parameter.type); - if (someType(type, t => !!(t.flags & TypeFlags.StringOrNumberLiteralOrUnique)) || isGenericType(type)) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_cannot_be_a_literal_type_or_generic_type_Consider_using_a_mapped_object_type_instead); - } - if (!everyType(type, isValidIndexKeyType)) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_must_be_string_number_symbol_or_a_template_literal_type); + const { equalsGreaterThanToken } = node; + const startLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.pos).line; + const endLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.end).line; + return startLine !== endLine && grammarErrorOnNode(equalsGreaterThanToken, Diagnostics.Line_terminator_not_permitted_before_arrow); + } + + function checkGrammarIndexSignatureParameters(node: SignatureDeclaration): boolean { + const parameter = node.parameters[0]; + if (node.parameters.length !== 1) { + if (parameter) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_must_have_exactly_one_parameter); } - if (!node.type) { - return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_a_type_annotation); + else { + return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_exactly_one_parameter); } - return false; } - - function checkGrammarIndexSignature(node: SignatureDeclaration) { - // Prevent cascading error by short-circuit - return checkGrammarDecoratorsAndModifiers(node) || checkGrammarIndexSignatureParameters(node); + checkGrammarForDisallowedTrailingComma(node.parameters, Diagnostics.An_index_signature_cannot_have_a_trailing_comma); + if (parameter.dotDotDotToken) { + return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.An_index_signature_cannot_have_a_rest_parameter); } - - function checkGrammarForAtLeastOneTypeArgument(node: Node, typeArguments: NodeArray | undefined): boolean { - if (typeArguments && typeArguments.length === 0) { - const sourceFile = getSourceFileOfNode(node); - const start = typeArguments.pos - "<".length; - const end = skipTrivia(sourceFile.text, typeArguments.end) + ">".length; - return grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.Type_argument_list_cannot_be_empty); - } - return false; + if (hasEffectiveModifiers(parameter)) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_accessibility_modifier); + } + if (parameter.questionToken) { + return grammarErrorOnNode(parameter.questionToken, Diagnostics.An_index_signature_parameter_cannot_have_a_question_mark); + } + if (parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_initializer); } + if (!parameter.type) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_must_have_a_type_annotation); + } + const type = getTypeFromTypeNode(parameter.type); + if (someType(type, t => !!(t.flags & TypeFlags.StringOrNumberLiteralOrUnique)) || isGenericType(type)) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_cannot_be_a_literal_type_or_generic_type_Consider_using_a_mapped_object_type_instead); + } + if (!everyType(type, isValidIndexKeyType)) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_must_be_string_number_symbol_or_a_template_literal_type); + } + if (!node.type) { + return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_a_type_annotation); + } + return false; + } + + function checkGrammarIndexSignature(node: SignatureDeclaration) { + // Prevent cascading error by short-circuit + return checkGrammarDecoratorsAndModifiers(node) || checkGrammarIndexSignatureParameters(node); + } - function checkGrammarTypeArguments(node: Node, typeArguments: NodeArray | undefined): boolean { - return checkGrammarForDisallowedTrailingComma(typeArguments) || - checkGrammarForAtLeastOneTypeArgument(node, typeArguments); + function checkGrammarForAtLeastOneTypeArgument(node: Node, typeArguments: NodeArray | undefined): boolean { + if (typeArguments && typeArguments.length === 0) { + const sourceFile = getSourceFileOfNode(node); + const start = typeArguments.pos - "<".length; + const end = skipTrivia(sourceFile.text, typeArguments.end) + ">".length; + return grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.Type_argument_list_cannot_be_empty); } + return false; + } - function checkGrammarTaggedTemplateChain(node: TaggedTemplateExpression): boolean { - if (node.questionDotToken || node.flags & NodeFlags.OptionalChain) { - return grammarErrorOnNode(node.template, Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain); - } - return false; + function checkGrammarTypeArguments(node: Node, typeArguments: NodeArray | undefined): boolean { + return checkGrammarForDisallowedTrailingComma(typeArguments) || + checkGrammarForAtLeastOneTypeArgument(node, typeArguments); + } + + function checkGrammarTaggedTemplateChain(node: TaggedTemplateExpression): boolean { + if (node.questionDotToken || node.flags & NodeFlags.OptionalChain) { + return grammarErrorOnNode(node.template, Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain); } + return false; + } - function checkGrammarForOmittedArgument(args: NodeArray | undefined): boolean { - if (args) { - for (const arg of args) { - if (arg.kind === SyntaxKind.OmittedExpression) { - return grammarErrorAtPos(arg, arg.pos, 0, Diagnostics.Argument_expression_expected); - } + function checkGrammarForOmittedArgument(args: NodeArray | undefined): boolean { + if (args) { + for (const arg of args) { + if (arg.kind === SyntaxKind.OmittedExpression) { + return grammarErrorAtPos(arg, arg.pos, 0, Diagnostics.Argument_expression_expected); } } - return false; } + return false; + } - function checkGrammarArguments(args: NodeArray | undefined): boolean { - return checkGrammarForOmittedArgument(args); - } + function checkGrammarArguments(args: NodeArray | undefined): boolean { + return checkGrammarForOmittedArgument(args); + } - function checkGrammarHeritageClause(node: HeritageClause): boolean { - const types = node.types; - if (checkGrammarForDisallowedTrailingComma(types)) { - return true; - } - if (types && types.length === 0) { - const listType = tokenToString(node.token); - return grammarErrorAtPos(node, types.pos, 0, Diagnostics._0_list_cannot_be_empty, listType); - } - return some(types, checkGrammarExpressionWithTypeArguments); + function checkGrammarHeritageClause(node: HeritageClause): boolean { + const types = node.types; + if (checkGrammarForDisallowedTrailingComma(types)) { + return true; } - - function checkGrammarExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { - return checkGrammarTypeArguments(node, node.typeArguments); + if (types && types.length === 0) { + const listType = tokenToString(node.token); + return grammarErrorAtPos(node, types.pos, 0, Diagnostics._0_list_cannot_be_empty, listType); } + return some(types, checkGrammarExpressionWithTypeArguments); + } - function checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) { - let seenExtendsClause = false; - let seenImplementsClause = false; + function checkGrammarExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { + return checkGrammarTypeArguments(node, node.typeArguments); + } - if (!checkGrammarDecoratorsAndModifiers(node) && node.heritageClauses) { - for (const heritageClause of node.heritageClauses) { - if (heritageClause.token === SyntaxKind.ExtendsKeyword) { - if (seenExtendsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); - } + function checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) { + let seenExtendsClause = false; + let seenImplementsClause = false; - if (seenImplementsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_must_precede_implements_clause); - } + if (!checkGrammarDecoratorsAndModifiers(node) && node.heritageClauses) { + for (const heritageClause of node.heritageClauses) { + if (heritageClause.token === SyntaxKind.ExtendsKeyword) { + if (seenExtendsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); + } - if (heritageClause.types.length > 1) { - return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class); - } + if (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_must_precede_implements_clause); + } - seenExtendsClause = true; + if (heritageClause.types.length > 1) { + return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class); } - else { - Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); - if (seenImplementsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.implements_clause_already_seen); - } - seenImplementsClause = true; + seenExtendsClause = true; + } + else { + Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); + if (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.implements_clause_already_seen); } - // Grammar checking heritageClause inside class declaration - checkGrammarHeritageClause(heritageClause); + seenImplementsClause = true; } + + // Grammar checking heritageClause inside class declaration + checkGrammarHeritageClause(heritageClause); } } + } - function checkGrammarInterfaceDeclaration(node: InterfaceDeclaration) { - let seenExtendsClause = false; - - if (node.heritageClauses) { - for (const heritageClause of node.heritageClauses) { - if (heritageClause.token === SyntaxKind.ExtendsKeyword) { - if (seenExtendsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); - } + function checkGrammarInterfaceDeclaration(node: InterfaceDeclaration) { + let seenExtendsClause = false; - seenExtendsClause = true; - } - else { - Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); - return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); + if (node.heritageClauses) { + for (const heritageClause of node.heritageClauses) { + if (heritageClause.token === SyntaxKind.ExtendsKeyword) { + if (seenExtendsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); } - // Grammar checking heritageClause inside class declaration - checkGrammarHeritageClause(heritageClause); + seenExtendsClause = true; + } + else { + Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); + return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); } - } - return false; - } - function checkGrammarComputedPropertyName(node: Node): boolean { - // If node is not a computedPropertyName, just skip the grammar checking - if (node.kind !== SyntaxKind.ComputedPropertyName) { - return false; + // Grammar checking heritageClause inside class declaration + checkGrammarHeritageClause(heritageClause); } + } + return false; + } - const computedPropertyName = node as ComputedPropertyName; - if (computedPropertyName.expression.kind === SyntaxKind.BinaryExpression && (computedPropertyName.expression as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken) { - return grammarErrorOnNode(computedPropertyName.expression, Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name); - } + function checkGrammarComputedPropertyName(node: Node): boolean { + // If node is not a computedPropertyName, just skip the grammar checking + if (node.kind !== SyntaxKind.ComputedPropertyName) { return false; } - function checkGrammarForGenerator(node: FunctionLikeDeclaration) { - if (node.asteriskToken) { - Debug.assert( - node.kind === SyntaxKind.FunctionDeclaration || - node.kind === SyntaxKind.FunctionExpression || - node.kind === SyntaxKind.MethodDeclaration); - if (node.flags & NodeFlags.Ambient) { - return grammarErrorOnNode(node.asteriskToken, Diagnostics.Generators_are_not_allowed_in_an_ambient_context); - } - if (!node.body) { - return grammarErrorOnNode(node.asteriskToken, Diagnostics.An_overload_signature_cannot_be_declared_as_a_generator); - } + const computedPropertyName = node as ComputedPropertyName; + if (computedPropertyName.expression.kind === SyntaxKind.BinaryExpression && (computedPropertyName.expression as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken) { + return grammarErrorOnNode(computedPropertyName.expression, Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name); + } + return false; + } + + function checkGrammarForGenerator(node: FunctionLikeDeclaration) { + if (node.asteriskToken) { + Debug.assert( + node.kind === SyntaxKind.FunctionDeclaration || + node.kind === SyntaxKind.FunctionExpression || + node.kind === SyntaxKind.MethodDeclaration); + if (node.flags & NodeFlags.Ambient) { + return grammarErrorOnNode(node.asteriskToken, Diagnostics.Generators_are_not_allowed_in_an_ambient_context); + } + if (!node.body) { + return grammarErrorOnNode(node.asteriskToken, Diagnostics.An_overload_signature_cannot_be_declared_as_a_generator); } } + } - function checkGrammarForInvalidQuestionMark(questionToken: QuestionToken | undefined, message: DiagnosticMessage): boolean { - return !!questionToken && grammarErrorOnNode(questionToken, message); - } + function checkGrammarForInvalidQuestionMark(questionToken: QuestionToken | undefined, message: DiagnosticMessage): boolean { + return !!questionToken && grammarErrorOnNode(questionToken, message); + } - function checkGrammarForInvalidExclamationToken(exclamationToken: ExclamationToken | undefined, message: DiagnosticMessage): boolean { - return !!exclamationToken && grammarErrorOnNode(exclamationToken, message); - } + function checkGrammarForInvalidExclamationToken(exclamationToken: ExclamationToken | undefined, message: DiagnosticMessage): boolean { + return !!exclamationToken && grammarErrorOnNode(exclamationToken, message); + } - function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { - const seen = new Map<__String, DeclarationMeaning>(); + function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { + const seen = new Map<__String, DeclarationMeaning>(); - for (const prop of node.properties) { - if (prop.kind === SyntaxKind.SpreadAssignment) { - if (inDestructuring) { - // a rest property cannot be destructured any further - const expression = skipParentheses(prop.expression); - if (isArrayLiteralExpression(expression) || isObjectLiteralExpression(expression)) { - return grammarErrorOnNode(prop.expression, Diagnostics.A_rest_element_cannot_contain_a_binding_pattern); - } + for (const prop of node.properties) { + if (prop.kind === SyntaxKind.SpreadAssignment) { + if (inDestructuring) { + // a rest property cannot be destructured any further + const expression = skipParentheses(prop.expression); + if (isArrayLiteralExpression(expression) || isObjectLiteralExpression(expression)) { + return grammarErrorOnNode(prop.expression, Diagnostics.A_rest_element_cannot_contain_a_binding_pattern); } - continue; - } - const name = prop.name; - if (name.kind === SyntaxKind.ComputedPropertyName) { - // If the name is not a ComputedPropertyName, the grammar checking will skip it - checkGrammarComputedPropertyName(name); } + continue; + } + const name = prop.name; + if (name.kind === SyntaxKind.ComputedPropertyName) { + // If the name is not a ComputedPropertyName, the grammar checking will skip it + checkGrammarComputedPropertyName(name); + } - if (prop.kind === SyntaxKind.ShorthandPropertyAssignment && !inDestructuring && prop.objectAssignmentInitializer) { - // having objectAssignmentInitializer is only valid in ObjectAssignmentPattern - // outside of destructuring it is a syntax error - return grammarErrorOnNode(prop.equalsToken!, Diagnostics.Did_you_mean_to_use_a_Colon_An_can_only_follow_a_property_name_when_the_containing_object_literal_is_part_of_a_destructuring_pattern); - } + if (prop.kind === SyntaxKind.ShorthandPropertyAssignment && !inDestructuring && prop.objectAssignmentInitializer) { + // having objectAssignmentInitializer is only valid in ObjectAssignmentPattern + // outside of destructuring it is a syntax error + return grammarErrorOnNode(prop.equalsToken!, Diagnostics.Did_you_mean_to_use_a_Colon_An_can_only_follow_a_property_name_when_the_containing_object_literal_is_part_of_a_destructuring_pattern); + } - if (name.kind === SyntaxKind.PrivateIdentifier) { - grammarErrorOnNode(name, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); - } + if (name.kind === SyntaxKind.PrivateIdentifier) { + grammarErrorOnNode(name, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + } - // Modifiers are never allowed on properties except for 'async' on a method declaration - if (prop.modifiers) { - for (const mod of prop.modifiers) { - if (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration) { - grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); - } + // Modifiers are never allowed on properties except for 'async' on a method declaration + if (prop.modifiers) { + for (const mod of prop.modifiers) { + if (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration) { + grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); } } + } - // ECMA-262 11.1.5 Object Initializer - // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true - // a.This production is contained in strict code and IsDataDescriptor(previous) is true and - // IsDataDescriptor(propId.descriptor) is true. - // b.IsDataDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true. - // c.IsAccessorDescriptor(previous) is true and IsDataDescriptor(propId.descriptor) is true. - // d.IsAccessorDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true - // and either both previous and propId.descriptor have[[Get]] fields or both previous and propId.descriptor have[[Set]] fields - let currentKind: DeclarationMeaning; - switch (prop.kind) { - case SyntaxKind.ShorthandPropertyAssignment: - checkGrammarForInvalidExclamationToken(prop.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context); - // falls through - case SyntaxKind.PropertyAssignment: - // Grammar checking for computedPropertyName and shorthandPropertyAssignment - checkGrammarForInvalidQuestionMark(prop.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional); - if (name.kind === SyntaxKind.NumericLiteral) { - checkGrammarNumericLiteral(name); - } - currentKind = DeclarationMeaning.PropertyAssignment; - break; - case SyntaxKind.MethodDeclaration: - currentKind = DeclarationMeaning.Method; - break; - case SyntaxKind.GetAccessor: - currentKind = DeclarationMeaning.GetAccessor; - break; - case SyntaxKind.SetAccessor: - currentKind = DeclarationMeaning.SetAccessor; - break; - default: - throw Debug.assertNever(prop, "Unexpected syntax kind:" + (prop as Node).kind); - } - - if (!inDestructuring) { - const effectiveName = getPropertyNameForPropertyNameNode(name); - if (effectiveName === undefined) { - continue; + // ECMA-262 11.1.5 Object Initializer + // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true + // a.This production is contained in strict code and IsDataDescriptor(previous) is true and + // IsDataDescriptor(propId.descriptor) is true. + // b.IsDataDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true. + // c.IsAccessorDescriptor(previous) is true and IsDataDescriptor(propId.descriptor) is true. + // d.IsAccessorDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true + // and either both previous and propId.descriptor have[[Get]] fields or both previous and propId.descriptor have[[Set]] fields + let currentKind: DeclarationMeaning; + switch (prop.kind) { + case SyntaxKind.ShorthandPropertyAssignment: + checkGrammarForInvalidExclamationToken(prop.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context); + // falls through + case SyntaxKind.PropertyAssignment: + // Grammar checking for computedPropertyName and shorthandPropertyAssignment + checkGrammarForInvalidQuestionMark(prop.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional); + if (name.kind === SyntaxKind.NumericLiteral) { + checkGrammarNumericLiteral(name); } + currentKind = DeclarationMeaning.PropertyAssignment; + break; + case SyntaxKind.MethodDeclaration: + currentKind = DeclarationMeaning.Method; + break; + case SyntaxKind.GetAccessor: + currentKind = DeclarationMeaning.GetAccessor; + break; + case SyntaxKind.SetAccessor: + currentKind = DeclarationMeaning.SetAccessor; + break; + default: + throw Debug.assertNever(prop, "Unexpected syntax kind:" + (prop as Node).kind); + } + + if (!inDestructuring) { + const effectiveName = getPropertyNameForPropertyNameNode(name); + if (effectiveName === undefined) { + continue; + } - const existingKind = seen.get(effectiveName); - if (!existingKind) { - seen.set(effectiveName, currentKind); + const existingKind = seen.get(effectiveName); + if (!existingKind) { + seen.set(effectiveName, currentKind); + } + else { + if ((currentKind & DeclarationMeaning.PropertyAssignmentOrMethod) && (existingKind & DeclarationMeaning.PropertyAssignmentOrMethod)) { + grammarErrorOnNode(name, Diagnostics.Duplicate_identifier_0, getTextOfNode(name)); } - else { - if ((currentKind & DeclarationMeaning.PropertyAssignmentOrMethod) && (existingKind & DeclarationMeaning.PropertyAssignmentOrMethod)) { - grammarErrorOnNode(name, Diagnostics.Duplicate_identifier_0, getTextOfNode(name)); - } - else if ((currentKind & DeclarationMeaning.GetOrSetAccessor) && (existingKind & DeclarationMeaning.GetOrSetAccessor)) { - if (existingKind !== DeclarationMeaning.GetOrSetAccessor && currentKind !== existingKind) { - seen.set(effectiveName, currentKind | existingKind); - } - else { - return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_get_Slashset_accessors_with_the_same_name); - } + else if ((currentKind & DeclarationMeaning.GetOrSetAccessor) && (existingKind & DeclarationMeaning.GetOrSetAccessor)) { + if (existingKind !== DeclarationMeaning.GetOrSetAccessor && currentKind !== existingKind) { + seen.set(effectiveName, currentKind | existingKind); } else { - return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_property_and_accessor_with_the_same_name); + return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_get_Slashset_accessors_with_the_same_name); } } + else { + return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_property_and_accessor_with_the_same_name); + } } } } + } - function checkGrammarJsxElement(node: JsxOpeningLikeElement) { - checkGrammarJsxName(node.tagName); - checkGrammarTypeArguments(node, node.typeArguments); - const seen = new Map<__String, boolean>(); + function checkGrammarJsxElement(node: JsxOpeningLikeElement) { + checkGrammarJsxName(node.tagName); + checkGrammarTypeArguments(node, node.typeArguments); + const seen = new Map<__String, boolean>(); - for (const attr of node.attributes.properties) { - if (attr.kind === SyntaxKind.JsxSpreadAttribute) { - continue; - } + for (const attr of node.attributes.properties) { + if (attr.kind === SyntaxKind.JsxSpreadAttribute) { + continue; + } - const { name, initializer } = attr; - if (!seen.get(name.escapedText)) { - seen.set(name.escapedText, true); - } - else { - return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name); - } + const { name, initializer } = attr; + if (!seen.get(name.escapedText)) { + seen.set(name.escapedText, true); + } + else { + return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name); + } - if (initializer && initializer.kind === SyntaxKind.JsxExpression && !initializer.expression) { - return grammarErrorOnNode(initializer, Diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression); - } + if (initializer && initializer.kind === SyntaxKind.JsxExpression && !initializer.expression) { + return grammarErrorOnNode(initializer, Diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression); } } + } - function checkGrammarJsxName(node: JsxTagNameExpression) { - if (isPropertyAccessExpression(node)) { - let propName: JsxTagNameExpression = node; - do { - const check = checkGrammarJsxNestedIdentifier(propName.name); - if (check) { - return check; - } - propName = propName.expression; - } while (isPropertyAccessExpression(propName)); - const check = checkGrammarJsxNestedIdentifier(propName); + function checkGrammarJsxName(node: JsxTagNameExpression) { + if (isPropertyAccessExpression(node)) { + let propName: JsxTagNameExpression = node; + do { + const check = checkGrammarJsxNestedIdentifier(propName.name); if (check) { return check; } + propName = propName.expression; + } while (isPropertyAccessExpression(propName)); + const check = checkGrammarJsxNestedIdentifier(propName); + if (check) { + return check; } + } - function checkGrammarJsxNestedIdentifier(name: MemberName | ThisExpression) { - if (isIdentifier(name) && idText(name).indexOf(":") !== -1) { - return grammarErrorOnNode(name, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names); - } + function checkGrammarJsxNestedIdentifier(name: MemberName | ThisExpression) { + if (isIdentifier(name) && idText(name).indexOf(":") !== -1) { + return grammarErrorOnNode(name, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names); } } + } - function checkGrammarJsxExpression(node: JsxExpression) { - if (node.expression && isCommaSequence(node.expression)) { - return grammarErrorOnNode(node.expression, Diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array); - } + function checkGrammarJsxExpression(node: JsxExpression) { + if (node.expression && isCommaSequence(node.expression)) { + return grammarErrorOnNode(node.expression, Diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array); } + } - function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInOrOfStatement): boolean { - if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { - return true; - } + function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInOrOfStatement): boolean { + if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { + return true; + } - if (forInOrOfStatement.kind === SyntaxKind.ForOfStatement && forInOrOfStatement.awaitModifier) { - if (!(forInOrOfStatement.flags & NodeFlags.AwaitContext)) { - const sourceFile = getSourceFileOfNode(forInOrOfStatement); - if (isInTopLevelContext(forInOrOfStatement)) { - if (!hasParseDiagnostics(sourceFile)) { - if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { - diagnostics.add(createDiagnosticForNode(forInOrOfStatement.awaitModifier, - Diagnostics.for_await_loops_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module)); - } - if ((moduleKind !== ModuleKind.ES2022 && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System && !(moduleKind === ModuleKind.NodeNext && getSourceFileOfNode(forInOrOfStatement).impliedNodeFormat === ModuleKind.ESNext)) || languageVersion < ScriptTarget.ES2017) { - diagnostics.add(createDiagnosticForNode(forInOrOfStatement.awaitModifier, - Diagnostics.Top_level_for_await_loops_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher)); - } + if (forInOrOfStatement.kind === SyntaxKind.ForOfStatement && forInOrOfStatement.awaitModifier) { + if (!(forInOrOfStatement.flags & NodeFlags.AwaitContext)) { + const sourceFile = getSourceFileOfNode(forInOrOfStatement); + if (isInTopLevelContext(forInOrOfStatement)) { + if (!hasParseDiagnostics(sourceFile)) { + if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { + diagnostics.add(createDiagnosticForNode(forInOrOfStatement.awaitModifier, + Diagnostics.for_await_loops_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module)); } - } - else { - // use of 'for-await-of' in non-async function - if (!hasParseDiagnostics(sourceFile)) { - const diagnostic = createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.for_await_loops_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); - const func = getContainingFunction(forInOrOfStatement); - if (func && func.kind !== SyntaxKind.Constructor) { - Debug.assert((getFunctionFlags(func) & FunctionFlags.Async) === 0, "Enclosing function should never be an async function."); - const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async); - addRelatedInfo(diagnostic, relatedInfo); - } - diagnostics.add(diagnostic); - return true; + if ((moduleKind !== ModuleKind.ES2022 && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System && !(moduleKind === ModuleKind.NodeNext && getSourceFileOfNode(forInOrOfStatement).impliedNodeFormat === ModuleKind.ESNext)) || languageVersion < ScriptTarget.ES2017) { + diagnostics.add(createDiagnosticForNode(forInOrOfStatement.awaitModifier, + Diagnostics.Top_level_for_await_loops_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher)); } } - return false; } - } - - if (isForOfStatement(forInOrOfStatement) && !(forInOrOfStatement.flags & NodeFlags.AwaitContext) && - isIdentifier(forInOrOfStatement.initializer) && forInOrOfStatement.initializer.escapedText === "async") { - grammarErrorOnNode(forInOrOfStatement.initializer, Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_async); - return false; - } - - if (forInOrOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) { - const variableList = forInOrOfStatement.initializer as VariableDeclarationList; - if (!checkGrammarVariableDeclarationList(variableList)) { - const declarations = variableList.declarations; - - // declarations.length can be zero if there is an error in variable declaration in for-of or for-in - // See http://www.ecma-international.org/ecma-262/6.0/#sec-for-in-and-for-of-statements for details - // For example: - // var let = 10; - // for (let of [1,2,3]) {} // this is invalid ES6 syntax - // for (let in [1,2,3]) {} // this is invalid ES6 syntax - // We will then want to skip on grammar checking on variableList declaration - if (!declarations.length) { - return false; - } - - if (declarations.length > 1) { - const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement - ? Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement - : Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_of_statement; - return grammarErrorOnFirstToken(variableList.declarations[1], diagnostic); - } - const firstDeclaration = declarations[0]; - - if (firstDeclaration.initializer) { - const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement - ? Diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer - : Diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer; - return grammarErrorOnNode(firstDeclaration.name, diagnostic); - } - if (firstDeclaration.type) { - const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement - ? Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_use_a_type_annotation - : Diagnostics.The_left_hand_side_of_a_for_of_statement_cannot_use_a_type_annotation; - return grammarErrorOnNode(firstDeclaration, diagnostic); + else { + // use of 'for-await-of' in non-async function + if (!hasParseDiagnostics(sourceFile)) { + const diagnostic = createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.for_await_loops_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); + const func = getContainingFunction(forInOrOfStatement); + if (func && func.kind !== SyntaxKind.Constructor) { + Debug.assert((getFunctionFlags(func) & FunctionFlags.Async) === 0, "Enclosing function should never be an async function."); + const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async); + addRelatedInfo(diagnostic, relatedInfo); + } + diagnostics.add(diagnostic); + return true; } } + return false; } + } + if (isForOfStatement(forInOrOfStatement) && !(forInOrOfStatement.flags & NodeFlags.AwaitContext) && + isIdentifier(forInOrOfStatement.initializer) && forInOrOfStatement.initializer.escapedText === "async") { + grammarErrorOnNode(forInOrOfStatement.initializer, Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_async); return false; } - function checkGrammarAccessor(accessor: AccessorDeclaration): boolean { - if (!(accessor.flags & NodeFlags.Ambient) && (accessor.parent.kind !== SyntaxKind.TypeLiteral) && (accessor.parent.kind !== SyntaxKind.InterfaceDeclaration)) { - if (languageVersion < ScriptTarget.ES5) { - return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher); - } - if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(accessor.name)) { - return grammarErrorOnNode(accessor.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); + if (forInOrOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) { + const variableList = forInOrOfStatement.initializer as VariableDeclarationList; + if (!checkGrammarVariableDeclarationList(variableList)) { + const declarations = variableList.declarations; + + // declarations.length can be zero if there is an error in variable declaration in for-of or for-in + // See http://www.ecma-international.org/ecma-262/6.0/#sec-for-in-and-for-of-statements for details + // For example: + // var let = 10; + // for (let of [1,2,3]) {} // this is invalid ES6 syntax + // for (let in [1,2,3]) {} // this is invalid ES6 syntax + // We will then want to skip on grammar checking on variableList declaration + if (!declarations.length) { + return false; } - if (accessor.body === undefined && !hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { - return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{"); + + if (declarations.length > 1) { + const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement + ? Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement + : Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_of_statement; + return grammarErrorOnFirstToken(variableList.declarations[1], diagnostic); } - } - if (accessor.body) { - if (hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { - return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation); + const firstDeclaration = declarations[0]; + + if (firstDeclaration.initializer) { + const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement + ? Diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer + : Diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer; + return grammarErrorOnNode(firstDeclaration.name, diagnostic); } - if (accessor.parent.kind === SyntaxKind.TypeLiteral || accessor.parent.kind === SyntaxKind.InterfaceDeclaration) { - return grammarErrorOnNode(accessor.body, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); + if (firstDeclaration.type) { + const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement + ? Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_use_a_type_annotation + : Diagnostics.The_left_hand_side_of_a_for_of_statement_cannot_use_a_type_annotation; + return grammarErrorOnNode(firstDeclaration, diagnostic); } } - if (accessor.typeParameters) { - return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters); + } + + return false; + } + + function checkGrammarAccessor(accessor: AccessorDeclaration): boolean { + if (!(accessor.flags & NodeFlags.Ambient) && (accessor.parent.kind !== SyntaxKind.TypeLiteral) && (accessor.parent.kind !== SyntaxKind.InterfaceDeclaration)) { + if (languageVersion < ScriptTarget.ES5) { + return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher); + } + if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(accessor.name)) { + return grammarErrorOnNode(accessor.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); } - if (!doesAccessorHaveCorrectParameterCount(accessor)) { - return grammarErrorOnNode(accessor.name, - accessor.kind === SyntaxKind.GetAccessor ? - Diagnostics.A_get_accessor_cannot_have_parameters : - Diagnostics.A_set_accessor_must_have_exactly_one_parameter); + if (accessor.body === undefined && !hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { + return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{"); } - if (accessor.kind === SyntaxKind.SetAccessor) { - if (accessor.type) { - return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_cannot_have_a_return_type_annotation); - } - const parameter = Debug.checkDefined(getSetAccessorValueParameter(accessor), "Return value does not match parameter count assertion."); - if (parameter.dotDotDotToken) { - return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter); - } - if (parameter.questionToken) { - return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_set_accessor_cannot_have_an_optional_parameter); - } - if (parameter.initializer) { - return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_parameter_cannot_have_an_initializer); - } + } + if (accessor.body) { + if (hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { + return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation); + } + if (accessor.parent.kind === SyntaxKind.TypeLiteral || accessor.parent.kind === SyntaxKind.InterfaceDeclaration) { + return grammarErrorOnNode(accessor.body, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); + } + } + if (accessor.typeParameters) { + return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters); + } + if (!doesAccessorHaveCorrectParameterCount(accessor)) { + return grammarErrorOnNode(accessor.name, + accessor.kind === SyntaxKind.GetAccessor ? + Diagnostics.A_get_accessor_cannot_have_parameters : + Diagnostics.A_set_accessor_must_have_exactly_one_parameter); + } + if (accessor.kind === SyntaxKind.SetAccessor) { + if (accessor.type) { + return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_cannot_have_a_return_type_annotation); + } + const parameter = Debug.checkDefined(getSetAccessorValueParameter(accessor), "Return value does not match parameter count assertion."); + if (parameter.dotDotDotToken) { + return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter); + } + if (parameter.questionToken) { + return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_set_accessor_cannot_have_an_optional_parameter); + } + if (parameter.initializer) { + return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_parameter_cannot_have_an_initializer); } - return false; } + return false; + } - /** Does the accessor have the right number of parameters? - * A get accessor has no parameters or a single `this` parameter. - * A set accessor has one parameter or a `this` parameter and one more parameter. - */ - function doesAccessorHaveCorrectParameterCount(accessor: AccessorDeclaration) { - return getAccessorThisParameter(accessor) || accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 0 : 1); + /** Does the accessor have the right number of parameters? + * A get accessor has no parameters or a single `this` parameter. + * A set accessor has one parameter or a `this` parameter and one more parameter. + */ + function doesAccessorHaveCorrectParameterCount(accessor: AccessorDeclaration) { + return getAccessorThisParameter(accessor) || accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 0 : 1); + } + + function getAccessorThisParameter(accessor: AccessorDeclaration): ParameterDeclaration | undefined { + if (accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 1 : 2)) { + return getThisParameter(accessor); } + } - function getAccessorThisParameter(accessor: AccessorDeclaration): ParameterDeclaration | undefined { - if (accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 1 : 2)) { - return getThisParameter(accessor); + function checkGrammarTypeOperatorNode(node: TypeOperatorNode) { + if (node.operator === SyntaxKind.UniqueKeyword) { + if (node.type.kind !== SyntaxKind.SymbolKeyword) { + return grammarErrorOnNode(node.type, Diagnostics._0_expected, tokenToString(SyntaxKind.SymbolKeyword)); } - } - function checkGrammarTypeOperatorNode(node: TypeOperatorNode) { - if (node.operator === SyntaxKind.UniqueKeyword) { - if (node.type.kind !== SyntaxKind.SymbolKeyword) { - return grammarErrorOnNode(node.type, Diagnostics._0_expected, tokenToString(SyntaxKind.SymbolKeyword)); + let parent = walkUpParenthesizedTypes(node.parent); + if (isInJSFile(parent) && isJSDocTypeExpression(parent)) { + parent = parent.parent; + if (isJSDocTypeTag(parent)) { + // walk up past JSDoc comment node + parent = parent.parent.parent; } - - let parent = walkUpParenthesizedTypes(node.parent); - if (isInJSFile(parent) && isJSDocTypeExpression(parent)) { - parent = parent.parent; - if (isJSDocTypeTag(parent)) { - // walk up past JSDoc comment node - parent = parent.parent.parent; + } + switch (parent.kind) { + case SyntaxKind.VariableDeclaration: + const decl = parent as VariableDeclaration; + if (decl.name.kind !== SyntaxKind.Identifier) { + return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_may_not_be_used_on_a_variable_declaration_with_a_binding_name); } - } - switch (parent.kind) { - case SyntaxKind.VariableDeclaration: - const decl = parent as VariableDeclaration; - if (decl.name.kind !== SyntaxKind.Identifier) { - return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_may_not_be_used_on_a_variable_declaration_with_a_binding_name); - } - if (!isVariableDeclarationInVariableStatement(decl)) { - return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_only_allowed_on_variables_in_a_variable_statement); - } - if (!(decl.parent.flags & NodeFlags.Const)) { - return grammarErrorOnNode((parent as VariableDeclaration).name, Diagnostics.A_variable_whose_type_is_a_unique_symbol_type_must_be_const); - } - break; + if (!isVariableDeclarationInVariableStatement(decl)) { + return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_only_allowed_on_variables_in_a_variable_statement); + } + if (!(decl.parent.flags & NodeFlags.Const)) { + return grammarErrorOnNode((parent as VariableDeclaration).name, Diagnostics.A_variable_whose_type_is_a_unique_symbol_type_must_be_const); + } + break; - case SyntaxKind.PropertyDeclaration: - if (!isStatic(parent) || - !hasEffectiveReadonlyModifier(parent)) { - return grammarErrorOnNode((parent as PropertyDeclaration).name, Diagnostics.A_property_of_a_class_whose_type_is_a_unique_symbol_type_must_be_both_static_and_readonly); - } - break; + case SyntaxKind.PropertyDeclaration: + if (!isStatic(parent) || + !hasEffectiveReadonlyModifier(parent)) { + return grammarErrorOnNode((parent as PropertyDeclaration).name, Diagnostics.A_property_of_a_class_whose_type_is_a_unique_symbol_type_must_be_both_static_and_readonly); + } + break; - case SyntaxKind.PropertySignature: - if (!hasSyntacticModifier(parent, ModifierFlags.Readonly)) { - return grammarErrorOnNode((parent as PropertySignature).name, Diagnostics.A_property_of_an_interface_or_type_literal_whose_type_is_a_unique_symbol_type_must_be_readonly); - } - break; + case SyntaxKind.PropertySignature: + if (!hasSyntacticModifier(parent, ModifierFlags.Readonly)) { + return grammarErrorOnNode((parent as PropertySignature).name, Diagnostics.A_property_of_an_interface_or_type_literal_whose_type_is_a_unique_symbol_type_must_be_readonly); + } + break; - default: - return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_not_allowed_here); - } + default: + return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_not_allowed_here); } - else if (node.operator === SyntaxKind.ReadonlyKeyword) { - if (node.type.kind !== SyntaxKind.ArrayType && node.type.kind !== SyntaxKind.TupleType) { - return grammarErrorOnFirstToken(node, Diagnostics.readonly_type_modifier_is_only_permitted_on_array_and_tuple_literal_types, tokenToString(SyntaxKind.SymbolKeyword)); - } + } + else if (node.operator === SyntaxKind.ReadonlyKeyword) { + if (node.type.kind !== SyntaxKind.ArrayType && node.type.kind !== SyntaxKind.TupleType) { + return grammarErrorOnFirstToken(node, Diagnostics.readonly_type_modifier_is_only_permitted_on_array_and_tuple_literal_types, tokenToString(SyntaxKind.SymbolKeyword)); } } + } - function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) { - if (isNonBindableDynamicName(node)) { - return grammarErrorOnNode(node, message); - } + function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) { + if (isNonBindableDynamicName(node)) { + return grammarErrorOnNode(node, message); } + } - function checkGrammarMethod(node: MethodDeclaration | MethodSignature) { - if (checkGrammarFunctionLikeDeclaration(node)) { - return true; - } + function checkGrammarMethod(node: MethodDeclaration | MethodSignature) { + if (checkGrammarFunctionLikeDeclaration(node)) { + return true; + } - if (node.kind === SyntaxKind.MethodDeclaration) { - if (node.parent.kind === SyntaxKind.ObjectLiteralExpression) { - // We only disallow modifier on a method declaration if it is a property of object-literal-expression - if (node.modifiers && !(node.modifiers.length === 1 && first(node.modifiers).kind === SyntaxKind.AsyncKeyword)) { - return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here); - } - else if (checkGrammarForInvalidQuestionMark(node.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional)) { - return true; - } - else if (checkGrammarForInvalidExclamationToken(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context)) { - return true; - } - else if (node.body === undefined) { - return grammarErrorAtPos(node, node.end - 1, ";".length, Diagnostics._0_expected, "{"); - } + if (node.kind === SyntaxKind.MethodDeclaration) { + if (node.parent.kind === SyntaxKind.ObjectLiteralExpression) { + // We only disallow modifier on a method declaration if it is a property of object-literal-expression + if (node.modifiers && !(node.modifiers.length === 1 && first(node.modifiers).kind === SyntaxKind.AsyncKeyword)) { + return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here); } - if (checkGrammarForGenerator(node)) { + else if (checkGrammarForInvalidQuestionMark(node.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional)) { return true; } - } - - if (isClassLike(node.parent)) { - if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) { - return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); - } - // Technically, computed properties in ambient contexts is disallowed - // for property declarations and accessors too, not just methods. - // However, property declarations disallow computed names in general, - // and accessors are not allowed in ambient contexts in general, - // so this error only really matters for methods. - if (node.flags & NodeFlags.Ambient) { - return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_ambient_context_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + else if (checkGrammarForInvalidExclamationToken(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context)) { + return true; } - else if (node.kind === SyntaxKind.MethodDeclaration && !node.body) { - return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_method_overload_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + else if (node.body === undefined) { + return grammarErrorAtPos(node, node.end - 1, ";".length, Diagnostics._0_expected, "{"); } } - else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { - return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); - } - else if (node.parent.kind === SyntaxKind.TypeLiteral) { - return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + if (checkGrammarForGenerator(node)) { + return true; } } - function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean { - let current: Node = node; - while (current) { - if (isFunctionLikeOrClassStaticBlockDeclaration(current)) { - return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); - } + if (isClassLike(node.parent)) { + if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) { + return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); + } + // Technically, computed properties in ambient contexts is disallowed + // for property declarations and accessors too, not just methods. + // However, property declarations disallow computed names in general, + // and accessors are not allowed in ambient contexts in general, + // so this error only really matters for methods. + if (node.flags & NodeFlags.Ambient) { + return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_ambient_context_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + else if (node.kind === SyntaxKind.MethodDeclaration && !node.body) { + return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_method_overload_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + } + else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { + return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + else if (node.parent.kind === SyntaxKind.TypeLiteral) { + return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + } - switch (current.kind) { - case SyntaxKind.LabeledStatement: - if (node.label && (current as LabeledStatement).label.escapedText === node.label.escapedText) { - // found matching label - verify that label usage is correct - // continue can only target labels that are on iteration statements - const isMisplacedContinueLabel = node.kind === SyntaxKind.ContinueStatement - && !isIterationStatement((current as LabeledStatement).statement, /*lookInLabeledStatement*/ true); + function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean { + let current: Node = node; + while (current) { + if (isFunctionLikeOrClassStaticBlockDeclaration(current)) { + return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); + } - if (isMisplacedContinueLabel) { - return grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement); - } + switch (current.kind) { + case SyntaxKind.LabeledStatement: + if (node.label && (current as LabeledStatement).label.escapedText === node.label.escapedText) { + // found matching label - verify that label usage is correct + // continue can only target labels that are on iteration statements + const isMisplacedContinueLabel = node.kind === SyntaxKind.ContinueStatement + && !isIterationStatement((current as LabeledStatement).statement, /*lookInLabeledStatement*/ true); - return false; - } - break; - case SyntaxKind.SwitchStatement: - if (node.kind === SyntaxKind.BreakStatement && !node.label) { - // unlabeled break within switch statement - ok - return false; - } - break; - default: - if (isIterationStatement(current, /*lookInLabeledStatement*/ false) && !node.label) { - // unlabeled break or continue within iteration statement - ok - return false; + if (isMisplacedContinueLabel) { + return grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement); } - break; - } - current = current.parent; + return false; + } + break; + case SyntaxKind.SwitchStatement: + if (node.kind === SyntaxKind.BreakStatement && !node.label) { + // unlabeled break within switch statement - ok + return false; + } + break; + default: + if (isIterationStatement(current, /*lookInLabeledStatement*/ false) && !node.label) { + // unlabeled break or continue within iteration statement - ok + return false; + } + break; + } + + current = current.parent; + } + + if (node.label) { + const message = node.kind === SyntaxKind.BreakStatement + ? Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement + : Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement; + + return grammarErrorOnNode(node, message); + } + else { + const message = node.kind === SyntaxKind.BreakStatement + ? Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement + : Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement; + return grammarErrorOnNode(node, message); + } + } + + function checkGrammarBindingElement(node: BindingElement) { + if (node.dotDotDotToken) { + const elements = node.parent.elements; + if (node !== last(elements)) { + return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); + } + checkGrammarForDisallowedTrailingComma(elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + + if (node.propertyName) { + return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_have_a_property_name); } + } + + if (node.dotDotDotToken && node.initializer) { + // Error on equals token which immediately precedes the initializer + return grammarErrorAtPos(node, node.initializer.pos - 1, 1, Diagnostics.A_rest_element_cannot_have_an_initializer); + } + } + + function isStringOrNumberLiteralExpression(expr: Expression) { + return isStringOrNumericLiteralLike(expr) || + expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && + (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.NumericLiteral; + } + + function isBigIntLiteralExpression(expr: Expression) { + return expr.kind === SyntaxKind.BigIntLiteral || + expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && + (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.BigIntLiteral; + } - if (node.label) { - const message = node.kind === SyntaxKind.BreakStatement - ? Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement - : Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement; + function isSimpleLiteralEnumReference(expr: Expression) { + if ((isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression))) && + isEntityNameExpression(expr.expression)) { + return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLiteral); + } + } - return grammarErrorOnNode(node, message); + function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) { + const {initializer} = node; + if (initializer) { + const isInvalidInitializer = !( + isStringOrNumberLiteralExpression(initializer) || + isSimpleLiteralEnumReference(initializer) || + initializer.kind === SyntaxKind.TrueKeyword || initializer.kind === SyntaxKind.FalseKeyword || + isBigIntLiteralExpression(initializer) + ); + const isConstOrReadonly = isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node); + if (isConstOrReadonly && !node.type) { + if (isInvalidInitializer) { + return grammarErrorOnNode(initializer, Diagnostics.A_const_initializer_in_an_ambient_context_must_be_a_string_or_numeric_literal_or_literal_enum_reference); + } } else { - const message = node.kind === SyntaxKind.BreakStatement - ? Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement - : Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement; - return grammarErrorOnNode(node, message); + return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); + } + if (!isConstOrReadonly || isInvalidInitializer) { + return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); } } + } - function checkGrammarBindingElement(node: BindingElement) { - if (node.dotDotDotToken) { - const elements = node.parent.elements; - if (node !== last(elements)) { - return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); + function checkGrammarVariableDeclaration(node: VariableDeclaration) { + if (node.parent.parent.kind !== SyntaxKind.ForInStatement && node.parent.parent.kind !== SyntaxKind.ForOfStatement) { + if (node.flags & NodeFlags.Ambient) { + checkAmbientInitializer(node); + } + else if (!node.initializer) { + if (isBindingPattern(node.name) && !isBindingPattern(node.parent)) { + return grammarErrorOnNode(node, Diagnostics.A_destructuring_declaration_must_have_an_initializer); } - checkGrammarForDisallowedTrailingComma(elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - - if (node.propertyName) { - return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_have_a_property_name); + if (isVarConst(node)) { + return grammarErrorOnNode(node, Diagnostics.const_declarations_must_be_initialized); } } + } - if (node.dotDotDotToken && node.initializer) { - // Error on equals token which immediately precedes the initializer - return grammarErrorAtPos(node, node.initializer.pos - 1, 1, Diagnostics.A_rest_element_cannot_have_an_initializer); - } + if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || node.flags & NodeFlags.Ambient)) { + const message = node.initializer + ? Diagnostics.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions + : !node.type + ? Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations + : Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context; + return grammarErrorOnNode(node.exclamationToken, message); + } + + if ((moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && moduleKind !== ModuleKind.System && + !(node.parent.parent.flags & NodeFlags.Ambient) && hasSyntacticModifier(node.parent.parent, ModifierFlags.Export)) { + checkESModuleMarker(node.name); } - function isStringOrNumberLiteralExpression(expr: Expression) { - return isStringOrNumericLiteralLike(expr) || - expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && - (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.NumericLiteral; - } + const checkLetConstNames = (isLet(node) || isVarConst(node)); + + // 1. LexicalDeclaration : LetOrConst BindingList ; + // It is a Syntax Error if the BoundNames of BindingList contains "let". + // 2. ForDeclaration: ForDeclaration : LetOrConst ForBinding + // It is a Syntax Error if the BoundNames of ForDeclaration contains "let". - function isBigIntLiteralExpression(expr: Expression) { - return expr.kind === SyntaxKind.BigIntLiteral || - expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && - (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.BigIntLiteral; - } + // It is a SyntaxError if a VariableDeclaration or VariableDeclarationNoIn occurs within strict code + // and its Identifier is eval or arguments + return checkLetConstNames && checkGrammarNameInLetOrConstDeclarations(node.name); + } - function isSimpleLiteralEnumReference(expr: Expression) { - if ((isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression))) && - isEntityNameExpression(expr.expression)) { - return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLiteral); + function checkESModuleMarker(name: Identifier | BindingPattern): boolean { + if (name.kind === SyntaxKind.Identifier) { + if (idText(name) === "__esModule") { + return grammarErrorOnNodeSkippedOn("noEmit", name, Diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules); } } - - function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) { - const {initializer} = node; - if (initializer) { - const isInvalidInitializer = !( - isStringOrNumberLiteralExpression(initializer) || - isSimpleLiteralEnumReference(initializer) || - initializer.kind === SyntaxKind.TrueKeyword || initializer.kind === SyntaxKind.FalseKeyword || - isBigIntLiteralExpression(initializer) - ); - const isConstOrReadonly = isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node); - if (isConstOrReadonly && !node.type) { - if (isInvalidInitializer) { - return grammarErrorOnNode(initializer, Diagnostics.A_const_initializer_in_an_ambient_context_must_be_a_string_or_numeric_literal_or_literal_enum_reference); - } - } - else { - return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); - } - if (!isConstOrReadonly || isInvalidInitializer) { - return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); + else { + const elements = name.elements; + for (const element of elements) { + if (!isOmittedExpression(element)) { + return checkESModuleMarker(element.name); } } } + return false; + } - function checkGrammarVariableDeclaration(node: VariableDeclaration) { - if (node.parent.parent.kind !== SyntaxKind.ForInStatement && node.parent.parent.kind !== SyntaxKind.ForOfStatement) { - if (node.flags & NodeFlags.Ambient) { - checkAmbientInitializer(node); - } - else if (!node.initializer) { - if (isBindingPattern(node.name) && !isBindingPattern(node.parent)) { - return grammarErrorOnNode(node, Diagnostics.A_destructuring_declaration_must_have_an_initializer); - } - if (isVarConst(node)) { - return grammarErrorOnNode(node, Diagnostics.const_declarations_must_be_initialized); - } - } - } - - if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || node.flags & NodeFlags.Ambient)) { - const message = node.initializer - ? Diagnostics.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions - : !node.type - ? Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations - : Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context; - return grammarErrorOnNode(node.exclamationToken, message); + function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean { + if (name.kind === SyntaxKind.Identifier) { + if (name.originalKeywordKind === SyntaxKind.LetKeyword) { + return grammarErrorOnNode(name, Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations); } - - if ((moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && moduleKind !== ModuleKind.System && - !(node.parent.parent.flags & NodeFlags.Ambient) && hasSyntacticModifier(node.parent.parent, ModifierFlags.Export)) { - checkESModuleMarker(node.name); + } + else { + const elements = name.elements; + for (const element of elements) { + if (!isOmittedExpression(element)) { + checkGrammarNameInLetOrConstDeclarations(element.name); + } } + } + return false; + } - const checkLetConstNames = (isLet(node) || isVarConst(node)); + function checkGrammarVariableDeclarationList(declarationList: VariableDeclarationList): boolean { + const declarations = declarationList.declarations; + if (checkGrammarForDisallowedTrailingComma(declarationList.declarations)) { + return true; + } - // 1. LexicalDeclaration : LetOrConst BindingList ; - // It is a Syntax Error if the BoundNames of BindingList contains "let". - // 2. ForDeclaration: ForDeclaration : LetOrConst ForBinding - // It is a Syntax Error if the BoundNames of ForDeclaration contains "let". + if (!declarationList.declarations.length) { + return grammarErrorAtPos(declarationList, declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty); + } + return false; + } - // It is a SyntaxError if a VariableDeclaration or VariableDeclarationNoIn occurs within strict code - // and its Identifier is eval or arguments - return checkLetConstNames && checkGrammarNameInLetOrConstDeclarations(node.name); + function allowLetAndConstDeclarations(parent: Node): boolean { + switch (parent.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + return false; + case SyntaxKind.LabeledStatement: + return allowLetAndConstDeclarations(parent.parent); } - function checkESModuleMarker(name: Identifier | BindingPattern): boolean { - if (name.kind === SyntaxKind.Identifier) { - if (idText(name) === "__esModule") { - return grammarErrorOnNodeSkippedOn("noEmit", name, Diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules); - } + return true; + } + + function checkGrammarForDisallowedLetOrConstStatement(node: VariableStatement) { + if (!allowLetAndConstDeclarations(node.parent)) { + if (isLet(node.declarationList)) { + return grammarErrorOnNode(node, Diagnostics.let_declarations_can_only_be_declared_inside_a_block); } - else { - const elements = name.elements; - for (const element of elements) { - if (!isOmittedExpression(element)) { - return checkESModuleMarker(element.name); - } - } + else if (isVarConst(node.declarationList)) { + return grammarErrorOnNode(node, Diagnostics.const_declarations_can_only_be_declared_inside_a_block); } - return false; } + } - function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean { - if (name.kind === SyntaxKind.Identifier) { - if (name.originalKeywordKind === SyntaxKind.LetKeyword) { - return grammarErrorOnNode(name, Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations); + function checkGrammarMetaProperty(node: MetaProperty) { + const escapedText = node.name.escapedText; + switch (node.keywordToken) { + case SyntaxKind.NewKeyword: + if (escapedText !== "target") { + return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "target"); } - } - else { - const elements = name.elements; - for (const element of elements) { - if (!isOmittedExpression(element)) { - checkGrammarNameInLetOrConstDeclarations(element.name); - } + break; + case SyntaxKind.ImportKeyword: + if (escapedText !== "meta") { + return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "meta"); } - } - return false; + break; } + } - function checkGrammarVariableDeclarationList(declarationList: VariableDeclarationList): boolean { - const declarations = declarationList.declarations; - if (checkGrammarForDisallowedTrailingComma(declarationList.declarations)) { - return true; - } + function hasParseDiagnostics(sourceFile: SourceFile): boolean { + return sourceFile.parseDiagnostics.length > 0; + } - if (!declarationList.declarations.length) { - return grammarErrorAtPos(declarationList, declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty); - } - return false; + function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2)); + return true; } + return false; + } - function allowLetAndConstDeclarations(parent: Node): boolean { - switch (parent.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - return false; - case SyntaxKind.LabeledStatement: - return allowLetAndConstDeclarations(parent.parent); - } + function grammarErrorAtPos(nodeForSourceFile: Node, start: number, length: number, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(nodeForSourceFile); + if (!hasParseDiagnostics(sourceFile)) { + diagnostics.add(createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2)); + return true; + } + return false; + } + function grammarErrorOnNodeSkippedOn(key: keyof CompilerOptions, node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + errorSkippedOn(key, node, message, arg0, arg1, arg2); return true; } + return false; + } - function checkGrammarForDisallowedLetOrConstStatement(node: VariableStatement) { - if (!allowLetAndConstDeclarations(node.parent)) { - if (isLet(node.declarationList)) { - return grammarErrorOnNode(node, Diagnostics.let_declarations_can_only_be_declared_inside_a_block); - } - else if (isVarConst(node.declarationList)) { - return grammarErrorOnNode(node, Diagnostics.const_declarations_can_only_be_declared_inside_a_block); - } - } + function grammarErrorOnNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + diagnostics.add(createDiagnosticForNode(node, message, arg0, arg1, arg2)); + return true; } + return false; + } - function checkGrammarMetaProperty(node: MetaProperty) { - const escapedText = node.name.escapedText; - switch (node.keywordToken) { - case SyntaxKind.NewKeyword: - if (escapedText !== "target") { - return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "target"); - } - break; - case SyntaxKind.ImportKeyword: - if (escapedText !== "meta") { - return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "meta"); - } - break; - } + function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) { + const jsdocTypeParameters = isInJSFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined; + const range = node.typeParameters || jsdocTypeParameters && firstOrUndefined(jsdocTypeParameters); + if (range) { + const pos = range.pos === range.end ? range.pos : skipTrivia(getSourceFileOfNode(node).text, range.pos); + return grammarErrorAtPos(node, pos, range.end - pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration); } + } - function hasParseDiagnostics(sourceFile: SourceFile): boolean { - return sourceFile.parseDiagnostics.length > 0; + function checkGrammarConstructorTypeAnnotation(node: ConstructorDeclaration) { + const type = getEffectiveReturnTypeNode(node); + if (type) { + return grammarErrorOnNode(type, Diagnostics.Type_annotation_cannot_appear_on_a_constructor_declaration); } + } - function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const span = getSpanOfTokenAtPosition(sourceFile, node.pos); - diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2)); + function checkGrammarProperty(node: PropertyDeclaration | PropertySignature) { + if (isComputedPropertyName(node.name) && isBinaryExpression(node.name.expression) && node.name.expression.operatorToken.kind === SyntaxKind.InKeyword) { + return grammarErrorOnNode( + (node.parent as ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode).members[0], + Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); + } + if (isClassLike(node.parent)) { + if (isStringLiteral(node.name) && node.name.text === "constructor") { + return grammarErrorOnNode(node.name, Diagnostics.Classes_may_not_have_a_field_named_constructor); + } + if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_have_a_simple_literal_type_or_a_unique_symbol_type)) { return true; } - return false; + if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) { + return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); + } } - - function grammarErrorAtPos(nodeForSourceFile: Node, start: number, length: number, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(nodeForSourceFile); - if (!hasParseDiagnostics(sourceFile)) { - diagnostics.add(createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2)); + else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { + if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { return true; } - return false; + if (node.initializer) { + return grammarErrorOnNode(node.initializer, Diagnostics.An_interface_property_cannot_have_an_initializer); + } } - - function grammarErrorOnNodeSkippedOn(key: keyof CompilerOptions, node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - errorSkippedOn(key, node, message, arg0, arg1, arg2); + else if (isTypeLiteralNode(node.parent)) { + if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { return true; } - return false; + if (node.initializer) { + return grammarErrorOnNode(node.initializer, Diagnostics.A_type_literal_property_cannot_have_an_initializer); + } } - function grammarErrorOnNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - diagnostics.add(createDiagnosticForNode(node, message, arg0, arg1, arg2)); - return true; - } - return false; + if (node.flags & NodeFlags.Ambient) { + checkAmbientInitializer(node); } - function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) { - const jsdocTypeParameters = isInJSFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined; - const range = node.typeParameters || jsdocTypeParameters && firstOrUndefined(jsdocTypeParameters); - if (range) { - const pos = range.pos === range.end ? range.pos : skipTrivia(getSourceFileOfNode(node).text, range.pos); - return grammarErrorAtPos(node, pos, range.end - pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration); - } + if (isPropertyDeclaration(node) && node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer || + node.flags & NodeFlags.Ambient || isStatic(node) || hasAbstractModifier(node))) { + const message = node.initializer + ? Diagnostics.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions + : !node.type + ? Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations + : Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context; + return grammarErrorOnNode(node.exclamationToken, message); } + } - function checkGrammarConstructorTypeAnnotation(node: ConstructorDeclaration) { - const type = getEffectiveReturnTypeNode(node); - if (type) { - return grammarErrorOnNode(type, Diagnostics.Type_annotation_cannot_appear_on_a_constructor_declaration); - } + function checkGrammarTopLevelElementForRequiredDeclareModifier(node: Node): boolean { + // A declare modifier is required for any top level .d.ts declaration except export=, export default, export as namespace + // interfaces and imports categories: + // + // DeclarationElement: + // ExportAssignment + // export_opt InterfaceDeclaration + // export_opt TypeAliasDeclaration + // export_opt ImportDeclaration + // export_opt ExternalImportDeclaration + // export_opt AmbientDeclaration + // + // TODO: The spec needs to be amended to reflect this grammar. + if (node.kind === SyntaxKind.InterfaceDeclaration || + node.kind === SyntaxKind.TypeAliasDeclaration || + node.kind === SyntaxKind.ImportDeclaration || + node.kind === SyntaxKind.ImportEqualsDeclaration || + node.kind === SyntaxKind.ExportDeclaration || + node.kind === SyntaxKind.ExportAssignment || + node.kind === SyntaxKind.NamespaceExportDeclaration || + hasSyntacticModifier(node, ModifierFlags.Ambient | ModifierFlags.Export | ModifierFlags.Default)) { + return false; } - function checkGrammarProperty(node: PropertyDeclaration | PropertySignature) { - if (isComputedPropertyName(node.name) && isBinaryExpression(node.name.expression) && node.name.expression.operatorToken.kind === SyntaxKind.InKeyword) { - return grammarErrorOnNode( - (node.parent as ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode).members[0], - Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); - } - if (isClassLike(node.parent)) { - if (isStringLiteral(node.name) && node.name.text === "constructor") { - return grammarErrorOnNode(node.name, Diagnostics.Classes_may_not_have_a_field_named_constructor); - } - if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_have_a_simple_literal_type_or_a_unique_symbol_type)) { - return true; - } - if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) { - return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); - } - } - else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { - if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { - return true; - } - if (node.initializer) { - return grammarErrorOnNode(node.initializer, Diagnostics.An_interface_property_cannot_have_an_initializer); - } - } - else if (isTypeLiteralNode(node.parent)) { - if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { + return grammarErrorOnFirstToken(node, Diagnostics.Top_level_declarations_in_d_ts_files_must_start_with_either_a_declare_or_export_modifier); + } + + function checkGrammarTopLevelElementsForRequiredDeclareModifier(file: SourceFile): boolean { + for (const decl of file.statements) { + if (isDeclaration(decl) || decl.kind === SyntaxKind.VariableStatement) { + if (checkGrammarTopLevelElementForRequiredDeclareModifier(decl)) { return true; } - if (node.initializer) { - return grammarErrorOnNode(node.initializer, Diagnostics.A_type_literal_property_cannot_have_an_initializer); - } } + } + return false; + } - if (node.flags & NodeFlags.Ambient) { - checkAmbientInitializer(node); - } + function checkGrammarSourceFile(node: SourceFile): boolean { + return !!(node.flags & NodeFlags.Ambient) && checkGrammarTopLevelElementsForRequiredDeclareModifier(node); + } - if (isPropertyDeclaration(node) && node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer || - node.flags & NodeFlags.Ambient || isStatic(node) || hasAbstractModifier(node))) { - const message = node.initializer - ? Diagnostics.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions - : !node.type - ? Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations - : Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context; - return grammarErrorOnNode(node.exclamationToken, message); + function checkGrammarStatementInAmbientContext(node: Node): boolean { + if (node.flags & NodeFlags.Ambient) { + // Find containing block which is either Block, ModuleBlock, SourceFile + const links = getNodeLinks(node); + if (!links.hasReportedStatementInAmbientContext && (isFunctionLike(node.parent) || isAccessor(node.parent))) { + return getNodeLinks(node).hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); } - } - function checkGrammarTopLevelElementForRequiredDeclareModifier(node: Node): boolean { - // A declare modifier is required for any top level .d.ts declaration except export=, export default, export as namespace - // interfaces and imports categories: - // - // DeclarationElement: - // ExportAssignment - // export_opt InterfaceDeclaration - // export_opt TypeAliasDeclaration - // export_opt ImportDeclaration - // export_opt ExternalImportDeclaration - // export_opt AmbientDeclaration + // We are either parented by another statement, or some sort of block. + // If we're in a block, we only want to really report an error once + // to prevent noisiness. So use a bit on the block to indicate if + // this has already been reported, and don't report if it has. // - // TODO: The spec needs to be amended to reflect this grammar. - if (node.kind === SyntaxKind.InterfaceDeclaration || - node.kind === SyntaxKind.TypeAliasDeclaration || - node.kind === SyntaxKind.ImportDeclaration || - node.kind === SyntaxKind.ImportEqualsDeclaration || - node.kind === SyntaxKind.ExportDeclaration || - node.kind === SyntaxKind.ExportAssignment || - node.kind === SyntaxKind.NamespaceExportDeclaration || - hasSyntacticModifier(node, ModifierFlags.Ambient | ModifierFlags.Export | ModifierFlags.Default)) { - return false; + if (node.parent.kind === SyntaxKind.Block || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + const links = getNodeLinks(node.parent); + // Check if the containing block ever report this error + if (!links.hasReportedStatementInAmbientContext) { + return links.hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.Statements_are_not_allowed_in_ambient_contexts); + } + } + else { + // We must be parented by a statement. If so, there's no need + // to report the error as our parent will have already done it. + // Debug.assert(isStatement(node.parent)); } - - return grammarErrorOnFirstToken(node, Diagnostics.Top_level_declarations_in_d_ts_files_must_start_with_either_a_declare_or_export_modifier); } + return false; + } - function checkGrammarTopLevelElementsForRequiredDeclareModifier(file: SourceFile): boolean { - for (const decl of file.statements) { - if (isDeclaration(decl) || decl.kind === SyntaxKind.VariableStatement) { - if (checkGrammarTopLevelElementForRequiredDeclareModifier(decl)) { - return true; - } - } + function checkGrammarNumericLiteral(node: NumericLiteral): boolean { + // Grammar checking + if (node.numericLiteralFlags & TokenFlags.Octal) { + let diagnosticMessage: DiagnosticMessage | undefined; + if (languageVersion >= ScriptTarget.ES5) { + diagnosticMessage = Diagnostics.Octal_literals_are_not_available_when_targeting_ECMAScript_5_and_higher_Use_the_syntax_0; + } + else if (isChildOfNodeWithKind(node, SyntaxKind.LiteralType)) { + diagnosticMessage = Diagnostics.Octal_literal_types_must_use_ES2015_syntax_Use_the_syntax_0; + } + else if (isChildOfNodeWithKind(node, SyntaxKind.EnumMember)) { + diagnosticMessage = Diagnostics.Octal_literals_are_not_allowed_in_enums_members_initializer_Use_the_syntax_0; + } + if (diagnosticMessage) { + const withMinus = isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.MinusToken; + const literal = (withMinus ? "-" : "") + "0o" + node.text; + return grammarErrorOnNode(withMinus ? node.parent : node, diagnosticMessage, literal); } - return false; } - function checkGrammarSourceFile(node: SourceFile): boolean { - return !!(node.flags & NodeFlags.Ambient) && checkGrammarTopLevelElementsForRequiredDeclareModifier(node); - } + // Realism (size) checking + checkNumericLiteralValueSize(node); - function checkGrammarStatementInAmbientContext(node: Node): boolean { - if (node.flags & NodeFlags.Ambient) { - // Find containing block which is either Block, ModuleBlock, SourceFile - const links = getNodeLinks(node); - if (!links.hasReportedStatementInAmbientContext && (isFunctionLike(node.parent) || isAccessor(node.parent))) { - return getNodeLinks(node).hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); - } + return false; + } - // We are either parented by another statement, or some sort of block. - // If we're in a block, we only want to really report an error once - // to prevent noisiness. So use a bit on the block to indicate if - // this has already been reported, and don't report if it has. - // - if (node.parent.kind === SyntaxKind.Block || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - const links = getNodeLinks(node.parent); - // Check if the containing block ever report this error - if (!links.hasReportedStatementInAmbientContext) { - return links.hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.Statements_are_not_allowed_in_ambient_contexts); - } - } - else { - // We must be parented by a statement. If so, there's no need - // to report the error as our parent will have already done it. - // Debug.assert(isStatement(node.parent)); - } - } - return false; + function checkNumericLiteralValueSize(node: NumericLiteral) { + // Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint + // Literals with 15 or fewer characters aren't long enough to reach past 2^53 - 1 + // Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway + if (node.numericLiteralFlags & TokenFlags.Scientific || node.text.length <= 15 || node.text.indexOf(".") !== -1) { + return; } - function checkGrammarNumericLiteral(node: NumericLiteral): boolean { - // Grammar checking - if (node.numericLiteralFlags & TokenFlags.Octal) { - let diagnosticMessage: DiagnosticMessage | undefined; - if (languageVersion >= ScriptTarget.ES5) { - diagnosticMessage = Diagnostics.Octal_literals_are_not_available_when_targeting_ECMAScript_5_and_higher_Use_the_syntax_0; - } - else if (isChildOfNodeWithKind(node, SyntaxKind.LiteralType)) { - diagnosticMessage = Diagnostics.Octal_literal_types_must_use_ES2015_syntax_Use_the_syntax_0; - } - else if (isChildOfNodeWithKind(node, SyntaxKind.EnumMember)) { - diagnosticMessage = Diagnostics.Octal_literals_are_not_allowed_in_enums_members_initializer_Use_the_syntax_0; - } - if (diagnosticMessage) { - const withMinus = isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.MinusToken; - const literal = (withMinus ? "-" : "") + "0o" + node.text; - return grammarErrorOnNode(withMinus ? node.parent : node, diagnosticMessage, literal); - } - } - - // Realism (size) checking - checkNumericLiteralValueSize(node); - - return false; + // We can't rely on the runtime to accurately store and compare extremely large numeric values + // Even for internal use, we use getTextOfNode: https://github.com/microsoft/TypeScript/issues/33298 + // Thus, if the runtime claims a too-large number is lower than Number.MAX_SAFE_INTEGER, + // it's likely addition operations on it will fail too + const apparentValue = +getTextOfNode(node); + if (apparentValue <= 2 ** 53 - 1 && apparentValue + 1 > apparentValue) { + return; } - function checkNumericLiteralValueSize(node: NumericLiteral) { - // Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint - // Literals with 15 or fewer characters aren't long enough to reach past 2^53 - 1 - // Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway - if (node.numericLiteralFlags & TokenFlags.Scientific || node.text.length <= 15 || node.text.indexOf(".") !== -1) { - return; - } + addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.Numeric_literals_with_absolute_values_equal_to_2_53_or_greater_are_too_large_to_be_represented_accurately_as_integers)); + } - // We can't rely on the runtime to accurately store and compare extremely large numeric values - // Even for internal use, we use getTextOfNode: https://github.com/microsoft/TypeScript/issues/33298 - // Thus, if the runtime claims a too-large number is lower than Number.MAX_SAFE_INTEGER, - // it's likely addition operations on it will fail too - const apparentValue = +getTextOfNode(node); - if (apparentValue <= 2 ** 53 - 1 && apparentValue + 1 > apparentValue) { - return; + function checkGrammarBigIntLiteral(node: BigIntLiteral): boolean { + const literalType = isLiteralTypeNode(node.parent) || + isPrefixUnaryExpression(node.parent) && isLiteralTypeNode(node.parent.parent); + if (!literalType) { + if (languageVersion < ScriptTarget.ES2020) { + if (grammarErrorOnNode(node, Diagnostics.BigInt_literals_are_not_available_when_targeting_lower_than_ES2020)) { + return true; + } } + } + return false; + } - addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.Numeric_literals_with_absolute_values_equal_to_2_53_or_greater_are_too_large_to_be_represented_accurately_as_integers)); + function grammarErrorAfterFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add(createFileDiagnostic(sourceFile, textSpanEnd(span), /*length*/ 0, message, arg0, arg1, arg2)); + return true; } + return false; + } - function checkGrammarBigIntLiteral(node: BigIntLiteral): boolean { - const literalType = isLiteralTypeNode(node.parent) || - isPrefixUnaryExpression(node.parent) && isLiteralTypeNode(node.parent.parent); - if (!literalType) { - if (languageVersion < ScriptTarget.ES2020) { - if (grammarErrorOnNode(node, Diagnostics.BigInt_literals_are_not_available_when_targeting_lower_than_ES2020)) { - return true; - } + function getAmbientModules(): Symbol[] { + if (!ambientModulesCache) { + ambientModulesCache = []; + globals.forEach((global, sym) => { + // No need to `unescapeLeadingUnderscores`, an escaped symbol is never an ambient module. + if (ambientModuleSymbolRegex.test(sym as string)) { + ambientModulesCache!.push(global); } - } - return false; + }); } + return ambientModulesCache; + } - function grammarErrorAfterFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const span = getSpanOfTokenAtPosition(sourceFile, node.pos); - diagnostics.add(createFileDiagnostic(sourceFile, textSpanEnd(span), /*length*/ 0, message, arg0, arg1, arg2)); - return true; - } - return false; + function checkGrammarImportClause(node: ImportClause): boolean { + if (node.isTypeOnly && node.name && node.namedBindings) { + return grammarErrorOnNode(node, Diagnostics.A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both); } - - function getAmbientModules(): Symbol[] { - if (!ambientModulesCache) { - ambientModulesCache = []; - globals.forEach((global, sym) => { - // No need to `unescapeLeadingUnderscores`, an escaped symbol is never an ambient module. - if (ambientModuleSymbolRegex.test(sym as string)) { - ambientModulesCache!.push(global); - } - }); - } - return ambientModulesCache; + if (node.isTypeOnly && node.namedBindings?.kind === SyntaxKind.NamedImports) { + return checkGrammarNamedImportsOrExports(node.namedBindings); } + return false; + } - function checkGrammarImportClause(node: ImportClause): boolean { - if (node.isTypeOnly && node.name && node.namedBindings) { - return grammarErrorOnNode(node, Diagnostics.A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both); + function checkGrammarNamedImportsOrExports(namedBindings: NamedImportsOrExports): boolean { + return !!forEach(namedBindings.elements, specifier => { + if (specifier.isTypeOnly) { + return grammarErrorOnFirstToken( + specifier, + specifier.kind === SyntaxKind.ImportSpecifier + ? Diagnostics.The_type_modifier_cannot_be_used_on_a_named_import_when_import_type_is_used_on_its_import_statement + : Diagnostics.The_type_modifier_cannot_be_used_on_a_named_export_when_export_type_is_used_on_its_export_statement); } - if (node.isTypeOnly && node.namedBindings?.kind === SyntaxKind.NamedImports) { - return checkGrammarNamedImportsOrExports(node.namedBindings); - } - return false; - } + }); + } - function checkGrammarNamedImportsOrExports(namedBindings: NamedImportsOrExports): boolean { - return !!forEach(namedBindings.elements, specifier => { - if (specifier.isTypeOnly) { - return grammarErrorOnFirstToken( - specifier, - specifier.kind === SyntaxKind.ImportSpecifier - ? Diagnostics.The_type_modifier_cannot_be_used_on_a_named_import_when_import_type_is_used_on_its_import_statement - : Diagnostics.The_type_modifier_cannot_be_used_on_a_named_export_when_export_type_is_used_on_its_export_statement); - } - }); + function checkGrammarImportCallExpression(node: ImportCall): boolean { + if (moduleKind === ModuleKind.ES2015) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_es2022_esnext_commonjs_amd_system_umd_node12_or_nodenext); } - function checkGrammarImportCallExpression(node: ImportCall): boolean { - if (moduleKind === ModuleKind.ES2015) { - return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_es2022_esnext_commonjs_amd_system_umd_node12_or_nodenext); - } - - if (node.typeArguments) { - return grammarErrorOnNode(node, Diagnostics.Dynamic_import_cannot_have_type_arguments); - } + if (node.typeArguments) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_import_cannot_have_type_arguments); + } - const nodeArguments = node.arguments; - if (moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.NodeNext) { - // We are allowed trailing comma after proposal-import-assertions. - checkGrammarForDisallowedTrailingComma(nodeArguments); + const nodeArguments = node.arguments; + if (moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.NodeNext) { + // We are allowed trailing comma after proposal-import-assertions. + checkGrammarForDisallowedTrailingComma(nodeArguments); - if (nodeArguments.length > 1) { - const assertionArgument = nodeArguments[1]; - return grammarErrorOnNode(assertionArgument, Diagnostics.Dynamic_imports_only_support_a_second_argument_when_the_module_option_is_set_to_esnext_or_nodenext); - } + if (nodeArguments.length > 1) { + const assertionArgument = nodeArguments[1]; + return grammarErrorOnNode(assertionArgument, Diagnostics.Dynamic_imports_only_support_a_second_argument_when_the_module_option_is_set_to_esnext_or_nodenext); } + } - if (nodeArguments.length === 0 || nodeArguments.length > 2) { - return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_assertion_as_arguments); - } + if (nodeArguments.length === 0 || nodeArguments.length > 2) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_assertion_as_arguments); + } - // see: parseArgumentOrArrayLiteralElement...we use this function which parse arguments of callExpression to parse specifier for dynamic import. - // parseArgumentOrArrayLiteralElement allows spread element to be in an argument list which is not allowed as specifier in dynamic import. - const spreadElement = find(nodeArguments, isSpreadElement); - if (spreadElement) { - return grammarErrorOnNode(spreadElement, Diagnostics.Argument_of_dynamic_import_cannot_be_spread_element); - } - return false; + // see: parseArgumentOrArrayLiteralElement...we use this function which parse arguments of callExpression to parse specifier for dynamic import. + // parseArgumentOrArrayLiteralElement allows spread element to be in an argument list which is not allowed as specifier in dynamic import. + const spreadElement = find(nodeArguments, isSpreadElement); + if (spreadElement) { + return grammarErrorOnNode(spreadElement, Diagnostics.Argument_of_dynamic_import_cannot_be_spread_element); } + return false; + } - function findMatchingTypeReferenceOrTypeAliasReference(source: Type, unionTarget: UnionOrIntersectionType) { - const sourceObjectFlags = getObjectFlags(source); - if (sourceObjectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous) && unionTarget.flags & TypeFlags.Union) { - return find(unionTarget.types, target => { - if (target.flags & TypeFlags.Object) { - const overlapObjFlags = sourceObjectFlags & getObjectFlags(target); - if (overlapObjFlags & ObjectFlags.Reference) { - return (source as TypeReference).target === (target as TypeReference).target; - } - if (overlapObjFlags & ObjectFlags.Anonymous) { - return !!(source as AnonymousType).aliasSymbol && (source as AnonymousType).aliasSymbol === (target as AnonymousType).aliasSymbol; - } + function findMatchingTypeReferenceOrTypeAliasReference(source: Type, unionTarget: UnionOrIntersectionType) { + const sourceObjectFlags = getObjectFlags(source); + if (sourceObjectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous) && unionTarget.flags & TypeFlags.Union) { + return find(unionTarget.types, target => { + if (target.flags & TypeFlags.Object) { + const overlapObjFlags = sourceObjectFlags & getObjectFlags(target); + if (overlapObjFlags & ObjectFlags.Reference) { + return (source as TypeReference).target === (target as TypeReference).target; } - return false; - }); - } + if (overlapObjFlags & ObjectFlags.Anonymous) { + return !!(source as AnonymousType).aliasSymbol && (source as AnonymousType).aliasSymbol === (target as AnonymousType).aliasSymbol; + } + } + return false; + }); } + } - function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) { - if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && someType(unionTarget, isArrayLikeType)) { - return find(unionTarget.types, t => !isArrayLikeType(t)); - } + function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) { + if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && someType(unionTarget, isArrayLikeType)) { + return find(unionTarget.types, t => !isArrayLikeType(t)); } + } - function findBestTypeForInvokable(source: Type, unionTarget: UnionOrIntersectionType) { - let signatureKind = SignatureKind.Call; - const hasSignatures = getSignaturesOfType(source, signatureKind).length > 0 || - (signatureKind = SignatureKind.Construct, getSignaturesOfType(source, signatureKind).length > 0); - if (hasSignatures) { - return find(unionTarget.types, t => getSignaturesOfType(t, signatureKind).length > 0); - } + function findBestTypeForInvokable(source: Type, unionTarget: UnionOrIntersectionType) { + let signatureKind = SignatureKind.Call; + const hasSignatures = getSignaturesOfType(source, signatureKind).length > 0 || + (signatureKind = SignatureKind.Construct, getSignaturesOfType(source, signatureKind).length > 0); + if (hasSignatures) { + return find(unionTarget.types, t => getSignaturesOfType(t, signatureKind).length > 0); } + } - function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) { - let bestMatch: Type | undefined; - let matchingCount = 0; - for (const target of unionTarget.types) { - const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]); - if (overlap.flags & TypeFlags.Index) { - // perfect overlap of keys - bestMatch = target; - matchingCount = Infinity; - } - else if (overlap.flags & TypeFlags.Union) { - // We only want to account for literal types otherwise. - // If we have a union of index types, it seems likely that we - // needed to elaborate between two generic mapped types anyway. - const len = length(filter((overlap as UnionType).types, isUnitType)); - if (len >= matchingCount) { - bestMatch = target; - matchingCount = len; - } - } - else if (isUnitType(overlap) && 1 >= matchingCount) { + function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) { + let bestMatch: Type | undefined; + let matchingCount = 0; + for (const target of unionTarget.types) { + const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]); + if (overlap.flags & TypeFlags.Index) { + // perfect overlap of keys + bestMatch = target; + matchingCount = Infinity; + } + else if (overlap.flags & TypeFlags.Union) { + // We only want to account for literal types otherwise. + // If we have a union of index types, it seems likely that we + // needed to elaborate between two generic mapped types anyway. + const len = length(filter((overlap as UnionType).types, isUnitType)); + if (len >= matchingCount) { bestMatch = target; - matchingCount = 1; + matchingCount = len; } } - return bestMatch; + else if (isUnitType(overlap) && 1 >= matchingCount) { + bestMatch = target; + matchingCount = 1; + } } + return bestMatch; + } - function filterPrimitivesIfContainsNonPrimitive(type: UnionType) { - if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) { - const result = filterType(type, t => !(t.flags & TypeFlags.Primitive)); - if (!(result.flags & TypeFlags.Never)) { - return result; - } + function filterPrimitivesIfContainsNonPrimitive(type: UnionType) { + if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) { + const result = filterType(type, t => !(t.flags & TypeFlags.Primitive)); + if (!(result.flags & TypeFlags.Never)) { + return result; } - return type; } + return type; + } - // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly - function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary, skipPartial?: boolean) { - if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { - const match = getMatchingUnionConstituentForType(target as UnionType, source); - if (match) { - return match; - } - const sourceProperties = getPropertiesOfType(source); - if (sourceProperties) { - const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); - if (sourcePropertiesFiltered) { - return discriminateTypeByDiscriminableItems(target as UnionType, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo, /*defaultValue*/ undefined, skipPartial); - } + // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly + function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary, skipPartial?: boolean) { + if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { + const match = getMatchingUnionConstituentForType(target as UnionType, source); + if (match) { + return match; + } + const sourceProperties = getPropertiesOfType(source); + if (sourceProperties) { + const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); + if (sourcePropertiesFiltered) { + return discriminateTypeByDiscriminableItems(target as UnionType, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo, /*defaultValue*/ undefined, skipPartial); } } - return undefined; } + return undefined; } +} - function isNotAccessor(declaration: Declaration): boolean { - // Accessors check for their own matching duplicates, and in contexts where they are valid, there are already duplicate identifier checks - return !isAccessor(declaration); - } +function isNotAccessor(declaration: Declaration): boolean { + // Accessors check for their own matching duplicates, and in contexts where they are valid, there are already duplicate identifier checks + return !isAccessor(declaration); +} - function isNotOverload(declaration: Declaration): boolean { - return (declaration.kind !== SyntaxKind.FunctionDeclaration && declaration.kind !== SyntaxKind.MethodDeclaration) || - !!(declaration as FunctionDeclaration).body; - } +function isNotOverload(declaration: Declaration): boolean { + return (declaration.kind !== SyntaxKind.FunctionDeclaration && declaration.kind !== SyntaxKind.MethodDeclaration) || + !!(declaration as FunctionDeclaration).body; +} - /** Like 'isDeclarationName', but returns true for LHS of `import { x as y }` or `export { x as y }`. */ - function isDeclarationNameOrImportPropertyName(name: Node): boolean { - switch (name.parent.kind) { - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - return isIdentifier(name); - default: - return isDeclarationName(name); - } +/** Like 'isDeclarationName', but returns true for LHS of `import { x as y }` or `export { x as y }`. */ +function isDeclarationNameOrImportPropertyName(name: Node): boolean { + switch (name.parent.kind) { + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + return isIdentifier(name); + default: + return isDeclarationName(name); } +} - namespace JsxNames { - export const JSX = "JSX" as __String; - export const IntrinsicElements = "IntrinsicElements" as __String; - export const ElementClass = "ElementClass" as __String; - export const ElementAttributesPropertyNameContainer = "ElementAttributesProperty" as __String; // TODO: Deprecate and remove support - export const ElementChildrenAttributeNameContainer = "ElementChildrenAttribute" as __String; - export const Element = "Element" as __String; - export const IntrinsicAttributes = "IntrinsicAttributes" as __String; - export const IntrinsicClassAttributes = "IntrinsicClassAttributes" as __String; - export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String; - } +namespace JsxNames { + export const JSX = "JSX" as __String; + export const IntrinsicElements = "IntrinsicElements" as __String; + export const ElementClass = "ElementClass" as __String; + export const ElementAttributesPropertyNameContainer = "ElementAttributesProperty" as __String; // TODO: Deprecate and remove support + export const ElementChildrenAttributeNameContainer = "ElementChildrenAttribute" as __String; + export const Element = "Element" as __String; + export const IntrinsicAttributes = "IntrinsicAttributes" as __String; + export const IntrinsicClassAttributes = "IntrinsicClassAttributes" as __String; + export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String; +} - function getIterationTypesKeyFromIterationTypeKind(typeKind: IterationTypeKind) { - switch (typeKind) { - case IterationTypeKind.Yield: return "yieldType"; - case IterationTypeKind.Return: return "returnType"; - case IterationTypeKind.Next: return "nextType"; - } +function getIterationTypesKeyFromIterationTypeKind(typeKind: IterationTypeKind) { + switch (typeKind) { + case IterationTypeKind.Yield: return "yieldType"; + case IterationTypeKind.Return: return "returnType"; + case IterationTypeKind.Next: return "nextType"; } +} - export function signatureHasRestParameter(s: Signature) { - return !!(s.flags & SignatureFlags.HasRestParameter); - } +export function signatureHasRestParameter(s: Signature) { + return !!(s.flags & SignatureFlags.HasRestParameter); +} - export function signatureHasLiteralTypes(s: Signature) { - return !!(s.flags & SignatureFlags.HasLiteralTypes); - } +export function signatureHasLiteralTypes(s: Signature) { + return !!(s.flags & SignatureFlags.HasLiteralTypes); +} } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index b9d39151148a1..2e0506d8d21d7 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1,3621 +1,3621 @@ namespace ts { - /* @internal */ - export const compileOnSaveCommandLineOption: CommandLineOption = { name: "compileOnSave", type: "boolean", defaultValueDescription: "false" }; - - const jsxOptionMap = new Map(getEntries({ - "preserve": JsxEmit.Preserve, - "react-native": JsxEmit.ReactNative, - "react": JsxEmit.React, - "react-jsx": JsxEmit.ReactJSX, - "react-jsxdev": JsxEmit.ReactJSXDev, - })); - - /* @internal */ - export const inverseJsxOptionMap = new Map(arrayFrom(mapIterator(jsxOptionMap.entries(), ([key, value]: [string, JsxEmit]) => ["" + value, key] as const))); - - // NOTE: The order here is important to default lib ordering as entries will have the same - // order in the generated program (see `getDefaultLibPriority` in program.ts). This - // order also affects overload resolution when a type declared in one lib is - // augmented in another lib. - const libEntries: [string, string][] = [ - // JavaScript only - ["es5", "lib.es5.d.ts"], - ["es6", "lib.es2015.d.ts"], - ["es2015", "lib.es2015.d.ts"], - ["es7", "lib.es2016.d.ts"], - ["es2016", "lib.es2016.d.ts"], - ["es2017", "lib.es2017.d.ts"], - ["es2018", "lib.es2018.d.ts"], - ["es2019", "lib.es2019.d.ts"], - ["es2020", "lib.es2020.d.ts"], - ["es2021", "lib.es2021.d.ts"], - ["es2022", "lib.es2022.d.ts"], - ["esnext", "lib.esnext.d.ts"], - // Host only - ["dom", "lib.dom.d.ts"], - ["dom.iterable", "lib.dom.iterable.d.ts"], - ["webworker", "lib.webworker.d.ts"], - ["webworker.importscripts", "lib.webworker.importscripts.d.ts"], - ["webworker.iterable", "lib.webworker.iterable.d.ts"], - ["scripthost", "lib.scripthost.d.ts"], - // ES2015 Or ESNext By-feature options - ["es2015.core", "lib.es2015.core.d.ts"], - ["es2015.collection", "lib.es2015.collection.d.ts"], - ["es2015.generator", "lib.es2015.generator.d.ts"], - ["es2015.iterable", "lib.es2015.iterable.d.ts"], - ["es2015.promise", "lib.es2015.promise.d.ts"], - ["es2015.proxy", "lib.es2015.proxy.d.ts"], - ["es2015.reflect", "lib.es2015.reflect.d.ts"], - ["es2015.symbol", "lib.es2015.symbol.d.ts"], - ["es2015.symbol.wellknown", "lib.es2015.symbol.wellknown.d.ts"], - ["es2016.array.include", "lib.es2016.array.include.d.ts"], - ["es2017.object", "lib.es2017.object.d.ts"], - ["es2017.sharedmemory", "lib.es2017.sharedmemory.d.ts"], - ["es2017.string", "lib.es2017.string.d.ts"], - ["es2017.intl", "lib.es2017.intl.d.ts"], - ["es2017.typedarrays", "lib.es2017.typedarrays.d.ts"], - ["es2018.asyncgenerator", "lib.es2018.asyncgenerator.d.ts"], - ["es2018.asynciterable", "lib.es2018.asynciterable.d.ts"], - ["es2018.intl", "lib.es2018.intl.d.ts"], - ["es2018.promise", "lib.es2018.promise.d.ts"], - ["es2018.regexp", "lib.es2018.regexp.d.ts"], - ["es2019.array", "lib.es2019.array.d.ts"], - ["es2019.object", "lib.es2019.object.d.ts"], - ["es2019.string", "lib.es2019.string.d.ts"], - ["es2019.symbol", "lib.es2019.symbol.d.ts"], - ["es2020.bigint", "lib.es2020.bigint.d.ts"], - ["es2020.promise", "lib.es2020.promise.d.ts"], - ["es2020.sharedmemory", "lib.es2020.sharedmemory.d.ts"], - ["es2020.string", "lib.es2020.string.d.ts"], - ["es2020.symbol.wellknown", "lib.es2020.symbol.wellknown.d.ts"], - ["es2020.intl", "lib.es2020.intl.d.ts"], - ["es2021.promise", "lib.es2021.promise.d.ts"], - ["es2021.string", "lib.es2021.string.d.ts"], - ["es2021.weakref", "lib.es2021.weakref.d.ts"], - ["es2021.intl", "lib.es2021.intl.d.ts"], - ["es2022.array", "lib.es2022.array.d.ts"], - ["es2022.error", "lib.es2022.error.d.ts"], - ["es2022.object", "lib.es2022.object.d.ts"], - ["es2022.string", "lib.es2022.string.d.ts"], - ["esnext.array", "lib.es2022.array.d.ts"], - ["esnext.symbol", "lib.es2019.symbol.d.ts"], - ["esnext.asynciterable", "lib.es2018.asynciterable.d.ts"], - ["esnext.intl", "lib.esnext.intl.d.ts"], - ["esnext.bigint", "lib.es2020.bigint.d.ts"], - ["esnext.string", "lib.es2022.string.d.ts"], - ["esnext.promise", "lib.es2021.promise.d.ts"], - ["esnext.weakref", "lib.es2021.weakref.d.ts"] - ]; - - /** - * An array of supported "lib" reference file names used to determine the order for inclusion - * when referenced, as well as for spelling suggestions. This ensures the correct ordering for - * overload resolution when a type declared in one lib is extended by another. - */ - /* @internal */ - export const libs = libEntries.map(entry => entry[0]); - - /** - * A map of lib names to lib files. This map is used both for parsing the "lib" command line - * option as well as for resolving lib reference directives. - */ - /* @internal */ - export const libMap = new Map(libEntries); - - // Watch related options - /* @internal */ - export const optionsForWatch: CommandLineOption[] = [ - { - name: "watchFile", - type: new Map(getEntries({ - fixedpollinginterval: WatchFileKind.FixedPollingInterval, - prioritypollinginterval: WatchFileKind.PriorityPollingInterval, - dynamicprioritypolling: WatchFileKind.DynamicPriorityPolling, - fixedchunksizepolling: WatchFileKind.FixedChunkSizePolling, - usefsevents: WatchFileKind.UseFsEvents, - usefseventsonparentdirectory: WatchFileKind.UseFsEventsOnParentDirectory, - })), - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Specify_how_the_TypeScript_watch_mode_works, - }, - { - name: "watchDirectory", - type: new Map(getEntries({ - usefsevents: WatchDirectoryKind.UseFsEvents, - fixedpollinginterval: WatchDirectoryKind.FixedPollingInterval, - dynamicprioritypolling: WatchDirectoryKind.DynamicPriorityPolling, - fixedchunksizepolling: WatchDirectoryKind.FixedChunkSizePolling, - })), - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Specify_how_directories_are_watched_on_systems_that_lack_recursive_file_watching_functionality, - }, - { - name: "fallbackPolling", - type: new Map(getEntries({ - fixedinterval: PollingWatchKind.FixedInterval, - priorityinterval: PollingWatchKind.PriorityInterval, - dynamicpriority: PollingWatchKind.DynamicPriority, - fixedchunksize: PollingWatchKind.FixedChunkSize, - })), - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Specify_what_approach_the_watcher_should_use_if_the_system_runs_out_of_native_file_watchers, - }, - { - name: "synchronousWatchDirectory", - type: "boolean", - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively, - defaultValueDescription: "false", - }, - { - name: "excludeDirectories", - type: "list", - element: { - name: "excludeDirectory", - type: "string", - isFilePath: true, - extraValidation: specToDiagnostic - }, - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Remove_a_list_of_directories_from_the_watch_process, - }, - { - name: "excludeFiles", - type: "list", - element: { - name: "excludeFile", - type: "string", - isFilePath: true, - extraValidation: specToDiagnostic - }, - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Remove_a_list_of_files_from_the_watch_mode_s_processing, - }, - ]; - - /* @internal */ - export const commonOptionsWithBuild: CommandLineOption[] = [ - { - name: "help", - shortName: "h", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Print_this_message, - defaultValueDescription: "false", - }, - { - name: "help", - shortName: "?", - type: "boolean", - defaultValueDescription: "false", - }, - { - name: "watch", - shortName: "w", - type: "boolean", - showInSimplifiedHelpView: true, - isCommandLineOnly: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Watch_input_files, - defaultValueDescription: "false", - }, - { - name: "preserveWatchOutput", - type: "boolean", - showInSimplifiedHelpView: false, - category: Diagnostics.Output_Formatting, - description: Diagnostics.Disable_wiping_the_console_in_watch_mode, - defaultValueDescription: "false", - }, - { - name: "listFiles", - type: "boolean", - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Print_all_of_the_files_read_during_the_compilation, - defaultValueDescription: "false" - }, - { - name: "explainFiles", - type: "boolean", - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Print_files_read_during_the_compilation_including_why_it_was_included, - defaultValueDescription: "false", - }, - { - name: "listEmittedFiles", - type: "boolean", - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Print_the_names_of_emitted_files_after_a_compilation, - defaultValueDescription: "false" - }, - { - name: "pretty", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Output_Formatting, - description: Diagnostics.Enable_color_and_formatting_in_TypeScript_s_output_to_make_compiler_errors_easier_to_read, - defaultValueDescription: "true" - }, - { - name: "traceResolution", - type: "boolean", - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Log_paths_used_during_the_moduleResolution_process, - defaultValueDescription: "false" - }, - { - name: "diagnostics", - type: "boolean", - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Output_compiler_performance_information_after_building, - defaultValueDescription: "false" - }, - { - name: "extendedDiagnostics", - type: "boolean", - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Output_more_detailed_compiler_performance_information_after_building, - defaultValueDescription: "false" - }, - { - name: "generateCpuProfile", +/* @internal */ +export const compileOnSaveCommandLineOption: CommandLineOption = { name: "compileOnSave", type: "boolean", defaultValueDescription: "false" }; + +const jsxOptionMap = new Map(getEntries({ + "preserve": JsxEmit.Preserve, + "react-native": JsxEmit.ReactNative, + "react": JsxEmit.React, + "react-jsx": JsxEmit.ReactJSX, + "react-jsxdev": JsxEmit.ReactJSXDev, +})); + +/* @internal */ +export const inverseJsxOptionMap = new Map(arrayFrom(mapIterator(jsxOptionMap.entries(), ([key, value]: [string, JsxEmit]) => ["" + value, key] as const))); + +// NOTE: The order here is important to default lib ordering as entries will have the same +// order in the generated program (see `getDefaultLibPriority` in program.ts). This +// order also affects overload resolution when a type declared in one lib is +// augmented in another lib. +const libEntries: [string, string][] = [ + // JavaScript only + ["es5", "lib.es5.d.ts"], + ["es6", "lib.es2015.d.ts"], + ["es2015", "lib.es2015.d.ts"], + ["es7", "lib.es2016.d.ts"], + ["es2016", "lib.es2016.d.ts"], + ["es2017", "lib.es2017.d.ts"], + ["es2018", "lib.es2018.d.ts"], + ["es2019", "lib.es2019.d.ts"], + ["es2020", "lib.es2020.d.ts"], + ["es2021", "lib.es2021.d.ts"], + ["es2022", "lib.es2022.d.ts"], + ["esnext", "lib.esnext.d.ts"], + // Host only + ["dom", "lib.dom.d.ts"], + ["dom.iterable", "lib.dom.iterable.d.ts"], + ["webworker", "lib.webworker.d.ts"], + ["webworker.importscripts", "lib.webworker.importscripts.d.ts"], + ["webworker.iterable", "lib.webworker.iterable.d.ts"], + ["scripthost", "lib.scripthost.d.ts"], + // ES2015 Or ESNext By-feature options + ["es2015.core", "lib.es2015.core.d.ts"], + ["es2015.collection", "lib.es2015.collection.d.ts"], + ["es2015.generator", "lib.es2015.generator.d.ts"], + ["es2015.iterable", "lib.es2015.iterable.d.ts"], + ["es2015.promise", "lib.es2015.promise.d.ts"], + ["es2015.proxy", "lib.es2015.proxy.d.ts"], + ["es2015.reflect", "lib.es2015.reflect.d.ts"], + ["es2015.symbol", "lib.es2015.symbol.d.ts"], + ["es2015.symbol.wellknown", "lib.es2015.symbol.wellknown.d.ts"], + ["es2016.array.include", "lib.es2016.array.include.d.ts"], + ["es2017.object", "lib.es2017.object.d.ts"], + ["es2017.sharedmemory", "lib.es2017.sharedmemory.d.ts"], + ["es2017.string", "lib.es2017.string.d.ts"], + ["es2017.intl", "lib.es2017.intl.d.ts"], + ["es2017.typedarrays", "lib.es2017.typedarrays.d.ts"], + ["es2018.asyncgenerator", "lib.es2018.asyncgenerator.d.ts"], + ["es2018.asynciterable", "lib.es2018.asynciterable.d.ts"], + ["es2018.intl", "lib.es2018.intl.d.ts"], + ["es2018.promise", "lib.es2018.promise.d.ts"], + ["es2018.regexp", "lib.es2018.regexp.d.ts"], + ["es2019.array", "lib.es2019.array.d.ts"], + ["es2019.object", "lib.es2019.object.d.ts"], + ["es2019.string", "lib.es2019.string.d.ts"], + ["es2019.symbol", "lib.es2019.symbol.d.ts"], + ["es2020.bigint", "lib.es2020.bigint.d.ts"], + ["es2020.promise", "lib.es2020.promise.d.ts"], + ["es2020.sharedmemory", "lib.es2020.sharedmemory.d.ts"], + ["es2020.string", "lib.es2020.string.d.ts"], + ["es2020.symbol.wellknown", "lib.es2020.symbol.wellknown.d.ts"], + ["es2020.intl", "lib.es2020.intl.d.ts"], + ["es2021.promise", "lib.es2021.promise.d.ts"], + ["es2021.string", "lib.es2021.string.d.ts"], + ["es2021.weakref", "lib.es2021.weakref.d.ts"], + ["es2021.intl", "lib.es2021.intl.d.ts"], + ["es2022.array", "lib.es2022.array.d.ts"], + ["es2022.error", "lib.es2022.error.d.ts"], + ["es2022.object", "lib.es2022.object.d.ts"], + ["es2022.string", "lib.es2022.string.d.ts"], + ["esnext.array", "lib.es2022.array.d.ts"], + ["esnext.symbol", "lib.es2019.symbol.d.ts"], + ["esnext.asynciterable", "lib.es2018.asynciterable.d.ts"], + ["esnext.intl", "lib.esnext.intl.d.ts"], + ["esnext.bigint", "lib.es2020.bigint.d.ts"], + ["esnext.string", "lib.es2022.string.d.ts"], + ["esnext.promise", "lib.es2021.promise.d.ts"], + ["esnext.weakref", "lib.es2021.weakref.d.ts"] +]; + +/** + * An array of supported "lib" reference file names used to determine the order for inclusion + * when referenced, as well as for spelling suggestions. This ensures the correct ordering for + * overload resolution when a type declared in one lib is extended by another. + */ +/* @internal */ +export const libs = libEntries.map(entry => entry[0]); + +/** + * A map of lib names to lib files. This map is used both for parsing the "lib" command line + * option as well as for resolving lib reference directives. + */ +/* @internal */ +export const libMap = new Map(libEntries); + +// Watch related options +/* @internal */ +export const optionsForWatch: CommandLineOption[] = [ + { + name: "watchFile", + type: new Map(getEntries({ + fixedpollinginterval: WatchFileKind.FixedPollingInterval, + prioritypollinginterval: WatchFileKind.PriorityPollingInterval, + dynamicprioritypolling: WatchFileKind.DynamicPriorityPolling, + fixedchunksizepolling: WatchFileKind.FixedChunkSizePolling, + usefsevents: WatchFileKind.UseFsEvents, + usefseventsonparentdirectory: WatchFileKind.UseFsEventsOnParentDirectory, + })), + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Specify_how_the_TypeScript_watch_mode_works, + }, + { + name: "watchDirectory", + type: new Map(getEntries({ + usefsevents: WatchDirectoryKind.UseFsEvents, + fixedpollinginterval: WatchDirectoryKind.FixedPollingInterval, + dynamicprioritypolling: WatchDirectoryKind.DynamicPriorityPolling, + fixedchunksizepolling: WatchDirectoryKind.FixedChunkSizePolling, + })), + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Specify_how_directories_are_watched_on_systems_that_lack_recursive_file_watching_functionality, + }, + { + name: "fallbackPolling", + type: new Map(getEntries({ + fixedinterval: PollingWatchKind.FixedInterval, + priorityinterval: PollingWatchKind.PriorityInterval, + dynamicpriority: PollingWatchKind.DynamicPriority, + fixedchunksize: PollingWatchKind.FixedChunkSize, + })), + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Specify_what_approach_the_watcher_should_use_if_the_system_runs_out_of_native_file_watchers, + }, + { + name: "synchronousWatchDirectory", + type: "boolean", + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively, + defaultValueDescription: "false", + }, + { + name: "excludeDirectories", + type: "list", + element: { + name: "excludeDirectory", type: "string", isFilePath: true, - paramType: Diagnostics.FILE_OR_DIRECTORY, - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Emit_a_v8_CPU_profile_of_the_compiler_run_for_debugging, - defaultValueDescription: "profile.cpuprofile" - }, - { - name: "generateTrace", + extraValidation: specToDiagnostic + }, + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Remove_a_list_of_directories_from_the_watch_process, + }, + { + name: "excludeFiles", + type: "list", + element: { + name: "excludeFile", type: "string", isFilePath: true, - isCommandLineOnly: true, - paramType: Diagnostics.DIRECTORY, - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Generates_an_event_trace_and_a_list_of_types - }, - { - name: "incremental", - shortName: "i", - type: "boolean", - category: Diagnostics.Projects, - description: Diagnostics.Enable_incremental_compilation, - transpileOptionValue: undefined, - defaultValueDescription: Diagnostics.false_unless_composite_is_set - }, - { - name: "assumeChangesOnlyAffectDirectDependencies", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsEmit: true, - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Have_recompiles_in_projects_that_use_incremental_and_watch_mode_assume_that_changes_within_a_file_will_only_affect_files_directly_depending_on_it, - defaultValueDescription: "false", - }, - { - name: "locale", - type: "string", - category: Diagnostics.Command_line_Options, - isCommandLineOnly: true, - description: Diagnostics.Set_the_language_of_the_messaging_from_TypeScript_This_does_not_affect_emit, - defaultValueDescription: Diagnostics.Platform_specific - }, - ]; - - /* @internal */ - export const targetOptionDeclaration: CommandLineOptionOfCustomType = { - name: "target", - shortName: "t", + extraValidation: specToDiagnostic + }, + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Remove_a_list_of_files_from_the_watch_mode_s_processing, + }, +]; + +/* @internal */ +export const commonOptionsWithBuild: CommandLineOption[] = [ + { + name: "help", + shortName: "h", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Print_this_message, + defaultValueDescription: "false", + }, + { + name: "help", + shortName: "?", + type: "boolean", + defaultValueDescription: "false", + }, + { + name: "watch", + shortName: "w", + type: "boolean", + showInSimplifiedHelpView: true, + isCommandLineOnly: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Watch_input_files, + defaultValueDescription: "false", + }, + { + name: "preserveWatchOutput", + type: "boolean", + showInSimplifiedHelpView: false, + category: Diagnostics.Output_Formatting, + description: Diagnostics.Disable_wiping_the_console_in_watch_mode, + defaultValueDescription: "false", + }, + { + name: "listFiles", + type: "boolean", + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Print_all_of_the_files_read_during_the_compilation, + defaultValueDescription: "false" + }, + { + name: "explainFiles", + type: "boolean", + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Print_files_read_during_the_compilation_including_why_it_was_included, + defaultValueDescription: "false", + }, + { + name: "listEmittedFiles", + type: "boolean", + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Print_the_names_of_emitted_files_after_a_compilation, + defaultValueDescription: "false" + }, + { + name: "pretty", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Output_Formatting, + description: Diagnostics.Enable_color_and_formatting_in_TypeScript_s_output_to_make_compiler_errors_easier_to_read, + defaultValueDescription: "true" + }, + { + name: "traceResolution", + type: "boolean", + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Log_paths_used_during_the_moduleResolution_process, + defaultValueDescription: "false" + }, + { + name: "diagnostics", + type: "boolean", + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Output_compiler_performance_information_after_building, + defaultValueDescription: "false" + }, + { + name: "extendedDiagnostics", + type: "boolean", + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Output_more_detailed_compiler_performance_information_after_building, + defaultValueDescription: "false" + }, + { + name: "generateCpuProfile", + type: "string", + isFilePath: true, + paramType: Diagnostics.FILE_OR_DIRECTORY, + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Emit_a_v8_CPU_profile_of_the_compiler_run_for_debugging, + defaultValueDescription: "profile.cpuprofile" + }, + { + name: "generateTrace", + type: "string", + isFilePath: true, + isCommandLineOnly: true, + paramType: Diagnostics.DIRECTORY, + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Generates_an_event_trace_and_a_list_of_types + }, + { + name: "incremental", + shortName: "i", + type: "boolean", + category: Diagnostics.Projects, + description: Diagnostics.Enable_incremental_compilation, + transpileOptionValue: undefined, + defaultValueDescription: Diagnostics.false_unless_composite_is_set + }, + { + name: "assumeChangesOnlyAffectDirectDependencies", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsEmit: true, + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Have_recompiles_in_projects_that_use_incremental_and_watch_mode_assume_that_changes_within_a_file_will_only_affect_files_directly_depending_on_it, + defaultValueDescription: "false", + }, + { + name: "locale", + type: "string", + category: Diagnostics.Command_line_Options, + isCommandLineOnly: true, + description: Diagnostics.Set_the_language_of_the_messaging_from_TypeScript_This_does_not_affect_emit, + defaultValueDescription: Diagnostics.Platform_specific + }, +]; + +/* @internal */ +export const targetOptionDeclaration: CommandLineOptionOfCustomType = { + name: "target", + shortName: "t", + type: new Map(getEntries({ + es3: ScriptTarget.ES3, + es5: ScriptTarget.ES5, + es6: ScriptTarget.ES2015, + es2015: ScriptTarget.ES2015, + es2016: ScriptTarget.ES2016, + es2017: ScriptTarget.ES2017, + es2018: ScriptTarget.ES2018, + es2019: ScriptTarget.ES2019, + es2020: ScriptTarget.ES2020, + es2021: ScriptTarget.ES2021, + es2022: ScriptTarget.ES2022, + esnext: ScriptTarget.ESNext, + })), + affectsSourceFile: true, + affectsModuleResolution: true, + affectsEmit: true, + paramType: Diagnostics.VERSION, + showInSimplifiedHelpView: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Set_the_JavaScript_language_version_for_emitted_JavaScript_and_include_compatible_library_declarations, + defaultValueDescription: "ES3" +}; + +const commandOptionsWithoutBuild: CommandLineOption[] = [ + // CommandLine only options + { + name: "all", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Show_all_compiler_options, + defaultValueDescription: "false", + }, + { + name: "version", + shortName: "v", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Print_the_compiler_s_version, + defaultValueDescription: "false", + }, + { + name: "init", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Initializes_a_TypeScript_project_and_creates_a_tsconfig_json_file, + defaultValueDescription: "false", + }, + { + name: "project", + shortName: "p", + type: "string", + isFilePath: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + paramType: Diagnostics.FILE_OR_DIRECTORY, + description: Diagnostics.Compile_the_project_given_the_path_to_its_configuration_file_or_to_a_folder_with_a_tsconfig_json, + }, + { + name: "build", + type: "boolean", + shortName: "b", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Build_one_or_more_projects_and_their_dependencies_if_out_of_date, + defaultValueDescription: "false", + }, + { + name: "showConfig", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + isCommandLineOnly: true, + description: Diagnostics.Print_the_final_configuration_instead_of_building, + defaultValueDescription: "false", + }, + { + name: "listFilesOnly", + type: "boolean", + category: Diagnostics.Command_line_Options, + affectsSemanticDiagnostics: true, + affectsEmit: true, + isCommandLineOnly: true, + description: Diagnostics.Print_names_of_files_that_are_part_of_the_compilation_and_then_stop_processing, + defaultValueDescription: "false", + }, + + // Basic + targetOptionDeclaration, + { + name: "module", + shortName: "m", type: new Map(getEntries({ - es3: ScriptTarget.ES3, - es5: ScriptTarget.ES5, - es6: ScriptTarget.ES2015, - es2015: ScriptTarget.ES2015, - es2016: ScriptTarget.ES2016, - es2017: ScriptTarget.ES2017, - es2018: ScriptTarget.ES2018, - es2019: ScriptTarget.ES2019, - es2020: ScriptTarget.ES2020, - es2021: ScriptTarget.ES2021, - es2022: ScriptTarget.ES2022, - esnext: ScriptTarget.ESNext, + none: ModuleKind.None, + commonjs: ModuleKind.CommonJS, + amd: ModuleKind.AMD, + system: ModuleKind.System, + umd: ModuleKind.UMD, + es6: ModuleKind.ES2015, + es2015: ModuleKind.ES2015, + es2020: ModuleKind.ES2020, + es2022: ModuleKind.ES2022, + esnext: ModuleKind.ESNext, + node12: ModuleKind.Node12, + nodenext: ModuleKind.NodeNext, })), - affectsSourceFile: true, affectsModuleResolution: true, affectsEmit: true, - paramType: Diagnostics.VERSION, + paramType: Diagnostics.KIND, showInSimplifiedHelpView: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Set_the_JavaScript_language_version_for_emitted_JavaScript_and_include_compatible_library_declarations, - defaultValueDescription: "ES3" - }; - - const commandOptionsWithoutBuild: CommandLineOption[] = [ - // CommandLine only options - { - name: "all", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Show_all_compiler_options, - defaultValueDescription: "false", - }, - { - name: "version", - shortName: "v", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Print_the_compiler_s_version, - defaultValueDescription: "false", - }, - { - name: "init", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Initializes_a_TypeScript_project_and_creates_a_tsconfig_json_file, - defaultValueDescription: "false", - }, - { - name: "project", - shortName: "p", - type: "string", - isFilePath: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - paramType: Diagnostics.FILE_OR_DIRECTORY, - description: Diagnostics.Compile_the_project_given_the_path_to_its_configuration_file_or_to_a_folder_with_a_tsconfig_json, - }, - { - name: "build", - type: "boolean", - shortName: "b", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Build_one_or_more_projects_and_their_dependencies_if_out_of_date, - defaultValueDescription: "false", - }, - { - name: "showConfig", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - isCommandLineOnly: true, - description: Diagnostics.Print_the_final_configuration_instead_of_building, - defaultValueDescription: "false", - }, - { - name: "listFilesOnly", - type: "boolean", - category: Diagnostics.Command_line_Options, - affectsSemanticDiagnostics: true, - affectsEmit: true, - isCommandLineOnly: true, - description: Diagnostics.Print_names_of_files_that_are_part_of_the_compilation_and_then_stop_processing, - defaultValueDescription: "false", - }, - - // Basic - targetOptionDeclaration, - { - name: "module", - shortName: "m", - type: new Map(getEntries({ - none: ModuleKind.None, - commonjs: ModuleKind.CommonJS, - amd: ModuleKind.AMD, - system: ModuleKind.System, - umd: ModuleKind.UMD, - es6: ModuleKind.ES2015, - es2015: ModuleKind.ES2015, - es2020: ModuleKind.ES2020, - es2022: ModuleKind.ES2022, - esnext: ModuleKind.ESNext, - node12: ModuleKind.Node12, - nodenext: ModuleKind.NodeNext, - })), - affectsModuleResolution: true, - affectsEmit: true, - paramType: Diagnostics.KIND, - showInSimplifiedHelpView: true, - category: Diagnostics.Modules, - description: Diagnostics.Specify_what_module_code_is_generated, - }, - { + category: Diagnostics.Modules, + description: Diagnostics.Specify_what_module_code_is_generated, + }, + { + name: "lib", + type: "list", + element: { name: "lib", - type: "list", - element: { - name: "lib", - type: libMap - }, - affectsProgramStructure: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Specify_a_set_of_bundled_library_declaration_files_that_describe_the_target_runtime_environment, - transpileOptionValue: undefined - }, - { - name: "allowJs", - type: "boolean", - affectsModuleResolution: true, - showInSimplifiedHelpView: true, - category: Diagnostics.JavaScript_Support, - description: Diagnostics.Allow_JavaScript_files_to_be_a_part_of_your_program_Use_the_checkJS_option_to_get_errors_from_these_files, - defaultValueDescription: "false" - }, - { - name: "checkJs", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.JavaScript_Support, - description: Diagnostics.Enable_error_reporting_in_type_checked_JavaScript_files, - defaultValueDescription: "false" - }, - { - name: "jsx", - type: jsxOptionMap, - affectsSourceFile: true, - affectsEmit: true, - affectsModuleResolution: true, - paramType: Diagnostics.KIND, - showInSimplifiedHelpView: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Specify_what_JSX_code_is_generated, - defaultValueDescription: "undefined" - }, - { - name: "declaration", - shortName: "d", - type: "boolean", - affectsEmit: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - transpileOptionValue: undefined, - description: Diagnostics.Generate_d_ts_files_from_TypeScript_and_JavaScript_files_in_your_project, - defaultValueDescription: Diagnostics.false_unless_composite_is_set, - }, - { - name: "declarationMap", - type: "boolean", - affectsEmit: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - transpileOptionValue: undefined, - defaultValueDescription: "false", - description: Diagnostics.Create_sourcemaps_for_d_ts_files - }, - { - name: "emitDeclarationOnly", - type: "boolean", - affectsEmit: true, - showInSimplifiedHelpView: true, - - category: Diagnostics.Emit, - description: Diagnostics.Only_output_d_ts_files_and_not_JavaScript_files, - transpileOptionValue: undefined, - defaultValueDescription: "false", - }, - { - name: "sourceMap", - type: "boolean", - affectsEmit: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - defaultValueDescription: "false", - description: Diagnostics.Create_source_map_files_for_emitted_JavaScript_files, - }, - { - name: "outFile", - type: "string", - affectsEmit: true, - isFilePath: true, - paramType: Diagnostics.FILE, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - description: Diagnostics.Specify_a_file_that_bundles_all_outputs_into_one_JavaScript_file_If_declaration_is_true_also_designates_a_file_that_bundles_all_d_ts_output, - transpileOptionValue: undefined, - }, - { - name: "outDir", - type: "string", - affectsEmit: true, - isFilePath: true, - paramType: Diagnostics.DIRECTORY, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - description: Diagnostics.Specify_an_output_folder_for_all_emitted_files, - }, - { - name: "rootDir", - type: "string", - affectsEmit: true, - isFilePath: true, - paramType: Diagnostics.LOCATION, - category: Diagnostics.Modules, - description: Diagnostics.Specify_the_root_folder_within_your_source_files, - defaultValueDescription: Diagnostics.Computed_from_the_list_of_input_files - }, - { - name: "composite", - type: "boolean", - affectsEmit: true, - isTSConfigOnly: true, - category: Diagnostics.Projects, - transpileOptionValue: undefined, - defaultValueDescription: "false", - description: Diagnostics.Enable_constraints_that_allow_a_TypeScript_project_to_be_used_with_project_references, - }, - { - name: "tsBuildInfoFile", - type: "string", - affectsEmit: true, - isFilePath: true, - paramType: Diagnostics.FILE, - category: Diagnostics.Projects, - transpileOptionValue: undefined, - defaultValueDescription: ".tsbuildinfo", - description: Diagnostics.Specify_the_folder_for_tsbuildinfo_incremental_compilation_files, - }, - { - name: "removeComments", - type: "boolean", - affectsEmit: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - defaultValueDescription: "false", - description: Diagnostics.Disable_emitting_comments, - }, - { - name: "noEmit", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - description: Diagnostics.Disable_emitting_files_from_a_compilation, - transpileOptionValue: undefined, - defaultValueDescription: "false" - }, - { - name: "importHelpers", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Emit, - description: Diagnostics.Allow_importing_helper_functions_from_tslib_once_per_project_instead_of_including_them_per_file, - defaultValueDescription: "false" - }, - { - name: "importsNotUsedAsValues", - type: new Map(getEntries({ - remove: ImportsNotUsedAsValues.Remove, - preserve: ImportsNotUsedAsValues.Preserve, - error: ImportsNotUsedAsValues.Error, - })), - affectsEmit: true, - affectsSemanticDiagnostics: true, - category: Diagnostics.Emit, - description: Diagnostics.Specify_emit_Slashchecking_behavior_for_imports_that_are_only_used_for_types - }, - { - name: "downlevelIteration", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Emit, - description: Diagnostics.Emit_more_compliant_but_verbose_and_less_performant_JavaScript_for_iteration, - defaultValueDescription: "false" - }, - { - name: "isolatedModules", - type: "boolean", - category: Diagnostics.Interop_Constraints, - description: Diagnostics.Ensure_that_each_file_can_be_safely_transpiled_without_relying_on_other_imports, - transpileOptionValue: true, - defaultValueDescription: "false" - }, - - // Strict Type Checks - { - name: "strict", - type: "boolean", - // Though this affects semantic diagnostics, affectsSemanticDiagnostics is not set here - // The value of each strictFlag depends on own strictFlag value or this and never accessed directly. - showInSimplifiedHelpView: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_all_strict_type_checking_options, - defaultValueDescription: "false" - }, - { - name: "noImplicitAny", - type: "boolean", - affectsSemanticDiagnostics: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_error_reporting_for_expressions_and_declarations_with_an_implied_any_type, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - { - name: "strictNullChecks", - type: "boolean", - affectsSemanticDiagnostics: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.When_type_checking_take_into_account_null_and_undefined, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - { - name: "strictFunctionTypes", - type: "boolean", - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.When_assigning_functions_check_to_ensure_parameters_and_the_return_values_are_subtype_compatible, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - { - name: "strictBindCallApply", - type: "boolean", - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Check_that_the_arguments_for_bind_call_and_apply_methods_match_the_original_function, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - { - name: "strictPropertyInitialization", - type: "boolean", - affectsSemanticDiagnostics: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Check_for_class_properties_that_are_declared_but_not_set_in_the_constructor, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - { - name: "noImplicitThis", - type: "boolean", - affectsSemanticDiagnostics: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_error_reporting_when_this_is_given_the_type_any, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - { - name: "useUnknownInCatchVariables", - type: "boolean", - affectsSemanticDiagnostics: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Type_catch_clause_variables_as_unknown_instead_of_any, - defaultValueDescription: "false", - }, - { - name: "alwaysStrict", - type: "boolean", - affectsSourceFile: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Ensure_use_strict_is_always_emitted, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - - // Additional Checks - { - name: "noUnusedLocals", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_error_reporting_when_a_local_variables_aren_t_read, - defaultValueDescription: "false" - }, - { - name: "noUnusedParameters", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Raise_an_error_when_a_function_parameter_isn_t_read, - defaultValueDescription: "false" - }, - { - name: "exactOptionalPropertyTypes", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Interpret_optional_property_types_as_written_rather_than_adding_undefined, - defaultValueDescription: "false", - }, - { - name: "noImplicitReturns", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_error_reporting_for_codepaths_that_do_not_explicitly_return_in_a_function, - defaultValueDescription: "false" - }, - { - name: "noFallthroughCasesInSwitch", - type: "boolean", - affectsBindDiagnostics: true, - affectsSemanticDiagnostics: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_error_reporting_for_fallthrough_cases_in_switch_statements, - defaultValueDescription: "false", - }, - { - name: "noUncheckedIndexedAccess", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Include_undefined_in_index_signature_results, - defaultValueDescription: "false", - }, - { - name: "noImplicitOverride", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Ensure_overriding_members_in_derived_classes_are_marked_with_an_override_modifier, - defaultValueDescription: "false", - }, - { - name: "noPropertyAccessFromIndexSignature", - type: "boolean", - showInSimplifiedHelpView: false, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enforces_using_indexed_accessors_for_keys_declared_using_an_indexed_type, - defaultValueDescription: "false" + type: libMap }, + affectsProgramStructure: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Specify_a_set_of_bundled_library_declaration_files_that_describe_the_target_runtime_environment, + transpileOptionValue: undefined + }, + { + name: "allowJs", + type: "boolean", + affectsModuleResolution: true, + showInSimplifiedHelpView: true, + category: Diagnostics.JavaScript_Support, + description: Diagnostics.Allow_JavaScript_files_to_be_a_part_of_your_program_Use_the_checkJS_option_to_get_errors_from_these_files, + defaultValueDescription: "false" + }, + { + name: "checkJs", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.JavaScript_Support, + description: Diagnostics.Enable_error_reporting_in_type_checked_JavaScript_files, + defaultValueDescription: "false" + }, + { + name: "jsx", + type: jsxOptionMap, + affectsSourceFile: true, + affectsEmit: true, + affectsModuleResolution: true, + paramType: Diagnostics.KIND, + showInSimplifiedHelpView: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Specify_what_JSX_code_is_generated, + defaultValueDescription: "undefined" + }, + { + name: "declaration", + shortName: "d", + type: "boolean", + affectsEmit: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + transpileOptionValue: undefined, + description: Diagnostics.Generate_d_ts_files_from_TypeScript_and_JavaScript_files_in_your_project, + defaultValueDescription: Diagnostics.false_unless_composite_is_set, + }, + { + name: "declarationMap", + type: "boolean", + affectsEmit: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + transpileOptionValue: undefined, + defaultValueDescription: "false", + description: Diagnostics.Create_sourcemaps_for_d_ts_files + }, + { + name: "emitDeclarationOnly", + type: "boolean", + affectsEmit: true, + showInSimplifiedHelpView: true, - // Module Resolution - { - name: "moduleResolution", - type: new Map(getEntries({ - node: ModuleResolutionKind.NodeJs, - classic: ModuleResolutionKind.Classic, - node12: ModuleResolutionKind.Node12, - nodenext: ModuleResolutionKind.NodeNext, - })), - affectsModuleResolution: true, - paramType: Diagnostics.STRATEGY, - category: Diagnostics.Modules, - description: Diagnostics.Specify_how_TypeScript_looks_up_a_file_from_a_given_module_specifier, - defaultValueDescription: Diagnostics.module_AMD_or_UMD_or_System_or_ES6_then_Classic_Otherwise_Node - }, - { - name: "baseUrl", - type: "string", - affectsModuleResolution: true, - isFilePath: true, - category: Diagnostics.Modules, - description: Diagnostics.Specify_the_base_directory_to_resolve_non_relative_module_names - }, - { - // this option can only be specified in tsconfig.json - // use type = object to copy the value as-is - name: "paths", - type: "object", - affectsModuleResolution: true, - isTSConfigOnly: true, - category: Diagnostics.Modules, - description: Diagnostics.Specify_a_set_of_entries_that_re_map_imports_to_additional_lookup_locations, - transpileOptionValue: undefined - }, - { - // this option can only be specified in tsconfig.json - // use type = object to copy the value as-is + category: Diagnostics.Emit, + description: Diagnostics.Only_output_d_ts_files_and_not_JavaScript_files, + transpileOptionValue: undefined, + defaultValueDescription: "false", + }, + { + name: "sourceMap", + type: "boolean", + affectsEmit: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + defaultValueDescription: "false", + description: Diagnostics.Create_source_map_files_for_emitted_JavaScript_files, + }, + { + name: "outFile", + type: "string", + affectsEmit: true, + isFilePath: true, + paramType: Diagnostics.FILE, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + description: Diagnostics.Specify_a_file_that_bundles_all_outputs_into_one_JavaScript_file_If_declaration_is_true_also_designates_a_file_that_bundles_all_d_ts_output, + transpileOptionValue: undefined, + }, + { + name: "outDir", + type: "string", + affectsEmit: true, + isFilePath: true, + paramType: Diagnostics.DIRECTORY, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + description: Diagnostics.Specify_an_output_folder_for_all_emitted_files, + }, + { + name: "rootDir", + type: "string", + affectsEmit: true, + isFilePath: true, + paramType: Diagnostics.LOCATION, + category: Diagnostics.Modules, + description: Diagnostics.Specify_the_root_folder_within_your_source_files, + defaultValueDescription: Diagnostics.Computed_from_the_list_of_input_files + }, + { + name: "composite", + type: "boolean", + affectsEmit: true, + isTSConfigOnly: true, + category: Diagnostics.Projects, + transpileOptionValue: undefined, + defaultValueDescription: "false", + description: Diagnostics.Enable_constraints_that_allow_a_TypeScript_project_to_be_used_with_project_references, + }, + { + name: "tsBuildInfoFile", + type: "string", + affectsEmit: true, + isFilePath: true, + paramType: Diagnostics.FILE, + category: Diagnostics.Projects, + transpileOptionValue: undefined, + defaultValueDescription: ".tsbuildinfo", + description: Diagnostics.Specify_the_folder_for_tsbuildinfo_incremental_compilation_files, + }, + { + name: "removeComments", + type: "boolean", + affectsEmit: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + defaultValueDescription: "false", + description: Diagnostics.Disable_emitting_comments, + }, + { + name: "noEmit", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + description: Diagnostics.Disable_emitting_files_from_a_compilation, + transpileOptionValue: undefined, + defaultValueDescription: "false" + }, + { + name: "importHelpers", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Emit, + description: Diagnostics.Allow_importing_helper_functions_from_tslib_once_per_project_instead_of_including_them_per_file, + defaultValueDescription: "false" + }, + { + name: "importsNotUsedAsValues", + type: new Map(getEntries({ + remove: ImportsNotUsedAsValues.Remove, + preserve: ImportsNotUsedAsValues.Preserve, + error: ImportsNotUsedAsValues.Error, + })), + affectsEmit: true, + affectsSemanticDiagnostics: true, + category: Diagnostics.Emit, + description: Diagnostics.Specify_emit_Slashchecking_behavior_for_imports_that_are_only_used_for_types + }, + { + name: "downlevelIteration", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Emit, + description: Diagnostics.Emit_more_compliant_but_verbose_and_less_performant_JavaScript_for_iteration, + defaultValueDescription: "false" + }, + { + name: "isolatedModules", + type: "boolean", + category: Diagnostics.Interop_Constraints, + description: Diagnostics.Ensure_that_each_file_can_be_safely_transpiled_without_relying_on_other_imports, + transpileOptionValue: true, + defaultValueDescription: "false" + }, + + // Strict Type Checks + { + name: "strict", + type: "boolean", + // Though this affects semantic diagnostics, affectsSemanticDiagnostics is not set here + // The value of each strictFlag depends on own strictFlag value or this and never accessed directly. + showInSimplifiedHelpView: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enable_all_strict_type_checking_options, + defaultValueDescription: "false" + }, + { + name: "noImplicitAny", + type: "boolean", + affectsSemanticDiagnostics: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enable_error_reporting_for_expressions_and_declarations_with_an_implied_any_type, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + { + name: "strictNullChecks", + type: "boolean", + affectsSemanticDiagnostics: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.When_type_checking_take_into_account_null_and_undefined, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + { + name: "strictFunctionTypes", + type: "boolean", + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.When_assigning_functions_check_to_ensure_parameters_and_the_return_values_are_subtype_compatible, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + { + name: "strictBindCallApply", + type: "boolean", + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Check_that_the_arguments_for_bind_call_and_apply_methods_match_the_original_function, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + { + name: "strictPropertyInitialization", + type: "boolean", + affectsSemanticDiagnostics: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Check_for_class_properties_that_are_declared_but_not_set_in_the_constructor, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + { + name: "noImplicitThis", + type: "boolean", + affectsSemanticDiagnostics: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enable_error_reporting_when_this_is_given_the_type_any, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + { + name: "useUnknownInCatchVariables", + type: "boolean", + affectsSemanticDiagnostics: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Type_catch_clause_variables_as_unknown_instead_of_any, + defaultValueDescription: "false", + }, + { + name: "alwaysStrict", + type: "boolean", + affectsSourceFile: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Ensure_use_strict_is_always_emitted, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + + // Additional Checks + { + name: "noUnusedLocals", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enable_error_reporting_when_a_local_variables_aren_t_read, + defaultValueDescription: "false" + }, + { + name: "noUnusedParameters", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Raise_an_error_when_a_function_parameter_isn_t_read, + defaultValueDescription: "false" + }, + { + name: "exactOptionalPropertyTypes", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Interpret_optional_property_types_as_written_rather_than_adding_undefined, + defaultValueDescription: "false", + }, + { + name: "noImplicitReturns", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enable_error_reporting_for_codepaths_that_do_not_explicitly_return_in_a_function, + defaultValueDescription: "false" + }, + { + name: "noFallthroughCasesInSwitch", + type: "boolean", + affectsBindDiagnostics: true, + affectsSemanticDiagnostics: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enable_error_reporting_for_fallthrough_cases_in_switch_statements, + defaultValueDescription: "false", + }, + { + name: "noUncheckedIndexedAccess", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Include_undefined_in_index_signature_results, + defaultValueDescription: "false", + }, + { + name: "noImplicitOverride", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Ensure_overriding_members_in_derived_classes_are_marked_with_an_override_modifier, + defaultValueDescription: "false", + }, + { + name: "noPropertyAccessFromIndexSignature", + type: "boolean", + showInSimplifiedHelpView: false, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enforces_using_indexed_accessors_for_keys_declared_using_an_indexed_type, + defaultValueDescription: "false" + }, + + // Module Resolution + { + name: "moduleResolution", + type: new Map(getEntries({ + node: ModuleResolutionKind.NodeJs, + classic: ModuleResolutionKind.Classic, + node12: ModuleResolutionKind.Node12, + nodenext: ModuleResolutionKind.NodeNext, + })), + affectsModuleResolution: true, + paramType: Diagnostics.STRATEGY, + category: Diagnostics.Modules, + description: Diagnostics.Specify_how_TypeScript_looks_up_a_file_from_a_given_module_specifier, + defaultValueDescription: Diagnostics.module_AMD_or_UMD_or_System_or_ES6_then_Classic_Otherwise_Node + }, + { + name: "baseUrl", + type: "string", + affectsModuleResolution: true, + isFilePath: true, + category: Diagnostics.Modules, + description: Diagnostics.Specify_the_base_directory_to_resolve_non_relative_module_names + }, + { + // this option can only be specified in tsconfig.json + // use type = object to copy the value as-is + name: "paths", + type: "object", + affectsModuleResolution: true, + isTSConfigOnly: true, + category: Diagnostics.Modules, + description: Diagnostics.Specify_a_set_of_entries_that_re_map_imports_to_additional_lookup_locations, + transpileOptionValue: undefined + }, + { + // this option can only be specified in tsconfig.json + // use type = object to copy the value as-is + name: "rootDirs", + type: "list", + isTSConfigOnly: true, + element: { name: "rootDirs", - type: "list", - isTSConfigOnly: true, - element: { - name: "rootDirs", - type: "string", - isFilePath: true - }, - affectsModuleResolution: true, - category: Diagnostics.Modules, - description: Diagnostics.Allow_multiple_folders_to_be_treated_as_one_when_resolving_modules, - transpileOptionValue: undefined, - defaultValueDescription: Diagnostics.Computed_from_the_list_of_input_files - }, - { - name: "typeRoots", - type: "list", - element: { - name: "typeRoots", - type: "string", - isFilePath: true - }, - affectsModuleResolution: true, - category: Diagnostics.Modules, - description: Diagnostics.Specify_multiple_folders_that_act_like_Slashnode_modules_Slash_types - }, - { - name: "types", - type: "list", - element: { - name: "types", - type: "string" - }, - affectsProgramStructure: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Modules, - description: Diagnostics.Specify_type_package_names_to_be_included_without_being_referenced_in_a_source_file, - transpileOptionValue: undefined - }, - { - name: "allowSyntheticDefaultImports", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Interop_Constraints, - description: Diagnostics.Allow_import_x_from_y_when_a_module_doesn_t_have_a_default_export, - defaultValueDescription: Diagnostics.module_system_or_esModuleInterop - }, - { - name: "esModuleInterop", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsEmit: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Interop_Constraints, - description: Diagnostics.Emit_additional_JavaScript_to_ease_support_for_importing_CommonJS_modules_This_enables_allowSyntheticDefaultImports_for_type_compatibility, - defaultValueDescription: "false" - }, - { - name: "preserveSymlinks", - type: "boolean", - category: Diagnostics.Interop_Constraints, - description: Diagnostics.Disable_resolving_symlinks_to_their_realpath_This_correlates_to_the_same_flag_in_node, - defaultValueDescription: "false", - }, - { - name: "allowUmdGlobalAccess", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Modules, - description: Diagnostics.Allow_accessing_UMD_globals_from_modules, - defaultValueDescription: "false" - }, - - // Source Maps - { - name: "sourceRoot", - type: "string", - affectsEmit: true, - paramType: Diagnostics.LOCATION, - category: Diagnostics.Emit, - description: Diagnostics.Specify_the_root_path_for_debuggers_to_find_the_reference_source_code, - }, - { - name: "mapRoot", - type: "string", - affectsEmit: true, - paramType: Diagnostics.LOCATION, - category: Diagnostics.Emit, - description: Diagnostics.Specify_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations, - }, - { - name: "inlineSourceMap", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Emit, - description: Diagnostics.Include_sourcemap_files_inside_the_emitted_JavaScript, - defaultValueDescription: "false" - }, - { - name: "inlineSources", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Emit, - description: Diagnostics.Include_source_code_in_the_sourcemaps_inside_the_emitted_JavaScript, - defaultValueDescription: "false" - }, - - // Experimental - { - name: "experimentalDecorators", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Enable_experimental_support_for_TC39_stage_2_draft_decorators, - defaultValueDescription: "false", - }, - { - name: "emitDecoratorMetadata", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsEmit: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Emit_design_type_metadata_for_decorated_declarations_in_source_files, - defaultValueDescription: "false", - }, - - // Advanced - { - name: "jsxFactory", - type: "string", - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Specify_the_JSX_factory_function_used_when_targeting_React_JSX_emit_e_g_React_createElement_or_h, - defaultValueDescription: "`React.createElement`" - }, - { - name: "jsxFragmentFactory", type: "string", - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Specify_the_JSX_Fragment_reference_used_for_fragments_when_targeting_React_JSX_emit_e_g_React_Fragment_or_Fragment + isFilePath: true }, - { - name: "jsxImportSource", - type: "string", - affectsSemanticDiagnostics: true, - affectsEmit: true, - affectsModuleResolution: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Specify_module_specifier_used_to_import_the_JSX_factory_functions_when_using_jsx_Colon_react_jsx_Asterisk, - defaultValueDescription: "react" - }, - { - name: "resolveJsonModule", - type: "boolean", - affectsModuleResolution: true, - category: Diagnostics.Modules, - description: Diagnostics.Enable_importing_json_files, - defaultValueDescription: "false" - }, - - { - name: "out", - type: "string", - affectsEmit: true, - isFilePath: false, // This is intentionally broken to support compatability with existing tsconfig files - // for correct behaviour, please use outFile - category: Diagnostics.Backwards_Compatibility, - paramType: Diagnostics.FILE, - transpileOptionValue: undefined, - description: Diagnostics.Deprecated_setting_Use_outFile_instead, - }, - { - name: "reactNamespace", - type: "string", - affectsEmit: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Specify_the_object_invoked_for_createElement_This_only_applies_when_targeting_react_JSX_emit, - defaultValueDescription: "`React`", - }, - { - name: "skipDefaultLibCheck", - type: "boolean", - category: Diagnostics.Completeness, - description: Diagnostics.Skip_type_checking_d_ts_files_that_are_included_with_TypeScript, - defaultValueDescription: "false", - }, - { - name: "charset", - type: "string", - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.No_longer_supported_In_early_versions_manually_set_the_text_encoding_for_reading_files, - defaultValueDescription: "utf8" - }, - { - name: "emitBOM", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Emit, - description: Diagnostics.Emit_a_UTF_8_Byte_Order_Mark_BOM_in_the_beginning_of_output_files, - defaultValueDescription: "false" - }, - { - name: "newLine", - type: new Map(getEntries({ - crlf: NewLineKind.CarriageReturnLineFeed, - lf: NewLineKind.LineFeed - })), - affectsEmit: true, - paramType: Diagnostics.NEWLINE, - category: Diagnostics.Emit, - description: Diagnostics.Set_the_newline_character_for_emitting_files, - defaultValueDescription: Diagnostics.Platform_specific - }, - { - name: "noErrorTruncation", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Output_Formatting, - description: Diagnostics.Disable_truncating_types_in_error_messages, - defaultValueDescription: "false" - }, - { - name: "noLib", - type: "boolean", - category: Diagnostics.Language_and_Environment, - affectsProgramStructure: true, - description: Diagnostics.Disable_including_any_library_files_including_the_default_lib_d_ts, - // We are not returning a sourceFile for lib file when asked by the program, - // so pass --noLib to avoid reporting a file not found error. - transpileOptionValue: true, - defaultValueDescription: "false" - }, - { - name: "noResolve", - type: "boolean", - affectsModuleResolution: true, - category: Diagnostics.Modules, - description: Diagnostics.Disallow_import_s_require_s_or_reference_s_from_expanding_the_number_of_files_TypeScript_should_add_to_a_project, - // We are not doing a full typecheck, we are not resolving the whole context, - // so pass --noResolve to avoid reporting missing file errors. - transpileOptionValue: true, - defaultValueDescription: "false" - }, - { - name: "stripInternal", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Emit, - description: Diagnostics.Disable_emitting_declarations_that_have_internal_in_their_JSDoc_comments, - defaultValueDescription: "false", - }, - { - name: "disableSizeLimit", - type: "boolean", - affectsProgramStructure: true, - category: Diagnostics.Editor_Support, - description: Diagnostics.Remove_the_20mb_cap_on_total_source_code_size_for_JavaScript_files_in_the_TypeScript_language_server, - defaultValueDescription: "false" - }, - { - name: "disableSourceOfProjectReferenceRedirect", - type: "boolean", - isTSConfigOnly: true, - category: Diagnostics.Projects, - description: Diagnostics.Disable_preferring_source_files_instead_of_declaration_files_when_referencing_composite_projects, - defaultValueDescription: "false", - }, - { - name: "disableSolutionSearching", - type: "boolean", - isTSConfigOnly: true, - category: Diagnostics.Projects, - description: Diagnostics.Opt_a_project_out_of_multi_project_reference_checking_when_editing, - defaultValueDescription: "false", - }, - { - name: "disableReferencedProjectLoad", - type: "boolean", - isTSConfigOnly: true, - category: Diagnostics.Projects, - description: Diagnostics.Reduce_the_number_of_projects_loaded_automatically_by_TypeScript, - defaultValueDescription: "false", - }, - { - name: "noImplicitUseStrict", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.Disable_adding_use_strict_directives_in_emitted_JavaScript_files, - defaultValueDescription: "false" - }, - { - name: "noEmitHelpers", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Emit, - description: Diagnostics.Disable_generating_custom_helper_functions_like_extends_in_compiled_output, - defaultValueDescription: "false" - }, - { - name: "noEmitOnError", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Emit, - transpileOptionValue: undefined, - description: Diagnostics.Disable_emitting_files_if_any_type_checking_errors_are_reported, - defaultValueDescription: "false" - }, - { - name: "preserveConstEnums", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Emit, - description: Diagnostics.Disable_erasing_const_enum_declarations_in_generated_code, - defaultValueDescription: "false", - }, - { - name: "declarationDir", + affectsModuleResolution: true, + category: Diagnostics.Modules, + description: Diagnostics.Allow_multiple_folders_to_be_treated_as_one_when_resolving_modules, + transpileOptionValue: undefined, + defaultValueDescription: Diagnostics.Computed_from_the_list_of_input_files + }, + { + name: "typeRoots", + type: "list", + element: { + name: "typeRoots", type: "string", - affectsEmit: true, - isFilePath: true, - paramType: Diagnostics.DIRECTORY, - category: Diagnostics.Emit, - transpileOptionValue: undefined, - description: Diagnostics.Specify_the_output_directory_for_generated_declaration_files, - }, - { - name: "skipLibCheck", - type: "boolean", - category: Diagnostics.Completeness, - description: Diagnostics.Skip_type_checking_all_d_ts_files, - defaultValueDescription: "false" - }, - { - name: "allowUnusedLabels", - type: "boolean", - affectsBindDiagnostics: true, - affectsSemanticDiagnostics: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Disable_error_reporting_for_unused_labels, - defaultValueDescription: "undefined" - }, - { - name: "allowUnreachableCode", - type: "boolean", - affectsBindDiagnostics: true, - affectsSemanticDiagnostics: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Disable_error_reporting_for_unreachable_code, - defaultValueDescription: "undefined" - }, - { - name: "suppressExcessPropertyErrors", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.Disable_reporting_of_excess_property_errors_during_the_creation_of_object_literals, - defaultValueDescription: "false" - }, - { - name: "suppressImplicitAnyIndexErrors", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.Suppress_noImplicitAny_errors_when_indexing_objects_that_lack_index_signatures, - defaultValueDescription: "false" - }, - { - name: "forceConsistentCasingInFileNames", - type: "boolean", - affectsModuleResolution: true, - category: Diagnostics.Interop_Constraints, - description: Diagnostics.Ensure_that_casing_is_correct_in_imports, - defaultValueDescription: "false" - }, - { - name: "maxNodeModuleJsDepth", - type: "number", - affectsModuleResolution: true, - category: Diagnostics.JavaScript_Support, - description: Diagnostics.Specify_the_maximum_folder_depth_used_for_checking_JavaScript_files_from_node_modules_Only_applicable_with_allowJs, - defaultValueDescription: "0" - }, - { - name: "noStrictGenericChecks", - type: "boolean", - affectsSemanticDiagnostics: true, - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.Disable_strict_checking_of_generic_signatures_in_function_types, - defaultValueDescription: "false" - }, - { - name: "useDefineForClassFields", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsEmit: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Emit_ECMAScript_standard_compliant_class_fields, - defaultValueDescription: Diagnostics.true_for_ES2022_and_above_including_ESNext - }, - { - name: "preserveValueImports", - type: "boolean", - affectsEmit: true, - category: Diagnostics.Emit, - description: Diagnostics.Preserve_unused_imported_values_in_the_JavaScript_output_that_would_otherwise_be_removed, - defaultValueDescription: "false", - }, - - { - name: "keyofStringsOnly", - type: "boolean", - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.Make_keyof_only_return_strings_instead_of_string_numbers_or_symbols_Legacy_option, - defaultValueDescription: "false" - }, - { - // A list of plugins to load in the language service - name: "plugins", - type: "list", - isTSConfigOnly: true, - element: { - name: "plugin", - type: "object" - }, - description: Diagnostics.List_of_language_service_plugins, - category: Diagnostics.Editor_Support, - - }, - ]; - - /* @internal */ - export const optionDeclarations: CommandLineOption[] = [ - ...commonOptionsWithBuild, - ...commandOptionsWithoutBuild, - ]; - - /* @internal */ - export const semanticDiagnosticsOptionDeclarations: readonly CommandLineOption[] = - optionDeclarations.filter(option => !!option.affectsSemanticDiagnostics); - - /* @internal */ - export const affectsEmitOptionDeclarations: readonly CommandLineOption[] = - optionDeclarations.filter(option => !!option.affectsEmit); - - /* @internal */ - export const moduleResolutionOptionDeclarations: readonly CommandLineOption[] = - optionDeclarations.filter(option => !!option.affectsModuleResolution); - - /* @internal */ - export const sourceFileAffectingCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => - !!option.affectsSourceFile || !!option.affectsModuleResolution || !!option.affectsBindDiagnostics); - - /* @internal */ - export const optionsAffectingProgramStructure: readonly CommandLineOption[] = - optionDeclarations.filter(option => !!option.affectsProgramStructure); - - /* @internal */ - export const transpileOptionValueCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => - hasProperty(option, "transpileOptionValue")); - - // Build related options - /* @internal */ - export const optionsForBuild: CommandLineOption[] = [ - { - name: "verbose", - shortName: "v", - category: Diagnostics.Command_line_Options, - description: Diagnostics.Enable_verbose_logging, - type: "boolean", - defaultValueDescription: "false", - }, - { - name: "dry", - shortName: "d", - category: Diagnostics.Command_line_Options, - description: Diagnostics.Show_what_would_be_built_or_deleted_if_specified_with_clean, - type: "boolean", - defaultValueDescription: "false", + isFilePath: true }, - { - name: "force", - shortName: "f", - category: Diagnostics.Command_line_Options, - description: Diagnostics.Build_all_projects_including_those_that_appear_to_be_up_to_date, - type: "boolean", - defaultValueDescription: "false", - }, - { - name: "clean", - category: Diagnostics.Command_line_Options, - description: Diagnostics.Delete_the_outputs_of_all_projects, - type: "boolean", - defaultValueDescription: "false", - } - ]; - - /* @internal */ - export const buildOpts: CommandLineOption[] = [ - ...commonOptionsWithBuild, - ...optionsForBuild - ]; - - /* @internal */ - export const typeAcquisitionDeclarations: CommandLineOption[] = [ - { - /* @deprecated typingOptions.enableAutoDiscovery - * Use typeAcquisition.enable instead. - */ - name: "enableAutoDiscovery", - type: "boolean", - defaultValueDescription: "false", - }, - { - name: "enable", - type: "boolean", - defaultValueDescription: "false", + affectsModuleResolution: true, + category: Diagnostics.Modules, + description: Diagnostics.Specify_multiple_folders_that_act_like_Slashnode_modules_Slash_types + }, + { + name: "types", + type: "list", + element: { + name: "types", + type: "string" }, - { + affectsProgramStructure: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Modules, + description: Diagnostics.Specify_type_package_names_to_be_included_without_being_referenced_in_a_source_file, + transpileOptionValue: undefined + }, + { + name: "allowSyntheticDefaultImports", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Interop_Constraints, + description: Diagnostics.Allow_import_x_from_y_when_a_module_doesn_t_have_a_default_export, + defaultValueDescription: Diagnostics.module_system_or_esModuleInterop + }, + { + name: "esModuleInterop", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsEmit: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Interop_Constraints, + description: Diagnostics.Emit_additional_JavaScript_to_ease_support_for_importing_CommonJS_modules_This_enables_allowSyntheticDefaultImports_for_type_compatibility, + defaultValueDescription: "false" + }, + { + name: "preserveSymlinks", + type: "boolean", + category: Diagnostics.Interop_Constraints, + description: Diagnostics.Disable_resolving_symlinks_to_their_realpath_This_correlates_to_the_same_flag_in_node, + defaultValueDescription: "false", + }, + { + name: "allowUmdGlobalAccess", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Modules, + description: Diagnostics.Allow_accessing_UMD_globals_from_modules, + defaultValueDescription: "false" + }, + + // Source Maps + { + name: "sourceRoot", + type: "string", + affectsEmit: true, + paramType: Diagnostics.LOCATION, + category: Diagnostics.Emit, + description: Diagnostics.Specify_the_root_path_for_debuggers_to_find_the_reference_source_code, + }, + { + name: "mapRoot", + type: "string", + affectsEmit: true, + paramType: Diagnostics.LOCATION, + category: Diagnostics.Emit, + description: Diagnostics.Specify_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations, + }, + { + name: "inlineSourceMap", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Emit, + description: Diagnostics.Include_sourcemap_files_inside_the_emitted_JavaScript, + defaultValueDescription: "false" + }, + { + name: "inlineSources", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Emit, + description: Diagnostics.Include_source_code_in_the_sourcemaps_inside_the_emitted_JavaScript, + defaultValueDescription: "false" + }, + + // Experimental + { + name: "experimentalDecorators", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Enable_experimental_support_for_TC39_stage_2_draft_decorators, + defaultValueDescription: "false", + }, + { + name: "emitDecoratorMetadata", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsEmit: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Emit_design_type_metadata_for_decorated_declarations_in_source_files, + defaultValueDescription: "false", + }, + + // Advanced + { + name: "jsxFactory", + type: "string", + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Specify_the_JSX_factory_function_used_when_targeting_React_JSX_emit_e_g_React_createElement_or_h, + defaultValueDescription: "`React.createElement`" + }, + { + name: "jsxFragmentFactory", + type: "string", + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Specify_the_JSX_Fragment_reference_used_for_fragments_when_targeting_React_JSX_emit_e_g_React_Fragment_or_Fragment + }, + { + name: "jsxImportSource", + type: "string", + affectsSemanticDiagnostics: true, + affectsEmit: true, + affectsModuleResolution: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Specify_module_specifier_used_to_import_the_JSX_factory_functions_when_using_jsx_Colon_react_jsx_Asterisk, + defaultValueDescription: "react" + }, + { + name: "resolveJsonModule", + type: "boolean", + affectsModuleResolution: true, + category: Diagnostics.Modules, + description: Diagnostics.Enable_importing_json_files, + defaultValueDescription: "false" + }, + + { + name: "out", + type: "string", + affectsEmit: true, + isFilePath: false, // This is intentionally broken to support compatability with existing tsconfig files + // for correct behaviour, please use outFile + category: Diagnostics.Backwards_Compatibility, + paramType: Diagnostics.FILE, + transpileOptionValue: undefined, + description: Diagnostics.Deprecated_setting_Use_outFile_instead, + }, + { + name: "reactNamespace", + type: "string", + affectsEmit: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Specify_the_object_invoked_for_createElement_This_only_applies_when_targeting_react_JSX_emit, + defaultValueDescription: "`React`", + }, + { + name: "skipDefaultLibCheck", + type: "boolean", + category: Diagnostics.Completeness, + description: Diagnostics.Skip_type_checking_d_ts_files_that_are_included_with_TypeScript, + defaultValueDescription: "false", + }, + { + name: "charset", + type: "string", + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.No_longer_supported_In_early_versions_manually_set_the_text_encoding_for_reading_files, + defaultValueDescription: "utf8" + }, + { + name: "emitBOM", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Emit, + description: Diagnostics.Emit_a_UTF_8_Byte_Order_Mark_BOM_in_the_beginning_of_output_files, + defaultValueDescription: "false" + }, + { + name: "newLine", + type: new Map(getEntries({ + crlf: NewLineKind.CarriageReturnLineFeed, + lf: NewLineKind.LineFeed + })), + affectsEmit: true, + paramType: Diagnostics.NEWLINE, + category: Diagnostics.Emit, + description: Diagnostics.Set_the_newline_character_for_emitting_files, + defaultValueDescription: Diagnostics.Platform_specific + }, + { + name: "noErrorTruncation", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Output_Formatting, + description: Diagnostics.Disable_truncating_types_in_error_messages, + defaultValueDescription: "false" + }, + { + name: "noLib", + type: "boolean", + category: Diagnostics.Language_and_Environment, + affectsProgramStructure: true, + description: Diagnostics.Disable_including_any_library_files_including_the_default_lib_d_ts, + // We are not returning a sourceFile for lib file when asked by the program, + // so pass --noLib to avoid reporting a file not found error. + transpileOptionValue: true, + defaultValueDescription: "false" + }, + { + name: "noResolve", + type: "boolean", + affectsModuleResolution: true, + category: Diagnostics.Modules, + description: Diagnostics.Disallow_import_s_require_s_or_reference_s_from_expanding_the_number_of_files_TypeScript_should_add_to_a_project, + // We are not doing a full typecheck, we are not resolving the whole context, + // so pass --noResolve to avoid reporting missing file errors. + transpileOptionValue: true, + defaultValueDescription: "false" + }, + { + name: "stripInternal", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Emit, + description: Diagnostics.Disable_emitting_declarations_that_have_internal_in_their_JSDoc_comments, + defaultValueDescription: "false", + }, + { + name: "disableSizeLimit", + type: "boolean", + affectsProgramStructure: true, + category: Diagnostics.Editor_Support, + description: Diagnostics.Remove_the_20mb_cap_on_total_source_code_size_for_JavaScript_files_in_the_TypeScript_language_server, + defaultValueDescription: "false" + }, + { + name: "disableSourceOfProjectReferenceRedirect", + type: "boolean", + isTSConfigOnly: true, + category: Diagnostics.Projects, + description: Diagnostics.Disable_preferring_source_files_instead_of_declaration_files_when_referencing_composite_projects, + defaultValueDescription: "false", + }, + { + name: "disableSolutionSearching", + type: "boolean", + isTSConfigOnly: true, + category: Diagnostics.Projects, + description: Diagnostics.Opt_a_project_out_of_multi_project_reference_checking_when_editing, + defaultValueDescription: "false", + }, + { + name: "disableReferencedProjectLoad", + type: "boolean", + isTSConfigOnly: true, + category: Diagnostics.Projects, + description: Diagnostics.Reduce_the_number_of_projects_loaded_automatically_by_TypeScript, + defaultValueDescription: "false", + }, + { + name: "noImplicitUseStrict", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.Disable_adding_use_strict_directives_in_emitted_JavaScript_files, + defaultValueDescription: "false" + }, + { + name: "noEmitHelpers", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Emit, + description: Diagnostics.Disable_generating_custom_helper_functions_like_extends_in_compiled_output, + defaultValueDescription: "false" + }, + { + name: "noEmitOnError", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Emit, + transpileOptionValue: undefined, + description: Diagnostics.Disable_emitting_files_if_any_type_checking_errors_are_reported, + defaultValueDescription: "false" + }, + { + name: "preserveConstEnums", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Emit, + description: Diagnostics.Disable_erasing_const_enum_declarations_in_generated_code, + defaultValueDescription: "false", + }, + { + name: "declarationDir", + type: "string", + affectsEmit: true, + isFilePath: true, + paramType: Diagnostics.DIRECTORY, + category: Diagnostics.Emit, + transpileOptionValue: undefined, + description: Diagnostics.Specify_the_output_directory_for_generated_declaration_files, + }, + { + name: "skipLibCheck", + type: "boolean", + category: Diagnostics.Completeness, + description: Diagnostics.Skip_type_checking_all_d_ts_files, + defaultValueDescription: "false" + }, + { + name: "allowUnusedLabels", + type: "boolean", + affectsBindDiagnostics: true, + affectsSemanticDiagnostics: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Disable_error_reporting_for_unused_labels, + defaultValueDescription: "undefined" + }, + { + name: "allowUnreachableCode", + type: "boolean", + affectsBindDiagnostics: true, + affectsSemanticDiagnostics: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Disable_error_reporting_for_unreachable_code, + defaultValueDescription: "undefined" + }, + { + name: "suppressExcessPropertyErrors", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.Disable_reporting_of_excess_property_errors_during_the_creation_of_object_literals, + defaultValueDescription: "false" + }, + { + name: "suppressImplicitAnyIndexErrors", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.Suppress_noImplicitAny_errors_when_indexing_objects_that_lack_index_signatures, + defaultValueDescription: "false" + }, + { + name: "forceConsistentCasingInFileNames", + type: "boolean", + affectsModuleResolution: true, + category: Diagnostics.Interop_Constraints, + description: Diagnostics.Ensure_that_casing_is_correct_in_imports, + defaultValueDescription: "false" + }, + { + name: "maxNodeModuleJsDepth", + type: "number", + affectsModuleResolution: true, + category: Diagnostics.JavaScript_Support, + description: Diagnostics.Specify_the_maximum_folder_depth_used_for_checking_JavaScript_files_from_node_modules_Only_applicable_with_allowJs, + defaultValueDescription: "0" + }, + { + name: "noStrictGenericChecks", + type: "boolean", + affectsSemanticDiagnostics: true, + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.Disable_strict_checking_of_generic_signatures_in_function_types, + defaultValueDescription: "false" + }, + { + name: "useDefineForClassFields", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsEmit: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Emit_ECMAScript_standard_compliant_class_fields, + defaultValueDescription: Diagnostics.true_for_ES2022_and_above_including_ESNext + }, + { + name: "preserveValueImports", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Emit, + description: Diagnostics.Preserve_unused_imported_values_in_the_JavaScript_output_that_would_otherwise_be_removed, + defaultValueDescription: "false", + }, + + { + name: "keyofStringsOnly", + type: "boolean", + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.Make_keyof_only_return_strings_instead_of_string_numbers_or_symbols_Legacy_option, + defaultValueDescription: "false" + }, + { + // A list of plugins to load in the language service + name: "plugins", + type: "list", + isTSConfigOnly: true, + element: { + name: "plugin", + type: "object" + }, + description: Diagnostics.List_of_language_service_plugins, + category: Diagnostics.Editor_Support, + + }, +]; + +/* @internal */ +export const optionDeclarations: CommandLineOption[] = [ + ...commonOptionsWithBuild, + ...commandOptionsWithoutBuild, +]; + +/* @internal */ +export const semanticDiagnosticsOptionDeclarations: readonly CommandLineOption[] = + optionDeclarations.filter(option => !!option.affectsSemanticDiagnostics); + +/* @internal */ +export const affectsEmitOptionDeclarations: readonly CommandLineOption[] = + optionDeclarations.filter(option => !!option.affectsEmit); + +/* @internal */ +export const moduleResolutionOptionDeclarations: readonly CommandLineOption[] = + optionDeclarations.filter(option => !!option.affectsModuleResolution); + +/* @internal */ +export const sourceFileAffectingCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => + !!option.affectsSourceFile || !!option.affectsModuleResolution || !!option.affectsBindDiagnostics); + +/* @internal */ +export const optionsAffectingProgramStructure: readonly CommandLineOption[] = + optionDeclarations.filter(option => !!option.affectsProgramStructure); + +/* @internal */ +export const transpileOptionValueCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => + hasProperty(option, "transpileOptionValue")); + +// Build related options +/* @internal */ +export const optionsForBuild: CommandLineOption[] = [ + { + name: "verbose", + shortName: "v", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Enable_verbose_logging, + type: "boolean", + defaultValueDescription: "false", + }, + { + name: "dry", + shortName: "d", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Show_what_would_be_built_or_deleted_if_specified_with_clean, + type: "boolean", + defaultValueDescription: "false", + }, + { + name: "force", + shortName: "f", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Build_all_projects_including_those_that_appear_to_be_up_to_date, + type: "boolean", + defaultValueDescription: "false", + }, + { + name: "clean", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Delete_the_outputs_of_all_projects, + type: "boolean", + defaultValueDescription: "false", + } +]; + +/* @internal */ +export const buildOpts: CommandLineOption[] = [ + ...commonOptionsWithBuild, + ...optionsForBuild +]; + +/* @internal */ +export const typeAcquisitionDeclarations: CommandLineOption[] = [ + { + /* @deprecated typingOptions.enableAutoDiscovery + * Use typeAcquisition.enable instead. + */ + name: "enableAutoDiscovery", + type: "boolean", + defaultValueDescription: "false", + }, + { + name: "enable", + type: "boolean", + defaultValueDescription: "false", + }, + { + name: "include", + type: "list", + element: { name: "include", - type: "list", - element: { - name: "include", - type: "string" - } - }, - { + type: "string" + } + }, + { + name: "exclude", + type: "list", + element: { name: "exclude", - type: "list", - element: { - name: "exclude", - type: "string" - } - }, - { - name: "disableFilenameBasedTypeAcquisition", - type: "boolean", - defaultValueDescription: "false", - }, - ]; - - /* @internal */ - export interface OptionsNameMap { - optionsNameMap: ESMap; - shortOptionNames: ESMap; - } - - /*@internal*/ - export function createOptionNameMap(optionDeclarations: readonly CommandLineOption[]): OptionsNameMap { - const optionsNameMap = new Map(); - const shortOptionNames = new Map(); - forEach(optionDeclarations, option => { - optionsNameMap.set(option.name.toLowerCase(), option); - if (option.shortName) { - shortOptionNames.set(option.shortName, option.name); - } - }); - - return { optionsNameMap, shortOptionNames }; - } + type: "string" + } + }, + { + name: "disableFilenameBasedTypeAcquisition", + type: "boolean", + defaultValueDescription: "false", + }, +]; + +/* @internal */ +export interface OptionsNameMap { + optionsNameMap: ESMap; + shortOptionNames: ESMap; +} - let optionsNameMapCache: OptionsNameMap; +/*@internal*/ +export function createOptionNameMap(optionDeclarations: readonly CommandLineOption[]): OptionsNameMap { + const optionsNameMap = new Map(); + const shortOptionNames = new Map(); + forEach(optionDeclarations, option => { + optionsNameMap.set(option.name.toLowerCase(), option); + if (option.shortName) { + shortOptionNames.set(option.shortName, option.name); + } + }); - /* @internal */ - export function getOptionsNameMap(): OptionsNameMap { - return optionsNameMapCache ||= createOptionNameMap(optionDeclarations); - } + return { optionsNameMap, shortOptionNames }; +} - const compilerOptionsAlternateMode: AlternateModeDiagnostics = { - diagnostic: Diagnostics.Compiler_option_0_may_only_be_used_with_build, - getOptionsNameMap: getBuildOptionsNameMap - }; +let optionsNameMapCache: OptionsNameMap; - /* @internal */ - export const defaultInitCompilerOptions: CompilerOptions = { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES2016, - strict: true, - esModuleInterop: true, - forceConsistentCasingInFileNames: true, - skipLibCheck: true - }; +/* @internal */ +export function getOptionsNameMap(): OptionsNameMap { + return optionsNameMapCache ||= createOptionNameMap(optionDeclarations); +} - /* @internal */ - export function convertEnableAutoDiscoveryToEnable(typeAcquisition: TypeAcquisition): TypeAcquisition { - // Convert deprecated typingOptions.enableAutoDiscovery to typeAcquisition.enable - if (typeAcquisition && typeAcquisition.enableAutoDiscovery !== undefined && typeAcquisition.enable === undefined) { - return { - enable: typeAcquisition.enableAutoDiscovery, - include: typeAcquisition.include || [], - exclude: typeAcquisition.exclude || [] - }; - } - return typeAcquisition; +const compilerOptionsAlternateMode: AlternateModeDiagnostics = { + diagnostic: Diagnostics.Compiler_option_0_may_only_be_used_with_build, + getOptionsNameMap: getBuildOptionsNameMap +}; + +/* @internal */ +export const defaultInitCompilerOptions: CompilerOptions = { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES2016, + strict: true, + esModuleInterop: true, + forceConsistentCasingInFileNames: true, + skipLibCheck: true +}; + +/* @internal */ +export function convertEnableAutoDiscoveryToEnable(typeAcquisition: TypeAcquisition): TypeAcquisition { + // Convert deprecated typingOptions.enableAutoDiscovery to typeAcquisition.enable + if (typeAcquisition && typeAcquisition.enableAutoDiscovery !== undefined && typeAcquisition.enable === undefined) { + return { + enable: typeAcquisition.enableAutoDiscovery, + include: typeAcquisition.include || [], + exclude: typeAcquisition.exclude || [] + }; } + return typeAcquisition; +} - /* @internal */ - export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { - return createDiagnosticForInvalidCustomType(opt, createCompilerDiagnostic); - } +/* @internal */ +export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { + return createDiagnosticForInvalidCustomType(opt, createCompilerDiagnostic); +} - function createDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType, createDiagnostic: (message: DiagnosticMessage, arg0: string, arg1: string) => Diagnostic): Diagnostic { - const namesOfType = arrayFrom(opt.type.keys()).map(key => `'${key}'`).join(", "); - return createDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); - } +function createDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType, createDiagnostic: (message: DiagnosticMessage, arg0: string, arg1: string) => Diagnostic): Diagnostic { + const namesOfType = arrayFrom(opt.type.keys()).map(key => `'${key}'`).join(", "); + return createDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); +} - /* @internal */ - export function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string, errors: Push) { - return convertJsonOptionOfCustomType(opt, trimString(value || ""), errors); - } +/* @internal */ +export function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string, errors: Push) { + return convertJsonOptionOfCustomType(opt, trimString(value || ""), errors); +} - /* @internal */ - export function parseListTypeOption(opt: CommandLineOptionOfListType, value = "", errors: Push): (string | number)[] | undefined { - value = trimString(value); - if (startsWith(value, "-")) { - return undefined; - } - if (value === "") { - return []; - } - const values = value.split(","); - switch (opt.element.type) { - case "number": - return mapDefined(values, v => validateJsonOptionValue(opt.element, parseInt(v), errors)); - case "string": - return mapDefined(values, v => validateJsonOptionValue(opt.element, v || "", errors)); - default: - return mapDefined(values, v => parseCustomTypeOption(opt.element as CommandLineOptionOfCustomType, v, errors)); - } +/* @internal */ +export function parseListTypeOption(opt: CommandLineOptionOfListType, value = "", errors: Push): (string | number)[] | undefined { + value = trimString(value); + if (startsWith(value, "-")) { + return undefined; } - - /*@internal*/ - export interface OptionsBase { - [option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined; + if (value === "") { + return []; } - - /*@internal*/ - export interface ParseCommandLineWorkerDiagnostics extends DidYouMeanOptionsDiagnostics { - getOptionsNameMap: () => OptionsNameMap; - optionTypeMismatchDiagnostic: DiagnosticMessage; + const values = value.split(","); + switch (opt.element.type) { + case "number": + return mapDefined(values, v => validateJsonOptionValue(opt.element, parseInt(v), errors)); + case "string": + return mapDefined(values, v => validateJsonOptionValue(opt.element, v || "", errors)); + default: + return mapDefined(values, v => parseCustomTypeOption(opt.element as CommandLineOptionOfCustomType, v, errors)); } +} - function getOptionName(option: CommandLineOption) { - return option.name; - } +/*@internal*/ +export interface OptionsBase { + [option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined; +} - function createUnknownOptionError( - unknownOption: string, - diagnostics: DidYouMeanOptionsDiagnostics, - createDiagnostics: (message: DiagnosticMessage, arg0: string, arg1?: string) => Diagnostic, - unknownOptionErrorText?: string - ) { - if (diagnostics.alternateMode?.getOptionsNameMap().optionsNameMap.has(unknownOption.toLowerCase())) { - return createDiagnostics(diagnostics.alternateMode.diagnostic, unknownOption); - } +/*@internal*/ +export interface ParseCommandLineWorkerDiagnostics extends DidYouMeanOptionsDiagnostics { + getOptionsNameMap: () => OptionsNameMap; + optionTypeMismatchDiagnostic: DiagnosticMessage; +} + +function getOptionName(option: CommandLineOption) { + return option.name; +} - const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName); - return possibleOption ? - createDiagnostics(diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : - createDiagnostics(diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption); +function createUnknownOptionError( + unknownOption: string, + diagnostics: DidYouMeanOptionsDiagnostics, + createDiagnostics: (message: DiagnosticMessage, arg0: string, arg1?: string) => Diagnostic, + unknownOptionErrorText?: string +) { + if (diagnostics.alternateMode?.getOptionsNameMap().optionsNameMap.has(unknownOption.toLowerCase())) { + return createDiagnostics(diagnostics.alternateMode.diagnostic, unknownOption); } - /*@internal*/ - export function parseCommandLineWorker( - diagnostics: ParseCommandLineWorkerDiagnostics, - commandLine: readonly string[], - readFile?: (path: string) => string | undefined) { - const options = {} as OptionsBase; - let watchOptions: WatchOptions | undefined; - const fileNames: string[] = []; - const errors: Diagnostic[] = []; + const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName); + return possibleOption ? + createDiagnostics(diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : + createDiagnostics(diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption); +} - parseStrings(commandLine); - return { - options, - watchOptions, - fileNames, - errors - }; +/*@internal*/ +export function parseCommandLineWorker( + diagnostics: ParseCommandLineWorkerDiagnostics, + commandLine: readonly string[], + readFile?: (path: string) => string | undefined) { + const options = {} as OptionsBase; + let watchOptions: WatchOptions | undefined; + const fileNames: string[] = []; + const errors: Diagnostic[] = []; + + parseStrings(commandLine); + return { + options, + watchOptions, + fileNames, + errors + }; - function parseStrings(args: readonly string[]) { - let i = 0; - while (i < args.length) { - const s = args[i]; - i++; - if (s.charCodeAt(0) === CharacterCodes.at) { - parseResponseFile(s.slice(1)); + function parseStrings(args: readonly string[]) { + let i = 0; + while (i < args.length) { + const s = args[i]; + i++; + if (s.charCodeAt(0) === CharacterCodes.at) { + parseResponseFile(s.slice(1)); + } + else if (s.charCodeAt(0) === CharacterCodes.minus) { + const inputOptionName = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1); + const opt = getOptionDeclarationFromName(diagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true); + if (opt) { + i = parseOptionValue(args, i, diagnostics, opt, options, errors); } - else if (s.charCodeAt(0) === CharacterCodes.minus) { - const inputOptionName = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1); - const opt = getOptionDeclarationFromName(diagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true); - if (opt) { - i = parseOptionValue(args, i, diagnostics, opt, options, errors); + else { + const watchOpt = getOptionDeclarationFromName(watchOptionsDidYouMeanDiagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true); + if (watchOpt) { + i = parseOptionValue(args, i, watchOptionsDidYouMeanDiagnostics, watchOpt, watchOptions || (watchOptions = {}), errors); } else { - const watchOpt = getOptionDeclarationFromName(watchOptionsDidYouMeanDiagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true); - if (watchOpt) { - i = parseOptionValue(args, i, watchOptionsDidYouMeanDiagnostics, watchOpt, watchOptions || (watchOptions = {}), errors); - } - else { - errors.push(createUnknownOptionError(inputOptionName, diagnostics, createCompilerDiagnostic, s)); - } + errors.push(createUnknownOptionError(inputOptionName, diagnostics, createCompilerDiagnostic, s)); } } - else { - fileNames.push(s); - } + } + else { + fileNames.push(s); } } + } - function parseResponseFile(fileName: string) { - const text = tryReadFile(fileName, readFile || (fileName => sys.readFile(fileName))); - if (!isString(text)) { - errors.push(text); - return; - } + function parseResponseFile(fileName: string) { + const text = tryReadFile(fileName, readFile || (fileName => sys.readFile(fileName))); + if (!isString(text)) { + errors.push(text); + return; + } - const args: string[] = []; - let pos = 0; - while (true) { - while (pos < text.length && text.charCodeAt(pos) <= CharacterCodes.space) pos++; - if (pos >= text.length) break; - const start = pos; - if (text.charCodeAt(start) === CharacterCodes.doubleQuote) { + const args: string[] = []; + let pos = 0; + while (true) { + while (pos < text.length && text.charCodeAt(pos) <= CharacterCodes.space) pos++; + if (pos >= text.length) break; + const start = pos; + if (text.charCodeAt(start) === CharacterCodes.doubleQuote) { + pos++; + while (pos < text.length && text.charCodeAt(pos) !== CharacterCodes.doubleQuote) pos++; + if (pos < text.length) { + args.push(text.substring(start + 1, pos)); pos++; - while (pos < text.length && text.charCodeAt(pos) !== CharacterCodes.doubleQuote) pos++; - if (pos < text.length) { - args.push(text.substring(start + 1, pos)); - pos++; - } - else { - errors.push(createCompilerDiagnostic(Diagnostics.Unterminated_quoted_string_in_response_file_0, fileName)); - } } else { - while (text.charCodeAt(pos) > CharacterCodes.space) pos++; - args.push(text.substring(start, pos)); + errors.push(createCompilerDiagnostic(Diagnostics.Unterminated_quoted_string_in_response_file_0, fileName)); } } - parseStrings(args); + else { + while (text.charCodeAt(pos) > CharacterCodes.space) pos++; + args.push(text.substring(start, pos)); + } } + parseStrings(args); } +} - function parseOptionValue( - args: readonly string[], - i: number, - diagnostics: ParseCommandLineWorkerDiagnostics, - opt: CommandLineOption, - options: OptionsBase, - errors: Diagnostic[] - ) { - if (opt.isTSConfigOnly) { - const optValue = args[i]; - if (optValue === "null") { - options[opt.name] = undefined; +function parseOptionValue( + args: readonly string[], + i: number, + diagnostics: ParseCommandLineWorkerDiagnostics, + opt: CommandLineOption, + options: OptionsBase, + errors: Diagnostic[] +) { + if (opt.isTSConfigOnly) { + const optValue = args[i]; + if (optValue === "null") { + options[opt.name] = undefined; + i++; + } + else if (opt.type === "boolean") { + if (optValue === "false") { + options[opt.name] = validateJsonOptionValue(opt, /*value*/ false, errors); i++; } - else if (opt.type === "boolean") { - if (optValue === "false") { - options[opt.name] = validateJsonOptionValue(opt, /*value*/ false, errors); - i++; - } - else { - if (optValue === "true") i++; - errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_false_or_null_on_command_line, opt.name)); - } - } else { - errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line, opt.name)); - if (optValue && !startsWith(optValue, "-")) i++; + if (optValue === "true") i++; + errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_false_or_null_on_command_line, opt.name)); } } else { - // Check to see if no argument was provided (e.g. "--locale" is the last command-line argument). - if (!args[i] && opt.type !== "boolean") { - errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name, getCompilerOptionValueTypeString(opt))); - } + errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line, opt.name)); + if (optValue && !startsWith(optValue, "-")) i++; + } + } + else { + // Check to see if no argument was provided (e.g. "--locale" is the last command-line argument). + if (!args[i] && opt.type !== "boolean") { + errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name, getCompilerOptionValueTypeString(opt))); + } - if (args[i] !== "null") { - switch (opt.type) { - case "number": - options[opt.name] = validateJsonOptionValue(opt, parseInt(args[i]), errors); - i++; - break; - case "boolean": - // boolean flag has optional value true, false, others - const optValue = args[i]; - options[opt.name] = validateJsonOptionValue(opt, optValue !== "false", errors); - // consume next argument as boolean flag value - if (optValue === "false" || optValue === "true") { - i++; - } - break; - case "string": - options[opt.name] = validateJsonOptionValue(opt, args[i] || "", errors); + if (args[i] !== "null") { + switch (opt.type) { + case "number": + options[opt.name] = validateJsonOptionValue(opt, parseInt(args[i]), errors); + i++; + break; + case "boolean": + // boolean flag has optional value true, false, others + const optValue = args[i]; + options[opt.name] = validateJsonOptionValue(opt, optValue !== "false", errors); + // consume next argument as boolean flag value + if (optValue === "false" || optValue === "true") { i++; - break; - case "list": - const result = parseListTypeOption(opt, args[i], errors); - options[opt.name] = result || []; - if (result) { - i++; - } - break; - // If not a primitive, the possible types are specified in what is effectively a map of options. - default: - options[opt.name] = parseCustomTypeOption(opt as CommandLineOptionOfCustomType, args[i], errors); + } + break; + case "string": + options[opt.name] = validateJsonOptionValue(opt, args[i] || "", errors); + i++; + break; + case "list": + const result = parseListTypeOption(opt, args[i], errors); + options[opt.name] = result || []; + if (result) { i++; - break; - } - } - else { - options[opt.name] = undefined; - i++; + } + break; + // If not a primitive, the possible types are specified in what is effectively a map of options. + default: + options[opt.name] = parseCustomTypeOption(opt as CommandLineOptionOfCustomType, args[i], errors); + i++; + break; } } - return i; + else { + options[opt.name] = undefined; + i++; + } } + return i; +} - /*@internal*/ - export const compilerOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { - alternateMode: compilerOptionsAlternateMode, - getOptionsNameMap, - optionDeclarations, - unknownOptionDiagnostic: Diagnostics.Unknown_compiler_option_0, - unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1, - optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument - }; - export function parseCommandLine(commandLine: readonly string[], readFile?: (path: string) => string | undefined): ParsedCommandLine { - return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile); - } +/*@internal*/ +export const compilerOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { + alternateMode: compilerOptionsAlternateMode, + getOptionsNameMap, + optionDeclarations, + unknownOptionDiagnostic: Diagnostics.Unknown_compiler_option_0, + unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1, + optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument +}; +export function parseCommandLine(commandLine: readonly string[], readFile?: (path: string) => string | undefined): ParsedCommandLine { + return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile); +} - /** @internal */ - export function getOptionFromName(optionName: string, allowShort?: boolean): CommandLineOption | undefined { - return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort); - } +/** @internal */ +export function getOptionFromName(optionName: string, allowShort?: boolean): CommandLineOption | undefined { + return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort); +} - function getOptionDeclarationFromName(getOptionNameMap: () => OptionsNameMap, optionName: string, allowShort = false): CommandLineOption | undefined { - optionName = optionName.toLowerCase(); - const { optionsNameMap, shortOptionNames } = getOptionNameMap(); - // Try to translate short option names to their full equivalents. - if (allowShort) { - const short = shortOptionNames.get(optionName); - if (short !== undefined) { - optionName = short; - } +function getOptionDeclarationFromName(getOptionNameMap: () => OptionsNameMap, optionName: string, allowShort = false): CommandLineOption | undefined { + optionName = optionName.toLowerCase(); + const { optionsNameMap, shortOptionNames } = getOptionNameMap(); + // Try to translate short option names to their full equivalents. + if (allowShort) { + const short = shortOptionNames.get(optionName); + if (short !== undefined) { + optionName = short; } - return optionsNameMap.get(optionName); - } - - /*@internal*/ - export interface ParsedBuildCommand { - buildOptions: BuildOptions; - watchOptions: WatchOptions | undefined; - projects: string[]; - errors: Diagnostic[]; - } - - let buildOptionsNameMapCache: OptionsNameMap; - function getBuildOptionsNameMap(): OptionsNameMap { - return buildOptionsNameMapCache || (buildOptionsNameMapCache = createOptionNameMap(buildOpts)); } + return optionsNameMap.get(optionName); +} - const buildOptionsAlternateMode: AlternateModeDiagnostics = { - diagnostic: Diagnostics.Compiler_option_0_may_not_be_used_with_build, - getOptionsNameMap - }; +/*@internal*/ +export interface ParsedBuildCommand { + buildOptions: BuildOptions; + watchOptions: WatchOptions | undefined; + projects: string[]; + errors: Diagnostic[]; +} - const buildOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { - alternateMode: buildOptionsAlternateMode, - getOptionsNameMap: getBuildOptionsNameMap, - optionDeclarations: buildOpts, - unknownOptionDiagnostic: Diagnostics.Unknown_build_option_0, - unknownDidYouMeanDiagnostic: Diagnostics.Unknown_build_option_0_Did_you_mean_1, - optionTypeMismatchDiagnostic: Diagnostics.Build_option_0_requires_a_value_of_type_1 - }; +let buildOptionsNameMapCache: OptionsNameMap; +function getBuildOptionsNameMap(): OptionsNameMap { + return buildOptionsNameMapCache || (buildOptionsNameMapCache = createOptionNameMap(buildOpts)); +} - /*@internal*/ - export function parseBuildCommand(args: readonly string[]): ParsedBuildCommand { - const { options, watchOptions, fileNames: projects, errors } = parseCommandLineWorker( - buildOptionsDidYouMeanDiagnostics, - args - ); - const buildOptions = options as BuildOptions; +const buildOptionsAlternateMode: AlternateModeDiagnostics = { + diagnostic: Diagnostics.Compiler_option_0_may_not_be_used_with_build, + getOptionsNameMap +}; - if (projects.length === 0) { - // tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ." - projects.push("."); - } +const buildOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { + alternateMode: buildOptionsAlternateMode, + getOptionsNameMap: getBuildOptionsNameMap, + optionDeclarations: buildOpts, + unknownOptionDiagnostic: Diagnostics.Unknown_build_option_0, + unknownDidYouMeanDiagnostic: Diagnostics.Unknown_build_option_0_Did_you_mean_1, + optionTypeMismatchDiagnostic: Diagnostics.Build_option_0_requires_a_value_of_type_1 +}; - // Nonsensical combinations - if (buildOptions.clean && buildOptions.force) { - errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force")); - } - if (buildOptions.clean && buildOptions.verbose) { - errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose")); - } - if (buildOptions.clean && buildOptions.watch) { - errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch")); - } - if (buildOptions.watch && buildOptions.dry) { - errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry")); - } +/*@internal*/ +export function parseBuildCommand(args: readonly string[]): ParsedBuildCommand { + const { options, watchOptions, fileNames: projects, errors } = parseCommandLineWorker( + buildOptionsDidYouMeanDiagnostics, + args + ); + const buildOptions = options as BuildOptions; - return { buildOptions, watchOptions, projects, errors }; + if (projects.length === 0) { + // tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ." + projects.push("."); } - /* @internal */ - export function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { - const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); - return diagnostic.messageText as string; + // Nonsensical combinations + if (buildOptions.clean && buildOptions.force) { + errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force")); } - - export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - /** - * Reports config file diagnostics - */ - export interface ConfigFileDiagnosticsReporter { - /** - * Reports unrecoverable error when parsing config file - */ - onUnRecoverableConfigFileDiagnostic: DiagnosticReporter; + if (buildOptions.clean && buildOptions.verbose) { + errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose")); } - - /** - * Interface extending ParseConfigHost to support ParseConfigFile that reads config file and reports errors - */ - export interface ParseConfigFileHost extends ParseConfigHost, ConfigFileDiagnosticsReporter { - getCurrentDirectory(): string; + if (buildOptions.clean && buildOptions.watch) { + errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch")); + } + if (buildOptions.watch && buildOptions.dry) { + errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry")); } - /** - * Reads the config file, reports errors if any and exits if the config file cannot be found - */ - export function getParsedCommandLineOfConfigFile( - configFileName: string, - optionsToExtend: CompilerOptions | undefined, - host: ParseConfigFileHost, - extendedConfigCache?: Map, - watchOptionsToExtend?: WatchOptions, - extraFileExtensions?: readonly FileExtensionInfo[], - ): ParsedCommandLine | undefined { - const configFileText = tryReadFile(configFileName, fileName => host.readFile(fileName)); - if (!isString(configFileText)) { - host.onUnRecoverableConfigFileDiagnostic(configFileText); - return undefined; - } + return { buildOptions, watchOptions, projects, errors }; +} - const result = parseJsonText(configFileName, configFileText); - const cwd = host.getCurrentDirectory(); - result.path = toPath(configFileName, cwd, createGetCanonicalFileName(host.useCaseSensitiveFileNames)); - result.resolvedPath = result.path; - result.originalFileName = result.fileName; - return parseJsonSourceFileConfigFileContent( - result, - host, - getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), - optionsToExtend, - getNormalizedAbsolutePath(configFileName, cwd), - /*resolutionStack*/ undefined, - extraFileExtensions, - extendedConfigCache, - watchOptionsToExtend - ); - } +/* @internal */ +export function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { + const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); + return diagnostic.messageText as string; +} +export type DiagnosticReporter = (diagnostic: Diagnostic) => void; +/** + * Reports config file diagnostics + */ +export interface ConfigFileDiagnosticsReporter { /** - * Read tsconfig.json file - * @param fileName The path to the config file + * Reports unrecoverable error when parsing config file */ - export function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { config?: any; error?: Diagnostic } { - const textOrDiagnostic = tryReadFile(fileName, readFile); - return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic }; - } + onUnRecoverableConfigFileDiagnostic: DiagnosticReporter; +} - /** - * Parse the text of the tsconfig.json file - * @param fileName The path to the config file - * @param jsonText The text of the config file - */ - export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } { - const jsonSourceFile = parseJsonText(fileName, jsonText); - return { - config: convertConfigFileToObject(jsonSourceFile, jsonSourceFile.parseDiagnostics, /*reportOptionsErrors*/ false, /*optionsIterator*/ undefined), - error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : undefined - }; - } +/** + * Interface extending ParseConfigHost to support ParseConfigFile that reads config file and reports errors + */ +export interface ParseConfigFileHost extends ParseConfigHost, ConfigFileDiagnosticsReporter { + getCurrentDirectory(): string; +} - /** - * Read tsconfig.json file - * @param fileName The path to the config file - */ - export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): TsConfigSourceFile { - const textOrDiagnostic = tryReadFile(fileName, readFile); - return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] } as TsConfigSourceFile; +/** + * Reads the config file, reports errors if any and exits if the config file cannot be found + */ +export function getParsedCommandLineOfConfigFile( + configFileName: string, + optionsToExtend: CompilerOptions | undefined, + host: ParseConfigFileHost, + extendedConfigCache?: Map, + watchOptionsToExtend?: WatchOptions, + extraFileExtensions?: readonly FileExtensionInfo[], +): ParsedCommandLine | undefined { + const configFileText = tryReadFile(configFileName, fileName => host.readFile(fileName)); + if (!isString(configFileText)) { + host.onUnRecoverableConfigFileDiagnostic(configFileText); + return undefined; } - /*@internal*/ - export function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic { - let text: string | undefined; - try { - text = readFile(fileName); - } - catch (e) { - return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message); - } - return text === undefined ? createCompilerDiagnostic(Diagnostics.Cannot_read_file_0, fileName) : text; - } + const result = parseJsonText(configFileName, configFileText); + const cwd = host.getCurrentDirectory(); + result.path = toPath(configFileName, cwd, createGetCanonicalFileName(host.useCaseSensitiveFileNames)); + result.resolvedPath = result.path; + result.originalFileName = result.fileName; + return parseJsonSourceFileConfigFileContent( + result, + host, + getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), + optionsToExtend, + getNormalizedAbsolutePath(configFileName, cwd), + /*resolutionStack*/ undefined, + extraFileExtensions, + extendedConfigCache, + watchOptionsToExtend + ); +} - function commandLineOptionsToMap(options: readonly CommandLineOption[]) { - return arrayToMap(options, getOptionName); - } +/** + * Read tsconfig.json file + * @param fileName The path to the config file + */ +export function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { config?: any; error?: Diagnostic } { + const textOrDiagnostic = tryReadFile(fileName, readFile); + return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic }; +} - const typeAcquisitionDidYouMeanDiagnostics: DidYouMeanOptionsDiagnostics = { - optionDeclarations: typeAcquisitionDeclarations, - unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0, - unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1, +/** + * Parse the text of the tsconfig.json file + * @param fileName The path to the config file + * @param jsonText The text of the config file + */ +export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } { + const jsonSourceFile = parseJsonText(fileName, jsonText); + return { + config: convertConfigFileToObject(jsonSourceFile, jsonSourceFile.parseDiagnostics, /*reportOptionsErrors*/ false, /*optionsIterator*/ undefined), + error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : undefined }; +} - let watchOptionsNameMapCache: OptionsNameMap; - function getWatchOptionsNameMap(): OptionsNameMap { - return watchOptionsNameMapCache || (watchOptionsNameMapCache = createOptionNameMap(optionsForWatch)); +/** + * Read tsconfig.json file + * @param fileName The path to the config file + */ +export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): TsConfigSourceFile { + const textOrDiagnostic = tryReadFile(fileName, readFile); + return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] } as TsConfigSourceFile; +} + +/*@internal*/ +export function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic { + let text: string | undefined; + try { + text = readFile(fileName); } - const watchOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { - getOptionsNameMap: getWatchOptionsNameMap, - optionDeclarations: optionsForWatch, - unknownOptionDiagnostic: Diagnostics.Unknown_watch_option_0, - unknownDidYouMeanDiagnostic: Diagnostics.Unknown_watch_option_0_Did_you_mean_1, - optionTypeMismatchDiagnostic: Diagnostics.Watch_option_0_requires_a_value_of_type_1 - }; + catch (e) { + return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message); + } + return text === undefined ? createCompilerDiagnostic(Diagnostics.Cannot_read_file_0, fileName) : text; +} - let commandLineCompilerOptionsMapCache: ESMap; - function getCommandLineCompilerOptionsMap() { - return commandLineCompilerOptionsMapCache || (commandLineCompilerOptionsMapCache = commandLineOptionsToMap(optionDeclarations)); - } - let commandLineWatchOptionsMapCache: ESMap; - function getCommandLineWatchOptionsMap() { - return commandLineWatchOptionsMapCache || (commandLineWatchOptionsMapCache = commandLineOptionsToMap(optionsForWatch)); - } - let commandLineTypeAcquisitionMapCache: ESMap; - function getCommandLineTypeAcquisitionMap() { - return commandLineTypeAcquisitionMapCache || (commandLineTypeAcquisitionMapCache = commandLineOptionsToMap(typeAcquisitionDeclarations)); - } - - let _tsconfigRootOptions: TsConfigOnlyOption; - function getTsconfigRootOptionsMap() { - if (_tsconfigRootOptions === undefined) { - _tsconfigRootOptions = { - name: undefined!, // should never be needed since this is root - type: "object", - elementOptions: commandLineOptionsToMap([ - { - name: "compilerOptions", - type: "object", - elementOptions: getCommandLineCompilerOptionsMap(), - extraKeyDiagnostics: compilerOptionsDidYouMeanDiagnostics, - }, - { - name: "watchOptions", - type: "object", - elementOptions: getCommandLineWatchOptionsMap(), - extraKeyDiagnostics: watchOptionsDidYouMeanDiagnostics, - }, - { - name: "typingOptions", - type: "object", - elementOptions: getCommandLineTypeAcquisitionMap(), - extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics, - }, - { - name: "typeAcquisition", - type: "object", - elementOptions: getCommandLineTypeAcquisitionMap(), - extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics - }, - { - name: "extends", - type: "string", - category: Diagnostics.File_Management, - }, - { +function commandLineOptionsToMap(options: readonly CommandLineOption[]) { + return arrayToMap(options, getOptionName); +} + +const typeAcquisitionDidYouMeanDiagnostics: DidYouMeanOptionsDiagnostics = { + optionDeclarations: typeAcquisitionDeclarations, + unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0, + unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1, +}; + +let watchOptionsNameMapCache: OptionsNameMap; +function getWatchOptionsNameMap(): OptionsNameMap { + return watchOptionsNameMapCache || (watchOptionsNameMapCache = createOptionNameMap(optionsForWatch)); +} +const watchOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { + getOptionsNameMap: getWatchOptionsNameMap, + optionDeclarations: optionsForWatch, + unknownOptionDiagnostic: Diagnostics.Unknown_watch_option_0, + unknownDidYouMeanDiagnostic: Diagnostics.Unknown_watch_option_0_Did_you_mean_1, + optionTypeMismatchDiagnostic: Diagnostics.Watch_option_0_requires_a_value_of_type_1 +}; + +let commandLineCompilerOptionsMapCache: ESMap; +function getCommandLineCompilerOptionsMap() { + return commandLineCompilerOptionsMapCache || (commandLineCompilerOptionsMapCache = commandLineOptionsToMap(optionDeclarations)); +} +let commandLineWatchOptionsMapCache: ESMap; +function getCommandLineWatchOptionsMap() { + return commandLineWatchOptionsMapCache || (commandLineWatchOptionsMapCache = commandLineOptionsToMap(optionsForWatch)); +} +let commandLineTypeAcquisitionMapCache: ESMap; +function getCommandLineTypeAcquisitionMap() { + return commandLineTypeAcquisitionMapCache || (commandLineTypeAcquisitionMapCache = commandLineOptionsToMap(typeAcquisitionDeclarations)); +} + +let _tsconfigRootOptions: TsConfigOnlyOption; +function getTsconfigRootOptionsMap() { + if (_tsconfigRootOptions === undefined) { + _tsconfigRootOptions = { + name: undefined!, // should never be needed since this is root + type: "object", + elementOptions: commandLineOptionsToMap([ + { + name: "compilerOptions", + type: "object", + elementOptions: getCommandLineCompilerOptionsMap(), + extraKeyDiagnostics: compilerOptionsDidYouMeanDiagnostics, + }, + { + name: "watchOptions", + type: "object", + elementOptions: getCommandLineWatchOptionsMap(), + extraKeyDiagnostics: watchOptionsDidYouMeanDiagnostics, + }, + { + name: "typingOptions", + type: "object", + elementOptions: getCommandLineTypeAcquisitionMap(), + extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics, + }, + { + name: "typeAcquisition", + type: "object", + elementOptions: getCommandLineTypeAcquisitionMap(), + extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics + }, + { + name: "extends", + type: "string", + category: Diagnostics.File_Management, + }, + { + name: "references", + type: "list", + element: { name: "references", - type: "list", - element: { - name: "references", - type: "object" - }, - category: Diagnostics.Projects, + type: "object" }, - { + category: Diagnostics.Projects, + }, + { + name: "files", + type: "list", + element: { name: "files", - type: "list", - element: { - name: "files", - type: "string" - }, - category: Diagnostics.File_Management, + type: "string" }, - { + category: Diagnostics.File_Management, + }, + { + name: "include", + type: "list", + element: { name: "include", - type: "list", - element: { - name: "include", - type: "string" - }, - category: Diagnostics.File_Management, - defaultValueDescription: Diagnostics.if_files_is_specified_otherwise_Asterisk_Asterisk_Slash_Asterisk + type: "string" }, - { + category: Diagnostics.File_Management, + defaultValueDescription: Diagnostics.if_files_is_specified_otherwise_Asterisk_Asterisk_Slash_Asterisk + }, + { + name: "exclude", + type: "list", + element: { name: "exclude", - type: "list", - element: { - name: "exclude", - type: "string" - }, - category: Diagnostics.File_Management, - defaultValueDescription: Diagnostics.node_modules_bower_components_jspm_packages_plus_the_value_of_outDir_if_one_is_specified + type: "string" }, - compileOnSaveCommandLineOption - ]) - }; - } - return _tsconfigRootOptions; - } - - /*@internal*/ - interface JsonConversionNotifier { - /** - * Notifies parent option object is being set with the optionKey and a valid optionValue - * Currently it notifies only if there is element with type object (parentOption) and - * has element's option declarations map associated with it - * @param parentOption parent option name in which the option and value are being set - * @param option option declaration which is being set with the value - * @param value value of the option - */ - onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue): void; - /** - * Notify when valid root key value option is being set - * @param key option key - * @param keyNode node corresponding to node in the source file - * @param value computed value of the key - * @param ValueNode node corresponding to value in the source file - */ - onSetValidOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; - /** - * Notify when unknown root key value option is being set - * @param key option key - * @param keyNode node corresponding to node in the source file - * @param value computed value of the key - * @param ValueNode node corresponding to value in the source file - */ - onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; - } - - function convertConfigFileToObject(sourceFile: JsonSourceFile, errors: Push, reportOptionsErrors: boolean, optionsIterator: JsonConversionNotifier | undefined): any { - const rootExpression: Expression | undefined = sourceFile.statements[0]?.expression; - const knownRootOptions = reportOptionsErrors ? getTsconfigRootOptionsMap() : undefined; - if (rootExpression && rootExpression.kind !== SyntaxKind.ObjectLiteralExpression) { - errors.push(createDiagnosticForNodeInSourceFile( - sourceFile, - rootExpression, - Diagnostics.The_root_value_of_a_0_file_must_be_an_object, - getBaseFileName(sourceFile.fileName) === "jsconfig.json" ? "jsconfig.json" : "tsconfig.json" - )); - // Last-ditch error recovery. Somewhat useful because the JSON parser will recover from some parse errors by - // synthesizing a top-level array literal expression. There's a reasonable chance the first element of that - // array is a well-formed configuration object, made into an array element by stray characters. - if (isArrayLiteralExpression(rootExpression)) { - const firstObject = find(rootExpression.elements, isObjectLiteralExpression); - if (firstObject) { - return convertToObjectWorker(sourceFile, firstObject, errors, /*returnValue*/ true, knownRootOptions, optionsIterator); - } - } - return {}; - } - return convertToObjectWorker(sourceFile, rootExpression, errors, /*returnValue*/ true, knownRootOptions, optionsIterator); + category: Diagnostics.File_Management, + defaultValueDescription: Diagnostics.node_modules_bower_components_jspm_packages_plus_the_value_of_outDir_if_one_is_specified + }, + compileOnSaveCommandLineOption + ]) + }; } + return _tsconfigRootOptions; +} +/*@internal*/ +interface JsonConversionNotifier { /** - * Convert the json syntax tree into the json value + * Notifies parent option object is being set with the optionKey and a valid optionValue + * Currently it notifies only if there is element with type object (parentOption) and + * has element's option declarations map associated with it + * @param parentOption parent option name in which the option and value are being set + * @param option option declaration which is being set with the value + * @param value value of the option */ - export function convertToObject(sourceFile: JsonSourceFile, errors: Push): any { - return convertToObjectWorker(sourceFile, sourceFile.statements[0]?.expression, errors, /*returnValue*/ true, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); - } - + onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue): void; /** - * Convert the json syntax tree into the json value and report errors - * This returns the json value (apart from checking errors) only if returnValue provided is true. - * Otherwise it just checks the errors and returns undefined + * Notify when valid root key value option is being set + * @param key option key + * @param keyNode node corresponding to node in the source file + * @param value computed value of the key + * @param ValueNode node corresponding to value in the source file */ - /*@internal*/ - export function convertToObjectWorker( - sourceFile: JsonSourceFile, - rootExpression: Expression | undefined, - errors: Push, - returnValue: boolean, - knownRootOptions: CommandLineOption | undefined, - jsonConversionNotifier: JsonConversionNotifier | undefined): any { - if (!rootExpression) { - return returnValue ? {} : undefined; - } - - return convertPropertyValueToJson(rootExpression, knownRootOptions); - - function isRootOptionMap(knownOptions: ESMap | undefined) { - return knownRootOptions && (knownRootOptions as TsConfigOnlyOption).elementOptions === knownOptions; - } - - function convertObjectLiteralExpressionToJson( - node: ObjectLiteralExpression, - knownOptions: ESMap | undefined, - extraKeyDiagnostics: DidYouMeanOptionsDiagnostics | undefined, - parentOption: string | undefined - ): any { - const result: any = returnValue ? {} : undefined; - for (const element of node.properties) { - if (element.kind !== SyntaxKind.PropertyAssignment) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element, Diagnostics.Property_assignment_expected)); - continue; - } + onSetValidOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; + /** + * Notify when unknown root key value option is being set + * @param key option key + * @param keyNode node corresponding to node in the source file + * @param value computed value of the key + * @param ValueNode node corresponding to value in the source file + */ + onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; +} + +function convertConfigFileToObject(sourceFile: JsonSourceFile, errors: Push, reportOptionsErrors: boolean, optionsIterator: JsonConversionNotifier | undefined): any { + const rootExpression: Expression | undefined = sourceFile.statements[0]?.expression; + const knownRootOptions = reportOptionsErrors ? getTsconfigRootOptionsMap() : undefined; + if (rootExpression && rootExpression.kind !== SyntaxKind.ObjectLiteralExpression) { + errors.push(createDiagnosticForNodeInSourceFile( + sourceFile, + rootExpression, + Diagnostics.The_root_value_of_a_0_file_must_be_an_object, + getBaseFileName(sourceFile.fileName) === "jsconfig.json" ? "jsconfig.json" : "tsconfig.json" + )); + // Last-ditch error recovery. Somewhat useful because the JSON parser will recover from some parse errors by + // synthesizing a top-level array literal expression. There's a reasonable chance the first element of that + // array is a well-formed configuration object, made into an array element by stray characters. + if (isArrayLiteralExpression(rootExpression)) { + const firstObject = find(rootExpression.elements, isObjectLiteralExpression); + if (firstObject) { + return convertToObjectWorker(sourceFile, firstObject, errors, /*returnValue*/ true, knownRootOptions, optionsIterator); + } + } + return {}; + } + return convertToObjectWorker(sourceFile, rootExpression, errors, /*returnValue*/ true, knownRootOptions, optionsIterator); +} - if (element.questionToken) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.questionToken, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")); +/** + * Convert the json syntax tree into the json value + */ +export function convertToObject(sourceFile: JsonSourceFile, errors: Push): any { + return convertToObjectWorker(sourceFile, sourceFile.statements[0]?.expression, errors, /*returnValue*/ true, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); +} + +/** + * Convert the json syntax tree into the json value and report errors + * This returns the json value (apart from checking errors) only if returnValue provided is true. + * Otherwise it just checks the errors and returns undefined + */ +/*@internal*/ +export function convertToObjectWorker( + sourceFile: JsonSourceFile, + rootExpression: Expression | undefined, + errors: Push, + returnValue: boolean, + knownRootOptions: CommandLineOption | undefined, + jsonConversionNotifier: JsonConversionNotifier | undefined): any { + if (!rootExpression) { + return returnValue ? {} : undefined; + } + + return convertPropertyValueToJson(rootExpression, knownRootOptions); + + function isRootOptionMap(knownOptions: ESMap | undefined) { + return knownRootOptions && (knownRootOptions as TsConfigOnlyOption).elementOptions === knownOptions; + } + + function convertObjectLiteralExpressionToJson( + node: ObjectLiteralExpression, + knownOptions: ESMap | undefined, + extraKeyDiagnostics: DidYouMeanOptionsDiagnostics | undefined, + parentOption: string | undefined + ): any { + const result: any = returnValue ? {} : undefined; + for (const element of node.properties) { + if (element.kind !== SyntaxKind.PropertyAssignment) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element, Diagnostics.Property_assignment_expected)); + continue; + } + + if (element.questionToken) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.questionToken, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")); + } + if (!isDoubleQuotedString(element.name)) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, Diagnostics.String_literal_with_double_quotes_expected)); + } + + const textOfKey = isComputedNonLiteralName(element.name) ? undefined : getTextOfPropertyName(element.name); + const keyText = textOfKey && unescapeLeadingUnderscores(textOfKey); + const option = keyText && knownOptions ? knownOptions.get(keyText) : undefined; + if (keyText && extraKeyDiagnostics && !option) { + if (knownOptions) { + errors.push(createUnknownOptionError( + keyText, + extraKeyDiagnostics, + (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, element.name, message, arg0, arg1) + )); } - if (!isDoubleQuotedString(element.name)) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, Diagnostics.String_literal_with_double_quotes_expected)); + else { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnostics.unknownOptionDiagnostic, keyText)); } - - const textOfKey = isComputedNonLiteralName(element.name) ? undefined : getTextOfPropertyName(element.name); - const keyText = textOfKey && unescapeLeadingUnderscores(textOfKey); - const option = keyText && knownOptions ? knownOptions.get(keyText) : undefined; - if (keyText && extraKeyDiagnostics && !option) { - if (knownOptions) { - errors.push(createUnknownOptionError( - keyText, - extraKeyDiagnostics, - (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, element.name, message, arg0, arg1) - )); - } - else { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnostics.unknownOptionDiagnostic, keyText)); - } + } + const value = convertPropertyValueToJson(element.initializer, option); + if (typeof keyText !== "undefined") { + if (returnValue) { + result[keyText] = value; } - const value = convertPropertyValueToJson(element.initializer, option); - if (typeof keyText !== "undefined") { - if (returnValue) { - result[keyText] = value; + // Notify key value set, if user asked for it + if (jsonConversionNotifier && + // Current callbacks are only on known parent option or if we are setting values in the root + (parentOption || isRootOptionMap(knownOptions))) { + const isValidOptionValue = isCompilerOptionsValue(option, value); + if (parentOption) { + if (isValidOptionValue) { + // Notify option set in the parent if its a valid option value + jsonConversionNotifier.onSetValidOptionKeyValueInParent(parentOption, option!, value); + } } - // Notify key value set, if user asked for it - if (jsonConversionNotifier && - // Current callbacks are only on known parent option or if we are setting values in the root - (parentOption || isRootOptionMap(knownOptions))) { - const isValidOptionValue = isCompilerOptionsValue(option, value); - if (parentOption) { - if (isValidOptionValue) { - // Notify option set in the parent if its a valid option value - jsonConversionNotifier.onSetValidOptionKeyValueInParent(parentOption, option!, value); - } + else if (isRootOptionMap(knownOptions)) { + if (isValidOptionValue) { + // Notify about the valid root key value being set + jsonConversionNotifier.onSetValidOptionKeyValueInRoot(keyText, element.name, value, element.initializer); } - else if (isRootOptionMap(knownOptions)) { - if (isValidOptionValue) { - // Notify about the valid root key value being set - jsonConversionNotifier.onSetValidOptionKeyValueInRoot(keyText, element.name, value, element.initializer); - } - else if (!option) { - // Notify about the unknown root key value being set - jsonConversionNotifier.onSetUnknownOptionKeyValueInRoot(keyText, element.name, value, element.initializer); - } + else if (!option) { + // Notify about the unknown root key value being set + jsonConversionNotifier.onSetUnknownOptionKeyValueInRoot(keyText, element.name, value, element.initializer); } } } } - return result; } + return result; + } - function convertArrayLiteralExpressionToJson( - elements: NodeArray, - elementOption: CommandLineOption | undefined - ) { - if (!returnValue) { - elements.forEach(element => convertPropertyValueToJson(element, elementOption)); - return undefined; - } - - // Filter out invalid values - return filter(elements.map(element => convertPropertyValueToJson(element, elementOption)), v => v !== undefined); + function convertArrayLiteralExpressionToJson( + elements: NodeArray, + elementOption: CommandLineOption | undefined + ) { + if (!returnValue) { + elements.forEach(element => convertPropertyValueToJson(element, elementOption)); + return undefined; } - function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption | undefined): any { - let invalidReported: boolean | undefined; - switch (valueExpression.kind) { - case SyntaxKind.TrueKeyword: - reportInvalidOptionValue(option && option.type !== "boolean"); - return validateValue(/*value*/ true); - - case SyntaxKind.FalseKeyword: - reportInvalidOptionValue(option && option.type !== "boolean"); - return validateValue(/*value*/ false); + // Filter out invalid values + return filter(elements.map(element => convertPropertyValueToJson(element, elementOption)), v => v !== undefined); + } - case SyntaxKind.NullKeyword: - reportInvalidOptionValue(option && option.name === "extends"); // "extends" is the only option we don't allow null/undefined for - return validateValue(/*value*/ null); // eslint-disable-line no-null/no-null + function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption | undefined): any { + let invalidReported: boolean | undefined; + switch (valueExpression.kind) { + case SyntaxKind.TrueKeyword: + reportInvalidOptionValue(option && option.type !== "boolean"); + return validateValue(/*value*/ true); - case SyntaxKind.StringLiteral: - if (!isDoubleQuotedString(valueExpression)) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected)); - } - reportInvalidOptionValue(option && (isString(option.type) && option.type !== "string")); - const text = (valueExpression as StringLiteral).text; - if (option && !isString(option.type)) { - const customOption = option as CommandLineOptionOfCustomType; - // Validate custom option type - if (!customOption.type.has(text.toLowerCase())) { - errors.push( - createDiagnosticForInvalidCustomType( - customOption, - (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, message, arg0, arg1) - ) - ); - invalidReported = true; - } - } - return validateValue(text); + case SyntaxKind.FalseKeyword: + reportInvalidOptionValue(option && option.type !== "boolean"); + return validateValue(/*value*/ false); - case SyntaxKind.NumericLiteral: - reportInvalidOptionValue(option && option.type !== "number"); - return validateValue(Number((valueExpression as NumericLiteral).text)); + case SyntaxKind.NullKeyword: + reportInvalidOptionValue(option && option.name === "extends"); // "extends" is the only option we don't allow null/undefined for + return validateValue(/*value*/ null); // eslint-disable-line no-null/no-null - case SyntaxKind.PrefixUnaryExpression: - if ((valueExpression as PrefixUnaryExpression).operator !== SyntaxKind.MinusToken || (valueExpression as PrefixUnaryExpression).operand.kind !== SyntaxKind.NumericLiteral) { - break; // not valid JSON syntax - } - reportInvalidOptionValue(option && option.type !== "number"); - return validateValue(-Number(((valueExpression as PrefixUnaryExpression).operand as NumericLiteral).text)); - - case SyntaxKind.ObjectLiteralExpression: - reportInvalidOptionValue(option && option.type !== "object"); - const objectLiteralExpression = valueExpression as ObjectLiteralExpression; - - // Currently having element option declaration in the tsconfig with type "object" - // determines if it needs onSetValidOptionKeyValueInParent callback or not - // At moment there are only "compilerOptions", "typeAcquisition" and "typingOptions" - // that satifies it and need it to modify options set in them (for normalizing file paths) - // vs what we set in the json - // If need arises, we can modify this interface and callbacks as needed - if (option) { - const { elementOptions, extraKeyDiagnostics, name: optionName } = option as TsConfigOnlyOption; - return validateValue(convertObjectLiteralExpressionToJson(objectLiteralExpression, - elementOptions, extraKeyDiagnostics, optionName)); - } - else { - return validateValue(convertObjectLiteralExpressionToJson( - objectLiteralExpression, /* knownOptions*/ undefined, - /*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined)); + case SyntaxKind.StringLiteral: + if (!isDoubleQuotedString(valueExpression)) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected)); + } + reportInvalidOptionValue(option && (isString(option.type) && option.type !== "string")); + const text = (valueExpression as StringLiteral).text; + if (option && !isString(option.type)) { + const customOption = option as CommandLineOptionOfCustomType; + // Validate custom option type + if (!customOption.type.has(text.toLowerCase())) { + errors.push( + createDiagnosticForInvalidCustomType( + customOption, + (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, message, arg0, arg1) + ) + ); + invalidReported = true; } + } + return validateValue(text); - case SyntaxKind.ArrayLiteralExpression: - reportInvalidOptionValue(option && option.type !== "list"); - return validateValue(convertArrayLiteralExpressionToJson( - (valueExpression as ArrayLiteralExpression).elements, - option && (option as CommandLineOptionOfListType).element)); - } + case SyntaxKind.NumericLiteral: + reportInvalidOptionValue(option && option.type !== "number"); + return validateValue(Number((valueExpression as NumericLiteral).text)); - // Not in expected format - if (option) { - reportInvalidOptionValue(/*isError*/ true); - } - else { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Property_value_can_only_be_string_literal_numeric_literal_true_false_null_object_literal_or_array_literal)); - } + case SyntaxKind.PrefixUnaryExpression: + if ((valueExpression as PrefixUnaryExpression).operator !== SyntaxKind.MinusToken || (valueExpression as PrefixUnaryExpression).operand.kind !== SyntaxKind.NumericLiteral) { + break; // not valid JSON syntax + } + reportInvalidOptionValue(option && option.type !== "number"); + return validateValue(-Number(((valueExpression as PrefixUnaryExpression).operand as NumericLiteral).text)); + + case SyntaxKind.ObjectLiteralExpression: + reportInvalidOptionValue(option && option.type !== "object"); + const objectLiteralExpression = valueExpression as ObjectLiteralExpression; + + // Currently having element option declaration in the tsconfig with type "object" + // determines if it needs onSetValidOptionKeyValueInParent callback or not + // At moment there are only "compilerOptions", "typeAcquisition" and "typingOptions" + // that satifies it and need it to modify options set in them (for normalizing file paths) + // vs what we set in the json + // If need arises, we can modify this interface and callbacks as needed + if (option) { + const { elementOptions, extraKeyDiagnostics, name: optionName } = option as TsConfigOnlyOption; + return validateValue(convertObjectLiteralExpressionToJson(objectLiteralExpression, + elementOptions, extraKeyDiagnostics, optionName)); + } + else { + return validateValue(convertObjectLiteralExpressionToJson( + objectLiteralExpression, /* knownOptions*/ undefined, + /*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined)); + } - return undefined; + case SyntaxKind.ArrayLiteralExpression: + reportInvalidOptionValue(option && option.type !== "list"); + return validateValue(convertArrayLiteralExpressionToJson( + (valueExpression as ArrayLiteralExpression).elements, + option && (option as CommandLineOptionOfListType).element)); + } - function validateValue(value: CompilerOptionsValue) { - if (!invalidReported) { - const diagnostic = option?.extraValidation?.(value); - if (diagnostic) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, ...diagnostic)); - return undefined; - } - } - return value; - } + // Not in expected format + if (option) { + reportInvalidOptionValue(/*isError*/ true); + } + else { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Property_value_can_only_be_string_literal_numeric_literal_true_false_null_object_literal_or_array_literal)); + } - function reportInvalidOptionValue(isError: boolean | undefined) { - if (isError) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option!.name, getCompilerOptionValueTypeString(option!))); - invalidReported = true; + return undefined; + + function validateValue(value: CompilerOptionsValue) { + if (!invalidReported) { + const diagnostic = option?.extraValidation?.(value); + if (diagnostic) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, ...diagnostic)); + return undefined; } } + return value; } - function isDoubleQuotedString(node: Node): boolean { - return isStringLiteral(node) && isStringDoubleQuoted(node, sourceFile); + function reportInvalidOptionValue(isError: boolean | undefined) { + if (isError) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option!.name, getCompilerOptionValueTypeString(option!))); + invalidReported = true; + } } } - function getCompilerOptionValueTypeString(option: CommandLineOption) { - return option.type === "list" ? - "Array" : - isString(option.type) ? option.type : "string"; + function isDoubleQuotedString(node: Node): boolean { + return isStringLiteral(node) && isStringDoubleQuoted(node, sourceFile); } +} - function isCompilerOptionsValue(option: CommandLineOption | undefined, value: any): value is CompilerOptionsValue { - if (option) { - if (isNullOrUndefined(value)) return true; // All options are undefinable/nullable - if (option.type === "list") { - return isArray(value); - } - const expectedType = isString(option.type) ? option.type : "string"; - return typeof value === expectedType; +function getCompilerOptionValueTypeString(option: CommandLineOption) { + return option.type === "list" ? + "Array" : + isString(option.type) ? option.type : "string"; +} + +function isCompilerOptionsValue(option: CommandLineOption | undefined, value: any): value is CompilerOptionsValue { + if (option) { + if (isNullOrUndefined(value)) return true; // All options are undefinable/nullable + if (option.type === "list") { + return isArray(value); } - return false; + const expectedType = isString(option.type) ? option.type : "string"; + return typeof value === expectedType; } + return false; +} - /** @internal */ - export interface TSConfig { - compilerOptions: CompilerOptions; - compileOnSave: boolean | undefined; - exclude?: readonly string[]; - files: readonly string[] | undefined; - include?: readonly string[]; - references: readonly ProjectReference[] | undefined; - } +/** @internal */ +export interface TSConfig { + compilerOptions: CompilerOptions; + compileOnSave: boolean | undefined; + exclude?: readonly string[]; + files: readonly string[] | undefined; + include?: readonly string[]; + references: readonly ProjectReference[] | undefined; +} - /** @internal */ - export interface ConvertToTSConfigHost { - getCurrentDirectory(): string; - useCaseSensitiveFileNames: boolean; - } +/** @internal */ +export interface ConvertToTSConfigHost { + getCurrentDirectory(): string; + useCaseSensitiveFileNames: boolean; +} - /** - * Generate an uncommented, complete tsconfig for use with "--showConfig" - * @param configParseResult options to be generated into tsconfig.json - * @param configFileName name of the parsed config file - output paths will be generated relative to this - * @param host provides current directory and case sensitivity services - */ - /** @internal */ - export function convertToTSConfig(configParseResult: ParsedCommandLine, configFileName: string, host: ConvertToTSConfigHost): TSConfig { - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - const files = map( - filter( - configParseResult.fileNames, - !configParseResult.options.configFile?.configFileSpecs?.validatedIncludeSpecs ? returnTrue : matchesSpecs( - configFileName, - configParseResult.options.configFile.configFileSpecs.validatedIncludeSpecs, - configParseResult.options.configFile.configFileSpecs.validatedExcludeSpecs, - host, - ) - ), - f => getRelativePathFromFile(getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), getNormalizedAbsolutePath(f, host.getCurrentDirectory()), getCanonicalFileName) - ); - const optionMap = serializeCompilerOptions(configParseResult.options, { configFilePath: getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), useCaseSensitiveFileNames: host.useCaseSensitiveFileNames }); - const watchOptionMap = configParseResult.watchOptions && serializeWatchOptions(configParseResult.watchOptions); - const config = { - compilerOptions: { - ...optionMapToObject(optionMap), - showConfig: undefined, - configFile: undefined, - configFilePath: undefined, - help: undefined, - init: undefined, - listFiles: undefined, - listEmittedFiles: undefined, - project: undefined, - build: undefined, - version: undefined, - }, - watchOptions: watchOptionMap && optionMapToObject(watchOptionMap), - references: map(configParseResult.projectReferences, r => ({ ...r, path: r.originalPath ? r.originalPath : "", originalPath: undefined })), - files: length(files) ? files : undefined, - ...(configParseResult.options.configFile?.configFileSpecs ? { - include: filterSameAsDefaultInclude(configParseResult.options.configFile.configFileSpecs.validatedIncludeSpecs), - exclude: configParseResult.options.configFile.configFileSpecs.validatedExcludeSpecs - } : {}), - compileOnSave: !!configParseResult.compileOnSave ? true : undefined - }; - return config; - } +/** + * Generate an uncommented, complete tsconfig for use with "--showConfig" + * @param configParseResult options to be generated into tsconfig.json + * @param configFileName name of the parsed config file - output paths will be generated relative to this + * @param host provides current directory and case sensitivity services + */ +/** @internal */ +export function convertToTSConfig(configParseResult: ParsedCommandLine, configFileName: string, host: ConvertToTSConfigHost): TSConfig { + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + const files = map( + filter( + configParseResult.fileNames, + !configParseResult.options.configFile?.configFileSpecs?.validatedIncludeSpecs ? returnTrue : matchesSpecs( + configFileName, + configParseResult.options.configFile.configFileSpecs.validatedIncludeSpecs, + configParseResult.options.configFile.configFileSpecs.validatedExcludeSpecs, + host, + ) + ), + f => getRelativePathFromFile(getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), getNormalizedAbsolutePath(f, host.getCurrentDirectory()), getCanonicalFileName) + ); + const optionMap = serializeCompilerOptions(configParseResult.options, { configFilePath: getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), useCaseSensitiveFileNames: host.useCaseSensitiveFileNames }); + const watchOptionMap = configParseResult.watchOptions && serializeWatchOptions(configParseResult.watchOptions); + const config = { + compilerOptions: { + ...optionMapToObject(optionMap), + showConfig: undefined, + configFile: undefined, + configFilePath: undefined, + help: undefined, + init: undefined, + listFiles: undefined, + listEmittedFiles: undefined, + project: undefined, + build: undefined, + version: undefined, + }, + watchOptions: watchOptionMap && optionMapToObject(watchOptionMap), + references: map(configParseResult.projectReferences, r => ({ ...r, path: r.originalPath ? r.originalPath : "", originalPath: undefined })), + files: length(files) ? files : undefined, + ...(configParseResult.options.configFile?.configFileSpecs ? { + include: filterSameAsDefaultInclude(configParseResult.options.configFile.configFileSpecs.validatedIncludeSpecs), + exclude: configParseResult.options.configFile.configFileSpecs.validatedExcludeSpecs + } : {}), + compileOnSave: !!configParseResult.compileOnSave ? true : undefined + }; + return config; +} - function optionMapToObject(optionMap: ESMap): object { - return { - ...arrayFrom(optionMap.entries()).reduce((prev, cur) => ({ ...prev, [cur[0]]: cur[1] }), {}), - }; - } +function optionMapToObject(optionMap: ESMap): object { + return { + ...arrayFrom(optionMap.entries()).reduce((prev, cur) => ({ ...prev, [cur[0]]: cur[1] }), {}), + }; +} - function filterSameAsDefaultInclude(specs: readonly string[] | undefined) { - if (!length(specs)) return undefined; - if (length(specs) !== 1) return specs; - if (specs![0] === "**/*") return undefined; - return specs; - } +function filterSameAsDefaultInclude(specs: readonly string[] | undefined) { + if (!length(specs)) return undefined; + if (length(specs) !== 1) return specs; + if (specs![0] === "**/*") return undefined; + return specs; +} - function matchesSpecs(path: string, includeSpecs: readonly string[] | undefined, excludeSpecs: readonly string[] | undefined, host: ConvertToTSConfigHost): (path: string) => boolean { - if (!includeSpecs) return returnTrue; - const patterns = getFileMatcherPatterns(path, excludeSpecs, includeSpecs, host.useCaseSensitiveFileNames, host.getCurrentDirectory()); - const excludeRe = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, host.useCaseSensitiveFileNames); - const includeRe = patterns.includeFilePattern && getRegexFromPattern(patterns.includeFilePattern, host.useCaseSensitiveFileNames); - if (includeRe) { - if (excludeRe) { - return path => !(includeRe.test(path) && !excludeRe.test(path)); - } - return path => !includeRe.test(path); - } +function matchesSpecs(path: string, includeSpecs: readonly string[] | undefined, excludeSpecs: readonly string[] | undefined, host: ConvertToTSConfigHost): (path: string) => boolean { + if (!includeSpecs) return returnTrue; + const patterns = getFileMatcherPatterns(path, excludeSpecs, includeSpecs, host.useCaseSensitiveFileNames, host.getCurrentDirectory()); + const excludeRe = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, host.useCaseSensitiveFileNames); + const includeRe = patterns.includeFilePattern && getRegexFromPattern(patterns.includeFilePattern, host.useCaseSensitiveFileNames); + if (includeRe) { if (excludeRe) { - return path => excludeRe.test(path); + return path => !(includeRe.test(path) && !excludeRe.test(path)); } - return returnTrue; + return path => !includeRe.test(path); } - - function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): ESMap | undefined { - if (optionDefinition.type === "string" || optionDefinition.type === "number" || optionDefinition.type === "boolean" || optionDefinition.type === "object") { - // this is of a type CommandLineOptionOfPrimitiveType - return undefined; - } - else if (optionDefinition.type === "list") { - return getCustomTypeMapOfCommandLineOption(optionDefinition.element); - } - else { - return optionDefinition.type; - } + if (excludeRe) { + return path => excludeRe.test(path); } + return returnTrue; +} - function getNameOfCompilerOptionValue(value: CompilerOptionsValue, customTypeMap: ESMap): string | undefined { - // There is a typeMap associated with this command-line option so use it to map value back to its name - return forEachEntry(customTypeMap, (mapValue, key) => { - if (mapValue === value) { - return key; - } - }); +function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): ESMap | undefined { + if (optionDefinition.type === "string" || optionDefinition.type === "number" || optionDefinition.type === "boolean" || optionDefinition.type === "object") { + // this is of a type CommandLineOptionOfPrimitiveType + return undefined; } - - function serializeCompilerOptions( - options: CompilerOptions, - pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean } - ): ESMap { - return serializeOptionBaseObject(options, getOptionsNameMap(), pathOptions); + else if (optionDefinition.type === "list") { + return getCustomTypeMapOfCommandLineOption(optionDefinition.element); } - - function serializeWatchOptions(options: WatchOptions) { - return serializeOptionBaseObject(options, getWatchOptionsNameMap()); + else { + return optionDefinition.type; } +} + +function getNameOfCompilerOptionValue(value: CompilerOptionsValue, customTypeMap: ESMap): string | undefined { + // There is a typeMap associated with this command-line option so use it to map value back to its name + return forEachEntry(customTypeMap, (mapValue, key) => { + if (mapValue === value) { + return key; + } + }); +} - function serializeOptionBaseObject( - options: OptionsBase, - { optionsNameMap }: OptionsNameMap, - pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean } - ): ESMap { - const result = new Map(); - const getCanonicalFileName = pathOptions && createGetCanonicalFileName(pathOptions.useCaseSensitiveFileNames); +function serializeCompilerOptions( + options: CompilerOptions, + pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean } +): ESMap { + return serializeOptionBaseObject(options, getOptionsNameMap(), pathOptions); +} + +function serializeWatchOptions(options: WatchOptions) { + return serializeOptionBaseObject(options, getWatchOptionsNameMap()); +} - for (const name in options) { - if (hasProperty(options, name)) { - // tsconfig only options cannot be specified via command line, - // so we can assume that only types that can appear here string | number | boolean - if (optionsNameMap.has(name) && (optionsNameMap.get(name)!.category === Diagnostics.Command_line_Options || optionsNameMap.get(name)!.category === Diagnostics.Output_Formatting)) { - continue; +function serializeOptionBaseObject( + options: OptionsBase, + { optionsNameMap }: OptionsNameMap, + pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean } +): ESMap { + const result = new Map(); + const getCanonicalFileName = pathOptions && createGetCanonicalFileName(pathOptions.useCaseSensitiveFileNames); + + for (const name in options) { + if (hasProperty(options, name)) { + // tsconfig only options cannot be specified via command line, + // so we can assume that only types that can appear here string | number | boolean + if (optionsNameMap.has(name) && (optionsNameMap.get(name)!.category === Diagnostics.Command_line_Options || optionsNameMap.get(name)!.category === Diagnostics.Output_Formatting)) { + continue; + } + const value = options[name] as CompilerOptionsValue; + const optionDefinition = optionsNameMap.get(name.toLowerCase()); + if (optionDefinition) { + const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition); + if (!customTypeMap) { + // There is no map associated with this compiler option then use the value as-is + // This is the case if the value is expect to be string, number, boolean or list of string + if (pathOptions && optionDefinition.isFilePath) { + result.set(name, getRelativePathFromFile(pathOptions.configFilePath, getNormalizedAbsolutePath(value as string, getDirectoryPath(pathOptions.configFilePath)), getCanonicalFileName!)); + } + else { + result.set(name, value); + } } - const value = options[name] as CompilerOptionsValue; - const optionDefinition = optionsNameMap.get(name.toLowerCase()); - if (optionDefinition) { - const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition); - if (!customTypeMap) { - // There is no map associated with this compiler option then use the value as-is - // This is the case if the value is expect to be string, number, boolean or list of string - if (pathOptions && optionDefinition.isFilePath) { - result.set(name, getRelativePathFromFile(pathOptions.configFilePath, getNormalizedAbsolutePath(value as string, getDirectoryPath(pathOptions.configFilePath)), getCanonicalFileName!)); - } - else { - result.set(name, value); - } + else { + if (optionDefinition.type === "list") { + result.set(name, (value as readonly (string | number)[]).map(element => getNameOfCompilerOptionValue(element, customTypeMap)!)); // TODO: GH#18217 } else { - if (optionDefinition.type === "list") { - result.set(name, (value as readonly (string | number)[]).map(element => getNameOfCompilerOptionValue(element, customTypeMap)!)); // TODO: GH#18217 - } - else { - // There is a typeMap associated with this command-line option so use it to map value back to its name - result.set(name, getNameOfCompilerOptionValue(value, customTypeMap)); - } + // There is a typeMap associated with this command-line option so use it to map value back to its name + result.set(name, getNameOfCompilerOptionValue(value, customTypeMap)); } } } } - return result; } + return result; +} - /** - * Generate a list of the compiler options whose value is not the default. - * @param options compilerOptions to be evaluated. - /** @internal */ - export function getCompilerOptionsDiffValue(options: CompilerOptions, newLine: string): string { - const compilerOptionsMap = getSerializedCompilerOption(options); - return getOverwrittenDefaultOptions(); - - function makePadding(paddingLength: number): string { - return Array(paddingLength + 1).join(" "); - } - - function getOverwrittenDefaultOptions() { - const result: string[] = []; - const tab = makePadding(2); - commandOptionsWithoutBuild.forEach(cmd => { - if (!compilerOptionsMap.has(cmd.name)) { - return; - } - - const newValue = compilerOptionsMap.get(cmd.name); - const defaultValue = getDefaultValueForOption(cmd); - if (newValue !== defaultValue) { - result.push(`${tab}${cmd.name}: ${newValue}`); - } - else if (hasProperty(defaultInitCompilerOptions, cmd.name)) { - result.push(`${tab}${cmd.name}: ${defaultValue}`); - } - }); - return result.join(newLine) + newLine; - } - } +/** + * Generate a list of the compiler options whose value is not the default. + * @param options compilerOptions to be evaluated. +/** @internal */ +export function getCompilerOptionsDiffValue(options: CompilerOptions, newLine: string): string { + const compilerOptionsMap = getSerializedCompilerOption(options); + return getOverwrittenDefaultOptions(); - /** - * Get the compiler options to be written into the tsconfig.json. - * @param options commandlineOptions to be included in the compileOptions. - */ - function getSerializedCompilerOption(options: CompilerOptions): ESMap { - const compilerOptions = extend(options, defaultInitCompilerOptions); - return serializeCompilerOptions(compilerOptions); + function makePadding(paddingLength: number): string { + return Array(paddingLength + 1).join(" "); } - /** - * Generate tsconfig configuration when running command line "--init" - * @param options commandlineOptions to be generated into tsconfig.json - * @param fileNames array of filenames to be generated into tsconfig.json - */ - /* @internal */ - export function generateTSConfig(options: CompilerOptions, fileNames: readonly string[], newLine: string): string { - const compilerOptionsMap = getSerializedCompilerOption(options); - return writeConfigurations(); - - function makePadding(paddingLength: number): string { - return Array(paddingLength + 1).join(" "); - } - - function isAllowedOptionForOutput({ category, name, isCommandLineOnly }: CommandLineOption): boolean { - // Skip options which do not have a category or have categories which are more niche - const categoriesToSkip = [Diagnostics.Command_line_Options, Diagnostics.Editor_Support, Diagnostics.Compiler_Diagnostics, Diagnostics.Backwards_Compatibility, Diagnostics.Watch_and_Build_Modes, Diagnostics.Output_Formatting]; - return !isCommandLineOnly && category !== undefined && (!categoriesToSkip.includes(category) || compilerOptionsMap.has(name)); - } - - function writeConfigurations() { - // Filter applicable options to place in the file - const categorizedOptions = createMultiMap(); - for (const option of optionDeclarations) { - const { category } = option; - if (isAllowedOptionForOutput(option)) { - categorizedOptions.add(getLocaleSpecificMessage(category!), option); - } + function getOverwrittenDefaultOptions() { + const result: string[] = []; + const tab = makePadding(2); + commandOptionsWithoutBuild.forEach(cmd => { + if (!compilerOptionsMap.has(cmd.name)) { + return; } - // Serialize all options and their descriptions - let marginLength = 0; - let seenKnownKeys = 0; - const entries: { value: string, description?: string }[] = []; - categorizedOptions.forEach((options, category) => { - if (entries.length !== 0) { - entries.push({ value: "" }); - } - entries.push({ value: `/* ${category} */` }); - for (const option of options) { - let optionName; - if (compilerOptionsMap.has(option.name)) { - optionName = `"${option.name}": ${JSON.stringify(compilerOptionsMap.get(option.name))}${(seenKnownKeys += 1) === compilerOptionsMap.size ? "" : ","}`; - } - else { - optionName = `// "${option.name}": ${JSON.stringify(getDefaultValueForOption(option))},`; - } - entries.push({ - value: optionName, - description: `/* ${option.description && getLocaleSpecificMessage(option.description) || option.name} */` - }); - marginLength = Math.max(optionName.length, marginLength); - } - }); - - // Write the output - const tab = makePadding(2); - const result: string[] = []; - result.push(`{`); - result.push(`${tab}"compilerOptions": {`); - result.push(`${tab}${tab}/* ${getLocaleSpecificMessage(Diagnostics.Visit_https_Colon_Slash_Slashaka_ms_Slashtsconfig_json_to_read_more_about_this_file)} */`); - result.push(""); - // Print out each row, aligning all the descriptions on the same column. - for (const entry of entries) { - const { value, description = "" } = entry; - result.push(value && `${tab}${tab}${value}${description && (makePadding(marginLength - value.length + 2) + description)}`); - } - if (fileNames.length) { - result.push(`${tab}},`); - result.push(`${tab}"files": [`); - for (let i = 0; i < fileNames.length; i++) { - result.push(`${tab}${tab}${JSON.stringify(fileNames[i])}${i === fileNames.length - 1 ? "" : ","}`); - } - result.push(`${tab}]`); + const newValue = compilerOptionsMap.get(cmd.name); + const defaultValue = getDefaultValueForOption(cmd); + if (newValue !== defaultValue) { + result.push(`${tab}${cmd.name}: ${newValue}`); } - else { - result.push(`${tab}}`); + else if (hasProperty(defaultInitCompilerOptions, cmd.name)) { + result.push(`${tab}${cmd.name}: ${defaultValue}`); } - result.push(`}`); - - return result.join(newLine) + newLine; - } + }); + return result.join(newLine) + newLine; } +} - /* @internal */ - export function convertToOptionsWithAbsolutePaths(options: CompilerOptions, toAbsolutePath: (path: string) => string) { - const result: CompilerOptions = {}; - const optionsNameMap = getOptionsNameMap().optionsNameMap; - - for (const name in options) { - if (hasProperty(options, name)) { - result[name] = convertToOptionValueWithAbsolutePaths( - optionsNameMap.get(name.toLowerCase()), - options[name] as CompilerOptionsValue, - toAbsolutePath - ); +/** + * Get the compiler options to be written into the tsconfig.json. + * @param options commandlineOptions to be included in the compileOptions. + */ +function getSerializedCompilerOption(options: CompilerOptions): ESMap { + const compilerOptions = extend(options, defaultInitCompilerOptions); + return serializeCompilerOptions(compilerOptions); +} +/** + * Generate tsconfig configuration when running command line "--init" + * @param options commandlineOptions to be generated into tsconfig.json + * @param fileNames array of filenames to be generated into tsconfig.json + */ +/* @internal */ +export function generateTSConfig(options: CompilerOptions, fileNames: readonly string[], newLine: string): string { + const compilerOptionsMap = getSerializedCompilerOption(options); + return writeConfigurations(); + + function makePadding(paddingLength: number): string { + return Array(paddingLength + 1).join(" "); + } + + function isAllowedOptionForOutput({ category, name, isCommandLineOnly }: CommandLineOption): boolean { + // Skip options which do not have a category or have categories which are more niche + const categoriesToSkip = [Diagnostics.Command_line_Options, Diagnostics.Editor_Support, Diagnostics.Compiler_Diagnostics, Diagnostics.Backwards_Compatibility, Diagnostics.Watch_and_Build_Modes, Diagnostics.Output_Formatting]; + return !isCommandLineOnly && category !== undefined && (!categoriesToSkip.includes(category) || compilerOptionsMap.has(name)); + } + + function writeConfigurations() { + // Filter applicable options to place in the file + const categorizedOptions = createMultiMap(); + for (const option of optionDeclarations) { + const { category } = option; + + if (isAllowedOptionForOutput(option)) { + categorizedOptions.add(getLocaleSpecificMessage(category!), option); + } + } + + // Serialize all options and their descriptions + let marginLength = 0; + let seenKnownKeys = 0; + const entries: { value: string, description?: string }[] = []; + categorizedOptions.forEach((options, category) => { + if (entries.length !== 0) { + entries.push({ value: "" }); + } + entries.push({ value: `/* ${category} */` }); + for (const option of options) { + let optionName; + if (compilerOptionsMap.has(option.name)) { + optionName = `"${option.name}": ${JSON.stringify(compilerOptionsMap.get(option.name))}${(seenKnownKeys += 1) === compilerOptionsMap.size ? "" : ","}`; + } + else { + optionName = `// "${option.name}": ${JSON.stringify(getDefaultValueForOption(option))},`; + } + entries.push({ + value: optionName, + description: `/* ${option.description && getLocaleSpecificMessage(option.description) || option.name} */` + }); + marginLength = Math.max(optionName.length, marginLength); } + }); + + // Write the output + const tab = makePadding(2); + const result: string[] = []; + result.push(`{`); + result.push(`${tab}"compilerOptions": {`); + result.push(`${tab}${tab}/* ${getLocaleSpecificMessage(Diagnostics.Visit_https_Colon_Slash_Slashaka_ms_Slashtsconfig_json_to_read_more_about_this_file)} */`); + result.push(""); + // Print out each row, aligning all the descriptions on the same column. + for (const entry of entries) { + const { value, description = "" } = entry; + result.push(value && `${tab}${tab}${value}${description && (makePadding(marginLength - value.length + 2) + description)}`); + } + if (fileNames.length) { + result.push(`${tab}},`); + result.push(`${tab}"files": [`); + for (let i = 0; i < fileNames.length; i++) { + result.push(`${tab}${tab}${JSON.stringify(fileNames[i])}${i === fileNames.length - 1 ? "" : ","}`); + } + result.push(`${tab}]`); } - if (result.configFilePath) { - result.configFilePath = toAbsolutePath(result.configFilePath); + else { + result.push(`${tab}}`); } - return result; - } + result.push(`}`); - function convertToOptionValueWithAbsolutePaths(option: CommandLineOption | undefined, value: CompilerOptionsValue, toAbsolutePath: (path: string) => string) { - if (option && !isNullOrUndefined(value)) { - if (option.type === "list") { - const values = value as readonly (string | number)[]; - if (option.element.isFilePath && values.length) { - return values.map(toAbsolutePath); - } - } - else if (option.isFilePath) { - return toAbsolutePath(value as string); - } - } - return value; + return result.join(newLine) + newLine; } +} - /** - * Parse the contents of a config file (tsconfig.json). - * @param json The contents of the config file to parse - * @param host Instance of ParseConfigHost used to enumerate files in folder. - * @param basePath A root directory to resolve relative path entries in the config - * file to. e.g. outDir - */ - export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map, existingWatchOptions?: WatchOptions): ParsedCommandLine { - return parseJsonConfigFileContentWorker(json, /*sourceFile*/ undefined, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); - } +/* @internal */ +export function convertToOptionsWithAbsolutePaths(options: CompilerOptions, toAbsolutePath: (path: string) => string) { + const result: CompilerOptions = {}; + const optionsNameMap = getOptionsNameMap().optionsNameMap; - /** - * Parse the contents of a config file (tsconfig.json). - * @param jsonNode The contents of the config file to parse - * @param host Instance of ParseConfigHost used to enumerate files in folder. - * @param basePath A root directory to resolve relative path entries in the config - * file to. e.g. outDir - */ - export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map, existingWatchOptions?: WatchOptions): ParsedCommandLine { - return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); + for (const name in options) { + if (hasProperty(options, name)) { + result[name] = convertToOptionValueWithAbsolutePaths( + optionsNameMap.get(name.toLowerCase()), + options[name] as CompilerOptionsValue, + toAbsolutePath + ); + } + } + if (result.configFilePath) { + result.configFilePath = toAbsolutePath(result.configFilePath); } + return result; +} - /*@internal*/ - export function setConfigFileInOptions(options: CompilerOptions, configFile: TsConfigSourceFile | undefined) { - if (configFile) { - Object.defineProperty(options, "configFile", { enumerable: false, writable: false, value: configFile }); +function convertToOptionValueWithAbsolutePaths(option: CommandLineOption | undefined, value: CompilerOptionsValue, toAbsolutePath: (path: string) => string) { + if (option && !isNullOrUndefined(value)) { + if (option.type === "list") { + const values = value as readonly (string | number)[]; + if (option.element.isFilePath && values.length) { + return values.map(toAbsolutePath); + } + } + else if (option.isFilePath) { + return toAbsolutePath(value as string); } } + return value; +} - function isNullOrUndefined(x: any): x is null | undefined { - return x === undefined || x === null; // eslint-disable-line no-null/no-null - } +/** + * Parse the contents of a config file (tsconfig.json). + * @param json The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + */ +export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map, existingWatchOptions?: WatchOptions): ParsedCommandLine { + return parseJsonConfigFileContentWorker(json, /*sourceFile*/ undefined, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); +} - function directoryOfCombinedPath(fileName: string, basePath: string) { - // Use the `getNormalizedAbsolutePath` function to avoid canonicalizing the path, as it must remain noncanonical - // until consistent casing errors are reported - return getDirectoryPath(getNormalizedAbsolutePath(fileName, basePath)); - } +/** + * Parse the contents of a config file (tsconfig.json). + * @param jsonNode The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + */ +export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map, existingWatchOptions?: WatchOptions): ParsedCommandLine { + return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); +} - /** - * Parse the contents of a config file from json or json source file (tsconfig.json). - * @param json The contents of the config file to parse - * @param sourceFile sourceFile corresponding to the Json - * @param host Instance of ParseConfigHost used to enumerate files in folder. - * @param basePath A root directory to resolve relative path entries in the config - * file to. e.g. outDir - * @param resolutionStack Only present for backwards-compatibility. Should be empty. - */ - function parseJsonConfigFileContentWorker( - json: any, - sourceFile: TsConfigSourceFile | undefined, - host: ParseConfigHost, - basePath: string, - existingOptions: CompilerOptions = {}, - existingWatchOptions: WatchOptions | undefined, - configFileName?: string, - resolutionStack: Path[] = [], - extraFileExtensions: readonly FileExtensionInfo[] = [], - extendedConfigCache?: ESMap - ): ParsedCommandLine { - Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined)); - const errors: Diagnostic[] = []; - - const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors, extendedConfigCache); - const { raw } = parsedConfig; - const options = extend(existingOptions, parsedConfig.options || {}); - const watchOptions = existingWatchOptions && parsedConfig.watchOptions ? - extend(existingWatchOptions, parsedConfig.watchOptions) : - parsedConfig.watchOptions || existingWatchOptions; - - options.configFilePath = configFileName && normalizeSlashes(configFileName); - const configFileSpecs = getConfigFileSpecs(); - if (sourceFile) sourceFile.configFileSpecs = configFileSpecs; - setConfigFileInOptions(options, sourceFile); - - const basePathForFileNames = normalizePath(configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath); - return { - options, - watchOptions, - fileNames: getFileNames(basePathForFileNames), - projectReferences: getProjectReferences(basePathForFileNames), - typeAcquisition: parsedConfig.typeAcquisition || getDefaultTypeAcquisition(), - raw, - errors, - // Wildcard directories (provided as part of a wildcard path) are stored in a - // file map that marks whether it was a regular wildcard match (with a `*` or `?` token), - // or a recursive directory. This information is used by filesystem watchers to monitor for - // new entries in these paths. - wildcardDirectories: getWildcardDirectories(configFileSpecs, basePathForFileNames, host.useCaseSensitiveFileNames), - compileOnSave: !!raw.compileOnSave, - }; +/*@internal*/ +export function setConfigFileInOptions(options: CompilerOptions, configFile: TsConfigSourceFile | undefined) { + if (configFile) { + Object.defineProperty(options, "configFile", { enumerable: false, writable: false, value: configFile }); + } +} - function getConfigFileSpecs(): ConfigFileSpecs { - const referencesOfRaw = getPropFromRaw("references", element => typeof element === "object", "object"); - const filesSpecs = toPropValue(getSpecsFromRaw("files")); - if (filesSpecs) { - const hasZeroOrNoReferences = referencesOfRaw === "no-prop" || isArray(referencesOfRaw) && referencesOfRaw.length === 0; - const hasExtends = hasProperty(raw, "extends"); - if (filesSpecs.length === 0 && hasZeroOrNoReferences && !hasExtends) { - if (sourceFile) { - const fileName = configFileName || "tsconfig.json"; - const diagnosticMessage = Diagnostics.The_files_list_in_config_file_0_is_empty; - const nodeValue = firstDefined(getTsConfigPropArray(sourceFile, "files"), property => property.initializer); - const error = nodeValue - ? createDiagnosticForNodeInSourceFile(sourceFile, nodeValue, diagnosticMessage, fileName) - : createCompilerDiagnostic(diagnosticMessage, fileName); - errors.push(error); - } - else { - createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"); - } - } - } +function isNullOrUndefined(x: any): x is null | undefined { + return x === undefined || x === null; // eslint-disable-line no-null/no-null +} - let includeSpecs = toPropValue(getSpecsFromRaw("include")); +function directoryOfCombinedPath(fileName: string, basePath: string) { + // Use the `getNormalizedAbsolutePath` function to avoid canonicalizing the path, as it must remain noncanonical + // until consistent casing errors are reported + return getDirectoryPath(getNormalizedAbsolutePath(fileName, basePath)); +} - const excludeOfRaw = getSpecsFromRaw("exclude"); - let excludeSpecs = toPropValue(excludeOfRaw); - if (excludeOfRaw === "no-prop" && raw.compilerOptions) { - const outDir = raw.compilerOptions.outDir; - const declarationDir = raw.compilerOptions.declarationDir; +/** + * Parse the contents of a config file from json or json source file (tsconfig.json). + * @param json The contents of the config file to parse + * @param sourceFile sourceFile corresponding to the Json + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + * @param resolutionStack Only present for backwards-compatibility. Should be empty. + */ +function parseJsonConfigFileContentWorker( + json: any, + sourceFile: TsConfigSourceFile | undefined, + host: ParseConfigHost, + basePath: string, + existingOptions: CompilerOptions = {}, + existingWatchOptions: WatchOptions | undefined, + configFileName?: string, + resolutionStack: Path[] = [], + extraFileExtensions: readonly FileExtensionInfo[] = [], + extendedConfigCache?: ESMap +): ParsedCommandLine { + Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined)); + const errors: Diagnostic[] = []; + + const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors, extendedConfigCache); + const { raw } = parsedConfig; + const options = extend(existingOptions, parsedConfig.options || {}); + const watchOptions = existingWatchOptions && parsedConfig.watchOptions ? + extend(existingWatchOptions, parsedConfig.watchOptions) : + parsedConfig.watchOptions || existingWatchOptions; + + options.configFilePath = configFileName && normalizeSlashes(configFileName); + const configFileSpecs = getConfigFileSpecs(); + if (sourceFile) sourceFile.configFileSpecs = configFileSpecs; + setConfigFileInOptions(options, sourceFile); + + const basePathForFileNames = normalizePath(configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath); + return { + options, + watchOptions, + fileNames: getFileNames(basePathForFileNames), + projectReferences: getProjectReferences(basePathForFileNames), + typeAcquisition: parsedConfig.typeAcquisition || getDefaultTypeAcquisition(), + raw, + errors, + // Wildcard directories (provided as part of a wildcard path) are stored in a + // file map that marks whether it was a regular wildcard match (with a `*` or `?` token), + // or a recursive directory. This information is used by filesystem watchers to monitor for + // new entries in these paths. + wildcardDirectories: getWildcardDirectories(configFileSpecs, basePathForFileNames, host.useCaseSensitiveFileNames), + compileOnSave: !!raw.compileOnSave, + }; - if (outDir || declarationDir) { - excludeSpecs = [outDir, declarationDir].filter(d => !!d); + function getConfigFileSpecs(): ConfigFileSpecs { + const referencesOfRaw = getPropFromRaw("references", element => typeof element === "object", "object"); + const filesSpecs = toPropValue(getSpecsFromRaw("files")); + if (filesSpecs) { + const hasZeroOrNoReferences = referencesOfRaw === "no-prop" || isArray(referencesOfRaw) && referencesOfRaw.length === 0; + const hasExtends = hasProperty(raw, "extends"); + if (filesSpecs.length === 0 && hasZeroOrNoReferences && !hasExtends) { + if (sourceFile) { + const fileName = configFileName || "tsconfig.json"; + const diagnosticMessage = Diagnostics.The_files_list_in_config_file_0_is_empty; + const nodeValue = firstDefined(getTsConfigPropArray(sourceFile, "files"), property => property.initializer); + const error = nodeValue + ? createDiagnosticForNodeInSourceFile(sourceFile, nodeValue, diagnosticMessage, fileName) + : createCompilerDiagnostic(diagnosticMessage, fileName); + errors.push(error); + } + else { + createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"); } } + } - if (filesSpecs === undefined && includeSpecs === undefined) { - includeSpecs = ["**/*"]; - } - let validatedIncludeSpecs: readonly string[] | undefined, validatedExcludeSpecs: readonly string[] | undefined; - - // The exclude spec list is converted into a regular expression, which allows us to quickly - // test whether a file or directory should be excluded before recursively traversing the - // file system. + let includeSpecs = toPropValue(getSpecsFromRaw("include")); - if (includeSpecs) { - validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*disallowTrailingRecursion*/ true, sourceFile, "include"); - } + const excludeOfRaw = getSpecsFromRaw("exclude"); + let excludeSpecs = toPropValue(excludeOfRaw); + if (excludeOfRaw === "no-prop" && raw.compilerOptions) { + const outDir = raw.compilerOptions.outDir; + const declarationDir = raw.compilerOptions.declarationDir; - if (excludeSpecs) { - validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*disallowTrailingRecursion*/ false, sourceFile, "exclude"); + if (outDir || declarationDir) { + excludeSpecs = [outDir, declarationDir].filter(d => !!d); } - - return { - filesSpecs, - includeSpecs, - excludeSpecs, - validatedFilesSpec: filter(filesSpecs, isString), - validatedIncludeSpecs, - validatedExcludeSpecs, - pathPatterns: undefined, // Initialized on first use - }; } - function getFileNames(basePath: string): string[] { - const fileNames = getFileNamesFromConfigSpecs(configFileSpecs, basePath, options, host, extraFileExtensions); - if (shouldReportNoInputFiles(fileNames, canJsonReportNoInputFiles(raw), resolutionStack)) { - errors.push(getErrorForNoInputFiles(configFileSpecs, configFileName)); - } - return fileNames; + if (filesSpecs === undefined && includeSpecs === undefined) { + includeSpecs = ["**/*"]; } + let validatedIncludeSpecs: readonly string[] | undefined, validatedExcludeSpecs: readonly string[] | undefined; - function getProjectReferences(basePath: string): readonly ProjectReference[] | undefined { - let projectReferences: ProjectReference[] | undefined; - const referencesOfRaw = getPropFromRaw("references", element => typeof element === "object", "object"); - if (isArray(referencesOfRaw)) { - for (const ref of referencesOfRaw) { - if (typeof ref.path !== "string") { - createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "reference.path", "string"); - } - else { - (projectReferences || (projectReferences = [])).push({ - path: getNormalizedAbsolutePath(ref.path, basePath), - originalPath: ref.path, - prepend: ref.prepend, - circular: ref.circular - }); - } - } - } - return projectReferences; + // The exclude spec list is converted into a regular expression, which allows us to quickly + // test whether a file or directory should be excluded before recursively traversing the + // file system. + + if (includeSpecs) { + validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*disallowTrailingRecursion*/ true, sourceFile, "include"); } - type PropOfRaw = readonly T[] | "not-array" | "no-prop"; - function toPropValue(specResult: PropOfRaw) { - return isArray(specResult) ? specResult : undefined; + if (excludeSpecs) { + validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*disallowTrailingRecursion*/ false, sourceFile, "exclude"); } - function getSpecsFromRaw(prop: "files" | "include" | "exclude"): PropOfRaw { - return getPropFromRaw(prop, isString, "string"); + return { + filesSpecs, + includeSpecs, + excludeSpecs, + validatedFilesSpec: filter(filesSpecs, isString), + validatedIncludeSpecs, + validatedExcludeSpecs, + pathPatterns: undefined, // Initialized on first use + }; + } + + function getFileNames(basePath: string): string[] { + const fileNames = getFileNamesFromConfigSpecs(configFileSpecs, basePath, options, host, extraFileExtensions); + if (shouldReportNoInputFiles(fileNames, canJsonReportNoInputFiles(raw), resolutionStack)) { + errors.push(getErrorForNoInputFiles(configFileSpecs, configFileName)); } + return fileNames; + } - function getPropFromRaw(prop: "files" | "include" | "exclude" | "references", validateElement: (value: unknown) => boolean, elementTypeName: string): PropOfRaw { - if (hasProperty(raw, prop) && !isNullOrUndefined(raw[prop])) { - if (isArray(raw[prop])) { - const result = raw[prop]; - if (!sourceFile && !every(result, validateElement)) { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, prop, elementTypeName)); - } - return result; + function getProjectReferences(basePath: string): readonly ProjectReference[] | undefined { + let projectReferences: ProjectReference[] | undefined; + const referencesOfRaw = getPropFromRaw("references", element => typeof element === "object", "object"); + if (isArray(referencesOfRaw)) { + for (const ref of referencesOfRaw) { + if (typeof ref.path !== "string") { + createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "reference.path", "string"); } else { - createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, prop, "Array"); - return "not-array"; + (projectReferences || (projectReferences = [])).push({ + path: getNormalizedAbsolutePath(ref.path, basePath), + originalPath: ref.path, + prepend: ref.prepend, + circular: ref.circular + }); } } - return "no-prop"; - } - - function createCompilerDiagnosticOnlyIfJson(message: DiagnosticMessage, arg0?: string, arg1?: string) { - if (!sourceFile) { - errors.push(createCompilerDiagnostic(message, arg0, arg1)); - } } + return projectReferences; } - function isErrorNoInputFiles(error: Diagnostic) { - return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code; + type PropOfRaw = readonly T[] | "not-array" | "no-prop"; + function toPropValue(specResult: PropOfRaw) { + return isArray(specResult) ? specResult : undefined; } - function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName: string | undefined) { - return createCompilerDiagnostic( - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - configFileName || "tsconfig.json", - JSON.stringify(includeSpecs || []), - JSON.stringify(excludeSpecs || [])); + function getSpecsFromRaw(prop: "files" | "include" | "exclude"): PropOfRaw { + return getPropFromRaw(prop, isString, "string"); } - function shouldReportNoInputFiles(fileNames: string[], canJsonReportNoInutFiles: boolean, resolutionStack?: Path[]) { - return fileNames.length === 0 && canJsonReportNoInutFiles && (!resolutionStack || resolutionStack.length === 0); - } - - /*@internal*/ - export function canJsonReportNoInputFiles(raw: any) { - return !hasProperty(raw, "files") && !hasProperty(raw, "references"); + function getPropFromRaw(prop: "files" | "include" | "exclude" | "references", validateElement: (value: unknown) => boolean, elementTypeName: string): PropOfRaw { + if (hasProperty(raw, prop) && !isNullOrUndefined(raw[prop])) { + if (isArray(raw[prop])) { + const result = raw[prop]; + if (!sourceFile && !every(result, validateElement)) { + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, prop, elementTypeName)); + } + return result; + } + else { + createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, prop, "Array"); + return "not-array"; + } + } + return "no-prop"; } - /*@internal*/ - export function updateErrorForNoInputFiles(fileNames: string[], configFileName: string, configFileSpecs: ConfigFileSpecs, configParseDiagnostics: Diagnostic[], canJsonReportNoInutFiles: boolean) { - const existingErrors = configParseDiagnostics.length; - if (shouldReportNoInputFiles(fileNames, canJsonReportNoInutFiles)) { - configParseDiagnostics.push(getErrorForNoInputFiles(configFileSpecs, configFileName)); - } - else { - filterMutate(configParseDiagnostics, error => !isErrorNoInputFiles(error)); + function createCompilerDiagnosticOnlyIfJson(message: DiagnosticMessage, arg0?: string, arg1?: string) { + if (!sourceFile) { + errors.push(createCompilerDiagnostic(message, arg0, arg1)); } - return existingErrors !== configParseDiagnostics.length; } +} - export interface ParsedTsconfig { - raw: any; - options?: CompilerOptions; - watchOptions?: WatchOptions; - typeAcquisition?: TypeAcquisition; - /** - * Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet - */ - extendedConfigPath?: string; - } +function isErrorNoInputFiles(error: Diagnostic) { + return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code; +} + +function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName: string | undefined) { + return createCompilerDiagnostic( + Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + configFileName || "tsconfig.json", + JSON.stringify(includeSpecs || []), + JSON.stringify(excludeSpecs || [])); +} + +function shouldReportNoInputFiles(fileNames: string[], canJsonReportNoInutFiles: boolean, resolutionStack?: Path[]) { + return fileNames.length === 0 && canJsonReportNoInutFiles && (!resolutionStack || resolutionStack.length === 0); +} + +/*@internal*/ +export function canJsonReportNoInputFiles(raw: any) { + return !hasProperty(raw, "files") && !hasProperty(raw, "references"); +} - function isSuccessfulParsedTsconfig(value: ParsedTsconfig) { - return !!value.options; +/*@internal*/ +export function updateErrorForNoInputFiles(fileNames: string[], configFileName: string, configFileSpecs: ConfigFileSpecs, configParseDiagnostics: Diagnostic[], canJsonReportNoInutFiles: boolean) { + const existingErrors = configParseDiagnostics.length; + if (shouldReportNoInputFiles(fileNames, canJsonReportNoInutFiles)) { + configParseDiagnostics.push(getErrorForNoInputFiles(configFileSpecs, configFileName)); + } + else { + filterMutate(configParseDiagnostics, error => !isErrorNoInputFiles(error)); } + return existingErrors !== configParseDiagnostics.length; +} +export interface ParsedTsconfig { + raw: any; + options?: CompilerOptions; + watchOptions?: WatchOptions; + typeAcquisition?: TypeAcquisition; /** - * This *just* extracts options/include/exclude/files out of a config file. - * It does *not* resolve the included files. + * Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet */ - function parseConfig( - json: any, - sourceFile: TsConfigSourceFile | undefined, - host: ParseConfigHost, - basePath: string, - configFileName: string | undefined, - resolutionStack: string[], - errors: Push, - extendedConfigCache?: ESMap - ): ParsedTsconfig { - basePath = normalizeSlashes(basePath); - const resolvedPath = getNormalizedAbsolutePath(configFileName || "", basePath); - - if (resolutionStack.indexOf(resolvedPath) >= 0) { - errors.push(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))); - return { raw: json || convertToObject(sourceFile!, errors) }; - } - - const ownConfig = json ? - parseOwnConfigOfJson(json, host, basePath, configFileName, errors) : - parseOwnConfigOfJsonSourceFile(sourceFile!, host, basePath, configFileName, errors); - - if (ownConfig.options?.paths) { - // If we end up needing to resolve relative paths from 'paths' relative to - // the config file location, we'll need to know where that config file was. - // Since 'paths' can be inherited from an extended config in another directory, - // we wouldn't know which directory to use unless we store it here. - ownConfig.options.pathsBasePath = basePath; - } - if (ownConfig.extendedConfigPath) { - // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. - resolutionStack = resolutionStack.concat([resolvedPath]); - const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, resolutionStack, errors, extendedConfigCache); - if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) { - const baseRaw = extendedConfig.raw; - const raw = ownConfig.raw; - let relativeDifference: string | undefined ; - const setPropertyInRawIfNotUndefined = (propertyName: string) => { - if (!raw[propertyName] && baseRaw[propertyName]) { - raw[propertyName] = map(baseRaw[propertyName], (path: string) => isRootedDiskPath(path) ? path : combinePaths( - relativeDifference ||= convertToRelativePath(getDirectoryPath(ownConfig.extendedConfigPath!), basePath, createGetCanonicalFileName(host.useCaseSensitiveFileNames)), - path - )); - } - }; - setPropertyInRawIfNotUndefined("include"); - setPropertyInRawIfNotUndefined("exclude"); - setPropertyInRawIfNotUndefined("files"); - if (raw.compileOnSave === undefined) { - raw.compileOnSave = baseRaw.compileOnSave; + extendedConfigPath?: string; +} + +function isSuccessfulParsedTsconfig(value: ParsedTsconfig) { + return !!value.options; +} + +/** + * This *just* extracts options/include/exclude/files out of a config file. + * It does *not* resolve the included files. + */ +function parseConfig( + json: any, + sourceFile: TsConfigSourceFile | undefined, + host: ParseConfigHost, + basePath: string, + configFileName: string | undefined, + resolutionStack: string[], + errors: Push, + extendedConfigCache?: ESMap +): ParsedTsconfig { + basePath = normalizeSlashes(basePath); + const resolvedPath = getNormalizedAbsolutePath(configFileName || "", basePath); + + if (resolutionStack.indexOf(resolvedPath) >= 0) { + errors.push(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))); + return { raw: json || convertToObject(sourceFile!, errors) }; + } + + const ownConfig = json ? + parseOwnConfigOfJson(json, host, basePath, configFileName, errors) : + parseOwnConfigOfJsonSourceFile(sourceFile!, host, basePath, configFileName, errors); + + if (ownConfig.options?.paths) { + // If we end up needing to resolve relative paths from 'paths' relative to + // the config file location, we'll need to know where that config file was. + // Since 'paths' can be inherited from an extended config in another directory, + // we wouldn't know which directory to use unless we store it here. + ownConfig.options.pathsBasePath = basePath; + } + if (ownConfig.extendedConfigPath) { + // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. + resolutionStack = resolutionStack.concat([resolvedPath]); + const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, resolutionStack, errors, extendedConfigCache); + if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) { + const baseRaw = extendedConfig.raw; + const raw = ownConfig.raw; + let relativeDifference: string | undefined ; + const setPropertyInRawIfNotUndefined = (propertyName: string) => { + if (!raw[propertyName] && baseRaw[propertyName]) { + raw[propertyName] = map(baseRaw[propertyName], (path: string) => isRootedDiskPath(path) ? path : combinePaths( + relativeDifference ||= convertToRelativePath(getDirectoryPath(ownConfig.extendedConfigPath!), basePath, createGetCanonicalFileName(host.useCaseSensitiveFileNames)), + path + )); } - ownConfig.options = assign({}, extendedConfig.options, ownConfig.options); - ownConfig.watchOptions = ownConfig.watchOptions && extendedConfig.watchOptions ? - assign({}, extendedConfig.watchOptions, ownConfig.watchOptions) : - ownConfig.watchOptions || extendedConfig.watchOptions; - // TODO extend type typeAcquisition + }; + setPropertyInRawIfNotUndefined("include"); + setPropertyInRawIfNotUndefined("exclude"); + setPropertyInRawIfNotUndefined("files"); + if (raw.compileOnSave === undefined) { + raw.compileOnSave = baseRaw.compileOnSave; } + ownConfig.options = assign({}, extendedConfig.options, ownConfig.options); + ownConfig.watchOptions = ownConfig.watchOptions && extendedConfig.watchOptions ? + assign({}, extendedConfig.watchOptions, ownConfig.watchOptions) : + ownConfig.watchOptions || extendedConfig.watchOptions; + // TODO extend type typeAcquisition } - - return ownConfig; } - function parseOwnConfigOfJson( - json: any, - host: ParseConfigHost, - basePath: string, - configFileName: string | undefined, - errors: Push - ): ParsedTsconfig { - if (hasProperty(json, "excludes")) { - errors.push(createCompilerDiagnostic(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); - } - - const options = convertCompilerOptionsFromJsonWorker(json.compilerOptions, basePath, errors, configFileName); - // typingOptions has been deprecated and is only supported for backward compatibility purposes. - // It should be removed in future releases - use typeAcquisition instead. - const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json.typeAcquisition || json.typingOptions, basePath, errors, configFileName); - const watchOptions = convertWatchOptionsFromJsonWorker(json.watchOptions, basePath, errors); - json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors); - let extendedConfigPath: string | undefined; + return ownConfig; +} - if (json.extends) { - if (!isString(json.extends)) { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string")); - } - else { - const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; - extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, errors, createCompilerDiagnostic); - } +function parseOwnConfigOfJson( + json: any, + host: ParseConfigHost, + basePath: string, + configFileName: string | undefined, + errors: Push +): ParsedTsconfig { + if (hasProperty(json, "excludes")) { + errors.push(createCompilerDiagnostic(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); + } + + const options = convertCompilerOptionsFromJsonWorker(json.compilerOptions, basePath, errors, configFileName); + // typingOptions has been deprecated and is only supported for backward compatibility purposes. + // It should be removed in future releases - use typeAcquisition instead. + const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json.typeAcquisition || json.typingOptions, basePath, errors, configFileName); + const watchOptions = convertWatchOptionsFromJsonWorker(json.watchOptions, basePath, errors); + json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors); + let extendedConfigPath: string | undefined; + + if (json.extends) { + if (!isString(json.extends)) { + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string")); } - return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; - } - - function parseOwnConfigOfJsonSourceFile( - sourceFile: TsConfigSourceFile, - host: ParseConfigHost, - basePath: string, - configFileName: string | undefined, - errors: Push - ): ParsedTsconfig { - const options = getDefaultCompilerOptions(configFileName); - let typeAcquisition: TypeAcquisition | undefined, typingOptionstypeAcquisition: TypeAcquisition | undefined; - let watchOptions: WatchOptions | undefined; - let extendedConfigPath: string | undefined; - let rootCompilerOptions: PropertyName[] | undefined; - - const optionsIterator: JsonConversionNotifier = { - onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue) { - let currentOption; - switch (parentOption) { - case "compilerOptions": - currentOption = options; - break; - case "watchOptions": - currentOption = (watchOptions || (watchOptions = {})); - break; - case "typeAcquisition": - currentOption = (typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName))); - break; - case "typingOptions": - currentOption = (typingOptionstypeAcquisition || (typingOptionstypeAcquisition = getDefaultTypeAcquisition(configFileName))); - break; - default: - Debug.fail("Unknown option"); - } + else { + const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; + extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, errors, createCompilerDiagnostic); + } + } + return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; +} - currentOption[option.name] = normalizeOptionValue(option, basePath, value); - }, - onSetValidOptionKeyValueInRoot(key: string, _keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression) { - switch (key) { - case "extends": - const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; - extendedConfigPath = getExtendsConfigPath( - value as string, - host, - newBase, - errors, - (message, arg0) => - createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) - ); - return; - } - }, - onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) { - if (key === "excludes") { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, keyNode, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); - } - if (find(commandOptionsWithoutBuild, (opt) => opt.name === key)) { - rootCompilerOptions = append(rootCompilerOptions, keyNode); - } +function parseOwnConfigOfJsonSourceFile( + sourceFile: TsConfigSourceFile, + host: ParseConfigHost, + basePath: string, + configFileName: string | undefined, + errors: Push +): ParsedTsconfig { + const options = getDefaultCompilerOptions(configFileName); + let typeAcquisition: TypeAcquisition | undefined, typingOptionstypeAcquisition: TypeAcquisition | undefined; + let watchOptions: WatchOptions | undefined; + let extendedConfigPath: string | undefined; + let rootCompilerOptions: PropertyName[] | undefined; + + const optionsIterator: JsonConversionNotifier = { + onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue) { + let currentOption; + switch (parentOption) { + case "compilerOptions": + currentOption = options; + break; + case "watchOptions": + currentOption = (watchOptions || (watchOptions = {})); + break; + case "typeAcquisition": + currentOption = (typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName))); + break; + case "typingOptions": + currentOption = (typingOptionstypeAcquisition || (typingOptionstypeAcquisition = getDefaultTypeAcquisition(configFileName))); + break; + default: + Debug.fail("Unknown option"); + } + + currentOption[option.name] = normalizeOptionValue(option, basePath, value); + }, + onSetValidOptionKeyValueInRoot(key: string, _keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression) { + switch (key) { + case "extends": + const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; + extendedConfigPath = getExtendsConfigPath( + value as string, + host, + newBase, + errors, + (message, arg0) => + createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) + ); + return; } - }; - const json = convertConfigFileToObject(sourceFile, errors, /*reportOptionsErrors*/ true, optionsIterator); - - if (!typeAcquisition) { - if (typingOptionstypeAcquisition) { - typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ? - { - enable: typingOptionstypeAcquisition.enableAutoDiscovery, - include: typingOptionstypeAcquisition.include, - exclude: typingOptionstypeAcquisition.exclude - } : - typingOptionstypeAcquisition; + }, + onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) { + if (key === "excludes") { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, keyNode, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); } - else { - typeAcquisition = getDefaultTypeAcquisition(configFileName); + if (find(commandOptionsWithoutBuild, (opt) => opt.name === key)) { + rootCompilerOptions = append(rootCompilerOptions, keyNode); } } - - if (rootCompilerOptions && json && json.compilerOptions === undefined) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, rootCompilerOptions[0], Diagnostics._0_should_be_set_inside_the_compilerOptions_object_of_the_config_json_file, getTextOfPropertyName(rootCompilerOptions[0]) as string)); - } - - return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; - } - - function getExtendsConfigPath( - extendedConfig: string, - host: ParseConfigHost, - basePath: string, - errors: Push, - createDiagnostic: (message: DiagnosticMessage, arg1?: string) => Diagnostic) { - extendedConfig = normalizeSlashes(extendedConfig); - if (isRootedDiskPath(extendedConfig) || startsWith(extendedConfig, "./") || startsWith(extendedConfig, "../")) { - let extendedConfigPath = getNormalizedAbsolutePath(extendedConfig, basePath); - if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) { - extendedConfigPath = `${extendedConfigPath}.json`; - if (!host.fileExists(extendedConfigPath)) { - errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); - return undefined; - } - } - return extendedConfigPath; + }; + const json = convertConfigFileToObject(sourceFile, errors, /*reportOptionsErrors*/ true, optionsIterator); + + if (!typeAcquisition) { + if (typingOptionstypeAcquisition) { + typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ? + { + enable: typingOptionstypeAcquisition.enableAutoDiscovery, + include: typingOptionstypeAcquisition.include, + exclude: typingOptionstypeAcquisition.exclude + } : + typingOptionstypeAcquisition; } - // If the path isn't a rooted or relative path, resolve like a module - const resolved = nodeModuleNameResolver(extendedConfig, combinePaths(basePath, "tsconfig.json"), { moduleResolution: ModuleResolutionKind.NodeJs }, host, /*cache*/ undefined, /*projectRefs*/ undefined, /*lookupConfig*/ true); - if (resolved.resolvedModule) { - return resolved.resolvedModule.resolvedFileName; + else { + typeAcquisition = getDefaultTypeAcquisition(configFileName); } - errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); - return undefined; } - export interface ExtendedConfigCacheEntry { - extendedResult: TsConfigSourceFile; - extendedConfig: ParsedTsconfig | undefined; + if (rootCompilerOptions && json && json.compilerOptions === undefined) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, rootCompilerOptions[0], Diagnostics._0_should_be_set_inside_the_compilerOptions_object_of_the_config_json_file, getTextOfPropertyName(rootCompilerOptions[0]) as string)); } - function getExtendedConfig( - sourceFile: TsConfigSourceFile | undefined, - extendedConfigPath: string, - host: ParseConfigHost, - resolutionStack: string[], - errors: Push, - extendedConfigCache?: ESMap - ): ParsedTsconfig | undefined { - const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toFileNameLowerCase(extendedConfigPath); - let value: ExtendedConfigCacheEntry | undefined; - let extendedResult: TsConfigSourceFile; - let extendedConfig: ParsedTsconfig | undefined; - if (extendedConfigCache && (value = extendedConfigCache.get(path))) { - ({ extendedResult, extendedConfig } = value); - } - else { - extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path)); - if (!extendedResult.parseDiagnostics.length) { - extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, getDirectoryPath(extendedConfigPath), - getBaseFileName(extendedConfigPath), resolutionStack, errors, extendedConfigCache); - } - if (extendedConfigCache) { - extendedConfigCache.set(path, { extendedResult, extendedConfig }); - } - } - if (sourceFile) { - sourceFile.extendedSourceFiles = [extendedResult.fileName]; - if (extendedResult.extendedSourceFiles) { - sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles); + return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; +} + +function getExtendsConfigPath( + extendedConfig: string, + host: ParseConfigHost, + basePath: string, + errors: Push, + createDiagnostic: (message: DiagnosticMessage, arg1?: string) => Diagnostic) { + extendedConfig = normalizeSlashes(extendedConfig); + if (isRootedDiskPath(extendedConfig) || startsWith(extendedConfig, "./") || startsWith(extendedConfig, "../")) { + let extendedConfigPath = getNormalizedAbsolutePath(extendedConfig, basePath); + if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) { + extendedConfigPath = `${extendedConfigPath}.json`; + if (!host.fileExists(extendedConfigPath)) { + errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); + return undefined; } } - if (extendedResult.parseDiagnostics.length) { - errors.push(...extendedResult.parseDiagnostics); - return undefined; - } - return extendedConfig!; + return extendedConfigPath; } - - function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Push): boolean { - if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) { - return false; - } - const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption.compileOnSave, basePath, errors); - return typeof result === "boolean" && result; + // If the path isn't a rooted or relative path, resolve like a module + const resolved = nodeModuleNameResolver(extendedConfig, combinePaths(basePath, "tsconfig.json"), { moduleResolution: ModuleResolutionKind.NodeJs }, host, /*cache*/ undefined, /*projectRefs*/ undefined, /*lookupConfig*/ true); + if (resolved.resolvedModule) { + return resolved.resolvedModule.resolvedFileName; } + errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); + return undefined; +} - export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: CompilerOptions, errors: Diagnostic[] } { - const errors: Diagnostic[] = []; - const options = convertCompilerOptionsFromJsonWorker(jsonOptions, basePath, errors, configFileName); - return { options, errors }; - } +export interface ExtendedConfigCacheEntry { + extendedResult: TsConfigSourceFile; + extendedConfig: ParsedTsconfig | undefined; +} - export function convertTypeAcquisitionFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: TypeAcquisition, errors: Diagnostic[] } { - const errors: Diagnostic[] = []; - const options = convertTypeAcquisitionFromJsonWorker(jsonOptions, basePath, errors, configFileName); - return { options, errors }; +function getExtendedConfig( + sourceFile: TsConfigSourceFile | undefined, + extendedConfigPath: string, + host: ParseConfigHost, + resolutionStack: string[], + errors: Push, + extendedConfigCache?: ESMap +): ParsedTsconfig | undefined { + const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toFileNameLowerCase(extendedConfigPath); + let value: ExtendedConfigCacheEntry | undefined; + let extendedResult: TsConfigSourceFile; + let extendedConfig: ParsedTsconfig | undefined; + if (extendedConfigCache && (value = extendedConfigCache.get(path))) { + ({ extendedResult, extendedConfig } = value); + } + else { + extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path)); + if (!extendedResult.parseDiagnostics.length) { + extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, getDirectoryPath(extendedConfigPath), + getBaseFileName(extendedConfigPath), resolutionStack, errors, extendedConfigCache); + } + if (extendedConfigCache) { + extendedConfigCache.set(path, { extendedResult, extendedConfig }); + } + } + if (sourceFile) { + sourceFile.extendedSourceFiles = [extendedResult.fileName]; + if (extendedResult.extendedSourceFiles) { + sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles); + } + } + if (extendedResult.parseDiagnostics.length) { + errors.push(...extendedResult.parseDiagnostics); + return undefined; } + return extendedConfig!; +} - function getDefaultCompilerOptions(configFileName?: string) { - const options: CompilerOptions = configFileName && getBaseFileName(configFileName) === "jsconfig.json" - ? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, skipLibCheck: true, noEmit: true } - : {}; - return options; +function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Push): boolean { + if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) { + return false; } + const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption.compileOnSave, basePath, errors); + return typeof result === "boolean" && result; +} - function convertCompilerOptionsFromJsonWorker(jsonOptions: any, - basePath: string, errors: Push, configFileName?: string): CompilerOptions { +export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: CompilerOptions, errors: Diagnostic[] } { + const errors: Diagnostic[] = []; + const options = convertCompilerOptionsFromJsonWorker(jsonOptions, basePath, errors, configFileName); + return { options, errors }; +} - const options = getDefaultCompilerOptions(configFileName); - convertOptionsFromJson(getCommandLineCompilerOptionsMap(), jsonOptions, basePath, options, compilerOptionsDidYouMeanDiagnostics, errors); - if (configFileName) { - options.configFilePath = normalizeSlashes(configFileName); - } - return options; - } +export function convertTypeAcquisitionFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: TypeAcquisition, errors: Diagnostic[] } { + const errors: Diagnostic[] = []; + const options = convertTypeAcquisitionFromJsonWorker(jsonOptions, basePath, errors, configFileName); + return { options, errors }; +} + +function getDefaultCompilerOptions(configFileName?: string) { + const options: CompilerOptions = configFileName && getBaseFileName(configFileName) === "jsconfig.json" + ? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, skipLibCheck: true, noEmit: true } + : {}; + return options; +} - function getDefaultTypeAcquisition(configFileName?: string): TypeAcquisition { - return { enable: !!configFileName && getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] }; +function convertCompilerOptionsFromJsonWorker(jsonOptions: any, + basePath: string, errors: Push, configFileName?: string): CompilerOptions { + + const options = getDefaultCompilerOptions(configFileName); + convertOptionsFromJson(getCommandLineCompilerOptionsMap(), jsonOptions, basePath, options, compilerOptionsDidYouMeanDiagnostics, errors); + if (configFileName) { + options.configFilePath = normalizeSlashes(configFileName); } + return options; +} - function convertTypeAcquisitionFromJsonWorker(jsonOptions: any, - basePath: string, errors: Push, configFileName?: string): TypeAcquisition { +function getDefaultTypeAcquisition(configFileName?: string): TypeAcquisition { + return { enable: !!configFileName && getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] }; +} - const options = getDefaultTypeAcquisition(configFileName); - const typeAcquisition = convertEnableAutoDiscoveryToEnable(jsonOptions); +function convertTypeAcquisitionFromJsonWorker(jsonOptions: any, + basePath: string, errors: Push, configFileName?: string): TypeAcquisition { - convertOptionsFromJson(getCommandLineTypeAcquisitionMap(), typeAcquisition, basePath, options, typeAcquisitionDidYouMeanDiagnostics, errors); - return options; - } + const options = getDefaultTypeAcquisition(configFileName); + const typeAcquisition = convertEnableAutoDiscoveryToEnable(jsonOptions); - function convertWatchOptionsFromJsonWorker(jsonOptions: any, basePath: string, errors: Push): WatchOptions | undefined { - return convertOptionsFromJson(getCommandLineWatchOptionsMap(), jsonOptions, basePath, /*defaultOptions*/ undefined, watchOptionsDidYouMeanDiagnostics, errors); - } + convertOptionsFromJson(getCommandLineTypeAcquisitionMap(), typeAcquisition, basePath, options, typeAcquisitionDidYouMeanDiagnostics, errors); + return options; +} - function convertOptionsFromJson(optionsNameMap: ESMap, jsonOptions: any, basePath: string, - defaultOptions: undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push): WatchOptions | undefined; - function convertOptionsFromJson(optionsNameMap: ESMap, jsonOptions: any, basePath: string, - defaultOptions: CompilerOptions | TypeAcquisition, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push): CompilerOptions | TypeAcquisition; - function convertOptionsFromJson(optionsNameMap: ESMap, jsonOptions: any, basePath: string, - defaultOptions: CompilerOptions | TypeAcquisition | WatchOptions | undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push) { +function convertWatchOptionsFromJsonWorker(jsonOptions: any, basePath: string, errors: Push): WatchOptions | undefined { + return convertOptionsFromJson(getCommandLineWatchOptionsMap(), jsonOptions, basePath, /*defaultOptions*/ undefined, watchOptionsDidYouMeanDiagnostics, errors); +} - if (!jsonOptions) { - return; - } +function convertOptionsFromJson(optionsNameMap: ESMap, jsonOptions: any, basePath: string, + defaultOptions: undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push): WatchOptions | undefined; +function convertOptionsFromJson(optionsNameMap: ESMap, jsonOptions: any, basePath: string, + defaultOptions: CompilerOptions | TypeAcquisition, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push): CompilerOptions | TypeAcquisition; +function convertOptionsFromJson(optionsNameMap: ESMap, jsonOptions: any, basePath: string, + defaultOptions: CompilerOptions | TypeAcquisition | WatchOptions | undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push) { - for (const id in jsonOptions) { - const opt = optionsNameMap.get(id); - if (opt) { - (defaultOptions || (defaultOptions = {}))[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors); - } - else { - errors.push(createUnknownOptionError(id, diagnostics, createCompilerDiagnostic)); - } - } - return defaultOptions; + if (!jsonOptions) { + return; } - /*@internal*/ - export function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push): CompilerOptionsValue { - if (isCompilerOptionsValue(opt, value)) { - const optType = opt.type; - if (optType === "list" && isArray(value)) { - return convertJsonOptionOfListType(opt as CommandLineOptionOfListType, value, basePath, errors); - } - else if (!isString(optType)) { - return convertJsonOptionOfCustomType(opt as CommandLineOptionOfCustomType, value as string, errors); - } - const validatedValue = validateJsonOptionValue(opt, value, errors); - return isNullOrUndefined(validatedValue) ? validatedValue : normalizeNonListOptionValue(opt, basePath, validatedValue); + for (const id in jsonOptions) { + const opt = optionsNameMap.get(id); + if (opt) { + (defaultOptions || (defaultOptions = {}))[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors); } else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, opt.name, getCompilerOptionValueTypeString(opt))); + errors.push(createUnknownOptionError(id, diagnostics, createCompilerDiagnostic)); } } + return defaultOptions; +} - function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { - if (isNullOrUndefined(value)) return undefined; - if (option.type === "list") { - const listOption = option; - if (listOption.element.isFilePath || !isString(listOption.element.type)) { - return filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => !!v) as CompilerOptionsValue; - } - return value; +/*@internal*/ +export function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push): CompilerOptionsValue { + if (isCompilerOptionsValue(opt, value)) { + const optType = opt.type; + if (optType === "list" && isArray(value)) { + return convertJsonOptionOfListType(opt as CommandLineOptionOfListType, value, basePath, errors); } - else if (!isString(option.type)) { - return option.type.get(isString(value) ? value.toLowerCase() : value); + else if (!isString(optType)) { + return convertJsonOptionOfCustomType(opt as CommandLineOptionOfCustomType, value as string, errors); } - return normalizeNonListOptionValue(option, basePath, value); + const validatedValue = validateJsonOptionValue(opt, value, errors); + return isNullOrUndefined(validatedValue) ? validatedValue : normalizeNonListOptionValue(opt, basePath, validatedValue); + } + else { + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, opt.name, getCompilerOptionValueTypeString(opt))); } +} - function normalizeNonListOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { - if (option.isFilePath) { - value = getNormalizedAbsolutePath(value, basePath); - if (value === "") { - value = "."; - } +function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { + if (isNullOrUndefined(value)) return undefined; + if (option.type === "list") { + const listOption = option; + if (listOption.element.isFilePath || !isString(listOption.element.type)) { + return filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => !!v) as CompilerOptionsValue; } return value; } - - function validateJsonOptionValue(opt: CommandLineOption, value: T, errors: Push): T | undefined { - if (isNullOrUndefined(value)) return undefined; - const d = opt.extraValidation?.(value); - if (!d) return value; - errors.push(createCompilerDiagnostic(...d)); - return undefined; + else if (!isString(option.type)) { + return option.type.get(isString(value) ? value.toLowerCase() : value); } + return normalizeNonListOptionValue(option, basePath, value); +} - function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Push) { - if (isNullOrUndefined(value)) return undefined; - const key = value.toLowerCase(); - const val = opt.type.get(key); - if (val !== undefined) { - return validateJsonOptionValue(opt, val, errors); - } - else { - errors.push(createCompilerDiagnosticForInvalidCustomType(opt)); +function normalizeNonListOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { + if (option.isFilePath) { + value = getNormalizedAbsolutePath(value, basePath); + if (value === "") { + value = "."; } } + return value; +} - function convertJsonOptionOfListType(option: CommandLineOptionOfListType, values: readonly any[], basePath: string, errors: Push): any[] { - return filter(map(values, v => convertJsonOption(option.element, v, basePath, errors)), v => !!v); - } - - /** - * Tests for a path that ends in a recursive directory wildcard. - * Matches **, \**, **\, and \**\, but not a**b. - * - * NOTE: used \ in place of / above to avoid issues with multiline comments. - * - * Breakdown: - * (^|\/) # matches either the beginning of the string or a directory separator. - * \*\* # matches the recursive directory wildcard "**". - * \/?$ # matches an optional trailing directory separator at the end of the string. - */ - const invalidTrailingRecursionPattern = /(^|\/)\*\*\/?$/; +function validateJsonOptionValue(opt: CommandLineOption, value: T, errors: Push): T | undefined { + if (isNullOrUndefined(value)) return undefined; + const d = opt.extraValidation?.(value); + if (!d) return value; + errors.push(createCompilerDiagnostic(...d)); + return undefined; +} - /** - * Matches the portion of a wildcard path that does not contain wildcards. - * Matches \a of \a\*, or \a\b\c of \a\b\c\?\d. - * - * NOTE: used \ in place of / above to avoid issues with multiline comments. - * - * Breakdown: - * ^ # matches the beginning of the string - * [^*?]* # matches any number of non-wildcard characters - * (?=\/[^/]*[*?]) # lookahead that matches a directory separator followed by - * # a path component that contains at least one wildcard character (* or ?). - */ - const wildcardDirectoryPattern = /^[^*?]*(?=\/[^/]*[*?])/; +function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Push) { + if (isNullOrUndefined(value)) return undefined; + const key = value.toLowerCase(); + const val = opt.type.get(key); + if (val !== undefined) { + return validateJsonOptionValue(opt, val, errors); + } + else { + errors.push(createCompilerDiagnosticForInvalidCustomType(opt)); + } +} - /** - * Gets the file names from the provided config file specs that contain, files, include, exclude and - * other properties needed to resolve the file names - * @param configFileSpecs The config file specs extracted with file names to include, wildcards to include/exclude and other details - * @param basePath The base path for any relative file specifications. - * @param options Compiler options. - * @param host The host used to resolve files and directories. - * @param extraFileExtensions optionaly file extra file extension information from host - */ - /* @internal */ - export function getFileNamesFromConfigSpecs( - configFileSpecs: ConfigFileSpecs, - basePath: string, - options: CompilerOptions, - host: ParseConfigHost, - extraFileExtensions: readonly FileExtensionInfo[] = emptyArray - ): string[] { - basePath = normalizePath(basePath); - - const keyMapper = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - - // Literal file names (provided via the "files" array in tsconfig.json) are stored in a - // file map with a possibly case insensitive key. We use this map later when when including - // wildcard paths. - const literalFileMap = new Map(); - - // Wildcard paths (provided via the "includes" array in tsconfig.json) are stored in a - // file map with a possibly case insensitive key. We use this map to store paths matched - // via wildcard, and to handle extension priority. - const wildcardFileMap = new Map(); - - // Wildcard paths of json files (provided via the "includes" array in tsconfig.json) are stored in a - // file map with a possibly case insensitive key. We use this map to store paths matched - // via wildcard of *.json kind - const wildCardJsonFileMap = new Map(); - const { validatedFilesSpec, validatedIncludeSpecs, validatedExcludeSpecs } = configFileSpecs; - - // Rather than re-query this for each file and filespec, we query the supported extensions - // once and store it on the expansion context. - const supportedExtensions = getSupportedExtensions(options, extraFileExtensions); - const supportedExtensionsWithJsonIfResolveJsonModule = getSupportedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); - - // Literal files are always included verbatim. An "include" or "exclude" specification cannot - // remove a literal file. - if (validatedFilesSpec) { - for (const fileName of validatedFilesSpec) { - const file = getNormalizedAbsolutePath(fileName, basePath); - literalFileMap.set(keyMapper(file), file); - } - } +function convertJsonOptionOfListType(option: CommandLineOptionOfListType, values: readonly any[], basePath: string, errors: Push): any[] { + return filter(map(values, v => convertJsonOption(option.element, v, basePath, errors)), v => !!v); +} - let jsonOnlyIncludeRegexes: readonly RegExp[] | undefined; - if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) { - for (const file of host.readDirectory(basePath, flatten(supportedExtensionsWithJsonIfResolveJsonModule), validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) { - if (fileExtensionIs(file, Extension.Json)) { - // Valid only if *.json specified - if (!jsonOnlyIncludeRegexes) { - const includes = validatedIncludeSpecs.filter(s => endsWith(s, Extension.Json)); - const includeFilePatterns = map(getRegularExpressionsForWildcards(includes, basePath, "files"), pattern => `^${pattern}$`); - jsonOnlyIncludeRegexes = includeFilePatterns ? includeFilePatterns.map(pattern => getRegexFromPattern(pattern, host.useCaseSensitiveFileNames)) : emptyArray; - } - const includeIndex = findIndex(jsonOnlyIncludeRegexes, re => re.test(file)); - if (includeIndex !== -1) { - const key = keyMapper(file); - if (!literalFileMap.has(key) && !wildCardJsonFileMap.has(key)) { - wildCardJsonFileMap.set(key, file); - } - } - continue; +/** + * Tests for a path that ends in a recursive directory wildcard. + * Matches **, \**, **\, and \**\, but not a**b. + * + * NOTE: used \ in place of / above to avoid issues with multiline comments. + * + * Breakdown: + * (^|\/) # matches either the beginning of the string or a directory separator. + * \*\* # matches the recursive directory wildcard "**". + * \/?$ # matches an optional trailing directory separator at the end of the string. + */ +const invalidTrailingRecursionPattern = /(^|\/)\*\*\/?$/; + +/** + * Matches the portion of a wildcard path that does not contain wildcards. + * Matches \a of \a\*, or \a\b\c of \a\b\c\?\d. + * + * NOTE: used \ in place of / above to avoid issues with multiline comments. + * + * Breakdown: + * ^ # matches the beginning of the string + * [^*?]* # matches any number of non-wildcard characters + * (?=\/[^/]*[*?]) # lookahead that matches a directory separator followed by + * # a path component that contains at least one wildcard character (* or ?). + */ +const wildcardDirectoryPattern = /^[^*?]*(?=\/[^/]*[*?])/; + +/** + * Gets the file names from the provided config file specs that contain, files, include, exclude and + * other properties needed to resolve the file names + * @param configFileSpecs The config file specs extracted with file names to include, wildcards to include/exclude and other details + * @param basePath The base path for any relative file specifications. + * @param options Compiler options. + * @param host The host used to resolve files and directories. + * @param extraFileExtensions optionaly file extra file extension information from host + */ +/* @internal */ +export function getFileNamesFromConfigSpecs( + configFileSpecs: ConfigFileSpecs, + basePath: string, + options: CompilerOptions, + host: ParseConfigHost, + extraFileExtensions: readonly FileExtensionInfo[] = emptyArray +): string[] { + basePath = normalizePath(basePath); + + const keyMapper = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + + // Literal file names (provided via the "files" array in tsconfig.json) are stored in a + // file map with a possibly case insensitive key. We use this map later when when including + // wildcard paths. + const literalFileMap = new Map(); + + // Wildcard paths (provided via the "includes" array in tsconfig.json) are stored in a + // file map with a possibly case insensitive key. We use this map to store paths matched + // via wildcard, and to handle extension priority. + const wildcardFileMap = new Map(); + + // Wildcard paths of json files (provided via the "includes" array in tsconfig.json) are stored in a + // file map with a possibly case insensitive key. We use this map to store paths matched + // via wildcard of *.json kind + const wildCardJsonFileMap = new Map(); + const { validatedFilesSpec, validatedIncludeSpecs, validatedExcludeSpecs } = configFileSpecs; + + // Rather than re-query this for each file and filespec, we query the supported extensions + // once and store it on the expansion context. + const supportedExtensions = getSupportedExtensions(options, extraFileExtensions); + const supportedExtensionsWithJsonIfResolveJsonModule = getSupportedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); + + // Literal files are always included verbatim. An "include" or "exclude" specification cannot + // remove a literal file. + if (validatedFilesSpec) { + for (const fileName of validatedFilesSpec) { + const file = getNormalizedAbsolutePath(fileName, basePath); + literalFileMap.set(keyMapper(file), file); + } + } + + let jsonOnlyIncludeRegexes: readonly RegExp[] | undefined; + if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) { + for (const file of host.readDirectory(basePath, flatten(supportedExtensionsWithJsonIfResolveJsonModule), validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) { + if (fileExtensionIs(file, Extension.Json)) { + // Valid only if *.json specified + if (!jsonOnlyIncludeRegexes) { + const includes = validatedIncludeSpecs.filter(s => endsWith(s, Extension.Json)); + const includeFilePatterns = map(getRegularExpressionsForWildcards(includes, basePath, "files"), pattern => `^${pattern}$`); + jsonOnlyIncludeRegexes = includeFilePatterns ? includeFilePatterns.map(pattern => getRegexFromPattern(pattern, host.useCaseSensitiveFileNames)) : emptyArray; } - // If we have already included a literal or wildcard path with a - // higher priority extension, we should skip this file. - // - // This handles cases where we may encounter both .ts and - // .d.ts (or .js if "allowJs" is enabled) in the same - // directory when they are compilation outputs. - if (hasFileWithHigherPriorityExtension(file, literalFileMap, wildcardFileMap, supportedExtensions, keyMapper)) { - continue; + const includeIndex = findIndex(jsonOnlyIncludeRegexes, re => re.test(file)); + if (includeIndex !== -1) { + const key = keyMapper(file); + if (!literalFileMap.has(key) && !wildCardJsonFileMap.has(key)) { + wildCardJsonFileMap.set(key, file); + } } + continue; + } + // If we have already included a literal or wildcard path with a + // higher priority extension, we should skip this file. + // + // This handles cases where we may encounter both .ts and + // .d.ts (or .js if "allowJs" is enabled) in the same + // directory when they are compilation outputs. + if (hasFileWithHigherPriorityExtension(file, literalFileMap, wildcardFileMap, supportedExtensions, keyMapper)) { + continue; + } - // We may have included a wildcard path with a lower priority - // extension due to the user-defined order of entries in the - // "include" array. If there is a lower priority extension in the - // same directory, we should remove it. - removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, supportedExtensions, keyMapper); + // We may have included a wildcard path with a lower priority + // extension due to the user-defined order of entries in the + // "include" array. If there is a lower priority extension in the + // same directory, we should remove it. + removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, supportedExtensions, keyMapper); - const key = keyMapper(file); - if (!literalFileMap.has(key) && !wildcardFileMap.has(key)) { - wildcardFileMap.set(key, file); - } + const key = keyMapper(file); + if (!literalFileMap.has(key) && !wildcardFileMap.has(key)) { + wildcardFileMap.set(key, file); } } - - const literalFiles = arrayFrom(literalFileMap.values()); - const wildcardFiles = arrayFrom(wildcardFileMap.values()); - - return literalFiles.concat(wildcardFiles, arrayFrom(wildCardJsonFileMap.values())); } - /* @internal */ - export function isExcludedFile( - pathToCheck: string, - spec: ConfigFileSpecs, - basePath: string, - useCaseSensitiveFileNames: boolean, - currentDirectory: string - ): boolean { - const { validatedFilesSpec, validatedIncludeSpecs, validatedExcludeSpecs } = spec; - if (!length(validatedIncludeSpecs) || !length(validatedExcludeSpecs)) return false; + const literalFiles = arrayFrom(literalFileMap.values()); + const wildcardFiles = arrayFrom(wildcardFileMap.values()); - basePath = normalizePath(basePath); + return literalFiles.concat(wildcardFiles, arrayFrom(wildCardJsonFileMap.values())); +} - const keyMapper = createGetCanonicalFileName(useCaseSensitiveFileNames); - if (validatedFilesSpec) { - for (const fileName of validatedFilesSpec) { - if (keyMapper(getNormalizedAbsolutePath(fileName, basePath)) === pathToCheck) return false; - } - } +/* @internal */ +export function isExcludedFile( + pathToCheck: string, + spec: ConfigFileSpecs, + basePath: string, + useCaseSensitiveFileNames: boolean, + currentDirectory: string +): boolean { + const { validatedFilesSpec, validatedIncludeSpecs, validatedExcludeSpecs } = spec; + if (!length(validatedIncludeSpecs) || !length(validatedExcludeSpecs)) return false; - return matchesExcludeWorker(pathToCheck, validatedExcludeSpecs, useCaseSensitiveFileNames, currentDirectory, basePath); - } + basePath = normalizePath(basePath); - function invalidDotDotAfterRecursiveWildcard(s: string) { - // We used to use the regex /(^|\/)\*\*\/(.*\/)?\.\.($|\/)/ to check for this case, but - // in v8, that has polynomial performance because the recursive wildcard match - **/ - - // can be matched in many arbitrary positions when multiple are present, resulting - // in bad backtracking (and we don't care which is matched - just that some /.. segment - // comes after some **/ segment). - const wildcardIndex = startsWith(s, "**/") ? 0 : s.indexOf("/**/"); - if (wildcardIndex === -1) { - return false; + const keyMapper = createGetCanonicalFileName(useCaseSensitiveFileNames); + if (validatedFilesSpec) { + for (const fileName of validatedFilesSpec) { + if (keyMapper(getNormalizedAbsolutePath(fileName, basePath)) === pathToCheck) return false; } - const lastDotIndex = endsWith(s, "/..") ? s.length : s.lastIndexOf("/../"); - return lastDotIndex > wildcardIndex; } - /* @internal */ - export function matchesExclude( - pathToCheck: string, - excludeSpecs: readonly string[] | undefined, - useCaseSensitiveFileNames: boolean, - currentDirectory: string - ) { - return matchesExcludeWorker( - pathToCheck, - filter(excludeSpecs, spec => !invalidDotDotAfterRecursiveWildcard(spec)), - useCaseSensitiveFileNames, - currentDirectory - ); - } - - function matchesExcludeWorker( - pathToCheck: string, - excludeSpecs: readonly string[] | undefined, - useCaseSensitiveFileNames: boolean, - currentDirectory: string, - basePath?: string - ) { - const excludePattern = getRegularExpressionForWildcard(excludeSpecs, combinePaths(normalizePath(currentDirectory), basePath), "exclude"); - const excludeRegex = excludePattern && getRegexFromPattern(excludePattern, useCaseSensitiveFileNames); - if (!excludeRegex) return false; - if (excludeRegex.test(pathToCheck)) return true; - return !hasExtension(pathToCheck) && excludeRegex.test(ensureTrailingDirectorySeparator(pathToCheck)); - } - - function validateSpecs(specs: readonly string[], errors: Push, disallowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] { - return specs.filter(spec => { - if (!isString(spec)) return false; - const diag = specToDiagnostic(spec, disallowTrailingRecursion); - if (diag !== undefined) { - errors.push(createDiagnostic(...diag)); - } - return diag === undefined; - }); + return matchesExcludeWorker(pathToCheck, validatedExcludeSpecs, useCaseSensitiveFileNames, currentDirectory, basePath); +} - function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic { - const element = getTsConfigPropArrayElementValue(jsonSourceFile, specKey, spec); - return element ? - createDiagnosticForNodeInSourceFile(jsonSourceFile!, element, message, spec) : - createCompilerDiagnostic(message, spec); - } +function invalidDotDotAfterRecursiveWildcard(s: string) { + // We used to use the regex /(^|\/)\*\*\/(.*\/)?\.\.($|\/)/ to check for this case, but + // in v8, that has polynomial performance because the recursive wildcard match - **/ - + // can be matched in many arbitrary positions when multiple are present, resulting + // in bad backtracking (and we don't care which is matched - just that some /.. segment + // comes after some **/ segment). + const wildcardIndex = startsWith(s, "**/") ? 0 : s.indexOf("/**/"); + if (wildcardIndex === -1) { + return false; } + const lastDotIndex = endsWith(s, "/..") ? s.length : s.lastIndexOf("/../"); + return lastDotIndex > wildcardIndex; +} - function specToDiagnostic(spec: string, disallowTrailingRecursion?: boolean): [DiagnosticMessage, string] | undefined { - if (disallowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) { - return [Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec]; - } - else if (invalidDotDotAfterRecursiveWildcard(spec)) { - return [Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec]; +/* @internal */ +export function matchesExclude( + pathToCheck: string, + excludeSpecs: readonly string[] | undefined, + useCaseSensitiveFileNames: boolean, + currentDirectory: string +) { + return matchesExcludeWorker( + pathToCheck, + filter(excludeSpecs, spec => !invalidDotDotAfterRecursiveWildcard(spec)), + useCaseSensitiveFileNames, + currentDirectory + ); +} + +function matchesExcludeWorker( + pathToCheck: string, + excludeSpecs: readonly string[] | undefined, + useCaseSensitiveFileNames: boolean, + currentDirectory: string, + basePath?: string +) { + const excludePattern = getRegularExpressionForWildcard(excludeSpecs, combinePaths(normalizePath(currentDirectory), basePath), "exclude"); + const excludeRegex = excludePattern && getRegexFromPattern(excludePattern, useCaseSensitiveFileNames); + if (!excludeRegex) return false; + if (excludeRegex.test(pathToCheck)) return true; + return !hasExtension(pathToCheck) && excludeRegex.test(ensureTrailingDirectorySeparator(pathToCheck)); +} + +function validateSpecs(specs: readonly string[], errors: Push, disallowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] { + return specs.filter(spec => { + if (!isString(spec)) return false; + const diag = specToDiagnostic(spec, disallowTrailingRecursion); + if (diag !== undefined) { + errors.push(createDiagnostic(...diag)); } + return diag === undefined; + }); + + function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic { + const element = getTsConfigPropArrayElementValue(jsonSourceFile, specKey, spec); + return element ? + createDiagnosticForNodeInSourceFile(jsonSourceFile!, element, message, spec) : + createCompilerDiagnostic(message, spec); } +} - /** - * Gets directories in a set of include patterns that should be watched for changes. - */ - function getWildcardDirectories({ validatedIncludeSpecs: include, validatedExcludeSpecs: exclude }: ConfigFileSpecs, path: string, useCaseSensitiveFileNames: boolean): MapLike { - // We watch a directory recursively if it contains a wildcard anywhere in a directory segment - // of the pattern: - // - // /a/b/**/d - Watch /a/b recursively to catch changes to any d in any subfolder recursively - // /a/b/*/d - Watch /a/b recursively to catch any d in any immediate subfolder, even if a new subfolder is added - // /a/b - Watch /a/b recursively to catch changes to anything in any recursive subfoler - // - // We watch a directory without recursion if it contains a wildcard in the file segment of - // the pattern: - // - // /a/b/* - Watch /a/b directly to catch any new file - // /a/b/a?z - Watch /a/b directly to catch any new file matching a?z - const rawExcludeRegex = getRegularExpressionForWildcard(exclude, path, "exclude"); - const excludeRegex = rawExcludeRegex && new RegExp(rawExcludeRegex, useCaseSensitiveFileNames ? "" : "i"); - const wildcardDirectories: MapLike = {}; - if (include !== undefined) { - const recursiveKeys: string[] = []; - for (const file of include) { - const spec = normalizePath(combinePaths(path, file)); - if (excludeRegex && excludeRegex.test(spec)) { - continue; - } +function specToDiagnostic(spec: string, disallowTrailingRecursion?: boolean): [DiagnosticMessage, string] | undefined { + if (disallowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) { + return [Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec]; + } + else if (invalidDotDotAfterRecursiveWildcard(spec)) { + return [Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec]; + } +} - const match = getWildcardDirectoryFromSpec(spec, useCaseSensitiveFileNames); - if (match) { - const { key, flags } = match; - const existingFlags = wildcardDirectories[key]; - if (existingFlags === undefined || existingFlags < flags) { - wildcardDirectories[key] = flags; - if (flags === WatchDirectoryFlags.Recursive) { - recursiveKeys.push(key); - } +/** + * Gets directories in a set of include patterns that should be watched for changes. + */ +function getWildcardDirectories({ validatedIncludeSpecs: include, validatedExcludeSpecs: exclude }: ConfigFileSpecs, path: string, useCaseSensitiveFileNames: boolean): MapLike { + // We watch a directory recursively if it contains a wildcard anywhere in a directory segment + // of the pattern: + // + // /a/b/**/d - Watch /a/b recursively to catch changes to any d in any subfolder recursively + // /a/b/*/d - Watch /a/b recursively to catch any d in any immediate subfolder, even if a new subfolder is added + // /a/b - Watch /a/b recursively to catch changes to anything in any recursive subfoler + // + // We watch a directory without recursion if it contains a wildcard in the file segment of + // the pattern: + // + // /a/b/* - Watch /a/b directly to catch any new file + // /a/b/a?z - Watch /a/b directly to catch any new file matching a?z + const rawExcludeRegex = getRegularExpressionForWildcard(exclude, path, "exclude"); + const excludeRegex = rawExcludeRegex && new RegExp(rawExcludeRegex, useCaseSensitiveFileNames ? "" : "i"); + const wildcardDirectories: MapLike = {}; + if (include !== undefined) { + const recursiveKeys: string[] = []; + for (const file of include) { + const spec = normalizePath(combinePaths(path, file)); + if (excludeRegex && excludeRegex.test(spec)) { + continue; + } + + const match = getWildcardDirectoryFromSpec(spec, useCaseSensitiveFileNames); + if (match) { + const { key, flags } = match; + const existingFlags = wildcardDirectories[key]; + if (existingFlags === undefined || existingFlags < flags) { + wildcardDirectories[key] = flags; + if (flags === WatchDirectoryFlags.Recursive) { + recursiveKeys.push(key); } } } + } - // Remove any subpaths under an existing recursively watched directory. - for (const key in wildcardDirectories) { - if (hasProperty(wildcardDirectories, key)) { - for (const recursiveKey of recursiveKeys) { - if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { - delete wildcardDirectories[key]; - } + // Remove any subpaths under an existing recursively watched directory. + for (const key in wildcardDirectories) { + if (hasProperty(wildcardDirectories, key)) { + for (const recursiveKey of recursiveKeys) { + if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { + delete wildcardDirectories[key]; } } } } + } - return wildcardDirectories; - } - - function getWildcardDirectoryFromSpec(spec: string, useCaseSensitiveFileNames: boolean): { key: string, flags: WatchDirectoryFlags } | undefined { - const match = wildcardDirectoryPattern.exec(spec); - if (match) { - // We check this with a few `indexOf` calls because 3 `indexOf`/`lastIndexOf` calls is - // less algorithmically complex (roughly O(3n) worst-case) than the regex we used to use, - // \/[^/]*?[*?][^/]*\/ which was polynominal in v8, since arbitrary sequences of wildcard - // characters could match any of the central patterns, resulting in bad backtracking. - const questionWildcardIndex = spec.indexOf("?"); - const starWildcardIndex = spec.indexOf("*"); - const lastDirectorySeperatorIndex = spec.lastIndexOf(directorySeparator); - return { - key: useCaseSensitiveFileNames ? match[0] : toFileNameLowerCase(match[0]), - flags: (questionWildcardIndex !== -1 && questionWildcardIndex < lastDirectorySeperatorIndex) - || (starWildcardIndex !== -1 && starWildcardIndex < lastDirectorySeperatorIndex) - ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None - }; - } - if (isImplicitGlob(spec)) { - return { - key: useCaseSensitiveFileNames ? spec : toFileNameLowerCase(spec), - flags: WatchDirectoryFlags.Recursive - }; - } - return undefined; + return wildcardDirectories; +} + +function getWildcardDirectoryFromSpec(spec: string, useCaseSensitiveFileNames: boolean): { key: string, flags: WatchDirectoryFlags } | undefined { + const match = wildcardDirectoryPattern.exec(spec); + if (match) { + // We check this with a few `indexOf` calls because 3 `indexOf`/`lastIndexOf` calls is + // less algorithmically complex (roughly O(3n) worst-case) than the regex we used to use, + // \/[^/]*?[*?][^/]*\/ which was polynominal in v8, since arbitrary sequences of wildcard + // characters could match any of the central patterns, resulting in bad backtracking. + const questionWildcardIndex = spec.indexOf("?"); + const starWildcardIndex = spec.indexOf("*"); + const lastDirectorySeperatorIndex = spec.lastIndexOf(directorySeparator); + return { + key: useCaseSensitiveFileNames ? match[0] : toFileNameLowerCase(match[0]), + flags: (questionWildcardIndex !== -1 && questionWildcardIndex < lastDirectorySeperatorIndex) + || (starWildcardIndex !== -1 && starWildcardIndex < lastDirectorySeperatorIndex) + ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None + }; } + if (isImplicitGlob(spec)) { + return { + key: useCaseSensitiveFileNames ? spec : toFileNameLowerCase(spec), + flags: WatchDirectoryFlags.Recursive + }; + } + return undefined; +} - /** - * Determines whether a literal or wildcard file has already been included that has a higher - * extension priority. - * - * @param file The path to the file. - */ - function hasFileWithHigherPriorityExtension(file: string, literalFiles: ESMap, wildcardFiles: ESMap, extensions: readonly string[][], keyMapper: (value: string) => string) { - const extensionGroup = forEach(extensions, group => fileExtensionIsOneOf(file, group) ? group : undefined); - if (!extensionGroup) { +/** + * Determines whether a literal or wildcard file has already been included that has a higher + * extension priority. + * + * @param file The path to the file. + */ +function hasFileWithHigherPriorityExtension(file: string, literalFiles: ESMap, wildcardFiles: ESMap, extensions: readonly string[][], keyMapper: (value: string) => string) { + const extensionGroup = forEach(extensions, group => fileExtensionIsOneOf(file, group) ? group : undefined); + if (!extensionGroup) { + return false; + } + for (const ext of extensionGroup) { + if (fileExtensionIs(file, ext)) { return false; } - for (const ext of extensionGroup) { - if (fileExtensionIs(file, ext)) { - return false; - } - const higherPriorityPath = keyMapper(changeExtension(file, ext)); - if (literalFiles.has(higherPriorityPath) || wildcardFiles.has(higherPriorityPath)) { - if (ext === Extension.Dts && (fileExtensionIs(file, Extension.Js) || fileExtensionIs(file, Extension.Jsx))) { - // LEGACY BEHAVIOR: An off-by-one bug somewhere in the extension priority system for wildcard module loading allowed declaration - // files to be loaded alongside their js(x) counterparts. We regard this as generally undesirable, but retain the behavior to - // prevent breakage. - continue; - } - return true; + const higherPriorityPath = keyMapper(changeExtension(file, ext)); + if (literalFiles.has(higherPriorityPath) || wildcardFiles.has(higherPriorityPath)) { + if (ext === Extension.Dts && (fileExtensionIs(file, Extension.Js) || fileExtensionIs(file, Extension.Jsx))) { + // LEGACY BEHAVIOR: An off-by-one bug somewhere in the extension priority system for wildcard module loading allowed declaration + // files to be loaded alongside their js(x) counterparts. We regard this as generally undesirable, but retain the behavior to + // prevent breakage. + continue; } + return true; } - - return false; } - /** - * Removes files included via wildcard expansion with a lower extension priority that have - * already been included. - * - * @param file The path to the file. - */ - function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: ESMap, extensions: readonly string[][], keyMapper: (value: string) => string) { - const extensionGroup = forEach(extensions, group => fileExtensionIsOneOf(file, group) ? group : undefined); - if (!extensionGroup) { + return false; +} + +/** + * Removes files included via wildcard expansion with a lower extension priority that have + * already been included. + * + * @param file The path to the file. + */ +function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: ESMap, extensions: readonly string[][], keyMapper: (value: string) => string) { + const extensionGroup = forEach(extensions, group => fileExtensionIsOneOf(file, group) ? group : undefined); + if (!extensionGroup) { + return; + } + for (let i = extensionGroup.length - 1; i >= 0; i--) { + const ext = extensionGroup[i]; + if (fileExtensionIs(file, ext)) { return; } - for (let i = extensionGroup.length - 1; i >= 0; i--) { - const ext = extensionGroup[i]; - if (fileExtensionIs(file, ext)) { - return; + const lowerPriorityPath = keyMapper(changeExtension(file, ext)); + wildcardFiles.delete(lowerPriorityPath); + } +} + +/** + * Produces a cleaned version of compiler options with personally identifying info (aka, paths) removed. + * Also converts enum values back to strings. + */ +/* @internal */ +export function convertCompilerOptionsForTelemetry(opts: CompilerOptions): CompilerOptions { + const out: CompilerOptions = {}; + for (const key in opts) { + if (opts.hasOwnProperty(key)) { + const type = getOptionFromName(key); + if (type !== undefined) { // Ignore unknown options + out[key] = getOptionValueWithEmptyStrings(opts[key], type); } - const lowerPriorityPath = keyMapper(changeExtension(file, ext)); - wildcardFiles.delete(lowerPriorityPath); } } + return out; +} - /** - * Produces a cleaned version of compiler options with personally identifying info (aka, paths) removed. - * Also converts enum values back to strings. - */ - /* @internal */ - export function convertCompilerOptionsForTelemetry(opts: CompilerOptions): CompilerOptions { - const out: CompilerOptions = {}; - for (const key in opts) { - if (opts.hasOwnProperty(key)) { - const type = getOptionFromName(key); - if (type !== undefined) { // Ignore unknown options - out[key] = getOptionValueWithEmptyStrings(opts[key], type); +function getOptionValueWithEmptyStrings(value: any, option: CommandLineOption): {} { + switch (option.type) { + case "object": // "paths". Can't get any useful information from the value since we blank out strings, so just return "". + return ""; + case "string": // Could be any arbitrary string -- use empty string instead. + return ""; + case "number": // Allow numbers, but be sure to check it's actually a number. + return typeof value === "number" ? value : ""; + case "boolean": + return typeof value === "boolean" ? value : ""; + case "list": + const elementType = option.element; + return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, elementType)) : ""; + default: + return forEachEntry(option.type, (optionEnumValue, optionStringValue) => { + if (optionEnumValue === value) { + return optionStringValue; } - } - } - return out; - } - - function getOptionValueWithEmptyStrings(value: any, option: CommandLineOption): {} { - switch (option.type) { - case "object": // "paths". Can't get any useful information from the value since we blank out strings, so just return "". - return ""; - case "string": // Could be any arbitrary string -- use empty string instead. - return ""; - case "number": // Allow numbers, but be sure to check it's actually a number. - return typeof value === "number" ? value : ""; - case "boolean": - return typeof value === "boolean" ? value : ""; - case "list": - const elementType = option.element; - return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, elementType)) : ""; - default: - return forEachEntry(option.type, (optionEnumValue, optionStringValue) => { - if (optionEnumValue === value) { - return optionStringValue; - } - })!; // TODO: GH#18217 - } + })!; // TODO: GH#18217 } +} - function getDefaultValueForOption(option: CommandLineOption) { - switch (option.type) { - case "number": - return 1; - case "boolean": - return true; - case "string": - return option.isFilePath ? "./" : ""; - case "list": - return []; - case "object": - return {}; - default: - const iterResult = option.type.keys().next(); - if (!iterResult.done) return iterResult.value; - return Debug.fail("Expected 'option.type' to have entries."); - } +function getDefaultValueForOption(option: CommandLineOption) { + switch (option.type) { + case "number": + return 1; + case "boolean": + return true; + case "string": + return option.isFilePath ? "./" : ""; + case "list": + return []; + case "object": + return {}; + default: + const iterResult = option.type.keys().next(); + if (!iterResult.done) return iterResult.value; + return Debug.fail("Expected 'option.type' to have entries."); } } +} diff --git a/src/compiler/core.ts b/src/compiler/core.ts index cfa677be3ea64..0106b4e056dce 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1,364 +1,405 @@ /* @internal */ namespace ts { - export function getIterator | ReadonlyESMap | undefined>(iterable: I): Iterator< - I extends ReadonlyESMap ? [K, V] : - I extends ReadonlySet ? T : - I extends readonly (infer T)[] ? T : - I extends undefined ? undefined : - never>; - export function getIterator(iterable: ReadonlyESMap): Iterator<[K, V]>; - export function getIterator(iterable: ReadonlyESMap | undefined): Iterator<[K, V]> | undefined; - export function getIterator(iterable: readonly T[] | ReadonlySet): Iterator; - export function getIterator(iterable: readonly T[] | ReadonlySet | undefined): Iterator | undefined; - export function getIterator(iterable: readonly any[] | ReadonlySet | ReadonlyESMap | undefined): Iterator | undefined { - if (iterable) { - if (isArray(iterable)) return arrayIterator(iterable); - if (iterable instanceof Map) return iterable.entries(); - if (iterable instanceof Set) return iterable.values(); - throw new Error("Iteration not supported."); - } +export function getIterator | ReadonlyESMap | undefined>(iterable: I): Iterator< + I extends ReadonlyESMap ? [K, V] : + I extends ReadonlySet ? T : + I extends readonly (infer T)[] ? T : + I extends undefined ? undefined : + never>; +export function getIterator(iterable: ReadonlyESMap): Iterator<[K, V]>; +export function getIterator(iterable: ReadonlyESMap | undefined): Iterator<[K, V]> | undefined; +export function getIterator(iterable: readonly T[] | ReadonlySet): Iterator; +export function getIterator(iterable: readonly T[] | ReadonlySet | undefined): Iterator | undefined; +export function getIterator(iterable: readonly any[] | ReadonlySet | ReadonlyESMap | undefined): Iterator | undefined { + if (iterable) { + if (isArray(iterable)) return arrayIterator(iterable); + if (iterable instanceof Map) return iterable.entries(); + if (iterable instanceof Set) return iterable.values(); + throw new Error("Iteration not supported."); } +} - export const emptyArray: never[] = [] as never[]; - export const emptyMap: ReadonlyESMap = new Map(); - export const emptySet: ReadonlySet = new Set(); +export const emptyArray: never[] = [] as never[]; +export const emptyMap: ReadonlyESMap = new Map(); +export const emptySet: ReadonlySet = new Set(); - export function length(array: readonly any[] | undefined): number { - return array ? array.length : 0; - } +export function length(array: readonly any[] | undefined): number { + return array ? array.length : 0; +} - /** - * Iterates through 'array' by index and performs the callback on each element of array until the callback - * returns a truthy value, then returns that value. - * If no such value is found, the callback is applied to each element of array and undefined is returned. - */ - export function forEach(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { - if (array) { - for (let i = 0; i < array.length; i++) { - const result = callback(array[i], i); - if (result) { - return result; - } +/** + * Iterates through 'array' by index and performs the callback on each element of array until the callback + * returns a truthy value, then returns that value. + * If no such value is found, the callback is applied to each element of array and undefined is returned. + */ +export function forEach(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { + if (array) { + for (let i = 0; i < array.length; i++) { + const result = callback(array[i], i); + if (result) { + return result; } } - return undefined; } + return undefined; +} - /** - * Like `forEach`, but iterates in reverse order. - */ - export function forEachRight(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { - if (array) { - for (let i = array.length - 1; i >= 0; i--) { - const result = callback(array[i], i); - if (result) { - return result; - } +/** + * Like `forEach`, but iterates in reverse order. + */ +export function forEachRight(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { + if (array) { + for (let i = array.length - 1; i >= 0; i--) { + const result = callback(array[i], i); + if (result) { + return result; } } + } + return undefined; +} + +/** Like `forEach`, but suitable for use with numbers and strings (which may be falsy). */ +export function firstDefined(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { + if (array === undefined) { return undefined; } - /** Like `forEach`, but suitable for use with numbers and strings (which may be falsy). */ - export function firstDefined(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { - if (array === undefined) { - return undefined; + for (let i = 0; i < array.length; i++) { + const result = callback(array[i], i); + if (result !== undefined) { + return result; } + } + return undefined; +} - for (let i = 0; i < array.length; i++) { - const result = callback(array[i], i); - if (result !== undefined) { - return result; - } +export function firstDefinedIterator(iter: Iterator, callback: (element: T) => U | undefined): U | undefined { + while (true) { + const iterResult = iter.next(); + if (iterResult.done) { + return undefined; + } + const result = callback(iterResult.value); + if (result !== undefined) { + return result; } - return undefined; } +} - export function firstDefinedIterator(iter: Iterator, callback: (element: T) => U | undefined): U | undefined { - while (true) { - const iterResult = iter.next(); - if (iterResult.done) { - return undefined; - } - const result = callback(iterResult.value); - if (result !== undefined) { - return result; - } +export function reduceLeftIterator(iterator: Iterator | undefined, f: (memo: U, value: T, i: number) => U, initial: U): U { + let result = initial; + if (iterator) { + for (let step = iterator.next(), pos = 0; !step.done; step = iterator.next(), pos++) { + result = f(result, step.value, pos); } } + return result; +} + +export function zipWith(arrayA: readonly T[], arrayB: readonly U[], callback: (a: T, b: U, index: number) => V): V[] { + const result: V[] = []; + Debug.assertEqual(arrayA.length, arrayB.length); + for (let i = 0; i < arrayA.length; i++) { + result.push(callback(arrayA[i], arrayB[i], i)); + } + return result; +} - export function reduceLeftIterator(iterator: Iterator | undefined, f: (memo: U, value: T, i: number) => U, initial: U): U { - let result = initial; - if (iterator) { - for (let step = iterator.next(), pos = 0; !step.done; step = iterator.next(), pos++) { - result = f(result, step.value, pos); +export function zipToIterator(arrayA: readonly T[], arrayB: readonly U[]): Iterator<[T, U]> { + Debug.assertEqual(arrayA.length, arrayB.length); + let i = 0; + return { + next() { + if (i === arrayA.length) { + return { value: undefined as never, done: true }; } + i++; + return { value: [arrayA[i - 1], arrayB[i - 1]] as [T, U], done: false }; } - return result; + }; +} + +export function zipToMap(keys: readonly K[], values: readonly V[]): ESMap { + Debug.assert(keys.length === values.length); + const map = new Map(); + for (let i = 0; i < keys.length; ++i) { + map.set(keys[i], values[i]); } + return map; +} - export function zipWith(arrayA: readonly T[], arrayB: readonly U[], callback: (a: T, b: U, index: number) => V): V[] { - const result: V[] = []; - Debug.assertEqual(arrayA.length, arrayB.length); - for (let i = 0; i < arrayA.length; i++) { - result.push(callback(arrayA[i], arrayB[i], i)); - } - return result; +/** + * Creates a new array with `element` interspersed in between each element of `input` + * if there is more than 1 value in `input`. Otherwise, returns the existing array. + */ +export function intersperse(input: T[], element: T): T[] { + if (input.length <= 1) { + return input; } + const result: T[] = []; + for (let i = 0, n = input.length; i < n; i++) { + if (i) result.push(element); + result.push(input[i]); + } + return result; +} - export function zipToIterator(arrayA: readonly T[], arrayB: readonly U[]): Iterator<[T, U]> { - Debug.assertEqual(arrayA.length, arrayB.length); - let i = 0; - return { - next() { - if (i === arrayA.length) { - return { value: undefined as never, done: true }; - } - i++; - return { value: [arrayA[i - 1], arrayB[i - 1]] as [T, U], done: false }; +/** + * Iterates through `array` by index and performs the callback on each element of array until the callback + * returns a falsey value, then returns false. + * If no such value is found, the callback is applied to each element of array and `true` is returned. + */ +export function every(array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean { + if (array) { + for (let i = 0; i < array.length; i++) { + if (!callback(array[i], i)) { + return false; } - }; + } } - export function zipToMap(keys: readonly K[], values: readonly V[]): ESMap { - Debug.assert(keys.length === values.length); - const map = new Map(); - for (let i = 0; i < keys.length; ++i) { - map.set(keys[i], values[i]); + return true; +} + +/** Works like Array.prototype.find, returning `undefined` if no element satisfying the predicate is found. */ +export function find(array: readonly T[], predicate: (element: T, index: number) => element is U): U | undefined; +export function find(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined; +export function find(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined { + for (let i = 0; i < array.length; i++) { + const value = array[i]; + if (predicate(value, i)) { + return value; } - return map; } + return undefined; +} - /** - * Creates a new array with `element` interspersed in between each element of `input` - * if there is more than 1 value in `input`. Otherwise, returns the existing array. - */ - export function intersperse(input: T[], element: T): T[] { - if (input.length <= 1) { - return input; - } - const result: T[] = []; - for (let i = 0, n = input.length; i < n; i++) { - if (i) result.push(element); - result.push(input[i]); +export function findLast(array: readonly T[], predicate: (element: T, index: number) => element is U): U | undefined; +export function findLast(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined; +export function findLast(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined { + for (let i = array.length - 1; i >= 0; i--) { + const value = array[i]; + if (predicate(value, i)) { + return value; } - return result; } + return undefined; +} - /** - * Iterates through `array` by index and performs the callback on each element of array until the callback - * returns a falsey value, then returns false. - * If no such value is found, the callback is applied to each element of array and `true` is returned. - */ - export function every(array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean { - if (array) { - for (let i = 0; i < array.length; i++) { - if (!callback(array[i], i)) { - return false; - } - } +/** Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found. */ +export function findIndex(array: readonly T[], predicate: (element: T, index: number) => boolean, startIndex?: number): number { + for (let i = startIndex || 0; i < array.length; i++) { + if (predicate(array[i], i)) { + return i; } - - return true; } + return -1; +} - /** Works like Array.prototype.find, returning `undefined` if no element satisfying the predicate is found. */ - export function find(array: readonly T[], predicate: (element: T, index: number) => element is U): U | undefined; - export function find(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined; - export function find(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined { - for (let i = 0; i < array.length; i++) { - const value = array[i]; - if (predicate(value, i)) { - return value; - } +export function findLastIndex(array: readonly T[], predicate: (element: T, index: number) => boolean, startIndex?: number): number { + for (let i = startIndex === undefined ? array.length - 1 : startIndex; i >= 0; i--) { + if (predicate(array[i], i)) { + return i; } - return undefined; } + return -1; +} - export function findLast(array: readonly T[], predicate: (element: T, index: number) => element is U): U | undefined; - export function findLast(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined; - export function findLast(array: readonly T[], predicate: (element: T, index: number) => boolean): T | undefined { - for (let i = array.length - 1; i >= 0; i--) { - const value = array[i]; - if (predicate(value, i)) { - return value; - } +/** + * Returns the first truthy result of `callback`, or else fails. + * This is like `forEach`, but never returns undefined. + */ +export function findMap(array: readonly T[], callback: (element: T, index: number) => U | undefined): U { + for (let i = 0; i < array.length; i++) { + const result = callback(array[i], i); + if (result) { + return result; } - return undefined; } + return Debug.fail(); +} - /** Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found. */ - export function findIndex(array: readonly T[], predicate: (element: T, index: number) => boolean, startIndex?: number): number { - for (let i = startIndex || 0; i < array.length; i++) { - if (predicate(array[i], i)) { - return i; +export function contains(array: readonly T[] | undefined, value: T, equalityComparer: EqualityComparer = equateValues): boolean { + if (array) { + for (const v of array) { + if (equalityComparer(v, value)) { + return true; } } - return -1; } + return false; +} - export function findLastIndex(array: readonly T[], predicate: (element: T, index: number) => boolean, startIndex?: number): number { - for (let i = startIndex === undefined ? array.length - 1 : startIndex; i >= 0; i--) { - if (predicate(array[i], i)) { - return i; - } +export function arraysEqual(a: readonly T[], b: readonly T[], equalityComparer: EqualityComparer = equateValues): boolean { + return a.length === b.length && a.every((x, i) => equalityComparer(x, b[i])); +} + +export function indexOfAnyCharCode(text: string, charCodes: readonly number[], start?: number): number { + for (let i = start || 0; i < text.length; i++) { + if (contains(charCodes, text.charCodeAt(i))) { + return i; } - return -1; } + return -1; +} - /** - * Returns the first truthy result of `callback`, or else fails. - * This is like `forEach`, but never returns undefined. - */ - export function findMap(array: readonly T[], callback: (element: T, index: number) => U | undefined): U { +export function countWhere(array: readonly T[] | undefined, predicate: (x: T, i: number) => boolean): number { + let count = 0; + if (array) { for (let i = 0; i < array.length; i++) { - const result = callback(array[i], i); - if (result) { - return result; + const v = array[i]; + if (predicate(v, i)) { + count++; } } - return Debug.fail(); } + return count; +} - export function contains(array: readonly T[] | undefined, value: T, equalityComparer: EqualityComparer = equateValues): boolean { - if (array) { - for (const v of array) { - if (equalityComparer(v, value)) { - return true; +/** + * Filters an array by a predicate function. Returns the same array instance if the predicate is + * true for all elements, otherwise returns a new array instance containing the filtered subset. + */ +export function filter(array: T[], f: (x: T) => x is U): U[]; +export function filter(array: T[], f: (x: T) => boolean): T[]; +export function filter(array: readonly T[], f: (x: T) => x is U): readonly U[]; +export function filter(array: readonly T[], f: (x: T) => boolean): readonly T[]; +export function filter(array: T[] | undefined, f: (x: T) => x is U): U[] | undefined; +export function filter(array: T[] | undefined, f: (x: T) => boolean): T[] | undefined; +export function filter(array: readonly T[] | undefined, f: (x: T) => x is U): readonly U[] | undefined; +export function filter(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined; +export function filter(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined { + if (array) { + const len = array.length; + let i = 0; + while (i < len && f(array[i])) i++; + if (i < len) { + const result = array.slice(0, i); + i++; + while (i < len) { + const item = array[i]; + if (f(item)) { + result.push(item); } + i++; } + return result; } - return false; } + return array; +} - export function arraysEqual(a: readonly T[], b: readonly T[], equalityComparer: EqualityComparer = equateValues): boolean { - return a.length === b.length && a.every((x, i) => equalityComparer(x, b[i])); +export function filterMutate(array: T[], f: (x: T, i: number, array: T[]) => boolean): void { + let outIndex = 0; + for (let i = 0; i < array.length; i++) { + if (f(array[i], i, array)) { + array[outIndex] = array[i]; + outIndex++; + } } + array.length = outIndex; +} - export function indexOfAnyCharCode(text: string, charCodes: readonly number[], start?: number): number { - for (let i = start || 0; i < text.length; i++) { - if (contains(charCodes, text.charCodeAt(i))) { - return i; - } +export function clear(array: {}[]): void { + array.length = 0; +} + +export function map(array: readonly T[], f: (x: T, i: number) => U): U[]; +export function map(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined; +export function map(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined { + let result: U[] | undefined; + if (array) { + result = []; + for (let i = 0; i < array.length; i++) { + result.push(f(array[i], i)); } - return -1; } + return result; +} - export function countWhere(array: readonly T[] | undefined, predicate: (x: T, i: number) => boolean): number { - let count = 0; - if (array) { - for (let i = 0; i < array.length; i++) { - const v = array[i]; - if (predicate(v, i)) { - count++; - } - } + +export function mapIterator(iter: Iterator, mapFn: (x: T) => U): Iterator { + return { + next() { + const iterRes = iter.next(); + return iterRes.done ? iterRes as { done: true, value: never } : { value: mapFn(iterRes.value), done: false }; } - return count; - } + }; +} - /** - * Filters an array by a predicate function. Returns the same array instance if the predicate is - * true for all elements, otherwise returns a new array instance containing the filtered subset. - */ - export function filter(array: T[], f: (x: T) => x is U): U[]; - export function filter(array: T[], f: (x: T) => boolean): T[]; - export function filter(array: readonly T[], f: (x: T) => x is U): readonly U[]; - export function filter(array: readonly T[], f: (x: T) => boolean): readonly T[]; - export function filter(array: T[] | undefined, f: (x: T) => x is U): U[] | undefined; - export function filter(array: T[] | undefined, f: (x: T) => boolean): T[] | undefined; - export function filter(array: readonly T[] | undefined, f: (x: T) => x is U): readonly U[] | undefined; - export function filter(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined; - export function filter(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined { - if (array) { - const len = array.length; - let i = 0; - while (i < len && f(array[i])) i++; - if (i < len) { +// Maps from T to T and avoids allocation if all elements map to themselves +export function sameMap(array: T[], f: (x: T, i: number) => T): T[]; +export function sameMap(array: readonly T[], f: (x: T, i: number) => T): readonly T[]; +export function sameMap(array: T[] | undefined, f: (x: T, i: number) => T): T[] | undefined; +export function sameMap(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined; +export function sameMap(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined { + if (array) { + for (let i = 0; i < array.length; i++) { + const item = array[i]; + const mapped = f(item, i); + if (item !== mapped) { const result = array.slice(0, i); - i++; - while (i < len) { - const item = array[i]; - if (f(item)) { - result.push(item); - } - i++; + result.push(mapped); + for (i++; i < array.length; i++) { + result.push(f(array[i], i)); } return result; } } - return array; } + return array; +} - export function filterMutate(array: T[], f: (x: T, i: number, array: T[]) => boolean): void { - let outIndex = 0; - for (let i = 0; i < array.length; i++) { - if (f(array[i], i, array)) { - array[outIndex] = array[i]; - outIndex++; +/** + * Flattens an array containing a mix of array or non-array elements. + * + * @param array The array to flatten. + */ +export function flatten(array: T[][] | readonly (T | readonly T[] | undefined)[]): T[] { + const result = []; + for (const v of array) { + if (v) { + if (isArray(v)) { + addRange(result, v); } - } - array.length = outIndex; - } - - export function clear(array: {}[]): void { - array.length = 0; - } - - export function map(array: readonly T[], f: (x: T, i: number) => U): U[]; - export function map(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined; - export function map(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined { - let result: U[] | undefined; - if (array) { - result = []; - for (let i = 0; i < array.length; i++) { - result.push(f(array[i], i)); + else { + result.push(v); } } - return result; - } - - - export function mapIterator(iter: Iterator, mapFn: (x: T) => U): Iterator { - return { - next() { - const iterRes = iter.next(); - return iterRes.done ? iterRes as { done: true, value: never } : { value: mapFn(iterRes.value), done: false }; - } - }; } + return result; +} - // Maps from T to T and avoids allocation if all elements map to themselves - export function sameMap(array: T[], f: (x: T, i: number) => T): T[]; - export function sameMap(array: readonly T[], f: (x: T, i: number) => T): readonly T[]; - export function sameMap(array: T[] | undefined, f: (x: T, i: number) => T): T[] | undefined; - export function sameMap(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined; - export function sameMap(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined { - if (array) { - for (let i = 0; i < array.length; i++) { - const item = array[i]; - const mapped = f(item, i); - if (item !== mapped) { - const result = array.slice(0, i); - result.push(mapped); - for (i++; i < array.length; i++) { - result.push(f(array[i], i)); - } - return result; +/** + * Maps an array. If the mapped value is an array, it is spread into the result. + * + * @param array The array to map. + * @param mapfn The callback used to map the result into one or more values. + */ +export function flatMap(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): readonly U[] { + let result: U[] | undefined; + if (array) { + for (let i = 0; i < array.length; i++) { + const v = mapfn(array[i], i); + if (v) { + if (isArray(v)) { + result = addRange(result, v); + } + else { + result = append(result, v); } } } - return array; } + return result || emptyArray; +} - /** - * Flattens an array containing a mix of array or non-array elements. - * - * @param array The array to flatten. - */ - export function flatten(array: T[][] | readonly (T | readonly T[] | undefined)[]): T[] { - const result = []; - for (const v of array) { +export function flatMapToMutable(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): U[] { + const result: U[] = []; + if (array) { + for (let i = 0; i < array.length; i++) { + const v = mapfn(array[i], i); if (v) { if (isArray(v)) { addRange(result, v); @@ -368,1939 +409,1898 @@ namespace ts { } } } - return result; } + return result; +} - /** - * Maps an array. If the mapped value is an array, it is spread into the result. - * - * @param array The array to map. - * @param mapfn The callback used to map the result into one or more values. - */ - export function flatMap(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): readonly U[] { - let result: U[] | undefined; - if (array) { - for (let i = 0; i < array.length; i++) { - const v = mapfn(array[i], i); - if (v) { - if (isArray(v)) { - result = addRange(result, v); - } - else { - result = append(result, v); - } +export function flatMapIterator(iter: Iterator, mapfn: (x: T) => readonly U[] | Iterator | undefined): Iterator { + const first = iter.next(); + if (first.done) { + return emptyIterator; + } + let currentIter = getIterator(first.value); + return { + next() { + while (true) { + const currentRes = currentIter.next(); + if (!currentRes.done) { + return currentRes; } - } - } - return result || emptyArray; - } - - export function flatMapToMutable(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): U[] { - const result: U[] = []; - if (array) { - for (let i = 0; i < array.length; i++) { - const v = mapfn(array[i], i); - if (v) { - if (isArray(v)) { - addRange(result, v); - } - else { - result.push(v); - } + const iterRes = iter.next(); + if (iterRes.done) { + return iterRes as { done: true, value: never }; } + currentIter = getIterator(iterRes.value); } - } - return result; + }, + }; + + function getIterator(x: T): Iterator { + const res = mapfn(x); + return res === undefined ? emptyIterator : isArray(res) ? arrayIterator(res) : res; } +} - export function flatMapIterator(iter: Iterator, mapfn: (x: T) => readonly U[] | Iterator | undefined): Iterator { - const first = iter.next(); - if (first.done) { - return emptyIterator; - } - let currentIter = getIterator(first.value); - return { - next() { - while (true) { - const currentRes = currentIter.next(); - if (!currentRes.done) { - return currentRes; - } - const iterRes = iter.next(); - if (iterRes.done) { - return iterRes as { done: true, value: never }; - } - currentIter = getIterator(iterRes.value); +/** + * Maps an array. If the mapped value is an array, it is spread into the result. + * Avoids allocation if all elements map to themselves. + * + * @param array The array to map. + * @param mapfn The callback used to map the result into one or more values. + */ +export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | readonly T[]): T[]; +export function sameFlatMap(array: readonly T[], mapfn: (x: T, i: number) => T | readonly T[]): readonly T[]; +export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | T[]): T[] { + let result: T[] | undefined; + if (array) { + for (let i = 0; i < array.length; i++) { + const item = array[i]; + const mapped = mapfn(item, i); + if (result || item !== mapped || isArray(mapped)) { + if (!result) { + result = array.slice(0, i); } - }, - }; - - function getIterator(x: T): Iterator { - const res = mapfn(x); - return res === undefined ? emptyIterator : isArray(res) ? arrayIterator(res) : res; + if (isArray(mapped)) { + addRange(result, mapped); + } + else { + result.push(mapped); + } + } } } + return result || array; +} - /** - * Maps an array. If the mapped value is an array, it is spread into the result. - * Avoids allocation if all elements map to themselves. - * - * @param array The array to map. - * @param mapfn The callback used to map the result into one or more values. - */ - export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | readonly T[]): T[]; - export function sameFlatMap(array: readonly T[], mapfn: (x: T, i: number) => T | readonly T[]): readonly T[]; - export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | T[]): T[] { - let result: T[] | undefined; - if (array) { - for (let i = 0; i < array.length; i++) { - const item = array[i]; - const mapped = mapfn(item, i); - if (result || item !== mapped || isArray(mapped)) { - if (!result) { - result = array.slice(0, i); - } - if (isArray(mapped)) { - addRange(result, mapped); - } - else { - result.push(mapped); - } - } - } +export function mapAllOrFail(array: readonly T[], mapFn: (x: T, i: number) => U | undefined): U[] | undefined { + const result: U[] = []; + for (let i = 0; i < array.length; i++) { + const mapped = mapFn(array[i], i); + if (mapped === undefined) { + return undefined; } - return result || array; + result.push(mapped); } + return result; +} - export function mapAllOrFail(array: readonly T[], mapFn: (x: T, i: number) => U | undefined): U[] | undefined { - const result: U[] = []; +export function mapDefined(array: readonly T[] | undefined, mapFn: (x: T, i: number) => U | undefined): U[] { + const result: U[] = []; + if (array) { for (let i = 0; i < array.length; i++) { const mapped = mapFn(array[i], i); - if (mapped === undefined) { - return undefined; + if (mapped !== undefined) { + result.push(mapped); } - result.push(mapped); } - return result; } + return result; +} - export function mapDefined(array: readonly T[] | undefined, mapFn: (x: T, i: number) => U | undefined): U[] { - const result: U[] = []; - if (array) { - for (let i = 0; i < array.length; i++) { - const mapped = mapFn(array[i], i); - if (mapped !== undefined) { - result.push(mapped); +export function mapDefinedIterator(iter: Iterator, mapFn: (x: T) => U | undefined): Iterator { + return { + next() { + while (true) { + const res = iter.next(); + if (res.done) { + return res as { done: true, value: never }; + } + const value = mapFn(res.value); + if (value !== undefined) { + return { value, done: false }; } } } - return result; - } + }; +} - export function mapDefinedIterator(iter: Iterator, mapFn: (x: T) => U | undefined): Iterator { - return { - next() { - while (true) { - const res = iter.next(); - if (res.done) { - return res as { done: true, value: never }; - } - const value = mapFn(res.value); - if (value !== undefined) { - return { value, done: false }; - } - } - } - }; +export function mapDefinedEntries(map: ReadonlyESMap, f: (key: K1, value: V1) => readonly [K2, V2] | undefined): ESMap; +export function mapDefinedEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2 | undefined, V2 | undefined] | undefined): ESMap | undefined; +export function mapDefinedEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2 | undefined, V2 | undefined] | undefined): ESMap | undefined { + if (!map) { + return undefined; } - export function mapDefinedEntries(map: ReadonlyESMap, f: (key: K1, value: V1) => readonly [K2, V2] | undefined): ESMap; - export function mapDefinedEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2 | undefined, V2 | undefined] | undefined): ESMap | undefined; - export function mapDefinedEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2 | undefined, V2 | undefined] | undefined): ESMap | undefined { - if (!map) { - return undefined; + const result = new Map(); + map.forEach((value, key) => { + const entry = f(key, value); + if (entry !== undefined) { + const [newKey, newValue] = entry; + if (newKey !== undefined && newValue !== undefined) { + result.set(newKey, newValue); + } } + }); - const result = new Map(); - map.forEach((value, key) => { - const entry = f(key, value); - if (entry !== undefined) { - const [newKey, newValue] = entry; - if (newKey !== undefined && newValue !== undefined) { - result.set(newKey, newValue); - } + return result; +} + +export function mapDefinedValues(set: ReadonlySet, f: (value: V1) => V2 | undefined): Set; +export function mapDefinedValues(set: ReadonlySet | undefined, f: (value: V1) => V2 | undefined): Set | undefined; +export function mapDefinedValues(set: ReadonlySet | undefined, f: (value: V1) => V2 | undefined): Set | undefined { + if (set) { + const result = new Set(); + set.forEach(value => { + const newValue = f(value); + if (newValue !== undefined) { + result.add(newValue); } }); - return result; } +} - export function mapDefinedValues(set: ReadonlySet, f: (value: V1) => V2 | undefined): Set; - export function mapDefinedValues(set: ReadonlySet | undefined, f: (value: V1) => V2 | undefined): Set | undefined; - export function mapDefinedValues(set: ReadonlySet | undefined, f: (value: V1) => V2 | undefined): Set | undefined { - if (set) { - const result = new Set(); - set.forEach(value => { - const newValue = f(value); - if (newValue !== undefined) { - result.add(newValue); - } - }); - return result; - } - } - - export function getOrUpdate(map: ESMap, key: K, callback: () => V) { - if (map.has(key)) { - return map.get(key)!; - } - const value = callback(); - map.set(key, value); - return value; +export function getOrUpdate(map: ESMap, key: K, callback: () => V) { + if (map.has(key)) { + return map.get(key)!; } + const value = callback(); + map.set(key, value); + return value; +} - export function tryAddToSet(set: Set, value: T) { - if (!set.has(value)) { - set.add(value); - return true; - } - return false; +export function tryAddToSet(set: Set, value: T) { + if (!set.has(value)) { + set.add(value); + return true; } + return false; +} - export const emptyIterator: Iterator = { next: () => ({ value: undefined as never, done: true }) }; +export const emptyIterator: Iterator = { next: () => ({ value: undefined as never, done: true }) }; - export function singleIterator(value: T): Iterator { - let done = false; - return { - next() { - const wasDone = done; - done = true; - return wasDone ? { value: undefined as never, done: true } : { value, done: false }; - } - }; - } +export function singleIterator(value: T): Iterator { + let done = false; + return { + next() { + const wasDone = done; + done = true; + return wasDone ? { value: undefined as never, done: true } : { value, done: false }; + } + }; +} - /** - * Maps contiguous spans of values with the same key. - * - * @param array The array to map. - * @param keyfn A callback used to select the key for an element. - * @param mapfn A callback used to map a contiguous chunk of values to a single value. - */ - export function spanMap(array: readonly T[], keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[]; - export function spanMap(array: readonly T[] | undefined, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] | undefined; - export function spanMap(array: readonly T[] | undefined, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] | undefined { - let result: U[] | undefined; - if (array) { - result = []; - const len = array.length; - let previousKey: K | undefined; - let key: K | undefined; - let start = 0; - let pos = 0; - while (start < len) { - while (pos < len) { - const value = array[pos]; - key = keyfn(value, pos); - if (pos === 0) { - previousKey = key; - } - else if (key !== previousKey) { - break; - } - - pos++; +/** + * Maps contiguous spans of values with the same key. + * + * @param array The array to map. + * @param keyfn A callback used to select the key for an element. + * @param mapfn A callback used to map a contiguous chunk of values to a single value. + */ +export function spanMap(array: readonly T[], keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[]; +export function spanMap(array: readonly T[] | undefined, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] | undefined; +export function spanMap(array: readonly T[] | undefined, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] | undefined { + let result: U[] | undefined; + if (array) { + result = []; + const len = array.length; + let previousKey: K | undefined; + let key: K | undefined; + let start = 0; + let pos = 0; + while (start < len) { + while (pos < len) { + const value = array[pos]; + key = keyfn(value, pos); + if (pos === 0) { + previousKey = key; + } + else if (key !== previousKey) { + break; } - if (start < pos) { - const v = mapfn(array.slice(start, pos), previousKey!, start, pos); - if (v) { - result.push(v); - } + pos++; + } - start = pos; + if (start < pos) { + const v = mapfn(array.slice(start, pos), previousKey!, start, pos); + if (v) { + result.push(v); } - previousKey = key; - pos++; + start = pos; } - } - return result; + previousKey = key; + pos++; + } } - export function mapEntries(map: ReadonlyESMap, f: (key: K1, value: V1) => readonly [K2, V2]): ESMap; - export function mapEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2, V2]): ESMap | undefined; - export function mapEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2, V2]): ESMap | undefined { - if (!map) { - return undefined; - } + return result; +} - const result = new Map(); - map.forEach((value, key) => { - const [newKey, newValue] = f(key, value); - result.set(newKey, newValue); - }); - return result; +export function mapEntries(map: ReadonlyESMap, f: (key: K1, value: V1) => readonly [K2, V2]): ESMap; +export function mapEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2, V2]): ESMap | undefined; +export function mapEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2, V2]): ESMap | undefined { + if (!map) { + return undefined; } - export function some(array: readonly T[] | undefined): array is readonly T[]; - export function some(array: readonly T[] | undefined, predicate: (value: T) => boolean): boolean; - export function some(array: readonly T[] | undefined, predicate?: (value: T) => boolean): boolean { - if (array) { - if (predicate) { - for (const v of array) { - if (predicate(v)) { - return true; - } + const result = new Map(); + map.forEach((value, key) => { + const [newKey, newValue] = f(key, value); + result.set(newKey, newValue); + }); + return result; +} + +export function some(array: readonly T[] | undefined): array is readonly T[]; +export function some(array: readonly T[] | undefined, predicate: (value: T) => boolean): boolean; +export function some(array: readonly T[] | undefined, predicate?: (value: T) => boolean): boolean { + if (array) { + if (predicate) { + for (const v of array) { + if (predicate(v)) { + return true; } } - else { - return array.length > 0; - } } - return false; + else { + return array.length > 0; + } } + return false; +} - /** Calls the callback with (start, afterEnd) index pairs for each range where 'pred' is true. */ - export function getRangesWhere(arr: readonly T[], pred: (t: T) => boolean, cb: (start: number, afterEnd: number) => void): void { - let start: number | undefined; - for (let i = 0; i < arr.length; i++) { - if (pred(arr[i])) { - start = start === undefined ? i : start; - } - else { - if (start !== undefined) { - cb(start, i); - start = undefined; - } +/** Calls the callback with (start, afterEnd) index pairs for each range where 'pred' is true. */ +export function getRangesWhere(arr: readonly T[], pred: (t: T) => boolean, cb: (start: number, afterEnd: number) => void): void { + let start: number | undefined; + for (let i = 0; i < arr.length; i++) { + if (pred(arr[i])) { + start = start === undefined ? i : start; + } + else { + if (start !== undefined) { + cb(start, i); + start = undefined; } } - if (start !== undefined) cb(start, arr.length); } + if (start !== undefined) cb(start, arr.length); +} - export function concatenate(array1: T[], array2: T[]): T[]; - export function concatenate(array1: readonly T[], array2: readonly T[]): readonly T[]; - export function concatenate(array1: T[] | undefined, array2: T[] | undefined): T[]; - export function concatenate(array1: readonly T[] | undefined, array2: readonly T[] | undefined): readonly T[]; - export function concatenate(array1: T[], array2: T[]): T[] { - if (!some(array2)) return array1; - if (!some(array1)) return array2; - return [...array1, ...array2]; - } +export function concatenate(array1: T[], array2: T[]): T[]; +export function concatenate(array1: readonly T[], array2: readonly T[]): readonly T[]; +export function concatenate(array1: T[] | undefined, array2: T[] | undefined): T[]; +export function concatenate(array1: readonly T[] | undefined, array2: readonly T[] | undefined): readonly T[]; +export function concatenate(array1: T[], array2: T[]): T[] { + if (!some(array2)) return array1; + if (!some(array1)) return array2; + return [...array1, ...array2]; +} - function selectIndex(_: unknown, i: number) { - return i; - } +function selectIndex(_: unknown, i: number) { + return i; +} - export function indicesOf(array: readonly unknown[]): number[] { - return array.map(selectIndex); - } +export function indicesOf(array: readonly unknown[]): number[] { + return array.map(selectIndex); +} - function deduplicateRelational(array: readonly T[], equalityComparer: EqualityComparer, comparer: Comparer) { - // Perform a stable sort of the array. This ensures the first entry in a list of - // duplicates remains the first entry in the result. - const indices = indicesOf(array); - stableSortIndices(array, indices, comparer); +function deduplicateRelational(array: readonly T[], equalityComparer: EqualityComparer, comparer: Comparer) { + // Perform a stable sort of the array. This ensures the first entry in a list of + // duplicates remains the first entry in the result. + const indices = indicesOf(array); + stableSortIndices(array, indices, comparer); - let last = array[indices[0]]; - const deduplicated: number[] = [indices[0]]; - for (let i = 1; i < indices.length; i++) { - const index = indices[i]; - const item = array[index]; - if (!equalityComparer(last, item)) { - deduplicated.push(index); - last = item; - } + let last = array[indices[0]]; + const deduplicated: number[] = [indices[0]]; + for (let i = 1; i < indices.length; i++) { + const index = indices[i]; + const item = array[index]; + if (!equalityComparer(last, item)) { + deduplicated.push(index); + last = item; } - - // restore original order - deduplicated.sort(); - return deduplicated.map(i => array[i]); } - function deduplicateEquality(array: readonly T[], equalityComparer: EqualityComparer) { - const result: T[] = []; - for (const item of array) { - pushIfUnique(result, item, equalityComparer); - } - return result; - } + // restore original order + deduplicated.sort(); + return deduplicated.map(i => array[i]); +} - /** - * Deduplicates an unsorted array. - * @param equalityComparer An `EqualityComparer` used to determine if two values are duplicates. - * @param comparer An optional `Comparer` used to sort entries before comparison, though the - * result will remain in the original order in `array`. - */ - export function deduplicate(array: readonly T[], equalityComparer: EqualityComparer, comparer?: Comparer): T[] { - return array.length === 0 ? [] : - array.length === 1 ? array.slice() : - comparer ? deduplicateRelational(array, equalityComparer, comparer) : - deduplicateEquality(array, equalityComparer); +function deduplicateEquality(array: readonly T[], equalityComparer: EqualityComparer) { + const result: T[] = []; + for (const item of array) { + pushIfUnique(result, item, equalityComparer); } + return result; +} - /** - * Deduplicates an array that has already been sorted. - */ - function deduplicateSorted(array: SortedReadonlyArray, comparer: EqualityComparer | Comparer): SortedReadonlyArray { - if (array.length === 0) return emptyArray as any as SortedReadonlyArray; - - let last = array[0]; - const deduplicated: T[] = [last]; - for (let i = 1; i < array.length; i++) { - const next = array[i]; - switch (comparer(next, last)) { - // equality comparison - case true: - - // relational comparison - // falls through - case Comparison.EqualTo: - continue; +/** + * Deduplicates an unsorted array. + * @param equalityComparer An `EqualityComparer` used to determine if two values are duplicates. + * @param comparer An optional `Comparer` used to sort entries before comparison, though the + * result will remain in the original order in `array`. + */ +export function deduplicate(array: readonly T[], equalityComparer: EqualityComparer, comparer?: Comparer): T[] { + return array.length === 0 ? [] : + array.length === 1 ? array.slice() : + comparer ? deduplicateRelational(array, equalityComparer, comparer) : + deduplicateEquality(array, equalityComparer); +} - case Comparison.LessThan: - // If `array` is sorted, `next` should **never** be less than `last`. - return Debug.fail("Array is unsorted."); - } +/** + * Deduplicates an array that has already been sorted. + */ +function deduplicateSorted(array: SortedReadonlyArray, comparer: EqualityComparer | Comparer): SortedReadonlyArray { + if (array.length === 0) return emptyArray as any as SortedReadonlyArray; - deduplicated.push(last = next); - } + let last = array[0]; + const deduplicated: T[] = [last]; + for (let i = 1; i < array.length; i++) { + const next = array[i]; + switch (comparer(next, last)) { + // equality comparison + case true: - return deduplicated as any as SortedReadonlyArray; - } + // relational comparison + // falls through + case Comparison.EqualTo: + continue; - export function insertSorted(array: SortedArray, insert: T, compare: Comparer): void { - if (array.length === 0) { - array.push(insert); - return; + case Comparison.LessThan: + // If `array` is sorted, `next` should **never** be less than `last`. + return Debug.fail("Array is unsorted."); } - const insertIndex = binarySearch(array, insert, identity, compare); - if (insertIndex < 0) { - array.splice(~insertIndex, 0, insert); - } + deduplicated.push(last = next); } - export function sortAndDeduplicate(array: readonly string[]): SortedReadonlyArray; - export function sortAndDeduplicate(array: readonly T[], comparer: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray; - export function sortAndDeduplicate(array: readonly T[], comparer?: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray { - return deduplicateSorted(sort(array, comparer), equalityComparer || comparer || compareStringsCaseSensitive as any as Comparer); + return deduplicated as any as SortedReadonlyArray; +} + +export function insertSorted(array: SortedArray, insert: T, compare: Comparer): void { + if (array.length === 0) { + array.push(insert); + return; } - export function arrayIsSorted(array: readonly T[], comparer: Comparer) { - if (array.length < 2) return true; - let prevElement = array[0]; - for (const element of array.slice(1)) { - if (comparer(prevElement, element) === Comparison.GreaterThan) { - return false; - } - prevElement = element; - } - return true; + const insertIndex = binarySearch(array, insert, identity, compare); + if (insertIndex < 0) { + array.splice(~insertIndex, 0, insert); } +} - export function arrayIsEqualTo(array1: readonly T[] | undefined, array2: readonly T[] | undefined, equalityComparer: (a: T, b: T, index: number) => boolean = equateValues): boolean { - if (!array1 || !array2) { - return array1 === array2; - } +export function sortAndDeduplicate(array: readonly string[]): SortedReadonlyArray; +export function sortAndDeduplicate(array: readonly T[], comparer: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray; +export function sortAndDeduplicate(array: readonly T[], comparer?: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray { + return deduplicateSorted(sort(array, comparer), equalityComparer || comparer || compareStringsCaseSensitive as any as Comparer); +} - if (array1.length !== array2.length) { +export function arrayIsSorted(array: readonly T[], comparer: Comparer) { + if (array.length < 2) return true; + let prevElement = array[0]; + for (const element of array.slice(1)) { + if (comparer(prevElement, element) === Comparison.GreaterThan) { return false; } + prevElement = element; + } + return true; +} - for (let i = 0; i < array1.length; i++) { - if (!equalityComparer(array1[i], array2[i], i)) { - return false; - } - } +export function arrayIsEqualTo(array1: readonly T[] | undefined, array2: readonly T[] | undefined, equalityComparer: (a: T, b: T, index: number) => boolean = equateValues): boolean { + if (!array1 || !array2) { + return array1 === array2; + } - return true; + if (array1.length !== array2.length) { + return false; } - /** - * Compacts an array, removing any falsey elements. - */ - export function compact(array: (T | undefined | null | false | 0 | "")[]): T[]; - export function compact(array: readonly (T | undefined | null | false | 0 | "")[]): readonly T[]; - // ESLint thinks these can be combined with the above - they cannot; they'd produce higher-priority inferences and prevent the falsey types from being stripped - export function compact(array: T[]): T[]; // eslint-disable-line @typescript-eslint/unified-signatures - export function compact(array: readonly T[]): readonly T[]; // eslint-disable-line @typescript-eslint/unified-signatures - export function compact(array: T[]): T[] { - let result: T[] | undefined; - if (array) { - for (let i = 0; i < array.length; i++) { - const v = array[i]; - if (result || !v) { - if (!result) { - result = array.slice(0, i); - } - if (v) { - result.push(v); - } - } - } + for (let i = 0; i < array1.length; i++) { + if (!equalityComparer(array1[i], array2[i], i)) { + return false; } - return result || array; } - /** - * Gets the relative complement of `arrayA` with respect to `arrayB`, returning the elements that - * are not present in `arrayA` but are present in `arrayB`. Assumes both arrays are sorted - * based on the provided comparer. - */ - export function relativeComplement(arrayA: T[] | undefined, arrayB: T[] | undefined, comparer: Comparer): T[] | undefined { - if (!arrayB || !arrayA || arrayB.length === 0 || arrayA.length === 0) return arrayB; - const result: T[] = []; - loopB: for (let offsetA = 0, offsetB = 0; offsetB < arrayB.length; offsetB++) { - if (offsetB > 0) { - // Ensure `arrayB` is properly sorted. - Debug.assertGreaterThanOrEqual(comparer(arrayB[offsetB], arrayB[offsetB - 1]), Comparison.EqualTo); - } + return true; +} - loopA: for (const startA = offsetA; offsetA < arrayA.length; offsetA++) { - if (offsetA > startA) { - // Ensure `arrayA` is properly sorted. We only need to perform this check if - // `offsetA` has changed since we entered the loop. - Debug.assertGreaterThanOrEqual(comparer(arrayA[offsetA], arrayA[offsetA - 1]), Comparison.EqualTo); +/** + * Compacts an array, removing any falsey elements. + */ +export function compact(array: (T | undefined | null | false | 0 | "")[]): T[]; +export function compact(array: readonly (T | undefined | null | false | 0 | "")[]): readonly T[]; +// ESLint thinks these can be combined with the above - they cannot; they'd produce higher-priority inferences and prevent the falsey types from being stripped +export function compact(array: T[]): T[]; // eslint-disable-line @typescript-eslint/unified-signatures +export function compact(array: readonly T[]): readonly T[]; // eslint-disable-line @typescript-eslint/unified-signatures +export function compact(array: T[]): T[] { + let result: T[] | undefined; + if (array) { + for (let i = 0; i < array.length; i++) { + const v = array[i]; + if (result || !v) { + if (!result) { + result = array.slice(0, i); } - - switch (comparer(arrayB[offsetB], arrayA[offsetA])) { - case Comparison.LessThan: - // If B is less than A, B does not exist in arrayA. Add B to the result and - // move to the next element in arrayB without changing the current position - // in arrayA. - result.push(arrayB[offsetB]); - continue loopB; - case Comparison.EqualTo: - // If B is equal to A, B exists in arrayA. Move to the next element in - // arrayB without adding B to the result or changing the current position - // in arrayA. - continue loopB; - case Comparison.GreaterThan: - // If B is greater than A, we need to keep looking for B in arrayA. Move to - // the next element in arrayA and recheck. - continue loopA; + if (v) { + result.push(v); } } } - return result; } + return result || array; +} - export function sum, K extends string>(array: readonly T[], prop: K): number { - let result = 0; - for (const v of array) { - result += v[prop]; +/** + * Gets the relative complement of `arrayA` with respect to `arrayB`, returning the elements that + * are not present in `arrayA` but are present in `arrayB`. Assumes both arrays are sorted + * based on the provided comparer. + */ +export function relativeComplement(arrayA: T[] | undefined, arrayB: T[] | undefined, comparer: Comparer): T[] | undefined { + if (!arrayB || !arrayA || arrayB.length === 0 || arrayA.length === 0) return arrayB; + const result: T[] = []; + loopB: for (let offsetA = 0, offsetB = 0; offsetB < arrayB.length; offsetB++) { + if (offsetB > 0) { + // Ensure `arrayB` is properly sorted. + Debug.assertGreaterThanOrEqual(comparer(arrayB[offsetB], arrayB[offsetB - 1]), Comparison.EqualTo); } - return result; - } - /** - * Appends a value to an array, returning the array. - * - * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array - * is created if `value` was appended. - * @param value The value to append to the array. If `value` is `undefined`, nothing is - * appended. - */ - export function append[number] | undefined>(to: TArray, value: TValue): [undefined, undefined] extends [TArray, TValue] ? TArray : NonNullable[number][]; - export function append(to: T[], value: T | undefined): T[]; - export function append(to: T[] | undefined, value: T): T[]; - export function append(to: T[] | undefined, value: T | undefined): T[] | undefined; - export function append(to: Push, value: T | undefined): void; - export function append(to: T[], value: T | undefined): T[] | undefined { - if (value === undefined) return to; - if (to === undefined) return [value]; - to.push(value); - return to; - } + loopA: for (const startA = offsetA; offsetA < arrayA.length; offsetA++) { + if (offsetA > startA) { + // Ensure `arrayA` is properly sorted. We only need to perform this check if + // `offsetA` has changed since we entered the loop. + Debug.assertGreaterThanOrEqual(comparer(arrayA[offsetA], arrayA[offsetA - 1]), Comparison.EqualTo); + } - /** - * Combines two arrays, values, or undefineds into the smallest container that can accommodate the resulting set: - * - * ``` - * undefined -> undefined -> undefined - * T -> undefined -> T - * T -> T -> T[] - * T[] -> undefined -> T[] (no-op) - * T[] -> T -> T[] (append) - * T[] -> T[] -> T[] (concatenate) - * ``` - */ - export function combine(xs: T | readonly T[] | undefined, ys: T | readonly T[] | undefined): T | readonly T[] | undefined; - export function combine(xs: T | T[] | undefined, ys: T | T[] | undefined): T | T[] | undefined; - export function combine(xs: T | T[] | undefined, ys: T | T[] | undefined) { - if (xs === undefined) return ys; - if (ys === undefined) return xs; - if (isArray(xs)) return isArray(ys) ? concatenate(xs, ys) : append(xs, ys); - if (isArray(ys)) return append(ys, xs); - return [xs, ys]; + switch (comparer(arrayB[offsetB], arrayA[offsetA])) { + case Comparison.LessThan: + // If B is less than A, B does not exist in arrayA. Add B to the result and + // move to the next element in arrayB without changing the current position + // in arrayA. + result.push(arrayB[offsetB]); + continue loopB; + case Comparison.EqualTo: + // If B is equal to A, B exists in arrayA. Move to the next element in + // arrayB without adding B to the result or changing the current position + // in arrayA. + continue loopB; + case Comparison.GreaterThan: + // If B is greater than A, we need to keep looking for B in arrayA. Move to + // the next element in arrayA and recheck. + continue loopA; + } + } } + return result; +} - /** - * Gets the actual offset into an array for a relative offset. Negative offsets indicate a - * position offset from the end of the array. - */ - function toOffset(array: readonly any[], offset: number) { - return offset < 0 ? array.length + offset : offset; +export function sum, K extends string>(array: readonly T[], prop: K): number { + let result = 0; + for (const v of array) { + result += v[prop]; } + return result; +} - /** - * Appends a range of value to an array, returning the array. - * - * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array - * is created if `value` was appended. - * @param from The values to append to the array. If `from` is `undefined`, nothing is - * appended. If an element of `from` is `undefined`, that element is not appended. - * @param start The offset in `from` at which to start copying values. - * @param end The offset in `from` at which to stop copying values (non-inclusive). - */ - export function addRange(to: T[], from: readonly T[] | undefined, start?: number, end?: number): T[]; - export function addRange(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined; - export function addRange(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined { - if (from === undefined || from.length === 0) return to; - if (to === undefined) return from.slice(start, end); - start = start === undefined ? 0 : toOffset(from, start); - end = end === undefined ? from.length : toOffset(from, end); - for (let i = start; i < end && i < from.length; i++) { - if (from[i] !== undefined) { - to.push(from[i]); - } - } - return to; +/** + * Appends a value to an array, returning the array. + * + * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array + * is created if `value` was appended. + * @param value The value to append to the array. If `value` is `undefined`, nothing is + * appended. + */ +export function append[number] | undefined>(to: TArray, value: TValue): [undefined, undefined] extends [TArray, TValue] ? TArray : NonNullable[number][]; +export function append(to: T[], value: T | undefined): T[]; +export function append(to: T[] | undefined, value: T): T[]; +export function append(to: T[] | undefined, value: T | undefined): T[] | undefined; +export function append(to: Push, value: T | undefined): void; +export function append(to: T[], value: T | undefined): T[] | undefined { + if (value === undefined) return to; + if (to === undefined) return [value]; + to.push(value); + return to; +} + +/** + * Combines two arrays, values, or undefineds into the smallest container that can accommodate the resulting set: + * + * ``` + * undefined -> undefined -> undefined + * T -> undefined -> T + * T -> T -> T[] + * T[] -> undefined -> T[] (no-op) + * T[] -> T -> T[] (append) + * T[] -> T[] -> T[] (concatenate) + * ``` + */ +export function combine(xs: T | readonly T[] | undefined, ys: T | readonly T[] | undefined): T | readonly T[] | undefined; +export function combine(xs: T | T[] | undefined, ys: T | T[] | undefined): T | T[] | undefined; +export function combine(xs: T | T[] | undefined, ys: T | T[] | undefined) { + if (xs === undefined) return ys; + if (ys === undefined) return xs; + if (isArray(xs)) return isArray(ys) ? concatenate(xs, ys) : append(xs, ys); + if (isArray(ys)) return append(ys, xs); + return [xs, ys]; +} + +/** + * Gets the actual offset into an array for a relative offset. Negative offsets indicate a + * position offset from the end of the array. + */ +function toOffset(array: readonly any[], offset: number) { + return offset < 0 ? array.length + offset : offset; +} + +/** + * Appends a range of value to an array, returning the array. + * + * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array + * is created if `value` was appended. + * @param from The values to append to the array. If `from` is `undefined`, nothing is + * appended. If an element of `from` is `undefined`, that element is not appended. + * @param start The offset in `from` at which to start copying values. + * @param end The offset in `from` at which to stop copying values (non-inclusive). + */ +export function addRange(to: T[], from: readonly T[] | undefined, start?: number, end?: number): T[]; +export function addRange(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined; +export function addRange(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined { + if (from === undefined || from.length === 0) return to; + if (to === undefined) return from.slice(start, end); + start = start === undefined ? 0 : toOffset(from, start); + end = end === undefined ? from.length : toOffset(from, end); + for (let i = start; i < end && i < from.length; i++) { + if (from[i] !== undefined) { + to.push(from[i]); + } + } + return to; +} + +/** + * @return Whether the value was added. + */ +export function pushIfUnique(array: T[], toAdd: T, equalityComparer?: EqualityComparer): boolean { + if (contains(array, toAdd, equalityComparer)) { + return false; + } + else { + array.push(toAdd); + return true; } +} - /** - * @return Whether the value was added. - */ - export function pushIfUnique(array: T[], toAdd: T, equalityComparer?: EqualityComparer): boolean { - if (contains(array, toAdd, equalityComparer)) { - return false; - } - else { - array.push(toAdd); - return true; - } +/** + * Unlike `pushIfUnique`, this can take `undefined` as an input, and returns a new array. + */ +export function appendIfUnique(array: T[] | undefined, toAdd: T, equalityComparer?: EqualityComparer): T[] { + if (array) { + pushIfUnique(array, toAdd, equalityComparer); + return array; } + else { + return [toAdd]; + } +} - /** - * Unlike `pushIfUnique`, this can take `undefined` as an input, and returns a new array. - */ - export function appendIfUnique(array: T[] | undefined, toAdd: T, equalityComparer?: EqualityComparer): T[] { - if (array) { - pushIfUnique(array, toAdd, equalityComparer); - return array; +function stableSortIndices(array: readonly T[], indices: number[], comparer: Comparer) { + // sort indices by value then position + indices.sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y)); +} + +/** + * Returns a new sorted array. + */ +export function sort(array: readonly T[], comparer?: Comparer): SortedReadonlyArray { + return (array.length === 0 ? array : array.slice().sort(comparer)) as SortedReadonlyArray; +} + +export function arrayIterator(array: readonly T[]): Iterator { + let i = 0; + return { next: () => { + if (i === array.length) { + return { value: undefined as never, done: true }; } else { - return [toAdd]; + i++; + return { value: array[i - 1], done: false }; } - } - - function stableSortIndices(array: readonly T[], indices: number[], comparer: Comparer) { - // sort indices by value then position - indices.sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y)); - } - - /** - * Returns a new sorted array. - */ - export function sort(array: readonly T[], comparer?: Comparer): SortedReadonlyArray { - return (array.length === 0 ? array : array.slice().sort(comparer)) as SortedReadonlyArray; - } + }}; +} - export function arrayIterator(array: readonly T[]): Iterator { - let i = 0; - return { next: () => { - if (i === array.length) { +export function arrayReverseIterator(array: readonly T[]): Iterator { + let i = array.length; + return { + next: () => { + if (i === 0) { return { value: undefined as never, done: true }; } else { - i++; - return { value: array[i - 1], done: false }; - } - }}; - } - - export function arrayReverseIterator(array: readonly T[]): Iterator { - let i = array.length; - return { - next: () => { - if (i === 0) { - return { value: undefined as never, done: true }; - } - else { - i--; - return { value: array[i], done: false }; - } + i--; + return { value: array[i], done: false }; } - }; - } + } + }; +} - /** - * Stable sort of an array. Elements equal to each other maintain their relative position in the array. - */ - export function stableSort(array: readonly T[], comparer: Comparer): SortedReadonlyArray { - const indices = indicesOf(array); - stableSortIndices(array, indices, comparer); - return indices.map(i => array[i]) as SortedArray as SortedReadonlyArray; - } +/** + * Stable sort of an array. Elements equal to each other maintain their relative position in the array. + */ +export function stableSort(array: readonly T[], comparer: Comparer): SortedReadonlyArray { + const indices = indicesOf(array); + stableSortIndices(array, indices, comparer); + return indices.map(i => array[i]) as SortedArray as SortedReadonlyArray; +} - export function rangeEquals(array1: readonly T[], array2: readonly T[], pos: number, end: number) { - while (pos < end) { - if (array1[pos] !== array2[pos]) { - return false; - } - pos++; +export function rangeEquals(array1: readonly T[], array2: readonly T[], pos: number, end: number) { + while (pos < end) { + if (array1[pos] !== array2[pos]) { + return false; } - return true; + pos++; } + return true; +} - /** - * Returns the element at a specific offset in an array if non-empty, `undefined` otherwise. - * A negative offset indicates the element should be retrieved from the end of the array. - */ - export function elementAt(array: readonly T[] | undefined, offset: number): T | undefined { - if (array) { - offset = toOffset(array, offset); - if (offset < array.length) { - return array[offset]; - } +/** + * Returns the element at a specific offset in an array if non-empty, `undefined` otherwise. + * A negative offset indicates the element should be retrieved from the end of the array. + */ +export function elementAt(array: readonly T[] | undefined, offset: number): T | undefined { + if (array) { + offset = toOffset(array, offset); + if (offset < array.length) { + return array[offset]; } - return undefined; } + return undefined; +} - /** - * Returns the first element of an array if non-empty, `undefined` otherwise. - */ - export function firstOrUndefined(array: readonly T[]): T | undefined { - return array.length === 0 ? undefined : array[0]; - } +/** + * Returns the first element of an array if non-empty, `undefined` otherwise. + */ +export function firstOrUndefined(array: readonly T[]): T | undefined { + return array.length === 0 ? undefined : array[0]; +} - export function first(array: readonly T[]): T { - Debug.assert(array.length !== 0); - return array[0]; - } +export function first(array: readonly T[]): T { + Debug.assert(array.length !== 0); + return array[0]; +} - /** - * Returns the last element of an array if non-empty, `undefined` otherwise. - */ - export function lastOrUndefined(array: readonly T[]): T | undefined { - return array.length === 0 ? undefined : array[array.length - 1]; - } +/** + * Returns the last element of an array if non-empty, `undefined` otherwise. + */ +export function lastOrUndefined(array: readonly T[]): T | undefined { + return array.length === 0 ? undefined : array[array.length - 1]; +} - export function last(array: readonly T[]): T { - Debug.assert(array.length !== 0); - return array[array.length - 1]; - } +export function last(array: readonly T[]): T { + Debug.assert(array.length !== 0); + return array[array.length - 1]; +} - /** - * Returns the only element of an array if it contains only one element, `undefined` otherwise. - */ - export function singleOrUndefined(array: readonly T[] | undefined): T | undefined { - return array && array.length === 1 - ? array[0] - : undefined; - } +/** + * Returns the only element of an array if it contains only one element, `undefined` otherwise. + */ +export function singleOrUndefined(array: readonly T[] | undefined): T | undefined { + return array && array.length === 1 + ? array[0] + : undefined; +} - /** - * Returns the only element of an array if it contains only one element; otherwise, returns the - * array. - */ - export function singleOrMany(array: T[]): T | T[]; - export function singleOrMany(array: readonly T[]): T | readonly T[]; - export function singleOrMany(array: T[] | undefined): T | T[] | undefined; - export function singleOrMany(array: readonly T[] | undefined): T | readonly T[] | undefined; - export function singleOrMany(array: readonly T[] | undefined): T | readonly T[] | undefined { - return array && array.length === 1 - ? array[0] - : array; - } - - export function replaceElement(array: readonly T[], index: number, value: T): T[] { - const result = array.slice(0); - result[index] = value; - return result; - } +/** + * Returns the only element of an array if it contains only one element; otherwise, returns the + * array. + */ +export function singleOrMany(array: T[]): T | T[]; +export function singleOrMany(array: readonly T[]): T | readonly T[]; +export function singleOrMany(array: T[] | undefined): T | T[] | undefined; +export function singleOrMany(array: readonly T[] | undefined): T | readonly T[] | undefined; +export function singleOrMany(array: readonly T[] | undefined): T | readonly T[] | undefined { + return array && array.length === 1 + ? array[0] + : array; +} - /** - * Performs a binary search, finding the index at which `value` occurs in `array`. - * If no such index is found, returns the 2's-complement of first index at which - * `array[index]` exceeds `value`. - * @param array A sorted array whose first element must be no larger than number - * @param value The value to be searched for in the array. - * @param keySelector A callback used to select the search key from `value` and each element of - * `array`. - * @param keyComparer A callback used to compare two keys in a sorted array. - * @param offset An offset into `array` at which to start the search. - */ - export function binarySearch(array: readonly T[], value: T, keySelector: (v: T) => U, keyComparer: Comparer, offset?: number): number { - return binarySearchKey(array, keySelector(value), keySelector, keyComparer, offset); - } +export function replaceElement(array: readonly T[], index: number, value: T): T[] { + const result = array.slice(0); + result[index] = value; + return result; +} - /** - * Performs a binary search, finding the index at which an object with `key` occurs in `array`. - * If no such index is found, returns the 2's-complement of first index at which - * `array[index]` exceeds `key`. - * @param array A sorted array whose first element must be no larger than number - * @param key The key to be searched for in the array. - * @param keySelector A callback used to select the search key from each element of `array`. - * @param keyComparer A callback used to compare two keys in a sorted array. - * @param offset An offset into `array` at which to start the search. - */ - export function binarySearchKey(array: readonly T[], key: U, keySelector: (v: T, i: number) => U, keyComparer: Comparer, offset?: number): number { - if (!some(array)) { - return -1; - } +/** + * Performs a binary search, finding the index at which `value` occurs in `array`. + * If no such index is found, returns the 2's-complement of first index at which + * `array[index]` exceeds `value`. + * @param array A sorted array whose first element must be no larger than number + * @param value The value to be searched for in the array. + * @param keySelector A callback used to select the search key from `value` and each element of + * `array`. + * @param keyComparer A callback used to compare two keys in a sorted array. + * @param offset An offset into `array` at which to start the search. + */ +export function binarySearch(array: readonly T[], value: T, keySelector: (v: T) => U, keyComparer: Comparer, offset?: number): number { + return binarySearchKey(array, keySelector(value), keySelector, keyComparer, offset); +} - let low = offset || 0; - let high = array.length - 1; - while (low <= high) { - const middle = low + ((high - low) >> 1); - const midKey = keySelector(array[middle], middle); - switch (keyComparer(midKey, key)) { - case Comparison.LessThan: - low = middle + 1; - break; - case Comparison.EqualTo: - return middle; - case Comparison.GreaterThan: - high = middle - 1; - break; - } - } +/** + * Performs a binary search, finding the index at which an object with `key` occurs in `array`. + * If no such index is found, returns the 2's-complement of first index at which + * `array[index]` exceeds `key`. + * @param array A sorted array whose first element must be no larger than number + * @param key The key to be searched for in the array. + * @param keySelector A callback used to select the search key from each element of `array`. + * @param keyComparer A callback used to compare two keys in a sorted array. + * @param offset An offset into `array` at which to start the search. + */ +export function binarySearchKey(array: readonly T[], key: U, keySelector: (v: T, i: number) => U, keyComparer: Comparer, offset?: number): number { + if (!some(array)) { + return -1; + } - return ~low; + let low = offset || 0; + let high = array.length - 1; + while (low <= high) { + const middle = low + ((high - low) >> 1); + const midKey = keySelector(array[middle], middle); + switch (keyComparer(midKey, key)) { + case Comparison.LessThan: + low = middle + 1; + break; + case Comparison.EqualTo: + return middle; + case Comparison.GreaterThan: + high = middle - 1; + break; + } } - export function reduceLeft(array: readonly T[] | undefined, f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U; - export function reduceLeft(array: readonly T[], f: (memo: T, value: T, i: number) => T): T | undefined; - export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T, start?: number, count?: number): T | undefined { - if (array && array.length > 0) { - const size = array.length; - if (size > 0) { - let pos = start === undefined || start < 0 ? 0 : start; - const end = count === undefined || pos + count > size - 1 ? size - 1 : pos + count; - let result: T; - if (arguments.length <= 2) { - result = array[pos]; - pos++; - } - else { - result = initial!; - } - while (pos <= end) { - result = f(result, array[pos], pos); - pos++; - } - return result; + return ~low; +} + +export function reduceLeft(array: readonly T[] | undefined, f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U; +export function reduceLeft(array: readonly T[], f: (memo: T, value: T, i: number) => T): T | undefined; +export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T, start?: number, count?: number): T | undefined { + if (array && array.length > 0) { + const size = array.length; + if (size > 0) { + let pos = start === undefined || start < 0 ? 0 : start; + const end = count === undefined || pos + count > size - 1 ? size - 1 : pos + count; + let result: T; + if (arguments.length <= 2) { + result = array[pos]; + pos++; + } + else { + result = initial!; + } + while (pos <= end) { + result = f(result, array[pos], pos); + pos++; } + return result; } - return initial; } + return initial; +} - const hasOwnProperty = Object.prototype.hasOwnProperty; +const hasOwnProperty = Object.prototype.hasOwnProperty; - /** - * Indicates whether a map-like contains an own property with the specified key. - * - * @param map A map-like. - * @param key A property key. - */ - export function hasProperty(map: MapLike, key: string): boolean { - return hasOwnProperty.call(map, key); - } +/** + * Indicates whether a map-like contains an own property with the specified key. + * + * @param map A map-like. + * @param key A property key. + */ +export function hasProperty(map: MapLike, key: string): boolean { + return hasOwnProperty.call(map, key); +} - /** - * Gets the value of an owned property in a map-like. - * - * @param map A map-like. - * @param key A property key. - */ - export function getProperty(map: MapLike, key: string): T | undefined { - return hasOwnProperty.call(map, key) ? map[key] : undefined; - } +/** + * Gets the value of an owned property in a map-like. + * + * @param map A map-like. + * @param key A property key. + */ +export function getProperty(map: MapLike, key: string): T | undefined { + return hasOwnProperty.call(map, key) ? map[key] : undefined; +} - /** - * Gets the owned, enumerable property keys of a map-like. - */ - export function getOwnKeys(map: MapLike): string[] { - const keys: string[] = []; - for (const key in map) { - if (hasOwnProperty.call(map, key)) { - keys.push(key); - } +/** + * Gets the owned, enumerable property keys of a map-like. + */ +export function getOwnKeys(map: MapLike): string[] { + const keys: string[] = []; + for (const key in map) { + if (hasOwnProperty.call(map, key)) { + keys.push(key); } - - return keys; } - export function getAllKeys(obj: object): string[] { - const result: string[] = []; - do { - const names = Object.getOwnPropertyNames(obj); - for (const name of names) { - pushIfUnique(result, name); - } - } while (obj = Object.getPrototypeOf(obj)); - return result; - } + return keys; +} - export function getOwnValues(sparseArray: T[]): T[] { - const values: T[] = []; - for (const key in sparseArray) { - if (hasOwnProperty.call(sparseArray, key)) { - values.push(sparseArray[key]); - } +export function getAllKeys(obj: object): string[] { + const result: string[] = []; + do { + const names = Object.getOwnPropertyNames(obj); + for (const name of names) { + pushIfUnique(result, name); } + } while (obj = Object.getPrototypeOf(obj)); + return result; +} - return values; +export function getOwnValues(sparseArray: T[]): T[] { + const values: T[] = []; + for (const key in sparseArray) { + if (hasOwnProperty.call(sparseArray, key)) { + values.push(sparseArray[key]); + } } - const _entries = Object.entries || ((obj: MapLike) => { - const keys = getOwnKeys(obj); - const result: [string, T][] = Array(keys.length); - for (let i = 0; i < keys.length; i++) { - result[i] = [keys[i], obj[keys[i]]]; - } - return result; - }); + return values; +} - export function getEntries(obj: MapLike): [string, T][] { - return obj ? _entries(obj) : []; +const _entries = Object.entries || ((obj: MapLike) => { + const keys = getOwnKeys(obj); + const result: [string, T][] = Array(keys.length); + for (let i = 0; i < keys.length; i++) { + result[i] = [keys[i], obj[keys[i]]]; } + return result; +}); - export function arrayOf(count: number, f: (index: number) => T): T[] { - const result = new Array(count); - for (let i = 0; i < count; i++) { - result[i] = f(i); - } - return result; +export function getEntries(obj: MapLike): [string, T][] { + return obj ? _entries(obj) : []; +} + +export function arrayOf(count: number, f: (index: number) => T): T[] { + const result = new Array(count); + for (let i = 0; i < count; i++) { + result[i] = f(i); } + return result; +} - /** Shims `Array.from`. */ - export function arrayFrom(iterator: Iterator | IterableIterator, map: (t: T) => U): U[]; - export function arrayFrom(iterator: Iterator | IterableIterator): T[]; - export function arrayFrom(iterator: Iterator | IterableIterator, map?: (t: T) => U): (T | U)[] { - const result: (T | U)[] = []; - for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { - result.push(map ? map(iterResult.value) : iterResult.value); - } - return result; +/** Shims `Array.from`. */ +export function arrayFrom(iterator: Iterator | IterableIterator, map: (t: T) => U): U[]; +export function arrayFrom(iterator: Iterator | IterableIterator): T[]; +export function arrayFrom(iterator: Iterator | IterableIterator, map?: (t: T) => U): (T | U)[] { + const result: (T | U)[] = []; + for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { + result.push(map ? map(iterResult.value) : iterResult.value); } + return result; +} - export function assign(t: T, ...args: (T | undefined)[]) { - for (const arg of args) { - if (arg === undefined) continue; - for (const p in arg) { - if (hasProperty(arg, p)) { - t[p] = arg[p]; - } +export function assign(t: T, ...args: (T | undefined)[]) { + for (const arg of args) { + if (arg === undefined) continue; + for (const p in arg) { + if (hasProperty(arg, p)) { + t[p] = arg[p]; } } - return t; } + return t; +} - /** - * Performs a shallow equality comparison of the contents of two map-likes. - * - * @param left A map-like whose properties should be compared. - * @param right A map-like whose properties should be compared. - */ - export function equalOwnProperties(left: MapLike | undefined, right: MapLike | undefined, equalityComparer: EqualityComparer = equateValues) { - if (left === right) return true; - if (!left || !right) return false; - for (const key in left) { - if (hasOwnProperty.call(left, key)) { - if (!hasOwnProperty.call(right, key)) return false; - if (!equalityComparer(left[key], right[key])) return false; - } +/** + * Performs a shallow equality comparison of the contents of two map-likes. + * + * @param left A map-like whose properties should be compared. + * @param right A map-like whose properties should be compared. + */ +export function equalOwnProperties(left: MapLike | undefined, right: MapLike | undefined, equalityComparer: EqualityComparer = equateValues) { + if (left === right) return true; + if (!left || !right) return false; + for (const key in left) { + if (hasOwnProperty.call(left, key)) { + if (!hasOwnProperty.call(right, key)) return false; + if (!equalityComparer(left[key], right[key])) return false; } + } - for (const key in right) { - if (hasOwnProperty.call(right, key)) { - if (!hasOwnProperty.call(left, key)) return false; - } + for (const key in right) { + if (hasOwnProperty.call(right, key)) { + if (!hasOwnProperty.call(left, key)) return false; } + } - return true; + return true; +} + +/** + * Creates a map from the elements of an array. + * + * @param array the array of input elements. + * @param makeKey a function that produces a key for a given element. + * + * This function makes no effort to avoid collisions; if any two elements produce + * the same key with the given 'makeKey' function, then the element with the higher + * index in the array will be the one associated with the produced key. + */ +export function arrayToMap(array: readonly V[], makeKey: (value: V) => K | undefined): ESMap; +export function arrayToMap(array: readonly V1[], makeKey: (value: V1) => K | undefined, makeValue: (value: V1) => V2): ESMap; +export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined): ESMap; +export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined, makeValue: (value: T) => U): ESMap; +export function arrayToMap(array: readonly V1[], makeKey: (value: V1) => K | undefined, makeValue: (value: V1) => V1 | V2 = identity): ESMap { + const result = new Map(); + for (const value of array) { + const key = makeKey(value); + if (key !== undefined) result.set(key, makeValue(value)); + } + return result; +} + +export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number): T[]; +export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number, makeValue: (value: T) => U): U[]; +export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number, makeValue: (value: T) => T | U = identity): (T | U)[] { + const result: (T | U)[] = []; + for (const value of array) { + result[makeKey(value)] = makeValue(value); + } + return result; +} + +export function arrayToMultiMap(values: readonly V[], makeKey: (value: V) => K): MultiMap; +export function arrayToMultiMap(values: readonly V[], makeKey: (value: V) => K, makeValue: (value: V) => U): MultiMap; +export function arrayToMultiMap(values: readonly V[], makeKey: (value: V) => K, makeValue: (value: V) => V | U = identity): MultiMap { + const result = createMultiMap(); + for (const value of values) { + result.add(makeKey(value), makeValue(value)); } + return result; +} - /** - * Creates a map from the elements of an array. - * - * @param array the array of input elements. - * @param makeKey a function that produces a key for a given element. - * - * This function makes no effort to avoid collisions; if any two elements produce - * the same key with the given 'makeKey' function, then the element with the higher - * index in the array will be the one associated with the produced key. - */ - export function arrayToMap(array: readonly V[], makeKey: (value: V) => K | undefined): ESMap; - export function arrayToMap(array: readonly V1[], makeKey: (value: V1) => K | undefined, makeValue: (value: V1) => V2): ESMap; - export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined): ESMap; - export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined, makeValue: (value: T) => U): ESMap; - export function arrayToMap(array: readonly V1[], makeKey: (value: V1) => K | undefined, makeValue: (value: V1) => V1 | V2 = identity): ESMap { - const result = new Map(); - for (const value of array) { - const key = makeKey(value); - if (key !== undefined) result.set(key, makeValue(value)); +export function group(values: readonly T[], getGroupId: (value: T) => K): readonly (readonly T[])[]; +export function group(values: readonly T[], getGroupId: (value: T) => K, resultSelector: (values: readonly T[]) => R): R[]; +export function group(values: readonly T[], getGroupId: (value: T) => string): readonly (readonly T[])[]; +export function group(values: readonly T[], getGroupId: (value: T) => string, resultSelector: (values: readonly T[]) => R): R[]; +export function group(values: readonly T[], getGroupId: (value: T) => K, resultSelector: (values: readonly T[]) => readonly T[] = identity): readonly (readonly T[])[] { + return arrayFrom(arrayToMultiMap(values, getGroupId).values(), resultSelector); +} + +export function clone(object: T): T { + const result: any = {}; + for (const id in object) { + if (hasOwnProperty.call(object, id)) { + result[id] = (object as any)[id]; } - return result; } + return result; +} - export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number): T[]; - export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number, makeValue: (value: T) => U): U[]; - export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number, makeValue: (value: T) => T | U = identity): (T | U)[] { - const result: (T | U)[] = []; - for (const value of array) { - result[makeKey(value)] = makeValue(value); +/** + * Creates a new object by adding the own properties of `second`, then the own properties of `first`. + * + * NOTE: This means that if a property exists in both `first` and `second`, the property in `first` will be chosen. + */ +export function extend(first: T1, second: T2): T1 & T2 { + const result: T1 & T2 = {} as any; + for (const id in second) { + if (hasOwnProperty.call(second, id)) { + (result as any)[id] = (second as any)[id]; } - return result; } - export function arrayToMultiMap(values: readonly V[], makeKey: (value: V) => K): MultiMap; - export function arrayToMultiMap(values: readonly V[], makeKey: (value: V) => K, makeValue: (value: V) => U): MultiMap; - export function arrayToMultiMap(values: readonly V[], makeKey: (value: V) => K, makeValue: (value: V) => V | U = identity): MultiMap { - const result = createMultiMap(); - for (const value of values) { - result.add(makeKey(value), makeValue(value)); + for (const id in first) { + if (hasOwnProperty.call(first, id)) { + (result as any)[id] = (first as any)[id]; } - return result; } - export function group(values: readonly T[], getGroupId: (value: T) => K): readonly (readonly T[])[]; - export function group(values: readonly T[], getGroupId: (value: T) => K, resultSelector: (values: readonly T[]) => R): R[]; - export function group(values: readonly T[], getGroupId: (value: T) => string): readonly (readonly T[])[]; - export function group(values: readonly T[], getGroupId: (value: T) => string, resultSelector: (values: readonly T[]) => R): R[]; - export function group(values: readonly T[], getGroupId: (value: T) => K, resultSelector: (values: readonly T[]) => readonly T[] = identity): readonly (readonly T[])[] { - return arrayFrom(arrayToMultiMap(values, getGroupId).values(), resultSelector); - } + return result; +} - export function clone(object: T): T { - const result: any = {}; - for (const id in object) { - if (hasOwnProperty.call(object, id)) { - result[id] = (object as any)[id]; - } +export function copyProperties(first: T1, second: T2) { + for (const id in second) { + if (hasOwnProperty.call(second, id)) { + (first as any)[id] = second[id]; } - return result; } +} +export function maybeBind(obj: T, fn: ((this: T, ...args: A) => R) | undefined): ((...args: A) => R) | undefined { + return fn ? fn.bind(obj) : undefined; +} + +export interface MultiMap extends ESMap { /** - * Creates a new object by adding the own properties of `second`, then the own properties of `first`. - * - * NOTE: This means that if a property exists in both `first` and `second`, the property in `first` will be chosen. + * Adds the value to an array of values associated with the key, and returns the array. + * Creates the array if it does not already exist. */ - export function extend(first: T1, second: T2): T1 & T2 { - const result: T1 & T2 = {} as any; - for (const id in second) { - if (hasOwnProperty.call(second, id)) { - (result as any)[id] = (second as any)[id]; - } - } - - for (const id in first) { - if (hasOwnProperty.call(first, id)) { - (result as any)[id] = (first as any)[id]; - } - } - - return result; - } - - export function copyProperties(first: T1, second: T2) { - for (const id in second) { - if (hasOwnProperty.call(second, id)) { - (first as any)[id] = second[id]; - } - } - } - - export function maybeBind(obj: T, fn: ((this: T, ...args: A) => R) | undefined): ((...args: A) => R) | undefined { - return fn ? fn.bind(obj) : undefined; - } - - export interface MultiMap extends ESMap { - /** - * Adds the value to an array of values associated with the key, and returns the array. - * Creates the array if it does not already exist. - */ - add(key: K, value: V): V[]; - /** - * Removes a value from an array of values associated with the key. - * Does not preserve the order of those values. - * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. - */ - remove(key: K, value: V): void; - } + add(key: K, value: V): V[]; + /** + * Removes a value from an array of values associated with the key. + * Does not preserve the order of those values. + * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. + */ + remove(key: K, value: V): void; +} - export function createMultiMap(): MultiMap; - export function createMultiMap(): MultiMap; - export function createMultiMap(): MultiMap { - const map = new Map() as MultiMap; - map.add = multiMapAdd; - map.remove = multiMapRemove; - return map; +export function createMultiMap(): MultiMap; +export function createMultiMap(): MultiMap; +export function createMultiMap(): MultiMap { + const map = new Map() as MultiMap; + map.add = multiMapAdd; + map.remove = multiMapRemove; + return map; +} +function multiMapAdd(this: MultiMap, key: K, value: V) { + let values = this.get(key); + if (values) { + values.push(value); } - function multiMapAdd(this: MultiMap, key: K, value: V) { - let values = this.get(key); - if (values) { - values.push(value); - } - else { - this.set(key, values = [value]); - } - return values; + else { + this.set(key, values = [value]); } - function multiMapRemove(this: MultiMap, key: K, value: V) { - const values = this.get(key); - if (values) { - unorderedRemoveItem(values, value); - if (!values.length) { - this.delete(key); - } + return values; +} +function multiMapRemove(this: MultiMap, key: K, value: V) { + const values = this.get(key); + if (values) { + unorderedRemoveItem(values, value); + if (!values.length) { + this.delete(key); } } +} - export interface UnderscoreEscapedMultiMap extends UnderscoreEscapedMap { - /** - * Adds the value to an array of values associated with the key, and returns the array. - * Creates the array if it does not already exist. - */ - add(key: __String, value: T): T[]; - /** - * Removes a value from an array of values associated with the key. - * Does not preserve the order of those values. - * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. - */ - remove(key: __String, value: T): void; - } - - export function createUnderscoreEscapedMultiMap(): UnderscoreEscapedMultiMap { - return createMultiMap() as UnderscoreEscapedMultiMap; - } - +export interface UnderscoreEscapedMultiMap extends UnderscoreEscapedMap { /** - * Tests whether a value is an array. + * Adds the value to an array of values associated with the key, and returns the array. + * Creates the array if it does not already exist. */ - export function isArray(value: any): value is readonly {}[] { - return Array.isArray ? Array.isArray(value) : value instanceof Array; - } - - export function toArray(value: T | T[]): T[]; - export function toArray(value: T | readonly T[]): readonly T[]; - export function toArray(value: T | T[]): T[] { - return isArray(value) ? value : [value]; - } - + add(key: __String, value: T): T[]; /** - * Tests whether a value is string + * Removes a value from an array of values associated with the key. + * Does not preserve the order of those values. + * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. */ - export function isString(text: unknown): text is string { - return typeof text === "string"; - } - export function isNumber(x: unknown): x is number { - return typeof x === "number"; - } + remove(key: __String, value: T): void; +} - export function tryCast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined; - export function tryCast(value: T, test: (value: T) => boolean): T | undefined; - export function tryCast(value: T, test: (value: T) => boolean): T | undefined { - return value !== undefined && test(value) ? value : undefined; - } +export function createUnderscoreEscapedMultiMap(): UnderscoreEscapedMultiMap { + return createMultiMap() as UnderscoreEscapedMultiMap; +} - export function cast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut { - if (value !== undefined && test(value)) return value; +/** + * Tests whether a value is an array. + */ +export function isArray(value: any): value is readonly {}[] { + return Array.isArray ? Array.isArray(value) : value instanceof Array; +} - return Debug.fail(`Invalid cast. The supplied value ${value} did not pass the test '${Debug.getFunctionName(test)}'.`); - } +export function toArray(value: T | T[]): T[]; +export function toArray(value: T | readonly T[]): readonly T[]; +export function toArray(value: T | T[]): T[] { + return isArray(value) ? value : [value]; +} - /** Does nothing. */ - export function noop(_?: {} | null | undefined): void { } +/** + * Tests whether a value is string + */ +export function isString(text: unknown): text is string { + return typeof text === "string"; +} +export function isNumber(x: unknown): x is number { + return typeof x === "number"; +} - /** Do nothing and return false */ - export function returnFalse(): false { - return false; - } +export function tryCast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined; +export function tryCast(value: T, test: (value: T) => boolean): T | undefined; +export function tryCast(value: T, test: (value: T) => boolean): T | undefined { + return value !== undefined && test(value) ? value : undefined; +} - /** Do nothing and return true */ - export function returnTrue(): true { - return true; - } +export function cast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut { + if (value !== undefined && test(value)) return value; - /** Do nothing and return undefined */ - export function returnUndefined(): undefined { - return undefined; - } + return Debug.fail(`Invalid cast. The supplied value ${value} did not pass the test '${Debug.getFunctionName(test)}'.`); +} - /** Returns its argument. */ - export function identity(x: T) { - return x; - } - - /** Returns lower case string */ - export function toLowerCase(x: string) { - return x.toLowerCase(); - } - - // We convert the file names to lower case as key for file name on case insensitive file system - // While doing so we need to handle special characters (eg \u0130) to ensure that we dont convert - // it to lower case, fileName with its lowercase form can exist along side it. - // Handle special characters and make those case sensitive instead - // - // |-#--|-Unicode--|-Char code-|-Desc-------------------------------------------------------------------| - // | 1. | i | 105 | Ascii i | - // | 2. | I | 73 | Ascii I | - // |-------- Special characters ------------------------------------------------------------------------| - // | 3. | \u0130 | 304 | Upper case I with dot above | - // | 4. | i,\u0307 | 105,775 | i, followed by 775: Lower case of (3rd item) | - // | 5. | I,\u0307 | 73,775 | I, followed by 775: Upper case of (4th item), lower case is (4th item) | - // | 6. | \u0131 | 305 | Lower case i without dot, upper case is I (2nd item) | - // | 7. | \u00DF | 223 | Lower case sharp s | - // - // Because item 3 is special where in its lowercase character has its own - // upper case form we cant convert its case. - // Rest special characters are either already in lower case format or - // they have corresponding upper case character so they dont need special handling - // - // But to avoid having to do string building for most common cases, also ignore - // a-z, 0-9, \u0131, \u00DF, \, /, ., : and space - const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_\. ]+/g; - /** - * Case insensitive file systems have descripencies in how they handle some characters (eg. turkish Upper case I with dot on top - \u0130) - * This function is used in places where we want to make file name as a key on these systems - * It is possible on mac to be able to refer to file name with I with dot on top as a fileName with its lower case form - * But on windows we cannot. Windows can have fileName with I with dot on top next to its lower case and they can not each be referred with the lowercase forms - * Technically we would want this function to be platform sepcific as well but - * our api has till now only taken caseSensitive as the only input and just for some characters we dont want to update API and ensure all customers use those api - * We could use upper case and we would still need to deal with the descripencies but - * we want to continue using lower case since in most cases filenames are lowercasewe and wont need any case changes and avoid having to store another string for the key - * So for this function purpose, we go ahead and assume character I with dot on top it as case sensitive since its very unlikely to use lower case form of that special character - */ - export function toFileNameLowerCase(x: string) { - return fileNameLowerCaseRegExp.test(x) ? - x.replace(fileNameLowerCaseRegExp, toLowerCase) : - x; - } +/** Does nothing. */ +export function noop(_?: {} | null | undefined): void { } - /** Throws an error because a function is not implemented. */ - export function notImplemented(): never { - throw new Error("Not implemented"); - } +/** Do nothing and return false */ +export function returnFalse(): false { + return false; +} - export function memoize(callback: () => T): () => T { - let value: T; - return () => { - if (callback) { - value = callback(); - callback = undefined!; - } - return value; - }; - } - - /** A version of `memoize` that supports a single primitive argument */ - export function memoizeOne(callback: (arg: A) => T): (arg: A) => T { - const map = new Map(); - return (arg: A) => { - const key = `${typeof arg}:${arg}`; - let value = map.get(key); - if (value === undefined && !map.has(key)) { - value = callback(arg); - map.set(key, value); - } - return value!; - }; - } +/** Do nothing and return true */ +export function returnTrue(): true { + return true; +} - /** - * High-order function, composes functions. Note that functions are composed inside-out; - * for example, `compose(a, b)` is the equivalent of `x => b(a(x))`. - * - * @param args The functions to compose. - */ - export function compose(...args: ((t: T) => T)[]): (t: T) => T; - export function compose(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T { - if (!!e) { - const args: ((t: T) => T)[] = []; - for (let i = 0; i < arguments.length; i++) { - args[i] = arguments[i]; - } +/** Do nothing and return undefined */ +export function returnUndefined(): undefined { + return undefined; +} - return t => reduceLeft(args, (u, f) => f(u), t); - } - else if (d) { - return t => d(c(b(a(t)))); - } - else if (c) { - return t => c(b(a(t))); - } - else if (b) { - return t => b(a(t)); - } - else if (a) { - return t => a(t); - } - else { - return t => t; - } - } +/** Returns its argument. */ +export function identity(x: T) { + return x; +} - export const enum AssertionLevel { - None = 0, - Normal = 1, - Aggressive = 2, - VeryAggressive = 3, - } +/** Returns lower case string */ +export function toLowerCase(x: string) { + return x.toLowerCase(); +} - /** - * Safer version of `Function` which should not be called. - * Every function should be assignable to this, but this should not be assignable to every function. - */ - export type AnyFunction = (...args: never[]) => void; - export type AnyConstructor = new (...args: unknown[]) => unknown; +// We convert the file names to lower case as key for file name on case insensitive file system +// While doing so we need to handle special characters (eg \u0130) to ensure that we dont convert +// it to lower case, fileName with its lowercase form can exist along side it. +// Handle special characters and make those case sensitive instead +// +// |-#--|-Unicode--|-Char code-|-Desc-------------------------------------------------------------------| +// | 1. | i | 105 | Ascii i | +// | 2. | I | 73 | Ascii I | +// |-------- Special characters ------------------------------------------------------------------------| +// | 3. | \u0130 | 304 | Upper case I with dot above | +// | 4. | i,\u0307 | 105,775 | i, followed by 775: Lower case of (3rd item) | +// | 5. | I,\u0307 | 73,775 | I, followed by 775: Upper case of (4th item), lower case is (4th item) | +// | 6. | \u0131 | 305 | Lower case i without dot, upper case is I (2nd item) | +// | 7. | \u00DF | 223 | Lower case sharp s | +// +// Because item 3 is special where in its lowercase character has its own +// upper case form we cant convert its case. +// Rest special characters are either already in lower case format or +// they have corresponding upper case character so they dont need special handling +// +// But to avoid having to do string building for most common cases, also ignore +// a-z, 0-9, \u0131, \u00DF, \, /, ., : and space +const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_\. ]+/g; +/** + * Case insensitive file systems have descripencies in how they handle some characters (eg. turkish Upper case I with dot on top - \u0130) + * This function is used in places where we want to make file name as a key on these systems + * It is possible on mac to be able to refer to file name with I with dot on top as a fileName with its lower case form + * But on windows we cannot. Windows can have fileName with I with dot on top next to its lower case and they can not each be referred with the lowercase forms + * Technically we would want this function to be platform sepcific as well but + * our api has till now only taken caseSensitive as the only input and just for some characters we dont want to update API and ensure all customers use those api + * We could use upper case and we would still need to deal with the descripencies but + * we want to continue using lower case since in most cases filenames are lowercasewe and wont need any case changes and avoid having to store another string for the key + * So for this function purpose, we go ahead and assume character I with dot on top it as case sensitive since its very unlikely to use lower case form of that special character + */ +export function toFileNameLowerCase(x: string) { + return fileNameLowerCaseRegExp.test(x) ? + x.replace(fileNameLowerCaseRegExp, toLowerCase) : + x; +} - export function equateValues(a: T, b: T) { - return a === b; - } +/** Throws an error because a function is not implemented. */ +export function notImplemented(): never { + throw new Error("Not implemented"); +} - /** - * Compare the equality of two strings using a case-sensitive ordinal comparison. - * - * Case-sensitive comparisons compare both strings one code-point at a time using the integer - * value of each code-point after applying `toUpperCase` to each string. We always map both - * strings to their upper-case form as some unicode characters do not properly round-trip to - * lowercase (such as `ẞ` (German sharp capital s)). - */ - export function equateStringsCaseInsensitive(a: string, b: string) { - return a === b - || a !== undefined - && b !== undefined - && a.toUpperCase() === b.toUpperCase(); - } +export function memoize(callback: () => T): () => T { + let value: T; + return () => { + if (callback) { + value = callback(); + callback = undefined!; + } + return value; + }; +} - /** - * Compare the equality of two strings using a case-sensitive ordinal comparison. - * - * Case-sensitive comparisons compare both strings one code-point at a time using the - * integer value of each code-point. - */ - export function equateStringsCaseSensitive(a: string, b: string) { - return equateValues(a, b); - } +/** A version of `memoize` that supports a single primitive argument */ +export function memoizeOne(callback: (arg: A) => T): (arg: A) => T { + const map = new Map(); + return (arg: A) => { + const key = `${typeof arg}:${arg}`; + let value = map.get(key); + if (value === undefined && !map.has(key)) { + value = callback(arg); + map.set(key, value); + } + return value!; + }; +} - function compareComparableValues(a: string | undefined, b: string | undefined): Comparison; - function compareComparableValues(a: number | undefined, b: number | undefined): Comparison; - function compareComparableValues(a: string | number | undefined, b: string | number | undefined) { - return a === b ? Comparison.EqualTo : - a === undefined ? Comparison.LessThan : - b === undefined ? Comparison.GreaterThan : - a < b ? Comparison.LessThan : - Comparison.GreaterThan; - } +/** + * High-order function, composes functions. Note that functions are composed inside-out; + * for example, `compose(a, b)` is the equivalent of `x => b(a(x))`. + * + * @param args The functions to compose. + */ +export function compose(...args: ((t: T) => T)[]): (t: T) => T; +export function compose(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T { + if (!!e) { + const args: ((t: T) => T)[] = []; + for (let i = 0; i < arguments.length; i++) { + args[i] = arguments[i]; + } - /** - * Compare two numeric values for their order relative to each other. - * To compare strings, use any of the `compareStrings` functions. - */ - export function compareValues(a: number | undefined, b: number | undefined): Comparison { - return compareComparableValues(a, b); + return t => reduceLeft(args, (u, f) => f(u), t); } - - /** - * Compare two TextSpans, first by `start`, then by `length`. - */ - export function compareTextSpans(a: Partial | undefined, b: Partial | undefined): Comparison { - return compareValues(a?.start, b?.start) || compareValues(a?.length, b?.length); + else if (d) { + return t => d(c(b(a(t)))); } - - export function min(a: T, b: T, compare: Comparer): T { - return compare(a, b) === Comparison.LessThan ? a : b; + else if (c) { + return t => c(b(a(t))); } - - /** - * Compare two strings using a case-insensitive ordinal comparison. - * - * Ordinal comparisons are based on the difference between the unicode code points of both - * strings. Characters with multiple unicode representations are considered unequal. Ordinal - * comparisons provide predictable ordering, but place "a" after "B". - * - * Case-insensitive comparisons compare both strings one code-point at a time using the integer - * value of each code-point after applying `toUpperCase` to each string. We always map both - * strings to their upper-case form as some unicode characters do not properly round-trip to - * lowercase (such as `ẞ` (German sharp capital s)). - */ - export function compareStringsCaseInsensitive(a: string, b: string) { - if (a === b) return Comparison.EqualTo; - if (a === undefined) return Comparison.LessThan; - if (b === undefined) return Comparison.GreaterThan; - a = a.toUpperCase(); - b = b.toUpperCase(); - return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; + else if (b) { + return t => b(a(t)); } - - /** - * Compare two strings using a case-sensitive ordinal comparison. - * - * Ordinal comparisons are based on the difference between the unicode code points of both - * strings. Characters with multiple unicode representations are considered unequal. Ordinal - * comparisons provide predictable ordering, but place "a" after "B". - * - * Case-sensitive comparisons compare both strings one code-point at a time using the integer - * value of each code-point. - */ - export function compareStringsCaseSensitive(a: string | undefined, b: string | undefined): Comparison { - return compareComparableValues(a, b); + else if (a) { + return t => a(t); } - - export function getStringComparer(ignoreCase?: boolean) { - return ignoreCase ? compareStringsCaseInsensitive : compareStringsCaseSensitive; + else { + return t => t; } +} - /** - * Creates a string comparer for use with string collation in the UI. - */ - const createUIStringComparer = (() => { - let defaultComparer: Comparer | undefined; - let enUSComparer: Comparer | undefined; +export const enum AssertionLevel { + None = 0, + Normal = 1, + Aggressive = 2, + VeryAggressive = 3, +} - const stringComparerFactory = getStringComparerFactory(); - return createStringComparer; +/** + * Safer version of `Function` which should not be called. + * Every function should be assignable to this, but this should not be assignable to every function. + */ +export type AnyFunction = (...args: never[]) => void; +export type AnyConstructor = new (...args: unknown[]) => unknown; - function compareWithCallback(a: string | undefined, b: string | undefined, comparer: (a: string, b: string) => number) { - if (a === b) return Comparison.EqualTo; - if (a === undefined) return Comparison.LessThan; - if (b === undefined) return Comparison.GreaterThan; - const value = comparer(a, b); - return value < 0 ? Comparison.LessThan : value > 0 ? Comparison.GreaterThan : Comparison.EqualTo; - } +export function equateValues(a: T, b: T) { + return a === b; +} - function createIntlCollatorStringComparer(locale: string | undefined): Comparer { - // Intl.Collator.prototype.compare is bound to the collator. See NOTE in - // http://www.ecma-international.org/ecma-402/2.0/#sec-Intl.Collator.prototype.compare - const comparer = new Intl.Collator(locale, { usage: "sort", sensitivity: "variant" }).compare; - return (a, b) => compareWithCallback(a, b, comparer); - } +/** + * Compare the equality of two strings using a case-sensitive ordinal comparison. + * + * Case-sensitive comparisons compare both strings one code-point at a time using the integer + * value of each code-point after applying `toUpperCase` to each string. We always map both + * strings to their upper-case form as some unicode characters do not properly round-trip to + * lowercase (such as `ẞ` (German sharp capital s)). + */ +export function equateStringsCaseInsensitive(a: string, b: string) { + return a === b + || a !== undefined + && b !== undefined + && a.toUpperCase() === b.toUpperCase(); +} - function createLocaleCompareStringComparer(locale: string | undefined): Comparer { - // if the locale is not the default locale (`undefined`), use the fallback comparer. - if (locale !== undefined) return createFallbackStringComparer(); +/** + * Compare the equality of two strings using a case-sensitive ordinal comparison. + * + * Case-sensitive comparisons compare both strings one code-point at a time using the + * integer value of each code-point. + */ +export function equateStringsCaseSensitive(a: string, b: string) { + return equateValues(a, b); +} - return (a, b) => compareWithCallback(a, b, compareStrings); +function compareComparableValues(a: string | undefined, b: string | undefined): Comparison; +function compareComparableValues(a: number | undefined, b: number | undefined): Comparison; +function compareComparableValues(a: string | number | undefined, b: string | number | undefined) { + return a === b ? Comparison.EqualTo : + a === undefined ? Comparison.LessThan : + b === undefined ? Comparison.GreaterThan : + a < b ? Comparison.LessThan : + Comparison.GreaterThan; +} - function compareStrings(a: string, b: string) { - return a.localeCompare(b); - } - } +/** + * Compare two numeric values for their order relative to each other. + * To compare strings, use any of the `compareStrings` functions. + */ +export function compareValues(a: number | undefined, b: number | undefined): Comparison { + return compareComparableValues(a, b); +} - function createFallbackStringComparer(): Comparer { - // An ordinal comparison puts "A" after "b", but for the UI we want "A" before "b". - // We first sort case insensitively. So "Aaa" will come before "baa". - // Then we sort case sensitively, so "aaa" will come before "Aaa". - // - // For case insensitive comparisons we always map both strings to their - // upper-case form as some unicode characters do not properly round-trip to - // lowercase (such as `ẞ` (German sharp capital s)). - return (a, b) => compareWithCallback(a, b, compareDictionaryOrder); +/** + * Compare two TextSpans, first by `start`, then by `length`. + */ +export function compareTextSpans(a: Partial | undefined, b: Partial | undefined): Comparison { + return compareValues(a?.start, b?.start) || compareValues(a?.length, b?.length); +} - function compareDictionaryOrder(a: string, b: string) { - return compareStrings(a.toUpperCase(), b.toUpperCase()) || compareStrings(a, b); - } +export function min(a: T, b: T, compare: Comparer): T { + return compare(a, b) === Comparison.LessThan ? a : b; +} - function compareStrings(a: string, b: string) { - return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; - } - } +/** + * Compare two strings using a case-insensitive ordinal comparison. + * + * Ordinal comparisons are based on the difference between the unicode code points of both + * strings. Characters with multiple unicode representations are considered unequal. Ordinal + * comparisons provide predictable ordering, but place "a" after "B". + * + * Case-insensitive comparisons compare both strings one code-point at a time using the integer + * value of each code-point after applying `toUpperCase` to each string. We always map both + * strings to their upper-case form as some unicode characters do not properly round-trip to + * lowercase (such as `ẞ` (German sharp capital s)). + */ +export function compareStringsCaseInsensitive(a: string, b: string) { + if (a === b) return Comparison.EqualTo; + if (a === undefined) return Comparison.LessThan; + if (b === undefined) return Comparison.GreaterThan; + a = a.toUpperCase(); + b = b.toUpperCase(); + return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; +} - function getStringComparerFactory() { - // If the host supports Intl, we use it for comparisons using the default locale. - if (typeof Intl === "object" && typeof Intl.Collator === "function") { - return createIntlCollatorStringComparer; - } +/** + * Compare two strings using a case-sensitive ordinal comparison. + * + * Ordinal comparisons are based on the difference between the unicode code points of both + * strings. Characters with multiple unicode representations are considered unequal. Ordinal + * comparisons provide predictable ordering, but place "a" after "B". + * + * Case-sensitive comparisons compare both strings one code-point at a time using the integer + * value of each code-point. + */ +export function compareStringsCaseSensitive(a: string | undefined, b: string | undefined): Comparison { + return compareComparableValues(a, b); +} - // If the host does not support Intl, we fall back to localeCompare. - // localeCompare in Node v0.10 is just an ordinal comparison, so don't use it. - if (typeof String.prototype.localeCompare === "function" && - typeof String.prototype.toLocaleUpperCase === "function" && - "a".localeCompare("B") < 0) { - return createLocaleCompareStringComparer; - } +export function getStringComparer(ignoreCase?: boolean) { + return ignoreCase ? compareStringsCaseInsensitive : compareStringsCaseSensitive; +} - // Otherwise, fall back to ordinal comparison: - return createFallbackStringComparer; - } +/** + * Creates a string comparer for use with string collation in the UI. + */ +const createUIStringComparer = (() => { + let defaultComparer: Comparer | undefined; + let enUSComparer: Comparer | undefined; - function createStringComparer(locale: string | undefined) { - // Hold onto common string comparers. This avoids constantly reallocating comparers during - // tests. - if (locale === undefined) { - return defaultComparer || (defaultComparer = stringComparerFactory(locale)); - } - else if (locale === "en-US") { - return enUSComparer || (enUSComparer = stringComparerFactory(locale)); - } - else { - return stringComparerFactory(locale); - } - } - })(); + const stringComparerFactory = getStringComparerFactory(); + return createStringComparer; - let uiComparerCaseSensitive: Comparer | undefined; - let uiLocale: string | undefined; + function compareWithCallback(a: string | undefined, b: string | undefined, comparer: (a: string, b: string) => number) { + if (a === b) return Comparison.EqualTo; + if (a === undefined) return Comparison.LessThan; + if (b === undefined) return Comparison.GreaterThan; + const value = comparer(a, b); + return value < 0 ? Comparison.LessThan : value > 0 ? Comparison.GreaterThan : Comparison.EqualTo; + } - export function getUILocale() { - return uiLocale; + function createIntlCollatorStringComparer(locale: string | undefined): Comparer { + // Intl.Collator.prototype.compare is bound to the collator. See NOTE in + // http://www.ecma-international.org/ecma-402/2.0/#sec-Intl.Collator.prototype.compare + const comparer = new Intl.Collator(locale, { usage: "sort", sensitivity: "variant" }).compare; + return (a, b) => compareWithCallback(a, b, comparer); } - export function setUILocale(value: string | undefined) { - if (uiLocale !== value) { - uiLocale = value; - uiComparerCaseSensitive = undefined; + function createLocaleCompareStringComparer(locale: string | undefined): Comparer { + // if the locale is not the default locale (`undefined`), use the fallback comparer. + if (locale !== undefined) return createFallbackStringComparer(); + + return (a, b) => compareWithCallback(a, b, compareStrings); + + function compareStrings(a: string, b: string) { + return a.localeCompare(b); } } - /** - * Compare two strings in a using the case-sensitive sort behavior of the UI locale. - * - * Ordering is not predictable between different host locales, but is best for displaying - * ordered data for UI presentation. Characters with multiple unicode representations may - * be considered equal. - * - * Case-sensitive comparisons compare strings that differ in base characters, or - * accents/diacritic marks, or case as unequal. - */ - export function compareStringsCaseSensitiveUI(a: string, b: string) { - const comparer = uiComparerCaseSensitive || (uiComparerCaseSensitive = createUIStringComparer(uiLocale)); - return comparer(a, b); + function createFallbackStringComparer(): Comparer { + // An ordinal comparison puts "A" after "b", but for the UI we want "A" before "b". + // We first sort case insensitively. So "Aaa" will come before "baa". + // Then we sort case sensitively, so "aaa" will come before "Aaa". + // + // For case insensitive comparisons we always map both strings to their + // upper-case form as some unicode characters do not properly round-trip to + // lowercase (such as `ẞ` (German sharp capital s)). + return (a, b) => compareWithCallback(a, b, compareDictionaryOrder); + + function compareDictionaryOrder(a: string, b: string) { + return compareStrings(a.toUpperCase(), b.toUpperCase()) || compareStrings(a, b); + } + + function compareStrings(a: string, b: string) { + return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; + } } - export function compareProperties(a: T | undefined, b: T | undefined, key: K, comparer: Comparer): Comparison { - return a === b ? Comparison.EqualTo : - a === undefined ? Comparison.LessThan : - b === undefined ? Comparison.GreaterThan : - comparer(a[key], b[key]); + function getStringComparerFactory() { + // If the host supports Intl, we use it for comparisons using the default locale. + if (typeof Intl === "object" && typeof Intl.Collator === "function") { + return createIntlCollatorStringComparer; + } + + // If the host does not support Intl, we fall back to localeCompare. + // localeCompare in Node v0.10 is just an ordinal comparison, so don't use it. + if (typeof String.prototype.localeCompare === "function" && + typeof String.prototype.toLocaleUpperCase === "function" && + "a".localeCompare("B") < 0) { + return createLocaleCompareStringComparer; + } + + // Otherwise, fall back to ordinal comparison: + return createFallbackStringComparer; } - /** True is greater than false. */ - export function compareBooleans(a: boolean, b: boolean): Comparison { - return compareValues(a ? 1 : 0, b ? 1 : 0); + function createStringComparer(locale: string | undefined) { + // Hold onto common string comparers. This avoids constantly reallocating comparers during + // tests. + if (locale === undefined) { + return defaultComparer || (defaultComparer = stringComparerFactory(locale)); + } + else if (locale === "en-US") { + return enUSComparer || (enUSComparer = stringComparerFactory(locale)); + } + else { + return stringComparerFactory(locale); + } } +})(); - /** - * Given a name and a list of names that are *not* equal to the name, return a spelling suggestion if there is one that is close enough. - * Names less than length 3 only check for case-insensitive equality. - * - * find the candidate with the smallest Levenshtein distance, - * except for candidates: - * * With no name - * * Whose length differs from the target name by more than 0.34 of the length of the name. - * * Whose levenshtein distance is more than 0.4 of the length of the name - * (0.4 allows 1 substitution/transposition for every 5 characters, - * and 1 insertion/deletion at 3 characters) - */ - export function getSpellingSuggestion(name: string, candidates: T[], getName: (candidate: T) => string | undefined): T | undefined { - const maximumLengthDifference = Math.min(2, Math.floor(name.length * 0.34)); - let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result is worse than this, don't bother. - let bestCandidate: T | undefined; - for (const candidate of candidates) { - const candidateName = getName(candidate); - if (candidateName !== undefined && Math.abs(candidateName.length - name.length) <= maximumLengthDifference) { - if (candidateName === name) { - continue; - } - // Only consider candidates less than 3 characters long when they differ by case. - // Otherwise, don't bother, since a user would usually notice differences of a 2-character name. - if (candidateName.length < 3 && candidateName.toLowerCase() !== name.toLowerCase()) { - continue; - } +let uiComparerCaseSensitive: Comparer | undefined; +let uiLocale: string | undefined; - const distance = levenshteinWithMax(name, candidateName, bestDistance - 0.1); - if (distance === undefined) { - continue; - } +export function getUILocale() { + return uiLocale; +} - Debug.assert(distance < bestDistance); // Else `levenshteinWithMax` should return undefined - bestDistance = distance; - bestCandidate = candidate; - } - } - return bestCandidate; +export function setUILocale(value: string | undefined) { + if (uiLocale !== value) { + uiLocale = value; + uiComparerCaseSensitive = undefined; } +} - function levenshteinWithMax(s1: string, s2: string, max: number): number | undefined { - let previous = new Array(s2.length + 1); - let current = new Array(s2.length + 1); - /** Represents any value > max. We don't care about the particular value. */ - const big = max + 0.01; +/** + * Compare two strings in a using the case-sensitive sort behavior of the UI locale. + * + * Ordering is not predictable between different host locales, but is best for displaying + * ordered data for UI presentation. Characters with multiple unicode representations may + * be considered equal. + * + * Case-sensitive comparisons compare strings that differ in base characters, or + * accents/diacritic marks, or case as unequal. + */ +export function compareStringsCaseSensitiveUI(a: string, b: string) { + const comparer = uiComparerCaseSensitive || (uiComparerCaseSensitive = createUIStringComparer(uiLocale)); + return comparer(a, b); +} - for (let i = 0; i <= s2.length; i++) { - previous[i] = i; - } +export function compareProperties(a: T | undefined, b: T | undefined, key: K, comparer: Comparer): Comparison { + return a === b ? Comparison.EqualTo : + a === undefined ? Comparison.LessThan : + b === undefined ? Comparison.GreaterThan : + comparer(a[key], b[key]); +} - for (let i = 1; i <= s1.length; i++) { - const c1 = s1.charCodeAt(i - 1); - const minJ = Math.ceil(i > max ? i - max : 1); - const maxJ = Math.floor(s2.length > max + i ? max + i : s2.length); - current[0] = i; - /** Smallest value of the matrix in the ith column. */ - let colMin = i; - for (let j = 1; j < minJ; j++) { - current[j] = big; - } - for (let j = minJ; j <= maxJ; j++) { - // case difference should be significantly cheaper than other differences - const substitutionDistance = s1[i - 1].toLowerCase() === s2[j-1].toLowerCase() - ? (previous[j - 1] + 0.1) - : (previous[j - 1] + 2); - const dist = c1 === s2.charCodeAt(j - 1) - ? previous[j - 1] - : Math.min(/*delete*/ previous[j] + 1, /*insert*/ current[j - 1] + 1, /*substitute*/ substitutionDistance); - current[j] = dist; - colMin = Math.min(colMin, dist); - } - for (let j = maxJ + 1; j <= s2.length; j++) { - current[j] = big; - } - if (colMin > max) { - // Give up -- everything in this column is > max and it can't get better in future columns. - return undefined; - } +/** True is greater than false. */ +export function compareBooleans(a: boolean, b: boolean): Comparison { + return compareValues(a ? 1 : 0, b ? 1 : 0); +} + +/** + * Given a name and a list of names that are *not* equal to the name, return a spelling suggestion if there is one that is close enough. + * Names less than length 3 only check for case-insensitive equality. + * + * find the candidate with the smallest Levenshtein distance, + * except for candidates: + * * With no name + * * Whose length differs from the target name by more than 0.34 of the length of the name. + * * Whose levenshtein distance is more than 0.4 of the length of the name + * (0.4 allows 1 substitution/transposition for every 5 characters, + * and 1 insertion/deletion at 3 characters) + */ +export function getSpellingSuggestion(name: string, candidates: T[], getName: (candidate: T) => string | undefined): T | undefined { + const maximumLengthDifference = Math.min(2, Math.floor(name.length * 0.34)); + let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result is worse than this, don't bother. + let bestCandidate: T | undefined; + for (const candidate of candidates) { + const candidateName = getName(candidate); + if (candidateName !== undefined && Math.abs(candidateName.length - name.length) <= maximumLengthDifference) { + if (candidateName === name) { + continue; + } + // Only consider candidates less than 3 characters long when they differ by case. + // Otherwise, don't bother, since a user would usually notice differences of a 2-character name. + if (candidateName.length < 3 && candidateName.toLowerCase() !== name.toLowerCase()) { + continue; + } + + const distance = levenshteinWithMax(name, candidateName, bestDistance - 0.1); + if (distance === undefined) { + continue; + } + + Debug.assert(distance < bestDistance); // Else `levenshteinWithMax` should return undefined + bestDistance = distance; + bestCandidate = candidate; + } + } + return bestCandidate; +} - const temp = previous; - previous = current; - current = temp; +function levenshteinWithMax(s1: string, s2: string, max: number): number | undefined { + let previous = new Array(s2.length + 1); + let current = new Array(s2.length + 1); + /** Represents any value > max. We don't care about the particular value. */ + const big = max + 0.01; + + for (let i = 0; i <= s2.length; i++) { + previous[i] = i; + } + + for (let i = 1; i <= s1.length; i++) { + const c1 = s1.charCodeAt(i - 1); + const minJ = Math.ceil(i > max ? i - max : 1); + const maxJ = Math.floor(s2.length > max + i ? max + i : s2.length); + current[0] = i; + /** Smallest value of the matrix in the ith column. */ + let colMin = i; + for (let j = 1; j < minJ; j++) { + current[j] = big; + } + for (let j = minJ; j <= maxJ; j++) { + // case difference should be significantly cheaper than other differences + const substitutionDistance = s1[i - 1].toLowerCase() === s2[j-1].toLowerCase() + ? (previous[j - 1] + 0.1) + : (previous[j - 1] + 2); + const dist = c1 === s2.charCodeAt(j - 1) + ? previous[j - 1] + : Math.min(/*delete*/ previous[j] + 1, /*insert*/ current[j - 1] + 1, /*substitute*/ substitutionDistance); + current[j] = dist; + colMin = Math.min(colMin, dist); + } + for (let j = maxJ + 1; j <= s2.length; j++) { + current[j] = big; + } + if (colMin > max) { + // Give up -- everything in this column is > max and it can't get better in future columns. + return undefined; } - const res = previous[s2.length]; - return res > max ? undefined : res; + const temp = previous; + previous = current; + current = temp; } - export function endsWith(str: string, suffix: string): boolean { - const expectedPos = str.length - suffix.length; - return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos; - } + const res = previous[s2.length]; + return res > max ? undefined : res; +} - export function removeSuffix(str: string, suffix: string): string { - return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : str; - } +export function endsWith(str: string, suffix: string): boolean { + const expectedPos = str.length - suffix.length; + return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos; +} - export function tryRemoveSuffix(str: string, suffix: string): string | undefined { - return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : undefined; - } +export function removeSuffix(str: string, suffix: string): string { + return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : str; +} - export function stringContains(str: string, substring: string): boolean { - return str.indexOf(substring) !== -1; - } +export function tryRemoveSuffix(str: string, suffix: string): string | undefined { + return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : undefined; +} - /** - * Takes a string like "jquery-min.4.2.3" and returns "jquery" - */ - export function removeMinAndVersionNumbers(fileName: string) { - // We used to use the regex /[.-]((min)|(\d+(\.\d+)*))$/ and would just .replace it twice. - // Unfortunately, that regex has O(n^2) performance because v8 doesn't match from the end of the string. - // Instead, we now essentially scan the filename (backwards) ourselves. - - let end: number = fileName.length; - - for (let pos = end - 1; pos > 0; pos--) { - let ch: number = fileName.charCodeAt(pos); - if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) { - // Match a \d+ segment - do { - --pos; - ch = fileName.charCodeAt(pos); - } while (pos > 0 && ch >= CharacterCodes._0 && ch <= CharacterCodes._9); - } - else if (pos > 4 && (ch === CharacterCodes.n || ch === CharacterCodes.N)) { - // Looking for "min" or "min" - // Already matched the 'n' - --pos; - ch = fileName.charCodeAt(pos); - if (ch !== CharacterCodes.i && ch !== CharacterCodes.I) { - break; - } - --pos; - ch = fileName.charCodeAt(pos); - if (ch !== CharacterCodes.m && ch !== CharacterCodes.M) { - break; - } +export function stringContains(str: string, substring: string): boolean { + return str.indexOf(substring) !== -1; +} + +/** + * Takes a string like "jquery-min.4.2.3" and returns "jquery" + */ +export function removeMinAndVersionNumbers(fileName: string) { + // We used to use the regex /[.-]((min)|(\d+(\.\d+)*))$/ and would just .replace it twice. + // Unfortunately, that regex has O(n^2) performance because v8 doesn't match from the end of the string. + // Instead, we now essentially scan the filename (backwards) ourselves. + + let end: number = fileName.length; + + for (let pos = end - 1; pos > 0; pos--) { + let ch: number = fileName.charCodeAt(pos); + if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) { + // Match a \d+ segment + do { --pos; ch = fileName.charCodeAt(pos); - } - else { - // This character is not part of either suffix pattern + } while (pos > 0 && ch >= CharacterCodes._0 && ch <= CharacterCodes._9); + } + else if (pos > 4 && (ch === CharacterCodes.n || ch === CharacterCodes.N)) { + // Looking for "min" or "min" + // Already matched the 'n' + --pos; + ch = fileName.charCodeAt(pos); + if (ch !== CharacterCodes.i && ch !== CharacterCodes.I) { break; } - - if (ch !== CharacterCodes.minus && ch !== CharacterCodes.dot) { + --pos; + ch = fileName.charCodeAt(pos); + if (ch !== CharacterCodes.m && ch !== CharacterCodes.M) { break; } + --pos; + ch = fileName.charCodeAt(pos); + } + else { + // This character is not part of either suffix pattern + break; + } - end = pos; + if (ch !== CharacterCodes.minus && ch !== CharacterCodes.dot) { + break; } - // end might be fileName.length, in which case this should internally no-op - return end === fileName.length ? fileName : fileName.slice(0, end); + end = pos; } - /** Remove an item from an array, moving everything to its right one space left. */ - export function orderedRemoveItem(array: T[], item: T): boolean { - for (let i = 0; i < array.length; i++) { - if (array[i] === item) { - orderedRemoveItemAt(array, i); - return true; - } - } - return false; - } + // end might be fileName.length, in which case this should internally no-op + return end === fileName.length ? fileName : fileName.slice(0, end); +} - /** Remove an item by index from an array, moving everything to its right one space left. */ - export function orderedRemoveItemAt(array: T[], index: number): void { - // This seems to be faster than either `array.splice(i, 1)` or `array.copyWithin(i, i+ 1)`. - for (let i = index; i < array.length - 1; i++) { - array[i] = array[i + 1]; +/** Remove an item from an array, moving everything to its right one space left. */ +export function orderedRemoveItem(array: T[], item: T): boolean { + for (let i = 0; i < array.length; i++) { + if (array[i] === item) { + orderedRemoveItemAt(array, i); + return true; } - array.pop(); } + return false; +} - export function unorderedRemoveItemAt(array: T[], index: number): void { - // Fill in the "hole" left at `index`. - array[index] = array[array.length - 1]; - array.pop(); +/** Remove an item by index from an array, moving everything to its right one space left. */ +export function orderedRemoveItemAt(array: T[], index: number): void { + // This seems to be faster than either `array.splice(i, 1)` or `array.copyWithin(i, i+ 1)`. + for (let i = index; i < array.length - 1; i++) { + array[i] = array[i + 1]; } + array.pop(); +} - /** Remove the *first* occurrence of `item` from the array. */ - export function unorderedRemoveItem(array: T[], item: T) { - return unorderedRemoveFirstItemWhere(array, element => element === item); - } +export function unorderedRemoveItemAt(array: T[], index: number): void { + // Fill in the "hole" left at `index`. + array[index] = array[array.length - 1]; + array.pop(); +} - /** Remove the *first* element satisfying `predicate`. */ - function unorderedRemoveFirstItemWhere(array: T[], predicate: (element: T) => boolean) { - for (let i = 0; i < array.length; i++) { - if (predicate(array[i])) { - unorderedRemoveItemAt(array, i); - return true; - } +/** Remove the *first* occurrence of `item` from the array. */ +export function unorderedRemoveItem(array: T[], item: T) { + return unorderedRemoveFirstItemWhere(array, element => element === item); +} + +/** Remove the *first* element satisfying `predicate`. */ +function unorderedRemoveFirstItemWhere(array: T[], predicate: (element: T) => boolean) { + for (let i = 0; i < array.length; i++) { + if (predicate(array[i])) { + unorderedRemoveItemAt(array, i); + return true; } - return false; } + return false; +} - export type GetCanonicalFileName = (fileName: string) => string; - export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName { - return useCaseSensitiveFileNames ? identity : toFileNameLowerCase; - } +export type GetCanonicalFileName = (fileName: string) => string; +export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName { + return useCaseSensitiveFileNames ? identity : toFileNameLowerCase; +} - /** Represents a "prefix*suffix" pattern. */ - export interface Pattern { - prefix: string; - suffix: string; - } +/** Represents a "prefix*suffix" pattern. */ +export interface Pattern { + prefix: string; + suffix: string; +} - export function patternText({ prefix, suffix }: Pattern): string { - return `${prefix}*${suffix}`; - } +export function patternText({ prefix, suffix }: Pattern): string { + return `${prefix}*${suffix}`; +} - /** - * Given that candidate matches pattern, returns the text matching the '*'. - * E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar" - */ - export function matchedText(pattern: Pattern, candidate: string): string { - Debug.assert(isPatternMatch(pattern, candidate)); - return candidate.substring(pattern.prefix.length, candidate.length - pattern.suffix.length); - } - - /** Return the object corresponding to the best pattern to match `candidate`. */ - export function findBestPatternMatch(values: readonly T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined { - let matchedValue: T | undefined; - // use length of prefix as betterness criteria - let longestMatchPrefixLength = -1; - - for (const v of values) { - const pattern = getPattern(v); - if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) { - longestMatchPrefixLength = pattern.prefix.length; - matchedValue = v; - } - } +/** + * Given that candidate matches pattern, returns the text matching the '*'. + * E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar" + */ +export function matchedText(pattern: Pattern, candidate: string): string { + Debug.assert(isPatternMatch(pattern, candidate)); + return candidate.substring(pattern.prefix.length, candidate.length - pattern.suffix.length); +} - return matchedValue; - } +/** Return the object corresponding to the best pattern to match `candidate`. */ +export function findBestPatternMatch(values: readonly T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined { + let matchedValue: T | undefined; + // use length of prefix as betterness criteria + let longestMatchPrefixLength = -1; - export function startsWith(str: string, prefix: string): boolean { - return str.lastIndexOf(prefix, 0) === 0; + for (const v of values) { + const pattern = getPattern(v); + if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) { + longestMatchPrefixLength = pattern.prefix.length; + matchedValue = v; + } } - export function removePrefix(str: string, prefix: string): string { - return startsWith(str, prefix) ? str.substr(prefix.length) : str; - } + return matchedValue; +} - export function tryRemovePrefix(str: string, prefix: string, getCanonicalFileName: GetCanonicalFileName = identity): string | undefined { - return startsWith(getCanonicalFileName(str), getCanonicalFileName(prefix)) ? str.substring(prefix.length) : undefined; - } +export function startsWith(str: string, prefix: string): boolean { + return str.lastIndexOf(prefix, 0) === 0; +} - function isPatternMatch({ prefix, suffix }: Pattern, candidate: string) { - return candidate.length >= prefix.length + suffix.length && - startsWith(candidate, prefix) && - endsWith(candidate, suffix); - } +export function removePrefix(str: string, prefix: string): string { + return startsWith(str, prefix) ? str.substr(prefix.length) : str; +} - export function and(f: (arg: T) => boolean, g: (arg: T) => boolean) { - return (arg: T) => f(arg) && g(arg); - } +export function tryRemovePrefix(str: string, prefix: string, getCanonicalFileName: GetCanonicalFileName = identity): string | undefined { + return startsWith(getCanonicalFileName(str), getCanonicalFileName(prefix)) ? str.substring(prefix.length) : undefined; +} - export function or(...fs: ((...args: T) => boolean)[]): (...args: T) => boolean { - return (...args) => { - for (const f of fs) { - if (f(...args)) { - return true; - } +function isPatternMatch({ prefix, suffix }: Pattern, candidate: string) { + return candidate.length >= prefix.length + suffix.length && + startsWith(candidate, prefix) && + endsWith(candidate, suffix); +} + +export function and(f: (arg: T) => boolean, g: (arg: T) => boolean) { + return (arg: T) => f(arg) && g(arg); +} + +export function or(...fs: ((...args: T) => boolean)[]): (...args: T) => boolean { + return (...args) => { + for (const f of fs) { + if (f(...args)) { + return true; } - return false; - }; - } + } + return false; + }; +} - export function not(fn: (...args: T) => boolean): (...args: T) => boolean { - return (...args) => !fn(...args); - } +export function not(fn: (...args: T) => boolean): (...args: T) => boolean { + return (...args) => !fn(...args); +} - export function assertType(_: T): void { } +export function assertType(_: T): void { } - export function singleElementArray(t: T | undefined): T[] | undefined { - return t === undefined ? undefined : [t]; - } +export function singleElementArray(t: T | undefined): T[] | undefined { + return t === undefined ? undefined : [t]; +} - export function enumerateInsertsAndDeletes(newItems: readonly T[], oldItems: readonly U[], comparer: (a: T, b: U) => Comparison, inserted: (newItem: T) => void, deleted: (oldItem: U) => void, unchanged?: (oldItem: U, newItem: T) => void) { - unchanged = unchanged || noop; - let newIndex = 0; - let oldIndex = 0; - const newLen = newItems.length; - const oldLen = oldItems.length; - let hasChanges = false; - while (newIndex < newLen && oldIndex < oldLen) { - const newItem = newItems[newIndex]; - const oldItem = oldItems[oldIndex]; - const compareResult = comparer(newItem, oldItem); - if (compareResult === Comparison.LessThan) { - inserted(newItem); - newIndex++; - hasChanges = true; - } - else if (compareResult === Comparison.GreaterThan) { - deleted(oldItem); - oldIndex++; - hasChanges = true; - } - else { - unchanged(oldItem, newItem); - newIndex++; - oldIndex++; - } - } - while (newIndex < newLen) { - inserted(newItems[newIndex++]); +export function enumerateInsertsAndDeletes(newItems: readonly T[], oldItems: readonly U[], comparer: (a: T, b: U) => Comparison, inserted: (newItem: T) => void, deleted: (oldItem: U) => void, unchanged?: (oldItem: U, newItem: T) => void) { + unchanged = unchanged || noop; + let newIndex = 0; + let oldIndex = 0; + const newLen = newItems.length; + const oldLen = oldItems.length; + let hasChanges = false; + while (newIndex < newLen && oldIndex < oldLen) { + const newItem = newItems[newIndex]; + const oldItem = oldItems[oldIndex]; + const compareResult = comparer(newItem, oldItem); + if (compareResult === Comparison.LessThan) { + inserted(newItem); + newIndex++; hasChanges = true; } - while (oldIndex < oldLen) { - deleted(oldItems[oldIndex++]); + else if (compareResult === Comparison.GreaterThan) { + deleted(oldItem); + oldIndex++; hasChanges = true; } - return hasChanges; - } - - export function fill(length: number, cb: (index: number) => T): T[] { - const result = Array(length); - for (let i = 0; i < length; i++) { - result[i] = cb(i); + else { + unchanged(oldItem, newItem); + newIndex++; + oldIndex++; } - return result; } - - export function cartesianProduct(arrays: readonly T[][]) { - const result: T[][] = []; - cartesianProductWorker(arrays, result, /*outer*/ undefined, 0); - return result; + while (newIndex < newLen) { + inserted(newItems[newIndex++]); + hasChanges = true; } - - function cartesianProductWorker(arrays: readonly (readonly T[])[], result: (readonly T[])[], outer: readonly T[] | undefined, index: number) { - for (const element of arrays[index]) { - let inner: T[]; - if (outer) { - inner = outer.slice(); - inner.push(element); - } - else { - inner = [element]; - } - if (index === arrays.length - 1) { - result.push(inner); - } - else { - cartesianProductWorker(arrays, result, inner, index + 1); - } - } + while (oldIndex < oldLen) { + deleted(oldItems[oldIndex++]); + hasChanges = true; } + return hasChanges; +} - - /** - * Returns string left-padded with spaces or zeros until it reaches the given length. - * - * @param s String to pad. - * @param length Final padded length. If less than or equal to 's.length', returns 's' unchanged. - * @param padString Character to use as padding (default " "). - */ - export function padLeft(s: string, length: number, padString: " " | "0" = " ") { - return length <= s.length ? s : padString.repeat(length - s.length) + s; +export function fill(length: number, cb: (index: number) => T): T[] { + const result = Array(length); + for (let i = 0; i < length; i++) { + result[i] = cb(i); } + return result; +} - /** - * Returns string right-padded with spaces until it reaches the given length. - * - * @param s String to pad. - * @param length Final padded length. If less than or equal to 's.length', returns 's' unchanged. - * @param padString Character to use as padding (default " "). - */ - export function padRight(s: string, length: number, padString: " " = " ") { - return length <= s.length ? s : s + padString.repeat(length - s.length); - } +export function cartesianProduct(arrays: readonly T[][]) { + const result: T[][] = []; + cartesianProductWorker(arrays, result, /*outer*/ undefined, 0); + return result; +} - export function takeWhile(array: readonly T[], predicate: (element: T) => element is U): U[]; - export function takeWhile(array: readonly T[], predicate: (element: T) => boolean): T[] { - const len = array.length; - let index = 0; - while (index < len && predicate(array[index])) { - index++; +function cartesianProductWorker(arrays: readonly (readonly T[])[], result: (readonly T[])[], outer: readonly T[] | undefined, index: number) { + for (const element of arrays[index]) { + let inner: T[]; + if (outer) { + inner = outer.slice(); + inner.push(element); + } + else { + inner = [element]; + } + if (index === arrays.length - 1) { + result.push(inner); + } + else { + cartesianProductWorker(arrays, result, inner, index + 1); } - return array.slice(0, index); } +} - /** - * Removes the leading and trailing white space and line terminator characters from a string. - */ - export const trimString = !!String.prototype.trim ? ((s: string) => s.trim()) : (s: string) => trimStringEnd(trimStringStart(s)); - /** - * Returns a copy with trailing whitespace removed. - */ - export const trimStringEnd = !!String.prototype.trimEnd ? ((s: string) => s.trimEnd()) : trimEndImpl; +/** + * Returns string left-padded with spaces or zeros until it reaches the given length. + * + * @param s String to pad. + * @param length Final padded length. If less than or equal to 's.length', returns 's' unchanged. + * @param padString Character to use as padding (default " "). + */ +export function padLeft(s: string, length: number, padString: " " | "0" = " ") { + return length <= s.length ? s : padString.repeat(length - s.length) + s; +} - /** - * Returns a copy with leading whitespace removed. - */ - export const trimStringStart = !!String.prototype.trimStart ? ((s: string) => s.trimStart()) : (s: string) => s.replace(/^\s+/g, ""); +/** + * Returns string right-padded with spaces until it reaches the given length. + * + * @param s String to pad. + * @param length Final padded length. If less than or equal to 's.length', returns 's' unchanged. + * @param padString Character to use as padding (default " "). + */ +export function padRight(s: string, length: number, padString: " " = " ") { + return length <= s.length ? s : s + padString.repeat(length - s.length); +} - /** - * https://jsbench.me/gjkoxld4au/1 - * The simple regex for this, /\s+$/g is O(n^2) in v8. - * The native .trimEnd method is by far best, but since that's technically ES2019, - * we provide a (still much faster than the simple regex) fallback. - */ - function trimEndImpl(s: string) { - let end = s.length - 1; - while (end >= 0) { - if (!isWhiteSpaceLike(s.charCodeAt(end))) break; - end--; - } - return s.slice(0, end + 1); +export function takeWhile(array: readonly T[], predicate: (element: T) => element is U): U[]; +export function takeWhile(array: readonly T[], predicate: (element: T) => boolean): T[] { + const len = array.length; + let index = 0; + while (index < len && predicate(array[index])) { + index++; } + return array.slice(0, index); +} + +/** + * Removes the leading and trailing white space and line terminator characters from a string. + */ +export const trimString = !!String.prototype.trim ? ((s: string) => s.trim()) : (s: string) => trimStringEnd(trimStringStart(s)); + +/** + * Returns a copy with trailing whitespace removed. + */ +export const trimStringEnd = !!String.prototype.trimEnd ? ((s: string) => s.trimEnd()) : trimEndImpl; + +/** + * Returns a copy with leading whitespace removed. + */ +export const trimStringStart = !!String.prototype.trimStart ? ((s: string) => s.trimStart()) : (s: string) => s.replace(/^\s+/g, ""); + +/** + * https://jsbench.me/gjkoxld4au/1 + * The simple regex for this, /\s+$/g is O(n^2) in v8. + * The native .trimEnd method is by far best, but since that's technically ES2019, + * we provide a (still much faster than the simple regex) fallback. + */ +function trimEndImpl(s: string) { + let end = s.length - 1; + while (end >= 0) { + if (!isWhiteSpaceLike(s.charCodeAt(end))) break; + end--; + } + return s.slice(0, end + 1); +} } diff --git a/src/compiler/corePublic.ts b/src/compiler/corePublic.ts index f15b438c02fc3..ba0724a90d443 100644 --- a/src/compiler/corePublic.ts +++ b/src/compiler/corePublic.ts @@ -1,162 +1,162 @@ namespace ts { - // WARNING: The script `configurePrerelease.ts` uses a regexp to parse out these values. - // If changing the text in this section, be sure to test `configurePrerelease` too. - export const versionMajorMinor = "4.6"; - // The following is baselined as a literal template type without intervention - /** The version of the TypeScript compiler release */ - // eslint-disable-next-line @typescript-eslint/no-inferrable-types - export const version: string = `${versionMajorMinor}.0-dev`; +// WARNING: The script `configurePrerelease.ts` uses a regexp to parse out these values. +// If changing the text in this section, be sure to test `configurePrerelease` too. +export const versionMajorMinor = "4.6"; +// The following is baselined as a literal template type without intervention +/** The version of the TypeScript compiler release */ +// eslint-disable-next-line @typescript-eslint/no-inferrable-types +export const version: string = `${versionMajorMinor}.0-dev`; + +/** + * Type of objects whose values are all of the same type. + * The `in` and `for-in` operators can *not* be safely used, + * since `Object.prototype` may be modified by outside code. + */ +export interface MapLike { + [index: string]: T; +} - /** - * Type of objects whose values are all of the same type. - * The `in` and `for-in` operators can *not* be safely used, - * since `Object.prototype` may be modified by outside code. - */ - export interface MapLike { - [index: string]: T; - } +export interface SortedReadonlyArray extends ReadonlyArray { + " __sortedArrayBrand": any; +} - export interface SortedReadonlyArray extends ReadonlyArray { - " __sortedArrayBrand": any; - } +export interface SortedArray extends Array { + " __sortedArrayBrand": any; +} - export interface SortedArray extends Array { - " __sortedArrayBrand": any; - } +/** Common read methods for ES6 Map/Set. */ +export interface ReadonlyCollection { + readonly size: number; + has(key: K): boolean; + keys(): Iterator; +} - /** Common read methods for ES6 Map/Set. */ - export interface ReadonlyCollection { - readonly size: number; - has(key: K): boolean; - keys(): Iterator; - } +/** Common write methods for ES6 Map/Set. */ +export interface Collection extends ReadonlyCollection { + delete(key: K): boolean; + clear(): void; +} - /** Common write methods for ES6 Map/Set. */ - export interface Collection extends ReadonlyCollection { - delete(key: K): boolean; - clear(): void; - } +/** ES6 Map interface, only read methods included. */ +export interface ReadonlyESMap extends ReadonlyCollection { + get(key: K): V | undefined; + values(): Iterator; + entries(): Iterator<[K, V]>; + forEach(action: (value: V, key: K) => void): void; +} - /** ES6 Map interface, only read methods included. */ - export interface ReadonlyESMap extends ReadonlyCollection { - get(key: K): V | undefined; - values(): Iterator; - entries(): Iterator<[K, V]>; - forEach(action: (value: V, key: K) => void): void; - } +/** + * ES6 Map interface, only read methods included. + */ +export interface ReadonlyMap extends ReadonlyESMap { +} - /** - * ES6 Map interface, only read methods included. - */ - export interface ReadonlyMap extends ReadonlyESMap { - } +/** ES6 Map interface. */ +export interface ESMap extends ReadonlyESMap, Collection { + set(key: K, value: V): this; +} - /** ES6 Map interface. */ - export interface ESMap extends ReadonlyESMap, Collection { - set(key: K, value: V): this; - } +/** + * ES6 Map interface. + */ +export interface Map extends ESMap { +} - /** - * ES6 Map interface. - */ - export interface Map extends ESMap { - } +/* @internal */ +export interface MapConstructor { + // eslint-disable-next-line @typescript-eslint/prefer-function-type + new (iterable?: readonly (readonly [K, V])[] | ReadonlyESMap): ESMap; +} - /* @internal */ - export interface MapConstructor { - // eslint-disable-next-line @typescript-eslint/prefer-function-type - new (iterable?: readonly (readonly [K, V])[] | ReadonlyESMap): ESMap; - } +/** ES6 Set interface, only read methods included. */ +export interface ReadonlySet extends ReadonlyCollection { + has(value: T): boolean; + values(): Iterator; + entries(): Iterator<[T, T]>; + forEach(action: (value: T, key: T) => void): void; +} - /** ES6 Set interface, only read methods included. */ - export interface ReadonlySet extends ReadonlyCollection { - has(value: T): boolean; - values(): Iterator; - entries(): Iterator<[T, T]>; - forEach(action: (value: T, key: T) => void): void; - } +/** ES6 Set interface. */ +export interface Set extends ReadonlySet, Collection { + add(value: T): this; + delete(value: T): boolean; +} - /** ES6 Set interface. */ - export interface Set extends ReadonlySet, Collection { - add(value: T): this; - delete(value: T): boolean; - } +/* @internal */ +export interface SetConstructor { + // eslint-disable-next-line @typescript-eslint/prefer-function-type + new (iterable?: readonly T[] | ReadonlySet): Set; +} - /* @internal */ - export interface SetConstructor { - // eslint-disable-next-line @typescript-eslint/prefer-function-type - new (iterable?: readonly T[] | ReadonlySet): Set; - } +/** ES6 Iterator type. */ +export interface Iterator { + next(): { value: T, done?: false } | { value: void, done: true }; +} - /** ES6 Iterator type. */ - export interface Iterator { - next(): { value: T, done?: false } | { value: void, done: true }; - } +/** Array that is only intended to be pushed to, never read. */ +export interface Push { + push(...values: T[]): void; + /* @internal*/ readonly length: number; +} - /** Array that is only intended to be pushed to, never read. */ - export interface Push { - push(...values: T[]): void; - /* @internal*/ readonly length: number; - } +/* @internal */ +export type EqualityComparer = (a: T, b: T) => boolean; - /* @internal */ - export type EqualityComparer = (a: T, b: T) => boolean; +/* @internal */ +export type Comparer = (a: T, b: T) => Comparison; - /* @internal */ - export type Comparer = (a: T, b: T) => Comparison; +/* @internal */ +export const enum Comparison { + LessThan = -1, + EqualTo = 0, + GreaterThan = 1 +} - /* @internal */ - export const enum Comparison { - LessThan = -1, - EqualTo = 0, - GreaterThan = 1 - } +/* @internal */ +namespace NativeCollections { + declare const Map: MapConstructor | undefined; + declare const Set: SetConstructor | undefined; - /* @internal */ - namespace NativeCollections { - declare const Map: MapConstructor | undefined; - declare const Set: SetConstructor | undefined; - - /** - * Returns the native Map implementation if it is available and compatible (i.e. supports iteration). - */ - export function tryGetNativeMap(): MapConstructor | undefined { - // Internet Explorer's Map doesn't support iteration, so don't use it. - // eslint-disable-next-line no-in-operator - return typeof Map !== "undefined" && "entries" in Map.prototype && new Map([[0, 0]]).size === 1 ? Map : undefined; - } - - /** - * Returns the native Set implementation if it is available and compatible (i.e. supports iteration). - */ - export function tryGetNativeSet(): SetConstructor | undefined { - // Internet Explorer's Set doesn't support iteration, so don't use it. - // eslint-disable-next-line no-in-operator - return typeof Set !== "undefined" && "entries" in Set.prototype && new Set([0]).size === 1 ? Set : undefined; - } + /** + * Returns the native Map implementation if it is available and compatible (i.e. supports iteration). + */ + export function tryGetNativeMap(): MapConstructor | undefined { + // Internet Explorer's Map doesn't support iteration, so don't use it. + // eslint-disable-next-line no-in-operator + return typeof Map !== "undefined" && "entries" in Map.prototype && new Map([[0, 0]]).size === 1 ? Map : undefined; } - /* @internal */ - export const Map = getCollectionImplementation("Map", "tryGetNativeMap", "createMapShim"); - /* @internal */ - export const Set = getCollectionImplementation("Set", "tryGetNativeSet", "createSetShim"); - - /* @internal */ - type GetIteratorCallback = | ReadonlyESMap | undefined>(iterable: I) => Iterator< - I extends ReadonlyESMap ? [K, V] : - I extends ReadonlySet ? T : - I extends readonly (infer T)[] ? T : - I extends undefined ? undefined : - never>; - - /* @internal */ - function getCollectionImplementation< - K1 extends MatchingKeys any>, - K2 extends MatchingKeys ReturnType<(typeof NativeCollections)[K1]>> - >(name: string, nativeFactory: K1, shimFactory: K2): NonNullable> { - // NOTE: ts.ShimCollections will be defined for typescriptServices.js but not for tsc.js, so we must test for it. - const constructor = NativeCollections[nativeFactory]() ?? ShimCollections?.[shimFactory](getIterator); - if (constructor) return constructor as NonNullable>; - throw new Error(`TypeScript requires an environment that provides a compatible native ${name} implementation.`); + /** + * Returns the native Set implementation if it is available and compatible (i.e. supports iteration). + */ + export function tryGetNativeSet(): SetConstructor | undefined { + // Internet Explorer's Set doesn't support iteration, so don't use it. + // eslint-disable-next-line no-in-operator + return typeof Set !== "undefined" && "entries" in Set.prototype && new Set([0]).size === 1 ? Set : undefined; } } + +/* @internal */ +export const Map = getCollectionImplementation("Map", "tryGetNativeMap", "createMapShim"); +/* @internal */ +export const Set = getCollectionImplementation("Set", "tryGetNativeSet", "createSetShim"); + +/* @internal */ +type GetIteratorCallback = | ReadonlyESMap | undefined>(iterable: I) => Iterator< + I extends ReadonlyESMap ? [K, V] : + I extends ReadonlySet ? T : + I extends readonly (infer T)[] ? T : + I extends undefined ? undefined : + never>; + +/* @internal */ +function getCollectionImplementation< + K1 extends MatchingKeys any>, + K2 extends MatchingKeys ReturnType<(typeof NativeCollections)[K1]>> +>(name: string, nativeFactory: K1, shimFactory: K2): NonNullable> { + // NOTE: ts.ShimCollections will be defined for typescriptServices.js but not for tsc.js, so we must test for it. + const constructor = NativeCollections[nativeFactory]() ?? ShimCollections?.[shimFactory](getIterator); + if (constructor) return constructor as NonNullable>; + throw new Error(`TypeScript requires an environment that provides a compatible native ${name} implementation.`); +} +} diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 5d22b0fe7981d..03da634ab3a85 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -1,737 +1,737 @@ /* @internal */ namespace ts { - export enum LogLevel { - Off, - Error, - Warning, - Info, - Verbose - } +export enum LogLevel { + Off, + Error, + Warning, + Info, + Verbose +} + +export interface LoggingHost { + log(level: LogLevel, s: string): void; +} + +export interface DeprecationOptions { + message?: string; + error?: boolean; + since?: Version | string; + warnAfter?: Version | string; + errorAfter?: Version | string; + typeScriptVersion?: Version | string; +} + +export namespace Debug { + let typeScriptVersion: Version | undefined; - export interface LoggingHost { - log(level: LogLevel, s: string): void; + /* eslint-disable prefer-const */ + let currentAssertionLevel = AssertionLevel.None; + export let currentLogLevel = LogLevel.Warning; + export let isDebugging = false; + export let loggingHost: LoggingHost | undefined; + /* eslint-enable prefer-const */ + + type AssertionKeys = MatchingKeys; + export function getTypeScriptVersion() { + return typeScriptVersion ?? (typeScriptVersion = new Version(version)); } - export interface DeprecationOptions { - message?: string; - error?: boolean; - since?: Version | string; - warnAfter?: Version | string; - errorAfter?: Version | string; - typeScriptVersion?: Version | string; + export function shouldLog(level: LogLevel): boolean { + return currentLogLevel <= level; } - export namespace Debug { - let typeScriptVersion: Version | undefined; + function logMessage(level: LogLevel, s: string): void { + if (loggingHost && shouldLog(level)) { + loggingHost.log(level, s); + } + } - /* eslint-disable prefer-const */ - let currentAssertionLevel = AssertionLevel.None; - export let currentLogLevel = LogLevel.Warning; - export let isDebugging = false; - export let loggingHost: LoggingHost | undefined; - /* eslint-enable prefer-const */ + export function log(s: string): void { + logMessage(LogLevel.Info, s); + } - type AssertionKeys = MatchingKeys; - export function getTypeScriptVersion() { - return typeScriptVersion ?? (typeScriptVersion = new Version(version)); + export namespace log { + export function error(s: string): void { + logMessage(LogLevel.Error, s); } - export function shouldLog(level: LogLevel): boolean { - return currentLogLevel <= level; - } - - function logMessage(level: LogLevel, s: string): void { - if (loggingHost && shouldLog(level)) { - loggingHost.log(level, s); - } + export function warn(s: string): void { + logMessage(LogLevel.Warning, s); } export function log(s: string): void { logMessage(LogLevel.Info, s); } - export namespace log { - export function error(s: string): void { - logMessage(LogLevel.Error, s); - } - - export function warn(s: string): void { - logMessage(LogLevel.Warning, s); - } - - export function log(s: string): void { - logMessage(LogLevel.Info, s); - } - - export function trace(s: string): void { - logMessage(LogLevel.Verbose, s); - } + export function trace(s: string): void { + logMessage(LogLevel.Verbose, s); } + } - const assertionCache: Partial> = {}; - - export function getAssertionLevel() { - return currentAssertionLevel; - } + const assertionCache: Partial> = {}; - export function setAssertionLevel(level: AssertionLevel) { - const prevAssertionLevel = currentAssertionLevel; - currentAssertionLevel = level; + export function getAssertionLevel() { + return currentAssertionLevel; + } - if (level > prevAssertionLevel) { - // restore assertion functions for the current assertion level (see `shouldAssertFunction`). - for (const key of getOwnKeys(assertionCache) as AssertionKeys[]) { - const cachedFunc = assertionCache[key]; - if (cachedFunc !== undefined && Debug[key] !== cachedFunc.assertion && level >= cachedFunc.level) { - (Debug as any)[key] = cachedFunc; - assertionCache[key] = undefined; - } + export function setAssertionLevel(level: AssertionLevel) { + const prevAssertionLevel = currentAssertionLevel; + currentAssertionLevel = level; + + if (level > prevAssertionLevel) { + // restore assertion functions for the current assertion level (see `shouldAssertFunction`). + for (const key of getOwnKeys(assertionCache) as AssertionKeys[]) { + const cachedFunc = assertionCache[key]; + if (cachedFunc !== undefined && Debug[key] !== cachedFunc.assertion && level >= cachedFunc.level) { + (Debug as any)[key] = cachedFunc; + assertionCache[key] = undefined; } } } + } - export function shouldAssert(level: AssertionLevel): boolean { - return currentAssertionLevel >= level; - } + export function shouldAssert(level: AssertionLevel): boolean { + return currentAssertionLevel >= level; + } - /** - * Tests whether an assertion function should be executed. If it shouldn't, it is cached and replaced with `ts.noop`. - * Replaced assertion functions are restored when `Debug.setAssertionLevel` is set to a high enough level. - * @param level The minimum assertion level required. - * @param name The name of the current assertion function. - */ - function shouldAssertFunction(level: AssertionLevel, name: K): boolean { - if (!shouldAssert(level)) { - assertionCache[name] = { level, assertion: Debug[name] }; - (Debug as any)[name] = noop; - return false; - } - return true; - } + /** + * Tests whether an assertion function should be executed. If it shouldn't, it is cached and replaced with `ts.noop`. + * Replaced assertion functions are restored when `Debug.setAssertionLevel` is set to a high enough level. + * @param level The minimum assertion level required. + * @param name The name of the current assertion function. + */ + function shouldAssertFunction(level: AssertionLevel, name: K): boolean { + if (!shouldAssert(level)) { + assertionCache[name] = { level, assertion: Debug[name] }; + (Debug as any)[name] = noop; + return false; + } + return true; + } - export function fail(message?: string, stackCrawlMark?: AnyFunction): never { - debugger; - const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); - if ((Error as any).captureStackTrace) { - (Error as any).captureStackTrace(e, stackCrawlMark || fail); - } - throw e; + export function fail(message?: string, stackCrawlMark?: AnyFunction): never { + debugger; + const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); + if ((Error as any).captureStackTrace) { + (Error as any).captureStackTrace(e, stackCrawlMark || fail); } + throw e; + } - export function failBadSyntaxKind(node: Node, message?: string, stackCrawlMark?: AnyFunction): never { - return fail( - `${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`, - stackCrawlMark || failBadSyntaxKind); - } + export function failBadSyntaxKind(node: Node, message?: string, stackCrawlMark?: AnyFunction): never { + return fail( + `${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`, + stackCrawlMark || failBadSyntaxKind); + } - export function assert(expression: unknown, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): asserts expression { - if (!expression) { - message = message ? `False expression: ${message}` : "False expression."; - if (verboseDebugInfo) { - message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo()); - } - fail(message, stackCrawlMark || assert); + export function assert(expression: unknown, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): asserts expression { + if (!expression) { + message = message ? `False expression: ${message}` : "False expression."; + if (verboseDebugInfo) { + message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo()); } + fail(message, stackCrawlMark || assert); } + } - export function assertEqual(a: T, b: T, msg?: string, msg2?: string, stackCrawlMark?: AnyFunction): void { - if (a !== b) { - const message = msg ? msg2 ? `${msg} ${msg2}` : msg : ""; - fail(`Expected ${a} === ${b}. ${message}`, stackCrawlMark || assertEqual); - } + export function assertEqual(a: T, b: T, msg?: string, msg2?: string, stackCrawlMark?: AnyFunction): void { + if (a !== b) { + const message = msg ? msg2 ? `${msg} ${msg2}` : msg : ""; + fail(`Expected ${a} === ${b}. ${message}`, stackCrawlMark || assertEqual); } + } - export function assertLessThan(a: number, b: number, msg?: string, stackCrawlMark?: AnyFunction): void { - if (a >= b) { - fail(`Expected ${a} < ${b}. ${msg || ""}`, stackCrawlMark || assertLessThan); - } + export function assertLessThan(a: number, b: number, msg?: string, stackCrawlMark?: AnyFunction): void { + if (a >= b) { + fail(`Expected ${a} < ${b}. ${msg || ""}`, stackCrawlMark || assertLessThan); } + } - export function assertLessThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void { - if (a > b) { - fail(`Expected ${a} <= ${b}`, stackCrawlMark || assertLessThanOrEqual); - } + export function assertLessThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void { + if (a > b) { + fail(`Expected ${a} <= ${b}`, stackCrawlMark || assertLessThanOrEqual); } + } - export function assertGreaterThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void { - if (a < b) { - fail(`Expected ${a} >= ${b}`, stackCrawlMark || assertGreaterThanOrEqual); - } + export function assertGreaterThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void { + if (a < b) { + fail(`Expected ${a} >= ${b}`, stackCrawlMark || assertGreaterThanOrEqual); } + } - export function assertIsDefined(value: T, message?: string, stackCrawlMark?: AnyFunction): asserts value is NonNullable { - // eslint-disable-next-line no-null/no-null - if (value === undefined || value === null) { - fail(message, stackCrawlMark || assertIsDefined); - } + export function assertIsDefined(value: T, message?: string, stackCrawlMark?: AnyFunction): asserts value is NonNullable { + // eslint-disable-next-line no-null/no-null + if (value === undefined || value === null) { + fail(message, stackCrawlMark || assertIsDefined); } + } - export function checkDefined(value: T | null | undefined, message?: string, stackCrawlMark?: AnyFunction): T { - assertIsDefined(value, message, stackCrawlMark || checkDefined); - return value; - } + export function checkDefined(value: T | null | undefined, message?: string, stackCrawlMark?: AnyFunction): T { + assertIsDefined(value, message, stackCrawlMark || checkDefined); + return value; + } - export function assertEachIsDefined(value: NodeArray, message?: string, stackCrawlMark?: AnyFunction): asserts value is NodeArray; - export function assertEachIsDefined(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction): asserts value is readonly NonNullable[]; - export function assertEachIsDefined(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction) { - for (const v of value) { - assertIsDefined(v, message, stackCrawlMark || assertEachIsDefined); - } + export function assertEachIsDefined(value: NodeArray, message?: string, stackCrawlMark?: AnyFunction): asserts value is NodeArray; + export function assertEachIsDefined(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction): asserts value is readonly NonNullable[]; + export function assertEachIsDefined(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction) { + for (const v of value) { + assertIsDefined(v, message, stackCrawlMark || assertEachIsDefined); } + } - export function checkEachDefined(value: A, message?: string, stackCrawlMark?: AnyFunction): A { - assertEachIsDefined(value, message, stackCrawlMark || checkEachDefined); - return value; - } + export function checkEachDefined(value: A, message?: string, stackCrawlMark?: AnyFunction): A { + assertEachIsDefined(value, message, stackCrawlMark || checkEachDefined); + return value; + } - export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never { - const detail = typeof member === "object" && hasProperty(member, "kind") && hasProperty(member, "pos") && formatSyntaxKind ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member); - return fail(`${message} ${detail}`, stackCrawlMark || assertNever); - } + export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never { + const detail = typeof member === "object" && hasProperty(member, "kind") && hasProperty(member, "pos") && formatSyntaxKind ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member); + return fail(`${message} ${detail}`, stackCrawlMark || assertNever); + } - export function assertEachNode(nodes: NodeArray, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is NodeArray; - export function assertEachNode(nodes: readonly T[], test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is readonly U[]; - export function assertEachNode(nodes: readonly Node[], test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction): void; - export function assertEachNode(nodes: readonly Node[], test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction) { - if (shouldAssertFunction(AssertionLevel.Normal, "assertEachNode")) { - assert( - test === undefined || every(nodes, test), - message || "Unexpected node.", - () => `Node array did not pass test '${getFunctionName(test)}'.`, - stackCrawlMark || assertEachNode); - } + export function assertEachNode(nodes: NodeArray, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is NodeArray; + export function assertEachNode(nodes: readonly T[], test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is readonly U[]; + export function assertEachNode(nodes: readonly Node[], test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction): void; + export function assertEachNode(nodes: readonly Node[], test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction) { + if (shouldAssertFunction(AssertionLevel.Normal, "assertEachNode")) { + assert( + test === undefined || every(nodes, test), + message || "Unexpected node.", + () => `Node array did not pass test '${getFunctionName(test)}'.`, + stackCrawlMark || assertEachNode); } + } - export function assertNode(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U; - export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; - export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { - if (shouldAssertFunction(AssertionLevel.Normal, "assertNode")) { - assert( - node !== undefined && (test === undefined || test(node)), - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`, - stackCrawlMark || assertNode); - } + export function assertNode(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U; + export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; + export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { + if (shouldAssertFunction(AssertionLevel.Normal, "assertNode")) { + assert( + node !== undefined && (test === undefined || test(node)), + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`, + stackCrawlMark || assertNode); } + } - export function assertNotNode(node: T | undefined, test: (node: Node) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is Exclude; - export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; - export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { - if (shouldAssertFunction(AssertionLevel.Normal, "assertNotNode")) { - assert( - node === undefined || test === undefined || !test(node), - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`, - stackCrawlMark || assertNotNode); - } + export function assertNotNode(node: T | undefined, test: (node: Node) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is Exclude; + export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; + export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { + if (shouldAssertFunction(AssertionLevel.Normal, "assertNotNode")) { + assert( + node === undefined || test === undefined || !test(node), + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`, + stackCrawlMark || assertNotNode); } + } - export function assertOptionalNode(node: T, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U; - export function assertOptionalNode(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U | undefined; - export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; - export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { - if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalNode")) { - assert( - test === undefined || node === undefined || test(node), - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`, - stackCrawlMark || assertOptionalNode); - } + export function assertOptionalNode(node: T, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U; + export function assertOptionalNode(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U | undefined; + export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; + export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { + if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalNode")) { + assert( + test === undefined || node === undefined || test(node), + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`, + stackCrawlMark || assertOptionalNode); } + } - export function assertOptionalToken(node: T, kind: K, message?: string, stackCrawlMark?: AnyFunction): asserts node is Extract; - export function assertOptionalToken(node: T | undefined, kind: K, message?: string, stackCrawlMark?: AnyFunction): asserts node is Extract | undefined; - export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction): void; - export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction) { - if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalToken")) { - assert( - kind === undefined || node === undefined || node.kind === kind, - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node?.kind)} was not a '${formatSyntaxKind(kind)}' token.`, - stackCrawlMark || assertOptionalToken); - } + export function assertOptionalToken(node: T, kind: K, message?: string, stackCrawlMark?: AnyFunction): asserts node is Extract; + export function assertOptionalToken(node: T | undefined, kind: K, message?: string, stackCrawlMark?: AnyFunction): asserts node is Extract | undefined; + export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction): void; + export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction) { + if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalToken")) { + assert( + kind === undefined || node === undefined || node.kind === kind, + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node?.kind)} was not a '${formatSyntaxKind(kind)}' token.`, + stackCrawlMark || assertOptionalToken); } + } - export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction): asserts node is undefined; - export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction) { - if (shouldAssertFunction(AssertionLevel.Normal, "assertMissingNode")) { - assert( - node === undefined, - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node!.kind)} was unexpected'.`, - stackCrawlMark || assertMissingNode); - } + export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction): asserts node is undefined; + export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction) { + if (shouldAssertFunction(AssertionLevel.Normal, "assertMissingNode")) { + assert( + node === undefined, + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node!.kind)} was unexpected'.`, + stackCrawlMark || assertMissingNode); } + } - /** - * Asserts a value has the specified type in typespace only (does not perform a runtime assertion). - * This is useful in cases where we switch on `node.kind` and can be reasonably sure the type is accurate, and - * as a result can reduce the number of unnecessary casts. - */ - export function type(value: unknown): asserts value is T; - export function type(_value: unknown) { } + /** + * Asserts a value has the specified type in typespace only (does not perform a runtime assertion). + * This is useful in cases where we switch on `node.kind` and can be reasonably sure the type is accurate, and + * as a result can reduce the number of unnecessary casts. + */ + export function type(value: unknown): asserts value is T; + export function type(_value: unknown) { } - export function getFunctionName(func: AnyFunction) { - if (typeof func !== "function") { - return ""; - } - else if (func.hasOwnProperty("name")) { - return (func as any).name; - } - else { - const text = Function.prototype.toString.call(func); - const match = /^function\s+([\w\$]+)\s*\(/.exec(text); - return match ? match[1] : ""; - } + export function getFunctionName(func: AnyFunction) { + if (typeof func !== "function") { + return ""; } - - export function formatSymbol(symbol: Symbol): string { - return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`; + else if (func.hasOwnProperty("name")) { + return (func as any).name; + } + else { + const text = Function.prototype.toString.call(func); + const match = /^function\s+([\w\$]+)\s*\(/.exec(text); + return match ? match[1] : ""; } + } - /** - * Formats an enum value as a string for debugging and debug assertions. - */ - export function formatEnum(value = 0, enumObject: any, isFlags?: boolean) { - const members = getEnumMembers(enumObject); - if (value === 0) { - return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0"; - } - if (isFlags) { - let result = ""; - let remainingFlags = value; - for (const [enumValue, enumName] of members) { - if (enumValue > value) { - break; - } - if (enumValue !== 0 && enumValue & value) { - result = `${result}${result ? "|" : ""}${enumName}`; - remainingFlags &= ~enumValue; - } + export function formatSymbol(symbol: Symbol): string { + return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`; + } + + /** + * Formats an enum value as a string for debugging and debug assertions. + */ + export function formatEnum(value = 0, enumObject: any, isFlags?: boolean) { + const members = getEnumMembers(enumObject); + if (value === 0) { + return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0"; + } + if (isFlags) { + let result = ""; + let remainingFlags = value; + for (const [enumValue, enumName] of members) { + if (enumValue > value) { + break; } - if (remainingFlags === 0) { - return result; + if (enumValue !== 0 && enumValue & value) { + result = `${result}${result ? "|" : ""}${enumName}`; + remainingFlags &= ~enumValue; } } - else { - for (const [enumValue, enumName] of members) { - if (enumValue === value) { - return enumName; - } - } + if (remainingFlags === 0) { + return result; } - return value.toString(); } - - function getEnumMembers(enumObject: any) { - const result: [number, string][] = []; - for (const name in enumObject) { - const value = enumObject[name]; - if (typeof value === "number") { - result.push([value, name]); + else { + for (const [enumValue, enumName] of members) { + if (enumValue === value) { + return enumName; } } - - return stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); } + return value.toString(); + } - export function formatSyntaxKind(kind: SyntaxKind | undefined): string { - return formatEnum(kind, (ts as any).SyntaxKind, /*isFlags*/ false); + function getEnumMembers(enumObject: any) { + const result: [number, string][] = []; + for (const name in enumObject) { + const value = enumObject[name]; + if (typeof value === "number") { + result.push([value, name]); + } } - export function formatSnippetKind(kind: SnippetKind | undefined): string { - return formatEnum(kind, (ts as any).SnippetKind, /*isFlags*/ false); - } + return stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); + } - export function formatNodeFlags(flags: NodeFlags | undefined): string { - return formatEnum(flags, (ts as any).NodeFlags, /*isFlags*/ true); - } + export function formatSyntaxKind(kind: SyntaxKind | undefined): string { + return formatEnum(kind, (ts as any).SyntaxKind, /*isFlags*/ false); + } - export function formatModifierFlags(flags: ModifierFlags | undefined): string { - return formatEnum(flags, (ts as any).ModifierFlags, /*isFlags*/ true); - } + export function formatSnippetKind(kind: SnippetKind | undefined): string { + return formatEnum(kind, (ts as any).SnippetKind, /*isFlags*/ false); + } - export function formatTransformFlags(flags: TransformFlags | undefined): string { - return formatEnum(flags, (ts as any).TransformFlags, /*isFlags*/ true); - } + export function formatNodeFlags(flags: NodeFlags | undefined): string { + return formatEnum(flags, (ts as any).NodeFlags, /*isFlags*/ true); + } - export function formatEmitFlags(flags: EmitFlags | undefined): string { - return formatEnum(flags, (ts as any).EmitFlags, /*isFlags*/ true); - } + export function formatModifierFlags(flags: ModifierFlags | undefined): string { + return formatEnum(flags, (ts as any).ModifierFlags, /*isFlags*/ true); + } - export function formatSymbolFlags(flags: SymbolFlags | undefined): string { - return formatEnum(flags, (ts as any).SymbolFlags, /*isFlags*/ true); - } + export function formatTransformFlags(flags: TransformFlags | undefined): string { + return formatEnum(flags, (ts as any).TransformFlags, /*isFlags*/ true); + } - export function formatTypeFlags(flags: TypeFlags | undefined): string { - return formatEnum(flags, (ts as any).TypeFlags, /*isFlags*/ true); - } + export function formatEmitFlags(flags: EmitFlags | undefined): string { + return formatEnum(flags, (ts as any).EmitFlags, /*isFlags*/ true); + } - export function formatSignatureFlags(flags: SignatureFlags | undefined): string { - return formatEnum(flags, (ts as any).SignatureFlags, /*isFlags*/ true); - } + export function formatSymbolFlags(flags: SymbolFlags | undefined): string { + return formatEnum(flags, (ts as any).SymbolFlags, /*isFlags*/ true); + } - export function formatObjectFlags(flags: ObjectFlags | undefined): string { - return formatEnum(flags, (ts as any).ObjectFlags, /*isFlags*/ true); - } + export function formatTypeFlags(flags: TypeFlags | undefined): string { + return formatEnum(flags, (ts as any).TypeFlags, /*isFlags*/ true); + } - export function formatFlowFlags(flags: FlowFlags | undefined): string { - return formatEnum(flags, (ts as any).FlowFlags, /*isFlags*/ true); - } + export function formatSignatureFlags(flags: SignatureFlags | undefined): string { + return formatEnum(flags, (ts as any).SignatureFlags, /*isFlags*/ true); + } - let isDebugInfoEnabled = false; + export function formatObjectFlags(flags: ObjectFlags | undefined): string { + return formatEnum(flags, (ts as any).ObjectFlags, /*isFlags*/ true); + } - interface ExtendedDebugModule { - init(_ts: typeof ts): void; - formatControlFlowGraph(flowNode: FlowNode): string; - } + export function formatFlowFlags(flags: FlowFlags | undefined): string { + return formatEnum(flags, (ts as any).FlowFlags, /*isFlags*/ true); + } - let extendedDebugModule: ExtendedDebugModule | undefined; + let isDebugInfoEnabled = false; - function extendedDebug() { - enableDebugInfo(); - if (!extendedDebugModule) { - throw new Error("Debugging helpers could not be loaded."); - } - return extendedDebugModule; - } + interface ExtendedDebugModule { + init(_ts: typeof ts): void; + formatControlFlowGraph(flowNode: FlowNode): string; + } - export function printControlFlowGraph(flowNode: FlowNode) { - return console.log(formatControlFlowGraph(flowNode)); - } + let extendedDebugModule: ExtendedDebugModule | undefined; - export function formatControlFlowGraph(flowNode: FlowNode) { - return extendedDebug().formatControlFlowGraph(flowNode); + function extendedDebug() { + enableDebugInfo(); + if (!extendedDebugModule) { + throw new Error("Debugging helpers could not be loaded."); } + return extendedDebugModule; + } - let flowNodeProto: FlowNodeBase | undefined; + export function printControlFlowGraph(flowNode: FlowNode) { + return console.log(formatControlFlowGraph(flowNode)); + } - function attachFlowNodeDebugInfoWorker(flowNode: FlowNodeBase) { - if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line no-in-operator - Object.defineProperties(flowNode, { - // for use with vscode-js-debug's new customDescriptionGenerator in launch.json - __tsDebuggerDisplay: { - value(this: FlowNodeBase) { - const flowHeader = - this.flags & FlowFlags.Start ? "FlowStart" : - this.flags & FlowFlags.BranchLabel ? "FlowBranchLabel" : - this.flags & FlowFlags.LoopLabel ? "FlowLoopLabel" : - this.flags & FlowFlags.Assignment ? "FlowAssignment" : - this.flags & FlowFlags.TrueCondition ? "FlowTrueCondition" : - this.flags & FlowFlags.FalseCondition ? "FlowFalseCondition" : - this.flags & FlowFlags.SwitchClause ? "FlowSwitchClause" : - this.flags & FlowFlags.ArrayMutation ? "FlowArrayMutation" : - this.flags & FlowFlags.Call ? "FlowCall" : - this.flags & FlowFlags.ReduceLabel ? "FlowReduceLabel" : - this.flags & FlowFlags.Unreachable ? "FlowUnreachable" : - "UnknownFlow"; - const remainingFlags = this.flags & ~(FlowFlags.Referenced - 1); - return `${flowHeader}${remainingFlags ? ` (${formatFlowFlags(remainingFlags)})`: ""}`; - } - }, - __debugFlowFlags: { get(this: FlowNodeBase) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } }, - __debugToString: { value(this: FlowNodeBase) { return formatControlFlowGraph(this); } } - }); - } - } + export function formatControlFlowGraph(flowNode: FlowNode) { + return extendedDebug().formatControlFlowGraph(flowNode); + } + + let flowNodeProto: FlowNodeBase | undefined; - export function attachFlowNodeDebugInfo(flowNode: FlowNodeBase) { - if (isDebugInfoEnabled) { - if (typeof Object.setPrototypeOf === "function") { - // if we're in es2015, attach the method to a shared prototype for `FlowNode` - // so the method doesn't show up in the watch window. - if (!flowNodeProto) { - flowNodeProto = Object.create(Object.prototype) as FlowNodeBase; - attachFlowNodeDebugInfoWorker(flowNodeProto); + function attachFlowNodeDebugInfoWorker(flowNode: FlowNodeBase) { + if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line no-in-operator + Object.defineProperties(flowNode, { + // for use with vscode-js-debug's new customDescriptionGenerator in launch.json + __tsDebuggerDisplay: { + value(this: FlowNodeBase) { + const flowHeader = + this.flags & FlowFlags.Start ? "FlowStart" : + this.flags & FlowFlags.BranchLabel ? "FlowBranchLabel" : + this.flags & FlowFlags.LoopLabel ? "FlowLoopLabel" : + this.flags & FlowFlags.Assignment ? "FlowAssignment" : + this.flags & FlowFlags.TrueCondition ? "FlowTrueCondition" : + this.flags & FlowFlags.FalseCondition ? "FlowFalseCondition" : + this.flags & FlowFlags.SwitchClause ? "FlowSwitchClause" : + this.flags & FlowFlags.ArrayMutation ? "FlowArrayMutation" : + this.flags & FlowFlags.Call ? "FlowCall" : + this.flags & FlowFlags.ReduceLabel ? "FlowReduceLabel" : + this.flags & FlowFlags.Unreachable ? "FlowUnreachable" : + "UnknownFlow"; + const remainingFlags = this.flags & ~(FlowFlags.Referenced - 1); + return `${flowHeader}${remainingFlags ? ` (${formatFlowFlags(remainingFlags)})`: ""}`; } - Object.setPrototypeOf(flowNode, flowNodeProto); - } - else { - // not running in an es2015 environment, attach the method directly. - attachFlowNodeDebugInfoWorker(flowNode); + }, + __debugFlowFlags: { get(this: FlowNodeBase) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } }, + __debugToString: { value(this: FlowNodeBase) { return formatControlFlowGraph(this); } } + }); + } + } + + export function attachFlowNodeDebugInfo(flowNode: FlowNodeBase) { + if (isDebugInfoEnabled) { + if (typeof Object.setPrototypeOf === "function") { + // if we're in es2015, attach the method to a shared prototype for `FlowNode` + // so the method doesn't show up in the watch window. + if (!flowNodeProto) { + flowNodeProto = Object.create(Object.prototype) as FlowNodeBase; + attachFlowNodeDebugInfoWorker(flowNodeProto); } + Object.setPrototypeOf(flowNode, flowNodeProto); + } + else { + // not running in an es2015 environment, attach the method directly. + attachFlowNodeDebugInfoWorker(flowNode); } } + } - let nodeArrayProto: NodeArray | undefined; + let nodeArrayProto: NodeArray | undefined; - function attachNodeArrayDebugInfoWorker(array: NodeArray) { - if (!("__tsDebuggerDisplay" in array)) { // eslint-disable-line no-in-operator - Object.defineProperties(array, { - __tsDebuggerDisplay: { - value(this: NodeArray, defaultValue: string) { - // An `Array` with extra properties is rendered as `[A, B, prop1: 1, prop2: 2]`. Most of - // these aren't immediately useful so we trim off the `prop1: ..., prop2: ...` part from the - // formatted string. - // This regex can trigger slow backtracking because of overlapping potential captures. - // We don't care, this is debug code that's only enabled with a debugger attached - - // we're just taking note of it for anyone checking regex performance in the future. - defaultValue = String(defaultValue).replace(/(?:,[\s\w\d_]+:[^,]+)+\]$/, "]"); - return `NodeArray ${defaultValue}`; - } + function attachNodeArrayDebugInfoWorker(array: NodeArray) { + if (!("__tsDebuggerDisplay" in array)) { // eslint-disable-line no-in-operator + Object.defineProperties(array, { + __tsDebuggerDisplay: { + value(this: NodeArray, defaultValue: string) { + // An `Array` with extra properties is rendered as `[A, B, prop1: 1, prop2: 2]`. Most of + // these aren't immediately useful so we trim off the `prop1: ..., prop2: ...` part from the + // formatted string. + // This regex can trigger slow backtracking because of overlapping potential captures. + // We don't care, this is debug code that's only enabled with a debugger attached - + // we're just taking note of it for anyone checking regex performance in the future. + defaultValue = String(defaultValue).replace(/(?:,[\s\w\d_]+:[^,]+)+\]$/, "]"); + return `NodeArray ${defaultValue}`; } - }); - } + } + }); } + } - export function attachNodeArrayDebugInfo(array: NodeArray) { - if (isDebugInfoEnabled) { - if (typeof Object.setPrototypeOf === "function") { - // if we're in es2015, attach the method to a shared prototype for `NodeArray` - // so the method doesn't show up in the watch window. - if (!nodeArrayProto) { - nodeArrayProto = Object.create(Array.prototype) as NodeArray; - attachNodeArrayDebugInfoWorker(nodeArrayProto); - } - Object.setPrototypeOf(array, nodeArrayProto); - } - else { - // not running in an es2015 environment, attach the method directly. - attachNodeArrayDebugInfoWorker(array); + export function attachNodeArrayDebugInfo(array: NodeArray) { + if (isDebugInfoEnabled) { + if (typeof Object.setPrototypeOf === "function") { + // if we're in es2015, attach the method to a shared prototype for `NodeArray` + // so the method doesn't show up in the watch window. + if (!nodeArrayProto) { + nodeArrayProto = Object.create(Array.prototype) as NodeArray; + attachNodeArrayDebugInfoWorker(nodeArrayProto); } + Object.setPrototypeOf(array, nodeArrayProto); + } + else { + // not running in an es2015 environment, attach the method directly. + attachNodeArrayDebugInfoWorker(array); } } + } - /** - * Injects debug information into frequently used types. - */ - export function enableDebugInfo() { - if (isDebugInfoEnabled) return; + /** + * Injects debug information into frequently used types. + */ + export function enableDebugInfo() { + if (isDebugInfoEnabled) return; - // avoid recomputing - let weakTypeTextMap: WeakMap | undefined; - let weakNodeTextMap: WeakMap | undefined; + // avoid recomputing + let weakTypeTextMap: WeakMap | undefined; + let weakNodeTextMap: WeakMap | undefined; - function getWeakTypeTextMap() { - if (weakTypeTextMap === undefined) { - if (typeof WeakMap === "function") weakTypeTextMap = new WeakMap(); - } - return weakTypeTextMap; + function getWeakTypeTextMap() { + if (weakTypeTextMap === undefined) { + if (typeof WeakMap === "function") weakTypeTextMap = new WeakMap(); } + return weakTypeTextMap; + } - function getWeakNodeTextMap() { - if (weakNodeTextMap === undefined) { - if (typeof WeakMap === "function") weakNodeTextMap = new WeakMap(); - } - return weakNodeTextMap; + function getWeakNodeTextMap() { + if (weakNodeTextMap === undefined) { + if (typeof WeakMap === "function") weakNodeTextMap = new WeakMap(); } + return weakNodeTextMap; + } - // Add additional properties in debug mode to assist with debugging. - Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, { - // for use with vscode-js-debug's new customDescriptionGenerator in launch.json - __tsDebuggerDisplay: { - value(this: Symbol) { - const symbolHeader = - this.flags & SymbolFlags.Transient ? "TransientSymbol" : - "Symbol"; - const remainingSymbolFlags = this.flags & ~SymbolFlags.Transient; - return `${symbolHeader} '${symbolName(this)}'${remainingSymbolFlags ? ` (${formatSymbolFlags(remainingSymbolFlags)})` : ""}`; - } - }, - __debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } } - }); - - Object.defineProperties(objectAllocator.getTypeConstructor().prototype, { - // for use with vscode-js-debug's new customDescriptionGenerator in launch.json - __tsDebuggerDisplay: { - value(this: Type) { - const typeHeader = - this.flags & TypeFlags.Nullable ? "NullableType" : - this.flags & TypeFlags.StringOrNumberLiteral ? `LiteralType ${JSON.stringify((this as LiteralType).value)}` : - this.flags & TypeFlags.BigIntLiteral ? `LiteralType ${(this as BigIntLiteralType).value.negative ? "-" : ""}${(this as BigIntLiteralType).value.base10Value}n` : - this.flags & TypeFlags.UniqueESSymbol ? "UniqueESSymbolType" : - this.flags & TypeFlags.Enum ? "EnumType" : - this.flags & TypeFlags.Intrinsic ? `IntrinsicType ${(this as IntrinsicType).intrinsicName}` : - this.flags & TypeFlags.Union ? "UnionType" : - this.flags & TypeFlags.Intersection ? "IntersectionType" : - this.flags & TypeFlags.Index ? "IndexType" : - this.flags & TypeFlags.IndexedAccess ? "IndexedAccessType" : - this.flags & TypeFlags.Conditional ? "ConditionalType" : - this.flags & TypeFlags.Substitution ? "SubstitutionType" : - this.flags & TypeFlags.TypeParameter ? "TypeParameter" : - this.flags & TypeFlags.Object ? - (this as ObjectType).objectFlags & ObjectFlags.ClassOrInterface ? "InterfaceType" : - (this as ObjectType).objectFlags & ObjectFlags.Reference ? "TypeReference" : - (this as ObjectType).objectFlags & ObjectFlags.Tuple ? "TupleType" : - (this as ObjectType).objectFlags & ObjectFlags.Anonymous ? "AnonymousType" : - (this as ObjectType).objectFlags & ObjectFlags.Mapped ? "MappedType" : - (this as ObjectType).objectFlags & ObjectFlags.ReverseMapped ? "ReverseMappedType" : - (this as ObjectType).objectFlags & ObjectFlags.EvolvingArray ? "EvolvingArrayType" : - "ObjectType" : - "Type"; - const remainingObjectFlags = this.flags & TypeFlags.Object ? (this as ObjectType).objectFlags & ~ObjectFlags.ObjectTypeKindMask : 0; - return `${typeHeader}${this.symbol ? ` '${symbolName(this.symbol)}'` : ""}${remainingObjectFlags ? ` (${formatObjectFlags(remainingObjectFlags)})` : ""}`; + // Add additional properties in debug mode to assist with debugging. + Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, { + // for use with vscode-js-debug's new customDescriptionGenerator in launch.json + __tsDebuggerDisplay: { + value(this: Symbol) { + const symbolHeader = + this.flags & SymbolFlags.Transient ? "TransientSymbol" : + "Symbol"; + const remainingSymbolFlags = this.flags & ~SymbolFlags.Transient; + return `${symbolHeader} '${symbolName(this)}'${remainingSymbolFlags ? ` (${formatSymbolFlags(remainingSymbolFlags)})` : ""}`; + } + }, + __debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } } + }); + + Object.defineProperties(objectAllocator.getTypeConstructor().prototype, { + // for use with vscode-js-debug's new customDescriptionGenerator in launch.json + __tsDebuggerDisplay: { + value(this: Type) { + const typeHeader = + this.flags & TypeFlags.Nullable ? "NullableType" : + this.flags & TypeFlags.StringOrNumberLiteral ? `LiteralType ${JSON.stringify((this as LiteralType).value)}` : + this.flags & TypeFlags.BigIntLiteral ? `LiteralType ${(this as BigIntLiteralType).value.negative ? "-" : ""}${(this as BigIntLiteralType).value.base10Value}n` : + this.flags & TypeFlags.UniqueESSymbol ? "UniqueESSymbolType" : + this.flags & TypeFlags.Enum ? "EnumType" : + this.flags & TypeFlags.Intrinsic ? `IntrinsicType ${(this as IntrinsicType).intrinsicName}` : + this.flags & TypeFlags.Union ? "UnionType" : + this.flags & TypeFlags.Intersection ? "IntersectionType" : + this.flags & TypeFlags.Index ? "IndexType" : + this.flags & TypeFlags.IndexedAccess ? "IndexedAccessType" : + this.flags & TypeFlags.Conditional ? "ConditionalType" : + this.flags & TypeFlags.Substitution ? "SubstitutionType" : + this.flags & TypeFlags.TypeParameter ? "TypeParameter" : + this.flags & TypeFlags.Object ? + (this as ObjectType).objectFlags & ObjectFlags.ClassOrInterface ? "InterfaceType" : + (this as ObjectType).objectFlags & ObjectFlags.Reference ? "TypeReference" : + (this as ObjectType).objectFlags & ObjectFlags.Tuple ? "TupleType" : + (this as ObjectType).objectFlags & ObjectFlags.Anonymous ? "AnonymousType" : + (this as ObjectType).objectFlags & ObjectFlags.Mapped ? "MappedType" : + (this as ObjectType).objectFlags & ObjectFlags.ReverseMapped ? "ReverseMappedType" : + (this as ObjectType).objectFlags & ObjectFlags.EvolvingArray ? "EvolvingArrayType" : + "ObjectType" : + "Type"; + const remainingObjectFlags = this.flags & TypeFlags.Object ? (this as ObjectType).objectFlags & ~ObjectFlags.ObjectTypeKindMask : 0; + return `${typeHeader}${this.symbol ? ` '${symbolName(this.symbol)}'` : ""}${remainingObjectFlags ? ` (${formatObjectFlags(remainingObjectFlags)})` : ""}`; + } + }, + __debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } }, + __debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((this as ObjectType).objectFlags) : ""; } }, + __debugTypeToString: { + value(this: Type) { + // avoid recomputing + const map = getWeakTypeTextMap(); + let text = map?.get(this); + if (text === undefined) { + text = this.checker.typeToString(this); + map?.set(this, text); } - }, - __debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } }, - __debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((this as ObjectType).objectFlags) : ""; } }, - __debugTypeToString: { - value(this: Type) { - // avoid recomputing - const map = getWeakTypeTextMap(); - let text = map?.get(this); - if (text === undefined) { - text = this.checker.typeToString(this); - map?.set(this, text); + return text; + } + }, + }); + + Object.defineProperties(objectAllocator.getSignatureConstructor().prototype, { + __debugFlags: { get(this: Signature) { return formatSignatureFlags(this.flags); } }, + __debugSignatureToString: { value(this: Signature) { return this.checker?.signatureToString(this); } } + }); + + const nodeConstructors = [ + objectAllocator.getNodeConstructor(), + objectAllocator.getIdentifierConstructor(), + objectAllocator.getTokenConstructor(), + objectAllocator.getSourceFileConstructor() + ]; + + for (const ctor of nodeConstructors) { + if (!ctor.prototype.hasOwnProperty("__debugKind")) { + Object.defineProperties(ctor.prototype, { + // for use with vscode-js-debug's new customDescriptionGenerator in launch.json + __tsDebuggerDisplay: { + value(this: Node) { + const nodeHeader = + isGeneratedIdentifier(this) ? "GeneratedIdentifier" : + isIdentifier(this) ? `Identifier '${idText(this)}'` : + isPrivateIdentifier(this) ? `PrivateIdentifier '${idText(this)}'` : + isStringLiteral(this) ? `StringLiteral ${JSON.stringify(this.text.length < 10 ? this.text : this.text.slice(10) + "...")}` : + isNumericLiteral(this) ? `NumericLiteral ${this.text}` : + isBigIntLiteral(this) ? `BigIntLiteral ${this.text}n` : + isTypeParameterDeclaration(this) ? "TypeParameterDeclaration" : + isParameter(this) ? "ParameterDeclaration" : + isConstructorDeclaration(this) ? "ConstructorDeclaration" : + isGetAccessorDeclaration(this) ? "GetAccessorDeclaration" : + isSetAccessorDeclaration(this) ? "SetAccessorDeclaration" : + isCallSignatureDeclaration(this) ? "CallSignatureDeclaration" : + isConstructSignatureDeclaration(this) ? "ConstructSignatureDeclaration" : + isIndexSignatureDeclaration(this) ? "IndexSignatureDeclaration" : + isTypePredicateNode(this) ? "TypePredicateNode" : + isTypeReferenceNode(this) ? "TypeReferenceNode" : + isFunctionTypeNode(this) ? "FunctionTypeNode" : + isConstructorTypeNode(this) ? "ConstructorTypeNode" : + isTypeQueryNode(this) ? "TypeQueryNode" : + isTypeLiteralNode(this) ? "TypeLiteralNode" : + isArrayTypeNode(this) ? "ArrayTypeNode" : + isTupleTypeNode(this) ? "TupleTypeNode" : + isOptionalTypeNode(this) ? "OptionalTypeNode" : + isRestTypeNode(this) ? "RestTypeNode" : + isUnionTypeNode(this) ? "UnionTypeNode" : + isIntersectionTypeNode(this) ? "IntersectionTypeNode" : + isConditionalTypeNode(this) ? "ConditionalTypeNode" : + isInferTypeNode(this) ? "InferTypeNode" : + isParenthesizedTypeNode(this) ? "ParenthesizedTypeNode" : + isThisTypeNode(this) ? "ThisTypeNode" : + isTypeOperatorNode(this) ? "TypeOperatorNode" : + isIndexedAccessTypeNode(this) ? "IndexedAccessTypeNode" : + isMappedTypeNode(this) ? "MappedTypeNode" : + isLiteralTypeNode(this) ? "LiteralTypeNode" : + isNamedTupleMember(this) ? "NamedTupleMember" : + isImportTypeNode(this) ? "ImportTypeNode" : + formatSyntaxKind(this.kind); + return `${nodeHeader}${this.flags ? ` (${formatNodeFlags(this.flags)})` : ""}`; } - return text; - } - }, - }); - - Object.defineProperties(objectAllocator.getSignatureConstructor().prototype, { - __debugFlags: { get(this: Signature) { return formatSignatureFlags(this.flags); } }, - __debugSignatureToString: { value(this: Signature) { return this.checker?.signatureToString(this); } } - }); - - const nodeConstructors = [ - objectAllocator.getNodeConstructor(), - objectAllocator.getIdentifierConstructor(), - objectAllocator.getTokenConstructor(), - objectAllocator.getSourceFileConstructor() - ]; - - for (const ctor of nodeConstructors) { - if (!ctor.prototype.hasOwnProperty("__debugKind")) { - Object.defineProperties(ctor.prototype, { - // for use with vscode-js-debug's new customDescriptionGenerator in launch.json - __tsDebuggerDisplay: { - value(this: Node) { - const nodeHeader = - isGeneratedIdentifier(this) ? "GeneratedIdentifier" : - isIdentifier(this) ? `Identifier '${idText(this)}'` : - isPrivateIdentifier(this) ? `PrivateIdentifier '${idText(this)}'` : - isStringLiteral(this) ? `StringLiteral ${JSON.stringify(this.text.length < 10 ? this.text : this.text.slice(10) + "...")}` : - isNumericLiteral(this) ? `NumericLiteral ${this.text}` : - isBigIntLiteral(this) ? `BigIntLiteral ${this.text}n` : - isTypeParameterDeclaration(this) ? "TypeParameterDeclaration" : - isParameter(this) ? "ParameterDeclaration" : - isConstructorDeclaration(this) ? "ConstructorDeclaration" : - isGetAccessorDeclaration(this) ? "GetAccessorDeclaration" : - isSetAccessorDeclaration(this) ? "SetAccessorDeclaration" : - isCallSignatureDeclaration(this) ? "CallSignatureDeclaration" : - isConstructSignatureDeclaration(this) ? "ConstructSignatureDeclaration" : - isIndexSignatureDeclaration(this) ? "IndexSignatureDeclaration" : - isTypePredicateNode(this) ? "TypePredicateNode" : - isTypeReferenceNode(this) ? "TypeReferenceNode" : - isFunctionTypeNode(this) ? "FunctionTypeNode" : - isConstructorTypeNode(this) ? "ConstructorTypeNode" : - isTypeQueryNode(this) ? "TypeQueryNode" : - isTypeLiteralNode(this) ? "TypeLiteralNode" : - isArrayTypeNode(this) ? "ArrayTypeNode" : - isTupleTypeNode(this) ? "TupleTypeNode" : - isOptionalTypeNode(this) ? "OptionalTypeNode" : - isRestTypeNode(this) ? "RestTypeNode" : - isUnionTypeNode(this) ? "UnionTypeNode" : - isIntersectionTypeNode(this) ? "IntersectionTypeNode" : - isConditionalTypeNode(this) ? "ConditionalTypeNode" : - isInferTypeNode(this) ? "InferTypeNode" : - isParenthesizedTypeNode(this) ? "ParenthesizedTypeNode" : - isThisTypeNode(this) ? "ThisTypeNode" : - isTypeOperatorNode(this) ? "TypeOperatorNode" : - isIndexedAccessTypeNode(this) ? "IndexedAccessTypeNode" : - isMappedTypeNode(this) ? "MappedTypeNode" : - isLiteralTypeNode(this) ? "LiteralTypeNode" : - isNamedTupleMember(this) ? "NamedTupleMember" : - isImportTypeNode(this) ? "ImportTypeNode" : - formatSyntaxKind(this.kind); - return `${nodeHeader}${this.flags ? ` (${formatNodeFlags(this.flags)})` : ""}`; - } - }, - __debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } }, - __debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } }, - __debugModifierFlags: { get(this: Node) { return formatModifierFlags(getEffectiveModifierFlagsNoCache(this)); } }, - __debugTransformFlags: { get(this: Node) { return formatTransformFlags(this.transformFlags); } }, - __debugIsParseTreeNode: { get(this: Node) { return isParseTreeNode(this); } }, - __debugEmitFlags: { get(this: Node) { return formatEmitFlags(getEmitFlags(this)); } }, - __debugGetText: { - value(this: Node, includeTrivia?: boolean) { - if (nodeIsSynthesized(this)) return ""; - // avoid recomputing - const map = getWeakNodeTextMap(); - let text = map?.get(this); - if (text === undefined) { - const parseNode = getParseTreeNode(this); - const sourceFile = parseNode && getSourceFileOfNode(parseNode); - text = sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode!, includeTrivia) : ""; - map?.set(this, text); - } - return text; + }, + __debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } }, + __debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } }, + __debugModifierFlags: { get(this: Node) { return formatModifierFlags(getEffectiveModifierFlagsNoCache(this)); } }, + __debugTransformFlags: { get(this: Node) { return formatTransformFlags(this.transformFlags); } }, + __debugIsParseTreeNode: { get(this: Node) { return isParseTreeNode(this); } }, + __debugEmitFlags: { get(this: Node) { return formatEmitFlags(getEmitFlags(this)); } }, + __debugGetText: { + value(this: Node, includeTrivia?: boolean) { + if (nodeIsSynthesized(this)) return ""; + // avoid recomputing + const map = getWeakNodeTextMap(); + let text = map?.get(this); + if (text === undefined) { + const parseNode = getParseTreeNode(this); + const sourceFile = parseNode && getSourceFileOfNode(parseNode); + text = sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode!, includeTrivia) : ""; + map?.set(this, text); } + return text; } - }); - } + } + }); } + } - // attempt to load extended debugging information - try { - if (sys && sys.require) { - const basePath = getDirectoryPath(resolvePath(sys.getExecutingFilePath())); - const result = sys.require(basePath, "./compiler-debug") as RequireResult; - if (!result.error) { - result.module.init(ts); - extendedDebugModule = result.module; - } + // attempt to load extended debugging information + try { + if (sys && sys.require) { + const basePath = getDirectoryPath(resolvePath(sys.getExecutingFilePath())); + const result = sys.require(basePath, "./compiler-debug") as RequireResult; + if (!result.error) { + result.module.init(ts); + extendedDebugModule = result.module; } } - catch { - // do nothing - } - - isDebugInfoEnabled = true; } - - function formatDeprecationMessage(name: string, error: boolean | undefined, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { - let deprecationMessage = error ? "DeprecationError: " : "DeprecationWarning: "; - deprecationMessage += `'${name}' `; - deprecationMessage += since ? `has been deprecated since v${since}` : "is deprecated"; - deprecationMessage += error ? " and can no longer be used." : errorAfter ? ` and will no longer be usable after v${errorAfter}.` : "."; - deprecationMessage += message ? ` ${formatStringFromArgs(message, [name], 0)}` : ""; - return deprecationMessage; + catch { + // do nothing } - function createErrorDeprecation(name: string, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { - const deprecationMessage = formatDeprecationMessage(name, /*error*/ true, errorAfter, since, message); - return () => { - throw new TypeError(deprecationMessage); - }; - } + isDebugInfoEnabled = true; + } - function createWarningDeprecation(name: string, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { - let hasWrittenDeprecation = false; - return () => { - if (!hasWrittenDeprecation) { - log.warn(formatDeprecationMessage(name, /*error*/ false, errorAfter, since, message)); - hasWrittenDeprecation = true; - } - }; - } + function formatDeprecationMessage(name: string, error: boolean | undefined, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { + let deprecationMessage = error ? "DeprecationError: " : "DeprecationWarning: "; + deprecationMessage += `'${name}' `; + deprecationMessage += since ? `has been deprecated since v${since}` : "is deprecated"; + deprecationMessage += error ? " and can no longer be used." : errorAfter ? ` and will no longer be usable after v${errorAfter}.` : "."; + deprecationMessage += message ? ` ${formatStringFromArgs(message, [name], 0)}` : ""; + return deprecationMessage; + } - function createDeprecation(name: string, options: DeprecationOptions & { error: true }): () => never; - function createDeprecation(name: string, options?: DeprecationOptions): () => void; - function createDeprecation(name: string, options: DeprecationOptions = {}) { - const version = typeof options.typeScriptVersion === "string" ? new Version(options.typeScriptVersion) : options.typeScriptVersion ?? getTypeScriptVersion(); - const errorAfter = typeof options.errorAfter === "string" ? new Version(options.errorAfter) : options.errorAfter; - const warnAfter = typeof options.warnAfter === "string" ? new Version(options.warnAfter) : options.warnAfter; - const since = typeof options.since === "string" ? new Version(options.since) : options.since ?? warnAfter; - const error = options.error || errorAfter && version.compareTo(errorAfter) <= 0; - const warn = !warnAfter || version.compareTo(warnAfter) >= 0; - return error ? createErrorDeprecation(name, errorAfter, since, options.message) : - warn ? createWarningDeprecation(name, errorAfter, since, options.message) : - noop; - } + function createErrorDeprecation(name: string, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { + const deprecationMessage = formatDeprecationMessage(name, /*error*/ true, errorAfter, since, message); + return () => { + throw new TypeError(deprecationMessage); + }; + } - function wrapFunction any>(deprecation: () => void, func: F): F { - return function (this: unknown) { - deprecation(); - return func.apply(this, arguments); - } as F; - } + function createWarningDeprecation(name: string, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { + let hasWrittenDeprecation = false; + return () => { + if (!hasWrittenDeprecation) { + log.warn(formatDeprecationMessage(name, /*error*/ false, errorAfter, since, message)); + hasWrittenDeprecation = true; + } + }; + } - export function deprecate any>(func: F, options?: DeprecationOptions): F { - const deprecation = createDeprecation(getFunctionName(func), options); - return wrapFunction(deprecation, func); - } + function createDeprecation(name: string, options: DeprecationOptions & { error: true }): () => never; + function createDeprecation(name: string, options?: DeprecationOptions): () => void; + function createDeprecation(name: string, options: DeprecationOptions = {}) { + const version = typeof options.typeScriptVersion === "string" ? new Version(options.typeScriptVersion) : options.typeScriptVersion ?? getTypeScriptVersion(); + const errorAfter = typeof options.errorAfter === "string" ? new Version(options.errorAfter) : options.errorAfter; + const warnAfter = typeof options.warnAfter === "string" ? new Version(options.warnAfter) : options.warnAfter; + const since = typeof options.since === "string" ? new Version(options.since) : options.since ?? warnAfter; + const error = options.error || errorAfter && version.compareTo(errorAfter) <= 0; + const warn = !warnAfter || version.compareTo(warnAfter) >= 0; + return error ? createErrorDeprecation(name, errorAfter, since, options.message) : + warn ? createWarningDeprecation(name, errorAfter, since, options.message) : + noop; + } + + function wrapFunction any>(deprecation: () => void, func: F): F { + return function (this: unknown) { + deprecation(); + return func.apply(this, arguments); + } as F; } + + export function deprecate any>(func: F, options?: DeprecationOptions): F { + const deprecation = createDeprecation(getFunctionName(func), options); + return wrapFunction(deprecation, func); + } +} } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 5a2fc96f58b68..f2d11719f9cef 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1,5818 +1,5818 @@ namespace ts { - const brackets = createBracketsMap(); +const brackets = createBracketsMap(); - /*@internal*/ - export function isBuildInfoFile(file: string) { - return fileExtensionIs(file, Extension.TsBuildInfo); - } +/*@internal*/ +export function isBuildInfoFile(file: string) { + return fileExtensionIs(file, Extension.TsBuildInfo); +} - /*@internal*/ - /** - * Iterates over the source files that are expected to have an emit output. - * - * @param host An EmitHost. - * @param action The action to execute. - * @param sourceFilesOrTargetSourceFile - * If an array, the full list of source files to emit. - * Else, calls `getSourceFilesToEmit` with the (optional) target source file to determine the list of source files to emit. - */ - export function forEachEmittedFile( - host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) => T, - sourceFilesOrTargetSourceFile?: readonly SourceFile[] | SourceFile, - forceDtsEmit = false, - onlyBuildInfo?: boolean, - includeBuildInfo?: boolean) { - const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile, forceDtsEmit); - const options = host.getCompilerOptions(); - if (outFile(options)) { - const prepends = host.getPrependNodes(); - if (sourceFiles.length || prepends.length) { - const bundle = factory.createBundle(sourceFiles, prepends); - const result = action(getOutputPathsFor(bundle, host, forceDtsEmit), bundle); +/*@internal*/ +/** + * Iterates over the source files that are expected to have an emit output. + * + * @param host An EmitHost. + * @param action The action to execute. + * @param sourceFilesOrTargetSourceFile + * If an array, the full list of source files to emit. + * Else, calls `getSourceFilesToEmit` with the (optional) target source file to determine the list of source files to emit. + */ +export function forEachEmittedFile( + host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) => T, + sourceFilesOrTargetSourceFile?: readonly SourceFile[] | SourceFile, + forceDtsEmit = false, + onlyBuildInfo?: boolean, + includeBuildInfo?: boolean) { + const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile, forceDtsEmit); + const options = host.getCompilerOptions(); + if (outFile(options)) { + const prepends = host.getPrependNodes(); + if (sourceFiles.length || prepends.length) { + const bundle = factory.createBundle(sourceFiles, prepends); + const result = action(getOutputPathsFor(bundle, host, forceDtsEmit), bundle); + if (result) { + return result; + } + } + } + else { + if (!onlyBuildInfo) { + for (const sourceFile of sourceFiles) { + const result = action(getOutputPathsFor(sourceFile, host, forceDtsEmit), sourceFile); if (result) { return result; } } } - else { - if (!onlyBuildInfo) { - for (const sourceFile of sourceFiles) { - const result = action(getOutputPathsFor(sourceFile, host, forceDtsEmit), sourceFile); - if (result) { - return result; - } - } - } - if (includeBuildInfo) { - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); - if (buildInfoPath) return action({ buildInfoPath }, /*sourceFileOrBundle*/ undefined); - } + if (includeBuildInfo) { + const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); + if (buildInfoPath) return action({ buildInfoPath }, /*sourceFileOrBundle*/ undefined); } } +} - export function getTsBuildInfoEmitOutputFilePath(options: CompilerOptions) { - const configFile = options.configFilePath; - if (!isIncrementalCompilation(options)) return undefined; - if (options.tsBuildInfoFile) return options.tsBuildInfoFile; - const outPath = outFile(options); - let buildInfoExtensionLess: string; - if (outPath) { - buildInfoExtensionLess = removeFileExtension(outPath); - } - else { - if (!configFile) return undefined; - const configFileExtensionLess = removeFileExtension(configFile); - buildInfoExtensionLess = options.outDir ? - options.rootDir ? - resolvePath(options.outDir, getRelativePathFromDirectory(options.rootDir, configFileExtensionLess, /*ignoreCase*/ true)) : - combinePaths(options.outDir, getBaseFileName(configFileExtensionLess)) : - configFileExtensionLess; - } - return buildInfoExtensionLess + Extension.TsBuildInfo; - } - - /*@internal*/ - export function getOutputPathsForBundle(options: CompilerOptions, forceDtsPaths: boolean): EmitFileNames { - const outPath = outFile(options)!; - const jsFilePath = options.emitDeclarationOnly ? undefined : outPath; - const sourceMapFilePath = jsFilePath && getSourceMapFilePath(jsFilePath, options); - const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? removeFileExtension(outPath) + Extension.Dts : undefined; +export function getTsBuildInfoEmitOutputFilePath(options: CompilerOptions) { + const configFile = options.configFilePath; + if (!isIncrementalCompilation(options)) return undefined; + if (options.tsBuildInfoFile) return options.tsBuildInfoFile; + const outPath = outFile(options); + let buildInfoExtensionLess: string; + if (outPath) { + buildInfoExtensionLess = removeFileExtension(outPath); + } + else { + if (!configFile) return undefined; + const configFileExtensionLess = removeFileExtension(configFile); + buildInfoExtensionLess = options.outDir ? + options.rootDir ? + resolvePath(options.outDir, getRelativePathFromDirectory(options.rootDir, configFileExtensionLess, /*ignoreCase*/ true)) : + combinePaths(options.outDir, getBaseFileName(configFileExtensionLess)) : + configFileExtensionLess; + } + return buildInfoExtensionLess + Extension.TsBuildInfo; +} + +/*@internal*/ +export function getOutputPathsForBundle(options: CompilerOptions, forceDtsPaths: boolean): EmitFileNames { + const outPath = outFile(options)!; + const jsFilePath = options.emitDeclarationOnly ? undefined : outPath; + const sourceMapFilePath = jsFilePath && getSourceMapFilePath(jsFilePath, options); + const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? removeFileExtension(outPath) + Extension.Dts : undefined; + const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined; + const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); + return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }; +} + +/*@internal*/ +export function getOutputPathsFor(sourceFile: SourceFile | Bundle, host: EmitHost, forceDtsPaths: boolean): EmitFileNames { + const options = host.getCompilerOptions(); + if (sourceFile.kind === SyntaxKind.Bundle) { + return getOutputPathsForBundle(options, forceDtsPaths); + } + else { + const ownOutputFilePath = getOwnEmitOutputFilePath(sourceFile.fileName, host, getOutputExtension(sourceFile.fileName, options)); + const isJsonFile = isJsonSourceFile(sourceFile); + // If json file emits to the same location skip writing it, if emitDeclarationOnly skip writing it + const isJsonEmittedToSameLocation = isJsonFile && + comparePaths(sourceFile.fileName, ownOutputFilePath, host.getCurrentDirectory(), !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + const jsFilePath = options.emitDeclarationOnly || isJsonEmittedToSameLocation ? undefined : ownOutputFilePath; + const sourceMapFilePath = !jsFilePath || isJsonSourceFile(sourceFile) ? undefined : getSourceMapFilePath(jsFilePath, options); + const declarationFilePath = (forceDtsPaths || (getEmitDeclarations(options) && !isJsonFile)) ? getDeclarationEmitOutputFilePath(sourceFile.fileName, host) : undefined; const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined; - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); - return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }; + return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath: undefined }; } +} + +function getSourceMapFilePath(jsFilePath: string, options: CompilerOptions) { + return (options.sourceMap && !options.inlineSourceMap) ? jsFilePath + ".map" : undefined; +} + +/* @internal */ +export function getOutputExtension(fileName: string, options: CompilerOptions): Extension { + return fileExtensionIs(fileName, Extension.Json) ? Extension.Json : + options.jsx === JsxEmit.Preserve && fileExtensionIsOneOf(fileName, [Extension.Jsx, Extension.Tsx]) ? Extension.Jsx : + fileExtensionIsOneOf(fileName, [Extension.Mts, Extension.Mjs]) ? Extension.Mjs : + fileExtensionIsOneOf(fileName, [Extension.Cts, Extension.Cjs]) ? Extension.Cjs : + Extension.Js; +} + +function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined, getCommonSourceDirectory?: () => string) { + return outputDir ? + resolvePath( + outputDir, + getRelativePathFromDirectory(getCommonSourceDirectory ? getCommonSourceDirectory() : getCommonSourceDirectoryOfConfig(configFile, ignoreCase), inputFileName, ignoreCase) + ) : + inputFileName; +} + +/* @internal */ +export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { + return changeExtension( + getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir, getCommonSourceDirectory), + getDeclarationEmitExtensionForPath(inputFileName) + ); +} - /*@internal*/ - export function getOutputPathsFor(sourceFile: SourceFile | Bundle, host: EmitHost, forceDtsPaths: boolean): EmitFileNames { - const options = host.getCompilerOptions(); - if (sourceFile.kind === SyntaxKind.Bundle) { - return getOutputPathsForBundle(options, forceDtsPaths); +function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { + if (configFile.options.emitDeclarationOnly) return undefined; + const isJsonFile = fileExtensionIs(inputFileName, Extension.Json); + const outputFileName = changeExtension( + getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir, getCommonSourceDirectory), + getOutputExtension(inputFileName, configFile.options) + ); + return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.checkDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ? + outputFileName : + undefined; +} + +function createAddOutput() { + let outputs: string[] | undefined; + return { addOutput, getOutputs }; + function addOutput(path: string | undefined) { + if (path) { + (outputs || (outputs = [])).push(path); } - else { - const ownOutputFilePath = getOwnEmitOutputFilePath(sourceFile.fileName, host, getOutputExtension(sourceFile.fileName, options)); - const isJsonFile = isJsonSourceFile(sourceFile); - // If json file emits to the same location skip writing it, if emitDeclarationOnly skip writing it - const isJsonEmittedToSameLocation = isJsonFile && - comparePaths(sourceFile.fileName, ownOutputFilePath, host.getCurrentDirectory(), !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; - const jsFilePath = options.emitDeclarationOnly || isJsonEmittedToSameLocation ? undefined : ownOutputFilePath; - const sourceMapFilePath = !jsFilePath || isJsonSourceFile(sourceFile) ? undefined : getSourceMapFilePath(jsFilePath, options); - const declarationFilePath = (forceDtsPaths || (getEmitDeclarations(options) && !isJsonFile)) ? getDeclarationEmitOutputFilePath(sourceFile.fileName, host) : undefined; - const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined; - return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath: undefined }; - } - } - - function getSourceMapFilePath(jsFilePath: string, options: CompilerOptions) { - return (options.sourceMap && !options.inlineSourceMap) ? jsFilePath + ".map" : undefined; - } - - /* @internal */ - export function getOutputExtension(fileName: string, options: CompilerOptions): Extension { - return fileExtensionIs(fileName, Extension.Json) ? Extension.Json : - options.jsx === JsxEmit.Preserve && fileExtensionIsOneOf(fileName, [Extension.Jsx, Extension.Tsx]) ? Extension.Jsx : - fileExtensionIsOneOf(fileName, [Extension.Mts, Extension.Mjs]) ? Extension.Mjs : - fileExtensionIsOneOf(fileName, [Extension.Cts, Extension.Cjs]) ? Extension.Cjs : - Extension.Js; - } - - function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined, getCommonSourceDirectory?: () => string) { - return outputDir ? - resolvePath( - outputDir, - getRelativePathFromDirectory(getCommonSourceDirectory ? getCommonSourceDirectory() : getCommonSourceDirectoryOfConfig(configFile, ignoreCase), inputFileName, ignoreCase) - ) : - inputFileName; - } - - /* @internal */ - export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { - return changeExtension( - getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir, getCommonSourceDirectory), - getDeclarationEmitExtensionForPath(inputFileName) - ); } - - function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { - if (configFile.options.emitDeclarationOnly) return undefined; - const isJsonFile = fileExtensionIs(inputFileName, Extension.Json); - const outputFileName = changeExtension( - getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir, getCommonSourceDirectory), - getOutputExtension(inputFileName, configFile.options) - ); - return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.checkDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ? - outputFileName : - undefined; + function getOutputs(): readonly string[] { + return outputs || emptyArray; } +} - function createAddOutput() { - let outputs: string[] | undefined; - return { addOutput, getOutputs }; - function addOutput(path: string | undefined) { - if (path) { - (outputs || (outputs = [])).push(path); - } - } - function getOutputs(): readonly string[] { - return outputs || emptyArray; +function getSingleOutputFileNames(configFile: ParsedCommandLine, addOutput: ReturnType["addOutput"]) { + const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); + addOutput(jsFilePath); + addOutput(sourceMapFilePath); + addOutput(declarationFilePath); + addOutput(declarationMapPath); + addOutput(buildInfoPath); +} + +function getOwnOutputFileNames(configFile: ParsedCommandLine, inputFileName: string, ignoreCase: boolean, addOutput: ReturnType["addOutput"], getCommonSourceDirectory?: () => string) { + if (fileExtensionIs(inputFileName, Extension.Dts)) return; + const js = getOutputJSFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); + addOutput(js); + if (fileExtensionIs(inputFileName, Extension.Json)) return; + if (js && configFile.options.sourceMap) { + addOutput(`${js}.map`); + } + if (getEmitDeclarations(configFile.options)) { + const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); + addOutput(dts); + if (configFile.options.declarationMap) { + addOutput(`${dts}.map`); } } +} - function getSingleOutputFileNames(configFile: ParsedCommandLine, addOutput: ReturnType["addOutput"]) { - const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); - addOutput(jsFilePath); - addOutput(sourceMapFilePath); - addOutput(declarationFilePath); - addOutput(declarationMapPath); - addOutput(buildInfoPath); +/*@internal*/ +export function getCommonSourceDirectory( + options: CompilerOptions, + emittedFiles: () => readonly string[], + currentDirectory: string, + getCanonicalFileName: GetCanonicalFileName, + checkSourceFilesBelongToPath?: (commonSourceDirectory: string) => void +): string { + let commonSourceDirectory; + if (options.rootDir) { + // If a rootDir is specified use it as the commonSourceDirectory + commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory); + checkSourceFilesBelongToPath?.(options.rootDir); + } + else if (options.composite && options.configFilePath) { + // Project compilations never infer their root from the input source paths + commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath)); + checkSourceFilesBelongToPath?.(commonSourceDirectory); + } + else { + commonSourceDirectory = computeCommonSourceDirectoryOfFilenames(emittedFiles(), currentDirectory, getCanonicalFileName); } - function getOwnOutputFileNames(configFile: ParsedCommandLine, inputFileName: string, ignoreCase: boolean, addOutput: ReturnType["addOutput"], getCommonSourceDirectory?: () => string) { - if (fileExtensionIs(inputFileName, Extension.Dts)) return; - const js = getOutputJSFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); - addOutput(js); - if (fileExtensionIs(inputFileName, Extension.Json)) return; - if (js && configFile.options.sourceMap) { - addOutput(`${js}.map`); - } - if (getEmitDeclarations(configFile.options)) { - const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); - addOutput(dts); - if (configFile.options.declarationMap) { - addOutput(`${dts}.map`); - } - } + if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) { + // Make sure directory path ends with directory separator so this string can directly + // used to replace with "" to get the relative path of the source file and the relative path doesn't + // start with / making it rooted path + commonSourceDirectory += directorySeparator; } + return commonSourceDirectory; +} - /*@internal*/ - export function getCommonSourceDirectory( - options: CompilerOptions, - emittedFiles: () => readonly string[], - currentDirectory: string, - getCanonicalFileName: GetCanonicalFileName, - checkSourceFilesBelongToPath?: (commonSourceDirectory: string) => void - ): string { - let commonSourceDirectory; - if (options.rootDir) { - // If a rootDir is specified use it as the commonSourceDirectory - commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory); - checkSourceFilesBelongToPath?.(options.rootDir); - } - else if (options.composite && options.configFilePath) { - // Project compilations never infer their root from the input source paths - commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath)); - checkSourceFilesBelongToPath?.(commonSourceDirectory); - } - else { - commonSourceDirectory = computeCommonSourceDirectoryOfFilenames(emittedFiles(), currentDirectory, getCanonicalFileName); - } +/*@internal*/ +export function getCommonSourceDirectoryOfConfig({ options, fileNames }: ParsedCommandLine, ignoreCase: boolean): string { + return getCommonSourceDirectory( + options, + () => filter(fileNames, file => !(options.noEmitForJsFiles && fileExtensionIsOneOf(file, supportedJSExtensionsFlat)) && !fileExtensionIs(file, Extension.Dts)), + getDirectoryPath(normalizeSlashes(Debug.checkDefined(options.configFilePath))), + createGetCanonicalFileName(!ignoreCase) + ); +} - if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) { - // Make sure directory path ends with directory separator so this string can directly - // used to replace with "" to get the relative path of the source file and the relative path doesn't - // start with / making it rooted path - commonSourceDirectory += directorySeparator; +/*@internal*/ +export function getAllProjectOutputs(configFile: ParsedCommandLine, ignoreCase: boolean): readonly string[] { + const { addOutput, getOutputs } = createAddOutput(); + if (outFile(configFile.options)) { + getSingleOutputFileNames(configFile, addOutput); + } + else { + const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(configFile, ignoreCase)); + for (const inputFileName of configFile.fileNames) { + getOwnOutputFileNames(configFile, inputFileName, ignoreCase, addOutput, getCommonSourceDirectory); } - return commonSourceDirectory; + addOutput(getTsBuildInfoEmitOutputFilePath(configFile.options)); } + return getOutputs(); +} - /*@internal*/ - export function getCommonSourceDirectoryOfConfig({ options, fileNames }: ParsedCommandLine, ignoreCase: boolean): string { - return getCommonSourceDirectory( - options, - () => filter(fileNames, file => !(options.noEmitForJsFiles && fileExtensionIsOneOf(file, supportedJSExtensionsFlat)) && !fileExtensionIs(file, Extension.Dts)), - getDirectoryPath(normalizeSlashes(Debug.checkDefined(options.configFilePath))), - createGetCanonicalFileName(!ignoreCase) - ); +export function getOutputFileNames(commandLine: ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[] { + inputFileName = normalizePath(inputFileName); + Debug.assert(contains(commandLine.fileNames, inputFileName), `Expected fileName to be present in command line`); + const { addOutput, getOutputs } = createAddOutput(); + if (outFile(commandLine.options)) { + getSingleOutputFileNames(commandLine, addOutput); } + else { + getOwnOutputFileNames(commandLine, inputFileName, ignoreCase, addOutput); + } + return getOutputs(); +} - /*@internal*/ - export function getAllProjectOutputs(configFile: ParsedCommandLine, ignoreCase: boolean): readonly string[] { - const { addOutput, getOutputs } = createAddOutput(); - if (outFile(configFile.options)) { - getSingleOutputFileNames(configFile, addOutput); - } - else { - const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(configFile, ignoreCase)); - for (const inputFileName of configFile.fileNames) { - getOwnOutputFileNames(configFile, inputFileName, ignoreCase, addOutput, getCommonSourceDirectory); - } - addOutput(getTsBuildInfoEmitOutputFilePath(configFile.options)); - } - return getOutputs(); +/*@internal*/ +export function getFirstProjectOutput(configFile: ParsedCommandLine, ignoreCase: boolean): string { + if (outFile(configFile.options)) { + const { jsFilePath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); + return Debug.checkDefined(jsFilePath, `project ${configFile.options.configFilePath} expected to have at least one output`); } - export function getOutputFileNames(commandLine: ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[] { - inputFileName = normalizePath(inputFileName); - Debug.assert(contains(commandLine.fileNames, inputFileName), `Expected fileName to be present in command line`); - const { addOutput, getOutputs } = createAddOutput(); - if (outFile(commandLine.options)) { - getSingleOutputFileNames(commandLine, addOutput); - } - else { - getOwnOutputFileNames(commandLine, inputFileName, ignoreCase, addOutput); + const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(configFile, ignoreCase)); + for (const inputFileName of configFile.fileNames) { + if (fileExtensionIs(inputFileName, Extension.Dts)) continue; + const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); + if (jsFilePath) return jsFilePath; + if (fileExtensionIs(inputFileName, Extension.Json)) continue; + if (getEmitDeclarations(configFile.options)) { + return getOutputDeclarationFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); } - return getOutputs(); } + const buildInfoPath = getTsBuildInfoEmitOutputFilePath(configFile.options); + if (buildInfoPath) return buildInfoPath; + return Debug.fail(`project ${configFile.options.configFilePath} expected to have at least one output`); +} - /*@internal*/ - export function getFirstProjectOutput(configFile: ParsedCommandLine, ignoreCase: boolean): string { - if (outFile(configFile.options)) { - const { jsFilePath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); - return Debug.checkDefined(jsFilePath, `project ${configFile.options.configFilePath} expected to have at least one output`); - } +/*@internal*/ +// targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature +export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, { scriptTransformers, declarationTransformers }: EmitTransformers, emitOnlyDtsFiles?: boolean, onlyBuildInfo?: boolean, forceDtsEmit?: boolean): EmitResult { + const compilerOptions = host.getCompilerOptions(); + const sourceMapDataList: SourceMapEmitResult[] | undefined = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined; + const emittedFilesList: string[] | undefined = compilerOptions.listEmittedFiles ? [] : undefined; + const emitterDiagnostics = createDiagnosticCollection(); + const newLine = getNewLineCharacter(compilerOptions, () => host.getNewLine()); + const writer = createTextWriter(newLine); + const { enter, exit } = performance.createTimer("printTime", "beforePrint", "afterPrint"); + let bundleBuildInfo: BundleBuildInfo | undefined; + let emitSkipped = false; + let exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined; + + // Emit each output file + enter(); + forEachEmittedFile( + host, + emitSourceFileOrBundle, + getSourceFilesToEmit(host, targetSourceFile, forceDtsEmit), + forceDtsEmit, + onlyBuildInfo, + !targetSourceFile + ); + exit(); + + + return { + emitSkipped, + diagnostics: emitterDiagnostics.getDiagnostics(), + emittedFiles: emittedFilesList, + sourceMaps: sourceMapDataList, + exportedModulesFromDeclarationEmit + }; - const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(configFile, ignoreCase)); - for (const inputFileName of configFile.fileNames) { - if (fileExtensionIs(inputFileName, Extension.Dts)) continue; - const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); - if (jsFilePath) return jsFilePath; - if (fileExtensionIs(inputFileName, Extension.Json)) continue; - if (getEmitDeclarations(configFile.options)) { - return getOutputDeclarationFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); - } + function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) { + let buildInfoDirectory: string | undefined; + if (buildInfoPath && sourceFileOrBundle && isBundle(sourceFileOrBundle)) { + buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); + bundleBuildInfo = { + commonSourceDirectory: relativeToBuildInfo(host.getCommonSourceDirectory()), + sourceFiles: sourceFileOrBundle.sourceFiles.map(file => relativeToBuildInfo(getNormalizedAbsolutePath(file.fileName, host.getCurrentDirectory()))) + }; } - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(configFile.options); - if (buildInfoPath) return buildInfoPath; - return Debug.fail(`project ${configFile.options.configFilePath} expected to have at least one output`); - } - - /*@internal*/ - // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature - export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, { scriptTransformers, declarationTransformers }: EmitTransformers, emitOnlyDtsFiles?: boolean, onlyBuildInfo?: boolean, forceDtsEmit?: boolean): EmitResult { - const compilerOptions = host.getCompilerOptions(); - const sourceMapDataList: SourceMapEmitResult[] | undefined = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined; - const emittedFilesList: string[] | undefined = compilerOptions.listEmittedFiles ? [] : undefined; - const emitterDiagnostics = createDiagnosticCollection(); - const newLine = getNewLineCharacter(compilerOptions, () => host.getNewLine()); - const writer = createTextWriter(newLine); - const { enter, exit } = performance.createTimer("printTime", "beforePrint", "afterPrint"); - let bundleBuildInfo: BundleBuildInfo | undefined; - let emitSkipped = false; - let exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined; - - // Emit each output file - enter(); - forEachEmittedFile( - host, - emitSourceFileOrBundle, - getSourceFilesToEmit(host, targetSourceFile, forceDtsEmit), - forceDtsEmit, - onlyBuildInfo, - !targetSourceFile - ); - exit(); + tracing?.push(tracing.Phase.Emit, "emitJsFileOrBundle", { jsFilePath }); + emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath, relativeToBuildInfo); + tracing?.pop(); + tracing?.push(tracing.Phase.Emit, "emitDeclarationFileOrBundle", { declarationFilePath }); + emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath, declarationMapPath, relativeToBuildInfo); + tracing?.pop(); - return { - emitSkipped, - diagnostics: emitterDiagnostics.getDiagnostics(), - emittedFiles: emittedFilesList, - sourceMaps: sourceMapDataList, - exportedModulesFromDeclarationEmit - }; + tracing?.push(tracing.Phase.Emit, "emitBuildInfo", { buildInfoPath }); + emitBuildInfo(bundleBuildInfo, buildInfoPath); + tracing?.pop(); - function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) { - let buildInfoDirectory: string | undefined; - if (buildInfoPath && sourceFileOrBundle && isBundle(sourceFileOrBundle)) { - buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); - bundleBuildInfo = { - commonSourceDirectory: relativeToBuildInfo(host.getCommonSourceDirectory()), - sourceFiles: sourceFileOrBundle.sourceFiles.map(file => relativeToBuildInfo(getNormalizedAbsolutePath(file.fileName, host.getCurrentDirectory()))) - }; - } - tracing?.push(tracing.Phase.Emit, "emitJsFileOrBundle", { jsFilePath }); - emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath, relativeToBuildInfo); - tracing?.pop(); - - tracing?.push(tracing.Phase.Emit, "emitDeclarationFileOrBundle", { declarationFilePath }); - emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath, declarationMapPath, relativeToBuildInfo); - tracing?.pop(); - - tracing?.push(tracing.Phase.Emit, "emitBuildInfo", { buildInfoPath }); - emitBuildInfo(bundleBuildInfo, buildInfoPath); - tracing?.pop(); - - if (!emitSkipped && emittedFilesList) { - if (!emitOnlyDtsFiles) { - if (jsFilePath) { - emittedFilesList.push(jsFilePath); - } - if (sourceMapFilePath) { - emittedFilesList.push(sourceMapFilePath); - } - if (buildInfoPath) { - emittedFilesList.push(buildInfoPath); - } + if (!emitSkipped && emittedFilesList) { + if (!emitOnlyDtsFiles) { + if (jsFilePath) { + emittedFilesList.push(jsFilePath); } - if (declarationFilePath) { - emittedFilesList.push(declarationFilePath); + if (sourceMapFilePath) { + emittedFilesList.push(sourceMapFilePath); } - if (declarationMapPath) { - emittedFilesList.push(declarationMapPath); + if (buildInfoPath) { + emittedFilesList.push(buildInfoPath); } } - - function relativeToBuildInfo(path: string) { - return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory!, path, host.getCanonicalFileName)); + if (declarationFilePath) { + emittedFilesList.push(declarationFilePath); } - } - - function emitBuildInfo(bundle: BundleBuildInfo | undefined, buildInfoPath: string | undefined) { - // Write build information if applicable - if (!buildInfoPath || targetSourceFile || emitSkipped) return; - const program = host.getProgramBuildInfo(); - if (host.isEmitBlocked(buildInfoPath)) { - emitSkipped = true; - return; + if (declarationMapPath) { + emittedFilesList.push(declarationMapPath); } - const version = ts.version; // Extracted into a const so the form is stable between namespace and module - writeFile(host, emitterDiagnostics, buildInfoPath, getBuildInfoText({ bundle, program, version }), /*writeByteOrderMark*/ false); } - function emitJsFileOrBundle( - sourceFileOrBundle: SourceFile | Bundle | undefined, - jsFilePath: string | undefined, - sourceMapFilePath: string | undefined, - relativeToBuildInfo: (path: string) => string) { - if (!sourceFileOrBundle || emitOnlyDtsFiles || !jsFilePath) { - return; - } - - // Make sure not to write js file and source map file if any of them cannot be written - if ((jsFilePath && host.isEmitBlocked(jsFilePath)) || compilerOptions.noEmit) { - emitSkipped = true; - return; - } - // Transform the source files - const transform = transformNodes(resolver, host, factory, compilerOptions, [sourceFileOrBundle], scriptTransformers, /*allowDtsFiles*/ false); - - const printerOptions: PrinterOptions = { - removeComments: compilerOptions.removeComments, - newLine: compilerOptions.newLine, - noEmitHelpers: compilerOptions.noEmitHelpers, - module: compilerOptions.module, - target: compilerOptions.target, - sourceMap: compilerOptions.sourceMap, - inlineSourceMap: compilerOptions.inlineSourceMap, - inlineSources: compilerOptions.inlineSources, - extendedDiagnostics: compilerOptions.extendedDiagnostics, - writeBundleFileInfo: !!bundleBuildInfo, - relativeToBuildInfo - }; + function relativeToBuildInfo(path: string) { + return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory!, path, host.getCanonicalFileName)); + } + } - // Create a printer to print the nodes - const printer = createPrinter(printerOptions, { - // resolver hooks - hasGlobalName: resolver.hasGlobalName, - - // transform hooks - onEmitNode: transform.emitNodeWithNotification, - isEmitNotificationEnabled: transform.isEmitNotificationEnabled, - substituteNode: transform.substituteNode, - }); - - Debug.assert(transform.transformed.length === 1, "Should only see one output from the transform"); - printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform.transformed[0], printer, compilerOptions); - - // Clean up emit nodes on parse tree - transform.dispose(); - if (bundleBuildInfo) bundleBuildInfo.js = printer.bundleFileInfo; - } - - function emitDeclarationFileOrBundle( - sourceFileOrBundle: SourceFile | Bundle | undefined, - declarationFilePath: string | undefined, - declarationMapPath: string | undefined, - relativeToBuildInfo: (path: string) => string) { - if (!sourceFileOrBundle) return; - if (!declarationFilePath) { - if (emitOnlyDtsFiles || compilerOptions.emitDeclarationOnly) emitSkipped = true; - return; - } - const sourceFiles = isSourceFile(sourceFileOrBundle) ? [sourceFileOrBundle] : sourceFileOrBundle.sourceFiles; - const filesForEmit = forceDtsEmit ? sourceFiles : filter(sourceFiles, isSourceFileNotJson); - // Setup and perform the transformation to retrieve declarations from the input files - const inputListOrBundle = outFile(compilerOptions) ? [factory.createBundle(filesForEmit, !isSourceFile(sourceFileOrBundle) ? sourceFileOrBundle.prepends : undefined)] : filesForEmit; - if (emitOnlyDtsFiles && !getEmitDeclarations(compilerOptions)) { - // Checker wont collect the linked aliases since thats only done when declaration is enabled. - // Do that here when emitting only dts files - filesForEmit.forEach(collectLinkedAliases); - } - const declarationTransform = transformNodes(resolver, host, factory, compilerOptions, inputListOrBundle, declarationTransformers, /*allowDtsFiles*/ false); - if (length(declarationTransform.diagnostics)) { - for (const diagnostic of declarationTransform.diagnostics!) { - emitterDiagnostics.add(diagnostic); - } - } + function emitBuildInfo(bundle: BundleBuildInfo | undefined, buildInfoPath: string | undefined) { + // Write build information if applicable + if (!buildInfoPath || targetSourceFile || emitSkipped) return; + const program = host.getProgramBuildInfo(); + if (host.isEmitBlocked(buildInfoPath)) { + emitSkipped = true; + return; + } + const version = ts.version; // Extracted into a const so the form is stable between namespace and module + writeFile(host, emitterDiagnostics, buildInfoPath, getBuildInfoText({ bundle, program, version }), /*writeByteOrderMark*/ false); + } - const printerOptions: PrinterOptions = { - removeComments: compilerOptions.removeComments, - newLine: compilerOptions.newLine, - noEmitHelpers: true, - module: compilerOptions.module, - target: compilerOptions.target, - sourceMap: compilerOptions.sourceMap, - inlineSourceMap: compilerOptions.inlineSourceMap, - extendedDiagnostics: compilerOptions.extendedDiagnostics, - onlyPrintJsDocStyle: true, - writeBundleFileInfo: !!bundleBuildInfo, - recordInternalSection: !!bundleBuildInfo, - relativeToBuildInfo - }; + function emitJsFileOrBundle( + sourceFileOrBundle: SourceFile | Bundle | undefined, + jsFilePath: string | undefined, + sourceMapFilePath: string | undefined, + relativeToBuildInfo: (path: string) => string) { + if (!sourceFileOrBundle || emitOnlyDtsFiles || !jsFilePath) { + return; + } + + // Make sure not to write js file and source map file if any of them cannot be written + if ((jsFilePath && host.isEmitBlocked(jsFilePath)) || compilerOptions.noEmit) { + emitSkipped = true; + return; + } + // Transform the source files + const transform = transformNodes(resolver, host, factory, compilerOptions, [sourceFileOrBundle], scriptTransformers, /*allowDtsFiles*/ false); + + const printerOptions: PrinterOptions = { + removeComments: compilerOptions.removeComments, + newLine: compilerOptions.newLine, + noEmitHelpers: compilerOptions.noEmitHelpers, + module: compilerOptions.module, + target: compilerOptions.target, + sourceMap: compilerOptions.sourceMap, + inlineSourceMap: compilerOptions.inlineSourceMap, + inlineSources: compilerOptions.inlineSources, + extendedDiagnostics: compilerOptions.extendedDiagnostics, + writeBundleFileInfo: !!bundleBuildInfo, + relativeToBuildInfo + }; - const declarationPrinter = createPrinter(printerOptions, { - // resolver hooks - hasGlobalName: resolver.hasGlobalName, - - // transform hooks - onEmitNode: declarationTransform.emitNodeWithNotification, - isEmitNotificationEnabled: declarationTransform.isEmitNotificationEnabled, - substituteNode: declarationTransform.substituteNode, - }); - const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit; - emitSkipped = emitSkipped || declBlocked; - if (!declBlocked || forceDtsEmit) { - Debug.assert(declarationTransform.transformed.length === 1, "Should only see one output from the decl transform"); - printSourceFileOrBundle( - declarationFilePath, - declarationMapPath, - declarationTransform.transformed[0], - declarationPrinter, - { - sourceMap: !forceDtsEmit && compilerOptions.declarationMap, - sourceRoot: compilerOptions.sourceRoot, - mapRoot: compilerOptions.mapRoot, - extendedDiagnostics: compilerOptions.extendedDiagnostics, - // Explicitly do not passthru either `inline` option - } - ); - if (forceDtsEmit && declarationTransform.transformed[0].kind === SyntaxKind.SourceFile) { - const sourceFile = declarationTransform.transformed[0]; - exportedModulesFromDeclarationEmit = sourceFile.exportedModulesFromDeclarationEmit; - } - } - declarationTransform.dispose(); - if (bundleBuildInfo) bundleBuildInfo.dts = declarationPrinter.bundleFileInfo; - } + // Create a printer to print the nodes + const printer = createPrinter(printerOptions, { + // resolver hooks + hasGlobalName: resolver.hasGlobalName, - function collectLinkedAliases(node: Node) { - if (isExportAssignment(node)) { - if (node.expression.kind === SyntaxKind.Identifier) { - resolver.collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true); - } - return; - } - else if (isExportSpecifier(node)) { - resolver.collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); - return; - } - forEachChild(node, collectLinkedAliases); - } - - function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, printer: Printer, mapOptions: SourceMapOptions) { - const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined; - const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined; - const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile!]; - - let sourceMapGenerator: SourceMapGenerator | undefined; - if (shouldEmitSourceMaps(mapOptions, sourceFileOrBundle)) { - sourceMapGenerator = createSourceMapGenerator( - host, - getBaseFileName(normalizeSlashes(jsFilePath)), - getSourceRoot(mapOptions), - getSourceMapDirectory(mapOptions, jsFilePath, sourceFile), - mapOptions); - } + // transform hooks + onEmitNode: transform.emitNodeWithNotification, + isEmitNotificationEnabled: transform.isEmitNotificationEnabled, + substituteNode: transform.substituteNode, + }); - if (bundle) { - printer.writeBundle(bundle, writer, sourceMapGenerator); - } - else { - printer.writeFile(sourceFile!, writer, sourceMapGenerator); - } + Debug.assert(transform.transformed.length === 1, "Should only see one output from the transform"); + printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform.transformed[0], printer, compilerOptions); - if (sourceMapGenerator) { - if (sourceMapDataList) { - sourceMapDataList.push({ - inputSourceFileNames: sourceMapGenerator.getSources(), - sourceMap: sourceMapGenerator.toJSON() - }); - } + // Clean up emit nodes on parse tree + transform.dispose(); + if (bundleBuildInfo) bundleBuildInfo.js = printer.bundleFileInfo; + } - const sourceMappingURL = getSourceMappingURL( - mapOptions, - sourceMapGenerator, - jsFilePath, - sourceMapFilePath, - sourceFile); + function emitDeclarationFileOrBundle( + sourceFileOrBundle: SourceFile | Bundle | undefined, + declarationFilePath: string | undefined, + declarationMapPath: string | undefined, + relativeToBuildInfo: (path: string) => string) { + if (!sourceFileOrBundle) return; + if (!declarationFilePath) { + if (emitOnlyDtsFiles || compilerOptions.emitDeclarationOnly) emitSkipped = true; + return; + } + const sourceFiles = isSourceFile(sourceFileOrBundle) ? [sourceFileOrBundle] : sourceFileOrBundle.sourceFiles; + const filesForEmit = forceDtsEmit ? sourceFiles : filter(sourceFiles, isSourceFileNotJson); + // Setup and perform the transformation to retrieve declarations from the input files + const inputListOrBundle = outFile(compilerOptions) ? [factory.createBundle(filesForEmit, !isSourceFile(sourceFileOrBundle) ? sourceFileOrBundle.prepends : undefined)] : filesForEmit; + if (emitOnlyDtsFiles && !getEmitDeclarations(compilerOptions)) { + // Checker wont collect the linked aliases since thats only done when declaration is enabled. + // Do that here when emitting only dts files + filesForEmit.forEach(collectLinkedAliases); + } + const declarationTransform = transformNodes(resolver, host, factory, compilerOptions, inputListOrBundle, declarationTransformers, /*allowDtsFiles*/ false); + if (length(declarationTransform.diagnostics)) { + for (const diagnostic of declarationTransform.diagnostics!) { + emitterDiagnostics.add(diagnostic); + } + } + + const printerOptions: PrinterOptions = { + removeComments: compilerOptions.removeComments, + newLine: compilerOptions.newLine, + noEmitHelpers: true, + module: compilerOptions.module, + target: compilerOptions.target, + sourceMap: compilerOptions.sourceMap, + inlineSourceMap: compilerOptions.inlineSourceMap, + extendedDiagnostics: compilerOptions.extendedDiagnostics, + onlyPrintJsDocStyle: true, + writeBundleFileInfo: !!bundleBuildInfo, + recordInternalSection: !!bundleBuildInfo, + relativeToBuildInfo + }; - if (sourceMappingURL) { - if (!writer.isAtStartOfLine()) writer.rawWrite(newLine); - writer.writeComment(`//# ${"sourceMappingURL"}=${sourceMappingURL}`); // Tools can sometimes see this line as a source mapping url comment - } + const declarationPrinter = createPrinter(printerOptions, { + // resolver hooks + hasGlobalName: resolver.hasGlobalName, - // Write the source map - if (sourceMapFilePath) { - const sourceMap = sourceMapGenerator.toString(); - writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap, /*writeByteOrderMark*/ false, sourceFiles); + // transform hooks + onEmitNode: declarationTransform.emitNodeWithNotification, + isEmitNotificationEnabled: declarationTransform.isEmitNotificationEnabled, + substituteNode: declarationTransform.substituteNode, + }); + const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit; + emitSkipped = emitSkipped || declBlocked; + if (!declBlocked || forceDtsEmit) { + Debug.assert(declarationTransform.transformed.length === 1, "Should only see one output from the decl transform"); + printSourceFileOrBundle( + declarationFilePath, + declarationMapPath, + declarationTransform.transformed[0], + declarationPrinter, + { + sourceMap: !forceDtsEmit && compilerOptions.declarationMap, + sourceRoot: compilerOptions.sourceRoot, + mapRoot: compilerOptions.mapRoot, + extendedDiagnostics: compilerOptions.extendedDiagnostics, + // Explicitly do not passthru either `inline` option } + ); + if (forceDtsEmit && declarationTransform.transformed[0].kind === SyntaxKind.SourceFile) { + const sourceFile = declarationTransform.transformed[0]; + exportedModulesFromDeclarationEmit = sourceFile.exportedModulesFromDeclarationEmit; } - else { - writer.writeLine(); - } - - // Write the output file - writeFile(host, emitterDiagnostics, jsFilePath, writer.getText(), !!compilerOptions.emitBOM, sourceFiles); - - // Reset state - writer.clear(); } + declarationTransform.dispose(); + if (bundleBuildInfo) bundleBuildInfo.dts = declarationPrinter.bundleFileInfo; + } - interface SourceMapOptions { - sourceMap?: boolean; - inlineSourceMap?: boolean; - inlineSources?: boolean; - sourceRoot?: string; - mapRoot?: string; - extendedDiagnostics?: boolean; + function collectLinkedAliases(node: Node) { + if (isExportAssignment(node)) { + if (node.expression.kind === SyntaxKind.Identifier) { + resolver.collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true); + } + return; } - - function shouldEmitSourceMaps(mapOptions: SourceMapOptions, sourceFileOrBundle: SourceFile | Bundle) { - return (mapOptions.sourceMap || mapOptions.inlineSourceMap) - && (sourceFileOrBundle.kind !== SyntaxKind.SourceFile || !fileExtensionIs(sourceFileOrBundle.fileName, Extension.Json)); + else if (isExportSpecifier(node)) { + resolver.collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); + return; } + forEachChild(node, collectLinkedAliases); + } - function getSourceRoot(mapOptions: SourceMapOptions) { - // Normalize source root and make sure it has trailing "/" so that it can be used to combine paths with the - // relative paths of the sources list in the sourcemap - const sourceRoot = normalizeSlashes(mapOptions.sourceRoot || ""); - return sourceRoot ? ensureTrailingDirectorySeparator(sourceRoot) : sourceRoot; - } + function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, printer: Printer, mapOptions: SourceMapOptions) { + const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined; + const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined; + const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile!]; - function getSourceMapDirectory(mapOptions: SourceMapOptions, filePath: string, sourceFile: SourceFile | undefined) { - if (mapOptions.sourceRoot) return host.getCommonSourceDirectory(); - if (mapOptions.mapRoot) { - let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); - if (sourceFile) { - // For modules or multiple emit files the mapRoot will have directory structure like the sources - // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map - sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); - } - if (getRootLength(sourceMapDir) === 0) { - // The relative paths are relative to the common directory - sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); - } - return sourceMapDir; - } - return getDirectoryPath(normalizePath(filePath)); + let sourceMapGenerator: SourceMapGenerator | undefined; + if (shouldEmitSourceMaps(mapOptions, sourceFileOrBundle)) { + sourceMapGenerator = createSourceMapGenerator( + host, + getBaseFileName(normalizeSlashes(jsFilePath)), + getSourceRoot(mapOptions), + getSourceMapDirectory(mapOptions, jsFilePath, sourceFile), + mapOptions); } - function getSourceMappingURL(mapOptions: SourceMapOptions, sourceMapGenerator: SourceMapGenerator, filePath: string, sourceMapFilePath: string | undefined, sourceFile: SourceFile | undefined) { - if (mapOptions.inlineSourceMap) { - // Encode the sourceMap into the sourceMap url - const sourceMapText = sourceMapGenerator.toString(); - const base64SourceMapText = base64encode(sys, sourceMapText); - return `data:application/json;base64,${base64SourceMapText}`; - } + if (bundle) { + printer.writeBundle(bundle, writer, sourceMapGenerator); + } + else { + printer.writeFile(sourceFile!, writer, sourceMapGenerator); + } - const sourceMapFile = getBaseFileName(normalizeSlashes(Debug.checkDefined(sourceMapFilePath))); - if (mapOptions.mapRoot) { - let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); - if (sourceFile) { - // For modules or multiple emit files the mapRoot will have directory structure like the sources - // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map - sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); - } - if (getRootLength(sourceMapDir) === 0) { - // The relative paths are relative to the common directory - sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); - return encodeURI( - getRelativePathToDirectoryOrUrl( - getDirectoryPath(normalizePath(filePath)), // get the relative sourceMapDir path based on jsFilePath - combinePaths(sourceMapDir, sourceMapFile), // this is where user expects to see sourceMap - host.getCurrentDirectory(), - host.getCanonicalFileName, - /*isAbsolutePathAnUrl*/ true)); - } - else { - return encodeURI(combinePaths(sourceMapDir, sourceMapFile)); - } + if (sourceMapGenerator) { + if (sourceMapDataList) { + sourceMapDataList.push({ + inputSourceFileNames: sourceMapGenerator.getSources(), + sourceMap: sourceMapGenerator.toJSON() + }); } - return encodeURI(sourceMapFile); - } - } - - /*@internal*/ - export function getBuildInfoText(buildInfo: BuildInfo) { - return JSON.stringify(buildInfo); - } - - /*@internal*/ - export function getBuildInfo(buildInfoText: string) { - return JSON.parse(buildInfoText) as BuildInfo; - } - - /*@internal*/ - export const notImplementedResolver: EmitResolver = { - hasGlobalName: notImplemented, - getReferencedExportContainer: notImplemented, - getReferencedImportDeclaration: notImplemented, - getReferencedDeclarationWithCollidingName: notImplemented, - isDeclarationWithCollidingName: notImplemented, - isValueAliasDeclaration: notImplemented, - isReferencedAliasDeclaration: notImplemented, - isTopLevelValueImportEqualsWithEntityName: notImplemented, - getNodeCheckFlags: notImplemented, - isDeclarationVisible: notImplemented, - isLateBound: (_node): _node is LateBoundDeclaration => false, - collectLinkedAliases: notImplemented, - isImplementationOfOverload: notImplemented, - isRequiredInitializedParameter: notImplemented, - isOptionalUninitializedParameterProperty: notImplemented, - isExpandoFunctionDeclaration: notImplemented, - getPropertiesOfContainerFunction: notImplemented, - createTypeOfDeclaration: notImplemented, - createReturnTypeOfSignatureDeclaration: notImplemented, - createTypeOfExpression: notImplemented, - createLiteralConstValue: notImplemented, - isSymbolAccessible: notImplemented, - isEntityNameVisible: notImplemented, - // Returns the constant value this property access resolves to: notImplemented, or 'undefined' for a non-constant - getConstantValue: notImplemented, - getReferencedValueDeclaration: notImplemented, - getTypeReferenceSerializationKind: notImplemented, - isOptionalParameter: notImplemented, - moduleExportsSomeValue: notImplemented, - isArgumentsLocalBinding: notImplemented, - getExternalModuleFileFromDeclaration: notImplemented, - getTypeReferenceDirectivesForEntityName: notImplemented, - getTypeReferenceDirectivesForSymbol: notImplemented, - isLiteralConstDeclaration: notImplemented, - getJsxFactoryEntity: notImplemented, - getJsxFragmentFactoryEntity: notImplemented, - getAllAccessorDeclarations: notImplemented, - getSymbolOfExternalModuleSpecifier: notImplemented, - isBindingCapturedByNode: notImplemented, - getDeclarationStatementsForSourceFile: notImplemented, - isImportRequiredByAugmentation: notImplemented, - }; - - /*@internal*/ - /** File that isnt present resulting in error or output files */ - export type EmitUsingBuildInfoResult = string | readonly OutputFile[]; - - /*@internal*/ - export interface EmitUsingBuildInfoHost extends ModuleResolutionHost { - getCurrentDirectory(): string; - getCanonicalFileName(fileName: string): string; - useCaseSensitiveFileNames(): boolean; - getNewLine(): string; - } - - function createSourceFilesFromBundleBuildInfo(bundle: BundleBuildInfo, buildInfoDirectory: string, host: EmitUsingBuildInfoHost): readonly SourceFile[] { - const jsBundle = Debug.checkDefined(bundle.js); - const prologueMap = jsBundle.sources?.prologues && arrayToMap(jsBundle.sources.prologues, prologueInfo => prologueInfo.file); - return bundle.sourceFiles.map((fileName, index) => { - const prologueInfo = prologueMap?.get(index); - const statements = prologueInfo?.directives.map(directive => { - const literal = setTextRange(factory.createStringLiteral(directive.expression.text), directive.expression); - const statement = setTextRange(factory.createExpressionStatement(literal), directive); - setParent(literal, statement); - return statement; - }); - const eofToken = factory.createToken(SyntaxKind.EndOfFileToken); - const sourceFile = factory.createSourceFile(statements ?? [], eofToken, NodeFlags.None); - sourceFile.fileName = getRelativePathFromDirectory( - host.getCurrentDirectory(), - getNormalizedAbsolutePath(fileName, buildInfoDirectory), - !host.useCaseSensitiveFileNames() - ); - sourceFile.text = prologueInfo?.text ?? ""; - setTextRangePosWidth(sourceFile, 0, prologueInfo?.text.length ?? 0); - setEachParent(sourceFile.statements, sourceFile); - setTextRangePosWidth(eofToken, sourceFile.end, 0); - setParent(eofToken, sourceFile); - return sourceFile; - }); - } - - /*@internal*/ - export function emitUsingBuildInfo( - config: ParsedCommandLine, - host: EmitUsingBuildInfoHost, - getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined, - customTransformers?: CustomTransformers - ): EmitUsingBuildInfoResult { - const { buildInfoPath, jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath } = getOutputPathsForBundle(config.options, /*forceDtsPaths*/ false); - const buildInfoText = host.readFile(Debug.checkDefined(buildInfoPath)); - if (!buildInfoText) return buildInfoPath!; - const jsFileText = host.readFile(Debug.checkDefined(jsFilePath)); - if (!jsFileText) return jsFilePath!; - const sourceMapText = sourceMapFilePath && host.readFile(sourceMapFilePath); - // error if no source map or for now if inline sourcemap - if ((sourceMapFilePath && !sourceMapText) || config.options.inlineSourceMap) return sourceMapFilePath || "inline sourcemap decoding"; - // read declaration text - const declarationText = declarationFilePath && host.readFile(declarationFilePath); - if (declarationFilePath && !declarationText) return declarationFilePath; - const declarationMapText = declarationMapPath && host.readFile(declarationMapPath); - // error if no source map or for now if inline sourcemap - if ((declarationMapPath && !declarationMapText) || config.options.inlineSourceMap) return declarationMapPath || "inline sourcemap decoding"; - - const buildInfo = getBuildInfo(buildInfoText); - if (!buildInfo.bundle || !buildInfo.bundle.js || (declarationText && !buildInfo.bundle.dts)) return buildInfoPath!; - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath!, host.getCurrentDirectory())); - const ownPrependInput = createInputFiles( - jsFileText, - declarationText!, - sourceMapFilePath, - sourceMapText, - declarationMapPath, - declarationMapText, - jsFilePath, - declarationFilePath, - buildInfoPath, - buildInfo, - /*onlyOwnText*/ true - ); - const outputFiles: OutputFile[] = []; - const prependNodes = createPrependNodes(config.projectReferences, getCommandLine, f => host.readFile(f)); - const sourceFilesForJsEmit = createSourceFilesFromBundleBuildInfo(buildInfo.bundle, buildInfoDirectory, host); - const emitHost: EmitHost = { - getPrependNodes: memoize(() => [...prependNodes, ownPrependInput]), - getCanonicalFileName: host.getCanonicalFileName, - getCommonSourceDirectory: () => getNormalizedAbsolutePath(buildInfo.bundle!.commonSourceDirectory, buildInfoDirectory), - getCompilerOptions: () => config.options, - getCurrentDirectory: () => host.getCurrentDirectory(), - getNewLine: () => host.getNewLine(), - getSourceFile: returnUndefined, - getSourceFileByPath: returnUndefined, - getSourceFiles: () => sourceFilesForJsEmit, - getLibFileFromReference: notImplemented, - isSourceFileFromExternalLibrary: returnFalse, - getResolvedProjectReferenceToRedirect: returnUndefined, - getProjectReferenceRedirect: returnUndefined, - isSourceOfProjectReferenceRedirect: returnFalse, - writeFile: (name, text, writeByteOrderMark) => { - switch (name) { - case jsFilePath: - if (jsFileText === text) return; - break; - case sourceMapFilePath: - if (sourceMapText === text) return; - break; - case buildInfoPath: - const newBuildInfo = getBuildInfo(text); - newBuildInfo.program = buildInfo.program; - // Update sourceFileInfo - const { js, dts, sourceFiles } = buildInfo.bundle!; - newBuildInfo.bundle!.js!.sources = js!.sources; - if (dts) { - newBuildInfo.bundle!.dts!.sources = dts.sources; - } - newBuildInfo.bundle!.sourceFiles = sourceFiles; - outputFiles.push({ name, text: getBuildInfoText(newBuildInfo), writeByteOrderMark }); - return; - case declarationFilePath: - if (declarationText === text) return; - break; - case declarationMapPath: - if (declarationMapText === text) return; - break; - default: - Debug.fail(`Unexpected path: ${name}`); - } - outputFiles.push({ name, text, writeByteOrderMark }); - }, - isEmitBlocked: returnFalse, - readFile: f => host.readFile(f), - fileExists: f => host.fileExists(f), - useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), - getProgramBuildInfo: returnUndefined, - getSourceFileFromReference: returnUndefined, - redirectTargetsMap: createMultiMap(), - getFileIncludeReasons: notImplemented, - }; - emitFiles( - notImplementedResolver, - emitHost, - /*targetSourceFile*/ undefined, - getTransformers(config.options, customTransformers) - ); - return outputFiles; - } - - const enum PipelinePhase { - Notification, - Substitution, - Comments, - SourceMaps, - Emit, - } - - export function createPrinter(printerOptions: PrinterOptions = {}, handlers: PrintHandlers = {}): Printer { - const { - hasGlobalName, - onEmitNode = noEmitNotification, - isEmitNotificationEnabled, - substituteNode = noEmitSubstitution, - onBeforeEmitNode, - onAfterEmitNode, - onBeforeEmitNodeArray, - onAfterEmitNodeArray, - onBeforeEmitToken, - onAfterEmitToken - } = handlers; - - const extendedDiagnostics = !!printerOptions.extendedDiagnostics; - const newLine = getNewLineCharacter(printerOptions); - const moduleKind = getEmitModuleKind(printerOptions); - const bundledHelpers = new Map(); - let currentSourceFile: SourceFile | undefined; - let nodeIdToGeneratedName: string[]; // Map of generated names for specific nodes. - let autoGeneratedIdToGeneratedName: string[]; // Map of generated names for temp and loop variables. - let generatedNames: Set; // Set of names generated by the NameGenerator. - let tempFlagsStack: TempFlags[]; // Stack of enclosing name generation scopes. - let tempFlags: TempFlags; // TempFlags for the current name generation scope. - let reservedNamesStack: Set[]; // Stack of TempFlags reserved in enclosing name generation scopes. - let reservedNames: Set; // TempFlags to reserve in nested name generation scopes. - let preserveSourceNewlines = printerOptions.preserveSourceNewlines; // Can be overridden inside nodes with the `IgnoreSourceNewlines` emit flag. - let nextListElementPos: number | undefined; // See comment in `getLeadingLineTerminatorCount`. - - let writer: EmitTextWriter; - let ownWriter: EmitTextWriter; // Reusable `EmitTextWriter` for basic printing. - let write = writeBase; - let isOwnFileEmit: boolean; - const bundleFileInfo = printerOptions.writeBundleFileInfo ? { sections: [] } as BundleFileInfo : undefined; - const relativeToBuildInfo = bundleFileInfo ? Debug.checkDefined(printerOptions.relativeToBuildInfo) : undefined; - const recordInternalSection = printerOptions.recordInternalSection; - let sourceFileTextPos = 0; - let sourceFileTextKind: BundleFileTextLikeKind = BundleFileSectionKind.Text; - - // Source Maps - let sourceMapsDisabled = true; - let sourceMapGenerator: SourceMapGenerator | undefined; - let sourceMapSource: SourceMapSource; - let sourceMapSourceIndex = -1; - let mostRecentlyAddedSourceMapSource: SourceMapSource; - let mostRecentlyAddedSourceMapSourceIndex = -1; - - // Comments - let containerPos = -1; - let containerEnd = -1; - let declarationListContainerEnd = -1; - let currentLineMap: readonly number[] | undefined; - let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number }[] | undefined; - let hasWrittenComment = false; - let commentsDisabled = !!printerOptions.removeComments; - let lastSubstitution: Node | undefined; - let currentParenthesizerRule: ((node: Node) => Node) | undefined; - const { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment"); - const parenthesizer = factory.parenthesizer; - const emitBinaryExpression = createEmitBinaryExpression(); - - reset(); - return { - // public API - printNode, - printList, - printFile, - printBundle, - - // internal API - writeNode, - writeList, - writeFile, - writeBundle, - bundleFileInfo - }; + const sourceMappingURL = getSourceMappingURL( + mapOptions, + sourceMapGenerator, + jsFilePath, + sourceMapFilePath, + sourceFile); - function printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string { - switch (hint) { - case EmitHint.SourceFile: - Debug.assert(isSourceFile(node), "Expected a SourceFile node."); - break; - case EmitHint.IdentifierName: - Debug.assert(isIdentifier(node), "Expected an Identifier node."); - break; - case EmitHint.Expression: - Debug.assert(isExpression(node), "Expected an Expression node."); - break; + if (sourceMappingURL) { + if (!writer.isAtStartOfLine()) writer.rawWrite(newLine); + writer.writeComment(`//# ${"sourceMappingURL"}=${sourceMappingURL}`); // Tools can sometimes see this line as a source mapping url comment } - switch (node.kind) { - case SyntaxKind.SourceFile: return printFile(node as SourceFile); - case SyntaxKind.Bundle: return printBundle(node as Bundle); - case SyntaxKind.UnparsedSource: return printUnparsedSource(node as UnparsedSource); + + // Write the source map + if (sourceMapFilePath) { + const sourceMap = sourceMapGenerator.toString(); + writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap, /*writeByteOrderMark*/ false, sourceFiles); } - writeNode(hint, node, sourceFile, beginPrint()); - return endPrint(); } - - function printList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile) { - writeList(format, nodes, sourceFile, beginPrint()); - return endPrint(); + else { + writer.writeLine(); } - function printBundle(bundle: Bundle): string { - writeBundle(bundle, beginPrint(), /*sourceMapEmitter*/ undefined); - return endPrint(); - } + // Write the output file + writeFile(host, emitterDiagnostics, jsFilePath, writer.getText(), !!compilerOptions.emitBOM, sourceFiles); - function printFile(sourceFile: SourceFile): string { - writeFile(sourceFile, beginPrint(), /*sourceMapEmitter*/ undefined); - return endPrint(); - } + // Reset state + writer.clear(); + } - function printUnparsedSource(unparsed: UnparsedSource): string { - writeUnparsedSource(unparsed, beginPrint()); - return endPrint(); - } + interface SourceMapOptions { + sourceMap?: boolean; + inlineSourceMap?: boolean; + inlineSources?: boolean; + sourceRoot?: string; + mapRoot?: string; + extendedDiagnostics?: boolean; + } - /** - * If `sourceFile` is `undefined`, `node` must be a synthesized `TypeNode`. - */ - function writeNode(hint: EmitHint, node: TypeNode, sourceFile: undefined, output: EmitTextWriter): void; - function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile, output: EmitTextWriter): void; - function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined, output: EmitTextWriter) { - const previousWriter = writer; - setWriter(output, /*_sourceMapGenerator*/ undefined); - print(hint, node, sourceFile); - reset(); - writer = previousWriter; - } + function shouldEmitSourceMaps(mapOptions: SourceMapOptions, sourceFileOrBundle: SourceFile | Bundle) { + return (mapOptions.sourceMap || mapOptions.inlineSourceMap) + && (sourceFileOrBundle.kind !== SyntaxKind.SourceFile || !fileExtensionIs(sourceFileOrBundle.fileName, Extension.Json)); + } + + function getSourceRoot(mapOptions: SourceMapOptions) { + // Normalize source root and make sure it has trailing "/" so that it can be used to combine paths with the + // relative paths of the sources list in the sourcemap + const sourceRoot = normalizeSlashes(mapOptions.sourceRoot || ""); + return sourceRoot ? ensureTrailingDirectorySeparator(sourceRoot) : sourceRoot; + } - function writeList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile | undefined, output: EmitTextWriter) { - const previousWriter = writer; - setWriter(output, /*_sourceMapGenerator*/ undefined); + function getSourceMapDirectory(mapOptions: SourceMapOptions, filePath: string, sourceFile: SourceFile | undefined) { + if (mapOptions.sourceRoot) return host.getCommonSourceDirectory(); + if (mapOptions.mapRoot) { + let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); if (sourceFile) { - setSourceFile(sourceFile); + // For modules or multiple emit files the mapRoot will have directory structure like the sources + // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map + sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); } - emitList(/*parentNode*/ undefined, nodes, format); - reset(); - writer = previousWriter; + if (getRootLength(sourceMapDir) === 0) { + // The relative paths are relative to the common directory + sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); + } + return sourceMapDir; } + return getDirectoryPath(normalizePath(filePath)); + } - function getTextPosWithWriteLine() { - return writer.getTextPosWithWriteLine ? writer.getTextPosWithWriteLine() : writer.getTextPos(); + function getSourceMappingURL(mapOptions: SourceMapOptions, sourceMapGenerator: SourceMapGenerator, filePath: string, sourceMapFilePath: string | undefined, sourceFile: SourceFile | undefined) { + if (mapOptions.inlineSourceMap) { + // Encode the sourceMap into the sourceMap url + const sourceMapText = sourceMapGenerator.toString(); + const base64SourceMapText = base64encode(sys, sourceMapText); + return `data:application/json;base64,${base64SourceMapText}`; } - function updateOrPushBundleFileTextLike(pos: number, end: number, kind: BundleFileTextLikeKind) { - const last = lastOrUndefined(bundleFileInfo!.sections); - if (last && last.kind === kind) { - last.end = end; + const sourceMapFile = getBaseFileName(normalizeSlashes(Debug.checkDefined(sourceMapFilePath))); + if (mapOptions.mapRoot) { + let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); + if (sourceFile) { + // For modules or multiple emit files the mapRoot will have directory structure like the sources + // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map + sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); + } + if (getRootLength(sourceMapDir) === 0) { + // The relative paths are relative to the common directory + sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); + return encodeURI( + getRelativePathToDirectoryOrUrl( + getDirectoryPath(normalizePath(filePath)), // get the relative sourceMapDir path based on jsFilePath + combinePaths(sourceMapDir, sourceMapFile), // this is where user expects to see sourceMap + host.getCurrentDirectory(), + host.getCanonicalFileName, + /*isAbsolutePathAnUrl*/ true)); } else { - bundleFileInfo!.sections.push({ pos, end, kind }); - } - } - - function recordBundleFileInternalSectionStart(node: Node) { - if (recordInternalSection && - bundleFileInfo && - currentSourceFile && - (isDeclaration(node) || isVariableStatement(node)) && - isInternalDeclaration(node, currentSourceFile) && - sourceFileTextKind !== BundleFileSectionKind.Internal) { - const prevSourceFileTextKind = sourceFileTextKind; - recordBundleFileTextLikeSection(writer.getTextPos()); - sourceFileTextPos = getTextPosWithWriteLine(); - sourceFileTextKind = BundleFileSectionKind.Internal; - return prevSourceFileTextKind; + return encodeURI(combinePaths(sourceMapDir, sourceMapFile)); } - return undefined; } + return encodeURI(sourceMapFile); + } +} - function recordBundleFileInternalSectionEnd(prevSourceFileTextKind: ReturnType) { - if (prevSourceFileTextKind) { - recordBundleFileTextLikeSection(writer.getTextPos()); - sourceFileTextPos = getTextPosWithWriteLine(); - sourceFileTextKind = prevSourceFileTextKind; - } - } +/*@internal*/ +export function getBuildInfoText(buildInfo: BuildInfo) { + return JSON.stringify(buildInfo); +} - function recordBundleFileTextLikeSection(end: number) { - if (sourceFileTextPos < end) { - updateOrPushBundleFileTextLike(sourceFileTextPos, end, sourceFileTextKind); - return true; - } - return false; - } +/*@internal*/ +export function getBuildInfo(buildInfoText: string) { + return JSON.parse(buildInfoText) as BuildInfo; +} - function writeBundle(bundle: Bundle, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { - isOwnFileEmit = false; - const previousWriter = writer; - setWriter(output, sourceMapGenerator); - emitShebangIfNeeded(bundle); - emitPrologueDirectivesIfNeeded(bundle); - emitHelpers(bundle); - emitSyntheticTripleSlashReferencesIfNeeded(bundle); +/*@internal*/ +export const notImplementedResolver: EmitResolver = { + hasGlobalName: notImplemented, + getReferencedExportContainer: notImplemented, + getReferencedImportDeclaration: notImplemented, + getReferencedDeclarationWithCollidingName: notImplemented, + isDeclarationWithCollidingName: notImplemented, + isValueAliasDeclaration: notImplemented, + isReferencedAliasDeclaration: notImplemented, + isTopLevelValueImportEqualsWithEntityName: notImplemented, + getNodeCheckFlags: notImplemented, + isDeclarationVisible: notImplemented, + isLateBound: (_node): _node is LateBoundDeclaration => false, + collectLinkedAliases: notImplemented, + isImplementationOfOverload: notImplemented, + isRequiredInitializedParameter: notImplemented, + isOptionalUninitializedParameterProperty: notImplemented, + isExpandoFunctionDeclaration: notImplemented, + getPropertiesOfContainerFunction: notImplemented, + createTypeOfDeclaration: notImplemented, + createReturnTypeOfSignatureDeclaration: notImplemented, + createTypeOfExpression: notImplemented, + createLiteralConstValue: notImplemented, + isSymbolAccessible: notImplemented, + isEntityNameVisible: notImplemented, + // Returns the constant value this property access resolves to: notImplemented, or 'undefined' for a non-constant + getConstantValue: notImplemented, + getReferencedValueDeclaration: notImplemented, + getTypeReferenceSerializationKind: notImplemented, + isOptionalParameter: notImplemented, + moduleExportsSomeValue: notImplemented, + isArgumentsLocalBinding: notImplemented, + getExternalModuleFileFromDeclaration: notImplemented, + getTypeReferenceDirectivesForEntityName: notImplemented, + getTypeReferenceDirectivesForSymbol: notImplemented, + isLiteralConstDeclaration: notImplemented, + getJsxFactoryEntity: notImplemented, + getJsxFragmentFactoryEntity: notImplemented, + getAllAccessorDeclarations: notImplemented, + getSymbolOfExternalModuleSpecifier: notImplemented, + isBindingCapturedByNode: notImplemented, + getDeclarationStatementsForSourceFile: notImplemented, + isImportRequiredByAugmentation: notImplemented, +}; + +/*@internal*/ +/** File that isnt present resulting in error or output files */ +export type EmitUsingBuildInfoResult = string | readonly OutputFile[]; + +/*@internal*/ +export interface EmitUsingBuildInfoHost extends ModuleResolutionHost { + getCurrentDirectory(): string; + getCanonicalFileName(fileName: string): string; + useCaseSensitiveFileNames(): boolean; + getNewLine(): string; +} - for (const prepend of bundle.prepends) { - writeLine(); - const pos = writer.getTextPos(); - const savedSections = bundleFileInfo && bundleFileInfo.sections; - if (savedSections) bundleFileInfo!.sections = []; - print(EmitHint.Unspecified, prepend, /*sourceFile*/ undefined); - if (bundleFileInfo) { - const newSections = bundleFileInfo.sections; - bundleFileInfo.sections = savedSections!; - if (prepend.oldFileOfCurrentEmit) bundleFileInfo.sections.push(...newSections); - else { - newSections.forEach(section => Debug.assert(isBundleFileTextLike(section))); - bundleFileInfo.sections.push({ - pos, - end: writer.getTextPos(), - kind: BundleFileSectionKind.Prepend, - data: relativeToBuildInfo!((prepend as UnparsedSource).fileName), - texts: newSections as BundleFileTextLike[] - }); - } - } - } +function createSourceFilesFromBundleBuildInfo(bundle: BundleBuildInfo, buildInfoDirectory: string, host: EmitUsingBuildInfoHost): readonly SourceFile[] { + const jsBundle = Debug.checkDefined(bundle.js); + const prologueMap = jsBundle.sources?.prologues && arrayToMap(jsBundle.sources.prologues, prologueInfo => prologueInfo.file); + return bundle.sourceFiles.map((fileName, index) => { + const prologueInfo = prologueMap?.get(index); + const statements = prologueInfo?.directives.map(directive => { + const literal = setTextRange(factory.createStringLiteral(directive.expression.text), directive.expression); + const statement = setTextRange(factory.createExpressionStatement(literal), directive); + setParent(literal, statement); + return statement; + }); + const eofToken = factory.createToken(SyntaxKind.EndOfFileToken); + const sourceFile = factory.createSourceFile(statements ?? [], eofToken, NodeFlags.None); + sourceFile.fileName = getRelativePathFromDirectory( + host.getCurrentDirectory(), + getNormalizedAbsolutePath(fileName, buildInfoDirectory), + !host.useCaseSensitiveFileNames() + ); + sourceFile.text = prologueInfo?.text ?? ""; + setTextRangePosWidth(sourceFile, 0, prologueInfo?.text.length ?? 0); + setEachParent(sourceFile.statements, sourceFile); + setTextRangePosWidth(eofToken, sourceFile.end, 0); + setParent(eofToken, sourceFile); + return sourceFile; + }); +} - sourceFileTextPos = getTextPosWithWriteLine(); - for (const sourceFile of bundle.sourceFiles) { - print(EmitHint.SourceFile, sourceFile, sourceFile); - } - if (bundleFileInfo && bundle.sourceFiles.length) { - const end = writer.getTextPos(); - if (recordBundleFileTextLikeSection(end)) { - // Store prologues - const prologues = getPrologueDirectivesFromBundledSourceFiles(bundle); - if (prologues) { - if (!bundleFileInfo.sources) bundleFileInfo.sources = {}; - bundleFileInfo.sources.prologues = prologues; +/*@internal*/ +export function emitUsingBuildInfo( + config: ParsedCommandLine, + host: EmitUsingBuildInfoHost, + getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined, + customTransformers?: CustomTransformers +): EmitUsingBuildInfoResult { + const { buildInfoPath, jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath } = getOutputPathsForBundle(config.options, /*forceDtsPaths*/ false); + const buildInfoText = host.readFile(Debug.checkDefined(buildInfoPath)); + if (!buildInfoText) return buildInfoPath!; + const jsFileText = host.readFile(Debug.checkDefined(jsFilePath)); + if (!jsFileText) return jsFilePath!; + const sourceMapText = sourceMapFilePath && host.readFile(sourceMapFilePath); + // error if no source map or for now if inline sourcemap + if ((sourceMapFilePath && !sourceMapText) || config.options.inlineSourceMap) return sourceMapFilePath || "inline sourcemap decoding"; + // read declaration text + const declarationText = declarationFilePath && host.readFile(declarationFilePath); + if (declarationFilePath && !declarationText) return declarationFilePath; + const declarationMapText = declarationMapPath && host.readFile(declarationMapPath); + // error if no source map or for now if inline sourcemap + if ((declarationMapPath && !declarationMapText) || config.options.inlineSourceMap) return declarationMapPath || "inline sourcemap decoding"; + + const buildInfo = getBuildInfo(buildInfoText); + if (!buildInfo.bundle || !buildInfo.bundle.js || (declarationText && !buildInfo.bundle.dts)) return buildInfoPath!; + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath!, host.getCurrentDirectory())); + const ownPrependInput = createInputFiles( + jsFileText, + declarationText!, + sourceMapFilePath, + sourceMapText, + declarationMapPath, + declarationMapText, + jsFilePath, + declarationFilePath, + buildInfoPath, + buildInfo, + /*onlyOwnText*/ true + ); + const outputFiles: OutputFile[] = []; + const prependNodes = createPrependNodes(config.projectReferences, getCommandLine, f => host.readFile(f)); + const sourceFilesForJsEmit = createSourceFilesFromBundleBuildInfo(buildInfo.bundle, buildInfoDirectory, host); + const emitHost: EmitHost = { + getPrependNodes: memoize(() => [...prependNodes, ownPrependInput]), + getCanonicalFileName: host.getCanonicalFileName, + getCommonSourceDirectory: () => getNormalizedAbsolutePath(buildInfo.bundle!.commonSourceDirectory, buildInfoDirectory), + getCompilerOptions: () => config.options, + getCurrentDirectory: () => host.getCurrentDirectory(), + getNewLine: () => host.getNewLine(), + getSourceFile: returnUndefined, + getSourceFileByPath: returnUndefined, + getSourceFiles: () => sourceFilesForJsEmit, + getLibFileFromReference: notImplemented, + isSourceFileFromExternalLibrary: returnFalse, + getResolvedProjectReferenceToRedirect: returnUndefined, + getProjectReferenceRedirect: returnUndefined, + isSourceOfProjectReferenceRedirect: returnFalse, + writeFile: (name, text, writeByteOrderMark) => { + switch (name) { + case jsFilePath: + if (jsFileText === text) return; + break; + case sourceMapFilePath: + if (sourceMapText === text) return; + break; + case buildInfoPath: + const newBuildInfo = getBuildInfo(text); + newBuildInfo.program = buildInfo.program; + // Update sourceFileInfo + const { js, dts, sourceFiles } = buildInfo.bundle!; + newBuildInfo.bundle!.js!.sources = js!.sources; + if (dts) { + newBuildInfo.bundle!.dts!.sources = dts.sources; } + newBuildInfo.bundle!.sourceFiles = sourceFiles; + outputFiles.push({ name, text: getBuildInfoText(newBuildInfo), writeByteOrderMark }); + return; + case declarationFilePath: + if (declarationText === text) return; + break; + case declarationMapPath: + if (declarationMapText === text) return; + break; + default: + Debug.fail(`Unexpected path: ${name}`); + } + outputFiles.push({ name, text, writeByteOrderMark }); + }, + isEmitBlocked: returnFalse, + readFile: f => host.readFile(f), + fileExists: f => host.fileExists(f), + useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), + getProgramBuildInfo: returnUndefined, + getSourceFileFromReference: returnUndefined, + redirectTargetsMap: createMultiMap(), + getFileIncludeReasons: notImplemented, + }; + emitFiles( + notImplementedResolver, + emitHost, + /*targetSourceFile*/ undefined, + getTransformers(config.options, customTransformers) + ); + return outputFiles; +} - // Store helpes - const helpers = getHelpersFromBundledSourceFiles(bundle); - if (helpers) { - if (!bundleFileInfo.sources) bundleFileInfo.sources = {}; - bundleFileInfo.sources.helpers = helpers; - } - } - } +const enum PipelinePhase { + Notification, + Substitution, + Comments, + SourceMaps, + Emit, +} - reset(); - writer = previousWriter; - } +export function createPrinter(printerOptions: PrinterOptions = {}, handlers: PrintHandlers = {}): Printer { + const { + hasGlobalName, + onEmitNode = noEmitNotification, + isEmitNotificationEnabled, + substituteNode = noEmitSubstitution, + onBeforeEmitNode, + onAfterEmitNode, + onBeforeEmitNodeArray, + onAfterEmitNodeArray, + onBeforeEmitToken, + onAfterEmitToken + } = handlers; + + const extendedDiagnostics = !!printerOptions.extendedDiagnostics; + const newLine = getNewLineCharacter(printerOptions); + const moduleKind = getEmitModuleKind(printerOptions); + const bundledHelpers = new Map(); + + let currentSourceFile: SourceFile | undefined; + let nodeIdToGeneratedName: string[]; // Map of generated names for specific nodes. + let autoGeneratedIdToGeneratedName: string[]; // Map of generated names for temp and loop variables. + let generatedNames: Set; // Set of names generated by the NameGenerator. + let tempFlagsStack: TempFlags[]; // Stack of enclosing name generation scopes. + let tempFlags: TempFlags; // TempFlags for the current name generation scope. + let reservedNamesStack: Set[]; // Stack of TempFlags reserved in enclosing name generation scopes. + let reservedNames: Set; // TempFlags to reserve in nested name generation scopes. + let preserveSourceNewlines = printerOptions.preserveSourceNewlines; // Can be overridden inside nodes with the `IgnoreSourceNewlines` emit flag. + let nextListElementPos: number | undefined; // See comment in `getLeadingLineTerminatorCount`. + + let writer: EmitTextWriter; + let ownWriter: EmitTextWriter; // Reusable `EmitTextWriter` for basic printing. + let write = writeBase; + let isOwnFileEmit: boolean; + const bundleFileInfo = printerOptions.writeBundleFileInfo ? { sections: [] } as BundleFileInfo : undefined; + const relativeToBuildInfo = bundleFileInfo ? Debug.checkDefined(printerOptions.relativeToBuildInfo) : undefined; + const recordInternalSection = printerOptions.recordInternalSection; + let sourceFileTextPos = 0; + let sourceFileTextKind: BundleFileTextLikeKind = BundleFileSectionKind.Text; + + // Source Maps + let sourceMapsDisabled = true; + let sourceMapGenerator: SourceMapGenerator | undefined; + let sourceMapSource: SourceMapSource; + let sourceMapSourceIndex = -1; + let mostRecentlyAddedSourceMapSource: SourceMapSource; + let mostRecentlyAddedSourceMapSourceIndex = -1; + + // Comments + let containerPos = -1; + let containerEnd = -1; + let declarationListContainerEnd = -1; + let currentLineMap: readonly number[] | undefined; + let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number }[] | undefined; + let hasWrittenComment = false; + let commentsDisabled = !!printerOptions.removeComments; + let lastSubstitution: Node | undefined; + let currentParenthesizerRule: ((node: Node) => Node) | undefined; + const { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment"); + const parenthesizer = factory.parenthesizer; + const emitBinaryExpression = createEmitBinaryExpression(); + + reset(); + return { + // public API + printNode, + printList, + printFile, + printBundle, + + // internal API + writeNode, + writeList, + writeFile, + writeBundle, + bundleFileInfo + }; - function writeUnparsedSource(unparsed: UnparsedSource, output: EmitTextWriter) { - const previousWriter = writer; - setWriter(output, /*_sourceMapGenerator*/ undefined); - print(EmitHint.Unspecified, unparsed, /*sourceFile*/ undefined); - reset(); - writer = previousWriter; - } + function printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string { + switch (hint) { + case EmitHint.SourceFile: + Debug.assert(isSourceFile(node), "Expected a SourceFile node."); + break; + case EmitHint.IdentifierName: + Debug.assert(isIdentifier(node), "Expected an Identifier node."); + break; + case EmitHint.Expression: + Debug.assert(isExpression(node), "Expected an Expression node."); + break; + } + switch (node.kind) { + case SyntaxKind.SourceFile: return printFile(node as SourceFile); + case SyntaxKind.Bundle: return printBundle(node as Bundle); + case SyntaxKind.UnparsedSource: return printUnparsedSource(node as UnparsedSource); + } + writeNode(hint, node, sourceFile, beginPrint()); + return endPrint(); + } - function writeFile(sourceFile: SourceFile, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { - isOwnFileEmit = true; - const previousWriter = writer; - setWriter(output, sourceMapGenerator); - emitShebangIfNeeded(sourceFile); - emitPrologueDirectivesIfNeeded(sourceFile); - print(EmitHint.SourceFile, sourceFile, sourceFile); - reset(); - writer = previousWriter; - } + function printList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile) { + writeList(format, nodes, sourceFile, beginPrint()); + return endPrint(); + } - function beginPrint() { - return ownWriter || (ownWriter = createTextWriter(newLine)); - } + function printBundle(bundle: Bundle): string { + writeBundle(bundle, beginPrint(), /*sourceMapEmitter*/ undefined); + return endPrint(); + } - function endPrint() { - const text = ownWriter.getText(); - ownWriter.clear(); - return text; - } + function printFile(sourceFile: SourceFile): string { + writeFile(sourceFile, beginPrint(), /*sourceMapEmitter*/ undefined); + return endPrint(); + } - function print(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined) { - if (sourceFile) { - setSourceFile(sourceFile); - } + function printUnparsedSource(unparsed: UnparsedSource): string { + writeUnparsedSource(unparsed, beginPrint()); + return endPrint(); + } - pipelineEmit(hint, node, /*parenthesizerRule*/ undefined); - } + /** + * If `sourceFile` is `undefined`, `node` must be a synthesized `TypeNode`. + */ + function writeNode(hint: EmitHint, node: TypeNode, sourceFile: undefined, output: EmitTextWriter): void; + function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile, output: EmitTextWriter): void; + function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined, output: EmitTextWriter) { + const previousWriter = writer; + setWriter(output, /*_sourceMapGenerator*/ undefined); + print(hint, node, sourceFile); + reset(); + writer = previousWriter; + } - function setSourceFile(sourceFile: SourceFile | undefined) { - currentSourceFile = sourceFile; - currentLineMap = undefined; - detachedCommentsInfo = undefined; - if (sourceFile) { - setSourceMapSource(sourceFile); - } + function writeList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile | undefined, output: EmitTextWriter) { + const previousWriter = writer; + setWriter(output, /*_sourceMapGenerator*/ undefined); + if (sourceFile) { + setSourceFile(sourceFile); } + emitList(/*parentNode*/ undefined, nodes, format); + reset(); + writer = previousWriter; + } - function setWriter(_writer: EmitTextWriter | undefined, _sourceMapGenerator: SourceMapGenerator | undefined) { - if (_writer && printerOptions.omitTrailingSemicolon) { - _writer = getTrailingSemicolonDeferringWriter(_writer); - } + function getTextPosWithWriteLine() { + return writer.getTextPosWithWriteLine ? writer.getTextPosWithWriteLine() : writer.getTextPos(); + } - writer = _writer!; // TODO: GH#18217 - sourceMapGenerator = _sourceMapGenerator; - sourceMapsDisabled = !writer || !sourceMapGenerator; + function updateOrPushBundleFileTextLike(pos: number, end: number, kind: BundleFileTextLikeKind) { + const last = lastOrUndefined(bundleFileInfo!.sections); + if (last && last.kind === kind) { + last.end = end; } - - function reset() { - nodeIdToGeneratedName = []; - autoGeneratedIdToGeneratedName = []; - generatedNames = new Set(); - tempFlagsStack = []; - tempFlags = TempFlags.Auto; - reservedNamesStack = []; - currentSourceFile = undefined; - currentLineMap = undefined; - detachedCommentsInfo = undefined; - setWriter(/*output*/ undefined, /*_sourceMapGenerator*/ undefined); + else { + bundleFileInfo!.sections.push({ pos, end, kind }); } + } - function getCurrentLineMap() { - return currentLineMap || (currentLineMap = getLineStarts(currentSourceFile!)); + function recordBundleFileInternalSectionStart(node: Node) { + if (recordInternalSection && + bundleFileInfo && + currentSourceFile && + (isDeclaration(node) || isVariableStatement(node)) && + isInternalDeclaration(node, currentSourceFile) && + sourceFileTextKind !== BundleFileSectionKind.Internal) { + const prevSourceFileTextKind = sourceFileTextKind; + recordBundleFileTextLikeSection(writer.getTextPos()); + sourceFileTextPos = getTextPosWithWriteLine(); + sourceFileTextKind = BundleFileSectionKind.Internal; + return prevSourceFileTextKind; } + return undefined; + } - function emit(node: Node, parenthesizerRule?: (node: Node) => Node): void; - function emit(node: Node | undefined, parenthesizerRule?: (node: Node) => Node): void; - function emit(node: Node | undefined, parenthesizerRule?: (node: Node) => Node) { - if (node === undefined) return; - const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node); - pipelineEmit(EmitHint.Unspecified, node, parenthesizerRule); - recordBundleFileInternalSectionEnd(prevSourceFileTextKind); + function recordBundleFileInternalSectionEnd(prevSourceFileTextKind: ReturnType) { + if (prevSourceFileTextKind) { + recordBundleFileTextLikeSection(writer.getTextPos()); + sourceFileTextPos = getTextPosWithWriteLine(); + sourceFileTextKind = prevSourceFileTextKind; } + } - function emitIdentifierName(node: Identifier): void; - function emitIdentifierName(node: Identifier | undefined): void; - function emitIdentifierName(node: Identifier | undefined) { - if (node === undefined) return; - pipelineEmit(EmitHint.IdentifierName, node, /*parenthesizerRule*/ undefined); + function recordBundleFileTextLikeSection(end: number) { + if (sourceFileTextPos < end) { + updateOrPushBundleFileTextLike(sourceFileTextPos, end, sourceFileTextKind); + return true; } + return false; + } + + function writeBundle(bundle: Bundle, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { + isOwnFileEmit = false; + const previousWriter = writer; + setWriter(output, sourceMapGenerator); + emitShebangIfNeeded(bundle); + emitPrologueDirectivesIfNeeded(bundle); + emitHelpers(bundle); + emitSyntheticTripleSlashReferencesIfNeeded(bundle); - function emitExpression(node: Expression, parenthesizerRule?: (node: Expression) => Expression): void; - function emitExpression(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression): void; - function emitExpression(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression) { - if (node === undefined) return; - pipelineEmit(EmitHint.Expression, node, parenthesizerRule); + for (const prepend of bundle.prepends) { + writeLine(); + const pos = writer.getTextPos(); + const savedSections = bundleFileInfo && bundleFileInfo.sections; + if (savedSections) bundleFileInfo!.sections = []; + print(EmitHint.Unspecified, prepend, /*sourceFile*/ undefined); + if (bundleFileInfo) { + const newSections = bundleFileInfo.sections; + bundleFileInfo.sections = savedSections!; + if (prepend.oldFileOfCurrentEmit) bundleFileInfo.sections.push(...newSections); + else { + newSections.forEach(section => Debug.assert(isBundleFileTextLike(section))); + bundleFileInfo.sections.push({ + pos, + end: writer.getTextPos(), + kind: BundleFileSectionKind.Prepend, + data: relativeToBuildInfo!((prepend as UnparsedSource).fileName), + texts: newSections as BundleFileTextLike[] + }); + } + } } - function emitJsxAttributeValue(node: StringLiteral | JsxExpression): void { - pipelineEmit(isStringLiteral(node) ? EmitHint.JsxAttributeValue : EmitHint.Unspecified, node); + sourceFileTextPos = getTextPosWithWriteLine(); + for (const sourceFile of bundle.sourceFiles) { + print(EmitHint.SourceFile, sourceFile, sourceFile); } + if (bundleFileInfo && bundle.sourceFiles.length) { + const end = writer.getTextPos(); + if (recordBundleFileTextLikeSection(end)) { + // Store prologues + const prologues = getPrologueDirectivesFromBundledSourceFiles(bundle); + if (prologues) { + if (!bundleFileInfo.sources) bundleFileInfo.sources = {}; + bundleFileInfo.sources.prologues = prologues; + } - function beforeEmitNode(node: Node) { - if (preserveSourceNewlines && (getEmitFlags(node) & EmitFlags.IgnoreSourceNewlines)) { - preserveSourceNewlines = false; + // Store helpes + const helpers = getHelpersFromBundledSourceFiles(bundle); + if (helpers) { + if (!bundleFileInfo.sources) bundleFileInfo.sources = {}; + bundleFileInfo.sources.helpers = helpers; + } } } - function afterEmitNode(savedPreserveSourceNewlines: boolean | undefined) { - preserveSourceNewlines = savedPreserveSourceNewlines; - } + reset(); + writer = previousWriter; + } - function pipelineEmit(emitHint: EmitHint, node: Node, parenthesizerRule?: (node: Node) => Node) { - currentParenthesizerRule = parenthesizerRule; - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, emitHint, node); - pipelinePhase(emitHint, node); - currentParenthesizerRule = undefined; - } + function writeUnparsedSource(unparsed: UnparsedSource, output: EmitTextWriter) { + const previousWriter = writer; + setWriter(output, /*_sourceMapGenerator*/ undefined); + print(EmitHint.Unspecified, unparsed, /*sourceFile*/ undefined); + reset(); + writer = previousWriter; + } - function shouldEmitComments(node: Node) { - return !commentsDisabled && !isSourceFile(node); - } + function writeFile(sourceFile: SourceFile, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { + isOwnFileEmit = true; + const previousWriter = writer; + setWriter(output, sourceMapGenerator); + emitShebangIfNeeded(sourceFile); + emitPrologueDirectivesIfNeeded(sourceFile); + print(EmitHint.SourceFile, sourceFile, sourceFile); + reset(); + writer = previousWriter; + } - function shouldEmitSourceMaps(node: Node) { - return !sourceMapsDisabled && - !isSourceFile(node) && - !isInJsonFile(node) && - !isUnparsedSource(node) && - !isUnparsedPrepend(node); - } + function beginPrint() { + return ownWriter || (ownWriter = createTextWriter(newLine)); + } - function getPipelinePhase(phase: PipelinePhase, emitHint: EmitHint, node: Node) { - switch (phase) { - case PipelinePhase.Notification: - if (onEmitNode !== noEmitNotification && (!isEmitNotificationEnabled || isEmitNotificationEnabled(node))) { - return pipelineEmitWithNotification; - } - // falls through - case PipelinePhase.Substitution: - if (substituteNode !== noEmitSubstitution && (lastSubstitution = substituteNode(emitHint, node) || node) !== node) { - if (currentParenthesizerRule) { - lastSubstitution = currentParenthesizerRule(lastSubstitution); - } - return pipelineEmitWithSubstitution; - } - // falls through - case PipelinePhase.Comments: - if (shouldEmitComments(node)) { - return pipelineEmitWithComments; - } - // falls through - case PipelinePhase.SourceMaps: - if (shouldEmitSourceMaps(node)) { - return pipelineEmitWithSourceMaps; - } - // falls through - case PipelinePhase.Emit: - return pipelineEmitWithHint; - default: - return Debug.assertNever(phase); - } + function endPrint() { + const text = ownWriter.getText(); + ownWriter.clear(); + return text; + } + + function print(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined) { + if (sourceFile) { + setSourceFile(sourceFile); } - function getNextPipelinePhase(currentPhase: PipelinePhase, emitHint: EmitHint, node: Node) { - return getPipelinePhase(currentPhase + 1, emitHint, node); + pipelineEmit(hint, node, /*parenthesizerRule*/ undefined); + } + + function setSourceFile(sourceFile: SourceFile | undefined) { + currentSourceFile = sourceFile; + currentLineMap = undefined; + detachedCommentsInfo = undefined; + if (sourceFile) { + setSourceMapSource(sourceFile); } + } - function pipelineEmitWithNotification(hint: EmitHint, node: Node) { - const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, hint, node); - onEmitNode(hint, node, pipelinePhase); + function setWriter(_writer: EmitTextWriter | undefined, _sourceMapGenerator: SourceMapGenerator | undefined) { + if (_writer && printerOptions.omitTrailingSemicolon) { + _writer = getTrailingSemicolonDeferringWriter(_writer); } - function pipelineEmitWithHint(hint: EmitHint, node: Node): void { - onBeforeEmitNode?.(node); - if (preserveSourceNewlines) { - const savedPreserveSourceNewlines = preserveSourceNewlines; - beforeEmitNode(node); - pipelineEmitWithHintWorker(hint, node); - afterEmitNode(savedPreserveSourceNewlines); - } - else { - pipelineEmitWithHintWorker(hint, node); - } - onAfterEmitNode?.(node); - // clear the parenthesizer rule as we ascend - currentParenthesizerRule = undefined; + writer = _writer!; // TODO: GH#18217 + sourceMapGenerator = _sourceMapGenerator; + sourceMapsDisabled = !writer || !sourceMapGenerator; + } + + function reset() { + nodeIdToGeneratedName = []; + autoGeneratedIdToGeneratedName = []; + generatedNames = new Set(); + tempFlagsStack = []; + tempFlags = TempFlags.Auto; + reservedNamesStack = []; + currentSourceFile = undefined; + currentLineMap = undefined; + detachedCommentsInfo = undefined; + setWriter(/*output*/ undefined, /*_sourceMapGenerator*/ undefined); + } + + function getCurrentLineMap() { + return currentLineMap || (currentLineMap = getLineStarts(currentSourceFile!)); + } + + function emit(node: Node, parenthesizerRule?: (node: Node) => Node): void; + function emit(node: Node | undefined, parenthesizerRule?: (node: Node) => Node): void; + function emit(node: Node | undefined, parenthesizerRule?: (node: Node) => Node) { + if (node === undefined) return; + const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node); + pipelineEmit(EmitHint.Unspecified, node, parenthesizerRule); + recordBundleFileInternalSectionEnd(prevSourceFileTextKind); + } + + function emitIdentifierName(node: Identifier): void; + function emitIdentifierName(node: Identifier | undefined): void; + function emitIdentifierName(node: Identifier | undefined) { + if (node === undefined) return; + pipelineEmit(EmitHint.IdentifierName, node, /*parenthesizerRule*/ undefined); + } + + function emitExpression(node: Expression, parenthesizerRule?: (node: Expression) => Expression): void; + function emitExpression(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression): void; + function emitExpression(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression) { + if (node === undefined) return; + pipelineEmit(EmitHint.Expression, node, parenthesizerRule); + } + + function emitJsxAttributeValue(node: StringLiteral | JsxExpression): void { + pipelineEmit(isStringLiteral(node) ? EmitHint.JsxAttributeValue : EmitHint.Unspecified, node); + } + + function beforeEmitNode(node: Node) { + if (preserveSourceNewlines && (getEmitFlags(node) & EmitFlags.IgnoreSourceNewlines)) { + preserveSourceNewlines = false; } + } - function pipelineEmitWithHintWorker(hint: EmitHint, node: Node, allowSnippets = true): void { - if (allowSnippets) { - const snippet = getSnippetElement(node); - if (snippet) { - return emitSnippetNode(hint, node, snippet); - } - } - if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile)); - if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier)); - if (hint === EmitHint.JsxAttributeValue) return emitLiteral(cast(node, isStringLiteral), /*jsxAttributeEscape*/ true); - if (hint === EmitHint.MappedTypeParameter) return emitMappedTypeParameter(cast(node, isTypeParameterDeclaration)); - if (hint === EmitHint.EmbeddedStatement) { - Debug.assertNode(node, isEmptyStatement); - return emitEmptyStatement(/*isEmbeddedStatement*/ true); - } - if (hint === EmitHint.Unspecified) { - switch (node.kind) { - // Pseudo-literals - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: - return emitLiteral(node as LiteralExpression, /*jsxAttributeEscape*/ false); - - // Identifiers - case SyntaxKind.Identifier: - return emitIdentifier(node as Identifier); - - // PrivateIdentifiers - case SyntaxKind.PrivateIdentifier: - return emitPrivateIdentifier(node as PrivateIdentifier); - - // Parse tree nodes - // Names - case SyntaxKind.QualifiedName: - return emitQualifiedName(node as QualifiedName); - case SyntaxKind.ComputedPropertyName: - return emitComputedPropertyName(node as ComputedPropertyName); - - // Signature elements - case SyntaxKind.TypeParameter: - return emitTypeParameter(node as TypeParameterDeclaration); - case SyntaxKind.Parameter: - return emitParameter(node as ParameterDeclaration); - case SyntaxKind.Decorator: - return emitDecorator(node as Decorator); - - // Type members - case SyntaxKind.PropertySignature: - return emitPropertySignature(node as PropertySignature); - case SyntaxKind.PropertyDeclaration: - return emitPropertyDeclaration(node as PropertyDeclaration); - case SyntaxKind.MethodSignature: - return emitMethodSignature(node as MethodSignature); - case SyntaxKind.MethodDeclaration: - return emitMethodDeclaration(node as MethodDeclaration); - case SyntaxKind.ClassStaticBlockDeclaration: - return emitClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); - case SyntaxKind.Constructor: - return emitConstructor(node as ConstructorDeclaration); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return emitAccessorDeclaration(node as AccessorDeclaration); - case SyntaxKind.CallSignature: - return emitCallSignature(node as CallSignatureDeclaration); - case SyntaxKind.ConstructSignature: - return emitConstructSignature(node as ConstructSignatureDeclaration); - case SyntaxKind.IndexSignature: - return emitIndexSignature(node as IndexSignatureDeclaration); - - // Types - case SyntaxKind.TypePredicate: - return emitTypePredicate(node as TypePredicateNode); - case SyntaxKind.TypeReference: - return emitTypeReference(node as TypeReferenceNode); - case SyntaxKind.FunctionType: - return emitFunctionType(node as FunctionTypeNode); - case SyntaxKind.ConstructorType: - return emitConstructorType(node as ConstructorTypeNode); - case SyntaxKind.TypeQuery: - return emitTypeQuery(node as TypeQueryNode); - case SyntaxKind.TypeLiteral: - return emitTypeLiteral(node as TypeLiteralNode); - case SyntaxKind.ArrayType: - return emitArrayType(node as ArrayTypeNode); - case SyntaxKind.TupleType: - return emitTupleType(node as TupleTypeNode); - case SyntaxKind.OptionalType: - return emitOptionalType(node as OptionalTypeNode); - // SyntaxKind.RestType is handled below - case SyntaxKind.UnionType: - return emitUnionType(node as UnionTypeNode); - case SyntaxKind.IntersectionType: - return emitIntersectionType(node as IntersectionTypeNode); - case SyntaxKind.ConditionalType: - return emitConditionalType(node as ConditionalTypeNode); - case SyntaxKind.InferType: - return emitInferType(node as InferTypeNode); - case SyntaxKind.ParenthesizedType: - return emitParenthesizedType(node as ParenthesizedTypeNode); - case SyntaxKind.ExpressionWithTypeArguments: - return emitExpressionWithTypeArguments(node as ExpressionWithTypeArguments); - case SyntaxKind.ThisType: - return emitThisType(); - case SyntaxKind.TypeOperator: - return emitTypeOperator(node as TypeOperatorNode); - case SyntaxKind.IndexedAccessType: - return emitIndexedAccessType(node as IndexedAccessTypeNode); - case SyntaxKind.MappedType: - return emitMappedType(node as MappedTypeNode); - case SyntaxKind.LiteralType: - return emitLiteralType(node as LiteralTypeNode); - case SyntaxKind.NamedTupleMember: - return emitNamedTupleMember(node as NamedTupleMember); - case SyntaxKind.TemplateLiteralType: - return emitTemplateType(node as TemplateLiteralTypeNode); - case SyntaxKind.TemplateLiteralTypeSpan: - return emitTemplateTypeSpan(node as TemplateLiteralTypeSpan); - case SyntaxKind.ImportType: - return emitImportTypeNode(node as ImportTypeNode); - - // Binding patterns - case SyntaxKind.ObjectBindingPattern: - return emitObjectBindingPattern(node as ObjectBindingPattern); - case SyntaxKind.ArrayBindingPattern: - return emitArrayBindingPattern(node as ArrayBindingPattern); - case SyntaxKind.BindingElement: - return emitBindingElement(node as BindingElement); - - // Misc - case SyntaxKind.TemplateSpan: - return emitTemplateSpan(node as TemplateSpan); - case SyntaxKind.SemicolonClassElement: - return emitSemicolonClassElement(); - - // Statements - case SyntaxKind.Block: - return emitBlock(node as Block); - case SyntaxKind.VariableStatement: - return emitVariableStatement(node as VariableStatement); - case SyntaxKind.EmptyStatement: - return emitEmptyStatement(/*isEmbeddedStatement*/ false); - case SyntaxKind.ExpressionStatement: - return emitExpressionStatement(node as ExpressionStatement); - case SyntaxKind.IfStatement: - return emitIfStatement(node as IfStatement); - case SyntaxKind.DoStatement: - return emitDoStatement(node as DoStatement); - case SyntaxKind.WhileStatement: - return emitWhileStatement(node as WhileStatement); - case SyntaxKind.ForStatement: - return emitForStatement(node as ForStatement); - case SyntaxKind.ForInStatement: - return emitForInStatement(node as ForInStatement); - case SyntaxKind.ForOfStatement: - return emitForOfStatement(node as ForOfStatement); - case SyntaxKind.ContinueStatement: - return emitContinueStatement(node as ContinueStatement); - case SyntaxKind.BreakStatement: - return emitBreakStatement(node as BreakStatement); - case SyntaxKind.ReturnStatement: - return emitReturnStatement(node as ReturnStatement); - case SyntaxKind.WithStatement: - return emitWithStatement(node as WithStatement); - case SyntaxKind.SwitchStatement: - return emitSwitchStatement(node as SwitchStatement); - case SyntaxKind.LabeledStatement: - return emitLabeledStatement(node as LabeledStatement); - case SyntaxKind.ThrowStatement: - return emitThrowStatement(node as ThrowStatement); - case SyntaxKind.TryStatement: - return emitTryStatement(node as TryStatement); - case SyntaxKind.DebuggerStatement: - return emitDebuggerStatement(node as DebuggerStatement); - - // Declarations - case SyntaxKind.VariableDeclaration: - return emitVariableDeclaration(node as VariableDeclaration); - case SyntaxKind.VariableDeclarationList: - return emitVariableDeclarationList(node as VariableDeclarationList); - case SyntaxKind.FunctionDeclaration: - return emitFunctionDeclaration(node as FunctionDeclaration); - case SyntaxKind.ClassDeclaration: - return emitClassDeclaration(node as ClassDeclaration); - case SyntaxKind.InterfaceDeclaration: - return emitInterfaceDeclaration(node as InterfaceDeclaration); - case SyntaxKind.TypeAliasDeclaration: - return emitTypeAliasDeclaration(node as TypeAliasDeclaration); - case SyntaxKind.EnumDeclaration: - return emitEnumDeclaration(node as EnumDeclaration); - case SyntaxKind.ModuleDeclaration: - return emitModuleDeclaration(node as ModuleDeclaration); - case SyntaxKind.ModuleBlock: - return emitModuleBlock(node as ModuleBlock); - case SyntaxKind.CaseBlock: - return emitCaseBlock(node as CaseBlock); - case SyntaxKind.NamespaceExportDeclaration: - return emitNamespaceExportDeclaration(node as NamespaceExportDeclaration); - case SyntaxKind.ImportEqualsDeclaration: - return emitImportEqualsDeclaration(node as ImportEqualsDeclaration); - case SyntaxKind.ImportDeclaration: - return emitImportDeclaration(node as ImportDeclaration); - case SyntaxKind.ImportClause: - return emitImportClause(node as ImportClause); - case SyntaxKind.NamespaceImport: - return emitNamespaceImport(node as NamespaceImport); - case SyntaxKind.NamespaceExport: - return emitNamespaceExport(node as NamespaceExport); - case SyntaxKind.NamedImports: - return emitNamedImports(node as NamedImports); - case SyntaxKind.ImportSpecifier: - return emitImportSpecifier(node as ImportSpecifier); - case SyntaxKind.ExportAssignment: - return emitExportAssignment(node as ExportAssignment); - case SyntaxKind.ExportDeclaration: - return emitExportDeclaration(node as ExportDeclaration); - case SyntaxKind.NamedExports: - return emitNamedExports(node as NamedExports); - case SyntaxKind.ExportSpecifier: - return emitExportSpecifier(node as ExportSpecifier); - case SyntaxKind.AssertClause: - return emitAssertClause(node as AssertClause); - case SyntaxKind.AssertEntry: - return emitAssertEntry(node as AssertEntry); - case SyntaxKind.MissingDeclaration: - return; - - // Module references - case SyntaxKind.ExternalModuleReference: - return emitExternalModuleReference(node as ExternalModuleReference); - - // JSX (non-expression) - case SyntaxKind.JsxText: - return emitJsxText(node as JsxText); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxOpeningFragment: - return emitJsxOpeningElementOrFragment(node as JsxOpeningElement); - case SyntaxKind.JsxClosingElement: - case SyntaxKind.JsxClosingFragment: - return emitJsxClosingElementOrFragment(node as JsxClosingElement); - case SyntaxKind.JsxAttribute: - return emitJsxAttribute(node as JsxAttribute); - case SyntaxKind.JsxAttributes: - return emitJsxAttributes(node as JsxAttributes); - case SyntaxKind.JsxSpreadAttribute: - return emitJsxSpreadAttribute(node as JsxSpreadAttribute); - case SyntaxKind.JsxExpression: - return emitJsxExpression(node as JsxExpression); - - // Clauses - case SyntaxKind.CaseClause: - return emitCaseClause(node as CaseClause); - case SyntaxKind.DefaultClause: - return emitDefaultClause(node as DefaultClause); - case SyntaxKind.HeritageClause: - return emitHeritageClause(node as HeritageClause); - case SyntaxKind.CatchClause: - return emitCatchClause(node as CatchClause); - - // Property assignments - case SyntaxKind.PropertyAssignment: - return emitPropertyAssignment(node as PropertyAssignment); - case SyntaxKind.ShorthandPropertyAssignment: - return emitShorthandPropertyAssignment(node as ShorthandPropertyAssignment); - case SyntaxKind.SpreadAssignment: - return emitSpreadAssignment(node as SpreadAssignment); - - // Enum - case SyntaxKind.EnumMember: - return emitEnumMember(node as EnumMember); - - // Unparsed - case SyntaxKind.UnparsedPrologue: - return writeUnparsedNode(node as UnparsedNode); - case SyntaxKind.UnparsedSource: - case SyntaxKind.UnparsedPrepend: - return emitUnparsedSourceOrPrepend(node as UnparsedSource); - case SyntaxKind.UnparsedText: - case SyntaxKind.UnparsedInternalText: - return emitUnparsedTextLike(node as UnparsedTextLike); - case SyntaxKind.UnparsedSyntheticReference: - return emitUnparsedSyntheticReference(node as UnparsedSyntheticReference); - - // Top-level nodes - case SyntaxKind.SourceFile: - return emitSourceFile(node as SourceFile); - case SyntaxKind.Bundle: - return Debug.fail("Bundles should be printed using printBundle"); - // SyntaxKind.UnparsedSource (handled above) - case SyntaxKind.InputFiles: - return Debug.fail("InputFiles should not be printed"); - - // JSDoc nodes (only used in codefixes currently) - case SyntaxKind.JSDocTypeExpression: - return emitJSDocTypeExpression(node as JSDocTypeExpression); - case SyntaxKind.JSDocNameReference: - return emitJSDocNameReference(node as JSDocNameReference); - case SyntaxKind.JSDocAllType: - return writePunctuation("*"); - case SyntaxKind.JSDocUnknownType: - return writePunctuation("?"); - case SyntaxKind.JSDocNullableType: - return emitJSDocNullableType(node as JSDocNullableType); - case SyntaxKind.JSDocNonNullableType: - return emitJSDocNonNullableType(node as JSDocNonNullableType); - case SyntaxKind.JSDocOptionalType: - return emitJSDocOptionalType(node as JSDocOptionalType); - case SyntaxKind.JSDocFunctionType: - return emitJSDocFunctionType(node as JSDocFunctionType); - case SyntaxKind.RestType: - case SyntaxKind.JSDocVariadicType: - return emitRestOrJSDocVariadicType(node as RestTypeNode | JSDocVariadicType); - case SyntaxKind.JSDocNamepathType: - return; - case SyntaxKind.JSDocComment: - return emitJSDoc(node as JSDoc); - case SyntaxKind.JSDocTypeLiteral: - return emitJSDocTypeLiteral(node as JSDocTypeLiteral); - case SyntaxKind.JSDocSignature: - return emitJSDocSignature(node as JSDocSignature); - case SyntaxKind.JSDocTag: - case SyntaxKind.JSDocClassTag: - case SyntaxKind.JSDocOverrideTag: - return emitJSDocSimpleTag(node as JSDocTag); - case SyntaxKind.JSDocAugmentsTag: - case SyntaxKind.JSDocImplementsTag: - return emitJSDocHeritageTag(node as JSDocImplementsTag | JSDocAugmentsTag); - case SyntaxKind.JSDocAuthorTag: - case SyntaxKind.JSDocDeprecatedTag: - return; - // SyntaxKind.JSDocClassTag (see JSDocTag, above) - case SyntaxKind.JSDocPublicTag: - case SyntaxKind.JSDocPrivateTag: - case SyntaxKind.JSDocProtectedTag: - case SyntaxKind.JSDocReadonlyTag: - return; - case SyntaxKind.JSDocCallbackTag: - return emitJSDocCallbackTag(node as JSDocCallbackTag); - // SyntaxKind.JSDocEnumTag (see below) - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - return emitJSDocPropertyLikeTag(node as JSDocPropertyLikeTag); - case SyntaxKind.JSDocEnumTag: - case SyntaxKind.JSDocReturnTag: - case SyntaxKind.JSDocThisTag: - case SyntaxKind.JSDocTypeTag: - return emitJSDocSimpleTypedTag(node as JSDocTypeTag); - case SyntaxKind.JSDocTemplateTag: - return emitJSDocTemplateTag(node as JSDocTemplateTag); - case SyntaxKind.JSDocTypedefTag: - return emitJSDocTypedefTag(node as JSDocTypedefTag); - case SyntaxKind.JSDocSeeTag: - return emitJSDocSeeTag(node as JSDocSeeTag); - // SyntaxKind.JSDocPropertyTag (see JSDocParameterTag, above) - - // Transformation nodes - case SyntaxKind.NotEmittedStatement: - case SyntaxKind.EndOfDeclarationMarker: - case SyntaxKind.MergeDeclarationMarker: - return; + function afterEmitNode(savedPreserveSourceNewlines: boolean | undefined) { + preserveSourceNewlines = savedPreserveSourceNewlines; + } + + function pipelineEmit(emitHint: EmitHint, node: Node, parenthesizerRule?: (node: Node) => Node) { + currentParenthesizerRule = parenthesizerRule; + const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, emitHint, node); + pipelinePhase(emitHint, node); + currentParenthesizerRule = undefined; + } + + function shouldEmitComments(node: Node) { + return !commentsDisabled && !isSourceFile(node); + } + + function shouldEmitSourceMaps(node: Node) { + return !sourceMapsDisabled && + !isSourceFile(node) && + !isInJsonFile(node) && + !isUnparsedSource(node) && + !isUnparsedPrepend(node); + } + + function getPipelinePhase(phase: PipelinePhase, emitHint: EmitHint, node: Node) { + switch (phase) { + case PipelinePhase.Notification: + if (onEmitNode !== noEmitNotification && (!isEmitNotificationEnabled || isEmitNotificationEnabled(node))) { + return pipelineEmitWithNotification; } - if (isExpression(node)) { - hint = EmitHint.Expression; - if (substituteNode !== noEmitSubstitution) { - const substitute = substituteNode(hint, node) || node; - if (substitute !== node) { - node = substitute; - if (currentParenthesizerRule) { - node = currentParenthesizerRule(node); - } - } + // falls through + case PipelinePhase.Substitution: + if (substituteNode !== noEmitSubstitution && (lastSubstitution = substituteNode(emitHint, node) || node) !== node) { + if (currentParenthesizerRule) { + lastSubstitution = currentParenthesizerRule(lastSubstitution); } + return pipelineEmitWithSubstitution; } - } - if (hint === EmitHint.Expression) { - switch (node.kind) { - // Literals - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - return emitNumericOrBigIntLiteral(node as NumericLiteral | BigIntLiteral); - - case SyntaxKind.StringLiteral: - case SyntaxKind.RegularExpressionLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return emitLiteral(node as LiteralExpression, /*jsxAttributeEscape*/ false); - - // Identifiers - case SyntaxKind.Identifier: - return emitIdentifier(node as Identifier); - case SyntaxKind.PrivateIdentifier: - return emitPrivateIdentifier(node as PrivateIdentifier); - - // Expressions - case SyntaxKind.ArrayLiteralExpression: - return emitArrayLiteralExpression(node as ArrayLiteralExpression); - case SyntaxKind.ObjectLiteralExpression: - return emitObjectLiteralExpression(node as ObjectLiteralExpression); - case SyntaxKind.PropertyAccessExpression: - return emitPropertyAccessExpression(node as PropertyAccessExpression); - case SyntaxKind.ElementAccessExpression: - return emitElementAccessExpression(node as ElementAccessExpression); - case SyntaxKind.CallExpression: - return emitCallExpression(node as CallExpression); - case SyntaxKind.NewExpression: - return emitNewExpression(node as NewExpression); - case SyntaxKind.TaggedTemplateExpression: - return emitTaggedTemplateExpression(node as TaggedTemplateExpression); - case SyntaxKind.TypeAssertionExpression: - return emitTypeAssertionExpression(node as TypeAssertion); - case SyntaxKind.ParenthesizedExpression: - return emitParenthesizedExpression(node as ParenthesizedExpression); - case SyntaxKind.FunctionExpression: - return emitFunctionExpression(node as FunctionExpression); - case SyntaxKind.ArrowFunction: - return emitArrowFunction(node as ArrowFunction); - case SyntaxKind.DeleteExpression: - return emitDeleteExpression(node as DeleteExpression); - case SyntaxKind.TypeOfExpression: - return emitTypeOfExpression(node as TypeOfExpression); - case SyntaxKind.VoidExpression: - return emitVoidExpression(node as VoidExpression); - case SyntaxKind.AwaitExpression: - return emitAwaitExpression(node as AwaitExpression); - case SyntaxKind.PrefixUnaryExpression: - return emitPrefixUnaryExpression(node as PrefixUnaryExpression); - case SyntaxKind.PostfixUnaryExpression: - return emitPostfixUnaryExpression(node as PostfixUnaryExpression); - case SyntaxKind.BinaryExpression: - return emitBinaryExpression(node as BinaryExpression); - case SyntaxKind.ConditionalExpression: - return emitConditionalExpression(node as ConditionalExpression); - case SyntaxKind.TemplateExpression: - return emitTemplateExpression(node as TemplateExpression); - case SyntaxKind.YieldExpression: - return emitYieldExpression(node as YieldExpression); - case SyntaxKind.SpreadElement: - return emitSpreadElement(node as SpreadElement); - case SyntaxKind.ClassExpression: - return emitClassExpression(node as ClassExpression); - case SyntaxKind.OmittedExpression: - return; - case SyntaxKind.AsExpression: - return emitAsExpression(node as AsExpression); - case SyntaxKind.NonNullExpression: - return emitNonNullExpression(node as NonNullExpression); - case SyntaxKind.MetaProperty: - return emitMetaProperty(node as MetaProperty); - case SyntaxKind.SyntheticExpression: - return Debug.fail("SyntheticExpression should never be printed."); - - // JSX - case SyntaxKind.JsxElement: - return emitJsxElement(node as JsxElement); - case SyntaxKind.JsxSelfClosingElement: - return emitJsxSelfClosingElement(node as JsxSelfClosingElement); - case SyntaxKind.JsxFragment: - return emitJsxFragment(node as JsxFragment); - - // Synthesized list - case SyntaxKind.SyntaxList: - return Debug.fail("SyntaxList should not be printed"); - - // Transformation nodes - case SyntaxKind.NotEmittedStatement: - return; - case SyntaxKind.PartiallyEmittedExpression: - return emitPartiallyEmittedExpression(node as PartiallyEmittedExpression); - case SyntaxKind.CommaListExpression: - return emitCommaList(node as CommaListExpression); - case SyntaxKind.MergeDeclarationMarker: - case SyntaxKind.EndOfDeclarationMarker: - return; - case SyntaxKind.SyntheticReferenceExpression: - return Debug.fail("SyntheticReferenceExpression should not be printed"); + // falls through + case PipelinePhase.Comments: + if (shouldEmitComments(node)) { + return pipelineEmitWithComments; } - } - if (isKeyword(node.kind)) return writeTokenNode(node, writeKeyword); - if (isTokenKind(node.kind)) return writeTokenNode(node, writePunctuation); - Debug.fail(`Unhandled SyntaxKind: ${Debug.formatSyntaxKind(node.kind)}.`); + // falls through + case PipelinePhase.SourceMaps: + if (shouldEmitSourceMaps(node)) { + return pipelineEmitWithSourceMaps; + } + // falls through + case PipelinePhase.Emit: + return pipelineEmitWithHint; + default: + return Debug.assertNever(phase); } + } - function emitMappedTypeParameter(node: TypeParameterDeclaration): void { - emit(node.name); - writeSpace(); - writeKeyword("in"); - writeSpace(); - emit(node.constraint); - } + function getNextPipelinePhase(currentPhase: PipelinePhase, emitHint: EmitHint, node: Node) { + return getPipelinePhase(currentPhase + 1, emitHint, node); + } - function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) { - const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, hint, node); - Debug.assertIsDefined(lastSubstitution); - node = lastSubstitution; - lastSubstitution = undefined; - pipelinePhase(hint, node); + function pipelineEmitWithNotification(hint: EmitHint, node: Node) { + const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, hint, node); + onEmitNode(hint, node, pipelinePhase); + } + + function pipelineEmitWithHint(hint: EmitHint, node: Node): void { + onBeforeEmitNode?.(node); + if (preserveSourceNewlines) { + const savedPreserveSourceNewlines = preserveSourceNewlines; + beforeEmitNode(node); + pipelineEmitWithHintWorker(hint, node); + afterEmitNode(savedPreserveSourceNewlines); } + else { + pipelineEmitWithHintWorker(hint, node); + } + onAfterEmitNode?.(node); + // clear the parenthesizer rule as we ascend + currentParenthesizerRule = undefined; + } - function getHelpersFromBundledSourceFiles(bundle: Bundle): string[] | undefined { - let result: string[] | undefined; - if (moduleKind === ModuleKind.None || printerOptions.noEmitHelpers) { - return undefined; - } - const bundledHelpers = new Map(); - for (const sourceFile of bundle.sourceFiles) { - const shouldSkip = getExternalHelpersModuleName(sourceFile) !== undefined; - const helpers = getSortedEmitHelpers(sourceFile); - if (!helpers) continue; - for (const helper of helpers) { - if (!helper.scoped && !shouldSkip && !bundledHelpers.get(helper.name)) { - bundledHelpers.set(helper.name, true); - (result || (result = [])).push(helper.name); - } - } + function pipelineEmitWithHintWorker(hint: EmitHint, node: Node, allowSnippets = true): void { + if (allowSnippets) { + const snippet = getSnippetElement(node); + if (snippet) { + return emitSnippetNode(hint, node, snippet); } - - return result; } + if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile)); + if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier)); + if (hint === EmitHint.JsxAttributeValue) return emitLiteral(cast(node, isStringLiteral), /*jsxAttributeEscape*/ true); + if (hint === EmitHint.MappedTypeParameter) return emitMappedTypeParameter(cast(node, isTypeParameterDeclaration)); + if (hint === EmitHint.EmbeddedStatement) { + Debug.assertNode(node, isEmptyStatement); + return emitEmptyStatement(/*isEmbeddedStatement*/ true); + } + if (hint === EmitHint.Unspecified) { + switch (node.kind) { + // Pseudo-literals + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + return emitLiteral(node as LiteralExpression, /*jsxAttributeEscape*/ false); - function emitHelpers(node: Node) { - let helpersEmitted = false; - const bundle = node.kind === SyntaxKind.Bundle ? node as Bundle : undefined; - if (bundle && moduleKind === ModuleKind.None) { - return; + // Identifiers + case SyntaxKind.Identifier: + return emitIdentifier(node as Identifier); + + // PrivateIdentifiers + case SyntaxKind.PrivateIdentifier: + return emitPrivateIdentifier(node as PrivateIdentifier); + + // Parse tree nodes + // Names + case SyntaxKind.QualifiedName: + return emitQualifiedName(node as QualifiedName); + case SyntaxKind.ComputedPropertyName: + return emitComputedPropertyName(node as ComputedPropertyName); + + // Signature elements + case SyntaxKind.TypeParameter: + return emitTypeParameter(node as TypeParameterDeclaration); + case SyntaxKind.Parameter: + return emitParameter(node as ParameterDeclaration); + case SyntaxKind.Decorator: + return emitDecorator(node as Decorator); + + // Type members + case SyntaxKind.PropertySignature: + return emitPropertySignature(node as PropertySignature); + case SyntaxKind.PropertyDeclaration: + return emitPropertyDeclaration(node as PropertyDeclaration); + case SyntaxKind.MethodSignature: + return emitMethodSignature(node as MethodSignature); + case SyntaxKind.MethodDeclaration: + return emitMethodDeclaration(node as MethodDeclaration); + case SyntaxKind.ClassStaticBlockDeclaration: + return emitClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); + case SyntaxKind.Constructor: + return emitConstructor(node as ConstructorDeclaration); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return emitAccessorDeclaration(node as AccessorDeclaration); + case SyntaxKind.CallSignature: + return emitCallSignature(node as CallSignatureDeclaration); + case SyntaxKind.ConstructSignature: + return emitConstructSignature(node as ConstructSignatureDeclaration); + case SyntaxKind.IndexSignature: + return emitIndexSignature(node as IndexSignatureDeclaration); + + // Types + case SyntaxKind.TypePredicate: + return emitTypePredicate(node as TypePredicateNode); + case SyntaxKind.TypeReference: + return emitTypeReference(node as TypeReferenceNode); + case SyntaxKind.FunctionType: + return emitFunctionType(node as FunctionTypeNode); + case SyntaxKind.ConstructorType: + return emitConstructorType(node as ConstructorTypeNode); + case SyntaxKind.TypeQuery: + return emitTypeQuery(node as TypeQueryNode); + case SyntaxKind.TypeLiteral: + return emitTypeLiteral(node as TypeLiteralNode); + case SyntaxKind.ArrayType: + return emitArrayType(node as ArrayTypeNode); + case SyntaxKind.TupleType: + return emitTupleType(node as TupleTypeNode); + case SyntaxKind.OptionalType: + return emitOptionalType(node as OptionalTypeNode); + // SyntaxKind.RestType is handled below + case SyntaxKind.UnionType: + return emitUnionType(node as UnionTypeNode); + case SyntaxKind.IntersectionType: + return emitIntersectionType(node as IntersectionTypeNode); + case SyntaxKind.ConditionalType: + return emitConditionalType(node as ConditionalTypeNode); + case SyntaxKind.InferType: + return emitInferType(node as InferTypeNode); + case SyntaxKind.ParenthesizedType: + return emitParenthesizedType(node as ParenthesizedTypeNode); + case SyntaxKind.ExpressionWithTypeArguments: + return emitExpressionWithTypeArguments(node as ExpressionWithTypeArguments); + case SyntaxKind.ThisType: + return emitThisType(); + case SyntaxKind.TypeOperator: + return emitTypeOperator(node as TypeOperatorNode); + case SyntaxKind.IndexedAccessType: + return emitIndexedAccessType(node as IndexedAccessTypeNode); + case SyntaxKind.MappedType: + return emitMappedType(node as MappedTypeNode); + case SyntaxKind.LiteralType: + return emitLiteralType(node as LiteralTypeNode); + case SyntaxKind.NamedTupleMember: + return emitNamedTupleMember(node as NamedTupleMember); + case SyntaxKind.TemplateLiteralType: + return emitTemplateType(node as TemplateLiteralTypeNode); + case SyntaxKind.TemplateLiteralTypeSpan: + return emitTemplateTypeSpan(node as TemplateLiteralTypeSpan); + case SyntaxKind.ImportType: + return emitImportTypeNode(node as ImportTypeNode); + + // Binding patterns + case SyntaxKind.ObjectBindingPattern: + return emitObjectBindingPattern(node as ObjectBindingPattern); + case SyntaxKind.ArrayBindingPattern: + return emitArrayBindingPattern(node as ArrayBindingPattern); + case SyntaxKind.BindingElement: + return emitBindingElement(node as BindingElement); + + // Misc + case SyntaxKind.TemplateSpan: + return emitTemplateSpan(node as TemplateSpan); + case SyntaxKind.SemicolonClassElement: + return emitSemicolonClassElement(); + + // Statements + case SyntaxKind.Block: + return emitBlock(node as Block); + case SyntaxKind.VariableStatement: + return emitVariableStatement(node as VariableStatement); + case SyntaxKind.EmptyStatement: + return emitEmptyStatement(/*isEmbeddedStatement*/ false); + case SyntaxKind.ExpressionStatement: + return emitExpressionStatement(node as ExpressionStatement); + case SyntaxKind.IfStatement: + return emitIfStatement(node as IfStatement); + case SyntaxKind.DoStatement: + return emitDoStatement(node as DoStatement); + case SyntaxKind.WhileStatement: + return emitWhileStatement(node as WhileStatement); + case SyntaxKind.ForStatement: + return emitForStatement(node as ForStatement); + case SyntaxKind.ForInStatement: + return emitForInStatement(node as ForInStatement); + case SyntaxKind.ForOfStatement: + return emitForOfStatement(node as ForOfStatement); + case SyntaxKind.ContinueStatement: + return emitContinueStatement(node as ContinueStatement); + case SyntaxKind.BreakStatement: + return emitBreakStatement(node as BreakStatement); + case SyntaxKind.ReturnStatement: + return emitReturnStatement(node as ReturnStatement); + case SyntaxKind.WithStatement: + return emitWithStatement(node as WithStatement); + case SyntaxKind.SwitchStatement: + return emitSwitchStatement(node as SwitchStatement); + case SyntaxKind.LabeledStatement: + return emitLabeledStatement(node as LabeledStatement); + case SyntaxKind.ThrowStatement: + return emitThrowStatement(node as ThrowStatement); + case SyntaxKind.TryStatement: + return emitTryStatement(node as TryStatement); + case SyntaxKind.DebuggerStatement: + return emitDebuggerStatement(node as DebuggerStatement); + + // Declarations + case SyntaxKind.VariableDeclaration: + return emitVariableDeclaration(node as VariableDeclaration); + case SyntaxKind.VariableDeclarationList: + return emitVariableDeclarationList(node as VariableDeclarationList); + case SyntaxKind.FunctionDeclaration: + return emitFunctionDeclaration(node as FunctionDeclaration); + case SyntaxKind.ClassDeclaration: + return emitClassDeclaration(node as ClassDeclaration); + case SyntaxKind.InterfaceDeclaration: + return emitInterfaceDeclaration(node as InterfaceDeclaration); + case SyntaxKind.TypeAliasDeclaration: + return emitTypeAliasDeclaration(node as TypeAliasDeclaration); + case SyntaxKind.EnumDeclaration: + return emitEnumDeclaration(node as EnumDeclaration); + case SyntaxKind.ModuleDeclaration: + return emitModuleDeclaration(node as ModuleDeclaration); + case SyntaxKind.ModuleBlock: + return emitModuleBlock(node as ModuleBlock); + case SyntaxKind.CaseBlock: + return emitCaseBlock(node as CaseBlock); + case SyntaxKind.NamespaceExportDeclaration: + return emitNamespaceExportDeclaration(node as NamespaceExportDeclaration); + case SyntaxKind.ImportEqualsDeclaration: + return emitImportEqualsDeclaration(node as ImportEqualsDeclaration); + case SyntaxKind.ImportDeclaration: + return emitImportDeclaration(node as ImportDeclaration); + case SyntaxKind.ImportClause: + return emitImportClause(node as ImportClause); + case SyntaxKind.NamespaceImport: + return emitNamespaceImport(node as NamespaceImport); + case SyntaxKind.NamespaceExport: + return emitNamespaceExport(node as NamespaceExport); + case SyntaxKind.NamedImports: + return emitNamedImports(node as NamedImports); + case SyntaxKind.ImportSpecifier: + return emitImportSpecifier(node as ImportSpecifier); + case SyntaxKind.ExportAssignment: + return emitExportAssignment(node as ExportAssignment); + case SyntaxKind.ExportDeclaration: + return emitExportDeclaration(node as ExportDeclaration); + case SyntaxKind.NamedExports: + return emitNamedExports(node as NamedExports); + case SyntaxKind.ExportSpecifier: + return emitExportSpecifier(node as ExportSpecifier); + case SyntaxKind.AssertClause: + return emitAssertClause(node as AssertClause); + case SyntaxKind.AssertEntry: + return emitAssertEntry(node as AssertEntry); + case SyntaxKind.MissingDeclaration: + return; + + // Module references + case SyntaxKind.ExternalModuleReference: + return emitExternalModuleReference(node as ExternalModuleReference); + + // JSX (non-expression) + case SyntaxKind.JsxText: + return emitJsxText(node as JsxText); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxOpeningFragment: + return emitJsxOpeningElementOrFragment(node as JsxOpeningElement); + case SyntaxKind.JsxClosingElement: + case SyntaxKind.JsxClosingFragment: + return emitJsxClosingElementOrFragment(node as JsxClosingElement); + case SyntaxKind.JsxAttribute: + return emitJsxAttribute(node as JsxAttribute); + case SyntaxKind.JsxAttributes: + return emitJsxAttributes(node as JsxAttributes); + case SyntaxKind.JsxSpreadAttribute: + return emitJsxSpreadAttribute(node as JsxSpreadAttribute); + case SyntaxKind.JsxExpression: + return emitJsxExpression(node as JsxExpression); + + // Clauses + case SyntaxKind.CaseClause: + return emitCaseClause(node as CaseClause); + case SyntaxKind.DefaultClause: + return emitDefaultClause(node as DefaultClause); + case SyntaxKind.HeritageClause: + return emitHeritageClause(node as HeritageClause); + case SyntaxKind.CatchClause: + return emitCatchClause(node as CatchClause); + + // Property assignments + case SyntaxKind.PropertyAssignment: + return emitPropertyAssignment(node as PropertyAssignment); + case SyntaxKind.ShorthandPropertyAssignment: + return emitShorthandPropertyAssignment(node as ShorthandPropertyAssignment); + case SyntaxKind.SpreadAssignment: + return emitSpreadAssignment(node as SpreadAssignment); + + // Enum + case SyntaxKind.EnumMember: + return emitEnumMember(node as EnumMember); + + // Unparsed + case SyntaxKind.UnparsedPrologue: + return writeUnparsedNode(node as UnparsedNode); + case SyntaxKind.UnparsedSource: + case SyntaxKind.UnparsedPrepend: + return emitUnparsedSourceOrPrepend(node as UnparsedSource); + case SyntaxKind.UnparsedText: + case SyntaxKind.UnparsedInternalText: + return emitUnparsedTextLike(node as UnparsedTextLike); + case SyntaxKind.UnparsedSyntheticReference: + return emitUnparsedSyntheticReference(node as UnparsedSyntheticReference); + + // Top-level nodes + case SyntaxKind.SourceFile: + return emitSourceFile(node as SourceFile); + case SyntaxKind.Bundle: + return Debug.fail("Bundles should be printed using printBundle"); + // SyntaxKind.UnparsedSource (handled above) + case SyntaxKind.InputFiles: + return Debug.fail("InputFiles should not be printed"); + + // JSDoc nodes (only used in codefixes currently) + case SyntaxKind.JSDocTypeExpression: + return emitJSDocTypeExpression(node as JSDocTypeExpression); + case SyntaxKind.JSDocNameReference: + return emitJSDocNameReference(node as JSDocNameReference); + case SyntaxKind.JSDocAllType: + return writePunctuation("*"); + case SyntaxKind.JSDocUnknownType: + return writePunctuation("?"); + case SyntaxKind.JSDocNullableType: + return emitJSDocNullableType(node as JSDocNullableType); + case SyntaxKind.JSDocNonNullableType: + return emitJSDocNonNullableType(node as JSDocNonNullableType); + case SyntaxKind.JSDocOptionalType: + return emitJSDocOptionalType(node as JSDocOptionalType); + case SyntaxKind.JSDocFunctionType: + return emitJSDocFunctionType(node as JSDocFunctionType); + case SyntaxKind.RestType: + case SyntaxKind.JSDocVariadicType: + return emitRestOrJSDocVariadicType(node as RestTypeNode | JSDocVariadicType); + case SyntaxKind.JSDocNamepathType: + return; + case SyntaxKind.JSDocComment: + return emitJSDoc(node as JSDoc); + case SyntaxKind.JSDocTypeLiteral: + return emitJSDocTypeLiteral(node as JSDocTypeLiteral); + case SyntaxKind.JSDocSignature: + return emitJSDocSignature(node as JSDocSignature); + case SyntaxKind.JSDocTag: + case SyntaxKind.JSDocClassTag: + case SyntaxKind.JSDocOverrideTag: + return emitJSDocSimpleTag(node as JSDocTag); + case SyntaxKind.JSDocAugmentsTag: + case SyntaxKind.JSDocImplementsTag: + return emitJSDocHeritageTag(node as JSDocImplementsTag | JSDocAugmentsTag); + case SyntaxKind.JSDocAuthorTag: + case SyntaxKind.JSDocDeprecatedTag: + return; + // SyntaxKind.JSDocClassTag (see JSDocTag, above) + case SyntaxKind.JSDocPublicTag: + case SyntaxKind.JSDocPrivateTag: + case SyntaxKind.JSDocProtectedTag: + case SyntaxKind.JSDocReadonlyTag: + return; + case SyntaxKind.JSDocCallbackTag: + return emitJSDocCallbackTag(node as JSDocCallbackTag); + // SyntaxKind.JSDocEnumTag (see below) + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocPropertyTag: + return emitJSDocPropertyLikeTag(node as JSDocPropertyLikeTag); + case SyntaxKind.JSDocEnumTag: + case SyntaxKind.JSDocReturnTag: + case SyntaxKind.JSDocThisTag: + case SyntaxKind.JSDocTypeTag: + return emitJSDocSimpleTypedTag(node as JSDocTypeTag); + case SyntaxKind.JSDocTemplateTag: + return emitJSDocTemplateTag(node as JSDocTemplateTag); + case SyntaxKind.JSDocTypedefTag: + return emitJSDocTypedefTag(node as JSDocTypedefTag); + case SyntaxKind.JSDocSeeTag: + return emitJSDocSeeTag(node as JSDocSeeTag); + // SyntaxKind.JSDocPropertyTag (see JSDocParameterTag, above) + + // Transformation nodes + case SyntaxKind.NotEmittedStatement: + case SyntaxKind.EndOfDeclarationMarker: + case SyntaxKind.MergeDeclarationMarker: + return; } - const numPrepends = bundle ? bundle.prepends.length : 0; - const numNodes = bundle ? bundle.sourceFiles.length + numPrepends : 1; - for (let i = 0; i < numNodes; i++) { - const currentNode = bundle ? i < numPrepends ? bundle.prepends[i] : bundle.sourceFiles[i - numPrepends] : node; - const sourceFile = isSourceFile(currentNode) ? currentNode : isUnparsedSource(currentNode) ? undefined : currentSourceFile!; - const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && hasRecordedExternalHelpers(sourceFile)); - const shouldBundle = (isSourceFile(currentNode) || isUnparsedSource(currentNode)) && !isOwnFileEmit; - const helpers = isUnparsedSource(currentNode) ? currentNode.helpers : getSortedEmitHelpers(currentNode); - if (helpers) { - for (const helper of helpers) { - if (!helper.scoped) { - // Skip the helper if it can be skipped and the noEmitHelpers compiler - // option is set, or if it can be imported and the importHelpers compiler - // option is set. - if (shouldSkip) continue; - - // Skip the helper if it can be bundled but hasn't already been emitted and we - // are emitting a bundled module. - if (shouldBundle) { - if (bundledHelpers.get(helper.name)) { - continue; - } - - bundledHelpers.set(helper.name, true); - } - } - else if (bundle) { - // Skip the helper if it is scoped and we are emitting bundled helpers - continue; - } - const pos = getTextPosWithWriteLine(); - if (typeof helper.text === "string") { - writeLines(helper.text); - } - else { - writeLines(helper.text(makeFileLevelOptimisticUniqueName)); + if (isExpression(node)) { + hint = EmitHint.Expression; + if (substituteNode !== noEmitSubstitution) { + const substitute = substituteNode(hint, node) || node; + if (substitute !== node) { + node = substitute; + if (currentParenthesizerRule) { + node = currentParenthesizerRule(node); } - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.EmitHelpers, data: helper.name }); - helpersEmitted = true; } } } - - return helpersEmitted; } + if (hint === EmitHint.Expression) { + switch (node.kind) { + // Literals + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + return emitNumericOrBigIntLiteral(node as NumericLiteral | BigIntLiteral); - function getSortedEmitHelpers(node: Node) { - const helpers = getEmitHelpers(node); - return helpers && stableSort(helpers, compareEmitHelpers); - } + case SyntaxKind.StringLiteral: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return emitLiteral(node as LiteralExpression, /*jsxAttributeEscape*/ false); - // - // Literals/Pseudo-literals - // - - // SyntaxKind.NumericLiteral - // SyntaxKind.BigIntLiteral - function emitNumericOrBigIntLiteral(node: NumericLiteral | BigIntLiteral) { - emitLiteral(node, /*jsxAttributeEscape*/ false); - } - - // SyntaxKind.StringLiteral - // SyntaxKind.RegularExpressionLiteral - // SyntaxKind.NoSubstitutionTemplateLiteral - // SyntaxKind.TemplateHead - // SyntaxKind.TemplateMiddle - // SyntaxKind.TemplateTail - function emitLiteral(node: LiteralLikeNode, jsxAttributeEscape: boolean) { - const text = getLiteralTextOfNode(node, printerOptions.neverAsciiEscape, jsxAttributeEscape); - if ((printerOptions.sourceMap || printerOptions.inlineSourceMap) - && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { - writeLiteral(text); - } - else { - // Quick info expects all literals to be called with writeStringLiteral, as there's no specific type for numberLiterals - writeStringLiteral(text); + // Identifiers + case SyntaxKind.Identifier: + return emitIdentifier(node as Identifier); + case SyntaxKind.PrivateIdentifier: + return emitPrivateIdentifier(node as PrivateIdentifier); + + // Expressions + case SyntaxKind.ArrayLiteralExpression: + return emitArrayLiteralExpression(node as ArrayLiteralExpression); + case SyntaxKind.ObjectLiteralExpression: + return emitObjectLiteralExpression(node as ObjectLiteralExpression); + case SyntaxKind.PropertyAccessExpression: + return emitPropertyAccessExpression(node as PropertyAccessExpression); + case SyntaxKind.ElementAccessExpression: + return emitElementAccessExpression(node as ElementAccessExpression); + case SyntaxKind.CallExpression: + return emitCallExpression(node as CallExpression); + case SyntaxKind.NewExpression: + return emitNewExpression(node as NewExpression); + case SyntaxKind.TaggedTemplateExpression: + return emitTaggedTemplateExpression(node as TaggedTemplateExpression); + case SyntaxKind.TypeAssertionExpression: + return emitTypeAssertionExpression(node as TypeAssertion); + case SyntaxKind.ParenthesizedExpression: + return emitParenthesizedExpression(node as ParenthesizedExpression); + case SyntaxKind.FunctionExpression: + return emitFunctionExpression(node as FunctionExpression); + case SyntaxKind.ArrowFunction: + return emitArrowFunction(node as ArrowFunction); + case SyntaxKind.DeleteExpression: + return emitDeleteExpression(node as DeleteExpression); + case SyntaxKind.TypeOfExpression: + return emitTypeOfExpression(node as TypeOfExpression); + case SyntaxKind.VoidExpression: + return emitVoidExpression(node as VoidExpression); + case SyntaxKind.AwaitExpression: + return emitAwaitExpression(node as AwaitExpression); + case SyntaxKind.PrefixUnaryExpression: + return emitPrefixUnaryExpression(node as PrefixUnaryExpression); + case SyntaxKind.PostfixUnaryExpression: + return emitPostfixUnaryExpression(node as PostfixUnaryExpression); + case SyntaxKind.BinaryExpression: + return emitBinaryExpression(node as BinaryExpression); + case SyntaxKind.ConditionalExpression: + return emitConditionalExpression(node as ConditionalExpression); + case SyntaxKind.TemplateExpression: + return emitTemplateExpression(node as TemplateExpression); + case SyntaxKind.YieldExpression: + return emitYieldExpression(node as YieldExpression); + case SyntaxKind.SpreadElement: + return emitSpreadElement(node as SpreadElement); + case SyntaxKind.ClassExpression: + return emitClassExpression(node as ClassExpression); + case SyntaxKind.OmittedExpression: + return; + case SyntaxKind.AsExpression: + return emitAsExpression(node as AsExpression); + case SyntaxKind.NonNullExpression: + return emitNonNullExpression(node as NonNullExpression); + case SyntaxKind.MetaProperty: + return emitMetaProperty(node as MetaProperty); + case SyntaxKind.SyntheticExpression: + return Debug.fail("SyntheticExpression should never be printed."); + + // JSX + case SyntaxKind.JsxElement: + return emitJsxElement(node as JsxElement); + case SyntaxKind.JsxSelfClosingElement: + return emitJsxSelfClosingElement(node as JsxSelfClosingElement); + case SyntaxKind.JsxFragment: + return emitJsxFragment(node as JsxFragment); + + // Synthesized list + case SyntaxKind.SyntaxList: + return Debug.fail("SyntaxList should not be printed"); + + // Transformation nodes + case SyntaxKind.NotEmittedStatement: + return; + case SyntaxKind.PartiallyEmittedExpression: + return emitPartiallyEmittedExpression(node as PartiallyEmittedExpression); + case SyntaxKind.CommaListExpression: + return emitCommaList(node as CommaListExpression); + case SyntaxKind.MergeDeclarationMarker: + case SyntaxKind.EndOfDeclarationMarker: + return; + case SyntaxKind.SyntheticReferenceExpression: + return Debug.fail("SyntheticReferenceExpression should not be printed"); } } + if (isKeyword(node.kind)) return writeTokenNode(node, writeKeyword); + if (isTokenKind(node.kind)) return writeTokenNode(node, writePunctuation); + Debug.fail(`Unhandled SyntaxKind: ${Debug.formatSyntaxKind(node.kind)}.`); + } - // SyntaxKind.UnparsedSource - // SyntaxKind.UnparsedPrepend - function emitUnparsedSourceOrPrepend(unparsed: UnparsedSource | UnparsedPrepend) { - for (const text of unparsed.texts) { - writeLine(); - emit(text); - } - } + function emitMappedTypeParameter(node: TypeParameterDeclaration): void { + emit(node.name); + writeSpace(); + writeKeyword("in"); + writeSpace(); + emit(node.constraint); + } - // SyntaxKind.UnparsedPrologue - // SyntaxKind.UnparsedText - // SyntaxKind.UnparsedInternal - // SyntaxKind.UnparsedSyntheticReference - function writeUnparsedNode(unparsed: UnparsedNode) { - writer.rawWrite(unparsed.parent.text.substring(unparsed.pos, unparsed.end)); - } + function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) { + const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, hint, node); + Debug.assertIsDefined(lastSubstitution); + node = lastSubstitution; + lastSubstitution = undefined; + pipelinePhase(hint, node); + } - // SyntaxKind.UnparsedText - // SyntaxKind.UnparsedInternal - function emitUnparsedTextLike(unparsed: UnparsedTextLike) { - const pos = getTextPosWithWriteLine(); - writeUnparsedNode(unparsed); - if (bundleFileInfo) { - updateOrPushBundleFileTextLike( - pos, - writer.getTextPos(), - unparsed.kind === SyntaxKind.UnparsedText ? - BundleFileSectionKind.Text : - BundleFileSectionKind.Internal - ); - } + function getHelpersFromBundledSourceFiles(bundle: Bundle): string[] | undefined { + let result: string[] | undefined; + if (moduleKind === ModuleKind.None || printerOptions.noEmitHelpers) { + return undefined; } - - // SyntaxKind.UnparsedSyntheticReference - function emitUnparsedSyntheticReference(unparsed: UnparsedSyntheticReference) { - const pos = getTextPosWithWriteLine(); - writeUnparsedNode(unparsed); - if (bundleFileInfo) { - const section = clone(unparsed.section); - section.pos = pos; - section.end = writer.getTextPos(); - bundleFileInfo.sections.push(section); + const bundledHelpers = new Map(); + for (const sourceFile of bundle.sourceFiles) { + const shouldSkip = getExternalHelpersModuleName(sourceFile) !== undefined; + const helpers = getSortedEmitHelpers(sourceFile); + if (!helpers) continue; + for (const helper of helpers) { + if (!helper.scoped && !shouldSkip && !bundledHelpers.get(helper.name)) { + bundledHelpers.set(helper.name, true); + (result || (result = [])).push(helper.name); + } } } - // - // Snippet Elements - // + return result; + } - function emitSnippetNode(hint: EmitHint, node: Node, snippet: SnippetElement) { - switch (snippet.kind) { - case SnippetKind.Placeholder: - emitPlaceholder(hint, node, snippet); - break; - case SnippetKind.TabStop: - emitTabStop(snippet); - break; + function emitHelpers(node: Node) { + let helpersEmitted = false; + const bundle = node.kind === SyntaxKind.Bundle ? node as Bundle : undefined; + if (bundle && moduleKind === ModuleKind.None) { + return; + } + const numPrepends = bundle ? bundle.prepends.length : 0; + const numNodes = bundle ? bundle.sourceFiles.length + numPrepends : 1; + for (let i = 0; i < numNodes; i++) { + const currentNode = bundle ? i < numPrepends ? bundle.prepends[i] : bundle.sourceFiles[i - numPrepends] : node; + const sourceFile = isSourceFile(currentNode) ? currentNode : isUnparsedSource(currentNode) ? undefined : currentSourceFile!; + const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && hasRecordedExternalHelpers(sourceFile)); + const shouldBundle = (isSourceFile(currentNode) || isUnparsedSource(currentNode)) && !isOwnFileEmit; + const helpers = isUnparsedSource(currentNode) ? currentNode.helpers : getSortedEmitHelpers(currentNode); + if (helpers) { + for (const helper of helpers) { + if (!helper.scoped) { + // Skip the helper if it can be skipped and the noEmitHelpers compiler + // option is set, or if it can be imported and the importHelpers compiler + // option is set. + if (shouldSkip) continue; + + // Skip the helper if it can be bundled but hasn't already been emitted and we + // are emitting a bundled module. + if (shouldBundle) { + if (bundledHelpers.get(helper.name)) { + continue; + } + + bundledHelpers.set(helper.name, true); + } + } + else if (bundle) { + // Skip the helper if it is scoped and we are emitting bundled helpers + continue; + } + const pos = getTextPosWithWriteLine(); + if (typeof helper.text === "string") { + writeLines(helper.text); + } + else { + writeLines(helper.text(makeFileLevelOptimisticUniqueName)); + } + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.EmitHelpers, data: helper.name }); + helpersEmitted = true; + } } } - function emitPlaceholder(hint: EmitHint, node: Node, snippet: Placeholder) { - nonEscapingWrite(`\$\{${snippet.order}:`); // `${2:` - pipelineEmitWithHintWorker(hint, node, /*allowSnippets*/ false); // `...` - nonEscapingWrite(`\}`); // `}` - // `${2:...}` - } + return helpersEmitted; + } - function emitTabStop(snippet: TabStop) { - nonEscapingWrite(`\$${snippet.order}`); - } + function getSortedEmitHelpers(node: Node) { + const helpers = getEmitHelpers(node); + return helpers && stableSort(helpers, compareEmitHelpers); + } - // - // Identifiers - // + // + // Literals/Pseudo-literals + // - function emitIdentifier(node: Identifier) { - const writeText = node.symbol ? writeSymbol : write; - writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); - emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments - } + // SyntaxKind.NumericLiteral + // SyntaxKind.BigIntLiteral + function emitNumericOrBigIntLiteral(node: NumericLiteral | BigIntLiteral) { + emitLiteral(node, /*jsxAttributeEscape*/ false); + } - // - // Names - // + // SyntaxKind.StringLiteral + // SyntaxKind.RegularExpressionLiteral + // SyntaxKind.NoSubstitutionTemplateLiteral + // SyntaxKind.TemplateHead + // SyntaxKind.TemplateMiddle + // SyntaxKind.TemplateTail + function emitLiteral(node: LiteralLikeNode, jsxAttributeEscape: boolean) { + const text = getLiteralTextOfNode(node, printerOptions.neverAsciiEscape, jsxAttributeEscape); + if ((printerOptions.sourceMap || printerOptions.inlineSourceMap) + && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { + writeLiteral(text); + } + else { + // Quick info expects all literals to be called with writeStringLiteral, as there's no specific type for numberLiterals + writeStringLiteral(text); + } + } - function emitPrivateIdentifier(node: PrivateIdentifier) { - const writeText = node.symbol ? writeSymbol : write; - writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); + // SyntaxKind.UnparsedSource + // SyntaxKind.UnparsedPrepend + function emitUnparsedSourceOrPrepend(unparsed: UnparsedSource | UnparsedPrepend) { + for (const text of unparsed.texts) { + writeLine(); + emit(text); } + } + // SyntaxKind.UnparsedPrologue + // SyntaxKind.UnparsedText + // SyntaxKind.UnparsedInternal + // SyntaxKind.UnparsedSyntheticReference + function writeUnparsedNode(unparsed: UnparsedNode) { + writer.rawWrite(unparsed.parent.text.substring(unparsed.pos, unparsed.end)); + } - function emitQualifiedName(node: QualifiedName) { - emitEntityName(node.left); - writePunctuation("."); - emit(node.right); + // SyntaxKind.UnparsedText + // SyntaxKind.UnparsedInternal + function emitUnparsedTextLike(unparsed: UnparsedTextLike) { + const pos = getTextPosWithWriteLine(); + writeUnparsedNode(unparsed); + if (bundleFileInfo) { + updateOrPushBundleFileTextLike( + pos, + writer.getTextPos(), + unparsed.kind === SyntaxKind.UnparsedText ? + BundleFileSectionKind.Text : + BundleFileSectionKind.Internal + ); } + } - function emitEntityName(node: EntityName) { - if (node.kind === SyntaxKind.Identifier) { - emitExpression(node); - } - else { - emit(node); - } + // SyntaxKind.UnparsedSyntheticReference + function emitUnparsedSyntheticReference(unparsed: UnparsedSyntheticReference) { + const pos = getTextPosWithWriteLine(); + writeUnparsedNode(unparsed); + if (bundleFileInfo) { + const section = clone(unparsed.section); + section.pos = pos; + section.end = writer.getTextPos(); + bundleFileInfo.sections.push(section); } + } - function emitComputedPropertyName(node: ComputedPropertyName) { - writePunctuation("["); - emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfComputedPropertyName); - writePunctuation("]"); + // + // Snippet Elements + // + + function emitSnippetNode(hint: EmitHint, node: Node, snippet: SnippetElement) { + switch (snippet.kind) { + case SnippetKind.Placeholder: + emitPlaceholder(hint, node, snippet); + break; + case SnippetKind.TabStop: + emitTabStop(snippet); + break; } + } - // - // Signature elements - // + function emitPlaceholder(hint: EmitHint, node: Node, snippet: Placeholder) { + nonEscapingWrite(`\$\{${snippet.order}:`); // `${2:` + pipelineEmitWithHintWorker(hint, node, /*allowSnippets*/ false); // `...` + nonEscapingWrite(`\}`); // `}` + // `${2:...}` + } - function emitTypeParameter(node: TypeParameterDeclaration) { - emit(node.name); - if (node.constraint) { - writeSpace(); - writeKeyword("extends"); - writeSpace(); - emit(node.constraint); - } - if (node.default) { - writeSpace(); - writeOperator("="); - writeSpace(); - emit(node.default); - } - } + function emitTabStop(snippet: TabStop) { + nonEscapingWrite(`\$${snippet.order}`); + } - function emitParameter(node: ParameterDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emit(node.dotDotDotToken); - emitNodeWithWriter(node.name, writeParameter); - emit(node.questionToken); - if (node.parent && node.parent.kind === SyntaxKind.JSDocFunctionType && !node.name) { - emit(node.type); - } - else { - emitTypeAnnotation(node.type); - } - // The comment position has to fallback to any present node within the parameterdeclaration because as it turns out, the parser can make parameter declarations with _just_ an initializer. - emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name ? node.name.end : node.modifiers ? node.modifiers.end : node.decorators ? node.decorators.end : node.pos, node, parenthesizer.parenthesizeExpressionForDisallowedComma); - } + // + // Identifiers + // - function emitDecorator(decorator: Decorator) { - writePunctuation("@"); - emitExpression(decorator.expression, parenthesizer.parenthesizeLeftSideOfAccess); - } + function emitIdentifier(node: Identifier) { + const writeText = node.symbol ? writeSymbol : write; + writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); + emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments + } - // - // Type members - // + // + // Names + // - function emitPropertySignature(node: PropertySignature) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emitNodeWithWriter(node.name, writeProperty); - emit(node.questionToken); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - } + function emitPrivateIdentifier(node: PrivateIdentifier) { + const writeText = node.symbol ? writeSymbol : write; + writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); + } - function emitPropertyDeclaration(node: PropertyDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emit(node.name); - emit(node.questionToken); - emit(node.exclamationToken); - emitTypeAnnotation(node.type); - emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name.end, node); - writeTrailingSemicolon(); - } - function emitMethodSignature(node: MethodSignature) { - pushNameGenerationScope(node); - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emit(node.name); - emit(node.questionToken); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - popNameGenerationScope(node); - } + function emitQualifiedName(node: QualifiedName) { + emitEntityName(node.left); + writePunctuation("."); + emit(node.right); + } - function emitMethodDeclaration(node: MethodDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emit(node.asteriskToken); - emit(node.name); - emit(node.questionToken); - emitSignatureAndBody(node, emitSignatureHead); + function emitEntityName(node: EntityName) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); } - - function emitClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("static"); - emitBlockFunctionBody(node.body); + else { + emit(node); } + } - function emitConstructor(node: ConstructorDeclaration) { - emitModifiers(node, node.modifiers); - writeKeyword("constructor"); - emitSignatureAndBody(node, emitSignatureHead); - } + function emitComputedPropertyName(node: ComputedPropertyName) { + writePunctuation("["); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfComputedPropertyName); + writePunctuation("]"); + } - function emitAccessorDeclaration(node: AccessorDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword(node.kind === SyntaxKind.GetAccessor ? "get" : "set"); - writeSpace(); - emit(node.name); - emitSignatureAndBody(node, emitSignatureHead); - } + // + // Signature elements + // - function emitCallSignature(node: CallSignatureDeclaration) { - pushNameGenerationScope(node); - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - popNameGenerationScope(node); + function emitTypeParameter(node: TypeParameterDeclaration) { + emit(node.name); + if (node.constraint) { + writeSpace(); + writeKeyword("extends"); + writeSpace(); + emit(node.constraint); } - - function emitConstructSignature(node: ConstructSignatureDeclaration) { - pushNameGenerationScope(node); - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("new"); + if (node.default) { writeSpace(); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - popNameGenerationScope(node); + writeOperator("="); + writeSpace(); + emit(node.default); } + } - function emitIndexSignature(node: IndexSignatureDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emitParametersForIndexSignature(node, node.parameters); + function emitParameter(node: ParameterDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.dotDotDotToken); + emitNodeWithWriter(node.name, writeParameter); + emit(node.questionToken); + if (node.parent && node.parent.kind === SyntaxKind.JSDocFunctionType && !node.name) { + emit(node.type); + } + else { emitTypeAnnotation(node.type); - writeTrailingSemicolon(); } + // The comment position has to fallback to any present node within the parameterdeclaration because as it turns out, the parser can make parameter declarations with _just_ an initializer. + emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name ? node.name.end : node.modifiers ? node.modifiers.end : node.decorators ? node.decorators.end : node.pos, node, parenthesizer.parenthesizeExpressionForDisallowedComma); + } - function emitTemplateTypeSpan(node: TemplateLiteralTypeSpan) { - emit(node.type); - emit(node.literal); - } + function emitDecorator(decorator: Decorator) { + writePunctuation("@"); + emitExpression(decorator.expression, parenthesizer.parenthesizeLeftSideOfAccess); + } - function emitSemicolonClassElement() { - writeTrailingSemicolon(); - } + // + // Type members + // + + function emitPropertySignature(node: PropertySignature) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitNodeWithWriter(node.name, writeProperty); + emit(node.questionToken); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + } - // - // Types - // + function emitPropertyDeclaration(node: PropertyDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.name); + emit(node.questionToken); + emit(node.exclamationToken); + emitTypeAnnotation(node.type); + emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name.end, node); + writeTrailingSemicolon(); + } - function emitTypePredicate(node: TypePredicateNode) { - if (node.assertsModifier) { - emit(node.assertsModifier); - writeSpace(); - } - emit(node.parameterName); - if (node.type) { - writeSpace(); - writeKeyword("is"); - writeSpace(); - emit(node.type); - } - } + function emitMethodSignature(node: MethodSignature) { + pushNameGenerationScope(node); + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.name); + emit(node.questionToken); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + popNameGenerationScope(node); + } - function emitTypeReference(node: TypeReferenceNode) { - emit(node.typeName); - emitTypeArguments(node, node.typeArguments); - } + function emitMethodDeclaration(node: MethodDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.asteriskToken); + emit(node.name); + emit(node.questionToken); + emitSignatureAndBody(node, emitSignatureHead); + } - function emitFunctionType(node: FunctionTypeNode) { - pushNameGenerationScope(node); - emitTypeParameters(node, node.typeParameters); - emitParametersForArrow(node, node.parameters); - writeSpace(); - writePunctuation("=>"); - writeSpace(); - emit(node.type); - popNameGenerationScope(node); - } + function emitClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("static"); + emitBlockFunctionBody(node.body); + } - function emitJSDocFunctionType(node: JSDocFunctionType) { - writeKeyword("function"); - emitParameters(node, node.parameters); - writePunctuation(":"); - emit(node.type); - } + function emitConstructor(node: ConstructorDeclaration) { + emitModifiers(node, node.modifiers); + writeKeyword("constructor"); + emitSignatureAndBody(node, emitSignatureHead); + } + function emitAccessorDeclaration(node: AccessorDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword(node.kind === SyntaxKind.GetAccessor ? "get" : "set"); + writeSpace(); + emit(node.name); + emitSignatureAndBody(node, emitSignatureHead); + } - function emitJSDocNullableType(node: JSDocNullableType) { - writePunctuation("?"); - emit(node.type); - } + function emitCallSignature(node: CallSignatureDeclaration) { + pushNameGenerationScope(node); + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + popNameGenerationScope(node); + } - function emitJSDocNonNullableType(node: JSDocNonNullableType) { - writePunctuation("!"); - emit(node.type); - } + function emitConstructSignature(node: ConstructSignatureDeclaration) { + pushNameGenerationScope(node); + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("new"); + writeSpace(); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + popNameGenerationScope(node); + } - function emitJSDocOptionalType(node: JSDocOptionalType) { - emit(node.type); - writePunctuation("="); - } + function emitIndexSignature(node: IndexSignatureDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitParametersForIndexSignature(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + } - function emitConstructorType(node: ConstructorTypeNode) { - pushNameGenerationScope(node); - emitModifiers(node, node.modifiers); - writeKeyword("new"); + function emitTemplateTypeSpan(node: TemplateLiteralTypeSpan) { + emit(node.type); + emit(node.literal); + } + + function emitSemicolonClassElement() { + writeTrailingSemicolon(); + } + + // + // Types + // + + function emitTypePredicate(node: TypePredicateNode) { + if (node.assertsModifier) { + emit(node.assertsModifier); writeSpace(); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); + } + emit(node.parameterName); + if (node.type) { writeSpace(); - writePunctuation("=>"); + writeKeyword("is"); writeSpace(); emit(node.type); - popNameGenerationScope(node); } + } - function emitTypeQuery(node: TypeQueryNode) { - writeKeyword("typeof"); - writeSpace(); - emit(node.exprName); - } + function emitTypeReference(node: TypeReferenceNode) { + emit(node.typeName); + emitTypeArguments(node, node.typeArguments); + } - function emitTypeLiteral(node: TypeLiteralNode) { - writePunctuation("{"); - const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTypeLiteralMembers : ListFormat.MultiLineTypeLiteralMembers; - emitList(node, node.members, flags | ListFormat.NoSpaceIfEmpty); - writePunctuation("}"); - } + function emitFunctionType(node: FunctionTypeNode) { + pushNameGenerationScope(node); + emitTypeParameters(node, node.typeParameters); + emitParametersForArrow(node, node.parameters); + writeSpace(); + writePunctuation("=>"); + writeSpace(); + emit(node.type); + popNameGenerationScope(node); + } - function emitArrayType(node: ArrayTypeNode) { - emit(node.elementType, parenthesizer.parenthesizeElementTypeOfArrayType); - writePunctuation("["); - writePunctuation("]"); - } + function emitJSDocFunctionType(node: JSDocFunctionType) { + writeKeyword("function"); + emitParameters(node, node.parameters); + writePunctuation(":"); + emit(node.type); + } - function emitRestOrJSDocVariadicType(node: RestTypeNode | JSDocVariadicType) { - writePunctuation("..."); - emit(node.type); - } - function emitTupleType(node: TupleTypeNode) { - emitTokenWithComment(SyntaxKind.OpenBracketToken, node.pos, writePunctuation, node); - const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTupleTypeElements : ListFormat.MultiLineTupleTypeElements; - emitList(node, node.elements, flags | ListFormat.NoSpaceIfEmpty); - emitTokenWithComment(SyntaxKind.CloseBracketToken, node.elements.end, writePunctuation, node); - } + function emitJSDocNullableType(node: JSDocNullableType) { + writePunctuation("?"); + emit(node.type); + } - function emitNamedTupleMember(node: NamedTupleMember) { - emit(node.dotDotDotToken); - emit(node.name); - emit(node.questionToken); - emitTokenWithComment(SyntaxKind.ColonToken, node.name.end, writePunctuation, node); - writeSpace(); - emit(node.type); - } + function emitJSDocNonNullableType(node: JSDocNonNullableType) { + writePunctuation("!"); + emit(node.type); + } - function emitOptionalType(node: OptionalTypeNode) { - emit(node.type, parenthesizer.parenthesizeElementTypeOfArrayType); - writePunctuation("?"); - } + function emitJSDocOptionalType(node: JSDocOptionalType) { + emit(node.type); + writePunctuation("="); + } - function emitUnionType(node: UnionTypeNode) { - emitList(node, node.types, ListFormat.UnionTypeConstituents, parenthesizer.parenthesizeMemberOfElementType); - } + function emitConstructorType(node: ConstructorTypeNode) { + pushNameGenerationScope(node); + emitModifiers(node, node.modifiers); + writeKeyword("new"); + writeSpace(); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + writeSpace(); + writePunctuation("=>"); + writeSpace(); + emit(node.type); + popNameGenerationScope(node); + } - function emitIntersectionType(node: IntersectionTypeNode) { - emitList(node, node.types, ListFormat.IntersectionTypeConstituents, parenthesizer.parenthesizeMemberOfElementType); - } + function emitTypeQuery(node: TypeQueryNode) { + writeKeyword("typeof"); + writeSpace(); + emit(node.exprName); + } - function emitConditionalType(node: ConditionalTypeNode) { - emit(node.checkType, parenthesizer.parenthesizeMemberOfConditionalType); - writeSpace(); - writeKeyword("extends"); - writeSpace(); - emit(node.extendsType, parenthesizer.parenthesizeMemberOfConditionalType); - writeSpace(); - writePunctuation("?"); - writeSpace(); - emit(node.trueType); - writeSpace(); - writePunctuation(":"); - writeSpace(); - emit(node.falseType); - } + function emitTypeLiteral(node: TypeLiteralNode) { + writePunctuation("{"); + const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTypeLiteralMembers : ListFormat.MultiLineTypeLiteralMembers; + emitList(node, node.members, flags | ListFormat.NoSpaceIfEmpty); + writePunctuation("}"); + } - function emitInferType(node: InferTypeNode) { - writeKeyword("infer"); - writeSpace(); - emit(node.typeParameter); - } + function emitArrayType(node: ArrayTypeNode) { + emit(node.elementType, parenthesizer.parenthesizeElementTypeOfArrayType); + writePunctuation("["); + writePunctuation("]"); + } - function emitParenthesizedType(node: ParenthesizedTypeNode) { - writePunctuation("("); - emit(node.type); - writePunctuation(")"); - } + function emitRestOrJSDocVariadicType(node: RestTypeNode | JSDocVariadicType) { + writePunctuation("..."); + emit(node.type); + } - function emitThisType() { - writeKeyword("this"); - } + function emitTupleType(node: TupleTypeNode) { + emitTokenWithComment(SyntaxKind.OpenBracketToken, node.pos, writePunctuation, node); + const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTupleTypeElements : ListFormat.MultiLineTupleTypeElements; + emitList(node, node.elements, flags | ListFormat.NoSpaceIfEmpty); + emitTokenWithComment(SyntaxKind.CloseBracketToken, node.elements.end, writePunctuation, node); + } - function emitTypeOperator(node: TypeOperatorNode) { - writeTokenText(node.operator, writeKeyword); - writeSpace(); - emit(node.type, parenthesizer.parenthesizeMemberOfElementType); - } + function emitNamedTupleMember(node: NamedTupleMember) { + emit(node.dotDotDotToken); + emit(node.name); + emit(node.questionToken); + emitTokenWithComment(SyntaxKind.ColonToken, node.name.end, writePunctuation, node); + writeSpace(); + emit(node.type); + } - function emitIndexedAccessType(node: IndexedAccessTypeNode) { - emit(node.objectType, parenthesizer.parenthesizeMemberOfElementType); - writePunctuation("["); - emit(node.indexType); - writePunctuation("]"); - } + function emitOptionalType(node: OptionalTypeNode) { + emit(node.type, parenthesizer.parenthesizeElementTypeOfArrayType); + writePunctuation("?"); + } - function emitMappedType(node: MappedTypeNode) { - const emitFlags = getEmitFlags(node); - writePunctuation("{"); - if (emitFlags & EmitFlags.SingleLine) { - writeSpace(); - } - else { - writeLine(); - increaseIndent(); - } - if (node.readonlyToken) { - emit(node.readonlyToken); - if (node.readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { - writeKeyword("readonly"); - } - writeSpace(); - } - writePunctuation("["); + function emitUnionType(node: UnionTypeNode) { + emitList(node, node.types, ListFormat.UnionTypeConstituents, parenthesizer.parenthesizeMemberOfElementType); + } - pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter); - if (node.nameType) { - writeSpace(); - writeKeyword("as"); - writeSpace(); - emit(node.nameType); - } + function emitIntersectionType(node: IntersectionTypeNode) { + emitList(node, node.types, ListFormat.IntersectionTypeConstituents, parenthesizer.parenthesizeMemberOfElementType); + } - writePunctuation("]"); - if (node.questionToken) { - emit(node.questionToken); - if (node.questionToken.kind !== SyntaxKind.QuestionToken) { - writePunctuation("?"); - } - } - writePunctuation(":"); - writeSpace(); - emit(node.type); - writeTrailingSemicolon(); - if (emitFlags & EmitFlags.SingleLine) { - writeSpace(); - } - else { - writeLine(); - decreaseIndent(); - } - writePunctuation("}"); - } + function emitConditionalType(node: ConditionalTypeNode) { + emit(node.checkType, parenthesizer.parenthesizeMemberOfConditionalType); + writeSpace(); + writeKeyword("extends"); + writeSpace(); + emit(node.extendsType, parenthesizer.parenthesizeMemberOfConditionalType); + writeSpace(); + writePunctuation("?"); + writeSpace(); + emit(node.trueType); + writeSpace(); + writePunctuation(":"); + writeSpace(); + emit(node.falseType); + } + + function emitInferType(node: InferTypeNode) { + writeKeyword("infer"); + writeSpace(); + emit(node.typeParameter); + } - function emitLiteralType(node: LiteralTypeNode) { - emitExpression(node.literal); - } + function emitParenthesizedType(node: ParenthesizedTypeNode) { + writePunctuation("("); + emit(node.type); + writePunctuation(")"); + } - function emitTemplateType(node: TemplateLiteralTypeNode) { - emit(node.head); - emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); - } + function emitThisType() { + writeKeyword("this"); + } - function emitImportTypeNode(node: ImportTypeNode) { - if (node.isTypeOf) { - writeKeyword("typeof"); - writeSpace(); - } - writeKeyword("import"); - writePunctuation("("); - emit(node.argument); - writePunctuation(")"); - if (node.qualifier) { - writePunctuation("."); - emit(node.qualifier); - } - emitTypeArguments(node, node.typeArguments); - } + function emitTypeOperator(node: TypeOperatorNode) { + writeTokenText(node.operator, writeKeyword); + writeSpace(); + emit(node.type, parenthesizer.parenthesizeMemberOfElementType); + } - // - // Binding patterns - // + function emitIndexedAccessType(node: IndexedAccessTypeNode) { + emit(node.objectType, parenthesizer.parenthesizeMemberOfElementType); + writePunctuation("["); + emit(node.indexType); + writePunctuation("]"); + } - function emitObjectBindingPattern(node: ObjectBindingPattern) { - writePunctuation("{"); - emitList(node, node.elements, ListFormat.ObjectBindingPatternElements); - writePunctuation("}"); + function emitMappedType(node: MappedTypeNode) { + const emitFlags = getEmitFlags(node); + writePunctuation("{"); + if (emitFlags & EmitFlags.SingleLine) { + writeSpace(); + } + else { + writeLine(); + increaseIndent(); + } + if (node.readonlyToken) { + emit(node.readonlyToken); + if (node.readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { + writeKeyword("readonly"); + } + writeSpace(); } + writePunctuation("["); - function emitArrayBindingPattern(node: ArrayBindingPattern) { - writePunctuation("["); - emitList(node, node.elements, ListFormat.ArrayBindingPatternElements); - writePunctuation("]"); + pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter); + if (node.nameType) { + writeSpace(); + writeKeyword("as"); + writeSpace(); + emit(node.nameType); } - function emitBindingElement(node: BindingElement) { - emit(node.dotDotDotToken); - if (node.propertyName) { - emit(node.propertyName); - writePunctuation(":"); - writeSpace(); + writePunctuation("]"); + if (node.questionToken) { + emit(node.questionToken); + if (node.questionToken.kind !== SyntaxKind.QuestionToken) { + writePunctuation("?"); } - emit(node.name); - emitInitializer(node.initializer, node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); } + writePunctuation(":"); + writeSpace(); + emit(node.type); + writeTrailingSemicolon(); + if (emitFlags & EmitFlags.SingleLine) { + writeSpace(); + } + else { + writeLine(); + decreaseIndent(); + } + writePunctuation("}"); + } - // - // Expressions - // + function emitLiteralType(node: LiteralTypeNode) { + emitExpression(node.literal); + } + + function emitTemplateType(node: TemplateLiteralTypeNode) { + emit(node.head); + emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); + } - function emitArrayLiteralExpression(node: ArrayLiteralExpression) { - const elements = node.elements; - const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; - emitExpressionList(node, elements, ListFormat.ArrayLiteralExpressionElements | preferNewLine, parenthesizer.parenthesizeExpressionForDisallowedComma); + function emitImportTypeNode(node: ImportTypeNode) { + if (node.isTypeOf) { + writeKeyword("typeof"); + writeSpace(); + } + writeKeyword("import"); + writePunctuation("("); + emit(node.argument); + writePunctuation(")"); + if (node.qualifier) { + writePunctuation("."); + emit(node.qualifier); } + emitTypeArguments(node, node.typeArguments); + } - function emitObjectLiteralExpression(node: ObjectLiteralExpression) { - forEach(node.properties, generateMemberNames); + // + // Binding patterns + // - const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; - if (indentedFlag) { - increaseIndent(); - } + function emitObjectBindingPattern(node: ObjectBindingPattern) { + writePunctuation("{"); + emitList(node, node.elements, ListFormat.ObjectBindingPatternElements); + writePunctuation("}"); + } - const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; - const allowTrailingComma = currentSourceFile!.languageVersion >= ScriptTarget.ES5 && !isJsonSourceFile(currentSourceFile!) ? ListFormat.AllowTrailingComma : ListFormat.None; - emitList(node, node.properties, ListFormat.ObjectLiteralExpressionProperties | allowTrailingComma | preferNewLine); + function emitArrayBindingPattern(node: ArrayBindingPattern) { + writePunctuation("["); + emitList(node, node.elements, ListFormat.ArrayBindingPatternElements); + writePunctuation("]"); + } - if (indentedFlag) { - decreaseIndent(); - } + function emitBindingElement(node: BindingElement) { + emit(node.dotDotDotToken); + if (node.propertyName) { + emit(node.propertyName); + writePunctuation(":"); + writeSpace(); } + emit(node.name); + emitInitializer(node.initializer, node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); + } - function emitPropertyAccessExpression(node: PropertyAccessExpression) { - emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); - const token = node.questionDotToken || setTextRangePosEnd(factory.createToken(SyntaxKind.DotToken) as DotToken, node.expression.end, node.name.pos); - const linesBeforeDot = getLinesBetweenNodes(node, node.expression, token); - const linesAfterDot = getLinesBetweenNodes(node, token, node.name); - - writeLinesAndIndent(linesBeforeDot, /*writeSpaceIfNotIndenting*/ false); + // + // Expressions + // - const shouldEmitDotDot = - token.kind !== SyntaxKind.QuestionDotToken && - mayNeedDotDotForPropertyAccess(node.expression) && - !writer.hasTrailingComment() && - !writer.hasTrailingWhitespace(); + function emitArrayLiteralExpression(node: ArrayLiteralExpression) { + const elements = node.elements; + const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; + emitExpressionList(node, elements, ListFormat.ArrayLiteralExpressionElements | preferNewLine, parenthesizer.parenthesizeExpressionForDisallowedComma); + } - if (shouldEmitDotDot) { - writePunctuation("."); - } + function emitObjectLiteralExpression(node: ObjectLiteralExpression) { + forEach(node.properties, generateMemberNames); - if (node.questionDotToken) { - emit(token); - } - else { - emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node); - } - writeLinesAndIndent(linesAfterDot, /*writeSpaceIfNotIndenting*/ false); - emit(node.name); - decreaseIndentIf(linesBeforeDot, linesAfterDot); - } - - // 1..toString is a valid property access, emit a dot after the literal - // Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal - function mayNeedDotDotForPropertyAccess(expression: Expression) { - expression = skipPartiallyEmittedExpressions(expression); - if (isNumericLiteral(expression)) { - // check if numeric literal is a decimal literal that was originally written with a dot - const text = getLiteralTextOfNode(expression as LiteralExpression, /*neverAsciiEscape*/ true, /*jsxAttributeEscape*/ false); - // If he number will be printed verbatim and it doesn't already contain a dot, add one - // if the expression doesn't have any comments that will be emitted. - return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!); - } - else if (isAccessExpression(expression)) { - // check if constant enum value is integer - const constantValue = getConstantValue(expression); - // isFinite handles cases when constantValue is undefined - return typeof constantValue === "number" && isFinite(constantValue) - && Math.floor(constantValue) === constantValue; - } + const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); } - function emitElementAccessExpression(node: ElementAccessExpression) { - emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); - emit(node.questionDotToken); - emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node); - emitExpression(node.argumentExpression); - emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node); - } + const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; + const allowTrailingComma = currentSourceFile!.languageVersion >= ScriptTarget.ES5 && !isJsonSourceFile(currentSourceFile!) ? ListFormat.AllowTrailingComma : ListFormat.None; + emitList(node, node.properties, ListFormat.ObjectLiteralExpressionProperties | allowTrailingComma | preferNewLine); - function emitCallExpression(node: CallExpression) { - const indirectCall = getEmitFlags(node) & EmitFlags.IndirectCall; - if (indirectCall) { - writePunctuation("("); - writeLiteral("0"); - writePunctuation(","); - writeSpace(); - } - emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); - if (indirectCall) { - writePunctuation(")"); - } - emit(node.questionDotToken); - emitTypeArguments(node, node.typeArguments); - emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments, parenthesizer.parenthesizeExpressionForDisallowedComma); + if (indentedFlag) { + decreaseIndent(); } + } - function emitNewExpression(node: NewExpression) { - emitTokenWithComment(SyntaxKind.NewKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfNew); - emitTypeArguments(node, node.typeArguments); - emitExpressionList(node, node.arguments, ListFormat.NewExpressionArguments, parenthesizer.parenthesizeExpressionForDisallowedComma); - } + function emitPropertyAccessExpression(node: PropertyAccessExpression) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + const token = node.questionDotToken || setTextRangePosEnd(factory.createToken(SyntaxKind.DotToken) as DotToken, node.expression.end, node.name.pos); + const linesBeforeDot = getLinesBetweenNodes(node, node.expression, token); + const linesAfterDot = getLinesBetweenNodes(node, token, node.name); - function emitTaggedTemplateExpression(node: TaggedTemplateExpression) { - const indirectCall = getEmitFlags(node) & EmitFlags.IndirectCall; - if (indirectCall) { - writePunctuation("("); - writeLiteral("0"); - writePunctuation(","); - writeSpace(); - } - emitExpression(node.tag, parenthesizer.parenthesizeLeftSideOfAccess); - if (indirectCall) { - writePunctuation(")"); - } - emitTypeArguments(node, node.typeArguments); - writeSpace(); - emitExpression(node.template); - } + writeLinesAndIndent(linesBeforeDot, /*writeSpaceIfNotIndenting*/ false); - function emitTypeAssertionExpression(node: TypeAssertion) { - writePunctuation("<"); - emit(node.type); - writePunctuation(">"); - emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); - } + const shouldEmitDotDot = + token.kind !== SyntaxKind.QuestionDotToken && + mayNeedDotDotForPropertyAccess(node.expression) && + !writer.hasTrailingComment() && + !writer.hasTrailingWhitespace(); - function emitParenthesizedExpression(node: ParenthesizedExpression) { - const openParenPos = emitTokenWithComment(SyntaxKind.OpenParenToken, node.pos, writePunctuation, node); - const indented = writeLineSeparatorsAndIndentBefore(node.expression, node); - emitExpression(node.expression, /*parenthesizerRules*/ undefined); - writeLineSeparatorsAfter(node.expression, node); - decreaseIndentIf(indented); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression ? node.expression.end : openParenPos, writePunctuation, node); + if (shouldEmitDotDot) { + writePunctuation("."); } - function emitFunctionExpression(node: FunctionExpression) { - generateNameIfNeeded(node.name); - emitFunctionDeclarationOrExpression(node); + if (node.questionDotToken) { + emit(token); } - - function emitArrowFunction(node: ArrowFunction) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emitSignatureAndBody(node, emitArrowFunctionHead); + else { + emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node); } + writeLinesAndIndent(linesAfterDot, /*writeSpaceIfNotIndenting*/ false); + emit(node.name); + decreaseIndentIf(linesBeforeDot, linesAfterDot); + } - function emitArrowFunctionHead(node: ArrowFunction) { - emitTypeParameters(node, node.typeParameters); - emitParametersForArrow(node, node.parameters); - emitTypeAnnotation(node.type); - writeSpace(); - emit(node.equalsGreaterThanToken); + // 1..toString is a valid property access, emit a dot after the literal + // Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal + function mayNeedDotDotForPropertyAccess(expression: Expression) { + expression = skipPartiallyEmittedExpressions(expression); + if (isNumericLiteral(expression)) { + // check if numeric literal is a decimal literal that was originally written with a dot + const text = getLiteralTextOfNode(expression as LiteralExpression, /*neverAsciiEscape*/ true, /*jsxAttributeEscape*/ false); + // If he number will be printed verbatim and it doesn't already contain a dot, add one + // if the expression doesn't have any comments that will be emitted. + return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!); + } + else if (isAccessExpression(expression)) { + // check if constant enum value is integer + const constantValue = getConstantValue(expression); + // isFinite handles cases when constantValue is undefined + return typeof constantValue === "number" && isFinite(constantValue) + && Math.floor(constantValue) === constantValue; } + } - function emitDeleteExpression(node: DeleteExpression) { - emitTokenWithComment(SyntaxKind.DeleteKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); - } + function emitElementAccessExpression(node: ElementAccessExpression) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + emit(node.questionDotToken); + emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node); + emitExpression(node.argumentExpression); + emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node); + } - function emitTypeOfExpression(node: TypeOfExpression) { - emitTokenWithComment(SyntaxKind.TypeOfKeyword, node.pos, writeKeyword, node); + function emitCallExpression(node: CallExpression) { + const indirectCall = getEmitFlags(node) & EmitFlags.IndirectCall; + if (indirectCall) { + writePunctuation("("); + writeLiteral("0"); + writePunctuation(","); writeSpace(); - emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); } - - function emitVoidExpression(node: VoidExpression) { - emitTokenWithComment(SyntaxKind.VoidKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + if (indirectCall) { + writePunctuation(")"); } + emit(node.questionDotToken); + emitTypeArguments(node, node.typeArguments); + emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments, parenthesizer.parenthesizeExpressionForDisallowedComma); + } - function emitAwaitExpression(node: AwaitExpression) { - emitTokenWithComment(SyntaxKind.AwaitKeyword, node.pos, writeKeyword, node); + function emitNewExpression(node: NewExpression) { + emitTokenWithComment(SyntaxKind.NewKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfNew); + emitTypeArguments(node, node.typeArguments); + emitExpressionList(node, node.arguments, ListFormat.NewExpressionArguments, parenthesizer.parenthesizeExpressionForDisallowedComma); + } + + function emitTaggedTemplateExpression(node: TaggedTemplateExpression) { + const indirectCall = getEmitFlags(node) & EmitFlags.IndirectCall; + if (indirectCall) { + writePunctuation("("); + writeLiteral("0"); + writePunctuation(","); writeSpace(); - emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); } - - function emitPrefixUnaryExpression(node: PrefixUnaryExpression) { - writeTokenText(node.operator, writeOperator); - if (shouldEmitWhitespaceBeforeOperand(node)) { - writeSpace(); - } - emitExpression(node.operand, parenthesizer.parenthesizeOperandOfPrefixUnary); + emitExpression(node.tag, parenthesizer.parenthesizeLeftSideOfAccess); + if (indirectCall) { + writePunctuation(")"); } + emitTypeArguments(node, node.typeArguments); + writeSpace(); + emitExpression(node.template); + } - function shouldEmitWhitespaceBeforeOperand(node: PrefixUnaryExpression) { - // In some cases, we need to emit a space between the operator and the operand. One obvious case - // is when the operator is an identifier, like delete or typeof. We also need to do this for plus - // and minus expressions in certain cases. Specifically, consider the following two cases (parens - // are just for clarity of exposition, and not part of the source code): - // - // (+(+1)) - // (+(++1)) - // - // We need to emit a space in both cases. In the first case, the absence of a space will make - // the resulting expression a prefix increment operation. And in the second, it will make the resulting - // expression a prefix increment whose operand is a plus expression - (++(+x)) - // The same is true of minus of course. - const operand = node.operand; - return operand.kind === SyntaxKind.PrefixUnaryExpression - && ((node.operator === SyntaxKind.PlusToken && ((operand as PrefixUnaryExpression).operator === SyntaxKind.PlusToken || (operand as PrefixUnaryExpression).operator === SyntaxKind.PlusPlusToken)) - || (node.operator === SyntaxKind.MinusToken && ((operand as PrefixUnaryExpression).operator === SyntaxKind.MinusToken || (operand as PrefixUnaryExpression).operator === SyntaxKind.MinusMinusToken))); - } - - function emitPostfixUnaryExpression(node: PostfixUnaryExpression) { - emitExpression(node.operand, parenthesizer.parenthesizeOperandOfPostfixUnary); - writeTokenText(node.operator, writeOperator); - } - - function createEmitBinaryExpression() { - interface WorkArea { - stackIndex: number; - preserveSourceNewlinesStack: (boolean | undefined)[]; - containerPosStack: number[]; - containerEndStack: number[]; - declarationListContainerEndStack: number[]; - shouldEmitCommentsStack: boolean[]; - shouldEmitSourceMapsStack: boolean[]; - } + function emitTypeAssertionExpression(node: TypeAssertion) { + writePunctuation("<"); + emit(node.type); + writePunctuation(">"); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + } - return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined); - - function onEnter(node: BinaryExpression, state: WorkArea | undefined) { - if (state) { - state.stackIndex++; - state.preserveSourceNewlinesStack[state.stackIndex] = preserveSourceNewlines; - state.containerPosStack[state.stackIndex] = containerPos; - state.containerEndStack[state.stackIndex] = containerEnd; - state.declarationListContainerEndStack[state.stackIndex] = declarationListContainerEnd; - const emitComments = state.shouldEmitCommentsStack[state.stackIndex] = shouldEmitComments(node); - const emitSourceMaps = state.shouldEmitSourceMapsStack[state.stackIndex] = shouldEmitSourceMaps(node); - onBeforeEmitNode?.(node); - if (emitComments) emitCommentsBeforeNode(node); - if (emitSourceMaps) emitSourceMapsBeforeNode(node); - beforeEmitNode(node); - } - else { - state = { - stackIndex: 0, - preserveSourceNewlinesStack: [undefined], - containerPosStack: [-1], - containerEndStack: [-1], - declarationListContainerEndStack: [-1], - shouldEmitCommentsStack: [false], - shouldEmitSourceMapsStack: [false], - }; - } - return state; - } + function emitParenthesizedExpression(node: ParenthesizedExpression) { + const openParenPos = emitTokenWithComment(SyntaxKind.OpenParenToken, node.pos, writePunctuation, node); + const indented = writeLineSeparatorsAndIndentBefore(node.expression, node); + emitExpression(node.expression, /*parenthesizerRules*/ undefined); + writeLineSeparatorsAfter(node.expression, node); + decreaseIndentIf(indented); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression ? node.expression.end : openParenPos, writePunctuation, node); + } - function onLeft(next: Expression, _workArea: WorkArea, parent: BinaryExpression) { - return maybeEmitExpression(next, parent, "left"); - } + function emitFunctionExpression(node: FunctionExpression) { + generateNameIfNeeded(node.name); + emitFunctionDeclarationOrExpression(node); + } - function onOperator(operatorToken: BinaryOperatorToken, _state: WorkArea, node: BinaryExpression) { - const isCommaOperator = operatorToken.kind !== SyntaxKind.CommaToken; - const linesBeforeOperator = getLinesBetweenNodes(node, node.left, operatorToken); - const linesAfterOperator = getLinesBetweenNodes(node, operatorToken, node.right); - writeLinesAndIndent(linesBeforeOperator, isCommaOperator); - emitLeadingCommentsOfPosition(operatorToken.pos); - writeTokenNode(operatorToken, operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator); - emitTrailingCommentsOfPosition(operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts - writeLinesAndIndent(linesAfterOperator, /*writeSpaceIfNotIndenting*/ true); - } + function emitArrowFunction(node: ArrowFunction) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitSignatureAndBody(node, emitArrowFunctionHead); + } - function onRight(next: Expression, _workArea: WorkArea, parent: BinaryExpression) { - return maybeEmitExpression(next, parent, "right"); - } + function emitArrowFunctionHead(node: ArrowFunction) { + emitTypeParameters(node, node.typeParameters); + emitParametersForArrow(node, node.parameters); + emitTypeAnnotation(node.type); + writeSpace(); + emit(node.equalsGreaterThanToken); + } - function onExit(node: BinaryExpression, state: WorkArea) { - const linesBeforeOperator = getLinesBetweenNodes(node, node.left, node.operatorToken); - const linesAfterOperator = getLinesBetweenNodes(node, node.operatorToken, node.right); - decreaseIndentIf(linesBeforeOperator, linesAfterOperator); - if (state.stackIndex > 0) { - const savedPreserveSourceNewlines = state.preserveSourceNewlinesStack[state.stackIndex]; - const savedContainerPos = state.containerPosStack[state.stackIndex]; - const savedContainerEnd = state.containerEndStack[state.stackIndex]; - const savedDeclarationListContainerEnd = state.declarationListContainerEndStack[state.stackIndex]; - const shouldEmitComments = state.shouldEmitCommentsStack[state.stackIndex]; - const shouldEmitSourceMaps = state.shouldEmitSourceMapsStack[state.stackIndex]; - afterEmitNode(savedPreserveSourceNewlines); - if (shouldEmitSourceMaps) emitSourceMapsAfterNode(node); - if (shouldEmitComments) emitCommentsAfterNode(node, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); - onAfterEmitNode?.(node); - state.stackIndex--; - } - } + function emitDeleteExpression(node: DeleteExpression) { + emitTokenWithComment(SyntaxKind.DeleteKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + } - function maybeEmitExpression(next: Expression, parent: BinaryExpression, side: "left" | "right") { - const parenthesizerRule = side === "left" ? - parenthesizer.getParenthesizeLeftSideOfBinaryForOperator(parent.operatorToken.kind) : - parenthesizer.getParenthesizeRightSideOfBinaryForOperator(parent.operatorToken.kind); - - let pipelinePhase = getPipelinePhase(PipelinePhase.Notification, EmitHint.Expression, next); - if (pipelinePhase === pipelineEmitWithSubstitution) { - Debug.assertIsDefined(lastSubstitution); - next = parenthesizerRule(cast(lastSubstitution, isExpression)); - pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, EmitHint.Expression, next); - lastSubstitution = undefined; - } + function emitTypeOfExpression(node: TypeOfExpression) { + emitTokenWithComment(SyntaxKind.TypeOfKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + } - if (pipelinePhase === pipelineEmitWithComments || - pipelinePhase === pipelineEmitWithSourceMaps || - pipelinePhase === pipelineEmitWithHint) { - if (isBinaryExpression(next)) { - return next; - } - } + function emitVoidExpression(node: VoidExpression) { + emitTokenWithComment(SyntaxKind.VoidKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + } - currentParenthesizerRule = parenthesizerRule; - pipelinePhase(EmitHint.Expression, next); - } + function emitAwaitExpression(node: AwaitExpression) { + emitTokenWithComment(SyntaxKind.AwaitKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + } + + function emitPrefixUnaryExpression(node: PrefixUnaryExpression) { + writeTokenText(node.operator, writeOperator); + if (shouldEmitWhitespaceBeforeOperand(node)) { + writeSpace(); } + emitExpression(node.operand, parenthesizer.parenthesizeOperandOfPrefixUnary); + } - function emitConditionalExpression(node: ConditionalExpression) { - const linesBeforeQuestion = getLinesBetweenNodes(node, node.condition, node.questionToken); - const linesAfterQuestion = getLinesBetweenNodes(node, node.questionToken, node.whenTrue); - const linesBeforeColon = getLinesBetweenNodes(node, node.whenTrue, node.colonToken); - const linesAfterColon = getLinesBetweenNodes(node, node.colonToken, node.whenFalse); + function shouldEmitWhitespaceBeforeOperand(node: PrefixUnaryExpression) { + // In some cases, we need to emit a space between the operator and the operand. One obvious case + // is when the operator is an identifier, like delete or typeof. We also need to do this for plus + // and minus expressions in certain cases. Specifically, consider the following two cases (parens + // are just for clarity of exposition, and not part of the source code): + // + // (+(+1)) + // (+(++1)) + // + // We need to emit a space in both cases. In the first case, the absence of a space will make + // the resulting expression a prefix increment operation. And in the second, it will make the resulting + // expression a prefix increment whose operand is a plus expression - (++(+x)) + // The same is true of minus of course. + const operand = node.operand; + return operand.kind === SyntaxKind.PrefixUnaryExpression + && ((node.operator === SyntaxKind.PlusToken && ((operand as PrefixUnaryExpression).operator === SyntaxKind.PlusToken || (operand as PrefixUnaryExpression).operator === SyntaxKind.PlusPlusToken)) + || (node.operator === SyntaxKind.MinusToken && ((operand as PrefixUnaryExpression).operator === SyntaxKind.MinusToken || (operand as PrefixUnaryExpression).operator === SyntaxKind.MinusMinusToken))); + } - emitExpression(node.condition, parenthesizer.parenthesizeConditionOfConditionalExpression); - writeLinesAndIndent(linesBeforeQuestion, /*writeSpaceIfNotIndenting*/ true); - emit(node.questionToken); - writeLinesAndIndent(linesAfterQuestion, /*writeSpaceIfNotIndenting*/ true); - emitExpression(node.whenTrue, parenthesizer.parenthesizeBranchOfConditionalExpression); - decreaseIndentIf(linesBeforeQuestion, linesAfterQuestion); + function emitPostfixUnaryExpression(node: PostfixUnaryExpression) { + emitExpression(node.operand, parenthesizer.parenthesizeOperandOfPostfixUnary); + writeTokenText(node.operator, writeOperator); + } - writeLinesAndIndent(linesBeforeColon, /*writeSpaceIfNotIndenting*/ true); - emit(node.colonToken); - writeLinesAndIndent(linesAfterColon, /*writeSpaceIfNotIndenting*/ true); - emitExpression(node.whenFalse, parenthesizer.parenthesizeBranchOfConditionalExpression); - decreaseIndentIf(linesBeforeColon, linesAfterColon); + function createEmitBinaryExpression() { + interface WorkArea { + stackIndex: number; + preserveSourceNewlinesStack: (boolean | undefined)[]; + containerPosStack: number[]; + containerEndStack: number[]; + declarationListContainerEndStack: number[]; + shouldEmitCommentsStack: boolean[]; + shouldEmitSourceMapsStack: boolean[]; + } + + return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined); + + function onEnter(node: BinaryExpression, state: WorkArea | undefined) { + if (state) { + state.stackIndex++; + state.preserveSourceNewlinesStack[state.stackIndex] = preserveSourceNewlines; + state.containerPosStack[state.stackIndex] = containerPos; + state.containerEndStack[state.stackIndex] = containerEnd; + state.declarationListContainerEndStack[state.stackIndex] = declarationListContainerEnd; + const emitComments = state.shouldEmitCommentsStack[state.stackIndex] = shouldEmitComments(node); + const emitSourceMaps = state.shouldEmitSourceMapsStack[state.stackIndex] = shouldEmitSourceMaps(node); + onBeforeEmitNode?.(node); + if (emitComments) emitCommentsBeforeNode(node); + if (emitSourceMaps) emitSourceMapsBeforeNode(node); + beforeEmitNode(node); + } + else { + state = { + stackIndex: 0, + preserveSourceNewlinesStack: [undefined], + containerPosStack: [-1], + containerEndStack: [-1], + declarationListContainerEndStack: [-1], + shouldEmitCommentsStack: [false], + shouldEmitSourceMapsStack: [false], + }; + } + return state; } - function emitTemplateExpression(node: TemplateExpression) { - emit(node.head); - emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); + function onLeft(next: Expression, _workArea: WorkArea, parent: BinaryExpression) { + return maybeEmitExpression(next, parent, "left"); } - function emitYieldExpression(node: YieldExpression) { - emitTokenWithComment(SyntaxKind.YieldKeyword, node.pos, writeKeyword, node); - emit(node.asteriskToken); - emitExpressionWithLeadingSpace(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); + function onOperator(operatorToken: BinaryOperatorToken, _state: WorkArea, node: BinaryExpression) { + const isCommaOperator = operatorToken.kind !== SyntaxKind.CommaToken; + const linesBeforeOperator = getLinesBetweenNodes(node, node.left, operatorToken); + const linesAfterOperator = getLinesBetweenNodes(node, operatorToken, node.right); + writeLinesAndIndent(linesBeforeOperator, isCommaOperator); + emitLeadingCommentsOfPosition(operatorToken.pos); + writeTokenNode(operatorToken, operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator); + emitTrailingCommentsOfPosition(operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts + writeLinesAndIndent(linesAfterOperator, /*writeSpaceIfNotIndenting*/ true); } - function emitSpreadElement(node: SpreadElement) { - emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); - emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); + function onRight(next: Expression, _workArea: WorkArea, parent: BinaryExpression) { + return maybeEmitExpression(next, parent, "right"); } - function emitClassExpression(node: ClassExpression) { - generateNameIfNeeded(node.name); - emitClassDeclarationOrExpression(node); + function onExit(node: BinaryExpression, state: WorkArea) { + const linesBeforeOperator = getLinesBetweenNodes(node, node.left, node.operatorToken); + const linesAfterOperator = getLinesBetweenNodes(node, node.operatorToken, node.right); + decreaseIndentIf(linesBeforeOperator, linesAfterOperator); + if (state.stackIndex > 0) { + const savedPreserveSourceNewlines = state.preserveSourceNewlinesStack[state.stackIndex]; + const savedContainerPos = state.containerPosStack[state.stackIndex]; + const savedContainerEnd = state.containerEndStack[state.stackIndex]; + const savedDeclarationListContainerEnd = state.declarationListContainerEndStack[state.stackIndex]; + const shouldEmitComments = state.shouldEmitCommentsStack[state.stackIndex]; + const shouldEmitSourceMaps = state.shouldEmitSourceMapsStack[state.stackIndex]; + afterEmitNode(savedPreserveSourceNewlines); + if (shouldEmitSourceMaps) emitSourceMapsAfterNode(node); + if (shouldEmitComments) emitCommentsAfterNode(node, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); + onAfterEmitNode?.(node); + state.stackIndex--; + } } - function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { - emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); - emitTypeArguments(node, node.typeArguments); - } + function maybeEmitExpression(next: Expression, parent: BinaryExpression, side: "left" | "right") { + const parenthesizerRule = side === "left" ? + parenthesizer.getParenthesizeLeftSideOfBinaryForOperator(parent.operatorToken.kind) : + parenthesizer.getParenthesizeRightSideOfBinaryForOperator(parent.operatorToken.kind); - function emitAsExpression(node: AsExpression) { - emitExpression(node.expression, /*parenthesizerRules*/ undefined); - if (node.type) { - writeSpace(); - writeKeyword("as"); - writeSpace(); - emit(node.type); + let pipelinePhase = getPipelinePhase(PipelinePhase.Notification, EmitHint.Expression, next); + if (pipelinePhase === pipelineEmitWithSubstitution) { + Debug.assertIsDefined(lastSubstitution); + next = parenthesizerRule(cast(lastSubstitution, isExpression)); + pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, EmitHint.Expression, next); + lastSubstitution = undefined; } - } - function emitNonNullExpression(node: NonNullExpression) { - emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); - writeOperator("!"); - } + if (pipelinePhase === pipelineEmitWithComments || + pipelinePhase === pipelineEmitWithSourceMaps || + pipelinePhase === pipelineEmitWithHint) { + if (isBinaryExpression(next)) { + return next; + } + } - function emitMetaProperty(node: MetaProperty) { - writeToken(node.keywordToken, node.pos, writePunctuation); - writePunctuation("."); - emit(node.name); + currentParenthesizerRule = parenthesizerRule; + pipelinePhase(EmitHint.Expression, next); } + } - // - // Misc - // + function emitConditionalExpression(node: ConditionalExpression) { + const linesBeforeQuestion = getLinesBetweenNodes(node, node.condition, node.questionToken); + const linesAfterQuestion = getLinesBetweenNodes(node, node.questionToken, node.whenTrue); + const linesBeforeColon = getLinesBetweenNodes(node, node.whenTrue, node.colonToken); + const linesAfterColon = getLinesBetweenNodes(node, node.colonToken, node.whenFalse); + + emitExpression(node.condition, parenthesizer.parenthesizeConditionOfConditionalExpression); + writeLinesAndIndent(linesBeforeQuestion, /*writeSpaceIfNotIndenting*/ true); + emit(node.questionToken); + writeLinesAndIndent(linesAfterQuestion, /*writeSpaceIfNotIndenting*/ true); + emitExpression(node.whenTrue, parenthesizer.parenthesizeBranchOfConditionalExpression); + decreaseIndentIf(linesBeforeQuestion, linesAfterQuestion); + + writeLinesAndIndent(linesBeforeColon, /*writeSpaceIfNotIndenting*/ true); + emit(node.colonToken); + writeLinesAndIndent(linesAfterColon, /*writeSpaceIfNotIndenting*/ true); + emitExpression(node.whenFalse, parenthesizer.parenthesizeBranchOfConditionalExpression); + decreaseIndentIf(linesBeforeColon, linesAfterColon); + } - function emitTemplateSpan(node: TemplateSpan) { - emitExpression(node.expression); - emit(node.literal); - } + function emitTemplateExpression(node: TemplateExpression) { + emit(node.head); + emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); + } - // - // Statements - // + function emitYieldExpression(node: YieldExpression) { + emitTokenWithComment(SyntaxKind.YieldKeyword, node.pos, writeKeyword, node); + emit(node.asteriskToken); + emitExpressionWithLeadingSpace(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); + } + + function emitSpreadElement(node: SpreadElement) { + emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); + } - function emitBlock(node: Block) { - emitBlockStatements(node, /*forceSingleLine*/ !node.multiLine && isEmptyBlock(node)); + function emitClassExpression(node: ClassExpression) { + generateNameIfNeeded(node.name); + emitClassDeclarationOrExpression(node); + } + + function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + emitTypeArguments(node, node.typeArguments); + } + + function emitAsExpression(node: AsExpression) { + emitExpression(node.expression, /*parenthesizerRules*/ undefined); + if (node.type) { + writeSpace(); + writeKeyword("as"); + writeSpace(); + emit(node.type); } + } + + function emitNonNullExpression(node: NonNullExpression) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + writeOperator("!"); + } + + function emitMetaProperty(node: MetaProperty) { + writeToken(node.keywordToken, node.pos, writePunctuation); + writePunctuation("."); + emit(node.name); + } + + // + // Misc + // + + function emitTemplateSpan(node: TemplateSpan) { + emitExpression(node.expression); + emit(node.literal); + } + + // + // Statements + // - function emitBlockStatements(node: BlockLike, forceSingleLine: boolean) { - emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, /*contextNode*/ node); - const format = forceSingleLine || getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineBlockStatements : ListFormat.MultiLineBlockStatements; - emitList(node, node.statements, format); - emitTokenWithComment(SyntaxKind.CloseBraceToken, node.statements.end, writePunctuation, /*contextNode*/ node, /*indentLeading*/ !!(format & ListFormat.MultiLine)); + function emitBlock(node: Block) { + emitBlockStatements(node, /*forceSingleLine*/ !node.multiLine && isEmptyBlock(node)); + } + + function emitBlockStatements(node: BlockLike, forceSingleLine: boolean) { + emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, /*contextNode*/ node); + const format = forceSingleLine || getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineBlockStatements : ListFormat.MultiLineBlockStatements; + emitList(node, node.statements, format); + emitTokenWithComment(SyntaxKind.CloseBraceToken, node.statements.end, writePunctuation, /*contextNode*/ node, /*indentLeading*/ !!(format & ListFormat.MultiLine)); + } + + function emitVariableStatement(node: VariableStatement) { + emitModifiers(node, node.modifiers); + emit(node.declarationList); + writeTrailingSemicolon(); + } + + function emitEmptyStatement(isEmbeddedStatement: boolean) { + // While most trailing semicolons are possibly insignificant, an embedded "empty" + // statement is significant and cannot be elided by a trailing-semicolon-omitting writer. + if (isEmbeddedStatement) { + writePunctuation(";"); + } + else { + writeTrailingSemicolon(); } + } - function emitVariableStatement(node: VariableStatement) { - emitModifiers(node, node.modifiers); - emit(node.declarationList); + function emitExpressionStatement(node: ExpressionStatement) { + emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfExpressionStatement); + // Emit semicolon in non json files + // or if json file that created synthesized expression(eg.define expression statement when --out and amd code generation) + if (!isJsonSourceFile(currentSourceFile!) || nodeIsSynthesized(node.expression)) { writeTrailingSemicolon(); } + } - function emitEmptyStatement(isEmbeddedStatement: boolean) { - // While most trailing semicolons are possibly insignificant, an embedded "empty" - // statement is significant and cannot be elided by a trailing-semicolon-omitting writer. - if (isEmbeddedStatement) { - writePunctuation(";"); + function emitIfStatement(node: IfStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.IfKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.thenStatement); + if (node.elseStatement) { + writeLineOrSpace(node, node.thenStatement, node.elseStatement); + emitTokenWithComment(SyntaxKind.ElseKeyword, node.thenStatement.end, writeKeyword, node); + if (node.elseStatement.kind === SyntaxKind.IfStatement) { + writeSpace(); + emit(node.elseStatement); } else { - writeTrailingSemicolon(); + emitEmbeddedStatement(node, node.elseStatement); } } + } - function emitExpressionStatement(node: ExpressionStatement) { - emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfExpressionStatement); - // Emit semicolon in non json files - // or if json file that created synthesized expression(eg.define expression statement when --out and amd code generation) - if (!isJsonSourceFile(currentSourceFile!) || nodeIsSynthesized(node.expression)) { - writeTrailingSemicolon(); - } - } + function emitWhileClause(node: WhileStatement | DoStatement, startPos: number) { + const openParenPos = emitTokenWithComment(SyntaxKind.WhileKeyword, startPos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + } - function emitIfStatement(node: IfStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.IfKeyword, node.pos, writeKeyword, node); + function emitDoStatement(node: DoStatement) { + emitTokenWithComment(SyntaxKind.DoKeyword, node.pos, writeKeyword, node); + emitEmbeddedStatement(node, node.statement); + if (isBlock(node.statement) && !preserveSourceNewlines) { writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.thenStatement); - if (node.elseStatement) { - writeLineOrSpace(node, node.thenStatement, node.elseStatement); - emitTokenWithComment(SyntaxKind.ElseKeyword, node.thenStatement.end, writeKeyword, node); - if (node.elseStatement.kind === SyntaxKind.IfStatement) { - writeSpace(); - emit(node.elseStatement); - } - else { - emitEmbeddedStatement(node, node.elseStatement); - } - } } - - function emitWhileClause(node: WhileStatement | DoStatement, startPos: number) { - const openParenPos = emitTokenWithComment(SyntaxKind.WhileKeyword, startPos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + else { + writeLineOrSpace(node, node.statement, node.expression); } - function emitDoStatement(node: DoStatement) { - emitTokenWithComment(SyntaxKind.DoKeyword, node.pos, writeKeyword, node); - emitEmbeddedStatement(node, node.statement); - if (isBlock(node.statement) && !preserveSourceNewlines) { - writeSpace(); - } - else { - writeLineOrSpace(node, node.statement, node.expression); - } + emitWhileClause(node, node.statement.end); + writeTrailingSemicolon(); + } - emitWhileClause(node, node.statement.end); - writeTrailingSemicolon(); - } + function emitWhileStatement(node: WhileStatement) { + emitWhileClause(node, node.pos); + emitEmbeddedStatement(node, node.statement); + } - function emitWhileStatement(node: WhileStatement) { - emitWhileClause(node, node.pos); - emitEmbeddedStatement(node, node.statement); - } + function emitForStatement(node: ForStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); + writeSpace(); + let pos = emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, /*contextNode*/ node); + emitForBinding(node.initializer); + pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.initializer ? node.initializer.end : pos, writePunctuation, node); + emitExpressionWithLeadingSpace(node.condition); + pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.condition ? node.condition.end : pos, writePunctuation, node); + emitExpressionWithLeadingSpace(node.incrementor); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.incrementor ? node.incrementor.end : pos, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); + } - function emitForStatement(node: ForStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); - writeSpace(); - let pos = emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, /*contextNode*/ node); - emitForBinding(node.initializer); - pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.initializer ? node.initializer.end : pos, writePunctuation, node); - emitExpressionWithLeadingSpace(node.condition); - pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.condition ? node.condition.end : pos, writePunctuation, node); - emitExpressionWithLeadingSpace(node.incrementor); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.incrementor ? node.incrementor.end : pos, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } - - function emitForInStatement(node: ForInStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitForBinding(node.initializer); - writeSpace(); - emitTokenWithComment(SyntaxKind.InKeyword, node.initializer.end, writeKeyword, node); - writeSpace(); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } + function emitForInStatement(node: ForInStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitForBinding(node.initializer); + writeSpace(); + emitTokenWithComment(SyntaxKind.InKeyword, node.initializer.end, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); + } - function emitForOfStatement(node: ForOfStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitWithTrailingSpace(node.awaitModifier); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitForBinding(node.initializer); - writeSpace(); - emitTokenWithComment(SyntaxKind.OfKeyword, node.initializer.end, writeKeyword, node); - writeSpace(); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } + function emitForOfStatement(node: ForOfStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitWithTrailingSpace(node.awaitModifier); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitForBinding(node.initializer); + writeSpace(); + emitTokenWithComment(SyntaxKind.OfKeyword, node.initializer.end, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); + } - function emitForBinding(node: VariableDeclarationList | Expression | undefined) { - if (node !== undefined) { - if (node.kind === SyntaxKind.VariableDeclarationList) { - emit(node); - } - else { - emitExpression(node); - } + function emitForBinding(node: VariableDeclarationList | Expression | undefined) { + if (node !== undefined) { + if (node.kind === SyntaxKind.VariableDeclarationList) { + emit(node); + } + else { + emitExpression(node); } } + } - function emitContinueStatement(node: ContinueStatement) { - emitTokenWithComment(SyntaxKind.ContinueKeyword, node.pos, writeKeyword, node); - emitWithLeadingSpace(node.label); - writeTrailingSemicolon(); - } - - function emitBreakStatement(node: BreakStatement) { - emitTokenWithComment(SyntaxKind.BreakKeyword, node.pos, writeKeyword, node); - emitWithLeadingSpace(node.label); - writeTrailingSemicolon(); - } + function emitContinueStatement(node: ContinueStatement) { + emitTokenWithComment(SyntaxKind.ContinueKeyword, node.pos, writeKeyword, node); + emitWithLeadingSpace(node.label); + writeTrailingSemicolon(); + } - function emitTokenWithComment(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode: Node, indentLeading?: boolean) { - const node = getParseTreeNode(contextNode); - const isSimilarNode = node && node.kind === contextNode.kind; - const startPos = pos; - if (isSimilarNode && currentSourceFile) { - pos = skipTrivia(currentSourceFile.text, pos); - } - if (isSimilarNode && contextNode.pos !== startPos) { - const needsIndent = indentLeading && currentSourceFile && !positionsAreOnSameLine(startPos, pos, currentSourceFile); - if (needsIndent) { - increaseIndent(); - } - emitLeadingCommentsOfPosition(startPos); - if (needsIndent) { - decreaseIndent(); - } + function emitBreakStatement(node: BreakStatement) { + emitTokenWithComment(SyntaxKind.BreakKeyword, node.pos, writeKeyword, node); + emitWithLeadingSpace(node.label); + writeTrailingSemicolon(); + } + + function emitTokenWithComment(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode: Node, indentLeading?: boolean) { + const node = getParseTreeNode(contextNode); + const isSimilarNode = node && node.kind === contextNode.kind; + const startPos = pos; + if (isSimilarNode && currentSourceFile) { + pos = skipTrivia(currentSourceFile.text, pos); + } + if (isSimilarNode && contextNode.pos !== startPos) { + const needsIndent = indentLeading && currentSourceFile && !positionsAreOnSameLine(startPos, pos, currentSourceFile); + if (needsIndent) { + increaseIndent(); } - pos = writeTokenText(token, writer, pos); - if (isSimilarNode && contextNode.end !== pos) { - const isJsxExprContext = contextNode.kind === SyntaxKind.JsxExpression; - emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ !isJsxExprContext, /*forceNoNewline*/ isJsxExprContext); + emitLeadingCommentsOfPosition(startPos); + if (needsIndent) { + decreaseIndent(); } - return pos; } - - function emitReturnStatement(node: ReturnStatement) { - emitTokenWithComment(SyntaxKind.ReturnKeyword, node.pos, writeKeyword, /*contextNode*/ node); - emitExpressionWithLeadingSpace(node.expression); - writeTrailingSemicolon(); + pos = writeTokenText(token, writer, pos); + if (isSimilarNode && contextNode.end !== pos) { + const isJsxExprContext = contextNode.kind === SyntaxKind.JsxExpression; + emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ !isJsxExprContext, /*forceNoNewline*/ isJsxExprContext); } + return pos; + } - function emitWithStatement(node: WithStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.WithKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } + function emitReturnStatement(node: ReturnStatement) { + emitTokenWithComment(SyntaxKind.ReturnKeyword, node.pos, writeKeyword, /*contextNode*/ node); + emitExpressionWithLeadingSpace(node.expression); + writeTrailingSemicolon(); + } - function emitSwitchStatement(node: SwitchStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.SwitchKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - writeSpace(); - emit(node.caseBlock); - } + function emitWithStatement(node: WithStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.WithKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); + } - function emitLabeledStatement(node: LabeledStatement) { - emit(node.label); - emitTokenWithComment(SyntaxKind.ColonToken, node.label.end, writePunctuation, node); - writeSpace(); - emit(node.statement); - } + function emitSwitchStatement(node: SwitchStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.SwitchKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + writeSpace(); + emit(node.caseBlock); + } - function emitThrowStatement(node: ThrowStatement) { - emitTokenWithComment(SyntaxKind.ThrowKeyword, node.pos, writeKeyword, node); - emitExpressionWithLeadingSpace(node.expression); - writeTrailingSemicolon(); - } + function emitLabeledStatement(node: LabeledStatement) { + emit(node.label); + emitTokenWithComment(SyntaxKind.ColonToken, node.label.end, writePunctuation, node); + writeSpace(); + emit(node.statement); + } + + function emitThrowStatement(node: ThrowStatement) { + emitTokenWithComment(SyntaxKind.ThrowKeyword, node.pos, writeKeyword, node); + emitExpressionWithLeadingSpace(node.expression); + writeTrailingSemicolon(); + } - function emitTryStatement(node: TryStatement) { - emitTokenWithComment(SyntaxKind.TryKeyword, node.pos, writeKeyword, node); + function emitTryStatement(node: TryStatement) { + emitTokenWithComment(SyntaxKind.TryKeyword, node.pos, writeKeyword, node); + writeSpace(); + emit(node.tryBlock); + if (node.catchClause) { + writeLineOrSpace(node, node.tryBlock, node.catchClause); + emit(node.catchClause); + } + if (node.finallyBlock) { + writeLineOrSpace(node, node.catchClause || node.tryBlock, node.finallyBlock); + emitTokenWithComment(SyntaxKind.FinallyKeyword, (node.catchClause || node.tryBlock).end, writeKeyword, node); writeSpace(); - emit(node.tryBlock); - if (node.catchClause) { - writeLineOrSpace(node, node.tryBlock, node.catchClause); - emit(node.catchClause); - } - if (node.finallyBlock) { - writeLineOrSpace(node, node.catchClause || node.tryBlock, node.finallyBlock); - emitTokenWithComment(SyntaxKind.FinallyKeyword, (node.catchClause || node.tryBlock).end, writeKeyword, node); - writeSpace(); - emit(node.finallyBlock); - } + emit(node.finallyBlock); } + } - function emitDebuggerStatement(node: DebuggerStatement) { - writeToken(SyntaxKind.DebuggerKeyword, node.pos, writeKeyword); - writeTrailingSemicolon(); - } + function emitDebuggerStatement(node: DebuggerStatement) { + writeToken(SyntaxKind.DebuggerKeyword, node.pos, writeKeyword); + writeTrailingSemicolon(); + } - // - // Declarations - // + // + // Declarations + // - function emitVariableDeclaration(node: VariableDeclaration) { - emit(node.name); - emit(node.exclamationToken); - emitTypeAnnotation(node.type); - emitInitializer(node.initializer, node.type ? node.type.end : node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); - } + function emitVariableDeclaration(node: VariableDeclaration) { + emit(node.name); + emit(node.exclamationToken); + emitTypeAnnotation(node.type); + emitInitializer(node.initializer, node.type ? node.type.end : node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); + } - function emitVariableDeclarationList(node: VariableDeclarationList) { - writeKeyword(isLet(node) ? "let" : isVarConst(node) ? "const" : "var"); - writeSpace(); - emitList(node, node.declarations, ListFormat.VariableDeclarationList); - } + function emitVariableDeclarationList(node: VariableDeclarationList) { + writeKeyword(isLet(node) ? "let" : isVarConst(node) ? "const" : "var"); + writeSpace(); + emitList(node, node.declarations, ListFormat.VariableDeclarationList); + } - function emitFunctionDeclaration(node: FunctionDeclaration) { - emitFunctionDeclarationOrExpression(node); - } + function emitFunctionDeclaration(node: FunctionDeclaration) { + emitFunctionDeclarationOrExpression(node); + } - function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("function"); - emit(node.asteriskToken); - writeSpace(); - emitIdentifierName(node.name); - emitSignatureAndBody(node, emitSignatureHead); - } + function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("function"); + emit(node.asteriskToken); + writeSpace(); + emitIdentifierName(node.name); + emitSignatureAndBody(node, emitSignatureHead); + } - function emitSignatureAndBody(node: FunctionLikeDeclaration, emitSignatureHead: (node: SignatureDeclaration) => void) { - const body = node.body; - if (body) { - if (isBlock(body)) { - const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; - if (indentedFlag) { - increaseIndent(); - } + function emitSignatureAndBody(node: FunctionLikeDeclaration, emitSignatureHead: (node: SignatureDeclaration) => void) { + const body = node.body; + if (body) { + if (isBlock(body)) { + const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); + } - pushNameGenerationScope(node); - forEach(node.parameters, generateNames); - generateNames(node.body); + pushNameGenerationScope(node); + forEach(node.parameters, generateNames); + generateNames(node.body); - emitSignatureHead(node); - emitBlockFunctionBody(body); - popNameGenerationScope(node); + emitSignatureHead(node); + emitBlockFunctionBody(body); + popNameGenerationScope(node); - if (indentedFlag) { - decreaseIndent(); - } - } - else { - emitSignatureHead(node); - writeSpace(); - emitExpression(body, parenthesizer.parenthesizeConciseBodyOfArrowFunction); + if (indentedFlag) { + decreaseIndent(); } } else { emitSignatureHead(node); - writeTrailingSemicolon(); + writeSpace(); + emitExpression(body, parenthesizer.parenthesizeConciseBodyOfArrowFunction); } - + } + else { + emitSignatureHead(node); + writeTrailingSemicolon(); } - function emitSignatureHead(node: FunctionDeclaration | FunctionExpression | MethodDeclaration | AccessorDeclaration | ConstructorDeclaration) { - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); + } + + function emitSignatureHead(node: FunctionDeclaration | FunctionExpression | MethodDeclaration | AccessorDeclaration | ConstructorDeclaration) { + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + } + + function shouldEmitBlockFunctionBodyOnSingleLine(body: Block) { + // We must emit a function body as a single-line body in the following case: + // * The body has NodeEmitFlags.SingleLine specified. + + // We must emit a function body as a multi-line body in the following cases: + // * The body is explicitly marked as multi-line. + // * A non-synthesized body's start and end position are on different lines. + // * Any statement in the body starts on a new line. + + if (getEmitFlags(body) & EmitFlags.SingleLine) { + return true; } - function shouldEmitBlockFunctionBodyOnSingleLine(body: Block) { - // We must emit a function body as a single-line body in the following case: - // * The body has NodeEmitFlags.SingleLine specified. + if (body.multiLine) { + return false; + } - // We must emit a function body as a multi-line body in the following cases: - // * The body is explicitly marked as multi-line. - // * A non-synthesized body's start and end position are on different lines. - // * Any statement in the body starts on a new line. + if (!nodeIsSynthesized(body) && !rangeIsOnSingleLine(body, currentSourceFile!)) { + return false; + } - if (getEmitFlags(body) & EmitFlags.SingleLine) { - return true; - } + if (getLeadingLineTerminatorCount(body, body.statements, ListFormat.PreserveLines) + || getClosingLineTerminatorCount(body, body.statements, ListFormat.PreserveLines)) { + return false; + } - if (body.multiLine) { + let previousStatement: Statement | undefined; + for (const statement of body.statements) { + if (getSeparatingLineTerminatorCount(previousStatement, statement, ListFormat.PreserveLines) > 0) { return false; } - if (!nodeIsSynthesized(body) && !rangeIsOnSingleLine(body, currentSourceFile!)) { - return false; - } + previousStatement = statement; + } - if (getLeadingLineTerminatorCount(body, body.statements, ListFormat.PreserveLines) - || getClosingLineTerminatorCount(body, body.statements, ListFormat.PreserveLines)) { - return false; - } + return true; + } - let previousStatement: Statement | undefined; - for (const statement of body.statements) { - if (getSeparatingLineTerminatorCount(previousStatement, statement, ListFormat.PreserveLines) > 0) { - return false; - } + function emitBlockFunctionBody(body: Block) { + onBeforeEmitNode?.(body); + writeSpace(); + writePunctuation("{"); + increaseIndent(); - previousStatement = statement; - } + const emitBlockFunctionBody = shouldEmitBlockFunctionBodyOnSingleLine(body) + ? emitBlockFunctionBodyOnSingleLine + : emitBlockFunctionBodyWorker; - return true; + if (emitBodyWithDetachedComments) { + emitBodyWithDetachedComments(body, body.statements, emitBlockFunctionBody); + } + else { + emitBlockFunctionBody(body); } - function emitBlockFunctionBody(body: Block) { - onBeforeEmitNode?.(body); - writeSpace(); - writePunctuation("{"); - increaseIndent(); - - const emitBlockFunctionBody = shouldEmitBlockFunctionBodyOnSingleLine(body) - ? emitBlockFunctionBodyOnSingleLine - : emitBlockFunctionBodyWorker; + decreaseIndent(); + writeToken(SyntaxKind.CloseBraceToken, body.statements.end, writePunctuation, body); + onAfterEmitNode?.(body); + } - if (emitBodyWithDetachedComments) { - emitBodyWithDetachedComments(body, body.statements, emitBlockFunctionBody); - } - else { - emitBlockFunctionBody(body); - } + function emitBlockFunctionBodyOnSingleLine(body: Block) { + emitBlockFunctionBodyWorker(body, /*emitBlockFunctionBodyOnSingleLine*/ true); + } + function emitBlockFunctionBodyWorker(body: Block, emitBlockFunctionBodyOnSingleLine?: boolean) { + // Emit all the prologue directives (like "use strict"). + const statementOffset = emitPrologueDirectives(body.statements); + const pos = writer.getTextPos(); + emitHelpers(body); + if (statementOffset === 0 && pos === writer.getTextPos() && emitBlockFunctionBodyOnSingleLine) { decreaseIndent(); - writeToken(SyntaxKind.CloseBraceToken, body.statements.end, writePunctuation, body); - onAfterEmitNode?.(body); + emitList(body, body.statements, ListFormat.SingleLineFunctionBodyStatements); + increaseIndent(); } - - function emitBlockFunctionBodyOnSingleLine(body: Block) { - emitBlockFunctionBodyWorker(body, /*emitBlockFunctionBodyOnSingleLine*/ true); + else { + emitList(body, body.statements, ListFormat.MultiLineFunctionBodyStatements, /*parenthesizerRule*/ undefined, statementOffset); } + } - function emitBlockFunctionBodyWorker(body: Block, emitBlockFunctionBodyOnSingleLine?: boolean) { - // Emit all the prologue directives (like "use strict"). - const statementOffset = emitPrologueDirectives(body.statements); - const pos = writer.getTextPos(); - emitHelpers(body); - if (statementOffset === 0 && pos === writer.getTextPos() && emitBlockFunctionBodyOnSingleLine) { - decreaseIndent(); - emitList(body, body.statements, ListFormat.SingleLineFunctionBodyStatements); - increaseIndent(); - } - else { - emitList(body, body.statements, ListFormat.MultiLineFunctionBodyStatements, /*parenthesizerRule*/ undefined, statementOffset); - } + function emitClassDeclaration(node: ClassDeclaration) { + emitClassDeclarationOrExpression(node); + } + + function emitClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression) { + forEach(node.members, generateMemberNames); + + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("class"); + if (node.name) { + writeSpace(); + emitIdentifierName(node.name); } - function emitClassDeclaration(node: ClassDeclaration) { - emitClassDeclarationOrExpression(node); + const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); } - function emitClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression) { - forEach(node.members, generateMemberNames); + emitTypeParameters(node, node.typeParameters); + emitList(node, node.heritageClauses, ListFormat.ClassHeritageClauses); - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("class"); - if (node.name) { - writeSpace(); - emitIdentifierName(node.name); - } + writeSpace(); + writePunctuation("{"); + emitList(node, node.members, ListFormat.ClassMembers); + writePunctuation("}"); - const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; - if (indentedFlag) { - increaseIndent(); - } + if (indentedFlag) { + decreaseIndent(); + } + } - emitTypeParameters(node, node.typeParameters); - emitList(node, node.heritageClauses, ListFormat.ClassHeritageClauses); + function emitInterfaceDeclaration(node: InterfaceDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("interface"); + writeSpace(); + emit(node.name); + emitTypeParameters(node, node.typeParameters); + emitList(node, node.heritageClauses, ListFormat.HeritageClauses); + writeSpace(); + writePunctuation("{"); + emitList(node, node.members, ListFormat.InterfaceMembers); + writePunctuation("}"); + } - writeSpace(); - writePunctuation("{"); - emitList(node, node.members, ListFormat.ClassMembers); - writePunctuation("}"); + function emitTypeAliasDeclaration(node: TypeAliasDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("type"); + writeSpace(); + emit(node.name); + emitTypeParameters(node, node.typeParameters); + writeSpace(); + writePunctuation("="); + writeSpace(); + emit(node.type); + writeTrailingSemicolon(); + } - if (indentedFlag) { - decreaseIndent(); - } - } + function emitEnumDeclaration(node: EnumDeclaration) { + emitModifiers(node, node.modifiers); + writeKeyword("enum"); + writeSpace(); + emit(node.name); - function emitInterfaceDeclaration(node: InterfaceDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("interface"); - writeSpace(); - emit(node.name); - emitTypeParameters(node, node.typeParameters); - emitList(node, node.heritageClauses, ListFormat.HeritageClauses); + writeSpace(); + writePunctuation("{"); + emitList(node, node.members, ListFormat.EnumMembers); + writePunctuation("}"); + } + + function emitModuleDeclaration(node: ModuleDeclaration) { + emitModifiers(node, node.modifiers); + if (~node.flags & NodeFlags.GlobalAugmentation) { + writeKeyword(node.flags & NodeFlags.Namespace ? "namespace" : "module"); writeSpace(); - writePunctuation("{"); - emitList(node, node.members, ListFormat.InterfaceMembers); - writePunctuation("}"); } + emit(node.name); - function emitTypeAliasDeclaration(node: TypeAliasDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("type"); - writeSpace(); - emit(node.name); - emitTypeParameters(node, node.typeParameters); - writeSpace(); - writePunctuation("="); - writeSpace(); - emit(node.type); - writeTrailingSemicolon(); + let body = node.body; + if (!body) return writeTrailingSemicolon(); + while (body && isModuleDeclaration(body)) { + writePunctuation("."); + emit(body.name); + body = body.body; } - function emitEnumDeclaration(node: EnumDeclaration) { - emitModifiers(node, node.modifiers); - writeKeyword("enum"); - writeSpace(); - emit(node.name); + writeSpace(); + emit(body); + } - writeSpace(); - writePunctuation("{"); - emitList(node, node.members, ListFormat.EnumMembers); - writePunctuation("}"); - } + function emitModuleBlock(node: ModuleBlock) { + pushNameGenerationScope(node); + forEach(node.statements, generateNames); + emitBlockStatements(node, /*forceSingleLine*/ isEmptyBlock(node)); + popNameGenerationScope(node); + } - function emitModuleDeclaration(node: ModuleDeclaration) { - emitModifiers(node, node.modifiers); - if (~node.flags & NodeFlags.GlobalAugmentation) { - writeKeyword(node.flags & NodeFlags.Namespace ? "namespace" : "module"); - writeSpace(); - } - emit(node.name); - - let body = node.body; - if (!body) return writeTrailingSemicolon(); - while (body && isModuleDeclaration(body)) { - writePunctuation("."); - emit(body.name); - body = body.body; - } + function emitCaseBlock(node: CaseBlock) { + emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); + emitList(node, node.clauses, ListFormat.CaseBlockClauses); + emitTokenWithComment(SyntaxKind.CloseBraceToken, node.clauses.end, writePunctuation, node, /*indentLeading*/ true); + } + function emitImportEqualsDeclaration(node: ImportEqualsDeclaration) { + emitModifiers(node, node.modifiers); + emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); + writeSpace(); + if (node.isTypeOnly) { + emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node); writeSpace(); - emit(body); } + emit(node.name); + writeSpace(); + emitTokenWithComment(SyntaxKind.EqualsToken, node.name.end, writePunctuation, node); + writeSpace(); + emitModuleReference(node.moduleReference); + writeTrailingSemicolon(); + } - function emitModuleBlock(node: ModuleBlock) { - pushNameGenerationScope(node); - forEach(node.statements, generateNames); - emitBlockStatements(node, /*forceSingleLine*/ isEmptyBlock(node)); - popNameGenerationScope(node); + function emitModuleReference(node: ModuleReference) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); } - - function emitCaseBlock(node: CaseBlock) { - emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); - emitList(node, node.clauses, ListFormat.CaseBlockClauses); - emitTokenWithComment(SyntaxKind.CloseBraceToken, node.clauses.end, writePunctuation, node, /*indentLeading*/ true); + else { + emit(node); } + } - function emitImportEqualsDeclaration(node: ImportEqualsDeclaration) { - emitModifiers(node, node.modifiers); - emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); - writeSpace(); - if (node.isTypeOnly) { - emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node); - writeSpace(); - } - emit(node.name); + function emitImportDeclaration(node: ImportDeclaration) { + emitModifiers(node, node.modifiers); + emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); + writeSpace(); + if (node.importClause) { + emit(node.importClause); writeSpace(); - emitTokenWithComment(SyntaxKind.EqualsToken, node.name.end, writePunctuation, node); + emitTokenWithComment(SyntaxKind.FromKeyword, node.importClause.end, writeKeyword, node); writeSpace(); - emitModuleReference(node.moduleReference); - writeTrailingSemicolon(); } - - function emitModuleReference(node: ModuleReference) { - if (node.kind === SyntaxKind.Identifier) { - emitExpression(node); - } - else { - emit(node); - } + emitExpression(node.moduleSpecifier); + if (node.assertClause) { + emitWithLeadingSpace(node.assertClause); } + writeTrailingSemicolon(); + } - function emitImportDeclaration(node: ImportDeclaration) { - emitModifiers(node, node.modifiers); - emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); + function emitImportClause(node: ImportClause) { + if (node.isTypeOnly) { + emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node); + writeSpace(); + } + emit(node.name); + if (node.name && node.namedBindings) { + emitTokenWithComment(SyntaxKind.CommaToken, node.name.end, writePunctuation, node); writeSpace(); - if (node.importClause) { - emit(node.importClause); - writeSpace(); - emitTokenWithComment(SyntaxKind.FromKeyword, node.importClause.end, writeKeyword, node); - writeSpace(); - } - emitExpression(node.moduleSpecifier); - if (node.assertClause) { - emitWithLeadingSpace(node.assertClause); - } - writeTrailingSemicolon(); } + emit(node.namedBindings); + } - function emitImportClause(node: ImportClause) { - if (node.isTypeOnly) { - emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node); - writeSpace(); - } - emit(node.name); - if (node.name && node.namedBindings) { - emitTokenWithComment(SyntaxKind.CommaToken, node.name.end, writePunctuation, node); - writeSpace(); - } - emit(node.namedBindings); + function emitNamespaceImport(node: NamespaceImport) { + const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); + writeSpace(); + emit(node.name); + } + + function emitNamedImports(node: NamedImports) { + emitNamedImportsOrExports(node); + } + + function emitImportSpecifier(node: ImportSpecifier) { + emitImportOrExportSpecifier(node); + } + + function emitExportAssignment(node: ExportAssignment) { + const nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeSpace(); + if (node.isExportEquals) { + emitTokenWithComment(SyntaxKind.EqualsToken, nextPos, writeOperator, node); } + else { + emitTokenWithComment(SyntaxKind.DefaultKeyword, nextPos, writeKeyword, node); + } + writeSpace(); + emitExpression(node.expression, node.isExportEquals ? + parenthesizer.getParenthesizeRightSideOfBinaryForOperator(SyntaxKind.EqualsToken) : + parenthesizer.parenthesizeExpressionOfExportDefault); + writeTrailingSemicolon(); + } - function emitNamespaceImport(node: NamespaceImport) { - const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); + function emitExportDeclaration(node: ExportDeclaration) { + let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeSpace(); + if (node.isTypeOnly) { + nextPos = emitTokenWithComment(SyntaxKind.TypeKeyword, nextPos, writeKeyword, node); writeSpace(); - emit(node.name); } - - function emitNamedImports(node: NamedImports) { - emitNamedImportsOrExports(node); + if (node.exportClause) { + emit(node.exportClause); } - - function emitImportSpecifier(node: ImportSpecifier) { - emitImportOrExportSpecifier(node); + else { + nextPos = emitTokenWithComment(SyntaxKind.AsteriskToken, nextPos, writePunctuation, node); } - - function emitExportAssignment(node: ExportAssignment) { - const nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + if (node.moduleSpecifier) { writeSpace(); - if (node.isExportEquals) { - emitTokenWithComment(SyntaxKind.EqualsToken, nextPos, writeOperator, node); - } - else { - emitTokenWithComment(SyntaxKind.DefaultKeyword, nextPos, writeKeyword, node); - } + const fromPos = node.exportClause ? node.exportClause.end : nextPos; + emitTokenWithComment(SyntaxKind.FromKeyword, fromPos, writeKeyword, node); writeSpace(); - emitExpression(node.expression, node.isExportEquals ? - parenthesizer.getParenthesizeRightSideOfBinaryForOperator(SyntaxKind.EqualsToken) : - parenthesizer.parenthesizeExpressionOfExportDefault); - writeTrailingSemicolon(); + emitExpression(node.moduleSpecifier); } - - function emitExportDeclaration(node: ExportDeclaration) { - let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); - writeSpace(); - if (node.isTypeOnly) { - nextPos = emitTokenWithComment(SyntaxKind.TypeKeyword, nextPos, writeKeyword, node); - writeSpace(); - } - if (node.exportClause) { - emit(node.exportClause); - } - else { - nextPos = emitTokenWithComment(SyntaxKind.AsteriskToken, nextPos, writePunctuation, node); - } - if (node.moduleSpecifier) { - writeSpace(); - const fromPos = node.exportClause ? node.exportClause.end : nextPos; - emitTokenWithComment(SyntaxKind.FromKeyword, fromPos, writeKeyword, node); - writeSpace(); - emitExpression(node.moduleSpecifier); - } - if (node.assertClause) { - emitWithLeadingSpace(node.assertClause); - } - writeTrailingSemicolon(); + if (node.assertClause) { + emitWithLeadingSpace(node.assertClause); } + writeTrailingSemicolon(); + } - function emitAssertClause(node: AssertClause) { - emitTokenWithComment(SyntaxKind.AssertKeyword, node.pos, writeKeyword, node); - writeSpace(); - const elements = node.elements; - emitList(node, elements, ListFormat.ImportClauseEntries); - } + function emitAssertClause(node: AssertClause) { + emitTokenWithComment(SyntaxKind.AssertKeyword, node.pos, writeKeyword, node); + writeSpace(); + const elements = node.elements; + emitList(node, elements, ListFormat.ImportClauseEntries); + } - function emitAssertEntry(node: AssertEntry) { - emit(node.name); - writePunctuation(":"); - writeSpace(); + function emitAssertEntry(node: AssertEntry) { + emit(node.name); + writePunctuation(":"); + writeSpace(); - const value = node.value; - /** @see {emitPropertyAssignment} */ - if ((getEmitFlags(value) & EmitFlags.NoLeadingComments) === 0) { - const commentRange = getCommentRange(value); - emitTrailingCommentsOfPosition(commentRange.pos); - } - emit(value); + const value = node.value; + /** @see {emitPropertyAssignment} */ + if ((getEmitFlags(value) & EmitFlags.NoLeadingComments) === 0) { + const commentRange = getCommentRange(value); + emitTrailingCommentsOfPosition(commentRange.pos); } + emit(value); + } - function emitNamespaceExportDeclaration(node: NamespaceExportDeclaration) { - let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); - writeSpace(); - nextPos = emitTokenWithComment(SyntaxKind.AsKeyword, nextPos, writeKeyword, node); - writeSpace(); - nextPos = emitTokenWithComment(SyntaxKind.NamespaceKeyword, nextPos, writeKeyword, node); + function emitNamespaceExportDeclaration(node: NamespaceExportDeclaration) { + let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeSpace(); + nextPos = emitTokenWithComment(SyntaxKind.AsKeyword, nextPos, writeKeyword, node); + writeSpace(); + nextPos = emitTokenWithComment(SyntaxKind.NamespaceKeyword, nextPos, writeKeyword, node); + writeSpace(); + emit(node.name); + writeTrailingSemicolon(); + } + + function emitNamespaceExport(node: NamespaceExport) { + const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); + writeSpace(); + emit(node.name); + } + + function emitNamedExports(node: NamedExports) { + emitNamedImportsOrExports(node); + } + + function emitExportSpecifier(node: ExportSpecifier) { + emitImportOrExportSpecifier(node); + } + + function emitNamedImportsOrExports(node: NamedImportsOrExports) { + writePunctuation("{"); + emitList(node, node.elements, ListFormat.NamedImportsOrExportsElements); + writePunctuation("}"); + } + + function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { + if (node.isTypeOnly) { + writeKeyword("type"); writeSpace(); - emit(node.name); - writeTrailingSemicolon(); } - - function emitNamespaceExport(node: NamespaceExport) { - const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); + if (node.propertyName) { + emit(node.propertyName); writeSpace(); - emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); + emitTokenWithComment(SyntaxKind.AsKeyword, node.propertyName.end, writeKeyword, node); writeSpace(); - emit(node.name); } - function emitNamedExports(node: NamedExports) { - emitNamedImportsOrExports(node); - } - - function emitExportSpecifier(node: ExportSpecifier) { - emitImportOrExportSpecifier(node); - } - - function emitNamedImportsOrExports(node: NamedImportsOrExports) { - writePunctuation("{"); - emitList(node, node.elements, ListFormat.NamedImportsOrExportsElements); - writePunctuation("}"); - } + emit(node.name); + } - function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { - if (node.isTypeOnly) { - writeKeyword("type"); - writeSpace(); - } - if (node.propertyName) { - emit(node.propertyName); - writeSpace(); - emitTokenWithComment(SyntaxKind.AsKeyword, node.propertyName.end, writeKeyword, node); - writeSpace(); - } + // + // Module references + // - emit(node.name); - } + function emitExternalModuleReference(node: ExternalModuleReference) { + writeKeyword("require"); + writePunctuation("("); + emitExpression(node.expression); + writePunctuation(")"); + } - // - // Module references - // + // + // JSX + // - function emitExternalModuleReference(node: ExternalModuleReference) { - writeKeyword("require"); - writePunctuation("("); - emitExpression(node.expression); - writePunctuation(")"); - } + function emitJsxElement(node: JsxElement) { + emit(node.openingElement); + emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); + emit(node.closingElement); + } - // - // JSX - // + function emitJsxSelfClosingElement(node: JsxSelfClosingElement) { + writePunctuation("<"); + emitJsxTagName(node.tagName); + emitTypeArguments(node, node.typeArguments); + writeSpace(); + emit(node.attributes); + writePunctuation("/>"); + } - function emitJsxElement(node: JsxElement) { - emit(node.openingElement); - emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); - emit(node.closingElement); - } + function emitJsxFragment(node: JsxFragment) { + emit(node.openingFragment); + emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); + emit(node.closingFragment); + } + + function emitJsxOpeningElementOrFragment(node: JsxOpeningElement | JsxOpeningFragment) { + writePunctuation("<"); - function emitJsxSelfClosingElement(node: JsxSelfClosingElement) { - writePunctuation("<"); + if (isJsxOpeningElement(node)) { + const indented = writeLineSeparatorsAndIndentBefore(node.tagName, node); emitJsxTagName(node.tagName); emitTypeArguments(node, node.typeArguments); - writeSpace(); + if (node.attributes.properties && node.attributes.properties.length > 0) { + writeSpace(); + } emit(node.attributes); - writePunctuation("/>"); - } - - function emitJsxFragment(node: JsxFragment) { - emit(node.openingFragment); - emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); - emit(node.closingFragment); + writeLineSeparatorsAfter(node.attributes, node); + decreaseIndentIf(indented); } - function emitJsxOpeningElementOrFragment(node: JsxOpeningElement | JsxOpeningFragment) { - writePunctuation("<"); + writePunctuation(">"); + } - if (isJsxOpeningElement(node)) { - const indented = writeLineSeparatorsAndIndentBefore(node.tagName, node); - emitJsxTagName(node.tagName); - emitTypeArguments(node, node.typeArguments); - if (node.attributes.properties && node.attributes.properties.length > 0) { - writeSpace(); - } - emit(node.attributes); - writeLineSeparatorsAfter(node.attributes, node); - decreaseIndentIf(indented); - } + function emitJsxText(node: JsxText) { + writer.writeLiteral(node.text); + } - writePunctuation(">"); + function emitJsxClosingElementOrFragment(node: JsxClosingElement | JsxClosingFragment) { + writePunctuation(""); + } - function emitJsxText(node: JsxText) { - writer.writeLiteral(node.text); - } + function emitJsxAttributes(node: JsxAttributes) { + emitList(node, node.properties, ListFormat.JsxElementAttributes); + } - function emitJsxClosingElementOrFragment(node: JsxClosingElement | JsxClosingFragment) { - writePunctuation(""); - } + function emitJsxAttribute(node: JsxAttribute) { + emit(node.name); + emitNodeWithPrefix("=", writePunctuation, node.initializer, emitJsxAttributeValue); + } - function emitJsxAttributes(node: JsxAttributes) { - emitList(node, node.properties, ListFormat.JsxElementAttributes); - } + function emitJsxSpreadAttribute(node: JsxSpreadAttribute) { + writePunctuation("{..."); + emitExpression(node.expression); + writePunctuation("}"); + } - function emitJsxAttribute(node: JsxAttribute) { - emit(node.name); - emitNodeWithPrefix("=", writePunctuation, node.initializer, emitJsxAttributeValue); - } + function hasTrailingCommentsAtPosition(pos: number) { + let result = false; + forEachTrailingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true); + return result; + } + + function hasLeadingCommentsAtPosition(pos: number) { + let result = false; + forEachLeadingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true); + return result; + } - function emitJsxSpreadAttribute(node: JsxSpreadAttribute) { - writePunctuation("{..."); + function hasCommentsAtPosition(pos: number) { + return hasTrailingCommentsAtPosition(pos) || hasLeadingCommentsAtPosition(pos); + } + + function emitJsxExpression(node: JsxExpression) { + if (node.expression || (!commentsDisabled && !nodeIsSynthesized(node) && hasCommentsAtPosition(node.pos))) { // preserve empty expressions if they contain comments! + const isMultiline = currentSourceFile && !nodeIsSynthesized(node) && getLineAndCharacterOfPosition(currentSourceFile, node.pos).line !== getLineAndCharacterOfPosition(currentSourceFile, node.end).line; + if (isMultiline) { + writer.increaseIndent(); + } + const end = emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); + emit(node.dotDotDotToken); emitExpression(node.expression); - writePunctuation("}"); + emitTokenWithComment(SyntaxKind.CloseBraceToken, node.expression?.end || end, writePunctuation, node); + if (isMultiline) { + writer.decreaseIndent(); + } } + } - function hasTrailingCommentsAtPosition(pos: number) { - let result = false; - forEachTrailingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true); - return result; + function emitJsxTagName(node: JsxTagNameExpression) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); } - - function hasLeadingCommentsAtPosition(pos: number) { - let result = false; - forEachLeadingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true); - return result; + else { + emit(node); } + } - function hasCommentsAtPosition(pos: number) { - return hasTrailingCommentsAtPosition(pos) || hasLeadingCommentsAtPosition(pos); - } + // + // Clauses + // - function emitJsxExpression(node: JsxExpression) { - if (node.expression || (!commentsDisabled && !nodeIsSynthesized(node) && hasCommentsAtPosition(node.pos))) { // preserve empty expressions if they contain comments! - const isMultiline = currentSourceFile && !nodeIsSynthesized(node) && getLineAndCharacterOfPosition(currentSourceFile, node.pos).line !== getLineAndCharacterOfPosition(currentSourceFile, node.end).line; - if (isMultiline) { - writer.increaseIndent(); - } - const end = emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); - emit(node.dotDotDotToken); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseBraceToken, node.expression?.end || end, writePunctuation, node); - if (isMultiline) { - writer.decreaseIndent(); - } - } - } + function emitCaseClause(node: CaseClause) { + emitTokenWithComment(SyntaxKind.CaseKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); - function emitJsxTagName(node: JsxTagNameExpression) { - if (node.kind === SyntaxKind.Identifier) { - emitExpression(node); - } - else { - emit(node); - } - } + emitCaseOrDefaultClauseRest(node, node.statements, node.expression.end); + } - // - // Clauses - // + function emitDefaultClause(node: DefaultClause) { + const pos = emitTokenWithComment(SyntaxKind.DefaultKeyword, node.pos, writeKeyword, node); + emitCaseOrDefaultClauseRest(node, node.statements, pos); + } - function emitCaseClause(node: CaseClause) { - emitTokenWithComment(SyntaxKind.CaseKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); + function emitCaseOrDefaultClauseRest(parentNode: Node, statements: NodeArray, colonPos: number) { + const emitAsSingleStatement = + statements.length === 1 && + ( + // treat synthesized nodes as located on the same line for emit purposes + nodeIsSynthesized(parentNode) || + nodeIsSynthesized(statements[0]) || + rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile!) + ); - emitCaseOrDefaultClauseRest(node, node.statements, node.expression.end); + let format = ListFormat.CaseOrDefaultClauseStatements; + if (emitAsSingleStatement) { + writeToken(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); + writeSpace(); + format &= ~(ListFormat.MultiLine | ListFormat.Indented); } - - function emitDefaultClause(node: DefaultClause) { - const pos = emitTokenWithComment(SyntaxKind.DefaultKeyword, node.pos, writeKeyword, node); - emitCaseOrDefaultClauseRest(node, node.statements, pos); + else { + emitTokenWithComment(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); } + emitList(parentNode, statements, format); + } - function emitCaseOrDefaultClauseRest(parentNode: Node, statements: NodeArray, colonPos: number) { - const emitAsSingleStatement = - statements.length === 1 && - ( - // treat synthesized nodes as located on the same line for emit purposes - nodeIsSynthesized(parentNode) || - nodeIsSynthesized(statements[0]) || - rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile!) - ); + function emitHeritageClause(node: HeritageClause) { + writeSpace(); + writeTokenText(node.token, writeKeyword); + writeSpace(); + emitList(node, node.types, ListFormat.HeritageClauseTypes); + } - let format = ListFormat.CaseOrDefaultClauseStatements; - if (emitAsSingleStatement) { - writeToken(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); - writeSpace(); - format &= ~(ListFormat.MultiLine | ListFormat.Indented); - } - else { - emitTokenWithComment(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); - } - emitList(parentNode, statements, format); + function emitCatchClause(node: CatchClause) { + const openParenPos = emitTokenWithComment(SyntaxKind.CatchKeyword, node.pos, writeKeyword, node); + writeSpace(); + if (node.variableDeclaration) { + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emit(node.variableDeclaration); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.variableDeclaration.end, writePunctuation, node); + writeSpace(); } + emit(node.block); + } + + // + // Property assignments + // + + function emitPropertyAssignment(node: PropertyAssignment) { + emit(node.name); + writePunctuation(":"); + writeSpace(); + // This is to ensure that we emit comment in the following case: + // For example: + // obj = { + // id: /*comment1*/ ()=>void + // } + // "comment1" is not considered to be leading comment for node.initializer + // but rather a trailing comment on the previous node. + const initializer = node.initializer; + if ((getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) { + const commentRange = getCommentRange(initializer); + emitTrailingCommentsOfPosition(commentRange.pos); + } + emitExpression(initializer, parenthesizer.parenthesizeExpressionForDisallowedComma); + } - function emitHeritageClause(node: HeritageClause) { + function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { + emit(node.name); + if (node.objectAssignmentInitializer) { writeSpace(); - writeTokenText(node.token, writeKeyword); + writePunctuation("="); writeSpace(); - emitList(node, node.types, ListFormat.HeritageClauseTypes); + emitExpression(node.objectAssignmentInitializer, parenthesizer.parenthesizeExpressionForDisallowedComma); } + } - function emitCatchClause(node: CatchClause) { - const openParenPos = emitTokenWithComment(SyntaxKind.CatchKeyword, node.pos, writeKeyword, node); - writeSpace(); - if (node.variableDeclaration) { - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emit(node.variableDeclaration); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.variableDeclaration.end, writePunctuation, node); - writeSpace(); - } - emit(node.block); + function emitSpreadAssignment(node: SpreadAssignment) { + if (node.expression) { + emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); } + } - // - // Property assignments - // + // + // Enum + // - function emitPropertyAssignment(node: PropertyAssignment) { - emit(node.name); - writePunctuation(":"); - writeSpace(); - // This is to ensure that we emit comment in the following case: - // For example: - // obj = { - // id: /*comment1*/ ()=>void - // } - // "comment1" is not considered to be leading comment for node.initializer - // but rather a trailing comment on the previous node. - const initializer = node.initializer; - if ((getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) { - const commentRange = getCommentRange(initializer); - emitTrailingCommentsOfPosition(commentRange.pos); + function emitEnumMember(node: EnumMember) { + emit(node.name); + emitInitializer(node.initializer, node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); + } + + // + // JSDoc + // + function emitJSDoc(node: JSDoc) { + write("/**"); + if (node.comment) { + const text = getTextOfJSDocComment(node.comment); + if (text) { + const lines = text.split(/\r\n?|\n/g); + for (const line of lines) { + writeLine(); + writeSpace(); + writePunctuation("*"); + writeSpace(); + write(line); + } } - emitExpression(initializer, parenthesizer.parenthesizeExpressionForDisallowedComma); } - - function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { - emit(node.name); - if (node.objectAssignmentInitializer) { + if (node.tags) { + if (node.tags.length === 1 && node.tags[0].kind === SyntaxKind.JSDocTypeTag && !node.comment) { writeSpace(); - writePunctuation("="); - writeSpace(); - emitExpression(node.objectAssignmentInitializer, parenthesizer.parenthesizeExpressionForDisallowedComma); + emit(node.tags[0]); } - } - - function emitSpreadAssignment(node: SpreadAssignment) { - if (node.expression) { - emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); - emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); + else { + emitList(node, node.tags, ListFormat.JSDocComment); } } + writeSpace(); + write("*/"); + } - // - // Enum - // + function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) { + emitJSDocTagName(tag.tagName); + emitJSDocTypeExpression(tag.typeExpression); + emitJSDocComment(tag.comment); + } - function emitEnumMember(node: EnumMember) { - emit(node.name); - emitInitializer(node.initializer, node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); - } + function emitJSDocSeeTag(tag: JSDocSeeTag) { + emitJSDocTagName(tag.tagName); + emit(tag.name); + emitJSDocComment(tag.comment); + } - // - // JSDoc - // - function emitJSDoc(node: JSDoc) { - write("/**"); - if (node.comment) { - const text = getTextOfJSDocComment(node.comment); - if (text) { - const lines = text.split(/\r\n?|\n/g); - for (const line of lines) { - writeLine(); - writeSpace(); - writePunctuation("*"); - writeSpace(); - write(line); - } - } + function emitJSDocNameReference(node: JSDocNameReference) { + writeSpace(); + writePunctuation("{"); + emit(node.name); + writePunctuation("}"); + } + + function emitJSDocHeritageTag(tag: JSDocImplementsTag | JSDocAugmentsTag) { + emitJSDocTagName(tag.tagName); + writeSpace(); + writePunctuation("{"); + emit(tag.class); + writePunctuation("}"); + emitJSDocComment(tag.comment); + } + + function emitJSDocTemplateTag(tag: JSDocTemplateTag) { + emitJSDocTagName(tag.tagName); + emitJSDocTypeExpression(tag.constraint); + writeSpace(); + emitList(tag, tag.typeParameters, ListFormat.CommaListElements); + emitJSDocComment(tag.comment); + } + + function emitJSDocTypedefTag(tag: JSDocTypedefTag) { + emitJSDocTagName(tag.tagName); + if (tag.typeExpression) { + if (tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { + emitJSDocTypeExpression(tag.typeExpression); } - if (node.tags) { - if (node.tags.length === 1 && node.tags[0].kind === SyntaxKind.JSDocTypeTag && !node.comment) { - writeSpace(); - emit(node.tags[0]); - } - else { - emitList(node, node.tags, ListFormat.JSDocComment); + else { + writeSpace(); + writePunctuation("{"); + write("Object"); + if (tag.typeExpression.isArrayType) { + writePunctuation("["); + writePunctuation("]"); } + writePunctuation("}"); } + } + if (tag.fullName) { writeSpace(); - write("*/"); + emit(tag.fullName); } - - function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) { - emitJSDocTagName(tag.tagName); - emitJSDocTypeExpression(tag.typeExpression); - emitJSDocComment(tag.comment); + emitJSDocComment(tag.comment); + if (tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeLiteral) { + emitJSDocTypeLiteral(tag.typeExpression); } + } - function emitJSDocSeeTag(tag: JSDocSeeTag) { - emitJSDocTagName(tag.tagName); + function emitJSDocCallbackTag(tag: JSDocCallbackTag) { + emitJSDocTagName(tag.tagName); + if (tag.name) { + writeSpace(); emit(tag.name); - emitJSDocComment(tag.comment); } + emitJSDocComment(tag.comment); + emitJSDocSignature(tag.typeExpression); + } - function emitJSDocNameReference(node: JSDocNameReference) { - writeSpace(); - writePunctuation("{"); - emit(node.name); - writePunctuation("}"); - } + function emitJSDocSimpleTag(tag: JSDocTag) { + emitJSDocTagName(tag.tagName); + emitJSDocComment(tag.comment); + } - function emitJSDocHeritageTag(tag: JSDocImplementsTag | JSDocAugmentsTag) { - emitJSDocTagName(tag.tagName); - writeSpace(); - writePunctuation("{"); - emit(tag.class); - writePunctuation("}"); - emitJSDocComment(tag.comment); - } + function emitJSDocTypeLiteral(lit: JSDocTypeLiteral) { + emitList(lit, factory.createNodeArray(lit.jsDocPropertyTags), ListFormat.JSDocComment); + } - function emitJSDocTemplateTag(tag: JSDocTemplateTag) { - emitJSDocTagName(tag.tagName); - emitJSDocTypeExpression(tag.constraint); - writeSpace(); - emitList(tag, tag.typeParameters, ListFormat.CommaListElements); - emitJSDocComment(tag.comment); + function emitJSDocSignature(sig: JSDocSignature) { + if (sig.typeParameters) { + emitList(sig, factory.createNodeArray(sig.typeParameters), ListFormat.JSDocComment); } - - function emitJSDocTypedefTag(tag: JSDocTypedefTag) { - emitJSDocTagName(tag.tagName); - if (tag.typeExpression) { - if (tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { - emitJSDocTypeExpression(tag.typeExpression); - } - else { - writeSpace(); - writePunctuation("{"); - write("Object"); - if (tag.typeExpression.isArrayType) { - writePunctuation("["); - writePunctuation("]"); - } - writePunctuation("}"); - } - } - if (tag.fullName) { - writeSpace(); - emit(tag.fullName); - } - emitJSDocComment(tag.comment); - if (tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeLiteral) { - emitJSDocTypeLiteral(tag.typeExpression); - } + if (sig.parameters) { + emitList(sig, factory.createNodeArray(sig.parameters), ListFormat.JSDocComment); } - - function emitJSDocCallbackTag(tag: JSDocCallbackTag) { - emitJSDocTagName(tag.tagName); - if (tag.name) { - writeSpace(); - emit(tag.name); - } - emitJSDocComment(tag.comment); - emitJSDocSignature(tag.typeExpression); + if (sig.type) { + writeLine(); + writeSpace(); + writePunctuation("*"); + writeSpace(); + emit(sig.type); } + } - function emitJSDocSimpleTag(tag: JSDocTag) { - emitJSDocTagName(tag.tagName); - emitJSDocComment(tag.comment); + function emitJSDocPropertyLikeTag(param: JSDocPropertyLikeTag) { + emitJSDocTagName(param.tagName); + emitJSDocTypeExpression(param.typeExpression); + writeSpace(); + if (param.isBracketed) { + writePunctuation("["); } - - function emitJSDocTypeLiteral(lit: JSDocTypeLiteral) { - emitList(lit, factory.createNodeArray(lit.jsDocPropertyTags), ListFormat.JSDocComment); + emit(param.name); + if (param.isBracketed) { + writePunctuation("]"); } + emitJSDocComment(param.comment); + } - function emitJSDocSignature(sig: JSDocSignature) { - if (sig.typeParameters) { - emitList(sig, factory.createNodeArray(sig.typeParameters), ListFormat.JSDocComment); - } - if (sig.parameters) { - emitList(sig, factory.createNodeArray(sig.parameters), ListFormat.JSDocComment); - } - if (sig.type) { - writeLine(); - writeSpace(); - writePunctuation("*"); - writeSpace(); - emit(sig.type); - } - } + function emitJSDocTagName(tagName: Identifier) { + writePunctuation("@"); + emit(tagName); + } - function emitJSDocPropertyLikeTag(param: JSDocPropertyLikeTag) { - emitJSDocTagName(param.tagName); - emitJSDocTypeExpression(param.typeExpression); + function emitJSDocComment(comment: string | NodeArray | undefined) { + const text = getTextOfJSDocComment(comment); + if (text) { writeSpace(); - if (param.isBracketed) { - writePunctuation("["); - } - emit(param.name); - if (param.isBracketed) { - writePunctuation("]"); - } - emitJSDocComment(param.comment); + write(text); } + } - function emitJSDocTagName(tagName: Identifier) { - writePunctuation("@"); - emit(tagName); + function emitJSDocTypeExpression(typeExpression: JSDocTypeExpression | undefined) { + if (typeExpression) { + writeSpace(); + writePunctuation("{"); + emit(typeExpression.type); + writePunctuation("}"); } + } - function emitJSDocComment(comment: string | NodeArray | undefined) { - const text = getTextOfJSDocComment(comment); - if (text) { - writeSpace(); - write(text); + // + // Top-level nodes + // + + function emitSourceFile(node: SourceFile) { + writeLine(); + const statements = node.statements; + if (emitBodyWithDetachedComments) { + // Emit detached comment if there are no prologue directives or if the first node is synthesized. + // The synthesized node will have no leading comment so some comments may be missed. + const shouldEmitDetachedComment = statements.length === 0 || + !isPrologueDirective(statements[0]) || + nodeIsSynthesized(statements[0]); + if (shouldEmitDetachedComment) { + emitBodyWithDetachedComments(node, statements, emitSourceFileWorker); + return; } } + emitSourceFileWorker(node); + } - function emitJSDocTypeExpression(typeExpression: JSDocTypeExpression | undefined) { - if (typeExpression) { - writeSpace(); - writePunctuation("{"); - emit(typeExpression.type); - writePunctuation("}"); + function emitSyntheticTripleSlashReferencesIfNeeded(node: Bundle) { + emitTripleSlashDirectives(!!node.hasNoDefaultLib, node.syntheticFileReferences || [], node.syntheticTypeReferences || [], node.syntheticLibReferences || []); + for (const prepend of node.prepends) { + if (isUnparsedSource(prepend) && prepend.syntheticReferences) { + for (const ref of prepend.syntheticReferences) { + emit(ref); + writeLine(); + } } } + } - // - // Top-level nodes - // + function emitTripleSlashDirectivesIfNeeded(node: SourceFile) { + if (node.isDeclarationFile) emitTripleSlashDirectives(node.hasNoDefaultLib, node.referencedFiles, node.typeReferenceDirectives, node.libReferenceDirectives); + } - function emitSourceFile(node: SourceFile) { + function emitTripleSlashDirectives(hasNoDefaultLib: boolean, files: readonly FileReference[], types: readonly FileReference[], libs: readonly FileReference[]) { + if (hasNoDefaultLib) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.NoDefaultLib }); writeLine(); - const statements = node.statements; - if (emitBodyWithDetachedComments) { - // Emit detached comment if there are no prologue directives or if the first node is synthesized. - // The synthesized node will have no leading comment so some comments may be missed. - const shouldEmitDetachedComment = statements.length === 0 || - !isPrologueDirective(statements[0]) || - nodeIsSynthesized(statements[0]); - if (shouldEmitDetachedComment) { - emitBodyWithDetachedComments(node, statements, emitSourceFileWorker); - return; - } - } - emitSourceFileWorker(node); - } - - function emitSyntheticTripleSlashReferencesIfNeeded(node: Bundle) { - emitTripleSlashDirectives(!!node.hasNoDefaultLib, node.syntheticFileReferences || [], node.syntheticTypeReferences || [], node.syntheticLibReferences || []); - for (const prepend of node.prepends) { - if (isUnparsedSource(prepend) && prepend.syntheticReferences) { - for (const ref of prepend.syntheticReferences) { - emit(ref); - writeLine(); - } - } - } } - - function emitTripleSlashDirectivesIfNeeded(node: SourceFile) { - if (node.isDeclarationFile) emitTripleSlashDirectives(node.hasNoDefaultLib, node.referencedFiles, node.typeReferenceDirectives, node.libReferenceDirectives); + if (currentSourceFile && currentSourceFile.moduleName) { + writeComment(`/// `); + writeLine(); } - - function emitTripleSlashDirectives(hasNoDefaultLib: boolean, files: readonly FileReference[], types: readonly FileReference[], libs: readonly FileReference[]) { - if (hasNoDefaultLib) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.NoDefaultLib }); - writeLine(); - } - if (currentSourceFile && currentSourceFile.moduleName) { - writeComment(`/// `); - writeLine(); - } - if (currentSourceFile && currentSourceFile.amdDependencies) { - for (const dep of currentSourceFile.amdDependencies) { - if (dep.name) { - writeComment(`/// `); - } - else { - writeComment(`/// `); - } - writeLine(); + if (currentSourceFile && currentSourceFile.amdDependencies) { + for (const dep of currentSourceFile.amdDependencies) { + if (dep.name) { + writeComment(`/// `); + } + else { + writeComment(`/// `); } - } - for (const directive of files) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Reference, data: directive.fileName }); - writeLine(); - } - for (const directive of types) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Type, data: directive.fileName }); - writeLine(); - } - for (const directive of libs) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Lib, data: directive.fileName }); writeLine(); } } - - function emitSourceFileWorker(node: SourceFile) { - const statements = node.statements; - pushNameGenerationScope(node); - forEach(node.statements, generateNames); - emitHelpers(node); - const index = findIndex(statements, statement => !isPrologueDirective(statement)); - emitTripleSlashDirectivesIfNeeded(node); - emitList(node, statements, ListFormat.MultiLine, /*parenthesizerRule*/ undefined, index === -1 ? statements.length : index); - popNameGenerationScope(node); + for (const directive of files) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Reference, data: directive.fileName }); + writeLine(); + } + for (const directive of types) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Type, data: directive.fileName }); + writeLine(); + } + for (const directive of libs) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Lib, data: directive.fileName }); + writeLine(); } + } - // Transformation nodes + function emitSourceFileWorker(node: SourceFile) { + const statements = node.statements; + pushNameGenerationScope(node); + forEach(node.statements, generateNames); + emitHelpers(node); + const index = findIndex(statements, statement => !isPrologueDirective(statement)); + emitTripleSlashDirectivesIfNeeded(node); + emitList(node, statements, ListFormat.MultiLine, /*parenthesizerRule*/ undefined, index === -1 ? statements.length : index); + popNameGenerationScope(node); + } - function emitPartiallyEmittedExpression(node: PartiallyEmittedExpression) { - emitExpression(node.expression); - } + // Transformation nodes - function emitCommaList(node: CommaListExpression) { - emitExpressionList(node, node.elements, ListFormat.CommaListElements, /*parenthesizerRule*/ undefined); - } - - /** - * Emits any prologue directives at the start of a Statement list, returning the - * number of prologue directives written to the output. - */ - function emitPrologueDirectives(statements: readonly Node[], sourceFile?: SourceFile, seenPrologueDirectives?: Set, recordBundleFileSection?: true): number { - let needsToSetSourceFile = !!sourceFile; - for (let i = 0; i < statements.length; i++) { - const statement = statements[i]; - if (isPrologueDirective(statement)) { - const shouldEmitPrologueDirective = seenPrologueDirectives ? !seenPrologueDirectives.has(statement.expression.text) : true; - if (shouldEmitPrologueDirective) { - if (needsToSetSourceFile) { - needsToSetSourceFile = false; - setSourceFile(sourceFile); - } - writeLine(); - const pos = writer.getTextPos(); - emit(statement); - if (recordBundleFileSection && bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: statement.expression.text }); - if (seenPrologueDirectives) { - seenPrologueDirectives.add(statement.expression.text); - } - } - } - else { - // return index of the first non prologue directive - return i; - } - } + function emitPartiallyEmittedExpression(node: PartiallyEmittedExpression) { + emitExpression(node.expression); + } - return statements.length; - } + function emitCommaList(node: CommaListExpression) { + emitExpressionList(node, node.elements, ListFormat.CommaListElements, /*parenthesizerRule*/ undefined); + } - function emitUnparsedPrologues(prologues: readonly UnparsedPrologue[], seenPrologueDirectives: Set) { - for (const prologue of prologues) { - if (!seenPrologueDirectives.has(prologue.data)) { + /** + * Emits any prologue directives at the start of a Statement list, returning the + * number of prologue directives written to the output. + */ + function emitPrologueDirectives(statements: readonly Node[], sourceFile?: SourceFile, seenPrologueDirectives?: Set, recordBundleFileSection?: true): number { + let needsToSetSourceFile = !!sourceFile; + for (let i = 0; i < statements.length; i++) { + const statement = statements[i]; + if (isPrologueDirective(statement)) { + const shouldEmitPrologueDirective = seenPrologueDirectives ? !seenPrologueDirectives.has(statement.expression.text) : true; + if (shouldEmitPrologueDirective) { + if (needsToSetSourceFile) { + needsToSetSourceFile = false; + setSourceFile(sourceFile); + } writeLine(); const pos = writer.getTextPos(); - emit(prologue); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: prologue.data }); + emit(statement); + if (recordBundleFileSection && bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: statement.expression.text }); if (seenPrologueDirectives) { - seenPrologueDirectives.add(prologue.data); + seenPrologueDirectives.add(statement.expression.text); } } } + else { + // return index of the first non prologue directive + return i; + } } - function emitPrologueDirectivesIfNeeded(sourceFileOrBundle: Bundle | SourceFile) { - if (isSourceFile(sourceFileOrBundle)) { - emitPrologueDirectives(sourceFileOrBundle.statements, sourceFileOrBundle); - } - else { - const seenPrologueDirectives = new Set(); - for (const prepend of sourceFileOrBundle.prepends) { - emitUnparsedPrologues((prepend as UnparsedSource).prologues, seenPrologueDirectives); - } - for (const sourceFile of sourceFileOrBundle.sourceFiles) { - emitPrologueDirectives(sourceFile.statements, sourceFile, seenPrologueDirectives, /*recordBundleFileSection*/ true); + return statements.length; + } + + function emitUnparsedPrologues(prologues: readonly UnparsedPrologue[], seenPrologueDirectives: Set) { + for (const prologue of prologues) { + if (!seenPrologueDirectives.has(prologue.data)) { + writeLine(); + const pos = writer.getTextPos(); + emit(prologue); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: prologue.data }); + if (seenPrologueDirectives) { + seenPrologueDirectives.add(prologue.data); } - setSourceFile(undefined); } } + } - function getPrologueDirectivesFromBundledSourceFiles(bundle: Bundle): SourceFilePrologueInfo[] | undefined { + function emitPrologueDirectivesIfNeeded(sourceFileOrBundle: Bundle | SourceFile) { + if (isSourceFile(sourceFileOrBundle)) { + emitPrologueDirectives(sourceFileOrBundle.statements, sourceFileOrBundle); + } + else { const seenPrologueDirectives = new Set(); - let prologues: SourceFilePrologueInfo[] | undefined; - for (let index = 0; index < bundle.sourceFiles.length; index++) { - const sourceFile = bundle.sourceFiles[index]; - let directives: SourceFilePrologueDirective[] | undefined; - let end = 0; - for (const statement of sourceFile.statements) { - if (!isPrologueDirective(statement)) break; - if (seenPrologueDirectives.has(statement.expression.text)) continue; - seenPrologueDirectives.add(statement.expression.text); - (directives || (directives = [])).push({ - pos: statement.pos, - end: statement.end, - expression: { - pos: statement.expression.pos, - end: statement.expression.end, - text: statement.expression.text - } - }); - end = end < statement.end ? statement.end : end; - } - if (directives) (prologues || (prologues = [])).push({ file: index, text: sourceFile.text.substring(0, end), directives }); + for (const prepend of sourceFileOrBundle.prepends) { + emitUnparsedPrologues((prepend as UnparsedSource).prologues, seenPrologueDirectives); + } + for (const sourceFile of sourceFileOrBundle.sourceFiles) { + emitPrologueDirectives(sourceFile.statements, sourceFile, seenPrologueDirectives, /*recordBundleFileSection*/ true); } - return prologues; + setSourceFile(undefined); } + } - function emitShebangIfNeeded(sourceFileOrBundle: Bundle | SourceFile | UnparsedSource) { - if (isSourceFile(sourceFileOrBundle) || isUnparsedSource(sourceFileOrBundle)) { - const shebang = getShebang(sourceFileOrBundle.text); - if (shebang) { - writeComment(shebang); - writeLine(); + function getPrologueDirectivesFromBundledSourceFiles(bundle: Bundle): SourceFilePrologueInfo[] | undefined { + const seenPrologueDirectives = new Set(); + let prologues: SourceFilePrologueInfo[] | undefined; + for (let index = 0; index < bundle.sourceFiles.length; index++) { + const sourceFile = bundle.sourceFiles[index]; + let directives: SourceFilePrologueDirective[] | undefined; + let end = 0; + for (const statement of sourceFile.statements) { + if (!isPrologueDirective(statement)) break; + if (seenPrologueDirectives.has(statement.expression.text)) continue; + seenPrologueDirectives.add(statement.expression.text); + (directives || (directives = [])).push({ + pos: statement.pos, + end: statement.end, + expression: { + pos: statement.expression.pos, + end: statement.expression.end, + text: statement.expression.text + } + }); + end = end < statement.end ? statement.end : end; + } + if (directives) (prologues || (prologues = [])).push({ file: index, text: sourceFile.text.substring(0, end), directives }); + } + return prologues; + } + + function emitShebangIfNeeded(sourceFileOrBundle: Bundle | SourceFile | UnparsedSource) { + if (isSourceFile(sourceFileOrBundle) || isUnparsedSource(sourceFileOrBundle)) { + const shebang = getShebang(sourceFileOrBundle.text); + if (shebang) { + writeComment(shebang); + writeLine(); + return true; + } + } + else { + for (const prepend of sourceFileOrBundle.prepends) { + Debug.assertNode(prepend, isUnparsedSource); + if (emitShebangIfNeeded(prepend)) { return true; } } - else { - for (const prepend of sourceFileOrBundle.prepends) { - Debug.assertNode(prepend, isUnparsedSource); - if (emitShebangIfNeeded(prepend)) { - return true; - } - } - for (const sourceFile of sourceFileOrBundle.sourceFiles) { - // Emit only the first encountered shebang - if (emitShebangIfNeeded(sourceFile)) { - return true; - } + for (const sourceFile of sourceFileOrBundle.sourceFiles) { + // Emit only the first encountered shebang + if (emitShebangIfNeeded(sourceFile)) { + return true; } } } + } - // - // Helpers - // + // + // Helpers + // - function emitNodeWithWriter(node: Node | undefined, writer: typeof write) { - if (!node) return; - const savedWrite = write; - write = writer; - emit(node); - write = savedWrite; - } + function emitNodeWithWriter(node: Node | undefined, writer: typeof write) { + if (!node) return; + const savedWrite = write; + write = writer; + emit(node); + write = savedWrite; + } - function emitModifiers(node: Node, modifiers: NodeArray | undefined) { - if (modifiers && modifiers.length) { - emitList(node, modifiers, ListFormat.Modifiers); - writeSpace(); - } + function emitModifiers(node: Node, modifiers: NodeArray | undefined) { + if (modifiers && modifiers.length) { + emitList(node, modifiers, ListFormat.Modifiers); + writeSpace(); } + } - function emitTypeAnnotation(node: TypeNode | undefined) { - if (node) { - writePunctuation(":"); - writeSpace(); - emit(node); - } + function emitTypeAnnotation(node: TypeNode | undefined) { + if (node) { + writePunctuation(":"); + writeSpace(); + emit(node); } + } - function emitInitializer(node: Expression | undefined, equalCommentStartPos: number, container: Node, parenthesizerRule?: (node: Expression) => Expression) { - if (node) { - writeSpace(); - emitTokenWithComment(SyntaxKind.EqualsToken, equalCommentStartPos, writeOperator, container); - writeSpace(); - emitExpression(node, parenthesizerRule); - } + function emitInitializer(node: Expression | undefined, equalCommentStartPos: number, container: Node, parenthesizerRule?: (node: Expression) => Expression) { + if (node) { + writeSpace(); + emitTokenWithComment(SyntaxKind.EqualsToken, equalCommentStartPos, writeOperator, container); + writeSpace(); + emitExpression(node, parenthesizerRule); } + } - function emitNodeWithPrefix(prefix: string, prefixWriter: (s: string) => void, node: T | undefined, emit: (node: T) => void) { - if (node) { - prefixWriter(prefix); - emit(node); - } + function emitNodeWithPrefix(prefix: string, prefixWriter: (s: string) => void, node: T | undefined, emit: (node: T) => void) { + if (node) { + prefixWriter(prefix); + emit(node); } + } - function emitWithLeadingSpace(node: Node | undefined) { - if (node) { - writeSpace(); - emit(node); - } + function emitWithLeadingSpace(node: Node | undefined) { + if (node) { + writeSpace(); + emit(node); } + } - function emitExpressionWithLeadingSpace(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression) { - if (node) { - writeSpace(); - emitExpression(node, parenthesizerRule); - } + function emitExpressionWithLeadingSpace(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression) { + if (node) { + writeSpace(); + emitExpression(node, parenthesizerRule); } + } - function emitWithTrailingSpace(node: Node | undefined) { - if (node) { - emit(node); - writeSpace(); - } + function emitWithTrailingSpace(node: Node | undefined) { + if (node) { + emit(node); + writeSpace(); } + } - function emitEmbeddedStatement(parent: Node, node: Statement) { - if (isBlock(node) || getEmitFlags(parent) & EmitFlags.SingleLine) { - writeSpace(); - emit(node); + function emitEmbeddedStatement(parent: Node, node: Statement) { + if (isBlock(node) || getEmitFlags(parent) & EmitFlags.SingleLine) { + writeSpace(); + emit(node); + } + else { + writeLine(); + increaseIndent(); + if (isEmptyStatement(node)) { + pipelineEmit(EmitHint.EmbeddedStatement, node); } else { - writeLine(); - increaseIndent(); - if (isEmptyStatement(node)) { - pipelineEmit(EmitHint.EmbeddedStatement, node); - } - else { - emit(node); - } - decreaseIndent(); + emit(node); } + decreaseIndent(); } + } + + function emitDecorators(parentNode: Node, decorators: NodeArray | undefined) { + emitList(parentNode, decorators, ListFormat.Decorators); + } + + function emitTypeArguments(parentNode: Node, typeArguments: NodeArray | undefined) { + emitList(parentNode, typeArguments, ListFormat.TypeArguments, parenthesizer.parenthesizeMemberOfElementType); + } - function emitDecorators(parentNode: Node, decorators: NodeArray | undefined) { - emitList(parentNode, decorators, ListFormat.Decorators); + function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression, typeParameters: NodeArray | undefined) { + if (isFunctionLike(parentNode) && parentNode.typeArguments) { // Quick info uses type arguments in place of type parameters on instantiated signatures + return emitTypeArguments(parentNode, parentNode.typeArguments); } + emitList(parentNode, typeParameters, ListFormat.TypeParameters); + } - function emitTypeArguments(parentNode: Node, typeArguments: NodeArray | undefined) { - emitList(parentNode, typeArguments, ListFormat.TypeArguments, parenthesizer.parenthesizeMemberOfElementType); + function emitParameters(parentNode: Node, parameters: NodeArray) { + emitList(parentNode, parameters, ListFormat.Parameters); + } + + function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { + const parameter = singleOrUndefined(parameters); + return parameter + && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter + && isArrowFunction(parentNode) // only arrow functions may have simple arrow head + && !parentNode.type // arrow function may not have return type annotation + && !some(parentNode.decorators) // parent may not have decorators + && !some(parentNode.modifiers) // parent may not have modifiers + && !some(parentNode.typeParameters) // parent may not have type parameters + && !some(parameter.decorators) // parameter may not have decorators + && !some(parameter.modifiers) // parameter may not have modifiers + && !parameter.dotDotDotToken // parameter may not be rest + && !parameter.questionToken // parameter may not be optional + && !parameter.type // parameter may not have a type annotation + && !parameter.initializer // parameter may not have an initializer + && isIdentifier(parameter.name); // parameter name must be identifier + } + + function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { + if (canEmitSimpleArrowHead(parentNode, parameters)) { + emitList(parentNode, parameters, ListFormat.Parameters & ~ListFormat.Parenthesis); } + else { + emitParameters(parentNode, parameters); + } + } - function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression, typeParameters: NodeArray | undefined) { - if (isFunctionLike(parentNode) && parentNode.typeArguments) { // Quick info uses type arguments in place of type parameters on instantiated signatures - return emitTypeArguments(parentNode, parentNode.typeArguments); - } - emitList(parentNode, typeParameters, ListFormat.TypeParameters); - } - - function emitParameters(parentNode: Node, parameters: NodeArray) { - emitList(parentNode, parameters, ListFormat.Parameters); - } - - function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { - const parameter = singleOrUndefined(parameters); - return parameter - && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter - && isArrowFunction(parentNode) // only arrow functions may have simple arrow head - && !parentNode.type // arrow function may not have return type annotation - && !some(parentNode.decorators) // parent may not have decorators - && !some(parentNode.modifiers) // parent may not have modifiers - && !some(parentNode.typeParameters) // parent may not have type parameters - && !some(parameter.decorators) // parameter may not have decorators - && !some(parameter.modifiers) // parameter may not have modifiers - && !parameter.dotDotDotToken // parameter may not be rest - && !parameter.questionToken // parameter may not be optional - && !parameter.type // parameter may not have a type annotation - && !parameter.initializer // parameter may not have an initializer - && isIdentifier(parameter.name); // parameter name must be identifier - } - - function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { - if (canEmitSimpleArrowHead(parentNode, parameters)) { - emitList(parentNode, parameters, ListFormat.Parameters & ~ListFormat.Parenthesis); - } - else { - emitParameters(parentNode, parameters); - } + function emitParametersForIndexSignature(parentNode: Node, parameters: NodeArray) { + emitList(parentNode, parameters, ListFormat.IndexSignatureParameters); + } + + function writeDelimiter(format: ListFormat) { + switch (format & ListFormat.DelimitersMask) { + case ListFormat.None: + break; + case ListFormat.CommaDelimited: + writePunctuation(","); + break; + case ListFormat.BarDelimited: + writeSpace(); + writePunctuation("|"); + break; + case ListFormat.AsteriskDelimited: + writeSpace(); + writePunctuation("*"); + writeSpace(); + break; + case ListFormat.AmpersandDelimited: + writeSpace(); + writePunctuation("&"); + break; } + } + + function emitList(parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule?: (node: Node) => Node, start?: number, count?: number) { + emitNodeList(emit, parentNode, children, format, parenthesizerRule, start, count); + } + + function emitExpressionList(parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule?: (node: Expression) => Expression, start?: number, count?: number) { + emitNodeList(emitExpression, parentNode, children, format, parenthesizerRule, start, count); + } - function emitParametersForIndexSignature(parentNode: Node, parameters: NodeArray) { - emitList(parentNode, parameters, ListFormat.IndexSignatureParameters); + function emitNodeList(emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule: ((node: Node) => Node) | undefined, start = 0, count = children ? children.length - start : 0) { + const isUndefined = children === undefined; + if (isUndefined && format & ListFormat.OptionalIfUndefined) { + return; } - function writeDelimiter(format: ListFormat) { - switch (format & ListFormat.DelimitersMask) { - case ListFormat.None: - break; - case ListFormat.CommaDelimited: - writePunctuation(","); - break; - case ListFormat.BarDelimited: - writeSpace(); - writePunctuation("|"); - break; - case ListFormat.AsteriskDelimited: - writeSpace(); - writePunctuation("*"); - writeSpace(); - break; - case ListFormat.AmpersandDelimited: - writeSpace(); - writePunctuation("&"); - break; + const isEmpty = children === undefined || start >= children.length || count === 0; + if (isEmpty && format & ListFormat.OptionalIfEmpty) { + if (onBeforeEmitNodeArray) { + onBeforeEmitNodeArray(children); + } + if (onAfterEmitNodeArray) { + onAfterEmitNodeArray(children); } + return; } - function emitList(parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule?: (node: Node) => Node, start?: number, count?: number) { - emitNodeList(emit, parentNode, children, format, parenthesizerRule, start, count); + if (format & ListFormat.BracketsMask) { + writePunctuation(getOpeningBracket(format)); + if (isEmpty && children) { + emitTrailingCommentsOfPosition(children.pos, /*prefixSpace*/ true); // Emit comments within empty bracketed lists + } } - function emitExpressionList(parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule?: (node: Expression) => Expression, start?: number, count?: number) { - emitNodeList(emitExpression, parentNode, children, format, parenthesizerRule, start, count); + if (onBeforeEmitNodeArray) { + onBeforeEmitNodeArray(children); } - function emitNodeList(emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule: ((node: Node) => Node) | undefined, start = 0, count = children ? children.length - start : 0) { - const isUndefined = children === undefined; - if (isUndefined && format & ListFormat.OptionalIfUndefined) { - return; + if (isEmpty) { + // Write a line terminator if the parent node was multi-line + if (format & ListFormat.MultiLine && !(preserveSourceNewlines && (!parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!)))) { + writeLine(); } - - const isEmpty = children === undefined || start >= children.length || count === 0; - if (isEmpty && format & ListFormat.OptionalIfEmpty) { - if (onBeforeEmitNodeArray) { - onBeforeEmitNodeArray(children); - } - if (onAfterEmitNodeArray) { - onAfterEmitNodeArray(children); - } - return; + else if (format & ListFormat.SpaceBetweenBraces && !(format & ListFormat.NoSpaceIfEmpty)) { + writeSpace(); } - - if (format & ListFormat.BracketsMask) { - writePunctuation(getOpeningBracket(format)); - if (isEmpty && children) { - emitTrailingCommentsOfPosition(children.pos, /*prefixSpace*/ true); // Emit comments within empty bracketed lists - } + } + else { + Debug.type>(children); + // Write the opening line terminator or leading whitespace. + const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0; + let shouldEmitInterveningComments = mayEmitInterveningComments; + const leadingLineTerminatorCount = getLeadingLineTerminatorCount(parentNode, children, format); // TODO: GH#18217 + if (leadingLineTerminatorCount) { + writeLine(leadingLineTerminatorCount); + shouldEmitInterveningComments = false; + } + else if (format & ListFormat.SpaceBetweenBraces) { + writeSpace(); } - if (onBeforeEmitNodeArray) { - onBeforeEmitNodeArray(children); + // Increase the indent, if requested. + if (format & ListFormat.Indented) { + increaseIndent(); } - if (isEmpty) { - // Write a line terminator if the parent node was multi-line - if (format & ListFormat.MultiLine && !(preserveSourceNewlines && (!parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!)))) { - writeLine(); - } - else if (format & ListFormat.SpaceBetweenBraces && !(format & ListFormat.NoSpaceIfEmpty)) { - writeSpace(); - } - } - else { - Debug.type>(children); - // Write the opening line terminator or leading whitespace. - const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0; - let shouldEmitInterveningComments = mayEmitInterveningComments; - const leadingLineTerminatorCount = getLeadingLineTerminatorCount(parentNode, children, format); // TODO: GH#18217 - if (leadingLineTerminatorCount) { - writeLine(leadingLineTerminatorCount); - shouldEmitInterveningComments = false; - } - else if (format & ListFormat.SpaceBetweenBraces) { - writeSpace(); - } + // Emit each child. + let previousSibling: Node | undefined; + let previousSourceFileTextKind: ReturnType; + let shouldDecreaseIndentAfterEmit = false; + for (let i = 0; i < count; i++) { + const child = children[start + i]; - // Increase the indent, if requested. - if (format & ListFormat.Indented) { - increaseIndent(); + // Write the delimiter if this is not the first node. + if (format & ListFormat.AsteriskDelimited) { + // always write JSDoc in the format "\n *" + writeLine(); + writeDelimiter(format); } - - // Emit each child. - let previousSibling: Node | undefined; - let previousSourceFileTextKind: ReturnType; - let shouldDecreaseIndentAfterEmit = false; - for (let i = 0; i < count; i++) { - const child = children[start + i]; - - // Write the delimiter if this is not the first node. - if (format & ListFormat.AsteriskDelimited) { - // always write JSDoc in the format "\n *" - writeLine(); - writeDelimiter(format); - } - else if (previousSibling) { - // i.e - // function commentedParameters( - // /* Parameter a */ - // a - // /* End of parameter a */ -> this comment isn't considered to be trailing comment of parameter "a" due to newline - // , - if (format & ListFormat.DelimitersMask && previousSibling.end !== (parentNode ? parentNode.end : -1)) { - emitLeadingCommentsOfPosition(previousSibling.end); - } - writeDelimiter(format); - recordBundleFileInternalSectionEnd(previousSourceFileTextKind); - - // Write either a line terminator or whitespace to separate the elements. - const separatingLineTerminatorCount = getSeparatingLineTerminatorCount(previousSibling, child, format); - if (separatingLineTerminatorCount > 0) { - // If a synthesized node in a single-line list starts on a new - // line, we should increase the indent. - if ((format & (ListFormat.LinesMask | ListFormat.Indented)) === ListFormat.SingleLine) { - increaseIndent(); - shouldDecreaseIndentAfterEmit = true; - } - - writeLine(separatingLineTerminatorCount); - shouldEmitInterveningComments = false; - } - else if (previousSibling && format & ListFormat.SpaceBetweenSiblings) { - writeSpace(); - } + else if (previousSibling) { + // i.e + // function commentedParameters( + // /* Parameter a */ + // a + // /* End of parameter a */ -> this comment isn't considered to be trailing comment of parameter "a" due to newline + // , + if (format & ListFormat.DelimitersMask && previousSibling.end !== (parentNode ? parentNode.end : -1)) { + emitLeadingCommentsOfPosition(previousSibling.end); } - - // Emit this child. - previousSourceFileTextKind = recordBundleFileInternalSectionStart(child); - if (shouldEmitInterveningComments) { - if (emitTrailingCommentsOfPosition) { - const commentRange = getCommentRange(child); - emitTrailingCommentsOfPosition(commentRange.pos); + writeDelimiter(format); + recordBundleFileInternalSectionEnd(previousSourceFileTextKind); + + // Write either a line terminator or whitespace to separate the elements. + const separatingLineTerminatorCount = getSeparatingLineTerminatorCount(previousSibling, child, format); + if (separatingLineTerminatorCount > 0) { + // If a synthesized node in a single-line list starts on a new + // line, we should increase the indent. + if ((format & (ListFormat.LinesMask | ListFormat.Indented)) === ListFormat.SingleLine) { + increaseIndent(); + shouldDecreaseIndentAfterEmit = true; } - } - else { - shouldEmitInterveningComments = mayEmitInterveningComments; - } - nextListElementPos = child.pos; - if (emit.length === 1) { - emit(child); - } - else { - emit(child, parenthesizerRule); + writeLine(separatingLineTerminatorCount); + shouldEmitInterveningComments = false; } - - if (shouldDecreaseIndentAfterEmit) { - decreaseIndent(); - shouldDecreaseIndentAfterEmit = false; + else if (previousSibling && format & ListFormat.SpaceBetweenSiblings) { + writeSpace(); } - - previousSibling = child; } - // Write a trailing comma, if requested. - const emitFlags = previousSibling ? getEmitFlags(previousSibling) : 0; - const skipTrailingComments = commentsDisabled || !!(emitFlags & EmitFlags.NoTrailingComments); - const hasTrailingComma = children?.hasTrailingComma && (format & ListFormat.AllowTrailingComma) && (format & ListFormat.CommaDelimited); - if (hasTrailingComma) { - if (previousSibling && !skipTrailingComments) { - emitTokenWithComment(SyntaxKind.CommaToken, previousSibling.end, writePunctuation, previousSibling); - } - else { - writePunctuation(","); + // Emit this child. + previousSourceFileTextKind = recordBundleFileInternalSectionStart(child); + if (shouldEmitInterveningComments) { + if (emitTrailingCommentsOfPosition) { + const commentRange = getCommentRange(child); + emitTrailingCommentsOfPosition(commentRange.pos); } } + else { + shouldEmitInterveningComments = mayEmitInterveningComments; + } - // Emit any trailing comment of the last element in the list - // i.e - // var array = [... - // 2 - // /* end of element 2 */ - // ]; - if (previousSibling && (parentNode ? parentNode.end : -1) !== previousSibling.end && (format & ListFormat.DelimitersMask) && !skipTrailingComments) { - emitLeadingCommentsOfPosition(hasTrailingComma && children?.end ? children.end : previousSibling.end); + nextListElementPos = child.pos; + if (emit.length === 1) { + emit(child); + } + else { + emit(child, parenthesizerRule); } - // Decrease the indent, if requested. - if (format & ListFormat.Indented) { + if (shouldDecreaseIndentAfterEmit) { decreaseIndent(); + shouldDecreaseIndentAfterEmit = false; } - recordBundleFileInternalSectionEnd(previousSourceFileTextKind); + previousSibling = child; + } - // Write the closing line terminator or closing whitespace. - const closingLineTerminatorCount = getClosingLineTerminatorCount(parentNode, children, format); - if (closingLineTerminatorCount) { - writeLine(closingLineTerminatorCount); + // Write a trailing comma, if requested. + const emitFlags = previousSibling ? getEmitFlags(previousSibling) : 0; + const skipTrailingComments = commentsDisabled || !!(emitFlags & EmitFlags.NoTrailingComments); + const hasTrailingComma = children?.hasTrailingComma && (format & ListFormat.AllowTrailingComma) && (format & ListFormat.CommaDelimited); + if (hasTrailingComma) { + if (previousSibling && !skipTrailingComments) { + emitTokenWithComment(SyntaxKind.CommaToken, previousSibling.end, writePunctuation, previousSibling); } - else if (format & (ListFormat.SpaceAfterList | ListFormat.SpaceBetweenBraces)) { - writeSpace(); + else { + writePunctuation(","); } } - if (onAfterEmitNodeArray) { - onAfterEmitNodeArray(children); + // Emit any trailing comment of the last element in the list + // i.e + // var array = [... + // 2 + // /* end of element 2 */ + // ]; + if (previousSibling && (parentNode ? parentNode.end : -1) !== previousSibling.end && (format & ListFormat.DelimitersMask) && !skipTrailingComments) { + emitLeadingCommentsOfPosition(hasTrailingComma && children?.end ? children.end : previousSibling.end); } - if (format & ListFormat.BracketsMask) { - if (isEmpty && children) { - emitLeadingCommentsOfPosition(children.end); // Emit leading comments within empty lists - } - writePunctuation(getClosingBracket(format)); + // Decrease the indent, if requested. + if (format & ListFormat.Indented) { + decreaseIndent(); } - } - // Writers + recordBundleFileInternalSectionEnd(previousSourceFileTextKind); - function writeLiteral(s: string) { - writer.writeLiteral(s); + // Write the closing line terminator or closing whitespace. + const closingLineTerminatorCount = getClosingLineTerminatorCount(parentNode, children, format); + if (closingLineTerminatorCount) { + writeLine(closingLineTerminatorCount); + } + else if (format & (ListFormat.SpaceAfterList | ListFormat.SpaceBetweenBraces)) { + writeSpace(); + } } - function writeStringLiteral(s: string) { - writer.writeStringLiteral(s); + if (onAfterEmitNodeArray) { + onAfterEmitNodeArray(children); } - function writeBase(s: string) { - writer.write(s); + if (format & ListFormat.BracketsMask) { + if (isEmpty && children) { + emitLeadingCommentsOfPosition(children.end); // Emit leading comments within empty lists + } + writePunctuation(getClosingBracket(format)); } + } - function writeSymbol(s: string, sym: Symbol) { - writer.writeSymbol(s, sym); - } + // Writers - function writePunctuation(s: string) { - writer.writePunctuation(s); - } + function writeLiteral(s: string) { + writer.writeLiteral(s); + } - function writeTrailingSemicolon() { - writer.writeTrailingSemicolon(";"); - } + function writeStringLiteral(s: string) { + writer.writeStringLiteral(s); + } - function writeKeyword(s: string) { - writer.writeKeyword(s); - } + function writeBase(s: string) { + writer.write(s); + } - function writeOperator(s: string) { - writer.writeOperator(s); - } + function writeSymbol(s: string, sym: Symbol) { + writer.writeSymbol(s, sym); + } - function writeParameter(s: string) { - writer.writeParameter(s); - } + function writePunctuation(s: string) { + writer.writePunctuation(s); + } + + function writeTrailingSemicolon() { + writer.writeTrailingSemicolon(";"); + } + + function writeKeyword(s: string) { + writer.writeKeyword(s); + } - function writeComment(s: string) { - writer.writeComment(s); + function writeOperator(s: string) { + writer.writeOperator(s); + } + + function writeParameter(s: string) { + writer.writeParameter(s); + } + + function writeComment(s: string) { + writer.writeComment(s); + } + + function writeSpace() { + writer.writeSpace(" "); + } + + function writeProperty(s: string) { + writer.writeProperty(s); + } + + function nonEscapingWrite(s: string) { + // This should be defined in a snippet-escaping text writer. + if (writer.nonEscapingWrite) { + writer.nonEscapingWrite(s); + } + else { + writer.write(s); } + } - function writeSpace() { - writer.writeSpace(" "); + function writeLine(count = 1) { + for (let i = 0; i < count; i++) { + writer.writeLine(i > 0); } + } - function writeProperty(s: string) { - writer.writeProperty(s); + function increaseIndent() { + writer.increaseIndent(); + } + + function decreaseIndent() { + writer.decreaseIndent(); + } + + function writeToken(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode?: Node) { + return !sourceMapsDisabled + ? emitTokenWithSourceMap(contextNode, token, writer, pos, writeTokenText) + : writeTokenText(token, writer, pos); + } + + function writeTokenNode(node: Node, writer: (s: string) => void) { + if (onBeforeEmitToken) { + onBeforeEmitToken(node); } + writer(tokenToString(node.kind)!); + if (onAfterEmitToken) { + onAfterEmitToken(node); + } + } - function nonEscapingWrite(s: string) { - // This should be defined in a snippet-escaping text writer. - if (writer.nonEscapingWrite) { - writer.nonEscapingWrite(s); + function writeTokenText(token: SyntaxKind, writer: (s: string) => void): void; + function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos: number): number; + function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos?: number): number { + const tokenString = tokenToString(token)!; + writer(tokenString); + return pos! < 0 ? pos! : pos! + tokenString.length; + } + + function writeLineOrSpace(parentNode: Node, prevChildNode: Node, nextChildNode: Node) { + if (getEmitFlags(parentNode) & EmitFlags.SingleLine) { + writeSpace(); + } + else if (preserveSourceNewlines) { + const lines = getLinesBetweenNodes(parentNode, prevChildNode, nextChildNode); + if (lines) { + writeLine(lines); } else { - writer.write(s); + writeSpace(); } } + else { + writeLine(); + } + } - function writeLine(count = 1) { - for (let i = 0; i < count; i++) { - writer.writeLine(i > 0); + function writeLines(text: string): void { + const lines = text.split(/\r\n?|\n/g); + const indentation = guessIndentation(lines); + for (const lineText of lines) { + const line = indentation ? lineText.slice(indentation) : lineText; + if (line.length) { + writeLine(); + write(line); } } + } - function increaseIndent() { - writer.increaseIndent(); + function writeLinesAndIndent(lineCount: number, writeSpaceIfNotIndenting: boolean) { + if (lineCount) { + increaseIndent(); + writeLine(lineCount); } - - function decreaseIndent() { - writer.decreaseIndent(); + else if (writeSpaceIfNotIndenting) { + writeSpace(); } + } - function writeToken(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode?: Node) { - return !sourceMapsDisabled - ? emitTokenWithSourceMap(contextNode, token, writer, pos, writeTokenText) - : writeTokenText(token, writer, pos); + // Helper function to decrease the indent if we previously indented. Allows multiple + // previous indent values to be considered at a time. This also allows caller to just + // call this once, passing in all their appropriate indent values, instead of needing + // to call this helper function multiple times. + function decreaseIndentIf(value1: boolean | number | undefined, value2?: boolean | number) { + if (value1) { + decreaseIndent(); } - - function writeTokenNode(node: Node, writer: (s: string) => void) { - if (onBeforeEmitToken) { - onBeforeEmitToken(node); - } - writer(tokenToString(node.kind)!); - if (onAfterEmitToken) { - onAfterEmitToken(node); - } + if (value2) { + decreaseIndent(); } + } - function writeTokenText(token: SyntaxKind, writer: (s: string) => void): void; - function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos: number): number; - function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos?: number): number { - const tokenString = tokenToString(token)!; - writer(tokenString); - return pos! < 0 ? pos! : pos! + tokenString.length; - } + function getLeadingLineTerminatorCount(parentNode: Node | undefined, children: readonly Node[], format: ListFormat): number { + if (format & ListFormat.PreserveLines || preserveSourceNewlines) { + if (format & ListFormat.PreferNewLine) { + return 1; + } - function writeLineOrSpace(parentNode: Node, prevChildNode: Node, nextChildNode: Node) { - if (getEmitFlags(parentNode) & EmitFlags.SingleLine) { - writeSpace(); + const firstChild = children[0]; + if (firstChild === undefined) { + return !parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1; } - else if (preserveSourceNewlines) { - const lines = getLinesBetweenNodes(parentNode, prevChildNode, nextChildNode); - if (lines) { - writeLine(lines); - } - else { - writeSpace(); - } + if (firstChild.pos === nextListElementPos) { + // If this child starts at the beginning of a list item in a parent list, its leading + // line terminators have already been written as the separating line terminators of the + // parent list. Example: + // + // class Foo { + // constructor() {} + // public foo() {} + // } + // + // The outer list is the list of class members, with one line terminator between the + // constructor and the method. The constructor is written, the separating line terminator + // is written, and then we start emitting the method. Its modifiers ([public]) constitute an inner + // list, so we look for its leading line terminators. If we didn't know that we had already + // written a newline as part of the parent list, it would appear that we need to write a + // leading newline to start the modifiers. + return 0; } - else { - writeLine(); + if (firstChild.kind === SyntaxKind.JsxText) { + // JsxText will be written with its leading whitespace, so don't add more manually. + return 0; } - } - - function writeLines(text: string): void { - const lines = text.split(/\r\n?|\n/g); - const indentation = guessIndentation(lines); - for (const lineText of lines) { - const line = indentation ? lineText.slice(indentation) : lineText; - if (line.length) { - writeLine(); - write(line); + if (parentNode && + !positionIsSynthesized(parentNode.pos) && + !nodeIsSynthesized(firstChild) && + (!firstChild.parent || getOriginalNode(firstChild.parent) === getOriginalNode(parentNode)) + ) { + if (preserveSourceNewlines) { + return getEffectiveLines( + includeComments => getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter( + firstChild.pos, + parentNode.pos, + currentSourceFile!, + includeComments)); } + return rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile!) ? 0 : 1; } - } - - function writeLinesAndIndent(lineCount: number, writeSpaceIfNotIndenting: boolean) { - if (lineCount) { - increaseIndent(); - writeLine(lineCount); - } - else if (writeSpaceIfNotIndenting) { - writeSpace(); + if (synthesizedNodeStartsOnNewLine(firstChild, format)) { + return 1; } } + return format & ListFormat.MultiLine ? 1 : 0; + } - // Helper function to decrease the indent if we previously indented. Allows multiple - // previous indent values to be considered at a time. This also allows caller to just - // call this once, passing in all their appropriate indent values, instead of needing - // to call this helper function multiple times. - function decreaseIndentIf(value1: boolean | number | undefined, value2?: boolean | number) { - if (value1) { - decreaseIndent(); - } - if (value2) { - decreaseIndent(); + function getSeparatingLineTerminatorCount(previousNode: Node | undefined, nextNode: Node, format: ListFormat): number { + if (format & ListFormat.PreserveLines || preserveSourceNewlines) { + if (previousNode === undefined || nextNode === undefined) { + return 0; } - } - - function getLeadingLineTerminatorCount(parentNode: Node | undefined, children: readonly Node[], format: ListFormat): number { - if (format & ListFormat.PreserveLines || preserveSourceNewlines) { - if (format & ListFormat.PreferNewLine) { - return 1; - } - - const firstChild = children[0]; - if (firstChild === undefined) { - return !parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1; - } - if (firstChild.pos === nextListElementPos) { - // If this child starts at the beginning of a list item in a parent list, its leading - // line terminators have already been written as the separating line terminators of the - // parent list. Example: - // - // class Foo { - // constructor() {} - // public foo() {} - // } - // - // The outer list is the list of class members, with one line terminator between the - // constructor and the method. The constructor is written, the separating line terminator - // is written, and then we start emitting the method. Its modifiers ([public]) constitute an inner - // list, so we look for its leading line terminators. If we didn't know that we had already - // written a newline as part of the parent list, it would appear that we need to write a - // leading newline to start the modifiers. - return 0; - } - if (firstChild.kind === SyntaxKind.JsxText) { - // JsxText will be written with its leading whitespace, so don't add more manually. - return 0; - } - if (parentNode && - !positionIsSynthesized(parentNode.pos) && - !nodeIsSynthesized(firstChild) && - (!firstChild.parent || getOriginalNode(firstChild.parent) === getOriginalNode(parentNode)) - ) { - if (preserveSourceNewlines) { - return getEffectiveLines( - includeComments => getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter( - firstChild.pos, - parentNode.pos, - currentSourceFile!, - includeComments)); - } - return rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile!) ? 0 : 1; - } - if (synthesizedNodeStartsOnNewLine(firstChild, format)) { - return 1; - } + if (nextNode.kind === SyntaxKind.JsxText) { + // JsxText will be written with its leading whitespace, so don't add more manually. + return 0; } - return format & ListFormat.MultiLine ? 1 : 0; - } - - function getSeparatingLineTerminatorCount(previousNode: Node | undefined, nextNode: Node, format: ListFormat): number { - if (format & ListFormat.PreserveLines || preserveSourceNewlines) { - if (previousNode === undefined || nextNode === undefined) { - return 0; - } - if (nextNode.kind === SyntaxKind.JsxText) { - // JsxText will be written with its leading whitespace, so don't add more manually. - return 0; - } - else if (!nodeIsSynthesized(previousNode) && !nodeIsSynthesized(nextNode)) { - if (preserveSourceNewlines && siblingNodePositionsAreComparable(previousNode, nextNode)) { - return getEffectiveLines( - includeComments => getLinesBetweenRangeEndAndRangeStart( - previousNode, - nextNode, - currentSourceFile!, - includeComments)); - } - // If `preserveSourceNewlines` is `false` we do not intend to preserve the effective lines between the - // previous and next node. Instead we naively check whether nodes are on separate lines within the - // same node parent. If so, we intend to preserve a single line terminator. This is less precise and - // expensive than checking with `preserveSourceNewlines` as above, but the goal is not to preserve the - // effective source lines between two sibling nodes. - else if (!preserveSourceNewlines && originalNodesHaveSameParent(previousNode, nextNode)) { - return rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile!) ? 0 : 1; - } - // If the two nodes are not comparable, add a line terminator based on the format that can indicate - // whether new lines are preferred or not. - return format & ListFormat.PreferNewLine ? 1 : 0; + else if (!nodeIsSynthesized(previousNode) && !nodeIsSynthesized(nextNode)) { + if (preserveSourceNewlines && siblingNodePositionsAreComparable(previousNode, nextNode)) { + return getEffectiveLines( + includeComments => getLinesBetweenRangeEndAndRangeStart( + previousNode, + nextNode, + currentSourceFile!, + includeComments)); } - else if (synthesizedNodeStartsOnNewLine(previousNode, format) || synthesizedNodeStartsOnNewLine(nextNode, format)) { - return 1; + // If `preserveSourceNewlines` is `false` we do not intend to preserve the effective lines between the + // previous and next node. Instead we naively check whether nodes are on separate lines within the + // same node parent. If so, we intend to preserve a single line terminator. This is less precise and + // expensive than checking with `preserveSourceNewlines` as above, but the goal is not to preserve the + // effective source lines between two sibling nodes. + else if (!preserveSourceNewlines && originalNodesHaveSameParent(previousNode, nextNode)) { + return rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile!) ? 0 : 1; } + // If the two nodes are not comparable, add a line terminator based on the format that can indicate + // whether new lines are preferred or not. + return format & ListFormat.PreferNewLine ? 1 : 0; } - else if (getStartsOnNewLine(nextNode)) { + else if (synthesizedNodeStartsOnNewLine(previousNode, format) || synthesizedNodeStartsOnNewLine(nextNode, format)) { return 1; } - return format & ListFormat.MultiLine ? 1 : 0; } + else if (getStartsOnNewLine(nextNode)) { + return 1; + } + return format & ListFormat.MultiLine ? 1 : 0; + } - function getClosingLineTerminatorCount(parentNode: Node | undefined, children: readonly Node[], format: ListFormat): number { - if (format & ListFormat.PreserveLines || preserveSourceNewlines) { - if (format & ListFormat.PreferNewLine) { - return 1; - } + function getClosingLineTerminatorCount(parentNode: Node | undefined, children: readonly Node[], format: ListFormat): number { + if (format & ListFormat.PreserveLines || preserveSourceNewlines) { + if (format & ListFormat.PreferNewLine) { + return 1; + } - const lastChild = lastOrUndefined(children); - if (lastChild === undefined) { - return !parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1; - } - if (parentNode && !positionIsSynthesized(parentNode.pos) && !nodeIsSynthesized(lastChild) && (!lastChild.parent || lastChild.parent === parentNode)) { - if (preserveSourceNewlines) { - const end = isNodeArray(children) && !positionIsSynthesized(children.end) ? children.end : lastChild.end; - return getEffectiveLines( - includeComments => getLinesBetweenPositionAndNextNonWhitespaceCharacter( - end, - parentNode.end, - currentSourceFile!, - includeComments)); - } - return rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile!) ? 0 : 1; - } - if (synthesizedNodeStartsOnNewLine(lastChild, format)) { - return 1; + const lastChild = lastOrUndefined(children); + if (lastChild === undefined) { + return !parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1; + } + if (parentNode && !positionIsSynthesized(parentNode.pos) && !nodeIsSynthesized(lastChild) && (!lastChild.parent || lastChild.parent === parentNode)) { + if (preserveSourceNewlines) { + const end = isNodeArray(children) && !positionIsSynthesized(children.end) ? children.end : lastChild.end; + return getEffectiveLines( + includeComments => getLinesBetweenPositionAndNextNonWhitespaceCharacter( + end, + parentNode.end, + currentSourceFile!, + includeComments)); } + return rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile!) ? 0 : 1; } - if (format & ListFormat.MultiLine && !(format & ListFormat.NoTrailingNewLine)) { + if (synthesizedNodeStartsOnNewLine(lastChild, format)) { return 1; } - return 0; } + if (format & ListFormat.MultiLine && !(format & ListFormat.NoTrailingNewLine)) { + return 1; + } + return 0; + } - function getEffectiveLines(getLineDifference: (includeComments: boolean) => number) { - // If 'preserveSourceNewlines' is disabled, we should never call this function - // because it could be more expensive than alternative approximations. - Debug.assert(!!preserveSourceNewlines); - // We start by measuring the line difference from a position to its adjacent comments, - // so that this is counted as a one-line difference, not two: + function getEffectiveLines(getLineDifference: (includeComments: boolean) => number) { + // If 'preserveSourceNewlines' is disabled, we should never call this function + // because it could be more expensive than alternative approximations. + Debug.assert(!!preserveSourceNewlines); + // We start by measuring the line difference from a position to its adjacent comments, + // so that this is counted as a one-line difference, not two: + // + // node1; + // // NODE2 COMMENT + // node2; + const lines = getLineDifference(/*includeComments*/ true); + if (lines === 0) { + // However, if the line difference considering comments was 0, we might have this: // - // node1; - // // NODE2 COMMENT + // node1; // NODE2 COMMENT // node2; - const lines = getLineDifference(/*includeComments*/ true); - if (lines === 0) { - // However, if the line difference considering comments was 0, we might have this: - // - // node1; // NODE2 COMMENT - // node2; - // - // in which case we should be ignoring node2's comment, so this too is counted as - // a one-line difference, not zero. - return getLineDifference(/*includeComments*/ false); - } - return lines; + // + // in which case we should be ignoring node2's comment, so this too is counted as + // a one-line difference, not zero. + return getLineDifference(/*includeComments*/ false); } + return lines; + } - function writeLineSeparatorsAndIndentBefore(node: Node, parent: Node): boolean { - const leadingNewlines = preserveSourceNewlines && getLeadingLineTerminatorCount(parent, [node], ListFormat.None); - if (leadingNewlines) { - writeLinesAndIndent(leadingNewlines, /*writeLinesIfNotIndenting*/ false); - } - return !!leadingNewlines; + function writeLineSeparatorsAndIndentBefore(node: Node, parent: Node): boolean { + const leadingNewlines = preserveSourceNewlines && getLeadingLineTerminatorCount(parent, [node], ListFormat.None); + if (leadingNewlines) { + writeLinesAndIndent(leadingNewlines, /*writeLinesIfNotIndenting*/ false); } + return !!leadingNewlines; + } - function writeLineSeparatorsAfter(node: Node, parent: Node) { - const trailingNewlines = preserveSourceNewlines && getClosingLineTerminatorCount(parent, [node], ListFormat.None); - if (trailingNewlines) { - writeLine(trailingNewlines); - } + function writeLineSeparatorsAfter(node: Node, parent: Node) { + const trailingNewlines = preserveSourceNewlines && getClosingLineTerminatorCount(parent, [node], ListFormat.None); + if (trailingNewlines) { + writeLine(trailingNewlines); } + } - function synthesizedNodeStartsOnNewLine(node: Node, format: ListFormat) { - if (nodeIsSynthesized(node)) { - const startsOnNewLine = getStartsOnNewLine(node); - if (startsOnNewLine === undefined) { - return (format & ListFormat.PreferNewLine) !== 0; - } - - return startsOnNewLine; + function synthesizedNodeStartsOnNewLine(node: Node, format: ListFormat) { + if (nodeIsSynthesized(node)) { + const startsOnNewLine = getStartsOnNewLine(node); + if (startsOnNewLine === undefined) { + return (format & ListFormat.PreferNewLine) !== 0; } - return (format & ListFormat.PreferNewLine) !== 0; + return startsOnNewLine; } - function getLinesBetweenNodes(parent: Node, node1: Node, node2: Node): number { - if (getEmitFlags(parent) & EmitFlags.NoIndentation) { - return 0; - } - - parent = skipSynthesizedParentheses(parent); - node1 = skipSynthesizedParentheses(node1); - node2 = skipSynthesizedParentheses(node2); - - // Always use a newline for synthesized code if the synthesizer desires it. - if (getStartsOnNewLine(node2)) { - return 1; - } - - if (!nodeIsSynthesized(parent) && !nodeIsSynthesized(node1) && !nodeIsSynthesized(node2)) { - if (preserveSourceNewlines) { - return getEffectiveLines( - includeComments => getLinesBetweenRangeEndAndRangeStart( - node1, - node2, - currentSourceFile!, - includeComments)); - } - return rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile!) ? 0 : 1; - } + return (format & ListFormat.PreferNewLine) !== 0; + } + function getLinesBetweenNodes(parent: Node, node1: Node, node2: Node): number { + if (getEmitFlags(parent) & EmitFlags.NoIndentation) { return 0; } - function isEmptyBlock(block: BlockLike) { - return block.statements.length === 0 - && rangeEndIsOnSameLineAsRangeStart(block, block, currentSourceFile!); - } - - function skipSynthesizedParentheses(node: Node) { - while (node.kind === SyntaxKind.ParenthesizedExpression && nodeIsSynthesized(node)) { - node = (node as ParenthesizedExpression).expression; - } + parent = skipSynthesizedParentheses(parent); + node1 = skipSynthesizedParentheses(node1); + node2 = skipSynthesizedParentheses(node2); - return node; + // Always use a newline for synthesized code if the synthesizer desires it. + if (getStartsOnNewLine(node2)) { + return 1; } - function getTextOfNode(node: Node, includeTrivia?: boolean): string { - if (isGeneratedIdentifier(node)) { - return generateName(node); - } - else if ((isIdentifier(node) || isPrivateIdentifier(node)) && (nodeIsSynthesized(node) || !node.parent || !currentSourceFile || (node.parent && currentSourceFile && getSourceFileOfNode(node) !== getOriginalNode(currentSourceFile)))) { - return idText(node); - } - else if (node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).textSourceNode) { - return getTextOfNode((node as StringLiteral).textSourceNode!, includeTrivia); - } - else if (isLiteralExpression(node) && (nodeIsSynthesized(node) || !node.parent)) { - return node.text; + if (!nodeIsSynthesized(parent) && !nodeIsSynthesized(node1) && !nodeIsSynthesized(node2)) { + if (preserveSourceNewlines) { + return getEffectiveLines( + includeComments => getLinesBetweenRangeEndAndRangeStart( + node1, + node2, + currentSourceFile!, + includeComments)); } - - return getSourceTextOfNodeFromSourceFile(currentSourceFile!, node, includeTrivia); + return rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile!) ? 0 : 1; } - function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined, jsxAttributeEscape: boolean): string { - if (node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).textSourceNode) { - const textSourceNode = (node as StringLiteral).textSourceNode!; - if (isIdentifier(textSourceNode) || isNumericLiteral(textSourceNode)) { - const text = isNumericLiteral(textSourceNode) ? textSourceNode.text : getTextOfNode(textSourceNode); - return jsxAttributeEscape ? `"${escapeJsxAttributeString(text)}"` : - neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? `"${escapeString(text)}"` : - `"${escapeNonAsciiString(text)}"`; - } - else { - return getLiteralTextOfNode(textSourceNode, neverAsciiEscape, jsxAttributeEscape); - } - } + return 0; + } - const flags = (neverAsciiEscape ? GetLiteralTextFlags.NeverAsciiEscape : 0) - | (jsxAttributeEscape ? GetLiteralTextFlags.JsxAttributeEscape : 0) - | (printerOptions.terminateUnterminatedLiterals ? GetLiteralTextFlags.TerminateUnterminatedLiterals : 0) - | (printerOptions.target && printerOptions.target === ScriptTarget.ESNext ? GetLiteralTextFlags.AllowNumericSeparator : 0); + function isEmptyBlock(block: BlockLike) { + return block.statements.length === 0 + && rangeEndIsOnSameLineAsRangeStart(block, block, currentSourceFile!); + } - return getLiteralText(node, currentSourceFile!, flags); + function skipSynthesizedParentheses(node: Node) { + while (node.kind === SyntaxKind.ParenthesizedExpression && nodeIsSynthesized(node)) { + node = (node as ParenthesizedExpression).expression; } - /** - * Push a new name generation scope. - */ - function pushNameGenerationScope(node: Node | undefined) { - if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { - return; - } - tempFlagsStack.push(tempFlags); - tempFlags = 0; - reservedNamesStack.push(reservedNames); + return node; + } + + function getTextOfNode(node: Node, includeTrivia?: boolean): string { + if (isGeneratedIdentifier(node)) { + return generateName(node); + } + else if ((isIdentifier(node) || isPrivateIdentifier(node)) && (nodeIsSynthesized(node) || !node.parent || !currentSourceFile || (node.parent && currentSourceFile && getSourceFileOfNode(node) !== getOriginalNode(currentSourceFile)))) { + return idText(node); + } + else if (node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).textSourceNode) { + return getTextOfNode((node as StringLiteral).textSourceNode!, includeTrivia); + } + else if (isLiteralExpression(node) && (nodeIsSynthesized(node) || !node.parent)) { + return node.text; } - /** - * Pop the current name generation scope. - */ - function popNameGenerationScope(node: Node | undefined) { - if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { - return; + return getSourceTextOfNodeFromSourceFile(currentSourceFile!, node, includeTrivia); + } + + function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined, jsxAttributeEscape: boolean): string { + if (node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).textSourceNode) { + const textSourceNode = (node as StringLiteral).textSourceNode!; + if (isIdentifier(textSourceNode) || isNumericLiteral(textSourceNode)) { + const text = isNumericLiteral(textSourceNode) ? textSourceNode.text : getTextOfNode(textSourceNode); + return jsxAttributeEscape ? `"${escapeJsxAttributeString(text)}"` : + neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? `"${escapeString(text)}"` : + `"${escapeNonAsciiString(text)}"`; + } + else { + return getLiteralTextOfNode(textSourceNode, neverAsciiEscape, jsxAttributeEscape); } - tempFlags = tempFlagsStack.pop()!; - reservedNames = reservedNamesStack.pop()!; } - function reserveNameInNestedScopes(name: string) { - if (!reservedNames || reservedNames === lastOrUndefined(reservedNamesStack)) { - reservedNames = new Set(); - } - reservedNames.add(name); + const flags = (neverAsciiEscape ? GetLiteralTextFlags.NeverAsciiEscape : 0) + | (jsxAttributeEscape ? GetLiteralTextFlags.JsxAttributeEscape : 0) + | (printerOptions.terminateUnterminatedLiterals ? GetLiteralTextFlags.TerminateUnterminatedLiterals : 0) + | (printerOptions.target && printerOptions.target === ScriptTarget.ESNext ? GetLiteralTextFlags.AllowNumericSeparator : 0); + + return getLiteralText(node, currentSourceFile!, flags); + } + + /** + * Push a new name generation scope. + */ + function pushNameGenerationScope(node: Node | undefined) { + if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { + return; } + tempFlagsStack.push(tempFlags); + tempFlags = 0; + reservedNamesStack.push(reservedNames); + } - function generateNames(node: Node | undefined) { - if (!node) return; - switch (node.kind) { - case SyntaxKind.Block: - forEach((node as Block).statements, generateNames); - break; - case SyntaxKind.LabeledStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - generateNames((node as LabeledStatement | WithStatement | DoStatement | WhileStatement).statement); - break; - case SyntaxKind.IfStatement: - generateNames((node as IfStatement).thenStatement); - generateNames((node as IfStatement).elseStatement); - break; - case SyntaxKind.ForStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForInStatement: - generateNames((node as ForStatement | ForInOrOfStatement).initializer); - generateNames((node as ForStatement | ForInOrOfStatement).statement); - break; - case SyntaxKind.SwitchStatement: - generateNames((node as SwitchStatement).caseBlock); - break; - case SyntaxKind.CaseBlock: - forEach((node as CaseBlock).clauses, generateNames); - break; - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - forEach((node as CaseOrDefaultClause).statements, generateNames); - break; - case SyntaxKind.TryStatement: - generateNames((node as TryStatement).tryBlock); - generateNames((node as TryStatement).catchClause); - generateNames((node as TryStatement).finallyBlock); - break; - case SyntaxKind.CatchClause: - generateNames((node as CatchClause).variableDeclaration); - generateNames((node as CatchClause).block); - break; - case SyntaxKind.VariableStatement: - generateNames((node as VariableStatement).declarationList); - break; - case SyntaxKind.VariableDeclarationList: - forEach((node as VariableDeclarationList).declarations, generateNames); - break; - case SyntaxKind.VariableDeclaration: - case SyntaxKind.Parameter: - case SyntaxKind.BindingElement: - case SyntaxKind.ClassDeclaration: - generateNameIfNeeded((node as NamedDeclaration).name); - break; - case SyntaxKind.FunctionDeclaration: - generateNameIfNeeded((node as FunctionDeclaration).name); - if (getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { - forEach((node as FunctionDeclaration).parameters, generateNames); - generateNames((node as FunctionDeclaration).body); - } - break; - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - forEach((node as BindingPattern).elements, generateNames); - break; - case SyntaxKind.ImportDeclaration: - generateNames((node as ImportDeclaration).importClause); - break; - case SyntaxKind.ImportClause: - generateNameIfNeeded((node as ImportClause).name); - generateNames((node as ImportClause).namedBindings); - break; - case SyntaxKind.NamespaceImport: - generateNameIfNeeded((node as NamespaceImport).name); - break; - case SyntaxKind.NamespaceExport: - generateNameIfNeeded((node as NamespaceExport).name); - break; - case SyntaxKind.NamedImports: - forEach((node as NamedImports).elements, generateNames); - break; - case SyntaxKind.ImportSpecifier: - generateNameIfNeeded((node as ImportSpecifier).propertyName || (node as ImportSpecifier).name); - break; - } + /** + * Pop the current name generation scope. + */ + function popNameGenerationScope(node: Node | undefined) { + if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { + return; } + tempFlags = tempFlagsStack.pop()!; + reservedNames = reservedNamesStack.pop()!; + } - function generateMemberNames(node: Node | undefined) { - if (!node) return; - switch (node.kind) { - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - generateNameIfNeeded((node as NamedDeclaration).name); - break; - } + function reserveNameInNestedScopes(name: string) { + if (!reservedNames || reservedNames === lastOrUndefined(reservedNamesStack)) { + reservedNames = new Set(); } + reservedNames.add(name); + } - function generateNameIfNeeded(name: DeclarationName | undefined) { - if (name) { - if (isGeneratedIdentifier(name)) { - generateName(name); + function generateNames(node: Node | undefined) { + if (!node) return; + switch (node.kind) { + case SyntaxKind.Block: + forEach((node as Block).statements, generateNames); + break; + case SyntaxKind.LabeledStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + generateNames((node as LabeledStatement | WithStatement | DoStatement | WhileStatement).statement); + break; + case SyntaxKind.IfStatement: + generateNames((node as IfStatement).thenStatement); + generateNames((node as IfStatement).elseStatement); + break; + case SyntaxKind.ForStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForInStatement: + generateNames((node as ForStatement | ForInOrOfStatement).initializer); + generateNames((node as ForStatement | ForInOrOfStatement).statement); + break; + case SyntaxKind.SwitchStatement: + generateNames((node as SwitchStatement).caseBlock); + break; + case SyntaxKind.CaseBlock: + forEach((node as CaseBlock).clauses, generateNames); + break; + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + forEach((node as CaseOrDefaultClause).statements, generateNames); + break; + case SyntaxKind.TryStatement: + generateNames((node as TryStatement).tryBlock); + generateNames((node as TryStatement).catchClause); + generateNames((node as TryStatement).finallyBlock); + break; + case SyntaxKind.CatchClause: + generateNames((node as CatchClause).variableDeclaration); + generateNames((node as CatchClause).block); + break; + case SyntaxKind.VariableStatement: + generateNames((node as VariableStatement).declarationList); + break; + case SyntaxKind.VariableDeclarationList: + forEach((node as VariableDeclarationList).declarations, generateNames); + break; + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + case SyntaxKind.ClassDeclaration: + generateNameIfNeeded((node as NamedDeclaration).name); + break; + case SyntaxKind.FunctionDeclaration: + generateNameIfNeeded((node as FunctionDeclaration).name); + if (getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { + forEach((node as FunctionDeclaration).parameters, generateNames); + generateNames((node as FunctionDeclaration).body); } - else if (isBindingPattern(name)) { - generateNames(name); - } - } + break; + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + forEach((node as BindingPattern).elements, generateNames); + break; + case SyntaxKind.ImportDeclaration: + generateNames((node as ImportDeclaration).importClause); + break; + case SyntaxKind.ImportClause: + generateNameIfNeeded((node as ImportClause).name); + generateNames((node as ImportClause).namedBindings); + break; + case SyntaxKind.NamespaceImport: + generateNameIfNeeded((node as NamespaceImport).name); + break; + case SyntaxKind.NamespaceExport: + generateNameIfNeeded((node as NamespaceExport).name); + break; + case SyntaxKind.NamedImports: + forEach((node as NamedImports).elements, generateNames); + break; + case SyntaxKind.ImportSpecifier: + generateNameIfNeeded((node as ImportSpecifier).propertyName || (node as ImportSpecifier).name); + break; } + } + + function generateMemberNames(node: Node | undefined) { + if (!node) return; + switch (node.kind) { + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + generateNameIfNeeded((node as NamedDeclaration).name); + break; + } + } - /** - * Generate the text for a generated identifier. - */ - function generateName(name: GeneratedIdentifier) { - if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) { - // Node names generate unique names based on their original node - // and are cached based on that node's id. - return generateNameCached(getNodeForGeneratedName(name), name.autoGenerateFlags); + function generateNameIfNeeded(name: DeclarationName | undefined) { + if (name) { + if (isGeneratedIdentifier(name)) { + generateName(name); } - else { - // Auto, Loop, and Unique names are cached based on their unique - // autoGenerateId. - const autoGenerateId = name.autoGenerateId!; - return autoGeneratedIdToGeneratedName[autoGenerateId] || (autoGeneratedIdToGeneratedName[autoGenerateId] = makeName(name)); + else if (isBindingPattern(name)) { + generateNames(name); } } + } - function generateNameCached(node: Node, flags?: GeneratedIdentifierFlags) { - const nodeId = getNodeId(node); - return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = generateNameForNode(node, flags)); + /** + * Generate the text for a generated identifier. + */ + function generateName(name: GeneratedIdentifier) { + if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) { + // Node names generate unique names based on their original node + // and are cached based on that node's id. + return generateNameCached(getNodeForGeneratedName(name), name.autoGenerateFlags); } - - /** - * Returns a value indicating whether a name is unique globally, within the current file, - * or within the NameGenerator. - */ - function isUniqueName(name: string): boolean { - return isFileLevelUniqueName(name) - && !generatedNames.has(name) - && !(reservedNames && reservedNames.has(name)); + else { + // Auto, Loop, and Unique names are cached based on their unique + // autoGenerateId. + const autoGenerateId = name.autoGenerateId!; + return autoGeneratedIdToGeneratedName[autoGenerateId] || (autoGeneratedIdToGeneratedName[autoGenerateId] = makeName(name)); } + } - /** - * Returns a value indicating whether a name is unique globally or within the current file. - */ - function isFileLevelUniqueName(name: string) { - return currentSourceFile ? ts.isFileLevelUniqueName(currentSourceFile, name, hasGlobalName) : true; - } + function generateNameCached(node: Node, flags?: GeneratedIdentifierFlags) { + const nodeId = getNodeId(node); + return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = generateNameForNode(node, flags)); + } - /** - * Returns a value indicating whether a name is unique within a container. - */ - function isUniqueLocalName(name: string, container: Node): boolean { - for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer!) { - if (node.locals) { - const local = node.locals.get(escapeLeadingUnderscores(name)); - // We conservatively include alias symbols to cover cases where they're emitted as locals - if (local && local.flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { - return false; - } + /** + * Returns a value indicating whether a name is unique globally, within the current file, + * or within the NameGenerator. + */ + function isUniqueName(name: string): boolean { + return isFileLevelUniqueName(name) + && !generatedNames.has(name) + && !(reservedNames && reservedNames.has(name)); + } + + /** + * Returns a value indicating whether a name is unique globally or within the current file. + */ + function isFileLevelUniqueName(name: string) { + return currentSourceFile ? ts.isFileLevelUniqueName(currentSourceFile, name, hasGlobalName) : true; + } + + /** + * Returns a value indicating whether a name is unique within a container. + */ + function isUniqueLocalName(name: string, container: Node): boolean { + for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer!) { + if (node.locals) { + const local = node.locals.get(escapeLeadingUnderscores(name)); + // We conservatively include alias symbols to cover cases where they're emitted as locals + if (local && local.flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { + return false; } } - return true; } + return true; + } - /** - * Return the next available name in the pattern _a ... _z, _0, _1, ... - * TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name. - * Note that names generated by makeTempVariableName and makeUniqueName will never conflict. - */ - function makeTempVariableName(flags: TempFlags, reservedInNestedScopes?: boolean): string { - if (flags && !(tempFlags & flags)) { - const name = flags === TempFlags._i ? "_i" : "_n"; + /** + * Return the next available name in the pattern _a ... _z, _0, _1, ... + * TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name. + * Note that names generated by makeTempVariableName and makeUniqueName will never conflict. + */ + function makeTempVariableName(flags: TempFlags, reservedInNestedScopes?: boolean): string { + if (flags && !(tempFlags & flags)) { + const name = flags === TempFlags._i ? "_i" : "_n"; + if (isUniqueName(name)) { + tempFlags |= flags; + if (reservedInNestedScopes) { + reserveNameInNestedScopes(name); + } + return name; + } + } + while (true) { + const count = tempFlags & TempFlags.CountMask; + tempFlags++; + // Skip over 'i' and 'n' + if (count !== 8 && count !== 13) { + const name = count < 26 + ? "_" + String.fromCharCode(CharacterCodes.a + count) + : "_" + (count - 26); if (isUniqueName(name)) { - tempFlags |= flags; if (reservedInNestedScopes) { reserveNameInNestedScopes(name); } return name; } } - while (true) { - const count = tempFlags & TempFlags.CountMask; - tempFlags++; - // Skip over 'i' and 'n' - if (count !== 8 && count !== 13) { - const name = count < 26 - ? "_" + String.fromCharCode(CharacterCodes.a + count) - : "_" + (count - 26); - if (isUniqueName(name)) { - if (reservedInNestedScopes) { - reserveNameInNestedScopes(name); - } - return name; - } - } - } } + } - /** - * Generate a name that is unique within the current file and doesn't conflict with any names - * in global scope. The name is formed by adding an '_n' suffix to the specified base name, - * where n is a positive integer. Note that names generated by makeTempVariableName and - * makeUniqueName are guaranteed to never conflict. - * If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1' - */ - function makeUniqueName(baseName: string, checkFn: (name: string) => boolean = isUniqueName, optimistic?: boolean, scoped?: boolean): string { - if (optimistic) { - if (checkFn(baseName)) { - if (scoped) { - reserveNameInNestedScopes(baseName); - } - else { - generatedNames.add(baseName); - } - return baseName; + /** + * Generate a name that is unique within the current file and doesn't conflict with any names + * in global scope. The name is formed by adding an '_n' suffix to the specified base name, + * where n is a positive integer. Note that names generated by makeTempVariableName and + * makeUniqueName are guaranteed to never conflict. + * If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1' + */ + function makeUniqueName(baseName: string, checkFn: (name: string) => boolean = isUniqueName, optimistic?: boolean, scoped?: boolean): string { + if (optimistic) { + if (checkFn(baseName)) { + if (scoped) { + reserveNameInNestedScopes(baseName); + } + else { + generatedNames.add(baseName); } + return baseName; } - // Find the first unique 'name_n', where n is a positive number - if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { - baseName += "_"; - } - let i = 1; - while (true) { - const generatedName = baseName + i; - if (checkFn(generatedName)) { - if (scoped) { - reserveNameInNestedScopes(generatedName); - } - else { - generatedNames.add(generatedName); - } - return generatedName; + } + // Find the first unique 'name_n', where n is a positive number + if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { + baseName += "_"; + } + let i = 1; + while (true) { + const generatedName = baseName + i; + if (checkFn(generatedName)) { + if (scoped) { + reserveNameInNestedScopes(generatedName); + } + else { + generatedNames.add(generatedName); } - i++; + return generatedName; } + i++; } + } - function makeFileLevelOptimisticUniqueName(name: string) { - return makeUniqueName(name, isFileLevelUniqueName, /*optimistic*/ true); - } + function makeFileLevelOptimisticUniqueName(name: string) { + return makeUniqueName(name, isFileLevelUniqueName, /*optimistic*/ true); + } - /** - * Generates a unique name for a ModuleDeclaration or EnumDeclaration. - */ - function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { - const name = getTextOfNode(node.name); - // Use module/enum name itself if it is unique, otherwise make a unique variation - return isUniqueLocalName(name, node) ? name : makeUniqueName(name); - } + /** + * Generates a unique name for a ModuleDeclaration or EnumDeclaration. + */ + function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { + const name = getTextOfNode(node.name); + // Use module/enum name itself if it is unique, otherwise make a unique variation + return isUniqueLocalName(name, node) ? name : makeUniqueName(name); + } - /** - * Generates a unique name for an ImportDeclaration or ExportDeclaration. - */ - function generateNameForImportOrExportDeclaration(node: ImportDeclaration | ExportDeclaration) { - const expr = getExternalModuleName(node)!; // TODO: GH#18217 - const baseName = isStringLiteral(expr) ? - makeIdentifierFromModuleName(expr.text) : "module"; - return makeUniqueName(baseName); - } + /** + * Generates a unique name for an ImportDeclaration or ExportDeclaration. + */ + function generateNameForImportOrExportDeclaration(node: ImportDeclaration | ExportDeclaration) { + const expr = getExternalModuleName(node)!; // TODO: GH#18217 + const baseName = isStringLiteral(expr) ? + makeIdentifierFromModuleName(expr.text) : "module"; + return makeUniqueName(baseName); + } - /** - * Generates a unique name for a default export. - */ - function generateNameForExportDefault() { - return makeUniqueName("default"); - } + /** + * Generates a unique name for a default export. + */ + function generateNameForExportDefault() { + return makeUniqueName("default"); + } - /** - * Generates a unique name for a class expression. - */ - function generateNameForClassExpression() { - return makeUniqueName("class"); - } + /** + * Generates a unique name for a class expression. + */ + function generateNameForClassExpression() { + return makeUniqueName("class"); + } - function generateNameForMethodOrAccessor(node: MethodDeclaration | AccessorDeclaration) { - if (isIdentifier(node.name)) { - return generateNameCached(node.name); - } - return makeTempVariableName(TempFlags.Auto); + function generateNameForMethodOrAccessor(node: MethodDeclaration | AccessorDeclaration) { + if (isIdentifier(node.name)) { + return generateNameCached(node.name); } + return makeTempVariableName(TempFlags.Auto); + } - /** - * Generates a unique name from a node. - */ - function generateNameForNode(node: Node, flags?: GeneratedIdentifierFlags): string { - switch (node.kind) { - case SyntaxKind.Identifier: - return makeUniqueName( - getTextOfNode(node), - isUniqueName, - !!(flags! & GeneratedIdentifierFlags.Optimistic), - !!(flags! & GeneratedIdentifierFlags.ReservedInNestedScopes) - ); - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - return generateNameForModuleOrEnum(node as ModuleDeclaration | EnumDeclaration); - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - return generateNameForImportOrExportDeclaration(node as ImportDeclaration | ExportDeclaration); - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ExportAssignment: - return generateNameForExportDefault(); - case SyntaxKind.ClassExpression: - return generateNameForClassExpression(); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return generateNameForMethodOrAccessor(node as MethodDeclaration | AccessorDeclaration); - case SyntaxKind.ComputedPropertyName: - return makeTempVariableName(TempFlags.Auto, /*reserveInNestedScopes*/ true); - default: - return makeTempVariableName(TempFlags.Auto); - } + /** + * Generates a unique name from a node. + */ + function generateNameForNode(node: Node, flags?: GeneratedIdentifierFlags): string { + switch (node.kind) { + case SyntaxKind.Identifier: + return makeUniqueName( + getTextOfNode(node), + isUniqueName, + !!(flags! & GeneratedIdentifierFlags.Optimistic), + !!(flags! & GeneratedIdentifierFlags.ReservedInNestedScopes) + ); + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + return generateNameForModuleOrEnum(node as ModuleDeclaration | EnumDeclaration); + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + return generateNameForImportOrExportDeclaration(node as ImportDeclaration | ExportDeclaration); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ExportAssignment: + return generateNameForExportDefault(); + case SyntaxKind.ClassExpression: + return generateNameForClassExpression(); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return generateNameForMethodOrAccessor(node as MethodDeclaration | AccessorDeclaration); + case SyntaxKind.ComputedPropertyName: + return makeTempVariableName(TempFlags.Auto, /*reserveInNestedScopes*/ true); + default: + return makeTempVariableName(TempFlags.Auto); } + } - /** - * Generates a unique identifier for a node. - */ - function makeName(name: GeneratedIdentifier) { - switch (name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) { - case GeneratedIdentifierFlags.Auto: - return makeTempVariableName(TempFlags.Auto, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); - case GeneratedIdentifierFlags.Loop: - return makeTempVariableName(TempFlags._i, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); - case GeneratedIdentifierFlags.Unique: - return makeUniqueName( - idText(name), - (name.autoGenerateFlags & GeneratedIdentifierFlags.FileLevel) ? isFileLevelUniqueName : isUniqueName, - !!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic), - !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes) - ); - } - - return Debug.fail("Unsupported GeneratedIdentifierKind."); + /** + * Generates a unique identifier for a node. + */ + function makeName(name: GeneratedIdentifier) { + switch (name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) { + case GeneratedIdentifierFlags.Auto: + return makeTempVariableName(TempFlags.Auto, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); + case GeneratedIdentifierFlags.Loop: + return makeTempVariableName(TempFlags._i, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); + case GeneratedIdentifierFlags.Unique: + return makeUniqueName( + idText(name), + (name.autoGenerateFlags & GeneratedIdentifierFlags.FileLevel) ? isFileLevelUniqueName : isUniqueName, + !!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic), + !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes) + ); } - /** - * Gets the node from which a name should be generated. - */ - function getNodeForGeneratedName(name: GeneratedIdentifier) { - const autoGenerateId = name.autoGenerateId; - let node = name as Node; - let original = node.original; - while (original) { - node = original; + return Debug.fail("Unsupported GeneratedIdentifierKind."); + } - // if "node" is a different generated name (having a different - // "autoGenerateId"), use it and stop traversing. - if (isIdentifier(node) - && !!(node.autoGenerateFlags! & GeneratedIdentifierFlags.Node) - && node.autoGenerateId !== autoGenerateId) { - break; - } + /** + * Gets the node from which a name should be generated. + */ + function getNodeForGeneratedName(name: GeneratedIdentifier) { + const autoGenerateId = name.autoGenerateId; + let node = name as Node; + let original = node.original; + while (original) { + node = original; - original = node.original; + // if "node" is a different generated name (having a different + // "autoGenerateId"), use it and stop traversing. + if (isIdentifier(node) + && !!(node.autoGenerateFlags! & GeneratedIdentifierFlags.Node) + && node.autoGenerateId !== autoGenerateId) { + break; } - // otherwise, return the original node for the source; - return node; + original = node.original; } - // Comments + // otherwise, return the original node for the source; + return node; + } + + // Comments - function pipelineEmitWithComments(hint: EmitHint, node: Node) { - const pipelinePhase = getNextPipelinePhase(PipelinePhase.Comments, hint, node); - const savedContainerPos = containerPos; - const savedContainerEnd = containerEnd; - const savedDeclarationListContainerEnd = declarationListContainerEnd; - emitCommentsBeforeNode(node); - pipelinePhase(hint, node); - emitCommentsAfterNode(node, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); - } + function pipelineEmitWithComments(hint: EmitHint, node: Node) { + const pipelinePhase = getNextPipelinePhase(PipelinePhase.Comments, hint, node); + const savedContainerPos = containerPos; + const savedContainerEnd = containerEnd; + const savedDeclarationListContainerEnd = declarationListContainerEnd; + emitCommentsBeforeNode(node); + pipelinePhase(hint, node); + emitCommentsAfterNode(node, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); + } - function emitCommentsBeforeNode(node: Node) { - const emitFlags = getEmitFlags(node); - const commentRange = getCommentRange(node); + function emitCommentsBeforeNode(node: Node) { + const emitFlags = getEmitFlags(node); + const commentRange = getCommentRange(node); - // Emit leading comments - emitLeadingCommentsOfNode(node, emitFlags, commentRange.pos, commentRange.end); - if (emitFlags & EmitFlags.NoNestedComments) { - commentsDisabled = true; - } + // Emit leading comments + emitLeadingCommentsOfNode(node, emitFlags, commentRange.pos, commentRange.end); + if (emitFlags & EmitFlags.NoNestedComments) { + commentsDisabled = true; } + } - function emitCommentsAfterNode(node: Node, savedContainerPos: number, savedContainerEnd: number, savedDeclarationListContainerEnd: number) { - const emitFlags = getEmitFlags(node); - const commentRange = getCommentRange(node); + function emitCommentsAfterNode(node: Node, savedContainerPos: number, savedContainerEnd: number, savedDeclarationListContainerEnd: number) { + const emitFlags = getEmitFlags(node); + const commentRange = getCommentRange(node); - // Emit trailing comments - if (emitFlags & EmitFlags.NoNestedComments) { - commentsDisabled = false; - } - emitTrailingCommentsOfNode(node, emitFlags, commentRange.pos, commentRange.end, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); + // Emit trailing comments + if (emitFlags & EmitFlags.NoNestedComments) { + commentsDisabled = false; } + emitTrailingCommentsOfNode(node, emitFlags, commentRange.pos, commentRange.end, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); + } - function emitLeadingCommentsOfNode(node: Node, emitFlags: EmitFlags, pos: number, end: number) { - enterComment(); - hasWrittenComment = false; + function emitLeadingCommentsOfNode(node: Node, emitFlags: EmitFlags, pos: number, end: number) { + enterComment(); + hasWrittenComment = false; - // We have to explicitly check that the node is JsxText because if the compilerOptions.jsx is "preserve" we will not do any transformation. - // It is expensive to walk entire tree just to set one kind of node to have no comments. - const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0 || node.kind === SyntaxKind.JsxText; - const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; + // We have to explicitly check that the node is JsxText because if the compilerOptions.jsx is "preserve" we will not do any transformation. + // It is expensive to walk entire tree just to set one kind of node to have no comments. + const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0 || node.kind === SyntaxKind.JsxText; + const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; - // Save current container state on the stack. - if ((pos > 0 || end > 0) && pos !== end) { - // Emit leading comments if the position is not synthesized and the node - // has not opted out from emitting leading comments. - if (!skipLeadingComments) { - emitLeadingComments(pos, /*isEmittedNode*/ node.kind !== SyntaxKind.NotEmittedStatement); - } + // Save current container state on the stack. + if ((pos > 0 || end > 0) && pos !== end) { + // Emit leading comments if the position is not synthesized and the node + // has not opted out from emitting leading comments. + if (!skipLeadingComments) { + emitLeadingComments(pos, /*isEmittedNode*/ node.kind !== SyntaxKind.NotEmittedStatement); + } - if (!skipLeadingComments || (pos >= 0 && (emitFlags & EmitFlags.NoLeadingComments) !== 0)) { - // Advance the container position if comments get emitted or if they've been disabled explicitly using NoLeadingComments. - containerPos = pos; - } + if (!skipLeadingComments || (pos >= 0 && (emitFlags & EmitFlags.NoLeadingComments) !== 0)) { + // Advance the container position if comments get emitted or if they've been disabled explicitly using NoLeadingComments. + containerPos = pos; + } - if (!skipTrailingComments || (end >= 0 && (emitFlags & EmitFlags.NoTrailingComments) !== 0)) { - // As above. - containerEnd = end; + if (!skipTrailingComments || (end >= 0 && (emitFlags & EmitFlags.NoTrailingComments) !== 0)) { + // As above. + containerEnd = end; - // To avoid invalid comment emit in a down-level binding pattern, we - // keep track of the last declaration list container's end - if (node.kind === SyntaxKind.VariableDeclarationList) { - declarationListContainerEnd = end; - } - } - } - forEach(getSyntheticLeadingComments(node), emitLeadingSynthesizedComment); - exitComment(); - } - - function emitTrailingCommentsOfNode(node: Node, emitFlags: EmitFlags, pos: number, end: number, savedContainerPos: number, savedContainerEnd: number, savedDeclarationListContainerEnd: number) { - enterComment(); - const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; - forEach(getSyntheticTrailingComments(node), emitTrailingSynthesizedComment); - if ((pos > 0 || end > 0) && pos !== end) { - // Restore previous container state. - containerPos = savedContainerPos; - containerEnd = savedContainerEnd; - declarationListContainerEnd = savedDeclarationListContainerEnd; - - // Emit trailing comments if the position is not synthesized and the node - // has not opted out from emitting leading comments and is an emitted node. - if (!skipTrailingComments && node.kind !== SyntaxKind.NotEmittedStatement) { - emitTrailingComments(end); + // To avoid invalid comment emit in a down-level binding pattern, we + // keep track of the last declaration list container's end + if (node.kind === SyntaxKind.VariableDeclarationList) { + declarationListContainerEnd = end; } } - exitComment(); } + forEach(getSyntheticLeadingComments(node), emitLeadingSynthesizedComment); + exitComment(); + } - function emitLeadingSynthesizedComment(comment: SynthesizedComment) { - if (comment.hasLeadingNewline || comment.kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); - } - writeSynthesizedComment(comment); - if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); - } - else { - writer.writeSpace(" "); - } - } + function emitTrailingCommentsOfNode(node: Node, emitFlags: EmitFlags, pos: number, end: number, savedContainerPos: number, savedContainerEnd: number, savedDeclarationListContainerEnd: number) { + enterComment(); + const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; + forEach(getSyntheticTrailingComments(node), emitTrailingSynthesizedComment); + if ((pos > 0 || end > 0) && pos !== end) { + // Restore previous container state. + containerPos = savedContainerPos; + containerEnd = savedContainerEnd; + declarationListContainerEnd = savedDeclarationListContainerEnd; - function emitTrailingSynthesizedComment(comment: SynthesizedComment) { - if (!writer.isAtStartOfLine()) { - writer.writeSpace(" "); - } - writeSynthesizedComment(comment); - if (comment.hasTrailingNewLine) { - writer.writeLine(); + // Emit trailing comments if the position is not synthesized and the node + // has not opted out from emitting leading comments and is an emitted node. + if (!skipTrailingComments && node.kind !== SyntaxKind.NotEmittedStatement) { + emitTrailingComments(end); } } + exitComment(); + } - function writeSynthesizedComment(comment: SynthesizedComment) { - const text = formatSynthesizedComment(comment); - const lineMap = comment.kind === SyntaxKind.MultiLineCommentTrivia ? computeLineStarts(text) : undefined; - writeCommentRange(text, lineMap!, writer, 0, text.length, newLine); + function emitLeadingSynthesizedComment(comment: SynthesizedComment) { + if (comment.hasLeadingNewline || comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); } - - function formatSynthesizedComment(comment: SynthesizedComment) { - return comment.kind === SyntaxKind.MultiLineCommentTrivia - ? `/*${comment.text}*/` - : `//${comment.text}`; + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); } - - function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { - enterComment(); - const { pos, end } = detachedRange; - const emitFlags = getEmitFlags(node); - const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0; - const skipTrailingComments = commentsDisabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; - if (!skipLeadingComments) { - emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); - } - - exitComment(); - if (emitFlags & EmitFlags.NoNestedComments && !commentsDisabled) { - commentsDisabled = true; - emitCallback(node); - commentsDisabled = false; - } - else { - emitCallback(node); - } - - enterComment(); - if (!skipTrailingComments) { - emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); - if (hasWrittenComment && !writer.isAtStartOfLine()) { - writer.writeLine(); - } - } - exitComment(); - + else { + writer.writeSpace(" "); } + } - function originalNodesHaveSameParent(nodeA: Node, nodeB: Node) { - nodeA = getOriginalNode(nodeA); - // For performance, do not call `getOriginalNode` for `nodeB` if `nodeA` doesn't even - // have a parent node. - return nodeA.parent && nodeA.parent === getOriginalNode(nodeB).parent; + function emitTrailingSynthesizedComment(comment: SynthesizedComment) { + if (!writer.isAtStartOfLine()) { + writer.writeSpace(" "); + } + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine) { + writer.writeLine(); } + } - function siblingNodePositionsAreComparable(previousNode: Node, nextNode: Node) { - if (nextNode.pos < previousNode.end) { - return false; - } + function writeSynthesizedComment(comment: SynthesizedComment) { + const text = formatSynthesizedComment(comment); + const lineMap = comment.kind === SyntaxKind.MultiLineCommentTrivia ? computeLineStarts(text) : undefined; + writeCommentRange(text, lineMap!, writer, 0, text.length, newLine); + } - previousNode = getOriginalNode(previousNode); - nextNode = getOriginalNode(nextNode); - const parent = previousNode.parent; - if (!parent || parent !== nextNode.parent) { - return false; - } + function formatSynthesizedComment(comment: SynthesizedComment) { + return comment.kind === SyntaxKind.MultiLineCommentTrivia + ? `/*${comment.text}*/` + : `//${comment.text}`; + } - const parentNodeArray = getContainingNodeArray(previousNode); - const prevNodeIndex = parentNodeArray?.indexOf(previousNode); - return prevNodeIndex !== undefined && prevNodeIndex > -1 && parentNodeArray!.indexOf(nextNode) === prevNodeIndex + 1; + function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { + enterComment(); + const { pos, end } = detachedRange; + const emitFlags = getEmitFlags(node); + const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0; + const skipTrailingComments = commentsDisabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; + if (!skipLeadingComments) { + emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); } - function emitLeadingComments(pos: number, isEmittedNode: boolean) { - hasWrittenComment = false; - - if (isEmittedNode) { - if (pos === 0 && currentSourceFile?.isDeclarationFile) { - forEachLeadingCommentToEmit(pos, emitNonTripleSlashLeadingComment); - } - else { - forEachLeadingCommentToEmit(pos, emitLeadingComment); - } - } - else if (pos === 0) { - // If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node, - // unless it is a triple slash comment at the top of the file. - // For Example: - // /// - // declare var x; - // /// - // interface F {} - // The first /// will NOT be removed while the second one will be removed even though both node will not be emitted - forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment); - } + exitComment(); + if (emitFlags & EmitFlags.NoNestedComments && !commentsDisabled) { + commentsDisabled = true; + emitCallback(node); + commentsDisabled = false; + } + else { + emitCallback(node); } - function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (isTripleSlashComment(commentPos, commentEnd)) { - emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); + enterComment(); + if (!skipTrailingComments) { + emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); + if (hasWrittenComment && !writer.isAtStartOfLine()) { + writer.writeLine(); } } + exitComment(); - function emitNonTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (!isTripleSlashComment(commentPos, commentEnd)) { - emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); - } + } + + function originalNodesHaveSameParent(nodeA: Node, nodeB: Node) { + nodeA = getOriginalNode(nodeA); + // For performance, do not call `getOriginalNode` for `nodeB` if `nodeA` doesn't even + // have a parent node. + return nodeA.parent && nodeA.parent === getOriginalNode(nodeB).parent; + } + + function siblingNodePositionsAreComparable(previousNode: Node, nextNode: Node) { + if (nextNode.pos < previousNode.end) { + return false; } - function shouldWriteComment(text: string, pos: number) { - if (printerOptions.onlyPrintJsDocStyle) { - return (isJSDocLikeText(text, pos) || isPinnedComment(text, pos)); - } - return true; + previousNode = getOriginalNode(previousNode); + nextNode = getOriginalNode(nextNode); + const parent = previousNode.parent; + if (!parent || parent !== nextNode.parent) { + return false; } - function emitLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; - if (!hasWrittenComment) { - emitNewLineBeforeLeadingCommentOfPosition(getCurrentLineMap(), writer, rangePos, commentPos); - hasWrittenComment = true; - } + const parentNodeArray = getContainingNodeArray(previousNode); + const prevNodeIndex = parentNodeArray?.indexOf(previousNode); + return prevNodeIndex !== undefined && prevNodeIndex > -1 && parentNodeArray!.indexOf(nextNode) === prevNodeIndex + 1; + } - // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); + function emitLeadingComments(pos: number, isEmittedNode: boolean) { + hasWrittenComment = false; - if (hasTrailingNewLine) { - writer.writeLine(); + if (isEmittedNode) { + if (pos === 0 && currentSourceFile?.isDeclarationFile) { + forEachLeadingCommentToEmit(pos, emitNonTripleSlashLeadingComment); } - else if (kind === SyntaxKind.MultiLineCommentTrivia) { - writer.writeSpace(" "); + else { + forEachLeadingCommentToEmit(pos, emitLeadingComment); } } + else if (pos === 0) { + // If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node, + // unless it is a triple slash comment at the top of the file. + // For Example: + // /// + // declare var x; + // /// + // interface F {} + // The first /// will NOT be removed while the second one will be removed even though both node will not be emitted + forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment); + } + } - function emitLeadingCommentsOfPosition(pos: number) { - if (commentsDisabled || pos === -1) { - return; - } + function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (isTripleSlashComment(commentPos, commentEnd)) { + emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); + } + } - emitLeadingComments(pos, /*isEmittedNode*/ true); + function emitNonTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (!isTripleSlashComment(commentPos, commentEnd)) { + emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); } + } - function emitTrailingComments(pos: number) { - forEachTrailingCommentToEmit(pos, emitTrailingComment); + function shouldWriteComment(text: string, pos: number) { + if (printerOptions.onlyPrintJsDocStyle) { + return (isJSDocLikeText(text, pos) || isPinnedComment(text, pos)); } + return true; + } - function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { - if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; - // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/ - if (!writer.isAtStartOfLine()) { - writer.writeSpace(" "); - } + function emitLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; + if (!hasWrittenComment) { + emitNewLineBeforeLeadingCommentOfPosition(getCurrentLineMap(), writer, rangePos, commentPos); + hasWrittenComment = true; + } - emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); + // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space + emitPos(commentPos); + writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); - if (hasTrailingNewLine) { - writer.writeLine(); - } + if (hasTrailingNewLine) { + writer.writeLine(); + } + else if (kind === SyntaxKind.MultiLineCommentTrivia) { + writer.writeSpace(" "); } + } - function emitTrailingCommentsOfPosition(pos: number, prefixSpace?: boolean, forceNoNewline?: boolean) { - if (commentsDisabled) { - return; - } - enterComment(); - forEachTrailingCommentToEmit(pos, prefixSpace ? emitTrailingComment : forceNoNewline ? emitTrailingCommentOfPositionNoNewline : emitTrailingCommentOfPosition); - exitComment(); + function emitLeadingCommentsOfPosition(pos: number) { + if (commentsDisabled || pos === -1) { + return; } - function emitTrailingCommentOfPositionNoNewline(commentPos: number, commentEnd: number, kind: SyntaxKind) { - // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space + emitLeadingComments(pos, /*isEmittedNode*/ true); + } - emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); + function emitTrailingComments(pos: number) { + forEachTrailingCommentToEmit(pos, emitTrailingComment); + } - if (kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); // still write a newline for single-line comments, so closing tokens aren't written on the same line - } + function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; + // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/ + if (!writer.isAtStartOfLine()) { + writer.writeSpace(" "); } - function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { - // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space + emitPos(commentPos); + writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); - emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); - - if (hasTrailingNewLine) { - writer.writeLine(); - } - else { - writer.writeSpace(" "); - } + if (hasTrailingNewLine) { + writer.writeLine(); } + } - function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { - // Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments - if (currentSourceFile && (containerPos === -1 || pos !== containerPos)) { - if (hasDetachedComments(pos)) { - forEachLeadingCommentWithoutDetachedComments(cb); - } - else { - forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); - } - } + function emitTrailingCommentsOfPosition(pos: number, prefixSpace?: boolean, forceNoNewline?: boolean) { + if (commentsDisabled) { + return; } + enterComment(); + forEachTrailingCommentToEmit(pos, prefixSpace ? emitTrailingComment : forceNoNewline ? emitTrailingCommentOfPositionNoNewline : emitTrailingCommentOfPosition); + exitComment(); + } - function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) { - // Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments - if (currentSourceFile && (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd))) { - forEachTrailingCommentRange(currentSourceFile.text, end, cb); - } + function emitTrailingCommentOfPositionNoNewline(commentPos: number, commentEnd: number, kind: SyntaxKind) { + // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space + + emitPos(commentPos); + writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + + if (kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); // still write a newline for single-line comments, so closing tokens aren't written on the same line } + } + + function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space - function hasDetachedComments(pos: number) { - return detachedCommentsInfo !== undefined && last(detachedCommentsInfo).nodePos === pos; + emitPos(commentPos); + writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + + if (hasTrailingNewLine) { + writer.writeLine(); + } + else { + writer.writeSpace(" "); } + } - function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { - // get the leading comments from detachedPos - const pos = last(detachedCommentsInfo!).detachedCommentEndPos; - if (detachedCommentsInfo!.length - 1) { - detachedCommentsInfo!.pop(); + function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { + // Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments + if (currentSourceFile && (containerPos === -1 || pos !== containerPos)) { + if (hasDetachedComments(pos)) { + forEachLeadingCommentWithoutDetachedComments(cb); } else { - detachedCommentsInfo = undefined; + forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); } - - forEachLeadingCommentRange(currentSourceFile!.text, pos, cb, /*state*/ pos); } + } - function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { - const currentDetachedCommentInfo = emitDetachedComments(currentSourceFile!.text, getCurrentLineMap(), writer, emitComment, range, newLine, commentsDisabled); - if (currentDetachedCommentInfo) { - if (detachedCommentsInfo) { - detachedCommentsInfo.push(currentDetachedCommentInfo); - } - else { - detachedCommentsInfo = [currentDetachedCommentInfo]; - } - } + function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) { + // Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments + if (currentSourceFile && (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd))) { + forEachTrailingCommentRange(currentSourceFile.text, end, cb); } + } - function emitComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { - if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; - emitPos(commentPos); - writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); - } + function hasDetachedComments(pos: number) { + return detachedCommentsInfo !== undefined && last(detachedCommentsInfo).nodePos === pos; + } - /** - * Determine if the given comment is a triple-slash - * - * @return true if the comment is a triple-slash comment else false - */ - function isTripleSlashComment(commentPos: number, commentEnd: number) { - return isRecognizedTripleSlashComment(currentSourceFile!.text, commentPos, commentEnd); + function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { + // get the leading comments from detachedPos + const pos = last(detachedCommentsInfo!).detachedCommentEndPos; + if (detachedCommentsInfo!.length - 1) { + detachedCommentsInfo!.pop(); + } + else { + detachedCommentsInfo = undefined; } - // Source Maps + forEachLeadingCommentRange(currentSourceFile!.text, pos, cb, /*state*/ pos); + } - function getParsedSourceMap(node: UnparsedSource) { - if (node.parsedSourceMap === undefined && node.sourceMapText !== undefined) { - node.parsedSourceMap = tryParseRawSourceMap(node.sourceMapText) || false; - } - return node.parsedSourceMap || undefined; - } - - function pipelineEmitWithSourceMaps(hint: EmitHint, node: Node) { - const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, hint, node); - emitSourceMapsBeforeNode(node); - pipelinePhase(hint, node); - emitSourceMapsAfterNode(node); - } - - function emitSourceMapsBeforeNode(node: Node) { - const emitFlags = getEmitFlags(node); - const sourceMapRange = getSourceMapRange(node); - - // Emit leading sourcemap - if (isUnparsedNode(node)) { - Debug.assertIsDefined(node.parent, "UnparsedNodes must have parent pointers"); - const parsed = getParsedSourceMap(node.parent); - if (parsed && sourceMapGenerator) { - sourceMapGenerator.appendSourceMap( - writer.getLine(), - writer.getColumn(), - parsed, - node.parent.sourceMapPath!, - node.parent.getLineAndCharacterOfPosition(node.pos), - node.parent.getLineAndCharacterOfPosition(node.end) - ); - } + function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { + const currentDetachedCommentInfo = emitDetachedComments(currentSourceFile!.text, getCurrentLineMap(), writer, emitComment, range, newLine, commentsDisabled); + if (currentDetachedCommentInfo) { + if (detachedCommentsInfo) { + detachedCommentsInfo.push(currentDetachedCommentInfo); } else { - const source = sourceMapRange.source || sourceMapSource; - if (node.kind !== SyntaxKind.NotEmittedStatement - && (emitFlags & EmitFlags.NoLeadingSourceMap) === 0 - && sourceMapRange.pos >= 0) { - emitSourcePos(sourceMapRange.source || sourceMapSource, skipSourceTrivia(source, sourceMapRange.pos)); - } - if (emitFlags & EmitFlags.NoNestedSourceMaps) { - sourceMapsDisabled = true; - } + detachedCommentsInfo = [currentDetachedCommentInfo]; } } + } - function emitSourceMapsAfterNode(node: Node) { - const emitFlags = getEmitFlags(node); - const sourceMapRange = getSourceMapRange(node); + function emitComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { + if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; + emitPos(commentPos); + writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + } - // Emit trailing sourcemap - if (!isUnparsedNode(node)) { - if (emitFlags & EmitFlags.NoNestedSourceMaps) { - sourceMapsDisabled = false; - } - if (node.kind !== SyntaxKind.NotEmittedStatement - && (emitFlags & EmitFlags.NoTrailingSourceMap) === 0 - && sourceMapRange.end >= 0) { - emitSourcePos(sourceMapRange.source || sourceMapSource, sourceMapRange.end); - } - } - } + /** + * Determine if the given comment is a triple-slash + * + * @return true if the comment is a triple-slash comment else false + */ + function isTripleSlashComment(commentPos: number, commentEnd: number) { + return isRecognizedTripleSlashComment(currentSourceFile!.text, commentPos, commentEnd); + } - /** - * Skips trivia such as comments and white-space that can be optionally overridden by the source-map source - */ - function skipSourceTrivia(source: SourceMapSource, pos: number): number { - return source.skipTrivia ? source.skipTrivia(pos) : skipTrivia(source.text, pos); + // Source Maps + + function getParsedSourceMap(node: UnparsedSource) { + if (node.parsedSourceMap === undefined && node.sourceMapText !== undefined) { + node.parsedSourceMap = tryParseRawSourceMap(node.sourceMapText) || false; } + return node.parsedSourceMap || undefined; + } - /** - * Emits a mapping. - * - * If the position is synthetic (undefined or a negative value), no mapping will be - * created. - * - * @param pos The position. - */ - function emitPos(pos: number) { - if (sourceMapsDisabled || positionIsSynthesized(pos) || isJsonSourceMapSource(sourceMapSource)) { - return; - } + function pipelineEmitWithSourceMaps(hint: EmitHint, node: Node) { + const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, hint, node); + emitSourceMapsBeforeNode(node); + pipelinePhase(hint, node); + emitSourceMapsAfterNode(node); + } - const { line: sourceLine, character: sourceCharacter } = getLineAndCharacterOfPosition(sourceMapSource, pos); - sourceMapGenerator!.addMapping( - writer.getLine(), - writer.getColumn(), - sourceMapSourceIndex, - sourceLine, - sourceCharacter, - /*nameIndex*/ undefined); - } - - function emitSourcePos(source: SourceMapSource, pos: number) { - if (source !== sourceMapSource) { - const savedSourceMapSource = sourceMapSource; - const savedSourceMapSourceIndex = sourceMapSourceIndex; - setSourceMapSource(source); - emitPos(pos); - resetSourceMapSource(savedSourceMapSource, savedSourceMapSourceIndex); + function emitSourceMapsBeforeNode(node: Node) { + const emitFlags = getEmitFlags(node); + const sourceMapRange = getSourceMapRange(node); + + // Emit leading sourcemap + if (isUnparsedNode(node)) { + Debug.assertIsDefined(node.parent, "UnparsedNodes must have parent pointers"); + const parsed = getParsedSourceMap(node.parent); + if (parsed && sourceMapGenerator) { + sourceMapGenerator.appendSourceMap( + writer.getLine(), + writer.getColumn(), + parsed, + node.parent.sourceMapPath!, + node.parent.getLineAndCharacterOfPosition(node.pos), + node.parent.getLineAndCharacterOfPosition(node.end) + ); } - else { - emitPos(pos); + } + else { + const source = sourceMapRange.source || sourceMapSource; + if (node.kind !== SyntaxKind.NotEmittedStatement + && (emitFlags & EmitFlags.NoLeadingSourceMap) === 0 + && sourceMapRange.pos >= 0) { + emitSourcePos(sourceMapRange.source || sourceMapSource, skipSourceTrivia(source, sourceMapRange.pos)); + } + if (emitFlags & EmitFlags.NoNestedSourceMaps) { + sourceMapsDisabled = true; } } + } + + function emitSourceMapsAfterNode(node: Node) { + const emitFlags = getEmitFlags(node); + const sourceMapRange = getSourceMapRange(node); - /** - * Emits a token of a node with possible leading and trailing source maps. - * - * @param node The node containing the token. - * @param token The token to emit. - * @param tokenStartPos The start pos of the token. - * @param emitCallback The callback used to emit the token. - */ - function emitTokenWithSourceMap(node: Node | undefined, token: SyntaxKind, writer: (s: string) => void, tokenPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number) { - if (sourceMapsDisabled || node && isInJsonFile(node)) { - return emitCallback(token, writer, tokenPos); + // Emit trailing sourcemap + if (!isUnparsedNode(node)) { + if (emitFlags & EmitFlags.NoNestedSourceMaps) { + sourceMapsDisabled = false; } + if (node.kind !== SyntaxKind.NotEmittedStatement + && (emitFlags & EmitFlags.NoTrailingSourceMap) === 0 + && sourceMapRange.end >= 0) { + emitSourcePos(sourceMapRange.source || sourceMapSource, sourceMapRange.end); + } + } + } - const emitNode = node && node.emitNode; - const emitFlags = emitNode && emitNode.flags || EmitFlags.None; - const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; - const source = range && range.source || sourceMapSource; + /** + * Skips trivia such as comments and white-space that can be optionally overridden by the source-map source + */ + function skipSourceTrivia(source: SourceMapSource, pos: number): number { + return source.skipTrivia ? source.skipTrivia(pos) : skipTrivia(source.text, pos); + } - tokenPos = skipSourceTrivia(source, range ? range.pos : tokenPos); - if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { - emitSourcePos(source, tokenPos); - } + /** + * Emits a mapping. + * + * If the position is synthetic (undefined or a negative value), no mapping will be + * created. + * + * @param pos The position. + */ + function emitPos(pos: number) { + if (sourceMapsDisabled || positionIsSynthesized(pos) || isJsonSourceMapSource(sourceMapSource)) { + return; + } + + const { line: sourceLine, character: sourceCharacter } = getLineAndCharacterOfPosition(sourceMapSource, pos); + sourceMapGenerator!.addMapping( + writer.getLine(), + writer.getColumn(), + sourceMapSourceIndex, + sourceLine, + sourceCharacter, + /*nameIndex*/ undefined); + } - tokenPos = emitCallback(token, writer, tokenPos); + function emitSourcePos(source: SourceMapSource, pos: number) { + if (source !== sourceMapSource) { + const savedSourceMapSource = sourceMapSource; + const savedSourceMapSourceIndex = sourceMapSourceIndex; + setSourceMapSource(source); + emitPos(pos); + resetSourceMapSource(savedSourceMapSource, savedSourceMapSourceIndex); + } + else { + emitPos(pos); + } + } - if (range) tokenPos = range.end; - if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) { - emitSourcePos(source, tokenPos); - } + /** + * Emits a token of a node with possible leading and trailing source maps. + * + * @param node The node containing the token. + * @param token The token to emit. + * @param tokenStartPos The start pos of the token. + * @param emitCallback The callback used to emit the token. + */ + function emitTokenWithSourceMap(node: Node | undefined, token: SyntaxKind, writer: (s: string) => void, tokenPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number) { + if (sourceMapsDisabled || node && isInJsonFile(node)) { + return emitCallback(token, writer, tokenPos); + } + + const emitNode = node && node.emitNode; + const emitFlags = emitNode && emitNode.flags || EmitFlags.None; + const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; + const source = range && range.source || sourceMapSource; - return tokenPos; + tokenPos = skipSourceTrivia(source, range ? range.pos : tokenPos); + if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { + emitSourcePos(source, tokenPos); } - function setSourceMapSource(source: SourceMapSource) { - if (sourceMapsDisabled) { - return; - } + tokenPos = emitCallback(token, writer, tokenPos); - sourceMapSource = source; + if (range) tokenPos = range.end; + if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) { + emitSourcePos(source, tokenPos); + } - if (source === mostRecentlyAddedSourceMapSource) { - // Fast path for when the new source map is the most recently added, in which case - // we use its captured index without going through the source map generator. - sourceMapSourceIndex = mostRecentlyAddedSourceMapSourceIndex; - return; - } + return tokenPos; + } - if (isJsonSourceMapSource(source)) { - return; - } + function setSourceMapSource(source: SourceMapSource) { + if (sourceMapsDisabled) { + return; + } - sourceMapSourceIndex = sourceMapGenerator!.addSource(source.fileName); - if (printerOptions.inlineSources) { - sourceMapGenerator!.setSourceContent(sourceMapSourceIndex, source.text); - } + sourceMapSource = source; - mostRecentlyAddedSourceMapSource = source; - mostRecentlyAddedSourceMapSourceIndex = sourceMapSourceIndex; + if (source === mostRecentlyAddedSourceMapSource) { + // Fast path for when the new source map is the most recently added, in which case + // we use its captured index without going through the source map generator. + sourceMapSourceIndex = mostRecentlyAddedSourceMapSourceIndex; + return; } - function resetSourceMapSource(source: SourceMapSource, sourceIndex: number) { - sourceMapSource = source; - sourceMapSourceIndex = sourceIndex; + if (isJsonSourceMapSource(source)) { + return; } - function isJsonSourceMapSource(sourceFile: SourceMapSource) { - return fileExtensionIs(sourceFile.fileName, Extension.Json); + sourceMapSourceIndex = sourceMapGenerator!.addSource(source.fileName); + if (printerOptions.inlineSources) { + sourceMapGenerator!.setSourceContent(sourceMapSourceIndex, source.text); } - } - function createBracketsMap() { - const brackets: string[][] = []; - brackets[ListFormat.Braces] = ["{", "}"]; - brackets[ListFormat.Parenthesis] = ["(", ")"]; - brackets[ListFormat.AngleBrackets] = ["<", ">"]; - brackets[ListFormat.SquareBrackets] = ["[", "]"]; - return brackets; + mostRecentlyAddedSourceMapSource = source; + mostRecentlyAddedSourceMapSourceIndex = sourceMapSourceIndex; } - function getOpeningBracket(format: ListFormat) { - return brackets[format & ListFormat.BracketsMask][0]; + function resetSourceMapSource(source: SourceMapSource, sourceIndex: number) { + sourceMapSource = source; + sourceMapSourceIndex = sourceIndex; } - function getClosingBracket(format: ListFormat) { - return brackets[format & ListFormat.BracketsMask][1]; + function isJsonSourceMapSource(sourceFile: SourceMapSource) { + return fileExtensionIs(sourceFile.fileName, Extension.Json); } +} - // Flags enum to track count of temp variables and a few dedicated names - const enum TempFlags { - Auto = 0x00000000, // No preferred name - CountMask = 0x0FFFFFFF, // Temp variable counter - _i = 0x10000000, // Use/preference flag for '_i' - } +function createBracketsMap() { + const brackets: string[][] = []; + brackets[ListFormat.Braces] = ["{", "}"]; + brackets[ListFormat.Parenthesis] = ["(", ")"]; + brackets[ListFormat.AngleBrackets] = ["<", ">"]; + brackets[ListFormat.SquareBrackets] = ["[", "]"]; + return brackets; +} + +function getOpeningBracket(format: ListFormat) { + return brackets[format & ListFormat.BracketsMask][0]; +} + +function getClosingBracket(format: ListFormat) { + return brackets[format & ListFormat.BracketsMask][1]; +} + +// Flags enum to track count of temp variables and a few dedicated names +const enum TempFlags { + Auto = 0x00000000, // No preferred name + CountMask = 0x0FFFFFFF, // Temp variable counter + _i = 0x10000000, // Use/preference flag for '_i' +} } diff --git a/src/compiler/factory/baseNodeFactory.ts b/src/compiler/factory/baseNodeFactory.ts index 26be7e95ef237..73a16f4565d54 100644 --- a/src/compiler/factory/baseNodeFactory.ts +++ b/src/compiler/factory/baseNodeFactory.ts @@ -1,56 +1,56 @@ /* @internal */ namespace ts { - /** - * A `BaseNodeFactory` is an abstraction over an `ObjectAllocator` that handles caching `Node` constructors - * and allocating `Node` instances based on a set of predefined types. - */ - /* @internal */ - export interface BaseNodeFactory { - createBaseSourceFileNode(kind: SyntaxKind): Node; - createBaseIdentifierNode(kind: SyntaxKind): Node; - createBasePrivateIdentifierNode(kind: SyntaxKind): Node; - createBaseTokenNode(kind: SyntaxKind): Node; - createBaseNode(kind: SyntaxKind): Node; - } +/** + * A `BaseNodeFactory` is an abstraction over an `ObjectAllocator` that handles caching `Node` constructors + * and allocating `Node` instances based on a set of predefined types. + */ +/* @internal */ +export interface BaseNodeFactory { + createBaseSourceFileNode(kind: SyntaxKind): Node; + createBaseIdentifierNode(kind: SyntaxKind): Node; + createBasePrivateIdentifierNode(kind: SyntaxKind): Node; + createBaseTokenNode(kind: SyntaxKind): Node; + createBaseNode(kind: SyntaxKind): Node; +} - /** - * Creates a `BaseNodeFactory` which can be used to create `Node` instances from the constructors provided by the object allocator. - */ - export function createBaseNodeFactory(): BaseNodeFactory { - // tslint:disable variable-name - let NodeConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let TokenConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let IdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let SourceFileConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - // tslint:enable variable-name +/** + * Creates a `BaseNodeFactory` which can be used to create `Node` instances from the constructors provided by the object allocator. + */ +export function createBaseNodeFactory(): BaseNodeFactory { + // tslint:disable variable-name + let NodeConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; + let TokenConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; + let IdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; + let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; + let SourceFileConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; + // tslint:enable variable-name - return { - createBaseSourceFileNode, - createBaseIdentifierNode, - createBasePrivateIdentifierNode, - createBaseTokenNode, - createBaseNode - }; + return { + createBaseSourceFileNode, + createBaseIdentifierNode, + createBasePrivateIdentifierNode, + createBaseTokenNode, + createBaseNode + }; - function createBaseSourceFileNode(kind: SyntaxKind): Node { - return new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, /*pos*/ -1, /*end*/ -1); - } + function createBaseSourceFileNode(kind: SyntaxKind): Node { + return new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, /*pos*/ -1, /*end*/ -1); + } - function createBaseIdentifierNode(kind: SyntaxKind): Node { - return new (IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()))(kind, /*pos*/ -1, /*end*/ -1); - } + function createBaseIdentifierNode(kind: SyntaxKind): Node { + return new (IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()))(kind, /*pos*/ -1, /*end*/ -1); + } - function createBasePrivateIdentifierNode(kind: SyntaxKind): Node { - return new (PrivateIdentifierConstructor || (PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor()))(kind, /*pos*/ -1, /*end*/ -1); - } + function createBasePrivateIdentifierNode(kind: SyntaxKind): Node { + return new (PrivateIdentifierConstructor || (PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor()))(kind, /*pos*/ -1, /*end*/ -1); + } - function createBaseTokenNode(kind: SyntaxKind): Node { - return new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, /*pos*/ -1, /*end*/ -1); - } + function createBaseTokenNode(kind: SyntaxKind): Node { + return new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, /*pos*/ -1, /*end*/ -1); + } - function createBaseNode(kind: SyntaxKind): Node { - return new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, /*pos*/ -1, /*end*/ -1); - } + function createBaseNode(kind: SyntaxKind): Node { + return new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, /*pos*/ -1, /*end*/ -1); } -} \ No newline at end of file +} +} diff --git a/src/compiler/factory/emitHelpers.ts b/src/compiler/factory/emitHelpers.ts index 1e820f2092697..e3d95bed04e72 100644 --- a/src/compiler/factory/emitHelpers.ts +++ b/src/compiler/factory/emitHelpers.ts @@ -1,479 +1,479 @@ /* @internal */ namespace ts { - export interface EmitHelperFactory { - getUnscopedHelperName(name: string): Identifier; +export interface EmitHelperFactory { + getUnscopedHelperName(name: string): Identifier; + // TypeScript Helpers + createDecorateHelper(decoratorExpressions: readonly Expression[], target: Expression, memberName?: Expression, descriptor?: Expression): Expression; + createMetadataHelper(metadataKey: string, metadataValue: Expression): Expression; + createParamHelper(expression: Expression, parameterOffset: number): Expression; + // ES2018 Helpers + createAssignHelper(attributesSegments: readonly Expression[]): Expression; + createAwaitHelper(expression: Expression): Expression; + createAsyncGeneratorHelper(generatorFunc: FunctionExpression, hasLexicalThis: boolean): Expression; + createAsyncDelegatorHelper(expression: Expression): Expression; + createAsyncValuesHelper(expression: Expression): Expression; + // ES2018 Destructuring Helpers + createRestHelper(value: Expression, elements: readonly BindingOrAssignmentElement[], computedTempVariables: readonly Expression[] | undefined, location: TextRange): Expression; + // ES2017 Helpers + createAwaiterHelper(hasLexicalThis: boolean, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression | undefined, body: Block): Expression; + // ES2015 Helpers + createExtendsHelper(name: Identifier): Expression; + createTemplateObjectHelper(cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression): Expression; + createSpreadArrayHelper(to: Expression, from: Expression, packFrom: boolean): Expression; + // ES2015 Destructuring Helpers + createValuesHelper(expression: Expression): Expression; + createReadHelper(iteratorRecord: Expression, count: number | undefined): Expression; + // ES2015 Generator Helpers + createGeneratorHelper(body: FunctionExpression): Expression; + // ES Module Helpers + createCreateBindingHelper(module: Expression, inputName: Expression, outputName: Expression | undefined): Expression; + createImportStarHelper(expression: Expression): Expression; + createImportStarCallbackHelper(): Expression; + createImportDefaultHelper(expression: Expression): Expression; + createExportStarHelper(moduleExpression: Expression, exportsExpression?: Expression): Expression; + // Class Fields Helpers + createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression; + createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression; + createClassPrivateFieldInHelper(state: Identifier, receiver: Expression): Expression; +} + +export function createEmitHelperFactory(context: TransformationContext): EmitHelperFactory { + const factory = context.factory; + const immutableTrue = memoize(() => setEmitFlags(factory.createTrue(), EmitFlags.Immutable)); + const immutableFalse = memoize(() => setEmitFlags(factory.createFalse(), EmitFlags.Immutable)); + + return { + getUnscopedHelperName, // TypeScript Helpers - createDecorateHelper(decoratorExpressions: readonly Expression[], target: Expression, memberName?: Expression, descriptor?: Expression): Expression; - createMetadataHelper(metadataKey: string, metadataValue: Expression): Expression; - createParamHelper(expression: Expression, parameterOffset: number): Expression; + createDecorateHelper, + createMetadataHelper, + createParamHelper, // ES2018 Helpers - createAssignHelper(attributesSegments: readonly Expression[]): Expression; - createAwaitHelper(expression: Expression): Expression; - createAsyncGeneratorHelper(generatorFunc: FunctionExpression, hasLexicalThis: boolean): Expression; - createAsyncDelegatorHelper(expression: Expression): Expression; - createAsyncValuesHelper(expression: Expression): Expression; + createAssignHelper, + createAwaitHelper, + createAsyncGeneratorHelper, + createAsyncDelegatorHelper, + createAsyncValuesHelper, // ES2018 Destructuring Helpers - createRestHelper(value: Expression, elements: readonly BindingOrAssignmentElement[], computedTempVariables: readonly Expression[] | undefined, location: TextRange): Expression; + createRestHelper, // ES2017 Helpers - createAwaiterHelper(hasLexicalThis: boolean, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression | undefined, body: Block): Expression; + createAwaiterHelper, // ES2015 Helpers - createExtendsHelper(name: Identifier): Expression; - createTemplateObjectHelper(cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression): Expression; - createSpreadArrayHelper(to: Expression, from: Expression, packFrom: boolean): Expression; + createExtendsHelper, + createTemplateObjectHelper, + createSpreadArrayHelper, // ES2015 Destructuring Helpers - createValuesHelper(expression: Expression): Expression; - createReadHelper(iteratorRecord: Expression, count: number | undefined): Expression; + createValuesHelper, + createReadHelper, // ES2015 Generator Helpers - createGeneratorHelper(body: FunctionExpression): Expression; + createGeneratorHelper, // ES Module Helpers - createCreateBindingHelper(module: Expression, inputName: Expression, outputName: Expression | undefined): Expression; - createImportStarHelper(expression: Expression): Expression; - createImportStarCallbackHelper(): Expression; - createImportDefaultHelper(expression: Expression): Expression; - createExportStarHelper(moduleExpression: Expression, exportsExpression?: Expression): Expression; + createCreateBindingHelper, + createImportStarHelper, + createImportStarCallbackHelper, + createImportDefaultHelper, + createExportStarHelper, // Class Fields Helpers - createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression; - createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression; - createClassPrivateFieldInHelper(state: Identifier, receiver: Expression): Expression; - } + createClassPrivateFieldGetHelper, + createClassPrivateFieldSetHelper, + createClassPrivateFieldInHelper + }; - export function createEmitHelperFactory(context: TransformationContext): EmitHelperFactory { - const factory = context.factory; - const immutableTrue = memoize(() => setEmitFlags(factory.createTrue(), EmitFlags.Immutable)); - const immutableFalse = memoize(() => setEmitFlags(factory.createFalse(), EmitFlags.Immutable)); - - return { - getUnscopedHelperName, - // TypeScript Helpers - createDecorateHelper, - createMetadataHelper, - createParamHelper, - // ES2018 Helpers - createAssignHelper, - createAwaitHelper, - createAsyncGeneratorHelper, - createAsyncDelegatorHelper, - createAsyncValuesHelper, - // ES2018 Destructuring Helpers - createRestHelper, - // ES2017 Helpers - createAwaiterHelper, - // ES2015 Helpers - createExtendsHelper, - createTemplateObjectHelper, - createSpreadArrayHelper, - // ES2015 Destructuring Helpers - createValuesHelper, - createReadHelper, - // ES2015 Generator Helpers - createGeneratorHelper, - // ES Module Helpers - createCreateBindingHelper, - createImportStarHelper, - createImportStarCallbackHelper, - createImportDefaultHelper, - createExportStarHelper, - // Class Fields Helpers - createClassPrivateFieldGetHelper, - createClassPrivateFieldSetHelper, - createClassPrivateFieldInHelper - }; - - /** - * Gets an identifier for the name of an *unscoped* emit helper. - */ - function getUnscopedHelperName(name: string) { - return setEmitFlags(factory.createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode); - } + /** + * Gets an identifier for the name of an *unscoped* emit helper. + */ + function getUnscopedHelperName(name: string) { + return setEmitFlags(factory.createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode); + } - // TypeScript Helpers + // TypeScript Helpers - function createDecorateHelper(decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression) { - context.requestEmitHelper(decorateHelper); + function createDecorateHelper(decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression) { + context.requestEmitHelper(decorateHelper); - const argumentsArray: Expression[] = []; - argumentsArray.push(factory.createArrayLiteralExpression(decoratorExpressions, /*multiLine*/ true)); - argumentsArray.push(target); - if (memberName) { - argumentsArray.push(memberName); - if (descriptor) { - argumentsArray.push(descriptor); - } + const argumentsArray: Expression[] = []; + argumentsArray.push(factory.createArrayLiteralExpression(decoratorExpressions, /*multiLine*/ true)); + argumentsArray.push(target); + if (memberName) { + argumentsArray.push(memberName); + if (descriptor) { + argumentsArray.push(descriptor); } - - return factory.createCallExpression( - getUnscopedHelperName("__decorate"), - /*typeArguments*/ undefined, - argumentsArray - ); } - function createMetadataHelper(metadataKey: string, metadataValue: Expression) { - context.requestEmitHelper(metadataHelper); - return factory.createCallExpression( - getUnscopedHelperName("__metadata"), + return factory.createCallExpression( + getUnscopedHelperName("__decorate"), + /*typeArguments*/ undefined, + argumentsArray + ); + } + + function createMetadataHelper(metadataKey: string, metadataValue: Expression) { + context.requestEmitHelper(metadataHelper); + return factory.createCallExpression( + getUnscopedHelperName("__metadata"), + /*typeArguments*/ undefined, + [ + factory.createStringLiteral(metadataKey), + metadataValue + ] + ); + } + + function createParamHelper(expression: Expression, parameterOffset: number, location?: TextRange) { + context.requestEmitHelper(paramHelper); + return setTextRange( + factory.createCallExpression( + getUnscopedHelperName("__param"), /*typeArguments*/ undefined, [ - factory.createStringLiteral(metadataKey), - metadataValue + factory.createNumericLiteral(parameterOffset + ""), + expression ] - ); - } - - function createParamHelper(expression: Expression, parameterOffset: number, location?: TextRange) { - context.requestEmitHelper(paramHelper); - return setTextRange( - factory.createCallExpression( - getUnscopedHelperName("__param"), - /*typeArguments*/ undefined, - [ - factory.createNumericLiteral(parameterOffset + ""), - expression - ] - ), - location - ); - } - - // ES2018 Helpers + ), + location + ); + } - function createAssignHelper(attributesSegments: Expression[]) { - if (getEmitScriptTarget(context.getCompilerOptions()) >= ScriptTarget.ES2015) { - return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("Object"), "assign"), - /*typeArguments*/ undefined, - attributesSegments); - } - context.requestEmitHelper(assignHelper); - return factory.createCallExpression( - getUnscopedHelperName("__assign"), - /*typeArguments*/ undefined, - attributesSegments - ); - } + // ES2018 Helpers - function createAwaitHelper(expression: Expression) { - context.requestEmitHelper(awaitHelper); - return factory.createCallExpression(getUnscopedHelperName("__await"), /*typeArguments*/ undefined, [expression]); + function createAssignHelper(attributesSegments: Expression[]) { + if (getEmitScriptTarget(context.getCompilerOptions()) >= ScriptTarget.ES2015) { + return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("Object"), "assign"), + /*typeArguments*/ undefined, + attributesSegments); } + context.requestEmitHelper(assignHelper); + return factory.createCallExpression( + getUnscopedHelperName("__assign"), + /*typeArguments*/ undefined, + attributesSegments + ); + } - function createAsyncGeneratorHelper(generatorFunc: FunctionExpression, hasLexicalThis: boolean) { - context.requestEmitHelper(awaitHelper); - context.requestEmitHelper(asyncGeneratorHelper); - - // Mark this node as originally an async function - (generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; + function createAwaitHelper(expression: Expression) { + context.requestEmitHelper(awaitHelper); + return factory.createCallExpression(getUnscopedHelperName("__await"), /*typeArguments*/ undefined, [expression]); + } - return factory.createCallExpression( - getUnscopedHelperName("__asyncGenerator"), - /*typeArguments*/ undefined, - [ - hasLexicalThis ? factory.createThis() : factory.createVoidZero(), - factory.createIdentifier("arguments"), - generatorFunc - ] - ); - } + function createAsyncGeneratorHelper(generatorFunc: FunctionExpression, hasLexicalThis: boolean) { + context.requestEmitHelper(awaitHelper); + context.requestEmitHelper(asyncGeneratorHelper); + + // Mark this node as originally an async function + (generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; + + return factory.createCallExpression( + getUnscopedHelperName("__asyncGenerator"), + /*typeArguments*/ undefined, + [ + hasLexicalThis ? factory.createThis() : factory.createVoidZero(), + factory.createIdentifier("arguments"), + generatorFunc + ] + ); + } - function createAsyncDelegatorHelper(expression: Expression) { - context.requestEmitHelper(awaitHelper); - context.requestEmitHelper(asyncDelegator); - return factory.createCallExpression( - getUnscopedHelperName("__asyncDelegator"), - /*typeArguments*/ undefined, - [expression] - ); - } + function createAsyncDelegatorHelper(expression: Expression) { + context.requestEmitHelper(awaitHelper); + context.requestEmitHelper(asyncDelegator); + return factory.createCallExpression( + getUnscopedHelperName("__asyncDelegator"), + /*typeArguments*/ undefined, + [expression] + ); + } - function createAsyncValuesHelper(expression: Expression) { - context.requestEmitHelper(asyncValues); - return factory.createCallExpression( - getUnscopedHelperName("__asyncValues"), - /*typeArguments*/ undefined, - [expression] - ); - } + function createAsyncValuesHelper(expression: Expression) { + context.requestEmitHelper(asyncValues); + return factory.createCallExpression( + getUnscopedHelperName("__asyncValues"), + /*typeArguments*/ undefined, + [expression] + ); + } - // ES2018 Destructuring Helpers + // ES2018 Destructuring Helpers - /** Given value: o, propName: p, pattern: { a, b, ...p } from the original statement - * `{ a, b, ...p } = o`, create `p = __rest(o, ["a", "b"]);` - */ - function createRestHelper(value: Expression, elements: readonly BindingOrAssignmentElement[], computedTempVariables: readonly Expression[] | undefined, location: TextRange): Expression { - context.requestEmitHelper(restHelper); - const propertyNames: Expression[] = []; - let computedTempVariableOffset = 0; - for (let i = 0; i < elements.length - 1; i++) { - const propertyName = getPropertyNameOfBindingOrAssignmentElement(elements[i]); - if (propertyName) { - if (isComputedPropertyName(propertyName)) { - Debug.assertIsDefined(computedTempVariables, "Encountered computed property name but 'computedTempVariables' argument was not provided."); - const temp = computedTempVariables[computedTempVariableOffset]; - computedTempVariableOffset++; - // typeof _tmp === "symbol" ? _tmp : _tmp + "" - propertyNames.push( - factory.createConditionalExpression( - factory.createTypeCheck(temp, "symbol"), - /*questionToken*/ undefined, - temp, - /*colonToken*/ undefined, - factory.createAdd(temp, factory.createStringLiteral("")) - ) - ); - } - else { - propertyNames.push(factory.createStringLiteralFromNode(propertyName)); - } + /** Given value: o, propName: p, pattern: { a, b, ...p } from the original statement + * `{ a, b, ...p } = o`, create `p = __rest(o, ["a", "b"]);` + */ + function createRestHelper(value: Expression, elements: readonly BindingOrAssignmentElement[], computedTempVariables: readonly Expression[] | undefined, location: TextRange): Expression { + context.requestEmitHelper(restHelper); + const propertyNames: Expression[] = []; + let computedTempVariableOffset = 0; + for (let i = 0; i < elements.length - 1; i++) { + const propertyName = getPropertyNameOfBindingOrAssignmentElement(elements[i]); + if (propertyName) { + if (isComputedPropertyName(propertyName)) { + Debug.assertIsDefined(computedTempVariables, "Encountered computed property name but 'computedTempVariables' argument was not provided."); + const temp = computedTempVariables[computedTempVariableOffset]; + computedTempVariableOffset++; + // typeof _tmp === "symbol" ? _tmp : _tmp + "" + propertyNames.push( + factory.createConditionalExpression( + factory.createTypeCheck(temp, "symbol"), + /*questionToken*/ undefined, + temp, + /*colonToken*/ undefined, + factory.createAdd(temp, factory.createStringLiteral("")) + ) + ); + } + else { + propertyNames.push(factory.createStringLiteralFromNode(propertyName)); } } - return factory.createCallExpression( - getUnscopedHelperName("__rest"), - /*typeArguments*/ undefined, - [ - value, - setTextRange( - factory.createArrayLiteralExpression(propertyNames), - location - )] - ); } + return factory.createCallExpression( + getUnscopedHelperName("__rest"), + /*typeArguments*/ undefined, + [ + value, + setTextRange( + factory.createArrayLiteralExpression(propertyNames), + location + )] + ); + } - // ES2017 Helpers - - function createAwaiterHelper(hasLexicalThis: boolean, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression | undefined, body: Block) { - context.requestEmitHelper(awaiterHelper); + // ES2017 Helpers - const generatorFunc = factory.createFunctionExpression( - /*modifiers*/ undefined, - factory.createToken(SyntaxKind.AsteriskToken), - /*name*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ [], - /*type*/ undefined, - body - ); + function createAwaiterHelper(hasLexicalThis: boolean, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression | undefined, body: Block) { + context.requestEmitHelper(awaiterHelper); + + const generatorFunc = factory.createFunctionExpression( + /*modifiers*/ undefined, + factory.createToken(SyntaxKind.AsteriskToken), + /*name*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ [], + /*type*/ undefined, + body + ); + + // Mark this node as originally an async function + (generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; + + return factory.createCallExpression( + getUnscopedHelperName("__awaiter"), + /*typeArguments*/ undefined, + [ + hasLexicalThis ? factory.createThis() : factory.createVoidZero(), + hasLexicalArguments ? factory.createIdentifier("arguments") : factory.createVoidZero(), + promiseConstructor ? createExpressionFromEntityName(factory, promiseConstructor) : factory.createVoidZero(), + generatorFunc + ] + ); + } - // Mark this node as originally an async function - (generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; + // ES2015 Helpers - return factory.createCallExpression( - getUnscopedHelperName("__awaiter"), - /*typeArguments*/ undefined, - [ - hasLexicalThis ? factory.createThis() : factory.createVoidZero(), - hasLexicalArguments ? factory.createIdentifier("arguments") : factory.createVoidZero(), - promiseConstructor ? createExpressionFromEntityName(factory, promiseConstructor) : factory.createVoidZero(), - generatorFunc - ] - ); - } + function createExtendsHelper(name: Identifier) { + context.requestEmitHelper(extendsHelper); + return factory.createCallExpression( + getUnscopedHelperName("__extends"), + /*typeArguments*/ undefined, + [name, factory.createUniqueName("_super", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel)] + ); + } - // ES2015 Helpers + function createTemplateObjectHelper(cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) { + context.requestEmitHelper(templateObjectHelper); + return factory.createCallExpression( + getUnscopedHelperName("__makeTemplateObject"), + /*typeArguments*/ undefined, + [cooked, raw] + ); + } - function createExtendsHelper(name: Identifier) { - context.requestEmitHelper(extendsHelper); - return factory.createCallExpression( - getUnscopedHelperName("__extends"), - /*typeArguments*/ undefined, - [name, factory.createUniqueName("_super", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel)] - ); - } + function createSpreadArrayHelper(to: Expression, from: Expression, packFrom: boolean) { + context.requestEmitHelper(spreadArrayHelper); + return factory.createCallExpression( + getUnscopedHelperName("__spreadArray"), + /*typeArguments*/ undefined, + [to, from, packFrom ? immutableTrue() : immutableFalse()] + ); + } - function createTemplateObjectHelper(cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) { - context.requestEmitHelper(templateObjectHelper); - return factory.createCallExpression( - getUnscopedHelperName("__makeTemplateObject"), - /*typeArguments*/ undefined, - [cooked, raw] - ); - } + // ES2015 Destructuring Helpers - function createSpreadArrayHelper(to: Expression, from: Expression, packFrom: boolean) { - context.requestEmitHelper(spreadArrayHelper); - return factory.createCallExpression( - getUnscopedHelperName("__spreadArray"), - /*typeArguments*/ undefined, - [to, from, packFrom ? immutableTrue() : immutableFalse()] - ); - } + function createValuesHelper(expression: Expression) { + context.requestEmitHelper(valuesHelper); + return factory.createCallExpression( + getUnscopedHelperName("__values"), + /*typeArguments*/ undefined, + [expression] + ); + } - // ES2015 Destructuring Helpers + function createReadHelper(iteratorRecord: Expression, count: number | undefined) { + context.requestEmitHelper(readHelper); + return factory.createCallExpression( + getUnscopedHelperName("__read"), + /*typeArguments*/ undefined, + count !== undefined + ? [iteratorRecord, factory.createNumericLiteral(count + "")] + : [iteratorRecord] + ); + } - function createValuesHelper(expression: Expression) { - context.requestEmitHelper(valuesHelper); - return factory.createCallExpression( - getUnscopedHelperName("__values"), - /*typeArguments*/ undefined, - [expression] - ); - } + // ES2015 Generator Helpers - function createReadHelper(iteratorRecord: Expression, count: number | undefined) { - context.requestEmitHelper(readHelper); - return factory.createCallExpression( - getUnscopedHelperName("__read"), - /*typeArguments*/ undefined, - count !== undefined - ? [iteratorRecord, factory.createNumericLiteral(count + "")] - : [iteratorRecord] - ); - } + function createGeneratorHelper(body: FunctionExpression) { + context.requestEmitHelper(generatorHelper); + return factory.createCallExpression( + getUnscopedHelperName("__generator"), + /*typeArguments*/ undefined, + [factory.createThis(), body]); + } - // ES2015 Generator Helpers + // ES Module Helpers - function createGeneratorHelper(body: FunctionExpression) { - context.requestEmitHelper(generatorHelper); - return factory.createCallExpression( - getUnscopedHelperName("__generator"), - /*typeArguments*/ undefined, - [factory.createThis(), body]); - } + function createCreateBindingHelper(module: Expression, inputName: Expression, outputName: Expression | undefined) { + context.requestEmitHelper(createBindingHelper); + return factory.createCallExpression( + getUnscopedHelperName("__createBinding"), + /*typeArguments*/ undefined, + [factory.createIdentifier("exports"), module, inputName, ...(outputName ? [outputName] : [])]); + } - // ES Module Helpers + function createImportStarHelper(expression: Expression) { + context.requestEmitHelper(importStarHelper); + return factory.createCallExpression( + getUnscopedHelperName("__importStar"), + /*typeArguments*/ undefined, + [expression] + ); + } - function createCreateBindingHelper(module: Expression, inputName: Expression, outputName: Expression | undefined) { - context.requestEmitHelper(createBindingHelper); - return factory.createCallExpression( - getUnscopedHelperName("__createBinding"), - /*typeArguments*/ undefined, - [factory.createIdentifier("exports"), module, inputName, ...(outputName ? [outputName] : [])]); - } + function createImportStarCallbackHelper() { + context.requestEmitHelper(importStarHelper); + return getUnscopedHelperName("__importStar"); + } - function createImportStarHelper(expression: Expression) { - context.requestEmitHelper(importStarHelper); - return factory.createCallExpression( - getUnscopedHelperName("__importStar"), - /*typeArguments*/ undefined, - [expression] - ); - } + function createImportDefaultHelper(expression: Expression) { + context.requestEmitHelper(importDefaultHelper); + return factory.createCallExpression( + getUnscopedHelperName("__importDefault"), + /*typeArguments*/ undefined, + [expression] + ); + } - function createImportStarCallbackHelper() { - context.requestEmitHelper(importStarHelper); - return getUnscopedHelperName("__importStar"); - } + function createExportStarHelper(moduleExpression: Expression, exportsExpression: Expression = factory.createIdentifier("exports")) { + context.requestEmitHelper(exportStarHelper); + context.requestEmitHelper(createBindingHelper); + return factory.createCallExpression( + getUnscopedHelperName("__exportStar"), + /*typeArguments*/ undefined, + [moduleExpression, exportsExpression] + ); + } - function createImportDefaultHelper(expression: Expression) { - context.requestEmitHelper(importDefaultHelper); - return factory.createCallExpression( - getUnscopedHelperName("__importDefault"), - /*typeArguments*/ undefined, - [expression] - ); - } + // Class Fields Helpers - function createExportStarHelper(moduleExpression: Expression, exportsExpression: Expression = factory.createIdentifier("exports")) { - context.requestEmitHelper(exportStarHelper); - context.requestEmitHelper(createBindingHelper); - return factory.createCallExpression( - getUnscopedHelperName("__exportStar"), - /*typeArguments*/ undefined, - [moduleExpression, exportsExpression] - ); + function createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined) { + context.requestEmitHelper(classPrivateFieldGetHelper); + let args; + if (!f) { + args = [receiver, state, factory.createStringLiteral(kind)]; } - - // Class Fields Helpers - - function createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined) { - context.requestEmitHelper(classPrivateFieldGetHelper); - let args; - if (!f) { - args = [receiver, state, factory.createStringLiteral(kind)]; - } - else { - args = [receiver, state, factory.createStringLiteral(kind), f]; - } - return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldGet"), /*typeArguments*/ undefined, args); + else { + args = [receiver, state, factory.createStringLiteral(kind), f]; } + return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldGet"), /*typeArguments*/ undefined, args); + } - function createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined) { - context.requestEmitHelper(classPrivateFieldSetHelper); - let args; - if (!f) { - args = [receiver, state, value, factory.createStringLiteral(kind)]; - } - else { - args = [receiver, state, value, factory.createStringLiteral(kind), f]; - } - return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldSet"), /*typeArguments*/ undefined, args); + function createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined) { + context.requestEmitHelper(classPrivateFieldSetHelper); + let args; + if (!f) { + args = [receiver, state, value, factory.createStringLiteral(kind)]; } - - function createClassPrivateFieldInHelper(state: Identifier, receiver: Expression) { - context.requestEmitHelper(classPrivateFieldInHelper); - return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldIn"), /* typeArguments*/ undefined, [state, receiver]); + else { + args = [receiver, state, value, factory.createStringLiteral(kind), f]; } + return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldSet"), /*typeArguments*/ undefined, args); } - /* @internal */ - export function compareEmitHelpers(x: EmitHelper, y: EmitHelper) { - if (x === y) return Comparison.EqualTo; - if (x.priority === y.priority) return Comparison.EqualTo; - if (x.priority === undefined) return Comparison.GreaterThan; - if (y.priority === undefined) return Comparison.LessThan; - return compareValues(x.priority, y.priority); + function createClassPrivateFieldInHelper(state: Identifier, receiver: Expression) { + context.requestEmitHelper(classPrivateFieldInHelper); + return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldIn"), /* typeArguments*/ undefined, [state, receiver]); } +} - /** - * @param input Template string input strings - * @param args Names which need to be made file-level unique - */ - export function helperString(input: TemplateStringsArray, ...args: string[]) { - return (uniqueName: EmitHelperUniqueNameCallback) => { - let result = ""; - for (let i = 0; i < args.length; i++) { - result += input[i]; - result += uniqueName(args[i]); - } - result += input[input.length - 1]; - return result; - }; - } +/* @internal */ +export function compareEmitHelpers(x: EmitHelper, y: EmitHelper) { + if (x === y) return Comparison.EqualTo; + if (x.priority === y.priority) return Comparison.EqualTo; + if (x.priority === undefined) return Comparison.GreaterThan; + if (y.priority === undefined) return Comparison.LessThan; + return compareValues(x.priority, y.priority); +} - // TypeScript Helpers +/** + * @param input Template string input strings + * @param args Names which need to be made file-level unique + */ +export function helperString(input: TemplateStringsArray, ...args: string[]) { + return (uniqueName: EmitHelperUniqueNameCallback) => { + let result = ""; + for (let i = 0; i < args.length; i++) { + result += input[i]; + result += uniqueName(args[i]); + } + result += input[input.length - 1]; + return result; + }; +} + +// TypeScript Helpers - export const decorateHelper: UnscopedEmitHelper = { - name: "typescript:decorate", - importName: "__decorate", - scoped: false, - priority: 2, - text: ` +export const decorateHelper: UnscopedEmitHelper = { + name: "typescript:decorate", + importName: "__decorate", + scoped: false, + priority: 2, + text: ` var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; };` - }; - - export const metadataHelper: UnscopedEmitHelper = { - name: "typescript:metadata", - importName: "__metadata", - scoped: false, - priority: 3, - text: ` +}; + +export const metadataHelper: UnscopedEmitHelper = { + name: "typescript:metadata", + importName: "__metadata", + scoped: false, + priority: 3, + text: ` var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); };` - }; - - export const paramHelper: UnscopedEmitHelper = { - name: "typescript:param", - importName: "__param", - scoped: false, - priority: 4, - text: ` +}; + +export const paramHelper: UnscopedEmitHelper = { + name: "typescript:param", + importName: "__param", + scoped: false, + priority: 4, + text: ` var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } };` - }; +}; - // ES2018 Helpers +// ES2018 Helpers - export const assignHelper: UnscopedEmitHelper = { - name: "typescript:assign", - importName: "__assign", - scoped: false, - priority: 1, - text: ` +export const assignHelper: UnscopedEmitHelper = { + name: "typescript:assign", + importName: "__assign", + scoped: false, + priority: 1, + text: ` var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { @@ -485,22 +485,22 @@ namespace ts { }; return __assign.apply(this, arguments); };` - }; +}; - export const awaitHelper: UnscopedEmitHelper = { - name: "typescript:await", - importName: "__await", - scoped: false, - text: ` +export const awaitHelper: UnscopedEmitHelper = { + name: "typescript:await", + importName: "__await", + scoped: false, + text: ` var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }` - }; - - export const asyncGeneratorHelper: UnscopedEmitHelper = { - name: "typescript:asyncGenerator", - importName: "__asyncGenerator", - scoped: false, - dependencies: [awaitHelper], - text: ` +}; + +export const asyncGeneratorHelper: UnscopedEmitHelper = { + name: "typescript:asyncGenerator", + importName: "__asyncGenerator", + scoped: false, + dependencies: [awaitHelper], + text: ` var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; @@ -512,26 +512,26 @@ namespace ts { function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } };` - }; - - export const asyncDelegator: UnscopedEmitHelper = { - name: "typescript:asyncDelegator", - importName: "__asyncDelegator", - scoped: false, - dependencies: [awaitHelper], - text: ` +}; + +export const asyncDelegator: UnscopedEmitHelper = { + name: "typescript:asyncDelegator", + importName: "__asyncDelegator", + scoped: false, + dependencies: [awaitHelper], + text: ` var __asyncDelegator = (this && this.__asyncDelegator) || function (o) { var i, p; return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } };` - }; +}; - export const asyncValues: UnscopedEmitHelper = { - name: "typescript:asyncValues", - importName: "__asyncValues", - scoped: false, - text: ` +export const asyncValues: UnscopedEmitHelper = { + name: "typescript:asyncValues", + importName: "__asyncValues", + scoped: false, + text: ` var __asyncValues = (this && this.__asyncValues) || function (o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; @@ -539,15 +539,15 @@ namespace ts { function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } };` - }; +}; - // ES2018 Destructuring Helpers +// ES2018 Destructuring Helpers - export const restHelper: UnscopedEmitHelper = { - name: "typescript:rest", - importName: "__rest", - scoped: false, - text: ` +export const restHelper: UnscopedEmitHelper = { + name: "typescript:rest", + importName: "__rest", + scoped: false, + text: ` var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) @@ -559,16 +559,16 @@ namespace ts { } return t; };` - }; +}; - // ES2017 Helpers +// ES2017 Helpers - export const awaiterHelper: UnscopedEmitHelper = { - name: "typescript:awaiter", - importName: "__awaiter", - scoped: false, - priority: 5, - text: ` +export const awaiterHelper: UnscopedEmitHelper = { + name: "typescript:awaiter", + importName: "__awaiter", + scoped: false, + priority: 5, + text: ` var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { @@ -578,16 +578,16 @@ namespace ts { step((generator = generator.apply(thisArg, _arguments || [])).next()); }); };` - }; +}; - // ES2015 Helpers +// ES2015 Helpers - export const extendsHelper: UnscopedEmitHelper = { - name: "typescript:extends", - importName: "__extends", - scoped: false, - priority: 0, - text: ` +export const extendsHelper: UnscopedEmitHelper = { + name: "typescript:extends", + importName: "__extends", + scoped: false, + priority: 0, + text: ` var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || @@ -604,25 +604,25 @@ namespace ts { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })();` - }; - - export const templateObjectHelper: UnscopedEmitHelper = { - name: "typescript:makeTemplateObject", - importName: "__makeTemplateObject", - scoped: false, - priority: 0, - text: ` +}; + +export const templateObjectHelper: UnscopedEmitHelper = { + name: "typescript:makeTemplateObject", + importName: "__makeTemplateObject", + scoped: false, + priority: 0, + text: ` var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; };` - }; +}; - export const readHelper: UnscopedEmitHelper = { - name: "typescript:read", - importName: "__read", - scoped: false, - text: ` +export const readHelper: UnscopedEmitHelper = { + name: "typescript:read", + importName: "__read", + scoped: false, + text: ` var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; @@ -639,13 +639,13 @@ namespace ts { } return ar; };` - }; +}; - export const spreadArrayHelper: UnscopedEmitHelper = { - name: "typescript:spreadArray", - importName: "__spreadArray", - scoped: false, - text: ` +export const spreadArrayHelper: UnscopedEmitHelper = { + name: "typescript:spreadArray", + importName: "__spreadArray", + scoped: false, + text: ` var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { @@ -655,15 +655,15 @@ namespace ts { } return to.concat(ar || Array.prototype.slice.call(from)); };` - }; +}; - // ES2015 Destructuring Helpers +// ES2015 Destructuring Helpers - export const valuesHelper: UnscopedEmitHelper = { - name: "typescript:values", - importName: "__values", - scoped: false, - text: ` +export const valuesHelper: UnscopedEmitHelper = { + name: "typescript:values", + importName: "__values", + scoped: false, + text: ` var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); @@ -675,75 +675,75 @@ namespace ts { }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); };` - }; - - // ES2015 Generator Helpers - - // The __generator helper is used by down-level transformations to emulate the runtime - // semantics of an ES2015 generator function. When called, this helper returns an - // object that implements the Iterator protocol, in that it has `next`, `return`, and - // `throw` methods that step through the generator when invoked. - // - // parameters: - // @param thisArg The value to use as the `this` binding for the transformed generator body. - // @param body A function that acts as the transformed generator body. - // - // variables: - // _ Persistent state for the generator that is shared between the helper and the - // generator body. The state object has the following members: - // sent() - A method that returns or throws the current completion value. - // label - The next point at which to resume evaluation of the generator body. - // trys - A stack of protected regions (try/catch/finally blocks). - // ops - A stack of pending instructions when inside of a finally block. - // f A value indicating whether the generator is executing. - // y An iterator to delegate for a yield*. - // t A temporary variable that holds one of the following values (note that these - // cases do not overlap): - // - The completion value when resuming from a `yield` or `yield*`. - // - The error value for a catch block. - // - The current protected region (array of try/catch/finally/end labels). - // - The verb (`next`, `throw`, or `return` method) to delegate to the expression - // of a `yield*`. - // - The result of evaluating the verb delegated to the expression of a `yield*`. - // - // functions: - // verb(n) Creates a bound callback to the `step` function for opcode `n`. - // step(op) Evaluates opcodes in a generator body until execution is suspended or - // completed. - // - // The __generator helper understands a limited set of instructions: - // 0: next(value?) - Start or resume the generator with the specified value. - // 1: throw(error) - Resume the generator with an exception. If the generator is - // suspended inside of one or more protected regions, evaluates - // any intervening finally blocks between the current label and - // the nearest catch block or function boundary. If uncaught, the - // exception is thrown to the caller. - // 2: return(value?) - Resume the generator as if with a return. If the generator is - // suspended inside of one or more protected regions, evaluates any - // intervening finally blocks. - // 3: break(label) - Jump to the specified label. If the label is outside of the - // current protected region, evaluates any intervening finally - // blocks. - // 4: yield(value?) - Yield execution to the caller with an optional value. When - // resumed, the generator will continue at the next label. - // 5: yield*(value) - Delegates evaluation to the supplied iterator. When - // delegation completes, the generator will continue at the next - // label. - // 6: catch(error) - Handles an exception thrown from within the generator body. If - // the current label is inside of one or more protected regions, - // evaluates any intervening finally blocks between the current - // label and the nearest catch block or function boundary. If - // uncaught, the exception is thrown to the caller. - // 7: endfinally - Ends a finally block, resuming the last instruction prior to - // entering a finally block. - // - // For examples of how these are used, see the comments in ./transformers/generators.ts - export const generatorHelper: UnscopedEmitHelper = { - name: "typescript:generator", - importName: "__generator", - scoped: false, - priority: 6, - text: ` +}; + +// ES2015 Generator Helpers + +// The __generator helper is used by down-level transformations to emulate the runtime +// semantics of an ES2015 generator function. When called, this helper returns an +// object that implements the Iterator protocol, in that it has `next`, `return`, and +// `throw` methods that step through the generator when invoked. +// +// parameters: +// @param thisArg The value to use as the `this` binding for the transformed generator body. +// @param body A function that acts as the transformed generator body. +// +// variables: +// _ Persistent state for the generator that is shared between the helper and the +// generator body. The state object has the following members: +// sent() - A method that returns or throws the current completion value. +// label - The next point at which to resume evaluation of the generator body. +// trys - A stack of protected regions (try/catch/finally blocks). +// ops - A stack of pending instructions when inside of a finally block. +// f A value indicating whether the generator is executing. +// y An iterator to delegate for a yield*. +// t A temporary variable that holds one of the following values (note that these +// cases do not overlap): +// - The completion value when resuming from a `yield` or `yield*`. +// - The error value for a catch block. +// - The current protected region (array of try/catch/finally/end labels). +// - The verb (`next`, `throw`, or `return` method) to delegate to the expression +// of a `yield*`. +// - The result of evaluating the verb delegated to the expression of a `yield*`. +// +// functions: +// verb(n) Creates a bound callback to the `step` function for opcode `n`. +// step(op) Evaluates opcodes in a generator body until execution is suspended or +// completed. +// +// The __generator helper understands a limited set of instructions: +// 0: next(value?) - Start or resume the generator with the specified value. +// 1: throw(error) - Resume the generator with an exception. If the generator is +// suspended inside of one or more protected regions, evaluates +// any intervening finally blocks between the current label and +// the nearest catch block or function boundary. If uncaught, the +// exception is thrown to the caller. +// 2: return(value?) - Resume the generator as if with a return. If the generator is +// suspended inside of one or more protected regions, evaluates any +// intervening finally blocks. +// 3: break(label) - Jump to the specified label. If the label is outside of the +// current protected region, evaluates any intervening finally +// blocks. +// 4: yield(value?) - Yield execution to the caller with an optional value. When +// resumed, the generator will continue at the next label. +// 5: yield*(value) - Delegates evaluation to the supplied iterator. When +// delegation completes, the generator will continue at the next +// label. +// 6: catch(error) - Handles an exception thrown from within the generator body. If +// the current label is inside of one or more protected regions, +// evaluates any intervening finally blocks between the current +// label and the nearest catch block or function boundary. If +// uncaught, the exception is thrown to the caller. +// 7: endfinally - Ends a finally block, resuming the last instruction prior to +// entering a finally block. +// +// For examples of how these are used, see the comments in ./transformers/generators.ts +export const generatorHelper: UnscopedEmitHelper = { + name: "typescript:generator", + importName: "__generator", + scoped: false, + priority: 6, + text: ` var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; @@ -771,16 +771,16 @@ namespace ts { if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } };` - }; +}; - // ES Module Helpers +// ES Module Helpers - export const createBindingHelper: UnscopedEmitHelper = { - name: "typescript:commonjscreatebinding", - importName: "__createBinding", - scoped: false, - priority: 1, - text: ` +export const createBindingHelper: UnscopedEmitHelper = { + name: "typescript:commonjscreatebinding", + importName: "__createBinding", + scoped: false, + priority: 1, + text: ` var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); @@ -788,29 +788,29 @@ namespace ts { if (k2 === undefined) k2 = k; o[k2] = m[k]; }));` - }; - - export const setModuleDefaultHelper: UnscopedEmitHelper = { - name: "typescript:commonjscreatevalue", - importName: "__setModuleDefault", - scoped: false, - priority: 1, - text: ` +}; + +export const setModuleDefaultHelper: UnscopedEmitHelper = { + name: "typescript:commonjscreatevalue", + importName: "__setModuleDefault", + scoped: false, + priority: 1, + text: ` var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; });` - }; - - // emit helper for `import * as Name from "foo"` - export const importStarHelper: UnscopedEmitHelper = { - name: "typescript:commonjsimportstar", - importName: "__importStar", - scoped: false, - dependencies: [createBindingHelper, setModuleDefaultHelper], - priority: 2, - text: ` +}; + +// emit helper for `import * as Name from "foo"` +export const importStarHelper: UnscopedEmitHelper = { + name: "typescript:commonjsimportstar", + importName: "__importStar", + scoped: false, + dependencies: [createBindingHelper, setModuleDefaultHelper], + priority: 2, + text: ` var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; @@ -818,230 +818,230 @@ namespace ts { __setModuleDefault(result, mod); return result; };` - }; - - // emit helper for `import Name from "foo"` - export const importDefaultHelper: UnscopedEmitHelper = { - name: "typescript:commonjsimportdefault", - importName: "__importDefault", - scoped: false, - text: ` +}; + +// emit helper for `import Name from "foo"` +export const importDefaultHelper: UnscopedEmitHelper = { + name: "typescript:commonjsimportdefault", + importName: "__importDefault", + scoped: false, + text: ` var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; };` - }; - - export const exportStarHelper: UnscopedEmitHelper = { - name: "typescript:export-star", - importName: "__exportStar", - scoped: false, - dependencies: [createBindingHelper], - priority: 2, - text: ` +}; + +export const exportStarHelper: UnscopedEmitHelper = { + name: "typescript:export-star", + importName: "__exportStar", + scoped: false, + dependencies: [createBindingHelper], + priority: 2, + text: ` var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); };` - }; - - /** - * Parameters: - * @param receiver — The object from which the private member will be read. - * @param state — One of the following: - * - A WeakMap used to read a private instance field. - * - A WeakSet used as an instance brand for private instance methods and accessors. - * - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors. - * @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values: - * - undefined — Indicates a private instance field (pre TS 4.3). - * - "f" — Indicates a private field (instance or static). - * - "m" — Indicates a private method (instance or static). - * - "a" — Indicates a private accessor (instance or static). - * @param f — (optional pre TS 4.3) Depends on the arguments for state and kind: - * - If kind is "m", this should be the function corresponding to the static or instance method. - * - If kind is "a", this should be the function corresponding to the getter method, or undefined if the getter was not defined. - * - If kind is "f" and state is a function, this should be an object holding the value of a static field, or undefined if the static field declaration has not yet been evaluated. - * Usage: - * This helper will only ever be used by the compiler in the following ways: - * - * Reading from a private instance field (pre TS 4.3): - * __classPrivateFieldGet(, ) - * - * Reading from a private instance field (TS 4.3+): - * __classPrivateFieldGet(, , "f") - * - * Reading from a private instance get accessor (when defined, TS 4.3+): - * __classPrivateFieldGet(, , "a", ) - * - * Reading from a private instance get accessor (when not defined, TS 4.3+): - * __classPrivateFieldGet(, , "a", void 0) - * NOTE: This always results in a runtime error. - * - * Reading from a private instance method (TS 4.3+): - * __classPrivateFieldGet(, , "m", ) - * - * Reading from a private static field (TS 4.3+): - * __classPrivateFieldGet(, , "f", <{ value: any }>) - * - * Reading from a private static get accessor (when defined, TS 4.3+): - * __classPrivateFieldGet(, , "a", ) - * - * Reading from a private static get accessor (when not defined, TS 4.3+): - * __classPrivateFieldGet(, , "a", void 0) - * NOTE: This always results in a runtime error. - * - * Reading from a private static method (TS 4.3+): - * __classPrivateFieldGet(, , "m", ) - */ - export const classPrivateFieldGetHelper: UnscopedEmitHelper = { - name: "typescript:classPrivateFieldGet", - importName: "__classPrivateFieldGet", - scoped: false, - text: ` +}; + +/** + * Parameters: + * @param receiver — The object from which the private member will be read. + * @param state — One of the following: + * - A WeakMap used to read a private instance field. + * - A WeakSet used as an instance brand for private instance methods and accessors. + * - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors. + * @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values: + * - undefined — Indicates a private instance field (pre TS 4.3). + * - "f" — Indicates a private field (instance or static). + * - "m" — Indicates a private method (instance or static). + * - "a" — Indicates a private accessor (instance or static). + * @param f — (optional pre TS 4.3) Depends on the arguments for state and kind: + * - If kind is "m", this should be the function corresponding to the static or instance method. + * - If kind is "a", this should be the function corresponding to the getter method, or undefined if the getter was not defined. + * - If kind is "f" and state is a function, this should be an object holding the value of a static field, or undefined if the static field declaration has not yet been evaluated. + * Usage: + * This helper will only ever be used by the compiler in the following ways: + * + * Reading from a private instance field (pre TS 4.3): + * __classPrivateFieldGet(, ) + * + * Reading from a private instance field (TS 4.3+): + * __classPrivateFieldGet(, , "f") + * + * Reading from a private instance get accessor (when defined, TS 4.3+): + * __classPrivateFieldGet(, , "a", ) + * + * Reading from a private instance get accessor (when not defined, TS 4.3+): + * __classPrivateFieldGet(, , "a", void 0) + * NOTE: This always results in a runtime error. + * + * Reading from a private instance method (TS 4.3+): + * __classPrivateFieldGet(, , "m", ) + * + * Reading from a private static field (TS 4.3+): + * __classPrivateFieldGet(, , "f", <{ value: any }>) + * + * Reading from a private static get accessor (when defined, TS 4.3+): + * __classPrivateFieldGet(, , "a", ) + * + * Reading from a private static get accessor (when not defined, TS 4.3+): + * __classPrivateFieldGet(, , "a", void 0) + * NOTE: This always results in a runtime error. + * + * Reading from a private static method (TS 4.3+): + * __classPrivateFieldGet(, , "m", ) + */ +export const classPrivateFieldGetHelper: UnscopedEmitHelper = { + name: "typescript:classPrivateFieldGet", + importName: "__classPrivateFieldGet", + scoped: false, + text: ` var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); };` - }; - - /** - * Parameters: - * @param receiver — The object on which the private member will be set. - * @param state — One of the following: - * - A WeakMap used to store a private instance field. - * - A WeakSet used as an instance brand for private instance methods and accessors. - * - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors. - * @param value — The value to set. - * @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values: - * - undefined — Indicates a private instance field (pre TS 4.3). - * - "f" — Indicates a private field (instance or static). - * - "m" — Indicates a private method (instance or static). - * - "a" — Indicates a private accessor (instance or static). - * @param f — (optional pre TS 4.3) Depends on the arguments for state and kind: - * - If kind is "m", this should be the function corresponding to the static or instance method. - * - If kind is "a", this should be the function corresponding to the setter method, or undefined if the setter was not defined. - * - If kind is "f" and state is a function, this should be an object holding the value of a static field, or undefined if the static field declaration has not yet been evaluated. - * Usage: - * This helper will only ever be used by the compiler in the following ways: - * - * Writing to a private instance field (pre TS 4.3): - * __classPrivateFieldSet(, , ) - * - * Writing to a private instance field (TS 4.3+): - * __classPrivateFieldSet(, , , "f") - * - * Writing to a private instance set accessor (when defined, TS 4.3+): - * __classPrivateFieldSet(, , , "a", ) - * - * Writing to a private instance set accessor (when not defined, TS 4.3+): - * __classPrivateFieldSet(, , , "a", void 0) - * NOTE: This always results in a runtime error. - * - * Writing to a private instance method (TS 4.3+): - * __classPrivateFieldSet(, , , "m", ) - * NOTE: This always results in a runtime error. - * - * Writing to a private static field (TS 4.3+): - * __classPrivateFieldSet(, , , "f", <{ value: any }>) - * - * Writing to a private static set accessor (when defined, TS 4.3+): - * __classPrivateFieldSet(, , , "a", ) - * - * Writing to a private static set accessor (when not defined, TS 4.3+): - * __classPrivateFieldSet(, , , "a", void 0) - * NOTE: This always results in a runtime error. - * - * Writing to a private static method (TS 4.3+): - * __classPrivateFieldSet(, , , "m", ) - * NOTE: This always results in a runtime error. - */ - export const classPrivateFieldSetHelper: UnscopedEmitHelper = { - name: "typescript:classPrivateFieldSet", - importName: "__classPrivateFieldSet", - scoped: false, - text: ` +}; + +/** + * Parameters: + * @param receiver — The object on which the private member will be set. + * @param state — One of the following: + * - A WeakMap used to store a private instance field. + * - A WeakSet used as an instance brand for private instance methods and accessors. + * - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors. + * @param value — The value to set. + * @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values: + * - undefined — Indicates a private instance field (pre TS 4.3). + * - "f" — Indicates a private field (instance or static). + * - "m" — Indicates a private method (instance or static). + * - "a" — Indicates a private accessor (instance or static). + * @param f — (optional pre TS 4.3) Depends on the arguments for state and kind: + * - If kind is "m", this should be the function corresponding to the static or instance method. + * - If kind is "a", this should be the function corresponding to the setter method, or undefined if the setter was not defined. + * - If kind is "f" and state is a function, this should be an object holding the value of a static field, or undefined if the static field declaration has not yet been evaluated. + * Usage: + * This helper will only ever be used by the compiler in the following ways: + * + * Writing to a private instance field (pre TS 4.3): + * __classPrivateFieldSet(, , ) + * + * Writing to a private instance field (TS 4.3+): + * __classPrivateFieldSet(, , , "f") + * + * Writing to a private instance set accessor (when defined, TS 4.3+): + * __classPrivateFieldSet(, , , "a", ) + * + * Writing to a private instance set accessor (when not defined, TS 4.3+): + * __classPrivateFieldSet(, , , "a", void 0) + * NOTE: This always results in a runtime error. + * + * Writing to a private instance method (TS 4.3+): + * __classPrivateFieldSet(, , , "m", ) + * NOTE: This always results in a runtime error. + * + * Writing to a private static field (TS 4.3+): + * __classPrivateFieldSet(, , , "f", <{ value: any }>) + * + * Writing to a private static set accessor (when defined, TS 4.3+): + * __classPrivateFieldSet(, , , "a", ) + * + * Writing to a private static set accessor (when not defined, TS 4.3+): + * __classPrivateFieldSet(, , , "a", void 0) + * NOTE: This always results in a runtime error. + * + * Writing to a private static method (TS 4.3+): + * __classPrivateFieldSet(, , , "m", ) + * NOTE: This always results in a runtime error. + */ +export const classPrivateFieldSetHelper: UnscopedEmitHelper = { + name: "typescript:classPrivateFieldSet", + importName: "__classPrivateFieldSet", + scoped: false, + text: ` var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; };` - }; - - /** - * Parameters: - * @param state — One of the following: - * - A WeakMap when the member is a private instance field. - * - A WeakSet when the member is a private instance method or accessor. - * - A function value that should be the undecorated class constructor when the member is a private static field, method, or accessor. - * @param receiver — The object being checked if it has the private member. - * - * Usage: - * This helper is used to transform `#field in expression` to - * `__classPrivateFieldIn(, expression)` - */ - export const classPrivateFieldInHelper: UnscopedEmitHelper = { - name: "typescript:classPrivateFieldIn", - importName: "__classPrivateFieldIn", - scoped: false, - text: ` +}; + +/** + * Parameters: + * @param state — One of the following: + * - A WeakMap when the member is a private instance field. + * - A WeakSet when the member is a private instance method or accessor. + * - A function value that should be the undecorated class constructor when the member is a private static field, method, or accessor. + * @param receiver — The object being checked if it has the private member. + * + * Usage: + * This helper is used to transform `#field in expression` to + * `__classPrivateFieldIn(, expression)` + */ +export const classPrivateFieldInHelper: UnscopedEmitHelper = { + name: "typescript:classPrivateFieldIn", + importName: "__classPrivateFieldIn", + scoped: false, + text: ` var __classPrivateFieldIn = (this && this.__classPrivateFieldIn) || function(state, receiver) { if (receiver === null || (typeof receiver !== "object" && typeof receiver !== "function")) throw new TypeError("Cannot use 'in' operator on non-object"); return typeof state === "function" ? receiver === state : state.has(receiver); };` - }; - - let allUnscopedEmitHelpers: ReadonlyESMap | undefined; - - export function getAllUnscopedEmitHelpers() { - return allUnscopedEmitHelpers || (allUnscopedEmitHelpers = arrayToMap([ - decorateHelper, - metadataHelper, - paramHelper, - assignHelper, - awaitHelper, - asyncGeneratorHelper, - asyncDelegator, - asyncValues, - restHelper, - awaiterHelper, - extendsHelper, - templateObjectHelper, - spreadArrayHelper, - valuesHelper, - readHelper, - generatorHelper, - importStarHelper, - importDefaultHelper, - exportStarHelper, - classPrivateFieldGetHelper, - classPrivateFieldSetHelper, - classPrivateFieldInHelper, - createBindingHelper, - setModuleDefaultHelper - ], helper => helper.name)); - } +}; + +let allUnscopedEmitHelpers: ReadonlyESMap | undefined; + +export function getAllUnscopedEmitHelpers() { + return allUnscopedEmitHelpers || (allUnscopedEmitHelpers = arrayToMap([ + decorateHelper, + metadataHelper, + paramHelper, + assignHelper, + awaitHelper, + asyncGeneratorHelper, + asyncDelegator, + asyncValues, + restHelper, + awaiterHelper, + extendsHelper, + templateObjectHelper, + spreadArrayHelper, + valuesHelper, + readHelper, + generatorHelper, + importStarHelper, + importDefaultHelper, + exportStarHelper, + classPrivateFieldGetHelper, + classPrivateFieldSetHelper, + classPrivateFieldInHelper, + createBindingHelper, + setModuleDefaultHelper + ], helper => helper.name)); +} - export const asyncSuperHelper: EmitHelper = { - name: "typescript:async-super", - scoped: true, - text: helperString` +export const asyncSuperHelper: EmitHelper = { + name: "typescript:async-super", + scoped: true, + text: helperString` const ${"_superIndex"} = name => super[name];` - }; +}; - export const advancedAsyncSuperHelper: EmitHelper = { - name: "typescript:advanced-async-super", - scoped: true, - text: helperString` +export const advancedAsyncSuperHelper: EmitHelper = { + name: "typescript:advanced-async-super", + scoped: true, + text: helperString` const ${"_superIndex"} = (function (geti, seti) { const cache = Object.create(null); return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); })(name => super[name], (name, value) => super[name] = value);` - }; +}; - export function isCallToHelper(firstSegment: Expression, helperName: __String): boolean { - return isCallExpression(firstSegment) - && isIdentifier(firstSegment.expression) - && (getEmitFlags(firstSegment.expression) & EmitFlags.HelperName) !== 0 - && firstSegment.expression.escapedText === helperName; - } +export function isCallToHelper(firstSegment: Expression, helperName: __String): boolean { + return isCallExpression(firstSegment) + && isIdentifier(firstSegment.expression) + && (getEmitFlags(firstSegment.expression) & EmitFlags.HelperName) !== 0 + && firstSegment.expression.escapedText === helperName; +} } diff --git a/src/compiler/factory/emitNode.ts b/src/compiler/factory/emitNode.ts index ebb7300efdeec..5bbd924fb34ca 100644 --- a/src/compiler/factory/emitNode.ts +++ b/src/compiler/factory/emitNode.ts @@ -1,282 +1,282 @@ namespace ts { - /** - * Associates a node with the current transformation, initializing - * various transient transformation properties. - * @internal - */ - export function getOrCreateEmitNode(node: Node): EmitNode { - if (!node.emitNode) { - if (isParseTreeNode(node)) { - // To avoid holding onto transformation artifacts, we keep track of any - // parse tree node we are annotating. This allows us to clean them up after - // all transformations have completed. - if (node.kind === SyntaxKind.SourceFile) { - return node.emitNode = { annotatedNodes: [node] } as EmitNode; - } - - const sourceFile = getSourceFileOfNode(getParseTreeNode(getSourceFileOfNode(node))) ?? Debug.fail("Could not determine parsed source file."); - getOrCreateEmitNode(sourceFile).annotatedNodes!.push(node); +/** + * Associates a node with the current transformation, initializing + * various transient transformation properties. + * @internal + */ +export function getOrCreateEmitNode(node: Node): EmitNode { + if (!node.emitNode) { + if (isParseTreeNode(node)) { + // To avoid holding onto transformation artifacts, we keep track of any + // parse tree node we are annotating. This allows us to clean them up after + // all transformations have completed. + if (node.kind === SyntaxKind.SourceFile) { + return node.emitNode = { annotatedNodes: [node] } as EmitNode; } - node.emitNode = {} as EmitNode; + const sourceFile = getSourceFileOfNode(getParseTreeNode(getSourceFileOfNode(node))) ?? Debug.fail("Could not determine parsed source file."); + getOrCreateEmitNode(sourceFile).annotatedNodes!.push(node); } - else { - Debug.assert(!(node.emitNode.flags & EmitFlags.Immutable), "Invalid attempt to mutate an immutable node."); - } - return node.emitNode; - } - - /** - * Clears any `EmitNode` entries from parse-tree nodes. - * @param sourceFile A source file. - */ - export function disposeEmitNodes(sourceFile: SourceFile | undefined) { - // During transformation we may need to annotate a parse tree node with transient - // transformation properties. As parse tree nodes live longer than transformation - // nodes, we need to make sure we reclaim any memory allocated for custom ranges - // from these nodes to ensure we do not hold onto entire subtrees just for position - // information. We also need to reset these nodes to a pre-transformation state - // for incremental parsing scenarios so that we do not impact later emit. - const annotatedNodes = getSourceFileOfNode(getParseTreeNode(sourceFile))?.emitNode?.annotatedNodes; - if (annotatedNodes) { - for (const node of annotatedNodes) { - node.emitNode = undefined; - } - } - } - - /** - * Sets `EmitFlags.NoComments` on a node and removes any leading and trailing synthetic comments. - * @internal - */ - export function removeAllComments(node: T): T { - const emitNode = getOrCreateEmitNode(node); - emitNode.flags |= EmitFlags.NoComments; - emitNode.leadingComments = undefined; - emitNode.trailingComments = undefined; - return node; - } - - /** - * Sets flags that control emit behavior of a node. - */ - export function setEmitFlags(node: T, emitFlags: EmitFlags) { - getOrCreateEmitNode(node).flags = emitFlags; - return node; - } - - /** - * Sets flags that control emit behavior of a node. - */ - /* @internal */ - export function addEmitFlags(node: T, emitFlags: EmitFlags) { - const emitNode = getOrCreateEmitNode(node); - emitNode.flags = emitNode.flags | emitFlags; - return node; - } - - /** - * Gets a custom text range to use when emitting source maps. - */ - export function getSourceMapRange(node: Node): SourceMapRange { - return node.emitNode?.sourceMapRange ?? node; - } - - /** - * Sets a custom text range to use when emitting source maps. - */ - export function setSourceMapRange(node: T, range: SourceMapRange | undefined) { - getOrCreateEmitNode(node).sourceMapRange = range; - return node; - } - - /** - * Gets the TextRange to use for source maps for a token of a node. - */ - export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined { - return node.emitNode?.tokenSourceMapRanges?.[token]; - } - - /** - * Sets the TextRange to use for source maps for a token of a node. - */ - export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: SourceMapRange | undefined) { - const emitNode = getOrCreateEmitNode(node); - const tokenSourceMapRanges = emitNode.tokenSourceMapRanges ?? (emitNode.tokenSourceMapRanges = []); - tokenSourceMapRanges[token] = range; - return node; - } - - /** - * Gets a custom text range to use when emitting comments. - */ - /*@internal*/ - export function getStartsOnNewLine(node: Node) { - return node.emitNode?.startsOnNewLine; - } - - /** - * Sets a custom text range to use when emitting comments. - */ - /*@internal*/ - export function setStartsOnNewLine(node: T, newLine: boolean) { - getOrCreateEmitNode(node).startsOnNewLine = newLine; - return node; - } - - /** - * Gets a custom text range to use when emitting comments. - */ - export function getCommentRange(node: Node) { - return node.emitNode?.commentRange ?? node; - } - - /** - * Sets a custom text range to use when emitting comments. - */ - export function setCommentRange(node: T, range: TextRange) { - getOrCreateEmitNode(node).commentRange = range; - return node; - } - - export function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined { - return node.emitNode?.leadingComments; - } - - export function setSyntheticLeadingComments(node: T, comments: SynthesizedComment[] | undefined) { - getOrCreateEmitNode(node).leadingComments = comments; - return node; - } - - export function addSyntheticLeadingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { - return setSyntheticLeadingComments(node, append(getSyntheticLeadingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); - } - - export function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined { - return node.emitNode?.trailingComments; - } - - export function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[] | undefined) { - getOrCreateEmitNode(node).trailingComments = comments; - return node; - } - - export function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { - return setSyntheticTrailingComments(node, append(getSyntheticTrailingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); - } - - export function moveSyntheticComments(node: T, original: Node): T { - setSyntheticLeadingComments(node, getSyntheticLeadingComments(original)); - setSyntheticTrailingComments(node, getSyntheticTrailingComments(original)); - const emit = getOrCreateEmitNode(original); - emit.leadingComments = undefined; - emit.trailingComments = undefined; - return node; - } - - /** - * Gets the constant value to emit for an expression representing an enum. - */ - export function getConstantValue(node: AccessExpression): string | number | undefined { - return node.emitNode?.constantValue; - } - /** - * Sets the constant value to emit for an expression. - */ - export function setConstantValue(node: AccessExpression, value: string | number): AccessExpression { - const emitNode = getOrCreateEmitNode(node); - emitNode.constantValue = value; - return node; + node.emitNode = {} as EmitNode; } - - /** - * Adds an EmitHelper to a node. - */ - export function addEmitHelper(node: T, helper: EmitHelper): T { - const emitNode = getOrCreateEmitNode(node); - emitNode.helpers = append(emitNode.helpers, helper); - return node; + else { + Debug.assert(!(node.emitNode.flags & EmitFlags.Immutable), "Invalid attempt to mutate an immutable node."); } - - /** - * Add EmitHelpers to a node. - */ - export function addEmitHelpers(node: T, helpers: EmitHelper[] | undefined): T { - if (some(helpers)) { - const emitNode = getOrCreateEmitNode(node); - for (const helper of helpers) { - emitNode.helpers = appendIfUnique(emitNode.helpers, helper); - } + return node.emitNode; +} + +/** + * Clears any `EmitNode` entries from parse-tree nodes. + * @param sourceFile A source file. + */ +export function disposeEmitNodes(sourceFile: SourceFile | undefined) { + // During transformation we may need to annotate a parse tree node with transient + // transformation properties. As parse tree nodes live longer than transformation + // nodes, we need to make sure we reclaim any memory allocated for custom ranges + // from these nodes to ensure we do not hold onto entire subtrees just for position + // information. We also need to reset these nodes to a pre-transformation state + // for incremental parsing scenarios so that we do not impact later emit. + const annotatedNodes = getSourceFileOfNode(getParseTreeNode(sourceFile))?.emitNode?.annotatedNodes; + if (annotatedNodes) { + for (const node of annotatedNodes) { + node.emitNode = undefined; } - return node; } - - /** - * Removes an EmitHelper from a node. - */ - export function removeEmitHelper(node: Node, helper: EmitHelper): boolean { - const helpers = node.emitNode?.helpers; - if (helpers) { - return orderedRemoveItem(helpers, helper); +} + +/** + * Sets `EmitFlags.NoComments` on a node and removes any leading and trailing synthetic comments. + * @internal + */ +export function removeAllComments(node: T): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.flags |= EmitFlags.NoComments; + emitNode.leadingComments = undefined; + emitNode.trailingComments = undefined; + return node; +} + +/** + * Sets flags that control emit behavior of a node. + */ +export function setEmitFlags(node: T, emitFlags: EmitFlags) { + getOrCreateEmitNode(node).flags = emitFlags; + return node; +} + +/** + * Sets flags that control emit behavior of a node. + */ +/* @internal */ +export function addEmitFlags(node: T, emitFlags: EmitFlags) { + const emitNode = getOrCreateEmitNode(node); + emitNode.flags = emitNode.flags | emitFlags; + return node; +} + +/** + * Gets a custom text range to use when emitting source maps. + */ +export function getSourceMapRange(node: Node): SourceMapRange { + return node.emitNode?.sourceMapRange ?? node; +} + +/** + * Sets a custom text range to use when emitting source maps. + */ +export function setSourceMapRange(node: T, range: SourceMapRange | undefined) { + getOrCreateEmitNode(node).sourceMapRange = range; + return node; +} + +/** + * Gets the TextRange to use for source maps for a token of a node. + */ +export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined { + return node.emitNode?.tokenSourceMapRanges?.[token]; +} + +/** + * Sets the TextRange to use for source maps for a token of a node. + */ +export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: SourceMapRange | undefined) { + const emitNode = getOrCreateEmitNode(node); + const tokenSourceMapRanges = emitNode.tokenSourceMapRanges ?? (emitNode.tokenSourceMapRanges = []); + tokenSourceMapRanges[token] = range; + return node; +} + +/** + * Gets a custom text range to use when emitting comments. + */ +/*@internal*/ +export function getStartsOnNewLine(node: Node) { + return node.emitNode?.startsOnNewLine; +} + +/** + * Sets a custom text range to use when emitting comments. + */ +/*@internal*/ +export function setStartsOnNewLine(node: T, newLine: boolean) { + getOrCreateEmitNode(node).startsOnNewLine = newLine; + return node; +} + +/** + * Gets a custom text range to use when emitting comments. + */ +export function getCommentRange(node: Node) { + return node.emitNode?.commentRange ?? node; +} + +/** + * Sets a custom text range to use when emitting comments. + */ +export function setCommentRange(node: T, range: TextRange) { + getOrCreateEmitNode(node).commentRange = range; + return node; +} + +export function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined { + return node.emitNode?.leadingComments; +} + +export function setSyntheticLeadingComments(node: T, comments: SynthesizedComment[] | undefined) { + getOrCreateEmitNode(node).leadingComments = comments; + return node; +} + +export function addSyntheticLeadingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { + return setSyntheticLeadingComments(node, append(getSyntheticLeadingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); +} + +export function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined { + return node.emitNode?.trailingComments; +} + +export function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[] | undefined) { + getOrCreateEmitNode(node).trailingComments = comments; + return node; +} + +export function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { + return setSyntheticTrailingComments(node, append(getSyntheticTrailingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); +} + +export function moveSyntheticComments(node: T, original: Node): T { + setSyntheticLeadingComments(node, getSyntheticLeadingComments(original)); + setSyntheticTrailingComments(node, getSyntheticTrailingComments(original)); + const emit = getOrCreateEmitNode(original); + emit.leadingComments = undefined; + emit.trailingComments = undefined; + return node; +} + +/** + * Gets the constant value to emit for an expression representing an enum. + */ +export function getConstantValue(node: AccessExpression): string | number | undefined { + return node.emitNode?.constantValue; +} + +/** + * Sets the constant value to emit for an expression. + */ +export function setConstantValue(node: AccessExpression, value: string | number): AccessExpression { + const emitNode = getOrCreateEmitNode(node); + emitNode.constantValue = value; + return node; +} + +/** + * Adds an EmitHelper to a node. + */ +export function addEmitHelper(node: T, helper: EmitHelper): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.helpers = append(emitNode.helpers, helper); + return node; +} + +/** + * Add EmitHelpers to a node. + */ +export function addEmitHelpers(node: T, helpers: EmitHelper[] | undefined): T { + if (some(helpers)) { + const emitNode = getOrCreateEmitNode(node); + for (const helper of helpers) { + emitNode.helpers = appendIfUnique(emitNode.helpers, helper); } - return false; } - - /** - * Gets the EmitHelpers of a node. - */ - export function getEmitHelpers(node: Node): EmitHelper[] | undefined { - return node.emitNode?.helpers; + return node; +} + +/** + * Removes an EmitHelper from a node. + */ +export function removeEmitHelper(node: Node, helper: EmitHelper): boolean { + const helpers = node.emitNode?.helpers; + if (helpers) { + return orderedRemoveItem(helpers, helper); } - - /** - * Moves matching emit helpers from a source node to a target node. - */ - export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) { - const sourceEmitNode = source.emitNode; - const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers; - if (!some(sourceEmitHelpers)) return; - - const targetEmitNode = getOrCreateEmitNode(target); - let helpersRemoved = 0; - for (let i = 0; i < sourceEmitHelpers.length; i++) { - const helper = sourceEmitHelpers[i]; - if (predicate(helper)) { - helpersRemoved++; - targetEmitNode.helpers = appendIfUnique(targetEmitNode.helpers, helper); - } - else if (helpersRemoved > 0) { - sourceEmitHelpers[i - helpersRemoved] = helper; - } + return false; +} + +/** + * Gets the EmitHelpers of a node. + */ +export function getEmitHelpers(node: Node): EmitHelper[] | undefined { + return node.emitNode?.helpers; +} + +/** + * Moves matching emit helpers from a source node to a target node. + */ +export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) { + const sourceEmitNode = source.emitNode; + const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers; + if (!some(sourceEmitHelpers)) return; + + const targetEmitNode = getOrCreateEmitNode(target); + let helpersRemoved = 0; + for (let i = 0; i < sourceEmitHelpers.length; i++) { + const helper = sourceEmitHelpers[i]; + if (predicate(helper)) { + helpersRemoved++; + targetEmitNode.helpers = appendIfUnique(targetEmitNode.helpers, helper); } - - if (helpersRemoved > 0) { - sourceEmitHelpers.length -= helpersRemoved; + else if (helpersRemoved > 0) { + sourceEmitHelpers[i - helpersRemoved] = helper; } } - /** - * Gets the SnippetElement of a node. - */ - /* @internal */ - export function getSnippetElement(node: Node): SnippetElement | undefined { - return node.emitNode?.snippetElement; - } - - /** - * Sets the SnippetElement of a node. - */ - /* @internal */ - export function setSnippetElement(node: T, snippet: SnippetElement): T { - const emitNode = getOrCreateEmitNode(node); - emitNode.snippetElement = snippet; - return node; - } - - /* @internal */ - export function ignoreSourceNewlines(node: T): T { - getOrCreateEmitNode(node).flags |= EmitFlags.IgnoreSourceNewlines; - return node; + if (helpersRemoved > 0) { + sourceEmitHelpers.length -= helpersRemoved; } -} \ No newline at end of file +} + +/** + * Gets the SnippetElement of a node. + */ +/* @internal */ +export function getSnippetElement(node: Node): SnippetElement | undefined { + return node.emitNode?.snippetElement; +} + +/** + * Sets the SnippetElement of a node. + */ +/* @internal */ +export function setSnippetElement(node: T, snippet: SnippetElement): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.snippetElement = snippet; + return node; +} + +/* @internal */ +export function ignoreSourceNewlines(node: T): T { + getOrCreateEmitNode(node).flags |= EmitFlags.IgnoreSourceNewlines; + return node; +} +} diff --git a/src/compiler/factory/nodeConverters.ts b/src/compiler/factory/nodeConverters.ts index 08aff2a91cd87..172be5351b2aa 100644 --- a/src/compiler/factory/nodeConverters.ts +++ b/src/compiler/factory/nodeConverters.ts @@ -1,137 +1,137 @@ /* @internal */ namespace ts { - export function createNodeConverters(factory: NodeFactory): NodeConverters { - return { - convertToFunctionBlock, - convertToFunctionExpression, - convertToArrayAssignmentElement, - convertToObjectAssignmentElement, - convertToAssignmentPattern, - convertToObjectAssignmentPattern, - convertToArrayAssignmentPattern, - convertToAssignmentElementTarget, - }; +export function createNodeConverters(factory: NodeFactory): NodeConverters { + return { + convertToFunctionBlock, + convertToFunctionExpression, + convertToArrayAssignmentElement, + convertToObjectAssignmentElement, + convertToAssignmentPattern, + convertToObjectAssignmentPattern, + convertToArrayAssignmentPattern, + convertToAssignmentElementTarget, + }; - function convertToFunctionBlock(node: ConciseBody, multiLine?: boolean): Block { - if (isBlock(node)) return node; - const returnStatement = factory.createReturnStatement(node); - setTextRange(returnStatement, node); - const body = factory.createBlock([returnStatement], multiLine); - setTextRange(body, node); - return body; - } + function convertToFunctionBlock(node: ConciseBody, multiLine?: boolean): Block { + if (isBlock(node)) return node; + const returnStatement = factory.createReturnStatement(node); + setTextRange(returnStatement, node); + const body = factory.createBlock([returnStatement], multiLine); + setTextRange(body, node); + return body; + } - function convertToFunctionExpression(node: FunctionDeclaration) { - if (!node.body) return Debug.fail(`Cannot convert a FunctionDeclaration without a body`); - const updated = factory.createFunctionExpression( - node.modifiers, - node.asteriskToken, - node.name, - node.typeParameters, - node.parameters, - node.type, - node.body - ); - setOriginalNode(updated, node); - setTextRange(updated, node); - if (getStartsOnNewLine(node)) { - setStartsOnNewLine(updated, /*newLine*/ true); - } - return updated; + function convertToFunctionExpression(node: FunctionDeclaration) { + if (!node.body) return Debug.fail(`Cannot convert a FunctionDeclaration without a body`); + const updated = factory.createFunctionExpression( + node.modifiers, + node.asteriskToken, + node.name, + node.typeParameters, + node.parameters, + node.type, + node.body + ); + setOriginalNode(updated, node); + setTextRange(updated, node); + if (getStartsOnNewLine(node)) { + setStartsOnNewLine(updated, /*newLine*/ true); } + return updated; + } - function convertToArrayAssignmentElement(element: ArrayBindingOrAssignmentElement) { - if (isBindingElement(element)) { - if (element.dotDotDotToken) { - Debug.assertNode(element.name, isIdentifier); - return setOriginalNode(setTextRange(factory.createSpreadElement(element.name), element), element); - } - const expression = convertToAssignmentElementTarget(element.name); - return element.initializer - ? setOriginalNode( - setTextRange( - factory.createAssignment(expression, element.initializer), - element - ), - element - ) - : expression; + function convertToArrayAssignmentElement(element: ArrayBindingOrAssignmentElement) { + if (isBindingElement(element)) { + if (element.dotDotDotToken) { + Debug.assertNode(element.name, isIdentifier); + return setOriginalNode(setTextRange(factory.createSpreadElement(element.name), element), element); } - return cast(element, isExpression); + const expression = convertToAssignmentElementTarget(element.name); + return element.initializer + ? setOriginalNode( + setTextRange( + factory.createAssignment(expression, element.initializer), + element + ), + element + ) + : expression; } + return cast(element, isExpression); + } - function convertToObjectAssignmentElement(element: ObjectBindingOrAssignmentElement) { - if (isBindingElement(element)) { - if (element.dotDotDotToken) { - Debug.assertNode(element.name, isIdentifier); - return setOriginalNode(setTextRange(factory.createSpreadAssignment(element.name), element), element); - } - if (element.propertyName) { - const expression = convertToAssignmentElementTarget(element.name); - return setOriginalNode(setTextRange(factory.createPropertyAssignment(element.propertyName, element.initializer ? factory.createAssignment(expression, element.initializer) : expression), element), element); - } + function convertToObjectAssignmentElement(element: ObjectBindingOrAssignmentElement) { + if (isBindingElement(element)) { + if (element.dotDotDotToken) { Debug.assertNode(element.name, isIdentifier); - return setOriginalNode(setTextRange(factory.createShorthandPropertyAssignment(element.name, element.initializer), element), element); + return setOriginalNode(setTextRange(factory.createSpreadAssignment(element.name), element), element); } - - return cast(element, isObjectLiteralElementLike); + if (element.propertyName) { + const expression = convertToAssignmentElementTarget(element.name); + return setOriginalNode(setTextRange(factory.createPropertyAssignment(element.propertyName, element.initializer ? factory.createAssignment(expression, element.initializer) : expression), element), element); + } + Debug.assertNode(element.name, isIdentifier); + return setOriginalNode(setTextRange(factory.createShorthandPropertyAssignment(element.name, element.initializer), element), element); } - function convertToAssignmentPattern(node: BindingOrAssignmentPattern): AssignmentPattern { - switch (node.kind) { - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ArrayLiteralExpression: - return convertToArrayAssignmentPattern(node); + return cast(element, isObjectLiteralElementLike); + } + + function convertToAssignmentPattern(node: BindingOrAssignmentPattern): AssignmentPattern { + switch (node.kind) { + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ArrayLiteralExpression: + return convertToArrayAssignmentPattern(node); - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ObjectLiteralExpression: - return convertToObjectAssignmentPattern(node); - } + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ObjectLiteralExpression: + return convertToObjectAssignmentPattern(node); } + } - function convertToObjectAssignmentPattern(node: ObjectBindingOrAssignmentPattern) { - if (isObjectBindingPattern(node)) { - return setOriginalNode( - setTextRange( - factory.createObjectLiteralExpression(map(node.elements, convertToObjectAssignmentElement)), - node - ), + function convertToObjectAssignmentPattern(node: ObjectBindingOrAssignmentPattern) { + if (isObjectBindingPattern(node)) { + return setOriginalNode( + setTextRange( + factory.createObjectLiteralExpression(map(node.elements, convertToObjectAssignmentElement)), node - ); - } - return cast(node, isObjectLiteralExpression); + ), + node + ); } + return cast(node, isObjectLiteralExpression); + } - function convertToArrayAssignmentPattern(node: ArrayBindingOrAssignmentPattern) { - if (isArrayBindingPattern(node)) { - return setOriginalNode( - setTextRange( - factory.createArrayLiteralExpression(map(node.elements, convertToArrayAssignmentElement)), - node - ), + function convertToArrayAssignmentPattern(node: ArrayBindingOrAssignmentPattern) { + if (isArrayBindingPattern(node)) { + return setOriginalNode( + setTextRange( + factory.createArrayLiteralExpression(map(node.elements, convertToArrayAssignmentElement)), node - ); - } - return cast(node, isArrayLiteralExpression); + ), + node + ); } + return cast(node, isArrayLiteralExpression); + } - function convertToAssignmentElementTarget(node: BindingOrAssignmentElementTarget): Expression { - if (isBindingPattern(node)) { - return convertToAssignmentPattern(node); - } - - return cast(node, isExpression); + function convertToAssignmentElementTarget(node: BindingOrAssignmentElementTarget): Expression { + if (isBindingPattern(node)) { + return convertToAssignmentPattern(node); } + + return cast(node, isExpression); } +} - export const nullNodeConverters: NodeConverters = { - convertToFunctionBlock: notImplemented, - convertToFunctionExpression: notImplemented, - convertToArrayAssignmentElement: notImplemented, - convertToObjectAssignmentElement: notImplemented, - convertToAssignmentPattern: notImplemented, - convertToObjectAssignmentPattern: notImplemented, - convertToArrayAssignmentPattern: notImplemented, - convertToAssignmentElementTarget: notImplemented, - }; -} \ No newline at end of file +export const nullNodeConverters: NodeConverters = { + convertToFunctionBlock: notImplemented, + convertToFunctionExpression: notImplemented, + convertToArrayAssignmentElement: notImplemented, + convertToObjectAssignmentElement: notImplemented, + convertToAssignmentPattern: notImplemented, + convertToObjectAssignmentPattern: notImplemented, + convertToArrayAssignmentPattern: notImplemented, + convertToAssignmentElementTarget: notImplemented, +}; +} diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index cbe3aa339e12a..730aa5d668e7a 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -1,6675 +1,6675 @@ namespace ts { - let nextAutoGenerateId = 0; +let nextAutoGenerateId = 0; + +/* @internal */ +export const enum NodeFactoryFlags { + None = 0, + // Disables the parenthesizer rules for the factory. + NoParenthesizerRules = 1 << 0, + // Disables the node converters for the factory. + NoNodeConverters = 1 << 1, + // Ensures new `PropertyAccessExpression` nodes are created with the `NoIndentation` emit flag set. + NoIndentationOnFreshPropertyAccess = 1 << 2, + // Do not set an `original` pointer when updating a node. + NoOriginalNode = 1 << 3, +} - /* @internal */ - export const enum NodeFactoryFlags { - None = 0, - // Disables the parenthesizer rules for the factory. - NoParenthesizerRules = 1 << 0, - // Disables the node converters for the factory. - NoNodeConverters = 1 << 1, - // Ensures new `PropertyAccessExpression` nodes are created with the `NoIndentation` emit flag set. - NoIndentationOnFreshPropertyAccess = 1 << 2, - // Do not set an `original` pointer when updating a node. - NoOriginalNode = 1 << 3, - } +/** + * Creates a `NodeFactory` that can be used to create and update a syntax tree. + * @param flags Flags that control factory behavior. + * @param baseFactory A `BaseNodeFactory` used to create the base `Node` objects. + */ +/* @internal */ +export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNodeFactory): NodeFactory { + const update = flags & NodeFactoryFlags.NoOriginalNode ? updateWithoutOriginal : updateWithOriginal; + + // Lazily load the parenthesizer, node converters, and some factory methods until they are used. + const parenthesizerRules = memoize(() => flags & NodeFactoryFlags.NoParenthesizerRules ? nullParenthesizerRules : createParenthesizerRules(factory)); + const converters = memoize(() => flags & NodeFactoryFlags.NoNodeConverters ? nullNodeConverters : createNodeConverters(factory)); + + // lazy initializaton of common operator factories + const getBinaryCreateFunction = memoizeOne((operator: BinaryOperator) => (left: Expression, right: Expression) => createBinaryExpression(left, operator, right)); + const getPrefixUnaryCreateFunction = memoizeOne((operator: PrefixUnaryOperator) => (operand: Expression) => createPrefixUnaryExpression(operator, operand)); + const getPostfixUnaryCreateFunction = memoizeOne((operator: PostfixUnaryOperator) => (operand: Expression) => createPostfixUnaryExpression(operand, operator)); + const getJSDocPrimaryTypeCreateFunction = memoizeOne((kind: T["kind"]) => () => createJSDocPrimaryTypeWorker(kind)); + const getJSDocUnaryTypeCreateFunction = memoizeOne((kind: T["kind"]) => (type: T["type"]) => createJSDocUnaryTypeWorker(kind, type)); + const getJSDocUnaryTypeUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, type: T["type"]) => updateJSDocUnaryTypeWorker(kind, node, type)); + const getJSDocSimpleTagCreateFunction = memoizeOne((kind: T["kind"]) => (tagName: Identifier | undefined, comment?: NodeArray) => createJSDocSimpleTagWorker(kind, tagName, comment)); + const getJSDocSimpleTagUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, tagName: Identifier | undefined, comment?: NodeArray) => updateJSDocSimpleTagWorker(kind, node, tagName, comment)); + const getJSDocTypeLikeTagCreateFunction = memoizeOne((kind: T["kind"]) => (tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: NodeArray) => createJSDocTypeLikeTagWorker(kind, tagName, typeExpression, comment)); + const getJSDocTypeLikeTagUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: NodeArray) => updateJSDocTypeLikeTagWorker(kind, node, tagName, typeExpression, comment)); + + const factory: NodeFactory = { + get parenthesizer() { return parenthesizerRules(); }, + get converters() { return converters(); }, + createNodeArray, + createNumericLiteral, + createBigIntLiteral, + createStringLiteral, + createStringLiteralFromNode, + createRegularExpressionLiteral, + createLiteralLikeNode, + createIdentifier, + updateIdentifier, + createTempVariable, + createLoopVariable, + createUniqueName, + getGeneratedNameForNode, + createPrivateIdentifier, + createToken, + createSuper, + createThis, + createNull, + createTrue, + createFalse, + createModifier, + createModifiersFromModifierFlags, + createQualifiedName, + updateQualifiedName, + createComputedPropertyName, + updateComputedPropertyName, + createTypeParameterDeclaration, + updateTypeParameterDeclaration, + createParameterDeclaration, + updateParameterDeclaration, + createDecorator, + updateDecorator, + createPropertySignature, + updatePropertySignature, + createPropertyDeclaration, + updatePropertyDeclaration, + createMethodSignature, + updateMethodSignature, + createMethodDeclaration, + updateMethodDeclaration, + createConstructorDeclaration, + updateConstructorDeclaration, + createGetAccessorDeclaration, + updateGetAccessorDeclaration, + createSetAccessorDeclaration, + updateSetAccessorDeclaration, + createCallSignature, + updateCallSignature, + createConstructSignature, + updateConstructSignature, + createIndexSignature, + updateIndexSignature, + createClassStaticBlockDeclaration, + updateClassStaticBlockDeclaration, + createTemplateLiteralTypeSpan, + updateTemplateLiteralTypeSpan, + createKeywordTypeNode, + createTypePredicateNode, + updateTypePredicateNode, + createTypeReferenceNode, + updateTypeReferenceNode, + createFunctionTypeNode, + updateFunctionTypeNode, + createConstructorTypeNode, + updateConstructorTypeNode, + createTypeQueryNode, + updateTypeQueryNode, + createTypeLiteralNode, + updateTypeLiteralNode, + createArrayTypeNode, + updateArrayTypeNode, + createTupleTypeNode, + updateTupleTypeNode, + createNamedTupleMember, + updateNamedTupleMember, + createOptionalTypeNode, + updateOptionalTypeNode, + createRestTypeNode, + updateRestTypeNode, + createUnionTypeNode, + updateUnionTypeNode, + createIntersectionTypeNode, + updateIntersectionTypeNode, + createConditionalTypeNode, + updateConditionalTypeNode, + createInferTypeNode, + updateInferTypeNode, + createImportTypeNode, + updateImportTypeNode, + createParenthesizedType, + updateParenthesizedType, + createThisTypeNode, + createTypeOperatorNode, + updateTypeOperatorNode, + createIndexedAccessTypeNode, + updateIndexedAccessTypeNode, + createMappedTypeNode, + updateMappedTypeNode, + createLiteralTypeNode, + updateLiteralTypeNode, + createTemplateLiteralType, + updateTemplateLiteralType, + createObjectBindingPattern, + updateObjectBindingPattern, + createArrayBindingPattern, + updateArrayBindingPattern, + createBindingElement, + updateBindingElement, + createArrayLiteralExpression, + updateArrayLiteralExpression, + createObjectLiteralExpression, + updateObjectLiteralExpression, + createPropertyAccessExpression: flags & NodeFactoryFlags.NoIndentationOnFreshPropertyAccess ? + (expression, name) => setEmitFlags(createPropertyAccessExpression(expression, name), EmitFlags.NoIndentation) : + createPropertyAccessExpression, + updatePropertyAccessExpression, + createPropertyAccessChain: flags & NodeFactoryFlags.NoIndentationOnFreshPropertyAccess ? + (expression, questionDotToken, name) => setEmitFlags(createPropertyAccessChain(expression, questionDotToken, name), EmitFlags.NoIndentation) : + createPropertyAccessChain, + updatePropertyAccessChain, + createElementAccessExpression, + updateElementAccessExpression, + createElementAccessChain, + updateElementAccessChain, + createCallExpression, + updateCallExpression, + createCallChain, + updateCallChain, + createNewExpression, + updateNewExpression, + createTaggedTemplateExpression, + updateTaggedTemplateExpression, + createTypeAssertion, + updateTypeAssertion, + createParenthesizedExpression, + updateParenthesizedExpression, + createFunctionExpression, + updateFunctionExpression, + createArrowFunction, + updateArrowFunction, + createDeleteExpression, + updateDeleteExpression, + createTypeOfExpression, + updateTypeOfExpression, + createVoidExpression, + updateVoidExpression, + createAwaitExpression, + updateAwaitExpression, + createPrefixUnaryExpression, + updatePrefixUnaryExpression, + createPostfixUnaryExpression, + updatePostfixUnaryExpression, + createBinaryExpression, + updateBinaryExpression, + createConditionalExpression, + updateConditionalExpression, + createTemplateExpression, + updateTemplateExpression, + createTemplateHead, + createTemplateMiddle, + createTemplateTail, + createNoSubstitutionTemplateLiteral, + createTemplateLiteralLikeNode, + createYieldExpression, + updateYieldExpression, + createSpreadElement, + updateSpreadElement, + createClassExpression, + updateClassExpression, + createOmittedExpression, + createExpressionWithTypeArguments, + updateExpressionWithTypeArguments, + createAsExpression, + updateAsExpression, + createNonNullExpression, + updateNonNullExpression, + createNonNullChain, + updateNonNullChain, + createMetaProperty, + updateMetaProperty, + createTemplateSpan, + updateTemplateSpan, + createSemicolonClassElement, + createBlock, + updateBlock, + createVariableStatement, + updateVariableStatement, + createEmptyStatement, + createExpressionStatement, + updateExpressionStatement, + createIfStatement, + updateIfStatement, + createDoStatement, + updateDoStatement, + createWhileStatement, + updateWhileStatement, + createForStatement, + updateForStatement, + createForInStatement, + updateForInStatement, + createForOfStatement, + updateForOfStatement, + createContinueStatement, + updateContinueStatement, + createBreakStatement, + updateBreakStatement, + createReturnStatement, + updateReturnStatement, + createWithStatement, + updateWithStatement, + createSwitchStatement, + updateSwitchStatement, + createLabeledStatement, + updateLabeledStatement, + createThrowStatement, + updateThrowStatement, + createTryStatement, + updateTryStatement, + createDebuggerStatement, + createVariableDeclaration, + updateVariableDeclaration, + createVariableDeclarationList, + updateVariableDeclarationList, + createFunctionDeclaration, + updateFunctionDeclaration, + createClassDeclaration, + updateClassDeclaration, + createInterfaceDeclaration, + updateInterfaceDeclaration, + createTypeAliasDeclaration, + updateTypeAliasDeclaration, + createEnumDeclaration, + updateEnumDeclaration, + createModuleDeclaration, + updateModuleDeclaration, + createModuleBlock, + updateModuleBlock, + createCaseBlock, + updateCaseBlock, + createNamespaceExportDeclaration, + updateNamespaceExportDeclaration, + createImportEqualsDeclaration, + updateImportEqualsDeclaration, + createImportDeclaration, + updateImportDeclaration, + createImportClause, + updateImportClause, + createAssertClause, + updateAssertClause, + createAssertEntry, + updateAssertEntry, + createNamespaceImport, + updateNamespaceImport, + createNamespaceExport, + updateNamespaceExport, + createNamedImports, + updateNamedImports, + createImportSpecifier, + updateImportSpecifier, + createExportAssignment, + updateExportAssignment, + createExportDeclaration, + updateExportDeclaration, + createNamedExports, + updateNamedExports, + createExportSpecifier, + updateExportSpecifier, + createMissingDeclaration, + createExternalModuleReference, + updateExternalModuleReference, + // lazily load factory members for JSDoc types with similar structure + get createJSDocAllType() { return getJSDocPrimaryTypeCreateFunction(SyntaxKind.JSDocAllType); }, + get createJSDocUnknownType() { return getJSDocPrimaryTypeCreateFunction(SyntaxKind.JSDocUnknownType); }, + get createJSDocNonNullableType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocNonNullableType); }, + get updateJSDocNonNullableType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocNonNullableType); }, + get createJSDocNullableType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocNullableType); }, + get updateJSDocNullableType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocNullableType); }, + get createJSDocOptionalType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocOptionalType); }, + get updateJSDocOptionalType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocOptionalType); }, + get createJSDocVariadicType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocVariadicType); }, + get updateJSDocVariadicType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocVariadicType); }, + get createJSDocNamepathType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocNamepathType); }, + get updateJSDocNamepathType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocNamepathType); }, + createJSDocFunctionType, + updateJSDocFunctionType, + createJSDocTypeLiteral, + updateJSDocTypeLiteral, + createJSDocTypeExpression, + updateJSDocTypeExpression, + createJSDocSignature, + updateJSDocSignature, + createJSDocTemplateTag, + updateJSDocTemplateTag, + createJSDocTypedefTag, + updateJSDocTypedefTag, + createJSDocParameterTag, + updateJSDocParameterTag, + createJSDocPropertyTag, + updateJSDocPropertyTag, + createJSDocCallbackTag, + updateJSDocCallbackTag, + createJSDocAugmentsTag, + updateJSDocAugmentsTag, + createJSDocImplementsTag, + updateJSDocImplementsTag, + createJSDocSeeTag, + updateJSDocSeeTag, + createJSDocNameReference, + updateJSDocNameReference, + createJSDocMemberName, + updateJSDocMemberName, + createJSDocLink, + updateJSDocLink, + createJSDocLinkCode, + updateJSDocLinkCode, + createJSDocLinkPlain, + updateJSDocLinkPlain, + // lazily load factory members for JSDoc tags with similar structure + get createJSDocTypeTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocTypeTag); }, + get updateJSDocTypeTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocTypeTag); }, + get createJSDocReturnTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocReturnTag); }, + get updateJSDocReturnTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocReturnTag); }, + get createJSDocThisTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocThisTag); }, + get updateJSDocThisTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocThisTag); }, + get createJSDocEnumTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocEnumTag); }, + get updateJSDocEnumTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocEnumTag); }, + get createJSDocAuthorTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocAuthorTag); }, + get updateJSDocAuthorTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocAuthorTag); }, + get createJSDocClassTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocClassTag); }, + get updateJSDocClassTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocClassTag); }, + get createJSDocPublicTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocPublicTag); }, + get updateJSDocPublicTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocPublicTag); }, + get createJSDocPrivateTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocPrivateTag); }, + get updateJSDocPrivateTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocPrivateTag); }, + get createJSDocProtectedTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocProtectedTag); }, + get updateJSDocProtectedTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocProtectedTag); }, + get createJSDocReadonlyTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocReadonlyTag); }, + get updateJSDocReadonlyTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocReadonlyTag); }, + get createJSDocOverrideTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocOverrideTag); }, + get updateJSDocOverrideTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocOverrideTag); }, + get createJSDocDeprecatedTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocDeprecatedTag); }, + get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocDeprecatedTag); }, + createJSDocUnknownTag, + updateJSDocUnknownTag, + createJSDocText, + updateJSDocText, + createJSDocComment, + updateJSDocComment, + createJsxElement, + updateJsxElement, + createJsxSelfClosingElement, + updateJsxSelfClosingElement, + createJsxOpeningElement, + updateJsxOpeningElement, + createJsxClosingElement, + updateJsxClosingElement, + createJsxFragment, + createJsxText, + updateJsxText, + createJsxOpeningFragment, + createJsxJsxClosingFragment, + updateJsxFragment, + createJsxAttribute, + updateJsxAttribute, + createJsxAttributes, + updateJsxAttributes, + createJsxSpreadAttribute, + updateJsxSpreadAttribute, + createJsxExpression, + updateJsxExpression, + createCaseClause, + updateCaseClause, + createDefaultClause, + updateDefaultClause, + createHeritageClause, + updateHeritageClause, + createCatchClause, + updateCatchClause, + createPropertyAssignment, + updatePropertyAssignment, + createShorthandPropertyAssignment, + updateShorthandPropertyAssignment, + createSpreadAssignment, + updateSpreadAssignment, + createEnumMember, + updateEnumMember, + createSourceFile, + updateSourceFile, + createBundle, + updateBundle, + createUnparsedSource, + createUnparsedPrologue, + createUnparsedPrepend, + createUnparsedTextLike, + createUnparsedSyntheticReference, + createInputFiles, + createSyntheticExpression, + createSyntaxList, + createNotEmittedStatement, + createPartiallyEmittedExpression, + updatePartiallyEmittedExpression, + createCommaListExpression, + updateCommaListExpression, + createEndOfDeclarationMarker, + createMergeDeclarationMarker, + createSyntheticReferenceExpression, + updateSyntheticReferenceExpression, + cloneNode, + + // Lazily load factory methods for common operator factories and utilities + get createComma() { return getBinaryCreateFunction(SyntaxKind.CommaToken); }, + get createAssignment() { return getBinaryCreateFunction(SyntaxKind.EqualsToken) as NodeFactory["createAssignment"]; }, + get createLogicalOr() { return getBinaryCreateFunction(SyntaxKind.BarBarToken); }, + get createLogicalAnd() { return getBinaryCreateFunction(SyntaxKind.AmpersandAmpersandToken); }, + get createBitwiseOr() { return getBinaryCreateFunction(SyntaxKind.BarToken); }, + get createBitwiseXor() { return getBinaryCreateFunction(SyntaxKind.CaretToken); }, + get createBitwiseAnd() { return getBinaryCreateFunction(SyntaxKind.AmpersandToken); }, + get createStrictEquality() { return getBinaryCreateFunction(SyntaxKind.EqualsEqualsEqualsToken); }, + get createStrictInequality() { return getBinaryCreateFunction(SyntaxKind.ExclamationEqualsEqualsToken); }, + get createEquality() { return getBinaryCreateFunction(SyntaxKind.EqualsEqualsToken); }, + get createInequality() { return getBinaryCreateFunction(SyntaxKind.ExclamationEqualsToken); }, + get createLessThan() { return getBinaryCreateFunction(SyntaxKind.LessThanToken); }, + get createLessThanEquals() { return getBinaryCreateFunction(SyntaxKind.LessThanEqualsToken); }, + get createGreaterThan() { return getBinaryCreateFunction(SyntaxKind.GreaterThanToken); }, + get createGreaterThanEquals() { return getBinaryCreateFunction(SyntaxKind.GreaterThanEqualsToken); }, + get createLeftShift() { return getBinaryCreateFunction(SyntaxKind.LessThanLessThanToken); }, + get createRightShift() { return getBinaryCreateFunction(SyntaxKind.GreaterThanGreaterThanToken); }, + get createUnsignedRightShift() { return getBinaryCreateFunction(SyntaxKind.GreaterThanGreaterThanGreaterThanToken); }, + get createAdd() { return getBinaryCreateFunction(SyntaxKind.PlusToken); }, + get createSubtract() { return getBinaryCreateFunction(SyntaxKind.MinusToken); }, + get createMultiply() { return getBinaryCreateFunction(SyntaxKind.AsteriskToken); }, + get createDivide() { return getBinaryCreateFunction(SyntaxKind.SlashToken); }, + get createModulo() { return getBinaryCreateFunction(SyntaxKind.PercentToken); }, + get createExponent() { return getBinaryCreateFunction(SyntaxKind.AsteriskAsteriskToken); }, + get createPrefixPlus() { return getPrefixUnaryCreateFunction(SyntaxKind.PlusToken); }, + get createPrefixMinus() { return getPrefixUnaryCreateFunction(SyntaxKind.MinusToken); }, + get createPrefixIncrement() { return getPrefixUnaryCreateFunction(SyntaxKind.PlusPlusToken); }, + get createPrefixDecrement() { return getPrefixUnaryCreateFunction(SyntaxKind.MinusMinusToken); }, + get createBitwiseNot() { return getPrefixUnaryCreateFunction(SyntaxKind.TildeToken); }, + get createLogicalNot() { return getPrefixUnaryCreateFunction(SyntaxKind.ExclamationToken); }, + get createPostfixIncrement() { return getPostfixUnaryCreateFunction(SyntaxKind.PlusPlusToken); }, + get createPostfixDecrement() { return getPostfixUnaryCreateFunction(SyntaxKind.MinusMinusToken); }, + + // Compound nodes + createImmediatelyInvokedFunctionExpression, + createImmediatelyInvokedArrowFunction, + createVoidZero, + createExportDefault, + createExternalModuleExport, + createTypeCheck, + createMethodCall, + createGlobalMethodCall, + createFunctionBindCall, + createFunctionCallCall, + createFunctionApplyCall, + createArraySliceCall, + createArrayConcatCall, + createObjectDefinePropertyCall, + createReflectGetCall, + createReflectSetCall, + createPropertyDescriptor, + createCallBinding, + createAssignmentTargetWrapper, - /** - * Creates a `NodeFactory` that can be used to create and update a syntax tree. - * @param flags Flags that control factory behavior. - * @param baseFactory A `BaseNodeFactory` used to create the base `Node` objects. - */ - /* @internal */ - export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNodeFactory): NodeFactory { - const update = flags & NodeFactoryFlags.NoOriginalNode ? updateWithoutOriginal : updateWithOriginal; - - // Lazily load the parenthesizer, node converters, and some factory methods until they are used. - const parenthesizerRules = memoize(() => flags & NodeFactoryFlags.NoParenthesizerRules ? nullParenthesizerRules : createParenthesizerRules(factory)); - const converters = memoize(() => flags & NodeFactoryFlags.NoNodeConverters ? nullNodeConverters : createNodeConverters(factory)); - - // lazy initializaton of common operator factories - const getBinaryCreateFunction = memoizeOne((operator: BinaryOperator) => (left: Expression, right: Expression) => createBinaryExpression(left, operator, right)); - const getPrefixUnaryCreateFunction = memoizeOne((operator: PrefixUnaryOperator) => (operand: Expression) => createPrefixUnaryExpression(operator, operand)); - const getPostfixUnaryCreateFunction = memoizeOne((operator: PostfixUnaryOperator) => (operand: Expression) => createPostfixUnaryExpression(operand, operator)); - const getJSDocPrimaryTypeCreateFunction = memoizeOne((kind: T["kind"]) => () => createJSDocPrimaryTypeWorker(kind)); - const getJSDocUnaryTypeCreateFunction = memoizeOne((kind: T["kind"]) => (type: T["type"]) => createJSDocUnaryTypeWorker(kind, type)); - const getJSDocUnaryTypeUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, type: T["type"]) => updateJSDocUnaryTypeWorker(kind, node, type)); - const getJSDocSimpleTagCreateFunction = memoizeOne((kind: T["kind"]) => (tagName: Identifier | undefined, comment?: NodeArray) => createJSDocSimpleTagWorker(kind, tagName, comment)); - const getJSDocSimpleTagUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, tagName: Identifier | undefined, comment?: NodeArray) => updateJSDocSimpleTagWorker(kind, node, tagName, comment)); - const getJSDocTypeLikeTagCreateFunction = memoizeOne((kind: T["kind"]) => (tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: NodeArray) => createJSDocTypeLikeTagWorker(kind, tagName, typeExpression, comment)); - const getJSDocTypeLikeTagUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: NodeArray) => updateJSDocTypeLikeTagWorker(kind, node, tagName, typeExpression, comment)); - - const factory: NodeFactory = { - get parenthesizer() { return parenthesizerRules(); }, - get converters() { return converters(); }, - createNodeArray, - createNumericLiteral, - createBigIntLiteral, - createStringLiteral, - createStringLiteralFromNode, - createRegularExpressionLiteral, - createLiteralLikeNode, - createIdentifier, - updateIdentifier, - createTempVariable, - createLoopVariable, - createUniqueName, - getGeneratedNameForNode, - createPrivateIdentifier, - createToken, - createSuper, - createThis, - createNull, - createTrue, - createFalse, - createModifier, - createModifiersFromModifierFlags, - createQualifiedName, - updateQualifiedName, - createComputedPropertyName, - updateComputedPropertyName, - createTypeParameterDeclaration, - updateTypeParameterDeclaration, - createParameterDeclaration, - updateParameterDeclaration, - createDecorator, - updateDecorator, - createPropertySignature, - updatePropertySignature, - createPropertyDeclaration, - updatePropertyDeclaration, - createMethodSignature, - updateMethodSignature, - createMethodDeclaration, - updateMethodDeclaration, - createConstructorDeclaration, - updateConstructorDeclaration, - createGetAccessorDeclaration, - updateGetAccessorDeclaration, - createSetAccessorDeclaration, - updateSetAccessorDeclaration, - createCallSignature, - updateCallSignature, - createConstructSignature, - updateConstructSignature, - createIndexSignature, - updateIndexSignature, - createClassStaticBlockDeclaration, - updateClassStaticBlockDeclaration, - createTemplateLiteralTypeSpan, - updateTemplateLiteralTypeSpan, - createKeywordTypeNode, - createTypePredicateNode, - updateTypePredicateNode, - createTypeReferenceNode, - updateTypeReferenceNode, - createFunctionTypeNode, - updateFunctionTypeNode, - createConstructorTypeNode, - updateConstructorTypeNode, - createTypeQueryNode, - updateTypeQueryNode, - createTypeLiteralNode, - updateTypeLiteralNode, - createArrayTypeNode, - updateArrayTypeNode, - createTupleTypeNode, - updateTupleTypeNode, - createNamedTupleMember, - updateNamedTupleMember, - createOptionalTypeNode, - updateOptionalTypeNode, - createRestTypeNode, - updateRestTypeNode, - createUnionTypeNode, - updateUnionTypeNode, - createIntersectionTypeNode, - updateIntersectionTypeNode, - createConditionalTypeNode, - updateConditionalTypeNode, - createInferTypeNode, - updateInferTypeNode, - createImportTypeNode, - updateImportTypeNode, - createParenthesizedType, - updateParenthesizedType, - createThisTypeNode, - createTypeOperatorNode, - updateTypeOperatorNode, - createIndexedAccessTypeNode, - updateIndexedAccessTypeNode, - createMappedTypeNode, - updateMappedTypeNode, - createLiteralTypeNode, - updateLiteralTypeNode, - createTemplateLiteralType, - updateTemplateLiteralType, - createObjectBindingPattern, - updateObjectBindingPattern, - createArrayBindingPattern, - updateArrayBindingPattern, - createBindingElement, - updateBindingElement, - createArrayLiteralExpression, - updateArrayLiteralExpression, - createObjectLiteralExpression, - updateObjectLiteralExpression, - createPropertyAccessExpression: flags & NodeFactoryFlags.NoIndentationOnFreshPropertyAccess ? - (expression, name) => setEmitFlags(createPropertyAccessExpression(expression, name), EmitFlags.NoIndentation) : - createPropertyAccessExpression, - updatePropertyAccessExpression, - createPropertyAccessChain: flags & NodeFactoryFlags.NoIndentationOnFreshPropertyAccess ? - (expression, questionDotToken, name) => setEmitFlags(createPropertyAccessChain(expression, questionDotToken, name), EmitFlags.NoIndentation) : - createPropertyAccessChain, - updatePropertyAccessChain, - createElementAccessExpression, - updateElementAccessExpression, - createElementAccessChain, - updateElementAccessChain, - createCallExpression, - updateCallExpression, - createCallChain, - updateCallChain, - createNewExpression, - updateNewExpression, - createTaggedTemplateExpression, - updateTaggedTemplateExpression, - createTypeAssertion, - updateTypeAssertion, - createParenthesizedExpression, - updateParenthesizedExpression, - createFunctionExpression, - updateFunctionExpression, - createArrowFunction, - updateArrowFunction, - createDeleteExpression, - updateDeleteExpression, - createTypeOfExpression, - updateTypeOfExpression, - createVoidExpression, - updateVoidExpression, - createAwaitExpression, - updateAwaitExpression, - createPrefixUnaryExpression, - updatePrefixUnaryExpression, - createPostfixUnaryExpression, - updatePostfixUnaryExpression, - createBinaryExpression, - updateBinaryExpression, - createConditionalExpression, - updateConditionalExpression, - createTemplateExpression, - updateTemplateExpression, - createTemplateHead, - createTemplateMiddle, - createTemplateTail, - createNoSubstitutionTemplateLiteral, - createTemplateLiteralLikeNode, - createYieldExpression, - updateYieldExpression, - createSpreadElement, - updateSpreadElement, - createClassExpression, - updateClassExpression, - createOmittedExpression, - createExpressionWithTypeArguments, - updateExpressionWithTypeArguments, - createAsExpression, - updateAsExpression, - createNonNullExpression, - updateNonNullExpression, - createNonNullChain, - updateNonNullChain, - createMetaProperty, - updateMetaProperty, - createTemplateSpan, - updateTemplateSpan, - createSemicolonClassElement, - createBlock, - updateBlock, - createVariableStatement, - updateVariableStatement, - createEmptyStatement, - createExpressionStatement, - updateExpressionStatement, - createIfStatement, - updateIfStatement, - createDoStatement, - updateDoStatement, - createWhileStatement, - updateWhileStatement, - createForStatement, - updateForStatement, - createForInStatement, - updateForInStatement, - createForOfStatement, - updateForOfStatement, - createContinueStatement, - updateContinueStatement, - createBreakStatement, - updateBreakStatement, - createReturnStatement, - updateReturnStatement, - createWithStatement, - updateWithStatement, - createSwitchStatement, - updateSwitchStatement, - createLabeledStatement, - updateLabeledStatement, - createThrowStatement, - updateThrowStatement, - createTryStatement, - updateTryStatement, - createDebuggerStatement, - createVariableDeclaration, - updateVariableDeclaration, - createVariableDeclarationList, - updateVariableDeclarationList, - createFunctionDeclaration, - updateFunctionDeclaration, - createClassDeclaration, - updateClassDeclaration, - createInterfaceDeclaration, - updateInterfaceDeclaration, - createTypeAliasDeclaration, - updateTypeAliasDeclaration, - createEnumDeclaration, - updateEnumDeclaration, - createModuleDeclaration, - updateModuleDeclaration, - createModuleBlock, - updateModuleBlock, - createCaseBlock, - updateCaseBlock, - createNamespaceExportDeclaration, - updateNamespaceExportDeclaration, - createImportEqualsDeclaration, - updateImportEqualsDeclaration, - createImportDeclaration, - updateImportDeclaration, - createImportClause, - updateImportClause, - createAssertClause, - updateAssertClause, - createAssertEntry, - updateAssertEntry, - createNamespaceImport, - updateNamespaceImport, - createNamespaceExport, - updateNamespaceExport, - createNamedImports, - updateNamedImports, - createImportSpecifier, - updateImportSpecifier, - createExportAssignment, - updateExportAssignment, - createExportDeclaration, - updateExportDeclaration, - createNamedExports, - updateNamedExports, - createExportSpecifier, - updateExportSpecifier, - createMissingDeclaration, - createExternalModuleReference, - updateExternalModuleReference, - // lazily load factory members for JSDoc types with similar structure - get createJSDocAllType() { return getJSDocPrimaryTypeCreateFunction(SyntaxKind.JSDocAllType); }, - get createJSDocUnknownType() { return getJSDocPrimaryTypeCreateFunction(SyntaxKind.JSDocUnknownType); }, - get createJSDocNonNullableType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocNonNullableType); }, - get updateJSDocNonNullableType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocNonNullableType); }, - get createJSDocNullableType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocNullableType); }, - get updateJSDocNullableType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocNullableType); }, - get createJSDocOptionalType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocOptionalType); }, - get updateJSDocOptionalType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocOptionalType); }, - get createJSDocVariadicType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocVariadicType); }, - get updateJSDocVariadicType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocVariadicType); }, - get createJSDocNamepathType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocNamepathType); }, - get updateJSDocNamepathType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocNamepathType); }, - createJSDocFunctionType, - updateJSDocFunctionType, - createJSDocTypeLiteral, - updateJSDocTypeLiteral, - createJSDocTypeExpression, - updateJSDocTypeExpression, - createJSDocSignature, - updateJSDocSignature, - createJSDocTemplateTag, - updateJSDocTemplateTag, - createJSDocTypedefTag, - updateJSDocTypedefTag, - createJSDocParameterTag, - updateJSDocParameterTag, - createJSDocPropertyTag, - updateJSDocPropertyTag, - createJSDocCallbackTag, - updateJSDocCallbackTag, - createJSDocAugmentsTag, - updateJSDocAugmentsTag, - createJSDocImplementsTag, - updateJSDocImplementsTag, - createJSDocSeeTag, - updateJSDocSeeTag, - createJSDocNameReference, - updateJSDocNameReference, - createJSDocMemberName, - updateJSDocMemberName, - createJSDocLink, - updateJSDocLink, - createJSDocLinkCode, - updateJSDocLinkCode, - createJSDocLinkPlain, - updateJSDocLinkPlain, - // lazily load factory members for JSDoc tags with similar structure - get createJSDocTypeTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocTypeTag); }, - get updateJSDocTypeTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocTypeTag); }, - get createJSDocReturnTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocReturnTag); }, - get updateJSDocReturnTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocReturnTag); }, - get createJSDocThisTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocThisTag); }, - get updateJSDocThisTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocThisTag); }, - get createJSDocEnumTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocEnumTag); }, - get updateJSDocEnumTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocEnumTag); }, - get createJSDocAuthorTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocAuthorTag); }, - get updateJSDocAuthorTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocAuthorTag); }, - get createJSDocClassTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocClassTag); }, - get updateJSDocClassTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocClassTag); }, - get createJSDocPublicTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocPublicTag); }, - get updateJSDocPublicTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocPublicTag); }, - get createJSDocPrivateTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocPrivateTag); }, - get updateJSDocPrivateTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocPrivateTag); }, - get createJSDocProtectedTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocProtectedTag); }, - get updateJSDocProtectedTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocProtectedTag); }, - get createJSDocReadonlyTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocReadonlyTag); }, - get updateJSDocReadonlyTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocReadonlyTag); }, - get createJSDocOverrideTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocOverrideTag); }, - get updateJSDocOverrideTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocOverrideTag); }, - get createJSDocDeprecatedTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocDeprecatedTag); }, - get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocDeprecatedTag); }, - createJSDocUnknownTag, - updateJSDocUnknownTag, - createJSDocText, - updateJSDocText, - createJSDocComment, - updateJSDocComment, - createJsxElement, - updateJsxElement, - createJsxSelfClosingElement, - updateJsxSelfClosingElement, - createJsxOpeningElement, - updateJsxOpeningElement, - createJsxClosingElement, - updateJsxClosingElement, - createJsxFragment, - createJsxText, - updateJsxText, - createJsxOpeningFragment, - createJsxJsxClosingFragment, - updateJsxFragment, - createJsxAttribute, - updateJsxAttribute, - createJsxAttributes, - updateJsxAttributes, - createJsxSpreadAttribute, - updateJsxSpreadAttribute, - createJsxExpression, - updateJsxExpression, - createCaseClause, - updateCaseClause, - createDefaultClause, - updateDefaultClause, - createHeritageClause, - updateHeritageClause, - createCatchClause, - updateCatchClause, - createPropertyAssignment, - updatePropertyAssignment, - createShorthandPropertyAssignment, - updateShorthandPropertyAssignment, - createSpreadAssignment, - updateSpreadAssignment, - createEnumMember, - updateEnumMember, - createSourceFile, - updateSourceFile, - createBundle, - updateBundle, - createUnparsedSource, - createUnparsedPrologue, - createUnparsedPrepend, - createUnparsedTextLike, - createUnparsedSyntheticReference, - createInputFiles, - createSyntheticExpression, - createSyntaxList, - createNotEmittedStatement, - createPartiallyEmittedExpression, - updatePartiallyEmittedExpression, - createCommaListExpression, - updateCommaListExpression, - createEndOfDeclarationMarker, - createMergeDeclarationMarker, - createSyntheticReferenceExpression, - updateSyntheticReferenceExpression, - cloneNode, - - // Lazily load factory methods for common operator factories and utilities - get createComma() { return getBinaryCreateFunction(SyntaxKind.CommaToken); }, - get createAssignment() { return getBinaryCreateFunction(SyntaxKind.EqualsToken) as NodeFactory["createAssignment"]; }, - get createLogicalOr() { return getBinaryCreateFunction(SyntaxKind.BarBarToken); }, - get createLogicalAnd() { return getBinaryCreateFunction(SyntaxKind.AmpersandAmpersandToken); }, - get createBitwiseOr() { return getBinaryCreateFunction(SyntaxKind.BarToken); }, - get createBitwiseXor() { return getBinaryCreateFunction(SyntaxKind.CaretToken); }, - get createBitwiseAnd() { return getBinaryCreateFunction(SyntaxKind.AmpersandToken); }, - get createStrictEquality() { return getBinaryCreateFunction(SyntaxKind.EqualsEqualsEqualsToken); }, - get createStrictInequality() { return getBinaryCreateFunction(SyntaxKind.ExclamationEqualsEqualsToken); }, - get createEquality() { return getBinaryCreateFunction(SyntaxKind.EqualsEqualsToken); }, - get createInequality() { return getBinaryCreateFunction(SyntaxKind.ExclamationEqualsToken); }, - get createLessThan() { return getBinaryCreateFunction(SyntaxKind.LessThanToken); }, - get createLessThanEquals() { return getBinaryCreateFunction(SyntaxKind.LessThanEqualsToken); }, - get createGreaterThan() { return getBinaryCreateFunction(SyntaxKind.GreaterThanToken); }, - get createGreaterThanEquals() { return getBinaryCreateFunction(SyntaxKind.GreaterThanEqualsToken); }, - get createLeftShift() { return getBinaryCreateFunction(SyntaxKind.LessThanLessThanToken); }, - get createRightShift() { return getBinaryCreateFunction(SyntaxKind.GreaterThanGreaterThanToken); }, - get createUnsignedRightShift() { return getBinaryCreateFunction(SyntaxKind.GreaterThanGreaterThanGreaterThanToken); }, - get createAdd() { return getBinaryCreateFunction(SyntaxKind.PlusToken); }, - get createSubtract() { return getBinaryCreateFunction(SyntaxKind.MinusToken); }, - get createMultiply() { return getBinaryCreateFunction(SyntaxKind.AsteriskToken); }, - get createDivide() { return getBinaryCreateFunction(SyntaxKind.SlashToken); }, - get createModulo() { return getBinaryCreateFunction(SyntaxKind.PercentToken); }, - get createExponent() { return getBinaryCreateFunction(SyntaxKind.AsteriskAsteriskToken); }, - get createPrefixPlus() { return getPrefixUnaryCreateFunction(SyntaxKind.PlusToken); }, - get createPrefixMinus() { return getPrefixUnaryCreateFunction(SyntaxKind.MinusToken); }, - get createPrefixIncrement() { return getPrefixUnaryCreateFunction(SyntaxKind.PlusPlusToken); }, - get createPrefixDecrement() { return getPrefixUnaryCreateFunction(SyntaxKind.MinusMinusToken); }, - get createBitwiseNot() { return getPrefixUnaryCreateFunction(SyntaxKind.TildeToken); }, - get createLogicalNot() { return getPrefixUnaryCreateFunction(SyntaxKind.ExclamationToken); }, - get createPostfixIncrement() { return getPostfixUnaryCreateFunction(SyntaxKind.PlusPlusToken); }, - get createPostfixDecrement() { return getPostfixUnaryCreateFunction(SyntaxKind.MinusMinusToken); }, - - // Compound nodes - createImmediatelyInvokedFunctionExpression, - createImmediatelyInvokedArrowFunction, - createVoidZero, - createExportDefault, - createExternalModuleExport, - createTypeCheck, - createMethodCall, - createGlobalMethodCall, - createFunctionBindCall, - createFunctionCallCall, - createFunctionApplyCall, - createArraySliceCall, - createArrayConcatCall, - createObjectDefinePropertyCall, - createReflectGetCall, - createReflectSetCall, - createPropertyDescriptor, - createCallBinding, - createAssignmentTargetWrapper, - - // Utilities - inlineExpressions, - getInternalName, - getLocalName, - getExportName, - getDeclarationName, - getNamespaceMemberName, - getExternalModuleOrNamespaceExportName, - restoreOuterExpressions, - restoreEnclosingLabel, - createUseStrictPrologue, - copyPrologue, - copyStandardPrologue, - copyCustomPrologue, - ensureUseStrict, - liftToBlock, - mergeLexicalEnvironment, - updateModifiers, - }; + // Utilities + inlineExpressions, + getInternalName, + getLocalName, + getExportName, + getDeclarationName, + getNamespaceMemberName, + getExternalModuleOrNamespaceExportName, + restoreOuterExpressions, + restoreEnclosingLabel, + createUseStrictPrologue, + copyPrologue, + copyStandardPrologue, + copyCustomPrologue, + ensureUseStrict, + liftToBlock, + mergeLexicalEnvironment, + updateModifiers, + }; - return factory; + return factory; - // @api - function createNodeArray(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray { - if (elements === undefined || elements === emptyArray) { - elements = []; - } - else if (isNodeArray(elements)) { - if (hasTrailingComma === undefined || elements.hasTrailingComma === hasTrailingComma) { - // Ensure the transform flags have been aggregated for this NodeArray - if (elements.transformFlags === undefined) { - aggregateChildrenFlags(elements as MutableNodeArray); - } - Debug.attachNodeArrayDebugInfo(elements); - return elements; + // @api + function createNodeArray(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray { + if (elements === undefined || elements === emptyArray) { + elements = []; + } + else if (isNodeArray(elements)) { + if (hasTrailingComma === undefined || elements.hasTrailingComma === hasTrailingComma) { + // Ensure the transform flags have been aggregated for this NodeArray + if (elements.transformFlags === undefined) { + aggregateChildrenFlags(elements as MutableNodeArray); } - - // This *was* a `NodeArray`, but the `hasTrailingComma` option differs. Recreate the - // array with the same elements, text range, and transform flags but with the updated - // value for `hasTrailingComma` - const array = elements.slice() as MutableNodeArray; - array.pos = elements.pos; - array.end = elements.end; - array.hasTrailingComma = hasTrailingComma; - array.transformFlags = elements.transformFlags; - Debug.attachNodeArrayDebugInfo(array); - return array; + Debug.attachNodeArrayDebugInfo(elements); + return elements; } - // Since the element list of a node array is typically created by starting with an empty array and - // repeatedly calling push(), the list may not have the optimal memory layout. We invoke slice() for - // small arrays (1 to 4 elements) to give the VM a chance to allocate an optimal representation. - const length = elements.length; - const array = (length >= 1 && length <= 4 ? elements.slice() : elements) as MutableNodeArray; - setTextRangePosEnd(array, -1, -1); - array.hasTrailingComma = !!hasTrailingComma; - aggregateChildrenFlags(array); + // This *was* a `NodeArray`, but the `hasTrailingComma` option differs. Recreate the + // array with the same elements, text range, and transform flags but with the updated + // value for `hasTrailingComma` + const array = elements.slice() as MutableNodeArray; + array.pos = elements.pos; + array.end = elements.end; + array.hasTrailingComma = hasTrailingComma; + array.transformFlags = elements.transformFlags; Debug.attachNodeArrayDebugInfo(array); return array; } - function createBaseNode(kind: T["kind"]) { - return baseFactory.createBaseNode(kind) as Mutable; - } + // Since the element list of a node array is typically created by starting with an empty array and + // repeatedly calling push(), the list may not have the optimal memory layout. We invoke slice() for + // small arrays (1 to 4 elements) to give the VM a chance to allocate an optimal representation. + const length = elements.length; + const array = (length >= 1 && length <= 4 ? elements.slice() : elements) as MutableNodeArray; + setTextRangePosEnd(array, -1, -1); + array.hasTrailingComma = !!hasTrailingComma; + aggregateChildrenFlags(array); + Debug.attachNodeArrayDebugInfo(array); + return array; + } - function createBaseDeclaration( - kind: T["kind"], - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined - ) { - const node = createBaseNode(kind); - node.decorators = asNodeArray(decorators); - node.modifiers = asNodeArray(modifiers); - node.transformFlags |= - propagateChildrenFlags(node.decorators) | - propagateChildrenFlags(node.modifiers); - // NOTE: The following properties are commonly set by the binder and are added here to - // ensure declarations have a stable shape. - node.symbol = undefined!; // initialized by binder - node.localSymbol = undefined; // initialized by binder - node.locals = undefined; // initialized by binder - node.nextContainer = undefined; // initialized by binder - return node; - } + function createBaseNode(kind: T["kind"]) { + return baseFactory.createBaseNode(kind) as Mutable; + } - function createBaseNamedDeclaration( - kind: T["kind"], - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined - ) { - const node = createBaseDeclaration( - kind, - decorators, - modifiers - ); - name = asName(name); - node.name = name; - - // The PropertyName of a member is allowed to be `await`. - // We don't need to exclude `await` for type signatures since types - // don't propagate child flags. - if (name) { - switch (node.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertyAssignment: - if (isIdentifier(name)) { - node.transformFlags |= propagateIdentifierNameFlags(name); - break; - } - // fall through - default: - node.transformFlags |= propagateChildFlags(name); + function createBaseDeclaration( + kind: T["kind"], + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined + ) { + const node = createBaseNode(kind); + node.decorators = asNodeArray(decorators); + node.modifiers = asNodeArray(modifiers); + node.transformFlags |= + propagateChildrenFlags(node.decorators) | + propagateChildrenFlags(node.modifiers); + // NOTE: The following properties are commonly set by the binder and are added here to + // ensure declarations have a stable shape. + node.symbol = undefined!; // initialized by binder + node.localSymbol = undefined; // initialized by binder + node.locals = undefined; // initialized by binder + node.nextContainer = undefined; // initialized by binder + return node; + } + + function createBaseNamedDeclaration( + kind: T["kind"], + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined + ) { + const node = createBaseDeclaration( + kind, + decorators, + modifiers + ); + name = asName(name); + node.name = name; + + // The PropertyName of a member is allowed to be `await`. + // We don't need to exclude `await` for type signatures since types + // don't propagate child flags. + if (name) { + switch (node.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertyAssignment: + if (isIdentifier(name)) { + node.transformFlags |= propagateIdentifierNameFlags(name); break; - } + } + // fall through + default: + node.transformFlags |= propagateChildFlags(name); + break; } - return node; } + return node; + } - function createBaseGenericNamedDeclaration }>( - kind: T["kind"], - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined - ) { - const node = createBaseNamedDeclaration( - kind, - decorators, - modifiers, - name - ); - node.typeParameters = asNodeArray(typeParameters); - node.transformFlags |= propagateChildrenFlags(node.typeParameters); - if (typeParameters) node.transformFlags |= TransformFlags.ContainsTypeScript; - return node; - } + function createBaseGenericNamedDeclaration }>( + kind: T["kind"], + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined + ) { + const node = createBaseNamedDeclaration( + kind, + decorators, + modifiers, + name + ); + node.typeParameters = asNodeArray(typeParameters); + node.transformFlags |= propagateChildrenFlags(node.typeParameters); + if (typeParameters) node.transformFlags |= TransformFlags.ContainsTypeScript; + return node; + } - function createBaseSignatureDeclaration( - kind: T["kind"], - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[] | undefined, - type: TypeNode | undefined - ) { - const node = createBaseGenericNamedDeclaration( - kind, - decorators, - modifiers, - name, - typeParameters - ); - node.parameters = createNodeArray(parameters); - node.type = type; - node.transformFlags |= - propagateChildrenFlags(node.parameters) | - propagateChildFlags(node.type); - if (type) node.transformFlags |= TransformFlags.ContainsTypeScript; - return node; - } + function createBaseSignatureDeclaration( + kind: T["kind"], + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[] | undefined, + type: TypeNode | undefined + ) { + const node = createBaseGenericNamedDeclaration( + kind, + decorators, + modifiers, + name, + typeParameters + ); + node.parameters = createNodeArray(parameters); + node.type = type; + node.transformFlags |= + propagateChildrenFlags(node.parameters) | + propagateChildFlags(node.type); + if (type) node.transformFlags |= TransformFlags.ContainsTypeScript; + return node; + } - function updateBaseSignatureDeclaration(updated: Mutable, original: T) { - // copy children used only for error reporting - if (original.typeArguments) updated.typeArguments = original.typeArguments; - return update(updated, original); - } + function updateBaseSignatureDeclaration(updated: Mutable, original: T) { + // copy children used only for error reporting + if (original.typeArguments) updated.typeArguments = original.typeArguments; + return update(updated, original); + } - function createBaseFunctionLikeDeclaration( - kind: T["kind"], - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[] | undefined, - type: TypeNode | undefined, - body: T["body"] - ) { - const node = createBaseSignatureDeclaration( - kind, - decorators, - modifiers, - name, - typeParameters, - parameters, - type - ); - node.body = body; - node.transformFlags |= propagateChildFlags(node.body) & ~TransformFlags.ContainsPossibleTopLevelAwait; - if (!body) node.transformFlags |= TransformFlags.ContainsTypeScript; - return node; - } + function createBaseFunctionLikeDeclaration( + kind: T["kind"], + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[] | undefined, + type: TypeNode | undefined, + body: T["body"] + ) { + const node = createBaseSignatureDeclaration( + kind, + decorators, + modifiers, + name, + typeParameters, + parameters, + type + ); + node.body = body; + node.transformFlags |= propagateChildFlags(node.body) & ~TransformFlags.ContainsPossibleTopLevelAwait; + if (!body) node.transformFlags |= TransformFlags.ContainsTypeScript; + return node; + } - function updateBaseFunctionLikeDeclaration(updated: Mutable, original: T) { - // copy children used only for error reporting - if (original.exclamationToken) updated.exclamationToken = original.exclamationToken; - if (original.typeArguments) updated.typeArguments = original.typeArguments; - return updateBaseSignatureDeclaration(updated, original); - } + function updateBaseFunctionLikeDeclaration(updated: Mutable, original: T) { + // copy children used only for error reporting + if (original.exclamationToken) updated.exclamationToken = original.exclamationToken; + if (original.typeArguments) updated.typeArguments = original.typeArguments; + return updateBaseSignatureDeclaration(updated, original); + } - function createBaseInterfaceOrClassLikeDeclaration( - kind: T["kind"], - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined - ) { - const node = createBaseGenericNamedDeclaration( - kind, - decorators, - modifiers, - name, - typeParameters - ); - node.heritageClauses = asNodeArray(heritageClauses); - node.transformFlags |= propagateChildrenFlags(node.heritageClauses); - return node; - } + function createBaseInterfaceOrClassLikeDeclaration( + kind: T["kind"], + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined + ) { + const node = createBaseGenericNamedDeclaration( + kind, + decorators, + modifiers, + name, + typeParameters + ); + node.heritageClauses = asNodeArray(heritageClauses); + node.transformFlags |= propagateChildrenFlags(node.heritageClauses); + return node; + } - function createBaseClassLikeDeclaration( - kind: T["kind"], - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[] - ) { - const node = createBaseInterfaceOrClassLikeDeclaration( - kind, - decorators, - modifiers, - name, - typeParameters, - heritageClauses - ); - node.members = createNodeArray(members); - node.transformFlags |= propagateChildrenFlags(node.members); - return node; - } + function createBaseClassLikeDeclaration( + kind: T["kind"], + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[] + ) { + const node = createBaseInterfaceOrClassLikeDeclaration( + kind, + decorators, + modifiers, + name, + typeParameters, + heritageClauses + ); + node.members = createNodeArray(members); + node.transformFlags |= propagateChildrenFlags(node.members); + return node; + } - function createBaseBindingLikeDeclaration( - kind: T["kind"], - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | T["name"] | undefined, - initializer: Expression | undefined - ) { - const node = createBaseNamedDeclaration( - kind, - decorators, - modifiers, - name - ); - node.initializer = initializer; - node.transformFlags |= propagateChildFlags(node.initializer); - return node; - } + function createBaseBindingLikeDeclaration( + kind: T["kind"], + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | T["name"] | undefined, + initializer: Expression | undefined + ) { + const node = createBaseNamedDeclaration( + kind, + decorators, + modifiers, + name + ); + node.initializer = initializer; + node.transformFlags |= propagateChildFlags(node.initializer); + return node; + } - function createBaseVariableLikeDeclaration( - kind: T["kind"], - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | T["name"] | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) { - const node = createBaseBindingLikeDeclaration( - kind, - decorators, - modifiers, - name, - initializer - ); - node.type = type; - node.transformFlags |= propagateChildFlags(type); - if (type) node.transformFlags |= TransformFlags.ContainsTypeScript; - return node; - } + function createBaseVariableLikeDeclaration( + kind: T["kind"], + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | T["name"] | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) { + const node = createBaseBindingLikeDeclaration( + kind, + decorators, + modifiers, + name, + initializer + ); + node.type = type; + node.transformFlags |= propagateChildFlags(type); + if (type) node.transformFlags |= TransformFlags.ContainsTypeScript; + return node; + } - // - // Literals - // + // + // Literals + // - function createBaseLiteral( - kind: T["kind"], - text: string - ) { - const node = createBaseToken(kind); - node.text = text; - return node; - } + function createBaseLiteral( + kind: T["kind"], + text: string + ) { + const node = createBaseToken(kind); + node.text = text; + return node; + } - // @api - function createNumericLiteral(value: string | number, numericLiteralFlags: TokenFlags = TokenFlags.None): NumericLiteral { - const node = createBaseLiteral(SyntaxKind.NumericLiteral, typeof value === "number" ? value + "" : value); - node.numericLiteralFlags = numericLiteralFlags; - if (numericLiteralFlags & TokenFlags.BinaryOrOctalSpecifier) node.transformFlags |= TransformFlags.ContainsES2015; - return node; - } + // @api + function createNumericLiteral(value: string | number, numericLiteralFlags: TokenFlags = TokenFlags.None): NumericLiteral { + const node = createBaseLiteral(SyntaxKind.NumericLiteral, typeof value === "number" ? value + "" : value); + node.numericLiteralFlags = numericLiteralFlags; + if (numericLiteralFlags & TokenFlags.BinaryOrOctalSpecifier) node.transformFlags |= TransformFlags.ContainsES2015; + return node; + } - // @api - function createBigIntLiteral(value: string | PseudoBigInt): BigIntLiteral { - const node = createBaseLiteral(SyntaxKind.BigIntLiteral, typeof value === "string" ? value : pseudoBigIntToString(value) + "n"); - node.transformFlags |= TransformFlags.ContainsESNext; - return node; - } + // @api + function createBigIntLiteral(value: string | PseudoBigInt): BigIntLiteral { + const node = createBaseLiteral(SyntaxKind.BigIntLiteral, typeof value === "string" ? value : pseudoBigIntToString(value) + "n"); + node.transformFlags |= TransformFlags.ContainsESNext; + return node; + } - function createBaseStringLiteral(text: string, isSingleQuote?: boolean) { - const node = createBaseLiteral(SyntaxKind.StringLiteral, text); - node.singleQuote = isSingleQuote; - return node; - } + function createBaseStringLiteral(text: string, isSingleQuote?: boolean) { + const node = createBaseLiteral(SyntaxKind.StringLiteral, text); + node.singleQuote = isSingleQuote; + return node; + } - // @api - function createStringLiteral(text: string, isSingleQuote?: boolean, hasExtendedUnicodeEscape?: boolean): StringLiteral { - const node = createBaseStringLiteral(text, isSingleQuote); - node.hasExtendedUnicodeEscape = hasExtendedUnicodeEscape; - if (hasExtendedUnicodeEscape) node.transformFlags |= TransformFlags.ContainsES2015; - return node; - } + // @api + function createStringLiteral(text: string, isSingleQuote?: boolean, hasExtendedUnicodeEscape?: boolean): StringLiteral { + const node = createBaseStringLiteral(text, isSingleQuote); + node.hasExtendedUnicodeEscape = hasExtendedUnicodeEscape; + if (hasExtendedUnicodeEscape) node.transformFlags |= TransformFlags.ContainsES2015; + return node; + } - // @api - function createStringLiteralFromNode(sourceNode: PropertyNameLiteral): StringLiteral { - const node = createBaseStringLiteral(getTextOfIdentifierOrLiteral(sourceNode), /*isSingleQuote*/ undefined); - node.textSourceNode = sourceNode; - return node; - } + // @api + function createStringLiteralFromNode(sourceNode: PropertyNameLiteral): StringLiteral { + const node = createBaseStringLiteral(getTextOfIdentifierOrLiteral(sourceNode), /*isSingleQuote*/ undefined); + node.textSourceNode = sourceNode; + return node; + } - // @api - function createRegularExpressionLiteral(text: string): RegularExpressionLiteral { - const node = createBaseLiteral(SyntaxKind.RegularExpressionLiteral, text); - return node; - } + // @api + function createRegularExpressionLiteral(text: string): RegularExpressionLiteral { + const node = createBaseLiteral(SyntaxKind.RegularExpressionLiteral, text); + return node; + } - // @api - function createLiteralLikeNode(kind: LiteralToken["kind"] | SyntaxKind.JsxTextAllWhiteSpaces, text: string): LiteralToken { - switch (kind) { - case SyntaxKind.NumericLiteral: return createNumericLiteral(text, /*numericLiteralFlags*/ 0); - case SyntaxKind.BigIntLiteral: return createBigIntLiteral(text); - case SyntaxKind.StringLiteral: return createStringLiteral(text, /*isSingleQuote*/ undefined); - case SyntaxKind.JsxText: return createJsxText(text, /*containsOnlyTriviaWhiteSpaces*/ false); - case SyntaxKind.JsxTextAllWhiteSpaces: return createJsxText(text, /*containsOnlyTriviaWhiteSpaces*/ true); - case SyntaxKind.RegularExpressionLiteral: return createRegularExpressionLiteral(text); - case SyntaxKind.NoSubstitutionTemplateLiteral: return createTemplateLiteralLikeNode(kind, text, /*rawText*/ undefined, /*templateFlags*/ 0) as NoSubstitutionTemplateLiteral; - } + // @api + function createLiteralLikeNode(kind: LiteralToken["kind"] | SyntaxKind.JsxTextAllWhiteSpaces, text: string): LiteralToken { + switch (kind) { + case SyntaxKind.NumericLiteral: return createNumericLiteral(text, /*numericLiteralFlags*/ 0); + case SyntaxKind.BigIntLiteral: return createBigIntLiteral(text); + case SyntaxKind.StringLiteral: return createStringLiteral(text, /*isSingleQuote*/ undefined); + case SyntaxKind.JsxText: return createJsxText(text, /*containsOnlyTriviaWhiteSpaces*/ false); + case SyntaxKind.JsxTextAllWhiteSpaces: return createJsxText(text, /*containsOnlyTriviaWhiteSpaces*/ true); + case SyntaxKind.RegularExpressionLiteral: return createRegularExpressionLiteral(text); + case SyntaxKind.NoSubstitutionTemplateLiteral: return createTemplateLiteralLikeNode(kind, text, /*rawText*/ undefined, /*templateFlags*/ 0) as NoSubstitutionTemplateLiteral; } + } - // - // Identifiers - // + // + // Identifiers + // - function createBaseIdentifier(text: string, originalKeywordKind: SyntaxKind | undefined) { - if (originalKeywordKind === undefined && text) { - originalKeywordKind = stringToToken(text); - } - if (originalKeywordKind === SyntaxKind.Identifier) { - originalKeywordKind = undefined; - } - const node = baseFactory.createBaseIdentifierNode(SyntaxKind.Identifier) as Mutable; - node.originalKeywordKind = originalKeywordKind; - node.escapedText = escapeLeadingUnderscores(text); - return node; + function createBaseIdentifier(text: string, originalKeywordKind: SyntaxKind | undefined) { + if (originalKeywordKind === undefined && text) { + originalKeywordKind = stringToToken(text); } - - function createBaseGeneratedIdentifier(text: string, autoGenerateFlags: GeneratedIdentifierFlags) { - const node = createBaseIdentifier(text, /*originalKeywordKind*/ undefined) as Mutable; - node.autoGenerateFlags = autoGenerateFlags; - node.autoGenerateId = nextAutoGenerateId; - nextAutoGenerateId++; - return node; + if (originalKeywordKind === SyntaxKind.Identifier) { + originalKeywordKind = undefined; } + const node = baseFactory.createBaseIdentifierNode(SyntaxKind.Identifier) as Mutable; + node.originalKeywordKind = originalKeywordKind; + node.escapedText = escapeLeadingUnderscores(text); + return node; + } - // @api - function createIdentifier(text: string, typeArguments?: readonly (TypeNode | TypeParameterDeclaration)[], originalKeywordKind?: SyntaxKind): Identifier { - const node = createBaseIdentifier(text, originalKeywordKind); - if (typeArguments) { - // NOTE: we do not use `setChildren` here because typeArguments in an identifier do not contribute to transformations - node.typeArguments = createNodeArray(typeArguments); - } - if (node.originalKeywordKind === SyntaxKind.AwaitKeyword) { - node.transformFlags |= TransformFlags.ContainsPossibleTopLevelAwait; - } - return node; - } + function createBaseGeneratedIdentifier(text: string, autoGenerateFlags: GeneratedIdentifierFlags) { + const node = createBaseIdentifier(text, /*originalKeywordKind*/ undefined) as Mutable; + node.autoGenerateFlags = autoGenerateFlags; + node.autoGenerateId = nextAutoGenerateId; + nextAutoGenerateId++; + return node; + } - // @api - function updateIdentifier(node: Identifier, typeArguments?: NodeArray | undefined): Identifier { - return node.typeArguments !== typeArguments - ? update(createIdentifier(idText(node), typeArguments), node) - : node; + // @api + function createIdentifier(text: string, typeArguments?: readonly (TypeNode | TypeParameterDeclaration)[], originalKeywordKind?: SyntaxKind): Identifier { + const node = createBaseIdentifier(text, originalKeywordKind); + if (typeArguments) { + // NOTE: we do not use `setChildren` here because typeArguments in an identifier do not contribute to transformations + node.typeArguments = createNodeArray(typeArguments); } - - // @api - function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes?: boolean): GeneratedIdentifier { - let flags = GeneratedIdentifierFlags.Auto; - if (reservedInNestedScopes) flags |= GeneratedIdentifierFlags.ReservedInNestedScopes; - const name = createBaseGeneratedIdentifier("", flags); - if (recordTempVariable) { - recordTempVariable(name); - } - return name; + if (node.originalKeywordKind === SyntaxKind.AwaitKeyword) { + node.transformFlags |= TransformFlags.ContainsPossibleTopLevelAwait; } + return node; + } - /** Create a unique temporary variable for use in a loop. */ - // @api - function createLoopVariable(reservedInNestedScopes?: boolean): Identifier { - let flags = GeneratedIdentifierFlags.Loop; - if (reservedInNestedScopes) flags |= GeneratedIdentifierFlags.ReservedInNestedScopes; - return createBaseGeneratedIdentifier("", flags); - } + // @api + function updateIdentifier(node: Identifier, typeArguments?: NodeArray | undefined): Identifier { + return node.typeArguments !== typeArguments + ? update(createIdentifier(idText(node), typeArguments), node) + : node; + } - /** Create a unique name based on the supplied text. */ - // @api - function createUniqueName(text: string, flags: GeneratedIdentifierFlags = GeneratedIdentifierFlags.None): Identifier { - Debug.assert(!(flags & GeneratedIdentifierFlags.KindMask), "Argument out of range: flags"); - Debug.assert((flags & (GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel)) !== GeneratedIdentifierFlags.FileLevel, "GeneratedIdentifierFlags.FileLevel cannot be set without also setting GeneratedIdentifierFlags.Optimistic"); - return createBaseGeneratedIdentifier(text, GeneratedIdentifierFlags.Unique | flags); + // @api + function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes?: boolean): GeneratedIdentifier { + let flags = GeneratedIdentifierFlags.Auto; + if (reservedInNestedScopes) flags |= GeneratedIdentifierFlags.ReservedInNestedScopes; + const name = createBaseGeneratedIdentifier("", flags); + if (recordTempVariable) { + recordTempVariable(name); } + return name; + } - /** Create a unique name generated for a node. */ - // @api - function getGeneratedNameForNode(node: Node | undefined, flags: GeneratedIdentifierFlags = 0): Identifier { - Debug.assert(!(flags & GeneratedIdentifierFlags.KindMask), "Argument out of range: flags"); - const name = createBaseGeneratedIdentifier(node && isIdentifier(node) ? idText(node) : "", GeneratedIdentifierFlags.Node | flags); - name.original = node; - return name; - } + /** Create a unique temporary variable for use in a loop. */ + // @api + function createLoopVariable(reservedInNestedScopes?: boolean): Identifier { + let flags = GeneratedIdentifierFlags.Loop; + if (reservedInNestedScopes) flags |= GeneratedIdentifierFlags.ReservedInNestedScopes; + return createBaseGeneratedIdentifier("", flags); + } - // @api - function createPrivateIdentifier(text: string): PrivateIdentifier { - if (!startsWith(text, "#")) Debug.fail("First character of private identifier must be #: " + text); - const node = baseFactory.createBasePrivateIdentifierNode(SyntaxKind.PrivateIdentifier) as Mutable; - node.escapedText = escapeLeadingUnderscores(text); - node.transformFlags |= TransformFlags.ContainsClassFields; - return node; - } + /** Create a unique name based on the supplied text. */ + // @api + function createUniqueName(text: string, flags: GeneratedIdentifierFlags = GeneratedIdentifierFlags.None): Identifier { + Debug.assert(!(flags & GeneratedIdentifierFlags.KindMask), "Argument out of range: flags"); + Debug.assert((flags & (GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel)) !== GeneratedIdentifierFlags.FileLevel, "GeneratedIdentifierFlags.FileLevel cannot be set without also setting GeneratedIdentifierFlags.Optimistic"); + return createBaseGeneratedIdentifier(text, GeneratedIdentifierFlags.Unique | flags); + } - // - // Punctuation - // + /** Create a unique name generated for a node. */ + // @api + function getGeneratedNameForNode(node: Node | undefined, flags: GeneratedIdentifierFlags = 0): Identifier { + Debug.assert(!(flags & GeneratedIdentifierFlags.KindMask), "Argument out of range: flags"); + const name = createBaseGeneratedIdentifier(node && isIdentifier(node) ? idText(node) : "", GeneratedIdentifierFlags.Node | flags); + name.original = node; + return name; + } - function createBaseToken(kind: T["kind"]) { - return baseFactory.createBaseTokenNode(kind) as Mutable; - } + // @api + function createPrivateIdentifier(text: string): PrivateIdentifier { + if (!startsWith(text, "#")) Debug.fail("First character of private identifier must be #: " + text); + const node = baseFactory.createBasePrivateIdentifierNode(SyntaxKind.PrivateIdentifier) as Mutable; + node.escapedText = escapeLeadingUnderscores(text); + node.transformFlags |= TransformFlags.ContainsClassFields; + return node; + } - // @api - function createToken(token: SyntaxKind.SuperKeyword): SuperExpression; - function createToken(token: SyntaxKind.ThisKeyword): ThisExpression; - function createToken(token: SyntaxKind.NullKeyword): NullLiteral; - function createToken(token: SyntaxKind.TrueKeyword): TrueLiteral; - function createToken(token: SyntaxKind.FalseKeyword): FalseLiteral; - function createToken(token: TKind): PunctuationToken; - function createToken(token: TKind): KeywordTypeNode; - function createToken(token: TKind): ModifierToken; - function createToken(token: TKind): KeywordToken; - function createToken(token: TKind): Token; - function createToken(token: TKind): Token; - function createToken(token: TKind) { - Debug.assert(token >= SyntaxKind.FirstToken && token <= SyntaxKind.LastToken, "Invalid token"); - Debug.assert(token <= SyntaxKind.FirstTemplateToken || token >= SyntaxKind.LastTemplateToken, "Invalid token. Use 'createTemplateLiteralLikeNode' to create template literals."); - Debug.assert(token <= SyntaxKind.FirstLiteralToken || token >= SyntaxKind.LastLiteralToken, "Invalid token. Use 'createLiteralLikeNode' to create literals."); - Debug.assert(token !== SyntaxKind.Identifier, "Invalid token. Use 'createIdentifier' to create identifiers"); - const node = createBaseToken>(token); - let transformFlags = TransformFlags.None; - switch (token) { - case SyntaxKind.AsyncKeyword: - // 'async' modifier is ES2017 (async functions) or ES2018 (async generators) - transformFlags = - TransformFlags.ContainsES2017 | - TransformFlags.ContainsES2018; - break; + // + // Punctuation + // - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.ReadonlyKeyword: - case SyntaxKind.AbstractKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.ConstKeyword: - case SyntaxKind.AnyKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.OverrideKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.UnknownKeyword: - case SyntaxKind.UndefinedKeyword: // `undefined` is an Identifier in the expression case. - transformFlags = TransformFlags.ContainsTypeScript; - break; - case SyntaxKind.SuperKeyword: - transformFlags = TransformFlags.ContainsES2015 | TransformFlags.ContainsLexicalSuper; - break; - case SyntaxKind.StaticKeyword: - transformFlags = TransformFlags.ContainsES2015; - break; - case SyntaxKind.ThisKeyword: - // 'this' indicates a lexical 'this' - transformFlags = TransformFlags.ContainsLexicalThis; - break; - } - if (transformFlags) { - node.transformFlags |= transformFlags; - } - return node; - } + function createBaseToken(kind: T["kind"]) { + return baseFactory.createBaseTokenNode(kind) as Mutable; + } - // - // Reserved words - // + // @api + function createToken(token: SyntaxKind.SuperKeyword): SuperExpression; + function createToken(token: SyntaxKind.ThisKeyword): ThisExpression; + function createToken(token: SyntaxKind.NullKeyword): NullLiteral; + function createToken(token: SyntaxKind.TrueKeyword): TrueLiteral; + function createToken(token: SyntaxKind.FalseKeyword): FalseLiteral; + function createToken(token: TKind): PunctuationToken; + function createToken(token: TKind): KeywordTypeNode; + function createToken(token: TKind): ModifierToken; + function createToken(token: TKind): KeywordToken; + function createToken(token: TKind): Token; + function createToken(token: TKind): Token; + function createToken(token: TKind) { + Debug.assert(token >= SyntaxKind.FirstToken && token <= SyntaxKind.LastToken, "Invalid token"); + Debug.assert(token <= SyntaxKind.FirstTemplateToken || token >= SyntaxKind.LastTemplateToken, "Invalid token. Use 'createTemplateLiteralLikeNode' to create template literals."); + Debug.assert(token <= SyntaxKind.FirstLiteralToken || token >= SyntaxKind.LastLiteralToken, "Invalid token. Use 'createLiteralLikeNode' to create literals."); + Debug.assert(token !== SyntaxKind.Identifier, "Invalid token. Use 'createIdentifier' to create identifiers"); + const node = createBaseToken>(token); + let transformFlags = TransformFlags.None; + switch (token) { + case SyntaxKind.AsyncKeyword: + // 'async' modifier is ES2017 (async functions) or ES2018 (async generators) + transformFlags = + TransformFlags.ContainsES2017 | + TransformFlags.ContainsES2018; + break; - // @api - function createSuper() { - return createToken(SyntaxKind.SuperKeyword); + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.AbstractKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.ConstKeyword: + case SyntaxKind.AnyKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.OverrideKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.UnknownKeyword: + case SyntaxKind.UndefinedKeyword: // `undefined` is an Identifier in the expression case. + transformFlags = TransformFlags.ContainsTypeScript; + break; + case SyntaxKind.SuperKeyword: + transformFlags = TransformFlags.ContainsES2015 | TransformFlags.ContainsLexicalSuper; + break; + case SyntaxKind.StaticKeyword: + transformFlags = TransformFlags.ContainsES2015; + break; + case SyntaxKind.ThisKeyword: + // 'this' indicates a lexical 'this' + transformFlags = TransformFlags.ContainsLexicalThis; + break; } - - // @api - function createThis() { - return createToken(SyntaxKind.ThisKeyword); + if (transformFlags) { + node.transformFlags |= transformFlags; } + return node; + } - // @api - function createNull() { - return createToken(SyntaxKind.NullKeyword); - } + // + // Reserved words + // - // @api - function createTrue() { - return createToken(SyntaxKind.TrueKeyword); - } + // @api + function createSuper() { + return createToken(SyntaxKind.SuperKeyword); + } - // @api - function createFalse() { - return createToken(SyntaxKind.FalseKeyword); - } + // @api + function createThis() { + return createToken(SyntaxKind.ThisKeyword); + } - // - // Modifiers - // + // @api + function createNull() { + return createToken(SyntaxKind.NullKeyword); + } - // @api - function createModifier(kind: T) { - return createToken(kind); - } + // @api + function createTrue() { + return createToken(SyntaxKind.TrueKeyword); + } - // @api - function createModifiersFromModifierFlags(flags: ModifierFlags) { - const result: Modifier[] = []; - if (flags & ModifierFlags.Export) result.push(createModifier(SyntaxKind.ExportKeyword)); - if (flags & ModifierFlags.Ambient) result.push(createModifier(SyntaxKind.DeclareKeyword)); - if (flags & ModifierFlags.Default) result.push(createModifier(SyntaxKind.DefaultKeyword)); - if (flags & ModifierFlags.Const) result.push(createModifier(SyntaxKind.ConstKeyword)); - if (flags & ModifierFlags.Public) result.push(createModifier(SyntaxKind.PublicKeyword)); - if (flags & ModifierFlags.Private) result.push(createModifier(SyntaxKind.PrivateKeyword)); - if (flags & ModifierFlags.Protected) result.push(createModifier(SyntaxKind.ProtectedKeyword)); - if (flags & ModifierFlags.Abstract) result.push(createModifier(SyntaxKind.AbstractKeyword)); - if (flags & ModifierFlags.Static) result.push(createModifier(SyntaxKind.StaticKeyword)); - if (flags & ModifierFlags.Override) result.push(createModifier(SyntaxKind.OverrideKeyword)); - if (flags & ModifierFlags.Readonly) result.push(createModifier(SyntaxKind.ReadonlyKeyword)); - if (flags & ModifierFlags.Async) result.push(createModifier(SyntaxKind.AsyncKeyword)); - return result; - } + // @api + function createFalse() { + return createToken(SyntaxKind.FalseKeyword); + } - // - // Names - // + // + // Modifiers + // - // @api - function createQualifiedName(left: EntityName, right: string | Identifier) { - const node = createBaseNode(SyntaxKind.QualifiedName); - node.left = left; - node.right = asName(right); - node.transformFlags |= - propagateChildFlags(node.left) | - propagateIdentifierNameFlags(node.right); - return node; - } + // @api + function createModifier(kind: T) { + return createToken(kind); + } - // @api - function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier) { - return node.left !== left - || node.right !== right - ? update(createQualifiedName(left, right), node) - : node; - } + // @api + function createModifiersFromModifierFlags(flags: ModifierFlags) { + const result: Modifier[] = []; + if (flags & ModifierFlags.Export) result.push(createModifier(SyntaxKind.ExportKeyword)); + if (flags & ModifierFlags.Ambient) result.push(createModifier(SyntaxKind.DeclareKeyword)); + if (flags & ModifierFlags.Default) result.push(createModifier(SyntaxKind.DefaultKeyword)); + if (flags & ModifierFlags.Const) result.push(createModifier(SyntaxKind.ConstKeyword)); + if (flags & ModifierFlags.Public) result.push(createModifier(SyntaxKind.PublicKeyword)); + if (flags & ModifierFlags.Private) result.push(createModifier(SyntaxKind.PrivateKeyword)); + if (flags & ModifierFlags.Protected) result.push(createModifier(SyntaxKind.ProtectedKeyword)); + if (flags & ModifierFlags.Abstract) result.push(createModifier(SyntaxKind.AbstractKeyword)); + if (flags & ModifierFlags.Static) result.push(createModifier(SyntaxKind.StaticKeyword)); + if (flags & ModifierFlags.Override) result.push(createModifier(SyntaxKind.OverrideKeyword)); + if (flags & ModifierFlags.Readonly) result.push(createModifier(SyntaxKind.ReadonlyKeyword)); + if (flags & ModifierFlags.Async) result.push(createModifier(SyntaxKind.AsyncKeyword)); + return result; + } - // @api - function createComputedPropertyName(expression: Expression) { - const node = createBaseNode(SyntaxKind.ComputedPropertyName); - node.expression = parenthesizerRules().parenthesizeExpressionOfComputedPropertyName(expression); - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsES2015 | - TransformFlags.ContainsComputedPropertyName; - return node; - } + // + // Names + // + + // @api + function createQualifiedName(left: EntityName, right: string | Identifier) { + const node = createBaseNode(SyntaxKind.QualifiedName); + node.left = left; + node.right = asName(right); + node.transformFlags |= + propagateChildFlags(node.left) | + propagateIdentifierNameFlags(node.right); + return node; + } - // @api - function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression) { - return node.expression !== expression - ? update(createComputedPropertyName(expression), node) - : node; - } + // @api + function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier) { + return node.left !== left + || node.right !== right + ? update(createQualifiedName(left, right), node) + : node; + } - // - // Signature elements - // + // @api + function createComputedPropertyName(expression: Expression) { + const node = createBaseNode(SyntaxKind.ComputedPropertyName); + node.expression = parenthesizerRules().parenthesizeExpressionOfComputedPropertyName(expression); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsES2015 | + TransformFlags.ContainsComputedPropertyName; + return node; + } - // @api - function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode) { - const node = createBaseNamedDeclaration( - SyntaxKind.TypeParameter, - /*decorators*/ undefined, - /*modifiers*/ undefined, - name - ); - node.constraint = constraint; - node.default = defaultType; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression) { + return node.expression !== expression + ? update(createComputedPropertyName(expression), node) + : node; + } - // @api - function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined) { - return node.name !== name - || node.constraint !== constraint - || node.default !== defaultType - ? update(createTypeParameterDeclaration(name, constraint, defaultType), node) - : node; - } + // + // Signature elements + // + + // @api + function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode) { + const node = createBaseNamedDeclaration( + SyntaxKind.TypeParameter, + /*decorators*/ undefined, + /*modifiers*/ undefined, + name + ); + node.constraint = constraint; + node.default = defaultType; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createParameterDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - dotDotDotToken: DotDotDotToken | undefined, - name: string | BindingName, - questionToken?: QuestionToken, - type?: TypeNode, - initializer?: Expression - ) { - const node = createBaseVariableLikeDeclaration( - SyntaxKind.Parameter, - decorators, - modifiers, - name, - type, - initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) - ); - node.dotDotDotToken = dotDotDotToken; - node.questionToken = questionToken; - if (isThisIdentifier(node.name)) { - node.transformFlags = TransformFlags.ContainsTypeScript; - } - else { - node.transformFlags |= - propagateChildFlags(node.dotDotDotToken) | - propagateChildFlags(node.questionToken); - if (questionToken) node.transformFlags |= TransformFlags.ContainsTypeScript; - if (modifiersToFlags(node.modifiers) & ModifierFlags.ParameterPropertyModifier) node.transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax; - if (initializer || dotDotDotToken) node.transformFlags |= TransformFlags.ContainsES2015; - } - return node; - } + // @api + function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined) { + return node.name !== name + || node.constraint !== constraint + || node.default !== defaultType + ? update(createTypeParameterDeclaration(name, constraint, defaultType), node) + : node; + } - // @api - function updateParameterDeclaration( - node: ParameterDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - dotDotDotToken: DotDotDotToken | undefined, - name: string | BindingName, - questionToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.dotDotDotToken !== dotDotDotToken - || node.name !== name - || node.questionToken !== questionToken - || node.type !== type - || node.initializer !== initializer - ? update(createParameterDeclaration(decorators, modifiers, dotDotDotToken, name, questionToken, type, initializer), node) - : node; + // @api + function createParameterDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + dotDotDotToken: DotDotDotToken | undefined, + name: string | BindingName, + questionToken?: QuestionToken, + type?: TypeNode, + initializer?: Expression + ) { + const node = createBaseVariableLikeDeclaration( + SyntaxKind.Parameter, + decorators, + modifiers, + name, + type, + initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) + ); + node.dotDotDotToken = dotDotDotToken; + node.questionToken = questionToken; + if (isThisIdentifier(node.name)) { + node.transformFlags = TransformFlags.ContainsTypeScript; } - - // @api - function createDecorator(expression: Expression) { - const node = createBaseNode(SyntaxKind.Decorator); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + else { node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsTypeScript | - TransformFlags.ContainsTypeScriptClassSyntax; - return node; - } - - // @api - function updateDecorator(node: Decorator, expression: Expression) { - return node.expression !== expression - ? update(createDecorator(expression), node) - : node; + propagateChildFlags(node.dotDotDotToken) | + propagateChildFlags(node.questionToken); + if (questionToken) node.transformFlags |= TransformFlags.ContainsTypeScript; + if (modifiersToFlags(node.modifiers) & ModifierFlags.ParameterPropertyModifier) node.transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax; + if (initializer || dotDotDotToken) node.transformFlags |= TransformFlags.ContainsES2015; } + return node; + } - // - // Type Elements - // + // @api + function updateParameterDeclaration( + node: ParameterDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + dotDotDotToken: DotDotDotToken | undefined, + name: string | BindingName, + questionToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.dotDotDotToken !== dotDotDotToken + || node.name !== name + || node.questionToken !== questionToken + || node.type !== type + || node.initializer !== initializer + ? update(createParameterDeclaration(decorators, modifiers, dotDotDotToken, name, questionToken, type, initializer), node) + : node; + } - // @api - function createPropertySignature( - modifiers: readonly Modifier[] | undefined, - name: PropertyName | string, - questionToken: QuestionToken | undefined, - type: TypeNode | undefined - ): PropertySignature { - const node = createBaseNamedDeclaration( - SyntaxKind.PropertySignature, - /*decorators*/ undefined, - modifiers, - name - ); - node.type = type; - node.questionToken = questionToken; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createDecorator(expression: Expression) { + const node = createBaseNode(SyntaxKind.Decorator); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsTypeScript | + TransformFlags.ContainsTypeScriptClassSyntax; + return node; + } - // @api - function updatePropertySignature( - node: PropertySignature, - modifiers: readonly Modifier[] | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined, - type: TypeNode | undefined - ) { - return node.modifiers !== modifiers - || node.name !== name - || node.questionToken !== questionToken - || node.type !== type - ? update(createPropertySignature(modifiers, name, questionToken, type), node) - : node; - } + // @api + function updateDecorator(node: Decorator, expression: Expression) { + return node.expression !== expression + ? update(createDecorator(expression), node) + : node; + } - // @api - function createPropertyDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) { - const node = createBaseVariableLikeDeclaration( - SyntaxKind.PropertyDeclaration, - decorators, - modifiers, - name, - type, - initializer - ); - node.questionToken = questionOrExclamationToken && isQuestionToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined; - node.exclamationToken = questionOrExclamationToken && isExclamationToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined; - node.transformFlags |= - propagateChildFlags(node.questionToken) | - propagateChildFlags(node.exclamationToken) | - TransformFlags.ContainsClassFields; - if (isComputedPropertyName(node.name) || (hasStaticModifier(node) && node.initializer)) { - node.transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax; - } - if (questionOrExclamationToken || modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - return node; - } + // + // Type Elements + // + + // @api + function createPropertySignature( + modifiers: readonly Modifier[] | undefined, + name: PropertyName | string, + questionToken: QuestionToken | undefined, + type: TypeNode | undefined + ): PropertySignature { + const node = createBaseNamedDeclaration( + SyntaxKind.PropertySignature, + /*decorators*/ undefined, + modifiers, + name + ); + node.type = type; + node.questionToken = questionToken; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updatePropertyDeclaration( - node: PropertyDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.questionToken !== (questionOrExclamationToken !== undefined && isQuestionToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined) - || node.exclamationToken !== (questionOrExclamationToken !== undefined && isExclamationToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined) - || node.type !== type - || node.initializer !== initializer - ? update(createPropertyDeclaration(decorators, modifiers, name, questionOrExclamationToken, type, initializer), node) - : node; - } + // @api + function updatePropertySignature( + node: PropertySignature, + modifiers: readonly Modifier[] | undefined, + name: PropertyName, + questionToken: QuestionToken | undefined, + type: TypeNode | undefined + ) { + return node.modifiers !== modifiers + || node.name !== name + || node.questionToken !== questionToken + || node.type !== type + ? update(createPropertySignature(modifiers, name, questionToken, type), node) + : node; + } - // @api - function createMethodSignature( - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionToken: QuestionToken | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ) { - const node = createBaseSignatureDeclaration( - SyntaxKind.MethodSignature, - /*decorators*/ undefined, - modifiers, - name, - typeParameters, - parameters, - type - ); - node.questionToken = questionToken; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; + // @api + function createPropertyDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) { + const node = createBaseVariableLikeDeclaration( + SyntaxKind.PropertyDeclaration, + decorators, + modifiers, + name, + type, + initializer + ); + node.questionToken = questionOrExclamationToken && isQuestionToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined; + node.exclamationToken = questionOrExclamationToken && isExclamationToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined; + node.transformFlags |= + propagateChildFlags(node.questionToken) | + propagateChildFlags(node.exclamationToken) | + TransformFlags.ContainsClassFields; + if (isComputedPropertyName(node.name) || (hasStaticModifier(node) && node.initializer)) { + node.transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax; + } + if (questionOrExclamationToken || modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { + node.transformFlags |= TransformFlags.ContainsTypeScript; } + return node; + } - // @api - function updateMethodSignature( - node: MethodSignature, - modifiers: readonly Modifier[] | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined, - typeParameters: NodeArray | undefined, - parameters: NodeArray, - type: TypeNode | undefined - ) { - return node.modifiers !== modifiers - || node.name !== name - || node.questionToken !== questionToken - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? updateBaseSignatureDeclaration(createMethodSignature(modifiers, name, questionToken, typeParameters, parameters, type), node) - : node; - } + // @api + function updatePropertyDeclaration( + node: PropertyDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.questionToken !== (questionOrExclamationToken !== undefined && isQuestionToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined) + || node.exclamationToken !== (questionOrExclamationToken !== undefined && isExclamationToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined) + || node.type !== type + || node.initializer !== initializer + ? update(createPropertyDeclaration(decorators, modifiers, name, questionOrExclamationToken, type, initializer), node) + : node; + } - // @api - function createMethodDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: string | PropertyName, - questionToken: QuestionToken | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined - ) { - const node = createBaseFunctionLikeDeclaration( - SyntaxKind.MethodDeclaration, - decorators, - modifiers, - name, - typeParameters, - parameters, - type, - body - ); - node.asteriskToken = asteriskToken; - node.questionToken = questionToken; - node.transformFlags |= - propagateChildFlags(node.asteriskToken) | - propagateChildFlags(node.questionToken) | - TransformFlags.ContainsES2015; - if (questionToken) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { - if (asteriskToken) { - node.transformFlags |= TransformFlags.ContainsES2018; - } - else { - node.transformFlags |= TransformFlags.ContainsES2017; - } - } - else if (asteriskToken) { - node.transformFlags |= TransformFlags.ContainsGenerator; - } - return node; - } - - // @api - function updateMethodDeclaration( - node: MethodDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.asteriskToken !== asteriskToken - || node.name !== name - || node.questionToken !== questionToken - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? updateBaseFunctionLikeDeclaration(createMethodDeclaration(decorators, modifiers, asteriskToken, name, questionToken, typeParameters, parameters, type, body), node) - : node; - } - - // @api - function createClassStaticBlockDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - body: Block - ): ClassStaticBlockDeclaration { - const node = createBaseGenericNamedDeclaration( - SyntaxKind.ClassStaticBlockDeclaration, - decorators, - modifiers, - /*name*/ undefined, - /*typeParameters*/ undefined - ); - node.body = body; - node.transformFlags = propagateChildFlags(body) | TransformFlags.ContainsClassFields; - return node; - } - - // @api - function updateClassStaticBlockDeclaration( - node: ClassStaticBlockDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - body: Block - ): ClassStaticBlockDeclaration { - return node.decorators !== decorators - || node.modifier !== modifiers - || node.body !== body - ? update(createClassStaticBlockDeclaration(decorators, modifiers, body), node) - : node; - } - - // @api - function createConstructorDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - body: Block | undefined - ) { - const node = createBaseFunctionLikeDeclaration( - SyntaxKind.Constructor, - decorators, - modifiers, - /*name*/ undefined, - /*typeParameters*/ undefined, - parameters, - /*type*/ undefined, - body - ); - node.transformFlags |= TransformFlags.ContainsES2015; - return node; - } - - // @api - function updateConstructorDeclaration( - node: ConstructorDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - body: Block | undefined - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.parameters !== parameters - || node.body !== body - ? updateBaseFunctionLikeDeclaration(createConstructorDeclaration(decorators, modifiers, parameters, body), node) - : node; - } - - // @api - function createGetAccessorDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined - ) { - return createBaseFunctionLikeDeclaration( - SyntaxKind.GetAccessor, - decorators, - modifiers, - name, - /*typeParameters*/ undefined, - parameters, - type, - body - ); - } - - // @api - function updateGetAccessorDeclaration( - node: GetAccessorDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: PropertyName, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? updateBaseFunctionLikeDeclaration(createGetAccessorDeclaration(decorators, modifiers, name, parameters, type, body), node) - : node; - } - - // @api - function createSetAccessorDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - parameters: readonly ParameterDeclaration[], - body: Block | undefined - ) { - return createBaseFunctionLikeDeclaration( - SyntaxKind.SetAccessor, - decorators, - modifiers, - name, - /*typeParameters*/ undefined, - parameters, - /*type*/ undefined, - body - ); - } - - // @api - function updateSetAccessorDeclaration( - node: SetAccessorDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: PropertyName, - parameters: readonly ParameterDeclaration[], - body: Block | undefined - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.parameters !== parameters - || node.body !== body - ? updateBaseFunctionLikeDeclaration(createSetAccessorDeclaration(decorators, modifiers, name, parameters, body), node) - : node; - } - - // @api - function createCallSignature( - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ): CallSignatureDeclaration { - const node = createBaseSignatureDeclaration( - SyntaxKind.CallSignature, - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*name*/ undefined, - typeParameters, - parameters, - type - ); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } - - // @api - function updateCallSignature( - node: CallSignatureDeclaration, - typeParameters: NodeArray | undefined, - parameters: NodeArray, - type: TypeNode | undefined - ) { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? updateBaseSignatureDeclaration(createCallSignature(typeParameters, parameters, type), node) - : node; - } - - // @api - function createConstructSignature( - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ): ConstructSignatureDeclaration { - const node = createBaseSignatureDeclaration( - SyntaxKind.ConstructSignature, - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*name*/ undefined, - typeParameters, - parameters, - type - ); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } - - // @api - function updateConstructSignature( - node: ConstructSignatureDeclaration, - typeParameters: NodeArray | undefined, - parameters: NodeArray, - type: TypeNode | undefined - ) { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? updateBaseSignatureDeclaration(createConstructSignature(typeParameters, parameters, type), node) - : node; - } - - // @api - function createIndexSignature( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ): IndexSignatureDeclaration { - const node = createBaseSignatureDeclaration( - SyntaxKind.IndexSignature, - decorators, - modifiers, - /*name*/ undefined, - /*typeParameters*/ undefined, - parameters, - type - ); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createMethodSignature( + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionToken: QuestionToken | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ) { + const node = createBaseSignatureDeclaration( + SyntaxKind.MethodSignature, + /*decorators*/ undefined, + modifiers, + name, + typeParameters, + parameters, + type + ); + node.questionToken = questionToken; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateIndexSignature( - node: IndexSignatureDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode - ) { - return node.parameters !== parameters - || node.type !== type - || node.decorators !== decorators - || node.modifiers !== modifiers - ? updateBaseSignatureDeclaration(createIndexSignature(decorators, modifiers, parameters, type), node) - : node; - } + // @api + function updateMethodSignature( + node: MethodSignature, + modifiers: readonly Modifier[] | undefined, + name: PropertyName, + questionToken: QuestionToken | undefined, + typeParameters: NodeArray | undefined, + parameters: NodeArray, + type: TypeNode | undefined + ) { + return node.modifiers !== modifiers + || node.name !== name + || node.questionToken !== questionToken + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? updateBaseSignatureDeclaration(createMethodSignature(modifiers, name, questionToken, typeParameters, parameters, type), node) + : node; + } - // @api - function createTemplateLiteralTypeSpan(type: TypeNode, literal: TemplateMiddle | TemplateTail) { - const node = createBaseNode(SyntaxKind.TemplateLiteralTypeSpan); - node.type = type; - node.literal = literal; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; + // @api + function createMethodDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: string | PropertyName, + questionToken: QuestionToken | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined + ) { + const node = createBaseFunctionLikeDeclaration( + SyntaxKind.MethodDeclaration, + decorators, + modifiers, + name, + typeParameters, + parameters, + type, + body + ); + node.asteriskToken = asteriskToken; + node.questionToken = questionToken; + node.transformFlags |= + propagateChildFlags(node.asteriskToken) | + propagateChildFlags(node.questionToken) | + TransformFlags.ContainsES2015; + if (questionToken) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } + if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { + if (asteriskToken) { + node.transformFlags |= TransformFlags.ContainsES2018; + } + else { + node.transformFlags |= TransformFlags.ContainsES2017; + } } - - // @api - function updateTemplateLiteralTypeSpan(node: TemplateLiteralTypeSpan, type: TypeNode, literal: TemplateMiddle | TemplateTail) { - return node.type !== type - || node.literal !== literal - ? update(createTemplateLiteralTypeSpan(type, literal), node) - : node; + else if (asteriskToken) { + node.transformFlags |= TransformFlags.ContainsGenerator; } + return node; + } - // - // Types - // + // @api + function updateMethodDeclaration( + node: MethodDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: PropertyName, + questionToken: QuestionToken | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.asteriskToken !== asteriskToken + || node.name !== name + || node.questionToken !== questionToken + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? updateBaseFunctionLikeDeclaration(createMethodDeclaration(decorators, modifiers, asteriskToken, name, questionToken, typeParameters, parameters, type, body), node) + : node; + } - // @api - function createKeywordTypeNode(kind: TKind) { - return createToken(kind); - } + // @api + function createClassStaticBlockDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + body: Block + ): ClassStaticBlockDeclaration { + const node = createBaseGenericNamedDeclaration( + SyntaxKind.ClassStaticBlockDeclaration, + decorators, + modifiers, + /*name*/ undefined, + /*typeParameters*/ undefined + ); + node.body = body; + node.transformFlags = propagateChildFlags(body) | TransformFlags.ContainsClassFields; + return node; + } - // @api - function createTypePredicateNode(assertsModifier: AssertsKeyword | undefined, parameterName: Identifier | ThisTypeNode | string, type: TypeNode | undefined) { - const node = createBaseNode(SyntaxKind.TypePredicate); - node.assertsModifier = assertsModifier; - node.parameterName = asName(parameterName); - node.type = type; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateClassStaticBlockDeclaration( + node: ClassStaticBlockDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + body: Block + ): ClassStaticBlockDeclaration { + return node.decorators !== decorators + || node.modifier !== modifiers + || node.body !== body + ? update(createClassStaticBlockDeclaration(decorators, modifiers, body), node) + : node; + } - // @api - function updateTypePredicateNode(node: TypePredicateNode, assertsModifier: AssertsKeyword | undefined, parameterName: Identifier | ThisTypeNode, type: TypeNode | undefined) { - return node.assertsModifier !== assertsModifier - || node.parameterName !== parameterName - || node.type !== type - ? update(createTypePredicateNode(assertsModifier, parameterName, type), node) - : node; - } + // @api + function createConstructorDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + parameters: readonly ParameterDeclaration[], + body: Block | undefined + ) { + const node = createBaseFunctionLikeDeclaration( + SyntaxKind.Constructor, + decorators, + modifiers, + /*name*/ undefined, + /*typeParameters*/ undefined, + parameters, + /*type*/ undefined, + body + ); + node.transformFlags |= TransformFlags.ContainsES2015; + return node; + } - // @api - function createTypeReferenceNode(typeName: string | EntityName, typeArguments: readonly TypeNode[] | undefined) { - const node = createBaseNode(SyntaxKind.TypeReference); - node.typeName = asName(typeName); - node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(createNodeArray(typeArguments)); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateConstructorDeclaration( + node: ConstructorDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + parameters: readonly ParameterDeclaration[], + body: Block | undefined + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.parameters !== parameters + || node.body !== body + ? updateBaseFunctionLikeDeclaration(createConstructorDeclaration(decorators, modifiers, parameters, body), node) + : node; + } - // @api - function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray | undefined) { - return node.typeName !== typeName - || node.typeArguments !== typeArguments - ? update(createTypeReferenceNode(typeName, typeArguments), node) - : node; - } + // @api + function createGetAccessorDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined + ) { + return createBaseFunctionLikeDeclaration( + SyntaxKind.GetAccessor, + decorators, + modifiers, + name, + /*typeParameters*/ undefined, + parameters, + type, + body + ); + } - // @api - function createFunctionTypeNode( - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ): FunctionTypeNode { - const node = createBaseSignatureDeclaration( - SyntaxKind.FunctionType, - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*name*/ undefined, - typeParameters, - parameters, - type - ); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateGetAccessorDeclaration( + node: GetAccessorDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: PropertyName, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? updateBaseFunctionLikeDeclaration(createGetAccessorDeclaration(decorators, modifiers, name, parameters, type, body), node) + : node; + } - // @api - function updateFunctionTypeNode( - node: FunctionTypeNode, - typeParameters: NodeArray | undefined, - parameters: NodeArray, - type: TypeNode | undefined - ) { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? updateBaseSignatureDeclaration(createFunctionTypeNode(typeParameters, parameters, type), node) - : node; - } + // @api + function createSetAccessorDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + parameters: readonly ParameterDeclaration[], + body: Block | undefined + ) { + return createBaseFunctionLikeDeclaration( + SyntaxKind.SetAccessor, + decorators, + modifiers, + name, + /*typeParameters*/ undefined, + parameters, + /*type*/ undefined, + body + ); + } - // @api - function createConstructorTypeNode(...args: Parameters) { - return args.length === 4 ? createConstructorTypeNode1(...args) : - args.length === 3 ? createConstructorTypeNode2(...args) : - Debug.fail("Incorrect number of arguments specified."); - } + // @api + function updateSetAccessorDeclaration( + node: SetAccessorDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: PropertyName, + parameters: readonly ParameterDeclaration[], + body: Block | undefined + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.parameters !== parameters + || node.body !== body + ? updateBaseFunctionLikeDeclaration(createSetAccessorDeclaration(decorators, modifiers, name, parameters, body), node) + : node; + } - function createConstructorTypeNode1( - modifiers: readonly Modifier[] | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ): ConstructorTypeNode { - const node = createBaseSignatureDeclaration( - SyntaxKind.ConstructorType, - /*decorators*/ undefined, - modifiers, - /*name*/ undefined, - typeParameters, - parameters, - type - ); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createCallSignature( + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ): CallSignatureDeclaration { + const node = createBaseSignatureDeclaration( + SyntaxKind.CallSignature, + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*name*/ undefined, + typeParameters, + parameters, + type + ); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - /** @deprecated */ - function createConstructorTypeNode2( - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ): ConstructorTypeNode { - return createConstructorTypeNode1(/*modifiers*/ undefined, typeParameters, parameters, type); - } + // @api + function updateCallSignature( + node: CallSignatureDeclaration, + typeParameters: NodeArray | undefined, + parameters: NodeArray, + type: TypeNode | undefined + ) { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? updateBaseSignatureDeclaration(createCallSignature(typeParameters, parameters, type), node) + : node; + } - // @api - function updateConstructorTypeNode(...args: Parameters) { - return args.length === 5 ? updateConstructorTypeNode1(...args) : - args.length === 4 ? updateConstructorTypeNode2(...args) : - Debug.fail("Incorrect number of arguments specified."); - } + // @api + function createConstructSignature( + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ): ConstructSignatureDeclaration { + const node = createBaseSignatureDeclaration( + SyntaxKind.ConstructSignature, + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*name*/ undefined, + typeParameters, + parameters, + type + ); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - function updateConstructorTypeNode1( - node: ConstructorTypeNode, - modifiers: readonly Modifier[] | undefined, - typeParameters: NodeArray | undefined, - parameters: NodeArray, - type: TypeNode | undefined - ) { - return node.modifiers !== modifiers - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? updateBaseSignatureDeclaration(createConstructorTypeNode(modifiers, typeParameters, parameters, type), node) - : node; - } + // @api + function updateConstructSignature( + node: ConstructSignatureDeclaration, + typeParameters: NodeArray | undefined, + parameters: NodeArray, + type: TypeNode | undefined + ) { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? updateBaseSignatureDeclaration(createConstructSignature(typeParameters, parameters, type), node) + : node; + } - /** @deprecated */ - function updateConstructorTypeNode2( - node: ConstructorTypeNode, - typeParameters: NodeArray | undefined, - parameters: NodeArray, - type: TypeNode | undefined - ) { - return updateConstructorTypeNode1(node, node.modifiers, typeParameters, parameters, type); - } + // @api + function createIndexSignature( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ): IndexSignatureDeclaration { + const node = createBaseSignatureDeclaration( + SyntaxKind.IndexSignature, + decorators, + modifiers, + /*name*/ undefined, + /*typeParameters*/ undefined, + parameters, + type + ); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createTypeQueryNode(exprName: EntityName) { - const node = createBaseNode(SyntaxKind.TypeQuery); - node.exprName = exprName; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateIndexSignature( + node: IndexSignatureDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode + ) { + return node.parameters !== parameters + || node.type !== type + || node.decorators !== decorators + || node.modifiers !== modifiers + ? updateBaseSignatureDeclaration(createIndexSignature(decorators, modifiers, parameters, type), node) + : node; + } - // @api - function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) { - return node.exprName !== exprName - ? update(createTypeQueryNode(exprName), node) - : node; - } + // @api + function createTemplateLiteralTypeSpan(type: TypeNode, literal: TemplateMiddle | TemplateTail) { + const node = createBaseNode(SyntaxKind.TemplateLiteralTypeSpan); + node.type = type; + node.literal = literal; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createTypeLiteralNode(members: readonly TypeElement[] | undefined) { - const node = createBaseNode(SyntaxKind.TypeLiteral); - node.members = createNodeArray(members); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateTemplateLiteralTypeSpan(node: TemplateLiteralTypeSpan, type: TypeNode, literal: TemplateMiddle | TemplateTail) { + return node.type !== type + || node.literal !== literal + ? update(createTemplateLiteralTypeSpan(type, literal), node) + : node; + } - // @api - function updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray) { - return node.members !== members - ? update(createTypeLiteralNode(members), node) - : node; - } + // + // Types + // - // @api - function createArrayTypeNode(elementType: TypeNode) { - const node = createBaseNode(SyntaxKind.ArrayType); - node.elementType = parenthesizerRules().parenthesizeElementTypeOfArrayType(elementType); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createKeywordTypeNode(kind: TKind) { + return createToken(kind); + } - // @api - function updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode { - return node.elementType !== elementType - ? update(createArrayTypeNode(elementType), node) - : node; - } + // @api + function createTypePredicateNode(assertsModifier: AssertsKeyword | undefined, parameterName: Identifier | ThisTypeNode | string, type: TypeNode | undefined) { + const node = createBaseNode(SyntaxKind.TypePredicate); + node.assertsModifier = assertsModifier; + node.parameterName = asName(parameterName); + node.type = type; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createTupleTypeNode(elements: readonly (TypeNode | NamedTupleMember)[]) { - const node = createBaseNode(SyntaxKind.TupleType); - node.elements = createNodeArray(elements); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateTypePredicateNode(node: TypePredicateNode, assertsModifier: AssertsKeyword | undefined, parameterName: Identifier | ThisTypeNode, type: TypeNode | undefined) { + return node.assertsModifier !== assertsModifier + || node.parameterName !== parameterName + || node.type !== type + ? update(createTypePredicateNode(assertsModifier, parameterName, type), node) + : node; + } - // @api - function updateTupleTypeNode(node: TupleTypeNode, elements: readonly (TypeNode | NamedTupleMember)[]) { - return node.elements !== elements - ? update(createTupleTypeNode(elements), node) - : node; - } + // @api + function createTypeReferenceNode(typeName: string | EntityName, typeArguments: readonly TypeNode[] | undefined) { + const node = createBaseNode(SyntaxKind.TypeReference); + node.typeName = asName(typeName); + node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(createNodeArray(typeArguments)); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createNamedTupleMember(dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode) { - const node = createBaseNode(SyntaxKind.NamedTupleMember); - node.dotDotDotToken = dotDotDotToken; - node.name = name; - node.questionToken = questionToken; - node.type = type; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray | undefined) { + return node.typeName !== typeName + || node.typeArguments !== typeArguments + ? update(createTypeReferenceNode(typeName, typeArguments), node) + : node; + } - // @api - function updateNamedTupleMember(node: NamedTupleMember, dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode) { - return node.dotDotDotToken !== dotDotDotToken - || node.name !== name - || node.questionToken !== questionToken - || node.type !== type - ? update(createNamedTupleMember(dotDotDotToken, name, questionToken, type), node) - : node; - } + // @api + function createFunctionTypeNode( + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ): FunctionTypeNode { + const node = createBaseSignatureDeclaration( + SyntaxKind.FunctionType, + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*name*/ undefined, + typeParameters, + parameters, + type + ); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createOptionalTypeNode(type: TypeNode) { - const node = createBaseNode(SyntaxKind.OptionalType); - node.type = parenthesizerRules().parenthesizeElementTypeOfArrayType(type); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateFunctionTypeNode( + node: FunctionTypeNode, + typeParameters: NodeArray | undefined, + parameters: NodeArray, + type: TypeNode | undefined + ) { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? updateBaseSignatureDeclaration(createFunctionTypeNode(typeParameters, parameters, type), node) + : node; + } - // @api - function updateOptionalTypeNode(node: OptionalTypeNode, type: TypeNode): OptionalTypeNode { - return node.type !== type - ? update(createOptionalTypeNode(type), node) - : node; - } + // @api + function createConstructorTypeNode(...args: Parameters) { + return args.length === 4 ? createConstructorTypeNode1(...args) : + args.length === 3 ? createConstructorTypeNode2(...args) : + Debug.fail("Incorrect number of arguments specified."); + } - // @api - function createRestTypeNode(type: TypeNode) { - const node = createBaseNode(SyntaxKind.RestType); - node.type = type; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + function createConstructorTypeNode1( + modifiers: readonly Modifier[] | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ): ConstructorTypeNode { + const node = createBaseSignatureDeclaration( + SyntaxKind.ConstructorType, + /*decorators*/ undefined, + modifiers, + /*name*/ undefined, + typeParameters, + parameters, + type + ); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateRestTypeNode(node: RestTypeNode, type: TypeNode): RestTypeNode { - return node.type !== type - ? update(createRestTypeNode(type), node) - : node; - } + /** @deprecated */ + function createConstructorTypeNode2( + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ): ConstructorTypeNode { + return createConstructorTypeNode1(/*modifiers*/ undefined, typeParameters, parameters, type); + } - function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: readonly TypeNode[]) { - const node = createBaseNode(kind); - node.types = parenthesizerRules().parenthesizeConstituentTypesOfUnionOrIntersectionType(types); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateConstructorTypeNode(...args: Parameters) { + return args.length === 5 ? updateConstructorTypeNode1(...args) : + args.length === 4 ? updateConstructorTypeNode2(...args) : + Debug.fail("Incorrect number of arguments specified."); + } - function updateUnionOrIntersectionTypeNode(node: T, types: NodeArray): T { - return node.types !== types - ? update(createUnionOrIntersectionTypeNode(node.kind, types) as T, node) - : node; - } + function updateConstructorTypeNode1( + node: ConstructorTypeNode, + modifiers: readonly Modifier[] | undefined, + typeParameters: NodeArray | undefined, + parameters: NodeArray, + type: TypeNode | undefined + ) { + return node.modifiers !== modifiers + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? updateBaseSignatureDeclaration(createConstructorTypeNode(modifiers, typeParameters, parameters, type), node) + : node; + } - // @api - function createUnionTypeNode(types: readonly TypeNode[]): UnionTypeNode { - return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, types) as UnionTypeNode; - } + /** @deprecated */ + function updateConstructorTypeNode2( + node: ConstructorTypeNode, + typeParameters: NodeArray | undefined, + parameters: NodeArray, + type: TypeNode | undefined + ) { + return updateConstructorTypeNode1(node, node.modifiers, typeParameters, parameters, type); + } - // @api - function updateUnionTypeNode(node: UnionTypeNode, types: NodeArray) { - return updateUnionOrIntersectionTypeNode(node, types); - } + // @api + function createTypeQueryNode(exprName: EntityName) { + const node = createBaseNode(SyntaxKind.TypeQuery); + node.exprName = exprName; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createIntersectionTypeNode(types: readonly TypeNode[]): IntersectionTypeNode { - return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, types) as IntersectionTypeNode; - } + // @api + function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) { + return node.exprName !== exprName + ? update(createTypeQueryNode(exprName), node) + : node; + } - // @api - function updateIntersectionTypeNode(node: IntersectionTypeNode, types: NodeArray) { - return updateUnionOrIntersectionTypeNode(node, types); - } + // @api + function createTypeLiteralNode(members: readonly TypeElement[] | undefined) { + const node = createBaseNode(SyntaxKind.TypeLiteral); + node.members = createNodeArray(members); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { - const node = createBaseNode(SyntaxKind.ConditionalType); - node.checkType = parenthesizerRules().parenthesizeMemberOfConditionalType(checkType); - node.extendsType = parenthesizerRules().parenthesizeMemberOfConditionalType(extendsType); - node.trueType = trueType; - node.falseType = falseType; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray) { + return node.members !== members + ? update(createTypeLiteralNode(members), node) + : node; + } - // @api - function updateConditionalTypeNode(node: ConditionalTypeNode, checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { - return node.checkType !== checkType - || node.extendsType !== extendsType - || node.trueType !== trueType - || node.falseType !== falseType - ? update(createConditionalTypeNode(checkType, extendsType, trueType, falseType), node) - : node; - } + // @api + function createArrayTypeNode(elementType: TypeNode) { + const node = createBaseNode(SyntaxKind.ArrayType); + node.elementType = parenthesizerRules().parenthesizeElementTypeOfArrayType(elementType); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createInferTypeNode(typeParameter: TypeParameterDeclaration) { - const node = createBaseNode(SyntaxKind.InferType); - node.typeParameter = typeParameter; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode { + return node.elementType !== elementType + ? update(createArrayTypeNode(elementType), node) + : node; + } - // @api - function updateInferTypeNode(node: InferTypeNode, typeParameter: TypeParameterDeclaration) { - return node.typeParameter !== typeParameter - ? update(createInferTypeNode(typeParameter), node) - : node; - } + // @api + function createTupleTypeNode(elements: readonly (TypeNode | NamedTupleMember)[]) { + const node = createBaseNode(SyntaxKind.TupleType); + node.elements = createNodeArray(elements); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createTemplateLiteralType(head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]) { - const node = createBaseNode(SyntaxKind.TemplateLiteralType); - node.head = head; - node.templateSpans = createNodeArray(templateSpans); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateTupleTypeNode(node: TupleTypeNode, elements: readonly (TypeNode | NamedTupleMember)[]) { + return node.elements !== elements + ? update(createTupleTypeNode(elements), node) + : node; + } - // @api - function updateTemplateLiteralType(node: TemplateLiteralTypeNode, head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]) { - return node.head !== head - || node.templateSpans !== templateSpans - ? update(createTemplateLiteralType(head, templateSpans), node) - : node; - } + // @api + function createNamedTupleMember(dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode) { + const node = createBaseNode(SyntaxKind.NamedTupleMember); + node.dotDotDotToken = dotDotDotToken; + node.name = name; + node.questionToken = questionToken; + node.type = type; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createImportTypeNode(argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf = false) { - const node = createBaseNode(SyntaxKind.ImportType); - node.argument = argument; - node.qualifier = qualifier; - node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments); - node.isTypeOf = isTypeOf; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateNamedTupleMember(node: NamedTupleMember, dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode) { + return node.dotDotDotToken !== dotDotDotToken + || node.name !== name + || node.questionToken !== questionToken + || node.type !== type + ? update(createNamedTupleMember(dotDotDotToken, name, questionToken, type), node) + : node; + } - // @api - function updateImportTypeNode(node: ImportTypeNode, argument: TypeNode, qualifier: EntityName | undefined, typeArguments: readonly TypeNode[] | undefined, isTypeOf = node.isTypeOf) { - return node.argument !== argument - || node.qualifier !== qualifier - || node.typeArguments !== typeArguments - || node.isTypeOf !== isTypeOf - ? update(createImportTypeNode(argument, qualifier, typeArguments, isTypeOf), node) - : node; - } + // @api + function createOptionalTypeNode(type: TypeNode) { + const node = createBaseNode(SyntaxKind.OptionalType); + node.type = parenthesizerRules().parenthesizeElementTypeOfArrayType(type); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createParenthesizedType(type: TypeNode) { - const node = createBaseNode(SyntaxKind.ParenthesizedType); - node.type = type; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateOptionalTypeNode(node: OptionalTypeNode, type: TypeNode): OptionalTypeNode { + return node.type !== type + ? update(createOptionalTypeNode(type), node) + : node; + } - // @api - function updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode) { - return node.type !== type - ? update(createParenthesizedType(type), node) - : node; - } + // @api + function createRestTypeNode(type: TypeNode) { + const node = createBaseNode(SyntaxKind.RestType); + node.type = type; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createThisTypeNode() { - const node = createBaseNode(SyntaxKind.ThisType); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateRestTypeNode(node: RestTypeNode, type: TypeNode): RestTypeNode { + return node.type !== type + ? update(createRestTypeNode(type), node) + : node; + } - // @api - function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode { - const node = createBaseNode(SyntaxKind.TypeOperator); - node.operator = operator; - node.type = parenthesizerRules().parenthesizeMemberOfElementType(type); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: readonly TypeNode[]) { + const node = createBaseNode(kind); + node.types = parenthesizerRules().parenthesizeConstituentTypesOfUnionOrIntersectionType(types); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode) { - return node.type !== type - ? update(createTypeOperatorNode(node.operator, type), node) - : node; - } + function updateUnionOrIntersectionTypeNode(node: T, types: NodeArray): T { + return node.types !== types + ? update(createUnionOrIntersectionTypeNode(node.kind, types) as T, node) + : node; + } - // @api - function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode) { - const node = createBaseNode(SyntaxKind.IndexedAccessType); - node.objectType = parenthesizerRules().parenthesizeMemberOfElementType(objectType); - node.indexType = indexType; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createUnionTypeNode(types: readonly TypeNode[]): UnionTypeNode { + return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, types) as UnionTypeNode; + } - // @api - function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode) { - return node.objectType !== objectType - || node.indexType !== indexType - ? update(createIndexedAccessTypeNode(objectType, indexType), node) - : node; - } + // @api + function updateUnionTypeNode(node: UnionTypeNode, types: NodeArray) { + return updateUnionOrIntersectionTypeNode(node, types); + } - // @api - function createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined, members: readonly TypeElement[] | undefined): MappedTypeNode { - const node = createBaseNode(SyntaxKind.MappedType); - node.readonlyToken = readonlyToken; - node.typeParameter = typeParameter; - node.nameType = nameType; - node.questionToken = questionToken; - node.type = type; - node.members = members && createNodeArray(members); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createIntersectionTypeNode(types: readonly TypeNode[]): IntersectionTypeNode { + return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, types) as IntersectionTypeNode; + } - // @api - function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined, members: NodeArray | undefined): MappedTypeNode { - return node.readonlyToken !== readonlyToken - || node.typeParameter !== typeParameter - || node.nameType !== nameType - || node.questionToken !== questionToken - || node.type !== type - || node.members !== members - ? update(createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), node) - : node; - } + // @api + function updateIntersectionTypeNode(node: IntersectionTypeNode, types: NodeArray) { + return updateUnionOrIntersectionTypeNode(node, types); + } - // @api - function createLiteralTypeNode(literal: LiteralTypeNode["literal"]) { - const node = createBaseNode(SyntaxKind.LiteralType); - node.literal = literal; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { + const node = createBaseNode(SyntaxKind.ConditionalType); + node.checkType = parenthesizerRules().parenthesizeMemberOfConditionalType(checkType); + node.extendsType = parenthesizerRules().parenthesizeMemberOfConditionalType(extendsType); + node.trueType = trueType; + node.falseType = falseType; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]) { - return node.literal !== literal - ? update(createLiteralTypeNode(literal), node) - : node; - } + // @api + function updateConditionalTypeNode(node: ConditionalTypeNode, checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { + return node.checkType !== checkType + || node.extendsType !== extendsType + || node.trueType !== trueType + || node.falseType !== falseType + ? update(createConditionalTypeNode(checkType, extendsType, trueType, falseType), node) + : node; + } - // - // Binding Patterns - // + // @api + function createInferTypeNode(typeParameter: TypeParameterDeclaration) { + const node = createBaseNode(SyntaxKind.InferType); + node.typeParameter = typeParameter; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createObjectBindingPattern(elements: readonly BindingElement[]) { - const node = createBaseNode(SyntaxKind.ObjectBindingPattern); - node.elements = createNodeArray(elements); - node.transformFlags |= - propagateChildrenFlags(node.elements) | - TransformFlags.ContainsES2015 | - TransformFlags.ContainsBindingPattern; - if (node.transformFlags & TransformFlags.ContainsRestOrSpread) { - node.transformFlags |= - TransformFlags.ContainsES2018 | - TransformFlags.ContainsObjectRestOrSpread; - } - return node; - } + // @api + function updateInferTypeNode(node: InferTypeNode, typeParameter: TypeParameterDeclaration) { + return node.typeParameter !== typeParameter + ? update(createInferTypeNode(typeParameter), node) + : node; + } - // @api - function updateObjectBindingPattern(node: ObjectBindingPattern, elements: readonly BindingElement[]) { - return node.elements !== elements - ? update(createObjectBindingPattern(elements), node) - : node; - } + // @api + function createTemplateLiteralType(head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]) { + const node = createBaseNode(SyntaxKind.TemplateLiteralType); + node.head = head; + node.templateSpans = createNodeArray(templateSpans); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createArrayBindingPattern(elements: readonly ArrayBindingElement[]) { - const node = createBaseNode(SyntaxKind.ArrayBindingPattern); - node.elements = createNodeArray(elements); - node.transformFlags |= - propagateChildrenFlags(node.elements) | - TransformFlags.ContainsES2015 | - TransformFlags.ContainsBindingPattern; - return node; - } + // @api + function updateTemplateLiteralType(node: TemplateLiteralTypeNode, head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]) { + return node.head !== head + || node.templateSpans !== templateSpans + ? update(createTemplateLiteralType(head, templateSpans), node) + : node; + } - // @api - function updateArrayBindingPattern(node: ArrayBindingPattern, elements: readonly ArrayBindingElement[]) { - return node.elements !== elements - ? update(createArrayBindingPattern(elements), node) - : node; - } + // @api + function createImportTypeNode(argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf = false) { + const node = createBaseNode(SyntaxKind.ImportType); + node.argument = argument; + node.qualifier = qualifier; + node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments); + node.isTypeOf = isTypeOf; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createBindingElement(dotDotDotToken: DotDotDotToken | undefined, propertyName: string | PropertyName | undefined, name: string | BindingName, initializer?: Expression) { - const node = createBaseBindingLikeDeclaration( - SyntaxKind.BindingElement, - /*decorators*/ undefined, - /*modifiers*/ undefined, - name, - initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) - ); - node.propertyName = asName(propertyName); - node.dotDotDotToken = dotDotDotToken; - node.transformFlags |= - propagateChildFlags(node.dotDotDotToken) | - TransformFlags.ContainsES2015; - if (node.propertyName) { - node.transformFlags |= isIdentifier(node.propertyName) ? - propagateIdentifierNameFlags(node.propertyName) : - propagateChildFlags(node.propertyName); - } - if (dotDotDotToken) node.transformFlags |= TransformFlags.ContainsRestOrSpread; - return node; - } + // @api + function updateImportTypeNode(node: ImportTypeNode, argument: TypeNode, qualifier: EntityName | undefined, typeArguments: readonly TypeNode[] | undefined, isTypeOf = node.isTypeOf) { + return node.argument !== argument + || node.qualifier !== qualifier + || node.typeArguments !== typeArguments + || node.isTypeOf !== isTypeOf + ? update(createImportTypeNode(argument, qualifier, typeArguments, isTypeOf), node) + : node; + } - // @api - function updateBindingElement(node: BindingElement, dotDotDotToken: DotDotDotToken | undefined, propertyName: PropertyName | undefined, name: BindingName, initializer: Expression | undefined) { - return node.propertyName !== propertyName - || node.dotDotDotToken !== dotDotDotToken - || node.name !== name - || node.initializer !== initializer - ? update(createBindingElement(dotDotDotToken, propertyName, name, initializer), node) - : node; - } + // @api + function createParenthesizedType(type: TypeNode) { + const node = createBaseNode(SyntaxKind.ParenthesizedType); + node.type = type; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // - // Expression - // + // @api + function updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode) { + return node.type !== type + ? update(createParenthesizedType(type), node) + : node; + } - function createBaseExpression(kind: T["kind"]) { - const node = createBaseNode(kind); - // the following properties are commonly set by the checker/binder - return node; - } + // @api + function createThisTypeNode() { + const node = createBaseNode(SyntaxKind.ThisType); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createArrayLiteralExpression(elements?: readonly Expression[], multiLine?: boolean) { - const node = createBaseExpression(SyntaxKind.ArrayLiteralExpression); - // Ensure we add a trailing comma for something like `[NumericLiteral(1), NumericLiteral(2), OmittedExpresion]` so that - // we end up with `[1, 2, ,]` instead of `[1, 2, ]` otherwise the `OmittedExpression` will just end up being treated like - // a trailing comma. - const lastElement = elements && lastOrUndefined(elements); - const elementsArray = createNodeArray(elements, lastElement && isOmittedExpression(lastElement) ? true : undefined); - node.elements = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(elementsArray); - node.multiLine = multiLine; - node.transformFlags |= propagateChildrenFlags(node.elements); - return node; - } + // @api + function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode { + const node = createBaseNode(SyntaxKind.TypeOperator); + node.operator = operator; + node.type = parenthesizerRules().parenthesizeMemberOfElementType(type); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateArrayLiteralExpression(node: ArrayLiteralExpression, elements: readonly Expression[]) { - return node.elements !== elements - ? update(createArrayLiteralExpression(elements, node.multiLine), node) - : node; - } + // @api + function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode) { + return node.type !== type + ? update(createTypeOperatorNode(node.operator, type), node) + : node; + } - // @api - function createObjectLiteralExpression(properties?: readonly ObjectLiteralElementLike[], multiLine?: boolean) { - const node = createBaseExpression(SyntaxKind.ObjectLiteralExpression); - node.properties = createNodeArray(properties); - node.multiLine = multiLine; - node.transformFlags |= propagateChildrenFlags(node.properties); - return node; - } + // @api + function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode) { + const node = createBaseNode(SyntaxKind.IndexedAccessType); + node.objectType = parenthesizerRules().parenthesizeMemberOfElementType(objectType); + node.indexType = indexType; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateObjectLiteralExpression(node: ObjectLiteralExpression, properties: readonly ObjectLiteralElementLike[]) { - return node.properties !== properties - ? update(createObjectLiteralExpression(properties, node.multiLine), node) - : node; - } + // @api + function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode) { + return node.objectType !== objectType + || node.indexType !== indexType + ? update(createIndexedAccessTypeNode(objectType, indexType), node) + : node; + } - // @api - function createPropertyAccessExpression(expression: Expression, name: string | Identifier | PrivateIdentifier) { - const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); - node.name = asName(name); - node.transformFlags = - propagateChildFlags(node.expression) | - (isIdentifier(node.name) ? - propagateIdentifierNameFlags(node.name) : - propagateChildFlags(node.name)); - if (isSuperKeyword(expression)) { - // super method calls require a lexical 'this' - // super method calls require 'super' hoisting in ES2017 and ES2018 async functions and async generators - node.transformFlags |= - TransformFlags.ContainsES2017 | - TransformFlags.ContainsES2018; - } - return node; - } + // @api + function createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined, members: readonly TypeElement[] | undefined): MappedTypeNode { + const node = createBaseNode(SyntaxKind.MappedType); + node.readonlyToken = readonlyToken; + node.typeParameter = typeParameter; + node.nameType = nameType; + node.questionToken = questionToken; + node.type = type; + node.members = members && createNodeArray(members); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updatePropertyAccessExpression(node: PropertyAccessExpression, expression: Expression, name: Identifier | PrivateIdentifier) { - if (isPropertyAccessChain(node)) { - return updatePropertyAccessChain(node, expression, node.questionDotToken, cast(name, isIdentifier)); - } - return node.expression !== expression - || node.name !== name - ? update(createPropertyAccessExpression(expression, name), node) - : node; - } + // @api + function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined, members: NodeArray | undefined): MappedTypeNode { + return node.readonlyToken !== readonlyToken + || node.typeParameter !== typeParameter + || node.nameType !== nameType + || node.questionToken !== questionToken + || node.type !== type + || node.members !== members + ? update(createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), node) + : node; + } - // @api - function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier | PrivateIdentifier) { - const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); - node.questionDotToken = questionDotToken; - node.name = asName(name); - node.transformFlags |= - TransformFlags.ContainsES2020 | - propagateChildFlags(node.expression) | - propagateChildFlags(node.questionDotToken) | - (isIdentifier(node.name) ? - propagateIdentifierNameFlags(node.name) : - propagateChildFlags(node.name)); - return node; - } + // @api + function createLiteralTypeNode(literal: LiteralTypeNode["literal"]) { + const node = createBaseNode(SyntaxKind.LiteralType); + node.literal = literal; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier | PrivateIdentifier) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a PropertyAccessExpression using updatePropertyAccessChain. Use updatePropertyAccess instead."); - // Because we are updating an existing PropertyAccessChain we want to inherit its emitFlags - // instead of using the default from createPropertyAccess - return node.expression !== expression - || node.questionDotToken !== questionDotToken - || node.name !== name - ? update(createPropertyAccessChain(expression, questionDotToken, name), node) - : node; - } + // @api + function updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]) { + return node.literal !== literal + ? update(createLiteralTypeNode(literal), node) + : node; + } - // @api - function createElementAccessExpression(expression: Expression, index: number | Expression) { - const node = createBaseExpression(SyntaxKind.ElementAccessExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); - node.argumentExpression = asExpression(index); + // + // Binding Patterns + // + + // @api + function createObjectBindingPattern(elements: readonly BindingElement[]) { + const node = createBaseNode(SyntaxKind.ObjectBindingPattern); + node.elements = createNodeArray(elements); + node.transformFlags |= + propagateChildrenFlags(node.elements) | + TransformFlags.ContainsES2015 | + TransformFlags.ContainsBindingPattern; + if (node.transformFlags & TransformFlags.ContainsRestOrSpread) { node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.argumentExpression); - if (isSuperKeyword(expression)) { - // super method calls require a lexical 'this' - // super method calls require 'super' hoisting in ES2017 and ES2018 async functions and async generators - node.transformFlags |= - TransformFlags.ContainsES2017 | - TransformFlags.ContainsES2018; - } - return node; + TransformFlags.ContainsES2018 | + TransformFlags.ContainsObjectRestOrSpread; } + return node; + } - // @api - function updateElementAccessExpression(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression) { - if (isElementAccessChain(node)) { - return updateElementAccessChain(node, expression, node.questionDotToken, argumentExpression); - } - return node.expression !== expression - || node.argumentExpression !== argumentExpression - ? update(createElementAccessExpression(expression, argumentExpression), node) - : node; - } + // @api + function updateObjectBindingPattern(node: ObjectBindingPattern, elements: readonly BindingElement[]) { + return node.elements !== elements + ? update(createObjectBindingPattern(elements), node) + : node; + } - // @api - function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) { - const node = createBaseExpression(SyntaxKind.ElementAccessExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); - node.questionDotToken = questionDotToken; - node.argumentExpression = asExpression(index); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.questionDotToken) | - propagateChildFlags(node.argumentExpression) | - TransformFlags.ContainsES2020; - return node; - } + // @api + function createArrayBindingPattern(elements: readonly ArrayBindingElement[]) { + const node = createBaseNode(SyntaxKind.ArrayBindingPattern); + node.elements = createNodeArray(elements); + node.transformFlags |= + propagateChildrenFlags(node.elements) | + TransformFlags.ContainsES2015 | + TransformFlags.ContainsBindingPattern; + return node; + } - // @api - function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a ElementAccessExpression using updateElementAccessChain. Use updateElementAccess instead."); - // Because we are updating an existing ElementAccessChain we want to inherit its emitFlags - // instead of using the default from createElementAccess - return node.expression !== expression - || node.questionDotToken !== questionDotToken - || node.argumentExpression !== argumentExpression - ? update(createElementAccessChain(expression, questionDotToken, argumentExpression), node) - : node; - } + // @api + function updateArrayBindingPattern(node: ArrayBindingPattern, elements: readonly ArrayBindingElement[]) { + return node.elements !== elements + ? update(createArrayBindingPattern(elements), node) + : node; + } - // @api - function createCallExpression(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - const node = createBaseExpression(SyntaxKind.CallExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); - node.typeArguments = asNodeArray(typeArguments); - node.arguments = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(createNodeArray(argumentsArray)); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildrenFlags(node.typeArguments) | - propagateChildrenFlags(node.arguments); - if (node.typeArguments) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - if (isImportKeyword(node.expression)) { - node.transformFlags |= TransformFlags.ContainsDynamicImport; - } - else if (isSuperProperty(node.expression)) { - node.transformFlags |= TransformFlags.ContainsLexicalThis; - } - return node; - } + // @api + function createBindingElement(dotDotDotToken: DotDotDotToken | undefined, propertyName: string | PropertyName | undefined, name: string | BindingName, initializer?: Expression) { + const node = createBaseBindingLikeDeclaration( + SyntaxKind.BindingElement, + /*decorators*/ undefined, + /*modifiers*/ undefined, + name, + initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) + ); + node.propertyName = asName(propertyName); + node.dotDotDotToken = dotDotDotToken; + node.transformFlags |= + propagateChildFlags(node.dotDotDotToken) | + TransformFlags.ContainsES2015; + if (node.propertyName) { + node.transformFlags |= isIdentifier(node.propertyName) ? + propagateIdentifierNameFlags(node.propertyName) : + propagateChildFlags(node.propertyName); + } + if (dotDotDotToken) node.transformFlags |= TransformFlags.ContainsRestOrSpread; + return node; + } - // @api - function updateCallExpression(node: CallExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { - if (isCallChain(node)) { - return updateCallChain(node, expression, node.questionDotToken, typeArguments, argumentsArray); - } - return node.expression !== expression - || node.typeArguments !== typeArguments - || node.arguments !== argumentsArray - ? update(createCallExpression(expression, typeArguments, argumentsArray), node) - : node; - } + // @api + function updateBindingElement(node: BindingElement, dotDotDotToken: DotDotDotToken | undefined, propertyName: PropertyName | undefined, name: BindingName, initializer: Expression | undefined) { + return node.propertyName !== propertyName + || node.dotDotDotToken !== dotDotDotToken + || node.name !== name + || node.initializer !== initializer + ? update(createBindingElement(dotDotDotToken, propertyName, name, initializer), node) + : node; + } - // @api - function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - const node = createBaseExpression(SyntaxKind.CallExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); - node.questionDotToken = questionDotToken; - node.typeArguments = asNodeArray(typeArguments); - node.arguments = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(createNodeArray(argumentsArray)); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.questionDotToken) | - propagateChildrenFlags(node.typeArguments) | - propagateChildrenFlags(node.arguments) | - TransformFlags.ContainsES2020; - if (node.typeArguments) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - if (isSuperProperty(node.expression)) { - node.transformFlags |= TransformFlags.ContainsLexicalThis; - } - return node; - } + // + // Expression + // - // @api - function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallExpression using updateCallChain. Use updateCall instead."); - return node.expression !== expression - || node.questionDotToken !== questionDotToken - || node.typeArguments !== typeArguments - || node.arguments !== argumentsArray - ? update(createCallChain(expression, questionDotToken, typeArguments, argumentsArray), node) - : node; - } + function createBaseExpression(kind: T["kind"]) { + const node = createBaseNode(kind); + // the following properties are commonly set by the checker/binder + return node; + } - // @api - function createNewExpression(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - const node = createBaseExpression(SyntaxKind.NewExpression); - node.expression = parenthesizerRules().parenthesizeExpressionOfNew(expression); - node.typeArguments = asNodeArray(typeArguments); - node.arguments = argumentsArray ? parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(argumentsArray) : undefined; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildrenFlags(node.typeArguments) | - propagateChildrenFlags(node.arguments) | - TransformFlags.ContainsES2020; - if (node.typeArguments) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - return node; - } + // @api + function createArrayLiteralExpression(elements?: readonly Expression[], multiLine?: boolean) { + const node = createBaseExpression(SyntaxKind.ArrayLiteralExpression); + // Ensure we add a trailing comma for something like `[NumericLiteral(1), NumericLiteral(2), OmittedExpresion]` so that + // we end up with `[1, 2, ,]` instead of `[1, 2, ]` otherwise the `OmittedExpression` will just end up being treated like + // a trailing comma. + const lastElement = elements && lastOrUndefined(elements); + const elementsArray = createNodeArray(elements, lastElement && isOmittedExpression(lastElement) ? true : undefined); + node.elements = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(elementsArray); + node.multiLine = multiLine; + node.transformFlags |= propagateChildrenFlags(node.elements); + return node; + } - // @api - function updateNewExpression(node: NewExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - return node.expression !== expression - || node.typeArguments !== typeArguments - || node.arguments !== argumentsArray - ? update(createNewExpression(expression, typeArguments, argumentsArray), node) - : node; - } + // @api + function updateArrayLiteralExpression(node: ArrayLiteralExpression, elements: readonly Expression[]) { + return node.elements !== elements + ? update(createArrayLiteralExpression(elements, node.multiLine), node) + : node; + } - // @api - function createTaggedTemplateExpression(tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral) { - const node = createBaseExpression(SyntaxKind.TaggedTemplateExpression); - node.tag = parenthesizerRules().parenthesizeLeftSideOfAccess(tag); - node.typeArguments = asNodeArray(typeArguments); - node.template = template; - node.transformFlags |= - propagateChildFlags(node.tag) | - propagateChildrenFlags(node.typeArguments) | - propagateChildFlags(node.template) | - TransformFlags.ContainsES2015; - if (node.typeArguments) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - if (hasInvalidEscape(node.template)) { - node.transformFlags |= TransformFlags.ContainsES2018; - } - return node; - } + // @api + function createObjectLiteralExpression(properties?: readonly ObjectLiteralElementLike[], multiLine?: boolean) { + const node = createBaseExpression(SyntaxKind.ObjectLiteralExpression); + node.properties = createNodeArray(properties); + node.multiLine = multiLine; + node.transformFlags |= propagateChildrenFlags(node.properties); + return node; + } - // @api - function updateTaggedTemplateExpression(node: TaggedTemplateExpression, tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral) { - return node.tag !== tag - || node.typeArguments !== typeArguments - || node.template !== template - ? update(createTaggedTemplateExpression(tag, typeArguments, template), node) - : node; - } + // @api + function updateObjectLiteralExpression(node: ObjectLiteralExpression, properties: readonly ObjectLiteralElementLike[]) { + return node.properties !== properties + ? update(createObjectLiteralExpression(properties, node.multiLine), node) + : node; + } - // @api - function createTypeAssertion(type: TypeNode, expression: Expression) { - const node = createBaseExpression(SyntaxKind.TypeAssertionExpression); - node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); - node.type = type; + // @api + function createPropertyAccessExpression(expression: Expression, name: string | Identifier | PrivateIdentifier) { + const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.name = asName(name); + node.transformFlags = + propagateChildFlags(node.expression) | + (isIdentifier(node.name) ? + propagateIdentifierNameFlags(node.name) : + propagateChildFlags(node.name)); + if (isSuperKeyword(expression)) { + // super method calls require a lexical 'this' + // super method calls require 'super' hoisting in ES2017 and ES2018 async functions and async generators node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.type) | - TransformFlags.ContainsTypeScript; - return node; + TransformFlags.ContainsES2017 | + TransformFlags.ContainsES2018; } + return node; + } - // @api - function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression) { - return node.type !== type - || node.expression !== expression - ? update(createTypeAssertion(type, expression), node) - : node; + // @api + function updatePropertyAccessExpression(node: PropertyAccessExpression, expression: Expression, name: Identifier | PrivateIdentifier) { + if (isPropertyAccessChain(node)) { + return updatePropertyAccessChain(node, expression, node.questionDotToken, cast(name, isIdentifier)); } + return node.expression !== expression + || node.name !== name + ? update(createPropertyAccessExpression(expression, name), node) + : node; + } - // @api - function createParenthesizedExpression(expression: Expression) { - const node = createBaseExpression(SyntaxKind.ParenthesizedExpression); - node.expression = expression; - node.transformFlags = propagateChildFlags(node.expression); - return node; - } + // @api + function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier | PrivateIdentifier) { + const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.questionDotToken = questionDotToken; + node.name = asName(name); + node.transformFlags |= + TransformFlags.ContainsES2020 | + propagateChildFlags(node.expression) | + propagateChildFlags(node.questionDotToken) | + (isIdentifier(node.name) ? + propagateIdentifierNameFlags(node.name) : + propagateChildFlags(node.name)); + return node; + } - // @api - function updateParenthesizedExpression(node: ParenthesizedExpression, expression: Expression) { - return node.expression !== expression - ? update(createParenthesizedExpression(expression), node) - : node; - } + // @api + function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier | PrivateIdentifier) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a PropertyAccessExpression using updatePropertyAccessChain. Use updatePropertyAccess instead."); + // Because we are updating an existing PropertyAccessChain we want to inherit its emitFlags + // instead of using the default from createPropertyAccess + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.name !== name + ? update(createPropertyAccessChain(expression, questionDotToken, name), node) + : node; + } - // @api - function createFunctionExpression( - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[] | undefined, - type: TypeNode | undefined, - body: Block - ) { - const node = createBaseFunctionLikeDeclaration( - SyntaxKind.FunctionExpression, - /*decorators*/ undefined, - modifiers, - name, - typeParameters, - parameters, - type, - body - ); - node.asteriskToken = asteriskToken; - node.transformFlags |= propagateChildFlags(node.asteriskToken); - if (node.typeParameters) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { - if (node.asteriskToken) { - node.transformFlags |= TransformFlags.ContainsES2018; - } - else { - node.transformFlags |= TransformFlags.ContainsES2017; - } - } - else if (node.asteriskToken) { - node.transformFlags |= TransformFlags.ContainsGenerator; - } - return node; + // @api + function createElementAccessExpression(expression: Expression, index: number | Expression) { + const node = createBaseExpression(SyntaxKind.ElementAccessExpression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.argumentExpression = asExpression(index); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.argumentExpression); + if (isSuperKeyword(expression)) { + // super method calls require a lexical 'this' + // super method calls require 'super' hoisting in ES2017 and ES2018 async functions and async generators + node.transformFlags |= + TransformFlags.ContainsES2017 | + TransformFlags.ContainsES2018; } + return node; + } - // @api - function updateFunctionExpression( - node: FunctionExpression, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block - ) { - return node.name !== name - || node.modifiers !== modifiers - || node.asteriskToken !== asteriskToken - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? updateBaseFunctionLikeDeclaration(createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) - : node; + // @api + function updateElementAccessExpression(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression) { + if (isElementAccessChain(node)) { + return updateElementAccessChain(node, expression, node.questionDotToken, argumentExpression); } + return node.expression !== expression + || node.argumentExpression !== argumentExpression + ? update(createElementAccessExpression(expression, argumentExpression), node) + : node; + } - // @api - function createArrowFunction( - modifiers: readonly Modifier[] | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - equalsGreaterThanToken: EqualsGreaterThanToken | undefined, - body: ConciseBody - ) { - const node = createBaseFunctionLikeDeclaration( - SyntaxKind.ArrowFunction, - /*decorators*/ undefined, - modifiers, - /*name*/ undefined, - typeParameters, - parameters, - type, - parenthesizerRules().parenthesizeConciseBodyOfArrowFunction(body) - ); - node.equalsGreaterThanToken = equalsGreaterThanToken ?? createToken(SyntaxKind.EqualsGreaterThanToken); - node.transformFlags |= - propagateChildFlags(node.equalsGreaterThanToken) | - TransformFlags.ContainsES2015; - if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { - node.transformFlags |= TransformFlags.ContainsES2017 | TransformFlags.ContainsLexicalThis; - } - return node; - } + // @api + function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) { + const node = createBaseExpression(SyntaxKind.ElementAccessExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.questionDotToken = questionDotToken; + node.argumentExpression = asExpression(index); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.questionDotToken) | + propagateChildFlags(node.argumentExpression) | + TransformFlags.ContainsES2020; + return node; + } - // @api - function updateArrowFunction( - node: ArrowFunction, - modifiers: readonly Modifier[] | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - equalsGreaterThanToken: EqualsGreaterThanToken, - body: ConciseBody - ): ArrowFunction { - return node.modifiers !== modifiers - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.equalsGreaterThanToken !== equalsGreaterThanToken - || node.body !== body - ? updateBaseFunctionLikeDeclaration(createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body), node) - : node; - } + // @api + function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a ElementAccessExpression using updateElementAccessChain. Use updateElementAccess instead."); + // Because we are updating an existing ElementAccessChain we want to inherit its emitFlags + // instead of using the default from createElementAccess + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.argumentExpression !== argumentExpression + ? update(createElementAccessChain(expression, questionDotToken, argumentExpression), node) + : node; + } - // @api - function createDeleteExpression(expression: Expression) { - const node = createBaseExpression(SyntaxKind.DeleteExpression); - node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); - node.transformFlags |= propagateChildFlags(node.expression); - return node; + // @api + function createCallExpression(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + const node = createBaseExpression(SyntaxKind.CallExpression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.typeArguments = asNodeArray(typeArguments); + node.arguments = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(createNodeArray(argumentsArray)); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildrenFlags(node.typeArguments) | + propagateChildrenFlags(node.arguments); + if (node.typeArguments) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } + if (isImportKeyword(node.expression)) { + node.transformFlags |= TransformFlags.ContainsDynamicImport; + } + else if (isSuperProperty(node.expression)) { + node.transformFlags |= TransformFlags.ContainsLexicalThis; } + return node; + } - // @api - function updateDeleteExpression(node: DeleteExpression, expression: Expression) { - return node.expression !== expression - ? update(createDeleteExpression(expression), node) - : node; + // @api + function updateCallExpression(node: CallExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { + if (isCallChain(node)) { + return updateCallChain(node, expression, node.questionDotToken, typeArguments, argumentsArray); } + return node.expression !== expression + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? update(createCallExpression(expression, typeArguments, argumentsArray), node) + : node; + } - // @api - function createTypeOfExpression(expression: Expression) { - const node = createBaseExpression(SyntaxKind.TypeOfExpression); - node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); - node.transformFlags |= propagateChildFlags(node.expression); - return node; + // @api + function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + const node = createBaseExpression(SyntaxKind.CallExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.questionDotToken = questionDotToken; + node.typeArguments = asNodeArray(typeArguments); + node.arguments = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(createNodeArray(argumentsArray)); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.questionDotToken) | + propagateChildrenFlags(node.typeArguments) | + propagateChildrenFlags(node.arguments) | + TransformFlags.ContainsES2020; + if (node.typeArguments) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } + if (isSuperProperty(node.expression)) { + node.transformFlags |= TransformFlags.ContainsLexicalThis; } + return node; + } - // @api - function updateTypeOfExpression(node: TypeOfExpression, expression: Expression) { - return node.expression !== expression - ? update(createTypeOfExpression(expression), node) - : node; - } + // @api + function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallExpression using updateCallChain. Use updateCall instead."); + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? update(createCallChain(expression, questionDotToken, typeArguments, argumentsArray), node) + : node; + } - // @api - function createVoidExpression(expression: Expression) { - const node = createBaseExpression(SyntaxKind.VoidExpression); - node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); - node.transformFlags |= propagateChildFlags(node.expression); - return node; + // @api + function createNewExpression(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + const node = createBaseExpression(SyntaxKind.NewExpression); + node.expression = parenthesizerRules().parenthesizeExpressionOfNew(expression); + node.typeArguments = asNodeArray(typeArguments); + node.arguments = argumentsArray ? parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(argumentsArray) : undefined; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildrenFlags(node.typeArguments) | + propagateChildrenFlags(node.arguments) | + TransformFlags.ContainsES2020; + if (node.typeArguments) { + node.transformFlags |= TransformFlags.ContainsTypeScript; } + return node; + } - // @api - function updateVoidExpression(node: VoidExpression, expression: Expression) { - return node.expression !== expression - ? update(createVoidExpression(expression), node) - : node; - } + // @api + function updateNewExpression(node: NewExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + return node.expression !== expression + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? update(createNewExpression(expression, typeArguments, argumentsArray), node) + : node; + } - // @api - function createAwaitExpression(expression: Expression) { - const node = createBaseExpression(SyntaxKind.AwaitExpression); - node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsES2017 | - TransformFlags.ContainsES2018 | - TransformFlags.ContainsAwait; - return node; + // @api + function createTaggedTemplateExpression(tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral) { + const node = createBaseExpression(SyntaxKind.TaggedTemplateExpression); + node.tag = parenthesizerRules().parenthesizeLeftSideOfAccess(tag); + node.typeArguments = asNodeArray(typeArguments); + node.template = template; + node.transformFlags |= + propagateChildFlags(node.tag) | + propagateChildrenFlags(node.typeArguments) | + propagateChildFlags(node.template) | + TransformFlags.ContainsES2015; + if (node.typeArguments) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } + if (hasInvalidEscape(node.template)) { + node.transformFlags |= TransformFlags.ContainsES2018; } + return node; + } - // @api - function updateAwaitExpression(node: AwaitExpression, expression: Expression) { - return node.expression !== expression - ? update(createAwaitExpression(expression), node) - : node; - } + // @api + function updateTaggedTemplateExpression(node: TaggedTemplateExpression, tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral) { + return node.tag !== tag + || node.typeArguments !== typeArguments + || node.template !== template + ? update(createTaggedTemplateExpression(tag, typeArguments, template), node) + : node; + } - // @api - function createPrefixUnaryExpression(operator: PrefixUnaryOperator, operand: Expression) { - const node = createBaseExpression(SyntaxKind.PrefixUnaryExpression); - node.operator = operator; - node.operand = parenthesizerRules().parenthesizeOperandOfPrefixUnary(operand); - node.transformFlags |= propagateChildFlags(node.operand); - // Only set this flag for non-generated identifiers and non-"local" names. See the - // comment in `visitPreOrPostfixUnaryExpression` in module.ts - if ((operator === SyntaxKind.PlusPlusToken || operator === SyntaxKind.MinusMinusToken) && - isIdentifier(node.operand) && - !isGeneratedIdentifier(node.operand) && - !isLocalName(node.operand)) { - node.transformFlags |= TransformFlags.ContainsUpdateExpressionForIdentifier; - } - return node; - } + // @api + function createTypeAssertion(type: TypeNode, expression: Expression) { + const node = createBaseExpression(SyntaxKind.TypeAssertionExpression); + node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); + node.type = type; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.type) | + TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updatePrefixUnaryExpression(node: PrefixUnaryExpression, operand: Expression) { - return node.operand !== operand - ? update(createPrefixUnaryExpression(node.operator, operand), node) - : node; - } + // @api + function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression) { + return node.type !== type + || node.expression !== expression + ? update(createTypeAssertion(type, expression), node) + : node; + } - // @api - function createPostfixUnaryExpression(operand: Expression, operator: PostfixUnaryOperator) { - const node = createBaseExpression(SyntaxKind.PostfixUnaryExpression); - node.operator = operator; - node.operand = parenthesizerRules().parenthesizeOperandOfPostfixUnary(operand); - node.transformFlags |= propagateChildFlags(node.operand); - // Only set this flag for non-generated identifiers and non-"local" names. See the - // comment in `visitPreOrPostfixUnaryExpression` in module.ts - if (isIdentifier(node.operand) && - !isGeneratedIdentifier(node.operand) && - !isLocalName(node.operand)) { - node.transformFlags |= TransformFlags.ContainsUpdateExpressionForIdentifier; - } - return node; - } + // @api + function createParenthesizedExpression(expression: Expression) { + const node = createBaseExpression(SyntaxKind.ParenthesizedExpression); + node.expression = expression; + node.transformFlags = propagateChildFlags(node.expression); + return node; + } - // @api - function updatePostfixUnaryExpression(node: PostfixUnaryExpression, operand: Expression) { - return node.operand !== operand - ? update(createPostfixUnaryExpression(operand, node.operator), node) - : node; - } + // @api + function updateParenthesizedExpression(node: ParenthesizedExpression, expression: Expression) { + return node.expression !== expression + ? update(createParenthesizedExpression(expression), node) + : node; + } - // @api - function createBinaryExpression(left: Expression, operator: BinaryOperator | BinaryOperatorToken, right: Expression) { - const node = createBaseExpression(SyntaxKind.BinaryExpression); - const operatorToken = asToken(operator); - const operatorKind = operatorToken.kind; - node.left = parenthesizerRules().parenthesizeLeftSideOfBinary(operatorKind, left); - node.operatorToken = operatorToken; - node.right = parenthesizerRules().parenthesizeRightSideOfBinary(operatorKind, node.left, right); - node.transformFlags |= - propagateChildFlags(node.left) | - propagateChildFlags(node.operatorToken) | - propagateChildFlags(node.right); - if (operatorKind === SyntaxKind.QuestionQuestionToken) { - node.transformFlags |= TransformFlags.ContainsES2020; - } - else if (operatorKind === SyntaxKind.EqualsToken) { - if (isObjectLiteralExpression(node.left)) { - node.transformFlags |= - TransformFlags.ContainsES2015 | - TransformFlags.ContainsES2018 | - TransformFlags.ContainsDestructuringAssignment | - propagateAssignmentPatternFlags(node.left); - } - else if (isArrayLiteralExpression(node.left)) { - node.transformFlags |= - TransformFlags.ContainsES2015 | - TransformFlags.ContainsDestructuringAssignment | - propagateAssignmentPatternFlags(node.left); - } - } - else if (operatorKind === SyntaxKind.AsteriskAsteriskToken || operatorKind === SyntaxKind.AsteriskAsteriskEqualsToken) { - node.transformFlags |= TransformFlags.ContainsES2016; + // @api + function createFunctionExpression( + modifiers: readonly Modifier[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[] | undefined, + type: TypeNode | undefined, + body: Block + ) { + const node = createBaseFunctionLikeDeclaration( + SyntaxKind.FunctionExpression, + /*decorators*/ undefined, + modifiers, + name, + typeParameters, + parameters, + type, + body + ); + node.asteriskToken = asteriskToken; + node.transformFlags |= propagateChildFlags(node.asteriskToken); + if (node.typeParameters) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } + if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { + if (node.asteriskToken) { + node.transformFlags |= TransformFlags.ContainsES2018; } - else if (isLogicalOrCoalescingAssignmentOperator(operatorKind)) { - node.transformFlags |= TransformFlags.ContainsES2021; + else { + node.transformFlags |= TransformFlags.ContainsES2017; } - return node; } - - function propagateAssignmentPatternFlags(node: AssignmentPattern): TransformFlags { - if (node.transformFlags & TransformFlags.ContainsObjectRestOrSpread) return TransformFlags.ContainsObjectRestOrSpread; - if (node.transformFlags & TransformFlags.ContainsES2018) { - // check for nested spread assignments, otherwise '{ x: { a, ...b } = foo } = c' - // will not be correctly interpreted by the ES2018 transformer - for (const element of getElementsOfBindingOrAssignmentPattern(node)) { - const target = getTargetOfBindingOrAssignmentElement(element); - if (target && isAssignmentPattern(target)) { - if (target.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { - return TransformFlags.ContainsObjectRestOrSpread; - } - if (target.transformFlags & TransformFlags.ContainsES2018) { - const flags = propagateAssignmentPatternFlags(target); - if (flags) return flags; - } - } - } - } - return TransformFlags.None; + else if (node.asteriskToken) { + node.transformFlags |= TransformFlags.ContainsGenerator; } + return node; + } - // @api - function updateBinaryExpression(node: BinaryExpression, left: Expression, operator: BinaryOperatorToken, right: Expression) { - return node.left !== left - || node.operatorToken !== operator - || node.right !== right - ? update(createBinaryExpression(left, operator, right), node) - : node; - } + // @api + function updateFunctionExpression( + node: FunctionExpression, + modifiers: readonly Modifier[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block + ) { + return node.name !== name + || node.modifiers !== modifiers + || node.asteriskToken !== asteriskToken + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? updateBaseFunctionLikeDeclaration(createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) + : node; + } - // @api - function createConditionalExpression(condition: Expression, questionToken: QuestionToken | undefined, whenTrue: Expression, colonToken: ColonToken | undefined, whenFalse: Expression) { - const node = createBaseExpression(SyntaxKind.ConditionalExpression); - node.condition = parenthesizerRules().parenthesizeConditionOfConditionalExpression(condition); - node.questionToken = questionToken ?? createToken(SyntaxKind.QuestionToken); - node.whenTrue = parenthesizerRules().parenthesizeBranchOfConditionalExpression(whenTrue); - node.colonToken = colonToken ?? createToken(SyntaxKind.ColonToken); - node.whenFalse = parenthesizerRules().parenthesizeBranchOfConditionalExpression(whenFalse); - node.transformFlags |= - propagateChildFlags(node.condition) | - propagateChildFlags(node.questionToken) | - propagateChildFlags(node.whenTrue) | - propagateChildFlags(node.colonToken) | - propagateChildFlags(node.whenFalse); - return node; + // @api + function createArrowFunction( + modifiers: readonly Modifier[] | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + equalsGreaterThanToken: EqualsGreaterThanToken | undefined, + body: ConciseBody + ) { + const node = createBaseFunctionLikeDeclaration( + SyntaxKind.ArrowFunction, + /*decorators*/ undefined, + modifiers, + /*name*/ undefined, + typeParameters, + parameters, + type, + parenthesizerRules().parenthesizeConciseBodyOfArrowFunction(body) + ); + node.equalsGreaterThanToken = equalsGreaterThanToken ?? createToken(SyntaxKind.EqualsGreaterThanToken); + node.transformFlags |= + propagateChildFlags(node.equalsGreaterThanToken) | + TransformFlags.ContainsES2015; + if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { + node.transformFlags |= TransformFlags.ContainsES2017 | TransformFlags.ContainsLexicalThis; } + return node; + } - // @api - function updateConditionalExpression( - node: ConditionalExpression, - condition: Expression, - questionToken: Token, - whenTrue: Expression, - colonToken: Token, - whenFalse: Expression - ): ConditionalExpression { - return node.condition !== condition - || node.questionToken !== questionToken - || node.whenTrue !== whenTrue - || node.colonToken !== colonToken - || node.whenFalse !== whenFalse - ? update(createConditionalExpression(condition, questionToken, whenTrue, colonToken, whenFalse), node) - : node; - } + // @api + function updateArrowFunction( + node: ArrowFunction, + modifiers: readonly Modifier[] | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + equalsGreaterThanToken: EqualsGreaterThanToken, + body: ConciseBody + ): ArrowFunction { + return node.modifiers !== modifiers + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.equalsGreaterThanToken !== equalsGreaterThanToken + || node.body !== body + ? updateBaseFunctionLikeDeclaration(createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body), node) + : node; + } - // @api - function createTemplateExpression(head: TemplateHead, templateSpans: readonly TemplateSpan[]) { - const node = createBaseExpression(SyntaxKind.TemplateExpression); - node.head = head; - node.templateSpans = createNodeArray(templateSpans); - node.transformFlags |= - propagateChildFlags(node.head) | - propagateChildrenFlags(node.templateSpans) | - TransformFlags.ContainsES2015; - return node; - } + // @api + function createDeleteExpression(expression: Expression) { + const node = createBaseExpression(SyntaxKind.DeleteExpression); + node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); + node.transformFlags |= propagateChildFlags(node.expression); + return node; + } - // @api - function updateTemplateExpression(node: TemplateExpression, head: TemplateHead, templateSpans: readonly TemplateSpan[]) { - return node.head !== head - || node.templateSpans !== templateSpans - ? update(createTemplateExpression(head, templateSpans), node) - : node; - } + // @api + function updateDeleteExpression(node: DeleteExpression, expression: Expression) { + return node.expression !== expression + ? update(createDeleteExpression(expression), node) + : node; + } - function createTemplateLiteralLikeNodeChecked(kind: TemplateLiteralToken["kind"], text: string | undefined, rawText: string | undefined, templateFlags = TokenFlags.None) { - Debug.assert(!(templateFlags & ~TokenFlags.TemplateLiteralLikeFlags), "Unsupported template flags."); - // NOTE: without the assignment to `undefined`, we don't narrow the initial type of `cooked`. - // eslint-disable-next-line no-undef-init - let cooked: string | object | undefined = undefined; - if (rawText !== undefined && rawText !== text) { - cooked = getCookedText(kind, rawText); - if (typeof cooked === "object") { - return Debug.fail("Invalid raw text"); - } - } - if (text === undefined) { - if (cooked === undefined) { - return Debug.fail("Arguments 'text' and 'rawText' may not both be undefined."); - } - text = cooked; - } - else if (cooked !== undefined) { - Debug.assert(text === cooked, "Expected argument 'text' to be the normalized (i.e. 'cooked') version of argument 'rawText'."); - } - return createTemplateLiteralLikeNode(kind, text, rawText, templateFlags); - } + // @api + function createTypeOfExpression(expression: Expression) { + const node = createBaseExpression(SyntaxKind.TypeOfExpression); + node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); + node.transformFlags |= propagateChildFlags(node.expression); + return node; + } - // @api - function createTemplateLiteralLikeNode(kind: TemplateLiteralToken["kind"], text: string, rawText: string | undefined, templateFlags: TokenFlags | undefined) { - const node = createBaseToken(kind); - node.text = text; - node.rawText = rawText; - node.templateFlags = templateFlags! & TokenFlags.TemplateLiteralLikeFlags; - node.transformFlags |= TransformFlags.ContainsES2015; - if (node.templateFlags) { - node.transformFlags |= TransformFlags.ContainsES2018; - } - return node; + // @api + function updateTypeOfExpression(node: TypeOfExpression, expression: Expression) { + return node.expression !== expression + ? update(createTypeOfExpression(expression), node) + : node; + } + + // @api + function createVoidExpression(expression: Expression) { + const node = createBaseExpression(SyntaxKind.VoidExpression); + node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); + node.transformFlags |= propagateChildFlags(node.expression); + return node; + } + + // @api + function updateVoidExpression(node: VoidExpression, expression: Expression) { + return node.expression !== expression + ? update(createVoidExpression(expression), node) + : node; + } + + // @api + function createAwaitExpression(expression: Expression) { + const node = createBaseExpression(SyntaxKind.AwaitExpression); + node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsES2017 | + TransformFlags.ContainsES2018 | + TransformFlags.ContainsAwait; + return node; + } + + // @api + function updateAwaitExpression(node: AwaitExpression, expression: Expression) { + return node.expression !== expression + ? update(createAwaitExpression(expression), node) + : node; + } + + // @api + function createPrefixUnaryExpression(operator: PrefixUnaryOperator, operand: Expression) { + const node = createBaseExpression(SyntaxKind.PrefixUnaryExpression); + node.operator = operator; + node.operand = parenthesizerRules().parenthesizeOperandOfPrefixUnary(operand); + node.transformFlags |= propagateChildFlags(node.operand); + // Only set this flag for non-generated identifiers and non-"local" names. See the + // comment in `visitPreOrPostfixUnaryExpression` in module.ts + if ((operator === SyntaxKind.PlusPlusToken || operator === SyntaxKind.MinusMinusToken) && + isIdentifier(node.operand) && + !isGeneratedIdentifier(node.operand) && + !isLocalName(node.operand)) { + node.transformFlags |= TransformFlags.ContainsUpdateExpressionForIdentifier; } + return node; + } + + // @api + function updatePrefixUnaryExpression(node: PrefixUnaryExpression, operand: Expression) { + return node.operand !== operand + ? update(createPrefixUnaryExpression(node.operator, operand), node) + : node; + } - // @api - function createTemplateHead(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { - return createTemplateLiteralLikeNodeChecked(SyntaxKind.TemplateHead, text, rawText, templateFlags) as TemplateHead; + // @api + function createPostfixUnaryExpression(operand: Expression, operator: PostfixUnaryOperator) { + const node = createBaseExpression(SyntaxKind.PostfixUnaryExpression); + node.operator = operator; + node.operand = parenthesizerRules().parenthesizeOperandOfPostfixUnary(operand); + node.transformFlags |= propagateChildFlags(node.operand); + // Only set this flag for non-generated identifiers and non-"local" names. See the + // comment in `visitPreOrPostfixUnaryExpression` in module.ts + if (isIdentifier(node.operand) && + !isGeneratedIdentifier(node.operand) && + !isLocalName(node.operand)) { + node.transformFlags |= TransformFlags.ContainsUpdateExpressionForIdentifier; } + return node; + } - // @api - function createTemplateMiddle(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { - return createTemplateLiteralLikeNodeChecked(SyntaxKind.TemplateMiddle, text, rawText, templateFlags) as TemplateMiddle; - } + // @api + function updatePostfixUnaryExpression(node: PostfixUnaryExpression, operand: Expression) { + return node.operand !== operand + ? update(createPostfixUnaryExpression(operand, node.operator), node) + : node; + } - // @api - function createTemplateTail(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { - return createTemplateLiteralLikeNodeChecked(SyntaxKind.TemplateTail, text, rawText, templateFlags) as TemplateTail; + // @api + function createBinaryExpression(left: Expression, operator: BinaryOperator | BinaryOperatorToken, right: Expression) { + const node = createBaseExpression(SyntaxKind.BinaryExpression); + const operatorToken = asToken(operator); + const operatorKind = operatorToken.kind; + node.left = parenthesizerRules().parenthesizeLeftSideOfBinary(operatorKind, left); + node.operatorToken = operatorToken; + node.right = parenthesizerRules().parenthesizeRightSideOfBinary(operatorKind, node.left, right); + node.transformFlags |= + propagateChildFlags(node.left) | + propagateChildFlags(node.operatorToken) | + propagateChildFlags(node.right); + if (operatorKind === SyntaxKind.QuestionQuestionToken) { + node.transformFlags |= TransformFlags.ContainsES2020; + } + else if (operatorKind === SyntaxKind.EqualsToken) { + if (isObjectLiteralExpression(node.left)) { + node.transformFlags |= + TransformFlags.ContainsES2015 | + TransformFlags.ContainsES2018 | + TransformFlags.ContainsDestructuringAssignment | + propagateAssignmentPatternFlags(node.left); + } + else if (isArrayLiteralExpression(node.left)) { + node.transformFlags |= + TransformFlags.ContainsES2015 | + TransformFlags.ContainsDestructuringAssignment | + propagateAssignmentPatternFlags(node.left); + } } - - // @api - function createNoSubstitutionTemplateLiteral(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { - return createTemplateLiteralLikeNodeChecked(SyntaxKind.NoSubstitutionTemplateLiteral, text, rawText, templateFlags) as NoSubstitutionTemplateLiteral; + else if (operatorKind === SyntaxKind.AsteriskAsteriskToken || operatorKind === SyntaxKind.AsteriskAsteriskEqualsToken) { + node.transformFlags |= TransformFlags.ContainsES2016; } - - // @api - function createYieldExpression(asteriskToken: AsteriskToken | undefined, expression: Expression | undefined): YieldExpression { - Debug.assert(!asteriskToken || !!expression, "A `YieldExpression` with an asteriskToken must have an expression."); - const node = createBaseExpression(SyntaxKind.YieldExpression); - node.expression = expression && parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); - node.asteriskToken = asteriskToken; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.asteriskToken) | - TransformFlags.ContainsES2015 | - TransformFlags.ContainsES2018 | - TransformFlags.ContainsYield; - return node; + else if (isLogicalOrCoalescingAssignmentOperator(operatorKind)) { + node.transformFlags |= TransformFlags.ContainsES2021; } + return node; + } - // @api - function updateYieldExpression(node: YieldExpression, asteriskToken: AsteriskToken | undefined, expression: Expression) { - return node.expression !== expression - || node.asteriskToken !== asteriskToken - ? update(createYieldExpression(asteriskToken, expression), node) - : node; + function propagateAssignmentPatternFlags(node: AssignmentPattern): TransformFlags { + if (node.transformFlags & TransformFlags.ContainsObjectRestOrSpread) return TransformFlags.ContainsObjectRestOrSpread; + if (node.transformFlags & TransformFlags.ContainsES2018) { + // check for nested spread assignments, otherwise '{ x: { a, ...b } = foo } = c' + // will not be correctly interpreted by the ES2018 transformer + for (const element of getElementsOfBindingOrAssignmentPattern(node)) { + const target = getTargetOfBindingOrAssignmentElement(element); + if (target && isAssignmentPattern(target)) { + if (target.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { + return TransformFlags.ContainsObjectRestOrSpread; + } + if (target.transformFlags & TransformFlags.ContainsES2018) { + const flags = propagateAssignmentPatternFlags(target); + if (flags) return flags; + } + } + } } + return TransformFlags.None; + } - // @api - function createSpreadElement(expression: Expression) { - const node = createBaseExpression(SyntaxKind.SpreadElement); - node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsES2015 | - TransformFlags.ContainsRestOrSpread; - return node; - } + // @api + function updateBinaryExpression(node: BinaryExpression, left: Expression, operator: BinaryOperatorToken, right: Expression) { + return node.left !== left + || node.operatorToken !== operator + || node.right !== right + ? update(createBinaryExpression(left, operator, right), node) + : node; + } - // @api - function updateSpreadElement(node: SpreadElement, expression: Expression) { - return node.expression !== expression - ? update(createSpreadElement(expression), node) - : node; - } + // @api + function createConditionalExpression(condition: Expression, questionToken: QuestionToken | undefined, whenTrue: Expression, colonToken: ColonToken | undefined, whenFalse: Expression) { + const node = createBaseExpression(SyntaxKind.ConditionalExpression); + node.condition = parenthesizerRules().parenthesizeConditionOfConditionalExpression(condition); + node.questionToken = questionToken ?? createToken(SyntaxKind.QuestionToken); + node.whenTrue = parenthesizerRules().parenthesizeBranchOfConditionalExpression(whenTrue); + node.colonToken = colonToken ?? createToken(SyntaxKind.ColonToken); + node.whenFalse = parenthesizerRules().parenthesizeBranchOfConditionalExpression(whenFalse); + node.transformFlags |= + propagateChildFlags(node.condition) | + propagateChildFlags(node.questionToken) | + propagateChildFlags(node.whenTrue) | + propagateChildFlags(node.colonToken) | + propagateChildFlags(node.whenFalse); + return node; + } - // @api - function createClassExpression( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[] - ) { - const node = createBaseClassLikeDeclaration( - SyntaxKind.ClassExpression, - decorators, - modifiers, - name, - typeParameters, - heritageClauses, - members - ); - node.transformFlags |= TransformFlags.ContainsES2015; - return node; - } + // @api + function updateConditionalExpression( + node: ConditionalExpression, + condition: Expression, + questionToken: Token, + whenTrue: Expression, + colonToken: Token, + whenFalse: Expression + ): ConditionalExpression { + return node.condition !== condition + || node.questionToken !== questionToken + || node.whenTrue !== whenTrue + || node.colonToken !== colonToken + || node.whenFalse !== whenFalse + ? update(createConditionalExpression(condition, questionToken, whenTrue, colonToken, whenFalse), node) + : node; + } - // @api - function updateClassExpression( - node: ClassExpression, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[] - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.heritageClauses !== heritageClauses - || node.members !== members - ? update(createClassExpression(decorators, modifiers, name, typeParameters, heritageClauses, members), node) - : node; - } + // @api + function createTemplateExpression(head: TemplateHead, templateSpans: readonly TemplateSpan[]) { + const node = createBaseExpression(SyntaxKind.TemplateExpression); + node.head = head; + node.templateSpans = createNodeArray(templateSpans); + node.transformFlags |= + propagateChildFlags(node.head) | + propagateChildrenFlags(node.templateSpans) | + TransformFlags.ContainsES2015; + return node; + } - // @api - function createOmittedExpression() { - return createBaseExpression(SyntaxKind.OmittedExpression); - } + // @api + function updateTemplateExpression(node: TemplateExpression, head: TemplateHead, templateSpans: readonly TemplateSpan[]) { + return node.head !== head + || node.templateSpans !== templateSpans + ? update(createTemplateExpression(head, templateSpans), node) + : node; + } - // @api - function createExpressionWithTypeArguments(expression: Expression, typeArguments: readonly TypeNode[] | undefined) { - const node = createBaseNode(SyntaxKind.ExpressionWithTypeArguments); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); - node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildrenFlags(node.typeArguments) | - TransformFlags.ContainsES2015; - return node; + function createTemplateLiteralLikeNodeChecked(kind: TemplateLiteralToken["kind"], text: string | undefined, rawText: string | undefined, templateFlags = TokenFlags.None) { + Debug.assert(!(templateFlags & ~TokenFlags.TemplateLiteralLikeFlags), "Unsupported template flags."); + // NOTE: without the assignment to `undefined`, we don't narrow the initial type of `cooked`. + // eslint-disable-next-line no-undef-init + let cooked: string | object | undefined = undefined; + if (rawText !== undefined && rawText !== text) { + cooked = getCookedText(kind, rawText); + if (typeof cooked === "object") { + return Debug.fail("Invalid raw text"); + } } - - // @api - function updateExpressionWithTypeArguments(node: ExpressionWithTypeArguments, expression: Expression, typeArguments: readonly TypeNode[] | undefined) { - return node.expression !== expression - || node.typeArguments !== typeArguments - ? update(createExpressionWithTypeArguments(expression, typeArguments), node) - : node; + if (text === undefined) { + if (cooked === undefined) { + return Debug.fail("Arguments 'text' and 'rawText' may not both be undefined."); + } + text = cooked; } - - // @api - function createAsExpression(expression: Expression, type: TypeNode) { - const node = createBaseExpression(SyntaxKind.AsExpression); - node.expression = expression; - node.type = type; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.type) | - TransformFlags.ContainsTypeScript; - return node; + else if (cooked !== undefined) { + Debug.assert(text === cooked, "Expected argument 'text' to be the normalized (i.e. 'cooked') version of argument 'rawText'."); } + return createTemplateLiteralLikeNode(kind, text, rawText, templateFlags); + } - // @api - function updateAsExpression(node: AsExpression, expression: Expression, type: TypeNode) { - return node.expression !== expression - || node.type !== type - ? update(createAsExpression(expression, type), node) - : node; + // @api + function createTemplateLiteralLikeNode(kind: TemplateLiteralToken["kind"], text: string, rawText: string | undefined, templateFlags: TokenFlags | undefined) { + const node = createBaseToken(kind); + node.text = text; + node.rawText = rawText; + node.templateFlags = templateFlags! & TokenFlags.TemplateLiteralLikeFlags; + node.transformFlags |= TransformFlags.ContainsES2015; + if (node.templateFlags) { + node.transformFlags |= TransformFlags.ContainsES2018; } + return node; + } - // @api - function createNonNullExpression(expression: Expression) { - const node = createBaseExpression(SyntaxKind.NonNullExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createTemplateHead(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { + return createTemplateLiteralLikeNodeChecked(SyntaxKind.TemplateHead, text, rawText, templateFlags) as TemplateHead; + } - // @api - function updateNonNullExpression(node: NonNullExpression, expression: Expression) { - if (isNonNullChain(node)) { - return updateNonNullChain(node, expression); - } - return node.expression !== expression - ? update(createNonNullExpression(expression), node) - : node; - } + // @api + function createTemplateMiddle(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { + return createTemplateLiteralLikeNodeChecked(SyntaxKind.TemplateMiddle, text, rawText, templateFlags) as TemplateMiddle; + } - // @api - function createNonNullChain(expression: Expression) { - const node = createBaseExpression(SyntaxKind.NonNullExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createTemplateTail(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { + return createTemplateLiteralLikeNodeChecked(SyntaxKind.TemplateTail, text, rawText, templateFlags) as TemplateTail; + } - // @api - function updateNonNullChain(node: NonNullChain, expression: Expression) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a NonNullExpression using updateNonNullChain. Use updateNonNullExpression instead."); - return node.expression !== expression - ? update(createNonNullChain(expression), node) - : node; - } + // @api + function createNoSubstitutionTemplateLiteral(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { + return createTemplateLiteralLikeNodeChecked(SyntaxKind.NoSubstitutionTemplateLiteral, text, rawText, templateFlags) as NoSubstitutionTemplateLiteral; + } - // @api - function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier) { - const node = createBaseExpression(SyntaxKind.MetaProperty); - node.keywordToken = keywordToken; - node.name = name; - node.transformFlags |= propagateChildFlags(node.name); - switch (keywordToken) { - case SyntaxKind.NewKeyword: - node.transformFlags |= TransformFlags.ContainsES2015; - break; - case SyntaxKind.ImportKeyword: - node.transformFlags |= TransformFlags.ContainsESNext; - break; - default: - return Debug.assertNever(keywordToken); - } - return node; - } + // @api + function createYieldExpression(asteriskToken: AsteriskToken | undefined, expression: Expression | undefined): YieldExpression { + Debug.assert(!asteriskToken || !!expression, "A `YieldExpression` with an asteriskToken must have an expression."); + const node = createBaseExpression(SyntaxKind.YieldExpression); + node.expression = expression && parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.asteriskToken = asteriskToken; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.asteriskToken) | + TransformFlags.ContainsES2015 | + TransformFlags.ContainsES2018 | + TransformFlags.ContainsYield; + return node; + } - // @api - function updateMetaProperty(node: MetaProperty, name: Identifier) { - return node.name !== name - ? update(createMetaProperty(node.keywordToken, name), node) - : node; - } + // @api + function updateYieldExpression(node: YieldExpression, asteriskToken: AsteriskToken | undefined, expression: Expression) { + return node.expression !== expression + || node.asteriskToken !== asteriskToken + ? update(createYieldExpression(asteriskToken, expression), node) + : node; + } - // - // Misc - // + // @api + function createSpreadElement(expression: Expression) { + const node = createBaseExpression(SyntaxKind.SpreadElement); + node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsES2015 | + TransformFlags.ContainsRestOrSpread; + return node; + } - // @api - function createTemplateSpan(expression: Expression, literal: TemplateMiddle | TemplateTail) { - const node = createBaseNode(SyntaxKind.TemplateSpan); - node.expression = expression; - node.literal = literal; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.literal) | - TransformFlags.ContainsES2015; - return node; - } + // @api + function updateSpreadElement(node: SpreadElement, expression: Expression) { + return node.expression !== expression + ? update(createSpreadElement(expression), node) + : node; + } - // @api - function updateTemplateSpan(node: TemplateSpan, expression: Expression, literal: TemplateMiddle | TemplateTail) { - return node.expression !== expression - || node.literal !== literal - ? update(createTemplateSpan(expression, literal), node) - : node; - } + // @api + function createClassExpression( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[] + ) { + const node = createBaseClassLikeDeclaration( + SyntaxKind.ClassExpression, + decorators, + modifiers, + name, + typeParameters, + heritageClauses, + members + ); + node.transformFlags |= TransformFlags.ContainsES2015; + return node; + } - // @api - function createSemicolonClassElement() { - const node = createBaseNode(SyntaxKind.SemicolonClassElement); - node.transformFlags |= TransformFlags.ContainsES2015; - return node; - } + // @api + function updateClassExpression( + node: ClassExpression, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[] + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.heritageClauses !== heritageClauses + || node.members !== members + ? update(createClassExpression(decorators, modifiers, name, typeParameters, heritageClauses, members), node) + : node; + } - // - // Element - // + // @api + function createOmittedExpression() { + return createBaseExpression(SyntaxKind.OmittedExpression); + } - // @api - function createBlock(statements: readonly Statement[], multiLine?: boolean): Block { - const node = createBaseNode(SyntaxKind.Block); - node.statements = createNodeArray(statements); - node.multiLine = multiLine; - node.transformFlags |= propagateChildrenFlags(node.statements); - return node; - } + // @api + function createExpressionWithTypeArguments(expression: Expression, typeArguments: readonly TypeNode[] | undefined) { + const node = createBaseNode(SyntaxKind.ExpressionWithTypeArguments); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildrenFlags(node.typeArguments) | + TransformFlags.ContainsES2015; + return node; + } - // @api - function updateBlock(node: Block, statements: readonly Statement[]) { - return node.statements !== statements - ? update(createBlock(statements, node.multiLine), node) - : node; - } + // @api + function updateExpressionWithTypeArguments(node: ExpressionWithTypeArguments, expression: Expression, typeArguments: readonly TypeNode[] | undefined) { + return node.expression !== expression + || node.typeArguments !== typeArguments + ? update(createExpressionWithTypeArguments(expression, typeArguments), node) + : node; + } - // @api - function createVariableStatement(modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList | readonly VariableDeclaration[]) { - const node = createBaseDeclaration(SyntaxKind.VariableStatement, /*decorators*/ undefined, modifiers); - node.declarationList = isArray(declarationList) ? createVariableDeclarationList(declarationList) : declarationList; - node.transformFlags |= - propagateChildFlags(node.declarationList); - if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { - node.transformFlags = TransformFlags.ContainsTypeScript; - } - return node; - } + // @api + function createAsExpression(expression: Expression, type: TypeNode) { + const node = createBaseExpression(SyntaxKind.AsExpression); + node.expression = expression; + node.type = type; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.type) | + TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateVariableStatement(node: VariableStatement, modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList) { - return node.modifiers !== modifiers - || node.declarationList !== declarationList - ? update(createVariableStatement(modifiers, declarationList), node) - : node; - } + // @api + function updateAsExpression(node: AsExpression, expression: Expression, type: TypeNode) { + return node.expression !== expression + || node.type !== type + ? update(createAsExpression(expression, type), node) + : node; + } - // @api - function createEmptyStatement() { - return createBaseNode(SyntaxKind.EmptyStatement); - } + // @api + function createNonNullExpression(expression: Expression) { + const node = createBaseExpression(SyntaxKind.NonNullExpression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createExpressionStatement(expression: Expression): ExpressionStatement { - const node = createBaseNode(SyntaxKind.ExpressionStatement); - node.expression = parenthesizerRules().parenthesizeExpressionOfExpressionStatement(expression); - node.transformFlags |= propagateChildFlags(node.expression); - return node; + // @api + function updateNonNullExpression(node: NonNullExpression, expression: Expression) { + if (isNonNullChain(node)) { + return updateNonNullChain(node, expression); } + return node.expression !== expression + ? update(createNonNullExpression(expression), node) + : node; + } - // @api - function updateExpressionStatement(node: ExpressionStatement, expression: Expression) { - return node.expression !== expression - ? update(createExpressionStatement(expression), node) - : node; - } + // @api + function createNonNullChain(expression: Expression) { + const node = createBaseExpression(SyntaxKind.NonNullExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createIfStatement(expression: Expression, thenStatement: Statement, elseStatement?: Statement) { - const node = createBaseNode(SyntaxKind.IfStatement); - node.expression = expression; - node.thenStatement = asEmbeddedStatement(thenStatement); - node.elseStatement = asEmbeddedStatement(elseStatement); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.thenStatement) | - propagateChildFlags(node.elseStatement); - return node; - } + // @api + function updateNonNullChain(node: NonNullChain, expression: Expression) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a NonNullExpression using updateNonNullChain. Use updateNonNullExpression instead."); + return node.expression !== expression + ? update(createNonNullChain(expression), node) + : node; + } - // @api - function updateIfStatement(node: IfStatement, expression: Expression, thenStatement: Statement, elseStatement: Statement | undefined) { - return node.expression !== expression - || node.thenStatement !== thenStatement - || node.elseStatement !== elseStatement - ? update(createIfStatement(expression, thenStatement, elseStatement), node) - : node; + // @api + function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier) { + const node = createBaseExpression(SyntaxKind.MetaProperty); + node.keywordToken = keywordToken; + node.name = name; + node.transformFlags |= propagateChildFlags(node.name); + switch (keywordToken) { + case SyntaxKind.NewKeyword: + node.transformFlags |= TransformFlags.ContainsES2015; + break; + case SyntaxKind.ImportKeyword: + node.transformFlags |= TransformFlags.ContainsESNext; + break; + default: + return Debug.assertNever(keywordToken); } + return node; + } - // @api - function createDoStatement(statement: Statement, expression: Expression) { - const node = createBaseNode(SyntaxKind.DoStatement); - node.statement = asEmbeddedStatement(statement); - node.expression = expression; - node.transformFlags |= - propagateChildFlags(node.statement) | - propagateChildFlags(node.expression); - return node; - } + // @api + function updateMetaProperty(node: MetaProperty, name: Identifier) { + return node.name !== name + ? update(createMetaProperty(node.keywordToken, name), node) + : node; + } - // @api - function updateDoStatement(node: DoStatement, statement: Statement, expression: Expression) { - return node.statement !== statement - || node.expression !== expression - ? update(createDoStatement(statement, expression), node) - : node; - } + // + // Misc + // + + // @api + function createTemplateSpan(expression: Expression, literal: TemplateMiddle | TemplateTail) { + const node = createBaseNode(SyntaxKind.TemplateSpan); + node.expression = expression; + node.literal = literal; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.literal) | + TransformFlags.ContainsES2015; + return node; + } - // @api - function createWhileStatement(expression: Expression, statement: Statement) { - const node = createBaseNode(SyntaxKind.WhileStatement); - node.expression = expression; - node.statement = asEmbeddedStatement(statement); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.statement); - return node; - } + // @api + function updateTemplateSpan(node: TemplateSpan, expression: Expression, literal: TemplateMiddle | TemplateTail) { + return node.expression !== expression + || node.literal !== literal + ? update(createTemplateSpan(expression, literal), node) + : node; + } - // @api - function updateWhileStatement(node: WhileStatement, expression: Expression, statement: Statement) { - return node.expression !== expression - || node.statement !== statement - ? update(createWhileStatement(expression, statement), node) - : node; - } + // @api + function createSemicolonClassElement() { + const node = createBaseNode(SyntaxKind.SemicolonClassElement); + node.transformFlags |= TransformFlags.ContainsES2015; + return node; + } - // @api - function createForStatement(initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { - const node = createBaseNode(SyntaxKind.ForStatement); - node.initializer = initializer; - node.condition = condition; - node.incrementor = incrementor; - node.statement = asEmbeddedStatement(statement); - node.transformFlags |= - propagateChildFlags(node.initializer) | - propagateChildFlags(node.condition) | - propagateChildFlags(node.incrementor) | - propagateChildFlags(node.statement); - return node; - } + // + // Element + // - // @api - function updateForStatement(node: ForStatement, initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { - return node.initializer !== initializer - || node.condition !== condition - || node.incrementor !== incrementor - || node.statement !== statement - ? update(createForStatement(initializer, condition, incrementor, statement), node) - : node; - } + // @api + function createBlock(statements: readonly Statement[], multiLine?: boolean): Block { + const node = createBaseNode(SyntaxKind.Block); + node.statements = createNodeArray(statements); + node.multiLine = multiLine; + node.transformFlags |= propagateChildrenFlags(node.statements); + return node; + } - // @api - function createForInStatement(initializer: ForInitializer, expression: Expression, statement: Statement) { - const node = createBaseNode(SyntaxKind.ForInStatement); - node.initializer = initializer; - node.expression = expression; - node.statement = asEmbeddedStatement(statement); - node.transformFlags |= - propagateChildFlags(node.initializer) | - propagateChildFlags(node.expression) | - propagateChildFlags(node.statement); - return node; - } + // @api + function updateBlock(node: Block, statements: readonly Statement[]) { + return node.statements !== statements + ? update(createBlock(statements, node.multiLine), node) + : node; + } - // @api - function updateForInStatement(node: ForInStatement, initializer: ForInitializer, expression: Expression, statement: Statement) { - return node.initializer !== initializer - || node.expression !== expression - || node.statement !== statement - ? update(createForInStatement(initializer, expression, statement), node) - : node; + // @api + function createVariableStatement(modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList | readonly VariableDeclaration[]) { + const node = createBaseDeclaration(SyntaxKind.VariableStatement, /*decorators*/ undefined, modifiers); + node.declarationList = isArray(declarationList) ? createVariableDeclarationList(declarationList) : declarationList; + node.transformFlags |= + propagateChildFlags(node.declarationList); + if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { + node.transformFlags = TransformFlags.ContainsTypeScript; } + return node; + } - // @api - function createForOfStatement(awaitModifier: AwaitKeyword | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { - const node = createBaseNode(SyntaxKind.ForOfStatement); - node.awaitModifier = awaitModifier; - node.initializer = initializer; - node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); - node.statement = asEmbeddedStatement(statement); - node.transformFlags |= - propagateChildFlags(node.awaitModifier) | - propagateChildFlags(node.initializer) | - propagateChildFlags(node.expression) | - propagateChildFlags(node.statement) | - TransformFlags.ContainsES2015; - if (awaitModifier) node.transformFlags |= TransformFlags.ContainsES2018; - return node; - } + // @api + function updateVariableStatement(node: VariableStatement, modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList) { + return node.modifiers !== modifiers + || node.declarationList !== declarationList + ? update(createVariableStatement(modifiers, declarationList), node) + : node; + } - // @api - function updateForOfStatement(node: ForOfStatement, awaitModifier: AwaitKeyword | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { - return node.awaitModifier !== awaitModifier - || node.initializer !== initializer - || node.expression !== expression - || node.statement !== statement - ? update(createForOfStatement(awaitModifier, initializer, expression, statement), node) - : node; - } + // @api + function createEmptyStatement() { + return createBaseNode(SyntaxKind.EmptyStatement); + } - // @api - function createContinueStatement(label?: string | Identifier): ContinueStatement { - const node = createBaseNode(SyntaxKind.ContinueStatement); - node.label = asName(label); - node.transformFlags |= - propagateChildFlags(node.label) | - TransformFlags.ContainsHoistedDeclarationOrCompletion; - return node; - } + // @api + function createExpressionStatement(expression: Expression): ExpressionStatement { + const node = createBaseNode(SyntaxKind.ExpressionStatement); + node.expression = parenthesizerRules().parenthesizeExpressionOfExpressionStatement(expression); + node.transformFlags |= propagateChildFlags(node.expression); + return node; + } - // @api - function updateContinueStatement(node: ContinueStatement, label: Identifier | undefined) { - return node.label !== label - ? update(createContinueStatement(label), node) - : node; - } + // @api + function updateExpressionStatement(node: ExpressionStatement, expression: Expression) { + return node.expression !== expression + ? update(createExpressionStatement(expression), node) + : node; + } - // @api - function createBreakStatement(label?: string | Identifier): BreakStatement { - const node = createBaseNode(SyntaxKind.BreakStatement); - node.label = asName(label); - node.transformFlags |= - propagateChildFlags(node.label) | - TransformFlags.ContainsHoistedDeclarationOrCompletion; - return node; - } + // @api + function createIfStatement(expression: Expression, thenStatement: Statement, elseStatement?: Statement) { + const node = createBaseNode(SyntaxKind.IfStatement); + node.expression = expression; + node.thenStatement = asEmbeddedStatement(thenStatement); + node.elseStatement = asEmbeddedStatement(elseStatement); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.thenStatement) | + propagateChildFlags(node.elseStatement); + return node; + } - // @api - function updateBreakStatement(node: BreakStatement, label: Identifier | undefined) { - return node.label !== label - ? update(createBreakStatement(label), node) - : node; - } + // @api + function updateIfStatement(node: IfStatement, expression: Expression, thenStatement: Statement, elseStatement: Statement | undefined) { + return node.expression !== expression + || node.thenStatement !== thenStatement + || node.elseStatement !== elseStatement + ? update(createIfStatement(expression, thenStatement, elseStatement), node) + : node; + } - // @api - function createReturnStatement(expression?: Expression): ReturnStatement { - const node = createBaseNode(SyntaxKind.ReturnStatement); - node.expression = expression; - // return in an ES2018 async generator must be awaited - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsES2018 | - TransformFlags.ContainsHoistedDeclarationOrCompletion; - return node; - } + // @api + function createDoStatement(statement: Statement, expression: Expression) { + const node = createBaseNode(SyntaxKind.DoStatement); + node.statement = asEmbeddedStatement(statement); + node.expression = expression; + node.transformFlags |= + propagateChildFlags(node.statement) | + propagateChildFlags(node.expression); + return node; + } - // @api - function updateReturnStatement(node: ReturnStatement, expression: Expression | undefined) { - return node.expression !== expression - ? update(createReturnStatement(expression), node) - : node; - } + // @api + function updateDoStatement(node: DoStatement, statement: Statement, expression: Expression) { + return node.statement !== statement + || node.expression !== expression + ? update(createDoStatement(statement, expression), node) + : node; + } - // @api - function createWithStatement(expression: Expression, statement: Statement) { - const node = createBaseNode(SyntaxKind.WithStatement); - node.expression = expression; - node.statement = asEmbeddedStatement(statement); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.statement); - return node; - } + // @api + function createWhileStatement(expression: Expression, statement: Statement) { + const node = createBaseNode(SyntaxKind.WhileStatement); + node.expression = expression; + node.statement = asEmbeddedStatement(statement); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.statement); + return node; + } - // @api - function updateWithStatement(node: WithStatement, expression: Expression, statement: Statement) { - return node.expression !== expression - || node.statement !== statement - ? update(createWithStatement(expression, statement), node) - : node; - } + // @api + function updateWhileStatement(node: WhileStatement, expression: Expression, statement: Statement) { + return node.expression !== expression + || node.statement !== statement + ? update(createWhileStatement(expression, statement), node) + : node; + } - // @api - function createSwitchStatement(expression: Expression, caseBlock: CaseBlock): SwitchStatement { - const node = createBaseNode(SyntaxKind.SwitchStatement); - node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); - node.caseBlock = caseBlock; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.caseBlock); - return node; - } + // @api + function createForStatement(initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { + const node = createBaseNode(SyntaxKind.ForStatement); + node.initializer = initializer; + node.condition = condition; + node.incrementor = incrementor; + node.statement = asEmbeddedStatement(statement); + node.transformFlags |= + propagateChildFlags(node.initializer) | + propagateChildFlags(node.condition) | + propagateChildFlags(node.incrementor) | + propagateChildFlags(node.statement); + return node; + } - // @api - function updateSwitchStatement(node: SwitchStatement, expression: Expression, caseBlock: CaseBlock) { - return node.expression !== expression - || node.caseBlock !== caseBlock - ? update(createSwitchStatement(expression, caseBlock), node) - : node; - } + // @api + function updateForStatement(node: ForStatement, initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { + return node.initializer !== initializer + || node.condition !== condition + || node.incrementor !== incrementor + || node.statement !== statement + ? update(createForStatement(initializer, condition, incrementor, statement), node) + : node; + } - // @api - function createLabeledStatement(label: string | Identifier, statement: Statement) { - const node = createBaseNode(SyntaxKind.LabeledStatement); - node.label = asName(label); - node.statement = asEmbeddedStatement(statement); - node.transformFlags |= - propagateChildFlags(node.label) | - propagateChildFlags(node.statement); - return node; - } + // @api + function createForInStatement(initializer: ForInitializer, expression: Expression, statement: Statement) { + const node = createBaseNode(SyntaxKind.ForInStatement); + node.initializer = initializer; + node.expression = expression; + node.statement = asEmbeddedStatement(statement); + node.transformFlags |= + propagateChildFlags(node.initializer) | + propagateChildFlags(node.expression) | + propagateChildFlags(node.statement); + return node; + } - // @api - function updateLabeledStatement(node: LabeledStatement, label: Identifier, statement: Statement) { - return node.label !== label - || node.statement !== statement - ? update(createLabeledStatement(label, statement), node) - : node; - } + // @api + function updateForInStatement(node: ForInStatement, initializer: ForInitializer, expression: Expression, statement: Statement) { + return node.initializer !== initializer + || node.expression !== expression + || node.statement !== statement + ? update(createForInStatement(initializer, expression, statement), node) + : node; + } - // @api - function createThrowStatement(expression: Expression) { - const node = createBaseNode(SyntaxKind.ThrowStatement); - node.expression = expression; - node.transformFlags |= propagateChildFlags(node.expression); - return node; - } + // @api + function createForOfStatement(awaitModifier: AwaitKeyword | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { + const node = createBaseNode(SyntaxKind.ForOfStatement); + node.awaitModifier = awaitModifier; + node.initializer = initializer; + node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.statement = asEmbeddedStatement(statement); + node.transformFlags |= + propagateChildFlags(node.awaitModifier) | + propagateChildFlags(node.initializer) | + propagateChildFlags(node.expression) | + propagateChildFlags(node.statement) | + TransformFlags.ContainsES2015; + if (awaitModifier) node.transformFlags |= TransformFlags.ContainsES2018; + return node; + } - // @api - function updateThrowStatement(node: ThrowStatement, expression: Expression) { - return node.expression !== expression - ? update(createThrowStatement(expression), node) - : node; - } + // @api + function updateForOfStatement(node: ForOfStatement, awaitModifier: AwaitKeyword | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { + return node.awaitModifier !== awaitModifier + || node.initializer !== initializer + || node.expression !== expression + || node.statement !== statement + ? update(createForOfStatement(awaitModifier, initializer, expression, statement), node) + : node; + } - // @api - function createTryStatement(tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { - const node = createBaseNode(SyntaxKind.TryStatement); - node.tryBlock = tryBlock; - node.catchClause = catchClause; - node.finallyBlock = finallyBlock; - node.transformFlags |= - propagateChildFlags(node.tryBlock) | - propagateChildFlags(node.catchClause) | - propagateChildFlags(node.finallyBlock); - return node; - } + // @api + function createContinueStatement(label?: string | Identifier): ContinueStatement { + const node = createBaseNode(SyntaxKind.ContinueStatement); + node.label = asName(label); + node.transformFlags |= + propagateChildFlags(node.label) | + TransformFlags.ContainsHoistedDeclarationOrCompletion; + return node; + } - // @api - function updateTryStatement(node: TryStatement, tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { - return node.tryBlock !== tryBlock - || node.catchClause !== catchClause - || node.finallyBlock !== finallyBlock - ? update(createTryStatement(tryBlock, catchClause, finallyBlock), node) - : node; - } + // @api + function updateContinueStatement(node: ContinueStatement, label: Identifier | undefined) { + return node.label !== label + ? update(createContinueStatement(label), node) + : node; + } - // @api - function createDebuggerStatement() { - return createBaseNode(SyntaxKind.DebuggerStatement); - } + // @api + function createBreakStatement(label?: string | Identifier): BreakStatement { + const node = createBaseNode(SyntaxKind.BreakStatement); + node.label = asName(label); + node.transformFlags |= + propagateChildFlags(node.label) | + TransformFlags.ContainsHoistedDeclarationOrCompletion; + return node; + } - // @api - function createVariableDeclaration(name: string | BindingName, exclamationToken: ExclamationToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { - const node = createBaseVariableLikeDeclaration( - SyntaxKind.VariableDeclaration, - /*decorators*/ undefined, - /*modifiers*/ undefined, - name, - type, - initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) - ); - node.exclamationToken = exclamationToken; - node.transformFlags |= propagateChildFlags(node.exclamationToken); - if (exclamationToken) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - return node; - } + // @api + function updateBreakStatement(node: BreakStatement, label: Identifier | undefined) { + return node.label !== label + ? update(createBreakStatement(label), node) + : node; + } - // @api - function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, exclamationToken: ExclamationToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { - return node.name !== name - || node.type !== type - || node.exclamationToken !== exclamationToken - || node.initializer !== initializer - ? update(createVariableDeclaration(name, exclamationToken, type, initializer), node) - : node; - } + // @api + function createReturnStatement(expression?: Expression): ReturnStatement { + const node = createBaseNode(SyntaxKind.ReturnStatement); + node.expression = expression; + // return in an ES2018 async generator must be awaited + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsES2018 | + TransformFlags.ContainsHoistedDeclarationOrCompletion; + return node; + } - // @api - function createVariableDeclarationList(declarations: readonly VariableDeclaration[], flags = NodeFlags.None) { - const node = createBaseNode(SyntaxKind.VariableDeclarationList); - node.flags |= flags & NodeFlags.BlockScoped; - node.declarations = createNodeArray(declarations); - node.transformFlags |= - propagateChildrenFlags(node.declarations) | - TransformFlags.ContainsHoistedDeclarationOrCompletion; - if (flags & NodeFlags.BlockScoped) { - node.transformFlags |= - TransformFlags.ContainsES2015 | - TransformFlags.ContainsBlockScopedBinding; - } - return node; - } + // @api + function updateReturnStatement(node: ReturnStatement, expression: Expression | undefined) { + return node.expression !== expression + ? update(createReturnStatement(expression), node) + : node; + } - // @api - function updateVariableDeclarationList(node: VariableDeclarationList, declarations: readonly VariableDeclaration[]) { - return node.declarations !== declarations - ? update(createVariableDeclarationList(declarations, node.flags), node) - : node; - } + // @api + function createWithStatement(expression: Expression, statement: Statement) { + const node = createBaseNode(SyntaxKind.WithStatement); + node.expression = expression; + node.statement = asEmbeddedStatement(statement); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.statement); + return node; + } - // @api - function createFunctionDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined - ) { - const node = createBaseFunctionLikeDeclaration( - SyntaxKind.FunctionDeclaration, - decorators, - modifiers, - name, - typeParameters, - parameters, - type, - body - ); - node.asteriskToken = asteriskToken; - if (!node.body || modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { - node.transformFlags = TransformFlags.ContainsTypeScript; - } - else { - node.transformFlags |= - propagateChildFlags(node.asteriskToken) | - TransformFlags.ContainsHoistedDeclarationOrCompletion; - if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { - if (node.asteriskToken) { - node.transformFlags |= TransformFlags.ContainsES2018; - } - else { - node.transformFlags |= TransformFlags.ContainsES2017; - } - } - else if (node.asteriskToken) { - node.transformFlags |= TransformFlags.ContainsGenerator; - } - } - return node; - } + // @api + function updateWithStatement(node: WithStatement, expression: Expression, statement: Statement) { + return node.expression !== expression + || node.statement !== statement + ? update(createWithStatement(expression, statement), node) + : node; + } - // @api - function updateFunctionDeclaration( - node: FunctionDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.asteriskToken !== asteriskToken - || node.name !== name - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? updateBaseFunctionLikeDeclaration(createFunctionDeclaration(decorators, modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) - : node; - } + // @api + function createSwitchStatement(expression: Expression, caseBlock: CaseBlock): SwitchStatement { + const node = createBaseNode(SyntaxKind.SwitchStatement); + node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.caseBlock = caseBlock; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.caseBlock); + return node; + } - // @api - function createClassDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[] - ) { - const node = createBaseClassLikeDeclaration( - SyntaxKind.ClassDeclaration, - decorators, - modifiers, - name, - typeParameters, - heritageClauses, - members - ); - if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { - node.transformFlags = TransformFlags.ContainsTypeScript; - } - else { - node.transformFlags |= TransformFlags.ContainsES2015; - if (node.transformFlags & TransformFlags.ContainsTypeScriptClassSyntax) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - } - return node; - } + // @api + function updateSwitchStatement(node: SwitchStatement, expression: Expression, caseBlock: CaseBlock) { + return node.expression !== expression + || node.caseBlock !== caseBlock + ? update(createSwitchStatement(expression, caseBlock), node) + : node; + } - // @api - function updateClassDeclaration( - node: ClassDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[] - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.heritageClauses !== heritageClauses - || node.members !== members - ? update(createClassDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members), node) - : node; - } + // @api + function createLabeledStatement(label: string | Identifier, statement: Statement) { + const node = createBaseNode(SyntaxKind.LabeledStatement); + node.label = asName(label); + node.statement = asEmbeddedStatement(statement); + node.transformFlags |= + propagateChildFlags(node.label) | + propagateChildFlags(node.statement); + return node; + } - // @api - function createInterfaceDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly TypeElement[] - ) { - const node = createBaseInterfaceOrClassLikeDeclaration( - SyntaxKind.InterfaceDeclaration, - decorators, - modifiers, - name, - typeParameters, - heritageClauses - ); - node.members = createNodeArray(members); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateLabeledStatement(node: LabeledStatement, label: Identifier, statement: Statement) { + return node.label !== label + || node.statement !== statement + ? update(createLabeledStatement(label, statement), node) + : node; + } - // @api - function updateInterfaceDeclaration( - node: InterfaceDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly TypeElement[] - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.heritageClauses !== heritageClauses - || node.members !== members - ? update(createInterfaceDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members), node) - : node; - } + // @api + function createThrowStatement(expression: Expression) { + const node = createBaseNode(SyntaxKind.ThrowStatement); + node.expression = expression; + node.transformFlags |= propagateChildFlags(node.expression); + return node; + } - // @api - function createTypeAliasDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - type: TypeNode - ) { - const node = createBaseGenericNamedDeclaration( - SyntaxKind.TypeAliasDeclaration, - decorators, - modifiers, - name, - typeParameters - ); - node.type = type; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateThrowStatement(node: ThrowStatement, expression: Expression) { + return node.expression !== expression + ? update(createThrowStatement(expression), node) + : node; + } + + // @api + function createTryStatement(tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { + const node = createBaseNode(SyntaxKind.TryStatement); + node.tryBlock = tryBlock; + node.catchClause = catchClause; + node.finallyBlock = finallyBlock; + node.transformFlags |= + propagateChildFlags(node.tryBlock) | + propagateChildFlags(node.catchClause) | + propagateChildFlags(node.finallyBlock); + return node; + } - // @api - function updateTypeAliasDeclaration( - node: TypeAliasDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - type: TypeNode - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.type !== type - ? update(createTypeAliasDeclaration(decorators, modifiers, name, typeParameters, type), node) - : node; + // @api + function updateTryStatement(node: TryStatement, tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { + return node.tryBlock !== tryBlock + || node.catchClause !== catchClause + || node.finallyBlock !== finallyBlock + ? update(createTryStatement(tryBlock, catchClause, finallyBlock), node) + : node; + } + + // @api + function createDebuggerStatement() { + return createBaseNode(SyntaxKind.DebuggerStatement); + } + + // @api + function createVariableDeclaration(name: string | BindingName, exclamationToken: ExclamationToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { + const node = createBaseVariableLikeDeclaration( + SyntaxKind.VariableDeclaration, + /*decorators*/ undefined, + /*modifiers*/ undefined, + name, + type, + initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) + ); + node.exclamationToken = exclamationToken; + node.transformFlags |= propagateChildFlags(node.exclamationToken); + if (exclamationToken) { + node.transformFlags |= TransformFlags.ContainsTypeScript; } + return node; + } - // @api - function createEnumDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: string | Identifier, - members: readonly EnumMember[] - ) { - const node = createBaseNamedDeclaration( - SyntaxKind.EnumDeclaration, - decorators, - modifiers, - name - ); - node.members = createNodeArray(members); + // @api + function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, exclamationToken: ExclamationToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { + return node.name !== name + || node.type !== type + || node.exclamationToken !== exclamationToken + || node.initializer !== initializer + ? update(createVariableDeclaration(name, exclamationToken, type, initializer), node) + : node; + } + + // @api + function createVariableDeclarationList(declarations: readonly VariableDeclaration[], flags = NodeFlags.None) { + const node = createBaseNode(SyntaxKind.VariableDeclarationList); + node.flags |= flags & NodeFlags.BlockScoped; + node.declarations = createNodeArray(declarations); + node.transformFlags |= + propagateChildrenFlags(node.declarations) | + TransformFlags.ContainsHoistedDeclarationOrCompletion; + if (flags & NodeFlags.BlockScoped) { node.transformFlags |= - propagateChildrenFlags(node.members) | - TransformFlags.ContainsTypeScript; - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Enum declarations cannot contain `await` - return node; + TransformFlags.ContainsES2015 | + TransformFlags.ContainsBlockScopedBinding; } + return node; + } - // @api - function updateEnumDeclaration( - node: EnumDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: Identifier, - members: readonly EnumMember[]) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.members !== members - ? update(createEnumDeclaration(decorators, modifiers, name, members), node) - : node; - } + // @api + function updateVariableDeclarationList(node: VariableDeclarationList, declarations: readonly VariableDeclaration[]) { + return node.declarations !== declarations + ? update(createVariableDeclarationList(declarations, node.flags), node) + : node; + } - // @api - function createModuleDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: ModuleName, - body: ModuleBody | undefined, - flags = NodeFlags.None - ) { - const node = createBaseDeclaration( - SyntaxKind.ModuleDeclaration, - decorators, - modifiers - ); - node.flags |= flags & (NodeFlags.Namespace | NodeFlags.NestedNamespace | NodeFlags.GlobalAugmentation); - node.name = name; - node.body = body; - if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { - node.transformFlags = TransformFlags.ContainsTypeScript; + // @api + function createFunctionDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined + ) { + const node = createBaseFunctionLikeDeclaration( + SyntaxKind.FunctionDeclaration, + decorators, + modifiers, + name, + typeParameters, + parameters, + type, + body + ); + node.asteriskToken = asteriskToken; + if (!node.body || modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { + node.transformFlags = TransformFlags.ContainsTypeScript; + } + else { + node.transformFlags |= + propagateChildFlags(node.asteriskToken) | + TransformFlags.ContainsHoistedDeclarationOrCompletion; + if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { + if (node.asteriskToken) { + node.transformFlags |= TransformFlags.ContainsES2018; + } + else { + node.transformFlags |= TransformFlags.ContainsES2017; + } } - else { - node.transformFlags |= - propagateChildFlags(node.name) | - propagateChildFlags(node.body) | - TransformFlags.ContainsTypeScript; + else if (node.asteriskToken) { + node.transformFlags |= TransformFlags.ContainsGenerator; } - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Module declarations cannot contain `await`. - return node; } + return node; + } - // @api - function updateModuleDeclaration( - node: ModuleDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - name: ModuleName, - body: ModuleBody | undefined - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.name !== name - || node.body !== body - ? update(createModuleDeclaration(decorators, modifiers, name, body, node.flags), node) - : node; - } + // @api + function updateFunctionDeclaration( + node: FunctionDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.asteriskToken !== asteriskToken + || node.name !== name + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? updateBaseFunctionLikeDeclaration(createFunctionDeclaration(decorators, modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) + : node; + } - // @api - function createModuleBlock(statements: readonly Statement[]) { - const node = createBaseNode(SyntaxKind.ModuleBlock); - node.statements = createNodeArray(statements); - node.transformFlags |= propagateChildrenFlags(node.statements); - return node; + // @api + function createClassDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[] + ) { + const node = createBaseClassLikeDeclaration( + SyntaxKind.ClassDeclaration, + decorators, + modifiers, + name, + typeParameters, + heritageClauses, + members + ); + if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { + node.transformFlags = TransformFlags.ContainsTypeScript; } - - // @api - function updateModuleBlock(node: ModuleBlock, statements: readonly Statement[]) { - return node.statements !== statements - ? update(createModuleBlock(statements), node) - : node; + else { + node.transformFlags |= TransformFlags.ContainsES2015; + if (node.transformFlags & TransformFlags.ContainsTypeScriptClassSyntax) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } } + return node; + } - // @api - function createCaseBlock(clauses: readonly CaseOrDefaultClause[]): CaseBlock { - const node = createBaseNode(SyntaxKind.CaseBlock); - node.clauses = createNodeArray(clauses); - node.transformFlags |= propagateChildrenFlags(node.clauses); - return node; - } + // @api + function updateClassDeclaration( + node: ClassDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[] + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.heritageClauses !== heritageClauses + || node.members !== members + ? update(createClassDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members), node) + : node; + } - // @api - function updateCaseBlock(node: CaseBlock, clauses: readonly CaseOrDefaultClause[]) { - return node.clauses !== clauses - ? update(createCaseBlock(clauses), node) - : node; - } + // @api + function createInterfaceDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | Identifier, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly TypeElement[] + ) { + const node = createBaseInterfaceOrClassLikeDeclaration( + SyntaxKind.InterfaceDeclaration, + decorators, + modifiers, + name, + typeParameters, + heritageClauses + ); + node.members = createNodeArray(members); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createNamespaceExportDeclaration(name: string | Identifier) { - const node = createBaseNamedDeclaration( - SyntaxKind.NamespaceExportDeclaration, - /*decorators*/ undefined, - /*modifiers*/ undefined, - name - ); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateInterfaceDeclaration( + node: InterfaceDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly TypeElement[] + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.heritageClauses !== heritageClauses + || node.members !== members + ? update(createInterfaceDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members), node) + : node; + } - // @api - function updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier) { - return node.name !== name - ? update(createNamespaceExportDeclaration(name), node) - : node; - } + // @api + function createTypeAliasDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | Identifier, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + type: TypeNode + ) { + const node = createBaseGenericNamedDeclaration( + SyntaxKind.TypeAliasDeclaration, + decorators, + modifiers, + name, + typeParameters + ); + node.type = type; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createImportEqualsDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - isTypeOnly: boolean, - name: string | Identifier, - moduleReference: ModuleReference - ) { - const node = createBaseNamedDeclaration( - SyntaxKind.ImportEqualsDeclaration, - decorators, - modifiers, - name - ); - node.isTypeOnly = isTypeOnly; - node.moduleReference = moduleReference; - node.transformFlags |= propagateChildFlags(node.moduleReference); - if (!isExternalModuleReference(node.moduleReference)) node.transformFlags |= TransformFlags.ContainsTypeScript; - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Import= declaration is always parsed in an Await context - return node; - } + // @api + function updateTypeAliasDeclaration( + node: TypeAliasDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + type: TypeNode + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.type !== type + ? update(createTypeAliasDeclaration(decorators, modifiers, name, typeParameters, type), node) + : node; + } - // @api - function updateImportEqualsDeclaration( - node: ImportEqualsDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - isTypeOnly: boolean, - name: Identifier, - moduleReference: ModuleReference - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.isTypeOnly !== isTypeOnly - || node.name !== name - || node.moduleReference !== moduleReference - ? update(createImportEqualsDeclaration(decorators, modifiers, isTypeOnly, name, moduleReference), node) - : node; - } + // @api + function createEnumDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | Identifier, + members: readonly EnumMember[] + ) { + const node = createBaseNamedDeclaration( + SyntaxKind.EnumDeclaration, + decorators, + modifiers, + name + ); + node.members = createNodeArray(members); + node.transformFlags |= + propagateChildrenFlags(node.members) | + TransformFlags.ContainsTypeScript; + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Enum declarations cannot contain `await` + return node; + } - // @api - function createImportDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - importClause: ImportClause | undefined, - moduleSpecifier: Expression, - assertClause: AssertClause | undefined - ): ImportDeclaration { - const node = createBaseDeclaration( - SyntaxKind.ImportDeclaration, - decorators, - modifiers - ); - node.importClause = importClause; - node.moduleSpecifier = moduleSpecifier; - node.assertClause = assertClause; - node.transformFlags |= - propagateChildFlags(node.importClause) | - propagateChildFlags(node.moduleSpecifier); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function updateEnumDeclaration( + node: EnumDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: Identifier, + members: readonly EnumMember[]) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.members !== members + ? update(createEnumDeclaration(decorators, modifiers, name, members), node) + : node; + } - // @api - function updateImportDeclaration( - node: ImportDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - importClause: ImportClause | undefined, - moduleSpecifier: Expression, - assertClause: AssertClause | undefined - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.importClause !== importClause - || node.moduleSpecifier !== moduleSpecifier - || node.assertClause !== assertClause - ? update(createImportDeclaration(decorators, modifiers, importClause, moduleSpecifier, assertClause), node) - : node; + // @api + function createModuleDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: ModuleName, + body: ModuleBody | undefined, + flags = NodeFlags.None + ) { + const node = createBaseDeclaration( + SyntaxKind.ModuleDeclaration, + decorators, + modifiers + ); + node.flags |= flags & (NodeFlags.Namespace | NodeFlags.NestedNamespace | NodeFlags.GlobalAugmentation); + node.name = name; + node.body = body; + if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { + node.transformFlags = TransformFlags.ContainsTypeScript; } - - // @api - function createImportClause(isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause { - const node = createBaseNode(SyntaxKind.ImportClause); - node.isTypeOnly = isTypeOnly; - node.name = name; - node.namedBindings = namedBindings; + else { node.transformFlags |= propagateChildFlags(node.name) | - propagateChildFlags(node.namedBindings); - if (isTypeOnly) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; + propagateChildFlags(node.body) | + TransformFlags.ContainsTypeScript; } + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Module declarations cannot contain `await`. + return node; + } - // @api - function updateImportClause(node: ImportClause, isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined) { - return node.isTypeOnly !== isTypeOnly - || node.name !== name - || node.namedBindings !== namedBindings - ? update(createImportClause(isTypeOnly, name, namedBindings), node) - : node; - } + // @api + function updateModuleDeclaration( + node: ModuleDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: ModuleName, + body: ModuleBody | undefined + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.name !== name + || node.body !== body + ? update(createModuleDeclaration(decorators, modifiers, name, body, node.flags), node) + : node; + } - // @api - function createAssertClause(elements: NodeArray, multiLine?: boolean): AssertClause { - const node = createBaseNode(SyntaxKind.AssertClause); - node.elements = elements; - node.multiLine = multiLine; - node.transformFlags |= TransformFlags.ContainsESNext; - return node; - } + // @api + function createModuleBlock(statements: readonly Statement[]) { + const node = createBaseNode(SyntaxKind.ModuleBlock); + node.statements = createNodeArray(statements); + node.transformFlags |= propagateChildrenFlags(node.statements); + return node; + } - // @api - function updateAssertClause(node: AssertClause, elements: NodeArray, multiLine?: boolean): AssertClause { - return node.elements !== elements - || node.multiLine !== multiLine - ? update(createAssertClause(elements, multiLine), node) - : node; - } + // @api + function updateModuleBlock(node: ModuleBlock, statements: readonly Statement[]) { + return node.statements !== statements + ? update(createModuleBlock(statements), node) + : node; + } - // @api - function createAssertEntry(name: AssertionKey, value: StringLiteral): AssertEntry { - const node = createBaseNode(SyntaxKind.AssertEntry); - node.name = name; - node.value = value; - node.transformFlags |= TransformFlags.ContainsESNext; - return node; - } + // @api + function createCaseBlock(clauses: readonly CaseOrDefaultClause[]): CaseBlock { + const node = createBaseNode(SyntaxKind.CaseBlock); + node.clauses = createNodeArray(clauses); + node.transformFlags |= propagateChildrenFlags(node.clauses); + return node; + } - // @api - function updateAssertEntry(node: AssertEntry, name: AssertionKey, value: StringLiteral): AssertEntry { - return node.name !== name - || node.value !== value - ? update(createAssertEntry(name, value), node) - : node; - } + // @api + function updateCaseBlock(node: CaseBlock, clauses: readonly CaseOrDefaultClause[]) { + return node.clauses !== clauses + ? update(createCaseBlock(clauses), node) + : node; + } - // @api - function createNamespaceImport(name: Identifier): NamespaceImport { - const node = createBaseNode(SyntaxKind.NamespaceImport); - node.name = name; - node.transformFlags |= propagateChildFlags(node.name); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createNamespaceExportDeclaration(name: string | Identifier) { + const node = createBaseNamedDeclaration( + SyntaxKind.NamespaceExportDeclaration, + /*decorators*/ undefined, + /*modifiers*/ undefined, + name + ); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateNamespaceImport(node: NamespaceImport, name: Identifier) { - return node.name !== name - ? update(createNamespaceImport(name), node) - : node; - } + // @api + function updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier) { + return node.name !== name + ? update(createNamespaceExportDeclaration(name), node) + : node; + } - // @api - function createNamespaceExport(name: Identifier): NamespaceExport { - const node = createBaseNode(SyntaxKind.NamespaceExport); - node.name = name; - node.transformFlags |= - propagateChildFlags(node.name) | - TransformFlags.ContainsESNext; - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createImportEqualsDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + isTypeOnly: boolean, + name: string | Identifier, + moduleReference: ModuleReference + ) { + const node = createBaseNamedDeclaration( + SyntaxKind.ImportEqualsDeclaration, + decorators, + modifiers, + name + ); + node.isTypeOnly = isTypeOnly; + node.moduleReference = moduleReference; + node.transformFlags |= propagateChildFlags(node.moduleReference); + if (!isExternalModuleReference(node.moduleReference)) node.transformFlags |= TransformFlags.ContainsTypeScript; + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Import= declaration is always parsed in an Await context + return node; + } - // @api - function updateNamespaceExport(node: NamespaceExport, name: Identifier) { - return node.name !== name - ? update(createNamespaceExport(name), node) - : node; - } + // @api + function updateImportEqualsDeclaration( + node: ImportEqualsDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + isTypeOnly: boolean, + name: Identifier, + moduleReference: ModuleReference + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.isTypeOnly !== isTypeOnly + || node.name !== name + || node.moduleReference !== moduleReference + ? update(createImportEqualsDeclaration(decorators, modifiers, isTypeOnly, name, moduleReference), node) + : node; + } - // @api - function createNamedImports(elements: readonly ImportSpecifier[]): NamedImports { - const node = createBaseNode(SyntaxKind.NamedImports); - node.elements = createNodeArray(elements); - node.transformFlags |= propagateChildrenFlags(node.elements); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createImportDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + importClause: ImportClause | undefined, + moduleSpecifier: Expression, + assertClause: AssertClause | undefined + ): ImportDeclaration { + const node = createBaseDeclaration( + SyntaxKind.ImportDeclaration, + decorators, + modifiers + ); + node.importClause = importClause; + node.moduleSpecifier = moduleSpecifier; + node.assertClause = assertClause; + node.transformFlags |= + propagateChildFlags(node.importClause) | + propagateChildFlags(node.moduleSpecifier); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]) { - return node.elements !== elements - ? update(createNamedImports(elements), node) - : node; - } + // @api + function updateImportDeclaration( + node: ImportDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + importClause: ImportClause | undefined, + moduleSpecifier: Expression, + assertClause: AssertClause | undefined + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.importClause !== importClause + || node.moduleSpecifier !== moduleSpecifier + || node.assertClause !== assertClause + ? update(createImportDeclaration(decorators, modifiers, importClause, moduleSpecifier, assertClause), node) + : node; + } - // @api - function createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { - const node = createBaseNode(SyntaxKind.ImportSpecifier); - node.isTypeOnly = isTypeOnly; - node.propertyName = propertyName; - node.name = name; - node.transformFlags |= - propagateChildFlags(node.propertyName) | - propagateChildFlags(node.name); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createImportClause(isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause { + const node = createBaseNode(SyntaxKind.ImportClause); + node.isTypeOnly = isTypeOnly; + node.name = name; + node.namedBindings = namedBindings; + node.transformFlags |= + propagateChildFlags(node.name) | + propagateChildFlags(node.namedBindings); + if (isTypeOnly) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { - return node.isTypeOnly !== isTypeOnly - || node.propertyName !== propertyName - || node.name !== name - ? update(createImportSpecifier(isTypeOnly, propertyName, name), node) - : node; - } + // @api + function updateImportClause(node: ImportClause, isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined) { + return node.isTypeOnly !== isTypeOnly + || node.name !== name + || node.namedBindings !== namedBindings + ? update(createImportClause(isTypeOnly, name, namedBindings), node) + : node; + } - // @api - function createExportAssignment( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - isExportEquals: boolean | undefined, - expression: Expression - ) { - const node = createBaseDeclaration( - SyntaxKind.ExportAssignment, - decorators, - modifiers - ); - node.isExportEquals = isExportEquals; - node.expression = isExportEquals - ? parenthesizerRules().parenthesizeRightSideOfBinary(SyntaxKind.EqualsToken, /*leftSide*/ undefined, expression) - : parenthesizerRules().parenthesizeExpressionOfExportDefault(expression); - node.transformFlags |= propagateChildFlags(node.expression); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createAssertClause(elements: NodeArray, multiLine?: boolean): AssertClause { + const node = createBaseNode(SyntaxKind.AssertClause); + node.elements = elements; + node.multiLine = multiLine; + node.transformFlags |= TransformFlags.ContainsESNext; + return node; + } - // @api - function updateExportAssignment( - node: ExportAssignment, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - expression: Expression - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.expression !== expression - ? update(createExportAssignment(decorators, modifiers, node.isExportEquals, expression), node) - : node; - } + // @api + function updateAssertClause(node: AssertClause, elements: NodeArray, multiLine?: boolean): AssertClause { + return node.elements !== elements + || node.multiLine !== multiLine + ? update(createAssertClause(elements, multiLine), node) + : node; + } - // @api - function createExportDeclaration( - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - isTypeOnly: boolean, - exportClause: NamedExportBindings | undefined, - moduleSpecifier?: Expression, - assertClause?: AssertClause - ) { - const node = createBaseDeclaration( - SyntaxKind.ExportDeclaration, - decorators, - modifiers - ); - node.isTypeOnly = isTypeOnly; - node.exportClause = exportClause; - node.moduleSpecifier = moduleSpecifier; - node.assertClause = assertClause; - node.transformFlags |= - propagateChildFlags(node.exportClause) | - propagateChildFlags(node.moduleSpecifier); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createAssertEntry(name: AssertionKey, value: StringLiteral): AssertEntry { + const node = createBaseNode(SyntaxKind.AssertEntry); + node.name = name; + node.value = value; + node.transformFlags |= TransformFlags.ContainsESNext; + return node; + } - // @api - function updateExportDeclaration( - node: ExportDeclaration, - decorators: readonly Decorator[] | undefined, - modifiers: readonly Modifier[] | undefined, - isTypeOnly: boolean, - exportClause: NamedExportBindings | undefined, - moduleSpecifier: Expression | undefined, - assertClause: AssertClause | undefined - ) { - return node.decorators !== decorators - || node.modifiers !== modifiers - || node.isTypeOnly !== isTypeOnly - || node.exportClause !== exportClause - || node.moduleSpecifier !== moduleSpecifier - || node.assertClause !== assertClause - ? update(createExportDeclaration(decorators, modifiers, isTypeOnly, exportClause, moduleSpecifier, assertClause), node) - : node; - } + // @api + function updateAssertEntry(node: AssertEntry, name: AssertionKey, value: StringLiteral): AssertEntry { + return node.name !== name + || node.value !== value + ? update(createAssertEntry(name, value), node) + : node; + } - // @api - function createNamedExports(elements: readonly ExportSpecifier[]) { - const node = createBaseNode(SyntaxKind.NamedExports); - node.elements = createNodeArray(elements); - node.transformFlags |= propagateChildrenFlags(node.elements); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createNamespaceImport(name: Identifier): NamespaceImport { + const node = createBaseNode(SyntaxKind.NamespaceImport); + node.name = name; + node.transformFlags |= propagateChildFlags(node.name); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]) { - return node.elements !== elements - ? update(createNamedExports(elements), node) - : node; - } + // @api + function updateNamespaceImport(node: NamespaceImport, name: Identifier) { + return node.name !== name + ? update(createNamespaceImport(name), node) + : node; + } - // @api - function createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) { - const node = createBaseNode(SyntaxKind.ExportSpecifier); - node.isTypeOnly = isTypeOnly; - node.propertyName = asName(propertyName); - node.name = asName(name); - node.transformFlags |= - propagateChildFlags(node.propertyName) | - propagateChildFlags(node.name); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createNamespaceExport(name: Identifier): NamespaceExport { + const node = createBaseNode(SyntaxKind.NamespaceExport); + node.name = name; + node.transformFlags |= + propagateChildFlags(node.name) | + TransformFlags.ContainsESNext; + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { - return node.isTypeOnly !== isTypeOnly - || node.propertyName !== propertyName - || node.name !== name - ? update(createExportSpecifier(isTypeOnly, propertyName, name), node) - : node; - } + // @api + function updateNamespaceExport(node: NamespaceExport, name: Identifier) { + return node.name !== name + ? update(createNamespaceExport(name), node) + : node; + } - // @api - function createMissingDeclaration() { - const node = createBaseDeclaration( - SyntaxKind.MissingDeclaration, - /*decorators*/ undefined, - /*modifiers*/ undefined - ); - return node; - } + // @api + function createNamedImports(elements: readonly ImportSpecifier[]): NamedImports { + const node = createBaseNode(SyntaxKind.NamedImports); + node.elements = createNodeArray(elements); + node.transformFlags |= propagateChildrenFlags(node.elements); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // - // Module references - // + // @api + function updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]) { + return node.elements !== elements + ? update(createNamedImports(elements), node) + : node; + } - // @api - function createExternalModuleReference(expression: Expression) { - const node = createBaseNode(SyntaxKind.ExternalModuleReference); - node.expression = expression; - node.transformFlags |= propagateChildFlags(node.expression); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { + const node = createBaseNode(SyntaxKind.ImportSpecifier); + node.isTypeOnly = isTypeOnly; + node.propertyName = propertyName; + node.name = name; + node.transformFlags |= + propagateChildFlags(node.propertyName) | + propagateChildFlags(node.name); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression) { - return node.expression !== expression - ? update(createExternalModuleReference(expression), node) - : node; - } + // @api + function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { + return node.isTypeOnly !== isTypeOnly + || node.propertyName !== propertyName + || node.name !== name + ? update(createImportSpecifier(isTypeOnly, propertyName, name), node) + : node; + } - // - // JSDoc - // + // @api + function createExportAssignment( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + isExportEquals: boolean | undefined, + expression: Expression + ) { + const node = createBaseDeclaration( + SyntaxKind.ExportAssignment, + decorators, + modifiers + ); + node.isExportEquals = isExportEquals; + node.expression = isExportEquals + ? parenthesizerRules().parenthesizeRightSideOfBinary(SyntaxKind.EqualsToken, /*leftSide*/ undefined, expression) + : parenthesizerRules().parenthesizeExpressionOfExportDefault(expression); + node.transformFlags |= propagateChildFlags(node.expression); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - // createJSDocAllType - // createJSDocUnknownType - function createJSDocPrimaryTypeWorker(kind: T["kind"]) { - return createBaseNode(kind); - } + // @api + function updateExportAssignment( + node: ExportAssignment, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + expression: Expression + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.expression !== expression + ? update(createExportAssignment(decorators, modifiers, node.isExportEquals, expression), node) + : node; + } - // @api - // createJSDocNonNullableType - // createJSDocNullableType - // createJSDocOptionalType - // createJSDocVariadicType - // createJSDocNamepathType + // @api + function createExportDeclaration( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + isTypeOnly: boolean, + exportClause: NamedExportBindings | undefined, + moduleSpecifier?: Expression, + assertClause?: AssertClause + ) { + const node = createBaseDeclaration( + SyntaxKind.ExportDeclaration, + decorators, + modifiers + ); + node.isTypeOnly = isTypeOnly; + node.exportClause = exportClause; + node.moduleSpecifier = moduleSpecifier; + node.assertClause = assertClause; + node.transformFlags |= + propagateChildFlags(node.exportClause) | + propagateChildFlags(node.moduleSpecifier); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - function createJSDocUnaryTypeWorker(kind: T["kind"], type: T["type"]): T { - const node = createBaseNode(kind); - node.type = type; - return node; - } + // @api + function updateExportDeclaration( + node: ExportDeclaration, + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + isTypeOnly: boolean, + exportClause: NamedExportBindings | undefined, + moduleSpecifier: Expression | undefined, + assertClause: AssertClause | undefined + ) { + return node.decorators !== decorators + || node.modifiers !== modifiers + || node.isTypeOnly !== isTypeOnly + || node.exportClause !== exportClause + || node.moduleSpecifier !== moduleSpecifier + || node.assertClause !== assertClause + ? update(createExportDeclaration(decorators, modifiers, isTypeOnly, exportClause, moduleSpecifier, assertClause), node) + : node; + } - // @api - // updateJSDocNonNullableType - // updateJSDocNullableType - // updateJSDocOptionalType - // updateJSDocVariadicType - // updateJSDocNamepathType - function updateJSDocUnaryTypeWorker(kind: T["kind"], node: T, type: T["type"]): T { - return node.type !== type - ? update(createJSDocUnaryTypeWorker(kind, type), node) - : node; - } + // @api + function createNamedExports(elements: readonly ExportSpecifier[]) { + const node = createBaseNode(SyntaxKind.NamedExports); + node.elements = createNodeArray(elements); + node.transformFlags |= propagateChildrenFlags(node.elements); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function createJSDocFunctionType(parameters: readonly ParameterDeclaration[], type: TypeNode | undefined): JSDocFunctionType { - const node = createBaseSignatureDeclaration( - SyntaxKind.JSDocFunctionType, - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - parameters, - type - ); - return node; - } + // @api + function updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]) { + return node.elements !== elements + ? update(createNamedExports(elements), node) + : node; + } - // @api - function updateJSDocFunctionType(node: JSDocFunctionType, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined): JSDocFunctionType { - return node.parameters !== parameters - || node.type !== type - ? update(createJSDocFunctionType(parameters, type), node) - : node; - } + // @api + function createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) { + const node = createBaseNode(SyntaxKind.ExportSpecifier); + node.isTypeOnly = isTypeOnly; + node.propertyName = asName(propertyName); + node.name = asName(name); + node.transformFlags |= + propagateChildFlags(node.propertyName) | + propagateChildFlags(node.name); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function createJSDocTypeLiteral(propertyTags?: readonly JSDocPropertyLikeTag[], isArrayType = false): JSDocTypeLiteral { - const node = createBaseNode(SyntaxKind.JSDocTypeLiteral); - node.jsDocPropertyTags = asNodeArray(propertyTags); - node.isArrayType = isArrayType; - return node; - } + // @api + function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { + return node.isTypeOnly !== isTypeOnly + || node.propertyName !== propertyName + || node.name !== name + ? update(createExportSpecifier(isTypeOnly, propertyName, name), node) + : node; + } - // @api - function updateJSDocTypeLiteral(node: JSDocTypeLiteral, propertyTags: readonly JSDocPropertyLikeTag[] | undefined, isArrayType: boolean): JSDocTypeLiteral { - return node.jsDocPropertyTags !== propertyTags - || node.isArrayType !== isArrayType - ? update(createJSDocTypeLiteral(propertyTags, isArrayType), node) - : node; - } + // @api + function createMissingDeclaration() { + const node = createBaseDeclaration( + SyntaxKind.MissingDeclaration, + /*decorators*/ undefined, + /*modifiers*/ undefined + ); + return node; + } - // @api - function createJSDocTypeExpression(type: TypeNode): JSDocTypeExpression { - const node = createBaseNode(SyntaxKind.JSDocTypeExpression); - node.type = type; - return node; - } + // + // Module references + // - // @api - function updateJSDocTypeExpression(node: JSDocTypeExpression, type: TypeNode): JSDocTypeExpression { - return node.type !== type - ? update(createJSDocTypeExpression(type), node) - : node; - } + // @api + function createExternalModuleReference(expression: Expression) { + const node = createBaseNode(SyntaxKind.ExternalModuleReference); + node.expression = expression; + node.transformFlags |= propagateChildFlags(node.expression); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function createJSDocSignature(typeParameters: readonly JSDocTemplateTag[] | undefined, parameters: readonly JSDocParameterTag[], type?: JSDocReturnTag): JSDocSignature { - const node = createBaseNode(SyntaxKind.JSDocSignature); - node.typeParameters = asNodeArray(typeParameters); - node.parameters = createNodeArray(parameters); - node.type = type; - return node; - } + // @api + function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression) { + return node.expression !== expression + ? update(createExternalModuleReference(expression), node) + : node; + } - // @api - function updateJSDocSignature(node: JSDocSignature, typeParameters: readonly JSDocTemplateTag[] | undefined, parameters: readonly JSDocParameterTag[], type: JSDocReturnTag | undefined): JSDocSignature { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? update(createJSDocSignature(typeParameters, parameters, type), node) - : node; - } + // + // JSDoc + // - function getDefaultTagName(node: JSDocTag) { - const defaultTagName = getDefaultTagNameForKind(node.kind); - return node.tagName.escapedText === escapeLeadingUnderscores(defaultTagName) - ? node.tagName - : createIdentifier(defaultTagName); - } + // @api + // createJSDocAllType + // createJSDocUnknownType + function createJSDocPrimaryTypeWorker(kind: T["kind"]) { + return createBaseNode(kind); + } - // @api - function createBaseJSDocTag(kind: T["kind"], tagName: Identifier, comment: string | NodeArray | undefined) { - const node = createBaseNode(kind); - node.tagName = tagName; - node.comment = comment; - return node; - } + // @api + // createJSDocNonNullableType + // createJSDocNullableType + // createJSDocOptionalType + // createJSDocVariadicType + // createJSDocNamepathType - // @api - function createJSDocTemplateTag(tagName: Identifier | undefined, constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment?: string | NodeArray): JSDocTemplateTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocTemplateTag, tagName ?? createIdentifier("template"), comment); - node.constraint = constraint; - node.typeParameters = createNodeArray(typeParameters); - return node; - } + function createJSDocUnaryTypeWorker(kind: T["kind"], type: T["type"]): T { + const node = createBaseNode(kind); + node.type = type; + return node; + } - // @api - function updateJSDocTemplateTag(node: JSDocTemplateTag, tagName: Identifier = getDefaultTagName(node), constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment: string | NodeArray | undefined): JSDocTemplateTag { - return node.tagName !== tagName - || node.constraint !== constraint - || node.typeParameters !== typeParameters - || node.comment !== comment - ? update(createJSDocTemplateTag(tagName, constraint, typeParameters, comment), node) - : node; - } + // @api + // updateJSDocNonNullableType + // updateJSDocNullableType + // updateJSDocOptionalType + // updateJSDocVariadicType + // updateJSDocNamepathType + function updateJSDocUnaryTypeWorker(kind: T["kind"], node: T, type: T["type"]): T { + return node.type !== type + ? update(createJSDocUnaryTypeWorker(kind, type), node) + : node; + } - // @api - function createJSDocTypedefTag(tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray): JSDocTypedefTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocTypedefTag, tagName ?? createIdentifier("typedef"), comment); - node.typeExpression = typeExpression; - node.fullName = fullName; - node.name = getJSDocTypeAliasName(fullName); - return node; - } + // @api + function createJSDocFunctionType(parameters: readonly ParameterDeclaration[], type: TypeNode | undefined): JSDocFunctionType { + const node = createBaseSignatureDeclaration( + SyntaxKind.JSDocFunctionType, + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + parameters, + type + ); + return node; + } - // @api - function updateJSDocTypedefTag(node: JSDocTypedefTag, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocTypeExpression | undefined, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray | undefined): JSDocTypedefTag { - return node.tagName !== tagName - || node.typeExpression !== typeExpression - || node.fullName !== fullName - || node.comment !== comment - ? update(createJSDocTypedefTag(tagName, typeExpression, fullName, comment), node) - : node; - } + // @api + function updateJSDocFunctionType(node: JSDocFunctionType, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined): JSDocFunctionType { + return node.parameters !== parameters + || node.type !== type + ? update(createJSDocFunctionType(parameters, type), node) + : node; + } + + // @api + function createJSDocTypeLiteral(propertyTags?: readonly JSDocPropertyLikeTag[], isArrayType = false): JSDocTypeLiteral { + const node = createBaseNode(SyntaxKind.JSDocTypeLiteral); + node.jsDocPropertyTags = asNodeArray(propertyTags); + node.isArrayType = isArrayType; + return node; + } - // @api - function createJSDocParameterTag(tagName: Identifier | undefined, name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, isNameFirst?: boolean, comment?: string | NodeArray): JSDocParameterTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocParameterTag, tagName ?? createIdentifier("param"), comment); - node.typeExpression = typeExpression; - node.name = name; - node.isNameFirst = !!isNameFirst; - node.isBracketed = isBracketed; - return node; - } + // @api + function updateJSDocTypeLiteral(node: JSDocTypeLiteral, propertyTags: readonly JSDocPropertyLikeTag[] | undefined, isArrayType: boolean): JSDocTypeLiteral { + return node.jsDocPropertyTags !== propertyTags + || node.isArrayType !== isArrayType + ? update(createJSDocTypeLiteral(propertyTags, isArrayType), node) + : node; + } - // @api - function updateJSDocParameterTag(node: JSDocParameterTag, tagName: Identifier = getDefaultTagName(node), name: EntityName, isBracketed: boolean, typeExpression: JSDocTypeExpression | undefined, isNameFirst: boolean, comment: string | NodeArray | undefined): JSDocParameterTag { - return node.tagName !== tagName - || node.name !== name - || node.isBracketed !== isBracketed - || node.typeExpression !== typeExpression - || node.isNameFirst !== isNameFirst - || node.comment !== comment - ? update(createJSDocParameterTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment), node) - : node; - } + // @api + function createJSDocTypeExpression(type: TypeNode): JSDocTypeExpression { + const node = createBaseNode(SyntaxKind.JSDocTypeExpression); + node.type = type; + return node; + } - // @api - function createJSDocPropertyTag(tagName: Identifier | undefined, name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, isNameFirst?: boolean, comment?: string | NodeArray): JSDocPropertyTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocPropertyTag, tagName ?? createIdentifier("prop"), comment); - node.typeExpression = typeExpression; - node.name = name; - node.isNameFirst = !!isNameFirst; - node.isBracketed = isBracketed; - return node; - } + // @api + function updateJSDocTypeExpression(node: JSDocTypeExpression, type: TypeNode): JSDocTypeExpression { + return node.type !== type + ? update(createJSDocTypeExpression(type), node) + : node; + } - // @api - function updateJSDocPropertyTag(node: JSDocPropertyTag, tagName: Identifier = getDefaultTagName(node), name: EntityName, isBracketed: boolean, typeExpression: JSDocTypeExpression | undefined, isNameFirst: boolean, comment: string | NodeArray | undefined): JSDocPropertyTag { - return node.tagName !== tagName - || node.name !== name - || node.isBracketed !== isBracketed - || node.typeExpression !== typeExpression - || node.isNameFirst !== isNameFirst - || node.comment !== comment - ? update(createJSDocPropertyTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment), node) - : node; - } + // @api + function createJSDocSignature(typeParameters: readonly JSDocTemplateTag[] | undefined, parameters: readonly JSDocParameterTag[], type?: JSDocReturnTag): JSDocSignature { + const node = createBaseNode(SyntaxKind.JSDocSignature); + node.typeParameters = asNodeArray(typeParameters); + node.parameters = createNodeArray(parameters); + node.type = type; + return node; + } - // @api - function createJSDocCallbackTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray): JSDocCallbackTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocCallbackTag, tagName ?? createIdentifier("callback"), comment); - node.typeExpression = typeExpression; - node.fullName = fullName; - node.name = getJSDocTypeAliasName(fullName); - return node; - } + // @api + function updateJSDocSignature(node: JSDocSignature, typeParameters: readonly JSDocTemplateTag[] | undefined, parameters: readonly JSDocParameterTag[], type: JSDocReturnTag | undefined): JSDocSignature { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? update(createJSDocSignature(typeParameters, parameters, type), node) + : node; + } - // @api - function updateJSDocCallbackTag(node: JSDocCallbackTag, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocSignature, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray | undefined): JSDocCallbackTag { - return node.tagName !== tagName - || node.typeExpression !== typeExpression - || node.fullName !== fullName - || node.comment !== comment - ? update(createJSDocCallbackTag(tagName, typeExpression, fullName, comment), node) - : node; - } + function getDefaultTagName(node: JSDocTag) { + const defaultTagName = getDefaultTagNameForKind(node.kind); + return node.tagName.escapedText === escapeLeadingUnderscores(defaultTagName) + ? node.tagName + : createIdentifier(defaultTagName); + } - // @api - function createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray): JSDocAugmentsTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocAugmentsTag, tagName ?? createIdentifier("augments"), comment); - node.class = className; - return node; - } + // @api + function createBaseJSDocTag(kind: T["kind"], tagName: Identifier, comment: string | NodeArray | undefined) { + const node = createBaseNode(kind); + node.tagName = tagName; + node.comment = comment; + return node; + } - // @api - function updateJSDocAugmentsTag(node: JSDocAugmentsTag, tagName: Identifier = getDefaultTagName(node), className: JSDocAugmentsTag["class"], comment: string | NodeArray | undefined): JSDocAugmentsTag { - return node.tagName !== tagName - || node.class !== className - || node.comment !== comment - ? update(createJSDocAugmentsTag(tagName, className, comment), node) - : node; - } + // @api + function createJSDocTemplateTag(tagName: Identifier | undefined, constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment?: string | NodeArray): JSDocTemplateTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocTemplateTag, tagName ?? createIdentifier("template"), comment); + node.constraint = constraint; + node.typeParameters = createNodeArray(typeParameters); + return node; + } - // @api - function createJSDocImplementsTag(tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment?: string | NodeArray): JSDocImplementsTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocImplementsTag, tagName ?? createIdentifier("implements"), comment); - node.class = className; - return node; - } + // @api + function updateJSDocTemplateTag(node: JSDocTemplateTag, tagName: Identifier = getDefaultTagName(node), constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment: string | NodeArray | undefined): JSDocTemplateTag { + return node.tagName !== tagName + || node.constraint !== constraint + || node.typeParameters !== typeParameters + || node.comment !== comment + ? update(createJSDocTemplateTag(tagName, constraint, typeParameters, comment), node) + : node; + } - // @api - function createJSDocSeeTag(tagName: Identifier | undefined, name: JSDocNameReference | undefined, comment?: string | NodeArray): JSDocSeeTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocSeeTag, tagName ?? createIdentifier("see"), comment); - node.name = name; - return node; - } + // @api + function createJSDocTypedefTag(tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray): JSDocTypedefTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocTypedefTag, tagName ?? createIdentifier("typedef"), comment); + node.typeExpression = typeExpression; + node.fullName = fullName; + node.name = getJSDocTypeAliasName(fullName); + return node; + } - // @api - function updateJSDocSeeTag(node: JSDocSeeTag, tagName: Identifier | undefined, name: JSDocNameReference | undefined, comment?: string | NodeArray): JSDocSeeTag { - return node.tagName !== tagName - || node.name !== name - || node.comment !== comment - ? update(createJSDocSeeTag(tagName, name, comment), node) - : node; - } + // @api + function updateJSDocTypedefTag(node: JSDocTypedefTag, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocTypeExpression | undefined, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray | undefined): JSDocTypedefTag { + return node.tagName !== tagName + || node.typeExpression !== typeExpression + || node.fullName !== fullName + || node.comment !== comment + ? update(createJSDocTypedefTag(tagName, typeExpression, fullName, comment), node) + : node; + } - // @api - function createJSDocNameReference(name: EntityName | JSDocMemberName): JSDocNameReference { - const node = createBaseNode(SyntaxKind.JSDocNameReference); - node.name = name; - return node; - } + // @api + function createJSDocParameterTag(tagName: Identifier | undefined, name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, isNameFirst?: boolean, comment?: string | NodeArray): JSDocParameterTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocParameterTag, tagName ?? createIdentifier("param"), comment); + node.typeExpression = typeExpression; + node.name = name; + node.isNameFirst = !!isNameFirst; + node.isBracketed = isBracketed; + return node; + } - // @api - function updateJSDocNameReference(node: JSDocNameReference, name: EntityName | JSDocMemberName): JSDocNameReference { - return node.name !== name - ? update(createJSDocNameReference(name), node) - : node; - } + // @api + function updateJSDocParameterTag(node: JSDocParameterTag, tagName: Identifier = getDefaultTagName(node), name: EntityName, isBracketed: boolean, typeExpression: JSDocTypeExpression | undefined, isNameFirst: boolean, comment: string | NodeArray | undefined): JSDocParameterTag { + return node.tagName !== tagName + || node.name !== name + || node.isBracketed !== isBracketed + || node.typeExpression !== typeExpression + || node.isNameFirst !== isNameFirst + || node.comment !== comment + ? update(createJSDocParameterTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment), node) + : node; + } - // @api - function createJSDocMemberName(left: EntityName | JSDocMemberName, right: Identifier) { - const node = createBaseNode(SyntaxKind.JSDocMemberName); - node.left = left; - node.right = right; - node.transformFlags |= - propagateChildFlags(node.left) | - propagateChildFlags(node.right); - return node; - } + // @api + function createJSDocPropertyTag(tagName: Identifier | undefined, name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, isNameFirst?: boolean, comment?: string | NodeArray): JSDocPropertyTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocPropertyTag, tagName ?? createIdentifier("prop"), comment); + node.typeExpression = typeExpression; + node.name = name; + node.isNameFirst = !!isNameFirst; + node.isBracketed = isBracketed; + return node; + } - // @api - function updateJSDocMemberName(node: JSDocMemberName, left: EntityName | JSDocMemberName, right: Identifier) { - return node.left !== left - || node.right !== right - ? update(createJSDocMemberName(left, right), node) - : node; - } + // @api + function updateJSDocPropertyTag(node: JSDocPropertyTag, tagName: Identifier = getDefaultTagName(node), name: EntityName, isBracketed: boolean, typeExpression: JSDocTypeExpression | undefined, isNameFirst: boolean, comment: string | NodeArray | undefined): JSDocPropertyTag { + return node.tagName !== tagName + || node.name !== name + || node.isBracketed !== isBracketed + || node.typeExpression !== typeExpression + || node.isNameFirst !== isNameFirst + || node.comment !== comment + ? update(createJSDocPropertyTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment), node) + : node; + } - // @api - function createJSDocLink(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLink { - const node = createBaseNode(SyntaxKind.JSDocLink); - node.name = name; - node.text = text; - return node; - } + // @api + function createJSDocCallbackTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray): JSDocCallbackTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocCallbackTag, tagName ?? createIdentifier("callback"), comment); + node.typeExpression = typeExpression; + node.fullName = fullName; + node.name = getJSDocTypeAliasName(fullName); + return node; + } - // @api - function updateJSDocLink(node: JSDocLink, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLink { - return node.name !== name - ? update(createJSDocLink(name, text), node) - : node; - } + // @api + function updateJSDocCallbackTag(node: JSDocCallbackTag, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocSignature, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray | undefined): JSDocCallbackTag { + return node.tagName !== tagName + || node.typeExpression !== typeExpression + || node.fullName !== fullName + || node.comment !== comment + ? update(createJSDocCallbackTag(tagName, typeExpression, fullName, comment), node) + : node; + } - // @api - function createJSDocLinkCode(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkCode { - const node = createBaseNode(SyntaxKind.JSDocLinkCode); - node.name = name; - node.text = text; - return node; - } + // @api + function createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray): JSDocAugmentsTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocAugmentsTag, tagName ?? createIdentifier("augments"), comment); + node.class = className; + return node; + } - // @api - function updateJSDocLinkCode(node: JSDocLinkCode, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkCode { - return node.name !== name - ? update(createJSDocLinkCode(name, text), node) - : node; - } + // @api + function updateJSDocAugmentsTag(node: JSDocAugmentsTag, tagName: Identifier = getDefaultTagName(node), className: JSDocAugmentsTag["class"], comment: string | NodeArray | undefined): JSDocAugmentsTag { + return node.tagName !== tagName + || node.class !== className + || node.comment !== comment + ? update(createJSDocAugmentsTag(tagName, className, comment), node) + : node; + } - // @api - function createJSDocLinkPlain(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkPlain { - const node = createBaseNode(SyntaxKind.JSDocLinkPlain); - node.name = name; - node.text = text; - return node; - } + // @api + function createJSDocImplementsTag(tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment?: string | NodeArray): JSDocImplementsTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocImplementsTag, tagName ?? createIdentifier("implements"), comment); + node.class = className; + return node; + } - // @api - function updateJSDocLinkPlain(node: JSDocLinkPlain, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkPlain { - return node.name !== name - ? update(createJSDocLinkPlain(name, text), node) - : node; - } + // @api + function createJSDocSeeTag(tagName: Identifier | undefined, name: JSDocNameReference | undefined, comment?: string | NodeArray): JSDocSeeTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocSeeTag, tagName ?? createIdentifier("see"), comment); + node.name = name; + return node; + } - // @api - function updateJSDocImplementsTag(node: JSDocImplementsTag, tagName: Identifier = getDefaultTagName(node), className: JSDocImplementsTag["class"], comment: string | NodeArray | undefined): JSDocImplementsTag { - return node.tagName !== tagName - || node.class !== className - || node.comment !== comment - ? update(createJSDocImplementsTag(tagName, className, comment), node) - : node; - } + // @api + function updateJSDocSeeTag(node: JSDocSeeTag, tagName: Identifier | undefined, name: JSDocNameReference | undefined, comment?: string | NodeArray): JSDocSeeTag { + return node.tagName !== tagName + || node.name !== name + || node.comment !== comment + ? update(createJSDocSeeTag(tagName, name, comment), node) + : node; + } - // @api - // createJSDocAuthorTag - // createJSDocClassTag - // createJSDocPublicTag - // createJSDocPrivateTag - // createJSDocProtectedTag - // createJSDocReadonlyTag - // createJSDocDeprecatedTag - function createJSDocSimpleTagWorker(kind: T["kind"], tagName: Identifier | undefined, comment?: string | NodeArray) { - const node = createBaseJSDocTag(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment); - return node; - } + // @api + function createJSDocNameReference(name: EntityName | JSDocMemberName): JSDocNameReference { + const node = createBaseNode(SyntaxKind.JSDocNameReference); + node.name = name; + return node; + } - // @api - // updateJSDocAuthorTag - // updateJSDocClassTag - // updateJSDocPublicTag - // updateJSDocPrivateTag - // updateJSDocProtectedTag - // updateJSDocReadonlyTag - // updateJSDocDeprecatedTag - function updateJSDocSimpleTagWorker(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), comment: string | NodeArray | undefined) { - return node.tagName !== tagName - || node.comment !== comment - ? update(createJSDocSimpleTagWorker(kind, tagName, comment), node) : - node; - } + // @api + function updateJSDocNameReference(node: JSDocNameReference, name: EntityName | JSDocMemberName): JSDocNameReference { + return node.name !== name + ? update(createJSDocNameReference(name), node) + : node; + } - // @api - // createJSDocTypeTag - // createJSDocReturnTag - // createJSDocThisTag - // createJSDocEnumTag - function createJSDocTypeLikeTagWorker(kind: T["kind"], tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: string | NodeArray) { - const node = createBaseJSDocTag(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment); - node.typeExpression = typeExpression; - return node; - } + // @api + function createJSDocMemberName(left: EntityName | JSDocMemberName, right: Identifier) { + const node = createBaseNode(SyntaxKind.JSDocMemberName); + node.left = left; + node.right = right; + node.transformFlags |= + propagateChildFlags(node.left) | + propagateChildFlags(node.right); + return node; + } - // @api - // updateJSDocTypeTag - // updateJSDocReturnTag - // updateJSDocThisTag - // updateJSDocEnumTag - function updateJSDocTypeLikeTagWorker(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocTypeExpression | undefined, comment: string | NodeArray | undefined) { - return node.tagName !== tagName - || node.typeExpression !== typeExpression - || node.comment !== comment - ? update(createJSDocTypeLikeTagWorker(kind, tagName, typeExpression, comment), node) - : node; - } + // @api + function updateJSDocMemberName(node: JSDocMemberName, left: EntityName | JSDocMemberName, right: Identifier) { + return node.left !== left + || node.right !== right + ? update(createJSDocMemberName(left, right), node) + : node; + } - // @api - function createJSDocUnknownTag(tagName: Identifier, comment?: string | NodeArray): JSDocUnknownTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocTag, tagName, comment); - return node; - } + // @api + function createJSDocLink(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLink { + const node = createBaseNode(SyntaxKind.JSDocLink); + node.name = name; + node.text = text; + return node; + } - // @api - function updateJSDocUnknownTag(node: JSDocUnknownTag, tagName: Identifier, comment: string | NodeArray | undefined): JSDocUnknownTag { - return node.tagName !== tagName - || node.comment !== comment - ? update(createJSDocUnknownTag(tagName, comment), node) - : node; - } + // @api + function updateJSDocLink(node: JSDocLink, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLink { + return node.name !== name + ? update(createJSDocLink(name, text), node) + : node; + } - // @api - function createJSDocText(text: string): JSDocText { - const node = createBaseNode(SyntaxKind.JSDocText); - node.text = text; - return node; - } + // @api + function createJSDocLinkCode(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkCode { + const node = createBaseNode(SyntaxKind.JSDocLinkCode); + node.name = name; + node.text = text; + return node; + } - // @api - function updateJSDocText(node: JSDocText, text: string): JSDocText { - return node.text !== text - ? update(createJSDocText(text), node) - : node; - } + // @api + function updateJSDocLinkCode(node: JSDocLinkCode, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkCode { + return node.name !== name + ? update(createJSDocLinkCode(name, text), node) + : node; + } - // @api - function createJSDocComment(comment?: string | NodeArray | undefined, tags?: readonly JSDocTag[] | undefined) { - const node = createBaseNode(SyntaxKind.JSDocComment); - node.comment = comment; - node.tags = asNodeArray(tags); - return node; - } + // @api + function createJSDocLinkPlain(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkPlain { + const node = createBaseNode(SyntaxKind.JSDocLinkPlain); + node.name = name; + node.text = text; + return node; + } - // @api - function updateJSDocComment(node: JSDoc, comment: string | NodeArray | undefined, tags: readonly JSDocTag[] | undefined) { - return node.comment !== comment - || node.tags !== tags - ? update(createJSDocComment(comment, tags), node) - : node; - } + // @api + function updateJSDocLinkPlain(node: JSDocLinkPlain, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkPlain { + return node.name !== name + ? update(createJSDocLinkPlain(name, text), node) + : node; + } - // - // JSX - // + // @api + function updateJSDocImplementsTag(node: JSDocImplementsTag, tagName: Identifier = getDefaultTagName(node), className: JSDocImplementsTag["class"], comment: string | NodeArray | undefined): JSDocImplementsTag { + return node.tagName !== tagName + || node.class !== className + || node.comment !== comment + ? update(createJSDocImplementsTag(tagName, className, comment), node) + : node; + } - // @api - function createJsxElement(openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { - const node = createBaseNode(SyntaxKind.JsxElement); - node.openingElement = openingElement; - node.children = createNodeArray(children); - node.closingElement = closingElement; - node.transformFlags |= - propagateChildFlags(node.openingElement) | - propagateChildrenFlags(node.children) | - propagateChildFlags(node.closingElement) | - TransformFlags.ContainsJsx; - return node; - } + // @api + // createJSDocAuthorTag + // createJSDocClassTag + // createJSDocPublicTag + // createJSDocPrivateTag + // createJSDocProtectedTag + // createJSDocReadonlyTag + // createJSDocDeprecatedTag + function createJSDocSimpleTagWorker(kind: T["kind"], tagName: Identifier | undefined, comment?: string | NodeArray) { + const node = createBaseJSDocTag(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment); + return node; + } - // @api - function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { - return node.openingElement !== openingElement - || node.children !== children - || node.closingElement !== closingElement - ? update(createJsxElement(openingElement, children, closingElement), node) - : node; - } + // @api + // updateJSDocAuthorTag + // updateJSDocClassTag + // updateJSDocPublicTag + // updateJSDocPrivateTag + // updateJSDocProtectedTag + // updateJSDocReadonlyTag + // updateJSDocDeprecatedTag + function updateJSDocSimpleTagWorker(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), comment: string | NodeArray | undefined) { + return node.tagName !== tagName + || node.comment !== comment + ? update(createJSDocSimpleTagWorker(kind, tagName, comment), node) : + node; + } - // @api - function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - const node = createBaseNode(SyntaxKind.JsxSelfClosingElement); - node.tagName = tagName; - node.typeArguments = asNodeArray(typeArguments); - node.attributes = attributes; - node.transformFlags |= - propagateChildFlags(node.tagName) | - propagateChildrenFlags(node.typeArguments) | - propagateChildFlags(node.attributes) | - TransformFlags.ContainsJsx; - if (node.typeArguments) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - return node; - } + // @api + // createJSDocTypeTag + // createJSDocReturnTag + // createJSDocThisTag + // createJSDocEnumTag + function createJSDocTypeLikeTagWorker(kind: T["kind"], tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: string | NodeArray) { + const node = createBaseJSDocTag(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment); + node.typeExpression = typeExpression; + return node; + } - // @api - function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - return node.tagName !== tagName - || node.typeArguments !== typeArguments - || node.attributes !== attributes - ? update(createJsxSelfClosingElement(tagName, typeArguments, attributes), node) - : node; - } + // @api + // updateJSDocTypeTag + // updateJSDocReturnTag + // updateJSDocThisTag + // updateJSDocEnumTag + function updateJSDocTypeLikeTagWorker(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocTypeExpression | undefined, comment: string | NodeArray | undefined) { + return node.tagName !== tagName + || node.typeExpression !== typeExpression + || node.comment !== comment + ? update(createJSDocTypeLikeTagWorker(kind, tagName, typeExpression, comment), node) + : node; + } - // @api - function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - const node = createBaseNode(SyntaxKind.JsxOpeningElement); - node.tagName = tagName; - node.typeArguments = asNodeArray(typeArguments); - node.attributes = attributes; - node.transformFlags |= - propagateChildFlags(node.tagName) | - propagateChildrenFlags(node.typeArguments) | - propagateChildFlags(node.attributes) | - TransformFlags.ContainsJsx; - if (typeArguments) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - return node; - } + // @api + function createJSDocUnknownTag(tagName: Identifier, comment?: string | NodeArray): JSDocUnknownTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocTag, tagName, comment); + return node; + } - // @api - function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - return node.tagName !== tagName - || node.typeArguments !== typeArguments - || node.attributes !== attributes - ? update(createJsxOpeningElement(tagName, typeArguments, attributes), node) - : node; - } + // @api + function updateJSDocUnknownTag(node: JSDocUnknownTag, tagName: Identifier, comment: string | NodeArray | undefined): JSDocUnknownTag { + return node.tagName !== tagName + || node.comment !== comment + ? update(createJSDocUnknownTag(tagName, comment), node) + : node; + } - // @api - function createJsxClosingElement(tagName: JsxTagNameExpression) { - const node = createBaseNode(SyntaxKind.JsxClosingElement); - node.tagName = tagName; - node.transformFlags |= - propagateChildFlags(node.tagName) | - TransformFlags.ContainsJsx; - return node; - } + // @api + function createJSDocText(text: string): JSDocText { + const node = createBaseNode(SyntaxKind.JSDocText); + node.text = text; + return node; + } - // @api - function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression) { - return node.tagName !== tagName - ? update(createJsxClosingElement(tagName), node) - : node; - } + // @api + function updateJSDocText(node: JSDocText, text: string): JSDocText { + return node.text !== text + ? update(createJSDocText(text), node) + : node; + } - // @api - function createJsxFragment(openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { - const node = createBaseNode(SyntaxKind.JsxFragment); - node.openingFragment = openingFragment; - node.children = createNodeArray(children); - node.closingFragment = closingFragment; - node.transformFlags |= - propagateChildFlags(node.openingFragment) | - propagateChildrenFlags(node.children) | - propagateChildFlags(node.closingFragment) | - TransformFlags.ContainsJsx; - return node; - } + // @api + function createJSDocComment(comment?: string | NodeArray | undefined, tags?: readonly JSDocTag[] | undefined) { + const node = createBaseNode(SyntaxKind.JSDocComment); + node.comment = comment; + node.tags = asNodeArray(tags); + return node; + } - // @api - function updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { - return node.openingFragment !== openingFragment - || node.children !== children - || node.closingFragment !== closingFragment - ? update(createJsxFragment(openingFragment, children, closingFragment), node) - : node; - } + // @api + function updateJSDocComment(node: JSDoc, comment: string | NodeArray | undefined, tags: readonly JSDocTag[] | undefined) { + return node.comment !== comment + || node.tags !== tags + ? update(createJSDocComment(comment, tags), node) + : node; + } - // @api - function createJsxText(text: string, containsOnlyTriviaWhiteSpaces?: boolean) { - const node = createBaseNode(SyntaxKind.JsxText); - node.text = text; - node.containsOnlyTriviaWhiteSpaces = !!containsOnlyTriviaWhiteSpaces; - node.transformFlags |= TransformFlags.ContainsJsx; - return node; - } + // + // JSX + // + + // @api + function createJsxElement(openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { + const node = createBaseNode(SyntaxKind.JsxElement); + node.openingElement = openingElement; + node.children = createNodeArray(children); + node.closingElement = closingElement; + node.transformFlags |= + propagateChildFlags(node.openingElement) | + propagateChildrenFlags(node.children) | + propagateChildFlags(node.closingElement) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function updateJsxText(node: JsxText, text: string, containsOnlyTriviaWhiteSpaces?: boolean) { - return node.text !== text - || node.containsOnlyTriviaWhiteSpaces !== containsOnlyTriviaWhiteSpaces - ? update(createJsxText(text, containsOnlyTriviaWhiteSpaces), node) - : node; - } + // @api + function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { + return node.openingElement !== openingElement + || node.children !== children + || node.closingElement !== closingElement + ? update(createJsxElement(openingElement, children, closingElement), node) + : node; + } - // @api - function createJsxOpeningFragment() { - const node = createBaseNode(SyntaxKind.JsxOpeningFragment); - node.transformFlags |= TransformFlags.ContainsJsx; - return node; + // @api + function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + const node = createBaseNode(SyntaxKind.JsxSelfClosingElement); + node.tagName = tagName; + node.typeArguments = asNodeArray(typeArguments); + node.attributes = attributes; + node.transformFlags |= + propagateChildFlags(node.tagName) | + propagateChildrenFlags(node.typeArguments) | + propagateChildFlags(node.attributes) | + TransformFlags.ContainsJsx; + if (node.typeArguments) { + node.transformFlags |= TransformFlags.ContainsTypeScript; } + return node; + } - // @api - function createJsxJsxClosingFragment() { - const node = createBaseNode(SyntaxKind.JsxClosingFragment); - node.transformFlags |= TransformFlags.ContainsJsx; - return node; - } + // @api + function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + return node.tagName !== tagName + || node.typeArguments !== typeArguments + || node.attributes !== attributes + ? update(createJsxSelfClosingElement(tagName, typeArguments, attributes), node) + : node; + } - // @api - function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined) { - const node = createBaseNode(SyntaxKind.JsxAttribute); - node.name = name; - node.initializer = initializer; - node.transformFlags |= - propagateChildFlags(node.name) | - propagateChildFlags(node.initializer) | - TransformFlags.ContainsJsx; - return node; + // @api + function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + const node = createBaseNode(SyntaxKind.JsxOpeningElement); + node.tagName = tagName; + node.typeArguments = asNodeArray(typeArguments); + node.attributes = attributes; + node.transformFlags |= + propagateChildFlags(node.tagName) | + propagateChildrenFlags(node.typeArguments) | + propagateChildFlags(node.attributes) | + TransformFlags.ContainsJsx; + if (typeArguments) { + node.transformFlags |= TransformFlags.ContainsTypeScript; } + return node; + } - // @api - function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined) { - return node.name !== name - || node.initializer !== initializer - ? update(createJsxAttribute(name, initializer), node) - : node; - } + // @api + function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + return node.tagName !== tagName + || node.typeArguments !== typeArguments + || node.attributes !== attributes + ? update(createJsxOpeningElement(tagName, typeArguments, attributes), node) + : node; + } - // @api - function createJsxAttributes(properties: readonly JsxAttributeLike[]) { - const node = createBaseNode(SyntaxKind.JsxAttributes); - node.properties = createNodeArray(properties); - node.transformFlags |= - propagateChildrenFlags(node.properties) | - TransformFlags.ContainsJsx; - return node; - } + // @api + function createJsxClosingElement(tagName: JsxTagNameExpression) { + const node = createBaseNode(SyntaxKind.JsxClosingElement); + node.tagName = tagName; + node.transformFlags |= + propagateChildFlags(node.tagName) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]) { - return node.properties !== properties - ? update(createJsxAttributes(properties), node) - : node; - } + // @api + function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression) { + return node.tagName !== tagName + ? update(createJsxClosingElement(tagName), node) + : node; + } - // @api - function createJsxSpreadAttribute(expression: Expression) { - const node = createBaseNode(SyntaxKind.JsxSpreadAttribute); - node.expression = expression; - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsJsx; - return node; - } + // @api + function createJsxFragment(openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { + const node = createBaseNode(SyntaxKind.JsxFragment); + node.openingFragment = openingFragment; + node.children = createNodeArray(children); + node.closingFragment = closingFragment; + node.transformFlags |= + propagateChildFlags(node.openingFragment) | + propagateChildrenFlags(node.children) | + propagateChildFlags(node.closingFragment) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression) { - return node.expression !== expression - ? update(createJsxSpreadAttribute(expression), node) - : node; - } + // @api + function updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { + return node.openingFragment !== openingFragment + || node.children !== children + || node.closingFragment !== closingFragment + ? update(createJsxFragment(openingFragment, children, closingFragment), node) + : node; + } - // @api - function createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined) { - const node = createBaseNode(SyntaxKind.JsxExpression); - node.dotDotDotToken = dotDotDotToken; - node.expression = expression; - node.transformFlags |= - propagateChildFlags(node.dotDotDotToken) | - propagateChildFlags(node.expression) | - TransformFlags.ContainsJsx; - return node; - } + // @api + function createJsxText(text: string, containsOnlyTriviaWhiteSpaces?: boolean) { + const node = createBaseNode(SyntaxKind.JsxText); + node.text = text; + node.containsOnlyTriviaWhiteSpaces = !!containsOnlyTriviaWhiteSpaces; + node.transformFlags |= TransformFlags.ContainsJsx; + return node; + } - // @api - function updateJsxExpression(node: JsxExpression, expression: Expression | undefined) { - return node.expression !== expression - ? update(createJsxExpression(node.dotDotDotToken, expression), node) - : node; - } + // @api + function updateJsxText(node: JsxText, text: string, containsOnlyTriviaWhiteSpaces?: boolean) { + return node.text !== text + || node.containsOnlyTriviaWhiteSpaces !== containsOnlyTriviaWhiteSpaces + ? update(createJsxText(text, containsOnlyTriviaWhiteSpaces), node) + : node; + } - // - // Clauses - // + // @api + function createJsxOpeningFragment() { + const node = createBaseNode(SyntaxKind.JsxOpeningFragment); + node.transformFlags |= TransformFlags.ContainsJsx; + return node; + } - // @api - function createCaseClause(expression: Expression, statements: readonly Statement[]) { - const node = createBaseNode(SyntaxKind.CaseClause); - node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); - node.statements = createNodeArray(statements); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildrenFlags(node.statements); - return node; - } + // @api + function createJsxJsxClosingFragment() { + const node = createBaseNode(SyntaxKind.JsxClosingFragment); + node.transformFlags |= TransformFlags.ContainsJsx; + return node; + } - // @api - function updateCaseClause(node: CaseClause, expression: Expression, statements: readonly Statement[]) { - return node.expression !== expression - || node.statements !== statements - ? update(createCaseClause(expression, statements), node) - : node; - } + // @api + function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined) { + const node = createBaseNode(SyntaxKind.JsxAttribute); + node.name = name; + node.initializer = initializer; + node.transformFlags |= + propagateChildFlags(node.name) | + propagateChildFlags(node.initializer) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function createDefaultClause(statements: readonly Statement[]) { - const node = createBaseNode(SyntaxKind.DefaultClause); - node.statements = createNodeArray(statements); - node.transformFlags = propagateChildrenFlags(node.statements); - return node; - } + // @api + function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined) { + return node.name !== name + || node.initializer !== initializer + ? update(createJsxAttribute(name, initializer), node) + : node; + } - // @api - function updateDefaultClause(node: DefaultClause, statements: readonly Statement[]) { - return node.statements !== statements - ? update(createDefaultClause(statements), node) - : node; - } + // @api + function createJsxAttributes(properties: readonly JsxAttributeLike[]) { + const node = createBaseNode(SyntaxKind.JsxAttributes); + node.properties = createNodeArray(properties); + node.transformFlags |= + propagateChildrenFlags(node.properties) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function createHeritageClause(token: HeritageClause["token"], types: readonly ExpressionWithTypeArguments[]) { - const node = createBaseNode(SyntaxKind.HeritageClause); - node.token = token; - node.types = createNodeArray(types); - node.transformFlags |= propagateChildrenFlags(node.types); - switch (token) { - case SyntaxKind.ExtendsKeyword: - node.transformFlags |= TransformFlags.ContainsES2015; - break; - case SyntaxKind.ImplementsKeyword: - node.transformFlags |= TransformFlags.ContainsTypeScript; - break; - default: - return Debug.assertNever(token); - } - return node; - } + // @api + function updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]) { + return node.properties !== properties + ? update(createJsxAttributes(properties), node) + : node; + } - // @api - function updateHeritageClause(node: HeritageClause, types: readonly ExpressionWithTypeArguments[]) { - return node.types !== types - ? update(createHeritageClause(node.token, types), node) - : node; - } + // @api + function createJsxSpreadAttribute(expression: Expression) { + const node = createBaseNode(SyntaxKind.JsxSpreadAttribute); + node.expression = expression; + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function createCatchClause(variableDeclaration: string | BindingName | VariableDeclaration | undefined, block: Block) { - const node = createBaseNode(SyntaxKind.CatchClause); - if (typeof variableDeclaration === "string" || variableDeclaration && !isVariableDeclaration(variableDeclaration)) { - variableDeclaration = createVariableDeclaration( - variableDeclaration, - /*exclamationToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined - ); - } - node.variableDeclaration = variableDeclaration; - node.block = block; - node.transformFlags |= - propagateChildFlags(node.variableDeclaration) | - propagateChildFlags(node.block); - if (!variableDeclaration) node.transformFlags |= TransformFlags.ContainsES2019; - return node; - } + // @api + function updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression) { + return node.expression !== expression + ? update(createJsxSpreadAttribute(expression), node) + : node; + } - // @api - function updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block) { - return node.variableDeclaration !== variableDeclaration - || node.block !== block - ? update(createCatchClause(variableDeclaration, block), node) - : node; - } + // @api + function createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined) { + const node = createBaseNode(SyntaxKind.JsxExpression); + node.dotDotDotToken = dotDotDotToken; + node.expression = expression; + node.transformFlags |= + propagateChildFlags(node.dotDotDotToken) | + propagateChildFlags(node.expression) | + TransformFlags.ContainsJsx; + return node; + } - // - // Property assignments - // + // @api + function updateJsxExpression(node: JsxExpression, expression: Expression | undefined) { + return node.expression !== expression + ? update(createJsxExpression(node.dotDotDotToken, expression), node) + : node; + } - // @api - function createPropertyAssignment(name: string | PropertyName, initializer: Expression) { - const node = createBaseNamedDeclaration( - SyntaxKind.PropertyAssignment, - /*decorators*/ undefined, - /*modifiers*/ undefined, - name - ); - node.initializer = parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer); - node.transformFlags |= - propagateChildFlags(node.name) | - propagateChildFlags(node.initializer); - return node; - } + // + // Clauses + // + + // @api + function createCaseClause(expression: Expression, statements: readonly Statement[]) { + const node = createBaseNode(SyntaxKind.CaseClause); + node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.statements = createNodeArray(statements); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildrenFlags(node.statements); + return node; + } - function finishUpdatePropertyAssignment(updated: Mutable, original: PropertyAssignment) { - // copy children used only for error reporting - if (original.decorators) updated.decorators = original.decorators; - if (original.modifiers) updated.modifiers = original.modifiers; - if (original.questionToken) updated.questionToken = original.questionToken; - if (original.exclamationToken) updated.exclamationToken = original.exclamationToken; - return update(updated, original); - } + // @api + function updateCaseClause(node: CaseClause, expression: Expression, statements: readonly Statement[]) { + return node.expression !== expression + || node.statements !== statements + ? update(createCaseClause(expression, statements), node) + : node; + } - // @api - function updatePropertyAssignment(node: PropertyAssignment, name: PropertyName, initializer: Expression) { - return node.name !== name - || node.initializer !== initializer - ? finishUpdatePropertyAssignment(createPropertyAssignment(name, initializer), node) - : node; - } + // @api + function createDefaultClause(statements: readonly Statement[]) { + const node = createBaseNode(SyntaxKind.DefaultClause); + node.statements = createNodeArray(statements); + node.transformFlags = propagateChildrenFlags(node.statements); + return node; + } - // @api - function createShorthandPropertyAssignment(name: string | Identifier, objectAssignmentInitializer?: Expression) { - const node = createBaseNamedDeclaration( - SyntaxKind.ShorthandPropertyAssignment, - /*decorators*/ undefined, - /*modifiers*/ undefined, - name - ); - node.objectAssignmentInitializer = objectAssignmentInitializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(objectAssignmentInitializer); - node.transformFlags |= - propagateChildFlags(node.objectAssignmentInitializer) | - TransformFlags.ContainsES2015; - return node; - } + // @api + function updateDefaultClause(node: DefaultClause, statements: readonly Statement[]) { + return node.statements !== statements + ? update(createDefaultClause(statements), node) + : node; + } - function finishUpdateShorthandPropertyAssignment(updated: Mutable, original: ShorthandPropertyAssignment) { - // copy children used only for error reporting - if (original.decorators) updated.decorators = original.decorators; - if (original.modifiers) updated.modifiers = original.modifiers; - if (original.equalsToken) updated.equalsToken = original.equalsToken; - if (original.questionToken) updated.questionToken = original.questionToken; - if (original.exclamationToken) updated.exclamationToken = original.exclamationToken; - return update(updated, original); + // @api + function createHeritageClause(token: HeritageClause["token"], types: readonly ExpressionWithTypeArguments[]) { + const node = createBaseNode(SyntaxKind.HeritageClause); + node.token = token; + node.types = createNodeArray(types); + node.transformFlags |= propagateChildrenFlags(node.types); + switch (token) { + case SyntaxKind.ExtendsKeyword: + node.transformFlags |= TransformFlags.ContainsES2015; + break; + case SyntaxKind.ImplementsKeyword: + node.transformFlags |= TransformFlags.ContainsTypeScript; + break; + default: + return Debug.assertNever(token); } + return node; + } - // @api - function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression | undefined) { - return node.name !== name - || node.objectAssignmentInitializer !== objectAssignmentInitializer - ? finishUpdateShorthandPropertyAssignment(createShorthandPropertyAssignment(name, objectAssignmentInitializer), node) - : node; - } + // @api + function updateHeritageClause(node: HeritageClause, types: readonly ExpressionWithTypeArguments[]) { + return node.types !== types + ? update(createHeritageClause(node.token, types), node) + : node; + } - // @api - function createSpreadAssignment(expression: Expression) { - const node = createBaseNode(SyntaxKind.SpreadAssignment); - node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsES2018 | - TransformFlags.ContainsObjectRestOrSpread; - return node; + // @api + function createCatchClause(variableDeclaration: string | BindingName | VariableDeclaration | undefined, block: Block) { + const node = createBaseNode(SyntaxKind.CatchClause); + if (typeof variableDeclaration === "string" || variableDeclaration && !isVariableDeclaration(variableDeclaration)) { + variableDeclaration = createVariableDeclaration( + variableDeclaration, + /*exclamationToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + ); } + node.variableDeclaration = variableDeclaration; + node.block = block; + node.transformFlags |= + propagateChildFlags(node.variableDeclaration) | + propagateChildFlags(node.block); + if (!variableDeclaration) node.transformFlags |= TransformFlags.ContainsES2019; + return node; + } - // @api - function updateSpreadAssignment(node: SpreadAssignment, expression: Expression) { - return node.expression !== expression - ? update(createSpreadAssignment(expression), node) - : node; - } + // @api + function updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block) { + return node.variableDeclaration !== variableDeclaration + || node.block !== block + ? update(createCatchClause(variableDeclaration, block), node) + : node; + } - // - // Enum - // + // + // Property assignments + // + + // @api + function createPropertyAssignment(name: string | PropertyName, initializer: Expression) { + const node = createBaseNamedDeclaration( + SyntaxKind.PropertyAssignment, + /*decorators*/ undefined, + /*modifiers*/ undefined, + name + ); + node.initializer = parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer); + node.transformFlags |= + propagateChildFlags(node.name) | + propagateChildFlags(node.initializer); + return node; + } - // @api - function createEnumMember(name: string | PropertyName, initializer?: Expression) { - const node = createBaseNode(SyntaxKind.EnumMember); - node.name = asName(name); - node.initializer = initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer); - node.transformFlags |= - propagateChildFlags(node.name) | - propagateChildFlags(node.initializer) | - TransformFlags.ContainsTypeScript; - return node; - } + function finishUpdatePropertyAssignment(updated: Mutable, original: PropertyAssignment) { + // copy children used only for error reporting + if (original.decorators) updated.decorators = original.decorators; + if (original.modifiers) updated.modifiers = original.modifiers; + if (original.questionToken) updated.questionToken = original.questionToken; + if (original.exclamationToken) updated.exclamationToken = original.exclamationToken; + return update(updated, original); + } - // @api - function updateEnumMember(node: EnumMember, name: PropertyName, initializer: Expression | undefined) { - return node.name !== name - || node.initializer !== initializer - ? update(createEnumMember(name, initializer), node) - : node; - } + // @api + function updatePropertyAssignment(node: PropertyAssignment, name: PropertyName, initializer: Expression) { + return node.name !== name + || node.initializer !== initializer + ? finishUpdatePropertyAssignment(createPropertyAssignment(name, initializer), node) + : node; + } - // - // Top-level nodes - // + // @api + function createShorthandPropertyAssignment(name: string | Identifier, objectAssignmentInitializer?: Expression) { + const node = createBaseNamedDeclaration( + SyntaxKind.ShorthandPropertyAssignment, + /*decorators*/ undefined, + /*modifiers*/ undefined, + name + ); + node.objectAssignmentInitializer = objectAssignmentInitializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(objectAssignmentInitializer); + node.transformFlags |= + propagateChildFlags(node.objectAssignmentInitializer) | + TransformFlags.ContainsES2015; + return node; + } - // @api - function createSourceFile( - statements: readonly Statement[], - endOfFileToken: EndOfFileToken, - flags: NodeFlags - ) { - const node = baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile) as Mutable; - node.statements = createNodeArray(statements); - node.endOfFileToken = endOfFileToken; - node.flags |= flags; - node.fileName = ""; - node.text = ""; - node.languageVersion = 0; - node.languageVariant = 0; - node.scriptKind = 0; - node.isDeclarationFile = false; - node.hasNoDefaultLib = false; - node.transformFlags |= - propagateChildrenFlags(node.statements) | - propagateChildFlags(node.endOfFileToken); - return node; - } + function finishUpdateShorthandPropertyAssignment(updated: Mutable, original: ShorthandPropertyAssignment) { + // copy children used only for error reporting + if (original.decorators) updated.decorators = original.decorators; + if (original.modifiers) updated.modifiers = original.modifiers; + if (original.equalsToken) updated.equalsToken = original.equalsToken; + if (original.questionToken) updated.questionToken = original.questionToken; + if (original.exclamationToken) updated.exclamationToken = original.exclamationToken; + return update(updated, original); + } - function cloneSourceFileWithChanges( - source: SourceFile, - statements: readonly Statement[], - isDeclarationFile: boolean, - referencedFiles: readonly FileReference[], - typeReferences: readonly FileReference[], - hasNoDefaultLib: boolean, - libReferences: readonly FileReference[] - ) { - const node = baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile) as Mutable; - for (const p in source) { - if (p === "emitNode" || hasProperty(node, p) || !hasProperty(source, p)) continue; - (node as any)[p] = (source as any)[p]; - } - node.flags |= source.flags; - node.statements = createNodeArray(statements); - node.endOfFileToken = source.endOfFileToken; - node.isDeclarationFile = isDeclarationFile; - node.referencedFiles = referencedFiles; - node.typeReferenceDirectives = typeReferences; - node.hasNoDefaultLib = hasNoDefaultLib; - node.libReferenceDirectives = libReferences; - node.transformFlags = - propagateChildrenFlags(node.statements) | - propagateChildFlags(node.endOfFileToken); - node.impliedNodeFormat = source.impliedNodeFormat; - return node; - } + // @api + function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression | undefined) { + return node.name !== name + || node.objectAssignmentInitializer !== objectAssignmentInitializer + ? finishUpdateShorthandPropertyAssignment(createShorthandPropertyAssignment(name, objectAssignmentInitializer), node) + : node; + } - // @api - function updateSourceFile( - node: SourceFile, - statements: readonly Statement[], - isDeclarationFile = node.isDeclarationFile, - referencedFiles = node.referencedFiles, - typeReferenceDirectives = node.typeReferenceDirectives, - hasNoDefaultLib = node.hasNoDefaultLib, - libReferenceDirectives = node.libReferenceDirectives - ) { - return node.statements !== statements - || node.isDeclarationFile !== isDeclarationFile - || node.referencedFiles !== referencedFiles - || node.typeReferenceDirectives !== typeReferenceDirectives - || node.hasNoDefaultLib !== hasNoDefaultLib - || node.libReferenceDirectives !== libReferenceDirectives - ? update(cloneSourceFileWithChanges(node, statements, isDeclarationFile, referencedFiles, typeReferenceDirectives, hasNoDefaultLib, libReferenceDirectives), node) - : node; - } + // @api + function createSpreadAssignment(expression: Expression) { + const node = createBaseNode(SyntaxKind.SpreadAssignment); + node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsES2018 | + TransformFlags.ContainsObjectRestOrSpread; + return node; + } - // @api - function createBundle(sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { - const node = createBaseNode(SyntaxKind.Bundle); - node.prepends = prepends; - node.sourceFiles = sourceFiles; - return node; - } + // @api + function updateSpreadAssignment(node: SpreadAssignment, expression: Expression) { + return node.expression !== expression + ? update(createSpreadAssignment(expression), node) + : node; + } - // @api - function updateBundle(node: Bundle, sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { - return node.sourceFiles !== sourceFiles - || node.prepends !== prepends - ? update(createBundle(sourceFiles, prepends), node) - : node; - } + // + // Enum + // + + // @api + function createEnumMember(name: string | PropertyName, initializer?: Expression) { + const node = createBaseNode(SyntaxKind.EnumMember); + node.name = asName(name); + node.initializer = initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer); + node.transformFlags |= + propagateChildFlags(node.name) | + propagateChildFlags(node.initializer) | + TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createUnparsedSource(prologues: readonly UnparsedPrologue[], syntheticReferences: readonly UnparsedSyntheticReference[] | undefined, texts: readonly UnparsedSourceText[]) { - const node = createBaseNode(SyntaxKind.UnparsedSource); - node.prologues = prologues; - node.syntheticReferences = syntheticReferences; - node.texts = texts; - node.fileName = ""; - node.text = ""; - node.referencedFiles = emptyArray; - node.libReferenceDirectives = emptyArray; - node.getLineAndCharacterOfPosition = pos => getLineAndCharacterOfPosition(node, pos); - return node; - } + // @api + function updateEnumMember(node: EnumMember, name: PropertyName, initializer: Expression | undefined) { + return node.name !== name + || node.initializer !== initializer + ? update(createEnumMember(name, initializer), node) + : node; + } - function createBaseUnparsedNode(kind: T["kind"], data?: string) { - const node = createBaseNode(kind); - node.data = data; - return node; - } + // + // Top-level nodes + // + + // @api + function createSourceFile( + statements: readonly Statement[], + endOfFileToken: EndOfFileToken, + flags: NodeFlags + ) { + const node = baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile) as Mutable; + node.statements = createNodeArray(statements); + node.endOfFileToken = endOfFileToken; + node.flags |= flags; + node.fileName = ""; + node.text = ""; + node.languageVersion = 0; + node.languageVariant = 0; + node.scriptKind = 0; + node.isDeclarationFile = false; + node.hasNoDefaultLib = false; + node.transformFlags |= + propagateChildrenFlags(node.statements) | + propagateChildFlags(node.endOfFileToken); + return node; + } - // @api - function createUnparsedPrologue(data?: string): UnparsedPrologue { - return createBaseUnparsedNode(SyntaxKind.UnparsedPrologue, data); - } + function cloneSourceFileWithChanges( + source: SourceFile, + statements: readonly Statement[], + isDeclarationFile: boolean, + referencedFiles: readonly FileReference[], + typeReferences: readonly FileReference[], + hasNoDefaultLib: boolean, + libReferences: readonly FileReference[] + ) { + const node = baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile) as Mutable; + for (const p in source) { + if (p === "emitNode" || hasProperty(node, p) || !hasProperty(source, p)) continue; + (node as any)[p] = (source as any)[p]; + } + node.flags |= source.flags; + node.statements = createNodeArray(statements); + node.endOfFileToken = source.endOfFileToken; + node.isDeclarationFile = isDeclarationFile; + node.referencedFiles = referencedFiles; + node.typeReferenceDirectives = typeReferences; + node.hasNoDefaultLib = hasNoDefaultLib; + node.libReferenceDirectives = libReferences; + node.transformFlags = + propagateChildrenFlags(node.statements) | + propagateChildFlags(node.endOfFileToken); + node.impliedNodeFormat = source.impliedNodeFormat; + return node; + } - // @api - function createUnparsedPrepend(data: string | undefined, texts: readonly UnparsedTextLike[]): UnparsedPrepend { - const node = createBaseUnparsedNode(SyntaxKind.UnparsedPrepend, data); - node.texts = texts; - return node; - } + // @api + function updateSourceFile( + node: SourceFile, + statements: readonly Statement[], + isDeclarationFile = node.isDeclarationFile, + referencedFiles = node.referencedFiles, + typeReferenceDirectives = node.typeReferenceDirectives, + hasNoDefaultLib = node.hasNoDefaultLib, + libReferenceDirectives = node.libReferenceDirectives + ) { + return node.statements !== statements + || node.isDeclarationFile !== isDeclarationFile + || node.referencedFiles !== referencedFiles + || node.typeReferenceDirectives !== typeReferenceDirectives + || node.hasNoDefaultLib !== hasNoDefaultLib + || node.libReferenceDirectives !== libReferenceDirectives + ? update(cloneSourceFileWithChanges(node, statements, isDeclarationFile, referencedFiles, typeReferenceDirectives, hasNoDefaultLib, libReferenceDirectives), node) + : node; + } - // @api - function createUnparsedTextLike(data: string | undefined, internal: boolean): UnparsedTextLike { - return createBaseUnparsedNode(internal ? SyntaxKind.UnparsedInternalText : SyntaxKind.UnparsedText, data); - } + // @api + function createBundle(sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { + const node = createBaseNode(SyntaxKind.Bundle); + node.prepends = prepends; + node.sourceFiles = sourceFiles; + return node; + } - // @api - function createUnparsedSyntheticReference(section: BundleFileHasNoDefaultLib | BundleFileReference): UnparsedSyntheticReference { - const node = createBaseNode(SyntaxKind.UnparsedSyntheticReference); - node.data = section.data; - node.section = section; - return node; - } + // @api + function updateBundle(node: Bundle, sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { + return node.sourceFiles !== sourceFiles + || node.prepends !== prepends + ? update(createBundle(sourceFiles, prepends), node) + : node; + } - // @api - function createInputFiles(): InputFiles { - const node = createBaseNode(SyntaxKind.InputFiles); - node.javascriptText = ""; - node.declarationText = ""; - return node; - } + // @api + function createUnparsedSource(prologues: readonly UnparsedPrologue[], syntheticReferences: readonly UnparsedSyntheticReference[] | undefined, texts: readonly UnparsedSourceText[]) { + const node = createBaseNode(SyntaxKind.UnparsedSource); + node.prologues = prologues; + node.syntheticReferences = syntheticReferences; + node.texts = texts; + node.fileName = ""; + node.text = ""; + node.referencedFiles = emptyArray; + node.libReferenceDirectives = emptyArray; + node.getLineAndCharacterOfPosition = pos => getLineAndCharacterOfPosition(node, pos); + return node; + } - // - // Synthetic Nodes (used by checker) - // + function createBaseUnparsedNode(kind: T["kind"], data?: string) { + const node = createBaseNode(kind); + node.data = data; + return node; + } - // @api - function createSyntheticExpression(type: Type, isSpread = false, tupleNameSource?: ParameterDeclaration | NamedTupleMember) { - const node = createBaseNode(SyntaxKind.SyntheticExpression); - node.type = type; - node.isSpread = isSpread; - node.tupleNameSource = tupleNameSource; - return node; - } + // @api + function createUnparsedPrologue(data?: string): UnparsedPrologue { + return createBaseUnparsedNode(SyntaxKind.UnparsedPrologue, data); + } - // @api - function createSyntaxList(children: Node[]) { - const node = createBaseNode(SyntaxKind.SyntaxList); - node._children = children; - return node; - } + // @api + function createUnparsedPrepend(data: string | undefined, texts: readonly UnparsedTextLike[]): UnparsedPrepend { + const node = createBaseUnparsedNode(SyntaxKind.UnparsedPrepend, data); + node.texts = texts; + return node; + } - // - // Transformation nodes - // + // @api + function createUnparsedTextLike(data: string | undefined, internal: boolean): UnparsedTextLike { + return createBaseUnparsedNode(internal ? SyntaxKind.UnparsedInternalText : SyntaxKind.UnparsedText, data); + } - /** - * Creates a synthetic statement to act as a placeholder for a not-emitted statement in - * order to preserve comments. - * - * @param original The original statement. - */ - // @api - function createNotEmittedStatement(original: Node) { - const node = createBaseNode(SyntaxKind.NotEmittedStatement); - node.original = original; - setTextRange(node, original); - return node; - } + // @api + function createUnparsedSyntheticReference(section: BundleFileHasNoDefaultLib | BundleFileReference): UnparsedSyntheticReference { + const node = createBaseNode(SyntaxKind.UnparsedSyntheticReference); + node.data = section.data; + node.section = section; + return node; + } - /** - * Creates a synthetic expression to act as a placeholder for a not-emitted expression in - * order to preserve comments or sourcemap positions. - * - * @param expression The inner expression to emit. - * @param original The original outer expression. - */ - // @api - function createPartiallyEmittedExpression(expression: Expression, original?: Node) { - const node = createBaseNode(SyntaxKind.PartiallyEmittedExpression); - node.expression = expression; - node.original = original; - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsTypeScript; - setTextRange(node, original); - return node; - } + // @api + function createInputFiles(): InputFiles { + const node = createBaseNode(SyntaxKind.InputFiles); + node.javascriptText = ""; + node.declarationText = ""; + return node; + } - // @api - function updatePartiallyEmittedExpression(node: PartiallyEmittedExpression, expression: Expression) { - return node.expression !== expression - ? update(createPartiallyEmittedExpression(expression, node.original), node) - : node; - } + // + // Synthetic Nodes (used by checker) + // - function flattenCommaElements(node: Expression): Expression | readonly Expression[] { - if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.emitNode && !node.id) { - if (isCommaListExpression(node)) { - return node.elements; - } - if (isBinaryExpression(node) && isCommaToken(node.operatorToken)) { - return [node.left, node.right]; - } - } - return node; - } + // @api + function createSyntheticExpression(type: Type, isSpread = false, tupleNameSource?: ParameterDeclaration | NamedTupleMember) { + const node = createBaseNode(SyntaxKind.SyntheticExpression); + node.type = type; + node.isSpread = isSpread; + node.tupleNameSource = tupleNameSource; + return node; + } - // @api - function createCommaListExpression(elements: readonly Expression[]) { - const node = createBaseNode(SyntaxKind.CommaListExpression); - node.elements = createNodeArray(sameFlatMap(elements, flattenCommaElements)); - node.transformFlags |= propagateChildrenFlags(node.elements); - return node; - } + // @api + function createSyntaxList(children: Node[]) { + const node = createBaseNode(SyntaxKind.SyntaxList); + node._children = children; + return node; + } - // @api - function updateCommaListExpression(node: CommaListExpression, elements: readonly Expression[]) { - return node.elements !== elements - ? update(createCommaListExpression(elements), node) - : node; - } + // + // Transformation nodes + // - /** - * Creates a synthetic element to act as a placeholder for the end of an emitted declaration in - * order to properly emit exports. - */ - // @api - function createEndOfDeclarationMarker(original: Node) { - const node = createBaseNode(SyntaxKind.EndOfDeclarationMarker); - node.emitNode = {} as EmitNode; - node.original = original; - return node; - } + /** + * Creates a synthetic statement to act as a placeholder for a not-emitted statement in + * order to preserve comments. + * + * @param original The original statement. + */ + // @api + function createNotEmittedStatement(original: Node) { + const node = createBaseNode(SyntaxKind.NotEmittedStatement); + node.original = original; + setTextRange(node, original); + return node; + } - /** - * Creates a synthetic element to act as a placeholder for the beginning of a merged declaration in - * order to properly emit exports. - */ - // @api - function createMergeDeclarationMarker(original: Node) { - const node = createBaseNode(SyntaxKind.MergeDeclarationMarker); - node.emitNode = {} as EmitNode; - node.original = original; - return node; - } + /** + * Creates a synthetic expression to act as a placeholder for a not-emitted expression in + * order to preserve comments or sourcemap positions. + * + * @param expression The inner expression to emit. + * @param original The original outer expression. + */ + // @api + function createPartiallyEmittedExpression(expression: Expression, original?: Node) { + const node = createBaseNode(SyntaxKind.PartiallyEmittedExpression); + node.expression = expression; + node.original = original; + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsTypeScript; + setTextRange(node, original); + return node; + } - // @api - function createSyntheticReferenceExpression(expression: Expression, thisArg: Expression) { - const node = createBaseNode(SyntaxKind.SyntheticReferenceExpression); - node.expression = expression; - node.thisArg = thisArg; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.thisArg); - return node; - } + // @api + function updatePartiallyEmittedExpression(node: PartiallyEmittedExpression, expression: Expression) { + return node.expression !== expression + ? update(createPartiallyEmittedExpression(expression, node.original), node) + : node; + } - // @api - function updateSyntheticReferenceExpression(node: SyntheticReferenceExpression, expression: Expression, thisArg: Expression) { - return node.expression !== expression - || node.thisArg !== thisArg - ? update(createSyntheticReferenceExpression(expression, thisArg), node) - : node; + function flattenCommaElements(node: Expression): Expression | readonly Expression[] { + if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.emitNode && !node.id) { + if (isCommaListExpression(node)) { + return node.elements; + } + if (isBinaryExpression(node) && isCommaToken(node.operatorToken)) { + return [node.left, node.right]; + } } + return node; + } - // @api - function cloneNode(node: T): T; - function cloneNode(node: T) { - // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of - // the original node. We also need to exclude specific properties and only include own- - // properties (to skip members already defined on the shared prototype). - if (node === undefined) { - return node; - } + // @api + function createCommaListExpression(elements: readonly Expression[]) { + const node = createBaseNode(SyntaxKind.CommaListExpression); + node.elements = createNodeArray(sameFlatMap(elements, flattenCommaElements)); + node.transformFlags |= propagateChildrenFlags(node.elements); + return node; + } + + // @api + function updateCommaListExpression(node: CommaListExpression, elements: readonly Expression[]) { + return node.elements !== elements + ? update(createCommaListExpression(elements), node) + : node; + } - const clone = - isSourceFile(node) ? baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile) as T : - isIdentifier(node) ? baseFactory.createBaseIdentifierNode(SyntaxKind.Identifier) as T : - isPrivateIdentifier(node) ? baseFactory.createBasePrivateIdentifierNode(SyntaxKind.PrivateIdentifier) as T : - !isNodeKind(node.kind) ? baseFactory.createBaseTokenNode(node.kind) as T : - baseFactory.createBaseNode(node.kind) as T; + /** + * Creates a synthetic element to act as a placeholder for the end of an emitted declaration in + * order to properly emit exports. + */ + // @api + function createEndOfDeclarationMarker(original: Node) { + const node = createBaseNode(SyntaxKind.EndOfDeclarationMarker); + node.emitNode = {} as EmitNode; + node.original = original; + return node; + } - (clone as Mutable).flags |= (node.flags & ~NodeFlags.Synthesized); - (clone as Mutable).transformFlags = node.transformFlags; - setOriginalNode(clone, node); + /** + * Creates a synthetic element to act as a placeholder for the beginning of a merged declaration in + * order to properly emit exports. + */ + // @api + function createMergeDeclarationMarker(original: Node) { + const node = createBaseNode(SyntaxKind.MergeDeclarationMarker); + node.emitNode = {} as EmitNode; + node.original = original; + return node; + } - for (const key in node) { - if (clone.hasOwnProperty(key) || !node.hasOwnProperty(key)) { - continue; - } + // @api + function createSyntheticReferenceExpression(expression: Expression, thisArg: Expression) { + const node = createBaseNode(SyntaxKind.SyntheticReferenceExpression); + node.expression = expression; + node.thisArg = thisArg; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.thisArg); + return node; + } - clone[key] = node[key]; - } + // @api + function updateSyntheticReferenceExpression(node: SyntheticReferenceExpression, expression: Expression, thisArg: Expression) { + return node.expression !== expression + || node.thisArg !== thisArg + ? update(createSyntheticReferenceExpression(expression, thisArg), node) + : node; + } - return clone; + // @api + function cloneNode(node: T): T; + function cloneNode(node: T) { + // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of + // the original node. We also need to exclude specific properties and only include own- + // properties (to skip members already defined on the shared prototype). + if (node === undefined) { + return node; } - // compound nodes - function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[]): CallExpression; - function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; - function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { - return createCallExpression( - createFunctionExpression( - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ param ? [param] : [], - /*type*/ undefined, - createBlock(statements, /*multiLine*/ true) - ), - /*typeArguments*/ undefined, - /*argumentsArray*/ paramValue ? [paramValue] : [] - ); - } + const clone = + isSourceFile(node) ? baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile) as T : + isIdentifier(node) ? baseFactory.createBaseIdentifierNode(SyntaxKind.Identifier) as T : + isPrivateIdentifier(node) ? baseFactory.createBasePrivateIdentifierNode(SyntaxKind.PrivateIdentifier) as T : + !isNodeKind(node.kind) ? baseFactory.createBaseTokenNode(node.kind) as T : + baseFactory.createBaseNode(node.kind) as T; - function createImmediatelyInvokedArrowFunction(statements: readonly Statement[]): CallExpression; - function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; - function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { - return createCallExpression( - createArrowFunction( - /*modifiers*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ param ? [param] : [], - /*type*/ undefined, - /*equalsGreaterThanToken*/ undefined, - createBlock(statements, /*multiLine*/ true) - ), - /*typeArguments*/ undefined, - /*argumentsArray*/ paramValue ? [paramValue] : [] - ); - } + (clone as Mutable).flags |= (node.flags & ~NodeFlags.Synthesized); + (clone as Mutable).transformFlags = node.transformFlags; + setOriginalNode(clone, node); + + for (const key in node) { + if (clone.hasOwnProperty(key) || !node.hasOwnProperty(key)) { + continue; + } - function createVoidZero() { - return createVoidExpression(createNumericLiteral("0")); + clone[key] = node[key]; } - function createExportDefault(expression: Expression) { - return createExportAssignment( - /*decorators*/ undefined, + return clone; + } + + // compound nodes + function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[]): CallExpression; + function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; + function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { + return createCallExpression( + createFunctionExpression( /*modifiers*/ undefined, - /*isExportEquals*/ false, - expression); - } + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ param ? [param] : [], + /*type*/ undefined, + createBlock(statements, /*multiLine*/ true) + ), + /*typeArguments*/ undefined, + /*argumentsArray*/ paramValue ? [paramValue] : [] + ); + } - function createExternalModuleExport(exportName: Identifier) { - return createExportDeclaration( - /*decorators*/ undefined, + function createImmediatelyInvokedArrowFunction(statements: readonly Statement[]): CallExpression; + function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; + function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { + return createCallExpression( + createArrowFunction( /*modifiers*/ undefined, - /*isTypeOnly*/ false, - createNamedExports([ - createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, exportName) - ]) - ); - } + /*typeParameters*/ undefined, + /*parameters*/ param ? [param] : [], + /*type*/ undefined, + /*equalsGreaterThanToken*/ undefined, + createBlock(statements, /*multiLine*/ true) + ), + /*typeArguments*/ undefined, + /*argumentsArray*/ paramValue ? [paramValue] : [] + ); + } - // - // Utilities - // + function createVoidZero() { + return createVoidExpression(createNumericLiteral("0")); + } - function createTypeCheck(value: Expression, tag: TypeOfTag) { - return tag === "undefined" - ? factory.createStrictEquality(value, createVoidZero()) - : factory.createStrictEquality(createTypeOfExpression(value), createStringLiteral(tag)); - } + function createExportDefault(expression: Expression) { + return createExportAssignment( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isExportEquals*/ false, + expression); + } - function createMethodCall(object: Expression, methodName: string | Identifier, argumentsList: readonly Expression[]) { - // Preserve the optionality of `object`. - if (isCallChain(object)) { - return createCallChain( - createPropertyAccessChain(object, /*questionDotToken*/ undefined, methodName), - /*questionDotToken*/ undefined, - /*typeArguments*/ undefined, - argumentsList - ); - } - return createCallExpression( - createPropertyAccessExpression(object, methodName), + function createExternalModuleExport(exportName: Identifier) { + return createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + createNamedExports([ + createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, exportName) + ]) + ); + } + + // + // Utilities + // + + function createTypeCheck(value: Expression, tag: TypeOfTag) { + return tag === "undefined" + ? factory.createStrictEquality(value, createVoidZero()) + : factory.createStrictEquality(createTypeOfExpression(value), createStringLiteral(tag)); + } + + function createMethodCall(object: Expression, methodName: string | Identifier, argumentsList: readonly Expression[]) { + // Preserve the optionality of `object`. + if (isCallChain(object)) { + return createCallChain( + createPropertyAccessChain(object, /*questionDotToken*/ undefined, methodName), + /*questionDotToken*/ undefined, /*typeArguments*/ undefined, argumentsList ); } + return createCallExpression( + createPropertyAccessExpression(object, methodName), + /*typeArguments*/ undefined, + argumentsList + ); + } - function createFunctionBindCall(target: Expression, thisArg: Expression, argumentsList: readonly Expression[]) { - return createMethodCall(target, "bind", [thisArg, ...argumentsList]); - } + function createFunctionBindCall(target: Expression, thisArg: Expression, argumentsList: readonly Expression[]) { + return createMethodCall(target, "bind", [thisArg, ...argumentsList]); + } - function createFunctionCallCall(target: Expression, thisArg: Expression, argumentsList: readonly Expression[]) { - return createMethodCall(target, "call", [thisArg, ...argumentsList]); - } + function createFunctionCallCall(target: Expression, thisArg: Expression, argumentsList: readonly Expression[]) { + return createMethodCall(target, "call", [thisArg, ...argumentsList]); + } - function createFunctionApplyCall(target: Expression, thisArg: Expression, argumentsExpression: Expression) { - return createMethodCall(target, "apply", [thisArg, argumentsExpression]); - } + function createFunctionApplyCall(target: Expression, thisArg: Expression, argumentsExpression: Expression) { + return createMethodCall(target, "apply", [thisArg, argumentsExpression]); + } - function createGlobalMethodCall(globalObjectName: string, methodName: string, argumentsList: readonly Expression[]) { - return createMethodCall(createIdentifier(globalObjectName), methodName, argumentsList); - } + function createGlobalMethodCall(globalObjectName: string, methodName: string, argumentsList: readonly Expression[]) { + return createMethodCall(createIdentifier(globalObjectName), methodName, argumentsList); + } - function createArraySliceCall(array: Expression, start?: number | Expression) { - return createMethodCall(array, "slice", start === undefined ? [] : [asExpression(start)]); - } + function createArraySliceCall(array: Expression, start?: number | Expression) { + return createMethodCall(array, "slice", start === undefined ? [] : [asExpression(start)]); + } - function createArrayConcatCall(array: Expression, argumentsList: readonly Expression[]) { - return createMethodCall(array, "concat", argumentsList); - } + function createArrayConcatCall(array: Expression, argumentsList: readonly Expression[]) { + return createMethodCall(array, "concat", argumentsList); + } - function createObjectDefinePropertyCall(target: Expression, propertyName: string | Expression, attributes: Expression) { - return createGlobalMethodCall("Object", "defineProperty", [target, asExpression(propertyName), attributes]); - } + function createObjectDefinePropertyCall(target: Expression, propertyName: string | Expression, attributes: Expression) { + return createGlobalMethodCall("Object", "defineProperty", [target, asExpression(propertyName), attributes]); + } - function createReflectGetCall(target: Expression, propertyKey: Expression, receiver?: Expression): CallExpression { - return createGlobalMethodCall("Reflect", "get", receiver ? [target, propertyKey, receiver] : [target, propertyKey]); - } + function createReflectGetCall(target: Expression, propertyKey: Expression, receiver?: Expression): CallExpression { + return createGlobalMethodCall("Reflect", "get", receiver ? [target, propertyKey, receiver] : [target, propertyKey]); + } - function createReflectSetCall(target: Expression, propertyKey: Expression, value: Expression, receiver?: Expression): CallExpression { - return createGlobalMethodCall("Reflect", "set", receiver ? [target, propertyKey, value, receiver] : [target, propertyKey, value]); - } + function createReflectSetCall(target: Expression, propertyKey: Expression, value: Expression, receiver?: Expression): CallExpression { + return createGlobalMethodCall("Reflect", "set", receiver ? [target, propertyKey, value, receiver] : [target, propertyKey, value]); + } - function tryAddPropertyAssignment(properties: Push, propertyName: string, expression: Expression | undefined) { - if (expression) { - properties.push(createPropertyAssignment(propertyName, expression)); - return true; - } - return false; + function tryAddPropertyAssignment(properties: Push, propertyName: string, expression: Expression | undefined) { + if (expression) { + properties.push(createPropertyAssignment(propertyName, expression)); + return true; } + return false; + } - function createPropertyDescriptor(attributes: PropertyDescriptorAttributes, singleLine?: boolean) { - const properties: PropertyAssignment[] = []; - tryAddPropertyAssignment(properties, "enumerable", asExpression(attributes.enumerable)); - tryAddPropertyAssignment(properties, "configurable", asExpression(attributes.configurable)); + function createPropertyDescriptor(attributes: PropertyDescriptorAttributes, singleLine?: boolean) { + const properties: PropertyAssignment[] = []; + tryAddPropertyAssignment(properties, "enumerable", asExpression(attributes.enumerable)); + tryAddPropertyAssignment(properties, "configurable", asExpression(attributes.configurable)); - let isData = tryAddPropertyAssignment(properties, "writable", asExpression(attributes.writable)); - isData = tryAddPropertyAssignment(properties, "value", attributes.value) || isData; + let isData = tryAddPropertyAssignment(properties, "writable", asExpression(attributes.writable)); + isData = tryAddPropertyAssignment(properties, "value", attributes.value) || isData; - let isAccessor = tryAddPropertyAssignment(properties, "get", attributes.get); - isAccessor = tryAddPropertyAssignment(properties, "set", attributes.set) || isAccessor; + let isAccessor = tryAddPropertyAssignment(properties, "get", attributes.get); + isAccessor = tryAddPropertyAssignment(properties, "set", attributes.set) || isAccessor; - Debug.assert(!(isData && isAccessor), "A PropertyDescriptor may not be both an accessor descriptor and a data descriptor."); - return createObjectLiteralExpression(properties, !singleLine); - } + Debug.assert(!(isData && isAccessor), "A PropertyDescriptor may not be both an accessor descriptor and a data descriptor."); + return createObjectLiteralExpression(properties, !singleLine); + } - function updateOuterExpression(outerExpression: OuterExpression, expression: Expression) { - switch (outerExpression.kind) { - case SyntaxKind.ParenthesizedExpression: return updateParenthesizedExpression(outerExpression, expression); - case SyntaxKind.TypeAssertionExpression: return updateTypeAssertion(outerExpression, outerExpression.type, expression); - case SyntaxKind.AsExpression: return updateAsExpression(outerExpression, expression, outerExpression.type); - case SyntaxKind.NonNullExpression: return updateNonNullExpression(outerExpression, expression); - case SyntaxKind.PartiallyEmittedExpression: return updatePartiallyEmittedExpression(outerExpression, expression); - } + function updateOuterExpression(outerExpression: OuterExpression, expression: Expression) { + switch (outerExpression.kind) { + case SyntaxKind.ParenthesizedExpression: return updateParenthesizedExpression(outerExpression, expression); + case SyntaxKind.TypeAssertionExpression: return updateTypeAssertion(outerExpression, outerExpression.type, expression); + case SyntaxKind.AsExpression: return updateAsExpression(outerExpression, expression, outerExpression.type); + case SyntaxKind.NonNullExpression: return updateNonNullExpression(outerExpression, expression); + case SyntaxKind.PartiallyEmittedExpression: return updatePartiallyEmittedExpression(outerExpression, expression); } + } - /** - * Determines whether a node is a parenthesized expression that can be ignored when recreating outer expressions. - * - * A parenthesized expression can be ignored when all of the following are true: - * - * - It's `pos` and `end` are not -1 - * - It does not have a custom source map range - * - It does not have a custom comment range - * - It does not have synthetic leading or trailing comments - * - * If an outermost parenthesized expression is ignored, but the containing expression requires a parentheses around - * the expression to maintain precedence, a new parenthesized expression should be created automatically when - * the containing expression is created/updated. - */ - function isIgnorableParen(node: Expression) { - return isParenthesizedExpression(node) - && nodeIsSynthesized(node) - && nodeIsSynthesized(getSourceMapRange(node)) - && nodeIsSynthesized(getCommentRange(node)) - && !some(getSyntheticLeadingComments(node)) - && !some(getSyntheticTrailingComments(node)); - } + /** + * Determines whether a node is a parenthesized expression that can be ignored when recreating outer expressions. + * + * A parenthesized expression can be ignored when all of the following are true: + * + * - It's `pos` and `end` are not -1 + * - It does not have a custom source map range + * - It does not have a custom comment range + * - It does not have synthetic leading or trailing comments + * + * If an outermost parenthesized expression is ignored, but the containing expression requires a parentheses around + * the expression to maintain precedence, a new parenthesized expression should be created automatically when + * the containing expression is created/updated. + */ + function isIgnorableParen(node: Expression) { + return isParenthesizedExpression(node) + && nodeIsSynthesized(node) + && nodeIsSynthesized(getSourceMapRange(node)) + && nodeIsSynthesized(getCommentRange(node)) + && !some(getSyntheticLeadingComments(node)) + && !some(getSyntheticTrailingComments(node)); + } - function restoreOuterExpressions(outerExpression: Expression | undefined, innerExpression: Expression, kinds = OuterExpressionKinds.All): Expression { - if (outerExpression && isOuterExpression(outerExpression, kinds) && !isIgnorableParen(outerExpression)) { - return updateOuterExpression( - outerExpression, - restoreOuterExpressions(outerExpression.expression, innerExpression) - ); - } - return innerExpression; + function restoreOuterExpressions(outerExpression: Expression | undefined, innerExpression: Expression, kinds = OuterExpressionKinds.All): Expression { + if (outerExpression && isOuterExpression(outerExpression, kinds) && !isIgnorableParen(outerExpression)) { + return updateOuterExpression( + outerExpression, + restoreOuterExpressions(outerExpression.expression, innerExpression) + ); } + return innerExpression; + } - function restoreEnclosingLabel(node: Statement, outermostLabeledStatement: LabeledStatement | undefined, afterRestoreLabelCallback?: (node: LabeledStatement) => void): Statement { - if (!outermostLabeledStatement) { - return node; - } - const updated = updateLabeledStatement( - outermostLabeledStatement, - outermostLabeledStatement.label, - isLabeledStatement(outermostLabeledStatement.statement) - ? restoreEnclosingLabel(node, outermostLabeledStatement.statement) - : node - ); - if (afterRestoreLabelCallback) { - afterRestoreLabelCallback(outermostLabeledStatement); - } - return updated; + function restoreEnclosingLabel(node: Statement, outermostLabeledStatement: LabeledStatement | undefined, afterRestoreLabelCallback?: (node: LabeledStatement) => void): Statement { + if (!outermostLabeledStatement) { + return node; } + const updated = updateLabeledStatement( + outermostLabeledStatement, + outermostLabeledStatement.label, + isLabeledStatement(outermostLabeledStatement.statement) + ? restoreEnclosingLabel(node, outermostLabeledStatement.statement) + : node + ); + if (afterRestoreLabelCallback) { + afterRestoreLabelCallback(outermostLabeledStatement); + } + return updated; + } - function shouldBeCapturedInTempVariable(node: Expression, cacheIdentifiers: boolean): boolean { - const target = skipParentheses(node); - switch (target.kind) { - case SyntaxKind.Identifier: - return cacheIdentifiers; - case SyntaxKind.ThisKeyword: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.StringLiteral: + function shouldBeCapturedInTempVariable(node: Expression, cacheIdentifiers: boolean): boolean { + const target = skipParentheses(node); + switch (target.kind) { + case SyntaxKind.Identifier: + return cacheIdentifiers; + case SyntaxKind.ThisKeyword: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.StringLiteral: + return false; + case SyntaxKind.ArrayLiteralExpression: + const elements = (target as ArrayLiteralExpression).elements; + if (elements.length === 0) { return false; - case SyntaxKind.ArrayLiteralExpression: - const elements = (target as ArrayLiteralExpression).elements; - if (elements.length === 0) { - return false; - } - return true; - case SyntaxKind.ObjectLiteralExpression: - return (target as ObjectLiteralExpression).properties.length > 0; - default: - return true; - } + } + return true; + case SyntaxKind.ObjectLiteralExpression: + return (target as ObjectLiteralExpression).properties.length > 0; + default: + return true; } + } - function createCallBinding(expression: Expression, recordTempVariable: (temp: Identifier) => void, languageVersion?: ScriptTarget, cacheIdentifiers = false): CallBinding { - const callee = skipOuterExpressions(expression, OuterExpressionKinds.All); - let thisArg: Expression; - let target: LeftHandSideExpression; - if (isSuperProperty(callee)) { - thisArg = createThis(); - target = callee; - } - else if (isSuperKeyword(callee)) { - thisArg = createThis(); - target = languageVersion !== undefined && languageVersion < ScriptTarget.ES2015 - ? setTextRange(createIdentifier("_super"), callee) - : callee as PrimaryExpression; - } - else if (getEmitFlags(callee) & EmitFlags.HelperName) { - thisArg = createVoidZero(); - target = parenthesizerRules().parenthesizeLeftSideOfAccess(callee); - } - else if (isPropertyAccessExpression(callee)) { - if (shouldBeCapturedInTempVariable(callee.expression, cacheIdentifiers)) { - // for `a.b()` target is `(_a = a).b` and thisArg is `_a` - thisArg = createTempVariable(recordTempVariable); - target = createPropertyAccessExpression( - setTextRange( - factory.createAssignment( - thisArg, - callee.expression - ), + function createCallBinding(expression: Expression, recordTempVariable: (temp: Identifier) => void, languageVersion?: ScriptTarget, cacheIdentifiers = false): CallBinding { + const callee = skipOuterExpressions(expression, OuterExpressionKinds.All); + let thisArg: Expression; + let target: LeftHandSideExpression; + if (isSuperProperty(callee)) { + thisArg = createThis(); + target = callee; + } + else if (isSuperKeyword(callee)) { + thisArg = createThis(); + target = languageVersion !== undefined && languageVersion < ScriptTarget.ES2015 + ? setTextRange(createIdentifier("_super"), callee) + : callee as PrimaryExpression; + } + else if (getEmitFlags(callee) & EmitFlags.HelperName) { + thisArg = createVoidZero(); + target = parenthesizerRules().parenthesizeLeftSideOfAccess(callee); + } + else if (isPropertyAccessExpression(callee)) { + if (shouldBeCapturedInTempVariable(callee.expression, cacheIdentifiers)) { + // for `a.b()` target is `(_a = a).b` and thisArg is `_a` + thisArg = createTempVariable(recordTempVariable); + target = createPropertyAccessExpression( + setTextRange( + factory.createAssignment( + thisArg, callee.expression ), - callee.name - ); - setTextRange(target, callee); - } - else { - thisArg = callee.expression; - target = callee; - } + callee.expression + ), + callee.name + ); + setTextRange(target, callee); + } + else { + thisArg = callee.expression; + target = callee; } - else if (isElementAccessExpression(callee)) { - if (shouldBeCapturedInTempVariable(callee.expression, cacheIdentifiers)) { - // for `a[b]()` target is `(_a = a)[b]` and thisArg is `_a` - thisArg = createTempVariable(recordTempVariable); - target = createElementAccessExpression( - setTextRange( - factory.createAssignment( - thisArg, - callee.expression - ), + } + else if (isElementAccessExpression(callee)) { + if (shouldBeCapturedInTempVariable(callee.expression, cacheIdentifiers)) { + // for `a[b]()` target is `(_a = a)[b]` and thisArg is `_a` + thisArg = createTempVariable(recordTempVariable); + target = createElementAccessExpression( + setTextRange( + factory.createAssignment( + thisArg, callee.expression ), - callee.argumentExpression - ); - setTextRange(target, callee); - } - else { - thisArg = callee.expression; - target = callee; - } + callee.expression + ), + callee.argumentExpression + ); + setTextRange(target, callee); } else { - // for `a()` target is `a` and thisArg is `void 0` - thisArg = createVoidZero(); - target = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); - } - - return { target, thisArg }; - } - - function createAssignmentTargetWrapper(paramName: Identifier, expression: Expression): LeftHandSideExpression { - return createPropertyAccessExpression( - // Explicit parens required because of v8 regression (https://bugs.chromium.org/p/v8/issues/detail?id=9560) - createParenthesizedExpression( - createObjectLiteralExpression([ - createSetAccessorDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - "value", - [createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - paramName, - /*questionToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined - )], - createBlock([ - createExpressionStatement(expression) - ]) - ) - ]) - ), - "value" - ); - } - - function inlineExpressions(expressions: readonly Expression[]) { - // Avoid deeply nested comma expressions as traversing them during emit can result in "Maximum call - // stack size exceeded" errors. - return expressions.length > 10 - ? createCommaListExpression(expressions) - : reduceLeft(expressions, factory.createComma)!; - } - - function getName(node: Declaration | undefined, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags: EmitFlags = 0) { - const nodeName = getNameOfDeclaration(node); - if (nodeName && isIdentifier(nodeName) && !isGeneratedIdentifier(nodeName)) { - // TODO(rbuckton): Does this need to be parented? - const name = setParent(setTextRange(cloneNode(nodeName), nodeName), nodeName.parent); - emitFlags |= getEmitFlags(nodeName); - if (!allowSourceMaps) emitFlags |= EmitFlags.NoSourceMap; - if (!allowComments) emitFlags |= EmitFlags.NoComments; - if (emitFlags) setEmitFlags(name, emitFlags); - return name; + thisArg = callee.expression; + target = callee; } - return getGeneratedNameForNode(node); } - - /** - * Gets the internal name of a declaration. This is primarily used for declarations that can be - * referred to by name in the body of an ES5 class function body. An internal name will *never* - * be prefixed with an module or namespace export modifier like "exports." when emitted as an - * expression. An internal name will also *never* be renamed due to a collision with a block - * scoped variable. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - function getInternalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { - return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName | EmitFlags.InternalName); + else { + // for `a()` target is `a` and thisArg is `void 0` + thisArg = createVoidZero(); + target = parenthesizerRules().parenthesizeLeftSideOfAccess(expression); } - /** - * Gets the local name of a declaration. This is primarily used for declarations that can be - * referred to by name in the declaration's immediate scope (classes, enums, namespaces). A - * local name will *never* be prefixed with an module or namespace export modifier like - * "exports." when emitted as an expression. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - function getLocalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { - return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName); - } + return { target, thisArg }; + } - /** - * Gets the export name of a declaration. This is primarily used for declarations that can be - * referred to by name in the declaration's immediate scope (classes, enums, namespaces). An - * export name will *always* be prefixed with an module or namespace export modifier like - * `"exports."` when emitted as an expression if the name points to an exported symbol. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - function getExportName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier { - return getName(node, allowComments, allowSourceMaps, EmitFlags.ExportName); - } + function createAssignmentTargetWrapper(paramName: Identifier, expression: Expression): LeftHandSideExpression { + return createPropertyAccessExpression( + // Explicit parens required because of v8 regression (https://bugs.chromium.org/p/v8/issues/detail?id=9560) + createParenthesizedExpression( + createObjectLiteralExpression([ + createSetAccessorDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + "value", + [createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + paramName, + /*questionToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + )], + createBlock([ + createExpressionStatement(expression) + ]) + ) + ]) + ), + "value" + ); + } - /** - * Gets the name of a declaration for use in declarations. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - function getDeclarationName(node: Declaration | undefined, allowComments?: boolean, allowSourceMaps?: boolean) { - return getName(node, allowComments, allowSourceMaps); - } + function inlineExpressions(expressions: readonly Expression[]) { + // Avoid deeply nested comma expressions as traversing them during emit can result in "Maximum call + // stack size exceeded" errors. + return expressions.length > 10 + ? createCommaListExpression(expressions) + : reduceLeft(expressions, factory.createComma)!; + } - /** - * Gets a namespace-qualified name for use in expressions. - * - * @param ns The namespace identifier. - * @param name The name. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - function getNamespaceMemberName(ns: Identifier, name: Identifier, allowComments?: boolean, allowSourceMaps?: boolean): PropertyAccessExpression { - const qualifiedName = createPropertyAccessExpression(ns, nodeIsSynthesized(name) ? name : cloneNode(name)); - setTextRange(qualifiedName, name); - let emitFlags: EmitFlags = 0; + function getName(node: Declaration | undefined, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags: EmitFlags = 0) { + const nodeName = getNameOfDeclaration(node); + if (nodeName && isIdentifier(nodeName) && !isGeneratedIdentifier(nodeName)) { + // TODO(rbuckton): Does this need to be parented? + const name = setParent(setTextRange(cloneNode(nodeName), nodeName), nodeName.parent); + emitFlags |= getEmitFlags(nodeName); if (!allowSourceMaps) emitFlags |= EmitFlags.NoSourceMap; if (!allowComments) emitFlags |= EmitFlags.NoComments; - if (emitFlags) setEmitFlags(qualifiedName, emitFlags); - return qualifiedName; + if (emitFlags) setEmitFlags(name, emitFlags); + return name; } + return getGeneratedNameForNode(node); + } - /** - * Gets the exported name of a declaration for use in expressions. - * - * An exported name will *always* be prefixed with an module or namespace export modifier like - * "exports." if the name points to an exported symbol. - * - * @param ns The namespace identifier. - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - function getExternalModuleOrNamespaceExportName(ns: Identifier | undefined, node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier | PropertyAccessExpression { - if (ns && hasSyntacticModifier(node, ModifierFlags.Export)) { - return getNamespaceMemberName(ns, getName(node), allowComments, allowSourceMaps); - } - return getExportName(node, allowComments, allowSourceMaps); - } + /** + * Gets the internal name of a declaration. This is primarily used for declarations that can be + * referred to by name in the body of an ES5 class function body. An internal name will *never* + * be prefixed with an module or namespace export modifier like "exports." when emitted as an + * expression. An internal name will also *never* be renamed due to a collision with a block + * scoped variable. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + function getInternalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { + return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName | EmitFlags.InternalName); + } - /** - * Copies any necessary standard and custom prologue-directives into target array. - * @param source origin statements array - * @param target result statements array - * @param ensureUseStrict boolean determining whether the function need to add prologue-directives - * @param visitor Optional callback used to visit any custom prologue directives. - */ - function copyPrologue(source: readonly Statement[], target: Push, ensureUseStrict?: boolean, visitor?: (node: Node) => VisitResult): number { - const offset = copyStandardPrologue(source, target, ensureUseStrict); - return copyCustomPrologue(source, target, offset, visitor); - } + /** + * Gets the local name of a declaration. This is primarily used for declarations that can be + * referred to by name in the declaration's immediate scope (classes, enums, namespaces). A + * local name will *never* be prefixed with an module or namespace export modifier like + * "exports." when emitted as an expression. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + function getLocalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { + return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName); + } - function isUseStrictPrologue(node: ExpressionStatement): boolean { - return isStringLiteral(node.expression) && node.expression.text === "use strict"; - } + /** + * Gets the export name of a declaration. This is primarily used for declarations that can be + * referred to by name in the declaration's immediate scope (classes, enums, namespaces). An + * export name will *always* be prefixed with an module or namespace export modifier like + * `"exports."` when emitted as an expression if the name points to an exported symbol. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + function getExportName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier { + return getName(node, allowComments, allowSourceMaps, EmitFlags.ExportName); + } + + /** + * Gets the name of a declaration for use in declarations. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + function getDeclarationName(node: Declaration | undefined, allowComments?: boolean, allowSourceMaps?: boolean) { + return getName(node, allowComments, allowSourceMaps); + } + + /** + * Gets a namespace-qualified name for use in expressions. + * + * @param ns The namespace identifier. + * @param name The name. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + function getNamespaceMemberName(ns: Identifier, name: Identifier, allowComments?: boolean, allowSourceMaps?: boolean): PropertyAccessExpression { + const qualifiedName = createPropertyAccessExpression(ns, nodeIsSynthesized(name) ? name : cloneNode(name)); + setTextRange(qualifiedName, name); + let emitFlags: EmitFlags = 0; + if (!allowSourceMaps) emitFlags |= EmitFlags.NoSourceMap; + if (!allowComments) emitFlags |= EmitFlags.NoComments; + if (emitFlags) setEmitFlags(qualifiedName, emitFlags); + return qualifiedName; + } - function createUseStrictPrologue() { - return startOnNewLine(createExpressionStatement(createStringLiteral("use strict"))) as PrologueDirective; + /** + * Gets the exported name of a declaration for use in expressions. + * + * An exported name will *always* be prefixed with an module or namespace export modifier like + * "exports." if the name points to an exported symbol. + * + * @param ns The namespace identifier. + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + function getExternalModuleOrNamespaceExportName(ns: Identifier | undefined, node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier | PropertyAccessExpression { + if (ns && hasSyntacticModifier(node, ModifierFlags.Export)) { + return getNamespaceMemberName(ns, getName(node), allowComments, allowSourceMaps); } + return getExportName(node, allowComments, allowSourceMaps); + } - /** - * Copies only the standard (string-expression) prologue-directives into the target statement-array. - * @param source origin statements array - * @param target result statements array - * @param ensureUseStrict boolean determining whether the function need to add prologue-directives - */ - function copyStandardPrologue(source: readonly Statement[], target: Push, ensureUseStrict?: boolean): number { - Debug.assert(target.length === 0, "Prologue directives should be at the first statement in the target statements array"); - let foundUseStrict = false; - let statementOffset = 0; - const numStatements = source.length; - while (statementOffset < numStatements) { - const statement = source[statementOffset]; - if (isPrologueDirective(statement)) { - if (isUseStrictPrologue(statement)) { - foundUseStrict = true; - } - target.push(statement); - } - else { - break; + /** + * Copies any necessary standard and custom prologue-directives into target array. + * @param source origin statements array + * @param target result statements array + * @param ensureUseStrict boolean determining whether the function need to add prologue-directives + * @param visitor Optional callback used to visit any custom prologue directives. + */ + function copyPrologue(source: readonly Statement[], target: Push, ensureUseStrict?: boolean, visitor?: (node: Node) => VisitResult): number { + const offset = copyStandardPrologue(source, target, ensureUseStrict); + return copyCustomPrologue(source, target, offset, visitor); + } + + function isUseStrictPrologue(node: ExpressionStatement): boolean { + return isStringLiteral(node.expression) && node.expression.text === "use strict"; + } + + function createUseStrictPrologue() { + return startOnNewLine(createExpressionStatement(createStringLiteral("use strict"))) as PrologueDirective; + } + + /** + * Copies only the standard (string-expression) prologue-directives into the target statement-array. + * @param source origin statements array + * @param target result statements array + * @param ensureUseStrict boolean determining whether the function need to add prologue-directives + */ + function copyStandardPrologue(source: readonly Statement[], target: Push, ensureUseStrict?: boolean): number { + Debug.assert(target.length === 0, "Prologue directives should be at the first statement in the target statements array"); + let foundUseStrict = false; + let statementOffset = 0; + const numStatements = source.length; + while (statementOffset < numStatements) { + const statement = source[statementOffset]; + if (isPrologueDirective(statement)) { + if (isUseStrictPrologue(statement)) { + foundUseStrict = true; } - statementOffset++; + target.push(statement); } - if (ensureUseStrict && !foundUseStrict) { - target.push(createUseStrictPrologue()); + else { + break; } - return statementOffset; + statementOffset++; } + if (ensureUseStrict && !foundUseStrict) { + target.push(createUseStrictPrologue()); + } + return statementOffset; + } - /** - * Copies only the custom prologue-directives into target statement-array. - * @param source origin statements array - * @param target result statements array - * @param statementOffset The offset at which to begin the copy. - * @param visitor Optional callback used to visit any custom prologue directives. - */ - function copyCustomPrologue(source: readonly Statement[], target: Push, statementOffset: number, visitor?: (node: Node) => VisitResult, filter?: (node: Node) => boolean): number; - function copyCustomPrologue(source: readonly Statement[], target: Push, statementOffset: number | undefined, visitor?: (node: Node) => VisitResult, filter?: (node: Node) => boolean): number | undefined; - function copyCustomPrologue(source: readonly Statement[], target: Push, statementOffset: number | undefined, visitor?: (node: Node) => VisitResult, filter: (node: Node) => boolean = returnTrue): number | undefined { - const numStatements = source.length; - while (statementOffset !== undefined && statementOffset < numStatements) { - const statement = source[statementOffset]; - if (getEmitFlags(statement) & EmitFlags.CustomPrologue && filter(statement)) { - append(target, visitor ? visitNode(statement, visitor, isStatement) : statement); - } - else { - break; - } - statementOffset++; + /** + * Copies only the custom prologue-directives into target statement-array. + * @param source origin statements array + * @param target result statements array + * @param statementOffset The offset at which to begin the copy. + * @param visitor Optional callback used to visit any custom prologue directives. + */ + function copyCustomPrologue(source: readonly Statement[], target: Push, statementOffset: number, visitor?: (node: Node) => VisitResult, filter?: (node: Node) => boolean): number; + function copyCustomPrologue(source: readonly Statement[], target: Push, statementOffset: number | undefined, visitor?: (node: Node) => VisitResult, filter?: (node: Node) => boolean): number | undefined; + function copyCustomPrologue(source: readonly Statement[], target: Push, statementOffset: number | undefined, visitor?: (node: Node) => VisitResult, filter: (node: Node) => boolean = returnTrue): number | undefined { + const numStatements = source.length; + while (statementOffset !== undefined && statementOffset < numStatements) { + const statement = source[statementOffset]; + if (getEmitFlags(statement) & EmitFlags.CustomPrologue && filter(statement)) { + append(target, visitor ? visitNode(statement, visitor, isStatement) : statement); } - return statementOffset; + else { + break; + } + statementOffset++; } + return statementOffset; + } - /** - * Ensures "use strict" directive is added - * - * @param statements An array of statements - */ - function ensureUseStrict(statements: NodeArray): NodeArray { - const foundUseStrict = findUseStrictPrologue(statements); - - if (!foundUseStrict) { - return setTextRange(createNodeArray([createUseStrictPrologue(), ...statements]), statements); - } + /** + * Ensures "use strict" directive is added + * + * @param statements An array of statements + */ + function ensureUseStrict(statements: NodeArray): NodeArray { + const foundUseStrict = findUseStrictPrologue(statements); - return statements; + if (!foundUseStrict) { + return setTextRange(createNodeArray([createUseStrictPrologue(), ...statements]), statements); } - /** - * Lifts a NodeArray containing only Statement nodes to a block. - * - * @param nodes The NodeArray. - */ - function liftToBlock(nodes: readonly Node[]): Statement { - Debug.assert(every(nodes, isStatementOrBlock), "Cannot lift nodes to a Block."); - return singleOrUndefined(nodes) as Statement || createBlock(nodes as readonly Statement[]); - } + return statements; + } - function findSpanEnd(array: readonly T[], test: (value: T) => boolean, start: number) { - let i = start; - while (i < array.length && test(array[i])) { - i++; - } - return i; - } + /** + * Lifts a NodeArray containing only Statement nodes to a block. + * + * @param nodes The NodeArray. + */ + function liftToBlock(nodes: readonly Node[]): Statement { + Debug.assert(every(nodes, isStatementOrBlock), "Cannot lift nodes to a Block."); + return singleOrUndefined(nodes) as Statement || createBlock(nodes as readonly Statement[]); + } - function mergeLexicalEnvironment(statements: NodeArray, declarations: readonly Statement[] | undefined): NodeArray; - function mergeLexicalEnvironment(statements: Statement[], declarations: readonly Statement[] | undefined): Statement[]; - function mergeLexicalEnvironment(statements: Statement[] | NodeArray, declarations: readonly Statement[] | undefined) { - if (!some(declarations)) { - return statements; - } + function findSpanEnd(array: readonly T[], test: (value: T) => boolean, start: number) { + let i = start; + while (i < array.length && test(array[i])) { + i++; + } + return i; + } - // When we merge new lexical statements into an existing statement list, we merge them in the following manner: - // - // Given: - // - // | Left | Right | - // |------------------------------------|-------------------------------------| - // | [standard prologues (left)] | [standard prologues (right)] | - // | [hoisted functions (left)] | [hoisted functions (right)] | - // | [hoisted variables (left)] | [hoisted variables (right)] | - // | [lexical init statements (left)] | [lexical init statements (right)] | - // | [other statements (left)] | | - // - // The resulting statement list will be: - // - // | Result | - // |-------------------------------------| - // | [standard prologues (right)] | - // | [standard prologues (left)] | - // | [hoisted functions (right)] | - // | [hoisted functions (left)] | - // | [hoisted variables (right)] | - // | [hoisted variables (left)] | - // | [lexical init statements (right)] | - // | [lexical init statements (left)] | - // | [other statements (left)] | - // - // NOTE: It is expected that new lexical init statements must be evaluated before existing lexical init statements, - // as the prior transformation may depend on the evaluation of the lexical init statements to be in the correct state. - - // find standard prologues on left in the following order: standard directives, hoisted functions, hoisted variables, other custom - const leftStandardPrologueEnd = findSpanEnd(statements, isPrologueDirective, 0); - const leftHoistedFunctionsEnd = findSpanEnd(statements, isHoistedFunction, leftStandardPrologueEnd); - const leftHoistedVariablesEnd = findSpanEnd(statements, isHoistedVariableStatement, leftHoistedFunctionsEnd); - - // find standard prologues on right in the following order: standard directives, hoisted functions, hoisted variables, other custom - const rightStandardPrologueEnd = findSpanEnd(declarations, isPrologueDirective, 0); - const rightHoistedFunctionsEnd = findSpanEnd(declarations, isHoistedFunction, rightStandardPrologueEnd); - const rightHoistedVariablesEnd = findSpanEnd(declarations, isHoistedVariableStatement, rightHoistedFunctionsEnd); - const rightCustomPrologueEnd = findSpanEnd(declarations, isCustomPrologue, rightHoistedVariablesEnd); - Debug.assert(rightCustomPrologueEnd === declarations.length, "Expected declarations to be valid standard or custom prologues"); - - // splice prologues from the right into the left. We do this in reverse order - // so that we don't need to recompute the index on the left when we insert items. - const left = isNodeArray(statements) ? statements.slice() : statements; - - // splice other custom prologues from right into left - if (rightCustomPrologueEnd > rightHoistedVariablesEnd) { - left.splice(leftHoistedVariablesEnd, 0, ...declarations.slice(rightHoistedVariablesEnd, rightCustomPrologueEnd)); - } + function mergeLexicalEnvironment(statements: NodeArray, declarations: readonly Statement[] | undefined): NodeArray; + function mergeLexicalEnvironment(statements: Statement[], declarations: readonly Statement[] | undefined): Statement[]; + function mergeLexicalEnvironment(statements: Statement[] | NodeArray, declarations: readonly Statement[] | undefined) { + if (!some(declarations)) { + return statements; + } - // splice hoisted variables from right into left - if (rightHoistedVariablesEnd > rightHoistedFunctionsEnd) { - left.splice(leftHoistedFunctionsEnd, 0, ...declarations.slice(rightHoistedFunctionsEnd, rightHoistedVariablesEnd)); - } + // When we merge new lexical statements into an existing statement list, we merge them in the following manner: + // + // Given: + // + // | Left | Right | + // |------------------------------------|-------------------------------------| + // | [standard prologues (left)] | [standard prologues (right)] | + // | [hoisted functions (left)] | [hoisted functions (right)] | + // | [hoisted variables (left)] | [hoisted variables (right)] | + // | [lexical init statements (left)] | [lexical init statements (right)] | + // | [other statements (left)] | | + // + // The resulting statement list will be: + // + // | Result | + // |-------------------------------------| + // | [standard prologues (right)] | + // | [standard prologues (left)] | + // | [hoisted functions (right)] | + // | [hoisted functions (left)] | + // | [hoisted variables (right)] | + // | [hoisted variables (left)] | + // | [lexical init statements (right)] | + // | [lexical init statements (left)] | + // | [other statements (left)] | + // + // NOTE: It is expected that new lexical init statements must be evaluated before existing lexical init statements, + // as the prior transformation may depend on the evaluation of the lexical init statements to be in the correct state. - // splice hoisted functions from right into left - if (rightHoistedFunctionsEnd > rightStandardPrologueEnd) { - left.splice(leftStandardPrologueEnd, 0, ...declarations.slice(rightStandardPrologueEnd, rightHoistedFunctionsEnd)); - } + // find standard prologues on left in the following order: standard directives, hoisted functions, hoisted variables, other custom + const leftStandardPrologueEnd = findSpanEnd(statements, isPrologueDirective, 0); + const leftHoistedFunctionsEnd = findSpanEnd(statements, isHoistedFunction, leftStandardPrologueEnd); + const leftHoistedVariablesEnd = findSpanEnd(statements, isHoistedVariableStatement, leftHoistedFunctionsEnd); - // splice standard prologues from right into left (that are not already in left) - if (rightStandardPrologueEnd > 0) { - if (leftStandardPrologueEnd === 0) { - left.splice(0, 0, ...declarations.slice(0, rightStandardPrologueEnd)); - } - else { - const leftPrologues = new Map(); - for (let i = 0; i < leftStandardPrologueEnd; i++) { - const leftPrologue = statements[i] as PrologueDirective; - leftPrologues.set(leftPrologue.expression.text, true); - } - for (let i = rightStandardPrologueEnd - 1; i >= 0; i--) { - const rightPrologue = declarations[i] as PrologueDirective; - if (!leftPrologues.has(rightPrologue.expression.text)) { - left.unshift(rightPrologue); - } - } - } - } + // find standard prologues on right in the following order: standard directives, hoisted functions, hoisted variables, other custom + const rightStandardPrologueEnd = findSpanEnd(declarations, isPrologueDirective, 0); + const rightHoistedFunctionsEnd = findSpanEnd(declarations, isHoistedFunction, rightStandardPrologueEnd); + const rightHoistedVariablesEnd = findSpanEnd(declarations, isHoistedVariableStatement, rightHoistedFunctionsEnd); + const rightCustomPrologueEnd = findSpanEnd(declarations, isCustomPrologue, rightHoistedVariablesEnd); + Debug.assert(rightCustomPrologueEnd === declarations.length, "Expected declarations to be valid standard or custom prologues"); - if (isNodeArray(statements)) { - return setTextRange(createNodeArray(left, statements.hasTrailingComma), statements); - } + // splice prologues from the right into the left. We do this in reverse order + // so that we don't need to recompute the index on the left when we insert items. + const left = isNodeArray(statements) ? statements.slice() : statements; - return statements; + // splice other custom prologues from right into left + if (rightCustomPrologueEnd > rightHoistedVariablesEnd) { + left.splice(leftHoistedVariablesEnd, 0, ...declarations.slice(rightHoistedVariablesEnd, rightCustomPrologueEnd)); } - function updateModifiers(node: T, modifiers: readonly Modifier[] | ModifierFlags): T; - function updateModifiers(node: HasModifiers, modifiers: readonly Modifier[] | ModifierFlags) { - if (typeof modifiers === "number") { - modifiers = createModifiersFromModifierFlags(modifiers); - } - return isParameter(node) ? updateParameterDeclaration(node, node.decorators, modifiers, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer) : - isPropertySignature(node) ? updatePropertySignature(node, modifiers, node.name, node.questionToken, node.type) : - isPropertyDeclaration(node) ? updatePropertyDeclaration(node, node.decorators, modifiers, node.name, node.questionToken ?? node.exclamationToken, node.type, node.initializer) : - isMethodSignature(node) ? updateMethodSignature(node, modifiers, node.name, node.questionToken, node.typeParameters, node.parameters, node.type) : - isMethodDeclaration(node) ? updateMethodDeclaration(node, node.decorators, modifiers, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body) : - isConstructorDeclaration(node) ? updateConstructorDeclaration(node, node.decorators, modifiers, node.parameters, node.body) : - isGetAccessorDeclaration(node) ? updateGetAccessorDeclaration(node, node.decorators, modifiers, node.name, node.parameters, node.type, node.body) : - isSetAccessorDeclaration(node) ? updateSetAccessorDeclaration(node, node.decorators, modifiers, node.name, node.parameters, node.body) : - isIndexSignatureDeclaration(node) ? updateIndexSignature(node, node.decorators, modifiers, node.parameters, node.type) : - isFunctionExpression(node) ? updateFunctionExpression(node, modifiers, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : - isArrowFunction(node) ? updateArrowFunction(node, modifiers, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, node.body) : - isClassExpression(node) ? updateClassExpression(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) : - isVariableStatement(node) ? updateVariableStatement(node, modifiers, node.declarationList) : - isFunctionDeclaration(node) ? updateFunctionDeclaration(node, node.decorators, modifiers, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : - isClassDeclaration(node) ? updateClassDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) : - isInterfaceDeclaration(node) ? updateInterfaceDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) : - isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.type) : - isEnumDeclaration(node) ? updateEnumDeclaration(node, node.decorators, modifiers, node.name, node.members) : - isModuleDeclaration(node) ? updateModuleDeclaration(node, node.decorators, modifiers, node.name, node.body) : - isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, node.decorators, modifiers, node.isTypeOnly, node.name, node.moduleReference) : - isImportDeclaration(node) ? updateImportDeclaration(node, node.decorators, modifiers, node.importClause, node.moduleSpecifier, node.assertClause) : - isExportAssignment(node) ? updateExportAssignment(node, node.decorators, modifiers, node.expression) : - isExportDeclaration(node) ? updateExportDeclaration(node, node.decorators, modifiers, node.isTypeOnly, node.exportClause, node.moduleSpecifier, node.assertClause) : - Debug.assertNever(node); + // splice hoisted variables from right into left + if (rightHoistedVariablesEnd > rightHoistedFunctionsEnd) { + left.splice(leftHoistedFunctionsEnd, 0, ...declarations.slice(rightHoistedFunctionsEnd, rightHoistedVariablesEnd)); } - function asNodeArray(array: readonly T[]): NodeArray; - function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined; - function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined { - return array ? createNodeArray(array) : undefined; + // splice hoisted functions from right into left + if (rightHoistedFunctionsEnd > rightStandardPrologueEnd) { + left.splice(leftStandardPrologueEnd, 0, ...declarations.slice(rightStandardPrologueEnd, rightHoistedFunctionsEnd)); } - function asName(name: string | T): T | Identifier { - return typeof name === "string" ? createIdentifier(name) : - name; + // splice standard prologues from right into left (that are not already in left) + if (rightStandardPrologueEnd > 0) { + if (leftStandardPrologueEnd === 0) { + left.splice(0, 0, ...declarations.slice(0, rightStandardPrologueEnd)); + } + else { + const leftPrologues = new Map(); + for (let i = 0; i < leftStandardPrologueEnd; i++) { + const leftPrologue = statements[i] as PrologueDirective; + leftPrologues.set(leftPrologue.expression.text, true); + } + for (let i = rightStandardPrologueEnd - 1; i >= 0; i--) { + const rightPrologue = declarations[i] as PrologueDirective; + if (!leftPrologues.has(rightPrologue.expression.text)) { + left.unshift(rightPrologue); + } + } + } } - function asExpression(value: string | number | boolean | T): T | StringLiteral | NumericLiteral | BooleanLiteral { - return typeof value === "string" ? createStringLiteral(value) : - typeof value === "number" ? createNumericLiteral(value) : - typeof value === "boolean" ? value ? createTrue() : createFalse() : - value; + if (isNodeArray(statements)) { + return setTextRange(createNodeArray(left, statements.hasTrailingComma), statements); } - function asToken(value: TKind | Token): Token { - return typeof value === "number" ? createToken(value) : value; - } + return statements; + } - function asEmbeddedStatement(statement: T): T | EmptyStatement; - function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined; - function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined { - return statement && isNotEmittedStatement(statement) ? setTextRange(setOriginalNode(createEmptyStatement(), statement), statement) : statement; - } + function updateModifiers(node: T, modifiers: readonly Modifier[] | ModifierFlags): T; + function updateModifiers(node: HasModifiers, modifiers: readonly Modifier[] | ModifierFlags) { + if (typeof modifiers === "number") { + modifiers = createModifiersFromModifierFlags(modifiers); + } + return isParameter(node) ? updateParameterDeclaration(node, node.decorators, modifiers, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer) : + isPropertySignature(node) ? updatePropertySignature(node, modifiers, node.name, node.questionToken, node.type) : + isPropertyDeclaration(node) ? updatePropertyDeclaration(node, node.decorators, modifiers, node.name, node.questionToken ?? node.exclamationToken, node.type, node.initializer) : + isMethodSignature(node) ? updateMethodSignature(node, modifiers, node.name, node.questionToken, node.typeParameters, node.parameters, node.type) : + isMethodDeclaration(node) ? updateMethodDeclaration(node, node.decorators, modifiers, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body) : + isConstructorDeclaration(node) ? updateConstructorDeclaration(node, node.decorators, modifiers, node.parameters, node.body) : + isGetAccessorDeclaration(node) ? updateGetAccessorDeclaration(node, node.decorators, modifiers, node.name, node.parameters, node.type, node.body) : + isSetAccessorDeclaration(node) ? updateSetAccessorDeclaration(node, node.decorators, modifiers, node.name, node.parameters, node.body) : + isIndexSignatureDeclaration(node) ? updateIndexSignature(node, node.decorators, modifiers, node.parameters, node.type) : + isFunctionExpression(node) ? updateFunctionExpression(node, modifiers, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : + isArrowFunction(node) ? updateArrowFunction(node, modifiers, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, node.body) : + isClassExpression(node) ? updateClassExpression(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) : + isVariableStatement(node) ? updateVariableStatement(node, modifiers, node.declarationList) : + isFunctionDeclaration(node) ? updateFunctionDeclaration(node, node.decorators, modifiers, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : + isClassDeclaration(node) ? updateClassDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) : + isInterfaceDeclaration(node) ? updateInterfaceDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) : + isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.type) : + isEnumDeclaration(node) ? updateEnumDeclaration(node, node.decorators, modifiers, node.name, node.members) : + isModuleDeclaration(node) ? updateModuleDeclaration(node, node.decorators, modifiers, node.name, node.body) : + isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, node.decorators, modifiers, node.isTypeOnly, node.name, node.moduleReference) : + isImportDeclaration(node) ? updateImportDeclaration(node, node.decorators, modifiers, node.importClause, node.moduleSpecifier, node.assertClause) : + isExportAssignment(node) ? updateExportAssignment(node, node.decorators, modifiers, node.expression) : + isExportDeclaration(node) ? updateExportDeclaration(node, node.decorators, modifiers, node.isTypeOnly, node.exportClause, node.moduleSpecifier, node.assertClause) : + Debug.assertNever(node); } - function updateWithoutOriginal(updated: T, original: T): T { - if (updated !== original) { - setTextRange(updated, original); - } - return updated; + function asNodeArray(array: readonly T[]): NodeArray; + function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined; + function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined { + return array ? createNodeArray(array) : undefined; } - function updateWithOriginal(updated: T, original: T): T { - if (updated !== original) { - setOriginalNode(updated, original); - setTextRange(updated, original); - } - return updated; + function asName(name: string | T): T | Identifier { + return typeof name === "string" ? createIdentifier(name) : + name; } - function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string { - switch (kind) { - case SyntaxKind.JSDocTypeTag: return "type"; - case SyntaxKind.JSDocReturnTag: return "returns"; - case SyntaxKind.JSDocThisTag: return "this"; - case SyntaxKind.JSDocEnumTag: return "enum"; - case SyntaxKind.JSDocAuthorTag: return "author"; - case SyntaxKind.JSDocClassTag: return "class"; - case SyntaxKind.JSDocPublicTag: return "public"; - case SyntaxKind.JSDocPrivateTag: return "private"; - case SyntaxKind.JSDocProtectedTag: return "protected"; - case SyntaxKind.JSDocReadonlyTag: return "readonly"; - case SyntaxKind.JSDocOverrideTag: return "override"; - case SyntaxKind.JSDocTemplateTag: return "template"; - case SyntaxKind.JSDocTypedefTag: return "typedef"; - case SyntaxKind.JSDocParameterTag: return "param"; - case SyntaxKind.JSDocPropertyTag: return "prop"; - case SyntaxKind.JSDocCallbackTag: return "callback"; - case SyntaxKind.JSDocAugmentsTag: return "augments"; - case SyntaxKind.JSDocImplementsTag: return "implements"; - default: - return Debug.fail(`Unsupported kind: ${Debug.formatSyntaxKind(kind)}`); - } + function asExpression(value: string | number | boolean | T): T | StringLiteral | NumericLiteral | BooleanLiteral { + return typeof value === "string" ? createStringLiteral(value) : + typeof value === "number" ? createNumericLiteral(value) : + typeof value === "boolean" ? value ? createTrue() : createFalse() : + value; } - let rawTextScanner: Scanner | undefined; - const invalidValueSentinel: object = { }; + function asToken(value: TKind | Token): Token { + return typeof value === "number" ? createToken(value) : value; + } - function getCookedText(kind: TemplateLiteralToken["kind"], rawText: string) { - if (!rawTextScanner) { - rawTextScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard); - } - switch (kind) { - case SyntaxKind.NoSubstitutionTemplateLiteral: - rawTextScanner.setText("`" + rawText + "`"); - break; - case SyntaxKind.TemplateHead: - // tslint:disable-next-line no-invalid-template-strings - rawTextScanner.setText("`" + rawText + "${"); - break; - case SyntaxKind.TemplateMiddle: - // tslint:disable-next-line no-invalid-template-strings - rawTextScanner.setText("}" + rawText + "${"); - break; - case SyntaxKind.TemplateTail: - rawTextScanner.setText("}" + rawText + "`"); - break; - } + function asEmbeddedStatement(statement: T): T | EmptyStatement; + function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined; + function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined { + return statement && isNotEmittedStatement(statement) ? setTextRange(setOriginalNode(createEmptyStatement(), statement), statement) : statement; + } +} - let token = rawTextScanner.scan(); - if (token === SyntaxKind.CloseBraceToken) { - token = rawTextScanner.reScanTemplateToken(/*isTaggedTemplate*/ false); - } +function updateWithoutOriginal(updated: T, original: T): T { + if (updated !== original) { + setTextRange(updated, original); + } + return updated; +} - if (rawTextScanner.isUnterminated()) { - rawTextScanner.setText(undefined); - return invalidValueSentinel; - } +function updateWithOriginal(updated: T, original: T): T { + if (updated !== original) { + setOriginalNode(updated, original); + setTextRange(updated, original); + } + return updated; +} - let tokenValue: string | undefined; - switch (token) { - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: - tokenValue = rawTextScanner.getTokenValue(); - break; - } +function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string { + switch (kind) { + case SyntaxKind.JSDocTypeTag: return "type"; + case SyntaxKind.JSDocReturnTag: return "returns"; + case SyntaxKind.JSDocThisTag: return "this"; + case SyntaxKind.JSDocEnumTag: return "enum"; + case SyntaxKind.JSDocAuthorTag: return "author"; + case SyntaxKind.JSDocClassTag: return "class"; + case SyntaxKind.JSDocPublicTag: return "public"; + case SyntaxKind.JSDocPrivateTag: return "private"; + case SyntaxKind.JSDocProtectedTag: return "protected"; + case SyntaxKind.JSDocReadonlyTag: return "readonly"; + case SyntaxKind.JSDocOverrideTag: return "override"; + case SyntaxKind.JSDocTemplateTag: return "template"; + case SyntaxKind.JSDocTypedefTag: return "typedef"; + case SyntaxKind.JSDocParameterTag: return "param"; + case SyntaxKind.JSDocPropertyTag: return "prop"; + case SyntaxKind.JSDocCallbackTag: return "callback"; + case SyntaxKind.JSDocAugmentsTag: return "augments"; + case SyntaxKind.JSDocImplementsTag: return "implements"; + default: + return Debug.fail(`Unsupported kind: ${Debug.formatSyntaxKind(kind)}`); + } +} - if (tokenValue === undefined || rawTextScanner.scan() !== SyntaxKind.EndOfFileToken) { - rawTextScanner.setText(undefined); - return invalidValueSentinel; - } +let rawTextScanner: Scanner | undefined; +const invalidValueSentinel: object = { }; - rawTextScanner.setText(undefined); - return tokenValue; +function getCookedText(kind: TemplateLiteralToken["kind"], rawText: string) { + if (!rawTextScanner) { + rawTextScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard); } - - function propagateIdentifierNameFlags(node: Identifier) { - // An IdentifierName is allowed to be `await` - return propagateChildFlags(node) & ~TransformFlags.ContainsPossibleTopLevelAwait; + switch (kind) { + case SyntaxKind.NoSubstitutionTemplateLiteral: + rawTextScanner.setText("`" + rawText + "`"); + break; + case SyntaxKind.TemplateHead: + // tslint:disable-next-line no-invalid-template-strings + rawTextScanner.setText("`" + rawText + "${"); + break; + case SyntaxKind.TemplateMiddle: + // tslint:disable-next-line no-invalid-template-strings + rawTextScanner.setText("}" + rawText + "${"); + break; + case SyntaxKind.TemplateTail: + rawTextScanner.setText("}" + rawText + "`"); + break; } - function propagatePropertyNameFlagsOfChild(node: PropertyName, transformFlags: TransformFlags) { - return transformFlags | (node.transformFlags & TransformFlags.PropertyNamePropagatingFlags); + let token = rawTextScanner.scan(); + if (token === SyntaxKind.CloseBraceToken) { + token = rawTextScanner.reScanTemplateToken(/*isTaggedTemplate*/ false); } - function propagateChildFlags(child: Node | undefined): TransformFlags { - if (!child) return TransformFlags.None; - const childFlags = child.transformFlags & ~getTransformFlagsSubtreeExclusions(child.kind); - return isNamedDeclaration(child) && isPropertyName(child.name) ? propagatePropertyNameFlagsOfChild(child.name, childFlags) : childFlags; + if (rawTextScanner.isUnterminated()) { + rawTextScanner.setText(undefined); + return invalidValueSentinel; } - function propagateChildrenFlags(children: NodeArray | undefined): TransformFlags { - return children ? children.transformFlags : TransformFlags.None; + let tokenValue: string | undefined; + switch (token) { + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + tokenValue = rawTextScanner.getTokenValue(); + break; } - function aggregateChildrenFlags(children: MutableNodeArray) { - let subtreeFlags = TransformFlags.None; - for (const child of children) { - subtreeFlags |= propagateChildFlags(child); - } - children.transformFlags = subtreeFlags; + if (tokenValue === undefined || rawTextScanner.scan() !== SyntaxKind.EndOfFileToken) { + rawTextScanner.setText(undefined); + return invalidValueSentinel; } - /** - * Gets the transform flags to exclude when unioning the transform flags of a subtree. - */ - /* @internal */ - export function getTransformFlagsSubtreeExclusions(kind: SyntaxKind) { - if (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) { - return TransformFlags.TypeExcludes; - } + rawTextScanner.setText(undefined); + return tokenValue; +} - switch (kind) { - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.ArrayLiteralExpression: - return TransformFlags.ArrayLiteralOrCallOrNewExcludes; - case SyntaxKind.ModuleDeclaration: - return TransformFlags.ModuleExcludes; - case SyntaxKind.Parameter: - return TransformFlags.ParameterExcludes; - case SyntaxKind.ArrowFunction: - return TransformFlags.ArrowFunctionExcludes; - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - return TransformFlags.FunctionExcludes; - case SyntaxKind.VariableDeclarationList: - return TransformFlags.VariableDeclarationListExcludes; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return TransformFlags.ClassExcludes; - case SyntaxKind.Constructor: - return TransformFlags.ConstructorExcludes; - case SyntaxKind.PropertyDeclaration: - return TransformFlags.PropertyExcludes; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return TransformFlags.MethodOrAccessorExcludes; - case SyntaxKind.AnyKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.TypeParameter: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - return TransformFlags.TypeExcludes; - case SyntaxKind.ObjectLiteralExpression: - return TransformFlags.ObjectLiteralExcludes; - case SyntaxKind.CatchClause: - return TransformFlags.CatchClauseExcludes; - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - return TransformFlags.BindingPatternExcludes; - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - case SyntaxKind.PartiallyEmittedExpression: - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.SuperKeyword: - return TransformFlags.OuterExpressionExcludes; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return TransformFlags.PropertyAccessExcludes; - default: - return TransformFlags.NodeExcludes; - } +function propagateIdentifierNameFlags(node: Identifier) { + // An IdentifierName is allowed to be `await` + return propagateChildFlags(node) & ~TransformFlags.ContainsPossibleTopLevelAwait; +} + +function propagatePropertyNameFlagsOfChild(node: PropertyName, transformFlags: TransformFlags) { + return transformFlags | (node.transformFlags & TransformFlags.PropertyNamePropagatingFlags); +} + +function propagateChildFlags(child: Node | undefined): TransformFlags { + if (!child) return TransformFlags.None; + const childFlags = child.transformFlags & ~getTransformFlagsSubtreeExclusions(child.kind); + return isNamedDeclaration(child) && isPropertyName(child.name) ? propagatePropertyNameFlagsOfChild(child.name, childFlags) : childFlags; +} + +function propagateChildrenFlags(children: NodeArray | undefined): TransformFlags { + return children ? children.transformFlags : TransformFlags.None; +} + +function aggregateChildrenFlags(children: MutableNodeArray) { + let subtreeFlags = TransformFlags.None; + for (const child of children) { + subtreeFlags |= propagateChildFlags(child); } + children.transformFlags = subtreeFlags; +} - const baseFactory = createBaseNodeFactory(); +/** + * Gets the transform flags to exclude when unioning the transform flags of a subtree. + */ +/* @internal */ +export function getTransformFlagsSubtreeExclusions(kind: SyntaxKind) { + if (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) { + return TransformFlags.TypeExcludes; + } - function makeSynthetic(node: Node) { - (node as Mutable).flags |= NodeFlags.Synthesized; - return node; + switch (kind) { + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.ArrayLiteralExpression: + return TransformFlags.ArrayLiteralOrCallOrNewExcludes; + case SyntaxKind.ModuleDeclaration: + return TransformFlags.ModuleExcludes; + case SyntaxKind.Parameter: + return TransformFlags.ParameterExcludes; + case SyntaxKind.ArrowFunction: + return TransformFlags.ArrowFunctionExcludes; + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + return TransformFlags.FunctionExcludes; + case SyntaxKind.VariableDeclarationList: + return TransformFlags.VariableDeclarationListExcludes; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return TransformFlags.ClassExcludes; + case SyntaxKind.Constructor: + return TransformFlags.ConstructorExcludes; + case SyntaxKind.PropertyDeclaration: + return TransformFlags.PropertyExcludes; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return TransformFlags.MethodOrAccessorExcludes; + case SyntaxKind.AnyKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.TypeParameter: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return TransformFlags.TypeExcludes; + case SyntaxKind.ObjectLiteralExpression: + return TransformFlags.ObjectLiteralExcludes; + case SyntaxKind.CatchClause: + return TransformFlags.CatchClauseExcludes; + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + return TransformFlags.BindingPatternExcludes; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.PartiallyEmittedExpression: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.SuperKeyword: + return TransformFlags.OuterExpressionExcludes; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return TransformFlags.PropertyAccessExcludes; + default: + return TransformFlags.NodeExcludes; } +} - const syntheticFactory: BaseNodeFactory = { - createBaseSourceFileNode: kind => makeSynthetic(baseFactory.createBaseSourceFileNode(kind)), - createBaseIdentifierNode: kind => makeSynthetic(baseFactory.createBaseIdentifierNode(kind)), - createBasePrivateIdentifierNode: kind => makeSynthetic(baseFactory.createBasePrivateIdentifierNode(kind)), - createBaseTokenNode: kind => makeSynthetic(baseFactory.createBaseTokenNode(kind)), - createBaseNode: kind => makeSynthetic(baseFactory.createBaseNode(kind)), - }; +const baseFactory = createBaseNodeFactory(); - export const factory = createNodeFactory(NodeFactoryFlags.NoIndentationOnFreshPropertyAccess, syntheticFactory); - - export function createUnparsedSourceFile(text: string): UnparsedSource; - export function createUnparsedSourceFile(inputFile: InputFiles, type: "js" | "dts", stripInternal?: boolean): UnparsedSource; - export function createUnparsedSourceFile(text: string, mapPath: string | undefined, map: string | undefined): UnparsedSource; - export function createUnparsedSourceFile(textOrInputFiles: string | InputFiles, mapPathOrType?: string, mapTextOrStripInternal?: string | boolean): UnparsedSource { - let stripInternal: boolean | undefined; - let bundleFileInfo: BundleFileInfo | undefined; - let fileName: string; - let text: string | undefined; - let length: number | (() => number); - let sourceMapPath: string | undefined; - let sourceMapText: string | undefined; - let getText: (() => string) | undefined; - let getSourceMapText: (() => string | undefined) | undefined; - let oldFileOfCurrentEmit: boolean | undefined; - - if (!isString(textOrInputFiles)) { - Debug.assert(mapPathOrType === "js" || mapPathOrType === "dts"); - fileName = (mapPathOrType === "js" ? textOrInputFiles.javascriptPath : textOrInputFiles.declarationPath) || ""; - sourceMapPath = mapPathOrType === "js" ? textOrInputFiles.javascriptMapPath : textOrInputFiles.declarationMapPath; - getText = () => mapPathOrType === "js" ? textOrInputFiles.javascriptText : textOrInputFiles.declarationText; - getSourceMapText = () => mapPathOrType === "js" ? textOrInputFiles.javascriptMapText : textOrInputFiles.declarationMapText; - length = () => getText!().length; - if (textOrInputFiles.buildInfo && textOrInputFiles.buildInfo.bundle) { - Debug.assert(mapTextOrStripInternal === undefined || typeof mapTextOrStripInternal === "boolean"); - stripInternal = mapTextOrStripInternal; - bundleFileInfo = mapPathOrType === "js" ? textOrInputFiles.buildInfo.bundle.js : textOrInputFiles.buildInfo.bundle.dts; - oldFileOfCurrentEmit = textOrInputFiles.oldFileOfCurrentEmit; - } - } - else { - fileName = ""; - text = textOrInputFiles; - length = textOrInputFiles.length; - sourceMapPath = mapPathOrType; - sourceMapText = mapTextOrStripInternal as string; - } - const node = oldFileOfCurrentEmit ? - parseOldFileOfCurrentEmit(Debug.checkDefined(bundleFileInfo)) : - parseUnparsedSourceFile(bundleFileInfo, stripInternal, length); - node.fileName = fileName; - node.sourceMapPath = sourceMapPath; - node.oldFileOfCurrentEmit = oldFileOfCurrentEmit; - if (getText && getSourceMapText) { - Object.defineProperty(node, "text", { get: getText }); - Object.defineProperty(node, "sourceMapText", { get: getSourceMapText }); - } - else { - Debug.assert(!oldFileOfCurrentEmit); - node.text = text ?? ""; - node.sourceMapText = sourceMapText; - } +function makeSynthetic(node: Node) { + (node as Mutable).flags |= NodeFlags.Synthesized; + return node; +} - return node; +const syntheticFactory: BaseNodeFactory = { + createBaseSourceFileNode: kind => makeSynthetic(baseFactory.createBaseSourceFileNode(kind)), + createBaseIdentifierNode: kind => makeSynthetic(baseFactory.createBaseIdentifierNode(kind)), + createBasePrivateIdentifierNode: kind => makeSynthetic(baseFactory.createBasePrivateIdentifierNode(kind)), + createBaseTokenNode: kind => makeSynthetic(baseFactory.createBaseTokenNode(kind)), + createBaseNode: kind => makeSynthetic(baseFactory.createBaseNode(kind)), +}; + +export const factory = createNodeFactory(NodeFactoryFlags.NoIndentationOnFreshPropertyAccess, syntheticFactory); + +export function createUnparsedSourceFile(text: string): UnparsedSource; +export function createUnparsedSourceFile(inputFile: InputFiles, type: "js" | "dts", stripInternal?: boolean): UnparsedSource; +export function createUnparsedSourceFile(text: string, mapPath: string | undefined, map: string | undefined): UnparsedSource; +export function createUnparsedSourceFile(textOrInputFiles: string | InputFiles, mapPathOrType?: string, mapTextOrStripInternal?: string | boolean): UnparsedSource { + let stripInternal: boolean | undefined; + let bundleFileInfo: BundleFileInfo | undefined; + let fileName: string; + let text: string | undefined; + let length: number | (() => number); + let sourceMapPath: string | undefined; + let sourceMapText: string | undefined; + let getText: (() => string) | undefined; + let getSourceMapText: (() => string | undefined) | undefined; + let oldFileOfCurrentEmit: boolean | undefined; + + if (!isString(textOrInputFiles)) { + Debug.assert(mapPathOrType === "js" || mapPathOrType === "dts"); + fileName = (mapPathOrType === "js" ? textOrInputFiles.javascriptPath : textOrInputFiles.declarationPath) || ""; + sourceMapPath = mapPathOrType === "js" ? textOrInputFiles.javascriptMapPath : textOrInputFiles.declarationMapPath; + getText = () => mapPathOrType === "js" ? textOrInputFiles.javascriptText : textOrInputFiles.declarationText; + getSourceMapText = () => mapPathOrType === "js" ? textOrInputFiles.javascriptMapText : textOrInputFiles.declarationMapText; + length = () => getText!().length; + if (textOrInputFiles.buildInfo && textOrInputFiles.buildInfo.bundle) { + Debug.assert(mapTextOrStripInternal === undefined || typeof mapTextOrStripInternal === "boolean"); + stripInternal = mapTextOrStripInternal; + bundleFileInfo = mapPathOrType === "js" ? textOrInputFiles.buildInfo.bundle.js : textOrInputFiles.buildInfo.bundle.dts; + oldFileOfCurrentEmit = textOrInputFiles.oldFileOfCurrentEmit; + } + } + else { + fileName = ""; + text = textOrInputFiles; + length = textOrInputFiles.length; + sourceMapPath = mapPathOrType; + sourceMapText = mapTextOrStripInternal as string; + } + const node = oldFileOfCurrentEmit ? + parseOldFileOfCurrentEmit(Debug.checkDefined(bundleFileInfo)) : + parseUnparsedSourceFile(bundleFileInfo, stripInternal, length); + node.fileName = fileName; + node.sourceMapPath = sourceMapPath; + node.oldFileOfCurrentEmit = oldFileOfCurrentEmit; + if (getText && getSourceMapText) { + Object.defineProperty(node, "text", { get: getText }); + Object.defineProperty(node, "sourceMapText", { get: getSourceMapText }); + } + else { + Debug.assert(!oldFileOfCurrentEmit); + node.text = text ?? ""; + node.sourceMapText = sourceMapText; } - function parseUnparsedSourceFile(bundleFileInfo: BundleFileInfo | undefined, stripInternal: boolean | undefined, length: number | (() => number)) { - let prologues: UnparsedPrologue[] | undefined; - let helpers: UnscopedEmitHelper[] | undefined; - let referencedFiles: FileReference[] | undefined; - let typeReferenceDirectives: string[] | undefined; - let libReferenceDirectives: FileReference[] | undefined; - let prependChildren: UnparsedTextLike[] | undefined; - let texts: UnparsedSourceText[] | undefined; - let hasNoDefaultLib: boolean | undefined; + return node; +} - for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) { - switch (section.kind) { - case BundleFileSectionKind.Prologue: - prologues = append(prologues, setTextRange(factory.createUnparsedPrologue(section.data), section)); - break; - case BundleFileSectionKind.EmitHelpers: - helpers = append(helpers, getAllUnscopedEmitHelpers().get(section.data)!); - break; - case BundleFileSectionKind.NoDefaultLib: - hasNoDefaultLib = true; - break; - case BundleFileSectionKind.Reference: - referencedFiles = append(referencedFiles, { pos: -1, end: -1, fileName: section.data }); - break; - case BundleFileSectionKind.Type: - typeReferenceDirectives = append(typeReferenceDirectives, section.data); - break; - case BundleFileSectionKind.Lib: - libReferenceDirectives = append(libReferenceDirectives, { pos: -1, end: -1, fileName: section.data }); - break; - case BundleFileSectionKind.Prepend: - let prependTexts: UnparsedTextLike[] | undefined; - for (const text of section.texts) { - if (!stripInternal || text.kind !== BundleFileSectionKind.Internal) { - prependTexts = append(prependTexts, setTextRange(factory.createUnparsedTextLike(text.data, text.kind === BundleFileSectionKind.Internal), text)); - } - } - prependChildren = addRange(prependChildren, prependTexts); - texts = append(texts, factory.createUnparsedPrepend(section.data, prependTexts ?? emptyArray)); - break; - case BundleFileSectionKind.Internal: - if (stripInternal) { - if (!texts) texts = []; - break; +function parseUnparsedSourceFile(bundleFileInfo: BundleFileInfo | undefined, stripInternal: boolean | undefined, length: number | (() => number)) { + let prologues: UnparsedPrologue[] | undefined; + let helpers: UnscopedEmitHelper[] | undefined; + let referencedFiles: FileReference[] | undefined; + let typeReferenceDirectives: string[] | undefined; + let libReferenceDirectives: FileReference[] | undefined; + let prependChildren: UnparsedTextLike[] | undefined; + let texts: UnparsedSourceText[] | undefined; + let hasNoDefaultLib: boolean | undefined; + + for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) { + switch (section.kind) { + case BundleFileSectionKind.Prologue: + prologues = append(prologues, setTextRange(factory.createUnparsedPrologue(section.data), section)); + break; + case BundleFileSectionKind.EmitHelpers: + helpers = append(helpers, getAllUnscopedEmitHelpers().get(section.data)!); + break; + case BundleFileSectionKind.NoDefaultLib: + hasNoDefaultLib = true; + break; + case BundleFileSectionKind.Reference: + referencedFiles = append(referencedFiles, { pos: -1, end: -1, fileName: section.data }); + break; + case BundleFileSectionKind.Type: + typeReferenceDirectives = append(typeReferenceDirectives, section.data); + break; + case BundleFileSectionKind.Lib: + libReferenceDirectives = append(libReferenceDirectives, { pos: -1, end: -1, fileName: section.data }); + break; + case BundleFileSectionKind.Prepend: + let prependTexts: UnparsedTextLike[] | undefined; + for (const text of section.texts) { + if (!stripInternal || text.kind !== BundleFileSectionKind.Internal) { + prependTexts = append(prependTexts, setTextRange(factory.createUnparsedTextLike(text.data, text.kind === BundleFileSectionKind.Internal), text)); } - // falls through - - case BundleFileSectionKind.Text: - texts = append(texts, setTextRange(factory.createUnparsedTextLike(section.data, section.kind === BundleFileSectionKind.Internal), section)); + } + prependChildren = addRange(prependChildren, prependTexts); + texts = append(texts, factory.createUnparsedPrepend(section.data, prependTexts ?? emptyArray)); + break; + case BundleFileSectionKind.Internal: + if (stripInternal) { + if (!texts) texts = []; break; - default: - Debug.assertNever(section); - } - } + } + // falls through - if (!texts) { - const textNode = factory.createUnparsedTextLike(/*data*/ undefined, /*internal*/ false); - setTextRangePosWidth(textNode, 0, typeof length === "function" ? length() : length); - texts = [textNode]; + case BundleFileSectionKind.Text: + texts = append(texts, setTextRange(factory.createUnparsedTextLike(section.data, section.kind === BundleFileSectionKind.Internal), section)); + break; + default: + Debug.assertNever(section); } + } - const node = parseNodeFactory.createUnparsedSource(prologues ?? emptyArray, /*syntheticReferences*/ undefined, texts); - setEachParent(prologues, node); - setEachParent(texts, node); - setEachParent(prependChildren, node); - node.hasNoDefaultLib = hasNoDefaultLib; - node.helpers = helpers; - node.referencedFiles = referencedFiles || emptyArray; - node.typeReferenceDirectives = typeReferenceDirectives; - node.libReferenceDirectives = libReferenceDirectives || emptyArray; - return node; + if (!texts) { + const textNode = factory.createUnparsedTextLike(/*data*/ undefined, /*internal*/ false); + setTextRangePosWidth(textNode, 0, typeof length === "function" ? length() : length); + texts = [textNode]; } - function parseOldFileOfCurrentEmit(bundleFileInfo: BundleFileInfo) { - let texts: UnparsedTextLike[] | undefined; - let syntheticReferences: UnparsedSyntheticReference[] | undefined; - for (const section of bundleFileInfo.sections) { - switch (section.kind) { - case BundleFileSectionKind.Internal: - case BundleFileSectionKind.Text: - texts = append(texts, setTextRange(factory.createUnparsedTextLike(section.data, section.kind === BundleFileSectionKind.Internal), section)); - break; + const node = parseNodeFactory.createUnparsedSource(prologues ?? emptyArray, /*syntheticReferences*/ undefined, texts); + setEachParent(prologues, node); + setEachParent(texts, node); + setEachParent(prependChildren, node); + node.hasNoDefaultLib = hasNoDefaultLib; + node.helpers = helpers; + node.referencedFiles = referencedFiles || emptyArray; + node.typeReferenceDirectives = typeReferenceDirectives; + node.libReferenceDirectives = libReferenceDirectives || emptyArray; + return node; +} - case BundleFileSectionKind.NoDefaultLib: - case BundleFileSectionKind.Reference: - case BundleFileSectionKind.Type: - case BundleFileSectionKind.Lib: - syntheticReferences = append(syntheticReferences, setTextRange(factory.createUnparsedSyntheticReference(section), section)); - break; +function parseOldFileOfCurrentEmit(bundleFileInfo: BundleFileInfo) { + let texts: UnparsedTextLike[] | undefined; + let syntheticReferences: UnparsedSyntheticReference[] | undefined; + for (const section of bundleFileInfo.sections) { + switch (section.kind) { + case BundleFileSectionKind.Internal: + case BundleFileSectionKind.Text: + texts = append(texts, setTextRange(factory.createUnparsedTextLike(section.data, section.kind === BundleFileSectionKind.Internal), section)); + break; - // Ignore - case BundleFileSectionKind.Prologue: - case BundleFileSectionKind.EmitHelpers: - case BundleFileSectionKind.Prepend: - break; + case BundleFileSectionKind.NoDefaultLib: + case BundleFileSectionKind.Reference: + case BundleFileSectionKind.Type: + case BundleFileSectionKind.Lib: + syntheticReferences = append(syntheticReferences, setTextRange(factory.createUnparsedSyntheticReference(section), section)); + break; - default: - Debug.assertNever(section); - } - } + // Ignore + case BundleFileSectionKind.Prologue: + case BundleFileSectionKind.EmitHelpers: + case BundleFileSectionKind.Prepend: + break; - const node = factory.createUnparsedSource(emptyArray, syntheticReferences, texts ?? emptyArray); - setEachParent(syntheticReferences, node); - setEachParent(texts, node); - node.helpers = map(bundleFileInfo.sources && bundleFileInfo.sources.helpers, name => getAllUnscopedEmitHelpers().get(name)!); - return node; - } - - // TODO(rbuckton): Move part of this to factory - export function createInputFiles( - javascriptText: string, - declarationText: string - ): InputFiles; - export function createInputFiles( - readFileText: (path: string) => string | undefined, - javascriptPath: string, - javascriptMapPath: string | undefined, - declarationPath: string, - declarationMapPath: string | undefined, - buildInfoPath: string | undefined - ): InputFiles; - export function createInputFiles( - javascriptText: string, - declarationText: string, - javascriptMapPath: string | undefined, - javascriptMapText: string | undefined, - declarationMapPath: string | undefined, - declarationMapText: string | undefined - ): InputFiles; - /*@internal*/ - export function createInputFiles( - javascriptText: string, - declarationText: string, - javascriptMapPath: string | undefined, - javascriptMapText: string | undefined, - declarationMapPath: string | undefined, - declarationMapText: string | undefined, - javascriptPath: string | undefined, - declarationPath: string | undefined, - buildInfoPath?: string | undefined, - buildInfo?: BuildInfo, - oldFileOfCurrentEmit?: boolean - ): InputFiles; - export function createInputFiles( - javascriptTextOrReadFileText: string | ((path: string) => string | undefined), - declarationTextOrJavascriptPath: string, - javascriptMapPath?: string, - javascriptMapTextOrDeclarationPath?: string, - declarationMapPath?: string, - declarationMapTextOrBuildInfoPath?: string, - javascriptPath?: string | undefined, - declarationPath?: string | undefined, - buildInfoPath?: string | undefined, - buildInfo?: BuildInfo, - oldFileOfCurrentEmit?: boolean - ): InputFiles { - const node = parseNodeFactory.createInputFiles(); - if (!isString(javascriptTextOrReadFileText)) { - const cache = new Map(); - const textGetter = (path: string | undefined) => { - if (path === undefined) return undefined; - let value = cache.get(path); - if (value === undefined) { - value = javascriptTextOrReadFileText(path); - cache.set(path, value !== undefined ? value : false); - } - return value !== false ? value as string : undefined; - }; - const definedTextGetter = (path: string) => { - const result = textGetter(path); - return result !== undefined ? result : `/* Input file ${path} was missing */\r\n`; - }; - let buildInfo: BuildInfo | false; - const getAndCacheBuildInfo = (getText: () => string | undefined) => { - if (buildInfo === undefined) { - const result = getText(); - buildInfo = result !== undefined ? getBuildInfo(result) : false; - } - return buildInfo || undefined; - }; - node.javascriptPath = declarationTextOrJavascriptPath; - node.javascriptMapPath = javascriptMapPath; - node.declarationPath = Debug.checkDefined(javascriptMapTextOrDeclarationPath); - node.declarationMapPath = declarationMapPath; - node.buildInfoPath = declarationMapTextOrBuildInfoPath; - Object.defineProperties(node, { - javascriptText: { get() { return definedTextGetter(declarationTextOrJavascriptPath); } }, - javascriptMapText: { get() { return textGetter(javascriptMapPath); } }, // TODO:: if there is inline sourceMap in jsFile, use that - declarationText: { get() { return definedTextGetter(Debug.checkDefined(javascriptMapTextOrDeclarationPath)); } }, - declarationMapText: { get() { return textGetter(declarationMapPath); } }, // TODO:: if there is inline sourceMap in dtsFile, use that - buildInfo: { get() { return getAndCacheBuildInfo(() => textGetter(declarationMapTextOrBuildInfoPath)); } } - }); - } - else { - node.javascriptText = javascriptTextOrReadFileText; - node.javascriptMapPath = javascriptMapPath; - node.javascriptMapText = javascriptMapTextOrDeclarationPath; - node.declarationText = declarationTextOrJavascriptPath; - node.declarationMapPath = declarationMapPath; - node.declarationMapText = declarationMapTextOrBuildInfoPath; - node.javascriptPath = javascriptPath; - node.declarationPath = declarationPath; - node.buildInfoPath = buildInfoPath; - node.buildInfo = buildInfo; - node.oldFileOfCurrentEmit = oldFileOfCurrentEmit; + default: + Debug.assertNever(section); } - return node; } - // tslint:disable-next-line variable-name - let SourceMapSource: new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; + const node = factory.createUnparsedSource(emptyArray, syntheticReferences, texts ?? emptyArray); + setEachParent(syntheticReferences, node); + setEachParent(texts, node); + node.helpers = map(bundleFileInfo.sources && bundleFileInfo.sources.helpers, name => getAllUnscopedEmitHelpers().get(name)!); + return node; +} - /** - * Create an external source map source file reference - */ - export function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number): SourceMapSource { - return new (SourceMapSource || (SourceMapSource = objectAllocator.getSourceMapSourceConstructor()))(fileName, text, skipTrivia); +// TODO(rbuckton): Move part of this to factory +export function createInputFiles( + javascriptText: string, + declarationText: string +): InputFiles; +export function createInputFiles( + readFileText: (path: string) => string | undefined, + javascriptPath: string, + javascriptMapPath: string | undefined, + declarationPath: string, + declarationMapPath: string | undefined, + buildInfoPath: string | undefined +): InputFiles; +export function createInputFiles( + javascriptText: string, + declarationText: string, + javascriptMapPath: string | undefined, + javascriptMapText: string | undefined, + declarationMapPath: string | undefined, + declarationMapText: string | undefined +): InputFiles; +/*@internal*/ +export function createInputFiles( + javascriptText: string, + declarationText: string, + javascriptMapPath: string | undefined, + javascriptMapText: string | undefined, + declarationMapPath: string | undefined, + declarationMapText: string | undefined, + javascriptPath: string | undefined, + declarationPath: string | undefined, + buildInfoPath?: string | undefined, + buildInfo?: BuildInfo, + oldFileOfCurrentEmit?: boolean +): InputFiles; +export function createInputFiles( + javascriptTextOrReadFileText: string | ((path: string) => string | undefined), + declarationTextOrJavascriptPath: string, + javascriptMapPath?: string, + javascriptMapTextOrDeclarationPath?: string, + declarationMapPath?: string, + declarationMapTextOrBuildInfoPath?: string, + javascriptPath?: string | undefined, + declarationPath?: string | undefined, + buildInfoPath?: string | undefined, + buildInfo?: BuildInfo, + oldFileOfCurrentEmit?: boolean +): InputFiles { + const node = parseNodeFactory.createInputFiles(); + if (!isString(javascriptTextOrReadFileText)) { + const cache = new Map(); + const textGetter = (path: string | undefined) => { + if (path === undefined) return undefined; + let value = cache.get(path); + if (value === undefined) { + value = javascriptTextOrReadFileText(path); + cache.set(path, value !== undefined ? value : false); + } + return value !== false ? value as string : undefined; + }; + const definedTextGetter = (path: string) => { + const result = textGetter(path); + return result !== undefined ? result : `/* Input file ${path} was missing */\r\n`; + }; + let buildInfo: BuildInfo | false; + const getAndCacheBuildInfo = (getText: () => string | undefined) => { + if (buildInfo === undefined) { + const result = getText(); + buildInfo = result !== undefined ? getBuildInfo(result) : false; + } + return buildInfo || undefined; + }; + node.javascriptPath = declarationTextOrJavascriptPath; + node.javascriptMapPath = javascriptMapPath; + node.declarationPath = Debug.checkDefined(javascriptMapTextOrDeclarationPath); + node.declarationMapPath = declarationMapPath; + node.buildInfoPath = declarationMapTextOrBuildInfoPath; + Object.defineProperties(node, { + javascriptText: { get() { return definedTextGetter(declarationTextOrJavascriptPath); } }, + javascriptMapText: { get() { return textGetter(javascriptMapPath); } }, // TODO:: if there is inline sourceMap in jsFile, use that + declarationText: { get() { return definedTextGetter(Debug.checkDefined(javascriptMapTextOrDeclarationPath)); } }, + declarationMapText: { get() { return textGetter(declarationMapPath); } }, // TODO:: if there is inline sourceMap in dtsFile, use that + buildInfo: { get() { return getAndCacheBuildInfo(() => textGetter(declarationMapTextOrBuildInfoPath)); } } + }); + } + else { + node.javascriptText = javascriptTextOrReadFileText; + node.javascriptMapPath = javascriptMapPath; + node.javascriptMapText = javascriptMapTextOrDeclarationPath; + node.declarationText = declarationTextOrJavascriptPath; + node.declarationMapPath = declarationMapPath; + node.declarationMapText = declarationMapTextOrBuildInfoPath; + node.javascriptPath = javascriptPath; + node.declarationPath = declarationPath; + node.buildInfoPath = buildInfoPath; + node.buildInfo = buildInfo; + node.oldFileOfCurrentEmit = oldFileOfCurrentEmit; } + return node; +} - // Utilities +// tslint:disable-next-line variable-name +let SourceMapSource: new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; - export function setOriginalNode(node: T, original: Node | undefined): T { - node.original = original; - if (original) { - const emitNode = original.emitNode; - if (emitNode) node.emitNode = mergeEmitNode(emitNode, node.emitNode); - } - return node; +/** + * Create an external source map source file reference + */ +export function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number): SourceMapSource { + return new (SourceMapSource || (SourceMapSource = objectAllocator.getSourceMapSourceConstructor()))(fileName, text, skipTrivia); +} + +// Utilities + +export function setOriginalNode(node: T, original: Node | undefined): T { + node.original = original; + if (original) { + const emitNode = original.emitNode; + if (emitNode) node.emitNode = mergeEmitNode(emitNode, node.emitNode); } + return node; +} - function mergeEmitNode(sourceEmitNode: EmitNode, destEmitNode: EmitNode | undefined) { - const { - flags, - leadingComments, - trailingComments, - commentRange, - sourceMapRange, - tokenSourceMapRanges, - constantValue, - helpers, - startsOnNewLine, - } = sourceEmitNode; - if (!destEmitNode) destEmitNode = {} as EmitNode; - // We are using `.slice()` here in case `destEmitNode.leadingComments` is pushed to later. - if (leadingComments) destEmitNode.leadingComments = addRange(leadingComments.slice(), destEmitNode.leadingComments); - if (trailingComments) destEmitNode.trailingComments = addRange(trailingComments.slice(), destEmitNode.trailingComments); - if (flags) destEmitNode.flags = flags & ~EmitFlags.Immutable; - if (commentRange) destEmitNode.commentRange = commentRange; - if (sourceMapRange) destEmitNode.sourceMapRange = sourceMapRange; - if (tokenSourceMapRanges) destEmitNode.tokenSourceMapRanges = mergeTokenSourceMapRanges(tokenSourceMapRanges, destEmitNode.tokenSourceMapRanges!); - if (constantValue !== undefined) destEmitNode.constantValue = constantValue; - if (helpers) { - for (const helper of helpers) { - destEmitNode.helpers = appendIfUnique(destEmitNode.helpers, helper); - } +function mergeEmitNode(sourceEmitNode: EmitNode, destEmitNode: EmitNode | undefined) { + const { + flags, + leadingComments, + trailingComments, + commentRange, + sourceMapRange, + tokenSourceMapRanges, + constantValue, + helpers, + startsOnNewLine, + } = sourceEmitNode; + if (!destEmitNode) destEmitNode = {} as EmitNode; + // We are using `.slice()` here in case `destEmitNode.leadingComments` is pushed to later. + if (leadingComments) destEmitNode.leadingComments = addRange(leadingComments.slice(), destEmitNode.leadingComments); + if (trailingComments) destEmitNode.trailingComments = addRange(trailingComments.slice(), destEmitNode.trailingComments); + if (flags) destEmitNode.flags = flags & ~EmitFlags.Immutable; + if (commentRange) destEmitNode.commentRange = commentRange; + if (sourceMapRange) destEmitNode.sourceMapRange = sourceMapRange; + if (tokenSourceMapRanges) destEmitNode.tokenSourceMapRanges = mergeTokenSourceMapRanges(tokenSourceMapRanges, destEmitNode.tokenSourceMapRanges!); + if (constantValue !== undefined) destEmitNode.constantValue = constantValue; + if (helpers) { + for (const helper of helpers) { + destEmitNode.helpers = appendIfUnique(destEmitNode.helpers, helper); } - if (startsOnNewLine !== undefined) destEmitNode.startsOnNewLine = startsOnNewLine; - return destEmitNode; } + if (startsOnNewLine !== undefined) destEmitNode.startsOnNewLine = startsOnNewLine; + return destEmitNode; +} - function mergeTokenSourceMapRanges(sourceRanges: (TextRange | undefined)[], destRanges: (TextRange | undefined)[]) { - if (!destRanges) destRanges = []; - for (const key in sourceRanges) { - destRanges[key] = sourceRanges[key]; - } - return destRanges; +function mergeTokenSourceMapRanges(sourceRanges: (TextRange | undefined)[], destRanges: (TextRange | undefined)[]) { + if (!destRanges) destRanges = []; + for (const key in sourceRanges) { + destRanges[key] = sourceRanges[key]; } + return destRanges; +} } diff --git a/src/compiler/factory/nodeTests.ts b/src/compiler/factory/nodeTests.ts index 274ade1886dfc..97508b188499e 100644 --- a/src/compiler/factory/nodeTests.ts +++ b/src/compiler/factory/nodeTests.ts @@ -1,942 +1,942 @@ namespace ts { - // Literals +// Literals - export function isNumericLiteral(node: Node): node is NumericLiteral { - return node.kind === SyntaxKind.NumericLiteral; - } +export function isNumericLiteral(node: Node): node is NumericLiteral { + return node.kind === SyntaxKind.NumericLiteral; +} - export function isBigIntLiteral(node: Node): node is BigIntLiteral { - return node.kind === SyntaxKind.BigIntLiteral; - } +export function isBigIntLiteral(node: Node): node is BigIntLiteral { + return node.kind === SyntaxKind.BigIntLiteral; +} - export function isStringLiteral(node: Node): node is StringLiteral { - return node.kind === SyntaxKind.StringLiteral; - } +export function isStringLiteral(node: Node): node is StringLiteral { + return node.kind === SyntaxKind.StringLiteral; +} - export function isJsxText(node: Node): node is JsxText { - return node.kind === SyntaxKind.JsxText; - } +export function isJsxText(node: Node): node is JsxText { + return node.kind === SyntaxKind.JsxText; +} - export function isRegularExpressionLiteral(node: Node): node is RegularExpressionLiteral { - return node.kind === SyntaxKind.RegularExpressionLiteral; - } +export function isRegularExpressionLiteral(node: Node): node is RegularExpressionLiteral { + return node.kind === SyntaxKind.RegularExpressionLiteral; +} - export function isNoSubstitutionTemplateLiteral(node: Node): node is NoSubstitutionTemplateLiteral { - return node.kind === SyntaxKind.NoSubstitutionTemplateLiteral; - } +export function isNoSubstitutionTemplateLiteral(node: Node): node is NoSubstitutionTemplateLiteral { + return node.kind === SyntaxKind.NoSubstitutionTemplateLiteral; +} - // Pseudo-literals - - export function isTemplateHead(node: Node): node is TemplateHead { - return node.kind === SyntaxKind.TemplateHead; - } - - export function isTemplateMiddle(node: Node): node is TemplateMiddle { - return node.kind === SyntaxKind.TemplateMiddle; - } - - export function isTemplateTail(node: Node): node is TemplateTail { - return node.kind === SyntaxKind.TemplateTail; - } - - // Punctuation - - export function isDotDotDotToken(node: Node): node is DotDotDotToken { - return node.kind === SyntaxKind.DotDotDotToken; - } - - /*@internal*/ - export function isCommaToken(node: Node): node is Token { - return node.kind === SyntaxKind.CommaToken; - } +// Pseudo-literals - export function isPlusToken(node: Node): node is PlusToken { - return node.kind === SyntaxKind.PlusToken; - } +export function isTemplateHead(node: Node): node is TemplateHead { + return node.kind === SyntaxKind.TemplateHead; +} - export function isMinusToken(node: Node): node is MinusToken { - return node.kind === SyntaxKind.MinusToken; - } +export function isTemplateMiddle(node: Node): node is TemplateMiddle { + return node.kind === SyntaxKind.TemplateMiddle; +} - export function isAsteriskToken(node: Node): node is AsteriskToken { - return node.kind === SyntaxKind.AsteriskToken; - } +export function isTemplateTail(node: Node): node is TemplateTail { + return node.kind === SyntaxKind.TemplateTail; +} - /*@internal*/ - export function isExclamationToken(node: Node): node is ExclamationToken { - return node.kind === SyntaxKind.ExclamationToken; - } - - /*@internal*/ - export function isQuestionToken(node: Node): node is QuestionToken { - return node.kind === SyntaxKind.QuestionToken; - } +// Punctuation - /*@internal*/ - export function isColonToken(node: Node): node is ColonToken { - return node.kind === SyntaxKind.ColonToken; - } +export function isDotDotDotToken(node: Node): node is DotDotDotToken { + return node.kind === SyntaxKind.DotDotDotToken; +} - /*@internal*/ - export function isQuestionDotToken(node: Node): node is QuestionDotToken { - return node.kind === SyntaxKind.QuestionDotToken; - } +/*@internal*/ +export function isCommaToken(node: Node): node is Token { + return node.kind === SyntaxKind.CommaToken; +} - /*@internal*/ - export function isEqualsGreaterThanToken(node: Node): node is EqualsGreaterThanToken { - return node.kind === SyntaxKind.EqualsGreaterThanToken; - } +export function isPlusToken(node: Node): node is PlusToken { + return node.kind === SyntaxKind.PlusToken; +} - // Identifiers +export function isMinusToken(node: Node): node is MinusToken { + return node.kind === SyntaxKind.MinusToken; +} - export function isIdentifier(node: Node): node is Identifier { - return node.kind === SyntaxKind.Identifier; - } +export function isAsteriskToken(node: Node): node is AsteriskToken { + return node.kind === SyntaxKind.AsteriskToken; +} - export function isPrivateIdentifier(node: Node): node is PrivateIdentifier { - return node.kind === SyntaxKind.PrivateIdentifier; - } +/*@internal*/ +export function isExclamationToken(node: Node): node is ExclamationToken { + return node.kind === SyntaxKind.ExclamationToken; +} - // Reserved Words +/*@internal*/ +export function isQuestionToken(node: Node): node is QuestionToken { + return node.kind === SyntaxKind.QuestionToken; +} - /* @internal */ - export function isExportModifier(node: Node): node is ExportKeyword { - return node.kind === SyntaxKind.ExportKeyword; - } +/*@internal*/ +export function isColonToken(node: Node): node is ColonToken { + return node.kind === SyntaxKind.ColonToken; +} - /* @internal */ - export function isAsyncModifier(node: Node): node is AsyncKeyword { - return node.kind === SyntaxKind.AsyncKeyword; - } +/*@internal*/ +export function isQuestionDotToken(node: Node): node is QuestionDotToken { + return node.kind === SyntaxKind.QuestionDotToken; +} - /* @internal */ - export function isAssertsKeyword(node: Node): node is AssertsKeyword { - return node.kind === SyntaxKind.AssertsKeyword; - } - - /* @internal */ - export function isAwaitKeyword(node: Node): node is AwaitKeyword { - return node.kind === SyntaxKind.AwaitKeyword; - } +/*@internal*/ +export function isEqualsGreaterThanToken(node: Node): node is EqualsGreaterThanToken { + return node.kind === SyntaxKind.EqualsGreaterThanToken; +} - /* @internal */ - export function isReadonlyKeyword(node: Node): node is ReadonlyKeyword { - return node.kind === SyntaxKind.ReadonlyKeyword; - } +// Identifiers - /* @internal */ - export function isStaticModifier(node: Node): node is StaticKeyword { - return node.kind === SyntaxKind.StaticKeyword; - } +export function isIdentifier(node: Node): node is Identifier { + return node.kind === SyntaxKind.Identifier; +} - /* @internal */ - export function isAbstractModifier(node: Node): node is AbstractKeyword { - return node.kind === SyntaxKind.AbstractKeyword; - } +export function isPrivateIdentifier(node: Node): node is PrivateIdentifier { + return node.kind === SyntaxKind.PrivateIdentifier; +} - /*@internal*/ - export function isSuperKeyword(node: Node): node is SuperExpression { - return node.kind === SyntaxKind.SuperKeyword; - } +// Reserved Words - /*@internal*/ - export function isImportKeyword(node: Node): node is ImportExpression { - return node.kind === SyntaxKind.ImportKeyword; - } - - // Names - - export function isQualifiedName(node: Node): node is QualifiedName { - return node.kind === SyntaxKind.QualifiedName; - } +/* @internal */ +export function isExportModifier(node: Node): node is ExportKeyword { + return node.kind === SyntaxKind.ExportKeyword; +} - export function isComputedPropertyName(node: Node): node is ComputedPropertyName { - return node.kind === SyntaxKind.ComputedPropertyName; - } +/* @internal */ +export function isAsyncModifier(node: Node): node is AsyncKeyword { + return node.kind === SyntaxKind.AsyncKeyword; +} - // Signature elements - - export function isTypeParameterDeclaration(node: Node): node is TypeParameterDeclaration { - return node.kind === SyntaxKind.TypeParameter; - } - - // TODO(rbuckton): Rename to 'isParameterDeclaration' - export function isParameter(node: Node): node is ParameterDeclaration { - return node.kind === SyntaxKind.Parameter; - } +/* @internal */ +export function isAssertsKeyword(node: Node): node is AssertsKeyword { + return node.kind === SyntaxKind.AssertsKeyword; +} - export function isDecorator(node: Node): node is Decorator { - return node.kind === SyntaxKind.Decorator; - } +/* @internal */ +export function isAwaitKeyword(node: Node): node is AwaitKeyword { + return node.kind === SyntaxKind.AwaitKeyword; +} - // TypeMember +/* @internal */ +export function isReadonlyKeyword(node: Node): node is ReadonlyKeyword { + return node.kind === SyntaxKind.ReadonlyKeyword; +} - export function isPropertySignature(node: Node): node is PropertySignature { - return node.kind === SyntaxKind.PropertySignature; - } +/* @internal */ +export function isStaticModifier(node: Node): node is StaticKeyword { + return node.kind === SyntaxKind.StaticKeyword; +} - export function isPropertyDeclaration(node: Node): node is PropertyDeclaration { - return node.kind === SyntaxKind.PropertyDeclaration; - } +/* @internal */ +export function isAbstractModifier(node: Node): node is AbstractKeyword { + return node.kind === SyntaxKind.AbstractKeyword; +} - export function isMethodSignature(node: Node): node is MethodSignature { - return node.kind === SyntaxKind.MethodSignature; - } +/*@internal*/ +export function isSuperKeyword(node: Node): node is SuperExpression { + return node.kind === SyntaxKind.SuperKeyword; +} - export function isMethodDeclaration(node: Node): node is MethodDeclaration { - return node.kind === SyntaxKind.MethodDeclaration; - } +/*@internal*/ +export function isImportKeyword(node: Node): node is ImportExpression { + return node.kind === SyntaxKind.ImportKeyword; +} - export function isClassStaticBlockDeclaration(node: Node): node is ClassStaticBlockDeclaration { - return node.kind === SyntaxKind.ClassStaticBlockDeclaration; - } +// Names - export function isConstructorDeclaration(node: Node): node is ConstructorDeclaration { - return node.kind === SyntaxKind.Constructor; - } +export function isQualifiedName(node: Node): node is QualifiedName { + return node.kind === SyntaxKind.QualifiedName; +} - export function isGetAccessorDeclaration(node: Node): node is GetAccessorDeclaration { - return node.kind === SyntaxKind.GetAccessor; - } +export function isComputedPropertyName(node: Node): node is ComputedPropertyName { + return node.kind === SyntaxKind.ComputedPropertyName; +} - export function isSetAccessorDeclaration(node: Node): node is SetAccessorDeclaration { - return node.kind === SyntaxKind.SetAccessor; - } +// Signature elements - export function isCallSignatureDeclaration(node: Node): node is CallSignatureDeclaration { - return node.kind === SyntaxKind.CallSignature; - } +export function isTypeParameterDeclaration(node: Node): node is TypeParameterDeclaration { + return node.kind === SyntaxKind.TypeParameter; +} - export function isConstructSignatureDeclaration(node: Node): node is ConstructSignatureDeclaration { - return node.kind === SyntaxKind.ConstructSignature; - } +// TODO(rbuckton): Rename to 'isParameterDeclaration' +export function isParameter(node: Node): node is ParameterDeclaration { + return node.kind === SyntaxKind.Parameter; +} - export function isIndexSignatureDeclaration(node: Node): node is IndexSignatureDeclaration { - return node.kind === SyntaxKind.IndexSignature; - } +export function isDecorator(node: Node): node is Decorator { + return node.kind === SyntaxKind.Decorator; +} - // Type +// TypeMember - export function isTypePredicateNode(node: Node): node is TypePredicateNode { - return node.kind === SyntaxKind.TypePredicate; - } +export function isPropertySignature(node: Node): node is PropertySignature { + return node.kind === SyntaxKind.PropertySignature; +} - export function isTypeReferenceNode(node: Node): node is TypeReferenceNode { - return node.kind === SyntaxKind.TypeReference; - } +export function isPropertyDeclaration(node: Node): node is PropertyDeclaration { + return node.kind === SyntaxKind.PropertyDeclaration; +} - export function isFunctionTypeNode(node: Node): node is FunctionTypeNode { - return node.kind === SyntaxKind.FunctionType; - } +export function isMethodSignature(node: Node): node is MethodSignature { + return node.kind === SyntaxKind.MethodSignature; +} - export function isConstructorTypeNode(node: Node): node is ConstructorTypeNode { - return node.kind === SyntaxKind.ConstructorType; - } +export function isMethodDeclaration(node: Node): node is MethodDeclaration { + return node.kind === SyntaxKind.MethodDeclaration; +} - export function isTypeQueryNode(node: Node): node is TypeQueryNode { - return node.kind === SyntaxKind.TypeQuery; - } +export function isClassStaticBlockDeclaration(node: Node): node is ClassStaticBlockDeclaration { + return node.kind === SyntaxKind.ClassStaticBlockDeclaration; +} - export function isTypeLiteralNode(node: Node): node is TypeLiteralNode { - return node.kind === SyntaxKind.TypeLiteral; - } +export function isConstructorDeclaration(node: Node): node is ConstructorDeclaration { + return node.kind === SyntaxKind.Constructor; +} - export function isArrayTypeNode(node: Node): node is ArrayTypeNode { - return node.kind === SyntaxKind.ArrayType; - } +export function isGetAccessorDeclaration(node: Node): node is GetAccessorDeclaration { + return node.kind === SyntaxKind.GetAccessor; +} - export function isTupleTypeNode(node: Node): node is TupleTypeNode { - return node.kind === SyntaxKind.TupleType; - } +export function isSetAccessorDeclaration(node: Node): node is SetAccessorDeclaration { + return node.kind === SyntaxKind.SetAccessor; +} - export function isNamedTupleMember(node: Node): node is NamedTupleMember { - return node.kind === SyntaxKind.NamedTupleMember; - } +export function isCallSignatureDeclaration(node: Node): node is CallSignatureDeclaration { + return node.kind === SyntaxKind.CallSignature; +} - export function isOptionalTypeNode(node: Node): node is OptionalTypeNode { - return node.kind === SyntaxKind.OptionalType; - } +export function isConstructSignatureDeclaration(node: Node): node is ConstructSignatureDeclaration { + return node.kind === SyntaxKind.ConstructSignature; +} - export function isRestTypeNode(node: Node): node is RestTypeNode { - return node.kind === SyntaxKind.RestType; - } +export function isIndexSignatureDeclaration(node: Node): node is IndexSignatureDeclaration { + return node.kind === SyntaxKind.IndexSignature; +} - export function isUnionTypeNode(node: Node): node is UnionTypeNode { - return node.kind === SyntaxKind.UnionType; - } +// Type - export function isIntersectionTypeNode(node: Node): node is IntersectionTypeNode { - return node.kind === SyntaxKind.IntersectionType; - } +export function isTypePredicateNode(node: Node): node is TypePredicateNode { + return node.kind === SyntaxKind.TypePredicate; +} - export function isConditionalTypeNode(node: Node): node is ConditionalTypeNode { - return node.kind === SyntaxKind.ConditionalType; - } +export function isTypeReferenceNode(node: Node): node is TypeReferenceNode { + return node.kind === SyntaxKind.TypeReference; +} - export function isInferTypeNode(node: Node): node is InferTypeNode { - return node.kind === SyntaxKind.InferType; - } +export function isFunctionTypeNode(node: Node): node is FunctionTypeNode { + return node.kind === SyntaxKind.FunctionType; +} - export function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode { - return node.kind === SyntaxKind.ParenthesizedType; - } +export function isConstructorTypeNode(node: Node): node is ConstructorTypeNode { + return node.kind === SyntaxKind.ConstructorType; +} - export function isThisTypeNode(node: Node): node is ThisTypeNode { - return node.kind === SyntaxKind.ThisType; - } +export function isTypeQueryNode(node: Node): node is TypeQueryNode { + return node.kind === SyntaxKind.TypeQuery; +} - export function isTypeOperatorNode(node: Node): node is TypeOperatorNode { - return node.kind === SyntaxKind.TypeOperator; - } +export function isTypeLiteralNode(node: Node): node is TypeLiteralNode { + return node.kind === SyntaxKind.TypeLiteral; +} - export function isIndexedAccessTypeNode(node: Node): node is IndexedAccessTypeNode { - return node.kind === SyntaxKind.IndexedAccessType; - } +export function isArrayTypeNode(node: Node): node is ArrayTypeNode { + return node.kind === SyntaxKind.ArrayType; +} - export function isMappedTypeNode(node: Node): node is MappedTypeNode { - return node.kind === SyntaxKind.MappedType; - } +export function isTupleTypeNode(node: Node): node is TupleTypeNode { + return node.kind === SyntaxKind.TupleType; +} - export function isLiteralTypeNode(node: Node): node is LiteralTypeNode { - return node.kind === SyntaxKind.LiteralType; - } +export function isNamedTupleMember(node: Node): node is NamedTupleMember { + return node.kind === SyntaxKind.NamedTupleMember; +} - export function isImportTypeNode(node: Node): node is ImportTypeNode { - return node.kind === SyntaxKind.ImportType; - } +export function isOptionalTypeNode(node: Node): node is OptionalTypeNode { + return node.kind === SyntaxKind.OptionalType; +} - export function isTemplateLiteralTypeSpan(node: Node): node is TemplateLiteralTypeSpan { - return node.kind === SyntaxKind.TemplateLiteralTypeSpan; - } +export function isRestTypeNode(node: Node): node is RestTypeNode { + return node.kind === SyntaxKind.RestType; +} - export function isTemplateLiteralTypeNode(node: Node): node is TemplateLiteralTypeNode { - return node.kind === SyntaxKind.TemplateLiteralType; - } +export function isUnionTypeNode(node: Node): node is UnionTypeNode { + return node.kind === SyntaxKind.UnionType; +} - // Binding patterns +export function isIntersectionTypeNode(node: Node): node is IntersectionTypeNode { + return node.kind === SyntaxKind.IntersectionType; +} - export function isObjectBindingPattern(node: Node): node is ObjectBindingPattern { - return node.kind === SyntaxKind.ObjectBindingPattern; - } +export function isConditionalTypeNode(node: Node): node is ConditionalTypeNode { + return node.kind === SyntaxKind.ConditionalType; +} - export function isArrayBindingPattern(node: Node): node is ArrayBindingPattern { - return node.kind === SyntaxKind.ArrayBindingPattern; - } +export function isInferTypeNode(node: Node): node is InferTypeNode { + return node.kind === SyntaxKind.InferType; +} - export function isBindingElement(node: Node): node is BindingElement { - return node.kind === SyntaxKind.BindingElement; - } +export function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode { + return node.kind === SyntaxKind.ParenthesizedType; +} - // Expression +export function isThisTypeNode(node: Node): node is ThisTypeNode { + return node.kind === SyntaxKind.ThisType; +} - export function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression { - return node.kind === SyntaxKind.ArrayLiteralExpression; - } +export function isTypeOperatorNode(node: Node): node is TypeOperatorNode { + return node.kind === SyntaxKind.TypeOperator; +} - export function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression { - return node.kind === SyntaxKind.ObjectLiteralExpression; - } +export function isIndexedAccessTypeNode(node: Node): node is IndexedAccessTypeNode { + return node.kind === SyntaxKind.IndexedAccessType; +} - export function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression { - return node.kind === SyntaxKind.PropertyAccessExpression; - } +export function isMappedTypeNode(node: Node): node is MappedTypeNode { + return node.kind === SyntaxKind.MappedType; +} - export function isElementAccessExpression(node: Node): node is ElementAccessExpression { - return node.kind === SyntaxKind.ElementAccessExpression; - } +export function isLiteralTypeNode(node: Node): node is LiteralTypeNode { + return node.kind === SyntaxKind.LiteralType; +} - export function isCallExpression(node: Node): node is CallExpression { - return node.kind === SyntaxKind.CallExpression; - } +export function isImportTypeNode(node: Node): node is ImportTypeNode { + return node.kind === SyntaxKind.ImportType; +} - export function isNewExpression(node: Node): node is NewExpression { - return node.kind === SyntaxKind.NewExpression; - } +export function isTemplateLiteralTypeSpan(node: Node): node is TemplateLiteralTypeSpan { + return node.kind === SyntaxKind.TemplateLiteralTypeSpan; +} - export function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression { - return node.kind === SyntaxKind.TaggedTemplateExpression; - } +export function isTemplateLiteralTypeNode(node: Node): node is TemplateLiteralTypeNode { + return node.kind === SyntaxKind.TemplateLiteralType; +} - export function isTypeAssertionExpression(node: Node): node is TypeAssertion { - return node.kind === SyntaxKind.TypeAssertionExpression; - } +// Binding patterns - export function isParenthesizedExpression(node: Node): node is ParenthesizedExpression { - return node.kind === SyntaxKind.ParenthesizedExpression; - } +export function isObjectBindingPattern(node: Node): node is ObjectBindingPattern { + return node.kind === SyntaxKind.ObjectBindingPattern; +} - export function isFunctionExpression(node: Node): node is FunctionExpression { - return node.kind === SyntaxKind.FunctionExpression; - } +export function isArrayBindingPattern(node: Node): node is ArrayBindingPattern { + return node.kind === SyntaxKind.ArrayBindingPattern; +} - export function isArrowFunction(node: Node): node is ArrowFunction { - return node.kind === SyntaxKind.ArrowFunction; - } +export function isBindingElement(node: Node): node is BindingElement { + return node.kind === SyntaxKind.BindingElement; +} - export function isDeleteExpression(node: Node): node is DeleteExpression { - return node.kind === SyntaxKind.DeleteExpression; - } +// Expression - export function isTypeOfExpression(node: Node): node is TypeOfExpression { - return node.kind === SyntaxKind.TypeOfExpression; - } +export function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression { + return node.kind === SyntaxKind.ArrayLiteralExpression; +} - export function isVoidExpression(node: Node): node is VoidExpression { - return node.kind === SyntaxKind.VoidExpression; - } +export function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression { + return node.kind === SyntaxKind.ObjectLiteralExpression; +} - export function isAwaitExpression(node: Node): node is AwaitExpression { - return node.kind === SyntaxKind.AwaitExpression; - } +export function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression { + return node.kind === SyntaxKind.PropertyAccessExpression; +} - export function isPrefixUnaryExpression(node: Node): node is PrefixUnaryExpression { - return node.kind === SyntaxKind.PrefixUnaryExpression; - } +export function isElementAccessExpression(node: Node): node is ElementAccessExpression { + return node.kind === SyntaxKind.ElementAccessExpression; +} - export function isPostfixUnaryExpression(node: Node): node is PostfixUnaryExpression { - return node.kind === SyntaxKind.PostfixUnaryExpression; - } +export function isCallExpression(node: Node): node is CallExpression { + return node.kind === SyntaxKind.CallExpression; +} - export function isBinaryExpression(node: Node): node is BinaryExpression { - return node.kind === SyntaxKind.BinaryExpression; - } +export function isNewExpression(node: Node): node is NewExpression { + return node.kind === SyntaxKind.NewExpression; +} - export function isConditionalExpression(node: Node): node is ConditionalExpression { - return node.kind === SyntaxKind.ConditionalExpression; - } +export function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression { + return node.kind === SyntaxKind.TaggedTemplateExpression; +} - export function isTemplateExpression(node: Node): node is TemplateExpression { - return node.kind === SyntaxKind.TemplateExpression; - } +export function isTypeAssertionExpression(node: Node): node is TypeAssertion { + return node.kind === SyntaxKind.TypeAssertionExpression; +} - export function isYieldExpression(node: Node): node is YieldExpression { - return node.kind === SyntaxKind.YieldExpression; - } +export function isParenthesizedExpression(node: Node): node is ParenthesizedExpression { + return node.kind === SyntaxKind.ParenthesizedExpression; +} - export function isSpreadElement(node: Node): node is SpreadElement { - return node.kind === SyntaxKind.SpreadElement; - } +export function isFunctionExpression(node: Node): node is FunctionExpression { + return node.kind === SyntaxKind.FunctionExpression; +} - export function isClassExpression(node: Node): node is ClassExpression { - return node.kind === SyntaxKind.ClassExpression; - } +export function isArrowFunction(node: Node): node is ArrowFunction { + return node.kind === SyntaxKind.ArrowFunction; +} - export function isOmittedExpression(node: Node): node is OmittedExpression { - return node.kind === SyntaxKind.OmittedExpression; - } +export function isDeleteExpression(node: Node): node is DeleteExpression { + return node.kind === SyntaxKind.DeleteExpression; +} - export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments { - return node.kind === SyntaxKind.ExpressionWithTypeArguments; - } +export function isTypeOfExpression(node: Node): node is TypeOfExpression { + return node.kind === SyntaxKind.TypeOfExpression; +} - export function isAsExpression(node: Node): node is AsExpression { - return node.kind === SyntaxKind.AsExpression; - } +export function isVoidExpression(node: Node): node is VoidExpression { + return node.kind === SyntaxKind.VoidExpression; +} - export function isNonNullExpression(node: Node): node is NonNullExpression { - return node.kind === SyntaxKind.NonNullExpression; - } +export function isAwaitExpression(node: Node): node is AwaitExpression { + return node.kind === SyntaxKind.AwaitExpression; +} - export function isMetaProperty(node: Node): node is MetaProperty { - return node.kind === SyntaxKind.MetaProperty; - } +export function isPrefixUnaryExpression(node: Node): node is PrefixUnaryExpression { + return node.kind === SyntaxKind.PrefixUnaryExpression; +} - export function isSyntheticExpression(node: Node): node is SyntheticExpression { - return node.kind === SyntaxKind.SyntheticExpression; - } +export function isPostfixUnaryExpression(node: Node): node is PostfixUnaryExpression { + return node.kind === SyntaxKind.PostfixUnaryExpression; +} - export function isPartiallyEmittedExpression(node: Node): node is PartiallyEmittedExpression { - return node.kind === SyntaxKind.PartiallyEmittedExpression; - } +export function isBinaryExpression(node: Node): node is BinaryExpression { + return node.kind === SyntaxKind.BinaryExpression; +} - export function isCommaListExpression(node: Node): node is CommaListExpression { - return node.kind === SyntaxKind.CommaListExpression; - } +export function isConditionalExpression(node: Node): node is ConditionalExpression { + return node.kind === SyntaxKind.ConditionalExpression; +} - // Misc +export function isTemplateExpression(node: Node): node is TemplateExpression { + return node.kind === SyntaxKind.TemplateExpression; +} - export function isTemplateSpan(node: Node): node is TemplateSpan { - return node.kind === SyntaxKind.TemplateSpan; - } +export function isYieldExpression(node: Node): node is YieldExpression { + return node.kind === SyntaxKind.YieldExpression; +} - export function isSemicolonClassElement(node: Node): node is SemicolonClassElement { - return node.kind === SyntaxKind.SemicolonClassElement; - } +export function isSpreadElement(node: Node): node is SpreadElement { + return node.kind === SyntaxKind.SpreadElement; +} - // Elements +export function isClassExpression(node: Node): node is ClassExpression { + return node.kind === SyntaxKind.ClassExpression; +} - export function isBlock(node: Node): node is Block { - return node.kind === SyntaxKind.Block; - } +export function isOmittedExpression(node: Node): node is OmittedExpression { + return node.kind === SyntaxKind.OmittedExpression; +} - export function isVariableStatement(node: Node): node is VariableStatement { - return node.kind === SyntaxKind.VariableStatement; - } +export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments { + return node.kind === SyntaxKind.ExpressionWithTypeArguments; +} - export function isEmptyStatement(node: Node): node is EmptyStatement { - return node.kind === SyntaxKind.EmptyStatement; - } +export function isAsExpression(node: Node): node is AsExpression { + return node.kind === SyntaxKind.AsExpression; +} - export function isExpressionStatement(node: Node): node is ExpressionStatement { - return node.kind === SyntaxKind.ExpressionStatement; - } +export function isNonNullExpression(node: Node): node is NonNullExpression { + return node.kind === SyntaxKind.NonNullExpression; +} - export function isIfStatement(node: Node): node is IfStatement { - return node.kind === SyntaxKind.IfStatement; - } +export function isMetaProperty(node: Node): node is MetaProperty { + return node.kind === SyntaxKind.MetaProperty; +} - export function isDoStatement(node: Node): node is DoStatement { - return node.kind === SyntaxKind.DoStatement; - } +export function isSyntheticExpression(node: Node): node is SyntheticExpression { + return node.kind === SyntaxKind.SyntheticExpression; +} - export function isWhileStatement(node: Node): node is WhileStatement { - return node.kind === SyntaxKind.WhileStatement; - } +export function isPartiallyEmittedExpression(node: Node): node is PartiallyEmittedExpression { + return node.kind === SyntaxKind.PartiallyEmittedExpression; +} - export function isForStatement(node: Node): node is ForStatement { - return node.kind === SyntaxKind.ForStatement; - } +export function isCommaListExpression(node: Node): node is CommaListExpression { + return node.kind === SyntaxKind.CommaListExpression; +} - export function isForInStatement(node: Node): node is ForInStatement { - return node.kind === SyntaxKind.ForInStatement; - } +// Misc - export function isForOfStatement(node: Node): node is ForOfStatement { - return node.kind === SyntaxKind.ForOfStatement; - } +export function isTemplateSpan(node: Node): node is TemplateSpan { + return node.kind === SyntaxKind.TemplateSpan; +} - export function isContinueStatement(node: Node): node is ContinueStatement { - return node.kind === SyntaxKind.ContinueStatement; - } +export function isSemicolonClassElement(node: Node): node is SemicolonClassElement { + return node.kind === SyntaxKind.SemicolonClassElement; +} - export function isBreakStatement(node: Node): node is BreakStatement { - return node.kind === SyntaxKind.BreakStatement; - } +// Elements - export function isReturnStatement(node: Node): node is ReturnStatement { - return node.kind === SyntaxKind.ReturnStatement; - } +export function isBlock(node: Node): node is Block { + return node.kind === SyntaxKind.Block; +} - export function isWithStatement(node: Node): node is WithStatement { - return node.kind === SyntaxKind.WithStatement; - } +export function isVariableStatement(node: Node): node is VariableStatement { + return node.kind === SyntaxKind.VariableStatement; +} - export function isSwitchStatement(node: Node): node is SwitchStatement { - return node.kind === SyntaxKind.SwitchStatement; - } +export function isEmptyStatement(node: Node): node is EmptyStatement { + return node.kind === SyntaxKind.EmptyStatement; +} - export function isLabeledStatement(node: Node): node is LabeledStatement { - return node.kind === SyntaxKind.LabeledStatement; - } +export function isExpressionStatement(node: Node): node is ExpressionStatement { + return node.kind === SyntaxKind.ExpressionStatement; +} - export function isThrowStatement(node: Node): node is ThrowStatement { - return node.kind === SyntaxKind.ThrowStatement; - } +export function isIfStatement(node: Node): node is IfStatement { + return node.kind === SyntaxKind.IfStatement; +} - export function isTryStatement(node: Node): node is TryStatement { - return node.kind === SyntaxKind.TryStatement; - } +export function isDoStatement(node: Node): node is DoStatement { + return node.kind === SyntaxKind.DoStatement; +} - export function isDebuggerStatement(node: Node): node is DebuggerStatement { - return node.kind === SyntaxKind.DebuggerStatement; - } +export function isWhileStatement(node: Node): node is WhileStatement { + return node.kind === SyntaxKind.WhileStatement; +} - export function isVariableDeclaration(node: Node): node is VariableDeclaration { - return node.kind === SyntaxKind.VariableDeclaration; - } +export function isForStatement(node: Node): node is ForStatement { + return node.kind === SyntaxKind.ForStatement; +} - export function isVariableDeclarationList(node: Node): node is VariableDeclarationList { - return node.kind === SyntaxKind.VariableDeclarationList; - } +export function isForInStatement(node: Node): node is ForInStatement { + return node.kind === SyntaxKind.ForInStatement; +} - export function isFunctionDeclaration(node: Node): node is FunctionDeclaration { - return node.kind === SyntaxKind.FunctionDeclaration; - } +export function isForOfStatement(node: Node): node is ForOfStatement { + return node.kind === SyntaxKind.ForOfStatement; +} - export function isClassDeclaration(node: Node): node is ClassDeclaration { - return node.kind === SyntaxKind.ClassDeclaration; - } +export function isContinueStatement(node: Node): node is ContinueStatement { + return node.kind === SyntaxKind.ContinueStatement; +} - export function isInterfaceDeclaration(node: Node): node is InterfaceDeclaration { - return node.kind === SyntaxKind.InterfaceDeclaration; - } +export function isBreakStatement(node: Node): node is BreakStatement { + return node.kind === SyntaxKind.BreakStatement; +} - export function isTypeAliasDeclaration(node: Node): node is TypeAliasDeclaration { - return node.kind === SyntaxKind.TypeAliasDeclaration; - } +export function isReturnStatement(node: Node): node is ReturnStatement { + return node.kind === SyntaxKind.ReturnStatement; +} - export function isEnumDeclaration(node: Node): node is EnumDeclaration { - return node.kind === SyntaxKind.EnumDeclaration; - } +export function isWithStatement(node: Node): node is WithStatement { + return node.kind === SyntaxKind.WithStatement; +} - export function isModuleDeclaration(node: Node): node is ModuleDeclaration { - return node.kind === SyntaxKind.ModuleDeclaration; - } +export function isSwitchStatement(node: Node): node is SwitchStatement { + return node.kind === SyntaxKind.SwitchStatement; +} - export function isModuleBlock(node: Node): node is ModuleBlock { - return node.kind === SyntaxKind.ModuleBlock; - } +export function isLabeledStatement(node: Node): node is LabeledStatement { + return node.kind === SyntaxKind.LabeledStatement; +} - export function isCaseBlock(node: Node): node is CaseBlock { - return node.kind === SyntaxKind.CaseBlock; - } +export function isThrowStatement(node: Node): node is ThrowStatement { + return node.kind === SyntaxKind.ThrowStatement; +} - export function isNamespaceExportDeclaration(node: Node): node is NamespaceExportDeclaration { - return node.kind === SyntaxKind.NamespaceExportDeclaration; - } +export function isTryStatement(node: Node): node is TryStatement { + return node.kind === SyntaxKind.TryStatement; +} - export function isImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration { - return node.kind === SyntaxKind.ImportEqualsDeclaration; - } +export function isDebuggerStatement(node: Node): node is DebuggerStatement { + return node.kind === SyntaxKind.DebuggerStatement; +} - export function isImportDeclaration(node: Node): node is ImportDeclaration { - return node.kind === SyntaxKind.ImportDeclaration; - } +export function isVariableDeclaration(node: Node): node is VariableDeclaration { + return node.kind === SyntaxKind.VariableDeclaration; +} - export function isImportClause(node: Node): node is ImportClause { - return node.kind === SyntaxKind.ImportClause; - } +export function isVariableDeclarationList(node: Node): node is VariableDeclarationList { + return node.kind === SyntaxKind.VariableDeclarationList; +} - export function isAssertClause(node: Node): node is AssertClause { - return node.kind === SyntaxKind.AssertClause; - } +export function isFunctionDeclaration(node: Node): node is FunctionDeclaration { + return node.kind === SyntaxKind.FunctionDeclaration; +} - export function isAssertEntry(node: Node): node is AssertEntry { - return node.kind === SyntaxKind.AssertEntry; - } +export function isClassDeclaration(node: Node): node is ClassDeclaration { + return node.kind === SyntaxKind.ClassDeclaration; +} - export function isNamespaceImport(node: Node): node is NamespaceImport { - return node.kind === SyntaxKind.NamespaceImport; - } +export function isInterfaceDeclaration(node: Node): node is InterfaceDeclaration { + return node.kind === SyntaxKind.InterfaceDeclaration; +} - export function isNamespaceExport(node: Node): node is NamespaceExport { - return node.kind === SyntaxKind.NamespaceExport; - } +export function isTypeAliasDeclaration(node: Node): node is TypeAliasDeclaration { + return node.kind === SyntaxKind.TypeAliasDeclaration; +} - export function isNamedImports(node: Node): node is NamedImports { - return node.kind === SyntaxKind.NamedImports; - } +export function isEnumDeclaration(node: Node): node is EnumDeclaration { + return node.kind === SyntaxKind.EnumDeclaration; +} - export function isImportSpecifier(node: Node): node is ImportSpecifier { - return node.kind === SyntaxKind.ImportSpecifier; - } +export function isModuleDeclaration(node: Node): node is ModuleDeclaration { + return node.kind === SyntaxKind.ModuleDeclaration; +} - export function isExportAssignment(node: Node): node is ExportAssignment { - return node.kind === SyntaxKind.ExportAssignment; - } +export function isModuleBlock(node: Node): node is ModuleBlock { + return node.kind === SyntaxKind.ModuleBlock; +} - export function isExportDeclaration(node: Node): node is ExportDeclaration { - return node.kind === SyntaxKind.ExportDeclaration; - } +export function isCaseBlock(node: Node): node is CaseBlock { + return node.kind === SyntaxKind.CaseBlock; +} - export function isNamedExports(node: Node): node is NamedExports { - return node.kind === SyntaxKind.NamedExports; - } +export function isNamespaceExportDeclaration(node: Node): node is NamespaceExportDeclaration { + return node.kind === SyntaxKind.NamespaceExportDeclaration; +} - export function isExportSpecifier(node: Node): node is ExportSpecifier { - return node.kind === SyntaxKind.ExportSpecifier; - } +export function isImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration { + return node.kind === SyntaxKind.ImportEqualsDeclaration; +} - export function isMissingDeclaration(node: Node): node is MissingDeclaration { - return node.kind === SyntaxKind.MissingDeclaration; - } +export function isImportDeclaration(node: Node): node is ImportDeclaration { + return node.kind === SyntaxKind.ImportDeclaration; +} - export function isNotEmittedStatement(node: Node): node is NotEmittedStatement { - return node.kind === SyntaxKind.NotEmittedStatement; - } +export function isImportClause(node: Node): node is ImportClause { + return node.kind === SyntaxKind.ImportClause; +} - /* @internal */ - export function isSyntheticReference(node: Node): node is SyntheticReferenceExpression { - return node.kind === SyntaxKind.SyntheticReferenceExpression; - } +export function isAssertClause(node: Node): node is AssertClause { + return node.kind === SyntaxKind.AssertClause; +} - /* @internal */ - export function isMergeDeclarationMarker(node: Node): node is MergeDeclarationMarker { - return node.kind === SyntaxKind.MergeDeclarationMarker; - } +export function isAssertEntry(node: Node): node is AssertEntry { + return node.kind === SyntaxKind.AssertEntry; +} - /* @internal */ - export function isEndOfDeclarationMarker(node: Node): node is EndOfDeclarationMarker { - return node.kind === SyntaxKind.EndOfDeclarationMarker; - } +export function isNamespaceImport(node: Node): node is NamespaceImport { + return node.kind === SyntaxKind.NamespaceImport; +} - // Module References +export function isNamespaceExport(node: Node): node is NamespaceExport { + return node.kind === SyntaxKind.NamespaceExport; +} - export function isExternalModuleReference(node: Node): node is ExternalModuleReference { - return node.kind === SyntaxKind.ExternalModuleReference; - } +export function isNamedImports(node: Node): node is NamedImports { + return node.kind === SyntaxKind.NamedImports; +} - // JSX +export function isImportSpecifier(node: Node): node is ImportSpecifier { + return node.kind === SyntaxKind.ImportSpecifier; +} + +export function isExportAssignment(node: Node): node is ExportAssignment { + return node.kind === SyntaxKind.ExportAssignment; +} + +export function isExportDeclaration(node: Node): node is ExportDeclaration { + return node.kind === SyntaxKind.ExportDeclaration; +} + +export function isNamedExports(node: Node): node is NamedExports { + return node.kind === SyntaxKind.NamedExports; +} + +export function isExportSpecifier(node: Node): node is ExportSpecifier { + return node.kind === SyntaxKind.ExportSpecifier; +} + +export function isMissingDeclaration(node: Node): node is MissingDeclaration { + return node.kind === SyntaxKind.MissingDeclaration; +} + +export function isNotEmittedStatement(node: Node): node is NotEmittedStatement { + return node.kind === SyntaxKind.NotEmittedStatement; +} + +/* @internal */ +export function isSyntheticReference(node: Node): node is SyntheticReferenceExpression { + return node.kind === SyntaxKind.SyntheticReferenceExpression; +} + +/* @internal */ +export function isMergeDeclarationMarker(node: Node): node is MergeDeclarationMarker { + return node.kind === SyntaxKind.MergeDeclarationMarker; +} + +/* @internal */ +export function isEndOfDeclarationMarker(node: Node): node is EndOfDeclarationMarker { + return node.kind === SyntaxKind.EndOfDeclarationMarker; +} + +// Module References + +export function isExternalModuleReference(node: Node): node is ExternalModuleReference { + return node.kind === SyntaxKind.ExternalModuleReference; +} - export function isJsxElement(node: Node): node is JsxElement { - return node.kind === SyntaxKind.JsxElement; - } +// JSX - export function isJsxSelfClosingElement(node: Node): node is JsxSelfClosingElement { - return node.kind === SyntaxKind.JsxSelfClosingElement; - } +export function isJsxElement(node: Node): node is JsxElement { + return node.kind === SyntaxKind.JsxElement; +} - export function isJsxOpeningElement(node: Node): node is JsxOpeningElement { - return node.kind === SyntaxKind.JsxOpeningElement; - } +export function isJsxSelfClosingElement(node: Node): node is JsxSelfClosingElement { + return node.kind === SyntaxKind.JsxSelfClosingElement; +} - export function isJsxClosingElement(node: Node): node is JsxClosingElement { - return node.kind === SyntaxKind.JsxClosingElement; - } +export function isJsxOpeningElement(node: Node): node is JsxOpeningElement { + return node.kind === SyntaxKind.JsxOpeningElement; +} - export function isJsxFragment(node: Node): node is JsxFragment { - return node.kind === SyntaxKind.JsxFragment; - } +export function isJsxClosingElement(node: Node): node is JsxClosingElement { + return node.kind === SyntaxKind.JsxClosingElement; +} + +export function isJsxFragment(node: Node): node is JsxFragment { + return node.kind === SyntaxKind.JsxFragment; +} - export function isJsxOpeningFragment(node: Node): node is JsxOpeningFragment { - return node.kind === SyntaxKind.JsxOpeningFragment; - } +export function isJsxOpeningFragment(node: Node): node is JsxOpeningFragment { + return node.kind === SyntaxKind.JsxOpeningFragment; +} - export function isJsxClosingFragment(node: Node): node is JsxClosingFragment { - return node.kind === SyntaxKind.JsxClosingFragment; - } +export function isJsxClosingFragment(node: Node): node is JsxClosingFragment { + return node.kind === SyntaxKind.JsxClosingFragment; +} - export function isJsxAttribute(node: Node): node is JsxAttribute { - return node.kind === SyntaxKind.JsxAttribute; - } +export function isJsxAttribute(node: Node): node is JsxAttribute { + return node.kind === SyntaxKind.JsxAttribute; +} - export function isJsxAttributes(node: Node): node is JsxAttributes { - return node.kind === SyntaxKind.JsxAttributes; - } +export function isJsxAttributes(node: Node): node is JsxAttributes { + return node.kind === SyntaxKind.JsxAttributes; +} - export function isJsxSpreadAttribute(node: Node): node is JsxSpreadAttribute { - return node.kind === SyntaxKind.JsxSpreadAttribute; - } +export function isJsxSpreadAttribute(node: Node): node is JsxSpreadAttribute { + return node.kind === SyntaxKind.JsxSpreadAttribute; +} - export function isJsxExpression(node: Node): node is JsxExpression { - return node.kind === SyntaxKind.JsxExpression; - } +export function isJsxExpression(node: Node): node is JsxExpression { + return node.kind === SyntaxKind.JsxExpression; +} - // Clauses +// Clauses - export function isCaseClause(node: Node): node is CaseClause { - return node.kind === SyntaxKind.CaseClause; - } +export function isCaseClause(node: Node): node is CaseClause { + return node.kind === SyntaxKind.CaseClause; +} - export function isDefaultClause(node: Node): node is DefaultClause { - return node.kind === SyntaxKind.DefaultClause; - } +export function isDefaultClause(node: Node): node is DefaultClause { + return node.kind === SyntaxKind.DefaultClause; +} - export function isHeritageClause(node: Node): node is HeritageClause { - return node.kind === SyntaxKind.HeritageClause; - } +export function isHeritageClause(node: Node): node is HeritageClause { + return node.kind === SyntaxKind.HeritageClause; +} - export function isCatchClause(node: Node): node is CatchClause { - return node.kind === SyntaxKind.CatchClause; - } +export function isCatchClause(node: Node): node is CatchClause { + return node.kind === SyntaxKind.CatchClause; +} - // Property assignments +// Property assignments - export function isPropertyAssignment(node: Node): node is PropertyAssignment { - return node.kind === SyntaxKind.PropertyAssignment; - } +export function isPropertyAssignment(node: Node): node is PropertyAssignment { + return node.kind === SyntaxKind.PropertyAssignment; +} - export function isShorthandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { - return node.kind === SyntaxKind.ShorthandPropertyAssignment; - } +export function isShorthandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { + return node.kind === SyntaxKind.ShorthandPropertyAssignment; +} - export function isSpreadAssignment(node: Node): node is SpreadAssignment { - return node.kind === SyntaxKind.SpreadAssignment; - } +export function isSpreadAssignment(node: Node): node is SpreadAssignment { + return node.kind === SyntaxKind.SpreadAssignment; +} - // Enum +// Enum - export function isEnumMember(node: Node): node is EnumMember { - return node.kind === SyntaxKind.EnumMember; - } +export function isEnumMember(node: Node): node is EnumMember { + return node.kind === SyntaxKind.EnumMember; +} - // Unparsed +// Unparsed - // TODO(rbuckton): isUnparsedPrologue +// TODO(rbuckton): isUnparsedPrologue - export function isUnparsedPrepend(node: Node): node is UnparsedPrepend { - return node.kind === SyntaxKind.UnparsedPrepend; - } +export function isUnparsedPrepend(node: Node): node is UnparsedPrepend { + return node.kind === SyntaxKind.UnparsedPrepend; +} - // TODO(rbuckton): isUnparsedText - // TODO(rbuckton): isUnparsedInternalText - // TODO(rbuckton): isUnparsedSyntheticReference +// TODO(rbuckton): isUnparsedText +// TODO(rbuckton): isUnparsedInternalText +// TODO(rbuckton): isUnparsedSyntheticReference - // Top-level nodes - export function isSourceFile(node: Node): node is SourceFile { - return node.kind === SyntaxKind.SourceFile; - } +// Top-level nodes +export function isSourceFile(node: Node): node is SourceFile { + return node.kind === SyntaxKind.SourceFile; +} - export function isBundle(node: Node): node is Bundle { - return node.kind === SyntaxKind.Bundle; - } +export function isBundle(node: Node): node is Bundle { + return node.kind === SyntaxKind.Bundle; +} - export function isUnparsedSource(node: Node): node is UnparsedSource { - return node.kind === SyntaxKind.UnparsedSource; - } +export function isUnparsedSource(node: Node): node is UnparsedSource { + return node.kind === SyntaxKind.UnparsedSource; +} - // TODO(rbuckton): isInputFiles +// TODO(rbuckton): isInputFiles - // JSDoc Elements +// JSDoc Elements - export function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression { - return node.kind === SyntaxKind.JSDocTypeExpression; - } +export function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression { + return node.kind === SyntaxKind.JSDocTypeExpression; +} - export function isJSDocNameReference(node: Node): node is JSDocNameReference { - return node.kind === SyntaxKind.JSDocNameReference; - } +export function isJSDocNameReference(node: Node): node is JSDocNameReference { + return node.kind === SyntaxKind.JSDocNameReference; +} - export function isJSDocMemberName(node: Node): node is JSDocMemberName { - return node.kind === SyntaxKind.JSDocMemberName; - } +export function isJSDocMemberName(node: Node): node is JSDocMemberName { + return node.kind === SyntaxKind.JSDocMemberName; +} - export function isJSDocLink(node: Node): node is JSDocLink { - return node.kind === SyntaxKind.JSDocLink; - } +export function isJSDocLink(node: Node): node is JSDocLink { + return node.kind === SyntaxKind.JSDocLink; +} - export function isJSDocLinkCode(node: Node): node is JSDocLinkCode { - return node.kind === SyntaxKind.JSDocLinkCode; - } +export function isJSDocLinkCode(node: Node): node is JSDocLinkCode { + return node.kind === SyntaxKind.JSDocLinkCode; +} - export function isJSDocLinkPlain(node: Node): node is JSDocLinkPlain { - return node.kind === SyntaxKind.JSDocLinkPlain; - } +export function isJSDocLinkPlain(node: Node): node is JSDocLinkPlain { + return node.kind === SyntaxKind.JSDocLinkPlain; +} - export function isJSDocAllType(node: Node): node is JSDocAllType { - return node.kind === SyntaxKind.JSDocAllType; - } +export function isJSDocAllType(node: Node): node is JSDocAllType { + return node.kind === SyntaxKind.JSDocAllType; +} - export function isJSDocUnknownType(node: Node): node is JSDocUnknownType { - return node.kind === SyntaxKind.JSDocUnknownType; - } +export function isJSDocUnknownType(node: Node): node is JSDocUnknownType { + return node.kind === SyntaxKind.JSDocUnknownType; +} - export function isJSDocNullableType(node: Node): node is JSDocNullableType { - return node.kind === SyntaxKind.JSDocNullableType; - } +export function isJSDocNullableType(node: Node): node is JSDocNullableType { + return node.kind === SyntaxKind.JSDocNullableType; +} - export function isJSDocNonNullableType(node: Node): node is JSDocNonNullableType { - return node.kind === SyntaxKind.JSDocNonNullableType; - } +export function isJSDocNonNullableType(node: Node): node is JSDocNonNullableType { + return node.kind === SyntaxKind.JSDocNonNullableType; +} - export function isJSDocOptionalType(node: Node): node is JSDocOptionalType { - return node.kind === SyntaxKind.JSDocOptionalType; - } +export function isJSDocOptionalType(node: Node): node is JSDocOptionalType { + return node.kind === SyntaxKind.JSDocOptionalType; +} - export function isJSDocFunctionType(node: Node): node is JSDocFunctionType { - return node.kind === SyntaxKind.JSDocFunctionType; - } +export function isJSDocFunctionType(node: Node): node is JSDocFunctionType { + return node.kind === SyntaxKind.JSDocFunctionType; +} - export function isJSDocVariadicType(node: Node): node is JSDocVariadicType { - return node.kind === SyntaxKind.JSDocVariadicType; - } +export function isJSDocVariadicType(node: Node): node is JSDocVariadicType { + return node.kind === SyntaxKind.JSDocVariadicType; +} - export function isJSDocNamepathType(node: Node): node is JSDocNamepathType { - return node.kind === SyntaxKind.JSDocNamepathType; - } +export function isJSDocNamepathType(node: Node): node is JSDocNamepathType { + return node.kind === SyntaxKind.JSDocNamepathType; +} - export function isJSDoc(node: Node): node is JSDoc { - return node.kind === SyntaxKind.JSDocComment; - } +export function isJSDoc(node: Node): node is JSDoc { + return node.kind === SyntaxKind.JSDocComment; +} - export function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral { - return node.kind === SyntaxKind.JSDocTypeLiteral; - } +export function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral { + return node.kind === SyntaxKind.JSDocTypeLiteral; +} - export function isJSDocSignature(node: Node): node is JSDocSignature { - return node.kind === SyntaxKind.JSDocSignature; - } +export function isJSDocSignature(node: Node): node is JSDocSignature { + return node.kind === SyntaxKind.JSDocSignature; +} - // JSDoc Tags +// JSDoc Tags - export function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag { - return node.kind === SyntaxKind.JSDocAugmentsTag; - } +export function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag { + return node.kind === SyntaxKind.JSDocAugmentsTag; +} - export function isJSDocAuthorTag(node: Node): node is JSDocAuthorTag { - return node.kind === SyntaxKind.JSDocAuthorTag; - } +export function isJSDocAuthorTag(node: Node): node is JSDocAuthorTag { + return node.kind === SyntaxKind.JSDocAuthorTag; +} - export function isJSDocClassTag(node: Node): node is JSDocClassTag { - return node.kind === SyntaxKind.JSDocClassTag; - } +export function isJSDocClassTag(node: Node): node is JSDocClassTag { + return node.kind === SyntaxKind.JSDocClassTag; +} - export function isJSDocCallbackTag(node: Node): node is JSDocCallbackTag { - return node.kind === SyntaxKind.JSDocCallbackTag; - } +export function isJSDocCallbackTag(node: Node): node is JSDocCallbackTag { + return node.kind === SyntaxKind.JSDocCallbackTag; +} - export function isJSDocPublicTag(node: Node): node is JSDocPublicTag { - return node.kind === SyntaxKind.JSDocPublicTag; - } +export function isJSDocPublicTag(node: Node): node is JSDocPublicTag { + return node.kind === SyntaxKind.JSDocPublicTag; +} - export function isJSDocPrivateTag(node: Node): node is JSDocPrivateTag { - return node.kind === SyntaxKind.JSDocPrivateTag; - } +export function isJSDocPrivateTag(node: Node): node is JSDocPrivateTag { + return node.kind === SyntaxKind.JSDocPrivateTag; +} - export function isJSDocProtectedTag(node: Node): node is JSDocProtectedTag { - return node.kind === SyntaxKind.JSDocProtectedTag; - } +export function isJSDocProtectedTag(node: Node): node is JSDocProtectedTag { + return node.kind === SyntaxKind.JSDocProtectedTag; +} - export function isJSDocReadonlyTag(node: Node): node is JSDocReadonlyTag { - return node.kind === SyntaxKind.JSDocReadonlyTag; - } +export function isJSDocReadonlyTag(node: Node): node is JSDocReadonlyTag { + return node.kind === SyntaxKind.JSDocReadonlyTag; +} - export function isJSDocOverrideTag(node: Node): node is JSDocOverrideTag { - return node.kind === SyntaxKind.JSDocOverrideTag; - } +export function isJSDocOverrideTag(node: Node): node is JSDocOverrideTag { + return node.kind === SyntaxKind.JSDocOverrideTag; +} - export function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag { - return node.kind === SyntaxKind.JSDocDeprecatedTag; - } +export function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag { + return node.kind === SyntaxKind.JSDocDeprecatedTag; +} - export function isJSDocSeeTag(node: Node): node is JSDocSeeTag { - return node.kind === SyntaxKind.JSDocSeeTag; - } +export function isJSDocSeeTag(node: Node): node is JSDocSeeTag { + return node.kind === SyntaxKind.JSDocSeeTag; +} - export function isJSDocEnumTag(node: Node): node is JSDocEnumTag { - return node.kind === SyntaxKind.JSDocEnumTag; - } +export function isJSDocEnumTag(node: Node): node is JSDocEnumTag { + return node.kind === SyntaxKind.JSDocEnumTag; +} - export function isJSDocParameterTag(node: Node): node is JSDocParameterTag { - return node.kind === SyntaxKind.JSDocParameterTag; - } +export function isJSDocParameterTag(node: Node): node is JSDocParameterTag { + return node.kind === SyntaxKind.JSDocParameterTag; +} - export function isJSDocReturnTag(node: Node): node is JSDocReturnTag { - return node.kind === SyntaxKind.JSDocReturnTag; - } +export function isJSDocReturnTag(node: Node): node is JSDocReturnTag { + return node.kind === SyntaxKind.JSDocReturnTag; +} - export function isJSDocThisTag(node: Node): node is JSDocThisTag { - return node.kind === SyntaxKind.JSDocThisTag; - } +export function isJSDocThisTag(node: Node): node is JSDocThisTag { + return node.kind === SyntaxKind.JSDocThisTag; +} - export function isJSDocTypeTag(node: Node): node is JSDocTypeTag { - return node.kind === SyntaxKind.JSDocTypeTag; - } +export function isJSDocTypeTag(node: Node): node is JSDocTypeTag { + return node.kind === SyntaxKind.JSDocTypeTag; +} - export function isJSDocTemplateTag(node: Node): node is JSDocTemplateTag { - return node.kind === SyntaxKind.JSDocTemplateTag; - } +export function isJSDocTemplateTag(node: Node): node is JSDocTemplateTag { + return node.kind === SyntaxKind.JSDocTemplateTag; +} - export function isJSDocTypedefTag(node: Node): node is JSDocTypedefTag { - return node.kind === SyntaxKind.JSDocTypedefTag; - } +export function isJSDocTypedefTag(node: Node): node is JSDocTypedefTag { + return node.kind === SyntaxKind.JSDocTypedefTag; +} - export function isJSDocUnknownTag(node: Node): node is JSDocUnknownTag { - return node.kind === SyntaxKind.JSDocTag; - } +export function isJSDocUnknownTag(node: Node): node is JSDocUnknownTag { + return node.kind === SyntaxKind.JSDocTag; +} - export function isJSDocPropertyTag(node: Node): node is JSDocPropertyTag { - return node.kind === SyntaxKind.JSDocPropertyTag; - } +export function isJSDocPropertyTag(node: Node): node is JSDocPropertyTag { + return node.kind === SyntaxKind.JSDocPropertyTag; +} - export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag { - return node.kind === SyntaxKind.JSDocImplementsTag; - } +export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag { + return node.kind === SyntaxKind.JSDocImplementsTag; +} - // Synthesized list +// Synthesized list - /* @internal */ - export function isSyntaxList(n: Node): n is SyntaxList { - return n.kind === SyntaxKind.SyntaxList; - } +/* @internal */ +export function isSyntaxList(n: Node): n is SyntaxList { + return n.kind === SyntaxKind.SyntaxList; +} } diff --git a/src/compiler/factory/parenthesizerRules.ts b/src/compiler/factory/parenthesizerRules.ts index 1e2cb936582fa..e6851149abede 100644 --- a/src/compiler/factory/parenthesizerRules.ts +++ b/src/compiler/factory/parenthesizerRules.ts @@ -1,455 +1,455 @@ /* @internal */ namespace ts { - export function createParenthesizerRules(factory: NodeFactory): ParenthesizerRules { - interface BinaryPlusExpression extends BinaryExpression { - cachedLiteralKind: SyntaxKind; +export function createParenthesizerRules(factory: NodeFactory): ParenthesizerRules { + interface BinaryPlusExpression extends BinaryExpression { + cachedLiteralKind: SyntaxKind; + } + + let binaryLeftOperandParenthesizerCache: ESMap Expression> | undefined; + let binaryRightOperandParenthesizerCache: ESMap Expression> | undefined; + + return { + getParenthesizeLeftSideOfBinaryForOperator, + getParenthesizeRightSideOfBinaryForOperator, + parenthesizeLeftSideOfBinary, + parenthesizeRightSideOfBinary, + parenthesizeExpressionOfComputedPropertyName, + parenthesizeConditionOfConditionalExpression, + parenthesizeBranchOfConditionalExpression, + parenthesizeExpressionOfExportDefault, + parenthesizeExpressionOfNew, + parenthesizeLeftSideOfAccess, + parenthesizeOperandOfPostfixUnary, + parenthesizeOperandOfPrefixUnary, + parenthesizeExpressionsOfCommaDelimitedList, + parenthesizeExpressionForDisallowedComma, + parenthesizeExpressionOfExpressionStatement, + parenthesizeConciseBodyOfArrowFunction, + parenthesizeMemberOfConditionalType, + parenthesizeMemberOfElementType, + parenthesizeElementTypeOfArrayType, + parenthesizeConstituentTypesOfUnionOrIntersectionType, + parenthesizeTypeArguments, + }; + + function getParenthesizeLeftSideOfBinaryForOperator(operatorKind: BinaryOperator) { + binaryLeftOperandParenthesizerCache ||= new Map(); + let parenthesizerRule = binaryLeftOperandParenthesizerCache.get(operatorKind); + if (!parenthesizerRule) { + parenthesizerRule = node => parenthesizeLeftSideOfBinary(operatorKind, node); + binaryLeftOperandParenthesizerCache.set(operatorKind, parenthesizerRule); } + return parenthesizerRule; + } - let binaryLeftOperandParenthesizerCache: ESMap Expression> | undefined; - let binaryRightOperandParenthesizerCache: ESMap Expression> | undefined; - - return { - getParenthesizeLeftSideOfBinaryForOperator, - getParenthesizeRightSideOfBinaryForOperator, - parenthesizeLeftSideOfBinary, - parenthesizeRightSideOfBinary, - parenthesizeExpressionOfComputedPropertyName, - parenthesizeConditionOfConditionalExpression, - parenthesizeBranchOfConditionalExpression, - parenthesizeExpressionOfExportDefault, - parenthesizeExpressionOfNew, - parenthesizeLeftSideOfAccess, - parenthesizeOperandOfPostfixUnary, - parenthesizeOperandOfPrefixUnary, - parenthesizeExpressionsOfCommaDelimitedList, - parenthesizeExpressionForDisallowedComma, - parenthesizeExpressionOfExpressionStatement, - parenthesizeConciseBodyOfArrowFunction, - parenthesizeMemberOfConditionalType, - parenthesizeMemberOfElementType, - parenthesizeElementTypeOfArrayType, - parenthesizeConstituentTypesOfUnionOrIntersectionType, - parenthesizeTypeArguments, - }; - - function getParenthesizeLeftSideOfBinaryForOperator(operatorKind: BinaryOperator) { - binaryLeftOperandParenthesizerCache ||= new Map(); - let parenthesizerRule = binaryLeftOperandParenthesizerCache.get(operatorKind); - if (!parenthesizerRule) { - parenthesizerRule = node => parenthesizeLeftSideOfBinary(operatorKind, node); - binaryLeftOperandParenthesizerCache.set(operatorKind, parenthesizerRule); - } - return parenthesizerRule; + function getParenthesizeRightSideOfBinaryForOperator(operatorKind: BinaryOperator) { + binaryRightOperandParenthesizerCache ||= new Map(); + let parenthesizerRule = binaryRightOperandParenthesizerCache.get(operatorKind); + if (!parenthesizerRule) { + parenthesizerRule = node => parenthesizeRightSideOfBinary(operatorKind, /*leftSide*/ undefined, node); + binaryRightOperandParenthesizerCache.set(operatorKind, parenthesizerRule); } + return parenthesizerRule; + } - function getParenthesizeRightSideOfBinaryForOperator(operatorKind: BinaryOperator) { - binaryRightOperandParenthesizerCache ||= new Map(); - let parenthesizerRule = binaryRightOperandParenthesizerCache.get(operatorKind); - if (!parenthesizerRule) { - parenthesizerRule = node => parenthesizeRightSideOfBinary(operatorKind, /*leftSide*/ undefined, node); - binaryRightOperandParenthesizerCache.set(operatorKind, parenthesizerRule); - } - return parenthesizerRule; + /** + * Determines whether the operand to a BinaryExpression needs to be parenthesized. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ + function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand: Expression | undefined) { + // If the operand has lower precedence, then it needs to be parenthesized to preserve the + // intent of the expression. For example, if the operand is `a + b` and the operator is + // `*`, then we need to parenthesize the operand to preserve the intended order of + // operations: `(a + b) * x`. + // + // If the operand has higher precedence, then it does not need to be parenthesized. For + // example, if the operand is `a * b` and the operator is `+`, then we do not need to + // parenthesize to preserve the intended order of operations: `a * b + x`. + // + // If the operand has the same precedence, then we need to check the associativity of + // the operator based on whether this is the left or right operand of the expression. + // + // For example, if `a / d` is on the right of operator `*`, we need to parenthesize + // to preserve the intended order of operations: `x * (a / d)` + // + // If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve + // the intended order of operations: `(a ** b) ** c` + const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator); + const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator); + const emittedOperand = skipPartiallyEmittedExpressions(operand); + if (!isLeftSideOfBinary && operand.kind === SyntaxKind.ArrowFunction && binaryOperatorPrecedence > OperatorPrecedence.Assignment) { + // We need to parenthesize arrow functions on the right side to avoid it being + // parsed as parenthesized expression: `a && (() => {})` + return true; } + const operandPrecedence = getExpressionPrecedence(emittedOperand); + switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) { + case Comparison.LessThan: + // If the operand is the right side of a right-associative binary operation + // and is a yield expression, then we do not need parentheses. + if (!isLeftSideOfBinary + && binaryOperatorAssociativity === Associativity.Right + && operand.kind === SyntaxKind.YieldExpression) { + return false; + } - /** - * Determines whether the operand to a BinaryExpression needs to be parenthesized. - * - * @param binaryOperator The operator for the BinaryExpression. - * @param operand The operand for the BinaryExpression. - * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the - * BinaryExpression. - */ - function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand: Expression | undefined) { - // If the operand has lower precedence, then it needs to be parenthesized to preserve the - // intent of the expression. For example, if the operand is `a + b` and the operator is - // `*`, then we need to parenthesize the operand to preserve the intended order of - // operations: `(a + b) * x`. - // - // If the operand has higher precedence, then it does not need to be parenthesized. For - // example, if the operand is `a * b` and the operator is `+`, then we do not need to - // parenthesize to preserve the intended order of operations: `a * b + x`. - // - // If the operand has the same precedence, then we need to check the associativity of - // the operator based on whether this is the left or right operand of the expression. - // - // For example, if `a / d` is on the right of operator `*`, we need to parenthesize - // to preserve the intended order of operations: `x * (a / d)` - // - // If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve - // the intended order of operations: `(a ** b) ** c` - const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator); - const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator); - const emittedOperand = skipPartiallyEmittedExpressions(operand); - if (!isLeftSideOfBinary && operand.kind === SyntaxKind.ArrowFunction && binaryOperatorPrecedence > OperatorPrecedence.Assignment) { - // We need to parenthesize arrow functions on the right side to avoid it being - // parsed as parenthesized expression: `a && (() => {})` return true; - } - const operandPrecedence = getExpressionPrecedence(emittedOperand); - switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) { - case Comparison.LessThan: - // If the operand is the right side of a right-associative binary operation - // and is a yield expression, then we do not need parentheses. - if (!isLeftSideOfBinary - && binaryOperatorAssociativity === Associativity.Right - && operand.kind === SyntaxKind.YieldExpression) { - return false; - } - - return true; - case Comparison.GreaterThan: - return false; + case Comparison.GreaterThan: + return false; + + case Comparison.EqualTo: + if (isLeftSideOfBinary) { + // No need to parenthesize the left operand when the binary operator is + // left associative: + // (a*b)/x -> a*b/x + // (a**b)/x -> a**b/x + // + // Parentheses are needed for the left operand when the binary operator is + // right associative: + // (a/b)**x -> (a/b)**x + // (a**b)**x -> (a**b)**x + return binaryOperatorAssociativity === Associativity.Right; + } + else { + if (isBinaryExpression(emittedOperand) + && emittedOperand.operatorToken.kind === binaryOperator) { + // No need to parenthesize the right operand when the binary operator and + // operand are the same and one of the following: + // x*(a*b) => x*a*b + // x|(a|b) => x|a|b + // x&(a&b) => x&a&b + // x^(a^b) => x^a^b + if (operatorHasAssociativeProperty(binaryOperator)) { + return false; + } - case Comparison.EqualTo: - if (isLeftSideOfBinary) { - // No need to parenthesize the left operand when the binary operator is - // left associative: - // (a*b)/x -> a*b/x - // (a**b)/x -> a**b/x - // - // Parentheses are needed for the left operand when the binary operator is - // right associative: - // (a/b)**x -> (a/b)**x - // (a**b)**x -> (a**b)**x - return binaryOperatorAssociativity === Associativity.Right; - } - else { - if (isBinaryExpression(emittedOperand) - && emittedOperand.operatorToken.kind === binaryOperator) { - // No need to parenthesize the right operand when the binary operator and - // operand are the same and one of the following: - // x*(a*b) => x*a*b - // x|(a|b) => x|a|b - // x&(a&b) => x&a&b - // x^(a^b) => x^a^b - if (operatorHasAssociativeProperty(binaryOperator)) { + // No need to parenthesize the right operand when the binary operator + // is plus (+) if both the left and right operands consist solely of either + // literals of the same kind or binary plus (+) expressions for literals of + // the same kind (recursively). + // "a"+(1+2) => "a"+(1+2) + // "a"+("b"+"c") => "a"+"b"+"c" + if (binaryOperator === SyntaxKind.PlusToken) { + const leftKind = leftOperand ? getLiteralKindOfBinaryPlusOperand(leftOperand) : SyntaxKind.Unknown; + if (isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand(emittedOperand)) { return false; } - - // No need to parenthesize the right operand when the binary operator - // is plus (+) if both the left and right operands consist solely of either - // literals of the same kind or binary plus (+) expressions for literals of - // the same kind (recursively). - // "a"+(1+2) => "a"+(1+2) - // "a"+("b"+"c") => "a"+"b"+"c" - if (binaryOperator === SyntaxKind.PlusToken) { - const leftKind = leftOperand ? getLiteralKindOfBinaryPlusOperand(leftOperand) : SyntaxKind.Unknown; - if (isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand(emittedOperand)) { - return false; - } - } } - - // No need to parenthesize the right operand when the operand is right - // associative: - // x/(a**b) -> x/a**b - // x**(a**b) -> x**a**b - // - // Parentheses are needed for the right operand when the operand is left - // associative: - // x/(a*b) -> x/(a*b) - // x**(a/b) -> x**(a/b) - const operandAssociativity = getExpressionAssociativity(emittedOperand); - return operandAssociativity === Associativity.Left; } - } - } - /** - * Determines whether a binary operator is mathematically associative. - * - * @param binaryOperator The binary operator. - */ - function operatorHasAssociativeProperty(binaryOperator: SyntaxKind) { - // The following operators are associative in JavaScript: - // (a*b)*c -> a*(b*c) -> a*b*c - // (a|b)|c -> a|(b|c) -> a|b|c - // (a&b)&c -> a&(b&c) -> a&b&c - // (a^b)^c -> a^(b^c) -> a^b^c - // - // While addition is associative in mathematics, JavaScript's `+` is not - // guaranteed to be associative as it is overloaded with string concatenation. - return binaryOperator === SyntaxKind.AsteriskToken - || binaryOperator === SyntaxKind.BarToken - || binaryOperator === SyntaxKind.AmpersandToken - || binaryOperator === SyntaxKind.CaretToken; + // No need to parenthesize the right operand when the operand is right + // associative: + // x/(a**b) -> x/a**b + // x**(a**b) -> x**a**b + // + // Parentheses are needed for the right operand when the operand is left + // associative: + // x/(a*b) -> x/(a*b) + // x**(a/b) -> x**(a/b) + const operandAssociativity = getExpressionAssociativity(emittedOperand); + return operandAssociativity === Associativity.Left; + } } + } - /** - * This function determines whether an expression consists of a homogeneous set of - * literal expressions or binary plus expressions that all share the same literal kind. - * It is used to determine whether the right-hand operand of a binary plus expression can be - * emitted without parentheses. - */ - function getLiteralKindOfBinaryPlusOperand(node: Expression): SyntaxKind { - node = skipPartiallyEmittedExpressions(node); - - if (isLiteralKind(node.kind)) { - return node.kind; - } - - if (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken) { - if ((node as BinaryPlusExpression).cachedLiteralKind !== undefined) { - return (node as BinaryPlusExpression).cachedLiteralKind; - } + /** + * Determines whether a binary operator is mathematically associative. + * + * @param binaryOperator The binary operator. + */ + function operatorHasAssociativeProperty(binaryOperator: SyntaxKind) { + // The following operators are associative in JavaScript: + // (a*b)*c -> a*(b*c) -> a*b*c + // (a|b)|c -> a|(b|c) -> a|b|c + // (a&b)&c -> a&(b&c) -> a&b&c + // (a^b)^c -> a^(b^c) -> a^b^c + // + // While addition is associative in mathematics, JavaScript's `+` is not + // guaranteed to be associative as it is overloaded with string concatenation. + return binaryOperator === SyntaxKind.AsteriskToken + || binaryOperator === SyntaxKind.BarToken + || binaryOperator === SyntaxKind.AmpersandToken + || binaryOperator === SyntaxKind.CaretToken; + } - const leftKind = getLiteralKindOfBinaryPlusOperand((node as BinaryExpression).left); - const literalKind = isLiteralKind(leftKind) - && leftKind === getLiteralKindOfBinaryPlusOperand((node as BinaryExpression).right) - ? leftKind - : SyntaxKind.Unknown; + /** + * This function determines whether an expression consists of a homogeneous set of + * literal expressions or binary plus expressions that all share the same literal kind. + * It is used to determine whether the right-hand operand of a binary plus expression can be + * emitted without parentheses. + */ + function getLiteralKindOfBinaryPlusOperand(node: Expression): SyntaxKind { + node = skipPartiallyEmittedExpressions(node); + + if (isLiteralKind(node.kind)) { + return node.kind; + } - (node as BinaryPlusExpression).cachedLiteralKind = literalKind; - return literalKind; + if (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken) { + if ((node as BinaryPlusExpression).cachedLiteralKind !== undefined) { + return (node as BinaryPlusExpression).cachedLiteralKind; } - return SyntaxKind.Unknown; + const leftKind = getLiteralKindOfBinaryPlusOperand((node as BinaryExpression).left); + const literalKind = isLiteralKind(leftKind) + && leftKind === getLiteralKindOfBinaryPlusOperand((node as BinaryExpression).right) + ? leftKind + : SyntaxKind.Unknown; + + (node as BinaryPlusExpression).cachedLiteralKind = literalKind; + return literalKind; } - /** - * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended - * order of operations. - * - * @param binaryOperator The operator for the BinaryExpression. - * @param operand The operand for the BinaryExpression. - * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the - * BinaryExpression. - */ - function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand?: Expression) { - const skipped = skipPartiallyEmittedExpressions(operand); - - // If the resulting expression is already parenthesized, we do not need to do any further processing. - if (skipped.kind === SyntaxKind.ParenthesizedExpression) { - return operand; - } + return SyntaxKind.Unknown; + } - return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary, leftOperand) - ? factory.createParenthesizedExpression(operand) - : operand; + /** + * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended + * order of operations. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ + function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand?: Expression) { + const skipped = skipPartiallyEmittedExpressions(operand); + + // If the resulting expression is already parenthesized, we do not need to do any further processing. + if (skipped.kind === SyntaxKind.ParenthesizedExpression) { + return operand; } + return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary, leftOperand) + ? factory.createParenthesizedExpression(operand) + : operand; + } - function parenthesizeLeftSideOfBinary(binaryOperator: SyntaxKind, leftSide: Expression): Expression { - return parenthesizeBinaryOperand(binaryOperator, leftSide, /*isLeftSideOfBinary*/ true); - } - function parenthesizeRightSideOfBinary(binaryOperator: SyntaxKind, leftSide: Expression | undefined, rightSide: Expression): Expression { - return parenthesizeBinaryOperand(binaryOperator, rightSide, /*isLeftSideOfBinary*/ false, leftSide); - } + function parenthesizeLeftSideOfBinary(binaryOperator: SyntaxKind, leftSide: Expression): Expression { + return parenthesizeBinaryOperand(binaryOperator, leftSide, /*isLeftSideOfBinary*/ true); + } - function parenthesizeExpressionOfComputedPropertyName(expression: Expression): Expression { - return isCommaSequence(expression) ? factory.createParenthesizedExpression(expression) : expression; - } + function parenthesizeRightSideOfBinary(binaryOperator: SyntaxKind, leftSide: Expression | undefined, rightSide: Expression): Expression { + return parenthesizeBinaryOperand(binaryOperator, rightSide, /*isLeftSideOfBinary*/ false, leftSide); + } - function parenthesizeConditionOfConditionalExpression(condition: Expression): Expression { - const conditionalPrecedence = getOperatorPrecedence(SyntaxKind.ConditionalExpression, SyntaxKind.QuestionToken); - const emittedCondition = skipPartiallyEmittedExpressions(condition); - const conditionPrecedence = getExpressionPrecedence(emittedCondition); - if (compareValues(conditionPrecedence, conditionalPrecedence) !== Comparison.GreaterThan) { - return factory.createParenthesizedExpression(condition); - } - return condition; - } + function parenthesizeExpressionOfComputedPropertyName(expression: Expression): Expression { + return isCommaSequence(expression) ? factory.createParenthesizedExpression(expression) : expression; + } - function parenthesizeBranchOfConditionalExpression(branch: Expression): Expression { - // per ES grammar both 'whenTrue' and 'whenFalse' parts of conditional expression are assignment expressions - // so in case when comma expression is introduced as a part of previous transformations - // if should be wrapped in parens since comma operator has the lowest precedence - const emittedExpression = skipPartiallyEmittedExpressions(branch); - return isCommaSequence(emittedExpression) - ? factory.createParenthesizedExpression(branch) - : branch; + function parenthesizeConditionOfConditionalExpression(condition: Expression): Expression { + const conditionalPrecedence = getOperatorPrecedence(SyntaxKind.ConditionalExpression, SyntaxKind.QuestionToken); + const emittedCondition = skipPartiallyEmittedExpressions(condition); + const conditionPrecedence = getExpressionPrecedence(emittedCondition); + if (compareValues(conditionPrecedence, conditionalPrecedence) !== Comparison.GreaterThan) { + return factory.createParenthesizedExpression(condition); } + return condition; + } - /** - * [Per the spec](https://tc39.github.io/ecma262/#prod-ExportDeclaration), `export default` accepts _AssigmentExpression_ but - * has a lookahead restriction for `function`, `async function`, and `class`. - * - * Basically, that means we need to parenthesize in the following cases: - * - * - BinaryExpression of CommaToken - * - CommaList (synthetic list of multiple comma expressions) - * - FunctionExpression - * - ClassExpression - */ - function parenthesizeExpressionOfExportDefault(expression: Expression): Expression { - const check = skipPartiallyEmittedExpressions(expression); - let needsParens = isCommaSequence(check); - if (!needsParens) { - switch (getLeftmostExpression(check, /*stopAtCallExpression*/ false).kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - needsParens = true; - } - } - return needsParens ? factory.createParenthesizedExpression(expression) : expression; - } + function parenthesizeBranchOfConditionalExpression(branch: Expression): Expression { + // per ES grammar both 'whenTrue' and 'whenFalse' parts of conditional expression are assignment expressions + // so in case when comma expression is introduced as a part of previous transformations + // if should be wrapped in parens since comma operator has the lowest precedence + const emittedExpression = skipPartiallyEmittedExpressions(branch); + return isCommaSequence(emittedExpression) + ? factory.createParenthesizedExpression(branch) + : branch; + } - /** - * Wraps an expression in parentheses if it is needed in order to use the expression - * as the expression of a `NewExpression` node. - */ - function parenthesizeExpressionOfNew(expression: Expression): LeftHandSideExpression { - const leftmostExpr = getLeftmostExpression(expression, /*stopAtCallExpressions*/ true); - switch (leftmostExpr.kind) { - case SyntaxKind.CallExpression: - return factory.createParenthesizedExpression(expression); - - case SyntaxKind.NewExpression: - return !(leftmostExpr as NewExpression).arguments - ? factory.createParenthesizedExpression(expression) - : expression as LeftHandSideExpression; // TODO(rbuckton): Verify this assertion holds + /** + * [Per the spec](https://tc39.github.io/ecma262/#prod-ExportDeclaration), `export default` accepts _AssigmentExpression_ but + * has a lookahead restriction for `function`, `async function`, and `class`. + * + * Basically, that means we need to parenthesize in the following cases: + * + * - BinaryExpression of CommaToken + * - CommaList (synthetic list of multiple comma expressions) + * - FunctionExpression + * - ClassExpression + */ + function parenthesizeExpressionOfExportDefault(expression: Expression): Expression { + const check = skipPartiallyEmittedExpressions(expression); + let needsParens = isCommaSequence(check); + if (!needsParens) { + switch (getLeftmostExpression(check, /*stopAtCallExpression*/ false).kind) { + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + needsParens = true; } + } + return needsParens ? factory.createParenthesizedExpression(expression) : expression; + } - return parenthesizeLeftSideOfAccess(expression); + /** + * Wraps an expression in parentheses if it is needed in order to use the expression + * as the expression of a `NewExpression` node. + */ + function parenthesizeExpressionOfNew(expression: Expression): LeftHandSideExpression { + const leftmostExpr = getLeftmostExpression(expression, /*stopAtCallExpressions*/ true); + switch (leftmostExpr.kind) { + case SyntaxKind.CallExpression: + return factory.createParenthesizedExpression(expression); + + case SyntaxKind.NewExpression: + return !(leftmostExpr as NewExpression).arguments + ? factory.createParenthesizedExpression(expression) + : expression as LeftHandSideExpression; // TODO(rbuckton): Verify this assertion holds } - /** - * Wraps an expression in parentheses if it is needed in order to use the expression for - * property or element access. - */ - function parenthesizeLeftSideOfAccess(expression: Expression): LeftHandSideExpression { - // isLeftHandSideExpression is almost the correct criterion for when it is not necessary - // to parenthesize the expression before a dot. The known exception is: - // - // NewExpression: - // new C.x -> not the same as (new C).x - // - const emittedExpression = skipPartiallyEmittedExpressions(expression); - if (isLeftHandSideExpression(emittedExpression) - && (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression as NewExpression).arguments)) { - // TODO(rbuckton): Verify whether this assertion holds. - return expression as LeftHandSideExpression; - } + return parenthesizeLeftSideOfAccess(expression); + } - // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - return setTextRange(factory.createParenthesizedExpression(expression), expression); + /** + * Wraps an expression in parentheses if it is needed in order to use the expression for + * property or element access. + */ + function parenthesizeLeftSideOfAccess(expression: Expression): LeftHandSideExpression { + // isLeftHandSideExpression is almost the correct criterion for when it is not necessary + // to parenthesize the expression before a dot. The known exception is: + // + // NewExpression: + // new C.x -> not the same as (new C).x + // + const emittedExpression = skipPartiallyEmittedExpressions(expression); + if (isLeftHandSideExpression(emittedExpression) + && (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression as NewExpression).arguments)) { + // TODO(rbuckton): Verify whether this assertion holds. + return expression as LeftHandSideExpression; } - function parenthesizeOperandOfPostfixUnary(operand: Expression): LeftHandSideExpression { - // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - return isLeftHandSideExpression(operand) ? operand : setTextRange(factory.createParenthesizedExpression(operand), operand); - } + // TODO(rbuckton): Verifiy whether `setTextRange` is needed. + return setTextRange(factory.createParenthesizedExpression(expression), expression); + } - function parenthesizeOperandOfPrefixUnary(operand: Expression): UnaryExpression { - // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - return isUnaryExpression(operand) ? operand : setTextRange(factory.createParenthesizedExpression(operand), operand); - } + function parenthesizeOperandOfPostfixUnary(operand: Expression): LeftHandSideExpression { + // TODO(rbuckton): Verifiy whether `setTextRange` is needed. + return isLeftHandSideExpression(operand) ? operand : setTextRange(factory.createParenthesizedExpression(operand), operand); + } - function parenthesizeExpressionsOfCommaDelimitedList(elements: NodeArray): NodeArray { - const result = sameMap(elements, parenthesizeExpressionForDisallowedComma); - return setTextRange(factory.createNodeArray(result, elements.hasTrailingComma), elements); - } + function parenthesizeOperandOfPrefixUnary(operand: Expression): UnaryExpression { + // TODO(rbuckton): Verifiy whether `setTextRange` is needed. + return isUnaryExpression(operand) ? operand : setTextRange(factory.createParenthesizedExpression(operand), operand); + } - function parenthesizeExpressionForDisallowedComma(expression: Expression): Expression { - const emittedExpression = skipPartiallyEmittedExpressions(expression); - const expressionPrecedence = getExpressionPrecedence(emittedExpression); - const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); - // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - return expressionPrecedence > commaPrecedence ? expression : setTextRange(factory.createParenthesizedExpression(expression), expression); - } + function parenthesizeExpressionsOfCommaDelimitedList(elements: NodeArray): NodeArray { + const result = sameMap(elements, parenthesizeExpressionForDisallowedComma); + return setTextRange(factory.createNodeArray(result, elements.hasTrailingComma), elements); + } - function parenthesizeExpressionOfExpressionStatement(expression: Expression): Expression { - const emittedExpression = skipPartiallyEmittedExpressions(expression); - if (isCallExpression(emittedExpression)) { - const callee = emittedExpression.expression; - const kind = skipPartiallyEmittedExpressions(callee).kind; - if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) { - // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - const updated = factory.updateCallExpression( - emittedExpression, - setTextRange(factory.createParenthesizedExpression(callee), callee), - emittedExpression.typeArguments, - emittedExpression.arguments - ); - return factory.restoreOuterExpressions(expression, updated, OuterExpressionKinds.PartiallyEmittedExpressions); - } - } + function parenthesizeExpressionForDisallowedComma(expression: Expression): Expression { + const emittedExpression = skipPartiallyEmittedExpressions(expression); + const expressionPrecedence = getExpressionPrecedence(emittedExpression); + const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); + // TODO(rbuckton): Verifiy whether `setTextRange` is needed. + return expressionPrecedence > commaPrecedence ? expression : setTextRange(factory.createParenthesizedExpression(expression), expression); + } - const leftmostExpressionKind = getLeftmostExpression(emittedExpression, /*stopAtCallExpressions*/ false).kind; - if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) { + function parenthesizeExpressionOfExpressionStatement(expression: Expression): Expression { + const emittedExpression = skipPartiallyEmittedExpressions(expression); + if (isCallExpression(emittedExpression)) { + const callee = emittedExpression.expression; + const kind = skipPartiallyEmittedExpressions(callee).kind; + if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) { // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - return setTextRange(factory.createParenthesizedExpression(expression), expression); + const updated = factory.updateCallExpression( + emittedExpression, + setTextRange(factory.createParenthesizedExpression(callee), callee), + emittedExpression.typeArguments, + emittedExpression.arguments + ); + return factory.restoreOuterExpressions(expression, updated, OuterExpressionKinds.PartiallyEmittedExpressions); } + } - return expression; + const leftmostExpressionKind = getLeftmostExpression(emittedExpression, /*stopAtCallExpressions*/ false).kind; + if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) { + // TODO(rbuckton): Verifiy whether `setTextRange` is needed. + return setTextRange(factory.createParenthesizedExpression(expression), expression); } - function parenthesizeConciseBodyOfArrowFunction(body: Expression): Expression; - function parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody; - function parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody { - if (!isBlock(body) && (isCommaSequence(body) || getLeftmostExpression(body, /*stopAtCallExpressions*/ false).kind === SyntaxKind.ObjectLiteralExpression)) { - // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - return setTextRange(factory.createParenthesizedExpression(body), body); - } + return expression; + } - return body; + function parenthesizeConciseBodyOfArrowFunction(body: Expression): Expression; + function parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody; + function parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody { + if (!isBlock(body) && (isCommaSequence(body) || getLeftmostExpression(body, /*stopAtCallExpressions*/ false).kind === SyntaxKind.ObjectLiteralExpression)) { + // TODO(rbuckton): Verifiy whether `setTextRange` is needed. + return setTextRange(factory.createParenthesizedExpression(body), body); } - function parenthesizeMemberOfConditionalType(member: TypeNode): TypeNode { - return member.kind === SyntaxKind.ConditionalType ? factory.createParenthesizedType(member) : member; - } + return body; + } - function parenthesizeMemberOfElementType(member: TypeNode): TypeNode { - switch (member.kind) { - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - return factory.createParenthesizedType(member); - } - return parenthesizeMemberOfConditionalType(member); + function parenthesizeMemberOfConditionalType(member: TypeNode): TypeNode { + return member.kind === SyntaxKind.ConditionalType ? factory.createParenthesizedType(member) : member; + } + + function parenthesizeMemberOfElementType(member: TypeNode): TypeNode { + switch (member.kind) { + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + return factory.createParenthesizedType(member); } + return parenthesizeMemberOfConditionalType(member); + } - function parenthesizeElementTypeOfArrayType(member: TypeNode): TypeNode { - switch (member.kind) { - case SyntaxKind.TypeQuery: - case SyntaxKind.TypeOperator: - case SyntaxKind.InferType: - return factory.createParenthesizedType(member); - } - return parenthesizeMemberOfElementType(member); + function parenthesizeElementTypeOfArrayType(member: TypeNode): TypeNode { + switch (member.kind) { + case SyntaxKind.TypeQuery: + case SyntaxKind.TypeOperator: + case SyntaxKind.InferType: + return factory.createParenthesizedType(member); } + return parenthesizeMemberOfElementType(member); + } - function parenthesizeConstituentTypesOfUnionOrIntersectionType(members: readonly TypeNode[]): NodeArray { - return factory.createNodeArray(sameMap(members, parenthesizeMemberOfElementType)); + function parenthesizeConstituentTypesOfUnionOrIntersectionType(members: readonly TypeNode[]): NodeArray { + return factory.createNodeArray(sameMap(members, parenthesizeMemberOfElementType)); - } + } - function parenthesizeOrdinalTypeArgument(node: TypeNode, i: number) { - return i === 0 && isFunctionOrConstructorTypeNode(node) && node.typeParameters ? factory.createParenthesizedType(node) : node; - } + function parenthesizeOrdinalTypeArgument(node: TypeNode, i: number) { + return i === 0 && isFunctionOrConstructorTypeNode(node) && node.typeParameters ? factory.createParenthesizedType(node) : node; + } - function parenthesizeTypeArguments(typeArguments: NodeArray | undefined): NodeArray | undefined { - if (some(typeArguments)) { - return factory.createNodeArray(sameMap(typeArguments, parenthesizeOrdinalTypeArgument)); - } + function parenthesizeTypeArguments(typeArguments: NodeArray | undefined): NodeArray | undefined { + if (some(typeArguments)) { + return factory.createNodeArray(sameMap(typeArguments, parenthesizeOrdinalTypeArgument)); } } +} - export const nullParenthesizerRules: ParenthesizerRules = { - getParenthesizeLeftSideOfBinaryForOperator: _ => identity, - getParenthesizeRightSideOfBinaryForOperator: _ => identity, - parenthesizeLeftSideOfBinary: (_binaryOperator, leftSide) => leftSide, - parenthesizeRightSideOfBinary: (_binaryOperator, _leftSide, rightSide) => rightSide, - parenthesizeExpressionOfComputedPropertyName: identity, - parenthesizeConditionOfConditionalExpression: identity, - parenthesizeBranchOfConditionalExpression: identity, - parenthesizeExpressionOfExportDefault: identity, - parenthesizeExpressionOfNew: expression => cast(expression, isLeftHandSideExpression), - parenthesizeLeftSideOfAccess: expression => cast(expression, isLeftHandSideExpression), - parenthesizeOperandOfPostfixUnary: operand => cast(operand, isLeftHandSideExpression), - parenthesizeOperandOfPrefixUnary: operand => cast(operand, isUnaryExpression), - parenthesizeExpressionsOfCommaDelimitedList: nodes => cast(nodes, isNodeArray), - parenthesizeExpressionForDisallowedComma: identity, - parenthesizeExpressionOfExpressionStatement: identity, - parenthesizeConciseBodyOfArrowFunction: identity, - parenthesizeMemberOfConditionalType: identity, - parenthesizeMemberOfElementType: identity, - parenthesizeElementTypeOfArrayType: identity, - parenthesizeConstituentTypesOfUnionOrIntersectionType: nodes => cast(nodes, isNodeArray), - parenthesizeTypeArguments: nodes => nodes && cast(nodes, isNodeArray), - }; +export const nullParenthesizerRules: ParenthesizerRules = { + getParenthesizeLeftSideOfBinaryForOperator: _ => identity, + getParenthesizeRightSideOfBinaryForOperator: _ => identity, + parenthesizeLeftSideOfBinary: (_binaryOperator, leftSide) => leftSide, + parenthesizeRightSideOfBinary: (_binaryOperator, _leftSide, rightSide) => rightSide, + parenthesizeExpressionOfComputedPropertyName: identity, + parenthesizeConditionOfConditionalExpression: identity, + parenthesizeBranchOfConditionalExpression: identity, + parenthesizeExpressionOfExportDefault: identity, + parenthesizeExpressionOfNew: expression => cast(expression, isLeftHandSideExpression), + parenthesizeLeftSideOfAccess: expression => cast(expression, isLeftHandSideExpression), + parenthesizeOperandOfPostfixUnary: operand => cast(operand, isLeftHandSideExpression), + parenthesizeOperandOfPrefixUnary: operand => cast(operand, isUnaryExpression), + parenthesizeExpressionsOfCommaDelimitedList: nodes => cast(nodes, isNodeArray), + parenthesizeExpressionForDisallowedComma: identity, + parenthesizeExpressionOfExpressionStatement: identity, + parenthesizeConciseBodyOfArrowFunction: identity, + parenthesizeMemberOfConditionalType: identity, + parenthesizeMemberOfElementType: identity, + parenthesizeElementTypeOfArrayType: identity, + parenthesizeConstituentTypesOfUnionOrIntersectionType: nodes => cast(nodes, isNodeArray), + parenthesizeTypeArguments: nodes => nodes && cast(nodes, isNodeArray), +}; } diff --git a/src/compiler/factory/utilities.ts b/src/compiler/factory/utilities.ts index 7daa31c522732..78081d0071a93 100644 --- a/src/compiler/factory/utilities.ts +++ b/src/compiler/factory/utilities.ts @@ -1,1218 +1,1218 @@ /* @internal */ namespace ts { - // Compound nodes +// Compound nodes - export function createEmptyExports(factory: NodeFactory) { - return factory.createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([]), /*moduleSpecifier*/ undefined); - } +export function createEmptyExports(factory: NodeFactory) { + return factory.createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([]), /*moduleSpecifier*/ undefined); +} - export function createMemberAccessForPropertyName(factory: NodeFactory, target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression { - if (isComputedPropertyName(memberName)) { - return setTextRange(factory.createElementAccessExpression(target, memberName.expression), location); - } - else { - const expression = setTextRange( - isMemberName(memberName) - ? factory.createPropertyAccessExpression(target, memberName) - : factory.createElementAccessExpression(target, memberName), - memberName - ); - getOrCreateEmitNode(expression).flags |= EmitFlags.NoNestedSourceMaps; - return expression; - } +export function createMemberAccessForPropertyName(factory: NodeFactory, target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression { + if (isComputedPropertyName(memberName)) { + return setTextRange(factory.createElementAccessExpression(target, memberName.expression), location); } - - function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) { - // To ensure the emit resolver can properly resolve the namespace, we need to - // treat this identifier as if it were a source tree node by clearing the `Synthesized` - // flag and setting a parent node. - const react = parseNodeFactory.createIdentifier(reactNamespace || "React"); - // Set the parent that is in parse tree - // this makes sure that parent chain is intact for checker to traverse complete scope tree - setParent(react, getParseTreeNode(parent)); - return react; + else { + const expression = setTextRange( + isMemberName(memberName) + ? factory.createPropertyAccessExpression(target, memberName) + : factory.createElementAccessExpression(target, memberName), + memberName + ); + getOrCreateEmitNode(expression).flags |= EmitFlags.NoNestedSourceMaps; + return expression; } +} - function createJsxFactoryExpressionFromEntityName(factory: NodeFactory, jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { - if (isQualifiedName(jsxFactory)) { - const left = createJsxFactoryExpressionFromEntityName(factory, jsxFactory.left, parent); - const right = factory.createIdentifier(idText(jsxFactory.right)) as Mutable; - right.escapedText = jsxFactory.right.escapedText; - return factory.createPropertyAccessExpression(left, right); - } - else { - return createReactNamespace(idText(jsxFactory), parent); - } - } +function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) { + // To ensure the emit resolver can properly resolve the namespace, we need to + // treat this identifier as if it were a source tree node by clearing the `Synthesized` + // flag and setting a parent node. + const react = parseNodeFactory.createIdentifier(reactNamespace || "React"); + // Set the parent that is in parse tree + // this makes sure that parent chain is intact for checker to traverse complete scope tree + setParent(react, getParseTreeNode(parent)); + return react; +} - export function createJsxFactoryExpression(factory: NodeFactory, jsxFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { - return jsxFactoryEntity ? - createJsxFactoryExpressionFromEntityName(factory, jsxFactoryEntity, parent) : - factory.createPropertyAccessExpression( - createReactNamespace(reactNamespace, parent), - "createElement" - ); +function createJsxFactoryExpressionFromEntityName(factory: NodeFactory, jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { + if (isQualifiedName(jsxFactory)) { + const left = createJsxFactoryExpressionFromEntityName(factory, jsxFactory.left, parent); + const right = factory.createIdentifier(idText(jsxFactory.right)) as Mutable; + right.escapedText = jsxFactory.right.escapedText; + return factory.createPropertyAccessExpression(left, right); + } + else { + return createReactNamespace(idText(jsxFactory), parent); } +} - function createJsxFragmentFactoryExpression(factory: NodeFactory, jsxFragmentFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { - return jsxFragmentFactoryEntity ? - createJsxFactoryExpressionFromEntityName(factory, jsxFragmentFactoryEntity, parent) : - factory.createPropertyAccessExpression( - createReactNamespace(reactNamespace, parent), - "Fragment" - ); +export function createJsxFactoryExpression(factory: NodeFactory, jsxFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { + return jsxFactoryEntity ? + createJsxFactoryExpressionFromEntityName(factory, jsxFactoryEntity, parent) : + factory.createPropertyAccessExpression( + createReactNamespace(reactNamespace, parent), + "createElement" + ); +} + +function createJsxFragmentFactoryExpression(factory: NodeFactory, jsxFragmentFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { + return jsxFragmentFactoryEntity ? + createJsxFactoryExpressionFromEntityName(factory, jsxFragmentFactoryEntity, parent) : + factory.createPropertyAccessExpression( + createReactNamespace(reactNamespace, parent), + "Fragment" + ); +} + +export function createExpressionForJsxElement(factory: NodeFactory, callee: Expression, tagName: Expression, props: Expression | undefined, children: readonly Expression[] | undefined, location: TextRange): LeftHandSideExpression { + const argumentsList = [tagName]; + if (props) { + argumentsList.push(props); } - export function createExpressionForJsxElement(factory: NodeFactory, callee: Expression, tagName: Expression, props: Expression | undefined, children: readonly Expression[] | undefined, location: TextRange): LeftHandSideExpression { - const argumentsList = [tagName]; - if (props) { - argumentsList.push(props); + if (children && children.length > 0) { + if (!props) { + argumentsList.push(factory.createNull()); } - if (children && children.length > 0) { - if (!props) { - argumentsList.push(factory.createNull()); - } - - if (children.length > 1) { - for (const child of children) { - startOnNewLine(child); - argumentsList.push(child); - } - } - else { - argumentsList.push(children[0]); + if (children.length > 1) { + for (const child of children) { + startOnNewLine(child); + argumentsList.push(child); } } - - return setTextRange( - factory.createCallExpression( - callee, - /*typeArguments*/ undefined, - argumentsList - ), - location - ); + else { + argumentsList.push(children[0]); + } } - export function createExpressionForJsxFragment(factory: NodeFactory, jsxFactoryEntity: EntityName | undefined, jsxFragmentFactoryEntity: EntityName | undefined, reactNamespace: string, children: readonly Expression[], parentElement: JsxOpeningFragment, location: TextRange): LeftHandSideExpression { - const tagName = createJsxFragmentFactoryExpression(factory, jsxFragmentFactoryEntity, reactNamespace, parentElement); - const argumentsList = [tagName, factory.createNull()]; + return setTextRange( + factory.createCallExpression( + callee, + /*typeArguments*/ undefined, + argumentsList + ), + location + ); +} - if (children && children.length > 0) { - if (children.length > 1) { - for (const child of children) { - startOnNewLine(child); - argumentsList.push(child); - } - } - else { - argumentsList.push(children[0]); +export function createExpressionForJsxFragment(factory: NodeFactory, jsxFactoryEntity: EntityName | undefined, jsxFragmentFactoryEntity: EntityName | undefined, reactNamespace: string, children: readonly Expression[], parentElement: JsxOpeningFragment, location: TextRange): LeftHandSideExpression { + const tagName = createJsxFragmentFactoryExpression(factory, jsxFragmentFactoryEntity, reactNamespace, parentElement); + const argumentsList = [tagName, factory.createNull()]; + + if (children && children.length > 0) { + if (children.length > 1) { + for (const child of children) { + startOnNewLine(child); + argumentsList.push(child); } } + else { + argumentsList.push(children[0]); + } + } + + return setTextRange( + factory.createCallExpression( + createJsxFactoryExpression(factory, jsxFactoryEntity, reactNamespace, parentElement), + /*typeArguments*/ undefined, + argumentsList + ), + location + ); +} +// Utilities + +export function createForOfBindingStatement(factory: NodeFactory, node: ForInitializer, boundValue: Expression): Statement { + if (isVariableDeclarationList(node)) { + const firstDeclaration = first(node.declarations); + const updatedDeclaration = factory.updateVariableDeclaration( + firstDeclaration, + firstDeclaration.name, + /*exclamationToken*/ undefined, + /*type*/ undefined, + boundValue + ); return setTextRange( - factory.createCallExpression( - createJsxFactoryExpression(factory, jsxFactoryEntity, reactNamespace, parentElement), - /*typeArguments*/ undefined, - argumentsList + factory.createVariableStatement( + /*modifiers*/ undefined, + factory.updateVariableDeclarationList(node, [updatedDeclaration]) ), - location + /*location*/ node ); } - - // Utilities - - export function createForOfBindingStatement(factory: NodeFactory, node: ForInitializer, boundValue: Expression): Statement { - if (isVariableDeclarationList(node)) { - const firstDeclaration = first(node.declarations); - const updatedDeclaration = factory.updateVariableDeclaration( - firstDeclaration, - firstDeclaration.name, - /*exclamationToken*/ undefined, - /*type*/ undefined, - boundValue - ); - return setTextRange( - factory.createVariableStatement( - /*modifiers*/ undefined, - factory.updateVariableDeclarationList(node, [updatedDeclaration]) - ), - /*location*/ node - ); - } - else { - const updatedExpression = setTextRange(factory.createAssignment(node, boundValue), /*location*/ node); - return setTextRange(factory.createExpressionStatement(updatedExpression), /*location*/ node); - } + else { + const updatedExpression = setTextRange(factory.createAssignment(node, boundValue), /*location*/ node); + return setTextRange(factory.createExpressionStatement(updatedExpression), /*location*/ node); } +} - export function insertLeadingStatement(factory: NodeFactory, dest: Statement, source: Statement) { - if (isBlock(dest)) { - return factory.updateBlock(dest, setTextRange(factory.createNodeArray([source, ...dest.statements]), dest.statements)); - } - else { - return factory.createBlock(factory.createNodeArray([dest, source]), /*multiLine*/ true); - } +export function insertLeadingStatement(factory: NodeFactory, dest: Statement, source: Statement) { + if (isBlock(dest)) { + return factory.updateBlock(dest, setTextRange(factory.createNodeArray([source, ...dest.statements]), dest.statements)); } - - export function createExpressionFromEntityName(factory: NodeFactory, node: EntityName | Expression): Expression { - if (isQualifiedName(node)) { - const left = createExpressionFromEntityName(factory, node.left); - // TODO(rbuckton): Does this need to be parented? - const right = setParent(setTextRange(factory.cloneNode(node.right), node.right), node.right.parent); - return setTextRange(factory.createPropertyAccessExpression(left, right), node); - } - else { - // TODO(rbuckton): Does this need to be parented? - return setParent(setTextRange(factory.cloneNode(node), node), node.parent); - } + else { + return factory.createBlock(factory.createNodeArray([dest, source]), /*multiLine*/ true); } +} - export function createExpressionForPropertyName(factory: NodeFactory, memberName: Exclude): Expression { - if (isIdentifier(memberName)) { - return factory.createStringLiteralFromNode(memberName); - } - else if (isComputedPropertyName(memberName)) { - // TODO(rbuckton): Does this need to be parented? - return setParent(setTextRange(factory.cloneNode(memberName.expression), memberName.expression), memberName.expression.parent); - } - else { - // TODO(rbuckton): Does this need to be parented? - return setParent(setTextRange(factory.cloneNode(memberName), memberName), memberName.parent); - } +export function createExpressionFromEntityName(factory: NodeFactory, node: EntityName | Expression): Expression { + if (isQualifiedName(node)) { + const left = createExpressionFromEntityName(factory, node.left); + // TODO(rbuckton): Does this need to be parented? + const right = setParent(setTextRange(factory.cloneNode(node.right), node.right), node.right.parent); + return setTextRange(factory.createPropertyAccessExpression(left, right), node); } - - function createExpressionForAccessorDeclaration(factory: NodeFactory, properties: NodeArray, property: AccessorDeclaration & { readonly name: Exclude; }, receiver: Expression, multiLine: boolean) { - const { firstAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(properties, property); - if (property === firstAccessor) { - return setTextRange( - factory.createObjectDefinePropertyCall( - receiver, - createExpressionForPropertyName(factory, property.name), - factory.createPropertyDescriptor({ - enumerable: factory.createFalse(), - configurable: true, - get: getAccessor && setTextRange( - setOriginalNode( - factory.createFunctionExpression( - getAccessor.modifiers, - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - getAccessor.parameters, - /*type*/ undefined, - getAccessor.body! // TODO: GH#18217 - ), - getAccessor - ), - getAccessor - ), - set: setAccessor && setTextRange( - setOriginalNode( - factory.createFunctionExpression( - setAccessor.modifiers, - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - setAccessor.parameters, - /*type*/ undefined, - setAccessor.body! // TODO: GH#18217 - ), - setAccessor - ), - setAccessor - ) - }, !multiLine) - ), - firstAccessor - ); - } - - return undefined; + else { + // TODO(rbuckton): Does this need to be parented? + return setParent(setTextRange(factory.cloneNode(node), node), node.parent); } +} - function createExpressionForPropertyAssignment(factory: NodeFactory, property: PropertyAssignment, receiver: Expression) { - return setOriginalNode( - setTextRange( - factory.createAssignment( - createMemberAccessForPropertyName(factory, receiver, property.name, /*location*/ property.name), - property.initializer - ), - property - ), - property - ); +export function createExpressionForPropertyName(factory: NodeFactory, memberName: Exclude): Expression { + if (isIdentifier(memberName)) { + return factory.createStringLiteralFromNode(memberName); } - - function createExpressionForShorthandPropertyAssignment(factory: NodeFactory, property: ShorthandPropertyAssignment, receiver: Expression) { - return setOriginalNode( - setTextRange( - factory.createAssignment( - createMemberAccessForPropertyName(factory, receiver, property.name, /*location*/ property.name), - factory.cloneNode(property.name) - ), - /*location*/ property - ), - /*original*/ property - ); + else if (isComputedPropertyName(memberName)) { + // TODO(rbuckton): Does this need to be parented? + return setParent(setTextRange(factory.cloneNode(memberName.expression), memberName.expression), memberName.expression.parent); + } + else { + // TODO(rbuckton): Does this need to be parented? + return setParent(setTextRange(factory.cloneNode(memberName), memberName), memberName.parent); } +} - function createExpressionForMethodDeclaration(factory: NodeFactory, method: MethodDeclaration, receiver: Expression) { - return setOriginalNode( - setTextRange( - factory.createAssignment( - createMemberAccessForPropertyName(factory, receiver, method.name, /*location*/ method.name), - setOriginalNode( - setTextRange( +function createExpressionForAccessorDeclaration(factory: NodeFactory, properties: NodeArray, property: AccessorDeclaration & { readonly name: Exclude; }, receiver: Expression, multiLine: boolean) { + const { firstAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(properties, property); + if (property === firstAccessor) { + return setTextRange( + factory.createObjectDefinePropertyCall( + receiver, + createExpressionForPropertyName(factory, property.name), + factory.createPropertyDescriptor({ + enumerable: factory.createFalse(), + configurable: true, + get: getAccessor && setTextRange( + setOriginalNode( factory.createFunctionExpression( - method.modifiers, - method.asteriskToken, + getAccessor.modifiers, + /*asteriskToken*/ undefined, /*name*/ undefined, /*typeParameters*/ undefined, - method.parameters, + getAccessor.parameters, /*type*/ undefined, - method.body! // TODO: GH#18217 + getAccessor.body! // TODO: GH#18217 ), - /*location*/ method + getAccessor ), - /*original*/ method + getAccessor + ), + set: setAccessor && setTextRange( + setOriginalNode( + factory.createFunctionExpression( + setAccessor.modifiers, + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + setAccessor.parameters, + /*type*/ undefined, + setAccessor.body! // TODO: GH#18217 + ), + setAccessor + ), + setAccessor ) - ), - /*location*/ method + }, !multiLine) ), - /*original*/ method + firstAccessor ); } - export function createExpressionForObjectLiteralElementLike(factory: NodeFactory, node: ObjectLiteralExpression, property: ObjectLiteralElementLike, receiver: Expression): Expression | undefined { - if (property.name && isPrivateIdentifier(property.name)) { - Debug.failBadSyntaxKind(property.name, "Private identifiers are not allowed in object literals."); - } - switch (property.kind) { - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return createExpressionForAccessorDeclaration(factory, node.properties, property as typeof property & { readonly name: Exclude }, receiver, !!node.multiLine); - case SyntaxKind.PropertyAssignment: - return createExpressionForPropertyAssignment(factory, property, receiver); - case SyntaxKind.ShorthandPropertyAssignment: - return createExpressionForShorthandPropertyAssignment(factory, property, receiver); - case SyntaxKind.MethodDeclaration: - return createExpressionForMethodDeclaration(factory, property, receiver); - } - } + return undefined; +} - /** - * Expand the read and increment/decrement operations a pre- or post-increment or pre- or post-decrement expression. - * - * ```ts - * // input - * ++ - * // output (if result is not discarded) - * var ; - * ( = , = ++, ) - * // output (if result is discarded) - * var ; - * ( = , ++, ) - * - * // input - * ++ - * // output (if result is not discarded) - * var ; - * ( = , = ++) - * // output (if result is discarded) - * var ; - * ( = , ++) - * ``` - * - * It is up to the caller to supply a temporary variable for `` if one is needed. - * The temporary variable `` is injected so that `++` and `--` work uniformly with `number` and `bigint`. - * The result of the expression is always the final result of incrementing or decrementing the expression, so that it can be used for storage. - * - * @param factory {@link NodeFactory} used to create the expanded representation. - * @param node The original prefix or postfix unary node. - * @param expression The expression to use as the value to increment or decrement - * @param resultVariable A temporary variable in which to store the result. Pass `undefined` if the result is discarded, or if the value of `` is the expected result. - */ - export function expandPreOrPostfixIncrementOrDecrementExpression(factory: NodeFactory, node: PrefixUnaryExpression | PostfixUnaryExpression, expression: Expression, recordTempVariable: (node: Identifier) => void, resultVariable: Identifier | undefined) { - const operator = node.operator; - Debug.assert(operator === SyntaxKind.PlusPlusToken || operator === SyntaxKind.MinusMinusToken, "Expected 'node' to be a pre- or post-increment or pre- or post-decrement expression"); +function createExpressionForPropertyAssignment(factory: NodeFactory, property: PropertyAssignment, receiver: Expression) { + return setOriginalNode( + setTextRange( + factory.createAssignment( + createMemberAccessForPropertyName(factory, receiver, property.name, /*location*/ property.name), + property.initializer + ), + property + ), + property + ); +} - const temp = factory.createTempVariable(recordTempVariable); - expression = factory.createAssignment(temp, expression); - setTextRange(expression, node.operand); +function createExpressionForShorthandPropertyAssignment(factory: NodeFactory, property: ShorthandPropertyAssignment, receiver: Expression) { + return setOriginalNode( + setTextRange( + factory.createAssignment( + createMemberAccessForPropertyName(factory, receiver, property.name, /*location*/ property.name), + factory.cloneNode(property.name) + ), + /*location*/ property + ), + /*original*/ property + ); +} - let operation: Expression = isPrefixUnaryExpression(node) ? - factory.createPrefixUnaryExpression(operator, temp) : - factory.createPostfixUnaryExpression(temp, operator); - setTextRange(operation, node); +function createExpressionForMethodDeclaration(factory: NodeFactory, method: MethodDeclaration, receiver: Expression) { + return setOriginalNode( + setTextRange( + factory.createAssignment( + createMemberAccessForPropertyName(factory, receiver, method.name, /*location*/ method.name), + setOriginalNode( + setTextRange( + factory.createFunctionExpression( + method.modifiers, + method.asteriskToken, + /*name*/ undefined, + /*typeParameters*/ undefined, + method.parameters, + /*type*/ undefined, + method.body! // TODO: GH#18217 + ), + /*location*/ method + ), + /*original*/ method + ) + ), + /*location*/ method + ), + /*original*/ method + ); +} - if (resultVariable) { - operation = factory.createAssignment(resultVariable, operation); - setTextRange(operation, node); - } +export function createExpressionForObjectLiteralElementLike(factory: NodeFactory, node: ObjectLiteralExpression, property: ObjectLiteralElementLike, receiver: Expression): Expression | undefined { + if (property.name && isPrivateIdentifier(property.name)) { + Debug.failBadSyntaxKind(property.name, "Private identifiers are not allowed in object literals."); + } + switch (property.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return createExpressionForAccessorDeclaration(factory, node.properties, property as typeof property & { readonly name: Exclude }, receiver, !!node.multiLine); + case SyntaxKind.PropertyAssignment: + return createExpressionForPropertyAssignment(factory, property, receiver); + case SyntaxKind.ShorthandPropertyAssignment: + return createExpressionForShorthandPropertyAssignment(factory, property, receiver); + case SyntaxKind.MethodDeclaration: + return createExpressionForMethodDeclaration(factory, property, receiver); + } +} - expression = factory.createComma(expression, operation); - setTextRange(expression, node); +/** + * Expand the read and increment/decrement operations a pre- or post-increment or pre- or post-decrement expression. + * + * ```ts + * // input + * ++ + * // output (if result is not discarded) + * var ; + * ( = , = ++, ) + * // output (if result is discarded) + * var ; + * ( = , ++, ) + * + * // input + * ++ + * // output (if result is not discarded) + * var ; + * ( = , = ++) + * // output (if result is discarded) + * var ; + * ( = , ++) + * ``` + * + * It is up to the caller to supply a temporary variable for `` if one is needed. + * The temporary variable `` is injected so that `++` and `--` work uniformly with `number` and `bigint`. + * The result of the expression is always the final result of incrementing or decrementing the expression, so that it can be used for storage. + * + * @param factory {@link NodeFactory} used to create the expanded representation. + * @param node The original prefix or postfix unary node. + * @param expression The expression to use as the value to increment or decrement + * @param resultVariable A temporary variable in which to store the result. Pass `undefined` if the result is discarded, or if the value of `` is the expected result. + */ +export function expandPreOrPostfixIncrementOrDecrementExpression(factory: NodeFactory, node: PrefixUnaryExpression | PostfixUnaryExpression, expression: Expression, recordTempVariable: (node: Identifier) => void, resultVariable: Identifier | undefined) { + const operator = node.operator; + Debug.assert(operator === SyntaxKind.PlusPlusToken || operator === SyntaxKind.MinusMinusToken, "Expected 'node' to be a pre- or post-increment or pre- or post-decrement expression"); + + const temp = factory.createTempVariable(recordTempVariable); + expression = factory.createAssignment(temp, expression); + setTextRange(expression, node.operand); + + let operation: Expression = isPrefixUnaryExpression(node) ? + factory.createPrefixUnaryExpression(operator, temp) : + factory.createPostfixUnaryExpression(temp, operator); + setTextRange(operation, node); + + if (resultVariable) { + operation = factory.createAssignment(resultVariable, operation); + setTextRange(operation, node); + } - if (isPostfixUnaryExpression(node)) { - expression = factory.createComma(expression, temp); - setTextRange(expression, node); - } + expression = factory.createComma(expression, operation); + setTextRange(expression, node); - return expression; + if (isPostfixUnaryExpression(node)) { + expression = factory.createComma(expression, temp); + setTextRange(expression, node); } - /** - * Gets whether an identifier should only be referred to by its internal name. - */ - export function isInternalName(node: Identifier) { - return (getEmitFlags(node) & EmitFlags.InternalName) !== 0; - } + return expression; +} - /** - * Gets whether an identifier should only be referred to by its local name. - */ - export function isLocalName(node: Identifier) { - return (getEmitFlags(node) & EmitFlags.LocalName) !== 0; - } +/** + * Gets whether an identifier should only be referred to by its internal name. + */ +export function isInternalName(node: Identifier) { + return (getEmitFlags(node) & EmitFlags.InternalName) !== 0; +} - /** - * Gets whether an identifier should only be referred to by its export representation if the - * name points to an exported symbol. - */ - export function isExportName(node: Identifier) { - return (getEmitFlags(node) & EmitFlags.ExportName) !== 0; - } +/** + * Gets whether an identifier should only be referred to by its local name. + */ +export function isLocalName(node: Identifier) { + return (getEmitFlags(node) & EmitFlags.LocalName) !== 0; +} - function isUseStrictPrologue(node: ExpressionStatement): boolean { - return isStringLiteral(node.expression) && node.expression.text === "use strict"; - } +/** + * Gets whether an identifier should only be referred to by its export representation if the + * name points to an exported symbol. + */ +export function isExportName(node: Identifier) { + return (getEmitFlags(node) & EmitFlags.ExportName) !== 0; +} - export function findUseStrictPrologue(statements: readonly Statement[]): Statement | undefined { - for (const statement of statements) { - if (isPrologueDirective(statement)) { - if (isUseStrictPrologue(statement)) { - return statement; - } - } - else { - break; +function isUseStrictPrologue(node: ExpressionStatement): boolean { + return isStringLiteral(node.expression) && node.expression.text === "use strict"; +} + +export function findUseStrictPrologue(statements: readonly Statement[]): Statement | undefined { + for (const statement of statements) { + if (isPrologueDirective(statement)) { + if (isUseStrictPrologue(statement)) { + return statement; } } - return undefined; + else { + break; + } } + return undefined; +} - export function startsWithUseStrict(statements: readonly Statement[]) { - const firstStatement = firstOrUndefined(statements); - return firstStatement !== undefined - && isPrologueDirective(firstStatement) - && isUseStrictPrologue(firstStatement); - } +export function startsWithUseStrict(statements: readonly Statement[]) { + const firstStatement = firstOrUndefined(statements); + return firstStatement !== undefined + && isPrologueDirective(firstStatement) + && isUseStrictPrologue(firstStatement); +} - export function isCommaSequence(node: Expression): node is BinaryExpression & {operatorToken: Token} | CommaListExpression { - return node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken || - node.kind === SyntaxKind.CommaListExpression; - } +export function isCommaSequence(node: Expression): node is BinaryExpression & {operatorToken: Token} | CommaListExpression { + return node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken || + node.kind === SyntaxKind.CommaListExpression; +} - export function isJSDocTypeAssertion(node: Node): node is JSDocTypeAssertion { - return isParenthesizedExpression(node) - && isInJSFile(node) - && !!getJSDocTypeTag(node); - } +export function isJSDocTypeAssertion(node: Node): node is JSDocTypeAssertion { + return isParenthesizedExpression(node) + && isInJSFile(node) + && !!getJSDocTypeTag(node); +} - export function getJSDocTypeAssertionType(node: JSDocTypeAssertion) { - const type = getJSDocType(node); - Debug.assertIsDefined(type); - return type; - } +export function getJSDocTypeAssertionType(node: JSDocTypeAssertion) { + const type = getJSDocType(node); + Debug.assertIsDefined(type); + return type; +} - export function isOuterExpression(node: Node, kinds = OuterExpressionKinds.All): node is OuterExpression { - switch (node.kind) { - case SyntaxKind.ParenthesizedExpression: - if (kinds & OuterExpressionKinds.ExcludeJSDocTypeAssertion && isJSDocTypeAssertion(node)) { - return false; - } - return (kinds & OuterExpressionKinds.Parentheses) !== 0; - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - return (kinds & OuterExpressionKinds.TypeAssertions) !== 0; - case SyntaxKind.NonNullExpression: - return (kinds & OuterExpressionKinds.NonNullAssertions) !== 0; - case SyntaxKind.PartiallyEmittedExpression: - return (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) !== 0; - } - return false; - } +export function isOuterExpression(node: Node, kinds = OuterExpressionKinds.All): node is OuterExpression { + switch (node.kind) { + case SyntaxKind.ParenthesizedExpression: + if (kinds & OuterExpressionKinds.ExcludeJSDocTypeAssertion && isJSDocTypeAssertion(node)) { + return false; + } + return (kinds & OuterExpressionKinds.Parentheses) !== 0; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return (kinds & OuterExpressionKinds.TypeAssertions) !== 0; + case SyntaxKind.NonNullExpression: + return (kinds & OuterExpressionKinds.NonNullAssertions) !== 0; + case SyntaxKind.PartiallyEmittedExpression: + return (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) !== 0; + } + return false; +} - export function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression; - export function skipOuterExpressions(node: Node, kinds?: OuterExpressionKinds): Node; - export function skipOuterExpressions(node: Node, kinds = OuterExpressionKinds.All) { - while (isOuterExpression(node, kinds)) { - node = node.expression; - } - return node; +export function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression; +export function skipOuterExpressions(node: Node, kinds?: OuterExpressionKinds): Node; +export function skipOuterExpressions(node: Node, kinds = OuterExpressionKinds.All) { + while (isOuterExpression(node, kinds)) { + node = node.expression; } + return node; +} - export function skipAssertions(node: Expression): Expression; - export function skipAssertions(node: Node): Node; - export function skipAssertions(node: Node): Node { - return skipOuterExpressions(node, OuterExpressionKinds.Assertions); - } +export function skipAssertions(node: Expression): Expression; +export function skipAssertions(node: Node): Node; +export function skipAssertions(node: Node): Node { + return skipOuterExpressions(node, OuterExpressionKinds.Assertions); +} - export function startOnNewLine(node: T): T { - return setStartsOnNewLine(node, /*newLine*/ true); - } +export function startOnNewLine(node: T): T { + return setStartsOnNewLine(node, /*newLine*/ true); +} - export function getExternalHelpersModuleName(node: SourceFile) { - const parseNode = getOriginalNode(node, isSourceFile); - const emitNode = parseNode && parseNode.emitNode; - return emitNode && emitNode.externalHelpersModuleName; - } +export function getExternalHelpersModuleName(node: SourceFile) { + const parseNode = getOriginalNode(node, isSourceFile); + const emitNode = parseNode && parseNode.emitNode; + return emitNode && emitNode.externalHelpersModuleName; +} - export function hasRecordedExternalHelpers(sourceFile: SourceFile) { - const parseNode = getOriginalNode(sourceFile, isSourceFile); - const emitNode = parseNode && parseNode.emitNode; - return !!emitNode && (!!emitNode.externalHelpersModuleName || !!emitNode.externalHelpers); - } +export function hasRecordedExternalHelpers(sourceFile: SourceFile) { + const parseNode = getOriginalNode(sourceFile, isSourceFile); + const emitNode = parseNode && parseNode.emitNode; + return !!emitNode && (!!emitNode.externalHelpersModuleName || !!emitNode.externalHelpers); +} - export function createExternalHelpersImportDeclarationIfNeeded(nodeFactory: NodeFactory, helperFactory: EmitHelperFactory, sourceFile: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStar?: boolean, hasImportDefault?: boolean) { - if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) { - let namedBindings: NamedImportBindings | undefined; - const moduleKind = getEmitModuleKind(compilerOptions); - if ((moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) || sourceFile.impliedNodeFormat === ModuleKind.ESNext) { - // use named imports - const helpers = getEmitHelpers(sourceFile); - if (helpers) { - const helperNames: string[] = []; - for (const helper of helpers) { - if (!helper.scoped) { - const importName = helper.importName; - if (importName) { - pushIfUnique(helperNames, importName); - } +export function createExternalHelpersImportDeclarationIfNeeded(nodeFactory: NodeFactory, helperFactory: EmitHelperFactory, sourceFile: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStar?: boolean, hasImportDefault?: boolean) { + if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) { + let namedBindings: NamedImportBindings | undefined; + const moduleKind = getEmitModuleKind(compilerOptions); + if ((moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) || sourceFile.impliedNodeFormat === ModuleKind.ESNext) { + // use named imports + const helpers = getEmitHelpers(sourceFile); + if (helpers) { + const helperNames: string[] = []; + for (const helper of helpers) { + if (!helper.scoped) { + const importName = helper.importName; + if (importName) { + pushIfUnique(helperNames, importName); } } - if (some(helperNames)) { - helperNames.sort(compareStringsCaseSensitive); - // Alias the imports if the names are used somewhere in the file. - // NOTE: We don't need to care about global import collisions as this is a module. - namedBindings = nodeFactory.createNamedImports( - map(helperNames, name => isFileLevelUniqueName(sourceFile, name) - ? nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, nodeFactory.createIdentifier(name)) - : nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, nodeFactory.createIdentifier(name), helperFactory.getUnscopedHelperName(name)) - ) - ); - const parseNode = getOriginalNode(sourceFile, isSourceFile); - const emitNode = getOrCreateEmitNode(parseNode); - emitNode.externalHelpers = true; - } } - } - else { - // use a namespace import - const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(nodeFactory, sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar || hasImportDefault); - if (externalHelpersModuleName) { - namedBindings = nodeFactory.createNamespaceImport(externalHelpersModuleName); + if (some(helperNames)) { + helperNames.sort(compareStringsCaseSensitive); + // Alias the imports if the names are used somewhere in the file. + // NOTE: We don't need to care about global import collisions as this is a module. + namedBindings = nodeFactory.createNamedImports( + map(helperNames, name => isFileLevelUniqueName(sourceFile, name) + ? nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, nodeFactory.createIdentifier(name)) + : nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, nodeFactory.createIdentifier(name), helperFactory.getUnscopedHelperName(name)) + ) + ); + const parseNode = getOriginalNode(sourceFile, isSourceFile); + const emitNode = getOrCreateEmitNode(parseNode); + emitNode.externalHelpers = true; } } - if (namedBindings) { - const externalHelpersImportDeclaration = nodeFactory.createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - nodeFactory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, namedBindings), - nodeFactory.createStringLiteral(externalHelpersModuleNameText), - /*assertClause*/ undefined - ); - addEmitFlags(externalHelpersImportDeclaration, EmitFlags.NeverApplyImportHelper); - return externalHelpersImportDeclaration; + } + else { + // use a namespace import + const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(nodeFactory, sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar || hasImportDefault); + if (externalHelpersModuleName) { + namedBindings = nodeFactory.createNamespaceImport(externalHelpersModuleName); } } + if (namedBindings) { + const externalHelpersImportDeclaration = nodeFactory.createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + nodeFactory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, namedBindings), + nodeFactory.createStringLiteral(externalHelpersModuleNameText), + /*assertClause*/ undefined + ); + addEmitFlags(externalHelpersImportDeclaration, EmitFlags.NeverApplyImportHelper); + return externalHelpersImportDeclaration; + } } +} - export function getOrCreateExternalHelpersModuleNameIfNeeded(factory: NodeFactory, node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) { - if (compilerOptions.importHelpers && isEffectiveExternalModule(node, compilerOptions)) { - const externalHelpersModuleName = getExternalHelpersModuleName(node); - if (externalHelpersModuleName) { - return externalHelpersModuleName; - } +export function getOrCreateExternalHelpersModuleNameIfNeeded(factory: NodeFactory, node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) { + if (compilerOptions.importHelpers && isEffectiveExternalModule(node, compilerOptions)) { + const externalHelpersModuleName = getExternalHelpersModuleName(node); + if (externalHelpersModuleName) { + return externalHelpersModuleName; + } - const moduleKind = getEmitModuleKind(compilerOptions); - let create = (hasExportStarsToExportValues || (getESModuleInterop(compilerOptions) && hasImportStarOrImportDefault)) - && moduleKind !== ModuleKind.System - && (moduleKind < ModuleKind.ES2015 || node.impliedNodeFormat === ModuleKind.CommonJS); - if (!create) { - const helpers = getEmitHelpers(node); - if (helpers) { - for (const helper of helpers) { - if (!helper.scoped) { - create = true; - break; - } + const moduleKind = getEmitModuleKind(compilerOptions); + let create = (hasExportStarsToExportValues || (getESModuleInterop(compilerOptions) && hasImportStarOrImportDefault)) + && moduleKind !== ModuleKind.System + && (moduleKind < ModuleKind.ES2015 || node.impliedNodeFormat === ModuleKind.CommonJS); + if (!create) { + const helpers = getEmitHelpers(node); + if (helpers) { + for (const helper of helpers) { + if (!helper.scoped) { + create = true; + break; } } } - - if (create) { - const parseNode = getOriginalNode(node, isSourceFile); - const emitNode = getOrCreateEmitNode(parseNode); - return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = factory.createUniqueName(externalHelpersModuleNameText)); - } } - } - /** - * Get the name of that target module from an import or export declaration - */ - export function getLocalNameForExternalImport(factory: NodeFactory, node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile): Identifier | undefined { - const namespaceDeclaration = getNamespaceDeclarationNode(node); - if (namespaceDeclaration && !isDefaultImport(node) && !isExportNamespaceAsDefaultDeclaration(node)) { - const name = namespaceDeclaration.name; - return isGeneratedIdentifier(name) ? name : factory.createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name)); - } - if (node.kind === SyntaxKind.ImportDeclaration && node.importClause) { - return factory.getGeneratedNameForNode(node); - } - if (node.kind === SyntaxKind.ExportDeclaration && node.moduleSpecifier) { - return factory.getGeneratedNameForNode(node); + if (create) { + const parseNode = getOriginalNode(node, isSourceFile); + const emitNode = getOrCreateEmitNode(parseNode); + return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = factory.createUniqueName(externalHelpersModuleNameText)); } - return undefined; } +} - /** - * Get the name of a target module from an import/export declaration as should be written in the emitted output. - * The emitted output name can be different from the input if: - * 1. The module has a /// - * 2. --out or --outFile is used, making the name relative to the rootDir - * 3- The containing SourceFile has an entry in renamedDependencies for the import as requested by some module loaders (e.g. System). - * Otherwise, a new StringLiteral node representing the module name will be returned. - */ - export function getExternalModuleNameLiteral(factory: NodeFactory, importNode: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration | ImportCall, sourceFile: SourceFile, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) { - const moduleName = getExternalModuleName(importNode); - if (moduleName && isStringLiteral(moduleName)) { - return tryGetModuleNameFromDeclaration(importNode, host, factory, resolver, compilerOptions) - || tryRenameExternalModule(factory, moduleName, sourceFile) - || factory.cloneNode(moduleName); - } - - return undefined; +/** + * Get the name of that target module from an import or export declaration + */ +export function getLocalNameForExternalImport(factory: NodeFactory, node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile): Identifier | undefined { + const namespaceDeclaration = getNamespaceDeclarationNode(node); + if (namespaceDeclaration && !isDefaultImport(node) && !isExportNamespaceAsDefaultDeclaration(node)) { + const name = namespaceDeclaration.name; + return isGeneratedIdentifier(name) ? name : factory.createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name)); } - - /** - * Some bundlers (SystemJS builder) sometimes want to rename dependencies. - * Here we check if alternative name was provided for a given moduleName and return it if possible. - */ - function tryRenameExternalModule(factory: NodeFactory, moduleName: LiteralExpression, sourceFile: SourceFile) { - const rename = sourceFile.renamedDependencies && sourceFile.renamedDependencies.get(moduleName.text); - return rename ? factory.createStringLiteral(rename) : undefined; + if (node.kind === SyntaxKind.ImportDeclaration && node.importClause) { + return factory.getGeneratedNameForNode(node); + } + if (node.kind === SyntaxKind.ExportDeclaration && node.moduleSpecifier) { + return factory.getGeneratedNameForNode(node); } + return undefined; +} - /** - * Get the name of a module as should be written in the emitted output. - * The emitted output name can be different from the input if: - * 1. The module has a /// - * 2. --out or --outFile is used, making the name relative to the rootDir - * Otherwise, a new StringLiteral node representing the module name will be returned. - */ - export function tryGetModuleNameFromFile(factory: NodeFactory, file: SourceFile | undefined, host: EmitHost, options: CompilerOptions): StringLiteral | undefined { - if (!file) { - return undefined; - } - if (file.moduleName) { - return factory.createStringLiteral(file.moduleName); - } - if (!file.isDeclarationFile && outFile(options)) { - return factory.createStringLiteral(getExternalModuleNameFromPath(host, file.fileName)); - } +/** + * Get the name of a target module from an import/export declaration as should be written in the emitted output. + * The emitted output name can be different from the input if: + * 1. The module has a /// + * 2. --out or --outFile is used, making the name relative to the rootDir + * 3- The containing SourceFile has an entry in renamedDependencies for the import as requested by some module loaders (e.g. System). + * Otherwise, a new StringLiteral node representing the module name will be returned. + */ +export function getExternalModuleNameLiteral(factory: NodeFactory, importNode: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration | ImportCall, sourceFile: SourceFile, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) { + const moduleName = getExternalModuleName(importNode); + if (moduleName && isStringLiteral(moduleName)) { + return tryGetModuleNameFromDeclaration(importNode, host, factory, resolver, compilerOptions) + || tryRenameExternalModule(factory, moduleName, sourceFile) + || factory.cloneNode(moduleName); + } + + return undefined; +} + +/** + * Some bundlers (SystemJS builder) sometimes want to rename dependencies. + * Here we check if alternative name was provided for a given moduleName and return it if possible. + */ +function tryRenameExternalModule(factory: NodeFactory, moduleName: LiteralExpression, sourceFile: SourceFile) { + const rename = sourceFile.renamedDependencies && sourceFile.renamedDependencies.get(moduleName.text); + return rename ? factory.createStringLiteral(rename) : undefined; +} + +/** + * Get the name of a module as should be written in the emitted output. + * The emitted output name can be different from the input if: + * 1. The module has a /// + * 2. --out or --outFile is used, making the name relative to the rootDir + * Otherwise, a new StringLiteral node representing the module name will be returned. + */ +export function tryGetModuleNameFromFile(factory: NodeFactory, file: SourceFile | undefined, host: EmitHost, options: CompilerOptions): StringLiteral | undefined { + if (!file) { return undefined; } - - function tryGetModuleNameFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ImportCall, host: EmitHost, factory: NodeFactory, resolver: EmitResolver, compilerOptions: CompilerOptions) { - return tryGetModuleNameFromFile(factory, resolver.getExternalModuleFileFromDeclaration(declaration), host, compilerOptions); + if (file.moduleName) { + return factory.createStringLiteral(file.moduleName); + } + if (!file.isDeclarationFile && outFile(options)) { + return factory.createStringLiteral(getExternalModuleNameFromPath(host, file.fileName)); } + return undefined; +} - /** - * Gets the initializer of an BindingOrAssignmentElement. - */ - export function getInitializerOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Expression | undefined { - if (isDeclarationBindingElement(bindingElement)) { - // `1` in `let { a = 1 } = ...` - // `1` in `let { a: b = 1 } = ...` - // `1` in `let { a: {b} = 1 } = ...` - // `1` in `let { a: [b] = 1 } = ...` - // `1` in `let [a = 1] = ...` - // `1` in `let [{a} = 1] = ...` - // `1` in `let [[a] = 1] = ...` - return bindingElement.initializer; - } +function tryGetModuleNameFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ImportCall, host: EmitHost, factory: NodeFactory, resolver: EmitResolver, compilerOptions: CompilerOptions) { + return tryGetModuleNameFromFile(factory, resolver.getExternalModuleFileFromDeclaration(declaration), host, compilerOptions); +} - if (isPropertyAssignment(bindingElement)) { - // `1` in `({ a: b = 1 } = ...)` - // `1` in `({ a: {b} = 1 } = ...)` - // `1` in `({ a: [b] = 1 } = ...)` - const initializer = bindingElement.initializer; - return isAssignmentExpression(initializer, /*excludeCompoundAssignment*/ true) - ? initializer.right - : undefined; - } +/** + * Gets the initializer of an BindingOrAssignmentElement. + */ +export function getInitializerOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Expression | undefined { + if (isDeclarationBindingElement(bindingElement)) { + // `1` in `let { a = 1 } = ...` + // `1` in `let { a: b = 1 } = ...` + // `1` in `let { a: {b} = 1 } = ...` + // `1` in `let { a: [b] = 1 } = ...` + // `1` in `let [a = 1] = ...` + // `1` in `let [{a} = 1] = ...` + // `1` in `let [[a] = 1] = ...` + return bindingElement.initializer; + } - if (isShorthandPropertyAssignment(bindingElement)) { - // `1` in `({ a = 1 } = ...)` - return bindingElement.objectAssignmentInitializer; - } + if (isPropertyAssignment(bindingElement)) { + // `1` in `({ a: b = 1 } = ...)` + // `1` in `({ a: {b} = 1 } = ...)` + // `1` in `({ a: [b] = 1 } = ...)` + const initializer = bindingElement.initializer; + return isAssignmentExpression(initializer, /*excludeCompoundAssignment*/ true) + ? initializer.right + : undefined; + } - if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { - // `1` in `[a = 1] = ...` - // `1` in `[{a} = 1] = ...` - // `1` in `[[a] = 1] = ...` - return bindingElement.right; - } + if (isShorthandPropertyAssignment(bindingElement)) { + // `1` in `({ a = 1 } = ...)` + return bindingElement.objectAssignmentInitializer; + } - if (isSpreadElement(bindingElement)) { - // Recovery consistent with existing emit. - return getInitializerOfBindingOrAssignmentElement(bindingElement.expression as BindingOrAssignmentElement); - } + if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { + // `1` in `[a = 1] = ...` + // `1` in `[{a} = 1] = ...` + // `1` in `[[a] = 1] = ...` + return bindingElement.right; } - /** - * Gets the name of an BindingOrAssignmentElement. - */ - export function getTargetOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementTarget | undefined { - if (isDeclarationBindingElement(bindingElement)) { - // `a` in `let { a } = ...` - // `a` in `let { a = 1 } = ...` - // `b` in `let { a: b } = ...` - // `b` in `let { a: b = 1 } = ...` - // `a` in `let { ...a } = ...` - // `{b}` in `let { a: {b} } = ...` - // `{b}` in `let { a: {b} = 1 } = ...` - // `[b]` in `let { a: [b] } = ...` - // `[b]` in `let { a: [b] = 1 } = ...` - // `a` in `let [a] = ...` - // `a` in `let [a = 1] = ...` - // `a` in `let [...a] = ...` - // `{a}` in `let [{a}] = ...` - // `{a}` in `let [{a} = 1] = ...` - // `[a]` in `let [[a]] = ...` - // `[a]` in `let [[a] = 1] = ...` - return bindingElement.name; - } + if (isSpreadElement(bindingElement)) { + // Recovery consistent with existing emit. + return getInitializerOfBindingOrAssignmentElement(bindingElement.expression as BindingOrAssignmentElement); + } +} - if (isObjectLiteralElementLike(bindingElement)) { - switch (bindingElement.kind) { - case SyntaxKind.PropertyAssignment: - // `b` in `({ a: b } = ...)` - // `b` in `({ a: b = 1 } = ...)` - // `{b}` in `({ a: {b} } = ...)` - // `{b}` in `({ a: {b} = 1 } = ...)` - // `[b]` in `({ a: [b] } = ...)` - // `[b]` in `({ a: [b] = 1 } = ...)` - // `b.c` in `({ a: b.c } = ...)` - // `b.c` in `({ a: b.c = 1 } = ...)` - // `b[0]` in `({ a: b[0] } = ...)` - // `b[0]` in `({ a: b[0] = 1 } = ...)` - return getTargetOfBindingOrAssignmentElement(bindingElement.initializer as BindingOrAssignmentElement); - - case SyntaxKind.ShorthandPropertyAssignment: - // `a` in `({ a } = ...)` - // `a` in `({ a = 1 } = ...)` - return bindingElement.name; - - case SyntaxKind.SpreadAssignment: - // `a` in `({ ...a } = ...)` - return getTargetOfBindingOrAssignmentElement(bindingElement.expression as BindingOrAssignmentElement); - } +/** + * Gets the name of an BindingOrAssignmentElement. + */ +export function getTargetOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementTarget | undefined { + if (isDeclarationBindingElement(bindingElement)) { + // `a` in `let { a } = ...` + // `a` in `let { a = 1 } = ...` + // `b` in `let { a: b } = ...` + // `b` in `let { a: b = 1 } = ...` + // `a` in `let { ...a } = ...` + // `{b}` in `let { a: {b} } = ...` + // `{b}` in `let { a: {b} = 1 } = ...` + // `[b]` in `let { a: [b] } = ...` + // `[b]` in `let { a: [b] = 1 } = ...` + // `a` in `let [a] = ...` + // `a` in `let [a = 1] = ...` + // `a` in `let [...a] = ...` + // `{a}` in `let [{a}] = ...` + // `{a}` in `let [{a} = 1] = ...` + // `[a]` in `let [[a]] = ...` + // `[a]` in `let [[a] = 1] = ...` + return bindingElement.name; + } + + if (isObjectLiteralElementLike(bindingElement)) { + switch (bindingElement.kind) { + case SyntaxKind.PropertyAssignment: + // `b` in `({ a: b } = ...)` + // `b` in `({ a: b = 1 } = ...)` + // `{b}` in `({ a: {b} } = ...)` + // `{b}` in `({ a: {b} = 1 } = ...)` + // `[b]` in `({ a: [b] } = ...)` + // `[b]` in `({ a: [b] = 1 } = ...)` + // `b.c` in `({ a: b.c } = ...)` + // `b.c` in `({ a: b.c = 1 } = ...)` + // `b[0]` in `({ a: b[0] } = ...)` + // `b[0]` in `({ a: b[0] = 1 } = ...)` + return getTargetOfBindingOrAssignmentElement(bindingElement.initializer as BindingOrAssignmentElement); - // no target - return undefined; - } + case SyntaxKind.ShorthandPropertyAssignment: + // `a` in `({ a } = ...)` + // `a` in `({ a = 1 } = ...)` + return bindingElement.name; - if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { - // `a` in `[a = 1] = ...` - // `{a}` in `[{a} = 1] = ...` - // `[a]` in `[[a] = 1] = ...` - // `a.b` in `[a.b = 1] = ...` - // `a[0]` in `[a[0] = 1] = ...` - return getTargetOfBindingOrAssignmentElement(bindingElement.left as BindingOrAssignmentElement); + case SyntaxKind.SpreadAssignment: + // `a` in `({ ...a } = ...)` + return getTargetOfBindingOrAssignmentElement(bindingElement.expression as BindingOrAssignmentElement); } - if (isSpreadElement(bindingElement)) { - // `a` in `[...a] = ...` - return getTargetOfBindingOrAssignmentElement(bindingElement.expression as BindingOrAssignmentElement); - } + // no target + return undefined; + } - // `a` in `[a] = ...` - // `{a}` in `[{a}] = ...` - // `[a]` in `[[a]] = ...` - // `a.b` in `[a.b] = ...` - // `a[0]` in `[a[0]] = ...` - return bindingElement; + if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { + // `a` in `[a = 1] = ...` + // `{a}` in `[{a} = 1] = ...` + // `[a]` in `[[a] = 1] = ...` + // `a.b` in `[a.b = 1] = ...` + // `a[0]` in `[a[0] = 1] = ...` + return getTargetOfBindingOrAssignmentElement(bindingElement.left as BindingOrAssignmentElement); } - /** - * Determines whether an BindingOrAssignmentElement is a rest element. - */ - export function getRestIndicatorOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementRestIndicator | undefined { - switch (bindingElement.kind) { - case SyntaxKind.Parameter: - case SyntaxKind.BindingElement: - // `...` in `let [...a] = ...` - return bindingElement.dotDotDotToken; + if (isSpreadElement(bindingElement)) { + // `a` in `[...a] = ...` + return getTargetOfBindingOrAssignmentElement(bindingElement.expression as BindingOrAssignmentElement); + } - case SyntaxKind.SpreadElement: - case SyntaxKind.SpreadAssignment: - // `...` in `[...a] = ...` - return bindingElement; - } + // `a` in `[a] = ...` + // `{a}` in `[{a}] = ...` + // `[a]` in `[[a]] = ...` + // `a.b` in `[a.b] = ...` + // `a[0]` in `[a[0]] = ...` + return bindingElement; +} - return undefined; - } +/** + * Determines whether an BindingOrAssignmentElement is a rest element. + */ +export function getRestIndicatorOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementRestIndicator | undefined { + switch (bindingElement.kind) { + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + // `...` in `let [...a] = ...` + return bindingElement.dotDotDotToken; - /** - * Gets the property name of a BindingOrAssignmentElement - */ - export function getPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Exclude | undefined { - const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement); - Debug.assert(!!propertyName || isSpreadAssignment(bindingElement), "Invalid property name for binding element."); - return propertyName; + case SyntaxKind.SpreadElement: + case SyntaxKind.SpreadAssignment: + // `...` in `[...a] = ...` + return bindingElement; } - export function tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Exclude | undefined { - switch (bindingElement.kind) { - case SyntaxKind.BindingElement: - // `a` in `let { a: b } = ...` - // `[a]` in `let { [a]: b } = ...` - // `"a"` in `let { "a": b } = ...` - // `1` in `let { 1: b } = ...` - if (bindingElement.propertyName) { - const propertyName = bindingElement.propertyName; - if (isPrivateIdentifier(propertyName)) { - return Debug.failBadSyntaxKind(propertyName); - } - return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression) - ? propertyName.expression - : propertyName; - } + return undefined; +} - break; +/** + * Gets the property name of a BindingOrAssignmentElement + */ +export function getPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Exclude | undefined { + const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement); + Debug.assert(!!propertyName || isSpreadAssignment(bindingElement), "Invalid property name for binding element."); + return propertyName; +} - case SyntaxKind.PropertyAssignment: - // `a` in `({ a: b } = ...)` - // `[a]` in `({ [a]: b } = ...)` - // `"a"` in `({ "a": b } = ...)` - // `1` in `({ 1: b } = ...)` - if (bindingElement.name) { - const propertyName = bindingElement.name; - if (isPrivateIdentifier(propertyName)) { - return Debug.failBadSyntaxKind(propertyName); - } - return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression) - ? propertyName.expression - : propertyName; +export function tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Exclude | undefined { + switch (bindingElement.kind) { + case SyntaxKind.BindingElement: + // `a` in `let { a: b } = ...` + // `[a]` in `let { [a]: b } = ...` + // `"a"` in `let { "a": b } = ...` + // `1` in `let { 1: b } = ...` + if (bindingElement.propertyName) { + const propertyName = bindingElement.propertyName; + if (isPrivateIdentifier(propertyName)) { + return Debug.failBadSyntaxKind(propertyName); } + return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression) + ? propertyName.expression + : propertyName; + } - break; - - case SyntaxKind.SpreadAssignment: - // `a` in `({ ...a } = ...)` - if (bindingElement.name && isPrivateIdentifier(bindingElement.name)) { - return Debug.failBadSyntaxKind(bindingElement.name); + break; + + case SyntaxKind.PropertyAssignment: + // `a` in `({ a: b } = ...)` + // `[a]` in `({ [a]: b } = ...)` + // `"a"` in `({ "a": b } = ...)` + // `1` in `({ 1: b } = ...)` + if (bindingElement.name) { + const propertyName = bindingElement.name; + if (isPrivateIdentifier(propertyName)) { + return Debug.failBadSyntaxKind(propertyName); } - return bindingElement.name; - } + return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression) + ? propertyName.expression + : propertyName; + } - const target = getTargetOfBindingOrAssignmentElement(bindingElement); - if (target && isPropertyName(target)) { - return target; - } + break; + + case SyntaxKind.SpreadAssignment: + // `a` in `({ ...a } = ...)` + if (bindingElement.name && isPrivateIdentifier(bindingElement.name)) { + return Debug.failBadSyntaxKind(bindingElement.name); + } + return bindingElement.name; } - function isStringOrNumericLiteral(node: Node): node is StringLiteral | NumericLiteral { - const kind = node.kind; - return kind === SyntaxKind.StringLiteral - || kind === SyntaxKind.NumericLiteral; + const target = getTargetOfBindingOrAssignmentElement(bindingElement); + if (target && isPropertyName(target)) { + return target; } +} - /** - * Gets the elements of a BindingOrAssignmentPattern - */ - export function getElementsOfBindingOrAssignmentPattern(name: BindingOrAssignmentPattern): readonly BindingOrAssignmentElement[] { - switch (name.kind) { - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ArrayLiteralExpression: - // `a` in `{a}` - // `a` in `[a]` - return name.elements as readonly BindingOrAssignmentElement[]; - - case SyntaxKind.ObjectLiteralExpression: - // `a` in `{a}` - return name.properties as readonly BindingOrAssignmentElement[]; - } +function isStringOrNumericLiteral(node: Node): node is StringLiteral | NumericLiteral { + const kind = node.kind; + return kind === SyntaxKind.StringLiteral + || kind === SyntaxKind.NumericLiteral; +} + +/** + * Gets the elements of a BindingOrAssignmentPattern + */ +export function getElementsOfBindingOrAssignmentPattern(name: BindingOrAssignmentPattern): readonly BindingOrAssignmentElement[] { + switch (name.kind) { + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ArrayLiteralExpression: + // `a` in `{a}` + // `a` in `[a]` + return name.elements as readonly BindingOrAssignmentElement[]; + + case SyntaxKind.ObjectLiteralExpression: + // `a` in `{a}` + return name.properties as readonly BindingOrAssignmentElement[]; } +} - /* @internal */ - export function getJSDocTypeAliasName(fullName: JSDocNamespaceBody | undefined) { - if (fullName) { - let rightNode = fullName; - while (true) { - if (isIdentifier(rightNode) || !rightNode.body) { - return isIdentifier(rightNode) ? rightNode : rightNode.name; - } - rightNode = rightNode.body; +/* @internal */ +export function getJSDocTypeAliasName(fullName: JSDocNamespaceBody | undefined) { + if (fullName) { + let rightNode = fullName; + while (true) { + if (isIdentifier(rightNode) || !rightNode.body) { + return isIdentifier(rightNode) ? rightNode : rightNode.name; } + rightNode = rightNode.body; } } +} - export function canHaveModifiers(node: Node): node is HasModifiers { - const kind = node.kind; - return kind === SyntaxKind.Parameter - || kind === SyntaxKind.PropertySignature - || kind === SyntaxKind.PropertyDeclaration - || kind === SyntaxKind.MethodSignature - || kind === SyntaxKind.MethodDeclaration - || kind === SyntaxKind.Constructor - || kind === SyntaxKind.GetAccessor - || kind === SyntaxKind.SetAccessor - || kind === SyntaxKind.IndexSignature - || kind === SyntaxKind.FunctionExpression - || kind === SyntaxKind.ArrowFunction - || kind === SyntaxKind.ClassExpression - || kind === SyntaxKind.VariableStatement - || kind === SyntaxKind.FunctionDeclaration - || kind === SyntaxKind.ClassDeclaration - || kind === SyntaxKind.InterfaceDeclaration - || kind === SyntaxKind.TypeAliasDeclaration - || kind === SyntaxKind.EnumDeclaration - || kind === SyntaxKind.ModuleDeclaration - || kind === SyntaxKind.ImportEqualsDeclaration - || kind === SyntaxKind.ImportDeclaration - || kind === SyntaxKind.ExportAssignment - || kind === SyntaxKind.ExportDeclaration; - } +export function canHaveModifiers(node: Node): node is HasModifiers { + const kind = node.kind; + return kind === SyntaxKind.Parameter + || kind === SyntaxKind.PropertySignature + || kind === SyntaxKind.PropertyDeclaration + || kind === SyntaxKind.MethodSignature + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.Constructor + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor + || kind === SyntaxKind.IndexSignature + || kind === SyntaxKind.FunctionExpression + || kind === SyntaxKind.ArrowFunction + || kind === SyntaxKind.ClassExpression + || kind === SyntaxKind.VariableStatement + || kind === SyntaxKind.FunctionDeclaration + || kind === SyntaxKind.ClassDeclaration + || kind === SyntaxKind.InterfaceDeclaration + || kind === SyntaxKind.TypeAliasDeclaration + || kind === SyntaxKind.EnumDeclaration + || kind === SyntaxKind.ModuleDeclaration + || kind === SyntaxKind.ImportEqualsDeclaration + || kind === SyntaxKind.ImportDeclaration + || kind === SyntaxKind.ExportAssignment + || kind === SyntaxKind.ExportDeclaration; +} - export const isTypeNodeOrTypeParameterDeclaration = or(isTypeNode, isTypeParameterDeclaration) as (node: Node) => node is TypeNode | TypeParameterDeclaration; - export const isQuestionOrExclamationToken = or(isQuestionToken, isExclamationToken) as (node: Node) => node is QuestionToken | ExclamationToken; - export const isIdentifierOrThisTypeNode = or(isIdentifier, isThisTypeNode) as (node: Node) => node is Identifier | ThisTypeNode; - export const isReadonlyKeywordOrPlusOrMinusToken = or(isReadonlyKeyword, isPlusToken, isMinusToken) as (node: Node) => node is ReadonlyKeyword | PlusToken | MinusToken; - export const isQuestionOrPlusOrMinusToken = or(isQuestionToken, isPlusToken, isMinusToken) as (node: Node) => node is QuestionToken | PlusToken | MinusToken; - export const isModuleName = or(isIdentifier, isStringLiteral) as (node: Node) => node is ModuleName; - - export function isLiteralTypeLikeExpression(node: Node): node is NullLiteral | BooleanLiteral | LiteralExpression | PrefixUnaryExpression { - const kind = node.kind; - return kind === SyntaxKind.NullKeyword - || kind === SyntaxKind.TrueKeyword - || kind === SyntaxKind.FalseKeyword - || isLiteralExpression(node) - || isPrefixUnaryExpression(node); - } +export const isTypeNodeOrTypeParameterDeclaration = or(isTypeNode, isTypeParameterDeclaration) as (node: Node) => node is TypeNode | TypeParameterDeclaration; +export const isQuestionOrExclamationToken = or(isQuestionToken, isExclamationToken) as (node: Node) => node is QuestionToken | ExclamationToken; +export const isIdentifierOrThisTypeNode = or(isIdentifier, isThisTypeNode) as (node: Node) => node is Identifier | ThisTypeNode; +export const isReadonlyKeywordOrPlusOrMinusToken = or(isReadonlyKeyword, isPlusToken, isMinusToken) as (node: Node) => node is ReadonlyKeyword | PlusToken | MinusToken; +export const isQuestionOrPlusOrMinusToken = or(isQuestionToken, isPlusToken, isMinusToken) as (node: Node) => node is QuestionToken | PlusToken | MinusToken; +export const isModuleName = or(isIdentifier, isStringLiteral) as (node: Node) => node is ModuleName; + +export function isLiteralTypeLikeExpression(node: Node): node is NullLiteral | BooleanLiteral | LiteralExpression | PrefixUnaryExpression { + const kind = node.kind; + return kind === SyntaxKind.NullKeyword + || kind === SyntaxKind.TrueKeyword + || kind === SyntaxKind.FalseKeyword + || isLiteralExpression(node) + || isPrefixUnaryExpression(node); +} - function isExponentiationOperator(kind: SyntaxKind): kind is ExponentiationOperator { - return kind === SyntaxKind.AsteriskAsteriskToken; - } +function isExponentiationOperator(kind: SyntaxKind): kind is ExponentiationOperator { + return kind === SyntaxKind.AsteriskAsteriskToken; +} - function isMultiplicativeOperator(kind: SyntaxKind): kind is MultiplicativeOperator { - return kind === SyntaxKind.AsteriskToken - || kind === SyntaxKind.SlashToken - || kind === SyntaxKind.PercentToken; - } +function isMultiplicativeOperator(kind: SyntaxKind): kind is MultiplicativeOperator { + return kind === SyntaxKind.AsteriskToken + || kind === SyntaxKind.SlashToken + || kind === SyntaxKind.PercentToken; +} - function isMultiplicativeOperatorOrHigher(kind: SyntaxKind): kind is MultiplicativeOperatorOrHigher { - return isExponentiationOperator(kind) - || isMultiplicativeOperator(kind); - } +function isMultiplicativeOperatorOrHigher(kind: SyntaxKind): kind is MultiplicativeOperatorOrHigher { + return isExponentiationOperator(kind) + || isMultiplicativeOperator(kind); +} - function isAdditiveOperator(kind: SyntaxKind): kind is AdditiveOperator { - return kind === SyntaxKind.PlusToken - || kind === SyntaxKind.MinusToken; - } +function isAdditiveOperator(kind: SyntaxKind): kind is AdditiveOperator { + return kind === SyntaxKind.PlusToken + || kind === SyntaxKind.MinusToken; +} - function isAdditiveOperatorOrHigher(kind: SyntaxKind): kind is AdditiveOperatorOrHigher { - return isAdditiveOperator(kind) - || isMultiplicativeOperatorOrHigher(kind); - } +function isAdditiveOperatorOrHigher(kind: SyntaxKind): kind is AdditiveOperatorOrHigher { + return isAdditiveOperator(kind) + || isMultiplicativeOperatorOrHigher(kind); +} - function isShiftOperator(kind: SyntaxKind): kind is ShiftOperator { - return kind === SyntaxKind.LessThanLessThanToken - || kind === SyntaxKind.GreaterThanGreaterThanToken - || kind === SyntaxKind.GreaterThanGreaterThanGreaterThanToken; - } +function isShiftOperator(kind: SyntaxKind): kind is ShiftOperator { + return kind === SyntaxKind.LessThanLessThanToken + || kind === SyntaxKind.GreaterThanGreaterThanToken + || kind === SyntaxKind.GreaterThanGreaterThanGreaterThanToken; +} - function isShiftOperatorOrHigher(kind: SyntaxKind): kind is ShiftOperatorOrHigher { - return isShiftOperator(kind) - || isAdditiveOperatorOrHigher(kind); - } +function isShiftOperatorOrHigher(kind: SyntaxKind): kind is ShiftOperatorOrHigher { + return isShiftOperator(kind) + || isAdditiveOperatorOrHigher(kind); +} - function isRelationalOperator(kind: SyntaxKind): kind is RelationalOperator { - return kind === SyntaxKind.LessThanToken - || kind === SyntaxKind.LessThanEqualsToken - || kind === SyntaxKind.GreaterThanToken - || kind === SyntaxKind.GreaterThanEqualsToken - || kind === SyntaxKind.InstanceOfKeyword - || kind === SyntaxKind.InKeyword; - } +function isRelationalOperator(kind: SyntaxKind): kind is RelationalOperator { + return kind === SyntaxKind.LessThanToken + || kind === SyntaxKind.LessThanEqualsToken + || kind === SyntaxKind.GreaterThanToken + || kind === SyntaxKind.GreaterThanEqualsToken + || kind === SyntaxKind.InstanceOfKeyword + || kind === SyntaxKind.InKeyword; +} - function isRelationalOperatorOrHigher(kind: SyntaxKind): kind is RelationalOperatorOrHigher { - return isRelationalOperator(kind) - || isShiftOperatorOrHigher(kind); - } +function isRelationalOperatorOrHigher(kind: SyntaxKind): kind is RelationalOperatorOrHigher { + return isRelationalOperator(kind) + || isShiftOperatorOrHigher(kind); +} - function isEqualityOperator(kind: SyntaxKind): kind is EqualityOperator { - return kind === SyntaxKind.EqualsEqualsToken - || kind === SyntaxKind.EqualsEqualsEqualsToken - || kind === SyntaxKind.ExclamationEqualsToken - || kind === SyntaxKind.ExclamationEqualsEqualsToken; - } +function isEqualityOperator(kind: SyntaxKind): kind is EqualityOperator { + return kind === SyntaxKind.EqualsEqualsToken + || kind === SyntaxKind.EqualsEqualsEqualsToken + || kind === SyntaxKind.ExclamationEqualsToken + || kind === SyntaxKind.ExclamationEqualsEqualsToken; +} - function isEqualityOperatorOrHigher(kind: SyntaxKind): kind is EqualityOperatorOrHigher { - return isEqualityOperator(kind) - || isRelationalOperatorOrHigher(kind); - } +function isEqualityOperatorOrHigher(kind: SyntaxKind): kind is EqualityOperatorOrHigher { + return isEqualityOperator(kind) + || isRelationalOperatorOrHigher(kind); +} - function isBitwiseOperator(kind: SyntaxKind): kind is BitwiseOperator { - return kind === SyntaxKind.AmpersandToken - || kind === SyntaxKind.BarToken - || kind === SyntaxKind.CaretToken; - } +function isBitwiseOperator(kind: SyntaxKind): kind is BitwiseOperator { + return kind === SyntaxKind.AmpersandToken + || kind === SyntaxKind.BarToken + || kind === SyntaxKind.CaretToken; +} - function isBitwiseOperatorOrHigher(kind: SyntaxKind): kind is BitwiseOperatorOrHigher { - return isBitwiseOperator(kind) - || isEqualityOperatorOrHigher(kind); - } +function isBitwiseOperatorOrHigher(kind: SyntaxKind): kind is BitwiseOperatorOrHigher { + return isBitwiseOperator(kind) + || isEqualityOperatorOrHigher(kind); +} - // NOTE: The version in utilities includes ExclamationToken, which is not a binary operator. - function isLogicalOperator(kind: SyntaxKind): kind is LogicalOperator { - return kind === SyntaxKind.AmpersandAmpersandToken - || kind === SyntaxKind.BarBarToken; - } +// NOTE: The version in utilities includes ExclamationToken, which is not a binary operator. +function isLogicalOperator(kind: SyntaxKind): kind is LogicalOperator { + return kind === SyntaxKind.AmpersandAmpersandToken + || kind === SyntaxKind.BarBarToken; +} - function isLogicalOperatorOrHigher(kind: SyntaxKind): kind is LogicalOperatorOrHigher { - return isLogicalOperator(kind) - || isBitwiseOperatorOrHigher(kind); - } +function isLogicalOperatorOrHigher(kind: SyntaxKind): kind is LogicalOperatorOrHigher { + return isLogicalOperator(kind) + || isBitwiseOperatorOrHigher(kind); +} - function isAssignmentOperatorOrHigher(kind: SyntaxKind): kind is AssignmentOperatorOrHigher { - return kind === SyntaxKind.QuestionQuestionToken - || isLogicalOperatorOrHigher(kind) - || isAssignmentOperator(kind); - } +function isAssignmentOperatorOrHigher(kind: SyntaxKind): kind is AssignmentOperatorOrHigher { + return kind === SyntaxKind.QuestionQuestionToken + || isLogicalOperatorOrHigher(kind) + || isAssignmentOperator(kind); +} - function isBinaryOperator(kind: SyntaxKind): kind is BinaryOperator { - return isAssignmentOperatorOrHigher(kind) - || kind === SyntaxKind.CommaToken; - } +function isBinaryOperator(kind: SyntaxKind): kind is BinaryOperator { + return isAssignmentOperatorOrHigher(kind) + || kind === SyntaxKind.CommaToken; +} - export function isBinaryOperatorToken(node: Node): node is BinaryOperatorToken { - return isBinaryOperator(node.kind); - } +export function isBinaryOperatorToken(node: Node): node is BinaryOperatorToken { + return isBinaryOperator(node.kind); +} - type BinaryExpressionState = (machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], resultHolder: { value: TResult }, outerState: TOuterState) => number; - - namespace BinaryExpressionState { - /** - * Handles walking into a `BinaryExpression`. - * @param machine State machine handler functions - * @param frame The current frame - * @returns The new frame - */ - export function enter(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, outerState: TOuterState): number { - const prevUserState = stackIndex > 0 ? userStateStack[stackIndex - 1] : undefined; - Debug.assertEqual(stateStack[stackIndex], enter); - userStateStack[stackIndex] = machine.onEnter(nodeStack[stackIndex], prevUserState, outerState); - stateStack[stackIndex] = nextState(machine, enter); - return stackIndex; - } +type BinaryExpressionState = (machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], resultHolder: { value: TResult }, outerState: TOuterState) => number; - /** - * Handles walking the `left` side of a `BinaryExpression`. - * @param machine State machine handler functions - * @param frame The current frame - * @returns The new frame - */ - export function left(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { - Debug.assertEqual(stateStack[stackIndex], left); - Debug.assertIsDefined(machine.onLeft); - stateStack[stackIndex] = nextState(machine, left); - const nextNode = machine.onLeft(nodeStack[stackIndex].left, userStateStack[stackIndex], nodeStack[stackIndex]); - if (nextNode) { - checkCircularity(stackIndex, nodeStack, nextNode); - return pushStack(stackIndex, stateStack, nodeStack, userStateStack, nextNode); - } - return stackIndex; - } +namespace BinaryExpressionState { + /** + * Handles walking into a `BinaryExpression`. + * @param machine State machine handler functions + * @param frame The current frame + * @returns The new frame + */ + export function enter(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, outerState: TOuterState): number { + const prevUserState = stackIndex > 0 ? userStateStack[stackIndex - 1] : undefined; + Debug.assertEqual(stateStack[stackIndex], enter); + userStateStack[stackIndex] = machine.onEnter(nodeStack[stackIndex], prevUserState, outerState); + stateStack[stackIndex] = nextState(machine, enter); + return stackIndex; + } - /** - * Handles walking the `operatorToken` of a `BinaryExpression`. - * @param machine State machine handler functions - * @param frame The current frame - * @returns The new frame - */ - export function operator(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { - Debug.assertEqual(stateStack[stackIndex], operator); - Debug.assertIsDefined(machine.onOperator); - stateStack[stackIndex] = nextState(machine, operator); - machine.onOperator(nodeStack[stackIndex].operatorToken, userStateStack[stackIndex], nodeStack[stackIndex]); - return stackIndex; + /** + * Handles walking the `left` side of a `BinaryExpression`. + * @param machine State machine handler functions + * @param frame The current frame + * @returns The new frame + */ + export function left(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { + Debug.assertEqual(stateStack[stackIndex], left); + Debug.assertIsDefined(machine.onLeft); + stateStack[stackIndex] = nextState(machine, left); + const nextNode = machine.onLeft(nodeStack[stackIndex].left, userStateStack[stackIndex], nodeStack[stackIndex]); + if (nextNode) { + checkCircularity(stackIndex, nodeStack, nextNode); + return pushStack(stackIndex, stateStack, nodeStack, userStateStack, nextNode); } + return stackIndex; + } - /** - * Handles walking the `right` side of a `BinaryExpression`. - * @param machine State machine handler functions - * @param frame The current frame - * @returns The new frame - */ - export function right(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { - Debug.assertEqual(stateStack[stackIndex], right); - Debug.assertIsDefined(machine.onRight); - stateStack[stackIndex] = nextState(machine, right); - const nextNode = machine.onRight(nodeStack[stackIndex].right, userStateStack[stackIndex], nodeStack[stackIndex]); - if (nextNode) { - checkCircularity(stackIndex, nodeStack, nextNode); - return pushStack(stackIndex, stateStack, nodeStack, userStateStack, nextNode); - } - return stackIndex; + /** + * Handles walking the `operatorToken` of a `BinaryExpression`. + * @param machine State machine handler functions + * @param frame The current frame + * @returns The new frame + */ + export function operator(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { + Debug.assertEqual(stateStack[stackIndex], operator); + Debug.assertIsDefined(machine.onOperator); + stateStack[stackIndex] = nextState(machine, operator); + machine.onOperator(nodeStack[stackIndex].operatorToken, userStateStack[stackIndex], nodeStack[stackIndex]); + return stackIndex; + } + + /** + * Handles walking the `right` side of a `BinaryExpression`. + * @param machine State machine handler functions + * @param frame The current frame + * @returns The new frame + */ + export function right(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { + Debug.assertEqual(stateStack[stackIndex], right); + Debug.assertIsDefined(machine.onRight); + stateStack[stackIndex] = nextState(machine, right); + const nextNode = machine.onRight(nodeStack[stackIndex].right, userStateStack[stackIndex], nodeStack[stackIndex]); + if (nextNode) { + checkCircularity(stackIndex, nodeStack, nextNode); + return pushStack(stackIndex, stateStack, nodeStack, userStateStack, nextNode); } + return stackIndex; + } - /** - * Handles walking out of a `BinaryExpression`. - * @param machine State machine handler functions - * @param frame The current frame - * @returns The new frame - */ - export function exit(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], resultHolder: { value: TResult }, _outerState: TOuterState): number { - Debug.assertEqual(stateStack[stackIndex], exit); - stateStack[stackIndex] = nextState(machine, exit); - const result = machine.onExit(nodeStack[stackIndex], userStateStack[stackIndex]); - if (stackIndex > 0) { - stackIndex--; - if (machine.foldState) { - const side = stateStack[stackIndex] === exit ? "right" : "left"; - userStateStack[stackIndex] = machine.foldState(userStateStack[stackIndex], result, side); - } - } - else { - resultHolder.value = result; + /** + * Handles walking out of a `BinaryExpression`. + * @param machine State machine handler functions + * @param frame The current frame + * @returns The new frame + */ + export function exit(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], resultHolder: { value: TResult }, _outerState: TOuterState): number { + Debug.assertEqual(stateStack[stackIndex], exit); + stateStack[stackIndex] = nextState(machine, exit); + const result = machine.onExit(nodeStack[stackIndex], userStateStack[stackIndex]); + if (stackIndex > 0) { + stackIndex--; + if (machine.foldState) { + const side = stateStack[stackIndex] === exit ? "right" : "left"; + userStateStack[stackIndex] = machine.foldState(userStateStack[stackIndex], result, side); } - return stackIndex; } - - /** - * Handles a frame that is already done. - * @returns The `done` state. - */ - export function done(_machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], _nodeStack: BinaryExpression[], _userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { - Debug.assertEqual(stateStack[stackIndex], done); - return stackIndex; + else { + resultHolder.value = result; } + return stackIndex; + } - export function nextState(machine: BinaryExpressionStateMachine, currentState: BinaryExpressionState) { - switch (currentState) { - case enter: - if (machine.onLeft) return left; - // falls through - case left: - if (machine.onOperator) return operator; - // falls through - case operator: - if (machine.onRight) return right; - // falls through - case right: return exit; - case exit: return done; - case done: return done; - default: Debug.fail("Invalid state"); - } + /** + * Handles a frame that is already done. + * @returns The `done` state. + */ + export function done(_machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], _nodeStack: BinaryExpression[], _userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { + Debug.assertEqual(stateStack[stackIndex], done); + return stackIndex; + } + + export function nextState(machine: BinaryExpressionStateMachine, currentState: BinaryExpressionState) { + switch (currentState) { + case enter: + if (machine.onLeft) return left; + // falls through + case left: + if (machine.onOperator) return operator; + // falls through + case operator: + if (machine.onRight) return right; + // falls through + case right: return exit; + case exit: return done; + case done: return done; + default: Debug.fail("Invalid state"); } + } - function pushStack(stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], node: BinaryExpression) { - stackIndex++; - stateStack[stackIndex] = enter; - nodeStack[stackIndex] = node; - userStateStack[stackIndex] = undefined!; - return stackIndex; - } + function pushStack(stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], node: BinaryExpression) { + stackIndex++; + stateStack[stackIndex] = enter; + nodeStack[stackIndex] = node; + userStateStack[stackIndex] = undefined!; + return stackIndex; + } - function checkCircularity(stackIndex: number, nodeStack: BinaryExpression[], node: BinaryExpression) { - if (Debug.shouldAssert(AssertionLevel.Aggressive)) { - while (stackIndex >= 0) { - Debug.assert(nodeStack[stackIndex] !== node, "Circular traversal detected."); - stackIndex--; - } + function checkCircularity(stackIndex: number, nodeStack: BinaryExpression[], node: BinaryExpression) { + if (Debug.shouldAssert(AssertionLevel.Aggressive)) { + while (stackIndex >= 0) { + Debug.assert(nodeStack[stackIndex] !== node, "Circular traversal detected."); + stackIndex--; } } } +} - /** - * Holds state machine handler functions - */ - class BinaryExpressionStateMachine { - constructor( - readonly onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState, - readonly onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - readonly onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, - readonly onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - readonly onExit: (node: BinaryExpression, userState: TState) => TResult, - readonly foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, - ) { - } +/** + * Holds state machine handler functions + */ +class BinaryExpressionStateMachine { + constructor( + readonly onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState, + readonly onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + readonly onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, + readonly onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + readonly onExit: (node: BinaryExpression, userState: TState) => TResult, + readonly foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, + ) { } +} - /** - * Creates a state machine that walks a `BinaryExpression` using the heap to reduce call-stack depth on a large tree. - * @param onEnter Callback evaluated when entering a `BinaryExpression`. Returns new user-defined state to associate with the node while walking. - * @param onLeft Callback evaluated when walking the left side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the right side. - * @param onRight Callback evaluated when walking the right side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the end of the node. - * @param onExit Callback evaluated when exiting a `BinaryExpression`. The result returned will either be folded into the parent's state, or returned from the walker if at the top frame. - * @param foldState Callback evaluated when the result from a nested `onExit` should be folded into the state of that node's parent. - * @returns A function that walks a `BinaryExpression` node using the above callbacks, returning the result of the call to `onExit` from the outermost `BinaryExpression` node. - */ - export function createBinaryExpressionTrampoline( - onEnter: (node: BinaryExpression, prev: TState | undefined) => TState, - onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, - onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - onExit: (node: BinaryExpression, userState: TState) => TResult, - foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, - ): (node: BinaryExpression) => TResult; - /** - * Creates a state machine that walks a `BinaryExpression` using the heap to reduce call-stack depth on a large tree. - * @param onEnter Callback evaluated when entering a `BinaryExpression`. Returns new user-defined state to associate with the node while walking. - * @param onLeft Callback evaluated when walking the left side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the right side. - * @param onRight Callback evaluated when walking the right side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the end of the node. - * @param onExit Callback evaluated when exiting a `BinaryExpression`. The result returned will either be folded into the parent's state, or returned from the walker if at the top frame. - * @param foldState Callback evaluated when the result from a nested `onExit` should be folded into the state of that node's parent. - * @returns A function that walks a `BinaryExpression` node using the above callbacks, returning the result of the call to `onExit` from the outermost `BinaryExpression` node. - */ - export function createBinaryExpressionTrampoline( - onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState, - onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, - onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - onExit: (node: BinaryExpression, userState: TState) => TResult, - foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, - ): (node: BinaryExpression, outerState: TOuterState) => TResult; - export function createBinaryExpressionTrampoline( - onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState, - onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, - onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - onExit: (node: BinaryExpression, userState: TState) => TResult, - foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, - ) { - const machine = new BinaryExpressionStateMachine(onEnter, onLeft, onOperator, onRight, onExit, foldState); - return trampoline; - - function trampoline(node: BinaryExpression, outerState?: TOuterState) { - const resultHolder: { value: TResult } = { value: undefined! }; - const stateStack: BinaryExpressionState[] = [BinaryExpressionState.enter]; - const nodeStack: BinaryExpression[] = [node]; - const userStateStack: TState[] = [undefined!]; - let stackIndex = 0; - while (stateStack[stackIndex] !== BinaryExpressionState.done) { - stackIndex = stateStack[stackIndex](machine, stackIndex, stateStack, nodeStack, userStateStack, resultHolder, outerState); - } - Debug.assertEqual(stackIndex, 0); - return resultHolder.value; +/** + * Creates a state machine that walks a `BinaryExpression` using the heap to reduce call-stack depth on a large tree. + * @param onEnter Callback evaluated when entering a `BinaryExpression`. Returns new user-defined state to associate with the node while walking. + * @param onLeft Callback evaluated when walking the left side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the right side. + * @param onRight Callback evaluated when walking the right side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the end of the node. + * @param onExit Callback evaluated when exiting a `BinaryExpression`. The result returned will either be folded into the parent's state, or returned from the walker if at the top frame. + * @param foldState Callback evaluated when the result from a nested `onExit` should be folded into the state of that node's parent. + * @returns A function that walks a `BinaryExpression` node using the above callbacks, returning the result of the call to `onExit` from the outermost `BinaryExpression` node. + */ + export function createBinaryExpressionTrampoline( + onEnter: (node: BinaryExpression, prev: TState | undefined) => TState, + onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, + onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + onExit: (node: BinaryExpression, userState: TState) => TResult, + foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, +): (node: BinaryExpression) => TResult; +/** + * Creates a state machine that walks a `BinaryExpression` using the heap to reduce call-stack depth on a large tree. + * @param onEnter Callback evaluated when entering a `BinaryExpression`. Returns new user-defined state to associate with the node while walking. + * @param onLeft Callback evaluated when walking the left side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the right side. + * @param onRight Callback evaluated when walking the right side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the end of the node. + * @param onExit Callback evaluated when exiting a `BinaryExpression`. The result returned will either be folded into the parent's state, or returned from the walker if at the top frame. + * @param foldState Callback evaluated when the result from a nested `onExit` should be folded into the state of that node's parent. + * @returns A function that walks a `BinaryExpression` node using the above callbacks, returning the result of the call to `onExit` from the outermost `BinaryExpression` node. + */ +export function createBinaryExpressionTrampoline( + onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState, + onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, + onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + onExit: (node: BinaryExpression, userState: TState) => TResult, + foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, +): (node: BinaryExpression, outerState: TOuterState) => TResult; +export function createBinaryExpressionTrampoline( + onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState, + onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, + onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + onExit: (node: BinaryExpression, userState: TState) => TResult, + foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, +) { + const machine = new BinaryExpressionStateMachine(onEnter, onLeft, onOperator, onRight, onExit, foldState); + return trampoline; + + function trampoline(node: BinaryExpression, outerState?: TOuterState) { + const resultHolder: { value: TResult } = { value: undefined! }; + const stateStack: BinaryExpressionState[] = [BinaryExpressionState.enter]; + const nodeStack: BinaryExpression[] = [node]; + const userStateStack: TState[] = [undefined!]; + let stackIndex = 0; + while (stateStack[stackIndex] !== BinaryExpressionState.done) { + stackIndex = stateStack[stackIndex](machine, stackIndex, stateStack, nodeStack, userStateStack, resultHolder, outerState); } + Debug.assertEqual(stackIndex, 0); + return resultHolder.value; } } +} diff --git a/src/compiler/factory/utilitiesPublic.ts b/src/compiler/factory/utilitiesPublic.ts index 9dd28667eb0e1..0fde57479aaef 100644 --- a/src/compiler/factory/utilitiesPublic.ts +++ b/src/compiler/factory/utilitiesPublic.ts @@ -1,5 +1,5 @@ namespace ts { - export function setTextRange(range: T, location: TextRange | undefined): T { - return location ? setTextRangePosEnd(range, location.pos, location.end) : range; - } -} \ No newline at end of file +export function setTextRange(range: T, location: TextRange | undefined): T { + return location ? setTextRangePosEnd(range, location.pos, location.end) : range; +} +} diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index bc606ffebe354..bdfac88e600c7 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1,2212 +1,2212 @@ namespace ts { - /* @internal */ - export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void; - export function trace(host: ModuleResolutionHost): void { - host.trace!(formatMessage.apply(undefined, arguments)); - } - - /* @internal */ - export function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean { - return !!compilerOptions.traceResolution && host.trace !== undefined; - } - - function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined): Resolved | undefined { - let packageId: PackageId | undefined; - if (r && packageInfo) { - const packageJsonContent = packageInfo.packageJsonContent as PackageJson; - if (typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string") { - packageId = { - name: packageJsonContent.name, - subModuleName: r.path.slice(packageInfo.packageDirectory.length + directorySeparator.length), - version: packageJsonContent.version - }; - } - } - return r && { path: r.path, extension: r.ext, packageId }; - } +/* @internal */ +export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void; +export function trace(host: ModuleResolutionHost): void { + host.trace!(formatMessage.apply(undefined, arguments)); +} - function noPackageId(r: PathAndExtension | undefined): Resolved | undefined { - return withPackageId(/*packageInfo*/ undefined, r); - } +/* @internal */ +export function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean { + return !!compilerOptions.traceResolution && host.trace !== undefined; +} - function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined { - if (r) { - Debug.assert(r.packageId === undefined); - return { path: r.path, ext: r.extension }; +function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined): Resolved | undefined { + let packageId: PackageId | undefined; + if (r && packageInfo) { + const packageJsonContent = packageInfo.packageJsonContent as PackageJson; + if (typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string") { + packageId = { + name: packageJsonContent.name, + subModuleName: r.path.slice(packageInfo.packageDirectory.length + directorySeparator.length), + version: packageJsonContent.version + }; } } + return r && { path: r.path, extension: r.ext, packageId }; +} - /** Result of trying to resolve a module. */ - interface Resolved { - path: string; - extension: Extension; - packageId: PackageId | undefined; - /** - * When the resolved is not created from cache, the value is - * - string if it is symbolic link to the resolved `path` - * - undefined if `path` is not a symbolic link - * When the resolved is created using value from cache of ResolvedModuleWithFailedLookupLocations, the value is: - * - string if it is symbolic link to the resolved `path` - * - true if `path` is not a symbolic link - this indicates that the `originalPath` calculation is already done and needs to be skipped - * Note: This is a file name with preserved original casing, not a normalized `Path`. - */ - originalPath?: string | true; - } +function noPackageId(r: PathAndExtension | undefined): Resolved | undefined { + return withPackageId(/*packageInfo*/ undefined, r); +} - /** Result of trying to resolve a module at a file. Needs to have 'packageId' added later. */ - interface PathAndExtension { - path: string; - // (Use a different name than `extension` to make sure Resolved isn't assignable to PathAndExtension.) - ext: Extension; +function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined { + if (r) { + Debug.assert(r.packageId === undefined); + return { path: r.path, ext: r.extension }; } +} +/** Result of trying to resolve a module. */ +interface Resolved { + path: string; + extension: Extension; + packageId: PackageId | undefined; /** - * Kinds of file that we are currently looking for. - * Typically there is one pass with Extensions.TypeScript, then a second pass with Extensions.JavaScript. + * When the resolved is not created from cache, the value is + * - string if it is symbolic link to the resolved `path` + * - undefined if `path` is not a symbolic link + * When the resolved is created using value from cache of ResolvedModuleWithFailedLookupLocations, the value is: + * - string if it is symbolic link to the resolved `path` + * - true if `path` is not a symbolic link - this indicates that the `originalPath` calculation is already done and needs to be skipped + * Note: This is a file name with preserved original casing, not a normalized `Path`. */ - enum Extensions { - TypeScript, /** '.ts', '.tsx', or '.d.ts' */ - JavaScript, /** '.js' or '.jsx' */ - Json, /** '.json' */ - TSConfig, /** '.json' with `tsconfig` used instead of `index` */ - DtsOnly /** Only '.d.ts' */ - } + originalPath?: string | true; +} - interface PathAndPackageId { - readonly fileName: string; - readonly packageId: PackageId | undefined; - } - /** Used with `Extensions.DtsOnly` to extract the path from TypeScript results. */ - function resolvedTypeScriptOnly(resolved: Resolved | undefined): PathAndPackageId | undefined { - if (!resolved) { - return undefined; - } - Debug.assert(extensionIsTS(resolved.extension)); - return { fileName: resolved.path, packageId: resolved.packageId }; - } +/** Result of trying to resolve a module at a file. Needs to have 'packageId' added later. */ +interface PathAndExtension { + path: string; + // (Use a different name than `extension` to make sure Resolved isn't assignable to PathAndExtension.) + ext: Extension; +} - function createResolvedModuleWithFailedLookupLocations(resolved: Resolved | undefined, isExternalLibraryImport: boolean | undefined, failedLookupLocations: string[], resultFromCache: ResolvedModuleWithFailedLookupLocations | undefined): ResolvedModuleWithFailedLookupLocations { - if (resultFromCache) { - resultFromCache.failedLookupLocations.push(...failedLookupLocations); - return resultFromCache; - } - return { - resolvedModule: resolved && { resolvedFileName: resolved.path, originalPath: resolved.originalPath === true ? undefined : resolved.originalPath, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId }, - failedLookupLocations - }; - } +/** + * Kinds of file that we are currently looking for. + * Typically there is one pass with Extensions.TypeScript, then a second pass with Extensions.JavaScript. + */ +enum Extensions { + TypeScript, /** '.ts', '.tsx', or '.d.ts' */ + JavaScript, /** '.js' or '.jsx' */ + Json, /** '.json' */ + TSConfig, /** '.json' with `tsconfig` used instead of `index` */ + DtsOnly /** Only '.d.ts' */ +} - /*@internal*/ - interface ModuleResolutionState { - host: ModuleResolutionHost; - compilerOptions: CompilerOptions; - traceEnabled: boolean; - failedLookupLocations: Push; - resultFromCache?: ResolvedModuleWithFailedLookupLocations; - packageJsonInfoCache: PackageJsonInfoCache | undefined; - features: NodeResolutionFeatures; - conditions: string[]; +interface PathAndPackageId { + readonly fileName: string; + readonly packageId: PackageId | undefined; +} +/** Used with `Extensions.DtsOnly` to extract the path from TypeScript results. */ +function resolvedTypeScriptOnly(resolved: Resolved | undefined): PathAndPackageId | undefined { + if (!resolved) { + return undefined; } + Debug.assert(extensionIsTS(resolved.extension)); + return { fileName: resolved.path, packageId: resolved.packageId }; +} - /** Just the fields that we use for module resolution. */ - /*@internal*/ - interface PackageJsonPathFields { - typings?: string; - types?: string; - typesVersions?: MapLike>; - main?: string; - tsconfig?: string; - type?: string; - imports?: object; - exports?: object; - name?: string; +function createResolvedModuleWithFailedLookupLocations(resolved: Resolved | undefined, isExternalLibraryImport: boolean | undefined, failedLookupLocations: string[], resultFromCache: ResolvedModuleWithFailedLookupLocations | undefined): ResolvedModuleWithFailedLookupLocations { + if (resultFromCache) { + resultFromCache.failedLookupLocations.push(...failedLookupLocations); + return resultFromCache; } + return { + resolvedModule: resolved && { resolvedFileName: resolved.path, originalPath: resolved.originalPath === true ? undefined : resolved.originalPath, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId }, + failedLookupLocations + }; +} - interface PackageJson extends PackageJsonPathFields { - name?: string; - version?: string; - } +/*@internal*/ +interface ModuleResolutionState { + host: ModuleResolutionHost; + compilerOptions: CompilerOptions; + traceEnabled: boolean; + failedLookupLocations: Push; + resultFromCache?: ResolvedModuleWithFailedLookupLocations; + packageJsonInfoCache: PackageJsonInfoCache | undefined; + features: NodeResolutionFeatures; + conditions: string[]; +} - function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined; - function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "object", state: ModuleResolutionState): PackageJson[K] | undefined; - function readPackageJsonField(jsonContent: PackageJson, fieldName: K, typeOfTag: "string" | "object", state: ModuleResolutionState): PackageJson[K] | undefined { - if (!hasProperty(jsonContent, fieldName)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_does_not_have_a_0_field, fieldName); - } - return; +/** Just the fields that we use for module resolution. */ +/*@internal*/ +interface PackageJsonPathFields { + typings?: string; + types?: string; + typesVersions?: MapLike>; + main?: string; + tsconfig?: string; + type?: string; + imports?: object; + exports?: object; + name?: string; +} + +interface PackageJson extends PackageJsonPathFields { + name?: string; + version?: string; +} + +function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined; +function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "object", state: ModuleResolutionState): PackageJson[K] | undefined; +function readPackageJsonField(jsonContent: PackageJson, fieldName: K, typeOfTag: "string" | "object", state: ModuleResolutionState): PackageJson[K] | undefined { + if (!hasProperty(jsonContent, fieldName)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_does_not_have_a_0_field, fieldName); } - const value = jsonContent[fieldName]; - if (typeof value !== typeOfTag || value === null) { // eslint-disable-line no-null/no-null - if (state.traceEnabled) { - // eslint-disable-next-line no-null/no-null - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, fieldName, typeOfTag, value === null ? "null" : typeof value); - } - return; + return; + } + const value = jsonContent[fieldName]; + if (typeof value !== typeOfTag || value === null) { // eslint-disable-line no-null/no-null + if (state.traceEnabled) { + // eslint-disable-next-line no-null/no-null + trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, fieldName, typeOfTag, value === null ? "null" : typeof value); } - return value; + return; } + return value; +} - function readPackageJsonPathField(jsonContent: PackageJson, fieldName: K, baseDirectory: string, state: ModuleResolutionState): PackageJson[K] | undefined { - const fileName = readPackageJsonField(jsonContent, fieldName, "string", state); - if (fileName === undefined) { - return; - } - if (!fileName) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_had_a_falsy_0_field, fieldName); - } - return; - } - const path = normalizePath(combinePaths(baseDirectory, fileName)); +function readPackageJsonPathField(jsonContent: PackageJson, fieldName: K, baseDirectory: string, state: ModuleResolutionState): PackageJson[K] | undefined { + const fileName = readPackageJsonField(jsonContent, fieldName, "string", state); + if (fileName === undefined) { + return; + } + if (!fileName) { if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path); + trace(state.host, Diagnostics.package_json_had_a_falsy_0_field, fieldName); } - return path; + return; } - - function readPackageJsonTypesFields(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { - return readPackageJsonPathField(jsonContent, "typings", baseDirectory, state) - || readPackageJsonPathField(jsonContent, "types", baseDirectory, state); + const path = normalizePath(combinePaths(baseDirectory, fileName)); + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path); } + return path; +} - function readPackageJsonTSConfigField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { - return readPackageJsonPathField(jsonContent, "tsconfig", baseDirectory, state); - } +function readPackageJsonTypesFields(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { + return readPackageJsonPathField(jsonContent, "typings", baseDirectory, state) + || readPackageJsonPathField(jsonContent, "types", baseDirectory, state); +} - function readPackageJsonMainField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { - return readPackageJsonPathField(jsonContent, "main", baseDirectory, state); - } +function readPackageJsonTSConfigField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { + return readPackageJsonPathField(jsonContent, "tsconfig", baseDirectory, state); +} - function readPackageJsonTypesVersionsField(jsonContent: PackageJson, state: ModuleResolutionState) { - const typesVersions = readPackageJsonField(jsonContent, "typesVersions", "object", state); - if (typesVersions === undefined) return; +function readPackageJsonMainField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { + return readPackageJsonPathField(jsonContent, "main", baseDirectory, state); +} - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_a_typesVersions_field_with_version_specific_path_mappings); - } +function readPackageJsonTypesVersionsField(jsonContent: PackageJson, state: ModuleResolutionState) { + const typesVersions = readPackageJsonField(jsonContent, "typesVersions", "object", state); + if (typesVersions === undefined) return; - return typesVersions; + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_a_typesVersions_field_with_version_specific_path_mappings); } - /*@internal*/ - interface VersionPaths { - version: string; - paths: MapLike; - } + return typesVersions; +} - function readPackageJsonTypesVersionPaths(jsonContent: PackageJson, state: ModuleResolutionState): VersionPaths | undefined { - const typesVersions = readPackageJsonTypesVersionsField(jsonContent, state); - if (typesVersions === undefined) return; +/*@internal*/ +interface VersionPaths { + version: string; + paths: MapLike; +} - if (state.traceEnabled) { - for (const key in typesVersions) { - if (hasProperty(typesVersions, key) && !VersionRange.tryParse(key)) { - trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_is_not_a_valid_semver_range, key); - } - } - } +function readPackageJsonTypesVersionPaths(jsonContent: PackageJson, state: ModuleResolutionState): VersionPaths | undefined { + const typesVersions = readPackageJsonTypesVersionsField(jsonContent, state); + if (typesVersions === undefined) return; - const result = getPackageJsonTypesVersionsPaths(typesVersions); - if (!result) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_does_not_have_a_typesVersions_entry_that_matches_version_0, versionMajorMinor); + if (state.traceEnabled) { + for (const key in typesVersions) { + if (hasProperty(typesVersions, key) && !VersionRange.tryParse(key)) { + trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_is_not_a_valid_semver_range, key); } - return; } + } - const { version: bestVersionKey, paths: bestVersionPaths } = result; - if (typeof bestVersionPaths !== "object") { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, `typesVersions['${bestVersionKey}']`, "object", typeof bestVersionPaths); - } - return; + const result = getPackageJsonTypesVersionsPaths(typesVersions); + if (!result) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_does_not_have_a_typesVersions_entry_that_matches_version_0, versionMajorMinor); } - - return result; + return; } - let typeScriptVersion: Version | undefined; + const { version: bestVersionKey, paths: bestVersionPaths } = result; + if (typeof bestVersionPaths !== "object") { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, `typesVersions['${bestVersionKey}']`, "object", typeof bestVersionPaths); + } + return; + } - /* @internal */ - export function getPackageJsonTypesVersionsPaths(typesVersions: MapLike>) { - if (!typeScriptVersion) typeScriptVersion = new Version(version); + return result; +} - for (const key in typesVersions) { - if (!hasProperty(typesVersions, key)) continue; +let typeScriptVersion: Version | undefined; - const keyRange = VersionRange.tryParse(key); - if (keyRange === undefined) { - continue; - } +/* @internal */ +export function getPackageJsonTypesVersionsPaths(typesVersions: MapLike>) { + if (!typeScriptVersion) typeScriptVersion = new Version(version); - // return the first entry whose range matches the current compiler version. - if (keyRange.test(typeScriptVersion)) { - return { version: key, paths: typesVersions[key] }; - } - } - } + for (const key in typesVersions) { + if (!hasProperty(typesVersions, key)) continue; - export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined { - if (options.typeRoots) { - return options.typeRoots; + const keyRange = VersionRange.tryParse(key); + if (keyRange === undefined) { + continue; } - let currentDirectory: string | undefined; - if (options.configFilePath) { - currentDirectory = getDirectoryPath(options.configFilePath); - } - else if (host.getCurrentDirectory) { - currentDirectory = host.getCurrentDirectory(); + // return the first entry whose range matches the current compiler version. + if (keyRange.test(typeScriptVersion)) { + return { version: key, paths: typesVersions[key] }; } + } +} - if (currentDirectory !== undefined) { - return getDefaultTypeRoots(currentDirectory, host); - } +export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined { + if (options.typeRoots) { + return options.typeRoots; } - /** - * Returns the path to every node_modules/@types directory from some ancestor directory. - * Returns undefined if there are none. - */ - function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined { - if (!host.directoryExists) { - return [combinePaths(currentDirectory, nodeModulesAtTypes)]; - // And if it doesn't exist, tough. - } + let currentDirectory: string | undefined; + if (options.configFilePath) { + currentDirectory = getDirectoryPath(options.configFilePath); + } + else if (host.getCurrentDirectory) { + currentDirectory = host.getCurrentDirectory(); + } - let typeRoots: string[] | undefined; - forEachAncestorDirectory(normalizePath(currentDirectory), directory => { - const atTypes = combinePaths(directory, nodeModulesAtTypes); - if (host.directoryExists!(atTypes)) { - (typeRoots || (typeRoots = [])).push(atTypes); - } - return undefined; - }); - return typeRoots; + if (currentDirectory !== undefined) { + return getDefaultTypeRoots(currentDirectory, host); } - const nodeModulesAtTypes = combinePaths("node_modules", "@types"); +} - function arePathsEqual(path1: string, path2: string, host: ModuleResolutionHost): boolean { - const useCaseSensitiveFileNames = typeof host.useCaseSensitiveFileNames === "function" ? host.useCaseSensitiveFileNames() : host.useCaseSensitiveFileNames; - return comparePaths(path1, path2, !useCaseSensitiveFileNames) === Comparison.EqualTo; +/** + * Returns the path to every node_modules/@types directory from some ancestor directory. + * Returns undefined if there are none. + */ +function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined { + if (!host.directoryExists) { + return [combinePaths(currentDirectory, nodeModulesAtTypes)]; + // And if it doesn't exist, tough. } - /** - * @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown. - * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups - * is assumed to be the same as root directory of the project. - */ - export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, cache?: TypeReferenceDirectiveResolutionCache): ResolvedTypeReferenceDirectiveWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(options, host); - if (redirectedReference) { - options = redirectedReference.commandLine.options; + let typeRoots: string[] | undefined; + forEachAncestorDirectory(normalizePath(currentDirectory), directory => { + const atTypes = combinePaths(directory, nodeModulesAtTypes); + if (host.directoryExists!(atTypes)) { + (typeRoots || (typeRoots = [])).push(atTypes); } + return undefined; + }); + return typeRoots; +} +const nodeModulesAtTypes = combinePaths("node_modules", "@types"); - const containingDirectory = containingFile ? getDirectoryPath(containingFile) : undefined; - const perFolderCache = containingDirectory ? cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference) : undefined; - let result = perFolderCache && perFolderCache.get(typeReferenceDirectiveName, /*mode*/ undefined); - if (result) { - if (traceEnabled) { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1, typeReferenceDirectiveName, containingFile); - if (redirectedReference) trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); - trace(host, Diagnostics.Resolution_for_type_reference_directive_0_was_found_in_cache_from_location_1, typeReferenceDirectiveName, containingDirectory); - traceResult(result); - } - return result; - } +function arePathsEqual(path1: string, path2: string, host: ModuleResolutionHost): boolean { + const useCaseSensitiveFileNames = typeof host.useCaseSensitiveFileNames === "function" ? host.useCaseSensitiveFileNames() : host.useCaseSensitiveFileNames; + return comparePaths(path1, path2, !useCaseSensitiveFileNames) === Comparison.EqualTo; +} - const typeRoots = getEffectiveTypeRoots(options, host); +/** + * @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown. + * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups + * is assumed to be the same as root directory of the project. + */ +export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, cache?: TypeReferenceDirectiveResolutionCache): ResolvedTypeReferenceDirectiveWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(options, host); + if (redirectedReference) { + options = redirectedReference.commandLine.options; + } + + const containingDirectory = containingFile ? getDirectoryPath(containingFile) : undefined; + const perFolderCache = containingDirectory ? cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference) : undefined; + let result = perFolderCache && perFolderCache.get(typeReferenceDirectiveName, /*mode*/ undefined); + if (result) { if (traceEnabled) { - if (containingFile === undefined) { - if (typeRoots === undefined) { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName); - } - else { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots); - } + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1, typeReferenceDirectiveName, containingFile); + if (redirectedReference) trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); + trace(host, Diagnostics.Resolution_for_type_reference_directive_0_was_found_in_cache_from_location_1, typeReferenceDirectiveName, containingDirectory); + traceResult(result); + } + return result; + } + + const typeRoots = getEffectiveTypeRoots(options, host); + if (traceEnabled) { + if (containingFile === undefined) { + if (typeRoots === undefined) { + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName); } else { - if (typeRoots === undefined) { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile); - } - else { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots); - } + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots); + } + } + else { + if (typeRoots === undefined) { + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile); } - if (redirectedReference) { - trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); + else { + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots); } } - - const failedLookupLocations: string[] = []; - const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache, features: NodeResolutionFeatures.AllFeatures, conditions: ["node", "require", "types"] }; - let resolved = primaryLookup(); - let primary = true; - if (!resolved) { - resolved = secondaryLookup(); - primary = false; + if (redirectedReference) { + trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); } + } - let resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined; - if (resolved) { - const { fileName, packageId } = resolved; - const resolvedFileName = options.preserveSymlinks ? fileName : realPath(fileName, host, traceEnabled); - resolvedTypeReferenceDirective = { - primary, - resolvedFileName, - originalPath: arePathsEqual(fileName, resolvedFileName, host) ? undefined : fileName, - packageId, - isExternalLibraryImport: pathContainsNodeModules(fileName), - }; - } - result = { resolvedTypeReferenceDirective, failedLookupLocations }; - perFolderCache?.set(typeReferenceDirectiveName, /*mode*/ undefined, result); - if (traceEnabled) traceResult(result); - return result; + const failedLookupLocations: string[] = []; + const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache, features: NodeResolutionFeatures.AllFeatures, conditions: ["node", "require", "types"] }; + let resolved = primaryLookup(); + let primary = true; + if (!resolved) { + resolved = secondaryLookup(); + primary = false; + } - function traceResult(result: ResolvedTypeReferenceDirectiveWithFailedLookupLocations) { - if (!result.resolvedTypeReferenceDirective?.resolvedFileName) { - trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName); - } - else if (result.resolvedTypeReferenceDirective.packageId) { - trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_with_Package_ID_2_primary_Colon_3, typeReferenceDirectiveName, result.resolvedTypeReferenceDirective.resolvedFileName, packageIdToString(result.resolvedTypeReferenceDirective.packageId), result.resolvedTypeReferenceDirective.primary); - } - else { - trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, result.resolvedTypeReferenceDirective.resolvedFileName, result.resolvedTypeReferenceDirective.primary); - } + let resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined; + if (resolved) { + const { fileName, packageId } = resolved; + const resolvedFileName = options.preserveSymlinks ? fileName : realPath(fileName, host, traceEnabled); + resolvedTypeReferenceDirective = { + primary, + resolvedFileName, + originalPath: arePathsEqual(fileName, resolvedFileName, host) ? undefined : fileName, + packageId, + isExternalLibraryImport: pathContainsNodeModules(fileName), + }; + } + result = { resolvedTypeReferenceDirective, failedLookupLocations }; + perFolderCache?.set(typeReferenceDirectiveName, /*mode*/ undefined, result); + if (traceEnabled) traceResult(result); + return result; + + function traceResult(result: ResolvedTypeReferenceDirectiveWithFailedLookupLocations) { + if (!result.resolvedTypeReferenceDirective?.resolvedFileName) { + trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName); } + else if (result.resolvedTypeReferenceDirective.packageId) { + trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_with_Package_ID_2_primary_Colon_3, typeReferenceDirectiveName, result.resolvedTypeReferenceDirective.resolvedFileName, packageIdToString(result.resolvedTypeReferenceDirective.packageId), result.resolvedTypeReferenceDirective.primary); + } + else { + trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, result.resolvedTypeReferenceDirective.resolvedFileName, result.resolvedTypeReferenceDirective.primary); + } + } - function primaryLookup(): PathAndPackageId | undefined { - // Check primary library paths - if (typeRoots && typeRoots.length) { - if (traceEnabled) { - trace(host, Diagnostics.Resolving_with_primary_search_path_0, typeRoots.join(", ")); - } - return firstDefined(typeRoots, typeRoot => { - const candidate = combinePaths(typeRoot, typeReferenceDirectiveName); - const candidateDirectory = getDirectoryPath(candidate); - const directoryExists = directoryProbablyExists(candidateDirectory, host); - if (!directoryExists && traceEnabled) { - trace(host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidateDirectory); - } - return resolvedTypeScriptOnly( - loadNodeModuleFromDirectory(Extensions.DtsOnly, candidate, - !directoryExists, moduleResolutionState)); - }); - } - else { - if (traceEnabled) { - trace(host, Diagnostics.Root_directory_cannot_be_determined_skipping_primary_search_paths); + function primaryLookup(): PathAndPackageId | undefined { + // Check primary library paths + if (typeRoots && typeRoots.length) { + if (traceEnabled) { + trace(host, Diagnostics.Resolving_with_primary_search_path_0, typeRoots.join(", ")); + } + return firstDefined(typeRoots, typeRoot => { + const candidate = combinePaths(typeRoot, typeReferenceDirectiveName); + const candidateDirectory = getDirectoryPath(candidate); + const directoryExists = directoryProbablyExists(candidateDirectory, host); + if (!directoryExists && traceEnabled) { + trace(host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidateDirectory); } + return resolvedTypeScriptOnly( + loadNodeModuleFromDirectory(Extensions.DtsOnly, candidate, + !directoryExists, moduleResolutionState)); + }); + } + else { + if (traceEnabled) { + trace(host, Diagnostics.Root_directory_cannot_be_determined_skipping_primary_search_paths); } } + } - function secondaryLookup(): PathAndPackageId | undefined { - const initialLocationForSecondaryLookup = containingFile && getDirectoryPath(containingFile); + function secondaryLookup(): PathAndPackageId | undefined { + const initialLocationForSecondaryLookup = containingFile && getDirectoryPath(containingFile); - if (initialLocationForSecondaryLookup !== undefined) { - // check secondary locations - if (traceEnabled) { - trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup); - } - let result: Resolved | undefined; - if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) { - const searchResult = loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined); - result = searchResult && searchResult.value; - } - else { - const { path: candidate } = normalizePathAndParts(combinePaths(initialLocationForSecondaryLookup, typeReferenceDirectiveName)); - result = nodeLoadModuleByRelativeName(Extensions.DtsOnly, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true); - } - return resolvedTypeScriptOnly(result); + if (initialLocationForSecondaryLookup !== undefined) { + // check secondary locations + if (traceEnabled) { + trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup); + } + let result: Resolved | undefined; + if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) { + const searchResult = loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined); + result = searchResult && searchResult.value; } else { - if (traceEnabled) { - trace(host, Diagnostics.Containing_file_is_not_specified_and_root_directory_cannot_be_determined_skipping_lookup_in_node_modules_folder); - } + const { path: candidate } = normalizePathAndParts(combinePaths(initialLocationForSecondaryLookup, typeReferenceDirectiveName)); + result = nodeLoadModuleByRelativeName(Extensions.DtsOnly, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true); + } + return resolvedTypeScriptOnly(result); + } + else { + if (traceEnabled) { + trace(host, Diagnostics.Containing_file_is_not_specified_and_root_directory_cannot_be_determined_skipping_lookup_in_node_modules_folder); } } } +} - /** - * Given a set of options, returns the set of type directive names - * that should be included for this program automatically. - * This list could either come from the config file, - * or from enumerating the types root + initial secondary types lookup location. - * More type directives might appear in the program later as a result of loading actual source files; - * this list is only the set of defaults that are implicitly included. - */ - export function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: ModuleResolutionHost): string[] { - // Use explicit type list from tsconfig.json - if (options.types) { - return options.types; - } - - // Walk the primary type lookup locations - const result: string[] = []; - if (host.directoryExists && host.getDirectories) { - const typeRoots = getEffectiveTypeRoots(options, host); - if (typeRoots) { - for (const root of typeRoots) { - if (host.directoryExists(root)) { - for (const typeDirectivePath of host.getDirectories(root)) { - const normalized = normalizePath(typeDirectivePath); - const packageJsonPath = combinePaths(root, normalized, "package.json"); - // `types-publisher` sometimes creates packages with `"typings": null` for packages that don't provide their own types. - // See `createNotNeededPackageJSON` in the types-publisher` repo. - // eslint-disable-next-line no-null/no-null - const isNotNeededPackage = host.fileExists(packageJsonPath) && (readJson(packageJsonPath, host) as PackageJson).typings === null; - if (!isNotNeededPackage) { - const baseFileName = getBaseFileName(normalized); - - // At this stage, skip results with leading dot. - if (baseFileName.charCodeAt(0) !== CharacterCodes.dot) { - // Return just the type directive names - result.push(baseFileName); - } +/** + * Given a set of options, returns the set of type directive names + * that should be included for this program automatically. + * This list could either come from the config file, + * or from enumerating the types root + initial secondary types lookup location. + * More type directives might appear in the program later as a result of loading actual source files; + * this list is only the set of defaults that are implicitly included. + */ +export function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: ModuleResolutionHost): string[] { + // Use explicit type list from tsconfig.json + if (options.types) { + return options.types; + } + + // Walk the primary type lookup locations + const result: string[] = []; + if (host.directoryExists && host.getDirectories) { + const typeRoots = getEffectiveTypeRoots(options, host); + if (typeRoots) { + for (const root of typeRoots) { + if (host.directoryExists(root)) { + for (const typeDirectivePath of host.getDirectories(root)) { + const normalized = normalizePath(typeDirectivePath); + const packageJsonPath = combinePaths(root, normalized, "package.json"); + // `types-publisher` sometimes creates packages with `"typings": null` for packages that don't provide their own types. + // See `createNotNeededPackageJSON` in the types-publisher` repo. + // eslint-disable-next-line no-null/no-null + const isNotNeededPackage = host.fileExists(packageJsonPath) && (readJson(packageJsonPath, host) as PackageJson).typings === null; + if (!isNotNeededPackage) { + const baseFileName = getBaseFileName(normalized); + + // At this stage, skip results with leading dot. + if (baseFileName.charCodeAt(0) !== CharacterCodes.dot) { + // Return just the type directive names + result.push(baseFileName); } } } } } } - return result; } + return result; +} - export interface TypeReferenceDirectiveResolutionCache extends PerDirectoryResolutionCache, PackageJsonInfoCache { - } +export interface TypeReferenceDirectiveResolutionCache extends PerDirectoryResolutionCache, PackageJsonInfoCache { +} - export interface ModeAwareCache { - get(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): T | undefined; - set(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, value: T): this; - delete(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): this; - has(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): boolean; - forEach(cb: (elem: T, key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => void): void; - size(): number; - } +export interface ModeAwareCache { + get(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): T | undefined; + set(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, value: T): this; + delete(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): this; + has(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): boolean; + forEach(cb: (elem: T, key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => void): void; + size(): number; +} +/** + * Cached resolutions per containing directory. + * This assumes that any module id will have the same resolution for sibling files located in the same folder. + */ +export interface PerDirectoryResolutionCache { + getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): ModeAwareCache; + clear(): void; /** - * Cached resolutions per containing directory. - * This assumes that any module id will have the same resolution for sibling files located in the same folder. + * Updates with the current compilerOptions the cache will operate with. + * This updates the redirects map as well if needed so module resolutions are cached if they can across the projects */ - export interface PerDirectoryResolutionCache { - getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): ModeAwareCache; - clear(): void; - /** - * Updates with the current compilerOptions the cache will operate with. - * This updates the redirects map as well if needed so module resolutions are cached if they can across the projects - */ - update(options: CompilerOptions): void; - } + update(options: CompilerOptions): void; +} - export interface ModuleResolutionCache extends PerDirectoryResolutionCache, NonRelativeModuleNameResolutionCache, PackageJsonInfoCache { - getPackageJsonInfoCache(): PackageJsonInfoCache; - } +export interface ModuleResolutionCache extends PerDirectoryResolutionCache, NonRelativeModuleNameResolutionCache, PackageJsonInfoCache { + getPackageJsonInfoCache(): PackageJsonInfoCache; +} - /** - * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory - * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. - */ - export interface NonRelativeModuleNameResolutionCache extends PackageJsonInfoCache { - getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; - } - - export interface PackageJsonInfoCache { - /*@internal*/ getPackageJsonInfo(packageJsonPath: string): PackageJsonInfo | boolean | undefined; - /*@internal*/ setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean): void; - /*@internal*/ entries(): [Path, PackageJsonInfo | boolean][]; - clear(): void; - } - - export interface PerModuleNameCache { - get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined; - set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void; - } - - /*@internal*/ - export interface CacheWithRedirects { - getOwnMap: () => ESMap; - redirectsMap: ESMap>; - getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): ESMap; - clear(): void; - setOwnOptions(newOptions: CompilerOptions): void; - setOwnMap(newOwnMap: ESMap): void; - } - - /*@internal*/ - export function createCacheWithRedirects(options?: CompilerOptions): CacheWithRedirects { - let ownMap: ESMap = new Map(); - const redirectsMap = new Map>(); - return { - getOwnMap, - redirectsMap, - getOrCreateMapOfCacheRedirects, - clear, - setOwnOptions, - setOwnMap - }; +/** + * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory + * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. + */ +export interface NonRelativeModuleNameResolutionCache extends PackageJsonInfoCache { + getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; +} - function getOwnMap() { - return ownMap; - } +export interface PackageJsonInfoCache { + /*@internal*/ getPackageJsonInfo(packageJsonPath: string): PackageJsonInfo | boolean | undefined; + /*@internal*/ setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean): void; + /*@internal*/ entries(): [Path, PackageJsonInfo | boolean][]; + clear(): void; +} - function setOwnOptions(newOptions: CompilerOptions) { - options = newOptions; - } +export interface PerModuleNameCache { + get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined; + set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void; +} - function setOwnMap(newOwnMap: ESMap) { - ownMap = newOwnMap; - } +/*@internal*/ +export interface CacheWithRedirects { + getOwnMap: () => ESMap; + redirectsMap: ESMap>; + getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): ESMap; + clear(): void; + setOwnOptions(newOptions: CompilerOptions): void; + setOwnMap(newOwnMap: ESMap): void; +} - function getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined) { - if (!redirectedReference) { - return ownMap; - } - const path = redirectedReference.sourceFile.path; - let redirects = redirectsMap.get(path); - if (!redirects) { - // Reuse map if redirected reference map uses same resolution - redirects = !options || optionsHaveModuleResolutionChanges(options, redirectedReference.commandLine.options) ? new Map() : ownMap; - redirectsMap.set(path, redirects); - } - return redirects; - } +/*@internal*/ +export function createCacheWithRedirects(options?: CompilerOptions): CacheWithRedirects { + let ownMap: ESMap = new Map(); + const redirectsMap = new Map>(); + return { + getOwnMap, + redirectsMap, + getOrCreateMapOfCacheRedirects, + clear, + setOwnOptions, + setOwnMap + }; - function clear() { - ownMap.clear(); - redirectsMap.clear(); - } + function getOwnMap() { + return ownMap; } - function createPackageJsonInfoCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): PackageJsonInfoCache { - let cache: ESMap | undefined; - return { getPackageJsonInfo, setPackageJsonInfo, clear, entries }; - function getPackageJsonInfo(packageJsonPath: string) { - return cache?.get(toPath(packageJsonPath, currentDirectory, getCanonicalFileName)); - } - function setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean) { - (cache ||= new Map()).set(toPath(packageJsonPath, currentDirectory, getCanonicalFileName), info); - } - function clear() { - cache = undefined; - } - function entries() { - const iter = cache?.entries(); - return iter ? arrayFrom(iter) : []; - } + function setOwnOptions(newOptions: CompilerOptions) { + options = newOptions; } - function getOrCreateCache(cacheWithRedirects: CacheWithRedirects, redirectedReference: ResolvedProjectReference | undefined, key: string, create: () => T): T { - const cache = cacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); - let result = cache.get(key); - if (!result) { - result = create(); - cache.set(key, result); - } - return result; + function setOwnMap(newOwnMap: ESMap) { + ownMap = newOwnMap; } - function updateRedirectsMap( - options: CompilerOptions, - directoryToModuleNameMap: CacheWithRedirects>, - moduleNameToDirectoryMap?: CacheWithRedirects - ) { - if (!options.configFile) return; - if (directoryToModuleNameMap.redirectsMap.size === 0) { - // The own map will be for projectCompilerOptions - Debug.assert(!moduleNameToDirectoryMap || moduleNameToDirectoryMap.redirectsMap.size === 0); - Debug.assert(directoryToModuleNameMap.getOwnMap().size === 0); - Debug.assert(!moduleNameToDirectoryMap || moduleNameToDirectoryMap.getOwnMap().size === 0); - directoryToModuleNameMap.redirectsMap.set(options.configFile.path, directoryToModuleNameMap.getOwnMap()); - moduleNameToDirectoryMap?.redirectsMap.set(options.configFile.path, moduleNameToDirectoryMap.getOwnMap()); + function getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined) { + if (!redirectedReference) { + return ownMap; } - else { - // Set correct own map - Debug.assert(!moduleNameToDirectoryMap || moduleNameToDirectoryMap.redirectsMap.size > 0); - const ref: ResolvedProjectReference = { - sourceFile: options.configFile, - commandLine: { options } as ParsedCommandLine - }; - directoryToModuleNameMap.setOwnMap(directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)); - moduleNameToDirectoryMap?.setOwnMap(moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)); + const path = redirectedReference.sourceFile.path; + let redirects = redirectsMap.get(path); + if (!redirects) { + // Reuse map if redirected reference map uses same resolution + redirects = !options || optionsHaveModuleResolutionChanges(options, redirectedReference.commandLine.options) ? new Map() : ownMap; + redirectsMap.set(path, redirects); } - directoryToModuleNameMap.setOwnOptions(options); - moduleNameToDirectoryMap?.setOwnOptions(options); + return redirects; } - function createPerDirectoryResolutionCache(currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, directoryToModuleNameMap: CacheWithRedirects>): PerDirectoryResolutionCache { - return { - getOrCreateCacheForDirectory, - clear, - update, - }; + function clear() { + ownMap.clear(); + redirectsMap.clear(); + } +} - function clear() { - directoryToModuleNameMap.clear(); - } - - function update(options: CompilerOptions) { - updateRedirectsMap(options, directoryToModuleNameMap); - } - - function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) { - const path = toPath(directoryName, currentDirectory, getCanonicalFileName); - return getOrCreateCache>(directoryToModuleNameMap, redirectedReference, path, () => createModeAwareCache()); - } - } - - /* @internal */ - export function createModeAwareCache(): ModeAwareCache { - const underlying = new Map(); - const memoizedReverseKeys = new Map(); - - const cache: ModeAwareCache = { - get(specifier, mode) { - return underlying.get(getUnderlyingCacheKey(specifier, mode)); - }, - set(specifier, mode, value) { - underlying.set(getUnderlyingCacheKey(specifier, mode), value); - return cache; - }, - delete(specifier, mode) { - underlying.delete(getUnderlyingCacheKey(specifier, mode)); - return cache; - }, - has(specifier, mode) { - return underlying.has(getUnderlyingCacheKey(specifier, mode)); - }, - forEach(cb) { - return underlying.forEach((elem, key) => { - const [specifier, mode] = memoizedReverseKeys.get(key)!; - return cb(elem, specifier, mode); - }); - }, - size() { - return underlying.size; - } - }; - return cache; +function createPackageJsonInfoCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): PackageJsonInfoCache { + let cache: ESMap | undefined; + return { getPackageJsonInfo, setPackageJsonInfo, clear, entries }; + function getPackageJsonInfo(packageJsonPath: string) { + return cache?.get(toPath(packageJsonPath, currentDirectory, getCanonicalFileName)); + } + function setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean) { + (cache ||= new Map()).set(toPath(packageJsonPath, currentDirectory, getCanonicalFileName), info); + } + function clear() { + cache = undefined; + } + function entries() { + const iter = cache?.entries(); + return iter ? arrayFrom(iter) : []; + } +} - function getUnderlyingCacheKey(specifier: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) { - const result = mode === undefined ? specifier : `${mode}|${specifier}`; - memoizedReverseKeys.set(result, [specifier, mode]); - return result; - } +function getOrCreateCache(cacheWithRedirects: CacheWithRedirects, redirectedReference: ResolvedProjectReference | undefined, key: string, create: () => T): T { + const cache = cacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); + let result = cache.get(key); + if (!result) { + result = create(); + cache.set(key, result); } + return result; +} - /* @internal */ - export function zipToModeAwareCache(file: SourceFile, keys: readonly string[], values: readonly V[]): ModeAwareCache { - Debug.assert(keys.length === values.length); - const map = createModeAwareCache(); - for (let i = 0; i < keys.length; ++i) { - map.set(keys[i], getModeForResolutionAtIndex(file, i), values[i]); - } - return map; - } - - export function createModuleResolutionCache( - currentDirectory: string, - getCanonicalFileName: (s: string) => string, - options?: CompilerOptions - ): ModuleResolutionCache; - /*@internal*/ - export function createModuleResolutionCache( - currentDirectory: string, - getCanonicalFileName: GetCanonicalFileName, - options: undefined, - directoryToModuleNameMap: CacheWithRedirects>, - moduleNameToDirectoryMap: CacheWithRedirects, - ): ModuleResolutionCache; - export function createModuleResolutionCache( - currentDirectory: string, - getCanonicalFileName: GetCanonicalFileName, - options?: CompilerOptions, - directoryToModuleNameMap?: CacheWithRedirects>, - moduleNameToDirectoryMap?: CacheWithRedirects, - ): ModuleResolutionCache { - const preDirectoryResolutionCache = createPerDirectoryResolutionCache(currentDirectory, getCanonicalFileName, directoryToModuleNameMap ||= createCacheWithRedirects(options)); - moduleNameToDirectoryMap ||= createCacheWithRedirects(options); - const packageJsonInfoCache = createPackageJsonInfoCache(currentDirectory, getCanonicalFileName); - - return { - ...packageJsonInfoCache, - ...preDirectoryResolutionCache, - getOrCreateCacheForModuleName, - clear, - update, - getPackageJsonInfoCache: () => packageJsonInfoCache, +function updateRedirectsMap( + options: CompilerOptions, + directoryToModuleNameMap: CacheWithRedirects>, + moduleNameToDirectoryMap?: CacheWithRedirects +) { + if (!options.configFile) return; + if (directoryToModuleNameMap.redirectsMap.size === 0) { + // The own map will be for projectCompilerOptions + Debug.assert(!moduleNameToDirectoryMap || moduleNameToDirectoryMap.redirectsMap.size === 0); + Debug.assert(directoryToModuleNameMap.getOwnMap().size === 0); + Debug.assert(!moduleNameToDirectoryMap || moduleNameToDirectoryMap.getOwnMap().size === 0); + directoryToModuleNameMap.redirectsMap.set(options.configFile.path, directoryToModuleNameMap.getOwnMap()); + moduleNameToDirectoryMap?.redirectsMap.set(options.configFile.path, moduleNameToDirectoryMap.getOwnMap()); + } + else { + // Set correct own map + Debug.assert(!moduleNameToDirectoryMap || moduleNameToDirectoryMap.redirectsMap.size > 0); + const ref: ResolvedProjectReference = { + sourceFile: options.configFile, + commandLine: { options } as ParsedCommandLine }; + directoryToModuleNameMap.setOwnMap(directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)); + moduleNameToDirectoryMap?.setOwnMap(moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)); + } + directoryToModuleNameMap.setOwnOptions(options); + moduleNameToDirectoryMap?.setOwnOptions(options); +} - function clear() { - preDirectoryResolutionCache.clear(); - moduleNameToDirectoryMap!.clear(); - packageJsonInfoCache.clear(); - } +function createPerDirectoryResolutionCache(currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, directoryToModuleNameMap: CacheWithRedirects>): PerDirectoryResolutionCache { + return { + getOrCreateCacheForDirectory, + clear, + update, + }; - function update(options: CompilerOptions) { - updateRedirectsMap(options, directoryToModuleNameMap!, moduleNameToDirectoryMap); - } + function clear() { + directoryToModuleNameMap.clear(); + } + + function update(options: CompilerOptions) { + updateRedirectsMap(options, directoryToModuleNameMap); + } - function getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, redirectedReference?: ResolvedProjectReference): PerModuleNameCache { - Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName)); - return getOrCreateCache(moduleNameToDirectoryMap!, redirectedReference, mode === undefined ? nonRelativeModuleName : `${mode}|${nonRelativeModuleName}`, createPerModuleNameCache); + function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) { + const path = toPath(directoryName, currentDirectory, getCanonicalFileName); + return getOrCreateCache>(directoryToModuleNameMap, redirectedReference, path, () => createModeAwareCache()); + } +} + +/* @internal */ +export function createModeAwareCache(): ModeAwareCache { + const underlying = new Map(); + const memoizedReverseKeys = new Map(); + + const cache: ModeAwareCache = { + get(specifier, mode) { + return underlying.get(getUnderlyingCacheKey(specifier, mode)); + }, + set(specifier, mode, value) { + underlying.set(getUnderlyingCacheKey(specifier, mode), value); + return cache; + }, + delete(specifier, mode) { + underlying.delete(getUnderlyingCacheKey(specifier, mode)); + return cache; + }, + has(specifier, mode) { + return underlying.has(getUnderlyingCacheKey(specifier, mode)); + }, + forEach(cb) { + return underlying.forEach((elem, key) => { + const [specifier, mode] = memoizedReverseKeys.get(key)!; + return cb(elem, specifier, mode); + }); + }, + size() { + return underlying.size; } + }; + return cache; - function createPerModuleNameCache(): PerModuleNameCache { - const directoryPathMap = new Map(); + function getUnderlyingCacheKey(specifier: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) { + const result = mode === undefined ? specifier : `${mode}|${specifier}`; + memoizedReverseKeys.set(result, [specifier, mode]); + return result; + } +} - return { get, set }; +/* @internal */ +export function zipToModeAwareCache(file: SourceFile, keys: readonly string[], values: readonly V[]): ModeAwareCache { + Debug.assert(keys.length === values.length); + const map = createModeAwareCache(); + for (let i = 0; i < keys.length; ++i) { + map.set(keys[i], getModeForResolutionAtIndex(file, i), values[i]); + } + return map; +} - function get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined { - return directoryPathMap.get(toPath(directory, currentDirectory, getCanonicalFileName)); - } +export function createModuleResolutionCache( + currentDirectory: string, + getCanonicalFileName: (s: string) => string, + options?: CompilerOptions +): ModuleResolutionCache; +/*@internal*/ +export function createModuleResolutionCache( + currentDirectory: string, + getCanonicalFileName: GetCanonicalFileName, + options: undefined, + directoryToModuleNameMap: CacheWithRedirects>, + moduleNameToDirectoryMap: CacheWithRedirects, +): ModuleResolutionCache; +export function createModuleResolutionCache( + currentDirectory: string, + getCanonicalFileName: GetCanonicalFileName, + options?: CompilerOptions, + directoryToModuleNameMap?: CacheWithRedirects>, + moduleNameToDirectoryMap?: CacheWithRedirects, +): ModuleResolutionCache { + const preDirectoryResolutionCache = createPerDirectoryResolutionCache(currentDirectory, getCanonicalFileName, directoryToModuleNameMap ||= createCacheWithRedirects(options)); + moduleNameToDirectoryMap ||= createCacheWithRedirects(options); + const packageJsonInfoCache = createPackageJsonInfoCache(currentDirectory, getCanonicalFileName); + + return { + ...packageJsonInfoCache, + ...preDirectoryResolutionCache, + getOrCreateCacheForModuleName, + clear, + update, + getPackageJsonInfoCache: () => packageJsonInfoCache, + }; + + function clear() { + preDirectoryResolutionCache.clear(); + moduleNameToDirectoryMap!.clear(); + packageJsonInfoCache.clear(); + } + + function update(options: CompilerOptions) { + updateRedirectsMap(options, directoryToModuleNameMap!, moduleNameToDirectoryMap); + } + + function getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, redirectedReference?: ResolvedProjectReference): PerModuleNameCache { + Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName)); + return getOrCreateCache(moduleNameToDirectoryMap!, redirectedReference, mode === undefined ? nonRelativeModuleName : `${mode}|${nonRelativeModuleName}`, createPerModuleNameCache); + } + + function createPerModuleNameCache(): PerModuleNameCache { + const directoryPathMap = new Map(); + + return { get, set }; + + function get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined { + return directoryPathMap.get(toPath(directory, currentDirectory, getCanonicalFileName)); + } - /** - * At first this function add entry directory -> module resolution result to the table. - * Then it computes the set of parent folders for 'directory' that should have the same module resolution result - * and for every parent folder in set it adds entry: parent -> module resolution. . - * Lets say we first directory name: /a/b/c/d/e and resolution result is: /a/b/bar.ts. - * Set of parent folders that should have the same result will be: - * [ - * /a/b/c/d, /a/b/c, /a/b - * ] - * this means that request for module resolution from file in any of these folder will be immediately found in cache. - */ - function set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void { - const path = toPath(directory, currentDirectory, getCanonicalFileName); - // if entry is already in cache do nothing - if (directoryPathMap.has(path)) { - return; - } - directoryPathMap.set(path, result); - - const resolvedFileName = result.resolvedModule && - (result.resolvedModule.originalPath || result.resolvedModule.resolvedFileName); - // find common prefix between directory and resolved file name - // this common prefix should be the shortest path that has the same resolution - // directory: /a/b/c/d/e - // resolvedFileName: /a/b/foo.d.ts - // commonPrefix: /a/b - // for failed lookups cache the result for every directory up to root - const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName); - let current = path; - while (current !== commonPrefix) { - const parent = getDirectoryPath(current); - if (parent === current || directoryPathMap.has(parent)) { - break; - } - directoryPathMap.set(parent, result); - current = parent; + /** + * At first this function add entry directory -> module resolution result to the table. + * Then it computes the set of parent folders for 'directory' that should have the same module resolution result + * and for every parent folder in set it adds entry: parent -> module resolution. . + * Lets say we first directory name: /a/b/c/d/e and resolution result is: /a/b/bar.ts. + * Set of parent folders that should have the same result will be: + * [ + * /a/b/c/d, /a/b/c, /a/b + * ] + * this means that request for module resolution from file in any of these folder will be immediately found in cache. + */ + function set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void { + const path = toPath(directory, currentDirectory, getCanonicalFileName); + // if entry is already in cache do nothing + if (directoryPathMap.has(path)) { + return; + } + directoryPathMap.set(path, result); + + const resolvedFileName = result.resolvedModule && + (result.resolvedModule.originalPath || result.resolvedModule.resolvedFileName); + // find common prefix between directory and resolved file name + // this common prefix should be the shortest path that has the same resolution + // directory: /a/b/c/d/e + // resolvedFileName: /a/b/foo.d.ts + // commonPrefix: /a/b + // for failed lookups cache the result for every directory up to root + const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName); + let current = path; + while (current !== commonPrefix) { + const parent = getDirectoryPath(current); + if (parent === current || directoryPathMap.has(parent)) { + break; } + directoryPathMap.set(parent, result); + current = parent; } + } - function getCommonPrefix(directory: Path, resolution: string) { - const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName); + function getCommonPrefix(directory: Path, resolution: string) { + const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName); - // find first position where directory and resolution differs - let i = 0; - const limit = Math.min(directory.length, resolutionDirectory.length); - while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) { - i++; - } - if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) { - return directory; - } - const rootLength = getRootLength(directory); - if (i < rootLength) { - return undefined; - } - const sep = directory.lastIndexOf(directorySeparator, i - 1); - if (sep === -1) { - return undefined; - } - return directory.substr(0, Math.max(sep, rootLength)); + // find first position where directory and resolution differs + let i = 0; + const limit = Math.min(directory.length, resolutionDirectory.length); + while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) { + i++; + } + if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) { + return directory; + } + const rootLength = getRootLength(directory); + if (i < rootLength) { + return undefined; } + const sep = directory.lastIndexOf(directorySeparator, i - 1); + if (sep === -1) { + return undefined; + } + return directory.substr(0, Math.max(sep, rootLength)); } } +} - export function createTypeReferenceDirectiveResolutionCache( - currentDirectory: string, - getCanonicalFileName: (s: string) => string, - options?: CompilerOptions, - packageJsonInfoCache?: PackageJsonInfoCache, - ): TypeReferenceDirectiveResolutionCache; - /*@internal*/ - export function createTypeReferenceDirectiveResolutionCache( - currentDirectory: string, - getCanonicalFileName: GetCanonicalFileName, - options: undefined, - packageJsonInfoCache: PackageJsonInfoCache | undefined, - directoryToModuleNameMap: CacheWithRedirects>, - ): TypeReferenceDirectiveResolutionCache; - export function createTypeReferenceDirectiveResolutionCache( - currentDirectory: string, - getCanonicalFileName: GetCanonicalFileName, - options?: CompilerOptions, - packageJsonInfoCache?: PackageJsonInfoCache | undefined, - directoryToModuleNameMap?: CacheWithRedirects>, - ): TypeReferenceDirectiveResolutionCache { - const preDirectoryResolutionCache = createPerDirectoryResolutionCache(currentDirectory, getCanonicalFileName, directoryToModuleNameMap ||= createCacheWithRedirects(options)); - packageJsonInfoCache ||= createPackageJsonInfoCache(currentDirectory, getCanonicalFileName); - - return { - ...packageJsonInfoCache, - ...preDirectoryResolutionCache, - clear, - }; - - function clear() { - preDirectoryResolutionCache.clear(); - packageJsonInfoCache!.clear(); - } +export function createTypeReferenceDirectiveResolutionCache( + currentDirectory: string, + getCanonicalFileName: (s: string) => string, + options?: CompilerOptions, + packageJsonInfoCache?: PackageJsonInfoCache, +): TypeReferenceDirectiveResolutionCache; +/*@internal*/ +export function createTypeReferenceDirectiveResolutionCache( + currentDirectory: string, + getCanonicalFileName: GetCanonicalFileName, + options: undefined, + packageJsonInfoCache: PackageJsonInfoCache | undefined, + directoryToModuleNameMap: CacheWithRedirects>, +): TypeReferenceDirectiveResolutionCache; +export function createTypeReferenceDirectiveResolutionCache( + currentDirectory: string, + getCanonicalFileName: GetCanonicalFileName, + options?: CompilerOptions, + packageJsonInfoCache?: PackageJsonInfoCache | undefined, + directoryToModuleNameMap?: CacheWithRedirects>, +): TypeReferenceDirectiveResolutionCache { + const preDirectoryResolutionCache = createPerDirectoryResolutionCache(currentDirectory, getCanonicalFileName, directoryToModuleNameMap ||= createCacheWithRedirects(options)); + packageJsonInfoCache ||= createPackageJsonInfoCache(currentDirectory, getCanonicalFileName); + + return { + ...packageJsonInfoCache, + ...preDirectoryResolutionCache, + clear, + }; + + function clear() { + preDirectoryResolutionCache.clear(); + packageJsonInfoCache!.clear(); } +} - export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache, mode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined { - const containingDirectory = getDirectoryPath(containingFile); - const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory); - if (!perFolderCache) return undefined; - return perFolderCache.get(moduleName, mode); - } +export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache, mode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined { + const containingDirectory = getDirectoryPath(containingFile); + const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory); + if (!perFolderCache) return undefined; + return perFolderCache.get(moduleName, mode); +} - export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(compilerOptions, host); +export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + if (redirectedReference) { + compilerOptions = redirectedReference.commandLine.options; + } + if (traceEnabled) { + trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile); if (redirectedReference) { - compilerOptions = redirectedReference.commandLine.options; - } - if (traceEnabled) { - trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile); - if (redirectedReference) { - trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); - } + trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); } - const containingDirectory = getDirectoryPath(containingFile); - const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference); - let result = perFolderCache && perFolderCache.get(moduleName, resolutionMode); + } + const containingDirectory = getDirectoryPath(containingFile); + const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference); + let result = perFolderCache && perFolderCache.get(moduleName, resolutionMode); - if (result) { - if (traceEnabled) { - trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); - } + if (result) { + if (traceEnabled) { + trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); } - else { - let moduleResolution = compilerOptions.moduleResolution; - if (moduleResolution === undefined) { - switch (getEmitModuleKind(compilerOptions)) { - case ModuleKind.CommonJS: - moduleResolution = ModuleResolutionKind.NodeJs; - break; - case ModuleKind.Node12: - moduleResolution = ModuleResolutionKind.Node12; - break; - case ModuleKind.NodeNext: - moduleResolution = ModuleResolutionKind.NodeNext; - break; - default: - moduleResolution = ModuleResolutionKind.Classic; - break; - } - if (traceEnabled) { - trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]); - } - } - else { - if (traceEnabled) { - trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]); - } - } - - perfLogger.logStartResolveModule(moduleName /* , containingFile, ModuleResolutionKind[moduleResolution]*/); - switch (moduleResolution) { - case ModuleResolutionKind.Node12: - result = node12ModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode); - break; - case ModuleResolutionKind.NodeNext: - result = nodeNextModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode); + } + else { + let moduleResolution = compilerOptions.moduleResolution; + if (moduleResolution === undefined) { + switch (getEmitModuleKind(compilerOptions)) { + case ModuleKind.CommonJS: + moduleResolution = ModuleResolutionKind.NodeJs; break; - case ModuleResolutionKind.NodeJs: - result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); + case ModuleKind.Node12: + moduleResolution = ModuleResolutionKind.Node12; break; - case ModuleResolutionKind.Classic: - result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); + case ModuleKind.NodeNext: + moduleResolution = ModuleResolutionKind.NodeNext; break; default: - return Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`); + moduleResolution = ModuleResolutionKind.Classic; + break; } - if (result && result.resolvedModule) perfLogger.logInfoEvent(`Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`); - perfLogger.logStopResolveModule((result && result.resolvedModule) ? "" + result.resolvedModule.resolvedFileName : "null"); - - if (perFolderCache) { - perFolderCache.set(moduleName, resolutionMode, result); - if (!isExternalModuleNameRelative(moduleName)) { - // put result in per-module name cache - cache!.getOrCreateCacheForModuleName(moduleName, resolutionMode, redirectedReference).set(containingDirectory, result); - } + if (traceEnabled) { + trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]); } } + else { + if (traceEnabled) { + trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]); + } + } + + perfLogger.logStartResolveModule(moduleName /* , containingFile, ModuleResolutionKind[moduleResolution]*/); + switch (moduleResolution) { + case ModuleResolutionKind.Node12: + result = node12ModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode); + break; + case ModuleResolutionKind.NodeNext: + result = nodeNextModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode); + break; + case ModuleResolutionKind.NodeJs: + result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); + break; + case ModuleResolutionKind.Classic: + result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); + break; + default: + return Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`); + } + if (result && result.resolvedModule) perfLogger.logInfoEvent(`Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`); + perfLogger.logStopResolveModule((result && result.resolvedModule) ? "" + result.resolvedModule.resolvedFileName : "null"); + + if (perFolderCache) { + perFolderCache.set(moduleName, resolutionMode, result); + if (!isExternalModuleNameRelative(moduleName)) { + // put result in per-module name cache + cache!.getOrCreateCacheForModuleName(moduleName, resolutionMode, redirectedReference).set(containingDirectory, result); + } + } + } - if (traceEnabled) { - if (result.resolvedModule) { - if (result.resolvedModule.packageId) { - trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1_with_Package_ID_2, moduleName, result.resolvedModule.resolvedFileName, packageIdToString(result.resolvedModule.packageId)); - } - else { - trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName); - } + if (traceEnabled) { + if (result.resolvedModule) { + if (result.resolvedModule.packageId) { + trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1_with_Package_ID_2, moduleName, result.resolvedModule.resolvedFileName, packageIdToString(result.resolvedModule.packageId)); } else { - trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName); + trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName); } } - - return result; + else { + trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName); + } } - /* - * Every module resolution kind can has its specific understanding how to load module from a specific path on disk - * I.e. for path '/a/b/c': - * - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails - * it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with - * 'typings' entry or file 'index' with some supported extension - * - Classic loader will only try to interpret '/a/b/c' as file. - */ - type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined; - - /** - * Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to - * mitigate differences between design time structure of the project and its runtime counterpart so the same import name - * can be resolved successfully by TypeScript compiler and runtime module loader. - * If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will - * fallback to standard resolution routine. - * - * - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative - * names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will - * be '/a/b/c/d' - * - paths - this setting can only be used when baseUrl is specified. allows to tune how non-relative module names - * will be resolved based on the content of the module name. - * Structure of 'paths' compiler options - * 'paths': { - * pattern-1: [...substitutions], - * pattern-2: [...substitutions], - * ... - * pattern-n: [...substitutions] - * } - * Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against - * all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case. - * If pattern contains '*' then to match pattern "*" module name must start with the and end with . - * denotes part of the module name between and . - * If module name can be matches with multiple patterns then pattern with the longest prefix will be picked. - * After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module - * from the candidate location. - * Substitution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every - * substitution in the list and replace '*' with string. If candidate location is not rooted it - * will be converted to absolute using baseUrl. - * For example: - * baseUrl: /a/b/c - * "paths": { - * // match all module names - * "*": [ - * "*", // use matched name as is, - * // will be looked as /a/b/c/ - * - * "folder1/*" // substitution will convert matched name to 'folder1/', - * // since it is not rooted then final candidate location will be /a/b/c/folder1/ - * ], - * // match module names that start with 'components/' - * "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/ to '/root/components/folder1/', - * // it is rooted so it will be final candidate location - * } - * - * 'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if - * they were in the same location. For example lets say there are two files - * '/local/src/content/file1.ts' - * '/shared/components/contracts/src/content/protocols/file2.ts' - * After bundling content of '/shared/components/contracts/src' will be merged with '/local/src' so - * if file1 has the following import 'import {x} from "./protocols/file2"' it will be resolved successfully in runtime. - * 'rootDirs' provides the way to tell compiler that in order to get the whole project it should behave as if content of all - * root dirs were merged together. - * I.e. for the example above 'rootDirs' will have two entries: [ '/local/src', '/shared/components/contracts/src' ]. - * Compiler will first convert './protocols/file2' into absolute path relative to the location of containing file: - * '/local/src/content/protocols/file2' and try to load it - failure. - * Then it will search 'rootDirs' looking for a longest matching prefix of this absolute path and if such prefix is found - absolute path will - * be converted to a path relative to found rootDir entry './content/protocols/file2' (*). As a last step compiler will check all remaining - * entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location. - */ - function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, - state: ModuleResolutionState): Resolved | undefined { - - const resolved = tryLoadModuleUsingPathsIfEligible(extensions, moduleName, loader, state); - if (resolved) return resolved.value; + return result; +} - if (!isExternalModuleNameRelative(moduleName)) { - return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, state); - } - else { - return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, state); - } +/* + * Every module resolution kind can has its specific understanding how to load module from a specific path on disk + * I.e. for path '/a/b/c': + * - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails + * it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with + * 'typings' entry or file 'index' with some supported extension + * - Classic loader will only try to interpret '/a/b/c' as file. + */ +type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined; + +/** + * Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to + * mitigate differences between design time structure of the project and its runtime counterpart so the same import name + * can be resolved successfully by TypeScript compiler and runtime module loader. + * If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will + * fallback to standard resolution routine. + * + * - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative + * names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will + * be '/a/b/c/d' + * - paths - this setting can only be used when baseUrl is specified. allows to tune how non-relative module names + * will be resolved based on the content of the module name. + * Structure of 'paths' compiler options + * 'paths': { + * pattern-1: [...substitutions], + * pattern-2: [...substitutions], + * ... + * pattern-n: [...substitutions] + * } + * Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against + * all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case. + * If pattern contains '*' then to match pattern "*" module name must start with the and end with . + * denotes part of the module name between and . + * If module name can be matches with multiple patterns then pattern with the longest prefix will be picked. + * After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module + * from the candidate location. + * Substitution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every + * substitution in the list and replace '*' with string. If candidate location is not rooted it + * will be converted to absolute using baseUrl. + * For example: + * baseUrl: /a/b/c + * "paths": { + * // match all module names + * "*": [ + * "*", // use matched name as is, + * // will be looked as /a/b/c/ + * + * "folder1/*" // substitution will convert matched name to 'folder1/', + * // since it is not rooted then final candidate location will be /a/b/c/folder1/ + * ], + * // match module names that start with 'components/' + * "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/ to '/root/components/folder1/', + * // it is rooted so it will be final candidate location + * } + * + * 'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if + * they were in the same location. For example lets say there are two files + * '/local/src/content/file1.ts' + * '/shared/components/contracts/src/content/protocols/file2.ts' + * After bundling content of '/shared/components/contracts/src' will be merged with '/local/src' so + * if file1 has the following import 'import {x} from "./protocols/file2"' it will be resolved successfully in runtime. + * 'rootDirs' provides the way to tell compiler that in order to get the whole project it should behave as if content of all + * root dirs were merged together. + * I.e. for the example above 'rootDirs' will have two entries: [ '/local/src', '/shared/components/contracts/src' ]. + * Compiler will first convert './protocols/file2' into absolute path relative to the location of containing file: + * '/local/src/content/protocols/file2' and try to load it - failure. + * Then it will search 'rootDirs' looking for a longest matching prefix of this absolute path and if such prefix is found - absolute path will + * be converted to a path relative to found rootDir entry './content/protocols/file2' (*). As a last step compiler will check all remaining + * entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location. + */ +function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, + state: ModuleResolutionState): Resolved | undefined { + + const resolved = tryLoadModuleUsingPathsIfEligible(extensions, moduleName, loader, state); + if (resolved) return resolved.value; + + if (!isExternalModuleNameRelative(moduleName)) { + return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, state); + } + else { + return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, state); } +} - function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) { - const { baseUrl, paths, configFile } = state.compilerOptions; - if (paths && !pathIsRelative(moduleName)) { - if (state.traceEnabled) { - if (baseUrl) { - trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); - } - trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); +function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) { + const { baseUrl, paths, configFile } = state.compilerOptions; + if (paths && !pathIsRelative(moduleName)) { + if (state.traceEnabled) { + if (baseUrl) { + trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); } - const baseDirectory = getPathsBasePath(state.compilerOptions, state.host)!; // Always defined when 'paths' is defined - const pathPatterns = configFile?.configFileSpecs ? configFile.configFileSpecs.pathPatterns ||= tryParsePatterns(paths) : undefined; - return tryLoadModuleUsingPaths(extensions, moduleName, baseDirectory, paths, pathPatterns, loader, /*onlyRecordFailures*/ false, state); + trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); } + const baseDirectory = getPathsBasePath(state.compilerOptions, state.host)!; // Always defined when 'paths' is defined + const pathPatterns = configFile?.configFileSpecs ? configFile.configFileSpecs.pathPatterns ||= tryParsePatterns(paths) : undefined; + return tryLoadModuleUsingPaths(extensions, moduleName, baseDirectory, paths, pathPatterns, loader, /*onlyRecordFailures*/ false, state); } +} - function tryLoadModuleUsingRootDirs(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, - state: ModuleResolutionState): Resolved | undefined { +function tryLoadModuleUsingRootDirs(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, + state: ModuleResolutionState): Resolved | undefined { - if (!state.compilerOptions.rootDirs) { - return undefined; + if (!state.compilerOptions.rootDirs) { + return undefined; + } + + if (state.traceEnabled) { + trace(state.host, Diagnostics.rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName); + } + + const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); + + let matchedRootDir: string | undefined; + let matchedNormalizedPrefix: string | undefined; + for (const rootDir of state.compilerOptions.rootDirs) { + // rootDirs are expected to be absolute + // in case of tsconfig.json this will happen automatically - compiler will expand relative names + // using location of tsconfig.json as base location + let normalizedRoot = normalizePath(rootDir); + if (!endsWith(normalizedRoot, directorySeparator)) { + normalizedRoot += directorySeparator; } + const isLongestMatchingPrefix = + startsWith(candidate, normalizedRoot) && + (matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length); if (state.traceEnabled) { - trace(state.host, Diagnostics.rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName); + trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix); } - const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); - - let matchedRootDir: string | undefined; - let matchedNormalizedPrefix: string | undefined; - for (const rootDir of state.compilerOptions.rootDirs) { - // rootDirs are expected to be absolute - // in case of tsconfig.json this will happen automatically - compiler will expand relative names - // using location of tsconfig.json as base location - let normalizedRoot = normalizePath(rootDir); - if (!endsWith(normalizedRoot, directorySeparator)) { - normalizedRoot += directorySeparator; - } - const isLongestMatchingPrefix = - startsWith(candidate, normalizedRoot) && - (matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length); + if (isLongestMatchingPrefix) { + matchedNormalizedPrefix = normalizedRoot; + matchedRootDir = rootDir; + } + } + if (matchedNormalizedPrefix) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Longest_matching_prefix_for_0_is_1, candidate, matchedNormalizedPrefix); + } + const suffix = candidate.substr(matchedNormalizedPrefix.length); - if (state.traceEnabled) { - trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix); - } + // first - try to load from a initial location + if (state.traceEnabled) { + trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate); + } + const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(containingDirectory, state.host), state); + if (resolvedFileName) { + return resolvedFileName; + } - if (isLongestMatchingPrefix) { - matchedNormalizedPrefix = normalizedRoot; - matchedRootDir = rootDir; - } + if (state.traceEnabled) { + trace(state.host, Diagnostics.Trying_other_entries_in_rootDirs); } - if (matchedNormalizedPrefix) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Longest_matching_prefix_for_0_is_1, candidate, matchedNormalizedPrefix); + // then try to resolve using remaining entries in rootDirs + for (const rootDir of state.compilerOptions.rootDirs) { + if (rootDir === matchedRootDir) { + // skip the initially matched entry + continue; } - const suffix = candidate.substr(matchedNormalizedPrefix.length); - - // first - try to load from a initial location + const candidate = combinePaths(normalizePath(rootDir), suffix); if (state.traceEnabled) { - trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate); + trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate); } - const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(containingDirectory, state.host), state); + const baseDirectory = getDirectoryPath(candidate); + const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(baseDirectory, state.host), state); if (resolvedFileName) { return resolvedFileName; } - - if (state.traceEnabled) { - trace(state.host, Diagnostics.Trying_other_entries_in_rootDirs); - } - // then try to resolve using remaining entries in rootDirs - for (const rootDir of state.compilerOptions.rootDirs) { - if (rootDir === matchedRootDir) { - // skip the initially matched entry - continue; - } - const candidate = combinePaths(normalizePath(rootDir), suffix); - if (state.traceEnabled) { - trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate); - } - const baseDirectory = getDirectoryPath(candidate); - const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(baseDirectory, state.host), state); - if (resolvedFileName) { - return resolvedFileName; - } - } - if (state.traceEnabled) { - trace(state.host, Diagnostics.Module_resolution_using_rootDirs_has_failed); - } } - return undefined; - } - - function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { - const { baseUrl } = state.compilerOptions; - if (!baseUrl) { - return undefined; - } - if (state.traceEnabled) { - trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); - } - const candidate = normalizePath(combinePaths(baseUrl, moduleName)); if (state.traceEnabled) { - trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, baseUrl, candidate); + trace(state.host, Diagnostics.Module_resolution_using_rootDirs_has_failed); } - return loader(extensions, candidate, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); } + return undefined; +} - /** - * Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations. - * No way to do this with `require()`: https://github.com/nodejs/node/issues/5963 - * Throws an error if the module can't be resolved. - */ - /* @internal */ - export function resolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string { - const { resolvedModule, failedLookupLocations } = tryResolveJSModuleWorker(moduleName, initialDir, host); - if (!resolvedModule) { - throw new Error(`Could not resolve JS module '${moduleName}' starting at '${initialDir}'. Looked in: ${failedLookupLocations.join(", ")}`); - } - return resolvedModule.resolvedFileName; - } - - /* @internal */ - export function tryResolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost) { - return tryResolveJSModuleWorker(moduleName, initialDir, host).resolvedModule; - } - - /* @internal */ - enum NodeResolutionFeatures { - None = 0, - // resolving `#local` names in your own package.json - Imports = 1 << 1, - // resolving `your-own-name` from your own package.json - SelfName = 1 << 2, - // respecting the `.exports` member of packages' package.json files and its (conditional) mappings of export names - Exports = 1 << 3, - // allowing `*` in the LHS of an export to be followed by more content, eg `"./whatever/*.js"` - // not currently backported to node 12 - https://github.com/nodejs/Release/issues/690 - ExportsPatternTrailers = 1 << 4, - AllFeatures = Imports | SelfName | Exports | ExportsPatternTrailers, - - EsmMode = 1 << 5, - } - - function node12ModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, - host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, - resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { - return nodeNextModuleNameResolverWorker( - NodeResolutionFeatures.Imports | NodeResolutionFeatures.SelfName | NodeResolutionFeatures.Exports, - moduleName, - containingFile, - compilerOptions, - host, - cache, - redirectedReference, - resolutionMode - ); - } - - function nodeNextModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, - host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, - resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { - return nodeNextModuleNameResolverWorker( - NodeResolutionFeatures.AllFeatures, - moduleName, - containingFile, - compilerOptions, - host, - cache, - redirectedReference, - resolutionMode - ); - } - - function nodeNextModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { - const containingDirectory = getDirectoryPath(containingFile); - - // es module file or cjs-like input file, use a variant of the legacy cjs resolver that supports the selected modern features - const esmMode = resolutionMode === ModuleKind.ESNext ? NodeResolutionFeatures.EsmMode : 0; - return nodeModuleNameResolverWorker(features | esmMode, moduleName, containingDirectory, compilerOptions, host, cache, compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions, redirectedReference); - } - - const jsOnlyExtensions = [Extensions.JavaScript]; - const tsExtensions = [Extensions.TypeScript, Extensions.JavaScript]; - const tsPlusJsonExtensions = [...tsExtensions, Extensions.Json]; - const tsconfigExtensions = [Extensions.TSConfig]; - function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { - return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, jsOnlyExtensions, /*redirectedReferences*/ undefined); - } - - export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; - /* @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures - export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations { - return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference); - } - - function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(compilerOptions, host); - - const failedLookupLocations: string[] = []; - // conditions are only used by the node12/nodenext resolver - there's no priority order in the list, - //it's essentially a set (priority is determined by object insertion order in the object we look at). - const state: ModuleResolutionState = { - compilerOptions, - host, - traceEnabled, - failedLookupLocations, - packageJsonInfoCache: cache, - features, - conditions: features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"] - }; +function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { + const { baseUrl } = state.compilerOptions; + if (!baseUrl) { + return undefined; + } + if (state.traceEnabled) { + trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); + } + const candidate = normalizePath(combinePaths(baseUrl, moduleName)); + if (state.traceEnabled) { + trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, baseUrl, candidate); + } + return loader(extensions, candidate, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); +} - const result = forEach(extensions, ext => tryResolve(ext)); - return createResolvedModuleWithFailedLookupLocations(result?.value?.resolved, result?.value?.isExternalLibraryImport, failedLookupLocations, state.resultFromCache); +/** + * Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations. + * No way to do this with `require()`: https://github.com/nodejs/node/issues/5963 + * Throws an error if the module can't be resolved. + */ +/* @internal */ +export function resolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string { + const { resolvedModule, failedLookupLocations } = tryResolveJSModuleWorker(moduleName, initialDir, host); + if (!resolvedModule) { + throw new Error(`Could not resolve JS module '${moduleName}' starting at '${initialDir}'. Looked in: ${failedLookupLocations.join(", ")}`); + } + return resolvedModule.resolvedFileName; +} - function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> { - const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true); - const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state); - if (resolved) { - return toSearchResult({ resolved, isExternalLibraryImport: pathContainsNodeModules(resolved.path) }); - } +/* @internal */ +export function tryResolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost) { + return tryResolveJSModuleWorker(moduleName, initialDir, host).resolvedModule; +} - if (!isExternalModuleNameRelative(moduleName)) { - let resolved: SearchResult | undefined; - if (features & NodeResolutionFeatures.Imports && startsWith(moduleName, "#")) { - resolved = loadModuleFromImports(extensions, moduleName, containingDirectory, state, cache, redirectedReference); - } - if (!resolved && features & NodeResolutionFeatures.SelfName) { - resolved = loadModuleFromSelfNameReference(extensions, moduleName, containingDirectory, state, cache, redirectedReference); - } - if (!resolved) { - if (traceEnabled) { - trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]); - } - resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference); - } - if (!resolved) return undefined; +/* @internal */ +enum NodeResolutionFeatures { + None = 0, + // resolving `#local` names in your own package.json + Imports = 1 << 1, + // resolving `your-own-name` from your own package.json + SelfName = 1 << 2, + // respecting the `.exports` member of packages' package.json files and its (conditional) mappings of export names + Exports = 1 << 3, + // allowing `*` in the LHS of an export to be followed by more content, eg `"./whatever/*.js"` + // not currently backported to node 12 - https://github.com/nodejs/Release/issues/690 + ExportsPatternTrailers = 1 << 4, + AllFeatures = Imports | SelfName | Exports | ExportsPatternTrailers, + + EsmMode = 1 << 5, +} - let resolvedValue = resolved.value; - if (!compilerOptions.preserveSymlinks && resolvedValue && !resolvedValue.originalPath) { - const path = realPath(resolvedValue.path, host, traceEnabled); - const originalPath = arePathsEqual(path, resolvedValue.path, host) ? undefined : resolvedValue.path; - resolvedValue = { ...resolvedValue, path, originalPath }; - } - // For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files. - return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } }; - } - else { - const { path: candidate, parts } = normalizePathAndParts(combinePaths(containingDirectory, moduleName)); - const resolved = nodeLoadModuleByRelativeName(extensions, candidate, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true); - // Treat explicit "node_modules" import as an external library import. - return resolved && toSearchResult({ resolved, isExternalLibraryImport: contains(parts, "node_modules") }); - } - } - } +function node12ModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, + host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, + resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { + return nodeNextModuleNameResolverWorker( + NodeResolutionFeatures.Imports | NodeResolutionFeatures.SelfName | NodeResolutionFeatures.Exports, + moduleName, + containingFile, + compilerOptions, + host, + cache, + redirectedReference, + resolutionMode + ); +} - function realPath(path: string, host: ModuleResolutionHost, traceEnabled: boolean): string { - if (!host.realpath) { - return path; - } +function nodeNextModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, + host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, + resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { + return nodeNextModuleNameResolverWorker( + NodeResolutionFeatures.AllFeatures, + moduleName, + containingFile, + compilerOptions, + host, + cache, + redirectedReference, + resolutionMode + ); +} - const real = normalizePath(host.realpath(path)); - if (traceEnabled) { - trace(host, Diagnostics.Resolving_real_path_for_0_result_1, path, real); - } - Debug.assert(host.fileExists(real), `${path} linked to nonexistent file ${real}`); - return real; - } +function nodeNextModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { + const containingDirectory = getDirectoryPath(containingFile); - function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]); + // es module file or cjs-like input file, use a variant of the legacy cjs resolver that supports the selected modern features + const esmMode = resolutionMode === ModuleKind.ESNext ? NodeResolutionFeatures.EsmMode : 0; + return nodeModuleNameResolverWorker(features | esmMode, moduleName, containingDirectory, compilerOptions, host, cache, compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions, redirectedReference); +} + +const jsOnlyExtensions = [Extensions.JavaScript]; +const tsExtensions = [Extensions.TypeScript, Extensions.JavaScript]; +const tsPlusJsonExtensions = [...tsExtensions, Extensions.Json]; +const tsconfigExtensions = [Extensions.TSConfig]; +function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { + return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, jsOnlyExtensions, /*redirectedReferences*/ undefined); +} + +export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; +/* @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures +export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations { + return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference); +} + +function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + + const failedLookupLocations: string[] = []; + // conditions are only used by the node12/nodenext resolver - there's no priority order in the list, + //it's essentially a set (priority is determined by object insertion order in the object we look at). + const state: ModuleResolutionState = { + compilerOptions, + host, + traceEnabled, + failedLookupLocations, + packageJsonInfoCache: cache, + features, + conditions: features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"] + }; + + const result = forEach(extensions, ext => tryResolve(ext)); + return createResolvedModuleWithFailedLookupLocations(result?.value?.resolved, result?.value?.isExternalLibraryImport, failedLookupLocations, state.resultFromCache); + + function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> { + const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true); + const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state); + if (resolved) { + return toSearchResult({ resolved, isExternalLibraryImport: pathContainsNodeModules(resolved.path) }); } - if (!hasTrailingDirectorySeparator(candidate)) { - if (!onlyRecordFailures) { - const parentOfCandidate = getDirectoryPath(candidate); - if (!directoryProbablyExists(parentOfCandidate, state.host)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, parentOfCandidate); - } - onlyRecordFailures = true; - } + + if (!isExternalModuleNameRelative(moduleName)) { + let resolved: SearchResult | undefined; + if (features & NodeResolutionFeatures.Imports && startsWith(moduleName, "#")) { + resolved = loadModuleFromImports(extensions, moduleName, containingDirectory, state, cache, redirectedReference); } - const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state); - if (resolvedFromFile) { - const packageDirectory = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile.path) : undefined; - const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined; - return withPackageId(packageInfo, resolvedFromFile); + if (!resolved && features & NodeResolutionFeatures.SelfName) { + resolved = loadModuleFromSelfNameReference(extensions, moduleName, containingDirectory, state, cache, redirectedReference); } - } - if (!onlyRecordFailures) { - const candidateExists = directoryProbablyExists(candidate, state.host); - if (!candidateExists) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidate); + if (!resolved) { + if (traceEnabled) { + trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]); } - onlyRecordFailures = true; + resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference); } - } - return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); - } + if (!resolved) return undefined; - /*@internal*/ - export const nodeModulesPathPart = "/node_modules/"; - /*@internal*/ - export function pathContainsNodeModules(path: string): boolean { - return stringContains(path, nodeModulesPathPart); - } - - /** - * This will be called on the successfully resolved path from `loadModuleFromFile`. - * (Not needed for `loadModuleFromNodeModules` as that looks up the `package.json` as part of resolution.) - * - * packageDirectory is the directory of the package itself. - * For `blah/node_modules/foo/index.d.ts` this is packageDirectory: "foo" - * For `/node_modules/foo/bar.d.ts` this is packageDirectory: "foo" - * For `/node_modules/@types/foo/bar/index.d.ts` this is packageDirectory: "@types/foo" - * For `/node_modules/foo/bar/index.d.ts` this is packageDirectory: "foo" - */ - /* @internal */ - export function parseNodeModuleFromPath(resolved: string): string | undefined { - const path = normalizePath(resolved); - const idx = path.lastIndexOf(nodeModulesPathPart); - if (idx === -1) { - return undefined; + let resolvedValue = resolved.value; + if (!compilerOptions.preserveSymlinks && resolvedValue && !resolvedValue.originalPath) { + const path = realPath(resolvedValue.path, host, traceEnabled); + const originalPath = arePathsEqual(path, resolvedValue.path, host) ? undefined : resolvedValue.path; + resolvedValue = { ...resolvedValue, path, originalPath }; + } + // For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files. + return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } }; } - - const indexAfterNodeModules = idx + nodeModulesPathPart.length; - let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules); - if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) { - indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); + else { + const { path: candidate, parts } = normalizePathAndParts(combinePaths(containingDirectory, moduleName)); + const resolved = nodeLoadModuleByRelativeName(extensions, candidate, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true); + // Treat explicit "node_modules" import as an external library import. + return resolved && toSearchResult({ resolved, isExternalLibraryImport: contains(parts, "node_modules") }); } - return path.slice(0, indexAfterPackageName); } +} - function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number): number { - const nextSeparatorIndex = path.indexOf(directorySeparator, prevSeparatorIndex + 1); - return nextSeparatorIndex === -1 ? prevSeparatorIndex : nextSeparatorIndex; +function realPath(path: string, host: ModuleResolutionHost, traceEnabled: boolean): string { + if (!host.realpath) { + return path; } - function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined { - return noPackageId(loadModuleFromFile(extensions, candidate, onlyRecordFailures, state)); + const real = normalizePath(host.realpath(path)); + if (traceEnabled) { + trace(host, Diagnostics.Resolving_real_path_for_0_result_1, path, real); } + Debug.assert(host.fileExists(real), `${path} linked to nonexistent file ${real}`); + return real; +} - /** - * @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary - * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. - */ - function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { - if (extensions === Extensions.Json || extensions === Extensions.TSConfig) { - const extensionLess = tryRemoveExtension(candidate, Extension.Json); - const extension = extensionLess ? candidate.substring(extensionLess.length) : ""; - return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, extension, onlyRecordFailures, state); - } - - // esm mode resolutions don't include automatic extension lookup (without additional flags, at least) - if (!(state.features & NodeResolutionFeatures.EsmMode)) { - // First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts" - const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, "", onlyRecordFailures, state); - if (resolvedByAddingExtension) { - return resolvedByAddingExtension; +function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]); + } + if (!hasTrailingDirectorySeparator(candidate)) { + if (!onlyRecordFailures) { + const parentOfCandidate = getDirectoryPath(candidate); + if (!directoryProbablyExists(parentOfCandidate, state.host)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, parentOfCandidate); + } + onlyRecordFailures = true; } } - - return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state); + const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state); + if (resolvedFromFile) { + const packageDirectory = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile.path) : undefined; + const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined; + return withPackageId(packageInfo, resolvedFromFile); + } } - - function loadModuleFromFileNoImplicitExtensions(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { - // If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one; - // e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" - if (hasJSFileExtension(candidate) || (fileExtensionIs(candidate, Extension.Json) && state.compilerOptions.resolveJsonModule)) { - const extensionless = removeFileExtension(candidate); - const extension = candidate.substring(extensionless.length); + if (!onlyRecordFailures) { + const candidateExists = directoryProbablyExists(candidate, state.host); + if (!candidateExists) { if (state.traceEnabled) { - trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension); + trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidate); } - return tryAddingExtensions(extensionless, extensions, extension, onlyRecordFailures, state); + onlyRecordFailures = true; } } + return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); +} - function loadJSOrExactTSFileName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { - if ((extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) && fileExtensionIsOneOf(candidate, [Extension.Dts, Extension.Dcts, Extension.Dmts])) { - const result = tryFile(candidate, onlyRecordFailures, state); - return result !== undefined ? { path: candidate, ext: forEach([Extension.Dts, Extension.Dcts, Extension.Dmts], e => fileExtensionIs(candidate, e) ? e : undefined)! } : undefined; - } +/*@internal*/ +export const nodeModulesPathPart = "/node_modules/"; +/*@internal*/ +export function pathContainsNodeModules(path: string): boolean { + return stringContains(path, nodeModulesPathPart); +} - return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state); +/** + * This will be called on the successfully resolved path from `loadModuleFromFile`. + * (Not needed for `loadModuleFromNodeModules` as that looks up the `package.json` as part of resolution.) + * + * packageDirectory is the directory of the package itself. + * For `blah/node_modules/foo/index.d.ts` this is packageDirectory: "foo" + * For `/node_modules/foo/bar.d.ts` this is packageDirectory: "foo" + * For `/node_modules/@types/foo/bar/index.d.ts` this is packageDirectory: "@types/foo" + * For `/node_modules/foo/bar/index.d.ts` this is packageDirectory: "foo" + */ +/* @internal */ +export function parseNodeModuleFromPath(resolved: string): string | undefined { + const path = normalizePath(resolved); + const idx = path.lastIndexOf(nodeModulesPathPart); + if (idx === -1) { + return undefined; } - /** Try to return an existing file that adds one of the `extensions` to `candidate`. */ - function tryAddingExtensions(candidate: string, extensions: Extensions, originalExtension: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { - if (!onlyRecordFailures) { - // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing - const directory = getDirectoryPath(candidate); - if (directory) { - onlyRecordFailures = !directoryProbablyExists(directory, state.host); - } - } + const indexAfterNodeModules = idx + nodeModulesPathPart.length; + let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules); + if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) { + indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); + } + return path.slice(0, indexAfterPackageName); +} - switch (extensions) { - case Extensions.DtsOnly: - switch (originalExtension) { - case Extension.Mjs: - case Extension.Mts: - case Extension.Dmts: - return tryExtension(Extension.Dmts); - case Extension.Cjs: - case Extension.Cts: - case Extension.Dcts: - return tryExtension(Extension.Dcts); - case Extension.Json: - candidate += Extension.Json; - return tryExtension(Extension.Dts); - default: return tryExtension(Extension.Dts); - } - case Extensions.TypeScript: - switch (originalExtension) { - case Extension.Mjs: - case Extension.Mts: - case Extension.Dmts: - return tryExtension(Extension.Mts) || tryExtension(Extension.Dmts); - case Extension.Cjs: - case Extension.Cts: - case Extension.Dcts: - return tryExtension(Extension.Cts) || tryExtension(Extension.Dcts); - case Extension.Json: - candidate += Extension.Json; - return tryExtension(Extension.Dts); - default: - return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts); - } - case Extensions.JavaScript: - switch (originalExtension) { - case Extension.Mjs: - case Extension.Mts: - case Extension.Dmts: - return tryExtension(Extension.Mjs); - case Extension.Cjs: - case Extension.Cts: - case Extension.Dcts: - return tryExtension(Extension.Cjs); - case Extension.Json: - return tryExtension(Extension.Json); - default: - return tryExtension(Extension.Js) || tryExtension(Extension.Jsx); - } - case Extensions.TSConfig: - case Extensions.Json: - return tryExtension(Extension.Json); - } +function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number): number { + const nextSeparatorIndex = path.indexOf(directorySeparator, prevSeparatorIndex + 1); + return nextSeparatorIndex === -1 ? prevSeparatorIndex : nextSeparatorIndex; +} - function tryExtension(ext: Extension): PathAndExtension | undefined { - const path = tryFile(candidate + ext, onlyRecordFailures, state); - return path === undefined ? undefined : { path, ext }; - } +function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined { + return noPackageId(loadModuleFromFile(extensions, candidate, onlyRecordFailures, state)); +} + +/** + * @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary + * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. + */ +function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { + if (extensions === Extensions.Json || extensions === Extensions.TSConfig) { + const extensionLess = tryRemoveExtension(candidate, Extension.Json); + const extension = extensionLess ? candidate.substring(extensionLess.length) : ""; + return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, extension, onlyRecordFailures, state); } - /** Return the file if it exists. */ - function tryFile(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { - if (!onlyRecordFailures) { - if (state.host.fileExists(fileName)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName); - } - return fileName; - } - else { - if (state.traceEnabled) { - trace(state.host, Diagnostics.File_0_does_not_exist, fileName); - } - } + // esm mode resolutions don't include automatic extension lookup (without additional flags, at least) + if (!(state.features & NodeResolutionFeatures.EsmMode)) { + // First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts" + const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, "", onlyRecordFailures, state); + if (resolvedByAddingExtension) { + return resolvedByAddingExtension; } - state.failedLookupLocations.push(fileName); - return undefined; } - function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) { - const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, onlyRecordFailures, state) : undefined; - const packageJsonContent = packageInfo && packageInfo.packageJsonContent; - const versionPaths = packageInfo && packageInfo.versionPaths; - return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); + return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state); +} + +function loadModuleFromFileNoImplicitExtensions(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { + // If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one; + // e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" + if (hasJSFileExtension(candidate) || (fileExtensionIs(candidate, Extension.Json) && state.compilerOptions.resolveJsonModule)) { + const extensionless = removeFileExtension(candidate); + const extension = candidate.substring(extensionless.length); + if (state.traceEnabled) { + trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension); + } + return tryAddingExtensions(extensionless, extensions, extension, onlyRecordFailures, state); } +} - /*@internal*/ - interface PackageJsonInfo { - packageDirectory: string; - packageJsonContent: PackageJsonPathFields; - versionPaths: VersionPaths | undefined; +function loadJSOrExactTSFileName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { + if ((extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) && fileExtensionIsOneOf(candidate, [Extension.Dts, Extension.Dcts, Extension.Dmts])) { + const result = tryFile(candidate, onlyRecordFailures, state); + return result !== undefined ? { path: candidate, ext: forEach([Extension.Dts, Extension.Dcts, Extension.Dmts], e => fileExtensionIs(candidate, e) ? e : undefined)! } : undefined; } - /** - * A function for locating the package.json scope for a given path - */ - /*@internal*/ - export function getPackageScopeForPath(fileName: Path, packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): PackageJsonInfo | undefined { - const state: { - host: ModuleResolutionHost; - compilerOptions: CompilerOptions; - traceEnabled: boolean; - failedLookupLocations: Push; - resultFromCache?: ResolvedModuleWithFailedLookupLocations; - packageJsonInfoCache: PackageJsonInfoCache | undefined; - features: number; - conditions: never[]; - } = { - host, - compilerOptions: options, - traceEnabled: isTraceEnabled(options, host), - failedLookupLocations: [], - packageJsonInfoCache, - features: 0, - conditions: [], - }; - const parts = getPathComponents(fileName); - parts.pop(); - while (parts.length > 0) { - const pkg = getPackageJsonInfo(getPathFromPathComponents(parts), /*onlyRecordFailures*/ false, state); - if (pkg) { - return pkg; + return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state); +} + +/** Try to return an existing file that adds one of the `extensions` to `candidate`. */ +function tryAddingExtensions(candidate: string, extensions: Extensions, originalExtension: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { + if (!onlyRecordFailures) { + // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing + const directory = getDirectoryPath(candidate); + if (directory) { + onlyRecordFailures = !directoryProbablyExists(directory, state.host); + } + } + + switch (extensions) { + case Extensions.DtsOnly: + switch (originalExtension) { + case Extension.Mjs: + case Extension.Mts: + case Extension.Dmts: + return tryExtension(Extension.Dmts); + case Extension.Cjs: + case Extension.Cts: + case Extension.Dcts: + return tryExtension(Extension.Dcts); + case Extension.Json: + candidate += Extension.Json; + return tryExtension(Extension.Dts); + default: return tryExtension(Extension.Dts); + } + case Extensions.TypeScript: + switch (originalExtension) { + case Extension.Mjs: + case Extension.Mts: + case Extension.Dmts: + return tryExtension(Extension.Mts) || tryExtension(Extension.Dmts); + case Extension.Cjs: + case Extension.Cts: + case Extension.Dcts: + return tryExtension(Extension.Cts) || tryExtension(Extension.Dcts); + case Extension.Json: + candidate += Extension.Json; + return tryExtension(Extension.Dts); + default: + return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts); + } + case Extensions.JavaScript: + switch (originalExtension) { + case Extension.Mjs: + case Extension.Mts: + case Extension.Dmts: + return tryExtension(Extension.Mjs); + case Extension.Cjs: + case Extension.Cts: + case Extension.Dcts: + return tryExtension(Extension.Cjs); + case Extension.Json: + return tryExtension(Extension.Json); + default: + return tryExtension(Extension.Js) || tryExtension(Extension.Jsx); } - parts.pop(); - } - return undefined; + case Extensions.TSConfig: + case Extensions.Json: + return tryExtension(Extension.Json); } - /*@internal*/ - export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { - const { host, traceEnabled } = state; - const packageJsonPath = combinePaths(packageDirectory, "package.json"); - if (onlyRecordFailures) { - state.failedLookupLocations.push(packageJsonPath); - return undefined; - } + function tryExtension(ext: Extension): PathAndExtension | undefined { + const path = tryFile(candidate + ext, onlyRecordFailures, state); + return path === undefined ? undefined : { path, ext }; + } +} - const existing = state.packageJsonInfoCache?.getPackageJsonInfo(packageJsonPath); - if (existing !== undefined) { - if (typeof existing !== "boolean") { - if (traceEnabled) trace(host, Diagnostics.File_0_exists_according_to_earlier_cached_lookups, packageJsonPath); - return existing; - } - else { - if (existing && traceEnabled) trace(host, Diagnostics.File_0_does_not_exist_according_to_earlier_cached_lookups, packageJsonPath); - state.failedLookupLocations.push(packageJsonPath); - return undefined; - } - } - const directoryExists = directoryProbablyExists(packageDirectory, host); - if (directoryExists && host.fileExists(packageJsonPath)) { - const packageJsonContent = readJson(packageJsonPath, host) as PackageJson; - if (traceEnabled) { - trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); +/** Return the file if it exists. */ +function tryFile(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { + if (!onlyRecordFailures) { + if (state.host.fileExists(fileName)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName); } - const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state); - const result = { packageDirectory, packageJsonContent, versionPaths }; - state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, result); - return result; + return fileName; } else { - if (directoryExists && traceEnabled) { - trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath); + if (state.traceEnabled) { + trace(state.host, Diagnostics.File_0_does_not_exist, fileName); } - state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, directoryExists); - // record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results - state.failedLookupLocations.push(packageJsonPath); } } + state.failedLookupLocations.push(fileName); + return undefined; +} - function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, jsonContent: PackageJsonPathFields | undefined, versionPaths: VersionPaths | undefined): PathAndExtension | undefined { - let packageFile: string | undefined; - if (jsonContent) { - switch (extensions) { - case Extensions.JavaScript: - case Extensions.Json: - packageFile = readPackageJsonMainField(jsonContent, candidate, state); - break; - case Extensions.TypeScript: - // When resolving typescript modules, try resolving using main field as well - packageFile = readPackageJsonTypesFields(jsonContent, candidate, state) || readPackageJsonMainField(jsonContent, candidate, state); - break; - case Extensions.DtsOnly: - packageFile = readPackageJsonTypesFields(jsonContent, candidate, state); - break; - case Extensions.TSConfig: - packageFile = readPackageJsonTSConfigField(jsonContent, candidate, state); - break; - default: - return Debug.assertNever(extensions); - } - } - - const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { - const fromFile = tryFile(candidate, onlyRecordFailures, state); - if (fromFile) { - const resolved = resolvedIfExtensionMatches(extensions, fromFile); - if (resolved) { - return noPackageId(resolved); - } - if (state.traceEnabled) { - trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile); - } - } - - // Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types" - const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions; - // Don't do package.json lookup recursively, because Node.js' package lookup doesn't. - return nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); - }; +function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) { + const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, onlyRecordFailures, state) : undefined; + const packageJsonContent = packageInfo && packageInfo.packageJsonContent; + const versionPaths = packageInfo && packageInfo.versionPaths; + return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); +} - const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined; - const onlyRecordFailuresForIndex = onlyRecordFailures || !directoryProbablyExists(candidate, state.host); - const indexPath = combinePaths(candidate, extensions === Extensions.TSConfig ? "tsconfig" : "index"); +/*@internal*/ +interface PackageJsonInfo { + packageDirectory: string; + packageJsonContent: PackageJsonPathFields; + versionPaths: VersionPaths | undefined; +} - if (versionPaths && (!packageFile || containsPath(candidate, packageFile))) { - const moduleName = getRelativePathFromDirectory(candidate, packageFile || indexPath, /*ignoreCase*/ false); - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, versionPaths.version, version, moduleName); - } - const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, /*pathPatterns*/ undefined, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state); - if (result) { - return removeIgnoredPackageId(result.value); - } +/** + * A function for locating the package.json scope for a given path + */ +/*@internal*/ + export function getPackageScopeForPath(fileName: Path, packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): PackageJsonInfo | undefined { + const state: { + host: ModuleResolutionHost; + compilerOptions: CompilerOptions; + traceEnabled: boolean; + failedLookupLocations: Push; + resultFromCache?: ResolvedModuleWithFailedLookupLocations; + packageJsonInfoCache: PackageJsonInfoCache | undefined; + features: number; + conditions: never[]; + } = { + host, + compilerOptions: options, + traceEnabled: isTraceEnabled(options, host), + failedLookupLocations: [], + packageJsonInfoCache, + features: 0, + conditions: [], + }; + const parts = getPathComponents(fileName); + parts.pop(); + while (parts.length > 0) { + const pkg = getPackageJsonInfo(getPathFromPathComponents(parts), /*onlyRecordFailures*/ false, state); + if (pkg) { + return pkg; } + parts.pop(); + } + return undefined; +} - // It won't have a `packageId` set, because we disabled `considerPackageJson`. - const packageFileResult = packageFile && removeIgnoredPackageId(loader(extensions, packageFile, onlyRecordFailuresForPackageFile!, state)); - if (packageFileResult) return packageFileResult; +/*@internal*/ +export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { + const { host, traceEnabled } = state; + const packageJsonPath = combinePaths(packageDirectory, "package.json"); + if (onlyRecordFailures) { + state.failedLookupLocations.push(packageJsonPath); + return undefined; + } - // esm mode resolutions don't do package `index` lookups - if (!(state.features & NodeResolutionFeatures.EsmMode)) { - return loadModuleFromFile(extensions, indexPath, onlyRecordFailuresForIndex, state); + const existing = state.packageJsonInfoCache?.getPackageJsonInfo(packageJsonPath); + if (existing !== undefined) { + if (typeof existing !== "boolean") { + if (traceEnabled) trace(host, Diagnostics.File_0_exists_according_to_earlier_cached_lookups, packageJsonPath); + return existing; + } + else { + if (existing && traceEnabled) trace(host, Diagnostics.File_0_does_not_exist_according_to_earlier_cached_lookups, packageJsonPath); + state.failedLookupLocations.push(packageJsonPath); + return undefined; } } - - /** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */ - function resolvedIfExtensionMatches(extensions: Extensions, path: string): PathAndExtension | undefined { - const ext = tryGetExtensionFromPath(path); - return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext } : undefined; + const directoryExists = directoryProbablyExists(packageDirectory, host); + if (directoryExists && host.fileExists(packageJsonPath)) { + const packageJsonContent = readJson(packageJsonPath, host) as PackageJson; + if (traceEnabled) { + trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); + } + const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state); + const result = { packageDirectory, packageJsonContent, versionPaths }; + state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, result); + return result; + } + else { + if (directoryExists && traceEnabled) { + trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath); + } + state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, directoryExists); + // record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results + state.failedLookupLocations.push(packageJsonPath); } +} - /** True if `extension` is one of the supported `extensions`. */ - function extensionIsOk(extensions: Extensions, extension: Extension): boolean { +function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, jsonContent: PackageJsonPathFields | undefined, versionPaths: VersionPaths | undefined): PathAndExtension | undefined { + let packageFile: string | undefined; + if (jsonContent) { switch (extensions) { case Extensions.JavaScript: - return extension === Extension.Js || extension === Extension.Jsx; - case Extensions.TSConfig: case Extensions.Json: - return extension === Extension.Json; + packageFile = readPackageJsonMainField(jsonContent, candidate, state); + break; case Extensions.TypeScript: - return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Dts; + // When resolving typescript modules, try resolving using main field as well + packageFile = readPackageJsonTypesFields(jsonContent, candidate, state) || readPackageJsonMainField(jsonContent, candidate, state); + break; case Extensions.DtsOnly: - return extension === Extension.Dts; + packageFile = readPackageJsonTypesFields(jsonContent, candidate, state); + break; + case Extensions.TSConfig: + packageFile = readPackageJsonTSConfigField(jsonContent, candidate, state); + break; + default: + return Debug.assertNever(extensions); } } - /* @internal */ - export function parsePackageName(moduleName: string): { packageName: string, rest: string } { - let idx = moduleName.indexOf(directorySeparator); - if (moduleName[0] === "@") { - idx = moduleName.indexOf(directorySeparator, idx + 1); + const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { + const fromFile = tryFile(candidate, onlyRecordFailures, state); + if (fromFile) { + const resolved = resolvedIfExtensionMatches(extensions, fromFile); + if (resolved) { + return noPackageId(resolved); + } + if (state.traceEnabled) { + trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile); + } + } + + // Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types" + const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions; + // Don't do package.json lookup recursively, because Node.js' package lookup doesn't. + return nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); + }; + + const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined; + const onlyRecordFailuresForIndex = onlyRecordFailures || !directoryProbablyExists(candidate, state.host); + const indexPath = combinePaths(candidate, extensions === Extensions.TSConfig ? "tsconfig" : "index"); + + if (versionPaths && (!packageFile || containsPath(candidate, packageFile))) { + const moduleName = getRelativePathFromDirectory(candidate, packageFile || indexPath, /*ignoreCase*/ false); + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, versionPaths.version, version, moduleName); + } + const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, /*pathPatterns*/ undefined, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state); + if (result) { + return removeIgnoredPackageId(result.value); } - return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) }; } - /* @internal */ - export function allKeysStartWithDot(obj: MapLike) { - return every(getOwnKeys(obj), k => startsWith(k, ".")); + // It won't have a `packageId` set, because we disabled `considerPackageJson`. + const packageFileResult = packageFile && removeIgnoredPackageId(loader(extensions, packageFile, onlyRecordFailuresForPackageFile!, state)); + if (packageFileResult) return packageFileResult; + + // esm mode resolutions don't do package `index` lookups + if (!(state.features & NodeResolutionFeatures.EsmMode)) { + return loadModuleFromFile(extensions, indexPath, onlyRecordFailuresForIndex, state); } +} + +/** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */ +function resolvedIfExtensionMatches(extensions: Extensions, path: string): PathAndExtension | undefined { + const ext = tryGetExtensionFromPath(path); + return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext } : undefined; +} - function noKeyStartsWithDot(obj: MapLike) { - return !some(getOwnKeys(obj), k => startsWith(k, ".")); +/** True if `extension` is one of the supported `extensions`. */ +function extensionIsOk(extensions: Extensions, extension: Extension): boolean { + switch (extensions) { + case Extensions.JavaScript: + return extension === Extension.Js || extension === Extension.Jsx; + case Extensions.TSConfig: + case Extensions.Json: + return extension === Extension.Json; + case Extensions.TypeScript: + return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Dts; + case Extensions.DtsOnly: + return extension === Extension.Dts; } +} - function loadModuleFromSelfNameReference(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - const useCaseSensitiveFileNames = typeof state.host.useCaseSensitiveFileNames === "function" ? state.host.useCaseSensitiveFileNames() : state.host.useCaseSensitiveFileNames; - const directoryPath = toPath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.(), createGetCanonicalFileName(useCaseSensitiveFileNames === undefined ? true : useCaseSensitiveFileNames)); - const scope = getPackageScopeForPath(directoryPath, state.packageJsonInfoCache, state.host, state.compilerOptions); - if (!scope || !scope.packageJsonContent.exports) { - return undefined; - } - if (typeof scope.packageJsonContent.name !== "string") { - return undefined; - } - const parts = getPathComponents(moduleName); // unrooted paths should have `""` as their 0th entry - const nameParts = getPathComponents(scope.packageJsonContent.name); - if (!every(nameParts, (p, i) => parts[i] === p)) { - return undefined; - } - const trailingParts = parts.slice(nameParts.length); - return loadModuleFromExports(scope, extensions, !length(trailingParts) ? "." : `.${directorySeparator}${trailingParts.join(directorySeparator)}`, state, cache, redirectedReference); +/* @internal */ +export function parsePackageName(moduleName: string): { packageName: string, rest: string } { + let idx = moduleName.indexOf(directorySeparator); + if (moduleName[0] === "@") { + idx = moduleName.indexOf(directorySeparator, idx + 1); } + return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) }; +} - function loadModuleFromExports(scope: PackageJsonInfo, extensions: Extensions, subpath: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - if (!scope.packageJsonContent.exports) { - return undefined; - } +/* @internal */ +export function allKeysStartWithDot(obj: MapLike) { + return every(getOwnKeys(obj), k => startsWith(k, ".")); +} - if (subpath === ".") { - let mainExport; - if (typeof scope.packageJsonContent.exports === "string" || Array.isArray(scope.packageJsonContent.exports) || (typeof scope.packageJsonContent.exports === "object" && noKeyStartsWithDot(scope.packageJsonContent.exports as MapLike))) { - mainExport = scope.packageJsonContent.exports; - } - else if (hasProperty(scope.packageJsonContent.exports as MapLike, ".")) { - mainExport = (scope.packageJsonContent.exports as MapLike)["."]; - } - if (mainExport) { - const loadModuleFromTargetImportOrExport = getLoadModuleFromTargetImportOrExport(extensions, state, cache, redirectedReference, subpath, scope, /*isImports*/ false); - return loadModuleFromTargetImportOrExport(mainExport, "", /*pattern*/ false); - } - } - else if (allKeysStartWithDot(scope.packageJsonContent.exports as MapLike)) { - if (typeof scope.packageJsonContent.exports !== "object") { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1, subpath, scope.packageDirectory); - } - return toSearchResult(/*value*/ undefined); - } - const result = loadModuleFromImportsOrExports(extensions, state, cache, redirectedReference, subpath, scope.packageJsonContent.exports, scope, /*isImports*/ false); - if (result) { - return result; - } - } +function noKeyStartsWithDot(obj: MapLike) { + return !some(getOwnKeys(obj), k => startsWith(k, ".")); +} - if (state.traceEnabled) { - trace(state.host, Diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1, subpath, scope.packageDirectory); - } - return toSearchResult(/*value*/ undefined); +function loadModuleFromSelfNameReference(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + const useCaseSensitiveFileNames = typeof state.host.useCaseSensitiveFileNames === "function" ? state.host.useCaseSensitiveFileNames() : state.host.useCaseSensitiveFileNames; + const directoryPath = toPath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.(), createGetCanonicalFileName(useCaseSensitiveFileNames === undefined ? true : useCaseSensitiveFileNames)); + const scope = getPackageScopeForPath(directoryPath, state.packageJsonInfoCache, state.host, state.compilerOptions); + if (!scope || !scope.packageJsonContent.exports) { + return undefined; + } + if (typeof scope.packageJsonContent.name !== "string") { + return undefined; + } + const parts = getPathComponents(moduleName); // unrooted paths should have `""` as their 0th entry + const nameParts = getPathComponents(scope.packageJsonContent.name); + if (!every(nameParts, (p, i) => parts[i] === p)) { + return undefined; } + const trailingParts = parts.slice(nameParts.length); + return loadModuleFromExports(scope, extensions, !length(trailingParts) ? "." : `.${directorySeparator}${trailingParts.join(directorySeparator)}`, state, cache, redirectedReference); +} - function loadModuleFromImports(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - if (moduleName === "#" || startsWith(moduleName, "#/")) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Invalid_import_specifier_0_has_no_possible_resolutions, moduleName); - } - return toSearchResult(/*value*/ undefined); +function loadModuleFromExports(scope: PackageJsonInfo, extensions: Extensions, subpath: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + if (!scope.packageJsonContent.exports) { + return undefined; + } + + if (subpath === ".") { + let mainExport; + if (typeof scope.packageJsonContent.exports === "string" || Array.isArray(scope.packageJsonContent.exports) || (typeof scope.packageJsonContent.exports === "object" && noKeyStartsWithDot(scope.packageJsonContent.exports as MapLike))) { + mainExport = scope.packageJsonContent.exports; } - const useCaseSensitiveFileNames = typeof state.host.useCaseSensitiveFileNames === "function" ? state.host.useCaseSensitiveFileNames() : state.host.useCaseSensitiveFileNames; - const directoryPath = toPath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.(), createGetCanonicalFileName(useCaseSensitiveFileNames === undefined ? true : useCaseSensitiveFileNames)); - const scope = getPackageScopeForPath(directoryPath, state.packageJsonInfoCache, state.host, state.compilerOptions); - if (!scope) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_has_no_containing_package_json_scope_Imports_will_not_resolve, directoryPath); - } - return toSearchResult(/*value*/ undefined); + else if (hasProperty(scope.packageJsonContent.exports as MapLike, ".")) { + mainExport = (scope.packageJsonContent.exports as MapLike)["."]; } - if (!scope.packageJsonContent.imports) { + if (mainExport) { + const loadModuleFromTargetImportOrExport = getLoadModuleFromTargetImportOrExport(extensions, state, cache, redirectedReference, subpath, scope, /*isImports*/ false); + return loadModuleFromTargetImportOrExport(mainExport, "", /*pattern*/ false); + } + } + else if (allKeysStartWithDot(scope.packageJsonContent.exports as MapLike)) { + if (typeof scope.packageJsonContent.exports !== "object") { if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_no_imports_defined, scope.packageDirectory); + trace(state.host, Diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1, subpath, scope.packageDirectory); } return toSearchResult(/*value*/ undefined); } - - const result = loadModuleFromImportsOrExports(extensions, state, cache, redirectedReference, moduleName, scope.packageJsonContent.imports, scope, /*isImports*/ true); + const result = loadModuleFromImportsOrExports(extensions, state, cache, redirectedReference, subpath, scope.packageJsonContent.exports, scope, /*isImports*/ false); if (result) { return result; } + } + + if (state.traceEnabled) { + trace(state.host, Diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1, subpath, scope.packageDirectory); + } + return toSearchResult(/*value*/ undefined); +} +function loadModuleFromImports(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + if (moduleName === "#" || startsWith(moduleName, "#/")) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Invalid_import_specifier_0_has_no_possible_resolutions, moduleName); + } + return toSearchResult(/*value*/ undefined); + } + const useCaseSensitiveFileNames = typeof state.host.useCaseSensitiveFileNames === "function" ? state.host.useCaseSensitiveFileNames() : state.host.useCaseSensitiveFileNames; + const directoryPath = toPath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.(), createGetCanonicalFileName(useCaseSensitiveFileNames === undefined ? true : useCaseSensitiveFileNames)); + const scope = getPackageScopeForPath(directoryPath, state.packageJsonInfoCache, state.host, state.compilerOptions); + if (!scope) { if (state.traceEnabled) { - trace(state.host, Diagnostics.Import_specifier_0_does_not_exist_in_package_json_scope_at_path_1, moduleName, scope.packageDirectory); + trace(state.host, Diagnostics.Directory_0_has_no_containing_package_json_scope_Imports_will_not_resolve, directoryPath); } return toSearchResult(/*value*/ undefined); } + if (!scope.packageJsonContent.imports) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_no_imports_defined, scope.packageDirectory); + } + return toSearchResult(/*value*/ undefined); + } + + const result = loadModuleFromImportsOrExports(extensions, state, cache, redirectedReference, moduleName, scope.packageJsonContent.imports, scope, /*isImports*/ true); + if (result) { + return result; + } - function loadModuleFromImportsOrExports(extensions: Extensions, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, moduleName: string, lookupTable: object, scope: PackageJsonInfo, isImports: boolean): SearchResult | undefined { - const loadModuleFromTargetImportOrExport = getLoadModuleFromTargetImportOrExport(extensions, state, cache, redirectedReference, moduleName, scope, isImports); + if (state.traceEnabled) { + trace(state.host, Diagnostics.Import_specifier_0_does_not_exist_in_package_json_scope_at_path_1, moduleName, scope.packageDirectory); + } + return toSearchResult(/*value*/ undefined); +} - if (!endsWith(moduleName, directorySeparator) && moduleName.indexOf("*") === -1 && hasProperty(lookupTable, moduleName)) { - const target = (lookupTable as {[idx: string]: unknown})[moduleName]; - return loadModuleFromTargetImportOrExport(target, /*subpath*/ "", /*pattern*/ false); +function loadModuleFromImportsOrExports(extensions: Extensions, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, moduleName: string, lookupTable: object, scope: PackageJsonInfo, isImports: boolean): SearchResult | undefined { + const loadModuleFromTargetImportOrExport = getLoadModuleFromTargetImportOrExport(extensions, state, cache, redirectedReference, moduleName, scope, isImports); + + if (!endsWith(moduleName, directorySeparator) && moduleName.indexOf("*") === -1 && hasProperty(lookupTable, moduleName)) { + const target = (lookupTable as {[idx: string]: unknown})[moduleName]; + return loadModuleFromTargetImportOrExport(target, /*subpath*/ "", /*pattern*/ false); + } + const expandingKeys = sort(filter(getOwnKeys(lookupTable as MapLike), k => k.indexOf("*") !== -1 || endsWith(k, "/")), (a, b) => a.length - b.length); + for (const potentialTarget of expandingKeys) { + if (state.features & NodeResolutionFeatures.ExportsPatternTrailers && matchesPatternWithTrailer(potentialTarget, moduleName)) { + const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; + const starPos = potentialTarget.indexOf("*"); + const subpath = moduleName.substring(potentialTarget.substring(0, starPos).length, moduleName.length - (potentialTarget.length - 1 - starPos)); + return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ true); } - const expandingKeys = sort(filter(getOwnKeys(lookupTable as MapLike), k => k.indexOf("*") !== -1 || endsWith(k, "/")), (a, b) => a.length - b.length); - for (const potentialTarget of expandingKeys) { - if (state.features & NodeResolutionFeatures.ExportsPatternTrailers && matchesPatternWithTrailer(potentialTarget, moduleName)) { - const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; - const starPos = potentialTarget.indexOf("*"); - const subpath = moduleName.substring(potentialTarget.substring(0, starPos).length, moduleName.length - (potentialTarget.length - 1 - starPos)); - return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ true); - } - else if (endsWith(potentialTarget, "*") && startsWith(moduleName, potentialTarget.substring(0, potentialTarget.length - 1))) { - const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; - const subpath = moduleName.substring(potentialTarget.length - 1); - return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ true); - } - else if (startsWith(moduleName, potentialTarget)) { - const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; - const subpath = moduleName.substring(potentialTarget.length); - return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ false); - } + else if (endsWith(potentialTarget, "*") && startsWith(moduleName, potentialTarget.substring(0, potentialTarget.length - 1))) { + const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; + const subpath = moduleName.substring(potentialTarget.length - 1); + return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ true); } - - function matchesPatternWithTrailer(target: string, name: string) { - if (endsWith(target, "*")) return false; // handled by next case in loop - const starPos = target.indexOf("*"); - if (starPos === -1) return false; // handled by last case in loop - return startsWith(name, target.substring(0, starPos)) && endsWith(name, target.substring(starPos + 1)); + else if (startsWith(moduleName, potentialTarget)) { + const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; + const subpath = moduleName.substring(potentialTarget.length); + return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ false); } } - /** - * Gets the self-recursive function specialized to retrieving the targeted import/export element for the given resolution configuration - */ - function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, moduleName: string, scope: PackageJsonInfo, isImports: boolean) { - return loadModuleFromTargetImportOrExport; - function loadModuleFromTargetImportOrExport(target: unknown, subpath: string, pattern: boolean): SearchResult | undefined { - if (typeof target === "string") { - if (!pattern && subpath.length > 0 && !endsWith(target, "/")) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); - } - return toSearchResult(/*value*/ undefined); + function matchesPatternWithTrailer(target: string, name: string) { + if (endsWith(target, "*")) return false; // handled by next case in loop + const starPos = target.indexOf("*"); + if (starPos === -1) return false; // handled by last case in loop + return startsWith(name, target.substring(0, starPos)) && endsWith(name, target.substring(starPos + 1)); + } +} + +/** + * Gets the self-recursive function specialized to retrieving the targeted import/export element for the given resolution configuration + */ +function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, moduleName: string, scope: PackageJsonInfo, isImports: boolean) { + return loadModuleFromTargetImportOrExport; + function loadModuleFromTargetImportOrExport(target: unknown, subpath: string, pattern: boolean): SearchResult | undefined { + if (typeof target === "string") { + if (!pattern && subpath.length > 0 && !endsWith(target, "/")) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } - if (!startsWith(target, "./")) { - if (isImports && !startsWith(target, "../") && !startsWith(target, "/") && !isRootedDiskPath(target)) { - const combinedLookup = pattern ? target.replace(/\*/g, subpath) : target + subpath; - const result = nodeModuleNameResolverWorker(state.features, combinedLookup, scope.packageDirectory + "/", state.compilerOptions, state.host, cache, [extensions], redirectedReference); - return toSearchResult(result.resolvedModule ? { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId, originalPath: result.resolvedModule.originalPath } : undefined); - } - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); - } - return toSearchResult(/*value*/ undefined); + return toSearchResult(/*value*/ undefined); + } + if (!startsWith(target, "./")) { + if (isImports && !startsWith(target, "../") && !startsWith(target, "/") && !isRootedDiskPath(target)) { + const combinedLookup = pattern ? target.replace(/\*/g, subpath) : target + subpath; + const result = nodeModuleNameResolverWorker(state.features, combinedLookup, scope.packageDirectory + "/", state.compilerOptions, state.host, cache, [extensions], redirectedReference); + return toSearchResult(result.resolvedModule ? { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId, originalPath: result.resolvedModule.originalPath } : undefined); } - const parts = pathIsRelative(target) ? getPathComponents(target).slice(1) : getPathComponents(target); - const partsAfterFirst = parts.slice(1); - if (partsAfterFirst.indexOf("..") >= 0 || partsAfterFirst.indexOf(".") >= 0 || partsAfterFirst.indexOf("node_modules") >= 0) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); - } - return toSearchResult(/*value*/ undefined); + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } - const resolvedTarget = combinePaths(scope.packageDirectory, target); - // TODO: Assert that `resolvedTarget` is actually within the package directory? That's what the spec says.... but I'm not sure we need - // to be in the business of validating everyone's import and export map correctness. - const subpathParts = getPathComponents(subpath); - if (subpathParts.indexOf("..") >= 0 || subpathParts.indexOf(".") >= 0 || subpathParts.indexOf("node_modules") >= 0) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); - } - return toSearchResult(/*value*/ undefined); + return toSearchResult(/*value*/ undefined); + } + const parts = pathIsRelative(target) ? getPathComponents(target).slice(1) : getPathComponents(target); + const partsAfterFirst = parts.slice(1); + if (partsAfterFirst.indexOf("..") >= 0 || partsAfterFirst.indexOf(".") >= 0 || partsAfterFirst.indexOf("node_modules") >= 0) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } - const finalPath = getNormalizedAbsolutePath(pattern ? resolvedTarget.replace(/\*/g, subpath) : resolvedTarget + subpath, state.host.getCurrentDirectory?.()); - - return toSearchResult(withPackageId(scope, loadJSOrExactTSFileName(extensions, finalPath, /*onlyRecordFailures*/ false, state))); + return toSearchResult(/*value*/ undefined); } - else if (typeof target === "object" && target !== null) { // eslint-disable-line no-null/no-null - if (!Array.isArray(target)) { - for (const key of getOwnKeys(target as MapLike)) { - if (key === "default" || state.conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(state.conditions, key)) { - const subTarget = (target as MapLike)[key]; - const result = loadModuleFromTargetImportOrExport(subTarget, subpath, pattern); - if (result) { - return result; - } - } - } - return undefined; + const resolvedTarget = combinePaths(scope.packageDirectory, target); + // TODO: Assert that `resolvedTarget` is actually within the package directory? That's what the spec says.... but I'm not sure we need + // to be in the business of validating everyone's import and export map correctness. + const subpathParts = getPathComponents(subpath); + if (subpathParts.indexOf("..") >= 0 || subpathParts.indexOf(".") >= 0 || subpathParts.indexOf("node_modules") >= 0) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } - else { - if (!length(target)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); - } - return toSearchResult(/*value*/ undefined); - } - for (const elem of target) { - const result = loadModuleFromTargetImportOrExport(elem, subpath, pattern); + return toSearchResult(/*value*/ undefined); + } + const finalPath = getNormalizedAbsolutePath(pattern ? resolvedTarget.replace(/\*/g, subpath) : resolvedTarget + subpath, state.host.getCurrentDirectory?.()); + + return toSearchResult(withPackageId(scope, loadJSOrExactTSFileName(extensions, finalPath, /*onlyRecordFailures*/ false, state))); + } + else if (typeof target === "object" && target !== null) { // eslint-disable-line no-null/no-null + if (!Array.isArray(target)) { + for (const key of getOwnKeys(target as MapLike)) { + if (key === "default" || state.conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(state.conditions, key)) { + const subTarget = (target as MapLike)[key]; + const result = loadModuleFromTargetImportOrExport(subTarget, subpath, pattern); if (result) { return result; } } } + return undefined; } - else if (target === null) { // eslint-disable-line no-null/no-null - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_explicitly_maps_specifier_1_to_null, scope.packageDirectory, moduleName); + else { + if (!length(target)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); + } + return toSearchResult(/*value*/ undefined); + } + for (const elem of target) { + const result = loadModuleFromTargetImportOrExport(elem, subpath, pattern); + if (result) { + return result; + } } - return toSearchResult(/*value*/ undefined); } + } + else if (target === null) { // eslint-disable-line no-null/no-null if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); + trace(state.host, Diagnostics.package_json_scope_0_explicitly_maps_specifier_1_to_null, scope.packageDirectory, moduleName); } return toSearchResult(/*value*/ undefined); } + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); + } + return toSearchResult(/*value*/ undefined); } +} - /* @internal */ - export function isApplicableVersionedTypesKey(conditions: string[], key: string) { - if (conditions.indexOf("types") === -1) return false; // only apply versioned types conditions if the types condition is applied - if (!startsWith(key, "types@")) return false; - const range = VersionRange.tryParse(key.substring("types@".length)); - if (!range) return false; - return range.test(version); - } +/* @internal */ +export function isApplicableVersionedTypesKey(conditions: string[], key: string) { + if (conditions.indexOf("types") === -1) return false; // only apply versioned types conditions if the types condition is applied + if (!startsWith(key, "types@")) return false; + const range = VersionRange.tryParse(key.substring("types@".length)); + if (!range) return false; + return range.test(version); +} - function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache, redirectedReference); - } +function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache, redirectedReference); +} - function loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName: string, directory: string, state: ModuleResolutionState): SearchResult { - // Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly. - return loadModuleFromNearestNodeModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined, /*redirectedReference*/ undefined); - } +function loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName: string, directory: string, state: ModuleResolutionState): SearchResult { + // Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly. + return loadModuleFromNearestNodeModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined, /*redirectedReference*/ undefined); +} - function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, state.features === 0 ? undefined : state.features & NodeResolutionFeatures.EsmMode ? ModuleKind.ESNext : ModuleKind.CommonJS, redirectedReference); - return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => { - if (getBaseFileName(ancestorDirectory) !== "node_modules") { - const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state); - if (resolutionFromCache) { - return resolutionFromCache; - } - return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly, cache, redirectedReference)); +function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, state.features === 0 ? undefined : state.features & NodeResolutionFeatures.EsmMode ? ModuleKind.ESNext : ModuleKind.CommonJS, redirectedReference); + return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => { + if (getBaseFileName(ancestorDirectory) !== "node_modules") { + const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state); + if (resolutionFromCache) { + return resolutionFromCache; } - }); - } - - function loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { - const nodeModulesFolder = combinePaths(directory, "node_modules"); - const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); - if (!nodeModulesFolderExists && state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesFolder); + return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly, cache, redirectedReference)); } + }); +} - const packageResult = typesScopeOnly ? undefined : loadModuleFromSpecificNodeModulesDirectory(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, state, cache, redirectedReference); - if (packageResult) { - return packageResult; - } - if (extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) { - const nodeModulesAtTypes = combinePaths(nodeModulesFolder, "@types"); - let nodeModulesAtTypesExists = nodeModulesFolderExists; - if (nodeModulesFolderExists && !directoryProbablyExists(nodeModulesAtTypes, state.host)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesAtTypes); - } - nodeModulesAtTypesExists = false; - } - return loadModuleFromSpecificNodeModulesDirectory(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, state, cache, redirectedReference); - } +function loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { + const nodeModulesFolder = combinePaths(directory, "node_modules"); + const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); + if (!nodeModulesFolderExists && state.traceEnabled) { + trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesFolder); } - function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { - const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName)); - - // First look for a nested package.json, as in `node_modules/foo/bar/package.json`. - let packageInfo = getPackageJsonInfo(candidate, !nodeModulesDirectoryExists, state); - // But only if we're not respecting export maps (if we are, we might redirect around this location) - if (!(state.features & NodeResolutionFeatures.Exports)) { - if (packageInfo) { - const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); - if (fromFile) { - return noPackageId(fromFile); - } - - const fromDirectory = loadNodeModuleFromDirectoryWorker( - extensions, - candidate, - !nodeModulesDirectoryExists, - state, - packageInfo.packageJsonContent, - packageInfo.versionPaths - ); - return withPackageId(packageInfo, fromDirectory); + const packageResult = typesScopeOnly ? undefined : loadModuleFromSpecificNodeModulesDirectory(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, state, cache, redirectedReference); + if (packageResult) { + return packageResult; + } + if (extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) { + const nodeModulesAtTypes = combinePaths(nodeModulesFolder, "@types"); + let nodeModulesAtTypesExists = nodeModulesFolderExists; + if (nodeModulesFolderExists && !directoryProbablyExists(nodeModulesAtTypes, state.host)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesAtTypes); } + nodeModulesAtTypesExists = false; } + return loadModuleFromSpecificNodeModulesDirectory(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, state, cache, redirectedReference); + } +} - const { packageName, rest } = parsePackageName(moduleName); - const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { - // package exports are higher priority than file/directory lookups (and, if there's exports present, blocks them) - if (packageInfo && packageInfo.packageJsonContent.exports && state.features & NodeResolutionFeatures.Exports) { - return loadModuleFromExports(packageInfo, extensions, combinePaths(".", rest), state, cache, redirectedReference)?.value; - } - const pathAndExtension = - loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) || - loadNodeModuleFromDirectoryWorker( - extensions, - candidate, - onlyRecordFailures, - state, - packageInfo && packageInfo.packageJsonContent, - packageInfo && packageInfo.versionPaths - ); - return withPackageId(packageInfo, pathAndExtension); - }; - - if (rest !== "") { // If "rest" is empty, we just did this search above. - const packageDirectory = combinePaths(nodeModulesDirectory, packageName); +function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { + const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName)); - // Don't use a "types" or "main" from here because we're not loading the root, but a subdirectory -- just here for the packageId and path mappings. - packageInfo = getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state); - if (packageInfo && packageInfo.versionPaths) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, packageInfo.versionPaths.version, version, rest); - } - const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host); - const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, packageInfo.versionPaths.paths, /*pathPatterns*/ undefined, loader, !packageDirectoryExists, state); - if (fromPaths) { - return fromPaths.value; - } + // First look for a nested package.json, as in `node_modules/foo/bar/package.json`. + let packageInfo = getPackageJsonInfo(candidate, !nodeModulesDirectoryExists, state); + // But only if we're not respecting export maps (if we are, we might redirect around this location) + if (!(state.features & NodeResolutionFeatures.Exports)) { + if (packageInfo) { + const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); + if (fromFile) { + return noPackageId(fromFile); + } + + const fromDirectory = loadNodeModuleFromDirectoryWorker( + extensions, + candidate, + !nodeModulesDirectoryExists, + state, + packageInfo.packageJsonContent, + packageInfo.versionPaths + ); + return withPackageId(packageInfo, fromDirectory); + } + } + + const { packageName, rest } = parsePackageName(moduleName); + const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { + // package exports are higher priority than file/directory lookups (and, if there's exports present, blocks them) + if (packageInfo && packageInfo.packageJsonContent.exports && state.features & NodeResolutionFeatures.Exports) { + return loadModuleFromExports(packageInfo, extensions, combinePaths(".", rest), state, cache, redirectedReference)?.value; + } + const pathAndExtension = + loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) || + loadNodeModuleFromDirectoryWorker( + extensions, + candidate, + onlyRecordFailures, + state, + packageInfo && packageInfo.packageJsonContent, + packageInfo && packageInfo.versionPaths + ); + return withPackageId(packageInfo, pathAndExtension); + }; + + if (rest !== "") { // If "rest" is empty, we just did this search above. + const packageDirectory = combinePaths(nodeModulesDirectory, packageName); + + // Don't use a "types" or "main" from here because we're not loading the root, but a subdirectory -- just here for the packageId and path mappings. + packageInfo = getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state); + if (packageInfo && packageInfo.versionPaths) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, packageInfo.versionPaths.version, version, rest); + } + const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host); + const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, packageInfo.versionPaths.paths, /*pathPatterns*/ undefined, loader, !packageDirectoryExists, state); + if (fromPaths) { + return fromPaths.value; } } - - return loader(extensions, candidate, !nodeModulesDirectoryExists, state); } - function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike, pathPatterns: readonly (string | Pattern)[] | undefined, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult { - pathPatterns ||= tryParsePatterns(paths); - const matchedPattern = matchPatternOrExact(pathPatterns, moduleName); - if (matchedPattern) { - const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName); - const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern); + return loader(extensions, candidate, !nodeModulesDirectoryExists, state); +} + +function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike, pathPatterns: readonly (string | Pattern)[] | undefined, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult { + pathPatterns ||= tryParsePatterns(paths); + const matchedPattern = matchPatternOrExact(pathPatterns, moduleName); + if (matchedPattern) { + const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName); + const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern); + if (state.traceEnabled) { + trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText); + } + const resolved = forEach(paths[matchedPatternText], subst => { + const path = matchedStar ? subst.replace("*", matchedStar) : subst; + // When baseUrl is not specified, the command line parser resolves relative paths to the config file location. + const candidate = normalizePath(combinePaths(baseDirectory, path)); if (state.traceEnabled) { - trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText); - } - const resolved = forEach(paths[matchedPatternText], subst => { - const path = matchedStar ? subst.replace("*", matchedStar) : subst; - // When baseUrl is not specified, the command line parser resolves relative paths to the config file location. - const candidate = normalizePath(combinePaths(baseDirectory, path)); - if (state.traceEnabled) { - trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path); + trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path); + } + // A path mapping may have an extension, in contrast to an import, which should omit it. + const extension = tryGetExtensionFromPath(subst); + if (extension !== undefined) { + const path = tryFile(candidate, onlyRecordFailures, state); + if (path !== undefined) { + return noPackageId({ path, ext: extension }); } - // A path mapping may have an extension, in contrast to an import, which should omit it. - const extension = tryGetExtensionFromPath(subst); - if (extension !== undefined) { - const path = tryFile(candidate, onlyRecordFailures, state); - if (path !== undefined) { - return noPackageId({ path, ext: extension }); - } - } - return loader(extensions, candidate, onlyRecordFailures || !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); - }); - return { value: resolved }; - } + } + return loader(extensions, candidate, onlyRecordFailures || !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); + }); + return { value: resolved }; } +} - /** Double underscores are used in DefinitelyTyped to delimit scoped packages. */ - const mangledScopedPackageSeparator = "__"; +/** Double underscores are used in DefinitelyTyped to delimit scoped packages. */ +const mangledScopedPackageSeparator = "__"; - /** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */ - function mangleScopedPackageNameWithTrace(packageName: string, state: ModuleResolutionState): string { - const mangled = mangleScopedPackageName(packageName); - if (state.traceEnabled && mangled !== packageName) { - trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled); - } - return mangled; +/** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */ +function mangleScopedPackageNameWithTrace(packageName: string, state: ModuleResolutionState): string { + const mangled = mangleScopedPackageName(packageName); + if (state.traceEnabled && mangled !== packageName) { + trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled); } + return mangled; +} - /* @internal */ - export function getTypesPackageName(packageName: string): string { - return `@types/${mangleScopedPackageName(packageName)}`; - } +/* @internal */ +export function getTypesPackageName(packageName: string): string { + return `@types/${mangleScopedPackageName(packageName)}`; +} - /* @internal */ - export function mangleScopedPackageName(packageName: string): string { - if (startsWith(packageName, "@")) { - const replaceSlash = packageName.replace(directorySeparator, mangledScopedPackageSeparator); - if (replaceSlash !== packageName) { - return replaceSlash.slice(1); // Take off the "@" - } +/* @internal */ +export function mangleScopedPackageName(packageName: string): string { + if (startsWith(packageName, "@")) { + const replaceSlash = packageName.replace(directorySeparator, mangledScopedPackageSeparator); + if (replaceSlash !== packageName) { + return replaceSlash.slice(1); // Take off the "@" } - return packageName; } + return packageName; +} - /* @internal */ - export function getPackageNameFromTypesPackageName(mangledName: string): string { - const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); - if (withoutAtTypePrefix !== mangledName) { - return unmangleScopedPackageName(withoutAtTypePrefix); - } - return mangledName; +/* @internal */ +export function getPackageNameFromTypesPackageName(mangledName: string): string { + const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); + if (withoutAtTypePrefix !== mangledName) { + return unmangleScopedPackageName(withoutAtTypePrefix); } + return mangledName; +} - /* @internal */ - export function unmangleScopedPackageName(typesPackageName: string): string { - return stringContains(typesPackageName, mangledScopedPackageSeparator) ? - "@" + typesPackageName.replace(mangledScopedPackageSeparator, directorySeparator) : - typesPackageName; - } +/* @internal */ +export function unmangleScopedPackageName(typesPackageName: string): string { + return stringContains(typesPackageName, mangledScopedPackageSeparator) ? + "@" + typesPackageName.replace(mangledScopedPackageSeparator, directorySeparator) : + typesPackageName; +} - function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, state: ModuleResolutionState): SearchResult { - const result = cache && cache.get(containingDirectory); - if (result) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); - } - state.resultFromCache = result; - return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } }; +function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, state: ModuleResolutionState): SearchResult { + const result = cache && cache.get(containingDirectory); + if (result) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); } + state.resultFromCache = result; + return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } }; } +} - export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(compilerOptions, host); - const failedLookupLocations: string[] = []; - const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache, features: NodeResolutionFeatures.None, conditions: [] }; - const containingDirectory = getDirectoryPath(containingFile); +export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + const failedLookupLocations: string[] = []; + const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache, features: NodeResolutionFeatures.None, conditions: [] }; + const containingDirectory = getDirectoryPath(containingFile); - const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript); - // No originalPath because classic resolution doesn't resolve realPath - return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*isExternalLibraryImport*/ false, failedLookupLocations, state.resultFromCache); + const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript); + // No originalPath because classic resolution doesn't resolve realPath + return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*isExternalLibraryImport*/ false, failedLookupLocations, state.resultFromCache); - function tryResolve(extensions: Extensions): SearchResult { - const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, state); - if (resolvedUsingSettings) { - return { value: resolvedUsingSettings }; - } + function tryResolve(extensions: Extensions): SearchResult { + const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, state); + if (resolvedUsingSettings) { + return { value: resolvedUsingSettings }; + } - if (!isExternalModuleNameRelative(moduleName)) { - const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, /*mode*/ undefined, redirectedReference); - // Climb up parent directories looking for a module. - const resolved = forEachAncestorDirectory(containingDirectory, directory => { - const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, state); - if (resolutionFromCache) { - return resolutionFromCache; - } - const searchName = normalizePath(combinePaths(directory, moduleName)); - return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state)); - }); - if (resolved) { - return resolved; - } - if (extensions === Extensions.TypeScript) { - // If we didn't find the file normally, look it up in @types. - return loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName, containingDirectory, state); + if (!isExternalModuleNameRelative(moduleName)) { + const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, /*mode*/ undefined, redirectedReference); + // Climb up parent directories looking for a module. + const resolved = forEachAncestorDirectory(containingDirectory, directory => { + const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, state); + if (resolutionFromCache) { + return resolutionFromCache; } + const searchName = normalizePath(combinePaths(directory, moduleName)); + return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state)); + }); + if (resolved) { + return resolved; } - else { - const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); - return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, /*onlyRecordFailures*/ false, state)); + if (extensions === Extensions.TypeScript) { + // If we didn't find the file normally, look it up in @types. + return loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName, containingDirectory, state); } } - } - - /** - * A host may load a module from a global cache of typings. - * This is the minumum code needed to expose that functionality; the rest is in the host. - */ - /* @internal */ - export function loadModuleFromGlobalCache(moduleName: string, projectName: string | undefined, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string, packageJsonInfoCache: PackageJsonInfoCache): ResolvedModuleWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(compilerOptions, host); - if (traceEnabled) { - trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache); + else { + const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); + return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, /*onlyRecordFailures*/ false, state)); } - const failedLookupLocations: string[] = []; - const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, packageJsonInfoCache, features: NodeResolutionFeatures.None, conditions: [] }; - const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false, /*cache*/ undefined, /*redirectedReference*/ undefined); - return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations, state.resultFromCache); } +} - /** - * Represents result of search. Normally when searching among several alternatives we treat value `undefined` as indicator - * that search fails and we should try another option. - * However this does not allow us to represent final result that should be used instead of further searching (i.e. a final result that was found in cache). - * SearchResult is used to deal with this issue, its values represents following outcomes: - * - undefined - not found, continue searching - * - { value: undefined } - not found - stop searching - * - { value: } - found - stop searching - */ - type SearchResult = { value: T | undefined } | undefined; +/** + * A host may load a module from a global cache of typings. + * This is the minumum code needed to expose that functionality; the rest is in the host. + */ +/* @internal */ +export function loadModuleFromGlobalCache(moduleName: string, projectName: string | undefined, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string, packageJsonInfoCache: PackageJsonInfoCache): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + if (traceEnabled) { + trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache); + } + const failedLookupLocations: string[] = []; + const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, packageJsonInfoCache, features: NodeResolutionFeatures.None, conditions: [] }; + const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false, /*cache*/ undefined, /*redirectedReference*/ undefined); + return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations, state.resultFromCache); +} - /** - * Wraps value to SearchResult. - * @returns undefined if value is undefined or { value } otherwise - */ - function toSearchResult(value: T | undefined): SearchResult { - return value !== undefined ? { value } : undefined; - } +/** + * Represents result of search. Normally when searching among several alternatives we treat value `undefined` as indicator + * that search fails and we should try another option. + * However this does not allow us to represent final result that should be used instead of further searching (i.e. a final result that was found in cache). + * SearchResult is used to deal with this issue, its values represents following outcomes: + * - undefined - not found, continue searching + * - { value: undefined } - not found - stop searching + * - { value: } - found - stop searching + */ +type SearchResult = { value: T | undefined } | undefined; + +/** + * Wraps value to SearchResult. + * @returns undefined if value is undefined or { value } otherwise + */ +function toSearchResult(value: T | undefined): SearchResult { + return value !== undefined ? { value } : undefined; +} } diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index cb2bad2e27166..a3792bc0ef678 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -1,896 +1,896 @@ // Used by importFixes, getEditsForFileRename, and declaration emit to synthesize import module specifiers. /* @internal */ namespace ts.moduleSpecifiers { - const enum RelativePreference { Relative, NonRelative, Shortest, ExternalNonRelative } - // See UserPreferences#importPathEnding - const enum Ending { Minimal, Index, JsExtension } - - // Processed preferences - interface Preferences { - readonly relativePreference: RelativePreference; - readonly ending: Ending; - } - - function getPreferences(host: ModuleSpecifierResolutionHost, { importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences, compilerOptions: CompilerOptions, importingSourceFile: SourceFile): Preferences { - return { - relativePreference: - importModuleSpecifierPreference === "relative" ? RelativePreference.Relative : - importModuleSpecifierPreference === "non-relative" ? RelativePreference.NonRelative : - importModuleSpecifierPreference === "project-relative" ? RelativePreference.ExternalNonRelative : - RelativePreference.Shortest, - ending: getEnding(), - }; - function getEnding(): Ending { - switch (importModuleSpecifierEnding) { - case "minimal": return Ending.Minimal; - case "index": return Ending.Index; - case "js": return Ending.JsExtension; - default: return usesJsExtensionOnImports(importingSourceFile) || isFormatRequiringExtensions(compilerOptions, importingSourceFile.path, host) ? Ending.JsExtension - : getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs ? Ending.Index : Ending.Minimal; - } +const enum RelativePreference { Relative, NonRelative, Shortest, ExternalNonRelative } +// See UserPreferences#importPathEnding +const enum Ending { Minimal, Index, JsExtension } + +// Processed preferences +interface Preferences { + readonly relativePreference: RelativePreference; + readonly ending: Ending; +} + +function getPreferences(host: ModuleSpecifierResolutionHost, { importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences, compilerOptions: CompilerOptions, importingSourceFile: SourceFile): Preferences { + return { + relativePreference: + importModuleSpecifierPreference === "relative" ? RelativePreference.Relative : + importModuleSpecifierPreference === "non-relative" ? RelativePreference.NonRelative : + importModuleSpecifierPreference === "project-relative" ? RelativePreference.ExternalNonRelative : + RelativePreference.Shortest, + ending: getEnding(), + }; + function getEnding(): Ending { + switch (importModuleSpecifierEnding) { + case "minimal": return Ending.Minimal; + case "index": return Ending.Index; + case "js": return Ending.JsExtension; + default: return usesJsExtensionOnImports(importingSourceFile) || isFormatRequiringExtensions(compilerOptions, importingSourceFile.path, host) ? Ending.JsExtension + : getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs ? Ending.Index : Ending.Minimal; } } +} + +function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Preferences { + return { + relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative, + ending: hasJSFileExtension(oldImportSpecifier) || isFormatRequiringExtensions(compilerOptions, importingSourceFileName, host) ? + Ending.JsExtension : + getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal, + }; +} - function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Preferences { - return { - relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative, - ending: hasJSFileExtension(oldImportSpecifier) || isFormatRequiringExtensions(compilerOptions, importingSourceFileName, host) ? - Ending.JsExtension : - getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal, - }; +function isFormatRequiringExtensions(compilerOptions: CompilerOptions, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost) { + if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node12 + && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { + return false; } + return getImpliedNodeFormatForFile(importingSourceFileName, /*packageJsonInfoCache*/ undefined, getModuleResolutionHost(host), compilerOptions) !== ModuleKind.CommonJS; +} - function isFormatRequiringExtensions(compilerOptions: CompilerOptions, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost) { - if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node12 - && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { - return false; - } - return getImpliedNodeFormatForFile(importingSourceFileName, /*packageJsonInfoCache*/ undefined, getModuleResolutionHost(host), compilerOptions) !== ModuleKind.CommonJS; - } - - function getModuleResolutionHost(host: ModuleSpecifierResolutionHost): ModuleResolutionHost { - return { - fileExists: host.fileExists, - readFile: Debug.checkDefined(host.readFile), - directoryExists: host.directoryExists, - getCurrentDirectory: host.getCurrentDirectory, - realpath: host.realpath, - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames?.(), - }; - } - - export function updateModuleSpecifier( - compilerOptions: CompilerOptions, - importingSourceFileName: Path, - toFileName: string, - host: ModuleSpecifierResolutionHost, - oldImportSpecifier: string, - ): string | undefined { - const res = getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier, importingSourceFileName, host), {}); - if (res === oldImportSpecifier) return undefined; - return res; - } - - // Note: importingSourceFile is just for usesJsExtensionOnImports - export function getModuleSpecifier( - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - importingSourceFileName: Path, - toFileName: string, - host: ModuleSpecifierResolutionHost, - ): string { - return getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferences(host, {}, compilerOptions, importingSourceFile), {}); - } - - export function getNodeModulesPackageName( - compilerOptions: CompilerOptions, - importingSourceFileName: Path, - nodeModulesFileName: string, - host: ModuleSpecifierResolutionHost, - preferences: UserPreferences, - ): string | undefined { - const info = getInfo(importingSourceFileName, host); - const modulePaths = getAllModulePaths(importingSourceFileName, nodeModulesFileName, host, preferences); - return firstDefined(modulePaths, - modulePath => tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions, /*packageNameOnly*/ true)); - } - - function getModuleSpecifierWorker( - compilerOptions: CompilerOptions, - importingSourceFileName: Path, - toFileName: string, - host: ModuleSpecifierResolutionHost, - preferences: Preferences, - userPreferences: UserPreferences, - ): string { - const info = getInfo(importingSourceFileName, host); - const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host, userPreferences); - return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions)) || - getLocalModuleSpecifier(toFileName, info, compilerOptions, host, preferences); - } - - export function tryGetModuleSpecifiersFromCache( - moduleSymbol: Symbol, - importingSourceFile: SourceFile, - host: ModuleSpecifierResolutionHost, - userPreferences: UserPreferences, - ): readonly string[] | undefined { - return tryGetModuleSpecifiersFromCacheWorker( - moduleSymbol, - importingSourceFile, - host, - userPreferences)[0]; - } - - function tryGetModuleSpecifiersFromCacheWorker( - moduleSymbol: Symbol, - importingSourceFile: SourceFile, - host: ModuleSpecifierResolutionHost, - userPreferences: UserPreferences, - ): readonly [specifiers?: readonly string[], moduleFile?: SourceFile, modulePaths?: readonly ModulePath[], cache?: ModuleSpecifierCache] { - const moduleSourceFile = getSourceFileOfModule(moduleSymbol); - if (!moduleSourceFile) { - return emptyArray as []; - } +function getModuleResolutionHost(host: ModuleSpecifierResolutionHost): ModuleResolutionHost { + return { + fileExists: host.fileExists, + readFile: Debug.checkDefined(host.readFile), + directoryExists: host.directoryExists, + getCurrentDirectory: host.getCurrentDirectory, + realpath: host.realpath, + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames?.(), + }; +} - const cache = host.getModuleSpecifierCache?.(); - const cached = cache?.get(importingSourceFile.path, moduleSourceFile.path, userPreferences); - return [cached?.moduleSpecifiers, moduleSourceFile, cached?.modulePaths, cache]; - } - - /** Returns an import for each symlink and for the realpath. */ - export function getModuleSpecifiers( - moduleSymbol: Symbol, - checker: TypeChecker, - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - host: ModuleSpecifierResolutionHost, - userPreferences: UserPreferences, - ): readonly string[] { - return getModuleSpecifiersWithCacheInfo( - moduleSymbol, - checker, - compilerOptions, - importingSourceFile, - host, - userPreferences, - ).moduleSpecifiers; - } - - export function getModuleSpecifiersWithCacheInfo( - moduleSymbol: Symbol, - checker: TypeChecker, - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - host: ModuleSpecifierResolutionHost, - userPreferences: UserPreferences, - ): { moduleSpecifiers: readonly string[], computedWithoutCache: boolean } { - let computedWithoutCache = false; - const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol, checker); - if (ambient) return { moduleSpecifiers: [ambient], computedWithoutCache }; - - // eslint-disable-next-line prefer-const - let [specifiers, moduleSourceFile, modulePaths, cache] = tryGetModuleSpecifiersFromCacheWorker( - moduleSymbol, - importingSourceFile, - host, - userPreferences, - ); - if (specifiers) return { moduleSpecifiers: specifiers, computedWithoutCache }; - if (!moduleSourceFile) return { moduleSpecifiers: emptyArray, computedWithoutCache }; - - computedWithoutCache = true; - modulePaths ||= getAllModulePathsWorker(importingSourceFile.path, moduleSourceFile.originalFileName, host); - const result = computeModuleSpecifiers(modulePaths, compilerOptions, importingSourceFile, host, userPreferences); - cache?.set(importingSourceFile.path, moduleSourceFile.path, userPreferences, modulePaths, result); - return { moduleSpecifiers: result, computedWithoutCache }; - } - - function computeModuleSpecifiers( - modulePaths: readonly ModulePath[], - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - host: ModuleSpecifierResolutionHost, - userPreferences: UserPreferences, - ): readonly string[] { - const info = getInfo(importingSourceFile.path, host); - const preferences = getPreferences(host, userPreferences, compilerOptions, importingSourceFile); - const existingSpecifier = forEach(modulePaths, modulePath => forEach( - host.getFileIncludeReasons().get(toPath(modulePath.path, host.getCurrentDirectory(), info.getCanonicalFileName)), - reason => { - if (reason.kind !== FileIncludeKind.Import || reason.file !== importingSourceFile.path) return undefined; - const specifier = getModuleNameStringLiteralAt(importingSourceFile, reason.index).text; - // If the preference is for non relative and the module specifier is relative, ignore it - return preferences.relativePreference !== RelativePreference.NonRelative || !pathIsRelative(specifier) ? - specifier : - undefined; - } - )); - if (existingSpecifier) { - const moduleSpecifiers = [existingSpecifier]; - return moduleSpecifiers; - } +export function updateModuleSpecifier( + compilerOptions: CompilerOptions, + importingSourceFileName: Path, + toFileName: string, + host: ModuleSpecifierResolutionHost, + oldImportSpecifier: string, +): string | undefined { + const res = getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier, importingSourceFileName, host), {}); + if (res === oldImportSpecifier) return undefined; + return res; +} - const importedFileIsInNodeModules = some(modulePaths, p => p.isInNodeModules); - - // Module specifier priority: - // 1. "Bare package specifiers" (e.g. "@foo/bar") resulting from a path through node_modules to a package.json's "types" entry - // 2. Specifiers generated using "paths" from tsconfig - // 3. Non-relative specfiers resulting from a path through node_modules (e.g. "@foo/bar/path/to/file") - // 4. Relative paths - let nodeModulesSpecifiers: string[] | undefined; - let pathsSpecifiers: string[] | undefined; - let relativeSpecifiers: string[] | undefined; - for (const modulePath of modulePaths) { - const specifier = tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions); - nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier); - if (specifier && modulePath.isRedirect) { - // If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar", - // not "@foo/bar/path/to/file"). No other specifier will be this good, so stop looking. - return nodeModulesSpecifiers!; - } +// Note: importingSourceFile is just for usesJsExtensionOnImports +export function getModuleSpecifier( + compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, + importingSourceFileName: Path, + toFileName: string, + host: ModuleSpecifierResolutionHost, +): string { + return getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferences(host, {}, compilerOptions, importingSourceFile), {}); +} - if (!specifier && !modulePath.isRedirect) { - const local = getLocalModuleSpecifier(modulePath.path, info, compilerOptions, host, preferences); - if (pathIsBareSpecifier(local)) { - pathsSpecifiers = append(pathsSpecifiers, local); - } - else if (!importedFileIsInNodeModules || modulePath.isInNodeModules) { - // Why this extra conditional, not just an `else`? If some path to the file contained - // 'node_modules', but we can't create a non-relative specifier (e.g. "@foo/bar/path/to/file"), - // that means we had to go through a *sibling's* node_modules, not one we can access directly. - // If some path to the file was in node_modules but another was not, this likely indicates that - // we have a monorepo structure with symlinks. In this case, the non-node_modules path is - // probably the realpath, e.g. "../bar/path/to/file", but a relative path to another package - // in a monorepo is probably not portable. So, the module specifier we actually go with will be - // the relative path through node_modules, so that the declaration emitter can produce a - // portability error. (See declarationEmitReexportedSymlinkReference3) - relativeSpecifiers = append(relativeSpecifiers, local); - } - } - } +export function getNodeModulesPackageName( + compilerOptions: CompilerOptions, + importingSourceFileName: Path, + nodeModulesFileName: string, + host: ModuleSpecifierResolutionHost, + preferences: UserPreferences, +): string | undefined { + const info = getInfo(importingSourceFileName, host); + const modulePaths = getAllModulePaths(importingSourceFileName, nodeModulesFileName, host, preferences); + return firstDefined(modulePaths, + modulePath => tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions, /*packageNameOnly*/ true)); +} - return pathsSpecifiers?.length ? pathsSpecifiers : - nodeModulesSpecifiers?.length ? nodeModulesSpecifiers : - Debug.checkDefined(relativeSpecifiers); - } +function getModuleSpecifierWorker( + compilerOptions: CompilerOptions, + importingSourceFileName: Path, + toFileName: string, + host: ModuleSpecifierResolutionHost, + preferences: Preferences, + userPreferences: UserPreferences, +): string { + const info = getInfo(importingSourceFileName, host); + const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host, userPreferences); + return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions)) || + getLocalModuleSpecifier(toFileName, info, compilerOptions, host, preferences); +} - interface Info { - readonly getCanonicalFileName: GetCanonicalFileName; - readonly importingSourceFileName: Path - readonly sourceDirectory: Path; - } - // importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path - function getInfo(importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Info { - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true); - const sourceDirectory = getDirectoryPath(importingSourceFileName); - return { getCanonicalFileName, importingSourceFileName, sourceDirectory }; +export function tryGetModuleSpecifiersFromCache( + moduleSymbol: Symbol, + importingSourceFile: SourceFile, + host: ModuleSpecifierResolutionHost, + userPreferences: UserPreferences, +): readonly string[] | undefined { + return tryGetModuleSpecifiersFromCacheWorker( + moduleSymbol, + importingSourceFile, + host, + userPreferences)[0]; +} + +function tryGetModuleSpecifiersFromCacheWorker( + moduleSymbol: Symbol, + importingSourceFile: SourceFile, + host: ModuleSpecifierResolutionHost, + userPreferences: UserPreferences, +): readonly [specifiers?: readonly string[], moduleFile?: SourceFile, modulePaths?: readonly ModulePath[], cache?: ModuleSpecifierCache] { + const moduleSourceFile = getSourceFileOfModule(moduleSymbol); + if (!moduleSourceFile) { + return emptyArray as []; } - function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, { ending, relativePreference }: Preferences): string { - const { baseUrl, paths, rootDirs } = compilerOptions; - const { sourceDirectory, getCanonicalFileName } = info; - const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, ending, compilerOptions) || - removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), ending, compilerOptions); - if (!baseUrl && !paths || relativePreference === RelativePreference.Relative) { - return relativePath; - } + const cache = host.getModuleSpecifierCache?.(); + const cached = cache?.get(importingSourceFile.path, moduleSourceFile.path, userPreferences); + return [cached?.moduleSpecifiers, moduleSourceFile, cached?.modulePaths, cache]; +} - const baseDirectory = getNormalizedAbsolutePath(getPathsBasePath(compilerOptions, host) || baseUrl!, host.getCurrentDirectory()); - const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseDirectory, getCanonicalFileName); - if (!relativeToBaseUrl) { - return relativePath; - } +/** Returns an import for each symlink and for the realpath. */ +export function getModuleSpecifiers( + moduleSymbol: Symbol, + checker: TypeChecker, + compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, + host: ModuleSpecifierResolutionHost, + userPreferences: UserPreferences, +): readonly string[] { + return getModuleSpecifiersWithCacheInfo( + moduleSymbol, + checker, + compilerOptions, + importingSourceFile, + host, + userPreferences, + ).moduleSpecifiers; +} - const importRelativeToBaseUrl = removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions); - const fromPaths = paths && tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl), importRelativeToBaseUrl, paths); - const nonRelative = fromPaths === undefined && baseUrl !== undefined ? importRelativeToBaseUrl : fromPaths; - if (!nonRelative) { - return relativePath; - } +export function getModuleSpecifiersWithCacheInfo( + moduleSymbol: Symbol, + checker: TypeChecker, + compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, + host: ModuleSpecifierResolutionHost, + userPreferences: UserPreferences, +): { moduleSpecifiers: readonly string[], computedWithoutCache: boolean } { + let computedWithoutCache = false; + const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol, checker); + if (ambient) return { moduleSpecifiers: [ambient], computedWithoutCache }; + + // eslint-disable-next-line prefer-const + let [specifiers, moduleSourceFile, modulePaths, cache] = tryGetModuleSpecifiersFromCacheWorker( + moduleSymbol, + importingSourceFile, + host, + userPreferences, + ); + if (specifiers) return { moduleSpecifiers: specifiers, computedWithoutCache }; + if (!moduleSourceFile) return { moduleSpecifiers: emptyArray, computedWithoutCache }; + + computedWithoutCache = true; + modulePaths ||= getAllModulePathsWorker(importingSourceFile.path, moduleSourceFile.originalFileName, host); + const result = computeModuleSpecifiers(modulePaths, compilerOptions, importingSourceFile, host, userPreferences); + cache?.set(importingSourceFile.path, moduleSourceFile.path, userPreferences, modulePaths, result); + return { moduleSpecifiers: result, computedWithoutCache }; +} - if (relativePreference === RelativePreference.NonRelative) { - return nonRelative; +function computeModuleSpecifiers( + modulePaths: readonly ModulePath[], + compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, + host: ModuleSpecifierResolutionHost, + userPreferences: UserPreferences, +): readonly string[] { + const info = getInfo(importingSourceFile.path, host); + const preferences = getPreferences(host, userPreferences, compilerOptions, importingSourceFile); + const existingSpecifier = forEach(modulePaths, modulePath => forEach( + host.getFileIncludeReasons().get(toPath(modulePath.path, host.getCurrentDirectory(), info.getCanonicalFileName)), + reason => { + if (reason.kind !== FileIncludeKind.Import || reason.file !== importingSourceFile.path) return undefined; + const specifier = getModuleNameStringLiteralAt(importingSourceFile, reason.index).text; + // If the preference is for non relative and the module specifier is relative, ignore it + return preferences.relativePreference !== RelativePreference.NonRelative || !pathIsRelative(specifier) ? + specifier : + undefined; + } + )); + if (existingSpecifier) { + const moduleSpecifiers = [existingSpecifier]; + return moduleSpecifiers; + } + + const importedFileIsInNodeModules = some(modulePaths, p => p.isInNodeModules); + + // Module specifier priority: + // 1. "Bare package specifiers" (e.g. "@foo/bar") resulting from a path through node_modules to a package.json's "types" entry + // 2. Specifiers generated using "paths" from tsconfig + // 3. Non-relative specfiers resulting from a path through node_modules (e.g. "@foo/bar/path/to/file") + // 4. Relative paths + let nodeModulesSpecifiers: string[] | undefined; + let pathsSpecifiers: string[] | undefined; + let relativeSpecifiers: string[] | undefined; + for (const modulePath of modulePaths) { + const specifier = tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions); + nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier); + if (specifier && modulePath.isRedirect) { + // If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar", + // not "@foo/bar/path/to/file"). No other specifier will be this good, so stop looking. + return nodeModulesSpecifiers!; + } + + if (!specifier && !modulePath.isRedirect) { + const local = getLocalModuleSpecifier(modulePath.path, info, compilerOptions, host, preferences); + if (pathIsBareSpecifier(local)) { + pathsSpecifiers = append(pathsSpecifiers, local); + } + else if (!importedFileIsInNodeModules || modulePath.isInNodeModules) { + // Why this extra conditional, not just an `else`? If some path to the file contained + // 'node_modules', but we can't create a non-relative specifier (e.g. "@foo/bar/path/to/file"), + // that means we had to go through a *sibling's* node_modules, not one we can access directly. + // If some path to the file was in node_modules but another was not, this likely indicates that + // we have a monorepo structure with symlinks. In this case, the non-node_modules path is + // probably the realpath, e.g. "../bar/path/to/file", but a relative path to another package + // in a monorepo is probably not portable. So, the module specifier we actually go with will be + // the relative path through node_modules, so that the declaration emitter can produce a + // portability error. (See declarationEmitReexportedSymlinkReference3) + relativeSpecifiers = append(relativeSpecifiers, local); + } } + } - if (relativePreference === RelativePreference.ExternalNonRelative) { - const projectDirectory = compilerOptions.configFilePath ? - toPath(getDirectoryPath(compilerOptions.configFilePath), host.getCurrentDirectory(), info.getCanonicalFileName) : - info.getCanonicalFileName(host.getCurrentDirectory()); - const modulePath = toPath(moduleFileName, projectDirectory, getCanonicalFileName); - const sourceIsInternal = startsWith(sourceDirectory, projectDirectory); - const targetIsInternal = startsWith(modulePath, projectDirectory); - if (sourceIsInternal && !targetIsInternal || !sourceIsInternal && targetIsInternal) { - // 1. The import path crosses the boundary of the tsconfig.json-containing directory. - // - // src/ - // tsconfig.json - // index.ts ------- - // lib/ | (path crosses tsconfig.json) - // imported.ts <--- - // - return nonRelative; - } + return pathsSpecifiers?.length ? pathsSpecifiers : + nodeModulesSpecifiers?.length ? nodeModulesSpecifiers : + Debug.checkDefined(relativeSpecifiers); +} - const nearestTargetPackageJson = getNearestAncestorDirectoryWithPackageJson(host, getDirectoryPath(modulePath)); - const nearestSourcePackageJson = getNearestAncestorDirectoryWithPackageJson(host, sourceDirectory); - if (nearestSourcePackageJson !== nearestTargetPackageJson) { - // 2. The importing and imported files are part of different packages. - // - // packages/a/ - // package.json - // index.ts -------- - // packages/b/ | (path crosses package.json) - // package.json | - // component.ts <--- - // - return nonRelative; - } +interface Info { + readonly getCanonicalFileName: GetCanonicalFileName; + readonly importingSourceFileName: Path + readonly sourceDirectory: Path; +} +// importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path +function getInfo(importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Info { + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true); + const sourceDirectory = getDirectoryPath(importingSourceFileName); + return { getCanonicalFileName, importingSourceFileName, sourceDirectory }; +} - return relativePath; +function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, { ending, relativePreference }: Preferences): string { + const { baseUrl, paths, rootDirs } = compilerOptions; + const { sourceDirectory, getCanonicalFileName } = info; + const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, ending, compilerOptions) || + removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), ending, compilerOptions); + if (!baseUrl && !paths || relativePreference === RelativePreference.Relative) { + return relativePath; + } + + const baseDirectory = getNormalizedAbsolutePath(getPathsBasePath(compilerOptions, host) || baseUrl!, host.getCurrentDirectory()); + const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseDirectory, getCanonicalFileName); + if (!relativeToBaseUrl) { + return relativePath; + } + + const importRelativeToBaseUrl = removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions); + const fromPaths = paths && tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl), importRelativeToBaseUrl, paths); + const nonRelative = fromPaths === undefined && baseUrl !== undefined ? importRelativeToBaseUrl : fromPaths; + if (!nonRelative) { + return relativePath; + } + + if (relativePreference === RelativePreference.NonRelative) { + return nonRelative; + } + + if (relativePreference === RelativePreference.ExternalNonRelative) { + const projectDirectory = compilerOptions.configFilePath ? + toPath(getDirectoryPath(compilerOptions.configFilePath), host.getCurrentDirectory(), info.getCanonicalFileName) : + info.getCanonicalFileName(host.getCurrentDirectory()); + const modulePath = toPath(moduleFileName, projectDirectory, getCanonicalFileName); + const sourceIsInternal = startsWith(sourceDirectory, projectDirectory); + const targetIsInternal = startsWith(modulePath, projectDirectory); + if (sourceIsInternal && !targetIsInternal || !sourceIsInternal && targetIsInternal) { + // 1. The import path crosses the boundary of the tsconfig.json-containing directory. + // + // src/ + // tsconfig.json + // index.ts ------- + // lib/ | (path crosses tsconfig.json) + // imported.ts <--- + // + return nonRelative; } - if (relativePreference !== RelativePreference.Shortest) Debug.assertNever(relativePreference); + const nearestTargetPackageJson = getNearestAncestorDirectoryWithPackageJson(host, getDirectoryPath(modulePath)); + const nearestSourcePackageJson = getNearestAncestorDirectoryWithPackageJson(host, sourceDirectory); + if (nearestSourcePackageJson !== nearestTargetPackageJson) { + // 2. The importing and imported files are part of different packages. + // + // packages/a/ + // package.json + // index.ts -------- + // packages/b/ | (path crosses package.json) + // package.json | + // component.ts <--- + // + return nonRelative; + } - // Prefer a relative import over a baseUrl import if it has fewer components. - return isPathRelativeToParent(nonRelative) || countPathComponents(relativePath) < countPathComponents(nonRelative) ? relativePath : nonRelative; + return relativePath; } - export function countPathComponents(path: string): number { - let count = 0; - for (let i = startsWith(path, "./") ? 2 : 0; i < path.length; i++) { - if (path.charCodeAt(i) === CharacterCodes.slash) count++; - } - return count; - } + if (relativePreference !== RelativePreference.Shortest) Debug.assertNever(relativePreference); - function usesJsExtensionOnImports({ imports }: SourceFile): boolean { - return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJSFileExtension(text) : undefined) || false; - } + // Prefer a relative import over a baseUrl import if it has fewer components. + return isPathRelativeToParent(nonRelative) || countPathComponents(relativePath) < countPathComponents(nonRelative) ? relativePath : nonRelative; +} - function comparePathsByRedirectAndNumberOfDirectorySeparators(a: ModulePath, b: ModulePath) { - return compareBooleans(b.isRedirect, a.isRedirect) || compareNumberOfDirectorySeparators(a.path, b.path); +export function countPathComponents(path: string): number { + let count = 0; + for (let i = startsWith(path, "./") ? 2 : 0; i < path.length; i++) { + if (path.charCodeAt(i) === CharacterCodes.slash) count++; } + return count; +} - function getNearestAncestorDirectoryWithPackageJson(host: ModuleSpecifierResolutionHost, fileName: string) { - if (host.getNearestAncestorDirectoryWithPackageJson) { - return host.getNearestAncestorDirectoryWithPackageJson(fileName); - } - return !!forEachAncestorDirectory(fileName, directory => { - return host.fileExists(combinePaths(directory, "package.json")) ? true : undefined; - }); - } +function usesJsExtensionOnImports({ imports }: SourceFile): boolean { + return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJSFileExtension(text) : undefined) || false; +} - export function forEachFileNameOfModule( - importingFileName: string, - importedFileName: string, - host: ModuleSpecifierResolutionHost, - preferSymlinks: boolean, - cb: (fileName: string, isRedirect: boolean) => T | undefined - ): T | undefined { - const getCanonicalFileName = hostGetCanonicalFileName(host); - const cwd = host.getCurrentDirectory(); - const referenceRedirect = host.isSourceOfProjectReferenceRedirect(importedFileName) ? host.getProjectReferenceRedirect(importedFileName) : undefined; - const importedPath = toPath(importedFileName, cwd, getCanonicalFileName); - const redirects = host.redirectTargetsMap.get(importedPath) || emptyArray; - const importedFileNames = [...(referenceRedirect ? [referenceRedirect] : emptyArray), importedFileName, ...redirects]; - const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd)); - let shouldFilterIgnoredPaths = !every(targets, containsIgnoredPath); - - if (!preferSymlinks) { - // Symlinks inside ignored paths are already filtered out of the symlink cache, - // so we only need to remove them from the realpath filenames. - const result = forEach(targets, p => !(shouldFilterIgnoredPaths && containsIgnoredPath(p)) && cb(p, referenceRedirect === p)); - if (result) return result; - } +function comparePathsByRedirectAndNumberOfDirectorySeparators(a: ModulePath, b: ModulePath) { + return compareBooleans(b.isRedirect, a.isRedirect) || compareNumberOfDirectorySeparators(a.path, b.path); +} - const symlinkedDirectories = host.getSymlinkCache?.().getSymlinkedDirectoriesByRealpath(); - const fullImportedFileName = getNormalizedAbsolutePath(importedFileName, cwd); - const result = symlinkedDirectories && forEachAncestorDirectory(getDirectoryPath(fullImportedFileName), realPathDirectory => { - const symlinkDirectories = symlinkedDirectories.get(ensureTrailingDirectorySeparator(toPath(realPathDirectory, cwd, getCanonicalFileName))); - if (!symlinkDirectories) return undefined; // Continue to ancestor directory +function getNearestAncestorDirectoryWithPackageJson(host: ModuleSpecifierResolutionHost, fileName: string) { + if (host.getNearestAncestorDirectoryWithPackageJson) { + return host.getNearestAncestorDirectoryWithPackageJson(fileName); + } + return !!forEachAncestorDirectory(fileName, directory => { + return host.fileExists(combinePaths(directory, "package.json")) ? true : undefined; + }); +} - // Don't want to a package to globally import from itself (importNameCodeFix_symlink_own_package.ts) - if (startsWithDirectory(importingFileName, realPathDirectory, getCanonicalFileName)) { - return false; // Stop search, each ancestor directory will also hit this condition +export function forEachFileNameOfModule( + importingFileName: string, + importedFileName: string, + host: ModuleSpecifierResolutionHost, + preferSymlinks: boolean, + cb: (fileName: string, isRedirect: boolean) => T | undefined +): T | undefined { + const getCanonicalFileName = hostGetCanonicalFileName(host); + const cwd = host.getCurrentDirectory(); + const referenceRedirect = host.isSourceOfProjectReferenceRedirect(importedFileName) ? host.getProjectReferenceRedirect(importedFileName) : undefined; + const importedPath = toPath(importedFileName, cwd, getCanonicalFileName); + const redirects = host.redirectTargetsMap.get(importedPath) || emptyArray; + const importedFileNames = [...(referenceRedirect ? [referenceRedirect] : emptyArray), importedFileName, ...redirects]; + const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd)); + let shouldFilterIgnoredPaths = !every(targets, containsIgnoredPath); + + if (!preferSymlinks) { + // Symlinks inside ignored paths are already filtered out of the symlink cache, + // so we only need to remove them from the realpath filenames. + const result = forEach(targets, p => !(shouldFilterIgnoredPaths && containsIgnoredPath(p)) && cb(p, referenceRedirect === p)); + if (result) return result; + } + + const symlinkedDirectories = host.getSymlinkCache?.().getSymlinkedDirectoriesByRealpath(); + const fullImportedFileName = getNormalizedAbsolutePath(importedFileName, cwd); + const result = symlinkedDirectories && forEachAncestorDirectory(getDirectoryPath(fullImportedFileName), realPathDirectory => { + const symlinkDirectories = symlinkedDirectories.get(ensureTrailingDirectorySeparator(toPath(realPathDirectory, cwd, getCanonicalFileName))); + if (!symlinkDirectories) return undefined; // Continue to ancestor directory + + // Don't want to a package to globally import from itself (importNameCodeFix_symlink_own_package.ts) + if (startsWithDirectory(importingFileName, realPathDirectory, getCanonicalFileName)) { + return false; // Stop search, each ancestor directory will also hit this condition + } + + return forEach(targets, target => { + if (!startsWithDirectory(target, realPathDirectory, getCanonicalFileName)) { + return; } - return forEach(targets, target => { - if (!startsWithDirectory(target, realPathDirectory, getCanonicalFileName)) { - return; - } - - const relative = getRelativePathFromDirectory(realPathDirectory, target, getCanonicalFileName); - for (const symlinkDirectory of symlinkDirectories) { - const option = resolvePath(symlinkDirectory, relative); - const result = cb(option, target === referenceRedirect); - shouldFilterIgnoredPaths = true; // We found a non-ignored path in symlinks, so we can reject ignored-path realpaths - if (result) return result; - } - }); + const relative = getRelativePathFromDirectory(realPathDirectory, target, getCanonicalFileName); + for (const symlinkDirectory of symlinkDirectories) { + const option = resolvePath(symlinkDirectory, relative); + const result = cb(option, target === referenceRedirect); + shouldFilterIgnoredPaths = true; // We found a non-ignored path in symlinks, so we can reject ignored-path realpaths + if (result) return result; + } }); - return result || (preferSymlinks - ? forEach(targets, p => shouldFilterIgnoredPaths && containsIgnoredPath(p) ? undefined : cb(p, p === referenceRedirect)) - : undefined); - } + }); + return result || (preferSymlinks + ? forEach(targets, p => shouldFilterIgnoredPaths && containsIgnoredPath(p) ? undefined : cb(p, p === referenceRedirect)) + : undefined); +} - /** - * Looks for existing imports that use symlinks to this module. - * Symlinks will be returned first so they are preferred over the real path. - */ - function getAllModulePaths( - importingFilePath: Path, - importedFileName: string, - host: ModuleSpecifierResolutionHost, - preferences: UserPreferences, - importedFilePath = toPath(importedFileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host)) +/** + * Looks for existing imports that use symlinks to this module. + * Symlinks will be returned first so they are preferred over the real path. + */ +function getAllModulePaths( + importingFilePath: Path, + importedFileName: string, + host: ModuleSpecifierResolutionHost, + preferences: UserPreferences, + importedFilePath = toPath(importedFileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host)) +) { + const cache = host.getModuleSpecifierCache?.(); + if (cache) { + const cached = cache.get(importingFilePath, importedFilePath, preferences); + if (cached?.modulePaths) return cached.modulePaths; + } + const modulePaths = getAllModulePathsWorker(importingFilePath, importedFileName, host); + if (cache) { + cache.setModulePaths(importingFilePath, importedFilePath, preferences, modulePaths); + } + return modulePaths; +} + +function getAllModulePathsWorker(importingFileName: Path, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly ModulePath[] { + const getCanonicalFileName = hostGetCanonicalFileName(host); + const allFileNames = new Map(); + let importedFileFromNodeModules = false; + forEachFileNameOfModule( + importingFileName, + importedFileName, + host, + /*preferSymlinks*/ true, + (path, isRedirect) => { + const isInNodeModules = pathContainsNodeModules(path); + allFileNames.set(path, { path: getCanonicalFileName(path), isRedirect, isInNodeModules }); + importedFileFromNodeModules = importedFileFromNodeModules || isInNodeModules; + // don't return value, so we collect everything + } + ); + + // Sort by paths closest to importing file Name directory + const sortedPaths: ModulePath[] = []; + for ( + let directory = getDirectoryPath(importingFileName); + allFileNames.size !== 0; ) { - const cache = host.getModuleSpecifierCache?.(); - if (cache) { - const cached = cache.get(importingFilePath, importedFilePath, preferences); - if (cached?.modulePaths) return cached.modulePaths; - } - const modulePaths = getAllModulePathsWorker(importingFilePath, importedFileName, host); - if (cache) { - cache.setModulePaths(importingFilePath, importedFilePath, preferences, modulePaths); - } - return modulePaths; - } - - function getAllModulePathsWorker(importingFileName: Path, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly ModulePath[] { - const getCanonicalFileName = hostGetCanonicalFileName(host); - const allFileNames = new Map(); - let importedFileFromNodeModules = false; - forEachFileNameOfModule( - importingFileName, - importedFileName, - host, - /*preferSymlinks*/ true, - (path, isRedirect) => { - const isInNodeModules = pathContainsNodeModules(path); - allFileNames.set(path, { path: getCanonicalFileName(path), isRedirect, isInNodeModules }); - importedFileFromNodeModules = importedFileFromNodeModules || isInNodeModules; - // don't return value, so we collect everything + const directoryStart = ensureTrailingDirectorySeparator(directory); + let pathsInDirectory: ModulePath[] | undefined; + allFileNames.forEach(({ path, isRedirect, isInNodeModules }, fileName) => { + if (startsWith(path, directoryStart)) { + (pathsInDirectory ||= []).push({ path: fileName, isRedirect, isInNodeModules }); + allFileNames.delete(fileName); } - ); - - // Sort by paths closest to importing file Name directory - const sortedPaths: ModulePath[] = []; - for ( - let directory = getDirectoryPath(importingFileName); - allFileNames.size !== 0; - ) { - const directoryStart = ensureTrailingDirectorySeparator(directory); - let pathsInDirectory: ModulePath[] | undefined; - allFileNames.forEach(({ path, isRedirect, isInNodeModules }, fileName) => { - if (startsWith(path, directoryStart)) { - (pathsInDirectory ||= []).push({ path: fileName, isRedirect, isInNodeModules }); - allFileNames.delete(fileName); - } - }); - if (pathsInDirectory) { - if (pathsInDirectory.length > 1) { - pathsInDirectory.sort(comparePathsByRedirectAndNumberOfDirectorySeparators); - } - sortedPaths.push(...pathsInDirectory); + }); + if (pathsInDirectory) { + if (pathsInDirectory.length > 1) { + pathsInDirectory.sort(comparePathsByRedirectAndNumberOfDirectorySeparators); } - const newDirectory = getDirectoryPath(directory); - if (newDirectory === directory) break; - directory = newDirectory; - } - if (allFileNames.size) { - const remainingPaths = arrayFrom(allFileNames.values()); - if (remainingPaths.length > 1) remainingPaths.sort(comparePathsByRedirectAndNumberOfDirectorySeparators); - sortedPaths.push(...remainingPaths); + sortedPaths.push(...pathsInDirectory); } - - return sortedPaths; + const newDirectory = getDirectoryPath(directory); + if (newDirectory === directory) break; + directory = newDirectory; + } + if (allFileNames.size) { + const remainingPaths = arrayFrom(allFileNames.values()); + if (remainingPaths.length > 1) remainingPaths.sort(comparePathsByRedirectAndNumberOfDirectorySeparators); + sortedPaths.push(...remainingPaths); } - function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol, checker: TypeChecker): string | undefined { - const decl = moduleSymbol.declarations?.find( - d => isNonGlobalAmbientModule(d) && (!isExternalModuleAugmentation(d) || !isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(d.name))) - ) as (ModuleDeclaration & { name: StringLiteral }) | undefined; - if (decl) { - return decl.name.text; - } + return sortedPaths; +} - // the module could be a namespace, which is export through "export=" from an ambient module. - /** - * declare module "m" { - * namespace ns { - * class c {} - * } - * export = ns; - * } - */ - // `import {c} from "m";` is valid, in which case, `moduleSymbol` is "ns", but the module name should be "m" - const ambientModuleDeclareCandidates = mapDefined(moduleSymbol.declarations, - d => { - if (!isModuleDeclaration(d)) return; - const topNamespace = getTopNamespace(d); - if (!(topNamespace?.parent?.parent - && isModuleBlock(topNamespace.parent) && isAmbientModule(topNamespace.parent.parent) && isSourceFile(topNamespace.parent.parent.parent))) return; - const exportAssignment = ((topNamespace.parent.parent.symbol.exports?.get("export=" as __String)?.valueDeclaration as ExportAssignment)?.expression as PropertyAccessExpression | Identifier); - if (!exportAssignment) return; - const exportSymbol = checker.getSymbolAtLocation(exportAssignment); - if (!exportSymbol) return; - const originalExportSymbol = exportSymbol?.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(exportSymbol) : exportSymbol; - if (originalExportSymbol === d.symbol) return topNamespace.parent.parent; - - function getTopNamespace(namespaceDeclaration: ModuleDeclaration) { - while (namespaceDeclaration.flags & NodeFlags.NestedNamespace) { - namespaceDeclaration = namespaceDeclaration.parent as ModuleDeclaration; - } - return namespaceDeclaration; +function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol, checker: TypeChecker): string | undefined { + const decl = moduleSymbol.declarations?.find( + d => isNonGlobalAmbientModule(d) && (!isExternalModuleAugmentation(d) || !isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(d.name))) + ) as (ModuleDeclaration & { name: StringLiteral }) | undefined; + if (decl) { + return decl.name.text; + } + + // the module could be a namespace, which is export through "export=" from an ambient module. + /** + * declare module "m" { + * namespace ns { + * class c {} + * } + * export = ns; + * } + */ + // `import {c} from "m";` is valid, in which case, `moduleSymbol` is "ns", but the module name should be "m" + const ambientModuleDeclareCandidates = mapDefined(moduleSymbol.declarations, + d => { + if (!isModuleDeclaration(d)) return; + const topNamespace = getTopNamespace(d); + if (!(topNamespace?.parent?.parent + && isModuleBlock(topNamespace.parent) && isAmbientModule(topNamespace.parent.parent) && isSourceFile(topNamespace.parent.parent.parent))) return; + const exportAssignment = ((topNamespace.parent.parent.symbol.exports?.get("export=" as __String)?.valueDeclaration as ExportAssignment)?.expression as PropertyAccessExpression | Identifier); + if (!exportAssignment) return; + const exportSymbol = checker.getSymbolAtLocation(exportAssignment); + if (!exportSymbol) return; + const originalExportSymbol = exportSymbol?.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(exportSymbol) : exportSymbol; + if (originalExportSymbol === d.symbol) return topNamespace.parent.parent; + + function getTopNamespace(namespaceDeclaration: ModuleDeclaration) { + while (namespaceDeclaration.flags & NodeFlags.NestedNamespace) { + namespaceDeclaration = namespaceDeclaration.parent as ModuleDeclaration; } + return namespaceDeclaration; } - ); - const ambientModuleDeclare = ambientModuleDeclareCandidates[0] as (AmbientModuleDeclaration & { name: StringLiteral }) | undefined; - if (ambientModuleDeclare) { - return ambientModuleDeclare.name.text; } + ); + const ambientModuleDeclare = ambientModuleDeclareCandidates[0] as (AmbientModuleDeclaration & { name: StringLiteral }) | undefined; + if (ambientModuleDeclare) { + return ambientModuleDeclare.name.text; } +} - function tryGetModuleNameFromPaths(relativeToBaseUrlWithIndex: string, relativeToBaseUrl: string, paths: MapLike): string | undefined { - for (const key in paths) { - for (const patternText of paths[key]) { - const pattern = removeFileExtension(normalizePath(patternText)); - const indexOfStar = pattern.indexOf("*"); - if (indexOfStar !== -1) { - const prefix = pattern.substr(0, indexOfStar); - const suffix = pattern.substr(indexOfStar + 1); - if (relativeToBaseUrl.length >= prefix.length + suffix.length && - startsWith(relativeToBaseUrl, prefix) && - endsWith(relativeToBaseUrl, suffix) || - !suffix && relativeToBaseUrl === removeTrailingDirectorySeparator(prefix)) { - const matchedStar = relativeToBaseUrl.substr(prefix.length, relativeToBaseUrl.length - suffix.length - prefix.length); - return key.replace("*", matchedStar); - } - } - else if (pattern === relativeToBaseUrl || pattern === relativeToBaseUrlWithIndex) { - return key; +function tryGetModuleNameFromPaths(relativeToBaseUrlWithIndex: string, relativeToBaseUrl: string, paths: MapLike): string | undefined { + for (const key in paths) { + for (const patternText of paths[key]) { + const pattern = removeFileExtension(normalizePath(patternText)); + const indexOfStar = pattern.indexOf("*"); + if (indexOfStar !== -1) { + const prefix = pattern.substr(0, indexOfStar); + const suffix = pattern.substr(indexOfStar + 1); + if (relativeToBaseUrl.length >= prefix.length + suffix.length && + startsWith(relativeToBaseUrl, prefix) && + endsWith(relativeToBaseUrl, suffix) || + !suffix && relativeToBaseUrl === removeTrailingDirectorySeparator(prefix)) { + const matchedStar = relativeToBaseUrl.substr(prefix.length, relativeToBaseUrl.length - suffix.length - prefix.length); + return key.replace("*", matchedStar); } } + else if (pattern === relativeToBaseUrl || pattern === relativeToBaseUrlWithIndex) { + return key; + } } } +} - const enum MatchingMode { - Exact, - Directory, - Pattern - } +const enum MatchingMode { + Exact, + Directory, + Pattern +} - function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode = MatchingMode.Exact): { moduleFileToTry: string } | undefined { - if (typeof exports === "string") { - const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined); - const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined; - switch (mode) { - case MatchingMode.Exact: - if (comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo || (extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo)) { - return { moduleFileToTry: packageName }; - } - break; - case MatchingMode.Directory: - if (containsPath(pathOrPattern, targetFilePath)) { - const fragment = getRelativePathFromDirectory(pathOrPattern, targetFilePath, /*ignoreCase*/ false); - return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; - } - break; - case MatchingMode.Pattern: - const starPos = pathOrPattern.indexOf("*"); - const leadingSlice = pathOrPattern.slice(0, starPos); - const trailingSlice = pathOrPattern.slice(starPos + 1); - if (startsWith(targetFilePath, leadingSlice) && endsWith(targetFilePath, trailingSlice)) { - const starReplacement = targetFilePath.slice(leadingSlice.length, targetFilePath.length - trailingSlice.length); - return { moduleFileToTry: packageName.replace("*", starReplacement) }; - } - if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice) && endsWith(extensionSwappedTarget, trailingSlice)) { - const starReplacement = extensionSwappedTarget.slice(leadingSlice.length, extensionSwappedTarget.length - trailingSlice.length); - return { moduleFileToTry: packageName.replace("*", starReplacement) }; - } - break; - } - } - else if (Array.isArray(exports)) { - return forEach(exports, e => tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, e, conditions)); +function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode = MatchingMode.Exact): { moduleFileToTry: string } | undefined { + if (typeof exports === "string") { + const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined); + const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined; + switch (mode) { + case MatchingMode.Exact: + if (comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo || (extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo)) { + return { moduleFileToTry: packageName }; + } + break; + case MatchingMode.Directory: + if (containsPath(pathOrPattern, targetFilePath)) { + const fragment = getRelativePathFromDirectory(pathOrPattern, targetFilePath, /*ignoreCase*/ false); + return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; + } + break; + case MatchingMode.Pattern: + const starPos = pathOrPattern.indexOf("*"); + const leadingSlice = pathOrPattern.slice(0, starPos); + const trailingSlice = pathOrPattern.slice(starPos + 1); + if (startsWith(targetFilePath, leadingSlice) && endsWith(targetFilePath, trailingSlice)) { + const starReplacement = targetFilePath.slice(leadingSlice.length, targetFilePath.length - trailingSlice.length); + return { moduleFileToTry: packageName.replace("*", starReplacement) }; + } + if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice) && endsWith(extensionSwappedTarget, trailingSlice)) { + const starReplacement = extensionSwappedTarget.slice(leadingSlice.length, extensionSwappedTarget.length - trailingSlice.length); + return { moduleFileToTry: packageName.replace("*", starReplacement) }; + } + break; + } + } + else if (Array.isArray(exports)) { + return forEach(exports, e => tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, e, conditions)); + } + else if (typeof exports === "object" && exports !== null) { // eslint-disable-line no-null/no-null + if (allKeysStartWithDot(exports as MapLike)) { + // sub-mappings + // 3 cases: + // * directory mappings (legacyish, key ends with / (technically allows index/extension resolution under cjs mode)) + // * pattern mappings (contains a *) + // * exact mappings (no *, does not end with /) + return forEach(getOwnKeys(exports as MapLike), k => { + const subPackageName = getNormalizedAbsolutePath(combinePaths(packageName, k), /*currentDirectory*/ undefined); + const mode = endsWith(k, "/") ? MatchingMode.Directory + : stringContains(k, "*") ? MatchingMode.Pattern + : MatchingMode.Exact; + return tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, subPackageName, (exports as MapLike)[k], conditions, mode); + }); } - else if (typeof exports === "object" && exports !== null) { // eslint-disable-line no-null/no-null - if (allKeysStartWithDot(exports as MapLike)) { - // sub-mappings - // 3 cases: - // * directory mappings (legacyish, key ends with / (technically allows index/extension resolution under cjs mode)) - // * pattern mappings (contains a *) - // * exact mappings (no *, does not end with /) - return forEach(getOwnKeys(exports as MapLike), k => { - const subPackageName = getNormalizedAbsolutePath(combinePaths(packageName, k), /*currentDirectory*/ undefined); - const mode = endsWith(k, "/") ? MatchingMode.Directory - : stringContains(k, "*") ? MatchingMode.Pattern - : MatchingMode.Exact; - return tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, subPackageName, (exports as MapLike)[k], conditions, mode); - }); - } - else { - // conditional mapping - for (const key of getOwnKeys(exports as MapLike)) { - if (key === "default" || conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(conditions, key)) { - const subTarget = (exports as MapLike)[key]; - const result = tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, subTarget, conditions); - if (result) { - return result; - } + else { + // conditional mapping + for (const key of getOwnKeys(exports as MapLike)) { + if (key === "default" || conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(conditions, key)) { + const subTarget = (exports as MapLike)[key]; + const result = tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, subTarget, conditions); + if (result) { + return result; } } } } + } + return undefined; +} + +function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, ending: Ending, compilerOptions: CompilerOptions): string | undefined { + const normalizedTargetPath = getPathRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName); + if (normalizedTargetPath === undefined) { return undefined; } - function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, ending: Ending, compilerOptions: CompilerOptions): string | undefined { - const normalizedTargetPath = getPathRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName); - if (normalizedTargetPath === undefined) { - return undefined; - } + const normalizedSourcePath = getPathRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName); + const relativePath = normalizedSourcePath !== undefined ? ensurePathIsNonModuleName(getRelativePathFromDirectory(normalizedSourcePath, normalizedTargetPath, getCanonicalFileName)) : normalizedTargetPath; + return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs + ? removeExtensionAndIndexPostFix(relativePath, ending, compilerOptions) + : removeFileExtension(relativePath); +} - const normalizedSourcePath = getPathRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName); - const relativePath = normalizedSourcePath !== undefined ? ensurePathIsNonModuleName(getRelativePathFromDirectory(normalizedSourcePath, normalizedTargetPath, getCanonicalFileName)) : normalizedTargetPath; - return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs - ? removeExtensionAndIndexPostFix(relativePath, ending, compilerOptions) - : removeFileExtension(relativePath); +function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean): string | undefined { + if (!host.fileExists || !host.readFile) { + return undefined; + } + const parts: NodeModulePathParts = getNodeModulePathParts(path)!; + if (!parts) { + return undefined; } - function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean): string | undefined { - if (!host.fileExists || !host.readFile) { - return undefined; - } - const parts: NodeModulePathParts = getNodeModulePathParts(path)!; - if (!parts) { - return undefined; - } + // Simplify the full file path to something that can be resolved by Node. - // Simplify the full file path to something that can be resolved by Node. - - let moduleSpecifier = path; - let isPackageRootPath = false; - if (!packageNameOnly) { - let packageRootIndex = parts.packageRootIndex; - let moduleFileNameForExtensionless: string | undefined; - while (true) { - // If the module could be imported by a directory name, use that directory's name - const { moduleFileToTry, packageRootPath, blockedByExports, verbatimFromExports } = tryDirectoryWithPackageJson(packageRootIndex); - if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Classic) { - if (blockedByExports) { - return undefined; // File is under this package.json, but is not publicly exported - there's no way to name it via `node_modules` resolution - } - if (verbatimFromExports) { - return moduleFileToTry; - } + let moduleSpecifier = path; + let isPackageRootPath = false; + if (!packageNameOnly) { + let packageRootIndex = parts.packageRootIndex; + let moduleFileNameForExtensionless: string | undefined; + while (true) { + // If the module could be imported by a directory name, use that directory's name + const { moduleFileToTry, packageRootPath, blockedByExports, verbatimFromExports } = tryDirectoryWithPackageJson(packageRootIndex); + if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Classic) { + if (blockedByExports) { + return undefined; // File is under this package.json, but is not publicly exported - there's no way to name it via `node_modules` resolution } - if (packageRootPath) { - moduleSpecifier = packageRootPath; - isPackageRootPath = true; - break; + if (verbatimFromExports) { + return moduleFileToTry; } - if (!moduleFileNameForExtensionless) moduleFileNameForExtensionless = moduleFileToTry; + } + if (packageRootPath) { + moduleSpecifier = packageRootPath; + isPackageRootPath = true; + break; + } + if (!moduleFileNameForExtensionless) moduleFileNameForExtensionless = moduleFileToTry; - // try with next level of directory - packageRootIndex = path.indexOf(directorySeparator, packageRootIndex + 1); - if (packageRootIndex === -1) { - moduleSpecifier = getExtensionlessFileName(moduleFileNameForExtensionless); - break; - } + // try with next level of directory + packageRootIndex = path.indexOf(directorySeparator, packageRootIndex + 1); + if (packageRootIndex === -1) { + moduleSpecifier = getExtensionlessFileName(moduleFileNameForExtensionless); + break; } } + } - if (isRedirect && !isPackageRootPath) { - return undefined; - } + if (isRedirect && !isPackageRootPath) { + return undefined; + } - const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation(); - // Get a path that's relative to node_modules or the importing file's path - // if node_modules folder is in this folder or any of its parent folders, no need to keep it. - const pathToTopLevelNodeModules = getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex)); - if (!(startsWith(sourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) { - return undefined; - } + const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation(); + // Get a path that's relative to node_modules or the importing file's path + // if node_modules folder is in this folder or any of its parent folders, no need to keep it. + const pathToTopLevelNodeModules = getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex)); + if (!(startsWith(sourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) { + return undefined; + } - // If the module was found in @types, get the actual Node package name - const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1); - const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName); - // For classic resolution, only allow importing from node_modules/@types, not other node_modules - return getEmitModuleResolutionKind(options) === ModuleResolutionKind.Classic && packageName === nodeModulesDirectoryName ? undefined : packageName; - - function tryDirectoryWithPackageJson(packageRootIndex: number): { moduleFileToTry: string, packageRootPath?: string, blockedByExports?: true, verbatimFromExports?: true } { - const packageRootPath = path.substring(0, packageRootIndex); - const packageJsonPath = combinePaths(packageRootPath, "package.json"); - let moduleFileToTry = path; - if (host.fileExists(packageJsonPath)) { - const packageJsonContent = JSON.parse(host.readFile!(packageJsonPath)!); - // TODO: Inject `require` or `import` condition based on the intended import mode - if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext) { - const fromExports = packageJsonContent.exports && typeof packageJsonContent.name === "string" ? tryGetModuleNameFromExports(options, path, packageRootPath, packageJsonContent.name, packageJsonContent.exports, ["node", "types"]) : undefined; - if (fromExports) { - const withJsExtension = !hasTSFileExtension(fromExports.moduleFileToTry) ? fromExports : { moduleFileToTry: removeFileExtension(fromExports.moduleFileToTry) + tryGetJSExtensionForFile(fromExports.moduleFileToTry, options) }; - return { ...withJsExtension, verbatimFromExports: true }; - } - if (packageJsonContent.exports) { - return { moduleFileToTry: path, blockedByExports: true }; - } + // If the module was found in @types, get the actual Node package name + const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1); + const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName); + // For classic resolution, only allow importing from node_modules/@types, not other node_modules + return getEmitModuleResolutionKind(options) === ModuleResolutionKind.Classic && packageName === nodeModulesDirectoryName ? undefined : packageName; + + function tryDirectoryWithPackageJson(packageRootIndex: number): { moduleFileToTry: string, packageRootPath?: string, blockedByExports?: true, verbatimFromExports?: true } { + const packageRootPath = path.substring(0, packageRootIndex); + const packageJsonPath = combinePaths(packageRootPath, "package.json"); + let moduleFileToTry = path; + if (host.fileExists(packageJsonPath)) { + const packageJsonContent = JSON.parse(host.readFile!(packageJsonPath)!); + // TODO: Inject `require` or `import` condition based on the intended import mode + if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext) { + const fromExports = packageJsonContent.exports && typeof packageJsonContent.name === "string" ? tryGetModuleNameFromExports(options, path, packageRootPath, packageJsonContent.name, packageJsonContent.exports, ["node", "types"]) : undefined; + if (fromExports) { + const withJsExtension = !hasTSFileExtension(fromExports.moduleFileToTry) ? fromExports : { moduleFileToTry: removeFileExtension(fromExports.moduleFileToTry) + tryGetJSExtensionForFile(fromExports.moduleFileToTry, options) }; + return { ...withJsExtension, verbatimFromExports: true }; } - const versionPaths = packageJsonContent.typesVersions - ? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions) - : undefined; - if (versionPaths) { - const subModuleName = path.slice(packageRootPath.length + 1); - const fromPaths = tryGetModuleNameFromPaths( - removeFileExtension(subModuleName), - removeExtensionAndIndexPostFix(subModuleName, Ending.Minimal, options), - versionPaths.paths - ); - if (fromPaths !== undefined) { - moduleFileToTry = combinePaths(packageRootPath, fromPaths); - } + if (packageJsonContent.exports) { + return { moduleFileToTry: path, blockedByExports: true }; } - // If the file is the main module, it can be imported by the package name - const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main; - if (isString(mainFileRelative)) { - const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName); - if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(moduleFileToTry))) { - return { packageRootPath, moduleFileToTry }; - } + } + const versionPaths = packageJsonContent.typesVersions + ? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions) + : undefined; + if (versionPaths) { + const subModuleName = path.slice(packageRootPath.length + 1); + const fromPaths = tryGetModuleNameFromPaths( + removeFileExtension(subModuleName), + removeExtensionAndIndexPostFix(subModuleName, Ending.Minimal, options), + versionPaths.paths + ); + if (fromPaths !== undefined) { + moduleFileToTry = combinePaths(packageRootPath, fromPaths); } } - return { moduleFileToTry }; - } - - function getExtensionlessFileName(path: string): string { - // We still have a file name - remove the extension - const fullModulePathWithoutExtension = removeFileExtension(path); - - // If the file is /index, it can be imported by its directory name - // IFF there is not _also_ a file by the same name - if (getCanonicalFileName(fullModulePathWithoutExtension.substring(parts.fileNameIndex)) === "/index" && !tryGetAnyFileFromPath(host, fullModulePathWithoutExtension.substring(0, parts.fileNameIndex))) { - return fullModulePathWithoutExtension.substring(0, parts.fileNameIndex); + // If the file is the main module, it can be imported by the package name + const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main; + if (isString(mainFileRelative)) { + const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName); + if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(moduleFileToTry))) { + return { packageRootPath, moduleFileToTry }; + } } - - return fullModulePathWithoutExtension; } + return { moduleFileToTry }; } - function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string) { - if (!host.fileExists) return; - // We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory - const extensions = flatten(getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }])); - for (const e of extensions) { - const fullPath = path + e; - if (host.fileExists(fullPath)) { - return fullPath; - } + function getExtensionlessFileName(path: string): string { + // We still have a file name - remove the extension + const fullModulePathWithoutExtension = removeFileExtension(path); + + // If the file is /index, it can be imported by its directory name + // IFF there is not _also_ a file by the same name + if (getCanonicalFileName(fullModulePathWithoutExtension.substring(parts.fileNameIndex)) === "/index" && !tryGetAnyFileFromPath(host, fullModulePathWithoutExtension.substring(0, parts.fileNameIndex))) { + return fullModulePathWithoutExtension.substring(0, parts.fileNameIndex); } - } - interface NodeModulePathParts { - readonly topLevelNodeModulesIndex: number; - readonly topLevelPackageNameIndex: number; - readonly packageRootIndex: number; - readonly fileNameIndex: number; + return fullModulePathWithoutExtension; } - function getNodeModulePathParts(fullPath: string): NodeModulePathParts | undefined { - // If fullPath can't be valid module file within node_modules, returns undefined. - // Example of expected pattern: /base/path/node_modules/[@scope/otherpackage/@otherscope/node_modules/]package/[subdirectory/]file.js - // Returns indices: ^ ^ ^ ^ - - let topLevelNodeModulesIndex = 0; - let topLevelPackageNameIndex = 0; - let packageRootIndex = 0; - let fileNameIndex = 0; +} - const enum States { - BeforeNodeModules, - NodeModules, - Scope, - PackageContent +function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string) { + if (!host.fileExists) return; + // We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory + const extensions = flatten(getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }])); + for (const e of extensions) { + const fullPath = path + e; + if (host.fileExists(fullPath)) { + return fullPath; } + } +} - let partStart = 0; - let partEnd = 0; - let state = States.BeforeNodeModules; - - while (partEnd >= 0) { - partStart = partEnd; - partEnd = fullPath.indexOf("/", partStart + 1); - switch (state) { - case States.BeforeNodeModules: - if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) { - topLevelNodeModulesIndex = partStart; - topLevelPackageNameIndex = partEnd; - state = States.NodeModules; - } - break; - case States.NodeModules: - case States.Scope: - if (state === States.NodeModules && fullPath.charAt(partStart + 1) === "@") { - state = States.Scope; - } - else { - packageRootIndex = partEnd; - state = States.PackageContent; - } - break; - case States.PackageContent: - if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) { - state = States.NodeModules; - } - else { - state = States.PackageContent; - } - break; - } +interface NodeModulePathParts { + readonly topLevelNodeModulesIndex: number; + readonly topLevelPackageNameIndex: number; + readonly packageRootIndex: number; + readonly fileNameIndex: number; +} +function getNodeModulePathParts(fullPath: string): NodeModulePathParts | undefined { + // If fullPath can't be valid module file within node_modules, returns undefined. + // Example of expected pattern: /base/path/node_modules/[@scope/otherpackage/@otherscope/node_modules/]package/[subdirectory/]file.js + // Returns indices: ^ ^ ^ ^ + + let topLevelNodeModulesIndex = 0; + let topLevelPackageNameIndex = 0; + let packageRootIndex = 0; + let fileNameIndex = 0; + + const enum States { + BeforeNodeModules, + NodeModules, + Scope, + PackageContent + } + + let partStart = 0; + let partEnd = 0; + let state = States.BeforeNodeModules; + + while (partEnd >= 0) { + partStart = partEnd; + partEnd = fullPath.indexOf("/", partStart + 1); + switch (state) { + case States.BeforeNodeModules: + if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) { + topLevelNodeModulesIndex = partStart; + topLevelPackageNameIndex = partEnd; + state = States.NodeModules; + } + break; + case States.NodeModules: + case States.Scope: + if (state === States.NodeModules && fullPath.charAt(partStart + 1) === "@") { + state = States.Scope; + } + else { + packageRootIndex = partEnd; + state = States.PackageContent; + } + break; + case States.PackageContent: + if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) { + state = States.NodeModules; + } + else { + state = States.PackageContent; + } + break; } + } - fileNameIndex = partStart; + fileNameIndex = partStart; - return state > States.NodeModules ? { topLevelNodeModulesIndex, topLevelPackageNameIndex, packageRootIndex, fileNameIndex } : undefined; - } + return state > States.NodeModules ? { topLevelNodeModulesIndex, topLevelPackageNameIndex, packageRootIndex, fileNameIndex } : undefined; +} - function getPathRelativeToRootDirs(path: string, rootDirs: readonly string[], getCanonicalFileName: GetCanonicalFileName): string | undefined { - return firstDefined(rootDirs, rootDir => { - const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName)!; // TODO: GH#18217 - return isPathRelativeToParent(relativePath) ? undefined : relativePath; - }); - } +function getPathRelativeToRootDirs(path: string, rootDirs: readonly string[], getCanonicalFileName: GetCanonicalFileName): string | undefined { + return firstDefined(rootDirs, rootDir => { + const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName)!; // TODO: GH#18217 + return isPathRelativeToParent(relativePath) ? undefined : relativePath; + }); +} - function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions): string { - if (fileExtensionIsOneOf(fileName, [Extension.Json, Extension.Mjs, Extension.Cjs])) return fileName; - const noExtension = removeFileExtension(fileName); - if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) return noExtension + getJSExtensionForFile(fileName, options); - switch (ending) { - case Ending.Minimal: - return removeSuffix(noExtension, "/index"); - case Ending.Index: - return noExtension; - case Ending.JsExtension: - return noExtension + getJSExtensionForFile(fileName, options); - default: - return Debug.assertNever(ending); - } +function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions): string { + if (fileExtensionIsOneOf(fileName, [Extension.Json, Extension.Mjs, Extension.Cjs])) return fileName; + const noExtension = removeFileExtension(fileName); + if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) return noExtension + getJSExtensionForFile(fileName, options); + switch (ending) { + case Ending.Minimal: + return removeSuffix(noExtension, "/index"); + case Ending.Index: + return noExtension; + case Ending.JsExtension: + return noExtension + getJSExtensionForFile(fileName, options); + default: + return Debug.assertNever(ending); } +} - function getJSExtensionForFile(fileName: string, options: CompilerOptions): Extension { - return tryGetJSExtensionForFile(fileName, options) ?? Debug.fail(`Extension ${extensionFromPath(fileName)} is unsupported:: FileName:: ${fileName}`); - } - - export function tryGetJSExtensionForFile(fileName: string, options: CompilerOptions): Extension | undefined { - const ext = tryGetExtensionFromPath(fileName); - switch (ext) { - case Extension.Ts: - case Extension.Dts: - return Extension.Js; - case Extension.Tsx: - return options.jsx === JsxEmit.Preserve ? Extension.Jsx : Extension.Js; - case Extension.Js: - case Extension.Jsx: - case Extension.Json: - return ext; - case Extension.Dmts: - case Extension.Mts: - case Extension.Mjs: - return Extension.Mjs; - case Extension.Dcts: - case Extension.Cts: - case Extension.Cjs: - return Extension.Cjs; - default: - return undefined; - } - } +function getJSExtensionForFile(fileName: string, options: CompilerOptions): Extension { + return tryGetJSExtensionForFile(fileName, options) ?? Debug.fail(`Extension ${extensionFromPath(fileName)} is unsupported:: FileName:: ${fileName}`); +} - function getRelativePathIfInDirectory(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined { - const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); - return isRootedDiskPath(relativePath) ? undefined : relativePath; +export function tryGetJSExtensionForFile(fileName: string, options: CompilerOptions): Extension | undefined { + const ext = tryGetExtensionFromPath(fileName); + switch (ext) { + case Extension.Ts: + case Extension.Dts: + return Extension.Js; + case Extension.Tsx: + return options.jsx === JsxEmit.Preserve ? Extension.Jsx : Extension.Js; + case Extension.Js: + case Extension.Jsx: + case Extension.Json: + return ext; + case Extension.Dmts: + case Extension.Mts: + case Extension.Mjs: + return Extension.Mjs; + case Extension.Dcts: + case Extension.Cts: + case Extension.Cjs: + return Extension.Cjs; + default: + return undefined; } +} - function isPathRelativeToParent(path: string): boolean { - return startsWith(path, ".."); - } +function getRelativePathIfInDirectory(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined { + const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); + return isRootedDiskPath(relativePath) ? undefined : relativePath; +} + +function isPathRelativeToParent(path: string): boolean { + return startsWith(path, ".."); +} } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 6bb5d0aec31b9..65d6ce443a458 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1,5321 +1,5214 @@ namespace ts { - const enum SignatureFlags { - None = 0, - Yield = 1 << 0, - Await = 1 << 1, - Type = 1 << 2, - IgnoreMissingOpenBrace = 1 << 4, - JSDoc = 1 << 5, - } - - const enum SpeculationKind { - TryParse, - Lookahead, - Reparse - } - - let NodeConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let TokenConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let IdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let SourceFileConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - - /** - * NOTE: You should not use this, it is only exported to support `createNode` in `~/src/deprecatedCompat/deprecations.ts`. - */ - /* @internal */ - export const parseBaseNodeFactory: BaseNodeFactory = { - createBaseSourceFileNode: kind => new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, -1, -1), - createBaseIdentifierNode: kind => new (IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()))(kind, -1, -1), - createBasePrivateIdentifierNode: kind => new (PrivateIdentifierConstructor || (PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor()))(kind, -1, -1), - createBaseTokenNode: kind => new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, -1, -1), - createBaseNode: kind => new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, -1, -1), - }; +const enum SignatureFlags { + None = 0, + Yield = 1 << 0, + Await = 1 << 1, + Type = 1 << 2, + IgnoreMissingOpenBrace = 1 << 4, + JSDoc = 1 << 5, +} - /* @internal */ - export const parseNodeFactory = createNodeFactory(NodeFactoryFlags.NoParenthesizerRules, parseBaseNodeFactory); +const enum SpeculationKind { + TryParse, + Lookahead, + Reparse +} - function visitNode(cbNode: (node: Node) => T, node: Node | undefined): T | undefined { - return node && cbNode(node); - } +let NodeConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; +let TokenConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; +let IdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; +let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; +let SourceFileConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; + +/** + * NOTE: You should not use this, it is only exported to support `createNode` in `~/src/deprecatedCompat/deprecations.ts`. + */ +/* @internal */ +export const parseBaseNodeFactory: BaseNodeFactory = { + createBaseSourceFileNode: kind => new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, -1, -1), + createBaseIdentifierNode: kind => new (IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()))(kind, -1, -1), + createBasePrivateIdentifierNode: kind => new (PrivateIdentifierConstructor || (PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor()))(kind, -1, -1), + createBaseTokenNode: kind => new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, -1, -1), + createBaseNode: kind => new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, -1, -1), +}; + +/* @internal */ +export const parseNodeFactory = createNodeFactory(NodeFactoryFlags.NoParenthesizerRules, parseBaseNodeFactory); + +function visitNode(cbNode: (node: Node) => T, node: Node | undefined): T | undefined { + return node && cbNode(node); +} - function visitNodes(cbNode: (node: Node) => T, cbNodes: ((node: NodeArray) => T | undefined) | undefined, nodes: NodeArray | undefined): T | undefined { - if (nodes) { - if (cbNodes) { - return cbNodes(nodes); - } - for (const node of nodes) { - const result = cbNode(node); - if (result) { - return result; - } +function visitNodes(cbNode: (node: Node) => T, cbNodes: ((node: NodeArray) => T | undefined) | undefined, nodes: NodeArray | undefined): T | undefined { + if (nodes) { + if (cbNodes) { + return cbNodes(nodes); + } + for (const node of nodes) { + const result = cbNode(node); + if (result) { + return result; } } } +} + +/*@internal*/ +export function isJSDocLikeText(text: string, start: number) { + return text.charCodeAt(start + 1) === CharacterCodes.asterisk && + text.charCodeAt(start + 2) === CharacterCodes.asterisk && + text.charCodeAt(start + 3) !== CharacterCodes.slash; +} - /*@internal*/ - export function isJSDocLikeText(text: string, start: number) { - return text.charCodeAt(start + 1) === CharacterCodes.asterisk && - text.charCodeAt(start + 2) === CharacterCodes.asterisk && - text.charCodeAt(start + 3) !== CharacterCodes.slash; +/** + * Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes + * stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise, + * embedded arrays are flattened and the 'cbNode' callback is invoked for each element. If a callback returns + * a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned. + * + * @param node a given node to visit its children + * @param cbNode a callback to be invoked for all child nodes + * @param cbNodes a callback to be invoked for embedded array + * + * @remarks `forEachChild` must visit the children of a node in the order + * that they appear in the source code. The language service depends on this property to locate nodes by position. + */ +export function forEachChild(node: Node, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + if (!node || node.kind <= SyntaxKind.LastToken) { + return; + } + switch (node.kind) { + case SyntaxKind.QualifiedName: + return visitNode(cbNode, (node as QualifiedName).left) || + visitNode(cbNode, (node as QualifiedName).right); + case SyntaxKind.TypeParameter: + return visitNode(cbNode, (node as TypeParameterDeclaration).name) || + visitNode(cbNode, (node as TypeParameterDeclaration).constraint) || + visitNode(cbNode, (node as TypeParameterDeclaration).default) || + visitNode(cbNode, (node as TypeParameterDeclaration).expression); + case SyntaxKind.ShorthandPropertyAssignment: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as ShorthandPropertyAssignment).name) || + visitNode(cbNode, (node as ShorthandPropertyAssignment).questionToken) || + visitNode(cbNode, (node as ShorthandPropertyAssignment).exclamationToken) || + visitNode(cbNode, (node as ShorthandPropertyAssignment).equalsToken) || + visitNode(cbNode, (node as ShorthandPropertyAssignment).objectAssignmentInitializer); + case SyntaxKind.SpreadAssignment: + return visitNode(cbNode, (node as SpreadAssignment).expression); + case SyntaxKind.Parameter: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as ParameterDeclaration).dotDotDotToken) || + visitNode(cbNode, (node as ParameterDeclaration).name) || + visitNode(cbNode, (node as ParameterDeclaration).questionToken) || + visitNode(cbNode, (node as ParameterDeclaration).type) || + visitNode(cbNode, (node as ParameterDeclaration).initializer); + case SyntaxKind.PropertyDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as PropertyDeclaration).name) || + visitNode(cbNode, (node as PropertyDeclaration).questionToken) || + visitNode(cbNode, (node as PropertyDeclaration).exclamationToken) || + visitNode(cbNode, (node as PropertyDeclaration).type) || + visitNode(cbNode, (node as PropertyDeclaration).initializer); + case SyntaxKind.PropertySignature: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as PropertySignature).name) || + visitNode(cbNode, (node as PropertySignature).questionToken) || + visitNode(cbNode, (node as PropertySignature).type) || + visitNode(cbNode, (node as PropertySignature).initializer); + case SyntaxKind.PropertyAssignment: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as PropertyAssignment).name) || + visitNode(cbNode, (node as PropertyAssignment).questionToken) || + visitNode(cbNode, (node as PropertyAssignment).initializer); + case SyntaxKind.VariableDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as VariableDeclaration).name) || + visitNode(cbNode, (node as VariableDeclaration).exclamationToken) || + visitNode(cbNode, (node as VariableDeclaration).type) || + visitNode(cbNode, (node as VariableDeclaration).initializer); + case SyntaxKind.BindingElement: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as BindingElement).dotDotDotToken) || + visitNode(cbNode, (node as BindingElement).propertyName) || + visitNode(cbNode, (node as BindingElement).name) || + visitNode(cbNode, (node as BindingElement).initializer); + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNodes(cbNode, cbNodes, (node as SignatureDeclaration).typeParameters) || + visitNodes(cbNode, cbNodes, (node as SignatureDeclaration).parameters) || + visitNode(cbNode, (node as SignatureDeclaration).type); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as FunctionLikeDeclaration).asteriskToken) || + visitNode(cbNode, (node as FunctionLikeDeclaration).name) || + visitNode(cbNode, (node as FunctionLikeDeclaration).questionToken) || + visitNode(cbNode, (node as FunctionLikeDeclaration).exclamationToken) || + visitNodes(cbNode, cbNodes, (node as FunctionLikeDeclaration).typeParameters) || + visitNodes(cbNode, cbNodes, (node as FunctionLikeDeclaration).parameters) || + visitNode(cbNode, (node as FunctionLikeDeclaration).type) || + visitNode(cbNode, (node as ArrowFunction).equalsGreaterThanToken) || + visitNode(cbNode, (node as FunctionLikeDeclaration).body); + case SyntaxKind.ClassStaticBlockDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as ClassStaticBlockDeclaration).body); + case SyntaxKind.TypeReference: + return visitNode(cbNode, (node as TypeReferenceNode).typeName) || + visitNodes(cbNode, cbNodes, (node as TypeReferenceNode).typeArguments); + case SyntaxKind.TypePredicate: + return visitNode(cbNode, (node as TypePredicateNode).assertsModifier) || + visitNode(cbNode, (node as TypePredicateNode).parameterName) || + visitNode(cbNode, (node as TypePredicateNode).type); + case SyntaxKind.TypeQuery: + return visitNode(cbNode, (node as TypeQueryNode).exprName); + case SyntaxKind.TypeLiteral: + return visitNodes(cbNode, cbNodes, (node as TypeLiteralNode).members); + case SyntaxKind.ArrayType: + return visitNode(cbNode, (node as ArrayTypeNode).elementType); + case SyntaxKind.TupleType: + return visitNodes(cbNode, cbNodes, (node as TupleTypeNode).elements); + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + return visitNodes(cbNode, cbNodes, (node as UnionOrIntersectionTypeNode).types); + case SyntaxKind.ConditionalType: + return visitNode(cbNode, (node as ConditionalTypeNode).checkType) || + visitNode(cbNode, (node as ConditionalTypeNode).extendsType) || + visitNode(cbNode, (node as ConditionalTypeNode).trueType) || + visitNode(cbNode, (node as ConditionalTypeNode).falseType); + case SyntaxKind.InferType: + return visitNode(cbNode, (node as InferTypeNode).typeParameter); + case SyntaxKind.ImportType: + return visitNode(cbNode, (node as ImportTypeNode).argument) || + visitNode(cbNode, (node as ImportTypeNode).qualifier) || + visitNodes(cbNode, cbNodes, (node as ImportTypeNode).typeArguments); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.TypeOperator: + return visitNode(cbNode, (node as ParenthesizedTypeNode | TypeOperatorNode).type); + case SyntaxKind.IndexedAccessType: + return visitNode(cbNode, (node as IndexedAccessTypeNode).objectType) || + visitNode(cbNode, (node as IndexedAccessTypeNode).indexType); + case SyntaxKind.MappedType: + return visitNode(cbNode, (node as MappedTypeNode).readonlyToken) || + visitNode(cbNode, (node as MappedTypeNode).typeParameter) || + visitNode(cbNode, (node as MappedTypeNode).nameType) || + visitNode(cbNode, (node as MappedTypeNode).questionToken) || + visitNode(cbNode, (node as MappedTypeNode).type) || + visitNodes(cbNode, cbNodes, (node as MappedTypeNode).members); + case SyntaxKind.LiteralType: + return visitNode(cbNode, (node as LiteralTypeNode).literal); + case SyntaxKind.NamedTupleMember: + return visitNode(cbNode, (node as NamedTupleMember).dotDotDotToken) || + visitNode(cbNode, (node as NamedTupleMember).name) || + visitNode(cbNode, (node as NamedTupleMember).questionToken) || + visitNode(cbNode, (node as NamedTupleMember).type); + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + return visitNodes(cbNode, cbNodes, (node as BindingPattern).elements); + case SyntaxKind.ArrayLiteralExpression: + return visitNodes(cbNode, cbNodes, (node as ArrayLiteralExpression).elements); + case SyntaxKind.ObjectLiteralExpression: + return visitNodes(cbNode, cbNodes, (node as ObjectLiteralExpression).properties); + case SyntaxKind.PropertyAccessExpression: + return visitNode(cbNode, (node as PropertyAccessExpression).expression) || + visitNode(cbNode, (node as PropertyAccessExpression).questionDotToken) || + visitNode(cbNode, (node as PropertyAccessExpression).name); + case SyntaxKind.ElementAccessExpression: + return visitNode(cbNode, (node as ElementAccessExpression).expression) || + visitNode(cbNode, (node as ElementAccessExpression).questionDotToken) || + visitNode(cbNode, (node as ElementAccessExpression).argumentExpression); + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + return visitNode(cbNode, (node as CallExpression).expression) || + visitNode(cbNode, (node as CallExpression).questionDotToken) || + visitNodes(cbNode, cbNodes, (node as CallExpression).typeArguments) || + visitNodes(cbNode, cbNodes, (node as CallExpression).arguments); + case SyntaxKind.TaggedTemplateExpression: + return visitNode(cbNode, (node as TaggedTemplateExpression).tag) || + visitNode(cbNode, (node as TaggedTemplateExpression).questionDotToken) || + visitNodes(cbNode, cbNodes, (node as TaggedTemplateExpression).typeArguments) || + visitNode(cbNode, (node as TaggedTemplateExpression).template); + case SyntaxKind.TypeAssertionExpression: + return visitNode(cbNode, (node as TypeAssertion).type) || + visitNode(cbNode, (node as TypeAssertion).expression); + case SyntaxKind.ParenthesizedExpression: + return visitNode(cbNode, (node as ParenthesizedExpression).expression); + case SyntaxKind.DeleteExpression: + return visitNode(cbNode, (node as DeleteExpression).expression); + case SyntaxKind.TypeOfExpression: + return visitNode(cbNode, (node as TypeOfExpression).expression); + case SyntaxKind.VoidExpression: + return visitNode(cbNode, (node as VoidExpression).expression); + case SyntaxKind.PrefixUnaryExpression: + return visitNode(cbNode, (node as PrefixUnaryExpression).operand); + case SyntaxKind.YieldExpression: + return visitNode(cbNode, (node as YieldExpression).asteriskToken) || + visitNode(cbNode, (node as YieldExpression).expression); + case SyntaxKind.AwaitExpression: + return visitNode(cbNode, (node as AwaitExpression).expression); + case SyntaxKind.PostfixUnaryExpression: + return visitNode(cbNode, (node as PostfixUnaryExpression).operand); + case SyntaxKind.BinaryExpression: + return visitNode(cbNode, (node as BinaryExpression).left) || + visitNode(cbNode, (node as BinaryExpression).operatorToken) || + visitNode(cbNode, (node as BinaryExpression).right); + case SyntaxKind.AsExpression: + return visitNode(cbNode, (node as AsExpression).expression) || + visitNode(cbNode, (node as AsExpression).type); + case SyntaxKind.NonNullExpression: + return visitNode(cbNode, (node as NonNullExpression).expression); + case SyntaxKind.MetaProperty: + return visitNode(cbNode, (node as MetaProperty).name); + case SyntaxKind.ConditionalExpression: + return visitNode(cbNode, (node as ConditionalExpression).condition) || + visitNode(cbNode, (node as ConditionalExpression).questionToken) || + visitNode(cbNode, (node as ConditionalExpression).whenTrue) || + visitNode(cbNode, (node as ConditionalExpression).colonToken) || + visitNode(cbNode, (node as ConditionalExpression).whenFalse); + case SyntaxKind.SpreadElement: + return visitNode(cbNode, (node as SpreadElement).expression); + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + return visitNodes(cbNode, cbNodes, (node as Block).statements); + case SyntaxKind.SourceFile: + return visitNodes(cbNode, cbNodes, (node as SourceFile).statements) || + visitNode(cbNode, (node as SourceFile).endOfFileToken); + case SyntaxKind.VariableStatement: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as VariableStatement).declarationList); + case SyntaxKind.VariableDeclarationList: + return visitNodes(cbNode, cbNodes, (node as VariableDeclarationList).declarations); + case SyntaxKind.ExpressionStatement: + return visitNode(cbNode, (node as ExpressionStatement).expression); + case SyntaxKind.IfStatement: + return visitNode(cbNode, (node as IfStatement).expression) || + visitNode(cbNode, (node as IfStatement).thenStatement) || + visitNode(cbNode, (node as IfStatement).elseStatement); + case SyntaxKind.DoStatement: + return visitNode(cbNode, (node as DoStatement).statement) || + visitNode(cbNode, (node as DoStatement).expression); + case SyntaxKind.WhileStatement: + return visitNode(cbNode, (node as WhileStatement).expression) || + visitNode(cbNode, (node as WhileStatement).statement); + case SyntaxKind.ForStatement: + return visitNode(cbNode, (node as ForStatement).initializer) || + visitNode(cbNode, (node as ForStatement).condition) || + visitNode(cbNode, (node as ForStatement).incrementor) || + visitNode(cbNode, (node as ForStatement).statement); + case SyntaxKind.ForInStatement: + return visitNode(cbNode, (node as ForInStatement).initializer) || + visitNode(cbNode, (node as ForInStatement).expression) || + visitNode(cbNode, (node as ForInStatement).statement); + case SyntaxKind.ForOfStatement: + return visitNode(cbNode, (node as ForOfStatement).awaitModifier) || + visitNode(cbNode, (node as ForOfStatement).initializer) || + visitNode(cbNode, (node as ForOfStatement).expression) || + visitNode(cbNode, (node as ForOfStatement).statement); + case SyntaxKind.ContinueStatement: + case SyntaxKind.BreakStatement: + return visitNode(cbNode, (node as BreakOrContinueStatement).label); + case SyntaxKind.ReturnStatement: + return visitNode(cbNode, (node as ReturnStatement).expression); + case SyntaxKind.WithStatement: + return visitNode(cbNode, (node as WithStatement).expression) || + visitNode(cbNode, (node as WithStatement).statement); + case SyntaxKind.SwitchStatement: + return visitNode(cbNode, (node as SwitchStatement).expression) || + visitNode(cbNode, (node as SwitchStatement).caseBlock); + case SyntaxKind.CaseBlock: + return visitNodes(cbNode, cbNodes, (node as CaseBlock).clauses); + case SyntaxKind.CaseClause: + return visitNode(cbNode, (node as CaseClause).expression) || + visitNodes(cbNode, cbNodes, (node as CaseClause).statements); + case SyntaxKind.DefaultClause: + return visitNodes(cbNode, cbNodes, (node as DefaultClause).statements); + case SyntaxKind.LabeledStatement: + return visitNode(cbNode, (node as LabeledStatement).label) || + visitNode(cbNode, (node as LabeledStatement).statement); + case SyntaxKind.ThrowStatement: + return visitNode(cbNode, (node as ThrowStatement).expression); + case SyntaxKind.TryStatement: + return visitNode(cbNode, (node as TryStatement).tryBlock) || + visitNode(cbNode, (node as TryStatement).catchClause) || + visitNode(cbNode, (node as TryStatement).finallyBlock); + case SyntaxKind.CatchClause: + return visitNode(cbNode, (node as CatchClause).variableDeclaration) || + visitNode(cbNode, (node as CatchClause).block); + case SyntaxKind.Decorator: + return visitNode(cbNode, (node as Decorator).expression); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as ClassLikeDeclaration).name) || + visitNodes(cbNode, cbNodes, (node as ClassLikeDeclaration).typeParameters) || + visitNodes(cbNode, cbNodes, (node as ClassLikeDeclaration).heritageClauses) || + visitNodes(cbNode, cbNodes, (node as ClassLikeDeclaration).members); + case SyntaxKind.InterfaceDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as InterfaceDeclaration).name) || + visitNodes(cbNode, cbNodes, (node as InterfaceDeclaration).typeParameters) || + visitNodes(cbNode, cbNodes, (node as ClassDeclaration).heritageClauses) || + visitNodes(cbNode, cbNodes, (node as InterfaceDeclaration).members); + case SyntaxKind.TypeAliasDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as TypeAliasDeclaration).name) || + visitNodes(cbNode, cbNodes, (node as TypeAliasDeclaration).typeParameters) || + visitNode(cbNode, (node as TypeAliasDeclaration).type); + case SyntaxKind.EnumDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as EnumDeclaration).name) || + visitNodes(cbNode, cbNodes, (node as EnumDeclaration).members); + case SyntaxKind.EnumMember: + return visitNode(cbNode, (node as EnumMember).name) || + visitNode(cbNode, (node as EnumMember).initializer); + case SyntaxKind.ModuleDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as ModuleDeclaration).name) || + visitNode(cbNode, (node as ModuleDeclaration).body); + case SyntaxKind.ImportEqualsDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as ImportEqualsDeclaration).name) || + visitNode(cbNode, (node as ImportEqualsDeclaration).moduleReference); + case SyntaxKind.ImportDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as ImportDeclaration).importClause) || + visitNode(cbNode, (node as ImportDeclaration).moduleSpecifier) || + visitNode(cbNode, (node as ImportDeclaration).assertClause); + case SyntaxKind.ImportClause: + return visitNode(cbNode, (node as ImportClause).name) || + visitNode(cbNode, (node as ImportClause).namedBindings); + case SyntaxKind.AssertClause: + return visitNodes(cbNode, cbNodes, (node as AssertClause).elements); + case SyntaxKind.AssertEntry: + return visitNode(cbNode, (node as AssertEntry).name) || + visitNode(cbNode, (node as AssertEntry).value); + case SyntaxKind.NamespaceExportDeclaration: + return visitNode(cbNode, (node as NamespaceExportDeclaration).name); + case SyntaxKind.NamespaceImport: + return visitNode(cbNode, (node as NamespaceImport).name); + case SyntaxKind.NamespaceExport: + return visitNode(cbNode, (node as NamespaceExport).name); + case SyntaxKind.NamedImports: + case SyntaxKind.NamedExports: + return visitNodes(cbNode, cbNodes, (node as NamedImportsOrExports).elements); + case SyntaxKind.ExportDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as ExportDeclaration).exportClause) || + visitNode(cbNode, (node as ExportDeclaration).moduleSpecifier) || + visitNode(cbNode, (node as ExportDeclaration).assertClause); + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + return visitNode(cbNode, (node as ImportOrExportSpecifier).propertyName) || + visitNode(cbNode, (node as ImportOrExportSpecifier).name); + case SyntaxKind.ExportAssignment: + return visitNodes(cbNode, cbNodes, node.decorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, (node as ExportAssignment).expression); + case SyntaxKind.TemplateExpression: + return visitNode(cbNode, (node as TemplateExpression).head) || visitNodes(cbNode, cbNodes, (node as TemplateExpression).templateSpans); + case SyntaxKind.TemplateSpan: + return visitNode(cbNode, (node as TemplateSpan).expression) || visitNode(cbNode, (node as TemplateSpan).literal); + case SyntaxKind.TemplateLiteralType: + return visitNode(cbNode, (node as TemplateLiteralTypeNode).head) || visitNodes(cbNode, cbNodes, (node as TemplateLiteralTypeNode).templateSpans); + case SyntaxKind.TemplateLiteralTypeSpan: + return visitNode(cbNode, (node as TemplateLiteralTypeSpan).type) || visitNode(cbNode, (node as TemplateLiteralTypeSpan).literal); + case SyntaxKind.ComputedPropertyName: + return visitNode(cbNode, (node as ComputedPropertyName).expression); + case SyntaxKind.HeritageClause: + return visitNodes(cbNode, cbNodes, (node as HeritageClause).types); + case SyntaxKind.ExpressionWithTypeArguments: + return visitNode(cbNode, (node as ExpressionWithTypeArguments).expression) || + visitNodes(cbNode, cbNodes, (node as ExpressionWithTypeArguments).typeArguments); + case SyntaxKind.ExternalModuleReference: + return visitNode(cbNode, (node as ExternalModuleReference).expression); + case SyntaxKind.MissingDeclaration: + return visitNodes(cbNode, cbNodes, node.decorators); + case SyntaxKind.CommaListExpression: + return visitNodes(cbNode, cbNodes, (node as CommaListExpression).elements); + + case SyntaxKind.JsxElement: + return visitNode(cbNode, (node as JsxElement).openingElement) || + visitNodes(cbNode, cbNodes, (node as JsxElement).children) || + visitNode(cbNode, (node as JsxElement).closingElement); + case SyntaxKind.JsxFragment: + return visitNode(cbNode, (node as JsxFragment).openingFragment) || + visitNodes(cbNode, cbNodes, (node as JsxFragment).children) || + visitNode(cbNode, (node as JsxFragment).closingFragment); + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxOpeningElement: + return visitNode(cbNode, (node as JsxOpeningLikeElement).tagName) || + visitNodes(cbNode, cbNodes, (node as JsxOpeningLikeElement).typeArguments) || + visitNode(cbNode, (node as JsxOpeningLikeElement).attributes); + case SyntaxKind.JsxAttributes: + return visitNodes(cbNode, cbNodes, (node as JsxAttributes).properties); + case SyntaxKind.JsxAttribute: + return visitNode(cbNode, (node as JsxAttribute).name) || + visitNode(cbNode, (node as JsxAttribute).initializer); + case SyntaxKind.JsxSpreadAttribute: + return visitNode(cbNode, (node as JsxSpreadAttribute).expression); + case SyntaxKind.JsxExpression: + return visitNode(cbNode, (node as JsxExpression).dotDotDotToken) || + visitNode(cbNode, (node as JsxExpression).expression); + case SyntaxKind.JsxClosingElement: + return visitNode(cbNode, (node as JsxClosingElement).tagName); + + case SyntaxKind.OptionalType: + case SyntaxKind.RestType: + case SyntaxKind.JSDocTypeExpression: + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocNullableType: + case SyntaxKind.JSDocOptionalType: + case SyntaxKind.JSDocVariadicType: + return visitNode(cbNode, (node as OptionalTypeNode | RestTypeNode | JSDocTypeExpression | JSDocTypeReferencingNode).type); + case SyntaxKind.JSDocFunctionType: + return visitNodes(cbNode, cbNodes, (node as JSDocFunctionType).parameters) || + visitNode(cbNode, (node as JSDocFunctionType).type); + case SyntaxKind.JSDocComment: + return (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)) + || visitNodes(cbNode, cbNodes, (node as JSDoc).tags); + case SyntaxKind.JSDocSeeTag: + return visitNode(cbNode, (node as JSDocSeeTag).tagName) || + visitNode(cbNode, (node as JSDocSeeTag).name) || + (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); + case SyntaxKind.JSDocNameReference: + return visitNode(cbNode, (node as JSDocNameReference).name); + case SyntaxKind.JSDocMemberName: + return visitNode(cbNode, (node as JSDocMemberName).left) || + visitNode(cbNode, (node as JSDocMemberName).right); + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocPropertyTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + ((node as JSDocPropertyLikeTag).isNameFirst + ? visitNode(cbNode, (node as JSDocPropertyLikeTag).name) || + visitNode(cbNode, (node as JSDocPropertyLikeTag).typeExpression) || + (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)) + : visitNode(cbNode, (node as JSDocPropertyLikeTag).typeExpression) || + visitNode(cbNode, (node as JSDocPropertyLikeTag).name) || + (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined))); + case SyntaxKind.JSDocAuthorTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); + case SyntaxKind.JSDocImplementsTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + visitNode(cbNode, (node as JSDocImplementsTag).class) || + (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); + case SyntaxKind.JSDocAugmentsTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + visitNode(cbNode, (node as JSDocAugmentsTag).class) || + (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); + case SyntaxKind.JSDocTemplateTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + visitNode(cbNode, (node as JSDocTemplateTag).constraint) || + visitNodes(cbNode, cbNodes, (node as JSDocTemplateTag).typeParameters) || + (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); + case SyntaxKind.JSDocTypedefTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + ((node as JSDocTypedefTag).typeExpression && + (node as JSDocTypedefTag).typeExpression!.kind === SyntaxKind.JSDocTypeExpression + ? visitNode(cbNode, (node as JSDocTypedefTag).typeExpression) || + visitNode(cbNode, (node as JSDocTypedefTag).fullName) || + (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)) + : visitNode(cbNode, (node as JSDocTypedefTag).fullName) || + visitNode(cbNode, (node as JSDocTypedefTag).typeExpression) || + (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined))); + case SyntaxKind.JSDocCallbackTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + visitNode(cbNode, (node as JSDocCallbackTag).fullName) || + visitNode(cbNode, (node as JSDocCallbackTag).typeExpression) || + (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); + case SyntaxKind.JSDocReturnTag: + case SyntaxKind.JSDocTypeTag: + case SyntaxKind.JSDocThisTag: + case SyntaxKind.JSDocEnumTag: + return visitNode(cbNode, (node as JSDocTag).tagName) || + visitNode(cbNode, (node as JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag).typeExpression) || + (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); + case SyntaxKind.JSDocSignature: + return forEach((node as JSDocSignature).typeParameters, cbNode) || + forEach((node as JSDocSignature).parameters, cbNode) || + visitNode(cbNode, (node as JSDocSignature).type); + case SyntaxKind.JSDocLink: + case SyntaxKind.JSDocLinkCode: + case SyntaxKind.JSDocLinkPlain: + return visitNode(cbNode, (node as JSDocLink | JSDocLinkCode | JSDocLinkPlain).name); + case SyntaxKind.JSDocTypeLiteral: + return forEach((node as JSDocTypeLiteral).jsDocPropertyTags, cbNode); + case SyntaxKind.JSDocTag: + case SyntaxKind.JSDocClassTag: + case SyntaxKind.JSDocPublicTag: + case SyntaxKind.JSDocPrivateTag: + case SyntaxKind.JSDocProtectedTag: + case SyntaxKind.JSDocReadonlyTag: + case SyntaxKind.JSDocDeprecatedTag: + return visitNode(cbNode, (node as JSDocTag).tagName) + || (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); + case SyntaxKind.PartiallyEmittedExpression: + return visitNode(cbNode, (node as PartiallyEmittedExpression).expression); } +} - /** - * Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes - * stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise, - * embedded arrays are flattened and the 'cbNode' callback is invoked for each element. If a callback returns - * a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned. - * - * @param node a given node to visit its children - * @param cbNode a callback to be invoked for all child nodes - * @param cbNodes a callback to be invoked for embedded array - * - * @remarks `forEachChild` must visit the children of a node in the order - * that they appear in the source code. The language service depends on this property to locate nodes by position. - */ - export function forEachChild(node: Node, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - if (!node || node.kind <= SyntaxKind.LastToken) { - return; - } - switch (node.kind) { - case SyntaxKind.QualifiedName: - return visitNode(cbNode, (node as QualifiedName).left) || - visitNode(cbNode, (node as QualifiedName).right); - case SyntaxKind.TypeParameter: - return visitNode(cbNode, (node as TypeParameterDeclaration).name) || - visitNode(cbNode, (node as TypeParameterDeclaration).constraint) || - visitNode(cbNode, (node as TypeParameterDeclaration).default) || - visitNode(cbNode, (node as TypeParameterDeclaration).expression); - case SyntaxKind.ShorthandPropertyAssignment: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as ShorthandPropertyAssignment).name) || - visitNode(cbNode, (node as ShorthandPropertyAssignment).questionToken) || - visitNode(cbNode, (node as ShorthandPropertyAssignment).exclamationToken) || - visitNode(cbNode, (node as ShorthandPropertyAssignment).equalsToken) || - visitNode(cbNode, (node as ShorthandPropertyAssignment).objectAssignmentInitializer); - case SyntaxKind.SpreadAssignment: - return visitNode(cbNode, (node as SpreadAssignment).expression); - case SyntaxKind.Parameter: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as ParameterDeclaration).dotDotDotToken) || - visitNode(cbNode, (node as ParameterDeclaration).name) || - visitNode(cbNode, (node as ParameterDeclaration).questionToken) || - visitNode(cbNode, (node as ParameterDeclaration).type) || - visitNode(cbNode, (node as ParameterDeclaration).initializer); - case SyntaxKind.PropertyDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as PropertyDeclaration).name) || - visitNode(cbNode, (node as PropertyDeclaration).questionToken) || - visitNode(cbNode, (node as PropertyDeclaration).exclamationToken) || - visitNode(cbNode, (node as PropertyDeclaration).type) || - visitNode(cbNode, (node as PropertyDeclaration).initializer); - case SyntaxKind.PropertySignature: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as PropertySignature).name) || - visitNode(cbNode, (node as PropertySignature).questionToken) || - visitNode(cbNode, (node as PropertySignature).type) || - visitNode(cbNode, (node as PropertySignature).initializer); - case SyntaxKind.PropertyAssignment: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as PropertyAssignment).name) || - visitNode(cbNode, (node as PropertyAssignment).questionToken) || - visitNode(cbNode, (node as PropertyAssignment).initializer); - case SyntaxKind.VariableDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as VariableDeclaration).name) || - visitNode(cbNode, (node as VariableDeclaration).exclamationToken) || - visitNode(cbNode, (node as VariableDeclaration).type) || - visitNode(cbNode, (node as VariableDeclaration).initializer); - case SyntaxKind.BindingElement: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as BindingElement).dotDotDotToken) || - visitNode(cbNode, (node as BindingElement).propertyName) || - visitNode(cbNode, (node as BindingElement).name) || - visitNode(cbNode, (node as BindingElement).initializer); - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNodes(cbNode, cbNodes, (node as SignatureDeclaration).typeParameters) || - visitNodes(cbNode, cbNodes, (node as SignatureDeclaration).parameters) || - visitNode(cbNode, (node as SignatureDeclaration).type); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ArrowFunction: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as FunctionLikeDeclaration).asteriskToken) || - visitNode(cbNode, (node as FunctionLikeDeclaration).name) || - visitNode(cbNode, (node as FunctionLikeDeclaration).questionToken) || - visitNode(cbNode, (node as FunctionLikeDeclaration).exclamationToken) || - visitNodes(cbNode, cbNodes, (node as FunctionLikeDeclaration).typeParameters) || - visitNodes(cbNode, cbNodes, (node as FunctionLikeDeclaration).parameters) || - visitNode(cbNode, (node as FunctionLikeDeclaration).type) || - visitNode(cbNode, (node as ArrowFunction).equalsGreaterThanToken) || - visitNode(cbNode, (node as FunctionLikeDeclaration).body); - case SyntaxKind.ClassStaticBlockDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as ClassStaticBlockDeclaration).body); - case SyntaxKind.TypeReference: - return visitNode(cbNode, (node as TypeReferenceNode).typeName) || - visitNodes(cbNode, cbNodes, (node as TypeReferenceNode).typeArguments); - case SyntaxKind.TypePredicate: - return visitNode(cbNode, (node as TypePredicateNode).assertsModifier) || - visitNode(cbNode, (node as TypePredicateNode).parameterName) || - visitNode(cbNode, (node as TypePredicateNode).type); - case SyntaxKind.TypeQuery: - return visitNode(cbNode, (node as TypeQueryNode).exprName); - case SyntaxKind.TypeLiteral: - return visitNodes(cbNode, cbNodes, (node as TypeLiteralNode).members); - case SyntaxKind.ArrayType: - return visitNode(cbNode, (node as ArrayTypeNode).elementType); - case SyntaxKind.TupleType: - return visitNodes(cbNode, cbNodes, (node as TupleTypeNode).elements); - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - return visitNodes(cbNode, cbNodes, (node as UnionOrIntersectionTypeNode).types); - case SyntaxKind.ConditionalType: - return visitNode(cbNode, (node as ConditionalTypeNode).checkType) || - visitNode(cbNode, (node as ConditionalTypeNode).extendsType) || - visitNode(cbNode, (node as ConditionalTypeNode).trueType) || - visitNode(cbNode, (node as ConditionalTypeNode).falseType); - case SyntaxKind.InferType: - return visitNode(cbNode, (node as InferTypeNode).typeParameter); - case SyntaxKind.ImportType: - return visitNode(cbNode, (node as ImportTypeNode).argument) || - visitNode(cbNode, (node as ImportTypeNode).qualifier) || - visitNodes(cbNode, cbNodes, (node as ImportTypeNode).typeArguments); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.TypeOperator: - return visitNode(cbNode, (node as ParenthesizedTypeNode | TypeOperatorNode).type); - case SyntaxKind.IndexedAccessType: - return visitNode(cbNode, (node as IndexedAccessTypeNode).objectType) || - visitNode(cbNode, (node as IndexedAccessTypeNode).indexType); - case SyntaxKind.MappedType: - return visitNode(cbNode, (node as MappedTypeNode).readonlyToken) || - visitNode(cbNode, (node as MappedTypeNode).typeParameter) || - visitNode(cbNode, (node as MappedTypeNode).nameType) || - visitNode(cbNode, (node as MappedTypeNode).questionToken) || - visitNode(cbNode, (node as MappedTypeNode).type) || - visitNodes(cbNode, cbNodes, (node as MappedTypeNode).members); - case SyntaxKind.LiteralType: - return visitNode(cbNode, (node as LiteralTypeNode).literal); - case SyntaxKind.NamedTupleMember: - return visitNode(cbNode, (node as NamedTupleMember).dotDotDotToken) || - visitNode(cbNode, (node as NamedTupleMember).name) || - visitNode(cbNode, (node as NamedTupleMember).questionToken) || - visitNode(cbNode, (node as NamedTupleMember).type); - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - return visitNodes(cbNode, cbNodes, (node as BindingPattern).elements); - case SyntaxKind.ArrayLiteralExpression: - return visitNodes(cbNode, cbNodes, (node as ArrayLiteralExpression).elements); - case SyntaxKind.ObjectLiteralExpression: - return visitNodes(cbNode, cbNodes, (node as ObjectLiteralExpression).properties); - case SyntaxKind.PropertyAccessExpression: - return visitNode(cbNode, (node as PropertyAccessExpression).expression) || - visitNode(cbNode, (node as PropertyAccessExpression).questionDotToken) || - visitNode(cbNode, (node as PropertyAccessExpression).name); - case SyntaxKind.ElementAccessExpression: - return visitNode(cbNode, (node as ElementAccessExpression).expression) || - visitNode(cbNode, (node as ElementAccessExpression).questionDotToken) || - visitNode(cbNode, (node as ElementAccessExpression).argumentExpression); - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - return visitNode(cbNode, (node as CallExpression).expression) || - visitNode(cbNode, (node as CallExpression).questionDotToken) || - visitNodes(cbNode, cbNodes, (node as CallExpression).typeArguments) || - visitNodes(cbNode, cbNodes, (node as CallExpression).arguments); - case SyntaxKind.TaggedTemplateExpression: - return visitNode(cbNode, (node as TaggedTemplateExpression).tag) || - visitNode(cbNode, (node as TaggedTemplateExpression).questionDotToken) || - visitNodes(cbNode, cbNodes, (node as TaggedTemplateExpression).typeArguments) || - visitNode(cbNode, (node as TaggedTemplateExpression).template); - case SyntaxKind.TypeAssertionExpression: - return visitNode(cbNode, (node as TypeAssertion).type) || - visitNode(cbNode, (node as TypeAssertion).expression); - case SyntaxKind.ParenthesizedExpression: - return visitNode(cbNode, (node as ParenthesizedExpression).expression); - case SyntaxKind.DeleteExpression: - return visitNode(cbNode, (node as DeleteExpression).expression); - case SyntaxKind.TypeOfExpression: - return visitNode(cbNode, (node as TypeOfExpression).expression); - case SyntaxKind.VoidExpression: - return visitNode(cbNode, (node as VoidExpression).expression); - case SyntaxKind.PrefixUnaryExpression: - return visitNode(cbNode, (node as PrefixUnaryExpression).operand); - case SyntaxKind.YieldExpression: - return visitNode(cbNode, (node as YieldExpression).asteriskToken) || - visitNode(cbNode, (node as YieldExpression).expression); - case SyntaxKind.AwaitExpression: - return visitNode(cbNode, (node as AwaitExpression).expression); - case SyntaxKind.PostfixUnaryExpression: - return visitNode(cbNode, (node as PostfixUnaryExpression).operand); - case SyntaxKind.BinaryExpression: - return visitNode(cbNode, (node as BinaryExpression).left) || - visitNode(cbNode, (node as BinaryExpression).operatorToken) || - visitNode(cbNode, (node as BinaryExpression).right); - case SyntaxKind.AsExpression: - return visitNode(cbNode, (node as AsExpression).expression) || - visitNode(cbNode, (node as AsExpression).type); - case SyntaxKind.NonNullExpression: - return visitNode(cbNode, (node as NonNullExpression).expression); - case SyntaxKind.MetaProperty: - return visitNode(cbNode, (node as MetaProperty).name); - case SyntaxKind.ConditionalExpression: - return visitNode(cbNode, (node as ConditionalExpression).condition) || - visitNode(cbNode, (node as ConditionalExpression).questionToken) || - visitNode(cbNode, (node as ConditionalExpression).whenTrue) || - visitNode(cbNode, (node as ConditionalExpression).colonToken) || - visitNode(cbNode, (node as ConditionalExpression).whenFalse); - case SyntaxKind.SpreadElement: - return visitNode(cbNode, (node as SpreadElement).expression); - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - return visitNodes(cbNode, cbNodes, (node as Block).statements); - case SyntaxKind.SourceFile: - return visitNodes(cbNode, cbNodes, (node as SourceFile).statements) || - visitNode(cbNode, (node as SourceFile).endOfFileToken); - case SyntaxKind.VariableStatement: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as VariableStatement).declarationList); - case SyntaxKind.VariableDeclarationList: - return visitNodes(cbNode, cbNodes, (node as VariableDeclarationList).declarations); - case SyntaxKind.ExpressionStatement: - return visitNode(cbNode, (node as ExpressionStatement).expression); - case SyntaxKind.IfStatement: - return visitNode(cbNode, (node as IfStatement).expression) || - visitNode(cbNode, (node as IfStatement).thenStatement) || - visitNode(cbNode, (node as IfStatement).elseStatement); - case SyntaxKind.DoStatement: - return visitNode(cbNode, (node as DoStatement).statement) || - visitNode(cbNode, (node as DoStatement).expression); - case SyntaxKind.WhileStatement: - return visitNode(cbNode, (node as WhileStatement).expression) || - visitNode(cbNode, (node as WhileStatement).statement); - case SyntaxKind.ForStatement: - return visitNode(cbNode, (node as ForStatement).initializer) || - visitNode(cbNode, (node as ForStatement).condition) || - visitNode(cbNode, (node as ForStatement).incrementor) || - visitNode(cbNode, (node as ForStatement).statement); - case SyntaxKind.ForInStatement: - return visitNode(cbNode, (node as ForInStatement).initializer) || - visitNode(cbNode, (node as ForInStatement).expression) || - visitNode(cbNode, (node as ForInStatement).statement); - case SyntaxKind.ForOfStatement: - return visitNode(cbNode, (node as ForOfStatement).awaitModifier) || - visitNode(cbNode, (node as ForOfStatement).initializer) || - visitNode(cbNode, (node as ForOfStatement).expression) || - visitNode(cbNode, (node as ForOfStatement).statement); - case SyntaxKind.ContinueStatement: - case SyntaxKind.BreakStatement: - return visitNode(cbNode, (node as BreakOrContinueStatement).label); - case SyntaxKind.ReturnStatement: - return visitNode(cbNode, (node as ReturnStatement).expression); - case SyntaxKind.WithStatement: - return visitNode(cbNode, (node as WithStatement).expression) || - visitNode(cbNode, (node as WithStatement).statement); - case SyntaxKind.SwitchStatement: - return visitNode(cbNode, (node as SwitchStatement).expression) || - visitNode(cbNode, (node as SwitchStatement).caseBlock); - case SyntaxKind.CaseBlock: - return visitNodes(cbNode, cbNodes, (node as CaseBlock).clauses); - case SyntaxKind.CaseClause: - return visitNode(cbNode, (node as CaseClause).expression) || - visitNodes(cbNode, cbNodes, (node as CaseClause).statements); - case SyntaxKind.DefaultClause: - return visitNodes(cbNode, cbNodes, (node as DefaultClause).statements); - case SyntaxKind.LabeledStatement: - return visitNode(cbNode, (node as LabeledStatement).label) || - visitNode(cbNode, (node as LabeledStatement).statement); - case SyntaxKind.ThrowStatement: - return visitNode(cbNode, (node as ThrowStatement).expression); - case SyntaxKind.TryStatement: - return visitNode(cbNode, (node as TryStatement).tryBlock) || - visitNode(cbNode, (node as TryStatement).catchClause) || - visitNode(cbNode, (node as TryStatement).finallyBlock); - case SyntaxKind.CatchClause: - return visitNode(cbNode, (node as CatchClause).variableDeclaration) || - visitNode(cbNode, (node as CatchClause).block); - case SyntaxKind.Decorator: - return visitNode(cbNode, (node as Decorator).expression); - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as ClassLikeDeclaration).name) || - visitNodes(cbNode, cbNodes, (node as ClassLikeDeclaration).typeParameters) || - visitNodes(cbNode, cbNodes, (node as ClassLikeDeclaration).heritageClauses) || - visitNodes(cbNode, cbNodes, (node as ClassLikeDeclaration).members); - case SyntaxKind.InterfaceDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as InterfaceDeclaration).name) || - visitNodes(cbNode, cbNodes, (node as InterfaceDeclaration).typeParameters) || - visitNodes(cbNode, cbNodes, (node as ClassDeclaration).heritageClauses) || - visitNodes(cbNode, cbNodes, (node as InterfaceDeclaration).members); - case SyntaxKind.TypeAliasDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as TypeAliasDeclaration).name) || - visitNodes(cbNode, cbNodes, (node as TypeAliasDeclaration).typeParameters) || - visitNode(cbNode, (node as TypeAliasDeclaration).type); - case SyntaxKind.EnumDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as EnumDeclaration).name) || - visitNodes(cbNode, cbNodes, (node as EnumDeclaration).members); - case SyntaxKind.EnumMember: - return visitNode(cbNode, (node as EnumMember).name) || - visitNode(cbNode, (node as EnumMember).initializer); - case SyntaxKind.ModuleDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as ModuleDeclaration).name) || - visitNode(cbNode, (node as ModuleDeclaration).body); - case SyntaxKind.ImportEqualsDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as ImportEqualsDeclaration).name) || - visitNode(cbNode, (node as ImportEqualsDeclaration).moduleReference); - case SyntaxKind.ImportDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as ImportDeclaration).importClause) || - visitNode(cbNode, (node as ImportDeclaration).moduleSpecifier) || - visitNode(cbNode, (node as ImportDeclaration).assertClause); - case SyntaxKind.ImportClause: - return visitNode(cbNode, (node as ImportClause).name) || - visitNode(cbNode, (node as ImportClause).namedBindings); - case SyntaxKind.AssertClause: - return visitNodes(cbNode, cbNodes, (node as AssertClause).elements); - case SyntaxKind.AssertEntry: - return visitNode(cbNode, (node as AssertEntry).name) || - visitNode(cbNode, (node as AssertEntry).value); - case SyntaxKind.NamespaceExportDeclaration: - return visitNode(cbNode, (node as NamespaceExportDeclaration).name); - case SyntaxKind.NamespaceImport: - return visitNode(cbNode, (node as NamespaceImport).name); - case SyntaxKind.NamespaceExport: - return visitNode(cbNode, (node as NamespaceExport).name); - case SyntaxKind.NamedImports: - case SyntaxKind.NamedExports: - return visitNodes(cbNode, cbNodes, (node as NamedImportsOrExports).elements); - case SyntaxKind.ExportDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as ExportDeclaration).exportClause) || - visitNode(cbNode, (node as ExportDeclaration).moduleSpecifier) || - visitNode(cbNode, (node as ExportDeclaration).assertClause); - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - return visitNode(cbNode, (node as ImportOrExportSpecifier).propertyName) || - visitNode(cbNode, (node as ImportOrExportSpecifier).name); - case SyntaxKind.ExportAssignment: - return visitNodes(cbNode, cbNodes, node.decorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, (node as ExportAssignment).expression); - case SyntaxKind.TemplateExpression: - return visitNode(cbNode, (node as TemplateExpression).head) || visitNodes(cbNode, cbNodes, (node as TemplateExpression).templateSpans); - case SyntaxKind.TemplateSpan: - return visitNode(cbNode, (node as TemplateSpan).expression) || visitNode(cbNode, (node as TemplateSpan).literal); - case SyntaxKind.TemplateLiteralType: - return visitNode(cbNode, (node as TemplateLiteralTypeNode).head) || visitNodes(cbNode, cbNodes, (node as TemplateLiteralTypeNode).templateSpans); - case SyntaxKind.TemplateLiteralTypeSpan: - return visitNode(cbNode, (node as TemplateLiteralTypeSpan).type) || visitNode(cbNode, (node as TemplateLiteralTypeSpan).literal); - case SyntaxKind.ComputedPropertyName: - return visitNode(cbNode, (node as ComputedPropertyName).expression); - case SyntaxKind.HeritageClause: - return visitNodes(cbNode, cbNodes, (node as HeritageClause).types); - case SyntaxKind.ExpressionWithTypeArguments: - return visitNode(cbNode, (node as ExpressionWithTypeArguments).expression) || - visitNodes(cbNode, cbNodes, (node as ExpressionWithTypeArguments).typeArguments); - case SyntaxKind.ExternalModuleReference: - return visitNode(cbNode, (node as ExternalModuleReference).expression); - case SyntaxKind.MissingDeclaration: - return visitNodes(cbNode, cbNodes, node.decorators); - case SyntaxKind.CommaListExpression: - return visitNodes(cbNode, cbNodes, (node as CommaListExpression).elements); - - case SyntaxKind.JsxElement: - return visitNode(cbNode, (node as JsxElement).openingElement) || - visitNodes(cbNode, cbNodes, (node as JsxElement).children) || - visitNode(cbNode, (node as JsxElement).closingElement); - case SyntaxKind.JsxFragment: - return visitNode(cbNode, (node as JsxFragment).openingFragment) || - visitNodes(cbNode, cbNodes, (node as JsxFragment).children) || - visitNode(cbNode, (node as JsxFragment).closingFragment); - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxOpeningElement: - return visitNode(cbNode, (node as JsxOpeningLikeElement).tagName) || - visitNodes(cbNode, cbNodes, (node as JsxOpeningLikeElement).typeArguments) || - visitNode(cbNode, (node as JsxOpeningLikeElement).attributes); - case SyntaxKind.JsxAttributes: - return visitNodes(cbNode, cbNodes, (node as JsxAttributes).properties); - case SyntaxKind.JsxAttribute: - return visitNode(cbNode, (node as JsxAttribute).name) || - visitNode(cbNode, (node as JsxAttribute).initializer); - case SyntaxKind.JsxSpreadAttribute: - return visitNode(cbNode, (node as JsxSpreadAttribute).expression); - case SyntaxKind.JsxExpression: - return visitNode(cbNode, (node as JsxExpression).dotDotDotToken) || - visitNode(cbNode, (node as JsxExpression).expression); - case SyntaxKind.JsxClosingElement: - return visitNode(cbNode, (node as JsxClosingElement).tagName); - - case SyntaxKind.OptionalType: - case SyntaxKind.RestType: - case SyntaxKind.JSDocTypeExpression: - case SyntaxKind.JSDocNonNullableType: - case SyntaxKind.JSDocNullableType: - case SyntaxKind.JSDocOptionalType: - case SyntaxKind.JSDocVariadicType: - return visitNode(cbNode, (node as OptionalTypeNode | RestTypeNode | JSDocTypeExpression | JSDocTypeReferencingNode).type); - case SyntaxKind.JSDocFunctionType: - return visitNodes(cbNode, cbNodes, (node as JSDocFunctionType).parameters) || - visitNode(cbNode, (node as JSDocFunctionType).type); - case SyntaxKind.JSDocComment: - return (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)) - || visitNodes(cbNode, cbNodes, (node as JSDoc).tags); - case SyntaxKind.JSDocSeeTag: - return visitNode(cbNode, (node as JSDocSeeTag).tagName) || - visitNode(cbNode, (node as JSDocSeeTag).name) || - (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); - case SyntaxKind.JSDocNameReference: - return visitNode(cbNode, (node as JSDocNameReference).name); - case SyntaxKind.JSDocMemberName: - return visitNode(cbNode, (node as JSDocMemberName).left) || - visitNode(cbNode, (node as JSDocMemberName).right); - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - ((node as JSDocPropertyLikeTag).isNameFirst - ? visitNode(cbNode, (node as JSDocPropertyLikeTag).name) || - visitNode(cbNode, (node as JSDocPropertyLikeTag).typeExpression) || - (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)) - : visitNode(cbNode, (node as JSDocPropertyLikeTag).typeExpression) || - visitNode(cbNode, (node as JSDocPropertyLikeTag).name) || - (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined))); - case SyntaxKind.JSDocAuthorTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); - case SyntaxKind.JSDocImplementsTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - visitNode(cbNode, (node as JSDocImplementsTag).class) || - (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); - case SyntaxKind.JSDocAugmentsTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - visitNode(cbNode, (node as JSDocAugmentsTag).class) || - (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); - case SyntaxKind.JSDocTemplateTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - visitNode(cbNode, (node as JSDocTemplateTag).constraint) || - visitNodes(cbNode, cbNodes, (node as JSDocTemplateTag).typeParameters) || - (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); - case SyntaxKind.JSDocTypedefTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - ((node as JSDocTypedefTag).typeExpression && - (node as JSDocTypedefTag).typeExpression!.kind === SyntaxKind.JSDocTypeExpression - ? visitNode(cbNode, (node as JSDocTypedefTag).typeExpression) || - visitNode(cbNode, (node as JSDocTypedefTag).fullName) || - (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)) - : visitNode(cbNode, (node as JSDocTypedefTag).fullName) || - visitNode(cbNode, (node as JSDocTypedefTag).typeExpression) || - (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined))); - case SyntaxKind.JSDocCallbackTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - visitNode(cbNode, (node as JSDocCallbackTag).fullName) || - visitNode(cbNode, (node as JSDocCallbackTag).typeExpression) || - (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); - case SyntaxKind.JSDocReturnTag: - case SyntaxKind.JSDocTypeTag: - case SyntaxKind.JSDocThisTag: - case SyntaxKind.JSDocEnumTag: - return visitNode(cbNode, (node as JSDocTag).tagName) || - visitNode(cbNode, (node as JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag).typeExpression) || - (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); - case SyntaxKind.JSDocSignature: - return forEach((node as JSDocSignature).typeParameters, cbNode) || - forEach((node as JSDocSignature).parameters, cbNode) || - visitNode(cbNode, (node as JSDocSignature).type); - case SyntaxKind.JSDocLink: - case SyntaxKind.JSDocLinkCode: - case SyntaxKind.JSDocLinkPlain: - return visitNode(cbNode, (node as JSDocLink | JSDocLinkCode | JSDocLinkPlain).name); - case SyntaxKind.JSDocTypeLiteral: - return forEach((node as JSDocTypeLiteral).jsDocPropertyTags, cbNode); - case SyntaxKind.JSDocTag: - case SyntaxKind.JSDocClassTag: - case SyntaxKind.JSDocPublicTag: - case SyntaxKind.JSDocPrivateTag: - case SyntaxKind.JSDocProtectedTag: - case SyntaxKind.JSDocReadonlyTag: - case SyntaxKind.JSDocDeprecatedTag: - return visitNode(cbNode, (node as JSDocTag).tagName) - || (typeof (node as JSDoc).comment === "string" ? undefined : visitNodes(cbNode, cbNodes, (node as JSDoc).comment as NodeArray | undefined)); - case SyntaxKind.PartiallyEmittedExpression: - return visitNode(cbNode, (node as PartiallyEmittedExpression).expression); - } - } - - /** @internal */ - /** - * Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes - * stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; additionally, - * unlike `forEachChild`, embedded arrays are flattened and the 'cbNode' callback is invoked for each element. - * If a callback returns a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned. - * - * @param node a given node to visit its children - * @param cbNode a callback to be invoked for all child nodes - * @param cbNodes a callback to be invoked for embedded array - * - * @remarks Unlike `forEachChild`, `forEachChildRecursively` handles recursively invoking the traversal on each child node found, - * and while doing so, handles traversing the structure without relying on the callstack to encode the tree structure. - */ - export function forEachChildRecursively(rootNode: Node, cbNode: (node: Node, parent: Node) => T | "skip" | undefined, cbNodes?: (nodes: NodeArray, parent: Node) => T | "skip" | undefined): T | undefined { - const queue: (Node | NodeArray)[] = gatherPossibleChildren(rootNode); - const parents: Node[] = []; // tracks parent references for elements in queue - while (parents.length < queue.length) { - parents.push(rootNode); - } - while (queue.length !== 0) { - const current = queue.pop()!; - const parent = parents.pop()!; - if (isArray(current)) { - if (cbNodes) { - const res = cbNodes(current, parent); - if (res) { - if (res === "skip") continue; - return res; - } - } - for (let i = current.length - 1; i >= 0; --i) { - queue.push(current[i]); - parents.push(parent); - } - } - else { - const res = cbNode(current, parent); +/** @internal */ +/** + * Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes + * stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; additionally, + * unlike `forEachChild`, embedded arrays are flattened and the 'cbNode' callback is invoked for each element. + * If a callback returns a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned. + * + * @param node a given node to visit its children + * @param cbNode a callback to be invoked for all child nodes + * @param cbNodes a callback to be invoked for embedded array + * + * @remarks Unlike `forEachChild`, `forEachChildRecursively` handles recursively invoking the traversal on each child node found, + * and while doing so, handles traversing the structure without relying on the callstack to encode the tree structure. + */ +export function forEachChildRecursively(rootNode: Node, cbNode: (node: Node, parent: Node) => T | "skip" | undefined, cbNodes?: (nodes: NodeArray, parent: Node) => T | "skip" | undefined): T | undefined { + const queue: (Node | NodeArray)[] = gatherPossibleChildren(rootNode); + const parents: Node[] = []; // tracks parent references for elements in queue + while (parents.length < queue.length) { + parents.push(rootNode); + } + while (queue.length !== 0) { + const current = queue.pop()!; + const parent = parents.pop()!; + if (isArray(current)) { + if (cbNodes) { + const res = cbNodes(current, parent); if (res) { if (res === "skip") continue; return res; } - if (current.kind >= SyntaxKind.FirstNode) { - // add children in reverse order to the queue, so popping gives the first child - for (const child of gatherPossibleChildren(current)) { - queue.push(child); - parents.push(current); - } + } + for (let i = current.length - 1; i >= 0; --i) { + queue.push(current[i]); + parents.push(parent); + } + } + else { + const res = cbNode(current, parent); + if (res) { + if (res === "skip") continue; + return res; + } + if (current.kind >= SyntaxKind.FirstNode) { + // add children in reverse order to the queue, so popping gives the first child + for (const child of gatherPossibleChildren(current)) { + queue.push(child); + parents.push(current); } } } } +} - function gatherPossibleChildren(node: Node) { - const children: (Node | NodeArray)[] = []; - forEachChild(node, addWorkItem, addWorkItem); // By using a stack above and `unshift` here, we emulate a depth-first preorder traversal - return children; +function gatherPossibleChildren(node: Node) { + const children: (Node | NodeArray)[] = []; + forEachChild(node, addWorkItem, addWorkItem); // By using a stack above and `unshift` here, we emulate a depth-first preorder traversal + return children; - function addWorkItem(n: Node | NodeArray) { - children.unshift(n); - } + function addWorkItem(n: Node | NodeArray) { + children.unshift(n); } +} - export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { - tracing?.push(tracing.Phase.Parse, "createSourceFile", { path: fileName }, /*separateBeginAndEnd*/ true); - performance.mark("beforeParse"); - let result: SourceFile; - - perfLogger.logStartParseSourceFile(fileName); - if (languageVersion === ScriptTarget.JSON) { - result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, ScriptKind.JSON); - } - else { - result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind); - } - perfLogger.logStopParseSourceFile(); +export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { + tracing?.push(tracing.Phase.Parse, "createSourceFile", { path: fileName }, /*separateBeginAndEnd*/ true); + performance.mark("beforeParse"); + let result: SourceFile; - performance.mark("afterParse"); - performance.measure("Parse", "beforeParse", "afterParse"); - tracing?.pop(); - return result; + perfLogger.logStartParseSourceFile(fileName); + if (languageVersion === ScriptTarget.JSON) { + result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, ScriptKind.JSON); } - - export function parseIsolatedEntityName(text: string, languageVersion: ScriptTarget): EntityName | undefined { - return Parser.parseIsolatedEntityName(text, languageVersion); + else { + result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind); } + perfLogger.logStopParseSourceFile(); - /** - * Parse json text into SyntaxTree and return node and parse errors if any - * @param fileName - * @param sourceText - */ - export function parseJsonText(fileName: string, sourceText: string): JsonSourceFile { - return Parser.parseJsonText(fileName, sourceText); - } + performance.mark("afterParse"); + performance.measure("Parse", "beforeParse", "afterParse"); + tracing?.pop(); + return result; +} - // See also `isExternalOrCommonJsModule` in utilities.ts - export function isExternalModule(file: SourceFile): boolean { - return file.externalModuleIndicator !== undefined; - } +export function parseIsolatedEntityName(text: string, languageVersion: ScriptTarget): EntityName | undefined { + return Parser.parseIsolatedEntityName(text, languageVersion); +} - // Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter - // indicates what changed between the 'text' that this SourceFile has and the 'newText'. - // The SourceFile will be created with the compiler attempting to reuse as many nodes from - // this file as possible. - // - // Note: this function mutates nodes from this SourceFile. That means any existing nodes - // from this SourceFile that are being held onto may change as a result (including - // becoming detached from any SourceFile). It is recommended that this SourceFile not - // be used once 'update' is called on it. - export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks = false): SourceFile { - const newSourceFile = IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); - // Because new source file node is created, it may not have the flag PossiblyContainDynamicImport. This is the case if there is no new edit to add dynamic import. - // We will manually port the flag to the new source file. - (newSourceFile as Mutable).flags |= (sourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags); - return newSourceFile; - } - - /* @internal */ - export function parseIsolatedJSDocComment(content: string, start?: number, length?: number) { - const result = Parser.JSDocParser.parseIsolatedJSDocComment(content, start, length); - if (result && result.jsDoc) { - // because the jsDocComment was parsed out of the source file, it might - // not be covered by the fixupParentReferences. - Parser.fixupParentReferences(result.jsDoc); - } +/** + * Parse json text into SyntaxTree and return node and parse errors if any + * @param fileName + * @param sourceText + */ +export function parseJsonText(fileName: string, sourceText: string): JsonSourceFile { + return Parser.parseJsonText(fileName, sourceText); +} - return result; - } +// See also `isExternalOrCommonJsModule` in utilities.ts +export function isExternalModule(file: SourceFile): boolean { + return file.externalModuleIndicator !== undefined; +} - /* @internal */ - // Exposed only for testing. - export function parseJSDocTypeExpressionForTests(content: string, start?: number, length?: number) { - return Parser.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length); - } +// Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter +// indicates what changed between the 'text' that this SourceFile has and the 'newText'. +// The SourceFile will be created with the compiler attempting to reuse as many nodes from +// this file as possible. +// +// Note: this function mutates nodes from this SourceFile. That means any existing nodes +// from this SourceFile that are being held onto may change as a result (including +// becoming detached from any SourceFile). It is recommended that this SourceFile not +// be used once 'update' is called on it. +export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks = false): SourceFile { + const newSourceFile = IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); + // Because new source file node is created, it may not have the flag PossiblyContainDynamicImport. This is the case if there is no new edit to add dynamic import. + // We will manually port the flag to the new source file. + (newSourceFile as Mutable).flags |= (sourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags); + return newSourceFile; +} - // Implement the parser as a singleton module. We do this for perf reasons because creating - // parser instances can actually be expensive enough to impact us on projects with many source - // files. - namespace Parser { - // Share a single scanner across all calls to parse a source file. This helps speed things - // up by avoiding the cost of creating/compiling scanners over and over again. - const scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); +/* @internal */ +export function parseIsolatedJSDocComment(content: string, start?: number, length?: number) { + const result = Parser.JSDocParser.parseIsolatedJSDocComment(content, start, length); + if (result && result.jsDoc) { + // because the jsDocComment was parsed out of the source file, it might + // not be covered by the fixupParentReferences. + Parser.fixupParentReferences(result.jsDoc); + } - const disallowInAndDecoratorContext = NodeFlags.DisallowInContext | NodeFlags.DecoratorContext; + return result; +} - // capture constructors in 'initializeState' to avoid null checks - // tslint:disable variable-name - let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - let TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - let IdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - // tslint:enable variable-name +/* @internal */ +// Exposed only for testing. +export function parseJSDocTypeExpressionForTests(content: string, start?: number, length?: number) { + return Parser.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length); +} - function countNode(node: Node) { - nodeCount++; - return node; - } +// Implement the parser as a singleton module. We do this for perf reasons because creating +// parser instances can actually be expensive enough to impact us on projects with many source +// files. +namespace Parser { + // Share a single scanner across all calls to parse a source file. This helps speed things + // up by avoiding the cost of creating/compiling scanners over and over again. + const scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); + + const disallowInAndDecoratorContext = NodeFlags.DisallowInContext | NodeFlags.DecoratorContext; + + // capture constructors in 'initializeState' to avoid null checks + // tslint:disable variable-name + let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let IdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + // tslint:enable variable-name + + function countNode(node: Node) { + nodeCount++; + return node; + } - // Rather than using `createBaseNodeFactory` here, we establish a `BaseNodeFactory` that closes over the - // constructors above, which are reset each time `initializeState` is called. - const baseNodeFactory: BaseNodeFactory = { - createBaseSourceFileNode: kind => countNode(new SourceFileConstructor(kind, /*pos*/ 0, /*end*/ 0)), - createBaseIdentifierNode: kind => countNode(new IdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)), - createBasePrivateIdentifierNode: kind => countNode(new PrivateIdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)), - createBaseTokenNode: kind => countNode(new TokenConstructor(kind, /*pos*/ 0, /*end*/ 0)), - createBaseNode: kind => countNode(new NodeConstructor(kind, /*pos*/ 0, /*end*/ 0)) - }; + // Rather than using `createBaseNodeFactory` here, we establish a `BaseNodeFactory` that closes over the + // constructors above, which are reset each time `initializeState` is called. + const baseNodeFactory: BaseNodeFactory = { + createBaseSourceFileNode: kind => countNode(new SourceFileConstructor(kind, /*pos*/ 0, /*end*/ 0)), + createBaseIdentifierNode: kind => countNode(new IdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)), + createBasePrivateIdentifierNode: kind => countNode(new PrivateIdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)), + createBaseTokenNode: kind => countNode(new TokenConstructor(kind, /*pos*/ 0, /*end*/ 0)), + createBaseNode: kind => countNode(new NodeConstructor(kind, /*pos*/ 0, /*end*/ 0)) + }; - const factory = createNodeFactory(NodeFactoryFlags.NoParenthesizerRules | NodeFactoryFlags.NoNodeConverters | NodeFactoryFlags.NoOriginalNode, baseNodeFactory); + const factory = createNodeFactory(NodeFactoryFlags.NoParenthesizerRules | NodeFactoryFlags.NoNodeConverters | NodeFactoryFlags.NoOriginalNode, baseNodeFactory); - let fileName: string; - let sourceFlags: NodeFlags; - let sourceText: string; - let languageVersion: ScriptTarget; - let scriptKind: ScriptKind; - let languageVariant: LanguageVariant; - let parseDiagnostics: DiagnosticWithDetachedLocation[]; - let jsDocDiagnostics: DiagnosticWithDetachedLocation[]; - let syntaxCursor: IncrementalParser.SyntaxCursor | undefined; + let fileName: string; + let sourceFlags: NodeFlags; + let sourceText: string; + let languageVersion: ScriptTarget; + let scriptKind: ScriptKind; + let languageVariant: LanguageVariant; + let parseDiagnostics: DiagnosticWithDetachedLocation[]; + let jsDocDiagnostics: DiagnosticWithDetachedLocation[]; + let syntaxCursor: IncrementalParser.SyntaxCursor | undefined; - let currentToken: SyntaxKind; - let nodeCount: number; - let identifiers: ESMap; - let privateIdentifiers: ESMap; - let identifierCount: number; + let currentToken: SyntaxKind; + let nodeCount: number; + let identifiers: ESMap; + let privateIdentifiers: ESMap; + let identifierCount: number; - let parsingContext: ParsingContext; + let parsingContext: ParsingContext; - let notParenthesizedArrow: Set | undefined; + let notParenthesizedArrow: Set | undefined; - // Flags that dictate what parsing context we're in. For example: - // Whether or not we are in strict parsing mode. All that changes in strict parsing mode is - // that some tokens that would be considered identifiers may be considered keywords. - // - // When adding more parser context flags, consider which is the more common case that the - // flag will be in. This should be the 'false' state for that flag. The reason for this is - // that we don't store data in our nodes unless the value is in the *non-default* state. So, - // for example, more often than code 'allows-in' (or doesn't 'disallow-in'). We opt for - // 'disallow-in' set to 'false'. Otherwise, if we had 'allowsIn' set to 'true', then almost - // all nodes would need extra state on them to store this info. - // - // Note: 'allowIn' and 'allowYield' track 1:1 with the [in] and [yield] concepts in the ES6 - // grammar specification. - // - // An important thing about these context concepts. By default they are effectively inherited - // while parsing through every grammar production. i.e. if you don't change them, then when - // you parse a sub-production, it will have the same context values as the parent production. - // This is great most of the time. After all, consider all the 'expression' grammar productions - // and how nearly all of them pass along the 'in' and 'yield' context values: - // - // EqualityExpression[In, Yield] : - // RelationalExpression[?In, ?Yield] - // EqualityExpression[?In, ?Yield] == RelationalExpression[?In, ?Yield] - // EqualityExpression[?In, ?Yield] != RelationalExpression[?In, ?Yield] - // EqualityExpression[?In, ?Yield] === RelationalExpression[?In, ?Yield] - // EqualityExpression[?In, ?Yield] !== RelationalExpression[?In, ?Yield] - // - // Where you have to be careful is then understanding what the points are in the grammar - // where the values are *not* passed along. For example: - // - // SingleNameBinding[Yield,GeneratorParameter] - // [+GeneratorParameter]BindingIdentifier[Yield] Initializer[In]opt - // [~GeneratorParameter]BindingIdentifier[?Yield]Initializer[In, ?Yield]opt - // - // Here this is saying that if the GeneratorParameter context flag is set, that we should - // explicitly set the 'yield' context flag to false before calling into the BindingIdentifier - // and we should explicitly unset the 'yield' context flag before calling into the Initializer. - // production. Conversely, if the GeneratorParameter context flag is not set, then we - // should leave the 'yield' context flag alone. - // - // Getting this all correct is tricky and requires careful reading of the grammar to - // understand when these values should be changed versus when they should be inherited. - // - // Note: it should not be necessary to save/restore these flags during speculative/lookahead - // parsing. These context flags are naturally stored and restored through normal recursive - // descent parsing and unwinding. - let contextFlags: NodeFlags; + // Flags that dictate what parsing context we're in. For example: + // Whether or not we are in strict parsing mode. All that changes in strict parsing mode is + // that some tokens that would be considered identifiers may be considered keywords. + // + // When adding more parser context flags, consider which is the more common case that the + // flag will be in. This should be the 'false' state for that flag. The reason for this is + // that we don't store data in our nodes unless the value is in the *non-default* state. So, + // for example, more often than code 'allows-in' (or doesn't 'disallow-in'). We opt for + // 'disallow-in' set to 'false'. Otherwise, if we had 'allowsIn' set to 'true', then almost + // all nodes would need extra state on them to store this info. + // + // Note: 'allowIn' and 'allowYield' track 1:1 with the [in] and [yield] concepts in the ES6 + // grammar specification. + // + // An important thing about these context concepts. By default they are effectively inherited + // while parsing through every grammar production. i.e. if you don't change them, then when + // you parse a sub-production, it will have the same context values as the parent production. + // This is great most of the time. After all, consider all the 'expression' grammar productions + // and how nearly all of them pass along the 'in' and 'yield' context values: + // + // EqualityExpression[In, Yield] : + // RelationalExpression[?In, ?Yield] + // EqualityExpression[?In, ?Yield] == RelationalExpression[?In, ?Yield] + // EqualityExpression[?In, ?Yield] != RelationalExpression[?In, ?Yield] + // EqualityExpression[?In, ?Yield] === RelationalExpression[?In, ?Yield] + // EqualityExpression[?In, ?Yield] !== RelationalExpression[?In, ?Yield] + // + // Where you have to be careful is then understanding what the points are in the grammar + // where the values are *not* passed along. For example: + // + // SingleNameBinding[Yield,GeneratorParameter] + // [+GeneratorParameter]BindingIdentifier[Yield] Initializer[In]opt + // [~GeneratorParameter]BindingIdentifier[?Yield]Initializer[In, ?Yield]opt + // + // Here this is saying that if the GeneratorParameter context flag is set, that we should + // explicitly set the 'yield' context flag to false before calling into the BindingIdentifier + // and we should explicitly unset the 'yield' context flag before calling into the Initializer. + // production. Conversely, if the GeneratorParameter context flag is not set, then we + // should leave the 'yield' context flag alone. + // + // Getting this all correct is tricky and requires careful reading of the grammar to + // understand when these values should be changed versus when they should be inherited. + // + // Note: it should not be necessary to save/restore these flags during speculative/lookahead + // parsing. These context flags are naturally stored and restored through normal recursive + // descent parsing and unwinding. + let contextFlags: NodeFlags; - // Indicates whether we are currently parsing top-level statements. - let topLevel = true; + // Indicates whether we are currently parsing top-level statements. + let topLevel = true; - // Whether or not we've had a parse error since creating the last AST node. If we have - // encountered an error, it will be stored on the next AST node we create. Parse errors - // can be broken down into three categories: - // - // 1) An error that occurred during scanning. For example, an unterminated literal, or a - // character that was completely not understood. - // - // 2) A token was expected, but was not present. This type of error is commonly produced - // by the 'parseExpected' function. - // - // 3) A token was present that no parsing function was able to consume. This type of error - // only occurs in the 'abortParsingListOrMoveToNextToken' function when the parser - // decides to skip the token. - // - // In all of these cases, we want to mark the next node as having had an error before it. - // With this mark, we can know in incremental settings if this node can be reused, or if - // we have to reparse it. If we don't keep this information around, we may just reuse the - // node. in that event we would then not produce the same errors as we did before, causing - // significant confusion problems. - // - // Note: it is necessary that this value be saved/restored during speculative/lookahead - // parsing. During lookahead parsing, we will often create a node. That node will have - // this value attached, and then this value will be set back to 'false'. If we decide to - // rewind, we must get back to the same value we had prior to the lookahead. - // - // Note: any errors at the end of the file that do not precede a regular node, should get - // attached to the EOF token. - let parseErrorBeforeNextFinishedNode = false; - - export function parseSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, syntaxCursor: IncrementalParser.SyntaxCursor | undefined, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { - scriptKind = ensureScriptKind(fileName, scriptKind); - if (scriptKind === ScriptKind.JSON) { - const result = parseJsonText(fileName, sourceText, languageVersion, syntaxCursor, setParentNodes); - convertToObjectWorker(result, result.statements[0]?.expression, result.parseDiagnostics, /*returnValue*/ false, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); - result.referencedFiles = emptyArray; - result.typeReferenceDirectives = emptyArray; - result.libReferenceDirectives = emptyArray; - result.amdDependencies = emptyArray; - result.hasNoDefaultLib = false; - result.pragmas = emptyMap as ReadonlyPragmaMap; - return result; - } + // Whether or not we've had a parse error since creating the last AST node. If we have + // encountered an error, it will be stored on the next AST node we create. Parse errors + // can be broken down into three categories: + // + // 1) An error that occurred during scanning. For example, an unterminated literal, or a + // character that was completely not understood. + // + // 2) A token was expected, but was not present. This type of error is commonly produced + // by the 'parseExpected' function. + // + // 3) A token was present that no parsing function was able to consume. This type of error + // only occurs in the 'abortParsingListOrMoveToNextToken' function when the parser + // decides to skip the token. + // + // In all of these cases, we want to mark the next node as having had an error before it. + // With this mark, we can know in incremental settings if this node can be reused, or if + // we have to reparse it. If we don't keep this information around, we may just reuse the + // node. in that event we would then not produce the same errors as we did before, causing + // significant confusion problems. + // + // Note: it is necessary that this value be saved/restored during speculative/lookahead + // parsing. During lookahead parsing, we will often create a node. That node will have + // this value attached, and then this value will be set back to 'false'. If we decide to + // rewind, we must get back to the same value we had prior to the lookahead. + // + // Note: any errors at the end of the file that do not precede a regular node, should get + // attached to the EOF token. + let parseErrorBeforeNextFinishedNode = false; + + export function parseSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, syntaxCursor: IncrementalParser.SyntaxCursor | undefined, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { + scriptKind = ensureScriptKind(fileName, scriptKind); + if (scriptKind === ScriptKind.JSON) { + const result = parseJsonText(fileName, sourceText, languageVersion, syntaxCursor, setParentNodes); + convertToObjectWorker(result, result.statements[0]?.expression, result.parseDiagnostics, /*returnValue*/ false, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); + result.referencedFiles = emptyArray; + result.typeReferenceDirectives = emptyArray; + result.libReferenceDirectives = emptyArray; + result.amdDependencies = emptyArray; + result.hasNoDefaultLib = false; + result.pragmas = emptyMap as ReadonlyPragmaMap; + return result; + } - initializeState(fileName, sourceText, languageVersion, syntaxCursor, scriptKind); + initializeState(fileName, sourceText, languageVersion, syntaxCursor, scriptKind); - const result = parseSourceFileWorker(languageVersion, setParentNodes, scriptKind); + const result = parseSourceFileWorker(languageVersion, setParentNodes, scriptKind); - clearState(); + clearState(); - return result; - } + return result; + } - export function parseIsolatedEntityName(content: string, languageVersion: ScriptTarget): EntityName | undefined { - // Choice of `isDeclarationFile` should be arbitrary - initializeState("", content, languageVersion, /*syntaxCursor*/ undefined, ScriptKind.JS); - // Prime the scanner. - nextToken(); - const entityName = parseEntityName(/*allowReservedWords*/ true); - const isInvalid = token() === SyntaxKind.EndOfFileToken && !parseDiagnostics.length; - clearState(); - return isInvalid ? entityName : undefined; - } + export function parseIsolatedEntityName(content: string, languageVersion: ScriptTarget): EntityName | undefined { + // Choice of `isDeclarationFile` should be arbitrary + initializeState("", content, languageVersion, /*syntaxCursor*/ undefined, ScriptKind.JS); + // Prime the scanner. + nextToken(); + const entityName = parseEntityName(/*allowReservedWords*/ true); + const isInvalid = token() === SyntaxKind.EndOfFileToken && !parseDiagnostics.length; + clearState(); + return isInvalid ? entityName : undefined; + } - export function parseJsonText(fileName: string, sourceText: string, languageVersion: ScriptTarget = ScriptTarget.ES2015, syntaxCursor?: IncrementalParser.SyntaxCursor, setParentNodes = false): JsonSourceFile { - initializeState(fileName, sourceText, languageVersion, syntaxCursor, ScriptKind.JSON); - sourceFlags = contextFlags; + export function parseJsonText(fileName: string, sourceText: string, languageVersion: ScriptTarget = ScriptTarget.ES2015, syntaxCursor?: IncrementalParser.SyntaxCursor, setParentNodes = false): JsonSourceFile { + initializeState(fileName, sourceText, languageVersion, syntaxCursor, ScriptKind.JSON); + sourceFlags = contextFlags; - // Prime the scanner. - nextToken(); - const pos = getNodePos(); - let statements, endOfFileToken; - if (token() === SyntaxKind.EndOfFileToken) { - statements = createNodeArray([], pos, pos); - endOfFileToken = parseTokenNode(); - } - else { - // Loop and synthesize an ArrayLiteralExpression if there are more than - // one top-level expressions to ensure all input text is consumed. - let expressions: Expression[] | Expression | undefined; - while (token() !== SyntaxKind.EndOfFileToken) { - let expression; - switch (token()) { - case SyntaxKind.OpenBracketToken: - expression = parseArrayLiteralExpression(); - break; - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - expression = parseTokenNode(); - break; - case SyntaxKind.MinusToken: - if (lookAhead(() => nextToken() === SyntaxKind.NumericLiteral && nextToken() !== SyntaxKind.ColonToken)) { - expression = parsePrefixUnaryExpression() as JsonMinusNumericLiteral; - } - else { - expression = parseObjectLiteralExpression(); - } - break; - case SyntaxKind.NumericLiteral: - case SyntaxKind.StringLiteral: - if (lookAhead(() => nextToken() !== SyntaxKind.ColonToken)) { - expression = parseLiteralNode() as StringLiteral | NumericLiteral; - break; - } - // falls through - default: + // Prime the scanner. + nextToken(); + const pos = getNodePos(); + let statements, endOfFileToken; + if (token() === SyntaxKind.EndOfFileToken) { + statements = createNodeArray([], pos, pos); + endOfFileToken = parseTokenNode(); + } + else { + // Loop and synthesize an ArrayLiteralExpression if there are more than + // one top-level expressions to ensure all input text is consumed. + let expressions: Expression[] | Expression | undefined; + while (token() !== SyntaxKind.EndOfFileToken) { + let expression; + switch (token()) { + case SyntaxKind.OpenBracketToken: + expression = parseArrayLiteralExpression(); + break; + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + expression = parseTokenNode(); + break; + case SyntaxKind.MinusToken: + if (lookAhead(() => nextToken() === SyntaxKind.NumericLiteral && nextToken() !== SyntaxKind.ColonToken)) { + expression = parsePrefixUnaryExpression() as JsonMinusNumericLiteral; + } + else { expression = parseObjectLiteralExpression(); + } + break; + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + if (lookAhead(() => nextToken() !== SyntaxKind.ColonToken)) { + expression = parseLiteralNode() as StringLiteral | NumericLiteral; break; - } - - // Error recovery: collect multiple top-level expressions - if (expressions && isArray(expressions)) { - expressions.push(expression); - } - else if (expressions) { - expressions = [expressions, expression]; - } - else { - expressions = expression; - if (token() !== SyntaxKind.EndOfFileToken) { - parseErrorAtCurrentToken(Diagnostics.Unexpected_token); } - } + // falls through + default: + expression = parseObjectLiteralExpression(); + break; } - const expression = isArray(expressions) ? finishNode(factory.createArrayLiteralExpression(expressions), pos) : Debug.checkDefined(expressions); - const statement = factory.createExpressionStatement(expression) as JsonObjectExpressionStatement; - finishNode(statement, pos); - statements = createNodeArray([statement], pos); - endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, Diagnostics.Unexpected_token); - } - - // Set source file so that errors will be reported with this file name - const sourceFile = createSourceFile(fileName, ScriptTarget.ES2015, ScriptKind.JSON, /*isDeclaration*/ false, statements, endOfFileToken, sourceFlags); - - if (setParentNodes) { - fixupParentReferences(sourceFile); - } - - sourceFile.nodeCount = nodeCount; - sourceFile.identifierCount = identifierCount; - sourceFile.identifiers = identifiers; - sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); - if (jsDocDiagnostics) { - sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); + // Error recovery: collect multiple top-level expressions + if (expressions && isArray(expressions)) { + expressions.push(expression); + } + else if (expressions) { + expressions = [expressions, expression]; + } + else { + expressions = expression; + if (token() !== SyntaxKind.EndOfFileToken) { + parseErrorAtCurrentToken(Diagnostics.Unexpected_token); + } + } } - const result = sourceFile as JsonSourceFile; - clearState(); - return result; + const expression = isArray(expressions) ? finishNode(factory.createArrayLiteralExpression(expressions), pos) : Debug.checkDefined(expressions); + const statement = factory.createExpressionStatement(expression) as JsonObjectExpressionStatement; + finishNode(statement, pos); + statements = createNodeArray([statement], pos); + endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, Diagnostics.Unexpected_token); } - function initializeState(_fileName: string, _sourceText: string, _languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, _scriptKind: ScriptKind) { - NodeConstructor = objectAllocator.getNodeConstructor(); - TokenConstructor = objectAllocator.getTokenConstructor(); - IdentifierConstructor = objectAllocator.getIdentifierConstructor(); - PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor(); - SourceFileConstructor = objectAllocator.getSourceFileConstructor(); - - fileName = normalizePath(_fileName); - sourceText = _sourceText; - languageVersion = _languageVersion; - syntaxCursor = _syntaxCursor; - scriptKind = _scriptKind; - languageVariant = getLanguageVariant(_scriptKind); - - parseDiagnostics = []; - parsingContext = 0; - identifiers = new Map(); - privateIdentifiers = new Map(); - identifierCount = 0; - nodeCount = 0; - sourceFlags = 0; - topLevel = true; - - switch (scriptKind) { - case ScriptKind.JS: - case ScriptKind.JSX: - contextFlags = NodeFlags.JavaScriptFile; - break; - case ScriptKind.JSON: - contextFlags = NodeFlags.JavaScriptFile | NodeFlags.JsonFile; - break; - default: - contextFlags = NodeFlags.None; - break; - } - parseErrorBeforeNextFinishedNode = false; + // Set source file so that errors will be reported with this file name + const sourceFile = createSourceFile(fileName, ScriptTarget.ES2015, ScriptKind.JSON, /*isDeclaration*/ false, statements, endOfFileToken, sourceFlags); - // Initialize and prime the scanner before parsing the source elements. - scanner.setText(sourceText); - scanner.setOnError(scanError); - scanner.setScriptTarget(languageVersion); - scanner.setLanguageVariant(languageVariant); + if (setParentNodes) { + fixupParentReferences(sourceFile); } - function clearState() { - // Clear out the text the scanner is pointing at, so it doesn't keep anything alive unnecessarily. - scanner.clearCommentDirectives(); - scanner.setText(""); - scanner.setOnError(undefined); - - // Clear any data. We don't want to accidentally hold onto it for too long. - sourceText = undefined!; - languageVersion = undefined!; - syntaxCursor = undefined; - scriptKind = undefined!; - languageVariant = undefined!; - sourceFlags = 0; - parseDiagnostics = undefined!; - jsDocDiagnostics = undefined!; - parsingContext = 0; - identifiers = undefined!; - notParenthesizedArrow = undefined; - topLevel = true; + sourceFile.nodeCount = nodeCount; + sourceFile.identifierCount = identifierCount; + sourceFile.identifiers = identifiers; + sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); + if (jsDocDiagnostics) { + sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); } - function parseSourceFileWorker(languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind): SourceFile { - const isDeclarationFile = isDeclarationFileName(fileName); - if (isDeclarationFile) { - contextFlags |= NodeFlags.Ambient; - } + const result = sourceFile as JsonSourceFile; + clearState(); + return result; + } - sourceFlags = contextFlags; + function initializeState(_fileName: string, _sourceText: string, _languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, _scriptKind: ScriptKind) { + NodeConstructor = objectAllocator.getNodeConstructor(); + TokenConstructor = objectAllocator.getTokenConstructor(); + IdentifierConstructor = objectAllocator.getIdentifierConstructor(); + PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor(); + SourceFileConstructor = objectAllocator.getSourceFileConstructor(); + + fileName = normalizePath(_fileName); + sourceText = _sourceText; + languageVersion = _languageVersion; + syntaxCursor = _syntaxCursor; + scriptKind = _scriptKind; + languageVariant = getLanguageVariant(_scriptKind); + + parseDiagnostics = []; + parsingContext = 0; + identifiers = new Map(); + privateIdentifiers = new Map(); + identifierCount = 0; + nodeCount = 0; + sourceFlags = 0; + topLevel = true; + + switch (scriptKind) { + case ScriptKind.JS: + case ScriptKind.JSX: + contextFlags = NodeFlags.JavaScriptFile; + break; + case ScriptKind.JSON: + contextFlags = NodeFlags.JavaScriptFile | NodeFlags.JsonFile; + break; + default: + contextFlags = NodeFlags.None; + break; + } + parseErrorBeforeNextFinishedNode = false; - // Prime the scanner. - nextToken(); + // Initialize and prime the scanner before parsing the source elements. + scanner.setText(sourceText); + scanner.setOnError(scanError); + scanner.setScriptTarget(languageVersion); + scanner.setLanguageVariant(languageVariant); + } - const statements = parseList(ParsingContext.SourceElements, parseStatement); - Debug.assert(token() === SyntaxKind.EndOfFileToken); - const endOfFileToken = addJSDocComment(parseTokenNode()); + function clearState() { + // Clear out the text the scanner is pointing at, so it doesn't keep anything alive unnecessarily. + scanner.clearCommentDirectives(); + scanner.setText(""); + scanner.setOnError(undefined); + + // Clear any data. We don't want to accidentally hold onto it for too long. + sourceText = undefined!; + languageVersion = undefined!; + syntaxCursor = undefined; + scriptKind = undefined!; + languageVariant = undefined!; + sourceFlags = 0; + parseDiagnostics = undefined!; + jsDocDiagnostics = undefined!; + parsingContext = 0; + identifiers = undefined!; + notParenthesizedArrow = undefined; + topLevel = true; + } - const sourceFile = createSourceFile(fileName, languageVersion, scriptKind, isDeclarationFile, statements, endOfFileToken, sourceFlags); + function parseSourceFileWorker(languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind): SourceFile { + const isDeclarationFile = isDeclarationFileName(fileName); + if (isDeclarationFile) { + contextFlags |= NodeFlags.Ambient; + } - // A member of ReadonlyArray isn't assignable to a member of T[] (and prevents a direct cast) - but this is where we set up those members so they can be readonly in the future - processCommentPragmas(sourceFile as {} as PragmaContext, sourceText); - processPragmasIntoFields(sourceFile as {} as PragmaContext, reportPragmaDiagnostic); + sourceFlags = contextFlags; - sourceFile.commentDirectives = scanner.getCommentDirectives(); - sourceFile.nodeCount = nodeCount; - sourceFile.identifierCount = identifierCount; - sourceFile.identifiers = identifiers; - sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); - if (jsDocDiagnostics) { - sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); - } + // Prime the scanner. + nextToken(); - if (setParentNodes) { - fixupParentReferences(sourceFile); - } + const statements = parseList(ParsingContext.SourceElements, parseStatement); + Debug.assert(token() === SyntaxKind.EndOfFileToken); + const endOfFileToken = addJSDocComment(parseTokenNode()); - return sourceFile; + const sourceFile = createSourceFile(fileName, languageVersion, scriptKind, isDeclarationFile, statements, endOfFileToken, sourceFlags); - function reportPragmaDiagnostic(pos: number, end: number, diagnostic: DiagnosticMessage) { - parseDiagnostics.push(createDetachedDiagnostic(fileName, pos, end, diagnostic)); - } - } + // A member of ReadonlyArray isn't assignable to a member of T[] (and prevents a direct cast) - but this is where we set up those members so they can be readonly in the future + processCommentPragmas(sourceFile as {} as PragmaContext, sourceText); + processPragmasIntoFields(sourceFile as {} as PragmaContext, reportPragmaDiagnostic); - function withJSDoc(node: T, hasJSDoc: boolean): T { - return hasJSDoc ? addJSDocComment(node) : node; + sourceFile.commentDirectives = scanner.getCommentDirectives(); + sourceFile.nodeCount = nodeCount; + sourceFile.identifierCount = identifierCount; + sourceFile.identifiers = identifiers; + sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); + if (jsDocDiagnostics) { + sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); } - let hasDeprecatedTag = false; - function addJSDocComment(node: T): T { - Debug.assert(!node.jsDoc); // Should only be called once per node - const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos)); - if (jsDoc.length) node.jsDoc = jsDoc; - if (hasDeprecatedTag) { - hasDeprecatedTag = false; - (node as Mutable).flags |= NodeFlags.Deprecated; - } - return node; + if (setParentNodes) { + fixupParentReferences(sourceFile); } - function reparseTopLevelAwait(sourceFile: SourceFile) { - const savedSyntaxCursor = syntaxCursor; - const baseSyntaxCursor = IncrementalParser.createSyntaxCursor(sourceFile); - syntaxCursor = { currentNode }; + return sourceFile; - const statements: Statement[] = []; - const savedParseDiagnostics = parseDiagnostics; + function reportPragmaDiagnostic(pos: number, end: number, diagnostic: DiagnosticMessage) { + parseDiagnostics.push(createDetachedDiagnostic(fileName, pos, end, diagnostic)); + } + } - parseDiagnostics = []; + function withJSDoc(node: T, hasJSDoc: boolean): T { + return hasJSDoc ? addJSDocComment(node) : node; + } - let pos = 0; - let start = findNextStatementWithAwait(sourceFile.statements, 0); - while (start !== -1) { - // append all statements between pos and start - const prevStatement = sourceFile.statements[pos]; - const nextStatement = sourceFile.statements[start]; - addRange(statements, sourceFile.statements, pos, start); - pos = findNextStatementWithoutAwait(sourceFile.statements, start); + let hasDeprecatedTag = false; + function addJSDocComment(node: T): T { + Debug.assert(!node.jsDoc); // Should only be called once per node + const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos)); + if (jsDoc.length) node.jsDoc = jsDoc; + if (hasDeprecatedTag) { + hasDeprecatedTag = false; + (node as Mutable).flags |= NodeFlags.Deprecated; + } + return node; + } - // append all diagnostics associated with the copied range - const diagnosticStart = findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= prevStatement.pos); - const diagnosticEnd = diagnosticStart >= 0 ? findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= nextStatement.pos, diagnosticStart) : -1; - if (diagnosticStart >= 0) { - addRange(parseDiagnostics, savedParseDiagnostics, diagnosticStart, diagnosticEnd >= 0 ? diagnosticEnd : undefined); - } + function reparseTopLevelAwait(sourceFile: SourceFile) { + const savedSyntaxCursor = syntaxCursor; + const baseSyntaxCursor = IncrementalParser.createSyntaxCursor(sourceFile); + syntaxCursor = { currentNode }; + + const statements: Statement[] = []; + const savedParseDiagnostics = parseDiagnostics; + + parseDiagnostics = []; + + let pos = 0; + let start = findNextStatementWithAwait(sourceFile.statements, 0); + while (start !== -1) { + // append all statements between pos and start + const prevStatement = sourceFile.statements[pos]; + const nextStatement = sourceFile.statements[start]; + addRange(statements, sourceFile.statements, pos, start); + pos = findNextStatementWithoutAwait(sourceFile.statements, start); + + // append all diagnostics associated with the copied range + const diagnosticStart = findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= prevStatement.pos); + const diagnosticEnd = diagnosticStart >= 0 ? findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= nextStatement.pos, diagnosticStart) : -1; + if (diagnosticStart >= 0) { + addRange(parseDiagnostics, savedParseDiagnostics, diagnosticStart, diagnosticEnd >= 0 ? diagnosticEnd : undefined); + } + + // reparse all statements between start and pos. We skip existing diagnostics for the same range and allow the parser to generate new ones. + speculationHelper(() => { + const savedContextFlags = contextFlags; + contextFlags |= NodeFlags.AwaitContext; + scanner.setTextPos(nextStatement.pos); + nextToken(); - // reparse all statements between start and pos. We skip existing diagnostics for the same range and allow the parser to generate new ones. - speculationHelper(() => { - const savedContextFlags = contextFlags; - contextFlags |= NodeFlags.AwaitContext; - scanner.setTextPos(nextStatement.pos); - nextToken(); + while (token() !== SyntaxKind.EndOfFileToken) { + const startPos = scanner.getStartPos(); + const statement = parseListElement(ParsingContext.SourceElements, parseStatement); + statements.push(statement); + if (startPos === scanner.getStartPos()) { + nextToken(); + } - while (token() !== SyntaxKind.EndOfFileToken) { - const startPos = scanner.getStartPos(); - const statement = parseListElement(ParsingContext.SourceElements, parseStatement); - statements.push(statement); - if (startPos === scanner.getStartPos()) { - nextToken(); + if (pos >= 0) { + const nonAwaitStatement = sourceFile.statements[pos]; + if (statement.end === nonAwaitStatement.pos) { + // done reparsing this section + break; } - - if (pos >= 0) { - const nonAwaitStatement = sourceFile.statements[pos]; - if (statement.end === nonAwaitStatement.pos) { - // done reparsing this section - break; - } - if (statement.end > nonAwaitStatement.pos) { - // we ate into the next statement, so we must reparse it. - pos = findNextStatementWithoutAwait(sourceFile.statements, pos + 1); - } + if (statement.end > nonAwaitStatement.pos) { + // we ate into the next statement, so we must reparse it. + pos = findNextStatementWithoutAwait(sourceFile.statements, pos + 1); } } + } - contextFlags = savedContextFlags; - }, SpeculationKind.Reparse); + contextFlags = savedContextFlags; + }, SpeculationKind.Reparse); - // find the next statement containing an `await` - start = pos >= 0 ? findNextStatementWithAwait(sourceFile.statements, pos) : -1; - } + // find the next statement containing an `await` + start = pos >= 0 ? findNextStatementWithAwait(sourceFile.statements, pos) : -1; + } - // append all statements between pos and the end of the list - if (pos >= 0) { - const prevStatement = sourceFile.statements[pos]; - addRange(statements, sourceFile.statements, pos); + // append all statements between pos and the end of the list + if (pos >= 0) { + const prevStatement = sourceFile.statements[pos]; + addRange(statements, sourceFile.statements, pos); - // append all diagnostics associated with the copied range - const diagnosticStart = findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= prevStatement.pos); - if (diagnosticStart >= 0) { - addRange(parseDiagnostics, savedParseDiagnostics, diagnosticStart); - } + // append all diagnostics associated with the copied range + const diagnosticStart = findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= prevStatement.pos); + if (diagnosticStart >= 0) { + addRange(parseDiagnostics, savedParseDiagnostics, diagnosticStart); } + } - syntaxCursor = savedSyntaxCursor; - return factory.updateSourceFile(sourceFile, setTextRange(factory.createNodeArray(statements), sourceFile.statements)); - - function containsPossibleTopLevelAwait(node: Node) { - return !(node.flags & NodeFlags.AwaitContext) - && !!(node.transformFlags & TransformFlags.ContainsPossibleTopLevelAwait); - } + syntaxCursor = savedSyntaxCursor; + return factory.updateSourceFile(sourceFile, setTextRange(factory.createNodeArray(statements), sourceFile.statements)); - function findNextStatementWithAwait(statements: NodeArray, start: number) { - for (let i = start; i < statements.length; i++) { - if (containsPossibleTopLevelAwait(statements[i])) { - return i; - } - } - return -1; - } + function containsPossibleTopLevelAwait(node: Node) { + return !(node.flags & NodeFlags.AwaitContext) + && !!(node.transformFlags & TransformFlags.ContainsPossibleTopLevelAwait); + } - function findNextStatementWithoutAwait(statements: NodeArray, start: number) { - for (let i = start; i < statements.length; i++) { - if (!containsPossibleTopLevelAwait(statements[i])) { - return i; - } + function findNextStatementWithAwait(statements: NodeArray, start: number) { + for (let i = start; i < statements.length; i++) { + if (containsPossibleTopLevelAwait(statements[i])) { + return i; } - return -1; } + return -1; + } - function currentNode(position: number) { - const node = baseSyntaxCursor.currentNode(position); - if (topLevel && node && containsPossibleTopLevelAwait(node)) { - node.intersectsChange = true; + function findNextStatementWithoutAwait(statements: NodeArray, start: number) { + for (let i = start; i < statements.length; i++) { + if (!containsPossibleTopLevelAwait(statements[i])) { + return i; } - return node; } - + return -1; } - export function fixupParentReferences(rootNode: Node) { - // normally parent references are set during binding. However, for clients that only need - // a syntax tree, and no semantic features, then the binding process is an unnecessary - // overhead. This functions allows us to set all the parents, without all the expense of - // binding. - setParentRecursive(rootNode, /*incremental*/ true); + function currentNode(position: number) { + const node = baseSyntaxCursor.currentNode(position); + if (topLevel && node && containsPossibleTopLevelAwait(node)) { + node.intersectsChange = true; + } + return node; } - function createSourceFile(fileName: string, languageVersion: ScriptTarget, scriptKind: ScriptKind, isDeclarationFile: boolean, statements: readonly Statement[], endOfFileToken: EndOfFileToken, flags: NodeFlags): SourceFile { - // code from createNode is inlined here so createNode won't have to deal with special case of creating source files - // this is quite rare comparing to other nodes and createNode should be as fast as possible - let sourceFile = factory.createSourceFile(statements, endOfFileToken, flags); - setTextRangePosWidth(sourceFile, 0, sourceText.length); - setExternalModuleIndicator(sourceFile); + } - // If we parsed this as an external module, it may contain top-level await - if (!isDeclarationFile && isExternalModule(sourceFile) && sourceFile.transformFlags & TransformFlags.ContainsPossibleTopLevelAwait) { - sourceFile = reparseTopLevelAwait(sourceFile); - } + export function fixupParentReferences(rootNode: Node) { + // normally parent references are set during binding. However, for clients that only need + // a syntax tree, and no semantic features, then the binding process is an unnecessary + // overhead. This functions allows us to set all the parents, without all the expense of + // binding. + setParentRecursive(rootNode, /*incremental*/ true); + } - sourceFile.text = sourceText; - sourceFile.bindDiagnostics = []; - sourceFile.bindSuggestionDiagnostics = undefined; - sourceFile.languageVersion = languageVersion; - sourceFile.fileName = fileName; - sourceFile.languageVariant = getLanguageVariant(scriptKind); - sourceFile.isDeclarationFile = isDeclarationFile; - sourceFile.scriptKind = scriptKind; + function createSourceFile(fileName: string, languageVersion: ScriptTarget, scriptKind: ScriptKind, isDeclarationFile: boolean, statements: readonly Statement[], endOfFileToken: EndOfFileToken, flags: NodeFlags): SourceFile { + // code from createNode is inlined here so createNode won't have to deal with special case of creating source files + // this is quite rare comparing to other nodes and createNode should be as fast as possible + let sourceFile = factory.createSourceFile(statements, endOfFileToken, flags); + setTextRangePosWidth(sourceFile, 0, sourceText.length); + setExternalModuleIndicator(sourceFile); - return sourceFile; + // If we parsed this as an external module, it may contain top-level await + if (!isDeclarationFile && isExternalModule(sourceFile) && sourceFile.transformFlags & TransformFlags.ContainsPossibleTopLevelAwait) { + sourceFile = reparseTopLevelAwait(sourceFile); } - function setContextFlag(val: boolean, flag: NodeFlags) { - if (val) { - contextFlags |= flag; - } - else { - contextFlags &= ~flag; - } - } + sourceFile.text = sourceText; + sourceFile.bindDiagnostics = []; + sourceFile.bindSuggestionDiagnostics = undefined; + sourceFile.languageVersion = languageVersion; + sourceFile.fileName = fileName; + sourceFile.languageVariant = getLanguageVariant(scriptKind); + sourceFile.isDeclarationFile = isDeclarationFile; + sourceFile.scriptKind = scriptKind; - function setDisallowInContext(val: boolean) { - setContextFlag(val, NodeFlags.DisallowInContext); - } + return sourceFile; + } - function setYieldContext(val: boolean) { - setContextFlag(val, NodeFlags.YieldContext); + function setContextFlag(val: boolean, flag: NodeFlags) { + if (val) { + contextFlags |= flag; } - - function setDecoratorContext(val: boolean) { - setContextFlag(val, NodeFlags.DecoratorContext); + else { + contextFlags &= ~flag; } + } - function setAwaitContext(val: boolean) { - setContextFlag(val, NodeFlags.AwaitContext); - } + function setDisallowInContext(val: boolean) { + setContextFlag(val, NodeFlags.DisallowInContext); + } - function doOutsideOfContext(context: NodeFlags, func: () => T): T { - // contextFlagsToClear will contain only the context flags that are - // currently set that we need to temporarily clear - // We don't just blindly reset to the previous flags to ensure - // that we do not mutate cached flags for the incremental - // parser (ThisNodeHasError, ThisNodeOrAnySubNodesHasError, and - // HasAggregatedChildData). - const contextFlagsToClear = context & contextFlags; - if (contextFlagsToClear) { - // clear the requested context flags - setContextFlag(/*val*/ false, contextFlagsToClear); - const result = func(); - // restore the context flags we just cleared - setContextFlag(/*val*/ true, contextFlagsToClear); - return result; - } + function setYieldContext(val: boolean) { + setContextFlag(val, NodeFlags.YieldContext); + } - // no need to do anything special as we are not in any of the requested contexts - return func(); - } - - function doInsideOfContext(context: NodeFlags, func: () => T): T { - // contextFlagsToSet will contain only the context flags that - // are not currently set that we need to temporarily enable. - // We don't just blindly reset to the previous flags to ensure - // that we do not mutate cached flags for the incremental - // parser (ThisNodeHasError, ThisNodeOrAnySubNodesHasError, and - // HasAggregatedChildData). - const contextFlagsToSet = context & ~contextFlags; - if (contextFlagsToSet) { - // set the requested context flags - setContextFlag(/*val*/ true, contextFlagsToSet); - const result = func(); - // reset the context flags we just set - setContextFlag(/*val*/ false, contextFlagsToSet); - return result; - } + function setDecoratorContext(val: boolean) { + setContextFlag(val, NodeFlags.DecoratorContext); + } - // no need to do anything special as we are already in all of the requested contexts - return func(); - } + function setAwaitContext(val: boolean) { + setContextFlag(val, NodeFlags.AwaitContext); + } - function allowInAnd(func: () => T): T { - return doOutsideOfContext(NodeFlags.DisallowInContext, func); + function doOutsideOfContext(context: NodeFlags, func: () => T): T { + // contextFlagsToClear will contain only the context flags that are + // currently set that we need to temporarily clear + // We don't just blindly reset to the previous flags to ensure + // that we do not mutate cached flags for the incremental + // parser (ThisNodeHasError, ThisNodeOrAnySubNodesHasError, and + // HasAggregatedChildData). + const contextFlagsToClear = context & contextFlags; + if (contextFlagsToClear) { + // clear the requested context flags + setContextFlag(/*val*/ false, contextFlagsToClear); + const result = func(); + // restore the context flags we just cleared + setContextFlag(/*val*/ true, contextFlagsToClear); + return result; } - function disallowInAnd(func: () => T): T { - return doInsideOfContext(NodeFlags.DisallowInContext, func); - } + // no need to do anything special as we are not in any of the requested contexts + return func(); + } - function doInYieldContext(func: () => T): T { - return doInsideOfContext(NodeFlags.YieldContext, func); + function doInsideOfContext(context: NodeFlags, func: () => T): T { + // contextFlagsToSet will contain only the context flags that + // are not currently set that we need to temporarily enable. + // We don't just blindly reset to the previous flags to ensure + // that we do not mutate cached flags for the incremental + // parser (ThisNodeHasError, ThisNodeOrAnySubNodesHasError, and + // HasAggregatedChildData). + const contextFlagsToSet = context & ~contextFlags; + if (contextFlagsToSet) { + // set the requested context flags + setContextFlag(/*val*/ true, contextFlagsToSet); + const result = func(); + // reset the context flags we just set + setContextFlag(/*val*/ false, contextFlagsToSet); + return result; } - function doInDecoratorContext(func: () => T): T { - return doInsideOfContext(NodeFlags.DecoratorContext, func); - } + // no need to do anything special as we are already in all of the requested contexts + return func(); + } - function doInAwaitContext(func: () => T): T { - return doInsideOfContext(NodeFlags.AwaitContext, func); - } + function allowInAnd(func: () => T): T { + return doOutsideOfContext(NodeFlags.DisallowInContext, func); + } - function doOutsideOfAwaitContext(func: () => T): T { - return doOutsideOfContext(NodeFlags.AwaitContext, func); - } + function disallowInAnd(func: () => T): T { + return doInsideOfContext(NodeFlags.DisallowInContext, func); + } - function doInYieldAndAwaitContext(func: () => T): T { - return doInsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext, func); - } + function doInYieldContext(func: () => T): T { + return doInsideOfContext(NodeFlags.YieldContext, func); + } - function doOutsideOfYieldAndAwaitContext(func: () => T): T { - return doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext, func); - } + function doInDecoratorContext(func: () => T): T { + return doInsideOfContext(NodeFlags.DecoratorContext, func); + } - function inContext(flags: NodeFlags) { - return (contextFlags & flags) !== 0; - } + function doInAwaitContext(func: () => T): T { + return doInsideOfContext(NodeFlags.AwaitContext, func); + } - function inYieldContext() { - return inContext(NodeFlags.YieldContext); - } + function doOutsideOfAwaitContext(func: () => T): T { + return doOutsideOfContext(NodeFlags.AwaitContext, func); + } - function inDisallowInContext() { - return inContext(NodeFlags.DisallowInContext); - } + function doInYieldAndAwaitContext(func: () => T): T { + return doInsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext, func); + } - function inDecoratorContext() { - return inContext(NodeFlags.DecoratorContext); - } + function doOutsideOfYieldAndAwaitContext(func: () => T): T { + return doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext, func); + } - function inAwaitContext() { - return inContext(NodeFlags.AwaitContext); - } + function inContext(flags: NodeFlags) { + return (contextFlags & flags) !== 0; + } - function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any): void { - parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), message, arg0); - } + function inYieldContext() { + return inContext(NodeFlags.YieldContext); + } - function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): void { - // Don't report another error if it would just be at the same position as the last error. - const lastError = lastOrUndefined(parseDiagnostics); - if (!lastError || start !== lastError.start) { - parseDiagnostics.push(createDetachedDiagnostic(fileName, start, length, message, arg0)); - } + function inDisallowInContext() { + return inContext(NodeFlags.DisallowInContext); + } - // Mark that we've encountered an error. We'll set an appropriate bit on the next - // node we finish so that it can't be reused incrementally. - parseErrorBeforeNextFinishedNode = true; - } + function inDecoratorContext() { + return inContext(NodeFlags.DecoratorContext); + } - function parseErrorAt(start: number, end: number, message: DiagnosticMessage, arg0?: any): void { - parseErrorAtPosition(start, end - start, message, arg0); - } + function inAwaitContext() { + return inContext(NodeFlags.AwaitContext); + } - function parseErrorAtRange(range: TextRange, message: DiagnosticMessage, arg0?: any): void { - parseErrorAt(range.pos, range.end, message, arg0); - } + function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any): void { + parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), message, arg0); + } - function scanError(message: DiagnosticMessage, length: number): void { - parseErrorAtPosition(scanner.getTextPos(), length, message); + function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): void { + // Don't report another error if it would just be at the same position as the last error. + const lastError = lastOrUndefined(parseDiagnostics); + if (!lastError || start !== lastError.start) { + parseDiagnostics.push(createDetachedDiagnostic(fileName, start, length, message, arg0)); } - function getNodePos(): number { - return scanner.getStartPos(); - } + // Mark that we've encountered an error. We'll set an appropriate bit on the next + // node we finish so that it can't be reused incrementally. + parseErrorBeforeNextFinishedNode = true; + } - function hasPrecedingJSDocComment() { - return scanner.hasPrecedingJSDocComment(); - } + function parseErrorAt(start: number, end: number, message: DiagnosticMessage, arg0?: any): void { + parseErrorAtPosition(start, end - start, message, arg0); + } - // Use this function to access the current token instead of reading the currentToken - // variable. Since function results aren't narrowed in control flow analysis, this ensures - // that the type checker doesn't make wrong assumptions about the type of the current - // token (e.g. a call to nextToken() changes the current token but the checker doesn't - // reason about this side effect). Mainstream VMs inline simple functions like this, so - // there is no performance penalty. - function token(): SyntaxKind { - return currentToken; - } + function parseErrorAtRange(range: TextRange, message: DiagnosticMessage, arg0?: any): void { + parseErrorAt(range.pos, range.end, message, arg0); + } - function nextTokenWithoutCheck() { - return currentToken = scanner.scan(); - } + function scanError(message: DiagnosticMessage, length: number): void { + parseErrorAtPosition(scanner.getTextPos(), length, message); + } - function nextTokenAnd(func: () => T): T { - nextToken(); - return func(); - } + function getNodePos(): number { + return scanner.getStartPos(); + } - function nextToken(): SyntaxKind { - // if the keyword had an escape - if (isKeyword(currentToken) && (scanner.hasUnicodeEscape() || scanner.hasExtendedUnicodeEscape())) { - // issue a parse error for the escape - parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), Diagnostics.Keywords_cannot_contain_escape_characters); - } - return nextTokenWithoutCheck(); - } + function hasPrecedingJSDocComment() { + return scanner.hasPrecedingJSDocComment(); + } - function nextTokenJSDoc(): JSDocSyntaxKind { - return currentToken = scanner.scanJsDocToken(); - } + // Use this function to access the current token instead of reading the currentToken + // variable. Since function results aren't narrowed in control flow analysis, this ensures + // that the type checker doesn't make wrong assumptions about the type of the current + // token (e.g. a call to nextToken() changes the current token but the checker doesn't + // reason about this side effect). Mainstream VMs inline simple functions like this, so + // there is no performance penalty. + function token(): SyntaxKind { + return currentToken; + } - function reScanGreaterToken(): SyntaxKind { - return currentToken = scanner.reScanGreaterToken(); - } + function nextTokenWithoutCheck() { + return currentToken = scanner.scan(); + } - function reScanSlashToken(): SyntaxKind { - return currentToken = scanner.reScanSlashToken(); - } + function nextTokenAnd(func: () => T): T { + nextToken(); + return func(); + } - function reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind { - return currentToken = scanner.reScanTemplateToken(isTaggedTemplate); + function nextToken(): SyntaxKind { + // if the keyword had an escape + if (isKeyword(currentToken) && (scanner.hasUnicodeEscape() || scanner.hasExtendedUnicodeEscape())) { + // issue a parse error for the escape + parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), Diagnostics.Keywords_cannot_contain_escape_characters); } + return nextTokenWithoutCheck(); + } - function reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind { - return currentToken = scanner.reScanTemplateHeadOrNoSubstitutionTemplate(); - } + function nextTokenJSDoc(): JSDocSyntaxKind { + return currentToken = scanner.scanJsDocToken(); + } - function reScanLessThanToken(): SyntaxKind { - return currentToken = scanner.reScanLessThanToken(); - } + function reScanGreaterToken(): SyntaxKind { + return currentToken = scanner.reScanGreaterToken(); + } - function reScanHashToken(): SyntaxKind { - return currentToken = scanner.reScanHashToken(); - } + function reScanSlashToken(): SyntaxKind { + return currentToken = scanner.reScanSlashToken(); + } - function scanJsxIdentifier(): SyntaxKind { - return currentToken = scanner.scanJsxIdentifier(); - } + function reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind { + return currentToken = scanner.reScanTemplateToken(isTaggedTemplate); + } - function scanJsxText(): SyntaxKind { - return currentToken = scanner.scanJsxToken(); - } + function reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind { + return currentToken = scanner.reScanTemplateHeadOrNoSubstitutionTemplate(); + } - function scanJsxAttributeValue(): SyntaxKind { - return currentToken = scanner.scanJsxAttributeValue(); - } + function reScanLessThanToken(): SyntaxKind { + return currentToken = scanner.reScanLessThanToken(); + } - function speculationHelper(callback: () => T, speculationKind: SpeculationKind): T { - // Keep track of the state we'll need to rollback to if lookahead fails (or if the - // caller asked us to always reset our state). - const saveToken = currentToken; - const saveParseDiagnosticsLength = parseDiagnostics.length; - const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; + function reScanHashToken(): SyntaxKind { + return currentToken = scanner.reScanHashToken(); + } - // Note: it is not actually necessary to save/restore the context flags here. That's - // because the saving/restoring of these flags happens naturally through the recursive - // descent nature of our parser. However, we still store this here just so we can - // assert that invariant holds. - const saveContextFlags = contextFlags; - - // If we're only looking ahead, then tell the scanner to only lookahead as well. - // Otherwise, if we're actually speculatively parsing, then tell the scanner to do the - // same. - const result = speculationKind !== SpeculationKind.TryParse - ? scanner.lookAhead(callback) - : scanner.tryScan(callback); - - Debug.assert(saveContextFlags === contextFlags); - - // If our callback returned something 'falsy' or we're just looking ahead, - // then unconditionally restore us to where we were. - if (!result || speculationKind !== SpeculationKind.TryParse) { - currentToken = saveToken; - if (speculationKind !== SpeculationKind.Reparse) { - parseDiagnostics.length = saveParseDiagnosticsLength; - } - parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; - } + function scanJsxIdentifier(): SyntaxKind { + return currentToken = scanner.scanJsxIdentifier(); + } - return result; - } + function scanJsxText(): SyntaxKind { + return currentToken = scanner.scanJsxToken(); + } - /** Invokes the provided callback then unconditionally restores the parser to the state it - * was in immediately prior to invoking the callback. The result of invoking the callback - * is returned from this function. - */ - function lookAhead(callback: () => T): T { - return speculationHelper(callback, SpeculationKind.Lookahead); - } + function scanJsxAttributeValue(): SyntaxKind { + return currentToken = scanner.scanJsxAttributeValue(); + } - /** Invokes the provided callback. If the callback returns something falsy, then it restores - * the parser to the state it was in immediately prior to invoking the callback. If the - * callback returns something truthy, then the parser state is not rolled back. The result - * of invoking the callback is returned from this function. - */ - function tryParse(callback: () => T): T { - return speculationHelper(callback, SpeculationKind.TryParse); + function speculationHelper(callback: () => T, speculationKind: SpeculationKind): T { + // Keep track of the state we'll need to rollback to if lookahead fails (or if the + // caller asked us to always reset our state). + const saveToken = currentToken; + const saveParseDiagnosticsLength = parseDiagnostics.length; + const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; + + // Note: it is not actually necessary to save/restore the context flags here. That's + // because the saving/restoring of these flags happens naturally through the recursive + // descent nature of our parser. However, we still store this here just so we can + // assert that invariant holds. + const saveContextFlags = contextFlags; + + // If we're only looking ahead, then tell the scanner to only lookahead as well. + // Otherwise, if we're actually speculatively parsing, then tell the scanner to do the + // same. + const result = speculationKind !== SpeculationKind.TryParse + ? scanner.lookAhead(callback) + : scanner.tryScan(callback); + + Debug.assert(saveContextFlags === contextFlags); + + // If our callback returned something 'falsy' or we're just looking ahead, + // then unconditionally restore us to where we were. + if (!result || speculationKind !== SpeculationKind.TryParse) { + currentToken = saveToken; + if (speculationKind !== SpeculationKind.Reparse) { + parseDiagnostics.length = saveParseDiagnosticsLength; + } + parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; } - function isBindingIdentifier(): boolean { - if (token() === SyntaxKind.Identifier) { - return true; - } + return result; + } - // `let await`/`let yield` in [Yield] or [Await] are allowed here and disallowed in the binder. - return token() > SyntaxKind.LastReservedWord; - } + /** Invokes the provided callback then unconditionally restores the parser to the state it + * was in immediately prior to invoking the callback. The result of invoking the callback + * is returned from this function. + */ + function lookAhead(callback: () => T): T { + return speculationHelper(callback, SpeculationKind.Lookahead); + } - // Ignore strict mode flag because we will report an error in type checker instead. - function isIdentifier(): boolean { - if (token() === SyntaxKind.Identifier) { - return true; - } + /** Invokes the provided callback. If the callback returns something falsy, then it restores + * the parser to the state it was in immediately prior to invoking the callback. If the + * callback returns something truthy, then the parser state is not rolled back. The result + * of invoking the callback is returned from this function. + */ + function tryParse(callback: () => T): T { + return speculationHelper(callback, SpeculationKind.TryParse); + } - // If we have a 'yield' keyword, and we're in the [yield] context, then 'yield' is - // considered a keyword and is not an identifier. - if (token() === SyntaxKind.YieldKeyword && inYieldContext()) { - return false; - } + function isBindingIdentifier(): boolean { + if (token() === SyntaxKind.Identifier) { + return true; + } - // If we have a 'await' keyword, and we're in the [Await] context, then 'await' is - // considered a keyword and is not an identifier. - if (token() === SyntaxKind.AwaitKeyword && inAwaitContext()) { - return false; - } + // `let await`/`let yield` in [Yield] or [Await] are allowed here and disallowed in the binder. + return token() > SyntaxKind.LastReservedWord; + } - return token() > SyntaxKind.LastReservedWord; + // Ignore strict mode flag because we will report an error in type checker instead. + function isIdentifier(): boolean { + if (token() === SyntaxKind.Identifier) { + return true; } - function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, shouldAdvance = true): boolean { - if (token() === kind) { - if (shouldAdvance) { - nextToken(); - } - return true; - } + // If we have a 'yield' keyword, and we're in the [yield] context, then 'yield' is + // considered a keyword and is not an identifier. + if (token() === SyntaxKind.YieldKeyword && inYieldContext()) { + return false; + } - // Report specific message if provided with one. Otherwise, report generic fallback message. - if (diagnosticMessage) { - parseErrorAtCurrentToken(diagnosticMessage); - } - else { - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); - } + // If we have a 'await' keyword, and we're in the [Await] context, then 'await' is + // considered a keyword and is not an identifier. + if (token() === SyntaxKind.AwaitKeyword && inAwaitContext()) { return false; } - const viableKeywordSuggestions = Object.keys(textToKeywordObj).filter(keyword => keyword.length > 2); + return token() > SyntaxKind.LastReservedWord; + } - /** - * Provides a better error message than the generic "';' expected" if possible for - * known common variants of a missing semicolon, such as from a mispelled names. - * - * @param node Node preceding the expected semicolon location. - */ - function parseErrorForMissingSemicolonAfter(node: Expression | PropertyName): void { - // Tagged template literals are sometimes used in places where only simple strings are allowed, i.e.: - // module `M1` { - // ^^^^^^^^^^^ This block is parsed as a template literal like module`M1`. - if (isTaggedTemplateExpression(node)) { - parseErrorAt(skipTrivia(sourceText, node.template.pos), node.template.end, Diagnostics.Module_declaration_names_may_only_use_or_quoted_strings); - return; + function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, shouldAdvance = true): boolean { + if (token() === kind) { + if (shouldAdvance) { + nextToken(); } + return true; + } - // Otherwise, if this isn't a well-known keyword-like identifier, give the generic fallback message. - const expressionText = ts.isIdentifier(node) ? idText(node) : undefined; - if (!expressionText || !isIdentifierText(expressionText, languageVersion)) { - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); - return; - } + // Report specific message if provided with one. Otherwise, report generic fallback message. + if (diagnosticMessage) { + parseErrorAtCurrentToken(diagnosticMessage); + } + else { + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); + } + return false; + } - const pos = skipTrivia(sourceText, node.pos); + const viableKeywordSuggestions = Object.keys(textToKeywordObj).filter(keyword => keyword.length > 2); - // Some known keywords are likely signs of syntax being used improperly. - switch (expressionText) { - case "const": - case "let": - case "var": - parseErrorAt(pos, node.end, Diagnostics.Variable_declaration_not_allowed_at_this_location); - return; + /** + * Provides a better error message than the generic "';' expected" if possible for + * known common variants of a missing semicolon, such as from a mispelled names. + * + * @param node Node preceding the expected semicolon location. + */ + function parseErrorForMissingSemicolonAfter(node: Expression | PropertyName): void { + // Tagged template literals are sometimes used in places where only simple strings are allowed, i.e.: + // module `M1` { + // ^^^^^^^^^^^ This block is parsed as a template literal like module`M1`. + if (isTaggedTemplateExpression(node)) { + parseErrorAt(skipTrivia(sourceText, node.template.pos), node.template.end, Diagnostics.Module_declaration_names_may_only_use_or_quoted_strings); + return; + } - case "declare": - // If a declared node failed to parse, it would have emitted a diagnostic already. - return; + // Otherwise, if this isn't a well-known keyword-like identifier, give the generic fallback message. + const expressionText = ts.isIdentifier(node) ? idText(node) : undefined; + if (!expressionText || !isIdentifierText(expressionText, languageVersion)) { + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); + return; + } - case "interface": - parseErrorForInvalidName(Diagnostics.Interface_name_cannot_be_0, Diagnostics.Interface_must_be_given_a_name, SyntaxKind.OpenBraceToken); - return; + const pos = skipTrivia(sourceText, node.pos); - case "is": - parseErrorAt(pos, scanner.getTextPos(), Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); - return; + // Some known keywords are likely signs of syntax being used improperly. + switch (expressionText) { + case "const": + case "let": + case "var": + parseErrorAt(pos, node.end, Diagnostics.Variable_declaration_not_allowed_at_this_location); + return; - case "module": - case "namespace": - parseErrorForInvalidName(Diagnostics.Namespace_name_cannot_be_0, Diagnostics.Namespace_must_be_given_a_name, SyntaxKind.OpenBraceToken); - return; + case "declare": + // If a declared node failed to parse, it would have emitted a diagnostic already. + return; - case "type": - parseErrorForInvalidName(Diagnostics.Type_alias_name_cannot_be_0, Diagnostics.Type_alias_must_be_given_a_name, SyntaxKind.EqualsToken); - return; - } + case "interface": + parseErrorForInvalidName(Diagnostics.Interface_name_cannot_be_0, Diagnostics.Interface_must_be_given_a_name, SyntaxKind.OpenBraceToken); + return; - // The user alternatively might have misspelled or forgotten to add a space after a common keyword. - const suggestion = getSpellingSuggestion(expressionText, viableKeywordSuggestions, n => n) ?? getSpaceSuggestion(expressionText); - if (suggestion) { - parseErrorAt(pos, node.end, Diagnostics.Unknown_keyword_or_identifier_Did_you_mean_0, suggestion); + case "is": + parseErrorAt(pos, scanner.getTextPos(), Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); return; - } - // Unknown tokens are handled with their own errors in the scanner - if (token() === SyntaxKind.Unknown) { + case "module": + case "namespace": + parseErrorForInvalidName(Diagnostics.Namespace_name_cannot_be_0, Diagnostics.Namespace_must_be_given_a_name, SyntaxKind.OpenBraceToken); return; - } - // Otherwise, we know this some kind of unknown word, not just a missing expected semicolon. - parseErrorAt(pos, node.end, Diagnostics.Unexpected_keyword_or_identifier); + case "type": + parseErrorForInvalidName(Diagnostics.Type_alias_name_cannot_be_0, Diagnostics.Type_alias_must_be_given_a_name, SyntaxKind.EqualsToken); + return; } - /** - * Reports a diagnostic error for the current token being an invalid name. - * - * @param blankDiagnostic Diagnostic to report for the case of the name being blank (matched tokenIfBlankName). - * @param nameDiagnostic Diagnostic to report for all other cases. - * @param tokenIfBlankName Current token if the name was invalid for being blank (not provided / skipped). - */ - function parseErrorForInvalidName(nameDiagnostic: DiagnosticMessage, blankDiagnostic: DiagnosticMessage, tokenIfBlankName: SyntaxKind) { - if (token() === tokenIfBlankName) { - parseErrorAtCurrentToken(blankDiagnostic); - } - else { - parseErrorAtCurrentToken(nameDiagnostic, scanner.getTokenValue()); - } + // The user alternatively might have misspelled or forgotten to add a space after a common keyword. + const suggestion = getSpellingSuggestion(expressionText, viableKeywordSuggestions, n => n) ?? getSpaceSuggestion(expressionText); + if (suggestion) { + parseErrorAt(pos, node.end, Diagnostics.Unknown_keyword_or_identifier_Did_you_mean_0, suggestion); + return; } - function getSpaceSuggestion(expressionText: string) { - for (const keyword of viableKeywordSuggestions) { - if (expressionText.length > keyword.length + 2 && startsWith(expressionText, keyword)) { - return `${keyword} ${expressionText.slice(keyword.length)}`; - } - } - - return undefined; + // Unknown tokens are handled with their own errors in the scanner + if (token() === SyntaxKind.Unknown) { + return; } - function parseSemicolonAfterPropertyName(name: PropertyName, type: TypeNode | undefined, initializer: Expression | undefined) { - if (token() === SyntaxKind.AtToken && !scanner.hasPrecedingLineBreak()) { - parseErrorAtCurrentToken(Diagnostics.Decorators_must_precede_the_name_and_all_keywords_of_property_declarations); - return; - } - - if (token() === SyntaxKind.OpenParenToken) { - parseErrorAtCurrentToken(Diagnostics.Cannot_start_a_function_call_in_a_type_annotation); - nextToken(); - return; - } + // Otherwise, we know this some kind of unknown word, not just a missing expected semicolon. + parseErrorAt(pos, node.end, Diagnostics.Unexpected_keyword_or_identifier); + } - if (type && !canParseSemicolon()) { - if (initializer) { - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); - } - else { - parseErrorAtCurrentToken(Diagnostics.Expected_for_property_initializer); - } - return; - } + /** + * Reports a diagnostic error for the current token being an invalid name. + * + * @param blankDiagnostic Diagnostic to report for the case of the name being blank (matched tokenIfBlankName). + * @param nameDiagnostic Diagnostic to report for all other cases. + * @param tokenIfBlankName Current token if the name was invalid for being blank (not provided / skipped). + */ + function parseErrorForInvalidName(nameDiagnostic: DiagnosticMessage, blankDiagnostic: DiagnosticMessage, tokenIfBlankName: SyntaxKind) { + if (token() === tokenIfBlankName) { + parseErrorAtCurrentToken(blankDiagnostic); + } + else { + parseErrorAtCurrentToken(nameDiagnostic, scanner.getTokenValue()); + } + } - if (tryParseSemicolon()) { - return; + function getSpaceSuggestion(expressionText: string) { + for (const keyword of viableKeywordSuggestions) { + if (expressionText.length > keyword.length + 2 && startsWith(expressionText, keyword)) { + return `${keyword} ${expressionText.slice(keyword.length)}`; } + } - // If an initializer was parsed but there is still an error in finding the next semicolon, - // we generally know there was an error already reported in the initializer... - // class Example { a = new Map([), ) } - // ~ - if (initializer) { - // ...unless we've found the start of a block after a property declaration, in which - // case we can know that regardless of the initializer we should complain on the block. - // class Example { a = 0 {} } - // ~ - if (token() === SyntaxKind.OpenBraceToken) { - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); - } - - return; - } + return undefined; + } - parseErrorForMissingSemicolonAfter(name); + function parseSemicolonAfterPropertyName(name: PropertyName, type: TypeNode | undefined, initializer: Expression | undefined) { + if (token() === SyntaxKind.AtToken && !scanner.hasPrecedingLineBreak()) { + parseErrorAtCurrentToken(Diagnostics.Decorators_must_precede_the_name_and_all_keywords_of_property_declarations); + return; } - function parseExpectedJSDoc(kind: JSDocSyntaxKind) { - if (token() === kind) { - nextTokenJSDoc(); - return true; - } - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); - return false; + if (token() === SyntaxKind.OpenParenToken) { + parseErrorAtCurrentToken(Diagnostics.Cannot_start_a_function_call_in_a_type_annotation); + nextToken(); + return; } - function parseOptional(t: SyntaxKind): boolean { - if (token() === t) { - nextToken(); - return true; + if (type && !canParseSemicolon()) { + if (initializer) { + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); } - return false; + else { + parseErrorAtCurrentToken(Diagnostics.Expected_for_property_initializer); + } + return; } - function parseOptionalToken(t: TKind): Token; - function parseOptionalToken(t: SyntaxKind): Node | undefined { - if (token() === t) { - return parseTokenNode(); - } - return undefined; + if (tryParseSemicolon()) { + return; } - function parseOptionalTokenJSDoc(t: TKind): Token; - function parseOptionalTokenJSDoc(t: JSDocSyntaxKind): Node | undefined { - if (token() === t) { - return parseTokenNodeJSDoc(); + // If an initializer was parsed but there is still an error in finding the next semicolon, + // we generally know there was an error already reported in the initializer... + // class Example { a = new Map([), ) } + // ~ + if (initializer) { + // ...unless we've found the start of a block after a property declaration, in which + // case we can know that regardless of the initializer we should complain on the block. + // class Example { a = 0 {} } + // ~ + if (token() === SyntaxKind.OpenBraceToken) { + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); } - return undefined; - } - function parseExpectedToken(t: TKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): Token; - function parseExpectedToken(t: SyntaxKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): Node { - return parseOptionalToken(t) || - createMissingNode(t, /*reportAtCurrentPosition*/ false, diagnosticMessage || Diagnostics._0_expected, arg0 || tokenToString(t)); + return; } - function parseExpectedTokenJSDoc(t: TKind): Token; - function parseExpectedTokenJSDoc(t: JSDocSyntaxKind): Node { - return parseOptionalTokenJSDoc(t) || - createMissingNode(t, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(t)); + parseErrorForMissingSemicolonAfter(name); + } + + function parseExpectedJSDoc(kind: JSDocSyntaxKind) { + if (token() === kind) { + nextTokenJSDoc(); + return true; } + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); + return false; + } - function parseTokenNode(): T { - const pos = getNodePos(); - const kind = token(); + function parseOptional(t: SyntaxKind): boolean { + if (token() === t) { nextToken(); - return finishNode(factory.createToken(kind), pos) as T; + return true; } + return false; + } - function parseTokenNodeJSDoc(): T { - const pos = getNodePos(); - const kind = token(); - nextTokenJSDoc(); - return finishNode(factory.createToken(kind), pos) as T; + function parseOptionalToken(t: TKind): Token; + function parseOptionalToken(t: SyntaxKind): Node | undefined { + if (token() === t) { + return parseTokenNode(); } + return undefined; + } - function canParseSemicolon() { - // If there's a real semicolon, then we can always parse it out. - if (token() === SyntaxKind.SemicolonToken) { - return true; - } - - // We can parse out an optional semicolon in ASI cases in the following cases. - return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.EndOfFileToken || scanner.hasPrecedingLineBreak(); + function parseOptionalTokenJSDoc(t: TKind): Token; + function parseOptionalTokenJSDoc(t: JSDocSyntaxKind): Node | undefined { + if (token() === t) { + return parseTokenNodeJSDoc(); } + return undefined; + } - function tryParseSemicolon() { - if (!canParseSemicolon()) { - return false; - } + function parseExpectedToken(t: TKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): Token; + function parseExpectedToken(t: SyntaxKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): Node { + return parseOptionalToken(t) || + createMissingNode(t, /*reportAtCurrentPosition*/ false, diagnosticMessage || Diagnostics._0_expected, arg0 || tokenToString(t)); + } - if (token() === SyntaxKind.SemicolonToken) { - // consume the semicolon if it was explicitly provided. - nextToken(); - } + function parseExpectedTokenJSDoc(t: TKind): Token; + function parseExpectedTokenJSDoc(t: JSDocSyntaxKind): Node { + return parseOptionalTokenJSDoc(t) || + createMissingNode(t, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(t)); + } + function parseTokenNode(): T { + const pos = getNodePos(); + const kind = token(); + nextToken(); + return finishNode(factory.createToken(kind), pos) as T; + } + + function parseTokenNodeJSDoc(): T { + const pos = getNodePos(); + const kind = token(); + nextTokenJSDoc(); + return finishNode(factory.createToken(kind), pos) as T; + } + + function canParseSemicolon() { + // If there's a real semicolon, then we can always parse it out. + if (token() === SyntaxKind.SemicolonToken) { return true; } - function parseSemicolon(): boolean { - return tryParseSemicolon() || parseExpected(SyntaxKind.SemicolonToken); - } + // We can parse out an optional semicolon in ASI cases in the following cases. + return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.EndOfFileToken || scanner.hasPrecedingLineBreak(); + } - function createNodeArray(elements: T[], pos: number, end?: number, hasTrailingComma?: boolean): NodeArray { - const array = factory.createNodeArray(elements, hasTrailingComma); - setTextRangePosEnd(array, pos, end ?? scanner.getStartPos()); - return array; + function tryParseSemicolon() { + if (!canParseSemicolon()) { + return false; } - function finishNode(node: T, pos: number, end?: number): T { - setTextRangePosEnd(node, pos, end ?? scanner.getStartPos()); - if (contextFlags) { - (node as Mutable).flags |= contextFlags; - } + if (token() === SyntaxKind.SemicolonToken) { + // consume the semicolon if it was explicitly provided. + nextToken(); + } - // Keep track on the node if we encountered an error while parsing it. If we did, then - // we cannot reuse the node incrementally. Once we've marked this node, clear out the - // flag so that we don't mark any subsequent nodes. - if (parseErrorBeforeNextFinishedNode) { - parseErrorBeforeNextFinishedNode = false; - (node as Mutable).flags |= NodeFlags.ThisNodeHasError; - } + return true; + } - return node; - } + function parseSemicolon(): boolean { + return tryParseSemicolon() || parseExpected(SyntaxKind.SemicolonToken); + } - function createMissingNode(kind: T["kind"], reportAtCurrentPosition: false, diagnosticMessage?: DiagnosticMessage, arg0?: any): T; - function createMissingNode(kind: T["kind"], reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): T; - function createMissingNode(kind: T["kind"], reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): T { - if (reportAtCurrentPosition) { - parseErrorAtPosition(scanner.getStartPos(), 0, diagnosticMessage, arg0); - } - else if (diagnosticMessage) { - parseErrorAtCurrentToken(diagnosticMessage, arg0); - } + function createNodeArray(elements: T[], pos: number, end?: number, hasTrailingComma?: boolean): NodeArray { + const array = factory.createNodeArray(elements, hasTrailingComma); + setTextRangePosEnd(array, pos, end ?? scanner.getStartPos()); + return array; + } - const pos = getNodePos(); - const result = - kind === SyntaxKind.Identifier ? factory.createIdentifier("", /*typeArguments*/ undefined, /*originalKeywordKind*/ undefined) : - isTemplateLiteralKind(kind) ? factory.createTemplateLiteralLikeNode(kind, "", "", /*templateFlags*/ undefined) : - kind === SyntaxKind.NumericLiteral ? factory.createNumericLiteral("", /*numericLiteralFlags*/ undefined) : - kind === SyntaxKind.StringLiteral ? factory.createStringLiteral("", /*isSingleQuote*/ undefined) : - kind === SyntaxKind.MissingDeclaration ? factory.createMissingDeclaration() : - factory.createToken(kind); - return finishNode(result, pos) as T; + function finishNode(node: T, pos: number, end?: number): T { + setTextRangePosEnd(node, pos, end ?? scanner.getStartPos()); + if (contextFlags) { + (node as Mutable).flags |= contextFlags; } - function internIdentifier(text: string): string { - let identifier = identifiers.get(text); - if (identifier === undefined) { - identifiers.set(text, identifier = text); - } - return identifier; + // Keep track on the node if we encountered an error while parsing it. If we did, then + // we cannot reuse the node incrementally. Once we've marked this node, clear out the + // flag so that we don't mark any subsequent nodes. + if (parseErrorBeforeNextFinishedNode) { + parseErrorBeforeNextFinishedNode = false; + (node as Mutable).flags |= NodeFlags.ThisNodeHasError; } - // An identifier that starts with two underscores has an extra underscore character prepended to it to avoid issues - // with magic property names like '__proto__'. The 'identifiers' object is used to share a single string instance for - // each identifier in order to reduce memory consumption. - function createIdentifier(isIdentifier: boolean, diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier { - if (isIdentifier) { - identifierCount++; - const pos = getNodePos(); - // Store original token kind if it is not just an Identifier so we can report appropriate error later in type checker - const originalKeywordKind = token(); - const text = internIdentifier(scanner.getTokenValue()); - nextTokenWithoutCheck(); - return finishNode(factory.createIdentifier(text, /*typeArguments*/ undefined, originalKeywordKind), pos); - } + return node; + } - if (token() === SyntaxKind.PrivateIdentifier) { - parseErrorAtCurrentToken(privateIdentifierDiagnosticMessage || Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); - return createIdentifier(/*isIdentifier*/ true); - } + function createMissingNode(kind: T["kind"], reportAtCurrentPosition: false, diagnosticMessage?: DiagnosticMessage, arg0?: any): T; + function createMissingNode(kind: T["kind"], reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): T; + function createMissingNode(kind: T["kind"], reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): T { + if (reportAtCurrentPosition) { + parseErrorAtPosition(scanner.getStartPos(), 0, diagnosticMessage, arg0); + } + else if (diagnosticMessage) { + parseErrorAtCurrentToken(diagnosticMessage, arg0); + } + + const pos = getNodePos(); + const result = + kind === SyntaxKind.Identifier ? factory.createIdentifier("", /*typeArguments*/ undefined, /*originalKeywordKind*/ undefined) : + isTemplateLiteralKind(kind) ? factory.createTemplateLiteralLikeNode(kind, "", "", /*templateFlags*/ undefined) : + kind === SyntaxKind.NumericLiteral ? factory.createNumericLiteral("", /*numericLiteralFlags*/ undefined) : + kind === SyntaxKind.StringLiteral ? factory.createStringLiteral("", /*isSingleQuote*/ undefined) : + kind === SyntaxKind.MissingDeclaration ? factory.createMissingDeclaration() : + factory.createToken(kind); + return finishNode(result, pos) as T; + } - if (token() === SyntaxKind.Unknown && scanner.tryScan(() => scanner.reScanInvalidIdentifier() === SyntaxKind.Identifier)) { - // Scanner has already recorded an 'Invalid character' error, so no need to add another from the parser. - return createIdentifier(/*isIdentifier*/ true); - } + function internIdentifier(text: string): string { + let identifier = identifiers.get(text); + if (identifier === undefined) { + identifiers.set(text, identifier = text); + } + return identifier; + } + // An identifier that starts with two underscores has an extra underscore character prepended to it to avoid issues + // with magic property names like '__proto__'. The 'identifiers' object is used to share a single string instance for + // each identifier in order to reduce memory consumption. + function createIdentifier(isIdentifier: boolean, diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier { + if (isIdentifier) { identifierCount++; - // Only for end of file because the error gets reported incorrectly on embedded script tags. - const reportAtCurrentPosition = token() === SyntaxKind.EndOfFileToken; - - const isReservedWord = scanner.isReservedWord(); - const msgArg = scanner.getTokenText(); - - const defaultMessage = isReservedWord ? - Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here : - Diagnostics.Identifier_expected; - - return createMissingNode(SyntaxKind.Identifier, reportAtCurrentPosition, diagnosticMessage || defaultMessage, msgArg); + const pos = getNodePos(); + // Store original token kind if it is not just an Identifier so we can report appropriate error later in type checker + const originalKeywordKind = token(); + const text = internIdentifier(scanner.getTokenValue()); + nextTokenWithoutCheck(); + return finishNode(factory.createIdentifier(text, /*typeArguments*/ undefined, originalKeywordKind), pos); } - function parseBindingIdentifier(privateIdentifierDiagnosticMessage?: DiagnosticMessage) { - return createIdentifier(isBindingIdentifier(), /*diagnosticMessage*/ undefined, privateIdentifierDiagnosticMessage); + if (token() === SyntaxKind.PrivateIdentifier) { + parseErrorAtCurrentToken(privateIdentifierDiagnosticMessage || Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + return createIdentifier(/*isIdentifier*/ true); } - function parseIdentifier(diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier { - return createIdentifier(isIdentifier(), diagnosticMessage, privateIdentifierDiagnosticMessage); + if (token() === SyntaxKind.Unknown && scanner.tryScan(() => scanner.reScanInvalidIdentifier() === SyntaxKind.Identifier)) { + // Scanner has already recorded an 'Invalid character' error, so no need to add another from the parser. + return createIdentifier(/*isIdentifier*/ true); } - function parseIdentifierName(diagnosticMessage?: DiagnosticMessage): Identifier { - return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage); - } + identifierCount++; + // Only for end of file because the error gets reported incorrectly on embedded script tags. + const reportAtCurrentPosition = token() === SyntaxKind.EndOfFileToken; - function isLiteralPropertyName(): boolean { - return tokenIsIdentifierOrKeyword(token()) || - token() === SyntaxKind.StringLiteral || - token() === SyntaxKind.NumericLiteral; - } + const isReservedWord = scanner.isReservedWord(); + const msgArg = scanner.getTokenText(); - function isAssertionKey(): boolean { - return tokenIsIdentifierOrKeyword(token()) || - token() === SyntaxKind.StringLiteral; - } + const defaultMessage = isReservedWord ? + Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here : + Diagnostics.Identifier_expected; - function parsePropertyNameWorker(allowComputedPropertyNames: boolean): PropertyName { - if (token() === SyntaxKind.StringLiteral || token() === SyntaxKind.NumericLiteral) { - const node = parseLiteralNode() as StringLiteral | NumericLiteral; - node.text = internIdentifier(node.text); - return node; - } - if (allowComputedPropertyNames && token() === SyntaxKind.OpenBracketToken) { - return parseComputedPropertyName(); - } - if (token() === SyntaxKind.PrivateIdentifier) { - return parsePrivateIdentifier(); - } - return parseIdentifierName(); - } + return createMissingNode(SyntaxKind.Identifier, reportAtCurrentPosition, diagnosticMessage || defaultMessage, msgArg); + } - function parsePropertyName(): PropertyName { - return parsePropertyNameWorker(/*allowComputedPropertyNames*/ true); - } + function parseBindingIdentifier(privateIdentifierDiagnosticMessage?: DiagnosticMessage) { + return createIdentifier(isBindingIdentifier(), /*diagnosticMessage*/ undefined, privateIdentifierDiagnosticMessage); + } - function parseComputedPropertyName(): ComputedPropertyName { - // PropertyName [Yield]: - // LiteralPropertyName - // ComputedPropertyName[?Yield] - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBracketToken); - // We parse any expression (including a comma expression). But the grammar - // says that only an assignment expression is allowed, so the grammar checker - // will error if it sees a comma expression. - const expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseBracketToken); - return finishNode(factory.createComputedPropertyName(expression), pos); - } + function parseIdentifier(diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier { + return createIdentifier(isIdentifier(), diagnosticMessage, privateIdentifierDiagnosticMessage); + } - function internPrivateIdentifier(text: string): string { - let privateIdentifier = privateIdentifiers.get(text); - if (privateIdentifier === undefined) { - privateIdentifiers.set(text, privateIdentifier = text); - } - return privateIdentifier; - } + function parseIdentifierName(diagnosticMessage?: DiagnosticMessage): Identifier { + return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage); + } - function parsePrivateIdentifier(): PrivateIdentifier { - const pos = getNodePos(); - const node = factory.createPrivateIdentifier(internPrivateIdentifier(scanner.getTokenText())); - nextToken(); - return finishNode(node, pos); - } + function isLiteralPropertyName(): boolean { + return tokenIsIdentifierOrKeyword(token()) || + token() === SyntaxKind.StringLiteral || + token() === SyntaxKind.NumericLiteral; + } - function parseContextualModifier(t: SyntaxKind): boolean { - return token() === t && tryParse(nextTokenCanFollowModifier); - } + function isAssertionKey(): boolean { + return tokenIsIdentifierOrKeyword(token()) || + token() === SyntaxKind.StringLiteral; + } - function nextTokenIsOnSameLineAndCanFollowModifier() { - nextToken(); - if (scanner.hasPrecedingLineBreak()) { - return false; - } - return canFollowModifier(); + function parsePropertyNameWorker(allowComputedPropertyNames: boolean): PropertyName { + if (token() === SyntaxKind.StringLiteral || token() === SyntaxKind.NumericLiteral) { + const node = parseLiteralNode() as StringLiteral | NumericLiteral; + node.text = internIdentifier(node.text); + return node; } - - function nextTokenCanFollowModifier() { - switch (token()) { - case SyntaxKind.ConstKeyword: - // 'const' is only a modifier if followed by 'enum'. - return nextToken() === SyntaxKind.EnumKeyword; - case SyntaxKind.ExportKeyword: - nextToken(); - if (token() === SyntaxKind.DefaultKeyword) { - return lookAhead(nextTokenCanFollowDefaultKeyword); - } - if (token() === SyntaxKind.TypeKeyword) { - return lookAhead(nextTokenCanFollowExportModifier); - } - return canFollowExportModifier(); - case SyntaxKind.DefaultKeyword: - return nextTokenCanFollowDefaultKeyword(); - case SyntaxKind.StaticKeyword: - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - nextToken(); - return canFollowModifier(); - default: - return nextTokenIsOnSameLineAndCanFollowModifier(); - } + if (allowComputedPropertyNames && token() === SyntaxKind.OpenBracketToken) { + return parseComputedPropertyName(); } - - function canFollowExportModifier(): boolean { - return token() !== SyntaxKind.AsteriskToken - && token() !== SyntaxKind.AsKeyword - && token() !== SyntaxKind.OpenBraceToken - && canFollowModifier(); + if (token() === SyntaxKind.PrivateIdentifier) { + return parsePrivateIdentifier(); } + return parseIdentifierName(); + } - function nextTokenCanFollowExportModifier(): boolean { - nextToken(); - return canFollowExportModifier(); - } + function parsePropertyName(): PropertyName { + return parsePropertyNameWorker(/*allowComputedPropertyNames*/ true); + } - function parseAnyContextualModifier(): boolean { - return isModifierKind(token()) && tryParse(nextTokenCanFollowModifier); - } + function parseComputedPropertyName(): ComputedPropertyName { + // PropertyName [Yield]: + // LiteralPropertyName + // ComputedPropertyName[?Yield] + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBracketToken); + // We parse any expression (including a comma expression). But the grammar + // says that only an assignment expression is allowed, so the grammar checker + // will error if it sees a comma expression. + const expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseBracketToken); + return finishNode(factory.createComputedPropertyName(expression), pos); + } - function canFollowModifier(): boolean { - return token() === SyntaxKind.OpenBracketToken - || token() === SyntaxKind.OpenBraceToken - || token() === SyntaxKind.AsteriskToken - || token() === SyntaxKind.DotDotDotToken - || isLiteralPropertyName(); + function internPrivateIdentifier(text: string): string { + let privateIdentifier = privateIdentifiers.get(text); + if (privateIdentifier === undefined) { + privateIdentifiers.set(text, privateIdentifier = text); } + return privateIdentifier; + } - function nextTokenCanFollowDefaultKeyword(): boolean { - nextToken(); - return token() === SyntaxKind.ClassKeyword || token() === SyntaxKind.FunctionKeyword || - token() === SyntaxKind.InterfaceKeyword || - (token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsClassKeywordOnSameLine)) || - (token() === SyntaxKind.AsyncKeyword && lookAhead(nextTokenIsFunctionKeywordOnSameLine)); + function parsePrivateIdentifier(): PrivateIdentifier { + const pos = getNodePos(); + const node = factory.createPrivateIdentifier(internPrivateIdentifier(scanner.getTokenText())); + nextToken(); + return finishNode(node, pos); + } + + function parseContextualModifier(t: SyntaxKind): boolean { + return token() === t && tryParse(nextTokenCanFollowModifier); + } + + function nextTokenIsOnSameLineAndCanFollowModifier() { + nextToken(); + if (scanner.hasPrecedingLineBreak()) { + return false; } + return canFollowModifier(); + } - // True if positioned at the start of a list element - function isListElement(parsingContext: ParsingContext, inErrorRecovery: boolean): boolean { - const node = currentNode(parsingContext); - if (node) { - return true; - } + function nextTokenCanFollowModifier() { + switch (token()) { + case SyntaxKind.ConstKeyword: + // 'const' is only a modifier if followed by 'enum'. + return nextToken() === SyntaxKind.EnumKeyword; + case SyntaxKind.ExportKeyword: + nextToken(); + if (token() === SyntaxKind.DefaultKeyword) { + return lookAhead(nextTokenCanFollowDefaultKeyword); + } + if (token() === SyntaxKind.TypeKeyword) { + return lookAhead(nextTokenCanFollowExportModifier); + } + return canFollowExportModifier(); + case SyntaxKind.DefaultKeyword: + return nextTokenCanFollowDefaultKeyword(); + case SyntaxKind.StaticKeyword: + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + nextToken(); + return canFollowModifier(); + default: + return nextTokenIsOnSameLineAndCanFollowModifier(); + } + } - switch (parsingContext) { - case ParsingContext.SourceElements: - case ParsingContext.BlockStatements: - case ParsingContext.SwitchClauseStatements: - // If we're in error recovery, then we don't want to treat ';' as an empty statement. - // The problem is that ';' can show up in far too many contexts, and if we see one - // and assume it's a statement, then we may bail out inappropriately from whatever - // we're parsing. For example, if we have a semicolon in the middle of a class, then - // we really don't want to assume the class is over and we're on a statement in the - // outer module. We just want to consume and move on. - return !(token() === SyntaxKind.SemicolonToken && inErrorRecovery) && isStartOfStatement(); - case ParsingContext.SwitchClauses: - return token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; - case ParsingContext.TypeMembers: - return lookAhead(isTypeMemberStart); - case ParsingContext.ClassMembers: - // We allow semicolons as class elements (as specified by ES6) as long as we're - // not in error recovery. If we're in error recovery, we don't want an errant - // semicolon to be treated as a class member (since they're almost always used - // for statements. - return lookAhead(isClassMemberStart) || (token() === SyntaxKind.SemicolonToken && !inErrorRecovery); - case ParsingContext.EnumMembers: - // Include open bracket computed properties. This technically also lets in indexers, - // which would be a candidate for improved error reporting. - return token() === SyntaxKind.OpenBracketToken || isLiteralPropertyName(); - case ParsingContext.ObjectLiteralMembers: - switch (token()) { - case SyntaxKind.OpenBracketToken: - case SyntaxKind.AsteriskToken: - case SyntaxKind.DotDotDotToken: - case SyntaxKind.DotToken: // Not an object literal member, but don't want to close the object (see `tests/cases/fourslash/completionsDotInObjectLiteral.ts`) - return true; - default: - return isLiteralPropertyName(); - } - case ParsingContext.RestProperties: - return isLiteralPropertyName(); - case ParsingContext.ObjectBindingElements: - return token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.DotDotDotToken || isLiteralPropertyName(); - case ParsingContext.AssertEntries: - return isAssertionKey(); - case ParsingContext.HeritageClauseElement: - // If we see `{ ... }` then only consume it as an expression if it is followed by `,` or `{` - // That way we won't consume the body of a class in its heritage clause. - if (token() === SyntaxKind.OpenBraceToken) { - return lookAhead(isValidHeritageClauseObjectLiteral); - } + function canFollowExportModifier(): boolean { + return token() !== SyntaxKind.AsteriskToken + && token() !== SyntaxKind.AsKeyword + && token() !== SyntaxKind.OpenBraceToken + && canFollowModifier(); + } - if (!inErrorRecovery) { - return isStartOfLeftHandSideExpression() && !isHeritageClauseExtendsOrImplementsKeyword(); - } - else { - // If we're in error recovery we tighten up what we're willing to match. - // That way we don't treat something like "this" as a valid heritage clause - // element during recovery. - return isIdentifier() && !isHeritageClauseExtendsOrImplementsKeyword(); - } - case ParsingContext.VariableDeclarations: - return isBindingIdentifierOrPrivateIdentifierOrPattern(); - case ParsingContext.ArrayBindingElements: - return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isBindingIdentifierOrPrivateIdentifierOrPattern(); - case ParsingContext.TypeParameters: - return isIdentifier(); - case ParsingContext.ArrayLiteralMembers: - switch (token()) { - case SyntaxKind.CommaToken: - case SyntaxKind.DotToken: // Not an array literal member, but don't want to close the array (see `tests/cases/fourslash/completionsDotInArrayLiteralInObjectLiteral.ts`) - return true; - } - // falls through - case ParsingContext.ArgumentExpressions: - return token() === SyntaxKind.DotDotDotToken || isStartOfExpression(); - case ParsingContext.Parameters: - return isStartOfParameter(/*isJSDocParameter*/ false); - case ParsingContext.JSDocParameters: - return isStartOfParameter(/*isJSDocParameter*/ true); - case ParsingContext.TypeArguments: - case ParsingContext.TupleElementTypes: - return token() === SyntaxKind.CommaToken || isStartOfType(); - case ParsingContext.HeritageClauses: - return isHeritageClause(); - case ParsingContext.ImportOrExportSpecifiers: - return tokenIsIdentifierOrKeyword(token()); - case ParsingContext.JsxAttributes: - return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken; - case ParsingContext.JsxChildren: - return true; - } + function nextTokenCanFollowExportModifier(): boolean { + nextToken(); + return canFollowExportModifier(); + } - return Debug.fail("Non-exhaustive case in 'isListElement'."); - } + function parseAnyContextualModifier(): boolean { + return isModifierKind(token()) && tryParse(nextTokenCanFollowModifier); + } - function isValidHeritageClauseObjectLiteral() { - Debug.assert(token() === SyntaxKind.OpenBraceToken); - if (nextToken() === SyntaxKind.CloseBraceToken) { - // if we see "extends {}" then only treat the {} as what we're extending (and not - // the class body) if we have: - // - // extends {} { - // extends {}, - // extends {} extends - // extends {} implements + function canFollowModifier(): boolean { + return token() === SyntaxKind.OpenBracketToken + || token() === SyntaxKind.OpenBraceToken + || token() === SyntaxKind.AsteriskToken + || token() === SyntaxKind.DotDotDotToken + || isLiteralPropertyName(); + } - const next = nextToken(); - return next === SyntaxKind.CommaToken || next === SyntaxKind.OpenBraceToken || next === SyntaxKind.ExtendsKeyword || next === SyntaxKind.ImplementsKeyword; - } + function nextTokenCanFollowDefaultKeyword(): boolean { + nextToken(); + return token() === SyntaxKind.ClassKeyword || token() === SyntaxKind.FunctionKeyword || + token() === SyntaxKind.InterfaceKeyword || + (token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsClassKeywordOnSameLine)) || + (token() === SyntaxKind.AsyncKeyword && lookAhead(nextTokenIsFunctionKeywordOnSameLine)); + } + // True if positioned at the start of a list element + function isListElement(parsingContext: ParsingContext, inErrorRecovery: boolean): boolean { + const node = currentNode(parsingContext); + if (node) { return true; } - function nextTokenIsIdentifier() { - nextToken(); - return isIdentifier(); - } - - function nextTokenIsIdentifierOrKeyword() { - nextToken(); - return tokenIsIdentifierOrKeyword(token()); - } + switch (parsingContext) { + case ParsingContext.SourceElements: + case ParsingContext.BlockStatements: + case ParsingContext.SwitchClauseStatements: + // If we're in error recovery, then we don't want to treat ';' as an empty statement. + // The problem is that ';' can show up in far too many contexts, and if we see one + // and assume it's a statement, then we may bail out inappropriately from whatever + // we're parsing. For example, if we have a semicolon in the middle of a class, then + // we really don't want to assume the class is over and we're on a statement in the + // outer module. We just want to consume and move on. + return !(token() === SyntaxKind.SemicolonToken && inErrorRecovery) && isStartOfStatement(); + case ParsingContext.SwitchClauses: + return token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; + case ParsingContext.TypeMembers: + return lookAhead(isTypeMemberStart); + case ParsingContext.ClassMembers: + // We allow semicolons as class elements (as specified by ES6) as long as we're + // not in error recovery. If we're in error recovery, we don't want an errant + // semicolon to be treated as a class member (since they're almost always used + // for statements. + return lookAhead(isClassMemberStart) || (token() === SyntaxKind.SemicolonToken && !inErrorRecovery); + case ParsingContext.EnumMembers: + // Include open bracket computed properties. This technically also lets in indexers, + // which would be a candidate for improved error reporting. + return token() === SyntaxKind.OpenBracketToken || isLiteralPropertyName(); + case ParsingContext.ObjectLiteralMembers: + switch (token()) { + case SyntaxKind.OpenBracketToken: + case SyntaxKind.AsteriskToken: + case SyntaxKind.DotDotDotToken: + case SyntaxKind.DotToken: // Not an object literal member, but don't want to close the object (see `tests/cases/fourslash/completionsDotInObjectLiteral.ts`) + return true; + default: + return isLiteralPropertyName(); + } + case ParsingContext.RestProperties: + return isLiteralPropertyName(); + case ParsingContext.ObjectBindingElements: + return token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.DotDotDotToken || isLiteralPropertyName(); + case ParsingContext.AssertEntries: + return isAssertionKey(); + case ParsingContext.HeritageClauseElement: + // If we see `{ ... }` then only consume it as an expression if it is followed by `,` or `{` + // That way we won't consume the body of a class in its heritage clause. + if (token() === SyntaxKind.OpenBraceToken) { + return lookAhead(isValidHeritageClauseObjectLiteral); + } - function nextTokenIsIdentifierOrKeywordOrGreaterThan() { - nextToken(); - return tokenIsIdentifierOrKeywordOrGreaterThan(token()); + if (!inErrorRecovery) { + return isStartOfLeftHandSideExpression() && !isHeritageClauseExtendsOrImplementsKeyword(); + } + else { + // If we're in error recovery we tighten up what we're willing to match. + // That way we don't treat something like "this" as a valid heritage clause + // element during recovery. + return isIdentifier() && !isHeritageClauseExtendsOrImplementsKeyword(); + } + case ParsingContext.VariableDeclarations: + return isBindingIdentifierOrPrivateIdentifierOrPattern(); + case ParsingContext.ArrayBindingElements: + return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isBindingIdentifierOrPrivateIdentifierOrPattern(); + case ParsingContext.TypeParameters: + return isIdentifier(); + case ParsingContext.ArrayLiteralMembers: + switch (token()) { + case SyntaxKind.CommaToken: + case SyntaxKind.DotToken: // Not an array literal member, but don't want to close the array (see `tests/cases/fourslash/completionsDotInArrayLiteralInObjectLiteral.ts`) + return true; + } + // falls through + case ParsingContext.ArgumentExpressions: + return token() === SyntaxKind.DotDotDotToken || isStartOfExpression(); + case ParsingContext.Parameters: + return isStartOfParameter(/*isJSDocParameter*/ false); + case ParsingContext.JSDocParameters: + return isStartOfParameter(/*isJSDocParameter*/ true); + case ParsingContext.TypeArguments: + case ParsingContext.TupleElementTypes: + return token() === SyntaxKind.CommaToken || isStartOfType(); + case ParsingContext.HeritageClauses: + return isHeritageClause(); + case ParsingContext.ImportOrExportSpecifiers: + return tokenIsIdentifierOrKeyword(token()); + case ParsingContext.JsxAttributes: + return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken; + case ParsingContext.JsxChildren: + return true; } - function isHeritageClauseExtendsOrImplementsKeyword(): boolean { - if (token() === SyntaxKind.ImplementsKeyword || - token() === SyntaxKind.ExtendsKeyword) { + return Debug.fail("Non-exhaustive case in 'isListElement'."); + } - return lookAhead(nextTokenIsStartOfExpression); - } + function isValidHeritageClauseObjectLiteral() { + Debug.assert(token() === SyntaxKind.OpenBraceToken); + if (nextToken() === SyntaxKind.CloseBraceToken) { + // if we see "extends {}" then only treat the {} as what we're extending (and not + // the class body) if we have: + // + // extends {} { + // extends {}, + // extends {} extends + // extends {} implements - return false; + const next = nextToken(); + return next === SyntaxKind.CommaToken || next === SyntaxKind.OpenBraceToken || next === SyntaxKind.ExtendsKeyword || next === SyntaxKind.ImplementsKeyword; } - function nextTokenIsStartOfExpression() { - nextToken(); - return isStartOfExpression(); - } + return true; + } - function nextTokenIsStartOfType() { - nextToken(); - return isStartOfType(); - } + function nextTokenIsIdentifier() { + nextToken(); + return isIdentifier(); + } - // True if positioned at a list terminator - function isListTerminator(kind: ParsingContext): boolean { - if (token() === SyntaxKind.EndOfFileToken) { - // Being at the end of the file ends all lists. - return true; - } + function nextTokenIsIdentifierOrKeyword() { + nextToken(); + return tokenIsIdentifierOrKeyword(token()); + } - switch (kind) { - case ParsingContext.BlockStatements: - case ParsingContext.SwitchClauses: - case ParsingContext.TypeMembers: - case ParsingContext.ClassMembers: - case ParsingContext.EnumMembers: - case ParsingContext.ObjectLiteralMembers: - case ParsingContext.ObjectBindingElements: - case ParsingContext.ImportOrExportSpecifiers: - case ParsingContext.AssertEntries: - return token() === SyntaxKind.CloseBraceToken; - case ParsingContext.SwitchClauseStatements: - return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; - case ParsingContext.HeritageClauseElement: - return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; - case ParsingContext.VariableDeclarations: - return isVariableDeclaratorListTerminator(); - case ParsingContext.TypeParameters: - // Tokens other than '>' are here for better error recovery - return token() === SyntaxKind.GreaterThanToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; - case ParsingContext.ArgumentExpressions: - // Tokens other than ')' are here for better error recovery - return token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.SemicolonToken; - case ParsingContext.ArrayLiteralMembers: - case ParsingContext.TupleElementTypes: - case ParsingContext.ArrayBindingElements: - return token() === SyntaxKind.CloseBracketToken; - case ParsingContext.JSDocParameters: - case ParsingContext.Parameters: - case ParsingContext.RestProperties: - // Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery - return token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.CloseBracketToken /*|| token === SyntaxKind.OpenBraceToken*/; - case ParsingContext.TypeArguments: - // All other tokens should cause the type-argument to terminate except comma token - return token() !== SyntaxKind.CommaToken; - case ParsingContext.HeritageClauses: - return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.CloseBraceToken; - case ParsingContext.JsxAttributes: - return token() === SyntaxKind.GreaterThanToken || token() === SyntaxKind.SlashToken; - case ParsingContext.JsxChildren: - return token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsSlash); - default: - return false; - } + function nextTokenIsIdentifierOrKeywordOrGreaterThan() { + nextToken(); + return tokenIsIdentifierOrKeywordOrGreaterThan(token()); + } + + function isHeritageClauseExtendsOrImplementsKeyword(): boolean { + if (token() === SyntaxKind.ImplementsKeyword || + token() === SyntaxKind.ExtendsKeyword) { + + return lookAhead(nextTokenIsStartOfExpression); } - function isVariableDeclaratorListTerminator(): boolean { - // If we can consume a semicolon (either explicitly, or with ASI), then consider us done - // with parsing the list of variable declarators. - if (canParseSemicolon()) { - return true; - } + return false; + } - // in the case where we're parsing the variable declarator of a 'for-in' statement, we - // are done if we see an 'in' keyword in front of us. Same with for-of - if (isInOrOfKeyword(token())) { - return true; - } + function nextTokenIsStartOfExpression() { + nextToken(); + return isStartOfExpression(); + } - // ERROR RECOVERY TWEAK: - // For better error recovery, if we see an '=>' then we just stop immediately. We've got an - // arrow function here and it's going to be very unlikely that we'll resynchronize and get - // another variable declaration. - if (token() === SyntaxKind.EqualsGreaterThanToken) { - return true; - } + function nextTokenIsStartOfType() { + nextToken(); + return isStartOfType(); + } - // Keep trying to parse out variable declarators. - return false; + // True if positioned at a list terminator + function isListTerminator(kind: ParsingContext): boolean { + if (token() === SyntaxKind.EndOfFileToken) { + // Being at the end of the file ends all lists. + return true; } - // True if positioned at element or terminator of the current list or any enclosing list - function isInSomeParsingContext(): boolean { - for (let kind = 0; kind < ParsingContext.Count; kind++) { - if (parsingContext & (1 << kind)) { - if (isListElement(kind, /*inErrorRecovery*/ true) || isListTerminator(kind)) { - return true; - } - } - } + switch (kind) { + case ParsingContext.BlockStatements: + case ParsingContext.SwitchClauses: + case ParsingContext.TypeMembers: + case ParsingContext.ClassMembers: + case ParsingContext.EnumMembers: + case ParsingContext.ObjectLiteralMembers: + case ParsingContext.ObjectBindingElements: + case ParsingContext.ImportOrExportSpecifiers: + case ParsingContext.AssertEntries: + return token() === SyntaxKind.CloseBraceToken; + case ParsingContext.SwitchClauseStatements: + return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; + case ParsingContext.HeritageClauseElement: + return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; + case ParsingContext.VariableDeclarations: + return isVariableDeclaratorListTerminator(); + case ParsingContext.TypeParameters: + // Tokens other than '>' are here for better error recovery + return token() === SyntaxKind.GreaterThanToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; + case ParsingContext.ArgumentExpressions: + // Tokens other than ')' are here for better error recovery + return token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.SemicolonToken; + case ParsingContext.ArrayLiteralMembers: + case ParsingContext.TupleElementTypes: + case ParsingContext.ArrayBindingElements: + return token() === SyntaxKind.CloseBracketToken; + case ParsingContext.JSDocParameters: + case ParsingContext.Parameters: + case ParsingContext.RestProperties: + // Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery + return token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.CloseBracketToken /*|| token === SyntaxKind.OpenBraceToken*/; + case ParsingContext.TypeArguments: + // All other tokens should cause the type-argument to terminate except comma token + return token() !== SyntaxKind.CommaToken; + case ParsingContext.HeritageClauses: + return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.CloseBraceToken; + case ParsingContext.JsxAttributes: + return token() === SyntaxKind.GreaterThanToken || token() === SyntaxKind.SlashToken; + case ParsingContext.JsxChildren: + return token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsSlash); + default: + return false; + } + } - return false; + function isVariableDeclaratorListTerminator(): boolean { + // If we can consume a semicolon (either explicitly, or with ASI), then consider us done + // with parsing the list of variable declarators. + if (canParseSemicolon()) { + return true; } - // Parses a list of elements - function parseList(kind: ParsingContext, parseElement: () => T): NodeArray { - const saveParsingContext = parsingContext; - parsingContext |= 1 << kind; - const list = []; - const listPos = getNodePos(); + // in the case where we're parsing the variable declarator of a 'for-in' statement, we + // are done if we see an 'in' keyword in front of us. Same with for-of + if (isInOrOfKeyword(token())) { + return true; + } - while (!isListTerminator(kind)) { - if (isListElement(kind, /*inErrorRecovery*/ false)) { - list.push(parseListElement(kind, parseElement)); + // ERROR RECOVERY TWEAK: + // For better error recovery, if we see an '=>' then we just stop immediately. We've got an + // arrow function here and it's going to be very unlikely that we'll resynchronize and get + // another variable declaration. + if (token() === SyntaxKind.EqualsGreaterThanToken) { + return true; + } - continue; - } + // Keep trying to parse out variable declarators. + return false; + } - if (abortParsingListOrMoveToNextToken(kind)) { - break; + // True if positioned at element or terminator of the current list or any enclosing list + function isInSomeParsingContext(): boolean { + for (let kind = 0; kind < ParsingContext.Count; kind++) { + if (parsingContext & (1 << kind)) { + if (isListElement(kind, /*inErrorRecovery*/ true) || isListTerminator(kind)) { + return true; } } - - parsingContext = saveParsingContext; - return createNodeArray(list, listPos); } - function parseListElement(parsingContext: ParsingContext, parseElement: () => T): T { - const node = currentNode(parsingContext); - if (node) { - return consumeNode(node) as T; + return false; + } + + // Parses a list of elements + function parseList(kind: ParsingContext, parseElement: () => T): NodeArray { + const saveParsingContext = parsingContext; + parsingContext |= 1 << kind; + const list = []; + const listPos = getNodePos(); + + while (!isListTerminator(kind)) { + if (isListElement(kind, /*inErrorRecovery*/ false)) { + list.push(parseListElement(kind, parseElement)); + + continue; } - return parseElement(); + if (abortParsingListOrMoveToNextToken(kind)) { + break; + } } - function currentNode(parsingContext: ParsingContext): Node | undefined { - // If we don't have a cursor or the parsing context isn't reusable, there's nothing to reuse. - // - // If there is an outstanding parse error that we've encountered, but not attached to - // some node, then we cannot get a node from the old source tree. This is because we - // want to mark the next node we encounter as being unusable. - // - // Note: This may be too conservative. Perhaps we could reuse the node and set the bit - // on it (or its leftmost child) as having the error. For now though, being conservative - // is nice and likely won't ever affect perf. - if (!syntaxCursor || !isReusableParsingContext(parsingContext) || parseErrorBeforeNextFinishedNode) { - return undefined; - } + parsingContext = saveParsingContext; + return createNodeArray(list, listPos); + } - const node = syntaxCursor.currentNode(scanner.getStartPos()); + function parseListElement(parsingContext: ParsingContext, parseElement: () => T): T { + const node = currentNode(parsingContext); + if (node) { + return consumeNode(node) as T; + } - // Can't reuse a missing node. - // Can't reuse a node that intersected the change range. - // Can't reuse a node that contains a parse error. This is necessary so that we - // produce the same set of errors again. - if (nodeIsMissing(node) || node.intersectsChange || containsParseError(node)) { - return undefined; - } + return parseElement(); + } - // We can only reuse a node if it was parsed under the same strict mode that we're - // currently in. i.e. if we originally parsed a node in non-strict mode, but then - // the user added 'using strict' at the top of the file, then we can't use that node - // again as the presence of strict mode may cause us to parse the tokens in the file - // differently. - // - // Note: we *can* reuse tokens when the strict mode changes. That's because tokens - // are unaffected by strict mode. It's just the parser will decide what to do with it - // differently depending on what mode it is in. - // - // This also applies to all our other context flags as well. - const nodeContextFlags = node.flags & NodeFlags.ContextFlags; - if (nodeContextFlags !== contextFlags) { - return undefined; - } + function currentNode(parsingContext: ParsingContext): Node | undefined { + // If we don't have a cursor or the parsing context isn't reusable, there's nothing to reuse. + // + // If there is an outstanding parse error that we've encountered, but not attached to + // some node, then we cannot get a node from the old source tree. This is because we + // want to mark the next node we encounter as being unusable. + // + // Note: This may be too conservative. Perhaps we could reuse the node and set the bit + // on it (or its leftmost child) as having the error. For now though, being conservative + // is nice and likely won't ever affect perf. + if (!syntaxCursor || !isReusableParsingContext(parsingContext) || parseErrorBeforeNextFinishedNode) { + return undefined; + } - // Ok, we have a node that looks like it could be reused. Now verify that it is valid - // in the current list parsing context that we're currently at. - if (!canReuseNode(node, parsingContext)) { - return undefined; - } + const node = syntaxCursor.currentNode(scanner.getStartPos()); - if ((node as JSDocContainer).jsDocCache) { - // jsDocCache may include tags from parent nodes, which might have been modified. - (node as JSDocContainer).jsDocCache = undefined; - } + // Can't reuse a missing node. + // Can't reuse a node that intersected the change range. + // Can't reuse a node that contains a parse error. This is necessary so that we + // produce the same set of errors again. + if (nodeIsMissing(node) || node.intersectsChange || containsParseError(node)) { + return undefined; + } - return node; + // We can only reuse a node if it was parsed under the same strict mode that we're + // currently in. i.e. if we originally parsed a node in non-strict mode, but then + // the user added 'using strict' at the top of the file, then we can't use that node + // again as the presence of strict mode may cause us to parse the tokens in the file + // differently. + // + // Note: we *can* reuse tokens when the strict mode changes. That's because tokens + // are unaffected by strict mode. It's just the parser will decide what to do with it + // differently depending on what mode it is in. + // + // This also applies to all our other context flags as well. + const nodeContextFlags = node.flags & NodeFlags.ContextFlags; + if (nodeContextFlags !== contextFlags) { + return undefined; } - function consumeNode(node: Node) { - // Move the scanner so it is after the node we just consumed. - scanner.setTextPos(node.end); - nextToken(); - return node; + // Ok, we have a node that looks like it could be reused. Now verify that it is valid + // in the current list parsing context that we're currently at. + if (!canReuseNode(node, parsingContext)) { + return undefined; } - function isReusableParsingContext(parsingContext: ParsingContext): boolean { - switch (parsingContext) { - case ParsingContext.ClassMembers: - case ParsingContext.SwitchClauses: - case ParsingContext.SourceElements: - case ParsingContext.BlockStatements: - case ParsingContext.SwitchClauseStatements: - case ParsingContext.EnumMembers: - case ParsingContext.TypeMembers: - case ParsingContext.VariableDeclarations: - case ParsingContext.JSDocParameters: - case ParsingContext.Parameters: - return true; - } - return false; + if ((node as JSDocContainer).jsDocCache) { + // jsDocCache may include tags from parent nodes, which might have been modified. + (node as JSDocContainer).jsDocCache = undefined; } - function canReuseNode(node: Node, parsingContext: ParsingContext): boolean { - switch (parsingContext) { - case ParsingContext.ClassMembers: - return isReusableClassMember(node); + return node; + } - case ParsingContext.SwitchClauses: - return isReusableSwitchClause(node); + function consumeNode(node: Node) { + // Move the scanner so it is after the node we just consumed. + scanner.setTextPos(node.end); + nextToken(); + return node; + } - case ParsingContext.SourceElements: - case ParsingContext.BlockStatements: - case ParsingContext.SwitchClauseStatements: - return isReusableStatement(node); + function isReusableParsingContext(parsingContext: ParsingContext): boolean { + switch (parsingContext) { + case ParsingContext.ClassMembers: + case ParsingContext.SwitchClauses: + case ParsingContext.SourceElements: + case ParsingContext.BlockStatements: + case ParsingContext.SwitchClauseStatements: + case ParsingContext.EnumMembers: + case ParsingContext.TypeMembers: + case ParsingContext.VariableDeclarations: + case ParsingContext.JSDocParameters: + case ParsingContext.Parameters: + return true; + } + return false; + } - case ParsingContext.EnumMembers: - return isReusableEnumMember(node); + function canReuseNode(node: Node, parsingContext: ParsingContext): boolean { + switch (parsingContext) { + case ParsingContext.ClassMembers: + return isReusableClassMember(node); - case ParsingContext.TypeMembers: - return isReusableTypeMember(node); + case ParsingContext.SwitchClauses: + return isReusableSwitchClause(node); - case ParsingContext.VariableDeclarations: - return isReusableVariableDeclaration(node); + case ParsingContext.SourceElements: + case ParsingContext.BlockStatements: + case ParsingContext.SwitchClauseStatements: + return isReusableStatement(node); - case ParsingContext.JSDocParameters: - case ParsingContext.Parameters: - return isReusableParameter(node); + case ParsingContext.EnumMembers: + return isReusableEnumMember(node); - // Any other lists we do not care about reusing nodes in. But feel free to add if - // you can do so safely. Danger areas involve nodes that may involve speculative - // parsing. If speculative parsing is involved with the node, then the range the - // parser reached while looking ahead might be in the edited range (see the example - // in canReuseVariableDeclaratorNode for a good case of this). + case ParsingContext.TypeMembers: + return isReusableTypeMember(node); - // case ParsingContext.HeritageClauses: - // This would probably be safe to reuse. There is no speculative parsing with - // heritage clauses. + case ParsingContext.VariableDeclarations: + return isReusableVariableDeclaration(node); - // case ParsingContext.TypeParameters: - // This would probably be safe to reuse. There is no speculative parsing with - // type parameters. Note that that's because type *parameters* only occur in - // unambiguous *type* contexts. While type *arguments* occur in very ambiguous - // *expression* contexts. + case ParsingContext.JSDocParameters: + case ParsingContext.Parameters: + return isReusableParameter(node); - // case ParsingContext.TupleElementTypes: - // This would probably be safe to reuse. There is no speculative parsing with - // tuple types. + // Any other lists we do not care about reusing nodes in. But feel free to add if + // you can do so safely. Danger areas involve nodes that may involve speculative + // parsing. If speculative parsing is involved with the node, then the range the + // parser reached while looking ahead might be in the edited range (see the example + // in canReuseVariableDeclaratorNode for a good case of this). - // Technically, type argument list types are probably safe to reuse. While - // speculative parsing is involved with them (since type argument lists are only - // produced from speculative parsing a < as a type argument list), we only have - // the types because speculative parsing succeeded. Thus, the lookahead never - // went past the end of the list and rewound. - // case ParsingContext.TypeArguments: + // case ParsingContext.HeritageClauses: + // This would probably be safe to reuse. There is no speculative parsing with + // heritage clauses. - // Note: these are almost certainly not safe to ever reuse. Expressions commonly - // need a large amount of lookahead, and we should not reuse them as they may - // have actually intersected the edit. - // case ParsingContext.ArgumentExpressions: + // case ParsingContext.TypeParameters: + // This would probably be safe to reuse. There is no speculative parsing with + // type parameters. Note that that's because type *parameters* only occur in + // unambiguous *type* contexts. While type *arguments* occur in very ambiguous + // *expression* contexts. - // This is not safe to reuse for the same reason as the 'AssignmentExpression' - // cases. i.e. a property assignment may end with an expression, and thus might - // have lookahead far beyond it's old node. - // case ParsingContext.ObjectLiteralMembers: + // case ParsingContext.TupleElementTypes: + // This would probably be safe to reuse. There is no speculative parsing with + // tuple types. - // This is probably not safe to reuse. There can be speculative parsing with - // type names in a heritage clause. There can be generic names in the type - // name list, and there can be left hand side expressions (which can have type - // arguments.) - // case ParsingContext.HeritageClauseElement: + // Technically, type argument list types are probably safe to reuse. While + // speculative parsing is involved with them (since type argument lists are only + // produced from speculative parsing a < as a type argument list), we only have + // the types because speculative parsing succeeded. Thus, the lookahead never + // went past the end of the list and rewound. + // case ParsingContext.TypeArguments: - // Perhaps safe to reuse, but it's unlikely we'd see more than a dozen attributes - // on any given element. Same for children. - // case ParsingContext.JsxAttributes: - // case ParsingContext.JsxChildren: + // Note: these are almost certainly not safe to ever reuse. Expressions commonly + // need a large amount of lookahead, and we should not reuse them as they may + // have actually intersected the edit. + // case ParsingContext.ArgumentExpressions: - } + // This is not safe to reuse for the same reason as the 'AssignmentExpression' + // cases. i.e. a property assignment may end with an expression, and thus might + // have lookahead far beyond it's old node. + // case ParsingContext.ObjectLiteralMembers: - return false; - } + // This is probably not safe to reuse. There can be speculative parsing with + // type names in a heritage clause. There can be generic names in the type + // name list, and there can be left hand side expressions (which can have type + // arguments.) + // case ParsingContext.HeritageClauseElement: - function isReusableClassMember(node: Node) { - if (node) { - switch (node.kind) { - case SyntaxKind.Constructor: - case SyntaxKind.IndexSignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.SemicolonClassElement: - return true; - case SyntaxKind.MethodDeclaration: - // Method declarations are not necessarily reusable. An object-literal - // may have a method calls "constructor(...)" and we must reparse that - // into an actual .ConstructorDeclaration. - const methodDeclaration = node as MethodDeclaration; - const nameIsConstructor = methodDeclaration.name.kind === SyntaxKind.Identifier && - methodDeclaration.name.originalKeywordKind === SyntaxKind.ConstructorKeyword; - - return !nameIsConstructor; - } - } + // Perhaps safe to reuse, but it's unlikely we'd see more than a dozen attributes + // on any given element. Same for children. + // case ParsingContext.JsxAttributes: + // case ParsingContext.JsxChildren: - return false; } - function isReusableSwitchClause(node: Node) { - if (node) { - switch (node.kind) { - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - return true; - } - } + return false; + } - return false; - } + function isReusableClassMember(node: Node) { + if (node) { + switch (node.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.IndexSignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.SemicolonClassElement: + return true; + case SyntaxKind.MethodDeclaration: + // Method declarations are not necessarily reusable. An object-literal + // may have a method calls "constructor(...)" and we must reparse that + // into an actual .ConstructorDeclaration. + const methodDeclaration = node as MethodDeclaration; + const nameIsConstructor = methodDeclaration.name.kind === SyntaxKind.Identifier && + methodDeclaration.name.originalKeywordKind === SyntaxKind.ConstructorKeyword; - function isReusableStatement(node: Node) { - if (node) { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.VariableStatement: - case SyntaxKind.Block: - case SyntaxKind.IfStatement: - case SyntaxKind.ExpressionStatement: - case SyntaxKind.ThrowStatement: - case SyntaxKind.ReturnStatement: - case SyntaxKind.SwitchStatement: - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.EmptyStatement: - case SyntaxKind.TryStatement: - case SyntaxKind.LabeledStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.DebuggerStatement: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExportAssignment: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - return true; - } + return !nameIsConstructor; } - - return false; } - function isReusableEnumMember(node: Node) { - return node.kind === SyntaxKind.EnumMember; - } + return false; + } - function isReusableTypeMember(node: Node) { - if (node) { - switch (node.kind) { - case SyntaxKind.ConstructSignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.PropertySignature: - case SyntaxKind.CallSignature: - return true; - } + function isReusableSwitchClause(node: Node) { + if (node) { + switch (node.kind) { + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + return true; } - - return false; } - function isReusableVariableDeclaration(node: Node) { - if (node.kind !== SyntaxKind.VariableDeclaration) { - return false; + return false; + } + + function isReusableStatement(node: Node) { + if (node) { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.VariableStatement: + case SyntaxKind.Block: + case SyntaxKind.IfStatement: + case SyntaxKind.ExpressionStatement: + case SyntaxKind.ThrowStatement: + case SyntaxKind.ReturnStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.BreakStatement: + case SyntaxKind.ContinueStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.EmptyStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.LabeledStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.DebuggerStatement: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExportAssignment: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return true; } + } - // Very subtle incremental parsing bug. Consider the following code: - // - // let v = new List < A, B - // - // This is actually legal code. It's a list of variable declarators "v = new List() - // - // then we have a problem. "v = new List() + // + // then we have a problem. "v = new List(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray { - const saveParsingContext = parsingContext; - parsingContext |= 1 << kind; - const list = []; - const listPos = getNodePos(); - - let commaStart = -1; // Meaning the previous token was not a comma - while (true) { - if (isListElement(kind, /*inErrorRecovery*/ false)) { - const startPos = scanner.getStartPos(); - list.push(parseListElement(kind, parseElement)); - commaStart = scanner.getTokenPos(); + // See the comment in isReusableVariableDeclaration for why we do this. + const parameter = node as ParameterDeclaration; + return parameter.initializer === undefined; + } - if (parseOptional(SyntaxKind.CommaToken)) { - // No need to check for a zero length node since we know we parsed a comma - continue; - } + // Returns true if we should abort parsing. + function abortParsingListOrMoveToNextToken(kind: ParsingContext) { + parsingContextErrors(kind); + if (isInSomeParsingContext()) { + return true; + } - commaStart = -1; // Back to the state where the last token was not a comma - if (isListTerminator(kind)) { - break; - } + nextToken(); + return false; + } - // We didn't get a comma, and the list wasn't terminated, explicitly parse - // out a comma so we give a good error message. - parseExpected(SyntaxKind.CommaToken, getExpectedCommaDiagnostic(kind)); + function parsingContextErrors(context: ParsingContext) { + switch (context) { + case ParsingContext.SourceElements: + return token() === SyntaxKind.DefaultKeyword + ? parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.ExportKeyword)) + : parseErrorAtCurrentToken(Diagnostics.Declaration_or_statement_expected); + case ParsingContext.BlockStatements: return parseErrorAtCurrentToken(Diagnostics.Declaration_or_statement_expected); + case ParsingContext.SwitchClauses: return parseErrorAtCurrentToken(Diagnostics.case_or_default_expected); + case ParsingContext.SwitchClauseStatements: return parseErrorAtCurrentToken(Diagnostics.Statement_expected); + case ParsingContext.RestProperties: // fallthrough + case ParsingContext.TypeMembers: return parseErrorAtCurrentToken(Diagnostics.Property_or_signature_expected); + case ParsingContext.ClassMembers: return parseErrorAtCurrentToken(Diagnostics.Unexpected_token_A_constructor_method_accessor_or_property_was_expected); + case ParsingContext.EnumMembers: return parseErrorAtCurrentToken(Diagnostics.Enum_member_expected); + case ParsingContext.HeritageClauseElement: return parseErrorAtCurrentToken(Diagnostics.Expression_expected); + case ParsingContext.VariableDeclarations: + return isKeyword(token()) + ? parseErrorAtCurrentToken(Diagnostics._0_is_not_allowed_as_a_variable_declaration_name, tokenToString(token())) + : parseErrorAtCurrentToken(Diagnostics.Variable_declaration_expected); + case ParsingContext.ObjectBindingElements: return parseErrorAtCurrentToken(Diagnostics.Property_destructuring_pattern_expected); + case ParsingContext.ArrayBindingElements: return parseErrorAtCurrentToken(Diagnostics.Array_element_destructuring_pattern_expected); + case ParsingContext.ArgumentExpressions: return parseErrorAtCurrentToken(Diagnostics.Argument_expression_expected); + case ParsingContext.ObjectLiteralMembers: return parseErrorAtCurrentToken(Diagnostics.Property_assignment_expected); + case ParsingContext.ArrayLiteralMembers: return parseErrorAtCurrentToken(Diagnostics.Expression_or_comma_expected); + case ParsingContext.JSDocParameters: return parseErrorAtCurrentToken(Diagnostics.Parameter_declaration_expected); + case ParsingContext.Parameters: + return isKeyword(token()) + ? parseErrorAtCurrentToken(Diagnostics._0_is_not_allowed_as_a_parameter_name, tokenToString(token())) + : parseErrorAtCurrentToken(Diagnostics.Parameter_declaration_expected); + case ParsingContext.TypeParameters: return parseErrorAtCurrentToken(Diagnostics.Type_parameter_declaration_expected); + case ParsingContext.TypeArguments: return parseErrorAtCurrentToken(Diagnostics.Type_argument_expected); + case ParsingContext.TupleElementTypes: return parseErrorAtCurrentToken(Diagnostics.Type_expected); + case ParsingContext.HeritageClauses: return parseErrorAtCurrentToken(Diagnostics.Unexpected_token_expected); + case ParsingContext.ImportOrExportSpecifiers: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected); + case ParsingContext.JsxAttributes: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected); + case ParsingContext.JsxChildren: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected); + default: return [undefined!]; // TODO: GH#18217 `default: Debug.assertNever(context);` + } + } - // If the token was a semicolon, and the caller allows that, then skip it and - // continue. This ensures we get back on track and don't result in tons of - // parse errors. For example, this can happen when people do things like use - // a semicolon to delimit object literal members. Note: we'll have already - // reported an error when we called parseExpected above. - if (considerSemicolonAsDelimiter && token() === SyntaxKind.SemicolonToken && !scanner.hasPrecedingLineBreak()) { - nextToken(); - } - if (startPos === scanner.getStartPos()) { - // What we're parsing isn't actually remotely recognizable as a element and we've consumed no tokens whatsoever - // Consume a token to advance the parser in some way and avoid an infinite loop - // This can happen when we're speculatively parsing parenthesized expressions which we think may be arrow functions, - // or when a modifier keyword which is disallowed as a parameter name (ie, `static` in strict mode) is supplied - nextToken(); - } + // Parses a comma-delimited list of elements + function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray { + const saveParsingContext = parsingContext; + parsingContext |= 1 << kind; + const list = []; + const listPos = getNodePos(); + + let commaStart = -1; // Meaning the previous token was not a comma + while (true) { + if (isListElement(kind, /*inErrorRecovery*/ false)) { + const startPos = scanner.getStartPos(); + list.push(parseListElement(kind, parseElement)); + commaStart = scanner.getTokenPos(); + + if (parseOptional(SyntaxKind.CommaToken)) { + // No need to check for a zero length node since we know we parsed a comma continue; } + commaStart = -1; // Back to the state where the last token was not a comma if (isListTerminator(kind)) { break; } - if (abortParsingListOrMoveToNextToken(kind)) { - break; + // We didn't get a comma, and the list wasn't terminated, explicitly parse + // out a comma so we give a good error message. + parseExpected(SyntaxKind.CommaToken, getExpectedCommaDiagnostic(kind)); + + // If the token was a semicolon, and the caller allows that, then skip it and + // continue. This ensures we get back on track and don't result in tons of + // parse errors. For example, this can happen when people do things like use + // a semicolon to delimit object literal members. Note: we'll have already + // reported an error when we called parseExpected above. + if (considerSemicolonAsDelimiter && token() === SyntaxKind.SemicolonToken && !scanner.hasPrecedingLineBreak()) { + nextToken(); + } + if (startPos === scanner.getStartPos()) { + // What we're parsing isn't actually remotely recognizable as a element and we've consumed no tokens whatsoever + // Consume a token to advance the parser in some way and avoid an infinite loop + // This can happen when we're speculatively parsing parenthesized expressions which we think may be arrow functions, + // or when a modifier keyword which is disallowed as a parameter name (ie, `static` in strict mode) is supplied + nextToken(); } + continue; } - parsingContext = saveParsingContext; - // Recording the trailing comma is deliberately done after the previous - // loop, and not just if we see a list terminator. This is because the list - // may have ended incorrectly, but it is still important to know if there - // was a trailing comma. - // Check if the last token was a comma. - // Always preserve a trailing comma by marking it on the NodeArray - return createNodeArray(list, listPos, /*end*/ undefined, commaStart >= 0); - } - - function getExpectedCommaDiagnostic(kind: ParsingContext) { - return kind === ParsingContext.EnumMembers ? Diagnostics.An_enum_member_name_must_be_followed_by_a_or : undefined; - } + if (isListTerminator(kind)) { + break; + } - interface MissingList extends NodeArray { - isMissingList: true; + if (abortParsingListOrMoveToNextToken(kind)) { + break; + } } - function createMissingList(): MissingList { - const list = createNodeArray([], getNodePos()) as MissingList; - list.isMissingList = true; - return list; - } + parsingContext = saveParsingContext; + // Recording the trailing comma is deliberately done after the previous + // loop, and not just if we see a list terminator. This is because the list + // may have ended incorrectly, but it is still important to know if there + // was a trailing comma. + // Check if the last token was a comma. + // Always preserve a trailing comma by marking it on the NodeArray + return createNodeArray(list, listPos, /*end*/ undefined, commaStart >= 0); + } - function isMissingList(arr: NodeArray): boolean { - return !!(arr as MissingList).isMissingList; - } + function getExpectedCommaDiagnostic(kind: ParsingContext) { + return kind === ParsingContext.EnumMembers ? Diagnostics.An_enum_member_name_must_be_followed_by_a_or : undefined; + } - function parseBracketedList(kind: ParsingContext, parseElement: () => T, open: SyntaxKind, close: SyntaxKind): NodeArray { - if (parseExpected(open)) { - const result = parseDelimitedList(kind, parseElement); - parseExpected(close); - return result; - } + interface MissingList extends NodeArray { + isMissingList: true; + } - return createMissingList(); - } + function createMissingList(): MissingList { + const list = createNodeArray([], getNodePos()) as MissingList; + list.isMissingList = true; + return list; + } - function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { - const pos = getNodePos(); - let entity: EntityName = allowReservedWords ? parseIdentifierName(diagnosticMessage) : parseIdentifier(diagnosticMessage); - let dotPos = getNodePos(); - while (parseOptional(SyntaxKind.DotToken)) { - if (token() === SyntaxKind.LessThanToken) { - // the entity is part of a JSDoc-style generic, so record the trailing dot for later error reporting - entity.jsdocDotPos = dotPos; - break; - } - dotPos = getNodePos(); - entity = finishNode( - factory.createQualifiedName( - entity, - parseRightSideOfDot(allowReservedWords, /* allowPrivateIdentifiers */ false) as Identifier - ), - pos - ); - } - return entity; - } + function isMissingList(arr: NodeArray): boolean { + return !!(arr as MissingList).isMissingList; + } - function createQualifiedName(entity: EntityName, name: Identifier): QualifiedName { - return finishNode(factory.createQualifiedName(entity, name), entity.pos); + function parseBracketedList(kind: ParsingContext, parseElement: () => T, open: SyntaxKind, close: SyntaxKind): NodeArray { + if (parseExpected(open)) { + const result = parseDelimitedList(kind, parseElement); + parseExpected(close); + return result; } - function parseRightSideOfDot(allowIdentifierNames: boolean, allowPrivateIdentifiers: boolean): Identifier | PrivateIdentifier { - // Technically a keyword is valid here as all identifiers and keywords are identifier names. - // However, often we'll encounter this in error situations when the identifier or keyword - // is actually starting another valid construct. - // - // So, we check for the following specific case: - // - // name. - // identifierOrKeyword identifierNameOrKeyword - // - // Note: the newlines are important here. For example, if that above code - // were rewritten into: - // - // name.identifierOrKeyword - // identifierNameOrKeyword - // - // Then we would consider it valid. That's because ASI would take effect and - // the code would be implicitly: "name.identifierOrKeyword; identifierNameOrKeyword". - // In the first case though, ASI will not take effect because there is not a - // line terminator after the identifier or keyword. - if (scanner.hasPrecedingLineBreak() && tokenIsIdentifierOrKeyword(token())) { - const matchesPattern = lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine); - - if (matchesPattern) { - // Report that we need an identifier. However, report it right after the dot, - // and not on the next token. This is because the next token might actually - // be an identifier and the error would be quite confusing. - return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected); - } - } - - if (token() === SyntaxKind.PrivateIdentifier) { - const node = parsePrivateIdentifier(); - return allowPrivateIdentifiers ? node : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected); - } - - return allowIdentifierNames ? parseIdentifierName() : parseIdentifier(); - } + return createMissingList(); + } - function parseTemplateSpans(isTaggedTemplate: boolean) { - const pos = getNodePos(); - const list = []; - let node: TemplateSpan; - do { - node = parseTemplateSpan(isTaggedTemplate); - list.push(node); + function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { + const pos = getNodePos(); + let entity: EntityName = allowReservedWords ? parseIdentifierName(diagnosticMessage) : parseIdentifier(diagnosticMessage); + let dotPos = getNodePos(); + while (parseOptional(SyntaxKind.DotToken)) { + if (token() === SyntaxKind.LessThanToken) { + // the entity is part of a JSDoc-style generic, so record the trailing dot for later error reporting + entity.jsdocDotPos = dotPos; + break; } - while (node.literal.kind === SyntaxKind.TemplateMiddle); - return createNodeArray(list, pos); - } - - function parseTemplateExpression(isTaggedTemplate: boolean): TemplateExpression { - const pos = getNodePos(); - return finishNode( - factory.createTemplateExpression( - parseTemplateHead(isTaggedTemplate), - parseTemplateSpans(isTaggedTemplate) + dotPos = getNodePos(); + entity = finishNode( + factory.createQualifiedName( + entity, + parseRightSideOfDot(allowReservedWords, /* allowPrivateIdentifiers */ false) as Identifier ), pos ); } + return entity; + } - function parseTemplateType(): TemplateLiteralTypeNode { - const pos = getNodePos(); - return finishNode( - factory.createTemplateLiteralType( - parseTemplateHead(/*isTaggedTemplate*/ false), - parseTemplateTypeSpans() - ), - pos - ); - } + function createQualifiedName(entity: EntityName, name: Identifier): QualifiedName { + return finishNode(factory.createQualifiedName(entity, name), entity.pos); + } - function parseTemplateTypeSpans() { - const pos = getNodePos(); - const list = []; - let node: TemplateLiteralTypeSpan; - do { - node = parseTemplateTypeSpan(); - list.push(node); + function parseRightSideOfDot(allowIdentifierNames: boolean, allowPrivateIdentifiers: boolean): Identifier | PrivateIdentifier { + // Technically a keyword is valid here as all identifiers and keywords are identifier names. + // However, often we'll encounter this in error situations when the identifier or keyword + // is actually starting another valid construct. + // + // So, we check for the following specific case: + // + // name. + // identifierOrKeyword identifierNameOrKeyword + // + // Note: the newlines are important here. For example, if that above code + // were rewritten into: + // + // name.identifierOrKeyword + // identifierNameOrKeyword + // + // Then we would consider it valid. That's because ASI would take effect and + // the code would be implicitly: "name.identifierOrKeyword; identifierNameOrKeyword". + // In the first case though, ASI will not take effect because there is not a + // line terminator after the identifier or keyword. + if (scanner.hasPrecedingLineBreak() && tokenIsIdentifierOrKeyword(token())) { + const matchesPattern = lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine); + + if (matchesPattern) { + // Report that we need an identifier. However, report it right after the dot, + // and not on the next token. This is because the next token might actually + // be an identifier and the error would be quite confusing. + return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected); } - while (node.literal.kind === SyntaxKind.TemplateMiddle); - return createNodeArray(list, pos); } - function parseTemplateTypeSpan(): TemplateLiteralTypeSpan { - const pos = getNodePos(); - return finishNode( - factory.createTemplateLiteralTypeSpan( - parseType(), - parseLiteralOfTemplateSpan(/*isTaggedTemplate*/ false) - ), - pos - ); + if (token() === SyntaxKind.PrivateIdentifier) { + const node = parsePrivateIdentifier(); + return allowPrivateIdentifiers ? node : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected); } - function parseLiteralOfTemplateSpan(isTaggedTemplate: boolean) { - if (token() === SyntaxKind.CloseBraceToken) { - reScanTemplateToken(isTaggedTemplate); - return parseTemplateMiddleOrTemplateTail(); - } - else { - // TODO(rbuckton): Do we need to call `parseExpectedToken` or can we just call `createMissingNode` directly? - return parseExpectedToken(SyntaxKind.TemplateTail, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken)) as TemplateTail; - } - } + return allowIdentifierNames ? parseIdentifierName() : parseIdentifier(); + } - function parseTemplateSpan(isTaggedTemplate: boolean): TemplateSpan { - const pos = getNodePos(); - return finishNode( - factory.createTemplateSpan( - allowInAnd(parseExpression), - parseLiteralOfTemplateSpan(isTaggedTemplate) - ), - pos - ); + function parseTemplateSpans(isTaggedTemplate: boolean) { + const pos = getNodePos(); + const list = []; + let node: TemplateSpan; + do { + node = parseTemplateSpan(isTaggedTemplate); + list.push(node); } + while (node.literal.kind === SyntaxKind.TemplateMiddle); + return createNodeArray(list, pos); + } - function parseLiteralNode(): LiteralExpression { - return parseLiteralLikeNode(token()) as LiteralExpression; - } + function parseTemplateExpression(isTaggedTemplate: boolean): TemplateExpression { + const pos = getNodePos(); + return finishNode( + factory.createTemplateExpression( + parseTemplateHead(isTaggedTemplate), + parseTemplateSpans(isTaggedTemplate) + ), + pos + ); + } - function parseTemplateHead(isTaggedTemplate: boolean): TemplateHead { - if (isTaggedTemplate) { - reScanTemplateHeadOrNoSubstitutionTemplate(); - } - const fragment = parseLiteralLikeNode(token()); - Debug.assert(fragment.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); - return fragment as TemplateHead; - } + function parseTemplateType(): TemplateLiteralTypeNode { + const pos = getNodePos(); + return finishNode( + factory.createTemplateLiteralType( + parseTemplateHead(/*isTaggedTemplate*/ false), + parseTemplateTypeSpans() + ), + pos + ); + } - function parseTemplateMiddleOrTemplateTail(): TemplateMiddle | TemplateTail { - const fragment = parseLiteralLikeNode(token()); - Debug.assert(fragment.kind === SyntaxKind.TemplateMiddle || fragment.kind === SyntaxKind.TemplateTail, "Template fragment has wrong token kind"); - return fragment as TemplateMiddle | TemplateTail; + function parseTemplateTypeSpans() { + const pos = getNodePos(); + const list = []; + let node: TemplateLiteralTypeSpan; + do { + node = parseTemplateTypeSpan(); + list.push(node); } + while (node.literal.kind === SyntaxKind.TemplateMiddle); + return createNodeArray(list, pos); + } - function getTemplateLiteralRawText(kind: TemplateLiteralToken["kind"]) { - const isLast = kind === SyntaxKind.NoSubstitutionTemplateLiteral || kind === SyntaxKind.TemplateTail; - const tokenText = scanner.getTokenText(); - return tokenText.substring(1, tokenText.length - (scanner.isUnterminated() ? 0 : isLast ? 1 : 2)); - } + function parseTemplateTypeSpan(): TemplateLiteralTypeSpan { + const pos = getNodePos(); + return finishNode( + factory.createTemplateLiteralTypeSpan( + parseType(), + parseLiteralOfTemplateSpan(/*isTaggedTemplate*/ false) + ), + pos + ); + } - function parseLiteralLikeNode(kind: SyntaxKind): LiteralLikeNode { - const pos = getNodePos(); - const node = - isTemplateLiteralKind(kind) ? factory.createTemplateLiteralLikeNode(kind, scanner.getTokenValue(), getTemplateLiteralRawText(kind), scanner.getTokenFlags() & TokenFlags.TemplateLiteralLikeFlags) : - // Octal literals are not allowed in strict mode or ES5 - // Note that theoretically the following condition would hold true literals like 009, - // which is not octal. But because of how the scanner separates the tokens, we would - // never get a token like this. Instead, we would get 00 and 9 as two separate tokens. - // We also do not need to check for negatives because any prefix operator would be part of a - // parent unary expression. - kind === SyntaxKind.NumericLiteral ? factory.createNumericLiteral(scanner.getTokenValue(), scanner.getNumericLiteralFlags()) : - kind === SyntaxKind.StringLiteral ? factory.createStringLiteral(scanner.getTokenValue(), /*isSingleQuote*/ undefined, scanner.hasExtendedUnicodeEscape()) : - isLiteralKind(kind) ? factory.createLiteralLikeNode(kind, scanner.getTokenValue()) : - Debug.fail(); + function parseLiteralOfTemplateSpan(isTaggedTemplate: boolean) { + if (token() === SyntaxKind.CloseBraceToken) { + reScanTemplateToken(isTaggedTemplate); + return parseTemplateMiddleOrTemplateTail(); + } + else { + // TODO(rbuckton): Do we need to call `parseExpectedToken` or can we just call `createMissingNode` directly? + return parseExpectedToken(SyntaxKind.TemplateTail, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken)) as TemplateTail; + } + } - if (scanner.hasExtendedUnicodeEscape()) { - node.hasExtendedUnicodeEscape = true; - } + function parseTemplateSpan(isTaggedTemplate: boolean): TemplateSpan { + const pos = getNodePos(); + return finishNode( + factory.createTemplateSpan( + allowInAnd(parseExpression), + parseLiteralOfTemplateSpan(isTaggedTemplate) + ), + pos + ); + } - if (scanner.isUnterminated()) { - node.isUnterminated = true; - } + function parseLiteralNode(): LiteralExpression { + return parseLiteralLikeNode(token()) as LiteralExpression; + } - nextToken(); - return finishNode(node, pos); + function parseTemplateHead(isTaggedTemplate: boolean): TemplateHead { + if (isTaggedTemplate) { + reScanTemplateHeadOrNoSubstitutionTemplate(); } + const fragment = parseLiteralLikeNode(token()); + Debug.assert(fragment.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); + return fragment as TemplateHead; + } + + function parseTemplateMiddleOrTemplateTail(): TemplateMiddle | TemplateTail { + const fragment = parseLiteralLikeNode(token()); + Debug.assert(fragment.kind === SyntaxKind.TemplateMiddle || fragment.kind === SyntaxKind.TemplateTail, "Template fragment has wrong token kind"); + return fragment as TemplateMiddle | TemplateTail; + } + + function getTemplateLiteralRawText(kind: TemplateLiteralToken["kind"]) { + const isLast = kind === SyntaxKind.NoSubstitutionTemplateLiteral || kind === SyntaxKind.TemplateTail; + const tokenText = scanner.getTokenText(); + return tokenText.substring(1, tokenText.length - (scanner.isUnterminated() ? 0 : isLast ? 1 : 2)); + } - // TYPES + function parseLiteralLikeNode(kind: SyntaxKind): LiteralLikeNode { + const pos = getNodePos(); + const node = + isTemplateLiteralKind(kind) ? factory.createTemplateLiteralLikeNode(kind, scanner.getTokenValue(), getTemplateLiteralRawText(kind), scanner.getTokenFlags() & TokenFlags.TemplateLiteralLikeFlags) : + // Octal literals are not allowed in strict mode or ES5 + // Note that theoretically the following condition would hold true literals like 009, + // which is not octal. But because of how the scanner separates the tokens, we would + // never get a token like this. Instead, we would get 00 and 9 as two separate tokens. + // We also do not need to check for negatives because any prefix operator would be part of a + // parent unary expression. + kind === SyntaxKind.NumericLiteral ? factory.createNumericLiteral(scanner.getTokenValue(), scanner.getNumericLiteralFlags()) : + kind === SyntaxKind.StringLiteral ? factory.createStringLiteral(scanner.getTokenValue(), /*isSingleQuote*/ undefined, scanner.hasExtendedUnicodeEscape()) : + isLiteralKind(kind) ? factory.createLiteralLikeNode(kind, scanner.getTokenValue()) : + Debug.fail(); - function parseEntityNameOfTypeReference() { - return parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected); + if (scanner.hasExtendedUnicodeEscape()) { + node.hasExtendedUnicodeEscape = true; } - function parseTypeArgumentsOfTypeReference() { - if (!scanner.hasPrecedingLineBreak() && reScanLessThanToken() === SyntaxKind.LessThanToken) { - return parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); - } + if (scanner.isUnterminated()) { + node.isUnterminated = true; } - function parseTypeReference(): TypeReferenceNode { - const pos = getNodePos(); - return finishNode( - factory.createTypeReferenceNode( - parseEntityNameOfTypeReference(), - parseTypeArgumentsOfTypeReference() - ), - pos - ); + nextToken(); + return finishNode(node, pos); + } + + // TYPES + + function parseEntityNameOfTypeReference() { + return parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected); + } + + function parseTypeArgumentsOfTypeReference() { + if (!scanner.hasPrecedingLineBreak() && reScanLessThanToken() === SyntaxKind.LessThanToken) { + return parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); } + } - // If true, we should abort parsing an error function. - function typeHasArrowFunctionBlockingParseError(node: TypeNode): boolean { - switch (node.kind) { - case SyntaxKind.TypeReference: - return nodeIsMissing((node as TypeReferenceNode).typeName); - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: { - const { parameters, type } = node as FunctionOrConstructorTypeNode; - return isMissingList(parameters) || typeHasArrowFunctionBlockingParseError(type); - } - case SyntaxKind.ParenthesizedType: - return typeHasArrowFunctionBlockingParseError((node as ParenthesizedTypeNode).type); - default: - return false; + function parseTypeReference(): TypeReferenceNode { + const pos = getNodePos(); + return finishNode( + factory.createTypeReferenceNode( + parseEntityNameOfTypeReference(), + parseTypeArgumentsOfTypeReference() + ), + pos + ); + } + + // If true, we should abort parsing an error function. + function typeHasArrowFunctionBlockingParseError(node: TypeNode): boolean { + switch (node.kind) { + case SyntaxKind.TypeReference: + return nodeIsMissing((node as TypeReferenceNode).typeName); + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: { + const { parameters, type } = node as FunctionOrConstructorTypeNode; + return isMissingList(parameters) || typeHasArrowFunctionBlockingParseError(type); } + case SyntaxKind.ParenthesizedType: + return typeHasArrowFunctionBlockingParseError((node as ParenthesizedTypeNode).type); + default: + return false; } + } - function parseThisTypePredicate(lhs: ThisTypeNode): TypePredicateNode { - nextToken(); - return finishNode(factory.createTypePredicateNode(/*assertsModifier*/ undefined, lhs, parseType()), lhs.pos); - } + function parseThisTypePredicate(lhs: ThisTypeNode): TypePredicateNode { + nextToken(); + return finishNode(factory.createTypePredicateNode(/*assertsModifier*/ undefined, lhs, parseType()), lhs.pos); + } - function parseThisTypeNode(): ThisTypeNode { - const pos = getNodePos(); - nextToken(); - return finishNode(factory.createThisTypeNode(), pos); - } + function parseThisTypeNode(): ThisTypeNode { + const pos = getNodePos(); + nextToken(); + return finishNode(factory.createThisTypeNode(), pos); + } - function parseJSDocAllType(): JSDocAllType | JSDocOptionalType { - const pos = getNodePos(); - nextToken(); - return finishNode(factory.createJSDocAllType(), pos); - } + function parseJSDocAllType(): JSDocAllType | JSDocOptionalType { + const pos = getNodePos(); + nextToken(); + return finishNode(factory.createJSDocAllType(), pos); + } - function parseJSDocNonNullableType(): TypeNode { - const pos = getNodePos(); - nextToken(); - return finishNode(factory.createJSDocNonNullableType(parseNonArrayType()), pos); - } + function parseJSDocNonNullableType(): TypeNode { + const pos = getNodePos(); + nextToken(); + return finishNode(factory.createJSDocNonNullableType(parseNonArrayType()), pos); + } - function parseJSDocUnknownOrNullableType(): JSDocUnknownType | JSDocNullableType { - const pos = getNodePos(); - // skip the ? - nextToken(); + function parseJSDocUnknownOrNullableType(): JSDocUnknownType | JSDocNullableType { + const pos = getNodePos(); + // skip the ? + nextToken(); - // Need to lookahead to decide if this is a nullable or unknown type. + // Need to lookahead to decide if this is a nullable or unknown type. - // Here are cases where we'll pick the unknown type: - // - // Foo(?, - // { a: ? } - // Foo(?) - // Foo - // Foo(?= - // (?| - if (token() === SyntaxKind.CommaToken || - token() === SyntaxKind.CloseBraceToken || - token() === SyntaxKind.CloseParenToken || - token() === SyntaxKind.GreaterThanToken || - token() === SyntaxKind.EqualsToken || - token() === SyntaxKind.BarToken) { - return finishNode(factory.createJSDocUnknownType(), pos); - } - else { - return finishNode(factory.createJSDocNullableType(parseType()), pos); - } + // Here are cases where we'll pick the unknown type: + // + // Foo(?, + // { a: ? } + // Foo(?) + // Foo + // Foo(?= + // (?| + if (token() === SyntaxKind.CommaToken || + token() === SyntaxKind.CloseBraceToken || + token() === SyntaxKind.CloseParenToken || + token() === SyntaxKind.GreaterThanToken || + token() === SyntaxKind.EqualsToken || + token() === SyntaxKind.BarToken) { + return finishNode(factory.createJSDocUnknownType(), pos); + } + else { + return finishNode(factory.createJSDocNullableType(parseType()), pos); } + } - function parseJSDocFunctionType(): JSDocFunctionType | TypeReferenceNode { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - if (lookAhead(nextTokenIsOpenParen)) { - nextToken(); - const parameters = parseParameters(SignatureFlags.Type | SignatureFlags.JSDoc); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - return withJSDoc(finishNode(factory.createJSDocFunctionType(parameters, type), pos), hasJSDoc); - } - return finishNode(factory.createTypeReferenceNode(parseIdentifierName(), /*typeArguments*/ undefined), pos); + function parseJSDocFunctionType(): JSDocFunctionType | TypeReferenceNode { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + if (lookAhead(nextTokenIsOpenParen)) { + nextToken(); + const parameters = parseParameters(SignatureFlags.Type | SignatureFlags.JSDoc); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + return withJSDoc(finishNode(factory.createJSDocFunctionType(parameters, type), pos), hasJSDoc); } + return finishNode(factory.createTypeReferenceNode(parseIdentifierName(), /*typeArguments*/ undefined), pos); + } - function parseJSDocParameter(): ParameterDeclaration { - const pos = getNodePos(); - let name: Identifier | undefined; - if (token() === SyntaxKind.ThisKeyword || token() === SyntaxKind.NewKeyword) { - name = parseIdentifierName(); - parseExpected(SyntaxKind.ColonToken); - } - return finishNode( - factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - // TODO(rbuckton): JSDoc parameters don't have names (except `this`/`new`), should we manufacture an empty identifier? - name!, - /*questionToken*/ undefined, - parseJSDocType(), - /*initializer*/ undefined - ), - pos - ); + function parseJSDocParameter(): ParameterDeclaration { + const pos = getNodePos(); + let name: Identifier | undefined; + if (token() === SyntaxKind.ThisKeyword || token() === SyntaxKind.NewKeyword) { + name = parseIdentifierName(); + parseExpected(SyntaxKind.ColonToken); } + return finishNode( + factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + // TODO(rbuckton): JSDoc parameters don't have names (except `this`/`new`), should we manufacture an empty identifier? + name!, + /*questionToken*/ undefined, + parseJSDocType(), + /*initializer*/ undefined + ), + pos + ); + } - function parseJSDocType(): TypeNode { - scanner.setInJSDocType(true); - const pos = getNodePos(); - if (parseOptional(SyntaxKind.ModuleKeyword)) { - // TODO(rbuckton): We never set the type for a JSDocNamepathType. What should we put here? - const moduleTag = factory.createJSDocNamepathType(/*type*/ undefined!); - terminate: while (true) { - switch (token()) { - case SyntaxKind.CloseBraceToken: - case SyntaxKind.EndOfFileToken: - case SyntaxKind.CommaToken: - case SyntaxKind.WhitespaceTrivia: - break terminate; - default: - nextTokenJSDoc(); - } + function parseJSDocType(): TypeNode { + scanner.setInJSDocType(true); + const pos = getNodePos(); + if (parseOptional(SyntaxKind.ModuleKeyword)) { + // TODO(rbuckton): We never set the type for a JSDocNamepathType. What should we put here? + const moduleTag = factory.createJSDocNamepathType(/*type*/ undefined!); + terminate: while (true) { + switch (token()) { + case SyntaxKind.CloseBraceToken: + case SyntaxKind.EndOfFileToken: + case SyntaxKind.CommaToken: + case SyntaxKind.WhitespaceTrivia: + break terminate; + default: + nextTokenJSDoc(); } - - scanner.setInJSDocType(false); - return finishNode(moduleTag, pos); } - const hasDotDotDot = parseOptional(SyntaxKind.DotDotDotToken); - let type = parseTypeOrTypePredicate(); scanner.setInJSDocType(false); - if (hasDotDotDot) { - type = finishNode(factory.createJSDocVariadicType(type), pos); - } - if (token() === SyntaxKind.EqualsToken) { - nextToken(); - return finishNode(factory.createJSDocOptionalType(type), pos); - } - return type; + return finishNode(moduleTag, pos); } - function parseTypeQuery(): TypeQueryNode { - const pos = getNodePos(); - parseExpected(SyntaxKind.TypeOfKeyword); - return finishNode(factory.createTypeQueryNode(parseEntityName(/*allowReservedWords*/ true)), pos); + const hasDotDotDot = parseOptional(SyntaxKind.DotDotDotToken); + let type = parseTypeOrTypePredicate(); + scanner.setInJSDocType(false); + if (hasDotDotDot) { + type = finishNode(factory.createJSDocVariadicType(type), pos); } - - function parseTypeParameter(): TypeParameterDeclaration { - const pos = getNodePos(); - const name = parseIdentifier(); - let constraint: TypeNode | undefined; - let expression: Expression | undefined; - if (parseOptional(SyntaxKind.ExtendsKeyword)) { - // It's not uncommon for people to write improper constraints to a generic. If the - // user writes a constraint that is an expression and not an actual type, then parse - // it out as an expression (so we can recover well), but report that a type is needed - // instead. - if (isStartOfType() || !isStartOfExpression()) { - constraint = parseType(); - } - else { - // It was not a type, and it looked like an expression. Parse out an expression - // here so we recover well. Note: it is important that we call parseUnaryExpression - // and not parseExpression here. If the user has: - // - // - // - // We do *not* want to consume the `>` as we're consuming the expression for "". - expression = parseUnaryExpressionOrHigher(); - } - } - - const defaultType = parseOptional(SyntaxKind.EqualsToken) ? parseType() : undefined; - const node = factory.createTypeParameterDeclaration(name, constraint, defaultType); - node.expression = expression; - return finishNode(node, pos); + if (token() === SyntaxKind.EqualsToken) { + nextToken(); + return finishNode(factory.createJSDocOptionalType(type), pos); } + return type; + } - function parseTypeParameters(): NodeArray | undefined { - if (token() === SyntaxKind.LessThanToken) { - return parseBracketedList(ParsingContext.TypeParameters, parseTypeParameter, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); - } - } - - function isStartOfParameter(isJSDocParameter: boolean): boolean { - return token() === SyntaxKind.DotDotDotToken || - isBindingIdentifierOrPrivateIdentifierOrPattern() || - isModifierKind(token()) || - token() === SyntaxKind.AtToken || - isStartOfType(/*inStartOfParameter*/ !isJSDocParameter); - } - - function parseNameOfParameter(modifiers: ModifiersArray | undefined) { - // FormalParameter [Yield,Await]: - // BindingElement[?Yield,?Await] - const name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_cannot_be_used_as_parameters); - if (getFullWidth(name) === 0 && !some(modifiers) && isModifierKind(token())) { - // in cases like - // 'use strict' - // function foo(static) - // isParameter('static') === true, because of isModifier('static') - // however 'static' is not a legal identifier in a strict mode. - // so result of this function will be ParameterDeclaration (flags = 0, name = missing, type = undefined, initializer = undefined) - // and current token will not change => parsing of the enclosing parameter list will last till the end of time (or OOM) - // to avoid this we'll advance cursor to the next token. - nextToken(); + function parseTypeQuery(): TypeQueryNode { + const pos = getNodePos(); + parseExpected(SyntaxKind.TypeOfKeyword); + return finishNode(factory.createTypeQueryNode(parseEntityName(/*allowReservedWords*/ true)), pos); + } + + function parseTypeParameter(): TypeParameterDeclaration { + const pos = getNodePos(); + const name = parseIdentifier(); + let constraint: TypeNode | undefined; + let expression: Expression | undefined; + if (parseOptional(SyntaxKind.ExtendsKeyword)) { + // It's not uncommon for people to write improper constraints to a generic. If the + // user writes a constraint that is an expression and not an actual type, then parse + // it out as an expression (so we can recover well), but report that a type is needed + // instead. + if (isStartOfType() || !isStartOfExpression()) { + constraint = parseType(); + } + else { + // It was not a type, and it looked like an expression. Parse out an expression + // here so we recover well. Note: it is important that we call parseUnaryExpression + // and not parseExpression here. If the user has: + // + // + // + // We do *not* want to consume the `>` as we're consuming the expression for "". + expression = parseUnaryExpressionOrHigher(); } - return name; } - function parseParameterInOuterAwaitContext() { - return parseParameterWorker(/*inOuterAwaitContext*/ true); - } + const defaultType = parseOptional(SyntaxKind.EqualsToken) ? parseType() : undefined; + const node = factory.createTypeParameterDeclaration(name, constraint, defaultType); + node.expression = expression; + return finishNode(node, pos); + } - function parseParameter(): ParameterDeclaration { - return parseParameterWorker(/*inOuterAwaitContext*/ false); + function parseTypeParameters(): NodeArray | undefined { + if (token() === SyntaxKind.LessThanToken) { + return parseBracketedList(ParsingContext.TypeParameters, parseTypeParameter, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); } + } - function parseParameterWorker(inOuterAwaitContext: boolean): ParameterDeclaration { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); + function isStartOfParameter(isJSDocParameter: boolean): boolean { + return token() === SyntaxKind.DotDotDotToken || + isBindingIdentifierOrPrivateIdentifierOrPattern() || + isModifierKind(token()) || + token() === SyntaxKind.AtToken || + isStartOfType(/*inStartOfParameter*/ !isJSDocParameter); + } - // FormalParameter [Yield,Await]: - // BindingElement[?Yield,?Await] + function parseNameOfParameter(modifiers: ModifiersArray | undefined) { + // FormalParameter [Yield,Await]: + // BindingElement[?Yield,?Await] + const name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_cannot_be_used_as_parameters); + if (getFullWidth(name) === 0 && !some(modifiers) && isModifierKind(token())) { + // in cases like + // 'use strict' + // function foo(static) + // isParameter('static') === true, because of isModifier('static') + // however 'static' is not a legal identifier in a strict mode. + // so result of this function will be ParameterDeclaration (flags = 0, name = missing, type = undefined, initializer = undefined) + // and current token will not change => parsing of the enclosing parameter list will last till the end of time (or OOM) + // to avoid this we'll advance cursor to the next token. + nextToken(); + } + return name; + } - // Decorators are parsed in the outer [Await] context, the rest of the parameter is parsed in the function's [Await] context. - const decorators = inOuterAwaitContext ? doInAwaitContext(parseDecorators) : parseDecorators(); + function parseParameterInOuterAwaitContext() { + return parseParameterWorker(/*inOuterAwaitContext*/ true); + } - if (token() === SyntaxKind.ThisKeyword) { - const node = factory.createParameterDeclaration( - decorators, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - createIdentifier(/*isIdentifier*/ true), - /*questionToken*/ undefined, - parseTypeAnnotation(), - /*initializer*/ undefined - ); + function parseParameter(): ParameterDeclaration { + return parseParameterWorker(/*inOuterAwaitContext*/ false); + } - if (decorators) { - parseErrorAtRange(decorators[0], Diagnostics.Decorators_may_not_be_applied_to_this_parameters); - } + function parseParameterWorker(inOuterAwaitContext: boolean): ParameterDeclaration { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); - return withJSDoc(finishNode(node, pos), hasJSDoc); - } + // FormalParameter [Yield,Await]: + // BindingElement[?Yield,?Await] - const savedTopLevel = topLevel; - topLevel = false; - const modifiers = parseModifiers(); - const node = withJSDoc( - finishNode( - factory.createParameterDeclaration( - decorators, - modifiers, - parseOptionalToken(SyntaxKind.DotDotDotToken), - parseNameOfParameter(modifiers), - parseOptionalToken(SyntaxKind.QuestionToken), - parseTypeAnnotation(), - parseInitializer() - ), - pos - ), - hasJSDoc + // Decorators are parsed in the outer [Await] context, the rest of the parameter is parsed in the function's [Await] context. + const decorators = inOuterAwaitContext ? doInAwaitContext(parseDecorators) : parseDecorators(); + + if (token() === SyntaxKind.ThisKeyword) { + const node = factory.createParameterDeclaration( + decorators, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + createIdentifier(/*isIdentifier*/ true), + /*questionToken*/ undefined, + parseTypeAnnotation(), + /*initializer*/ undefined ); - topLevel = savedTopLevel; - return node; - } - function parseReturnType(returnToken: SyntaxKind.EqualsGreaterThanToken, isType: boolean): TypeNode; - function parseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean): TypeNode | undefined; - function parseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean) { - if (shouldParseReturnType(returnToken, isType)) { - return parseTypeOrTypePredicate(); + if (decorators) { + parseErrorAtRange(decorators[0], Diagnostics.Decorators_may_not_be_applied_to_this_parameters); } + + return withJSDoc(finishNode(node, pos), hasJSDoc); } - function shouldParseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean): boolean { - if (returnToken === SyntaxKind.EqualsGreaterThanToken) { - parseExpected(returnToken); - return true; - } - else if (parseOptional(SyntaxKind.ColonToken)) { - return true; - } - else if (isType && token() === SyntaxKind.EqualsGreaterThanToken) { - // This is easy to get backward, especially in type contexts, so parse the type anyway - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)); - nextToken(); - return true; - } - return false; + const savedTopLevel = topLevel; + topLevel = false; + const modifiers = parseModifiers(); + const node = withJSDoc( + finishNode( + factory.createParameterDeclaration( + decorators, + modifiers, + parseOptionalToken(SyntaxKind.DotDotDotToken), + parseNameOfParameter(modifiers), + parseOptionalToken(SyntaxKind.QuestionToken), + parseTypeAnnotation(), + parseInitializer() + ), + pos + ), + hasJSDoc + ); + topLevel = savedTopLevel; + return node; + } + + function parseReturnType(returnToken: SyntaxKind.EqualsGreaterThanToken, isType: boolean): TypeNode; + function parseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean): TypeNode | undefined; + function parseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean) { + if (shouldParseReturnType(returnToken, isType)) { + return parseTypeOrTypePredicate(); } + } - function parseParametersWorker(flags: SignatureFlags) { - // FormalParameters [Yield,Await]: (modified) - // [empty] - // FormalParameterList[?Yield,Await] - // - // FormalParameter[Yield,Await]: (modified) - // BindingElement[?Yield,Await] - // - // BindingElement [Yield,Await]: (modified) - // SingleNameBinding[?Yield,?Await] - // BindingPattern[?Yield,?Await]Initializer [In, ?Yield,?Await] opt - // - // SingleNameBinding [Yield,Await]: - // BindingIdentifier[?Yield,?Await]Initializer [In, ?Yield,?Await] opt - const savedYieldContext = inYieldContext(); - const savedAwaitContext = inAwaitContext(); + function shouldParseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean): boolean { + if (returnToken === SyntaxKind.EqualsGreaterThanToken) { + parseExpected(returnToken); + return true; + } + else if (parseOptional(SyntaxKind.ColonToken)) { + return true; + } + else if (isType && token() === SyntaxKind.EqualsGreaterThanToken) { + // This is easy to get backward, especially in type contexts, so parse the type anyway + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)); + nextToken(); + return true; + } + return false; + } - setYieldContext(!!(flags & SignatureFlags.Yield)); - setAwaitContext(!!(flags & SignatureFlags.Await)); + function parseParametersWorker(flags: SignatureFlags) { + // FormalParameters [Yield,Await]: (modified) + // [empty] + // FormalParameterList[?Yield,Await] + // + // FormalParameter[Yield,Await]: (modified) + // BindingElement[?Yield,Await] + // + // BindingElement [Yield,Await]: (modified) + // SingleNameBinding[?Yield,?Await] + // BindingPattern[?Yield,?Await]Initializer [In, ?Yield,?Await] opt + // + // SingleNameBinding [Yield,Await]: + // BindingIdentifier[?Yield,?Await]Initializer [In, ?Yield,?Await] opt + const savedYieldContext = inYieldContext(); + const savedAwaitContext = inAwaitContext(); - const parameters = flags & SignatureFlags.JSDoc ? - parseDelimitedList(ParsingContext.JSDocParameters, parseJSDocParameter) : - parseDelimitedList(ParsingContext.Parameters, savedAwaitContext ? parseParameterInOuterAwaitContext : parseParameter); + setYieldContext(!!(flags & SignatureFlags.Yield)); + setAwaitContext(!!(flags & SignatureFlags.Await)); - setYieldContext(savedYieldContext); - setAwaitContext(savedAwaitContext); + const parameters = flags & SignatureFlags.JSDoc ? + parseDelimitedList(ParsingContext.JSDocParameters, parseJSDocParameter) : + parseDelimitedList(ParsingContext.Parameters, savedAwaitContext ? parseParameterInOuterAwaitContext : parseParameter); - return parameters; - } + setYieldContext(savedYieldContext); + setAwaitContext(savedAwaitContext); - function parseParameters(flags: SignatureFlags): NodeArray { - // FormalParameters [Yield,Await]: (modified) - // [empty] - // FormalParameterList[?Yield,Await] - // - // FormalParameter[Yield,Await]: (modified) - // BindingElement[?Yield,Await] - // - // BindingElement [Yield,Await]: (modified) - // SingleNameBinding[?Yield,?Await] - // BindingPattern[?Yield,?Await]Initializer [In, ?Yield,?Await] opt - // - // SingleNameBinding [Yield,Await]: - // BindingIdentifier[?Yield,?Await]Initializer [In, ?Yield,?Await] opt - if (!parseExpected(SyntaxKind.OpenParenToken)) { - return createMissingList(); - } + return parameters; + } - const parameters = parseParametersWorker(flags); - parseExpected(SyntaxKind.CloseParenToken); - return parameters; + function parseParameters(flags: SignatureFlags): NodeArray { + // FormalParameters [Yield,Await]: (modified) + // [empty] + // FormalParameterList[?Yield,Await] + // + // FormalParameter[Yield,Await]: (modified) + // BindingElement[?Yield,Await] + // + // BindingElement [Yield,Await]: (modified) + // SingleNameBinding[?Yield,?Await] + // BindingPattern[?Yield,?Await]Initializer [In, ?Yield,?Await] opt + // + // SingleNameBinding [Yield,Await]: + // BindingIdentifier[?Yield,?Await]Initializer [In, ?Yield,?Await] opt + if (!parseExpected(SyntaxKind.OpenParenToken)) { + return createMissingList(); } - function parseTypeMemberSemicolon() { - // We allow type members to be separated by commas or (possibly ASI) semicolons. - // First check if it was a comma. If so, we're done with the member. - if (parseOptional(SyntaxKind.CommaToken)) { - return; - } + const parameters = parseParametersWorker(flags); + parseExpected(SyntaxKind.CloseParenToken); + return parameters; + } - // Didn't have a comma. We must have a (possible ASI) semicolon. - parseSemicolon(); + function parseTypeMemberSemicolon() { + // We allow type members to be separated by commas or (possibly ASI) semicolons. + // First check if it was a comma. If so, we're done with the member. + if (parseOptional(SyntaxKind.CommaToken)) { + return; } - function parseSignatureMember(kind: SyntaxKind.CallSignature | SyntaxKind.ConstructSignature): CallSignatureDeclaration | ConstructSignatureDeclaration { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - if (kind === SyntaxKind.ConstructSignature) { - parseExpected(SyntaxKind.NewKeyword); - } + // Didn't have a comma. We must have a (possible ASI) semicolon. + parseSemicolon(); + } - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(SignatureFlags.Type); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ true); - parseTypeMemberSemicolon(); - const node = kind === SyntaxKind.CallSignature - ? factory.createCallSignature(typeParameters, parameters, type) - : factory.createConstructSignature(typeParameters, parameters, type); - return withJSDoc(finishNode(node, pos), hasJSDoc); + function parseSignatureMember(kind: SyntaxKind.CallSignature | SyntaxKind.ConstructSignature): CallSignatureDeclaration | ConstructSignatureDeclaration { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + if (kind === SyntaxKind.ConstructSignature) { + parseExpected(SyntaxKind.NewKeyword); } - function isIndexSignature(): boolean { - return token() === SyntaxKind.OpenBracketToken && lookAhead(isUnambiguouslyIndexSignature); - } + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(SignatureFlags.Type); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ true); + parseTypeMemberSemicolon(); + const node = kind === SyntaxKind.CallSignature + ? factory.createCallSignature(typeParameters, parameters, type) + : factory.createConstructSignature(typeParameters, parameters, type); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function isUnambiguouslyIndexSignature() { - // The only allowed sequence is: - // - // [id: - // - // However, for error recovery, we also check the following cases: - // - // [... - // [id, - // [id?, - // [id?: - // [id?] - // [public id - // [private id - // [protected id - // [] - // - nextToken(); - if (token() === SyntaxKind.DotDotDotToken || token() === SyntaxKind.CloseBracketToken) { - return true; - } + function isIndexSignature(): boolean { + return token() === SyntaxKind.OpenBracketToken && lookAhead(isUnambiguouslyIndexSignature); + } - if (isModifierKind(token())) { - nextToken(); - if (isIdentifier()) { - return true; - } - } - else if (!isIdentifier()) { - return false; - } - else { - // Skip the identifier - nextToken(); - } + function isUnambiguouslyIndexSignature() { + // The only allowed sequence is: + // + // [id: + // + // However, for error recovery, we also check the following cases: + // + // [... + // [id, + // [id?, + // [id?: + // [id?] + // [public id + // [private id + // [protected id + // [] + // + nextToken(); + if (token() === SyntaxKind.DotDotDotToken || token() === SyntaxKind.CloseBracketToken) { + return true; + } - // A colon signifies a well formed indexer - // A comma should be a badly formed indexer because comma expressions are not allowed - // in computed properties. - if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken) { + if (isModifierKind(token())) { + nextToken(); + if (isIdentifier()) { return true; } - - // Question mark could be an indexer with an optional property, - // or it could be a conditional expression in a computed property. - if (token() !== SyntaxKind.QuestionToken) { - return false; - } - - // If any of the following tokens are after the question mark, it cannot - // be a conditional expression, so treat it as an indexer. - nextToken(); - return token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || token() === SyntaxKind.CloseBracketToken; } - - function parseIndexSignatureDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): IndexSignatureDeclaration { - const parameters = parseBracketedList(ParsingContext.Parameters, parseParameter, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); - const type = parseTypeAnnotation(); - parseTypeMemberSemicolon(); - const node = factory.createIndexSignature(decorators, modifiers, parameters, type); - return withJSDoc(finishNode(node, pos), hasJSDoc); + else if (!isIdentifier()) { + return false; + } + else { + // Skip the identifier + nextToken(); } - function parsePropertyOrMethodSignature(pos: number, hasJSDoc: boolean, modifiers: NodeArray | undefined): PropertySignature | MethodSignature { - const name = parsePropertyName(); - const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - let node: PropertySignature | MethodSignature; - if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { - // Method signatures don't exist in expression contexts. So they have neither - // [Yield] nor [Await] - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(SignatureFlags.Type); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ true); - node = factory.createMethodSignature(modifiers, name, questionToken, typeParameters, parameters, type); - } - else { - const type = parseTypeAnnotation(); - node = factory.createPropertySignature(modifiers, name, questionToken, type); - // Although type literal properties cannot not have initializers, we attempt - // to parse an initializer so we can report in the checker that an interface - // property or type literal property cannot have an initializer. - if (token() === SyntaxKind.EqualsToken) node.initializer = parseInitializer(); - } - parseTypeMemberSemicolon(); - return withJSDoc(finishNode(node, pos), hasJSDoc); + // A colon signifies a well formed indexer + // A comma should be a badly formed indexer because comma expressions are not allowed + // in computed properties. + if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken) { + return true; } - function isTypeMemberStart(): boolean { - // Return true if we have the start of a signature member - if (token() === SyntaxKind.OpenParenToken || - token() === SyntaxKind.LessThanToken || - token() === SyntaxKind.GetKeyword || - token() === SyntaxKind.SetKeyword) { - return true; - } - let idToken = false; - // Eat up all modifiers, but hold on to the last one in case it is actually an identifier - while (isModifierKind(token())) { - idToken = true; - nextToken(); - } - // Index signatures and computed property names are type members - if (token() === SyntaxKind.OpenBracketToken) { - return true; - } - // Try to get the first property-like token following all modifiers - if (isLiteralPropertyName()) { - idToken = true; - nextToken(); - } - // If we were able to get any potential identifier, check that it is - // the start of a member declaration - if (idToken) { - return token() === SyntaxKind.OpenParenToken || - token() === SyntaxKind.LessThanToken || - token() === SyntaxKind.QuestionToken || - token() === SyntaxKind.ColonToken || - token() === SyntaxKind.CommaToken || - canParseSemicolon(); - } + // Question mark could be an indexer with an optional property, + // or it could be a conditional expression in a computed property. + if (token() !== SyntaxKind.QuestionToken) { return false; } - function parseTypeMember(): TypeElement { - if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { - return parseSignatureMember(SyntaxKind.CallSignature); - } - if (token() === SyntaxKind.NewKeyword && lookAhead(nextTokenIsOpenParenOrLessThan)) { - return parseSignatureMember(SyntaxKind.ConstructSignature); - } - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const modifiers = parseModifiers(); - if (parseContextualModifier(SyntaxKind.GetKeyword)) { - return parseAccessorDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers, SyntaxKind.GetAccessor); - } + // If any of the following tokens are after the question mark, it cannot + // be a conditional expression, so treat it as an indexer. + nextToken(); + return token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || token() === SyntaxKind.CloseBracketToken; + } - if (parseContextualModifier(SyntaxKind.SetKeyword)) { - return parseAccessorDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers, SyntaxKind.SetAccessor); - } + function parseIndexSignatureDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): IndexSignatureDeclaration { + const parameters = parseBracketedList(ParsingContext.Parameters, parseParameter, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); + const type = parseTypeAnnotation(); + parseTypeMemberSemicolon(); + const node = factory.createIndexSignature(decorators, modifiers, parameters, type); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - if (isIndexSignature()) { - return parseIndexSignatureDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers); - } - return parsePropertyOrMethodSignature(pos, hasJSDoc, modifiers); + function parsePropertyOrMethodSignature(pos: number, hasJSDoc: boolean, modifiers: NodeArray | undefined): PropertySignature | MethodSignature { + const name = parsePropertyName(); + const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + let node: PropertySignature | MethodSignature; + if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { + // Method signatures don't exist in expression contexts. So they have neither + // [Yield] nor [Await] + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(SignatureFlags.Type); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ true); + node = factory.createMethodSignature(modifiers, name, questionToken, typeParameters, parameters, type); } + else { + const type = parseTypeAnnotation(); + node = factory.createPropertySignature(modifiers, name, questionToken, type); + // Although type literal properties cannot not have initializers, we attempt + // to parse an initializer so we can report in the checker that an interface + // property or type literal property cannot have an initializer. + if (token() === SyntaxKind.EqualsToken) node.initializer = parseInitializer(); + } + parseTypeMemberSemicolon(); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function nextTokenIsOpenParenOrLessThan() { + function isTypeMemberStart(): boolean { + // Return true if we have the start of a signature member + if (token() === SyntaxKind.OpenParenToken || + token() === SyntaxKind.LessThanToken || + token() === SyntaxKind.GetKeyword || + token() === SyntaxKind.SetKeyword) { + return true; + } + let idToken = false; + // Eat up all modifiers, but hold on to the last one in case it is actually an identifier + while (isModifierKind(token())) { + idToken = true; + nextToken(); + } + // Index signatures and computed property names are type members + if (token() === SyntaxKind.OpenBracketToken) { + return true; + } + // Try to get the first property-like token following all modifiers + if (isLiteralPropertyName()) { + idToken = true; nextToken(); - return token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken; } + // If we were able to get any potential identifier, check that it is + // the start of a member declaration + if (idToken) { + return token() === SyntaxKind.OpenParenToken || + token() === SyntaxKind.LessThanToken || + token() === SyntaxKind.QuestionToken || + token() === SyntaxKind.ColonToken || + token() === SyntaxKind.CommaToken || + canParseSemicolon(); + } + return false; + } - function nextTokenIsDot() { - return nextToken() === SyntaxKind.DotToken; + function parseTypeMember(): TypeElement { + if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { + return parseSignatureMember(SyntaxKind.CallSignature); + } + if (token() === SyntaxKind.NewKeyword && lookAhead(nextTokenIsOpenParenOrLessThan)) { + return parseSignatureMember(SyntaxKind.ConstructSignature); + } + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const modifiers = parseModifiers(); + if (parseContextualModifier(SyntaxKind.GetKeyword)) { + return parseAccessorDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers, SyntaxKind.GetAccessor); } - function nextTokenIsOpenParenOrLessThanOrDot() { - switch (nextToken()) { - case SyntaxKind.OpenParenToken: - case SyntaxKind.LessThanToken: - case SyntaxKind.DotToken: - return true; - } - return false; + if (parseContextualModifier(SyntaxKind.SetKeyword)) { + return parseAccessorDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers, SyntaxKind.SetAccessor); } - function parseTypeLiteral(): TypeLiteralNode { - const pos = getNodePos(); - return finishNode(factory.createTypeLiteralNode(parseObjectTypeMembers()), pos); + if (isIndexSignature()) { + return parseIndexSignatureDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers); } + return parsePropertyOrMethodSignature(pos, hasJSDoc, modifiers); + } - function parseObjectTypeMembers(): NodeArray { - let members: NodeArray; - if (parseExpected(SyntaxKind.OpenBraceToken)) { - members = parseList(ParsingContext.TypeMembers, parseTypeMember); - parseExpected(SyntaxKind.CloseBraceToken); - } - else { - members = createMissingList(); - } + function nextTokenIsOpenParenOrLessThan() { + nextToken(); + return token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken; + } - return members; - } + function nextTokenIsDot() { + return nextToken() === SyntaxKind.DotToken; + } - function isStartOfMappedType() { - nextToken(); - if (token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { - return nextToken() === SyntaxKind.ReadonlyKeyword; - } - if (token() === SyntaxKind.ReadonlyKeyword) { - nextToken(); - } - return token() === SyntaxKind.OpenBracketToken && nextTokenIsIdentifier() && nextToken() === SyntaxKind.InKeyword; + function nextTokenIsOpenParenOrLessThanOrDot() { + switch (nextToken()) { + case SyntaxKind.OpenParenToken: + case SyntaxKind.LessThanToken: + case SyntaxKind.DotToken: + return true; } + return false; + } - function parseMappedTypeParameter() { - const pos = getNodePos(); - const name = parseIdentifierName(); - parseExpected(SyntaxKind.InKeyword); - const type = parseType(); - return finishNode(factory.createTypeParameterDeclaration(name, type, /*defaultType*/ undefined), pos); - } + function parseTypeLiteral(): TypeLiteralNode { + const pos = getNodePos(); + return finishNode(factory.createTypeLiteralNode(parseObjectTypeMembers()), pos); + } - function parseMappedType() { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBraceToken); - let readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined; - if (token() === SyntaxKind.ReadonlyKeyword || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { - readonlyToken = parseTokenNode(); - if (readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { - parseExpected(SyntaxKind.ReadonlyKeyword); - } - } - parseExpected(SyntaxKind.OpenBracketToken); - const typeParameter = parseMappedTypeParameter(); - const nameType = parseOptional(SyntaxKind.AsKeyword) ? parseType() : undefined; - parseExpected(SyntaxKind.CloseBracketToken); - let questionToken: QuestionToken | PlusToken | MinusToken | undefined; - if (token() === SyntaxKind.QuestionToken || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { - questionToken = parseTokenNode(); - if (questionToken.kind !== SyntaxKind.QuestionToken) { - parseExpected(SyntaxKind.QuestionToken); - } - } - const type = parseTypeAnnotation(); - parseSemicolon(); - const members = parseList(ParsingContext.TypeMembers, parseTypeMember); + function parseObjectTypeMembers(): NodeArray { + let members: NodeArray; + if (parseExpected(SyntaxKind.OpenBraceToken)) { + members = parseList(ParsingContext.TypeMembers, parseTypeMember); parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), pos); } - - function parseTupleElementType() { - const pos = getNodePos(); - if (parseOptional(SyntaxKind.DotDotDotToken)) { - return finishNode(factory.createRestTypeNode(parseType()), pos); - } - const type = parseType(); - if (isJSDocNullableType(type) && type.pos === type.type.pos) { - const node = factory.createOptionalTypeNode(type.type); - setTextRange(node, type); - (node as Mutable).flags = type.flags; - return node; - } - return type; + else { + members = createMissingList(); } - function isNextTokenColonOrQuestionColon() { - return nextToken() === SyntaxKind.ColonToken || (token() === SyntaxKind.QuestionToken && nextToken() === SyntaxKind.ColonToken); - } + return members; + } - function isTupleElementName() { - if (token() === SyntaxKind.DotDotDotToken) { - return tokenIsIdentifierOrKeyword(nextToken()) && isNextTokenColonOrQuestionColon(); - } - return tokenIsIdentifierOrKeyword(token()) && isNextTokenColonOrQuestionColon(); + function isStartOfMappedType() { + nextToken(); + if (token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { + return nextToken() === SyntaxKind.ReadonlyKeyword; } + if (token() === SyntaxKind.ReadonlyKeyword) { + nextToken(); + } + return token() === SyntaxKind.OpenBracketToken && nextTokenIsIdentifier() && nextToken() === SyntaxKind.InKeyword; + } - function parseTupleElementNameOrTupleElementType() { - if (lookAhead(isTupleElementName)) { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - const name = parseIdentifierName(); - const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - parseExpected(SyntaxKind.ColonToken); - const type = parseTupleElementType(); - const node = factory.createNamedTupleMember(dotDotDotToken, name, questionToken, type); - return withJSDoc(finishNode(node, pos), hasJSDoc); + function parseMappedTypeParameter() { + const pos = getNodePos(); + const name = parseIdentifierName(); + parseExpected(SyntaxKind.InKeyword); + const type = parseType(); + return finishNode(factory.createTypeParameterDeclaration(name, type, /*defaultType*/ undefined), pos); + } + + function parseMappedType() { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBraceToken); + let readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined; + if (token() === SyntaxKind.ReadonlyKeyword || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { + readonlyToken = parseTokenNode(); + if (readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { + parseExpected(SyntaxKind.ReadonlyKeyword); + } + } + parseExpected(SyntaxKind.OpenBracketToken); + const typeParameter = parseMappedTypeParameter(); + const nameType = parseOptional(SyntaxKind.AsKeyword) ? parseType() : undefined; + parseExpected(SyntaxKind.CloseBracketToken); + let questionToken: QuestionToken | PlusToken | MinusToken | undefined; + if (token() === SyntaxKind.QuestionToken || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { + questionToken = parseTokenNode(); + if (questionToken.kind !== SyntaxKind.QuestionToken) { + parseExpected(SyntaxKind.QuestionToken); } - return parseTupleElementType(); } + const type = parseTypeAnnotation(); + parseSemicolon(); + const members = parseList(ParsingContext.TypeMembers, parseTypeMember); + parseExpected(SyntaxKind.CloseBraceToken); + return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), pos); + } - function parseTupleType(): TupleTypeNode { - const pos = getNodePos(); - return finishNode( - factory.createTupleTypeNode( - parseBracketedList(ParsingContext.TupleElementTypes, parseTupleElementNameOrTupleElementType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken) - ), - pos - ); + function parseTupleElementType() { + const pos = getNodePos(); + if (parseOptional(SyntaxKind.DotDotDotToken)) { + return finishNode(factory.createRestTypeNode(parseType()), pos); } - - function parseParenthesizedType(): TypeNode { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenParenToken); - const type = parseType(); - parseExpected(SyntaxKind.CloseParenToken); - return finishNode(factory.createParenthesizedType(type), pos); + const type = parseType(); + if (isJSDocNullableType(type) && type.pos === type.type.pos) { + const node = factory.createOptionalTypeNode(type.type); + setTextRange(node, type); + (node as Mutable).flags = type.flags; + return node; } + return type; + } - function parseModifiersForConstructorType(): NodeArray | undefined { - let modifiers: NodeArray | undefined; - if (token() === SyntaxKind.AbstractKeyword) { - const pos = getNodePos(); - nextToken(); - const modifier = finishNode(factory.createToken(SyntaxKind.AbstractKeyword), pos); - modifiers = createNodeArray([modifier], pos); - } - return modifiers; + function isNextTokenColonOrQuestionColon() { + return nextToken() === SyntaxKind.ColonToken || (token() === SyntaxKind.QuestionToken && nextToken() === SyntaxKind.ColonToken); + } + + function isTupleElementName() { + if (token() === SyntaxKind.DotDotDotToken) { + return tokenIsIdentifierOrKeyword(nextToken()) && isNextTokenColonOrQuestionColon(); } + return tokenIsIdentifierOrKeyword(token()) && isNextTokenColonOrQuestionColon(); + } - function parseFunctionOrConstructorType(): TypeNode { + function parseTupleElementNameOrTupleElementType() { + if (lookAhead(isTupleElementName)) { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); - const modifiers = parseModifiersForConstructorType(); - const isConstructorType = parseOptional(SyntaxKind.NewKeyword); - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(SignatureFlags.Type); - const type = parseReturnType(SyntaxKind.EqualsGreaterThanToken, /*isType*/ false); - const node = isConstructorType - ? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, type) - : factory.createFunctionTypeNode(typeParameters, parameters, type); - if (!isConstructorType) (node as Mutable).modifiers = modifiers; + const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + const name = parseIdentifierName(); + const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + parseExpected(SyntaxKind.ColonToken); + const type = parseTupleElementType(); + const node = factory.createNamedTupleMember(dotDotDotToken, name, questionToken, type); return withJSDoc(finishNode(node, pos), hasJSDoc); } + return parseTupleElementType(); + } - function parseKeywordAndNoDot(): TypeNode | undefined { - const node = parseTokenNode(); - return token() === SyntaxKind.DotToken ? undefined : node; - } + function parseTupleType(): TupleTypeNode { + const pos = getNodePos(); + return finishNode( + factory.createTupleTypeNode( + parseBracketedList(ParsingContext.TupleElementTypes, parseTupleElementNameOrTupleElementType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken) + ), + pos + ); + } - function parseLiteralTypeNode(negative?: boolean): LiteralTypeNode { - const pos = getNodePos(); - if (negative) { - nextToken(); - } - let expression: BooleanLiteral | NullLiteral | LiteralExpression | PrefixUnaryExpression = - token() === SyntaxKind.TrueKeyword || token() === SyntaxKind.FalseKeyword || token() === SyntaxKind.NullKeyword ? - parseTokenNode() : - parseLiteralLikeNode(token()) as LiteralExpression; - if (negative) { - expression = finishNode(factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, expression), pos); - } - return finishNode(factory.createLiteralTypeNode(expression), pos); - } + function parseParenthesizedType(): TypeNode { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenParenToken); + const type = parseType(); + parseExpected(SyntaxKind.CloseParenToken); + return finishNode(factory.createParenthesizedType(type), pos); + } - function isStartOfTypeOfImportType() { + function parseModifiersForConstructorType(): NodeArray | undefined { + let modifiers: NodeArray | undefined; + if (token() === SyntaxKind.AbstractKeyword) { + const pos = getNodePos(); nextToken(); - return token() === SyntaxKind.ImportKeyword; + const modifier = finishNode(factory.createToken(SyntaxKind.AbstractKeyword), pos); + modifiers = createNodeArray([modifier], pos); } + return modifiers; + } - function parseImportType(): ImportTypeNode { - sourceFlags |= NodeFlags.PossiblyContainsDynamicImport; - const pos = getNodePos(); - const isTypeOf = parseOptional(SyntaxKind.TypeOfKeyword); - parseExpected(SyntaxKind.ImportKeyword); - parseExpected(SyntaxKind.OpenParenToken); - const type = parseType(); - parseExpected(SyntaxKind.CloseParenToken); - const qualifier = parseOptional(SyntaxKind.DotToken) ? parseEntityNameOfTypeReference() : undefined; - const typeArguments = parseTypeArgumentsOfTypeReference(); - return finishNode(factory.createImportTypeNode(type, qualifier, typeArguments, isTypeOf), pos); - } + function parseFunctionOrConstructorType(): TypeNode { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const modifiers = parseModifiersForConstructorType(); + const isConstructorType = parseOptional(SyntaxKind.NewKeyword); + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(SignatureFlags.Type); + const type = parseReturnType(SyntaxKind.EqualsGreaterThanToken, /*isType*/ false); + const node = isConstructorType + ? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, type) + : factory.createFunctionTypeNode(typeParameters, parameters, type); + if (!isConstructorType) (node as Mutable).modifiers = modifiers; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } + + function parseKeywordAndNoDot(): TypeNode | undefined { + const node = parseTokenNode(); + return token() === SyntaxKind.DotToken ? undefined : node; + } - function nextTokenIsNumericOrBigIntLiteral() { + function parseLiteralTypeNode(negative?: boolean): LiteralTypeNode { + const pos = getNodePos(); + if (negative) { nextToken(); - return token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral; } + let expression: BooleanLiteral | NullLiteral | LiteralExpression | PrefixUnaryExpression = + token() === SyntaxKind.TrueKeyword || token() === SyntaxKind.FalseKeyword || token() === SyntaxKind.NullKeyword ? + parseTokenNode() : + parseLiteralLikeNode(token()) as LiteralExpression; + if (negative) { + expression = finishNode(factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, expression), pos); + } + return finishNode(factory.createLiteralTypeNode(expression), pos); + } - function parseNonArrayType(): TypeNode { - switch (token()) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.UnknownKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.UndefinedKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.ObjectKeyword: - // If these are followed by a dot, then parse these out as a dotted type reference instead. - return tryParse(parseKeywordAndNoDot) || parseTypeReference(); - case SyntaxKind.AsteriskEqualsToken: - // If there is '*=', treat it as * followed by postfix = - scanner.reScanAsteriskEqualsToken(); - // falls through - case SyntaxKind.AsteriskToken: - return parseJSDocAllType(); - case SyntaxKind.QuestionQuestionToken: - // If there is '??', treat it as prefix-'?' in JSDoc type. - scanner.reScanQuestionToken(); - // falls through - case SyntaxKind.QuestionToken: - return parseJSDocUnknownOrNullableType(); - case SyntaxKind.FunctionKeyword: - return parseJSDocFunctionType(); - case SyntaxKind.ExclamationToken: - return parseJSDocNonNullableType(); - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - return parseLiteralTypeNode(); - case SyntaxKind.MinusToken: - return lookAhead(nextTokenIsNumericOrBigIntLiteral) ? parseLiteralTypeNode(/*negative*/ true) : parseTypeReference(); - case SyntaxKind.VoidKeyword: - return parseTokenNode(); - case SyntaxKind.ThisKeyword: { - const thisKeyword = parseThisTypeNode(); - if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { - return parseThisTypePredicate(thisKeyword); - } - else { - return thisKeyword; - } - } - case SyntaxKind.TypeOfKeyword: - return lookAhead(isStartOfTypeOfImportType) ? parseImportType() : parseTypeQuery(); - case SyntaxKind.OpenBraceToken: - return lookAhead(isStartOfMappedType) ? parseMappedType() : parseTypeLiteral(); - case SyntaxKind.OpenBracketToken: - return parseTupleType(); - case SyntaxKind.OpenParenToken: - return parseParenthesizedType(); - case SyntaxKind.ImportKeyword: - return parseImportType(); - case SyntaxKind.AssertsKeyword: - return lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine) ? parseAssertsTypePredicate() : parseTypeReference(); - case SyntaxKind.TemplateHead: - return parseTemplateType(); - default: - return parseTypeReference(); - } - } + function isStartOfTypeOfImportType() { + nextToken(); + return token() === SyntaxKind.ImportKeyword; + } - function isStartOfType(inStartOfParameter?: boolean): boolean { - switch (token()) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.UnknownKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.ReadonlyKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.UniqueKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.UndefinedKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.ThisKeyword: - case SyntaxKind.TypeOfKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.OpenBraceToken: - case SyntaxKind.OpenBracketToken: - case SyntaxKind.LessThanToken: - case SyntaxKind.BarToken: - case SyntaxKind.AmpersandToken: - case SyntaxKind.NewKeyword: - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.AsteriskToken: - case SyntaxKind.QuestionToken: - case SyntaxKind.ExclamationToken: - case SyntaxKind.DotDotDotToken: - case SyntaxKind.InferKeyword: - case SyntaxKind.ImportKeyword: - case SyntaxKind.AssertsKeyword: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateHead: - return true; - case SyntaxKind.FunctionKeyword: - return !inStartOfParameter; - case SyntaxKind.MinusToken: - return !inStartOfParameter && lookAhead(nextTokenIsNumericOrBigIntLiteral); - case SyntaxKind.OpenParenToken: - // Only consider '(' the start of a type if followed by ')', '...', an identifier, a modifier, - // or something that starts a type. We don't want to consider things like '(1)' a type. - return !inStartOfParameter && lookAhead(isStartOfParenthesizedOrFunctionType); - default: - return isIdentifier(); - } + function parseImportType(): ImportTypeNode { + sourceFlags |= NodeFlags.PossiblyContainsDynamicImport; + const pos = getNodePos(); + const isTypeOf = parseOptional(SyntaxKind.TypeOfKeyword); + parseExpected(SyntaxKind.ImportKeyword); + parseExpected(SyntaxKind.OpenParenToken); + const type = parseType(); + parseExpected(SyntaxKind.CloseParenToken); + const qualifier = parseOptional(SyntaxKind.DotToken) ? parseEntityNameOfTypeReference() : undefined; + const typeArguments = parseTypeArgumentsOfTypeReference(); + return finishNode(factory.createImportTypeNode(type, qualifier, typeArguments, isTypeOf), pos); + } + + function nextTokenIsNumericOrBigIntLiteral() { + nextToken(); + return token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral; + } + + function parseNonArrayType(): TypeNode { + switch (token()) { + case SyntaxKind.AnyKeyword: + case SyntaxKind.UnknownKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.ObjectKeyword: + // If these are followed by a dot, then parse these out as a dotted type reference instead. + return tryParse(parseKeywordAndNoDot) || parseTypeReference(); + case SyntaxKind.AsteriskEqualsToken: + // If there is '*=', treat it as * followed by postfix = + scanner.reScanAsteriskEqualsToken(); + // falls through + case SyntaxKind.AsteriskToken: + return parseJSDocAllType(); + case SyntaxKind.QuestionQuestionToken: + // If there is '??', treat it as prefix-'?' in JSDoc type. + scanner.reScanQuestionToken(); + // falls through + case SyntaxKind.QuestionToken: + return parseJSDocUnknownOrNullableType(); + case SyntaxKind.FunctionKeyword: + return parseJSDocFunctionType(); + case SyntaxKind.ExclamationToken: + return parseJSDocNonNullableType(); + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + return parseLiteralTypeNode(); + case SyntaxKind.MinusToken: + return lookAhead(nextTokenIsNumericOrBigIntLiteral) ? parseLiteralTypeNode(/*negative*/ true) : parseTypeReference(); + case SyntaxKind.VoidKeyword: + return parseTokenNode(); + case SyntaxKind.ThisKeyword: { + const thisKeyword = parseThisTypeNode(); + if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { + return parseThisTypePredicate(thisKeyword); + } + else { + return thisKeyword; + } + } + case SyntaxKind.TypeOfKeyword: + return lookAhead(isStartOfTypeOfImportType) ? parseImportType() : parseTypeQuery(); + case SyntaxKind.OpenBraceToken: + return lookAhead(isStartOfMappedType) ? parseMappedType() : parseTypeLiteral(); + case SyntaxKind.OpenBracketToken: + return parseTupleType(); + case SyntaxKind.OpenParenToken: + return parseParenthesizedType(); + case SyntaxKind.ImportKeyword: + return parseImportType(); + case SyntaxKind.AssertsKeyword: + return lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine) ? parseAssertsTypePredicate() : parseTypeReference(); + case SyntaxKind.TemplateHead: + return parseTemplateType(); + default: + return parseTypeReference(); } + } - function isStartOfParenthesizedOrFunctionType() { - nextToken(); - return token() === SyntaxKind.CloseParenToken || isStartOfParameter(/*isJSDocParameter*/ false) || isStartOfType(); + function isStartOfType(inStartOfParameter?: boolean): boolean { + switch (token()) { + case SyntaxKind.AnyKeyword: + case SyntaxKind.UnknownKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.UniqueKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.ThisKeyword: + case SyntaxKind.TypeOfKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.OpenBraceToken: + case SyntaxKind.OpenBracketToken: + case SyntaxKind.LessThanToken: + case SyntaxKind.BarToken: + case SyntaxKind.AmpersandToken: + case SyntaxKind.NewKeyword: + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.AsteriskToken: + case SyntaxKind.QuestionToken: + case SyntaxKind.ExclamationToken: + case SyntaxKind.DotDotDotToken: + case SyntaxKind.InferKeyword: + case SyntaxKind.ImportKeyword: + case SyntaxKind.AssertsKeyword: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + return true; + case SyntaxKind.FunctionKeyword: + return !inStartOfParameter; + case SyntaxKind.MinusToken: + return !inStartOfParameter && lookAhead(nextTokenIsNumericOrBigIntLiteral); + case SyntaxKind.OpenParenToken: + // Only consider '(' the start of a type if followed by ')', '...', an identifier, a modifier, + // or something that starts a type. We don't want to consider things like '(1)' a type. + return !inStartOfParameter && lookAhead(isStartOfParenthesizedOrFunctionType); + default: + return isIdentifier(); } + } - function parsePostfixTypeOrHigher(): TypeNode { - const pos = getNodePos(); - let type = parseNonArrayType(); - while (!scanner.hasPrecedingLineBreak()) { - switch (token()) { - case SyntaxKind.ExclamationToken: - nextToken(); - type = finishNode(factory.createJSDocNonNullableType(type), pos); - break; - case SyntaxKind.QuestionToken: - // If next token is start of a type we have a conditional type - if (lookAhead(nextTokenIsStartOfType)) { - return type; - } - nextToken(); - type = finishNode(factory.createJSDocNullableType(type), pos); - break; - case SyntaxKind.OpenBracketToken: - parseExpected(SyntaxKind.OpenBracketToken); - if (isStartOfType()) { - const indexType = parseType(); - parseExpected(SyntaxKind.CloseBracketToken); - type = finishNode(factory.createIndexedAccessTypeNode(type, indexType), pos); - } - else { - parseExpected(SyntaxKind.CloseBracketToken); - type = finishNode(factory.createArrayTypeNode(type), pos); - } - break; - default: + function isStartOfParenthesizedOrFunctionType() { + nextToken(); + return token() === SyntaxKind.CloseParenToken || isStartOfParameter(/*isJSDocParameter*/ false) || isStartOfType(); + } + + function parsePostfixTypeOrHigher(): TypeNode { + const pos = getNodePos(); + let type = parseNonArrayType(); + while (!scanner.hasPrecedingLineBreak()) { + switch (token()) { + case SyntaxKind.ExclamationToken: + nextToken(); + type = finishNode(factory.createJSDocNonNullableType(type), pos); + break; + case SyntaxKind.QuestionToken: + // If next token is start of a type we have a conditional type + if (lookAhead(nextTokenIsStartOfType)) { return type; - } + } + nextToken(); + type = finishNode(factory.createJSDocNullableType(type), pos); + break; + case SyntaxKind.OpenBracketToken: + parseExpected(SyntaxKind.OpenBracketToken); + if (isStartOfType()) { + const indexType = parseType(); + parseExpected(SyntaxKind.CloseBracketToken); + type = finishNode(factory.createIndexedAccessTypeNode(type, indexType), pos); + } + else { + parseExpected(SyntaxKind.CloseBracketToken); + type = finishNode(factory.createArrayTypeNode(type), pos); + } + break; + default: + return type; } - return type; } + return type; + } - function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword) { - const pos = getNodePos(); - parseExpected(operator); - return finishNode(factory.createTypeOperatorNode(operator, parseTypeOperatorOrHigher()), pos); - } + function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword) { + const pos = getNodePos(); + parseExpected(operator); + return finishNode(factory.createTypeOperatorNode(operator, parseTypeOperatorOrHigher()), pos); + } - function parseTypeParameterOfInferType() { - const pos = getNodePos(); - return finishNode( - factory.createTypeParameterDeclaration( - parseIdentifier(), - /*constraint*/ undefined, - /*defaultType*/ undefined - ), - pos - ); - } + function parseTypeParameterOfInferType() { + const pos = getNodePos(); + return finishNode( + factory.createTypeParameterDeclaration( + parseIdentifier(), + /*constraint*/ undefined, + /*defaultType*/ undefined + ), + pos + ); + } - function parseInferType(): InferTypeNode { - const pos = getNodePos(); - parseExpected(SyntaxKind.InferKeyword); - return finishNode(factory.createInferTypeNode(parseTypeParameterOfInferType()), pos); - } + function parseInferType(): InferTypeNode { + const pos = getNodePos(); + parseExpected(SyntaxKind.InferKeyword); + return finishNode(factory.createInferTypeNode(parseTypeParameterOfInferType()), pos); + } - function parseTypeOperatorOrHigher(): TypeNode { - const operator = token(); - switch (operator) { - case SyntaxKind.KeyOfKeyword: - case SyntaxKind.UniqueKeyword: - case SyntaxKind.ReadonlyKeyword: - return parseTypeOperator(operator); - case SyntaxKind.InferKeyword: - return parseInferType(); - } - return parsePostfixTypeOrHigher(); - } - - function parseFunctionOrConstructorTypeToError( - isInUnionType: boolean - ): TypeNode | undefined { - // the function type and constructor type shorthand notation - // are not allowed directly in unions and intersections, but we'll - // try to parse them gracefully and issue a helpful message. - if (isStartOfFunctionTypeOrConstructorType()) { - const type = parseFunctionOrConstructorType(); - let diagnostic: DiagnosticMessage; - if (isFunctionTypeNode(type)) { - diagnostic = isInUnionType - ? Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_a_union_type - : Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_an_intersection_type; - } - else { - diagnostic = isInUnionType - ? Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_a_union_type - : Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_an_intersection_type; + function parseTypeOperatorOrHigher(): TypeNode { + const operator = token(); + switch (operator) { + case SyntaxKind.KeyOfKeyword: + case SyntaxKind.UniqueKeyword: + case SyntaxKind.ReadonlyKeyword: + return parseTypeOperator(operator); + case SyntaxKind.InferKeyword: + return parseInferType(); + } + return parsePostfixTypeOrHigher(); + } - } - parseErrorAtRange(type, diagnostic); - return type; + function parseFunctionOrConstructorTypeToError( + isInUnionType: boolean + ): TypeNode | undefined { + // the function type and constructor type shorthand notation + // are not allowed directly in unions and intersections, but we'll + // try to parse them gracefully and issue a helpful message. + if (isStartOfFunctionTypeOrConstructorType()) { + const type = parseFunctionOrConstructorType(); + let diagnostic: DiagnosticMessage; + if (isFunctionTypeNode(type)) { + diagnostic = isInUnionType + ? Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_a_union_type + : Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_an_intersection_type; } - return undefined; - } + else { + diagnostic = isInUnionType + ? Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_a_union_type + : Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_an_intersection_type; - function parseUnionOrIntersectionType( - operator: SyntaxKind.BarToken | SyntaxKind.AmpersandToken, - parseConstituentType: () => TypeNode, - createTypeNode: (types: NodeArray) => UnionOrIntersectionTypeNode - ): TypeNode { - const pos = getNodePos(); - const isUnionType = operator === SyntaxKind.BarToken; - const hasLeadingOperator = parseOptional(operator); - let type = hasLeadingOperator && parseFunctionOrConstructorTypeToError(isUnionType) - || parseConstituentType(); - if (token() === operator || hasLeadingOperator) { - const types = [type]; - while (parseOptional(operator)) { - types.push(parseFunctionOrConstructorTypeToError(isUnionType) || parseConstituentType()); - } - type = finishNode(createTypeNode(createNodeArray(types, pos)), pos); } + parseErrorAtRange(type, diagnostic); return type; } + return undefined; + } - function parseIntersectionTypeOrHigher(): TypeNode { - return parseUnionOrIntersectionType(SyntaxKind.AmpersandToken, parseTypeOperatorOrHigher, factory.createIntersectionTypeNode); - } + function parseUnionOrIntersectionType( + operator: SyntaxKind.BarToken | SyntaxKind.AmpersandToken, + parseConstituentType: () => TypeNode, + createTypeNode: (types: NodeArray) => UnionOrIntersectionTypeNode + ): TypeNode { + const pos = getNodePos(); + const isUnionType = operator === SyntaxKind.BarToken; + const hasLeadingOperator = parseOptional(operator); + let type = hasLeadingOperator && parseFunctionOrConstructorTypeToError(isUnionType) + || parseConstituentType(); + if (token() === operator || hasLeadingOperator) { + const types = [type]; + while (parseOptional(operator)) { + types.push(parseFunctionOrConstructorTypeToError(isUnionType) || parseConstituentType()); + } + type = finishNode(createTypeNode(createNodeArray(types, pos)), pos); + } + return type; + } + + function parseIntersectionTypeOrHigher(): TypeNode { + return parseUnionOrIntersectionType(SyntaxKind.AmpersandToken, parseTypeOperatorOrHigher, factory.createIntersectionTypeNode); + } - function parseUnionTypeOrHigher(): TypeNode { - return parseUnionOrIntersectionType(SyntaxKind.BarToken, parseIntersectionTypeOrHigher, factory.createUnionTypeNode); + function parseUnionTypeOrHigher(): TypeNode { + return parseUnionOrIntersectionType(SyntaxKind.BarToken, parseIntersectionTypeOrHigher, factory.createUnionTypeNode); + } + + function nextTokenIsNewKeyword(): boolean { + nextToken(); + return token() === SyntaxKind.NewKeyword; + } + + function isStartOfFunctionTypeOrConstructorType(): boolean { + if (token() === SyntaxKind.LessThanToken) { + return true; + } + if (token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType)) { + return true; } + return token() === SyntaxKind.NewKeyword || + token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsNewKeyword); + } - function nextTokenIsNewKeyword(): boolean { + function skipParameterStart(): boolean { + if (isModifierKind(token())) { + // Skip modifiers + parseModifiers(); + } + if (isIdentifier() || token() === SyntaxKind.ThisKeyword) { nextToken(); - return token() === SyntaxKind.NewKeyword; + return true; } - - function isStartOfFunctionTypeOrConstructorType(): boolean { - if (token() === SyntaxKind.LessThanToken) { - return true; - } - if (token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType)) { - return true; - } - return token() === SyntaxKind.NewKeyword || - token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsNewKeyword); + if (token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.OpenBraceToken) { + // Return true if we can parse an array or object binding pattern with no errors + const previousErrorCount = parseDiagnostics.length; + parseIdentifierOrPattern(); + return previousErrorCount === parseDiagnostics.length; } + return false; + } - function skipParameterStart(): boolean { - if (isModifierKind(token())) { - // Skip modifiers - parseModifiers(); - } - if (isIdentifier() || token() === SyntaxKind.ThisKeyword) { - nextToken(); - return true; - } - if (token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.OpenBraceToken) { - // Return true if we can parse an array or object binding pattern with no errors - const previousErrorCount = parseDiagnostics.length; - parseIdentifierOrPattern(); - return previousErrorCount === parseDiagnostics.length; - } - return false; + function isUnambiguouslyStartOfFunctionType() { + nextToken(); + if (token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.DotDotDotToken) { + // ( ) + // ( ... + return true; } - - function isUnambiguouslyStartOfFunctionType() { - nextToken(); - if (token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.DotDotDotToken) { - // ( ) - // ( ... + if (skipParameterStart()) { + // We successfully skipped modifiers (if any) and an identifier or binding pattern, + // now see if we have something that indicates a parameter declaration + if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || + token() === SyntaxKind.QuestionToken || token() === SyntaxKind.EqualsToken) { + // ( xxx : + // ( xxx , + // ( xxx ? + // ( xxx = return true; } - if (skipParameterStart()) { - // We successfully skipped modifiers (if any) and an identifier or binding pattern, - // now see if we have something that indicates a parameter declaration - if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || - token() === SyntaxKind.QuestionToken || token() === SyntaxKind.EqualsToken) { - // ( xxx : - // ( xxx , - // ( xxx ? - // ( xxx = + if (token() === SyntaxKind.CloseParenToken) { + nextToken(); + if (token() === SyntaxKind.EqualsGreaterThanToken) { + // ( xxx ) => return true; } - if (token() === SyntaxKind.CloseParenToken) { - nextToken(); - if (token() === SyntaxKind.EqualsGreaterThanToken) { - // ( xxx ) => - return true; - } - } } - return false; } + return false; + } - function parseTypeOrTypePredicate(): TypeNode { - const pos = getNodePos(); - const typePredicateVariable = isIdentifier() && tryParse(parseTypePredicatePrefix); - const type = parseType(); - if (typePredicateVariable) { - return finishNode(factory.createTypePredicateNode(/*assertsModifier*/ undefined, typePredicateVariable, type), pos); - } - else { - return type; - } + function parseTypeOrTypePredicate(): TypeNode { + const pos = getNodePos(); + const typePredicateVariable = isIdentifier() && tryParse(parseTypePredicatePrefix); + const type = parseType(); + if (typePredicateVariable) { + return finishNode(factory.createTypePredicateNode(/*assertsModifier*/ undefined, typePredicateVariable, type), pos); } - - function parseTypePredicatePrefix() { - const id = parseIdentifier(); - if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { - nextToken(); - return id; - } + else { + return type; } + } - function parseAssertsTypePredicate(): TypeNode { - const pos = getNodePos(); - const assertsModifier = parseExpectedToken(SyntaxKind.AssertsKeyword); - const parameterName = token() === SyntaxKind.ThisKeyword ? parseThisTypeNode() : parseIdentifier(); - const type = parseOptional(SyntaxKind.IsKeyword) ? parseType() : undefined; - return finishNode(factory.createTypePredicateNode(assertsModifier, parameterName, type), pos); + function parseTypePredicatePrefix() { + const id = parseIdentifier(); + if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { + nextToken(); + return id; } + } + + function parseAssertsTypePredicate(): TypeNode { + const pos = getNodePos(); + const assertsModifier = parseExpectedToken(SyntaxKind.AssertsKeyword); + const parameterName = token() === SyntaxKind.ThisKeyword ? parseThisTypeNode() : parseIdentifier(); + const type = parseOptional(SyntaxKind.IsKeyword) ? parseType() : undefined; + return finishNode(factory.createTypePredicateNode(assertsModifier, parameterName, type), pos); + } - function parseType(): TypeNode { - // The rules about 'yield' only apply to actual code/expression contexts. They don't - // apply to 'type' contexts. So we disable these parameters here before moving on. - return doOutsideOfContext(NodeFlags.TypeExcludesFlags, parseTypeWorker); + function parseType(): TypeNode { + // The rules about 'yield' only apply to actual code/expression contexts. They don't + // apply to 'type' contexts. So we disable these parameters here before moving on. + return doOutsideOfContext(NodeFlags.TypeExcludesFlags, parseTypeWorker); + } + + function parseTypeWorker(noConditionalTypes?: boolean): TypeNode { + if (isStartOfFunctionTypeOrConstructorType()) { + return parseFunctionOrConstructorType(); + } + const pos = getNodePos(); + const type = parseUnionTypeOrHigher(); + if (!noConditionalTypes && !scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.ExtendsKeyword)) { + // The type following 'extends' is not permitted to be another conditional type + const extendsType = parseTypeWorker(/*noConditionalTypes*/ true); + parseExpected(SyntaxKind.QuestionToken); + const trueType = parseTypeWorker(); + parseExpected(SyntaxKind.ColonToken); + const falseType = parseTypeWorker(); + return finishNode(factory.createConditionalTypeNode(type, extendsType, trueType, falseType), pos); } + return type; + } - function parseTypeWorker(noConditionalTypes?: boolean): TypeNode { - if (isStartOfFunctionTypeOrConstructorType()) { - return parseFunctionOrConstructorType(); - } - const pos = getNodePos(); - const type = parseUnionTypeOrHigher(); - if (!noConditionalTypes && !scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.ExtendsKeyword)) { - // The type following 'extends' is not permitted to be another conditional type - const extendsType = parseTypeWorker(/*noConditionalTypes*/ true); - parseExpected(SyntaxKind.QuestionToken); - const trueType = parseTypeWorker(); - parseExpected(SyntaxKind.ColonToken); - const falseType = parseTypeWorker(); - return finishNode(factory.createConditionalTypeNode(type, extendsType, trueType, falseType), pos); - } - return type; + function parseTypeAnnotation(): TypeNode | undefined { + return parseOptional(SyntaxKind.ColonToken) ? parseType() : undefined; + } + + // EXPRESSIONS + function isStartOfLeftHandSideExpression(): boolean { + switch (token()) { + case SyntaxKind.ThisKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + case SyntaxKind.OpenParenToken: + case SyntaxKind.OpenBracketToken: + case SyntaxKind.OpenBraceToken: + case SyntaxKind.FunctionKeyword: + case SyntaxKind.ClassKeyword: + case SyntaxKind.NewKeyword: + case SyntaxKind.SlashToken: + case SyntaxKind.SlashEqualsToken: + case SyntaxKind.Identifier: + return true; + case SyntaxKind.ImportKeyword: + return lookAhead(nextTokenIsOpenParenOrLessThanOrDot); + default: + return isIdentifier(); } + } - function parseTypeAnnotation(): TypeNode | undefined { - return parseOptional(SyntaxKind.ColonToken) ? parseType() : undefined; + function isStartOfExpression(): boolean { + if (isStartOfLeftHandSideExpression()) { + return true; } - // EXPRESSIONS - function isStartOfLeftHandSideExpression(): boolean { - switch (token()) { - case SyntaxKind.ThisKeyword: - case SyntaxKind.SuperKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateHead: - case SyntaxKind.OpenParenToken: - case SyntaxKind.OpenBracketToken: - case SyntaxKind.OpenBraceToken: - case SyntaxKind.FunctionKeyword: - case SyntaxKind.ClassKeyword: - case SyntaxKind.NewKeyword: - case SyntaxKind.SlashToken: - case SyntaxKind.SlashEqualsToken: - case SyntaxKind.Identifier: + switch (token()) { + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.TildeToken: + case SyntaxKind.ExclamationToken: + case SyntaxKind.DeleteKeyword: + case SyntaxKind.TypeOfKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.PlusPlusToken: + case SyntaxKind.MinusMinusToken: + case SyntaxKind.LessThanToken: + case SyntaxKind.AwaitKeyword: + case SyntaxKind.YieldKeyword: + case SyntaxKind.PrivateIdentifier: + // Yield/await always starts an expression. Either it is an identifier (in which case + // it is definitely an expression). Or it's a keyword (either because we're in + // a generator or async function, or in strict mode (or both)) and it started a yield or await expression. + return true; + default: + // Error tolerance. If we see the start of some binary operator, we consider + // that the start of an expression. That way we'll parse out a missing identifier, + // give a good message about an identifier being missing, and then consume the + // rest of the binary expression. + if (isBinaryOperator()) { return true; - case SyntaxKind.ImportKeyword: - return lookAhead(nextTokenIsOpenParenOrLessThanOrDot); - default: - return isIdentifier(); - } + } + + return isIdentifier(); } + } - function isStartOfExpression(): boolean { - if (isStartOfLeftHandSideExpression()) { - return true; - } + function isStartOfExpressionStatement(): boolean { + // As per the grammar, none of '{' or 'function' or 'class' can start an expression statement. + return token() !== SyntaxKind.OpenBraceToken && + token() !== SyntaxKind.FunctionKeyword && + token() !== SyntaxKind.ClassKeyword && + token() !== SyntaxKind.AtToken && + isStartOfExpression(); + } - switch (token()) { - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - case SyntaxKind.ExclamationToken: - case SyntaxKind.DeleteKeyword: - case SyntaxKind.TypeOfKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.PlusPlusToken: - case SyntaxKind.MinusMinusToken: - case SyntaxKind.LessThanToken: - case SyntaxKind.AwaitKeyword: - case SyntaxKind.YieldKeyword: - case SyntaxKind.PrivateIdentifier: - // Yield/await always starts an expression. Either it is an identifier (in which case - // it is definitely an expression). Or it's a keyword (either because we're in - // a generator or async function, or in strict mode (or both)) and it started a yield or await expression. - return true; - default: - // Error tolerance. If we see the start of some binary operator, we consider - // that the start of an expression. That way we'll parse out a missing identifier, - // give a good message about an identifier being missing, and then consume the - // rest of the binary expression. - if (isBinaryOperator()) { - return true; - } + function parseExpression(): Expression { + // Expression[in]: + // AssignmentExpression[in] + // Expression[in] , AssignmentExpression[in] - return isIdentifier(); - } + // clear the decorator context when parsing Expression, as it should be unambiguous when parsing a decorator + const saveDecoratorContext = inDecoratorContext(); + if (saveDecoratorContext) { + setDecoratorContext(/*val*/ false); } - function isStartOfExpressionStatement(): boolean { - // As per the grammar, none of '{' or 'function' or 'class' can start an expression statement. - return token() !== SyntaxKind.OpenBraceToken && - token() !== SyntaxKind.FunctionKeyword && - token() !== SyntaxKind.ClassKeyword && - token() !== SyntaxKind.AtToken && - isStartOfExpression(); + const pos = getNodePos(); + let expr = parseAssignmentExpressionOrHigher(); + let operatorToken: BinaryOperatorToken; + while ((operatorToken = parseOptionalToken(SyntaxKind.CommaToken))) { + expr = makeBinaryExpression(expr, operatorToken, parseAssignmentExpressionOrHigher(), pos); } - function parseExpression(): Expression { - // Expression[in]: - // AssignmentExpression[in] - // Expression[in] , AssignmentExpression[in] + if (saveDecoratorContext) { + setDecoratorContext(/*val*/ true); + } + return expr; + } - // clear the decorator context when parsing Expression, as it should be unambiguous when parsing a decorator - const saveDecoratorContext = inDecoratorContext(); - if (saveDecoratorContext) { - setDecoratorContext(/*val*/ false); - } + function parseInitializer(): Expression | undefined { + return parseOptional(SyntaxKind.EqualsToken) ? parseAssignmentExpressionOrHigher() : undefined; + } - const pos = getNodePos(); - let expr = parseAssignmentExpressionOrHigher(); - let operatorToken: BinaryOperatorToken; - while ((operatorToken = parseOptionalToken(SyntaxKind.CommaToken))) { - expr = makeBinaryExpression(expr, operatorToken, parseAssignmentExpressionOrHigher(), pos); - } + function parseAssignmentExpressionOrHigher(): Expression { + // AssignmentExpression[in,yield]: + // 1) ConditionalExpression[?in,?yield] + // 2) LeftHandSideExpression = AssignmentExpression[?in,?yield] + // 3) LeftHandSideExpression AssignmentOperator AssignmentExpression[?in,?yield] + // 4) ArrowFunctionExpression[?in,?yield] + // 5) AsyncArrowFunctionExpression[in,yield,await] + // 6) [+Yield] YieldExpression[?In] + // + // Note: for ease of implementation we treat productions '2' and '3' as the same thing. + // (i.e. they're both BinaryExpressions with an assignment operator in it). - if (saveDecoratorContext) { - setDecoratorContext(/*val*/ true); - } - return expr; + // First, do the simple check if we have a YieldExpression (production '6'). + if (isYieldExpression()) { + return parseYieldExpression(); } - function parseInitializer(): Expression | undefined { - return parseOptional(SyntaxKind.EqualsToken) ? parseAssignmentExpressionOrHigher() : undefined; + // Then, check if we have an arrow function (production '4' and '5') that starts with a parenthesized + // parameter list or is an async arrow function. + // AsyncArrowFunctionExpression: + // 1) async[no LineTerminator here]AsyncArrowBindingIdentifier[?Yield][no LineTerminator here]=>AsyncConciseBody[?In] + // 2) CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await][no LineTerminator here]=>AsyncConciseBody[?In] + // Production (1) of AsyncArrowFunctionExpression is parsed in "tryParseAsyncSimpleArrowFunctionExpression". + // And production (2) is parsed in "tryParseParenthesizedArrowFunctionExpression". + // + // If we do successfully parse arrow-function, we must *not* recurse for productions 1, 2 or 3. An ArrowFunction is + // not a LeftHandSideExpression, nor does it start a ConditionalExpression. So we are done + // with AssignmentExpression if we see one. + const arrowExpression = tryParseParenthesizedArrowFunctionExpression() || tryParseAsyncSimpleArrowFunctionExpression(); + if (arrowExpression) { + return arrowExpression; } - function parseAssignmentExpressionOrHigher(): Expression { - // AssignmentExpression[in,yield]: - // 1) ConditionalExpression[?in,?yield] - // 2) LeftHandSideExpression = AssignmentExpression[?in,?yield] - // 3) LeftHandSideExpression AssignmentOperator AssignmentExpression[?in,?yield] - // 4) ArrowFunctionExpression[?in,?yield] - // 5) AsyncArrowFunctionExpression[in,yield,await] - // 6) [+Yield] YieldExpression[?In] - // - // Note: for ease of implementation we treat productions '2' and '3' as the same thing. - // (i.e. they're both BinaryExpressions with an assignment operator in it). + // Now try to see if we're in production '1', '2' or '3'. A conditional expression can + // start with a LogicalOrExpression, while the assignment productions can only start with + // LeftHandSideExpressions. + // + // So, first, we try to just parse out a BinaryExpression. If we get something that is a + // LeftHandSide or higher, then we can try to parse out the assignment expression part. + // Otherwise, we try to parse out the conditional expression bit. We want to allow any + // binary expression here, so we pass in the 'lowest' precedence here so that it matches + // and consumes anything. + const pos = getNodePos(); + const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); + + // To avoid a look-ahead, we did not handle the case of an arrow function with a single un-parenthesized + // parameter ('x => ...') above. We handle it here by checking if the parsed expression was a single + // identifier and the current token is an arrow. + if (expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { + return parseSimpleArrowFunctionExpression(pos, expr as Identifier, /*asyncModifier*/ undefined); + } + + // Now see if we might be in cases '2' or '3'. + // If the expression was a LHS expression, and we have an assignment operator, then + // we're in '2' or '3'. Consume the assignment and return. + // + // Note: we call reScanGreaterToken so that we get an appropriately merged token + // for cases like `> > =` becoming `>>=` + if (isLeftHandSideExpression(expr) && isAssignmentOperator(reScanGreaterToken())) { + return makeBinaryExpression(expr, parseTokenNode(), parseAssignmentExpressionOrHigher(), pos); + } - // First, do the simple check if we have a YieldExpression (production '6'). - if (isYieldExpression()) { - return parseYieldExpression(); - } + // It wasn't an assignment or a lambda. This is a conditional expression: + return parseConditionalExpressionRest(expr, pos); + } - // Then, check if we have an arrow function (production '4' and '5') that starts with a parenthesized - // parameter list or is an async arrow function. - // AsyncArrowFunctionExpression: - // 1) async[no LineTerminator here]AsyncArrowBindingIdentifier[?Yield][no LineTerminator here]=>AsyncConciseBody[?In] - // 2) CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await][no LineTerminator here]=>AsyncConciseBody[?In] - // Production (1) of AsyncArrowFunctionExpression is parsed in "tryParseAsyncSimpleArrowFunctionExpression". - // And production (2) is parsed in "tryParseParenthesizedArrowFunctionExpression". - // - // If we do successfully parse arrow-function, we must *not* recurse for productions 1, 2 or 3. An ArrowFunction is - // not a LeftHandSideExpression, nor does it start a ConditionalExpression. So we are done - // with AssignmentExpression if we see one. - const arrowExpression = tryParseParenthesizedArrowFunctionExpression() || tryParseAsyncSimpleArrowFunctionExpression(); - if (arrowExpression) { - return arrowExpression; + function isYieldExpression(): boolean { + if (token() === SyntaxKind.YieldKeyword) { + // If we have a 'yield' keyword, and this is a context where yield expressions are + // allowed, then definitely parse out a yield expression. + if (inYieldContext()) { + return true; } - // Now try to see if we're in production '1', '2' or '3'. A conditional expression can - // start with a LogicalOrExpression, while the assignment productions can only start with - // LeftHandSideExpressions. + // We're in a context where 'yield expr' is not allowed. However, if we can + // definitely tell that the user was trying to parse a 'yield expr' and not + // just a normal expr that start with a 'yield' identifier, then parse out + // a 'yield expr'. We can then report an error later that they are only + // allowed in generator expressions. // - // So, first, we try to just parse out a BinaryExpression. If we get something that is a - // LeftHandSide or higher, then we can try to parse out the assignment expression part. - // Otherwise, we try to parse out the conditional expression bit. We want to allow any - // binary expression here, so we pass in the 'lowest' precedence here so that it matches - // and consumes anything. - const pos = getNodePos(); - const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); + // for example, if we see 'yield(foo)', then we'll have to treat that as an + // invocation expression of something called 'yield'. However, if we have + // 'yield foo' then that is not legal as a normal expression, so we can + // definitely recognize this as a yield expression. + // + // for now we just check if the next token is an identifier. More heuristics + // can be added here later as necessary. We just need to make sure that we + // don't accidentally consume something legal. + return lookAhead(nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine); + } - // To avoid a look-ahead, we did not handle the case of an arrow function with a single un-parenthesized - // parameter ('x => ...') above. We handle it here by checking if the parsed expression was a single - // identifier and the current token is an arrow. - if (expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { - return parseSimpleArrowFunctionExpression(pos, expr as Identifier, /*asyncModifier*/ undefined); - } + return false; + } - // Now see if we might be in cases '2' or '3'. - // If the expression was a LHS expression, and we have an assignment operator, then - // we're in '2' or '3'. Consume the assignment and return. - // - // Note: we call reScanGreaterToken so that we get an appropriately merged token - // for cases like `> > =` becoming `>>=` - if (isLeftHandSideExpression(expr) && isAssignmentOperator(reScanGreaterToken())) { - return makeBinaryExpression(expr, parseTokenNode(), parseAssignmentExpressionOrHigher(), pos); - } + function nextTokenIsIdentifierOnSameLine() { + nextToken(); + return !scanner.hasPrecedingLineBreak() && isIdentifier(); + } - // It wasn't an assignment or a lambda. This is a conditional expression: - return parseConditionalExpressionRest(expr, pos); - } + function parseYieldExpression(): YieldExpression { + const pos = getNodePos(); - function isYieldExpression(): boolean { - if (token() === SyntaxKind.YieldKeyword) { - // If we have a 'yield' keyword, and this is a context where yield expressions are - // allowed, then definitely parse out a yield expression. - if (inYieldContext()) { - return true; - } + // YieldExpression[In] : + // yield + // yield [no LineTerminator here] [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] + // yield [no LineTerminator here] * [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] + nextToken(); - // We're in a context where 'yield expr' is not allowed. However, if we can - // definitely tell that the user was trying to parse a 'yield expr' and not - // just a normal expr that start with a 'yield' identifier, then parse out - // a 'yield expr'. We can then report an error later that they are only - // allowed in generator expressions. - // - // for example, if we see 'yield(foo)', then we'll have to treat that as an - // invocation expression of something called 'yield'. However, if we have - // 'yield foo' then that is not legal as a normal expression, so we can - // definitely recognize this as a yield expression. - // - // for now we just check if the next token is an identifier. More heuristics - // can be added here later as necessary. We just need to make sure that we - // don't accidentally consume something legal. - return lookAhead(nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine); - } + if (!scanner.hasPrecedingLineBreak() && + (token() === SyntaxKind.AsteriskToken || isStartOfExpression())) { + return finishNode( + factory.createYieldExpression( + parseOptionalToken(SyntaxKind.AsteriskToken), + parseAssignmentExpressionOrHigher() + ), + pos + ); + } + else { + // if the next token is not on the same line as yield. or we don't have an '*' or + // the start of an expression, then this is just a simple "yield" expression. + return finishNode(factory.createYieldExpression(/*asteriskToken*/ undefined, /*expression*/ undefined), pos); + } + } - return false; + function parseSimpleArrowFunctionExpression(pos: number, identifier: Identifier, asyncModifier?: NodeArray | undefined): ArrowFunction { + Debug.assert(token() === SyntaxKind.EqualsGreaterThanToken, "parseSimpleArrowFunctionExpression should only have been called if we had a =>"); + const parameter = factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + identifier, + /*questionToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + ); + finishNode(parameter, identifier.pos); + + const parameters = createNodeArray([parameter], parameter.pos, parameter.end); + const equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); + const body = parseArrowFunctionExpressionBody(/*isAsync*/ !!asyncModifier); + const node = factory.createArrowFunction(asyncModifier, /*typeParameters*/ undefined, parameters, /*type*/ undefined, equalsGreaterThanToken, body); + return addJSDocComment(finishNode(node, pos)); + } + + function tryParseParenthesizedArrowFunctionExpression(): Expression | undefined { + const triState = isParenthesizedArrowFunctionExpression(); + if (triState === Tristate.False) { + // It's definitely not a parenthesized arrow function expression. + return undefined; } - function nextTokenIsIdentifierOnSameLine() { - nextToken(); - return !scanner.hasPrecedingLineBreak() && isIdentifier(); + // If we definitely have an arrow function, then we can just parse one, not requiring a + // following => or { token. Otherwise, we *might* have an arrow function. Try to parse + // it out, but don't allow any ambiguity, and return 'undefined' if this could be an + // expression instead. + return triState === Tristate.True ? + parseParenthesizedArrowFunctionExpression(/*allowAmbiguity*/ true) : + tryParse(parsePossibleParenthesizedArrowFunctionExpression); + } + + // True -> We definitely expect a parenthesized arrow function here. + // False -> There *cannot* be a parenthesized arrow function here. + // Unknown -> There *might* be a parenthesized arrow function here. + // Speculatively look ahead to be sure, and rollback if not. + function isParenthesizedArrowFunctionExpression(): Tristate { + if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken || token() === SyntaxKind.AsyncKeyword) { + return lookAhead(isParenthesizedArrowFunctionExpressionWorker); } - function parseYieldExpression(): YieldExpression { - const pos = getNodePos(); + if (token() === SyntaxKind.EqualsGreaterThanToken) { + // ERROR RECOVERY TWEAK: + // If we see a standalone => try to parse it as an arrow function expression as that's + // likely what the user intended to write. + return Tristate.True; + } + // Definitely not a parenthesized arrow function. + return Tristate.False; + } - // YieldExpression[In] : - // yield - // yield [no LineTerminator here] [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] - // yield [no LineTerminator here] * [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] + function isParenthesizedArrowFunctionExpressionWorker() { + if (token() === SyntaxKind.AsyncKeyword) { nextToken(); - - if (!scanner.hasPrecedingLineBreak() && - (token() === SyntaxKind.AsteriskToken || isStartOfExpression())) { - return finishNode( - factory.createYieldExpression( - parseOptionalToken(SyntaxKind.AsteriskToken), - parseAssignmentExpressionOrHigher() - ), - pos - ); + if (scanner.hasPrecedingLineBreak()) { + return Tristate.False; } - else { - // if the next token is not on the same line as yield. or we don't have an '*' or - // the start of an expression, then this is just a simple "yield" expression. - return finishNode(factory.createYieldExpression(/*asteriskToken*/ undefined, /*expression*/ undefined), pos); + if (token() !== SyntaxKind.OpenParenToken && token() !== SyntaxKind.LessThanToken) { + return Tristate.False; } } - function parseSimpleArrowFunctionExpression(pos: number, identifier: Identifier, asyncModifier?: NodeArray | undefined): ArrowFunction { - Debug.assert(token() === SyntaxKind.EqualsGreaterThanToken, "parseSimpleArrowFunctionExpression should only have been called if we had a =>"); - const parameter = factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - identifier, - /*questionToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined - ); - finishNode(parameter, identifier.pos); - - const parameters = createNodeArray([parameter], parameter.pos, parameter.end); - const equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); - const body = parseArrowFunctionExpressionBody(/*isAsync*/ !!asyncModifier); - const node = factory.createArrowFunction(asyncModifier, /*typeParameters*/ undefined, parameters, /*type*/ undefined, equalsGreaterThanToken, body); - return addJSDocComment(finishNode(node, pos)); - } + const first = token(); + const second = nextToken(); - function tryParseParenthesizedArrowFunctionExpression(): Expression | undefined { - const triState = isParenthesizedArrowFunctionExpression(); - if (triState === Tristate.False) { - // It's definitely not a parenthesized arrow function expression. - return undefined; + if (first === SyntaxKind.OpenParenToken) { + if (second === SyntaxKind.CloseParenToken) { + // Simple cases: "() =>", "(): ", and "() {". + // This is an arrow function with no parameters. + // The last one is not actually an arrow function, + // but this is probably what the user intended. + const third = nextToken(); + switch (third) { + case SyntaxKind.EqualsGreaterThanToken: + case SyntaxKind.ColonToken: + case SyntaxKind.OpenBraceToken: + return Tristate.True; + default: + return Tristate.False; + } } - // If we definitely have an arrow function, then we can just parse one, not requiring a - // following => or { token. Otherwise, we *might* have an arrow function. Try to parse - // it out, but don't allow any ambiguity, and return 'undefined' if this could be an - // expression instead. - return triState === Tristate.True ? - parseParenthesizedArrowFunctionExpression(/*allowAmbiguity*/ true) : - tryParse(parsePossibleParenthesizedArrowFunctionExpression); - } + // If encounter "([" or "({", this could be the start of a binding pattern. + // Examples: + // ([ x ]) => { } + // ({ x }) => { } + // ([ x ]) + // ({ x }) + if (second === SyntaxKind.OpenBracketToken || second === SyntaxKind.OpenBraceToken) { + return Tristate.Unknown; + } - // True -> We definitely expect a parenthesized arrow function here. - // False -> There *cannot* be a parenthesized arrow function here. - // Unknown -> There *might* be a parenthesized arrow function here. - // Speculatively look ahead to be sure, and rollback if not. - function isParenthesizedArrowFunctionExpression(): Tristate { - if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken || token() === SyntaxKind.AsyncKeyword) { - return lookAhead(isParenthesizedArrowFunctionExpressionWorker); + // Simple case: "(..." + // This is an arrow function with a rest parameter. + if (second === SyntaxKind.DotDotDotToken) { + return Tristate.True; } - if (token() === SyntaxKind.EqualsGreaterThanToken) { - // ERROR RECOVERY TWEAK: - // If we see a standalone => try to parse it as an arrow function expression as that's - // likely what the user intended to write. + // Check for "(xxx yyy", where xxx is a modifier and yyy is an identifier. This + // isn't actually allowed, but we want to treat it as a lambda so we can provide + // a good error message. + if (isModifierKind(second) && second !== SyntaxKind.AsyncKeyword && lookAhead(nextTokenIsIdentifier)) { return Tristate.True; } - // Definitely not a parenthesized arrow function. - return Tristate.False; - } - function isParenthesizedArrowFunctionExpressionWorker() { - if (token() === SyntaxKind.AsyncKeyword) { - nextToken(); - if (scanner.hasPrecedingLineBreak()) { - return Tristate.False; - } - if (token() !== SyntaxKind.OpenParenToken && token() !== SyntaxKind.LessThanToken) { - return Tristate.False; - } + // If we had "(" followed by something that's not an identifier, + // then this definitely doesn't look like a lambda. "this" is not + // valid, but we want to parse it and then give a semantic error. + if (!isIdentifier() && second !== SyntaxKind.ThisKeyword) { + return Tristate.False; } - const first = token(); - const second = nextToken(); - - if (first === SyntaxKind.OpenParenToken) { - if (second === SyntaxKind.CloseParenToken) { - // Simple cases: "() =>", "(): ", and "() {". - // This is an arrow function with no parameters. - // The last one is not actually an arrow function, - // but this is probably what the user intended. - const third = nextToken(); - switch (third) { - case SyntaxKind.EqualsGreaterThanToken: - case SyntaxKind.ColonToken: - case SyntaxKind.OpenBraceToken: - return Tristate.True; - default: - return Tristate.False; - } - } - - // If encounter "([" or "({", this could be the start of a binding pattern. - // Examples: - // ([ x ]) => { } - // ({ x }) => { } - // ([ x ]) - // ({ x }) - if (second === SyntaxKind.OpenBracketToken || second === SyntaxKind.OpenBraceToken) { - return Tristate.Unknown; - } - - // Simple case: "(..." - // This is an arrow function with a rest parameter. - if (second === SyntaxKind.DotDotDotToken) { - return Tristate.True; - } - - // Check for "(xxx yyy", where xxx is a modifier and yyy is an identifier. This - // isn't actually allowed, but we want to treat it as a lambda so we can provide - // a good error message. - if (isModifierKind(second) && second !== SyntaxKind.AsyncKeyword && lookAhead(nextTokenIsIdentifier)) { + switch (nextToken()) { + case SyntaxKind.ColonToken: + // If we have something like "(a:", then we must have a + // type-annotated parameter in an arrow function expression. return Tristate.True; - } - - // If we had "(" followed by something that's not an identifier, - // then this definitely doesn't look like a lambda. "this" is not - // valid, but we want to parse it and then give a semantic error. - if (!isIdentifier() && second !== SyntaxKind.ThisKeyword) { + case SyntaxKind.QuestionToken: + nextToken(); + // If we have "(a?:" or "(a?," or "(a?=" or "(a?)" then it is definitely a lambda. + if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || token() === SyntaxKind.EqualsToken || token() === SyntaxKind.CloseParenToken) { + return Tristate.True; + } + // Otherwise it is definitely not a lambda. return Tristate.False; - } + case SyntaxKind.CommaToken: + case SyntaxKind.EqualsToken: + case SyntaxKind.CloseParenToken: + // If we have "(a," or "(a=" or "(a)" this *could* be an arrow function + return Tristate.Unknown; + } + // It is definitely not an arrow function + return Tristate.False; + } + else { + Debug.assert(first === SyntaxKind.LessThanToken); - switch (nextToken()) { - case SyntaxKind.ColonToken: - // If we have something like "(a:", then we must have a - // type-annotated parameter in an arrow function expression. - return Tristate.True; - case SyntaxKind.QuestionToken: - nextToken(); - // If we have "(a?:" or "(a?," or "(a?=" or "(a?)" then it is definitely a lambda. - if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || token() === SyntaxKind.EqualsToken || token() === SyntaxKind.CloseParenToken) { - return Tristate.True; - } - // Otherwise it is definitely not a lambda. - return Tristate.False; - case SyntaxKind.CommaToken: - case SyntaxKind.EqualsToken: - case SyntaxKind.CloseParenToken: - // If we have "(a," or "(a=" or "(a)" this *could* be an arrow function - return Tristate.Unknown; - } - // It is definitely not an arrow function + // If we have "<" not followed by an identifier, + // then this definitely is not an arrow function. + if (!isIdentifier()) { return Tristate.False; } - else { - Debug.assert(first === SyntaxKind.LessThanToken); - - // If we have "<" not followed by an identifier, - // then this definitely is not an arrow function. - if (!isIdentifier()) { - return Tristate.False; - } - // JSX overrides - if (languageVariant === LanguageVariant.JSX) { - const isArrowFunctionInJsx = lookAhead(() => { - const third = nextToken(); - if (third === SyntaxKind.ExtendsKeyword) { - const fourth = nextToken(); - switch (fourth) { - case SyntaxKind.EqualsToken: - case SyntaxKind.GreaterThanToken: - return false; - default: - return true; - } - } - else if (third === SyntaxKind.CommaToken) { - return true; + // JSX overrides + if (languageVariant === LanguageVariant.JSX) { + const isArrowFunctionInJsx = lookAhead(() => { + const third = nextToken(); + if (third === SyntaxKind.ExtendsKeyword) { + const fourth = nextToken(); + switch (fourth) { + case SyntaxKind.EqualsToken: + case SyntaxKind.GreaterThanToken: + return false; + default: + return true; } - return false; - }); - - if (isArrowFunctionInJsx) { - return Tristate.True; } + else if (third === SyntaxKind.CommaToken) { + return true; + } + return false; + }); - return Tristate.False; + if (isArrowFunctionInJsx) { + return Tristate.True; } - // This *could* be a parenthesized arrow function. - return Tristate.Unknown; - } - } - - function parsePossibleParenthesizedArrowFunctionExpression(): ArrowFunction | undefined { - const tokenPos = scanner.getTokenPos(); - if (notParenthesizedArrow?.has(tokenPos)) { - return undefined; - } - - const result = parseParenthesizedArrowFunctionExpression(/*allowAmbiguity*/ false); - if (!result) { - (notParenthesizedArrow || (notParenthesizedArrow = new Set())).add(tokenPos); + return Tristate.False; } - return result; + // This *could* be a parenthesized arrow function. + return Tristate.Unknown; } + } - function tryParseAsyncSimpleArrowFunctionExpression(): ArrowFunction | undefined { - // We do a check here so that we won't be doing unnecessarily call to "lookAhead" - if (token() === SyntaxKind.AsyncKeyword) { - if (lookAhead(isUnParenthesizedAsyncArrowFunctionWorker) === Tristate.True) { - const pos = getNodePos(); - const asyncModifier = parseModifiersForArrowFunction(); - const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); - return parseSimpleArrowFunctionExpression(pos, expr as Identifier, asyncModifier); - } - } + function parsePossibleParenthesizedArrowFunctionExpression(): ArrowFunction | undefined { + const tokenPos = scanner.getTokenPos(); + if (notParenthesizedArrow?.has(tokenPos)) { return undefined; } - function isUnParenthesizedAsyncArrowFunctionWorker(): Tristate { - // AsyncArrowFunctionExpression: - // 1) async[no LineTerminator here]AsyncArrowBindingIdentifier[?Yield][no LineTerminator here]=>AsyncConciseBody[?In] - // 2) CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await][no LineTerminator here]=>AsyncConciseBody[?In] - if (token() === SyntaxKind.AsyncKeyword) { - nextToken(); - // If the "async" is followed by "=>" token then it is not a beginning of an async arrow-function - // but instead a simple arrow-function which will be parsed inside "parseAssignmentExpressionOrHigher" - if (scanner.hasPrecedingLineBreak() || token() === SyntaxKind.EqualsGreaterThanToken) { - return Tristate.False; - } - // Check for un-parenthesized AsyncArrowFunction + const result = parseParenthesizedArrowFunctionExpression(/*allowAmbiguity*/ false); + if (!result) { + (notParenthesizedArrow || (notParenthesizedArrow = new Set())).add(tokenPos); + } + + return result; + } + + function tryParseAsyncSimpleArrowFunctionExpression(): ArrowFunction | undefined { + // We do a check here so that we won't be doing unnecessarily call to "lookAhead" + if (token() === SyntaxKind.AsyncKeyword) { + if (lookAhead(isUnParenthesizedAsyncArrowFunctionWorker) === Tristate.True) { + const pos = getNodePos(); + const asyncModifier = parseModifiersForArrowFunction(); const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); - if (!scanner.hasPrecedingLineBreak() && expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { - return Tristate.True; - } + return parseSimpleArrowFunctionExpression(pos, expr as Identifier, asyncModifier); } - - return Tristate.False; } + return undefined; + } - function parseParenthesizedArrowFunctionExpression(allowAmbiguity: boolean): ArrowFunction | undefined { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const modifiers = parseModifiersForArrowFunction(); - const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; - // Arrow functions are never generators. - // - // If we're speculatively parsing a signature for a parenthesized arrow function, then - // we have to have a complete parameter list. Otherwise we might see something like - // a => (b => c) - // And think that "(b =>" was actually a parenthesized arrow function with a missing - // close paren. - const typeParameters = parseTypeParameters(); - - let parameters: NodeArray; - if (!parseExpected(SyntaxKind.OpenParenToken)) { - if (!allowAmbiguity) { - return undefined; - } - parameters = createMissingList(); + function isUnParenthesizedAsyncArrowFunctionWorker(): Tristate { + // AsyncArrowFunctionExpression: + // 1) async[no LineTerminator here]AsyncArrowBindingIdentifier[?Yield][no LineTerminator here]=>AsyncConciseBody[?In] + // 2) CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await][no LineTerminator here]=>AsyncConciseBody[?In] + if (token() === SyntaxKind.AsyncKeyword) { + nextToken(); + // If the "async" is followed by "=>" token then it is not a beginning of an async arrow-function + // but instead a simple arrow-function which will be parsed inside "parseAssignmentExpressionOrHigher" + if (scanner.hasPrecedingLineBreak() || token() === SyntaxKind.EqualsGreaterThanToken) { + return Tristate.False; } - else { - parameters = parseParametersWorker(isAsync); - if (!parseExpected(SyntaxKind.CloseParenToken) && !allowAmbiguity) { - return undefined; - } + // Check for un-parenthesized AsyncArrowFunction + const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); + if (!scanner.hasPrecedingLineBreak() && expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { + return Tristate.True; } + } - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - if (type && !allowAmbiguity && typeHasArrowFunctionBlockingParseError(type)) { + return Tristate.False; + } + + function parseParenthesizedArrowFunctionExpression(allowAmbiguity: boolean): ArrowFunction | undefined { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const modifiers = parseModifiersForArrowFunction(); + const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; + // Arrow functions are never generators. + // + // If we're speculatively parsing a signature for a parenthesized arrow function, then + // we have to have a complete parameter list. Otherwise we might see something like + // a => (b => c) + // And think that "(b =>" was actually a parenthesized arrow function with a missing + // close paren. + const typeParameters = parseTypeParameters(); + + let parameters: NodeArray; + if (!parseExpected(SyntaxKind.OpenParenToken)) { + if (!allowAmbiguity) { return undefined; } - - // Parsing a signature isn't enough. - // Parenthesized arrow signatures often look like other valid expressions. - // For instance: - // - "(x = 10)" is an assignment expression parsed as a signature with a default parameter value. - // - "(x,y)" is a comma expression parsed as a signature with two parameters. - // - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation. - // - "a ? (b): function() {}" will too, since function() is a valid JSDoc function type. - // - // So we need just a bit of lookahead to ensure that it can only be a signature. - const hasJSDocFunctionType = type && isJSDocFunctionType(type); - if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && (hasJSDocFunctionType || token() !== SyntaxKind.OpenBraceToken)) { - // Returning undefined here will cause our caller to rewind to where we started from. - return undefined; + parameters = createMissingList(); + } + else { + parameters = parseParametersWorker(isAsync); + if (!parseExpected(SyntaxKind.CloseParenToken) && !allowAmbiguity) { + return undefined; } + } - // If we have an arrow, then try to parse the body. Even if not, try to parse if we - // have an opening brace, just in case we're in an error state. - const lastToken = token(); - const equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); - const body = (lastToken === SyntaxKind.EqualsGreaterThanToken || lastToken === SyntaxKind.OpenBraceToken) - ? parseArrowFunctionExpressionBody(some(modifiers, isAsyncModifier)) - : parseIdentifier(); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + if (type && !allowAmbiguity && typeHasArrowFunctionBlockingParseError(type)) { + return undefined; + } - const node = factory.createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body); - return withJSDoc(finishNode(node, pos), hasJSDoc); + // Parsing a signature isn't enough. + // Parenthesized arrow signatures often look like other valid expressions. + // For instance: + // - "(x = 10)" is an assignment expression parsed as a signature with a default parameter value. + // - "(x,y)" is a comma expression parsed as a signature with two parameters. + // - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation. + // - "a ? (b): function() {}" will too, since function() is a valid JSDoc function type. + // + // So we need just a bit of lookahead to ensure that it can only be a signature. + const hasJSDocFunctionType = type && isJSDocFunctionType(type); + if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && (hasJSDocFunctionType || token() !== SyntaxKind.OpenBraceToken)) { + // Returning undefined here will cause our caller to rewind to where we started from. + return undefined; } - function parseArrowFunctionExpressionBody(isAsync: boolean): Block | Expression { - if (token() === SyntaxKind.OpenBraceToken) { - return parseFunctionBlock(isAsync ? SignatureFlags.Await : SignatureFlags.None); - } + // If we have an arrow, then try to parse the body. Even if not, try to parse if we + // have an opening brace, just in case we're in an error state. + const lastToken = token(); + const equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); + const body = (lastToken === SyntaxKind.EqualsGreaterThanToken || lastToken === SyntaxKind.OpenBraceToken) + ? parseArrowFunctionExpressionBody(some(modifiers, isAsyncModifier)) + : parseIdentifier(); - if (token() !== SyntaxKind.SemicolonToken && - token() !== SyntaxKind.FunctionKeyword && - token() !== SyntaxKind.ClassKeyword && - isStartOfStatement() && - !isStartOfExpressionStatement()) { - // Check if we got a plain statement (i.e. no expression-statements, no function/class expressions/declarations) - // - // Here we try to recover from a potential error situation in the case where the - // user meant to supply a block. For example, if the user wrote: - // - // a => - // let v = 0; - // } - // - // they may be missing an open brace. Check to see if that's the case so we can - // try to recover better. If we don't do this, then the next close curly we see may end - // up preemptively closing the containing construct. - // - // Note: even when 'IgnoreMissingOpenBrace' is passed, parseBody will still error. - return parseFunctionBlock(SignatureFlags.IgnoreMissingOpenBrace | (isAsync ? SignatureFlags.Await : SignatureFlags.None)); - } + const node = factory.createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - const savedTopLevel = topLevel; - topLevel = false; - const node = isAsync - ? doInAwaitContext(parseAssignmentExpressionOrHigher) - : doOutsideOfAwaitContext(parseAssignmentExpressionOrHigher); - topLevel = savedTopLevel; - return node; + function parseArrowFunctionExpressionBody(isAsync: boolean): Block | Expression { + if (token() === SyntaxKind.OpenBraceToken) { + return parseFunctionBlock(isAsync ? SignatureFlags.Await : SignatureFlags.None); } - function parseConditionalExpressionRest(leftOperand: Expression, pos: number): Expression { - // Note: we are passed in an expression which was produced from parseBinaryExpressionOrHigher. - const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - if (!questionToken) { - return leftOperand; - } - - // Note: we explicitly 'allowIn' in the whenTrue part of the condition expression, and - // we do not that for the 'whenFalse' part. - let colonToken; - return finishNode( - factory.createConditionalExpression( - leftOperand, - questionToken, - doOutsideOfContext(disallowInAndDecoratorContext, parseAssignmentExpressionOrHigher), - colonToken = parseExpectedToken(SyntaxKind.ColonToken), - nodeIsPresent(colonToken) - ? parseAssignmentExpressionOrHigher() - : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)) - ), - pos - ); + if (token() !== SyntaxKind.SemicolonToken && + token() !== SyntaxKind.FunctionKeyword && + token() !== SyntaxKind.ClassKeyword && + isStartOfStatement() && + !isStartOfExpressionStatement()) { + // Check if we got a plain statement (i.e. no expression-statements, no function/class expressions/declarations) + // + // Here we try to recover from a potential error situation in the case where the + // user meant to supply a block. For example, if the user wrote: + // + // a => + // let v = 0; + // } + // + // they may be missing an open brace. Check to see if that's the case so we can + // try to recover better. If we don't do this, then the next close curly we see may end + // up preemptively closing the containing construct. + // + // Note: even when 'IgnoreMissingOpenBrace' is passed, parseBody will still error. + return parseFunctionBlock(SignatureFlags.IgnoreMissingOpenBrace | (isAsync ? SignatureFlags.Await : SignatureFlags.None)); } - function parseBinaryExpressionOrHigher(precedence: OperatorPrecedence): Expression { - const pos = getNodePos(); - const leftOperand = parseUnaryExpressionOrHigher(); - return parseBinaryExpressionRest(precedence, leftOperand, pos); - } + const savedTopLevel = topLevel; + topLevel = false; + const node = isAsync + ? doInAwaitContext(parseAssignmentExpressionOrHigher) + : doOutsideOfAwaitContext(parseAssignmentExpressionOrHigher); + topLevel = savedTopLevel; + return node; + } - function isInOrOfKeyword(t: SyntaxKind) { - return t === SyntaxKind.InKeyword || t === SyntaxKind.OfKeyword; + function parseConditionalExpressionRest(leftOperand: Expression, pos: number): Expression { + // Note: we are passed in an expression which was produced from parseBinaryExpressionOrHigher. + const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + if (!questionToken) { + return leftOperand; } - function parseBinaryExpressionRest(precedence: OperatorPrecedence, leftOperand: Expression, pos: number): Expression { - while (true) { - // We either have a binary operator here, or we're finished. We call - // reScanGreaterToken so that we merge token sequences like > and = into >= - - reScanGreaterToken(); - const newPrecedence = getBinaryOperatorPrecedence(token()); - - // Check the precedence to see if we should "take" this operator - // - For left associative operator (all operator but **), consume the operator, - // recursively call the function below, and parse binaryExpression as a rightOperand - // of the caller if the new precedence of the operator is greater then or equal to the current precedence. - // For example: - // a - b - c; - // ^token; leftOperand = b. Return b to the caller as a rightOperand - // a * b - c - // ^token; leftOperand = b. Return b to the caller as a rightOperand - // a - b * c; - // ^token; leftOperand = b. Return b * c to the caller as a rightOperand - // - For right associative operator (**), consume the operator, recursively call the function - // and parse binaryExpression as a rightOperand of the caller if the new precedence of - // the operator is strictly grater than the current precedence - // For example: - // a ** b ** c; - // ^^token; leftOperand = b. Return b ** c to the caller as a rightOperand - // a - b ** c; - // ^^token; leftOperand = b. Return b ** c to the caller as a rightOperand - // a ** b - c - // ^token; leftOperand = b. Return b to the caller as a rightOperand - const consumeCurrentOperator = token() === SyntaxKind.AsteriskAsteriskToken ? - newPrecedence >= precedence : - newPrecedence > precedence; - - if (!consumeCurrentOperator) { - break; - } + // Note: we explicitly 'allowIn' in the whenTrue part of the condition expression, and + // we do not that for the 'whenFalse' part. + let colonToken; + return finishNode( + factory.createConditionalExpression( + leftOperand, + questionToken, + doOutsideOfContext(disallowInAndDecoratorContext, parseAssignmentExpressionOrHigher), + colonToken = parseExpectedToken(SyntaxKind.ColonToken), + nodeIsPresent(colonToken) + ? parseAssignmentExpressionOrHigher() + : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)) + ), + pos + ); + } - if (token() === SyntaxKind.InKeyword && inDisallowInContext()) { - break; - } + function parseBinaryExpressionOrHigher(precedence: OperatorPrecedence): Expression { + const pos = getNodePos(); + const leftOperand = parseUnaryExpressionOrHigher(); + return parseBinaryExpressionRest(precedence, leftOperand, pos); + } - if (token() === SyntaxKind.AsKeyword) { - // Make sure we *do* perform ASI for constructs like this: - // var x = foo - // as (Bar) - // This should be parsed as an initialized variable, followed - // by a function call to 'as' with the argument 'Bar' - if (scanner.hasPrecedingLineBreak()) { - break; - } - else { - nextToken(); - leftOperand = makeAsExpression(leftOperand, parseType()); - } + function isInOrOfKeyword(t: SyntaxKind) { + return t === SyntaxKind.InKeyword || t === SyntaxKind.OfKeyword; + } + + function parseBinaryExpressionRest(precedence: OperatorPrecedence, leftOperand: Expression, pos: number): Expression { + while (true) { + // We either have a binary operator here, or we're finished. We call + // reScanGreaterToken so that we merge token sequences like > and = into >= + + reScanGreaterToken(); + const newPrecedence = getBinaryOperatorPrecedence(token()); + + // Check the precedence to see if we should "take" this operator + // - For left associative operator (all operator but **), consume the operator, + // recursively call the function below, and parse binaryExpression as a rightOperand + // of the caller if the new precedence of the operator is greater then or equal to the current precedence. + // For example: + // a - b - c; + // ^token; leftOperand = b. Return b to the caller as a rightOperand + // a * b - c + // ^token; leftOperand = b. Return b to the caller as a rightOperand + // a - b * c; + // ^token; leftOperand = b. Return b * c to the caller as a rightOperand + // - For right associative operator (**), consume the operator, recursively call the function + // and parse binaryExpression as a rightOperand of the caller if the new precedence of + // the operator is strictly grater than the current precedence + // For example: + // a ** b ** c; + // ^^token; leftOperand = b. Return b ** c to the caller as a rightOperand + // a - b ** c; + // ^^token; leftOperand = b. Return b ** c to the caller as a rightOperand + // a ** b - c + // ^token; leftOperand = b. Return b to the caller as a rightOperand + const consumeCurrentOperator = token() === SyntaxKind.AsteriskAsteriskToken ? + newPrecedence >= precedence : + newPrecedence > precedence; + + if (!consumeCurrentOperator) { + break; + } + + if (token() === SyntaxKind.InKeyword && inDisallowInContext()) { + break; + } + + if (token() === SyntaxKind.AsKeyword) { + // Make sure we *do* perform ASI for constructs like this: + // var x = foo + // as (Bar) + // This should be parsed as an initialized variable, followed + // by a function call to 'as' with the argument 'Bar' + if (scanner.hasPrecedingLineBreak()) { + break; } else { - leftOperand = makeBinaryExpression(leftOperand, parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence), pos); + nextToken(); + leftOperand = makeAsExpression(leftOperand, parseType()); } } - - return leftOperand; + else { + leftOperand = makeBinaryExpression(leftOperand, parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence), pos); + } } - function isBinaryOperator() { - if (inDisallowInContext() && token() === SyntaxKind.InKeyword) { - return false; - } + return leftOperand; + } - return getBinaryOperatorPrecedence(token()) > 0; + function isBinaryOperator() { + if (inDisallowInContext() && token() === SyntaxKind.InKeyword) { + return false; } - function makeBinaryExpression(left: Expression, operatorToken: BinaryOperatorToken, right: Expression, pos: number): BinaryExpression { - return finishNode(factory.createBinaryExpression(left, operatorToken, right), pos); - } + return getBinaryOperatorPrecedence(token()) > 0; + } - function makeAsExpression(left: Expression, right: TypeNode): AsExpression { - return finishNode(factory.createAsExpression(left, right), left.pos); - } + function makeBinaryExpression(left: Expression, operatorToken: BinaryOperatorToken, right: Expression, pos: number): BinaryExpression { + return finishNode(factory.createBinaryExpression(left, operatorToken, right), pos); + } - function parsePrefixUnaryExpression() { - const pos = getNodePos(); - return finishNode(factory.createPrefixUnaryExpression(token() as PrefixUnaryOperator, nextTokenAnd(parseSimpleUnaryExpression)), pos); - } + function makeAsExpression(left: Expression, right: TypeNode): AsExpression { + return finishNode(factory.createAsExpression(left, right), left.pos); + } - function parseDeleteExpression() { - const pos = getNodePos(); - return finishNode(factory.createDeleteExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); - } + function parsePrefixUnaryExpression() { + const pos = getNodePos(); + return finishNode(factory.createPrefixUnaryExpression(token() as PrefixUnaryOperator, nextTokenAnd(parseSimpleUnaryExpression)), pos); + } - function parseTypeOfExpression() { - const pos = getNodePos(); - return finishNode(factory.createTypeOfExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); - } + function parseDeleteExpression() { + const pos = getNodePos(); + return finishNode(factory.createDeleteExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); + } - function parseVoidExpression() { - const pos = getNodePos(); - return finishNode(factory.createVoidExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); - } + function parseTypeOfExpression() { + const pos = getNodePos(); + return finishNode(factory.createTypeOfExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); + } - function isAwaitExpression(): boolean { - if (token() === SyntaxKind.AwaitKeyword) { - if (inAwaitContext()) { - return true; - } + function parseVoidExpression() { + const pos = getNodePos(); + return finishNode(factory.createVoidExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); + } - // here we are using similar heuristics as 'isYieldExpression' - return lookAhead(nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine); + function isAwaitExpression(): boolean { + if (token() === SyntaxKind.AwaitKeyword) { + if (inAwaitContext()) { + return true; } - return false; + // here we are using similar heuristics as 'isYieldExpression' + return lookAhead(nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine); } - function parseAwaitExpression() { - const pos = getNodePos(); - return finishNode(factory.createAwaitExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); - } + return false; + } + + function parseAwaitExpression() { + const pos = getNodePos(); + return finishNode(factory.createAwaitExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); + } + /** + * Parse ES7 exponential expression and await expression + * + * ES7 ExponentiationExpression: + * 1) UnaryExpression[?Yield] + * 2) UpdateExpression[?Yield] ** ExponentiationExpression[?Yield] + * + */ + function parseUnaryExpressionOrHigher(): UnaryExpression | BinaryExpression { /** - * Parse ES7 exponential expression and await expression - * - * ES7 ExponentiationExpression: - * 1) UnaryExpression[?Yield] - * 2) UpdateExpression[?Yield] ** ExponentiationExpression[?Yield] - * + * ES7 UpdateExpression: + * 1) LeftHandSideExpression[?Yield] + * 2) LeftHandSideExpression[?Yield][no LineTerminator here]++ + * 3) LeftHandSideExpression[?Yield][no LineTerminator here]-- + * 4) ++UnaryExpression[?Yield] + * 5) --UnaryExpression[?Yield] */ - function parseUnaryExpressionOrHigher(): UnaryExpression | BinaryExpression { - /** - * ES7 UpdateExpression: - * 1) LeftHandSideExpression[?Yield] - * 2) LeftHandSideExpression[?Yield][no LineTerminator here]++ - * 3) LeftHandSideExpression[?Yield][no LineTerminator here]-- - * 4) ++UnaryExpression[?Yield] - * 5) --UnaryExpression[?Yield] - */ - if (isUpdateExpression()) { - const pos = getNodePos(); - const updateExpression = parseUpdateExpression(); - return token() === SyntaxKind.AsteriskAsteriskToken ? - parseBinaryExpressionRest(getBinaryOperatorPrecedence(token()), updateExpression, pos) as BinaryExpression : - updateExpression; - } - - /** - * ES7 UnaryExpression: - * 1) UpdateExpression[?yield] - * 2) delete UpdateExpression[?yield] - * 3) void UpdateExpression[?yield] - * 4) typeof UpdateExpression[?yield] - * 5) + UpdateExpression[?yield] - * 6) - UpdateExpression[?yield] - * 7) ~ UpdateExpression[?yield] - * 8) ! UpdateExpression[?yield] - */ - const unaryOperator = token(); - const simpleUnaryExpression = parseSimpleUnaryExpression(); - if (token() === SyntaxKind.AsteriskAsteriskToken) { - const pos = skipTrivia(sourceText, simpleUnaryExpression.pos); - const { end } = simpleUnaryExpression; - if (simpleUnaryExpression.kind === SyntaxKind.TypeAssertionExpression) { - parseErrorAt(pos, end, Diagnostics.A_type_assertion_expression_is_not_allowed_in_the_left_hand_side_of_an_exponentiation_expression_Consider_enclosing_the_expression_in_parentheses); - } - else { - parseErrorAt(pos, end, Diagnostics.An_unary_expression_with_the_0_operator_is_not_allowed_in_the_left_hand_side_of_an_exponentiation_expression_Consider_enclosing_the_expression_in_parentheses, tokenToString(unaryOperator)); - } - } - return simpleUnaryExpression; + if (isUpdateExpression()) { + const pos = getNodePos(); + const updateExpression = parseUpdateExpression(); + return token() === SyntaxKind.AsteriskAsteriskToken ? + parseBinaryExpressionRest(getBinaryOperatorPrecedence(token()), updateExpression, pos) as BinaryExpression : + updateExpression; } /** - * Parse ES7 simple-unary expression or higher: - * * ES7 UnaryExpression: * 1) UpdateExpression[?yield] - * 2) delete UnaryExpression[?yield] - * 3) void UnaryExpression[?yield] - * 4) typeof UnaryExpression[?yield] - * 5) + UnaryExpression[?yield] - * 6) - UnaryExpression[?yield] - * 7) ~ UnaryExpression[?yield] - * 8) ! UnaryExpression[?yield] - * 9) [+Await] await UnaryExpression[?yield] + * 2) delete UpdateExpression[?yield] + * 3) void UpdateExpression[?yield] + * 4) typeof UpdateExpression[?yield] + * 5) + UpdateExpression[?yield] + * 6) - UpdateExpression[?yield] + * 7) ~ UpdateExpression[?yield] + * 8) ! UpdateExpression[?yield] */ - function parseSimpleUnaryExpression(): UnaryExpression { - switch (token()) { - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - case SyntaxKind.ExclamationToken: - return parsePrefixUnaryExpression(); - case SyntaxKind.DeleteKeyword: - return parseDeleteExpression(); - case SyntaxKind.TypeOfKeyword: - return parseTypeOfExpression(); - case SyntaxKind.VoidKeyword: - return parseVoidExpression(); - case SyntaxKind.LessThanToken: - // This is modified UnaryExpression grammar in TypeScript - // UnaryExpression (modified): - // < type > UnaryExpression - return parseTypeAssertion(); - case SyntaxKind.AwaitKeyword: - if (isAwaitExpression()) { - return parseAwaitExpression(); - } - // falls through - default: - return parseUpdateExpression(); + const unaryOperator = token(); + const simpleUnaryExpression = parseSimpleUnaryExpression(); + if (token() === SyntaxKind.AsteriskAsteriskToken) { + const pos = skipTrivia(sourceText, simpleUnaryExpression.pos); + const { end } = simpleUnaryExpression; + if (simpleUnaryExpression.kind === SyntaxKind.TypeAssertionExpression) { + parseErrorAt(pos, end, Diagnostics.A_type_assertion_expression_is_not_allowed_in_the_left_hand_side_of_an_exponentiation_expression_Consider_enclosing_the_expression_in_parentheses); + } + else { + parseErrorAt(pos, end, Diagnostics.An_unary_expression_with_the_0_operator_is_not_allowed_in_the_left_hand_side_of_an_exponentiation_expression_Consider_enclosing_the_expression_in_parentheses, tokenToString(unaryOperator)); } } + return simpleUnaryExpression; + } - /** - * Check if the current token can possibly be an ES7 increment expression. - * - * ES7 UpdateExpression: - * LeftHandSideExpression[?Yield] - * LeftHandSideExpression[?Yield][no LineTerminator here]++ - * LeftHandSideExpression[?Yield][no LineTerminator here]-- - * ++LeftHandSideExpression[?Yield] - * --LeftHandSideExpression[?Yield] - */ - function isUpdateExpression(): boolean { - // This function is called inside parseUnaryExpression to decide - // whether to call parseSimpleUnaryExpression or call parseUpdateExpression directly - switch (token()) { - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - case SyntaxKind.ExclamationToken: - case SyntaxKind.DeleteKeyword: - case SyntaxKind.TypeOfKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.AwaitKeyword: - return false; - case SyntaxKind.LessThanToken: - // If we are not in JSX context, we are parsing TypeAssertion which is an UnaryExpression - if (languageVariant !== LanguageVariant.JSX) { - return false; - } - // We are in JSX context and the token is part of JSXElement. - // falls through - default: - return true; - } + /** + * Parse ES7 simple-unary expression or higher: + * + * ES7 UnaryExpression: + * 1) UpdateExpression[?yield] + * 2) delete UnaryExpression[?yield] + * 3) void UnaryExpression[?yield] + * 4) typeof UnaryExpression[?yield] + * 5) + UnaryExpression[?yield] + * 6) - UnaryExpression[?yield] + * 7) ~ UnaryExpression[?yield] + * 8) ! UnaryExpression[?yield] + * 9) [+Await] await UnaryExpression[?yield] + */ + function parseSimpleUnaryExpression(): UnaryExpression { + switch (token()) { + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.TildeToken: + case SyntaxKind.ExclamationToken: + return parsePrefixUnaryExpression(); + case SyntaxKind.DeleteKeyword: + return parseDeleteExpression(); + case SyntaxKind.TypeOfKeyword: + return parseTypeOfExpression(); + case SyntaxKind.VoidKeyword: + return parseVoidExpression(); + case SyntaxKind.LessThanToken: + // This is modified UnaryExpression grammar in TypeScript + // UnaryExpression (modified): + // < type > UnaryExpression + return parseTypeAssertion(); + case SyntaxKind.AwaitKeyword: + if (isAwaitExpression()) { + return parseAwaitExpression(); + } + // falls through + default: + return parseUpdateExpression(); } + } - /** - * Parse ES7 UpdateExpression. UpdateExpression is used instead of ES6's PostFixExpression. - * - * ES7 UpdateExpression[yield]: - * 1) LeftHandSideExpression[?yield] - * 2) LeftHandSideExpression[?yield] [[no LineTerminator here]]++ - * 3) LeftHandSideExpression[?yield] [[no LineTerminator here]]-- - * 4) ++LeftHandSideExpression[?yield] - * 5) --LeftHandSideExpression[?yield] - * In TypeScript (2), (3) are parsed as PostfixUnaryExpression. (4), (5) are parsed as PrefixUnaryExpression - */ - function parseUpdateExpression(): UpdateExpression { - if (token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) { - const pos = getNodePos(); - return finishNode(factory.createPrefixUnaryExpression(token() as PrefixUnaryOperator, nextTokenAnd(parseLeftHandSideExpressionOrHigher)), pos); - } - else if (languageVariant === LanguageVariant.JSX && token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsIdentifierOrKeywordOrGreaterThan)) { - // JSXElement is part of primaryExpression - return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true); - } + /** + * Check if the current token can possibly be an ES7 increment expression. + * + * ES7 UpdateExpression: + * LeftHandSideExpression[?Yield] + * LeftHandSideExpression[?Yield][no LineTerminator here]++ + * LeftHandSideExpression[?Yield][no LineTerminator here]-- + * ++LeftHandSideExpression[?Yield] + * --LeftHandSideExpression[?Yield] + */ + function isUpdateExpression(): boolean { + // This function is called inside parseUnaryExpression to decide + // whether to call parseSimpleUnaryExpression or call parseUpdateExpression directly + switch (token()) { + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.TildeToken: + case SyntaxKind.ExclamationToken: + case SyntaxKind.DeleteKeyword: + case SyntaxKind.TypeOfKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.AwaitKeyword: + return false; + case SyntaxKind.LessThanToken: + // If we are not in JSX context, we are parsing TypeAssertion which is an UnaryExpression + if (languageVariant !== LanguageVariant.JSX) { + return false; + } + // We are in JSX context and the token is part of JSXElement. + // falls through + default: + return true; + } + } - const expression = parseLeftHandSideExpressionOrHigher(); + /** + * Parse ES7 UpdateExpression. UpdateExpression is used instead of ES6's PostFixExpression. + * + * ES7 UpdateExpression[yield]: + * 1) LeftHandSideExpression[?yield] + * 2) LeftHandSideExpression[?yield] [[no LineTerminator here]]++ + * 3) LeftHandSideExpression[?yield] [[no LineTerminator here]]-- + * 4) ++LeftHandSideExpression[?yield] + * 5) --LeftHandSideExpression[?yield] + * In TypeScript (2), (3) are parsed as PostfixUnaryExpression. (4), (5) are parsed as PrefixUnaryExpression + */ + function parseUpdateExpression(): UpdateExpression { + if (token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) { + const pos = getNodePos(); + return finishNode(factory.createPrefixUnaryExpression(token() as PrefixUnaryOperator, nextTokenAnd(parseLeftHandSideExpressionOrHigher)), pos); + } + else if (languageVariant === LanguageVariant.JSX && token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsIdentifierOrKeywordOrGreaterThan)) { + // JSXElement is part of primaryExpression + return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true); + } - Debug.assert(isLeftHandSideExpression(expression)); - if ((token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) && !scanner.hasPrecedingLineBreak()) { - const operator = token() as PostfixUnaryOperator; - nextToken(); - return finishNode(factory.createPostfixUnaryExpression(expression, operator), expression.pos); - } + const expression = parseLeftHandSideExpressionOrHigher(); - return expression; + Debug.assert(isLeftHandSideExpression(expression)); + if ((token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) && !scanner.hasPrecedingLineBreak()) { + const operator = token() as PostfixUnaryOperator; + nextToken(); + return finishNode(factory.createPostfixUnaryExpression(expression, operator), expression.pos); } - function parseLeftHandSideExpressionOrHigher(): LeftHandSideExpression { - // Original Ecma: - // LeftHandSideExpression: See 11.2 - // NewExpression - // CallExpression - // - // Our simplification: - // - // LeftHandSideExpression: See 11.2 - // MemberExpression - // CallExpression - // - // See comment in parseMemberExpressionOrHigher on how we replaced NewExpression with - // MemberExpression to make our lives easier. - // - // to best understand the below code, it's important to see how CallExpression expands - // out into its own productions: - // - // CallExpression: - // MemberExpression Arguments - // CallExpression Arguments - // CallExpression[Expression] - // CallExpression.IdentifierName - // import (AssignmentExpression) - // super Arguments - // super.IdentifierName - // - // Because of the recursion in these calls, we need to bottom out first. There are three - // bottom out states we can run into: 1) We see 'super' which must start either of - // the last two CallExpression productions. 2) We see 'import' which must start import call. - // 3)we have a MemberExpression which either completes the LeftHandSideExpression, - // or starts the beginning of the first four CallExpression productions. - const pos = getNodePos(); - let expression: MemberExpression; - if (token() === SyntaxKind.ImportKeyword) { - if (lookAhead(nextTokenIsOpenParenOrLessThan)) { - // We don't want to eagerly consume all import keyword as import call expression so we look ahead to find "(" - // For example: - // var foo3 = require("subfolder - // import * as foo1 from "module-from-node - // We want this import to be a statement rather than import call expression - sourceFlags |= NodeFlags.PossiblyContainsDynamicImport; - expression = parseTokenNode(); - } - else if (lookAhead(nextTokenIsDot)) { - // This is an 'import.*' metaproperty (i.e. 'import.meta') - nextToken(); // advance past the 'import' - nextToken(); // advance past the dot - expression = finishNode(factory.createMetaProperty(SyntaxKind.ImportKeyword, parseIdentifierName()), pos); - sourceFlags |= NodeFlags.PossiblyContainsImportMeta; - } - else { - expression = parseMemberExpressionOrHigher(); - } + return expression; + } + + function parseLeftHandSideExpressionOrHigher(): LeftHandSideExpression { + // Original Ecma: + // LeftHandSideExpression: See 11.2 + // NewExpression + // CallExpression + // + // Our simplification: + // + // LeftHandSideExpression: See 11.2 + // MemberExpression + // CallExpression + // + // See comment in parseMemberExpressionOrHigher on how we replaced NewExpression with + // MemberExpression to make our lives easier. + // + // to best understand the below code, it's important to see how CallExpression expands + // out into its own productions: + // + // CallExpression: + // MemberExpression Arguments + // CallExpression Arguments + // CallExpression[Expression] + // CallExpression.IdentifierName + // import (AssignmentExpression) + // super Arguments + // super.IdentifierName + // + // Because of the recursion in these calls, we need to bottom out first. There are three + // bottom out states we can run into: 1) We see 'super' which must start either of + // the last two CallExpression productions. 2) We see 'import' which must start import call. + // 3)we have a MemberExpression which either completes the LeftHandSideExpression, + // or starts the beginning of the first four CallExpression productions. + const pos = getNodePos(); + let expression: MemberExpression; + if (token() === SyntaxKind.ImportKeyword) { + if (lookAhead(nextTokenIsOpenParenOrLessThan)) { + // We don't want to eagerly consume all import keyword as import call expression so we look ahead to find "(" + // For example: + // var foo3 = require("subfolder + // import * as foo1 from "module-from-node + // We want this import to be a statement rather than import call expression + sourceFlags |= NodeFlags.PossiblyContainsDynamicImport; + expression = parseTokenNode(); + } + else if (lookAhead(nextTokenIsDot)) { + // This is an 'import.*' metaproperty (i.e. 'import.meta') + nextToken(); // advance past the 'import' + nextToken(); // advance past the dot + expression = finishNode(factory.createMetaProperty(SyntaxKind.ImportKeyword, parseIdentifierName()), pos); + sourceFlags |= NodeFlags.PossiblyContainsImportMeta; } else { - expression = token() === SyntaxKind.SuperKeyword ? parseSuperExpression() : parseMemberExpressionOrHigher(); + expression = parseMemberExpressionOrHigher(); } - - // Now, we *may* be complete. However, we might have consumed the start of a - // CallExpression or OptionalExpression. As such, we need to consume the rest - // of it here to be complete. - return parseCallExpressionRest(pos, expression); } - - function parseMemberExpressionOrHigher(): MemberExpression { - // Note: to make our lives simpler, we decompose the NewExpression productions and - // place ObjectCreationExpression and FunctionExpression into PrimaryExpression. - // like so: - // - // PrimaryExpression : See 11.1 - // this - // Identifier - // Literal - // ArrayLiteral - // ObjectLiteral - // (Expression) - // FunctionExpression - // new MemberExpression Arguments? - // - // MemberExpression : See 11.2 - // PrimaryExpression - // MemberExpression[Expression] - // MemberExpression.IdentifierName - // - // CallExpression : See 11.2 - // MemberExpression - // CallExpression Arguments - // CallExpression[Expression] - // CallExpression.IdentifierName - // - // Technically this is ambiguous. i.e. CallExpression defines: - // - // CallExpression: - // CallExpression Arguments - // - // If you see: "new Foo()" - // - // Then that could be treated as a single ObjectCreationExpression, or it could be - // treated as the invocation of "new Foo". We disambiguate that in code (to match - // the original grammar) by making sure that if we see an ObjectCreationExpression - // we always consume arguments if they are there. So we treat "new Foo()" as an - // object creation only, and not at all as an invocation. Another way to think - // about this is that for every "new" that we see, we will consume an argument list if - // it is there as part of the *associated* object creation node. Any additional - // argument lists we see, will become invocation expressions. - // - // Because there are no other places in the grammar now that refer to FunctionExpression - // or ObjectCreationExpression, it is safe to push down into the PrimaryExpression - // production. - // - // Because CallExpression and MemberExpression are left recursive, we need to bottom out - // of the recursion immediately. So we parse out a primary expression to start with. - const pos = getNodePos(); - const expression = parsePrimaryExpression(); - return parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true); + else { + expression = token() === SyntaxKind.SuperKeyword ? parseSuperExpression() : parseMemberExpressionOrHigher(); } - function parseSuperExpression(): MemberExpression { - const pos = getNodePos(); - const expression = parseTokenNode(); - if (token() === SyntaxKind.LessThanToken) { - const startPos = getNodePos(); - const typeArguments = tryParse(parseTypeArgumentsInExpression); - if (typeArguments !== undefined) { - parseErrorAt(startPos, getNodePos(), Diagnostics.super_may_not_use_type_arguments); - } - } + // Now, we *may* be complete. However, we might have consumed the start of a + // CallExpression or OptionalExpression. As such, we need to consume the rest + // of it here to be complete. + return parseCallExpressionRest(pos, expression); + } + + function parseMemberExpressionOrHigher(): MemberExpression { + // Note: to make our lives simpler, we decompose the NewExpression productions and + // place ObjectCreationExpression and FunctionExpression into PrimaryExpression. + // like so: + // + // PrimaryExpression : See 11.1 + // this + // Identifier + // Literal + // ArrayLiteral + // ObjectLiteral + // (Expression) + // FunctionExpression + // new MemberExpression Arguments? + // + // MemberExpression : See 11.2 + // PrimaryExpression + // MemberExpression[Expression] + // MemberExpression.IdentifierName + // + // CallExpression : See 11.2 + // MemberExpression + // CallExpression Arguments + // CallExpression[Expression] + // CallExpression.IdentifierName + // + // Technically this is ambiguous. i.e. CallExpression defines: + // + // CallExpression: + // CallExpression Arguments + // + // If you see: "new Foo()" + // + // Then that could be treated as a single ObjectCreationExpression, or it could be + // treated as the invocation of "new Foo". We disambiguate that in code (to match + // the original grammar) by making sure that if we see an ObjectCreationExpression + // we always consume arguments if they are there. So we treat "new Foo()" as an + // object creation only, and not at all as an invocation. Another way to think + // about this is that for every "new" that we see, we will consume an argument list if + // it is there as part of the *associated* object creation node. Any additional + // argument lists we see, will become invocation expressions. + // + // Because there are no other places in the grammar now that refer to FunctionExpression + // or ObjectCreationExpression, it is safe to push down into the PrimaryExpression + // production. + // + // Because CallExpression and MemberExpression are left recursive, we need to bottom out + // of the recursion immediately. So we parse out a primary expression to start with. + const pos = getNodePos(); + const expression = parsePrimaryExpression(); + return parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true); + } - if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.DotToken || token() === SyntaxKind.OpenBracketToken) { - return expression; + function parseSuperExpression(): MemberExpression { + const pos = getNodePos(); + const expression = parseTokenNode(); + if (token() === SyntaxKind.LessThanToken) { + const startPos = getNodePos(); + const typeArguments = tryParse(parseTypeArgumentsInExpression); + if (typeArguments !== undefined) { + parseErrorAt(startPos, getNodePos(), Diagnostics.super_may_not_use_type_arguments); } + } - // If we have seen "super" it must be followed by '(' or '.'. - // If it wasn't then just try to parse out a '.' and report an error. - parseExpectedToken(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access); - // private names will never work with `super` (`super.#foo`), but that's a semantic error, not syntactic - return finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true)), pos); + if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.DotToken || token() === SyntaxKind.OpenBracketToken) { + return expression; } - function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean, topInvalidNodePosition?: number, openingTag?: JsxOpeningElement | JsxOpeningFragment): JsxElement | JsxSelfClosingElement | JsxFragment { - const pos = getNodePos(); - const opening = parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext); - let result: JsxElement | JsxSelfClosingElement | JsxFragment; - if (opening.kind === SyntaxKind.JsxOpeningElement) { - let children = parseJsxChildren(opening); - let closingElement: JsxClosingElement; - - const lastChild: JsxChild | undefined = children[children.length - 1]; - if (lastChild?.kind === SyntaxKind.JsxElement - && !tagNamesAreEquivalent(lastChild.openingElement.tagName, lastChild.closingElement.tagName) - && tagNamesAreEquivalent(opening.tagName, lastChild.closingElement.tagName)) { - // when an unclosed JsxOpeningElement incorrectly parses its parent's JsxClosingElement, - // restructure (
(......
)) --> (
(......)
) - // (no need to error; the parent will error) - const end = lastChild.children.end; - const newLast = finishNode(factory.createJsxElement( - lastChild.openingElement, - lastChild.children, - finishNode(factory.createJsxClosingElement(finishNode(factory.createIdentifier(""), end, end)), end, end)), - lastChild.openingElement.pos, - end); - - children = createNodeArray([...children.slice(0, children.length - 1), newLast], children.pos, end); - closingElement = lastChild.closingElement; - } - else { - closingElement = parseJsxClosingElement(opening, inExpressionContext); - if (!tagNamesAreEquivalent(opening.tagName, closingElement.tagName)) { - if (openingTag && isJsxOpeningElement(openingTag) && tagNamesAreEquivalent(closingElement.tagName, openingTag.tagName)) { - // opening incorrectly matched with its parent's closing -- put error on opening - parseErrorAtRange(opening.tagName, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, opening.tagName)); - } - else { - // other opening/closing mismatches -- put error on closing - parseErrorAtRange(closingElement.tagName, Diagnostics.Expected_corresponding_JSX_closing_tag_for_0, getTextOfNodeFromSourceText(sourceText, opening.tagName)); - } - } - } - result = finishNode(factory.createJsxElement(opening, children, closingElement), pos); - } - else if (opening.kind === SyntaxKind.JsxOpeningFragment) { - result = finishNode(factory.createJsxFragment(opening, parseJsxChildren(opening), parseJsxClosingFragment(inExpressionContext)), pos); + // If we have seen "super" it must be followed by '(' or '.'. + // If it wasn't then just try to parse out a '.' and report an error. + parseExpectedToken(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access); + // private names will never work with `super` (`super.#foo`), but that's a semantic error, not syntactic + return finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true)), pos); + } + + function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean, topInvalidNodePosition?: number, openingTag?: JsxOpeningElement | JsxOpeningFragment): JsxElement | JsxSelfClosingElement | JsxFragment { + const pos = getNodePos(); + const opening = parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext); + let result: JsxElement | JsxSelfClosingElement | JsxFragment; + if (opening.kind === SyntaxKind.JsxOpeningElement) { + let children = parseJsxChildren(opening); + let closingElement: JsxClosingElement; + + const lastChild: JsxChild | undefined = children[children.length - 1]; + if (lastChild?.kind === SyntaxKind.JsxElement + && !tagNamesAreEquivalent(lastChild.openingElement.tagName, lastChild.closingElement.tagName) + && tagNamesAreEquivalent(opening.tagName, lastChild.closingElement.tagName)) { + // when an unclosed JsxOpeningElement incorrectly parses its parent's JsxClosingElement, + // restructure (
(......
)) --> (
(......)
) + // (no need to error; the parent will error) + const end = lastChild.children.end; + const newLast = finishNode(factory.createJsxElement( + lastChild.openingElement, + lastChild.children, + finishNode(factory.createJsxClosingElement(finishNode(factory.createIdentifier(""), end, end)), end, end)), + lastChild.openingElement.pos, + end); + + children = createNodeArray([...children.slice(0, children.length - 1), newLast], children.pos, end); + closingElement = lastChild.closingElement; } else { - Debug.assert(opening.kind === SyntaxKind.JsxSelfClosingElement); - // Nothing else to do for self-closing elements - result = opening; - } - - // If the user writes the invalid code '
' in an expression context (i.e. not wrapped in - // an enclosing tag), we'll naively try to parse ^ this as a 'less than' operator and the remainder of the tag - // as garbage, which will cause the formatter to badly mangle the JSX. Perform a speculative parse of a JSX - // element if we see a < token so that we can wrap it in a synthetic binary expression so the formatter - // does less damage and we can report a better error. - // Since JSX elements are invalid < operands anyway, this lookahead parse will only occur in error scenarios - // of one sort or another. - if (inExpressionContext && token() === SyntaxKind.LessThanToken) { - const topBadPos = typeof topInvalidNodePosition === "undefined" ? result.pos : topInvalidNodePosition; - const invalidElement = tryParse(() => parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true, topBadPos)); - if (invalidElement) { - const operatorToken = createMissingNode(SyntaxKind.CommaToken, /*reportAtCurrentPosition*/ false); - setTextRangePosWidth(operatorToken, invalidElement.pos, 0); - parseErrorAt(skipTrivia(sourceText, topBadPos), invalidElement.end, Diagnostics.JSX_expressions_must_have_one_parent_element); - return finishNode(factory.createBinaryExpression(result, operatorToken as Token, invalidElement), pos) as Node as JsxElement; - } - } - - return result; - } - - function parseJsxText(): JsxText { - const pos = getNodePos(); - const node = factory.createJsxText(scanner.getTokenValue(), currentToken === SyntaxKind.JsxTextAllWhiteSpaces); - currentToken = scanner.scanJsxToken(); - return finishNode(node, pos); - } - - function parseJsxChild(openingTag: JsxOpeningElement | JsxOpeningFragment, token: JsxTokenSyntaxKind): JsxChild | undefined { - switch (token) { - case SyntaxKind.EndOfFileToken: - // If we hit EOF, issue the error at the tag that lacks the closing element - // rather than at the end of the file (which is useless) - if (isJsxOpeningFragment(openingTag)) { - parseErrorAtRange(openingTag, Diagnostics.JSX_fragment_has_no_corresponding_closing_tag); + closingElement = parseJsxClosingElement(opening, inExpressionContext); + if (!tagNamesAreEquivalent(opening.tagName, closingElement.tagName)) { + if (openingTag && isJsxOpeningElement(openingTag) && tagNamesAreEquivalent(closingElement.tagName, openingTag.tagName)) { + // opening incorrectly matched with its parent's closing -- put error on opening + parseErrorAtRange(opening.tagName, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, opening.tagName)); } else { - // We want the error span to cover only 'Foo.Bar' in < Foo.Bar > - // or to cover only 'Foo' in < Foo > - const tag = openingTag.tagName; - const start = skipTrivia(sourceText, tag.pos); - parseErrorAt(start, tag.end, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, openingTag.tagName)); + // other opening/closing mismatches -- put error on closing + parseErrorAtRange(closingElement.tagName, Diagnostics.Expected_corresponding_JSX_closing_tag_for_0, getTextOfNodeFromSourceText(sourceText, opening.tagName)); } - return undefined; - case SyntaxKind.LessThanSlashToken: - case SyntaxKind.ConflictMarkerTrivia: - return undefined; - case SyntaxKind.JsxText: - case SyntaxKind.JsxTextAllWhiteSpaces: - return parseJsxText(); - case SyntaxKind.OpenBraceToken: - return parseJsxExpression(/*inExpressionContext*/ false); - case SyntaxKind.LessThanToken: - return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ false, /*topInvalidNodePosition*/ undefined, openingTag); - default: - return Debug.assertNever(token); - } - } - - function parseJsxChildren(openingTag: JsxOpeningElement | JsxOpeningFragment): NodeArray { - const list = []; - const listPos = getNodePos(); - const saveParsingContext = parsingContext; - parsingContext |= 1 << ParsingContext.JsxChildren; - - while (true) { - const child = parseJsxChild(openingTag, currentToken = scanner.reScanJsxToken()); - if (!child) break; - list.push(child); - if (isJsxOpeningElement(openingTag) - && child?.kind === SyntaxKind.JsxElement - && !tagNamesAreEquivalent(child.openingElement.tagName, child.closingElement.tagName) - && tagNamesAreEquivalent(openingTag.tagName, child.closingElement.tagName)) { - // stop after parsing a mismatched child like
...(
) in order to reattach the
higher - break; } } - - parsingContext = saveParsingContext; - return createNodeArray(list, listPos); + result = finishNode(factory.createJsxElement(opening, children, closingElement), pos); } - - function parseJsxAttributes(): JsxAttributes { - const pos = getNodePos(); - return finishNode(factory.createJsxAttributes(parseList(ParsingContext.JsxAttributes, parseJsxAttribute)), pos); + else if (opening.kind === SyntaxKind.JsxOpeningFragment) { + result = finishNode(factory.createJsxFragment(opening, parseJsxChildren(opening), parseJsxClosingFragment(inExpressionContext)), pos); + } + else { + Debug.assert(opening.kind === SyntaxKind.JsxSelfClosingElement); + // Nothing else to do for self-closing elements + result = opening; } - function parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement | JsxOpeningFragment { - const pos = getNodePos(); - - parseExpected(SyntaxKind.LessThanToken); - - if (token() === SyntaxKind.GreaterThanToken) { - // See below for explanation of scanJsxText - scanJsxText(); - return finishNode(factory.createJsxOpeningFragment(), pos); + // If the user writes the invalid code '
' in an expression context (i.e. not wrapped in + // an enclosing tag), we'll naively try to parse ^ this as a 'less than' operator and the remainder of the tag + // as garbage, which will cause the formatter to badly mangle the JSX. Perform a speculative parse of a JSX + // element if we see a < token so that we can wrap it in a synthetic binary expression so the formatter + // does less damage and we can report a better error. + // Since JSX elements are invalid < operands anyway, this lookahead parse will only occur in error scenarios + // of one sort or another. + if (inExpressionContext && token() === SyntaxKind.LessThanToken) { + const topBadPos = typeof topInvalidNodePosition === "undefined" ? result.pos : topInvalidNodePosition; + const invalidElement = tryParse(() => parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true, topBadPos)); + if (invalidElement) { + const operatorToken = createMissingNode(SyntaxKind.CommaToken, /*reportAtCurrentPosition*/ false); + setTextRangePosWidth(operatorToken, invalidElement.pos, 0); + parseErrorAt(skipTrivia(sourceText, topBadPos), invalidElement.end, Diagnostics.JSX_expressions_must_have_one_parent_element); + return finishNode(factory.createBinaryExpression(result, operatorToken as Token, invalidElement), pos) as Node as JsxElement; } - const tagName = parseJsxElementName(); - const typeArguments = (contextFlags & NodeFlags.JavaScriptFile) === 0 ? tryParseTypeArguments() : undefined; - const attributes = parseJsxAttributes(); + } - let node: JsxOpeningLikeElement; + return result; + } - if (token() === SyntaxKind.GreaterThanToken) { - // Closing tag, so scan the immediately-following text with the JSX scanning instead - // of regular scanning to avoid treating illegal characters (e.g. '#') as immediate - // scanning errors - scanJsxText(); - node = factory.createJsxOpeningElement(tagName, typeArguments, attributes); - } - else { - parseExpected(SyntaxKind.SlashToken); - if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { - // manually advance the scanner in order to look for jsx text inside jsx - if (inExpressionContext) { - nextToken(); - } - else { - scanJsxText(); - } - } - node = factory.createJsxSelfClosingElement(tagName, typeArguments, attributes); - } + function parseJsxText(): JsxText { + const pos = getNodePos(); + const node = factory.createJsxText(scanner.getTokenValue(), currentToken === SyntaxKind.JsxTextAllWhiteSpaces); + currentToken = scanner.scanJsxToken(); + return finishNode(node, pos); + } - return finishNode(node, pos); + function parseJsxChild(openingTag: JsxOpeningElement | JsxOpeningFragment, token: JsxTokenSyntaxKind): JsxChild | undefined { + switch (token) { + case SyntaxKind.EndOfFileToken: + // If we hit EOF, issue the error at the tag that lacks the closing element + // rather than at the end of the file (which is useless) + if (isJsxOpeningFragment(openingTag)) { + parseErrorAtRange(openingTag, Diagnostics.JSX_fragment_has_no_corresponding_closing_tag); + } + else { + // We want the error span to cover only 'Foo.Bar' in < Foo.Bar > + // or to cover only 'Foo' in < Foo > + const tag = openingTag.tagName; + const start = skipTrivia(sourceText, tag.pos); + parseErrorAt(start, tag.end, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, openingTag.tagName)); + } + return undefined; + case SyntaxKind.LessThanSlashToken: + case SyntaxKind.ConflictMarkerTrivia: + return undefined; + case SyntaxKind.JsxText: + case SyntaxKind.JsxTextAllWhiteSpaces: + return parseJsxText(); + case SyntaxKind.OpenBraceToken: + return parseJsxExpression(/*inExpressionContext*/ false); + case SyntaxKind.LessThanToken: + return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ false, /*topInvalidNodePosition*/ undefined, openingTag); + default: + return Debug.assertNever(token); } + } - function parseJsxElementName(): JsxTagNameExpression { - const pos = getNodePos(); - scanJsxIdentifier(); - // JsxElement can have name in the form of - // propertyAccessExpression - // primaryExpression in the form of an identifier and "this" keyword - // We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword - // We only want to consider "this" as a primaryExpression - let expression: JsxTagNameExpression = token() === SyntaxKind.ThisKeyword ? - parseTokenNode() : parseIdentifierName(); - while (parseOptional(SyntaxKind.DotToken)) { - expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess; + function parseJsxChildren(openingTag: JsxOpeningElement | JsxOpeningFragment): NodeArray { + const list = []; + const listPos = getNodePos(); + const saveParsingContext = parsingContext; + parsingContext |= 1 << ParsingContext.JsxChildren; + + while (true) { + const child = parseJsxChild(openingTag, currentToken = scanner.reScanJsxToken()); + if (!child) break; + list.push(child); + if (isJsxOpeningElement(openingTag) + && child?.kind === SyntaxKind.JsxElement + && !tagNamesAreEquivalent(child.openingElement.tagName, child.closingElement.tagName) + && tagNamesAreEquivalent(openingTag.tagName, child.closingElement.tagName)) { + // stop after parsing a mismatched child like
...(
) in order to reattach the
higher + break; } - return expression; } - function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined { - const pos = getNodePos(); - if (!parseExpected(SyntaxKind.OpenBraceToken)) { - return undefined; - } + parsingContext = saveParsingContext; + return createNodeArray(list, listPos); + } - let dotDotDotToken: DotDotDotToken | undefined; - let expression: Expression | undefined; - if (token() !== SyntaxKind.CloseBraceToken) { - dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - // Only an AssignmentExpression is valid here per the JSX spec, - // but we can unambiguously parse a comma sequence and provide - // a better error message in grammar checking. - expression = parseExpression(); - } - if (inExpressionContext) { - parseExpected(SyntaxKind.CloseBraceToken); - } - else { - if (parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false)) { - scanJsxText(); - } - } + function parseJsxAttributes(): JsxAttributes { + const pos = getNodePos(); + return finishNode(factory.createJsxAttributes(parseList(ParsingContext.JsxAttributes, parseJsxAttribute)), pos); + } - return finishNode(factory.createJsxExpression(dotDotDotToken, expression), pos); - } + function parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement | JsxOpeningFragment { + const pos = getNodePos(); - function parseJsxAttribute(): JsxAttribute | JsxSpreadAttribute { - if (token() === SyntaxKind.OpenBraceToken) { - return parseJsxSpreadAttribute(); - } + parseExpected(SyntaxKind.LessThanToken); - scanJsxIdentifier(); - const pos = getNodePos(); - return finishNode( - factory.createJsxAttribute( - parseIdentifierName(), - token() !== SyntaxKind.EqualsToken ? undefined : - scanJsxAttributeValue() === SyntaxKind.StringLiteral ? parseLiteralNode() as StringLiteral : - parseJsxExpression(/*inExpressionContext*/ true) - ), - pos - ); + if (token() === SyntaxKind.GreaterThanToken) { + // See below for explanation of scanJsxText + scanJsxText(); + return finishNode(factory.createJsxOpeningFragment(), pos); } + const tagName = parseJsxElementName(); + const typeArguments = (contextFlags & NodeFlags.JavaScriptFile) === 0 ? tryParseTypeArguments() : undefined; + const attributes = parseJsxAttributes(); - function parseJsxSpreadAttribute(): JsxSpreadAttribute { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBraceToken); - parseExpected(SyntaxKind.DotDotDotToken); - const expression = parseExpression(); - parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(factory.createJsxSpreadAttribute(expression), pos); - } + let node: JsxOpeningLikeElement; - function parseJsxClosingElement(open: JsxOpeningElement, inExpressionContext: boolean): JsxClosingElement { - const pos = getNodePos(); - parseExpected(SyntaxKind.LessThanSlashToken); - const tagName = parseJsxElementName(); - if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { - // manually advance the scanner in order to look for jsx text inside jsx - if (inExpressionContext || !tagNamesAreEquivalent(open.tagName, tagName)) { - nextToken(); - } - else { - scanJsxText(); - } - } - return finishNode(factory.createJsxClosingElement(tagName), pos); + if (token() === SyntaxKind.GreaterThanToken) { + // Closing tag, so scan the immediately-following text with the JSX scanning instead + // of regular scanning to avoid treating illegal characters (e.g. '#') as immediate + // scanning errors + scanJsxText(); + node = factory.createJsxOpeningElement(tagName, typeArguments, attributes); } - - function parseJsxClosingFragment(inExpressionContext: boolean): JsxClosingFragment { - const pos = getNodePos(); - parseExpected(SyntaxKind.LessThanSlashToken); - if (tokenIsIdentifierOrKeyword(token())) { - parseErrorAtRange(parseJsxElementName(), Diagnostics.Expected_corresponding_closing_tag_for_JSX_fragment); - } + else { + parseExpected(SyntaxKind.SlashToken); if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { // manually advance the scanner in order to look for jsx text inside jsx if (inExpressionContext) { @@ -5325,409 +5218,605 @@ namespace ts { scanJsxText(); } } - return finishNode(factory.createJsxJsxClosingFragment(), pos); + node = factory.createJsxSelfClosingElement(tagName, typeArguments, attributes); } - function parseTypeAssertion(): TypeAssertion { - const pos = getNodePos(); - parseExpected(SyntaxKind.LessThanToken); - const type = parseType(); - parseExpected(SyntaxKind.GreaterThanToken); - const expression = parseSimpleUnaryExpression(); - return finishNode(factory.createTypeAssertion(type, expression), pos); - } + return finishNode(node, pos); + } - function nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate() { - nextToken(); - return tokenIsIdentifierOrKeyword(token()) - || token() === SyntaxKind.OpenBracketToken - || isTemplateStartOfTaggedTemplate(); + function parseJsxElementName(): JsxTagNameExpression { + const pos = getNodePos(); + scanJsxIdentifier(); + // JsxElement can have name in the form of + // propertyAccessExpression + // primaryExpression in the form of an identifier and "this" keyword + // We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword + // We only want to consider "this" as a primaryExpression + let expression: JsxTagNameExpression = token() === SyntaxKind.ThisKeyword ? + parseTokenNode() : parseIdentifierName(); + while (parseOptional(SyntaxKind.DotToken)) { + expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess; + } + return expression; + } + + function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined { + const pos = getNodePos(); + if (!parseExpected(SyntaxKind.OpenBraceToken)) { + return undefined; } - function isStartOfOptionalPropertyOrElementAccessChain() { - return token() === SyntaxKind.QuestionDotToken - && lookAhead(nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate); + let dotDotDotToken: DotDotDotToken | undefined; + let expression: Expression | undefined; + if (token() !== SyntaxKind.CloseBraceToken) { + dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + // Only an AssignmentExpression is valid here per the JSX spec, + // but we can unambiguously parse a comma sequence and provide + // a better error message in grammar checking. + expression = parseExpression(); + } + if (inExpressionContext) { + parseExpected(SyntaxKind.CloseBraceToken); + } + else { + if (parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false)) { + scanJsxText(); + } } - function tryReparseOptionalChain(node: Expression) { - if (node.flags & NodeFlags.OptionalChain) { - return true; + return finishNode(factory.createJsxExpression(dotDotDotToken, expression), pos); + } + + function parseJsxAttribute(): JsxAttribute | JsxSpreadAttribute { + if (token() === SyntaxKind.OpenBraceToken) { + return parseJsxSpreadAttribute(); + } + + scanJsxIdentifier(); + const pos = getNodePos(); + return finishNode( + factory.createJsxAttribute( + parseIdentifierName(), + token() !== SyntaxKind.EqualsToken ? undefined : + scanJsxAttributeValue() === SyntaxKind.StringLiteral ? parseLiteralNode() as StringLiteral : + parseJsxExpression(/*inExpressionContext*/ true) + ), + pos + ); + } + + function parseJsxSpreadAttribute(): JsxSpreadAttribute { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBraceToken); + parseExpected(SyntaxKind.DotDotDotToken); + const expression = parseExpression(); + parseExpected(SyntaxKind.CloseBraceToken); + return finishNode(factory.createJsxSpreadAttribute(expression), pos); + } + + function parseJsxClosingElement(open: JsxOpeningElement, inExpressionContext: boolean): JsxClosingElement { + const pos = getNodePos(); + parseExpected(SyntaxKind.LessThanSlashToken); + const tagName = parseJsxElementName(); + if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { + // manually advance the scanner in order to look for jsx text inside jsx + if (inExpressionContext || !tagNamesAreEquivalent(open.tagName, tagName)) { + nextToken(); } - // check for an optional chain in a non-null expression - if (isNonNullExpression(node)) { - let expr = node.expression; - while (isNonNullExpression(expr) && !(expr.flags & NodeFlags.OptionalChain)) { - expr = expr.expression; - } - if (expr.flags & NodeFlags.OptionalChain) { - // this is part of an optional chain. Walk down from `node` to `expression` and set the flag. - while (isNonNullExpression(node)) { - (node as Mutable).flags |= NodeFlags.OptionalChain; - node = node.expression; - } - return true; - } + else { + scanJsxText(); } - return false; } + return finishNode(factory.createJsxClosingElement(tagName), pos); + } - function parsePropertyAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { - const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true); - const isOptionalChain = questionDotToken || tryReparseOptionalChain(expression); - const propertyAccess = isOptionalChain ? - factory.createPropertyAccessChain(expression, questionDotToken, name) : - factory.createPropertyAccessExpression(expression, name); - if (isOptionalChain && isPrivateIdentifier(propertyAccess.name)) { - parseErrorAtRange(propertyAccess.name, Diagnostics.An_optional_chain_cannot_contain_private_identifiers); + function parseJsxClosingFragment(inExpressionContext: boolean): JsxClosingFragment { + const pos = getNodePos(); + parseExpected(SyntaxKind.LessThanSlashToken); + if (tokenIsIdentifierOrKeyword(token())) { + parseErrorAtRange(parseJsxElementName(), Diagnostics.Expected_corresponding_closing_tag_for_JSX_fragment); + } + if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { + // manually advance the scanner in order to look for jsx text inside jsx + if (inExpressionContext) { + nextToken(); + } + else { + scanJsxText(); } - return finishNode(propertyAccess, pos); } + return finishNode(factory.createJsxJsxClosingFragment(), pos); + } + + function parseTypeAssertion(): TypeAssertion { + const pos = getNodePos(); + parseExpected(SyntaxKind.LessThanToken); + const type = parseType(); + parseExpected(SyntaxKind.GreaterThanToken); + const expression = parseSimpleUnaryExpression(); + return finishNode(factory.createTypeAssertion(type, expression), pos); + } + + function nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate() { + nextToken(); + return tokenIsIdentifierOrKeyword(token()) + || token() === SyntaxKind.OpenBracketToken + || isTemplateStartOfTaggedTemplate(); + } + + function isStartOfOptionalPropertyOrElementAccessChain() { + return token() === SyntaxKind.QuestionDotToken + && lookAhead(nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate); + } - function parseElementAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { - let argumentExpression: Expression; - if (token() === SyntaxKind.CloseBracketToken) { - argumentExpression = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.An_element_access_expression_should_take_an_argument); + function tryReparseOptionalChain(node: Expression) { + if (node.flags & NodeFlags.OptionalChain) { + return true; + } + // check for an optional chain in a non-null expression + if (isNonNullExpression(node)) { + let expr = node.expression; + while (isNonNullExpression(expr) && !(expr.flags & NodeFlags.OptionalChain)) { + expr = expr.expression; } - else { - const argument = allowInAnd(parseExpression); - if (isStringOrNumericLiteralLike(argument)) { - argument.text = internIdentifier(argument.text); + if (expr.flags & NodeFlags.OptionalChain) { + // this is part of an optional chain. Walk down from `node` to `expression` and set the flag. + while (isNonNullExpression(node)) { + (node as Mutable).flags |= NodeFlags.OptionalChain; + node = node.expression; } - argumentExpression = argument; + return true; } + } + return false; + } - parseExpected(SyntaxKind.CloseBracketToken); - - const indexedAccess = questionDotToken || tryReparseOptionalChain(expression) ? - factory.createElementAccessChain(expression, questionDotToken, argumentExpression) : - factory.createElementAccessExpression(expression, argumentExpression); - return finishNode(indexedAccess, pos); + function parsePropertyAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { + const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true); + const isOptionalChain = questionDotToken || tryReparseOptionalChain(expression); + const propertyAccess = isOptionalChain ? + factory.createPropertyAccessChain(expression, questionDotToken, name) : + factory.createPropertyAccessExpression(expression, name); + if (isOptionalChain && isPrivateIdentifier(propertyAccess.name)) { + parseErrorAtRange(propertyAccess.name, Diagnostics.An_optional_chain_cannot_contain_private_identifiers); } + return finishNode(propertyAccess, pos); + } - function parseMemberExpressionRest(pos: number, expression: LeftHandSideExpression, allowOptionalChain: boolean): MemberExpression { - while (true) { - let questionDotToken: QuestionDotToken | undefined; - let isPropertyAccess = false; - if (allowOptionalChain && isStartOfOptionalPropertyOrElementAccessChain()) { - questionDotToken = parseExpectedToken(SyntaxKind.QuestionDotToken); - isPropertyAccess = tokenIsIdentifierOrKeyword(token()); - } - else { - isPropertyAccess = parseOptional(SyntaxKind.DotToken); - } + function parseElementAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { + let argumentExpression: Expression; + if (token() === SyntaxKind.CloseBracketToken) { + argumentExpression = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.An_element_access_expression_should_take_an_argument); + } + else { + const argument = allowInAnd(parseExpression); + if (isStringOrNumericLiteralLike(argument)) { + argument.text = internIdentifier(argument.text); + } + argumentExpression = argument; + } - if (isPropertyAccess) { - expression = parsePropertyAccessExpressionRest(pos, expression, questionDotToken); - continue; - } + parseExpected(SyntaxKind.CloseBracketToken); - if (!questionDotToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { - nextToken(); - expression = finishNode(factory.createNonNullExpression(expression), pos); - continue; - } + const indexedAccess = questionDotToken || tryReparseOptionalChain(expression) ? + factory.createElementAccessChain(expression, questionDotToken, argumentExpression) : + factory.createElementAccessExpression(expression, argumentExpression); + return finishNode(indexedAccess, pos); + } - // when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName - if ((questionDotToken || !inDecoratorContext()) && parseOptional(SyntaxKind.OpenBracketToken)) { - expression = parseElementAccessExpressionRest(pos, expression, questionDotToken); - continue; - } + function parseMemberExpressionRest(pos: number, expression: LeftHandSideExpression, allowOptionalChain: boolean): MemberExpression { + while (true) { + let questionDotToken: QuestionDotToken | undefined; + let isPropertyAccess = false; + if (allowOptionalChain && isStartOfOptionalPropertyOrElementAccessChain()) { + questionDotToken = parseExpectedToken(SyntaxKind.QuestionDotToken); + isPropertyAccess = tokenIsIdentifierOrKeyword(token()); + } + else { + isPropertyAccess = parseOptional(SyntaxKind.DotToken); + } - if (isTemplateStartOfTaggedTemplate()) { - expression = parseTaggedTemplateRest(pos, expression, questionDotToken, /*typeArguments*/ undefined); - continue; - } + if (isPropertyAccess) { + expression = parsePropertyAccessExpressionRest(pos, expression, questionDotToken); + continue; + } - return expression as MemberExpression; + if (!questionDotToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { + nextToken(); + expression = finishNode(factory.createNonNullExpression(expression), pos); + continue; } - } - function isTemplateStartOfTaggedTemplate() { - return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead; - } + // when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName + if ((questionDotToken || !inDecoratorContext()) && parseOptional(SyntaxKind.OpenBracketToken)) { + expression = parseElementAccessExpressionRest(pos, expression, questionDotToken); + continue; + } - function parseTaggedTemplateRest(pos: number, tag: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined, typeArguments: NodeArray | undefined) { - const tagExpression = factory.createTaggedTemplateExpression( - tag, - typeArguments, - token() === SyntaxKind.NoSubstitutionTemplateLiteral ? - (reScanTemplateHeadOrNoSubstitutionTemplate(), parseLiteralNode() as NoSubstitutionTemplateLiteral) : - parseTemplateExpression(/*isTaggedTemplate*/ true) - ); - if (questionDotToken || tag.flags & NodeFlags.OptionalChain) { - (tagExpression as Mutable).flags |= NodeFlags.OptionalChain; + if (isTemplateStartOfTaggedTemplate()) { + expression = parseTaggedTemplateRest(pos, expression, questionDotToken, /*typeArguments*/ undefined); + continue; } - tagExpression.questionDotToken = questionDotToken; - return finishNode(tagExpression, pos); + + return expression as MemberExpression; } + } - function parseCallExpressionRest(pos: number, expression: LeftHandSideExpression): LeftHandSideExpression { - while (true) { - expression = parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true); - const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken); - // handle 'foo<()' - // parse template arguments only in TypeScript files (not in JavaScript files). - if ((contextFlags & NodeFlags.JavaScriptFile) === 0 && (token() === SyntaxKind.LessThanToken || token() === SyntaxKind.LessThanLessThanToken)) { - // See if this is the start of a generic invocation. If so, consume it and - // keep checking for postfix expressions. Otherwise, it's just a '<' that's - // part of an arithmetic expression. Break out so we consume it higher in the - // stack. - const typeArguments = tryParse(parseTypeArgumentsInExpression); - if (typeArguments) { - if (isTemplateStartOfTaggedTemplate()) { - expression = parseTaggedTemplateRest(pos, expression, questionDotToken, typeArguments); - continue; - } + function isTemplateStartOfTaggedTemplate() { + return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead; + } + + function parseTaggedTemplateRest(pos: number, tag: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined, typeArguments: NodeArray | undefined) { + const tagExpression = factory.createTaggedTemplateExpression( + tag, + typeArguments, + token() === SyntaxKind.NoSubstitutionTemplateLiteral ? + (reScanTemplateHeadOrNoSubstitutionTemplate(), parseLiteralNode() as NoSubstitutionTemplateLiteral) : + parseTemplateExpression(/*isTaggedTemplate*/ true) + ); + if (questionDotToken || tag.flags & NodeFlags.OptionalChain) { + (tagExpression as Mutable).flags |= NodeFlags.OptionalChain; + } + tagExpression.questionDotToken = questionDotToken; + return finishNode(tagExpression, pos); + } - const argumentList = parseArgumentList(); - const callExpr = questionDotToken || tryReparseOptionalChain(expression) ? - factory.createCallChain(expression, questionDotToken, typeArguments, argumentList) : - factory.createCallExpression(expression, typeArguments, argumentList); - expression = finishNode(callExpr, pos); + function parseCallExpressionRest(pos: number, expression: LeftHandSideExpression): LeftHandSideExpression { + while (true) { + expression = parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true); + const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken); + // handle 'foo<()' + // parse template arguments only in TypeScript files (not in JavaScript files). + if ((contextFlags & NodeFlags.JavaScriptFile) === 0 && (token() === SyntaxKind.LessThanToken || token() === SyntaxKind.LessThanLessThanToken)) { + // See if this is the start of a generic invocation. If so, consume it and + // keep checking for postfix expressions. Otherwise, it's just a '<' that's + // part of an arithmetic expression. Break out so we consume it higher in the + // stack. + const typeArguments = tryParse(parseTypeArgumentsInExpression); + if (typeArguments) { + if (isTemplateStartOfTaggedTemplate()) { + expression = parseTaggedTemplateRest(pos, expression, questionDotToken, typeArguments); continue; } - } - else if (token() === SyntaxKind.OpenParenToken) { + const argumentList = parseArgumentList(); const callExpr = questionDotToken || tryReparseOptionalChain(expression) ? - factory.createCallChain(expression, questionDotToken, /*typeArguments*/ undefined, argumentList) : - factory.createCallExpression(expression, /*typeArguments*/ undefined, argumentList); + factory.createCallChain(expression, questionDotToken, typeArguments, argumentList) : + factory.createCallExpression(expression, typeArguments, argumentList); expression = finishNode(callExpr, pos); continue; } - if (questionDotToken) { - // We failed to parse anything, so report a missing identifier here. - const name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics.Identifier_expected); - expression = finishNode(factory.createPropertyAccessChain(expression, questionDotToken, name), pos); - } - break; } - return expression; + else if (token() === SyntaxKind.OpenParenToken) { + const argumentList = parseArgumentList(); + const callExpr = questionDotToken || tryReparseOptionalChain(expression) ? + factory.createCallChain(expression, questionDotToken, /*typeArguments*/ undefined, argumentList) : + factory.createCallExpression(expression, /*typeArguments*/ undefined, argumentList); + expression = finishNode(callExpr, pos); + continue; + } + if (questionDotToken) { + // We failed to parse anything, so report a missing identifier here. + const name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics.Identifier_expected); + expression = finishNode(factory.createPropertyAccessChain(expression, questionDotToken, name), pos); + } + break; } + return expression; + } - function parseArgumentList() { - parseExpected(SyntaxKind.OpenParenToken); - const result = parseDelimitedList(ParsingContext.ArgumentExpressions, parseArgumentExpression); - parseExpected(SyntaxKind.CloseParenToken); - return result; + function parseArgumentList() { + parseExpected(SyntaxKind.OpenParenToken); + const result = parseDelimitedList(ParsingContext.ArgumentExpressions, parseArgumentExpression); + parseExpected(SyntaxKind.CloseParenToken); + return result; + } + + function parseTypeArgumentsInExpression() { + if ((contextFlags & NodeFlags.JavaScriptFile) !== 0) { + // TypeArguments must not be parsed in JavaScript files to avoid ambiguity with binary operators. + return undefined; } - function parseTypeArgumentsInExpression() { - if ((contextFlags & NodeFlags.JavaScriptFile) !== 0) { - // TypeArguments must not be parsed in JavaScript files to avoid ambiguity with binary operators. - return undefined; - } + if (reScanLessThanToken() !== SyntaxKind.LessThanToken) { + return undefined; + } + nextToken(); - if (reScanLessThanToken() !== SyntaxKind.LessThanToken) { - return undefined; - } - nextToken(); + const typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseType); + if (!parseExpected(SyntaxKind.GreaterThanToken)) { + // If it doesn't have the closing `>` then it's definitely not an type argument list. + return undefined; + } - const typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseType); - if (!parseExpected(SyntaxKind.GreaterThanToken)) { - // If it doesn't have the closing `>` then it's definitely not an type argument list. - return undefined; - } + // If we have a '<', then only parse this as a argument list if the type arguments + // are complete and we have an open paren. if we don't, rewind and return nothing. + return typeArguments && canFollowTypeArgumentsInExpression() + ? typeArguments + : undefined; + } - // If we have a '<', then only parse this as a argument list if the type arguments - // are complete and we have an open paren. if we don't, rewind and return nothing. - return typeArguments && canFollowTypeArgumentsInExpression() - ? typeArguments - : undefined; + function canFollowTypeArgumentsInExpression(): boolean { + switch (token()) { + case SyntaxKind.OpenParenToken: // foo( + case SyntaxKind.NoSubstitutionTemplateLiteral: // foo `...` + case SyntaxKind.TemplateHead: // foo `...${100}...` + // these are the only tokens can legally follow a type argument + // list. So we definitely want to treat them as type arg lists. + // falls through + case SyntaxKind.DotToken: // foo. + case SyntaxKind.CloseParenToken: // foo) + case SyntaxKind.CloseBracketToken: // foo] + case SyntaxKind.ColonToken: // foo: + case SyntaxKind.SemicolonToken: // foo; + case SyntaxKind.QuestionToken: // foo? + case SyntaxKind.EqualsEqualsToken: // foo == + case SyntaxKind.EqualsEqualsEqualsToken: // foo === + case SyntaxKind.ExclamationEqualsToken: // foo != + case SyntaxKind.ExclamationEqualsEqualsToken: // foo !== + case SyntaxKind.AmpersandAmpersandToken: // foo && + case SyntaxKind.BarBarToken: // foo || + case SyntaxKind.QuestionQuestionToken: // foo ?? + case SyntaxKind.CaretToken: // foo ^ + case SyntaxKind.AmpersandToken: // foo & + case SyntaxKind.BarToken: // foo | + case SyntaxKind.CloseBraceToken: // foo } + case SyntaxKind.EndOfFileToken: // foo + // these cases can't legally follow a type arg list. However, they're not legal + // expressions either. The user is probably in the middle of a generic type. So + // treat it as such. + return true; + + case SyntaxKind.CommaToken: // foo, + case SyntaxKind.OpenBraceToken: // foo { + // We don't want to treat these as type arguments. Otherwise we'll parse this + // as an invocation expression. Instead, we want to parse out the expression + // in isolation from the type arguments. + // falls through + default: + // Anything else treat as an expression. + return false; } + } - function canFollowTypeArgumentsInExpression(): boolean { - switch (token()) { - case SyntaxKind.OpenParenToken: // foo( - case SyntaxKind.NoSubstitutionTemplateLiteral: // foo `...` - case SyntaxKind.TemplateHead: // foo `...${100}...` - // these are the only tokens can legally follow a type argument - // list. So we definitely want to treat them as type arg lists. - // falls through - case SyntaxKind.DotToken: // foo. - case SyntaxKind.CloseParenToken: // foo) - case SyntaxKind.CloseBracketToken: // foo] - case SyntaxKind.ColonToken: // foo: - case SyntaxKind.SemicolonToken: // foo; - case SyntaxKind.QuestionToken: // foo? - case SyntaxKind.EqualsEqualsToken: // foo == - case SyntaxKind.EqualsEqualsEqualsToken: // foo === - case SyntaxKind.ExclamationEqualsToken: // foo != - case SyntaxKind.ExclamationEqualsEqualsToken: // foo !== - case SyntaxKind.AmpersandAmpersandToken: // foo && - case SyntaxKind.BarBarToken: // foo || - case SyntaxKind.QuestionQuestionToken: // foo ?? - case SyntaxKind.CaretToken: // foo ^ - case SyntaxKind.AmpersandToken: // foo & - case SyntaxKind.BarToken: // foo | - case SyntaxKind.CloseBraceToken: // foo } - case SyntaxKind.EndOfFileToken: // foo - // these cases can't legally follow a type arg list. However, they're not legal - // expressions either. The user is probably in the middle of a generic type. So - // treat it as such. - return true; + function parsePrimaryExpression(): PrimaryExpression { + switch (token()) { + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return parseLiteralNode(); + case SyntaxKind.ThisKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + return parseTokenNode(); + case SyntaxKind.OpenParenToken: + return parseParenthesizedExpression(); + case SyntaxKind.OpenBracketToken: + return parseArrayLiteralExpression(); + case SyntaxKind.OpenBraceToken: + return parseObjectLiteralExpression(); + case SyntaxKind.AsyncKeyword: + // Async arrow functions are parsed earlier in parseAssignmentExpressionOrHigher. + // If we encounter `async [no LineTerminator here] function` then this is an async + // function; otherwise, its an identifier. + if (!lookAhead(nextTokenIsFunctionKeywordOnSameLine)) { + break; + } - case SyntaxKind.CommaToken: // foo, - case SyntaxKind.OpenBraceToken: // foo { - // We don't want to treat these as type arguments. Otherwise we'll parse this - // as an invocation expression. Instead, we want to parse out the expression - // in isolation from the type arguments. - // falls through - default: - // Anything else treat as an expression. - return false; - } + return parseFunctionExpression(); + case SyntaxKind.ClassKeyword: + return parseClassExpression(); + case SyntaxKind.FunctionKeyword: + return parseFunctionExpression(); + case SyntaxKind.NewKeyword: + return parseNewExpressionOrNewDotTarget(); + case SyntaxKind.SlashToken: + case SyntaxKind.SlashEqualsToken: + if (reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) { + return parseLiteralNode(); + } + break; + case SyntaxKind.TemplateHead: + return parseTemplateExpression(/* isTaggedTemplate */ false); + case SyntaxKind.PrivateIdentifier: + return parsePrivateIdentifier(); } - function parsePrimaryExpression(): PrimaryExpression { - switch (token()) { - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return parseLiteralNode(); - case SyntaxKind.ThisKeyword: - case SyntaxKind.SuperKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - return parseTokenNode(); - case SyntaxKind.OpenParenToken: - return parseParenthesizedExpression(); - case SyntaxKind.OpenBracketToken: - return parseArrayLiteralExpression(); - case SyntaxKind.OpenBraceToken: - return parseObjectLiteralExpression(); - case SyntaxKind.AsyncKeyword: - // Async arrow functions are parsed earlier in parseAssignmentExpressionOrHigher. - // If we encounter `async [no LineTerminator here] function` then this is an async - // function; otherwise, its an identifier. - if (!lookAhead(nextTokenIsFunctionKeywordOnSameLine)) { - break; - } + return parseIdentifier(Diagnostics.Expression_expected); + } - return parseFunctionExpression(); - case SyntaxKind.ClassKeyword: - return parseClassExpression(); - case SyntaxKind.FunctionKeyword: - return parseFunctionExpression(); - case SyntaxKind.NewKeyword: - return parseNewExpressionOrNewDotTarget(); - case SyntaxKind.SlashToken: - case SyntaxKind.SlashEqualsToken: - if (reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) { - return parseLiteralNode(); - } - break; - case SyntaxKind.TemplateHead: - return parseTemplateExpression(/* isTaggedTemplate */ false); - case SyntaxKind.PrivateIdentifier: - return parsePrivateIdentifier(); - } + function parseParenthesizedExpression(): ParenthesizedExpression { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.OpenParenToken); + const expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + return withJSDoc(finishNode(factory.createParenthesizedExpression(expression), pos), hasJSDoc); + } - return parseIdentifier(Diagnostics.Expression_expected); - } + function parseSpreadElement(): Expression { + const pos = getNodePos(); + parseExpected(SyntaxKind.DotDotDotToken); + const expression = parseAssignmentExpressionOrHigher(); + return finishNode(factory.createSpreadElement(expression), pos); + } - function parseParenthesizedExpression(): ParenthesizedExpression { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.OpenParenToken); - const expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - return withJSDoc(finishNode(factory.createParenthesizedExpression(expression), pos), hasJSDoc); - } + function parseArgumentOrArrayLiteralElement(): Expression { + return token() === SyntaxKind.DotDotDotToken ? parseSpreadElement() : + token() === SyntaxKind.CommaToken ? finishNode(factory.createOmittedExpression(), getNodePos()) : + parseAssignmentExpressionOrHigher(); + } - function parseSpreadElement(): Expression { - const pos = getNodePos(); - parseExpected(SyntaxKind.DotDotDotToken); + function parseArgumentExpression(): Expression { + return doOutsideOfContext(disallowInAndDecoratorContext, parseArgumentOrArrayLiteralElement); + } + + function parseArrayLiteralExpression(): ArrayLiteralExpression { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBracketToken); + const multiLine = scanner.hasPrecedingLineBreak(); + const elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArgumentOrArrayLiteralElement); + parseExpected(SyntaxKind.CloseBracketToken); + return finishNode(factory.createArrayLiteralExpression(elements, multiLine), pos); + } + + function parseObjectLiteralElement(): ObjectLiteralElementLike { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + + if (parseOptionalToken(SyntaxKind.DotDotDotToken)) { const expression = parseAssignmentExpressionOrHigher(); - return finishNode(factory.createSpreadElement(expression), pos); + return withJSDoc(finishNode(factory.createSpreadAssignment(expression), pos), hasJSDoc); } - function parseArgumentOrArrayLiteralElement(): Expression { - return token() === SyntaxKind.DotDotDotToken ? parseSpreadElement() : - token() === SyntaxKind.CommaToken ? finishNode(factory.createOmittedExpression(), getNodePos()) : - parseAssignmentExpressionOrHigher(); - } + const decorators = parseDecorators(); + const modifiers = parseModifiers(); - function parseArgumentExpression(): Expression { - return doOutsideOfContext(disallowInAndDecoratorContext, parseArgumentOrArrayLiteralElement); + if (parseContextualModifier(SyntaxKind.GetKeyword)) { + return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.GetAccessor); + } + if (parseContextualModifier(SyntaxKind.SetKeyword)) { + return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.SetAccessor); } - function parseArrayLiteralExpression(): ArrayLiteralExpression { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBracketToken); - const multiLine = scanner.hasPrecedingLineBreak(); - const elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArgumentOrArrayLiteralElement); - parseExpected(SyntaxKind.CloseBracketToken); - return finishNode(factory.createArrayLiteralExpression(elements, multiLine), pos); + const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + const tokenIsIdentifier = isIdentifier(); + const name = parsePropertyName(); + + // Disallowing of optional property assignments and definite assignment assertion happens in the grammar checker. + const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + const exclamationToken = parseOptionalToken(SyntaxKind.ExclamationToken); + + if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { + return parseMethodDeclaration(pos, hasJSDoc, decorators, modifiers, asteriskToken, name, questionToken, exclamationToken); } - function parseObjectLiteralElement(): ObjectLiteralElementLike { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); + // check if it is short-hand property assignment or normal property assignment + // NOTE: if token is EqualsToken it is interpreted as CoverInitializedName production + // CoverInitializedName[Yield] : + // IdentifierReference[?Yield] Initializer[In, ?Yield] + // this is necessary because ObjectLiteral productions are also used to cover grammar for ObjectAssignmentPattern + let node: Mutable; + const isShorthandPropertyAssignment = tokenIsIdentifier && (token() !== SyntaxKind.ColonToken); + if (isShorthandPropertyAssignment) { + const equalsToken = parseOptionalToken(SyntaxKind.EqualsToken); + const objectAssignmentInitializer = equalsToken ? allowInAnd(parseAssignmentExpressionOrHigher) : undefined; + node = factory.createShorthandPropertyAssignment(name as Identifier, objectAssignmentInitializer); + // Save equals token for error reporting. + // TODO(rbuckton): Consider manufacturing this when we need to report an error as it is otherwise not useful. + node.equalsToken = equalsToken; + } + else { + parseExpected(SyntaxKind.ColonToken); + const initializer = allowInAnd(parseAssignmentExpressionOrHigher); + node = factory.createPropertyAssignment(name, initializer); + } + // Decorators, Modifiers, questionToken, and exclamationToken are not supported by property assignments and are reported in the grammar checker + node.decorators = decorators; + node.modifiers = modifiers; + node.questionToken = questionToken; + node.exclamationToken = exclamationToken; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - if (parseOptionalToken(SyntaxKind.DotDotDotToken)) { - const expression = parseAssignmentExpressionOrHigher(); - return withJSDoc(finishNode(factory.createSpreadAssignment(expression), pos), hasJSDoc); + function parseObjectLiteralExpression(): ObjectLiteralExpression { + const pos = getNodePos(); + const openBracePosition = scanner.getTokenPos(); + parseExpected(SyntaxKind.OpenBraceToken); + const multiLine = scanner.hasPrecedingLineBreak(); + const properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement, /*considerSemicolonAsDelimiter*/ true); + if (!parseExpected(SyntaxKind.CloseBraceToken)) { + const lastError = lastOrUndefined(parseDiagnostics); + if (lastError && lastError.code === Diagnostics._0_expected.code) { + addRelatedInfo( + lastError, + createDetachedDiagnostic(fileName, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_to_match_the_token_here) + ); } + } + return finishNode(factory.createObjectLiteralExpression(properties, multiLine), pos); + } - const decorators = parseDecorators(); - const modifiers = parseModifiers(); + function parseFunctionExpression(): FunctionExpression { + // GeneratorExpression: + // function* BindingIdentifier [Yield][opt](FormalParameters[Yield]){ GeneratorBody } + // + // FunctionExpression: + // function BindingIdentifier[opt](FormalParameters){ FunctionBody } + const savedDecoratorContext = inDecoratorContext(); + setDecoratorContext(/*val*/ false); + + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const modifiers = parseModifiers(); + parseExpected(SyntaxKind.FunctionKeyword); + const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; + const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; + const name = isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalBindingIdentifier) : + isGenerator ? doInYieldContext(parseOptionalBindingIdentifier) : + isAsync ? doInAwaitContext(parseOptionalBindingIdentifier) : + parseOptionalBindingIdentifier(); + + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(isGenerator | isAsync); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + const body = parseFunctionBlock(isGenerator | isAsync); + + setDecoratorContext(savedDecoratorContext); + + const node = factory.createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - if (parseContextualModifier(SyntaxKind.GetKeyword)) { - return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.GetAccessor); - } - if (parseContextualModifier(SyntaxKind.SetKeyword)) { - return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.SetAccessor); - } + function parseOptionalBindingIdentifier(): Identifier | undefined { + return isBindingIdentifier() ? parseBindingIdentifier() : undefined; + } - const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - const tokenIsIdentifier = isIdentifier(); - const name = parsePropertyName(); + function parseNewExpressionOrNewDotTarget(): NewExpression | MetaProperty { + const pos = getNodePos(); + parseExpected(SyntaxKind.NewKeyword); + if (parseOptional(SyntaxKind.DotToken)) { + const name = parseIdentifierName(); + return finishNode(factory.createMetaProperty(SyntaxKind.NewKeyword, name), pos); + } - // Disallowing of optional property assignments and definite assignment assertion happens in the grammar checker. - const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - const exclamationToken = parseOptionalToken(SyntaxKind.ExclamationToken); - - if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { - return parseMethodDeclaration(pos, hasJSDoc, decorators, modifiers, asteriskToken, name, questionToken, exclamationToken); - } - - // check if it is short-hand property assignment or normal property assignment - // NOTE: if token is EqualsToken it is interpreted as CoverInitializedName production - // CoverInitializedName[Yield] : - // IdentifierReference[?Yield] Initializer[In, ?Yield] - // this is necessary because ObjectLiteral productions are also used to cover grammar for ObjectAssignmentPattern - let node: Mutable; - const isShorthandPropertyAssignment = tokenIsIdentifier && (token() !== SyntaxKind.ColonToken); - if (isShorthandPropertyAssignment) { - const equalsToken = parseOptionalToken(SyntaxKind.EqualsToken); - const objectAssignmentInitializer = equalsToken ? allowInAnd(parseAssignmentExpressionOrHigher) : undefined; - node = factory.createShorthandPropertyAssignment(name as Identifier, objectAssignmentInitializer); - // Save equals token for error reporting. - // TODO(rbuckton): Consider manufacturing this when we need to report an error as it is otherwise not useful. - node.equalsToken = equalsToken; + const expressionPos = getNodePos(); + let expression: MemberExpression = parsePrimaryExpression(); + let typeArguments; + while (true) { + expression = parseMemberExpressionRest(expressionPos, expression, /*allowOptionalChain*/ false); + typeArguments = tryParse(parseTypeArgumentsInExpression); + if (isTemplateStartOfTaggedTemplate()) { + Debug.assert(!!typeArguments, + "Expected a type argument list; all plain tagged template starts should be consumed in 'parseMemberExpressionRest'"); + expression = parseTaggedTemplateRest(expressionPos, expression, /*optionalChain*/ undefined, typeArguments); + typeArguments = undefined; } - else { - parseExpected(SyntaxKind.ColonToken); - const initializer = allowInAnd(parseAssignmentExpressionOrHigher); - node = factory.createPropertyAssignment(name, initializer); - } - // Decorators, Modifiers, questionToken, and exclamationToken are not supported by property assignments and are reported in the grammar checker - node.decorators = decorators; - node.modifiers = modifiers; - node.questionToken = questionToken; - node.exclamationToken = exclamationToken; - return withJSDoc(finishNode(node, pos), hasJSDoc); + break; } - function parseObjectLiteralExpression(): ObjectLiteralExpression { - const pos = getNodePos(); - const openBracePosition = scanner.getTokenPos(); - parseExpected(SyntaxKind.OpenBraceToken); + let argumentsArray: NodeArray | undefined; + if (token() === SyntaxKind.OpenParenToken) { + argumentsArray = parseArgumentList(); + } + else if (typeArguments) { + parseErrorAt(pos, scanner.getStartPos(), Diagnostics.A_new_expression_with_type_arguments_must_always_be_followed_by_a_parenthesized_argument_list); + } + return finishNode(factory.createNewExpression(expression, typeArguments, argumentsArray), pos); + } + + // STATEMENTS + function parseBlock(ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const openBracePosition = scanner.getTokenPos(); + if (parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage) || ignoreMissingOpenBrace) { const multiLine = scanner.hasPrecedingLineBreak(); - const properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement, /*considerSemicolonAsDelimiter*/ true); + const statements = parseList(ParsingContext.BlockStatements, parseStatement); if (!parseExpected(SyntaxKind.CloseBraceToken)) { const lastError = lastOrUndefined(parseDiagnostics); if (lastError && lastError.code === Diagnostics._0_expected.code) { @@ -5737,3790 +5826,3701 @@ namespace ts { ); } } - return finishNode(factory.createObjectLiteralExpression(properties, multiLine), pos); - } - - function parseFunctionExpression(): FunctionExpression { - // GeneratorExpression: - // function* BindingIdentifier [Yield][opt](FormalParameters[Yield]){ GeneratorBody } - // - // FunctionExpression: - // function BindingIdentifier[opt](FormalParameters){ FunctionBody } - const savedDecoratorContext = inDecoratorContext(); - setDecoratorContext(/*val*/ false); - - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const modifiers = parseModifiers(); - parseExpected(SyntaxKind.FunctionKeyword); - const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; - const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; - const name = isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalBindingIdentifier) : - isGenerator ? doInYieldContext(parseOptionalBindingIdentifier) : - isAsync ? doInAwaitContext(parseOptionalBindingIdentifier) : - parseOptionalBindingIdentifier(); - - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(isGenerator | isAsync); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - const body = parseFunctionBlock(isGenerator | isAsync); - - setDecoratorContext(savedDecoratorContext); + const result = withJSDoc(finishNode(factory.createBlock(statements, multiLine), pos), hasJSDoc); + if (token() === SyntaxKind.EqualsToken) { + parseErrorAtCurrentToken(Diagnostics.Declaration_or_statement_expected_This_follows_a_block_of_statements_so_if_you_intended_to_write_a_destructuring_assignment_you_might_need_to_wrap_the_the_whole_assignment_in_parentheses); + nextToken(); + } - const node = factory.createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body); - return withJSDoc(finishNode(node, pos), hasJSDoc); + return result; } - - function parseOptionalBindingIdentifier(): Identifier | undefined { - return isBindingIdentifier() ? parseBindingIdentifier() : undefined; + else { + const statements = createMissingList(); + return withJSDoc(finishNode(factory.createBlock(statements, /*multiLine*/ undefined), pos), hasJSDoc); } + } - function parseNewExpressionOrNewDotTarget(): NewExpression | MetaProperty { - const pos = getNodePos(); - parseExpected(SyntaxKind.NewKeyword); - if (parseOptional(SyntaxKind.DotToken)) { - const name = parseIdentifierName(); - return finishNode(factory.createMetaProperty(SyntaxKind.NewKeyword, name), pos); - } + function parseFunctionBlock(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block { + const savedYieldContext = inYieldContext(); + setYieldContext(!!(flags & SignatureFlags.Yield)); - const expressionPos = getNodePos(); - let expression: MemberExpression = parsePrimaryExpression(); - let typeArguments; - while (true) { - expression = parseMemberExpressionRest(expressionPos, expression, /*allowOptionalChain*/ false); - typeArguments = tryParse(parseTypeArgumentsInExpression); - if (isTemplateStartOfTaggedTemplate()) { - Debug.assert(!!typeArguments, - "Expected a type argument list; all plain tagged template starts should be consumed in 'parseMemberExpressionRest'"); - expression = parseTaggedTemplateRest(expressionPos, expression, /*optionalChain*/ undefined, typeArguments); - typeArguments = undefined; - } - break; - } + const savedAwaitContext = inAwaitContext(); + setAwaitContext(!!(flags & SignatureFlags.Await)); - let argumentsArray: NodeArray | undefined; - if (token() === SyntaxKind.OpenParenToken) { - argumentsArray = parseArgumentList(); - } - else if (typeArguments) { - parseErrorAt(pos, scanner.getStartPos(), Diagnostics.A_new_expression_with_type_arguments_must_always_be_followed_by_a_parenthesized_argument_list); - } - return finishNode(factory.createNewExpression(expression, typeArguments, argumentsArray), pos); + const savedTopLevel = topLevel; + topLevel = false; + + // We may be in a [Decorator] context when parsing a function expression or + // arrow function. The body of the function is not in [Decorator] context. + const saveDecoratorContext = inDecoratorContext(); + if (saveDecoratorContext) { + setDecoratorContext(/*val*/ false); } - // STATEMENTS - function parseBlock(ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const openBracePosition = scanner.getTokenPos(); - if (parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage) || ignoreMissingOpenBrace) { - const multiLine = scanner.hasPrecedingLineBreak(); - const statements = parseList(ParsingContext.BlockStatements, parseStatement); - if (!parseExpected(SyntaxKind.CloseBraceToken)) { - const lastError = lastOrUndefined(parseDiagnostics); - if (lastError && lastError.code === Diagnostics._0_expected.code) { - addRelatedInfo( - lastError, - createDetachedDiagnostic(fileName, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_to_match_the_token_here) - ); - } - } - const result = withJSDoc(finishNode(factory.createBlock(statements, multiLine), pos), hasJSDoc); - if (token() === SyntaxKind.EqualsToken) { - parseErrorAtCurrentToken(Diagnostics.Declaration_or_statement_expected_This_follows_a_block_of_statements_so_if_you_intended_to_write_a_destructuring_assignment_you_might_need_to_wrap_the_the_whole_assignment_in_parentheses); - nextToken(); - } + const block = parseBlock(!!(flags & SignatureFlags.IgnoreMissingOpenBrace), diagnosticMessage); - return result; - } - else { - const statements = createMissingList(); - return withJSDoc(finishNode(factory.createBlock(statements, /*multiLine*/ undefined), pos), hasJSDoc); - } + if (saveDecoratorContext) { + setDecoratorContext(/*val*/ true); } - function parseFunctionBlock(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block { - const savedYieldContext = inYieldContext(); - setYieldContext(!!(flags & SignatureFlags.Yield)); - - const savedAwaitContext = inAwaitContext(); - setAwaitContext(!!(flags & SignatureFlags.Await)); + topLevel = savedTopLevel; + setYieldContext(savedYieldContext); + setAwaitContext(savedAwaitContext); - const savedTopLevel = topLevel; - topLevel = false; + return block; + } - // We may be in a [Decorator] context when parsing a function expression or - // arrow function. The body of the function is not in [Decorator] context. - const saveDecoratorContext = inDecoratorContext(); - if (saveDecoratorContext) { - setDecoratorContext(/*val*/ false); - } + function parseEmptyStatement(): Statement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.SemicolonToken); + return withJSDoc(finishNode(factory.createEmptyStatement(), pos), hasJSDoc); + } - const block = parseBlock(!!(flags & SignatureFlags.IgnoreMissingOpenBrace), diagnosticMessage); + function parseIfStatement(): IfStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.IfKeyword); + parseExpected(SyntaxKind.OpenParenToken); + const expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + const thenStatement = parseStatement(); + const elseStatement = parseOptional(SyntaxKind.ElseKeyword) ? parseStatement() : undefined; + return withJSDoc(finishNode(factory.createIfStatement(expression, thenStatement, elseStatement), pos), hasJSDoc); + } - if (saveDecoratorContext) { - setDecoratorContext(/*val*/ true); - } + function parseDoStatement(): DoStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.DoKeyword); + const statement = parseStatement(); + parseExpected(SyntaxKind.WhileKeyword); + parseExpected(SyntaxKind.OpenParenToken); + const expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + + // From: https://mail.mozilla.org/pipermail/es-discuss/2011-August/016188.html + // 157 min --- All allen at wirfs-brock.com CONF --- "do{;}while(false)false" prohibited in + // spec but allowed in consensus reality. Approved -- this is the de-facto standard whereby + // do;while(0)x will have a semicolon inserted before x. + parseOptional(SyntaxKind.SemicolonToken); + return withJSDoc(finishNode(factory.createDoStatement(statement, expression), pos), hasJSDoc); + } - topLevel = savedTopLevel; - setYieldContext(savedYieldContext); - setAwaitContext(savedAwaitContext); + function parseWhileStatement(): WhileStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.WhileKeyword); + parseExpected(SyntaxKind.OpenParenToken); + const expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + const statement = parseStatement(); + return withJSDoc(finishNode(factory.createWhileStatement(expression, statement), pos), hasJSDoc); + } - return block; - } + function parseForOrForInOrForOfStatement(): Statement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.ForKeyword); + const awaitToken = parseOptionalToken(SyntaxKind.AwaitKeyword); + parseExpected(SyntaxKind.OpenParenToken); - function parseEmptyStatement(): Statement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.SemicolonToken); - return withJSDoc(finishNode(factory.createEmptyStatement(), pos), hasJSDoc); + let initializer!: VariableDeclarationList | Expression; + if (token() !== SyntaxKind.SemicolonToken) { + if (token() === SyntaxKind.VarKeyword || token() === SyntaxKind.LetKeyword || token() === SyntaxKind.ConstKeyword) { + initializer = parseVariableDeclarationList(/*inForStatementInitializer*/ true); + } + else { + initializer = disallowInAnd(parseExpression); + } } - function parseIfStatement(): IfStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.IfKeyword); - parseExpected(SyntaxKind.OpenParenToken); - const expression = allowInAnd(parseExpression); + let node: IterationStatement; + if (awaitToken ? parseExpected(SyntaxKind.OfKeyword) : parseOptional(SyntaxKind.OfKeyword)) { + const expression = allowInAnd(parseAssignmentExpressionOrHigher); parseExpected(SyntaxKind.CloseParenToken); - const thenStatement = parseStatement(); - const elseStatement = parseOptional(SyntaxKind.ElseKeyword) ? parseStatement() : undefined; - return withJSDoc(finishNode(factory.createIfStatement(expression, thenStatement, elseStatement), pos), hasJSDoc); + node = factory.createForOfStatement(awaitToken, initializer, expression, parseStatement()); } - - function parseDoStatement(): DoStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.DoKeyword); - const statement = parseStatement(); - parseExpected(SyntaxKind.WhileKeyword); - parseExpected(SyntaxKind.OpenParenToken); + else if (parseOptional(SyntaxKind.InKeyword)) { const expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); - - // From: https://mail.mozilla.org/pipermail/es-discuss/2011-August/016188.html - // 157 min --- All allen at wirfs-brock.com CONF --- "do{;}while(false)false" prohibited in - // spec but allowed in consensus reality. Approved -- this is the de-facto standard whereby - // do;while(0)x will have a semicolon inserted before x. - parseOptional(SyntaxKind.SemicolonToken); - return withJSDoc(finishNode(factory.createDoStatement(statement, expression), pos), hasJSDoc); + node = factory.createForInStatement(initializer, expression, parseStatement()); } - - function parseWhileStatement(): WhileStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.WhileKeyword); - parseExpected(SyntaxKind.OpenParenToken); - const expression = allowInAnd(parseExpression); + else { + parseExpected(SyntaxKind.SemicolonToken); + const condition = token() !== SyntaxKind.SemicolonToken && token() !== SyntaxKind.CloseParenToken + ? allowInAnd(parseExpression) + : undefined; + parseExpected(SyntaxKind.SemicolonToken); + const incrementor = token() !== SyntaxKind.CloseParenToken + ? allowInAnd(parseExpression) + : undefined; parseExpected(SyntaxKind.CloseParenToken); - const statement = parseStatement(); - return withJSDoc(finishNode(factory.createWhileStatement(expression, statement), pos), hasJSDoc); + node = factory.createForStatement(initializer, condition, incrementor, parseStatement()); } - function parseForOrForInOrForOfStatement(): Statement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.ForKeyword); - const awaitToken = parseOptionalToken(SyntaxKind.AwaitKeyword); - parseExpected(SyntaxKind.OpenParenToken); - - let initializer!: VariableDeclarationList | Expression; - if (token() !== SyntaxKind.SemicolonToken) { - if (token() === SyntaxKind.VarKeyword || token() === SyntaxKind.LetKeyword || token() === SyntaxKind.ConstKeyword) { - initializer = parseVariableDeclarationList(/*inForStatementInitializer*/ true); - } - else { - initializer = disallowInAnd(parseExpression); - } - } + return withJSDoc(finishNode(node, pos) as ForStatement | ForInOrOfStatement, hasJSDoc); + } - let node: IterationStatement; - if (awaitToken ? parseExpected(SyntaxKind.OfKeyword) : parseOptional(SyntaxKind.OfKeyword)) { - const expression = allowInAnd(parseAssignmentExpressionOrHigher); - parseExpected(SyntaxKind.CloseParenToken); - node = factory.createForOfStatement(awaitToken, initializer, expression, parseStatement()); - } - else if (parseOptional(SyntaxKind.InKeyword)) { - const expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - node = factory.createForInStatement(initializer, expression, parseStatement()); - } - else { - parseExpected(SyntaxKind.SemicolonToken); - const condition = token() !== SyntaxKind.SemicolonToken && token() !== SyntaxKind.CloseParenToken - ? allowInAnd(parseExpression) - : undefined; - parseExpected(SyntaxKind.SemicolonToken); - const incrementor = token() !== SyntaxKind.CloseParenToken - ? allowInAnd(parseExpression) - : undefined; - parseExpected(SyntaxKind.CloseParenToken); - node = factory.createForStatement(initializer, condition, incrementor, parseStatement()); - } + function parseBreakOrContinueStatement(kind: SyntaxKind): BreakOrContinueStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); - return withJSDoc(finishNode(node, pos) as ForStatement | ForInOrOfStatement, hasJSDoc); - } + parseExpected(kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword); + const label = canParseSemicolon() ? undefined : parseIdentifier(); - function parseBreakOrContinueStatement(kind: SyntaxKind): BreakOrContinueStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); + parseSemicolon(); + const node = kind === SyntaxKind.BreakStatement + ? factory.createBreakStatement(label) + : factory.createContinueStatement(label); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - parseExpected(kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword); - const label = canParseSemicolon() ? undefined : parseIdentifier(); + function parseReturnStatement(): ReturnStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.ReturnKeyword); + const expression = canParseSemicolon() ? undefined : allowInAnd(parseExpression); + parseSemicolon(); + return withJSDoc(finishNode(factory.createReturnStatement(expression), pos), hasJSDoc); + } - parseSemicolon(); - const node = kind === SyntaxKind.BreakStatement - ? factory.createBreakStatement(label) - : factory.createContinueStatement(label); - return withJSDoc(finishNode(node, pos), hasJSDoc); - } + function parseWithStatement(): WithStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.WithKeyword); + parseExpected(SyntaxKind.OpenParenToken); + const expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + const statement = doInsideOfContext(NodeFlags.InWithStatement, parseStatement); + return withJSDoc(finishNode(factory.createWithStatement(expression, statement), pos), hasJSDoc); + } - function parseReturnStatement(): ReturnStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.ReturnKeyword); - const expression = canParseSemicolon() ? undefined : allowInAnd(parseExpression); - parseSemicolon(); - return withJSDoc(finishNode(factory.createReturnStatement(expression), pos), hasJSDoc); - } + function parseCaseClause(): CaseClause { + const pos = getNodePos(); + parseExpected(SyntaxKind.CaseKeyword); + const expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.ColonToken); + const statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); + return finishNode(factory.createCaseClause(expression, statements), pos); + } - function parseWithStatement(): WithStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.WithKeyword); - parseExpected(SyntaxKind.OpenParenToken); - const expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - const statement = doInsideOfContext(NodeFlags.InWithStatement, parseStatement); - return withJSDoc(finishNode(factory.createWithStatement(expression, statement), pos), hasJSDoc); - } + function parseDefaultClause(): DefaultClause { + const pos = getNodePos(); + parseExpected(SyntaxKind.DefaultKeyword); + parseExpected(SyntaxKind.ColonToken); + const statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); + return finishNode(factory.createDefaultClause(statements), pos); + } - function parseCaseClause(): CaseClause { - const pos = getNodePos(); - parseExpected(SyntaxKind.CaseKeyword); - const expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.ColonToken); - const statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); - return finishNode(factory.createCaseClause(expression, statements), pos); - } + function parseCaseOrDefaultClause(): CaseOrDefaultClause { + return token() === SyntaxKind.CaseKeyword ? parseCaseClause() : parseDefaultClause(); + } - function parseDefaultClause(): DefaultClause { - const pos = getNodePos(); - parseExpected(SyntaxKind.DefaultKeyword); - parseExpected(SyntaxKind.ColonToken); - const statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); - return finishNode(factory.createDefaultClause(statements), pos); - } + function parseCaseBlock(): CaseBlock { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBraceToken); + const clauses = parseList(ParsingContext.SwitchClauses, parseCaseOrDefaultClause); + parseExpected(SyntaxKind.CloseBraceToken); + return finishNode(factory.createCaseBlock(clauses), pos); + } - function parseCaseOrDefaultClause(): CaseOrDefaultClause { - return token() === SyntaxKind.CaseKeyword ? parseCaseClause() : parseDefaultClause(); - } + function parseSwitchStatement(): SwitchStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.SwitchKeyword); + parseExpected(SyntaxKind.OpenParenToken); + const expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + const caseBlock = parseCaseBlock(); + return withJSDoc(finishNode(factory.createSwitchStatement(expression, caseBlock), pos), hasJSDoc); + } - function parseCaseBlock(): CaseBlock { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBraceToken); - const clauses = parseList(ParsingContext.SwitchClauses, parseCaseOrDefaultClause); - parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(factory.createCaseBlock(clauses), pos); + function parseThrowStatement(): ThrowStatement { + // ThrowStatement[Yield] : + // throw [no LineTerminator here]Expression[In, ?Yield]; + + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.ThrowKeyword); + + // Because of automatic semicolon insertion, we need to report error if this + // throw could be terminated with a semicolon. Note: we can't call 'parseExpression' + // directly as that might consume an expression on the following line. + // Instead, we create a "missing" identifier, but don't report an error. The actual error + // will be reported in the grammar walker. + let expression = scanner.hasPrecedingLineBreak() ? undefined : allowInAnd(parseExpression); + if (expression === undefined) { + identifierCount++; + expression = finishNode(factory.createIdentifier(""), getNodePos()); } - - function parseSwitchStatement(): SwitchStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.SwitchKeyword); - parseExpected(SyntaxKind.OpenParenToken); - const expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - const caseBlock = parseCaseBlock(); - return withJSDoc(finishNode(factory.createSwitchStatement(expression, caseBlock), pos), hasJSDoc); + if (!tryParseSemicolon()) { + parseErrorForMissingSemicolonAfter(expression); } + return withJSDoc(finishNode(factory.createThrowStatement(expression), pos), hasJSDoc); + } - function parseThrowStatement(): ThrowStatement { - // ThrowStatement[Yield] : - // throw [no LineTerminator here]Expression[In, ?Yield]; + // TODO: Review for error recovery + function parseTryStatement(): TryStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.ThrowKeyword); - - // Because of automatic semicolon insertion, we need to report error if this - // throw could be terminated with a semicolon. Note: we can't call 'parseExpression' - // directly as that might consume an expression on the following line. - // Instead, we create a "missing" identifier, but don't report an error. The actual error - // will be reported in the grammar walker. - let expression = scanner.hasPrecedingLineBreak() ? undefined : allowInAnd(parseExpression); - if (expression === undefined) { - identifierCount++; - expression = finishNode(factory.createIdentifier(""), getNodePos()); - } - if (!tryParseSemicolon()) { - parseErrorForMissingSemicolonAfter(expression); - } - return withJSDoc(finishNode(factory.createThrowStatement(expression), pos), hasJSDoc); - } + parseExpected(SyntaxKind.TryKeyword); + const tryBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); + const catchClause = token() === SyntaxKind.CatchKeyword ? parseCatchClause() : undefined; - // TODO: Review for error recovery - function parseTryStatement(): TryStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); + // If we don't have a catch clause, then we must have a finally clause. Try to parse + // one out no matter what. + let finallyBlock: Block | undefined; + if (!catchClause || token() === SyntaxKind.FinallyKeyword) { + parseExpected(SyntaxKind.FinallyKeyword); + finallyBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); + } - parseExpected(SyntaxKind.TryKeyword); - const tryBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); - const catchClause = token() === SyntaxKind.CatchKeyword ? parseCatchClause() : undefined; + return withJSDoc(finishNode(factory.createTryStatement(tryBlock, catchClause, finallyBlock), pos), hasJSDoc); + } - // If we don't have a catch clause, then we must have a finally clause. Try to parse - // one out no matter what. - let finallyBlock: Block | undefined; - if (!catchClause || token() === SyntaxKind.FinallyKeyword) { - parseExpected(SyntaxKind.FinallyKeyword); - finallyBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); - } + function parseCatchClause(): CatchClause { + const pos = getNodePos(); + parseExpected(SyntaxKind.CatchKeyword); - return withJSDoc(finishNode(factory.createTryStatement(tryBlock, catchClause, finallyBlock), pos), hasJSDoc); + let variableDeclaration; + if (parseOptional(SyntaxKind.OpenParenToken)) { + variableDeclaration = parseVariableDeclaration(); + parseExpected(SyntaxKind.CloseParenToken); + } + else { + // Keep shape of node to avoid degrading performance. + variableDeclaration = undefined; } - function parseCatchClause(): CatchClause { - const pos = getNodePos(); - parseExpected(SyntaxKind.CatchKeyword); - - let variableDeclaration; - if (parseOptional(SyntaxKind.OpenParenToken)) { - variableDeclaration = parseVariableDeclaration(); - parseExpected(SyntaxKind.CloseParenToken); - } - else { - // Keep shape of node to avoid degrading performance. - variableDeclaration = undefined; - } + const block = parseBlock(/*ignoreMissingOpenBrace*/ false); + return finishNode(factory.createCatchClause(variableDeclaration, block), pos); + } - const block = parseBlock(/*ignoreMissingOpenBrace*/ false); - return finishNode(factory.createCatchClause(variableDeclaration, block), pos); - } + function parseDebuggerStatement(): Statement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.DebuggerKeyword); + parseSemicolon(); + return withJSDoc(finishNode(factory.createDebuggerStatement(), pos), hasJSDoc); + } - function parseDebuggerStatement(): Statement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.DebuggerKeyword); - parseSemicolon(); - return withJSDoc(finishNode(factory.createDebuggerStatement(), pos), hasJSDoc); + function parseExpressionOrLabeledStatement(): ExpressionStatement | LabeledStatement { + // Avoiding having to do the lookahead for a labeled statement by just trying to parse + // out an expression, seeing if it is identifier and then seeing if it is followed by + // a colon. + const pos = getNodePos(); + let hasJSDoc = hasPrecedingJSDocComment(); + let node: ExpressionStatement | LabeledStatement; + const hasParen = token() === SyntaxKind.OpenParenToken; + const expression = allowInAnd(parseExpression); + if (ts.isIdentifier(expression) && parseOptional(SyntaxKind.ColonToken)) { + node = factory.createLabeledStatement(expression, parseStatement()); } - - function parseExpressionOrLabeledStatement(): ExpressionStatement | LabeledStatement { - // Avoiding having to do the lookahead for a labeled statement by just trying to parse - // out an expression, seeing if it is identifier and then seeing if it is followed by - // a colon. - const pos = getNodePos(); - let hasJSDoc = hasPrecedingJSDocComment(); - let node: ExpressionStatement | LabeledStatement; - const hasParen = token() === SyntaxKind.OpenParenToken; - const expression = allowInAnd(parseExpression); - if (ts.isIdentifier(expression) && parseOptional(SyntaxKind.ColonToken)) { - node = factory.createLabeledStatement(expression, parseStatement()); + else { + if (!tryParseSemicolon()) { + parseErrorForMissingSemicolonAfter(expression); } - else { - if (!tryParseSemicolon()) { - parseErrorForMissingSemicolonAfter(expression); - } - node = factory.createExpressionStatement(expression); - if (hasParen) { - // do not parse the same jsdoc twice - hasJSDoc = false; - } + node = factory.createExpressionStatement(expression); + if (hasParen) { + // do not parse the same jsdoc twice + hasJSDoc = false; } - return withJSDoc(finishNode(node, pos), hasJSDoc); } + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function nextTokenIsIdentifierOrKeywordOnSameLine() { - nextToken(); - return tokenIsIdentifierOrKeyword(token()) && !scanner.hasPrecedingLineBreak(); - } + function nextTokenIsIdentifierOrKeywordOnSameLine() { + nextToken(); + return tokenIsIdentifierOrKeyword(token()) && !scanner.hasPrecedingLineBreak(); + } - function nextTokenIsClassKeywordOnSameLine() { - nextToken(); - return token() === SyntaxKind.ClassKeyword && !scanner.hasPrecedingLineBreak(); - } + function nextTokenIsClassKeywordOnSameLine() { + nextToken(); + return token() === SyntaxKind.ClassKeyword && !scanner.hasPrecedingLineBreak(); + } - function nextTokenIsFunctionKeywordOnSameLine() { - nextToken(); - return token() === SyntaxKind.FunctionKeyword && !scanner.hasPrecedingLineBreak(); - } + function nextTokenIsFunctionKeywordOnSameLine() { + nextToken(); + return token() === SyntaxKind.FunctionKeyword && !scanner.hasPrecedingLineBreak(); + } - function nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine() { - nextToken(); - return (tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral || token() === SyntaxKind.StringLiteral) && !scanner.hasPrecedingLineBreak(); - } + function nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine() { + nextToken(); + return (tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral || token() === SyntaxKind.StringLiteral) && !scanner.hasPrecedingLineBreak(); + } - function isDeclaration(): boolean { - while (true) { - switch (token()) { - case SyntaxKind.VarKeyword: - case SyntaxKind.LetKeyword: - case SyntaxKind.ConstKeyword: - case SyntaxKind.FunctionKeyword: - case SyntaxKind.ClassKeyword: - case SyntaxKind.EnumKeyword: - return true; - - // 'declare', 'module', 'namespace', 'interface'* and 'type' are all legal JavaScript identifiers; - // however, an identifier cannot be followed by another identifier on the same line. This is what we - // count on to parse out the respective declarations. For instance, we exploit this to say that - // - // namespace n - // - // can be none other than the beginning of a namespace declaration, but need to respect that JavaScript sees - // - // namespace - // n - // - // as the identifier 'namespace' on one line followed by the identifier 'n' on another. - // We need to look one token ahead to see if it permissible to try parsing a declaration. - // - // *Note*: 'interface' is actually a strict mode reserved word. So while - // - // "use strict" - // interface - // I {} - // - // could be legal, it would add complexity for very little gain. - case SyntaxKind.InterfaceKeyword: - case SyntaxKind.TypeKeyword: - return nextTokenIsIdentifierOnSameLine(); - case SyntaxKind.ModuleKeyword: - case SyntaxKind.NamespaceKeyword: - return nextTokenIsIdentifierOrStringLiteralOnSameLine(); - case SyntaxKind.AbstractKeyword: - case SyntaxKind.AsyncKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.PublicKeyword: - case SyntaxKind.ReadonlyKeyword: - nextToken(); - // ASI takes effect for this modifier. - if (scanner.hasPrecedingLineBreak()) { - return false; - } - continue; - - case SyntaxKind.GlobalKeyword: - nextToken(); - return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.Identifier || token() === SyntaxKind.ExportKeyword; - - case SyntaxKind.ImportKeyword: - nextToken(); - return token() === SyntaxKind.StringLiteral || token() === SyntaxKind.AsteriskToken || - token() === SyntaxKind.OpenBraceToken || tokenIsIdentifierOrKeyword(token()); - case SyntaxKind.ExportKeyword: - let currentToken = nextToken(); - if (currentToken === SyntaxKind.TypeKeyword) { - currentToken = lookAhead(nextToken); - } - if (currentToken === SyntaxKind.EqualsToken || currentToken === SyntaxKind.AsteriskToken || - currentToken === SyntaxKind.OpenBraceToken || currentToken === SyntaxKind.DefaultKeyword || - currentToken === SyntaxKind.AsKeyword) { - return true; - } - continue; - - case SyntaxKind.StaticKeyword: - nextToken(); - continue; - default: - return false; - } - } - } - - function isStartOfDeclaration(): boolean { - return lookAhead(isDeclaration); - } - - function isStartOfStatement(): boolean { + function isDeclaration(): boolean { + while (true) { switch (token()) { - case SyntaxKind.AtToken: - case SyntaxKind.SemicolonToken: - case SyntaxKind.OpenBraceToken: case SyntaxKind.VarKeyword: case SyntaxKind.LetKeyword: + case SyntaxKind.ConstKeyword: case SyntaxKind.FunctionKeyword: case SyntaxKind.ClassKeyword: case SyntaxKind.EnumKeyword: - case SyntaxKind.IfKeyword: - case SyntaxKind.DoKeyword: - case SyntaxKind.WhileKeyword: - case SyntaxKind.ForKeyword: - case SyntaxKind.ContinueKeyword: - case SyntaxKind.BreakKeyword: - case SyntaxKind.ReturnKeyword: - case SyntaxKind.WithKeyword: - case SyntaxKind.SwitchKeyword: - case SyntaxKind.ThrowKeyword: - case SyntaxKind.TryKeyword: - case SyntaxKind.DebuggerKeyword: - // 'catch' and 'finally' do not actually indicate that the code is part of a statement, - // however, we say they are here so that we may gracefully parse them and error later. - // falls through - case SyntaxKind.CatchKeyword: - case SyntaxKind.FinallyKeyword: - return true; - - case SyntaxKind.ImportKeyword: - return isStartOfDeclaration() || lookAhead(nextTokenIsOpenParenOrLessThanOrDot); - - case SyntaxKind.ConstKeyword: - case SyntaxKind.ExportKeyword: - return isStartOfDeclaration(); - - case SyntaxKind.AsyncKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.InterfaceKeyword: - case SyntaxKind.ModuleKeyword: - case SyntaxKind.NamespaceKeyword: - case SyntaxKind.TypeKeyword: - case SyntaxKind.GlobalKeyword: - // When these don't start a declaration, they're an identifier in an expression statement return true; - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.StaticKeyword: - case SyntaxKind.ReadonlyKeyword: - // When these don't start a declaration, they may be the start of a class member if an identifier - // immediately follows. Otherwise they're an identifier in an expression statement. - return isStartOfDeclaration() || !lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine); - - default: - return isStartOfExpression(); - } - } - - function nextTokenIsBindingIdentifierOrStartOfDestructuring() { - nextToken(); - return isBindingIdentifier() || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.OpenBracketToken; - } - - function isLetDeclaration() { - // In ES6 'let' always starts a lexical declaration if followed by an identifier or { - // or [. - return lookAhead(nextTokenIsBindingIdentifierOrStartOfDestructuring); - } - - function parseStatement(): Statement { - switch (token()) { - case SyntaxKind.SemicolonToken: - return parseEmptyStatement(); - case SyntaxKind.OpenBraceToken: - return parseBlock(/*ignoreMissingOpenBrace*/ false); - case SyntaxKind.VarKeyword: - return parseVariableStatement(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); - case SyntaxKind.LetKeyword: - if (isLetDeclaration()) { - return parseVariableStatement(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); - } - break; - case SyntaxKind.FunctionKeyword: - return parseFunctionDeclaration(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); - case SyntaxKind.ClassKeyword: - return parseClassDeclaration(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); - case SyntaxKind.IfKeyword: - return parseIfStatement(); - case SyntaxKind.DoKeyword: - return parseDoStatement(); - case SyntaxKind.WhileKeyword: - return parseWhileStatement(); - case SyntaxKind.ForKeyword: - return parseForOrForInOrForOfStatement(); - case SyntaxKind.ContinueKeyword: - return parseBreakOrContinueStatement(SyntaxKind.ContinueStatement); - case SyntaxKind.BreakKeyword: - return parseBreakOrContinueStatement(SyntaxKind.BreakStatement); - case SyntaxKind.ReturnKeyword: - return parseReturnStatement(); - case SyntaxKind.WithKeyword: - return parseWithStatement(); - case SyntaxKind.SwitchKeyword: - return parseSwitchStatement(); - case SyntaxKind.ThrowKeyword: - return parseThrowStatement(); - case SyntaxKind.TryKeyword: - // Include 'catch' and 'finally' for error recovery. - // falls through - case SyntaxKind.CatchKeyword: - case SyntaxKind.FinallyKeyword: - return parseTryStatement(); - case SyntaxKind.DebuggerKeyword: - return parseDebuggerStatement(); - case SyntaxKind.AtToken: - return parseDeclaration(); - case SyntaxKind.AsyncKeyword: + // 'declare', 'module', 'namespace', 'interface'* and 'type' are all legal JavaScript identifiers; + // however, an identifier cannot be followed by another identifier on the same line. This is what we + // count on to parse out the respective declarations. For instance, we exploit this to say that + // + // namespace n + // + // can be none other than the beginning of a namespace declaration, but need to respect that JavaScript sees + // + // namespace + // n + // + // as the identifier 'namespace' on one line followed by the identifier 'n' on another. + // We need to look one token ahead to see if it permissible to try parsing a declaration. + // + // *Note*: 'interface' is actually a strict mode reserved word. So while + // + // "use strict" + // interface + // I {} + // + // could be legal, it would add complexity for very little gain. case SyntaxKind.InterfaceKeyword: case SyntaxKind.TypeKeyword: + return nextTokenIsIdentifierOnSameLine(); case SyntaxKind.ModuleKeyword: case SyntaxKind.NamespaceKeyword: + return nextTokenIsIdentifierOrStringLiteralOnSameLine(); + case SyntaxKind.AbstractKeyword: + case SyntaxKind.AsyncKeyword: case SyntaxKind.DeclareKeyword: - case SyntaxKind.ConstKeyword: - case SyntaxKind.EnumKeyword: - case SyntaxKind.ExportKeyword: - case SyntaxKind.ImportKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.PublicKeyword: - case SyntaxKind.AbstractKeyword: - case SyntaxKind.StaticKeyword: case SyntaxKind.ReadonlyKeyword: - case SyntaxKind.GlobalKeyword: - if (isStartOfDeclaration()) { - return parseDeclaration(); + nextToken(); + // ASI takes effect for this modifier. + if (scanner.hasPrecedingLineBreak()) { + return false; } - break; - } - return parseExpressionOrLabeledStatement(); - } - - function isDeclareModifier(modifier: Modifier) { - return modifier.kind === SyntaxKind.DeclareKeyword; - } - - function parseDeclaration(): Statement { - // TODO: Can we hold onto the parsed decorators/modifiers and advance the scanner - // if we can't reuse the declaration, so that we don't do this work twice? - // - // `parseListElement` attempted to get the reused node at this position, - // but the ambient context flag was not yet set, so the node appeared - // not reusable in that context. - const isAmbient = some(lookAhead(() => (parseDecorators(), parseModifiers())), isDeclareModifier); - if (isAmbient) { - const node = tryReuseAmbientDeclaration(); - if (node) { - return node; - } - } - - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const decorators = parseDecorators(); - const modifiers = parseModifiers(); - if (isAmbient) { - for (const m of modifiers!) { - (m as Mutable).flags |= NodeFlags.Ambient; - } - return doInsideOfContext(NodeFlags.Ambient, () => parseDeclarationWorker(pos, hasJSDoc, decorators, modifiers)); - } - else { - return parseDeclarationWorker(pos, hasJSDoc, decorators, modifiers); - } - } - - function tryReuseAmbientDeclaration(): Statement | undefined { - return doInsideOfContext(NodeFlags.Ambient, () => { - const node = currentNode(parsingContext); - if (node) { - return consumeNode(node) as Statement; - } - }); - } + continue; - function parseDeclarationWorker(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): Statement { - switch (token()) { - case SyntaxKind.VarKeyword: - case SyntaxKind.LetKeyword: - case SyntaxKind.ConstKeyword: - return parseVariableStatement(pos, hasJSDoc, decorators, modifiers); - case SyntaxKind.FunctionKeyword: - return parseFunctionDeclaration(pos, hasJSDoc, decorators, modifiers); - case SyntaxKind.ClassKeyword: - return parseClassDeclaration(pos, hasJSDoc, decorators, modifiers); - case SyntaxKind.InterfaceKeyword: - return parseInterfaceDeclaration(pos, hasJSDoc, decorators, modifiers); - case SyntaxKind.TypeKeyword: - return parseTypeAliasDeclaration(pos, hasJSDoc, decorators, modifiers); - case SyntaxKind.EnumKeyword: - return parseEnumDeclaration(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.GlobalKeyword: - case SyntaxKind.ModuleKeyword: - case SyntaxKind.NamespaceKeyword: - return parseModuleDeclaration(pos, hasJSDoc, decorators, modifiers); + nextToken(); + return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.Identifier || token() === SyntaxKind.ExportKeyword; + case SyntaxKind.ImportKeyword: - return parseImportDeclarationOrImportEqualsDeclaration(pos, hasJSDoc, decorators, modifiers); - case SyntaxKind.ExportKeyword: nextToken(); - switch (token()) { - case SyntaxKind.DefaultKeyword: - case SyntaxKind.EqualsToken: - return parseExportAssignment(pos, hasJSDoc, decorators, modifiers); - case SyntaxKind.AsKeyword: - return parseNamespaceExportDeclaration(pos, hasJSDoc, decorators, modifiers); - default: - return parseExportDeclaration(pos, hasJSDoc, decorators, modifiers); + return token() === SyntaxKind.StringLiteral || token() === SyntaxKind.AsteriskToken || + token() === SyntaxKind.OpenBraceToken || tokenIsIdentifierOrKeyword(token()); + case SyntaxKind.ExportKeyword: + let currentToken = nextToken(); + if (currentToken === SyntaxKind.TypeKeyword) { + currentToken = lookAhead(nextToken); } - default: - if (decorators || modifiers) { - // We reached this point because we encountered decorators and/or modifiers and assumed a declaration - // would follow. For recovery and error reporting purposes, return an incomplete declaration. - const missing = createMissingNode(SyntaxKind.MissingDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); - setTextRangePos(missing, pos); - missing.decorators = decorators; - missing.modifiers = modifiers; - return missing; + if (currentToken === SyntaxKind.EqualsToken || currentToken === SyntaxKind.AsteriskToken || + currentToken === SyntaxKind.OpenBraceToken || currentToken === SyntaxKind.DefaultKeyword || + currentToken === SyntaxKind.AsKeyword) { + return true; } - return undefined!; // TODO: GH#18217 + continue; + + case SyntaxKind.StaticKeyword: + nextToken(); + continue; + default: + return false; } } + } - function nextTokenIsIdentifierOrStringLiteralOnSameLine() { - nextToken(); - return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token() === SyntaxKind.StringLiteral); - } + function isStartOfDeclaration(): boolean { + return lookAhead(isDeclaration); + } - function parseFunctionBlockOrSemicolon(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block | undefined { - if (token() !== SyntaxKind.OpenBraceToken && canParseSemicolon()) { - parseSemicolon(); - return; - } + function isStartOfStatement(): boolean { + switch (token()) { + case SyntaxKind.AtToken: + case SyntaxKind.SemicolonToken: + case SyntaxKind.OpenBraceToken: + case SyntaxKind.VarKeyword: + case SyntaxKind.LetKeyword: + case SyntaxKind.FunctionKeyword: + case SyntaxKind.ClassKeyword: + case SyntaxKind.EnumKeyword: + case SyntaxKind.IfKeyword: + case SyntaxKind.DoKeyword: + case SyntaxKind.WhileKeyword: + case SyntaxKind.ForKeyword: + case SyntaxKind.ContinueKeyword: + case SyntaxKind.BreakKeyword: + case SyntaxKind.ReturnKeyword: + case SyntaxKind.WithKeyword: + case SyntaxKind.SwitchKeyword: + case SyntaxKind.ThrowKeyword: + case SyntaxKind.TryKeyword: + case SyntaxKind.DebuggerKeyword: + // 'catch' and 'finally' do not actually indicate that the code is part of a statement, + // however, we say they are here so that we may gracefully parse them and error later. + // falls through + case SyntaxKind.CatchKeyword: + case SyntaxKind.FinallyKeyword: + return true; - return parseFunctionBlock(flags, diagnosticMessage); - } + case SyntaxKind.ImportKeyword: + return isStartOfDeclaration() || lookAhead(nextTokenIsOpenParenOrLessThanOrDot); + + case SyntaxKind.ConstKeyword: + case SyntaxKind.ExportKeyword: + return isStartOfDeclaration(); + + case SyntaxKind.AsyncKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.InterfaceKeyword: + case SyntaxKind.ModuleKeyword: + case SyntaxKind.NamespaceKeyword: + case SyntaxKind.TypeKeyword: + case SyntaxKind.GlobalKeyword: + // When these don't start a declaration, they're an identifier in an expression statement + return true; - // DECLARATIONS + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.StaticKeyword: + case SyntaxKind.ReadonlyKeyword: + // When these don't start a declaration, they may be the start of a class member if an identifier + // immediately follows. Otherwise they're an identifier in an expression statement. + return isStartOfDeclaration() || !lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine); - function parseArrayBindingElement(): ArrayBindingElement { - const pos = getNodePos(); - if (token() === SyntaxKind.CommaToken) { - return finishNode(factory.createOmittedExpression(), pos); - } - const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - const name = parseIdentifierOrPattern(); - const initializer = parseInitializer(); - return finishNode(factory.createBindingElement(dotDotDotToken, /*propertyName*/ undefined, name, initializer), pos); + default: + return isStartOfExpression(); } + } - function parseObjectBindingElement(): BindingElement { - const pos = getNodePos(); - const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - const tokenIsIdentifier = isBindingIdentifier(); - let propertyName: PropertyName | undefined = parsePropertyName(); - let name: BindingName; - if (tokenIsIdentifier && token() !== SyntaxKind.ColonToken) { - name = propertyName as Identifier; - propertyName = undefined; - } - else { - parseExpected(SyntaxKind.ColonToken); - name = parseIdentifierOrPattern(); - } - const initializer = parseInitializer(); - return finishNode(factory.createBindingElement(dotDotDotToken, propertyName, name, initializer), pos); - } + function nextTokenIsBindingIdentifierOrStartOfDestructuring() { + nextToken(); + return isBindingIdentifier() || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.OpenBracketToken; + } - function parseObjectBindingPattern(): ObjectBindingPattern { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBraceToken); - const elements = parseDelimitedList(ParsingContext.ObjectBindingElements, parseObjectBindingElement); - parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(factory.createObjectBindingPattern(elements), pos); - } + function isLetDeclaration() { + // In ES6 'let' always starts a lexical declaration if followed by an identifier or { + // or [. + return lookAhead(nextTokenIsBindingIdentifierOrStartOfDestructuring); + } - function parseArrayBindingPattern(): ArrayBindingPattern { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBracketToken); - const elements = parseDelimitedList(ParsingContext.ArrayBindingElements, parseArrayBindingElement); - parseExpected(SyntaxKind.CloseBracketToken); - return finishNode(factory.createArrayBindingPattern(elements), pos); + function parseStatement(): Statement { + switch (token()) { + case SyntaxKind.SemicolonToken: + return parseEmptyStatement(); + case SyntaxKind.OpenBraceToken: + return parseBlock(/*ignoreMissingOpenBrace*/ false); + case SyntaxKind.VarKeyword: + return parseVariableStatement(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); + case SyntaxKind.LetKeyword: + if (isLetDeclaration()) { + return parseVariableStatement(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); + } + break; + case SyntaxKind.FunctionKeyword: + return parseFunctionDeclaration(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); + case SyntaxKind.ClassKeyword: + return parseClassDeclaration(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); + case SyntaxKind.IfKeyword: + return parseIfStatement(); + case SyntaxKind.DoKeyword: + return parseDoStatement(); + case SyntaxKind.WhileKeyword: + return parseWhileStatement(); + case SyntaxKind.ForKeyword: + return parseForOrForInOrForOfStatement(); + case SyntaxKind.ContinueKeyword: + return parseBreakOrContinueStatement(SyntaxKind.ContinueStatement); + case SyntaxKind.BreakKeyword: + return parseBreakOrContinueStatement(SyntaxKind.BreakStatement); + case SyntaxKind.ReturnKeyword: + return parseReturnStatement(); + case SyntaxKind.WithKeyword: + return parseWithStatement(); + case SyntaxKind.SwitchKeyword: + return parseSwitchStatement(); + case SyntaxKind.ThrowKeyword: + return parseThrowStatement(); + case SyntaxKind.TryKeyword: + // Include 'catch' and 'finally' for error recovery. + // falls through + case SyntaxKind.CatchKeyword: + case SyntaxKind.FinallyKeyword: + return parseTryStatement(); + case SyntaxKind.DebuggerKeyword: + return parseDebuggerStatement(); + case SyntaxKind.AtToken: + return parseDeclaration(); + case SyntaxKind.AsyncKeyword: + case SyntaxKind.InterfaceKeyword: + case SyntaxKind.TypeKeyword: + case SyntaxKind.ModuleKeyword: + case SyntaxKind.NamespaceKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.ConstKeyword: + case SyntaxKind.EnumKeyword: + case SyntaxKind.ExportKeyword: + case SyntaxKind.ImportKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PublicKeyword: + case SyntaxKind.AbstractKeyword: + case SyntaxKind.StaticKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.GlobalKeyword: + if (isStartOfDeclaration()) { + return parseDeclaration(); + } + break; } + return parseExpressionOrLabeledStatement(); + } - function isBindingIdentifierOrPrivateIdentifierOrPattern() { - return token() === SyntaxKind.OpenBraceToken - || token() === SyntaxKind.OpenBracketToken - || token() === SyntaxKind.PrivateIdentifier - || isBindingIdentifier(); - } + function isDeclareModifier(modifier: Modifier) { + return modifier.kind === SyntaxKind.DeclareKeyword; + } - function parseIdentifierOrPattern(privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier | BindingPattern { - if (token() === SyntaxKind.OpenBracketToken) { - return parseArrayBindingPattern(); - } - if (token() === SyntaxKind.OpenBraceToken) { - return parseObjectBindingPattern(); + function parseDeclaration(): Statement { + // TODO: Can we hold onto the parsed decorators/modifiers and advance the scanner + // if we can't reuse the declaration, so that we don't do this work twice? + // + // `parseListElement` attempted to get the reused node at this position, + // but the ambient context flag was not yet set, so the node appeared + // not reusable in that context. + const isAmbient = some(lookAhead(() => (parseDecorators(), parseModifiers())), isDeclareModifier); + if (isAmbient) { + const node = tryReuseAmbientDeclaration(); + if (node) { + return node; } - return parseBindingIdentifier(privateIdentifierDiagnosticMessage); } - function parseVariableDeclarationAllowExclamation() { - return parseVariableDeclaration(/*allowExclamation*/ true); - } - - function parseVariableDeclaration(allowExclamation?: boolean): VariableDeclaration { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_are_not_allowed_in_variable_declarations); - let exclamationToken: ExclamationToken | undefined; - if (allowExclamation && name.kind === SyntaxKind.Identifier && - token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { - exclamationToken = parseTokenNode>(); + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const decorators = parseDecorators(); + const modifiers = parseModifiers(); + if (isAmbient) { + for (const m of modifiers!) { + (m as Mutable).flags |= NodeFlags.Ambient; } - const type = parseTypeAnnotation(); - const initializer = isInOrOfKeyword(token()) ? undefined : parseInitializer(); - const node = factory.createVariableDeclaration(name, exclamationToken, type, initializer); - return withJSDoc(finishNode(node, pos), hasJSDoc); + return doInsideOfContext(NodeFlags.Ambient, () => parseDeclarationWorker(pos, hasJSDoc, decorators, modifiers)); } + else { + return parseDeclarationWorker(pos, hasJSDoc, decorators, modifiers); + } + } - function parseVariableDeclarationList(inForStatementInitializer: boolean): VariableDeclarationList { - const pos = getNodePos(); - - let flags: NodeFlags = 0; - switch (token()) { - case SyntaxKind.VarKeyword: - break; - case SyntaxKind.LetKeyword: - flags |= NodeFlags.Let; - break; - case SyntaxKind.ConstKeyword: - flags |= NodeFlags.Const; - break; - default: - Debug.fail(); - } - - nextToken(); - - // The user may have written the following: - // - // for (let of X) { } - // - // In this case, we want to parse an empty declaration list, and then parse 'of' - // as a keyword. The reason this is not automatic is that 'of' is a valid identifier. - // So we need to look ahead to determine if 'of' should be treated as a keyword in - // this context. - // The checker will then give an error that there is an empty declaration list. - let declarations: readonly VariableDeclaration[]; - if (token() === SyntaxKind.OfKeyword && lookAhead(canFollowContextualOfKeyword)) { - declarations = createMissingList(); - } - else { - const savedDisallowIn = inDisallowInContext(); - setDisallowInContext(inForStatementInitializer); - - declarations = parseDelimitedList(ParsingContext.VariableDeclarations, - inForStatementInitializer ? parseVariableDeclaration : parseVariableDeclarationAllowExclamation); - - setDisallowInContext(savedDisallowIn); + function tryReuseAmbientDeclaration(): Statement | undefined { + return doInsideOfContext(NodeFlags.Ambient, () => { + const node = currentNode(parsingContext); + if (node) { + return consumeNode(node) as Statement; } + }); + } - return finishNode(factory.createVariableDeclarationList(declarations, flags), pos); + function parseDeclarationWorker(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): Statement { + switch (token()) { + case SyntaxKind.VarKeyword: + case SyntaxKind.LetKeyword: + case SyntaxKind.ConstKeyword: + return parseVariableStatement(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.FunctionKeyword: + return parseFunctionDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.ClassKeyword: + return parseClassDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.InterfaceKeyword: + return parseInterfaceDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.TypeKeyword: + return parseTypeAliasDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.EnumKeyword: + return parseEnumDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.GlobalKeyword: + case SyntaxKind.ModuleKeyword: + case SyntaxKind.NamespaceKeyword: + return parseModuleDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.ImportKeyword: + return parseImportDeclarationOrImportEqualsDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.ExportKeyword: + nextToken(); + switch (token()) { + case SyntaxKind.DefaultKeyword: + case SyntaxKind.EqualsToken: + return parseExportAssignment(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.AsKeyword: + return parseNamespaceExportDeclaration(pos, hasJSDoc, decorators, modifiers); + default: + return parseExportDeclaration(pos, hasJSDoc, decorators, modifiers); + } + default: + if (decorators || modifiers) { + // We reached this point because we encountered decorators and/or modifiers and assumed a declaration + // would follow. For recovery and error reporting purposes, return an incomplete declaration. + const missing = createMissingNode(SyntaxKind.MissingDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); + setTextRangePos(missing, pos); + missing.decorators = decorators; + missing.modifiers = modifiers; + return missing; + } + return undefined!; // TODO: GH#18217 } + } - function canFollowContextualOfKeyword(): boolean { - return nextTokenIsIdentifier() && nextToken() === SyntaxKind.CloseParenToken; - } + function nextTokenIsIdentifierOrStringLiteralOnSameLine() { + nextToken(); + return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token() === SyntaxKind.StringLiteral); + } - function parseVariableStatement(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): VariableStatement { - const declarationList = parseVariableDeclarationList(/*inForStatementInitializer*/ false); + function parseFunctionBlockOrSemicolon(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block | undefined { + if (token() !== SyntaxKind.OpenBraceToken && canParseSemicolon()) { parseSemicolon(); - const node = factory.createVariableStatement(modifiers, declarationList); - // Decorators are not allowed on a variable statement, so we keep track of them to report them in the grammar checker. - node.decorators = decorators; - return withJSDoc(finishNode(node, pos), hasJSDoc); - } - - function parseFunctionDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): FunctionDeclaration { - const savedAwaitContext = inAwaitContext(); - - const modifierFlags = modifiersToFlags(modifiers); - parseExpected(SyntaxKind.FunctionKeyword); - const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - // We don't parse the name here in await context, instead we will report a grammar error in the checker. - const name = modifierFlags & ModifierFlags.Default ? parseOptionalBindingIdentifier() : parseBindingIdentifier(); - const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; - const isAsync = modifierFlags & ModifierFlags.Async ? SignatureFlags.Await : SignatureFlags.None; - const typeParameters = parseTypeParameters(); - if (modifierFlags & ModifierFlags.Export) setAwaitContext(/*value*/ true); - const parameters = parseParameters(isGenerator | isAsync); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - const body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, Diagnostics.or_expected); - setAwaitContext(savedAwaitContext); - const node = factory.createFunctionDeclaration(decorators, modifiers, asteriskToken, name, typeParameters, parameters, type, body); - return withJSDoc(finishNode(node, pos), hasJSDoc); - } - - function parseConstructorName() { - if (token() === SyntaxKind.ConstructorKeyword) { - return parseExpected(SyntaxKind.ConstructorKeyword); - } - if (token() === SyntaxKind.StringLiteral && lookAhead(nextToken) === SyntaxKind.OpenParenToken) { - return tryParse(() => { - const literalNode = parseLiteralNode(); - return literalNode.text === "constructor" ? literalNode : undefined; - }); - } + return; } - function tryParseConstructorDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ConstructorDeclaration | undefined { - return tryParse(() => { - if (parseConstructorName()) { - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(SignatureFlags.None); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - const body = parseFunctionBlockOrSemicolon(SignatureFlags.None, Diagnostics.or_expected); - const node = factory.createConstructorDeclaration(decorators, modifiers, parameters, body); - // Attach `typeParameters` and `type` if they exist so that we can report them in the grammar checker. - node.typeParameters = typeParameters; - node.type = type; - return withJSDoc(finishNode(node, pos), hasJSDoc); - } - }); - } + return parseFunctionBlock(flags, diagnosticMessage); + } - function parseMethodDeclaration( - pos: number, - hasJSDoc: boolean, - decorators: NodeArray | undefined, - modifiers: NodeArray | undefined, - asteriskToken: AsteriskToken | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined, - exclamationToken: ExclamationToken | undefined, - diagnosticMessage?: DiagnosticMessage - ): MethodDeclaration { - const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; - const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(isGenerator | isAsync); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - const body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, diagnosticMessage); - const node = factory.createMethodDeclaration( - decorators, - modifiers, - asteriskToken, - name, - questionToken, - typeParameters, - parameters, - type, - body - ); - // An exclamation token on a method is invalid syntax and will be handled by the grammar checker - node.exclamationToken = exclamationToken; - return withJSDoc(finishNode(node, pos), hasJSDoc); - } + // DECLARATIONS - function parsePropertyDeclaration( - pos: number, - hasJSDoc: boolean, - decorators: NodeArray | undefined, - modifiers: NodeArray | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined - ): PropertyDeclaration { - const exclamationToken = !questionToken && !scanner.hasPrecedingLineBreak() ? parseOptionalToken(SyntaxKind.ExclamationToken) : undefined; - const type = parseTypeAnnotation(); - const initializer = doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext | NodeFlags.DisallowInContext, parseInitializer); - parseSemicolonAfterPropertyName(name, type, initializer); - const node = factory.createPropertyDeclaration(decorators, modifiers, name, questionToken || exclamationToken, type, initializer); - return withJSDoc(finishNode(node, pos), hasJSDoc); + function parseArrayBindingElement(): ArrayBindingElement { + const pos = getNodePos(); + if (token() === SyntaxKind.CommaToken) { + return finishNode(factory.createOmittedExpression(), pos); } + const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + const name = parseIdentifierOrPattern(); + const initializer = parseInitializer(); + return finishNode(factory.createBindingElement(dotDotDotToken, /*propertyName*/ undefined, name, initializer), pos); + } - function parsePropertyOrMethodDeclaration( - pos: number, - hasJSDoc: boolean, - decorators: NodeArray | undefined, - modifiers: NodeArray | undefined - ): PropertyDeclaration | MethodDeclaration { - const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - const name = parsePropertyName(); - // Note: this is not legal as per the grammar. But we allow it in the parser and - // report an error in the grammar checker. - const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { - return parseMethodDeclaration(pos, hasJSDoc, decorators, modifiers, asteriskToken, name, questionToken, /*exclamationToken*/ undefined, Diagnostics.or_expected); - } - return parsePropertyDeclaration(pos, hasJSDoc, decorators, modifiers, name, questionToken); + function parseObjectBindingElement(): BindingElement { + const pos = getNodePos(); + const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + const tokenIsIdentifier = isBindingIdentifier(); + let propertyName: PropertyName | undefined = parsePropertyName(); + let name: BindingName; + if (tokenIsIdentifier && token() !== SyntaxKind.ColonToken) { + name = propertyName as Identifier; + propertyName = undefined; } - - function parseAccessorDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, kind: AccessorDeclaration["kind"]): AccessorDeclaration { - const name = parsePropertyName(); - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(SignatureFlags.None); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - const body = parseFunctionBlockOrSemicolon(SignatureFlags.None); - const node = kind === SyntaxKind.GetAccessor - ? factory.createGetAccessorDeclaration(decorators, modifiers, name, parameters, type, body) - : factory.createSetAccessorDeclaration(decorators, modifiers, name, parameters, body); - // Keep track of `typeParameters` (for both) and `type` (for setters) if they were parsed those indicate grammar errors - node.typeParameters = typeParameters; - if (type && node.kind === SyntaxKind.SetAccessor) (node as Mutable).type = type; - return withJSDoc(finishNode(node, pos), hasJSDoc); + else { + parseExpected(SyntaxKind.ColonToken); + name = parseIdentifierOrPattern(); } + const initializer = parseInitializer(); + return finishNode(factory.createBindingElement(dotDotDotToken, propertyName, name, initializer), pos); + } - function isClassMemberStart(): boolean { - let idToken: SyntaxKind | undefined; - - if (token() === SyntaxKind.AtToken) { - return true; - } - - // Eat up all modifiers, but hold on to the last one in case it is actually an identifier. - while (isModifierKind(token())) { - idToken = token(); - // If the idToken is a class modifier (protected, private, public, and static), it is - // certain that we are starting to parse class member. This allows better error recovery - // Example: - // public foo() ... // true - // public @dec blah ... // true; we will then report an error later - // export public ... // true; we will then report an error later - if (isClassMemberModifier(idToken)) { - return true; - } - - nextToken(); - } - - if (token() === SyntaxKind.AsteriskToken) { - return true; - } - - // Try to get the first property-like token following all modifiers. - // This can either be an identifier or the 'get' or 'set' keywords. - if (isLiteralPropertyName()) { - idToken = token(); - nextToken(); - } - - // Index signatures and computed properties are class members; we can parse. - if (token() === SyntaxKind.OpenBracketToken) { - return true; - } + function parseObjectBindingPattern(): ObjectBindingPattern { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBraceToken); + const elements = parseDelimitedList(ParsingContext.ObjectBindingElements, parseObjectBindingElement); + parseExpected(SyntaxKind.CloseBraceToken); + return finishNode(factory.createObjectBindingPattern(elements), pos); + } - // If we were able to get any potential identifier... - if (idToken !== undefined) { - // If we have a non-keyword identifier, or if we have an accessor, then it's safe to parse. - if (!isKeyword(idToken) || idToken === SyntaxKind.SetKeyword || idToken === SyntaxKind.GetKeyword) { - return true; - } + function parseArrayBindingPattern(): ArrayBindingPattern { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBracketToken); + const elements = parseDelimitedList(ParsingContext.ArrayBindingElements, parseArrayBindingElement); + parseExpected(SyntaxKind.CloseBracketToken); + return finishNode(factory.createArrayBindingPattern(elements), pos); + } - // If it *is* a keyword, but not an accessor, check a little farther along - // to see if it should actually be parsed as a class member. - switch (token()) { - case SyntaxKind.OpenParenToken: // Method declaration - case SyntaxKind.LessThanToken: // Generic Method declaration - case SyntaxKind.ExclamationToken: // Non-null assertion on property name - case SyntaxKind.ColonToken: // Type Annotation for declaration - case SyntaxKind.EqualsToken: // Initializer for declaration - case SyntaxKind.QuestionToken: // Not valid, but permitted so that it gets caught later on. - return true; - default: - // Covers - // - Semicolons (declaration termination) - // - Closing braces (end-of-class, must be declaration) - // - End-of-files (not valid, but permitted so that it gets caught later on) - // - Line-breaks (enabling *automatic semicolon insertion*) - return canParseSemicolon(); - } - } + function isBindingIdentifierOrPrivateIdentifierOrPattern() { + return token() === SyntaxKind.OpenBraceToken + || token() === SyntaxKind.OpenBracketToken + || token() === SyntaxKind.PrivateIdentifier + || isBindingIdentifier(); + } - return false; + function parseIdentifierOrPattern(privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier | BindingPattern { + if (token() === SyntaxKind.OpenBracketToken) { + return parseArrayBindingPattern(); } - - function parseClassStaticBlockDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: ModifiersArray | undefined): ClassStaticBlockDeclaration { - parseExpectedToken(SyntaxKind.StaticKeyword); - const body = parseClassStaticBlockBody(); - return withJSDoc(finishNode(factory.createClassStaticBlockDeclaration(decorators, modifiers, body), pos), hasJSDoc); + if (token() === SyntaxKind.OpenBraceToken) { + return parseObjectBindingPattern(); } + return parseBindingIdentifier(privateIdentifierDiagnosticMessage); + } - function parseClassStaticBlockBody() { - const savedYieldContext = inYieldContext(); - const savedAwaitContext = inAwaitContext(); - - setYieldContext(false); - setAwaitContext(true); + function parseVariableDeclarationAllowExclamation() { + return parseVariableDeclaration(/*allowExclamation*/ true); + } - const body = parseBlock(/*ignoreMissingOpenBrace*/ false); + function parseVariableDeclaration(allowExclamation?: boolean): VariableDeclaration { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_are_not_allowed_in_variable_declarations); + let exclamationToken: ExclamationToken | undefined; + if (allowExclamation && name.kind === SyntaxKind.Identifier && + token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { + exclamationToken = parseTokenNode>(); + } + const type = parseTypeAnnotation(); + const initializer = isInOrOfKeyword(token()) ? undefined : parseInitializer(); + const node = factory.createVariableDeclaration(name, exclamationToken, type, initializer); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - setYieldContext(savedYieldContext); - setAwaitContext(savedAwaitContext); + function parseVariableDeclarationList(inForStatementInitializer: boolean): VariableDeclarationList { + const pos = getNodePos(); - return body; + let flags: NodeFlags = 0; + switch (token()) { + case SyntaxKind.VarKeyword: + break; + case SyntaxKind.LetKeyword: + flags |= NodeFlags.Let; + break; + case SyntaxKind.ConstKeyword: + flags |= NodeFlags.Const; + break; + default: + Debug.fail(); } - function parseDecoratorExpression() { - if (inAwaitContext() && token() === SyntaxKind.AwaitKeyword) { - // `@await` is is disallowed in an [Await] context, but can cause parsing to go off the rails - // This simply parses the missing identifier and moves on. - const pos = getNodePos(); - const awaitExpression = parseIdentifier(Diagnostics.Expression_expected); - nextToken(); - const memberExpression = parseMemberExpressionRest(pos, awaitExpression, /*allowOptionalChain*/ true); - return parseCallExpressionRest(pos, memberExpression); - } - return parseLeftHandSideExpressionOrHigher(); - } + nextToken(); - function tryParseDecorator(): Decorator | undefined { - const pos = getNodePos(); - if (!parseOptional(SyntaxKind.AtToken)) { - return undefined; - } - const expression = doInDecoratorContext(parseDecoratorExpression); - return finishNode(factory.createDecorator(expression), pos); + // The user may have written the following: + // + // for (let of X) { } + // + // In this case, we want to parse an empty declaration list, and then parse 'of' + // as a keyword. The reason this is not automatic is that 'of' is a valid identifier. + // So we need to look ahead to determine if 'of' should be treated as a keyword in + // this context. + // The checker will then give an error that there is an empty declaration list. + let declarations: readonly VariableDeclaration[]; + if (token() === SyntaxKind.OfKeyword && lookAhead(canFollowContextualOfKeyword)) { + declarations = createMissingList(); } + else { + const savedDisallowIn = inDisallowInContext(); + setDisallowInContext(inForStatementInitializer); - function parseDecorators(): NodeArray | undefined { - const pos = getNodePos(); - let list, decorator; - while (decorator = tryParseDecorator()) { - list = append(list, decorator); - } - return list && createNodeArray(list, pos); + declarations = parseDelimitedList(ParsingContext.VariableDeclarations, + inForStatementInitializer ? parseVariableDeclaration : parseVariableDeclarationAllowExclamation); + + setDisallowInContext(savedDisallowIn); } - function tryParseModifier(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean, hasSeenStaticModifier?: boolean): Modifier | undefined { - const pos = getNodePos(); - const kind = token(); + return finishNode(factory.createVariableDeclarationList(declarations, flags), pos); + } - if (token() === SyntaxKind.ConstKeyword && permitInvalidConstAsModifier) { - // We need to ensure that any subsequent modifiers appear on the same line - // so that when 'const' is a standalone declaration, we don't issue an error. - if (!tryParse(nextTokenIsOnSameLineAndCanFollowModifier)) { - return undefined; - } - } - else if (stopOnStartOfClassStaticBlock && token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { - return undefined; - } - else if (hasSeenStaticModifier && token() === SyntaxKind.StaticKeyword) { - return undefined; - } - else { - if (!parseAnyContextualModifier()) { - return undefined; - } - } + function canFollowContextualOfKeyword(): boolean { + return nextTokenIsIdentifier() && nextToken() === SyntaxKind.CloseParenToken; + } - return finishNode(factory.createToken(kind as Modifier["kind"]), pos); - } + function parseVariableStatement(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): VariableStatement { + const declarationList = parseVariableDeclarationList(/*inForStatementInitializer*/ false); + parseSemicolon(); + const node = factory.createVariableStatement(modifiers, declarationList); + // Decorators are not allowed on a variable statement, so we keep track of them to report them in the grammar checker. + node.decorators = decorators; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - /* - * There are situations in which a modifier like 'const' will appear unexpectedly, such as on a class member. - * In those situations, if we are entirely sure that 'const' is not valid on its own (such as when ASI takes effect - * and turns it into a standalone declaration), then it is better to parse it and report an error later. - * - * In such situations, 'permitInvalidConstAsModifier' should be set to true. - */ - function parseModifiers(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean): NodeArray | undefined { - const pos = getNodePos(); - let list, modifier, hasSeenStatic = false; - while (modifier = tryParseModifier(permitInvalidConstAsModifier, stopOnStartOfClassStaticBlock, hasSeenStatic)) { - if (modifier.kind === SyntaxKind.StaticKeyword) hasSeenStatic = true; - list = append(list, modifier); - } - return list && createNodeArray(list, pos); - } + function parseFunctionDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): FunctionDeclaration { + const savedAwaitContext = inAwaitContext(); + + const modifierFlags = modifiersToFlags(modifiers); + parseExpected(SyntaxKind.FunctionKeyword); + const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + // We don't parse the name here in await context, instead we will report a grammar error in the checker. + const name = modifierFlags & ModifierFlags.Default ? parseOptionalBindingIdentifier() : parseBindingIdentifier(); + const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; + const isAsync = modifierFlags & ModifierFlags.Async ? SignatureFlags.Await : SignatureFlags.None; + const typeParameters = parseTypeParameters(); + if (modifierFlags & ModifierFlags.Export) setAwaitContext(/*value*/ true); + const parameters = parseParameters(isGenerator | isAsync); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + const body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, Diagnostics.or_expected); + setAwaitContext(savedAwaitContext); + const node = factory.createFunctionDeclaration(decorators, modifiers, asteriskToken, name, typeParameters, parameters, type, body); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function parseModifiersForArrowFunction(): NodeArray | undefined { - let modifiers: NodeArray | undefined; - if (token() === SyntaxKind.AsyncKeyword) { - const pos = getNodePos(); - nextToken(); - const modifier = finishNode(factory.createToken(SyntaxKind.AsyncKeyword), pos); - modifiers = createNodeArray([modifier], pos); - } - return modifiers; + function parseConstructorName() { + if (token() === SyntaxKind.ConstructorKeyword) { + return parseExpected(SyntaxKind.ConstructorKeyword); + } + if (token() === SyntaxKind.StringLiteral && lookAhead(nextToken) === SyntaxKind.OpenParenToken) { + return tryParse(() => { + const literalNode = parseLiteralNode(); + return literalNode.text === "constructor" ? literalNode : undefined; + }); } + } - function parseClassElement(): ClassElement { - const pos = getNodePos(); - if (token() === SyntaxKind.SemicolonToken) { - nextToken(); - return finishNode(factory.createSemicolonClassElement(), pos); + function tryParseConstructorDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ConstructorDeclaration | undefined { + return tryParse(() => { + if (parseConstructorName()) { + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(SignatureFlags.None); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + const body = parseFunctionBlockOrSemicolon(SignatureFlags.None, Diagnostics.or_expected); + const node = factory.createConstructorDeclaration(decorators, modifiers, parameters, body); + // Attach `typeParameters` and `type` if they exist so that we can report them in the grammar checker. + node.typeParameters = typeParameters; + node.type = type; + return withJSDoc(finishNode(node, pos), hasJSDoc); } + }); + } - const hasJSDoc = hasPrecedingJSDocComment(); - const decorators = parseDecorators(); - const modifiers = parseModifiers(/*permitInvalidConstAsModifier*/ true, /*stopOnStartOfClassStaticBlock*/ true); - if (token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { - return parseClassStaticBlockDeclaration(pos, hasJSDoc, decorators, modifiers); - } + function parseMethodDeclaration( + pos: number, + hasJSDoc: boolean, + decorators: NodeArray | undefined, + modifiers: NodeArray | undefined, + asteriskToken: AsteriskToken | undefined, + name: PropertyName, + questionToken: QuestionToken | undefined, + exclamationToken: ExclamationToken | undefined, + diagnosticMessage?: DiagnosticMessage + ): MethodDeclaration { + const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; + const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(isGenerator | isAsync); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + const body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, diagnosticMessage); + const node = factory.createMethodDeclaration( + decorators, + modifiers, + asteriskToken, + name, + questionToken, + typeParameters, + parameters, + type, + body + ); + // An exclamation token on a method is invalid syntax and will be handled by the grammar checker + node.exclamationToken = exclamationToken; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - if (parseContextualModifier(SyntaxKind.GetKeyword)) { - return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.GetAccessor); - } + function parsePropertyDeclaration( + pos: number, + hasJSDoc: boolean, + decorators: NodeArray | undefined, + modifiers: NodeArray | undefined, + name: PropertyName, + questionToken: QuestionToken | undefined + ): PropertyDeclaration { + const exclamationToken = !questionToken && !scanner.hasPrecedingLineBreak() ? parseOptionalToken(SyntaxKind.ExclamationToken) : undefined; + const type = parseTypeAnnotation(); + const initializer = doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext | NodeFlags.DisallowInContext, parseInitializer); + parseSemicolonAfterPropertyName(name, type, initializer); + const node = factory.createPropertyDeclaration(decorators, modifiers, name, questionToken || exclamationToken, type, initializer); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - if (parseContextualModifier(SyntaxKind.SetKeyword)) { - return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.SetAccessor); - } + function parsePropertyOrMethodDeclaration( + pos: number, + hasJSDoc: boolean, + decorators: NodeArray | undefined, + modifiers: NodeArray | undefined + ): PropertyDeclaration | MethodDeclaration { + const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + const name = parsePropertyName(); + // Note: this is not legal as per the grammar. But we allow it in the parser and + // report an error in the grammar checker. + const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { + return parseMethodDeclaration(pos, hasJSDoc, decorators, modifiers, asteriskToken, name, questionToken, /*exclamationToken*/ undefined, Diagnostics.or_expected); + } + return parsePropertyDeclaration(pos, hasJSDoc, decorators, modifiers, name, questionToken); + } - if (token() === SyntaxKind.ConstructorKeyword || token() === SyntaxKind.StringLiteral) { - const constructorDeclaration = tryParseConstructorDeclaration(pos, hasJSDoc, decorators, modifiers); - if (constructorDeclaration) { - return constructorDeclaration; - } - } + function parseAccessorDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, kind: AccessorDeclaration["kind"]): AccessorDeclaration { + const name = parsePropertyName(); + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(SignatureFlags.None); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + const body = parseFunctionBlockOrSemicolon(SignatureFlags.None); + const node = kind === SyntaxKind.GetAccessor + ? factory.createGetAccessorDeclaration(decorators, modifiers, name, parameters, type, body) + : factory.createSetAccessorDeclaration(decorators, modifiers, name, parameters, body); + // Keep track of `typeParameters` (for both) and `type` (for setters) if they were parsed those indicate grammar errors + node.typeParameters = typeParameters; + if (type && node.kind === SyntaxKind.SetAccessor) (node as Mutable).type = type; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - if (isIndexSignature()) { - return parseIndexSignatureDeclaration(pos, hasJSDoc, decorators, modifiers); - } + function isClassMemberStart(): boolean { + let idToken: SyntaxKind | undefined; - // It is very important that we check this *after* checking indexers because - // the [ token can start an index signature or a computed property name - if (tokenIsIdentifierOrKeyword(token()) || - token() === SyntaxKind.StringLiteral || - token() === SyntaxKind.NumericLiteral || - token() === SyntaxKind.AsteriskToken || - token() === SyntaxKind.OpenBracketToken) { - const isAmbient = some(modifiers, isDeclareModifier); - if (isAmbient) { - for (const m of modifiers!) { - (m as Mutable).flags |= NodeFlags.Ambient; - } - return doInsideOfContext(NodeFlags.Ambient, () => parsePropertyOrMethodDeclaration(pos, hasJSDoc, decorators, modifiers)); - } - else { - return parsePropertyOrMethodDeclaration(pos, hasJSDoc, decorators, modifiers); - } - } + if (token() === SyntaxKind.AtToken) { + return true; + } - if (decorators || modifiers) { - // treat this as a property declaration with a missing name. - const name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); - return parsePropertyDeclaration(pos, hasJSDoc, decorators, modifiers, name, /*questionToken*/ undefined); + // Eat up all modifiers, but hold on to the last one in case it is actually an identifier. + while (isModifierKind(token())) { + idToken = token(); + // If the idToken is a class modifier (protected, private, public, and static), it is + // certain that we are starting to parse class member. This allows better error recovery + // Example: + // public foo() ... // true + // public @dec blah ... // true; we will then report an error later + // export public ... // true; we will then report an error later + if (isClassMemberModifier(idToken)) { + return true; } - // 'isClassMemberStart' should have hinted not to attempt parsing. - return Debug.fail("Should not have attempted to parse class member declaration."); + nextToken(); } - function parseClassExpression(): ClassExpression { - return parseClassDeclarationOrExpression(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined, SyntaxKind.ClassExpression) as ClassExpression; + if (token() === SyntaxKind.AsteriskToken) { + return true; } - function parseClassDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ClassDeclaration { - return parseClassDeclarationOrExpression(pos, hasJSDoc, decorators, modifiers, SyntaxKind.ClassDeclaration) as ClassDeclaration; + // Try to get the first property-like token following all modifiers. + // This can either be an identifier or the 'get' or 'set' keywords. + if (isLiteralPropertyName()) { + idToken = token(); + nextToken(); } - function parseClassDeclarationOrExpression(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, kind: ClassLikeDeclaration["kind"]): ClassLikeDeclaration { - const savedAwaitContext = inAwaitContext(); - parseExpected(SyntaxKind.ClassKeyword); - - // We don't parse the name here in await context, instead we will report a grammar error in the checker. - const name = parseNameOfClassDeclarationOrExpression(); - const typeParameters = parseTypeParameters(); - if (some(modifiers, isExportModifier)) setAwaitContext(/*value*/ true); - const heritageClauses = parseHeritageClauses(); + // Index signatures and computed properties are class members; we can parse. + if (token() === SyntaxKind.OpenBracketToken) { + return true; + } - let members; - if (parseExpected(SyntaxKind.OpenBraceToken)) { - // ClassTail[Yield,Await] : (Modified) See 14.5 - // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } - members = parseClassMembers(); - parseExpected(SyntaxKind.CloseBraceToken); + // If we were able to get any potential identifier... + if (idToken !== undefined) { + // If we have a non-keyword identifier, or if we have an accessor, then it's safe to parse. + if (!isKeyword(idToken) || idToken === SyntaxKind.SetKeyword || idToken === SyntaxKind.GetKeyword) { + return true; } - else { - members = createMissingList(); + + // If it *is* a keyword, but not an accessor, check a little farther along + // to see if it should actually be parsed as a class member. + switch (token()) { + case SyntaxKind.OpenParenToken: // Method declaration + case SyntaxKind.LessThanToken: // Generic Method declaration + case SyntaxKind.ExclamationToken: // Non-null assertion on property name + case SyntaxKind.ColonToken: // Type Annotation for declaration + case SyntaxKind.EqualsToken: // Initializer for declaration + case SyntaxKind.QuestionToken: // Not valid, but permitted so that it gets caught later on. + return true; + default: + // Covers + // - Semicolons (declaration termination) + // - Closing braces (end-of-class, must be declaration) + // - End-of-files (not valid, but permitted so that it gets caught later on) + // - Line-breaks (enabling *automatic semicolon insertion*) + return canParseSemicolon(); } - setAwaitContext(savedAwaitContext); - const node = kind === SyntaxKind.ClassDeclaration - ? factory.createClassDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members) - : factory.createClassExpression(decorators, modifiers, name, typeParameters, heritageClauses, members); - return withJSDoc(finishNode(node, pos), hasJSDoc); } - function parseNameOfClassDeclarationOrExpression(): Identifier | undefined { - // implements is a future reserved word so - // 'class implements' might mean either - // - class expression with omitted name, 'implements' starts heritage clause - // - class with name 'implements' - // 'isImplementsClause' helps to disambiguate between these two cases - return isBindingIdentifier() && !isImplementsClause() - ? createIdentifier(isBindingIdentifier()) - : undefined; - } + return false; + } - function isImplementsClause() { - return token() === SyntaxKind.ImplementsKeyword && lookAhead(nextTokenIsIdentifierOrKeyword); - } + function parseClassStaticBlockDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: ModifiersArray | undefined): ClassStaticBlockDeclaration { + parseExpectedToken(SyntaxKind.StaticKeyword); + const body = parseClassStaticBlockBody(); + return withJSDoc(finishNode(factory.createClassStaticBlockDeclaration(decorators, modifiers, body), pos), hasJSDoc); + } - function parseHeritageClauses(): NodeArray | undefined { - // ClassTail[Yield,Await] : (Modified) See 14.5 - // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } + function parseClassStaticBlockBody() { + const savedYieldContext = inYieldContext(); + const savedAwaitContext = inAwaitContext(); - if (isHeritageClause()) { - return parseList(ParsingContext.HeritageClauses, parseHeritageClause); - } + setYieldContext(false); + setAwaitContext(true); - return undefined; - } + const body = parseBlock(/*ignoreMissingOpenBrace*/ false); + + setYieldContext(savedYieldContext); + setAwaitContext(savedAwaitContext); - function parseHeritageClause(): HeritageClause { + return body; + } + + function parseDecoratorExpression() { + if (inAwaitContext() && token() === SyntaxKind.AwaitKeyword) { + // `@await` is is disallowed in an [Await] context, but can cause parsing to go off the rails + // This simply parses the missing identifier and moves on. const pos = getNodePos(); - const tok = token(); - Debug.assert(tok === SyntaxKind.ExtendsKeyword || tok === SyntaxKind.ImplementsKeyword); // isListElement() should ensure this. + const awaitExpression = parseIdentifier(Diagnostics.Expression_expected); nextToken(); - const types = parseDelimitedList(ParsingContext.HeritageClauseElement, parseExpressionWithTypeArguments); - return finishNode(factory.createHeritageClause(tok, types), pos); + const memberExpression = parseMemberExpressionRest(pos, awaitExpression, /*allowOptionalChain*/ true); + return parseCallExpressionRest(pos, memberExpression); } + return parseLeftHandSideExpressionOrHigher(); + } - function parseExpressionWithTypeArguments(): ExpressionWithTypeArguments { - const pos = getNodePos(); - const expression = parseLeftHandSideExpressionOrHigher(); - const typeArguments = tryParseTypeArguments(); - return finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos); + function tryParseDecorator(): Decorator | undefined { + const pos = getNodePos(); + if (!parseOptional(SyntaxKind.AtToken)) { + return undefined; } + const expression = doInDecoratorContext(parseDecoratorExpression); + return finishNode(factory.createDecorator(expression), pos); + } - function tryParseTypeArguments(): NodeArray | undefined { - return token() === SyntaxKind.LessThanToken ? - parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken) : undefined; + function parseDecorators(): NodeArray | undefined { + const pos = getNodePos(); + let list, decorator; + while (decorator = tryParseDecorator()) { + list = append(list, decorator); } + return list && createNodeArray(list, pos); + } + + function tryParseModifier(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean, hasSeenStaticModifier?: boolean): Modifier | undefined { + const pos = getNodePos(); + const kind = token(); - function isHeritageClause(): boolean { - return token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; + if (token() === SyntaxKind.ConstKeyword && permitInvalidConstAsModifier) { + // We need to ensure that any subsequent modifiers appear on the same line + // so that when 'const' is a standalone declaration, we don't issue an error. + if (!tryParse(nextTokenIsOnSameLineAndCanFollowModifier)) { + return undefined; + } + } + else if (stopOnStartOfClassStaticBlock && token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { + return undefined; } + else if (hasSeenStaticModifier && token() === SyntaxKind.StaticKeyword) { + return undefined; + } + else { + if (!parseAnyContextualModifier()) { + return undefined; + } + } + + return finishNode(factory.createToken(kind as Modifier["kind"]), pos); + } - function parseClassMembers(): NodeArray { - return parseList(ParsingContext.ClassMembers, parseClassElement); + /* + * There are situations in which a modifier like 'const' will appear unexpectedly, such as on a class member. + * In those situations, if we are entirely sure that 'const' is not valid on its own (such as when ASI takes effect + * and turns it into a standalone declaration), then it is better to parse it and report an error later. + * + * In such situations, 'permitInvalidConstAsModifier' should be set to true. + */ + function parseModifiers(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean): NodeArray | undefined { + const pos = getNodePos(); + let list, modifier, hasSeenStatic = false; + while (modifier = tryParseModifier(permitInvalidConstAsModifier, stopOnStartOfClassStaticBlock, hasSeenStatic)) { + if (modifier.kind === SyntaxKind.StaticKeyword) hasSeenStatic = true; + list = append(list, modifier); + } + return list && createNodeArray(list, pos); + } + + function parseModifiersForArrowFunction(): NodeArray | undefined { + let modifiers: NodeArray | undefined; + if (token() === SyntaxKind.AsyncKeyword) { + const pos = getNodePos(); + nextToken(); + const modifier = finishNode(factory.createToken(SyntaxKind.AsyncKeyword), pos); + modifiers = createNodeArray([modifier], pos); } + return modifiers; + } - function parseInterfaceDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): InterfaceDeclaration { - parseExpected(SyntaxKind.InterfaceKeyword); - const name = parseIdentifier(); - const typeParameters = parseTypeParameters(); - const heritageClauses = parseHeritageClauses(); - const members = parseObjectTypeMembers(); - const node = factory.createInterfaceDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members); - return withJSDoc(finishNode(node, pos), hasJSDoc); + function parseClassElement(): ClassElement { + const pos = getNodePos(); + if (token() === SyntaxKind.SemicolonToken) { + nextToken(); + return finishNode(factory.createSemicolonClassElement(), pos); } - function parseTypeAliasDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): TypeAliasDeclaration { - parseExpected(SyntaxKind.TypeKeyword); - const name = parseIdentifier(); - const typeParameters = parseTypeParameters(); - parseExpected(SyntaxKind.EqualsToken); - const type = token() === SyntaxKind.IntrinsicKeyword && tryParse(parseKeywordAndNoDot) || parseType(); - parseSemicolon(); - const node = factory.createTypeAliasDeclaration(decorators, modifiers, name, typeParameters, type); - return withJSDoc(finishNode(node, pos), hasJSDoc); + const hasJSDoc = hasPrecedingJSDocComment(); + const decorators = parseDecorators(); + const modifiers = parseModifiers(/*permitInvalidConstAsModifier*/ true, /*stopOnStartOfClassStaticBlock*/ true); + if (token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { + return parseClassStaticBlockDeclaration(pos, hasJSDoc, decorators, modifiers); } - // In an ambient declaration, the grammar only allows integer literals as initializers. - // In a non-ambient declaration, the grammar allows uninitialized members only in a - // ConstantEnumMemberSection, which starts at the beginning of an enum declaration - // or any time an integer literal initializer is encountered. - function parseEnumMember(): EnumMember { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const name = parsePropertyName(); - const initializer = allowInAnd(parseInitializer); - return withJSDoc(finishNode(factory.createEnumMember(name, initializer), pos), hasJSDoc); + if (parseContextualModifier(SyntaxKind.GetKeyword)) { + return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.GetAccessor); } - function parseEnumDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): EnumDeclaration { - parseExpected(SyntaxKind.EnumKeyword); - const name = parseIdentifier(); - let members; - if (parseExpected(SyntaxKind.OpenBraceToken)) { - members = doOutsideOfYieldAndAwaitContext(() => parseDelimitedList(ParsingContext.EnumMembers, parseEnumMember)); - parseExpected(SyntaxKind.CloseBraceToken); - } - else { - members = createMissingList(); - } - const node = factory.createEnumDeclaration(decorators, modifiers, name, members); - return withJSDoc(finishNode(node, pos), hasJSDoc); + if (parseContextualModifier(SyntaxKind.SetKeyword)) { + return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.SetAccessor); } - function parseModuleBlock(): ModuleBlock { - const pos = getNodePos(); - let statements; - if (parseExpected(SyntaxKind.OpenBraceToken)) { - statements = parseList(ParsingContext.BlockStatements, parseStatement); - parseExpected(SyntaxKind.CloseBraceToken); + if (token() === SyntaxKind.ConstructorKeyword || token() === SyntaxKind.StringLiteral) { + const constructorDeclaration = tryParseConstructorDeclaration(pos, hasJSDoc, decorators, modifiers); + if (constructorDeclaration) { + return constructorDeclaration; } - else { - statements = createMissingList(); - } - return finishNode(factory.createModuleBlock(statements), pos); } - function parseModuleOrNamespaceDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, flags: NodeFlags): ModuleDeclaration { - // If we are parsing a dotted namespace name, we want to - // propagate the 'Namespace' flag across the names if set. - const namespaceFlag = flags & NodeFlags.Namespace; - const name = parseIdentifier(); - const body = parseOptional(SyntaxKind.DotToken) - ? parseModuleOrNamespaceDeclaration(getNodePos(), /*hasJSDoc*/ false, /*decorators*/ undefined, /*modifiers*/ undefined, NodeFlags.NestedNamespace | namespaceFlag) as NamespaceDeclaration - : parseModuleBlock(); - const node = factory.createModuleDeclaration(decorators, modifiers, name, body, flags); - return withJSDoc(finishNode(node, pos), hasJSDoc); + if (isIndexSignature()) { + return parseIndexSignatureDeclaration(pos, hasJSDoc, decorators, modifiers); } - function parseAmbientExternalModuleDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ModuleDeclaration { - let flags: NodeFlags = 0; - let name; - if (token() === SyntaxKind.GlobalKeyword) { - // parse 'global' as name of global scope augmentation - name = parseIdentifier(); - flags |= NodeFlags.GlobalAugmentation; - } - else { - name = parseLiteralNode() as StringLiteral; - name.text = internIdentifier(name.text); - } - let body: ModuleBlock | undefined; - if (token() === SyntaxKind.OpenBraceToken) { - body = parseModuleBlock(); + // It is very important that we check this *after* checking indexers because + // the [ token can start an index signature or a computed property name + if (tokenIsIdentifierOrKeyword(token()) || + token() === SyntaxKind.StringLiteral || + token() === SyntaxKind.NumericLiteral || + token() === SyntaxKind.AsteriskToken || + token() === SyntaxKind.OpenBracketToken) { + const isAmbient = some(modifiers, isDeclareModifier); + if (isAmbient) { + for (const m of modifiers!) { + (m as Mutable).flags |= NodeFlags.Ambient; + } + return doInsideOfContext(NodeFlags.Ambient, () => parsePropertyOrMethodDeclaration(pos, hasJSDoc, decorators, modifiers)); } else { - parseSemicolon(); + return parsePropertyOrMethodDeclaration(pos, hasJSDoc, decorators, modifiers); } - const node = factory.createModuleDeclaration(decorators, modifiers, name, body, flags); - return withJSDoc(finishNode(node, pos), hasJSDoc); } - function parseModuleDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ModuleDeclaration { - let flags: NodeFlags = 0; - if (token() === SyntaxKind.GlobalKeyword) { - // global augmentation - return parseAmbientExternalModuleDeclaration(pos, hasJSDoc, decorators, modifiers); - } - else if (parseOptional(SyntaxKind.NamespaceKeyword)) { - flags |= NodeFlags.Namespace; - } - else { - parseExpected(SyntaxKind.ModuleKeyword); - if (token() === SyntaxKind.StringLiteral) { - return parseAmbientExternalModuleDeclaration(pos, hasJSDoc, decorators, modifiers); - } - } - return parseModuleOrNamespaceDeclaration(pos, hasJSDoc, decorators, modifiers, flags); + if (decorators || modifiers) { + // treat this as a property declaration with a missing name. + const name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); + return parsePropertyDeclaration(pos, hasJSDoc, decorators, modifiers, name, /*questionToken*/ undefined); } - function isExternalModuleReference() { - return token() === SyntaxKind.RequireKeyword && - lookAhead(nextTokenIsOpenParen); - } + // 'isClassMemberStart' should have hinted not to attempt parsing. + return Debug.fail("Should not have attempted to parse class member declaration."); + } - function nextTokenIsOpenParen() { - return nextToken() === SyntaxKind.OpenParenToken; - } + function parseClassExpression(): ClassExpression { + return parseClassDeclarationOrExpression(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined, SyntaxKind.ClassExpression) as ClassExpression; + } - function nextTokenIsOpenBrace() { - return nextToken() === SyntaxKind.OpenBraceToken; - } + function parseClassDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ClassDeclaration { + return parseClassDeclarationOrExpression(pos, hasJSDoc, decorators, modifiers, SyntaxKind.ClassDeclaration) as ClassDeclaration; + } - function nextTokenIsSlash() { - return nextToken() === SyntaxKind.SlashToken; + function parseClassDeclarationOrExpression(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, kind: ClassLikeDeclaration["kind"]): ClassLikeDeclaration { + const savedAwaitContext = inAwaitContext(); + parseExpected(SyntaxKind.ClassKeyword); + + // We don't parse the name here in await context, instead we will report a grammar error in the checker. + const name = parseNameOfClassDeclarationOrExpression(); + const typeParameters = parseTypeParameters(); + if (some(modifiers, isExportModifier)) setAwaitContext(/*value*/ true); + const heritageClauses = parseHeritageClauses(); + + let members; + if (parseExpected(SyntaxKind.OpenBraceToken)) { + // ClassTail[Yield,Await] : (Modified) See 14.5 + // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } + members = parseClassMembers(); + parseExpected(SyntaxKind.CloseBraceToken); + } + else { + members = createMissingList(); } + setAwaitContext(savedAwaitContext); + const node = kind === SyntaxKind.ClassDeclaration + ? factory.createClassDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members) + : factory.createClassExpression(decorators, modifiers, name, typeParameters, heritageClauses, members); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function parseNamespaceExportDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): NamespaceExportDeclaration { - parseExpected(SyntaxKind.AsKeyword); - parseExpected(SyntaxKind.NamespaceKeyword); - const name = parseIdentifier(); - parseSemicolon(); - const node = factory.createNamespaceExportDeclaration(name); - // NamespaceExportDeclaration nodes cannot have decorators or modifiers, so we attach them here so we can report them in the grammar checker - node.decorators = decorators; - node.modifiers = modifiers; - return withJSDoc(finishNode(node, pos), hasJSDoc); + function parseNameOfClassDeclarationOrExpression(): Identifier | undefined { + // implements is a future reserved word so + // 'class implements' might mean either + // - class expression with omitted name, 'implements' starts heritage clause + // - class with name 'implements' + // 'isImplementsClause' helps to disambiguate between these two cases + return isBindingIdentifier() && !isImplementsClause() + ? createIdentifier(isBindingIdentifier()) + : undefined; + } + + function isImplementsClause() { + return token() === SyntaxKind.ImplementsKeyword && lookAhead(nextTokenIsIdentifierOrKeyword); + } + + function parseHeritageClauses(): NodeArray | undefined { + // ClassTail[Yield,Await] : (Modified) See 14.5 + // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } + + if (isHeritageClause()) { + return parseList(ParsingContext.HeritageClauses, parseHeritageClause); } - function parseImportDeclarationOrImportEqualsDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ImportEqualsDeclaration | ImportDeclaration { - parseExpected(SyntaxKind.ImportKeyword); + return undefined; + } - const afterImportPos = scanner.getStartPos(); + function parseHeritageClause(): HeritageClause { + const pos = getNodePos(); + const tok = token(); + Debug.assert(tok === SyntaxKind.ExtendsKeyword || tok === SyntaxKind.ImplementsKeyword); // isListElement() should ensure this. + nextToken(); + const types = parseDelimitedList(ParsingContext.HeritageClauseElement, parseExpressionWithTypeArguments); + return finishNode(factory.createHeritageClause(tok, types), pos); + } - // We don't parse the identifier here in await context, instead we will report a grammar error in the checker. - let identifier: Identifier | undefined; - if (isIdentifier()) { - identifier = parseIdentifier(); - } + function parseExpressionWithTypeArguments(): ExpressionWithTypeArguments { + const pos = getNodePos(); + const expression = parseLeftHandSideExpressionOrHigher(); + const typeArguments = tryParseTypeArguments(); + return finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos); + } - let isTypeOnly = false; - if (token() !== SyntaxKind.FromKeyword && - identifier?.escapedText === "type" && - (isIdentifier() || tokenAfterImportDefinitelyProducesImportDeclaration()) - ) { - isTypeOnly = true; - identifier = isIdentifier() ? parseIdentifier() : undefined; - } + function tryParseTypeArguments(): NodeArray | undefined { + return token() === SyntaxKind.LessThanToken ? + parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken) : undefined; + } - if (identifier && !tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration()) { - return parseImportEqualsDeclaration(pos, hasJSDoc, decorators, modifiers, identifier, isTypeOnly); - } + function isHeritageClause(): boolean { + return token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; + } - // ImportDeclaration: - // import ImportClause from ModuleSpecifier ; - // import ModuleSpecifier; - let importClause: ImportClause | undefined; - if (identifier || // import id - token() === SyntaxKind.AsteriskToken || // import * - token() === SyntaxKind.OpenBraceToken // import { - ) { - importClause = parseImportClause(identifier, afterImportPos, isTypeOnly); - parseExpected(SyntaxKind.FromKeyword); - } - const moduleSpecifier = parseModuleSpecifier(); + function parseClassMembers(): NodeArray { + return parseList(ParsingContext.ClassMembers, parseClassElement); + } - let assertClause: AssertClause | undefined; - if (token() === SyntaxKind.AssertKeyword && !scanner.hasPrecedingLineBreak()) { - assertClause = parseAssertClause(); - } + function parseInterfaceDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): InterfaceDeclaration { + parseExpected(SyntaxKind.InterfaceKeyword); + const name = parseIdentifier(); + const typeParameters = parseTypeParameters(); + const heritageClauses = parseHeritageClauses(); + const members = parseObjectTypeMembers(); + const node = factory.createInterfaceDeclaration(decorators, modifiers, name, typeParameters, heritageClauses, members); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - parseSemicolon(); - const node = factory.createImportDeclaration(decorators, modifiers, importClause, moduleSpecifier, assertClause); - return withJSDoc(finishNode(node, pos), hasJSDoc); - } + function parseTypeAliasDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): TypeAliasDeclaration { + parseExpected(SyntaxKind.TypeKeyword); + const name = parseIdentifier(); + const typeParameters = parseTypeParameters(); + parseExpected(SyntaxKind.EqualsToken); + const type = token() === SyntaxKind.IntrinsicKeyword && tryParse(parseKeywordAndNoDot) || parseType(); + parseSemicolon(); + const node = factory.createTypeAliasDeclaration(decorators, modifiers, name, typeParameters, type); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function parseAssertEntry() { - const pos = getNodePos(); - const name = tokenIsIdentifierOrKeyword(token()) ? parseIdentifierName() : parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral; - parseExpected(SyntaxKind.ColonToken); - const value = parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral; - return finishNode(factory.createAssertEntry(name, value), pos); - } + // In an ambient declaration, the grammar only allows integer literals as initializers. + // In a non-ambient declaration, the grammar allows uninitialized members only in a + // ConstantEnumMemberSection, which starts at the beginning of an enum declaration + // or any time an integer literal initializer is encountered. + function parseEnumMember(): EnumMember { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const name = parsePropertyName(); + const initializer = allowInAnd(parseInitializer); + return withJSDoc(finishNode(factory.createEnumMember(name, initializer), pos), hasJSDoc); + } - function parseAssertClause() { - const pos = getNodePos(); - parseExpected(SyntaxKind.AssertKeyword); - const openBracePosition = scanner.getTokenPos(); - if (parseExpected(SyntaxKind.OpenBraceToken)) { - const multiLine = scanner.hasPrecedingLineBreak(); - const elements = parseDelimitedList(ParsingContext.AssertEntries, parseAssertEntry, /*considerSemicolonAsDelimiter*/ true); - if (!parseExpected(SyntaxKind.CloseBraceToken)) { - const lastError = lastOrUndefined(parseDiagnostics); - if (lastError && lastError.code === Diagnostics._0_expected.code) { - addRelatedInfo( - lastError, - createDetachedDiagnostic(fileName, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_to_match_the_token_here) - ); - } - } - return finishNode(factory.createAssertClause(elements, multiLine), pos); - } - else { - const elements = createNodeArray([], getNodePos(), /*end*/ undefined, /*hasTrailingComma*/ false); - return finishNode(factory.createAssertClause(elements, /*multiLine*/ false), pos); - } + function parseEnumDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): EnumDeclaration { + parseExpected(SyntaxKind.EnumKeyword); + const name = parseIdentifier(); + let members; + if (parseExpected(SyntaxKind.OpenBraceToken)) { + members = doOutsideOfYieldAndAwaitContext(() => parseDelimitedList(ParsingContext.EnumMembers, parseEnumMember)); + parseExpected(SyntaxKind.CloseBraceToken); } - - function tokenAfterImportDefinitelyProducesImportDeclaration() { - return token() === SyntaxKind.AsteriskToken || token() === SyntaxKind.OpenBraceToken; + else { + members = createMissingList(); } + const node = factory.createEnumDeclaration(decorators, modifiers, name, members); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration() { - // In `import id ___`, the current token decides whether to produce - // an ImportDeclaration or ImportEqualsDeclaration. - return token() === SyntaxKind.CommaToken || token() === SyntaxKind.FromKeyword; + function parseModuleBlock(): ModuleBlock { + const pos = getNodePos(); + let statements; + if (parseExpected(SyntaxKind.OpenBraceToken)) { + statements = parseList(ParsingContext.BlockStatements, parseStatement); + parseExpected(SyntaxKind.CloseBraceToken); + } + else { + statements = createMissingList(); } + return finishNode(factory.createModuleBlock(statements), pos); + } + + function parseModuleOrNamespaceDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, flags: NodeFlags): ModuleDeclaration { + // If we are parsing a dotted namespace name, we want to + // propagate the 'Namespace' flag across the names if set. + const namespaceFlag = flags & NodeFlags.Namespace; + const name = parseIdentifier(); + const body = parseOptional(SyntaxKind.DotToken) + ? parseModuleOrNamespaceDeclaration(getNodePos(), /*hasJSDoc*/ false, /*decorators*/ undefined, /*modifiers*/ undefined, NodeFlags.NestedNamespace | namespaceFlag) as NamespaceDeclaration + : parseModuleBlock(); + const node = factory.createModuleDeclaration(decorators, modifiers, name, body, flags); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function parseImportEqualsDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, identifier: Identifier, isTypeOnly: boolean): ImportEqualsDeclaration { - parseExpected(SyntaxKind.EqualsToken); - const moduleReference = parseModuleReference(); + function parseAmbientExternalModuleDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ModuleDeclaration { + let flags: NodeFlags = 0; + let name; + if (token() === SyntaxKind.GlobalKeyword) { + // parse 'global' as name of global scope augmentation + name = parseIdentifier(); + flags |= NodeFlags.GlobalAugmentation; + } + else { + name = parseLiteralNode() as StringLiteral; + name.text = internIdentifier(name.text); + } + let body: ModuleBlock | undefined; + if (token() === SyntaxKind.OpenBraceToken) { + body = parseModuleBlock(); + } + else { parseSemicolon(); - const node = factory.createImportEqualsDeclaration(decorators, modifiers, isTypeOnly, identifier, moduleReference); - const finished = withJSDoc(finishNode(node, pos), hasJSDoc); - return finished; } + const node = factory.createModuleDeclaration(decorators, modifiers, name, body, flags); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function parseImportClause(identifier: Identifier | undefined, pos: number, isTypeOnly: boolean) { - // ImportClause: - // ImportedDefaultBinding - // NameSpaceImport - // NamedImports - // ImportedDefaultBinding, NameSpaceImport - // ImportedDefaultBinding, NamedImports - - // If there was no default import or if there is comma token after default import - // parse namespace or named imports - let namedBindings: NamespaceImport | NamedImports | undefined; - if (!identifier || - parseOptional(SyntaxKind.CommaToken)) { - namedBindings = token() === SyntaxKind.AsteriskToken ? parseNamespaceImport() : parseNamedImportsOrExports(SyntaxKind.NamedImports); + function parseModuleDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ModuleDeclaration { + let flags: NodeFlags = 0; + if (token() === SyntaxKind.GlobalKeyword) { + // global augmentation + return parseAmbientExternalModuleDeclaration(pos, hasJSDoc, decorators, modifiers); + } + else if (parseOptional(SyntaxKind.NamespaceKeyword)) { + flags |= NodeFlags.Namespace; + } + else { + parseExpected(SyntaxKind.ModuleKeyword); + if (token() === SyntaxKind.StringLiteral) { + return parseAmbientExternalModuleDeclaration(pos, hasJSDoc, decorators, modifiers); } + } + return parseModuleOrNamespaceDeclaration(pos, hasJSDoc, decorators, modifiers, flags); + } + + function isExternalModuleReference() { + return token() === SyntaxKind.RequireKeyword && + lookAhead(nextTokenIsOpenParen); + } + + function nextTokenIsOpenParen() { + return nextToken() === SyntaxKind.OpenParenToken; + } + + function nextTokenIsOpenBrace() { + return nextToken() === SyntaxKind.OpenBraceToken; + } + + function nextTokenIsSlash() { + return nextToken() === SyntaxKind.SlashToken; + } + + function parseNamespaceExportDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): NamespaceExportDeclaration { + parseExpected(SyntaxKind.AsKeyword); + parseExpected(SyntaxKind.NamespaceKeyword); + const name = parseIdentifier(); + parseSemicolon(); + const node = factory.createNamespaceExportDeclaration(name); + // NamespaceExportDeclaration nodes cannot have decorators or modifiers, so we attach them here so we can report them in the grammar checker + node.decorators = decorators; + node.modifiers = modifiers; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } + + function parseImportDeclarationOrImportEqualsDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ImportEqualsDeclaration | ImportDeclaration { + parseExpected(SyntaxKind.ImportKeyword); - return finishNode(factory.createImportClause(isTypeOnly, identifier, namedBindings), pos); + const afterImportPos = scanner.getStartPos(); + + // We don't parse the identifier here in await context, instead we will report a grammar error in the checker. + let identifier: Identifier | undefined; + if (isIdentifier()) { + identifier = parseIdentifier(); } - function parseModuleReference() { - return isExternalModuleReference() - ? parseExternalModuleReference() - : parseEntityName(/*allowReservedWords*/ false); + let isTypeOnly = false; + if (token() !== SyntaxKind.FromKeyword && + identifier?.escapedText === "type" && + (isIdentifier() || tokenAfterImportDefinitelyProducesImportDeclaration()) + ) { + isTypeOnly = true; + identifier = isIdentifier() ? parseIdentifier() : undefined; } - function parseExternalModuleReference() { - const pos = getNodePos(); - parseExpected(SyntaxKind.RequireKeyword); - parseExpected(SyntaxKind.OpenParenToken); - const expression = parseModuleSpecifier(); - parseExpected(SyntaxKind.CloseParenToken); - return finishNode(factory.createExternalModuleReference(expression), pos); + if (identifier && !tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration()) { + return parseImportEqualsDeclaration(pos, hasJSDoc, decorators, modifiers, identifier, isTypeOnly); } - function parseModuleSpecifier(): Expression { - if (token() === SyntaxKind.StringLiteral) { - const result = parseLiteralNode(); - result.text = internIdentifier(result.text); - return result; - } - else { - // We allow arbitrary expressions here, even though the grammar only allows string - // literals. We check to ensure that it is only a string literal later in the grammar - // check pass. - return parseExpression(); - } + // ImportDeclaration: + // import ImportClause from ModuleSpecifier ; + // import ModuleSpecifier; + let importClause: ImportClause | undefined; + if (identifier || // import id + token() === SyntaxKind.AsteriskToken || // import * + token() === SyntaxKind.OpenBraceToken // import { + ) { + importClause = parseImportClause(identifier, afterImportPos, isTypeOnly); + parseExpected(SyntaxKind.FromKeyword); } + const moduleSpecifier = parseModuleSpecifier(); - function parseNamespaceImport(): NamespaceImport { - // NameSpaceImport: - // * as ImportedBinding - const pos = getNodePos(); - parseExpected(SyntaxKind.AsteriskToken); - parseExpected(SyntaxKind.AsKeyword); - const name = parseIdentifier(); - return finishNode(factory.createNamespaceImport(name), pos); + let assertClause: AssertClause | undefined; + if (token() === SyntaxKind.AssertKeyword && !scanner.hasPrecedingLineBreak()) { + assertClause = parseAssertClause(); } - function parseNamedImportsOrExports(kind: SyntaxKind.NamedImports): NamedImports; - function parseNamedImportsOrExports(kind: SyntaxKind.NamedExports): NamedExports; - function parseNamedImportsOrExports(kind: SyntaxKind): NamedImportsOrExports { - const pos = getNodePos(); + parseSemicolon(); + const node = factory.createImportDeclaration(decorators, modifiers, importClause, moduleSpecifier, assertClause); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - // NamedImports: - // { } - // { ImportsList } - // { ImportsList, } + function parseAssertEntry() { + const pos = getNodePos(); + const name = tokenIsIdentifierOrKeyword(token()) ? parseIdentifierName() : parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral; + parseExpected(SyntaxKind.ColonToken); + const value = parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral; + return finishNode(factory.createAssertEntry(name, value), pos); + } - // ImportsList: - // ImportSpecifier - // ImportsList, ImportSpecifier - const node = kind === SyntaxKind.NamedImports - ? factory.createNamedImports(parseBracketedList(ParsingContext.ImportOrExportSpecifiers, parseImportSpecifier, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken)) - : factory.createNamedExports(parseBracketedList(ParsingContext.ImportOrExportSpecifiers, parseExportSpecifier, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken)); - return finishNode(node, pos); + function parseAssertClause() { + const pos = getNodePos(); + parseExpected(SyntaxKind.AssertKeyword); + const openBracePosition = scanner.getTokenPos(); + if (parseExpected(SyntaxKind.OpenBraceToken)) { + const multiLine = scanner.hasPrecedingLineBreak(); + const elements = parseDelimitedList(ParsingContext.AssertEntries, parseAssertEntry, /*considerSemicolonAsDelimiter*/ true); + if (!parseExpected(SyntaxKind.CloseBraceToken)) { + const lastError = lastOrUndefined(parseDiagnostics); + if (lastError && lastError.code === Diagnostics._0_expected.code) { + addRelatedInfo( + lastError, + createDetachedDiagnostic(fileName, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_to_match_the_token_here) + ); + } + } + return finishNode(factory.createAssertClause(elements, multiLine), pos); + } + else { + const elements = createNodeArray([], getNodePos(), /*end*/ undefined, /*hasTrailingComma*/ false); + return finishNode(factory.createAssertClause(elements, /*multiLine*/ false), pos); } + } + + function tokenAfterImportDefinitelyProducesImportDeclaration() { + return token() === SyntaxKind.AsteriskToken || token() === SyntaxKind.OpenBraceToken; + } - function parseExportSpecifier() { - return parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier) as ExportSpecifier; + function tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration() { + // In `import id ___`, the current token decides whether to produce + // an ImportDeclaration or ImportEqualsDeclaration. + return token() === SyntaxKind.CommaToken || token() === SyntaxKind.FromKeyword; + } + + function parseImportEqualsDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, identifier: Identifier, isTypeOnly: boolean): ImportEqualsDeclaration { + parseExpected(SyntaxKind.EqualsToken); + const moduleReference = parseModuleReference(); + parseSemicolon(); + const node = factory.createImportEqualsDeclaration(decorators, modifiers, isTypeOnly, identifier, moduleReference); + const finished = withJSDoc(finishNode(node, pos), hasJSDoc); + return finished; + } + + function parseImportClause(identifier: Identifier | undefined, pos: number, isTypeOnly: boolean) { + // ImportClause: + // ImportedDefaultBinding + // NameSpaceImport + // NamedImports + // ImportedDefaultBinding, NameSpaceImport + // ImportedDefaultBinding, NamedImports + + // If there was no default import or if there is comma token after default import + // parse namespace or named imports + let namedBindings: NamespaceImport | NamedImports | undefined; + if (!identifier || + parseOptional(SyntaxKind.CommaToken)) { + namedBindings = token() === SyntaxKind.AsteriskToken ? parseNamespaceImport() : parseNamedImportsOrExports(SyntaxKind.NamedImports); } - function parseImportSpecifier() { - return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier) as ImportSpecifier; + return finishNode(factory.createImportClause(isTypeOnly, identifier, namedBindings), pos); + } + + function parseModuleReference() { + return isExternalModuleReference() + ? parseExternalModuleReference() + : parseEntityName(/*allowReservedWords*/ false); + } + + function parseExternalModuleReference() { + const pos = getNodePos(); + parseExpected(SyntaxKind.RequireKeyword); + parseExpected(SyntaxKind.OpenParenToken); + const expression = parseModuleSpecifier(); + parseExpected(SyntaxKind.CloseParenToken); + return finishNode(factory.createExternalModuleReference(expression), pos); + } + + function parseModuleSpecifier(): Expression { + if (token() === SyntaxKind.StringLiteral) { + const result = parseLiteralNode(); + result.text = internIdentifier(result.text); + return result; } + else { + // We allow arbitrary expressions here, even though the grammar only allows string + // literals. We check to ensure that it is only a string literal later in the grammar + // check pass. + return parseExpression(); + } + } - function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier { - const pos = getNodePos(); - // ImportSpecifier: - // BindingIdentifier - // IdentifierName as BindingIdentifier - // ExportSpecifier: - // IdentifierName - // IdentifierName as IdentifierName - let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); - let checkIdentifierStart = scanner.getTokenPos(); - let checkIdentifierEnd = scanner.getTextPos(); - let isTypeOnly = false; - let propertyName: Identifier | undefined; - let canParseAsKeyword = true; - let name = parseIdentifierName(); - if (name.escapedText === "type") { - // If the first token of an import specifier is 'type', there are a lot of possibilities, - // especially if we see 'as' afterwards: - // - // import { type } from "mod"; - isTypeOnly: false, name: type - // import { type as } from "mod"; - isTypeOnly: true, name: as - // import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type - // import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as - if (token() === SyntaxKind.AsKeyword) { - // { type as ...? } - const firstAs = parseIdentifierName(); - if (token() === SyntaxKind.AsKeyword) { - // { type as as ...? } - const secondAs = parseIdentifierName(); - if (tokenIsIdentifierOrKeyword(token())) { - // { type as as something } - isTypeOnly = true; - propertyName = firstAs; - name = parseNameWithKeywordCheck(); - canParseAsKeyword = false; - } - else { - // { type as as } - propertyName = name; - name = secondAs; - canParseAsKeyword = false; - } - } - else if (tokenIsIdentifierOrKeyword(token())) { - // { type as something } - propertyName = name; - canParseAsKeyword = false; + function parseNamespaceImport(): NamespaceImport { + // NameSpaceImport: + // * as ImportedBinding + const pos = getNodePos(); + parseExpected(SyntaxKind.AsteriskToken); + parseExpected(SyntaxKind.AsKeyword); + const name = parseIdentifier(); + return finishNode(factory.createNamespaceImport(name), pos); + } + + function parseNamedImportsOrExports(kind: SyntaxKind.NamedImports): NamedImports; + function parseNamedImportsOrExports(kind: SyntaxKind.NamedExports): NamedExports; + function parseNamedImportsOrExports(kind: SyntaxKind): NamedImportsOrExports { + const pos = getNodePos(); + + // NamedImports: + // { } + // { ImportsList } + // { ImportsList, } + + // ImportsList: + // ImportSpecifier + // ImportsList, ImportSpecifier + const node = kind === SyntaxKind.NamedImports + ? factory.createNamedImports(parseBracketedList(ParsingContext.ImportOrExportSpecifiers, parseImportSpecifier, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken)) + : factory.createNamedExports(parseBracketedList(ParsingContext.ImportOrExportSpecifiers, parseExportSpecifier, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken)); + return finishNode(node, pos); + } + + function parseExportSpecifier() { + return parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier) as ExportSpecifier; + } + + function parseImportSpecifier() { + return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier) as ImportSpecifier; + } + + function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier { + const pos = getNodePos(); + // ImportSpecifier: + // BindingIdentifier + // IdentifierName as BindingIdentifier + // ExportSpecifier: + // IdentifierName + // IdentifierName as IdentifierName + let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); + let checkIdentifierStart = scanner.getTokenPos(); + let checkIdentifierEnd = scanner.getTextPos(); + let isTypeOnly = false; + let propertyName: Identifier | undefined; + let canParseAsKeyword = true; + let name = parseIdentifierName(); + if (name.escapedText === "type") { + // If the first token of an import specifier is 'type', there are a lot of possibilities, + // especially if we see 'as' afterwards: + // + // import { type } from "mod"; - isTypeOnly: false, name: type + // import { type as } from "mod"; - isTypeOnly: true, name: as + // import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type + // import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as + if (token() === SyntaxKind.AsKeyword) { + // { type as ...? } + const firstAs = parseIdentifierName(); + if (token() === SyntaxKind.AsKeyword) { + // { type as as ...? } + const secondAs = parseIdentifierName(); + if (tokenIsIdentifierOrKeyword(token())) { + // { type as as something } + isTypeOnly = true; + propertyName = firstAs; name = parseNameWithKeywordCheck(); + canParseAsKeyword = false; } else { - // { type as } - isTypeOnly = true; - name = firstAs; + // { type as as } + propertyName = name; + name = secondAs; + canParseAsKeyword = false; } } else if (tokenIsIdentifierOrKeyword(token())) { - // { type something ...? } - isTypeOnly = true; + // { type as something } + propertyName = name; + canParseAsKeyword = false; name = parseNameWithKeywordCheck(); } + else { + // { type as } + isTypeOnly = true; + name = firstAs; + } } - - if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) { - propertyName = name; - parseExpected(SyntaxKind.AsKeyword); + else if (tokenIsIdentifierOrKeyword(token())) { + // { type something ...? } + isTypeOnly = true; name = parseNameWithKeywordCheck(); } - if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) { - parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected); - } - const node = kind === SyntaxKind.ImportSpecifier - ? factory.createImportSpecifier(isTypeOnly, propertyName, name) - : factory.createExportSpecifier(isTypeOnly, propertyName, name); - return finishNode(node, pos); + } - function parseNameWithKeywordCheck() { - checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); - checkIdentifierStart = scanner.getTokenPos(); - checkIdentifierEnd = scanner.getTextPos(); - return parseIdentifierName(); - } + if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) { + propertyName = name; + parseExpected(SyntaxKind.AsKeyword); + name = parseNameWithKeywordCheck(); + } + if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) { + parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected); } + const node = kind === SyntaxKind.ImportSpecifier + ? factory.createImportSpecifier(isTypeOnly, propertyName, name) + : factory.createExportSpecifier(isTypeOnly, propertyName, name); + return finishNode(node, pos); - function parseNamespaceExport(pos: number): NamespaceExport { - return finishNode(factory.createNamespaceExport(parseIdentifierName()), pos); + function parseNameWithKeywordCheck() { + checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); + checkIdentifierStart = scanner.getTokenPos(); + checkIdentifierEnd = scanner.getTextPos(); + return parseIdentifierName(); } + } - function parseExportDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ExportDeclaration { - const savedAwaitContext = inAwaitContext(); - setAwaitContext(/*value*/ true); - let exportClause: NamedExportBindings | undefined; - let moduleSpecifier: Expression | undefined; - let assertClause: AssertClause | undefined; - const isTypeOnly = parseOptional(SyntaxKind.TypeKeyword); - const namespaceExportPos = getNodePos(); - if (parseOptional(SyntaxKind.AsteriskToken)) { - if (parseOptional(SyntaxKind.AsKeyword)) { - exportClause = parseNamespaceExport(namespaceExportPos); - } + function parseNamespaceExport(pos: number): NamespaceExport { + return finishNode(factory.createNamespaceExport(parseIdentifierName()), pos); + } + + function parseExportDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ExportDeclaration { + const savedAwaitContext = inAwaitContext(); + setAwaitContext(/*value*/ true); + let exportClause: NamedExportBindings | undefined; + let moduleSpecifier: Expression | undefined; + let assertClause: AssertClause | undefined; + const isTypeOnly = parseOptional(SyntaxKind.TypeKeyword); + const namespaceExportPos = getNodePos(); + if (parseOptional(SyntaxKind.AsteriskToken)) { + if (parseOptional(SyntaxKind.AsKeyword)) { + exportClause = parseNamespaceExport(namespaceExportPos); + } + parseExpected(SyntaxKind.FromKeyword); + moduleSpecifier = parseModuleSpecifier(); + } + else { + exportClause = parseNamedImportsOrExports(SyntaxKind.NamedExports); + // It is not uncommon to accidentally omit the 'from' keyword. Additionally, in editing scenarios, + // the 'from' keyword can be parsed as a named export when the export clause is unterminated (i.e. `export { from "moduleName";`) + // If we don't have a 'from' keyword, see if we have a string literal such that ASI won't take effect. + if (token() === SyntaxKind.FromKeyword || (token() === SyntaxKind.StringLiteral && !scanner.hasPrecedingLineBreak())) { parseExpected(SyntaxKind.FromKeyword); moduleSpecifier = parseModuleSpecifier(); } - else { - exportClause = parseNamedImportsOrExports(SyntaxKind.NamedExports); - // It is not uncommon to accidentally omit the 'from' keyword. Additionally, in editing scenarios, - // the 'from' keyword can be parsed as a named export when the export clause is unterminated (i.e. `export { from "moduleName";`) - // If we don't have a 'from' keyword, see if we have a string literal such that ASI won't take effect. - if (token() === SyntaxKind.FromKeyword || (token() === SyntaxKind.StringLiteral && !scanner.hasPrecedingLineBreak())) { - parseExpected(SyntaxKind.FromKeyword); - moduleSpecifier = parseModuleSpecifier(); - } - } - if (moduleSpecifier && token() === SyntaxKind.AssertKeyword && !scanner.hasPrecedingLineBreak()) { - assertClause = parseAssertClause(); - } - parseSemicolon(); - setAwaitContext(savedAwaitContext); - const node = factory.createExportDeclaration(decorators, modifiers, isTypeOnly, exportClause, moduleSpecifier, assertClause); - return withJSDoc(finishNode(node, pos), hasJSDoc); } + if (moduleSpecifier && token() === SyntaxKind.AssertKeyword && !scanner.hasPrecedingLineBreak()) { + assertClause = parseAssertClause(); + } + parseSemicolon(); + setAwaitContext(savedAwaitContext); + const node = factory.createExportDeclaration(decorators, modifiers, isTypeOnly, exportClause, moduleSpecifier, assertClause); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function parseExportAssignment(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ExportAssignment { - const savedAwaitContext = inAwaitContext(); - setAwaitContext(/*value*/ true); - let isExportEquals: boolean | undefined; - if (parseOptional(SyntaxKind.EqualsToken)) { - isExportEquals = true; - } - else { - parseExpected(SyntaxKind.DefaultKeyword); - } - const expression = parseAssignmentExpressionOrHigher(); - parseSemicolon(); - setAwaitContext(savedAwaitContext); - const node = factory.createExportAssignment(decorators, modifiers, isExportEquals, expression); - return withJSDoc(finishNode(node, pos), hasJSDoc); + function parseExportAssignment(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ExportAssignment { + const savedAwaitContext = inAwaitContext(); + setAwaitContext(/*value*/ true); + let isExportEquals: boolean | undefined; + if (parseOptional(SyntaxKind.EqualsToken)) { + isExportEquals = true; + } + else { + parseExpected(SyntaxKind.DefaultKeyword); } + const expression = parseAssignmentExpressionOrHigher(); + parseSemicolon(); + setAwaitContext(savedAwaitContext); + const node = factory.createExportAssignment(decorators, modifiers, isExportEquals, expression); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function setExternalModuleIndicator(sourceFile: SourceFile) { - // Try to use the first top-level import/export when available, then - // fall back to looking for an 'import.meta' somewhere in the tree if necessary. - sourceFile.externalModuleIndicator = - forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) || - getImportMetaIfNecessary(sourceFile); - } - - function isAnExternalModuleIndicatorNode(node: Node) { - return hasModifierOfKind(node, SyntaxKind.ExportKeyword) - || isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference) - || isImportDeclaration(node) - || isExportAssignment(node) - || isExportDeclaration(node) ? node : undefined; - } - - function getImportMetaIfNecessary(sourceFile: SourceFile) { - return sourceFile.flags & NodeFlags.PossiblyContainsImportMeta ? - walkTreeForExternalModuleIndicators(sourceFile) : - undefined; - } - - function walkTreeForExternalModuleIndicators(node: Node): Node | undefined { - return isImportMeta(node) ? node : forEachChild(node, walkTreeForExternalModuleIndicators); - } - - /** Do not use hasModifier inside the parser; it relies on parent pointers. Use this instead. */ - function hasModifierOfKind(node: Node, kind: SyntaxKind) { - return some(node.modifiers, m => m.kind === kind); - } - - function isImportMeta(node: Node): boolean { - return isMetaProperty(node) && node.keywordToken === SyntaxKind.ImportKeyword && node.name.escapedText === "meta"; - } - - const enum ParsingContext { - SourceElements, // Elements in source file - BlockStatements, // Statements in block - SwitchClauses, // Clauses in switch statement - SwitchClauseStatements, // Statements in switch clause - TypeMembers, // Members in interface or type literal - ClassMembers, // Members in class declaration - EnumMembers, // Members in enum declaration - HeritageClauseElement, // Elements in a heritage clause - VariableDeclarations, // Variable declarations in variable statement - ObjectBindingElements, // Binding elements in object binding list - ArrayBindingElements, // Binding elements in array binding list - ArgumentExpressions, // Expressions in argument list - ObjectLiteralMembers, // Members in object literal - JsxAttributes, // Attributes in jsx element - JsxChildren, // Things between opening and closing JSX tags - ArrayLiteralMembers, // Members in array literal - Parameters, // Parameters in parameter list - JSDocParameters, // JSDoc parameters in parameter list of JSDoc function type - RestProperties, // Property names in a rest type list - TypeParameters, // Type parameters in type parameter list - TypeArguments, // Type arguments in type argument list - TupleElementTypes, // Element types in tuple element type list - HeritageClauses, // Heritage clauses for a class or interface declaration. - ImportOrExportSpecifiers, // Named import clause's import specifier list, - AssertEntries, // Import entries list. - Count // Number of parsing contexts - } - - const enum Tristate { - False, - True, - Unknown - } - - export namespace JSDocParser { - export function parseJSDocTypeExpressionForTests(content: string, start: number | undefined, length: number | undefined): { jsDocTypeExpression: JSDocTypeExpression, diagnostics: Diagnostic[] } | undefined { - initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); - scanner.setText(content, start, length); - currentToken = scanner.scan(); - const jsDocTypeExpression = parseJSDocTypeExpression(); - - const sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS, /*isDeclarationFile*/ false, [], factory.createToken(SyntaxKind.EndOfFileToken), NodeFlags.None); - const diagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); - if (jsDocDiagnostics) { - sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); - } + function setExternalModuleIndicator(sourceFile: SourceFile) { + // Try to use the first top-level import/export when available, then + // fall back to looking for an 'import.meta' somewhere in the tree if necessary. + sourceFile.externalModuleIndicator = + forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) || + getImportMetaIfNecessary(sourceFile); + } - clearState(); + function isAnExternalModuleIndicatorNode(node: Node) { + return hasModifierOfKind(node, SyntaxKind.ExportKeyword) + || isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference) + || isImportDeclaration(node) + || isExportAssignment(node) + || isExportDeclaration(node) ? node : undefined; + } - return jsDocTypeExpression ? { jsDocTypeExpression, diagnostics } : undefined; - } + function getImportMetaIfNecessary(sourceFile: SourceFile) { + return sourceFile.flags & NodeFlags.PossiblyContainsImportMeta ? + walkTreeForExternalModuleIndicators(sourceFile) : + undefined; + } - // Parses out a JSDoc type expression. - export function parseJSDocTypeExpression(mayOmitBraces?: boolean): JSDocTypeExpression { - const pos = getNodePos(); - const hasBrace = (mayOmitBraces ? parseOptional : parseExpected)(SyntaxKind.OpenBraceToken); - const type = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); - if (!mayOmitBraces || hasBrace) { - parseExpectedJSDoc(SyntaxKind.CloseBraceToken); - } + function walkTreeForExternalModuleIndicators(node: Node): Node | undefined { + return isImportMeta(node) ? node : forEachChild(node, walkTreeForExternalModuleIndicators); + } - const result = factory.createJSDocTypeExpression(type); - fixupParentReferences(result); - return finishNode(result, pos); - } + /** Do not use hasModifier inside the parser; it relies on parent pointers. Use this instead. */ + function hasModifierOfKind(node: Node, kind: SyntaxKind) { + return some(node.modifiers, m => m.kind === kind); + } - export function parseJSDocNameReference(): JSDocNameReference { - const pos = getNodePos(); - const hasBrace = parseOptional(SyntaxKind.OpenBraceToken); - const p2 = getNodePos(); - let entityName: EntityName | JSDocMemberName = parseEntityName(/* allowReservedWords*/ false); - while (token() === SyntaxKind.PrivateIdentifier) { - reScanHashToken(); // rescan #id as # id - nextTokenJSDoc(); // then skip the # - entityName = finishNode(factory.createJSDocMemberName(entityName, parseIdentifier()), p2); - } - if (hasBrace) { - parseExpectedJSDoc(SyntaxKind.CloseBraceToken); - } + function isImportMeta(node: Node): boolean { + return isMetaProperty(node) && node.keywordToken === SyntaxKind.ImportKeyword && node.name.escapedText === "meta"; + } - const result = factory.createJSDocNameReference(entityName); - fixupParentReferences(result); - return finishNode(result, pos); - } + const enum ParsingContext { + SourceElements, // Elements in source file + BlockStatements, // Statements in block + SwitchClauses, // Clauses in switch statement + SwitchClauseStatements, // Statements in switch clause + TypeMembers, // Members in interface or type literal + ClassMembers, // Members in class declaration + EnumMembers, // Members in enum declaration + HeritageClauseElement, // Elements in a heritage clause + VariableDeclarations, // Variable declarations in variable statement + ObjectBindingElements, // Binding elements in object binding list + ArrayBindingElements, // Binding elements in array binding list + ArgumentExpressions, // Expressions in argument list + ObjectLiteralMembers, // Members in object literal + JsxAttributes, // Attributes in jsx element + JsxChildren, // Things between opening and closing JSX tags + ArrayLiteralMembers, // Members in array literal + Parameters, // Parameters in parameter list + JSDocParameters, // JSDoc parameters in parameter list of JSDoc function type + RestProperties, // Property names in a rest type list + TypeParameters, // Type parameters in type parameter list + TypeArguments, // Type arguments in type argument list + TupleElementTypes, // Element types in tuple element type list + HeritageClauses, // Heritage clauses for a class or interface declaration. + ImportOrExportSpecifiers, // Named import clause's import specifier list, + AssertEntries, // Import entries list. + Count // Number of parsing contexts + } - export function parseIsolatedJSDocComment(content: string, start: number | undefined, length: number | undefined): { jsDoc: JSDoc, diagnostics: Diagnostic[] } | undefined { - initializeState("", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); - const jsDoc = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length)); + const enum Tristate { + False, + True, + Unknown + } - const sourceFile = { languageVariant: LanguageVariant.Standard, text: content } as SourceFile; - const diagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); - clearState(); + export namespace JSDocParser { + export function parseJSDocTypeExpressionForTests(content: string, start: number | undefined, length: number | undefined): { jsDocTypeExpression: JSDocTypeExpression, diagnostics: Diagnostic[] } | undefined { + initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); + scanner.setText(content, start, length); + currentToken = scanner.scan(); + const jsDocTypeExpression = parseJSDocTypeExpression(); - return jsDoc ? { jsDoc, diagnostics } : undefined; + const sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS, /*isDeclarationFile*/ false, [], factory.createToken(SyntaxKind.EndOfFileToken), NodeFlags.None); + const diagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); + if (jsDocDiagnostics) { + sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); } - export function parseJSDocComment(parent: HasJSDoc, start: number, length: number): JSDoc | undefined { - const saveToken = currentToken; - const saveParseDiagnosticsLength = parseDiagnostics.length; - const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; + clearState(); - const comment = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length)); - setParent(comment, parent); + return jsDocTypeExpression ? { jsDocTypeExpression, diagnostics } : undefined; + } - if (contextFlags & NodeFlags.JavaScriptFile) { - if (!jsDocDiagnostics) { - jsDocDiagnostics = []; - } - jsDocDiagnostics.push(...parseDiagnostics); - } - currentToken = saveToken; - parseDiagnostics.length = saveParseDiagnosticsLength; - parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; - return comment; + // Parses out a JSDoc type expression. + export function parseJSDocTypeExpression(mayOmitBraces?: boolean): JSDocTypeExpression { + const pos = getNodePos(); + const hasBrace = (mayOmitBraces ? parseOptional : parseExpected)(SyntaxKind.OpenBraceToken); + const type = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); + if (!mayOmitBraces || hasBrace) { + parseExpectedJSDoc(SyntaxKind.CloseBraceToken); } - const enum JSDocState { - BeginningOfLine, - SawAsterisk, - SavingComments, - SavingBackticks, // NOTE: Only used when parsing tag comments - } + const result = factory.createJSDocTypeExpression(type); + fixupParentReferences(result); + return finishNode(result, pos); + } - const enum PropertyLikeParse { - Property = 1 << 0, - Parameter = 1 << 1, - CallbackParameter = 1 << 2, + export function parseJSDocNameReference(): JSDocNameReference { + const pos = getNodePos(); + const hasBrace = parseOptional(SyntaxKind.OpenBraceToken); + const p2 = getNodePos(); + let entityName: EntityName | JSDocMemberName = parseEntityName(/* allowReservedWords*/ false); + while (token() === SyntaxKind.PrivateIdentifier) { + reScanHashToken(); // rescan #id as # id + nextTokenJSDoc(); // then skip the # + entityName = finishNode(factory.createJSDocMemberName(entityName, parseIdentifier()), p2); + } + if (hasBrace) { + parseExpectedJSDoc(SyntaxKind.CloseBraceToken); } - function parseJSDocCommentWorker(start = 0, length: number | undefined): JSDoc | undefined { - const content = sourceText; - const end = length === undefined ? content.length : start + length; - length = end - start; + const result = factory.createJSDocNameReference(entityName); + fixupParentReferences(result); + return finishNode(result, pos); + } - Debug.assert(start >= 0); - Debug.assert(start <= end); - Debug.assert(end <= content.length); + export function parseIsolatedJSDocComment(content: string, start: number | undefined, length: number | undefined): { jsDoc: JSDoc, diagnostics: Diagnostic[] } | undefined { + initializeState("", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); + const jsDoc = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length)); - // Check for /** (JSDoc opening part) - if (!isJSDocLikeText(content, start)) { - return undefined; + const sourceFile = { languageVariant: LanguageVariant.Standard, text: content } as SourceFile; + const diagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); + clearState(); + + return jsDoc ? { jsDoc, diagnostics } : undefined; + } + + export function parseJSDocComment(parent: HasJSDoc, start: number, length: number): JSDoc | undefined { + const saveToken = currentToken; + const saveParseDiagnosticsLength = parseDiagnostics.length; + const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; + + const comment = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length)); + setParent(comment, parent); + + if (contextFlags & NodeFlags.JavaScriptFile) { + if (!jsDocDiagnostics) { + jsDocDiagnostics = []; } + jsDocDiagnostics.push(...parseDiagnostics); + } + currentToken = saveToken; + parseDiagnostics.length = saveParseDiagnosticsLength; + parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; + return comment; + } - let tags: JSDocTag[]; - let tagsPos: number; - let tagsEnd: number; - let linkEnd: number; - let commentsPos: number | undefined; - let comments: string[] = []; - const parts: JSDocComment[] = []; + const enum JSDocState { + BeginningOfLine, + SawAsterisk, + SavingComments, + SavingBackticks, // NOTE: Only used when parsing tag comments + } - // + 3 for leading /**, - 5 in total for /** */ - return scanner.scanRange(start + 3, length - 5, () => { - // Initially we can parse out a tag. We also have seen a starting asterisk. - // This is so that /** * @type */ doesn't parse. - let state = JSDocState.SawAsterisk; - let margin: number | undefined; - // + 4 for leading '/** ' - // + 1 because the last index of \n is always one index before the first character in the line and coincidentally, if there is no \n before start, it is -1, which is also one index before the first character - let indent = start - (content.lastIndexOf("\n", start) + 1) + 4; - function pushComment(text: string) { - if (!margin) { - margin = indent; - } - comments.push(text); - indent += text.length; - } + const enum PropertyLikeParse { + Property = 1 << 0, + Parameter = 1 << 1, + CallbackParameter = 1 << 2, + } - nextTokenJSDoc(); - while (parseOptionalJsdoc(SyntaxKind.WhitespaceTrivia)); - if (parseOptionalJsdoc(SyntaxKind.NewLineTrivia)) { - state = JSDocState.BeginningOfLine; - indent = 0; + function parseJSDocCommentWorker(start = 0, length: number | undefined): JSDoc | undefined { + const content = sourceText; + const end = length === undefined ? content.length : start + length; + length = end - start; + + Debug.assert(start >= 0); + Debug.assert(start <= end); + Debug.assert(end <= content.length); + + // Check for /** (JSDoc opening part) + if (!isJSDocLikeText(content, start)) { + return undefined; + } + + let tags: JSDocTag[]; + let tagsPos: number; + let tagsEnd: number; + let linkEnd: number; + let commentsPos: number | undefined; + let comments: string[] = []; + const parts: JSDocComment[] = []; + + // + 3 for leading /**, - 5 in total for /** */ + return scanner.scanRange(start + 3, length - 5, () => { + // Initially we can parse out a tag. We also have seen a starting asterisk. + // This is so that /** * @type */ doesn't parse. + let state = JSDocState.SawAsterisk; + let margin: number | undefined; + // + 4 for leading '/** ' + // + 1 because the last index of \n is always one index before the first character in the line and coincidentally, if there is no \n before start, it is -1, which is also one index before the first character + let indent = start - (content.lastIndexOf("\n", start) + 1) + 4; + function pushComment(text: string) { + if (!margin) { + margin = indent; } - loop: while (true) { - switch (token()) { - case SyntaxKind.AtToken: - if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) { - removeTrailingWhitespace(comments); - if (!commentsPos) commentsPos = getNodePos(); - addTag(parseTag(indent)); - // NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag. - // Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning - // for malformed examples like `/** @param {string} x @returns {number} the length */` - state = JSDocState.BeginningOfLine; - margin = undefined; - } - else { - pushComment(scanner.getTokenText()); - } - break; - case SyntaxKind.NewLineTrivia: - comments.push(scanner.getTokenText()); + comments.push(text); + indent += text.length; + } + + nextTokenJSDoc(); + while (parseOptionalJsdoc(SyntaxKind.WhitespaceTrivia)); + if (parseOptionalJsdoc(SyntaxKind.NewLineTrivia)) { + state = JSDocState.BeginningOfLine; + indent = 0; + } + loop: while (true) { + switch (token()) { + case SyntaxKind.AtToken: + if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) { + removeTrailingWhitespace(comments); + if (!commentsPos) commentsPos = getNodePos(); + addTag(parseTag(indent)); + // NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag. + // Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning + // for malformed examples like `/** @param {string} x @returns {number} the length */` state = JSDocState.BeginningOfLine; - indent = 0; - break; - case SyntaxKind.AsteriskToken: - const asterisk = scanner.getTokenText(); - if (state === JSDocState.SawAsterisk || state === JSDocState.SavingComments) { - // If we've already seen an asterisk, then we can no longer parse a tag on this line - state = JSDocState.SavingComments; - pushComment(asterisk); - } - else { - // Ignore the first asterisk on a line - state = JSDocState.SawAsterisk; - indent += asterisk.length; - } - break; - case SyntaxKind.WhitespaceTrivia: - // only collect whitespace if we're already saving comments or have just crossed the comment indent margin - const whitespace = scanner.getTokenText(); - if (state === JSDocState.SavingComments) { - comments.push(whitespace); - } - else if (margin !== undefined && indent + whitespace.length > margin) { - comments.push(whitespace.slice(margin - indent)); - } - indent += whitespace.length; - break; - case SyntaxKind.EndOfFileToken: - break loop; - case SyntaxKind.OpenBraceToken: + margin = undefined; + } + else { + pushComment(scanner.getTokenText()); + } + break; + case SyntaxKind.NewLineTrivia: + comments.push(scanner.getTokenText()); + state = JSDocState.BeginningOfLine; + indent = 0; + break; + case SyntaxKind.AsteriskToken: + const asterisk = scanner.getTokenText(); + if (state === JSDocState.SawAsterisk || state === JSDocState.SavingComments) { + // If we've already seen an asterisk, then we can no longer parse a tag on this line state = JSDocState.SavingComments; - const commentEnd = scanner.getStartPos(); - const linkStart = scanner.getTextPos() - 1; - const link = parseJSDocLink(linkStart); - if (link) { - if (!linkEnd) { - removeLeadingNewlines(comments); - } - parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? start, commentEnd)); - parts.push(link); - comments = []; - linkEnd = scanner.getTextPos(); - break; + pushComment(asterisk); + } + else { + // Ignore the first asterisk on a line + state = JSDocState.SawAsterisk; + indent += asterisk.length; + } + break; + case SyntaxKind.WhitespaceTrivia: + // only collect whitespace if we're already saving comments or have just crossed the comment indent margin + const whitespace = scanner.getTokenText(); + if (state === JSDocState.SavingComments) { + comments.push(whitespace); + } + else if (margin !== undefined && indent + whitespace.length > margin) { + comments.push(whitespace.slice(margin - indent)); + } + indent += whitespace.length; + break; + case SyntaxKind.EndOfFileToken: + break loop; + case SyntaxKind.OpenBraceToken: + state = JSDocState.SavingComments; + const commentEnd = scanner.getStartPos(); + const linkStart = scanner.getTextPos() - 1; + const link = parseJSDocLink(linkStart); + if (link) { + if (!linkEnd) { + removeLeadingNewlines(comments); } - // fallthrough if it's not a {@link sequence - default: - // Anything else is doc comment text. We just save it. Because it - // wasn't a tag, we can no longer parse a tag on this line until we hit the next - // line break. - state = JSDocState.SavingComments; - pushComment(scanner.getTokenText()); + parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? start, commentEnd)); + parts.push(link); + comments = []; + linkEnd = scanner.getTextPos(); break; - } - nextTokenJSDoc(); - } - removeTrailingWhitespace(comments); - if (parts.length && comments.length) { - parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? start, commentsPos)); - } - if (parts.length && tags) Debug.assertIsDefined(commentsPos, "having parsed tags implies that the end of the comment span should be set"); - const tagsArray = tags && createNodeArray(tags, tagsPos, tagsEnd); - return finishNode(factory.createJSDocComment(parts.length ? createNodeArray(parts, start, commentsPos) : comments.length ? comments.join("") : undefined, tagsArray), start, end); - }); - - function removeLeadingNewlines(comments: string[]) { - while (comments.length && (comments[0] === "\n" || comments[0] === "\r")) { - comments.shift(); + } + // fallthrough if it's not a {@link sequence + default: + // Anything else is doc comment text. We just save it. Because it + // wasn't a tag, we can no longer parse a tag on this line until we hit the next + // line break. + state = JSDocState.SavingComments; + pushComment(scanner.getTokenText()); + break; } + nextTokenJSDoc(); } + removeTrailingWhitespace(comments); + if (parts.length && comments.length) { + parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? start, commentsPos)); + } + if (parts.length && tags) Debug.assertIsDefined(commentsPos, "having parsed tags implies that the end of the comment span should be set"); + const tagsArray = tags && createNodeArray(tags, tagsPos, tagsEnd); + return finishNode(factory.createJSDocComment(parts.length ? createNodeArray(parts, start, commentsPos) : comments.length ? comments.join("") : undefined, tagsArray), start, end); + }); - function removeTrailingWhitespace(comments: string[]) { - while (comments.length && comments[comments.length - 1].trim() === "") { - comments.pop(); - } + function removeLeadingNewlines(comments: string[]) { + while (comments.length && (comments[0] === "\n" || comments[0] === "\r")) { + comments.shift(); } + } - function isNextNonwhitespaceTokenEndOfFile(): boolean { - // We must use infinite lookahead, as there could be any number of newlines :( - while (true) { - nextTokenJSDoc(); - if (token() === SyntaxKind.EndOfFileToken) { - return true; - } - if (!(token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia)) { - return false; - } - } + function removeTrailingWhitespace(comments: string[]) { + while (comments.length && comments[comments.length - 1].trim() === "") { + comments.pop(); } + } - function skipWhitespace(): void { - if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { - if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) { - return; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range - } + function isNextNonwhitespaceTokenEndOfFile(): boolean { + // We must use infinite lookahead, as there could be any number of newlines :( + while (true) { + nextTokenJSDoc(); + if (token() === SyntaxKind.EndOfFileToken) { + return true; } - while (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { - nextTokenJSDoc(); + if (!(token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia)) { + return false; } } + } - function skipWhitespaceOrAsterisk(): string { - if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { - if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) { - return ""; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range - } + function skipWhitespace(): void { + if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { + if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) { + return; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range } + } + while (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { + nextTokenJSDoc(); + } + } - let precedingLineBreak = scanner.hasPrecedingLineBreak(); - let seenLineBreak = false; - let indentText = ""; - while ((precedingLineBreak && token() === SyntaxKind.AsteriskToken) || token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { - indentText += scanner.getTokenText(); - if (token() === SyntaxKind.NewLineTrivia) { - precedingLineBreak = true; - seenLineBreak = true; - indentText = ""; - } - else if (token() === SyntaxKind.AsteriskToken) { - precedingLineBreak = false; - } - nextTokenJSDoc(); + function skipWhitespaceOrAsterisk(): string { + if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { + if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) { + return ""; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range } - return seenLineBreak ? indentText : ""; } - function parseTag(margin: number) { - Debug.assert(token() === SyntaxKind.AtToken); - const start = scanner.getTokenPos(); + let precedingLineBreak = scanner.hasPrecedingLineBreak(); + let seenLineBreak = false; + let indentText = ""; + while ((precedingLineBreak && token() === SyntaxKind.AsteriskToken) || token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { + indentText += scanner.getTokenText(); + if (token() === SyntaxKind.NewLineTrivia) { + precedingLineBreak = true; + seenLineBreak = true; + indentText = ""; + } + else if (token() === SyntaxKind.AsteriskToken) { + precedingLineBreak = false; + } nextTokenJSDoc(); + } + return seenLineBreak ? indentText : ""; + } - const tagName = parseJSDocIdentifierName(/*message*/ undefined); - const indentText = skipWhitespaceOrAsterisk(); + function parseTag(margin: number) { + Debug.assert(token() === SyntaxKind.AtToken); + const start = scanner.getTokenPos(); + nextTokenJSDoc(); - let tag: JSDocTag | undefined; - switch (tagName.escapedText) { - case "author": - tag = parseAuthorTag(start, tagName, margin, indentText); - break; - case "implements": - tag = parseImplementsTag(start, tagName, margin, indentText); - break; - case "augments": - case "extends": - tag = parseAugmentsTag(start, tagName, margin, indentText); - break; - case "class": - case "constructor": - tag = parseSimpleTag(start, factory.createJSDocClassTag, tagName, margin, indentText); - break; - case "public": - tag = parseSimpleTag(start, factory.createJSDocPublicTag, tagName, margin, indentText); - break; - case "private": - tag = parseSimpleTag(start, factory.createJSDocPrivateTag, tagName, margin, indentText); - break; - case "protected": - tag = parseSimpleTag(start, factory.createJSDocProtectedTag, tagName, margin, indentText); - break; - case "readonly": - tag = parseSimpleTag(start, factory.createJSDocReadonlyTag, tagName, margin, indentText); - break; - case "override": - tag = parseSimpleTag(start, factory.createJSDocOverrideTag, tagName, margin, indentText); - break; - case "deprecated": - hasDeprecatedTag = true; - tag = parseSimpleTag(start, factory.createJSDocDeprecatedTag, tagName, margin, indentText); - break; - case "this": - tag = parseThisTag(start, tagName, margin, indentText); - break; - case "enum": - tag = parseEnumTag(start, tagName, margin, indentText); - break; - case "arg": - case "argument": - case "param": - return parseParameterOrPropertyTag(start, tagName, PropertyLikeParse.Parameter, margin); - case "return": - case "returns": - tag = parseReturnTag(start, tagName, margin, indentText); - break; - case "template": - tag = parseTemplateTag(start, tagName, margin, indentText); - break; - case "type": - tag = parseTypeTag(start, tagName, margin, indentText); - break; - case "typedef": - tag = parseTypedefTag(start, tagName, margin, indentText); - break; - case "callback": - tag = parseCallbackTag(start, tagName, margin, indentText); - break; - case "see": - tag = parseSeeTag(start, tagName, margin, indentText); - break; - default: - tag = parseUnknownTag(start, tagName, margin, indentText); - break; - } - return tag; + const tagName = parseJSDocIdentifierName(/*message*/ undefined); + const indentText = skipWhitespaceOrAsterisk(); + + let tag: JSDocTag | undefined; + switch (tagName.escapedText) { + case "author": + tag = parseAuthorTag(start, tagName, margin, indentText); + break; + case "implements": + tag = parseImplementsTag(start, tagName, margin, indentText); + break; + case "augments": + case "extends": + tag = parseAugmentsTag(start, tagName, margin, indentText); + break; + case "class": + case "constructor": + tag = parseSimpleTag(start, factory.createJSDocClassTag, tagName, margin, indentText); + break; + case "public": + tag = parseSimpleTag(start, factory.createJSDocPublicTag, tagName, margin, indentText); + break; + case "private": + tag = parseSimpleTag(start, factory.createJSDocPrivateTag, tagName, margin, indentText); + break; + case "protected": + tag = parseSimpleTag(start, factory.createJSDocProtectedTag, tagName, margin, indentText); + break; + case "readonly": + tag = parseSimpleTag(start, factory.createJSDocReadonlyTag, tagName, margin, indentText); + break; + case "override": + tag = parseSimpleTag(start, factory.createJSDocOverrideTag, tagName, margin, indentText); + break; + case "deprecated": + hasDeprecatedTag = true; + tag = parseSimpleTag(start, factory.createJSDocDeprecatedTag, tagName, margin, indentText); + break; + case "this": + tag = parseThisTag(start, tagName, margin, indentText); + break; + case "enum": + tag = parseEnumTag(start, tagName, margin, indentText); + break; + case "arg": + case "argument": + case "param": + return parseParameterOrPropertyTag(start, tagName, PropertyLikeParse.Parameter, margin); + case "return": + case "returns": + tag = parseReturnTag(start, tagName, margin, indentText); + break; + case "template": + tag = parseTemplateTag(start, tagName, margin, indentText); + break; + case "type": + tag = parseTypeTag(start, tagName, margin, indentText); + break; + case "typedef": + tag = parseTypedefTag(start, tagName, margin, indentText); + break; + case "callback": + tag = parseCallbackTag(start, tagName, margin, indentText); + break; + case "see": + tag = parseSeeTag(start, tagName, margin, indentText); + break; + default: + tag = parseUnknownTag(start, tagName, margin, indentText); + break; } + return tag; + } - function parseTrailingTagComments(pos: number, end: number, margin: number, indentText: string) { - // some tags, like typedef and callback, have already parsed their comments earlier - if (!indentText) { - margin += end - pos; - } - return parseTagComments(margin, indentText.slice(margin)); + function parseTrailingTagComments(pos: number, end: number, margin: number, indentText: string) { + // some tags, like typedef and callback, have already parsed their comments earlier + if (!indentText) { + margin += end - pos; } + return parseTagComments(margin, indentText.slice(margin)); + } - function parseTagComments(indent: number, initialMargin?: string): string | NodeArray | undefined { - const commentsPos = getNodePos(); - let comments: string[] = []; - const parts: JSDocComment[] = []; - let linkEnd; - let state = JSDocState.BeginningOfLine; - let previousWhitespace = true; - let margin: number | undefined; - function pushComment(text: string) { - if (!margin) { - margin = indent; - } - comments.push(text); - indent += text.length; + function parseTagComments(indent: number, initialMargin?: string): string | NodeArray | undefined { + const commentsPos = getNodePos(); + let comments: string[] = []; + const parts: JSDocComment[] = []; + let linkEnd; + let state = JSDocState.BeginningOfLine; + let previousWhitespace = true; + let margin: number | undefined; + function pushComment(text: string) { + if (!margin) { + margin = indent; } - if (initialMargin !== undefined) { - // jump straight to saving comments if there is some initial indentation - if (initialMargin !== "") { - pushComment(initialMargin); - } - state = JSDocState.SawAsterisk; + comments.push(text); + indent += text.length; + } + if (initialMargin !== undefined) { + // jump straight to saving comments if there is some initial indentation + if (initialMargin !== "") { + pushComment(initialMargin); } - let tok = token() as JSDocSyntaxKind; - loop: while (true) { - switch (tok) { - case SyntaxKind.NewLineTrivia: - state = JSDocState.BeginningOfLine; - // don't use pushComment here because we want to keep the margin unchanged + state = JSDocState.SawAsterisk; + } + let tok = token() as JSDocSyntaxKind; + loop: while (true) { + switch (tok) { + case SyntaxKind.NewLineTrivia: + state = JSDocState.BeginningOfLine; + // don't use pushComment here because we want to keep the margin unchanged + comments.push(scanner.getTokenText()); + indent = 0; + break; + case SyntaxKind.AtToken: + if (state === JSDocState.SavingBackticks + || state === JSDocState.SavingComments && (!previousWhitespace || lookAhead(isNextJSDocTokenWhitespace))) { + // @ doesn't start a new tag inside ``, and inside a comment, only after whitespace or not before whitespace comments.push(scanner.getTokenText()); - indent = 0; - break; - case SyntaxKind.AtToken: - if (state === JSDocState.SavingBackticks - || state === JSDocState.SavingComments && (!previousWhitespace || lookAhead(isNextJSDocTokenWhitespace))) { - // @ doesn't start a new tag inside ``, and inside a comment, only after whitespace or not before whitespace - comments.push(scanner.getTokenText()); - break; - } - scanner.setTextPos(scanner.getTextPos() - 1); - // falls through - case SyntaxKind.EndOfFileToken: - // Done - break loop; - case SyntaxKind.WhitespaceTrivia: - if (state === JSDocState.SavingComments || state === JSDocState.SavingBackticks) { - pushComment(scanner.getTokenText()); - } - else { - const whitespace = scanner.getTokenText(); - // if the whitespace crosses the margin, take only the whitespace that passes the margin - if (margin !== undefined && indent + whitespace.length > margin) { - comments.push(whitespace.slice(margin - indent)); - } - indent += whitespace.length; - } - break; - case SyntaxKind.OpenBraceToken: - state = JSDocState.SavingComments; - const commentEnd = scanner.getStartPos(); - const linkStart = scanner.getTextPos() - 1; - const link = parseJSDocLink(linkStart); - if (link) { - parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? commentsPos, commentEnd)); - parts.push(link); - comments = []; - linkEnd = scanner.getTextPos(); - } - else { - pushComment(scanner.getTokenText()); - } break; - case SyntaxKind.BacktickToken: - if (state === JSDocState.SavingBackticks) { - state = JSDocState.SavingComments; - } - else { - state = JSDocState.SavingBackticks; - } + } + scanner.setTextPos(scanner.getTextPos() - 1); + // falls through + case SyntaxKind.EndOfFileToken: + // Done + break loop; + case SyntaxKind.WhitespaceTrivia: + if (state === JSDocState.SavingComments || state === JSDocState.SavingBackticks) { pushComment(scanner.getTokenText()); - break; - case SyntaxKind.AsteriskToken: - if (state === JSDocState.BeginningOfLine) { - // leading asterisks start recording on the *next* (non-whitespace) token - state = JSDocState.SawAsterisk; - indent += 1; - break; - } - // record the * as a comment - // falls through - default: - if (state !== JSDocState.SavingBackticks) { - state = JSDocState.SavingComments; // leading identifiers start recording as well + } + else { + const whitespace = scanner.getTokenText(); + // if the whitespace crosses the margin, take only the whitespace that passes the margin + if (margin !== undefined && indent + whitespace.length > margin) { + comments.push(whitespace.slice(margin - indent)); } + indent += whitespace.length; + } + break; + case SyntaxKind.OpenBraceToken: + state = JSDocState.SavingComments; + const commentEnd = scanner.getStartPos(); + const linkStart = scanner.getTextPos() - 1; + const link = parseJSDocLink(linkStart); + if (link) { + parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? commentsPos, commentEnd)); + parts.push(link); + comments = []; + linkEnd = scanner.getTextPos(); + } + else { pushComment(scanner.getTokenText()); + } + break; + case SyntaxKind.BacktickToken: + if (state === JSDocState.SavingBackticks) { + state = JSDocState.SavingComments; + } + else { + state = JSDocState.SavingBackticks; + } + pushComment(scanner.getTokenText()); + break; + case SyntaxKind.AsteriskToken: + if (state === JSDocState.BeginningOfLine) { + // leading asterisks start recording on the *next* (non-whitespace) token + state = JSDocState.SawAsterisk; + indent += 1; break; - } - previousWhitespace = token() === SyntaxKind.WhitespaceTrivia; - tok = nextTokenJSDoc(); + } + // record the * as a comment + // falls through + default: + if (state !== JSDocState.SavingBackticks) { + state = JSDocState.SavingComments; // leading identifiers start recording as well + } + pushComment(scanner.getTokenText()); + break; } + previousWhitespace = token() === SyntaxKind.WhitespaceTrivia; + tok = nextTokenJSDoc(); + } - removeLeadingNewlines(comments); - removeTrailingWhitespace(comments); - if (parts.length) { - if (comments.length) { - parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? commentsPos)); - } - return createNodeArray(parts, commentsPos, scanner.getTextPos()); - } - else if (comments.length) { - return comments.join(""); + removeLeadingNewlines(comments); + removeTrailingWhitespace(comments); + if (parts.length) { + if (comments.length) { + parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? commentsPos)); } + return createNodeArray(parts, commentsPos, scanner.getTextPos()); } - - function isNextJSDocTokenWhitespace() { - const next = nextTokenJSDoc(); - return next === SyntaxKind.WhitespaceTrivia || next === SyntaxKind.NewLineTrivia; + else if (comments.length) { + return comments.join(""); } + } - function parseJSDocLink(start: number) { - const linkType = tryParse(parseJSDocLinkPrefix); - if (!linkType) { - return undefined; - } - nextTokenJSDoc(); // start at token after link, then skip any whitespace - skipWhitespace(); - // parseEntityName logs an error for non-identifier, so create a MissingNode ourselves to avoid the error - const p2 = getNodePos(); - let name: EntityName | JSDocMemberName | undefined = tokenIsIdentifierOrKeyword(token()) - ? parseEntityName(/*allowReservedWords*/ true) - : undefined; - if (name) { - while (token() === SyntaxKind.PrivateIdentifier) { - reScanHashToken(); // rescan #id as # id - nextTokenJSDoc(); // then skip the # - name = finishNode(factory.createJSDocMemberName(name, parseIdentifier()), p2); - } - } - const text = []; - while (token() !== SyntaxKind.CloseBraceToken && token() !== SyntaxKind.NewLineTrivia && token() !== SyntaxKind.EndOfFileToken) { - text.push(scanner.getTokenText()); - nextTokenJSDoc(); - } - const create = linkType === "link" ? factory.createJSDocLink - : linkType === "linkcode" ? factory.createJSDocLinkCode - : factory.createJSDocLinkPlain; - return finishNode(create(name, text.join("")), start, scanner.getTextPos()); - } + function isNextJSDocTokenWhitespace() { + const next = nextTokenJSDoc(); + return next === SyntaxKind.WhitespaceTrivia || next === SyntaxKind.NewLineTrivia; + } - function parseJSDocLinkPrefix() { - skipWhitespaceOrAsterisk(); - if (token() === SyntaxKind.OpenBraceToken - && nextTokenJSDoc() === SyntaxKind.AtToken - && tokenIsIdentifierOrKeyword(nextTokenJSDoc())) { - const kind = scanner.getTokenValue(); - if(kind === "link" || kind === "linkcode" || kind === "linkplain") { - return kind; - } + function parseJSDocLink(start: number) { + const linkType = tryParse(parseJSDocLinkPrefix); + if (!linkType) { + return undefined; + } + nextTokenJSDoc(); // start at token after link, then skip any whitespace + skipWhitespace(); + // parseEntityName logs an error for non-identifier, so create a MissingNode ourselves to avoid the error + const p2 = getNodePos(); + let name: EntityName | JSDocMemberName | undefined = tokenIsIdentifierOrKeyword(token()) + ? parseEntityName(/*allowReservedWords*/ true) + : undefined; + if (name) { + while (token() === SyntaxKind.PrivateIdentifier) { + reScanHashToken(); // rescan #id as # id + nextTokenJSDoc(); // then skip the # + name = finishNode(factory.createJSDocMemberName(name, parseIdentifier()), p2); } } - - function parseUnknownTag(start: number, tagName: Identifier, indent: number, indentText: string) { - return finishNode(factory.createJSDocUnknownTag(tagName, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); + const text = []; + while (token() !== SyntaxKind.CloseBraceToken && token() !== SyntaxKind.NewLineTrivia && token() !== SyntaxKind.EndOfFileToken) { + text.push(scanner.getTokenText()); + nextTokenJSDoc(); } + const create = linkType === "link" ? factory.createJSDocLink + : linkType === "linkcode" ? factory.createJSDocLinkCode + : factory.createJSDocLinkPlain; + return finishNode(create(name, text.join("")), start, scanner.getTextPos()); + } - function addTag(tag: JSDocTag | undefined): void { - if (!tag) { - return; - } - if (!tags) { - tags = [tag]; - tagsPos = tag.pos; - } - else { - tags.push(tag); + function parseJSDocLinkPrefix() { + skipWhitespaceOrAsterisk(); + if (token() === SyntaxKind.OpenBraceToken + && nextTokenJSDoc() === SyntaxKind.AtToken + && tokenIsIdentifierOrKeyword(nextTokenJSDoc())) { + const kind = scanner.getTokenValue(); + if(kind === "link" || kind === "linkcode" || kind === "linkplain") { + return kind; } - tagsEnd = tag.end; } + } - function tryParseTypeExpression(): JSDocTypeExpression | undefined { - skipWhitespaceOrAsterisk(); - return token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; + function parseUnknownTag(start: number, tagName: Identifier, indent: number, indentText: string) { + return finishNode(factory.createJSDocUnknownTag(tagName, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); + } + + function addTag(tag: JSDocTag | undefined): void { + if (!tag) { + return; + } + if (!tags) { + tags = [tag]; + tagsPos = tag.pos; + } + else { + tags.push(tag); } + tagsEnd = tag.end; + } - function parseBracketNameInPropertyAndParamTag(): { name: EntityName, isBracketed: boolean } { - // Looking for something like '[foo]', 'foo', '[foo.bar]' or 'foo.bar' - const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken); - if (isBracketed) { - skipWhitespace(); - } - // a markdown-quoted name: `arg` is not legal jsdoc, but occurs in the wild - const isBackquoted = parseOptionalJsdoc(SyntaxKind.BacktickToken); - const name = parseJSDocEntityName(); - if (isBackquoted) { - parseExpectedTokenJSDoc(SyntaxKind.BacktickToken); - } - if (isBracketed) { - skipWhitespace(); - // May have an optional default, e.g. '[foo = 42]' - if (parseOptionalToken(SyntaxKind.EqualsToken)) { - parseExpression(); - } + function tryParseTypeExpression(): JSDocTypeExpression | undefined { + skipWhitespaceOrAsterisk(); + return token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; + } - parseExpected(SyntaxKind.CloseBracketToken); + function parseBracketNameInPropertyAndParamTag(): { name: EntityName, isBracketed: boolean } { + // Looking for something like '[foo]', 'foo', '[foo.bar]' or 'foo.bar' + const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken); + if (isBracketed) { + skipWhitespace(); + } + // a markdown-quoted name: `arg` is not legal jsdoc, but occurs in the wild + const isBackquoted = parseOptionalJsdoc(SyntaxKind.BacktickToken); + const name = parseJSDocEntityName(); + if (isBackquoted) { + parseExpectedTokenJSDoc(SyntaxKind.BacktickToken); + } + if (isBracketed) { + skipWhitespace(); + // May have an optional default, e.g. '[foo = 42]' + if (parseOptionalToken(SyntaxKind.EqualsToken)) { + parseExpression(); } - return { name, isBracketed }; + parseExpected(SyntaxKind.CloseBracketToken); } - function isObjectOrObjectArrayTypeReference(node: TypeNode): boolean { - switch (node.kind) { - case SyntaxKind.ObjectKeyword: - return true; - case SyntaxKind.ArrayType: - return isObjectOrObjectArrayTypeReference((node as ArrayTypeNode).elementType); - default: - return isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && node.typeName.escapedText === "Object" && !node.typeArguments; - } + return { name, isBracketed }; + } + + function isObjectOrObjectArrayTypeReference(node: TypeNode): boolean { + switch (node.kind) { + case SyntaxKind.ObjectKeyword: + return true; + case SyntaxKind.ArrayType: + return isObjectOrObjectArrayTypeReference((node as ArrayTypeNode).elementType); + default: + return isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && node.typeName.escapedText === "Object" && !node.typeArguments; } + } - function parseParameterOrPropertyTag(start: number, tagName: Identifier, target: PropertyLikeParse, indent: number): JSDocParameterTag | JSDocPropertyTag { - let typeExpression = tryParseTypeExpression(); - let isNameFirst = !typeExpression; - skipWhitespaceOrAsterisk(); + function parseParameterOrPropertyTag(start: number, tagName: Identifier, target: PropertyLikeParse, indent: number): JSDocParameterTag | JSDocPropertyTag { + let typeExpression = tryParseTypeExpression(); + let isNameFirst = !typeExpression; + skipWhitespaceOrAsterisk(); - const { name, isBracketed } = parseBracketNameInPropertyAndParamTag(); - const indentText = skipWhitespaceOrAsterisk(); + const { name, isBracketed } = parseBracketNameInPropertyAndParamTag(); + const indentText = skipWhitespaceOrAsterisk(); - if (isNameFirst && !lookAhead(parseJSDocLinkPrefix)) { - typeExpression = tryParseTypeExpression(); - } + if (isNameFirst && !lookAhead(parseJSDocLinkPrefix)) { + typeExpression = tryParseTypeExpression(); + } - const comment = parseTrailingTagComments(start, getNodePos(), indent, indentText); + const comment = parseTrailingTagComments(start, getNodePos(), indent, indentText); - const nestedTypeLiteral = target !== PropertyLikeParse.CallbackParameter && parseNestedTypeLiteral(typeExpression, name, target, indent); - if (nestedTypeLiteral) { - typeExpression = nestedTypeLiteral; - isNameFirst = true; - } - const result = target === PropertyLikeParse.Property - ? factory.createJSDocPropertyTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment) - : factory.createJSDocParameterTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment); - return finishNode(result, start); + const nestedTypeLiteral = target !== PropertyLikeParse.CallbackParameter && parseNestedTypeLiteral(typeExpression, name, target, indent); + if (nestedTypeLiteral) { + typeExpression = nestedTypeLiteral; + isNameFirst = true; } + const result = target === PropertyLikeParse.Property + ? factory.createJSDocPropertyTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment) + : factory.createJSDocParameterTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment); + return finishNode(result, start); + } - function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression | undefined, name: EntityName, target: PropertyLikeParse, indent: number) { - if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) { - const pos = getNodePos(); - let child: JSDocPropertyLikeTag | JSDocTypeTag | false; - let children: JSDocPropertyLikeTag[] | undefined; - while (child = tryParse(() => parseChildParameterOrPropertyTag(target, indent, name))) { - if (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) { - children = append(children, child); - } - } - if (children) { - const literal = finishNode(factory.createJSDocTypeLiteral(children, typeExpression.type.kind === SyntaxKind.ArrayType), pos); - return finishNode(factory.createJSDocTypeExpression(literal), pos); + function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression | undefined, name: EntityName, target: PropertyLikeParse, indent: number) { + if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) { + const pos = getNodePos(); + let child: JSDocPropertyLikeTag | JSDocTypeTag | false; + let children: JSDocPropertyLikeTag[] | undefined; + while (child = tryParse(() => parseChildParameterOrPropertyTag(target, indent, name))) { + if (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) { + children = append(children, child); } } - } - - function parseReturnTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocReturnTag { - if (some(tags, isJSDocReturnTag)) { - parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); + if (children) { + const literal = finishNode(factory.createJSDocTypeLiteral(children, typeExpression.type.kind === SyntaxKind.ArrayType), pos); + return finishNode(factory.createJSDocTypeExpression(literal), pos); } + } + } - const typeExpression = tryParseTypeExpression(); - return finishNode(factory.createJSDocReturnTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); + function parseReturnTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocReturnTag { + if (some(tags, isJSDocReturnTag)) { + parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); } - function parseTypeTag(start: number, tagName: Identifier, indent?: number, indentText?: string): JSDocTypeTag { - if (some(tags, isJSDocTypeTag)) { - parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); - } + const typeExpression = tryParseTypeExpression(); + return finishNode(factory.createJSDocReturnTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); + } - const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); - const comments = indent !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), indent, indentText) : undefined; - return finishNode(factory.createJSDocTypeTag(tagName, typeExpression, comments), start); + function parseTypeTag(start: number, tagName: Identifier, indent?: number, indentText?: string): JSDocTypeTag { + if (some(tags, isJSDocTypeTag)) { + parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); } - function parseSeeTag(start: number, tagName: Identifier, indent?: number, indentText?: string): JSDocSeeTag { - const isMarkdownOrJSDocLink = token() === SyntaxKind.OpenBracketToken - || lookAhead(() => nextTokenJSDoc() === SyntaxKind.AtToken && tokenIsIdentifierOrKeyword(nextTokenJSDoc()) && scanner.getTokenValue() === "link"); - const nameExpression = isMarkdownOrJSDocLink ? undefined : parseJSDocNameReference(); - const comments = indent !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), indent, indentText) : undefined; - return finishNode(factory.createJSDocSeeTag(tagName, nameExpression, comments), start); - } + const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + const comments = indent !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), indent, indentText) : undefined; + return finishNode(factory.createJSDocTypeTag(tagName, typeExpression, comments), start); + } - function parseAuthorTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocAuthorTag { - const commentStart = getNodePos(); - const textOnly = parseAuthorNameAndEmail(); - let commentEnd = scanner.getStartPos(); - const comments = parseTrailingTagComments(start, commentEnd, indent, indentText); - if (!comments) { - commentEnd = scanner.getStartPos(); - } - const allParts = typeof comments !== "string" - ? createNodeArray(concatenate([finishNode(textOnly, commentStart, commentEnd)], comments) as JSDocComment[], commentStart) // cast away readonly - : textOnly.text + comments; - return finishNode(factory.createJSDocAuthorTag(tagName, allParts), start); + function parseSeeTag(start: number, tagName: Identifier, indent?: number, indentText?: string): JSDocSeeTag { + const isMarkdownOrJSDocLink = token() === SyntaxKind.OpenBracketToken + || lookAhead(() => nextTokenJSDoc() === SyntaxKind.AtToken && tokenIsIdentifierOrKeyword(nextTokenJSDoc()) && scanner.getTokenValue() === "link"); + const nameExpression = isMarkdownOrJSDocLink ? undefined : parseJSDocNameReference(); + const comments = indent !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), indent, indentText) : undefined; + return finishNode(factory.createJSDocSeeTag(tagName, nameExpression, comments), start); + } + + function parseAuthorTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocAuthorTag { + const commentStart = getNodePos(); + const textOnly = parseAuthorNameAndEmail(); + let commentEnd = scanner.getStartPos(); + const comments = parseTrailingTagComments(start, commentEnd, indent, indentText); + if (!comments) { + commentEnd = scanner.getStartPos(); } + const allParts = typeof comments !== "string" + ? createNodeArray(concatenate([finishNode(textOnly, commentStart, commentEnd)], comments) as JSDocComment[], commentStart) // cast away readonly + : textOnly.text + comments; + return finishNode(factory.createJSDocAuthorTag(tagName, allParts), start); + } - function parseAuthorNameAndEmail(): JSDocText { - const comments: string[] = []; - let inEmail = false; - let token = scanner.getToken(); - while (token !== SyntaxKind.EndOfFileToken && token !== SyntaxKind.NewLineTrivia) { - if (token === SyntaxKind.LessThanToken) { - inEmail = true; - } - else if (token === SyntaxKind.AtToken && !inEmail) { - break; - } - else if (token === SyntaxKind.GreaterThanToken && inEmail) { - comments.push(scanner.getTokenText()); - scanner.setTextPos(scanner.getTokenPos() + 1); - break; - } + function parseAuthorNameAndEmail(): JSDocText { + const comments: string[] = []; + let inEmail = false; + let token = scanner.getToken(); + while (token !== SyntaxKind.EndOfFileToken && token !== SyntaxKind.NewLineTrivia) { + if (token === SyntaxKind.LessThanToken) { + inEmail = true; + } + else if (token === SyntaxKind.AtToken && !inEmail) { + break; + } + else if (token === SyntaxKind.GreaterThanToken && inEmail) { comments.push(scanner.getTokenText()); - token = nextTokenJSDoc(); + scanner.setTextPos(scanner.getTokenPos() + 1); + break; } - - return factory.createJSDocText(comments.join("")); + comments.push(scanner.getTokenText()); + token = nextTokenJSDoc(); } - function parseImplementsTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocImplementsTag { - const className = parseExpressionWithTypeArgumentsForAugments(); - return finishNode(factory.createJSDocImplementsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); - } + return factory.createJSDocText(comments.join("")); + } - function parseAugmentsTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocAugmentsTag { - const className = parseExpressionWithTypeArgumentsForAugments(); - return finishNode(factory.createJSDocAugmentsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); - } + function parseImplementsTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocImplementsTag { + const className = parseExpressionWithTypeArgumentsForAugments(); + return finishNode(factory.createJSDocImplementsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); + } - function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression } { - const usedBrace = parseOptional(SyntaxKind.OpenBraceToken); - const pos = getNodePos(); - const expression = parsePropertyAccessEntityNameExpression(); - const typeArguments = tryParseTypeArguments(); - const node = factory.createExpressionWithTypeArguments(expression, typeArguments) as ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression }; - const res = finishNode(node, pos); - if (usedBrace) { - parseExpected(SyntaxKind.CloseBraceToken); - } - return res; - } + function parseAugmentsTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocAugmentsTag { + const className = parseExpressionWithTypeArgumentsForAugments(); + return finishNode(factory.createJSDocAugmentsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); + } - function parsePropertyAccessEntityNameExpression() { - const pos = getNodePos(); - let node: Identifier | PropertyAccessEntityNameExpression = parseJSDocIdentifierName(); - while (parseOptional(SyntaxKind.DotToken)) { - const name = parseJSDocIdentifierName(); - node = finishNode(factory.createPropertyAccessExpression(node, name), pos) as PropertyAccessEntityNameExpression; - } - return node; + function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression } { + const usedBrace = parseOptional(SyntaxKind.OpenBraceToken); + const pos = getNodePos(); + const expression = parsePropertyAccessEntityNameExpression(); + const typeArguments = tryParseTypeArguments(); + const node = factory.createExpressionWithTypeArguments(expression, typeArguments) as ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression }; + const res = finishNode(node, pos); + if (usedBrace) { + parseExpected(SyntaxKind.CloseBraceToken); } + return res; + } - function parseSimpleTag(start: number, createTag: (tagName: Identifier | undefined, comment?: string | NodeArray) => JSDocTag, tagName: Identifier, margin: number, indentText: string): JSDocTag { - return finishNode(createTag(tagName, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); + function parsePropertyAccessEntityNameExpression() { + const pos = getNodePos(); + let node: Identifier | PropertyAccessEntityNameExpression = parseJSDocIdentifierName(); + while (parseOptional(SyntaxKind.DotToken)) { + const name = parseJSDocIdentifierName(); + node = finishNode(factory.createPropertyAccessExpression(node, name), pos) as PropertyAccessEntityNameExpression; } + return node; + } - function parseThisTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocThisTag { - const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); - skipWhitespace(); - return finishNode(factory.createJSDocThisTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); - } + function parseSimpleTag(start: number, createTag: (tagName: Identifier | undefined, comment?: string | NodeArray) => JSDocTag, tagName: Identifier, margin: number, indentText: string): JSDocTag { + return finishNode(createTag(tagName, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); + } - function parseEnumTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocEnumTag { - const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); - skipWhitespace(); - return finishNode(factory.createJSDocEnumTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); - } + function parseThisTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocThisTag { + const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + skipWhitespace(); + return finishNode(factory.createJSDocThisTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); + } - function parseTypedefTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocTypedefTag { - let typeExpression: JSDocTypeExpression | JSDocTypeLiteral | undefined = tryParseTypeExpression(); - skipWhitespaceOrAsterisk(); + function parseEnumTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocEnumTag { + const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + skipWhitespace(); + return finishNode(factory.createJSDocEnumTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); + } - const fullName = parseJSDocTypeNameWithNamespace(); - skipWhitespace(); - let comment = parseTagComments(indent); - - let end: number | undefined; - if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { - let child: JSDocTypeTag | JSDocPropertyTag | false; - let childTypeTag: JSDocTypeTag | undefined; - let jsDocPropertyTags: JSDocPropertyTag[] | undefined; - let hasChildren = false; - while (child = tryParse(() => parseChildPropertyTag(indent))) { - hasChildren = true; - if (child.kind === SyntaxKind.JSDocTypeTag) { - if (childTypeTag) { - parseErrorAtCurrentToken(Diagnostics.A_JSDoc_typedef_comment_may_not_contain_multiple_type_tags); - const lastError = lastOrUndefined(parseDiagnostics); - if (lastError) { - addRelatedInfo( - lastError, - createDetachedDiagnostic(fileName, 0, 0, Diagnostics.The_tag_was_first_specified_here) - ); - } - break; - } - else { - childTypeTag = child; + function parseTypedefTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocTypedefTag { + let typeExpression: JSDocTypeExpression | JSDocTypeLiteral | undefined = tryParseTypeExpression(); + skipWhitespaceOrAsterisk(); + + const fullName = parseJSDocTypeNameWithNamespace(); + skipWhitespace(); + let comment = parseTagComments(indent); + + let end: number | undefined; + if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { + let child: JSDocTypeTag | JSDocPropertyTag | false; + let childTypeTag: JSDocTypeTag | undefined; + let jsDocPropertyTags: JSDocPropertyTag[] | undefined; + let hasChildren = false; + while (child = tryParse(() => parseChildPropertyTag(indent))) { + hasChildren = true; + if (child.kind === SyntaxKind.JSDocTypeTag) { + if (childTypeTag) { + parseErrorAtCurrentToken(Diagnostics.A_JSDoc_typedef_comment_may_not_contain_multiple_type_tags); + const lastError = lastOrUndefined(parseDiagnostics); + if (lastError) { + addRelatedInfo( + lastError, + createDetachedDiagnostic(fileName, 0, 0, Diagnostics.The_tag_was_first_specified_here) + ); } + break; } else { - jsDocPropertyTags = append(jsDocPropertyTags, child); + childTypeTag = child; } } - if (hasChildren) { - const isArrayType = typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType; - const jsdocTypeLiteral = factory.createJSDocTypeLiteral(jsDocPropertyTags, isArrayType); - typeExpression = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? - childTypeTag.typeExpression : - finishNode(jsdocTypeLiteral, start); - end = typeExpression.end; + else { + jsDocPropertyTags = append(jsDocPropertyTags, child); } } - - // Only include the characters between the name end and the next token if a comment was actually parsed out - otherwise it's just whitespace - end = end || comment !== undefined ? - getNodePos() : - (fullName ?? typeExpression ?? tagName).end; - - if (!comment) { - comment = parseTrailingTagComments(start, end, indent, indentText); + if (hasChildren) { + const isArrayType = typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType; + const jsdocTypeLiteral = factory.createJSDocTypeLiteral(jsDocPropertyTags, isArrayType); + typeExpression = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? + childTypeTag.typeExpression : + finishNode(jsdocTypeLiteral, start); + end = typeExpression.end; } - - const typedefTag = factory.createJSDocTypedefTag(tagName, typeExpression, fullName, comment); - return finishNode(typedefTag, start, end); } - function parseJSDocTypeNameWithNamespace(nested?: boolean) { - const pos = scanner.getTokenPos(); - if (!tokenIsIdentifierOrKeyword(token())) { - return undefined; - } - const typeNameOrNamespaceName = parseJSDocIdentifierName(); - if (parseOptional(SyntaxKind.DotToken)) { - const body = parseJSDocTypeNameWithNamespace(/*nested*/ true); - const jsDocNamespaceNode = factory.createModuleDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - typeNameOrNamespaceName, - body, - nested ? NodeFlags.NestedNamespace : undefined - ) as JSDocNamespaceDeclaration; - return finishNode(jsDocNamespaceNode, pos); - } + // Only include the characters between the name end and the next token if a comment was actually parsed out - otherwise it's just whitespace + end = end || comment !== undefined ? + getNodePos() : + (fullName ?? typeExpression ?? tagName).end; - if (nested) { - typeNameOrNamespaceName.isInJSDocNamespace = true; - } - return typeNameOrNamespaceName; + if (!comment) { + comment = parseTrailingTagComments(start, end, indent, indentText); } + const typedefTag = factory.createJSDocTypedefTag(tagName, typeExpression, fullName, comment); + return finishNode(typedefTag, start, end); + } - function parseCallbackTagParameters(indent: number) { - const pos = getNodePos(); - let child: JSDocParameterTag | false; - let parameters; - while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter, indent) as JSDocParameterTag)) { - parameters = append(parameters, child); - } - return createNodeArray(parameters || [], pos); + function parseJSDocTypeNameWithNamespace(nested?: boolean) { + const pos = scanner.getTokenPos(); + if (!tokenIsIdentifierOrKeyword(token())) { + return undefined; + } + const typeNameOrNamespaceName = parseJSDocIdentifierName(); + if (parseOptional(SyntaxKind.DotToken)) { + const body = parseJSDocTypeNameWithNamespace(/*nested*/ true); + const jsDocNamespaceNode = factory.createModuleDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + typeNameOrNamespaceName, + body, + nested ? NodeFlags.NestedNamespace : undefined + ) as JSDocNamespaceDeclaration; + return finishNode(jsDocNamespaceNode, pos); } - function parseCallbackTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocCallbackTag { - const fullName = parseJSDocTypeNameWithNamespace(); - skipWhitespace(); - let comment = parseTagComments(indent); - const parameters = parseCallbackTagParameters(indent); - const returnTag = tryParse(() => { - if (parseOptionalJsdoc(SyntaxKind.AtToken)) { - const tag = parseTag(indent); - if (tag && tag.kind === SyntaxKind.JSDocReturnTag) { - return tag as JSDocReturnTag; - } - } - }); - const typeExpression = finishNode(factory.createJSDocSignature(/*typeParameters*/ undefined, parameters, returnTag), start); - if (!comment) { - comment = parseTrailingTagComments(start, getNodePos(), indent, indentText); - } - return finishNode(factory.createJSDocCallbackTag(tagName, typeExpression, fullName, comment), start); + if (nested) { + typeNameOrNamespaceName.isInJSDocNamespace = true; } + return typeNameOrNamespaceName; + } - function escapedTextsEqual(a: EntityName, b: EntityName): boolean { - while (!ts.isIdentifier(a) || !ts.isIdentifier(b)) { - if (!ts.isIdentifier(a) && !ts.isIdentifier(b) && a.right.escapedText === b.right.escapedText) { - a = a.left; - b = b.left; - } - else { - return false; + + function parseCallbackTagParameters(indent: number) { + const pos = getNodePos(); + let child: JSDocParameterTag | false; + let parameters; + while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter, indent) as JSDocParameterTag)) { + parameters = append(parameters, child); + } + return createNodeArray(parameters || [], pos); + } + + function parseCallbackTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocCallbackTag { + const fullName = parseJSDocTypeNameWithNamespace(); + skipWhitespace(); + let comment = parseTagComments(indent); + const parameters = parseCallbackTagParameters(indent); + const returnTag = tryParse(() => { + if (parseOptionalJsdoc(SyntaxKind.AtToken)) { + const tag = parseTag(indent); + if (tag && tag.kind === SyntaxKind.JSDocReturnTag) { + return tag as JSDocReturnTag; } } - return a.escapedText === b.escapedText; - } - - function parseChildPropertyTag(indent: number) { - return parseChildParameterOrPropertyTag(PropertyLikeParse.Property, indent) as JSDocTypeTag | JSDocPropertyTag | false; + }); + const typeExpression = finishNode(factory.createJSDocSignature(/*typeParameters*/ undefined, parameters, returnTag), start); + if (!comment) { + comment = parseTrailingTagComments(start, getNodePos(), indent, indentText); } + return finishNode(factory.createJSDocCallbackTag(tagName, typeExpression, fullName, comment), start); + } - function parseChildParameterOrPropertyTag(target: PropertyLikeParse, indent: number, name?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { - let canParseTag = true; - let seenAsterisk = false; - while (true) { - switch (nextTokenJSDoc()) { - case SyntaxKind.AtToken: - if (canParseTag) { - const child = tryParseChildTag(target, indent); - if (child && (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) && - target !== PropertyLikeParse.CallbackParameter && - name && (ts.isIdentifier(child.name) || !escapedTextsEqual(name, child.name.left))) { - return false; - } - return child; - } - seenAsterisk = false; - break; - case SyntaxKind.NewLineTrivia: - canParseTag = true; - seenAsterisk = false; - break; - case SyntaxKind.AsteriskToken: - if (seenAsterisk) { - canParseTag = false; - } - seenAsterisk = true; - break; - case SyntaxKind.Identifier: - canParseTag = false; - break; - case SyntaxKind.EndOfFileToken: - return false; - } + function escapedTextsEqual(a: EntityName, b: EntityName): boolean { + while (!ts.isIdentifier(a) || !ts.isIdentifier(b)) { + if (!ts.isIdentifier(a) && !ts.isIdentifier(b) && a.right.escapedText === b.right.escapedText) { + a = a.left; + b = b.left; + } + else { + return false; } } + return a.escapedText === b.escapedText; + } - function tryParseChildTag(target: PropertyLikeParse, indent: number): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { - Debug.assert(token() === SyntaxKind.AtToken); - const start = scanner.getStartPos(); - nextTokenJSDoc(); + function parseChildPropertyTag(indent: number) { + return parseChildParameterOrPropertyTag(PropertyLikeParse.Property, indent) as JSDocTypeTag | JSDocPropertyTag | false; + } - const tagName = parseJSDocIdentifierName(); - skipWhitespace(); - let t: PropertyLikeParse; - switch (tagName.escapedText) { - case "type": - return target === PropertyLikeParse.Property && parseTypeTag(start, tagName); - case "prop": - case "property": - t = PropertyLikeParse.Property; + function parseChildParameterOrPropertyTag(target: PropertyLikeParse, indent: number, name?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { + let canParseTag = true; + let seenAsterisk = false; + while (true) { + switch (nextTokenJSDoc()) { + case SyntaxKind.AtToken: + if (canParseTag) { + const child = tryParseChildTag(target, indent); + if (child && (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) && + target !== PropertyLikeParse.CallbackParameter && + name && (ts.isIdentifier(child.name) || !escapedTextsEqual(name, child.name.left))) { + return false; + } + return child; + } + seenAsterisk = false; break; - case "arg": - case "argument": - case "param": - t = PropertyLikeParse.Parameter | PropertyLikeParse.CallbackParameter; + case SyntaxKind.NewLineTrivia: + canParseTag = true; + seenAsterisk = false; break; - default: + case SyntaxKind.AsteriskToken: + if (seenAsterisk) { + canParseTag = false; + } + seenAsterisk = true; + break; + case SyntaxKind.Identifier: + canParseTag = false; + break; + case SyntaxKind.EndOfFileToken: return false; } - if (!(target & t)) { - return false; - } - return parseParameterOrPropertyTag(start, tagName, target, indent); } + } - function parseTemplateTagTypeParameter() { - const typeParameterPos = getNodePos(); - const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken); - if (isBracketed) { - skipWhitespace(); - } - const name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces); + function tryParseChildTag(target: PropertyLikeParse, indent: number): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { + Debug.assert(token() === SyntaxKind.AtToken); + const start = scanner.getStartPos(); + nextTokenJSDoc(); - let defaultType: TypeNode | undefined; - if (isBracketed) { - skipWhitespace(); - parseExpected(SyntaxKind.EqualsToken); - defaultType = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); - parseExpected(SyntaxKind.CloseBracketToken); - } + const tagName = parseJSDocIdentifierName(); + skipWhitespace(); + let t: PropertyLikeParse; + switch (tagName.escapedText) { + case "type": + return target === PropertyLikeParse.Property && parseTypeTag(start, tagName); + case "prop": + case "property": + t = PropertyLikeParse.Property; + break; + case "arg": + case "argument": + case "param": + t = PropertyLikeParse.Parameter | PropertyLikeParse.CallbackParameter; + break; + default: + return false; + } + if (!(target & t)) { + return false; + } + return parseParameterOrPropertyTag(start, tagName, target, indent); + } - if (nodeIsMissing(name)) { - return undefined; - } - return finishNode(factory.createTypeParameterDeclaration(name, /*constraint*/ undefined, defaultType), typeParameterPos); + function parseTemplateTagTypeParameter() { + const typeParameterPos = getNodePos(); + const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken); + if (isBracketed) { + skipWhitespace(); } + const name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces); - function parseTemplateTagTypeParameters() { - const pos = getNodePos(); - const typeParameters = []; - do { - skipWhitespace(); - const node = parseTemplateTagTypeParameter(); - if (node !== undefined) { - typeParameters.push(node); - } - skipWhitespaceOrAsterisk(); - } while (parseOptionalJsdoc(SyntaxKind.CommaToken)); - return createNodeArray(typeParameters, pos); + let defaultType: TypeNode | undefined; + if (isBracketed) { + skipWhitespace(); + parseExpected(SyntaxKind.EqualsToken); + defaultType = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); + parseExpected(SyntaxKind.CloseBracketToken); } - function parseTemplateTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocTemplateTag { - // The template tag looks like one of the following: - // @template T,U,V - // @template {Constraint} T - // - // According to the [closure docs](https://github.com/google/closure-compiler/wiki/Generic-Types#multiple-bounded-template-types): - // > Multiple bounded generics cannot be declared on the same line. For the sake of clarity, if multiple templates share the same - // > type bound they must be declared on separate lines. - // - // TODO: Determine whether we should enforce this in the checker. - // TODO: Consider moving the `constraint` to the first type parameter as we could then remove `getEffectiveConstraintOfTypeParameter`. - // TODO: Consider only parsing a single type parameter if there is a constraint. - const constraint = token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; - const typeParameters = parseTemplateTagTypeParameters(); - return finishNode(factory.createJSDocTemplateTag(tagName, constraint, typeParameters, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); + if (nodeIsMissing(name)) { + return undefined; } + return finishNode(factory.createTypeParameterDeclaration(name, /*constraint*/ undefined, defaultType), typeParameterPos); + } - function parseOptionalJsdoc(t: JSDocSyntaxKind): boolean { - if (token() === t) { - nextTokenJSDoc(); - return true; + function parseTemplateTagTypeParameters() { + const pos = getNodePos(); + const typeParameters = []; + do { + skipWhitespace(); + const node = parseTemplateTagTypeParameter(); + if (node !== undefined) { + typeParameters.push(node); } - return false; + skipWhitespaceOrAsterisk(); + } while (parseOptionalJsdoc(SyntaxKind.CommaToken)); + return createNodeArray(typeParameters, pos); + } + + function parseTemplateTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocTemplateTag { + // The template tag looks like one of the following: + // @template T,U,V + // @template {Constraint} T + // + // According to the [closure docs](https://github.com/google/closure-compiler/wiki/Generic-Types#multiple-bounded-template-types): + // > Multiple bounded generics cannot be declared on the same line. For the sake of clarity, if multiple templates share the same + // > type bound they must be declared on separate lines. + // + // TODO: Determine whether we should enforce this in the checker. + // TODO: Consider moving the `constraint` to the first type parameter as we could then remove `getEffectiveConstraintOfTypeParameter`. + // TODO: Consider only parsing a single type parameter if there is a constraint. + const constraint = token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; + const typeParameters = parseTemplateTagTypeParameters(); + return finishNode(factory.createJSDocTemplateTag(tagName, constraint, typeParameters, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); + } + + function parseOptionalJsdoc(t: JSDocSyntaxKind): boolean { + if (token() === t) { + nextTokenJSDoc(); + return true; } + return false; + } - function parseJSDocEntityName(): EntityName { - let entity: EntityName = parseJSDocIdentifierName(); + function parseJSDocEntityName(): EntityName { + let entity: EntityName = parseJSDocIdentifierName(); + if (parseOptional(SyntaxKind.OpenBracketToken)) { + parseExpected(SyntaxKind.CloseBracketToken); + // Note that y[] is accepted as an entity name, but the postfix brackets are not saved for checking. + // Technically usejsdoc.org requires them for specifying a property of a type equivalent to Array<{ x: ...}> + // but it's not worth it to enforce that restriction. + } + while (parseOptional(SyntaxKind.DotToken)) { + const name = parseJSDocIdentifierName(); if (parseOptional(SyntaxKind.OpenBracketToken)) { parseExpected(SyntaxKind.CloseBracketToken); - // Note that y[] is accepted as an entity name, but the postfix brackets are not saved for checking. - // Technically usejsdoc.org requires them for specifying a property of a type equivalent to Array<{ x: ...}> - // but it's not worth it to enforce that restriction. - } - while (parseOptional(SyntaxKind.DotToken)) { - const name = parseJSDocIdentifierName(); - if (parseOptional(SyntaxKind.OpenBracketToken)) { - parseExpected(SyntaxKind.CloseBracketToken); - } - entity = createQualifiedName(entity, name); } - return entity; + entity = createQualifiedName(entity, name); } + return entity; + } - function parseJSDocIdentifierName(message?: DiagnosticMessage): Identifier { - if (!tokenIsIdentifierOrKeyword(token())) { - return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ !message, message || Diagnostics.Identifier_expected); - } - - identifierCount++; - const pos = scanner.getTokenPos(); - const end = scanner.getTextPos(); - const originalKeywordKind = token(); - const text = internIdentifier(scanner.getTokenValue()); - const result = finishNode(factory.createIdentifier(text, /*typeArguments*/ undefined, originalKeywordKind), pos, end); - nextTokenJSDoc(); - return result; + function parseJSDocIdentifierName(message?: DiagnosticMessage): Identifier { + if (!tokenIsIdentifierOrKeyword(token())) { + return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ !message, message || Diagnostics.Identifier_expected); } + + identifierCount++; + const pos = scanner.getTokenPos(); + const end = scanner.getTextPos(); + const originalKeywordKind = token(); + const text = internIdentifier(scanner.getTokenValue()); + const result = finishNode(factory.createIdentifier(text, /*typeArguments*/ undefined, originalKeywordKind), pos, end); + nextTokenJSDoc(); + return result; } } } +} - namespace IncrementalParser { - export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean): SourceFile { - aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive); - - checkChangeRange(sourceFile, newText, textChangeRange, aggressiveChecks); - if (textChangeRangeIsUnchanged(textChangeRange)) { - // if the text didn't change, then we can just return our current source file as-is. - return sourceFile; - } +namespace IncrementalParser { + export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean): SourceFile { + aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive); - if (sourceFile.statements.length === 0) { - // If we don't have any statements in the current source file, then there's no real - // way to incrementally parse. So just do a full parse instead. - return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setParentNodes*/ true, sourceFile.scriptKind); - } + checkChangeRange(sourceFile, newText, textChangeRange, aggressiveChecks); + if (textChangeRangeIsUnchanged(textChangeRange)) { + // if the text didn't change, then we can just return our current source file as-is. + return sourceFile; + } - // Make sure we're not trying to incrementally update a source file more than once. Once - // we do an update the original source file is considered unusable from that point onwards. - // - // This is because we do incremental parsing in-place. i.e. we take nodes from the old - // tree and give them new positions and parents. From that point on, trusting the old - // tree at all is not possible as far too much of it may violate invariants. - const incrementalSourceFile = sourceFile as Node as IncrementalNode; - Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed); - incrementalSourceFile.hasBeenIncrementallyParsed = true; - Parser.fixupParentReferences(incrementalSourceFile); - const oldText = sourceFile.text; - const syntaxCursor = createSyntaxCursor(sourceFile); - - // Make the actual change larger so that we know to reparse anything whose lookahead - // might have intersected the change. - const changeRange = extendToAffectedRange(sourceFile, textChangeRange); - checkChangeRange(sourceFile, newText, changeRange, aggressiveChecks); - - // Ensure that extending the affected range only moved the start of the change range - // earlier in the file. - Debug.assert(changeRange.span.start <= textChangeRange.span.start); - Debug.assert(textSpanEnd(changeRange.span) === textSpanEnd(textChangeRange.span)); - Debug.assert(textSpanEnd(textChangeRangeNewSpan(changeRange)) === textSpanEnd(textChangeRangeNewSpan(textChangeRange))); - - // The is the amount the nodes after the edit range need to be adjusted. It can be - // positive (if the edit added characters), negative (if the edit deleted characters) - // or zero (if this was a pure overwrite with nothing added/removed). - const delta = textChangeRangeNewSpan(changeRange).length - changeRange.span.length; - - // If we added or removed characters during the edit, then we need to go and adjust all - // the nodes after the edit. Those nodes may move forward (if we inserted chars) or they - // may move backward (if we deleted chars). - // - // Doing this helps us out in two ways. First, it means that any nodes/tokens we want - // to reuse are already at the appropriate position in the new text. That way when we - // reuse them, we don't have to figure out if they need to be adjusted. Second, it makes - // it very easy to determine if we can reuse a node. If the node's position is at where - // we are in the text, then we can reuse it. Otherwise we can't. If the node's position - // is ahead of us, then we'll need to rescan tokens. If the node's position is behind - // us, then we'll need to skip it or crumble it as appropriate - // - // We will also adjust the positions of nodes that intersect the change range as well. - // By doing this, we ensure that all the positions in the old tree are consistent, not - // just the positions of nodes entirely before/after the change range. By being - // consistent, we can then easily map from positions to nodes in the old tree easily. - // - // Also, mark any syntax elements that intersect the changed span. We know, up front, - // that we cannot reuse these elements. - updateTokenPositionsAndMarkElements(incrementalSourceFile, - changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks); - - // Now that we've set up our internal incremental state just proceed and parse the - // source file in the normal fashion. When possible the parser will retrieve and - // reuse nodes from the old tree. - // - // Note: passing in 'true' for setNodeParents is very important. When incrementally - // parsing, we will be reusing nodes from the old tree, and placing it into new - // parents. If we don't set the parents now, we'll end up with an observably - // inconsistent tree. Setting the parents on the new tree should be very fast. We - // will immediately bail out of walking any subtrees when we can see that their parents - // are already correct. - const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, sourceFile.scriptKind); - result.commentDirectives = getNewCommentDirectives( - sourceFile.commentDirectives, - result.commentDirectives, - changeRange.span.start, - textSpanEnd(changeRange.span), - delta, - oldText, - newText, - aggressiveChecks - ); - result.impliedNodeFormat = sourceFile.impliedNodeFormat; - return result; + if (sourceFile.statements.length === 0) { + // If we don't have any statements in the current source file, then there's no real + // way to incrementally parse. So just do a full parse instead. + return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setParentNodes*/ true, sourceFile.scriptKind); } - function getNewCommentDirectives( - oldDirectives: CommentDirective[] | undefined, - newDirectives: CommentDirective[] | undefined, - changeStart: number, - changeRangeOldEnd: number, - delta: number, - oldText: string, - newText: string, - aggressiveChecks: boolean - ): CommentDirective[] | undefined { - if (!oldDirectives) return newDirectives; - let commentDirectives: CommentDirective[] | undefined; - let addedNewlyScannedDirectives = false; - for (const directive of oldDirectives) { - const { range, type } = directive; - // Range before the change - if (range.end < changeStart) { - commentDirectives = append(commentDirectives, directive); - } - else if (range.pos > changeRangeOldEnd) { - addNewlyScannedDirectives(); - // Node is entirely past the change range. We need to move both its pos and - // end, forward or backward appropriately. - const updatedDirective: CommentDirective = { - range: { pos: range.pos + delta, end: range.end + delta }, - type - }; - commentDirectives = append(commentDirectives, updatedDirective); - if (aggressiveChecks) { - Debug.assert(oldText.substring(range.pos, range.end) === newText.substring(updatedDirective.range.pos, updatedDirective.range.end)); - } - } - // Ignore ranges that fall in change range - } - addNewlyScannedDirectives(); - return commentDirectives; + // Make sure we're not trying to incrementally update a source file more than once. Once + // we do an update the original source file is considered unusable from that point onwards. + // + // This is because we do incremental parsing in-place. i.e. we take nodes from the old + // tree and give them new positions and parents. From that point on, trusting the old + // tree at all is not possible as far too much of it may violate invariants. + const incrementalSourceFile = sourceFile as Node as IncrementalNode; + Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed); + incrementalSourceFile.hasBeenIncrementallyParsed = true; + Parser.fixupParentReferences(incrementalSourceFile); + const oldText = sourceFile.text; + const syntaxCursor = createSyntaxCursor(sourceFile); + + // Make the actual change larger so that we know to reparse anything whose lookahead + // might have intersected the change. + const changeRange = extendToAffectedRange(sourceFile, textChangeRange); + checkChangeRange(sourceFile, newText, changeRange, aggressiveChecks); + + // Ensure that extending the affected range only moved the start of the change range + // earlier in the file. + Debug.assert(changeRange.span.start <= textChangeRange.span.start); + Debug.assert(textSpanEnd(changeRange.span) === textSpanEnd(textChangeRange.span)); + Debug.assert(textSpanEnd(textChangeRangeNewSpan(changeRange)) === textSpanEnd(textChangeRangeNewSpan(textChangeRange))); + + // The is the amount the nodes after the edit range need to be adjusted. It can be + // positive (if the edit added characters), negative (if the edit deleted characters) + // or zero (if this was a pure overwrite with nothing added/removed). + const delta = textChangeRangeNewSpan(changeRange).length - changeRange.span.length; + + // If we added or removed characters during the edit, then we need to go and adjust all + // the nodes after the edit. Those nodes may move forward (if we inserted chars) or they + // may move backward (if we deleted chars). + // + // Doing this helps us out in two ways. First, it means that any nodes/tokens we want + // to reuse are already at the appropriate position in the new text. That way when we + // reuse them, we don't have to figure out if they need to be adjusted. Second, it makes + // it very easy to determine if we can reuse a node. If the node's position is at where + // we are in the text, then we can reuse it. Otherwise we can't. If the node's position + // is ahead of us, then we'll need to rescan tokens. If the node's position is behind + // us, then we'll need to skip it or crumble it as appropriate + // + // We will also adjust the positions of nodes that intersect the change range as well. + // By doing this, we ensure that all the positions in the old tree are consistent, not + // just the positions of nodes entirely before/after the change range. By being + // consistent, we can then easily map from positions to nodes in the old tree easily. + // + // Also, mark any syntax elements that intersect the changed span. We know, up front, + // that we cannot reuse these elements. + updateTokenPositionsAndMarkElements(incrementalSourceFile, + changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks); + + // Now that we've set up our internal incremental state just proceed and parse the + // source file in the normal fashion. When possible the parser will retrieve and + // reuse nodes from the old tree. + // + // Note: passing in 'true' for setNodeParents is very important. When incrementally + // parsing, we will be reusing nodes from the old tree, and placing it into new + // parents. If we don't set the parents now, we'll end up with an observably + // inconsistent tree. Setting the parents on the new tree should be very fast. We + // will immediately bail out of walking any subtrees when we can see that their parents + // are already correct. + const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, sourceFile.scriptKind); + result.commentDirectives = getNewCommentDirectives( + sourceFile.commentDirectives, + result.commentDirectives, + changeRange.span.start, + textSpanEnd(changeRange.span), + delta, + oldText, + newText, + aggressiveChecks + ); + result.impliedNodeFormat = sourceFile.impliedNodeFormat; + return result; + } - function addNewlyScannedDirectives() { - if (addedNewlyScannedDirectives) return; - addedNewlyScannedDirectives = true; - if (!commentDirectives) { - commentDirectives = newDirectives; - } - else if (newDirectives) { - commentDirectives.push(...newDirectives); + function getNewCommentDirectives( + oldDirectives: CommentDirective[] | undefined, + newDirectives: CommentDirective[] | undefined, + changeStart: number, + changeRangeOldEnd: number, + delta: number, + oldText: string, + newText: string, + aggressiveChecks: boolean + ): CommentDirective[] | undefined { + if (!oldDirectives) return newDirectives; + let commentDirectives: CommentDirective[] | undefined; + let addedNewlyScannedDirectives = false; + for (const directive of oldDirectives) { + const { range, type } = directive; + // Range before the change + if (range.end < changeStart) { + commentDirectives = append(commentDirectives, directive); + } + else if (range.pos > changeRangeOldEnd) { + addNewlyScannedDirectives(); + // Node is entirely past the change range. We need to move both its pos and + // end, forward or backward appropriately. + const updatedDirective: CommentDirective = { + range: { pos: range.pos + delta, end: range.end + delta }, + type + }; + commentDirectives = append(commentDirectives, updatedDirective); + if (aggressiveChecks) { + Debug.assert(oldText.substring(range.pos, range.end) === newText.substring(updatedDirective.range.pos, updatedDirective.range.end)); } } + // Ignore ranges that fall in change range } + addNewlyScannedDirectives(); + return commentDirectives; - function moveElementEntirelyPastChangeRange(element: IncrementalElement, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { - if (isArray) { - visitArray(element as IncrementalNodeArray); + function addNewlyScannedDirectives() { + if (addedNewlyScannedDirectives) return; + addedNewlyScannedDirectives = true; + if (!commentDirectives) { + commentDirectives = newDirectives; } - else { - visitNode(element as IncrementalNode); + else if (newDirectives) { + commentDirectives.push(...newDirectives); } - return; + } + } - function visitNode(node: IncrementalNode) { - let text = ""; - if (aggressiveChecks && shouldCheckNode(node)) { - text = oldText.substring(node.pos, node.end); - } + function moveElementEntirelyPastChangeRange(element: IncrementalElement, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { + if (isArray) { + visitArray(element as IncrementalNodeArray); + } + else { + visitNode(element as IncrementalNode); + } + return; - // Ditch any existing LS children we may have created. This way we can avoid - // moving them forward. - if (node._children) { - node._children = undefined; - } + function visitNode(node: IncrementalNode) { + let text = ""; + if (aggressiveChecks && shouldCheckNode(node)) { + text = oldText.substring(node.pos, node.end); + } - setTextRangePosEnd(node, node.pos + delta, node.end + delta); + // Ditch any existing LS children we may have created. This way we can avoid + // moving them forward. + if (node._children) { + node._children = undefined; + } - if (aggressiveChecks && shouldCheckNode(node)) { - Debug.assert(text === newText.substring(node.pos, node.end)); - } + setTextRangePosEnd(node, node.pos + delta, node.end + delta); - forEachChild(node, visitNode, visitArray); - if (hasJSDocNodes(node)) { - for (const jsDocComment of node.jsDoc!) { - visitNode(jsDocComment as Node as IncrementalNode); - } - } - checkNodePositions(node, aggressiveChecks); + if (aggressiveChecks && shouldCheckNode(node)) { + Debug.assert(text === newText.substring(node.pos, node.end)); } - function visitArray(array: IncrementalNodeArray) { - array._children = undefined; - setTextRangePosEnd(array, array.pos + delta, array.end + delta); - - for (const node of array) { - visitNode(node); + forEachChild(node, visitNode, visitArray); + if (hasJSDocNodes(node)) { + for (const jsDocComment of node.jsDoc!) { + visitNode(jsDocComment as Node as IncrementalNode); } } + checkNodePositions(node, aggressiveChecks); } - function shouldCheckNode(node: Node) { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.Identifier: - return true; + function visitArray(array: IncrementalNodeArray) { + array._children = undefined; + setTextRangePosEnd(array, array.pos + delta, array.end + delta); + + for (const node of array) { + visitNode(node); } + } + } - return false; + function shouldCheckNode(node: Node) { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.Identifier: + return true; } - function adjustIntersectingElement(element: IncrementalElement, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number) { - Debug.assert(element.end >= changeStart, "Adjusting an element that was entirely before the change range"); - Debug.assert(element.pos <= changeRangeOldEnd, "Adjusting an element that was entirely after the change range"); - Debug.assert(element.pos <= element.end); + return false; + } - // We have an element that intersects the change range in some way. It may have its - // start, or its end (or both) in the changed range. We want to adjust any part - // that intersects such that the final tree is in a consistent state. i.e. all - // children have spans within the span of their parent, and all siblings are ordered - // properly. + function adjustIntersectingElement(element: IncrementalElement, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number) { + Debug.assert(element.end >= changeStart, "Adjusting an element that was entirely before the change range"); + Debug.assert(element.pos <= changeRangeOldEnd, "Adjusting an element that was entirely after the change range"); + Debug.assert(element.pos <= element.end); - // We may need to update both the 'pos' and the 'end' of the element. + // We have an element that intersects the change range in some way. It may have its + // start, or its end (or both) in the changed range. We want to adjust any part + // that intersects such that the final tree is in a consistent state. i.e. all + // children have spans within the span of their parent, and all siblings are ordered + // properly. - // If the 'pos' is before the start of the change, then we don't need to touch it. - // If it isn't, then the 'pos' must be inside the change. How we update it will - // depend if delta is positive or negative. If delta is positive then we have - // something like: - // - // -------------------AAA----------------- - // -------------------BBBCCCCCCC----------------- - // - // In this case, we consider any node that started in the change range to still be - // starting at the same position. - // - // however, if the delta is negative, then we instead have something like this: - // - // -------------------XXXYYYYYYY----------------- - // -------------------ZZZ----------------- - // - // In this case, any element that started in the 'X' range will keep its position. - // However any element that started after that will have their pos adjusted to be - // at the end of the new range. i.e. any node that started in the 'Y' range will - // be adjusted to have their start at the end of the 'Z' range. - // - // The element will keep its position if possible. Or Move backward to the new-end - // if it's in the 'Y' range. - const pos = Math.min(element.pos, changeRangeNewEnd); - - // If the 'end' is after the change range, then we always adjust it by the delta - // amount. However, if the end is in the change range, then how we adjust it - // will depend on if delta is positive or negative. If delta is positive then we - // have something like: - // - // -------------------AAA----------------- - // -------------------BBBCCCCCCC----------------- - // - // In this case, we consider any node that ended inside the change range to keep its - // end position. - // - // however, if the delta is negative, then we instead have something like this: - // - // -------------------XXXYYYYYYY----------------- - // -------------------ZZZ----------------- - // - // In this case, any element that ended in the 'X' range will keep its position. - // However any element that ended after that will have their pos adjusted to be - // at the end of the new range. i.e. any node that ended in the 'Y' range will - // be adjusted to have their end at the end of the 'Z' range. - const end = element.end >= changeRangeOldEnd ? - // Element ends after the change range. Always adjust the end pos. - element.end + delta : - // Element ends in the change range. The element will keep its position if - // possible. Or Move backward to the new-end if it's in the 'Y' range. - Math.min(element.end, changeRangeNewEnd); - - Debug.assert(pos <= end); - if (element.parent) { - Debug.assertGreaterThanOrEqual(pos, element.parent.pos); - Debug.assertLessThanOrEqual(end, element.parent.end); - } - - setTextRangePosEnd(element, pos, end); - } - - function checkNodePositions(node: Node, aggressiveChecks: boolean) { - if (aggressiveChecks) { - let pos = node.pos; - const visitNode = (child: Node) => { - Debug.assert(child.pos >= pos); - pos = child.end; - }; - if (hasJSDocNodes(node)) { - for (const jsDocComment of node.jsDoc!) { - visitNode(jsDocComment); - } + // We may need to update both the 'pos' and the 'end' of the element. + + // If the 'pos' is before the start of the change, then we don't need to touch it. + // If it isn't, then the 'pos' must be inside the change. How we update it will + // depend if delta is positive or negative. If delta is positive then we have + // something like: + // + // -------------------AAA----------------- + // -------------------BBBCCCCCCC----------------- + // + // In this case, we consider any node that started in the change range to still be + // starting at the same position. + // + // however, if the delta is negative, then we instead have something like this: + // + // -------------------XXXYYYYYYY----------------- + // -------------------ZZZ----------------- + // + // In this case, any element that started in the 'X' range will keep its position. + // However any element that started after that will have their pos adjusted to be + // at the end of the new range. i.e. any node that started in the 'Y' range will + // be adjusted to have their start at the end of the 'Z' range. + // + // The element will keep its position if possible. Or Move backward to the new-end + // if it's in the 'Y' range. + const pos = Math.min(element.pos, changeRangeNewEnd); + + // If the 'end' is after the change range, then we always adjust it by the delta + // amount. However, if the end is in the change range, then how we adjust it + // will depend on if delta is positive or negative. If delta is positive then we + // have something like: + // + // -------------------AAA----------------- + // -------------------BBBCCCCCCC----------------- + // + // In this case, we consider any node that ended inside the change range to keep its + // end position. + // + // however, if the delta is negative, then we instead have something like this: + // + // -------------------XXXYYYYYYY----------------- + // -------------------ZZZ----------------- + // + // In this case, any element that ended in the 'X' range will keep its position. + // However any element that ended after that will have their pos adjusted to be + // at the end of the new range. i.e. any node that ended in the 'Y' range will + // be adjusted to have their end at the end of the 'Z' range. + const end = element.end >= changeRangeOldEnd ? + // Element ends after the change range. Always adjust the end pos. + element.end + delta : + // Element ends in the change range. The element will keep its position if + // possible. Or Move backward to the new-end if it's in the 'Y' range. + Math.min(element.end, changeRangeNewEnd); + + Debug.assert(pos <= end); + if (element.parent) { + Debug.assertGreaterThanOrEqual(pos, element.parent.pos); + Debug.assertLessThanOrEqual(end, element.parent.end); + } + + setTextRangePosEnd(element, pos, end); + } + + function checkNodePositions(node: Node, aggressiveChecks: boolean) { + if (aggressiveChecks) { + let pos = node.pos; + const visitNode = (child: Node) => { + Debug.assert(child.pos >= pos); + pos = child.end; + }; + if (hasJSDocNodes(node)) { + for (const jsDocComment of node.jsDoc!) { + visitNode(jsDocComment); } - forEachChild(node, visitNode); - Debug.assert(pos <= node.end); } + forEachChild(node, visitNode); + Debug.assert(pos <= node.end); } + } - function updateTokenPositionsAndMarkElements( - sourceFile: IncrementalNode, - changeStart: number, - changeRangeOldEnd: number, - changeRangeNewEnd: number, - delta: number, - oldText: string, - newText: string, - aggressiveChecks: boolean): void { + function updateTokenPositionsAndMarkElements( + sourceFile: IncrementalNode, + changeStart: number, + changeRangeOldEnd: number, + changeRangeNewEnd: number, + delta: number, + oldText: string, + newText: string, + aggressiveChecks: boolean): void { + + visitNode(sourceFile); + return; - visitNode(sourceFile); - return; + function visitNode(child: IncrementalNode) { + Debug.assert(child.pos <= child.end); + if (child.pos > changeRangeOldEnd) { + // Node is entirely past the change range. We need to move both its pos and + // end, forward or backward appropriately. + moveElementEntirelyPastChangeRange(child, /*isArray*/ false, delta, oldText, newText, aggressiveChecks); + return; + } - function visitNode(child: IncrementalNode) { - Debug.assert(child.pos <= child.end); - if (child.pos > changeRangeOldEnd) { - // Node is entirely past the change range. We need to move both its pos and - // end, forward or backward appropriately. - moveElementEntirelyPastChangeRange(child, /*isArray*/ false, delta, oldText, newText, aggressiveChecks); - return; - } + // Check if the element intersects the change range. If it does, then it is not + // reusable. Also, we'll need to recurse to see what constituent portions we may + // be able to use. + const fullEnd = child.end; + if (fullEnd >= changeStart) { + child.intersectsChange = true; + child._children = undefined; - // Check if the element intersects the change range. If it does, then it is not - // reusable. Also, we'll need to recurse to see what constituent portions we may - // be able to use. - const fullEnd = child.end; - if (fullEnd >= changeStart) { - child.intersectsChange = true; - child._children = undefined; - - // Adjust the pos or end (or both) of the intersecting element accordingly. - adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); - forEachChild(child, visitNode, visitArray); - if (hasJSDocNodes(child)) { - for (const jsDocComment of child.jsDoc!) { - visitNode(jsDocComment as Node as IncrementalNode); - } + // Adjust the pos or end (or both) of the intersecting element accordingly. + adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); + forEachChild(child, visitNode, visitArray); + if (hasJSDocNodes(child)) { + for (const jsDocComment of child.jsDoc!) { + visitNode(jsDocComment as Node as IncrementalNode); } - checkNodePositions(child, aggressiveChecks); - return; } + checkNodePositions(child, aggressiveChecks); + return; + } + + // Otherwise, the node is entirely before the change range. No need to do anything with it. + Debug.assert(fullEnd < changeStart); + } - // Otherwise, the node is entirely before the change range. No need to do anything with it. - Debug.assert(fullEnd < changeStart); + function visitArray(array: IncrementalNodeArray) { + Debug.assert(array.pos <= array.end); + if (array.pos > changeRangeOldEnd) { + // Array is entirely after the change range. We need to move it, and move any of + // its children. + moveElementEntirelyPastChangeRange(array, /*isArray*/ true, delta, oldText, newText, aggressiveChecks); + return; } - function visitArray(array: IncrementalNodeArray) { - Debug.assert(array.pos <= array.end); - if (array.pos > changeRangeOldEnd) { - // Array is entirely after the change range. We need to move it, and move any of - // its children. - moveElementEntirelyPastChangeRange(array, /*isArray*/ true, delta, oldText, newText, aggressiveChecks); - return; - } + // Check if the element intersects the change range. If it does, then it is not + // reusable. Also, we'll need to recurse to see what constituent portions we may + // be able to use. + const fullEnd = array.end; + if (fullEnd >= changeStart) { + array.intersectsChange = true; + array._children = undefined; - // Check if the element intersects the change range. If it does, then it is not - // reusable. Also, we'll need to recurse to see what constituent portions we may - // be able to use. - const fullEnd = array.end; - if (fullEnd >= changeStart) { - array.intersectsChange = true; - array._children = undefined; - - // Adjust the pos or end (or both) of the intersecting array accordingly. - adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); - for (const node of array) { - visitNode(node); - } - return; + // Adjust the pos or end (or both) of the intersecting array accordingly. + adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); + for (const node of array) { + visitNode(node); } - - // Otherwise, the array is entirely before the change range. No need to do anything with it. - Debug.assert(fullEnd < changeStart); + return; } - } - - function extendToAffectedRange(sourceFile: SourceFile, changeRange: TextChangeRange): TextChangeRange { - // Consider the following code: - // void foo() { /; } - // - // If the text changes with an insertion of / just before the semicolon then we end up with: - // void foo() { //; } - // - // If we were to just use the changeRange a is, then we would not rescan the { token - // (as it does not intersect the actual original change range). Because an edit may - // change the token touching it, we actually need to look back *at least* one token so - // that the prior token sees that change. - const maxLookahead = 1; - let start = changeRange.span.start; + // Otherwise, the array is entirely before the change range. No need to do anything with it. + Debug.assert(fullEnd < changeStart); + } + } - // the first iteration aligns us with the change start. subsequent iteration move us to - // the left by maxLookahead tokens. We only need to do this as long as we're not at the - // start of the tree. - for (let i = 0; start > 0 && i <= maxLookahead; i++) { - const nearestNode = findNearestNodeStartingBeforeOrAtPosition(sourceFile, start); - Debug.assert(nearestNode.pos <= start); - const position = nearestNode.pos; + function extendToAffectedRange(sourceFile: SourceFile, changeRange: TextChangeRange): TextChangeRange { + // Consider the following code: + // void foo() { /; } + // + // If the text changes with an insertion of / just before the semicolon then we end up with: + // void foo() { //; } + // + // If we were to just use the changeRange a is, then we would not rescan the { token + // (as it does not intersect the actual original change range). Because an edit may + // change the token touching it, we actually need to look back *at least* one token so + // that the prior token sees that change. + const maxLookahead = 1; - start = Math.max(0, position - 1); - } + let start = changeRange.span.start; - const finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span)); - const finalLength = changeRange.newLength + (changeRange.span.start - start); + // the first iteration aligns us with the change start. subsequent iteration move us to + // the left by maxLookahead tokens. We only need to do this as long as we're not at the + // start of the tree. + for (let i = 0; start > 0 && i <= maxLookahead; i++) { + const nearestNode = findNearestNodeStartingBeforeOrAtPosition(sourceFile, start); + Debug.assert(nearestNode.pos <= start); + const position = nearestNode.pos; - return createTextChangeRange(finalSpan, finalLength); + start = Math.max(0, position - 1); } - function findNearestNodeStartingBeforeOrAtPosition(sourceFile: SourceFile, position: number): Node { - let bestResult: Node = sourceFile; - let lastNodeEntirelyBeforePosition: Node | undefined; + const finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span)); + const finalLength = changeRange.newLength + (changeRange.span.start - start); - forEachChild(sourceFile, visit); + return createTextChangeRange(finalSpan, finalLength); + } - if (lastNodeEntirelyBeforePosition) { - const lastChildOfLastEntireNodeBeforePosition = getLastDescendant(lastNodeEntirelyBeforePosition); - if (lastChildOfLastEntireNodeBeforePosition.pos > bestResult.pos) { - bestResult = lastChildOfLastEntireNodeBeforePosition; - } - } + function findNearestNodeStartingBeforeOrAtPosition(sourceFile: SourceFile, position: number): Node { + let bestResult: Node = sourceFile; + let lastNodeEntirelyBeforePosition: Node | undefined; - return bestResult; + forEachChild(sourceFile, visit); - function getLastDescendant(node: Node): Node { - while (true) { - const lastChild = getLastChild(node); - if (lastChild) { - node = lastChild; - } - else { - return node; - } - } + if (lastNodeEntirelyBeforePosition) { + const lastChildOfLastEntireNodeBeforePosition = getLastDescendant(lastNodeEntirelyBeforePosition); + if (lastChildOfLastEntireNodeBeforePosition.pos > bestResult.pos) { + bestResult = lastChildOfLastEntireNodeBeforePosition; } + } - function visit(child: Node) { - if (nodeIsMissing(child)) { - // Missing nodes are effectively invisible to us. We never even consider them - // When trying to find the nearest node before us. - return; - } - - // If the child intersects this position, then this node is currently the nearest - // node that starts before the position. - if (child.pos <= position) { - if (child.pos >= bestResult.pos) { - // This node starts before the position, and is closer to the position than - // the previous best node we found. It is now the new best node. - bestResult = child; - } + return bestResult; - // Now, the node may overlap the position, or it may end entirely before the - // position. If it overlaps with the position, then either it, or one of its - // children must be the nearest node before the position. So we can just - // recurse into this child to see if we can find something better. - if (position < child.end) { - // The nearest node is either this child, or one of the children inside - // of it. We've already marked this child as the best so far. Recurse - // in case one of the children is better. - forEachChild(child, visit); - - // Once we look at the children of this node, then there's no need to - // continue any further. - return true; - } - else { - Debug.assert(child.end <= position); - // The child ends entirely before this position. Say you have the following - // (where $ is the position) - // - // ? $ : <...> <...> - // - // We would want to find the nearest preceding node in "complex expr 2". - // To support that, we keep track of this node, and once we're done searching - // for a best node, we recurse down this node to see if we can find a good - // result in it. - // - // This approach allows us to quickly skip over nodes that are entirely - // before the position, while still allowing us to find any nodes in the - // last one that might be what we want. - lastNodeEntirelyBeforePosition = child; - } + function getLastDescendant(node: Node): Node { + while (true) { + const lastChild = getLastChild(node); + if (lastChild) { + node = lastChild; } else { - Debug.assert(child.pos > position); - // We're now at a node that is entirely past the position we're searching for. - // This node (and all following nodes) could never contribute to the result, - // so just skip them by returning 'true' here. - return true; + return node; } } } - function checkChangeRange(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean) { - const oldText = sourceFile.text; - if (textChangeRange) { - Debug.assert((oldText.length - textChangeRange.span.length + textChangeRange.newLength) === newText.length); + function visit(child: Node) { + if (nodeIsMissing(child)) { + // Missing nodes are effectively invisible to us. We never even consider them + // When trying to find the nearest node before us. + return; + } + + // If the child intersects this position, then this node is currently the nearest + // node that starts before the position. + if (child.pos <= position) { + if (child.pos >= bestResult.pos) { + // This node starts before the position, and is closer to the position than + // the previous best node we found. It is now the new best node. + bestResult = child; + } - if (aggressiveChecks || Debug.shouldAssert(AssertionLevel.VeryAggressive)) { - const oldTextPrefix = oldText.substr(0, textChangeRange.span.start); - const newTextPrefix = newText.substr(0, textChangeRange.span.start); - Debug.assert(oldTextPrefix === newTextPrefix); + // Now, the node may overlap the position, or it may end entirely before the + // position. If it overlaps with the position, then either it, or one of its + // children must be the nearest node before the position. So we can just + // recurse into this child to see if we can find something better. + if (position < child.end) { + // The nearest node is either this child, or one of the children inside + // of it. We've already marked this child as the best so far. Recurse + // in case one of the children is better. + forEachChild(child, visit); - const oldTextSuffix = oldText.substring(textSpanEnd(textChangeRange.span), oldText.length); - const newTextSuffix = newText.substring(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.length); - Debug.assert(oldTextSuffix === newTextSuffix); + // Once we look at the children of this node, then there's no need to + // continue any further. + return true; + } + else { + Debug.assert(child.end <= position); + // The child ends entirely before this position. Say you have the following + // (where $ is the position) + // + // ? $ : <...> <...> + // + // We would want to find the nearest preceding node in "complex expr 2". + // To support that, we keep track of this node, and once we're done searching + // for a best node, we recurse down this node to see if we can find a good + // result in it. + // + // This approach allows us to quickly skip over nodes that are entirely + // before the position, while still allowing us to find any nodes in the + // last one that might be what we want. + lastNodeEntirelyBeforePosition = child; } } + else { + Debug.assert(child.pos > position); + // We're now at a node that is entirely past the position we're searching for. + // This node (and all following nodes) could never contribute to the result, + // so just skip them by returning 'true' here. + return true; + } } + } - interface IncrementalElement extends ReadonlyTextRange { - readonly parent: Node; - intersectsChange: boolean; - length?: number; - _children: Node[] | undefined; - } + function checkChangeRange(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean) { + const oldText = sourceFile.text; + if (textChangeRange) { + Debug.assert((oldText.length - textChangeRange.span.length + textChangeRange.newLength) === newText.length); - export interface IncrementalNode extends Node, IncrementalElement { - hasBeenIncrementallyParsed: boolean; - } + if (aggressiveChecks || Debug.shouldAssert(AssertionLevel.VeryAggressive)) { + const oldTextPrefix = oldText.substr(0, textChangeRange.span.start); + const newTextPrefix = newText.substr(0, textChangeRange.span.start); + Debug.assert(oldTextPrefix === newTextPrefix); - interface IncrementalNodeArray extends NodeArray, IncrementalElement { - length: number; + const oldTextSuffix = oldText.substring(textSpanEnd(textChangeRange.span), oldText.length); + const newTextSuffix = newText.substring(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.length); + Debug.assert(oldTextSuffix === newTextSuffix); + } } + } - // Allows finding nodes in the source file at a certain position in an efficient manner. - // The implementation takes advantage of the calling pattern it knows the parser will - // make in order to optimize finding nodes as quickly as possible. - export interface SyntaxCursor { - currentNode(position: number): IncrementalNode; - } + interface IncrementalElement extends ReadonlyTextRange { + readonly parent: Node; + intersectsChange: boolean; + length?: number; + _children: Node[] | undefined; + } - export function createSyntaxCursor(sourceFile: SourceFile): SyntaxCursor { - let currentArray: NodeArray = sourceFile.statements; - let currentArrayIndex = 0; + export interface IncrementalNode extends Node, IncrementalElement { + hasBeenIncrementallyParsed: boolean; + } - Debug.assert(currentArrayIndex < currentArray.length); - let current = currentArray[currentArrayIndex]; - let lastQueriedPosition = InvalidPosition.Value; + interface IncrementalNodeArray extends NodeArray, IncrementalElement { + length: number; + } - return { - currentNode(position: number) { - // Only compute the current node if the position is different than the last time - // we were asked. The parser commonly asks for the node at the same position - // twice. Once to know if can read an appropriate list element at a certain point, - // and then to actually read and consume the node. - if (position !== lastQueriedPosition) { - // Much of the time the parser will need the very next node in the array that - // we just returned a node from.So just simply check for that case and move - // forward in the array instead of searching for the node again. - if (current && current.end === position && currentArrayIndex < (currentArray.length - 1)) { - currentArrayIndex++; - current = currentArray[currentArrayIndex]; - } + // Allows finding nodes in the source file at a certain position in an efficient manner. + // The implementation takes advantage of the calling pattern it knows the parser will + // make in order to optimize finding nodes as quickly as possible. + export interface SyntaxCursor { + currentNode(position: number): IncrementalNode; + } - // If we don't have a node, or the node we have isn't in the right position, - // then try to find a viable node at the position requested. - if (!current || current.pos !== position) { - findHighestListElementThatStartsAtPosition(position); - } + export function createSyntaxCursor(sourceFile: SourceFile): SyntaxCursor { + let currentArray: NodeArray = sourceFile.statements; + let currentArrayIndex = 0; + + Debug.assert(currentArrayIndex < currentArray.length); + let current = currentArray[currentArrayIndex]; + let lastQueriedPosition = InvalidPosition.Value; + + return { + currentNode(position: number) { + // Only compute the current node if the position is different than the last time + // we were asked. The parser commonly asks for the node at the same position + // twice. Once to know if can read an appropriate list element at a certain point, + // and then to actually read and consume the node. + if (position !== lastQueriedPosition) { + // Much of the time the parser will need the very next node in the array that + // we just returned a node from.So just simply check for that case and move + // forward in the array instead of searching for the node again. + if (current && current.end === position && currentArrayIndex < (currentArray.length - 1)) { + currentArrayIndex++; + current = currentArray[currentArrayIndex]; } - // Cache this query so that we don't do any extra work if the parser calls back - // into us. Note: this is very common as the parser will make pairs of calls like - // 'isListElement -> parseListElement'. If we were unable to find a node when - // called with 'isListElement', we don't want to redo the work when parseListElement - // is called immediately after. - lastQueriedPosition = position; - - // Either we don'd have a node, or we have a node at the position being asked for. - Debug.assert(!current || current.pos === position); - return current as IncrementalNode; + // If we don't have a node, or the node we have isn't in the right position, + // then try to find a viable node at the position requested. + if (!current || current.pos !== position) { + findHighestListElementThatStartsAtPosition(position); + } } - }; - // Finds the highest element in the tree we can find that starts at the provided position. - // The element must be a direct child of some node list in the tree. This way after we - // return it, we can easily return its next sibling in the list. - function findHighestListElementThatStartsAtPosition(position: number) { - // Clear out any cached state about the last node we found. - currentArray = undefined!; - currentArrayIndex = InvalidPosition.Value; - current = undefined!; - - // Recurse into the source file to find the highest node at this position. - forEachChild(sourceFile, visitNode, visitArray); - return; + // Cache this query so that we don't do any extra work if the parser calls back + // into us. Note: this is very common as the parser will make pairs of calls like + // 'isListElement -> parseListElement'. If we were unable to find a node when + // called with 'isListElement', we don't want to redo the work when parseListElement + // is called immediately after. + lastQueriedPosition = position; + + // Either we don'd have a node, or we have a node at the position being asked for. + Debug.assert(!current || current.pos === position); + return current as IncrementalNode; + } + }; - function visitNode(node: Node) { - if (position >= node.pos && position < node.end) { - // Position was within this node. Keep searching deeper to find the node. - forEachChild(node, visitNode, visitArray); + // Finds the highest element in the tree we can find that starts at the provided position. + // The element must be a direct child of some node list in the tree. This way after we + // return it, we can easily return its next sibling in the list. + function findHighestListElementThatStartsAtPosition(position: number) { + // Clear out any cached state about the last node we found. + currentArray = undefined!; + currentArrayIndex = InvalidPosition.Value; + current = undefined!; + + // Recurse into the source file to find the highest node at this position. + forEachChild(sourceFile, visitNode, visitArray); + return; - // don't proceed any further in the search. - return true; - } + function visitNode(node: Node) { + if (position >= node.pos && position < node.end) { + // Position was within this node. Keep searching deeper to find the node. + forEachChild(node, visitNode, visitArray); - // position wasn't in this node, have to keep searching. - return false; + // don't proceed any further in the search. + return true; } - function visitArray(array: NodeArray) { - if (position >= array.pos && position < array.end) { - // position was in this array. Search through this array to see if we find a - // viable element. - for (let i = 0; i < array.length; i++) { - const child = array[i]; - if (child) { - if (child.pos === position) { - // Found the right node. We're done. - currentArray = array; - currentArrayIndex = i; - current = child; + // position wasn't in this node, have to keep searching. + return false; + } + + function visitArray(array: NodeArray) { + if (position >= array.pos && position < array.end) { + // position was in this array. Search through this array to see if we find a + // viable element. + for (let i = 0; i < array.length; i++) { + const child = array[i]; + if (child) { + if (child.pos === position) { + // Found the right node. We're done. + currentArray = array; + currentArrayIndex = i; + current = child; + return true; + } + else { + if (child.pos < position && position < child.end) { + // Position in somewhere within this child. Search in it and + // stop searching in this array. + forEachChild(child, visitNode, visitArray); return true; } - else { - if (child.pos < position && position < child.end) { - // Position in somewhere within this child. Search in it and - // stop searching in this array. - forEachChild(child, visitNode, visitArray); - return true; - } - } } } } - - // position wasn't in this array, have to keep searching. - return false; } - } - } - const enum InvalidPosition { - Value = -1 + // position wasn't in this array, have to keep searching. + return false; + } } } - /** @internal */ - export function isDeclarationFileName(fileName: string): boolean { - return fileExtensionIsOneOf(fileName, [Extension.Dts, Extension.Dmts, Extension.Dcts]); + const enum InvalidPosition { + Value = -1 } +} - /*@internal*/ - export interface PragmaContext { - languageVersion: ScriptTarget; - pragmas?: PragmaMap; - checkJsDirective?: CheckJsDirective; - referencedFiles: FileReference[]; - typeReferenceDirectives: FileReference[]; - libReferenceDirectives: FileReference[]; - amdDependencies: AmdDependency[]; - hasNoDefaultLib?: boolean; - moduleName?: string; - } +/** @internal */ +export function isDeclarationFileName(fileName: string): boolean { + return fileExtensionIsOneOf(fileName, [Extension.Dts, Extension.Dmts, Extension.Dcts]); +} - /*@internal*/ - export function processCommentPragmas(context: PragmaContext, sourceText: string): void { - const pragmas: PragmaPseudoMapEntry[] = []; +/*@internal*/ +export interface PragmaContext { + languageVersion: ScriptTarget; + pragmas?: PragmaMap; + checkJsDirective?: CheckJsDirective; + referencedFiles: FileReference[]; + typeReferenceDirectives: FileReference[]; + libReferenceDirectives: FileReference[]; + amdDependencies: AmdDependency[]; + hasNoDefaultLib?: boolean; + moduleName?: string; +} - for (const range of getLeadingCommentRanges(sourceText, 0) || emptyArray) { - const comment = sourceText.substring(range.pos, range.end); - extractPragmas(pragmas, range, comment); - } +/*@internal*/ +export function processCommentPragmas(context: PragmaContext, sourceText: string): void { + const pragmas: PragmaPseudoMapEntry[] = []; - context.pragmas = new Map() as PragmaMap; - for (const pragma of pragmas) { - if (context.pragmas.has(pragma.name)) { - const currentValue = context.pragmas.get(pragma.name); - if (currentValue instanceof Array) { - currentValue.push(pragma.args); - } - else { - context.pragmas.set(pragma.name, [currentValue, pragma.args]); - } - continue; - } - context.pragmas.set(pragma.name, pragma.args); - } - } - - /*@internal*/ - type PragmaDiagnosticReporter = (pos: number, length: number, message: DiagnosticMessage) => void; - - /*@internal*/ - export function processPragmasIntoFields(context: PragmaContext, reportDiagnostic: PragmaDiagnosticReporter): void { - context.checkJsDirective = undefined; - context.referencedFiles = []; - context.typeReferenceDirectives = []; - context.libReferenceDirectives = []; - context.amdDependencies = []; - context.hasNoDefaultLib = false; - context.pragmas!.forEach((entryOrList, key) => { // TODO: GH#18217 - // TODO: The below should be strongly type-guarded and not need casts/explicit annotations, since entryOrList is related to - // key and key is constrained to a union; but it's not (see GH#21483 for at least partial fix) :( - switch (key) { - case "reference": { - const referencedFiles = context.referencedFiles; - const typeReferenceDirectives = context.typeReferenceDirectives; - const libReferenceDirectives = context.libReferenceDirectives; - forEach(toArray(entryOrList) as PragmaPseudoMap["reference"][], arg => { - const { types, lib, path } = arg.arguments; - if (arg.arguments["no-default-lib"]) { - context.hasNoDefaultLib = true; - } - else if (types) { - typeReferenceDirectives.push({ pos: types.pos, end: types.end, fileName: types.value }); - } - else if (lib) { - libReferenceDirectives.push({ pos: lib.pos, end: lib.end, fileName: lib.value }); - } - else if (path) { - referencedFiles.push({ pos: path.pos, end: path.end, fileName: path.value }); - } - else { - reportDiagnostic(arg.range.pos, arg.range.end - arg.range.pos, Diagnostics.Invalid_reference_directive_syntax); - } - }); - break; - } - case "amd-dependency": { - context.amdDependencies = map( - toArray(entryOrList) as PragmaPseudoMap["amd-dependency"][], - x => ({ name: x.arguments.name, path: x.arguments.path })); - break; - } - case "amd-module": { - if (entryOrList instanceof Array) { - for (const entry of entryOrList) { - if (context.moduleName) { - // TODO: It's probably fine to issue this diagnostic on all instances of the pragma - reportDiagnostic(entry.range.pos, entry.range.end - entry.range.pos, Diagnostics.An_AMD_module_cannot_have_multiple_name_assignments); - } - context.moduleName = (entry as PragmaPseudoMap["amd-module"]).arguments.name; - } - } - else { - context.moduleName = (entryOrList as PragmaPseudoMap["amd-module"]).arguments.name; - } - break; - } - case "ts-nocheck": - case "ts-check": { - // _last_ of either nocheck or check in a file is the "winner" - forEach(toArray(entryOrList), entry => { - if (!context.checkJsDirective || entry.range.pos > context.checkJsDirective.pos) { - context.checkJsDirective = { - enabled: key === "ts-check", - end: entry.range.end, - pos: entry.range.pos - }; - } - }); - break; - } - case "jsx": - case "jsxfrag": - case "jsximportsource": - case "jsxruntime": - return; // Accessed directly - default: Debug.fail("Unhandled pragma kind"); // Can this be made into an assertNever in the future? - } - }); + for (const range of getLeadingCommentRanges(sourceText, 0) || emptyArray) { + const comment = sourceText.substring(range.pos, range.end); + extractPragmas(pragmas, range, comment); } - const namedArgRegExCache = new Map(); - function getNamedArgRegEx(name: string): RegExp { - if (namedArgRegExCache.has(name)) { - return namedArgRegExCache.get(name)!; + context.pragmas = new Map() as PragmaMap; + for (const pragma of pragmas) { + if (context.pragmas.has(pragma.name)) { + const currentValue = context.pragmas.get(pragma.name); + if (currentValue instanceof Array) { + currentValue.push(pragma.args); + } + else { + context.pragmas.set(pragma.name, [currentValue, pragma.args]); + } + continue; } - const result = new RegExp(`(\\s${name}\\s*=\\s*)(?:(?:'([^']*)')|(?:"([^"]*)"))`, "im"); - namedArgRegExCache.set(name, result); - return result; + context.pragmas.set(pragma.name, pragma.args); } +} - const tripleSlashXMLCommentStartRegEx = /^\/\/\/\s*<(\S+)\s.*?\/>/im; - const singleLinePragmaRegEx = /^\/\/\/?\s*@(\S+)\s*(.*)\s*$/im; - function extractPragmas(pragmas: PragmaPseudoMapEntry[], range: CommentRange, text: string) { - const tripleSlash = range.kind === SyntaxKind.SingleLineCommentTrivia && tripleSlashXMLCommentStartRegEx.exec(text); - if (tripleSlash) { - const name = tripleSlash[1].toLowerCase() as keyof PragmaPseudoMap; // Technically unsafe cast, but we do it so the below check to make it safe typechecks - const pragma = commentPragmas[name] as PragmaDefinition; - if (!pragma || !(pragma.kind! & PragmaKindFlags.TripleSlashXML)) { - return; - } - if (pragma.args) { - const argument: {[index: string]: string | {value: string, pos: number, end: number}} = {}; - for (const arg of pragma.args) { - const matcher = getNamedArgRegEx(arg.name); - const matchResult = matcher.exec(text); - if (!matchResult && !arg.optional) { - return; // Missing required argument, don't parse +/*@internal*/ +type PragmaDiagnosticReporter = (pos: number, length: number, message: DiagnosticMessage) => void; + +/*@internal*/ +export function processPragmasIntoFields(context: PragmaContext, reportDiagnostic: PragmaDiagnosticReporter): void { + context.checkJsDirective = undefined; + context.referencedFiles = []; + context.typeReferenceDirectives = []; + context.libReferenceDirectives = []; + context.amdDependencies = []; + context.hasNoDefaultLib = false; + context.pragmas!.forEach((entryOrList, key) => { // TODO: GH#18217 + // TODO: The below should be strongly type-guarded and not need casts/explicit annotations, since entryOrList is related to + // key and key is constrained to a union; but it's not (see GH#21483 for at least partial fix) :( + switch (key) { + case "reference": { + const referencedFiles = context.referencedFiles; + const typeReferenceDirectives = context.typeReferenceDirectives; + const libReferenceDirectives = context.libReferenceDirectives; + forEach(toArray(entryOrList) as PragmaPseudoMap["reference"][], arg => { + const { types, lib, path } = arg.arguments; + if (arg.arguments["no-default-lib"]) { + context.hasNoDefaultLib = true; } - else if (matchResult) { - const value = matchResult[2] || matchResult[3]; - if (arg.captureSpan) { - const startPos = range.pos + matchResult.index + matchResult[1].length + 1; - argument[arg.name] = { - value, - pos: startPos, - end: startPos + value.length - }; - } - else { - argument[arg.name] = value; + else if (types) { + typeReferenceDirectives.push({ pos: types.pos, end: types.end, fileName: types.value }); + } + else if (lib) { + libReferenceDirectives.push({ pos: lib.pos, end: lib.end, fileName: lib.value }); + } + else if (path) { + referencedFiles.push({ pos: path.pos, end: path.end, fileName: path.value }); + } + else { + reportDiagnostic(arg.range.pos, arg.range.end - arg.range.pos, Diagnostics.Invalid_reference_directive_syntax); + } + }); + break; + } + case "amd-dependency": { + context.amdDependencies = map( + toArray(entryOrList) as PragmaPseudoMap["amd-dependency"][], + x => ({ name: x.arguments.name, path: x.arguments.path })); + break; + } + case "amd-module": { + if (entryOrList instanceof Array) { + for (const entry of entryOrList) { + if (context.moduleName) { + // TODO: It's probably fine to issue this diagnostic on all instances of the pragma + reportDiagnostic(entry.range.pos, entry.range.end - entry.range.pos, Diagnostics.An_AMD_module_cannot_have_multiple_name_assignments); } + context.moduleName = (entry as PragmaPseudoMap["amd-module"]).arguments.name; } } - pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry); + else { + context.moduleName = (entryOrList as PragmaPseudoMap["amd-module"]).arguments.name; + } + break; } - else { - pragmas.push({ name, args: { arguments: {}, range } } as PragmaPseudoMapEntry); + case "ts-nocheck": + case "ts-check": { + // _last_ of either nocheck or check in a file is the "winner" + forEach(toArray(entryOrList), entry => { + if (!context.checkJsDirective || entry.range.pos > context.checkJsDirective.pos) { + context.checkJsDirective = { + enabled: key === "ts-check", + end: entry.range.end, + pos: entry.range.pos + }; + } + }); + break; } - return; - } - - const singleLine = range.kind === SyntaxKind.SingleLineCommentTrivia && singleLinePragmaRegEx.exec(text); - if (singleLine) { - return addPragmaForMatch(pragmas, range, PragmaKindFlags.SingleLine, singleLine); + case "jsx": + case "jsxfrag": + case "jsximportsource": + case "jsxruntime": + return; // Accessed directly + default: Debug.fail("Unhandled pragma kind"); // Can this be made into an assertNever in the future? } + }); +} - if (range.kind === SyntaxKind.MultiLineCommentTrivia) { - const multiLinePragmaRegEx = /@(\S+)(\s+.*)?$/gim; // Defined inline since it uses the "g" flag, which keeps a persistent index (for iterating) - let multiLineMatch: RegExpExecArray | null; - while (multiLineMatch = multiLinePragmaRegEx.exec(text)) { - addPragmaForMatch(pragmas, range, PragmaKindFlags.MultiLine, multiLineMatch); - } - } +const namedArgRegExCache = new Map(); +function getNamedArgRegEx(name: string): RegExp { + if (namedArgRegExCache.has(name)) { + return namedArgRegExCache.get(name)!; } + const result = new RegExp(`(\\s${name}\\s*=\\s*)(?:(?:'([^']*)')|(?:"([^"]*)"))`, "im"); + namedArgRegExCache.set(name, result); + return result; +} - function addPragmaForMatch(pragmas: PragmaPseudoMapEntry[], range: CommentRange, kind: PragmaKindFlags, match: RegExpExecArray) { - if (!match) return; - const name = match[1].toLowerCase() as keyof PragmaPseudoMap; // Technically unsafe cast, but we do it so they below check to make it safe typechecks +const tripleSlashXMLCommentStartRegEx = /^\/\/\/\s*<(\S+)\s.*?\/>/im; +const singleLinePragmaRegEx = /^\/\/\/?\s*@(\S+)\s*(.*)\s*$/im; +function extractPragmas(pragmas: PragmaPseudoMapEntry[], range: CommentRange, text: string) { + const tripleSlash = range.kind === SyntaxKind.SingleLineCommentTrivia && tripleSlashXMLCommentStartRegEx.exec(text); + if (tripleSlash) { + const name = tripleSlash[1].toLowerCase() as keyof PragmaPseudoMap; // Technically unsafe cast, but we do it so the below check to make it safe typechecks const pragma = commentPragmas[name] as PragmaDefinition; - if (!pragma || !(pragma.kind! & kind)) { + if (!pragma || !(pragma.kind! & PragmaKindFlags.TripleSlashXML)) { return; } - const args = match[2]; // Split on spaces and match up positionally with definition - const argument = getNamedPragmaArguments(pragma, args); - if (argument === "fail") return; // Missing required argument, fail to parse it - pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry); + if (pragma.args) { + const argument: {[index: string]: string | {value: string, pos: number, end: number}} = {}; + for (const arg of pragma.args) { + const matcher = getNamedArgRegEx(arg.name); + const matchResult = matcher.exec(text); + if (!matchResult && !arg.optional) { + return; // Missing required argument, don't parse + } + else if (matchResult) { + const value = matchResult[2] || matchResult[3]; + if (arg.captureSpan) { + const startPos = range.pos + matchResult.index + matchResult[1].length + 1; + argument[arg.name] = { + value, + pos: startPos, + end: startPos + value.length + }; + } + else { + argument[arg.name] = value; + } + } + } + pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry); + } + else { + pragmas.push({ name, args: { arguments: {}, range } } as PragmaPseudoMapEntry); + } return; } - function getNamedPragmaArguments(pragma: PragmaDefinition, text: string | undefined): {[index: string]: string} | "fail" { - if (!text) return {}; - if (!pragma.args) return {}; - const args = trimString(text).split(/\s+/); - const argMap: {[index: string]: string} = {}; - for (let i = 0; i < pragma.args.length; i++) { - const argument = pragma.args[i]; - if (!args[i] && !argument.optional) { - return "fail"; - } - if (argument.captureSpan) { - return Debug.fail("Capture spans not yet implemented for non-xml pragmas"); - } - argMap[argument.name] = args[i]; - } - return argMap; + const singleLine = range.kind === SyntaxKind.SingleLineCommentTrivia && singleLinePragmaRegEx.exec(text); + if (singleLine) { + return addPragmaForMatch(pragmas, range, PragmaKindFlags.SingleLine, singleLine); } - /** @internal */ - export function tagNamesAreEquivalent(lhs: JsxTagNameExpression, rhs: JsxTagNameExpression): boolean { - if (lhs.kind !== rhs.kind) { - return false; + if (range.kind === SyntaxKind.MultiLineCommentTrivia) { + const multiLinePragmaRegEx = /@(\S+)(\s+.*)?$/gim; // Defined inline since it uses the "g" flag, which keeps a persistent index (for iterating) + let multiLineMatch: RegExpExecArray | null; + while (multiLineMatch = multiLinePragmaRegEx.exec(text)) { + addPragmaForMatch(pragmas, range, PragmaKindFlags.MultiLine, multiLineMatch); } + } +} - if (lhs.kind === SyntaxKind.Identifier) { - return lhs.escapedText === (rhs as Identifier).escapedText; - } +function addPragmaForMatch(pragmas: PragmaPseudoMapEntry[], range: CommentRange, kind: PragmaKindFlags, match: RegExpExecArray) { + if (!match) return; + const name = match[1].toLowerCase() as keyof PragmaPseudoMap; // Technically unsafe cast, but we do it so they below check to make it safe typechecks + const pragma = commentPragmas[name] as PragmaDefinition; + if (!pragma || !(pragma.kind! & kind)) { + return; + } + const args = match[2]; // Split on spaces and match up positionally with definition + const argument = getNamedPragmaArguments(pragma, args); + if (argument === "fail") return; // Missing required argument, fail to parse it + pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry); + return; +} - if (lhs.kind === SyntaxKind.ThisKeyword) { - return true; +function getNamedPragmaArguments(pragma: PragmaDefinition, text: string | undefined): {[index: string]: string} | "fail" { + if (!text) return {}; + if (!pragma.args) return {}; + const args = trimString(text).split(/\s+/); + const argMap: {[index: string]: string} = {}; + for (let i = 0; i < pragma.args.length; i++) { + const argument = pragma.args[i]; + if (!args[i] && !argument.optional) { + return "fail"; + } + if (argument.captureSpan) { + return Debug.fail("Capture spans not yet implemented for non-xml pragmas"); } + argMap[argument.name] = args[i]; + } + return argMap; +} + +/** @internal */ +export function tagNamesAreEquivalent(lhs: JsxTagNameExpression, rhs: JsxTagNameExpression): boolean { + if (lhs.kind !== rhs.kind) { + return false; + } - // If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only - // take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression - // it is safe to case the expression property as such. See parseJsxElementName for how we parse tag name in Jsx element - return (lhs as PropertyAccessExpression).name.escapedText === (rhs as PropertyAccessExpression).name.escapedText && - tagNamesAreEquivalent((lhs as PropertyAccessExpression).expression as JsxTagNameExpression, (rhs as PropertyAccessExpression).expression as JsxTagNameExpression); + if (lhs.kind === SyntaxKind.Identifier) { + return lhs.escapedText === (rhs as Identifier).escapedText; } + + if (lhs.kind === SyntaxKind.ThisKeyword) { + return true; + } + + // If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only + // take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression + // it is safe to case the expression property as such. See parseJsxElementName for how we parse tag name in Jsx element + return (lhs as PropertyAccessExpression).name.escapedText === (rhs as PropertyAccessExpression).name.escapedText && + tagNamesAreEquivalent((lhs as PropertyAccessExpression).expression as JsxTagNameExpression, (rhs as PropertyAccessExpression).expression as JsxTagNameExpression); +} } diff --git a/src/compiler/path.ts b/src/compiler/path.ts index d09108d34c7f3..b6c3c2a15fac1 100644 --- a/src/compiler/path.ts +++ b/src/compiler/path.ts @@ -1,886 +1,886 @@ /* @internal */ namespace ts { - /** - * Internally, we represent paths as strings with '/' as the directory separator. - * When we make system calls (eg: LanguageServiceHost.getDirectory()), - * we expect the host to correctly handle paths in our specified format. - */ - export const directorySeparator = "/"; - export const altDirectorySeparator = "\\"; - const urlSchemeSeparator = "://"; - const backslashRegExp = /\\/g; - - //// Path Tests - - /** - * Determines whether a charCode corresponds to `/` or `\`. - */ - export function isAnyDirectorySeparator(charCode: number): boolean { - return charCode === CharacterCodes.slash || charCode === CharacterCodes.backslash; - } - - /** - * Determines whether a path starts with a URL scheme (e.g. starts with `http://`, `ftp://`, `file://`, etc.). - */ - export function isUrl(path: string) { - return getEncodedRootLength(path) < 0; - } - - /** - * Determines whether a path is an absolute disk path (e.g. starts with `/`, or a dos path - * like `c:`, `c:\` or `c:/`). - */ - export function isRootedDiskPath(path: string) { - return getEncodedRootLength(path) > 0; - } - - /** - * Determines whether a path consists only of a path root. - */ - export function isDiskPathRoot(path: string) { - const rootLength = getEncodedRootLength(path); - return rootLength > 0 && rootLength === path.length; - } - - /** - * Determines whether a path starts with an absolute path component (i.e. `/`, `c:/`, `file://`, etc.). - * - * ```ts - * // POSIX - * pathIsAbsolute("/path/to/file.ext") === true - * // DOS - * pathIsAbsolute("c:/path/to/file.ext") === true - * // URL - * pathIsAbsolute("file:///path/to/file.ext") === true - * // Non-absolute - * pathIsAbsolute("path/to/file.ext") === false - * pathIsAbsolute("./path/to/file.ext") === false - * ``` - */ - export function pathIsAbsolute(path: string): boolean { - return getEncodedRootLength(path) !== 0; - } - - /** - * Determines whether a path starts with a relative path component (i.e. `.` or `..`). - */ - export function pathIsRelative(path: string): boolean { - return /^\.\.?($|[\\/])/.test(path); - } - - /** - * Determines whether a path is neither relative nor absolute, e.g. "path/to/file". - * Also known misleadingly as "non-relative". - */ - export function pathIsBareSpecifier(path: string): boolean { - return !pathIsAbsolute(path) && !pathIsRelative(path); - } - - export function hasExtension(fileName: string): boolean { - return stringContains(getBaseFileName(fileName), "."); - } - - export function fileExtensionIs(path: string, extension: string): boolean { - return path.length > extension.length && endsWith(path, extension); - } - - export function fileExtensionIsOneOf(path: string, extensions: readonly string[]): boolean { - for (const extension of extensions) { - if (fileExtensionIs(path, extension)) { - return true; - } - } +/** + * Internally, we represent paths as strings with '/' as the directory separator. + * When we make system calls (eg: LanguageServiceHost.getDirectory()), + * we expect the host to correctly handle paths in our specified format. + */ +export const directorySeparator = "/"; +export const altDirectorySeparator = "\\"; +const urlSchemeSeparator = "://"; +const backslashRegExp = /\\/g; + +//// Path Tests + +/** + * Determines whether a charCode corresponds to `/` or `\`. + */ +export function isAnyDirectorySeparator(charCode: number): boolean { + return charCode === CharacterCodes.slash || charCode === CharacterCodes.backslash; +} - return false; - } +/** + * Determines whether a path starts with a URL scheme (e.g. starts with `http://`, `ftp://`, `file://`, etc.). + */ +export function isUrl(path: string) { + return getEncodedRootLength(path) < 0; +} - /** - * Determines whether a path has a trailing separator (`/` or `\\`). - */ - export function hasTrailingDirectorySeparator(path: string) { - return path.length > 0 && isAnyDirectorySeparator(path.charCodeAt(path.length - 1)); - } +/** + * Determines whether a path is an absolute disk path (e.g. starts with `/`, or a dos path + * like `c:`, `c:\` or `c:/`). + */ +export function isRootedDiskPath(path: string) { + return getEncodedRootLength(path) > 0; +} - //// Path Parsing +/** + * Determines whether a path consists only of a path root. + */ +export function isDiskPathRoot(path: string) { + const rootLength = getEncodedRootLength(path); + return rootLength > 0 && rootLength === path.length; +} - function isVolumeCharacter(charCode: number) { - return (charCode >= CharacterCodes.a && charCode <= CharacterCodes.z) || - (charCode >= CharacterCodes.A && charCode <= CharacterCodes.Z); - } +/** + * Determines whether a path starts with an absolute path component (i.e. `/`, `c:/`, `file://`, etc.). + * + * ```ts + * // POSIX + * pathIsAbsolute("/path/to/file.ext") === true + * // DOS + * pathIsAbsolute("c:/path/to/file.ext") === true + * // URL + * pathIsAbsolute("file:///path/to/file.ext") === true + * // Non-absolute + * pathIsAbsolute("path/to/file.ext") === false + * pathIsAbsolute("./path/to/file.ext") === false + * ``` + */ +export function pathIsAbsolute(path: string): boolean { + return getEncodedRootLength(path) !== 0; +} + +/** + * Determines whether a path starts with a relative path component (i.e. `.` or `..`). + */ +export function pathIsRelative(path: string): boolean { + return /^\.\.?($|[\\/])/.test(path); +} + +/** + * Determines whether a path is neither relative nor absolute, e.g. "path/to/file". + * Also known misleadingly as "non-relative". + */ +export function pathIsBareSpecifier(path: string): boolean { + return !pathIsAbsolute(path) && !pathIsRelative(path); +} + +export function hasExtension(fileName: string): boolean { + return stringContains(getBaseFileName(fileName), "."); +} + +export function fileExtensionIs(path: string, extension: string): boolean { + return path.length > extension.length && endsWith(path, extension); +} - function getFileUrlVolumeSeparatorEnd(url: string, start: number) { - const ch0 = url.charCodeAt(start); - if (ch0 === CharacterCodes.colon) return start + 1; - if (ch0 === CharacterCodes.percent && url.charCodeAt(start + 1) === CharacterCodes._3) { - const ch2 = url.charCodeAt(start + 2); - if (ch2 === CharacterCodes.a || ch2 === CharacterCodes.A) return start + 3; +export function fileExtensionIsOneOf(path: string, extensions: readonly string[]): boolean { + for (const extension of extensions) { + if (fileExtensionIs(path, extension)) { + return true; } - return -1; } - /** - * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). - * If the root is part of a URL, the twos-complement of the root length is returned. - */ - function getEncodedRootLength(path: string): number { - if (!path) return 0; - const ch0 = path.charCodeAt(0); + return false; +} - // POSIX or UNC - if (ch0 === CharacterCodes.slash || ch0 === CharacterCodes.backslash) { - if (path.charCodeAt(1) !== ch0) return 1; // POSIX: "/" (or non-normalized "\") +/** + * Determines whether a path has a trailing separator (`/` or `\\`). + */ +export function hasTrailingDirectorySeparator(path: string) { + return path.length > 0 && isAnyDirectorySeparator(path.charCodeAt(path.length - 1)); +} - const p1 = path.indexOf(ch0 === CharacterCodes.slash ? directorySeparator : altDirectorySeparator, 2); - if (p1 < 0) return path.length; // UNC: "//server" or "\\server" +//// Path Parsing - return p1 + 1; // UNC: "//server/" or "\\server\" - } +function isVolumeCharacter(charCode: number) { + return (charCode >= CharacterCodes.a && charCode <= CharacterCodes.z) || + (charCode >= CharacterCodes.A && charCode <= CharacterCodes.Z); +} - // DOS - if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodes.colon) { - const ch2 = path.charCodeAt(2); - if (ch2 === CharacterCodes.slash || ch2 === CharacterCodes.backslash) return 3; // DOS: "c:/" or "c:\" - if (path.length === 2) return 2; // DOS: "c:" (but not "c:d") - } +function getFileUrlVolumeSeparatorEnd(url: string, start: number) { + const ch0 = url.charCodeAt(start); + if (ch0 === CharacterCodes.colon) return start + 1; + if (ch0 === CharacterCodes.percent && url.charCodeAt(start + 1) === CharacterCodes._3) { + const ch2 = url.charCodeAt(start + 2); + if (ch2 === CharacterCodes.a || ch2 === CharacterCodes.A) return start + 3; + } + return -1; +} - // URL - const schemeEnd = path.indexOf(urlSchemeSeparator); - if (schemeEnd !== -1) { - const authorityStart = schemeEnd + urlSchemeSeparator.length; - const authorityEnd = path.indexOf(directorySeparator, authorityStart); - if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path" - // For local "file" URLs, include the leading DOS volume (if present). - // Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a - // special case interpreted as "the machine from which the URL is being interpreted". - const scheme = path.slice(0, schemeEnd); - const authority = path.slice(authorityStart, authorityEnd); - if (scheme === "file" && (authority === "" || authority === "localhost") && - isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) { - const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2); - if (volumeSeparatorEnd !== -1) { - if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodes.slash) { - // URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/" - return ~(volumeSeparatorEnd + 1); - } - if (volumeSeparatorEnd === path.length) { - // URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a" - // but not "file:///c:d" or "file:///c%3ad" - return ~volumeSeparatorEnd; - } +/** + * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). + * If the root is part of a URL, the twos-complement of the root length is returned. + */ +function getEncodedRootLength(path: string): number { + if (!path) return 0; + const ch0 = path.charCodeAt(0); + + // POSIX or UNC + if (ch0 === CharacterCodes.slash || ch0 === CharacterCodes.backslash) { + if (path.charCodeAt(1) !== ch0) return 1; // POSIX: "/" (or non-normalized "\") + + const p1 = path.indexOf(ch0 === CharacterCodes.slash ? directorySeparator : altDirectorySeparator, 2); + if (p1 < 0) return path.length; // UNC: "//server" or "\\server" + + return p1 + 1; // UNC: "//server/" or "\\server\" + } + + // DOS + if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodes.colon) { + const ch2 = path.charCodeAt(2); + if (ch2 === CharacterCodes.slash || ch2 === CharacterCodes.backslash) return 3; // DOS: "c:/" or "c:\" + if (path.length === 2) return 2; // DOS: "c:" (but not "c:d") + } + + // URL + const schemeEnd = path.indexOf(urlSchemeSeparator); + if (schemeEnd !== -1) { + const authorityStart = schemeEnd + urlSchemeSeparator.length; + const authorityEnd = path.indexOf(directorySeparator, authorityStart); + if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path" + // For local "file" URLs, include the leading DOS volume (if present). + // Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a + // special case interpreted as "the machine from which the URL is being interpreted". + const scheme = path.slice(0, schemeEnd); + const authority = path.slice(authorityStart, authorityEnd); + if (scheme === "file" && (authority === "" || authority === "localhost") && + isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) { + const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2); + if (volumeSeparatorEnd !== -1) { + if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodes.slash) { + // URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/" + return ~(volumeSeparatorEnd + 1); + } + if (volumeSeparatorEnd === path.length) { + // URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a" + // but not "file:///c:d" or "file:///c%3ad" + return ~volumeSeparatorEnd; } } - return ~(authorityEnd + 1); // URL: "file://server/", "http://server/" } - return ~path.length; // URL: "file://server", "http://server" + return ~(authorityEnd + 1); // URL: "file://server/", "http://server/" } + return ~path.length; // URL: "file://server", "http://server" + } - // relative - return 0; - } - - /** - * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). - * - * For example: - * ```ts - * getRootLength("a") === 0 // "" - * getRootLength("/") === 1 // "/" - * getRootLength("c:") === 2 // "c:" - * getRootLength("c:d") === 0 // "" - * getRootLength("c:/") === 3 // "c:/" - * getRootLength("c:\\") === 3 // "c:\\" - * getRootLength("//server") === 7 // "//server" - * getRootLength("//server/share") === 8 // "//server/" - * getRootLength("\\\\server") === 7 // "\\\\server" - * getRootLength("\\\\server\\share") === 8 // "\\\\server\\" - * getRootLength("file:///path") === 8 // "file:///" - * getRootLength("file:///c:") === 10 // "file:///c:" - * getRootLength("file:///c:d") === 8 // "file:///" - * getRootLength("file:///c:/path") === 11 // "file:///c:/" - * getRootLength("file://server") === 13 // "file://server" - * getRootLength("file://server/path") === 14 // "file://server/" - * getRootLength("http://server") === 13 // "http://server" - * getRootLength("http://server/path") === 14 // "http://server/" - * ``` - */ - export function getRootLength(path: string) { - const rootLength = getEncodedRootLength(path); - return rootLength < 0 ? ~rootLength : rootLength; - } - - /** - * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` - * except that we support URLs as well. - * - * ```ts - * // POSIX - * getDirectoryPath("/path/to/file.ext") === "/path/to" - * getDirectoryPath("/path/to/") === "/path" - * getDirectoryPath("/") === "/" - * // DOS - * getDirectoryPath("c:/path/to/file.ext") === "c:/path/to" - * getDirectoryPath("c:/path/to/") === "c:/path" - * getDirectoryPath("c:/") === "c:/" - * getDirectoryPath("c:") === "c:" - * // URL - * getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to" - * getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path" - * getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/" - * getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org" - * ``` - */ - export function getDirectoryPath(path: Path): Path; - /** - * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` - * except that we support URLs as well. - * - * ```ts - * // POSIX - * getDirectoryPath("/path/to/file.ext") === "/path/to" - * getDirectoryPath("/path/to/") === "/path" - * getDirectoryPath("/") === "/" - * // DOS - * getDirectoryPath("c:/path/to/file.ext") === "c:/path/to" - * getDirectoryPath("c:/path/to/") === "c:/path" - * getDirectoryPath("c:/") === "c:/" - * getDirectoryPath("c:") === "c:" - * // URL - * getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to" - * getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path" - * getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/" - * getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org" - * getDirectoryPath("file://server/path/to/file.ext") === "file://server/path/to" - * getDirectoryPath("file://server/path/to") === "file://server/path" - * getDirectoryPath("file://server/") === "file://server/" - * getDirectoryPath("file://server") === "file://server" - * getDirectoryPath("file:///path/to/file.ext") === "file:///path/to" - * getDirectoryPath("file:///path/to") === "file:///path" - * getDirectoryPath("file:///") === "file:///" - * getDirectoryPath("file://") === "file://" - * ``` - */ - export function getDirectoryPath(path: string): string; - export function getDirectoryPath(path: string): string { - path = normalizeSlashes(path); - - // If the path provided is itself the root, then return it. - const rootLength = getRootLength(path); - if (rootLength === path.length) return path; - - // return the leading portion of the path up to the last (non-terminal) directory separator - // but not including any trailing directory separator. - path = removeTrailingDirectorySeparator(path); - return path.slice(0, Math.max(rootLength, path.lastIndexOf(directorySeparator))); - } - - /** - * Returns the path except for its containing directory name. - * Semantics align with NodeJS's `path.basename` except that we support URL's as well. - * - * ```ts - * // POSIX - * getBaseFileName("/path/to/file.ext") === "file.ext" - * getBaseFileName("/path/to/") === "to" - * getBaseFileName("/") === "" - * // DOS - * getBaseFileName("c:/path/to/file.ext") === "file.ext" - * getBaseFileName("c:/path/to/") === "to" - * getBaseFileName("c:/") === "" - * getBaseFileName("c:") === "" - * // URL - * getBaseFileName("http://typescriptlang.org/path/to/file.ext") === "file.ext" - * getBaseFileName("http://typescriptlang.org/path/to/") === "to" - * getBaseFileName("http://typescriptlang.org/") === "" - * getBaseFileName("http://typescriptlang.org") === "" - * getBaseFileName("file://server/path/to/file.ext") === "file.ext" - * getBaseFileName("file://server/path/to/") === "to" - * getBaseFileName("file://server/") === "" - * getBaseFileName("file://server") === "" - * getBaseFileName("file:///path/to/file.ext") === "file.ext" - * getBaseFileName("file:///path/to/") === "to" - * getBaseFileName("file:///") === "" - * getBaseFileName("file://") === "" - * ``` - */ - export function getBaseFileName(path: string): string; - /** - * Gets the portion of a path following the last (non-terminal) separator (`/`). - * Semantics align with NodeJS's `path.basename` except that we support URL's as well. - * If the base name has any one of the provided extensions, it is removed. - * - * ```ts - * getBaseFileName("/path/to/file.ext", ".ext", true) === "file" - * getBaseFileName("/path/to/file.js", ".ext", true) === "file.js" - * getBaseFileName("/path/to/file.js", [".ext", ".js"], true) === "file" - * getBaseFileName("/path/to/file.ext", ".EXT", false) === "file.ext" - * ``` - */ - export function getBaseFileName(path: string, extensions: string | readonly string[], ignoreCase: boolean): string; - export function getBaseFileName(path: string, extensions?: string | readonly string[], ignoreCase?: boolean) { - path = normalizeSlashes(path); - - // if the path provided is itself the root, then it has not file name. - const rootLength = getRootLength(path); - if (rootLength === path.length) return ""; - - // return the trailing portion of the path starting after the last (non-terminal) directory - // separator but not including any trailing directory separator. - path = removeTrailingDirectorySeparator(path); - const name = path.slice(Math.max(getRootLength(path), path.lastIndexOf(directorySeparator) + 1)); - const extension = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(name, extensions, ignoreCase) : undefined; - return extension ? name.slice(0, name.length - extension.length) : name; - } - - function tryGetExtensionFromPath(path: string, extension: string, stringEqualityComparer: (a: string, b: string) => boolean) { - if (!startsWith(extension, ".")) extension = "." + extension; - if (path.length >= extension.length && path.charCodeAt(path.length - extension.length) === CharacterCodes.dot) { - const pathExtension = path.slice(path.length - extension.length); - if (stringEqualityComparer(pathExtension, extension)) { - return pathExtension; - } + // relative + return 0; +} + +/** + * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). + * + * For example: + * ```ts + * getRootLength("a") === 0 // "" + * getRootLength("/") === 1 // "/" + * getRootLength("c:") === 2 // "c:" + * getRootLength("c:d") === 0 // "" + * getRootLength("c:/") === 3 // "c:/" + * getRootLength("c:\\") === 3 // "c:\\" + * getRootLength("//server") === 7 // "//server" + * getRootLength("//server/share") === 8 // "//server/" + * getRootLength("\\\\server") === 7 // "\\\\server" + * getRootLength("\\\\server\\share") === 8 // "\\\\server\\" + * getRootLength("file:///path") === 8 // "file:///" + * getRootLength("file:///c:") === 10 // "file:///c:" + * getRootLength("file:///c:d") === 8 // "file:///" + * getRootLength("file:///c:/path") === 11 // "file:///c:/" + * getRootLength("file://server") === 13 // "file://server" + * getRootLength("file://server/path") === 14 // "file://server/" + * getRootLength("http://server") === 13 // "http://server" + * getRootLength("http://server/path") === 14 // "http://server/" + * ``` + */ +export function getRootLength(path: string) { + const rootLength = getEncodedRootLength(path); + return rootLength < 0 ? ~rootLength : rootLength; +} + +/** + * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` + * except that we support URLs as well. + * + * ```ts + * // POSIX + * getDirectoryPath("/path/to/file.ext") === "/path/to" + * getDirectoryPath("/path/to/") === "/path" + * getDirectoryPath("/") === "/" + * // DOS + * getDirectoryPath("c:/path/to/file.ext") === "c:/path/to" + * getDirectoryPath("c:/path/to/") === "c:/path" + * getDirectoryPath("c:/") === "c:/" + * getDirectoryPath("c:") === "c:" + * // URL + * getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to" + * getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path" + * getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/" + * getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org" + * ``` + */ +export function getDirectoryPath(path: Path): Path; +/** + * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` + * except that we support URLs as well. + * + * ```ts + * // POSIX + * getDirectoryPath("/path/to/file.ext") === "/path/to" + * getDirectoryPath("/path/to/") === "/path" + * getDirectoryPath("/") === "/" + * // DOS + * getDirectoryPath("c:/path/to/file.ext") === "c:/path/to" + * getDirectoryPath("c:/path/to/") === "c:/path" + * getDirectoryPath("c:/") === "c:/" + * getDirectoryPath("c:") === "c:" + * // URL + * getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to" + * getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path" + * getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/" + * getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org" + * getDirectoryPath("file://server/path/to/file.ext") === "file://server/path/to" + * getDirectoryPath("file://server/path/to") === "file://server/path" + * getDirectoryPath("file://server/") === "file://server/" + * getDirectoryPath("file://server") === "file://server" + * getDirectoryPath("file:///path/to/file.ext") === "file:///path/to" + * getDirectoryPath("file:///path/to") === "file:///path" + * getDirectoryPath("file:///") === "file:///" + * getDirectoryPath("file://") === "file://" + * ``` + */ +export function getDirectoryPath(path: string): string; +export function getDirectoryPath(path: string): string { + path = normalizeSlashes(path); + + // If the path provided is itself the root, then return it. + const rootLength = getRootLength(path); + if (rootLength === path.length) return path; + + // return the leading portion of the path up to the last (non-terminal) directory separator + // but not including any trailing directory separator. + path = removeTrailingDirectorySeparator(path); + return path.slice(0, Math.max(rootLength, path.lastIndexOf(directorySeparator))); +} + +/** + * Returns the path except for its containing directory name. + * Semantics align with NodeJS's `path.basename` except that we support URL's as well. + * + * ```ts + * // POSIX + * getBaseFileName("/path/to/file.ext") === "file.ext" + * getBaseFileName("/path/to/") === "to" + * getBaseFileName("/") === "" + * // DOS + * getBaseFileName("c:/path/to/file.ext") === "file.ext" + * getBaseFileName("c:/path/to/") === "to" + * getBaseFileName("c:/") === "" + * getBaseFileName("c:") === "" + * // URL + * getBaseFileName("http://typescriptlang.org/path/to/file.ext") === "file.ext" + * getBaseFileName("http://typescriptlang.org/path/to/") === "to" + * getBaseFileName("http://typescriptlang.org/") === "" + * getBaseFileName("http://typescriptlang.org") === "" + * getBaseFileName("file://server/path/to/file.ext") === "file.ext" + * getBaseFileName("file://server/path/to/") === "to" + * getBaseFileName("file://server/") === "" + * getBaseFileName("file://server") === "" + * getBaseFileName("file:///path/to/file.ext") === "file.ext" + * getBaseFileName("file:///path/to/") === "to" + * getBaseFileName("file:///") === "" + * getBaseFileName("file://") === "" + * ``` + */ +export function getBaseFileName(path: string): string; +/** + * Gets the portion of a path following the last (non-terminal) separator (`/`). + * Semantics align with NodeJS's `path.basename` except that we support URL's as well. + * If the base name has any one of the provided extensions, it is removed. + * + * ```ts + * getBaseFileName("/path/to/file.ext", ".ext", true) === "file" + * getBaseFileName("/path/to/file.js", ".ext", true) === "file.js" + * getBaseFileName("/path/to/file.js", [".ext", ".js"], true) === "file" + * getBaseFileName("/path/to/file.ext", ".EXT", false) === "file.ext" + * ``` + */ +export function getBaseFileName(path: string, extensions: string | readonly string[], ignoreCase: boolean): string; +export function getBaseFileName(path: string, extensions?: string | readonly string[], ignoreCase?: boolean) { + path = normalizeSlashes(path); + + // if the path provided is itself the root, then it has not file name. + const rootLength = getRootLength(path); + if (rootLength === path.length) return ""; + + // return the trailing portion of the path starting after the last (non-terminal) directory + // separator but not including any trailing directory separator. + path = removeTrailingDirectorySeparator(path); + const name = path.slice(Math.max(getRootLength(path), path.lastIndexOf(directorySeparator) + 1)); + const extension = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(name, extensions, ignoreCase) : undefined; + return extension ? name.slice(0, name.length - extension.length) : name; +} + +function tryGetExtensionFromPath(path: string, extension: string, stringEqualityComparer: (a: string, b: string) => boolean) { + if (!startsWith(extension, ".")) extension = "." + extension; + if (path.length >= extension.length && path.charCodeAt(path.length - extension.length) === CharacterCodes.dot) { + const pathExtension = path.slice(path.length - extension.length); + if (stringEqualityComparer(pathExtension, extension)) { + return pathExtension; } } +} + +function getAnyExtensionFromPathWorker(path: string, extensions: string | readonly string[], stringEqualityComparer: (a: string, b: string) => boolean) { + if (typeof extensions === "string") { + return tryGetExtensionFromPath(path, extensions, stringEqualityComparer) || ""; + } + for (const extension of extensions) { + const result = tryGetExtensionFromPath(path, extension, stringEqualityComparer); + if (result) return result; + } + return ""; +} - function getAnyExtensionFromPathWorker(path: string, extensions: string | readonly string[], stringEqualityComparer: (a: string, b: string) => boolean) { - if (typeof extensions === "string") { - return tryGetExtensionFromPath(path, extensions, stringEqualityComparer) || ""; - } - for (const extension of extensions) { - const result = tryGetExtensionFromPath(path, extension, stringEqualityComparer); - if (result) return result; - } - return ""; - } - - /** - * Gets the file extension for a path. - * - * ```ts - * getAnyExtensionFromPath("/path/to/file.ext") === ".ext" - * getAnyExtensionFromPath("/path/to/file.ext/") === ".ext" - * getAnyExtensionFromPath("/path/to/file") === "" - * getAnyExtensionFromPath("/path/to.ext/file") === "" - * ``` - */ - export function getAnyExtensionFromPath(path: string): string; - /** - * Gets the file extension for a path, provided it is one of the provided extensions. - * - * ```ts - * getAnyExtensionFromPath("/path/to/file.ext", ".ext", true) === ".ext" - * getAnyExtensionFromPath("/path/to/file.js", ".ext", true) === "" - * getAnyExtensionFromPath("/path/to/file.js", [".ext", ".js"], true) === ".js" - * getAnyExtensionFromPath("/path/to/file.ext", ".EXT", false) === "" - */ - export function getAnyExtensionFromPath(path: string, extensions: string | readonly string[], ignoreCase: boolean): string; - export function getAnyExtensionFromPath(path: string, extensions?: string | readonly string[], ignoreCase?: boolean): string { - // Retrieves any string from the final "." onwards from a base file name. - // Unlike extensionFromPath, which throws an exception on unrecognized extensions. - if (extensions) { - return getAnyExtensionFromPathWorker(removeTrailingDirectorySeparator(path), extensions, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive); - } - const baseFileName = getBaseFileName(path); - const extensionIndex = baseFileName.lastIndexOf("."); - if (extensionIndex >= 0) { - return baseFileName.substring(extensionIndex); - } - return ""; - } - - function pathComponents(path: string, rootLength: number) { - const root = path.substring(0, rootLength); - const rest = path.substring(rootLength).split(directorySeparator); - if (rest.length && !lastOrUndefined(rest)) rest.pop(); - return [root, ...rest]; - } - - /** - * Parse a path into an array containing a root component (at index 0) and zero or more path - * components (at indices > 0). The result is not normalized. - * If the path is relative, the root component is `""`. - * If the path is absolute, the root component includes the first path separator (`/`). - * - * ```ts - * // POSIX - * getPathComponents("/path/to/file.ext") === ["/", "path", "to", "file.ext"] - * getPathComponents("/path/to/") === ["/", "path", "to"] - * getPathComponents("/") === ["/"] - * // DOS - * getPathComponents("c:/path/to/file.ext") === ["c:/", "path", "to", "file.ext"] - * getPathComponents("c:/path/to/") === ["c:/", "path", "to"] - * getPathComponents("c:/") === ["c:/"] - * getPathComponents("c:") === ["c:"] - * // URL - * getPathComponents("http://typescriptlang.org/path/to/file.ext") === ["http://typescriptlang.org/", "path", "to", "file.ext"] - * getPathComponents("http://typescriptlang.org/path/to/") === ["http://typescriptlang.org/", "path", "to"] - * getPathComponents("http://typescriptlang.org/") === ["http://typescriptlang.org/"] - * getPathComponents("http://typescriptlang.org") === ["http://typescriptlang.org"] - * getPathComponents("file://server/path/to/file.ext") === ["file://server/", "path", "to", "file.ext"] - * getPathComponents("file://server/path/to/") === ["file://server/", "path", "to"] - * getPathComponents("file://server/") === ["file://server/"] - * getPathComponents("file://server") === ["file://server"] - * getPathComponents("file:///path/to/file.ext") === ["file:///", "path", "to", "file.ext"] - * getPathComponents("file:///path/to/") === ["file:///", "path", "to"] - * getPathComponents("file:///") === ["file:///"] - * getPathComponents("file://") === ["file://"] - */ - export function getPathComponents(path: string, currentDirectory = "") { - path = combinePaths(currentDirectory, path); - return pathComponents(path, getRootLength(path)); - } - - //// Path Formatting - - /** - * Formats a parsed path consisting of a root component (at index 0) and zero or more path - * segments (at indices > 0). - * - * ```ts - * getPathFromPathComponents(["/", "path", "to", "file.ext"]) === "/path/to/file.ext" - * ``` - */ - export function getPathFromPathComponents(pathComponents: readonly string[]) { - if (pathComponents.length === 0) return ""; - - const root = pathComponents[0] && ensureTrailingDirectorySeparator(pathComponents[0]); - return root + pathComponents.slice(1).join(directorySeparator); - } - - //// Path Normalization - - /** - * Normalize path separators, converting `\` into `/`. - */ - export function normalizeSlashes(path: string): string { - const index = path.indexOf("\\"); - if (index === -1) { - return path; - } - backslashRegExp.lastIndex = index; // prime regex with known position - return path.replace(backslashRegExp, directorySeparator); - } - - /** - * Reduce an array of path components to a more simplified path by navigating any - * `"."` or `".."` entries in the path. - */ - export function reducePathComponents(components: readonly string[]) { - if (!some(components)) return []; - const reduced = [components[0]]; - for (let i = 1; i < components.length; i++) { - const component = components[i]; - if (!component) continue; - if (component === ".") continue; - if (component === "..") { - if (reduced.length > 1) { - if (reduced[reduced.length - 1] !== "..") { - reduced.pop(); - continue; - } +/** + * Gets the file extension for a path. + * + * ```ts + * getAnyExtensionFromPath("/path/to/file.ext") === ".ext" + * getAnyExtensionFromPath("/path/to/file.ext/") === ".ext" + * getAnyExtensionFromPath("/path/to/file") === "" + * getAnyExtensionFromPath("/path/to.ext/file") === "" + * ``` + */ +export function getAnyExtensionFromPath(path: string): string; +/** + * Gets the file extension for a path, provided it is one of the provided extensions. + * + * ```ts + * getAnyExtensionFromPath("/path/to/file.ext", ".ext", true) === ".ext" + * getAnyExtensionFromPath("/path/to/file.js", ".ext", true) === "" + * getAnyExtensionFromPath("/path/to/file.js", [".ext", ".js"], true) === ".js" + * getAnyExtensionFromPath("/path/to/file.ext", ".EXT", false) === "" + */ +export function getAnyExtensionFromPath(path: string, extensions: string | readonly string[], ignoreCase: boolean): string; +export function getAnyExtensionFromPath(path: string, extensions?: string | readonly string[], ignoreCase?: boolean): string { + // Retrieves any string from the final "." onwards from a base file name. + // Unlike extensionFromPath, which throws an exception on unrecognized extensions. + if (extensions) { + return getAnyExtensionFromPathWorker(removeTrailingDirectorySeparator(path), extensions, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive); + } + const baseFileName = getBaseFileName(path); + const extensionIndex = baseFileName.lastIndexOf("."); + if (extensionIndex >= 0) { + return baseFileName.substring(extensionIndex); + } + return ""; +} + +function pathComponents(path: string, rootLength: number) { + const root = path.substring(0, rootLength); + const rest = path.substring(rootLength).split(directorySeparator); + if (rest.length && !lastOrUndefined(rest)) rest.pop(); + return [root, ...rest]; +} + +/** + * Parse a path into an array containing a root component (at index 0) and zero or more path + * components (at indices > 0). The result is not normalized. + * If the path is relative, the root component is `""`. + * If the path is absolute, the root component includes the first path separator (`/`). + * + * ```ts + * // POSIX + * getPathComponents("/path/to/file.ext") === ["/", "path", "to", "file.ext"] + * getPathComponents("/path/to/") === ["/", "path", "to"] + * getPathComponents("/") === ["/"] + * // DOS + * getPathComponents("c:/path/to/file.ext") === ["c:/", "path", "to", "file.ext"] + * getPathComponents("c:/path/to/") === ["c:/", "path", "to"] + * getPathComponents("c:/") === ["c:/"] + * getPathComponents("c:") === ["c:"] + * // URL + * getPathComponents("http://typescriptlang.org/path/to/file.ext") === ["http://typescriptlang.org/", "path", "to", "file.ext"] + * getPathComponents("http://typescriptlang.org/path/to/") === ["http://typescriptlang.org/", "path", "to"] + * getPathComponents("http://typescriptlang.org/") === ["http://typescriptlang.org/"] + * getPathComponents("http://typescriptlang.org") === ["http://typescriptlang.org"] + * getPathComponents("file://server/path/to/file.ext") === ["file://server/", "path", "to", "file.ext"] + * getPathComponents("file://server/path/to/") === ["file://server/", "path", "to"] + * getPathComponents("file://server/") === ["file://server/"] + * getPathComponents("file://server") === ["file://server"] + * getPathComponents("file:///path/to/file.ext") === ["file:///", "path", "to", "file.ext"] + * getPathComponents("file:///path/to/") === ["file:///", "path", "to"] + * getPathComponents("file:///") === ["file:///"] + * getPathComponents("file://") === ["file://"] + */ +export function getPathComponents(path: string, currentDirectory = "") { + path = combinePaths(currentDirectory, path); + return pathComponents(path, getRootLength(path)); +} + +//// Path Formatting + +/** + * Formats a parsed path consisting of a root component (at index 0) and zero or more path + * segments (at indices > 0). + * + * ```ts + * getPathFromPathComponents(["/", "path", "to", "file.ext"]) === "/path/to/file.ext" + * ``` + */ +export function getPathFromPathComponents(pathComponents: readonly string[]) { + if (pathComponents.length === 0) return ""; + + const root = pathComponents[0] && ensureTrailingDirectorySeparator(pathComponents[0]); + return root + pathComponents.slice(1).join(directorySeparator); +} + +//// Path Normalization + +/** + * Normalize path separators, converting `\` into `/`. + */ +export function normalizeSlashes(path: string): string { + const index = path.indexOf("\\"); + if (index === -1) { + return path; + } + backslashRegExp.lastIndex = index; // prime regex with known position + return path.replace(backslashRegExp, directorySeparator); +} + +/** + * Reduce an array of path components to a more simplified path by navigating any + * `"."` or `".."` entries in the path. + */ +export function reducePathComponents(components: readonly string[]) { + if (!some(components)) return []; + const reduced = [components[0]]; + for (let i = 1; i < components.length; i++) { + const component = components[i]; + if (!component) continue; + if (component === ".") continue; + if (component === "..") { + if (reduced.length > 1) { + if (reduced[reduced.length - 1] !== "..") { + reduced.pop(); + continue; } - else if (reduced[0]) continue; } - reduced.push(component); + else if (reduced[0]) continue; } - return reduced; - } - - /** - * Combines paths. If a path is absolute, it replaces any previous path. Relative paths are not simplified. - * - * ```ts - * // Non-rooted - * combinePaths("path", "to", "file.ext") === "path/to/file.ext" - * combinePaths("path", "dir", "..", "to", "file.ext") === "path/dir/../to/file.ext" - * // POSIX - * combinePaths("/path", "to", "file.ext") === "/path/to/file.ext" - * combinePaths("/path", "/to", "file.ext") === "/to/file.ext" - * // DOS - * combinePaths("c:/path", "to", "file.ext") === "c:/path/to/file.ext" - * combinePaths("c:/path", "c:/to", "file.ext") === "c:/to/file.ext" - * // URL - * combinePaths("file:///path", "to", "file.ext") === "file:///path/to/file.ext" - * combinePaths("file:///path", "file:///to", "file.ext") === "file:///to/file.ext" - * ``` - */ - export function combinePaths(path: string, ...paths: (string | undefined)[]): string { - if (path) path = normalizeSlashes(path); - for (let relativePath of paths) { - if (!relativePath) continue; - relativePath = normalizeSlashes(relativePath); - if (!path || getRootLength(relativePath) !== 0) { - path = relativePath; - } - else { - path = ensureTrailingDirectorySeparator(path) + relativePath; - } + reduced.push(component); + } + return reduced; +} + +/** + * Combines paths. If a path is absolute, it replaces any previous path. Relative paths are not simplified. + * + * ```ts + * // Non-rooted + * combinePaths("path", "to", "file.ext") === "path/to/file.ext" + * combinePaths("path", "dir", "..", "to", "file.ext") === "path/dir/../to/file.ext" + * // POSIX + * combinePaths("/path", "to", "file.ext") === "/path/to/file.ext" + * combinePaths("/path", "/to", "file.ext") === "/to/file.ext" + * // DOS + * combinePaths("c:/path", "to", "file.ext") === "c:/path/to/file.ext" + * combinePaths("c:/path", "c:/to", "file.ext") === "c:/to/file.ext" + * // URL + * combinePaths("file:///path", "to", "file.ext") === "file:///path/to/file.ext" + * combinePaths("file:///path", "file:///to", "file.ext") === "file:///to/file.ext" + * ``` + */ +export function combinePaths(path: string, ...paths: (string | undefined)[]): string { + if (path) path = normalizeSlashes(path); + for (let relativePath of paths) { + if (!relativePath) continue; + relativePath = normalizeSlashes(relativePath); + if (!path || getRootLength(relativePath) !== 0) { + path = relativePath; + } + else { + path = ensureTrailingDirectorySeparator(path) + relativePath; } - return path; } + return path; +} - /** - * Combines and resolves paths. If a path is absolute, it replaces any previous path. Any - * `.` and `..` path components are resolved. Trailing directory separators are preserved. - * - * ```ts - * resolvePath("/path", "to", "file.ext") === "path/to/file.ext" - * resolvePath("/path", "to", "file.ext/") === "path/to/file.ext/" - * resolvePath("/path", "dir", "..", "to", "file.ext") === "path/to/file.ext" - * ``` - */ - export function resolvePath(path: string, ...paths: (string | undefined)[]): string { - return normalizePath(some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path)); - } - - /** - * Parse a path into an array containing a root component (at index 0) and zero or more path - * components (at indices > 0). The result is normalized. - * If the path is relative, the root component is `""`. - * If the path is absolute, the root component includes the first path separator (`/`). - * - * ```ts - * getNormalizedPathComponents("to/dir/../file.ext", "/path/") === ["/", "path", "to", "file.ext"] - * ``` - */ - export function getNormalizedPathComponents(path: string, currentDirectory: string | undefined) { - return reducePathComponents(getPathComponents(path, currentDirectory)); - } - - export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string | undefined) { - return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); - } - - export function normalizePath(path: string): string { - path = normalizeSlashes(path); - // Most paths don't require normalization +/** + * Combines and resolves paths. If a path is absolute, it replaces any previous path. Any + * `.` and `..` path components are resolved. Trailing directory separators are preserved. + * + * ```ts + * resolvePath("/path", "to", "file.ext") === "path/to/file.ext" + * resolvePath("/path", "to", "file.ext/") === "path/to/file.ext/" + * resolvePath("/path", "dir", "..", "to", "file.ext") === "path/to/file.ext" + * ``` + */ +export function resolvePath(path: string, ...paths: (string | undefined)[]): string { + return normalizePath(some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path)); +} + +/** + * Parse a path into an array containing a root component (at index 0) and zero or more path + * components (at indices > 0). The result is normalized. + * If the path is relative, the root component is `""`. + * If the path is absolute, the root component includes the first path separator (`/`). + * + * ```ts + * getNormalizedPathComponents("to/dir/../file.ext", "/path/") === ["/", "path", "to", "file.ext"] + * ``` + */ +export function getNormalizedPathComponents(path: string, currentDirectory: string | undefined) { + return reducePathComponents(getPathComponents(path, currentDirectory)); +} + +export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string | undefined) { + return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); +} + +export function normalizePath(path: string): string { + path = normalizeSlashes(path); + // Most paths don't require normalization + if (!relativePathSegmentRegExp.test(path)) { + return path; + } + // Some paths only require cleanup of `/./` or leading `./` + const simplified = path.replace(/\/\.\//g, "/").replace(/^\.\//, ""); + if (simplified !== path) { + path = simplified; if (!relativePathSegmentRegExp.test(path)) { return path; } - // Some paths only require cleanup of `/./` or leading `./` - const simplified = path.replace(/\/\.\//g, "/").replace(/^\.\//, ""); - if (simplified !== path) { - path = simplified; - if (!relativePathSegmentRegExp.test(path)) { - return path; - } - } - // Other paths require full normalization - const normalized = getPathFromPathComponents(reducePathComponents(getPathComponents(path))); - return normalized && hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalized) : normalized; } + // Other paths require full normalization + const normalized = getPathFromPathComponents(reducePathComponents(getPathComponents(path))); + return normalized && hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalized) : normalized; +} - function getPathWithoutRoot(pathComponents: readonly string[]) { - if (pathComponents.length === 0) return ""; - return pathComponents.slice(1).join(directorySeparator); - } +function getPathWithoutRoot(pathComponents: readonly string[]) { + if (pathComponents.length === 0) return ""; + return pathComponents.slice(1).join(directorySeparator); +} - export function getNormalizedAbsolutePathWithoutRoot(fileName: string, currentDirectory: string | undefined) { - return getPathWithoutRoot(getNormalizedPathComponents(fileName, currentDirectory)); - } +export function getNormalizedAbsolutePathWithoutRoot(fileName: string, currentDirectory: string | undefined) { + return getPathWithoutRoot(getNormalizedPathComponents(fileName, currentDirectory)); +} - export function toPath(fileName: string, basePath: string | undefined, getCanonicalFileName: (path: string) => string): Path { - const nonCanonicalizedPath = isRootedDiskPath(fileName) - ? normalizePath(fileName) - : getNormalizedAbsolutePath(fileName, basePath); - return getCanonicalFileName(nonCanonicalizedPath) as Path; - } +export function toPath(fileName: string, basePath: string | undefined, getCanonicalFileName: (path: string) => string): Path { + const nonCanonicalizedPath = isRootedDiskPath(fileName) + ? normalizePath(fileName) + : getNormalizedAbsolutePath(fileName, basePath); + return getCanonicalFileName(nonCanonicalizedPath) as Path; +} - export function normalizePathAndParts(path: string): { path: string, parts: string[] } { - path = normalizeSlashes(path); - const [root, ...parts] = reducePathComponents(getPathComponents(path)); - if (parts.length) { - const joinedParts = root + parts.join(directorySeparator); - return { path: hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(joinedParts) : joinedParts, parts }; - } - else { - return { path: root, parts }; - } +export function normalizePathAndParts(path: string): { path: string, parts: string[] } { + path = normalizeSlashes(path); + const [root, ...parts] = reducePathComponents(getPathComponents(path)); + if (parts.length) { + const joinedParts = root + parts.join(directorySeparator); + return { path: hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(joinedParts) : joinedParts, parts }; } + else { + return { path: root, parts }; + } +} - //// Path Mutation - - /** - * Removes a trailing directory separator from a path, if it does not already have one. - * - * ```ts - * removeTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext" - * removeTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext" - * ``` - */ - export function removeTrailingDirectorySeparator(path: Path): Path; - export function removeTrailingDirectorySeparator(path: string): string; - export function removeTrailingDirectorySeparator(path: string) { - if (hasTrailingDirectorySeparator(path)) { - return path.substr(0, path.length - 1); - } +//// Path Mutation - return path; +/** + * Removes a trailing directory separator from a path, if it does not already have one. + * + * ```ts + * removeTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext" + * removeTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext" + * ``` + */ +export function removeTrailingDirectorySeparator(path: Path): Path; +export function removeTrailingDirectorySeparator(path: string): string; +export function removeTrailingDirectorySeparator(path: string) { + if (hasTrailingDirectorySeparator(path)) { + return path.substr(0, path.length - 1); } - /** - * Adds a trailing directory separator to a path, if it does not already have one. - * - * ```ts - * ensureTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext/" - * ensureTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext/" - * ``` - */ - export function ensureTrailingDirectorySeparator(path: Path): Path; - export function ensureTrailingDirectorySeparator(path: string): string; - export function ensureTrailingDirectorySeparator(path: string) { - if (!hasTrailingDirectorySeparator(path)) { - return path + directorySeparator; - } + return path; +} - return path; +/** + * Adds a trailing directory separator to a path, if it does not already have one. + * + * ```ts + * ensureTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext/" + * ensureTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext/" + * ``` + */ +export function ensureTrailingDirectorySeparator(path: Path): Path; +export function ensureTrailingDirectorySeparator(path: string): string; +export function ensureTrailingDirectorySeparator(path: string) { + if (!hasTrailingDirectorySeparator(path)) { + return path + directorySeparator; + } + + return path; +} + +/** + * Ensures a path is either absolute (prefixed with `/` or `c:`) or dot-relative (prefixed + * with `./` or `../`) so as not to be confused with an unprefixed module name. + * + * ```ts + * ensurePathIsNonModuleName("/path/to/file.ext") === "/path/to/file.ext" + * ensurePathIsNonModuleName("./path/to/file.ext") === "./path/to/file.ext" + * ensurePathIsNonModuleName("../path/to/file.ext") === "../path/to/file.ext" + * ensurePathIsNonModuleName("path/to/file.ext") === "./path/to/file.ext" + * ``` + */ +export function ensurePathIsNonModuleName(path: string): string { + return !pathIsAbsolute(path) && !pathIsRelative(path) ? "./" + path : path; +} + +/** + * Changes the extension of a path to the provided extension. + * + * ```ts + * changeAnyExtension("/path/to/file.ext", ".js") === "/path/to/file.js" + * ``` + */ +export function changeAnyExtension(path: string, ext: string): string; +/** + * Changes the extension of a path to the provided extension if it has one of the provided extensions. + * + * ```ts + * changeAnyExtension("/path/to/file.ext", ".js", ".ext") === "/path/to/file.js" + * changeAnyExtension("/path/to/file.ext", ".js", ".ts") === "/path/to/file.ext" + * changeAnyExtension("/path/to/file.ext", ".js", [".ext", ".ts"]) === "/path/to/file.js" + * ``` + */ +export function changeAnyExtension(path: string, ext: string, extensions: string | readonly string[], ignoreCase: boolean): string; +export function changeAnyExtension(path: string, ext: string, extensions?: string | readonly string[], ignoreCase?: boolean) { + const pathext = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(path, extensions, ignoreCase) : getAnyExtensionFromPath(path); + return pathext ? path.slice(0, path.length - pathext.length) + (startsWith(ext, ".") ? ext : "." + ext) : path; +} + +//// Path Comparisons + +// check path for these segments: '', '.'. '..' +const relativePathSegmentRegExp = /(?:\/\/)|(?:^|\/)\.\.?(?:$|\/)/; + +function comparePathsWorker(a: string, b: string, componentComparer: (a: string, b: string) => Comparison) { + if (a === b) return Comparison.EqualTo; + if (a === undefined) return Comparison.LessThan; + if (b === undefined) return Comparison.GreaterThan; + + // NOTE: Performance optimization - shortcut if the root segments differ as there would be no + // need to perform path reduction. + const aRoot = a.substring(0, getRootLength(a)); + const bRoot = b.substring(0, getRootLength(b)); + const result = compareStringsCaseInsensitive(aRoot, bRoot); + if (result !== Comparison.EqualTo) { + return result; } - /** - * Ensures a path is either absolute (prefixed with `/` or `c:`) or dot-relative (prefixed - * with `./` or `../`) so as not to be confused with an unprefixed module name. - * - * ```ts - * ensurePathIsNonModuleName("/path/to/file.ext") === "/path/to/file.ext" - * ensurePathIsNonModuleName("./path/to/file.ext") === "./path/to/file.ext" - * ensurePathIsNonModuleName("../path/to/file.ext") === "../path/to/file.ext" - * ensurePathIsNonModuleName("path/to/file.ext") === "./path/to/file.ext" - * ``` - */ - export function ensurePathIsNonModuleName(path: string): string { - return !pathIsAbsolute(path) && !pathIsRelative(path) ? "./" + path : path; - } - - /** - * Changes the extension of a path to the provided extension. - * - * ```ts - * changeAnyExtension("/path/to/file.ext", ".js") === "/path/to/file.js" - * ``` - */ - export function changeAnyExtension(path: string, ext: string): string; - /** - * Changes the extension of a path to the provided extension if it has one of the provided extensions. - * - * ```ts - * changeAnyExtension("/path/to/file.ext", ".js", ".ext") === "/path/to/file.js" - * changeAnyExtension("/path/to/file.ext", ".js", ".ts") === "/path/to/file.ext" - * changeAnyExtension("/path/to/file.ext", ".js", [".ext", ".ts"]) === "/path/to/file.js" - * ``` - */ - export function changeAnyExtension(path: string, ext: string, extensions: string | readonly string[], ignoreCase: boolean): string; - export function changeAnyExtension(path: string, ext: string, extensions?: string | readonly string[], ignoreCase?: boolean) { - const pathext = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(path, extensions, ignoreCase) : getAnyExtensionFromPath(path); - return pathext ? path.slice(0, path.length - pathext.length) + (startsWith(ext, ".") ? ext : "." + ext) : path; - } - - //// Path Comparisons - - // check path for these segments: '', '.'. '..' - const relativePathSegmentRegExp = /(?:\/\/)|(?:^|\/)\.\.?(?:$|\/)/; - - function comparePathsWorker(a: string, b: string, componentComparer: (a: string, b: string) => Comparison) { - if (a === b) return Comparison.EqualTo; - if (a === undefined) return Comparison.LessThan; - if (b === undefined) return Comparison.GreaterThan; - - // NOTE: Performance optimization - shortcut if the root segments differ as there would be no - // need to perform path reduction. - const aRoot = a.substring(0, getRootLength(a)); - const bRoot = b.substring(0, getRootLength(b)); - const result = compareStringsCaseInsensitive(aRoot, bRoot); + // NOTE: Performance optimization - shortcut if there are no relative path segments in + // the non-root portion of the path + const aRest = a.substring(aRoot.length); + const bRest = b.substring(bRoot.length); + if (!relativePathSegmentRegExp.test(aRest) && !relativePathSegmentRegExp.test(bRest)) { + return componentComparer(aRest, bRest); + } + + // The path contains a relative path segment. Normalize the paths and perform a slower component + // by component comparison. + const aComponents = reducePathComponents(getPathComponents(a)); + const bComponents = reducePathComponents(getPathComponents(b)); + const sharedLength = Math.min(aComponents.length, bComponents.length); + for (let i = 1; i < sharedLength; i++) { + const result = componentComparer(aComponents[i], bComponents[i]); if (result !== Comparison.EqualTo) { return result; } + } + return compareValues(aComponents.length, bComponents.length); +} - // NOTE: Performance optimization - shortcut if there are no relative path segments in - // the non-root portion of the path - const aRest = a.substring(aRoot.length); - const bRest = b.substring(bRoot.length); - if (!relativePathSegmentRegExp.test(aRest) && !relativePathSegmentRegExp.test(bRest)) { - return componentComparer(aRest, bRest); - } +/** + * Performs a case-sensitive comparison of two paths. Path roots are always compared case-insensitively. + */ +export function comparePathsCaseSensitive(a: string, b: string) { + return comparePathsWorker(a, b, compareStringsCaseSensitive); +} - // The path contains a relative path segment. Normalize the paths and perform a slower component - // by component comparison. - const aComponents = reducePathComponents(getPathComponents(a)); - const bComponents = reducePathComponents(getPathComponents(b)); - const sharedLength = Math.min(aComponents.length, bComponents.length); - for (let i = 1; i < sharedLength; i++) { - const result = componentComparer(aComponents[i], bComponents[i]); - if (result !== Comparison.EqualTo) { - return result; - } - } - return compareValues(aComponents.length, bComponents.length); - } +/** + * Performs a case-insensitive comparison of two paths. + */ +export function comparePathsCaseInsensitive(a: string, b: string) { + return comparePathsWorker(a, b, compareStringsCaseInsensitive); +} - /** - * Performs a case-sensitive comparison of two paths. Path roots are always compared case-insensitively. - */ - export function comparePathsCaseSensitive(a: string, b: string) { - return comparePathsWorker(a, b, compareStringsCaseSensitive); +/** + * Compare two paths using the provided case sensitivity. + */ +export function comparePaths(a: string, b: string, ignoreCase?: boolean): Comparison; +export function comparePaths(a: string, b: string, currentDirectory: string, ignoreCase?: boolean): Comparison; +export function comparePaths(a: string, b: string, currentDirectory?: string | boolean, ignoreCase?: boolean) { + if (typeof currentDirectory === "string") { + a = combinePaths(currentDirectory, a); + b = combinePaths(currentDirectory, b); } + else if (typeof currentDirectory === "boolean") { + ignoreCase = currentDirectory; + } + return comparePathsWorker(a, b, getStringComparer(ignoreCase)); +} - /** - * Performs a case-insensitive comparison of two paths. - */ - export function comparePathsCaseInsensitive(a: string, b: string) { - return comparePathsWorker(a, b, compareStringsCaseInsensitive); +/** + * Determines whether a `parent` path contains a `child` path using the provide case sensitivity. + */ +export function containsPath(parent: string, child: string, ignoreCase?: boolean): boolean; +export function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean): boolean; +export function containsPath(parent: string, child: string, currentDirectory?: string | boolean, ignoreCase?: boolean) { + if (typeof currentDirectory === "string") { + parent = combinePaths(currentDirectory, parent); + child = combinePaths(currentDirectory, child); + } + else if (typeof currentDirectory === "boolean") { + ignoreCase = currentDirectory; + } + if (parent === undefined || child === undefined) return false; + if (parent === child) return true; + const parentComponents = reducePathComponents(getPathComponents(parent)); + const childComponents = reducePathComponents(getPathComponents(child)); + if (childComponents.length < parentComponents.length) { + return false; } - /** - * Compare two paths using the provided case sensitivity. - */ - export function comparePaths(a: string, b: string, ignoreCase?: boolean): Comparison; - export function comparePaths(a: string, b: string, currentDirectory: string, ignoreCase?: boolean): Comparison; - export function comparePaths(a: string, b: string, currentDirectory?: string | boolean, ignoreCase?: boolean) { - if (typeof currentDirectory === "string") { - a = combinePaths(currentDirectory, a); - b = combinePaths(currentDirectory, b); - } - else if (typeof currentDirectory === "boolean") { - ignoreCase = currentDirectory; - } - return comparePathsWorker(a, b, getStringComparer(ignoreCase)); - } - - /** - * Determines whether a `parent` path contains a `child` path using the provide case sensitivity. - */ - export function containsPath(parent: string, child: string, ignoreCase?: boolean): boolean; - export function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean): boolean; - export function containsPath(parent: string, child: string, currentDirectory?: string | boolean, ignoreCase?: boolean) { - if (typeof currentDirectory === "string") { - parent = combinePaths(currentDirectory, parent); - child = combinePaths(currentDirectory, child); - } - else if (typeof currentDirectory === "boolean") { - ignoreCase = currentDirectory; - } - if (parent === undefined || child === undefined) return false; - if (parent === child) return true; - const parentComponents = reducePathComponents(getPathComponents(parent)); - const childComponents = reducePathComponents(getPathComponents(child)); - if (childComponents.length < parentComponents.length) { + const componentEqualityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive; + for (let i = 0; i < parentComponents.length; i++) { + const equalityComparer = i === 0 ? equateStringsCaseInsensitive : componentEqualityComparer; + if (!equalityComparer(parentComponents[i], childComponents[i])) { return false; } + } - const componentEqualityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive; - for (let i = 0; i < parentComponents.length; i++) { - const equalityComparer = i === 0 ? equateStringsCaseInsensitive : componentEqualityComparer; - if (!equalityComparer(parentComponents[i], childComponents[i])) { - return false; - } - } + return true; +} - return true; +/** + * Determines whether `fileName` starts with the specified `directoryName` using the provided path canonicalization callback. + * Comparison is case-sensitive between the canonical paths. + * + * Use `containsPath` if file names are not already reduced and absolute. + */ +export function startsWithDirectory(fileName: string, directoryName: string, getCanonicalFileName: GetCanonicalFileName): boolean { + const canonicalFileName = getCanonicalFileName(fileName); + const canonicalDirectoryName = getCanonicalFileName(directoryName); + return startsWith(canonicalFileName, canonicalDirectoryName + "/") || startsWith(canonicalFileName, canonicalDirectoryName + "\\"); +} + +//// Relative Paths + +export function getPathComponentsRelativeTo(from: string, to: string, stringEqualityComparer: (a: string, b: string) => boolean, getCanonicalFileName: GetCanonicalFileName) { + const fromComponents = reducePathComponents(getPathComponents(from)); + const toComponents = reducePathComponents(getPathComponents(to)); + + let start: number; + for (start = 0; start < fromComponents.length && start < toComponents.length; start++) { + const fromComponent = getCanonicalFileName(fromComponents[start]); + const toComponent = getCanonicalFileName(toComponents[start]); + const comparer = start === 0 ? equateStringsCaseInsensitive : stringEqualityComparer; + if (!comparer(fromComponent, toComponent)) break; } - /** - * Determines whether `fileName` starts with the specified `directoryName` using the provided path canonicalization callback. - * Comparison is case-sensitive between the canonical paths. - * - * Use `containsPath` if file names are not already reduced and absolute. - */ - export function startsWithDirectory(fileName: string, directoryName: string, getCanonicalFileName: GetCanonicalFileName): boolean { - const canonicalFileName = getCanonicalFileName(fileName); - const canonicalDirectoryName = getCanonicalFileName(directoryName); - return startsWith(canonicalFileName, canonicalDirectoryName + "/") || startsWith(canonicalFileName, canonicalDirectoryName + "\\"); + if (start === 0) { + return toComponents; } - //// Relative Paths + const components = toComponents.slice(start); + const relative: string[] = []; + for (; start < fromComponents.length; start++) { + relative.push(".."); + } + return ["", ...relative, ...components]; +} - export function getPathComponentsRelativeTo(from: string, to: string, stringEqualityComparer: (a: string, b: string) => boolean, getCanonicalFileName: GetCanonicalFileName) { - const fromComponents = reducePathComponents(getPathComponents(from)); - const toComponents = reducePathComponents(getPathComponents(to)); +/** + * Gets a relative path that can be used to traverse between `from` and `to`. + */ +export function getRelativePathFromDirectory(from: string, to: string, ignoreCase: boolean): string; +/** + * Gets a relative path that can be used to traverse between `from` and `to`. + */ +export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileName: GetCanonicalFileName): string; // eslint-disable-line @typescript-eslint/unified-signatures +export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean) { + Debug.assert((getRootLength(fromDirectory) > 0) === (getRootLength(to) > 0), "Paths must either both be absolute or both be relative"); + const getCanonicalFileName = typeof getCanonicalFileNameOrIgnoreCase === "function" ? getCanonicalFileNameOrIgnoreCase : identity; + const ignoreCase = typeof getCanonicalFileNameOrIgnoreCase === "boolean" ? getCanonicalFileNameOrIgnoreCase : false; + const pathComponents = getPathComponentsRelativeTo(fromDirectory, to, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive, getCanonicalFileName); + return getPathFromPathComponents(pathComponents); +} - let start: number; - for (start = 0; start < fromComponents.length && start < toComponents.length; start++) { - const fromComponent = getCanonicalFileName(fromComponents[start]); - const toComponent = getCanonicalFileName(toComponents[start]); - const comparer = start === 0 ? equateStringsCaseInsensitive : stringEqualityComparer; - if (!comparer(fromComponent, toComponent)) break; - } +export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string { + return !isRootedDiskPath(absoluteOrRelativePath) + ? absoluteOrRelativePath + : getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); +} - if (start === 0) { - return toComponents; - } +export function getRelativePathFromFile(from: string, to: string, getCanonicalFileName: GetCanonicalFileName) { + return ensurePathIsNonModuleName(getRelativePathFromDirectory(getDirectoryPath(from), to, getCanonicalFileName)); +} - const components = toComponents.slice(start); - const relative: string[] = []; - for (; start < fromComponents.length; start++) { - relative.push(".."); - } - return ["", ...relative, ...components]; - } - - /** - * Gets a relative path that can be used to traverse between `from` and `to`. - */ - export function getRelativePathFromDirectory(from: string, to: string, ignoreCase: boolean): string; - /** - * Gets a relative path that can be used to traverse between `from` and `to`. - */ - export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileName: GetCanonicalFileName): string; // eslint-disable-line @typescript-eslint/unified-signatures - export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean) { - Debug.assert((getRootLength(fromDirectory) > 0) === (getRootLength(to) > 0), "Paths must either both be absolute or both be relative"); - const getCanonicalFileName = typeof getCanonicalFileNameOrIgnoreCase === "function" ? getCanonicalFileNameOrIgnoreCase : identity; - const ignoreCase = typeof getCanonicalFileNameOrIgnoreCase === "boolean" ? getCanonicalFileNameOrIgnoreCase : false; - const pathComponents = getPathComponentsRelativeTo(fromDirectory, to, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive, getCanonicalFileName); - return getPathFromPathComponents(pathComponents); - } - - export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string { - return !isRootedDiskPath(absoluteOrRelativePath) - ? absoluteOrRelativePath - : getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); - } - - export function getRelativePathFromFile(from: string, to: string, getCanonicalFileName: GetCanonicalFileName) { - return ensurePathIsNonModuleName(getRelativePathFromDirectory(getDirectoryPath(from), to, getCanonicalFileName)); - } - - export function getRelativePathToDirectoryOrUrl(directoryPathOrUrl: string, relativeOrAbsolutePath: string, currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, isAbsolutePathAnUrl: boolean) { - const pathComponents = getPathComponentsRelativeTo( - resolvePath(currentDirectory, directoryPathOrUrl), - resolvePath(currentDirectory, relativeOrAbsolutePath), - equateStringsCaseSensitive, - getCanonicalFileName - ); - - const firstComponent = pathComponents[0]; - if (isAbsolutePathAnUrl && isRootedDiskPath(firstComponent)) { - const prefix = firstComponent.charAt(0) === directorySeparator ? "file://" : "file:///"; - pathComponents[0] = prefix + firstComponent; - } +export function getRelativePathToDirectoryOrUrl(directoryPathOrUrl: string, relativeOrAbsolutePath: string, currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, isAbsolutePathAnUrl: boolean) { + const pathComponents = getPathComponentsRelativeTo( + resolvePath(currentDirectory, directoryPathOrUrl), + resolvePath(currentDirectory, relativeOrAbsolutePath), + equateStringsCaseSensitive, + getCanonicalFileName + ); - return getPathFromPathComponents(pathComponents); + const firstComponent = pathComponents[0]; + if (isAbsolutePathAnUrl && isRootedDiskPath(firstComponent)) { + const prefix = firstComponent.charAt(0) === directorySeparator ? "file://" : "file:///"; + pathComponents[0] = prefix + firstComponent; } - //// Path Traversal - - /** - * Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. - */ - export function forEachAncestorDirectory(directory: Path, callback: (directory: Path) => T | undefined): T | undefined; - export function forEachAncestorDirectory(directory: string, callback: (directory: string) => T | undefined): T | undefined; - export function forEachAncestorDirectory(directory: Path, callback: (directory: Path) => T | undefined): T | undefined { - while (true) { - const result = callback(directory); - if (result !== undefined) { - return result; - } + return getPathFromPathComponents(pathComponents); +} - const parentPath = getDirectoryPath(directory); - if (parentPath === directory) { - return undefined; - } +//// Path Traversal + +/** + * Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. + */ +export function forEachAncestorDirectory(directory: Path, callback: (directory: Path) => T | undefined): T | undefined; +export function forEachAncestorDirectory(directory: string, callback: (directory: string) => T | undefined): T | undefined; +export function forEachAncestorDirectory(directory: Path, callback: (directory: Path) => T | undefined): T | undefined { + while (true) { + const result = callback(directory); + if (result !== undefined) { + return result; + } - directory = parentPath; + const parentPath = getDirectoryPath(directory); + if (parentPath === directory) { + return undefined; } - } - export function isNodeModulesDirectory(dirPath: Path) { - return endsWith(dirPath, "/node_modules"); + directory = parentPath; } } + +export function isNodeModulesDirectory(dirPath: Path) { + return endsWith(dirPath, "/node_modules"); +} +} diff --git a/src/compiler/perfLogger.ts b/src/compiler/perfLogger.ts index f05e8a3bd1053..e67bb3a00eca3 100644 --- a/src/compiler/perfLogger.ts +++ b/src/compiler/perfLogger.ts @@ -1,43 +1,43 @@ /* @internal */ namespace ts { - type PerfLogger = typeof import("@microsoft/typescript-etw"); - const nullLogger: PerfLogger = { - logEvent: noop, - logErrEvent: noop, - logPerfEvent: noop, - logInfoEvent: noop, - logStartCommand: noop, - logStopCommand: noop, - logStartUpdateProgram: noop, - logStopUpdateProgram: noop, - logStartUpdateGraph: noop, - logStopUpdateGraph: noop, - logStartResolveModule: noop, - logStopResolveModule: noop, - logStartParseSourceFile: noop, - logStopParseSourceFile: noop, - logStartReadFile: noop, - logStopReadFile: noop, - logStartBindFile: noop, - logStopBindFile: noop, - logStartScheduledOperation: noop, - logStopScheduledOperation: noop, - }; +type PerfLogger = typeof import("@microsoft/typescript-etw"); +const nullLogger: PerfLogger = { + logEvent: noop, + logErrEvent: noop, + logPerfEvent: noop, + logInfoEvent: noop, + logStartCommand: noop, + logStopCommand: noop, + logStartUpdateProgram: noop, + logStopUpdateProgram: noop, + logStartUpdateGraph: noop, + logStopUpdateGraph: noop, + logStartResolveModule: noop, + logStopResolveModule: noop, + logStartParseSourceFile: noop, + logStopParseSourceFile: noop, + logStartReadFile: noop, + logStopReadFile: noop, + logStartBindFile: noop, + logStopBindFile: noop, + logStartScheduledOperation: noop, + logStopScheduledOperation: noop, +}; - // Load optional module to enable Event Tracing for Windows - // See https://github.com/microsoft/typescript-etw for more information - let etwModule; - try { - const etwModulePath = process.env.TS_ETW_MODULE_PATH ?? "./node_modules/@microsoft/typescript-etw"; +// Load optional module to enable Event Tracing for Windows +// See https://github.com/microsoft/typescript-etw for more information +let etwModule; +try { + const etwModulePath = process.env.TS_ETW_MODULE_PATH ?? "./node_modules/@microsoft/typescript-etw"; - // require() will throw an exception if the module is not found - // It may also return undefined if not installed properly - etwModule = require(etwModulePath); - } - catch (e) { - etwModule = undefined; - } + // require() will throw an exception if the module is not found + // It may also return undefined if not installed properly + etwModule = require(etwModulePath); +} +catch (e) { + etwModule = undefined; +} - /** Performance logger that will generate ETW events if possible - check for `logEvent` member, as `etwModule` will be `{}` when browserified */ - export const perfLogger: PerfLogger = etwModule && etwModule.logEvent ? etwModule : nullLogger; +/** Performance logger that will generate ETW events if possible - check for `logEvent` member, as `etwModule` will be `{}` when browserified */ +export const perfLogger: PerfLogger = etwModule && etwModule.logEvent ? etwModule : nullLogger; } diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index 3f6c23f2b0735..9c0013bf91fb8 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -1,146 +1,146 @@ /*@internal*/ /** Performance measurements for the compiler. */ namespace ts.performance { - let perfHooks: PerformanceHooks | undefined; - // when set, indicates the implementation of `Performance` to use for user timing. - // when unset, indicates user timing is unavailable or disabled. - let performanceImpl: Performance | undefined; +let perfHooks: PerformanceHooks | undefined; +// when set, indicates the implementation of `Performance` to use for user timing. +// when unset, indicates user timing is unavailable or disabled. +let performanceImpl: Performance | undefined; - export interface Timer { - enter(): void; - exit(): void; - } +export interface Timer { + enter(): void; + exit(): void; +} - export function createTimerIf(condition: boolean, measureName: string, startMarkName: string, endMarkName: string) { - return condition ? createTimer(measureName, startMarkName, endMarkName) : nullTimer; - } +export function createTimerIf(condition: boolean, measureName: string, startMarkName: string, endMarkName: string) { + return condition ? createTimer(measureName, startMarkName, endMarkName) : nullTimer; +} - export function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer { - let enterCount = 0; - return { - enter, - exit - }; +export function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer { + let enterCount = 0; + return { + enter, + exit + }; - function enter() { - if (++enterCount === 1) { - mark(startMarkName); - } + function enter() { + if (++enterCount === 1) { + mark(startMarkName); } + } - function exit() { - if (--enterCount === 0) { - mark(endMarkName); - measure(measureName, startMarkName, endMarkName); - } - else if (enterCount < 0) { - Debug.fail("enter/exit count does not match."); - } + function exit() { + if (--enterCount === 0) { + mark(endMarkName); + measure(measureName, startMarkName, endMarkName); + } + else if (enterCount < 0) { + Debug.fail("enter/exit count does not match."); } } +} - export const nullTimer: Timer = { enter: noop, exit: noop }; +export const nullTimer: Timer = { enter: noop, exit: noop }; - let enabled = false; - let timeorigin = timestamp(); - const marks = new Map(); - const counts = new Map(); - const durations = new Map(); +let enabled = false; +let timeorigin = timestamp(); +const marks = new Map(); +const counts = new Map(); +const durations = new Map(); - /** - * Marks a performance event. - * - * @param markName The name of the mark. - */ - export function mark(markName: string) { - if (enabled) { - const count = counts.get(markName) ?? 0; - counts.set(markName, count + 1); - marks.set(markName, timestamp()); - performanceImpl?.mark(markName); - } +/** + * Marks a performance event. + * + * @param markName The name of the mark. + */ +export function mark(markName: string) { + if (enabled) { + const count = counts.get(markName) ?? 0; + counts.set(markName, count + 1); + marks.set(markName, timestamp()); + performanceImpl?.mark(markName); } +} - /** - * Adds a performance measurement with the specified name. - * - * @param measureName The name of the performance measurement. - * @param startMarkName The name of the starting mark. If not supplied, the point at which the - * profiler was enabled is used. - * @param endMarkName The name of the ending mark. If not supplied, the current timestamp is - * used. - */ - export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { - if (enabled) { - const end = (endMarkName !== undefined ? marks.get(endMarkName) : undefined) ?? timestamp(); - const start = (startMarkName !== undefined ? marks.get(startMarkName) : undefined) ?? timeorigin; - const previousDuration = durations.get(measureName) || 0; - durations.set(measureName, previousDuration + (end - start)); - performanceImpl?.measure(measureName, startMarkName, endMarkName); - } +/** + * Adds a performance measurement with the specified name. + * + * @param measureName The name of the performance measurement. + * @param startMarkName The name of the starting mark. If not supplied, the point at which the + * profiler was enabled is used. + * @param endMarkName The name of the ending mark. If not supplied, the current timestamp is + * used. + */ +export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { + if (enabled) { + const end = (endMarkName !== undefined ? marks.get(endMarkName) : undefined) ?? timestamp(); + const start = (startMarkName !== undefined ? marks.get(startMarkName) : undefined) ?? timeorigin; + const previousDuration = durations.get(measureName) || 0; + durations.set(measureName, previousDuration + (end - start)); + performanceImpl?.measure(measureName, startMarkName, endMarkName); } +} - /** - * Gets the number of times a marker was encountered. - * - * @param markName The name of the mark. - */ - export function getCount(markName: string) { - return counts.get(markName) || 0; - } +/** + * Gets the number of times a marker was encountered. + * + * @param markName The name of the mark. + */ +export function getCount(markName: string) { + return counts.get(markName) || 0; +} - /** - * Gets the total duration of all measurements with the supplied name. - * - * @param measureName The name of the measure whose durations should be accumulated. - */ - export function getDuration(measureName: string) { - return durations.get(measureName) || 0; - } +/** + * Gets the total duration of all measurements with the supplied name. + * + * @param measureName The name of the measure whose durations should be accumulated. + */ +export function getDuration(measureName: string) { + return durations.get(measureName) || 0; +} - /** - * Iterate over each measure, performing some action - * - * @param cb The action to perform for each measure - */ - export function forEachMeasure(cb: (measureName: string, duration: number) => void) { - durations.forEach((duration, measureName) => cb(measureName, duration)); - } +/** + * Iterate over each measure, performing some action + * + * @param cb The action to perform for each measure + */ +export function forEachMeasure(cb: (measureName: string, duration: number) => void) { + durations.forEach((duration, measureName) => cb(measureName, duration)); +} - /** - * Indicates whether the performance API is enabled. - */ - export function isEnabled() { - return enabled; - } +/** + * Indicates whether the performance API is enabled. + */ +export function isEnabled() { + return enabled; +} - /** Enables (and resets) performance measurements for the compiler. */ - export function enable(system: System = sys) { - if (!enabled) { - enabled = true; - perfHooks ||= tryGetNativePerformanceHooks(); - if (perfHooks) { - timeorigin = perfHooks.performance.timeOrigin; - // NodeJS's Web Performance API is currently slower than expected, but we'd still like - // to be able to leverage native trace events when node is run with either `--cpu-prof` - // or `--prof`, if we're running with our own `--generateCpuProfile` flag, or when - // running in debug mode (since its possible to generate a cpu profile while debugging). - if (perfHooks.shouldWriteNativeEvents || system?.cpuProfilingEnabled?.() || system?.debugMode) { - performanceImpl = perfHooks.performance; - } +/** Enables (and resets) performance measurements for the compiler. */ +export function enable(system: System = sys) { + if (!enabled) { + enabled = true; + perfHooks ||= tryGetNativePerformanceHooks(); + if (perfHooks) { + timeorigin = perfHooks.performance.timeOrigin; + // NodeJS's Web Performance API is currently slower than expected, but we'd still like + // to be able to leverage native trace events when node is run with either `--cpu-prof` + // or `--prof`, if we're running with our own `--generateCpuProfile` flag, or when + // running in debug mode (since its possible to generate a cpu profile while debugging). + if (perfHooks.shouldWriteNativeEvents || system?.cpuProfilingEnabled?.() || system?.debugMode) { + performanceImpl = perfHooks.performance; } } - return true; } + return true; +} - /** Disables performance measurements for the compiler. */ - export function disable() { - if (enabled) { - marks.clear(); - counts.clear(); - durations.clear(); - performanceImpl = undefined; - enabled = false; - } +/** Disables performance measurements for the compiler. */ +export function disable() { + if (enabled) { + marks.clear(); + counts.clear(); + durations.clear(); + performanceImpl = undefined; + enabled = false; } } +} diff --git a/src/compiler/performanceCore.ts b/src/compiler/performanceCore.ts index ac40726ebb273..89bde7449db18 100644 --- a/src/compiler/performanceCore.ts +++ b/src/compiler/performanceCore.ts @@ -1,129 +1,129 @@ /*@internal*/ namespace ts { - // The following definitions provide the minimum compatible support for the Web Performance User Timings API - // between browsers and NodeJS: +// The following definitions provide the minimum compatible support for the Web Performance User Timings API +// between browsers and NodeJS: - export interface PerformanceHooks { - /** Indicates whether we should write native performance events */ - shouldWriteNativeEvents: boolean; - performance: Performance; - PerformanceObserver: PerformanceObserverConstructor; - } +export interface PerformanceHooks { + /** Indicates whether we should write native performance events */ + shouldWriteNativeEvents: boolean; + performance: Performance; + PerformanceObserver: PerformanceObserverConstructor; +} - export interface Performance { - mark(name: string): void; - measure(name: string, startMark?: string, endMark?: string): void; - now(): number; - timeOrigin: number; - } +export interface Performance { + mark(name: string): void; + measure(name: string, startMark?: string, endMark?: string): void; + now(): number; + timeOrigin: number; +} - export interface PerformanceEntry { - name: string; - entryType: string; - startTime: number; - duration: number; - } +export interface PerformanceEntry { + name: string; + entryType: string; + startTime: number; + duration: number; +} - export interface PerformanceObserverEntryList { - getEntries(): PerformanceEntryList; - getEntriesByName(name: string, type?: string): PerformanceEntryList; - getEntriesByType(type: string): PerformanceEntryList; - } +export interface PerformanceObserverEntryList { + getEntries(): PerformanceEntryList; + getEntriesByName(name: string, type?: string): PerformanceEntryList; + getEntriesByType(type: string): PerformanceEntryList; +} - export interface PerformanceObserver { - disconnect(): void; - observe(options: { entryTypes: readonly ("mark" | "measure")[] }): void; - } +export interface PerformanceObserver { + disconnect(): void; + observe(options: { entryTypes: readonly ("mark" | "measure")[] }): void; +} - export type PerformanceObserverConstructor = new (callback: (list: PerformanceObserverEntryList, observer: PerformanceObserver) => void) => PerformanceObserver; - export type PerformanceEntryList = PerformanceEntry[]; +export type PerformanceObserverConstructor = new (callback: (list: PerformanceObserverEntryList, observer: PerformanceObserver) => void) => PerformanceObserver; +export type PerformanceEntryList = PerformanceEntry[]; - // Browser globals for the Web Performance User Timings API - declare const process: any; - declare const performance: Performance | undefined; - declare const PerformanceObserver: PerformanceObserverConstructor | undefined; +// Browser globals for the Web Performance User Timings API +declare const process: any; +declare const performance: Performance | undefined; +declare const PerformanceObserver: PerformanceObserverConstructor | undefined; - // eslint-disable-next-line @typescript-eslint/naming-convention - function hasRequiredAPI(performance: Performance | undefined, PerformanceObserver: PerformanceObserverConstructor | undefined) { - return typeof performance === "object" && - typeof performance.timeOrigin === "number" && - typeof performance.mark === "function" && - typeof performance.measure === "function" && - typeof performance.now === "function" && - typeof PerformanceObserver === "function"; - } +// eslint-disable-next-line @typescript-eslint/naming-convention +function hasRequiredAPI(performance: Performance | undefined, PerformanceObserver: PerformanceObserverConstructor | undefined) { + return typeof performance === "object" && + typeof performance.timeOrigin === "number" && + typeof performance.mark === "function" && + typeof performance.measure === "function" && + typeof performance.now === "function" && + typeof PerformanceObserver === "function"; +} - function tryGetWebPerformanceHooks(): PerformanceHooks | undefined { - if (typeof performance === "object" && - typeof PerformanceObserver === "function" && - hasRequiredAPI(performance, PerformanceObserver)) { - return { - // For now we always write native performance events when running in the browser. We may - // make this conditional in the future if we find that native web performance hooks - // in the browser also slow down compilation. - shouldWriteNativeEvents: true, - performance, - PerformanceObserver - }; - } +function tryGetWebPerformanceHooks(): PerformanceHooks | undefined { + if (typeof performance === "object" && + typeof PerformanceObserver === "function" && + hasRequiredAPI(performance, PerformanceObserver)) { + return { + // For now we always write native performance events when running in the browser. We may + // make this conditional in the future if we find that native web performance hooks + // in the browser also slow down compilation. + shouldWriteNativeEvents: true, + performance, + PerformanceObserver + }; } +} - function tryGetNodePerformanceHooks(): PerformanceHooks | undefined { - if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof module === "object" && typeof require === "function") { - try { - let performance: Performance; - const { performance: nodePerformance, PerformanceObserver } = require("perf_hooks") as typeof import("perf_hooks"); - if (hasRequiredAPI(nodePerformance, PerformanceObserver)) { - performance = nodePerformance; - // There is a bug in Node's performance.measure prior to 12.16.3/13.13.0 that does not - // match the Web Performance API specification. Node's implementation did not allow - // optional `start` and `end` arguments for `performance.measure`. - // See https://github.com/nodejs/node/pull/32651 for more information. - const version = new Version(process.versions.node); - const range = new VersionRange("<12.16.3 || 13 <13.13"); - if (range.test(version)) { - performance = { - get timeOrigin() { return nodePerformance.timeOrigin; }, - now() { return nodePerformance.now(); }, - mark(name) { return nodePerformance.mark(name); }, - measure(name, start = "nodeStart", end?) { - if (end === undefined) { - end = "__performance.measure-fix__"; - nodePerformance.mark(end); - } - nodePerformance.measure(name, start, end); - if (end === "__performance.measure-fix__") { - nodePerformance.clearMarks("__performance.measure-fix__"); - } +function tryGetNodePerformanceHooks(): PerformanceHooks | undefined { + if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof module === "object" && typeof require === "function") { + try { + let performance: Performance; + const { performance: nodePerformance, PerformanceObserver } = require("perf_hooks") as typeof import("perf_hooks"); + if (hasRequiredAPI(nodePerformance, PerformanceObserver)) { + performance = nodePerformance; + // There is a bug in Node's performance.measure prior to 12.16.3/13.13.0 that does not + // match the Web Performance API specification. Node's implementation did not allow + // optional `start` and `end` arguments for `performance.measure`. + // See https://github.com/nodejs/node/pull/32651 for more information. + const version = new Version(process.versions.node); + const range = new VersionRange("<12.16.3 || 13 <13.13"); + if (range.test(version)) { + performance = { + get timeOrigin() { return nodePerformance.timeOrigin; }, + now() { return nodePerformance.now(); }, + mark(name) { return nodePerformance.mark(name); }, + measure(name, start = "nodeStart", end?) { + if (end === undefined) { + end = "__performance.measure-fix__"; + nodePerformance.mark(end); + } + nodePerformance.measure(name, start, end); + if (end === "__performance.measure-fix__") { + nodePerformance.clearMarks("__performance.measure-fix__"); } - }; - } - return { - // By default, only write native events when generating a cpu profile or using the v8 profiler. - shouldWriteNativeEvents: false, - performance, - PerformanceObserver + } }; } - } - catch { - // ignore errors + return { + // By default, only write native events when generating a cpu profile or using the v8 profiler. + shouldWriteNativeEvents: false, + performance, + PerformanceObserver + }; } } + catch { + // ignore errors + } } +} - // Unlike with the native Map/Set 'tryGet' functions in corePublic.ts, we eagerly evaluate these - // since we will need them for `timestamp`, below. - const nativePerformanceHooks = tryGetWebPerformanceHooks() || tryGetNodePerformanceHooks(); - const nativePerformance = nativePerformanceHooks?.performance; +// Unlike with the native Map/Set 'tryGet' functions in corePublic.ts, we eagerly evaluate these +// since we will need them for `timestamp`, below. +const nativePerformanceHooks = tryGetWebPerformanceHooks() || tryGetNodePerformanceHooks(); +const nativePerformance = nativePerformanceHooks?.performance; - export function tryGetNativePerformanceHooks() { - return nativePerformanceHooks; - } +export function tryGetNativePerformanceHooks() { + return nativePerformanceHooks; +} - /** Gets a timestamp with (at least) ms resolution */ - export const timestamp = - nativePerformance ? () => nativePerformance.now() : - Date.now ? Date.now : - () => +(new Date()); -} \ No newline at end of file +/** Gets a timestamp with (at least) ms resolution */ +export const timestamp = + nativePerformance ? () => nativePerformance.now() : + Date.now ? Date.now : + () => +(new Date()); +} diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 8801f94dfa258..7f412b6ac5b8a 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1,2795 +1,2698 @@ namespace ts { - export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string | undefined { - return forEachAncestorDirectory(searchPath, ancestor => { - const fileName = combinePaths(ancestor, configName); - return fileExists(fileName) ? fileName : undefined; - }); - } - - export function resolveTripleslashReference(moduleName: string, containingFile: string): string { - const basePath = getDirectoryPath(containingFile); - const referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); - return normalizePath(referencedFileName); - } - - /* @internal */ - export function computeCommonSourceDirectoryOfFilenames(fileNames: readonly string[], currentDirectory: string, getCanonicalFileName: GetCanonicalFileName): string { - let commonPathComponents: string[] | undefined; - const failed = forEach(fileNames, sourceFile => { - // Each file contributes into common source file path - const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory); - sourcePathComponents.pop(); // The base file name is not part of the common directory path +export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string | undefined { + return forEachAncestorDirectory(searchPath, ancestor => { + const fileName = combinePaths(ancestor, configName); + return fileExists(fileName) ? fileName : undefined; + }); +} - if (!commonPathComponents) { - // first file - commonPathComponents = sourcePathComponents; - return; - } +export function resolveTripleslashReference(moduleName: string, containingFile: string): string { + const basePath = getDirectoryPath(containingFile); + const referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); + return normalizePath(referencedFileName); +} - const n = Math.min(commonPathComponents.length, sourcePathComponents.length); - for (let i = 0; i < n; i++) { - if (getCanonicalFileName(commonPathComponents[i]) !== getCanonicalFileName(sourcePathComponents[i])) { - if (i === 0) { - // Failed to find any common path component - return true; - } +/* @internal */ +export function computeCommonSourceDirectoryOfFilenames(fileNames: readonly string[], currentDirectory: string, getCanonicalFileName: GetCanonicalFileName): string { + let commonPathComponents: string[] | undefined; + const failed = forEach(fileNames, sourceFile => { + // Each file contributes into common source file path + const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory); + sourcePathComponents.pop(); // The base file name is not part of the common directory path + + if (!commonPathComponents) { + // first file + commonPathComponents = sourcePathComponents; + return; + } - // New common path found that is 0 -> i-1 - commonPathComponents.length = i; - break; + const n = Math.min(commonPathComponents.length, sourcePathComponents.length); + for (let i = 0; i < n; i++) { + if (getCanonicalFileName(commonPathComponents[i]) !== getCanonicalFileName(sourcePathComponents[i])) { + if (i === 0) { + // Failed to find any common path component + return true; } - } - // If the sourcePathComponents was shorter than the commonPathComponents, truncate to the sourcePathComponents - if (sourcePathComponents.length < commonPathComponents.length) { - commonPathComponents.length = sourcePathComponents.length; + // New common path found that is 0 -> i-1 + commonPathComponents.length = i; + break; } - }); - - // A common path can not be found when paths span multiple drives on windows, for example - if (failed) { - return ""; } - if (!commonPathComponents) { // Can happen when all input files are .d.ts files - return currentDirectory; + // If the sourcePathComponents was shorter than the commonPathComponents, truncate to the sourcePathComponents + if (sourcePathComponents.length < commonPathComponents.length) { + commonPathComponents.length = sourcePathComponents.length; } + }); - return getPathFromPathComponents(commonPathComponents); + // A common path can not be found when paths span multiple drives on windows, for example + if (failed) { + return ""; } - interface OutputFingerprint { - hash: string; - byteOrderMark: boolean; - mtime: Date; + if (!commonPathComponents) { // Can happen when all input files are .d.ts files + return currentDirectory; } - export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { - return createCompilerHostWorker(options, setParentNodes); + return getPathFromPathComponents(commonPathComponents); +} + +interface OutputFingerprint { + hash: string; + byteOrderMark: boolean; + mtime: Date; +} + +export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { + return createCompilerHostWorker(options, setParentNodes); +} + +/*@internal*/ +// TODO(shkamat): update this after reworking ts build API +export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost { + const existingDirectories = new Map(); + const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); + const computeHash = maybeBind(system, system.createHash) || generateDjb2Hash; + function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined { + let text: string | undefined; + try { + performance.mark("beforeIORead"); + text = compilerHost.readFile(fileName); + performance.mark("afterIORead"); + performance.measure("I/O Read", "beforeIORead", "afterIORead"); + } + catch (e) { + if (onError) { + onError(e.message); + } + text = ""; + } + return text !== undefined ? createSourceFile(fileName, text, languageVersion, setParentNodes) : undefined; } - /*@internal*/ - // TODO(shkamat): update this after reworking ts build API - export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost { - const existingDirectories = new Map(); - const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); - const computeHash = maybeBind(system, system.createHash) || generateDjb2Hash; - function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined { - let text: string | undefined; - try { - performance.mark("beforeIORead"); - text = compilerHost.readFile(fileName); - performance.mark("afterIORead"); - performance.measure("I/O Read", "beforeIORead", "afterIORead"); - } - catch (e) { - if (onError) { - onError(e.message); - } - text = ""; - } - return text !== undefined ? createSourceFile(fileName, text, languageVersion, setParentNodes) : undefined; + function directoryExists(directoryPath: string): boolean { + if (existingDirectories.has(directoryPath)) { + return true; } - - function directoryExists(directoryPath: string): boolean { - if (existingDirectories.has(directoryPath)) { - return true; - } - if ((compilerHost.directoryExists || system.directoryExists)(directoryPath)) { - existingDirectories.set(directoryPath, true); - return true; - } - return false; + if ((compilerHost.directoryExists || system.directoryExists)(directoryPath)) { + existingDirectories.set(directoryPath, true); + return true; } + return false; + } - function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { - try { - performance.mark("beforeIOWrite"); + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { + try { + performance.mark("beforeIOWrite"); - // NOTE: If patchWriteFileEnsuringDirectory has been called, - // the system.writeFile will do its own directory creation and - // the ensureDirectoriesExist call will always be redundant. - writeFileEnsuringDirectories( - fileName, - data, - writeByteOrderMark, - (path, data, writeByteOrderMark) => writeFileWorker(path, data, writeByteOrderMark), - path => (compilerHost.createDirectory || system.createDirectory)(path), - path => directoryExists(path)); + // NOTE: If patchWriteFileEnsuringDirectory has been called, + // the system.writeFile will do its own directory creation and + // the ensureDirectoriesExist call will always be redundant. + writeFileEnsuringDirectories( + fileName, + data, + writeByteOrderMark, + (path, data, writeByteOrderMark) => writeFileWorker(path, data, writeByteOrderMark), + path => (compilerHost.createDirectory || system.createDirectory)(path), + path => directoryExists(path)); - performance.mark("afterIOWrite"); - performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); - } - catch (e) { - if (onError) { - onError(e.message); - } + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + if (onError) { + onError(e.message); } } + } + + let outputFingerprints: ESMap; + function writeFileWorker(fileName: string, data: string, writeByteOrderMark: boolean) { + if (!isWatchSet(options) || !system.getModifiedTime) { + system.writeFile(fileName, data, writeByteOrderMark); + return; + } + + if (!outputFingerprints) { + outputFingerprints = new Map(); + } + + const hash = computeHash(data); + const mtimeBefore = system.getModifiedTime(fileName); - let outputFingerprints: ESMap; - function writeFileWorker(fileName: string, data: string, writeByteOrderMark: boolean) { - if (!isWatchSet(options) || !system.getModifiedTime) { - system.writeFile(fileName, data, writeByteOrderMark); + if (mtimeBefore) { + const fingerprint = outputFingerprints.get(fileName); + // If output has not been changed, and the file has no external modification + if (fingerprint && + fingerprint.byteOrderMark === writeByteOrderMark && + fingerprint.hash === hash && + fingerprint.mtime.getTime() === mtimeBefore.getTime()) { return; } + } - if (!outputFingerprints) { - outputFingerprints = new Map(); - } + system.writeFile(fileName, data, writeByteOrderMark); - const hash = computeHash(data); - const mtimeBefore = system.getModifiedTime(fileName); + const mtimeAfter = system.getModifiedTime(fileName) || missingFileModifiedTime; - if (mtimeBefore) { - const fingerprint = outputFingerprints.get(fileName); - // If output has not been changed, and the file has no external modification - if (fingerprint && - fingerprint.byteOrderMark === writeByteOrderMark && - fingerprint.hash === hash && - fingerprint.mtime.getTime() === mtimeBefore.getTime()) { - return; - } - } + outputFingerprints.set(fileName, { + hash, + byteOrderMark: writeByteOrderMark, + mtime: mtimeAfter + }); + } - system.writeFile(fileName, data, writeByteOrderMark); + function getDefaultLibLocation(): string { + return getDirectoryPath(normalizePath(system.getExecutingFilePath())); + } - const mtimeAfter = system.getModifiedTime(fileName) || missingFileModifiedTime; + const newLine = getNewLineCharacter(options, () => system.newLine); + const realpath = system.realpath && ((path: string) => system.realpath!(path)); + const compilerHost: CompilerHost = { + getSourceFile, + getDefaultLibLocation, + getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), + writeFile, + getCurrentDirectory: memoize(() => system.getCurrentDirectory()), + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + getCanonicalFileName, + getNewLine: () => newLine, + fileExists: fileName => system.fileExists(fileName), + readFile: fileName => system.readFile(fileName), + trace: (s: string) => system.write(s + newLine), + directoryExists: directoryName => system.directoryExists(directoryName), + getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", + getDirectories: (path: string) => system.getDirectories(path), + realpath, + readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth), + createDirectory: d => system.createDirectory(d), + createHash: maybeBind(system, system.createHash) + }; + return compilerHost; +} - outputFingerprints.set(fileName, { - hash, - byteOrderMark: writeByteOrderMark, - mtime: mtimeAfter - }); - } +/*@internal*/ +interface CompilerHostLikeForCache { + fileExists(fileName: string): boolean; + readFile(fileName: string, encoding?: string): string | undefined; + directoryExists?(directory: string): boolean; + createDirectory?(directory: string): void; + writeFile?: WriteFileCallback; +} - function getDefaultLibLocation(): string { - return getDirectoryPath(normalizePath(system.getExecutingFilePath())); +/*@internal*/ +export function changeCompilerHostLikeToUseCache( + host: CompilerHostLikeForCache, + toPath: (fileName: string) => Path, + getSourceFile?: CompilerHost["getSourceFile"] +) { + const originalReadFile = host.readFile; + const originalFileExists = host.fileExists; + const originalDirectoryExists = host.directoryExists; + const originalCreateDirectory = host.createDirectory; + const originalWriteFile = host.writeFile; + const readFileCache = new Map(); + const fileExistsCache = new Map(); + const directoryExistsCache = new Map(); + const sourceFileCache = new Map(); + + const readFileWithCache = (fileName: string): string | undefined => { + const key = toPath(fileName); + const value = readFileCache.get(key); + if (value !== undefined) return value !== false ? value : undefined; + return setReadFileCache(key, fileName); + }; + const setReadFileCache = (key: Path, fileName: string) => { + const newValue = originalReadFile.call(host, fileName); + readFileCache.set(key, newValue !== undefined ? newValue : false); + return newValue; + }; + host.readFile = fileName => { + const key = toPath(fileName); + const value = readFileCache.get(key); + if (value !== undefined) return value !== false ? value : undefined; // could be .d.ts from output + // Cache json or buildInfo + if (!fileExtensionIs(fileName, Extension.Json) && !isBuildInfoFile(fileName)) { + return originalReadFile.call(host, fileName); } - const newLine = getNewLineCharacter(options, () => system.newLine); - const realpath = system.realpath && ((path: string) => system.realpath!(path)); - const compilerHost: CompilerHost = { - getSourceFile, - getDefaultLibLocation, - getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), - writeFile, - getCurrentDirectory: memoize(() => system.getCurrentDirectory()), - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, - getCanonicalFileName, - getNewLine: () => newLine, - fileExists: fileName => system.fileExists(fileName), - readFile: fileName => system.readFile(fileName), - trace: (s: string) => system.write(s + newLine), - directoryExists: directoryName => system.directoryExists(directoryName), - getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", - getDirectories: (path: string) => system.getDirectories(path), - realpath, - readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth), - createDirectory: d => system.createDirectory(d), - createHash: maybeBind(system, system.createHash) - }; - return compilerHost; - } - - /*@internal*/ - interface CompilerHostLikeForCache { - fileExists(fileName: string): boolean; - readFile(fileName: string, encoding?: string): string | undefined; - directoryExists?(directory: string): boolean; - createDirectory?(directory: string): void; - writeFile?: WriteFileCallback; - } - - /*@internal*/ - export function changeCompilerHostLikeToUseCache( - host: CompilerHostLikeForCache, - toPath: (fileName: string) => Path, - getSourceFile?: CompilerHost["getSourceFile"] - ) { - const originalReadFile = host.readFile; - const originalFileExists = host.fileExists; - const originalDirectoryExists = host.directoryExists; - const originalCreateDirectory = host.createDirectory; - const originalWriteFile = host.writeFile; - const readFileCache = new Map(); - const fileExistsCache = new Map(); - const directoryExistsCache = new Map(); - const sourceFileCache = new Map(); - - const readFileWithCache = (fileName: string): string | undefined => { - const key = toPath(fileName); - const value = readFileCache.get(key); - if (value !== undefined) return value !== false ? value : undefined; - return setReadFileCache(key, fileName); - }; - const setReadFileCache = (key: Path, fileName: string) => { - const newValue = originalReadFile.call(host, fileName); - readFileCache.set(key, newValue !== undefined ? newValue : false); - return newValue; - }; - host.readFile = fileName => { - const key = toPath(fileName); - const value = readFileCache.get(key); - if (value !== undefined) return value !== false ? value : undefined; // could be .d.ts from output - // Cache json or buildInfo - if (!fileExtensionIs(fileName, Extension.Json) && !isBuildInfoFile(fileName)) { - return originalReadFile.call(host, fileName); - } - - return setReadFileCache(key, fileName); - }; + return setReadFileCache(key, fileName); + }; - const getSourceFileWithCache: CompilerHost["getSourceFile"] | undefined = getSourceFile ? (fileName, languageVersion, onError, shouldCreateNewSourceFile) => { + const getSourceFileWithCache: CompilerHost["getSourceFile"] | undefined = getSourceFile ? (fileName, languageVersion, onError, shouldCreateNewSourceFile) => { + const key = toPath(fileName); + const value = sourceFileCache.get(key); + if (value) return value; + + const sourceFile = getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile); + if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) { + sourceFileCache.set(key, sourceFile); + } + return sourceFile; + } : undefined; + + // fileExists for any kind of extension + host.fileExists = fileName => { + const key = toPath(fileName); + const value = fileExistsCache.get(key); + if (value !== undefined) return value; + const newValue = originalFileExists.call(host, fileName); + fileExistsCache.set(key, !!newValue); + return newValue; + }; + if (originalWriteFile) { + host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => { const key = toPath(fileName); - const value = sourceFileCache.get(key); - if (value) return value; + fileExistsCache.delete(key); - const sourceFile = getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile); - if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) { - sourceFileCache.set(key, sourceFile); + const value = readFileCache.get(key); + if (value !== undefined && value !== data) { + readFileCache.delete(key); + sourceFileCache.delete(key); } - return sourceFile; - } : undefined; + else if (getSourceFileWithCache) { + const sourceFile = sourceFileCache.get(key); + if (sourceFile && sourceFile.text !== data) { + sourceFileCache.delete(key); + } + } + originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles); + }; + } - // fileExists for any kind of extension - host.fileExists = fileName => { - const key = toPath(fileName); - const value = fileExistsCache.get(key); + // directoryExists + if (originalDirectoryExists && originalCreateDirectory) { + host.directoryExists = directory => { + const key = toPath(directory); + const value = directoryExistsCache.get(key); if (value !== undefined) return value; - const newValue = originalFileExists.call(host, fileName); - fileExistsCache.set(key, !!newValue); + const newValue = originalDirectoryExists.call(host, directory); + directoryExistsCache.set(key, !!newValue); return newValue; }; - if (originalWriteFile) { - host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => { - const key = toPath(fileName); - fileExistsCache.delete(key); - - const value = readFileCache.get(key); - if (value !== undefined && value !== data) { - readFileCache.delete(key); - sourceFileCache.delete(key); - } - else if (getSourceFileWithCache) { - const sourceFile = sourceFileCache.get(key); - if (sourceFile && sourceFile.text !== data) { - sourceFileCache.delete(key); - } - } - originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles); - }; - } + host.createDirectory = directory => { + const key = toPath(directory); + directoryExistsCache.delete(key); + originalCreateDirectory.call(host, directory); + }; + } - // directoryExists - if (originalDirectoryExists && originalCreateDirectory) { - host.directoryExists = directory => { - const key = toPath(directory); - const value = directoryExistsCache.get(key); - if (value !== undefined) return value; - const newValue = originalDirectoryExists.call(host, directory); - directoryExistsCache.set(key, !!newValue); - return newValue; - }; - host.createDirectory = directory => { - const key = toPath(directory); - directoryExistsCache.delete(key); - originalCreateDirectory.call(host, directory); - }; - } + return { + originalReadFile, + originalFileExists, + originalDirectoryExists, + originalCreateDirectory, + originalWriteFile, + getSourceFileWithCache, + readFileWithCache + }; +} - return { - originalReadFile, - originalFileExists, - originalDirectoryExists, - originalCreateDirectory, - originalWriteFile, - getSourceFileWithCache, - readFileWithCache - }; +export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; +/*@internal*/ export function getPreEmitDiagnostics(program: BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; // eslint-disable-line @typescript-eslint/unified-signatures +export function getPreEmitDiagnostics(program: Program | BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + let diagnostics: Diagnostic[] | undefined; + diagnostics = addRange(diagnostics, program.getConfigFileParsingDiagnostics()); + diagnostics = addRange(diagnostics, program.getOptionsDiagnostics(cancellationToken)); + diagnostics = addRange(diagnostics, program.getSyntacticDiagnostics(sourceFile, cancellationToken)); + diagnostics = addRange(diagnostics, program.getGlobalDiagnostics(cancellationToken)); + diagnostics = addRange(diagnostics, program.getSemanticDiagnostics(sourceFile, cancellationToken)); + + if (getEmitDeclarations(program.getCompilerOptions())) { + diagnostics = addRange(diagnostics, program.getDeclarationDiagnostics(sourceFile, cancellationToken)); } - export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - /*@internal*/ export function getPreEmitDiagnostics(program: BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; // eslint-disable-line @typescript-eslint/unified-signatures - export function getPreEmitDiagnostics(program: Program | BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - let diagnostics: Diagnostic[] | undefined; - diagnostics = addRange(diagnostics, program.getConfigFileParsingDiagnostics()); - diagnostics = addRange(diagnostics, program.getOptionsDiagnostics(cancellationToken)); - diagnostics = addRange(diagnostics, program.getSyntacticDiagnostics(sourceFile, cancellationToken)); - diagnostics = addRange(diagnostics, program.getGlobalDiagnostics(cancellationToken)); - diagnostics = addRange(diagnostics, program.getSemanticDiagnostics(sourceFile, cancellationToken)); + return sortAndDeduplicateDiagnostics(diagnostics || emptyArray); +} - if (getEmitDeclarations(program.getCompilerOptions())) { - diagnostics = addRange(diagnostics, program.getDeclarationDiagnostics(sourceFile, cancellationToken)); - } +export interface FormatDiagnosticsHost { + getCurrentDirectory(): string; + getCanonicalFileName(fileName: string): string; + getNewLine(): string; +} - return sortAndDeduplicateDiagnostics(diagnostics || emptyArray); - } +export function formatDiagnostics(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost): string { + let output = ""; - export interface FormatDiagnosticsHost { - getCurrentDirectory(): string; - getCanonicalFileName(fileName: string): string; - getNewLine(): string; + for (const diagnostic of diagnostics) { + output += formatDiagnostic(diagnostic, host); } + return output; +} - export function formatDiagnostics(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost): string { - let output = ""; +export function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string { + const errorMessage = `${diagnosticCategoryName(diagnostic)} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`; - for (const diagnostic of diagnostics) { - output += formatDiagnostic(diagnostic, host); - } - return output; + if (diagnostic.file) { + const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start!); // TODO: GH#18217 + const fileName = diagnostic.file.fileName; + const relativeFileName = convertToRelativePath(fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)); + return `${relativeFileName}(${line + 1},${character + 1}): ` + errorMessage; } - export function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string { - const errorMessage = `${diagnosticCategoryName(diagnostic)} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`; - - if (diagnostic.file) { - const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start!); // TODO: GH#18217 - const fileName = diagnostic.file.fileName; - const relativeFileName = convertToRelativePath(fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)); - return `${relativeFileName}(${line + 1},${character + 1}): ` + errorMessage; - } + return errorMessage; +} - return errorMessage; +/** @internal */ +export enum ForegroundColorEscapeSequences { + Grey = "\u001b[90m", + Red = "\u001b[91m", + Yellow = "\u001b[93m", + Blue = "\u001b[94m", + Cyan = "\u001b[96m" +} +const gutterStyleSequence = "\u001b[7m"; +const gutterSeparator = " "; +const resetEscapeSequence = "\u001b[0m"; +const ellipsis = "..."; +const halfIndent = " "; +const indent = " "; +function getCategoryFormat(category: DiagnosticCategory): ForegroundColorEscapeSequences { + switch (category) { + case DiagnosticCategory.Error: return ForegroundColorEscapeSequences.Red; + case DiagnosticCategory.Warning: return ForegroundColorEscapeSequences.Yellow; + case DiagnosticCategory.Suggestion: return Debug.fail("Should never get an Info diagnostic on the command line."); + case DiagnosticCategory.Message: return ForegroundColorEscapeSequences.Blue; } +} - /** @internal */ - export enum ForegroundColorEscapeSequences { - Grey = "\u001b[90m", - Red = "\u001b[91m", - Yellow = "\u001b[93m", - Blue = "\u001b[94m", - Cyan = "\u001b[96m" - } - const gutterStyleSequence = "\u001b[7m"; - const gutterSeparator = " "; - const resetEscapeSequence = "\u001b[0m"; - const ellipsis = "..."; - const halfIndent = " "; - const indent = " "; - function getCategoryFormat(category: DiagnosticCategory): ForegroundColorEscapeSequences { - switch (category) { - case DiagnosticCategory.Error: return ForegroundColorEscapeSequences.Red; - case DiagnosticCategory.Warning: return ForegroundColorEscapeSequences.Yellow; - case DiagnosticCategory.Suggestion: return Debug.fail("Should never get an Info diagnostic on the command line."); - case DiagnosticCategory.Message: return ForegroundColorEscapeSequences.Blue; - } - } +/** @internal */ +export function formatColorAndReset(text: string, formatStyle: string) { + return formatStyle + text + resetEscapeSequence; +} - /** @internal */ - export function formatColorAndReset(text: string, formatStyle: string) { - return formatStyle + text + resetEscapeSequence; - } +function formatCodeSpan(file: SourceFile, start: number, length: number, indent: string, squiggleColor: ForegroundColorEscapeSequences, host: FormatDiagnosticsHost) { + const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); + const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length); + const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line; - function formatCodeSpan(file: SourceFile, start: number, length: number, indent: string, squiggleColor: ForegroundColorEscapeSequences, host: FormatDiagnosticsHost) { - const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); - const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length); - const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line; + const hasMoreThanFiveLines = (lastLine - firstLine) >= 4; + let gutterWidth = (lastLine + 1 + "").length; + if (hasMoreThanFiveLines) { + gutterWidth = Math.max(ellipsis.length, gutterWidth); + } - const hasMoreThanFiveLines = (lastLine - firstLine) >= 4; - let gutterWidth = (lastLine + 1 + "").length; - if (hasMoreThanFiveLines) { - gutterWidth = Math.max(ellipsis.length, gutterWidth); + let context = ""; + for (let i = firstLine; i <= lastLine; i++) { + context += host.getNewLine(); + // If the error spans over 5 lines, we'll only show the first 2 and last 2 lines, + // so we'll skip ahead to the second-to-last line. + if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) { + context += indent + formatColorAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + host.getNewLine(); + i = lastLine - 1; } - let context = ""; - for (let i = firstLine; i <= lastLine; i++) { - context += host.getNewLine(); - // If the error spans over 5 lines, we'll only show the first 2 and last 2 lines, - // so we'll skip ahead to the second-to-last line. - if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) { - context += indent + formatColorAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + host.getNewLine(); - i = lastLine - 1; - } - - const lineStart = getPositionOfLineAndCharacter(file, i, 0); - const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length; - let lineContent = file.text.slice(lineStart, lineEnd); - lineContent = trimStringEnd(lineContent); // trim from end - lineContent = lineContent.replace(/\t/g, " "); // convert tabs to single spaces + const lineStart = getPositionOfLineAndCharacter(file, i, 0); + const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length; + let lineContent = file.text.slice(lineStart, lineEnd); + lineContent = trimStringEnd(lineContent); // trim from end + lineContent = lineContent.replace(/\t/g, " "); // convert tabs to single spaces - // Output the gutter and the actual contents of the line. - context += indent + formatColorAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator; - context += lineContent + host.getNewLine(); + // Output the gutter and the actual contents of the line. + context += indent + formatColorAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator; + context += lineContent + host.getNewLine(); - // Output the gutter and the error span for the line using tildes. - context += indent + formatColorAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator; - context += squiggleColor; - if (i === firstLine) { - // If we're on the last line, then limit it to the last character of the last line. - // Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position. - const lastCharForLine = i === lastLine ? lastLineChar : undefined; + // Output the gutter and the error span for the line using tildes. + context += indent + formatColorAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator; + context += squiggleColor; + if (i === firstLine) { + // If we're on the last line, then limit it to the last character of the last line. + // Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position. + const lastCharForLine = i === lastLine ? lastLineChar : undefined; - context += lineContent.slice(0, firstLineChar).replace(/\S/g, " "); - context += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~"); - } - else if (i === lastLine) { - context += lineContent.slice(0, lastLineChar).replace(/./g, "~"); - } - else { - // Squiggle the entire line. - context += lineContent.replace(/./g, "~"); - } - context += resetEscapeSequence; + context += lineContent.slice(0, firstLineChar).replace(/\S/g, " "); + context += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~"); + } + else if (i === lastLine) { + context += lineContent.slice(0, lastLineChar).replace(/./g, "~"); + } + else { + // Squiggle the entire line. + context += lineContent.replace(/./g, "~"); } - return context; + context += resetEscapeSequence; } + return context; +} - /* @internal */ - export function formatLocation(file: SourceFile, start: number, host: FormatDiagnosticsHost, color = formatColorAndReset) { - const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); // TODO: GH#18217 - const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName; - - let output = ""; - output += color(relativeFileName, ForegroundColorEscapeSequences.Cyan); - output += ":"; - output += color(`${firstLine + 1}`, ForegroundColorEscapeSequences.Yellow); - output += ":"; - output += color(`${firstLineChar + 1}`, ForegroundColorEscapeSequences.Yellow); - return output; - } +/* @internal */ +export function formatLocation(file: SourceFile, start: number, host: FormatDiagnosticsHost, color = formatColorAndReset) { + const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); // TODO: GH#18217 + const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName; + + let output = ""; + output += color(relativeFileName, ForegroundColorEscapeSequences.Cyan); + output += ":"; + output += color(`${firstLine + 1}`, ForegroundColorEscapeSequences.Yellow); + output += ":"; + output += color(`${firstLineChar + 1}`, ForegroundColorEscapeSequences.Yellow); + return output; +} - export function formatDiagnosticsWithColorAndContext(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost): string { - let output = ""; - for (const diagnostic of diagnostics) { - if (diagnostic.file) { - const { file, start } = diagnostic; - output += formatLocation(file, start!, host); // TODO: GH#18217 - output += " - "; - } +export function formatDiagnosticsWithColorAndContext(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost): string { + let output = ""; + for (const diagnostic of diagnostics) { + if (diagnostic.file) { + const { file, start } = diagnostic; + output += formatLocation(file, start!, host); // TODO: GH#18217 + output += " - "; + } - output += formatColorAndReset(diagnosticCategoryName(diagnostic), getCategoryFormat(diagnostic.category)); - output += formatColorAndReset(` TS${diagnostic.code}: `, ForegroundColorEscapeSequences.Grey); - output += flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine()); + output += formatColorAndReset(diagnosticCategoryName(diagnostic), getCategoryFormat(diagnostic.category)); + output += formatColorAndReset(` TS${diagnostic.code}: `, ForegroundColorEscapeSequences.Grey); + output += flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine()); - if (diagnostic.file) { - output += host.getNewLine(); - output += formatCodeSpan(diagnostic.file, diagnostic.start!, diagnostic.length!, "", getCategoryFormat(diagnostic.category), host); // TODO: GH#18217 - } - if (diagnostic.relatedInformation) { - output += host.getNewLine(); - for (const { file, start, length, messageText } of diagnostic.relatedInformation) { - if (file) { - output += host.getNewLine(); - output += halfIndent + formatLocation(file, start!, host); // TODO: GH#18217 - output += formatCodeSpan(file, start!, length!, indent, ForegroundColorEscapeSequences.Cyan, host); // TODO: GH#18217 - } + if (diagnostic.file) { + output += host.getNewLine(); + output += formatCodeSpan(diagnostic.file, diagnostic.start!, diagnostic.length!, "", getCategoryFormat(diagnostic.category), host); // TODO: GH#18217 + } + if (diagnostic.relatedInformation) { + output += host.getNewLine(); + for (const { file, start, length, messageText } of diagnostic.relatedInformation) { + if (file) { output += host.getNewLine(); - output += indent + flattenDiagnosticMessageText(messageText, host.getNewLine()); + output += halfIndent + formatLocation(file, start!, host); // TODO: GH#18217 + output += formatCodeSpan(file, start!, length!, indent, ForegroundColorEscapeSequences.Cyan, host); // TODO: GH#18217 } + output += host.getNewLine(); + output += indent + flattenDiagnosticMessageText(messageText, host.getNewLine()); } - output += host.getNewLine(); } - return output; + output += host.getNewLine(); } + return output; +} - export function flattenDiagnosticMessageText(diag: string | DiagnosticMessageChain | undefined, newLine: string, indent = 0): string { - if (isString(diag)) { - return diag; - } - else if (diag === undefined) { - return ""; - } - let result = ""; - if (indent) { - result += newLine; +export function flattenDiagnosticMessageText(diag: string | DiagnosticMessageChain | undefined, newLine: string, indent = 0): string { + if (isString(diag)) { + return diag; + } + else if (diag === undefined) { + return ""; + } + let result = ""; + if (indent) { + result += newLine; - for (let i = 0; i < indent; i++) { - result += " "; - } + for (let i = 0; i < indent; i++) { + result += " "; } - result += diag.messageText; - indent++; - if (diag.next) { - for (const kid of diag.next) { - result += flattenDiagnosticMessageText(kid, newLine, indent); - } + } + result += diag.messageText; + indent++; + if (diag.next) { + for (const kid of diag.next) { + result += flattenDiagnosticMessageText(kid, newLine, indent); } - return result; } + return result; +} - /* @internal */ - export function loadWithLocalCache(names: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, loader: (name: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] { - if (names.length === 0) { - return []; +/* @internal */ +export function loadWithLocalCache(names: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, loader: (name: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] { + if (names.length === 0) { + return []; + } + const resolutions: T[] = []; + const cache = new Map(); + for (const name of names) { + let result: T; + if (cache.has(name)) { + result = cache.get(name)!; } - const resolutions: T[] = []; - const cache = new Map(); - for (const name of names) { - let result: T; - if (cache.has(name)) { - result = cache.get(name)!; - } - else { - cache.set(name, result = loader(name, containingFile, redirectedReference)); - } - resolutions.push(result); + else { + cache.set(name, result = loader(name, containingFile, redirectedReference)); } - return resolutions; + resolutions.push(result); } + return resolutions; +} - /* @internal */ - interface SourceFileImportsList { - imports: SourceFile["imports"]; - moduleAugmentations: SourceFile["moduleAugmentations"]; - impliedNodeFormat?: SourceFile["impliedNodeFormat"]; - }; +/* @internal */ +interface SourceFileImportsList { + imports: SourceFile["imports"]; + moduleAugmentations: SourceFile["moduleAugmentations"]; + impliedNodeFormat?: SourceFile["impliedNodeFormat"]; +}; + +/* @internal */ +export function getModeForResolutionAtIndex(file: SourceFileImportsList, index: number) { + if (file.impliedNodeFormat === undefined) return undefined; + // we ensure all elements of file.imports and file.moduleAugmentations have the relevant parent pointers set during program setup, + // so it's safe to use them even pre-bind + return getModeForUsageLocation(file, getModuleNameStringLiteralAt(file, index)); +} - /* @internal */ - export function getModeForResolutionAtIndex(file: SourceFileImportsList, index: number) { - if (file.impliedNodeFormat === undefined) return undefined; - // we ensure all elements of file.imports and file.moduleAugmentations have the relevant parent pointers set during program setup, - // so it's safe to use them even pre-bind - return getModeForUsageLocation(file, getModuleNameStringLiteralAt(file, index)); - } - - /* @internal */ - export function getModeForUsageLocation(file: {impliedNodeFormat?: SourceFile["impliedNodeFormat"]}, usage: StringLiteralLike) { - if (file.impliedNodeFormat === undefined) return undefined; - if (file.impliedNodeFormat !== ModuleKind.ESNext) { - // in cjs files, import call expressions are esm format, otherwise everything is cjs - return isImportCall(walkUpParenthesizedExpressions(usage.parent)) ? ModuleKind.ESNext : ModuleKind.CommonJS; - } - // in esm files, import=require statements are cjs format, otherwise everything is esm - // imports are only parent'd up to their containing declaration/expression, so access farther parents with care - const exprParentParent = walkUpParenthesizedExpressions(usage.parent)?.parent; - return exprParentParent && isImportEqualsDeclaration(exprParentParent) ? ModuleKind.CommonJS : ModuleKind.ESNext; - } - - /* @internal */ - export function loadWithModeAwareCache(names: string[], containingFile: SourceFile, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined, loader: (name: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] { - if (names.length === 0) { - return []; - } - const resolutions: T[] = []; - const cache = new Map(); - let i = 0; - for (const name of names) { - let result: T; - const mode = getModeForResolutionAtIndex(containingFile, i); - i++; - const cacheKey = mode !== undefined ? `${mode}|${name}` : name; - if (cache.has(cacheKey)) { - result = cache.get(cacheKey)!; - } - else { - cache.set(cacheKey, result = loader(name, mode, containingFileName, redirectedReference)); - } - resolutions.push(result); - } - return resolutions; +/* @internal */ +export function getModeForUsageLocation(file: {impliedNodeFormat?: SourceFile["impliedNodeFormat"]}, usage: StringLiteralLike) { + if (file.impliedNodeFormat === undefined) return undefined; + if (file.impliedNodeFormat !== ModuleKind.ESNext) { + // in cjs files, import call expressions are esm format, otherwise everything is cjs + return isImportCall(walkUpParenthesizedExpressions(usage.parent)) ? ModuleKind.ESNext : ModuleKind.CommonJS; } + // in esm files, import=require statements are cjs format, otherwise everything is esm + // imports are only parent'd up to their containing declaration/expression, so access farther parents with care + const exprParentParent = walkUpParenthesizedExpressions(usage.parent)?.parent; + return exprParentParent && isImportEqualsDeclaration(exprParentParent) ? ModuleKind.CommonJS : ModuleKind.ESNext; +} - /* @internal */ - export function forEachResolvedProjectReference( - resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, - cb: (resolvedProjectReference: ResolvedProjectReference, parent: ResolvedProjectReference | undefined) => T | undefined - ): T | undefined { - return forEachProjectReference(/*projectReferences*/ undefined, resolvedProjectReferences, (resolvedRef, parent) => resolvedRef && cb(resolvedRef, parent)); +/* @internal */ +export function loadWithModeAwareCache(names: string[], containingFile: SourceFile, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined, loader: (name: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] { + if (names.length === 0) { + return []; + } + const resolutions: T[] = []; + const cache = new Map(); + let i = 0; + for (const name of names) { + let result: T; + const mode = getModeForResolutionAtIndex(containingFile, i); + i++; + const cacheKey = mode !== undefined ? `${mode}|${name}` : name; + if (cache.has(cacheKey)) { + result = cache.get(cacheKey)!; + } + else { + cache.set(cacheKey, result = loader(name, mode, containingFileName, redirectedReference)); + } + resolutions.push(result); } + return resolutions; +} + +/* @internal */ +export function forEachResolvedProjectReference( + resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, + cb: (resolvedProjectReference: ResolvedProjectReference, parent: ResolvedProjectReference | undefined) => T | undefined +): T | undefined { + return forEachProjectReference(/*projectReferences*/ undefined, resolvedProjectReferences, (resolvedRef, parent) => resolvedRef && cb(resolvedRef, parent)); +} - function forEachProjectReference( +function forEachProjectReference( + projectReferences: readonly ProjectReference[] | undefined, + resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, + cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, parent: ResolvedProjectReference | undefined, index: number) => T | undefined, + cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined +): T | undefined { + let seenResolvedRefs: Set | undefined; + + return worker(projectReferences, resolvedProjectReferences, /*parent*/ undefined); + + function worker( projectReferences: readonly ProjectReference[] | undefined, resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, - cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, parent: ResolvedProjectReference | undefined, index: number) => T | undefined, - cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined + parent: ResolvedProjectReference | undefined, ): T | undefined { - let seenResolvedRefs: Set | undefined; - return worker(projectReferences, resolvedProjectReferences, /*parent*/ undefined); - - function worker( - projectReferences: readonly ProjectReference[] | undefined, - resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, - parent: ResolvedProjectReference | undefined, - ): T | undefined { + // Visit project references first + if (cbRef) { + const result = cbRef(projectReferences, parent); + if (result) return result; + } - // Visit project references first - if (cbRef) { - const result = cbRef(projectReferences, parent); - if (result) return result; + return forEach(resolvedProjectReferences, (resolvedRef, index) => { + if (resolvedRef && seenResolvedRefs?.has(resolvedRef.sourceFile.path)) { + // ignore recursives + return undefined; } - return forEach(resolvedProjectReferences, (resolvedRef, index) => { - if (resolvedRef && seenResolvedRefs?.has(resolvedRef.sourceFile.path)) { - // ignore recursives - return undefined; - } - - const result = cbResolvedRef(resolvedRef, parent, index); - if (result || !resolvedRef) return result; + const result = cbResolvedRef(resolvedRef, parent, index); + if (result || !resolvedRef) return result; - (seenResolvedRefs ||= new Set()).add(resolvedRef.sourceFile.path); - return worker(resolvedRef.commandLine.projectReferences, resolvedRef.references, resolvedRef); - }); - } + (seenResolvedRefs ||= new Set()).add(resolvedRef.sourceFile.path); + return worker(resolvedRef.commandLine.projectReferences, resolvedRef.references, resolvedRef); + }); } +} - /* @internal */ - export const inferredTypesContainingFile = "__inferred type names__.ts"; +/* @internal */ +export const inferredTypesContainingFile = "__inferred type names__.ts"; - interface DiagnosticCache { - perFile?: ESMap; - allDiagnostics?: readonly T[]; - } +interface DiagnosticCache { + perFile?: ESMap; + allDiagnostics?: readonly T[]; +} - /*@internal*/ - export function isReferencedFile(reason: FileIncludeReason | undefined): reason is ReferencedFile { - switch (reason?.kind) { - case FileIncludeKind.Import: - case FileIncludeKind.ReferenceFile: - case FileIncludeKind.TypeReferenceDirective: - case FileIncludeKind.LibReferenceDirective: - return true; - default: - return false; - } +/*@internal*/ +export function isReferencedFile(reason: FileIncludeReason | undefined): reason is ReferencedFile { + switch (reason?.kind) { + case FileIncludeKind.Import: + case FileIncludeKind.ReferenceFile: + case FileIncludeKind.TypeReferenceDirective: + case FileIncludeKind.LibReferenceDirective: + return true; + default: + return false; } +} - /*@internal*/ - export interface ReferenceFileLocation { - file: SourceFile; - pos: number; - end: number; - packageId: PackageId | undefined; - } +/*@internal*/ +export interface ReferenceFileLocation { + file: SourceFile; + pos: number; + end: number; + packageId: PackageId | undefined; +} - /*@internal*/ - export interface SyntheticReferenceFileLocation { - file: SourceFile; - packageId: PackageId | undefined; - text: string; - } +/*@internal*/ +export interface SyntheticReferenceFileLocation { + file: SourceFile; + packageId: PackageId | undefined; + text: string; +} - /*@internal*/ - export function isReferenceFileLocation(location: ReferenceFileLocation | SyntheticReferenceFileLocation): location is ReferenceFileLocation { - return (location as ReferenceFileLocation).pos !== undefined; - } +/*@internal*/ +export function isReferenceFileLocation(location: ReferenceFileLocation | SyntheticReferenceFileLocation): location is ReferenceFileLocation { + return (location as ReferenceFileLocation).pos !== undefined; +} - /*@internal*/ - export function getReferencedFileLocation(getSourceFileByPath: (path: Path) => SourceFile | undefined, ref: ReferencedFile): ReferenceFileLocation | SyntheticReferenceFileLocation { - const file = Debug.checkDefined(getSourceFileByPath(ref.file)); - const { kind, index } = ref; - let pos: number | undefined, end: number | undefined, packageId: PackageId | undefined; - switch (kind) { - case FileIncludeKind.Import: - const importLiteral = getModuleNameStringLiteralAt(file, index); - packageId = file.resolvedModules?.get(importLiteral.text, getModeForResolutionAtIndex(file, index))?.packageId; - if (importLiteral.pos === -1) return { file, packageId, text: importLiteral.text }; - pos = skipTrivia(file.text, importLiteral.pos); - end = importLiteral.end; - break; - case FileIncludeKind.ReferenceFile: - ({ pos, end } = file.referencedFiles[index]); - break; - case FileIncludeKind.TypeReferenceDirective: - ({ pos, end } = file.typeReferenceDirectives[index]); - packageId = file.resolvedTypeReferenceDirectiveNames?.get(toFileNameLowerCase(file.typeReferenceDirectives[index].fileName), file.impliedNodeFormat)?.packageId; - break; - case FileIncludeKind.LibReferenceDirective: - ({ pos, end } = file.libReferenceDirectives[index]); - break; - default: - return Debug.assertNever(kind); - } - return { file, pos, end, packageId }; +/*@internal*/ +export function getReferencedFileLocation(getSourceFileByPath: (path: Path) => SourceFile | undefined, ref: ReferencedFile): ReferenceFileLocation | SyntheticReferenceFileLocation { + const file = Debug.checkDefined(getSourceFileByPath(ref.file)); + const { kind, index } = ref; + let pos: number | undefined, end: number | undefined, packageId: PackageId | undefined; + switch (kind) { + case FileIncludeKind.Import: + const importLiteral = getModuleNameStringLiteralAt(file, index); + packageId = file.resolvedModules?.get(importLiteral.text, getModeForResolutionAtIndex(file, index))?.packageId; + if (importLiteral.pos === -1) return { file, packageId, text: importLiteral.text }; + pos = skipTrivia(file.text, importLiteral.pos); + end = importLiteral.end; + break; + case FileIncludeKind.ReferenceFile: + ({ pos, end } = file.referencedFiles[index]); + break; + case FileIncludeKind.TypeReferenceDirective: + ({ pos, end } = file.typeReferenceDirectives[index]); + packageId = file.resolvedTypeReferenceDirectiveNames?.get(toFileNameLowerCase(file.typeReferenceDirectives[index].fileName), file.impliedNodeFormat)?.packageId; + break; + case FileIncludeKind.LibReferenceDirective: + ({ pos, end } = file.libReferenceDirectives[index]); + break; + default: + return Debug.assertNever(kind); } + return { file, pos, end, packageId }; +} - /** - * Determines if program structure is upto date or needs to be recreated - */ - /* @internal */ - export function isProgramUptoDate( - program: Program | undefined, - rootFileNames: string[], - newOptions: CompilerOptions, - getSourceVersion: (path: Path, fileName: string) => string | undefined, - fileExists: (fileName: string) => boolean, - hasInvalidatedResolution: HasInvalidatedResolution, - hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined, - getParsedCommandLine: (fileName: string) => ParsedCommandLine | undefined, - projectReferences: readonly ProjectReference[] | undefined - ): boolean { - // If we haven't created a program yet or have changed automatic type directives, then it is not up-to-date - if (!program || hasChangedAutomaticTypeDirectiveNames?.()) return false; - - // If root file names don't match - if (!arrayIsEqualTo(program.getRootFileNames(), rootFileNames)) return false; - - let seenResolvedRefs: ResolvedProjectReference[] | undefined; +/** + * Determines if program structure is upto date or needs to be recreated + */ +/* @internal */ +export function isProgramUptoDate( + program: Program | undefined, + rootFileNames: string[], + newOptions: CompilerOptions, + getSourceVersion: (path: Path, fileName: string) => string | undefined, + fileExists: (fileName: string) => boolean, + hasInvalidatedResolution: HasInvalidatedResolution, + hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined, + getParsedCommandLine: (fileName: string) => ParsedCommandLine | undefined, + projectReferences: readonly ProjectReference[] | undefined +): boolean { + // If we haven't created a program yet or have changed automatic type directives, then it is not up-to-date + if (!program || hasChangedAutomaticTypeDirectiveNames?.()) return false; + + // If root file names don't match + if (!arrayIsEqualTo(program.getRootFileNames(), rootFileNames)) return false; + + let seenResolvedRefs: ResolvedProjectReference[] | undefined; + + // If project references don't match + if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences, projectReferenceUptoDate)) return false; + + // If any file is not up-to-date, then the whole program is not up-to-date + if (program.getSourceFiles().some(sourceFileNotUptoDate)) return false; + + // If any of the missing file paths are now created + if (program.getMissingFilePaths().some(fileExists)) return false; + + const currentOptions = program.getCompilerOptions(); + // If the compilation settings do no match, then the program is not up-to-date + if (!compareDataObjects(currentOptions, newOptions)) return false; + + // If everything matches but the text of config file is changed, + // error locations can change for program options, so update the program + if (currentOptions.configFile && newOptions.configFile) return currentOptions.configFile.text === newOptions.configFile.text; + + return true; + + function sourceFileNotUptoDate(sourceFile: SourceFile) { + return !sourceFileVersionUptoDate(sourceFile) || + hasInvalidatedResolution(sourceFile.path); + } - // If project references don't match - if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences, projectReferenceUptoDate)) return false; + function sourceFileVersionUptoDate(sourceFile: SourceFile) { + return sourceFile.version === getSourceVersion(sourceFile.resolvedPath, sourceFile.fileName); + } - // If any file is not up-to-date, then the whole program is not up-to-date - if (program.getSourceFiles().some(sourceFileNotUptoDate)) return false; + function projectReferenceUptoDate(oldRef: ProjectReference, newRef: ProjectReference, index: number) { + return projectReferenceIsEqualTo(oldRef, newRef) && + resolvedProjectReferenceUptoDate(program!.getResolvedProjectReferences()![index], oldRef); + } - // If any of the missing file paths are now created - if (program.getMissingFilePaths().some(fileExists)) return false; + function resolvedProjectReferenceUptoDate(oldResolvedRef: ResolvedProjectReference | undefined, oldRef: ProjectReference): boolean { + if (oldResolvedRef) { + // Assume true + if (contains(seenResolvedRefs, oldResolvedRef)) return true; - const currentOptions = program.getCompilerOptions(); - // If the compilation settings do no match, then the program is not up-to-date - if (!compareDataObjects(currentOptions, newOptions)) return false; + const refPath = resolveProjectReferencePath(oldRef); + const newParsedCommandLine = getParsedCommandLine(refPath); - // If everything matches but the text of config file is changed, - // error locations can change for program options, so update the program - if (currentOptions.configFile && newOptions.configFile) return currentOptions.configFile.text === newOptions.configFile.text; + // Check if config file exists + if (!newParsedCommandLine) return false; - return true; + // If change in source file + if (oldResolvedRef.commandLine.options.configFile !== newParsedCommandLine.options.configFile) return false; - function sourceFileNotUptoDate(sourceFile: SourceFile) { - return !sourceFileVersionUptoDate(sourceFile) || - hasInvalidatedResolution(sourceFile.path); - } + // check file names + if (!arrayIsEqualTo(oldResolvedRef.commandLine.fileNames, newParsedCommandLine.fileNames)) return false; - function sourceFileVersionUptoDate(sourceFile: SourceFile) { - return sourceFile.version === getSourceVersion(sourceFile.resolvedPath, sourceFile.fileName); - } + // Add to seen before checking the referenced paths of this config file + (seenResolvedRefs || (seenResolvedRefs = [])).push(oldResolvedRef); - function projectReferenceUptoDate(oldRef: ProjectReference, newRef: ProjectReference, index: number) { - return projectReferenceIsEqualTo(oldRef, newRef) && - resolvedProjectReferenceUptoDate(program!.getResolvedProjectReferences()![index], oldRef); + // If child project references are upto date, this project reference is uptodate + return !forEach(oldResolvedRef.references, (childResolvedRef, index) => + !resolvedProjectReferenceUptoDate(childResolvedRef, oldResolvedRef.commandLine.projectReferences![index])); } - function resolvedProjectReferenceUptoDate(oldResolvedRef: ResolvedProjectReference | undefined, oldRef: ProjectReference): boolean { - if (oldResolvedRef) { - // Assume true - if (contains(seenResolvedRefs, oldResolvedRef)) return true; - - const refPath = resolveProjectReferencePath(oldRef); - const newParsedCommandLine = getParsedCommandLine(refPath); - - // Check if config file exists - if (!newParsedCommandLine) return false; - - // If change in source file - if (oldResolvedRef.commandLine.options.configFile !== newParsedCommandLine.options.configFile) return false; - - // check file names - if (!arrayIsEqualTo(oldResolvedRef.commandLine.fileNames, newParsedCommandLine.fileNames)) return false; - - // Add to seen before checking the referenced paths of this config file - (seenResolvedRefs || (seenResolvedRefs = [])).push(oldResolvedRef); + // In old program, not able to resolve project reference path, + // so if config file doesnt exist, it is uptodate. + const refPath = resolveProjectReferencePath(oldRef); + return !getParsedCommandLine(refPath); + } +} - // If child project references are upto date, this project reference is uptodate - return !forEach(oldResolvedRef.references, (childResolvedRef, index) => - !resolvedProjectReferenceUptoDate(childResolvedRef, oldResolvedRef.commandLine.projectReferences![index])); - } +export function getConfigFileParsingDiagnostics(configFileParseResult: ParsedCommandLine): readonly Diagnostic[] { + return configFileParseResult.options.configFile ? + [...configFileParseResult.options.configFile.parseDiagnostics, ...configFileParseResult.errors] : + configFileParseResult.errors; +} - // In old program, not able to resolve project reference path, - // so if config file doesnt exist, it is uptodate. - const refPath = resolveProjectReferencePath(oldRef); - return !getParsedCommandLine(refPath); - } +/** + * A function for determining if a given file is esm or cjs format, assuming modern node module resolution rules, as configured by the + * `options` parameter. + * + * @param fileName The normalized absolute path to check the format of (it need not exist on disk) + * @param [packageJsonInfoCache] A cache for package file lookups - it's best to have a cache when this function is called often + * @param host The ModuleResolutionHost which can perform the filesystem lookups for package json data + * @param options The compiler options to perform the analysis under - relevant options are `moduleResolution` and `traceResolution` + * @returns `undefined` if the path has no relevant implied format, `ModuleKind.ESNext` for esm format, and `ModuleKind.CommonJS` for cjs format + */ +export function getImpliedNodeFormatForFile(fileName: Path, packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): ModuleKind.ESNext | ModuleKind.CommonJS | undefined { + switch (getEmitModuleResolutionKind(options)) { + case ModuleResolutionKind.Node12: + case ModuleResolutionKind.NodeNext: + return fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Mjs]) ? ModuleKind.ESNext : + fileExtensionIsOneOf(fileName, [Extension.Dcts, Extension.Cts, Extension.Cjs]) ? ModuleKind.CommonJS : + fileExtensionIsOneOf(fileName, [Extension.Dts, Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx]) ? lookupFromPackageJson() : + undefined; // other extensions, like `json` or `tsbuildinfo`, are set as `undefined` here but they should never be fed through the transformer pipeline + default: + return undefined; } + function lookupFromPackageJson(): ModuleKind.ESNext | ModuleKind.CommonJS { + const scope = getPackageScopeForPath(fileName, packageJsonInfoCache, host, options); + return scope?.packageJsonContent.type === "module" ? ModuleKind.ESNext : ModuleKind.CommonJS; - export function getConfigFileParsingDiagnostics(configFileParseResult: ParsedCommandLine): readonly Diagnostic[] { - return configFileParseResult.options.configFile ? - [...configFileParseResult.options.configFile.parseDiagnostics, ...configFileParseResult.errors] : - configFileParseResult.errors; } +} - /** - * A function for determining if a given file is esm or cjs format, assuming modern node module resolution rules, as configured by the - * `options` parameter. - * - * @param fileName The normalized absolute path to check the format of (it need not exist on disk) - * @param [packageJsonInfoCache] A cache for package file lookups - it's best to have a cache when this function is called often - * @param host The ModuleResolutionHost which can perform the filesystem lookups for package json data - * @param options The compiler options to perform the analysis under - relevant options are `moduleResolution` and `traceResolution` - * @returns `undefined` if the path has no relevant implied format, `ModuleKind.ESNext` for esm format, and `ModuleKind.CommonJS` for cjs format - */ - export function getImpliedNodeFormatForFile(fileName: Path, packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): ModuleKind.ESNext | ModuleKind.CommonJS | undefined { - switch (getEmitModuleResolutionKind(options)) { - case ModuleResolutionKind.Node12: - case ModuleResolutionKind.NodeNext: - return fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Mjs]) ? ModuleKind.ESNext : - fileExtensionIsOneOf(fileName, [Extension.Dcts, Extension.Cts, Extension.Cjs]) ? ModuleKind.CommonJS : - fileExtensionIsOneOf(fileName, [Extension.Dts, Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx]) ? lookupFromPackageJson() : - undefined; // other extensions, like `json` or `tsbuildinfo`, are set as `undefined` here but they should never be fed through the transformer pipeline - default: - return undefined; - } - function lookupFromPackageJson(): ModuleKind.ESNext | ModuleKind.CommonJS { - const scope = getPackageScopeForPath(fileName, packageJsonInfoCache, host, options); - return scope?.packageJsonContent.type === "module" ? ModuleKind.ESNext : ModuleKind.CommonJS; - - } - } +/** @internal */ +export const plainJSErrors: Set = new Set([ + Diagnostics.Cannot_redeclare_block_scoped_variable_0.code, + Diagnostics.A_module_cannot_have_multiple_default_exports.code, + Diagnostics.Another_export_default_is_here.code, + Diagnostics.The_first_export_default_is_here.code, + Diagnostics.Identifier_expected_0_is_a_reserved_word_at_the_top_level_of_a_module.code, + Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode.code, + Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here.code, + Diagnostics.constructor_is_a_reserved_word.code, + Diagnostics.delete_cannot_be_called_on_an_identifier_in_strict_mode.code, + Diagnostics.Code_contained_in_a_class_is_evaluated_in_JavaScript_s_strict_mode_which_does_not_allow_this_use_of_0_For_more_information_see_https_Colon_Slash_Slashdeveloper_mozilla_org_Slashen_US_Slashdocs_SlashWeb_SlashJavaScript_SlashReference_SlashStrict_mode.code, + Diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode.code, + Diagnostics.Invalid_use_of_0_in_strict_mode.code, + Diagnostics.A_label_is_not_allowed_here.code, + Diagnostics.Octal_literals_are_not_allowed_in_strict_mode.code, + Diagnostics.with_statements_are_not_allowed_in_strict_mode.code, +]); + +/** + * Determine if source file needs to be re-created even if its text hasn't changed + */ +function shouldProgramCreateNewSourceFiles(program: Program | undefined, newOptions: CompilerOptions): boolean { + if (!program) return false; + // If any compiler options change, we can't reuse old source file even if version match + // The change in options like these could result in change in syntax tree or `sourceFile.bindDiagnostics`. + return optionsHaveChanges(program.getCompilerOptions(), newOptions, sourceFileAffectingCompilerOptions); +} - /** @internal */ - export const plainJSErrors: Set = new Set([ - Diagnostics.Cannot_redeclare_block_scoped_variable_0.code, - Diagnostics.A_module_cannot_have_multiple_default_exports.code, - Diagnostics.Another_export_default_is_here.code, - Diagnostics.The_first_export_default_is_here.code, - Diagnostics.Identifier_expected_0_is_a_reserved_word_at_the_top_level_of_a_module.code, - Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode.code, - Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here.code, - Diagnostics.constructor_is_a_reserved_word.code, - Diagnostics.delete_cannot_be_called_on_an_identifier_in_strict_mode.code, - Diagnostics.Code_contained_in_a_class_is_evaluated_in_JavaScript_s_strict_mode_which_does_not_allow_this_use_of_0_For_more_information_see_https_Colon_Slash_Slashdeveloper_mozilla_org_Slashen_US_Slashdocs_SlashWeb_SlashJavaScript_SlashReference_SlashStrict_mode.code, - Diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode.code, - Diagnostics.Invalid_use_of_0_in_strict_mode.code, - Diagnostics.A_label_is_not_allowed_here.code, - Diagnostics.Octal_literals_are_not_allowed_in_strict_mode.code, - Diagnostics.with_statements_are_not_allowed_in_strict_mode.code, - ]); +function createCreateProgramOptions(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): CreateProgramOptions { + return { + rootNames, + options, + host, + oldProgram, + configFileParsingDiagnostics + }; +} - /** - * Determine if source file needs to be re-created even if its text hasn't changed - */ - function shouldProgramCreateNewSourceFiles(program: Program | undefined, newOptions: CompilerOptions): boolean { - if (!program) return false; - // If any compiler options change, we can't reuse old source file even if version match - // The change in options like these could result in change in syntax tree or `sourceFile.bindDiagnostics`. - return optionsHaveChanges(program.getCompilerOptions(), newOptions, sourceFileAffectingCompilerOptions); +/** + * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' + * that represent a compilation unit. + * + * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and + * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. + * + * @param createProgramOptions - The options for creating a program. + * @returns A 'Program' object. + */ +export function createProgram(createProgramOptions: CreateProgramOptions): Program; +/** + * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' + * that represent a compilation unit. + * + * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and + * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. + * + * @param rootNames - A set of root files. + * @param options - The compiler options which should be used. + * @param host - The host interacts with the underlying file system. + * @param oldProgram - Reuses an old program structure. + * @param configFileParsingDiagnostics - error during config file parsing + * @returns A 'Program' object. + */ +export function createProgram(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): Program; +export function createProgram(rootNamesOrOptions: readonly string[] | CreateProgramOptions, _options?: CompilerOptions, _host?: CompilerHost, _oldProgram?: Program, _configFileParsingDiagnostics?: readonly Diagnostic[]): Program { + const createProgramOptions = isArray(rootNamesOrOptions) ? createCreateProgramOptions(rootNamesOrOptions, _options!, _host, _oldProgram, _configFileParsingDiagnostics) : rootNamesOrOptions; // TODO: GH#18217 + const { rootNames, options, configFileParsingDiagnostics, projectReferences } = createProgramOptions; + let { oldProgram } = createProgramOptions; + + let processingDefaultLibFiles: SourceFile[] | undefined; + let processingOtherFiles: SourceFile[] | undefined; + let files: SourceFile[]; + let symlinks: SymlinkCache | undefined; + let commonSourceDirectory: string; + let diagnosticsProducingTypeChecker: TypeChecker; + let noDiagnosticsTypeChecker: TypeChecker; + let classifiableNames: Set<__String>; + const ambientModuleNameToUnmodifiedFileName = new Map(); + let fileReasons = createMultiMap(); + const cachedBindAndCheckDiagnosticsForFile: DiagnosticCache = {}; + const cachedDeclarationDiagnosticsForFile: DiagnosticCache = {}; + + let resolvedTypeReferenceDirectives = new Map(); + let fileProcessingDiagnostics: FilePreprocessingDiagnostics[] | undefined; + + // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. + // This works as imported modules are discovered recursively in a depth first manner, specifically: + // - For each root file, findSourceFile is called. + // - This calls processImportedModules for each module imported in the source file. + // - This calls resolveModuleNames, and then calls findSourceFile for each resolved module. + // As all these operations happen - and are nested - within the createProgram call, they close over the below variables. + // The current resolution depth is tracked by incrementing/decrementing as the depth first search progresses. + const maxNodeModuleJsDepth = typeof options.maxNodeModuleJsDepth === "number" ? options.maxNodeModuleJsDepth : 0; + let currentNodeModulesDepth = 0; + + // If a module has some of its imports skipped due to being at the depth limit under node_modules, then track + // this, as it may be imported at a shallower depth later, and then it will need its skipped imports processed. + const modulesWithElidedImports = new Map(); + + // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. + const sourceFilesFoundSearchingNodeModules = new Map(); + + tracing?.push(tracing.Phase.Program, "createProgram", { configFilePath: options.configFilePath, rootDir: options.rootDir }, /*separateBeginAndEnd*/ true); + performance.mark("beforeProgram"); + + const host = createProgramOptions.host || createCompilerHost(options); + const configParsingHost = parseConfigHostFromCompilerHostLike(host); + + let skipDefaultLib = options.noLib; + const getDefaultLibraryFileName = memoize(() => host.getDefaultLibFileName(options)); + const defaultLibraryPath = host.getDefaultLibLocation ? host.getDefaultLibLocation() : getDirectoryPath(getDefaultLibraryFileName()); + const programDiagnostics = createDiagnosticCollection(); + const currentDirectory = host.getCurrentDirectory(); + const supportedExtensions = getSupportedExtensions(options); + const supportedExtensionsWithJsonIfResolveJsonModule = getSupportedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); + + // Map storing if there is emit blocking diagnostics for given input + const hasEmitBlockingDiagnostics = new Map(); + let _compilerOptionsObjectLiteralSyntax: ObjectLiteralExpression | false | undefined; + + let moduleResolutionCache: ModuleResolutionCache | undefined; + let typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache | undefined; + let actualResolveModuleNamesWorker: (moduleNames: string[], containingFile: SourceFile, containingFileName: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference) => ResolvedModuleFull[]; + const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; + if (host.resolveModuleNames) { + actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, reusedNames, redirectedReference) => host.resolveModuleNames!(Debug.checkEachDefined(moduleNames), containingFileName, reusedNames, redirectedReference, options, containingFile).map(resolved => { + // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. + if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) { + return resolved as ResolvedModuleFull; + } + const withExtension = clone(resolved) as ResolvedModuleFull; + withExtension.extension = extensionFromPath(resolved.resolvedFileName); + return withExtension; + }); + moduleResolutionCache = host.getModuleResolutionCache?.(); + } + else { + moduleResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options); + const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFileName, options, host, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule!; // TODO: GH#18217 + actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, _reusedNames, redirectedReference) => loadWithModeAwareCache(Debug.checkEachDefined(moduleNames), containingFile, containingFileName, redirectedReference, loader); } - function createCreateProgramOptions(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): CreateProgramOptions { - return { - rootNames, + let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference) => (ResolvedTypeReferenceDirective | undefined)[]; + if (host.resolveTypeReferenceDirectives) { + actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference) => host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options); + } + else { + typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache(currentDirectory, getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache()); + const loader = (typesRef: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveTypeReferenceDirective( + typesRef, + containingFile, options, host, - oldProgram, - configFileParsingDiagnostics - }; + redirectedReference, + typeReferenceDirectiveResolutionCache, + ).resolvedTypeReferenceDirective!; // TODO: GH#18217 + actualResolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile, redirectedReference) => loadWithLocalCache(Debug.checkEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, loader); } + // Map from a stringified PackageId to the source file with that id. + // Only one source file may have a given packageId. Others become redirects (see createRedirectSourceFile). + // `packageIdToSourceFile` is only used while building the program, while `sourceFileToPackageName` and `isSourceFileTargetOfRedirect` are kept around. + const packageIdToSourceFile = new Map(); + // Maps from a SourceFile's `.path` to the name of the package it was imported with. + let sourceFileToPackageName = new Map(); + // Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it. + let redirectTargetsMap = createMultiMap(); + let usesUriStyleNodeCoreModules = false; + /** - * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' - * that represent a compilation unit. - * - * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and - * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. - * - * @param createProgramOptions - The options for creating a program. - * @returns A 'Program' object. - */ - export function createProgram(createProgramOptions: CreateProgramOptions): Program; - /** - * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' - * that represent a compilation unit. - * - * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and - * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. - * - * @param rootNames - A set of root files. - * @param options - The compiler options which should be used. - * @param host - The host interacts with the underlying file system. - * @param oldProgram - Reuses an old program structure. - * @param configFileParsingDiagnostics - error during config file parsing - * @returns A 'Program' object. + * map with + * - SourceFile if present + * - false if sourceFile missing for source of project reference redirect + * - undefined otherwise */ - export function createProgram(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): Program; - export function createProgram(rootNamesOrOptions: readonly string[] | CreateProgramOptions, _options?: CompilerOptions, _host?: CompilerHost, _oldProgram?: Program, _configFileParsingDiagnostics?: readonly Diagnostic[]): Program { - const createProgramOptions = isArray(rootNamesOrOptions) ? createCreateProgramOptions(rootNamesOrOptions, _options!, _host, _oldProgram, _configFileParsingDiagnostics) : rootNamesOrOptions; // TODO: GH#18217 - const { rootNames, options, configFileParsingDiagnostics, projectReferences } = createProgramOptions; - let { oldProgram } = createProgramOptions; - - let processingDefaultLibFiles: SourceFile[] | undefined; - let processingOtherFiles: SourceFile[] | undefined; - let files: SourceFile[]; - let symlinks: SymlinkCache | undefined; - let commonSourceDirectory: string; - let diagnosticsProducingTypeChecker: TypeChecker; - let noDiagnosticsTypeChecker: TypeChecker; - let classifiableNames: Set<__String>; - const ambientModuleNameToUnmodifiedFileName = new Map(); - let fileReasons = createMultiMap(); - const cachedBindAndCheckDiagnosticsForFile: DiagnosticCache = {}; - const cachedDeclarationDiagnosticsForFile: DiagnosticCache = {}; - - let resolvedTypeReferenceDirectives = new Map(); - let fileProcessingDiagnostics: FilePreprocessingDiagnostics[] | undefined; - - // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. - // This works as imported modules are discovered recursively in a depth first manner, specifically: - // - For each root file, findSourceFile is called. - // - This calls processImportedModules for each module imported in the source file. - // - This calls resolveModuleNames, and then calls findSourceFile for each resolved module. - // As all these operations happen - and are nested - within the createProgram call, they close over the below variables. - // The current resolution depth is tracked by incrementing/decrementing as the depth first search progresses. - const maxNodeModuleJsDepth = typeof options.maxNodeModuleJsDepth === "number" ? options.maxNodeModuleJsDepth : 0; - let currentNodeModulesDepth = 0; - - // If a module has some of its imports skipped due to being at the depth limit under node_modules, then track - // this, as it may be imported at a shallower depth later, and then it will need its skipped imports processed. - const modulesWithElidedImports = new Map(); - - // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. - const sourceFilesFoundSearchingNodeModules = new Map(); - - tracing?.push(tracing.Phase.Program, "createProgram", { configFilePath: options.configFilePath, rootDir: options.rootDir }, /*separateBeginAndEnd*/ true); - performance.mark("beforeProgram"); - - const host = createProgramOptions.host || createCompilerHost(options); - const configParsingHost = parseConfigHostFromCompilerHostLike(host); - - let skipDefaultLib = options.noLib; - const getDefaultLibraryFileName = memoize(() => host.getDefaultLibFileName(options)); - const defaultLibraryPath = host.getDefaultLibLocation ? host.getDefaultLibLocation() : getDirectoryPath(getDefaultLibraryFileName()); - const programDiagnostics = createDiagnosticCollection(); - const currentDirectory = host.getCurrentDirectory(); - const supportedExtensions = getSupportedExtensions(options); - const supportedExtensionsWithJsonIfResolveJsonModule = getSupportedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); - - // Map storing if there is emit blocking diagnostics for given input - const hasEmitBlockingDiagnostics = new Map(); - let _compilerOptionsObjectLiteralSyntax: ObjectLiteralExpression | false | undefined; - - let moduleResolutionCache: ModuleResolutionCache | undefined; - let typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache | undefined; - let actualResolveModuleNamesWorker: (moduleNames: string[], containingFile: SourceFile, containingFileName: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference) => ResolvedModuleFull[]; - const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; - if (host.resolveModuleNames) { - actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, reusedNames, redirectedReference) => host.resolveModuleNames!(Debug.checkEachDefined(moduleNames), containingFileName, reusedNames, redirectedReference, options, containingFile).map(resolved => { - // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. - if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) { - return resolved as ResolvedModuleFull; - } - const withExtension = clone(resolved) as ResolvedModuleFull; - withExtension.extension = extensionFromPath(resolved.resolvedFileName); - return withExtension; - }); - moduleResolutionCache = host.getModuleResolutionCache?.(); - } - else { - moduleResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options); - const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFileName, options, host, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule!; // TODO: GH#18217 - actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, _reusedNames, redirectedReference) => loadWithModeAwareCache(Debug.checkEachDefined(moduleNames), containingFile, containingFileName, redirectedReference, loader); - } - - let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference) => (ResolvedTypeReferenceDirective | undefined)[]; - if (host.resolveTypeReferenceDirectives) { - actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference) => host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options); - } - else { - typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache(currentDirectory, getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache()); - const loader = (typesRef: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveTypeReferenceDirective( - typesRef, - containingFile, - options, - host, - redirectedReference, - typeReferenceDirectiveResolutionCache, - ).resolvedTypeReferenceDirective!; // TODO: GH#18217 - actualResolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile, redirectedReference) => loadWithLocalCache(Debug.checkEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, loader); - } - - // Map from a stringified PackageId to the source file with that id. - // Only one source file may have a given packageId. Others become redirects (see createRedirectSourceFile). - // `packageIdToSourceFile` is only used while building the program, while `sourceFileToPackageName` and `isSourceFileTargetOfRedirect` are kept around. - const packageIdToSourceFile = new Map(); - // Maps from a SourceFile's `.path` to the name of the package it was imported with. - let sourceFileToPackageName = new Map(); - // Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it. - let redirectTargetsMap = createMultiMap(); - let usesUriStyleNodeCoreModules = false; - - /** - * map with - * - SourceFile if present - * - false if sourceFile missing for source of project reference redirect - * - undefined otherwise - */ - const filesByName = new Map(); - let missingFilePaths: readonly Path[] | undefined; - // stores 'filename -> file association' ignoring case - // used to track cases when two file names differ only in casing - const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? new Map() : undefined; - - // A parallel array to projectReferences storing the results of reading in the referenced tsconfig files - let resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined; - let projectReferenceRedirects: ESMap | undefined; - let mapFromFileToProjectReferenceRedirects: ESMap | undefined; - let mapFromToProjectReferenceRedirectSource: ESMap | undefined; - - const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect?.() && - !options.disableSourceOfProjectReferenceRedirect; - const { onProgramCreateComplete, fileExists, directoryExists } = updateHostForUseSourceOfProjectReferenceRedirect({ - compilerHost: host, - getSymlinkCache, - useSourceOfProjectReferenceRedirect, - toPath, - getResolvedProjectReferences, - getSourceOfProjectReferenceRedirect, - forEachResolvedProjectReference - }); - const readFile = host.readFile.bind(host) as typeof host.readFile; - - tracing?.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram }); - const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); - tracing?.pop(); - // We set `structuralIsReused` to `undefined` because `tryReuseStructureFromOldProgram` calls `tryReuseStructureFromOldProgram` which checks - // `structuralIsReused`, which would be a TDZ violation if it was not set in advance to `undefined`. - let structureIsReused: StructureIsReused; - tracing?.push(tracing.Phase.Program, "tryReuseStructureFromOldProgram", {}); - structureIsReused = tryReuseStructureFromOldProgram(); // eslint-disable-line prefer-const - tracing?.pop(); - if (structureIsReused !== StructureIsReused.Completely) { - processingDefaultLibFiles = []; - processingOtherFiles = []; - - if (projectReferences) { - if (!resolvedProjectReferences) { - resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); - } - if (rootNames.length) { - resolvedProjectReferences?.forEach((parsedRef, index) => { - if (!parsedRef) return; - const out = outFile(parsedRef.commandLine.options); - if (useSourceOfProjectReferenceRedirect) { - if (out || getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { - for (const fileName of parsedRef.commandLine.fileNames) { - processProjectReferenceFile(fileName, { kind: FileIncludeKind.SourceFromProjectReference, index }); - } + const filesByName = new Map(); + let missingFilePaths: readonly Path[] | undefined; + // stores 'filename -> file association' ignoring case + // used to track cases when two file names differ only in casing + const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? new Map() : undefined; + + // A parallel array to projectReferences storing the results of reading in the referenced tsconfig files + let resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined; + let projectReferenceRedirects: ESMap | undefined; + let mapFromFileToProjectReferenceRedirects: ESMap | undefined; + let mapFromToProjectReferenceRedirectSource: ESMap | undefined; + + const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect?.() && + !options.disableSourceOfProjectReferenceRedirect; + const { onProgramCreateComplete, fileExists, directoryExists } = updateHostForUseSourceOfProjectReferenceRedirect({ + compilerHost: host, + getSymlinkCache, + useSourceOfProjectReferenceRedirect, + toPath, + getResolvedProjectReferences, + getSourceOfProjectReferenceRedirect, + forEachResolvedProjectReference + }); + const readFile = host.readFile.bind(host) as typeof host.readFile; + + tracing?.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram }); + const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); + tracing?.pop(); + // We set `structuralIsReused` to `undefined` because `tryReuseStructureFromOldProgram` calls `tryReuseStructureFromOldProgram` which checks + // `structuralIsReused`, which would be a TDZ violation if it was not set in advance to `undefined`. + let structureIsReused: StructureIsReused; + tracing?.push(tracing.Phase.Program, "tryReuseStructureFromOldProgram", {}); + structureIsReused = tryReuseStructureFromOldProgram(); // eslint-disable-line prefer-const + tracing?.pop(); + if (structureIsReused !== StructureIsReused.Completely) { + processingDefaultLibFiles = []; + processingOtherFiles = []; + + if (projectReferences) { + if (!resolvedProjectReferences) { + resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); + } + if (rootNames.length) { + resolvedProjectReferences?.forEach((parsedRef, index) => { + if (!parsedRef) return; + const out = outFile(parsedRef.commandLine.options); + if (useSourceOfProjectReferenceRedirect) { + if (out || getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { + for (const fileName of parsedRef.commandLine.fileNames) { + processProjectReferenceFile(fileName, { kind: FileIncludeKind.SourceFromProjectReference, index }); } } - else { - if (out) { - processProjectReferenceFile(changeExtension(out, ".d.ts"), { kind: FileIncludeKind.OutputFromProjectReference, index }); - } - else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { - const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(parsedRef.commandLine, !host.useCaseSensitiveFileNames())); - for (const fileName of parsedRef.commandLine.fileNames) { - if (!fileExtensionIs(fileName, Extension.Dts) && !fileExtensionIs(fileName, Extension.Json)) { - processProjectReferenceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames(), getCommonSourceDirectory), { kind: FileIncludeKind.OutputFromProjectReference, index }); - } + } + else { + if (out) { + processProjectReferenceFile(changeExtension(out, ".d.ts"), { kind: FileIncludeKind.OutputFromProjectReference, index }); + } + else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { + const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(parsedRef.commandLine, !host.useCaseSensitiveFileNames())); + for (const fileName of parsedRef.commandLine.fileNames) { + if (!fileExtensionIs(fileName, Extension.Dts) && !fileExtensionIs(fileName, Extension.Json)) { + processProjectReferenceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames(), getCommonSourceDirectory), { kind: FileIncludeKind.OutputFromProjectReference, index }); } } } - }); - } + } + }); } + } - tracing?.push(tracing.Phase.Program, "processRootFiles", { count: rootNames.length }); - forEach(rootNames, (name, index) => processRootFile(name, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.RootFile, index })); - tracing?.pop(); + tracing?.push(tracing.Phase.Program, "processRootFiles", { count: rootNames.length }); + forEach(rootNames, (name, index) => processRootFile(name, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.RootFile, index })); + tracing?.pop(); - // load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders - const typeReferences: string[] = rootNames.length ? getAutomaticTypeDirectiveNames(options, host) : emptyArray; - - if (typeReferences.length) { - tracing?.push(tracing.Phase.Program, "processTypeReferences", { count: typeReferences.length }); - // This containingFilename needs to match with the one used in managed-side - const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : host.getCurrentDirectory(); - const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile); - const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename); - for (let i = 0; i < typeReferences.length; i++) { - processTypeReferenceDirective(typeReferences[i], resolutions[i], { kind: FileIncludeKind.AutomaticTypeDirectiveFile, typeReference: typeReferences[i], packageId: resolutions[i]?.packageId }); - } - tracing?.pop(); - } - - // Do not process the default library if: - // - The '--noLib' flag is used. - // - A 'no-default-lib' reference comment is encountered in - // processing the root files. - if (rootNames.length && !skipDefaultLib) { - // If '--lib' is not specified, include default library file according to '--target' - // otherwise, using options specified in '--lib' instead of '--target' default library file - const defaultLibraryFileName = getDefaultLibraryFileName(); - if (!options.lib && defaultLibraryFileName) { - processRootFile(defaultLibraryFileName, /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.LibFile }); - } - else { - forEach(options.lib, (libFileName, index) => { - processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.LibFile, index }); - }); - } - } + // load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders + const typeReferences: string[] = rootNames.length ? getAutomaticTypeDirectiveNames(options, host) : emptyArray; - missingFilePaths = arrayFrom(mapDefinedIterator(filesByName.entries(), ([path, file]) => file === undefined ? path as Path : undefined)); - files = stableSort(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles); - processingDefaultLibFiles = undefined; - processingOtherFiles = undefined; + if (typeReferences.length) { + tracing?.push(tracing.Phase.Program, "processTypeReferences", { count: typeReferences.length }); + // This containingFilename needs to match with the one used in managed-side + const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : host.getCurrentDirectory(); + const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile); + const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename); + for (let i = 0; i < typeReferences.length; i++) { + processTypeReferenceDirective(typeReferences[i], resolutions[i], { kind: FileIncludeKind.AutomaticTypeDirectiveFile, typeReference: typeReferences[i], packageId: resolutions[i]?.packageId }); + } + tracing?.pop(); } - Debug.assert(!!missingFilePaths); - - // Release any files we have acquired in the old program but are - // not part of the new program. - if (oldProgram && host.onReleaseOldSourceFile) { - const oldSourceFiles = oldProgram.getSourceFiles(); - for (const oldSourceFile of oldSourceFiles) { - const newFile = getSourceFileByPath(oldSourceFile.resolvedPath); - if (shouldCreateNewSourceFile || !newFile || - // old file wasn't redirect but new file is - (oldSourceFile.resolvedPath === oldSourceFile.path && newFile.resolvedPath !== oldSourceFile.path)) { - host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions(), !!getSourceFileByPath(oldSourceFile.path)); - } + // Do not process the default library if: + // - The '--noLib' flag is used. + // - A 'no-default-lib' reference comment is encountered in + // processing the root files. + if (rootNames.length && !skipDefaultLib) { + // If '--lib' is not specified, include default library file according to '--target' + // otherwise, using options specified in '--lib' instead of '--target' default library file + const defaultLibraryFileName = getDefaultLibraryFileName(); + if (!options.lib && defaultLibraryFileName) { + processRootFile(defaultLibraryFileName, /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.LibFile }); } - if (!host.getParsedCommandLine) { - oldProgram.forEachResolvedProjectReference(resolvedProjectReference => { - if (!getResolvedProjectReferenceByPath(resolvedProjectReference.sourceFile.path)) { - host.onReleaseOldSourceFile!(resolvedProjectReference.sourceFile, oldProgram!.getCompilerOptions(), /*hasSourceFileByPath*/ false); - } + else { + forEach(options.lib, (libFileName, index) => { + processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.LibFile, index }); }); } } - // Release commandlines that new program does not use - if (oldProgram && host.onReleaseParsedCommandLine) { - forEachProjectReference( - oldProgram.getProjectReferences(), - oldProgram.getResolvedProjectReferences(), - (oldResolvedRef, parent, index) => { - const oldReference = parent?.commandLine.projectReferences![index] || oldProgram!.getProjectReferences()![index]; - const oldRefPath = resolveProjectReferencePath(oldReference); - if (!projectReferenceRedirects?.has(toPath(oldRefPath))) { - host.onReleaseParsedCommandLine!(oldRefPath, oldResolvedRef, oldProgram!.getCompilerOptions()); - } + missingFilePaths = arrayFrom(mapDefinedIterator(filesByName.entries(), ([path, file]) => file === undefined ? path as Path : undefined)); + files = stableSort(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles); + processingDefaultLibFiles = undefined; + processingOtherFiles = undefined; + } + + Debug.assert(!!missingFilePaths); + + // Release any files we have acquired in the old program but are + // not part of the new program. + if (oldProgram && host.onReleaseOldSourceFile) { + const oldSourceFiles = oldProgram.getSourceFiles(); + for (const oldSourceFile of oldSourceFiles) { + const newFile = getSourceFileByPath(oldSourceFile.resolvedPath); + if (shouldCreateNewSourceFile || !newFile || + // old file wasn't redirect but new file is + (oldSourceFile.resolvedPath === oldSourceFile.path && newFile.resolvedPath !== oldSourceFile.path)) { + host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions(), !!getSourceFileByPath(oldSourceFile.path)); + } + } + if (!host.getParsedCommandLine) { + oldProgram.forEachResolvedProjectReference(resolvedProjectReference => { + if (!getResolvedProjectReferenceByPath(resolvedProjectReference.sourceFile.path)) { + host.onReleaseOldSourceFile!(resolvedProjectReference.sourceFile, oldProgram!.getCompilerOptions(), /*hasSourceFileByPath*/ false); } - ); + }); } + } - typeReferenceDirectiveResolutionCache = undefined; - - // unconditionally set oldProgram to undefined to prevent it from being captured in closure - oldProgram = undefined; - - const program: Program = { - getRootFileNames: () => rootNames, - getSourceFile, - getSourceFileByPath, - getSourceFiles: () => files, - getMissingFilePaths: () => missingFilePaths!, // TODO: GH#18217 - getModuleResolutionCache: () => moduleResolutionCache, - getFilesByNameMap: () => filesByName, - getCompilerOptions: () => options, - getSyntacticDiagnostics, - getOptionsDiagnostics, - getGlobalDiagnostics, - getSemanticDiagnostics, - getCachedSemanticDiagnostics, - getSuggestionDiagnostics, - getDeclarationDiagnostics, - getBindAndCheckDiagnostics, - getProgramDiagnostics, - getTypeChecker, - getClassifiableNames, - getDiagnosticsProducingTypeChecker, - getCommonSourceDirectory, - emit, - getCurrentDirectory: () => currentDirectory, - getNodeCount: () => getDiagnosticsProducingTypeChecker().getNodeCount(), - getIdentifierCount: () => getDiagnosticsProducingTypeChecker().getIdentifierCount(), - getSymbolCount: () => getDiagnosticsProducingTypeChecker().getSymbolCount(), - getTypeCount: () => getDiagnosticsProducingTypeChecker().getTypeCount(), - getInstantiationCount: () => getDiagnosticsProducingTypeChecker().getInstantiationCount(), - getRelationCacheSizes: () => getDiagnosticsProducingTypeChecker().getRelationCacheSizes(), - getFileProcessingDiagnostics: () => fileProcessingDiagnostics, - getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives, - isSourceFileFromExternalLibrary, - isSourceFileDefaultLibrary, - dropDiagnosticsProducingTypeChecker, - getSourceFileFromReference, - getLibFileFromReference, - sourceFileToPackageName, - redirectTargetsMap, - usesUriStyleNodeCoreModules, - isEmittedFile, - getConfigFileParsingDiagnostics, - getResolvedModuleWithFailedLookupLocationsFromCache, - getProjectReferences, - getResolvedProjectReferences, - getProjectReferenceRedirect, - getResolvedProjectReferenceToRedirect, - getResolvedProjectReferenceByPath, - forEachResolvedProjectReference, - isSourceOfProjectReferenceRedirect, - emitBuildInfo, - fileExists, - readFile, - directoryExists, - getSymlinkCache, - realpath: host.realpath?.bind(host), - useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), - getFileIncludeReasons: () => fileReasons, - structureIsReused, - }; + // Release commandlines that new program does not use + if (oldProgram && host.onReleaseParsedCommandLine) { + forEachProjectReference( + oldProgram.getProjectReferences(), + oldProgram.getResolvedProjectReferences(), + (oldResolvedRef, parent, index) => { + const oldReference = parent?.commandLine.projectReferences![index] || oldProgram!.getProjectReferences()![index]; + const oldRefPath = resolveProjectReferencePath(oldReference); + if (!projectReferenceRedirects?.has(toPath(oldRefPath))) { + host.onReleaseParsedCommandLine!(oldRefPath, oldResolvedRef, oldProgram!.getCompilerOptions()); + } + } + ); + } - onProgramCreateComplete(); + typeReferenceDirectiveResolutionCache = undefined; + + // unconditionally set oldProgram to undefined to prevent it from being captured in closure + oldProgram = undefined; + + const program: Program = { + getRootFileNames: () => rootNames, + getSourceFile, + getSourceFileByPath, + getSourceFiles: () => files, + getMissingFilePaths: () => missingFilePaths!, // TODO: GH#18217 + getModuleResolutionCache: () => moduleResolutionCache, + getFilesByNameMap: () => filesByName, + getCompilerOptions: () => options, + getSyntacticDiagnostics, + getOptionsDiagnostics, + getGlobalDiagnostics, + getSemanticDiagnostics, + getCachedSemanticDiagnostics, + getSuggestionDiagnostics, + getDeclarationDiagnostics, + getBindAndCheckDiagnostics, + getProgramDiagnostics, + getTypeChecker, + getClassifiableNames, + getDiagnosticsProducingTypeChecker, + getCommonSourceDirectory, + emit, + getCurrentDirectory: () => currentDirectory, + getNodeCount: () => getDiagnosticsProducingTypeChecker().getNodeCount(), + getIdentifierCount: () => getDiagnosticsProducingTypeChecker().getIdentifierCount(), + getSymbolCount: () => getDiagnosticsProducingTypeChecker().getSymbolCount(), + getTypeCount: () => getDiagnosticsProducingTypeChecker().getTypeCount(), + getInstantiationCount: () => getDiagnosticsProducingTypeChecker().getInstantiationCount(), + getRelationCacheSizes: () => getDiagnosticsProducingTypeChecker().getRelationCacheSizes(), + getFileProcessingDiagnostics: () => fileProcessingDiagnostics, + getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives, + isSourceFileFromExternalLibrary, + isSourceFileDefaultLibrary, + dropDiagnosticsProducingTypeChecker, + getSourceFileFromReference, + getLibFileFromReference, + sourceFileToPackageName, + redirectTargetsMap, + usesUriStyleNodeCoreModules, + isEmittedFile, + getConfigFileParsingDiagnostics, + getResolvedModuleWithFailedLookupLocationsFromCache, + getProjectReferences, + getResolvedProjectReferences, + getProjectReferenceRedirect, + getResolvedProjectReferenceToRedirect, + getResolvedProjectReferenceByPath, + forEachResolvedProjectReference, + isSourceOfProjectReferenceRedirect, + emitBuildInfo, + fileExists, + readFile, + directoryExists, + getSymlinkCache, + realpath: host.realpath?.bind(host), + useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), + getFileIncludeReasons: () => fileReasons, + structureIsReused, + }; - // Add file processingDiagnostics - fileProcessingDiagnostics?.forEach(diagnostic => { - switch (diagnostic.kind) { - case FilePreprocessingDiagnosticsKind.FilePreprocessingFileExplainingDiagnostic: - return programDiagnostics.add(createDiagnosticExplainingFile(diagnostic.file && getSourceFileByPath(diagnostic.file), diagnostic.fileProcessingReason, diagnostic.diagnostic, diagnostic.args || emptyArray)); - case FilePreprocessingDiagnosticsKind.FilePreprocessingReferencedDiagnostic: - const { file, pos, end } = getReferencedFileLocation(getSourceFileByPath, diagnostic.reason) as ReferenceFileLocation; - return programDiagnostics.add(createFileDiagnostic(file, Debug.checkDefined(pos), Debug.checkDefined(end) - pos, diagnostic.diagnostic, ...diagnostic.args || emptyArray)); - default: - Debug.assertNever(diagnostic); - } - }); + onProgramCreateComplete(); - verifyCompilerOptions(); - performance.mark("afterProgram"); - performance.measure("Program", "beforeProgram", "afterProgram"); + // Add file processingDiagnostics + fileProcessingDiagnostics?.forEach(diagnostic => { + switch (diagnostic.kind) { + case FilePreprocessingDiagnosticsKind.FilePreprocessingFileExplainingDiagnostic: + return programDiagnostics.add(createDiagnosticExplainingFile(diagnostic.file && getSourceFileByPath(diagnostic.file), diagnostic.fileProcessingReason, diagnostic.diagnostic, diagnostic.args || emptyArray)); + case FilePreprocessingDiagnosticsKind.FilePreprocessingReferencedDiagnostic: + const { file, pos, end } = getReferencedFileLocation(getSourceFileByPath, diagnostic.reason) as ReferenceFileLocation; + return programDiagnostics.add(createFileDiagnostic(file, Debug.checkDefined(pos), Debug.checkDefined(end) - pos, diagnostic.diagnostic, ...diagnostic.args || emptyArray)); + default: + Debug.assertNever(diagnostic); + } + }); + + verifyCompilerOptions(); + performance.mark("afterProgram"); + performance.measure("Program", "beforeProgram", "afterProgram"); + tracing?.pop(); + + return program; + + function resolveModuleNamesWorker(moduleNames: string[], containingFile: SourceFile, reusedNames: string[] | undefined): readonly ResolvedModuleFull[] { + if (!moduleNames.length) return emptyArray; + const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory); + const redirectedReference = getRedirectReferenceForResolution(containingFile); + tracing?.push(tracing.Phase.Program, "resolveModuleNamesWorker", { containingFileName }); + performance.mark("beforeResolveModule"); + const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, reusedNames, redirectedReference); + performance.mark("afterResolveModule"); + performance.measure("ResolveModule", "beforeResolveModule", "afterResolveModule"); tracing?.pop(); + return result; + } - return program; - - function resolveModuleNamesWorker(moduleNames: string[], containingFile: SourceFile, reusedNames: string[] | undefined): readonly ResolvedModuleFull[] { - if (!moduleNames.length) return emptyArray; - const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory); - const redirectedReference = getRedirectReferenceForResolution(containingFile); - tracing?.push(tracing.Phase.Program, "resolveModuleNamesWorker", { containingFileName }); - performance.mark("beforeResolveModule"); - const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, reusedNames, redirectedReference); - performance.mark("afterResolveModule"); - performance.measure("ResolveModule", "beforeResolveModule", "afterResolveModule"); - tracing?.pop(); - return result; - } + function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] { + if (!typeDirectiveNames.length) return []; + const containingFileName = !isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile; + const redirectedReference = !isString(containingFile) ? getRedirectReferenceForResolution(containingFile) : undefined; + tracing?.push(tracing.Phase.Program, "resolveTypeReferenceDirectiveNamesWorker", { containingFileName }); + performance.mark("beforeResolveTypeReference"); + const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference); + performance.mark("afterResolveTypeReference"); + performance.measure("ResolveTypeReference", "beforeResolveTypeReference", "afterResolveTypeReference"); + tracing?.pop(); + return result; + } - function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] { - if (!typeDirectiveNames.length) return []; - const containingFileName = !isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile; - const redirectedReference = !isString(containingFile) ? getRedirectReferenceForResolution(containingFile) : undefined; - tracing?.push(tracing.Phase.Program, "resolveTypeReferenceDirectiveNamesWorker", { containingFileName }); - performance.mark("beforeResolveTypeReference"); - const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference); - performance.mark("afterResolveTypeReference"); - performance.measure("ResolveTypeReference", "beforeResolveTypeReference", "afterResolveTypeReference"); - tracing?.pop(); - return result; - } + function getRedirectReferenceForResolution(file: SourceFile) { + const redirect = getResolvedProjectReferenceToRedirect(file.originalFileName); + if (redirect || !fileExtensionIsOneOf(file.originalFileName, [Extension.Dts, Extension.Dcts, Extension.Dmts])) return redirect; + + // The originalFileName could not be actual source file name if file found was d.ts from referecned project + // So in this case try to look up if this is output from referenced project, if it is use the redirected project in that case + const resultFromDts = getRedirectReferenceForResolutionFromSourceOfProject(file.path); + if (resultFromDts) return resultFromDts; + + // If preserveSymlinks is true, module resolution wont jump the symlink + // but the resolved real path may be the .d.ts from project reference + // Note:: Currently we try the real path only if the + // file is from node_modules to avoid having to run real path on all file paths + if (!host.realpath || !options.preserveSymlinks || !stringContains(file.originalFileName, nodeModulesPathPart)) return undefined; + const realDeclarationPath = toPath(host.realpath(file.originalFileName)); + return realDeclarationPath === file.path ? undefined : getRedirectReferenceForResolutionFromSourceOfProject(realDeclarationPath); + } - function getRedirectReferenceForResolution(file: SourceFile) { - const redirect = getResolvedProjectReferenceToRedirect(file.originalFileName); - if (redirect || !fileExtensionIsOneOf(file.originalFileName, [Extension.Dts, Extension.Dcts, Extension.Dmts])) return redirect; + function getRedirectReferenceForResolutionFromSourceOfProject(filePath: Path) { + const source = getSourceOfProjectReferenceRedirect(filePath); + if (isString(source)) return getResolvedProjectReferenceToRedirect(source); + if (!source) return undefined; + // Output of .d.ts file so return resolved ref that matches the out file name + return forEachResolvedProjectReference(resolvedRef => { + const out = outFile(resolvedRef.commandLine.options); + if (!out) return undefined; + return toPath(out) === filePath ? resolvedRef : undefined; + }); + } - // The originalFileName could not be actual source file name if file found was d.ts from referecned project - // So in this case try to look up if this is output from referenced project, if it is use the redirected project in that case - const resultFromDts = getRedirectReferenceForResolutionFromSourceOfProject(file.path); - if (resultFromDts) return resultFromDts; + function compareDefaultLibFiles(a: SourceFile, b: SourceFile) { + return compareValues(getDefaultLibFilePriority(a), getDefaultLibFilePriority(b)); + } - // If preserveSymlinks is true, module resolution wont jump the symlink - // but the resolved real path may be the .d.ts from project reference - // Note:: Currently we try the real path only if the - // file is from node_modules to avoid having to run real path on all file paths - if (!host.realpath || !options.preserveSymlinks || !stringContains(file.originalFileName, nodeModulesPathPart)) return undefined; - const realDeclarationPath = toPath(host.realpath(file.originalFileName)); - return realDeclarationPath === file.path ? undefined : getRedirectReferenceForResolutionFromSourceOfProject(realDeclarationPath); + function getDefaultLibFilePriority(a: SourceFile) { + if (containsPath(defaultLibraryPath, a.fileName, /*ignoreCase*/ false)) { + const basename = getBaseFileName(a.fileName); + if (basename === "lib.d.ts" || basename === "lib.es6.d.ts") return 0; + const name = removeSuffix(removePrefix(basename, "lib."), ".d.ts"); + const index = libs.indexOf(name); + if (index !== -1) return index + 1; } + return libs.length + 2; + } - function getRedirectReferenceForResolutionFromSourceOfProject(filePath: Path) { - const source = getSourceOfProjectReferenceRedirect(filePath); - if (isString(source)) return getResolvedProjectReferenceToRedirect(source); - if (!source) return undefined; - // Output of .d.ts file so return resolved ref that matches the out file name - return forEachResolvedProjectReference(resolvedRef => { - const out = outFile(resolvedRef.commandLine.options); - if (!out) return undefined; - return toPath(out) === filePath ? resolvedRef : undefined; - }); - } + function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, mode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined { + return moduleResolutionCache && resolveModuleNameFromCache(moduleName, containingFile, moduleResolutionCache, mode); + } + + function toPath(fileName: string): Path { + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + } - function compareDefaultLibFiles(a: SourceFile, b: SourceFile) { - return compareValues(getDefaultLibFilePriority(a), getDefaultLibFilePriority(b)); + function getCommonSourceDirectory() { + if (commonSourceDirectory === undefined) { + const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program)); + commonSourceDirectory = ts.getCommonSourceDirectory( + options, + () => mapDefined(emittedFiles, file => file.isDeclarationFile ? undefined : file.fileName), + currentDirectory, + getCanonicalFileName, + commonSourceDirectory => checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory) + ); } + return commonSourceDirectory; + } - function getDefaultLibFilePriority(a: SourceFile) { - if (containsPath(defaultLibraryPath, a.fileName, /*ignoreCase*/ false)) { - const basename = getBaseFileName(a.fileName); - if (basename === "lib.d.ts" || basename === "lib.es6.d.ts") return 0; - const name = removeSuffix(removePrefix(basename, "lib."), ".d.ts"); - const index = libs.indexOf(name); - if (index !== -1) return index + 1; + function getClassifiableNames() { + if (!classifiableNames) { + // Initialize a checker so that all our files are bound. + getTypeChecker(); + classifiableNames = new Set(); + + for (const sourceFile of files) { + sourceFile.classifiableNames?.forEach(value => classifiableNames.add(value)); } - return libs.length + 2; } - function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, mode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined { - return moduleResolutionCache && resolveModuleNameFromCache(moduleName, containingFile, moduleResolutionCache, mode); - } + return classifiableNames; + } - function toPath(fileName: string): Path { - return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + function resolveModuleNamesReusingOldState(moduleNames: string[], file: SourceFile): readonly ResolvedModuleFull[] { + if (structureIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) { + // If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules, + // the best we can do is fallback to the default logic. + return resolveModuleNamesWorker(moduleNames, file, /*reusedNames*/ undefined); } - function getCommonSourceDirectory() { - if (commonSourceDirectory === undefined) { - const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program)); - commonSourceDirectory = ts.getCommonSourceDirectory( - options, - () => mapDefined(emittedFiles, file => file.isDeclarationFile ? undefined : file.fileName), - currentDirectory, - getCanonicalFileName, - commonSourceDirectory => checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory) - ); + const oldSourceFile = oldProgram && oldProgram.getSourceFile(file.fileName); + if (oldSourceFile !== file && file.resolvedModules) { + // `file` was created for the new program. + // + // We only set `file.resolvedModules` via work from the current function, + // so it is defined iff we already called the current function on `file`. + // That call happened no later than the creation of the `file` object, + // which per above occurred during the current program creation. + // Since we assume the filesystem does not change during program creation, + // it is safe to reuse resolutions from the earlier call. + const result: ResolvedModuleFull[] = []; + let i = 0; + for (const moduleName of moduleNames) { + const resolvedModule = file.resolvedModules.get(moduleName, getModeForResolutionAtIndex(file, i))!; + i++; + result.push(resolvedModule); } - return commonSourceDirectory; + return result; } + // At this point, we know at least one of the following hold: + // - file has local declarations for ambient modules + // - old program state is available + // With this information, we can infer some module resolutions without performing resolution. - function getClassifiableNames() { - if (!classifiableNames) { - // Initialize a checker so that all our files are bound. - getTypeChecker(); - classifiableNames = new Set(); - - for (const sourceFile of files) { - sourceFile.classifiableNames?.forEach(value => classifiableNames.add(value)); + /** An ordered list of module names for which we cannot recover the resolution. */ + let unknownModuleNames: string[] | undefined; + /** + * The indexing of elements in this list matches that of `moduleNames`. + * + * Before combining results, result[i] is in one of the following states: + * * undefined: needs to be recomputed, + * * predictedToResolveToAmbientModuleMarker: known to be an ambient module. + * Needs to be reset to undefined before returning, + * * ResolvedModuleFull instance: can be reused. + */ + let result: ResolvedModuleFull[] | undefined; + let reusedNames: string[] | undefined; + /** A transient placeholder used to mark predicted resolution in the result list. */ + const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = {} as any; + + for (let i = 0; i < moduleNames.length; i++) { + const moduleName = moduleNames[i]; + // If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions + if (file === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path)) { + const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName, getModeForResolutionAtIndex(oldSourceFile, i)); + if (oldResolvedModule) { + if (isTraceEnabled(options, host)) { + trace(host, + oldResolvedModule.packageId ? + Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : + Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2, + moduleName, + getNormalizedAbsolutePath(file.originalFileName, currentDirectory), + oldResolvedModule.resolvedFileName, + oldResolvedModule.packageId && packageIdToString(oldResolvedModule.packageId) + ); + } + (result || (result = new Array(moduleNames.length)))[i] = oldResolvedModule; + (reusedNames || (reusedNames = [])).push(moduleName); + continue; + } + } + // We know moduleName resolves to an ambient module provided that moduleName: + // - is in the list of ambient modules locally declared in the current source file. + // - resolved to an ambient module in the old program whose declaration is in an unmodified file + // (so the same module declaration will land in the new program) + let resolvesToAmbientModuleInNonModifiedFile = false; + if (contains(file.ambientModuleNames, moduleName)) { + resolvesToAmbientModuleInNonModifiedFile = true; + if (isTraceEnabled(options, host)) { + trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, getNormalizedAbsolutePath(file.originalFileName, currentDirectory)); } } + else { + resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName, i); + } - return classifiableNames; + if (resolvesToAmbientModuleInNonModifiedFile) { + (result || (result = new Array(moduleNames.length)))[i] = predictedToResolveToAmbientModuleMarker; + } + else { + // Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result. + (unknownModuleNames || (unknownModuleNames = [])).push(moduleName); + } } - function resolveModuleNamesReusingOldState(moduleNames: string[], file: SourceFile): readonly ResolvedModuleFull[] { - if (structureIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) { - // If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules, - // the best we can do is fallback to the default logic. - return resolveModuleNamesWorker(moduleNames, file, /*reusedNames*/ undefined); - } + const resolutions = unknownModuleNames && unknownModuleNames.length + ? resolveModuleNamesWorker(unknownModuleNames, file, reusedNames) + : emptyArray; - const oldSourceFile = oldProgram && oldProgram.getSourceFile(file.fileName); - if (oldSourceFile !== file && file.resolvedModules) { - // `file` was created for the new program. - // - // We only set `file.resolvedModules` via work from the current function, - // so it is defined iff we already called the current function on `file`. - // That call happened no later than the creation of the `file` object, - // which per above occurred during the current program creation. - // Since we assume the filesystem does not change during program creation, - // it is safe to reuse resolutions from the earlier call. - const result: ResolvedModuleFull[] = []; - let i = 0; - for (const moduleName of moduleNames) { - const resolvedModule = file.resolvedModules.get(moduleName, getModeForResolutionAtIndex(file, i))!; - i++; - result.push(resolvedModule); + // Combine results of resolutions and predicted results + if (!result) { + // There were no unresolved/ambient resolutions. + Debug.assert(resolutions.length === moduleNames.length); + return resolutions; + } + + let j = 0; + for (let i = 0; i < result.length; i++) { + if (result[i]) { + // `result[i]` is either a `ResolvedModuleFull` or a marker. + // If it is the former, we can leave it as is. + if (result[i] === predictedToResolveToAmbientModuleMarker) { + result[i] = undefined!; // TODO: GH#18217 } - return result; } - // At this point, we know at least one of the following hold: - // - file has local declarations for ambient modules - // - old program state is available - // With this information, we can infer some module resolutions without performing resolution. - - /** An ordered list of module names for which we cannot recover the resolution. */ - let unknownModuleNames: string[] | undefined; - /** - * The indexing of elements in this list matches that of `moduleNames`. - * - * Before combining results, result[i] is in one of the following states: - * * undefined: needs to be recomputed, - * * predictedToResolveToAmbientModuleMarker: known to be an ambient module. - * Needs to be reset to undefined before returning, - * * ResolvedModuleFull instance: can be reused. - */ - let result: ResolvedModuleFull[] | undefined; - let reusedNames: string[] | undefined; - /** A transient placeholder used to mark predicted resolution in the result list. */ - const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = {} as any; - - for (let i = 0; i < moduleNames.length; i++) { - const moduleName = moduleNames[i]; - // If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions - if (file === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path)) { - const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName, getModeForResolutionAtIndex(oldSourceFile, i)); - if (oldResolvedModule) { - if (isTraceEnabled(options, host)) { - trace(host, - oldResolvedModule.packageId ? - Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : - Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2, - moduleName, - getNormalizedAbsolutePath(file.originalFileName, currentDirectory), - oldResolvedModule.resolvedFileName, - oldResolvedModule.packageId && packageIdToString(oldResolvedModule.packageId) - ); - } - (result || (result = new Array(moduleNames.length)))[i] = oldResolvedModule; - (reusedNames || (reusedNames = [])).push(moduleName); - continue; - } - } - // We know moduleName resolves to an ambient module provided that moduleName: - // - is in the list of ambient modules locally declared in the current source file. - // - resolved to an ambient module in the old program whose declaration is in an unmodified file - // (so the same module declaration will land in the new program) - let resolvesToAmbientModuleInNonModifiedFile = false; - if (contains(file.ambientModuleNames, moduleName)) { - resolvesToAmbientModuleInNonModifiedFile = true; - if (isTraceEnabled(options, host)) { - trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, getNormalizedAbsolutePath(file.originalFileName, currentDirectory)); - } - } - else { - resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName, i); - } + else { + result[i] = resolutions[j]; + j++; + } + } + Debug.assert(j === resolutions.length); - if (resolvesToAmbientModuleInNonModifiedFile) { - (result || (result = new Array(moduleNames.length)))[i] = predictedToResolveToAmbientModuleMarker; - } - else { - // Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result. - (unknownModuleNames || (unknownModuleNames = [])).push(moduleName); - } + return result; + + // If we change our policy of rechecking failed lookups on each program create, + // we should adjust the value returned here. + function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string, index: number): boolean { + if (index >= length(oldSourceFile?.imports) + length(oldSourceFile?.moduleAugmentations)) return false; // mode index out of bounds, don't reuse resolution + const resolutionToFile = getResolvedModule(oldSourceFile, moduleName, oldSourceFile && getModeForResolutionAtIndex(oldSourceFile, index)); + const resolvedFile = resolutionToFile && oldProgram!.getSourceFile(resolutionToFile.resolvedFileName); + if (resolutionToFile && resolvedFile) { + // In the old program, we resolved to an ambient module that was in the same + // place as we expected to find an actual module file. + // We actually need to return 'false' here even though this seems like a 'true' case + // because the normal module resolution algorithm will find this anyway. + return false; } - const resolutions = unknownModuleNames && unknownModuleNames.length - ? resolveModuleNamesWorker(unknownModuleNames, file, reusedNames) - : emptyArray; + // at least one of declarations should come from non-modified source file + const unmodifiedFile = ambientModuleNameToUnmodifiedFileName.get(moduleName); - // Combine results of resolutions and predicted results - if (!result) { - // There were no unresolved/ambient resolutions. - Debug.assert(resolutions.length === moduleNames.length); - return resolutions; + if (!unmodifiedFile) { + return false; } - let j = 0; - for (let i = 0; i < result.length; i++) { - if (result[i]) { - // `result[i]` is either a `ResolvedModuleFull` or a marker. - // If it is the former, we can leave it as is. - if (result[i] === predictedToResolveToAmbientModuleMarker) { - result[i] = undefined!; // TODO: GH#18217 - } + if (isTraceEnabled(options, host)) { + trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName, unmodifiedFile); + } + return true; + } + } + + function canReuseProjectReferences(): boolean { + return !forEachProjectReference( + oldProgram!.getProjectReferences(), + oldProgram!.getResolvedProjectReferences(), + (oldResolvedRef, parent, index) => { + const newRef = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; + const newResolvedRef = parseProjectReferenceConfigFile(newRef); + if (oldResolvedRef) { + // Resolved project reference has gone missing or changed + return !newResolvedRef || + newResolvedRef.sourceFile !== oldResolvedRef.sourceFile || + !arrayIsEqualTo(oldResolvedRef.commandLine.fileNames, newResolvedRef.commandLine.fileNames); } else { - result[i] = resolutions[j]; - j++; + // A previously-unresolved reference may be resolved now + return newResolvedRef !== undefined; } + }, + (oldProjectReferences, parent) => { + // If array of references is changed, we cant resue old program + const newReferences = parent ? getResolvedProjectReferenceByPath(parent.sourceFile.path)!.commandLine.projectReferences : projectReferences; + return !arrayIsEqualTo(oldProjectReferences, newReferences, projectReferenceIsEqualTo); } - Debug.assert(j === resolutions.length); - - return result; + ); + } - // If we change our policy of rechecking failed lookups on each program create, - // we should adjust the value returned here. - function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string, index: number): boolean { - if (index >= length(oldSourceFile?.imports) + length(oldSourceFile?.moduleAugmentations)) return false; // mode index out of bounds, don't reuse resolution - const resolutionToFile = getResolvedModule(oldSourceFile, moduleName, oldSourceFile && getModeForResolutionAtIndex(oldSourceFile, index)); - const resolvedFile = resolutionToFile && oldProgram!.getSourceFile(resolutionToFile.resolvedFileName); - if (resolutionToFile && resolvedFile) { - // In the old program, we resolved to an ambient module that was in the same - // place as we expected to find an actual module file. - // We actually need to return 'false' here even though this seems like a 'true' case - // because the normal module resolution algorithm will find this anyway. - return false; - } + function tryReuseStructureFromOldProgram(): StructureIsReused { + if (!oldProgram) { + return StructureIsReused.Not; + } - // at least one of declarations should come from non-modified source file - const unmodifiedFile = ambientModuleNameToUnmodifiedFileName.get(moduleName); + // check properties that can affect structure of the program or module resolution strategy + // if any of these properties has changed - structure cannot be reused + const oldOptions = oldProgram.getCompilerOptions(); + if (changesAffectModuleResolution(oldOptions, options)) { + return StructureIsReused.Not; + } - if (!unmodifiedFile) { - return false; - } + // there is an old program, check if we can reuse its structure + const oldRootNames = oldProgram.getRootFileNames(); + if (!arrayIsEqualTo(oldRootNames, rootNames)) { + return StructureIsReused.Not; + } - if (isTraceEnabled(options, host)) { - trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName, unmodifiedFile); - } - return true; - } + // Check if any referenced project tsconfig files are different + if (!canReuseProjectReferences()) { + return StructureIsReused.Not; + } + if (projectReferences) { + resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); } - function canReuseProjectReferences(): boolean { - return !forEachProjectReference( - oldProgram!.getProjectReferences(), - oldProgram!.getResolvedProjectReferences(), - (oldResolvedRef, parent, index) => { - const newRef = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; - const newResolvedRef = parseProjectReferenceConfigFile(newRef); - if (oldResolvedRef) { - // Resolved project reference has gone missing or changed - return !newResolvedRef || - newResolvedRef.sourceFile !== oldResolvedRef.sourceFile || - !arrayIsEqualTo(oldResolvedRef.commandLine.fileNames, newResolvedRef.commandLine.fileNames); - } - else { - // A previously-unresolved reference may be resolved now - return newResolvedRef !== undefined; - } - }, - (oldProjectReferences, parent) => { - // If array of references is changed, we cant resue old program - const newReferences = parent ? getResolvedProjectReferenceByPath(parent.sourceFile.path)!.commandLine.projectReferences : projectReferences; - return !arrayIsEqualTo(oldProjectReferences, newReferences, projectReferenceIsEqualTo); - } - ); + // check if program source files has changed in the way that can affect structure of the program + const newSourceFiles: SourceFile[] = []; + const modifiedSourceFiles: { oldFile: SourceFile, newFile: SourceFile }[] = []; + structureIsReused = StructureIsReused.Completely; + + // If the missing file paths are now present, it can change the progam structure, + // and hence cant reuse the structure. + // This is same as how we dont reuse the structure if one of the file from old program is now missing + if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) { + return StructureIsReused.Not; } - function tryReuseStructureFromOldProgram(): StructureIsReused { - if (!oldProgram) { - return StructureIsReused.Not; - } + const oldSourceFiles = oldProgram.getSourceFiles(); + const enum SeenPackageName { Exists, Modified } + const seenPackageNames = new Map(); - // check properties that can affect structure of the program or module resolution strategy - // if any of these properties has changed - structure cannot be reused - const oldOptions = oldProgram.getCompilerOptions(); - if (changesAffectModuleResolution(oldOptions, options)) { - return StructureIsReused.Not; - } + for (const oldSourceFile of oldSourceFiles) { + let newSourceFile = host.getSourceFileByPath + ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, getEmitScriptTarget(options), /*onError*/ undefined, shouldCreateNewSourceFile) + : host.getSourceFile(oldSourceFile.fileName, getEmitScriptTarget(options), /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217 - // there is an old program, check if we can reuse its structure - const oldRootNames = oldProgram.getRootFileNames(); - if (!arrayIsEqualTo(oldRootNames, rootNames)) { + if (!newSourceFile) { return StructureIsReused.Not; } - // Check if any referenced project tsconfig files are different - if (!canReuseProjectReferences()) { - return StructureIsReused.Not; + Debug.assert(!newSourceFile.redirectInfo, "Host should not return a redirect source file from `getSourceFile`"); + + let fileChanged: boolean; + if (oldSourceFile.redirectInfo) { + // We got `newSourceFile` by path, so it is actually for the unredirected file. + // This lets us know if the unredirected file has changed. If it has we should break the redirect. + if (newSourceFile !== oldSourceFile.redirectInfo.unredirected) { + // Underlying file has changed. Might not redirect anymore. Must rebuild program. + return StructureIsReused.Not; + } + fileChanged = false; + newSourceFile = oldSourceFile; // Use the redirect. } - if (projectReferences) { - resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); + else if (oldProgram.redirectTargetsMap.has(oldSourceFile.path)) { + // If a redirected-to source file changes, the redirect may be broken. + if (newSourceFile !== oldSourceFile) { + return StructureIsReused.Not; + } + fileChanged = false; } - - // check if program source files has changed in the way that can affect structure of the program - const newSourceFiles: SourceFile[] = []; - const modifiedSourceFiles: { oldFile: SourceFile, newFile: SourceFile }[] = []; - structureIsReused = StructureIsReused.Completely; - - // If the missing file paths are now present, it can change the progam structure, - // and hence cant reuse the structure. - // This is same as how we dont reuse the structure if one of the file from old program is now missing - if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) { - return StructureIsReused.Not; + else { + fileChanged = newSourceFile !== oldSourceFile; + } + + // Since the project references havent changed, its right to set originalFileName and resolvedPath here + newSourceFile.path = oldSourceFile.path; + newSourceFile.originalFileName = oldSourceFile.originalFileName; + newSourceFile.resolvedPath = oldSourceFile.resolvedPath; + newSourceFile.fileName = oldSourceFile.fileName; + newSourceFile.impliedNodeFormat = oldSourceFile.impliedNodeFormat; + + const packageName = oldProgram.sourceFileToPackageName.get(oldSourceFile.path); + if (packageName !== undefined) { + // If there are 2 different source files for the same package name and at least one of them changes, + // they might become redirects. So we must rebuild the program. + const prevKind = seenPackageNames.get(packageName); + const newKind = fileChanged ? SeenPackageName.Modified : SeenPackageName.Exists; + if ((prevKind !== undefined && newKind === SeenPackageName.Modified) || prevKind === SeenPackageName.Modified) { + return StructureIsReused.Not; + } + seenPackageNames.set(packageName, newKind); } - const oldSourceFiles = oldProgram.getSourceFiles(); - const enum SeenPackageName { Exists, Modified } - const seenPackageNames = new Map(); + if (fileChanged) { + // The `newSourceFile` object was created for the new program. - for (const oldSourceFile of oldSourceFiles) { - let newSourceFile = host.getSourceFileByPath - ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, getEmitScriptTarget(options), /*onError*/ undefined, shouldCreateNewSourceFile) - : host.getSourceFile(oldSourceFile.fileName, getEmitScriptTarget(options), /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217 + if (!arrayIsEqualTo(oldSourceFile.libReferenceDirectives, newSourceFile.libReferenceDirectives, fileReferenceIsEqualTo)) { + // 'lib' references has changed. Matches behavior in changesAffectModuleResolution + structureIsReused = StructureIsReused.SafeModules; + } - if (!newSourceFile) { - return StructureIsReused.Not; + if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) { + // value of no-default-lib has changed + // this will affect if default library is injected into the list of files + structureIsReused = StructureIsReused.SafeModules; } - Debug.assert(!newSourceFile.redirectInfo, "Host should not return a redirect source file from `getSourceFile`"); + // check tripleslash references + if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) { + // tripleslash references has changed + structureIsReused = StructureIsReused.SafeModules; + } - let fileChanged: boolean; - if (oldSourceFile.redirectInfo) { - // We got `newSourceFile` by path, so it is actually for the unredirected file. - // This lets us know if the unredirected file has changed. If it has we should break the redirect. - if (newSourceFile !== oldSourceFile.redirectInfo.unredirected) { - // Underlying file has changed. Might not redirect anymore. Must rebuild program. - return StructureIsReused.Not; - } - fileChanged = false; - newSourceFile = oldSourceFile; // Use the redirect. + // check imports and module augmentations + collectExternalModuleReferences(newSourceFile); + if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) { + // imports has changed + structureIsReused = StructureIsReused.SafeModules; } - else if (oldProgram.redirectTargetsMap.has(oldSourceFile.path)) { - // If a redirected-to source file changes, the redirect may be broken. - if (newSourceFile !== oldSourceFile) { - return StructureIsReused.Not; - } - fileChanged = false; + if (!arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations, moduleNameIsEqualTo)) { + // moduleAugmentations has changed + structureIsReused = StructureIsReused.SafeModules; } - else { - fileChanged = newSourceFile !== oldSourceFile; + if ((oldSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags) !== (newSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags)) { + // dynamicImport has changed + structureIsReused = StructureIsReused.SafeModules; } - // Since the project references havent changed, its right to set originalFileName and resolvedPath here - newSourceFile.path = oldSourceFile.path; - newSourceFile.originalFileName = oldSourceFile.originalFileName; - newSourceFile.resolvedPath = oldSourceFile.resolvedPath; - newSourceFile.fileName = oldSourceFile.fileName; - newSourceFile.impliedNodeFormat = oldSourceFile.impliedNodeFormat; - - const packageName = oldProgram.sourceFileToPackageName.get(oldSourceFile.path); - if (packageName !== undefined) { - // If there are 2 different source files for the same package name and at least one of them changes, - // they might become redirects. So we must rebuild the program. - const prevKind = seenPackageNames.get(packageName); - const newKind = fileChanged ? SeenPackageName.Modified : SeenPackageName.Exists; - if ((prevKind !== undefined && newKind === SeenPackageName.Modified) || prevKind === SeenPackageName.Modified) { - return StructureIsReused.Not; - } - seenPackageNames.set(packageName, newKind); + if (!arrayIsEqualTo(oldSourceFile.typeReferenceDirectives, newSourceFile.typeReferenceDirectives, fileReferenceIsEqualTo)) { + // 'types' references has changed + structureIsReused = StructureIsReused.SafeModules; } - if (fileChanged) { - // The `newSourceFile` object was created for the new program. - - if (!arrayIsEqualTo(oldSourceFile.libReferenceDirectives, newSourceFile.libReferenceDirectives, fileReferenceIsEqualTo)) { - // 'lib' references has changed. Matches behavior in changesAffectModuleResolution - structureIsReused = StructureIsReused.SafeModules; - } - - if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) { - // value of no-default-lib has changed - // this will affect if default library is injected into the list of files - structureIsReused = StructureIsReused.SafeModules; - } + // tentatively approve the file + modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); + } + else if (hasInvalidatedResolution(oldSourceFile.path)) { + // 'module/types' references could have changed + structureIsReused = StructureIsReused.SafeModules; - // check tripleslash references - if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) { - // tripleslash references has changed - structureIsReused = StructureIsReused.SafeModules; - } + // add file to the modified list so that we will resolve it later + modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); + } - // check imports and module augmentations - collectExternalModuleReferences(newSourceFile); - if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) { - // imports has changed - structureIsReused = StructureIsReused.SafeModules; - } - if (!arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations, moduleNameIsEqualTo)) { - // moduleAugmentations has changed - structureIsReused = StructureIsReused.SafeModules; - } - if ((oldSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags) !== (newSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags)) { - // dynamicImport has changed - structureIsReused = StructureIsReused.SafeModules; - } + // if file has passed all checks it should be safe to reuse it + newSourceFiles.push(newSourceFile); + } - if (!arrayIsEqualTo(oldSourceFile.typeReferenceDirectives, newSourceFile.typeReferenceDirectives, fileReferenceIsEqualTo)) { - // 'types' references has changed - structureIsReused = StructureIsReused.SafeModules; - } + if (structureIsReused !== StructureIsReused.Completely) { + return structureIsReused; + } - // tentatively approve the file - modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); + const modifiedFiles = modifiedSourceFiles.map(f => f.oldFile); + for (const oldFile of oldSourceFiles) { + if (!contains(modifiedFiles, oldFile)) { + for (const moduleName of oldFile.ambientModuleNames) { + ambientModuleNameToUnmodifiedFileName.set(moduleName, oldFile.fileName); } - else if (hasInvalidatedResolution(oldSourceFile.path)) { - // 'module/types' references could have changed - structureIsReused = StructureIsReused.SafeModules; + } + } + // try to verify results of module resolution + for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) { + const moduleNames = getModuleNames(newSourceFile); + const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFile); + // ensure that module resolution results are still correct + const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, oldSourceFile, moduleResolutionIsEqualTo); + if (resolutionsChanged) { + structureIsReused = StructureIsReused.SafeModules; + newSourceFile.resolvedModules = zipToModeAwareCache(newSourceFile, moduleNames, resolutions); + } + else { + newSourceFile.resolvedModules = oldSourceFile.resolvedModules; + } + // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. + const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, ref => toFileNameLowerCase(ref.fileName)); + const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFile); + // ensure that types resolutions are still correct + const typeReferenceResolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, oldSourceFile, typeDirectiveIsEqualTo); + if (typeReferenceResolutionsChanged) { + structureIsReused = StructureIsReused.SafeModules; + newSourceFile.resolvedTypeReferenceDirectiveNames = zipToModeAwareCache(newSourceFile, typesReferenceDirectives, typeReferenceResolutions); + } + else { + newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames; + } + } - // add file to the modified list so that we will resolve it later - modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); - } + if (structureIsReused !== StructureIsReused.Completely) { + return structureIsReused; + } - // if file has passed all checks it should be safe to reuse it - newSourceFiles.push(newSourceFile); - } + if (changesAffectingProgramStructure(oldOptions, options) || host.hasChangedAutomaticTypeDirectiveNames?.()) { + return StructureIsReused.SafeModules; + } - if (structureIsReused !== StructureIsReused.Completely) { - return structureIsReused; - } + missingFilePaths = oldProgram.getMissingFilePaths(); - const modifiedFiles = modifiedSourceFiles.map(f => f.oldFile); - for (const oldFile of oldSourceFiles) { - if (!contains(modifiedFiles, oldFile)) { - for (const moduleName of oldFile.ambientModuleNames) { - ambientModuleNameToUnmodifiedFileName.set(moduleName, oldFile.fileName); - } - } + // update fileName -> file mapping + Debug.assert(newSourceFiles.length === oldProgram.getSourceFiles().length); + for (const newSourceFile of newSourceFiles) { + filesByName.set(newSourceFile.path, newSourceFile); + } + const oldFilesByNameMap = oldProgram.getFilesByNameMap(); + oldFilesByNameMap.forEach((oldFile, path) => { + if (!oldFile) { + filesByName.set(path, oldFile); + return; } - // try to verify results of module resolution - for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) { - const moduleNames = getModuleNames(newSourceFile); - const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFile); - // ensure that module resolution results are still correct - const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, oldSourceFile, moduleResolutionIsEqualTo); - if (resolutionsChanged) { - structureIsReused = StructureIsReused.SafeModules; - newSourceFile.resolvedModules = zipToModeAwareCache(newSourceFile, moduleNames, resolutions); - } - else { - newSourceFile.resolvedModules = oldSourceFile.resolvedModules; - } - // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. - const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, ref => toFileNameLowerCase(ref.fileName)); - const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFile); - // ensure that types resolutions are still correct - const typeReferenceResolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, oldSourceFile, typeDirectiveIsEqualTo); - if (typeReferenceResolutionsChanged) { - structureIsReused = StructureIsReused.SafeModules; - newSourceFile.resolvedTypeReferenceDirectiveNames = zipToModeAwareCache(newSourceFile, typesReferenceDirectives, typeReferenceResolutions); - } - else { - newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames; + if (oldFile.path === path) { + // Set the file as found during node modules search if it was found that way in old progra, + if (oldProgram!.isSourceFileFromExternalLibrary(oldFile)) { + sourceFilesFoundSearchingNodeModules.set(oldFile.path, true); } + return; } + filesByName.set(path, filesByName.get(oldFile.path)); + }); - if (structureIsReused !== StructureIsReused.Completely) { - return structureIsReused; - } + files = newSourceFiles; + fileReasons = oldProgram.getFileIncludeReasons(); + fileProcessingDiagnostics = oldProgram.getFileProcessingDiagnostics(); + resolvedTypeReferenceDirectives = oldProgram.getResolvedTypeReferenceDirectives(); - if (changesAffectingProgramStructure(oldOptions, options) || host.hasChangedAutomaticTypeDirectiveNames?.()) { - return StructureIsReused.SafeModules; - } + sourceFileToPackageName = oldProgram.sourceFileToPackageName; + redirectTargetsMap = oldProgram.redirectTargetsMap; + usesUriStyleNodeCoreModules = oldProgram.usesUriStyleNodeCoreModules; - missingFilePaths = oldProgram.getMissingFilePaths(); + return StructureIsReused.Completely; + } - // update fileName -> file mapping - Debug.assert(newSourceFiles.length === oldProgram.getSourceFiles().length); - for (const newSourceFile of newSourceFiles) { - filesByName.set(newSourceFile.path, newSourceFile); - } - const oldFilesByNameMap = oldProgram.getFilesByNameMap(); - oldFilesByNameMap.forEach((oldFile, path) => { - if (!oldFile) { - filesByName.set(path, oldFile); - return; - } - if (oldFile.path === path) { - // Set the file as found during node modules search if it was found that way in old progra, - if (oldProgram!.isSourceFileFromExternalLibrary(oldFile)) { - sourceFilesFoundSearchingNodeModules.set(oldFile.path, true); - } - return; - } - filesByName.set(path, filesByName.get(oldFile.path)); - }); + function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost { + return { + getPrependNodes, + getCanonicalFileName, + getCommonSourceDirectory: program.getCommonSourceDirectory, + getCompilerOptions: program.getCompilerOptions, + getCurrentDirectory: () => currentDirectory, + getNewLine: () => host.getNewLine(), + getSourceFile: program.getSourceFile, + getSourceFileByPath: program.getSourceFileByPath, + getSourceFiles: program.getSourceFiles, + getLibFileFromReference: program.getLibFileFromReference, + isSourceFileFromExternalLibrary, + getResolvedProjectReferenceToRedirect, + getProjectReferenceRedirect, + isSourceOfProjectReferenceRedirect, + getSymlinkCache, + writeFile: writeFileCallback || ( + (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), + isEmitBlocked, + readFile: f => host.readFile(f), + fileExists: f => { + // Use local caches + const path = toPath(f); + if (getSourceFileByPath(path)) return true; + if (contains(missingFilePaths, path)) return false; + // Before falling back to the host + return host.fileExists(f); + }, + useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), + getProgramBuildInfo: () => program.getProgramBuildInfo && program.getProgramBuildInfo(), + getSourceFileFromReference: (file, ref) => program.getSourceFileFromReference(file, ref), + redirectTargetsMap, + getFileIncludeReasons: program.getFileIncludeReasons, + }; + } - files = newSourceFiles; - fileReasons = oldProgram.getFileIncludeReasons(); - fileProcessingDiagnostics = oldProgram.getFileProcessingDiagnostics(); - resolvedTypeReferenceDirectives = oldProgram.getResolvedTypeReferenceDirectives(); + function emitBuildInfo(writeFileCallback?: WriteFileCallback): EmitResult { + Debug.assert(!outFile(options)); + tracing?.push(tracing.Phase.Emit, "emitBuildInfo", {}, /*separateBeginAndEnd*/ true); + performance.mark("beforeEmit"); + const emitResult = emitFiles( + notImplementedResolver, + getEmitHost(writeFileCallback), + /*targetSourceFile*/ undefined, + /*transformers*/ noTransformers, + /*emitOnlyDtsFiles*/ false, + /*onlyBuildInfo*/ true + ); + + performance.mark("afterEmit"); + performance.measure("Emit", "beforeEmit", "afterEmit"); + tracing?.pop(); + return emitResult; + } - sourceFileToPackageName = oldProgram.sourceFileToPackageName; - redirectTargetsMap = oldProgram.redirectTargetsMap; - usesUriStyleNodeCoreModules = oldProgram.usesUriStyleNodeCoreModules; + function getResolvedProjectReferences() { + return resolvedProjectReferences; + } - return StructureIsReused.Completely; - } + function getProjectReferences() { + return projectReferences; + } - function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost { - return { - getPrependNodes, - getCanonicalFileName, - getCommonSourceDirectory: program.getCommonSourceDirectory, - getCompilerOptions: program.getCompilerOptions, - getCurrentDirectory: () => currentDirectory, - getNewLine: () => host.getNewLine(), - getSourceFile: program.getSourceFile, - getSourceFileByPath: program.getSourceFileByPath, - getSourceFiles: program.getSourceFiles, - getLibFileFromReference: program.getLibFileFromReference, - isSourceFileFromExternalLibrary, - getResolvedProjectReferenceToRedirect, - getProjectReferenceRedirect, - isSourceOfProjectReferenceRedirect, - getSymlinkCache, - writeFile: writeFileCallback || ( - (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), - isEmitBlocked, - readFile: f => host.readFile(f), - fileExists: f => { - // Use local caches - const path = toPath(f); - if (getSourceFileByPath(path)) return true; - if (contains(missingFilePaths, path)) return false; - // Before falling back to the host - return host.fileExists(f); - }, - useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), - getProgramBuildInfo: () => program.getProgramBuildInfo && program.getProgramBuildInfo(), - getSourceFileFromReference: (file, ref) => program.getSourceFileFromReference(file, ref), - redirectTargetsMap, - getFileIncludeReasons: program.getFileIncludeReasons, - }; - } + function getPrependNodes() { + return createPrependNodes( + projectReferences, + (_ref, index) => resolvedProjectReferences![index]?.commandLine, + fileName => { + const path = toPath(fileName); + const sourceFile = getSourceFileByPath(path); + return sourceFile ? sourceFile.text : filesByName.has(path) ? undefined : host.readFile(path); + } + ); + } - function emitBuildInfo(writeFileCallback?: WriteFileCallback): EmitResult { - Debug.assert(!outFile(options)); - tracing?.push(tracing.Phase.Emit, "emitBuildInfo", {}, /*separateBeginAndEnd*/ true); - performance.mark("beforeEmit"); - const emitResult = emitFiles( - notImplementedResolver, - getEmitHost(writeFileCallback), - /*targetSourceFile*/ undefined, - /*transformers*/ noTransformers, - /*emitOnlyDtsFiles*/ false, - /*onlyBuildInfo*/ true - ); + function isSourceFileFromExternalLibrary(file: SourceFile): boolean { + return !!sourceFilesFoundSearchingNodeModules.get(file.path); + } - performance.mark("afterEmit"); - performance.measure("Emit", "beforeEmit", "afterEmit"); - tracing?.pop(); - return emitResult; + function isSourceFileDefaultLibrary(file: SourceFile): boolean { + if (file.hasNoDefaultLib) { + return true; } - function getResolvedProjectReferences() { - return resolvedProjectReferences; + if (!options.noLib) { + return false; } - function getProjectReferences() { - return projectReferences; + // If '--lib' is not specified, include default library file according to '--target' + // otherwise, using options specified in '--lib' instead of '--target' default library file + const equalityComparer = host.useCaseSensitiveFileNames() ? equateStringsCaseSensitive : equateStringsCaseInsensitive; + if (!options.lib) { + return equalityComparer(file.fileName, getDefaultLibraryFileName()); } - - function getPrependNodes() { - return createPrependNodes( - projectReferences, - (_ref, index) => resolvedProjectReferences![index]?.commandLine, - fileName => { - const path = toPath(fileName); - const sourceFile = getSourceFileByPath(path); - return sourceFile ? sourceFile.text : filesByName.has(path) ? undefined : host.readFile(path); - } - ); + else { + return some(options.lib, libFileName => equalityComparer(file.fileName, pathForLibFile(libFileName))); } + } - function isSourceFileFromExternalLibrary(file: SourceFile): boolean { - return !!sourceFilesFoundSearchingNodeModules.get(file.path); - } + function getDiagnosticsProducingTypeChecker() { + return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ true)); + } - function isSourceFileDefaultLibrary(file: SourceFile): boolean { - if (file.hasNoDefaultLib) { - return true; - } + function dropDiagnosticsProducingTypeChecker() { + diagnosticsProducingTypeChecker = undefined!; + } - if (!options.noLib) { - return false; - } + function getTypeChecker() { + return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false)); + } - // If '--lib' is not specified, include default library file according to '--target' - // otherwise, using options specified in '--lib' instead of '--target' default library file - const equalityComparer = host.useCaseSensitiveFileNames() ? equateStringsCaseSensitive : equateStringsCaseInsensitive; - if (!options.lib) { - return equalityComparer(file.fileName, getDefaultLibraryFileName()); - } - else { - return some(options.lib, libFileName => equalityComparer(file.fileName, pathForLibFile(libFileName))); - } - } + function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, transformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult { + tracing?.push(tracing.Phase.Emit, "emit", { path: sourceFile?.path }, /*separateBeginAndEnd*/ true); + const result = runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles, transformers, forceDtsEmit)); + tracing?.pop(); + return result; + } - function getDiagnosticsProducingTypeChecker() { - return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ true)); - } + function isEmitBlocked(emitFileName: string): boolean { + return hasEmitBlockingDiagnostics.has(toPath(emitFileName)); + } - function dropDiagnosticsProducingTypeChecker() { - diagnosticsProducingTypeChecker = undefined!; - } + function emitWorker(program: Program, sourceFile: SourceFile | undefined, writeFileCallback: WriteFileCallback | undefined, cancellationToken: CancellationToken | undefined, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult { + if (!forceDtsEmit) { + const result = handleNoEmitOptions(program, sourceFile, writeFileCallback, cancellationToken); + if (result) return result; + } + + // Create the emit resolver outside of the "emitTime" tracking code below. That way + // any cost associated with it (like type checking) are appropriate associated with + // the type-checking counter. + // + // If the -out option is specified, we should not pass the source file to getEmitResolver. + // This is because in the -out scenario all files need to be emitted, and therefore all + // files need to be type checked. And the way to specify that all files need to be type + // checked is to not pass the file to getEmitResolver. + const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver(outFile(options) ? undefined : sourceFile, cancellationToken); + + performance.mark("beforeEmit"); + + const emitResult = emitFiles( + emitResolver, + getEmitHost(writeFileCallback), + sourceFile, + getTransformers(options, customTransformers, emitOnlyDtsFiles), + emitOnlyDtsFiles, + /*onlyBuildInfo*/ false, + forceDtsEmit + ); + + performance.mark("afterEmit"); + performance.measure("Emit", "beforeEmit", "afterEmit"); + return emitResult; + } - function getTypeChecker() { - return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false)); - } + function getSourceFile(fileName: string): SourceFile | undefined { + return getSourceFileByPath(toPath(fileName)); + } - function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, transformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult { - tracing?.push(tracing.Phase.Emit, "emit", { path: sourceFile?.path }, /*separateBeginAndEnd*/ true); - const result = runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles, transformers, forceDtsEmit)); - tracing?.pop(); - return result; - } + function getSourceFileByPath(path: Path): SourceFile | undefined { + return filesByName.get(path) || undefined; + } - function isEmitBlocked(emitFileName: string): boolean { - return hasEmitBlockingDiagnostics.has(toPath(emitFileName)); + function getDiagnosticsHelper( + sourceFile: SourceFile | undefined, + getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken | undefined) => readonly T[], + cancellationToken: CancellationToken | undefined): readonly T[] { + if (sourceFile) { + return getDiagnostics(sourceFile, cancellationToken); } - - function emitWorker(program: Program, sourceFile: SourceFile | undefined, writeFileCallback: WriteFileCallback | undefined, cancellationToken: CancellationToken | undefined, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult { - if (!forceDtsEmit) { - const result = handleNoEmitOptions(program, sourceFile, writeFileCallback, cancellationToken); - if (result) return result; + return sortAndDeduplicateDiagnostics(flatMap(program.getSourceFiles(), sourceFile => { + if (cancellationToken) { + cancellationToken.throwIfCancellationRequested(); } + return getDiagnostics(sourceFile, cancellationToken); + })); + } - // Create the emit resolver outside of the "emitTime" tracking code below. That way - // any cost associated with it (like type checking) are appropriate associated with - // the type-checking counter. - // - // If the -out option is specified, we should not pass the source file to getEmitResolver. - // This is because in the -out scenario all files need to be emitted, and therefore all - // files need to be type checked. And the way to specify that all files need to be type - // checked is to not pass the file to getEmitResolver. - const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver(outFile(options) ? undefined : sourceFile, cancellationToken); - - performance.mark("beforeEmit"); - - const emitResult = emitFiles( - emitResolver, - getEmitHost(writeFileCallback), - sourceFile, - getTransformers(options, customTransformers, emitOnlyDtsFiles), - emitOnlyDtsFiles, - /*onlyBuildInfo*/ false, - forceDtsEmit - ); + function getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { + return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken); + } + + function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken); + } + + function getCachedSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] | undefined { + return sourceFile + ? cachedBindAndCheckDiagnosticsForFile.perFile?.get(sourceFile.path) + : cachedBindAndCheckDiagnosticsForFile.allDiagnostics; + } + + function getBindAndCheckDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken); + } - performance.mark("afterEmit"); - performance.measure("Emit", "beforeEmit", "afterEmit"); - return emitResult; + function getProgramDiagnostics(sourceFile: SourceFile): readonly Diagnostic[] { + if (skipTypeChecking(sourceFile, options, program)) { + return emptyArray; } - function getSourceFile(fileName: string): SourceFile | undefined { - return getSourceFileByPath(toPath(fileName)); + const programDiagnosticsInFile = programDiagnostics.getDiagnostics(sourceFile.fileName); + if (!sourceFile.commentDirectives?.length) { + return programDiagnosticsInFile; } - function getSourceFileByPath(path: Path): SourceFile | undefined { - return filesByName.get(path) || undefined; + return getDiagnosticsWithPrecedingDirectives(sourceFile, sourceFile.commentDirectives, programDiagnosticsInFile).diagnostics; + } + + function getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { + const options = program.getCompilerOptions(); + // collect diagnostics from the program only once if either no source file was specified or out/outFile is set (bundled emit) + if (!sourceFile || outFile(options)) { + return getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); } + else { + return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken); + } + } - function getDiagnosticsHelper( - sourceFile: SourceFile | undefined, - getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken | undefined) => readonly T[], - cancellationToken: CancellationToken | undefined): readonly T[] { - if (sourceFile) { - return getDiagnostics(sourceFile, cancellationToken); + function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): readonly DiagnosticWithLocation[] { + // For JavaScript files, we report semantic errors for using TypeScript-only + // constructs from within a JavaScript file as syntactic errors. + if (isSourceFileJS(sourceFile)) { + if (!sourceFile.additionalSyntacticDiagnostics) { + sourceFile.additionalSyntacticDiagnostics = getJSSyntacticDiagnosticsForFile(sourceFile); } - return sortAndDeduplicateDiagnostics(flatMap(program.getSourceFiles(), sourceFile => { - if (cancellationToken) { - cancellationToken.throwIfCancellationRequested(); - } - return getDiagnostics(sourceFile, cancellationToken); - })); + return concatenate(sourceFile.additionalSyntacticDiagnostics, sourceFile.parseDiagnostics); } + return sourceFile.parseDiagnostics; + } - function getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { - return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken); + function runWithCancellationToken(func: () => T): T { + try { + return func(); } + catch (e) { + if (e instanceof OperationCanceledException) { + // We were canceled while performing the operation. Because our type checker + // might be a bad state, we need to throw it away. + // + // Note: we are overly aggressive here. We do not actually *have* to throw away + // the "noDiagnosticsTypeChecker". However, for simplicity, i'd like to keep + // the lifetimes of these two TypeCheckers the same. Also, we generally only + // cancel when the user has made a change anyways. And, in that case, we (the + // program instance) will get thrown away anyways. So trying to keep one of + // these type checkers alive doesn't serve much purpose. + noDiagnosticsTypeChecker = undefined!; + diagnosticsProducingTypeChecker = undefined!; + } - function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken); + throw e; } + } - function getCachedSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] | undefined { - return sourceFile - ? cachedBindAndCheckDiagnosticsForFile.perFile?.get(sourceFile.path) - : cachedBindAndCheckDiagnosticsForFile.allDiagnostics; - } + function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { + return concatenate( + filterSemanticDiagnostics(getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken), options), + getProgramDiagnostics(sourceFile) + ); + } - function getBindAndCheckDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken); - } + function getBindAndCheckDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { + return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedBindAndCheckDiagnosticsForFile, getBindAndCheckDiagnosticsForFileNoCache); + } - function getProgramDiagnostics(sourceFile: SourceFile): readonly Diagnostic[] { + function getBindAndCheckDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { + return runWithCancellationToken(() => { if (skipTypeChecking(sourceFile, options, program)) { return emptyArray; } - const programDiagnosticsInFile = programDiagnostics.getDiagnostics(sourceFile.fileName); - if (!sourceFile.commentDirectives?.length) { - return programDiagnosticsInFile; - } - - return getDiagnosticsWithPrecedingDirectives(sourceFile, sourceFile.commentDirectives, programDiagnosticsInFile).diagnostics; - } + const typeChecker = getDiagnosticsProducingTypeChecker(); - function getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { - const options = program.getCompilerOptions(); - // collect diagnostics from the program only once if either no source file was specified or out/outFile is set (bundled emit) - if (!sourceFile || outFile(options)) { - return getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); - } - else { - return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken); - } - } + Debug.assert(!!sourceFile.bindDiagnostics); - function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): readonly DiagnosticWithLocation[] { - // For JavaScript files, we report semantic errors for using TypeScript-only - // constructs from within a JavaScript file as syntactic errors. - if (isSourceFileJS(sourceFile)) { - if (!sourceFile.additionalSyntacticDiagnostics) { - sourceFile.additionalSyntacticDiagnostics = getJSSyntacticDiagnosticsForFile(sourceFile); - } - return concatenate(sourceFile.additionalSyntacticDiagnostics, sourceFile.parseDiagnostics); - } - return sourceFile.parseDiagnostics; - } - - function runWithCancellationToken(func: () => T): T { - try { - return func(); - } - catch (e) { - if (e instanceof OperationCanceledException) { - // We were canceled while performing the operation. Because our type checker - // might be a bad state, we need to throw it away. - // - // Note: we are overly aggressive here. We do not actually *have* to throw away - // the "noDiagnosticsTypeChecker". However, for simplicity, i'd like to keep - // the lifetimes of these two TypeCheckers the same. Also, we generally only - // cancel when the user has made a change anyways. And, in that case, we (the - // program instance) will get thrown away anyways. So trying to keep one of - // these type checkers alive doesn't serve much purpose. - noDiagnosticsTypeChecker = undefined!; - diagnosticsProducingTypeChecker = undefined!; - } + const isJs = sourceFile.scriptKind === ScriptKind.JS || sourceFile.scriptKind === ScriptKind.JSX; + const isCheckJs = isJs && isCheckJsEnabledForFile(sourceFile, options); + const isPlainJs = isJs && !sourceFile.checkJsDirective && options.checkJs === undefined; + const isTsNoCheck = !!sourceFile.checkJsDirective && sourceFile.checkJsDirective.enabled === false; - throw e; + // By default, only type-check .ts, .tsx, Deferred, plain JS, checked JS and External + // - plain JS: .js files with no // ts-check and checkJs: undefined + // - check JS: .js files with either // ts-check or checkJs: true + // - external: files that are added by plugins + const includeBindAndCheckDiagnostics = !isTsNoCheck && (sourceFile.scriptKind === ScriptKind.TS || sourceFile.scriptKind === ScriptKind.TSX + || sourceFile.scriptKind === ScriptKind.External || isPlainJs || isCheckJs || sourceFile.scriptKind === ScriptKind.Deferred); + let bindDiagnostics: readonly Diagnostic[] = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray; + const checkDiagnostics = includeBindAndCheckDiagnostics && !isPlainJs ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray; + if (isPlainJs) { + bindDiagnostics = filter(bindDiagnostics, d => plainJSErrors.has(d.code)); } - } - - function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { - return concatenate( - filterSemanticDiagnostics(getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken), options), - getProgramDiagnostics(sourceFile) - ); - } + // skip ts-expect-error errors in plain JS files, and skip JSDoc errors except in checked JS + return getMergedBindAndCheckDiagnostics(sourceFile, includeBindAndCheckDiagnostics && !isPlainJs, bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined); + }); + } - function getBindAndCheckDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { - return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedBindAndCheckDiagnosticsForFile, getBindAndCheckDiagnosticsForFileNoCache); + function getMergedBindAndCheckDiagnostics(sourceFile: SourceFile, includeBindAndCheckDiagnostics: boolean, ...allDiagnostics: (readonly Diagnostic[] | undefined)[]) { + const flatDiagnostics = flatten(allDiagnostics); + if (!includeBindAndCheckDiagnostics || !sourceFile.commentDirectives?.length) { + return flatDiagnostics; } - function getBindAndCheckDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { - return runWithCancellationToken(() => { - if (skipTypeChecking(sourceFile, options, program)) { - return emptyArray; - } + const { diagnostics, directives } = getDiagnosticsWithPrecedingDirectives(sourceFile, sourceFile.commentDirectives, flatDiagnostics); - const typeChecker = getDiagnosticsProducingTypeChecker(); - - Debug.assert(!!sourceFile.bindDiagnostics); - - const isJs = sourceFile.scriptKind === ScriptKind.JS || sourceFile.scriptKind === ScriptKind.JSX; - const isCheckJs = isJs && isCheckJsEnabledForFile(sourceFile, options); - const isPlainJs = isJs && !sourceFile.checkJsDirective && options.checkJs === undefined; - const isTsNoCheck = !!sourceFile.checkJsDirective && sourceFile.checkJsDirective.enabled === false; - - // By default, only type-check .ts, .tsx, Deferred, plain JS, checked JS and External - // - plain JS: .js files with no // ts-check and checkJs: undefined - // - check JS: .js files with either // ts-check or checkJs: true - // - external: files that are added by plugins - const includeBindAndCheckDiagnostics = !isTsNoCheck && (sourceFile.scriptKind === ScriptKind.TS || sourceFile.scriptKind === ScriptKind.TSX - || sourceFile.scriptKind === ScriptKind.External || isPlainJs || isCheckJs || sourceFile.scriptKind === ScriptKind.Deferred); - let bindDiagnostics: readonly Diagnostic[] = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray; - const checkDiagnostics = includeBindAndCheckDiagnostics && !isPlainJs ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray; - if (isPlainJs) { - bindDiagnostics = filter(bindDiagnostics, d => plainJSErrors.has(d.code)); - } - // skip ts-expect-error errors in plain JS files, and skip JSDoc errors except in checked JS - return getMergedBindAndCheckDiagnostics(sourceFile, includeBindAndCheckDiagnostics && !isPlainJs, bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined); - }); + for (const errorExpectation of directives.getUnusedExpectations()) { + diagnostics.push(createDiagnosticForRange(sourceFile, errorExpectation.range, Diagnostics.Unused_ts_expect_error_directive)); } - function getMergedBindAndCheckDiagnostics(sourceFile: SourceFile, includeBindAndCheckDiagnostics: boolean, ...allDiagnostics: (readonly Diagnostic[] | undefined)[]) { - const flatDiagnostics = flatten(allDiagnostics); - if (!includeBindAndCheckDiagnostics || !sourceFile.commentDirectives?.length) { - return flatDiagnostics; - } - - const { diagnostics, directives } = getDiagnosticsWithPrecedingDirectives(sourceFile, sourceFile.commentDirectives, flatDiagnostics); - - for (const errorExpectation of directives.getUnusedExpectations()) { - diagnostics.push(createDiagnosticForRange(sourceFile, errorExpectation.range, Diagnostics.Unused_ts_expect_error_directive)); - } + return diagnostics; + } - return diagnostics; - } + /** + * Creates a map of comment directives along with the diagnostics immediately preceded by one of them. + * Comments that match to any of those diagnostics are marked as used. + */ + function getDiagnosticsWithPrecedingDirectives(sourceFile: SourceFile, commentDirectives: CommentDirective[], flatDiagnostics: Diagnostic[]) { + // Diagnostics are only reported if there is no comment directive preceding them + // This will modify the directives map by marking "used" ones with a corresponding diagnostic + const directives = createCommentDirectivesMap(sourceFile, commentDirectives); + const diagnostics = flatDiagnostics.filter(diagnostic => markPrecedingCommentDirectiveLine(diagnostic, directives) === -1); - /** - * Creates a map of comment directives along with the diagnostics immediately preceded by one of them. - * Comments that match to any of those diagnostics are marked as used. - */ - function getDiagnosticsWithPrecedingDirectives(sourceFile: SourceFile, commentDirectives: CommentDirective[], flatDiagnostics: Diagnostic[]) { - // Diagnostics are only reported if there is no comment directive preceding them - // This will modify the directives map by marking "used" ones with a corresponding diagnostic - const directives = createCommentDirectivesMap(sourceFile, commentDirectives); - const diagnostics = flatDiagnostics.filter(diagnostic => markPrecedingCommentDirectiveLine(diagnostic, directives) === -1); + return { diagnostics, directives }; + } - return { diagnostics, directives }; - } + function getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { + return runWithCancellationToken(() => { + return getDiagnosticsProducingTypeChecker().getSuggestionDiagnostics(sourceFile, cancellationToken); + }); + } - function getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { - return runWithCancellationToken(() => { - return getDiagnosticsProducingTypeChecker().getSuggestionDiagnostics(sourceFile, cancellationToken); - }); + /** + * @returns The line index marked as preceding the diagnostic, or -1 if none was. + */ + function markPrecedingCommentDirectiveLine(diagnostic: Diagnostic, directives: CommentDirectivesMap) { + const { file, start } = diagnostic; + if (!file) { + return -1; } - /** - * @returns The line index marked as preceding the diagnostic, or -1 if none was. - */ - function markPrecedingCommentDirectiveLine(diagnostic: Diagnostic, directives: CommentDirectivesMap) { - const { file, start } = diagnostic; - if (!file) { - return -1; + // Start out with the line just before the text + const lineStarts = getLineStarts(file); + let line = computeLineAndCharacterOfPosition(lineStarts, start!).line - 1; // TODO: GH#18217 + while (line >= 0) { + // As soon as that line is known to have a comment directive, use that + if (directives.markUsed(line)) { + return line; } - // Start out with the line just before the text - const lineStarts = getLineStarts(file); - let line = computeLineAndCharacterOfPosition(lineStarts, start!).line - 1; // TODO: GH#18217 - while (line >= 0) { - // As soon as that line is known to have a comment directive, use that - if (directives.markUsed(line)) { - return line; - } - - // Stop searching if the line is not empty and not a comment - const lineText = file.text.slice(lineStarts[line], lineStarts[line + 1]).trim(); - if (lineText !== "" && !/^(\s*)\/\/(.*)$/.test(lineText)) { - return -1; - } - - line--; + // Stop searching if the line is not empty and not a comment + const lineText = file.text.slice(lineStarts[line], lineStarts[line + 1]).trim(); + if (lineText !== "" && !/^(\s*)\/\/(.*)$/.test(lineText)) { + return -1; } - return -1; + line--; } - function getJSSyntacticDiagnosticsForFile(sourceFile: SourceFile): DiagnosticWithLocation[] { - return runWithCancellationToken(() => { - const diagnostics: DiagnosticWithLocation[] = []; - walk(sourceFile, sourceFile); - forEachChildRecursively(sourceFile, walk, walkArray); + return -1; + } + + function getJSSyntacticDiagnosticsForFile(sourceFile: SourceFile): DiagnosticWithLocation[] { + return runWithCancellationToken(() => { + const diagnostics: DiagnosticWithLocation[] = []; + walk(sourceFile, sourceFile); + forEachChildRecursively(sourceFile, walk, walkArray); - return diagnostics; + return diagnostics; - function walk(node: Node, parent: Node) { - // Return directly from the case if the given node doesnt want to visit each child - // Otherwise break to visit each child + function walk(node: Node, parent: Node) { + // Return directly from the case if the given node doesnt want to visit each child + // Otherwise break to visit each child - switch (parent.kind) { - case SyntaxKind.Parameter: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - if ((parent as ParameterDeclaration | PropertyDeclaration | MethodDeclaration).questionToken === node) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")); - return "skip"; - } - // falls through - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ArrowFunction: - case SyntaxKind.VariableDeclaration: - // type annotation - if ((parent as FunctionLikeDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration).type === node) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_annotations_can_only_be_used_in_TypeScript_files)); - return "skip"; - } - } + switch (parent.kind) { + case SyntaxKind.Parameter: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + if ((parent as ParameterDeclaration | PropertyDeclaration | MethodDeclaration).questionToken === node) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")); + return "skip"; + } + // falls through + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + case SyntaxKind.VariableDeclaration: + // type annotation + if ((parent as FunctionLikeDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration).type === node) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_annotations_can_only_be_used_in_TypeScript_files)); + return "skip"; + } + } - switch (node.kind) { - case SyntaxKind.ImportClause: - if ((node as ImportClause).isTypeOnly) { - diagnostics.push(createDiagnosticForNode(parent, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "import type")); - return "skip"; - } - break; - case SyntaxKind.ExportDeclaration: - if ((node as ExportDeclaration).isTypeOnly) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "export type")); - return "skip"; - } - break; - case SyntaxKind.ImportEqualsDeclaration: - diagnostics.push(createDiagnosticForNode(node, Diagnostics.import_can_only_be_used_in_TypeScript_files)); + switch (node.kind) { + case SyntaxKind.ImportClause: + if ((node as ImportClause).isTypeOnly) { + diagnostics.push(createDiagnosticForNode(parent, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "import type")); return "skip"; - case SyntaxKind.ExportAssignment: - if ((node as ExportAssignment).isExportEquals) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics.export_can_only_be_used_in_TypeScript_files)); - return "skip"; - } - break; - case SyntaxKind.HeritageClause: - const heritageClause = node as HeritageClause; - if (heritageClause.token === SyntaxKind.ImplementsKeyword) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics.implements_clauses_can_only_be_used_in_TypeScript_files)); - return "skip"; - } - break; - case SyntaxKind.InterfaceDeclaration: - const interfaceKeyword = tokenToString(SyntaxKind.InterfaceKeyword); - Debug.assertIsDefined(interfaceKeyword); - diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, interfaceKeyword)); + } + break; + case SyntaxKind.ExportDeclaration: + if ((node as ExportDeclaration).isTypeOnly) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "export type")); + return "skip"; + } + break; + case SyntaxKind.ImportEqualsDeclaration: + diagnostics.push(createDiagnosticForNode(node, Diagnostics.import_can_only_be_used_in_TypeScript_files)); + return "skip"; + case SyntaxKind.ExportAssignment: + if ((node as ExportAssignment).isExportEquals) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics.export_can_only_be_used_in_TypeScript_files)); return "skip"; - case SyntaxKind.ModuleDeclaration: - const moduleKeyword = node.flags & NodeFlags.Namespace ? tokenToString(SyntaxKind.NamespaceKeyword) : tokenToString(SyntaxKind.ModuleKeyword); - Debug.assertIsDefined(moduleKeyword); - diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, moduleKeyword)); + } + break; + case SyntaxKind.HeritageClause: + const heritageClause = node as HeritageClause; + if (heritageClause.token === SyntaxKind.ImplementsKeyword) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics.implements_clauses_can_only_be_used_in_TypeScript_files)); + return "skip"; + } + break; + case SyntaxKind.InterfaceDeclaration: + const interfaceKeyword = tokenToString(SyntaxKind.InterfaceKeyword); + Debug.assertIsDefined(interfaceKeyword); + diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, interfaceKeyword)); + return "skip"; + case SyntaxKind.ModuleDeclaration: + const moduleKeyword = node.flags & NodeFlags.Namespace ? tokenToString(SyntaxKind.NamespaceKeyword) : tokenToString(SyntaxKind.ModuleKeyword); + Debug.assertIsDefined(moduleKeyword); + diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, moduleKeyword)); + return "skip"; + case SyntaxKind.TypeAliasDeclaration: + diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_aliases_can_only_be_used_in_TypeScript_files)); + return "skip"; + case SyntaxKind.EnumDeclaration: + const enumKeyword = Debug.checkDefined(tokenToString(SyntaxKind.EnumKeyword)); + diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, enumKeyword)); + return "skip"; + case SyntaxKind.NonNullExpression: + diagnostics.push(createDiagnosticForNode(node, Diagnostics.Non_null_assertions_can_only_be_used_in_TypeScript_files)); + return "skip"; + case SyntaxKind.AsExpression: + diagnostics.push(createDiagnosticForNode((node as AsExpression).type, Diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files)); + return "skip"; + case SyntaxKind.TypeAssertionExpression: + Debug.fail(); // Won't parse these in a JS file anyway, as they are interpreted as JSX. + } + } + + function walkArray(nodes: NodeArray, parent: Node) { + if (parent.decorators === nodes && !options.experimentalDecorators) { + diagnostics.push(createDiagnosticForNode(parent, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning)); + } + + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + // Check type parameters + if (nodes === (parent as DeclarationWithTypeParameterChildren).typeParameters) { + diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_parameter_declarations_can_only_be_used_in_TypeScript_files)); return "skip"; - case SyntaxKind.TypeAliasDeclaration: - diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_aliases_can_only_be_used_in_TypeScript_files)); + } + // falls through + + case SyntaxKind.VariableStatement: + // Check modifiers + if (nodes === parent.modifiers) { + checkModifiers(parent.modifiers, parent.kind === SyntaxKind.VariableStatement); return "skip"; - case SyntaxKind.EnumDeclaration: - const enumKeyword = Debug.checkDefined(tokenToString(SyntaxKind.EnumKeyword)); - diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, enumKeyword)); + } + break; + case SyntaxKind.PropertyDeclaration: + // Check modifiers of property declaration + if (nodes === (parent as PropertyDeclaration).modifiers) { + for (const modifier of nodes as NodeArray) { + if (modifier.kind !== SyntaxKind.StaticKeyword) { + diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind))); + } + } return "skip"; - case SyntaxKind.NonNullExpression: - diagnostics.push(createDiagnosticForNode(node, Diagnostics.Non_null_assertions_can_only_be_used_in_TypeScript_files)); + } + break; + case SyntaxKind.Parameter: + // Check modifiers of parameter declaration + if (nodes === (parent as ParameterDeclaration).modifiers) { + diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Parameter_modifiers_can_only_be_used_in_TypeScript_files)); return "skip"; - case SyntaxKind.AsExpression: - diagnostics.push(createDiagnosticForNode((node as AsExpression).type, Diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files)); + } + break; + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.TaggedTemplateExpression: + // Check type arguments + if (nodes === (parent as NodeWithTypeArguments).typeArguments) { + diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_arguments_can_only_be_used_in_TypeScript_files)); return "skip"; - case SyntaxKind.TypeAssertionExpression: - Debug.fail(); // Won't parse these in a JS file anyway, as they are interpreted as JSX. - } + } + break; } + } - function walkArray(nodes: NodeArray, parent: Node) { - if (parent.decorators === nodes && !options.experimentalDecorators) { - diagnostics.push(createDiagnosticForNode(parent, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning)); - } - - switch (parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ArrowFunction: - // Check type parameters - if (nodes === (parent as DeclarationWithTypeParameterChildren).typeParameters) { - diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_parameter_declarations_can_only_be_used_in_TypeScript_files)); - return "skip"; + function checkModifiers(modifiers: NodeArray, isConstValid: boolean) { + for (const modifier of modifiers) { + switch (modifier.kind) { + case SyntaxKind.ConstKeyword: + if (isConstValid) { + continue; } + // to report error, // falls through - - case SyntaxKind.VariableStatement: - // Check modifiers - if (nodes === parent.modifiers) { - checkModifiers(parent.modifiers, parent.kind === SyntaxKind.VariableStatement); - return "skip"; - } - break; - case SyntaxKind.PropertyDeclaration: - // Check modifiers of property declaration - if (nodes === (parent as PropertyDeclaration).modifiers) { - for (const modifier of nodes as NodeArray) { - if (modifier.kind !== SyntaxKind.StaticKeyword) { - diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind))); - } - } - return "skip"; - } - break; - case SyntaxKind.Parameter: - // Check modifiers of parameter declaration - if (nodes === (parent as ParameterDeclaration).modifiers) { - diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Parameter_modifiers_can_only_be_used_in_TypeScript_files)); - return "skip"; - } + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.AbstractKeyword: + case SyntaxKind.OverrideKeyword: + diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind))); break; - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.ExpressionWithTypeArguments: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.TaggedTemplateExpression: - // Check type arguments - if (nodes === (parent as NodeWithTypeArguments).typeArguments) { - diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_arguments_can_only_be_used_in_TypeScript_files)); - return "skip"; - } - break; - } - } - function checkModifiers(modifiers: NodeArray, isConstValid: boolean) { - for (const modifier of modifiers) { - switch (modifier.kind) { - case SyntaxKind.ConstKeyword: - if (isConstValid) { - continue; - } - // to report error, - // falls through - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.ReadonlyKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.AbstractKeyword: - case SyntaxKind.OverrideKeyword: - diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind))); - break; - - // These are all legal modifiers. - case SyntaxKind.StaticKeyword: - case SyntaxKind.ExportKeyword: - case SyntaxKind.DefaultKeyword: - } + // These are all legal modifiers. + case SyntaxKind.StaticKeyword: + case SyntaxKind.ExportKeyword: + case SyntaxKind.DefaultKeyword: } } + } - function createDiagnosticForNodeArray(nodes: NodeArray, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { - const start = nodes.pos; - return createFileDiagnostic(sourceFile, start, nodes.end - start, message, arg0, arg1, arg2); - } + function createDiagnosticForNodeArray(nodes: NodeArray, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { + const start = nodes.pos; + return createFileDiagnostic(sourceFile, start, nodes.end - start, message, arg0, arg1, arg2); + } - // Since these are syntactic diagnostics, parent might not have been set - // this means the sourceFile cannot be infered from the node - function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { - return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2); - } - }); - } + // Since these are syntactic diagnostics, parent might not have been set + // this means the sourceFile cannot be infered from the node + function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { + return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2); + } + }); + } - function getDeclarationDiagnosticsWorker(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] { - return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedDeclarationDiagnosticsForFile, getDeclarationDiagnosticsForFileNoCache); - } + function getDeclarationDiagnosticsWorker(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] { + return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedDeclarationDiagnosticsForFile, getDeclarationDiagnosticsForFileNoCache); + } - function getDeclarationDiagnosticsForFileNoCache(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] { - return runWithCancellationToken(() => { - const resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile, cancellationToken); - // Don't actually write any files since we're just getting diagnostics. - return ts.getDeclarationDiagnostics(getEmitHost(noop), resolver, sourceFile) || emptyArray; - }); - } + function getDeclarationDiagnosticsForFileNoCache(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] { + return runWithCancellationToken(() => { + const resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile, cancellationToken); + // Don't actually write any files since we're just getting diagnostics. + return ts.getDeclarationDiagnostics(getEmitHost(noop), resolver, sourceFile) || emptyArray; + }); + } - function getAndCacheDiagnostics( - sourceFile: T, - cancellationToken: CancellationToken | undefined, - cache: DiagnosticCache, - getDiagnostics: (sourceFile: T, cancellationToken: CancellationToken | undefined) => readonly U[], - ): readonly U[] { + function getAndCacheDiagnostics( + sourceFile: T, + cancellationToken: CancellationToken | undefined, + cache: DiagnosticCache, + getDiagnostics: (sourceFile: T, cancellationToken: CancellationToken | undefined) => readonly U[], + ): readonly U[] { - const cachedResult = sourceFile - ? cache.perFile?.get(sourceFile.path) - : cache.allDiagnostics; + const cachedResult = sourceFile + ? cache.perFile?.get(sourceFile.path) + : cache.allDiagnostics; - if (cachedResult) { - return cachedResult; - } - const result = getDiagnostics(sourceFile, cancellationToken); - if (sourceFile) { - (cache.perFile || (cache.perFile = new Map())).set(sourceFile.path, result); - } - else { - cache.allDiagnostics = result; - } - return result; + if (cachedResult) { + return cachedResult; } - - function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { - return sourceFile.isDeclarationFile ? [] : getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); + const result = getDiagnostics(sourceFile, cancellationToken); + if (sourceFile) { + (cache.perFile || (cache.perFile = new Map())).set(sourceFile.path, result); } - - function getOptionsDiagnostics(): SortedReadonlyArray { - return sortAndDeduplicateDiagnostics(concatenate( - programDiagnostics.getGlobalDiagnostics(), - getOptionsDiagnosticsOfConfigFile() - )); + else { + cache.allDiagnostics = result; } + return result; + } - function getOptionsDiagnosticsOfConfigFile() { - if (!options.configFile) return emptyArray; - let diagnostics = programDiagnostics.getDiagnostics(options.configFile.fileName); - forEachResolvedProjectReference(resolvedRef => { - diagnostics = concatenate(diagnostics, programDiagnostics.getDiagnostics(resolvedRef.sourceFile.fileName)); - }); - return diagnostics; - } + function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { + return sourceFile.isDeclarationFile ? [] : getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); + } - function getGlobalDiagnostics(): SortedReadonlyArray { - return rootNames.length ? sortAndDeduplicateDiagnostics(getDiagnosticsProducingTypeChecker().getGlobalDiagnostics().slice()) : emptyArray as any as SortedReadonlyArray; - } + function getOptionsDiagnostics(): SortedReadonlyArray { + return sortAndDeduplicateDiagnostics(concatenate( + programDiagnostics.getGlobalDiagnostics(), + getOptionsDiagnosticsOfConfigFile() + )); + } - function getConfigFileParsingDiagnostics(): readonly Diagnostic[] { - return configFileParsingDiagnostics || emptyArray; - } + function getOptionsDiagnosticsOfConfigFile() { + if (!options.configFile) return emptyArray; + let diagnostics = programDiagnostics.getDiagnostics(options.configFile.fileName); + forEachResolvedProjectReference(resolvedRef => { + diagnostics = concatenate(diagnostics, programDiagnostics.getDiagnostics(resolvedRef.sourceFile.fileName)); + }); + return diagnostics; + } - function processRootFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason) { - processSourceFile(normalizePath(fileName), isDefaultLib, ignoreNoDefaultLib, /*packageId*/ undefined, reason); - } + function getGlobalDiagnostics(): SortedReadonlyArray { + return rootNames.length ? sortAndDeduplicateDiagnostics(getDiagnosticsProducingTypeChecker().getGlobalDiagnostics().slice()) : emptyArray as any as SortedReadonlyArray; + } - function fileReferenceIsEqualTo(a: FileReference, b: FileReference): boolean { - return a.fileName === b.fileName; - } + function getConfigFileParsingDiagnostics(): readonly Diagnostic[] { + return configFileParsingDiagnostics || emptyArray; + } - function moduleNameIsEqualTo(a: StringLiteralLike | Identifier, b: StringLiteralLike | Identifier): boolean { - return a.kind === SyntaxKind.Identifier - ? b.kind === SyntaxKind.Identifier && a.escapedText === b.escapedText - : b.kind === SyntaxKind.StringLiteral && a.text === b.text; - } + function processRootFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason) { + processSourceFile(normalizePath(fileName), isDefaultLib, ignoreNoDefaultLib, /*packageId*/ undefined, reason); + } - function createSyntheticImport(text: string, file: SourceFile) { - const externalHelpersModuleReference = factory.createStringLiteral(text); - const importDecl = factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*importClause*/ undefined, externalHelpersModuleReference, /*assertClause*/ undefined); - addEmitFlags(importDecl, EmitFlags.NeverApplyImportHelper); - setParent(externalHelpersModuleReference, importDecl); - setParent(importDecl, file); - // explicitly unset the synthesized flag on these declarations so the checker API will answer questions about them - // (which is required to build the dependency graph for incremental emit) - (externalHelpersModuleReference as Mutable).flags &= ~NodeFlags.Synthesized; - (importDecl as Mutable).flags &= ~NodeFlags.Synthesized; - return externalHelpersModuleReference; - } + function fileReferenceIsEqualTo(a: FileReference, b: FileReference): boolean { + return a.fileName === b.fileName; + } - function collectExternalModuleReferences(file: SourceFile): void { - if (file.imports) { - return; - } + function moduleNameIsEqualTo(a: StringLiteralLike | Identifier, b: StringLiteralLike | Identifier): boolean { + return a.kind === SyntaxKind.Identifier + ? b.kind === SyntaxKind.Identifier && a.escapedText === b.escapedText + : b.kind === SyntaxKind.StringLiteral && a.text === b.text; + } - const isJavaScriptFile = isSourceFileJS(file); - const isExternalModuleFile = isExternalModule(file); + function createSyntheticImport(text: string, file: SourceFile) { + const externalHelpersModuleReference = factory.createStringLiteral(text); + const importDecl = factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*importClause*/ undefined, externalHelpersModuleReference, /*assertClause*/ undefined); + addEmitFlags(importDecl, EmitFlags.NeverApplyImportHelper); + setParent(externalHelpersModuleReference, importDecl); + setParent(importDecl, file); + // explicitly unset the synthesized flag on these declarations so the checker API will answer questions about them + // (which is required to build the dependency graph for incremental emit) + (externalHelpersModuleReference as Mutable).flags &= ~NodeFlags.Synthesized; + (importDecl as Mutable).flags &= ~NodeFlags.Synthesized; + return externalHelpersModuleReference; + } - // file.imports may not be undefined if there exists dynamic import - let imports: StringLiteralLike[] | undefined; - let moduleAugmentations: (StringLiteral | Identifier)[] | undefined; - let ambientModules: string[] | undefined; + function collectExternalModuleReferences(file: SourceFile): void { + if (file.imports) { + return; + } - // If we are importing helpers, we need to add a synthetic reference to resolve the - // helpers library. - if ((options.isolatedModules || isExternalModuleFile) - && !file.isDeclarationFile) { - if (options.importHelpers) { - // synthesize 'import "tslib"' declaration - imports = [createSyntheticImport(externalHelpersModuleNameText, file)]; - } - const jsxImport = getJSXRuntimeImport(getJSXImplicitImportBase(options, file), options); - if (jsxImport) { - // synthesize `import "base/jsx-runtime"` declaration - (imports ||= []).push(createSyntheticImport(jsxImport, file)); - } - } + const isJavaScriptFile = isSourceFileJS(file); + const isExternalModuleFile = isExternalModule(file); - for (const node of file.statements) { - collectModuleReferences(node, /*inAmbientModule*/ false); + // file.imports may not be undefined if there exists dynamic import + let imports: StringLiteralLike[] | undefined; + let moduleAugmentations: (StringLiteral | Identifier)[] | undefined; + let ambientModules: string[] | undefined; + + // If we are importing helpers, we need to add a synthetic reference to resolve the + // helpers library. + if ((options.isolatedModules || isExternalModuleFile) + && !file.isDeclarationFile) { + if (options.importHelpers) { + // synthesize 'import "tslib"' declaration + imports = [createSyntheticImport(externalHelpersModuleNameText, file)]; } - if ((file.flags & NodeFlags.PossiblyContainsDynamicImport) || isJavaScriptFile) { - collectDynamicImportOrRequireCalls(file); + const jsxImport = getJSXRuntimeImport(getJSXImplicitImportBase(options, file), options); + if (jsxImport) { + // synthesize `import "base/jsx-runtime"` declaration + (imports ||= []).push(createSyntheticImport(jsxImport, file)); } + } - file.imports = imports || emptyArray; - file.moduleAugmentations = moduleAugmentations || emptyArray; - file.ambientModuleNames = ambientModules || emptyArray; + for (const node of file.statements) { + collectModuleReferences(node, /*inAmbientModule*/ false); + } + if ((file.flags & NodeFlags.PossiblyContainsDynamicImport) || isJavaScriptFile) { + collectDynamicImportOrRequireCalls(file); + } - return; + file.imports = imports || emptyArray; + file.moduleAugmentations = moduleAugmentations || emptyArray; + file.ambientModuleNames = ambientModules || emptyArray; - function collectModuleReferences(node: Statement, inAmbientModule: boolean): void { - if (isAnyImportOrReExport(node)) { - const moduleNameExpr = getExternalModuleName(node); - // TypeScript 1.0 spec (April 2014): 12.1.6 - // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules - // only through top - level external module names. Relative external module names are not permitted. - if (moduleNameExpr && isStringLiteral(moduleNameExpr) && moduleNameExpr.text && (!inAmbientModule || !isExternalModuleNameRelative(moduleNameExpr.text))) { - setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here - imports = append(imports, moduleNameExpr); - if (!usesUriStyleNodeCoreModules && currentNodeModulesDepth === 0 && !file.isDeclarationFile) { - usesUriStyleNodeCoreModules = startsWith(moduleNameExpr.text, "node:"); - } + return; + + function collectModuleReferences(node: Statement, inAmbientModule: boolean): void { + if (isAnyImportOrReExport(node)) { + const moduleNameExpr = getExternalModuleName(node); + // TypeScript 1.0 spec (April 2014): 12.1.6 + // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules + // only through top - level external module names. Relative external module names are not permitted. + if (moduleNameExpr && isStringLiteral(moduleNameExpr) && moduleNameExpr.text && (!inAmbientModule || !isExternalModuleNameRelative(moduleNameExpr.text))) { + setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here + imports = append(imports, moduleNameExpr); + if (!usesUriStyleNodeCoreModules && currentNodeModulesDepth === 0 && !file.isDeclarationFile) { + usesUriStyleNodeCoreModules = startsWith(moduleNameExpr.text, "node:"); } } - else if (isModuleDeclaration(node)) { - if (isAmbientModule(node) && (inAmbientModule || hasSyntacticModifier(node, ModifierFlags.Ambient) || file.isDeclarationFile)) { - (node.name as Mutable).parent = node; - const nameText = getTextOfIdentifierOrLiteral(node.name); - // Ambient module declarations can be interpreted as augmentations for some existing external modules. - // This will happen in two cases: - // - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope - // - if current file is not external module then module augmentation is an ambient module declaration with non-relative module name - // immediately nested in top level ambient module declaration . - if (isExternalModuleFile || (inAmbientModule && !isExternalModuleNameRelative(nameText))) { - (moduleAugmentations || (moduleAugmentations = [])).push(node.name); + } + else if (isModuleDeclaration(node)) { + if (isAmbientModule(node) && (inAmbientModule || hasSyntacticModifier(node, ModifierFlags.Ambient) || file.isDeclarationFile)) { + (node.name as Mutable).parent = node; + const nameText = getTextOfIdentifierOrLiteral(node.name); + // Ambient module declarations can be interpreted as augmentations for some existing external modules. + // This will happen in two cases: + // - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope + // - if current file is not external module then module augmentation is an ambient module declaration with non-relative module name + // immediately nested in top level ambient module declaration . + if (isExternalModuleFile || (inAmbientModule && !isExternalModuleNameRelative(nameText))) { + (moduleAugmentations || (moduleAugmentations = [])).push(node.name); + } + else if (!inAmbientModule) { + if (file.isDeclarationFile) { + // for global .d.ts files record name of ambient module + (ambientModules || (ambientModules = [])).push(nameText); } - else if (!inAmbientModule) { - if (file.isDeclarationFile) { - // for global .d.ts files record name of ambient module - (ambientModules || (ambientModules = [])).push(nameText); - } - // An AmbientExternalModuleDeclaration declares an external module. - // This type of declaration is permitted only in the global module. - // The StringLiteral must specify a top - level external module name. - // Relative external module names are not permitted - - // NOTE: body of ambient module is always a module block, if it exists - const body = (node as ModuleDeclaration).body as ModuleBlock; - if (body) { - for (const statement of body.statements) { - collectModuleReferences(statement, /*inAmbientModule*/ true); - } + // An AmbientExternalModuleDeclaration declares an external module. + // This type of declaration is permitted only in the global module. + // The StringLiteral must specify a top - level external module name. + // Relative external module names are not permitted + + // NOTE: body of ambient module is always a module block, if it exists + const body = (node as ModuleDeclaration).body as ModuleBlock; + if (body) { + for (const statement of body.statements) { + collectModuleReferences(statement, /*inAmbientModule*/ true); } } } } } + } - function collectDynamicImportOrRequireCalls(file: SourceFile) { - const r = /import|require/g; - while (r.exec(file.text) !== null) { // eslint-disable-line no-null/no-null - const node = getNodeAtPosition(file, r.lastIndex); - if (isJavaScriptFile && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { - setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here - imports = append(imports, node.arguments[0]); - } - // we have to check the argument list has length of at least 1. We will still have to process these even though we have parsing error. - else if (isImportCall(node) && node.arguments.length >= 1 && isStringLiteralLike(node.arguments[0])) { - setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here - imports = append(imports, node.arguments[0]); - } - else if (isLiteralImportTypeNode(node)) { - setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here - imports = append(imports, node.argument.literal); - } + function collectDynamicImportOrRequireCalls(file: SourceFile) { + const r = /import|require/g; + while (r.exec(file.text) !== null) { // eslint-disable-line no-null/no-null + const node = getNodeAtPosition(file, r.lastIndex); + if (isJavaScriptFile && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { + setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here + imports = append(imports, node.arguments[0]); } - } - - /** Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only in JS files */ - function getNodeAtPosition(sourceFile: SourceFile, position: number): Node { - let current: Node = sourceFile; - const getContainingChild = (child: Node) => { - if (child.pos <= position && (position < child.end || (position === child.end && (child.kind === SyntaxKind.EndOfFileToken)))) { - return child; - } - }; - while (true) { - const child = isJavaScriptFile && hasJSDocNodes(current) && forEach(current.jsDoc, getContainingChild) || forEachChild(current, getContainingChild); - if (!child) { - return current; - } - current = child; + // we have to check the argument list has length of at least 1. We will still have to process these even though we have parsing error. + else if (isImportCall(node) && node.arguments.length >= 1 && isStringLiteralLike(node.arguments[0])) { + setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here + imports = append(imports, node.arguments[0]); + } + else if (isLiteralImportTypeNode(node)) { + setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here + imports = append(imports, node.argument.literal); } } } - function getLibFileFromReference(ref: FileReference) { - const libName = toFileNameLowerCase(ref.fileName); - const libFileName = libMap.get(libName); - if (libFileName) { - return getSourceFile(pathForLibFile(libFileName)); + /** Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only in JS files */ + function getNodeAtPosition(sourceFile: SourceFile, position: number): Node { + let current: Node = sourceFile; + const getContainingChild = (child: Node) => { + if (child.pos <= position && (position < child.end || (position === child.end && (child.kind === SyntaxKind.EndOfFileToken)))) { + return child; + } + }; + while (true) { + const child = isJavaScriptFile && hasJSDocNodes(current) && forEach(current.jsDoc, getContainingChild) || forEachChild(current, getContainingChild); + if (!child) { + return current; + } + current = child; } } + } - /** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ - function getSourceFileFromReference(referencingFile: SourceFile | UnparsedSource, ref: FileReference): SourceFile | undefined { - return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), getSourceFile); + function getLibFileFromReference(ref: FileReference) { + const libName = toFileNameLowerCase(ref.fileName); + const libFileName = libMap.get(libName); + if (libFileName) { + return getSourceFile(pathForLibFile(libFileName)); } + } - function getSourceFileFromReferenceWorker( - fileName: string, - getSourceFile: (fileName: string) => SourceFile | undefined, - fail?: (diagnostic: DiagnosticMessage, ...argument: string[]) => void, - reason?: FileIncludeReason): SourceFile | undefined { + /** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ + function getSourceFileFromReference(referencingFile: SourceFile | UnparsedSource, ref: FileReference): SourceFile | undefined { + return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), getSourceFile); + } - if (hasExtension(fileName)) { - const canonicalFileName = host.getCanonicalFileName(fileName); - if (!options.allowNonTsExtensions && !forEach(flatten(supportedExtensionsWithJsonIfResolveJsonModule), extension => fileExtensionIs(canonicalFileName, extension))) { - if (fail) { - if (hasJSFileExtension(canonicalFileName)) { - fail(Diagnostics.File_0_is_a_JavaScript_file_Did_you_mean_to_enable_the_allowJs_option, fileName); - } - else { - fail(Diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1, fileName, "'" + flatten(supportedExtensions).join("', '") + "'"); - } - } - return undefined; - } + function getSourceFileFromReferenceWorker( + fileName: string, + getSourceFile: (fileName: string) => SourceFile | undefined, + fail?: (diagnostic: DiagnosticMessage, ...argument: string[]) => void, + reason?: FileIncludeReason): SourceFile | undefined { - const sourceFile = getSourceFile(fileName); + if (hasExtension(fileName)) { + const canonicalFileName = host.getCanonicalFileName(fileName); + if (!options.allowNonTsExtensions && !forEach(flatten(supportedExtensionsWithJsonIfResolveJsonModule), extension => fileExtensionIs(canonicalFileName, extension))) { if (fail) { - if (!sourceFile) { - const redirect = getProjectReferenceRedirect(fileName); - if (redirect) { - fail(Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, fileName); - } - else { - fail(Diagnostics.File_0_not_found, fileName); - } + if (hasJSFileExtension(canonicalFileName)) { + fail(Diagnostics.File_0_is_a_JavaScript_file_Did_you_mean_to_enable_the_allowJs_option, fileName); } - else if (isReferencedFile(reason) && canonicalFileName === host.getCanonicalFileName(getSourceFileByPath(reason.file)!.fileName)) { - fail(Diagnostics.A_file_cannot_have_a_reference_to_itself); + else { + fail(Diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1, fileName, "'" + flatten(supportedExtensions).join("', '") + "'"); } } - return sourceFile; + return undefined; } - else { - const sourceFileNoExtension = options.allowNonTsExtensions && getSourceFile(fileName); - if (sourceFileNoExtension) return sourceFileNoExtension; - if (fail && options.allowNonTsExtensions) { - fail(Diagnostics.File_0_not_found, fileName); - return undefined; + const sourceFile = getSourceFile(fileName); + if (fail) { + if (!sourceFile) { + const redirect = getProjectReferenceRedirect(fileName); + if (redirect) { + fail(Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, fileName); + } + else { + fail(Diagnostics.File_0_not_found, fileName); + } + } + else if (isReferencedFile(reason) && canonicalFileName === host.getCanonicalFileName(getSourceFileByPath(reason.file)!.fileName)) { + fail(Diagnostics.A_file_cannot_have_a_reference_to_itself); } - - // Only try adding extensions from the first supported group (which should be .ts/.tsx/.d.ts) - const sourceFileWithAddedExtension = forEach(supportedExtensions[0], extension => getSourceFile(fileName + extension)); - if (fail && !sourceFileWithAddedExtension) fail(Diagnostics.Could_not_resolve_the_path_0_with_the_extensions_Colon_1, fileName, "'" + flatten(supportedExtensions).join("', '") + "'"); - return sourceFileWithAddedExtension; } + return sourceFile; } + else { + const sourceFileNoExtension = options.allowNonTsExtensions && getSourceFile(fileName); + if (sourceFileNoExtension) return sourceFileNoExtension; - /** This has side effects through `findSourceFile`. */ - function processSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, packageId: PackageId | undefined, reason: FileIncludeReason): void { - getSourceFileFromReferenceWorker( - fileName, - fileName => findSourceFile(fileName, isDefaultLib, ignoreNoDefaultLib, reason, packageId), // TODO: GH#18217 - (diagnostic, ...args) => addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, diagnostic, args), - reason - ); - } - - function processProjectReferenceFile(fileName: string, reason: ProjectReferenceFile) { - return processSourceFile(fileName, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined, reason); - } - - function reportFileNamesDifferOnlyInCasingError(fileName: string, existingFile: SourceFile, reason: FileIncludeReason): void { - const hasExistingReasonToReportErrorOn = !isReferencedFile(reason) && some(fileReasons.get(existingFile.path), isReferencedFile); - if (hasExistingReasonToReportErrorOn) { - addFilePreprocessingFileExplainingDiagnostic(existingFile, reason, Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, [existingFile.fileName, fileName]); - } - else { - addFilePreprocessingFileExplainingDiagnostic(existingFile, reason, Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, [fileName, existingFile.fileName]); + if (fail && options.allowNonTsExtensions) { + fail(Diagnostics.File_0_not_found, fileName); + return undefined; } - } - function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path, resolvedPath: Path, originalFileName: string): SourceFile { - const redirect: SourceFile = Object.create(redirectTarget); - redirect.fileName = fileName; - redirect.path = path; - redirect.resolvedPath = resolvedPath; - redirect.originalFileName = originalFileName; - redirect.redirectInfo = { redirectTarget, unredirected }; - sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); - Object.defineProperties(redirect, { - id: { - get(this: SourceFile) { return this.redirectInfo!.redirectTarget.id; }, - set(this: SourceFile, value: SourceFile["id"]) { this.redirectInfo!.redirectTarget.id = value; }, - }, - symbol: { - get(this: SourceFile) { return this.redirectInfo!.redirectTarget.symbol; }, - set(this: SourceFile, value: SourceFile["symbol"]) { this.redirectInfo!.redirectTarget.symbol = value; }, - }, - }); - return redirect; - } - - // Get source file from normalized fileName - function findSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined { - tracing?.push(tracing.Phase.Program, "findSourceFile", { - fileName, - isDefaultLib: isDefaultLib || undefined, - fileIncludeKind: (FileIncludeKind as any)[reason.kind], - }); - const result = findSourceFileWorker(fileName, isDefaultLib, ignoreNoDefaultLib, reason, packageId); - tracing?.pop(); - return result; + // Only try adding extensions from the first supported group (which should be .ts/.tsx/.d.ts) + const sourceFileWithAddedExtension = forEach(supportedExtensions[0], extension => getSourceFile(fileName + extension)); + if (fail && !sourceFileWithAddedExtension) fail(Diagnostics.Could_not_resolve_the_path_0_with_the_extensions_Colon_1, fileName, "'" + flatten(supportedExtensions).join("', '") + "'"); + return sourceFileWithAddedExtension; } + } - function findSourceFileWorker(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined { - const path = toPath(fileName); - if (useSourceOfProjectReferenceRedirect) { - let source = getSourceOfProjectReferenceRedirect(path); - // If preserveSymlinks is true, module resolution wont jump the symlink - // but the resolved real path may be the .d.ts from project reference - // Note:: Currently we try the real path only if the - // file is from node_modules to avoid having to run real path on all file paths - if (!source && - host.realpath && - options.preserveSymlinks && - isDeclarationFileName(fileName) && - stringContains(fileName, nodeModulesPathPart)) { - const realPath = toPath(host.realpath(fileName)); - if (realPath !== path) source = getSourceOfProjectReferenceRedirect(realPath); - } - if (source) { - const file = isString(source) ? - findSourceFile(source, isDefaultLib, ignoreNoDefaultLib, reason, packageId) : - undefined; - if (file) addFileToFilesByName(file, path, /*redirectedPath*/ undefined); - return file; - } - } - const originalFileName = fileName; - if (filesByName.has(path)) { - const file = filesByName.get(path); - addFileIncludeReason(file || undefined, reason); - // try to check if we've already seen this file but with a different casing in path - // NOTE: this only makes sense for case-insensitive file systems, and only on files which are not redirected - if (file && options.forceConsistentCasingInFileNames) { - const checkedName = file.fileName; - const isRedirect = toPath(checkedName) !== toPath(fileName); - if (isRedirect) { - fileName = getProjectReferenceRedirect(fileName) || fileName; - } - // Check if it differs only in drive letters its ok to ignore that error: - const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); - const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(fileName, currentDirectory); - if (checkedAbsolutePath !== inputAbsolutePath) { - reportFileNamesDifferOnlyInCasingError(fileName, file, reason); - } - } - - // If the file was previously found via a node_modules search, but is now being processed as a root file, - // then everything it sucks in may also be marked incorrectly, and needs to be checked again. - if (file && sourceFilesFoundSearchingNodeModules.get(file.path) && currentNodeModulesDepth === 0) { - sourceFilesFoundSearchingNodeModules.set(file.path, false); - if (!options.noResolve) { - processReferencedFiles(file, isDefaultLib); - processTypeReferenceDirectives(file); - } - if (!options.noLib) { - processLibReferenceDirectives(file); - } - - modulesWithElidedImports.set(file.path, false); - processImportedModules(file); - } - // See if we need to reprocess the imports due to prior skipped imports - else if (file && modulesWithElidedImports.get(file.path)) { - if (currentNodeModulesDepth < maxNodeModuleJsDepth) { - modulesWithElidedImports.set(file.path, false); - processImportedModules(file); - } - } - - return file || undefined; - } + /** This has side effects through `findSourceFile`. */ + function processSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, packageId: PackageId | undefined, reason: FileIncludeReason): void { + getSourceFileFromReferenceWorker( + fileName, + fileName => findSourceFile(fileName, isDefaultLib, ignoreNoDefaultLib, reason, packageId), // TODO: GH#18217 + (diagnostic, ...args) => addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, diagnostic, args), + reason + ); + } - let redirectedPath: Path | undefined; - if (isReferencedFile(reason) && !useSourceOfProjectReferenceRedirect) { - const redirectProject = getProjectReferenceRedirectProject(fileName); - if (redirectProject) { - if (outFile(redirectProject.commandLine.options)) { - // Shouldnt create many to 1 mapping file in --out scenario - return undefined; - } - const redirect = getProjectReferenceOutputName(redirectProject, fileName); - fileName = redirect; - // Once we start redirecting to a file, we can potentially come back to it - // via a back-reference from another file in the .d.ts folder. If that happens we'll - // end up trying to add it to the program *again* because we were tracking it via its - // original (un-redirected) name. So we have to map both the original path and the redirected path - // to the source file we're about to find/create - redirectedPath = toPath(redirect); - } - } + function processProjectReferenceFile(fileName: string, reason: ProjectReferenceFile) { + return processSourceFile(fileName, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined, reason); + } - // We haven't looked for this file, do so now and cache result - const file = host.getSourceFile( - fileName, - getEmitScriptTarget(options), - hostErrorMessage => addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, Diagnostics.Cannot_read_file_0_Colon_1, [fileName, hostErrorMessage]), - shouldCreateNewSourceFile - ); + function reportFileNamesDifferOnlyInCasingError(fileName: string, existingFile: SourceFile, reason: FileIncludeReason): void { + const hasExistingReasonToReportErrorOn = !isReferencedFile(reason) && some(fileReasons.get(existingFile.path), isReferencedFile); + if (hasExistingReasonToReportErrorOn) { + addFilePreprocessingFileExplainingDiagnostic(existingFile, reason, Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, [existingFile.fileName, fileName]); + } + else { + addFilePreprocessingFileExplainingDiagnostic(existingFile, reason, Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, [fileName, existingFile.fileName]); + } + } - if (packageId) { - const packageIdKey = packageIdToString(packageId); - const fileFromPackageId = packageIdToSourceFile.get(packageIdKey); - if (fileFromPackageId) { - // Some other SourceFile already exists with this package name and version. - // Instead of creating a duplicate, just redirect to the existing one. - const dupFile = createRedirectSourceFile(fileFromPackageId, file!, fileName, path, toPath(fileName), originalFileName); // TODO: GH#18217 - redirectTargetsMap.add(fileFromPackageId.path, fileName); - addFileToFilesByName(dupFile, path, redirectedPath); - addFileIncludeReason(dupFile, reason); - sourceFileToPackageName.set(path, packageId.name); - processingOtherFiles!.push(dupFile); - return dupFile; - } - else if (file) { - // This is the first source file to have this packageId. - packageIdToSourceFile.set(packageIdKey, file); - sourceFileToPackageName.set(path, packageId.name); - } - } - addFileToFilesByName(file, path, redirectedPath); - - if (file) { - sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); - file.fileName = fileName; // Ensure that source file has same name as what we were looking for - file.path = path; - file.resolvedPath = toPath(fileName); - file.originalFileName = originalFileName; - // It's a _little odd_ that we can't set `impliedNodeFormat` until the program step - but it's the first and only time we have a resolution cache - // and a freshly made source file node on hand at the same time, and we need both to set the field. Persisting the resolution cache all the way - // to the check and emit steps would be bad - so we much prefer detecting and storing the format information on the source file node upfront. - file.impliedNodeFormat = getImpliedNodeFormatForFile(file.resolvedPath, moduleResolutionCache?.getPackageJsonInfoCache(), host, options); - addFileIncludeReason(file, reason); - - if (host.useCaseSensitiveFileNames()) { - const pathLowerCase = toFileNameLowerCase(path); - // for case-sensitive file systems check if we've already seen some file with similar filename ignoring case - const existingFile = filesByNameIgnoreCase!.get(pathLowerCase); - if (existingFile) { - reportFileNamesDifferOnlyInCasingError(fileName, existingFile, reason); - } - else { - filesByNameIgnoreCase!.set(pathLowerCase, file); - } - } + function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path, resolvedPath: Path, originalFileName: string): SourceFile { + const redirect: SourceFile = Object.create(redirectTarget); + redirect.fileName = fileName; + redirect.path = path; + redirect.resolvedPath = resolvedPath; + redirect.originalFileName = originalFileName; + redirect.redirectInfo = { redirectTarget, unredirected }; + sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); + Object.defineProperties(redirect, { + id: { + get(this: SourceFile) { return this.redirectInfo!.redirectTarget.id; }, + set(this: SourceFile, value: SourceFile["id"]) { this.redirectInfo!.redirectTarget.id = value; }, + }, + symbol: { + get(this: SourceFile) { return this.redirectInfo!.redirectTarget.symbol; }, + set(this: SourceFile, value: SourceFile["symbol"]) { this.redirectInfo!.redirectTarget.symbol = value; }, + }, + }); + return redirect; + } - skipDefaultLib = skipDefaultLib || (file.hasNoDefaultLib && !ignoreNoDefaultLib); + // Get source file from normalized fileName + function findSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined { + tracing?.push(tracing.Phase.Program, "findSourceFile", { + fileName, + isDefaultLib: isDefaultLib || undefined, + fileIncludeKind: (FileIncludeKind as any)[reason.kind], + }); + const result = findSourceFileWorker(fileName, isDefaultLib, ignoreNoDefaultLib, reason, packageId); + tracing?.pop(); + return result; + } + function findSourceFileWorker(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined { + const path = toPath(fileName); + if (useSourceOfProjectReferenceRedirect) { + let source = getSourceOfProjectReferenceRedirect(path); + // If preserveSymlinks is true, module resolution wont jump the symlink + // but the resolved real path may be the .d.ts from project reference + // Note:: Currently we try the real path only if the + // file is from node_modules to avoid having to run real path on all file paths + if (!source && + host.realpath && + options.preserveSymlinks && + isDeclarationFileName(fileName) && + stringContains(fileName, nodeModulesPathPart)) { + const realPath = toPath(host.realpath(fileName)); + if (realPath !== path) source = getSourceOfProjectReferenceRedirect(realPath); + } + if (source) { + const file = isString(source) ? + findSourceFile(source, isDefaultLib, ignoreNoDefaultLib, reason, packageId) : + undefined; + if (file) addFileToFilesByName(file, path, /*redirectedPath*/ undefined); + return file; + } + } + const originalFileName = fileName; + if (filesByName.has(path)) { + const file = filesByName.get(path); + addFileIncludeReason(file || undefined, reason); + // try to check if we've already seen this file but with a different casing in path + // NOTE: this only makes sense for case-insensitive file systems, and only on files which are not redirected + if (file && options.forceConsistentCasingInFileNames) { + const checkedName = file.fileName; + const isRedirect = toPath(checkedName) !== toPath(fileName); + if (isRedirect) { + fileName = getProjectReferenceRedirect(fileName) || fileName; + } + // Check if it differs only in drive letters its ok to ignore that error: + const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); + const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(fileName, currentDirectory); + if (checkedAbsolutePath !== inputAbsolutePath) { + reportFileNamesDifferOnlyInCasingError(fileName, file, reason); + } + } + + // If the file was previously found via a node_modules search, but is now being processed as a root file, + // then everything it sucks in may also be marked incorrectly, and needs to be checked again. + if (file && sourceFilesFoundSearchingNodeModules.get(file.path) && currentNodeModulesDepth === 0) { + sourceFilesFoundSearchingNodeModules.set(file.path, false); if (!options.noResolve) { processReferencedFiles(file, isDefaultLib); processTypeReferenceDirectives(file); @@ -2798,1393 +2701,1490 @@ namespace ts { processLibReferenceDirectives(file); } - - // always process imported modules to record module name resolutions + modulesWithElidedImports.set(file.path, false); processImportedModules(file); + } + // See if we need to reprocess the imports due to prior skipped imports + else if (file && modulesWithElidedImports.get(file.path)) { + if (currentNodeModulesDepth < maxNodeModuleJsDepth) { + modulesWithElidedImports.set(file.path, false); + processImportedModules(file); + } + } + + return file || undefined; + } - if (isDefaultLib) { - processingDefaultLibFiles!.push(file); + let redirectedPath: Path | undefined; + if (isReferencedFile(reason) && !useSourceOfProjectReferenceRedirect) { + const redirectProject = getProjectReferenceRedirectProject(fileName); + if (redirectProject) { + if (outFile(redirectProject.commandLine.options)) { + // Shouldnt create many to 1 mapping file in --out scenario + return undefined; + } + const redirect = getProjectReferenceOutputName(redirectProject, fileName); + fileName = redirect; + // Once we start redirecting to a file, we can potentially come back to it + // via a back-reference from another file in the .d.ts folder. If that happens we'll + // end up trying to add it to the program *again* because we were tracking it via its + // original (un-redirected) name. So we have to map both the original path and the redirected path + // to the source file we're about to find/create + redirectedPath = toPath(redirect); + } + } + + // We haven't looked for this file, do so now and cache result + const file = host.getSourceFile( + fileName, + getEmitScriptTarget(options), + hostErrorMessage => addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, Diagnostics.Cannot_read_file_0_Colon_1, [fileName, hostErrorMessage]), + shouldCreateNewSourceFile + ); + + if (packageId) { + const packageIdKey = packageIdToString(packageId); + const fileFromPackageId = packageIdToSourceFile.get(packageIdKey); + if (fileFromPackageId) { + // Some other SourceFile already exists with this package name and version. + // Instead of creating a duplicate, just redirect to the existing one. + const dupFile = createRedirectSourceFile(fileFromPackageId, file!, fileName, path, toPath(fileName), originalFileName); // TODO: GH#18217 + redirectTargetsMap.add(fileFromPackageId.path, fileName); + addFileToFilesByName(dupFile, path, redirectedPath); + addFileIncludeReason(dupFile, reason); + sourceFileToPackageName.set(path, packageId.name); + processingOtherFiles!.push(dupFile); + return dupFile; + } + else if (file) { + // This is the first source file to have this packageId. + packageIdToSourceFile.set(packageIdKey, file); + sourceFileToPackageName.set(path, packageId.name); + } + } + addFileToFilesByName(file, path, redirectedPath); + + if (file) { + sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); + file.fileName = fileName; // Ensure that source file has same name as what we were looking for + file.path = path; + file.resolvedPath = toPath(fileName); + file.originalFileName = originalFileName; + // It's a _little odd_ that we can't set `impliedNodeFormat` until the program step - but it's the first and only time we have a resolution cache + // and a freshly made source file node on hand at the same time, and we need both to set the field. Persisting the resolution cache all the way + // to the check and emit steps would be bad - so we much prefer detecting and storing the format information on the source file node upfront. + file.impliedNodeFormat = getImpliedNodeFormatForFile(file.resolvedPath, moduleResolutionCache?.getPackageJsonInfoCache(), host, options); + addFileIncludeReason(file, reason); + + if (host.useCaseSensitiveFileNames()) { + const pathLowerCase = toFileNameLowerCase(path); + // for case-sensitive file systems check if we've already seen some file with similar filename ignoring case + const existingFile = filesByNameIgnoreCase!.get(pathLowerCase); + if (existingFile) { + reportFileNamesDifferOnlyInCasingError(fileName, existingFile, reason); } else { - processingOtherFiles!.push(file); + filesByNameIgnoreCase!.set(pathLowerCase, file); } } - return file; - } - function addFileIncludeReason(file: SourceFile | undefined, reason: FileIncludeReason) { - if (file) fileReasons.add(file.path, reason); - } + skipDefaultLib = skipDefaultLib || (file.hasNoDefaultLib && !ignoreNoDefaultLib); + + if (!options.noResolve) { + processReferencedFiles(file, isDefaultLib); + processTypeReferenceDirectives(file); + } + if (!options.noLib) { + processLibReferenceDirectives(file); + } + - function addFileToFilesByName(file: SourceFile | undefined, path: Path, redirectedPath: Path | undefined) { - if (redirectedPath) { - filesByName.set(redirectedPath, file); - filesByName.set(path, file || false); + // always process imported modules to record module name resolutions + processImportedModules(file); + + if (isDefaultLib) { + processingDefaultLibFiles!.push(file); } else { - filesByName.set(path, file); + processingOtherFiles!.push(file); } } + return file; + } - function getProjectReferenceRedirect(fileName: string): string | undefined { - const referencedProject = getProjectReferenceRedirectProject(fileName); - return referencedProject && getProjectReferenceOutputName(referencedProject, fileName); + function addFileIncludeReason(file: SourceFile | undefined, reason: FileIncludeReason) { + if (file) fileReasons.add(file.path, reason); + } + + function addFileToFilesByName(file: SourceFile | undefined, path: Path, redirectedPath: Path | undefined) { + if (redirectedPath) { + filesByName.set(redirectedPath, file); + filesByName.set(path, file || false); + } + else { + filesByName.set(path, file); } + } - function getProjectReferenceRedirectProject(fileName: string) { - // Ignore dts or any json files - if (!resolvedProjectReferences || !resolvedProjectReferences.length || fileExtensionIs(fileName, Extension.Dts) || fileExtensionIs(fileName, Extension.Json)) { - return undefined; - } + function getProjectReferenceRedirect(fileName: string): string | undefined { + const referencedProject = getProjectReferenceRedirectProject(fileName); + return referencedProject && getProjectReferenceOutputName(referencedProject, fileName); + } - // If this file is produced by a referenced project, we need to rewrite it to - // look in the output folder of the referenced project rather than the input - return getResolvedProjectReferenceToRedirect(fileName); + function getProjectReferenceRedirectProject(fileName: string) { + // Ignore dts or any json files + if (!resolvedProjectReferences || !resolvedProjectReferences.length || fileExtensionIs(fileName, Extension.Dts) || fileExtensionIs(fileName, Extension.Json)) { + return undefined; } + // If this file is produced by a referenced project, we need to rewrite it to + // look in the output folder of the referenced project rather than the input + return getResolvedProjectReferenceToRedirect(fileName); + } - function getProjectReferenceOutputName(referencedProject: ResolvedProjectReference, fileName: string) { - const out = outFile(referencedProject.commandLine.options); - return out ? - changeExtension(out, Extension.Dts) : - getOutputDeclarationFileName(fileName, referencedProject.commandLine, !host.useCaseSensitiveFileNames()); - } - /** - * Get the referenced project if the file is input file from that reference project - */ - function getResolvedProjectReferenceToRedirect(fileName: string) { - if (mapFromFileToProjectReferenceRedirects === undefined) { - mapFromFileToProjectReferenceRedirects = new Map(); - forEachResolvedProjectReference(referencedProject => { - // not input file from the referenced project, ignore - if (toPath(options.configFilePath!) !== referencedProject.sourceFile.path) { - referencedProject.commandLine.fileNames.forEach(f => - mapFromFileToProjectReferenceRedirects!.set(toPath(f), referencedProject.sourceFile.path)); - } - }); - } + function getProjectReferenceOutputName(referencedProject: ResolvedProjectReference, fileName: string) { + const out = outFile(referencedProject.commandLine.options); + return out ? + changeExtension(out, Extension.Dts) : + getOutputDeclarationFileName(fileName, referencedProject.commandLine, !host.useCaseSensitiveFileNames()); + } - const referencedProjectPath = mapFromFileToProjectReferenceRedirects.get(toPath(fileName)); - return referencedProjectPath && getResolvedProjectReferenceByPath(referencedProjectPath); + /** + * Get the referenced project if the file is input file from that reference project + */ + function getResolvedProjectReferenceToRedirect(fileName: string) { + if (mapFromFileToProjectReferenceRedirects === undefined) { + mapFromFileToProjectReferenceRedirects = new Map(); + forEachResolvedProjectReference(referencedProject => { + // not input file from the referenced project, ignore + if (toPath(options.configFilePath!) !== referencedProject.sourceFile.path) { + referencedProject.commandLine.fileNames.forEach(f => + mapFromFileToProjectReferenceRedirects!.set(toPath(f), referencedProject.sourceFile.path)); + } + }); } - function forEachResolvedProjectReference( - cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined - ): T | undefined { - return ts.forEachResolvedProjectReference(resolvedProjectReferences, cb); - } + const referencedProjectPath = mapFromFileToProjectReferenceRedirects.get(toPath(fileName)); + return referencedProjectPath && getResolvedProjectReferenceByPath(referencedProjectPath); + } - function getSourceOfProjectReferenceRedirect(path: Path) { - if (!isDeclarationFileName(path)) return undefined; - if (mapFromToProjectReferenceRedirectSource === undefined) { - mapFromToProjectReferenceRedirectSource = new Map(); - forEachResolvedProjectReference(resolvedRef => { - const out = outFile(resolvedRef.commandLine.options); - if (out) { - // Dont know which source file it means so return true? - const outputDts = changeExtension(out, Extension.Dts); - mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true); - } - else { - const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(resolvedRef.commandLine, !host.useCaseSensitiveFileNames())); - forEach(resolvedRef.commandLine.fileNames, fileName => { - if (!fileExtensionIs(fileName, Extension.Dts) && !fileExtensionIs(fileName, Extension.Json)) { - const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, !host.useCaseSensitiveFileNames(), getCommonSourceDirectory); - mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName); - } - }); - } - }); - } - return mapFromToProjectReferenceRedirectSource.get(path); - } + function forEachResolvedProjectReference( + cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined + ): T | undefined { + return ts.forEachResolvedProjectReference(resolvedProjectReferences, cb); + } - function isSourceOfProjectReferenceRedirect(fileName: string) { - return useSourceOfProjectReferenceRedirect && !!getResolvedProjectReferenceToRedirect(fileName); + function getSourceOfProjectReferenceRedirect(path: Path) { + if (!isDeclarationFileName(path)) return undefined; + if (mapFromToProjectReferenceRedirectSource === undefined) { + mapFromToProjectReferenceRedirectSource = new Map(); + forEachResolvedProjectReference(resolvedRef => { + const out = outFile(resolvedRef.commandLine.options); + if (out) { + // Dont know which source file it means so return true? + const outputDts = changeExtension(out, Extension.Dts); + mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true); + } + else { + const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(resolvedRef.commandLine, !host.useCaseSensitiveFileNames())); + forEach(resolvedRef.commandLine.fileNames, fileName => { + if (!fileExtensionIs(fileName, Extension.Dts) && !fileExtensionIs(fileName, Extension.Json)) { + const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, !host.useCaseSensitiveFileNames(), getCommonSourceDirectory); + mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName); + } + }); + } + }); } + return mapFromToProjectReferenceRedirectSource.get(path); + } - function getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined { - if (!projectReferenceRedirects) { - return undefined; - } + function isSourceOfProjectReferenceRedirect(fileName: string) { + return useSourceOfProjectReferenceRedirect && !!getResolvedProjectReferenceToRedirect(fileName); + } - return projectReferenceRedirects.get(projectReferencePath) || undefined; + function getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined { + if (!projectReferenceRedirects) { + return undefined; } - function processReferencedFiles(file: SourceFile, isDefaultLib: boolean) { - forEach(file.referencedFiles, (ref, index) => { - processSourceFile( - resolveTripleslashReference(ref.fileName, file.fileName), - isDefaultLib, - /*ignoreNoDefaultLib*/ false, - /*packageId*/ undefined, - { kind: FileIncludeKind.ReferenceFile, file: file.path, index, } - ); - }); - } + return projectReferenceRedirects.get(projectReferencePath) || undefined; + } - function processTypeReferenceDirectives(file: SourceFile) { - // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. - const typeDirectives = map(file.typeReferenceDirectives, ref => toFileNameLowerCase(ref.fileName)); - if (!typeDirectives) { - return; - } + function processReferencedFiles(file: SourceFile, isDefaultLib: boolean) { + forEach(file.referencedFiles, (ref, index) => { + processSourceFile( + resolveTripleslashReference(ref.fileName, file.fileName), + isDefaultLib, + /*ignoreNoDefaultLib*/ false, + /*packageId*/ undefined, + { kind: FileIncludeKind.ReferenceFile, file: file.path, index, } + ); + }); + } - const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file); - for (let index = 0; index < typeDirectives.length; index++) { - const ref = file.typeReferenceDirectives[index]; - const resolvedTypeReferenceDirective = resolutions[index]; - // store resolved type directive on the file - const fileName = toFileNameLowerCase(ref.fileName); - setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective); - processTypeReferenceDirective(fileName, resolvedTypeReferenceDirective, { kind: FileIncludeKind.TypeReferenceDirective, file: file.path, index, }); - } + function processTypeReferenceDirectives(file: SourceFile) { + // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. + const typeDirectives = map(file.typeReferenceDirectives, ref => toFileNameLowerCase(ref.fileName)); + if (!typeDirectives) { + return; } - function processTypeReferenceDirective( - typeReferenceDirective: string, - resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined, - reason: FileIncludeReason - ): void { - tracing?.push(tracing.Phase.Program, "processTypeReferenceDirective", { directive: typeReferenceDirective, hasResolved: !!resolveModuleNamesReusingOldState, refKind: reason.kind, refPath: isReferencedFile(reason) ? reason.file : undefined }); - processTypeReferenceDirectiveWorker(typeReferenceDirective, resolvedTypeReferenceDirective, reason); - tracing?.pop(); + const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file); + for (let index = 0; index < typeDirectives.length; index++) { + const ref = file.typeReferenceDirectives[index]; + const resolvedTypeReferenceDirective = resolutions[index]; + // store resolved type directive on the file + const fileName = toFileNameLowerCase(ref.fileName); + setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective); + processTypeReferenceDirective(fileName, resolvedTypeReferenceDirective, { kind: FileIncludeKind.TypeReferenceDirective, file: file.path, index, }); } + } + + function processTypeReferenceDirective( + typeReferenceDirective: string, + resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined, + reason: FileIncludeReason + ): void { + tracing?.push(tracing.Phase.Program, "processTypeReferenceDirective", { directive: typeReferenceDirective, hasResolved: !!resolveModuleNamesReusingOldState, refKind: reason.kind, refPath: isReferencedFile(reason) ? reason.file : undefined }); + processTypeReferenceDirectiveWorker(typeReferenceDirective, resolvedTypeReferenceDirective, reason); + tracing?.pop(); + } - function processTypeReferenceDirectiveWorker( - typeReferenceDirective: string, - resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined, - reason: FileIncludeReason - ): void { + function processTypeReferenceDirectiveWorker( + typeReferenceDirective: string, + resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined, + reason: FileIncludeReason + ): void { - // If we already found this library as a primary reference - nothing to do - const previousResolution = resolvedTypeReferenceDirectives.get(typeReferenceDirective); - if (previousResolution && previousResolution.primary) { - return; - } - let saveResolution = true; - if (resolvedTypeReferenceDirective) { - if (resolvedTypeReferenceDirective.isExternalLibraryImport) currentNodeModulesDepth++; + // If we already found this library as a primary reference - nothing to do + const previousResolution = resolvedTypeReferenceDirectives.get(typeReferenceDirective); + if (previousResolution && previousResolution.primary) { + return; + } + let saveResolution = true; + if (resolvedTypeReferenceDirective) { + if (resolvedTypeReferenceDirective.isExternalLibraryImport) currentNodeModulesDepth++; - if (resolvedTypeReferenceDirective.primary) { - // resolved from the primary path - processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, reason); // TODO: GH#18217 - } - else { - // If we already resolved to this file, it must have been a secondary reference. Check file contents - // for sameness and possibly issue an error - if (previousResolution) { - // Don't bother reading the file again if it's the same file. - if (resolvedTypeReferenceDirective.resolvedFileName !== previousResolution.resolvedFileName) { - const otherFileText = host.readFile(resolvedTypeReferenceDirective.resolvedFileName!); - const existingFile = getSourceFile(previousResolution.resolvedFileName!)!; - if (otherFileText !== existingFile.text) { - addFilePreprocessingFileExplainingDiagnostic( - existingFile, - reason, - Diagnostics.Conflicting_definitions_for_0_found_at_1_and_2_Consider_installing_a_specific_version_of_this_library_to_resolve_the_conflict, - [typeReferenceDirective, resolvedTypeReferenceDirective.resolvedFileName, previousResolution.resolvedFileName] - ); - } + if (resolvedTypeReferenceDirective.primary) { + // resolved from the primary path + processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, reason); // TODO: GH#18217 + } + else { + // If we already resolved to this file, it must have been a secondary reference. Check file contents + // for sameness and possibly issue an error + if (previousResolution) { + // Don't bother reading the file again if it's the same file. + if (resolvedTypeReferenceDirective.resolvedFileName !== previousResolution.resolvedFileName) { + const otherFileText = host.readFile(resolvedTypeReferenceDirective.resolvedFileName!); + const existingFile = getSourceFile(previousResolution.resolvedFileName!)!; + if (otherFileText !== existingFile.text) { + addFilePreprocessingFileExplainingDiagnostic( + existingFile, + reason, + Diagnostics.Conflicting_definitions_for_0_found_at_1_and_2_Consider_installing_a_specific_version_of_this_library_to_resolve_the_conflict, + [typeReferenceDirective, resolvedTypeReferenceDirective.resolvedFileName, previousResolution.resolvedFileName] + ); } - // don't overwrite previous resolution result - saveResolution = false; - } - else { - // First resolution of this library - processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, reason); } + // don't overwrite previous resolution result + saveResolution = false; + } + else { + // First resolution of this library + processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, reason); } - - if (resolvedTypeReferenceDirective.isExternalLibraryImport) currentNodeModulesDepth--; - } - else { - addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, Diagnostics.Cannot_find_type_definition_file_for_0, [typeReferenceDirective]); } - if (saveResolution) { - resolvedTypeReferenceDirectives.set(typeReferenceDirective, resolvedTypeReferenceDirective); - } + if (resolvedTypeReferenceDirective.isExternalLibraryImport) currentNodeModulesDepth--; } - - function pathForLibFile(libFileName: string): string { - // Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and - // lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable - // lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown - const components = libFileName.split("."); - let path = components[1]; - let i = 2; - while (components[i] && components[i] !== "d") { - path += (i === 2 ? "/" : "-") + components[i]; - i++; - } - const resolveFrom = combinePaths(currentDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`); - const localOverrideModuleResult = resolveModuleName("@typescript/lib-" + path, resolveFrom, { moduleResolution: ModuleResolutionKind.NodeJs }, host, moduleResolutionCache); - if (localOverrideModuleResult?.resolvedModule) { - return localOverrideModuleResult.resolvedModule.resolvedFileName; - } - return combinePaths(defaultLibraryPath, libFileName); + else { + addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, Diagnostics.Cannot_find_type_definition_file_for_0, [typeReferenceDirective]); } - function processLibReferenceDirectives(file: SourceFile) { - forEach(file.libReferenceDirectives, (libReference, index) => { - const libName = toFileNameLowerCase(libReference.fileName); - const libFileName = libMap.get(libName); - if (libFileName) { - // we ignore any 'no-default-lib' reference set on this file. - processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, }); - } - else { - const unqualifiedLibName = removeSuffix(removePrefix(libName, "lib."), ".d.ts"); - const suggestion = getSpellingSuggestion(unqualifiedLibName, libs, identity); - const diagnostic = suggestion ? Diagnostics.Cannot_find_lib_definition_for_0_Did_you_mean_1 : Diagnostics.Cannot_find_lib_definition_for_0; - (fileProcessingDiagnostics ||= []).push({ - kind: FilePreprocessingDiagnosticsKind.FilePreprocessingReferencedDiagnostic, - reason: { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, }, - diagnostic, - args: [libName, suggestion] - }); - } - }); + if (saveResolution) { + resolvedTypeReferenceDirectives.set(typeReferenceDirective, resolvedTypeReferenceDirective); } + } - function getCanonicalFileName(fileName: string): string { - return host.getCanonicalFileName(fileName); + function pathForLibFile(libFileName: string): string { + // Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and + // lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable + // lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown + const components = libFileName.split("."); + let path = components[1]; + let i = 2; + while (components[i] && components[i] !== "d") { + path += (i === 2 ? "/" : "-") + components[i]; + i++; } + const resolveFrom = combinePaths(currentDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`); + const localOverrideModuleResult = resolveModuleName("@typescript/lib-" + path, resolveFrom, { moduleResolution: ModuleResolutionKind.NodeJs }, host, moduleResolutionCache); + if (localOverrideModuleResult?.resolvedModule) { + return localOverrideModuleResult.resolvedModule.resolvedFileName; + } + return combinePaths(defaultLibraryPath, libFileName); + } - function processImportedModules(file: SourceFile) { - collectExternalModuleReferences(file); - if (file.imports.length || file.moduleAugmentations.length) { - // Because global augmentation doesn't have string literal name, we can check for global augmentation as such. - const moduleNames = getModuleNames(file); - const resolutions = resolveModuleNamesReusingOldState(moduleNames, file); - Debug.assert(resolutions.length === moduleNames.length); - const optionsForFile = (useSourceOfProjectReferenceRedirect ? getRedirectReferenceForResolution(file)?.commandLine.options : undefined) || options; - for (let index = 0; index < moduleNames.length; index++) { - const resolution = resolutions[index]; - setResolvedModule(file, moduleNames[index], resolution, getModeForResolutionAtIndex(file, index)); - - if (!resolution) { - continue; - } - - const isFromNodeModulesSearch = resolution.isExternalLibraryImport; - const isJsFile = !resolutionExtensionIsTSOrJson(resolution.extension); - const isJsFileFromNodeModules = isFromNodeModulesSearch && isJsFile; - const resolvedFileName = resolution.resolvedFileName; + function processLibReferenceDirectives(file: SourceFile) { + forEach(file.libReferenceDirectives, (libReference, index) => { + const libName = toFileNameLowerCase(libReference.fileName); + const libFileName = libMap.get(libName); + if (libFileName) { + // we ignore any 'no-default-lib' reference set on this file. + processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, }); + } + else { + const unqualifiedLibName = removeSuffix(removePrefix(libName, "lib."), ".d.ts"); + const suggestion = getSpellingSuggestion(unqualifiedLibName, libs, identity); + const diagnostic = suggestion ? Diagnostics.Cannot_find_lib_definition_for_0_Did_you_mean_1 : Diagnostics.Cannot_find_lib_definition_for_0; + (fileProcessingDiagnostics ||= []).push({ + kind: FilePreprocessingDiagnosticsKind.FilePreprocessingReferencedDiagnostic, + reason: { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, }, + diagnostic, + args: [libName, suggestion] + }); + } + }); + } - if (isFromNodeModulesSearch) { - currentNodeModulesDepth++; - } + function getCanonicalFileName(fileName: string): string { + return host.getCanonicalFileName(fileName); + } - // add file to program only if: - // - resolution was successful - // - noResolve is falsy - // - module name comes from the list of imports - // - it's not a top level JavaScript module that exceeded the search max - const elideImport = isJsFileFromNodeModules && currentNodeModulesDepth > maxNodeModuleJsDepth; - // Don't add the file if it has a bad extension (e.g. 'tsx' if we don't have '--allowJs') - // This may still end up being an untyped module -- the file won't be included but imports will be allowed. - const shouldAddFile = resolvedFileName - && !getResolutionDiagnostic(optionsForFile, resolution) - && !optionsForFile.noResolve - && index < file.imports.length - && !elideImport - && !(isJsFile && !getAllowJSCompilerOption(optionsForFile)) - && (isInJSFile(file.imports[index]) || !(file.imports[index].flags & NodeFlags.JSDoc)); - - if (elideImport) { - modulesWithElidedImports.set(file.path, true); - } - else if (shouldAddFile) { - findSourceFile( - resolvedFileName, - /*isDefaultLib*/ false, - /*ignoreNoDefaultLib*/ false, - { kind: FileIncludeKind.Import, file: file.path, index, }, - resolution.packageId, - ); - } + function processImportedModules(file: SourceFile) { + collectExternalModuleReferences(file); + if (file.imports.length || file.moduleAugmentations.length) { + // Because global augmentation doesn't have string literal name, we can check for global augmentation as such. + const moduleNames = getModuleNames(file); + const resolutions = resolveModuleNamesReusingOldState(moduleNames, file); + Debug.assert(resolutions.length === moduleNames.length); + const optionsForFile = (useSourceOfProjectReferenceRedirect ? getRedirectReferenceForResolution(file)?.commandLine.options : undefined) || options; + for (let index = 0; index < moduleNames.length; index++) { + const resolution = resolutions[index]; + setResolvedModule(file, moduleNames[index], resolution, getModeForResolutionAtIndex(file, index)); + + if (!resolution) { + continue; + } + + const isFromNodeModulesSearch = resolution.isExternalLibraryImport; + const isJsFile = !resolutionExtensionIsTSOrJson(resolution.extension); + const isJsFileFromNodeModules = isFromNodeModulesSearch && isJsFile; + const resolvedFileName = resolution.resolvedFileName; + + if (isFromNodeModulesSearch) { + currentNodeModulesDepth++; + } + + // add file to program only if: + // - resolution was successful + // - noResolve is falsy + // - module name comes from the list of imports + // - it's not a top level JavaScript module that exceeded the search max + const elideImport = isJsFileFromNodeModules && currentNodeModulesDepth > maxNodeModuleJsDepth; + // Don't add the file if it has a bad extension (e.g. 'tsx' if we don't have '--allowJs') + // This may still end up being an untyped module -- the file won't be included but imports will be allowed. + const shouldAddFile = resolvedFileName + && !getResolutionDiagnostic(optionsForFile, resolution) + && !optionsForFile.noResolve + && index < file.imports.length + && !elideImport + && !(isJsFile && !getAllowJSCompilerOption(optionsForFile)) + && (isInJSFile(file.imports[index]) || !(file.imports[index].flags & NodeFlags.JSDoc)); + + if (elideImport) { + modulesWithElidedImports.set(file.path, true); + } + else if (shouldAddFile) { + findSourceFile( + resolvedFileName, + /*isDefaultLib*/ false, + /*ignoreNoDefaultLib*/ false, + { kind: FileIncludeKind.Import, file: file.path, index, }, + resolution.packageId, + ); + } - if (isFromNodeModulesSearch) { - currentNodeModulesDepth--; - } + if (isFromNodeModulesSearch) { + currentNodeModulesDepth--; } } - else { - // no imports - drop cached module resolutions - file.resolvedModules = undefined; - } - } - - function checkSourceFilesBelongToPath(sourceFiles: readonly SourceFile[], rootDirectory: string): boolean { - let allFilesBelongToPath = true; - const absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory)); - for (const sourceFile of sourceFiles) { - if (!sourceFile.isDeclarationFile) { - const absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory)); - if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) { - addProgramDiagnosticExplainingFile( - sourceFile, - Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, - [sourceFile.fileName, rootDirectory] - ); - allFilesBelongToPath = false; - } + } + else { + // no imports - drop cached module resolutions + file.resolvedModules = undefined; + } + } + + function checkSourceFilesBelongToPath(sourceFiles: readonly SourceFile[], rootDirectory: string): boolean { + let allFilesBelongToPath = true; + const absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory)); + for (const sourceFile of sourceFiles) { + if (!sourceFile.isDeclarationFile) { + const absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory)); + if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) { + addProgramDiagnosticExplainingFile( + sourceFile, + Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, + [sourceFile.fileName, rootDirectory] + ); + allFilesBelongToPath = false; } } - - return allFilesBelongToPath; } - function parseProjectReferenceConfigFile(ref: ProjectReference): ResolvedProjectReference | undefined { - if (!projectReferenceRedirects) { - projectReferenceRedirects = new Map(); - } + return allFilesBelongToPath; + } - // The actual filename (i.e. add "/tsconfig.json" if necessary) - const refPath = resolveProjectReferencePath(ref); - const sourceFilePath = toPath(refPath); - const fromCache = projectReferenceRedirects.get(sourceFilePath); - if (fromCache !== undefined) { - return fromCache || undefined; - } + function parseProjectReferenceConfigFile(ref: ProjectReference): ResolvedProjectReference | undefined { + if (!projectReferenceRedirects) { + projectReferenceRedirects = new Map(); + } - let commandLine: ParsedCommandLine | undefined; - let sourceFile: JsonSourceFile | undefined; - if (host.getParsedCommandLine) { - commandLine = host.getParsedCommandLine(refPath); - if (!commandLine) { - addFileToFilesByName(/*sourceFile*/ undefined, sourceFilePath, /*redirectedPath*/ undefined); - projectReferenceRedirects.set(sourceFilePath, false); - return undefined; - } - sourceFile = Debug.checkDefined(commandLine.options.configFile); - Debug.assert(!sourceFile.path || sourceFile.path === sourceFilePath); - addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); + // The actual filename (i.e. add "/tsconfig.json" if necessary) + const refPath = resolveProjectReferencePath(ref); + const sourceFilePath = toPath(refPath); + const fromCache = projectReferenceRedirects.get(sourceFilePath); + if (fromCache !== undefined) { + return fromCache || undefined; + } + + let commandLine: ParsedCommandLine | undefined; + let sourceFile: JsonSourceFile | undefined; + if (host.getParsedCommandLine) { + commandLine = host.getParsedCommandLine(refPath); + if (!commandLine) { + addFileToFilesByName(/*sourceFile*/ undefined, sourceFilePath, /*redirectedPath*/ undefined); + projectReferenceRedirects.set(sourceFilePath, false); + return undefined; } - else { - // An absolute path pointing to the containing directory of the config file - const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory()); - sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined; - addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); - if (sourceFile === undefined) { - projectReferenceRedirects.set(sourceFilePath, false); - return undefined; - } - commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath); + sourceFile = Debug.checkDefined(commandLine.options.configFile); + Debug.assert(!sourceFile.path || sourceFile.path === sourceFilePath); + addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); + } + else { + // An absolute path pointing to the containing directory of the config file + const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory()); + sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined; + addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); + if (sourceFile === undefined) { + projectReferenceRedirects.set(sourceFilePath, false); + return undefined; } - sourceFile.fileName = refPath; - sourceFile.path = sourceFilePath; - sourceFile.resolvedPath = sourceFilePath; - sourceFile.originalFileName = refPath; + commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath); + } + sourceFile.fileName = refPath; + sourceFile.path = sourceFilePath; + sourceFile.resolvedPath = sourceFilePath; + sourceFile.originalFileName = refPath; - const resolvedRef: ResolvedProjectReference = { commandLine, sourceFile }; - projectReferenceRedirects.set(sourceFilePath, resolvedRef); - if (commandLine.projectReferences) { - resolvedRef.references = commandLine.projectReferences.map(parseProjectReferenceConfigFile); - } - return resolvedRef; + const resolvedRef: ResolvedProjectReference = { commandLine, sourceFile }; + projectReferenceRedirects.set(sourceFilePath, resolvedRef); + if (commandLine.projectReferences) { + resolvedRef.references = commandLine.projectReferences.map(parseProjectReferenceConfigFile); } + return resolvedRef; + } - function verifyCompilerOptions() { - const isNightly = stringContains(version, "-dev") || stringContains(version, "-insiders"); - if (!isNightly) { - if (getEmitModuleKind(options) === ModuleKind.Node12) { - createOptionValueDiagnostic("module", Diagnostics.Compiler_option_0_of_value_1_is_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next, "module", "node12"); - } - else if (getEmitModuleKind(options) === ModuleKind.NodeNext) { - createOptionValueDiagnostic("module", Diagnostics.Compiler_option_0_of_value_1_is_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next, "module", "nodenext"); - } - else if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node12) { - createOptionValueDiagnostic("moduleResolution", Diagnostics.Compiler_option_0_of_value_1_is_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next, "moduleResolution", "node12"); - } - else if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext) { - createOptionValueDiagnostic("moduleResolution", Diagnostics.Compiler_option_0_of_value_1_is_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next, "moduleResolution", "nodenext"); - } + function verifyCompilerOptions() { + const isNightly = stringContains(version, "-dev") || stringContains(version, "-insiders"); + if (!isNightly) { + if (getEmitModuleKind(options) === ModuleKind.Node12) { + createOptionValueDiagnostic("module", Diagnostics.Compiler_option_0_of_value_1_is_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next, "module", "node12"); } - if (options.strictPropertyInitialization && !getStrictOptionValue(options, "strictNullChecks")) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "strictPropertyInitialization", "strictNullChecks"); + else if (getEmitModuleKind(options) === ModuleKind.NodeNext) { + createOptionValueDiagnostic("module", Diagnostics.Compiler_option_0_of_value_1_is_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next, "module", "nodenext"); } - if (options.exactOptionalPropertyTypes && !getStrictOptionValue(options, "strictNullChecks")) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "exactOptionalPropertyTypes", "strictNullChecks"); + else if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node12) { + createOptionValueDiagnostic("moduleResolution", Diagnostics.Compiler_option_0_of_value_1_is_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next, "moduleResolution", "node12"); } + else if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext) { + createOptionValueDiagnostic("moduleResolution", Diagnostics.Compiler_option_0_of_value_1_is_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next, "moduleResolution", "nodenext"); + } + } + if (options.strictPropertyInitialization && !getStrictOptionValue(options, "strictNullChecks")) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "strictPropertyInitialization", "strictNullChecks"); + } + if (options.exactOptionalPropertyTypes && !getStrictOptionValue(options, "strictNullChecks")) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "exactOptionalPropertyTypes", "strictNullChecks"); + } - if (options.isolatedModules) { - if (options.out) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "isolatedModules"); - } - - if (options.outFile) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "outFile", "isolatedModules"); - } + if (options.isolatedModules) { + if (options.out) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "isolatedModules"); } - if (options.inlineSourceMap) { - if (options.sourceMap) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "sourceMap", "inlineSourceMap"); - } - if (options.mapRoot) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "mapRoot", "inlineSourceMap"); - } + if (options.outFile) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "outFile", "isolatedModules"); } + } - if (options.composite) { - if (options.declaration === false) { - createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_declaration_emit, "declaration"); - } - if (options.incremental === false) { - createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_incremental_compilation, "declaration"); - } + if (options.inlineSourceMap) { + if (options.sourceMap) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "sourceMap", "inlineSourceMap"); } + if (options.mapRoot) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "mapRoot", "inlineSourceMap"); + } + } - const outputFile = outFile(options); - if (options.tsBuildInfoFile) { - if (!isIncrementalCompilation(options)) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "tsBuildInfoFile", "incremental", "composite"); - } + if (options.composite) { + if (options.declaration === false) { + createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_declaration_emit, "declaration"); } - else if (options.incremental && !outputFile && !options.configFilePath) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified)); + if (options.incremental === false) { + createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_incremental_compilation, "declaration"); } + } - verifyProjectReferences(); + const outputFile = outFile(options); + if (options.tsBuildInfoFile) { + if (!isIncrementalCompilation(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "tsBuildInfoFile", "incremental", "composite"); + } + } + else if (options.incremental && !outputFile && !options.configFilePath) { + programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified)); + } - // List of collected files is complete; validate exhautiveness if this is a project with a file list - if (options.composite) { - const rootPaths = new Set(rootNames.map(toPath)); - for (const file of files) { - // Ignore file that is not emitted - if (sourceFileMayBeEmitted(file, program) && !rootPaths.has(file.path)) { - addProgramDiagnosticExplainingFile( - file, - Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, - [file.fileName, options.configFilePath || ""] - ); - } + verifyProjectReferences(); + + // List of collected files is complete; validate exhautiveness if this is a project with a file list + if (options.composite) { + const rootPaths = new Set(rootNames.map(toPath)); + for (const file of files) { + // Ignore file that is not emitted + if (sourceFileMayBeEmitted(file, program) && !rootPaths.has(file.path)) { + addProgramDiagnosticExplainingFile( + file, + Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, + [file.fileName, options.configFilePath || ""] + ); } } + } - if (options.paths) { - for (const key in options.paths) { - if (!hasProperty(options.paths, key)) { - continue; - } - if (!hasZeroOrOneAsteriskCharacter(key)) { - createDiagnosticForOptionPaths(/*onKey*/ true, key, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, key); + if (options.paths) { + for (const key in options.paths) { + if (!hasProperty(options.paths, key)) { + continue; + } + if (!hasZeroOrOneAsteriskCharacter(key)) { + createDiagnosticForOptionPaths(/*onKey*/ true, key, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, key); + } + if (isArray(options.paths[key])) { + const len = options.paths[key].length; + if (len === 0) { + createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_shouldn_t_be_an_empty_array, key); } - if (isArray(options.paths[key])) { - const len = options.paths[key].length; - if (len === 0) { - createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_shouldn_t_be_an_empty_array, key); - } - for (let i = 0; i < len; i++) { - const subst = options.paths[key][i]; - const typeOfSubst = typeof subst; - if (typeOfSubst === "string") { - if (!hasZeroOrOneAsteriskCharacter(subst)) { - createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_in_pattern_1_can_have_at_most_one_Asterisk_character, subst, key); - } - if (!options.baseUrl && !pathIsRelative(subst) && !pathIsAbsolute(subst)) { - createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Non_relative_paths_are_not_allowed_when_baseUrl_is_not_set_Did_you_forget_a_leading_Slash); - } + for (let i = 0; i < len; i++) { + const subst = options.paths[key][i]; + const typeOfSubst = typeof subst; + if (typeOfSubst === "string") { + if (!hasZeroOrOneAsteriskCharacter(subst)) { + createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_in_pattern_1_can_have_at_most_one_Asterisk_character, subst, key); } - else { - createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_for_pattern_1_has_incorrect_type_expected_string_got_2, subst, key, typeOfSubst); + if (!options.baseUrl && !pathIsRelative(subst) && !pathIsAbsolute(subst)) { + createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Non_relative_paths_are_not_allowed_when_baseUrl_is_not_set_Did_you_forget_a_leading_Slash); } } + else { + createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_for_pattern_1_has_incorrect_type_expected_string_got_2, subst, key, typeOfSubst); + } } - else { - createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_should_be_an_array, key); - } - } - } - - if (!options.sourceMap && !options.inlineSourceMap) { - if (options.inlineSources) { - createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "inlineSources"); } - if (options.sourceRoot) { - createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "sourceRoot"); + else { + createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_should_be_an_array, key); } } + } - if (options.out && options.outFile) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile"); + if (!options.sourceMap && !options.inlineSourceMap) { + if (options.inlineSources) { + createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "inlineSources"); } - - if (options.mapRoot && !(options.sourceMap || options.declarationMap)) { - // Error to specify --mapRoot without --sourcemap - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "mapRoot", "sourceMap", "declarationMap"); + if (options.sourceRoot) { + createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "sourceRoot"); } + } - if (options.declarationDir) { - if (!getEmitDeclarations(options)) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationDir", "declaration", "composite"); - } - if (outputFile) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declarationDir", options.out ? "out" : "outFile"); - } - } + if (options.out && options.outFile) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile"); + } - if (options.declarationMap && !getEmitDeclarations(options)) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationMap", "declaration", "composite"); - } + if (options.mapRoot && !(options.sourceMap || options.declarationMap)) { + // Error to specify --mapRoot without --sourcemap + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "mapRoot", "sourceMap", "declarationMap"); + } - if (options.lib && options.noLib) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib"); + if (options.declarationDir) { + if (!getEmitDeclarations(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationDir", "declaration", "composite"); } - - if (options.noImplicitUseStrict && getStrictOptionValue(options, "alwaysStrict")) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict"); + if (outputFile) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declarationDir", options.out ? "out" : "outFile"); } + } - const languageVersion = getEmitScriptTarget(options); + if (options.declarationMap && !getEmitDeclarations(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationMap", "declaration", "composite"); + } - const firstNonAmbientExternalModuleSourceFile = find(files, f => isExternalModule(f) && !f.isDeclarationFile); - if (options.isolatedModules) { - if (options.module === ModuleKind.None && languageVersion < ScriptTarget.ES2015) { - createDiagnosticForOptionName(Diagnostics.Option_isolatedModules_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES2015_or_higher, "isolatedModules", "target"); - } + if (options.lib && options.noLib) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib"); + } - if (options.preserveConstEnums === false) { - createDiagnosticForOptionName(Diagnostics.Option_preserveConstEnums_cannot_be_disabled_when_isolatedModules_is_enabled, "preserveConstEnums", "isolatedModules"); - } + if (options.noImplicitUseStrict && getStrictOptionValue(options, "alwaysStrict")) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict"); + } + + const languageVersion = getEmitScriptTarget(options); + + const firstNonAmbientExternalModuleSourceFile = find(files, f => isExternalModule(f) && !f.isDeclarationFile); + if (options.isolatedModules) { + if (options.module === ModuleKind.None && languageVersion < ScriptTarget.ES2015) { + createDiagnosticForOptionName(Diagnostics.Option_isolatedModules_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES2015_or_higher, "isolatedModules", "target"); + } + + if (options.preserveConstEnums === false) { + createDiagnosticForOptionName(Diagnostics.Option_preserveConstEnums_cannot_be_disabled_when_isolatedModules_is_enabled, "preserveConstEnums", "isolatedModules"); + } + + const firstNonExternalModuleSourceFile = find(files, f => !isExternalModule(f) && !isSourceFileJS(f) && !f.isDeclarationFile && f.scriptKind !== ScriptKind.JSON); + if (firstNonExternalModuleSourceFile) { + const span = getErrorSpanForNode(firstNonExternalModuleSourceFile, firstNonExternalModuleSourceFile); + programDiagnostics.add(createFileDiagnostic(firstNonExternalModuleSourceFile, span.start, span.length, + Diagnostics._0_cannot_be_compiled_under_isolatedModules_because_it_is_considered_a_global_script_file_Add_an_import_export_or_an_empty_export_statement_to_make_it_a_module, getBaseFileName(firstNonExternalModuleSourceFile.fileName))); + } + } + else if (firstNonAmbientExternalModuleSourceFile && languageVersion < ScriptTarget.ES2015 && options.module === ModuleKind.None) { + // We cannot use createDiagnosticFromNode because nodes do not have parents yet + const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!); + programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none)); + } - const firstNonExternalModuleSourceFile = find(files, f => !isExternalModule(f) && !isSourceFileJS(f) && !f.isDeclarationFile && f.scriptKind !== ScriptKind.JSON); - if (firstNonExternalModuleSourceFile) { - const span = getErrorSpanForNode(firstNonExternalModuleSourceFile, firstNonExternalModuleSourceFile); - programDiagnostics.add(createFileDiagnostic(firstNonExternalModuleSourceFile, span.start, span.length, - Diagnostics._0_cannot_be_compiled_under_isolatedModules_because_it_is_considered_a_global_script_file_Add_an_import_export_or_an_empty_export_statement_to_make_it_a_module, getBaseFileName(firstNonExternalModuleSourceFile.fileName))); - } + // Cannot specify module gen that isn't amd or system with --out + if (outputFile && !options.emitDeclarationOnly) { + if (options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System)) { + createDiagnosticForOptionName(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile", "module"); } - else if (firstNonAmbientExternalModuleSourceFile && languageVersion < ScriptTarget.ES2015 && options.module === ModuleKind.None) { - // We cannot use createDiagnosticFromNode because nodes do not have parents yet + else if (options.module === undefined && firstNonAmbientExternalModuleSourceFile) { const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!); - programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none)); + programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_modules_using_option_0_unless_the_module_flag_is_amd_or_system, options.out ? "out" : "outFile")); } + } - // Cannot specify module gen that isn't amd or system with --out - if (outputFile && !options.emitDeclarationOnly) { - if (options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System)) { - createDiagnosticForOptionName(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile", "module"); - } - else if (options.module === undefined && firstNonAmbientExternalModuleSourceFile) { - const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!); - programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_modules_using_option_0_unless_the_module_flag_is_amd_or_system, options.out ? "out" : "outFile")); - } + if (options.resolveJsonModule) { + if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && + getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node12 && + getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) { + createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_cannot_be_specified_without_node_module_resolution_strategy, "resolveJsonModule"); } - - if (options.resolveJsonModule) { - if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && - getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node12 && - getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) { - createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_cannot_be_specified_without_node_module_resolution_strategy, "resolveJsonModule"); - } - // Any emit other than common js, amd, es2015 or esnext is error - else if (!hasJsonModuleEmitEnabled(options)) { - createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_can_only_be_specified_when_module_code_generation_is_commonjs_amd_es2015_or_esNext, "resolveJsonModule", "module"); - } + // Any emit other than common js, amd, es2015 or esnext is error + else if (!hasJsonModuleEmitEnabled(options)) { + createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_can_only_be_specified_when_module_code_generation_is_commonjs_amd_es2015_or_esNext, "resolveJsonModule", "module"); } + } - // there has to be common source directory if user specified --outdir || --rootDir || --sourceRoot - // if user specified --mapRoot, there needs to be common source directory if there would be multiple files being emitted - if (options.outDir || // there is --outDir specified - options.rootDir || // there is --rootDir specified - options.sourceRoot || // there is --sourceRoot specified - options.mapRoot) { // there is --mapRoot specified - - // Precalculate and cache the common source directory - const dir = getCommonSourceDirectory(); + // there has to be common source directory if user specified --outdir || --rootDir || --sourceRoot + // if user specified --mapRoot, there needs to be common source directory if there would be multiple files being emitted + if (options.outDir || // there is --outDir specified + options.rootDir || // there is --rootDir specified + options.sourceRoot || // there is --sourceRoot specified + options.mapRoot) { // there is --mapRoot specified - // If we failed to find a good common directory, but outDir is specified and at least one of our files is on a windows drive/URL/other resource, add a failure - if (options.outDir && dir === "" && files.some(file => getRootLength(file.fileName) > 1)) { - createDiagnosticForOptionName(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files, "outDir"); - } - } + // Precalculate and cache the common source directory + const dir = getCommonSourceDirectory(); - if (options.useDefineForClassFields && languageVersion === ScriptTarget.ES3) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_target_is_ES3, "useDefineForClassFields"); + // If we failed to find a good common directory, but outDir is specified and at least one of our files is on a windows drive/URL/other resource, add a failure + if (options.outDir && dir === "" && files.some(file => getRootLength(file.fileName) > 1)) { + createDiagnosticForOptionName(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files, "outDir"); } + } - if (options.checkJs && !getAllowJSCompilerOption(options)) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "checkJs", "allowJs")); - } + if (options.useDefineForClassFields && languageVersion === ScriptTarget.ES3) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_target_is_ES3, "useDefineForClassFields"); + } - if (options.emitDeclarationOnly) { - if (!getEmitDeclarations(options)) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "emitDeclarationOnly", "declaration", "composite"); - } + if (options.checkJs && !getAllowJSCompilerOption(options)) { + programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "checkJs", "allowJs")); + } - if (options.noEmit) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "emitDeclarationOnly", "noEmit"); - } + if (options.emitDeclarationOnly) { + if (!getEmitDeclarations(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "emitDeclarationOnly", "declaration", "composite"); } - if (options.emitDecoratorMetadata && - !options.experimentalDecorators) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators"); + if (options.noEmit) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "emitDeclarationOnly", "noEmit"); } + } - if (options.jsxFactory) { - if (options.reactNamespace) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory"); - } - if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxFactory", inverseJsxOptionMap.get("" + options.jsx)); - } - if (!parseIsolatedEntityName(options.jsxFactory, languageVersion)) { - createOptionValueDiagnostic("jsxFactory", Diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory); - } + if (options.emitDecoratorMetadata && + !options.experimentalDecorators) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators"); + } + + if (options.jsxFactory) { + if (options.reactNamespace) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory"); } - else if (options.reactNamespace && !isIdentifierText(options.reactNamespace, languageVersion)) { - createOptionValueDiagnostic("reactNamespace", Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace); + if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxFactory", inverseJsxOptionMap.get("" + options.jsx)); } - - if (options.jsxFragmentFactory) { - if (!options.jsxFactory) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "jsxFragmentFactory", "jsxFactory"); - } - if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxFragmentFactory", inverseJsxOptionMap.get("" + options.jsx)); - } - if (!parseIsolatedEntityName(options.jsxFragmentFactory, languageVersion)) { - createOptionValueDiagnostic("jsxFragmentFactory", Diagnostics.Invalid_value_for_jsxFragmentFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFragmentFactory); - } + if (!parseIsolatedEntityName(options.jsxFactory, languageVersion)) { + createOptionValueDiagnostic("jsxFactory", Diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory); } + } + else if (options.reactNamespace && !isIdentifierText(options.reactNamespace, languageVersion)) { + createOptionValueDiagnostic("reactNamespace", Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace); + } - if (options.reactNamespace) { - if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "reactNamespace", inverseJsxOptionMap.get("" + options.jsx)); - } + if (options.jsxFragmentFactory) { + if (!options.jsxFactory) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "jsxFragmentFactory", "jsxFactory"); } - - if (options.jsxImportSource) { - if (options.jsx === JsxEmit.React) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxImportSource", inverseJsxOptionMap.get("" + options.jsx)); - } + if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxFragmentFactory", inverseJsxOptionMap.get("" + options.jsx)); } + if (!parseIsolatedEntityName(options.jsxFragmentFactory, languageVersion)) { + createOptionValueDiagnostic("jsxFragmentFactory", Diagnostics.Invalid_value_for_jsxFragmentFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFragmentFactory); + } + } - if (options.preserveValueImports && getEmitModuleKind(options) < ModuleKind.ES2015) { - createOptionValueDiagnostic("importsNotUsedAsValues", Diagnostics.Option_preserveValueImports_can_only_be_used_when_module_is_set_to_es2015_or_later); + if (options.reactNamespace) { + if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "reactNamespace", inverseJsxOptionMap.get("" + options.jsx)); } + } - // If the emit is enabled make sure that every output file is unique and not overwriting any of the input files - if (!options.noEmit && !options.suppressOutputPathCheck) { - const emitHost = getEmitHost(); - const emitFilesSeen = new Set(); - forEachEmittedFile(emitHost, (emitFileNames) => { - if (!options.emitDeclarationOnly) { - verifyEmitFilePath(emitFileNames.jsFilePath, emitFilesSeen); - } - verifyEmitFilePath(emitFileNames.declarationFilePath, emitFilesSeen); - }); + if (options.jsxImportSource) { + if (options.jsx === JsxEmit.React) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxImportSource", inverseJsxOptionMap.get("" + options.jsx)); } + } - // Verify that all the emit files are unique and don't overwrite input files - function verifyEmitFilePath(emitFileName: string | undefined, emitFilesSeen: Set) { - if (emitFileName) { - const emitFilePath = toPath(emitFileName); - // Report error if the output overwrites input file - if (filesByName.has(emitFilePath)) { - let chain: DiagnosticMessageChain | undefined; - if (!options.configFilePath) { - // The program is from either an inferred project or an external project - chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Adding_a_tsconfig_json_file_will_help_organize_projects_that_contain_both_TypeScript_and_JavaScript_files_Learn_more_at_https_Colon_Slash_Slashaka_ms_Slashtsconfig); - } - chain = chainDiagnosticMessages(chain, Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file, emitFileName); - blockEmittingOfFile(emitFileName, createCompilerDiagnosticFromMessageChain(chain)); - } + if (options.preserveValueImports && getEmitModuleKind(options) < ModuleKind.ES2015) { + createOptionValueDiagnostic("importsNotUsedAsValues", Diagnostics.Option_preserveValueImports_can_only_be_used_when_module_is_set_to_es2015_or_later); + } - const emitFileKey = !host.useCaseSensitiveFileNames() ? toFileNameLowerCase(emitFilePath) : emitFilePath; - // Report error if multiple files write into same file - if (emitFilesSeen.has(emitFileKey)) { - // Already seen the same emit file - report error - blockEmittingOfFile(emitFileName, createCompilerDiagnostic(Diagnostics.Cannot_write_file_0_because_it_would_be_overwritten_by_multiple_input_files, emitFileName)); - } - else { - emitFilesSeen.add(emitFileKey); - } + // If the emit is enabled make sure that every output file is unique and not overwriting any of the input files + if (!options.noEmit && !options.suppressOutputPathCheck) { + const emitHost = getEmitHost(); + const emitFilesSeen = new Set(); + forEachEmittedFile(emitHost, (emitFileNames) => { + if (!options.emitDeclarationOnly) { + verifyEmitFilePath(emitFileNames.jsFilePath, emitFilesSeen); } - } + verifyEmitFilePath(emitFileNames.declarationFilePath, emitFilesSeen); + }); } - function createDiagnosticExplainingFile(file: SourceFile | undefined, fileProcessingReason: FileIncludeReason | undefined, diagnostic: DiagnosticMessage, args: (string | number | undefined)[] | undefined): Diagnostic { - let fileIncludeReasons: DiagnosticMessageChain[] | undefined; - let relatedInfo: Diagnostic[] | undefined; - let locationReason = isReferencedFile(fileProcessingReason) ? fileProcessingReason : undefined; - if (file) fileReasons.get(file.path)?.forEach(processReason); - if (fileProcessingReason) processReason(fileProcessingReason); - // If we have location and there is only one reason file is in which is the location, dont add details for file include - if (locationReason && fileIncludeReasons?.length === 1) fileIncludeReasons = undefined; - const location = locationReason && getReferencedFileLocation(getSourceFileByPath, locationReason); - const fileIncludeReasonDetails = fileIncludeReasons && chainDiagnosticMessages(fileIncludeReasons, Diagnostics.The_file_is_in_the_program_because_Colon); - const redirectInfo = file && explainIfFileIsRedirect(file); - const chain = chainDiagnosticMessages(redirectInfo ? fileIncludeReasonDetails ? [fileIncludeReasonDetails, ...redirectInfo] : redirectInfo : fileIncludeReasonDetails, diagnostic, ...args || emptyArray); - return location && isReferenceFileLocation(location) ? - createFileDiagnosticFromMessageChain(location.file, location.pos, location.end - location.pos, chain, relatedInfo) : - createCompilerDiagnosticFromMessageChain(chain, relatedInfo); - - function processReason(reason: FileIncludeReason) { - (fileIncludeReasons ||= []).push(fileIncludeReasonToDiagnostics(program, reason)); - if (!locationReason && isReferencedFile(reason)) { - // Report error at first reference file or file currently in processing and dont report in related information - locationReason = reason; + // Verify that all the emit files are unique and don't overwrite input files + function verifyEmitFilePath(emitFileName: string | undefined, emitFilesSeen: Set) { + if (emitFileName) { + const emitFilePath = toPath(emitFileName); + // Report error if the output overwrites input file + if (filesByName.has(emitFilePath)) { + let chain: DiagnosticMessageChain | undefined; + if (!options.configFilePath) { + // The program is from either an inferred project or an external project + chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Adding_a_tsconfig_json_file_will_help_organize_projects_that_contain_both_TypeScript_and_JavaScript_files_Learn_more_at_https_Colon_Slash_Slashaka_ms_Slashtsconfig); + } + chain = chainDiagnosticMessages(chain, Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file, emitFileName); + blockEmittingOfFile(emitFileName, createCompilerDiagnosticFromMessageChain(chain)); + } + + const emitFileKey = !host.useCaseSensitiveFileNames() ? toFileNameLowerCase(emitFilePath) : emitFilePath; + // Report error if multiple files write into same file + if (emitFilesSeen.has(emitFileKey)) { + // Already seen the same emit file - report error + blockEmittingOfFile(emitFileName, createCompilerDiagnostic(Diagnostics.Cannot_write_file_0_because_it_would_be_overwritten_by_multiple_input_files, emitFileName)); } - else if (locationReason !== reason) { - relatedInfo = append(relatedInfo, fileIncludeReasonToRelatedInformation(reason)); + else { + emitFilesSeen.add(emitFileKey); } - // Remove fileProcessingReason if its already included in fileReasons of the program - if (reason === fileProcessingReason) fileProcessingReason = undefined; } } + } - function addFilePreprocessingFileExplainingDiagnostic(file: SourceFile | undefined, fileProcessingReason: FileIncludeReason, diagnostic: DiagnosticMessage, args?: (string | number | undefined)[]) { - (fileProcessingDiagnostics ||= []).push({ - kind: FilePreprocessingDiagnosticsKind.FilePreprocessingFileExplainingDiagnostic, - file: file && file.path, - fileProcessingReason, - diagnostic, - args - }); + function createDiagnosticExplainingFile(file: SourceFile | undefined, fileProcessingReason: FileIncludeReason | undefined, diagnostic: DiagnosticMessage, args: (string | number | undefined)[] | undefined): Diagnostic { + let fileIncludeReasons: DiagnosticMessageChain[] | undefined; + let relatedInfo: Diagnostic[] | undefined; + let locationReason = isReferencedFile(fileProcessingReason) ? fileProcessingReason : undefined; + if (file) fileReasons.get(file.path)?.forEach(processReason); + if (fileProcessingReason) processReason(fileProcessingReason); + // If we have location and there is only one reason file is in which is the location, dont add details for file include + if (locationReason && fileIncludeReasons?.length === 1) fileIncludeReasons = undefined; + const location = locationReason && getReferencedFileLocation(getSourceFileByPath, locationReason); + const fileIncludeReasonDetails = fileIncludeReasons && chainDiagnosticMessages(fileIncludeReasons, Diagnostics.The_file_is_in_the_program_because_Colon); + const redirectInfo = file && explainIfFileIsRedirect(file); + const chain = chainDiagnosticMessages(redirectInfo ? fileIncludeReasonDetails ? [fileIncludeReasonDetails, ...redirectInfo] : redirectInfo : fileIncludeReasonDetails, diagnostic, ...args || emptyArray); + return location && isReferenceFileLocation(location) ? + createFileDiagnosticFromMessageChain(location.file, location.pos, location.end - location.pos, chain, relatedInfo) : + createCompilerDiagnosticFromMessageChain(chain, relatedInfo); + + function processReason(reason: FileIncludeReason) { + (fileIncludeReasons ||= []).push(fileIncludeReasonToDiagnostics(program, reason)); + if (!locationReason && isReferencedFile(reason)) { + // Report error at first reference file or file currently in processing and dont report in related information + locationReason = reason; + } + else if (locationReason !== reason) { + relatedInfo = append(relatedInfo, fileIncludeReasonToRelatedInformation(reason)); + } + // Remove fileProcessingReason if its already included in fileReasons of the program + if (reason === fileProcessingReason) fileProcessingReason = undefined; } + } - function addProgramDiagnosticExplainingFile(file: SourceFile, diagnostic: DiagnosticMessage, args?: (string | number | undefined)[]) { - programDiagnostics.add(createDiagnosticExplainingFile(file, /*fileProcessingReason*/ undefined, diagnostic, args)); - } + function addFilePreprocessingFileExplainingDiagnostic(file: SourceFile | undefined, fileProcessingReason: FileIncludeReason, diagnostic: DiagnosticMessage, args?: (string | number | undefined)[]) { + (fileProcessingDiagnostics ||= []).push({ + kind: FilePreprocessingDiagnosticsKind.FilePreprocessingFileExplainingDiagnostic, + file: file && file.path, + fileProcessingReason, + diagnostic, + args + }); + } - function fileIncludeReasonToRelatedInformation(reason: FileIncludeReason): DiagnosticWithLocation | undefined { - if (isReferencedFile(reason)) { - const referenceLocation = getReferencedFileLocation(getSourceFileByPath, reason); - let message: DiagnosticMessage; - switch (reason.kind) { - case FileIncludeKind.Import: - message = Diagnostics.File_is_included_via_import_here; - break; - case FileIncludeKind.ReferenceFile: - message = Diagnostics.File_is_included_via_reference_here; - break; - case FileIncludeKind.TypeReferenceDirective: - message = Diagnostics.File_is_included_via_type_library_reference_here; - break; - case FileIncludeKind.LibReferenceDirective: - message = Diagnostics.File_is_included_via_library_reference_here; - break; - default: - Debug.assertNever(reason); - } - return isReferenceFileLocation(referenceLocation) ? createFileDiagnostic( - referenceLocation.file, - referenceLocation.pos, - referenceLocation.end - referenceLocation.pos, - message, - ) : undefined; - } + function addProgramDiagnosticExplainingFile(file: SourceFile, diagnostic: DiagnosticMessage, args?: (string | number | undefined)[]) { + programDiagnostics.add(createDiagnosticExplainingFile(file, /*fileProcessingReason*/ undefined, diagnostic, args)); + } - if (!options.configFile) return undefined; - let configFileNode: Node | undefined; + function fileIncludeReasonToRelatedInformation(reason: FileIncludeReason): DiagnosticWithLocation | undefined { + if (isReferencedFile(reason)) { + const referenceLocation = getReferencedFileLocation(getSourceFileByPath, reason); let message: DiagnosticMessage; switch (reason.kind) { - case FileIncludeKind.RootFile: - if (!options.configFile.configFileSpecs) return undefined; - const fileName = getNormalizedAbsolutePath(rootNames[reason.index], currentDirectory); - const matchedByFiles = getMatchedFileSpec(program, fileName); - if (matchedByFiles) { - configFileNode = getTsConfigPropArrayElementValue(options.configFile, "files", matchedByFiles); - message = Diagnostics.File_is_matched_by_files_list_specified_here; - break; - } - const matchedByInclude = getMatchedIncludeSpec(program, fileName); - // Could be additional files specified as roots - if (!matchedByInclude) return undefined; - configFileNode = getTsConfigPropArrayElementValue(options.configFile, "include", matchedByInclude); - message = Diagnostics.File_is_matched_by_include_pattern_specified_here; + case FileIncludeKind.Import: + message = Diagnostics.File_is_included_via_import_here; break; - case FileIncludeKind.SourceFromProjectReference: - case FileIncludeKind.OutputFromProjectReference: - const referencedResolvedRef = Debug.checkDefined(resolvedProjectReferences?.[reason.index]); - const referenceInfo = forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, parent, index) => - resolvedRef === referencedResolvedRef ? { sourceFile: parent?.sourceFile || options.configFile!, index } : undefined - ); - if (!referenceInfo) return undefined; - const { sourceFile, index } = referenceInfo; - const referencesSyntax = firstDefined(getTsConfigPropArray(sourceFile as TsConfigSourceFile, "references"), - property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined); - return referencesSyntax && referencesSyntax.elements.length > index ? - createDiagnosticForNodeInSourceFile( - sourceFile, - referencesSyntax.elements[index], - reason.kind === FileIncludeKind.OutputFromProjectReference ? - Diagnostics.File_is_output_from_referenced_project_specified_here : - Diagnostics.File_is_source_from_referenced_project_specified_here, - ) : - undefined; - case FileIncludeKind.AutomaticTypeDirectiveFile: - if (!options.types) return undefined; - configFileNode = getOptionsSyntaxByArrayElementValue("types", reason.typeReference); - message = Diagnostics.File_is_entry_point_of_type_library_specified_here; + case FileIncludeKind.ReferenceFile: + message = Diagnostics.File_is_included_via_reference_here; break; - case FileIncludeKind.LibFile: - if (reason.index !== undefined) { - configFileNode = getOptionsSyntaxByArrayElementValue("lib", options.lib![reason.index]); - message = Diagnostics.File_is_library_specified_here; - break; - } - const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === getEmitScriptTarget(options) ? key : undefined); - configFileNode = target ? getOptionsSyntaxByValue("target", target) : undefined; - message = Diagnostics.File_is_default_library_for_target_specified_here; + case FileIncludeKind.TypeReferenceDirective: + message = Diagnostics.File_is_included_via_type_library_reference_here; + break; + case FileIncludeKind.LibReferenceDirective: + message = Diagnostics.File_is_included_via_library_reference_here; break; default: Debug.assertNever(reason); } - return configFileNode && createDiagnosticForNodeInSourceFile( - options.configFile, - configFileNode, + return isReferenceFileLocation(referenceLocation) ? createFileDiagnostic( + referenceLocation.file, + referenceLocation.pos, + referenceLocation.end - referenceLocation.pos, message, - ); + ) : undefined; + } + + if (!options.configFile) return undefined; + let configFileNode: Node | undefined; + let message: DiagnosticMessage; + switch (reason.kind) { + case FileIncludeKind.RootFile: + if (!options.configFile.configFileSpecs) return undefined; + const fileName = getNormalizedAbsolutePath(rootNames[reason.index], currentDirectory); + const matchedByFiles = getMatchedFileSpec(program, fileName); + if (matchedByFiles) { + configFileNode = getTsConfigPropArrayElementValue(options.configFile, "files", matchedByFiles); + message = Diagnostics.File_is_matched_by_files_list_specified_here; + break; + } + const matchedByInclude = getMatchedIncludeSpec(program, fileName); + // Could be additional files specified as roots + if (!matchedByInclude) return undefined; + configFileNode = getTsConfigPropArrayElementValue(options.configFile, "include", matchedByInclude); + message = Diagnostics.File_is_matched_by_include_pattern_specified_here; + break; + case FileIncludeKind.SourceFromProjectReference: + case FileIncludeKind.OutputFromProjectReference: + const referencedResolvedRef = Debug.checkDefined(resolvedProjectReferences?.[reason.index]); + const referenceInfo = forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, parent, index) => + resolvedRef === referencedResolvedRef ? { sourceFile: parent?.sourceFile || options.configFile!, index } : undefined + ); + if (!referenceInfo) return undefined; + const { sourceFile, index } = referenceInfo; + const referencesSyntax = firstDefined(getTsConfigPropArray(sourceFile as TsConfigSourceFile, "references"), + property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined); + return referencesSyntax && referencesSyntax.elements.length > index ? + createDiagnosticForNodeInSourceFile( + sourceFile, + referencesSyntax.elements[index], + reason.kind === FileIncludeKind.OutputFromProjectReference ? + Diagnostics.File_is_output_from_referenced_project_specified_here : + Diagnostics.File_is_source_from_referenced_project_specified_here, + ) : + undefined; + case FileIncludeKind.AutomaticTypeDirectiveFile: + if (!options.types) return undefined; + configFileNode = getOptionsSyntaxByArrayElementValue("types", reason.typeReference); + message = Diagnostics.File_is_entry_point_of_type_library_specified_here; + break; + case FileIncludeKind.LibFile: + if (reason.index !== undefined) { + configFileNode = getOptionsSyntaxByArrayElementValue("lib", options.lib![reason.index]); + message = Diagnostics.File_is_library_specified_here; + break; + } + const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === getEmitScriptTarget(options) ? key : undefined); + configFileNode = target ? getOptionsSyntaxByValue("target", target) : undefined; + message = Diagnostics.File_is_default_library_for_target_specified_here; + break; + default: + Debug.assertNever(reason); } + return configFileNode && createDiagnosticForNodeInSourceFile( + options.configFile, + configFileNode, + message, + ); + } - function verifyProjectReferences() { - const buildInfoPath = !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined; - forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, parent, index) => { - const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; - const parentFile = parent && parent.sourceFile as JsonSourceFile; - if (!resolvedRef) { - createDiagnosticForReference(parentFile, index, Diagnostics.File_0_not_found, ref.path); - return; - } - const options = resolvedRef.commandLine.options; - if (!options.composite || options.noEmit) { - // ok to not have composite if the current program is container only - const inputs = parent ? parent.commandLine.fileNames : rootNames; - if (inputs.length) { - if (!options.composite) createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true, ref.path); - if (options.noEmit) createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_may_not_disable_emit, ref.path); - } + function verifyProjectReferences() { + const buildInfoPath = !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined; + forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, parent, index) => { + const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; + const parentFile = parent && parent.sourceFile as JsonSourceFile; + if (!resolvedRef) { + createDiagnosticForReference(parentFile, index, Diagnostics.File_0_not_found, ref.path); + return; + } + const options = resolvedRef.commandLine.options; + if (!options.composite || options.noEmit) { + // ok to not have composite if the current program is container only + const inputs = parent ? parent.commandLine.fileNames : rootNames; + if (inputs.length) { + if (!options.composite) createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true, ref.path); + if (options.noEmit) createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_may_not_disable_emit, ref.path); } - if (ref.prepend) { - const out = outFile(options); - if (out) { - if (!host.fileExists(out)) { - createDiagnosticForReference(parentFile, index, Diagnostics.Output_file_0_from_project_1_does_not_exist, out, ref.path); - } - } - else { - createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_prepend_project_0_because_it_does_not_have_outFile_set, ref.path); + } + if (ref.prepend) { + const out = outFile(options); + if (out) { + if (!host.fileExists(out)) { + createDiagnosticForReference(parentFile, index, Diagnostics.Output_file_0_from_project_1_does_not_exist, out, ref.path); } } - if (!parent && buildInfoPath && buildInfoPath === getTsBuildInfoEmitOutputFilePath(options)) { - createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, buildInfoPath, ref.path); - hasEmitBlockingDiagnostics.set(toPath(buildInfoPath), true); + else { + createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_prepend_project_0_because_it_does_not_have_outFile_set, ref.path); } - }); - } + } + if (!parent && buildInfoPath && buildInfoPath === getTsBuildInfoEmitOutputFilePath(options)) { + createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, buildInfoPath, ref.path); + hasEmitBlockingDiagnostics.set(toPath(buildInfoPath), true); + } + }); + } - function createDiagnosticForOptionPathKeyValue(key: string, valueIndex: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number) { - let needCompilerDiagnostic = true; - const pathsSyntax = getOptionPathsSyntax(); - for (const pathProp of pathsSyntax) { - if (isObjectLiteralExpression(pathProp.initializer)) { - for (const keyProps of getPropertyAssignment(pathProp.initializer, key)) { - const initializer = keyProps.initializer; - if (isArrayLiteralExpression(initializer) && initializer.elements.length > valueIndex) { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, initializer.elements[valueIndex], message, arg0, arg1, arg2)); - needCompilerDiagnostic = false; - } + function createDiagnosticForOptionPathKeyValue(key: string, valueIndex: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number) { + let needCompilerDiagnostic = true; + const pathsSyntax = getOptionPathsSyntax(); + for (const pathProp of pathsSyntax) { + if (isObjectLiteralExpression(pathProp.initializer)) { + for (const keyProps of getPropertyAssignment(pathProp.initializer, key)) { + const initializer = keyProps.initializer; + if (isArrayLiteralExpression(initializer) && initializer.elements.length > valueIndex) { + programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, initializer.elements[valueIndex], message, arg0, arg1, arg2)); + needCompilerDiagnostic = false; } } } + } - if (needCompilerDiagnostic) { - programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); - } + if (needCompilerDiagnostic) { + programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); } + } - function createDiagnosticForOptionPaths(onKey: boolean, key: string, message: DiagnosticMessage, arg0: string | number) { - let needCompilerDiagnostic = true; - const pathsSyntax = getOptionPathsSyntax(); - for (const pathProp of pathsSyntax) { - if (isObjectLiteralExpression(pathProp.initializer) && - createOptionDiagnosticInObjectLiteralSyntax( - pathProp.initializer, onKey, key, /*key2*/ undefined, - message, arg0)) { - needCompilerDiagnostic = false; - } - } - if (needCompilerDiagnostic) { - programDiagnostics.add(createCompilerDiagnostic(message, arg0)); + function createDiagnosticForOptionPaths(onKey: boolean, key: string, message: DiagnosticMessage, arg0: string | number) { + let needCompilerDiagnostic = true; + const pathsSyntax = getOptionPathsSyntax(); + for (const pathProp of pathsSyntax) { + if (isObjectLiteralExpression(pathProp.initializer) && + createOptionDiagnosticInObjectLiteralSyntax( + pathProp.initializer, onKey, key, /*key2*/ undefined, + message, arg0)) { + needCompilerDiagnostic = false; } } - - function getOptionsSyntaxByName(name: string) { - const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); - return compilerOptionsObjectLiteralSyntax && getPropertyAssignment(compilerOptionsObjectLiteralSyntax, name); + if (needCompilerDiagnostic) { + programDiagnostics.add(createCompilerDiagnostic(message, arg0)); } + } - function getOptionPathsSyntax() { - return getOptionsSyntaxByName("paths") || emptyArray; - } + function getOptionsSyntaxByName(name: string) { + const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); + return compilerOptionsObjectLiteralSyntax && getPropertyAssignment(compilerOptionsObjectLiteralSyntax, name); + } - function getOptionsSyntaxByValue(name: string, value: string) { - const syntaxByName = getOptionsSyntaxByName(name); - return syntaxByName && firstDefined(syntaxByName, property => isStringLiteral(property.initializer) && property.initializer.text === value ? property.initializer : undefined); - } + function getOptionPathsSyntax() { + return getOptionsSyntaxByName("paths") || emptyArray; + } - function getOptionsSyntaxByArrayElementValue(name: string, value: string) { - const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); - return compilerOptionsObjectLiteralSyntax && getPropertyArrayElementValue(compilerOptionsObjectLiteralSyntax, name, value); - } + function getOptionsSyntaxByValue(name: string, value: string) { + const syntaxByName = getOptionsSyntaxByName(name); + return syntaxByName && firstDefined(syntaxByName, property => isStringLiteral(property.initializer) && property.initializer.text === value ? property.initializer : undefined); + } - function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string, option3?: string) { - createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2, option3); - } + function getOptionsSyntaxByArrayElementValue(name: string, value: string) { + const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); + return compilerOptionsObjectLiteralSyntax && getPropertyArrayElementValue(compilerOptionsObjectLiteralSyntax, name, value); + } - function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0?: string, arg1?: string) { - createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0, arg1); - } + function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string, option3?: string) { + createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2, option3); + } - function createDiagnosticForReference(sourceFile: JsonSourceFile | undefined, index: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number) { - const referencesSyntax = firstDefined(getTsConfigPropArray(sourceFile || options.configFile, "references"), - property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined); - if (referencesSyntax && referencesSyntax.elements.length > index) { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(sourceFile || options.configFile!, referencesSyntax.elements[index], message, arg0, arg1)); - } - else { - programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1)); - } + function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0?: string, arg1?: string) { + createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0, arg1); + } + + function createDiagnosticForReference(sourceFile: JsonSourceFile | undefined, index: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number) { + const referencesSyntax = firstDefined(getTsConfigPropArray(sourceFile || options.configFile, "references"), + property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined); + if (referencesSyntax && referencesSyntax.elements.length > index) { + programDiagnostics.add(createDiagnosticForNodeInSourceFile(sourceFile || options.configFile!, referencesSyntax.elements[index], message, arg0, arg1)); + } + else { + programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1)); } + } - function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number) { - const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); - const needCompilerDiagnostic = !compilerOptionsObjectLiteralSyntax || - !createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, onKey, option1, option2, message, arg0, arg1, arg2); + function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number) { + const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); + const needCompilerDiagnostic = !compilerOptionsObjectLiteralSyntax || + !createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, onKey, option1, option2, message, arg0, arg1, arg2); - if (needCompilerDiagnostic) { - programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); - } + if (needCompilerDiagnostic) { + programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); } + } - function getCompilerOptionsObjectLiteralSyntax() { - if (_compilerOptionsObjectLiteralSyntax === undefined) { - _compilerOptionsObjectLiteralSyntax = false; - const jsonObjectLiteral = getTsConfigObjectLiteralExpression(options.configFile); - if (jsonObjectLiteral) { - for (const prop of getPropertyAssignment(jsonObjectLiteral, "compilerOptions")) { - if (isObjectLiteralExpression(prop.initializer)) { - _compilerOptionsObjectLiteralSyntax = prop.initializer; - break; - } + function getCompilerOptionsObjectLiteralSyntax() { + if (_compilerOptionsObjectLiteralSyntax === undefined) { + _compilerOptionsObjectLiteralSyntax = false; + const jsonObjectLiteral = getTsConfigObjectLiteralExpression(options.configFile); + if (jsonObjectLiteral) { + for (const prop of getPropertyAssignment(jsonObjectLiteral, "compilerOptions")) { + if (isObjectLiteralExpression(prop.initializer)) { + _compilerOptionsObjectLiteralSyntax = prop.initializer; + break; } } } - return _compilerOptionsObjectLiteralSyntax || undefined; - } - - function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): boolean { - const props = getPropertyAssignment(objectLiteral, key1, key2); - for (const prop of props) { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, onKey ? prop.name : prop.initializer, message, arg0, arg1, arg2)); - } - return !!props.length; } + return _compilerOptionsObjectLiteralSyntax || undefined; + } - function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) { - hasEmitBlockingDiagnostics.set(toPath(emitFileName), true); - programDiagnostics.add(diag); + function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): boolean { + const props = getPropertyAssignment(objectLiteral, key1, key2); + for (const prop of props) { + programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, onKey ? prop.name : prop.initializer, message, arg0, arg1, arg2)); } + return !!props.length; + } - function isEmittedFile(file: string): boolean { - if (options.noEmit) { - return false; - } - - // If this is source file, its not emitted file - const filePath = toPath(file); - if (getSourceFileByPath(filePath)) { - return false; - } + function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) { + hasEmitBlockingDiagnostics.set(toPath(emitFileName), true); + programDiagnostics.add(diag); + } - // If options have --outFile or --out just check that - const out = outFile(options); - if (out) { - return isSameFile(filePath, out) || isSameFile(filePath, removeFileExtension(out) + Extension.Dts); - } + function isEmittedFile(file: string): boolean { + if (options.noEmit) { + return false; + } - // If declarationDir is specified, return if its a file in that directory - if (options.declarationDir && containsPath(options.declarationDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames())) { - return true; - } + // If this is source file, its not emitted file + const filePath = toPath(file); + if (getSourceFileByPath(filePath)) { + return false; + } - // If --outDir, check if file is in that directory - if (options.outDir) { - return containsPath(options.outDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames()); - } + // If options have --outFile or --out just check that + const out = outFile(options); + if (out) { + return isSameFile(filePath, out) || isSameFile(filePath, removeFileExtension(out) + Extension.Dts); + } - if (fileExtensionIsOneOf(filePath, supportedJSExtensionsFlat) || fileExtensionIs(filePath, Extension.Dts)) { - // Otherwise just check if sourceFile with the name exists - const filePathWithoutExtension = removeFileExtension(filePath); - return !!getSourceFileByPath((filePathWithoutExtension + Extension.Ts) as Path) || - !!getSourceFileByPath((filePathWithoutExtension + Extension.Tsx) as Path); - } - return false; + // If declarationDir is specified, return if its a file in that directory + if (options.declarationDir && containsPath(options.declarationDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames())) { + return true; } - function isSameFile(file1: string, file2: string) { - return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + // If --outDir, check if file is in that directory + if (options.outDir) { + return containsPath(options.outDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames()); } - function getSymlinkCache(): SymlinkCache { - if (host.getSymlinkCache) { - return host.getSymlinkCache(); - } - if (!symlinks) { - symlinks = createSymlinkCache(currentDirectory, getCanonicalFileName); - } - if (files && resolvedTypeReferenceDirectives && !symlinks.hasProcessedResolutions()) { - symlinks.setSymlinksFromResolutions(files, resolvedTypeReferenceDirectives); - } - return symlinks; + if (fileExtensionIsOneOf(filePath, supportedJSExtensionsFlat) || fileExtensionIs(filePath, Extension.Dts)) { + // Otherwise just check if sourceFile with the name exists + const filePathWithoutExtension = removeFileExtension(filePath); + return !!getSourceFileByPath((filePathWithoutExtension + Extension.Ts) as Path) || + !!getSourceFileByPath((filePathWithoutExtension + Extension.Tsx) as Path); } + return false; } - interface HostForUseSourceOfProjectReferenceRedirect { - compilerHost: CompilerHost; - getSymlinkCache: () => SymlinkCache; - useSourceOfProjectReferenceRedirect: boolean; - toPath(fileName: string): Path; - getResolvedProjectReferences(): readonly (ResolvedProjectReference | undefined)[] | undefined; - getSourceOfProjectReferenceRedirect(path: Path): SourceOfProjectReferenceRedirect | undefined; - forEachResolvedProjectReference(cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined): T | undefined; + function isSameFile(file1: string, file2: string) { + return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; } - function updateHostForUseSourceOfProjectReferenceRedirect(host: HostForUseSourceOfProjectReferenceRedirect) { - let setOfDeclarationDirectories: Set | undefined; - const originalFileExists = host.compilerHost.fileExists; - const originalDirectoryExists = host.compilerHost.directoryExists; - const originalGetDirectories = host.compilerHost.getDirectories; - const originalRealpath = host.compilerHost.realpath; - - if (!host.useSourceOfProjectReferenceRedirect) return { onProgramCreateComplete: noop, fileExists }; - - host.compilerHost.fileExists = fileExists; - - let directoryExists; - if (originalDirectoryExists) { - // This implementation of directoryExists checks if the directory being requested is - // directory of .d.ts file for the referenced Project. - // If it is it returns true irrespective of whether that directory exists on host - directoryExists = host.compilerHost.directoryExists = path => { - if (originalDirectoryExists.call(host.compilerHost, path)) { - handleDirectoryCouldBeSymlink(path); - return true; - } - - if (!host.getResolvedProjectReferences()) return false; - - if (!setOfDeclarationDirectories) { - setOfDeclarationDirectories = new Set(); - host.forEachResolvedProjectReference(ref => { - const out = outFile(ref.commandLine.options); - if (out) { - setOfDeclarationDirectories!.add(getDirectoryPath(host.toPath(out))); - } - else { - // Set declaration's in different locations only, if they are next to source the directory present doesnt change - const declarationDir = ref.commandLine.options.declarationDir || ref.commandLine.options.outDir; - if (declarationDir) { - setOfDeclarationDirectories!.add(host.toPath(declarationDir)); - } - } - }); - } - - return fileOrDirectoryExistsUsingSource(path, /*isFile*/ false); - }; + function getSymlinkCache(): SymlinkCache { + if (host.getSymlinkCache) { + return host.getSymlinkCache(); } - - if (originalGetDirectories) { - // Call getDirectories only if directory actually present on the host - // This is needed to ensure that we arent getting directories that we fake about presence for - host.compilerHost.getDirectories = path => - !host.getResolvedProjectReferences() || (originalDirectoryExists && originalDirectoryExists.call(host.compilerHost, path)) ? - originalGetDirectories.call(host.compilerHost, path) : - []; + if (!symlinks) { + symlinks = createSymlinkCache(currentDirectory, getCanonicalFileName); } - - // This is something we keep for life time of the host - if (originalRealpath) { - host.compilerHost.realpath = s => - host.getSymlinkCache().getSymlinkedFiles()?.get(host.toPath(s)) || - originalRealpath.call(host.compilerHost, s); + if (files && resolvedTypeReferenceDirectives && !symlinks.hasProcessedResolutions()) { + symlinks.setSymlinksFromResolutions(files, resolvedTypeReferenceDirectives); } + return symlinks; + } +} - return { onProgramCreateComplete, fileExists, directoryExists }; +interface HostForUseSourceOfProjectReferenceRedirect { + compilerHost: CompilerHost; + getSymlinkCache: () => SymlinkCache; + useSourceOfProjectReferenceRedirect: boolean; + toPath(fileName: string): Path; + getResolvedProjectReferences(): readonly (ResolvedProjectReference | undefined)[] | undefined; + getSourceOfProjectReferenceRedirect(path: Path): SourceOfProjectReferenceRedirect | undefined; + forEachResolvedProjectReference(cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined): T | undefined; +} - function onProgramCreateComplete() { - host.compilerHost.fileExists = originalFileExists; - host.compilerHost.directoryExists = originalDirectoryExists; - host.compilerHost.getDirectories = originalGetDirectories; - // DO not revert realpath as it could be used later - } +function updateHostForUseSourceOfProjectReferenceRedirect(host: HostForUseSourceOfProjectReferenceRedirect) { + let setOfDeclarationDirectories: Set | undefined; + const originalFileExists = host.compilerHost.fileExists; + const originalDirectoryExists = host.compilerHost.directoryExists; + const originalGetDirectories = host.compilerHost.getDirectories; + const originalRealpath = host.compilerHost.realpath; + + if (!host.useSourceOfProjectReferenceRedirect) return { onProgramCreateComplete: noop, fileExists }; + + host.compilerHost.fileExists = fileExists; + + let directoryExists; + if (originalDirectoryExists) { + // This implementation of directoryExists checks if the directory being requested is + // directory of .d.ts file for the referenced Project. + // If it is it returns true irrespective of whether that directory exists on host + directoryExists = host.compilerHost.directoryExists = path => { + if (originalDirectoryExists.call(host.compilerHost, path)) { + handleDirectoryCouldBeSymlink(path); + return true; + } - // This implementation of fileExists checks if the file being requested is - // .d.ts file for the referenced Project. - // If it is it returns true irrespective of whether that file exists on host - function fileExists(file: string) { - if (originalFileExists.call(host.compilerHost, file)) return true; if (!host.getResolvedProjectReferences()) return false; - if (!isDeclarationFileName(file)) return false; - // Project references go to source file instead of .d.ts file - return fileOrDirectoryExistsUsingSource(file, /*isFile*/ true); - } + if (!setOfDeclarationDirectories) { + setOfDeclarationDirectories = new Set(); + host.forEachResolvedProjectReference(ref => { + const out = outFile(ref.commandLine.options); + if (out) { + setOfDeclarationDirectories!.add(getDirectoryPath(host.toPath(out))); + } + else { + // Set declaration's in different locations only, if they are next to source the directory present doesnt change + const declarationDir = ref.commandLine.options.declarationDir || ref.commandLine.options.outDir; + if (declarationDir) { + setOfDeclarationDirectories!.add(host.toPath(declarationDir)); + } + } + }); + } - function fileExistsIfProjectReferenceDts(file: string) { - const source = host.getSourceOfProjectReferenceRedirect(host.toPath(file)); - return source !== undefined ? - isString(source) ? originalFileExists.call(host.compilerHost, source) as boolean : true : - undefined; - } + return fileOrDirectoryExistsUsingSource(path, /*isFile*/ false); + }; + } - function directoryExistsIfProjectReferenceDeclDir(dir: string) { - const dirPath = host.toPath(dir); - const dirPathWithTrailingDirectorySeparator = `${dirPath}${directorySeparator}`; - return forEachKey( - setOfDeclarationDirectories!, - declDirPath => dirPath === declDirPath || - // Any parent directory of declaration dir - startsWith(declDirPath, dirPathWithTrailingDirectorySeparator) || - // Any directory inside declaration dir - startsWith(dirPath, `${declDirPath}/`) - ); - } + if (originalGetDirectories) { + // Call getDirectories only if directory actually present on the host + // This is needed to ensure that we arent getting directories that we fake about presence for + host.compilerHost.getDirectories = path => + !host.getResolvedProjectReferences() || (originalDirectoryExists && originalDirectoryExists.call(host.compilerHost, path)) ? + originalGetDirectories.call(host.compilerHost, path) : + []; + } - function handleDirectoryCouldBeSymlink(directory: string) { - if (!host.getResolvedProjectReferences() || containsIgnoredPath(directory)) return; + // This is something we keep for life time of the host + if (originalRealpath) { + host.compilerHost.realpath = s => + host.getSymlinkCache().getSymlinkedFiles()?.get(host.toPath(s)) || + originalRealpath.call(host.compilerHost, s); + } - // Because we already watch node_modules, handle symlinks in there - if (!originalRealpath || !stringContains(directory, nodeModulesPathPart)) return; - const symlinkCache = host.getSymlinkCache(); - const directoryPath = ensureTrailingDirectorySeparator(host.toPath(directory)); - if (symlinkCache.getSymlinkedDirectories()?.has(directoryPath)) return; + return { onProgramCreateComplete, fileExists, directoryExists }; - const real = normalizePath(originalRealpath.call(host.compilerHost, directory)); - let realPath: Path; - if (real === directory || - (realPath = ensureTrailingDirectorySeparator(host.toPath(real))) === directoryPath) { - // not symlinked - symlinkCache.setSymlinkedDirectory(directoryPath, false); - return; - } + function onProgramCreateComplete() { + host.compilerHost.fileExists = originalFileExists; + host.compilerHost.directoryExists = originalDirectoryExists; + host.compilerHost.getDirectories = originalGetDirectories; + // DO not revert realpath as it could be used later + } - symlinkCache.setSymlinkedDirectory(directory, { - real: ensureTrailingDirectorySeparator(real), - realPath - }); - } + // This implementation of fileExists checks if the file being requested is + // .d.ts file for the referenced Project. + // If it is it returns true irrespective of whether that file exists on host + function fileExists(file: string) { + if (originalFileExists.call(host.compilerHost, file)) return true; + if (!host.getResolvedProjectReferences()) return false; + if (!isDeclarationFileName(file)) return false; - function fileOrDirectoryExistsUsingSource(fileOrDirectory: string, isFile: boolean): boolean { - const fileOrDirectoryExistsUsingSource = isFile ? - (file: string) => fileExistsIfProjectReferenceDts(file) : - (dir: string) => directoryExistsIfProjectReferenceDeclDir(dir); - // Check current directory or file - const result = fileOrDirectoryExistsUsingSource(fileOrDirectory); - if (result !== undefined) return result; - - const symlinkCache = host.getSymlinkCache(); - const symlinkedDirectories = symlinkCache.getSymlinkedDirectories(); - if (!symlinkedDirectories) return false; - const fileOrDirectoryPath = host.toPath(fileOrDirectory); - if (!stringContains(fileOrDirectoryPath, nodeModulesPathPart)) return false; - if (isFile && symlinkCache.getSymlinkedFiles()?.has(fileOrDirectoryPath)) return true; - - // If it contains node_modules check if its one of the symlinked path we know of - return firstDefinedIterator( - symlinkedDirectories.entries(), - ([directoryPath, symlinkedDirectory]) => { - if (!symlinkedDirectory || !startsWith(fileOrDirectoryPath, directoryPath)) return undefined; - const result = fileOrDirectoryExistsUsingSource(fileOrDirectoryPath.replace(directoryPath, symlinkedDirectory.realPath)); - if (isFile && result) { - // Store the real path for the file' - const absolutePath = getNormalizedAbsolutePath(fileOrDirectory, host.compilerHost.getCurrentDirectory()); - symlinkCache.setSymlinkedFile( - fileOrDirectoryPath, - `${symlinkedDirectory.real}${absolutePath.replace(new RegExp(directoryPath, "i"), "")}` - ); - } - return result; - } - ) || false; - } + // Project references go to source file instead of .d.ts file + return fileOrDirectoryExistsUsingSource(file, /*isFile*/ true); } - /*@internal*/ - export const emitSkippedWithNoDiagnostics: EmitResult = { diagnostics: emptyArray, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true }; - - /*@internal*/ - export function handleNoEmitOptions( - program: Program | T, - sourceFile: SourceFile | undefined, - writeFile: WriteFileCallback | undefined, - cancellationToken: CancellationToken | undefined - ): EmitResult | undefined { - const options = program.getCompilerOptions(); - if (options.noEmit) { - // Cache the semantic diagnostics - program.getSemanticDiagnostics(sourceFile, cancellationToken); - return sourceFile || outFile(options) ? - emitSkippedWithNoDiagnostics : - program.emitBuildInfo(writeFile, cancellationToken); - } + function fileExistsIfProjectReferenceDts(file: string) { + const source = host.getSourceOfProjectReferenceRedirect(host.toPath(file)); + return source !== undefined ? + isString(source) ? originalFileExists.call(host.compilerHost, source) as boolean : true : + undefined; + } - // If the noEmitOnError flag is set, then check if we have any errors so far. If so, - // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we - // get any preEmit diagnostics, not just the ones - if (!options.noEmitOnError) return undefined; - let diagnostics: readonly Diagnostic[] = [ - ...program.getOptionsDiagnostics(cancellationToken), - ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), - ...program.getGlobalDiagnostics(cancellationToken), - ...program.getSemanticDiagnostics(sourceFile, cancellationToken) - ]; + function directoryExistsIfProjectReferenceDeclDir(dir: string) { + const dirPath = host.toPath(dir); + const dirPathWithTrailingDirectorySeparator = `${dirPath}${directorySeparator}`; + return forEachKey( + setOfDeclarationDirectories!, + declDirPath => dirPath === declDirPath || + // Any parent directory of declaration dir + startsWith(declDirPath, dirPathWithTrailingDirectorySeparator) || + // Any directory inside declaration dir + startsWith(dirPath, `${declDirPath}/`) + ); + } - if (diagnostics.length === 0 && getEmitDeclarations(program.getCompilerOptions())) { - diagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken); + function handleDirectoryCouldBeSymlink(directory: string) { + if (!host.getResolvedProjectReferences() || containsIgnoredPath(directory)) return; + + // Because we already watch node_modules, handle symlinks in there + if (!originalRealpath || !stringContains(directory, nodeModulesPathPart)) return; + const symlinkCache = host.getSymlinkCache(); + const directoryPath = ensureTrailingDirectorySeparator(host.toPath(directory)); + if (symlinkCache.getSymlinkedDirectories()?.has(directoryPath)) return; + + const real = normalizePath(originalRealpath.call(host.compilerHost, directory)); + let realPath: Path; + if (real === directory || + (realPath = ensureTrailingDirectorySeparator(host.toPath(real))) === directoryPath) { + // not symlinked + symlinkCache.setSymlinkedDirectory(directoryPath, false); + return; } - if (!diagnostics.length) return undefined; - let emittedFiles: string[] | undefined; - if (!sourceFile && !outFile(options)) { - const emitResult = program.emitBuildInfo(writeFile, cancellationToken); - if (emitResult.diagnostics) diagnostics = [...diagnostics, ...emitResult.diagnostics]; - emittedFiles = emitResult.emittedFiles; - } - return { diagnostics, sourceMaps: undefined, emittedFiles, emitSkipped: true }; + symlinkCache.setSymlinkedDirectory(directory, { + real: ensureTrailingDirectorySeparator(real), + realPath + }); } - /*@internal*/ - export function filterSemanticDiagnostics(diagnostic: readonly Diagnostic[], option: CompilerOptions): readonly Diagnostic[] { - return filter(diagnostic, d => !d.skippedOn || !option[d.skippedOn]); + function fileOrDirectoryExistsUsingSource(fileOrDirectory: string, isFile: boolean): boolean { + const fileOrDirectoryExistsUsingSource = isFile ? + (file: string) => fileExistsIfProjectReferenceDts(file) : + (dir: string) => directoryExistsIfProjectReferenceDeclDir(dir); + // Check current directory or file + const result = fileOrDirectoryExistsUsingSource(fileOrDirectory); + if (result !== undefined) return result; + + const symlinkCache = host.getSymlinkCache(); + const symlinkedDirectories = symlinkCache.getSymlinkedDirectories(); + if (!symlinkedDirectories) return false; + const fileOrDirectoryPath = host.toPath(fileOrDirectory); + if (!stringContains(fileOrDirectoryPath, nodeModulesPathPart)) return false; + if (isFile && symlinkCache.getSymlinkedFiles()?.has(fileOrDirectoryPath)) return true; + + // If it contains node_modules check if its one of the symlinked path we know of + return firstDefinedIterator( + symlinkedDirectories.entries(), + ([directoryPath, symlinkedDirectory]) => { + if (!symlinkedDirectory || !startsWith(fileOrDirectoryPath, directoryPath)) return undefined; + const result = fileOrDirectoryExistsUsingSource(fileOrDirectoryPath.replace(directoryPath, symlinkedDirectory.realPath)); + if (isFile && result) { + // Store the real path for the file' + const absolutePath = getNormalizedAbsolutePath(fileOrDirectory, host.compilerHost.getCurrentDirectory()); + symlinkCache.setSymlinkedFile( + fileOrDirectoryPath, + `${symlinkedDirectory.real}${absolutePath.replace(new RegExp(directoryPath, "i"), "")}` + ); + } + return result; + } + ) || false; } +} - /*@internal*/ - interface CompilerHostLike { - useCaseSensitiveFileNames(): boolean; - getCurrentDirectory(): string; - fileExists(fileName: string): boolean; - readFile(fileName: string): string | undefined; - readDirectory?(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[], depth?: number): string[]; - trace?(s: string): void; - onUnRecoverableConfigFileDiagnostic?: DiagnosticReporter; +/*@internal*/ +export const emitSkippedWithNoDiagnostics: EmitResult = { diagnostics: emptyArray, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true }; + +/*@internal*/ +export function handleNoEmitOptions( + program: Program | T, + sourceFile: SourceFile | undefined, + writeFile: WriteFileCallback | undefined, + cancellationToken: CancellationToken | undefined +): EmitResult | undefined { + const options = program.getCompilerOptions(); + if (options.noEmit) { + // Cache the semantic diagnostics + program.getSemanticDiagnostics(sourceFile, cancellationToken); + return sourceFile || outFile(options) ? + emitSkippedWithNoDiagnostics : + program.emitBuildInfo(writeFile, cancellationToken); } - /* @internal */ - export function parseConfigHostFromCompilerHostLike(host: CompilerHostLike, directoryStructureHost: DirectoryStructureHost = host): ParseConfigFileHost { - return { - fileExists: f => directoryStructureHost.fileExists(f), - readDirectory(root, extensions, excludes, includes, depth) { - Debug.assertIsDefined(directoryStructureHost.readDirectory, "'CompilerHost.readDirectory' must be implemented to correctly process 'projectReferences'"); - return directoryStructureHost.readDirectory(root, extensions, excludes, includes, depth); - }, - readFile: f => directoryStructureHost.readFile(f), - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), - getCurrentDirectory: () => host.getCurrentDirectory(), - onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic || returnUndefined, - trace: host.trace ? (s) => host.trace!(s) : undefined - }; + // If the noEmitOnError flag is set, then check if we have any errors so far. If so, + // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we + // get any preEmit diagnostics, not just the ones + if (!options.noEmitOnError) return undefined; + let diagnostics: readonly Diagnostic[] = [ + ...program.getOptionsDiagnostics(cancellationToken), + ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), + ...program.getGlobalDiagnostics(cancellationToken), + ...program.getSemanticDiagnostics(sourceFile, cancellationToken) + ]; + + if (diagnostics.length === 0 && getEmitDeclarations(program.getCompilerOptions())) { + diagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken); } - // For backward compatibility - /** @deprecated */ export interface ResolveProjectReferencePathHost { - fileExists(fileName: string): boolean; + if (!diagnostics.length) return undefined; + let emittedFiles: string[] | undefined; + if (!sourceFile && !outFile(options)) { + const emitResult = program.emitBuildInfo(writeFile, cancellationToken); + if (emitResult.diagnostics) diagnostics = [...diagnostics, ...emitResult.diagnostics]; + emittedFiles = emitResult.emittedFiles; } + return { diagnostics, sourceMaps: undefined, emittedFiles, emitSkipped: true }; +} + +/*@internal*/ +export function filterSemanticDiagnostics(diagnostic: readonly Diagnostic[], option: CompilerOptions): readonly Diagnostic[] { + return filter(diagnostic, d => !d.skippedOn || !option[d.skippedOn]); +} - /* @internal */ - export function createPrependNodes(projectReferences: readonly ProjectReference[] | undefined, getCommandLine: (ref: ProjectReference, index: number) => ParsedCommandLine | undefined, readFile: (path: string) => string | undefined) { - if (!projectReferences) return emptyArray; - let nodes: InputFiles[] | undefined; - for (let i = 0; i < projectReferences.length; i++) { - const ref = projectReferences[i]; - const resolvedRefOpts = getCommandLine(ref, i); - if (ref.prepend && resolvedRefOpts && resolvedRefOpts.options) { - const out = outFile(resolvedRefOpts.options); - // Upstream project didn't have outFile set -- skip (error will have been issued earlier) - if (!out) continue; +/*@internal*/ +interface CompilerHostLike { + useCaseSensitiveFileNames(): boolean; + getCurrentDirectory(): string; + fileExists(fileName: string): boolean; + readFile(fileName: string): string | undefined; + readDirectory?(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[], depth?: number): string[]; + trace?(s: string): void; + onUnRecoverableConfigFileDiagnostic?: DiagnosticReporter; +} - const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(resolvedRefOpts.options, /*forceDtsPaths*/ true); - const node = createInputFiles(readFile, jsFilePath!, sourceMapFilePath, declarationFilePath!, declarationMapPath, buildInfoPath); - (nodes || (nodes = [])).push(node); - } +/* @internal */ +export function parseConfigHostFromCompilerHostLike(host: CompilerHostLike, directoryStructureHost: DirectoryStructureHost = host): ParseConfigFileHost { + return { + fileExists: f => directoryStructureHost.fileExists(f), + readDirectory(root, extensions, excludes, includes, depth) { + Debug.assertIsDefined(directoryStructureHost.readDirectory, "'CompilerHost.readDirectory' must be implemented to correctly process 'projectReferences'"); + return directoryStructureHost.readDirectory(root, extensions, excludes, includes, depth); + }, + readFile: f => directoryStructureHost.readFile(f), + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), + getCurrentDirectory: () => host.getCurrentDirectory(), + onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic || returnUndefined, + trace: host.trace ? (s) => host.trace!(s) : undefined + }; +} + +// For backward compatibility +/** @deprecated */ export interface ResolveProjectReferencePathHost { + fileExists(fileName: string): boolean; +} + +/* @internal */ +export function createPrependNodes(projectReferences: readonly ProjectReference[] | undefined, getCommandLine: (ref: ProjectReference, index: number) => ParsedCommandLine | undefined, readFile: (path: string) => string | undefined) { + if (!projectReferences) return emptyArray; + let nodes: InputFiles[] | undefined; + for (let i = 0; i < projectReferences.length; i++) { + const ref = projectReferences[i]; + const resolvedRefOpts = getCommandLine(ref, i); + if (ref.prepend && resolvedRefOpts && resolvedRefOpts.options) { + const out = outFile(resolvedRefOpts.options); + // Upstream project didn't have outFile set -- skip (error will have been issued earlier) + if (!out) continue; + + const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(resolvedRefOpts.options, /*forceDtsPaths*/ true); + const node = createInputFiles(readFile, jsFilePath!, sourceMapFilePath, declarationFilePath!, declarationMapPath, buildInfoPath); + (nodes || (nodes = [])).push(node); } - return nodes || emptyArray; - } - /** - * Returns the target config filename of a project reference. - * Note: The file might not exist. - */ - export function resolveProjectReferencePath(ref: ProjectReference): ResolvedConfigFileName; - /** @deprecated */ export function resolveProjectReferencePath(host: ResolveProjectReferencePathHost, ref: ProjectReference): ResolvedConfigFileName; - export function resolveProjectReferencePath(hostOrRef: ResolveProjectReferencePathHost | ProjectReference, ref?: ProjectReference): ResolvedConfigFileName { - const passedInRef = ref ? ref : hostOrRef as ProjectReference; - return resolveConfigFileProjectName(passedInRef.path); } + return nodes || emptyArray; +} +/** + * Returns the target config filename of a project reference. + * Note: The file might not exist. + */ +export function resolveProjectReferencePath(ref: ProjectReference): ResolvedConfigFileName; +/** @deprecated */ export function resolveProjectReferencePath(host: ResolveProjectReferencePathHost, ref: ProjectReference): ResolvedConfigFileName; +export function resolveProjectReferencePath(hostOrRef: ResolveProjectReferencePathHost | ProjectReference, ref?: ProjectReference): ResolvedConfigFileName { + const passedInRef = ref ? ref : hostOrRef as ProjectReference; + return resolveConfigFileProjectName(passedInRef.path); +} - /* @internal */ - /** - * Returns a DiagnosticMessage if we won't include a resolved module due to its extension. - * The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to. - * This returns a diagnostic even if the module will be an untyped module. - */ - export function getResolutionDiagnostic(options: CompilerOptions, { extension }: ResolvedModuleFull): DiagnosticMessage | undefined { - switch (extension) { - case Extension.Ts: - case Extension.Dts: - // These are always allowed. - return undefined; - case Extension.Tsx: - return needJsx(); - case Extension.Jsx: - return needJsx() || needAllowJs(); - case Extension.Js: - return needAllowJs(); - case Extension.Json: - return needResolveJsonModule(); - } +/* @internal */ +/** + * Returns a DiagnosticMessage if we won't include a resolved module due to its extension. + * The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to. + * This returns a diagnostic even if the module will be an untyped module. + */ +export function getResolutionDiagnostic(options: CompilerOptions, { extension }: ResolvedModuleFull): DiagnosticMessage | undefined { + switch (extension) { + case Extension.Ts: + case Extension.Dts: + // These are always allowed. + return undefined; + case Extension.Tsx: + return needJsx(); + case Extension.Jsx: + return needJsx() || needAllowJs(); + case Extension.Js: + return needAllowJs(); + case Extension.Json: + return needResolveJsonModule(); + } - function needJsx() { - return options.jsx ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set; - } - function needAllowJs() { - return getAllowJSCompilerOption(options) || !getStrictOptionValue(options, "noImplicitAny") ? undefined : Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type; - } - function needResolveJsonModule() { - return options.resolveJsonModule ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used; - } + function needJsx() { + return options.jsx ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set; + } + function needAllowJs() { + return getAllowJSCompilerOption(options) || !getStrictOptionValue(options, "noImplicitAny") ? undefined : Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type; } + function needResolveJsonModule() { + return options.resolveJsonModule ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used; + } +} - function getModuleNames({ imports, moduleAugmentations }: SourceFile): string[] { - const res = imports.map(i => i.text); - for (const aug of moduleAugmentations) { - if (aug.kind === SyntaxKind.StringLiteral) { - res.push(aug.text); - } - // Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`. +function getModuleNames({ imports, moduleAugmentations }: SourceFile): string[] { + const res = imports.map(i => i.text); + for (const aug of moduleAugmentations) { + if (aug.kind === SyntaxKind.StringLiteral) { + res.push(aug.text); } - return res; + // Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`. } + return res; +} - /* @internal */ - export function getModuleNameStringLiteralAt({ imports, moduleAugmentations }: SourceFileImportsList, index: number): StringLiteralLike { - if (index < imports.length) return imports[index]; - let augIndex = imports.length; - for (const aug of moduleAugmentations) { - if (aug.kind === SyntaxKind.StringLiteral) { - if (index === augIndex) return aug; - augIndex++; - } - // Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`. +/* @internal */ +export function getModuleNameStringLiteralAt({ imports, moduleAugmentations }: SourceFileImportsList, index: number): StringLiteralLike { + if (index < imports.length) return imports[index]; + let augIndex = imports.length; + for (const aug of moduleAugmentations) { + if (aug.kind === SyntaxKind.StringLiteral) { + if (index === augIndex) return aug; + augIndex++; } - Debug.fail("should never ask for module name at index higher than possible module name"); + // Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`. } + Debug.fail("should never ask for module name at index higher than possible module name"); +} } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index e06d81ca4c9aa..b7c1119d79ae5 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -1,979 +1,979 @@ /*@internal*/ namespace ts { - /** This is the cache of module/typedirectives resolution that can be retained across program */ - export interface ResolutionCache { - startRecordingFilesWithChangedResolutions(): void; - finishRecordingFilesWithChangedResolutions(): Path[] | undefined; +/** This is the cache of module/typedirectives resolution that can be retained across program */ +export interface ResolutionCache { + startRecordingFilesWithChangedResolutions(): void; + finishRecordingFilesWithChangedResolutions(): Path[] | undefined; - resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[]; - getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined; - resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[]; + resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[]; + getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined; + resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[]; - invalidateResolutionsOfFailedLookupLocations(): boolean; - invalidateResolutionOfFile(filePath: Path): void; - removeResolutionsOfFile(filePath: Path): void; - removeResolutionsFromProjectReferenceRedirects(filePath: Path): void; - setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: ESMap): void; - createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution; - hasChangedAutomaticTypeDirectiveNames(): boolean; - isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean; + invalidateResolutionsOfFailedLookupLocations(): boolean; + invalidateResolutionOfFile(filePath: Path): void; + removeResolutionsOfFile(filePath: Path): void; + removeResolutionsFromProjectReferenceRedirects(filePath: Path): void; + setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: ESMap): void; + createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution; + hasChangedAutomaticTypeDirectiveNames(): boolean; + isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean; - startCachingPerDirectoryResolution(): void; - finishCachingPerDirectoryResolution(): void; + startCachingPerDirectoryResolution(): void; + finishCachingPerDirectoryResolution(): void; - updateTypeRootsWatch(): void; - closeTypeRootsWatch(): void; + updateTypeRootsWatch(): void; + closeTypeRootsWatch(): void; - getModuleResolutionCache(): ModuleResolutionCache; + getModuleResolutionCache(): ModuleResolutionCache; - clear(): void; - } + clear(): void; +} - interface ResolutionWithFailedLookupLocations { - readonly failedLookupLocations: string[]; - isInvalidated?: boolean; - refCount?: number; - // Files that have this resolution using - files?: Path[]; - } +interface ResolutionWithFailedLookupLocations { + readonly failedLookupLocations: string[]; + isInvalidated?: boolean; + refCount?: number; + // Files that have this resolution using + files?: Path[]; +} - interface ResolutionWithResolvedFileName { - resolvedFileName: string | undefined; - packagetId?: PackageId; - } +interface ResolutionWithResolvedFileName { + resolvedFileName: string | undefined; + packagetId?: PackageId; +} - interface CachedResolvedModuleWithFailedLookupLocations extends ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations { - } +interface CachedResolvedModuleWithFailedLookupLocations extends ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations { +} - interface CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations { - } +interface CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations { +} - export interface ResolutionCacheHost extends ModuleResolutionHost { - toPath(fileName: string): Path; - getCanonicalFileName: GetCanonicalFileName; - getCompilationSettings(): CompilerOptions; - watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; - onInvalidatedResolution(): void; - watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; - onChangedAutomaticTypeDirectiveNames(): void; - scheduleInvalidateResolutionsOfFailedLookupLocations(): void; - getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined; - projectName?: string; - getGlobalCache?(): string | undefined; - globalCacheResolutionModuleName?(externalModuleName: string): string; - writeLog(s: string): void; - getCurrentProgram(): Program | undefined; - fileIsOpen(filePath: Path): boolean; - getCompilerHost?(): CompilerHost | undefined; - onDiscoveredSymlink?(): void; - } +export interface ResolutionCacheHost extends ModuleResolutionHost { + toPath(fileName: string): Path; + getCanonicalFileName: GetCanonicalFileName; + getCompilationSettings(): CompilerOptions; + watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; + onInvalidatedResolution(): void; + watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; + onChangedAutomaticTypeDirectiveNames(): void; + scheduleInvalidateResolutionsOfFailedLookupLocations(): void; + getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined; + projectName?: string; + getGlobalCache?(): string | undefined; + globalCacheResolutionModuleName?(externalModuleName: string): string; + writeLog(s: string): void; + getCurrentProgram(): Program | undefined; + fileIsOpen(filePath: Path): boolean; + getCompilerHost?(): CompilerHost | undefined; + onDiscoveredSymlink?(): void; +} - interface DirectoryWatchesOfFailedLookup { - /** watcher for the directory of failed lookup */ - watcher: FileWatcher; - /** ref count keeping this directory watch alive */ - refCount: number; - /** is the directory watched being non recursive */ - nonRecursive?: boolean; - } +interface DirectoryWatchesOfFailedLookup { + /** watcher for the directory of failed lookup */ + watcher: FileWatcher; + /** ref count keeping this directory watch alive */ + refCount: number; + /** is the directory watched being non recursive */ + nonRecursive?: boolean; +} + +interface DirectoryOfFailedLookupWatch { + dir: string; + dirPath: Path; + nonRecursive?: boolean; +} - interface DirectoryOfFailedLookupWatch { - dir: string; - dirPath: Path; - nonRecursive?: boolean; +export function removeIgnoredPath(path: Path): Path | undefined { + // Consider whole staging folder as if node_modules changed. + if (endsWith(path, "/node_modules/.staging")) { + return removeSuffix(path, "/.staging") as Path; } - export function removeIgnoredPath(path: Path): Path | undefined { - // Consider whole staging folder as if node_modules changed. - if (endsWith(path, "/node_modules/.staging")) { - return removeSuffix(path, "/.staging") as Path; - } + return some(ignoredPaths, searchPath => stringContains(path, searchPath)) ? + undefined : + path; +} - return some(ignoredPaths, searchPath => stringContains(path, searchPath)) ? - undefined : - path; +/** + * Filter out paths like + * "/", "/user", "/user/username", "/user/username/folderAtRoot", + * "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot" + * @param dirPath + */ +export function canWatchDirectory(dirPath: Path) { + const rootLength = getRootLength(dirPath); + if (dirPath.length === rootLength) { + // Ignore "/", "c:/" + return false; } - /** - * Filter out paths like - * "/", "/user", "/user/username", "/user/username/folderAtRoot", - * "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot" - * @param dirPath - */ - export function canWatchDirectory(dirPath: Path) { - const rootLength = getRootLength(dirPath); - if (dirPath.length === rootLength) { - // Ignore "/", "c:/" - return false; - } + let nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength); + if (nextDirectorySeparator === -1) { + // ignore "/user", "c:/users" or "c:/folderAtRoot" + return false; + } - let nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength); + let pathPartForUserCheck = dirPath.substring(rootLength, nextDirectorySeparator + 1); + const isNonDirectorySeparatorRoot = rootLength > 1 || dirPath.charCodeAt(0) !== CharacterCodes.slash; + if (isNonDirectorySeparatorRoot && + dirPath.search(/[a-zA-Z]:/) !== 0 && // Non dos style paths + pathPartForUserCheck.search(/[a-zA-z]\$\//) === 0) { // Dos style nextPart + nextDirectorySeparator = dirPath.indexOf(directorySeparator, nextDirectorySeparator + 1); if (nextDirectorySeparator === -1) { - // ignore "/user", "c:/users" or "c:/folderAtRoot" + // ignore "//vda1cs4850/c$/folderAtRoot" return false; } - let pathPartForUserCheck = dirPath.substring(rootLength, nextDirectorySeparator + 1); - const isNonDirectorySeparatorRoot = rootLength > 1 || dirPath.charCodeAt(0) !== CharacterCodes.slash; - if (isNonDirectorySeparatorRoot && - dirPath.search(/[a-zA-Z]:/) !== 0 && // Non dos style paths - pathPartForUserCheck.search(/[a-zA-z]\$\//) === 0) { // Dos style nextPart - nextDirectorySeparator = dirPath.indexOf(directorySeparator, nextDirectorySeparator + 1); - if (nextDirectorySeparator === -1) { - // ignore "//vda1cs4850/c$/folderAtRoot" - return false; - } - - pathPartForUserCheck = dirPath.substring(rootLength + pathPartForUserCheck.length, nextDirectorySeparator + 1); - } - - if (isNonDirectorySeparatorRoot && - pathPartForUserCheck.search(/users\//i) !== 0) { - // Paths like c:/folderAtRoot/subFolder are allowed - return true; - } + pathPartForUserCheck = dirPath.substring(rootLength + pathPartForUserCheck.length, nextDirectorySeparator + 1); + } - for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) { - searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1; - if (searchIndex === 0) { - // Folder isnt at expected minimum levels - return false; - } - } + if (isNonDirectorySeparatorRoot && + pathPartForUserCheck.search(/users\//i) !== 0) { + // Paths like c:/folderAtRoot/subFolder are allowed return true; } - type GetResolutionWithResolvedFileName = - (resolution: T) => R | undefined; - - export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache { - let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; - let filesWithInvalidatedResolutions: Set | undefined; - let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyESMap | undefined; - const nonRelativeExternalModuleResolutions = createMultiMap(); - - const resolutionsWithFailedLookups: ResolutionWithFailedLookupLocations[] = []; - const resolvedFileToResolution = createMultiMap(); - - let hasChangedAutomaticTypeDirectiveNames = false; - let failedLookupChecks: Path[] | undefined; - let startsWithPathChecks: Set | undefined; - let isInDirectoryChecks: Path[] | undefined; - - const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217 - const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); - - // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file. - // The key in the map is source file's path. - // The values are Map of resolutions with key being name lookedup. - const resolvedModuleNames = new Map>(); - const perDirectoryResolvedModuleNames: CacheWithRedirects> = createCacheWithRedirects(); - const nonRelativeModuleNameCache: CacheWithRedirects = createCacheWithRedirects(); - const moduleResolutionCache = createModuleResolutionCache( - getCurrentDirectory(), - resolutionHost.getCanonicalFileName, - /*options*/ undefined, - perDirectoryResolvedModuleNames, - nonRelativeModuleNameCache, - ); + for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) { + searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1; + if (searchIndex === 0) { + // Folder isnt at expected minimum levels + return false; + } + } + return true; +} - const resolvedTypeReferenceDirectives = new Map>(); - const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects> = createCacheWithRedirects(); - const typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache( - getCurrentDirectory(), - resolutionHost.getCanonicalFileName, - /*options*/ undefined, - moduleResolutionCache.getPackageJsonInfoCache(), - perDirectoryResolvedTypeReferenceDirectives - ); +type GetResolutionWithResolvedFileName = + (resolution: T) => R | undefined; + +export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache { + let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; + let filesWithInvalidatedResolutions: Set | undefined; + let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyESMap | undefined; + const nonRelativeExternalModuleResolutions = createMultiMap(); + + const resolutionsWithFailedLookups: ResolutionWithFailedLookupLocations[] = []; + const resolvedFileToResolution = createMultiMap(); + + let hasChangedAutomaticTypeDirectiveNames = false; + let failedLookupChecks: Path[] | undefined; + let startsWithPathChecks: Set | undefined; + let isInDirectoryChecks: Path[] | undefined; + + const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217 + const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); + + // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file. + // The key in the map is source file's path. + // The values are Map of resolutions with key being name lookedup. + const resolvedModuleNames = new Map>(); + const perDirectoryResolvedModuleNames: CacheWithRedirects> = createCacheWithRedirects(); + const nonRelativeModuleNameCache: CacheWithRedirects = createCacheWithRedirects(); + const moduleResolutionCache = createModuleResolutionCache( + getCurrentDirectory(), + resolutionHost.getCanonicalFileName, + /*options*/ undefined, + perDirectoryResolvedModuleNames, + nonRelativeModuleNameCache, + ); + + const resolvedTypeReferenceDirectives = new Map>(); + const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects> = createCacheWithRedirects(); + const typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache( + getCurrentDirectory(), + resolutionHost.getCanonicalFileName, + /*options*/ undefined, + moduleResolutionCache.getPackageJsonInfoCache(), + perDirectoryResolvedTypeReferenceDirectives + ); - /** - * These are the extensions that failed lookup files will have by default, - * any other extension of failed lookup will be store that path in custom failed lookup path - * This helps in not having to comb through all resolutions when files are added/removed - * Note that .d.ts file also has .d.ts extension hence will be part of default extensions - */ - const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json]; - const customFailedLookupPaths = new Map(); - - const directoryWatchesOfFailedLookups = new Map(); - const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory())); - const rootPath = (rootDir && resolutionHost.toPath(rootDir)) as Path; // TODO: GH#18217 - const rootSplitLength = rootPath !== undefined ? rootPath.split(directorySeparator).length : 0; - - // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames - const typeRootsWatches = new Map(); - - return { - getModuleResolutionCache: () => moduleResolutionCache, - startRecordingFilesWithChangedResolutions, - finishRecordingFilesWithChangedResolutions, - // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update - // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) - startCachingPerDirectoryResolution: clearPerDirectoryResolutions, - finishCachingPerDirectoryResolution, - resolveModuleNames, - getResolvedModuleWithFailedLookupLocationsFromCache, - resolveTypeReferenceDirectives, - removeResolutionsFromProjectReferenceRedirects, - removeResolutionsOfFile, - hasChangedAutomaticTypeDirectiveNames: () => hasChangedAutomaticTypeDirectiveNames, - invalidateResolutionOfFile, - invalidateResolutionsOfFailedLookupLocations, - setFilesWithInvalidatedNonRelativeUnresolvedImports, - createHasInvalidatedResolution, - isFileWithInvalidatedNonRelativeUnresolvedImports, - updateTypeRootsWatch, - closeTypeRootsWatch, - clear - }; - - function getResolvedModule(resolution: CachedResolvedModuleWithFailedLookupLocations) { - return resolution.resolvedModule; - } - - function getResolvedTypeReferenceDirective(resolution: CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations) { - return resolution.resolvedTypeReferenceDirective; - } - - function isInDirectoryPath(dir: Path | undefined, file: Path) { - if (dir === undefined || file.length <= dir.length) { - return false; - } - return startsWith(file, dir) && file[dir.length] === directorySeparator; - } + /** + * These are the extensions that failed lookup files will have by default, + * any other extension of failed lookup will be store that path in custom failed lookup path + * This helps in not having to comb through all resolutions when files are added/removed + * Note that .d.ts file also has .d.ts extension hence will be part of default extensions + */ + const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json]; + const customFailedLookupPaths = new Map(); + + const directoryWatchesOfFailedLookups = new Map(); + const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory())); + const rootPath = (rootDir && resolutionHost.toPath(rootDir)) as Path; // TODO: GH#18217 + const rootSplitLength = rootPath !== undefined ? rootPath.split(directorySeparator).length : 0; + + // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames + const typeRootsWatches = new Map(); + + return { + getModuleResolutionCache: () => moduleResolutionCache, + startRecordingFilesWithChangedResolutions, + finishRecordingFilesWithChangedResolutions, + // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update + // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) + startCachingPerDirectoryResolution: clearPerDirectoryResolutions, + finishCachingPerDirectoryResolution, + resolveModuleNames, + getResolvedModuleWithFailedLookupLocationsFromCache, + resolveTypeReferenceDirectives, + removeResolutionsFromProjectReferenceRedirects, + removeResolutionsOfFile, + hasChangedAutomaticTypeDirectiveNames: () => hasChangedAutomaticTypeDirectiveNames, + invalidateResolutionOfFile, + invalidateResolutionsOfFailedLookupLocations, + setFilesWithInvalidatedNonRelativeUnresolvedImports, + createHasInvalidatedResolution, + isFileWithInvalidatedNonRelativeUnresolvedImports, + updateTypeRootsWatch, + closeTypeRootsWatch, + clear + }; + + function getResolvedModule(resolution: CachedResolvedModuleWithFailedLookupLocations) { + return resolution.resolvedModule; + } - function clear() { - clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); - customFailedLookupPaths.clear(); - nonRelativeExternalModuleResolutions.clear(); - closeTypeRootsWatch(); - resolvedModuleNames.clear(); - resolvedTypeReferenceDirectives.clear(); - resolvedFileToResolution.clear(); - resolutionsWithFailedLookups.length = 0; - failedLookupChecks = undefined; - startsWithPathChecks = undefined; - isInDirectoryChecks = undefined; - // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update - // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) - clearPerDirectoryResolutions(); - hasChangedAutomaticTypeDirectiveNames = false; - } + function getResolvedTypeReferenceDirective(resolution: CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations) { + return resolution.resolvedTypeReferenceDirective; + } - function startRecordingFilesWithChangedResolutions() { - filesWithChangedSetOfUnresolvedImports = []; + function isInDirectoryPath(dir: Path | undefined, file: Path) { + if (dir === undefined || file.length <= dir.length) { + return false; } + return startsWith(file, dir) && file[dir.length] === directorySeparator; + } - function finishRecordingFilesWithChangedResolutions() { - const collected = filesWithChangedSetOfUnresolvedImports; - filesWithChangedSetOfUnresolvedImports = undefined; - return collected; - } + function clear() { + clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); + customFailedLookupPaths.clear(); + nonRelativeExternalModuleResolutions.clear(); + closeTypeRootsWatch(); + resolvedModuleNames.clear(); + resolvedTypeReferenceDirectives.clear(); + resolvedFileToResolution.clear(); + resolutionsWithFailedLookups.length = 0; + failedLookupChecks = undefined; + startsWithPathChecks = undefined; + isInDirectoryChecks = undefined; + // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update + // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) + clearPerDirectoryResolutions(); + hasChangedAutomaticTypeDirectiveNames = false; + } - function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean { - if (!filesWithInvalidatedNonRelativeUnresolvedImports) { - return false; - } + function startRecordingFilesWithChangedResolutions() { + filesWithChangedSetOfUnresolvedImports = []; + } - // Invalidated if file has unresolved imports - const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path); - return !!value && !!value.length; - } + function finishRecordingFilesWithChangedResolutions() { + const collected = filesWithChangedSetOfUnresolvedImports; + filesWithChangedSetOfUnresolvedImports = undefined; + return collected; + } - function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution { - // Ensure pending resolutions are applied - invalidateResolutionsOfFailedLookupLocations(); - if (forceAllFilesAsInvalidated) { - // Any file asked would have invalidated resolution - filesWithInvalidatedResolutions = undefined; - return returnTrue; - } - const collected = filesWithInvalidatedResolutions; - filesWithInvalidatedResolutions = undefined; - return path => (!!collected && collected.has(path)) || - isFileWithInvalidatedNonRelativeUnresolvedImports(path); + function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean { + if (!filesWithInvalidatedNonRelativeUnresolvedImports) { + return false; } - function clearPerDirectoryResolutions() { - moduleResolutionCache.clear(); - typeReferenceDirectiveResolutionCache.clear(); - nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); - nonRelativeExternalModuleResolutions.clear(); - } + // Invalidated if file has unresolved imports + const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path); + return !!value && !!value.length; + } - function finishCachingPerDirectoryResolution() { - filesWithInvalidatedNonRelativeUnresolvedImports = undefined; - clearPerDirectoryResolutions(); - directoryWatchesOfFailedLookups.forEach((watcher, path) => { - if (watcher.refCount === 0) { - directoryWatchesOfFailedLookups.delete(path); - watcher.watcher.close(); - } - }); - hasChangedAutomaticTypeDirectiveNames = false; + function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution { + // Ensure pending resolutions are applied + invalidateResolutionsOfFailedLookupLocations(); + if (forceAllFilesAsInvalidated) { + // Any file asked would have invalidated resolution + filesWithInvalidatedResolutions = undefined; + return returnTrue; } + const collected = filesWithInvalidatedResolutions; + filesWithInvalidatedResolutions = undefined; + return path => (!!collected && collected.has(path)) || + isFileWithInvalidatedNonRelativeUnresolvedImports(path); + } - function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): CachedResolvedModuleWithFailedLookupLocations { - const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference); - // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts - if (!resolutionHost.getGlobalCache) { - return primaryResult; - } + function clearPerDirectoryResolutions() { + moduleResolutionCache.clear(); + typeReferenceDirectiveResolutionCache.clear(); + nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); + nonRelativeExternalModuleResolutions.clear(); + } - // otherwise try to load typings from @types - const globalCache = resolutionHost.getGlobalCache(); - if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTS(primaryResult.resolvedModule.extension))) { - // create different collection of failed lookup locations for second pass - // if it will fail and we've already found something during the first pass - we don't want to pollute its results - const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache( - Debug.checkDefined(resolutionHost.globalCacheResolutionModuleName)(moduleName), - resolutionHost.projectName, - compilerOptions, - host, - globalCache, - moduleResolutionCache, - ); - if (resolvedModule) { - // Modify existing resolution so its saved in the directory cache as well - (primaryResult.resolvedModule as any) = resolvedModule; - primaryResult.failedLookupLocations.push(...failedLookupLocations); - return primaryResult; - } + function finishCachingPerDirectoryResolution() { + filesWithInvalidatedNonRelativeUnresolvedImports = undefined; + clearPerDirectoryResolutions(); + directoryWatchesOfFailedLookups.forEach((watcher, path) => { + if (watcher.refCount === 0) { + directoryWatchesOfFailedLookups.delete(path); + watcher.watcher.close(); } + }); + hasChangedAutomaticTypeDirectiveNames = false; + } - // Default return the result from the first pass + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): CachedResolvedModuleWithFailedLookupLocations { + const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference); + // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts + if (!resolutionHost.getGlobalCache) { return primaryResult; } - function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations { - return ts.resolveTypeReferenceDirective(typeReferenceDirectiveName, containingFile, options, host, redirectedReference, typeReferenceDirectiveResolutionCache); - } - - interface ResolveNamesWithLocalCacheInput { - names: readonly string[]; - containingFile: string; - redirectedReference: ResolvedProjectReference | undefined; - cache: ESMap>; - perDirectoryCacheWithRedirects: CacheWithRedirects>; - loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile) => T; - getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName; - shouldRetryResolution: (t: T) => boolean; - reusedNames?: readonly string[]; - logChanges?: boolean; - containingSourceFile?: SourceFile; - } - function resolveNamesWithLocalCache({ - names, containingFile, redirectedReference, - cache, perDirectoryCacheWithRedirects, - loader, getResolutionWithResolvedFileName, - shouldRetryResolution, reusedNames, logChanges, containingSourceFile - }: ResolveNamesWithLocalCacheInput): (R | undefined)[] { - const path = resolutionHost.toPath(containingFile); - const resolutionsInFile = cache.get(path) || cache.set(path, createModeAwareCache()).get(path)!; - const dirPath = getDirectoryPath(path); - const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); - let perDirectoryResolution = perDirectoryCache.get(dirPath); - if (!perDirectoryResolution) { - perDirectoryResolution = createModeAwareCache(); - perDirectoryCache.set(dirPath, perDirectoryResolution); + // otherwise try to load typings from @types + const globalCache = resolutionHost.getGlobalCache(); + if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTS(primaryResult.resolvedModule.extension))) { + // create different collection of failed lookup locations for second pass + // if it will fail and we've already found something during the first pass - we don't want to pollute its results + const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache( + Debug.checkDefined(resolutionHost.globalCacheResolutionModuleName)(moduleName), + resolutionHost.projectName, + compilerOptions, + host, + globalCache, + moduleResolutionCache, + ); + if (resolvedModule) { + // Modify existing resolution so its saved in the directory cache as well + (primaryResult.resolvedModule as any) = resolvedModule; + primaryResult.failedLookupLocations.push(...failedLookupLocations); + return primaryResult; } - const resolvedModules: (R | undefined)[] = []; - const compilerOptions = resolutionHost.getCompilationSettings(); - const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path); - - // All the resolutions in this file are invalidated if this file wasn't resolved using same redirect - const program = resolutionHost.getCurrentProgram(); - const oldRedirect = program && program.getResolvedProjectReferenceToRedirect(containingFile); - const unmatchedRedirects = oldRedirect ? - !redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path : - !!redirectedReference; - - const seenNamesInFile = createModeAwareCache(); - let i = 0; - for (const name of names) { - const mode = containingSourceFile ? getModeForResolutionAtIndex(containingSourceFile, i) : undefined; - i++; - let resolution = resolutionsInFile.get(name, mode); - // Resolution is valid if it is present and not invalidated - if (!seenNamesInFile.has(name, mode) && - unmatchedRedirects || !resolution || resolution.isInvalidated || - // If the name is unresolved import that was invalidated, recalculate - (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) { - const existingResolution = resolution; - const resolutionInDirectory = perDirectoryResolution.get(name, mode); - if (resolutionInDirectory) { - resolution = resolutionInDirectory; - const host = resolutionHost.getCompilerHost?.() || resolutionHost; - if (isTraceEnabled(compilerOptions, host)) { - const resolved = getResolutionWithResolvedFileName(resolution); - trace( - host, - loader === resolveModuleName as unknown ? - resolved?.resolvedFileName ? - resolved.packagetId ? - Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4: - Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3: - Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_not_resolved : - resolved?.resolvedFileName ? - resolved.packagetId ? - Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4 : - Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3 : - Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_not_resolved, - name, - containingFile, - getDirectoryPath(containingFile), - resolved?.resolvedFileName, - resolved?.packagetId && packageIdToString(resolved.packagetId) - ); - } - } - else { - resolution = loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference, containingSourceFile); - perDirectoryResolution.set(name, mode, resolution); - if (resolutionHost.onDiscoveredSymlink && resolutionIsSymlink(resolution)) { - resolutionHost.onDiscoveredSymlink(); - } - } - resolutionsInFile.set(name, mode, resolution); - watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution, path, getResolutionWithResolvedFileName); - if (existingResolution) { - stopWatchFailedLookupLocationOfResolution(existingResolution, path, getResolutionWithResolvedFileName); - } + } - if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { - filesWithChangedSetOfUnresolvedImports.push(path); - // reset log changes to avoid recording the same file multiple times - logChanges = false; - } - } - else { + // Default return the result from the first pass + return primaryResult; + } + + function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations { + return ts.resolveTypeReferenceDirective(typeReferenceDirectiveName, containingFile, options, host, redirectedReference, typeReferenceDirectiveResolutionCache); + } + + interface ResolveNamesWithLocalCacheInput { + names: readonly string[]; + containingFile: string; + redirectedReference: ResolvedProjectReference | undefined; + cache: ESMap>; + perDirectoryCacheWithRedirects: CacheWithRedirects>; + loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile) => T; + getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName; + shouldRetryResolution: (t: T) => boolean; + reusedNames?: readonly string[]; + logChanges?: boolean; + containingSourceFile?: SourceFile; + } + function resolveNamesWithLocalCache({ + names, containingFile, redirectedReference, + cache, perDirectoryCacheWithRedirects, + loader, getResolutionWithResolvedFileName, + shouldRetryResolution, reusedNames, logChanges, containingSourceFile + }: ResolveNamesWithLocalCacheInput): (R | undefined)[] { + const path = resolutionHost.toPath(containingFile); + const resolutionsInFile = cache.get(path) || cache.set(path, createModeAwareCache()).get(path)!; + const dirPath = getDirectoryPath(path); + const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); + let perDirectoryResolution = perDirectoryCache.get(dirPath); + if (!perDirectoryResolution) { + perDirectoryResolution = createModeAwareCache(); + perDirectoryCache.set(dirPath, perDirectoryResolution); + } + const resolvedModules: (R | undefined)[] = []; + const compilerOptions = resolutionHost.getCompilationSettings(); + const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path); + + // All the resolutions in this file are invalidated if this file wasn't resolved using same redirect + const program = resolutionHost.getCurrentProgram(); + const oldRedirect = program && program.getResolvedProjectReferenceToRedirect(containingFile); + const unmatchedRedirects = oldRedirect ? + !redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path : + !!redirectedReference; + + const seenNamesInFile = createModeAwareCache(); + let i = 0; + for (const name of names) { + const mode = containingSourceFile ? getModeForResolutionAtIndex(containingSourceFile, i) : undefined; + i++; + let resolution = resolutionsInFile.get(name, mode); + // Resolution is valid if it is present and not invalidated + if (!seenNamesInFile.has(name, mode) && + unmatchedRedirects || !resolution || resolution.isInvalidated || + // If the name is unresolved import that was invalidated, recalculate + (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) { + const existingResolution = resolution; + const resolutionInDirectory = perDirectoryResolution.get(name, mode); + if (resolutionInDirectory) { + resolution = resolutionInDirectory; const host = resolutionHost.getCompilerHost?.() || resolutionHost; - if (isTraceEnabled(compilerOptions, host) && !seenNamesInFile.has(name, mode)) { + if (isTraceEnabled(compilerOptions, host)) { const resolved = getResolutionWithResolvedFileName(resolution); trace( host, loader === resolveModuleName as unknown ? resolved?.resolvedFileName ? resolved.packagetId ? - Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : - Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 : - Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved : + Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4: + Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3: + Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_not_resolved : resolved?.resolvedFileName ? resolved.packagetId ? - Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : - Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2 : - Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_not_resolved, + Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4 : + Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3 : + Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_not_resolved, name, containingFile, + getDirectoryPath(containingFile), resolved?.resolvedFileName, resolved?.packagetId && packageIdToString(resolved.packagetId) ); } } - Debug.assert(resolution !== undefined && !resolution.isInvalidated); - seenNamesInFile.set(name, mode, true); - resolvedModules.push(getResolutionWithResolvedFileName(resolution)); - } - - // Stop watching and remove the unused name - resolutionsInFile.forEach((resolution, name, mode) => { - if (!seenNamesInFile.has(name, mode) && !contains(reusedNames, name)) { - stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName); - resolutionsInFile.delete(name, mode); - } - }); - - return resolvedModules; - - function resolutionIsEqualTo(oldResolution: T | undefined, newResolution: T | undefined): boolean { - if (oldResolution === newResolution) { - return true; + else { + resolution = loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference, containingSourceFile); + perDirectoryResolution.set(name, mode, resolution); + if (resolutionHost.onDiscoveredSymlink && resolutionIsSymlink(resolution)) { + resolutionHost.onDiscoveredSymlink(); + } } - if (!oldResolution || !newResolution) { - return false; + resolutionsInFile.set(name, mode, resolution); + watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution, path, getResolutionWithResolvedFileName); + if (existingResolution) { + stopWatchFailedLookupLocationOfResolution(existingResolution, path, getResolutionWithResolvedFileName); } - const oldResult = getResolutionWithResolvedFileName(oldResolution); - const newResult = getResolutionWithResolvedFileName(newResolution); - if (oldResult === newResult) { - return true; + + if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { + filesWithChangedSetOfUnresolvedImports.push(path); + // reset log changes to avoid recording the same file multiple times + logChanges = false; } - if (!oldResult || !newResult) { - return false; + } + else { + const host = resolutionHost.getCompilerHost?.() || resolutionHost; + if (isTraceEnabled(compilerOptions, host) && !seenNamesInFile.has(name, mode)) { + const resolved = getResolutionWithResolvedFileName(resolution); + trace( + host, + loader === resolveModuleName as unknown ? + resolved?.resolvedFileName ? + resolved.packagetId ? + Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : + Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 : + Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved : + resolved?.resolvedFileName ? + resolved.packagetId ? + Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : + Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2 : + Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_not_resolved, + name, + containingFile, + resolved?.resolvedFileName, + resolved?.packagetId && packageIdToString(resolved.packagetId) + ); } - return oldResult.resolvedFileName === newResult.resolvedFileName; } + Debug.assert(resolution !== undefined && !resolution.isInvalidated); + seenNamesInFile.set(name, mode, true); + resolvedModules.push(getResolutionWithResolvedFileName(resolution)); } - function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[] { - return resolveNamesWithLocalCache({ - names: typeDirectiveNames, - containingFile, - redirectedReference, - cache: resolvedTypeReferenceDirectives, - perDirectoryCacheWithRedirects: perDirectoryResolvedTypeReferenceDirectives, - loader: resolveTypeReferenceDirective, - getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective, - shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined, - }); - } - - function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[] { - return resolveNamesWithLocalCache({ - names: moduleNames, - containingFile, - redirectedReference, - cache: resolvedModuleNames, - perDirectoryCacheWithRedirects: perDirectoryResolvedModuleNames, - loader: resolveModuleName, - getResolutionWithResolvedFileName: getResolvedModule, - shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension), - reusedNames, - logChanges: logChangesWhenResolvingModule, - containingSourceFile, - }); - } - - function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined { - const cache = resolvedModuleNames.get(resolutionHost.toPath(containingFile)); - if (!cache) return undefined; - return cache.get(moduleName, resolutionMode); - } - - function isNodeModulesAtTypesDirectory(dirPath: Path) { - return endsWith(dirPath, "/node_modules/@types"); - } - - function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch | undefined { - if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { - // Ensure failed look up is normalized path - failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()); - const failedLookupPathSplit = failedLookupLocationPath.split(directorySeparator); - const failedLookupSplit = failedLookupLocation.split(directorySeparator); - Debug.assert(failedLookupSplit.length === failedLookupPathSplit.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`); - if (failedLookupPathSplit.length > rootSplitLength + 1) { - // Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution - return { - dir: failedLookupSplit.slice(0, rootSplitLength + 1).join(directorySeparator), - dirPath: failedLookupPathSplit.slice(0, rootSplitLength + 1).join(directorySeparator) as Path - }; - } - else { - // Always watch root directory non recursively - return { - dir: rootDir!, - dirPath: rootPath, - nonRecursive: false - }; - } + // Stop watching and remove the unused name + resolutionsInFile.forEach((resolution, name, mode) => { + if (!seenNamesInFile.has(name, mode) && !contains(reusedNames, name)) { + stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName); + resolutionsInFile.delete(name, mode); } + }); - return getDirectoryToWatchFromFailedLookupLocationDirectory( - getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())), - getDirectoryPath(failedLookupLocationPath) - ); - } + return resolvedModules; - function getDirectoryToWatchFromFailedLookupLocationDirectory(dir: string, dirPath: Path): DirectoryOfFailedLookupWatch | undefined { - // If directory path contains node module, get the most parent node_modules directory for watching - while (pathContainsNodeModules(dirPath)) { - dir = getDirectoryPath(dir); - dirPath = getDirectoryPath(dirPath); + function resolutionIsEqualTo(oldResolution: T | undefined, newResolution: T | undefined): boolean { + if (oldResolution === newResolution) { + return true; } - - // If the directory is node_modules use it to watch, always watch it recursively - if (isNodeModulesDirectory(dirPath)) { - return canWatchDirectory(getDirectoryPath(dirPath)) ? { dir, dirPath } : undefined; + if (!oldResolution || !newResolution) { + return false; } - - let nonRecursive = true; - // Use some ancestor of the root directory - let subDirectoryPath: Path | undefined, subDirectory: string | undefined; - if (rootPath !== undefined) { - while (!isInDirectoryPath(dirPath, rootPath)) { - const parentPath = getDirectoryPath(dirPath); - if (parentPath === dirPath) { - break; - } - nonRecursive = false; - subDirectoryPath = dirPath; - subDirectory = dir; - dirPath = parentPath; - dir = getDirectoryPath(dir); - } + const oldResult = getResolutionWithResolvedFileName(oldResolution); + const newResult = getResolutionWithResolvedFileName(newResolution); + if (oldResult === newResult) { + return true; } - - return canWatchDirectory(dirPath) ? { dir: subDirectory || dir, dirPath: subDirectoryPath || dirPath, nonRecursive } : undefined; + if (!oldResult || !newResult) { + return false; + } + return oldResult.resolvedFileName === newResult.resolvedFileName; } + } - function isPathWithDefaultFailedLookupExtension(path: Path) { - return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); - } + function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[] { + return resolveNamesWithLocalCache({ + names: typeDirectiveNames, + containingFile, + redirectedReference, + cache: resolvedTypeReferenceDirectives, + perDirectoryCacheWithRedirects: perDirectoryResolvedTypeReferenceDirectives, + loader: resolveTypeReferenceDirective, + getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective, + shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined, + }); + } - function watchFailedLookupLocationsOfExternalModuleResolutions( - name: string, - resolution: T, - filePath: Path, - getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, - ) { - if (resolution.refCount) { - resolution.refCount++; - Debug.assertIsDefined(resolution.files); + function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[] { + return resolveNamesWithLocalCache({ + names: moduleNames, + containingFile, + redirectedReference, + cache: resolvedModuleNames, + perDirectoryCacheWithRedirects: perDirectoryResolvedModuleNames, + loader: resolveModuleName, + getResolutionWithResolvedFileName: getResolvedModule, + shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension), + reusedNames, + logChanges: logChangesWhenResolvingModule, + containingSourceFile, + }); + } + + function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined { + const cache = resolvedModuleNames.get(resolutionHost.toPath(containingFile)); + if (!cache) return undefined; + return cache.get(moduleName, resolutionMode); + } + + function isNodeModulesAtTypesDirectory(dirPath: Path) { + return endsWith(dirPath, "/node_modules/@types"); + } + + function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch | undefined { + if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { + // Ensure failed look up is normalized path + failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()); + const failedLookupPathSplit = failedLookupLocationPath.split(directorySeparator); + const failedLookupSplit = failedLookupLocation.split(directorySeparator); + Debug.assert(failedLookupSplit.length === failedLookupPathSplit.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`); + if (failedLookupPathSplit.length > rootSplitLength + 1) { + // Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution + return { + dir: failedLookupSplit.slice(0, rootSplitLength + 1).join(directorySeparator), + dirPath: failedLookupPathSplit.slice(0, rootSplitLength + 1).join(directorySeparator) as Path + }; } else { - resolution.refCount = 1; - Debug.assert(length(resolution.files) === 0); // This resolution shouldnt be referenced by any file yet - if (isExternalModuleNameRelative(name)) { - watchFailedLookupLocationOfResolution(resolution); - } - else { - nonRelativeExternalModuleResolutions.add(name, resolution); - } - const resolved = getResolutionWithResolvedFileName(resolution); - if (resolved && resolved.resolvedFileName) { - resolvedFileToResolution.add(resolutionHost.toPath(resolved.resolvedFileName), resolution); - } - } - (resolution.files || (resolution.files = [])).push(filePath); - } - - function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { - Debug.assert(!!resolution.refCount); - - const { failedLookupLocations } = resolution; - if (!failedLookupLocations.length) return; - resolutionsWithFailedLookups.push(resolution); - - let setAtRoot = false; - for (const failedLookupLocation of failedLookupLocations) { - const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); - const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - if (toWatch) { - const { dir, dirPath, nonRecursive } = toWatch; - // If the failed lookup location path is not one of the supported extensions, - // store it in the custom path - if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { - const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; - customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); - } - if (dirPath === rootPath) { - Debug.assert(!nonRecursive); - setAtRoot = true; - } - else { - setDirectoryWatcher(dir, dirPath, nonRecursive); - } - } + // Always watch root directory non recursively + return { + dir: rootDir!, + dirPath: rootPath, + nonRecursive: false + }; } + } - if (setAtRoot) { - // This is always non recursive - setDirectoryWatcher(rootDir!, rootPath, /*nonRecursive*/ true); // TODO: GH#18217 - } + return getDirectoryToWatchFromFailedLookupLocationDirectory( + getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())), + getDirectoryPath(failedLookupLocationPath) + ); + } + + function getDirectoryToWatchFromFailedLookupLocationDirectory(dir: string, dirPath: Path): DirectoryOfFailedLookupWatch | undefined { + // If directory path contains node module, get the most parent node_modules directory for watching + while (pathContainsNodeModules(dirPath)) { + dir = getDirectoryPath(dir); + dirPath = getDirectoryPath(dirPath); } - function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) { - const program = resolutionHost.getCurrentProgram(); - if (!program || !program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name)) { - resolutions.forEach(watchFailedLookupLocationOfResolution); - } + // If the directory is node_modules use it to watch, always watch it recursively + if (isNodeModulesDirectory(dirPath)) { + return canWatchDirectory(getDirectoryPath(dirPath)) ? { dir, dirPath } : undefined; } - function setDirectoryWatcher(dir: string, dirPath: Path, nonRecursive?: boolean) { - const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); - if (dirWatcher) { - Debug.assert(!!nonRecursive === !!dirWatcher.nonRecursive); - dirWatcher.refCount++; - } - else { - directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath, nonRecursive), refCount: 1, nonRecursive }); + let nonRecursive = true; + // Use some ancestor of the root directory + let subDirectoryPath: Path | undefined, subDirectory: string | undefined; + if (rootPath !== undefined) { + while (!isInDirectoryPath(dirPath, rootPath)) { + const parentPath = getDirectoryPath(dirPath); + if (parentPath === dirPath) { + break; + } + nonRecursive = false; + subDirectoryPath = dirPath; + subDirectory = dir; + dirPath = parentPath; + dir = getDirectoryPath(dir); } } - function stopWatchFailedLookupLocationOfResolution( - resolution: T, - filePath: Path, - getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, - ) { - unorderedRemoveItem(Debug.checkDefined(resolution.files), filePath); - resolution.refCount!--; - if (resolution.refCount) { - return; + return canWatchDirectory(dirPath) ? { dir: subDirectory || dir, dirPath: subDirectoryPath || dirPath, nonRecursive } : undefined; + } + + function isPathWithDefaultFailedLookupExtension(path: Path) { + return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); + } + + function watchFailedLookupLocationsOfExternalModuleResolutions( + name: string, + resolution: T, + filePath: Path, + getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, + ) { + if (resolution.refCount) { + resolution.refCount++; + Debug.assertIsDefined(resolution.files); + } + else { + resolution.refCount = 1; + Debug.assert(length(resolution.files) === 0); // This resolution shouldnt be referenced by any file yet + if (isExternalModuleNameRelative(name)) { + watchFailedLookupLocationOfResolution(resolution); + } + else { + nonRelativeExternalModuleResolutions.add(name, resolution); } const resolved = getResolutionWithResolvedFileName(resolution); if (resolved && resolved.resolvedFileName) { - resolvedFileToResolution.remove(resolutionHost.toPath(resolved.resolvedFileName), resolution); + resolvedFileToResolution.add(resolutionHost.toPath(resolved.resolvedFileName), resolution); } + } + (resolution.files || (resolution.files = [])).push(filePath); + } - if (!unorderedRemoveItem(resolutionsWithFailedLookups, resolution)) { - // If not watching failed lookups, it wont be there in resolutionsWithFailedLookups - return; + function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { + Debug.assert(!!resolution.refCount); + + const { failedLookupLocations } = resolution; + if (!failedLookupLocations.length) return; + resolutionsWithFailedLookups.push(resolution); + + let setAtRoot = false; + for (const failedLookupLocation of failedLookupLocations) { + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + if (toWatch) { + const { dir, dirPath, nonRecursive } = toWatch; + // If the failed lookup location path is not one of the supported extensions, + // store it in the custom path + if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { + const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; + customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); + } + if (dirPath === rootPath) { + Debug.assert(!nonRecursive); + setAtRoot = true; + } + else { + setDirectoryWatcher(dir, dirPath, nonRecursive); + } } + } - const { failedLookupLocations } = resolution; - let removeAtRoot = false; - for (const failedLookupLocation of failedLookupLocations) { - const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); - const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - if (toWatch) { - const { dirPath } = toWatch; - const refCount = customFailedLookupPaths.get(failedLookupLocationPath); - if (refCount) { - if (refCount === 1) { - customFailedLookupPaths.delete(failedLookupLocationPath); - } - else { - Debug.assert(refCount > 1); - customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); - } - } + if (setAtRoot) { + // This is always non recursive + setDirectoryWatcher(rootDir!, rootPath, /*nonRecursive*/ true); // TODO: GH#18217 + } + } - if (dirPath === rootPath) { - removeAtRoot = true; + function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) { + const program = resolutionHost.getCurrentProgram(); + if (!program || !program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name)) { + resolutions.forEach(watchFailedLookupLocationOfResolution); + } + } + + function setDirectoryWatcher(dir: string, dirPath: Path, nonRecursive?: boolean) { + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); + if (dirWatcher) { + Debug.assert(!!nonRecursive === !!dirWatcher.nonRecursive); + dirWatcher.refCount++; + } + else { + directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath, nonRecursive), refCount: 1, nonRecursive }); + } + } + + function stopWatchFailedLookupLocationOfResolution( + resolution: T, + filePath: Path, + getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, + ) { + unorderedRemoveItem(Debug.checkDefined(resolution.files), filePath); + resolution.refCount!--; + if (resolution.refCount) { + return; + } + const resolved = getResolutionWithResolvedFileName(resolution); + if (resolved && resolved.resolvedFileName) { + resolvedFileToResolution.remove(resolutionHost.toPath(resolved.resolvedFileName), resolution); + } + + if (!unorderedRemoveItem(resolutionsWithFailedLookups, resolution)) { + // If not watching failed lookups, it wont be there in resolutionsWithFailedLookups + return; + } + + const { failedLookupLocations } = resolution; + let removeAtRoot = false; + for (const failedLookupLocation of failedLookupLocations) { + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + if (toWatch) { + const { dirPath } = toWatch; + const refCount = customFailedLookupPaths.get(failedLookupLocationPath); + if (refCount) { + if (refCount === 1) { + customFailedLookupPaths.delete(failedLookupLocationPath); } else { - removeDirectoryWatcher(dirPath); + Debug.assert(refCount > 1); + customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); } } - } - if (removeAtRoot) { - removeDirectoryWatcher(rootPath); + + if (dirPath === rootPath) { + removeAtRoot = true; + } + else { + removeDirectoryWatcher(dirPath); + } } } - - function removeDirectoryWatcher(dirPath: string) { - const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath)!; - // Do not close the watcher yet since it might be needed by other failed lookup locations. - dirWatcher.refCount--; + if (removeAtRoot) { + removeDirectoryWatcher(rootPath); } + } - function createDirectoryWatcher(directory: string, dirPath: Path, nonRecursive: boolean | undefined) { - return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => { - const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); - if (cachedDirectoryStructureHost) { - // Since the file existence changed, update the sourceFiles cache - cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); - } + function removeDirectoryWatcher(dirPath: string) { + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath)!; + // Do not close the watcher yet since it might be needed by other failed lookup locations. + dirWatcher.refCount--; + } - scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath); - }, nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive); - } - - function removeResolutionsOfFileFromCache( - cache: ESMap>, - filePath: Path, - getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, - ) { - // Deleted file, stop watching failed lookups for all the resolutions in the file - const resolutions = cache.get(filePath); - if (resolutions) { - resolutions.forEach(resolution => stopWatchFailedLookupLocationOfResolution(resolution, filePath, getResolutionWithResolvedFileName)); - cache.delete(filePath); + function createDirectoryWatcher(directory: string, dirPath: Path, nonRecursive: boolean | undefined) { + return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => { + const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); + if (cachedDirectoryStructureHost) { + // Since the file existence changed, update the sourceFiles cache + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } + + scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath); + }, nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive); + } + + function removeResolutionsOfFileFromCache( + cache: ESMap>, + filePath: Path, + getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, + ) { + // Deleted file, stop watching failed lookups for all the resolutions in the file + const resolutions = cache.get(filePath); + if (resolutions) { + resolutions.forEach(resolution => stopWatchFailedLookupLocationOfResolution(resolution, filePath, getResolutionWithResolvedFileName)); + cache.delete(filePath); } + } - function removeResolutionsFromProjectReferenceRedirects(filePath: Path) { - if (!fileExtensionIs(filePath, Extension.Json)) return; + function removeResolutionsFromProjectReferenceRedirects(filePath: Path) { + if (!fileExtensionIs(filePath, Extension.Json)) return; - const program = resolutionHost.getCurrentProgram(); - if (!program) return; + const program = resolutionHost.getCurrentProgram(); + if (!program) return; - // If this file is input file for the referenced project, get it - const resolvedProjectReference = program.getResolvedProjectReferenceByPath(filePath); - if (!resolvedProjectReference) return; + // If this file is input file for the referenced project, get it + const resolvedProjectReference = program.getResolvedProjectReferenceByPath(filePath); + if (!resolvedProjectReference) return; - // filePath is for the projectReference and the containing file is from this project reference, invalidate the resolution - resolvedProjectReference.commandLine.fileNames.forEach(f => removeResolutionsOfFile(resolutionHost.toPath(f))); - } + // filePath is for the projectReference and the containing file is from this project reference, invalidate the resolution + resolvedProjectReference.commandLine.fileNames.forEach(f => removeResolutionsOfFile(resolutionHost.toPath(f))); + } - function removeResolutionsOfFile(filePath: Path) { - removeResolutionsOfFileFromCache(resolvedModuleNames, filePath, getResolvedModule); - removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath, getResolvedTypeReferenceDirective); - } + function removeResolutionsOfFile(filePath: Path) { + removeResolutionsOfFileFromCache(resolvedModuleNames, filePath, getResolvedModule); + removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath, getResolvedTypeReferenceDirective); + } - function invalidateResolutions(resolutions: ResolutionWithFailedLookupLocations[] | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean) { - if (!resolutions) return false; - let invalidated = false; - for (const resolution of resolutions) { - if (resolution.isInvalidated || !canInvalidate(resolution)) continue; - resolution.isInvalidated = invalidated = true; - for (const containingFilePath of Debug.checkDefined(resolution.files)) { - (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = new Set())).add(containingFilePath); - // When its a file with inferred types resolution, invalidate type reference directive resolution - hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames || endsWith(containingFilePath, inferredTypesContainingFile); - } + function invalidateResolutions(resolutions: ResolutionWithFailedLookupLocations[] | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean) { + if (!resolutions) return false; + let invalidated = false; + for (const resolution of resolutions) { + if (resolution.isInvalidated || !canInvalidate(resolution)) continue; + resolution.isInvalidated = invalidated = true; + for (const containingFilePath of Debug.checkDefined(resolution.files)) { + (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = new Set())).add(containingFilePath); + // When its a file with inferred types resolution, invalidate type reference directive resolution + hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames || endsWith(containingFilePath, inferredTypesContainingFile); } - return invalidated; } + return invalidated; + } - function invalidateResolutionOfFile(filePath: Path) { - removeResolutionsOfFile(filePath); - // Resolution is invalidated if the resulting file name is same as the deleted file path - const prevHasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; - if (invalidateResolutions(resolvedFileToResolution.get(filePath), returnTrue) && - hasChangedAutomaticTypeDirectiveNames && - !prevHasChangedAutomaticTypeDirectiveNames) { - resolutionHost.onChangedAutomaticTypeDirectiveNames(); - } + function invalidateResolutionOfFile(filePath: Path) { + removeResolutionsOfFile(filePath); + // Resolution is invalidated if the resulting file name is same as the deleted file path + const prevHasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; + if (invalidateResolutions(resolvedFileToResolution.get(filePath), returnTrue) && + hasChangedAutomaticTypeDirectiveNames && + !prevHasChangedAutomaticTypeDirectiveNames) { + resolutionHost.onChangedAutomaticTypeDirectiveNames(); } + } + + function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: ReadonlyESMap) { + Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined); + filesWithInvalidatedNonRelativeUnresolvedImports = filesMap; + } - function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: ReadonlyESMap) { - Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined); - filesWithInvalidatedNonRelativeUnresolvedImports = filesMap; + function scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) { + if (isCreatingWatchedDirectory) { + // Watching directory is created + // Invalidate any resolution has failed lookup in this directory + (isInDirectoryChecks ||= []).push(fileOrDirectoryPath); } + else { + // If something to do with folder/file starting with "." in node_modules folder, skip it + const updatedPath = removeIgnoredPath(fileOrDirectoryPath); + if (!updatedPath) return false; + fileOrDirectoryPath = updatedPath; - function scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) { - if (isCreatingWatchedDirectory) { - // Watching directory is created - // Invalidate any resolution has failed lookup in this directory - (isInDirectoryChecks ||= []).push(fileOrDirectoryPath); + // prevent saving an open file from over-eagerly triggering invalidation + if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { + return false; } - else { - // If something to do with folder/file starting with "." in node_modules folder, skip it - const updatedPath = removeIgnoredPath(fileOrDirectoryPath); - if (!updatedPath) return false; - fileOrDirectoryPath = updatedPath; - // prevent saving an open file from over-eagerly triggering invalidation - if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { + // Some file or directory in the watching directory is created + // Return early if it does not have any of the watching extension or not the custom failed lookup path + const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath); + if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || isNodeModulesDirectory(fileOrDirectoryPath) || + isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) { + // Invalidate any resolution from this directory + (failedLookupChecks ||= []).push(fileOrDirectoryPath); + (startsWithPathChecks ||= new Set()).add(fileOrDirectoryPath); + } + else { + if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) { return false; } - - // Some file or directory in the watching directory is created - // Return early if it does not have any of the watching extension or not the custom failed lookup path - const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath); - if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || isNodeModulesDirectory(fileOrDirectoryPath) || - isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) { - // Invalidate any resolution from this directory - (failedLookupChecks ||= []).push(fileOrDirectoryPath); - (startsWithPathChecks ||= new Set()).add(fileOrDirectoryPath); - } - else { - if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) { - return false; - } - // Ignore emits from the program - if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) { - return false; - } - // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created - (failedLookupChecks ||= []).push(fileOrDirectoryPath); - - // If the invalidated file is from a node_modules package, invalidate everything else - // in the package since we might not get notifications for other files in the package. - // This hardens our logic against unreliable file watchers. - const packagePath = parseNodeModuleFromPath(fileOrDirectoryPath); - if (packagePath) (startsWithPathChecks ||= new Set()).add(packagePath as Path); + // Ignore emits from the program + if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) { + return false; } - } - resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations(); - } + // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created + (failedLookupChecks ||= []).push(fileOrDirectoryPath); - function invalidateResolutionsOfFailedLookupLocations() { - if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks) { - return false; + // If the invalidated file is from a node_modules package, invalidate everything else + // in the package since we might not get notifications for other files in the package. + // This hardens our logic against unreliable file watchers. + const packagePath = parseNodeModuleFromPath(fileOrDirectoryPath); + if (packagePath) (startsWithPathChecks ||= new Set()).add(packagePath as Path); } - - const invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution); - failedLookupChecks = undefined; - startsWithPathChecks = undefined; - isInDirectoryChecks = undefined; - return invalidated; } + resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations(); + } - function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) { - return resolution.failedLookupLocations.some(location => { - const locationPath = resolutionHost.toPath(location); - return contains(failedLookupChecks, locationPath) || - firstDefinedIterator(startsWithPathChecks?.keys() || emptyIterator, fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath) ? true : undefined) || - isInDirectoryChecks?.some(fileOrDirectoryPath => isInDirectoryPath(fileOrDirectoryPath, locationPath)); - }); + function invalidateResolutionsOfFailedLookupLocations() { + if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks) { + return false; } - function closeTypeRootsWatch() { - clearMap(typeRootsWatches, closeFileWatcher); - } + const invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution); + failedLookupChecks = undefined; + startsWithPathChecks = undefined; + isInDirectoryChecks = undefined; + return invalidated; + } - function getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot: string, typeRootPath: Path): Path | undefined { - if (isInDirectoryPath(rootPath, typeRootPath)) { - return rootPath; - } - const toWatch = getDirectoryToWatchFromFailedLookupLocationDirectory(typeRoot, typeRootPath); - return toWatch && directoryWatchesOfFailedLookups.has(toWatch.dirPath) ? toWatch.dirPath : undefined; - } + function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) { + return resolution.failedLookupLocations.some(location => { + const locationPath = resolutionHost.toPath(location); + return contains(failedLookupChecks, locationPath) || + firstDefinedIterator(startsWithPathChecks?.keys() || emptyIterator, fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath) ? true : undefined) || + isInDirectoryChecks?.some(fileOrDirectoryPath => isInDirectoryPath(fileOrDirectoryPath, locationPath)); + }); + } - function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher { - // Create new watch and recursive info - return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { - const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); - if (cachedDirectoryStructureHost) { - // Since the file existence changed, update the sourceFiles cache - cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); - } + function closeTypeRootsWatch() { + clearMap(typeRootsWatches, closeFileWatcher); + } - // For now just recompile - // We could potentially store more data here about whether it was/would be really be used or not - // and with that determine to trigger compilation but for now this is enough - hasChangedAutomaticTypeDirectiveNames = true; - resolutionHost.onChangedAutomaticTypeDirectiveNames(); - - // Since directory watchers invoked are flaky, the failed lookup location events might not be triggered - // So handle to failed lookup locations here as well to ensure we are invalidating resolutions - const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath); - if (dirPath) { - scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath); - } - }, WatchDirectoryFlags.Recursive); - } - - /** - * Watches the types that would get added as part of getAutomaticTypeDirectiveNames - * To be called when compiler options change - */ - function updateTypeRootsWatch() { - const options = resolutionHost.getCompilationSettings(); - if (options.types) { - // No need to do any watch since resolution cache is going to handle the failed lookups - // for the types added by this - closeTypeRootsWatch(); - return; - } + function getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot: string, typeRootPath: Path): Path | undefined { + if (isInDirectoryPath(rootPath, typeRootPath)) { + return rootPath; + } + const toWatch = getDirectoryToWatchFromFailedLookupLocationDirectory(typeRoot, typeRootPath); + return toWatch && directoryWatchesOfFailedLookups.has(toWatch.dirPath) ? toWatch.dirPath : undefined; + } - // we need to assume the directories exist to ensure that we can get all the type root directories that get included - // But filter directories that are at root level to say directory doesnt exist, so that we arent watching them - const typeRoots = getEffectiveTypeRoots(options, { directoryExists: directoryExistsForTypeRootWatch, getCurrentDirectory }); - if (typeRoots) { - mutateMap( - typeRootsWatches, - arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)), - { - createNewValue: createTypeRootsWatch, - onDeleteValue: closeFileWatcher - } - ); - } - else { - closeTypeRootsWatch(); + function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher { + // Create new watch and recursive info + return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { + const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); + if (cachedDirectoryStructureHost) { + // Since the file existence changed, update the sourceFiles cache + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + } + + // For now just recompile + // We could potentially store more data here about whether it was/would be really be used or not + // and with that determine to trigger compilation but for now this is enough + hasChangedAutomaticTypeDirectiveNames = true; + resolutionHost.onChangedAutomaticTypeDirectiveNames(); + + // Since directory watchers invoked are flaky, the failed lookup location events might not be triggered + // So handle to failed lookup locations here as well to ensure we are invalidating resolutions + const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath); + if (dirPath) { + scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath); } - } + }, WatchDirectoryFlags.Recursive); + } - /** - * Use this function to return if directory exists to get type roots to watch - * If we return directory exists then only the paths will be added to type roots - * Hence return true for all directories except root directories which are filtered from watching - */ - function directoryExistsForTypeRootWatch(nodeTypesDirectory: string) { - const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory)); - const dirPath = resolutionHost.toPath(dir); - return dirPath === rootPath || canWatchDirectory(dirPath); + /** + * Watches the types that would get added as part of getAutomaticTypeDirectiveNames + * To be called when compiler options change + */ + function updateTypeRootsWatch() { + const options = resolutionHost.getCompilationSettings(); + if (options.types) { + // No need to do any watch since resolution cache is going to handle the failed lookups + // for the types added by this + closeTypeRootsWatch(); + return; + } + + // we need to assume the directories exist to ensure that we can get all the type root directories that get included + // But filter directories that are at root level to say directory doesnt exist, so that we arent watching them + const typeRoots = getEffectiveTypeRoots(options, { directoryExists: directoryExistsForTypeRootWatch, getCurrentDirectory }); + if (typeRoots) { + mutateMap( + typeRootsWatches, + arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)), + { + createNewValue: createTypeRootsWatch, + onDeleteValue: closeFileWatcher + } + ); + } + else { + closeTypeRootsWatch(); } } - function resolutionIsSymlink(resolution: ResolutionWithFailedLookupLocations) { - return !!( - (resolution as ResolvedModuleWithFailedLookupLocations).resolvedModule?.originalPath || - (resolution as ResolvedTypeReferenceDirectiveWithFailedLookupLocations).resolvedTypeReferenceDirective?.originalPath - ); + /** + * Use this function to return if directory exists to get type roots to watch + * If we return directory exists then only the paths will be added to type roots + * Hence return true for all directories except root directories which are filtered from watching + */ + function directoryExistsForTypeRootWatch(nodeTypesDirectory: string) { + const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory)); + const dirPath = resolutionHost.toPath(dir); + return dirPath === rootPath || canWatchDirectory(dirPath); } } + +function resolutionIsSymlink(resolution: ResolutionWithFailedLookupLocations) { + return !!( + (resolution as ResolvedModuleWithFailedLookupLocations).resolvedModule?.originalPath || + (resolution as ResolvedTypeReferenceDirectiveWithFailedLookupLocations).resolvedTypeReferenceDirective?.originalPath + ); +} +} diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 6cac0cb1936d1..4b662f7f8e055 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1,608 +1,808 @@ namespace ts { - export type ErrorCallback = (message: DiagnosticMessage, length: number) => void; +export type ErrorCallback = (message: DiagnosticMessage, length: number) => void; - /* @internal */ - export function tokenIsIdentifierOrKeyword(token: SyntaxKind): boolean { - return token >= SyntaxKind.Identifier; - } - - /* @internal */ - export function tokenIsIdentifierOrKeywordOrGreaterThan(token: SyntaxKind): boolean { - return token === SyntaxKind.GreaterThanToken || tokenIsIdentifierOrKeyword(token); - } - - export interface Scanner { - getStartPos(): number; - getToken(): SyntaxKind; - getTextPos(): number; - getTokenPos(): number; - getTokenText(): string; - getTokenValue(): string; - hasUnicodeEscape(): boolean; - hasExtendedUnicodeEscape(): boolean; - hasPrecedingLineBreak(): boolean; - /* @internal */ - hasPrecedingJSDocComment(): boolean; - isIdentifier(): boolean; - isReservedWord(): boolean; - isUnterminated(): boolean; - /* @internal */ - getNumericLiteralFlags(): TokenFlags; - /* @internal */ - getCommentDirectives(): CommentDirective[] | undefined; - /* @internal */ - getTokenFlags(): TokenFlags; - reScanGreaterToken(): SyntaxKind; - reScanSlashToken(): SyntaxKind; - reScanAsteriskEqualsToken(): SyntaxKind; - reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind; - reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind; - scanJsxIdentifier(): SyntaxKind; - scanJsxAttributeValue(): SyntaxKind; - reScanJsxAttributeValue(): SyntaxKind; - reScanJsxToken(allowMultilineJsxText?: boolean): JsxTokenSyntaxKind; - reScanLessThanToken(): SyntaxKind; - reScanHashToken(): SyntaxKind; - reScanQuestionToken(): SyntaxKind; - reScanInvalidIdentifier(): SyntaxKind; - scanJsxToken(): JsxTokenSyntaxKind; - scanJsDocToken(): JSDocSyntaxKind; - scan(): SyntaxKind; - - getText(): string; - /* @internal */ - clearCommentDirectives(): void; - // Sets the text for the scanner to scan. An optional subrange starting point and length - // can be provided to have the scanner only scan a portion of the text. - setText(text: string | undefined, start?: number, length?: number): void; - setOnError(onError: ErrorCallback | undefined): void; - setScriptTarget(scriptTarget: ScriptTarget): void; - setLanguageVariant(variant: LanguageVariant): void; - setTextPos(textPos: number): void; - /* @internal */ - setInJSDocType(inType: boolean): void; - // Invokes the provided callback then unconditionally restores the scanner to the state it - // was in immediately prior to invoking the callback. The result of invoking the callback - // is returned from this function. - lookAhead(callback: () => T): T; - - // Invokes the callback with the scanner set to scan the specified range. When the callback - // returns, the scanner is restored to the state it was in before scanRange was called. - scanRange(start: number, length: number, callback: () => T): T; - - // Invokes the provided callback. If the callback returns something falsy, then it restores - // the scanner to the state it was in immediately prior to invoking the callback. If the - // callback returns something truthy, then the scanner state is not rolled back. The result - // of invoking the callback is returned from this function. - tryScan(callback: () => T): T; - } - - /** @internal */ - export const textToKeywordObj: MapLike = { - abstract: SyntaxKind.AbstractKeyword, - any: SyntaxKind.AnyKeyword, - as: SyntaxKind.AsKeyword, - asserts: SyntaxKind.AssertsKeyword, - assert: SyntaxKind.AssertKeyword, - bigint: SyntaxKind.BigIntKeyword, - boolean: SyntaxKind.BooleanKeyword, - break: SyntaxKind.BreakKeyword, - case: SyntaxKind.CaseKeyword, - catch: SyntaxKind.CatchKeyword, - class: SyntaxKind.ClassKeyword, - continue: SyntaxKind.ContinueKeyword, - const: SyntaxKind.ConstKeyword, - ["" + "constructor"]: SyntaxKind.ConstructorKeyword, - debugger: SyntaxKind.DebuggerKeyword, - declare: SyntaxKind.DeclareKeyword, - default: SyntaxKind.DefaultKeyword, - delete: SyntaxKind.DeleteKeyword, - do: SyntaxKind.DoKeyword, - else: SyntaxKind.ElseKeyword, - enum: SyntaxKind.EnumKeyword, - export: SyntaxKind.ExportKeyword, - extends: SyntaxKind.ExtendsKeyword, - false: SyntaxKind.FalseKeyword, - finally: SyntaxKind.FinallyKeyword, - for: SyntaxKind.ForKeyword, - from: SyntaxKind.FromKeyword, - function: SyntaxKind.FunctionKeyword, - get: SyntaxKind.GetKeyword, - if: SyntaxKind.IfKeyword, - implements: SyntaxKind.ImplementsKeyword, - import: SyntaxKind.ImportKeyword, - in: SyntaxKind.InKeyword, - infer: SyntaxKind.InferKeyword, - instanceof: SyntaxKind.InstanceOfKeyword, - interface: SyntaxKind.InterfaceKeyword, - intrinsic: SyntaxKind.IntrinsicKeyword, - is: SyntaxKind.IsKeyword, - keyof: SyntaxKind.KeyOfKeyword, - let: SyntaxKind.LetKeyword, - module: SyntaxKind.ModuleKeyword, - namespace: SyntaxKind.NamespaceKeyword, - never: SyntaxKind.NeverKeyword, - new: SyntaxKind.NewKeyword, - null: SyntaxKind.NullKeyword, - number: SyntaxKind.NumberKeyword, - object: SyntaxKind.ObjectKeyword, - package: SyntaxKind.PackageKeyword, - private: SyntaxKind.PrivateKeyword, - protected: SyntaxKind.ProtectedKeyword, - public: SyntaxKind.PublicKeyword, - override: SyntaxKind.OverrideKeyword, - readonly: SyntaxKind.ReadonlyKeyword, - require: SyntaxKind.RequireKeyword, - global: SyntaxKind.GlobalKeyword, - return: SyntaxKind.ReturnKeyword, - set: SyntaxKind.SetKeyword, - static: SyntaxKind.StaticKeyword, - string: SyntaxKind.StringKeyword, - super: SyntaxKind.SuperKeyword, - switch: SyntaxKind.SwitchKeyword, - symbol: SyntaxKind.SymbolKeyword, - this: SyntaxKind.ThisKeyword, - throw: SyntaxKind.ThrowKeyword, - true: SyntaxKind.TrueKeyword, - try: SyntaxKind.TryKeyword, - type: SyntaxKind.TypeKeyword, - typeof: SyntaxKind.TypeOfKeyword, - undefined: SyntaxKind.UndefinedKeyword, - unique: SyntaxKind.UniqueKeyword, - unknown: SyntaxKind.UnknownKeyword, - var: SyntaxKind.VarKeyword, - void: SyntaxKind.VoidKeyword, - while: SyntaxKind.WhileKeyword, - with: SyntaxKind.WithKeyword, - yield: SyntaxKind.YieldKeyword, - async: SyntaxKind.AsyncKeyword, - await: SyntaxKind.AwaitKeyword, - of: SyntaxKind.OfKeyword, - }; +/* @internal */ +export function tokenIsIdentifierOrKeyword(token: SyntaxKind): boolean { + return token >= SyntaxKind.Identifier; +} - const textToKeyword = new Map(getEntries(textToKeywordObj)); - - const textToToken = new Map(getEntries({ - ...textToKeywordObj, - "{": SyntaxKind.OpenBraceToken, - "}": SyntaxKind.CloseBraceToken, - "(": SyntaxKind.OpenParenToken, - ")": SyntaxKind.CloseParenToken, - "[": SyntaxKind.OpenBracketToken, - "]": SyntaxKind.CloseBracketToken, - ".": SyntaxKind.DotToken, - "...": SyntaxKind.DotDotDotToken, - ";": SyntaxKind.SemicolonToken, - ",": SyntaxKind.CommaToken, - "<": SyntaxKind.LessThanToken, - ">": SyntaxKind.GreaterThanToken, - "<=": SyntaxKind.LessThanEqualsToken, - ">=": SyntaxKind.GreaterThanEqualsToken, - "==": SyntaxKind.EqualsEqualsToken, - "!=": SyntaxKind.ExclamationEqualsToken, - "===": SyntaxKind.EqualsEqualsEqualsToken, - "!==": SyntaxKind.ExclamationEqualsEqualsToken, - "=>": SyntaxKind.EqualsGreaterThanToken, - "+": SyntaxKind.PlusToken, - "-": SyntaxKind.MinusToken, - "**": SyntaxKind.AsteriskAsteriskToken, - "*": SyntaxKind.AsteriskToken, - "/": SyntaxKind.SlashToken, - "%": SyntaxKind.PercentToken, - "++": SyntaxKind.PlusPlusToken, - "--": SyntaxKind.MinusMinusToken, - "<<": SyntaxKind.LessThanLessThanToken, - ">": SyntaxKind.GreaterThanGreaterThanToken, - ">>>": SyntaxKind.GreaterThanGreaterThanGreaterThanToken, - "&": SyntaxKind.AmpersandToken, - "|": SyntaxKind.BarToken, - "^": SyntaxKind.CaretToken, - "!": SyntaxKind.ExclamationToken, - "~": SyntaxKind.TildeToken, - "&&": SyntaxKind.AmpersandAmpersandToken, - "||": SyntaxKind.BarBarToken, - "?": SyntaxKind.QuestionToken, - "??": SyntaxKind.QuestionQuestionToken, - "?.": SyntaxKind.QuestionDotToken, - ":": SyntaxKind.ColonToken, - "=": SyntaxKind.EqualsToken, - "+=": SyntaxKind.PlusEqualsToken, - "-=": SyntaxKind.MinusEqualsToken, - "*=": SyntaxKind.AsteriskEqualsToken, - "**=": SyntaxKind.AsteriskAsteriskEqualsToken, - "/=": SyntaxKind.SlashEqualsToken, - "%=": SyntaxKind.PercentEqualsToken, - "<<=": SyntaxKind.LessThanLessThanEqualsToken, - ">>=": SyntaxKind.GreaterThanGreaterThanEqualsToken, - ">>>=": SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, - "&=": SyntaxKind.AmpersandEqualsToken, - "|=": SyntaxKind.BarEqualsToken, - "^=": SyntaxKind.CaretEqualsToken, - "||=": SyntaxKind.BarBarEqualsToken, - "&&=": SyntaxKind.AmpersandAmpersandEqualsToken, - "??=": SyntaxKind.QuestionQuestionEqualsToken, - "@": SyntaxKind.AtToken, - "#": SyntaxKind.HashToken, - "`": SyntaxKind.BacktickToken, - })); - - /* - As per ECMAScript Language Specification 3th Edition, Section 7.6: Identifiers - IdentifierStart :: - Can contain Unicode 3.0.0 categories: - Uppercase letter (Lu), - Lowercase letter (Ll), - Titlecase letter (Lt), - Modifier letter (Lm), - Other letter (Lo), or - Letter number (Nl). - IdentifierPart :: = - Can contain IdentifierStart + Unicode 3.0.0 categories: - Non-spacing mark (Mn), - Combining spacing mark (Mc), - Decimal number (Nd), or - Connector punctuation (Pc). - - Codepoint ranges for ES3 Identifiers are extracted from the Unicode 3.0.0 specification at: - http://www.unicode.org/Public/3.0-Update/UnicodeData-3.0.0.txt - */ - const unicodeES3IdentifierStart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 543, 546, 563, 592, 685, 688, 696, 699, 705, 720, 721, 736, 740, 750, 750, 890, 890, 902, 902, 904, 906, 908, 908, 910, 929, 931, 974, 976, 983, 986, 1011, 1024, 1153, 1164, 1220, 1223, 1224, 1227, 1228, 1232, 1269, 1272, 1273, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1569, 1594, 1600, 1610, 1649, 1747, 1749, 1749, 1765, 1766, 1786, 1788, 1808, 1808, 1810, 1836, 1920, 1957, 2309, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2699, 2701, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2784, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2870, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 2997, 2999, 3001, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3294, 3294, 3296, 3297, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3424, 3425, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805, 3840, 3840, 3904, 3911, 3913, 3946, 3976, 3979, 4096, 4129, 4131, 4135, 4137, 4138, 4176, 4181, 4256, 4293, 4304, 4342, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4614, 4616, 4678, 4680, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4742, 4744, 4744, 4746, 4749, 4752, 4782, 4784, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4814, 4816, 4822, 4824, 4846, 4848, 4878, 4880, 4880, 4882, 4885, 4888, 4894, 4896, 4934, 4936, 4954, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 6016, 6067, 6176, 6263, 6272, 6312, 7680, 7835, 7840, 7929, 7936, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8319, 8319, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8497, 8499, 8505, 8544, 8579, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12346, 12353, 12436, 12445, 12446, 12449, 12538, 12540, 12542, 12549, 12588, 12593, 12686, 12704, 12727, 13312, 19893, 19968, 40869, 40960, 42124, 44032, 55203, 63744, 64045, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65138, 65140, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; - const unicodeES3IdentifierPart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 543, 546, 563, 592, 685, 688, 696, 699, 705, 720, 721, 736, 740, 750, 750, 768, 846, 864, 866, 890, 890, 902, 902, 904, 906, 908, 908, 910, 929, 931, 974, 976, 983, 986, 1011, 1024, 1153, 1155, 1158, 1164, 1220, 1223, 1224, 1227, 1228, 1232, 1269, 1272, 1273, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1441, 1443, 1465, 1467, 1469, 1471, 1471, 1473, 1474, 1476, 1476, 1488, 1514, 1520, 1522, 1569, 1594, 1600, 1621, 1632, 1641, 1648, 1747, 1749, 1756, 1759, 1768, 1770, 1773, 1776, 1788, 1808, 1836, 1840, 1866, 1920, 1968, 2305, 2307, 2309, 2361, 2364, 2381, 2384, 2388, 2392, 2403, 2406, 2415, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2492, 2494, 2500, 2503, 2504, 2507, 2509, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2562, 2562, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2649, 2652, 2654, 2654, 2662, 2676, 2689, 2691, 2693, 2699, 2701, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2784, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2870, 2873, 2876, 2883, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2913, 2918, 2927, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 2997, 2999, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3031, 3031, 3047, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3134, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3168, 3169, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3262, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3297, 3302, 3311, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3390, 3395, 3398, 3400, 3402, 3405, 3415, 3415, 3424, 3425, 3430, 3439, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3805, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3946, 3953, 3972, 3974, 3979, 3984, 3991, 3993, 4028, 4038, 4038, 4096, 4129, 4131, 4135, 4137, 4138, 4140, 4146, 4150, 4153, 4160, 4169, 4176, 4185, 4256, 4293, 4304, 4342, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4614, 4616, 4678, 4680, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4742, 4744, 4744, 4746, 4749, 4752, 4782, 4784, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4814, 4816, 4822, 4824, 4846, 4848, 4878, 4880, 4880, 4882, 4885, 4888, 4894, 4896, 4934, 4936, 4954, 4969, 4977, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 6016, 6099, 6112, 6121, 6160, 6169, 6176, 6263, 6272, 6313, 7680, 7835, 7840, 7929, 7936, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8255, 8256, 8319, 8319, 8400, 8412, 8417, 8417, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8497, 8499, 8505, 8544, 8579, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12346, 12353, 12436, 12441, 12442, 12445, 12446, 12449, 12542, 12549, 12588, 12593, 12686, 12704, 12727, 13312, 19893, 19968, 40869, 40960, 42124, 44032, 55203, 63744, 64045, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65056, 65059, 65075, 65076, 65101, 65103, 65136, 65138, 65140, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65381, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; - - /* - As per ECMAScript Language Specification 5th Edition, Section 7.6: ISyntaxToken Names and Identifiers - IdentifierStart :: - Can contain Unicode 6.2 categories: - Uppercase letter (Lu), - Lowercase letter (Ll), - Titlecase letter (Lt), - Modifier letter (Lm), - Other letter (Lo), or - Letter number (Nl). - IdentifierPart :: - Can contain IdentifierStart + Unicode 6.2 categories: - Non-spacing mark (Mn), - Combining spacing mark (Mc), - Decimal number (Nd), - Connector punctuation (Pc), - , or - . - - Codepoint ranges for ES5 Identifiers are extracted from the Unicode 6.2 specification at: - http://www.unicode.org/Public/6.2.0/ucd/UnicodeData.txt - */ - const unicodeES5IdentifierStart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2208, 2208, 2210, 2220, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516, 6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409, 7413, 7414, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; - const unicodeES5IdentifierPart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1520, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2048, 2093, 2112, 2139, 2208, 2208, 2210, 2220, 2276, 2302, 2304, 2403, 2406, 2415, 2417, 2423, 2425, 2431, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3161, 3168, 3171, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3396, 3398, 3400, 3402, 3406, 3415, 3415, 3424, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6263, 6272, 6314, 6320, 6389, 6400, 6428, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6617, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7376, 7378, 7380, 7414, 7424, 7654, 7676, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8204, 8205, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 11823, 11823, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12442, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42647, 42655, 42737, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43047, 43072, 43123, 43136, 43204, 43216, 43225, 43232, 43255, 43259, 43259, 43264, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43643, 43648, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65062, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; +/* @internal */ +export function tokenIsIdentifierOrKeywordOrGreaterThan(token: SyntaxKind): boolean { + return token === SyntaxKind.GreaterThanToken || tokenIsIdentifierOrKeyword(token); +} - /** - * Generated by scripts/regenerate-unicode-identifier-parts.js on node v12.4.0 with unicode 12.1 - * based on http://www.unicode.org/reports/tr31/ and https://www.ecma-international.org/ecma-262/6.0/#sec-names-and-keywords - * unicodeESNextIdentifierStart corresponds to the ID_Start and Other_ID_Start property, and - * unicodeESNextIdentifierPart corresponds to ID_Continue, Other_ID_Continue, plus ID_Start and Other_ID_Start - */ - const unicodeESNextIdentifierStart = [65, 90, 97, 122, 170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 895, 895, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1327, 1329, 1366, 1369, 1369, 1376, 1416, 1488, 1514, 1519, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2144, 2154, 2208, 2228, 2230, 2237, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2432, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2556, 2556, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2809, 2809, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3133, 3160, 3162, 3168, 3169, 3200, 3200, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3412, 3414, 3423, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3718, 3722, 3724, 3747, 3749, 3749, 3751, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5880, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6264, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6430, 6480, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7296, 7304, 7312, 7354, 7357, 7359, 7401, 7404, 7406, 7411, 7413, 7414, 7418, 7418, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8472, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12443, 12447, 12449, 12538, 12540, 12543, 12549, 12591, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40943, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42653, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42943, 42946, 42950, 42999, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43261, 43262, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43488, 43492, 43494, 43503, 43514, 43518, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43646, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43824, 43866, 43868, 43879, 43888, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, 65856, 65908, 66176, 66204, 66208, 66256, 66304, 66335, 66349, 66378, 66384, 66421, 66432, 66461, 66464, 66499, 66504, 66511, 66513, 66517, 66560, 66717, 66736, 66771, 66776, 66811, 66816, 66855, 66864, 66915, 67072, 67382, 67392, 67413, 67424, 67431, 67584, 67589, 67592, 67592, 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, 67680, 67702, 67712, 67742, 67808, 67826, 67828, 67829, 67840, 67861, 67872, 67897, 67968, 68023, 68030, 68031, 68096, 68096, 68112, 68115, 68117, 68119, 68121, 68149, 68192, 68220, 68224, 68252, 68288, 68295, 68297, 68324, 68352, 68405, 68416, 68437, 68448, 68466, 68480, 68497, 68608, 68680, 68736, 68786, 68800, 68850, 68864, 68899, 69376, 69404, 69415, 69415, 69424, 69445, 69600, 69622, 69635, 69687, 69763, 69807, 69840, 69864, 69891, 69926, 69956, 69956, 69968, 70002, 70006, 70006, 70019, 70066, 70081, 70084, 70106, 70106, 70108, 70108, 70144, 70161, 70163, 70187, 70272, 70278, 70280, 70280, 70282, 70285, 70287, 70301, 70303, 70312, 70320, 70366, 70405, 70412, 70415, 70416, 70419, 70440, 70442, 70448, 70450, 70451, 70453, 70457, 70461, 70461, 70480, 70480, 70493, 70497, 70656, 70708, 70727, 70730, 70751, 70751, 70784, 70831, 70852, 70853, 70855, 70855, 71040, 71086, 71128, 71131, 71168, 71215, 71236, 71236, 71296, 71338, 71352, 71352, 71424, 71450, 71680, 71723, 71840, 71903, 71935, 71935, 72096, 72103, 72106, 72144, 72161, 72161, 72163, 72163, 72192, 72192, 72203, 72242, 72250, 72250, 72272, 72272, 72284, 72329, 72349, 72349, 72384, 72440, 72704, 72712, 72714, 72750, 72768, 72768, 72818, 72847, 72960, 72966, 72968, 72969, 72971, 73008, 73030, 73030, 73056, 73061, 73063, 73064, 73066, 73097, 73112, 73112, 73440, 73458, 73728, 74649, 74752, 74862, 74880, 75075, 77824, 78894, 82944, 83526, 92160, 92728, 92736, 92766, 92880, 92909, 92928, 92975, 92992, 92995, 93027, 93047, 93053, 93071, 93760, 93823, 93952, 94026, 94032, 94032, 94099, 94111, 94176, 94177, 94179, 94179, 94208, 100343, 100352, 101106, 110592, 110878, 110928, 110930, 110948, 110951, 110960, 111355, 113664, 113770, 113776, 113788, 113792, 113800, 113808, 113817, 119808, 119892, 119894, 119964, 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, 123136, 123180, 123191, 123197, 123214, 123214, 123584, 123627, 124928, 125124, 125184, 125251, 125259, 125259, 126464, 126467, 126469, 126495, 126497, 126498, 126500, 126500, 126503, 126503, 126505, 126514, 126516, 126519, 126521, 126521, 126523, 126523, 126530, 126530, 126535, 126535, 126537, 126537, 126539, 126539, 126541, 126543, 126545, 126546, 126548, 126548, 126551, 126551, 126553, 126553, 126555, 126555, 126557, 126557, 126559, 126559, 126561, 126562, 126564, 126564, 126567, 126570, 126572, 126578, 126580, 126583, 126585, 126588, 126590, 126590, 126592, 126601, 126603, 126619, 126625, 126627, 126629, 126633, 126635, 126651, 131072, 173782, 173824, 177972, 177984, 178205, 178208, 183969, 183984, 191456, 194560, 195101]; - const unicodeESNextIdentifierPart = [48, 57, 65, 90, 95, 95, 97, 122, 170, 170, 181, 181, 183, 183, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 895, 895, 902, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1327, 1329, 1366, 1369, 1369, 1376, 1416, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1519, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2045, 2045, 2048, 2093, 2112, 2139, 2144, 2154, 2208, 2228, 2230, 2237, 2259, 2273, 2275, 2403, 2406, 2415, 2417, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2556, 2556, 2558, 2558, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2809, 2815, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3072, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3162, 3168, 3171, 3174, 3183, 3200, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3328, 3331, 3333, 3340, 3342, 3344, 3346, 3396, 3398, 3400, 3402, 3406, 3412, 3415, 3423, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3558, 3567, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3718, 3722, 3724, 3747, 3749, 3749, 3751, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4969, 4977, 4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5880, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6264, 6272, 6314, 6320, 6389, 6400, 6430, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6618, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6832, 6845, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7296, 7304, 7312, 7354, 7357, 7359, 7376, 7378, 7380, 7418, 7424, 7673, 7675, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8472, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12447, 12449, 12538, 12540, 12543, 12549, 12591, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40943, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42737, 42775, 42783, 42786, 42888, 42891, 42943, 42946, 42950, 42999, 43047, 43072, 43123, 43136, 43205, 43216, 43225, 43232, 43255, 43259, 43259, 43261, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43488, 43518, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43824, 43866, 43868, 43879, 43888, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65071, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, 65856, 65908, 66045, 66045, 66176, 66204, 66208, 66256, 66272, 66272, 66304, 66335, 66349, 66378, 66384, 66426, 66432, 66461, 66464, 66499, 66504, 66511, 66513, 66517, 66560, 66717, 66720, 66729, 66736, 66771, 66776, 66811, 66816, 66855, 66864, 66915, 67072, 67382, 67392, 67413, 67424, 67431, 67584, 67589, 67592, 67592, 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, 67680, 67702, 67712, 67742, 67808, 67826, 67828, 67829, 67840, 67861, 67872, 67897, 67968, 68023, 68030, 68031, 68096, 68099, 68101, 68102, 68108, 68115, 68117, 68119, 68121, 68149, 68152, 68154, 68159, 68159, 68192, 68220, 68224, 68252, 68288, 68295, 68297, 68326, 68352, 68405, 68416, 68437, 68448, 68466, 68480, 68497, 68608, 68680, 68736, 68786, 68800, 68850, 68864, 68903, 68912, 68921, 69376, 69404, 69415, 69415, 69424, 69456, 69600, 69622, 69632, 69702, 69734, 69743, 69759, 69818, 69840, 69864, 69872, 69881, 69888, 69940, 69942, 69951, 69956, 69958, 69968, 70003, 70006, 70006, 70016, 70084, 70089, 70092, 70096, 70106, 70108, 70108, 70144, 70161, 70163, 70199, 70206, 70206, 70272, 70278, 70280, 70280, 70282, 70285, 70287, 70301, 70303, 70312, 70320, 70378, 70384, 70393, 70400, 70403, 70405, 70412, 70415, 70416, 70419, 70440, 70442, 70448, 70450, 70451, 70453, 70457, 70459, 70468, 70471, 70472, 70475, 70477, 70480, 70480, 70487, 70487, 70493, 70499, 70502, 70508, 70512, 70516, 70656, 70730, 70736, 70745, 70750, 70751, 70784, 70853, 70855, 70855, 70864, 70873, 71040, 71093, 71096, 71104, 71128, 71133, 71168, 71232, 71236, 71236, 71248, 71257, 71296, 71352, 71360, 71369, 71424, 71450, 71453, 71467, 71472, 71481, 71680, 71738, 71840, 71913, 71935, 71935, 72096, 72103, 72106, 72151, 72154, 72161, 72163, 72164, 72192, 72254, 72263, 72263, 72272, 72345, 72349, 72349, 72384, 72440, 72704, 72712, 72714, 72758, 72760, 72768, 72784, 72793, 72818, 72847, 72850, 72871, 72873, 72886, 72960, 72966, 72968, 72969, 72971, 73014, 73018, 73018, 73020, 73021, 73023, 73031, 73040, 73049, 73056, 73061, 73063, 73064, 73066, 73102, 73104, 73105, 73107, 73112, 73120, 73129, 73440, 73462, 73728, 74649, 74752, 74862, 74880, 75075, 77824, 78894, 82944, 83526, 92160, 92728, 92736, 92766, 92768, 92777, 92880, 92909, 92912, 92916, 92928, 92982, 92992, 92995, 93008, 93017, 93027, 93047, 93053, 93071, 93760, 93823, 93952, 94026, 94031, 94087, 94095, 94111, 94176, 94177, 94179, 94179, 94208, 100343, 100352, 101106, 110592, 110878, 110928, 110930, 110948, 110951, 110960, 111355, 113664, 113770, 113776, 113788, 113792, 113800, 113808, 113817, 113821, 113822, 119141, 119145, 119149, 119154, 119163, 119170, 119173, 119179, 119210, 119213, 119362, 119364, 119808, 119892, 119894, 119964, 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, 120782, 120831, 121344, 121398, 121403, 121452, 121461, 121461, 121476, 121476, 121499, 121503, 121505, 121519, 122880, 122886, 122888, 122904, 122907, 122913, 122915, 122916, 122918, 122922, 123136, 123180, 123184, 123197, 123200, 123209, 123214, 123214, 123584, 123641, 124928, 125124, 125136, 125142, 125184, 125259, 125264, 125273, 126464, 126467, 126469, 126495, 126497, 126498, 126500, 126500, 126503, 126503, 126505, 126514, 126516, 126519, 126521, 126521, 126523, 126523, 126530, 126530, 126535, 126535, 126537, 126537, 126539, 126539, 126541, 126543, 126545, 126546, 126548, 126548, 126551, 126551, 126553, 126553, 126555, 126555, 126557, 126557, 126559, 126559, 126561, 126562, 126564, 126564, 126567, 126570, 126572, 126578, 126580, 126583, 126585, 126588, 126590, 126590, 126592, 126601, 126603, 126619, 126625, 126627, 126629, 126633, 126635, 126651, 131072, 173782, 173824, 177972, 177984, 178205, 178208, 183969, 183984, 191456, 194560, 195101, 917760, 917999]; +export interface Scanner { + getStartPos(): number; + getToken(): SyntaxKind; + getTextPos(): number; + getTokenPos(): number; + getTokenText(): string; + getTokenValue(): string; + hasUnicodeEscape(): boolean; + hasExtendedUnicodeEscape(): boolean; + hasPrecedingLineBreak(): boolean; + /* @internal */ + hasPrecedingJSDocComment(): boolean; + isIdentifier(): boolean; + isReservedWord(): boolean; + isUnterminated(): boolean; + /* @internal */ + getNumericLiteralFlags(): TokenFlags; + /* @internal */ + getCommentDirectives(): CommentDirective[] | undefined; + /* @internal */ + getTokenFlags(): TokenFlags; + reScanGreaterToken(): SyntaxKind; + reScanSlashToken(): SyntaxKind; + reScanAsteriskEqualsToken(): SyntaxKind; + reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind; + reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind; + scanJsxIdentifier(): SyntaxKind; + scanJsxAttributeValue(): SyntaxKind; + reScanJsxAttributeValue(): SyntaxKind; + reScanJsxToken(allowMultilineJsxText?: boolean): JsxTokenSyntaxKind; + reScanLessThanToken(): SyntaxKind; + reScanHashToken(): SyntaxKind; + reScanQuestionToken(): SyntaxKind; + reScanInvalidIdentifier(): SyntaxKind; + scanJsxToken(): JsxTokenSyntaxKind; + scanJsDocToken(): JSDocSyntaxKind; + scan(): SyntaxKind; + + getText(): string; + /* @internal */ + clearCommentDirectives(): void; + // Sets the text for the scanner to scan. An optional subrange starting point and length + // can be provided to have the scanner only scan a portion of the text. + setText(text: string | undefined, start?: number, length?: number): void; + setOnError(onError: ErrorCallback | undefined): void; + setScriptTarget(scriptTarget: ScriptTarget): void; + setLanguageVariant(variant: LanguageVariant): void; + setTextPos(textPos: number): void; + /* @internal */ + setInJSDocType(inType: boolean): void; + // Invokes the provided callback then unconditionally restores the scanner to the state it + // was in immediately prior to invoking the callback. The result of invoking the callback + // is returned from this function. + lookAhead(callback: () => T): T; + + // Invokes the callback with the scanner set to scan the specified range. When the callback + // returns, the scanner is restored to the state it was in before scanRange was called. + scanRange(start: number, length: number, callback: () => T): T; + + // Invokes the provided callback. If the callback returns something falsy, then it restores + // the scanner to the state it was in immediately prior to invoking the callback. If the + // callback returns something truthy, then the scanner state is not rolled back. The result + // of invoking the callback is returned from this function. + tryScan(callback: () => T): T; +} - /** - * Test for whether a single line comment with leading whitespace trimmed's text contains a directive. - */ - const commentDirectiveRegExSingleLine = /^\/\/\/?\s*@(ts-expect-error|ts-ignore)/; +/** @internal */ +export const textToKeywordObj: MapLike = { + abstract: SyntaxKind.AbstractKeyword, + any: SyntaxKind.AnyKeyword, + as: SyntaxKind.AsKeyword, + asserts: SyntaxKind.AssertsKeyword, + assert: SyntaxKind.AssertKeyword, + bigint: SyntaxKind.BigIntKeyword, + boolean: SyntaxKind.BooleanKeyword, + break: SyntaxKind.BreakKeyword, + case: SyntaxKind.CaseKeyword, + catch: SyntaxKind.CatchKeyword, + class: SyntaxKind.ClassKeyword, + continue: SyntaxKind.ContinueKeyword, + const: SyntaxKind.ConstKeyword, + ["" + "constructor"]: SyntaxKind.ConstructorKeyword, + debugger: SyntaxKind.DebuggerKeyword, + declare: SyntaxKind.DeclareKeyword, + default: SyntaxKind.DefaultKeyword, + delete: SyntaxKind.DeleteKeyword, + do: SyntaxKind.DoKeyword, + else: SyntaxKind.ElseKeyword, + enum: SyntaxKind.EnumKeyword, + export: SyntaxKind.ExportKeyword, + extends: SyntaxKind.ExtendsKeyword, + false: SyntaxKind.FalseKeyword, + finally: SyntaxKind.FinallyKeyword, + for: SyntaxKind.ForKeyword, + from: SyntaxKind.FromKeyword, + function: SyntaxKind.FunctionKeyword, + get: SyntaxKind.GetKeyword, + if: SyntaxKind.IfKeyword, + implements: SyntaxKind.ImplementsKeyword, + import: SyntaxKind.ImportKeyword, + in: SyntaxKind.InKeyword, + infer: SyntaxKind.InferKeyword, + instanceof: SyntaxKind.InstanceOfKeyword, + interface: SyntaxKind.InterfaceKeyword, + intrinsic: SyntaxKind.IntrinsicKeyword, + is: SyntaxKind.IsKeyword, + keyof: SyntaxKind.KeyOfKeyword, + let: SyntaxKind.LetKeyword, + module: SyntaxKind.ModuleKeyword, + namespace: SyntaxKind.NamespaceKeyword, + never: SyntaxKind.NeverKeyword, + new: SyntaxKind.NewKeyword, + null: SyntaxKind.NullKeyword, + number: SyntaxKind.NumberKeyword, + object: SyntaxKind.ObjectKeyword, + package: SyntaxKind.PackageKeyword, + private: SyntaxKind.PrivateKeyword, + protected: SyntaxKind.ProtectedKeyword, + public: SyntaxKind.PublicKeyword, + override: SyntaxKind.OverrideKeyword, + readonly: SyntaxKind.ReadonlyKeyword, + require: SyntaxKind.RequireKeyword, + global: SyntaxKind.GlobalKeyword, + return: SyntaxKind.ReturnKeyword, + set: SyntaxKind.SetKeyword, + static: SyntaxKind.StaticKeyword, + string: SyntaxKind.StringKeyword, + super: SyntaxKind.SuperKeyword, + switch: SyntaxKind.SwitchKeyword, + symbol: SyntaxKind.SymbolKeyword, + this: SyntaxKind.ThisKeyword, + throw: SyntaxKind.ThrowKeyword, + true: SyntaxKind.TrueKeyword, + try: SyntaxKind.TryKeyword, + type: SyntaxKind.TypeKeyword, + typeof: SyntaxKind.TypeOfKeyword, + undefined: SyntaxKind.UndefinedKeyword, + unique: SyntaxKind.UniqueKeyword, + unknown: SyntaxKind.UnknownKeyword, + var: SyntaxKind.VarKeyword, + void: SyntaxKind.VoidKeyword, + while: SyntaxKind.WhileKeyword, + with: SyntaxKind.WithKeyword, + yield: SyntaxKind.YieldKeyword, + async: SyntaxKind.AsyncKeyword, + await: SyntaxKind.AwaitKeyword, + of: SyntaxKind.OfKeyword, +}; + +const textToKeyword = new Map(getEntries(textToKeywordObj)); + +const textToToken = new Map(getEntries({ + ...textToKeywordObj, + "{": SyntaxKind.OpenBraceToken, + "}": SyntaxKind.CloseBraceToken, + "(": SyntaxKind.OpenParenToken, + ")": SyntaxKind.CloseParenToken, + "[": SyntaxKind.OpenBracketToken, + "]": SyntaxKind.CloseBracketToken, + ".": SyntaxKind.DotToken, + "...": SyntaxKind.DotDotDotToken, + ";": SyntaxKind.SemicolonToken, + ",": SyntaxKind.CommaToken, + "<": SyntaxKind.LessThanToken, + ">": SyntaxKind.GreaterThanToken, + "<=": SyntaxKind.LessThanEqualsToken, + ">=": SyntaxKind.GreaterThanEqualsToken, + "==": SyntaxKind.EqualsEqualsToken, + "!=": SyntaxKind.ExclamationEqualsToken, + "===": SyntaxKind.EqualsEqualsEqualsToken, + "!==": SyntaxKind.ExclamationEqualsEqualsToken, + "=>": SyntaxKind.EqualsGreaterThanToken, + "+": SyntaxKind.PlusToken, + "-": SyntaxKind.MinusToken, + "**": SyntaxKind.AsteriskAsteriskToken, + "*": SyntaxKind.AsteriskToken, + "/": SyntaxKind.SlashToken, + "%": SyntaxKind.PercentToken, + "++": SyntaxKind.PlusPlusToken, + "--": SyntaxKind.MinusMinusToken, + "<<": SyntaxKind.LessThanLessThanToken, + ">": SyntaxKind.GreaterThanGreaterThanToken, + ">>>": SyntaxKind.GreaterThanGreaterThanGreaterThanToken, + "&": SyntaxKind.AmpersandToken, + "|": SyntaxKind.BarToken, + "^": SyntaxKind.CaretToken, + "!": SyntaxKind.ExclamationToken, + "~": SyntaxKind.TildeToken, + "&&": SyntaxKind.AmpersandAmpersandToken, + "||": SyntaxKind.BarBarToken, + "?": SyntaxKind.QuestionToken, + "??": SyntaxKind.QuestionQuestionToken, + "?.": SyntaxKind.QuestionDotToken, + ":": SyntaxKind.ColonToken, + "=": SyntaxKind.EqualsToken, + "+=": SyntaxKind.PlusEqualsToken, + "-=": SyntaxKind.MinusEqualsToken, + "*=": SyntaxKind.AsteriskEqualsToken, + "**=": SyntaxKind.AsteriskAsteriskEqualsToken, + "/=": SyntaxKind.SlashEqualsToken, + "%=": SyntaxKind.PercentEqualsToken, + "<<=": SyntaxKind.LessThanLessThanEqualsToken, + ">>=": SyntaxKind.GreaterThanGreaterThanEqualsToken, + ">>>=": SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, + "&=": SyntaxKind.AmpersandEqualsToken, + "|=": SyntaxKind.BarEqualsToken, + "^=": SyntaxKind.CaretEqualsToken, + "||=": SyntaxKind.BarBarEqualsToken, + "&&=": SyntaxKind.AmpersandAmpersandEqualsToken, + "??=": SyntaxKind.QuestionQuestionEqualsToken, + "@": SyntaxKind.AtToken, + "#": SyntaxKind.HashToken, + "`": SyntaxKind.BacktickToken, +})); + +/* + As per ECMAScript Language Specification 3th Edition, Section 7.6: Identifiers + IdentifierStart :: + Can contain Unicode 3.0.0 categories: + Uppercase letter (Lu), + Lowercase letter (Ll), + Titlecase letter (Lt), + Modifier letter (Lm), + Other letter (Lo), or + Letter number (Nl). + IdentifierPart :: = + Can contain IdentifierStart + Unicode 3.0.0 categories: + Non-spacing mark (Mn), + Combining spacing mark (Mc), + Decimal number (Nd), or + Connector punctuation (Pc). + + Codepoint ranges for ES3 Identifiers are extracted from the Unicode 3.0.0 specification at: + http://www.unicode.org/Public/3.0-Update/UnicodeData-3.0.0.txt +*/ +const unicodeES3IdentifierStart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 543, 546, 563, 592, 685, 688, 696, 699, 705, 720, 721, 736, 740, 750, 750, 890, 890, 902, 902, 904, 906, 908, 908, 910, 929, 931, 974, 976, 983, 986, 1011, 1024, 1153, 1164, 1220, 1223, 1224, 1227, 1228, 1232, 1269, 1272, 1273, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1569, 1594, 1600, 1610, 1649, 1747, 1749, 1749, 1765, 1766, 1786, 1788, 1808, 1808, 1810, 1836, 1920, 1957, 2309, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2699, 2701, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2784, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2870, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 2997, 2999, 3001, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3294, 3294, 3296, 3297, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3424, 3425, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805, 3840, 3840, 3904, 3911, 3913, 3946, 3976, 3979, 4096, 4129, 4131, 4135, 4137, 4138, 4176, 4181, 4256, 4293, 4304, 4342, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4614, 4616, 4678, 4680, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4742, 4744, 4744, 4746, 4749, 4752, 4782, 4784, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4814, 4816, 4822, 4824, 4846, 4848, 4878, 4880, 4880, 4882, 4885, 4888, 4894, 4896, 4934, 4936, 4954, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 6016, 6067, 6176, 6263, 6272, 6312, 7680, 7835, 7840, 7929, 7936, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8319, 8319, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8497, 8499, 8505, 8544, 8579, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12346, 12353, 12436, 12445, 12446, 12449, 12538, 12540, 12542, 12549, 12588, 12593, 12686, 12704, 12727, 13312, 19893, 19968, 40869, 40960, 42124, 44032, 55203, 63744, 64045, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65138, 65140, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; +const unicodeES3IdentifierPart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 543, 546, 563, 592, 685, 688, 696, 699, 705, 720, 721, 736, 740, 750, 750, 768, 846, 864, 866, 890, 890, 902, 902, 904, 906, 908, 908, 910, 929, 931, 974, 976, 983, 986, 1011, 1024, 1153, 1155, 1158, 1164, 1220, 1223, 1224, 1227, 1228, 1232, 1269, 1272, 1273, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1441, 1443, 1465, 1467, 1469, 1471, 1471, 1473, 1474, 1476, 1476, 1488, 1514, 1520, 1522, 1569, 1594, 1600, 1621, 1632, 1641, 1648, 1747, 1749, 1756, 1759, 1768, 1770, 1773, 1776, 1788, 1808, 1836, 1840, 1866, 1920, 1968, 2305, 2307, 2309, 2361, 2364, 2381, 2384, 2388, 2392, 2403, 2406, 2415, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2492, 2494, 2500, 2503, 2504, 2507, 2509, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2562, 2562, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2649, 2652, 2654, 2654, 2662, 2676, 2689, 2691, 2693, 2699, 2701, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2784, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2870, 2873, 2876, 2883, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2913, 2918, 2927, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 2997, 2999, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3031, 3031, 3047, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3134, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3168, 3169, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3262, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3297, 3302, 3311, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3390, 3395, 3398, 3400, 3402, 3405, 3415, 3415, 3424, 3425, 3430, 3439, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3805, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3946, 3953, 3972, 3974, 3979, 3984, 3991, 3993, 4028, 4038, 4038, 4096, 4129, 4131, 4135, 4137, 4138, 4140, 4146, 4150, 4153, 4160, 4169, 4176, 4185, 4256, 4293, 4304, 4342, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4614, 4616, 4678, 4680, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4742, 4744, 4744, 4746, 4749, 4752, 4782, 4784, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4814, 4816, 4822, 4824, 4846, 4848, 4878, 4880, 4880, 4882, 4885, 4888, 4894, 4896, 4934, 4936, 4954, 4969, 4977, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 6016, 6099, 6112, 6121, 6160, 6169, 6176, 6263, 6272, 6313, 7680, 7835, 7840, 7929, 7936, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8255, 8256, 8319, 8319, 8400, 8412, 8417, 8417, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8497, 8499, 8505, 8544, 8579, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12346, 12353, 12436, 12441, 12442, 12445, 12446, 12449, 12542, 12549, 12588, 12593, 12686, 12704, 12727, 13312, 19893, 19968, 40869, 40960, 42124, 44032, 55203, 63744, 64045, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65056, 65059, 65075, 65076, 65101, 65103, 65136, 65138, 65140, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65381, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; + +/* + As per ECMAScript Language Specification 5th Edition, Section 7.6: ISyntaxToken Names and Identifiers + IdentifierStart :: + Can contain Unicode 6.2 categories: + Uppercase letter (Lu), + Lowercase letter (Ll), + Titlecase letter (Lt), + Modifier letter (Lm), + Other letter (Lo), or + Letter number (Nl). + IdentifierPart :: + Can contain IdentifierStart + Unicode 6.2 categories: + Non-spacing mark (Mn), + Combining spacing mark (Mc), + Decimal number (Nd), + Connector punctuation (Pc), + , or + . + + Codepoint ranges for ES5 Identifiers are extracted from the Unicode 6.2 specification at: + http://www.unicode.org/Public/6.2.0/ucd/UnicodeData.txt +*/ +const unicodeES5IdentifierStart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2208, 2208, 2210, 2220, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516, 6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409, 7413, 7414, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; +const unicodeES5IdentifierPart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1520, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2048, 2093, 2112, 2139, 2208, 2208, 2210, 2220, 2276, 2302, 2304, 2403, 2406, 2415, 2417, 2423, 2425, 2431, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3161, 3168, 3171, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3396, 3398, 3400, 3402, 3406, 3415, 3415, 3424, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6263, 6272, 6314, 6320, 6389, 6400, 6428, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6617, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7376, 7378, 7380, 7414, 7424, 7654, 7676, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8204, 8205, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 11823, 11823, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12442, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42647, 42655, 42737, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43047, 43072, 43123, 43136, 43204, 43216, 43225, 43232, 43255, 43259, 43259, 43264, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43643, 43648, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65062, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; + +/** + * Generated by scripts/regenerate-unicode-identifier-parts.js on node v12.4.0 with unicode 12.1 + * based on http://www.unicode.org/reports/tr31/ and https://www.ecma-international.org/ecma-262/6.0/#sec-names-and-keywords + * unicodeESNextIdentifierStart corresponds to the ID_Start and Other_ID_Start property, and + * unicodeESNextIdentifierPart corresponds to ID_Continue, Other_ID_Continue, plus ID_Start and Other_ID_Start + */ +const unicodeESNextIdentifierStart = [65, 90, 97, 122, 170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 895, 895, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1327, 1329, 1366, 1369, 1369, 1376, 1416, 1488, 1514, 1519, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2144, 2154, 2208, 2228, 2230, 2237, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2432, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2556, 2556, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2809, 2809, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3133, 3160, 3162, 3168, 3169, 3200, 3200, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3412, 3414, 3423, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3718, 3722, 3724, 3747, 3749, 3749, 3751, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5880, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6264, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6430, 6480, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7296, 7304, 7312, 7354, 7357, 7359, 7401, 7404, 7406, 7411, 7413, 7414, 7418, 7418, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8472, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12443, 12447, 12449, 12538, 12540, 12543, 12549, 12591, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40943, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42653, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42943, 42946, 42950, 42999, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43261, 43262, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43488, 43492, 43494, 43503, 43514, 43518, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43646, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43824, 43866, 43868, 43879, 43888, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, 65856, 65908, 66176, 66204, 66208, 66256, 66304, 66335, 66349, 66378, 66384, 66421, 66432, 66461, 66464, 66499, 66504, 66511, 66513, 66517, 66560, 66717, 66736, 66771, 66776, 66811, 66816, 66855, 66864, 66915, 67072, 67382, 67392, 67413, 67424, 67431, 67584, 67589, 67592, 67592, 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, 67680, 67702, 67712, 67742, 67808, 67826, 67828, 67829, 67840, 67861, 67872, 67897, 67968, 68023, 68030, 68031, 68096, 68096, 68112, 68115, 68117, 68119, 68121, 68149, 68192, 68220, 68224, 68252, 68288, 68295, 68297, 68324, 68352, 68405, 68416, 68437, 68448, 68466, 68480, 68497, 68608, 68680, 68736, 68786, 68800, 68850, 68864, 68899, 69376, 69404, 69415, 69415, 69424, 69445, 69600, 69622, 69635, 69687, 69763, 69807, 69840, 69864, 69891, 69926, 69956, 69956, 69968, 70002, 70006, 70006, 70019, 70066, 70081, 70084, 70106, 70106, 70108, 70108, 70144, 70161, 70163, 70187, 70272, 70278, 70280, 70280, 70282, 70285, 70287, 70301, 70303, 70312, 70320, 70366, 70405, 70412, 70415, 70416, 70419, 70440, 70442, 70448, 70450, 70451, 70453, 70457, 70461, 70461, 70480, 70480, 70493, 70497, 70656, 70708, 70727, 70730, 70751, 70751, 70784, 70831, 70852, 70853, 70855, 70855, 71040, 71086, 71128, 71131, 71168, 71215, 71236, 71236, 71296, 71338, 71352, 71352, 71424, 71450, 71680, 71723, 71840, 71903, 71935, 71935, 72096, 72103, 72106, 72144, 72161, 72161, 72163, 72163, 72192, 72192, 72203, 72242, 72250, 72250, 72272, 72272, 72284, 72329, 72349, 72349, 72384, 72440, 72704, 72712, 72714, 72750, 72768, 72768, 72818, 72847, 72960, 72966, 72968, 72969, 72971, 73008, 73030, 73030, 73056, 73061, 73063, 73064, 73066, 73097, 73112, 73112, 73440, 73458, 73728, 74649, 74752, 74862, 74880, 75075, 77824, 78894, 82944, 83526, 92160, 92728, 92736, 92766, 92880, 92909, 92928, 92975, 92992, 92995, 93027, 93047, 93053, 93071, 93760, 93823, 93952, 94026, 94032, 94032, 94099, 94111, 94176, 94177, 94179, 94179, 94208, 100343, 100352, 101106, 110592, 110878, 110928, 110930, 110948, 110951, 110960, 111355, 113664, 113770, 113776, 113788, 113792, 113800, 113808, 113817, 119808, 119892, 119894, 119964, 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, 123136, 123180, 123191, 123197, 123214, 123214, 123584, 123627, 124928, 125124, 125184, 125251, 125259, 125259, 126464, 126467, 126469, 126495, 126497, 126498, 126500, 126500, 126503, 126503, 126505, 126514, 126516, 126519, 126521, 126521, 126523, 126523, 126530, 126530, 126535, 126535, 126537, 126537, 126539, 126539, 126541, 126543, 126545, 126546, 126548, 126548, 126551, 126551, 126553, 126553, 126555, 126555, 126557, 126557, 126559, 126559, 126561, 126562, 126564, 126564, 126567, 126570, 126572, 126578, 126580, 126583, 126585, 126588, 126590, 126590, 126592, 126601, 126603, 126619, 126625, 126627, 126629, 126633, 126635, 126651, 131072, 173782, 173824, 177972, 177984, 178205, 178208, 183969, 183984, 191456, 194560, 195101]; +const unicodeESNextIdentifierPart = [48, 57, 65, 90, 95, 95, 97, 122, 170, 170, 181, 181, 183, 183, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 895, 895, 902, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1327, 1329, 1366, 1369, 1369, 1376, 1416, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1519, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2045, 2045, 2048, 2093, 2112, 2139, 2144, 2154, 2208, 2228, 2230, 2237, 2259, 2273, 2275, 2403, 2406, 2415, 2417, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2556, 2556, 2558, 2558, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2809, 2815, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3072, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3162, 3168, 3171, 3174, 3183, 3200, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3328, 3331, 3333, 3340, 3342, 3344, 3346, 3396, 3398, 3400, 3402, 3406, 3412, 3415, 3423, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3558, 3567, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3718, 3722, 3724, 3747, 3749, 3749, 3751, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4969, 4977, 4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5880, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6264, 6272, 6314, 6320, 6389, 6400, 6430, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6618, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6832, 6845, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7296, 7304, 7312, 7354, 7357, 7359, 7376, 7378, 7380, 7418, 7424, 7673, 7675, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8472, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12447, 12449, 12538, 12540, 12543, 12549, 12591, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40943, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42737, 42775, 42783, 42786, 42888, 42891, 42943, 42946, 42950, 42999, 43047, 43072, 43123, 43136, 43205, 43216, 43225, 43232, 43255, 43259, 43259, 43261, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43488, 43518, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43824, 43866, 43868, 43879, 43888, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65071, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, 65856, 65908, 66045, 66045, 66176, 66204, 66208, 66256, 66272, 66272, 66304, 66335, 66349, 66378, 66384, 66426, 66432, 66461, 66464, 66499, 66504, 66511, 66513, 66517, 66560, 66717, 66720, 66729, 66736, 66771, 66776, 66811, 66816, 66855, 66864, 66915, 67072, 67382, 67392, 67413, 67424, 67431, 67584, 67589, 67592, 67592, 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, 67680, 67702, 67712, 67742, 67808, 67826, 67828, 67829, 67840, 67861, 67872, 67897, 67968, 68023, 68030, 68031, 68096, 68099, 68101, 68102, 68108, 68115, 68117, 68119, 68121, 68149, 68152, 68154, 68159, 68159, 68192, 68220, 68224, 68252, 68288, 68295, 68297, 68326, 68352, 68405, 68416, 68437, 68448, 68466, 68480, 68497, 68608, 68680, 68736, 68786, 68800, 68850, 68864, 68903, 68912, 68921, 69376, 69404, 69415, 69415, 69424, 69456, 69600, 69622, 69632, 69702, 69734, 69743, 69759, 69818, 69840, 69864, 69872, 69881, 69888, 69940, 69942, 69951, 69956, 69958, 69968, 70003, 70006, 70006, 70016, 70084, 70089, 70092, 70096, 70106, 70108, 70108, 70144, 70161, 70163, 70199, 70206, 70206, 70272, 70278, 70280, 70280, 70282, 70285, 70287, 70301, 70303, 70312, 70320, 70378, 70384, 70393, 70400, 70403, 70405, 70412, 70415, 70416, 70419, 70440, 70442, 70448, 70450, 70451, 70453, 70457, 70459, 70468, 70471, 70472, 70475, 70477, 70480, 70480, 70487, 70487, 70493, 70499, 70502, 70508, 70512, 70516, 70656, 70730, 70736, 70745, 70750, 70751, 70784, 70853, 70855, 70855, 70864, 70873, 71040, 71093, 71096, 71104, 71128, 71133, 71168, 71232, 71236, 71236, 71248, 71257, 71296, 71352, 71360, 71369, 71424, 71450, 71453, 71467, 71472, 71481, 71680, 71738, 71840, 71913, 71935, 71935, 72096, 72103, 72106, 72151, 72154, 72161, 72163, 72164, 72192, 72254, 72263, 72263, 72272, 72345, 72349, 72349, 72384, 72440, 72704, 72712, 72714, 72758, 72760, 72768, 72784, 72793, 72818, 72847, 72850, 72871, 72873, 72886, 72960, 72966, 72968, 72969, 72971, 73014, 73018, 73018, 73020, 73021, 73023, 73031, 73040, 73049, 73056, 73061, 73063, 73064, 73066, 73102, 73104, 73105, 73107, 73112, 73120, 73129, 73440, 73462, 73728, 74649, 74752, 74862, 74880, 75075, 77824, 78894, 82944, 83526, 92160, 92728, 92736, 92766, 92768, 92777, 92880, 92909, 92912, 92916, 92928, 92982, 92992, 92995, 93008, 93017, 93027, 93047, 93053, 93071, 93760, 93823, 93952, 94026, 94031, 94087, 94095, 94111, 94176, 94177, 94179, 94179, 94208, 100343, 100352, 101106, 110592, 110878, 110928, 110930, 110948, 110951, 110960, 111355, 113664, 113770, 113776, 113788, 113792, 113800, 113808, 113817, 113821, 113822, 119141, 119145, 119149, 119154, 119163, 119170, 119173, 119179, 119210, 119213, 119362, 119364, 119808, 119892, 119894, 119964, 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, 120782, 120831, 121344, 121398, 121403, 121452, 121461, 121461, 121476, 121476, 121499, 121503, 121505, 121519, 122880, 122886, 122888, 122904, 122907, 122913, 122915, 122916, 122918, 122922, 123136, 123180, 123184, 123197, 123200, 123209, 123214, 123214, 123584, 123641, 124928, 125124, 125136, 125142, 125184, 125259, 125264, 125273, 126464, 126467, 126469, 126495, 126497, 126498, 126500, 126500, 126503, 126503, 126505, 126514, 126516, 126519, 126521, 126521, 126523, 126523, 126530, 126530, 126535, 126535, 126537, 126537, 126539, 126539, 126541, 126543, 126545, 126546, 126548, 126548, 126551, 126551, 126553, 126553, 126555, 126555, 126557, 126557, 126559, 126559, 126561, 126562, 126564, 126564, 126567, 126570, 126572, 126578, 126580, 126583, 126585, 126588, 126590, 126590, 126592, 126601, 126603, 126619, 126625, 126627, 126629, 126633, 126635, 126651, 131072, 173782, 173824, 177972, 177984, 178205, 178208, 183969, 183984, 191456, 194560, 195101, 917760, 917999]; + +/** + * Test for whether a single line comment with leading whitespace trimmed's text contains a directive. + */ +const commentDirectiveRegExSingleLine = /^\/\/\/?\s*@(ts-expect-error|ts-ignore)/; + +/** + * Test for whether a multi-line comment with leading whitespace trimmed's last line contains a directive. + */ +const commentDirectiveRegExMultiLine = /^(?:\/|\*)*\s*@(ts-expect-error|ts-ignore)/; + +function lookupInUnicodeMap(code: number, map: readonly number[]): boolean { + // Bail out quickly if it couldn't possibly be in the map. + if (code < map[0]) { + return false; + } - /** - * Test for whether a multi-line comment with leading whitespace trimmed's last line contains a directive. - */ - const commentDirectiveRegExMultiLine = /^(?:\/|\*)*\s*@(ts-expect-error|ts-ignore)/; + // Perform binary search in one of the Unicode range maps + let lo = 0; + let hi: number = map.length; + let mid: number; - function lookupInUnicodeMap(code: number, map: readonly number[]): boolean { - // Bail out quickly if it couldn't possibly be in the map. - if (code < map[0]) { - return false; + while (lo + 1 < hi) { + mid = lo + (hi - lo) / 2; + // mid has to be even to catch a range's beginning + mid -= mid % 2; + if (map[mid] <= code && code <= map[mid + 1]) { + return true; } - // Perform binary search in one of the Unicode range maps - let lo = 0; - let hi: number = map.length; - let mid: number; - - while (lo + 1 < hi) { - mid = lo + (hi - lo) / 2; - // mid has to be even to catch a range's beginning - mid -= mid % 2; - if (map[mid] <= code && code <= map[mid + 1]) { - return true; - } - - if (code < map[mid]) { - hi = mid; - } - else { - lo = mid + 2; - } + if (code < map[mid]) { + hi = mid; + } + else { + lo = mid + 2; } - - return false; } - /* @internal */ export function isUnicodeIdentifierStart(code: number, languageVersion: ScriptTarget | undefined) { - return languageVersion! >= ScriptTarget.ES2015 ? - lookupInUnicodeMap(code, unicodeESNextIdentifierStart) : - languageVersion === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierStart) : - lookupInUnicodeMap(code, unicodeES3IdentifierStart); - } + return false; +} - function isUnicodeIdentifierPart(code: number, languageVersion: ScriptTarget | undefined) { - return languageVersion! >= ScriptTarget.ES2015 ? - lookupInUnicodeMap(code, unicodeESNextIdentifierPart) : - languageVersion === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierPart) : - lookupInUnicodeMap(code, unicodeES3IdentifierPart); - } +/* @internal */ export function isUnicodeIdentifierStart(code: number, languageVersion: ScriptTarget | undefined) { + return languageVersion! >= ScriptTarget.ES2015 ? + lookupInUnicodeMap(code, unicodeESNextIdentifierStart) : + languageVersion === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierStart) : + lookupInUnicodeMap(code, unicodeES3IdentifierStart); +} - function makeReverseMap(source: ESMap): string[] { - const result: string[] = []; - source.forEach((value, name) => { - result[value] = name; - }); - return result; - } +function isUnicodeIdentifierPart(code: number, languageVersion: ScriptTarget | undefined) { + return languageVersion! >= ScriptTarget.ES2015 ? + lookupInUnicodeMap(code, unicodeESNextIdentifierPart) : + languageVersion === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierPart) : + lookupInUnicodeMap(code, unicodeES3IdentifierPart); +} - const tokenStrings = makeReverseMap(textToToken); - export function tokenToString(t: SyntaxKind): string | undefined { - return tokenStrings[t]; - } +function makeReverseMap(source: ESMap): string[] { + const result: string[] = []; + source.forEach((value, name) => { + result[value] = name; + }); + return result; +} - /* @internal */ - export function stringToToken(s: string): SyntaxKind | undefined { - return textToToken.get(s); - } +const tokenStrings = makeReverseMap(textToToken); +export function tokenToString(t: SyntaxKind): string | undefined { + return tokenStrings[t]; +} - /* @internal */ - export function computeLineStarts(text: string): number[] { - const result: number[] = new Array(); - let pos = 0; - let lineStart = 0; - while (pos < text.length) { - const ch = text.charCodeAt(pos); - pos++; - switch (ch) { - case CharacterCodes.carriageReturn: - if (text.charCodeAt(pos) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: +/* @internal */ +export function stringToToken(s: string): SyntaxKind | undefined { + return textToToken.get(s); +} + +/* @internal */ +export function computeLineStarts(text: string): number[] { + const result: number[] = new Array(); + let pos = 0; + let lineStart = 0; + while (pos < text.length) { + const ch = text.charCodeAt(pos); + pos++; + switch (ch) { + case CharacterCodes.carriageReturn: + if (text.charCodeAt(pos) === CharacterCodes.lineFeed) { + pos++; + } + // falls through + case CharacterCodes.lineFeed: + result.push(lineStart); + lineStart = pos; + break; + default: + if (ch > CharacterCodes.maxAsciiCharacter && isLineBreak(ch)) { result.push(lineStart); lineStart = pos; - break; - default: - if (ch > CharacterCodes.maxAsciiCharacter && isLineBreak(ch)) { - result.push(lineStart); - lineStart = pos; - } - break; - } + } + break; } - result.push(lineStart); - return result; - } - - export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number; - /* @internal */ - export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number; // eslint-disable-line @typescript-eslint/unified-signatures - export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number { - return sourceFile.getPositionOfLineAndCharacter ? - sourceFile.getPositionOfLineAndCharacter(line, character, allowEdits) : - computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, allowEdits); } + result.push(lineStart); + return result; +} - /* @internal */ - export function computePositionOfLineAndCharacter(lineStarts: readonly number[], line: number, character: number, debugText?: string, allowEdits?: true): number { - if (line < 0 || line >= lineStarts.length) { - if (allowEdits) { - // Clamp line to nearest allowable value - line = line < 0 ? 0 : line >= lineStarts.length ? lineStarts.length - 1 : line; - } - else { - Debug.fail(`Bad line number. Line: ${line}, lineStarts.length: ${lineStarts.length} , line map is correct? ${debugText !== undefined ? arraysEqual(lineStarts, computeLineStarts(debugText)) : "unknown"}`); - } - } +export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number; +/* @internal */ +export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number; // eslint-disable-line @typescript-eslint/unified-signatures +export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number { + return sourceFile.getPositionOfLineAndCharacter ? + sourceFile.getPositionOfLineAndCharacter(line, character, allowEdits) : + computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, allowEdits); +} - const res = lineStarts[line] + character; +/* @internal */ +export function computePositionOfLineAndCharacter(lineStarts: readonly number[], line: number, character: number, debugText?: string, allowEdits?: true): number { + if (line < 0 || line >= lineStarts.length) { if (allowEdits) { - // Clamp to nearest allowable values to allow the underlying to be edited without crashing (accuracy is lost, instead) - // TODO: Somehow track edits between file as it was during the creation of sourcemap we have and the current file and - // apply them to the computed position to improve accuracy - return res > lineStarts[line + 1] ? lineStarts[line + 1] : typeof debugText === "string" && res > debugText.length ? debugText.length : res; + // Clamp line to nearest allowable value + line = line < 0 ? 0 : line >= lineStarts.length ? lineStarts.length - 1 : line; } - if (line < lineStarts.length - 1) { - Debug.assert(res < lineStarts[line + 1]); - } - else if (debugText !== undefined) { - Debug.assert(res <= debugText.length); // Allow single character overflow for trailing newline + else { + Debug.fail(`Bad line number. Line: ${line}, lineStarts.length: ${lineStarts.length} , line map is correct? ${debugText !== undefined ? arraysEqual(lineStarts, computeLineStarts(debugText)) : "unknown"}`); } - return res; } - /* @internal */ - export function getLineStarts(sourceFile: SourceFileLike): readonly number[] { - return sourceFile.lineMap || (sourceFile.lineMap = computeLineStarts(sourceFile.text)); + const res = lineStarts[line] + character; + if (allowEdits) { + // Clamp to nearest allowable values to allow the underlying to be edited without crashing (accuracy is lost, instead) + // TODO: Somehow track edits between file as it was during the creation of sourcemap we have and the current file and + // apply them to the computed position to improve accuracy + return res > lineStarts[line + 1] ? lineStarts[line + 1] : typeof debugText === "string" && res > debugText.length ? debugText.length : res; } - - /* @internal */ - export function computeLineAndCharacterOfPosition(lineStarts: readonly number[], position: number): LineAndCharacter { - const lineNumber = computeLineOfPosition(lineStarts, position); - return { - line: lineNumber, - character: position - lineStarts[lineNumber] - }; + if (line < lineStarts.length - 1) { + Debug.assert(res < lineStarts[line + 1]); } - - /** - * @internal - * We assume the first line starts at position 0 and 'position' is non-negative. - */ - export function computeLineOfPosition(lineStarts: readonly number[], position: number, lowerBound?: number) { - let lineNumber = binarySearch(lineStarts, position, identity, compareValues, lowerBound); - if (lineNumber < 0) { - // If the actual position was not found, - // the binary search returns the 2's-complement of the next line start - // e.g. if the line starts at [5, 10, 23, 80] and the position requested was 20 - // then the search will return -2. - // - // We want the index of the previous line start, so we subtract 1. - // Review 2's-complement if this is confusing. - lineNumber = ~lineNumber - 1; - Debug.assert(lineNumber !== -1, "position cannot precede the beginning of the file"); - } - return lineNumber; + else if (debugText !== undefined) { + Debug.assert(res <= debugText.length); // Allow single character overflow for trailing newline } + return res; +} - /** @internal */ - export function getLinesBetweenPositions(sourceFile: SourceFileLike, pos1: number, pos2: number) { - if (pos1 === pos2) return 0; - const lineStarts = getLineStarts(sourceFile); - const lower = Math.min(pos1, pos2); - const isNegative = lower === pos2; - const upper = isNegative ? pos1 : pos2; - const lowerLine = computeLineOfPosition(lineStarts, lower); - const upperLine = computeLineOfPosition(lineStarts, upper, lowerLine); - return isNegative ? lowerLine - upperLine : upperLine - lowerLine; - } +/* @internal */ +export function getLineStarts(sourceFile: SourceFileLike): readonly number[] { + return sourceFile.lineMap || (sourceFile.lineMap = computeLineStarts(sourceFile.text)); +} - export function getLineAndCharacterOfPosition(sourceFile: SourceFileLike, position: number): LineAndCharacter { - return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position); - } +/* @internal */ +export function computeLineAndCharacterOfPosition(lineStarts: readonly number[], position: number): LineAndCharacter { + const lineNumber = computeLineOfPosition(lineStarts, position); + return { + line: lineNumber, + character: position - lineStarts[lineNumber] + }; +} - export function isWhiteSpaceLike(ch: number): boolean { - return isWhiteSpaceSingleLine(ch) || isLineBreak(ch); +/** + * @internal + * We assume the first line starts at position 0 and 'position' is non-negative. + */ +export function computeLineOfPosition(lineStarts: readonly number[], position: number, lowerBound?: number) { + let lineNumber = binarySearch(lineStarts, position, identity, compareValues, lowerBound); + if (lineNumber < 0) { + // If the actual position was not found, + // the binary search returns the 2's-complement of the next line start + // e.g. if the line starts at [5, 10, 23, 80] and the position requested was 20 + // then the search will return -2. + // + // We want the index of the previous line start, so we subtract 1. + // Review 2's-complement if this is confusing. + lineNumber = ~lineNumber - 1; + Debug.assert(lineNumber !== -1, "position cannot precede the beginning of the file"); } + return lineNumber; +} - /** Does not include line breaks. For that, see isWhiteSpaceLike. */ - export function isWhiteSpaceSingleLine(ch: number): boolean { - // Note: nextLine is in the Zs space, and should be considered to be a whitespace. - // It is explicitly not a line-break as it isn't in the exact set specified by EcmaScript. - return ch === CharacterCodes.space || - ch === CharacterCodes.tab || - ch === CharacterCodes.verticalTab || - ch === CharacterCodes.formFeed || - ch === CharacterCodes.nonBreakingSpace || - ch === CharacterCodes.nextLine || - ch === CharacterCodes.ogham || - ch >= CharacterCodes.enQuad && ch <= CharacterCodes.zeroWidthSpace || - ch === CharacterCodes.narrowNoBreakSpace || - ch === CharacterCodes.mathematicalSpace || - ch === CharacterCodes.ideographicSpace || - ch === CharacterCodes.byteOrderMark; - } +/** @internal */ +export function getLinesBetweenPositions(sourceFile: SourceFileLike, pos1: number, pos2: number) { + if (pos1 === pos2) return 0; + const lineStarts = getLineStarts(sourceFile); + const lower = Math.min(pos1, pos2); + const isNegative = lower === pos2; + const upper = isNegative ? pos1 : pos2; + const lowerLine = computeLineOfPosition(lineStarts, lower); + const upperLine = computeLineOfPosition(lineStarts, upper, lowerLine); + return isNegative ? lowerLine - upperLine : upperLine - lowerLine; +} + +export function getLineAndCharacterOfPosition(sourceFile: SourceFileLike, position: number): LineAndCharacter { + return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position); +} - export function isLineBreak(ch: number): boolean { - // ES5 7.3: - // The ECMAScript line terminator characters are listed in Table 3. - // Table 3: Line Terminator Characters - // Code Unit Value Name Formal Name - // \u000A Line Feed - // \u000D Carriage Return - // \u2028 Line separator - // \u2029 Paragraph separator - // Only the characters in Table 3 are treated as line terminators. Other new line or line - // breaking characters are treated as white space but not as line terminators. +export function isWhiteSpaceLike(ch: number): boolean { + return isWhiteSpaceSingleLine(ch) || isLineBreak(ch); +} - return ch === CharacterCodes.lineFeed || - ch === CharacterCodes.carriageReturn || - ch === CharacterCodes.lineSeparator || - ch === CharacterCodes.paragraphSeparator; - } +/** Does not include line breaks. For that, see isWhiteSpaceLike. */ +export function isWhiteSpaceSingleLine(ch: number): boolean { + // Note: nextLine is in the Zs space, and should be considered to be a whitespace. + // It is explicitly not a line-break as it isn't in the exact set specified by EcmaScript. + return ch === CharacterCodes.space || + ch === CharacterCodes.tab || + ch === CharacterCodes.verticalTab || + ch === CharacterCodes.formFeed || + ch === CharacterCodes.nonBreakingSpace || + ch === CharacterCodes.nextLine || + ch === CharacterCodes.ogham || + ch >= CharacterCodes.enQuad && ch <= CharacterCodes.zeroWidthSpace || + ch === CharacterCodes.narrowNoBreakSpace || + ch === CharacterCodes.mathematicalSpace || + ch === CharacterCodes.ideographicSpace || + ch === CharacterCodes.byteOrderMark; +} - function isDigit(ch: number): boolean { - return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; - } +export function isLineBreak(ch: number): boolean { + // ES5 7.3: + // The ECMAScript line terminator characters are listed in Table 3. + // Table 3: Line Terminator Characters + // Code Unit Value Name Formal Name + // \u000A Line Feed + // \u000D Carriage Return + // \u2028 Line separator + // \u2029 Paragraph separator + // Only the characters in Table 3 are treated as line terminators. Other new line or line + // breaking characters are treated as white space but not as line terminators. + + return ch === CharacterCodes.lineFeed || + ch === CharacterCodes.carriageReturn || + ch === CharacterCodes.lineSeparator || + ch === CharacterCodes.paragraphSeparator; +} - function isHexDigit(ch: number): boolean { - return isDigit(ch) || ch >= CharacterCodes.A && ch <= CharacterCodes.F || ch >= CharacterCodes.a && ch <= CharacterCodes.f; - } +function isDigit(ch: number): boolean { + return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; +} - function isCodePoint(code: number): boolean { - return code <= 0x10FFFF; +function isHexDigit(ch: number): boolean { + return isDigit(ch) || ch >= CharacterCodes.A && ch <= CharacterCodes.F || ch >= CharacterCodes.a && ch <= CharacterCodes.f; +} + +function isCodePoint(code: number): boolean { + return code <= 0x10FFFF; +} + +/* @internal */ +export function isOctalDigit(ch: number): boolean { + return ch >= CharacterCodes._0 && ch <= CharacterCodes._7; +} + +export function couldStartTrivia(text: string, pos: number): boolean { + // Keep in sync with skipTrivia + const ch = text.charCodeAt(pos); + switch (ch) { + case CharacterCodes.carriageReturn: + case CharacterCodes.lineFeed: + case CharacterCodes.tab: + case CharacterCodes.verticalTab: + case CharacterCodes.formFeed: + case CharacterCodes.space: + case CharacterCodes.slash: + // starts of normal trivia + // falls through + case CharacterCodes.lessThan: + case CharacterCodes.bar: + case CharacterCodes.equals: + case CharacterCodes.greaterThan: + // Starts of conflict marker trivia + return true; + case CharacterCodes.hash: + // Only if its the beginning can we have #! trivia + return pos === 0; + default: + return ch > CharacterCodes.maxAsciiCharacter; } +} - /* @internal */ - export function isOctalDigit(ch: number): boolean { - return ch >= CharacterCodes._0 && ch <= CharacterCodes._7; +/* @internal */ +export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean, stopAtComments?: boolean, inJSDoc?: boolean): number { + if (positionIsSynthesized(pos)) { + return pos; } - export function couldStartTrivia(text: string, pos: number): boolean { - // Keep in sync with skipTrivia + let canConsumeStar = false; + // Keep in sync with couldStartTrivia + while (true) { const ch = text.charCodeAt(pos); switch (ch) { case CharacterCodes.carriageReturn: + if (text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { + pos++; + } + // falls through case CharacterCodes.lineFeed: + pos++; + if (stopAfterLineBreak) { + return pos; + } + canConsumeStar = !!inJSDoc; + continue; case CharacterCodes.tab: case CharacterCodes.verticalTab: case CharacterCodes.formFeed: case CharacterCodes.space: + pos++; + continue; case CharacterCodes.slash: - // starts of normal trivia - // falls through + if (stopAtComments) { + break; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + while (pos < text.length) { + if (isLineBreak(text.charCodeAt(pos))) { + break; + } + pos++; + } + canConsumeStar = false; + continue; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { + pos += 2; + while (pos < text.length) { + if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + break; + } + pos++; + } + canConsumeStar = false; + continue; + } + break; + case CharacterCodes.lessThan: case CharacterCodes.bar: case CharacterCodes.equals: case CharacterCodes.greaterThan: - // Starts of conflict marker trivia - return true; + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos); + canConsumeStar = false; + continue; + } + break; + case CharacterCodes.hash: - // Only if its the beginning can we have #! trivia - return pos === 0; + if (pos === 0 && isShebangTrivia(text, pos)) { + pos = scanShebangTrivia(text, pos); + canConsumeStar = false; + continue; + } + break; + + case CharacterCodes.asterisk: + if (canConsumeStar) { + pos++; + canConsumeStar = false; + continue; + } + break; + default: - return ch > CharacterCodes.maxAsciiCharacter; + if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) { + pos++; + continue; + } + break; } + return pos; } +} - /* @internal */ - export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean, stopAtComments?: boolean, inJSDoc?: boolean): number { - if (positionIsSynthesized(pos)) { - return pos; +// All conflict markers consist of the same character repeated seven times. If it is +// a <<<<<<< or >>>>>>> marker then it is also followed by a space. +const mergeConflictMarkerLength = "<<<<<<<".length; + +function isConflictMarkerTrivia(text: string, pos: number) { + Debug.assert(pos >= 0); + + // Conflict markers must be at the start of a line. + if (pos === 0 || isLineBreak(text.charCodeAt(pos - 1))) { + const ch = text.charCodeAt(pos); + + if ((pos + mergeConflictMarkerLength) < text.length) { + for (let i = 0; i < mergeConflictMarkerLength; i++) { + if (text.charCodeAt(pos + i) !== ch) { + return false; + } + } + + return ch === CharacterCodes.equals || + text.charCodeAt(pos + mergeConflictMarkerLength) === CharacterCodes.space; } + } - let canConsumeStar = false; - // Keep in sync with couldStartTrivia - while (true) { - const ch = text.charCodeAt(pos); - switch (ch) { - case CharacterCodes.carriageReturn: - if (text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: - pos++; - if (stopAfterLineBreak) { - return pos; - } - canConsumeStar = !!inJSDoc; - continue; - case CharacterCodes.tab: - case CharacterCodes.verticalTab: - case CharacterCodes.formFeed: - case CharacterCodes.space: + return false; +} + +function scanConflictMarkerTrivia(text: string, pos: number, error?: (diag: DiagnosticMessage, pos?: number, len?: number) => void) { + if (error) { + error(Diagnostics.Merge_conflict_marker_encountered, pos, mergeConflictMarkerLength); + } + + const ch = text.charCodeAt(pos); + const len = text.length; + + if (ch === CharacterCodes.lessThan || ch === CharacterCodes.greaterThan) { + while (pos < len && !isLineBreak(text.charCodeAt(pos))) { + pos++; + } + } + else { + Debug.assert(ch === CharacterCodes.bar || ch === CharacterCodes.equals); + // Consume everything from the start of a ||||||| or ======= marker to the start + // of the next ======= or >>>>>>> marker. + while (pos < len) { + const currentChar = text.charCodeAt(pos); + if ((currentChar === CharacterCodes.equals || currentChar === CharacterCodes.greaterThan) && currentChar !== ch && isConflictMarkerTrivia(text, pos)) { + break; + } + + pos++; + } + } + + return pos; +} + +const shebangTriviaRegex = /^#!.*/; + +/*@internal*/ +export function isShebangTrivia(text: string, pos: number) { + // Shebangs check must only be done at the start of the file + Debug.assert(pos === 0); + return shebangTriviaRegex.test(text); +} + +/*@internal*/ +export function scanShebangTrivia(text: string, pos: number) { + const shebang = shebangTriviaRegex.exec(text)![0]; + pos = pos + shebang.length; + return pos; +} + +/** + * Invokes a callback for each comment range following the provided position. + * + * Single-line comment ranges include the leading double-slash characters but not the ending + * line break. Multi-line comment ranges include the leading slash-asterisk and trailing + * asterisk-slash characters. + * + * @param reduce If true, accumulates the result of calling the callback in a fashion similar + * to reduceLeft. If false, iteration stops when the callback returns a truthy value. + * @param text The source text to scan. + * @param pos The position at which to start scanning. + * @param trailing If false, whitespace is skipped until the first line break and comments + * between that location and the next token are returned. If true, comments occurring + * between the given position and the next line break are returned. + * @param cb The callback to execute as each comment range is encountered. + * @param state A state value to pass to each iteration of the callback. + * @param initial An initial value to pass when accumulating results (when "reduce" is true). + * @returns If "reduce" is true, the accumulated value. If "reduce" is false, the first truthy + * return value of the callback. + */ +function iterateCommentRanges(reduce: boolean, text: string, pos: number, trailing: boolean, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U | undefined) => U, state: T, initial?: U): U | undefined { + let pendingPos!: number; + let pendingEnd!: number; + let pendingKind!: CommentKind; + let pendingHasTrailingNewLine!: boolean; + let hasPendingCommentRange = false; + let collecting = trailing; + let accumulator = initial; + if (pos === 0) { + collecting = true; + const shebang = getShebang(text); + if (shebang) { + pos = shebang.length; + } + } + scan: while (pos >= 0 && pos < text.length) { + const ch = text.charCodeAt(pos); + switch (ch) { + case CharacterCodes.carriageReturn: + if (text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { pos++; - continue; - case CharacterCodes.slash: - if (stopAtComments) { - break; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; + } + // falls through + case CharacterCodes.lineFeed: + pos++; + if (trailing) { + break scan; + } + + collecting = true; + if (hasPendingCommentRange) { + pendingHasTrailingNewLine = true; + } + + continue; + case CharacterCodes.tab: + case CharacterCodes.verticalTab: + case CharacterCodes.formFeed: + case CharacterCodes.space: + pos++; + continue; + case CharacterCodes.slash: + const nextChar = text.charCodeAt(pos + 1); + let hasTrailingNewLine = false; + if (nextChar === CharacterCodes.slash || nextChar === CharacterCodes.asterisk) { + const kind = nextChar === CharacterCodes.slash ? SyntaxKind.SingleLineCommentTrivia : SyntaxKind.MultiLineCommentTrivia; + const startPos = pos; + pos += 2; + if (nextChar === CharacterCodes.slash) { while (pos < text.length) { if (isLineBreak(text.charCodeAt(pos))) { + hasTrailingNewLine = true; break; } pos++; } - canConsumeStar = false; - continue; } - if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { - pos += 2; + else { while (pos < text.length) { if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { pos += 2; @@ -610,2024 +810,1824 @@ namespace ts { } pos++; } - canConsumeStar = false; - continue; } - break; - case CharacterCodes.lessThan: - case CharacterCodes.bar: - case CharacterCodes.equals: - case CharacterCodes.greaterThan: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos); - canConsumeStar = false; - continue; - } - break; + if (collecting) { + if (hasPendingCommentRange) { + accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator); + if (!reduce && accumulator) { + // If we are not reducing and we have a truthy result, return it. + return accumulator; + } + } - case CharacterCodes.hash: - if (pos === 0 && isShebangTrivia(text, pos)) { - pos = scanShebangTrivia(text, pos); - canConsumeStar = false; - continue; + pendingPos = startPos; + pendingEnd = pos; + pendingKind = kind; + pendingHasTrailingNewLine = hasTrailingNewLine; + hasPendingCommentRange = true; } - break; - - case CharacterCodes.asterisk: - if (canConsumeStar) { - pos++; - canConsumeStar = false; - continue; - } - break; - default: - if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) { - pos++; - continue; + continue; + } + break scan; + default: + if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) { + if (hasPendingCommentRange && isLineBreak(ch)) { + pendingHasTrailingNewLine = true; } - break; - } - return pos; + pos++; + continue; + } + break scan; } } - // All conflict markers consist of the same character repeated seven times. If it is - // a <<<<<<< or >>>>>>> marker then it is also followed by a space. - const mergeConflictMarkerLength = "<<<<<<<".length; + if (hasPendingCommentRange) { + accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator); + } - function isConflictMarkerTrivia(text: string, pos: number) { - Debug.assert(pos >= 0); + return accumulator; +} - // Conflict markers must be at the start of a line. - if (pos === 0 || isLineBreak(text.charCodeAt(pos - 1))) { - const ch = text.charCodeAt(pos); +export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; +export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; +export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { + return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ false, cb, state); +} - if ((pos + mergeConflictMarkerLength) < text.length) { - for (let i = 0; i < mergeConflictMarkerLength; i++) { - if (text.charCodeAt(pos + i) !== ch) { - return false; - } - } +export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; +export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; +export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { + return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ true, cb, state); +} - return ch === CharacterCodes.equals || - text.charCodeAt(pos + mergeConflictMarkerLength) === CharacterCodes.space; - } - } +export function reduceEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { + return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ false, cb, state, initial); +} - return false; - } +export function reduceEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { + return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ true, cb, state, initial); +} - function scanConflictMarkerTrivia(text: string, pos: number, error?: (diag: DiagnosticMessage, pos?: number, len?: number) => void) { - if (error) { - error(Diagnostics.Merge_conflict_marker_encountered, pos, mergeConflictMarkerLength); - } +function appendCommentRange(pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, _state: any, comments: CommentRange[]) { + if (!comments) { + comments = []; + } - const ch = text.charCodeAt(pos); - const len = text.length; + comments.push({ kind, pos, end, hasTrailingNewLine }); + return comments; +} - if (ch === CharacterCodes.lessThan || ch === CharacterCodes.greaterThan) { - while (pos < len && !isLineBreak(text.charCodeAt(pos))) { - pos++; - } - } - else { - Debug.assert(ch === CharacterCodes.bar || ch === CharacterCodes.equals); - // Consume everything from the start of a ||||||| or ======= marker to the start - // of the next ======= or >>>>>>> marker. - while (pos < len) { - const currentChar = text.charCodeAt(pos); - if ((currentChar === CharacterCodes.equals || currentChar === CharacterCodes.greaterThan) && currentChar !== ch && isConflictMarkerTrivia(text, pos)) { - break; - } +export function getLeadingCommentRanges(text: string, pos: number): CommentRange[] | undefined { + return reduceEachLeadingCommentRange(text, pos, appendCommentRange, /*state*/ undefined, /*initial*/ undefined); +} - pos++; - } - } +export function getTrailingCommentRanges(text: string, pos: number): CommentRange[] | undefined { + return reduceEachTrailingCommentRange(text, pos, appendCommentRange, /*state*/ undefined, /*initial*/ undefined); +} - return pos; +/** Optionally, get the shebang */ +export function getShebang(text: string): string | undefined { + const match = shebangTriviaRegex.exec(text); + if (match) { + return match[0]; } +} - const shebangTriviaRegex = /^#!.*/; +export function isIdentifierStart(ch: number, languageVersion: ScriptTarget | undefined): boolean { + return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || + ch === CharacterCodes.$ || ch === CharacterCodes._ || + ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierStart(ch, languageVersion); +} - /*@internal*/ - export function isShebangTrivia(text: string, pos: number) { - // Shebangs check must only be done at the start of the file - Debug.assert(pos === 0); - return shebangTriviaRegex.test(text); - } +export function isIdentifierPart(ch: number, languageVersion: ScriptTarget | undefined, identifierVariant?: LanguageVariant): boolean { + return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || + ch >= CharacterCodes._0 && ch <= CharacterCodes._9 || ch === CharacterCodes.$ || ch === CharacterCodes._ || + // "-" and ":" are valid in JSX Identifiers + (identifierVariant === LanguageVariant.JSX ? (ch === CharacterCodes.minus || ch === CharacterCodes.colon) : false) || + ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierPart(ch, languageVersion); +} - /*@internal*/ - export function scanShebangTrivia(text: string, pos: number) { - const shebang = shebangTriviaRegex.exec(text)![0]; - pos = pos + shebang.length; - return pos; +/* @internal */ +export function isIdentifierText(name: string, languageVersion: ScriptTarget | undefined, identifierVariant?: LanguageVariant): boolean { + let ch = codePointAt(name, 0); + if (!isIdentifierStart(ch, languageVersion)) { + return false; } - /** - * Invokes a callback for each comment range following the provided position. - * - * Single-line comment ranges include the leading double-slash characters but not the ending - * line break. Multi-line comment ranges include the leading slash-asterisk and trailing - * asterisk-slash characters. - * - * @param reduce If true, accumulates the result of calling the callback in a fashion similar - * to reduceLeft. If false, iteration stops when the callback returns a truthy value. - * @param text The source text to scan. - * @param pos The position at which to start scanning. - * @param trailing If false, whitespace is skipped until the first line break and comments - * between that location and the next token are returned. If true, comments occurring - * between the given position and the next line break are returned. - * @param cb The callback to execute as each comment range is encountered. - * @param state A state value to pass to each iteration of the callback. - * @param initial An initial value to pass when accumulating results (when "reduce" is true). - * @returns If "reduce" is true, the accumulated value. If "reduce" is false, the first truthy - * return value of the callback. - */ - function iterateCommentRanges(reduce: boolean, text: string, pos: number, trailing: boolean, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U | undefined) => U, state: T, initial?: U): U | undefined { - let pendingPos!: number; - let pendingEnd!: number; - let pendingKind!: CommentKind; - let pendingHasTrailingNewLine!: boolean; - let hasPendingCommentRange = false; - let collecting = trailing; - let accumulator = initial; - if (pos === 0) { - collecting = true; - const shebang = getShebang(text); - if (shebang) { - pos = shebang.length; - } + for (let i = charSize(ch); i < name.length; i += charSize(ch)) { + if (!isIdentifierPart(ch = codePointAt(name, i), languageVersion, identifierVariant)) { + return false; } - scan: while (pos >= 0 && pos < text.length) { - const ch = text.charCodeAt(pos); - switch (ch) { - case CharacterCodes.carriageReturn: - if (text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: - pos++; - if (trailing) { - break scan; - } - - collecting = true; - if (hasPendingCommentRange) { - pendingHasTrailingNewLine = true; - } + } - continue; - case CharacterCodes.tab: - case CharacterCodes.verticalTab: - case CharacterCodes.formFeed: - case CharacterCodes.space: - pos++; - continue; - case CharacterCodes.slash: - const nextChar = text.charCodeAt(pos + 1); - let hasTrailingNewLine = false; - if (nextChar === CharacterCodes.slash || nextChar === CharacterCodes.asterisk) { - const kind = nextChar === CharacterCodes.slash ? SyntaxKind.SingleLineCommentTrivia : SyntaxKind.MultiLineCommentTrivia; - const startPos = pos; - pos += 2; - if (nextChar === CharacterCodes.slash) { - while (pos < text.length) { - if (isLineBreak(text.charCodeAt(pos))) { - hasTrailingNewLine = true; - break; - } - pos++; - } - } - else { - while (pos < text.length) { - if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - break; - } - pos++; - } - } + return true; +} - if (collecting) { - if (hasPendingCommentRange) { - accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator); - if (!reduce && accumulator) { - // If we are not reducing and we have a truthy result, return it. - return accumulator; - } - } +// Creates a scanner over a (possibly unspecified) range of a piece of text. +export function createScanner(languageVersion: ScriptTarget, + skipTrivia: boolean, + languageVariant = LanguageVariant.Standard, + textInitial?: string, + onError?: ErrorCallback, + start?: number, + length?: number): Scanner { + + let text = textInitial!; + + // Current position (end position of text of current token) + let pos: number; + + + // end of text + let end: number; + + // Start position of whitespace before current token + let startPos: number; + + // Start position of text of current token + let tokenPos: number; + + let token: SyntaxKind; + let tokenValue!: string; + let tokenFlags: TokenFlags; + + let commentDirectives: CommentDirective[] | undefined; + let inJSDocType = 0; + + setText(text, start, length); + + const scanner: Scanner = { + getStartPos: () => startPos, + getTextPos: () => pos, + getToken: () => token, + getTokenPos: () => tokenPos, + getTokenText: () => text.substring(tokenPos, pos), + getTokenValue: () => tokenValue, + hasUnicodeEscape: () => (tokenFlags & TokenFlags.UnicodeEscape) !== 0, + hasExtendedUnicodeEscape: () => (tokenFlags & TokenFlags.ExtendedUnicodeEscape) !== 0, + hasPrecedingLineBreak: () => (tokenFlags & TokenFlags.PrecedingLineBreak) !== 0, + hasPrecedingJSDocComment: () => (tokenFlags & TokenFlags.PrecedingJSDocComment) !== 0, + isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord, + isReservedWord: () => token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord, + isUnterminated: () => (tokenFlags & TokenFlags.Unterminated) !== 0, + getCommentDirectives: () => commentDirectives, + getNumericLiteralFlags: () => tokenFlags & TokenFlags.NumericLiteralFlags, + getTokenFlags: () => tokenFlags, + reScanGreaterToken, + reScanAsteriskEqualsToken, + reScanSlashToken, + reScanTemplateToken, + reScanTemplateHeadOrNoSubstitutionTemplate, + scanJsxIdentifier, + scanJsxAttributeValue, + reScanJsxAttributeValue, + reScanJsxToken, + reScanLessThanToken, + reScanHashToken, + reScanQuestionToken, + reScanInvalidIdentifier, + scanJsxToken, + scanJsDocToken, + scan, + getText, + clearCommentDirectives, + setText, + setScriptTarget, + setLanguageVariant, + setOnError, + setTextPos, + setInJSDocType, + tryScan, + lookAhead, + scanRange, + }; - pendingPos = startPos; - pendingEnd = pos; - pendingKind = kind; - pendingHasTrailingNewLine = hasTrailingNewLine; - hasPendingCommentRange = true; - } + if (Debug.isDebugging) { + Object.defineProperty(scanner, "__debugShowCurrentPositionInText", { + get: () => { + const text = scanner.getText(); + return text.slice(0, scanner.getStartPos()) + "║" + text.slice(scanner.getStartPos()); + }, + }); + } - continue; - } - break scan; - default: - if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) { - if (hasPendingCommentRange && isLineBreak(ch)) { - pendingHasTrailingNewLine = true; - } - pos++; - continue; - } - break scan; - } - } + return scanner; - if (hasPendingCommentRange) { - accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator); + function error(message: DiagnosticMessage): void; + function error(message: DiagnosticMessage, errPos: number, length: number): void; + function error(message: DiagnosticMessage, errPos: number = pos, length?: number): void { + if (onError) { + const oldPos = pos; + pos = errPos; + onError(message, length || 0); + pos = oldPos; } - - return accumulator; - } - - export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; - export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; - export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { - return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ false, cb, state); } - export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; - export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; - export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { - return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ true, cb, state); + function scanNumberFragment(): string { + let start = pos; + let allowSeparator = false; + let isPreviousTokenSeparator = false; + let result = ""; + while (true) { + const ch = text.charCodeAt(pos); + if (ch === CharacterCodes._) { + tokenFlags |= TokenFlags.ContainsSeparator; + if (allowSeparator) { + allowSeparator = false; + isPreviousTokenSeparator = true; + result += text.substring(start, pos); + } + else if (isPreviousTokenSeparator) { + error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + } + else { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); + } + pos++; + start = pos; + continue; + } + if (isDigit(ch)) { + allowSeparator = true; + isPreviousTokenSeparator = false; + pos++; + continue; + } + break; + } + if (text.charCodeAt(pos - 1) === CharacterCodes._) { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); + } + return result + text.substring(start, pos); } - export function reduceEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { - return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ false, cb, state, initial); - } + function scanNumber(): { type: SyntaxKind, value: string } { + const start = pos; + const mainFragment = scanNumberFragment(); + let decimalFragment: string | undefined; + let scientificFragment: string | undefined; + if (text.charCodeAt(pos) === CharacterCodes.dot) { + pos++; + decimalFragment = scanNumberFragment(); + } + let end = pos; + if (text.charCodeAt(pos) === CharacterCodes.E || text.charCodeAt(pos) === CharacterCodes.e) { + pos++; + tokenFlags |= TokenFlags.Scientific; + if (text.charCodeAt(pos) === CharacterCodes.plus || text.charCodeAt(pos) === CharacterCodes.minus) pos++; + const preNumericPart = pos; + const finalFragment = scanNumberFragment(); + if (!finalFragment) { + error(Diagnostics.Digit_expected); + } + else { + scientificFragment = text.substring(end, preNumericPart) + finalFragment; + end = pos; + } + } + let result: string; + if (tokenFlags & TokenFlags.ContainsSeparator) { + result = mainFragment; + if (decimalFragment) { + result += "." + decimalFragment; + } + if (scientificFragment) { + result += scientificFragment; + } + } + else { + result = text.substring(start, end); // No need to use all the fragments; no _ removal needed + } - export function reduceEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { - return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ true, cb, state, initial); + if (decimalFragment !== undefined || tokenFlags & TokenFlags.Scientific) { + checkForIdentifierStartAfterNumericLiteral(start, decimalFragment === undefined && !!(tokenFlags & TokenFlags.Scientific)); + return { + type: SyntaxKind.NumericLiteral, + value: "" + +result // if value is not an integer, it can be safely coerced to a number + }; + } + else { + tokenValue = result; + const type = checkBigIntSuffix(); // if value is an integer, check whether it is a bigint + checkForIdentifierStartAfterNumericLiteral(start); + return { type, value: tokenValue }; + } } - function appendCommentRange(pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, _state: any, comments: CommentRange[]) { - if (!comments) { - comments = []; + function checkForIdentifierStartAfterNumericLiteral(numericStart: number, isScientific?: boolean) { + if (!isIdentifierStart(codePointAt(text, pos), languageVersion)) { + return; } - comments.push({ kind, pos, end, hasTrailingNewLine }); - return comments; - } - - export function getLeadingCommentRanges(text: string, pos: number): CommentRange[] | undefined { - return reduceEachLeadingCommentRange(text, pos, appendCommentRange, /*state*/ undefined, /*initial*/ undefined); - } + const identifierStart = pos; + const { length } = scanIdentifierParts(); - export function getTrailingCommentRanges(text: string, pos: number): CommentRange[] | undefined { - return reduceEachTrailingCommentRange(text, pos, appendCommentRange, /*state*/ undefined, /*initial*/ undefined); + if (length === 1 && text[identifierStart] === "n") { + if (isScientific) { + error(Diagnostics.A_bigint_literal_cannot_use_exponential_notation, numericStart, identifierStart - numericStart + 1); + } + else { + error(Diagnostics.A_bigint_literal_must_be_an_integer, numericStart, identifierStart - numericStart + 1); + } + } + else { + error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length); + pos = identifierStart; + } } - /** Optionally, get the shebang */ - export function getShebang(text: string): string | undefined { - const match = shebangTriviaRegex.exec(text); - if (match) { - return match[0]; + function scanOctalDigits(): number { + const start = pos; + while (isOctalDigit(text.charCodeAt(pos))) { + pos++; } + return +(text.substring(start, pos)); } - export function isIdentifierStart(ch: number, languageVersion: ScriptTarget | undefined): boolean { - return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || - ch === CharacterCodes.$ || ch === CharacterCodes._ || - ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierStart(ch, languageVersion); + /** + * Scans the given number of hexadecimal digits in the text, + * returning -1 if the given number is unavailable. + */ + function scanExactNumberOfHexDigits(count: number, canHaveSeparators: boolean): number { + const valueString = scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ false, canHaveSeparators); + return valueString ? parseInt(valueString, 16) : -1; } - export function isIdentifierPart(ch: number, languageVersion: ScriptTarget | undefined, identifierVariant?: LanguageVariant): boolean { - return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || - ch >= CharacterCodes._0 && ch <= CharacterCodes._9 || ch === CharacterCodes.$ || ch === CharacterCodes._ || - // "-" and ":" are valid in JSX Identifiers - (identifierVariant === LanguageVariant.JSX ? (ch === CharacterCodes.minus || ch === CharacterCodes.colon) : false) || - ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierPart(ch, languageVersion); + /** + * Scans as many hexadecimal digits as are available in the text, + * returning "" if the given number of digits was unavailable. + */ + function scanMinimumNumberOfHexDigits(count: number, canHaveSeparators: boolean): string { + return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ true, canHaveSeparators); } - /* @internal */ - export function isIdentifierText(name: string, languageVersion: ScriptTarget | undefined, identifierVariant?: LanguageVariant): boolean { - let ch = codePointAt(name, 0); - if (!isIdentifierStart(ch, languageVersion)) { - return false; + function scanHexDigits(minCount: number, scanAsManyAsPossible: boolean, canHaveSeparators: boolean): string { + let valueChars: number[] = []; + let allowSeparator = false; + let isPreviousTokenSeparator = false; + while (valueChars.length < minCount || scanAsManyAsPossible) { + let ch = text.charCodeAt(pos); + if (canHaveSeparators && ch === CharacterCodes._) { + tokenFlags |= TokenFlags.ContainsSeparator; + if (allowSeparator) { + allowSeparator = false; + isPreviousTokenSeparator = true; + } + else if (isPreviousTokenSeparator) { + error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + } + else { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); + } + pos++; + continue; + } + allowSeparator = canHaveSeparators; + if (ch >= CharacterCodes.A && ch <= CharacterCodes.F) { + ch += CharacterCodes.a - CharacterCodes.A; // standardize hex literals to lowercase + } + else if (!((ch >= CharacterCodes._0 && ch <= CharacterCodes._9) || + (ch >= CharacterCodes.a && ch <= CharacterCodes.f) + )) { + break; + } + valueChars.push(ch); + pos++; + isPreviousTokenSeparator = false; + } + if (valueChars.length < minCount) { + valueChars = []; } + if (text.charCodeAt(pos - 1) === CharacterCodes._) { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); + } + return String.fromCharCode(...valueChars); + } - for (let i = charSize(ch); i < name.length; i += charSize(ch)) { - if (!isIdentifierPart(ch = codePointAt(name, i), languageVersion, identifierVariant)) { - return false; + function scanString(jsxAttributeString = false): string { + const quote = text.charCodeAt(pos); + pos++; + let result = ""; + let start = pos; + while (true) { + if (pos >= end) { + result += text.substring(start, pos); + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_string_literal); + break; + } + const ch = text.charCodeAt(pos); + if (ch === quote) { + result += text.substring(start, pos); + pos++; + break; + } + if (ch === CharacterCodes.backslash && !jsxAttributeString) { + result += text.substring(start, pos); + result += scanEscapeSequence(); + start = pos; + continue; } + if (isLineBreak(ch) && !jsxAttributeString) { + result += text.substring(start, pos); + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_string_literal); + break; + } + pos++; } - - return true; + return result; } - // Creates a scanner over a (possibly unspecified) range of a piece of text. - export function createScanner(languageVersion: ScriptTarget, - skipTrivia: boolean, - languageVariant = LanguageVariant.Standard, - textInitial?: string, - onError?: ErrorCallback, - start?: number, - length?: number): Scanner { - - let text = textInitial!; - - // Current position (end position of text of current token) - let pos: number; - - - // end of text - let end: number; - - // Start position of whitespace before current token - let startPos: number; - - // Start position of text of current token - let tokenPos: number; + /** + * Sets the current 'tokenValue' and returns a NoSubstitutionTemplateLiteral or + * a literal component of a TemplateExpression. + */ + function scanTemplateAndSetTokenValue(isTaggedTemplate: boolean): SyntaxKind { + const startedWithBacktick = text.charCodeAt(pos) === CharacterCodes.backtick; - let token: SyntaxKind; - let tokenValue!: string; - let tokenFlags: TokenFlags; + pos++; + let start = pos; + let contents = ""; + let resultingToken: SyntaxKind; - let commentDirectives: CommentDirective[] | undefined; - let inJSDocType = 0; + while (true) { + if (pos >= end) { + contents += text.substring(start, pos); + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_template_literal); + resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; + break; + } - setText(text, start, length); + const currChar = text.charCodeAt(pos); - const scanner: Scanner = { - getStartPos: () => startPos, - getTextPos: () => pos, - getToken: () => token, - getTokenPos: () => tokenPos, - getTokenText: () => text.substring(tokenPos, pos), - getTokenValue: () => tokenValue, - hasUnicodeEscape: () => (tokenFlags & TokenFlags.UnicodeEscape) !== 0, - hasExtendedUnicodeEscape: () => (tokenFlags & TokenFlags.ExtendedUnicodeEscape) !== 0, - hasPrecedingLineBreak: () => (tokenFlags & TokenFlags.PrecedingLineBreak) !== 0, - hasPrecedingJSDocComment: () => (tokenFlags & TokenFlags.PrecedingJSDocComment) !== 0, - isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord, - isReservedWord: () => token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord, - isUnterminated: () => (tokenFlags & TokenFlags.Unterminated) !== 0, - getCommentDirectives: () => commentDirectives, - getNumericLiteralFlags: () => tokenFlags & TokenFlags.NumericLiteralFlags, - getTokenFlags: () => tokenFlags, - reScanGreaterToken, - reScanAsteriskEqualsToken, - reScanSlashToken, - reScanTemplateToken, - reScanTemplateHeadOrNoSubstitutionTemplate, - scanJsxIdentifier, - scanJsxAttributeValue, - reScanJsxAttributeValue, - reScanJsxToken, - reScanLessThanToken, - reScanHashToken, - reScanQuestionToken, - reScanInvalidIdentifier, - scanJsxToken, - scanJsDocToken, - scan, - getText, - clearCommentDirectives, - setText, - setScriptTarget, - setLanguageVariant, - setOnError, - setTextPos, - setInJSDocType, - tryScan, - lookAhead, - scanRange, - }; - - if (Debug.isDebugging) { - Object.defineProperty(scanner, "__debugShowCurrentPositionInText", { - get: () => { - const text = scanner.getText(); - return text.slice(0, scanner.getStartPos()) + "║" + text.slice(scanner.getStartPos()); - }, - }); - } - - return scanner; - - function error(message: DiagnosticMessage): void; - function error(message: DiagnosticMessage, errPos: number, length: number): void; - function error(message: DiagnosticMessage, errPos: number = pos, length?: number): void { - if (onError) { - const oldPos = pos; - pos = errPos; - onError(message, length || 0); - pos = oldPos; + // '`' + if (currChar === CharacterCodes.backtick) { + contents += text.substring(start, pos); + pos++; + resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; + break; } - } - function scanNumberFragment(): string { - let start = pos; - let allowSeparator = false; - let isPreviousTokenSeparator = false; - let result = ""; - while (true) { - const ch = text.charCodeAt(pos); - if (ch === CharacterCodes._) { - tokenFlags |= TokenFlags.ContainsSeparator; - if (allowSeparator) { - allowSeparator = false; - isPreviousTokenSeparator = true; - result += text.substring(start, pos); - } - else if (isPreviousTokenSeparator) { - error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); - } - else { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); - } - pos++; - start = pos; - continue; - } - if (isDigit(ch)) { - allowSeparator = true; - isPreviousTokenSeparator = false; - pos++; - continue; - } + // '${' + if (currChar === CharacterCodes.$ && pos + 1 < end && text.charCodeAt(pos + 1) === CharacterCodes.openBrace) { + contents += text.substring(start, pos); + pos += 2; + resultingToken = startedWithBacktick ? SyntaxKind.TemplateHead : SyntaxKind.TemplateMiddle; break; } - if (text.charCodeAt(pos - 1) === CharacterCodes._) { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); - } - return result + text.substring(start, pos); - } - function scanNumber(): { type: SyntaxKind, value: string } { - const start = pos; - const mainFragment = scanNumberFragment(); - let decimalFragment: string | undefined; - let scientificFragment: string | undefined; - if (text.charCodeAt(pos) === CharacterCodes.dot) { - pos++; - decimalFragment = scanNumberFragment(); + // Escape character + if (currChar === CharacterCodes.backslash) { + contents += text.substring(start, pos); + contents += scanEscapeSequence(isTaggedTemplate); + start = pos; + continue; } - let end = pos; - if (text.charCodeAt(pos) === CharacterCodes.E || text.charCodeAt(pos) === CharacterCodes.e) { + + // Speculated ECMAScript 6 Spec 11.8.6.1: + // and LineTerminatorSequences are normalized to for Template Values + if (currChar === CharacterCodes.carriageReturn) { + contents += text.substring(start, pos); pos++; - tokenFlags |= TokenFlags.Scientific; - if (text.charCodeAt(pos) === CharacterCodes.plus || text.charCodeAt(pos) === CharacterCodes.minus) pos++; - const preNumericPart = pos; - const finalFragment = scanNumberFragment(); - if (!finalFragment) { - error(Diagnostics.Digit_expected); - } - else { - scientificFragment = text.substring(end, preNumericPart) + finalFragment; - end = pos; - } - } - let result: string; - if (tokenFlags & TokenFlags.ContainsSeparator) { - result = mainFragment; - if (decimalFragment) { - result += "." + decimalFragment; - } - if (scientificFragment) { - result += scientificFragment; + + if (pos < end && text.charCodeAt(pos) === CharacterCodes.lineFeed) { + pos++; } - } - else { - result = text.substring(start, end); // No need to use all the fragments; no _ removal needed - } - if (decimalFragment !== undefined || tokenFlags & TokenFlags.Scientific) { - checkForIdentifierStartAfterNumericLiteral(start, decimalFragment === undefined && !!(tokenFlags & TokenFlags.Scientific)); - return { - type: SyntaxKind.NumericLiteral, - value: "" + +result // if value is not an integer, it can be safely coerced to a number - }; - } - else { - tokenValue = result; - const type = checkBigIntSuffix(); // if value is an integer, check whether it is a bigint - checkForIdentifierStartAfterNumericLiteral(start); - return { type, value: tokenValue }; + contents += "\n"; + start = pos; + continue; } + + pos++; } - function checkForIdentifierStartAfterNumericLiteral(numericStart: number, isScientific?: boolean) { - if (!isIdentifierStart(codePointAt(text, pos), languageVersion)) { - return; - } + Debug.assert(resultingToken !== undefined); - const identifierStart = pos; - const { length } = scanIdentifierParts(); + tokenValue = contents; + return resultingToken; + } - if (length === 1 && text[identifierStart] === "n") { - if (isScientific) { - error(Diagnostics.A_bigint_literal_cannot_use_exponential_notation, numericStart, identifierStart - numericStart + 1); + function scanEscapeSequence(isTaggedTemplate?: boolean): string { + const start = pos; + pos++; + if (pos >= end) { + error(Diagnostics.Unexpected_end_of_text); + return ""; + } + const ch = text.charCodeAt(pos); + pos++; + switch (ch) { + case CharacterCodes._0: + // '\01' + if (isTaggedTemplate && pos < end && isDigit(text.charCodeAt(pos))) { + pos++; + tokenFlags |= TokenFlags.ContainsInvalidEscape; + return text.substring(start, pos); } - else { - error(Diagnostics.A_bigint_literal_must_be_an_integer, numericStart, identifierStart - numericStart + 1); + return "\0"; + case CharacterCodes.b: + return "\b"; + case CharacterCodes.t: + return "\t"; + case CharacterCodes.n: + return "\n"; + case CharacterCodes.v: + return "\v"; + case CharacterCodes.f: + return "\f"; + case CharacterCodes.r: + return "\r"; + case CharacterCodes.singleQuote: + return "\'"; + case CharacterCodes.doubleQuote: + return "\""; + case CharacterCodes.u: + if (isTaggedTemplate) { + // '\u' or '\u0' or '\u00' or '\u000' + for (let escapePos = pos; escapePos < pos + 4; escapePos++) { + if (escapePos < end && !isHexDigit(text.charCodeAt(escapePos)) && text.charCodeAt(escapePos) !== CharacterCodes.openBrace) { + pos = escapePos; + tokenFlags |= TokenFlags.ContainsInvalidEscape; + return text.substring(start, pos); + } + } } - } - else { - error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length); - pos = identifierStart; - } - } + // '\u{DDDDDDDD}' + if (pos < end && text.charCodeAt(pos) === CharacterCodes.openBrace) { + pos++; - function scanOctalDigits(): number { - const start = pos; - while (isOctalDigit(text.charCodeAt(pos))) { - pos++; - } - return +(text.substring(start, pos)); - } - - /** - * Scans the given number of hexadecimal digits in the text, - * returning -1 if the given number is unavailable. - */ - function scanExactNumberOfHexDigits(count: number, canHaveSeparators: boolean): number { - const valueString = scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ false, canHaveSeparators); - return valueString ? parseInt(valueString, 16) : -1; - } - - /** - * Scans as many hexadecimal digits as are available in the text, - * returning "" if the given number of digits was unavailable. - */ - function scanMinimumNumberOfHexDigits(count: number, canHaveSeparators: boolean): string { - return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ true, canHaveSeparators); - } - - function scanHexDigits(minCount: number, scanAsManyAsPossible: boolean, canHaveSeparators: boolean): string { - let valueChars: number[] = []; - let allowSeparator = false; - let isPreviousTokenSeparator = false; - while (valueChars.length < minCount || scanAsManyAsPossible) { - let ch = text.charCodeAt(pos); - if (canHaveSeparators && ch === CharacterCodes._) { - tokenFlags |= TokenFlags.ContainsSeparator; - if (allowSeparator) { - allowSeparator = false; - isPreviousTokenSeparator = true; + // '\u{' + if (isTaggedTemplate && !isHexDigit(text.charCodeAt(pos))) { + tokenFlags |= TokenFlags.ContainsInvalidEscape; + return text.substring(start, pos); } - else if (isPreviousTokenSeparator) { - error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + + if (isTaggedTemplate) { + const savePos = pos; + const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); + const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; + + // '\u{Not Code Point' or '\u{CodePoint' + if (!isCodePoint(escapedValue) || text.charCodeAt(pos) !== CharacterCodes.closeBrace) { + tokenFlags |= TokenFlags.ContainsInvalidEscape; + return text.substring(start, pos); + } + else { + pos = savePos; + } } - else { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); - } - pos++; - continue; - } - allowSeparator = canHaveSeparators; - if (ch >= CharacterCodes.A && ch <= CharacterCodes.F) { - ch += CharacterCodes.a - CharacterCodes.A; // standardize hex literals to lowercase + tokenFlags |= TokenFlags.ExtendedUnicodeEscape; + return scanExtendedUnicodeEscape(); } - else if (!((ch >= CharacterCodes._0 && ch <= CharacterCodes._9) || - (ch >= CharacterCodes.a && ch <= CharacterCodes.f) - )) { - break; - } - valueChars.push(ch); - pos++; - isPreviousTokenSeparator = false; - } - if (valueChars.length < minCount) { - valueChars = []; - } - if (text.charCodeAt(pos - 1) === CharacterCodes._) { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); - } - return String.fromCharCode(...valueChars); - } - function scanString(jsxAttributeString = false): string { - const quote = text.charCodeAt(pos); - pos++; - let result = ""; - let start = pos; - while (true) { - if (pos >= end) { - result += text.substring(start, pos); - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_string_literal); - break; - } - const ch = text.charCodeAt(pos); - if (ch === quote) { - result += text.substring(start, pos); - pos++; - break; - } - if (ch === CharacterCodes.backslash && !jsxAttributeString) { - result += text.substring(start, pos); - result += scanEscapeSequence(); - start = pos; - continue; - } - if (isLineBreak(ch) && !jsxAttributeString) { - result += text.substring(start, pos); - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_string_literal); - break; - } - pos++; - } - return result; - } + tokenFlags |= TokenFlags.UnicodeEscape; + // '\uDDDD' + return scanHexadecimalEscape(/*numDigits*/ 4); - /** - * Sets the current 'tokenValue' and returns a NoSubstitutionTemplateLiteral or - * a literal component of a TemplateExpression. - */ - function scanTemplateAndSetTokenValue(isTaggedTemplate: boolean): SyntaxKind { - const startedWithBacktick = text.charCodeAt(pos) === CharacterCodes.backtick; - - pos++; - let start = pos; - let contents = ""; - let resultingToken: SyntaxKind; - - while (true) { - if (pos >= end) { - contents += text.substring(start, pos); - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_template_literal); - resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; - break; + case CharacterCodes.x: + if (isTaggedTemplate) { + if (!isHexDigit(text.charCodeAt(pos))) { + tokenFlags |= TokenFlags.ContainsInvalidEscape; + return text.substring(start, pos); + } + else if (!isHexDigit(text.charCodeAt(pos + 1))) { + pos++; + tokenFlags |= TokenFlags.ContainsInvalidEscape; + return text.substring(start, pos); + } } + // '\xDD' + return scanHexadecimalEscape(/*numDigits*/ 2); - const currChar = text.charCodeAt(pos); - - // '`' - if (currChar === CharacterCodes.backtick) { - contents += text.substring(start, pos); + // when encountering a LineContinuation (i.e. a backslash and a line terminator sequence), + // the line terminator is interpreted to be "the empty code unit sequence". + case CharacterCodes.carriageReturn: + if (pos < end && text.charCodeAt(pos) === CharacterCodes.lineFeed) { pos++; - resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; - break; - } - - // '${' - if (currChar === CharacterCodes.$ && pos + 1 < end && text.charCodeAt(pos + 1) === CharacterCodes.openBrace) { - contents += text.substring(start, pos); - pos += 2; - resultingToken = startedWithBacktick ? SyntaxKind.TemplateHead : SyntaxKind.TemplateMiddle; - break; } + // falls through + case CharacterCodes.lineFeed: + case CharacterCodes.lineSeparator: + case CharacterCodes.paragraphSeparator: + return ""; + default: + return String.fromCharCode(ch); + } + } - // Escape character - if (currChar === CharacterCodes.backslash) { - contents += text.substring(start, pos); - contents += scanEscapeSequence(isTaggedTemplate); - start = pos; - continue; - } - - // Speculated ECMAScript 6 Spec 11.8.6.1: - // and LineTerminatorSequences are normalized to for Template Values - if (currChar === CharacterCodes.carriageReturn) { - contents += text.substring(start, pos); - pos++; - - if (pos < end && text.charCodeAt(pos) === CharacterCodes.lineFeed) { - pos++; - } - - contents += "\n"; - start = pos; - continue; - } + function scanHexadecimalEscape(numDigits: number): string { + const escapedValue = scanExactNumberOfHexDigits(numDigits, /*canHaveSeparators*/ false); - pos++; - } + if (escapedValue >= 0) { + return String.fromCharCode(escapedValue); + } + else { + error(Diagnostics.Hexadecimal_digit_expected); + return ""; + } + } - Debug.assert(resultingToken !== undefined); + function scanExtendedUnicodeEscape(): string { + const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); + const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; + let isInvalidExtendedEscape = false; - tokenValue = contents; - return resultingToken; + // Validate the value of the digit + if (escapedValue < 0) { + error(Diagnostics.Hexadecimal_digit_expected); + isInvalidExtendedEscape = true; + } + else if (escapedValue > 0x10FFFF) { + error(Diagnostics.An_extended_Unicode_escape_value_must_be_between_0x0_and_0x10FFFF_inclusive); + isInvalidExtendedEscape = true; } - function scanEscapeSequence(isTaggedTemplate?: boolean): string { - const start = pos; - pos++; - if (pos >= end) { - error(Diagnostics.Unexpected_end_of_text); - return ""; - } - const ch = text.charCodeAt(pos); + if (pos >= end) { + error(Diagnostics.Unexpected_end_of_text); + isInvalidExtendedEscape = true; + } + else if (text.charCodeAt(pos) === CharacterCodes.closeBrace) { + // Only swallow the following character up if it's a '}'. pos++; - switch (ch) { - case CharacterCodes._0: - // '\01' - if (isTaggedTemplate && pos < end && isDigit(text.charCodeAt(pos))) { - pos++; - tokenFlags |= TokenFlags.ContainsInvalidEscape; - return text.substring(start, pos); - } - return "\0"; - case CharacterCodes.b: - return "\b"; - case CharacterCodes.t: - return "\t"; - case CharacterCodes.n: - return "\n"; - case CharacterCodes.v: - return "\v"; - case CharacterCodes.f: - return "\f"; - case CharacterCodes.r: - return "\r"; - case CharacterCodes.singleQuote: - return "\'"; - case CharacterCodes.doubleQuote: - return "\""; - case CharacterCodes.u: - if (isTaggedTemplate) { - // '\u' or '\u0' or '\u00' or '\u000' - for (let escapePos = pos; escapePos < pos + 4; escapePos++) { - if (escapePos < end && !isHexDigit(text.charCodeAt(escapePos)) && text.charCodeAt(escapePos) !== CharacterCodes.openBrace) { - pos = escapePos; - tokenFlags |= TokenFlags.ContainsInvalidEscape; - return text.substring(start, pos); - } - } - } - // '\u{DDDDDDDD}' - if (pos < end && text.charCodeAt(pos) === CharacterCodes.openBrace) { - pos++; - - // '\u{' - if (isTaggedTemplate && !isHexDigit(text.charCodeAt(pos))) { - tokenFlags |= TokenFlags.ContainsInvalidEscape; - return text.substring(start, pos); - } - - if (isTaggedTemplate) { - const savePos = pos; - const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); - const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; - - // '\u{Not Code Point' or '\u{CodePoint' - if (!isCodePoint(escapedValue) || text.charCodeAt(pos) !== CharacterCodes.closeBrace) { - tokenFlags |= TokenFlags.ContainsInvalidEscape; - return text.substring(start, pos); - } - else { - pos = savePos; - } - } - tokenFlags |= TokenFlags.ExtendedUnicodeEscape; - return scanExtendedUnicodeEscape(); - } - - tokenFlags |= TokenFlags.UnicodeEscape; - // '\uDDDD' - return scanHexadecimalEscape(/*numDigits*/ 4); - - case CharacterCodes.x: - if (isTaggedTemplate) { - if (!isHexDigit(text.charCodeAt(pos))) { - tokenFlags |= TokenFlags.ContainsInvalidEscape; - return text.substring(start, pos); - } - else if (!isHexDigit(text.charCodeAt(pos + 1))) { - pos++; - tokenFlags |= TokenFlags.ContainsInvalidEscape; - return text.substring(start, pos); - } - } - // '\xDD' - return scanHexadecimalEscape(/*numDigits*/ 2); + } + else { + error(Diagnostics.Unterminated_Unicode_escape_sequence); + isInvalidExtendedEscape = true; + } - // when encountering a LineContinuation (i.e. a backslash and a line terminator sequence), - // the line terminator is interpreted to be "the empty code unit sequence". - case CharacterCodes.carriageReturn: - if (pos < end && text.charCodeAt(pos) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: - case CharacterCodes.lineSeparator: - case CharacterCodes.paragraphSeparator: - return ""; - default: - return String.fromCharCode(ch); - } + if (isInvalidExtendedEscape) { + return ""; } - function scanHexadecimalEscape(numDigits: number): string { - const escapedValue = scanExactNumberOfHexDigits(numDigits, /*canHaveSeparators*/ false); + return utf16EncodeAsString(escapedValue); + } - if (escapedValue >= 0) { - return String.fromCharCode(escapedValue); - } - else { - error(Diagnostics.Hexadecimal_digit_expected); - return ""; - } + // Current character is known to be a backslash. Check for Unicode escape of the form '\uXXXX' + // and return code point value if valid Unicode escape is found. Otherwise return -1. + function peekUnicodeEscape(): number { + if (pos + 5 < end && text.charCodeAt(pos + 1) === CharacterCodes.u) { + const start = pos; + pos += 2; + const value = scanExactNumberOfHexDigits(4, /*canHaveSeparators*/ false); + pos = start; + return value; } + return -1; + } + - function scanExtendedUnicodeEscape(): string { + function peekExtendedUnicodeEscape(): number { + if (languageVersion >= ScriptTarget.ES2015 && codePointAt(text, pos + 1) === CharacterCodes.u && codePointAt(text, pos + 2) === CharacterCodes.openBrace) { + const start = pos; + pos += 3; const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; - let isInvalidExtendedEscape = false; - - // Validate the value of the digit - if (escapedValue < 0) { - error(Diagnostics.Hexadecimal_digit_expected); - isInvalidExtendedEscape = true; - } - else if (escapedValue > 0x10FFFF) { - error(Diagnostics.An_extended_Unicode_escape_value_must_be_between_0x0_and_0x10FFFF_inclusive); - isInvalidExtendedEscape = true; - } + pos = start; + return escapedValue; + } + return -1; + } - if (pos >= end) { - error(Diagnostics.Unexpected_end_of_text); - isInvalidExtendedEscape = true; + function scanIdentifierParts(): string { + let result = ""; + let start = pos; + while (pos < end) { + let ch = codePointAt(text, pos); + if (isIdentifierPart(ch, languageVersion)) { + pos += charSize(ch); } - else if (text.charCodeAt(pos) === CharacterCodes.closeBrace) { - // Only swallow the following character up if it's a '}'. - pos++; + else if (ch === CharacterCodes.backslash) { + ch = peekExtendedUnicodeEscape(); + if (ch >= 0 && isIdentifierPart(ch, languageVersion)) { + pos += 3; + tokenFlags |= TokenFlags.ExtendedUnicodeEscape; + result += scanExtendedUnicodeEscape(); + start = pos; + continue; + } + ch = peekUnicodeEscape(); + if (!(ch >= 0 && isIdentifierPart(ch, languageVersion))) { + break; + } + tokenFlags |= TokenFlags.UnicodeEscape; + result += text.substring(start, pos); + result += utf16EncodeAsString(ch); + // Valid Unicode escape is always six characters + pos += 6; + start = pos; } else { - error(Diagnostics.Unterminated_Unicode_escape_sequence); - isInvalidExtendedEscape = true; - } - - if (isInvalidExtendedEscape) { - return ""; - } - - return utf16EncodeAsString(escapedValue); - } - - // Current character is known to be a backslash. Check for Unicode escape of the form '\uXXXX' - // and return code point value if valid Unicode escape is found. Otherwise return -1. - function peekUnicodeEscape(): number { - if (pos + 5 < end && text.charCodeAt(pos + 1) === CharacterCodes.u) { - const start = pos; - pos += 2; - const value = scanExactNumberOfHexDigits(4, /*canHaveSeparators*/ false); - pos = start; - return value; + break; } - return -1; } + result += text.substring(start, pos); + return result; + } - - function peekExtendedUnicodeEscape(): number { - if (languageVersion >= ScriptTarget.ES2015 && codePointAt(text, pos + 1) === CharacterCodes.u && codePointAt(text, pos + 2) === CharacterCodes.openBrace) { - const start = pos; - pos += 3; - const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); - const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; - pos = start; - return escapedValue; + function getIdentifierToken(): SyntaxKind.Identifier | KeywordSyntaxKind { + // Reserved words are between 2 and 12 characters long and start with a lowercase letter + const len = tokenValue.length; + if (len >= 2 && len <= 12) { + const ch = tokenValue.charCodeAt(0); + if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) { + const keyword = textToKeyword.get(tokenValue); + if (keyword !== undefined) { + return token = keyword; + } } - return -1; } + return token = SyntaxKind.Identifier; + } - function scanIdentifierParts(): string { - let result = ""; - let start = pos; - while (pos < end) { - let ch = codePointAt(text, pos); - if (isIdentifierPart(ch, languageVersion)) { - pos += charSize(ch); + function scanBinaryOrOctalDigits(base: 2 | 8): string { + let value = ""; + // For counting number of digits; Valid binaryIntegerLiteral must have at least one binary digit following B or b. + // Similarly valid octalIntegerLiteral must have at least one octal digit following o or O. + let separatorAllowed = false; + let isPreviousTokenSeparator = false; + while (true) { + const ch = text.charCodeAt(pos); + // Numeric separators are allowed anywhere within a numeric literal, except not at the beginning, or following another separator + if (ch === CharacterCodes._) { + tokenFlags |= TokenFlags.ContainsSeparator; + if (separatorAllowed) { + separatorAllowed = false; + isPreviousTokenSeparator = true; } - else if (ch === CharacterCodes.backslash) { - ch = peekExtendedUnicodeEscape(); - if (ch >= 0 && isIdentifierPart(ch, languageVersion)) { - pos += 3; - tokenFlags |= TokenFlags.ExtendedUnicodeEscape; - result += scanExtendedUnicodeEscape(); - start = pos; - continue; - } - ch = peekUnicodeEscape(); - if (!(ch >= 0 && isIdentifierPart(ch, languageVersion))) { - break; - } - tokenFlags |= TokenFlags.UnicodeEscape; - result += text.substring(start, pos); - result += utf16EncodeAsString(ch); - // Valid Unicode escape is always six characters - pos += 6; - start = pos; + else if (isPreviousTokenSeparator) { + error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); } else { - break; + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); } + pos++; + continue; } - result += text.substring(start, pos); - return result; - } - - function getIdentifierToken(): SyntaxKind.Identifier | KeywordSyntaxKind { - // Reserved words are between 2 and 12 characters long and start with a lowercase letter - const len = tokenValue.length; - if (len >= 2 && len <= 12) { - const ch = tokenValue.charCodeAt(0); - if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) { - const keyword = textToKeyword.get(tokenValue); - if (keyword !== undefined) { - return token = keyword; - } - } + separatorAllowed = true; + if (!isDigit(ch) || ch - CharacterCodes._0 >= base) { + break; } - return token = SyntaxKind.Identifier; + value += text[pos]; + pos++; + isPreviousTokenSeparator = false; + } + if (text.charCodeAt(pos - 1) === CharacterCodes._) { + // Literal ends with underscore - not allowed + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); } + return value; + } - function scanBinaryOrOctalDigits(base: 2 | 8): string { - let value = ""; - // For counting number of digits; Valid binaryIntegerLiteral must have at least one binary digit following B or b. - // Similarly valid octalIntegerLiteral must have at least one octal digit following o or O. - let separatorAllowed = false; - let isPreviousTokenSeparator = false; - while (true) { - const ch = text.charCodeAt(pos); - // Numeric separators are allowed anywhere within a numeric literal, except not at the beginning, or following another separator - if (ch === CharacterCodes._) { - tokenFlags |= TokenFlags.ContainsSeparator; - if (separatorAllowed) { - separatorAllowed = false; - isPreviousTokenSeparator = true; - } - else if (isPreviousTokenSeparator) { - error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); - } - else { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); - } - pos++; - continue; - } - separatorAllowed = true; - if (!isDigit(ch) || ch - CharacterCodes._0 >= base) { - break; - } - value += text[pos]; - pos++; - isPreviousTokenSeparator = false; + function checkBigIntSuffix(): SyntaxKind { + if (text.charCodeAt(pos) === CharacterCodes.n) { + tokenValue += "n"; + // Use base 10 instead of base 2 or base 8 for shorter literals + if (tokenFlags & TokenFlags.BinaryOrOctalSpecifier) { + tokenValue = parsePseudoBigInt(tokenValue) + "n"; } - if (text.charCodeAt(pos - 1) === CharacterCodes._) { - // Literal ends with underscore - not allowed - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); - } - return value; + pos++; + return SyntaxKind.BigIntLiteral; + } + else { // not a bigint, so can convert to number in simplified form + // Number() may not support 0b or 0o, so use parseInt() instead + const numericValue = tokenFlags & TokenFlags.BinarySpecifier + ? parseInt(tokenValue.slice(2), 2) // skip "0b" + : tokenFlags & TokenFlags.OctalSpecifier + ? parseInt(tokenValue.slice(2), 8) // skip "0o" + : +tokenValue; + tokenValue = "" + numericValue; + return SyntaxKind.NumericLiteral; } + } - function checkBigIntSuffix(): SyntaxKind { - if (text.charCodeAt(pos) === CharacterCodes.n) { - tokenValue += "n"; - // Use base 10 instead of base 2 or base 8 for shorter literals - if (tokenFlags & TokenFlags.BinaryOrOctalSpecifier) { - tokenValue = parsePseudoBigInt(tokenValue) + "n"; - } - pos++; - return SyntaxKind.BigIntLiteral; - } - else { // not a bigint, so can convert to number in simplified form - // Number() may not support 0b or 0o, so use parseInt() instead - const numericValue = tokenFlags & TokenFlags.BinarySpecifier - ? parseInt(tokenValue.slice(2), 2) // skip "0b" - : tokenFlags & TokenFlags.OctalSpecifier - ? parseInt(tokenValue.slice(2), 8) // skip "0o" - : +tokenValue; - tokenValue = "" + numericValue; - return SyntaxKind.NumericLiteral; + function scan(): SyntaxKind { + startPos = pos; + tokenFlags = TokenFlags.None; + let asteriskSeen = false; + while (true) { + tokenPos = pos; + if (pos >= end) { + return token = SyntaxKind.EndOfFileToken; } - } + const ch = codePointAt(text, pos); - function scan(): SyntaxKind { - startPos = pos; - tokenFlags = TokenFlags.None; - let asteriskSeen = false; - while (true) { - tokenPos = pos; - if (pos >= end) { - return token = SyntaxKind.EndOfFileToken; + // Special handling for shebang + if (ch === CharacterCodes.hash && pos === 0 && isShebangTrivia(text, pos)) { + pos = scanShebangTrivia(text, pos); + if (skipTrivia) { + continue; } - const ch = codePointAt(text, pos); + else { + return token = SyntaxKind.ShebangTrivia; + } + } - // Special handling for shebang - if (ch === CharacterCodes.hash && pos === 0 && isShebangTrivia(text, pos)) { - pos = scanShebangTrivia(text, pos); + switch (ch) { + case CharacterCodes.lineFeed: + case CharacterCodes.carriageReturn: + tokenFlags |= TokenFlags.PrecedingLineBreak; if (skipTrivia) { + pos++; continue; } else { - return token = SyntaxKind.ShebangTrivia; + if (ch === CharacterCodes.carriageReturn && pos + 1 < end && text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { + // consume both CR and LF + pos += 2; + } + else { + pos++; + } + return token = SyntaxKind.NewLineTrivia; } - } + case CharacterCodes.tab: + case CharacterCodes.verticalTab: + case CharacterCodes.formFeed: + case CharacterCodes.space: + case CharacterCodes.nonBreakingSpace: + case CharacterCodes.ogham: + case CharacterCodes.enQuad: + case CharacterCodes.emQuad: + case CharacterCodes.enSpace: + case CharacterCodes.emSpace: + case CharacterCodes.threePerEmSpace: + case CharacterCodes.fourPerEmSpace: + case CharacterCodes.sixPerEmSpace: + case CharacterCodes.figureSpace: + case CharacterCodes.punctuationSpace: + case CharacterCodes.thinSpace: + case CharacterCodes.hairSpace: + case CharacterCodes.zeroWidthSpace: + case CharacterCodes.narrowNoBreakSpace: + case CharacterCodes.mathematicalSpace: + case CharacterCodes.ideographicSpace: + case CharacterCodes.byteOrderMark: + if (skipTrivia) { + pos++; + continue; + } + else { + while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) { + pos++; + } + return token = SyntaxKind.WhitespaceTrivia; + } + case CharacterCodes.exclamation: + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.ExclamationEqualsEqualsToken; + } + return pos += 2, token = SyntaxKind.ExclamationEqualsToken; + } + pos++; + return token = SyntaxKind.ExclamationToken; + case CharacterCodes.doubleQuote: + case CharacterCodes.singleQuote: + tokenValue = scanString(); + return token = SyntaxKind.StringLiteral; + case CharacterCodes.backtick: + return token = scanTemplateAndSetTokenValue(/* isTaggedTemplate */ false); + case CharacterCodes.percent: + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.PercentEqualsToken; + } + pos++; + return token = SyntaxKind.PercentToken; + case CharacterCodes.ampersand: + if (text.charCodeAt(pos + 1) === CharacterCodes.ampersand) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.AmpersandAmpersandEqualsToken; + } + return pos += 2, token = SyntaxKind.AmpersandAmpersandToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.AmpersandEqualsToken; + } + pos++; + return token = SyntaxKind.AmpersandToken; + case CharacterCodes.openParen: + pos++; + return token = SyntaxKind.OpenParenToken; + case CharacterCodes.closeParen: + pos++; + return token = SyntaxKind.CloseParenToken; + case CharacterCodes.asterisk: + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.AsteriskEqualsToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.AsteriskAsteriskEqualsToken; + } + return pos += 2, token = SyntaxKind.AsteriskAsteriskToken; + } + pos++; + if (inJSDocType && !asteriskSeen && (tokenFlags & TokenFlags.PrecedingLineBreak)) { + // decoration at the start of a JSDoc comment line + asteriskSeen = true; + continue; + } + return token = SyntaxKind.AsteriskToken; + case CharacterCodes.plus: + if (text.charCodeAt(pos + 1) === CharacterCodes.plus) { + return pos += 2, token = SyntaxKind.PlusPlusToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.PlusEqualsToken; + } + pos++; + return token = SyntaxKind.PlusToken; + case CharacterCodes.comma: + pos++; + return token = SyntaxKind.CommaToken; + case CharacterCodes.minus: + if (text.charCodeAt(pos + 1) === CharacterCodes.minus) { + return pos += 2, token = SyntaxKind.MinusMinusToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.MinusEqualsToken; + } + pos++; + return token = SyntaxKind.MinusToken; + case CharacterCodes.dot: + if (isDigit(text.charCodeAt(pos + 1))) { + tokenValue = scanNumber().value; + return token = SyntaxKind.NumericLiteral; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.dot && text.charCodeAt(pos + 2) === CharacterCodes.dot) { + return pos += 3, token = SyntaxKind.DotDotDotToken; + } + pos++; + return token = SyntaxKind.DotToken; + case CharacterCodes.slash: + // Single-line comment + if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; - switch (ch) { - case CharacterCodes.lineFeed: - case CharacterCodes.carriageReturn: - tokenFlags |= TokenFlags.PrecedingLineBreak; - if (skipTrivia) { - pos++; - continue; - } - else { - if (ch === CharacterCodes.carriageReturn && pos + 1 < end && text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { - // consume both CR and LF - pos += 2; - } - else { - pos++; + while (pos < end) { + if (isLineBreak(text.charCodeAt(pos))) { + break; } - return token = SyntaxKind.NewLineTrivia; + pos++; } - case CharacterCodes.tab: - case CharacterCodes.verticalTab: - case CharacterCodes.formFeed: - case CharacterCodes.space: - case CharacterCodes.nonBreakingSpace: - case CharacterCodes.ogham: - case CharacterCodes.enQuad: - case CharacterCodes.emQuad: - case CharacterCodes.enSpace: - case CharacterCodes.emSpace: - case CharacterCodes.threePerEmSpace: - case CharacterCodes.fourPerEmSpace: - case CharacterCodes.sixPerEmSpace: - case CharacterCodes.figureSpace: - case CharacterCodes.punctuationSpace: - case CharacterCodes.thinSpace: - case CharacterCodes.hairSpace: - case CharacterCodes.zeroWidthSpace: - case CharacterCodes.narrowNoBreakSpace: - case CharacterCodes.mathematicalSpace: - case CharacterCodes.ideographicSpace: - case CharacterCodes.byteOrderMark: + + commentDirectives = appendIfCommentDirective( + commentDirectives, + text.slice(tokenPos, pos), + commentDirectiveRegExSingleLine, + tokenPos, + ); + if (skipTrivia) { - pos++; continue; } else { - while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) { - pos++; - } - return token = SyntaxKind.WhitespaceTrivia; - } - case CharacterCodes.exclamation: - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.ExclamationEqualsEqualsToken; - } - return pos += 2, token = SyntaxKind.ExclamationEqualsToken; - } - pos++; - return token = SyntaxKind.ExclamationToken; - case CharacterCodes.doubleQuote: - case CharacterCodes.singleQuote: - tokenValue = scanString(); - return token = SyntaxKind.StringLiteral; - case CharacterCodes.backtick: - return token = scanTemplateAndSetTokenValue(/* isTaggedTemplate */ false); - case CharacterCodes.percent: - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.PercentEqualsToken; - } - pos++; - return token = SyntaxKind.PercentToken; - case CharacterCodes.ampersand: - if (text.charCodeAt(pos + 1) === CharacterCodes.ampersand) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.AmpersandAmpersandEqualsToken; - } - return pos += 2, token = SyntaxKind.AmpersandAmpersandToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.AmpersandEqualsToken; - } - pos++; - return token = SyntaxKind.AmpersandToken; - case CharacterCodes.openParen: - pos++; - return token = SyntaxKind.OpenParenToken; - case CharacterCodes.closeParen: - pos++; - return token = SyntaxKind.CloseParenToken; - case CharacterCodes.asterisk: - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.AsteriskEqualsToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.AsteriskAsteriskEqualsToken; - } - return pos += 2, token = SyntaxKind.AsteriskAsteriskToken; - } - pos++; - if (inJSDocType && !asteriskSeen && (tokenFlags & TokenFlags.PrecedingLineBreak)) { - // decoration at the start of a JSDoc comment line - asteriskSeen = true; - continue; - } - return token = SyntaxKind.AsteriskToken; - case CharacterCodes.plus: - if (text.charCodeAt(pos + 1) === CharacterCodes.plus) { - return pos += 2, token = SyntaxKind.PlusPlusToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.PlusEqualsToken; - } - pos++; - return token = SyntaxKind.PlusToken; - case CharacterCodes.comma: - pos++; - return token = SyntaxKind.CommaToken; - case CharacterCodes.minus: - if (text.charCodeAt(pos + 1) === CharacterCodes.minus) { - return pos += 2, token = SyntaxKind.MinusMinusToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.MinusEqualsToken; - } - pos++; - return token = SyntaxKind.MinusToken; - case CharacterCodes.dot: - if (isDigit(text.charCodeAt(pos + 1))) { - tokenValue = scanNumber().value; - return token = SyntaxKind.NumericLiteral; + return token = SyntaxKind.SingleLineCommentTrivia; } - if (text.charCodeAt(pos + 1) === CharacterCodes.dot && text.charCodeAt(pos + 2) === CharacterCodes.dot) { - return pos += 3, token = SyntaxKind.DotDotDotToken; + } + // Multi-line comment + if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { + pos += 2; + if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) !== CharacterCodes.slash) { + tokenFlags |= TokenFlags.PrecedingJSDocComment; } - pos++; - return token = SyntaxKind.DotToken; - case CharacterCodes.slash: - // Single-line comment - if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - while (pos < end) { - if (isLineBreak(text.charCodeAt(pos))) { - break; - } - pos++; + let commentClosed = false; + let lastLineStart = tokenPos; + while (pos < end) { + const ch = text.charCodeAt(pos); + + if (ch === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + commentClosed = true; + break; } - commentDirectives = appendIfCommentDirective( - commentDirectives, - text.slice(tokenPos, pos), - commentDirectiveRegExSingleLine, - tokenPos, - ); + pos++; - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.SingleLineCommentTrivia; + if (isLineBreak(ch)) { + lastLineStart = pos; + tokenFlags |= TokenFlags.PrecedingLineBreak; } } - // Multi-line comment - if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { - pos += 2; - if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) !== CharacterCodes.slash) { - tokenFlags |= TokenFlags.PrecedingJSDocComment; - } - let commentClosed = false; - let lastLineStart = tokenPos; - while (pos < end) { - const ch = text.charCodeAt(pos); + commentDirectives = appendIfCommentDirective(commentDirectives, text.slice(lastLineStart, pos), commentDirectiveRegExMultiLine, lastLineStart); - if (ch === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - commentClosed = true; - break; - } - - pos++; - - if (isLineBreak(ch)) { - lastLineStart = pos; - tokenFlags |= TokenFlags.PrecedingLineBreak; - } - } - - commentDirectives = appendIfCommentDirective(commentDirectives, text.slice(lastLineStart, pos), commentDirectiveRegExMultiLine, lastLineStart); + if (!commentClosed) { + error(Diagnostics.Asterisk_Slash_expected); + } + if (skipTrivia) { + continue; + } + else { if (!commentClosed) { - error(Diagnostics.Asterisk_Slash_expected); - } - - if (skipTrivia) { - continue; - } - else { - if (!commentClosed) { - tokenFlags |= TokenFlags.Unterminated; - } - return token = SyntaxKind.MultiLineCommentTrivia; + tokenFlags |= TokenFlags.Unterminated; } + return token = SyntaxKind.MultiLineCommentTrivia; } + } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.SlashEqualsToken; - } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.SlashEqualsToken; + } - pos++; - return token = SyntaxKind.SlashToken; + pos++; + return token = SyntaxKind.SlashToken; - case CharacterCodes._0: - if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.X || text.charCodeAt(pos + 1) === CharacterCodes.x)) { - pos += 2; - tokenValue = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ true); - if (!tokenValue) { - error(Diagnostics.Hexadecimal_digit_expected); - tokenValue = "0"; - } - tokenValue = "0x" + tokenValue; - tokenFlags |= TokenFlags.HexSpecifier; - return token = checkBigIntSuffix(); + case CharacterCodes._0: + if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.X || text.charCodeAt(pos + 1) === CharacterCodes.x)) { + pos += 2; + tokenValue = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ true); + if (!tokenValue) { + error(Diagnostics.Hexadecimal_digit_expected); + tokenValue = "0"; } - else if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.B || text.charCodeAt(pos + 1) === CharacterCodes.b)) { - pos += 2; - tokenValue = scanBinaryOrOctalDigits(/* base */ 2); - if (!tokenValue) { - error(Diagnostics.Binary_digit_expected); - tokenValue = "0"; - } - tokenValue = "0b" + tokenValue; - tokenFlags |= TokenFlags.BinarySpecifier; - return token = checkBigIntSuffix(); + tokenValue = "0x" + tokenValue; + tokenFlags |= TokenFlags.HexSpecifier; + return token = checkBigIntSuffix(); + } + else if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.B || text.charCodeAt(pos + 1) === CharacterCodes.b)) { + pos += 2; + tokenValue = scanBinaryOrOctalDigits(/* base */ 2); + if (!tokenValue) { + error(Diagnostics.Binary_digit_expected); + tokenValue = "0"; } - else if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.O || text.charCodeAt(pos + 1) === CharacterCodes.o)) { - pos += 2; - tokenValue = scanBinaryOrOctalDigits(/* base */ 8); - if (!tokenValue) { - error(Diagnostics.Octal_digit_expected); - tokenValue = "0"; - } - tokenValue = "0o" + tokenValue; - tokenFlags |= TokenFlags.OctalSpecifier; - return token = checkBigIntSuffix(); + tokenValue = "0b" + tokenValue; + tokenFlags |= TokenFlags.BinarySpecifier; + return token = checkBigIntSuffix(); + } + else if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.O || text.charCodeAt(pos + 1) === CharacterCodes.o)) { + pos += 2; + tokenValue = scanBinaryOrOctalDigits(/* base */ 8); + if (!tokenValue) { + error(Diagnostics.Octal_digit_expected); + tokenValue = "0"; } - // Try to parse as an octal - if (pos + 1 < end && isOctalDigit(text.charCodeAt(pos + 1))) { - tokenValue = "" + scanOctalDigits(); - tokenFlags |= TokenFlags.Octal; - return token = SyntaxKind.NumericLiteral; + tokenValue = "0o" + tokenValue; + tokenFlags |= TokenFlags.OctalSpecifier; + return token = checkBigIntSuffix(); + } + // Try to parse as an octal + if (pos + 1 < end && isOctalDigit(text.charCodeAt(pos + 1))) { + tokenValue = "" + scanOctalDigits(); + tokenFlags |= TokenFlags.Octal; + return token = SyntaxKind.NumericLiteral; + } + // This fall-through is a deviation from the EcmaScript grammar. The grammar says that a leading zero + // can only be followed by an octal digit, a dot, or the end of the number literal. However, we are being + // permissive and allowing decimal digits of the form 08* and 09* (which many browsers also do). + // falls through + case CharacterCodes._1: + case CharacterCodes._2: + case CharacterCodes._3: + case CharacterCodes._4: + case CharacterCodes._5: + case CharacterCodes._6: + case CharacterCodes._7: + case CharacterCodes._8: + case CharacterCodes._9: + ({ type: token, value: tokenValue } = scanNumber()); + return token; + case CharacterCodes.colon: + pos++; + return token = SyntaxKind.ColonToken; + case CharacterCodes.semicolon: + pos++; + return token = SyntaxKind.SemicolonToken; + case CharacterCodes.lessThan: + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + if (skipTrivia) { + continue; } - // This fall-through is a deviation from the EcmaScript grammar. The grammar says that a leading zero - // can only be followed by an octal digit, a dot, or the end of the number literal. However, we are being - // permissive and allowing decimal digits of the form 08* and 09* (which many browsers also do). - // falls through - case CharacterCodes._1: - case CharacterCodes._2: - case CharacterCodes._3: - case CharacterCodes._4: - case CharacterCodes._5: - case CharacterCodes._6: - case CharacterCodes._7: - case CharacterCodes._8: - case CharacterCodes._9: - ({ type: token, value: tokenValue } = scanNumber()); - return token; - case CharacterCodes.colon: - pos++; - return token = SyntaxKind.ColonToken; - case CharacterCodes.semicolon: - pos++; - return token = SyntaxKind.SemicolonToken; - case CharacterCodes.lessThan: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.ConflictMarkerTrivia; - } + else { + return token = SyntaxKind.ConflictMarkerTrivia; } + } - if (text.charCodeAt(pos + 1) === CharacterCodes.lessThan) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.LessThanLessThanEqualsToken; - } - return pos += 2, token = SyntaxKind.LessThanLessThanToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.LessThanEqualsToken; + if (text.charCodeAt(pos + 1) === CharacterCodes.lessThan) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.LessThanLessThanEqualsToken; } - if (languageVariant === LanguageVariant.JSX && - text.charCodeAt(pos + 1) === CharacterCodes.slash && - text.charCodeAt(pos + 2) !== CharacterCodes.asterisk) { - return pos += 2, token = SyntaxKind.LessThanSlashToken; + return pos += 2, token = SyntaxKind.LessThanLessThanToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.LessThanEqualsToken; + } + if (languageVariant === LanguageVariant.JSX && + text.charCodeAt(pos + 1) === CharacterCodes.slash && + text.charCodeAt(pos + 2) !== CharacterCodes.asterisk) { + return pos += 2, token = SyntaxKind.LessThanSlashToken; + } + pos++; + return token = SyntaxKind.LessThanToken; + case CharacterCodes.equals: + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + if (skipTrivia) { + continue; } - pos++; - return token = SyntaxKind.LessThanToken; - case CharacterCodes.equals: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.ConflictMarkerTrivia; - } + else { + return token = SyntaxKind.ConflictMarkerTrivia; } + } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.EqualsEqualsEqualsToken; - } - return pos += 2, token = SyntaxKind.EqualsEqualsToken; + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.EqualsEqualsEqualsToken; } - if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { - return pos += 2, token = SyntaxKind.EqualsGreaterThanToken; + return pos += 2, token = SyntaxKind.EqualsEqualsToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { + return pos += 2, token = SyntaxKind.EqualsGreaterThanToken; + } + pos++; + return token = SyntaxKind.EqualsToken; + case CharacterCodes.greaterThan: + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + if (skipTrivia) { + continue; } - pos++; - return token = SyntaxKind.EqualsToken; - case CharacterCodes.greaterThan: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.ConflictMarkerTrivia; - } + else { + return token = SyntaxKind.ConflictMarkerTrivia; } + } - pos++; - return token = SyntaxKind.GreaterThanToken; - case CharacterCodes.question: - if (text.charCodeAt(pos + 1) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 2))) { - return pos += 2, token = SyntaxKind.QuestionDotToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.question) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.QuestionQuestionEqualsToken; - } - return pos += 2, token = SyntaxKind.QuestionQuestionToken; + pos++; + return token = SyntaxKind.GreaterThanToken; + case CharacterCodes.question: + if (text.charCodeAt(pos + 1) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 2))) { + return pos += 2, token = SyntaxKind.QuestionDotToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.question) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.QuestionQuestionEqualsToken; } - pos++; - return token = SyntaxKind.QuestionToken; - case CharacterCodes.openBracket: - pos++; - return token = SyntaxKind.OpenBracketToken; - case CharacterCodes.closeBracket: - pos++; - return token = SyntaxKind.CloseBracketToken; - case CharacterCodes.caret: - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.CaretEqualsToken; + return pos += 2, token = SyntaxKind.QuestionQuestionToken; + } + pos++; + return token = SyntaxKind.QuestionToken; + case CharacterCodes.openBracket: + pos++; + return token = SyntaxKind.OpenBracketToken; + case CharacterCodes.closeBracket: + pos++; + return token = SyntaxKind.CloseBracketToken; + case CharacterCodes.caret: + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.CaretEqualsToken; + } + pos++; + return token = SyntaxKind.CaretToken; + case CharacterCodes.openBrace: + pos++; + return token = SyntaxKind.OpenBraceToken; + case CharacterCodes.bar: + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + if (skipTrivia) { + continue; } - pos++; - return token = SyntaxKind.CaretToken; - case CharacterCodes.openBrace: - pos++; - return token = SyntaxKind.OpenBraceToken; - case CharacterCodes.bar: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.ConflictMarkerTrivia; - } + else { + return token = SyntaxKind.ConflictMarkerTrivia; } + } - if (text.charCodeAt(pos + 1) === CharacterCodes.bar) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.BarBarEqualsToken; - } - return pos += 2, token = SyntaxKind.BarBarToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.BarEqualsToken; - } - pos++; - return token = SyntaxKind.BarToken; - case CharacterCodes.closeBrace: - pos++; - return token = SyntaxKind.CloseBraceToken; - case CharacterCodes.tilde: - pos++; - return token = SyntaxKind.TildeToken; - case CharacterCodes.at: - pos++; - return token = SyntaxKind.AtToken; - case CharacterCodes.backslash: - const extendedCookedChar = peekExtendedUnicodeEscape(); - if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) { - pos += 3; - tokenFlags |= TokenFlags.ExtendedUnicodeEscape; - tokenValue = scanExtendedUnicodeEscape() + scanIdentifierParts(); - return token = getIdentifierToken(); + if (text.charCodeAt(pos + 1) === CharacterCodes.bar) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.BarBarEqualsToken; } + return pos += 2, token = SyntaxKind.BarBarToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.BarEqualsToken; + } + pos++; + return token = SyntaxKind.BarToken; + case CharacterCodes.closeBrace: + pos++; + return token = SyntaxKind.CloseBraceToken; + case CharacterCodes.tilde: + pos++; + return token = SyntaxKind.TildeToken; + case CharacterCodes.at: + pos++; + return token = SyntaxKind.AtToken; + case CharacterCodes.backslash: + const extendedCookedChar = peekExtendedUnicodeEscape(); + if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) { + pos += 3; + tokenFlags |= TokenFlags.ExtendedUnicodeEscape; + tokenValue = scanExtendedUnicodeEscape() + scanIdentifierParts(); + return token = getIdentifierToken(); + } - const cookedChar = peekUnicodeEscape(); - if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) { - pos += 6; - tokenFlags |= TokenFlags.UnicodeEscape; - tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts(); - return token = getIdentifierToken(); - } + const cookedChar = peekUnicodeEscape(); + if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) { + pos += 6; + tokenFlags |= TokenFlags.UnicodeEscape; + tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts(); + return token = getIdentifierToken(); + } - error(Diagnostics.Invalid_character); + error(Diagnostics.Invalid_character); + pos++; + return token = SyntaxKind.Unknown; + case CharacterCodes.hash: + if (pos !== 0 && text[pos + 1] === "!") { + error(Diagnostics.can_only_be_used_at_the_start_of_a_file); pos++; return token = SyntaxKind.Unknown; - case CharacterCodes.hash: - if (pos !== 0 && text[pos + 1] === "!") { - error(Diagnostics.can_only_be_used_at_the_start_of_a_file); - pos++; - return token = SyntaxKind.Unknown; - } + } - if (isIdentifierStart(codePointAt(text, pos + 1), languageVersion)) { - pos++; - scanIdentifier(codePointAt(text, pos), languageVersion); - } - else { - tokenValue = String.fromCharCode(codePointAt(text, pos)); - error(Diagnostics.Invalid_character, pos++, charSize(ch)); - } - return token = SyntaxKind.PrivateIdentifier; - default: - const identifierKind = scanIdentifier(ch, languageVersion); - if (identifierKind) { - return token = identifierKind; - } - else if (isWhiteSpaceSingleLine(ch)) { - pos += charSize(ch); - continue; - } - else if (isLineBreak(ch)) { - tokenFlags |= TokenFlags.PrecedingLineBreak; - pos += charSize(ch); - continue; - } - const size = charSize(ch); - error(Diagnostics.Invalid_character, pos, size); - pos += size; - return token = SyntaxKind.Unknown; - } + if (isIdentifierStart(codePointAt(text, pos + 1), languageVersion)) { + pos++; + scanIdentifier(codePointAt(text, pos), languageVersion); + } + else { + tokenValue = String.fromCharCode(codePointAt(text, pos)); + error(Diagnostics.Invalid_character, pos++, charSize(ch)); + } + return token = SyntaxKind.PrivateIdentifier; + default: + const identifierKind = scanIdentifier(ch, languageVersion); + if (identifierKind) { + return token = identifierKind; + } + else if (isWhiteSpaceSingleLine(ch)) { + pos += charSize(ch); + continue; + } + else if (isLineBreak(ch)) { + tokenFlags |= TokenFlags.PrecedingLineBreak; + pos += charSize(ch); + continue; + } + const size = charSize(ch); + error(Diagnostics.Invalid_character, pos, size); + pos += size; + return token = SyntaxKind.Unknown; } } + } - function reScanInvalidIdentifier(): SyntaxKind { - Debug.assert(token === SyntaxKind.Unknown, "'reScanInvalidIdentifier' should only be called when the current token is 'SyntaxKind.Unknown'."); - pos = tokenPos = startPos; - tokenFlags = 0; - const ch = codePointAt(text, pos); - const identifierKind = scanIdentifier(ch, ScriptTarget.ESNext); - if (identifierKind) { - return token = identifierKind; - } - pos += charSize(ch); - return token; // Still `SyntaKind.Unknown` - } + function reScanInvalidIdentifier(): SyntaxKind { + Debug.assert(token === SyntaxKind.Unknown, "'reScanInvalidIdentifier' should only be called when the current token is 'SyntaxKind.Unknown'."); + pos = tokenPos = startPos; + tokenFlags = 0; + const ch = codePointAt(text, pos); + const identifierKind = scanIdentifier(ch, ScriptTarget.ESNext); + if (identifierKind) { + return token = identifierKind; + } + pos += charSize(ch); + return token; // Still `SyntaKind.Unknown` + } - function scanIdentifier(startCharacter: number, languageVersion: ScriptTarget) { - let ch = startCharacter; - if (isIdentifierStart(ch, languageVersion)) { - pos += charSize(ch); - while (pos < end && isIdentifierPart(ch = codePointAt(text, pos), languageVersion)) pos += charSize(ch); - tokenValue = text.substring(tokenPos, pos); - if (ch === CharacterCodes.backslash) { - tokenValue += scanIdentifierParts(); - } - return getIdentifierToken(); + function scanIdentifier(startCharacter: number, languageVersion: ScriptTarget) { + let ch = startCharacter; + if (isIdentifierStart(ch, languageVersion)) { + pos += charSize(ch); + while (pos < end && isIdentifierPart(ch = codePointAt(text, pos), languageVersion)) pos += charSize(ch); + tokenValue = text.substring(tokenPos, pos); + if (ch === CharacterCodes.backslash) { + tokenValue += scanIdentifierParts(); } + return getIdentifierToken(); } + } - function reScanGreaterToken(): SyntaxKind { - if (token === SyntaxKind.GreaterThanToken) { - if (text.charCodeAt(pos) === CharacterCodes.greaterThan) { - if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken; - } - return pos += 2, token = SyntaxKind.GreaterThanGreaterThanGreaterThanToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.GreaterThanGreaterThanEqualsToken; + function reScanGreaterToken(): SyntaxKind { + if (token === SyntaxKind.GreaterThanToken) { + if (text.charCodeAt(pos) === CharacterCodes.greaterThan) { + if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken; } - pos++; - return token = SyntaxKind.GreaterThanGreaterThanToken; + return pos += 2, token = SyntaxKind.GreaterThanGreaterThanGreaterThanToken; } - if (text.charCodeAt(pos) === CharacterCodes.equals) { - pos++; - return token = SyntaxKind.GreaterThanEqualsToken; + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.GreaterThanGreaterThanEqualsToken; } + pos++; + return token = SyntaxKind.GreaterThanGreaterThanToken; + } + if (text.charCodeAt(pos) === CharacterCodes.equals) { + pos++; + return token = SyntaxKind.GreaterThanEqualsToken; } - return token; } + return token; + } - function reScanAsteriskEqualsToken(): SyntaxKind { - Debug.assert(token === SyntaxKind.AsteriskEqualsToken, "'reScanAsteriskEqualsToken' should only be called on a '*='"); - pos = tokenPos + 1; - return token = SyntaxKind.EqualsToken; - } - - function reScanSlashToken(): SyntaxKind { - if (token === SyntaxKind.SlashToken || token === SyntaxKind.SlashEqualsToken) { - let p = tokenPos + 1; - let inEscape = false; - let inCharacterClass = false; - while (true) { - // If we reach the end of a file, or hit a newline, then this is an unterminated - // regex. Report error and return what we have so far. - if (p >= end) { - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_regular_expression_literal); - break; - } + function reScanAsteriskEqualsToken(): SyntaxKind { + Debug.assert(token === SyntaxKind.AsteriskEqualsToken, "'reScanAsteriskEqualsToken' should only be called on a '*='"); + pos = tokenPos + 1; + return token = SyntaxKind.EqualsToken; + } - const ch = text.charCodeAt(p); - if (isLineBreak(ch)) { - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_regular_expression_literal); - break; - } + function reScanSlashToken(): SyntaxKind { + if (token === SyntaxKind.SlashToken || token === SyntaxKind.SlashEqualsToken) { + let p = tokenPos + 1; + let inEscape = false; + let inCharacterClass = false; + while (true) { + // If we reach the end of a file, or hit a newline, then this is an unterminated + // regex. Report error and return what we have so far. + if (p >= end) { + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_regular_expression_literal); + break; + } - if (inEscape) { - // Parsing an escape character; - // reset the flag and just advance to the next char. - inEscape = false; - } - else if (ch === CharacterCodes.slash && !inCharacterClass) { - // A slash within a character class is permissible, - // but in general it signals the end of the regexp literal. - p++; - break; - } - else if (ch === CharacterCodes.openBracket) { - inCharacterClass = true; - } - else if (ch === CharacterCodes.backslash) { - inEscape = true; - } - else if (ch === CharacterCodes.closeBracket) { - inCharacterClass = false; - } - p++; + const ch = text.charCodeAt(p); + if (isLineBreak(ch)) { + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_regular_expression_literal); + break; } - while (p < end && isIdentifierPart(text.charCodeAt(p), languageVersion)) { + if (inEscape) { + // Parsing an escape character; + // reset the flag and just advance to the next char. + inEscape = false; + } + else if (ch === CharacterCodes.slash && !inCharacterClass) { + // A slash within a character class is permissible, + // but in general it signals the end of the regexp literal. p++; + break; } - pos = p; - tokenValue = text.substring(tokenPos, pos); - token = SyntaxKind.RegularExpressionLiteral; - } - return token; - } - - function appendIfCommentDirective( - commentDirectives: CommentDirective[] | undefined, - text: string, - commentDirectiveRegEx: RegExp, - lineStart: number, - ) { - const type = getDirectiveFromComment(trimStringStart(text), commentDirectiveRegEx); - if (type === undefined) { - return commentDirectives; + else if (ch === CharacterCodes.openBracket) { + inCharacterClass = true; + } + else if (ch === CharacterCodes.backslash) { + inEscape = true; + } + else if (ch === CharacterCodes.closeBracket) { + inCharacterClass = false; + } + p++; } - return append( - commentDirectives, - { - range: { pos: lineStart, end: pos }, - type, - }, - ); - } - - function getDirectiveFromComment(text: string, commentDirectiveRegEx: RegExp) { - const match = commentDirectiveRegEx.exec(text); - if (!match) { - return undefined; + while (p < end && isIdentifierPart(text.charCodeAt(p), languageVersion)) { + p++; } + pos = p; + tokenValue = text.substring(tokenPos, pos); + token = SyntaxKind.RegularExpressionLiteral; + } + return token; + } - switch (match[1]) { - case "ts-expect-error": - return CommentDirectiveType.ExpectError; - - case "ts-ignore": - return CommentDirectiveType.Ignore; - } + function appendIfCommentDirective( + commentDirectives: CommentDirective[] | undefined, + text: string, + commentDirectiveRegEx: RegExp, + lineStart: number, + ) { + const type = getDirectiveFromComment(trimStringStart(text), commentDirectiveRegEx); + if (type === undefined) { + return commentDirectives; + } + + return append( + commentDirectives, + { + range: { pos: lineStart, end: pos }, + type, + }, + ); + } + function getDirectiveFromComment(text: string, commentDirectiveRegEx: RegExp) { + const match = commentDirectiveRegEx.exec(text); + if (!match) { return undefined; } - /** - * Unconditionally back up and scan a template expression portion. - */ - function reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind { - Debug.assert(token === SyntaxKind.CloseBraceToken, "'reScanTemplateToken' should only be called on a '}'"); - pos = tokenPos; - return token = scanTemplateAndSetTokenValue(isTaggedTemplate); + switch (match[1]) { + case "ts-expect-error": + return CommentDirectiveType.ExpectError; + + case "ts-ignore": + return CommentDirectiveType.Ignore; } - function reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind { - pos = tokenPos; - return token = scanTemplateAndSetTokenValue(/* isTaggedTemplate */ true); + return undefined; + } + + /** + * Unconditionally back up and scan a template expression portion. + */ + function reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind { + Debug.assert(token === SyntaxKind.CloseBraceToken, "'reScanTemplateToken' should only be called on a '}'"); + pos = tokenPos; + return token = scanTemplateAndSetTokenValue(isTaggedTemplate); + } + + function reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind { + pos = tokenPos; + return token = scanTemplateAndSetTokenValue(/* isTaggedTemplate */ true); + } + + function reScanJsxToken(allowMultilineJsxText = true): JsxTokenSyntaxKind { + pos = tokenPos = startPos; + return token = scanJsxToken(allowMultilineJsxText); + } + + function reScanLessThanToken(): SyntaxKind { + if (token === SyntaxKind.LessThanLessThanToken) { + pos = tokenPos + 1; + return token = SyntaxKind.LessThanToken; } + return token; + } - function reScanJsxToken(allowMultilineJsxText = true): JsxTokenSyntaxKind { - pos = tokenPos = startPos; - return token = scanJsxToken(allowMultilineJsxText); + function reScanHashToken(): SyntaxKind { + if (token === SyntaxKind.PrivateIdentifier) { + pos = tokenPos + 1; + return token = SyntaxKind.HashToken; } + return token; + } - function reScanLessThanToken(): SyntaxKind { - if (token === SyntaxKind.LessThanLessThanToken) { - pos = tokenPos + 1; - return token = SyntaxKind.LessThanToken; - } - return token; + function reScanQuestionToken(): SyntaxKind { + Debug.assert(token === SyntaxKind.QuestionQuestionToken, "'reScanQuestionToken' should only be called on a '??'"); + pos = tokenPos + 1; + return token = SyntaxKind.QuestionToken; + } + + function scanJsxToken(allowMultilineJsxText = true): JsxTokenSyntaxKind { + startPos = tokenPos = pos; + + if (pos >= end) { + return token = SyntaxKind.EndOfFileToken; } - function reScanHashToken(): SyntaxKind { - if (token === SyntaxKind.PrivateIdentifier) { - pos = tokenPos + 1; - return token = SyntaxKind.HashToken; + let char = text.charCodeAt(pos); + if (char === CharacterCodes.lessThan) { + if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + return token = SyntaxKind.LessThanSlashToken; } - return token; + pos++; + return token = SyntaxKind.LessThanToken; } - function reScanQuestionToken(): SyntaxKind { - Debug.assert(token === SyntaxKind.QuestionQuestionToken, "'reScanQuestionToken' should only be called on a '??'"); - pos = tokenPos + 1; - return token = SyntaxKind.QuestionToken; + if (char === CharacterCodes.openBrace) { + pos++; + return token = SyntaxKind.OpenBraceToken; } - function scanJsxToken(allowMultilineJsxText = true): JsxTokenSyntaxKind { - startPos = tokenPos = pos; + // First non-whitespace character on this line. + let firstNonWhitespace = 0; - if (pos >= end) { - return token = SyntaxKind.EndOfFileToken; - } + // These initial values are special because the first line is: + // firstNonWhitespace = 0 to indicate that we want leading whitespace, - let char = text.charCodeAt(pos); + while (pos < end) { + char = text.charCodeAt(pos); + if (char === CharacterCodes.openBrace) { + break; + } if (char === CharacterCodes.lessThan) { - if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - return token = SyntaxKind.LessThanSlashToken; + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + return token = SyntaxKind.ConflictMarkerTrivia; } - pos++; - return token = SyntaxKind.LessThanToken; + break; + } + if (char === CharacterCodes.greaterThan) { + error(Diagnostics.Unexpected_token_Did_you_mean_or_gt, pos, 1); + } + if (char === CharacterCodes.closeBrace) { + error(Diagnostics.Unexpected_token_Did_you_mean_or_rbrace, pos, 1); } - if (char === CharacterCodes.openBrace) { - pos++; - return token = SyntaxKind.OpenBraceToken; + // FirstNonWhitespace is 0, then we only see whitespaces so far. If we see a linebreak, we want to ignore that whitespaces. + // i.e (- : whitespace) + //
---- + //
becomes
+ // + //
----
becomes
----
+ if (isLineBreak(char) && firstNonWhitespace === 0) { + firstNonWhitespace = -1; + } + else if (!allowMultilineJsxText && isLineBreak(char) && firstNonWhitespace > 0) { + // Stop JsxText on each line during formatting. This allows the formatter to + // indent each line correctly. + break; + } + else if (!isWhiteSpaceLike(char)) { + firstNonWhitespace = pos; } - // First non-whitespace character on this line. - let firstNonWhitespace = 0; + pos++; + } + + tokenValue = text.substring(startPos, pos); - // These initial values are special because the first line is: - // firstNonWhitespace = 0 to indicate that we want leading whitespace, + return firstNonWhitespace === -1 ? SyntaxKind.JsxTextAllWhiteSpaces : SyntaxKind.JsxText; + } + // Scans a JSX identifier; these differ from normal identifiers in that + // they allow dashes + function scanJsxIdentifier(): SyntaxKind { + if (tokenIsIdentifierOrKeyword(token)) { + // An identifier or keyword has already been parsed - check for a `-` or a single instance of `:` and then append it and + // everything after it to the token + // Do note that this means that `scanJsxIdentifier` effectively _mutates_ the visible token without advancing to a new token + // Any caller should be expecting this behavior and should only read the pos or token value after calling it. + let namespaceSeparator = false; while (pos < end) { - char = text.charCodeAt(pos); - if (char === CharacterCodes.openBrace) { - break; - } - if (char === CharacterCodes.lessThan) { - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - return token = SyntaxKind.ConflictMarkerTrivia; - } - break; - } - if (char === CharacterCodes.greaterThan) { - error(Diagnostics.Unexpected_token_Did_you_mean_or_gt, pos, 1); - } - if (char === CharacterCodes.closeBrace) { - error(Diagnostics.Unexpected_token_Did_you_mean_or_rbrace, pos, 1); + const ch = text.charCodeAt(pos); + if (ch === CharacterCodes.minus) { + tokenValue += "-"; + pos++; + continue; } - - // FirstNonWhitespace is 0, then we only see whitespaces so far. If we see a linebreak, we want to ignore that whitespaces. - // i.e (- : whitespace) - //
---- - //
becomes
- // - //
----
becomes
----
- if (isLineBreak(char) && firstNonWhitespace === 0) { - firstNonWhitespace = -1; + else if (ch === CharacterCodes.colon && !namespaceSeparator) { + tokenValue += ":"; + pos++; + namespaceSeparator = true; + token = SyntaxKind.Identifier; // swap from keyword kind to identifier kind + continue; } - else if (!allowMultilineJsxText && isLineBreak(char) && firstNonWhitespace > 0) { - // Stop JsxText on each line during formatting. This allows the formatter to - // indent each line correctly. + const oldPos = pos; + tokenValue += scanIdentifierParts(); // reuse `scanIdentifierParts` so unicode escapes are handled + if (pos === oldPos) { break; } - else if (!isWhiteSpaceLike(char)) { - firstNonWhitespace = pos; - } - - pos++; } - - tokenValue = text.substring(startPos, pos); - - return firstNonWhitespace === -1 ? SyntaxKind.JsxTextAllWhiteSpaces : SyntaxKind.JsxText; - } - - // Scans a JSX identifier; these differ from normal identifiers in that - // they allow dashes - function scanJsxIdentifier(): SyntaxKind { - if (tokenIsIdentifierOrKeyword(token)) { - // An identifier or keyword has already been parsed - check for a `-` or a single instance of `:` and then append it and - // everything after it to the token - // Do note that this means that `scanJsxIdentifier` effectively _mutates_ the visible token without advancing to a new token - // Any caller should be expecting this behavior and should only read the pos or token value after calling it. - let namespaceSeparator = false; - while (pos < end) { - const ch = text.charCodeAt(pos); - if (ch === CharacterCodes.minus) { - tokenValue += "-"; - pos++; - continue; - } - else if (ch === CharacterCodes.colon && !namespaceSeparator) { - tokenValue += ":"; - pos++; - namespaceSeparator = true; - token = SyntaxKind.Identifier; // swap from keyword kind to identifier kind - continue; - } - const oldPos = pos; - tokenValue += scanIdentifierParts(); // reuse `scanIdentifierParts` so unicode escapes are handled - if (pos === oldPos) { - break; - } - } - // Do not include a trailing namespace separator in the token, since this is against the spec. - if (tokenValue.slice(-1) === ":") { - tokenValue = tokenValue.slice(0, -1); - pos--; - } - return getIdentifierToken(); + // Do not include a trailing namespace separator in the token, since this is against the spec. + if (tokenValue.slice(-1) === ":") { + tokenValue = tokenValue.slice(0, -1); + pos--; } - return token; + return getIdentifierToken(); } + return token; + } - function scanJsxAttributeValue(): SyntaxKind { - startPos = pos; - - switch (text.charCodeAt(pos)) { - case CharacterCodes.doubleQuote: - case CharacterCodes.singleQuote: - tokenValue = scanString(/*jsxAttributeString*/ true); - return token = SyntaxKind.StringLiteral; - default: - // If this scans anything other than `{`, it's a parse error. - return scan(); - } - } + function scanJsxAttributeValue(): SyntaxKind { + startPos = pos; - function reScanJsxAttributeValue(): SyntaxKind { - pos = tokenPos = startPos; - return scanJsxAttributeValue(); + switch (text.charCodeAt(pos)) { + case CharacterCodes.doubleQuote: + case CharacterCodes.singleQuote: + tokenValue = scanString(/*jsxAttributeString*/ true); + return token = SyntaxKind.StringLiteral; + default: + // If this scans anything other than `{`, it's a parse error. + return scan(); } + } - function scanJsDocToken(): JSDocSyntaxKind { - startPos = tokenPos = pos; - tokenFlags = TokenFlags.None; - if (pos >= end) { - return token = SyntaxKind.EndOfFileToken; - } + function reScanJsxAttributeValue(): SyntaxKind { + pos = tokenPos = startPos; + return scanJsxAttributeValue(); + } - const ch = codePointAt(text, pos); - pos += charSize(ch); - switch (ch) { - case CharacterCodes.tab: - case CharacterCodes.verticalTab: - case CharacterCodes.formFeed: - case CharacterCodes.space: - while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) { - pos++; - } - return token = SyntaxKind.WhitespaceTrivia; - case CharacterCodes.at: - return token = SyntaxKind.AtToken; - case CharacterCodes.carriageReturn: - if (text.charCodeAt(pos) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: - tokenFlags |= TokenFlags.PrecedingLineBreak; - return token = SyntaxKind.NewLineTrivia; - case CharacterCodes.asterisk: - return token = SyntaxKind.AsteriskToken; - case CharacterCodes.openBrace: - return token = SyntaxKind.OpenBraceToken; - case CharacterCodes.closeBrace: - return token = SyntaxKind.CloseBraceToken; - case CharacterCodes.openBracket: - return token = SyntaxKind.OpenBracketToken; - case CharacterCodes.closeBracket: - return token = SyntaxKind.CloseBracketToken; - case CharacterCodes.lessThan: - return token = SyntaxKind.LessThanToken; - case CharacterCodes.greaterThan: - return token = SyntaxKind.GreaterThanToken; - case CharacterCodes.equals: - return token = SyntaxKind.EqualsToken; - case CharacterCodes.comma: - return token = SyntaxKind.CommaToken; - case CharacterCodes.dot: - return token = SyntaxKind.DotToken; - case CharacterCodes.backtick: - return token = SyntaxKind.BacktickToken; - case CharacterCodes.hash: - return token = SyntaxKind.HashToken; - case CharacterCodes.backslash: - pos--; - const extendedCookedChar = peekExtendedUnicodeEscape(); - if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) { - pos += 3; - tokenFlags |= TokenFlags.ExtendedUnicodeEscape; - tokenValue = scanExtendedUnicodeEscape() + scanIdentifierParts(); - return token = getIdentifierToken(); - } + function scanJsDocToken(): JSDocSyntaxKind { + startPos = tokenPos = pos; + tokenFlags = TokenFlags.None; + if (pos >= end) { + return token = SyntaxKind.EndOfFileToken; + } - const cookedChar = peekUnicodeEscape(); - if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) { - pos += 6; - tokenFlags |= TokenFlags.UnicodeEscape; - tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts(); - return token = getIdentifierToken(); - } + const ch = codePointAt(text, pos); + pos += charSize(ch); + switch (ch) { + case CharacterCodes.tab: + case CharacterCodes.verticalTab: + case CharacterCodes.formFeed: + case CharacterCodes.space: + while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) { pos++; - return token = SyntaxKind.Unknown; - } + } + return token = SyntaxKind.WhitespaceTrivia; + case CharacterCodes.at: + return token = SyntaxKind.AtToken; + case CharacterCodes.carriageReturn: + if (text.charCodeAt(pos) === CharacterCodes.lineFeed) { + pos++; + } + // falls through + case CharacterCodes.lineFeed: + tokenFlags |= TokenFlags.PrecedingLineBreak; + return token = SyntaxKind.NewLineTrivia; + case CharacterCodes.asterisk: + return token = SyntaxKind.AsteriskToken; + case CharacterCodes.openBrace: + return token = SyntaxKind.OpenBraceToken; + case CharacterCodes.closeBrace: + return token = SyntaxKind.CloseBraceToken; + case CharacterCodes.openBracket: + return token = SyntaxKind.OpenBracketToken; + case CharacterCodes.closeBracket: + return token = SyntaxKind.CloseBracketToken; + case CharacterCodes.lessThan: + return token = SyntaxKind.LessThanToken; + case CharacterCodes.greaterThan: + return token = SyntaxKind.GreaterThanToken; + case CharacterCodes.equals: + return token = SyntaxKind.EqualsToken; + case CharacterCodes.comma: + return token = SyntaxKind.CommaToken; + case CharacterCodes.dot: + return token = SyntaxKind.DotToken; + case CharacterCodes.backtick: + return token = SyntaxKind.BacktickToken; + case CharacterCodes.hash: + return token = SyntaxKind.HashToken; + case CharacterCodes.backslash: + pos--; + const extendedCookedChar = peekExtendedUnicodeEscape(); + if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) { + pos += 3; + tokenFlags |= TokenFlags.ExtendedUnicodeEscape; + tokenValue = scanExtendedUnicodeEscape() + scanIdentifierParts(); + return token = getIdentifierToken(); + } - if (isIdentifierStart(ch, languageVersion)) { - let char = ch; - while (pos < end && isIdentifierPart(char = codePointAt(text, pos), languageVersion) || text.charCodeAt(pos) === CharacterCodes.minus) pos += charSize(char); - tokenValue = text.substring(tokenPos, pos); - if (char === CharacterCodes.backslash) { - tokenValue += scanIdentifierParts(); + const cookedChar = peekUnicodeEscape(); + if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) { + pos += 6; + tokenFlags |= TokenFlags.UnicodeEscape; + tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts(); + return token = getIdentifierToken(); } - return token = getIdentifierToken(); - } - else { + pos++; return token = SyntaxKind.Unknown; - } } - function speculationHelper(callback: () => T, isLookahead: boolean): T { - const savePos = pos; - const saveStartPos = startPos; - const saveTokenPos = tokenPos; - const saveToken = token; - const saveTokenValue = tokenValue; - const saveTokenFlags = tokenFlags; - const result = callback(); - - // If our callback returned something 'falsy' or we're just looking ahead, - // then unconditionally restore us to where we were. - if (!result || isLookahead) { - pos = savePos; - startPos = saveStartPos; - tokenPos = saveTokenPos; - token = saveToken; - tokenValue = saveTokenValue; - tokenFlags = saveTokenFlags; + if (isIdentifierStart(ch, languageVersion)) { + let char = ch; + while (pos < end && isIdentifierPart(char = codePointAt(text, pos), languageVersion) || text.charCodeAt(pos) === CharacterCodes.minus) pos += charSize(char); + tokenValue = text.substring(tokenPos, pos); + if (char === CharacterCodes.backslash) { + tokenValue += scanIdentifierParts(); } - return result; + return token = getIdentifierToken(); } + else { + return token = SyntaxKind.Unknown; + } + } - function scanRange(start: number, length: number, callback: () => T): T { - const saveEnd = end; - const savePos = pos; - const saveStartPos = startPos; - const saveTokenPos = tokenPos; - const saveToken = token; - const saveTokenValue = tokenValue; - const saveTokenFlags = tokenFlags; - const saveErrorExpectations = commentDirectives; - - setText(text, start, length); - const result = callback(); - - end = saveEnd; + function speculationHelper(callback: () => T, isLookahead: boolean): T { + const savePos = pos; + const saveStartPos = startPos; + const saveTokenPos = tokenPos; + const saveToken = token; + const saveTokenValue = tokenValue; + const saveTokenFlags = tokenFlags; + const result = callback(); + + // If our callback returned something 'falsy' or we're just looking ahead, + // then unconditionally restore us to where we were. + if (!result || isLookahead) { pos = savePos; startPos = saveStartPos; tokenPos = saveTokenPos; token = saveToken; tokenValue = saveTokenValue; tokenFlags = saveTokenFlags; - commentDirectives = saveErrorExpectations; - - return result; } + return result; + } - function lookAhead(callback: () => T): T { - return speculationHelper(callback, /*isLookahead*/ true); - } + function scanRange(start: number, length: number, callback: () => T): T { + const saveEnd = end; + const savePos = pos; + const saveStartPos = startPos; + const saveTokenPos = tokenPos; + const saveToken = token; + const saveTokenValue = tokenValue; + const saveTokenFlags = tokenFlags; + const saveErrorExpectations = commentDirectives; - function tryScan(callback: () => T): T { - return speculationHelper(callback, /*isLookahead*/ false); - } + setText(text, start, length); + const result = callback(); - function getText(): string { - return text; - } + end = saveEnd; + pos = savePos; + startPos = saveStartPos; + tokenPos = saveTokenPos; + token = saveToken; + tokenValue = saveTokenValue; + tokenFlags = saveTokenFlags; + commentDirectives = saveErrorExpectations; - function clearCommentDirectives() { - commentDirectives = undefined; - } + return result; + } - function setText(newText: string | undefined, start: number | undefined, length: number | undefined) { - text = newText || ""; - end = length === undefined ? text.length : start! + length; - setTextPos(start || 0); - } + function lookAhead(callback: () => T): T { + return speculationHelper(callback, /*isLookahead*/ true); + } - function setOnError(errorCallback: ErrorCallback | undefined) { - onError = errorCallback; - } + function tryScan(callback: () => T): T { + return speculationHelper(callback, /*isLookahead*/ false); + } - function setScriptTarget(scriptTarget: ScriptTarget) { - languageVersion = scriptTarget; - } + function getText(): string { + return text; + } - function setLanguageVariant(variant: LanguageVariant) { - languageVariant = variant; - } + function clearCommentDirectives() { + commentDirectives = undefined; + } - function setTextPos(textPos: number) { - Debug.assert(textPos >= 0); - pos = textPos; - startPos = textPos; - tokenPos = textPos; - token = SyntaxKind.Unknown; - tokenValue = undefined!; - tokenFlags = TokenFlags.None; - } + function setText(newText: string | undefined, start: number | undefined, length: number | undefined) { + text = newText || ""; + end = length === undefined ? text.length : start! + length; + setTextPos(start || 0); + } - function setInJSDocType(inType: boolean) { - inJSDocType += inType ? 1 : -1; - } + function setOnError(errorCallback: ErrorCallback | undefined) { + onError = errorCallback; } - /* @internal */ - const codePointAt: (s: string, i: number) => number = (String.prototype as any).codePointAt ? (s, i) => (s as any).codePointAt(i) : function codePointAt(str, i): number { - // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt - const size = str.length; - // Account for out-of-bounds indices: - if (i < 0 || i >= size) { - return undefined!; // String.codePointAt returns `undefined` for OOB indexes - } - // Get the first code unit - const first = str.charCodeAt(i); - // check if it’s the start of a surrogate pair - if (first >= 0xD800 && first <= 0xDBFF && size > i + 1) { // high surrogate and there is a next code unit - const second = str.charCodeAt(i + 1); - if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate - // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - } - } - return first; - }; + function setScriptTarget(scriptTarget: ScriptTarget) { + languageVersion = scriptTarget; + } - /* @internal */ - function charSize(ch: number) { - if (ch >= 0x10000) { - return 2; - } - return 1; + function setLanguageVariant(variant: LanguageVariant) { + languageVariant = variant; } - // Derived from the 10.1.1 UTF16Encoding of the ES6 Spec. - function utf16EncodeAsStringFallback(codePoint: number) { - Debug.assert(0x0 <= codePoint && codePoint <= 0x10FFFF); + function setTextPos(textPos: number) { + Debug.assert(textPos >= 0); + pos = textPos; + startPos = textPos; + tokenPos = textPos; + token = SyntaxKind.Unknown; + tokenValue = undefined!; + tokenFlags = TokenFlags.None; + } - if (codePoint <= 65535) { - return String.fromCharCode(codePoint); - } + function setInJSDocType(inType: boolean) { + inJSDocType += inType ? 1 : -1; + } +} - const codeUnit1 = Math.floor((codePoint - 65536) / 1024) + 0xD800; - const codeUnit2 = ((codePoint - 65536) % 1024) + 0xDC00; +/* @internal */ +const codePointAt: (s: string, i: number) => number = (String.prototype as any).codePointAt ? (s, i) => (s as any).codePointAt(i) : function codePointAt(str, i): number { + // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt + const size = str.length; + // Account for out-of-bounds indices: + if (i < 0 || i >= size) { + return undefined!; // String.codePointAt returns `undefined` for OOB indexes + } + // Get the first code unit + const first = str.charCodeAt(i); + // check if it’s the start of a surrogate pair + if (first >= 0xD800 && first <= 0xDBFF && size > i + 1) { // high surrogate and there is a next code unit + const second = str.charCodeAt(i + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; +}; - return String.fromCharCode(codeUnit1, codeUnit2); +/* @internal */ +function charSize(ch: number) { + if (ch >= 0x10000) { + return 2; } + return 1; +} - const utf16EncodeAsStringWorker: (codePoint: number) => string = (String as any).fromCodePoint ? codePoint => (String as any).fromCodePoint(codePoint) : utf16EncodeAsStringFallback; +// Derived from the 10.1.1 UTF16Encoding of the ES6 Spec. +function utf16EncodeAsStringFallback(codePoint: number) { + Debug.assert(0x0 <= codePoint && codePoint <= 0x10FFFF); - /* @internal */ - export function utf16EncodeAsString(codePoint: number) { - return utf16EncodeAsStringWorker(codePoint); + if (codePoint <= 65535) { + return String.fromCharCode(codePoint); } + + const codeUnit1 = Math.floor((codePoint - 65536) / 1024) + 0xD800; + const codeUnit2 = ((codePoint - 65536) % 1024) + 0xDC00; + + return String.fromCharCode(codeUnit1, codeUnit2); +} + +const utf16EncodeAsStringWorker: (codePoint: number) => string = (String as any).fromCodePoint ? codePoint => (String as any).fromCodePoint(codePoint) : utf16EncodeAsStringFallback; + +/* @internal */ +export function utf16EncodeAsString(codePoint: number) { + return utf16EncodeAsStringWorker(codePoint); +} } diff --git a/src/compiler/semver.ts b/src/compiler/semver.ts index 76d4cf2d65e7a..65adb5f69c4e8 100644 --- a/src/compiler/semver.ts +++ b/src/compiler/semver.ts @@ -1,392 +1,392 @@ /* @internal */ namespace ts { - // https://semver.org/#spec-item-2 - // > A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative - // > integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor - // > version, and Z is the patch version. Each element MUST increase numerically. - // - // NOTE: We differ here in that we allow X and X.Y, with missing parts having the default - // value of `0`. - const versionRegExp = /^(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; - - // https://semver.org/#spec-item-9 - // > A pre-release version MAY be denoted by appending a hyphen and a series of dot separated - // > identifiers immediately following the patch version. Identifiers MUST comprise only ASCII - // > alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers - // > MUST NOT include leading zeroes. - const prereleaseRegExp = /^(?:0|[1-9]\d*|[a-z-][a-z0-9-]*)(?:\.(?:0|[1-9]\d*|[a-z-][a-z0-9-]*))*$/i; - - // https://semver.org/#spec-item-10 - // > Build metadata MAY be denoted by appending a plus sign and a series of dot separated - // > identifiers immediately following the patch or pre-release version. Identifiers MUST - // > comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. - const buildRegExp = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/i; - - // https://semver.org/#spec-item-9 - // > Numeric identifiers MUST NOT include leading zeroes. - const numericIdentifierRegExp = /^(0|[1-9]\d*)$/; - - /** - * Describes a precise semantic version number, https://semver.org - */ - export class Version { - static readonly zero = new Version(0, 0, 0); - - readonly major: number; - readonly minor: number; - readonly patch: number; - readonly prerelease: readonly string[]; - readonly build: readonly string[]; - - constructor(text: string); - constructor(major: number, minor?: number, patch?: number, prerelease?: string, build?: string); - constructor(major: number | string, minor = 0, patch = 0, prerelease = "", build = "") { - if (typeof major === "string") { - const result = Debug.checkDefined(tryParseComponents(major), "Invalid version"); - ({ major, minor, patch, prerelease, build } = result); - } - - Debug.assert(major >= 0, "Invalid argument: major"); - Debug.assert(minor >= 0, "Invalid argument: minor"); - Debug.assert(patch >= 0, "Invalid argument: patch"); - Debug.assert(!prerelease || prereleaseRegExp.test(prerelease), "Invalid argument: prerelease"); - Debug.assert(!build || buildRegExp.test(build), "Invalid argument: build"); - this.major = major; - this.minor = minor; - this.patch = patch; - this.prerelease = prerelease ? prerelease.split(".") : emptyArray; - this.build = build ? build.split(".") : emptyArray; - } - - static tryParse(text: string) { - const result = tryParseComponents(text); - if (!result) return undefined; - - const { major, minor, patch, prerelease, build } = result; - return new Version(major, minor, patch, prerelease, build); +// https://semver.org/#spec-item-2 +// > A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative +// > integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor +// > version, and Z is the patch version. Each element MUST increase numerically. +// +// NOTE: We differ here in that we allow X and X.Y, with missing parts having the default +// value of `0`. +const versionRegExp = /^(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; + +// https://semver.org/#spec-item-9 +// > A pre-release version MAY be denoted by appending a hyphen and a series of dot separated +// > identifiers immediately following the patch version. Identifiers MUST comprise only ASCII +// > alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers +// > MUST NOT include leading zeroes. +const prereleaseRegExp = /^(?:0|[1-9]\d*|[a-z-][a-z0-9-]*)(?:\.(?:0|[1-9]\d*|[a-z-][a-z0-9-]*))*$/i; + +// https://semver.org/#spec-item-10 +// > Build metadata MAY be denoted by appending a plus sign and a series of dot separated +// > identifiers immediately following the patch or pre-release version. Identifiers MUST +// > comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. +const buildRegExp = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/i; + +// https://semver.org/#spec-item-9 +// > Numeric identifiers MUST NOT include leading zeroes. +const numericIdentifierRegExp = /^(0|[1-9]\d*)$/; + +/** + * Describes a precise semantic version number, https://semver.org + */ +export class Version { + static readonly zero = new Version(0, 0, 0); + + readonly major: number; + readonly minor: number; + readonly patch: number; + readonly prerelease: readonly string[]; + readonly build: readonly string[]; + + constructor(text: string); + constructor(major: number, minor?: number, patch?: number, prerelease?: string, build?: string); + constructor(major: number | string, minor = 0, patch = 0, prerelease = "", build = "") { + if (typeof major === "string") { + const result = Debug.checkDefined(tryParseComponents(major), "Invalid version"); + ({ major, minor, patch, prerelease, build } = result); } - compareTo(other: Version | undefined) { - // https://semver.org/#spec-item-11 - // > Precedence is determined by the first difference when comparing each of these - // > identifiers from left to right as follows: Major, minor, and patch versions are - // > always compared numerically. - // - // https://semver.org/#spec-item-11 - // > Precedence for two pre-release versions with the same major, minor, and patch version - // > MUST be determined by comparing each dot separated identifier from left to right until - // > a difference is found [...] - // - // https://semver.org/#spec-item-11 - // > Build metadata does not figure into precedence - if (this === other) return Comparison.EqualTo; - if (other === undefined) return Comparison.GreaterThan; - return compareValues(this.major, other.major) - || compareValues(this.minor, other.minor) - || compareValues(this.patch, other.patch) - || comparePrereleaseIdentifiers(this.prerelease, other.prerelease); - } - - increment(field: "major" | "minor" | "patch") { - switch (field) { - case "major": return new Version(this.major + 1, 0, 0); - case "minor": return new Version(this.major, this.minor + 1, 0); - case "patch": return new Version(this.major, this.minor, this.patch + 1); - default: return Debug.assertNever(field); - } - } - - toString() { - let result = `${this.major}.${this.minor}.${this.patch}`; - if (some(this.prerelease)) result += `-${this.prerelease.join(".")}`; - if (some(this.build)) result += `+${this.build.join(".")}`; - return result; - } + Debug.assert(major >= 0, "Invalid argument: major"); + Debug.assert(minor >= 0, "Invalid argument: minor"); + Debug.assert(patch >= 0, "Invalid argument: patch"); + Debug.assert(!prerelease || prereleaseRegExp.test(prerelease), "Invalid argument: prerelease"); + Debug.assert(!build || buildRegExp.test(build), "Invalid argument: build"); + this.major = major; + this.minor = minor; + this.patch = patch; + this.prerelease = prerelease ? prerelease.split(".") : emptyArray; + this.build = build ? build.split(".") : emptyArray; } - function tryParseComponents(text: string) { - const match = versionRegExp.exec(text); - if (!match) return undefined; - - const [, major, minor = "0", patch = "0", prerelease = "", build = ""] = match; - if (prerelease && !prereleaseRegExp.test(prerelease)) return undefined; - if (build && !buildRegExp.test(build)) return undefined; - return { - major: parseInt(major, 10), - minor: parseInt(minor, 10), - patch: parseInt(patch, 10), - prerelease, - build - }; + static tryParse(text: string) { + const result = tryParseComponents(text); + if (!result) return undefined; + + const { major, minor, patch, prerelease, build } = result; + return new Version(major, minor, patch, prerelease, build); } - function comparePrereleaseIdentifiers(left: readonly string[], right: readonly string[]) { + compareTo(other: Version | undefined) { // https://semver.org/#spec-item-11 - // > When major, minor, and patch are equal, a pre-release version has lower precedence - // > than a normal version. - if (left === right) return Comparison.EqualTo; - if (left.length === 0) return right.length === 0 ? Comparison.EqualTo : Comparison.GreaterThan; - if (right.length === 0) return Comparison.LessThan; - + // > Precedence is determined by the first difference when comparing each of these + // > identifiers from left to right as follows: Major, minor, and patch versions are + // > always compared numerically. + // // https://semver.org/#spec-item-11 // > Precedence for two pre-release versions with the same major, minor, and patch version // > MUST be determined by comparing each dot separated identifier from left to right until // > a difference is found [...] - const length = Math.min(left.length, right.length); - for (let i = 0; i < length; i++) { - const leftIdentifier = left[i]; - const rightIdentifier = right[i]; - if (leftIdentifier === rightIdentifier) continue; - - const leftIsNumeric = numericIdentifierRegExp.test(leftIdentifier); - const rightIsNumeric = numericIdentifierRegExp.test(rightIdentifier); - if (leftIsNumeric || rightIsNumeric) { - // https://semver.org/#spec-item-11 - // > Numeric identifiers always have lower precedence than non-numeric identifiers. - if (leftIsNumeric !== rightIsNumeric) return leftIsNumeric ? Comparison.LessThan : Comparison.GreaterThan; - - // https://semver.org/#spec-item-11 - // > identifiers consisting of only digits are compared numerically - const result = compareValues(+leftIdentifier, +rightIdentifier); - if (result) return result; - } - else { - // https://semver.org/#spec-item-11 - // > identifiers with letters or hyphens are compared lexically in ASCII sort order. - const result = compareStringsCaseSensitive(leftIdentifier, rightIdentifier); - if (result) return result; - } - } - + // // https://semver.org/#spec-item-11 - // > A larger set of pre-release fields has a higher precedence than a smaller set, if all - // > of the preceding identifiers are equal. - return compareValues(left.length, right.length); + // > Build metadata does not figure into precedence + if (this === other) return Comparison.EqualTo; + if (other === undefined) return Comparison.GreaterThan; + return compareValues(this.major, other.major) + || compareValues(this.minor, other.minor) + || compareValues(this.patch, other.patch) + || comparePrereleaseIdentifiers(this.prerelease, other.prerelease); } - /** - * Describes a semantic version range, per https://github.com/npm/node-semver#ranges - */ - export class VersionRange { - private _alternatives: readonly (readonly Comparator[])[]; - - constructor(spec: string) { - this._alternatives = spec ? Debug.checkDefined(parseRange(spec), "Invalid range spec.") : emptyArray; + increment(field: "major" | "minor" | "patch") { + switch (field) { + case "major": return new Version(this.major + 1, 0, 0); + case "minor": return new Version(this.major, this.minor + 1, 0); + case "patch": return new Version(this.major, this.minor, this.patch + 1); + default: return Debug.assertNever(field); } + } - static tryParse(text: string) { - const sets = parseRange(text); - if (sets) { - const range = new VersionRange(""); - range._alternatives = sets; - return range; - } - return undefined; - } + toString() { + let result = `${this.major}.${this.minor}.${this.patch}`; + if (some(this.prerelease)) result += `-${this.prerelease.join(".")}`; + if (some(this.build)) result += `+${this.build.join(".")}`; + return result; + } +} - test(version: Version | string) { - if (typeof version === "string") version = new Version(version); - return testDisjunction(version, this._alternatives); - } +function tryParseComponents(text: string) { + const match = versionRegExp.exec(text); + if (!match) return undefined; + + const [, major, minor = "0", patch = "0", prerelease = "", build = ""] = match; + if (prerelease && !prereleaseRegExp.test(prerelease)) return undefined; + if (build && !buildRegExp.test(build)) return undefined; + return { + major: parseInt(major, 10), + minor: parseInt(minor, 10), + patch: parseInt(patch, 10), + prerelease, + build + }; +} - toString() { - return formatDisjunction(this._alternatives); +function comparePrereleaseIdentifiers(left: readonly string[], right: readonly string[]) { + // https://semver.org/#spec-item-11 + // > When major, minor, and patch are equal, a pre-release version has lower precedence + // > than a normal version. + if (left === right) return Comparison.EqualTo; + if (left.length === 0) return right.length === 0 ? Comparison.EqualTo : Comparison.GreaterThan; + if (right.length === 0) return Comparison.LessThan; + + // https://semver.org/#spec-item-11 + // > Precedence for two pre-release versions with the same major, minor, and patch version + // > MUST be determined by comparing each dot separated identifier from left to right until + // > a difference is found [...] + const length = Math.min(left.length, right.length); + for (let i = 0; i < length; i++) { + const leftIdentifier = left[i]; + const rightIdentifier = right[i]; + if (leftIdentifier === rightIdentifier) continue; + + const leftIsNumeric = numericIdentifierRegExp.test(leftIdentifier); + const rightIsNumeric = numericIdentifierRegExp.test(rightIdentifier); + if (leftIsNumeric || rightIsNumeric) { + // https://semver.org/#spec-item-11 + // > Numeric identifiers always have lower precedence than non-numeric identifiers. + if (leftIsNumeric !== rightIsNumeric) return leftIsNumeric ? Comparison.LessThan : Comparison.GreaterThan; + + // https://semver.org/#spec-item-11 + // > identifiers consisting of only digits are compared numerically + const result = compareValues(+leftIdentifier, +rightIdentifier); + if (result) return result; + } + else { + // https://semver.org/#spec-item-11 + // > identifiers with letters or hyphens are compared lexically in ASCII sort order. + const result = compareStringsCaseSensitive(leftIdentifier, rightIdentifier); + if (result) return result; } } - interface Comparator { - readonly operator: "<" | "<=" | ">" | ">=" | "="; - readonly operand: Version; + // https://semver.org/#spec-item-11 + // > A larger set of pre-release fields has a higher precedence than a smaller set, if all + // > of the preceding identifiers are equal. + return compareValues(left.length, right.length); +} + +/** + * Describes a semantic version range, per https://github.com/npm/node-semver#ranges + */ +export class VersionRange { + private _alternatives: readonly (readonly Comparator[])[]; + + constructor(spec: string) { + this._alternatives = spec ? Debug.checkDefined(parseRange(spec), "Invalid range spec.") : emptyArray; } - // https://github.com/npm/node-semver#range-grammar - // - // range-set ::= range ( logical-or range ) * - // range ::= hyphen | simple ( ' ' simple ) * | '' - // logical-or ::= ( ' ' ) * '||' ( ' ' ) * - const logicalOrRegExp = /\|\|/g; - const whitespaceRegExp = /\s+/g; - - // https://github.com/npm/node-semver#range-grammar - // - // partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? - // xr ::= 'x' | 'X' | '*' | nr - // nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) * - // qualifier ::= ( '-' pre )? ( '+' build )? - // pre ::= parts - // build ::= parts - // parts ::= part ( '.' part ) * - // part ::= nr | [-0-9A-Za-z]+ - const partialRegExp = /^([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; - - // https://github.com/npm/node-semver#range-grammar - // - // hyphen ::= partial ' - ' partial - const hyphenRegExp = /^\s*([a-z0-9-+.*]+)\s+-\s+([a-z0-9-+.*]+)\s*$/i; - - // https://github.com/npm/node-semver#range-grammar - // - // simple ::= primitive | partial | tilde | caret - // primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial - // tilde ::= '~' partial - // caret ::= '^' partial - const rangeRegExp = /^(~|\^|<|<=|>|>=|=)?\s*([a-z0-9-+.*]+)$/i; - - function parseRange(text: string) { - const alternatives: Comparator[][] = []; - for (let range of trimString(text).split(logicalOrRegExp)) { - if (!range) continue; - const comparators: Comparator[] = []; - range = trimString(range); - const match = hyphenRegExp.exec(range); - if (match) { - if (!parseHyphen(match[1], match[2], comparators)) return undefined; - } - else { - for (const simple of range.split(whitespaceRegExp)) { - const match = rangeRegExp.exec(trimString(simple)); - if (!match || !parseComparator(match[1], match[2], comparators)) return undefined; - } - } - alternatives.push(comparators); + static tryParse(text: string) { + const sets = parseRange(text); + if (sets) { + const range = new VersionRange(""); + range._alternatives = sets; + return range; } - return alternatives; + return undefined; } - function parsePartial(text: string) { - const match = partialRegExp.exec(text); - if (!match) return undefined; - - const [, major, minor = "*", patch = "*", prerelease, build] = match; - const version = new Version( - isWildcard(major) ? 0 : parseInt(major, 10), - isWildcard(major) || isWildcard(minor) ? 0 : parseInt(minor, 10), - isWildcard(major) || isWildcard(minor) || isWildcard(patch) ? 0 : parseInt(patch, 10), - prerelease, - build); - - return { version, major, minor, patch }; + test(version: Version | string) { + if (typeof version === "string") version = new Version(version); + return testDisjunction(version, this._alternatives); } - function parseHyphen(left: string, right: string, comparators: Comparator[]) { - const leftResult = parsePartial(left); - if (!leftResult) return false; + toString() { + return formatDisjunction(this._alternatives); + } +} - const rightResult = parsePartial(right); - if (!rightResult) return false; +interface Comparator { + readonly operator: "<" | "<=" | ">" | ">=" | "="; + readonly operand: Version; +} - if (!isWildcard(leftResult.major)) { - comparators.push(createComparator(">=", leftResult.version)); +// https://github.com/npm/node-semver#range-grammar +// +// range-set ::= range ( logical-or range ) * +// range ::= hyphen | simple ( ' ' simple ) * | '' +// logical-or ::= ( ' ' ) * '||' ( ' ' ) * +const logicalOrRegExp = /\|\|/g; +const whitespaceRegExp = /\s+/g; + +// https://github.com/npm/node-semver#range-grammar +// +// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? +// xr ::= 'x' | 'X' | '*' | nr +// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) * +// qualifier ::= ( '-' pre )? ( '+' build )? +// pre ::= parts +// build ::= parts +// parts ::= part ( '.' part ) * +// part ::= nr | [-0-9A-Za-z]+ +const partialRegExp = /^([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; + +// https://github.com/npm/node-semver#range-grammar +// +// hyphen ::= partial ' - ' partial +const hyphenRegExp = /^\s*([a-z0-9-+.*]+)\s+-\s+([a-z0-9-+.*]+)\s*$/i; + +// https://github.com/npm/node-semver#range-grammar +// +// simple ::= primitive | partial | tilde | caret +// primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial +// tilde ::= '~' partial +// caret ::= '^' partial +const rangeRegExp = /^(~|\^|<|<=|>|>=|=)?\s*([a-z0-9-+.*]+)$/i; + +function parseRange(text: string) { + const alternatives: Comparator[][] = []; + for (let range of trimString(text).split(logicalOrRegExp)) { + if (!range) continue; + const comparators: Comparator[] = []; + range = trimString(range); + const match = hyphenRegExp.exec(range); + if (match) { + if (!parseHyphen(match[1], match[2], comparators)) return undefined; } - - if (!isWildcard(rightResult.major)) { - comparators.push( - isWildcard(rightResult.minor) ? createComparator("<", rightResult.version.increment("major")) : - isWildcard(rightResult.patch) ? createComparator("<", rightResult.version.increment("minor")) : - createComparator("<=", rightResult.version)); + else { + for (const simple of range.split(whitespaceRegExp)) { + const match = rangeRegExp.exec(trimString(simple)); + if (!match || !parseComparator(match[1], match[2], comparators)) return undefined; + } } - - return true; + alternatives.push(comparators); } + return alternatives; +} - function parseComparator(operator: string, text: string, comparators: Comparator[]) { - const result = parsePartial(text); - if (!result) return false; +function parsePartial(text: string) { + const match = partialRegExp.exec(text); + if (!match) return undefined; - const { version, major, minor, patch } = result; - if (!isWildcard(major)) { - switch (operator) { - case "~": - comparators.push(createComparator(">=", version)); - comparators.push(createComparator("<", version.increment( - isWildcard(minor) ? "major" : - "minor"))); - break; - case "^": - comparators.push(createComparator(">=", version)); - comparators.push(createComparator("<", version.increment( - version.major > 0 || isWildcard(minor) ? "major" : - version.minor > 0 || isWildcard(patch) ? "minor" : - "patch"))); - break; - case "<": - case ">=": - comparators.push(createComparator(operator, version)); - break; - case "<=": - case ">": - comparators.push( - isWildcard(minor) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("major")) : - isWildcard(patch) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("minor")) : - createComparator(operator, version)); - break; - case "=": - case undefined: - if (isWildcard(minor) || isWildcard(patch)) { - comparators.push(createComparator(">=", version)); - comparators.push(createComparator("<", version.increment(isWildcard(minor) ? "major" : "minor"))); - } - else { - comparators.push(createComparator("=", version)); - } - break; - default: - // unrecognized - return false; - } - } - else if (operator === "<" || operator === ">") { - comparators.push(createComparator("<", Version.zero)); - } + const [, major, minor = "*", patch = "*", prerelease, build] = match; + const version = new Version( + isWildcard(major) ? 0 : parseInt(major, 10), + isWildcard(major) || isWildcard(minor) ? 0 : parseInt(minor, 10), + isWildcard(major) || isWildcard(minor) || isWildcard(patch) ? 0 : parseInt(patch, 10), + prerelease, + build); - return true; - } + return { version, major, minor, patch }; +} - function isWildcard(part: string) { - return part === "*" || part === "x" || part === "X"; - } +function parseHyphen(left: string, right: string, comparators: Comparator[]) { + const leftResult = parsePartial(left); + if (!leftResult) return false; - function createComparator(operator: Comparator["operator"], operand: Version) { - return { operator, operand }; - } + const rightResult = parsePartial(right); + if (!rightResult) return false; - function testDisjunction(version: Version, alternatives: readonly (readonly Comparator[])[]) { - // an empty disjunction is treated as "*" (all versions) - if (alternatives.length === 0) return true; - for (const alternative of alternatives) { - if (testAlternative(version, alternative)) return true; - } - return false; + if (!isWildcard(leftResult.major)) { + comparators.push(createComparator(">=", leftResult.version)); } - function testAlternative(version: Version, comparators: readonly Comparator[]) { - for (const comparator of comparators) { - if (!testComparator(version, comparator.operator, comparator.operand)) return false; - } - return true; + if (!isWildcard(rightResult.major)) { + comparators.push( + isWildcard(rightResult.minor) ? createComparator("<", rightResult.version.increment("major")) : + isWildcard(rightResult.patch) ? createComparator("<", rightResult.version.increment("minor")) : + createComparator("<=", rightResult.version)); } - function testComparator(version: Version, operator: Comparator["operator"], operand: Version) { - const cmp = version.compareTo(operand); + return true; +} + +function parseComparator(operator: string, text: string, comparators: Comparator[]) { + const result = parsePartial(text); + if (!result) return false; + + const { version, major, minor, patch } = result; + if (!isWildcard(major)) { switch (operator) { - case "<": return cmp < 0; - case "<=": return cmp <= 0; - case ">": return cmp > 0; - case ">=": return cmp >= 0; - case "=": return cmp === 0; - default: return Debug.assertNever(operator); + case "~": + comparators.push(createComparator(">=", version)); + comparators.push(createComparator("<", version.increment( + isWildcard(minor) ? "major" : + "minor"))); + break; + case "^": + comparators.push(createComparator(">=", version)); + comparators.push(createComparator("<", version.increment( + version.major > 0 || isWildcard(minor) ? "major" : + version.minor > 0 || isWildcard(patch) ? "minor" : + "patch"))); + break; + case "<": + case ">=": + comparators.push(createComparator(operator, version)); + break; + case "<=": + case ">": + comparators.push( + isWildcard(minor) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("major")) : + isWildcard(patch) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("minor")) : + createComparator(operator, version)); + break; + case "=": + case undefined: + if (isWildcard(minor) || isWildcard(patch)) { + comparators.push(createComparator(">=", version)); + comparators.push(createComparator("<", version.increment(isWildcard(minor) ? "major" : "minor"))); + } + else { + comparators.push(createComparator("=", version)); + } + break; + default: + // unrecognized + return false; } } + else if (operator === "<" || operator === ">") { + comparators.push(createComparator("<", Version.zero)); + } + + return true; +} + +function isWildcard(part: string) { + return part === "*" || part === "x" || part === "X"; +} + +function createComparator(operator: Comparator["operator"], operand: Version) { + return { operator, operand }; +} - function formatDisjunction(alternatives: readonly (readonly Comparator[])[]) { - return map(alternatives, formatAlternative).join(" || ") || "*"; +function testDisjunction(version: Version, alternatives: readonly (readonly Comparator[])[]) { + // an empty disjunction is treated as "*" (all versions) + if (alternatives.length === 0) return true; + for (const alternative of alternatives) { + if (testAlternative(version, alternative)) return true; } + return false; +} - function formatAlternative(comparators: readonly Comparator[]) { - return map(comparators, formatComparator).join(" "); +function testAlternative(version: Version, comparators: readonly Comparator[]) { + for (const comparator of comparators) { + if (!testComparator(version, comparator.operator, comparator.operand)) return false; } + return true; +} - function formatComparator(comparator: Comparator) { - return `${comparator.operator}${comparator.operand}`; +function testComparator(version: Version, operator: Comparator["operator"], operand: Version) { + const cmp = version.compareTo(operand); + switch (operator) { + case "<": return cmp < 0; + case "<=": return cmp <= 0; + case ">": return cmp > 0; + case ">=": return cmp >= 0; + case "=": return cmp === 0; + default: return Debug.assertNever(operator); } } + +function formatDisjunction(alternatives: readonly (readonly Comparator[])[]) { + return map(alternatives, formatAlternative).join(" || ") || "*"; +} + +function formatAlternative(comparators: readonly Comparator[]) { + return map(comparators, formatComparator).join(" "); +} + +function formatComparator(comparator: Comparator) { + return `${comparator.operator}${comparator.operand}`; +} +} diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index e3aebc849b6a3..13ee377e40b1e 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -1,760 +1,760 @@ /* @internal */ namespace ts { - export interface SourceMapGeneratorOptions { - extendedDiagnostics?: boolean; - } - - export function createSourceMapGenerator(host: EmitHost, file: string, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator { - const { enter, exit } = generatorOptions.extendedDiagnostics - ? performance.createTimer("Source Map", "beforeSourcemap", "afterSourcemap") - : performance.nullTimer; - - // Current source map file and its index in the sources list - const rawSources: string[] = []; - const sources: string[] = []; - const sourceToSourceIndexMap = new Map(); - let sourcesContent: (string | null)[] | undefined; - - const names: string[] = []; - let nameToNameIndexMap: ESMap | undefined; - const mappingCharCodes: number[] = []; - let mappings = ""; - - // Last recorded and encoded mappings - let lastGeneratedLine = 0; - let lastGeneratedCharacter = 0; - let lastSourceIndex = 0; - let lastSourceLine = 0; - let lastSourceCharacter = 0; - let lastNameIndex = 0; - let hasLast = false; - - let pendingGeneratedLine = 0; - let pendingGeneratedCharacter = 0; - let pendingSourceIndex = 0; - let pendingSourceLine = 0; - let pendingSourceCharacter = 0; - let pendingNameIndex = 0; - let hasPending = false; - let hasPendingSource = false; - let hasPendingName = false; +export interface SourceMapGeneratorOptions { + extendedDiagnostics?: boolean; +} - return { - getSources: () => rawSources, - addSource, - setSourceContent, - addName, - addMapping, - appendSourceMap, - toJSON, - toString: () => JSON.stringify(toJSON()) - }; +export function createSourceMapGenerator(host: EmitHost, file: string, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator { + const { enter, exit } = generatorOptions.extendedDiagnostics + ? performance.createTimer("Source Map", "beforeSourcemap", "afterSourcemap") + : performance.nullTimer; + + // Current source map file and its index in the sources list + const rawSources: string[] = []; + const sources: string[] = []; + const sourceToSourceIndexMap = new Map(); + let sourcesContent: (string | null)[] | undefined; + + const names: string[] = []; + let nameToNameIndexMap: ESMap | undefined; + const mappingCharCodes: number[] = []; + let mappings = ""; + + // Last recorded and encoded mappings + let lastGeneratedLine = 0; + let lastGeneratedCharacter = 0; + let lastSourceIndex = 0; + let lastSourceLine = 0; + let lastSourceCharacter = 0; + let lastNameIndex = 0; + let hasLast = false; + + let pendingGeneratedLine = 0; + let pendingGeneratedCharacter = 0; + let pendingSourceIndex = 0; + let pendingSourceLine = 0; + let pendingSourceCharacter = 0; + let pendingNameIndex = 0; + let hasPending = false; + let hasPendingSource = false; + let hasPendingName = false; + + return { + getSources: () => rawSources, + addSource, + setSourceContent, + addName, + addMapping, + appendSourceMap, + toJSON, + toString: () => JSON.stringify(toJSON()) + }; - function addSource(fileName: string) { - enter(); - const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath, - fileName, - host.getCurrentDirectory(), - host.getCanonicalFileName, - /*isAbsolutePathAnUrl*/ true); - - let sourceIndex = sourceToSourceIndexMap.get(source); - if (sourceIndex === undefined) { - sourceIndex = sources.length; - sources.push(source); - rawSources.push(fileName); - sourceToSourceIndexMap.set(source, sourceIndex); - } - exit(); - return sourceIndex; + function addSource(fileName: string) { + enter(); + const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath, + fileName, + host.getCurrentDirectory(), + host.getCanonicalFileName, + /*isAbsolutePathAnUrl*/ true); + + let sourceIndex = sourceToSourceIndexMap.get(source); + if (sourceIndex === undefined) { + sourceIndex = sources.length; + sources.push(source); + rawSources.push(fileName); + sourceToSourceIndexMap.set(source, sourceIndex); } + exit(); + return sourceIndex; + } - /* eslint-disable boolean-trivia, no-null/no-null */ - function setSourceContent(sourceIndex: number, content: string | null) { - enter(); - if (content !== null) { - if (!sourcesContent) sourcesContent = []; - while (sourcesContent.length < sourceIndex) { - sourcesContent.push(null); - } - sourcesContent[sourceIndex] = content; + /* eslint-disable boolean-trivia, no-null/no-null */ + function setSourceContent(sourceIndex: number, content: string | null) { + enter(); + if (content !== null) { + if (!sourcesContent) sourcesContent = []; + while (sourcesContent.length < sourceIndex) { + sourcesContent.push(null); } - exit(); + sourcesContent[sourceIndex] = content; } - /* eslint-enable boolean-trivia, no-null/no-null */ - - function addName(name: string) { - enter(); - if (!nameToNameIndexMap) nameToNameIndexMap = new Map(); - let nameIndex = nameToNameIndexMap.get(name); - if (nameIndex === undefined) { - nameIndex = names.length; - names.push(name); - nameToNameIndexMap.set(name, nameIndex); - } - exit(); - return nameIndex; + exit(); + } + /* eslint-enable boolean-trivia, no-null/no-null */ + + function addName(name: string) { + enter(); + if (!nameToNameIndexMap) nameToNameIndexMap = new Map(); + let nameIndex = nameToNameIndexMap.get(name); + if (nameIndex === undefined) { + nameIndex = names.length; + names.push(name); + nameToNameIndexMap.set(name, nameIndex); } + exit(); + return nameIndex; + } - function isNewGeneratedPosition(generatedLine: number, generatedCharacter: number) { - return !hasPending - || pendingGeneratedLine !== generatedLine - || pendingGeneratedCharacter !== generatedCharacter; - } + function isNewGeneratedPosition(generatedLine: number, generatedCharacter: number) { + return !hasPending + || pendingGeneratedLine !== generatedLine + || pendingGeneratedCharacter !== generatedCharacter; + } - function isBacktrackingSourcePosition(sourceIndex: number | undefined, sourceLine: number | undefined, sourceCharacter: number | undefined) { - return sourceIndex !== undefined - && sourceLine !== undefined - && sourceCharacter !== undefined - && pendingSourceIndex === sourceIndex - && (pendingSourceLine > sourceLine - || pendingSourceLine === sourceLine && pendingSourceCharacter > sourceCharacter); - } + function isBacktrackingSourcePosition(sourceIndex: number | undefined, sourceLine: number | undefined, sourceCharacter: number | undefined) { + return sourceIndex !== undefined + && sourceLine !== undefined + && sourceCharacter !== undefined + && pendingSourceIndex === sourceIndex + && (pendingSourceLine > sourceLine + || pendingSourceLine === sourceLine && pendingSourceCharacter > sourceCharacter); + } - function addMapping(generatedLine: number, generatedCharacter: number, sourceIndex?: number, sourceLine?: number, sourceCharacter?: number, nameIndex?: number) { - Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); - Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); - Debug.assert(sourceIndex === undefined || sourceIndex >= 0, "sourceIndex cannot be negative"); - Debug.assert(sourceLine === undefined || sourceLine >= 0, "sourceLine cannot be negative"); - Debug.assert(sourceCharacter === undefined || sourceCharacter >= 0, "sourceCharacter cannot be negative"); - enter(); - // If this location wasn't recorded or the location in source is going backwards, record the mapping - if (isNewGeneratedPosition(generatedLine, generatedCharacter) || - isBacktrackingSourcePosition(sourceIndex, sourceLine, sourceCharacter)) { - commitPendingMapping(); - pendingGeneratedLine = generatedLine; - pendingGeneratedCharacter = generatedCharacter; - hasPendingSource = false; - hasPendingName = false; - hasPending = true; - } + function addMapping(generatedLine: number, generatedCharacter: number, sourceIndex?: number, sourceLine?: number, sourceCharacter?: number, nameIndex?: number) { + Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); + Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); + Debug.assert(sourceIndex === undefined || sourceIndex >= 0, "sourceIndex cannot be negative"); + Debug.assert(sourceLine === undefined || sourceLine >= 0, "sourceLine cannot be negative"); + Debug.assert(sourceCharacter === undefined || sourceCharacter >= 0, "sourceCharacter cannot be negative"); + enter(); + // If this location wasn't recorded or the location in source is going backwards, record the mapping + if (isNewGeneratedPosition(generatedLine, generatedCharacter) || + isBacktrackingSourcePosition(sourceIndex, sourceLine, sourceCharacter)) { + commitPendingMapping(); + pendingGeneratedLine = generatedLine; + pendingGeneratedCharacter = generatedCharacter; + hasPendingSource = false; + hasPendingName = false; + hasPending = true; + } - if (sourceIndex !== undefined && sourceLine !== undefined && sourceCharacter !== undefined) { - pendingSourceIndex = sourceIndex; - pendingSourceLine = sourceLine; - pendingSourceCharacter = sourceCharacter; - hasPendingSource = true; - if (nameIndex !== undefined) { - pendingNameIndex = nameIndex; - hasPendingName = true; - } + if (sourceIndex !== undefined && sourceLine !== undefined && sourceCharacter !== undefined) { + pendingSourceIndex = sourceIndex; + pendingSourceLine = sourceLine; + pendingSourceCharacter = sourceCharacter; + hasPendingSource = true; + if (nameIndex !== undefined) { + pendingNameIndex = nameIndex; + hasPendingName = true; } - exit(); } + exit(); + } - function appendSourceMap(generatedLine: number, generatedCharacter: number, map: RawSourceMap, sourceMapPath: string, start?: LineAndCharacter, end?: LineAndCharacter) { - Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); - Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); - enter(); - // First, decode the old component sourcemap - const sourceIndexToNewSourceIndexMap: number[] = []; - let nameIndexToNewNameIndexMap: number[] | undefined; - const mappingIterator = decodeMappings(map.mappings); - for (let iterResult = mappingIterator.next(); !iterResult.done; iterResult = mappingIterator.next()) { - const raw = iterResult.value; - if (end && ( - raw.generatedLine > end.line || - (raw.generatedLine === end.line && raw.generatedCharacter > end.character))) { - break; - } + function appendSourceMap(generatedLine: number, generatedCharacter: number, map: RawSourceMap, sourceMapPath: string, start?: LineAndCharacter, end?: LineAndCharacter) { + Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); + Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); + enter(); + // First, decode the old component sourcemap + const sourceIndexToNewSourceIndexMap: number[] = []; + let nameIndexToNewNameIndexMap: number[] | undefined; + const mappingIterator = decodeMappings(map.mappings); + for (let iterResult = mappingIterator.next(); !iterResult.done; iterResult = mappingIterator.next()) { + const raw = iterResult.value; + if (end && ( + raw.generatedLine > end.line || + (raw.generatedLine === end.line && raw.generatedCharacter > end.character))) { + break; + } - if (start && ( - raw.generatedLine < start.line || - (start.line === raw.generatedLine && raw.generatedCharacter < start.character))) { - continue; - } - // Then reencode all the updated mappings into the overall map - let newSourceIndex: number | undefined; - let newSourceLine: number | undefined; - let newSourceCharacter: number | undefined; - let newNameIndex: number | undefined; - if (raw.sourceIndex !== undefined) { - newSourceIndex = sourceIndexToNewSourceIndexMap[raw.sourceIndex]; - if (newSourceIndex === undefined) { - // Apply offsets to each position and fixup source entries - const rawPath = map.sources[raw.sourceIndex]; - const relativePath = map.sourceRoot ? combinePaths(map.sourceRoot, rawPath) : rawPath; - const combinedPath = combinePaths(getDirectoryPath(sourceMapPath), relativePath); - sourceIndexToNewSourceIndexMap[raw.sourceIndex] = newSourceIndex = addSource(combinedPath); - if (map.sourcesContent && typeof map.sourcesContent[raw.sourceIndex] === "string") { - setSourceContent(newSourceIndex, map.sourcesContent[raw.sourceIndex]); - } + if (start && ( + raw.generatedLine < start.line || + (start.line === raw.generatedLine && raw.generatedCharacter < start.character))) { + continue; + } + // Then reencode all the updated mappings into the overall map + let newSourceIndex: number | undefined; + let newSourceLine: number | undefined; + let newSourceCharacter: number | undefined; + let newNameIndex: number | undefined; + if (raw.sourceIndex !== undefined) { + newSourceIndex = sourceIndexToNewSourceIndexMap[raw.sourceIndex]; + if (newSourceIndex === undefined) { + // Apply offsets to each position and fixup source entries + const rawPath = map.sources[raw.sourceIndex]; + const relativePath = map.sourceRoot ? combinePaths(map.sourceRoot, rawPath) : rawPath; + const combinedPath = combinePaths(getDirectoryPath(sourceMapPath), relativePath); + sourceIndexToNewSourceIndexMap[raw.sourceIndex] = newSourceIndex = addSource(combinedPath); + if (map.sourcesContent && typeof map.sourcesContent[raw.sourceIndex] === "string") { + setSourceContent(newSourceIndex, map.sourcesContent[raw.sourceIndex]); } + } - newSourceLine = raw.sourceLine; - newSourceCharacter = raw.sourceCharacter; - if (map.names && raw.nameIndex !== undefined) { - if (!nameIndexToNewNameIndexMap) nameIndexToNewNameIndexMap = []; - newNameIndex = nameIndexToNewNameIndexMap[raw.nameIndex]; - if (newNameIndex === undefined) { - nameIndexToNewNameIndexMap[raw.nameIndex] = newNameIndex = addName(map.names[raw.nameIndex]); - } + newSourceLine = raw.sourceLine; + newSourceCharacter = raw.sourceCharacter; + if (map.names && raw.nameIndex !== undefined) { + if (!nameIndexToNewNameIndexMap) nameIndexToNewNameIndexMap = []; + newNameIndex = nameIndexToNewNameIndexMap[raw.nameIndex]; + if (newNameIndex === undefined) { + nameIndexToNewNameIndexMap[raw.nameIndex] = newNameIndex = addName(map.names[raw.nameIndex]); } } - - const rawGeneratedLine = raw.generatedLine - (start ? start.line : 0); - const newGeneratedLine = rawGeneratedLine + generatedLine; - const rawGeneratedCharacter = start && start.line === raw.generatedLine ? raw.generatedCharacter - start.character : raw.generatedCharacter; - const newGeneratedCharacter = rawGeneratedLine === 0 ? rawGeneratedCharacter + generatedCharacter : rawGeneratedCharacter; - addMapping(newGeneratedLine, newGeneratedCharacter, newSourceIndex, newSourceLine, newSourceCharacter, newNameIndex); } - exit(); - } - function shouldCommitMapping() { - return !hasLast - || lastGeneratedLine !== pendingGeneratedLine - || lastGeneratedCharacter !== pendingGeneratedCharacter - || lastSourceIndex !== pendingSourceIndex - || lastSourceLine !== pendingSourceLine - || lastSourceCharacter !== pendingSourceCharacter - || lastNameIndex !== pendingNameIndex; + const rawGeneratedLine = raw.generatedLine - (start ? start.line : 0); + const newGeneratedLine = rawGeneratedLine + generatedLine; + const rawGeneratedCharacter = start && start.line === raw.generatedLine ? raw.generatedCharacter - start.character : raw.generatedCharacter; + const newGeneratedCharacter = rawGeneratedLine === 0 ? rawGeneratedCharacter + generatedCharacter : rawGeneratedCharacter; + addMapping(newGeneratedLine, newGeneratedCharacter, newSourceIndex, newSourceLine, newSourceCharacter, newNameIndex); } + exit(); + } - function appendMappingCharCode(charCode: number) { - mappingCharCodes.push(charCode); - // String.fromCharCode accepts its arguments on the stack, so we have to chunk the input, - // otherwise we can get stack overflows for large source maps - if (mappingCharCodes.length >= 1024) { - flushMappingBuffer(); - } + function shouldCommitMapping() { + return !hasLast + || lastGeneratedLine !== pendingGeneratedLine + || lastGeneratedCharacter !== pendingGeneratedCharacter + || lastSourceIndex !== pendingSourceIndex + || lastSourceLine !== pendingSourceLine + || lastSourceCharacter !== pendingSourceCharacter + || lastNameIndex !== pendingNameIndex; + } + + function appendMappingCharCode(charCode: number) { + mappingCharCodes.push(charCode); + // String.fromCharCode accepts its arguments on the stack, so we have to chunk the input, + // otherwise we can get stack overflows for large source maps + if (mappingCharCodes.length >= 1024) { + flushMappingBuffer(); } + } - function commitPendingMapping() { - if (!hasPending || !shouldCommitMapping()) { - return; - } + function commitPendingMapping() { + if (!hasPending || !shouldCommitMapping()) { + return; + } - enter(); + enter(); - // Line/Comma delimiters - if (lastGeneratedLine < pendingGeneratedLine) { - // Emit line delimiters - do { - appendMappingCharCode(CharacterCodes.semicolon); - lastGeneratedLine++; - } - while (lastGeneratedLine < pendingGeneratedLine); - // Only need to set this once - lastGeneratedCharacter = 0; + // Line/Comma delimiters + if (lastGeneratedLine < pendingGeneratedLine) { + // Emit line delimiters + do { + appendMappingCharCode(CharacterCodes.semicolon); + lastGeneratedLine++; } - else { - Debug.assertEqual(lastGeneratedLine, pendingGeneratedLine, "generatedLine cannot backtrack"); - // Emit comma to separate the entry - if (hasLast) { - appendMappingCharCode(CharacterCodes.comma); - } + while (lastGeneratedLine < pendingGeneratedLine); + // Only need to set this once + lastGeneratedCharacter = 0; + } + else { + Debug.assertEqual(lastGeneratedLine, pendingGeneratedLine, "generatedLine cannot backtrack"); + // Emit comma to separate the entry + if (hasLast) { + appendMappingCharCode(CharacterCodes.comma); } + } - // 1. Relative generated character - appendBase64VLQ(pendingGeneratedCharacter - lastGeneratedCharacter); - lastGeneratedCharacter = pendingGeneratedCharacter; + // 1. Relative generated character + appendBase64VLQ(pendingGeneratedCharacter - lastGeneratedCharacter); + lastGeneratedCharacter = pendingGeneratedCharacter; - if (hasPendingSource) { - // 2. Relative sourceIndex - appendBase64VLQ(pendingSourceIndex - lastSourceIndex); - lastSourceIndex = pendingSourceIndex; + if (hasPendingSource) { + // 2. Relative sourceIndex + appendBase64VLQ(pendingSourceIndex - lastSourceIndex); + lastSourceIndex = pendingSourceIndex; - // 3. Relative source line - appendBase64VLQ(pendingSourceLine - lastSourceLine); - lastSourceLine = pendingSourceLine; + // 3. Relative source line + appendBase64VLQ(pendingSourceLine - lastSourceLine); + lastSourceLine = pendingSourceLine; - // 4. Relative source character - appendBase64VLQ(pendingSourceCharacter - lastSourceCharacter); - lastSourceCharacter = pendingSourceCharacter; + // 4. Relative source character + appendBase64VLQ(pendingSourceCharacter - lastSourceCharacter); + lastSourceCharacter = pendingSourceCharacter; - if (hasPendingName) { - // 5. Relative nameIndex - appendBase64VLQ(pendingNameIndex - lastNameIndex); - lastNameIndex = pendingNameIndex; - } + if (hasPendingName) { + // 5. Relative nameIndex + appendBase64VLQ(pendingNameIndex - lastNameIndex); + lastNameIndex = pendingNameIndex; } - - hasLast = true; - exit(); } - function flushMappingBuffer(): void { - if (mappingCharCodes.length > 0) { - mappings += String.fromCharCode.apply(undefined, mappingCharCodes); - mappingCharCodes.length = 0; - } - } + hasLast = true; + exit(); + } - function toJSON(): RawSourceMap { - commitPendingMapping(); - flushMappingBuffer(); - return { - version: 3, - file, - sourceRoot, - sources, - names, - mappings, - sourcesContent, - }; + function flushMappingBuffer(): void { + if (mappingCharCodes.length > 0) { + mappings += String.fromCharCode.apply(undefined, mappingCharCodes); + mappingCharCodes.length = 0; } + } - function appendBase64VLQ(inValue: number): void { - // Add a new least significant bit that has the sign of the value. - // if negative number the least significant bit that gets added to the number has value 1 - // else least significant bit value that gets added is 0 - // eg. -1 changes to binary : 01 [1] => 3 - // +1 changes to binary : 01 [0] => 2 - if (inValue < 0) { - inValue = ((-inValue) << 1) + 1; - } - else { - inValue = inValue << 1; - } + function toJSON(): RawSourceMap { + commitPendingMapping(); + flushMappingBuffer(); + return { + version: 3, + file, + sourceRoot, + sources, + names, + mappings, + sourcesContent, + }; + } - // Encode 5 bits at a time starting from least significant bits - do { - let currentDigit = inValue & 31; // 11111 - inValue = inValue >> 5; - if (inValue > 0) { - // There are still more digits to decode, set the msb (6th bit) - currentDigit = currentDigit | 32; - } - appendMappingCharCode(base64FormatEncode(currentDigit)); - } while (inValue > 0); + function appendBase64VLQ(inValue: number): void { + // Add a new least significant bit that has the sign of the value. + // if negative number the least significant bit that gets added to the number has value 1 + // else least significant bit value that gets added is 0 + // eg. -1 changes to binary : 01 [1] => 3 + // +1 changes to binary : 01 [0] => 2 + if (inValue < 0) { + inValue = ((-inValue) << 1) + 1; + } + else { + inValue = inValue << 1; } + + // Encode 5 bits at a time starting from least significant bits + do { + let currentDigit = inValue & 31; // 11111 + inValue = inValue >> 5; + if (inValue > 0) { + // There are still more digits to decode, set the msb (6th bit) + currentDigit = currentDigit | 32; + } + appendMappingCharCode(base64FormatEncode(currentDigit)); + } while (inValue > 0); } +} - // Sometimes tools can see the following line as a source mapping url comment, so we mangle it a bit (the [M]) - const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\r?\n?$/; +// Sometimes tools can see the following line as a source mapping url comment, so we mangle it a bit (the [M]) +const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\r?\n?$/; - const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/; +const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/; - export interface LineInfo { - getLineCount(): number; - getLineText(line: number): string; - } +export interface LineInfo { + getLineCount(): number; + getLineText(line: number): string; +} - export function getLineInfo(text: string, lineStarts: readonly number[]): LineInfo { - return { - getLineCount: () => lineStarts.length, - getLineText: line => text.substring(lineStarts[line], lineStarts[line + 1]) - }; - } +export function getLineInfo(text: string, lineStarts: readonly number[]): LineInfo { + return { + getLineCount: () => lineStarts.length, + getLineText: line => text.substring(lineStarts[line], lineStarts[line + 1]) + }; +} - /** - * Tries to find the sourceMappingURL comment at the end of a file. - */ - export function tryGetSourceMappingURL(lineInfo: LineInfo) { - for (let index = lineInfo.getLineCount() - 1; index >= 0; index--) { - const line = lineInfo.getLineText(index); - const comment = sourceMapCommentRegExp.exec(line); - if (comment) { - return trimStringEnd(comment[1]); - } - // If we see a non-whitespace/map comment-like line, break, to avoid scanning up the entire file - else if (!line.match(whitespaceOrMapCommentRegExp)) { - break; - } +/** + * Tries to find the sourceMappingURL comment at the end of a file. + */ +export function tryGetSourceMappingURL(lineInfo: LineInfo) { + for (let index = lineInfo.getLineCount() - 1; index >= 0; index--) { + const line = lineInfo.getLineText(index); + const comment = sourceMapCommentRegExp.exec(line); + if (comment) { + return trimStringEnd(comment[1]); + } + // If we see a non-whitespace/map comment-like line, break, to avoid scanning up the entire file + else if (!line.match(whitespaceOrMapCommentRegExp)) { + break; } } +} - /* eslint-disable no-null/no-null */ - function isStringOrNull(x: any) { - return typeof x === "string" || x === null; - } +/* eslint-disable no-null/no-null */ +function isStringOrNull(x: any) { + return typeof x === "string" || x === null; +} - export function isRawSourceMap(x: any): x is RawSourceMap { - return x !== null - && typeof x === "object" - && x.version === 3 - && typeof x.file === "string" - && typeof x.mappings === "string" - && isArray(x.sources) && every(x.sources, isString) - && (x.sourceRoot === undefined || x.sourceRoot === null || typeof x.sourceRoot === "string") - && (x.sourcesContent === undefined || x.sourcesContent === null || isArray(x.sourcesContent) && every(x.sourcesContent, isStringOrNull)) - && (x.names === undefined || x.names === null || isArray(x.names) && every(x.names, isString)); - } - /* eslint-enable no-null/no-null */ +export function isRawSourceMap(x: any): x is RawSourceMap { + return x !== null + && typeof x === "object" + && x.version === 3 + && typeof x.file === "string" + && typeof x.mappings === "string" + && isArray(x.sources) && every(x.sources, isString) + && (x.sourceRoot === undefined || x.sourceRoot === null || typeof x.sourceRoot === "string") + && (x.sourcesContent === undefined || x.sourcesContent === null || isArray(x.sourcesContent) && every(x.sourcesContent, isStringOrNull)) + && (x.names === undefined || x.names === null || isArray(x.names) && every(x.names, isString)); +} +/* eslint-enable no-null/no-null */ - export function tryParseRawSourceMap(text: string) { - try { - const parsed = JSON.parse(text); - if (isRawSourceMap(parsed)) { - return parsed; - } +export function tryParseRawSourceMap(text: string) { + try { + const parsed = JSON.parse(text); + if (isRawSourceMap(parsed)) { + return parsed; } - catch { - // empty - } - - return undefined; } - - export interface MappingsDecoder extends Iterator { - readonly pos: number; - readonly error: string | undefined; - readonly state: Required; + catch { + // empty } - export interface Mapping { - generatedLine: number; - generatedCharacter: number; - sourceIndex?: number; - sourceLine?: number; - sourceCharacter?: number; - nameIndex?: number; - } + return undefined; +} - export interface SourceMapping extends Mapping { - sourceIndex: number; - sourceLine: number; - sourceCharacter: number; - } +export interface MappingsDecoder extends Iterator { + readonly pos: number; + readonly error: string | undefined; + readonly state: Required; +} - export function decodeMappings(mappings: string): MappingsDecoder { - let done = false; - let pos = 0; - let generatedLine = 0; - let generatedCharacter = 0; - let sourceIndex = 0; - let sourceLine = 0; - let sourceCharacter = 0; - let nameIndex = 0; - let error: string | undefined; +export interface Mapping { + generatedLine: number; + generatedCharacter: number; + sourceIndex?: number; + sourceLine?: number; + sourceCharacter?: number; + nameIndex?: number; +} - return { - get pos() { return pos; }, - get error() { return error; }, - get state() { return captureMapping(/*hasSource*/ true, /*hasName*/ true); }, - next() { - while (!done && pos < mappings.length) { - const ch = mappings.charCodeAt(pos); - if (ch === CharacterCodes.semicolon) { - // new line - generatedLine++; - generatedCharacter = 0; - pos++; - continue; - } +export interface SourceMapping extends Mapping { + sourceIndex: number; + sourceLine: number; + sourceCharacter: number; +} - if (ch === CharacterCodes.comma) { - // Next entry is on same line - no action needed - pos++; - continue; - } +export function decodeMappings(mappings: string): MappingsDecoder { + let done = false; + let pos = 0; + let generatedLine = 0; + let generatedCharacter = 0; + let sourceIndex = 0; + let sourceLine = 0; + let sourceCharacter = 0; + let nameIndex = 0; + let error: string | undefined; + + return { + get pos() { return pos; }, + get error() { return error; }, + get state() { return captureMapping(/*hasSource*/ true, /*hasName*/ true); }, + next() { + while (!done && pos < mappings.length) { + const ch = mappings.charCodeAt(pos); + if (ch === CharacterCodes.semicolon) { + // new line + generatedLine++; + generatedCharacter = 0; + pos++; + continue; + } - let hasSource = false; - let hasName = false; + if (ch === CharacterCodes.comma) { + // Next entry is on same line - no action needed + pos++; + continue; + } - generatedCharacter += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (generatedCharacter < 0) return setErrorAndStopIterating("Invalid generatedCharacter found"); + let hasSource = false; + let hasName = false; - if (!isSourceMappingSegmentEnd()) { - hasSource = true; + generatedCharacter += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (generatedCharacter < 0) return setErrorAndStopIterating("Invalid generatedCharacter found"); - sourceIndex += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (sourceIndex < 0) return setErrorAndStopIterating("Invalid sourceIndex found"); - if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceIndex"); + if (!isSourceMappingSegmentEnd()) { + hasSource = true; - sourceLine += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (sourceLine < 0) return setErrorAndStopIterating("Invalid sourceLine found"); - if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceLine"); + sourceIndex += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (sourceIndex < 0) return setErrorAndStopIterating("Invalid sourceIndex found"); + if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceIndex"); - sourceCharacter += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (sourceCharacter < 0) return setErrorAndStopIterating("Invalid sourceCharacter found"); + sourceLine += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (sourceLine < 0) return setErrorAndStopIterating("Invalid sourceLine found"); + if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceLine"); - if (!isSourceMappingSegmentEnd()) { - hasName = true; - nameIndex += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (nameIndex < 0) return setErrorAndStopIterating("Invalid nameIndex found"); + sourceCharacter += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (sourceCharacter < 0) return setErrorAndStopIterating("Invalid sourceCharacter found"); - if (!isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Error Format: Entries after nameIndex"); - } - } + if (!isSourceMappingSegmentEnd()) { + hasName = true; + nameIndex += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (nameIndex < 0) return setErrorAndStopIterating("Invalid nameIndex found"); - return { value: captureMapping(hasSource, hasName), done }; + if (!isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Error Format: Entries after nameIndex"); + } } - return stopIterating(); + return { value: captureMapping(hasSource, hasName), done }; } - }; - function captureMapping(hasSource: true, hasName: true): Required; - function captureMapping(hasSource: boolean, hasName: boolean): Mapping; - function captureMapping(hasSource: boolean, hasName: boolean): Mapping { - return { - generatedLine, - generatedCharacter, - sourceIndex: hasSource ? sourceIndex : undefined, - sourceLine: hasSource ? sourceLine : undefined, - sourceCharacter: hasSource ? sourceCharacter : undefined, - nameIndex: hasName ? nameIndex : undefined - }; + return stopIterating(); } + }; - function stopIterating(): { value: never, done: true } { - done = true; - return { value: undefined!, done: true }; - } + function captureMapping(hasSource: true, hasName: true): Required; + function captureMapping(hasSource: boolean, hasName: boolean): Mapping; + function captureMapping(hasSource: boolean, hasName: boolean): Mapping { + return { + generatedLine, + generatedCharacter, + sourceIndex: hasSource ? sourceIndex : undefined, + sourceLine: hasSource ? sourceLine : undefined, + sourceCharacter: hasSource ? sourceCharacter : undefined, + nameIndex: hasName ? nameIndex : undefined + }; + } - function setError(message: string) { - if (error === undefined) { - error = message; - } - } + function stopIterating(): { value: never, done: true } { + done = true; + return { value: undefined!, done: true }; + } - function setErrorAndStopIterating(message: string) { - setError(message); - return stopIterating(); + function setError(message: string) { + if (error === undefined) { + error = message; } + } - function hasReportedError() { - return error !== undefined; - } + function setErrorAndStopIterating(message: string) { + setError(message); + return stopIterating(); + } - function isSourceMappingSegmentEnd() { - return (pos === mappings.length || - mappings.charCodeAt(pos) === CharacterCodes.comma || - mappings.charCodeAt(pos) === CharacterCodes.semicolon); - } + function hasReportedError() { + return error !== undefined; + } - function base64VLQFormatDecode(): number { - let moreDigits = true; - let shiftCount = 0; - let value = 0; + function isSourceMappingSegmentEnd() { + return (pos === mappings.length || + mappings.charCodeAt(pos) === CharacterCodes.comma || + mappings.charCodeAt(pos) === CharacterCodes.semicolon); + } - for (; moreDigits; pos++) { - if (pos >= mappings.length) return setError("Error in decoding base64VLQFormatDecode, past the mapping string"), -1; + function base64VLQFormatDecode(): number { + let moreDigits = true; + let shiftCount = 0; + let value = 0; - // 6 digit number - const currentByte = base64FormatDecode(mappings.charCodeAt(pos)); - if (currentByte === -1) return setError("Invalid character in VLQ"), -1; + for (; moreDigits; pos++) { + if (pos >= mappings.length) return setError("Error in decoding base64VLQFormatDecode, past the mapping string"), -1; - // If msb is set, we still have more bits to continue - moreDigits = (currentByte & 32) !== 0; + // 6 digit number + const currentByte = base64FormatDecode(mappings.charCodeAt(pos)); + if (currentByte === -1) return setError("Invalid character in VLQ"), -1; - // least significant 5 bits are the next msbs in the final value. - value = value | ((currentByte & 31) << shiftCount); - shiftCount += 5; - } + // If msb is set, we still have more bits to continue + moreDigits = (currentByte & 32) !== 0; - // Least significant bit if 1 represents negative and rest of the msb is actual absolute value - if ((value & 1) === 0) { - // + number - value = value >> 1; - } - else { - // - number - value = value >> 1; - value = -value; - } + // least significant 5 bits are the next msbs in the final value. + value = value | ((currentByte & 31) << shiftCount); + shiftCount += 5; + } - return value; + // Least significant bit if 1 represents negative and rest of the msb is actual absolute value + if ((value & 1) === 0) { + // + number + value = value >> 1; + } + else { + // - number + value = value >> 1; + value = -value; } - } - export function sameMapping(left: T, right: T) { - return left === right - || left.generatedLine === right.generatedLine - && left.generatedCharacter === right.generatedCharacter - && left.sourceIndex === right.sourceIndex - && left.sourceLine === right.sourceLine - && left.sourceCharacter === right.sourceCharacter - && left.nameIndex === right.nameIndex; + return value; } +} - export function isSourceMapping(mapping: Mapping): mapping is SourceMapping { - return mapping.sourceIndex !== undefined - && mapping.sourceLine !== undefined - && mapping.sourceCharacter !== undefined; - } +export function sameMapping(left: T, right: T) { + return left === right + || left.generatedLine === right.generatedLine + && left.generatedCharacter === right.generatedCharacter + && left.sourceIndex === right.sourceIndex + && left.sourceLine === right.sourceLine + && left.sourceCharacter === right.sourceCharacter + && left.nameIndex === right.nameIndex; +} - function base64FormatEncode(value: number) { - return value >= 0 && value < 26 ? CharacterCodes.A + value : - value >= 26 && value < 52 ? CharacterCodes.a + value - 26 : - value >= 52 && value < 62 ? CharacterCodes._0 + value - 52 : - value === 62 ? CharacterCodes.plus : - value === 63 ? CharacterCodes.slash : - Debug.fail(`${value}: not a base64 value`); - } +export function isSourceMapping(mapping: Mapping): mapping is SourceMapping { + return mapping.sourceIndex !== undefined + && mapping.sourceLine !== undefined + && mapping.sourceCharacter !== undefined; +} - function base64FormatDecode(ch: number) { - return ch >= CharacterCodes.A && ch <= CharacterCodes.Z ? ch - CharacterCodes.A : - ch >= CharacterCodes.a && ch <= CharacterCodes.z ? ch - CharacterCodes.a + 26 : - ch >= CharacterCodes._0 && ch <= CharacterCodes._9 ? ch - CharacterCodes._0 + 52 : - ch === CharacterCodes.plus ? 62 : - ch === CharacterCodes.slash ? 63 : - -1; - } +function base64FormatEncode(value: number) { + return value >= 0 && value < 26 ? CharacterCodes.A + value : + value >= 26 && value < 52 ? CharacterCodes.a + value - 26 : + value >= 52 && value < 62 ? CharacterCodes._0 + value - 52 : + value === 62 ? CharacterCodes.plus : + value === 63 ? CharacterCodes.slash : + Debug.fail(`${value}: not a base64 value`); +} - interface MappedPosition { - generatedPosition: number; - source: string | undefined; - sourceIndex: number | undefined; - sourcePosition: number | undefined; - nameIndex: number | undefined; - } +function base64FormatDecode(ch: number) { + return ch >= CharacterCodes.A && ch <= CharacterCodes.Z ? ch - CharacterCodes.A : + ch >= CharacterCodes.a && ch <= CharacterCodes.z ? ch - CharacterCodes.a + 26 : + ch >= CharacterCodes._0 && ch <= CharacterCodes._9 ? ch - CharacterCodes._0 + 52 : + ch === CharacterCodes.plus ? 62 : + ch === CharacterCodes.slash ? 63 : + -1; +} - interface SourceMappedPosition extends MappedPosition { - source: string; - sourceIndex: number; - sourcePosition: number; - } +interface MappedPosition { + generatedPosition: number; + source: string | undefined; + sourceIndex: number | undefined; + sourcePosition: number | undefined; + nameIndex: number | undefined; +} - function isSourceMappedPosition(value: MappedPosition): value is SourceMappedPosition { - return value.sourceIndex !== undefined - && value.sourcePosition !== undefined; - } +interface SourceMappedPosition extends MappedPosition { + source: string; + sourceIndex: number; + sourcePosition: number; +} - function sameMappedPosition(left: MappedPosition, right: MappedPosition) { - return left.generatedPosition === right.generatedPosition - && left.sourceIndex === right.sourceIndex - && left.sourcePosition === right.sourcePosition; - } +function isSourceMappedPosition(value: MappedPosition): value is SourceMappedPosition { + return value.sourceIndex !== undefined + && value.sourcePosition !== undefined; +} - function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) { - // Compares sourcePosition without comparing sourceIndex - // since the mappings are grouped by sourceIndex - Debug.assert(left.sourceIndex === right.sourceIndex); - return compareValues(left.sourcePosition, right.sourcePosition); - } +function sameMappedPosition(left: MappedPosition, right: MappedPosition) { + return left.generatedPosition === right.generatedPosition + && left.sourceIndex === right.sourceIndex + && left.sourcePosition === right.sourcePosition; +} - function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) { - return compareValues(left.generatedPosition, right.generatedPosition); - } +function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) { + // Compares sourcePosition without comparing sourceIndex + // since the mappings are grouped by sourceIndex + Debug.assert(left.sourceIndex === right.sourceIndex); + return compareValues(left.sourcePosition, right.sourcePosition); +} - function getSourcePositionOfMapping(value: SourceMappedPosition) { - return value.sourcePosition; - } +function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) { + return compareValues(left.generatedPosition, right.generatedPosition); +} - function getGeneratedPositionOfMapping(value: MappedPosition) { - return value.generatedPosition; - } +function getSourcePositionOfMapping(value: SourceMappedPosition) { + return value.sourcePosition; +} - export function createDocumentPositionMapper(host: DocumentPositionMapperHost, map: RawSourceMap, mapPath: string): DocumentPositionMapper { - const mapDirectory = getDirectoryPath(mapPath); - const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, mapDirectory) : mapDirectory; - const generatedAbsoluteFilePath = getNormalizedAbsolutePath(map.file, mapDirectory); - const generatedFile = host.getSourceFileLike(generatedAbsoluteFilePath); - const sourceFileAbsolutePaths = map.sources.map(source => getNormalizedAbsolutePath(source, sourceRoot)); - const sourceToSourceIndexMap = new Map(sourceFileAbsolutePaths.map((source, i) => [host.getCanonicalFileName(source), i])); - let decodedMappings: readonly MappedPosition[] | undefined; - let generatedMappings: SortedReadonlyArray | undefined; - let sourceMappings: readonly SortedReadonlyArray[] | undefined; +function getGeneratedPositionOfMapping(value: MappedPosition) { + return value.generatedPosition; +} - return { - getSourcePosition, - getGeneratedPosition - }; +export function createDocumentPositionMapper(host: DocumentPositionMapperHost, map: RawSourceMap, mapPath: string): DocumentPositionMapper { + const mapDirectory = getDirectoryPath(mapPath); + const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, mapDirectory) : mapDirectory; + const generatedAbsoluteFilePath = getNormalizedAbsolutePath(map.file, mapDirectory); + const generatedFile = host.getSourceFileLike(generatedAbsoluteFilePath); + const sourceFileAbsolutePaths = map.sources.map(source => getNormalizedAbsolutePath(source, sourceRoot)); + const sourceToSourceIndexMap = new Map(sourceFileAbsolutePaths.map((source, i) => [host.getCanonicalFileName(source), i])); + let decodedMappings: readonly MappedPosition[] | undefined; + let generatedMappings: SortedReadonlyArray | undefined; + let sourceMappings: readonly SortedReadonlyArray[] | undefined; + + return { + getSourcePosition, + getGeneratedPosition + }; - function processMapping(mapping: Mapping): MappedPosition { - const generatedPosition = generatedFile !== undefined - ? getPositionOfLineAndCharacter(generatedFile, mapping.generatedLine, mapping.generatedCharacter, /*allowEdits*/ true) + function processMapping(mapping: Mapping): MappedPosition { + const generatedPosition = generatedFile !== undefined + ? getPositionOfLineAndCharacter(generatedFile, mapping.generatedLine, mapping.generatedCharacter, /*allowEdits*/ true) + : -1; + let source: string | undefined; + let sourcePosition: number | undefined; + if (isSourceMapping(mapping)) { + const sourceFile = host.getSourceFileLike(sourceFileAbsolutePaths[mapping.sourceIndex]); + source = map.sources[mapping.sourceIndex]; + sourcePosition = sourceFile !== undefined + ? getPositionOfLineAndCharacter(sourceFile, mapping.sourceLine, mapping.sourceCharacter, /*allowEdits*/ true) : -1; - let source: string | undefined; - let sourcePosition: number | undefined; - if (isSourceMapping(mapping)) { - const sourceFile = host.getSourceFileLike(sourceFileAbsolutePaths[mapping.sourceIndex]); - source = map.sources[mapping.sourceIndex]; - sourcePosition = sourceFile !== undefined - ? getPositionOfLineAndCharacter(sourceFile, mapping.sourceLine, mapping.sourceCharacter, /*allowEdits*/ true) - : -1; - } - return { - generatedPosition, - source, - sourceIndex: mapping.sourceIndex, - sourcePosition, - nameIndex: mapping.nameIndex - }; } + return { + generatedPosition, + source, + sourceIndex: mapping.sourceIndex, + sourcePosition, + nameIndex: mapping.nameIndex + }; + } - function getDecodedMappings() { - if (decodedMappings === undefined) { - const decoder = decodeMappings(map.mappings); - const mappings = arrayFrom(decoder, processMapping); - if (decoder.error !== undefined) { - if (host.log) { - host.log(`Encountered error while decoding sourcemap: ${decoder.error}`); - } - decodedMappings = emptyArray; - } - else { - decodedMappings = mappings; + function getDecodedMappings() { + if (decodedMappings === undefined) { + const decoder = decodeMappings(map.mappings); + const mappings = arrayFrom(decoder, processMapping); + if (decoder.error !== undefined) { + if (host.log) { + host.log(`Encountered error while decoding sourcemap: ${decoder.error}`); } + decodedMappings = emptyArray; + } + else { + decodedMappings = mappings; } - return decodedMappings; } + return decodedMappings; + } - function getSourceMappings(sourceIndex: number) { - if (sourceMappings === undefined) { - const lists: SourceMappedPosition[][] = []; - for (const mapping of getDecodedMappings()) { - if (!isSourceMappedPosition(mapping)) continue; - let list = lists[mapping.sourceIndex]; - if (!list) lists[mapping.sourceIndex] = list = []; - list.push(mapping); - } - sourceMappings = lists.map(list => sortAndDeduplicate(list, compareSourcePositions, sameMappedPosition)); + function getSourceMappings(sourceIndex: number) { + if (sourceMappings === undefined) { + const lists: SourceMappedPosition[][] = []; + for (const mapping of getDecodedMappings()) { + if (!isSourceMappedPosition(mapping)) continue; + let list = lists[mapping.sourceIndex]; + if (!list) lists[mapping.sourceIndex] = list = []; + list.push(mapping); } - return sourceMappings[sourceIndex]; + sourceMappings = lists.map(list => sortAndDeduplicate(list, compareSourcePositions, sameMappedPosition)); } + return sourceMappings[sourceIndex]; + } - function getGeneratedMappings() { - if (generatedMappings === undefined) { - const list: MappedPosition[] = []; - for (const mapping of getDecodedMappings()) { - list.push(mapping); - } - generatedMappings = sortAndDeduplicate(list, compareGeneratedPositions, sameMappedPosition); + function getGeneratedMappings() { + if (generatedMappings === undefined) { + const list: MappedPosition[] = []; + for (const mapping of getDecodedMappings()) { + list.push(mapping); } - return generatedMappings; + generatedMappings = sortAndDeduplicate(list, compareGeneratedPositions, sameMappedPosition); } + return generatedMappings; + } - function getGeneratedPosition(loc: DocumentPosition): DocumentPosition { - const sourceIndex = sourceToSourceIndexMap.get(host.getCanonicalFileName(loc.fileName)); - if (sourceIndex === undefined) return loc; - - const sourceMappings = getSourceMappings(sourceIndex); - if (!some(sourceMappings)) return loc; + function getGeneratedPosition(loc: DocumentPosition): DocumentPosition { + const sourceIndex = sourceToSourceIndexMap.get(host.getCanonicalFileName(loc.fileName)); + if (sourceIndex === undefined) return loc; - let targetIndex = binarySearchKey(sourceMappings, loc.pos, getSourcePositionOfMapping, compareValues); - if (targetIndex < 0) { - // if no exact match, closest is 2's complement of result - targetIndex = ~targetIndex; - } + const sourceMappings = getSourceMappings(sourceIndex); + if (!some(sourceMappings)) return loc; - const mapping = sourceMappings[targetIndex]; - if (mapping === undefined || mapping.sourceIndex !== sourceIndex) { - return loc; - } + let targetIndex = binarySearchKey(sourceMappings, loc.pos, getSourcePositionOfMapping, compareValues); + if (targetIndex < 0) { + // if no exact match, closest is 2's complement of result + targetIndex = ~targetIndex; + } - return { fileName: generatedAbsoluteFilePath, pos: mapping.generatedPosition }; // Closest pos + const mapping = sourceMappings[targetIndex]; + if (mapping === undefined || mapping.sourceIndex !== sourceIndex) { + return loc; } - function getSourcePosition(loc: DocumentPosition): DocumentPosition { - const generatedMappings = getGeneratedMappings(); - if (!some(generatedMappings)) return loc; + return { fileName: generatedAbsoluteFilePath, pos: mapping.generatedPosition }; // Closest pos + } - let targetIndex = binarySearchKey(generatedMappings, loc.pos, getGeneratedPositionOfMapping, compareValues); - if (targetIndex < 0) { - // if no exact match, closest is 2's complement of result - targetIndex = ~targetIndex; - } + function getSourcePosition(loc: DocumentPosition): DocumentPosition { + const generatedMappings = getGeneratedMappings(); + if (!some(generatedMappings)) return loc; - const mapping = generatedMappings[targetIndex]; - if (mapping === undefined || !isSourceMappedPosition(mapping)) { - return loc; - } + let targetIndex = binarySearchKey(generatedMappings, loc.pos, getGeneratedPositionOfMapping, compareValues); + if (targetIndex < 0) { + // if no exact match, closest is 2's complement of result + targetIndex = ~targetIndex; + } - return { fileName: sourceFileAbsolutePaths[mapping.sourceIndex], pos: mapping.sourcePosition }; // Closest pos + const mapping = generatedMappings[targetIndex]; + if (mapping === undefined || !isSourceMappedPosition(mapping)) { + return loc; } + + return { fileName: sourceFileAbsolutePaths[mapping.sourceIndex], pos: mapping.sourcePosition }; // Closest pos } +} - export const identitySourceMapConsumer: DocumentPositionMapper = { - getSourcePosition: identity, - getGeneratedPosition: identity - }; +export const identitySourceMapConsumer: DocumentPositionMapper = { + getSourcePosition: identity, + getGeneratedPosition: identity +}; } diff --git a/src/compiler/symbolWalker.ts b/src/compiler/symbolWalker.ts index 4130d7fc84f9e..4abdcff1799f3 100644 --- a/src/compiler/symbolWalker.ts +++ b/src/compiler/symbolWalker.ts @@ -1,190 +1,190 @@ /** @internal */ namespace ts { - export function createGetSymbolWalker( - getRestTypeOfSignature: (sig: Signature) => Type, - getTypePredicateOfSignature: (sig: Signature) => TypePredicate | undefined, - getReturnTypeOfSignature: (sig: Signature) => Type, - getBaseTypes: (type: Type) => Type[], - resolveStructuredTypeMembers: (type: ObjectType) => ResolvedType, - getTypeOfSymbol: (sym: Symbol) => Type, - getResolvedSymbol: (node: Node) => Symbol, - getConstraintOfTypeParameter: (typeParameter: TypeParameter) => Type | undefined, - getFirstIdentifier: (node: EntityNameOrEntityNameExpression) => Identifier, - getTypeArguments: (type: TypeReference) => readonly Type[]) { - - return getSymbolWalker; - - function getSymbolWalker(accept: (symbol: Symbol) => boolean = () => true): SymbolWalker { - const visitedTypes: Type[] = []; // Sparse array from id to type - const visitedSymbols: Symbol[] = []; // Sparse array from id to symbol - - return { - walkType: type => { - try { - visitType(type); - return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; - } - finally { - clear(visitedTypes); - clear(visitedSymbols); - } - }, - walkSymbol: symbol => { - try { - visitSymbol(symbol); - return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; - } - finally { - clear(visitedTypes); - clear(visitedSymbols); - } - }, - }; - - function visitType(type: Type | undefined): void { - if (!type) { - return; - } +export function createGetSymbolWalker( + getRestTypeOfSignature: (sig: Signature) => Type, + getTypePredicateOfSignature: (sig: Signature) => TypePredicate | undefined, + getReturnTypeOfSignature: (sig: Signature) => Type, + getBaseTypes: (type: Type) => Type[], + resolveStructuredTypeMembers: (type: ObjectType) => ResolvedType, + getTypeOfSymbol: (sym: Symbol) => Type, + getResolvedSymbol: (node: Node) => Symbol, + getConstraintOfTypeParameter: (typeParameter: TypeParameter) => Type | undefined, + getFirstIdentifier: (node: EntityNameOrEntityNameExpression) => Identifier, + getTypeArguments: (type: TypeReference) => readonly Type[]) { + + return getSymbolWalker; + + function getSymbolWalker(accept: (symbol: Symbol) => boolean = () => true): SymbolWalker { + const visitedTypes: Type[] = []; // Sparse array from id to type + const visitedSymbols: Symbol[] = []; // Sparse array from id to symbol + + return { + walkType: type => { + try { + visitType(type); + return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; + } + finally { + clear(visitedTypes); + clear(visitedSymbols); + } + }, + walkSymbol: symbol => { + try { + visitSymbol(symbol); + return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; + } + finally { + clear(visitedTypes); + clear(visitedSymbols); + } + }, + }; + + function visitType(type: Type | undefined): void { + if (!type) { + return; + } - if (visitedTypes[type.id]) { - return; - } - visitedTypes[type.id] = type; - - // Reuse visitSymbol to visit the type's symbol, - // but be sure to bail on recuring into the type if accept declines the symbol. - const shouldBail = visitSymbol(type.symbol); - if (shouldBail) return; - - // Visit the type's related types, if any - if (type.flags & TypeFlags.Object) { - const objectType = type as ObjectType; - const objectFlags = objectType.objectFlags; - if (objectFlags & ObjectFlags.Reference) { - visitTypeReference(type as TypeReference); - } - if (objectFlags & ObjectFlags.Mapped) { - visitMappedType(type as MappedType); - } - if (objectFlags & (ObjectFlags.Class | ObjectFlags.Interface)) { - visitInterfaceType(type as InterfaceType); - } - if (objectFlags & (ObjectFlags.Tuple | ObjectFlags.Anonymous)) { - visitObjectType(objectType); - } - } - if (type.flags & TypeFlags.TypeParameter) { - visitTypeParameter(type as TypeParameter); + if (visitedTypes[type.id]) { + return; + } + visitedTypes[type.id] = type; + + // Reuse visitSymbol to visit the type's symbol, + // but be sure to bail on recuring into the type if accept declines the symbol. + const shouldBail = visitSymbol(type.symbol); + if (shouldBail) return; + + // Visit the type's related types, if any + if (type.flags & TypeFlags.Object) { + const objectType = type as ObjectType; + const objectFlags = objectType.objectFlags; + if (objectFlags & ObjectFlags.Reference) { + visitTypeReference(type as TypeReference); } - if (type.flags & TypeFlags.UnionOrIntersection) { - visitUnionOrIntersectionType(type as UnionOrIntersectionType); + if (objectFlags & ObjectFlags.Mapped) { + visitMappedType(type as MappedType); } - if (type.flags & TypeFlags.Index) { - visitIndexType(type as IndexType); + if (objectFlags & (ObjectFlags.Class | ObjectFlags.Interface)) { + visitInterfaceType(type as InterfaceType); } - if (type.flags & TypeFlags.IndexedAccess) { - visitIndexedAccessType(type as IndexedAccessType); + if (objectFlags & (ObjectFlags.Tuple | ObjectFlags.Anonymous)) { + visitObjectType(objectType); } } - - function visitTypeReference(type: TypeReference): void { - visitType(type.target); - forEach(getTypeArguments(type), visitType); + if (type.flags & TypeFlags.TypeParameter) { + visitTypeParameter(type as TypeParameter); } - - function visitTypeParameter(type: TypeParameter): void { - visitType(getConstraintOfTypeParameter(type)); + if (type.flags & TypeFlags.UnionOrIntersection) { + visitUnionOrIntersectionType(type as UnionOrIntersectionType); } - - function visitUnionOrIntersectionType(type: UnionOrIntersectionType): void { - forEach(type.types, visitType); + if (type.flags & TypeFlags.Index) { + visitIndexType(type as IndexType); } - - function visitIndexType(type: IndexType): void { - visitType(type.type); + if (type.flags & TypeFlags.IndexedAccess) { + visitIndexedAccessType(type as IndexedAccessType); } + } - function visitIndexedAccessType(type: IndexedAccessType): void { - visitType(type.objectType); - visitType(type.indexType); - visitType(type.constraint); - } + function visitTypeReference(type: TypeReference): void { + visitType(type.target); + forEach(getTypeArguments(type), visitType); + } - function visitMappedType(type: MappedType): void { - visitType(type.typeParameter); - visitType(type.constraintType); - visitType(type.templateType); - visitType(type.modifiersType); - } + function visitTypeParameter(type: TypeParameter): void { + visitType(getConstraintOfTypeParameter(type)); + } - function visitSignature(signature: Signature): void { - const typePredicate = getTypePredicateOfSignature(signature); - if (typePredicate) { - visitType(typePredicate.type); - } - forEach(signature.typeParameters, visitType); + function visitUnionOrIntersectionType(type: UnionOrIntersectionType): void { + forEach(type.types, visitType); + } - for (const parameter of signature.parameters) { - visitSymbol(parameter); - } - visitType(getRestTypeOfSignature(signature)); - visitType(getReturnTypeOfSignature(signature)); + function visitIndexType(type: IndexType): void { + visitType(type.type); + } + + function visitIndexedAccessType(type: IndexedAccessType): void { + visitType(type.objectType); + visitType(type.indexType); + visitType(type.constraint); + } + + function visitMappedType(type: MappedType): void { + visitType(type.typeParameter); + visitType(type.constraintType); + visitType(type.templateType); + visitType(type.modifiersType); + } + + function visitSignature(signature: Signature): void { + const typePredicate = getTypePredicateOfSignature(signature); + if (typePredicate) { + visitType(typePredicate.type); } + forEach(signature.typeParameters, visitType); - function visitInterfaceType(interfaceT: InterfaceType): void { - visitObjectType(interfaceT); - forEach(interfaceT.typeParameters, visitType); - forEach(getBaseTypes(interfaceT), visitType); - visitType(interfaceT.thisType); + for (const parameter of signature.parameters) { + visitSymbol(parameter); } + visitType(getRestTypeOfSignature(signature)); + visitType(getReturnTypeOfSignature(signature)); + } - function visitObjectType(type: ObjectType): void { - const resolved = resolveStructuredTypeMembers(type); - for (const info of resolved.indexInfos) { - visitType(info.keyType); - visitType(info.type); - } - for (const signature of resolved.callSignatures) { - visitSignature(signature); - } - for (const signature of resolved.constructSignatures) { - visitSignature(signature); - } - for (const p of resolved.properties) { - visitSymbol(p); - } + function visitInterfaceType(interfaceT: InterfaceType): void { + visitObjectType(interfaceT); + forEach(interfaceT.typeParameters, visitType); + forEach(getBaseTypes(interfaceT), visitType); + visitType(interfaceT.thisType); + } + + function visitObjectType(type: ObjectType): void { + const resolved = resolveStructuredTypeMembers(type); + for (const info of resolved.indexInfos) { + visitType(info.keyType); + visitType(info.type); + } + for (const signature of resolved.callSignatures) { + visitSignature(signature); } + for (const signature of resolved.constructSignatures) { + visitSignature(signature); + } + for (const p of resolved.properties) { + visitSymbol(p); + } + } - function visitSymbol(symbol: Symbol | undefined): boolean { - if (!symbol) { - return false; - } - const symbolId = getSymbolId(symbol); - if (visitedSymbols[symbolId]) { - return false; - } - visitedSymbols[symbolId] = symbol; - if (!accept(symbol)) { - return true; - } - const t = getTypeOfSymbol(symbol); - visitType(t); // Should handle members on classes and such - if (symbol.exports) { - symbol.exports.forEach(visitSymbol); - } - forEach(symbol.declarations, d => { - // Type queries are too far resolved when we just visit the symbol's type - // (their type resolved directly to the member deeply referenced) - // So to get the intervening symbols, we need to check if there's a type - // query node on any of the symbol's declarations and get symbols there - if ((d as any).type && (d as any).type.kind === SyntaxKind.TypeQuery) { - const query = (d as any).type as TypeQueryNode; - const entity = getResolvedSymbol(getFirstIdentifier(query.exprName)); - visitSymbol(entity); - } - }); + function visitSymbol(symbol: Symbol | undefined): boolean { + if (!symbol) { + return false; + } + const symbolId = getSymbolId(symbol); + if (visitedSymbols[symbolId]) { return false; } + visitedSymbols[symbolId] = symbol; + if (!accept(symbol)) { + return true; + } + const t = getTypeOfSymbol(symbol); + visitType(t); // Should handle members on classes and such + if (symbol.exports) { + symbol.exports.forEach(visitSymbol); + } + forEach(symbol.declarations, d => { + // Type queries are too far resolved when we just visit the symbol's type + // (their type resolved directly to the member deeply referenced) + // So to get the intervening symbols, we need to check if there's a type + // query node on any of the symbol's declarations and get symbols there + if ((d as any).type && (d as any).type.kind === SyntaxKind.TypeQuery) { + const query = (d as any).type as TypeQueryNode; + const entity = getResolvedSymbol(getFirstIdentifier(query.exprName)); + visitSymbol(entity); + } + }); + return false; } } -} \ No newline at end of file +} +} diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index b41e89e991567..8d59933c8893d 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -2,1915 +2,1915 @@ declare function setTimeout(handler: (...args: any[]) => void, timeout: number): declare function clearTimeout(handle: any): void; namespace ts { - /** - * djb2 hashing algorithm - * http://www.cse.yorku.ca/~oz/hash.html - */ - /* @internal */ - export function generateDjb2Hash(data: string): string { - let acc = 5381; - for (let i = 0; i < data.length; i++) { - acc = ((acc << 5) + acc) + data.charCodeAt(i); - } - return acc.toString(); +/** + * djb2 hashing algorithm + * http://www.cse.yorku.ca/~oz/hash.html + */ +/* @internal */ +export function generateDjb2Hash(data: string): string { + let acc = 5381; + for (let i = 0; i < data.length; i++) { + acc = ((acc << 5) + acc) + data.charCodeAt(i); } + return acc.toString(); +} - /** - * Set a high stack trace limit to provide more information in case of an error. - * Called for command-line and server use cases. - * Not called if TypeScript is used as a library. - */ - /* @internal */ - export function setStackTraceLimit() { - if ((Error as any).stackTraceLimit < 100) { // Also tests that we won't set the property if it doesn't exist. - (Error as any).stackTraceLimit = 100; - } +/** + * Set a high stack trace limit to provide more information in case of an error. + * Called for command-line and server use cases. + * Not called if TypeScript is used as a library. + */ +/* @internal */ +export function setStackTraceLimit() { + if ((Error as any).stackTraceLimit < 100) { // Also tests that we won't set the property if it doesn't exist. + (Error as any).stackTraceLimit = 100; } +} - export enum FileWatcherEventKind { - Created, - Changed, - Deleted - } +export enum FileWatcherEventKind { + Created, + Changed, + Deleted +} - export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind) => void; - export type DirectoryWatcherCallback = (fileName: string) => void; - /*@internal*/ - export interface WatchedFile { - readonly fileName: string; - readonly callback: FileWatcherCallback; - mtime: Date; - } +export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind) => void; +export type DirectoryWatcherCallback = (fileName: string) => void; +/*@internal*/ +export interface WatchedFile { + readonly fileName: string; + readonly callback: FileWatcherCallback; + mtime: Date; +} - /* @internal */ - export enum PollingInterval { - High = 2000, - Medium = 500, - Low = 250 - } +/* @internal */ +export enum PollingInterval { + High = 2000, + Medium = 500, + Low = 250 +} - /* @internal */ - export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) => FileWatcher; - /* @internal */ - export type HostWatchDirectory = (fileName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined) => FileWatcher; +/* @internal */ +export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) => FileWatcher; +/* @internal */ +export type HostWatchDirectory = (fileName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined) => FileWatcher; - /* @internal */ - export const missingFileModifiedTime = new Date(0); // Any subsequent modification will occur after this time +/* @internal */ +export const missingFileModifiedTime = new Date(0); // Any subsequent modification will occur after this time - /* @internal */ - export function getModifiedTime(host: { getModifiedTime: NonNullable; }, fileName: string) { - return host.getModifiedTime(fileName) || missingFileModifiedTime; - } +/* @internal */ +export function getModifiedTime(host: { getModifiedTime: NonNullable; }, fileName: string) { + return host.getModifiedTime(fileName) || missingFileModifiedTime; +} - interface Levels { - Low: number; - Medium: number; - High: number; - } +interface Levels { + Low: number; + Medium: number; + High: number; +} - function createPollingIntervalBasedLevels(levels: Levels) { - return { - [PollingInterval.Low]: levels.Low, - [PollingInterval.Medium]: levels.Medium, - [PollingInterval.High]: levels.High - }; +function createPollingIntervalBasedLevels(levels: Levels) { + return { + [PollingInterval.Low]: levels.Low, + [PollingInterval.Medium]: levels.Medium, + [PollingInterval.High]: levels.High + }; +} + +const defaultChunkLevels: Levels = { Low: 32, Medium: 64, High: 256 }; +let pollingChunkSize = createPollingIntervalBasedLevels(defaultChunkLevels); +/* @internal */ +export let unchangedPollThresholds = createPollingIntervalBasedLevels(defaultChunkLevels); + +/* @internal */ +export function setCustomPollingValues(system: System) { + if (!system.getEnvironmentVariable) { + return; } + const pollingIntervalChanged = setCustomLevels("TSC_WATCH_POLLINGINTERVAL", PollingInterval); + pollingChunkSize = getCustomPollingBasedLevels("TSC_WATCH_POLLINGCHUNKSIZE", defaultChunkLevels) || pollingChunkSize; + unchangedPollThresholds = getCustomPollingBasedLevels("TSC_WATCH_UNCHANGEDPOLLTHRESHOLDS", defaultChunkLevels) || unchangedPollThresholds; - const defaultChunkLevels: Levels = { Low: 32, Medium: 64, High: 256 }; - let pollingChunkSize = createPollingIntervalBasedLevels(defaultChunkLevels); - /* @internal */ - export let unchangedPollThresholds = createPollingIntervalBasedLevels(defaultChunkLevels); + function getLevel(envVar: string, level: keyof Levels) { + return system.getEnvironmentVariable(`${envVar}_${level.toUpperCase()}`); + } - /* @internal */ - export function setCustomPollingValues(system: System) { - if (!system.getEnvironmentVariable) { - return; + function getCustomLevels(baseVariable: string) { + let customLevels: Partial | undefined; + setCustomLevel("Low"); + setCustomLevel("Medium"); + setCustomLevel("High"); + return customLevels; + + function setCustomLevel(level: keyof Levels) { + const customLevel = getLevel(baseVariable, level); + if (customLevel) { + (customLevels || (customLevels = {}))[level] = Number(customLevel); + } } - const pollingIntervalChanged = setCustomLevels("TSC_WATCH_POLLINGINTERVAL", PollingInterval); - pollingChunkSize = getCustomPollingBasedLevels("TSC_WATCH_POLLINGCHUNKSIZE", defaultChunkLevels) || pollingChunkSize; - unchangedPollThresholds = getCustomPollingBasedLevels("TSC_WATCH_UNCHANGEDPOLLTHRESHOLDS", defaultChunkLevels) || unchangedPollThresholds; + } - function getLevel(envVar: string, level: keyof Levels) { - return system.getEnvironmentVariable(`${envVar}_${level.toUpperCase()}`); + function setCustomLevels(baseVariable: string, levels: Levels) { + const customLevels = getCustomLevels(baseVariable); + if (customLevels) { + setLevel("Low"); + setLevel("Medium"); + setLevel("High"); + return true; } + return false; - function getCustomLevels(baseVariable: string) { - let customLevels: Partial | undefined; - setCustomLevel("Low"); - setCustomLevel("Medium"); - setCustomLevel("High"); - return customLevels; - - function setCustomLevel(level: keyof Levels) { - const customLevel = getLevel(baseVariable, level); - if (customLevel) { - (customLevels || (customLevels = {}))[level] = Number(customLevel); - } - } + function setLevel(level: keyof Levels) { + levels[level] = customLevels![level] || levels[level]; } + } - function setCustomLevels(baseVariable: string, levels: Levels) { - const customLevels = getCustomLevels(baseVariable); - if (customLevels) { - setLevel("Low"); - setLevel("Medium"); - setLevel("High"); - return true; - } - return false; + function getCustomPollingBasedLevels(baseVariable: string, defaultLevels: Levels) { + const customLevels = getCustomLevels(baseVariable); + return (pollingIntervalChanged || customLevels) && + createPollingIntervalBasedLevels(customLevels ? { ...defaultLevels, ...customLevels } : defaultLevels); + } +} - function setLevel(level: keyof Levels) { - levels[level] = customLevels![level] || levels[level]; - } +interface WatchedFileWithIsClosed extends WatchedFile { + isClosed?: boolean; +} +function pollWatchedFileQueue( + host: { getModifiedTime: NonNullable; }, + queue: (T | undefined)[], + pollIndex: number, chunkSize: number, + callbackOnWatchFileStat?: (watchedFile: T, pollIndex: number, fileChanged: boolean) => void +) { + let definedValueCopyToIndex = pollIndex; + // Max visit would be all elements of the queue + for (let canVisit = queue.length; chunkSize && canVisit; nextPollIndex(), canVisit--) { + const watchedFile = queue[pollIndex]; + if (!watchedFile) { + continue; + } + else if (watchedFile.isClosed) { + queue[pollIndex] = undefined; + continue; } - function getCustomPollingBasedLevels(baseVariable: string, defaultLevels: Levels) { - const customLevels = getCustomLevels(baseVariable); - return (pollingIntervalChanged || customLevels) && - createPollingIntervalBasedLevels(customLevels ? { ...defaultLevels, ...customLevels } : defaultLevels); + // Only files polled count towards chunkSize + chunkSize--; + const fileChanged = onWatchedFileStat(watchedFile, getModifiedTime(host, watchedFile.fileName)); + if (watchedFile.isClosed) { + // Closed watcher as part of callback + queue[pollIndex] = undefined; + continue; } - } - interface WatchedFileWithIsClosed extends WatchedFile { - isClosed?: boolean; - } - function pollWatchedFileQueue( - host: { getModifiedTime: NonNullable; }, - queue: (T | undefined)[], - pollIndex: number, chunkSize: number, - callbackOnWatchFileStat?: (watchedFile: T, pollIndex: number, fileChanged: boolean) => void - ) { - let definedValueCopyToIndex = pollIndex; - // Max visit would be all elements of the queue - for (let canVisit = queue.length; chunkSize && canVisit; nextPollIndex(), canVisit--) { - const watchedFile = queue[pollIndex]; - if (!watchedFile) { - continue; - } - else if (watchedFile.isClosed) { + callbackOnWatchFileStat?.(watchedFile, pollIndex, fileChanged); + // Defragment the queue while we are at it + if (queue[pollIndex]) { + // Copy this file to the non hole location + if (definedValueCopyToIndex < pollIndex) { + queue[definedValueCopyToIndex] = watchedFile; queue[pollIndex] = undefined; - continue; } + definedValueCopyToIndex++; + } + } - // Only files polled count towards chunkSize - chunkSize--; - const fileChanged = onWatchedFileStat(watchedFile, getModifiedTime(host, watchedFile.fileName)); - if (watchedFile.isClosed) { - // Closed watcher as part of callback - queue[pollIndex] = undefined; - continue; - } + // Return next poll index + return pollIndex; - callbackOnWatchFileStat?.(watchedFile, pollIndex, fileChanged); - // Defragment the queue while we are at it - if (queue[pollIndex]) { - // Copy this file to the non hole location - if (definedValueCopyToIndex < pollIndex) { - queue[definedValueCopyToIndex] = watchedFile; - queue[pollIndex] = undefined; - } - definedValueCopyToIndex++; + function nextPollIndex() { + pollIndex++; + if (pollIndex === queue.length) { + if (definedValueCopyToIndex < pollIndex) { + // There are holes from definedValueCopyToIndex to end of queue, change queue size + queue.length = definedValueCopyToIndex; } + pollIndex = 0; + definedValueCopyToIndex = 0; } + } +} - // Return next poll index - return pollIndex; +/* @internal */ +export function createDynamicPriorityPollingWatchFile(host: { + getModifiedTime: NonNullable; + setTimeout: NonNullable; +}): HostWatchFile { + interface WatchedFile extends ts.WatchedFile { + isClosed?: boolean; + unchangedPolls: number; + } - function nextPollIndex() { - pollIndex++; - if (pollIndex === queue.length) { - if (definedValueCopyToIndex < pollIndex) { - // There are holes from definedValueCopyToIndex to end of queue, change queue size - queue.length = definedValueCopyToIndex; - } - pollIndex = 0; - definedValueCopyToIndex = 0; - } - } + interface PollingIntervalQueue extends Array { + pollingInterval: PollingInterval; + pollIndex: number; + pollScheduled: boolean; } - /* @internal */ - export function createDynamicPriorityPollingWatchFile(host: { - getModifiedTime: NonNullable; - setTimeout: NonNullable; - }): HostWatchFile { - interface WatchedFile extends ts.WatchedFile { - isClosed?: boolean; - unchangedPolls: number; - } + const watchedFiles: WatchedFile[] = []; + const changedFilesInLastPoll: WatchedFile[] = []; + const lowPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Low); + const mediumPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Medium); + const highPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.High); + return watchFile; + + function watchFile(fileName: string, callback: FileWatcherCallback, defaultPollingInterval: PollingInterval): FileWatcher { + const file: WatchedFile = { + fileName, + callback, + unchangedPolls: 0, + mtime: getModifiedTime(host, fileName) + }; + watchedFiles.push(file); - interface PollingIntervalQueue extends Array { - pollingInterval: PollingInterval; - pollIndex: number; - pollScheduled: boolean; - } + addToPollingIntervalQueue(file, defaultPollingInterval); + return { + close: () => { + file.isClosed = true; + // Remove from watchedFiles + unorderedRemoveItem(watchedFiles, file); + // Do not update polling interval queue since that will happen as part of polling + } + }; + } - const watchedFiles: WatchedFile[] = []; - const changedFilesInLastPoll: WatchedFile[] = []; - const lowPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Low); - const mediumPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Medium); - const highPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.High); - return watchFile; - - function watchFile(fileName: string, callback: FileWatcherCallback, defaultPollingInterval: PollingInterval): FileWatcher { - const file: WatchedFile = { - fileName, - callback, - unchangedPolls: 0, - mtime: getModifiedTime(host, fileName) - }; - watchedFiles.push(file); + function createPollingIntervalQueue(pollingInterval: PollingInterval): PollingIntervalQueue { + const queue = [] as WatchedFile[] as PollingIntervalQueue; + queue.pollingInterval = pollingInterval; + queue.pollIndex = 0; + queue.pollScheduled = false; + return queue; + } - addToPollingIntervalQueue(file, defaultPollingInterval); - return { - close: () => { - file.isClosed = true; - // Remove from watchedFiles - unorderedRemoveItem(watchedFiles, file); - // Do not update polling interval queue since that will happen as part of polling - } - }; + function pollPollingIntervalQueue(queue: PollingIntervalQueue) { + queue.pollIndex = pollQueue(queue, queue.pollingInterval, queue.pollIndex, pollingChunkSize[queue.pollingInterval]); + // Set the next polling index and timeout + if (queue.length) { + scheduleNextPoll(queue.pollingInterval); } - - function createPollingIntervalQueue(pollingInterval: PollingInterval): PollingIntervalQueue { - const queue = [] as WatchedFile[] as PollingIntervalQueue; - queue.pollingInterval = pollingInterval; - queue.pollIndex = 0; + else { + Debug.assert(queue.pollIndex === 0); queue.pollScheduled = false; - return queue; - } - - function pollPollingIntervalQueue(queue: PollingIntervalQueue) { - queue.pollIndex = pollQueue(queue, queue.pollingInterval, queue.pollIndex, pollingChunkSize[queue.pollingInterval]); - // Set the next polling index and timeout - if (queue.length) { - scheduleNextPoll(queue.pollingInterval); - } - else { - Debug.assert(queue.pollIndex === 0); - queue.pollScheduled = false; - } } + } - function pollLowPollingIntervalQueue(queue: PollingIntervalQueue) { - // Always poll complete list of changedFilesInLastPoll - pollQueue(changedFilesInLastPoll, PollingInterval.Low, /*pollIndex*/ 0, changedFilesInLastPoll.length); + function pollLowPollingIntervalQueue(queue: PollingIntervalQueue) { + // Always poll complete list of changedFilesInLastPoll + pollQueue(changedFilesInLastPoll, PollingInterval.Low, /*pollIndex*/ 0, changedFilesInLastPoll.length); - // Finally do the actual polling of the queue - pollPollingIntervalQueue(queue); - // Schedule poll if there are files in changedFilesInLastPoll but no files in the actual queue - // as pollPollingIntervalQueue wont schedule for next poll - if (!queue.pollScheduled && changedFilesInLastPoll.length) { - scheduleNextPoll(PollingInterval.Low); - } + // Finally do the actual polling of the queue + pollPollingIntervalQueue(queue); + // Schedule poll if there are files in changedFilesInLastPoll but no files in the actual queue + // as pollPollingIntervalQueue wont schedule for next poll + if (!queue.pollScheduled && changedFilesInLastPoll.length) { + scheduleNextPoll(PollingInterval.Low); } + } - function pollQueue(queue: (WatchedFile | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) { - return pollWatchedFileQueue( - host, - queue, - pollIndex, - chunkSize, - onWatchFileStat - ); + function pollQueue(queue: (WatchedFile | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) { + return pollWatchedFileQueue( + host, + queue, + pollIndex, + chunkSize, + onWatchFileStat + ); - function onWatchFileStat(watchedFile: WatchedFile, pollIndex: number, fileChanged: boolean) { - if (fileChanged) { - watchedFile.unchangedPolls = 0; - // Changed files go to changedFilesInLastPoll queue - if (queue !== changedFilesInLastPoll) { - queue[pollIndex] = undefined; - addChangedFileToLowPollingIntervalQueue(watchedFile); - } - } - else if (watchedFile.unchangedPolls !== unchangedPollThresholds[pollingInterval]) { - watchedFile.unchangedPolls++; - } - else if (queue === changedFilesInLastPoll) { - // Restart unchangedPollCount for unchanged file and move to low polling interval queue - watchedFile.unchangedPolls = 1; - queue[pollIndex] = undefined; - addToPollingIntervalQueue(watchedFile, PollingInterval.Low); - } - else if (pollingInterval !== PollingInterval.High) { - watchedFile.unchangedPolls++; + function onWatchFileStat(watchedFile: WatchedFile, pollIndex: number, fileChanged: boolean) { + if (fileChanged) { + watchedFile.unchangedPolls = 0; + // Changed files go to changedFilesInLastPoll queue + if (queue !== changedFilesInLastPoll) { queue[pollIndex] = undefined; - addToPollingIntervalQueue(watchedFile, pollingInterval === PollingInterval.Low ? PollingInterval.Medium : PollingInterval.High); + addChangedFileToLowPollingIntervalQueue(watchedFile); } } - } - - function pollingIntervalQueue(pollingInterval: PollingInterval) { - switch (pollingInterval) { - case PollingInterval.Low: - return lowPollingIntervalQueue; - case PollingInterval.Medium: - return mediumPollingIntervalQueue; - case PollingInterval.High: - return highPollingIntervalQueue; + else if (watchedFile.unchangedPolls !== unchangedPollThresholds[pollingInterval]) { + watchedFile.unchangedPolls++; + } + else if (queue === changedFilesInLastPoll) { + // Restart unchangedPollCount for unchanged file and move to low polling interval queue + watchedFile.unchangedPolls = 1; + queue[pollIndex] = undefined; + addToPollingIntervalQueue(watchedFile, PollingInterval.Low); + } + else if (pollingInterval !== PollingInterval.High) { + watchedFile.unchangedPolls++; + queue[pollIndex] = undefined; + addToPollingIntervalQueue(watchedFile, pollingInterval === PollingInterval.Low ? PollingInterval.Medium : PollingInterval.High); } } + } - function addToPollingIntervalQueue(file: WatchedFile, pollingInterval: PollingInterval) { - pollingIntervalQueue(pollingInterval).push(file); - scheduleNextPollIfNotAlreadyScheduled(pollingInterval); + function pollingIntervalQueue(pollingInterval: PollingInterval) { + switch (pollingInterval) { + case PollingInterval.Low: + return lowPollingIntervalQueue; + case PollingInterval.Medium: + return mediumPollingIntervalQueue; + case PollingInterval.High: + return highPollingIntervalQueue; } + } - function addChangedFileToLowPollingIntervalQueue(file: WatchedFile) { - changedFilesInLastPoll.push(file); - scheduleNextPollIfNotAlreadyScheduled(PollingInterval.Low); - } + function addToPollingIntervalQueue(file: WatchedFile, pollingInterval: PollingInterval) { + pollingIntervalQueue(pollingInterval).push(file); + scheduleNextPollIfNotAlreadyScheduled(pollingInterval); + } - function scheduleNextPollIfNotAlreadyScheduled(pollingInterval: PollingInterval) { - if (!pollingIntervalQueue(pollingInterval).pollScheduled) { - scheduleNextPoll(pollingInterval); - } - } + function addChangedFileToLowPollingIntervalQueue(file: WatchedFile) { + changedFilesInLastPoll.push(file); + scheduleNextPollIfNotAlreadyScheduled(PollingInterval.Low); + } - function scheduleNextPoll(pollingInterval: PollingInterval) { - pollingIntervalQueue(pollingInterval).pollScheduled = host.setTimeout(pollingInterval === PollingInterval.Low ? pollLowPollingIntervalQueue : pollPollingIntervalQueue, pollingInterval, pollingIntervalQueue(pollingInterval)); + function scheduleNextPollIfNotAlreadyScheduled(pollingInterval: PollingInterval) { + if (!pollingIntervalQueue(pollingInterval).pollScheduled) { + scheduleNextPoll(pollingInterval); } } - function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSensitiveFileNames: boolean): HostWatchFile { - // One file can have multiple watchers - const fileWatcherCallbacks = createMultiMap(); - const dirWatchers = new Map(); - const toCanonicalName = createGetCanonicalFileName(useCaseSensitiveFileNames); - return nonPollingWatchFile; - - function nonPollingWatchFile(fileName: string, callback: FileWatcherCallback, _pollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined): FileWatcher { - const filePath = toCanonicalName(fileName); - fileWatcherCallbacks.add(filePath, callback); - const dirPath = getDirectoryPath(filePath) || "."; - const watcher = dirWatchers.get(dirPath) || - createDirectoryWatcher(getDirectoryPath(fileName) || ".", dirPath, fallbackOptions); - watcher.referenceCount++; - return { - close: () => { - if (watcher.referenceCount === 1) { - watcher.close(); - dirWatchers.delete(dirPath); - } - else { - watcher.referenceCount--; - } - fileWatcherCallbacks.remove(filePath, callback); + function scheduleNextPoll(pollingInterval: PollingInterval) { + pollingIntervalQueue(pollingInterval).pollScheduled = host.setTimeout(pollingInterval === PollingInterval.Low ? pollLowPollingIntervalQueue : pollPollingIntervalQueue, pollingInterval, pollingIntervalQueue(pollingInterval)); + } +} + +function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSensitiveFileNames: boolean): HostWatchFile { + // One file can have multiple watchers + const fileWatcherCallbacks = createMultiMap(); + const dirWatchers = new Map(); + const toCanonicalName = createGetCanonicalFileName(useCaseSensitiveFileNames); + return nonPollingWatchFile; + + function nonPollingWatchFile(fileName: string, callback: FileWatcherCallback, _pollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined): FileWatcher { + const filePath = toCanonicalName(fileName); + fileWatcherCallbacks.add(filePath, callback); + const dirPath = getDirectoryPath(filePath) || "."; + const watcher = dirWatchers.get(dirPath) || + createDirectoryWatcher(getDirectoryPath(fileName) || ".", dirPath, fallbackOptions); + watcher.referenceCount++; + return { + close: () => { + if (watcher.referenceCount === 1) { + watcher.close(); + dirWatchers.delete(dirPath); } - }; - } + else { + watcher.referenceCount--; + } + fileWatcherCallbacks.remove(filePath, callback); + } + }; + } - function createDirectoryWatcher(dirName: string, dirPath: string, fallbackOptions: WatchOptions | undefined) { - const watcher = fsWatch( - dirName, - FileSystemEntryKind.Directory, - (_eventName: string, relativeFileName) => { - // When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined" - if (!isString(relativeFileName)) return; - const fileName = getNormalizedAbsolutePath(relativeFileName, dirName); - // Some applications save a working file via rename operations - const callbacks = fileName && fileWatcherCallbacks.get(toCanonicalName(fileName)); - if (callbacks) { - for (const fileCallback of callbacks) { - fileCallback(fileName, FileWatcherEventKind.Changed); - } + function createDirectoryWatcher(dirName: string, dirPath: string, fallbackOptions: WatchOptions | undefined) { + const watcher = fsWatch( + dirName, + FileSystemEntryKind.Directory, + (_eventName: string, relativeFileName) => { + // When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined" + if (!isString(relativeFileName)) return; + const fileName = getNormalizedAbsolutePath(relativeFileName, dirName); + // Some applications save a working file via rename operations + const callbacks = fileName && fileWatcherCallbacks.get(toCanonicalName(fileName)); + if (callbacks) { + for (const fileCallback of callbacks) { + fileCallback(fileName, FileWatcherEventKind.Changed); } - }, - /*recursive*/ false, - PollingInterval.Medium, - fallbackOptions - ) as DirectoryWatcher; - watcher.referenceCount = 0; - dirWatchers.set(dirPath, watcher); - return watcher; - } + } + }, + /*recursive*/ false, + PollingInterval.Medium, + fallbackOptions + ) as DirectoryWatcher; + watcher.referenceCount = 0; + dirWatchers.set(dirPath, watcher); + return watcher; } +} - function createFixedChunkSizePollingWatchFile(host: { - getModifiedTime: NonNullable; - setTimeout: NonNullable; - }): HostWatchFile { - const watchedFiles: (WatchedFileWithIsClosed | undefined)[] = []; - let pollIndex = 0; - let pollScheduled: any; - return watchFile; - - function watchFile(fileName: string, callback: FileWatcherCallback): FileWatcher { - const file: WatchedFileWithIsClosed = { - fileName, - callback, - mtime: getModifiedTime(host, fileName) - }; - watchedFiles.push(file); - scheduleNextPoll(); - return { - close: () => { - file.isClosed = true; - unorderedRemoveItem(watchedFiles, file); - } - }; - } +function createFixedChunkSizePollingWatchFile(host: { + getModifiedTime: NonNullable; + setTimeout: NonNullable; +}): HostWatchFile { + const watchedFiles: (WatchedFileWithIsClosed | undefined)[] = []; + let pollIndex = 0; + let pollScheduled: any; + return watchFile; + + function watchFile(fileName: string, callback: FileWatcherCallback): FileWatcher { + const file: WatchedFileWithIsClosed = { + fileName, + callback, + mtime: getModifiedTime(host, fileName) + }; + watchedFiles.push(file); + scheduleNextPoll(); + return { + close: () => { + file.isClosed = true; + unorderedRemoveItem(watchedFiles, file); + } + }; + } - function pollQueue() { - pollScheduled = undefined; - pollIndex = pollWatchedFileQueue(host, watchedFiles, pollIndex, pollingChunkSize[PollingInterval.Low]); - scheduleNextPoll(); - } + function pollQueue() { + pollScheduled = undefined; + pollIndex = pollWatchedFileQueue(host, watchedFiles, pollIndex, pollingChunkSize[PollingInterval.Low]); + scheduleNextPoll(); + } - function scheduleNextPoll() { - if (!watchedFiles.length || pollScheduled) return; - pollScheduled = host.setTimeout(pollQueue, PollingInterval.High); - } + function scheduleNextPoll() { + if (!watchedFiles.length || pollScheduled) return; + pollScheduled = host.setTimeout(pollQueue, PollingInterval.High); } +} - /* @internal */ - export function createSingleFileWatcherPerName( - watchFile: HostWatchFile, - useCaseSensitiveFileNames: boolean - ): HostWatchFile { - interface SingleFileWatcher { - watcher: FileWatcher; - refCount: number; +/* @internal */ +export function createSingleFileWatcherPerName( + watchFile: HostWatchFile, + useCaseSensitiveFileNames: boolean +): HostWatchFile { + interface SingleFileWatcher { + watcher: FileWatcher; + refCount: number; + } + const cache = new Map(); + const callbacksCache = createMultiMap(); + const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + + return (fileName, callback, pollingInterval, options) => { + const path = toCanonicalFileName(fileName); + const existing = cache.get(path); + if (existing) { + existing.refCount++; } - const cache = new Map(); - const callbacksCache = createMultiMap(); - const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - - return (fileName, callback, pollingInterval, options) => { - const path = toCanonicalFileName(fileName); - const existing = cache.get(path); - if (existing) { - existing.refCount++; - } - else { - cache.set(path, { - watcher: watchFile( - fileName, - (fileName, eventKind) => forEach( - callbacksCache.get(path), - cb => cb(fileName, eventKind) - ), - pollingInterval, - options + else { + cache.set(path, { + watcher: watchFile( + fileName, + (fileName, eventKind) => forEach( + callbacksCache.get(path), + cb => cb(fileName, eventKind) ), - refCount: 1 - }); - } - callbacksCache.add(path, callback); + pollingInterval, + options + ), + refCount: 1 + }); + } + callbacksCache.add(path, callback); - return { - close: () => { - const watcher = Debug.checkDefined(cache.get(path)); - callbacksCache.remove(path, callback); - watcher.refCount--; - if (watcher.refCount) return; - cache.delete(path); - closeFileWatcherOf(watcher); - } - }; + return { + close: () => { + const watcher = Debug.checkDefined(cache.get(path)); + callbacksCache.remove(path, callback); + watcher.refCount--; + if (watcher.refCount) return; + cache.delete(path); + closeFileWatcherOf(watcher); + } }; + }; +} + +/** + * Returns true if file status changed + */ +/*@internal*/ +export function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean { + const oldTime = watchedFile.mtime.getTime(); + const newTime = modifiedTime.getTime(); + if (oldTime !== newTime) { + watchedFile.mtime = modifiedTime; + watchedFile.callback(watchedFile.fileName, getFileWatcherEventKind(oldTime, newTime)); + return true; } - /** - * Returns true if file status changed - */ - /*@internal*/ - export function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean { - const oldTime = watchedFile.mtime.getTime(); - const newTime = modifiedTime.getTime(); - if (oldTime !== newTime) { - watchedFile.mtime = modifiedTime; - watchedFile.callback(watchedFile.fileName, getFileWatcherEventKind(oldTime, newTime)); - return true; - } + return false; +} - return false; - } +/*@internal*/ +export function getFileWatcherEventKind(oldTime: number, newTime: number) { + return oldTime === 0 + ? FileWatcherEventKind.Created + : newTime === 0 + ? FileWatcherEventKind.Deleted + : FileWatcherEventKind.Changed; +} - /*@internal*/ - export function getFileWatcherEventKind(oldTime: number, newTime: number) { - return oldTime === 0 - ? FileWatcherEventKind.Created - : newTime === 0 - ? FileWatcherEventKind.Deleted - : FileWatcherEventKind.Changed; - } +/*@internal*/ +export const ignoredPaths = ["/node_modules/.", "/.git", "/.#"]; - /*@internal*/ - export const ignoredPaths = ["/node_modules/.", "/.git", "/.#"]; +/*@internal*/ +export let sysLog: (s: string) => void = noop; // eslint-disable-line prefer-const - /*@internal*/ - export let sysLog: (s: string) => void = noop; // eslint-disable-line prefer-const +/*@internal*/ +export function setSysLog(logger: typeof sysLog) { + sysLog = logger; +} - /*@internal*/ - export function setSysLog(logger: typeof sysLog) { - sysLog = logger; - } +/*@internal*/ +export interface RecursiveDirectoryWatcherHost { + watchDirectory: HostWatchDirectory; + useCaseSensitiveFileNames: boolean; + getCurrentDirectory: System["getCurrentDirectory"]; + getAccessibleSortedChildDirectories(path: string): readonly string[]; + directoryExists(dir: string): boolean; + realpath(s: string): string; + setTimeout: NonNullable; + clearTimeout: NonNullable; +} - /*@internal*/ - export interface RecursiveDirectoryWatcherHost { - watchDirectory: HostWatchDirectory; - useCaseSensitiveFileNames: boolean; - getCurrentDirectory: System["getCurrentDirectory"]; - getAccessibleSortedChildDirectories(path: string): readonly string[]; - directoryExists(dir: string): boolean; - realpath(s: string): string; - setTimeout: NonNullable; - clearTimeout: NonNullable; +/** + * Watch the directory recursively using host provided method to watch child directories + * that means if this is recursive watcher, watch the children directories as well + * (eg on OS that dont support recursive watch using fs.watch use fs.watchFile) + */ +/*@internal*/ +export function createDirectoryWatcherSupportingRecursive({ + watchDirectory, + useCaseSensitiveFileNames, + getCurrentDirectory, + getAccessibleSortedChildDirectories, + directoryExists, + realpath, + setTimeout, + clearTimeout +}: RecursiveDirectoryWatcherHost): HostWatchDirectory { + interface ChildDirectoryWatcher extends FileWatcher { + dirName: string; } + type ChildWatches = readonly ChildDirectoryWatcher[]; + interface HostDirectoryWatcher { + watcher: FileWatcher; + childWatches: ChildWatches; + refCount: number; + } + + const cache = new Map(); + const callbackCache = createMultiMap(); + const cacheToUpdateChildWatches = new Map(); + let timerToUpdateChildWatches: any; + + const filePathComparer = getStringComparer(!useCaseSensitiveFileNames); + const toCanonicalFilePath = createGetCanonicalFileName(useCaseSensitiveFileNames); + + return (dirName, callback, recursive, options) => recursive ? + createDirectoryWatcher(dirName, options, callback) : + watchDirectory(dirName, callback, recursive, options); /** - * Watch the directory recursively using host provided method to watch child directories - * that means if this is recursive watcher, watch the children directories as well - * (eg on OS that dont support recursive watch using fs.watch use fs.watchFile) + * Create the directory watcher for the dirPath. */ - /*@internal*/ - export function createDirectoryWatcherSupportingRecursive({ - watchDirectory, - useCaseSensitiveFileNames, - getCurrentDirectory, - getAccessibleSortedChildDirectories, - directoryExists, - realpath, - setTimeout, - clearTimeout - }: RecursiveDirectoryWatcherHost): HostWatchDirectory { - interface ChildDirectoryWatcher extends FileWatcher { - dirName: string; + function createDirectoryWatcher(dirName: string, options: WatchOptions | undefined, callback?: DirectoryWatcherCallback): ChildDirectoryWatcher { + const dirPath = toCanonicalFilePath(dirName) as Path; + let directoryWatcher = cache.get(dirPath); + if (directoryWatcher) { + directoryWatcher.refCount++; } - type ChildWatches = readonly ChildDirectoryWatcher[]; - interface HostDirectoryWatcher { - watcher: FileWatcher; - childWatches: ChildWatches; - refCount: number; - } - - const cache = new Map(); - const callbackCache = createMultiMap(); - const cacheToUpdateChildWatches = new Map(); - let timerToUpdateChildWatches: any; + else { + directoryWatcher = { + watcher: watchDirectory(dirName, fileName => { + if (isIgnoredPath(fileName, options)) return; - const filePathComparer = getStringComparer(!useCaseSensitiveFileNames); - const toCanonicalFilePath = createGetCanonicalFileName(useCaseSensitiveFileNames); + if (options?.synchronousWatchDirectory) { + // Call the actual callback + invokeCallbacks(dirPath, fileName); - return (dirName, callback, recursive, options) => recursive ? - createDirectoryWatcher(dirName, options, callback) : - watchDirectory(dirName, callback, recursive, options); + // Iterate through existing children and update the watches if needed + updateChildWatches(dirName, dirPath, options); + } + else { + nonSyncUpdateChildWatches(dirName, dirPath, fileName, options); + } + }, /*recursive*/ false, options), + refCount: 1, + childWatches: emptyArray + }; + cache.set(dirPath, directoryWatcher); + updateChildWatches(dirName, dirPath, options); + } - /** - * Create the directory watcher for the dirPath. - */ - function createDirectoryWatcher(dirName: string, options: WatchOptions | undefined, callback?: DirectoryWatcherCallback): ChildDirectoryWatcher { - const dirPath = toCanonicalFilePath(dirName) as Path; - let directoryWatcher = cache.get(dirPath); - if (directoryWatcher) { - directoryWatcher.refCount++; - } - else { - directoryWatcher = { - watcher: watchDirectory(dirName, fileName => { - if (isIgnoredPath(fileName, options)) return; + const callbackToAdd = callback && { dirName, callback }; + if (callbackToAdd) { + callbackCache.add(dirPath, callbackToAdd); + } - if (options?.synchronousWatchDirectory) { - // Call the actual callback - invokeCallbacks(dirPath, fileName); + return { + dirName, + close: () => { + const directoryWatcher = Debug.checkDefined(cache.get(dirPath)); + if (callbackToAdd) callbackCache.remove(dirPath, callbackToAdd); + directoryWatcher.refCount--; - // Iterate through existing children and update the watches if needed - updateChildWatches(dirName, dirPath, options); - } - else { - nonSyncUpdateChildWatches(dirName, dirPath, fileName, options); - } - }, /*recursive*/ false, options), - refCount: 1, - childWatches: emptyArray - }; - cache.set(dirPath, directoryWatcher); - updateChildWatches(dirName, dirPath, options); - } + if (directoryWatcher.refCount) return; - const callbackToAdd = callback && { dirName, callback }; - if (callbackToAdd) { - callbackCache.add(dirPath, callbackToAdd); + cache.delete(dirPath); + closeFileWatcherOf(directoryWatcher); + directoryWatcher.childWatches.forEach(closeFileWatcher); } + }; + } - return { - dirName, - close: () => { - const directoryWatcher = Debug.checkDefined(cache.get(dirPath)); - if (callbackToAdd) callbackCache.remove(dirPath, callbackToAdd); - directoryWatcher.refCount--; - - if (directoryWatcher.refCount) return; - - cache.delete(dirPath); - closeFileWatcherOf(directoryWatcher); - directoryWatcher.childWatches.forEach(closeFileWatcher); - } - }; + type InvokeMap = ESMap; + function invokeCallbacks(dirPath: Path, fileName: string): void; + function invokeCallbacks(dirPath: Path, invokeMap: InvokeMap, fileNames: string[] | undefined): void; + function invokeCallbacks(dirPath: Path, fileNameOrInvokeMap: string | InvokeMap, fileNames?: string[]) { + let fileName: string | undefined; + let invokeMap: InvokeMap | undefined; + if (isString(fileNameOrInvokeMap)) { + fileName = fileNameOrInvokeMap; } - - type InvokeMap = ESMap; - function invokeCallbacks(dirPath: Path, fileName: string): void; - function invokeCallbacks(dirPath: Path, invokeMap: InvokeMap, fileNames: string[] | undefined): void; - function invokeCallbacks(dirPath: Path, fileNameOrInvokeMap: string | InvokeMap, fileNames?: string[]) { - let fileName: string | undefined; - let invokeMap: InvokeMap | undefined; - if (isString(fileNameOrInvokeMap)) { - fileName = fileNameOrInvokeMap; - } - else { - invokeMap = fileNameOrInvokeMap; - } - // Call the actual callback - callbackCache.forEach((callbacks, rootDirName) => { - if (invokeMap && invokeMap.get(rootDirName) === true) return; - if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) { - if (invokeMap) { - if (fileNames) { - const existing = invokeMap.get(rootDirName); - if (existing) { - (existing as string[]).push(...fileNames); - } - else { - invokeMap.set(rootDirName, fileNames.slice()); - } + else { + invokeMap = fileNameOrInvokeMap; + } + // Call the actual callback + callbackCache.forEach((callbacks, rootDirName) => { + if (invokeMap && invokeMap.get(rootDirName) === true) return; + if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) { + if (invokeMap) { + if (fileNames) { + const existing = invokeMap.get(rootDirName); + if (existing) { + (existing as string[]).push(...fileNames); } else { - invokeMap.set(rootDirName, true); + invokeMap.set(rootDirName, fileNames.slice()); } } else { - callbacks.forEach(({ callback }) => callback(fileName!)); + invokeMap.set(rootDirName, true); } } - }); - } - - function nonSyncUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { - // Iterate through existing children and update the watches if needed - const parentWatcher = cache.get(dirPath); - if (parentWatcher && directoryExists(dirName)) { - // Schedule the update and postpone invoke for callbacks - scheduleUpdateChildWatches(dirName, dirPath, fileName, options); - return; + else { + callbacks.forEach(({ callback }) => callback(fileName!)); + } } + }); + } - // Call the actual callbacks and remove child watches - invokeCallbacks(dirPath, fileName); - removeChildWatches(parentWatcher); + function nonSyncUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { + // Iterate through existing children and update the watches if needed + const parentWatcher = cache.get(dirPath); + if (parentWatcher && directoryExists(dirName)) { + // Schedule the update and postpone invoke for callbacks + scheduleUpdateChildWatches(dirName, dirPath, fileName, options); + return; } - function scheduleUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { - const existing = cacheToUpdateChildWatches.get(dirPath); - if (existing) { - existing.fileNames.push(fileName); - } - else { - cacheToUpdateChildWatches.set(dirPath, { dirName, options, fileNames: [fileName] }); - } - if (timerToUpdateChildWatches) { - clearTimeout(timerToUpdateChildWatches); - timerToUpdateChildWatches = undefined; - } - timerToUpdateChildWatches = setTimeout(onTimerToUpdateChildWatches, 1000); - } + // Call the actual callbacks and remove child watches + invokeCallbacks(dirPath, fileName); + removeChildWatches(parentWatcher); + } - function onTimerToUpdateChildWatches() { + function scheduleUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { + const existing = cacheToUpdateChildWatches.get(dirPath); + if (existing) { + existing.fileNames.push(fileName); + } + else { + cacheToUpdateChildWatches.set(dirPath, { dirName, options, fileNames: [fileName] }); + } + if (timerToUpdateChildWatches) { + clearTimeout(timerToUpdateChildWatches); timerToUpdateChildWatches = undefined; - sysLog(`sysLog:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size}`); - const start = timestamp(); - const invokeMap = new Map(); - - while (!timerToUpdateChildWatches && cacheToUpdateChildWatches.size) { - const result = cacheToUpdateChildWatches.entries().next(); - Debug.assert(!result.done); - const { value: [dirPath, { dirName, options, fileNames }] } = result; - cacheToUpdateChildWatches.delete(dirPath); - // Because the child refresh is fresh, we would need to invalidate whole root directory being watched - // to ensure that all the changes are reflected at this time - const hasChanges = updateChildWatches(dirName, dirPath, options); - invokeCallbacks(dirPath, invokeMap, hasChanges ? undefined : fileNames); - } - - sysLog(`sysLog:: invokingWatchers:: Elapsed:: ${timestamp() - start}ms:: ${cacheToUpdateChildWatches.size}`); - callbackCache.forEach((callbacks, rootDirName) => { - const existing = invokeMap.get(rootDirName); - if (existing) { - callbacks.forEach(({ callback, dirName }) => { - if (isArray(existing)) { - existing.forEach(callback); - } - else { - callback(dirName); - } - }); - } - }); - - const elapsed = timestamp() - start; - sysLog(`sysLog:: Elapsed:: ${elapsed}ms:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size} ${timerToUpdateChildWatches}`); } + timerToUpdateChildWatches = setTimeout(onTimerToUpdateChildWatches, 1000); + } - function removeChildWatches(parentWatcher: HostDirectoryWatcher | undefined) { - if (!parentWatcher) return; - const existingChildWatches = parentWatcher.childWatches; - parentWatcher.childWatches = emptyArray; - for (const childWatcher of existingChildWatches) { - childWatcher.close(); - removeChildWatches(cache.get(toCanonicalFilePath(childWatcher.dirName))); - } + function onTimerToUpdateChildWatches() { + timerToUpdateChildWatches = undefined; + sysLog(`sysLog:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size}`); + const start = timestamp(); + const invokeMap = new Map(); + + while (!timerToUpdateChildWatches && cacheToUpdateChildWatches.size) { + const result = cacheToUpdateChildWatches.entries().next(); + Debug.assert(!result.done); + const { value: [dirPath, { dirName, options, fileNames }] } = result; + cacheToUpdateChildWatches.delete(dirPath); + // Because the child refresh is fresh, we would need to invalidate whole root directory being watched + // to ensure that all the changes are reflected at this time + const hasChanges = updateChildWatches(dirName, dirPath, options); + invokeCallbacks(dirPath, invokeMap, hasChanges ? undefined : fileNames); } - function updateChildWatches(parentDir: string, parentDirPath: Path, options: WatchOptions | undefined) { - // Iterate through existing children and update the watches if needed - const parentWatcher = cache.get(parentDirPath); - if (!parentWatcher) return false; - let newChildWatches: ChildDirectoryWatcher[] | undefined; - const hasChanges = enumerateInsertsAndDeletes( - directoryExists(parentDir) ? mapDefined(getAccessibleSortedChildDirectories(parentDir), child => { - const childFullName = getNormalizedAbsolutePath(child, parentDir); - // Filter our the symbolic link directories since those arent included in recursive watch - // which is same behaviour when recursive: true is passed to fs.watch - return !isIgnoredPath(childFullName, options) && filePathComparer(childFullName, normalizePath(realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined; - }) : emptyArray, - parentWatcher.childWatches, - (child, childWatcher) => filePathComparer(child, childWatcher.dirName), - createAndAddChildDirectoryWatcher, - closeFileWatcher, - addChildDirectoryWatcher - ); - parentWatcher.childWatches = newChildWatches || emptyArray; - return hasChanges; - - /** - * Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list - */ - function createAndAddChildDirectoryWatcher(childName: string) { - const result = createDirectoryWatcher(childName, options); - addChildDirectoryWatcher(result); + sysLog(`sysLog:: invokingWatchers:: Elapsed:: ${timestamp() - start}ms:: ${cacheToUpdateChildWatches.size}`); + callbackCache.forEach((callbacks, rootDirName) => { + const existing = invokeMap.get(rootDirName); + if (existing) { + callbacks.forEach(({ callback, dirName }) => { + if (isArray(existing)) { + existing.forEach(callback); + } + else { + callback(dirName); + } + }); } + }); - /** - * Add child directory watcher to the new ChildDirectoryWatcher list - */ - function addChildDirectoryWatcher(childWatcher: ChildDirectoryWatcher) { - (newChildWatches || (newChildWatches = [])).push(childWatcher); - } + const elapsed = timestamp() - start; + sysLog(`sysLog:: Elapsed:: ${elapsed}ms:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size} ${timerToUpdateChildWatches}`); + } + + function removeChildWatches(parentWatcher: HostDirectoryWatcher | undefined) { + if (!parentWatcher) return; + const existingChildWatches = parentWatcher.childWatches; + parentWatcher.childWatches = emptyArray; + for (const childWatcher of existingChildWatches) { + childWatcher.close(); + removeChildWatches(cache.get(toCanonicalFilePath(childWatcher.dirName))); } + } + + function updateChildWatches(parentDir: string, parentDirPath: Path, options: WatchOptions | undefined) { + // Iterate through existing children and update the watches if needed + const parentWatcher = cache.get(parentDirPath); + if (!parentWatcher) return false; + let newChildWatches: ChildDirectoryWatcher[] | undefined; + const hasChanges = enumerateInsertsAndDeletes( + directoryExists(parentDir) ? mapDefined(getAccessibleSortedChildDirectories(parentDir), child => { + const childFullName = getNormalizedAbsolutePath(child, parentDir); + // Filter our the symbolic link directories since those arent included in recursive watch + // which is same behaviour when recursive: true is passed to fs.watch + return !isIgnoredPath(childFullName, options) && filePathComparer(childFullName, normalizePath(realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined; + }) : emptyArray, + parentWatcher.childWatches, + (child, childWatcher) => filePathComparer(child, childWatcher.dirName), + createAndAddChildDirectoryWatcher, + closeFileWatcher, + addChildDirectoryWatcher + ); + parentWatcher.childWatches = newChildWatches || emptyArray; + return hasChanges; - function isIgnoredPath(path: string, options: WatchOptions | undefined) { - return some(ignoredPaths, searchPath => isInPath(path, searchPath)) || - isIgnoredByWatchOptions(path, options, useCaseSensitiveFileNames, getCurrentDirectory); + /** + * Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list + */ + function createAndAddChildDirectoryWatcher(childName: string) { + const result = createDirectoryWatcher(childName, options); + addChildDirectoryWatcher(result); } - function isInPath(path: string, searchPath: string) { - if (stringContains(path, searchPath)) return true; - if (useCaseSensitiveFileNames) return false; - return stringContains(toCanonicalFilePath(path), searchPath); + /** + * Add child directory watcher to the new ChildDirectoryWatcher list + */ + function addChildDirectoryWatcher(childWatcher: ChildDirectoryWatcher) { + (newChildWatches || (newChildWatches = [])).push(childWatcher); } } - /*@internal*/ - export type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string | undefined) => void; - /*@internal*/ - export type FsWatch = (fileOrDirectory: string, entryKind: FileSystemEntryKind, callback: FsWatchCallback, recursive: boolean, fallbackPollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined) => FileWatcher; - - /*@internal*/ - export const enum FileSystemEntryKind { - File, - Directory, + function isIgnoredPath(path: string, options: WatchOptions | undefined) { + return some(ignoredPaths, searchPath => isInPath(path, searchPath)) || + isIgnoredByWatchOptions(path, options, useCaseSensitiveFileNames, getCurrentDirectory); } - /*@internal*/ - export function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback { - return (_fileName, eventKind) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", ""); + function isInPath(path: string, searchPath: string) { + if (stringContains(path, searchPath)) return true; + if (useCaseSensitiveFileNames) return false; + return stringContains(toCanonicalFilePath(path), searchPath); } +} - function createFsWatchCallbackForFileWatcherCallback( - fileName: string, - callback: FileWatcherCallback, - fileExists: System["fileExists"] - ): FsWatchCallback { - return eventName => { - if (eventName === "rename") { - callback(fileName, fileExists(fileName) ? FileWatcherEventKind.Created : FileWatcherEventKind.Deleted); - } - else { - // Change - callback(fileName, FileWatcherEventKind.Changed); - } - }; - } +/*@internal*/ +export type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string | undefined) => void; +/*@internal*/ +export type FsWatch = (fileOrDirectory: string, entryKind: FileSystemEntryKind, callback: FsWatchCallback, recursive: boolean, fallbackPollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined) => FileWatcher; - function isIgnoredByWatchOptions( - pathToCheck: string, - options: WatchOptions | undefined, - useCaseSensitiveFileNames: boolean, - getCurrentDirectory: System["getCurrentDirectory"], - ) { - return (options?.excludeDirectories || options?.excludeFiles) && ( - matchesExclude(pathToCheck, options?.excludeFiles, useCaseSensitiveFileNames, getCurrentDirectory()) || - matchesExclude(pathToCheck, options?.excludeDirectories, useCaseSensitiveFileNames, getCurrentDirectory()) - ); - } +/*@internal*/ +export const enum FileSystemEntryKind { + File, + Directory, +} - function createFsWatchCallbackForDirectoryWatcherCallback( - directoryName: string, - callback: DirectoryWatcherCallback, - options: WatchOptions | undefined, - useCaseSensitiveFileNames: boolean, - getCurrentDirectory: System["getCurrentDirectory"], - ): FsWatchCallback { - return (eventName, relativeFileName) => { - // In watchDirectory we only care about adding and removing files (when event name is - // "rename"); changes made within files are handled by corresponding fileWatchers (when - // event name is "change") - if (eventName === "rename") { - // When deleting a file, the passed baseFileName is null - const fileName = !relativeFileName ? directoryName : normalizePath(combinePaths(directoryName, relativeFileName)); - if (!relativeFileName || !isIgnoredByWatchOptions(fileName, options, useCaseSensitiveFileNames, getCurrentDirectory)) { - callback(fileName); - } - } - }; - } +/*@internal*/ +export function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback { + return (_fileName, eventKind) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", ""); +} - /*@internal*/ - export interface CreateSystemWatchFunctions { - // Polling watch file - pollingWatchFile: HostWatchFile; - // For dynamic polling watch file - getModifiedTime: NonNullable; - setTimeout: NonNullable; - clearTimeout: NonNullable; - // For fs events : - fsWatch: FsWatch; - fileExists: System["fileExists"]; - useCaseSensitiveFileNames: boolean; - getCurrentDirectory: System["getCurrentDirectory"]; - fsSupportsRecursiveFsWatch: boolean; - directoryExists: System["directoryExists"]; - getAccessibleSortedChildDirectories(path: string): readonly string[]; - realpath(s: string): string; - // For backward compatibility environment variables - tscWatchFile: string | undefined; - useNonPollingWatchers?: boolean; - tscWatchDirectory: string | undefined; - defaultWatchFileKind: System["defaultWatchFileKind"]; - } +function createFsWatchCallbackForFileWatcherCallback( + fileName: string, + callback: FileWatcherCallback, + fileExists: System["fileExists"] +): FsWatchCallback { + return eventName => { + if (eventName === "rename") { + callback(fileName, fileExists(fileName) ? FileWatcherEventKind.Created : FileWatcherEventKind.Deleted); + } + else { + // Change + callback(fileName, FileWatcherEventKind.Changed); + } + }; +} - /*@internal*/ - export function createSystemWatchFunctions({ - pollingWatchFile, - getModifiedTime, - setTimeout, - clearTimeout, - fsWatch, - fileExists, - useCaseSensitiveFileNames, - getCurrentDirectory, - fsSupportsRecursiveFsWatch, - directoryExists, - getAccessibleSortedChildDirectories, - realpath, - tscWatchFile, - useNonPollingWatchers, - tscWatchDirectory, - defaultWatchFileKind, - }: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } { - let dynamicPollingWatchFile: HostWatchFile | undefined; - let fixedChunkSizePollingWatchFile: HostWatchFile | undefined; - let nonPollingWatchFile: HostWatchFile | undefined; - let hostRecursiveDirectoryWatcher: HostWatchDirectory | undefined; - return { - watchFile, - watchDirectory - }; +function isIgnoredByWatchOptions( + pathToCheck: string, + options: WatchOptions | undefined, + useCaseSensitiveFileNames: boolean, + getCurrentDirectory: System["getCurrentDirectory"], +) { + return (options?.excludeDirectories || options?.excludeFiles) && ( + matchesExclude(pathToCheck, options?.excludeFiles, useCaseSensitiveFileNames, getCurrentDirectory()) || + matchesExclude(pathToCheck, options?.excludeDirectories, useCaseSensitiveFileNames, getCurrentDirectory()) + ); +} - function watchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined): FileWatcher { - options = updateOptionsForWatchFile(options, useNonPollingWatchers); - const watchFileKind = Debug.checkDefined(options.watchFile); - switch (watchFileKind) { - case WatchFileKind.FixedPollingInterval: - return pollingWatchFile(fileName, callback, PollingInterval.Low, /*options*/ undefined); - case WatchFileKind.PriorityPollingInterval: - return pollingWatchFile(fileName, callback, pollingInterval, /*options*/ undefined); - case WatchFileKind.DynamicPriorityPolling: - return ensureDynamicPollingWatchFile()(fileName, callback, pollingInterval, /*options*/ undefined); - case WatchFileKind.FixedChunkSizePolling: - return ensureFixedChunkSizePollingWatchFile()(fileName, callback, /* pollingInterval */ undefined!, /*options*/ undefined); - case WatchFileKind.UseFsEvents: - return fsWatch( - fileName, - FileSystemEntryKind.File, - createFsWatchCallbackForFileWatcherCallback(fileName, callback, fileExists), - /*recursive*/ false, - pollingInterval, - getFallbackOptions(options) - ); - case WatchFileKind.UseFsEventsOnParentDirectory: - if (!nonPollingWatchFile) { - nonPollingWatchFile = createUseFsEventsOnParentDirectoryWatchFile(fsWatch, useCaseSensitiveFileNames); - } - return nonPollingWatchFile(fileName, callback, pollingInterval, getFallbackOptions(options)); - default: - Debug.assertNever(watchFileKind); +function createFsWatchCallbackForDirectoryWatcherCallback( + directoryName: string, + callback: DirectoryWatcherCallback, + options: WatchOptions | undefined, + useCaseSensitiveFileNames: boolean, + getCurrentDirectory: System["getCurrentDirectory"], +): FsWatchCallback { + return (eventName, relativeFileName) => { + // In watchDirectory we only care about adding and removing files (when event name is + // "rename"); changes made within files are handled by corresponding fileWatchers (when + // event name is "change") + if (eventName === "rename") { + // When deleting a file, the passed baseFileName is null + const fileName = !relativeFileName ? directoryName : normalizePath(combinePaths(directoryName, relativeFileName)); + if (!relativeFileName || !isIgnoredByWatchOptions(fileName, options, useCaseSensitiveFileNames, getCurrentDirectory)) { + callback(fileName); } } + }; +} - function ensureDynamicPollingWatchFile() { - return dynamicPollingWatchFile ||= createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout }); - } +/*@internal*/ +export interface CreateSystemWatchFunctions { + // Polling watch file + pollingWatchFile: HostWatchFile; + // For dynamic polling watch file + getModifiedTime: NonNullable; + setTimeout: NonNullable; + clearTimeout: NonNullable; + // For fs events : + fsWatch: FsWatch; + fileExists: System["fileExists"]; + useCaseSensitiveFileNames: boolean; + getCurrentDirectory: System["getCurrentDirectory"]; + fsSupportsRecursiveFsWatch: boolean; + directoryExists: System["directoryExists"]; + getAccessibleSortedChildDirectories(path: string): readonly string[]; + realpath(s: string): string; + // For backward compatibility environment variables + tscWatchFile: string | undefined; + useNonPollingWatchers?: boolean; + tscWatchDirectory: string | undefined; + defaultWatchFileKind: System["defaultWatchFileKind"]; +} - function ensureFixedChunkSizePollingWatchFile() { - return fixedChunkSizePollingWatchFile ||= createFixedChunkSizePollingWatchFile({ getModifiedTime, setTimeout }); +/*@internal*/ +export function createSystemWatchFunctions({ + pollingWatchFile, + getModifiedTime, + setTimeout, + clearTimeout, + fsWatch, + fileExists, + useCaseSensitiveFileNames, + getCurrentDirectory, + fsSupportsRecursiveFsWatch, + directoryExists, + getAccessibleSortedChildDirectories, + realpath, + tscWatchFile, + useNonPollingWatchers, + tscWatchDirectory, + defaultWatchFileKind, +}: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } { + let dynamicPollingWatchFile: HostWatchFile | undefined; + let fixedChunkSizePollingWatchFile: HostWatchFile | undefined; + let nonPollingWatchFile: HostWatchFile | undefined; + let hostRecursiveDirectoryWatcher: HostWatchDirectory | undefined; + return { + watchFile, + watchDirectory + }; + + function watchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined): FileWatcher { + options = updateOptionsForWatchFile(options, useNonPollingWatchers); + const watchFileKind = Debug.checkDefined(options.watchFile); + switch (watchFileKind) { + case WatchFileKind.FixedPollingInterval: + return pollingWatchFile(fileName, callback, PollingInterval.Low, /*options*/ undefined); + case WatchFileKind.PriorityPollingInterval: + return pollingWatchFile(fileName, callback, pollingInterval, /*options*/ undefined); + case WatchFileKind.DynamicPriorityPolling: + return ensureDynamicPollingWatchFile()(fileName, callback, pollingInterval, /*options*/ undefined); + case WatchFileKind.FixedChunkSizePolling: + return ensureFixedChunkSizePollingWatchFile()(fileName, callback, /* pollingInterval */ undefined!, /*options*/ undefined); + case WatchFileKind.UseFsEvents: + return fsWatch( + fileName, + FileSystemEntryKind.File, + createFsWatchCallbackForFileWatcherCallback(fileName, callback, fileExists), + /*recursive*/ false, + pollingInterval, + getFallbackOptions(options) + ); + case WatchFileKind.UseFsEventsOnParentDirectory: + if (!nonPollingWatchFile) { + nonPollingWatchFile = createUseFsEventsOnParentDirectoryWatchFile(fsWatch, useCaseSensitiveFileNames); + } + return nonPollingWatchFile(fileName, callback, pollingInterval, getFallbackOptions(options)); + default: + Debug.assertNever(watchFileKind); } + } + + function ensureDynamicPollingWatchFile() { + return dynamicPollingWatchFile ||= createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout }); + } + + function ensureFixedChunkSizePollingWatchFile() { + return fixedChunkSizePollingWatchFile ||= createFixedChunkSizePollingWatchFile({ getModifiedTime, setTimeout }); + } - function updateOptionsForWatchFile(options: WatchOptions | undefined, useNonPollingWatchers?: boolean): WatchOptions { - if (options && options.watchFile !== undefined) return options; - switch (tscWatchFile) { - case "PriorityPollingInterval": - // Use polling interval based on priority when create watch using host.watchFile - return { watchFile: WatchFileKind.PriorityPollingInterval }; - case "DynamicPriorityPolling": - // Use polling interval but change the interval depending on file changes and their default polling interval - return { watchFile: WatchFileKind.DynamicPriorityPolling }; - case "UseFsEvents": + function updateOptionsForWatchFile(options: WatchOptions | undefined, useNonPollingWatchers?: boolean): WatchOptions { + if (options && options.watchFile !== undefined) return options; + switch (tscWatchFile) { + case "PriorityPollingInterval": + // Use polling interval based on priority when create watch using host.watchFile + return { watchFile: WatchFileKind.PriorityPollingInterval }; + case "DynamicPriorityPolling": + // Use polling interval but change the interval depending on file changes and their default polling interval + return { watchFile: WatchFileKind.DynamicPriorityPolling }; + case "UseFsEvents": + // Use notifications from FS to watch with falling back to fs.watchFile + return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.PriorityInterval, options); + case "UseFsEventsWithFallbackDynamicPolling": + // Use notifications from FS to watch with falling back to dynamic watch file + return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.DynamicPriority, options); + case "UseFsEventsOnParentDirectory": + useNonPollingWatchers = true; + // fall through + default: + return useNonPollingWatchers ? // Use notifications from FS to watch with falling back to fs.watchFile - return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.PriorityInterval, options); - case "UseFsEventsWithFallbackDynamicPolling": - // Use notifications from FS to watch with falling back to dynamic watch file - return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.DynamicPriority, options); - case "UseFsEventsOnParentDirectory": - useNonPollingWatchers = true; - // fall through - default: - return useNonPollingWatchers ? - // Use notifications from FS to watch with falling back to fs.watchFile - generateWatchFileOptions(WatchFileKind.UseFsEventsOnParentDirectory, PollingWatchKind.PriorityInterval, options) : - // Default to do not use fixed polling interval - { watchFile: defaultWatchFileKind?.() || WatchFileKind.FixedPollingInterval }; - } + generateWatchFileOptions(WatchFileKind.UseFsEventsOnParentDirectory, PollingWatchKind.PriorityInterval, options) : + // Default to do not use fixed polling interval + { watchFile: defaultWatchFileKind?.() || WatchFileKind.FixedPollingInterval }; } + } - function generateWatchFileOptions( - watchFile: WatchFileKind, - fallbackPolling: PollingWatchKind, - options: WatchOptions | undefined - ): WatchOptions { - const defaultFallbackPolling = options?.fallbackPolling; - return { - watchFile, - fallbackPolling: defaultFallbackPolling === undefined ? - fallbackPolling : - defaultFallbackPolling - }; + function generateWatchFileOptions( + watchFile: WatchFileKind, + fallbackPolling: PollingWatchKind, + options: WatchOptions | undefined + ): WatchOptions { + const defaultFallbackPolling = options?.fallbackPolling; + return { + watchFile, + fallbackPolling: defaultFallbackPolling === undefined ? + fallbackPolling : + defaultFallbackPolling + }; + } + + function watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { + if (fsSupportsRecursiveFsWatch) { + return fsWatch( + directoryName, + FileSystemEntryKind.Directory, + createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback, options, useCaseSensitiveFileNames, getCurrentDirectory), + recursive, + PollingInterval.Medium, + getFallbackOptions(options) + ); } - function watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { - if (fsSupportsRecursiveFsWatch) { + if (!hostRecursiveDirectoryWatcher) { + hostRecursiveDirectoryWatcher = createDirectoryWatcherSupportingRecursive({ + useCaseSensitiveFileNames, + getCurrentDirectory, + directoryExists, + getAccessibleSortedChildDirectories, + watchDirectory: nonRecursiveWatchDirectory, + realpath, + setTimeout, + clearTimeout + }); + } + return hostRecursiveDirectoryWatcher(directoryName, callback, recursive, options); + } + + function nonRecursiveWatchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { + Debug.assert(!recursive); + const watchDirectoryOptions = updateOptionsForWatchDirectory(options); + const watchDirectoryKind = Debug.checkDefined(watchDirectoryOptions.watchDirectory); + switch (watchDirectoryKind) { + case WatchDirectoryKind.FixedPollingInterval: + return pollingWatchFile( + directoryName, + () => callback(directoryName), + PollingInterval.Medium, + /*options*/ undefined + ); + case WatchDirectoryKind.DynamicPriorityPolling: + return ensureDynamicPollingWatchFile()( + directoryName, + () => callback(directoryName), + PollingInterval.Medium, + /*options*/ undefined + ); + case WatchDirectoryKind.FixedChunkSizePolling: + return ensureFixedChunkSizePollingWatchFile()( + directoryName, + () => callback(directoryName), + /* pollingInterval */ undefined!, + /*options*/ undefined + ); + case WatchDirectoryKind.UseFsEvents: return fsWatch( directoryName, FileSystemEntryKind.Directory, createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback, options, useCaseSensitiveFileNames, getCurrentDirectory), recursive, PollingInterval.Medium, - getFallbackOptions(options) + getFallbackOptions(watchDirectoryOptions) ); - } - - if (!hostRecursiveDirectoryWatcher) { - hostRecursiveDirectoryWatcher = createDirectoryWatcherSupportingRecursive({ - useCaseSensitiveFileNames, - getCurrentDirectory, - directoryExists, - getAccessibleSortedChildDirectories, - watchDirectory: nonRecursiveWatchDirectory, - realpath, - setTimeout, - clearTimeout - }); - } - return hostRecursiveDirectoryWatcher(directoryName, callback, recursive, options); - } - - function nonRecursiveWatchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { - Debug.assert(!recursive); - const watchDirectoryOptions = updateOptionsForWatchDirectory(options); - const watchDirectoryKind = Debug.checkDefined(watchDirectoryOptions.watchDirectory); - switch (watchDirectoryKind) { - case WatchDirectoryKind.FixedPollingInterval: - return pollingWatchFile( - directoryName, - () => callback(directoryName), - PollingInterval.Medium, - /*options*/ undefined - ); - case WatchDirectoryKind.DynamicPriorityPolling: - return ensureDynamicPollingWatchFile()( - directoryName, - () => callback(directoryName), - PollingInterval.Medium, - /*options*/ undefined - ); - case WatchDirectoryKind.FixedChunkSizePolling: - return ensureFixedChunkSizePollingWatchFile()( - directoryName, - () => callback(directoryName), - /* pollingInterval */ undefined!, - /*options*/ undefined - ); - case WatchDirectoryKind.UseFsEvents: - return fsWatch( - directoryName, - FileSystemEntryKind.Directory, - createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback, options, useCaseSensitiveFileNames, getCurrentDirectory), - recursive, - PollingInterval.Medium, - getFallbackOptions(watchDirectoryOptions) - ); - default: - Debug.assertNever(watchDirectoryKind); - } + default: + Debug.assertNever(watchDirectoryKind); } + } - function updateOptionsForWatchDirectory(options: WatchOptions | undefined): WatchOptions { - if (options && options.watchDirectory !== undefined) return options; - switch (tscWatchDirectory) { - case "RecursiveDirectoryUsingFsWatchFile": - // Use polling interval based on priority when create watch using host.watchFile - return { watchDirectory: WatchDirectoryKind.FixedPollingInterval }; - case "RecursiveDirectoryUsingDynamicPriorityPolling": - // Use polling interval but change the interval depending on file changes and their default polling interval - return { watchDirectory: WatchDirectoryKind.DynamicPriorityPolling }; - default: - const defaultFallbackPolling = options?.fallbackPolling; - return { - watchDirectory: WatchDirectoryKind.UseFsEvents, - fallbackPolling: defaultFallbackPolling !== undefined ? - defaultFallbackPolling : - undefined - }; - } + function updateOptionsForWatchDirectory(options: WatchOptions | undefined): WatchOptions { + if (options && options.watchDirectory !== undefined) return options; + switch (tscWatchDirectory) { + case "RecursiveDirectoryUsingFsWatchFile": + // Use polling interval based on priority when create watch using host.watchFile + return { watchDirectory: WatchDirectoryKind.FixedPollingInterval }; + case "RecursiveDirectoryUsingDynamicPriorityPolling": + // Use polling interval but change the interval depending on file changes and their default polling interval + return { watchDirectory: WatchDirectoryKind.DynamicPriorityPolling }; + default: + const defaultFallbackPolling = options?.fallbackPolling; + return { + watchDirectory: WatchDirectoryKind.UseFsEvents, + fallbackPolling: defaultFallbackPolling !== undefined ? + defaultFallbackPolling : + undefined + }; } } +} + +/** + * patch writefile to create folder before writing the file + */ +/*@internal*/ +export function patchWriteFileEnsuringDirectory(sys: System) { + // patch writefile to create folder before writing the file + const originalWriteFile = sys.writeFile; + sys.writeFile = (path, data, writeBom) => + writeFileEnsuringDirectories( + path, + data, + !!writeBom, + (path, data, writeByteOrderMark) => originalWriteFile.call(sys, path, data, writeByteOrderMark), + path => sys.createDirectory(path), + path => sys.directoryExists(path)); +} + +/*@internal*/ +export type BufferEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex"; + +/*@internal*/ +interface NodeBuffer extends Uint8Array { + constructor: any; + write(str: string, encoding?: BufferEncoding): number; + write(str: string, offset: number, encoding?: BufferEncoding): number; + write(str: string, offset: number, length: number, encoding?: BufferEncoding): number; + toString(encoding?: string, start?: number, end?: number): string; + toJSON(): { type: "Buffer"; data: number[] }; + equals(otherBuffer: Uint8Array): boolean; + compare( + otherBuffer: Uint8Array, + targetStart?: number, + targetEnd?: number, + sourceStart?: number, + sourceEnd?: number + ): number; + copy(targetBuffer: Uint8Array, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; + slice(begin?: number, end?: number): Buffer; + subarray(begin?: number, end?: number): Buffer; + writeUIntLE(value: number, offset: number, byteLength: number): number; + writeUIntBE(value: number, offset: number, byteLength: number): number; + writeIntLE(value: number, offset: number, byteLength: number): number; + writeIntBE(value: number, offset: number, byteLength: number): number; + readUIntLE(offset: number, byteLength: number): number; + readUIntBE(offset: number, byteLength: number): number; + readIntLE(offset: number, byteLength: number): number; + readIntBE(offset: number, byteLength: number): number; + readUInt8(offset: number): number; + readUInt16LE(offset: number): number; + readUInt16BE(offset: number): number; + readUInt32LE(offset: number): number; + readUInt32BE(offset: number): number; + readInt8(offset: number): number; + readInt16LE(offset: number): number; + readInt16BE(offset: number): number; + readInt32LE(offset: number): number; + readInt32BE(offset: number): number; + readFloatLE(offset: number): number; + readFloatBE(offset: number): number; + readDoubleLE(offset: number): number; + readDoubleBE(offset: number): number; + reverse(): this; + swap16(): Buffer; + swap32(): Buffer; + swap64(): Buffer; + writeUInt8(value: number, offset: number): number; + writeUInt16LE(value: number, offset: number): number; + writeUInt16BE(value: number, offset: number): number; + writeUInt32LE(value: number, offset: number): number; + writeUInt32BE(value: number, offset: number): number; + writeInt8(value: number, offset: number): number; + writeInt16LE(value: number, offset: number): number; + writeInt16BE(value: number, offset: number): number; + writeInt32LE(value: number, offset: number): number; + writeInt32BE(value: number, offset: number): number; + writeFloatLE(value: number, offset: number): number; + writeFloatBE(value: number, offset: number): number; + writeDoubleLE(value: number, offset: number): number; + writeDoubleBE(value: number, offset: number): number; + readBigUInt64BE?(offset?: number): bigint; + readBigUInt64LE?(offset?: number): bigint; + readBigInt64BE?(offset?: number): bigint; + readBigInt64LE?(offset?: number): bigint; + writeBigInt64BE?(value: bigint, offset?: number): number; + writeBigInt64LE?(value: bigint, offset?: number): number; + writeBigUInt64BE?(value: bigint, offset?: number): number; + writeBigUInt64LE?(value: bigint, offset?: number): number; + fill(value: string | Uint8Array | number, offset?: number, end?: number, encoding?: BufferEncoding): this; + indexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; + lastIndexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; + entries(): IterableIterator<[number, number]>; + includes(value: string | number | Buffer, byteOffset?: number, encoding?: BufferEncoding): boolean; + keys(): IterableIterator; + values(): IterableIterator; +} + +/*@internal*/ +interface Buffer extends NodeBuffer { } + +// TODO: GH#18217 Methods on System are often used as if they are certainly defined +export interface System { + args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; + write(s: string): void; + writeOutputIsTTY?(): boolean; + getWidthOfTerminal?(): number; + readFile(path: string, encoding?: string): string | undefined; + getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** - * patch writefile to create folder before writing the file + * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that + * use native OS file watching */ - /*@internal*/ - export function patchWriteFileEnsuringDirectory(sys: System) { - // patch writefile to create folder before writing the file - const originalWriteFile = sys.writeFile; - sys.writeFile = (path, data, writeBom) => - writeFileEnsuringDirectories( - path, - data, - !!writeBom, - (path, data, writeByteOrderMark) => originalWriteFile.call(sys, path, data, writeByteOrderMark), - path => sys.createDirectory(path), - path => sys.directoryExists(path)); - } + watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher; + watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher; + resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; + getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; + getModifiedTime?(path: string): Date | undefined; + setModifiedTime?(path: string, time: Date): void; + deleteFile?(path: string): void; + /** + * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) + */ + createHash?(data: string): string; + /** This must be cryptographically secure. Only implement this method using `crypto.createHash("sha256")`. */ + createSHA256Hash?(data: string): string; + getMemoryUsage?(): number; + exit(exitCode?: number): void; + /*@internal*/ enableCPUProfiler?(path: string, continuation: () => void): boolean; + /*@internal*/ disableCPUProfiler?(continuation: () => void): boolean; + /*@internal*/ cpuProfilingEnabled?(): boolean; + realpath?(path: string): string; + /*@internal*/ getEnvironmentVariable(name: string): string; + /*@internal*/ tryEnableSourceMapsForHost?(): void; + /*@internal*/ debugMode?: boolean; + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + clearTimeout?(timeoutId: any): void; + clearScreen?(): void; + /*@internal*/ setBlocking?(): void; + base64decode?(input: string): string; + base64encode?(input: string): string; + /*@internal*/ bufferFrom?(input: string, encoding?: string): Buffer; + // For testing + /*@internal*/ now?(): Date; + /*@internal*/ disableUseFileVersionAsSignature?: boolean; + /*@internal*/ require?(baseDir: string, moduleName: string): RequireResult; + /*@internal*/ defaultWatchFileKind?(): WatchFileKind | undefined; +} - /*@internal*/ - export type BufferEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex"; - - /*@internal*/ - interface NodeBuffer extends Uint8Array { - constructor: any; - write(str: string, encoding?: BufferEncoding): number; - write(str: string, offset: number, encoding?: BufferEncoding): number; - write(str: string, offset: number, length: number, encoding?: BufferEncoding): number; - toString(encoding?: string, start?: number, end?: number): string; - toJSON(): { type: "Buffer"; data: number[] }; - equals(otherBuffer: Uint8Array): boolean; - compare( - otherBuffer: Uint8Array, - targetStart?: number, - targetEnd?: number, - sourceStart?: number, - sourceEnd?: number - ): number; - copy(targetBuffer: Uint8Array, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; - slice(begin?: number, end?: number): Buffer; - subarray(begin?: number, end?: number): Buffer; - writeUIntLE(value: number, offset: number, byteLength: number): number; - writeUIntBE(value: number, offset: number, byteLength: number): number; - writeIntLE(value: number, offset: number, byteLength: number): number; - writeIntBE(value: number, offset: number, byteLength: number): number; - readUIntLE(offset: number, byteLength: number): number; - readUIntBE(offset: number, byteLength: number): number; - readIntLE(offset: number, byteLength: number): number; - readIntBE(offset: number, byteLength: number): number; - readUInt8(offset: number): number; - readUInt16LE(offset: number): number; - readUInt16BE(offset: number): number; - readUInt32LE(offset: number): number; - readUInt32BE(offset: number): number; - readInt8(offset: number): number; - readInt16LE(offset: number): number; - readInt16BE(offset: number): number; - readInt32LE(offset: number): number; - readInt32BE(offset: number): number; - readFloatLE(offset: number): number; - readFloatBE(offset: number): number; - readDoubleLE(offset: number): number; - readDoubleBE(offset: number): number; - reverse(): this; - swap16(): Buffer; - swap32(): Buffer; - swap64(): Buffer; - writeUInt8(value: number, offset: number): number; - writeUInt16LE(value: number, offset: number): number; - writeUInt16BE(value: number, offset: number): number; - writeUInt32LE(value: number, offset: number): number; - writeUInt32BE(value: number, offset: number): number; - writeInt8(value: number, offset: number): number; - writeInt16LE(value: number, offset: number): number; - writeInt16BE(value: number, offset: number): number; - writeInt32LE(value: number, offset: number): number; - writeInt32BE(value: number, offset: number): number; - writeFloatLE(value: number, offset: number): number; - writeFloatBE(value: number, offset: number): number; - writeDoubleLE(value: number, offset: number): number; - writeDoubleBE(value: number, offset: number): number; - readBigUInt64BE?(offset?: number): bigint; - readBigUInt64LE?(offset?: number): bigint; - readBigInt64BE?(offset?: number): bigint; - readBigInt64LE?(offset?: number): bigint; - writeBigInt64BE?(value: bigint, offset?: number): number; - writeBigInt64LE?(value: bigint, offset?: number): number; - writeBigUInt64BE?(value: bigint, offset?: number): number; - writeBigUInt64LE?(value: bigint, offset?: number): number; - fill(value: string | Uint8Array | number, offset?: number, end?: number, encoding?: BufferEncoding): this; - indexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; - lastIndexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; - entries(): IterableIterator<[number, number]>; - includes(value: string | number | Buffer, byteOffset?: number, encoding?: BufferEncoding): boolean; - keys(): IterableIterator; - values(): IterableIterator; - } +export interface FileWatcher { + close(): void; +} - /*@internal*/ - interface Buffer extends NodeBuffer { } - - // TODO: GH#18217 Methods on System are often used as if they are certainly defined - export interface System { - args: string[]; - newLine: string; - useCaseSensitiveFileNames: boolean; - write(s: string): void; - writeOutputIsTTY?(): boolean; - getWidthOfTerminal?(): number; - readFile(path: string, encoding?: string): string | undefined; - getFileSize?(path: string): number; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; +interface DirectoryWatcher extends FileWatcher { + referenceCount: number; +} - /** - * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that - * use native OS file watching - */ - watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher; - watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher; - resolvePath(path: string): string; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getExecutingFilePath(): string; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; - getModifiedTime?(path: string): Date | undefined; - setModifiedTime?(path: string, time: Date): void; - deleteFile?(path: string): void; - /** - * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) - */ - createHash?(data: string): string; - /** This must be cryptographically secure. Only implement this method using `crypto.createHash("sha256")`. */ - createSHA256Hash?(data: string): string; - getMemoryUsage?(): number; - exit(exitCode?: number): void; - /*@internal*/ enableCPUProfiler?(path: string, continuation: () => void): boolean; - /*@internal*/ disableCPUProfiler?(continuation: () => void): boolean; - /*@internal*/ cpuProfilingEnabled?(): boolean; - realpath?(path: string): string; - /*@internal*/ getEnvironmentVariable(name: string): string; - /*@internal*/ tryEnableSourceMapsForHost?(): void; - /*@internal*/ debugMode?: boolean; - setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; - clearTimeout?(timeoutId: any): void; - clearScreen?(): void; - /*@internal*/ setBlocking?(): void; - base64decode?(input: string): string; - base64encode?(input: string): string; - /*@internal*/ bufferFrom?(input: string, encoding?: string): Buffer; - // For testing - /*@internal*/ now?(): Date; - /*@internal*/ disableUseFileVersionAsSignature?: boolean; - /*@internal*/ require?(baseDir: string, moduleName: string): RequireResult; - /*@internal*/ defaultWatchFileKind?(): WatchFileKind | undefined; - } +declare const require: any; +declare const process: any; +declare const global: any; +declare const __filename: string; +declare const __dirname: string; - export interface FileWatcher { - close(): void; +export function getNodeMajorVersion(): number | undefined { + if (typeof process === "undefined") { + return undefined; } - - interface DirectoryWatcher extends FileWatcher { - referenceCount: number; + const version: string = process.version; + if (!version) { + return undefined; } + const dot = version.indexOf("."); + if (dot === -1) { + return undefined; + } + return parseInt(version.substring(1, dot)); +} - declare const require: any; - declare const process: any; - declare const global: any; - declare const __filename: string; - declare const __dirname: string; - - export function getNodeMajorVersion(): number | undefined { - if (typeof process === "undefined") { - return undefined; - } - const version: string = process.version; - if (!version) { - return undefined; +// TODO: GH#18217 this is used as if it's certainly defined in many places. +// eslint-disable-next-line prefer-const +export let sys: System = (() => { + // NodeJS detects "\uFEFF" at the start of the string and *replaces* it with the actual + // byte order mark from the specified encoding. Using any other byte order mark does + // not actually work. + const byteOrderMarkIndicator = "\uFEFF"; + + function getNodeSystem(): System { + const nativePattern = /^native |^\([^)]+\)$|^(internal[\\/]|[a-zA-Z0-9_\s]+(\.js)?$)/; + const _fs: typeof import("fs") = require("fs"); + const _path: typeof import("path") = require("path"); + const _os = require("os"); + // crypto can be absent on reduced node installations + let _crypto: typeof import("crypto") | undefined; + try { + _crypto = require("crypto"); } - const dot = version.indexOf("."); - if (dot === -1) { - return undefined; + catch { + _crypto = undefined; } - return parseInt(version.substring(1, dot)); - } - - // TODO: GH#18217 this is used as if it's certainly defined in many places. - // eslint-disable-next-line prefer-const - export let sys: System = (() => { - // NodeJS detects "\uFEFF" at the start of the string and *replaces* it with the actual - // byte order mark from the specified encoding. Using any other byte order mark does - // not actually work. - const byteOrderMarkIndicator = "\uFEFF"; - - function getNodeSystem(): System { - const nativePattern = /^native |^\([^)]+\)$|^(internal[\\/]|[a-zA-Z0-9_\s]+(\.js)?$)/; - const _fs: typeof import("fs") = require("fs"); - const _path: typeof import("path") = require("path"); - const _os = require("os"); - // crypto can be absent on reduced node installations - let _crypto: typeof import("crypto") | undefined; - try { - _crypto = require("crypto"); - } - catch { - _crypto = undefined; - } - let activeSession: import("inspector").Session | "stopping" | undefined; - let profilePath = "./profile.cpuprofile"; - - let hitSystemWatcherLimit = false; - - const Buffer: { - new (input: string, encoding?: string): any; - from?(input: string, encoding?: string): any; - } = require("buffer").Buffer; - - const nodeVersion = getNodeMajorVersion(); - const isNode4OrLater = nodeVersion! >= 4; - const isLinuxOrMacOs = process.platform === "linux" || process.platform === "darwin"; - - const platform: string = _os.platform(); - const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); - const realpathSync = _fs.realpathSync.native ?? _fs.realpathSync; - - const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin"); - const getCurrentDirectory = memoize(() => process.cwd()); - const { watchFile, watchDirectory } = createSystemWatchFunctions({ - pollingWatchFile: createSingleFileWatcherPerName(fsWatchFileWorker, useCaseSensitiveFileNames), - getModifiedTime, - setTimeout, - clearTimeout, - fsWatch, - useCaseSensitiveFileNames, - getCurrentDirectory, - fileExists, - // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows - // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) - fsSupportsRecursiveFsWatch, - directoryExists, - getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories, - realpath, - tscWatchFile: process.env.TSC_WATCHFILE, - useNonPollingWatchers: process.env.TSC_NONPOLLING_WATCHER, - tscWatchDirectory: process.env.TSC_WATCHDIRECTORY, - defaultWatchFileKind: () => sys!.defaultWatchFileKind?.(), - }); - const nodeSystem: System = { - args: process.argv.slice(2), - newLine: _os.EOL, - useCaseSensitiveFileNames, - write(s: string): void { - process.stdout.write(s); - }, - getWidthOfTerminal(){ - return process.stdout.columns; - }, - writeOutputIsTTY() { - return process.stdout.isTTY; - }, - readFile, - writeFile, - watchFile, - watchDirectory, - resolvePath: path => _path.resolve(path), - fileExists, - directoryExists, - createDirectory(directoryName: string) { - if (!nodeSystem.directoryExists(directoryName)) { - // Wrapped in a try-catch to prevent crashing if we are in a race - // with another copy of ourselves to create the same directory - try { - _fs.mkdirSync(directoryName); - } - catch (e) { - if (e.code !== "EEXIST") { - // Failed for some other reason (access denied?); still throw - throw e; - } - } - } - }, - getExecutingFilePath() { - return __filename; - }, - getCurrentDirectory, - getDirectories, - getEnvironmentVariable(name: string) { - return process.env[name] || ""; - }, - readDirectory, - getModifiedTime, - setModifiedTime, - deleteFile, - createHash: _crypto ? createSHA256Hash : generateDjb2Hash, - createSHA256Hash: _crypto ? createSHA256Hash : undefined, - getMemoryUsage() { - if (global.gc) { - global.gc(); - } - return process.memoryUsage().heapUsed; - }, - getFileSize(path) { - try { - const stat = statSync(path); - if (stat?.isFile()) { - return stat.size; - } - } - catch { /*ignore*/ } - return 0; - }, - exit(exitCode?: number): void { - disableCPUProfiler(() => process.exit(exitCode)); - }, - enableCPUProfiler, - disableCPUProfiler, - cpuProfilingEnabled: () => !!activeSession || contains(process.execArgv, "--cpu-prof") || contains(process.execArgv, "--prof"), - realpath, - debugMode: !!process.env.NODE_INSPECTOR_IPC || !!process.env.VSCODE_INSPECTOR_OPTIONS || some(process.execArgv as string[], arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)), - tryEnableSourceMapsForHost() { + let activeSession: import("inspector").Session | "stopping" | undefined; + let profilePath = "./profile.cpuprofile"; + + let hitSystemWatcherLimit = false; + + const Buffer: { + new (input: string, encoding?: string): any; + from?(input: string, encoding?: string): any; + } = require("buffer").Buffer; + + const nodeVersion = getNodeMajorVersion(); + const isNode4OrLater = nodeVersion! >= 4; + const isLinuxOrMacOs = process.platform === "linux" || process.platform === "darwin"; + + const platform: string = _os.platform(); + const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); + const realpathSync = _fs.realpathSync.native ?? _fs.realpathSync; + + const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin"); + const getCurrentDirectory = memoize(() => process.cwd()); + const { watchFile, watchDirectory } = createSystemWatchFunctions({ + pollingWatchFile: createSingleFileWatcherPerName(fsWatchFileWorker, useCaseSensitiveFileNames), + getModifiedTime, + setTimeout, + clearTimeout, + fsWatch, + useCaseSensitiveFileNames, + getCurrentDirectory, + fileExists, + // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows + // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) + fsSupportsRecursiveFsWatch, + directoryExists, + getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories, + realpath, + tscWatchFile: process.env.TSC_WATCHFILE, + useNonPollingWatchers: process.env.TSC_NONPOLLING_WATCHER, + tscWatchDirectory: process.env.TSC_WATCHDIRECTORY, + defaultWatchFileKind: () => sys!.defaultWatchFileKind?.(), + }); + const nodeSystem: System = { + args: process.argv.slice(2), + newLine: _os.EOL, + useCaseSensitiveFileNames, + write(s: string): void { + process.stdout.write(s); + }, + getWidthOfTerminal(){ + return process.stdout.columns; + }, + writeOutputIsTTY() { + return process.stdout.isTTY; + }, + readFile, + writeFile, + watchFile, + watchDirectory, + resolvePath: path => _path.resolve(path), + fileExists, + directoryExists, + createDirectory(directoryName: string) { + if (!nodeSystem.directoryExists(directoryName)) { + // Wrapped in a try-catch to prevent crashing if we are in a race + // with another copy of ourselves to create the same directory try { - require("source-map-support").install(); - } - catch { - // Could not enable source maps. + _fs.mkdirSync(directoryName); } - }, - setTimeout, - clearTimeout, - clearScreen: () => { - process.stdout.write("\x1Bc"); - }, - setBlocking: () => { - if (process.stdout && process.stdout._handle && process.stdout._handle.setBlocking) { - process.stdout._handle.setBlocking(true); - } - }, - bufferFrom, - base64decode: input => bufferFrom(input, "base64").toString("utf8"), - base64encode: input => bufferFrom(input).toString("base64"), - require: (baseDir, moduleName) => { - try { - const modulePath = resolveJSModule(moduleName, baseDir, nodeSystem); - return { module: require(modulePath), modulePath, error: undefined }; + catch (e) { + if (e.code !== "EEXIST") { + // Failed for some other reason (access denied?); still throw + throw e; + } } - catch (error) { - return { module: undefined, modulePath: undefined, error }; + } + }, + getExecutingFilePath() { + return __filename; + }, + getCurrentDirectory, + getDirectories, + getEnvironmentVariable(name: string) { + return process.env[name] || ""; + }, + readDirectory, + getModifiedTime, + setModifiedTime, + deleteFile, + createHash: _crypto ? createSHA256Hash : generateDjb2Hash, + createSHA256Hash: _crypto ? createSHA256Hash : undefined, + getMemoryUsage() { + if (global.gc) { + global.gc(); + } + return process.memoryUsage().heapUsed; + }, + getFileSize(path) { + try { + const stat = statSync(path); + if (stat?.isFile()) { + return stat.size; } } - }; - return nodeSystem; + catch { /*ignore*/ } + return 0; + }, + exit(exitCode?: number): void { + disableCPUProfiler(() => process.exit(exitCode)); + }, + enableCPUProfiler, + disableCPUProfiler, + cpuProfilingEnabled: () => !!activeSession || contains(process.execArgv, "--cpu-prof") || contains(process.execArgv, "--prof"), + realpath, + debugMode: !!process.env.NODE_INSPECTOR_IPC || !!process.env.VSCODE_INSPECTOR_OPTIONS || some(process.execArgv as string[], arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)), + tryEnableSourceMapsForHost() { + try { + require("source-map-support").install(); + } + catch { + // Could not enable source maps. + } + }, + setTimeout, + clearTimeout, + clearScreen: () => { + process.stdout.write("\x1Bc"); + }, + setBlocking: () => { + if (process.stdout && process.stdout._handle && process.stdout._handle.setBlocking) { + process.stdout._handle.setBlocking(true); + } + }, + bufferFrom, + base64decode: input => bufferFrom(input, "base64").toString("utf8"), + base64encode: input => bufferFrom(input).toString("base64"), + require: (baseDir, moduleName) => { + try { + const modulePath = resolveJSModule(moduleName, baseDir, nodeSystem); + return { module: require(modulePath), modulePath, error: undefined }; + } + catch (error) { + return { module: undefined, modulePath: undefined, error }; + } + } + }; + return nodeSystem; - /** - * `throwIfNoEntry` was added so recently that it's not in the node types. - * This helper encapsulates the mitigating usage of `any`. - * See https://github.com/nodejs/node/pull/33716 - */ - function statSync(path: string): import("fs").Stats | undefined { - // throwIfNoEntry will be ignored by older versions of node - return (_fs as any).statSync(path, { throwIfNoEntry: false }); + /** + * `throwIfNoEntry` was added so recently that it's not in the node types. + * This helper encapsulates the mitigating usage of `any`. + * See https://github.com/nodejs/node/pull/33716 + */ + function statSync(path: string): import("fs").Stats | undefined { + // throwIfNoEntry will be ignored by older versions of node + return (_fs as any).statSync(path, { throwIfNoEntry: false }); + } + + /** + * Uses the builtin inspector APIs to capture a CPU profile + * See https://nodejs.org/api/inspector.html#inspector_example_usage for details + */ + function enableCPUProfiler(path: string, cb: () => void) { + if (activeSession) { + cb(); + return false; } + const inspector: typeof import("inspector") = require("inspector"); + if (!inspector || !inspector.Session) { + cb(); + return false; + } + const session = new inspector.Session(); + session.connect(); - /** - * Uses the builtin inspector APIs to capture a CPU profile - * See https://nodejs.org/api/inspector.html#inspector_example_usage for details - */ - function enableCPUProfiler(path: string, cb: () => void) { - if (activeSession) { - cb(); - return false; - } - const inspector: typeof import("inspector") = require("inspector"); - if (!inspector || !inspector.Session) { + session.post("Profiler.enable", () => { + session.post("Profiler.start", () => { + activeSession = session; + profilePath = path; cb(); - return false; - } - const session = new inspector.Session(); - session.connect(); - - session.post("Profiler.enable", () => { - session.post("Profiler.start", () => { - activeSession = session; - profilePath = path; - cb(); - }); }); - return true; - } + }); + return true; + } - /** - * Strips non-TS paths from the profile, so users with private projects shouldn't - * need to worry about leaking paths by submitting a cpu profile to us - */ - function cleanupPaths(profile: import("inspector").Profiler.Profile) { - let externalFileCounter = 0; - const remappedPaths = new Map(); - const normalizedDir = normalizeSlashes(__dirname); - // Windows rooted dir names need an extra `/` prepended to be valid file:/// urls - const fileUrlRoot = `file://${getRootLength(normalizedDir) === 1 ? "" : "/"}${normalizedDir}`; - for (const node of profile.nodes) { - if (node.callFrame.url) { - const url = normalizeSlashes(node.callFrame.url); - if (containsPath(fileUrlRoot, url, useCaseSensitiveFileNames)) { - node.callFrame.url = getRelativePathToDirectoryOrUrl(fileUrlRoot, url, fileUrlRoot, createGetCanonicalFileName(useCaseSensitiveFileNames), /*isAbsolutePathAnUrl*/ true); - } - else if (!nativePattern.test(url)) { - node.callFrame.url = (remappedPaths.has(url) ? remappedPaths : remappedPaths.set(url, `external${externalFileCounter}.js`)).get(url)!; - externalFileCounter++; - } + /** + * Strips non-TS paths from the profile, so users with private projects shouldn't + * need to worry about leaking paths by submitting a cpu profile to us + */ + function cleanupPaths(profile: import("inspector").Profiler.Profile) { + let externalFileCounter = 0; + const remappedPaths = new Map(); + const normalizedDir = normalizeSlashes(__dirname); + // Windows rooted dir names need an extra `/` prepended to be valid file:/// urls + const fileUrlRoot = `file://${getRootLength(normalizedDir) === 1 ? "" : "/"}${normalizedDir}`; + for (const node of profile.nodes) { + if (node.callFrame.url) { + const url = normalizeSlashes(node.callFrame.url); + if (containsPath(fileUrlRoot, url, useCaseSensitiveFileNames)) { + node.callFrame.url = getRelativePathToDirectoryOrUrl(fileUrlRoot, url, fileUrlRoot, createGetCanonicalFileName(useCaseSensitiveFileNames), /*isAbsolutePathAnUrl*/ true); + } + else if (!nativePattern.test(url)) { + node.callFrame.url = (remappedPaths.has(url) ? remappedPaths : remappedPaths.set(url, `external${externalFileCounter}.js`)).get(url)!; + externalFileCounter++; } } - return profile; } + return profile; + } - function disableCPUProfiler(cb: () => void) { - if (activeSession && activeSession !== "stopping") { - const s = activeSession; - activeSession.post("Profiler.stop", (err, { profile }) => { - if (!err) { - try { - if (statSync(profilePath)?.isDirectory()) { - profilePath = _path.join(profilePath, `${(new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`); - } - } - catch { - // do nothing and ignore fallible fs operation - } - try { - _fs.mkdirSync(_path.dirname(profilePath), { recursive: true }); - } - catch { - // do nothing and ignore fallible fs operation + function disableCPUProfiler(cb: () => void) { + if (activeSession && activeSession !== "stopping") { + const s = activeSession; + activeSession.post("Profiler.stop", (err, { profile }) => { + if (!err) { + try { + if (statSync(profilePath)?.isDirectory()) { + profilePath = _path.join(profilePath, `${(new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`); } - _fs.writeFileSync(profilePath, JSON.stringify(cleanupPaths(profile))); } - activeSession = undefined; - s.disconnect(); - cb(); - }); - activeSession = "stopping"; - return true; - } - else { + catch { + // do nothing and ignore fallible fs operation + } + try { + _fs.mkdirSync(_path.dirname(profilePath), { recursive: true }); + } + catch { + // do nothing and ignore fallible fs operation + } + _fs.writeFileSync(profilePath, JSON.stringify(cleanupPaths(profile))); + } + activeSession = undefined; + s.disconnect(); cb(); - return false; - } + }); + activeSession = "stopping"; + return true; } - - function bufferFrom(input: string, encoding?: string): Buffer { - // See https://github.com/Microsoft/TypeScript/issues/25652 - return Buffer.from && (Buffer.from as Function) !== Int8Array.from - ? Buffer.from(input, encoding) - : new Buffer(input, encoding); + else { + cb(); + return false; } + } - function isFileSystemCaseSensitive(): boolean { - // win32\win64 are case insensitive platforms - if (platform === "win32" || platform === "win64") { - return false; - } - // If this file exists under a different case, we must be case-insensitve. - return !fileExists(swapCase(__filename)); - } + function bufferFrom(input: string, encoding?: string): Buffer { + // See https://github.com/Microsoft/TypeScript/issues/25652 + return Buffer.from && (Buffer.from as Function) !== Int8Array.from + ? Buffer.from(input, encoding) + : new Buffer(input, encoding); + } - /** Convert all lowercase chars to uppercase, and vice-versa */ - function swapCase(s: string): string { - return s.replace(/\w/g, (ch) => { - const up = ch.toUpperCase(); - return ch === up ? ch.toLowerCase() : up; - }); + function isFileSystemCaseSensitive(): boolean { + // win32\win64 are case insensitive platforms + if (platform === "win32" || platform === "win64") { + return false; } + // If this file exists under a different case, we must be case-insensitve. + return !fileExists(swapCase(__filename)); + } - function fsWatchFileWorker(fileName: string, callback: FileWatcherCallback, pollingInterval: number): FileWatcher { - _fs.watchFile(fileName, { persistent: true, interval: pollingInterval }, fileChanged); - let eventKind: FileWatcherEventKind; - return { - close: () => _fs.unwatchFile(fileName, fileChanged) - }; + /** Convert all lowercase chars to uppercase, and vice-versa */ + function swapCase(s: string): string { + return s.replace(/\w/g, (ch) => { + const up = ch.toUpperCase(); + return ch === up ? ch.toLowerCase() : up; + }); + } - function fileChanged(curr: any, prev: any) { - // previous event kind check is to ensure we recongnize the file as previously also missing when it is restored or renamed twice (that is it disappears and reappears) - // In such case, prevTime returned is same as prev time of event when file was deleted as per node documentation - const isPreviouslyDeleted = +prev.mtime === 0 || eventKind === FileWatcherEventKind.Deleted; - if (+curr.mtime === 0) { - if (isPreviouslyDeleted) { - // Already deleted file, no need to callback again - return; - } - eventKind = FileWatcherEventKind.Deleted; - } - else if (isPreviouslyDeleted) { - eventKind = FileWatcherEventKind.Created; - } - // If there is no change in modified time, ignore the event - else if (+curr.mtime === +prev.mtime) { + function fsWatchFileWorker(fileName: string, callback: FileWatcherCallback, pollingInterval: number): FileWatcher { + _fs.watchFile(fileName, { persistent: true, interval: pollingInterval }, fileChanged); + let eventKind: FileWatcherEventKind; + return { + close: () => _fs.unwatchFile(fileName, fileChanged) + }; + + function fileChanged(curr: any, prev: any) { + // previous event kind check is to ensure we recongnize the file as previously also missing when it is restored or renamed twice (that is it disappears and reappears) + // In such case, prevTime returned is same as prev time of event when file was deleted as per node documentation + const isPreviouslyDeleted = +prev.mtime === 0 || eventKind === FileWatcherEventKind.Deleted; + if (+curr.mtime === 0) { + if (isPreviouslyDeleted) { + // Already deleted file, no need to callback again return; } - else { - // File changed - eventKind = FileWatcherEventKind.Changed; - } - callback(fileName, eventKind); + eventKind = FileWatcherEventKind.Deleted; + } + else if (isPreviouslyDeleted) { + eventKind = FileWatcherEventKind.Created; } + // If there is no change in modified time, ignore the event + else if (+curr.mtime === +prev.mtime) { + return; + } + else { + // File changed + eventKind = FileWatcherEventKind.Changed; + } + callback(fileName, eventKind); } + } - function fsWatch( - fileOrDirectory: string, - entryKind: FileSystemEntryKind, - callback: FsWatchCallback, - recursive: boolean, - fallbackPollingInterval: PollingInterval, - fallbackOptions: WatchOptions | undefined - ): FileWatcher { - let options: any; - let lastDirectoryPartWithDirectorySeparator: string | undefined; - let lastDirectoryPart: string | undefined; - if (isLinuxOrMacOs) { - lastDirectoryPartWithDirectorySeparator = fileOrDirectory.substr(fileOrDirectory.lastIndexOf(directorySeparator)); - lastDirectoryPart = lastDirectoryPartWithDirectorySeparator.slice(directorySeparator.length); + function fsWatch( + fileOrDirectory: string, + entryKind: FileSystemEntryKind, + callback: FsWatchCallback, + recursive: boolean, + fallbackPollingInterval: PollingInterval, + fallbackOptions: WatchOptions | undefined + ): FileWatcher { + let options: any; + let lastDirectoryPartWithDirectorySeparator: string | undefined; + let lastDirectoryPart: string | undefined; + if (isLinuxOrMacOs) { + lastDirectoryPartWithDirectorySeparator = fileOrDirectory.substr(fileOrDirectory.lastIndexOf(directorySeparator)); + lastDirectoryPart = lastDirectoryPartWithDirectorySeparator.slice(directorySeparator.length); + } + /** Watcher for the file system entry depending on whether it is missing or present */ + let watcher = !fileSystemEntryExists(fileOrDirectory, entryKind) ? + watchMissingFileSystemEntry() : + watchPresentFileSystemEntry(); + return { + close: () => { + // Close the watcher (either existing file system entry watcher or missing file system entry watcher) + watcher.close(); + watcher = undefined!; } - /** Watcher for the file system entry depending on whether it is missing or present */ - let watcher = !fileSystemEntryExists(fileOrDirectory, entryKind) ? - watchMissingFileSystemEntry() : - watchPresentFileSystemEntry(); - return { - close: () => { - // Close the watcher (either existing file system entry watcher or missing file system entry watcher) - watcher.close(); - watcher = undefined!; - } - }; + }; - /** - * Invoke the callback with rename and update the watcher if not closed - * @param createWatcher - */ - function invokeCallbackAndUpdateWatcher(createWatcher: () => FileWatcher) { - sysLog(`sysLog:: ${fileOrDirectory}:: Changing watcher to ${createWatcher === watchPresentFileSystemEntry ? "Present" : "Missing"}FileSystemEntryWatcher`); - // Call the callback for current directory - callback("rename", ""); - - // If watcher is not closed, update it - if (watcher) { - watcher.close(); - watcher = createWatcher(); - } + /** + * Invoke the callback with rename and update the watcher if not closed + * @param createWatcher + */ + function invokeCallbackAndUpdateWatcher(createWatcher: () => FileWatcher) { + sysLog(`sysLog:: ${fileOrDirectory}:: Changing watcher to ${createWatcher === watchPresentFileSystemEntry ? "Present" : "Missing"}FileSystemEntryWatcher`); + // Call the callback for current directory + callback("rename", ""); + + // If watcher is not closed, update it + if (watcher) { + watcher.close(); + watcher = createWatcher(); } + } - /** - * Watch the file or directory that is currently present - * and when the watched file or directory is deleted, switch to missing file system entry watcher - */ - function watchPresentFileSystemEntry(): FileWatcher { - // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows - // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) - if (options === undefined) { - if (fsSupportsRecursiveFsWatch) { - options = { persistent: true, recursive: !!recursive }; - } - else { - options = { persistent: true }; - } - } - - if (hitSystemWatcherLimit) { - sysLog(`sysLog:: ${fileOrDirectory}:: Defaulting to fsWatchFile`); - return watchPresentFileSystemEntryWithFsWatchFile(); - } - try { - const presentWatcher = _fs.watch( - fileOrDirectory, - options, - isLinuxOrMacOs ? - callbackChangingToMissingFileSystemEntry : - callback - ); - // Watch the missing file or directory or error - presentWatcher.on("error", () => invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry)); - return presentWatcher; + /** + * Watch the file or directory that is currently present + * and when the watched file or directory is deleted, switch to missing file system entry watcher + */ + function watchPresentFileSystemEntry(): FileWatcher { + // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows + // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) + if (options === undefined) { + if (fsSupportsRecursiveFsWatch) { + options = { persistent: true, recursive: !!recursive }; } - catch (e) { - // Catch the exception and use polling instead - // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point - // so instead of throwing error, use fs.watchFile - hitSystemWatcherLimit ||= e.code === "ENOSPC"; - sysLog(`sysLog:: ${fileOrDirectory}:: Changing to fsWatchFile`); - return watchPresentFileSystemEntryWithFsWatchFile(); + else { + options = { persistent: true }; } } - function callbackChangingToMissingFileSystemEntry(event: "rename" | "change", relativeName: string | undefined) { - // because relativeName is not guaranteed to be correct we need to check on each rename with few combinations - // Eg on ubuntu while watching app/node_modules the relativeName is "node_modules" which is neither relative nor full path - return event === "rename" && - (!relativeName || - relativeName === lastDirectoryPart || - (relativeName.lastIndexOf(lastDirectoryPartWithDirectorySeparator!) !== -1 && relativeName.lastIndexOf(lastDirectoryPartWithDirectorySeparator!) === relativeName.length - lastDirectoryPartWithDirectorySeparator!.length)) && - !fileSystemEntryExists(fileOrDirectory, entryKind) ? - invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry) : - callback(event, relativeName); + if (hitSystemWatcherLimit) { + sysLog(`sysLog:: ${fileOrDirectory}:: Defaulting to fsWatchFile`); + return watchPresentFileSystemEntryWithFsWatchFile(); } - - /** - * Watch the file or directory using fs.watchFile since fs.watch threw exception - * Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point - */ - function watchPresentFileSystemEntryWithFsWatchFile(): FileWatcher { - return watchFile( + try { + const presentWatcher = _fs.watch( fileOrDirectory, - createFileWatcherCallback(callback), - fallbackPollingInterval, - fallbackOptions + options, + isLinuxOrMacOs ? + callbackChangingToMissingFileSystemEntry : + callback ); + // Watch the missing file or directory or error + presentWatcher.on("error", () => invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry)); + return presentWatcher; } - - /** - * Watch the file or directory that is missing - * and switch to existing file or directory when the missing filesystem entry is created - */ - function watchMissingFileSystemEntry(): FileWatcher { - return watchFile( - fileOrDirectory, - (_fileName, eventKind) => { - if (eventKind === FileWatcherEventKind.Created && fileSystemEntryExists(fileOrDirectory, entryKind)) { - // Call the callback for current file or directory - // For now it could be callback for the inner directory creation, - // but just return current directory, better than current no-op - invokeCallbackAndUpdateWatcher(watchPresentFileSystemEntry); - } - }, - fallbackPollingInterval, - fallbackOptions - ); + catch (e) { + // Catch the exception and use polling instead + // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point + // so instead of throwing error, use fs.watchFile + hitSystemWatcherLimit ||= e.code === "ENOSPC"; + sysLog(`sysLog:: ${fileOrDirectory}:: Changing to fsWatchFile`); + return watchPresentFileSystemEntryWithFsWatchFile(); } } - function readFileWorker(fileName: string, _encoding?: string): string | undefined { - let buffer: Buffer; - try { - buffer = _fs.readFileSync(fileName); - } - catch (e) { - return undefined; - } - let len = buffer.length; - if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) { - // Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js, - // flip all byte pairs and treat as little endian. - len &= ~1; // Round down to a multiple of 2 - for (let i = 0; i < len; i += 2) { - const temp = buffer[i]; - buffer[i] = buffer[i + 1]; - buffer[i + 1] = temp; - } - return buffer.toString("utf16le", 2); - } - if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) { - // Little endian UTF-16 byte order mark detected - return buffer.toString("utf16le", 2); - } - if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) { - // UTF-8 byte order mark detected - return buffer.toString("utf8", 3); - } - // Default is UTF-8 with no byte order mark - return buffer.toString("utf8"); + function callbackChangingToMissingFileSystemEntry(event: "rename" | "change", relativeName: string | undefined) { + // because relativeName is not guaranteed to be correct we need to check on each rename with few combinations + // Eg on ubuntu while watching app/node_modules the relativeName is "node_modules" which is neither relative nor full path + return event === "rename" && + (!relativeName || + relativeName === lastDirectoryPart || + (relativeName.lastIndexOf(lastDirectoryPartWithDirectorySeparator!) !== -1 && relativeName.lastIndexOf(lastDirectoryPartWithDirectorySeparator!) === relativeName.length - lastDirectoryPartWithDirectorySeparator!.length)) && + !fileSystemEntryExists(fileOrDirectory, entryKind) ? + invokeCallbackAndUpdateWatcher(watchMissingFileSystemEntry) : + callback(event, relativeName); } - function readFile(fileName: string, _encoding?: string): string | undefined { - perfLogger.logStartReadFile(fileName); - const file = readFileWorker(fileName, _encoding); - perfLogger.logStopReadFile(); - return file; + /** + * Watch the file or directory using fs.watchFile since fs.watch threw exception + * Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point + */ + function watchPresentFileSystemEntryWithFsWatchFile(): FileWatcher { + return watchFile( + fileOrDirectory, + createFileWatcherCallback(callback), + fallbackPollingInterval, + fallbackOptions + ); } - function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { - perfLogger.logEvent("WriteFile: " + fileName); - // If a BOM is required, emit one - if (writeByteOrderMark) { - data = byteOrderMarkIndicator + data; + /** + * Watch the file or directory that is missing + * and switch to existing file or directory when the missing filesystem entry is created + */ + function watchMissingFileSystemEntry(): FileWatcher { + return watchFile( + fileOrDirectory, + (_fileName, eventKind) => { + if (eventKind === FileWatcherEventKind.Created && fileSystemEntryExists(fileOrDirectory, entryKind)) { + // Call the callback for current file or directory + // For now it could be callback for the inner directory creation, + // but just return current directory, better than current no-op + invokeCallbackAndUpdateWatcher(watchPresentFileSystemEntry); + } + }, + fallbackPollingInterval, + fallbackOptions + ); + } + } + + function readFileWorker(fileName: string, _encoding?: string): string | undefined { + let buffer: Buffer; + try { + buffer = _fs.readFileSync(fileName); + } + catch (e) { + return undefined; + } + let len = buffer.length; + if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) { + // Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js, + // flip all byte pairs and treat as little endian. + len &= ~1; // Round down to a multiple of 2 + for (let i = 0; i < len; i += 2) { + const temp = buffer[i]; + buffer[i] = buffer[i + 1]; + buffer[i + 1] = temp; } + return buffer.toString("utf16le", 2); + } + if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) { + // Little endian UTF-16 byte order mark detected + return buffer.toString("utf16le", 2); + } + if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) { + // UTF-8 byte order mark detected + return buffer.toString("utf8", 3); + } + // Default is UTF-8 with no byte order mark + return buffer.toString("utf8"); + } - let fd: number | undefined; + function readFile(fileName: string, _encoding?: string): string | undefined { + perfLogger.logStartReadFile(fileName); + const file = readFileWorker(fileName, _encoding); + perfLogger.logStopReadFile(); + return file; + } - try { - fd = _fs.openSync(fileName, "w"); - _fs.writeSync(fd, data, /*position*/ undefined, "utf8"); - } - finally { - if (fd !== undefined) { - _fs.closeSync(fd); - } + function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { + perfLogger.logEvent("WriteFile: " + fileName); + // If a BOM is required, emit one + if (writeByteOrderMark) { + data = byteOrderMarkIndicator + data; + } + + let fd: number | undefined; + + try { + fd = _fs.openSync(fileName, "w"); + _fs.writeSync(fd, data, /*position*/ undefined, "utf8"); + } + finally { + if (fd !== undefined) { + _fs.closeSync(fd); } } + } - function getAccessibleFileSystemEntries(path: string): FileSystemEntries { - perfLogger.logEvent("ReadDir: " + (path || ".")); - try { - const entries = _fs.readdirSync(path || ".", { withFileTypes: true }); - const files: string[] = []; - const directories: string[] = []; - for (const dirent of entries) { - // withFileTypes is not supported before Node 10.10. - const entry = typeof dirent === "string" ? dirent : dirent.name; - - // This is necessary because on some file system node fails to exclude - // "." and "..". See https://github.com/nodejs/node/issues/4002 - if (entry === "." || entry === "..") { - continue; - } + function getAccessibleFileSystemEntries(path: string): FileSystemEntries { + perfLogger.logEvent("ReadDir: " + (path || ".")); + try { + const entries = _fs.readdirSync(path || ".", { withFileTypes: true }); + const files: string[] = []; + const directories: string[] = []; + for (const dirent of entries) { + // withFileTypes is not supported before Node 10.10. + const entry = typeof dirent === "string" ? dirent : dirent.name; + + // This is necessary because on some file system node fails to exclude + // "." and "..". See https://github.com/nodejs/node/issues/4002 + if (entry === "." || entry === "..") { + continue; + } - let stat: any; - if (typeof dirent === "string" || dirent.isSymbolicLink()) { - const name = combinePaths(path, entry); + let stat: any; + if (typeof dirent === "string" || dirent.isSymbolicLink()) { + const name = combinePaths(path, entry); - try { - stat = statSync(name); - if (!stat) { - continue; - } - } - catch (e) { + try { + stat = statSync(name); + if (!stat) { continue; } } - else { - stat = dirent; + catch (e) { + continue; } + } + else { + stat = dirent; + } - if (stat.isFile()) { - files.push(entry); - } - else if (stat.isDirectory()) { - directories.push(entry); - } + if (stat.isFile()) { + files.push(entry); + } + else if (stat.isDirectory()) { + directories.push(entry); } - files.sort(); - directories.sort(); - return { files, directories }; - } - catch (e) { - return emptyFileSystemEntries; } + files.sort(); + directories.sort(); + return { files, directories }; } - - function readDirectory(path: string, extensions?: readonly string[], excludes?: readonly string[], includes?: readonly string[], depth?: number): string[] { - return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath); + catch (e) { + return emptyFileSystemEntries; } + } - function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean { - // Since the error thrown by fs.statSync isn't used, we can avoid collecting a stack trace to improve - // the CPU time performance. - const originalStackTraceLimit = Error.stackTraceLimit; - Error.stackTraceLimit = 0; + function readDirectory(path: string, extensions?: readonly string[], excludes?: readonly string[], includes?: readonly string[], depth?: number): string[] { + return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath); + } - try { - const stat = statSync(path); - if (!stat) { - return false; - } - switch (entryKind) { - case FileSystemEntryKind.File: return stat.isFile(); - case FileSystemEntryKind.Directory: return stat.isDirectory(); - default: return false; - } - } - catch (e) { + function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean { + // Since the error thrown by fs.statSync isn't used, we can avoid collecting a stack trace to improve + // the CPU time performance. + const originalStackTraceLimit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + + try { + const stat = statSync(path); + if (!stat) { return false; } - finally { - Error.stackTraceLimit = originalStackTraceLimit; + switch (entryKind) { + case FileSystemEntryKind.File: return stat.isFile(); + case FileSystemEntryKind.Directory: return stat.isDirectory(); + default: return false; } } - - function fileExists(path: string): boolean { - return fileSystemEntryExists(path, FileSystemEntryKind.File); + catch (e) { + return false; } - - function directoryExists(path: string): boolean { - return fileSystemEntryExists(path, FileSystemEntryKind.Directory); + finally { + Error.stackTraceLimit = originalStackTraceLimit; } + } - function getDirectories(path: string): string[] { - return getAccessibleFileSystemEntries(path).directories.slice(); - } + function fileExists(path: string): boolean { + return fileSystemEntryExists(path, FileSystemEntryKind.File); + } - function realpath(path: string): string { - try { - return realpathSync(path); - } - catch { - return path; - } - } + function directoryExists(path: string): boolean { + return fileSystemEntryExists(path, FileSystemEntryKind.Directory); + } - function getModifiedTime(path: string) { - try { - return statSync(path)?.mtime; - } - catch (e) { - return undefined; - } - } + function getDirectories(path: string): string[] { + return getAccessibleFileSystemEntries(path).directories.slice(); + } - function setModifiedTime(path: string, time: Date) { - try { - _fs.utimesSync(path, time, time); - } - catch (e) { - return; - } + function realpath(path: string): string { + try { + return realpathSync(path); } - - function deleteFile(path: string) { - try { - return _fs.unlinkSync(path); - } - catch (e) { - return; - } + catch { + return path; } + } - function createSHA256Hash(data: string): string { - const hash = _crypto!.createHash("sha256"); - hash.update(data); - return hash.digest("hex"); + function getModifiedTime(path: string) { + try { + return statSync(path)?.mtime; + } + catch (e) { + return undefined; } } - let sys: System | undefined; - if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof require !== "undefined") { - // process and process.nextTick checks if current environment is node-like - // process.browser check excludes webpack and browserify - sys = getNodeSystem(); + function setModifiedTime(path: string, time: Date) { + try { + _fs.utimesSync(path, time, time); + } + catch (e) { + return; + } } - if (sys) { - // patch writefile to create folder before writing the file - patchWriteFileEnsuringDirectory(sys); + + function deleteFile(path: string) { + try { + return _fs.unlinkSync(path); + } + catch (e) { + return; + } } - return sys!; - })(); - /*@internal*/ - export function setSys(s: System) { - sys = s; + function createSHA256Hash(data: string): string { + const hash = _crypto!.createHash("sha256"); + hash.update(data); + return hash.digest("hex"); + } } - if (sys && sys.getEnvironmentVariable) { - setCustomPollingValues(sys); - Debug.setAssertionLevel(/^development$/i.test(sys.getEnvironmentVariable("NODE_ENV")) - ? AssertionLevel.Normal - : AssertionLevel.None); + let sys: System | undefined; + if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof require !== "undefined") { + // process and process.nextTick checks if current environment is node-like + // process.browser check excludes webpack and browserify + sys = getNodeSystem(); } - if (sys && sys.debugMode) { - Debug.isDebugging = true; + if (sys) { + // patch writefile to create folder before writing the file + patchWriteFileEnsuringDirectory(sys); } + return sys!; +})(); + +/*@internal*/ +export function setSys(s: System) { + sys = s; +} + +if (sys && sys.getEnvironmentVariable) { + setCustomPollingValues(sys); + Debug.setAssertionLevel(/^development$/i.test(sys.getEnvironmentVariable("NODE_ENV")) + ? AssertionLevel.Normal + : AssertionLevel.None); +} +if (sys && sys.debugMode) { + Debug.isDebugging = true; +} } diff --git a/src/compiler/tracing.ts b/src/compiler/tracing.ts index 31c1c8910cf91..d446d0c1b285f 100644 --- a/src/compiler/tracing.ts +++ b/src/compiler/tracing.ts @@ -2,343 +2,343 @@ /*@internal*/ namespace ts { // eslint-disable-line one-namespace-per-file - // should be used as tracing?.___ - export let tracing: typeof tracingEnabled | undefined; - // enable the above using startTracing() +// should be used as tracing?.___ +export let tracing: typeof tracingEnabled | undefined; +// enable the above using startTracing() - // `tracingEnabled` should never be used directly, only through the above - namespace tracingEnabled { // eslint-disable-line one-namespace-per-file - type Mode = "project" | "build" | "server"; +// `tracingEnabled` should never be used directly, only through the above +namespace tracingEnabled { // eslint-disable-line one-namespace-per-file + type Mode = "project" | "build" | "server"; - let fs: typeof import("fs"); + let fs: typeof import("fs"); - let traceCount = 0; - let traceFd = 0; + let traceCount = 0; + let traceFd = 0; - let mode: Mode; + let mode: Mode; - const typeCatalog: Type[] = []; // NB: id is index + 1 + const typeCatalog: Type[] = []; // NB: id is index + 1 - let legendPath: string | undefined; - const legend: TraceRecord[] = []; + let legendPath: string | undefined; + const legend: TraceRecord[] = []; - // The actual constraint is that JSON.stringify be able to serialize it without throwing. - interface Args { - [key: string]: string | number | boolean | null | undefined | Args | readonly (string | number | boolean | null | undefined | Args)[]; - }; + // The actual constraint is that JSON.stringify be able to serialize it without throwing. + interface Args { + [key: string]: string | number | boolean | null | undefined | Args | readonly (string | number | boolean | null | undefined | Args)[]; + }; - /** Starts tracing for the given project. */ - export function startTracing(tracingMode: Mode, traceDir: string, configFilePath?: string) { - Debug.assert(!tracing, "Tracing already started"); + /** Starts tracing for the given project. */ + export function startTracing(tracingMode: Mode, traceDir: string, configFilePath?: string) { + Debug.assert(!tracing, "Tracing already started"); - if (fs === undefined) { - try { - fs = require("fs"); - } - catch (e) { - throw new Error(`tracing requires having fs\n(original error: ${e.message || e})`); - } + if (fs === undefined) { + try { + fs = require("fs"); } - - mode = tracingMode; - typeCatalog.length = 0; - - if (legendPath === undefined) { - legendPath = combinePaths(traceDir, "legend.json"); + catch (e) { + throw new Error(`tracing requires having fs\n(original error: ${e.message || e})`); } + } - // Note that writing will fail later on if it exists and is not a directory - if (!fs.existsSync(traceDir)) { - fs.mkdirSync(traceDir, { recursive: true }); - } + mode = tracingMode; + typeCatalog.length = 0; - const countPart = - mode === "build" ? `.${process.pid}-${++traceCount}` - : mode === "server" ? `.${process.pid}` - : ``; - const tracePath = combinePaths(traceDir, `trace${countPart}.json`); - const typesPath = combinePaths(traceDir, `types${countPart}.json`); - - legend.push({ - configFilePath, - tracePath, - typesPath, - }); - - traceFd = fs.openSync(tracePath, "w"); - tracing = tracingEnabled; // only when traceFd is properly set - - // Start with a prefix that contains some metadata that the devtools profiler expects (also avoids a warning on import) - const meta = { cat: "__metadata", ph: "M", ts: 1000 * timestamp(), pid: 1, tid: 1 }; - fs.writeSync(traceFd, - "[\n" - + [{ name: "process_name", args: { name: "tsc" }, ...meta }, - { name: "thread_name", args: { name: "Main" }, ...meta }, - { name: "TracingStartedInBrowser", ...meta, cat: "disabled-by-default-devtools.timeline" }] - .map(v => JSON.stringify(v)).join(",\n")); + if (legendPath === undefined) { + legendPath = combinePaths(traceDir, "legend.json"); } - /** Stops tracing for the in-progress project and dumps the type catalog. */ - export function stopTracing() { - Debug.assert(tracing, "Tracing is not in progress"); - Debug.assert(!!typeCatalog.length === (mode !== "server")); // Have a type catalog iff not in server mode + // Note that writing will fail later on if it exists and is not a directory + if (!fs.existsSync(traceDir)) { + fs.mkdirSync(traceDir, { recursive: true }); + } - fs.writeSync(traceFd, `\n]\n`); - fs.closeSync(traceFd); - tracing = undefined; + const countPart = + mode === "build" ? `.${process.pid}-${++traceCount}` + : mode === "server" ? `.${process.pid}` + : ``; + const tracePath = combinePaths(traceDir, `trace${countPart}.json`); + const typesPath = combinePaths(traceDir, `types${countPart}.json`); + + legend.push({ + configFilePath, + tracePath, + typesPath, + }); + + traceFd = fs.openSync(tracePath, "w"); + tracing = tracingEnabled; // only when traceFd is properly set + + // Start with a prefix that contains some metadata that the devtools profiler expects (also avoids a warning on import) + const meta = { cat: "__metadata", ph: "M", ts: 1000 * timestamp(), pid: 1, tid: 1 }; + fs.writeSync(traceFd, + "[\n" + + [{ name: "process_name", args: { name: "tsc" }, ...meta }, + { name: "thread_name", args: { name: "Main" }, ...meta }, + { name: "TracingStartedInBrowser", ...meta, cat: "disabled-by-default-devtools.timeline" }] + .map(v => JSON.stringify(v)).join(",\n")); + } - if (typeCatalog.length) { - dumpTypes(typeCatalog); - } - else { - // We pre-computed this path for convenience, but clear it - // now that the file won't be created. - legend[legend.length - 1].typesPath = undefined; - } - } + /** Stops tracing for the in-progress project and dumps the type catalog. */ + export function stopTracing() { + Debug.assert(tracing, "Tracing is not in progress"); + Debug.assert(!!typeCatalog.length === (mode !== "server")); // Have a type catalog iff not in server mode - export function recordType(type: Type): void { - if (mode !== "server") { - typeCatalog.push(type); - } - } + fs.writeSync(traceFd, `\n]\n`); + fs.closeSync(traceFd); + tracing = undefined; - export const enum Phase { - Parse = "parse", - Program = "program", - Bind = "bind", - Check = "check", // Before we get into checking types (e.g. checkSourceFile) - CheckTypes = "checkTypes", - Emit = "emit", - Session = "session", + if (typeCatalog.length) { + dumpTypes(typeCatalog); + } + else { + // We pre-computed this path for convenience, but clear it + // now that the file won't be created. + legend[legend.length - 1].typesPath = undefined; } + } - export function instant(phase: Phase, name: string, args?: Args) { - writeEvent("I", phase, name, args, `"s":"g"`); + export function recordType(type: Type): void { + if (mode !== "server") { + typeCatalog.push(type); } + } - const eventStack: { phase: Phase, name: string, args?: Args, time: number, separateBeginAndEnd: boolean }[] = []; - - /** - * @param separateBeginAndEnd - used for special cases where we need the trace point even if the event - * never terminates (typically for reducing a scenario too big to trace to one that can be completed). - * In the future we might implement an exit handler to dump unfinished events which would deprecate - * these operations. - */ - export function push(phase: Phase, name: string, args?: Args, separateBeginAndEnd = false) { - if (separateBeginAndEnd) { - writeEvent("B", phase, name, args); - } - eventStack.push({ phase, name, args, time: 1000 * timestamp(), separateBeginAndEnd }); + export const enum Phase { + Parse = "parse", + Program = "program", + Bind = "bind", + Check = "check", // Before we get into checking types (e.g. checkSourceFile) + CheckTypes = "checkTypes", + Emit = "emit", + Session = "session", + } + + export function instant(phase: Phase, name: string, args?: Args) { + writeEvent("I", phase, name, args, `"s":"g"`); + } + + const eventStack: { phase: Phase, name: string, args?: Args, time: number, separateBeginAndEnd: boolean }[] = []; + + /** + * @param separateBeginAndEnd - used for special cases where we need the trace point even if the event + * never terminates (typically for reducing a scenario too big to trace to one that can be completed). + * In the future we might implement an exit handler to dump unfinished events which would deprecate + * these operations. + */ + export function push(phase: Phase, name: string, args?: Args, separateBeginAndEnd = false) { + if (separateBeginAndEnd) { + writeEvent("B", phase, name, args); } - export function pop() { - Debug.assert(eventStack.length > 0); - writeStackEvent(eventStack.length - 1, 1000 * timestamp()); - eventStack.length--; + eventStack.push({ phase, name, args, time: 1000 * timestamp(), separateBeginAndEnd }); + } + export function pop() { + Debug.assert(eventStack.length > 0); + writeStackEvent(eventStack.length - 1, 1000 * timestamp()); + eventStack.length--; + } + export function popAll() { + const endTime = 1000 * timestamp(); + for (let i = eventStack.length - 1; i >= 0; i--) { + writeStackEvent(i, endTime); } - export function popAll() { - const endTime = 1000 * timestamp(); - for (let i = eventStack.length - 1; i >= 0; i--) { - writeStackEvent(i, endTime); - } - eventStack.length = 0; + eventStack.length = 0; + } + // sample every 10ms + const sampleInterval = 1000 * 10; + function writeStackEvent(index: number, endTime: number) { + const { phase, name, args, time, separateBeginAndEnd } = eventStack[index]; + if (separateBeginAndEnd) { + writeEvent("E", phase, name, args, /*extras*/ undefined, endTime); } - // sample every 10ms - const sampleInterval = 1000 * 10; - function writeStackEvent(index: number, endTime: number) { - const { phase, name, args, time, separateBeginAndEnd } = eventStack[index]; - if (separateBeginAndEnd) { - writeEvent("E", phase, name, args, /*extras*/ undefined, endTime); - } - // test if [time,endTime) straddles a sampling point - else if (sampleInterval - (time % sampleInterval) <= endTime - time) { - writeEvent("X", phase, name, args, `"dur":${endTime - time}`, time); - } + // test if [time,endTime) straddles a sampling point + else if (sampleInterval - (time % sampleInterval) <= endTime - time) { + writeEvent("X", phase, name, args, `"dur":${endTime - time}`, time); } + } - function writeEvent(eventType: string, phase: Phase, name: string, args: Args | undefined, extras?: string, - time: number = 1000 * timestamp()) { - - // In server mode, there's no easy way to dump type information, so we drop events that would require it. - if (mode === "server" && phase === Phase.CheckTypes) return; + function writeEvent(eventType: string, phase: Phase, name: string, args: Args | undefined, extras?: string, + time: number = 1000 * timestamp()) { - performance.mark("beginTracing"); - fs.writeSync(traceFd, `,\n{"pid":1,"tid":1,"ph":"${eventType}","cat":"${phase}","ts":${time},"name":"${name}"`); - if (extras) fs.writeSync(traceFd, `,${extras}`); - if (args) fs.writeSync(traceFd, `,"args":${JSON.stringify(args)}`); - fs.writeSync(traceFd, `}`); - performance.mark("endTracing"); - performance.measure("Tracing", "beginTracing", "endTracing"); - } + // In server mode, there's no easy way to dump type information, so we drop events that would require it. + if (mode === "server" && phase === Phase.CheckTypes) return; - function getLocation(node: Node | undefined) { - const file = getSourceFileOfNode(node); - return !file - ? undefined - : { - path: file.path, - start: indexFromOne(getLineAndCharacterOfPosition(file, node!.pos)), - end: indexFromOne(getLineAndCharacterOfPosition(file, node!.end)), - }; + performance.mark("beginTracing"); + fs.writeSync(traceFd, `,\n{"pid":1,"tid":1,"ph":"${eventType}","cat":"${phase}","ts":${time},"name":"${name}"`); + if (extras) fs.writeSync(traceFd, `,${extras}`); + if (args) fs.writeSync(traceFd, `,"args":${JSON.stringify(args)}`); + fs.writeSync(traceFd, `}`); + performance.mark("endTracing"); + performance.measure("Tracing", "beginTracing", "endTracing"); + } - function indexFromOne(lc: LineAndCharacter): LineAndCharacter { - return { - line: lc.line + 1, - character: lc.character + 1, - }; - } + function getLocation(node: Node | undefined) { + const file = getSourceFileOfNode(node); + return !file + ? undefined + : { + path: file.path, + start: indexFromOne(getLineAndCharacterOfPosition(file, node!.pos)), + end: indexFromOne(getLineAndCharacterOfPosition(file, node!.end)), + }; + + function indexFromOne(lc: LineAndCharacter): LineAndCharacter { + return { + line: lc.line + 1, + character: lc.character + 1, + }; } + } - function dumpTypes(types: readonly Type[]) { - performance.mark("beginDumpTypes"); - - const typesPath = legend[legend.length - 1].typesPath!; - const typesFd = fs.openSync(typesPath, "w"); + function dumpTypes(types: readonly Type[]) { + performance.mark("beginDumpTypes"); - const recursionIdentityMap = new Map(); + const typesPath = legend[legend.length - 1].typesPath!; + const typesFd = fs.openSync(typesPath, "w"); - // Cleverness: no line break here so that the type ID will match the line number - fs.writeSync(typesFd, "["); + const recursionIdentityMap = new Map(); - const numTypes = types.length; - for (let i = 0; i < numTypes; i++) { - const type = types[i]; - const objectFlags = (type as any).objectFlags; - const symbol = type.aliasSymbol ?? type.symbol; + // Cleverness: no line break here so that the type ID will match the line number + fs.writeSync(typesFd, "["); - // It's slow to compute the display text, so skip it unless it's really valuable (or cheap) - let display: string | undefined; - if ((objectFlags & ObjectFlags.Anonymous) | (type.flags & TypeFlags.Literal)) { - try { - display = type.checker?.typeToString(type); - } - catch { - display = undefined; - } - } + const numTypes = types.length; + for (let i = 0; i < numTypes; i++) { + const type = types[i]; + const objectFlags = (type as any).objectFlags; + const symbol = type.aliasSymbol ?? type.symbol; - let indexedAccessProperties: object = {}; - if (type.flags & TypeFlags.IndexedAccess) { - const indexedAccessType = type as IndexedAccessType; - indexedAccessProperties = { - indexedAccessObjectType: indexedAccessType.objectType?.id, - indexedAccessIndexType: indexedAccessType.indexType?.id, - }; + // It's slow to compute the display text, so skip it unless it's really valuable (or cheap) + let display: string | undefined; + if ((objectFlags & ObjectFlags.Anonymous) | (type.flags & TypeFlags.Literal)) { + try { + display = type.checker?.typeToString(type); } - - let referenceProperties: object = {}; - if (objectFlags & ObjectFlags.Reference) { - const referenceType = type as TypeReference; - referenceProperties = { - instantiatedType: referenceType.target?.id, - typeArguments: referenceType.resolvedTypeArguments?.map(t => t.id), - referenceLocation: getLocation(referenceType.node), - }; + catch { + display = undefined; } + } - let conditionalProperties: object = {}; - if (type.flags & TypeFlags.Conditional) { - const conditionalType = type as ConditionalType; - conditionalProperties = { - conditionalCheckType: conditionalType.checkType?.id, - conditionalExtendsType: conditionalType.extendsType?.id, - conditionalTrueType: conditionalType.resolvedTrueType?.id ?? -1, - conditionalFalseType: conditionalType.resolvedFalseType?.id ?? -1, - }; - } + let indexedAccessProperties: object = {}; + if (type.flags & TypeFlags.IndexedAccess) { + const indexedAccessType = type as IndexedAccessType; + indexedAccessProperties = { + indexedAccessObjectType: indexedAccessType.objectType?.id, + indexedAccessIndexType: indexedAccessType.indexType?.id, + }; + } - let substitutionProperties: object = {}; - if (type.flags & TypeFlags.Substitution) { - const substitutionType = type as SubstitutionType; - substitutionProperties = { - substitutionBaseType: substitutionType.baseType?.id, - substituteType: substitutionType.substitute?.id, - }; - } + let referenceProperties: object = {}; + if (objectFlags & ObjectFlags.Reference) { + const referenceType = type as TypeReference; + referenceProperties = { + instantiatedType: referenceType.target?.id, + typeArguments: referenceType.resolvedTypeArguments?.map(t => t.id), + referenceLocation: getLocation(referenceType.node), + }; + } - let reverseMappedProperties: object = {}; - if (objectFlags & ObjectFlags.ReverseMapped) { - const reverseMappedType = type as ReverseMappedType; - reverseMappedProperties = { - reverseMappedSourceType: reverseMappedType.source?.id, - reverseMappedMappedType: reverseMappedType.mappedType?.id, - reverseMappedConstraintType: reverseMappedType.constraintType?.id, - }; - } + let conditionalProperties: object = {}; + if (type.flags & TypeFlags.Conditional) { + const conditionalType = type as ConditionalType; + conditionalProperties = { + conditionalCheckType: conditionalType.checkType?.id, + conditionalExtendsType: conditionalType.extendsType?.id, + conditionalTrueType: conditionalType.resolvedTrueType?.id ?? -1, + conditionalFalseType: conditionalType.resolvedFalseType?.id ?? -1, + }; + } - let evolvingArrayProperties: object = {}; - if (objectFlags & ObjectFlags.EvolvingArray) { - const evolvingArrayType = type as EvolvingArrayType; - evolvingArrayProperties = { - evolvingArrayElementType: evolvingArrayType.elementType.id, - evolvingArrayFinalType: evolvingArrayType.finalArrayType?.id, - }; - } + let substitutionProperties: object = {}; + if (type.flags & TypeFlags.Substitution) { + const substitutionType = type as SubstitutionType; + substitutionProperties = { + substitutionBaseType: substitutionType.baseType?.id, + substituteType: substitutionType.substitute?.id, + }; + } - // We can't print out an arbitrary object, so just assign each one a unique number. - // Don't call it an "id" so people don't treat it as a type id. - let recursionToken: number | undefined; - const recursionIdentity = type.checker.getRecursionIdentity(type); - if (recursionIdentity) { - recursionToken = recursionIdentityMap.get(recursionIdentity); - if (!recursionToken) { - recursionToken = recursionIdentityMap.size; - recursionIdentityMap.set(recursionIdentity, recursionToken); - } - } + let reverseMappedProperties: object = {}; + if (objectFlags & ObjectFlags.ReverseMapped) { + const reverseMappedType = type as ReverseMappedType; + reverseMappedProperties = { + reverseMappedSourceType: reverseMappedType.source?.id, + reverseMappedMappedType: reverseMappedType.mappedType?.id, + reverseMappedConstraintType: reverseMappedType.constraintType?.id, + }; + } - const descriptor = { - id: type.id, - intrinsicName: (type as any).intrinsicName, - symbolName: symbol?.escapedName && unescapeLeadingUnderscores(symbol.escapedName), - recursionId: recursionToken, - isTuple: objectFlags & ObjectFlags.Tuple ? true : undefined, - unionTypes: (type.flags & TypeFlags.Union) ? (type as UnionType).types?.map(t => t.id) : undefined, - intersectionTypes: (type.flags & TypeFlags.Intersection) ? (type as IntersectionType).types.map(t => t.id) : undefined, - aliasTypeArguments: type.aliasTypeArguments?.map(t => t.id), - keyofType: (type.flags & TypeFlags.Index) ? (type as IndexType).type?.id : undefined, - ...indexedAccessProperties, - ...referenceProperties, - ...conditionalProperties, - ...substitutionProperties, - ...reverseMappedProperties, - ...evolvingArrayProperties, - destructuringPattern: getLocation(type.pattern), - firstDeclaration: getLocation(symbol?.declarations?.[0]), - flags: Debug.formatTypeFlags(type.flags).split("|"), - display, + let evolvingArrayProperties: object = {}; + if (objectFlags & ObjectFlags.EvolvingArray) { + const evolvingArrayType = type as EvolvingArrayType; + evolvingArrayProperties = { + evolvingArrayElementType: evolvingArrayType.elementType.id, + evolvingArrayFinalType: evolvingArrayType.finalArrayType?.id, }; + } - fs.writeSync(typesFd, JSON.stringify(descriptor)); - if (i < numTypes - 1) { - fs.writeSync(typesFd, ",\n"); + // We can't print out an arbitrary object, so just assign each one a unique number. + // Don't call it an "id" so people don't treat it as a type id. + let recursionToken: number | undefined; + const recursionIdentity = type.checker.getRecursionIdentity(type); + if (recursionIdentity) { + recursionToken = recursionIdentityMap.get(recursionIdentity); + if (!recursionToken) { + recursionToken = recursionIdentityMap.size; + recursionIdentityMap.set(recursionIdentity, recursionToken); } } - fs.writeSync(typesFd, "]\n"); + const descriptor = { + id: type.id, + intrinsicName: (type as any).intrinsicName, + symbolName: symbol?.escapedName && unescapeLeadingUnderscores(symbol.escapedName), + recursionId: recursionToken, + isTuple: objectFlags & ObjectFlags.Tuple ? true : undefined, + unionTypes: (type.flags & TypeFlags.Union) ? (type as UnionType).types?.map(t => t.id) : undefined, + intersectionTypes: (type.flags & TypeFlags.Intersection) ? (type as IntersectionType).types.map(t => t.id) : undefined, + aliasTypeArguments: type.aliasTypeArguments?.map(t => t.id), + keyofType: (type.flags & TypeFlags.Index) ? (type as IndexType).type?.id : undefined, + ...indexedAccessProperties, + ...referenceProperties, + ...conditionalProperties, + ...substitutionProperties, + ...reverseMappedProperties, + ...evolvingArrayProperties, + destructuringPattern: getLocation(type.pattern), + firstDeclaration: getLocation(symbol?.declarations?.[0]), + flags: Debug.formatTypeFlags(type.flags).split("|"), + display, + }; + + fs.writeSync(typesFd, JSON.stringify(descriptor)); + if (i < numTypes - 1) { + fs.writeSync(typesFd, ",\n"); + } + } - fs.closeSync(typesFd); + fs.writeSync(typesFd, "]\n"); - performance.mark("endDumpTypes"); - performance.measure("Dump types", "beginDumpTypes", "endDumpTypes"); - } + fs.closeSync(typesFd); - export function dumpLegend() { - if (!legendPath) { - return; - } + performance.mark("endDumpTypes"); + performance.measure("Dump types", "beginDumpTypes", "endDumpTypes"); + } - fs.writeFileSync(legendPath, JSON.stringify(legend)); + export function dumpLegend() { + if (!legendPath) { + return; } - interface TraceRecord { - configFilePath?: string; - tracePath: string; - typesPath?: string; - } + fs.writeFileSync(legendPath, JSON.stringify(legend)); + } + + interface TraceRecord { + configFilePath?: string; + tracePath: string; + typesPath?: string; } +} - // define after tracingEnabled is initialized - export const startTracing = tracingEnabled.startTracing; - export const dumpTracingLegend = tracingEnabled.dumpLegend; +// define after tracingEnabled is initialized +export const startTracing = tracingEnabled.startTracing; +export const dumpTracingLegend = tracingEnabled.dumpLegend; } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 7207d8a60d75b..d4eedc5fb6211 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -1,596 +1,596 @@ /* @internal */ namespace ts { - function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory { - switch (moduleKind) { - case ModuleKind.ESNext: - case ModuleKind.ES2022: - case ModuleKind.ES2020: - case ModuleKind.ES2015: - return transformECMAScriptModule; - case ModuleKind.System: - return transformSystemModule; - case ModuleKind.Node12: - case ModuleKind.NodeNext: - return transformNodeModule; - default: - return transformModule; - } +function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory { + switch (moduleKind) { + case ModuleKind.ESNext: + case ModuleKind.ES2022: + case ModuleKind.ES2020: + case ModuleKind.ES2015: + return transformECMAScriptModule; + case ModuleKind.System: + return transformSystemModule; + case ModuleKind.Node12: + case ModuleKind.NodeNext: + return transformNodeModule; + default: + return transformModule; } +} + +const enum TransformationState { + Uninitialized, + Initialized, + Completed, + Disposed +} + +const enum SyntaxKindFeatureFlags { + Substitution = 1 << 0, + EmitNotifications = 1 << 1, +} + +export const noTransformers: EmitTransformers = { scriptTransformers: emptyArray, declarationTransformers: emptyArray }; + +export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean): EmitTransformers { + return { + scriptTransformers: getScriptTransformers(compilerOptions, customTransformers, emitOnlyDtsFiles), + declarationTransformers: getDeclarationTransformers(customTransformers), + }; +} - const enum TransformationState { - Uninitialized, - Initialized, - Completed, - Disposed +function getScriptTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean) { + if (emitOnlyDtsFiles) return emptyArray; + + const languageVersion = getEmitScriptTarget(compilerOptions); + const moduleKind = getEmitModuleKind(compilerOptions); + const transformers: TransformerFactory[] = []; + + addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory)); + + transformers.push(transformTypeScript); + transformers.push(transformClassFields); + + if (getJSXTransformEnabled(compilerOptions)) { + transformers.push(transformJsx); } - const enum SyntaxKindFeatureFlags { - Substitution = 1 << 0, - EmitNotifications = 1 << 1, + if (languageVersion < ScriptTarget.ESNext) { + transformers.push(transformESNext); } - export const noTransformers: EmitTransformers = { scriptTransformers: emptyArray, declarationTransformers: emptyArray }; + if (languageVersion < ScriptTarget.ES2021) { + transformers.push(transformES2021); + } - export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean): EmitTransformers { - return { - scriptTransformers: getScriptTransformers(compilerOptions, customTransformers, emitOnlyDtsFiles), - declarationTransformers: getDeclarationTransformers(customTransformers), - }; + if (languageVersion < ScriptTarget.ES2020) { + transformers.push(transformES2020); } - function getScriptTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean) { - if (emitOnlyDtsFiles) return emptyArray; + if (languageVersion < ScriptTarget.ES2019) { + transformers.push(transformES2019); + } + + if (languageVersion < ScriptTarget.ES2018) { + transformers.push(transformES2018); + } - const languageVersion = getEmitScriptTarget(compilerOptions); - const moduleKind = getEmitModuleKind(compilerOptions); - const transformers: TransformerFactory[] = []; + if (languageVersion < ScriptTarget.ES2017) { + transformers.push(transformES2017); + } - addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory)); + if (languageVersion < ScriptTarget.ES2016) { + transformers.push(transformES2016); + } - transformers.push(transformTypeScript); - transformers.push(transformClassFields); + if (languageVersion < ScriptTarget.ES2015) { + transformers.push(transformES2015); + transformers.push(transformGenerators); + } - if (getJSXTransformEnabled(compilerOptions)) { - transformers.push(transformJsx); - } + transformers.push(getModuleTransformer(moduleKind)); - if (languageVersion < ScriptTarget.ESNext) { - transformers.push(transformESNext); - } + // The ES5 transformer is last so that it can substitute expressions like `exports.default` + // for ES3. + if (languageVersion < ScriptTarget.ES5) { + transformers.push(transformES5); + } - if (languageVersion < ScriptTarget.ES2021) { - transformers.push(transformES2021); - } + addRange(transformers, customTransformers && map(customTransformers.after, wrapScriptTransformerFactory)); + return transformers; +} - if (languageVersion < ScriptTarget.ES2020) { - transformers.push(transformES2020); - } +function getDeclarationTransformers(customTransformers?: CustomTransformers) { + const transformers: TransformerFactory[] = []; + transformers.push(transformDeclarations); + addRange(transformers, customTransformers && map(customTransformers.afterDeclarations, wrapDeclarationTransformerFactory)); + return transformers; +} - if (languageVersion < ScriptTarget.ES2019) { - transformers.push(transformES2019); - } +/** + * Wrap a custom script or declaration transformer object in a `Transformer` callback with fallback support for transforming bundles. + */ +function wrapCustomTransformer(transformer: CustomTransformer): Transformer { + return node => isBundle(node) ? transformer.transformBundle(node) : transformer.transformSourceFile(node); +} - if (languageVersion < ScriptTarget.ES2018) { - transformers.push(transformES2018); - } +/** + * Wrap a transformer factory that may return a custom script or declaration transformer object. + */ +function wrapCustomTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory, handleDefault: (context: TransformationContext, tx: Transformer) => Transformer): TransformerFactory { + return context => { + const customTransformer = transformer(context); + return typeof customTransformer === "function" + ? handleDefault(context, customTransformer) + : wrapCustomTransformer(customTransformer); + }; +} - if (languageVersion < ScriptTarget.ES2017) { - transformers.push(transformES2017); - } +function wrapScriptTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { + return wrapCustomTransformerFactory(transformer, chainBundle); +} - if (languageVersion < ScriptTarget.ES2016) { - transformers.push(transformES2016); - } +function wrapDeclarationTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { + return wrapCustomTransformerFactory(transformer, (_, node) => node); +} - if (languageVersion < ScriptTarget.ES2015) { - transformers.push(transformES2015); - transformers.push(transformGenerators); +export function noEmitSubstitution(_hint: EmitHint, node: Node) { + return node; +} + +export function noEmitNotification(hint: EmitHint, node: Node, callback: (hint: EmitHint, node: Node) => void) { + callback(hint, node); +} + +/** + * Transforms an array of SourceFiles by passing them through each transformer. + * + * @param resolver The emit resolver provided by the checker. + * @param host The emit host object used to interact with the file system. + * @param options Compiler options to surface in the `TransformationContext`. + * @param nodes An array of nodes to transform. + * @param transforms An array of `TransformerFactory` callbacks. + * @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files. + */ +export function transformNodes(resolver: EmitResolver | undefined, host: EmitHost | undefined, factory: NodeFactory, options: CompilerOptions, nodes: readonly T[], transformers: readonly TransformerFactory[], allowDtsFiles: boolean): TransformationResult { + const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); + let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; + let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; + let lexicalEnvironmentStatements: Statement[]; + let lexicalEnvironmentFlags = LexicalEnvironmentFlags.None; + let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; + let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; + let lexicalEnvironmentStatementsStack: Statement[][] = []; + let lexicalEnvironmentFlagsStack: LexicalEnvironmentFlags[] = []; + let lexicalEnvironmentStackOffset = 0; + let lexicalEnvironmentSuspended = false; + let blockScopedVariableDeclarationsStack: Identifier[][] = []; + let blockScopeStackOffset = 0; + let blockScopedVariableDeclarations: Identifier[]; + let emitHelpers: EmitHelper[] | undefined; + let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution; + let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification; + let state = TransformationState.Uninitialized; + const diagnostics: DiagnosticWithLocation[] = []; + + // The transformation context is provided to each transformer as part of transformer + // initialization. + const context: TransformationContext = { + factory, + getCompilerOptions: () => options, + getEmitResolver: () => resolver!, // TODO: GH#18217 + getEmitHost: () => host!, // TODO: GH#18217 + getEmitHelperFactory: memoize(() => createEmitHelperFactory(context)), + startLexicalEnvironment, + suspendLexicalEnvironment, + resumeLexicalEnvironment, + endLexicalEnvironment, + setLexicalEnvironmentFlags, + getLexicalEnvironmentFlags, + hoistVariableDeclaration, + hoistFunctionDeclaration, + addInitializationStatement, + startBlockScope, + endBlockScope, + addBlockScopedVariable, + requestEmitHelper, + readEmitHelpers, + enableSubstitution, + enableEmitNotification, + isSubstitutionEnabled, + isEmitNotificationEnabled, + get onSubstituteNode() { return onSubstituteNode; }, + set onSubstituteNode(value) { + Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); + Debug.assert(value !== undefined, "Value must not be 'undefined'"); + onSubstituteNode = value; + }, + get onEmitNode() { return onEmitNode; }, + set onEmitNode(value) { + Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); + Debug.assert(value !== undefined, "Value must not be 'undefined'"); + onEmitNode = value; + }, + addDiagnostic(diag) { + diagnostics.push(diag); } + }; + + // Ensure the parse tree is clean before applying transformations + for (const node of nodes) { + disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); + } - transformers.push(getModuleTransformer(moduleKind)); + performance.mark("beforeTransform"); - // The ES5 transformer is last so that it can substitute expressions like `exports.default` - // for ES3. - if (languageVersion < ScriptTarget.ES5) { - transformers.push(transformES5); + // Chain together and initialize each transformer. + const transformersWithContext = transformers.map(t => t(context)); + const transformation = (node: T): T => { + for (const transform of transformersWithContext) { + node = transform(node); } + return node; + }; - addRange(transformers, customTransformers && map(customTransformers.after, wrapScriptTransformerFactory)); - return transformers; + // prevent modification of transformation hooks. + state = TransformationState.Initialized; + + // Transform each node. + const transformed: T[] = []; + for (const node of nodes) { + tracing?.push(tracing.Phase.Emit, "transformNodes", node.kind === SyntaxKind.SourceFile ? { path: (node as any as SourceFile).path } : { kind: node.kind, pos: node.pos, end: node.end }); + transformed.push((allowDtsFiles ? transformation : transformRoot)(node)); + tracing?.pop(); } - function getDeclarationTransformers(customTransformers?: CustomTransformers) { - const transformers: TransformerFactory[] = []; - transformers.push(transformDeclarations); - addRange(transformers, customTransformers && map(customTransformers.afterDeclarations, wrapDeclarationTransformerFactory)); - return transformers; + // prevent modification of the lexical environment. + state = TransformationState.Completed; + + performance.mark("afterTransform"); + performance.measure("transformTime", "beforeTransform", "afterTransform"); + + return { + transformed, + substituteNode, + emitNodeWithNotification, + isEmitNotificationEnabled, + dispose, + diagnostics + }; + + function transformRoot(node: T) { + return node && (!isSourceFile(node) || !node.isDeclarationFile) ? transformation(node) : node; } /** - * Wrap a custom script or declaration transformer object in a `Transformer` callback with fallback support for transforming bundles. + * Enables expression substitutions in the pretty printer for the provided SyntaxKind. */ - function wrapCustomTransformer(transformer: CustomTransformer): Transformer { - return node => isBundle(node) ? transformer.transformBundle(node) : transformer.transformSourceFile(node); + function enableSubstitution(kind: SyntaxKind) { + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution; } /** - * Wrap a transformer factory that may return a custom script or declaration transformer object. + * Determines whether expression substitutions are enabled for the provided node. */ - function wrapCustomTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory, handleDefault: (context: TransformationContext, tx: Transformer) => Transformer): TransformerFactory { - return context => { - const customTransformer = transformer(context); - return typeof customTransformer === "function" - ? handleDefault(context, customTransformer) - : wrapCustomTransformer(customTransformer); - }; - } - - function wrapScriptTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { - return wrapCustomTransformerFactory(transformer, chainBundle); + function isSubstitutionEnabled(node: Node) { + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0 + && (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0; } - function wrapDeclarationTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { - return wrapCustomTransformerFactory(transformer, (_, node) => node); + /** + * Emits a node with possible substitution. + * + * @param hint A hint as to the intended usage of the node. + * @param node The node to emit. + * @param emitCallback The callback used to emit the node or its substitute. + */ + function substituteNode(hint: EmitHint, node: Node) { + Debug.assert(state < TransformationState.Disposed, "Cannot substitute a node after the result is disposed."); + return node && isSubstitutionEnabled(node) && onSubstituteNode(hint, node) || node; } - export function noEmitSubstitution(_hint: EmitHint, node: Node) { - return node; + /** + * Enables before/after emit notifications in the pretty printer for the provided SyntaxKind. + */ + function enableEmitNotification(kind: SyntaxKind) { + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; } - export function noEmitNotification(hint: EmitHint, node: Node, callback: (hint: EmitHint, node: Node) => void) { - callback(hint, node); + /** + * Determines whether before/after emit notifications should be raised in the pretty + * printer when it emits a node. + */ + function isEmitNotificationEnabled(node: Node) { + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0 + || (getEmitFlags(node) & EmitFlags.AdviseOnEmitNode) !== 0; } /** - * Transforms an array of SourceFiles by passing them through each transformer. + * Emits a node with possible emit notification. * - * @param resolver The emit resolver provided by the checker. - * @param host The emit host object used to interact with the file system. - * @param options Compiler options to surface in the `TransformationContext`. - * @param nodes An array of nodes to transform. - * @param transforms An array of `TransformerFactory` callbacks. - * @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files. + * @param hint A hint as to the intended usage of the node. + * @param node The node to emit. + * @param emitCallback The callback used to emit the node. */ - export function transformNodes(resolver: EmitResolver | undefined, host: EmitHost | undefined, factory: NodeFactory, options: CompilerOptions, nodes: readonly T[], transformers: readonly TransformerFactory[], allowDtsFiles: boolean): TransformationResult { - const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); - let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; - let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; - let lexicalEnvironmentStatements: Statement[]; - let lexicalEnvironmentFlags = LexicalEnvironmentFlags.None; - let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; - let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; - let lexicalEnvironmentStatementsStack: Statement[][] = []; - let lexicalEnvironmentFlagsStack: LexicalEnvironmentFlags[] = []; - let lexicalEnvironmentStackOffset = 0; - let lexicalEnvironmentSuspended = false; - let blockScopedVariableDeclarationsStack: Identifier[][] = []; - let blockScopeStackOffset = 0; - let blockScopedVariableDeclarations: Identifier[]; - let emitHelpers: EmitHelper[] | undefined; - let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution; - let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification; - let state = TransformationState.Uninitialized; - const diagnostics: DiagnosticWithLocation[] = []; - - // The transformation context is provided to each transformer as part of transformer - // initialization. - const context: TransformationContext = { - factory, - getCompilerOptions: () => options, - getEmitResolver: () => resolver!, // TODO: GH#18217 - getEmitHost: () => host!, // TODO: GH#18217 - getEmitHelperFactory: memoize(() => createEmitHelperFactory(context)), - startLexicalEnvironment, - suspendLexicalEnvironment, - resumeLexicalEnvironment, - endLexicalEnvironment, - setLexicalEnvironmentFlags, - getLexicalEnvironmentFlags, - hoistVariableDeclaration, - hoistFunctionDeclaration, - addInitializationStatement, - startBlockScope, - endBlockScope, - addBlockScopedVariable, - requestEmitHelper, - readEmitHelpers, - enableSubstitution, - enableEmitNotification, - isSubstitutionEnabled, - isEmitNotificationEnabled, - get onSubstituteNode() { return onSubstituteNode; }, - set onSubstituteNode(value) { - Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); - Debug.assert(value !== undefined, "Value must not be 'undefined'"); - onSubstituteNode = value; - }, - get onEmitNode() { return onEmitNode; }, - set onEmitNode(value) { - Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); - Debug.assert(value !== undefined, "Value must not be 'undefined'"); - onEmitNode = value; - }, - addDiagnostic(diag) { - diagnostics.push(diag); + function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { + Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); + if (node) { + // TODO: Remove check and unconditionally use onEmitNode when API is breakingly changed + // (see https://github.com/microsoft/TypeScript/pull/36248/files/5062623f39120171b98870c71344b3242eb03d23#r369766739) + if (isEmitNotificationEnabled(node)) { + onEmitNode(hint, node, emitCallback); } - }; - - // Ensure the parse tree is clean before applying transformations - for (const node of nodes) { - disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); - } - - performance.mark("beforeTransform"); - - // Chain together and initialize each transformer. - const transformersWithContext = transformers.map(t => t(context)); - const transformation = (node: T): T => { - for (const transform of transformersWithContext) { - node = transform(node); + else { + emitCallback(hint, node); } - return node; - }; - - // prevent modification of transformation hooks. - state = TransformationState.Initialized; - - // Transform each node. - const transformed: T[] = []; - for (const node of nodes) { - tracing?.push(tracing.Phase.Emit, "transformNodes", node.kind === SyntaxKind.SourceFile ? { path: (node as any as SourceFile).path } : { kind: node.kind, pos: node.pos, end: node.end }); - transformed.push((allowDtsFiles ? transformation : transformRoot)(node)); - tracing?.pop(); - } - - // prevent modification of the lexical environment. - state = TransformationState.Completed; - - performance.mark("afterTransform"); - performance.measure("transformTime", "beforeTransform", "afterTransform"); - - return { - transformed, - substituteNode, - emitNodeWithNotification, - isEmitNotificationEnabled, - dispose, - diagnostics - }; - - function transformRoot(node: T) { - return node && (!isSourceFile(node) || !node.isDeclarationFile) ? transformation(node) : node; - } - - /** - * Enables expression substitutions in the pretty printer for the provided SyntaxKind. - */ - function enableSubstitution(kind: SyntaxKind) { - Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution; - } - - /** - * Determines whether expression substitutions are enabled for the provided node. - */ - function isSubstitutionEnabled(node: Node) { - return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0 - && (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0; } + } - /** - * Emits a node with possible substitution. - * - * @param hint A hint as to the intended usage of the node. - * @param node The node to emit. - * @param emitCallback The callback used to emit the node or its substitute. - */ - function substituteNode(hint: EmitHint, node: Node) { - Debug.assert(state < TransformationState.Disposed, "Cannot substitute a node after the result is disposed."); - return node && isSubstitutionEnabled(node) && onSubstituteNode(hint, node) || node; + /** + * Records a hoisted variable declaration for the provided name within a lexical environment. + */ + function hoistVariableDeclaration(name: Identifier): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + const decl = setEmitFlags(factory.createVariableDeclaration(name), EmitFlags.NoNestedSourceMaps); + if (!lexicalEnvironmentVariableDeclarations) { + lexicalEnvironmentVariableDeclarations = [decl]; } - - /** - * Enables before/after emit notifications in the pretty printer for the provided SyntaxKind. - */ - function enableEmitNotification(kind: SyntaxKind) { - Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; + else { + lexicalEnvironmentVariableDeclarations.push(decl); } - - /** - * Determines whether before/after emit notifications should be raised in the pretty - * printer when it emits a node. - */ - function isEmitNotificationEnabled(node: Node) { - return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0 - || (getEmitFlags(node) & EmitFlags.AdviseOnEmitNode) !== 0; + if (lexicalEnvironmentFlags & LexicalEnvironmentFlags.InParameters) { + lexicalEnvironmentFlags |= LexicalEnvironmentFlags.VariablesHoistedInParameters; } + } - /** - * Emits a node with possible emit notification. - * - * @param hint A hint as to the intended usage of the node. - * @param node The node to emit. - * @param emitCallback The callback used to emit the node. - */ - function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { - Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); - if (node) { - // TODO: Remove check and unconditionally use onEmitNode when API is breakingly changed - // (see https://github.com/microsoft/TypeScript/pull/36248/files/5062623f39120171b98870c71344b3242eb03d23#r369766739) - if (isEmitNotificationEnabled(node)) { - onEmitNode(hint, node, emitCallback); - } - else { - emitCallback(hint, node); - } - } + /** + * Records a hoisted function declaration within a lexical environment. + */ + function hoistFunctionDeclaration(func: FunctionDeclaration): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + setEmitFlags(func, EmitFlags.CustomPrologue); + if (!lexicalEnvironmentFunctionDeclarations) { + lexicalEnvironmentFunctionDeclarations = [func]; } - - /** - * Records a hoisted variable declaration for the provided name within a lexical environment. - */ - function hoistVariableDeclaration(name: Identifier): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - const decl = setEmitFlags(factory.createVariableDeclaration(name), EmitFlags.NoNestedSourceMaps); - if (!lexicalEnvironmentVariableDeclarations) { - lexicalEnvironmentVariableDeclarations = [decl]; - } - else { - lexicalEnvironmentVariableDeclarations.push(decl); - } - if (lexicalEnvironmentFlags & LexicalEnvironmentFlags.InParameters) { - lexicalEnvironmentFlags |= LexicalEnvironmentFlags.VariablesHoistedInParameters; - } + else { + lexicalEnvironmentFunctionDeclarations.push(func); } + } - /** - * Records a hoisted function declaration within a lexical environment. - */ - function hoistFunctionDeclaration(func: FunctionDeclaration): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - setEmitFlags(func, EmitFlags.CustomPrologue); - if (!lexicalEnvironmentFunctionDeclarations) { - lexicalEnvironmentFunctionDeclarations = [func]; - } - else { - lexicalEnvironmentFunctionDeclarations.push(func); - } + /** + * Adds an initialization statement to the top of the lexical environment. + */ + function addInitializationStatement(node: Statement): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + setEmitFlags(node, EmitFlags.CustomPrologue); + if (!lexicalEnvironmentStatements) { + lexicalEnvironmentStatements = [node]; } - - /** - * Adds an initialization statement to the top of the lexical environment. - */ - function addInitializationStatement(node: Statement): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - setEmitFlags(node, EmitFlags.CustomPrologue); - if (!lexicalEnvironmentStatements) { - lexicalEnvironmentStatements = [node]; - } - else { - lexicalEnvironmentStatements.push(node); - } + else { + lexicalEnvironmentStatements.push(node); } + } - /** - * Starts a new lexical environment. Any existing hoisted variable or function declarations - * are pushed onto a stack, and the related storage variables are reset. - */ - function startLexicalEnvironment(): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); - - // Save the current lexical environment. Rather than resizing the array we adjust the - // stack size variable. This allows us to reuse existing array slots we've - // already allocated between transformations to avoid allocation and GC overhead during - // transformation. - lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations; - lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations; - lexicalEnvironmentStatementsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentStatements; - lexicalEnvironmentFlagsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFlags; - lexicalEnvironmentStackOffset++; - lexicalEnvironmentVariableDeclarations = undefined!; - lexicalEnvironmentFunctionDeclarations = undefined!; - lexicalEnvironmentStatements = undefined!; - lexicalEnvironmentFlags = LexicalEnvironmentFlags.None; - } + /** + * Starts a new lexical environment. Any existing hoisted variable or function declarations + * are pushed onto a stack, and the related storage variables are reset. + */ + function startLexicalEnvironment(): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); + + // Save the current lexical environment. Rather than resizing the array we adjust the + // stack size variable. This allows us to reuse existing array slots we've + // already allocated between transformations to avoid allocation and GC overhead during + // transformation. + lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations; + lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations; + lexicalEnvironmentStatementsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentStatements; + lexicalEnvironmentFlagsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFlags; + lexicalEnvironmentStackOffset++; + lexicalEnvironmentVariableDeclarations = undefined!; + lexicalEnvironmentFunctionDeclarations = undefined!; + lexicalEnvironmentStatements = undefined!; + lexicalEnvironmentFlags = LexicalEnvironmentFlags.None; + } - /** Suspends the current lexical environment, usually after visiting a parameter list. */ - function suspendLexicalEnvironment(): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended."); - lexicalEnvironmentSuspended = true; - } + /** Suspends the current lexical environment, usually after visiting a parameter list. */ + function suspendLexicalEnvironment(): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended."); + lexicalEnvironmentSuspended = true; + } - /** Resumes a suspended lexical environment, usually before visiting a function body. */ - function resumeLexicalEnvironment(): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended."); - lexicalEnvironmentSuspended = false; - } + /** Resumes a suspended lexical environment, usually before visiting a function body. */ + function resumeLexicalEnvironment(): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended."); + lexicalEnvironmentSuspended = false; + } - /** - * Ends a lexical environment. The previous set of hoisted declarations are restored and - * any hoisted declarations added in this environment are returned. - */ - function endLexicalEnvironment(): Statement[] | undefined { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); - - let statements: Statement[] | undefined; - if (lexicalEnvironmentVariableDeclarations || - lexicalEnvironmentFunctionDeclarations || - lexicalEnvironmentStatements) { - if (lexicalEnvironmentFunctionDeclarations) { - statements = [...lexicalEnvironmentFunctionDeclarations]; - } + /** + * Ends a lexical environment. The previous set of hoisted declarations are restored and + * any hoisted declarations added in this environment are returned. + */ + function endLexicalEnvironment(): Statement[] | undefined { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); + + let statements: Statement[] | undefined; + if (lexicalEnvironmentVariableDeclarations || + lexicalEnvironmentFunctionDeclarations || + lexicalEnvironmentStatements) { + if (lexicalEnvironmentFunctionDeclarations) { + statements = [...lexicalEnvironmentFunctionDeclarations]; + } - if (lexicalEnvironmentVariableDeclarations) { - const statement = factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList(lexicalEnvironmentVariableDeclarations) - ); + if (lexicalEnvironmentVariableDeclarations) { + const statement = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList(lexicalEnvironmentVariableDeclarations) + ); - setEmitFlags(statement, EmitFlags.CustomPrologue); + setEmitFlags(statement, EmitFlags.CustomPrologue); - if (!statements) { - statements = [statement]; - } - else { - statements.push(statement); - } + if (!statements) { + statements = [statement]; } - - if (lexicalEnvironmentStatements) { - if (!statements) { - statements = [...lexicalEnvironmentStatements]; - } - else { - statements = [...statements, ...lexicalEnvironmentStatements]; - } + else { + statements.push(statement); } } - // Restore the previous lexical environment. - lexicalEnvironmentStackOffset--; - lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; - lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; - lexicalEnvironmentStatements = lexicalEnvironmentStatementsStack[lexicalEnvironmentStackOffset]; - lexicalEnvironmentFlags = lexicalEnvironmentFlagsStack[lexicalEnvironmentStackOffset]; - if (lexicalEnvironmentStackOffset === 0) { - lexicalEnvironmentVariableDeclarationsStack = []; - lexicalEnvironmentFunctionDeclarationsStack = []; - lexicalEnvironmentStatementsStack = []; - lexicalEnvironmentFlagsStack = []; + if (lexicalEnvironmentStatements) { + if (!statements) { + statements = [...lexicalEnvironmentStatements]; + } + else { + statements = [...statements, ...lexicalEnvironmentStatements]; + } } - return statements; } - function setLexicalEnvironmentFlags(flags: LexicalEnvironmentFlags, value: boolean): void { - lexicalEnvironmentFlags = value ? - lexicalEnvironmentFlags | flags : - lexicalEnvironmentFlags & ~flags; + // Restore the previous lexical environment. + lexicalEnvironmentStackOffset--; + lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; + lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; + lexicalEnvironmentStatements = lexicalEnvironmentStatementsStack[lexicalEnvironmentStackOffset]; + lexicalEnvironmentFlags = lexicalEnvironmentFlagsStack[lexicalEnvironmentStackOffset]; + if (lexicalEnvironmentStackOffset === 0) { + lexicalEnvironmentVariableDeclarationsStack = []; + lexicalEnvironmentFunctionDeclarationsStack = []; + lexicalEnvironmentStatementsStack = []; + lexicalEnvironmentFlagsStack = []; } + return statements; + } - function getLexicalEnvironmentFlags(): LexicalEnvironmentFlags { - return lexicalEnvironmentFlags; - } + function setLexicalEnvironmentFlags(flags: LexicalEnvironmentFlags, value: boolean): void { + lexicalEnvironmentFlags = value ? + lexicalEnvironmentFlags | flags : + lexicalEnvironmentFlags & ~flags; + } - /** - * Starts a block scope. Any existing block hoisted variables are pushed onto the stack and the related storage variables are reset. - */ - function startBlockScope() { - Debug.assert(state > TransformationState.Uninitialized, "Cannot start a block scope during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot start a block scope after transformation has completed."); - blockScopedVariableDeclarationsStack[blockScopeStackOffset] = blockScopedVariableDeclarations; - blockScopeStackOffset++; - blockScopedVariableDeclarations = undefined!; - } + function getLexicalEnvironmentFlags(): LexicalEnvironmentFlags { + return lexicalEnvironmentFlags; + } - /** - * Ends a block scope. The previous set of block hoisted variables are restored. Any hoisted declarations are returned. - */ - function endBlockScope() { - Debug.assert(state > TransformationState.Uninitialized, "Cannot end a block scope during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot end a block scope after transformation has completed."); - const statements: Statement[] | undefined = some(blockScopedVariableDeclarations) ? - [ - factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList( - blockScopedVariableDeclarations.map(identifier => factory.createVariableDeclaration(identifier)), - NodeFlags.Let - ) + /** + * Starts a block scope. Any existing block hoisted variables are pushed onto the stack and the related storage variables are reset. + */ + function startBlockScope() { + Debug.assert(state > TransformationState.Uninitialized, "Cannot start a block scope during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot start a block scope after transformation has completed."); + blockScopedVariableDeclarationsStack[blockScopeStackOffset] = blockScopedVariableDeclarations; + blockScopeStackOffset++; + blockScopedVariableDeclarations = undefined!; + } + + /** + * Ends a block scope. The previous set of block hoisted variables are restored. Any hoisted declarations are returned. + */ + function endBlockScope() { + Debug.assert(state > TransformationState.Uninitialized, "Cannot end a block scope during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot end a block scope after transformation has completed."); + const statements: Statement[] | undefined = some(blockScopedVariableDeclarations) ? + [ + factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + blockScopedVariableDeclarations.map(identifier => factory.createVariableDeclaration(identifier)), + NodeFlags.Let ) - ] : undefined; - blockScopeStackOffset--; - blockScopedVariableDeclarations = blockScopedVariableDeclarationsStack[blockScopeStackOffset]; - if (blockScopeStackOffset === 0) { - blockScopedVariableDeclarationsStack = []; + ) + ] : undefined; + blockScopeStackOffset--; + blockScopedVariableDeclarations = blockScopedVariableDeclarationsStack[blockScopeStackOffset]; + if (blockScopeStackOffset === 0) { + blockScopedVariableDeclarationsStack = []; + } + return statements; + } + + function addBlockScopedVariable(name: Identifier): void { + Debug.assert(blockScopeStackOffset > 0, "Cannot add a block scoped variable outside of an iteration body."); + (blockScopedVariableDeclarations || (blockScopedVariableDeclarations = [])).push(name); + } + + function requestEmitHelper(helper: EmitHelper): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + Debug.assert(!helper.scoped, "Cannot request a scoped emit helper."); + if (helper.dependencies) { + for (const h of helper.dependencies) { + requestEmitHelper(h); } - return statements; } + emitHelpers = append(emitHelpers, helper); + } - function addBlockScopedVariable(name: Identifier): void { - Debug.assert(blockScopeStackOffset > 0, "Cannot add a block scoped variable outside of an iteration body."); - (blockScopedVariableDeclarations || (blockScopedVariableDeclarations = [])).push(name); - } + function readEmitHelpers(): EmitHelper[] | undefined { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + const helpers = emitHelpers; + emitHelpers = undefined; + return helpers; + } - function requestEmitHelper(helper: EmitHelper): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - Debug.assert(!helper.scoped, "Cannot request a scoped emit helper."); - if (helper.dependencies) { - for (const h of helper.dependencies) { - requestEmitHelper(h); - } + function dispose() { + if (state < TransformationState.Disposed) { + // Clean up emit nodes on parse tree + for (const node of nodes) { + disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); } - emitHelpers = append(emitHelpers, helper); - } - function readEmitHelpers(): EmitHelper[] | undefined { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - const helpers = emitHelpers; + // Release references to external entries for GC purposes. + lexicalEnvironmentVariableDeclarations = undefined!; + lexicalEnvironmentVariableDeclarationsStack = undefined!; + lexicalEnvironmentFunctionDeclarations = undefined!; + lexicalEnvironmentFunctionDeclarationsStack = undefined!; + onSubstituteNode = undefined!; + onEmitNode = undefined!; emitHelpers = undefined; - return helpers; - } - function dispose() { - if (state < TransformationState.Disposed) { - // Clean up emit nodes on parse tree - for (const node of nodes) { - disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); - } - - // Release references to external entries for GC purposes. - lexicalEnvironmentVariableDeclarations = undefined!; - lexicalEnvironmentVariableDeclarationsStack = undefined!; - lexicalEnvironmentFunctionDeclarations = undefined!; - lexicalEnvironmentFunctionDeclarationsStack = undefined!; - onSubstituteNode = undefined!; - onEmitNode = undefined!; - emitHelpers = undefined; - - // Prevent further use of the transformation result. - state = TransformationState.Disposed; - } + // Prevent further use of the transformation result. + state = TransformationState.Disposed; } } +} - export const nullTransformationContext: TransformationContext = { - factory: factory, // eslint-disable-line object-shorthand - getCompilerOptions: () => ({}), - getEmitResolver: notImplemented, - getEmitHost: notImplemented, - getEmitHelperFactory: notImplemented, - startLexicalEnvironment: noop, - resumeLexicalEnvironment: noop, - suspendLexicalEnvironment: noop, - endLexicalEnvironment: returnUndefined, - setLexicalEnvironmentFlags: noop, - getLexicalEnvironmentFlags: () => 0, - hoistVariableDeclaration: noop, - hoistFunctionDeclaration: noop, - addInitializationStatement: noop, - startBlockScope: noop, - endBlockScope: returnUndefined, - addBlockScopedVariable: noop, - requestEmitHelper: noop, - readEmitHelpers: notImplemented, - enableSubstitution: noop, - enableEmitNotification: noop, - isSubstitutionEnabled: notImplemented, - isEmitNotificationEnabled: notImplemented, - onSubstituteNode: noEmitSubstitution, - onEmitNode: noEmitNotification, - addDiagnostic: noop, - }; +export const nullTransformationContext: TransformationContext = { + factory: factory, // eslint-disable-line object-shorthand + getCompilerOptions: () => ({}), + getEmitResolver: notImplemented, + getEmitHost: notImplemented, + getEmitHelperFactory: notImplemented, + startLexicalEnvironment: noop, + resumeLexicalEnvironment: noop, + suspendLexicalEnvironment: noop, + endLexicalEnvironment: returnUndefined, + setLexicalEnvironmentFlags: noop, + getLexicalEnvironmentFlags: () => 0, + hoistVariableDeclaration: noop, + hoistFunctionDeclaration: noop, + addInitializationStatement: noop, + startBlockScope: noop, + endBlockScope: returnUndefined, + addBlockScopedVariable: noop, + requestEmitHelper: noop, + readEmitHelpers: notImplemented, + enableSubstitution: noop, + enableEmitNotification: noop, + isSubstitutionEnabled: notImplemented, + isEmitNotificationEnabled: notImplemented, + onSubstituteNode: noEmitSubstitution, + onEmitNode: noEmitNotification, + addDiagnostic: noop, +}; } diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts index 7d4afe2baffc4..836462e0c1cbc 100644 --- a/src/compiler/transformers/classFields.ts +++ b/src/compiler/transformers/classFields.ts @@ -1,372 +1,437 @@ /*@internal*/ namespace ts { - const enum ClassPropertySubstitutionFlags { - /** - * Enables substitutions for class expressions with static fields - * which have initializers that reference the class name. - */ - ClassAliases = 1 << 0, - /** - * Enables substitutions for class expressions with static fields - * which have initializers that reference the 'this' or 'super'. - */ - ClassStaticThisOrSuperReference = 1 << 1, - } - export const enum PrivateIdentifierKind { - Field = "f", - Method = "m", - Accessor = "a" - } - interface PrivateIdentifierInfoBase { - /** - * brandCheckIdentifier can contain: - * - For instance field: The WeakMap that will be the storage for the field. - * - For instance methods or accessors: The WeakSet that will be used for brand checking. - * - For static members: The constructor that will be used for brand checking. - */ - brandCheckIdentifier: Identifier; - /** - * Stores if the identifier is static or not - */ - isStatic: boolean; - /** - * Stores if the identifier declaration is valid or not. Reserved names (e.g. #constructor) - * or duplicate identifiers are considered invalid. - */ - isValid: boolean; - } - interface PrivateIdentifierAccessorInfo extends PrivateIdentifierInfoBase { - kind: PrivateIdentifierKind.Accessor; - /** - * Identifier for a variable that will contain the private get accessor implementation, if any. - */ - getterName?: Identifier; - /** - * Identifier for a variable that will contain the private set accessor implementation, if any. - */ - setterName?: Identifier; - } - interface PrivateIdentifierMethodInfo extends PrivateIdentifierInfoBase { - kind: PrivateIdentifierKind.Method; - /** - * Identifier for a variable that will contain the private method implementation. - */ - methodName: Identifier; - } - interface PrivateIdentifierInstanceFieldInfo extends PrivateIdentifierInfoBase { - kind: PrivateIdentifierKind.Field; - isStatic: false; - /** - * Defined for ease of access when in a union with PrivateIdentifierStaticFieldInfo. - */ - variableName: undefined; - } - interface PrivateIdentifierStaticFieldInfo extends PrivateIdentifierInfoBase { - kind: PrivateIdentifierKind.Field; - isStatic: true; - /** - * Contains the variable that will server as the storage for the field. - */ - variableName: Identifier; - } - - type PrivateIdentifierInfo = - | PrivateIdentifierMethodInfo - | PrivateIdentifierInstanceFieldInfo - | PrivateIdentifierStaticFieldInfo - | PrivateIdentifierAccessorInfo; - - interface PrivateIdentifierEnvironment { - /** - * Used for prefixing generated variable names. - */ - className: string; - /** - * Used for brand check on private methods. - */ - weakSetName?: Identifier; - /** - * A mapping of private names to information needed for transformation. - */ - identifiers: UnderscoreEscapedMap - } - - interface ClassLexicalEnvironment { - facts: ClassFacts; - /** - * Used for brand checks on static members, and `this` references in static initializers - */ - classConstructor: Identifier | undefined; - /** - * Used for `super` references in static initializers. - */ - superClassReference: Identifier | undefined; - privateIdentifierEnvironment: PrivateIdentifierEnvironment | undefined; - } - - const enum ClassFacts { - None = 0, - ClassWasDecorated = 1 << 0, - NeedsClassConstructorReference = 1 << 1, - NeedsClassSuperReference = 1 << 2, - NeedsSubstitutionForThisInClassStaticField = 1 << 3, - } +const enum ClassPropertySubstitutionFlags { + /** + * Enables substitutions for class expressions with static fields + * which have initializers that reference the class name. + */ + ClassAliases = 1 << 0, + /** + * Enables substitutions for class expressions with static fields + * which have initializers that reference the 'this' or 'super'. + */ + ClassStaticThisOrSuperReference = 1 << 1, +} +export const enum PrivateIdentifierKind { + Field = "f", + Method = "m", + Accessor = "a" +} +interface PrivateIdentifierInfoBase { + /** + * brandCheckIdentifier can contain: + * - For instance field: The WeakMap that will be the storage for the field. + * - For instance methods or accessors: The WeakSet that will be used for brand checking. + * - For static members: The constructor that will be used for brand checking. + */ + brandCheckIdentifier: Identifier; + /** + * Stores if the identifier is static or not + */ + isStatic: boolean; + /** + * Stores if the identifier declaration is valid or not. Reserved names (e.g. #constructor) + * or duplicate identifiers are considered invalid. + */ + isValid: boolean; +} +interface PrivateIdentifierAccessorInfo extends PrivateIdentifierInfoBase { + kind: PrivateIdentifierKind.Accessor; + /** + * Identifier for a variable that will contain the private get accessor implementation, if any. + */ + getterName?: Identifier; + /** + * Identifier for a variable that will contain the private set accessor implementation, if any. + */ + setterName?: Identifier; +} +interface PrivateIdentifierMethodInfo extends PrivateIdentifierInfoBase { + kind: PrivateIdentifierKind.Method; + /** + * Identifier for a variable that will contain the private method implementation. + */ + methodName: Identifier; +} +interface PrivateIdentifierInstanceFieldInfo extends PrivateIdentifierInfoBase { + kind: PrivateIdentifierKind.Field; + isStatic: false; + /** + * Defined for ease of access when in a union with PrivateIdentifierStaticFieldInfo. + */ + variableName: undefined; +} +interface PrivateIdentifierStaticFieldInfo extends PrivateIdentifierInfoBase { + kind: PrivateIdentifierKind.Field; + isStatic: true; + /** + * Contains the variable that will server as the storage for the field. + */ + variableName: Identifier; +} + +type PrivateIdentifierInfo = + | PrivateIdentifierMethodInfo + | PrivateIdentifierInstanceFieldInfo + | PrivateIdentifierStaticFieldInfo + | PrivateIdentifierAccessorInfo; +interface PrivateIdentifierEnvironment { /** - * Transforms ECMAScript Class Syntax. - * TypeScript parameter property syntax is transformed in the TypeScript transformer. - * For now, this transforms public field declarations using TypeScript class semantics, - * where declarations are elided and initializers are transformed as assignments in the constructor. - * When --useDefineForClassFields is on, this transforms to ECMAScript semantics, with Object.defineProperty. + * Used for prefixing generated variable names. */ - export function transformClassFields(context: TransformationContext) { - const { - factory, - hoistVariableDeclaration, - endLexicalEnvironment, - startLexicalEnvironment, - resumeLexicalEnvironment, - addBlockScopedVariable - } = context; - const resolver = context.getEmitResolver(); - const compilerOptions = context.getCompilerOptions(); - const languageVersion = getEmitScriptTarget(compilerOptions); - const useDefineForClassFields = getUseDefineForClassFields(compilerOptions); - - const shouldTransformPrivateElementsOrClassStaticBlocks = languageVersion < ScriptTarget.ESNext; - - // We don't need to transform `super` property access when targeting ES5, ES3 because - // the es2015 transformation handles those. - const shouldTransformSuperInStaticInitializers = (languageVersion <= ScriptTarget.ES2021 || !useDefineForClassFields) && languageVersion >= ScriptTarget.ES2015; - const shouldTransformThisInStaticInitializers = languageVersion <= ScriptTarget.ES2021 || !useDefineForClassFields; - - const previousOnSubstituteNode = context.onSubstituteNode; - context.onSubstituteNode = onSubstituteNode; - const previousOnEmitNode = context.onEmitNode; - context.onEmitNode = onEmitNode; - - let enabledSubstitutions: ClassPropertySubstitutionFlags; - - let classAliases: Identifier[]; - - /** - * Tracks what computed name expressions originating from elided names must be inlined - * at the next execution site, in document order - */ - let pendingExpressions: Expression[] | undefined; - - /** - * Tracks what computed name expression statements and static property initializers must be - * emitted at the next execution site, in document order (for decorated classes). - */ - let pendingStatements: Statement[] | undefined; - - const classLexicalEnvironmentStack: (ClassLexicalEnvironment | undefined)[] = []; - const classLexicalEnvironmentMap = new Map(); - let currentClassLexicalEnvironment: ClassLexicalEnvironment | undefined; - let currentComputedPropertyNameClassLexicalEnvironment: ClassLexicalEnvironment | undefined; - let currentStaticPropertyDeclarationOrStaticBlock: PropertyDeclaration | ClassStaticBlockDeclaration | undefined; - - return chainBundle(context, transformSourceFile); - - function transformSourceFile(node: SourceFile) { - const options = context.getCompilerOptions(); - if (node.isDeclarationFile - || useDefineForClassFields && getEmitScriptTarget(options) === ScriptTarget.ESNext) { - return node; - } - const visited = visitEachChild(node, visitor, context); - addEmitHelpers(visited, context.readEmitHelpers()); - return visited; - } - - function visitorWorker(node: Node, valueIsDiscarded: boolean): VisitResult { - if (node.transformFlags & TransformFlags.ContainsClassFields) { - switch (node.kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - return visitClassLike(node as ClassLikeDeclaration); - case SyntaxKind.PropertyDeclaration: - return visitPropertyDeclaration(node as PropertyDeclaration); - case SyntaxKind.VariableStatement: - return visitVariableStatement(node as VariableStatement); - case SyntaxKind.PrivateIdentifier: - return visitPrivateIdentifier(node as PrivateIdentifier); - case SyntaxKind.ClassStaticBlockDeclaration: - return visitClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); - } + className: string; + /** + * Used for brand check on private methods. + */ + weakSetName?: Identifier; + /** + * A mapping of private names to information needed for transformation. + */ + identifiers: UnderscoreEscapedMap +} + +interface ClassLexicalEnvironment { + facts: ClassFacts; + /** + * Used for brand checks on static members, and `this` references in static initializers + */ + classConstructor: Identifier | undefined; + /** + * Used for `super` references in static initializers. + */ + superClassReference: Identifier | undefined; + privateIdentifierEnvironment: PrivateIdentifierEnvironment | undefined; +} + +const enum ClassFacts { + None = 0, + ClassWasDecorated = 1 << 0, + NeedsClassConstructorReference = 1 << 1, + NeedsClassSuperReference = 1 << 2, + NeedsSubstitutionForThisInClassStaticField = 1 << 3, +} + +/** + * Transforms ECMAScript Class Syntax. + * TypeScript parameter property syntax is transformed in the TypeScript transformer. + * For now, this transforms public field declarations using TypeScript class semantics, + * where declarations are elided and initializers are transformed as assignments in the constructor. + * When --useDefineForClassFields is on, this transforms to ECMAScript semantics, with Object.defineProperty. + */ +export function transformClassFields(context: TransformationContext) { + const { + factory, + hoistVariableDeclaration, + endLexicalEnvironment, + startLexicalEnvironment, + resumeLexicalEnvironment, + addBlockScopedVariable + } = context; + const resolver = context.getEmitResolver(); + const compilerOptions = context.getCompilerOptions(); + const languageVersion = getEmitScriptTarget(compilerOptions); + const useDefineForClassFields = getUseDefineForClassFields(compilerOptions); + + const shouldTransformPrivateElementsOrClassStaticBlocks = languageVersion < ScriptTarget.ESNext; + + // We don't need to transform `super` property access when targeting ES5, ES3 because + // the es2015 transformation handles those. + const shouldTransformSuperInStaticInitializers = (languageVersion <= ScriptTarget.ES2021 || !useDefineForClassFields) && languageVersion >= ScriptTarget.ES2015; + const shouldTransformThisInStaticInitializers = languageVersion <= ScriptTarget.ES2021 || !useDefineForClassFields; + + const previousOnSubstituteNode = context.onSubstituteNode; + context.onSubstituteNode = onSubstituteNode; + const previousOnEmitNode = context.onEmitNode; + context.onEmitNode = onEmitNode; + + let enabledSubstitutions: ClassPropertySubstitutionFlags; + + let classAliases: Identifier[]; + + /** + * Tracks what computed name expressions originating from elided names must be inlined + * at the next execution site, in document order + */ + let pendingExpressions: Expression[] | undefined; + + /** + * Tracks what computed name expression statements and static property initializers must be + * emitted at the next execution site, in document order (for decorated classes). + */ + let pendingStatements: Statement[] | undefined; + + const classLexicalEnvironmentStack: (ClassLexicalEnvironment | undefined)[] = []; + const classLexicalEnvironmentMap = new Map(); + let currentClassLexicalEnvironment: ClassLexicalEnvironment | undefined; + let currentComputedPropertyNameClassLexicalEnvironment: ClassLexicalEnvironment | undefined; + let currentStaticPropertyDeclarationOrStaticBlock: PropertyDeclaration | ClassStaticBlockDeclaration | undefined; + + return chainBundle(context, transformSourceFile); + + function transformSourceFile(node: SourceFile) { + const options = context.getCompilerOptions(); + if (node.isDeclarationFile + || useDefineForClassFields && getEmitScriptTarget(options) === ScriptTarget.ESNext) { + return node; + } + const visited = visitEachChild(node, visitor, context); + addEmitHelpers(visited, context.readEmitHelpers()); + return visited; + } + + function visitorWorker(node: Node, valueIsDiscarded: boolean): VisitResult { + if (node.transformFlags & TransformFlags.ContainsClassFields) { + switch (node.kind) { + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + return visitClassLike(node as ClassLikeDeclaration); + case SyntaxKind.PropertyDeclaration: + return visitPropertyDeclaration(node as PropertyDeclaration); + case SyntaxKind.VariableStatement: + return visitVariableStatement(node as VariableStatement); + case SyntaxKind.PrivateIdentifier: + return visitPrivateIdentifier(node as PrivateIdentifier); + case SyntaxKind.ClassStaticBlockDeclaration: + return visitClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); } - if (node.transformFlags & TransformFlags.ContainsClassFields || - node.transformFlags & TransformFlags.ContainsLexicalSuper && - shouldTransformSuperInStaticInitializers && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment) { - switch (node.kind) { - case SyntaxKind.PrefixUnaryExpression: - case SyntaxKind.PostfixUnaryExpression: - return visitPreOrPostfixUnaryExpression(node as PrefixUnaryExpression | PostfixUnaryExpression, valueIsDiscarded); - case SyntaxKind.BinaryExpression: - return visitBinaryExpression(node as BinaryExpression, valueIsDiscarded); - case SyntaxKind.CallExpression: - return visitCallExpression(node as CallExpression); - case SyntaxKind.TaggedTemplateExpression: - return visitTaggedTemplateExpression(node as TaggedTemplateExpression); - case SyntaxKind.PropertyAccessExpression: - return visitPropertyAccessExpression(node as PropertyAccessExpression); - case SyntaxKind.ElementAccessExpression: - return visitElementAccessExpression(node as ElementAccessExpression); - case SyntaxKind.ExpressionStatement: - return visitExpressionStatement(node as ExpressionStatement); - case SyntaxKind.ForStatement: - return visitForStatement(node as ForStatement); - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.Constructor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: { - const savedCurrentStaticPropertyDeclarationOrStaticBlock = currentStaticPropertyDeclarationOrStaticBlock; - currentStaticPropertyDeclarationOrStaticBlock = undefined; - const result = visitEachChild(node, visitor, context); - currentStaticPropertyDeclarationOrStaticBlock = savedCurrentStaticPropertyDeclarationOrStaticBlock; - return result; - } + } + if (node.transformFlags & TransformFlags.ContainsClassFields || + node.transformFlags & TransformFlags.ContainsLexicalSuper && + shouldTransformSuperInStaticInitializers && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment) { + switch (node.kind) { + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.PostfixUnaryExpression: + return visitPreOrPostfixUnaryExpression(node as PrefixUnaryExpression | PostfixUnaryExpression, valueIsDiscarded); + case SyntaxKind.BinaryExpression: + return visitBinaryExpression(node as BinaryExpression, valueIsDiscarded); + case SyntaxKind.CallExpression: + return visitCallExpression(node as CallExpression); + case SyntaxKind.TaggedTemplateExpression: + return visitTaggedTemplateExpression(node as TaggedTemplateExpression); + case SyntaxKind.PropertyAccessExpression: + return visitPropertyAccessExpression(node as PropertyAccessExpression); + case SyntaxKind.ElementAccessExpression: + return visitElementAccessExpression(node as ElementAccessExpression); + case SyntaxKind.ExpressionStatement: + return visitExpressionStatement(node as ExpressionStatement); + case SyntaxKind.ForStatement: + return visitForStatement(node as ForStatement); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: { + const savedCurrentStaticPropertyDeclarationOrStaticBlock = currentStaticPropertyDeclarationOrStaticBlock; + currentStaticPropertyDeclarationOrStaticBlock = undefined; + const result = visitEachChild(node, visitor, context); + currentStaticPropertyDeclarationOrStaticBlock = savedCurrentStaticPropertyDeclarationOrStaticBlock; + return result; } } - return visitEachChild(node, visitor, context); } + return visitEachChild(node, visitor, context); + } - function discardedValueVisitor(node: Node): VisitResult { - return visitorWorker(node, /*valueIsDiscarded*/ true); + function discardedValueVisitor(node: Node): VisitResult { + return visitorWorker(node, /*valueIsDiscarded*/ true); + } + + function visitor(node: Node): VisitResult { + return visitorWorker(node, /*valueIsDiscarded*/ false); + } + + function heritageClauseVisitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.HeritageClause: + return visitEachChild(node, heritageClauseVisitor, context); + case SyntaxKind.ExpressionWithTypeArguments: + return visitExpressionWithTypeArguments(node as ExpressionWithTypeArguments); } + return visitor(node); + } - function visitor(node: Node): VisitResult { - return visitorWorker(node, /*valueIsDiscarded*/ false); + function visitorDestructuringTarget(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ArrayLiteralExpression: + return visitAssignmentPattern(node as AssignmentPattern); + default: + return visitor(node); } + } - function heritageClauseVisitor(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.HeritageClause: - return visitEachChild(node, heritageClauseVisitor, context); - case SyntaxKind.ExpressionWithTypeArguments: - return visitExpressionWithTypeArguments(node as ExpressionWithTypeArguments); - } - return visitor(node); + /** + * If we visit a private name, this means it is an undeclared private name. + * Replace it with an empty identifier to indicate a problem with the code, + * unless we are in a statement position - otherwise this will not trigger + * a SyntaxError. + */ + function visitPrivateIdentifier(node: PrivateIdentifier) { + if (!shouldTransformPrivateElementsOrClassStaticBlocks) { + return node; } + if (isStatement(node.parent)) { + return node; + } + return setOriginalNode(factory.createIdentifier(""), node); + } - function visitorDestructuringTarget(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ArrayLiteralExpression: - return visitAssignmentPattern(node as AssignmentPattern); - default: - return visitor(node); - } + /** + * Visits `#id in expr` + */ + function visitPrivateIdentifierInInExpression(node: BinaryExpression) { + if (!shouldTransformPrivateElementsOrClassStaticBlocks) { + return node; } + const privId = node.left; + Debug.assertNode(privId, isPrivateIdentifier); + Debug.assert(node.operatorToken.kind === SyntaxKind.InKeyword); + const info = accessPrivateIdentifier(privId); + if (info) { + const receiver = visitNode(node.right, visitor, isExpression); - /** - * If we visit a private name, this means it is an undeclared private name. - * Replace it with an empty identifier to indicate a problem with the code, - * unless we are in a statement position - otherwise this will not trigger - * a SyntaxError. - */ - function visitPrivateIdentifier(node: PrivateIdentifier) { - if (!shouldTransformPrivateElementsOrClassStaticBlocks) { - return node; - } - if (isStatement(node.parent)) { - return node; - } - return setOriginalNode(factory.createIdentifier(""), node); + return setOriginalNode( + context.getEmitHelperFactory().createClassPrivateFieldInHelper(info.brandCheckIdentifier, receiver), + node + ); } - /** - * Visits `#id in expr` - */ - function visitPrivateIdentifierInInExpression(node: BinaryExpression) { - if (!shouldTransformPrivateElementsOrClassStaticBlocks) { - return node; - } - const privId = node.left; - Debug.assertNode(privId, isPrivateIdentifier); - Debug.assert(node.operatorToken.kind === SyntaxKind.InKeyword); - const info = accessPrivateIdentifier(privId); - if (info) { - const receiver = visitNode(node.right, visitor, isExpression); + // Private name has not been declared. Subsequent transformers will handle this error + return visitEachChild(node, visitor, context); + } - return setOriginalNode( - context.getEmitHelperFactory().createClassPrivateFieldInHelper(info.brandCheckIdentifier, receiver), - node - ); - } + /** + * Visits the members of a class that has fields. + * + * @param node The node to visit. + */ + function classElementVisitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.Constructor: + // Constructors for classes using class fields are transformed in + // `visitClassDeclaration` or `visitClassExpression`. + return undefined; - // Private name has not been declared. Subsequent transformers will handle this error - return visitEachChild(node, visitor, context); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + return visitMethodOrAccessorDeclaration(node as MethodDeclaration | AccessorDeclaration); + + case SyntaxKind.PropertyDeclaration: + return visitPropertyDeclaration(node as PropertyDeclaration); + + case SyntaxKind.ComputedPropertyName: + return visitComputedPropertyName(node as ComputedPropertyName); + + case SyntaxKind.SemicolonClassElement: + return node; + + default: + return visitor(node); } + } - /** - * Visits the members of a class that has fields. - * - * @param node The node to visit. - */ - function classElementVisitor(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.Constructor: - // Constructors for classes using class fields are transformed in - // `visitClassDeclaration` or `visitClassExpression`. - return undefined; + function visitVariableStatement(node: VariableStatement) { + const savedPendingStatements = pendingStatements; + pendingStatements = []; - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - return visitMethodOrAccessorDeclaration(node as MethodDeclaration | AccessorDeclaration); + const visitedNode = visitEachChild(node, visitor, context); + const statement = some(pendingStatements) ? + [visitedNode, ...pendingStatements] : + visitedNode; - case SyntaxKind.PropertyDeclaration: - return visitPropertyDeclaration(node as PropertyDeclaration); + pendingStatements = savedPendingStatements; + return statement; + } - case SyntaxKind.ComputedPropertyName: - return visitComputedPropertyName(node as ComputedPropertyName); + function visitComputedPropertyName(name: ComputedPropertyName) { + let node = visitEachChild(name, visitor, context); + if (some(pendingExpressions)) { + const expressions = pendingExpressions; + expressions.push(node.expression); + pendingExpressions = []; + node = factory.updateComputedPropertyName( + node, + factory.inlineExpressions(expressions) + ); + } + return node; + } - case SyntaxKind.SemicolonClassElement: - return node; + function visitMethodOrAccessorDeclaration(node: MethodDeclaration | AccessorDeclaration) { + Debug.assert(!some(node.decorators)); - default: - return visitor(node); - } + if (!shouldTransformPrivateElementsOrClassStaticBlocks || !isPrivateIdentifier(node.name)) { + return visitEachChild(node, classElementVisitor, context); + } + + // leave invalid code untransformed + const info = accessPrivateIdentifier(node.name); + Debug.assert(info, "Undeclared private name for property declaration."); + if (!info.isValid) { + return node; + } + + const functionName = getHoistedFunctionName(node); + if (functionName) { + getPendingExpressions().push( + factory.createAssignment( + functionName, + factory.createFunctionExpression( + filter(node.modifiers, m => !isStaticModifier(m)), + node.asteriskToken, + functionName, + /* typeParameters */ undefined, + visitParameterList(node.parameters, classElementVisitor, context), + /* type */ undefined, + visitFunctionBody(node.body!, classElementVisitor, context) + ) + ) + ); } - function visitVariableStatement(node: VariableStatement) { - const savedPendingStatements = pendingStatements; - pendingStatements = []; + // remove method declaration from class + return undefined; + } - const visitedNode = visitEachChild(node, visitor, context); - const statement = some(pendingStatements) ? - [visitedNode, ...pendingStatements] : - visitedNode; + function getHoistedFunctionName(node: MethodDeclaration | AccessorDeclaration) { + Debug.assert(isPrivateIdentifier(node.name)); + const info = accessPrivateIdentifier(node.name); + Debug.assert(info, "Undeclared private name for property declaration."); - pendingStatements = savedPendingStatements; - return statement; + if (info.kind === PrivateIdentifierKind.Method) { + return info.methodName; } - function visitComputedPropertyName(name: ComputedPropertyName) { - let node = visitEachChild(name, visitor, context); - if (some(pendingExpressions)) { - const expressions = pendingExpressions; - expressions.push(node.expression); - pendingExpressions = []; - node = factory.updateComputedPropertyName( - node, - factory.inlineExpressions(expressions) - ); + if (info.kind === PrivateIdentifierKind.Accessor) { + if (isGetAccessor(node)) { + return info.getterName; + } + if (isSetAccessor(node)) { + return info.setterName; } - return node; } + } - function visitMethodOrAccessorDeclaration(node: MethodDeclaration | AccessorDeclaration) { - Debug.assert(!some(node.decorators)); + function visitPropertyDeclaration(node: PropertyDeclaration) { + Debug.assert(!some(node.decorators)); - if (!shouldTransformPrivateElementsOrClassStaticBlocks || !isPrivateIdentifier(node.name)) { - return visitEachChild(node, classElementVisitor, context); + if (isPrivateIdentifier(node.name)) { + if (!shouldTransformPrivateElementsOrClassStaticBlocks) { + // Initializer is elided as the field is initialized in transformConstructor. + return factory.updatePropertyDeclaration( + node, + /*decorators*/ undefined, + visitNodes(node.modifiers, visitor, isModifier), + node.name, + /*questionOrExclamationToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + ); } // leave invalid code untransformed @@ -375,195 +440,182 @@ namespace ts { if (!info.isValid) { return node; } - - const functionName = getHoistedFunctionName(node); - if (functionName) { - getPendingExpressions().push( - factory.createAssignment( - functionName, - factory.createFunctionExpression( - filter(node.modifiers, m => !isStaticModifier(m)), - node.asteriskToken, - functionName, - /* typeParameters */ undefined, - visitParameterList(node.parameters, classElementVisitor, context), - /* type */ undefined, - visitFunctionBody(node.body!, classElementVisitor, context) - ) - ) - ); - } - - // remove method declaration from class - return undefined; } + // Create a temporary variable to store a computed property name (if necessary). + // If it's not inlineable, then we emit an expression after the class which assigns + // the property name to the temporary variable. + const expr = getPropertyNameExpressionIfNeeded(node.name, !!node.initializer || useDefineForClassFields); + if (expr && !isSimpleInlineableExpression(expr)) { + getPendingExpressions().push(expr); + } + return undefined; + } - function getHoistedFunctionName(node: MethodDeclaration | AccessorDeclaration) { - Debug.assert(isPrivateIdentifier(node.name)); - const info = accessPrivateIdentifier(node.name); - Debug.assert(info, "Undeclared private name for property declaration."); + function createPrivateIdentifierAccess(info: PrivateIdentifierInfo, receiver: Expression): Expression { + return createPrivateIdentifierAccessHelper(info, visitNode(receiver, visitor, isExpression)); + } - if (info.kind === PrivateIdentifierKind.Method) { - return info.methodName; - } + function createPrivateIdentifierAccessHelper(info: PrivateIdentifierInfo, receiver: Expression): Expression { + setCommentRange(receiver, moveRangePos(receiver, -1)); - if (info.kind === PrivateIdentifierKind.Accessor) { - if (isGetAccessor(node)) { - return info.getterName; - } - if (isSetAccessor(node)) { - return info.setterName; - } - } + switch(info.kind) { + case PrivateIdentifierKind.Accessor: + return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( + receiver, + info.brandCheckIdentifier, + info.kind, + info.getterName + ); + case PrivateIdentifierKind.Method: + return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( + receiver, + info.brandCheckIdentifier, + info.kind, + info.methodName + ); + case PrivateIdentifierKind.Field: + return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( + receiver, + info.brandCheckIdentifier, + info.kind, + info.variableName + ); + default: + Debug.assertNever(info, "Unknown private element type"); } + } - function visitPropertyDeclaration(node: PropertyDeclaration) { - Debug.assert(!some(node.decorators)); - - if (isPrivateIdentifier(node.name)) { - if (!shouldTransformPrivateElementsOrClassStaticBlocks) { - // Initializer is elided as the field is initialized in transformConstructor. - return factory.updatePropertyDeclaration( - node, - /*decorators*/ undefined, - visitNodes(node.modifiers, visitor, isModifier), - node.name, - /*questionOrExclamationToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined - ); - } - - // leave invalid code untransformed - const info = accessPrivateIdentifier(node.name); - Debug.assert(info, "Undeclared private name for property declaration."); - if (!info.isValid) { - return node; - } - } - // Create a temporary variable to store a computed property name (if necessary). - // If it's not inlineable, then we emit an expression after the class which assigns - // the property name to the temporary variable. - const expr = getPropertyNameExpressionIfNeeded(node.name, !!node.initializer || useDefineForClassFields); - if (expr && !isSimpleInlineableExpression(expr)) { - getPendingExpressions().push(expr); + function visitPropertyAccessExpression(node: PropertyAccessExpression) { + if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifier(node.name)) { + const privateIdentifierInfo = accessPrivateIdentifier(node.name); + if (privateIdentifierInfo) { + return setTextRange( + setOriginalNode( + createPrivateIdentifierAccess(privateIdentifierInfo, node.expression), + node + ), + node + ); } - return undefined; } - - function createPrivateIdentifierAccess(info: PrivateIdentifierInfo, receiver: Expression): Expression { - return createPrivateIdentifierAccessHelper(info, visitNode(receiver, visitor, isExpression)); + if (shouldTransformSuperInStaticInitializers && + isSuperProperty(node) && + isIdentifier(node.name) && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment) { + const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; + if (facts & ClassFacts.ClassWasDecorated) { + return visitInvalidSuperProperty(node); + } + if (classConstructor && superClassReference) { + // converts `super.x` into `Reflect.get(_baseTemp, "x", _classTemp)` + const superProperty = factory.createReflectGetCall( + superClassReference, + factory.createStringLiteralFromNode(node.name), + classConstructor + ); + setOriginalNode(superProperty, node.expression); + setTextRange(superProperty, node.expression); + return superProperty; + } } + return visitEachChild(node, visitor, context); + } - function createPrivateIdentifierAccessHelper(info: PrivateIdentifierInfo, receiver: Expression): Expression { - setCommentRange(receiver, moveRangePos(receiver, -1)); - - switch(info.kind) { - case PrivateIdentifierKind.Accessor: - return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( - receiver, - info.brandCheckIdentifier, - info.kind, - info.getterName - ); - case PrivateIdentifierKind.Method: - return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( - receiver, - info.brandCheckIdentifier, - info.kind, - info.methodName - ); - case PrivateIdentifierKind.Field: - return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( - receiver, - info.brandCheckIdentifier, - info.kind, - info.variableName - ); - default: - Debug.assertNever(info, "Unknown private element type"); + function visitElementAccessExpression(node: ElementAccessExpression) { + if (shouldTransformSuperInStaticInitializers && + isSuperProperty(node) && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment) { + const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; + if (facts & ClassFacts.ClassWasDecorated) { + return visitInvalidSuperProperty(node); + } + + if (classConstructor && superClassReference) { + // converts `super[x]` into `Reflect.get(_baseTemp, x, _classTemp)` + const superProperty = factory.createReflectGetCall( + superClassReference, + visitNode(node.argumentExpression, visitor, isExpression), + classConstructor + ); + setOriginalNode(superProperty, node.expression); + setTextRange(superProperty, node.expression); + return superProperty; } } + return visitEachChild(node, visitor, context); + } - function visitPropertyAccessExpression(node: PropertyAccessExpression) { - if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifier(node.name)) { - const privateIdentifierInfo = accessPrivateIdentifier(node.name); - if (privateIdentifierInfo) { - return setTextRange( - setOriginalNode( - createPrivateIdentifierAccess(privateIdentifierInfo, node.expression), - node - ), - node - ); - } - } - if (shouldTransformSuperInStaticInitializers && - isSuperProperty(node) && - isIdentifier(node.name) && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment) { - const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; - if (facts & ClassFacts.ClassWasDecorated) { - return visitInvalidSuperProperty(node); - } - if (classConstructor && superClassReference) { - // converts `super.x` into `Reflect.get(_baseTemp, "x", _classTemp)` - const superProperty = factory.createReflectGetCall( - superClassReference, - factory.createStringLiteralFromNode(node.name), - classConstructor + function visitPreOrPostfixUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression, valueIsDiscarded: boolean) { + if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { + if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierPropertyAccessExpression(node.operand)) { + let info: PrivateIdentifierInfo | undefined; + if (info = accessPrivateIdentifier(node.operand.name)) { + const receiver = visitNode(node.operand.expression, visitor, isExpression); + const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver); + + let expression: Expression = createPrivateIdentifierAccess(info, readExpression); + const temp = isPrefixUnaryExpression(node) || valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); + expression = expandPreOrPostfixIncrementOrDecrementExpression(factory, node, expression, hoistVariableDeclaration, temp); + expression = createPrivateIdentifierAssignment( + info, + initializeExpression || readExpression, + expression, + SyntaxKind.EqualsToken ); - setOriginalNode(superProperty, node.expression); - setTextRange(superProperty, node.expression); - return superProperty; + setOriginalNode(expression, node); + setTextRange(expression, node); + if (temp) { + expression = factory.createComma(expression, temp); + setTextRange(expression, node); + } + return expression; } } - return visitEachChild(node, visitor, context); - } - - function visitElementAccessExpression(node: ElementAccessExpression) { - if (shouldTransformSuperInStaticInitializers && - isSuperProperty(node) && + else if (shouldTransformSuperInStaticInitializers && + isSuperProperty(node.operand) && currentStaticPropertyDeclarationOrStaticBlock && currentClassLexicalEnvironment) { + // converts `++super.a` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = ++_a), _classTemp), _b)` + // converts `++super[f()]` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = ++_b), _classTemp), _c)` + // converts `--super.a` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = --_a), _classTemp), _b)` + // converts `--super[f()]` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = --_b), _classTemp), _c)` + // converts `super.a++` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = _a++), _classTemp), _b)` + // converts `super[f()]++` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = _b++), _classTemp), _c)` + // converts `super.a--` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = _a--), _classTemp), _b)` + // converts `super[f()]--` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = _b--), _classTemp), _c)` const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; if (facts & ClassFacts.ClassWasDecorated) { - return visitInvalidSuperProperty(node); + const operand = visitInvalidSuperProperty(node.operand); + return isPrefixUnaryExpression(node) ? + factory.updatePrefixUnaryExpression(node, operand) : + factory.updatePostfixUnaryExpression(node, operand); } - if (classConstructor && superClassReference) { - // converts `super[x]` into `Reflect.get(_baseTemp, x, _classTemp)` - const superProperty = factory.createReflectGetCall( - superClassReference, - visitNode(node.argumentExpression, visitor, isExpression), - classConstructor - ); - setOriginalNode(superProperty, node.expression); - setTextRange(superProperty, node.expression); - return superProperty; - } - } - return visitEachChild(node, visitor, context); - } - - function visitPreOrPostfixUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression, valueIsDiscarded: boolean) { - if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { - if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierPropertyAccessExpression(node.operand)) { - let info: PrivateIdentifierInfo | undefined; - if (info = accessPrivateIdentifier(node.operand.name)) { - const receiver = visitNode(node.operand.expression, visitor, isExpression); - const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver); + let setterName: Expression | undefined; + let getterName: Expression | undefined; + if (isPropertyAccessExpression(node.operand)) { + if (isIdentifier(node.operand.name)) { + getterName = setterName = factory.createStringLiteralFromNode(node.operand.name); + } + } + else { + if (isSimpleInlineableExpression(node.operand.argumentExpression)) { + getterName = setterName = node.operand.argumentExpression; + } + else { + getterName = factory.createTempVariable(hoistVariableDeclaration); + setterName = factory.createAssignment(getterName, visitNode(node.operand.argumentExpression, visitor, isExpression)); + } + } + if (setterName && getterName) { + let expression: Expression = factory.createReflectGetCall(superClassReference, getterName, classConstructor); + setTextRange(expression, node.operand); - let expression: Expression = createPrivateIdentifierAccess(info, readExpression); - const temp = isPrefixUnaryExpression(node) || valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); + const temp = valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); expression = expandPreOrPostfixIncrementOrDecrementExpression(factory, node, expression, hoistVariableDeclaration, temp); - expression = createPrivateIdentifierAssignment( - info, - initializeExpression || readExpression, - expression, - SyntaxKind.EqualsToken - ); + expression = factory.createReflectSetCall(superClassReference, setterName, expression, classConstructor); setOriginalNode(expression, node); setTextRange(expression, node); if (temp) { @@ -573,1350 +625,1353 @@ namespace ts { return expression; } } - else if (shouldTransformSuperInStaticInitializers && - isSuperProperty(node.operand) && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment) { - // converts `++super.a` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = ++_a), _classTemp), _b)` - // converts `++super[f()]` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = ++_b), _classTemp), _c)` - // converts `--super.a` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = --_a), _classTemp), _b)` - // converts `--super[f()]` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = --_b), _classTemp), _c)` - // converts `super.a++` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = _a++), _classTemp), _b)` - // converts `super[f()]++` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = _b++), _classTemp), _c)` - // converts `super.a--` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = _a--), _classTemp), _b)` - // converts `super[f()]--` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = _b--), _classTemp), _c)` - const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; - if (facts & ClassFacts.ClassWasDecorated) { - const operand = visitInvalidSuperProperty(node.operand); - return isPrefixUnaryExpression(node) ? - factory.updatePrefixUnaryExpression(node, operand) : - factory.updatePostfixUnaryExpression(node, operand); - } - if (classConstructor && superClassReference) { - let setterName: Expression | undefined; - let getterName: Expression | undefined; - if (isPropertyAccessExpression(node.operand)) { - if (isIdentifier(node.operand.name)) { - getterName = setterName = factory.createStringLiteralFromNode(node.operand.name); - } - } - else { - if (isSimpleInlineableExpression(node.operand.argumentExpression)) { - getterName = setterName = node.operand.argumentExpression; - } - else { - getterName = factory.createTempVariable(hoistVariableDeclaration); - setterName = factory.createAssignment(getterName, visitNode(node.operand.argumentExpression, visitor, isExpression)); - } - } - if (setterName && getterName) { - let expression: Expression = factory.createReflectGetCall(superClassReference, getterName, classConstructor); - setTextRange(expression, node.operand); - - const temp = valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); - expression = expandPreOrPostfixIncrementOrDecrementExpression(factory, node, expression, hoistVariableDeclaration, temp); - expression = factory.createReflectSetCall(superClassReference, setterName, expression, classConstructor); - setOriginalNode(expression, node); - setTextRange(expression, node); - if (temp) { - expression = factory.createComma(expression, temp); - setTextRange(expression, node); - } - return expression; - } - } - } } - return visitEachChild(node, visitor, context); } + return visitEachChild(node, visitor, context); + } - function visitForStatement(node: ForStatement) { - return factory.updateForStatement( - node, - visitNode(node.initializer, discardedValueVisitor, isForInitializer), - visitNode(node.condition, visitor, isExpression), - visitNode(node.incrementor, discardedValueVisitor, isExpression), - visitIterationBody(node.statement, visitor, context) - ); - } + function visitForStatement(node: ForStatement) { + return factory.updateForStatement( + node, + visitNode(node.initializer, discardedValueVisitor, isForInitializer), + visitNode(node.condition, visitor, isExpression), + visitNode(node.incrementor, discardedValueVisitor, isExpression), + visitIterationBody(node.statement, visitor, context) + ); + } - function visitExpressionStatement(node: ExpressionStatement) { - return factory.updateExpressionStatement( - node, - visitNode(node.expression, discardedValueVisitor, isExpression) - ); - } + function visitExpressionStatement(node: ExpressionStatement) { + return factory.updateExpressionStatement( + node, + visitNode(node.expression, discardedValueVisitor, isExpression) + ); + } - function createCopiableReceiverExpr(receiver: Expression): { readExpression: Expression; initializeExpression: Expression | undefined } { - const clone = nodeIsSynthesized(receiver) ? receiver : factory.cloneNode(receiver); - if (isSimpleInlineableExpression(receiver)) { - return { readExpression: clone, initializeExpression: undefined }; - } - const readExpression = factory.createTempVariable(hoistVariableDeclaration); - const initializeExpression = factory.createAssignment(readExpression, clone); - return { readExpression, initializeExpression }; + function createCopiableReceiverExpr(receiver: Expression): { readExpression: Expression; initializeExpression: Expression | undefined } { + const clone = nodeIsSynthesized(receiver) ? receiver : factory.cloneNode(receiver); + if (isSimpleInlineableExpression(receiver)) { + return { readExpression: clone, initializeExpression: undefined }; } + const readExpression = factory.createTempVariable(hoistVariableDeclaration); + const initializeExpression = factory.createAssignment(readExpression, clone); + return { readExpression, initializeExpression }; + } - function visitCallExpression(node: CallExpression) { - if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierPropertyAccessExpression(node.expression)) { - // Transform call expressions of private names to properly bind the `this` parameter. - const { thisArg, target } = factory.createCallBinding(node.expression, hoistVariableDeclaration, languageVersion); - if (isCallChain(node)) { - return factory.updateCallChain( - node, - factory.createPropertyAccessChain(visitNode(target, visitor), node.questionDotToken, "call"), - /*questionDotToken*/ undefined, - /*typeArguments*/ undefined, - [visitNode(thisArg, visitor, isExpression), ...visitNodes(node.arguments, visitor, isExpression)] - ); - } - return factory.updateCallExpression( + function visitCallExpression(node: CallExpression) { + if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierPropertyAccessExpression(node.expression)) { + // Transform call expressions of private names to properly bind the `this` parameter. + const { thisArg, target } = factory.createCallBinding(node.expression, hoistVariableDeclaration, languageVersion); + if (isCallChain(node)) { + return factory.updateCallChain( node, - factory.createPropertyAccessExpression(visitNode(target, visitor), "call"), + factory.createPropertyAccessChain(visitNode(target, visitor), node.questionDotToken, "call"), + /*questionDotToken*/ undefined, /*typeArguments*/ undefined, [visitNode(thisArg, visitor, isExpression), ...visitNodes(node.arguments, visitor, isExpression)] ); } + return factory.updateCallExpression( + node, + factory.createPropertyAccessExpression(visitNode(target, visitor), "call"), + /*typeArguments*/ undefined, + [visitNode(thisArg, visitor, isExpression), ...visitNodes(node.arguments, visitor, isExpression)] + ); + } - if (shouldTransformSuperInStaticInitializers && - isSuperProperty(node.expression) && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment?.classConstructor) { - - // converts `super.f(...)` into `Reflect.get(_baseTemp, "f", _classTemp).call(_classTemp, ...)` - const invocation = factory.createFunctionCallCall( - visitNode(node.expression, visitor, isExpression), - currentClassLexicalEnvironment.classConstructor, - visitNodes(node.arguments, visitor, isExpression) - ); - setOriginalNode(invocation, node); - setTextRange(invocation, node); - return invocation; - } + if (shouldTransformSuperInStaticInitializers && + isSuperProperty(node.expression) && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment?.classConstructor) { - return visitEachChild(node, visitor, context); + // converts `super.f(...)` into `Reflect.get(_baseTemp, "f", _classTemp).call(_classTemp, ...)` + const invocation = factory.createFunctionCallCall( + visitNode(node.expression, visitor, isExpression), + currentClassLexicalEnvironment.classConstructor, + visitNodes(node.arguments, visitor, isExpression) + ); + setOriginalNode(invocation, node); + setTextRange(invocation, node); + return invocation; } - function visitTaggedTemplateExpression(node: TaggedTemplateExpression) { - if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierPropertyAccessExpression(node.tag)) { - // Bind the `this` correctly for tagged template literals when the tag is a private identifier property access. - const { thisArg, target } = factory.createCallBinding(node.tag, hoistVariableDeclaration, languageVersion); - return factory.updateTaggedTemplateExpression( - node, - factory.createCallExpression( - factory.createPropertyAccessExpression(visitNode(target, visitor), "bind"), - /*typeArguments*/ undefined, - [visitNode(thisArg, visitor, isExpression)] - ), - /*typeArguments*/ undefined, - visitNode(node.template, visitor, isTemplateLiteral) - ); - } - if (shouldTransformSuperInStaticInitializers && - isSuperProperty(node.tag) && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment?.classConstructor) { + return visitEachChild(node, visitor, context); + } - // converts `` super.f`x` `` into `` Reflect.get(_baseTemp, "f", _classTemp).bind(_classTemp)`x` `` - const invocation = factory.createFunctionBindCall( - visitNode(node.tag, visitor, isExpression), - currentClassLexicalEnvironment.classConstructor, - [] - ); - setOriginalNode(invocation, node); - setTextRange(invocation, node); - return factory.updateTaggedTemplateExpression( - node, - invocation, + function visitTaggedTemplateExpression(node: TaggedTemplateExpression) { + if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierPropertyAccessExpression(node.tag)) { + // Bind the `this` correctly for tagged template literals when the tag is a private identifier property access. + const { thisArg, target } = factory.createCallBinding(node.tag, hoistVariableDeclaration, languageVersion); + return factory.updateTaggedTemplateExpression( + node, + factory.createCallExpression( + factory.createPropertyAccessExpression(visitNode(target, visitor), "bind"), /*typeArguments*/ undefined, - visitNode(node.template, visitor, isTemplateLiteral) - ); + [visitNode(thisArg, visitor, isExpression)] + ), + /*typeArguments*/ undefined, + visitNode(node.template, visitor, isTemplateLiteral) + ); + } + if (shouldTransformSuperInStaticInitializers && + isSuperProperty(node.tag) && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment?.classConstructor) { + + // converts `` super.f`x` `` into `` Reflect.get(_baseTemp, "f", _classTemp).bind(_classTemp)`x` `` + const invocation = factory.createFunctionBindCall( + visitNode(node.tag, visitor, isExpression), + currentClassLexicalEnvironment.classConstructor, + [] + ); + setOriginalNode(invocation, node); + setTextRange(invocation, node); + return factory.updateTaggedTemplateExpression( + node, + invocation, + /*typeArguments*/ undefined, + visitNode(node.template, visitor, isTemplateLiteral) + ); + } + return visitEachChild(node, visitor, context); + } + + function transformClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { + if (shouldTransformPrivateElementsOrClassStaticBlocks) { + if (currentClassLexicalEnvironment) { + classLexicalEnvironmentMap.set(getOriginalNodeId(node), currentClassLexicalEnvironment); } - return visitEachChild(node, visitor, context); + + startLexicalEnvironment(); + const savedCurrentStaticPropertyDeclarationOrStaticBlock = currentStaticPropertyDeclarationOrStaticBlock; + currentStaticPropertyDeclarationOrStaticBlock = node; + let statements = visitNodes(node.body.statements, visitor, isStatement); + statements = factory.mergeLexicalEnvironment(statements, endLexicalEnvironment()); + currentStaticPropertyDeclarationOrStaticBlock = savedCurrentStaticPropertyDeclarationOrStaticBlock; + + const iife = factory.createImmediatelyInvokedArrowFunction(statements); + setOriginalNode(iife, node); + setTextRange(iife, node); + addEmitFlags(iife, EmitFlags.AdviseOnEmitNode); + return iife; } + } - function transformClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { - if (shouldTransformPrivateElementsOrClassStaticBlocks) { - if (currentClassLexicalEnvironment) { - classLexicalEnvironmentMap.set(getOriginalNodeId(node), currentClassLexicalEnvironment); + function visitBinaryExpression(node: BinaryExpression, valueIsDiscarded: boolean) { + if (isDestructuringAssignment(node)) { + const savedPendingExpressions = pendingExpressions; + pendingExpressions = undefined; + node = factory.updateBinaryExpression( + node, + visitNode(node.left, visitorDestructuringTarget), + node.operatorToken, + visitNode(node.right, visitor) + ); + const expr = some(pendingExpressions) ? + factory.inlineExpressions(compact([...pendingExpressions, node])) : + node; + pendingExpressions = savedPendingExpressions; + return expr; + } + if (isAssignmentExpression(node)) { + if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierPropertyAccessExpression(node.left)) { + const info = accessPrivateIdentifier(node.left.name); + if (info) { + return setTextRange( + setOriginalNode( + createPrivateIdentifierAssignment(info, node.left.expression, node.right, node.operatorToken.kind), + node + ), + node + ); } - - startLexicalEnvironment(); - const savedCurrentStaticPropertyDeclarationOrStaticBlock = currentStaticPropertyDeclarationOrStaticBlock; - currentStaticPropertyDeclarationOrStaticBlock = node; - let statements = visitNodes(node.body.statements, visitor, isStatement); - statements = factory.mergeLexicalEnvironment(statements, endLexicalEnvironment()); - currentStaticPropertyDeclarationOrStaticBlock = savedCurrentStaticPropertyDeclarationOrStaticBlock; - - const iife = factory.createImmediatelyInvokedArrowFunction(statements); - setOriginalNode(iife, node); - setTextRange(iife, node); - addEmitFlags(iife, EmitFlags.AdviseOnEmitNode); - return iife; - } - } - - function visitBinaryExpression(node: BinaryExpression, valueIsDiscarded: boolean) { - if (isDestructuringAssignment(node)) { - const savedPendingExpressions = pendingExpressions; - pendingExpressions = undefined; - node = factory.updateBinaryExpression( - node, - visitNode(node.left, visitorDestructuringTarget), - node.operatorToken, - visitNode(node.right, visitor) - ); - const expr = some(pendingExpressions) ? - factory.inlineExpressions(compact([...pendingExpressions, node])) : - node; - pendingExpressions = savedPendingExpressions; - return expr; } - if (isAssignmentExpression(node)) { - if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierPropertyAccessExpression(node.left)) { - const info = accessPrivateIdentifier(node.left.name); - if (info) { - return setTextRange( - setOriginalNode( - createPrivateIdentifierAssignment(info, node.left.expression, node.right, node.operatorToken.kind), - node - ), - node - ); - } + else if (shouldTransformSuperInStaticInitializers && + isSuperProperty(node.left) && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment) { + const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; + if (facts & ClassFacts.ClassWasDecorated) { + return factory.updateBinaryExpression( + node, + visitInvalidSuperProperty(node.left), + node.operatorToken, + visitNode(node.right, visitor, isExpression)); } - else if (shouldTransformSuperInStaticInitializers && - isSuperProperty(node.left) && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment) { - const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; - if (facts & ClassFacts.ClassWasDecorated) { - return factory.updateBinaryExpression( - node, - visitInvalidSuperProperty(node.left), - node.operatorToken, - visitNode(node.right, visitor, isExpression)); - } - if (classConstructor && superClassReference) { - let setterName = - isElementAccessExpression(node.left) ? visitNode(node.left.argumentExpression, visitor, isExpression) : - isIdentifier(node.left.name) ? factory.createStringLiteralFromNode(node.left.name) : - undefined; - if (setterName) { - // converts `super.x = 1` into `(Reflect.set(_baseTemp, "x", _a = 1, _classTemp), _a)` - // converts `super[f()] = 1` into `(Reflect.set(_baseTemp, f(), _a = 1, _classTemp), _a)` - // converts `super.x += 1` into `(Reflect.set(_baseTemp, "x", _a = Reflect.get(_baseTemp, "x", _classtemp) + 1, _classTemp), _a)` - // converts `super[f()] += 1` into `(Reflect.set(_baseTemp, _a = f(), _b = Reflect.get(_baseTemp, _a, _classtemp) + 1, _classTemp), _b)` - - let expression = visitNode(node.right, visitor, isExpression); - if (isCompoundAssignment(node.operatorToken.kind)) { - let getterName = setterName; - if (!isSimpleInlineableExpression(setterName)) { - getterName = factory.createTempVariable(hoistVariableDeclaration); - setterName = factory.createAssignment(getterName, setterName); - } - const superPropertyGet = factory.createReflectGetCall( - superClassReference, - getterName, - classConstructor - ); - setOriginalNode(superPropertyGet, node.left); - setTextRange(superPropertyGet, node.left); - - expression = factory.createBinaryExpression( - superPropertyGet, - getNonAssignmentOperatorForCompoundAssignment(node.operatorToken.kind), - expression - ); - setTextRange(expression, node); - } - - const temp = valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); - if (temp) { - expression = factory.createAssignment(temp, expression); - setTextRange(temp, node); + if (classConstructor && superClassReference) { + let setterName = + isElementAccessExpression(node.left) ? visitNode(node.left.argumentExpression, visitor, isExpression) : + isIdentifier(node.left.name) ? factory.createStringLiteralFromNode(node.left.name) : + undefined; + if (setterName) { + // converts `super.x = 1` into `(Reflect.set(_baseTemp, "x", _a = 1, _classTemp), _a)` + // converts `super[f()] = 1` into `(Reflect.set(_baseTemp, f(), _a = 1, _classTemp), _a)` + // converts `super.x += 1` into `(Reflect.set(_baseTemp, "x", _a = Reflect.get(_baseTemp, "x", _classtemp) + 1, _classTemp), _a)` + // converts `super[f()] += 1` into `(Reflect.set(_baseTemp, _a = f(), _b = Reflect.get(_baseTemp, _a, _classtemp) + 1, _classTemp), _b)` + + let expression = visitNode(node.right, visitor, isExpression); + if (isCompoundAssignment(node.operatorToken.kind)) { + let getterName = setterName; + if (!isSimpleInlineableExpression(setterName)) { + getterName = factory.createTempVariable(hoistVariableDeclaration); + setterName = factory.createAssignment(getterName, setterName); } - - expression = factory.createReflectSetCall( + const superPropertyGet = factory.createReflectGetCall( superClassReference, - setterName, - expression, + getterName, classConstructor ); - setOriginalNode(expression, node); + setOriginalNode(superPropertyGet, node.left); + setTextRange(superPropertyGet, node.left); + + expression = factory.createBinaryExpression( + superPropertyGet, + getNonAssignmentOperatorForCompoundAssignment(node.operatorToken.kind), + expression + ); setTextRange(expression, node); + } - if (temp) { - expression = factory.createComma(expression, temp); - setTextRange(expression, node); - } + const temp = valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); + if (temp) { + expression = factory.createAssignment(temp, expression); + setTextRange(temp, node); + } + + expression = factory.createReflectSetCall( + superClassReference, + setterName, + expression, + classConstructor + ); + setOriginalNode(expression, node); + setTextRange(expression, node); - return expression; + if (temp) { + expression = factory.createComma(expression, temp); + setTextRange(expression, node); } + + return expression; } } } - if (node.operatorToken.kind === SyntaxKind.InKeyword && isPrivateIdentifier(node.left)) { - return visitPrivateIdentifierInInExpression(node); - } - return visitEachChild(node, visitor, context); } + if (node.operatorToken.kind === SyntaxKind.InKeyword && isPrivateIdentifier(node.left)) { + return visitPrivateIdentifierInInExpression(node); + } + return visitEachChild(node, visitor, context); + } - function createPrivateIdentifierAssignment(info: PrivateIdentifierInfo, receiver: Expression, right: Expression, operator: AssignmentOperator): Expression { - receiver = visitNode(receiver, visitor, isExpression); - right = visitNode(right, visitor, isExpression); - - if (isCompoundAssignment(operator)) { - const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver); - receiver = initializeExpression || readExpression; - right = factory.createBinaryExpression( - createPrivateIdentifierAccessHelper(info, readExpression), - getNonAssignmentOperatorForCompoundAssignment(operator), - right - ); - } + function createPrivateIdentifierAssignment(info: PrivateIdentifierInfo, receiver: Expression, right: Expression, operator: AssignmentOperator): Expression { + receiver = visitNode(receiver, visitor, isExpression); + right = visitNode(right, visitor, isExpression); + + if (isCompoundAssignment(operator)) { + const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver); + receiver = initializeExpression || readExpression; + right = factory.createBinaryExpression( + createPrivateIdentifierAccessHelper(info, readExpression), + getNonAssignmentOperatorForCompoundAssignment(operator), + right + ); + } - setCommentRange(receiver, moveRangePos(receiver, -1)); + setCommentRange(receiver, moveRangePos(receiver, -1)); - switch(info.kind) { - case PrivateIdentifierKind.Accessor: - return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( - receiver, - info.brandCheckIdentifier, - right, - info.kind, - info.setterName - ); - case PrivateIdentifierKind.Method: - return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( - receiver, - info.brandCheckIdentifier, - right, - info.kind, - /* f */ undefined - ); - case PrivateIdentifierKind.Field: - return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( - receiver, - info.brandCheckIdentifier, - right, - info.kind, - info.variableName - ); - default: - Debug.assertNever(info, "Unknown private element type"); - } + switch(info.kind) { + case PrivateIdentifierKind.Accessor: + return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( + receiver, + info.brandCheckIdentifier, + right, + info.kind, + info.setterName + ); + case PrivateIdentifierKind.Method: + return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( + receiver, + info.brandCheckIdentifier, + right, + info.kind, + /* f */ undefined + ); + case PrivateIdentifierKind.Field: + return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( + receiver, + info.brandCheckIdentifier, + right, + info.kind, + info.variableName + ); + default: + Debug.assertNever(info, "Unknown private element type"); } + } - /** - * Set up the environment for a class. - */ - function visitClassLike(node: ClassLikeDeclaration) { - if (!forEach(node.members, doesClassElementNeedTransform)) { - return visitEachChild(node, visitor, context); - } + /** + * Set up the environment for a class. + */ + function visitClassLike(node: ClassLikeDeclaration) { + if (!forEach(node.members, doesClassElementNeedTransform)) { + return visitEachChild(node, visitor, context); + } - const savedPendingExpressions = pendingExpressions; - pendingExpressions = undefined; - startClassLexicalEnvironment(); + const savedPendingExpressions = pendingExpressions; + pendingExpressions = undefined; + startClassLexicalEnvironment(); - if (shouldTransformPrivateElementsOrClassStaticBlocks) { - const name = getNameOfDeclaration(node); - if (name && isIdentifier(name)) { - getPrivateIdentifierEnvironment().className = idText(name); - } + if (shouldTransformPrivateElementsOrClassStaticBlocks) { + const name = getNameOfDeclaration(node); + if (name && isIdentifier(name)) { + getPrivateIdentifierEnvironment().className = idText(name); + } - const privateInstanceMethodsAndAccessors = getPrivateInstanceMethodsAndAccessors(node); - if (some(privateInstanceMethodsAndAccessors)) { - getPrivateIdentifierEnvironment().weakSetName = createHoistedVariableForClass( - "instances", - privateInstanceMethodsAndAccessors[0].name - ); - } + const privateInstanceMethodsAndAccessors = getPrivateInstanceMethodsAndAccessors(node); + if (some(privateInstanceMethodsAndAccessors)) { + getPrivateIdentifierEnvironment().weakSetName = createHoistedVariableForClass( + "instances", + privateInstanceMethodsAndAccessors[0].name + ); } + } - const result = isClassDeclaration(node) ? - visitClassDeclaration(node) : - visitClassExpression(node); + const result = isClassDeclaration(node) ? + visitClassDeclaration(node) : + visitClassExpression(node); - endClassLexicalEnvironment(); - pendingExpressions = savedPendingExpressions; - return result; - } + endClassLexicalEnvironment(); + pendingExpressions = savedPendingExpressions; + return result; + } - function doesClassElementNeedTransform(node: ClassElement) { - return isPropertyDeclaration(node) || isClassStaticBlockDeclaration(node) || (shouldTransformPrivateElementsOrClassStaticBlocks && node.name && isPrivateIdentifier(node.name)); - } + function doesClassElementNeedTransform(node: ClassElement) { + return isPropertyDeclaration(node) || isClassStaticBlockDeclaration(node) || (shouldTransformPrivateElementsOrClassStaticBlocks && node.name && isPrivateIdentifier(node.name)); + } - function getPrivateInstanceMethodsAndAccessors(node: ClassLikeDeclaration) { - return filter(node.members, isNonStaticMethodOrAccessorWithPrivateName); - } + function getPrivateInstanceMethodsAndAccessors(node: ClassLikeDeclaration) { + return filter(node.members, isNonStaticMethodOrAccessorWithPrivateName); + } - function getClassFacts(node: ClassLikeDeclaration) { - let facts = ClassFacts.None; - const original = getOriginalNode(node); - if (isClassDeclaration(original) && classOrConstructorParameterIsDecorated(original)) { - facts |= ClassFacts.ClassWasDecorated; - } - for (const member of node.members) { - if (!isStatic(member)) continue; - if (member.name && isPrivateIdentifier(member.name) && shouldTransformPrivateElementsOrClassStaticBlocks) { - facts |= ClassFacts.NeedsClassConstructorReference; - } - if (isPropertyDeclaration(member) || isClassStaticBlockDeclaration(member)) { - if (shouldTransformThisInStaticInitializers && member.transformFlags & TransformFlags.ContainsLexicalThis) { - facts |= ClassFacts.NeedsSubstitutionForThisInClassStaticField; - if (!(facts & ClassFacts.ClassWasDecorated)) { - facts |= ClassFacts.NeedsClassConstructorReference; - } + function getClassFacts(node: ClassLikeDeclaration) { + let facts = ClassFacts.None; + const original = getOriginalNode(node); + if (isClassDeclaration(original) && classOrConstructorParameterIsDecorated(original)) { + facts |= ClassFacts.ClassWasDecorated; + } + for (const member of node.members) { + if (!isStatic(member)) continue; + if (member.name && isPrivateIdentifier(member.name) && shouldTransformPrivateElementsOrClassStaticBlocks) { + facts |= ClassFacts.NeedsClassConstructorReference; + } + if (isPropertyDeclaration(member) || isClassStaticBlockDeclaration(member)) { + if (shouldTransformThisInStaticInitializers && member.transformFlags & TransformFlags.ContainsLexicalThis) { + facts |= ClassFacts.NeedsSubstitutionForThisInClassStaticField; + if (!(facts & ClassFacts.ClassWasDecorated)) { + facts |= ClassFacts.NeedsClassConstructorReference; } - if (shouldTransformSuperInStaticInitializers && member.transformFlags & TransformFlags.ContainsLexicalSuper) { - if (!(facts & ClassFacts.ClassWasDecorated)) { - facts |= ClassFacts.NeedsClassConstructorReference | ClassFacts.NeedsClassSuperReference; - } + } + if (shouldTransformSuperInStaticInitializers && member.transformFlags & TransformFlags.ContainsLexicalSuper) { + if (!(facts & ClassFacts.ClassWasDecorated)) { + facts |= ClassFacts.NeedsClassConstructorReference | ClassFacts.NeedsClassSuperReference; } } } - return facts; } + return facts; + } - function visitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { - const facts = currentClassLexicalEnvironment?.facts || ClassFacts.None; - if (facts & ClassFacts.NeedsClassSuperReference) { - const temp = factory.createTempVariable(hoistVariableDeclaration, /*reserveInNestedScopes*/ true); - getClassLexicalEnvironment().superClassReference = temp; - return factory.updateExpressionWithTypeArguments( - node, - factory.createAssignment( - temp, - visitNode(node.expression, visitor, isExpression) - ), - /*typeArguments*/ undefined - ); - } - return visitEachChild(node, visitor, context); + function visitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { + const facts = currentClassLexicalEnvironment?.facts || ClassFacts.None; + if (facts & ClassFacts.NeedsClassSuperReference) { + const temp = factory.createTempVariable(hoistVariableDeclaration, /*reserveInNestedScopes*/ true); + getClassLexicalEnvironment().superClassReference = temp; + return factory.updateExpressionWithTypeArguments( + node, + factory.createAssignment( + temp, + visitNode(node.expression, visitor, isExpression) + ), + /*typeArguments*/ undefined + ); } + return visitEachChild(node, visitor, context); + } - function visitClassDeclaration(node: ClassDeclaration) { - const facts = getClassFacts(node); - if (facts) { - getClassLexicalEnvironment().facts = facts; - } - if (facts & ClassFacts.NeedsSubstitutionForThisInClassStaticField) { - enableSubstitutionForClassStaticThisOrSuperReference(); - } + function visitClassDeclaration(node: ClassDeclaration) { + const facts = getClassFacts(node); + if (facts) { + getClassLexicalEnvironment().facts = facts; + } + if (facts & ClassFacts.NeedsSubstitutionForThisInClassStaticField) { + enableSubstitutionForClassStaticThisOrSuperReference(); + } - const staticProperties = getStaticPropertiesAndClassStaticBlock(node); + const staticProperties = getStaticPropertiesAndClassStaticBlock(node); - // If a class has private static fields, or a static field has a `this` or `super` reference, - // then we need to allocate a temp variable to hold on to that reference. - let pendingClassReferenceAssignment: BinaryExpression | undefined; - if (facts & ClassFacts.NeedsClassConstructorReference) { - const temp = factory.createTempVariable(hoistVariableDeclaration, /*reservedInNestedScopes*/ true); - getClassLexicalEnvironment().classConstructor = factory.cloneNode(temp); - pendingClassReferenceAssignment = factory.createAssignment(temp, factory.getInternalName(node)); - } + // If a class has private static fields, or a static field has a `this` or `super` reference, + // then we need to allocate a temp variable to hold on to that reference. + let pendingClassReferenceAssignment: BinaryExpression | undefined; + if (facts & ClassFacts.NeedsClassConstructorReference) { + const temp = factory.createTempVariable(hoistVariableDeclaration, /*reservedInNestedScopes*/ true); + getClassLexicalEnvironment().classConstructor = factory.cloneNode(temp); + pendingClassReferenceAssignment = factory.createAssignment(temp, factory.getInternalName(node)); + } - const extendsClauseElement = getEffectiveBaseTypeNode(node); - const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); + const extendsClauseElement = getEffectiveBaseTypeNode(node); + const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); - const statements: Statement[] = [ - factory.updateClassDeclaration( - node, - /*decorators*/ undefined, - node.modifiers, - node.name, - /*typeParameters*/ undefined, - visitNodes(node.heritageClauses, heritageClauseVisitor, isHeritageClause), - transformClassMembers(node, isDerivedClass) - ) - ]; + const statements: Statement[] = [ + factory.updateClassDeclaration( + node, + /*decorators*/ undefined, + node.modifiers, + node.name, + /*typeParameters*/ undefined, + visitNodes(node.heritageClauses, heritageClauseVisitor, isHeritageClause), + transformClassMembers(node, isDerivedClass) + ) + ]; - if (pendingClassReferenceAssignment) { - getPendingExpressions().unshift(pendingClassReferenceAssignment); - } + if (pendingClassReferenceAssignment) { + getPendingExpressions().unshift(pendingClassReferenceAssignment); + } - // Write any pending expressions from elided or moved computed property names - if (some(pendingExpressions)) { - statements.push(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))); - } + // Write any pending expressions from elided or moved computed property names + if (some(pendingExpressions)) { + statements.push(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))); + } - // Emit static property assignment. Because classDeclaration is lexically evaluated, - // it is safe to emit static property assignment after classDeclaration - // From ES6 specification: - // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using - // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration. + // Emit static property assignment. Because classDeclaration is lexically evaluated, + // it is safe to emit static property assignment after classDeclaration + // From ES6 specification: + // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using + // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration. - if (some(staticProperties)) { - addPropertyOrClassStaticBlockStatements(statements, staticProperties, factory.getInternalName(node)); - } + if (some(staticProperties)) { + addPropertyOrClassStaticBlockStatements(statements, staticProperties, factory.getInternalName(node)); + } - return statements; + return statements; + } + + function visitClassExpression(node: ClassExpression): Expression { + const facts = getClassFacts(node); + if (facts) { + getClassLexicalEnvironment().facts = facts; } - function visitClassExpression(node: ClassExpression): Expression { - const facts = getClassFacts(node); - if (facts) { - getClassLexicalEnvironment().facts = facts; - } + if (facts & ClassFacts.NeedsSubstitutionForThisInClassStaticField) { + enableSubstitutionForClassStaticThisOrSuperReference(); + } - if (facts & ClassFacts.NeedsSubstitutionForThisInClassStaticField) { - enableSubstitutionForClassStaticThisOrSuperReference(); - } + // If this class expression is a transformation of a decorated class declaration, + // then we want to output the pendingExpressions as statements, not as inlined + // expressions with the class statement. + // + // In this case, we use pendingStatements to produce the same output as the + // class declaration transformation. The VariableStatement visitor will insert + // these statements after the class expression variable statement. + const isDecoratedClassDeclaration = !!(facts & ClassFacts.ClassWasDecorated); - // If this class expression is a transformation of a decorated class declaration, - // then we want to output the pendingExpressions as statements, not as inlined - // expressions with the class statement. - // - // In this case, we use pendingStatements to produce the same output as the - // class declaration transformation. The VariableStatement visitor will insert - // these statements after the class expression variable statement. - const isDecoratedClassDeclaration = !!(facts & ClassFacts.ClassWasDecorated); - - const staticPropertiesOrClassStaticBlocks = getStaticPropertiesAndClassStaticBlock(node); - - const extendsClauseElement = getEffectiveBaseTypeNode(node); - const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); - - const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; - let temp: Identifier | undefined; - function createClassTempVar() { - const classCheckFlags = resolver.getNodeCheckFlags(node); - const isClassWithConstructorReference = classCheckFlags & NodeCheckFlags.ClassWithConstructorReference; - const requiresBlockScopedVar = classCheckFlags & NodeCheckFlags.BlockScopedBindingInLoop; - return factory.createTempVariable(requiresBlockScopedVar ? addBlockScopedVariable : hoistVariableDeclaration, !!isClassWithConstructorReference); - } + const staticPropertiesOrClassStaticBlocks = getStaticPropertiesAndClassStaticBlock(node); - if (facts & ClassFacts.NeedsClassConstructorReference) { - temp = createClassTempVar(); - getClassLexicalEnvironment().classConstructor = factory.cloneNode(temp); - } + const extendsClauseElement = getEffectiveBaseTypeNode(node); + const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); - const classExpression = factory.updateClassExpression( - node, - visitNodes(node.decorators, visitor, isDecorator), - node.modifiers, - node.name, - /*typeParameters*/ undefined, - visitNodes(node.heritageClauses, heritageClauseVisitor, isHeritageClause), - transformClassMembers(node, isDerivedClass) - ); + const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; + let temp: Identifier | undefined; + function createClassTempVar() { + const classCheckFlags = resolver.getNodeCheckFlags(node); + const isClassWithConstructorReference = classCheckFlags & NodeCheckFlags.ClassWithConstructorReference; + const requiresBlockScopedVar = classCheckFlags & NodeCheckFlags.BlockScopedBindingInLoop; + return factory.createTempVariable(requiresBlockScopedVar ? addBlockScopedVariable : hoistVariableDeclaration, !!isClassWithConstructorReference); + } - const hasTransformableStatics = some(staticPropertiesOrClassStaticBlocks, p => isClassStaticBlockDeclaration(p) || !!p.initializer || (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifier(p.name))); - if (hasTransformableStatics || some(pendingExpressions)) { - if (isDecoratedClassDeclaration) { - Debug.assertIsDefined(pendingStatements, "Decorated classes transformed by TypeScript are expected to be within a variable declaration."); + if (facts & ClassFacts.NeedsClassConstructorReference) { + temp = createClassTempVar(); + getClassLexicalEnvironment().classConstructor = factory.cloneNode(temp); + } - // Write any pending expressions from elided or moved computed property names - if (pendingStatements && pendingExpressions && some(pendingExpressions)) { - pendingStatements.push(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))); - } + const classExpression = factory.updateClassExpression( + node, + visitNodes(node.decorators, visitor, isDecorator), + node.modifiers, + node.name, + /*typeParameters*/ undefined, + visitNodes(node.heritageClauses, heritageClauseVisitor, isHeritageClause), + transformClassMembers(node, isDerivedClass) + ); - if (pendingStatements && some(staticPropertiesOrClassStaticBlocks)) { - addPropertyOrClassStaticBlockStatements(pendingStatements, staticPropertiesOrClassStaticBlocks, factory.getInternalName(node)); - } - if (temp) { - return factory.inlineExpressions([factory.createAssignment(temp, classExpression), temp]); - } - return classExpression; - } - else { - const expressions: Expression[] = []; - temp ||= createClassTempVar(); - if (isClassWithConstructorReference) { - // record an alias as the class name is not in scope for statics. - enableSubstitutionForClassAliases(); - const alias = factory.cloneNode(temp) as GeneratedIdentifier; - alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; - classAliases[getOriginalNodeId(node)] = alias; - } + const hasTransformableStatics = some(staticPropertiesOrClassStaticBlocks, p => isClassStaticBlockDeclaration(p) || !!p.initializer || (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifier(p.name))); + if (hasTransformableStatics || some(pendingExpressions)) { + if (isDecoratedClassDeclaration) { + Debug.assertIsDefined(pendingStatements, "Decorated classes transformed by TypeScript are expected to be within a variable declaration."); - // To preserve the behavior of the old emitter, we explicitly indent - // the body of a class with static initializers. - setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression)); - expressions.push(startOnNewLine(factory.createAssignment(temp, classExpression))); - // Add any pending expressions leftover from elided or relocated computed property names - addRange(expressions, map(pendingExpressions, startOnNewLine)); - addRange(expressions, generateInitializedPropertyExpressionsOrClassStaticBlock(staticPropertiesOrClassStaticBlocks, temp)); - expressions.push(startOnNewLine(temp)); + // Write any pending expressions from elided or moved computed property names + if (pendingStatements && pendingExpressions && some(pendingExpressions)) { + pendingStatements.push(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))); + } - return factory.inlineExpressions(expressions); + if (pendingStatements && some(staticPropertiesOrClassStaticBlocks)) { + addPropertyOrClassStaticBlockStatements(pendingStatements, staticPropertiesOrClassStaticBlocks, factory.getInternalName(node)); } + if (temp) { + return factory.inlineExpressions([factory.createAssignment(temp, classExpression), temp]); + } + return classExpression; } + else { + const expressions: Expression[] = []; + temp ||= createClassTempVar(); + if (isClassWithConstructorReference) { + // record an alias as the class name is not in scope for statics. + enableSubstitutionForClassAliases(); + const alias = factory.cloneNode(temp) as GeneratedIdentifier; + alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; + classAliases[getOriginalNodeId(node)] = alias; + } - return classExpression; - } + // To preserve the behavior of the old emitter, we explicitly indent + // the body of a class with static initializers. + setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression)); + expressions.push(startOnNewLine(factory.createAssignment(temp, classExpression))); + // Add any pending expressions leftover from elided or relocated computed property names + addRange(expressions, map(pendingExpressions, startOnNewLine)); + addRange(expressions, generateInitializedPropertyExpressionsOrClassStaticBlock(staticPropertiesOrClassStaticBlocks, temp)); + expressions.push(startOnNewLine(temp)); - function visitClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { - if (!shouldTransformPrivateElementsOrClassStaticBlocks) { - return visitEachChild(node, classElementVisitor, context); + return factory.inlineExpressions(expressions); } - // ClassStaticBlockDeclaration for classes are transformed in `visitClassDeclaration` or `visitClassExpression`. - return undefined; } - function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { - if (shouldTransformPrivateElementsOrClassStaticBlocks) { - // Declare private names. - for (const member of node.members) { - if (isPrivateIdentifierClassElementDeclaration(member)) { - addPrivateIdentifierToEnvironment(member); - } - } + return classExpression; + } + + function visitClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { + if (!shouldTransformPrivateElementsOrClassStaticBlocks) { + return visitEachChild(node, classElementVisitor, context); + } + // ClassStaticBlockDeclaration for classes are transformed in `visitClassDeclaration` or `visitClassExpression`. + return undefined; + } - if (some(getPrivateInstanceMethodsAndAccessors(node))) { - createBrandCheckWeakSetForPrivateMethods(); + function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { + if (shouldTransformPrivateElementsOrClassStaticBlocks) { + // Declare private names. + for (const member of node.members) { + if (isPrivateIdentifierClassElementDeclaration(member)) { + addPrivateIdentifierToEnvironment(member); } } - const members: ClassElement[] = []; - const constructor = transformConstructor(node, isDerivedClass); - if (constructor) { - members.push(constructor); + if (some(getPrivateInstanceMethodsAndAccessors(node))) { + createBrandCheckWeakSetForPrivateMethods(); } - addRange(members, visitNodes(node.members, classElementVisitor, isClassElement)); - return setTextRange(factory.createNodeArray(members), /*location*/ node.members); } - function createBrandCheckWeakSetForPrivateMethods() { - const { weakSetName } = getPrivateIdentifierEnvironment(); - Debug.assert(weakSetName, "weakSetName should be set in private identifier environment"); + const members: ClassElement[] = []; + const constructor = transformConstructor(node, isDerivedClass); + if (constructor) { + members.push(constructor); + } + addRange(members, visitNodes(node.members, classElementVisitor, isClassElement)); + return setTextRange(factory.createNodeArray(members), /*location*/ node.members); + } - getPendingExpressions().push( - factory.createAssignment( - weakSetName, - factory.createNewExpression( - factory.createIdentifier("WeakSet"), - /*typeArguments*/ undefined, - [] - ) + function createBrandCheckWeakSetForPrivateMethods() { + const { weakSetName } = getPrivateIdentifierEnvironment(); + Debug.assert(weakSetName, "weakSetName should be set in private identifier environment"); + + getPendingExpressions().push( + factory.createAssignment( + weakSetName, + factory.createNewExpression( + factory.createIdentifier("WeakSet"), + /*typeArguments*/ undefined, + [] ) - ); - } + ) + ); + } - function isClassElementThatRequiresConstructorStatement(member: ClassElement) { - if (isStatic(member) || hasSyntacticModifier(getOriginalNode(member), ModifierFlags.Abstract)) { - return false; - } - if (useDefineForClassFields) { - // If we are using define semantics and targeting ESNext or higher, - // then we don't need to transform any class properties. - return languageVersion < ScriptTarget.ESNext; - } - return isInitializedProperty(member) || shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierClassElementDeclaration(member); + function isClassElementThatRequiresConstructorStatement(member: ClassElement) { + if (isStatic(member) || hasSyntacticModifier(getOriginalNode(member), ModifierFlags.Abstract)) { + return false; + } + if (useDefineForClassFields) { + // If we are using define semantics and targeting ESNext or higher, + // then we don't need to transform any class properties. + return languageVersion < ScriptTarget.ESNext; } + return isInitializedProperty(member) || shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierClassElementDeclaration(member); + } - function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { - const constructor = visitNode(getFirstConstructorWithBody(node), visitor, isConstructorDeclaration); - const elements = node.members.filter(isClassElementThatRequiresConstructorStatement); - if (!some(elements)) { - return constructor; - } - const parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context); - const body = transformConstructorBody(node, constructor, isDerivedClass); - if (!body) { - return undefined; - } - return startOnNewLine( - setOriginalNode( - setTextRange( - factory.createConstructorDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - parameters ?? [], - body - ), - constructor || node + function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { + const constructor = visitNode(getFirstConstructorWithBody(node), visitor, isConstructorDeclaration); + const elements = node.members.filter(isClassElementThatRequiresConstructorStatement); + if (!some(elements)) { + return constructor; + } + const parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context); + const body = transformConstructorBody(node, constructor, isDerivedClass); + if (!body) { + return undefined; + } + return startOnNewLine( + setOriginalNode( + setTextRange( + factory.createConstructorDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + parameters ?? [], + body ), - constructor - ) - ); + constructor || node + ), + constructor + ) + ); + } + + function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) { + let properties = getProperties(node, /*requireInitializer*/ false, /*isStatic*/ false); + if (!useDefineForClassFields) { + properties = filter(properties, property => !!property.initializer || isPrivateIdentifier(property.name)); } - function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) { - let properties = getProperties(node, /*requireInitializer*/ false, /*isStatic*/ false); - if (!useDefineForClassFields) { - properties = filter(properties, property => !!property.initializer || isPrivateIdentifier(property.name)); - } + const privateMethodsAndAccessors = getPrivateInstanceMethodsAndAccessors(node); + const needsConstructorBody = some(properties) || some(privateMethodsAndAccessors); - const privateMethodsAndAccessors = getPrivateInstanceMethodsAndAccessors(node); - const needsConstructorBody = some(properties) || some(privateMethodsAndAccessors); + // Only generate synthetic constructor when there are property initializers to move. + if (!constructor && !needsConstructorBody) { + return visitFunctionBody(/*node*/ undefined, visitor, context); + } - // Only generate synthetic constructor when there are property initializers to move. - if (!constructor && !needsConstructorBody) { - return visitFunctionBody(/*node*/ undefined, visitor, context); - } + resumeLexicalEnvironment(); - resumeLexicalEnvironment(); - - let indexOfFirstStatement = 0; - let statements: Statement[] = []; - - if (!constructor && isDerivedClass) { - // Add a synthetic `super` call: - // - // super(...arguments); - // - statements.push( - factory.createExpressionStatement( - factory.createCallExpression( - factory.createSuper(), - /*typeArguments*/ undefined, - [factory.createSpreadElement(factory.createIdentifier("arguments"))] - ) - ) - ); - } + let indexOfFirstStatement = 0; + let statements: Statement[] = []; - if (constructor) { - indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(factory, constructor, statements, visitor); - } - // Add the property initializers. Transforms this: - // - // public x = 1; - // - // Into this: + if (!constructor && isDerivedClass) { + // Add a synthetic `super` call: // - // constructor() { - // this.x = 1; - // } + // super(...arguments); // - if (constructor?.body) { - let afterParameterProperties = findIndex(constructor.body.statements, s => !isParameterPropertyDeclaration(getOriginalNode(s), constructor), indexOfFirstStatement); - if (afterParameterProperties === -1) { - afterParameterProperties = constructor.body.statements.length; - } - if (afterParameterProperties > indexOfFirstStatement) { - if (!useDefineForClassFields) { - addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, indexOfFirstStatement, afterParameterProperties - indexOfFirstStatement)); - } - indexOfFirstStatement = afterParameterProperties; - } - } - const receiver = factory.createThis(); - // private methods can be called in property initializers, they should execute first. - addMethodStatements(statements, privateMethodsAndAccessors, receiver); - addPropertyOrClassStaticBlockStatements(statements, properties, receiver); - - // Add existing statements, skipping the initial super call. - if (constructor) { - addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); - } - - statements = factory.mergeLexicalEnvironment(statements, endLexicalEnvironment()); - - return setTextRange( - factory.createBlock( - setTextRange( - factory.createNodeArray(statements), - /*location*/ constructor ? constructor.body!.statements : node.members - ), - /*multiLine*/ true - ), - /*location*/ constructor ? constructor.body : undefined + statements.push( + factory.createExpressionStatement( + factory.createCallExpression( + factory.createSuper(), + /*typeArguments*/ undefined, + [factory.createSpreadElement(factory.createIdentifier("arguments"))] + ) + ) ); } - /** - * Generates assignment statements for property initializers. - * - * @param properties An array of property declarations to transform. - * @param receiver The receiver on which each property should be assigned. - */ - function addPropertyOrClassStaticBlockStatements(statements: Statement[], properties: readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[], receiver: LeftHandSideExpression) { - for (const property of properties) { - const expression = isClassStaticBlockDeclaration(property) ? - transformClassStaticBlockDeclaration(property) : - transformProperty(property, receiver); - if (!expression) { - continue; + if (constructor) { + indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(factory, constructor, statements, visitor); + } + // Add the property initializers. Transforms this: + // + // public x = 1; + // + // Into this: + // + // constructor() { + // this.x = 1; + // } + // + if (constructor?.body) { + let afterParameterProperties = findIndex(constructor.body.statements, s => !isParameterPropertyDeclaration(getOriginalNode(s), constructor), indexOfFirstStatement); + if (afterParameterProperties === -1) { + afterParameterProperties = constructor.body.statements.length; + } + if (afterParameterProperties > indexOfFirstStatement) { + if (!useDefineForClassFields) { + addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, indexOfFirstStatement, afterParameterProperties - indexOfFirstStatement)); } - const statement = factory.createExpressionStatement(expression); - setSourceMapRange(statement, moveRangePastModifiers(property)); - setCommentRange(statement, property); - setOriginalNode(statement, property); - statements.push(statement); + indexOfFirstStatement = afterParameterProperties; } } + const receiver = factory.createThis(); + // private methods can be called in property initializers, they should execute first. + addMethodStatements(statements, privateMethodsAndAccessors, receiver); + addPropertyOrClassStaticBlockStatements(statements, properties, receiver); - /** - * Generates assignment expressions for property initializers. - * - * @param propertiesOrClassStaticBlocks An array of property declarations to transform. - * @param receiver The receiver on which each property should be assigned. - */ - function generateInitializedPropertyExpressionsOrClassStaticBlock(propertiesOrClassStaticBlocks: readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[], receiver: LeftHandSideExpression) { - const expressions: Expression[] = []; - for (const property of propertiesOrClassStaticBlocks) { - const expression = isClassStaticBlockDeclaration(property) ? transformClassStaticBlockDeclaration(property) : transformProperty(property, receiver); - if (!expression) { - continue; - } - startOnNewLine(expression); - setSourceMapRange(expression, moveRangePastModifiers(property)); - setCommentRange(expression, property); - setOriginalNode(expression, property); - expressions.push(expression); - } + // Add existing statements, skipping the initial super call. + if (constructor) { + addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); + } + + statements = factory.mergeLexicalEnvironment(statements, endLexicalEnvironment()); - return expressions; + return setTextRange( + factory.createBlock( + setTextRange( + factory.createNodeArray(statements), + /*location*/ constructor ? constructor.body!.statements : node.members + ), + /*multiLine*/ true + ), + /*location*/ constructor ? constructor.body : undefined + ); + } + + /** + * Generates assignment statements for property initializers. + * + * @param properties An array of property declarations to transform. + * @param receiver The receiver on which each property should be assigned. + */ + function addPropertyOrClassStaticBlockStatements(statements: Statement[], properties: readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[], receiver: LeftHandSideExpression) { + for (const property of properties) { + const expression = isClassStaticBlockDeclaration(property) ? + transformClassStaticBlockDeclaration(property) : + transformProperty(property, receiver); + if (!expression) { + continue; + } + const statement = factory.createExpressionStatement(expression); + setSourceMapRange(statement, moveRangePastModifiers(property)); + setCommentRange(statement, property); + setOriginalNode(statement, property); + statements.push(statement); } + } - /** - * Transforms a property initializer into an assignment statement. - * - * @param property The property declaration. - * @param receiver The object receiving the property assignment. - */ - function transformProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) { - const savedCurrentStaticPropertyDeclarationOrStaticBlock = currentStaticPropertyDeclarationOrStaticBlock; - const transformed = transformPropertyWorker(property, receiver); - if (transformed && hasStaticModifier(property) && currentClassLexicalEnvironment?.facts) { - // capture the lexical environment for the member - setOriginalNode(transformed, property); - addEmitFlags(transformed, EmitFlags.AdviseOnEmitNode); - classLexicalEnvironmentMap.set(getOriginalNodeId(transformed), currentClassLexicalEnvironment); + /** + * Generates assignment expressions for property initializers. + * + * @param propertiesOrClassStaticBlocks An array of property declarations to transform. + * @param receiver The receiver on which each property should be assigned. + */ + function generateInitializedPropertyExpressionsOrClassStaticBlock(propertiesOrClassStaticBlocks: readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[], receiver: LeftHandSideExpression) { + const expressions: Expression[] = []; + for (const property of propertiesOrClassStaticBlocks) { + const expression = isClassStaticBlockDeclaration(property) ? transformClassStaticBlockDeclaration(property) : transformProperty(property, receiver); + if (!expression) { + continue; } - currentStaticPropertyDeclarationOrStaticBlock = savedCurrentStaticPropertyDeclarationOrStaticBlock; - return transformed; + startOnNewLine(expression); + setSourceMapRange(expression, moveRangePastModifiers(property)); + setCommentRange(expression, property); + setOriginalNode(expression, property); + expressions.push(expression); } - function transformPropertyWorker(property: PropertyDeclaration, receiver: LeftHandSideExpression) { - // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name) - const emitAssignment = !useDefineForClassFields; - const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression) - ? factory.updateComputedPropertyName(property.name, factory.getGeneratedNameForNode(property.name)) - : property.name; + return expressions; + } - if (hasStaticModifier(property)) { - currentStaticPropertyDeclarationOrStaticBlock = property; - } + /** + * Transforms a property initializer into an assignment statement. + * + * @param property The property declaration. + * @param receiver The object receiving the property assignment. + */ + function transformProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) { + const savedCurrentStaticPropertyDeclarationOrStaticBlock = currentStaticPropertyDeclarationOrStaticBlock; + const transformed = transformPropertyWorker(property, receiver); + if (transformed && hasStaticModifier(property) && currentClassLexicalEnvironment?.facts) { + // capture the lexical environment for the member + setOriginalNode(transformed, property); + addEmitFlags(transformed, EmitFlags.AdviseOnEmitNode); + classLexicalEnvironmentMap.set(getOriginalNodeId(transformed), currentClassLexicalEnvironment); + } + currentStaticPropertyDeclarationOrStaticBlock = savedCurrentStaticPropertyDeclarationOrStaticBlock; + return transformed; + } - if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifier(propertyName)) { - const privateIdentifierInfo = accessPrivateIdentifier(propertyName); - if (privateIdentifierInfo) { - if (privateIdentifierInfo.kind === PrivateIdentifierKind.Field) { - if (!privateIdentifierInfo.isStatic) { - return createPrivateInstanceFieldInitializer( - receiver, - visitNode(property.initializer, visitor, isExpression), - privateIdentifierInfo.brandCheckIdentifier - ); - } - else { - return createPrivateStaticFieldInitializer( - privateIdentifierInfo.variableName, - visitNode(property.initializer, visitor, isExpression) - ); - } + function transformPropertyWorker(property: PropertyDeclaration, receiver: LeftHandSideExpression) { + // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name) + const emitAssignment = !useDefineForClassFields; + const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression) + ? factory.updateComputedPropertyName(property.name, factory.getGeneratedNameForNode(property.name)) + : property.name; + + if (hasStaticModifier(property)) { + currentStaticPropertyDeclarationOrStaticBlock = property; + } + + if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifier(propertyName)) { + const privateIdentifierInfo = accessPrivateIdentifier(propertyName); + if (privateIdentifierInfo) { + if (privateIdentifierInfo.kind === PrivateIdentifierKind.Field) { + if (!privateIdentifierInfo.isStatic) { + return createPrivateInstanceFieldInitializer( + receiver, + visitNode(property.initializer, visitor, isExpression), + privateIdentifierInfo.brandCheckIdentifier + ); } else { - return undefined; + return createPrivateStaticFieldInitializer( + privateIdentifierInfo.variableName, + visitNode(property.initializer, visitor, isExpression) + ); } } else { - Debug.fail("Undeclared private name for property declaration."); + return undefined; } } - if ((isPrivateIdentifier(propertyName) || hasStaticModifier(property)) && !property.initializer) { - return undefined; + else { + Debug.fail("Undeclared private name for property declaration."); } + } + if ((isPrivateIdentifier(propertyName) || hasStaticModifier(property)) && !property.initializer) { + return undefined; + } - const propertyOriginalNode = getOriginalNode(property); - if (hasSyntacticModifier(propertyOriginalNode, ModifierFlags.Abstract)) { - return undefined; - } + const propertyOriginalNode = getOriginalNode(property); + if (hasSyntacticModifier(propertyOriginalNode, ModifierFlags.Abstract)) { + return undefined; + } - const initializer = property.initializer || emitAssignment ? visitNode(property.initializer, visitor, isExpression) ?? factory.createVoidZero() - : isParameterPropertyDeclaration(propertyOriginalNode, propertyOriginalNode.parent) && isIdentifier(propertyName) ? propertyName - : factory.createVoidZero(); + const initializer = property.initializer || emitAssignment ? visitNode(property.initializer, visitor, isExpression) ?? factory.createVoidZero() + : isParameterPropertyDeclaration(propertyOriginalNode, propertyOriginalNode.parent) && isIdentifier(propertyName) ? propertyName + : factory.createVoidZero(); - if (emitAssignment || isPrivateIdentifier(propertyName)) { - const memberAccess = createMemberAccessForPropertyName(factory, receiver, propertyName, /*location*/ propertyName); - return factory.createAssignment(memberAccess, initializer); - } - else { - const name = isComputedPropertyName(propertyName) ? propertyName.expression - : isIdentifier(propertyName) ? factory.createStringLiteral(unescapeLeadingUnderscores(propertyName.escapedText)) - : propertyName; - const descriptor = factory.createPropertyDescriptor({ value: initializer, configurable: true, writable: true, enumerable: true }); - return factory.createObjectDefinePropertyCall(receiver, name, descriptor); - } + if (emitAssignment || isPrivateIdentifier(propertyName)) { + const memberAccess = createMemberAccessForPropertyName(factory, receiver, propertyName, /*location*/ propertyName); + return factory.createAssignment(memberAccess, initializer); } + else { + const name = isComputedPropertyName(propertyName) ? propertyName.expression + : isIdentifier(propertyName) ? factory.createStringLiteral(unescapeLeadingUnderscores(propertyName.escapedText)) + : propertyName; + const descriptor = factory.createPropertyDescriptor({ value: initializer, configurable: true, writable: true, enumerable: true }); + return factory.createObjectDefinePropertyCall(receiver, name, descriptor); + } + } - function enableSubstitutionForClassAliases() { - if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) === 0) { - enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassAliases; + function enableSubstitutionForClassAliases() { + if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) === 0) { + enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassAliases; - // We need to enable substitutions for identifiers. This allows us to - // substitute class names inside of a class declaration. - context.enableSubstitution(SyntaxKind.Identifier); + // We need to enable substitutions for identifiers. This allows us to + // substitute class names inside of a class declaration. + context.enableSubstitution(SyntaxKind.Identifier); - // Keep track of class aliases. - classAliases = []; - } + // Keep track of class aliases. + classAliases = []; } + } - function enableSubstitutionForClassStaticThisOrSuperReference() { - if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassStaticThisOrSuperReference) === 0) { - enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassStaticThisOrSuperReference; + function enableSubstitutionForClassStaticThisOrSuperReference() { + if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassStaticThisOrSuperReference) === 0) { + enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassStaticThisOrSuperReference; - // substitute `this` in a static field initializer - context.enableSubstitution(SyntaxKind.ThisKeyword); + // substitute `this` in a static field initializer + context.enableSubstitution(SyntaxKind.ThisKeyword); - // these push a new lexical environment that is not the class lexical environment - context.enableEmitNotification(SyntaxKind.FunctionDeclaration); - context.enableEmitNotification(SyntaxKind.FunctionExpression); - context.enableEmitNotification(SyntaxKind.Constructor); + // these push a new lexical environment that is not the class lexical environment + context.enableEmitNotification(SyntaxKind.FunctionDeclaration); + context.enableEmitNotification(SyntaxKind.FunctionExpression); + context.enableEmitNotification(SyntaxKind.Constructor); - // these push a new lexical environment that is not the class lexical environment, except - // when they have a computed property name - context.enableEmitNotification(SyntaxKind.GetAccessor); - context.enableEmitNotification(SyntaxKind.SetAccessor); - context.enableEmitNotification(SyntaxKind.MethodDeclaration); - context.enableEmitNotification(SyntaxKind.PropertyDeclaration); + // these push a new lexical environment that is not the class lexical environment, except + // when they have a computed property name + context.enableEmitNotification(SyntaxKind.GetAccessor); + context.enableEmitNotification(SyntaxKind.SetAccessor); + context.enableEmitNotification(SyntaxKind.MethodDeclaration); + context.enableEmitNotification(SyntaxKind.PropertyDeclaration); - // class lexical environments are restored when entering a computed property name - context.enableEmitNotification(SyntaxKind.ComputedPropertyName); - } + // class lexical environments are restored when entering a computed property name + context.enableEmitNotification(SyntaxKind.ComputedPropertyName); + } + } + + /** + * Generates brand-check initializer for private methods. + * + * @param statements Statement list that should be used to append new statements. + * @param methods An array of method declarations. + * @param receiver The receiver on which each method should be assigned. + */ + function addMethodStatements(statements: Statement[], methods: readonly (MethodDeclaration | AccessorDeclaration)[], receiver: LeftHandSideExpression) { + if (!shouldTransformPrivateElementsOrClassStaticBlocks || !some(methods)) { + return; } - /** - * Generates brand-check initializer for private methods. - * - * @param statements Statement list that should be used to append new statements. - * @param methods An array of method declarations. - * @param receiver The receiver on which each method should be assigned. - */ - function addMethodStatements(statements: Statement[], methods: readonly (MethodDeclaration | AccessorDeclaration)[], receiver: LeftHandSideExpression) { - if (!shouldTransformPrivateElementsOrClassStaticBlocks || !some(methods)) { + const { weakSetName } = getPrivateIdentifierEnvironment(); + Debug.assert(weakSetName, "weakSetName should be set in private identifier environment"); + statements.push( + factory.createExpressionStatement( + createPrivateInstanceMethodInitializer(receiver, weakSetName) + ) + ); + } + + function visitInvalidSuperProperty(node: SuperProperty) { + return isPropertyAccessExpression(node) ? + factory.updatePropertyAccessExpression( + node, + factory.createVoidZero(), + node.name) : + factory.updateElementAccessExpression( + node, + factory.createVoidZero(), + visitNode(node.argumentExpression, visitor, isExpression)); + } + + function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { + const original = getOriginalNode(node); + if (original.id) { + const classLexicalEnvironment = classLexicalEnvironmentMap.get(original.id); + if (classLexicalEnvironment) { + const savedClassLexicalEnvironment = currentClassLexicalEnvironment; + const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; + currentClassLexicalEnvironment = classLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = classLexicalEnvironment; + previousOnEmitNode(hint, node, emitCallback); + currentClassLexicalEnvironment = savedClassLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; return; } - - const { weakSetName } = getPrivateIdentifierEnvironment(); - Debug.assert(weakSetName, "weakSetName should be set in private identifier environment"); - statements.push( - factory.createExpressionStatement( - createPrivateInstanceMethodInitializer(receiver, weakSetName) - ) - ); } - function visitInvalidSuperProperty(node: SuperProperty) { - return isPropertyAccessExpression(node) ? - factory.updatePropertyAccessExpression( - node, - factory.createVoidZero(), - node.name) : - factory.updateElementAccessExpression( - node, - factory.createVoidZero(), - visitNode(node.argumentExpression, visitor, isExpression)); - } - - function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { - const original = getOriginalNode(node); - if (original.id) { - const classLexicalEnvironment = classLexicalEnvironmentMap.get(original.id); - if (classLexicalEnvironment) { - const savedClassLexicalEnvironment = currentClassLexicalEnvironment; - const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; - currentClassLexicalEnvironment = classLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = classLexicalEnvironment; - previousOnEmitNode(hint, node, emitCallback); - currentClassLexicalEnvironment = savedClassLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; - return; + switch (node.kind) { + case SyntaxKind.FunctionExpression: + if (isArrowFunction(original) || getEmitFlags(node) & EmitFlags.AsyncFunctionBody) { + break; } - } - - switch (node.kind) { - case SyntaxKind.FunctionExpression: - if (isArrowFunction(original) || getEmitFlags(node) & EmitFlags.AsyncFunctionBody) { - break; - } - // falls through - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.Constructor: { - const savedClassLexicalEnvironment = currentClassLexicalEnvironment; - const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; - currentClassLexicalEnvironment = undefined; - currentComputedPropertyNameClassLexicalEnvironment = undefined; - previousOnEmitNode(hint, node, emitCallback); - currentClassLexicalEnvironment = savedClassLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; - return; - } + // falls through + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.Constructor: { + const savedClassLexicalEnvironment = currentClassLexicalEnvironment; + const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; + currentClassLexicalEnvironment = undefined; + currentComputedPropertyNameClassLexicalEnvironment = undefined; + previousOnEmitNode(hint, node, emitCallback); + currentClassLexicalEnvironment = savedClassLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; + return; + } - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.PropertyDeclaration: { - const savedClassLexicalEnvironment = currentClassLexicalEnvironment; - const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = currentClassLexicalEnvironment; - currentClassLexicalEnvironment = undefined; - previousOnEmitNode(hint, node, emitCallback); - currentClassLexicalEnvironment = savedClassLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; - return; - } - case SyntaxKind.ComputedPropertyName: { - const savedClassLexicalEnvironment = currentClassLexicalEnvironment; - const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; - currentClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = undefined; - previousOnEmitNode(hint, node, emitCallback); - currentClassLexicalEnvironment = savedClassLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; - return; - } + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.PropertyDeclaration: { + const savedClassLexicalEnvironment = currentClassLexicalEnvironment; + const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = currentClassLexicalEnvironment; + currentClassLexicalEnvironment = undefined; + previousOnEmitNode(hint, node, emitCallback); + currentClassLexicalEnvironment = savedClassLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; + return; } - previousOnEmitNode(hint, node, emitCallback); - } - - /** - * Hooks node substitutions. - * - * @param hint The context for the emitter. - * @param node The node to substitute. - */ - function onSubstituteNode(hint: EmitHint, node: Node) { - node = previousOnSubstituteNode(hint, node); - if (hint === EmitHint.Expression) { - return substituteExpression(node as Expression); + case SyntaxKind.ComputedPropertyName: { + const savedClassLexicalEnvironment = currentClassLexicalEnvironment; + const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; + currentClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = undefined; + previousOnEmitNode(hint, node, emitCallback); + currentClassLexicalEnvironment = savedClassLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; + return; } - return node; } + previousOnEmitNode(hint, node, emitCallback); + } - function substituteExpression(node: Expression) { - switch (node.kind) { - case SyntaxKind.Identifier: - return substituteExpressionIdentifier(node as Identifier); - case SyntaxKind.ThisKeyword: - return substituteThisExpression(node as ThisExpression); - } - return node; + /** + * Hooks node substitutions. + * + * @param hint The context for the emitter. + * @param node The node to substitute. + */ + function onSubstituteNode(hint: EmitHint, node: Node) { + node = previousOnSubstituteNode(hint, node); + if (hint === EmitHint.Expression) { + return substituteExpression(node as Expression); } + return node; + } - function substituteThisExpression(node: ThisExpression) { - if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassStaticThisOrSuperReference && currentClassLexicalEnvironment) { - const { facts, classConstructor } = currentClassLexicalEnvironment; - if (facts & ClassFacts.ClassWasDecorated) { - return factory.createParenthesizedExpression(factory.createVoidZero()); - } - if (classConstructor) { - return setTextRange( - setOriginalNode( - factory.cloneNode(classConstructor), - node, - ), - node - ); - } + function substituteExpression(node: Expression) { + switch (node.kind) { + case SyntaxKind.Identifier: + return substituteExpressionIdentifier(node as Identifier); + case SyntaxKind.ThisKeyword: + return substituteThisExpression(node as ThisExpression); + } + return node; + } + + function substituteThisExpression(node: ThisExpression) { + if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassStaticThisOrSuperReference && currentClassLexicalEnvironment) { + const { facts, classConstructor } = currentClassLexicalEnvironment; + if (facts & ClassFacts.ClassWasDecorated) { + return factory.createParenthesizedExpression(factory.createVoidZero()); + } + if (classConstructor) { + return setTextRange( + setOriginalNode( + factory.cloneNode(classConstructor), + node, + ), + node + ); } - return node; } + return node; + } - function substituteExpressionIdentifier(node: Identifier): Expression { - return trySubstituteClassAlias(node) || node; - } - - function trySubstituteClassAlias(node: Identifier): Expression | undefined { - if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) { - if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) { - // Due to the emit for class decorators, any reference to the class from inside of the class body - // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind - // behavior of class names in ES6. - // Also, when emitting statics for class expressions, we must substitute a class alias for - // constructor references in static property initializers. - const declaration = resolver.getReferencedValueDeclaration(node); - if (declaration) { - const classAlias = classAliases[declaration.id!]; // TODO: GH#18217 - if (classAlias) { - const clone = factory.cloneNode(classAlias); - setSourceMapRange(clone, node); - setCommentRange(clone, node); - return clone; - } + function substituteExpressionIdentifier(node: Identifier): Expression { + return trySubstituteClassAlias(node) || node; + } + + function trySubstituteClassAlias(node: Identifier): Expression | undefined { + if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) { + if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) { + // Due to the emit for class decorators, any reference to the class from inside of the class body + // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind + // behavior of class names in ES6. + // Also, when emitting statics for class expressions, we must substitute a class alias for + // constructor references in static property initializers. + const declaration = resolver.getReferencedValueDeclaration(node); + if (declaration) { + const classAlias = classAliases[declaration.id!]; // TODO: GH#18217 + if (classAlias) { + const clone = factory.cloneNode(classAlias); + setSourceMapRange(clone, node); + setCommentRange(clone, node); + return clone; } } } - - return undefined; } - /** - * If the name is a computed property, this function transforms it, then either returns an expression which caches the - * value of the result or the expression itself if the value is either unused or safe to inline into multiple locations - * @param shouldHoist Does the expression need to be reused? (ie, for an initializer or a decorator) - */ - function getPropertyNameExpressionIfNeeded(name: PropertyName, shouldHoist: boolean): Expression | undefined { - if (isComputedPropertyName(name)) { - const expression = visitNode(name.expression, visitor, isExpression); - const innerExpression = skipPartiallyEmittedExpressions(expression); - const inlinable = isSimpleInlineableExpression(innerExpression); - const alreadyTransformed = isAssignmentExpression(innerExpression) && isGeneratedIdentifier(innerExpression.left); - if (!alreadyTransformed && !inlinable && shouldHoist) { - const generatedName = factory.getGeneratedNameForNode(name); - if (resolver.getNodeCheckFlags(name) & NodeCheckFlags.BlockScopedBindingInLoop) { - addBlockScopedVariable(generatedName); - } - else { - hoistVariableDeclaration(generatedName); - } - return factory.createAssignment(generatedName, expression); + return undefined; + } + + /** + * If the name is a computed property, this function transforms it, then either returns an expression which caches the + * value of the result or the expression itself if the value is either unused or safe to inline into multiple locations + * @param shouldHoist Does the expression need to be reused? (ie, for an initializer or a decorator) + */ + function getPropertyNameExpressionIfNeeded(name: PropertyName, shouldHoist: boolean): Expression | undefined { + if (isComputedPropertyName(name)) { + const expression = visitNode(name.expression, visitor, isExpression); + const innerExpression = skipPartiallyEmittedExpressions(expression); + const inlinable = isSimpleInlineableExpression(innerExpression); + const alreadyTransformed = isAssignmentExpression(innerExpression) && isGeneratedIdentifier(innerExpression.left); + if (!alreadyTransformed && !inlinable && shouldHoist) { + const generatedName = factory.getGeneratedNameForNode(name); + if (resolver.getNodeCheckFlags(name) & NodeCheckFlags.BlockScopedBindingInLoop) { + addBlockScopedVariable(generatedName); + } + else { + hoistVariableDeclaration(generatedName); } - return (inlinable || isIdentifier(innerExpression)) ? undefined : expression; + return factory.createAssignment(generatedName, expression); } + return (inlinable || isIdentifier(innerExpression)) ? undefined : expression; } + } - function startClassLexicalEnvironment() { - classLexicalEnvironmentStack.push(currentClassLexicalEnvironment); - currentClassLexicalEnvironment = undefined; - } + function startClassLexicalEnvironment() { + classLexicalEnvironmentStack.push(currentClassLexicalEnvironment); + currentClassLexicalEnvironment = undefined; + } - function endClassLexicalEnvironment() { - currentClassLexicalEnvironment = classLexicalEnvironmentStack.pop(); - } + function endClassLexicalEnvironment() { + currentClassLexicalEnvironment = classLexicalEnvironmentStack.pop(); + } - function getClassLexicalEnvironment() { - return currentClassLexicalEnvironment ||= { - facts: ClassFacts.None, - classConstructor: undefined, - superClassReference: undefined, - privateIdentifierEnvironment: undefined, - }; - } + function getClassLexicalEnvironment() { + return currentClassLexicalEnvironment ||= { + facts: ClassFacts.None, + classConstructor: undefined, + superClassReference: undefined, + privateIdentifierEnvironment: undefined, + }; + } - function getPrivateIdentifierEnvironment() { - const lex = getClassLexicalEnvironment(); - lex.privateIdentifierEnvironment ||= { - className: "", - identifiers: new Map() - }; - return lex.privateIdentifierEnvironment; - } + function getPrivateIdentifierEnvironment() { + const lex = getClassLexicalEnvironment(); + lex.privateIdentifierEnvironment ||= { + className: "", + identifiers: new Map() + }; + return lex.privateIdentifierEnvironment; + } - function getPendingExpressions() { - return pendingExpressions || (pendingExpressions = []); - } + function getPendingExpressions() { + return pendingExpressions || (pendingExpressions = []); + } - function addPrivateIdentifierToEnvironment(node: PrivateClassElementDeclaration) { - const text = getTextOfPropertyName(node.name) as string; - const lex = getClassLexicalEnvironment(); - const { classConstructor } = lex; + function addPrivateIdentifierToEnvironment(node: PrivateClassElementDeclaration) { + const text = getTextOfPropertyName(node.name) as string; + const lex = getClassLexicalEnvironment(); + const { classConstructor } = lex; - const privateEnv = getPrivateIdentifierEnvironment(); - const { weakSetName } = privateEnv; + const privateEnv = getPrivateIdentifierEnvironment(); + const { weakSetName } = privateEnv; - const assignmentExpressions: Expression[] = []; + const assignmentExpressions: Expression[] = []; - const privateName = node.name.escapedText; - const previousInfo = privateEnv.identifiers.get(privateName); - const isValid = !isReservedPrivateName(node.name) && previousInfo === undefined; + const privateName = node.name.escapedText; + const previousInfo = privateEnv.identifiers.get(privateName); + const isValid = !isReservedPrivateName(node.name) && previousInfo === undefined; - if (hasStaticModifier(node)) { - Debug.assert(classConstructor, "weakSetName should be set in private identifier environment"); - if (isPropertyDeclaration(node)) { - const variableName = createHoistedVariableForPrivateName(text, node); + if (hasStaticModifier(node)) { + Debug.assert(classConstructor, "weakSetName should be set in private identifier environment"); + if (isPropertyDeclaration(node)) { + const variableName = createHoistedVariableForPrivateName(text, node); + privateEnv.identifiers.set(privateName, { + kind: PrivateIdentifierKind.Field, + variableName, + brandCheckIdentifier: classConstructor, + isStatic: true, + isValid, + }); + } + else if (isMethodDeclaration(node)) { + const functionName = createHoistedVariableForPrivateName(text, node); + privateEnv.identifiers.set(privateName, { + kind: PrivateIdentifierKind.Method, + methodName: functionName, + brandCheckIdentifier: classConstructor, + isStatic: true, + isValid, + }); + } + else if (isGetAccessorDeclaration(node)) { + const getterName = createHoistedVariableForPrivateName(text + "_get", node); + if (previousInfo?.kind === PrivateIdentifierKind.Accessor && previousInfo.isStatic && !previousInfo.getterName) { + previousInfo.getterName = getterName; + } + else { privateEnv.identifiers.set(privateName, { - kind: PrivateIdentifierKind.Field, - variableName, + kind: PrivateIdentifierKind.Accessor, + getterName, + setterName: undefined, brandCheckIdentifier: classConstructor, isStatic: true, isValid, }); } - else if (isMethodDeclaration(node)) { - const functionName = createHoistedVariableForPrivateName(text, node); + } + else if (isSetAccessorDeclaration(node)) { + const setterName = createHoistedVariableForPrivateName(text + "_set", node); + if (previousInfo?.kind === PrivateIdentifierKind.Accessor && previousInfo.isStatic && !previousInfo.setterName) { + previousInfo.setterName = setterName; + } + else { privateEnv.identifiers.set(privateName, { - kind: PrivateIdentifierKind.Method, - methodName: functionName, + kind: PrivateIdentifierKind.Accessor, + getterName: undefined, + setterName, brandCheckIdentifier: classConstructor, isStatic: true, isValid, }); } - else if (isGetAccessorDeclaration(node)) { - const getterName = createHoistedVariableForPrivateName(text + "_get", node); - if (previousInfo?.kind === PrivateIdentifierKind.Accessor && previousInfo.isStatic && !previousInfo.getterName) { - previousInfo.getterName = getterName; - } - else { - privateEnv.identifiers.set(privateName, { - kind: PrivateIdentifierKind.Accessor, - getterName, - setterName: undefined, - brandCheckIdentifier: classConstructor, - isStatic: true, - isValid, - }); - } - } - else if (isSetAccessorDeclaration(node)) { - const setterName = createHoistedVariableForPrivateName(text + "_set", node); - if (previousInfo?.kind === PrivateIdentifierKind.Accessor && previousInfo.isStatic && !previousInfo.setterName) { - previousInfo.setterName = setterName; - } - else { - privateEnv.identifiers.set(privateName, { - kind: PrivateIdentifierKind.Accessor, - getterName: undefined, - setterName, - brandCheckIdentifier: classConstructor, - isStatic: true, - isValid, - }); - } - } - else { - Debug.assertNever(node, "Unknown class element type."); - } } - else if (isPropertyDeclaration(node)) { - const weakMapName = createHoistedVariableForPrivateName(text, node); - privateEnv.identifiers.set(privateName, { - kind: PrivateIdentifierKind.Field, - brandCheckIdentifier: weakMapName, - isStatic: false, - variableName: undefined, - isValid, - }); - - assignmentExpressions.push(factory.createAssignment( - weakMapName, - factory.createNewExpression( - factory.createIdentifier("WeakMap"), - /*typeArguments*/ undefined, - [] - ) - )); + else { + Debug.assertNever(node, "Unknown class element type."); } - else if (isMethodDeclaration(node)) { - Debug.assert(weakSetName, "weakSetName should be set in private identifier environment"); + } + else if (isPropertyDeclaration(node)) { + const weakMapName = createHoistedVariableForPrivateName(text, node); + privateEnv.identifiers.set(privateName, { + kind: PrivateIdentifierKind.Field, + brandCheckIdentifier: weakMapName, + isStatic: false, + variableName: undefined, + isValid, + }); + + assignmentExpressions.push(factory.createAssignment( + weakMapName, + factory.createNewExpression( + factory.createIdentifier("WeakMap"), + /*typeArguments*/ undefined, + [] + ) + )); + } + else if (isMethodDeclaration(node)) { + Debug.assert(weakSetName, "weakSetName should be set in private identifier environment"); - privateEnv.identifiers.set(privateName, { - kind: PrivateIdentifierKind.Method, - methodName: createHoistedVariableForPrivateName(text, node), - brandCheckIdentifier: weakSetName, - isStatic: false, - isValid, - }); - } - else if (isAccessor(node)) { - Debug.assert(weakSetName, "weakSetName should be set in private identifier environment"); + privateEnv.identifiers.set(privateName, { + kind: PrivateIdentifierKind.Method, + methodName: createHoistedVariableForPrivateName(text, node), + brandCheckIdentifier: weakSetName, + isStatic: false, + isValid, + }); + } + else if (isAccessor(node)) { + Debug.assert(weakSetName, "weakSetName should be set in private identifier environment"); - if (isGetAccessor(node)) { - const getterName = createHoistedVariableForPrivateName(text + "_get", node); + if (isGetAccessor(node)) { + const getterName = createHoistedVariableForPrivateName(text + "_get", node); - if (previousInfo?.kind === PrivateIdentifierKind.Accessor && !previousInfo.isStatic && !previousInfo.getterName) { - previousInfo.getterName = getterName; - } - else { - privateEnv.identifiers.set(privateName, { - kind: PrivateIdentifierKind.Accessor, - getterName, - setterName: undefined, - brandCheckIdentifier: weakSetName, - isStatic: false, - isValid, - }); - } + if (previousInfo?.kind === PrivateIdentifierKind.Accessor && !previousInfo.isStatic && !previousInfo.getterName) { + previousInfo.getterName = getterName; } else { - const setterName = createHoistedVariableForPrivateName(text + "_set", node); - - if (previousInfo?.kind === PrivateIdentifierKind.Accessor && !previousInfo.isStatic && !previousInfo.setterName) { - previousInfo.setterName = setterName; - } - else { - privateEnv.identifiers.set(privateName, { - kind: PrivateIdentifierKind.Accessor, - getterName: undefined, - setterName, - brandCheckIdentifier: weakSetName, - isStatic: false, - isValid, - }); - } + privateEnv.identifiers.set(privateName, { + kind: PrivateIdentifierKind.Accessor, + getterName, + setterName: undefined, + brandCheckIdentifier: weakSetName, + isStatic: false, + isValid, + }); } } else { - Debug.assertNever(node, "Unknown class element type."); + const setterName = createHoistedVariableForPrivateName(text + "_set", node); + + if (previousInfo?.kind === PrivateIdentifierKind.Accessor && !previousInfo.isStatic && !previousInfo.setterName) { + previousInfo.setterName = setterName; + } + else { + privateEnv.identifiers.set(privateName, { + kind: PrivateIdentifierKind.Accessor, + getterName: undefined, + setterName, + brandCheckIdentifier: weakSetName, + isStatic: false, + isValid, + }); + } } + } + else { + Debug.assertNever(node, "Unknown class element type."); + } + + getPendingExpressions().push(...assignmentExpressions); + } - getPendingExpressions().push(...assignmentExpressions); + function createHoistedVariableForClass(name: string, node: PrivateIdentifier | ClassStaticBlockDeclaration): Identifier { + const { className } = getPrivateIdentifierEnvironment(); + const prefix = className ? `_${className}` : ""; + const identifier = factory.createUniqueName(`${prefix}_${name}`, GeneratedIdentifierFlags.Optimistic); + + if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.BlockScopedBindingInLoop) { + addBlockScopedVariable(identifier); + } + else { + hoistVariableDeclaration(identifier); } - function createHoistedVariableForClass(name: string, node: PrivateIdentifier | ClassStaticBlockDeclaration): Identifier { - const { className } = getPrivateIdentifierEnvironment(); - const prefix = className ? `_${className}` : ""; - const identifier = factory.createUniqueName(`${prefix}_${name}`, GeneratedIdentifierFlags.Optimistic); + return identifier; + } + + function createHoistedVariableForPrivateName(privateName: string, node: PrivateClassElementDeclaration): Identifier { + return createHoistedVariableForClass(privateName.substring(1), node.name); + } - if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.BlockScopedBindingInLoop) { - addBlockScopedVariable(identifier); + function accessPrivateIdentifier(name: PrivateIdentifier) { + if (currentClassLexicalEnvironment?.privateIdentifierEnvironment) { + const info = currentClassLexicalEnvironment.privateIdentifierEnvironment.identifiers.get(name.escapedText); + if (info) { + return info; } - else { - hoistVariableDeclaration(identifier); + } + for (let i = classLexicalEnvironmentStack.length - 1; i >= 0; --i) { + const env = classLexicalEnvironmentStack[i]; + if (!env) { + continue; + } + const info = env.privateIdentifierEnvironment?.identifiers.get(name.escapedText); + if (info) { + return info; } - - return identifier; } + return undefined; + } - function createHoistedVariableForPrivateName(privateName: string, node: PrivateClassElementDeclaration): Identifier { - return createHoistedVariableForClass(privateName.substring(1), node.name); + function wrapPrivateIdentifierForDestructuringTarget(node: PrivateIdentifierPropertyAccessExpression) { + const parameter = factory.getGeneratedNameForNode(node); + const info = accessPrivateIdentifier(node.name); + if (!info) { + return visitEachChild(node, visitor, context); } + let receiver = node.expression; + // We cannot copy `this` or `super` into the function because they will be bound + // differently inside the function. + if (isThisProperty(node) || isSuperProperty(node) || !isSimpleCopiableExpression(node.expression)) { + receiver = factory.createTempVariable(hoistVariableDeclaration, /*reservedInNestedScopes*/ true); + getPendingExpressions().push(factory.createBinaryExpression(receiver, SyntaxKind.EqualsToken, visitNode(node.expression, visitor, isExpression))); + } + return factory.createAssignmentTargetWrapper( + parameter, + createPrivateIdentifierAssignment( + info, + receiver, + parameter, + SyntaxKind.EqualsToken + ) + ); + } - function accessPrivateIdentifier(name: PrivateIdentifier) { - if (currentClassLexicalEnvironment?.privateIdentifierEnvironment) { - const info = currentClassLexicalEnvironment.privateIdentifierEnvironment.identifiers.get(name.escapedText); - if (info) { - return info; - } + function visitArrayAssignmentTarget(node: BindingOrAssignmentElement) { + const target = getTargetOfBindingOrAssignmentElement(node); + if (target) { + let wrapped: LeftHandSideExpression | undefined; + if (isPrivateIdentifierPropertyAccessExpression(target)) { + wrapped = wrapPrivateIdentifierForDestructuringTarget(target); } - for (let i = classLexicalEnvironmentStack.length - 1; i >= 0; --i) { - const env = classLexicalEnvironmentStack[i]; - if (!env) { - continue; + else if (shouldTransformSuperInStaticInitializers && + isSuperProperty(target) && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment) { + const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; + if (facts & ClassFacts.ClassWasDecorated) { + wrapped = visitInvalidSuperProperty(target); } - const info = env.privateIdentifierEnvironment?.identifiers.get(name.escapedText); - if (info) { - return info; + else if (classConstructor && superClassReference) { + const name = + isElementAccessExpression(target) ? visitNode(target.argumentExpression, visitor, isExpression) : + isIdentifier(target.name) ? factory.createStringLiteralFromNode(target.name) : + undefined; + if (name) { + const temp = factory.createTempVariable(/*recordTempVariable*/ undefined); + wrapped = factory.createAssignmentTargetWrapper( + temp, + factory.createReflectSetCall( + superClassReference, + name, + temp, + classConstructor, + ) + ); + } } } - return undefined; - } - - function wrapPrivateIdentifierForDestructuringTarget(node: PrivateIdentifierPropertyAccessExpression) { - const parameter = factory.getGeneratedNameForNode(node); - const info = accessPrivateIdentifier(node.name); - if (!info) { - return visitEachChild(node, visitor, context); - } - let receiver = node.expression; - // We cannot copy `this` or `super` into the function because they will be bound - // differently inside the function. - if (isThisProperty(node) || isSuperProperty(node) || !isSimpleCopiableExpression(node.expression)) { - receiver = factory.createTempVariable(hoistVariableDeclaration, /*reservedInNestedScopes*/ true); - getPendingExpressions().push(factory.createBinaryExpression(receiver, SyntaxKind.EqualsToken, visitNode(node.expression, visitor, isExpression))); + if (wrapped) { + if (isAssignmentExpression(node)) { + return factory.updateBinaryExpression( + node, + wrapped, + node.operatorToken, + visitNode(node.right, visitor, isExpression) + ); + } + else if (isSpreadElement(node)) { + return factory.updateSpreadElement(node, wrapped); + } + else { + return wrapped; + } } - return factory.createAssignmentTargetWrapper( - parameter, - createPrivateIdentifierAssignment( - info, - receiver, - parameter, - SyntaxKind.EqualsToken - ) - ); } + return visitNode(node, visitorDestructuringTarget); + } - function visitArrayAssignmentTarget(node: BindingOrAssignmentElement) { + function visitObjectAssignmentTarget(node: ObjectLiteralElementLike) { + if (isObjectBindingOrAssignmentElement(node) && !isShorthandPropertyAssignment(node)) { const target = getTargetOfBindingOrAssignmentElement(node); + let wrapped: LeftHandSideExpression | undefined; if (target) { - let wrapped: LeftHandSideExpression | undefined; if (isPrivateIdentifierPropertyAccessExpression(target)) { wrapped = wrapPrivateIdentifierForDestructuringTarget(target); } @@ -1947,141 +2002,86 @@ namespace ts { } } } - if (wrapped) { - if (isAssignmentExpression(node)) { - return factory.updateBinaryExpression( - node, - wrapped, - node.operatorToken, - visitNode(node.right, visitor, isExpression) - ); - } - else if (isSpreadElement(node)) { - return factory.updateSpreadElement(node, wrapped); - } - else { - return wrapped; - } - } - } - return visitNode(node, visitorDestructuringTarget); - } - - function visitObjectAssignmentTarget(node: ObjectLiteralElementLike) { - if (isObjectBindingOrAssignmentElement(node) && !isShorthandPropertyAssignment(node)) { - const target = getTargetOfBindingOrAssignmentElement(node); - let wrapped: LeftHandSideExpression | undefined; - if (target) { - if (isPrivateIdentifierPropertyAccessExpression(target)) { - wrapped = wrapPrivateIdentifierForDestructuringTarget(target); - } - else if (shouldTransformSuperInStaticInitializers && - isSuperProperty(target) && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment) { - const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; - if (facts & ClassFacts.ClassWasDecorated) { - wrapped = visitInvalidSuperProperty(target); - } - else if (classConstructor && superClassReference) { - const name = - isElementAccessExpression(target) ? visitNode(target.argumentExpression, visitor, isExpression) : - isIdentifier(target.name) ? factory.createStringLiteralFromNode(target.name) : - undefined; - if (name) { - const temp = factory.createTempVariable(/*recordTempVariable*/ undefined); - wrapped = factory.createAssignmentTargetWrapper( - temp, - factory.createReflectSetCall( - superClassReference, - name, - temp, - classConstructor, - ) - ); - } - } - } - } - if (isPropertyAssignment(node)) { - const initializer = getInitializerOfBindingOrAssignmentElement(node); - return factory.updatePropertyAssignment( - node, - visitNode(node.name, visitor, isPropertyName), - wrapped ? - initializer ? factory.createAssignment(wrapped, visitNode(initializer, visitor)) : wrapped : - visitNode(node.initializer, visitorDestructuringTarget, isExpression) - ); - } - if (isSpreadAssignment(node)) { - return factory.updateSpreadAssignment( - node, - wrapped || visitNode(node.expression, visitorDestructuringTarget, isExpression) - ); - } - Debug.assert(wrapped === undefined, "Should not have generated a wrapped target"); } - return visitNode(node, visitor); - } - - function visitAssignmentPattern(node: AssignmentPattern) { - if (isArrayLiteralExpression(node)) { - // Transforms private names in destructuring assignment array bindings. - // Transforms SuperProperty assignments in destructuring assignment array bindings in static initializers. - // - // Source: - // ([ this.#myProp ] = [ "hello" ]); - // - // Transformation: - // [ { set value(x) { this.#myProp = x; } }.value ] = [ "hello" ]; - return factory.updateArrayLiteralExpression( + if (isPropertyAssignment(node)) { + const initializer = getInitializerOfBindingOrAssignmentElement(node); + return factory.updatePropertyAssignment( node, - visitNodes(node.elements, visitArrayAssignmentTarget, isExpression) + visitNode(node.name, visitor, isPropertyName), + wrapped ? + initializer ? factory.createAssignment(wrapped, visitNode(initializer, visitor)) : wrapped : + visitNode(node.initializer, visitorDestructuringTarget, isExpression) ); } - else { - // Transforms private names in destructuring assignment object bindings. - // Transforms SuperProperty assignments in destructuring assignment object bindings in static initializers. - // - // Source: - // ({ stringProperty: this.#myProp } = { stringProperty: "hello" }); - // - // Transformation: - // ({ stringProperty: { set value(x) { this.#myProp = x; } }.value }) = { stringProperty: "hello" }; - return factory.updateObjectLiteralExpression( + if (isSpreadAssignment(node)) { + return factory.updateSpreadAssignment( node, - visitNodes(node.properties, visitObjectAssignmentTarget, isObjectLiteralElementLike) + wrapped || visitNode(node.expression, visitorDestructuringTarget, isExpression) ); } + Debug.assert(wrapped === undefined, "Should not have generated a wrapped target"); } + return visitNode(node, visitor); } - function createPrivateStaticFieldInitializer(variableName: Identifier, initializer: Expression | undefined) { - return factory.createAssignment( - variableName, - factory.createObjectLiteralExpression([ - factory.createPropertyAssignment("value", initializer || factory.createVoidZero()) - ]) - ); + function visitAssignmentPattern(node: AssignmentPattern) { + if (isArrayLiteralExpression(node)) { + // Transforms private names in destructuring assignment array bindings. + // Transforms SuperProperty assignments in destructuring assignment array bindings in static initializers. + // + // Source: + // ([ this.#myProp ] = [ "hello" ]); + // + // Transformation: + // [ { set value(x) { this.#myProp = x; } }.value ] = [ "hello" ]; + return factory.updateArrayLiteralExpression( + node, + visitNodes(node.elements, visitArrayAssignmentTarget, isExpression) + ); + } + else { + // Transforms private names in destructuring assignment object bindings. + // Transforms SuperProperty assignments in destructuring assignment object bindings in static initializers. + // + // Source: + // ({ stringProperty: this.#myProp } = { stringProperty: "hello" }); + // + // Transformation: + // ({ stringProperty: { set value(x) { this.#myProp = x; } }.value }) = { stringProperty: "hello" }; + return factory.updateObjectLiteralExpression( + node, + visitNodes(node.properties, visitObjectAssignmentTarget, isObjectLiteralElementLike) + ); + } } +} - function createPrivateInstanceFieldInitializer(receiver: LeftHandSideExpression, initializer: Expression | undefined, weakMapName: Identifier) { - return factory.createCallExpression( - factory.createPropertyAccessExpression(weakMapName, "set"), - /*typeArguments*/ undefined, - [receiver, initializer || factory.createVoidZero()] - ); - } +function createPrivateStaticFieldInitializer(variableName: Identifier, initializer: Expression | undefined) { + return factory.createAssignment( + variableName, + factory.createObjectLiteralExpression([ + factory.createPropertyAssignment("value", initializer || factory.createVoidZero()) + ]) + ); +} - function createPrivateInstanceMethodInitializer(receiver: LeftHandSideExpression, weakSetName: Identifier) { - return factory.createCallExpression( - factory.createPropertyAccessExpression(weakSetName, "add"), - /*typeArguments*/ undefined, - [receiver] - ); - } +function createPrivateInstanceFieldInitializer(receiver: LeftHandSideExpression, initializer: Expression | undefined, weakMapName: Identifier) { + return factory.createCallExpression( + factory.createPropertyAccessExpression(weakMapName, "set"), + /*typeArguments*/ undefined, + [receiver, initializer || factory.createVoidZero()] + ); +} - function isReservedPrivateName(node: PrivateIdentifier) { - return node.escapedText === "#constructor"; - } +function createPrivateInstanceMethodInitializer(receiver: LeftHandSideExpression, weakSetName: Identifier) { + return factory.createCallExpression( + factory.createPropertyAccessExpression(weakSetName, "add"), + /*typeArguments*/ undefined, + [receiver] + ); +} + +function isReservedPrivateName(node: PrivateIdentifier) { + return node.escapedText === "#constructor"; +} } diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 6cea3b85055ee..48478e3d2260b 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -1,1728 +1,1728 @@ /*@internal*/ namespace ts { - export function getDeclarationDiagnostics(host: EmitHost, resolver: EmitResolver, file: SourceFile | undefined): DiagnosticWithLocation[] | undefined { - const compilerOptions = host.getCompilerOptions(); - const result = transformNodes(resolver, host, factory, compilerOptions, file ? [file] : filter(host.getSourceFiles(), isSourceFileNotJson), [transformDeclarations], /*allowDtsFiles*/ false); - return result.diagnostics; - } +export function getDeclarationDiagnostics(host: EmitHost, resolver: EmitResolver, file: SourceFile | undefined): DiagnosticWithLocation[] | undefined { + const compilerOptions = host.getCompilerOptions(); + const result = transformNodes(resolver, host, factory, compilerOptions, file ? [file] : filter(host.getSourceFiles(), isSourceFileNotJson), [transformDeclarations], /*allowDtsFiles*/ false); + return result.diagnostics; +} - function hasInternalAnnotation(range: CommentRange, currentSourceFile: SourceFile) { - const comment = currentSourceFile.text.substring(range.pos, range.end); - return stringContains(comment, "@internal"); - } +function hasInternalAnnotation(range: CommentRange, currentSourceFile: SourceFile) { + const comment = currentSourceFile.text.substring(range.pos, range.end); + return stringContains(comment, "@internal"); +} - export function isInternalDeclaration(node: Node, currentSourceFile: SourceFile) { - const parseTreeNode = getParseTreeNode(node); - if (parseTreeNode && parseTreeNode.kind === SyntaxKind.Parameter) { - const paramIdx = (parseTreeNode.parent as SignatureDeclaration).parameters.indexOf(parseTreeNode as ParameterDeclaration); - const previousSibling = paramIdx > 0 ? (parseTreeNode.parent as SignatureDeclaration).parameters[paramIdx - 1] : undefined; - const text = currentSourceFile.text; - const commentRanges = previousSibling - ? concatenate( - // to handle - // ... parameters, /* @internal */ - // public param: string - getTrailingCommentRanges(text, skipTrivia(text, previousSibling.end + 1, /* stopAfterLineBreak */ false, /* stopAtComments */ true)), - getLeadingCommentRanges(text, node.pos) - ) - : getTrailingCommentRanges(text, skipTrivia(text, node.pos, /* stopAfterLineBreak */ false, /* stopAtComments */ true)); - return commentRanges && commentRanges.length && hasInternalAnnotation(last(commentRanges), currentSourceFile); - } - const leadingCommentRanges = parseTreeNode && getLeadingCommentRangesOfNode(parseTreeNode, currentSourceFile); - return !!forEach(leadingCommentRanges, range => { - return hasInternalAnnotation(range, currentSourceFile); - }); +export function isInternalDeclaration(node: Node, currentSourceFile: SourceFile) { + const parseTreeNode = getParseTreeNode(node); + if (parseTreeNode && parseTreeNode.kind === SyntaxKind.Parameter) { + const paramIdx = (parseTreeNode.parent as SignatureDeclaration).parameters.indexOf(parseTreeNode as ParameterDeclaration); + const previousSibling = paramIdx > 0 ? (parseTreeNode.parent as SignatureDeclaration).parameters[paramIdx - 1] : undefined; + const text = currentSourceFile.text; + const commentRanges = previousSibling + ? concatenate( + // to handle + // ... parameters, /* @internal */ + // public param: string + getTrailingCommentRanges(text, skipTrivia(text, previousSibling.end + 1, /* stopAfterLineBreak */ false, /* stopAtComments */ true)), + getLeadingCommentRanges(text, node.pos) + ) + : getTrailingCommentRanges(text, skipTrivia(text, node.pos, /* stopAfterLineBreak */ false, /* stopAtComments */ true)); + return commentRanges && commentRanges.length && hasInternalAnnotation(last(commentRanges), currentSourceFile); } + const leadingCommentRanges = parseTreeNode && getLeadingCommentRangesOfNode(parseTreeNode, currentSourceFile); + return !!forEach(leadingCommentRanges, range => { + return hasInternalAnnotation(range, currentSourceFile); + }); +} - const declarationEmitNodeBuilderFlags = - NodeBuilderFlags.MultilineObjectLiterals | - NodeBuilderFlags.WriteClassExpressionAsTypeLiteral | - NodeBuilderFlags.UseTypeOfFunction | - NodeBuilderFlags.UseStructuralFallback | - NodeBuilderFlags.AllowEmptyTuple | - NodeBuilderFlags.GenerateNamesForShadowedTypeParams | - NodeBuilderFlags.NoTruncation; - - /** - * Transforms a ts file into a .d.ts file - * This process requires type information, which is retrieved through the emit resolver. Because of this, - * in many places this transformer assumes it will be operating on parse tree nodes directly. - * This means that _no transforms should be allowed to occur before this one_. - */ - export function transformDeclarations(context: TransformationContext) { - const throwDiagnostic = () => Debug.fail("Diagnostic emitted without context"); - let getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic = throwDiagnostic; - let needsDeclare = true; - let isBundledEmit = false; - let resultHasExternalModuleIndicator = false; - let needsScopeFixMarker = false; - let resultHasScopeMarker = false; - let enclosingDeclaration: Node; - let necessaryTypeReferences: Set | undefined; - let lateMarkedStatements: LateVisibilityPaintedStatement[] | undefined; - let lateStatementReplacementMap: ESMap>; - let suppressNewDiagnosticContexts: boolean; - let exportedModulesFromDeclarationEmit: Symbol[] | undefined; - - const { factory } = context; - const host = context.getEmitHost(); - const symbolTracker: SymbolTracker = { - trackSymbol, - reportInaccessibleThisError, - reportInaccessibleUniqueSymbolError, - reportCyclicStructureError, - reportPrivateInBaseOfClassExpression, - reportLikelyUnsafeImportRequiredError, - reportTruncationError, - moduleResolverHost: host, - trackReferencedAmbientModule, - trackExternalModuleSymbolOfImportTypeNode, - reportNonlocalAugmentation, - reportNonSerializableProperty - }; - let errorNameNode: DeclarationName | undefined; - let errorFallbackNode: Declaration | undefined; - - let currentSourceFile: SourceFile; - let refs: ESMap; - let libs: ESMap; - let emittedImports: readonly AnyImportSyntax[] | undefined; // must be declared in container so it can be `undefined` while transformer's first pass - const resolver = context.getEmitResolver(); - const options = context.getCompilerOptions(); - const { noResolve, stripInternal } = options; - return transformRoot; - - function recordTypeReferenceDirectivesIfNecessary(typeReferenceDirectives: readonly string[] | undefined): void { - if (!typeReferenceDirectives) { - return; - } - necessaryTypeReferences = necessaryTypeReferences || new Set(); - for (const ref of typeReferenceDirectives) { - necessaryTypeReferences.add(ref); - } +const declarationEmitNodeBuilderFlags = + NodeBuilderFlags.MultilineObjectLiterals | + NodeBuilderFlags.WriteClassExpressionAsTypeLiteral | + NodeBuilderFlags.UseTypeOfFunction | + NodeBuilderFlags.UseStructuralFallback | + NodeBuilderFlags.AllowEmptyTuple | + NodeBuilderFlags.GenerateNamesForShadowedTypeParams | + NodeBuilderFlags.NoTruncation; + +/** + * Transforms a ts file into a .d.ts file + * This process requires type information, which is retrieved through the emit resolver. Because of this, + * in many places this transformer assumes it will be operating on parse tree nodes directly. + * This means that _no transforms should be allowed to occur before this one_. + */ +export function transformDeclarations(context: TransformationContext) { + const throwDiagnostic = () => Debug.fail("Diagnostic emitted without context"); + let getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic = throwDiagnostic; + let needsDeclare = true; + let isBundledEmit = false; + let resultHasExternalModuleIndicator = false; + let needsScopeFixMarker = false; + let resultHasScopeMarker = false; + let enclosingDeclaration: Node; + let necessaryTypeReferences: Set | undefined; + let lateMarkedStatements: LateVisibilityPaintedStatement[] | undefined; + let lateStatementReplacementMap: ESMap>; + let suppressNewDiagnosticContexts: boolean; + let exportedModulesFromDeclarationEmit: Symbol[] | undefined; + + const { factory } = context; + const host = context.getEmitHost(); + const symbolTracker: SymbolTracker = { + trackSymbol, + reportInaccessibleThisError, + reportInaccessibleUniqueSymbolError, + reportCyclicStructureError, + reportPrivateInBaseOfClassExpression, + reportLikelyUnsafeImportRequiredError, + reportTruncationError, + moduleResolverHost: host, + trackReferencedAmbientModule, + trackExternalModuleSymbolOfImportTypeNode, + reportNonlocalAugmentation, + reportNonSerializableProperty + }; + let errorNameNode: DeclarationName | undefined; + let errorFallbackNode: Declaration | undefined; + + let currentSourceFile: SourceFile; + let refs: ESMap; + let libs: ESMap; + let emittedImports: readonly AnyImportSyntax[] | undefined; // must be declared in container so it can be `undefined` while transformer's first pass + const resolver = context.getEmitResolver(); + const options = context.getCompilerOptions(); + const { noResolve, stripInternal } = options; + return transformRoot; + + function recordTypeReferenceDirectivesIfNecessary(typeReferenceDirectives: readonly string[] | undefined): void { + if (!typeReferenceDirectives) { + return; + } + necessaryTypeReferences = necessaryTypeReferences || new Set(); + for (const ref of typeReferenceDirectives) { + necessaryTypeReferences.add(ref); } + } - function trackReferencedAmbientModule(node: ModuleDeclaration, symbol: Symbol) { - // If it is visible via `// `, then we should just use that - const directives = resolver.getTypeReferenceDirectivesForSymbol(symbol, SymbolFlags.All); - if (length(directives)) { - return recordTypeReferenceDirectivesIfNecessary(directives); - } - // Otherwise we should emit a path-based reference - const container = getSourceFileOfNode(node); - refs.set(getOriginalNodeId(container), container); + function trackReferencedAmbientModule(node: ModuleDeclaration, symbol: Symbol) { + // If it is visible via `// `, then we should just use that + const directives = resolver.getTypeReferenceDirectivesForSymbol(symbol, SymbolFlags.All); + if (length(directives)) { + return recordTypeReferenceDirectivesIfNecessary(directives); } + // Otherwise we should emit a path-based reference + const container = getSourceFileOfNode(node); + refs.set(getOriginalNodeId(container), container); + } - function handleSymbolAccessibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { - if (symbolAccessibilityResult.accessibility === SymbolAccessibility.Accessible) { - // Add aliases back onto the possible imports list if they're not there so we can try them again with updated visibility info - if (symbolAccessibilityResult && symbolAccessibilityResult.aliasesToMakeVisible) { - if (!lateMarkedStatements) { - lateMarkedStatements = symbolAccessibilityResult.aliasesToMakeVisible; - } - else { - for (const ref of symbolAccessibilityResult.aliasesToMakeVisible) { - pushIfUnique(lateMarkedStatements, ref); - } + function handleSymbolAccessibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { + if (symbolAccessibilityResult.accessibility === SymbolAccessibility.Accessible) { + // Add aliases back onto the possible imports list if they're not there so we can try them again with updated visibility info + if (symbolAccessibilityResult && symbolAccessibilityResult.aliasesToMakeVisible) { + if (!lateMarkedStatements) { + lateMarkedStatements = symbolAccessibilityResult.aliasesToMakeVisible; + } + else { + for (const ref of symbolAccessibilityResult.aliasesToMakeVisible) { + pushIfUnique(lateMarkedStatements, ref); } } - - // TODO: Do all these accessibility checks inside/after the first pass in the checker when declarations are enabled, if possible } - else { - // Report error - const errorInfo = getSymbolAccessibilityDiagnostic(symbolAccessibilityResult); - if (errorInfo) { - if (errorInfo.typeName) { - context.addDiagnostic(createDiagnosticForNode(symbolAccessibilityResult.errorNode || errorInfo.errorNode, - errorInfo.diagnosticMessage, - getTextOfNode(errorInfo.typeName), - symbolAccessibilityResult.errorSymbolName, - symbolAccessibilityResult.errorModuleName)); - } - else { - context.addDiagnostic(createDiagnosticForNode(symbolAccessibilityResult.errorNode || errorInfo.errorNode, - errorInfo.diagnosticMessage, - symbolAccessibilityResult.errorSymbolName, - symbolAccessibilityResult.errorModuleName)); - } - return true; + + // TODO: Do all these accessibility checks inside/after the first pass in the checker when declarations are enabled, if possible + } + else { + // Report error + const errorInfo = getSymbolAccessibilityDiagnostic(symbolAccessibilityResult); + if (errorInfo) { + if (errorInfo.typeName) { + context.addDiagnostic(createDiagnosticForNode(symbolAccessibilityResult.errorNode || errorInfo.errorNode, + errorInfo.diagnosticMessage, + getTextOfNode(errorInfo.typeName), + symbolAccessibilityResult.errorSymbolName, + symbolAccessibilityResult.errorModuleName)); + } + else { + context.addDiagnostic(createDiagnosticForNode(symbolAccessibilityResult.errorNode || errorInfo.errorNode, + errorInfo.diagnosticMessage, + symbolAccessibilityResult.errorSymbolName, + symbolAccessibilityResult.errorModuleName)); } + return true; } - return false; } + return false; + } - function trackExternalModuleSymbolOfImportTypeNode(symbol: Symbol) { - if (!isBundledEmit) { - (exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol); - } + function trackExternalModuleSymbolOfImportTypeNode(symbol: Symbol) { + if (!isBundledEmit) { + (exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol); } + } + + function trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) { + if (symbol.flags & SymbolFlags.TypeParameter) return false; + const issuedDiagnostic = handleSymbolAccessibilityError(resolver.isSymbolAccessible(symbol, enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ true)); + recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForSymbol(symbol, meaning)); + return issuedDiagnostic; + } - function trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) { - if (symbol.flags & SymbolFlags.TypeParameter) return false; - const issuedDiagnostic = handleSymbolAccessibilityError(resolver.isSymbolAccessible(symbol, enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ true)); - recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForSymbol(symbol, meaning)); - return issuedDiagnostic; + function reportPrivateInBaseOfClassExpression(propertyName: string) { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic( + createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.Property_0_of_exported_class_expression_may_not_be_private_or_protected, propertyName)); } + } - function reportPrivateInBaseOfClassExpression(propertyName: string) { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic( - createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.Property_0_of_exported_class_expression_may_not_be_private_or_protected, propertyName)); - } + function errorDeclarationNameWithFallback() { + return errorNameNode ? declarationNameToString(errorNameNode) : + errorFallbackNode && getNameOfDeclaration(errorFallbackNode) ? declarationNameToString(getNameOfDeclaration(errorFallbackNode)) : + errorFallbackNode && isExportAssignment(errorFallbackNode) ? errorFallbackNode.isExportEquals ? "export=" : "default" : + "(Missing)"; // same fallback declarationNameToString uses when node is zero-width (ie, nameless) + } + + function reportInaccessibleUniqueSymbolError() { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, + errorDeclarationNameWithFallback(), + "unique symbol")); } + } - function errorDeclarationNameWithFallback() { - return errorNameNode ? declarationNameToString(errorNameNode) : - errorFallbackNode && getNameOfDeclaration(errorFallbackNode) ? declarationNameToString(getNameOfDeclaration(errorFallbackNode)) : - errorFallbackNode && isExportAssignment(errorFallbackNode) ? errorFallbackNode.isExportEquals ? "export=" : "default" : - "(Missing)"; // same fallback declarationNameToString uses when node is zero-width (ie, nameless) + function reportCyclicStructureError() { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_references_a_type_with_a_cyclic_structure_which_cannot_be_trivially_serialized_A_type_annotation_is_necessary, + errorDeclarationNameWithFallback())); } + } - function reportInaccessibleUniqueSymbolError() { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, - errorDeclarationNameWithFallback(), - "unique symbol")); - } + function reportInaccessibleThisError() { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, + errorDeclarationNameWithFallback(), + "this")); } + } - function reportCyclicStructureError() { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_references_a_type_with_a_cyclic_structure_which_cannot_be_trivially_serialized_A_type_annotation_is_necessary, - errorDeclarationNameWithFallback())); - } + function reportLikelyUnsafeImportRequiredError(specifier: string) { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_cannot_be_named_without_a_reference_to_1_This_is_likely_not_portable_A_type_annotation_is_necessary, + errorDeclarationNameWithFallback(), + specifier)); } + } - function reportInaccessibleThisError() { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, - errorDeclarationNameWithFallback(), - "this")); - } + function reportTruncationError() { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_this_node_exceeds_the_maximum_length_the_compiler_will_serialize_An_explicit_type_annotation_is_needed)); } + } - function reportLikelyUnsafeImportRequiredError(specifier: string) { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_cannot_be_named_without_a_reference_to_1_This_is_likely_not_portable_A_type_annotation_is_necessary, - errorDeclarationNameWithFallback(), - specifier)); + function reportNonlocalAugmentation(containingFile: SourceFile, parentSymbol: Symbol, symbol: Symbol) { + const primaryDeclaration = parentSymbol.declarations?.find(d => getSourceFileOfNode(d) === containingFile)!; + const augmentingDeclarations = filter(symbol.declarations, d => getSourceFileOfNode(d) !== containingFile); + if (augmentingDeclarations) { + for (const augmentations of augmentingDeclarations) { + context.addDiagnostic(addRelatedInfo( + createDiagnosticForNode(augmentations, Diagnostics.Declaration_augments_declaration_in_another_file_This_cannot_be_serialized), + createDiagnosticForNode(primaryDeclaration, Diagnostics.This_is_the_declaration_being_augmented_Consider_moving_the_augmenting_declaration_into_the_same_file) + )); } } + } + + function reportNonSerializableProperty(propertyName: string) { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_type_of_this_node_cannot_be_serialized_because_its_property_0_cannot_be_serialized, propertyName)); + } + } - function reportTruncationError() { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_this_node_exceeds_the_maximum_length_the_compiler_will_serialize_An_explicit_type_annotation_is_needed)); - } + function transformDeclarationsForJS(sourceFile: SourceFile, bundled?: boolean) { + const oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = (s) => (s.errorNode && canProduceDiagnostics(s.errorNode) ? createGetSymbolAccessibilityDiagnosticForNode(s.errorNode)(s) : ({ + diagnosticMessage: s.errorModuleName + ? Diagnostics.Declaration_emit_for_this_file_requires_using_private_name_0_from_module_1_An_explicit_type_annotation_may_unblock_declaration_emit + : Diagnostics.Declaration_emit_for_this_file_requires_using_private_name_0_An_explicit_type_annotation_may_unblock_declaration_emit, + errorNode: s.errorNode || sourceFile + })); + const result = resolver.getDeclarationStatementsForSourceFile(sourceFile, declarationEmitNodeBuilderFlags, symbolTracker, bundled); + getSymbolAccessibilityDiagnostic = oldDiag; + return result; + } + + function transformRoot(node: Bundle): Bundle; + function transformRoot(node: SourceFile): SourceFile; + function transformRoot(node: SourceFile | Bundle): SourceFile | Bundle; + function transformRoot(node: SourceFile | Bundle) { + if (node.kind === SyntaxKind.SourceFile && node.isDeclarationFile) { + return node; } - function reportNonlocalAugmentation(containingFile: SourceFile, parentSymbol: Symbol, symbol: Symbol) { - const primaryDeclaration = parentSymbol.declarations?.find(d => getSourceFileOfNode(d) === containingFile)!; - const augmentingDeclarations = filter(symbol.declarations, d => getSourceFileOfNode(d) !== containingFile); - if (augmentingDeclarations) { - for (const augmentations of augmentingDeclarations) { - context.addDiagnostic(addRelatedInfo( - createDiagnosticForNode(augmentations, Diagnostics.Declaration_augments_declaration_in_another_file_This_cannot_be_serialized), - createDiagnosticForNode(primaryDeclaration, Diagnostics.This_is_the_declaration_being_augmented_Consider_moving_the_augmenting_declaration_into_the_same_file) - )); + if (node.kind === SyntaxKind.Bundle) { + isBundledEmit = true; + refs = new Map(); + libs = new Map(); + let hasNoDefaultLib = false; + const bundle = factory.createBundle(map(node.sourceFiles, + sourceFile => { + if (sourceFile.isDeclarationFile) return undefined!; // Omit declaration files from bundle results, too // TODO: GH#18217 + hasNoDefaultLib = hasNoDefaultLib || sourceFile.hasNoDefaultLib; + currentSourceFile = sourceFile; + enclosingDeclaration = sourceFile; + lateMarkedStatements = undefined; + suppressNewDiagnosticContexts = false; + lateStatementReplacementMap = new Map(); + getSymbolAccessibilityDiagnostic = throwDiagnostic; + needsScopeFixMarker = false; + resultHasScopeMarker = false; + collectReferences(sourceFile, refs); + collectLibs(sourceFile, libs); + if (isExternalOrCommonJsModule(sourceFile) || isJsonSourceFile(sourceFile)) { + resultHasExternalModuleIndicator = false; // unused in external module bundle emit (all external modules are within module blocks, therefore are known to be modules) + needsDeclare = false; + const statements = isSourceFileJS(sourceFile) ? factory.createNodeArray(transformDeclarationsForJS(sourceFile, /*bundled*/ true)) : visitNodes(sourceFile.statements, visitDeclarationStatements); + const newFile = factory.updateSourceFile(sourceFile, [factory.createModuleDeclaration( + [], + [factory.createModifier(SyntaxKind.DeclareKeyword)], + factory.createStringLiteral(getResolvedExternalModuleName(context.getEmitHost(), sourceFile)), + factory.createModuleBlock(setTextRange(factory.createNodeArray(transformAndReplaceLatePaintedStatements(statements)), sourceFile.statements)) + )], /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); + return newFile; + } + needsDeclare = true; + const updated = isSourceFileJS(sourceFile) ? factory.createNodeArray(transformDeclarationsForJS(sourceFile)) : visitNodes(sourceFile.statements, visitDeclarationStatements); + return factory.updateSourceFile(sourceFile, transformAndReplaceLatePaintedStatements(updated), /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); } - } + ), mapDefined(node.prepends, prepend => { + if (prepend.kind === SyntaxKind.InputFiles) { + const sourceFile = createUnparsedSourceFile(prepend, "dts", stripInternal); + hasNoDefaultLib = hasNoDefaultLib || !!sourceFile.hasNoDefaultLib; + collectReferences(sourceFile, refs); + recordTypeReferenceDirectivesIfNecessary(sourceFile.typeReferenceDirectives); + collectLibs(sourceFile, libs); + return sourceFile; + } + return prepend; + })); + bundle.syntheticFileReferences = []; + bundle.syntheticTypeReferences = getFileReferencesForUsedTypeReferences(); + bundle.syntheticLibReferences = getLibReferences(); + bundle.hasNoDefaultLib = hasNoDefaultLib; + const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!)); + const referenceVisitor = mapReferencesIntoArray(bundle.syntheticFileReferences as FileReference[], outputFilePath); + refs.forEach(referenceVisitor); + return bundle; } - function reportNonSerializableProperty(propertyName: string) { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_type_of_this_node_cannot_be_serialized_because_its_property_0_cannot_be_serialized, propertyName)); + // Single source file + needsDeclare = true; + needsScopeFixMarker = false; + resultHasScopeMarker = false; + enclosingDeclaration = node; + currentSourceFile = node; + getSymbolAccessibilityDiagnostic = throwDiagnostic; + isBundledEmit = false; + resultHasExternalModuleIndicator = false; + suppressNewDiagnosticContexts = false; + lateMarkedStatements = undefined; + lateStatementReplacementMap = new Map(); + necessaryTypeReferences = undefined; + refs = collectReferences(currentSourceFile, new Map()); + libs = collectLibs(currentSourceFile, new Map()); + const references: FileReference[] = []; + const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!)); + const referenceVisitor = mapReferencesIntoArray(references, outputFilePath); + let combinedStatements: NodeArray; + if (isSourceFileJS(currentSourceFile)) { + combinedStatements = factory.createNodeArray(transformDeclarationsForJS(node)); + refs.forEach(referenceVisitor); + emittedImports = filter(combinedStatements, isAnyImportSyntax); + } + else { + const statements = visitNodes(node.statements, visitDeclarationStatements); + combinedStatements = setTextRange(factory.createNodeArray(transformAndReplaceLatePaintedStatements(statements)), node.statements); + refs.forEach(referenceVisitor); + emittedImports = filter(combinedStatements, isAnyImportSyntax); + if (isExternalModule(node) && (!resultHasExternalModuleIndicator || (needsScopeFixMarker && !resultHasScopeMarker))) { + combinedStatements = setTextRange(factory.createNodeArray([...combinedStatements, createEmptyExports(factory)]), combinedStatements); } } + const updated = factory.updateSourceFile(node, combinedStatements, /*isDeclarationFile*/ true, references, getFileReferencesForUsedTypeReferences(), node.hasNoDefaultLib, getLibReferences()); + updated.exportedModulesFromDeclarationEmit = exportedModulesFromDeclarationEmit; + return updated; - function transformDeclarationsForJS(sourceFile: SourceFile, bundled?: boolean) { - const oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = (s) => (s.errorNode && canProduceDiagnostics(s.errorNode) ? createGetSymbolAccessibilityDiagnosticForNode(s.errorNode)(s) : ({ - diagnosticMessage: s.errorModuleName - ? Diagnostics.Declaration_emit_for_this_file_requires_using_private_name_0_from_module_1_An_explicit_type_annotation_may_unblock_declaration_emit - : Diagnostics.Declaration_emit_for_this_file_requires_using_private_name_0_An_explicit_type_annotation_may_unblock_declaration_emit, - errorNode: s.errorNode || sourceFile - })); - const result = resolver.getDeclarationStatementsForSourceFile(sourceFile, declarationEmitNodeBuilderFlags, symbolTracker, bundled); - getSymbolAccessibilityDiagnostic = oldDiag; - return result; + function getLibReferences() { + return map(arrayFrom(libs.keys()), lib => ({ fileName: lib, pos: -1, end: -1 })); } - function transformRoot(node: Bundle): Bundle; - function transformRoot(node: SourceFile): SourceFile; - function transformRoot(node: SourceFile | Bundle): SourceFile | Bundle; - function transformRoot(node: SourceFile | Bundle) { - if (node.kind === SyntaxKind.SourceFile && node.isDeclarationFile) { - return node; - } + function getFileReferencesForUsedTypeReferences() { + return necessaryTypeReferences ? mapDefined(arrayFrom(necessaryTypeReferences.keys()), getFileReferenceForTypeName) : []; + } - if (node.kind === SyntaxKind.Bundle) { - isBundledEmit = true; - refs = new Map(); - libs = new Map(); - let hasNoDefaultLib = false; - const bundle = factory.createBundle(map(node.sourceFiles, - sourceFile => { - if (sourceFile.isDeclarationFile) return undefined!; // Omit declaration files from bundle results, too // TODO: GH#18217 - hasNoDefaultLib = hasNoDefaultLib || sourceFile.hasNoDefaultLib; - currentSourceFile = sourceFile; - enclosingDeclaration = sourceFile; - lateMarkedStatements = undefined; - suppressNewDiagnosticContexts = false; - lateStatementReplacementMap = new Map(); - getSymbolAccessibilityDiagnostic = throwDiagnostic; - needsScopeFixMarker = false; - resultHasScopeMarker = false; - collectReferences(sourceFile, refs); - collectLibs(sourceFile, libs); - if (isExternalOrCommonJsModule(sourceFile) || isJsonSourceFile(sourceFile)) { - resultHasExternalModuleIndicator = false; // unused in external module bundle emit (all external modules are within module blocks, therefore are known to be modules) - needsDeclare = false; - const statements = isSourceFileJS(sourceFile) ? factory.createNodeArray(transformDeclarationsForJS(sourceFile, /*bundled*/ true)) : visitNodes(sourceFile.statements, visitDeclarationStatements); - const newFile = factory.updateSourceFile(sourceFile, [factory.createModuleDeclaration( - [], - [factory.createModifier(SyntaxKind.DeclareKeyword)], - factory.createStringLiteral(getResolvedExternalModuleName(context.getEmitHost(), sourceFile)), - factory.createModuleBlock(setTextRange(factory.createNodeArray(transformAndReplaceLatePaintedStatements(statements)), sourceFile.statements)) - )], /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); - return newFile; + function getFileReferenceForTypeName(typeName: string): FileReference | undefined { + // Elide type references for which we have imports + if (emittedImports) { + for (const importStatement of emittedImports) { + if (isImportEqualsDeclaration(importStatement) && isExternalModuleReference(importStatement.moduleReference)) { + const expr = importStatement.moduleReference.expression; + if (isStringLiteralLike(expr) && expr.text === typeName) { + return undefined; } - needsDeclare = true; - const updated = isSourceFileJS(sourceFile) ? factory.createNodeArray(transformDeclarationsForJS(sourceFile)) : visitNodes(sourceFile.statements, visitDeclarationStatements); - return factory.updateSourceFile(sourceFile, transformAndReplaceLatePaintedStatements(updated), /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); } - ), mapDefined(node.prepends, prepend => { - if (prepend.kind === SyntaxKind.InputFiles) { - const sourceFile = createUnparsedSourceFile(prepend, "dts", stripInternal); - hasNoDefaultLib = hasNoDefaultLib || !!sourceFile.hasNoDefaultLib; - collectReferences(sourceFile, refs); - recordTypeReferenceDirectivesIfNecessary(sourceFile.typeReferenceDirectives); - collectLibs(sourceFile, libs); - return sourceFile; + else if (isImportDeclaration(importStatement) && isStringLiteral(importStatement.moduleSpecifier) && importStatement.moduleSpecifier.text === typeName) { + return undefined; } - return prepend; - })); - bundle.syntheticFileReferences = []; - bundle.syntheticTypeReferences = getFileReferencesForUsedTypeReferences(); - bundle.syntheticLibReferences = getLibReferences(); - bundle.hasNoDefaultLib = hasNoDefaultLib; - const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!)); - const referenceVisitor = mapReferencesIntoArray(bundle.syntheticFileReferences as FileReference[], outputFilePath); - refs.forEach(referenceVisitor); - return bundle; - } - - // Single source file - needsDeclare = true; - needsScopeFixMarker = false; - resultHasScopeMarker = false; - enclosingDeclaration = node; - currentSourceFile = node; - getSymbolAccessibilityDiagnostic = throwDiagnostic; - isBundledEmit = false; - resultHasExternalModuleIndicator = false; - suppressNewDiagnosticContexts = false; - lateMarkedStatements = undefined; - lateStatementReplacementMap = new Map(); - necessaryTypeReferences = undefined; - refs = collectReferences(currentSourceFile, new Map()); - libs = collectLibs(currentSourceFile, new Map()); - const references: FileReference[] = []; - const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!)); - const referenceVisitor = mapReferencesIntoArray(references, outputFilePath); - let combinedStatements: NodeArray; - if (isSourceFileJS(currentSourceFile)) { - combinedStatements = factory.createNodeArray(transformDeclarationsForJS(node)); - refs.forEach(referenceVisitor); - emittedImports = filter(combinedStatements, isAnyImportSyntax); - } - else { - const statements = visitNodes(node.statements, visitDeclarationStatements); - combinedStatements = setTextRange(factory.createNodeArray(transformAndReplaceLatePaintedStatements(statements)), node.statements); - refs.forEach(referenceVisitor); - emittedImports = filter(combinedStatements, isAnyImportSyntax); - if (isExternalModule(node) && (!resultHasExternalModuleIndicator || (needsScopeFixMarker && !resultHasScopeMarker))) { - combinedStatements = setTextRange(factory.createNodeArray([...combinedStatements, createEmptyExports(factory)]), combinedStatements); } } - const updated = factory.updateSourceFile(node, combinedStatements, /*isDeclarationFile*/ true, references, getFileReferencesForUsedTypeReferences(), node.hasNoDefaultLib, getLibReferences()); - updated.exportedModulesFromDeclarationEmit = exportedModulesFromDeclarationEmit; - return updated; - - function getLibReferences() { - return map(arrayFrom(libs.keys()), lib => ({ fileName: lib, pos: -1, end: -1 })); - } - - function getFileReferencesForUsedTypeReferences() { - return necessaryTypeReferences ? mapDefined(arrayFrom(necessaryTypeReferences.keys()), getFileReferenceForTypeName) : []; - } + return { fileName: typeName, pos: -1, end: -1 }; + } - function getFileReferenceForTypeName(typeName: string): FileReference | undefined { - // Elide type references for which we have imports - if (emittedImports) { - for (const importStatement of emittedImports) { - if (isImportEqualsDeclaration(importStatement) && isExternalModuleReference(importStatement.moduleReference)) { - const expr = importStatement.moduleReference.expression; - if (isStringLiteralLike(expr) && expr.text === typeName) { - return undefined; - } - } - else if (isImportDeclaration(importStatement) && isStringLiteral(importStatement.moduleSpecifier) && importStatement.moduleSpecifier.text === typeName) { - return undefined; - } - } + function mapReferencesIntoArray(references: FileReference[], outputFilePath: string): (file: SourceFile) => void { + return file => { + let declFileName: string; + if (file.isDeclarationFile) { // Neither decl files or js should have their refs changed + declFileName = file.fileName; + } + else { + if (isBundledEmit && contains((node as Bundle).sourceFiles, file)) return; // Omit references to files which are being merged + const paths = getOutputPathsFor(file, host, /*forceDtsPaths*/ true); + declFileName = paths.declarationFilePath || paths.jsFilePath || file.fileName; } - return { fileName: typeName, pos: -1, end: -1 }; - } - function mapReferencesIntoArray(references: FileReference[], outputFilePath: string): (file: SourceFile) => void { - return file => { - let declFileName: string; - if (file.isDeclarationFile) { // Neither decl files or js should have their refs changed - declFileName = file.fileName; - } - else { - if (isBundledEmit && contains((node as Bundle).sourceFiles, file)) return; // Omit references to files which are being merged - const paths = getOutputPathsFor(file, host, /*forceDtsPaths*/ true); - declFileName = paths.declarationFilePath || paths.jsFilePath || file.fileName; + if (declFileName) { + const specifier = moduleSpecifiers.getModuleSpecifier( + options, + currentSourceFile, + toPath(outputFilePath, host.getCurrentDirectory(), host.getCanonicalFileName), + toPath(declFileName, host.getCurrentDirectory(), host.getCanonicalFileName), + host, + ); + if (!pathIsRelative(specifier)) { + // If some compiler option/symlink/whatever allows access to the file containing the ambient module declaration + // via a non-relative name, emit a type reference directive to that non-relative name, rather than + // a relative path to the declaration file + recordTypeReferenceDirectivesIfNecessary([specifier]); + return; } - if (declFileName) { - const specifier = moduleSpecifiers.getModuleSpecifier( - options, - currentSourceFile, - toPath(outputFilePath, host.getCurrentDirectory(), host.getCanonicalFileName), - toPath(declFileName, host.getCurrentDirectory(), host.getCanonicalFileName), - host, - ); - if (!pathIsRelative(specifier)) { - // If some compiler option/symlink/whatever allows access to the file containing the ambient module declaration - // via a non-relative name, emit a type reference directive to that non-relative name, rather than - // a relative path to the declaration file - recordTypeReferenceDirectivesIfNecessary([specifier]); - return; - } - - let fileName = getRelativePathToDirectoryOrUrl( - outputFilePath, - declFileName, - host.getCurrentDirectory(), - host.getCanonicalFileName, - /*isAbsolutePathAnUrl*/ false - ); - if (startsWith(fileName, "./") && hasExtension(fileName)) { - fileName = fileName.substring(2); - } - - // omit references to files from node_modules (npm may disambiguate module - // references when installing this package, making the path is unreliable). - if (startsWith(fileName, "node_modules/") || pathContainsNodeModules(fileName)) { - return; - } + let fileName = getRelativePathToDirectoryOrUrl( + outputFilePath, + declFileName, + host.getCurrentDirectory(), + host.getCanonicalFileName, + /*isAbsolutePathAnUrl*/ false + ); + if (startsWith(fileName, "./") && hasExtension(fileName)) { + fileName = fileName.substring(2); + } - references.push({ pos: -1, end: -1, fileName }); + // omit references to files from node_modules (npm may disambiguate module + // references when installing this package, making the path is unreliable). + if (startsWith(fileName, "node_modules/") || pathContainsNodeModules(fileName)) { + return; } - }; - } - } - function collectReferences(sourceFile: SourceFile | UnparsedSource, ret: ESMap) { - if (noResolve || (!isUnparsedSource(sourceFile) && isSourceFileJS(sourceFile))) return ret; - forEach(sourceFile.referencedFiles, f => { - const elem = host.getSourceFileFromReference(sourceFile, f); - if (elem) { - ret.set(getOriginalNodeId(elem), elem); + references.push({ pos: -1, end: -1, fileName }); } - }); - return ret; + }; } + } - function collectLibs(sourceFile: SourceFile | UnparsedSource, ret: ESMap) { - forEach(sourceFile.libReferenceDirectives, ref => { - const lib = host.getLibFileFromReference(ref); - if (lib) { - ret.set(toFileNameLowerCase(ref.fileName), true); - } - }); - return ret; - } + function collectReferences(sourceFile: SourceFile | UnparsedSource, ret: ESMap) { + if (noResolve || (!isUnparsedSource(sourceFile) && isSourceFileJS(sourceFile))) return ret; + forEach(sourceFile.referencedFiles, f => { + const elem = host.getSourceFileFromReference(sourceFile, f); + if (elem) { + ret.set(getOriginalNodeId(elem), elem); + } + }); + return ret; + } + + function collectLibs(sourceFile: SourceFile | UnparsedSource, ret: ESMap) { + forEach(sourceFile.libReferenceDirectives, ref => { + const lib = host.getLibFileFromReference(ref); + if (lib) { + ret.set(toFileNameLowerCase(ref.fileName), true); + } + }); + return ret; + } - function filterBindingPatternInitializers(name: BindingName) { - if (name.kind === SyntaxKind.Identifier) { - return name; + function filterBindingPatternInitializers(name: BindingName) { + if (name.kind === SyntaxKind.Identifier) { + return name; + } + else { + if (name.kind === SyntaxKind.ArrayBindingPattern) { + return factory.updateArrayBindingPattern(name, visitNodes(name.elements, visitBindingElement)); } else { - if (name.kind === SyntaxKind.ArrayBindingPattern) { - return factory.updateArrayBindingPattern(name, visitNodes(name.elements, visitBindingElement)); - } - else { - return factory.updateObjectBindingPattern(name, visitNodes(name.elements, visitBindingElement)); - } + return factory.updateObjectBindingPattern(name, visitNodes(name.elements, visitBindingElement)); } + } - function visitBindingElement(elem: T): T; - function visitBindingElement(elem: ArrayBindingElement): ArrayBindingElement { - if (elem.kind === SyntaxKind.OmittedExpression) { - return elem; - } - return factory.updateBindingElement(elem, elem.dotDotDotToken, elem.propertyName, filterBindingPatternInitializers(elem.name), shouldPrintWithInitializer(elem) ? elem.initializer : undefined); + function visitBindingElement(elem: T): T; + function visitBindingElement(elem: ArrayBindingElement): ArrayBindingElement { + if (elem.kind === SyntaxKind.OmittedExpression) { + return elem; } + return factory.updateBindingElement(elem, elem.dotDotDotToken, elem.propertyName, filterBindingPatternInitializers(elem.name), shouldPrintWithInitializer(elem) ? elem.initializer : undefined); } + } - function ensureParameter(p: ParameterDeclaration, modifierMask?: ModifierFlags, type?: TypeNode): ParameterDeclaration { - let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined; - if (!suppressNewDiagnosticContexts) { - oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p); - } - const newParam = factory.updateParameterDeclaration( - p, - /*decorators*/ undefined, - maskModifiers(p, modifierMask), - p.dotDotDotToken, - filterBindingPatternInitializers(p.name), - resolver.isOptionalParameter(p) ? (p.questionToken || factory.createToken(SyntaxKind.QuestionToken)) : undefined, - ensureType(p, type || p.type, /*ignorePrivate*/ true), // Ignore private param props, since this type is going straight back into a param - ensureNoInitializer(p) - ); - if (!suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag!; - } - return newParam; + function ensureParameter(p: ParameterDeclaration, modifierMask?: ModifierFlags, type?: TypeNode): ParameterDeclaration { + let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined; + if (!suppressNewDiagnosticContexts) { + oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p); } + const newParam = factory.updateParameterDeclaration( + p, + /*decorators*/ undefined, + maskModifiers(p, modifierMask), + p.dotDotDotToken, + filterBindingPatternInitializers(p.name), + resolver.isOptionalParameter(p) ? (p.questionToken || factory.createToken(SyntaxKind.QuestionToken)) : undefined, + ensureType(p, type || p.type, /*ignorePrivate*/ true), // Ignore private param props, since this type is going straight back into a param + ensureNoInitializer(p) + ); + if (!suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = oldDiag!; + } + return newParam; + } - function shouldPrintWithInitializer(node: Node) { - return canHaveLiteralInitializer(node) && resolver.isLiteralConstDeclaration(getParseTreeNode(node) as CanHaveLiteralInitializer); // TODO: Make safe + function shouldPrintWithInitializer(node: Node) { + return canHaveLiteralInitializer(node) && resolver.isLiteralConstDeclaration(getParseTreeNode(node) as CanHaveLiteralInitializer); // TODO: Make safe + } + + function ensureNoInitializer(node: CanHaveLiteralInitializer) { + if (shouldPrintWithInitializer(node)) { + return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe } + return undefined; + } - function ensureNoInitializer(node: CanHaveLiteralInitializer) { - if (shouldPrintWithInitializer(node)) { - return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe - } - return undefined; - } - - type HasInferredType = - | FunctionDeclaration - | MethodDeclaration - | GetAccessorDeclaration - | SetAccessorDeclaration - | BindingElement - | ConstructSignatureDeclaration - | VariableDeclaration - | MethodSignature - | CallSignatureDeclaration - | ParameterDeclaration - | PropertyDeclaration - | PropertySignature; - - function ensureType(node: HasInferredType, type: TypeNode | undefined, ignorePrivate?: boolean): TypeNode | undefined { - if (!ignorePrivate && hasEffectiveModifier(node, ModifierFlags.Private)) { - // Private nodes emit no types (except private parameter properties, whose parameter types are actually visible) - return; - } - if (shouldPrintWithInitializer(node)) { - // Literal const declarations will have an initializer ensured rather than a type - return; - } - const shouldUseResolverType = node.kind === SyntaxKind.Parameter && - (resolver.isRequiredInitializedParameter(node) || - resolver.isOptionalUninitializedParameterProperty(node)); - if (type && !shouldUseResolverType) { - return visitNode(type, visitDeclarationSubtree); - } - if (!getParseTreeNode(node)) { - return type ? visitNode(type, visitDeclarationSubtree) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - if (node.kind === SyntaxKind.SetAccessor) { - // Set accessors with no associated type node (from it's param or get accessor return) are `any` since they are never contextually typed right now - // (The inferred type here will be void, but the old declaration emitter printed `any`, so this replicates that) - return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - errorNameNode = node.name; - let oldDiag: typeof getSymbolAccessibilityDiagnostic; - if (!suppressNewDiagnosticContexts) { - oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(node); - } - if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { - return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); - } - if (node.kind === SyntaxKind.Parameter - || node.kind === SyntaxKind.PropertyDeclaration - || node.kind === SyntaxKind.PropertySignature) { - if (!node.initializer) return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType)); - return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType) || resolver.createTypeOfExpression(node.initializer, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); - } - return cleanup(resolver.createReturnTypeOfSignatureDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); + type HasInferredType = + | FunctionDeclaration + | MethodDeclaration + | GetAccessorDeclaration + | SetAccessorDeclaration + | BindingElement + | ConstructSignatureDeclaration + | VariableDeclaration + | MethodSignature + | CallSignatureDeclaration + | ParameterDeclaration + | PropertyDeclaration + | PropertySignature; - function cleanup(returnValue: TypeNode | undefined) { - errorNameNode = undefined; - if (!suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag; - } - return returnValue || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - } + function ensureType(node: HasInferredType, type: TypeNode | undefined, ignorePrivate?: boolean): TypeNode | undefined { + if (!ignorePrivate && hasEffectiveModifier(node, ModifierFlags.Private)) { + // Private nodes emit no types (except private parameter properties, whose parameter types are actually visible) + return; + } + if (shouldPrintWithInitializer(node)) { + // Literal const declarations will have an initializer ensured rather than a type + return; } + const shouldUseResolverType = node.kind === SyntaxKind.Parameter && + (resolver.isRequiredInitializedParameter(node) || + resolver.isOptionalUninitializedParameterProperty(node)); + if (type && !shouldUseResolverType) { + return visitNode(type, visitDeclarationSubtree); + } + if (!getParseTreeNode(node)) { + return type ? visitNode(type, visitDeclarationSubtree) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + if (node.kind === SyntaxKind.SetAccessor) { + // Set accessors with no associated type node (from it's param or get accessor return) are `any` since they are never contextually typed right now + // (The inferred type here will be void, but the old declaration emitter printed `any`, so this replicates that) + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + errorNameNode = node.name; + let oldDiag: typeof getSymbolAccessibilityDiagnostic; + if (!suppressNewDiagnosticContexts) { + oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(node); + } + if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { + return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); + } + if (node.kind === SyntaxKind.Parameter + || node.kind === SyntaxKind.PropertyDeclaration + || node.kind === SyntaxKind.PropertySignature) { + if (!node.initializer) return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType)); + return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType) || resolver.createTypeOfExpression(node.initializer, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); + } + return cleanup(resolver.createReturnTypeOfSignatureDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); - function isDeclarationAndNotVisible(node: NamedDeclaration) { - node = getParseTreeNode(node) as NamedDeclaration; - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.EnumDeclaration: - return !resolver.isDeclarationVisible(node); - // The following should be doing their own visibility checks based on filtering their members - case SyntaxKind.VariableDeclaration: - return !getBindingNameVisible(node as VariableDeclaration); - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExportAssignment: - return false; - case SyntaxKind.ClassStaticBlockDeclaration: - return true; + function cleanup(returnValue: TypeNode | undefined) { + errorNameNode = undefined; + if (!suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = oldDiag; } - return false; + return returnValue || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } + } - // If the ExpandoFunctionDeclaration have multiple overloads, then we only need to emit properties for the last one. - function shouldEmitFunctionProperties(input: FunctionDeclaration) { - if (input.body) { + function isDeclarationAndNotVisible(node: NamedDeclaration) { + node = getParseTreeNode(node) as NamedDeclaration; + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + return !resolver.isDeclarationVisible(node); + // The following should be doing their own visibility checks based on filtering their members + case SyntaxKind.VariableDeclaration: + return !getBindingNameVisible(node as VariableDeclaration); + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExportAssignment: + return false; + case SyntaxKind.ClassStaticBlockDeclaration: return true; - } + } + return false; + } - const overloadSignatures = input.symbol.declarations?.filter(decl => isFunctionDeclaration(decl) && !decl.body); - return !overloadSignatures || overloadSignatures.indexOf(input) === overloadSignatures.length - 1; + // If the ExpandoFunctionDeclaration have multiple overloads, then we only need to emit properties for the last one. + function shouldEmitFunctionProperties(input: FunctionDeclaration) { + if (input.body) { + return true; } - function getBindingNameVisible(elem: BindingElement | VariableDeclaration | OmittedExpression): boolean { - if (isOmittedExpression(elem)) { - return false; + const overloadSignatures = input.symbol.declarations?.filter(decl => isFunctionDeclaration(decl) && !decl.body); + return !overloadSignatures || overloadSignatures.indexOf(input) === overloadSignatures.length - 1; + } + + function getBindingNameVisible(elem: BindingElement | VariableDeclaration | OmittedExpression): boolean { + if (isOmittedExpression(elem)) { + return false; + } + if (isBindingPattern(elem.name)) { + // If any child binding pattern element has been marked visible (usually by collect linked aliases), then this is visible + return some(elem.name.elements, getBindingNameVisible); + } + else { + return resolver.isDeclarationVisible(elem); + } + } + + function updateParamsList(node: Node, params: NodeArray, modifierMask?: ModifierFlags) { + if (hasEffectiveModifier(node, ModifierFlags.Private)) { + return undefined!; // TODO: GH#18217 + } + const newParams = map(params, p => ensureParameter(p, modifierMask)); + if (!newParams) { + return undefined!; // TODO: GH#18217 + } + return factory.createNodeArray(newParams, params.hasTrailingComma); + } + + function updateAccessorParamsList(input: AccessorDeclaration, isPrivate: boolean) { + let newParams: ParameterDeclaration[] | undefined; + if (!isPrivate) { + const thisParameter = getThisParameter(input); + if (thisParameter) { + newParams = [ensureParameter(thisParameter)]; } - if (isBindingPattern(elem.name)) { - // If any child binding pattern element has been marked visible (usually by collect linked aliases), then this is visible - return some(elem.name.elements, getBindingNameVisible); + } + if (isSetAccessorDeclaration(input)) { + let newValueParameter: ParameterDeclaration | undefined; + if (!isPrivate) { + const valueParameter = getSetAccessorValueParameter(input); + if (valueParameter) { + const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); + newValueParameter = ensureParameter(valueParameter, /*modifierMask*/ undefined, accessorType); + } } - else { - return resolver.isDeclarationVisible(elem); + if (!newValueParameter) { + newValueParameter = factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + "value" + ); } + newParams = append(newParams, newValueParameter); } + return factory.createNodeArray(newParams || emptyArray); + } - function updateParamsList(node: Node, params: NodeArray, modifierMask?: ModifierFlags) { - if (hasEffectiveModifier(node, ModifierFlags.Private)) { - return undefined!; // TODO: GH#18217 - } - const newParams = map(params, p => ensureParameter(p, modifierMask)); - if (!newParams) { - return undefined!; // TODO: GH#18217 - } - return factory.createNodeArray(newParams, params.hasTrailingComma); + function ensureTypeParams(node: Node, params: NodeArray | undefined) { + return hasEffectiveModifier(node, ModifierFlags.Private) ? undefined : visitNodes(params, visitDeclarationSubtree); + } + + function isEnclosingDeclaration(node: Node) { + return isSourceFile(node) + || isTypeAliasDeclaration(node) + || isModuleDeclaration(node) + || isClassDeclaration(node) + || isInterfaceDeclaration(node) + || isFunctionLike(node) + || isIndexSignatureDeclaration(node) + || isMappedTypeNode(node); + } + + function checkEntityNameVisibility(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node) { + const visibilityResult = resolver.isEntityNameVisible(entityName, enclosingDeclaration); + handleSymbolAccessibilityError(visibilityResult); + recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForEntityName(entityName)); + } + + function preserveJsDoc(updated: T, original: Node): T { + if (hasJSDocNodes(updated) && hasJSDocNodes(original)) { + updated.jsDoc = original.jsDoc; } + return setCommentRange(updated, getCommentRange(original)); + } - function updateAccessorParamsList(input: AccessorDeclaration, isPrivate: boolean) { - let newParams: ParameterDeclaration[] | undefined; - if (!isPrivate) { - const thisParameter = getThisParameter(input); - if (thisParameter) { - newParams = [ensureParameter(thisParameter)]; + function rewriteModuleSpecifier(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode, input: T | undefined): T | StringLiteral { + if (!input) return undefined!; // TODO: GH#18217 + resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || (parent.kind !== SyntaxKind.ModuleDeclaration && parent.kind !== SyntaxKind.ImportType); + if (isStringLiteralLike(input)) { + if (isBundledEmit) { + const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent); + if (newName) { + return factory.createStringLiteral(newName); } } - if (isSetAccessorDeclaration(input)) { - let newValueParameter: ParameterDeclaration | undefined; - if (!isPrivate) { - const valueParameter = getSetAccessorValueParameter(input); - if (valueParameter) { - const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); - newValueParameter = ensureParameter(valueParameter, /*modifierMask*/ undefined, accessorType); - } - } - if (!newValueParameter) { - newValueParameter = factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - "value" - ); + else { + const symbol = resolver.getSymbolOfExternalModuleSpecifier(input); + if (symbol) { + (exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol); } - newParams = append(newParams, newValueParameter); } - return factory.createNodeArray(newParams || emptyArray); } + return input; + } - function ensureTypeParams(node: Node, params: NodeArray | undefined) { - return hasEffectiveModifier(node, ModifierFlags.Private) ? undefined : visitNodes(params, visitDeclarationSubtree); + function transformImportEqualsDeclaration(decl: ImportEqualsDeclaration) { + if (!resolver.isDeclarationVisible(decl)) return; + if (decl.moduleReference.kind === SyntaxKind.ExternalModuleReference) { + // Rewrite external module names if necessary + const specifier = getExternalModuleImportEqualsDeclarationExpression(decl); + return factory.updateImportEqualsDeclaration( + decl, + /*decorators*/ undefined, + decl.modifiers, + decl.isTypeOnly, + decl.name, + factory.updateExternalModuleReference(decl.moduleReference, rewriteModuleSpecifier(decl, specifier)) + ); } - - function isEnclosingDeclaration(node: Node) { - return isSourceFile(node) - || isTypeAliasDeclaration(node) - || isModuleDeclaration(node) - || isClassDeclaration(node) - || isInterfaceDeclaration(node) - || isFunctionLike(node) - || isIndexSignatureDeclaration(node) - || isMappedTypeNode(node); + else { + const oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(decl); + checkEntityNameVisibility(decl.moduleReference, enclosingDeclaration); + getSymbolAccessibilityDiagnostic = oldDiag; + return decl; } + } - function checkEntityNameVisibility(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node) { - const visibilityResult = resolver.isEntityNameVisible(entityName, enclosingDeclaration); - handleSymbolAccessibilityError(visibilityResult); - recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForEntityName(entityName)); + function transformImportDeclaration(decl: ImportDeclaration) { + if (!decl.importClause) { + // import "mod" - possibly needed for side effects? (global interface patches, module augmentations, etc) + return factory.updateImportDeclaration( + decl, + /*decorators*/ undefined, + decl.modifiers, + decl.importClause, + rewriteModuleSpecifier(decl, decl.moduleSpecifier), + /*assertClause*/ undefined + ); + } + // The `importClause` visibility corresponds to the default's visibility. + const visibleDefaultBinding = decl.importClause && decl.importClause.name && resolver.isDeclarationVisible(decl.importClause) ? decl.importClause.name : undefined; + if (!decl.importClause.namedBindings) { + // No named bindings (either namespace or list), meaning the import is just default or should be elided + return visibleDefaultBinding && factory.updateImportDeclaration(decl, /*decorators*/ undefined, decl.modifiers, factory.updateImportClause( + decl.importClause, + decl.importClause.isTypeOnly, + visibleDefaultBinding, + /*namedBindings*/ undefined, + ), rewriteModuleSpecifier(decl, decl.moduleSpecifier), /*assertClause*/ undefined); + } + if (decl.importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + // Namespace import (optionally with visible default) + const namedBindings = resolver.isDeclarationVisible(decl.importClause.namedBindings) ? decl.importClause.namedBindings : /*namedBindings*/ undefined; + return visibleDefaultBinding || namedBindings ? factory.updateImportDeclaration(decl, /*decorators*/ undefined, decl.modifiers, factory.updateImportClause( + decl.importClause, + decl.importClause.isTypeOnly, + visibleDefaultBinding, + namedBindings, + ), rewriteModuleSpecifier(decl, decl.moduleSpecifier), /*assertClause*/ undefined) : undefined; + } + // Named imports (optionally with visible default) + const bindingList = mapDefined(decl.importClause.namedBindings.elements, b => resolver.isDeclarationVisible(b) ? b : undefined); + if ((bindingList && bindingList.length) || visibleDefaultBinding) { + return factory.updateImportDeclaration( + decl, + /*decorators*/ undefined, + decl.modifiers, + factory.updateImportClause( + decl.importClause, + decl.importClause.isTypeOnly, + visibleDefaultBinding, + bindingList && bindingList.length ? factory.updateNamedImports(decl.importClause.namedBindings, bindingList) : undefined, + ), + rewriteModuleSpecifier(decl, decl.moduleSpecifier), + /*assertClause*/ undefined + ); + } + // Augmentation of export depends on import + if (resolver.isImportRequiredByAugmentation(decl)) { + return factory.updateImportDeclaration( + decl, + /*decorators*/ undefined, + decl.modifiers, + /*importClause*/ undefined, + rewriteModuleSpecifier(decl, decl.moduleSpecifier), + /*assertClause*/ undefined + ); } + // Nothing visible + } - function preserveJsDoc(updated: T, original: Node): T { - if (hasJSDocNodes(updated) && hasJSDocNodes(original)) { - updated.jsDoc = original.jsDoc; - } - return setCommentRange(updated, getCommentRange(original)); + function transformAndReplaceLatePaintedStatements(statements: NodeArray): NodeArray { + // This is a `while` loop because `handleSymbolAccessibilityError` can see additional import aliases marked as visible during + // error handling which must now be included in the output and themselves checked for errors. + // For example: + // ``` + // module A { + // export module Q {} + // import B = Q; + // import C = B; + // export import D = C; + // } + // ``` + // In such a scenario, only Q and D are initially visible, but we don't consider imports as private names - instead we say they if they are referenced they must + // be recorded. So while checking D's visibility we mark C as visible, then we must check C which in turn marks B, completing the chain of + // dependent imports and allowing a valid declaration file output. Today, this dependent alias marking only happens for internal import aliases. + while (length(lateMarkedStatements)) { + const i = lateMarkedStatements!.shift()!; + if (!isLateVisibilityPaintedStatement(i)) { + return Debug.fail(`Late replaced statement was found which is not handled by the declaration transformer!: ${(ts as any).SyntaxKind ? (ts as any).SyntaxKind[(i as any).kind] : (i as any).kind}`); + } + const priorNeedsDeclare = needsDeclare; + needsDeclare = i.parent && isSourceFile(i.parent) && !(isExternalModule(i.parent) && isBundledEmit); + const result = transformTopLevelDeclaration(i); + needsDeclare = priorNeedsDeclare; + lateStatementReplacementMap.set(getOriginalNodeId(i), result); } - function rewriteModuleSpecifier(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode, input: T | undefined): T | StringLiteral { - if (!input) return undefined!; // TODO: GH#18217 - resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || (parent.kind !== SyntaxKind.ModuleDeclaration && parent.kind !== SyntaxKind.ImportType); - if (isStringLiteralLike(input)) { - if (isBundledEmit) { - const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent); - if (newName) { - return factory.createStringLiteral(newName); - } - } - else { - const symbol = resolver.getSymbolOfExternalModuleSpecifier(input); - if (symbol) { - (exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol); + // And lastly, we need to get the final form of all those indetermine import declarations from before and add them to the output list + // (and remove them from the set to examine for outter declarations) + return visitNodes(statements, visitLateVisibilityMarkedStatements); + + function visitLateVisibilityMarkedStatements(statement: Statement) { + if (isLateVisibilityPaintedStatement(statement)) { + const key = getOriginalNodeId(statement); + if (lateStatementReplacementMap.has(key)) { + const result = lateStatementReplacementMap.get(key); + lateStatementReplacementMap.delete(key); + if (result) { + if (isArray(result) ? some(result, needsScopeMarker) : needsScopeMarker(result)) { + // Top-level declarations in .d.ts files are always considered exported even without a modifier unless there's an export assignment or specifier + needsScopeFixMarker = true; + } + if (isSourceFile(statement.parent) && (isArray(result) ? some(result, isExternalModuleIndicator) : isExternalModuleIndicator(result))) { + resultHasExternalModuleIndicator = true; + } } + return result; } } - return input; + return statement; } + } - function transformImportEqualsDeclaration(decl: ImportEqualsDeclaration) { - if (!resolver.isDeclarationVisible(decl)) return; - if (decl.moduleReference.kind === SyntaxKind.ExternalModuleReference) { - // Rewrite external module names if necessary - const specifier = getExternalModuleImportEqualsDeclarationExpression(decl); - return factory.updateImportEqualsDeclaration( - decl, - /*decorators*/ undefined, - decl.modifiers, - decl.isTypeOnly, - decl.name, - factory.updateExternalModuleReference(decl.moduleReference, rewriteModuleSpecifier(decl, specifier)) - ); - } - else { - const oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(decl); - checkEntityNameVisibility(decl.moduleReference, enclosingDeclaration); - getSymbolAccessibilityDiagnostic = oldDiag; - return decl; + function visitDeclarationSubtree(input: Node): VisitResult { + if (shouldStripInternal(input)) return; + if (isDeclaration(input)) { + if (isDeclarationAndNotVisible(input)) return; + if (hasDynamicName(input) && !resolver.isLateBound(getParseTreeNode(input) as Declaration)) { + return; } } - function transformImportDeclaration(decl: ImportDeclaration) { - if (!decl.importClause) { - // import "mod" - possibly needed for side effects? (global interface patches, module augmentations, etc) - return factory.updateImportDeclaration( - decl, - /*decorators*/ undefined, - decl.modifiers, - decl.importClause, - rewriteModuleSpecifier(decl, decl.moduleSpecifier), - /*assertClause*/ undefined - ); - } - // The `importClause` visibility corresponds to the default's visibility. - const visibleDefaultBinding = decl.importClause && decl.importClause.name && resolver.isDeclarationVisible(decl.importClause) ? decl.importClause.name : undefined; - if (!decl.importClause.namedBindings) { - // No named bindings (either namespace or list), meaning the import is just default or should be elided - return visibleDefaultBinding && factory.updateImportDeclaration(decl, /*decorators*/ undefined, decl.modifiers, factory.updateImportClause( - decl.importClause, - decl.importClause.isTypeOnly, - visibleDefaultBinding, - /*namedBindings*/ undefined, - ), rewriteModuleSpecifier(decl, decl.moduleSpecifier), /*assertClause*/ undefined); - } - if (decl.importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - // Namespace import (optionally with visible default) - const namedBindings = resolver.isDeclarationVisible(decl.importClause.namedBindings) ? decl.importClause.namedBindings : /*namedBindings*/ undefined; - return visibleDefaultBinding || namedBindings ? factory.updateImportDeclaration(decl, /*decorators*/ undefined, decl.modifiers, factory.updateImportClause( - decl.importClause, - decl.importClause.isTypeOnly, - visibleDefaultBinding, - namedBindings, - ), rewriteModuleSpecifier(decl, decl.moduleSpecifier), /*assertClause*/ undefined) : undefined; - } - // Named imports (optionally with visible default) - const bindingList = mapDefined(decl.importClause.namedBindings.elements, b => resolver.isDeclarationVisible(b) ? b : undefined); - if ((bindingList && bindingList.length) || visibleDefaultBinding) { - return factory.updateImportDeclaration( - decl, - /*decorators*/ undefined, - decl.modifiers, - factory.updateImportClause( - decl.importClause, - decl.importClause.isTypeOnly, - visibleDefaultBinding, - bindingList && bindingList.length ? factory.updateNamedImports(decl.importClause.namedBindings, bindingList) : undefined, - ), - rewriteModuleSpecifier(decl, decl.moduleSpecifier), - /*assertClause*/ undefined - ); - } - // Augmentation of export depends on import - if (resolver.isImportRequiredByAugmentation(decl)) { - return factory.updateImportDeclaration( - decl, - /*decorators*/ undefined, - decl.modifiers, - /*importClause*/ undefined, - rewriteModuleSpecifier(decl, decl.moduleSpecifier), - /*assertClause*/ undefined - ); - } - // Nothing visible - } - - function transformAndReplaceLatePaintedStatements(statements: NodeArray): NodeArray { - // This is a `while` loop because `handleSymbolAccessibilityError` can see additional import aliases marked as visible during - // error handling which must now be included in the output and themselves checked for errors. - // For example: - // ``` - // module A { - // export module Q {} - // import B = Q; - // import C = B; - // export import D = C; - // } - // ``` - // In such a scenario, only Q and D are initially visible, but we don't consider imports as private names - instead we say they if they are referenced they must - // be recorded. So while checking D's visibility we mark C as visible, then we must check C which in turn marks B, completing the chain of - // dependent imports and allowing a valid declaration file output. Today, this dependent alias marking only happens for internal import aliases. - while (length(lateMarkedStatements)) { - const i = lateMarkedStatements!.shift()!; - if (!isLateVisibilityPaintedStatement(i)) { - return Debug.fail(`Late replaced statement was found which is not handled by the declaration transformer!: ${(ts as any).SyntaxKind ? (ts as any).SyntaxKind[(i as any).kind] : (i as any).kind}`); - } - const priorNeedsDeclare = needsDeclare; - needsDeclare = i.parent && isSourceFile(i.parent) && !(isExternalModule(i.parent) && isBundledEmit); - const result = transformTopLevelDeclaration(i); - needsDeclare = priorNeedsDeclare; - lateStatementReplacementMap.set(getOriginalNodeId(i), result); - } + // Elide implementation signatures from overload sets + if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) return; - // And lastly, we need to get the final form of all those indetermine import declarations from before and add them to the output list - // (and remove them from the set to examine for outter declarations) - return visitNodes(statements, visitLateVisibilityMarkedStatements); - - function visitLateVisibilityMarkedStatements(statement: Statement) { - if (isLateVisibilityPaintedStatement(statement)) { - const key = getOriginalNodeId(statement); - if (lateStatementReplacementMap.has(key)) { - const result = lateStatementReplacementMap.get(key); - lateStatementReplacementMap.delete(key); - if (result) { - if (isArray(result) ? some(result, needsScopeMarker) : needsScopeMarker(result)) { - // Top-level declarations in .d.ts files are always considered exported even without a modifier unless there's an export assignment or specifier - needsScopeFixMarker = true; - } - if (isSourceFile(statement.parent) && (isArray(result) ? some(result, isExternalModuleIndicator) : isExternalModuleIndicator(result))) { - resultHasExternalModuleIndicator = true; - } - } - return result; - } - } - return statement; - } - } + // Elide semicolon class statements + if (isSemicolonClassElement(input)) return; - function visitDeclarationSubtree(input: Node): VisitResult { - if (shouldStripInternal(input)) return; - if (isDeclaration(input)) { - if (isDeclarationAndNotVisible(input)) return; - if (hasDynamicName(input) && !resolver.isLateBound(getParseTreeNode(input) as Declaration)) { - return; - } - } - - // Elide implementation signatures from overload sets - if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) return; + let previousEnclosingDeclaration: typeof enclosingDeclaration; + if (isEnclosingDeclaration(input)) { + previousEnclosingDeclaration = enclosingDeclaration; + enclosingDeclaration = input as Declaration; + } + const oldDiag = getSymbolAccessibilityDiagnostic; - // Elide semicolon class statements - if (isSemicolonClassElement(input)) return; + // Setup diagnostic-related flags before first potential `cleanup` call, otherwise + // We'd see a TDZ violation at runtime + const canProduceDiagnostic = canProduceDiagnostics(input); + const oldWithinObjectLiteralType = suppressNewDiagnosticContexts; + let shouldEnterSuppressNewDiagnosticsContextContext = ((input.kind === SyntaxKind.TypeLiteral || input.kind === SyntaxKind.MappedType) && input.parent.kind !== SyntaxKind.TypeAliasDeclaration); - let previousEnclosingDeclaration: typeof enclosingDeclaration; - if (isEnclosingDeclaration(input)) { - previousEnclosingDeclaration = enclosingDeclaration; - enclosingDeclaration = input as Declaration; - } - const oldDiag = getSymbolAccessibilityDiagnostic; - - // Setup diagnostic-related flags before first potential `cleanup` call, otherwise - // We'd see a TDZ violation at runtime - const canProduceDiagnostic = canProduceDiagnostics(input); - const oldWithinObjectLiteralType = suppressNewDiagnosticContexts; - let shouldEnterSuppressNewDiagnosticsContextContext = ((input.kind === SyntaxKind.TypeLiteral || input.kind === SyntaxKind.MappedType) && input.parent.kind !== SyntaxKind.TypeAliasDeclaration); - - // Emit methods which are private as properties with no type information - if (isMethodDeclaration(input) || isMethodSignature(input)) { - if (hasEffectiveModifier(input, ModifierFlags.Private)) { - if (input.symbol && input.symbol.declarations && input.symbol.declarations[0] !== input) return; // Elide all but the first overload - return cleanup(factory.createPropertyDeclaration(/*decorators*/ undefined, ensureModifiers(input), input.name, /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)); - } + // Emit methods which are private as properties with no type information + if (isMethodDeclaration(input) || isMethodSignature(input)) { + if (hasEffectiveModifier(input, ModifierFlags.Private)) { + if (input.symbol && input.symbol.declarations && input.symbol.declarations[0] !== input) return; // Elide all but the first overload + return cleanup(factory.createPropertyDeclaration(/*decorators*/ undefined, ensureModifiers(input), input.name, /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)); } + } - if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input as DeclarationDiagnosticProducing); - } + if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input as DeclarationDiagnosticProducing); + } - if (isTypeQueryNode(input)) { - checkEntityNameVisibility(input.exprName, enclosingDeclaration); - } + if (isTypeQueryNode(input)) { + checkEntityNameVisibility(input.exprName, enclosingDeclaration); + } - if (shouldEnterSuppressNewDiagnosticsContextContext) { - // We stop making new diagnostic contexts within object literal types. Unless it's an object type on the RHS of a type alias declaration. Then we do. - suppressNewDiagnosticContexts = true; - } + if (shouldEnterSuppressNewDiagnosticsContextContext) { + // We stop making new diagnostic contexts within object literal types. Unless it's an object type on the RHS of a type alias declaration. Then we do. + suppressNewDiagnosticContexts = true; + } - if (isProcessedComponent(input)) { - switch (input.kind) { - case SyntaxKind.ExpressionWithTypeArguments: { - if ((isEntityName(input.expression) || isEntityNameExpression(input.expression))) { - checkEntityNameVisibility(input.expression, enclosingDeclaration); - } - const node = visitEachChild(input, visitDeclarationSubtree, context); - return cleanup(factory.updateExpressionWithTypeArguments(node, node.expression, node.typeArguments)); - } - case SyntaxKind.TypeReference: { - checkEntityNameVisibility(input.typeName, enclosingDeclaration); - const node = visitEachChild(input, visitDeclarationSubtree, context); - return cleanup(factory.updateTypeReferenceNode(node, node.typeName, node.typeArguments)); - } - case SyntaxKind.ConstructSignature: - return cleanup(factory.updateConstructSignature( - input, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type) - )); - case SyntaxKind.Constructor: { - // A constructor declaration may not have a type annotation - const ctor = factory.createConstructorDeclaration( - /*decorators*/ undefined, - /*modifiers*/ ensureModifiers(input), - updateParamsList(input, input.parameters, ModifierFlags.None), - /*body*/ undefined - ); - return cleanup(ctor); - } - case SyntaxKind.MethodDeclaration: { - if (isPrivateIdentifier(input.name)) { - return cleanup(/*returnValue*/ undefined); - } - const sig = factory.createMethodDeclaration( - /*decorators*/ undefined, - ensureModifiers(input), - /*asteriskToken*/ undefined, - input.name, - input.questionToken, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type), - /*body*/ undefined - ); - return cleanup(sig); - } - case SyntaxKind.GetAccessor: { - if (isPrivateIdentifier(input.name)) { - return cleanup(/*returnValue*/ undefined); - } - const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); - return cleanup(factory.updateGetAccessorDeclaration( - input, - /*decorators*/ undefined, - ensureModifiers(input), - input.name, - updateAccessorParamsList(input, hasEffectiveModifier(input, ModifierFlags.Private)), - ensureType(input, accessorType), - /*body*/ undefined)); - } - case SyntaxKind.SetAccessor: { - if (isPrivateIdentifier(input.name)) { - return cleanup(/*returnValue*/ undefined); - } - return cleanup(factory.updateSetAccessorDeclaration( - input, - /*decorators*/ undefined, - ensureModifiers(input), - input.name, - updateAccessorParamsList(input, hasEffectiveModifier(input, ModifierFlags.Private)), - /*body*/ undefined)); - } - case SyntaxKind.PropertyDeclaration: - if (isPrivateIdentifier(input.name)) { - return cleanup(/*returnValue*/ undefined); - } - return cleanup(factory.updatePropertyDeclaration( - input, - /*decorators*/ undefined, - ensureModifiers(input), - input.name, - input.questionToken, - ensureType(input, input.type), - ensureNoInitializer(input) - )); - case SyntaxKind.PropertySignature: - if (isPrivateIdentifier(input.name)) { - return cleanup(/*returnValue*/ undefined); - } - return cleanup(factory.updatePropertySignature( - input, - ensureModifiers(input), - input.name, - input.questionToken, - ensureType(input, input.type) - )); - case SyntaxKind.MethodSignature: { - if (isPrivateIdentifier(input.name)) { - return cleanup(/*returnValue*/ undefined); - } - return cleanup(factory.updateMethodSignature( - input, - ensureModifiers(input), - input.name, - input.questionToken, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type) - )); - } - case SyntaxKind.CallSignature: { - return cleanup(factory.updateCallSignature( - input, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type) - )); + if (isProcessedComponent(input)) { + switch (input.kind) { + case SyntaxKind.ExpressionWithTypeArguments: { + if ((isEntityName(input.expression) || isEntityNameExpression(input.expression))) { + checkEntityNameVisibility(input.expression, enclosingDeclaration); } - case SyntaxKind.IndexSignature: { - return cleanup(factory.updateIndexSignature( - input, - /*decorators*/ undefined, - ensureModifiers(input), - updateParamsList(input, input.parameters), - visitNode(input.type, visitDeclarationSubtree) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - )); + const node = visitEachChild(input, visitDeclarationSubtree, context); + return cleanup(factory.updateExpressionWithTypeArguments(node, node.expression, node.typeArguments)); + } + case SyntaxKind.TypeReference: { + checkEntityNameVisibility(input.typeName, enclosingDeclaration); + const node = visitEachChild(input, visitDeclarationSubtree, context); + return cleanup(factory.updateTypeReferenceNode(node, node.typeName, node.typeArguments)); + } + case SyntaxKind.ConstructSignature: + return cleanup(factory.updateConstructSignature( + input, + ensureTypeParams(input, input.typeParameters), + updateParamsList(input, input.parameters), + ensureType(input, input.type) + )); + case SyntaxKind.Constructor: { + // A constructor declaration may not have a type annotation + const ctor = factory.createConstructorDeclaration( + /*decorators*/ undefined, + /*modifiers*/ ensureModifiers(input), + updateParamsList(input, input.parameters, ModifierFlags.None), + /*body*/ undefined + ); + return cleanup(ctor); + } + case SyntaxKind.MethodDeclaration: { + if (isPrivateIdentifier(input.name)) { + return cleanup(/*returnValue*/ undefined); } - case SyntaxKind.VariableDeclaration: { - if (isBindingPattern(input.name)) { - return recreateBindingPattern(input.name); - } - shouldEnterSuppressNewDiagnosticsContextContext = true; - suppressNewDiagnosticContexts = true; // Variable declaration types also suppress new diagnostic contexts, provided the contexts wouldn't be made for binding pattern types - return cleanup(factory.updateVariableDeclaration(input, input.name, /*exclamationToken*/ undefined, ensureType(input, input.type), ensureNoInitializer(input))); + const sig = factory.createMethodDeclaration( + /*decorators*/ undefined, + ensureModifiers(input), + /*asteriskToken*/ undefined, + input.name, + input.questionToken, + ensureTypeParams(input, input.typeParameters), + updateParamsList(input, input.parameters), + ensureType(input, input.type), + /*body*/ undefined + ); + return cleanup(sig); + } + case SyntaxKind.GetAccessor: { + if (isPrivateIdentifier(input.name)) { + return cleanup(/*returnValue*/ undefined); } - case SyntaxKind.TypeParameter: { - if (isPrivateMethodTypeParameter(input) && (input.default || input.constraint)) { - return cleanup(factory.updateTypeParameterDeclaration(input, input.name, /*constraint*/ undefined, /*defaultType*/ undefined)); - } - return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); + const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); + return cleanup(factory.updateGetAccessorDeclaration( + input, + /*decorators*/ undefined, + ensureModifiers(input), + input.name, + updateAccessorParamsList(input, hasEffectiveModifier(input, ModifierFlags.Private)), + ensureType(input, accessorType), + /*body*/ undefined)); + } + case SyntaxKind.SetAccessor: { + if (isPrivateIdentifier(input.name)) { + return cleanup(/*returnValue*/ undefined); } - case SyntaxKind.ConditionalType: { - // We have to process conditional types in a special way because for visibility purposes we need to push a new enclosingDeclaration - // just for the `infer` types in the true branch. It's an implicit declaration scope that only applies to _part_ of the type. - const checkType = visitNode(input.checkType, visitDeclarationSubtree); - const extendsType = visitNode(input.extendsType, visitDeclarationSubtree); - const oldEnclosingDecl = enclosingDeclaration; - enclosingDeclaration = input.trueType; - const trueType = visitNode(input.trueType, visitDeclarationSubtree); - enclosingDeclaration = oldEnclosingDecl; - const falseType = visitNode(input.falseType, visitDeclarationSubtree); - return cleanup(factory.updateConditionalTypeNode(input, checkType, extendsType, trueType, falseType)); + return cleanup(factory.updateSetAccessorDeclaration( + input, + /*decorators*/ undefined, + ensureModifiers(input), + input.name, + updateAccessorParamsList(input, hasEffectiveModifier(input, ModifierFlags.Private)), + /*body*/ undefined)); + } + case SyntaxKind.PropertyDeclaration: + if (isPrivateIdentifier(input.name)) { + return cleanup(/*returnValue*/ undefined); } - case SyntaxKind.FunctionType: { - return cleanup(factory.updateFunctionTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree))); + return cleanup(factory.updatePropertyDeclaration( + input, + /*decorators*/ undefined, + ensureModifiers(input), + input.name, + input.questionToken, + ensureType(input, input.type), + ensureNoInitializer(input) + )); + case SyntaxKind.PropertySignature: + if (isPrivateIdentifier(input.name)) { + return cleanup(/*returnValue*/ undefined); } - case SyntaxKind.ConstructorType: { - return cleanup(factory.updateConstructorTypeNode(input, ensureModifiers(input), visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree))); + return cleanup(factory.updatePropertySignature( + input, + ensureModifiers(input), + input.name, + input.questionToken, + ensureType(input, input.type) + )); + case SyntaxKind.MethodSignature: { + if (isPrivateIdentifier(input.name)) { + return cleanup(/*returnValue*/ undefined); } - case SyntaxKind.ImportType: { - if (!isLiteralImportTypeNode(input)) return cleanup(input); - return cleanup(factory.updateImportTypeNode( - input, - factory.updateLiteralTypeNode(input.argument, rewriteModuleSpecifier(input, input.argument.literal)), - input.qualifier, - visitNodes(input.typeArguments, visitDeclarationSubtree, isTypeNode), - input.isTypeOf - )); + return cleanup(factory.updateMethodSignature( + input, + ensureModifiers(input), + input.name, + input.questionToken, + ensureTypeParams(input, input.typeParameters), + updateParamsList(input, input.parameters), + ensureType(input, input.type) + )); + } + case SyntaxKind.CallSignature: { + return cleanup(factory.updateCallSignature( + input, + ensureTypeParams(input, input.typeParameters), + updateParamsList(input, input.parameters), + ensureType(input, input.type) + )); + } + case SyntaxKind.IndexSignature: { + return cleanup(factory.updateIndexSignature( + input, + /*decorators*/ undefined, + ensureModifiers(input), + updateParamsList(input, input.parameters), + visitNode(input.type, visitDeclarationSubtree) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + )); + } + case SyntaxKind.VariableDeclaration: { + if (isBindingPattern(input.name)) { + return recreateBindingPattern(input.name); } - default: Debug.assertNever(input, `Attempted to process unhandled node kind: ${(ts as any).SyntaxKind[(input as any).kind]}`); + shouldEnterSuppressNewDiagnosticsContextContext = true; + suppressNewDiagnosticContexts = true; // Variable declaration types also suppress new diagnostic contexts, provided the contexts wouldn't be made for binding pattern types + return cleanup(factory.updateVariableDeclaration(input, input.name, /*exclamationToken*/ undefined, ensureType(input, input.type), ensureNoInitializer(input))); } - } - - if (isTupleTypeNode(input) && (getLineAndCharacterOfPosition(currentSourceFile, input.pos).line === getLineAndCharacterOfPosition(currentSourceFile, input.end).line)) { - setEmitFlags(input, EmitFlags.SingleLine); - } - - return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); - - function cleanup(returnValue: T | undefined): T | undefined { - if (returnValue && canProduceDiagnostic && hasDynamicName(input as Declaration)) { - checkName(input as DeclarationDiagnosticProducing); + case SyntaxKind.TypeParameter: { + if (isPrivateMethodTypeParameter(input) && (input.default || input.constraint)) { + return cleanup(factory.updateTypeParameterDeclaration(input, input.name, /*constraint*/ undefined, /*defaultType*/ undefined)); + } + return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); } - if (isEnclosingDeclaration(input)) { - enclosingDeclaration = previousEnclosingDeclaration; + case SyntaxKind.ConditionalType: { + // We have to process conditional types in a special way because for visibility purposes we need to push a new enclosingDeclaration + // just for the `infer` types in the true branch. It's an implicit declaration scope that only applies to _part_ of the type. + const checkType = visitNode(input.checkType, visitDeclarationSubtree); + const extendsType = visitNode(input.extendsType, visitDeclarationSubtree); + const oldEnclosingDecl = enclosingDeclaration; + enclosingDeclaration = input.trueType; + const trueType = visitNode(input.trueType, visitDeclarationSubtree); + enclosingDeclaration = oldEnclosingDecl; + const falseType = visitNode(input.falseType, visitDeclarationSubtree); + return cleanup(factory.updateConditionalTypeNode(input, checkType, extendsType, trueType, falseType)); } - if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag; + case SyntaxKind.FunctionType: { + return cleanup(factory.updateFunctionTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree))); } - if (shouldEnterSuppressNewDiagnosticsContextContext) { - suppressNewDiagnosticContexts = oldWithinObjectLiteralType; + case SyntaxKind.ConstructorType: { + return cleanup(factory.updateConstructorTypeNode(input, ensureModifiers(input), visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree))); } - if (returnValue === input) { - return returnValue; + case SyntaxKind.ImportType: { + if (!isLiteralImportTypeNode(input)) return cleanup(input); + return cleanup(factory.updateImportTypeNode( + input, + factory.updateLiteralTypeNode(input.argument, rewriteModuleSpecifier(input, input.argument.literal)), + input.qualifier, + visitNodes(input.typeArguments, visitDeclarationSubtree, isTypeNode), + input.isTypeOf + )); } - return returnValue && setOriginalNode(preserveJsDoc(returnValue, input), input); + default: Debug.assertNever(input, `Attempted to process unhandled node kind: ${(ts as any).SyntaxKind[(input as any).kind]}`); } } - function isPrivateMethodTypeParameter(node: TypeParameterDeclaration) { - return node.parent.kind === SyntaxKind.MethodDeclaration && hasEffectiveModifier(node.parent, ModifierFlags.Private); + if (isTupleTypeNode(input) && (getLineAndCharacterOfPosition(currentSourceFile, input.pos).line === getLineAndCharacterOfPosition(currentSourceFile, input.end).line)) { + setEmitFlags(input, EmitFlags.SingleLine); } - function visitDeclarationStatements(input: Node): VisitResult { - if (!isPreservedDeclarationStatement(input)) { - // return undefined for unmatched kinds to omit them from the tree - return; - } - if (shouldStripInternal(input)) return; + return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); - switch (input.kind) { - case SyntaxKind.ExportDeclaration: { - if (isSourceFile(input.parent)) { - resultHasExternalModuleIndicator = true; - } - resultHasScopeMarker = true; - // Always visible if the parent node isn't dropped for being not visible - // Rewrite external module names if necessary - return factory.updateExportDeclaration( - input, - /*decorators*/ undefined, - input.modifiers, - input.isTypeOnly, - input.exportClause, - rewriteModuleSpecifier(input, input.moduleSpecifier), - /*assertClause*/ undefined - ); - } - case SyntaxKind.ExportAssignment: { - // Always visible if the parent node isn't dropped for being not visible - if (isSourceFile(input.parent)) { - resultHasExternalModuleIndicator = true; - } - resultHasScopeMarker = true; - if (input.expression.kind === SyntaxKind.Identifier) { - return input; - } - else { - const newId = factory.createUniqueName("_default", GeneratedIdentifierFlags.Optimistic); - getSymbolAccessibilityDiagnostic = () => ({ - diagnosticMessage: Diagnostics.Default_export_of_the_module_has_or_is_using_private_name_0, - errorNode: input - }); - errorFallbackNode = input; - const varDecl = factory.createVariableDeclaration(newId, /*exclamationToken*/ undefined, resolver.createTypeOfExpression(input.expression, input, declarationEmitNodeBuilderFlags, symbolTracker), /*initializer*/ undefined); - errorFallbackNode = undefined; - const statement = factory.createVariableStatement(needsDeclare ? [factory.createModifier(SyntaxKind.DeclareKeyword)] : [], factory.createVariableDeclarationList([varDecl], NodeFlags.Const)); - return [statement, factory.updateExportAssignment(input, input.decorators, input.modifiers, newId)]; - } - } + function cleanup(returnValue: T | undefined): T | undefined { + if (returnValue && canProduceDiagnostic && hasDynamicName(input as Declaration)) { + checkName(input as DeclarationDiagnosticProducing); } - - const result = transformTopLevelDeclaration(input); - // Don't actually transform yet; just leave as original node - will be elided/swapped by late pass - lateStatementReplacementMap.set(getOriginalNodeId(input), result); - return input; + if (isEnclosingDeclaration(input)) { + enclosingDeclaration = previousEnclosingDeclaration; + } + if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = oldDiag; + } + if (shouldEnterSuppressNewDiagnosticsContextContext) { + suppressNewDiagnosticContexts = oldWithinObjectLiteralType; + } + if (returnValue === input) { + return returnValue; + } + return returnValue && setOriginalNode(preserveJsDoc(returnValue, input), input); } + } - function stripExportModifiers(statement: Statement): Statement { - if (isImportEqualsDeclaration(statement) || hasEffectiveModifier(statement, ModifierFlags.Default) || !canHaveModifiers(statement)) { - // `export import` statements should remain as-is, as imports are _not_ implicitly exported in an ambient namespace - // Likewise, `export default` classes and the like and just be `default`, so we preserve their `export` modifiers, too - return statement; - } + function isPrivateMethodTypeParameter(node: TypeParameterDeclaration) { + return node.parent.kind === SyntaxKind.MethodDeclaration && hasEffectiveModifier(node.parent, ModifierFlags.Private); + } - const modifiers = factory.createModifiersFromModifierFlags(getEffectiveModifierFlags(statement) & (ModifierFlags.All ^ ModifierFlags.Export)); - return factory.updateModifiers(statement, modifiers); + function visitDeclarationStatements(input: Node): VisitResult { + if (!isPreservedDeclarationStatement(input)) { + // return undefined for unmatched kinds to omit them from the tree + return; } + if (shouldStripInternal(input)) return; - function transformTopLevelDeclaration(input: LateVisibilityPaintedStatement) { - if (shouldStripInternal(input)) return; - switch (input.kind) { - case SyntaxKind.ImportEqualsDeclaration: { - return transformImportEqualsDeclaration(input); + switch (input.kind) { + case SyntaxKind.ExportDeclaration: { + if (isSourceFile(input.parent)) { + resultHasExternalModuleIndicator = true; } - case SyntaxKind.ImportDeclaration: { - return transformImportDeclaration(input); + resultHasScopeMarker = true; + // Always visible if the parent node isn't dropped for being not visible + // Rewrite external module names if necessary + return factory.updateExportDeclaration( + input, + /*decorators*/ undefined, + input.modifiers, + input.isTypeOnly, + input.exportClause, + rewriteModuleSpecifier(input, input.moduleSpecifier), + /*assertClause*/ undefined + ); + } + case SyntaxKind.ExportAssignment: { + // Always visible if the parent node isn't dropped for being not visible + if (isSourceFile(input.parent)) { + resultHasExternalModuleIndicator = true; + } + resultHasScopeMarker = true; + if (input.expression.kind === SyntaxKind.Identifier) { + return input; + } + else { + const newId = factory.createUniqueName("_default", GeneratedIdentifierFlags.Optimistic); + getSymbolAccessibilityDiagnostic = () => ({ + diagnosticMessage: Diagnostics.Default_export_of_the_module_has_or_is_using_private_name_0, + errorNode: input + }); + errorFallbackNode = input; + const varDecl = factory.createVariableDeclaration(newId, /*exclamationToken*/ undefined, resolver.createTypeOfExpression(input.expression, input, declarationEmitNodeBuilderFlags, symbolTracker), /*initializer*/ undefined); + errorFallbackNode = undefined; + const statement = factory.createVariableStatement(needsDeclare ? [factory.createModifier(SyntaxKind.DeclareKeyword)] : [], factory.createVariableDeclarationList([varDecl], NodeFlags.Const)); + return [statement, factory.updateExportAssignment(input, input.decorators, input.modifiers, newId)]; } } - if (isDeclaration(input) && isDeclarationAndNotVisible(input)) return; + } - // Elide implementation signatures from overload sets - if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) return; + const result = transformTopLevelDeclaration(input); + // Don't actually transform yet; just leave as original node - will be elided/swapped by late pass + lateStatementReplacementMap.set(getOriginalNodeId(input), result); + return input; + } - let previousEnclosingDeclaration: typeof enclosingDeclaration; - if (isEnclosingDeclaration(input)) { - previousEnclosingDeclaration = enclosingDeclaration; - enclosingDeclaration = input as Declaration; - } + function stripExportModifiers(statement: Statement): Statement { + if (isImportEqualsDeclaration(statement) || hasEffectiveModifier(statement, ModifierFlags.Default) || !canHaveModifiers(statement)) { + // `export import` statements should remain as-is, as imports are _not_ implicitly exported in an ambient namespace + // Likewise, `export default` classes and the like and just be `default`, so we preserve their `export` modifiers, too + return statement; + } - const canProdiceDiagnostic = canProduceDiagnostics(input); - const oldDiag = getSymbolAccessibilityDiagnostic; - if (canProdiceDiagnostic) { - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input as DeclarationDiagnosticProducing); - } + const modifiers = factory.createModifiersFromModifierFlags(getEffectiveModifierFlags(statement) & (ModifierFlags.All ^ ModifierFlags.Export)); + return factory.updateModifiers(statement, modifiers); + } - const previousNeedsDeclare = needsDeclare; - switch (input.kind) { - case SyntaxKind.TypeAliasDeclaration: // Type aliases get `declare`d if need be (for legacy support), but that's all - return cleanup(factory.updateTypeAliasDeclaration( - input, - /*decorators*/ undefined, - ensureModifiers(input), - input.name, - visitNodes(input.typeParameters, visitDeclarationSubtree, isTypeParameterDeclaration), - visitNode(input.type, visitDeclarationSubtree, isTypeNode) - )); - case SyntaxKind.InterfaceDeclaration: { - return cleanup(factory.updateInterfaceDeclaration( - input, - /*decorators*/ undefined, - ensureModifiers(input), - input.name, - ensureTypeParams(input, input.typeParameters), - transformHeritageClauses(input.heritageClauses), - visitNodes(input.members, visitDeclarationSubtree) - )); - } - case SyntaxKind.FunctionDeclaration: { - // Generators lose their generator-ness, excepting their return type - const clean = cleanup(factory.updateFunctionDeclaration( - input, - /*decorators*/ undefined, - ensureModifiers(input), - /*asteriskToken*/ undefined, - input.name, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type), - /*body*/ undefined - )); - if (clean && resolver.isExpandoFunctionDeclaration(input) && shouldEmitFunctionProperties(input)) { - const props = resolver.getPropertiesOfContainerFunction(input); - // Use parseNodeFactory so it is usable as an enclosing declaration - const fakespace = parseNodeFactory.createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, clean.name || factory.createIdentifier("_default"), factory.createModuleBlock([]), NodeFlags.Namespace); - setParent(fakespace, enclosingDeclaration as SourceFile | NamespaceDeclaration); - fakespace.locals = createSymbolTable(props); - fakespace.symbol = props[0].parent!; - const exportMappings: [Identifier, string][] = []; - let declarations: (VariableStatement | ExportDeclaration)[] = mapDefined(props, p => { - if (!p.valueDeclaration || !isPropertyAccessExpression(p.valueDeclaration)) { - return undefined; // TODO GH#33569: Handle element access expressions that created late bound names (rather than silently omitting them) - } - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p.valueDeclaration); - const type = resolver.createTypeOfDeclaration(p.valueDeclaration, fakespace, declarationEmitNodeBuilderFlags, symbolTracker); - getSymbolAccessibilityDiagnostic = oldDiag; - const nameStr = unescapeLeadingUnderscores(p.escapedName); - const isNonContextualKeywordName = isStringANonContextualKeyword(nameStr); - const name = isNonContextualKeywordName ? factory.getGeneratedNameForNode(p.valueDeclaration) : factory.createIdentifier(nameStr); - if (isNonContextualKeywordName) { - exportMappings.push([name, nameStr]); - } - const varDecl = factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, /*initializer*/ undefined); - return factory.createVariableStatement(isNonContextualKeywordName ? undefined : [factory.createToken(SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([varDecl])); - }); - if (!exportMappings.length) { - declarations = mapDefined(declarations, declaration => factory.updateModifiers(declaration, ModifierFlags.None)); - } - else { - declarations.push(factory.createExportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports(map(exportMappings, ([gen, exp]) => { - return factory.createExportSpecifier(/*isTypeOnly*/ false, gen, exp); - })) - )); - } - const namespaceDecl = factory.createModuleDeclaration(/*decorators*/ undefined, ensureModifiers(input), input.name!, factory.createModuleBlock(declarations), NodeFlags.Namespace); - if (!hasEffectiveModifier(clean, ModifierFlags.Default)) { - return [clean, namespaceDecl]; - } + function transformTopLevelDeclaration(input: LateVisibilityPaintedStatement) { + if (shouldStripInternal(input)) return; + switch (input.kind) { + case SyntaxKind.ImportEqualsDeclaration: { + return transformImportEqualsDeclaration(input); + } + case SyntaxKind.ImportDeclaration: { + return transformImportDeclaration(input); + } + } + if (isDeclaration(input) && isDeclarationAndNotVisible(input)) return; - const modifiers = factory.createModifiersFromModifierFlags((getEffectiveModifierFlags(clean) & ~ModifierFlags.ExportDefault) | ModifierFlags.Ambient); - const cleanDeclaration = factory.updateFunctionDeclaration( - clean, - /*decorators*/ undefined, - modifiers, - /*asteriskToken*/ undefined, - clean.name, - clean.typeParameters, - clean.parameters, - clean.type, - /*body*/ undefined - ); - - const namespaceDeclaration = factory.updateModuleDeclaration( - namespaceDecl, - /*decorators*/ undefined, - modifiers, - namespaceDecl.name, - namespaceDecl.body - ); + // Elide implementation signatures from overload sets + if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) return; - const exportDefaultDeclaration = factory.createExportAssignment( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isExportEquals*/ false, - namespaceDecl.name - ); + let previousEnclosingDeclaration: typeof enclosingDeclaration; + if (isEnclosingDeclaration(input)) { + previousEnclosingDeclaration = enclosingDeclaration; + enclosingDeclaration = input as Declaration; + } - if (isSourceFile(input.parent)) { - resultHasExternalModuleIndicator = true; - } - resultHasScopeMarker = true; + const canProdiceDiagnostic = canProduceDiagnostics(input); + const oldDiag = getSymbolAccessibilityDiagnostic; + if (canProdiceDiagnostic) { + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input as DeclarationDiagnosticProducing); + } - return [cleanDeclaration, namespaceDeclaration, exportDefaultDeclaration]; - } - else { - return clean; - } - } - case SyntaxKind.ModuleDeclaration: { - needsDeclare = false; - const inner = input.body; - if (inner && inner.kind === SyntaxKind.ModuleBlock) { - const oldNeedsScopeFix = needsScopeFixMarker; - const oldHasScopeFix = resultHasScopeMarker; - resultHasScopeMarker = false; - needsScopeFixMarker = false; - const statements = visitNodes(inner.statements, visitDeclarationStatements); - let lateStatements = transformAndReplaceLatePaintedStatements(statements); - if (input.flags & NodeFlags.Ambient) { - needsScopeFixMarker = false; // If it was `declare`'d everything is implicitly exported already, ignore late printed "privates" + const previousNeedsDeclare = needsDeclare; + switch (input.kind) { + case SyntaxKind.TypeAliasDeclaration: // Type aliases get `declare`d if need be (for legacy support), but that's all + return cleanup(factory.updateTypeAliasDeclaration( + input, + /*decorators*/ undefined, + ensureModifiers(input), + input.name, + visitNodes(input.typeParameters, visitDeclarationSubtree, isTypeParameterDeclaration), + visitNode(input.type, visitDeclarationSubtree, isTypeNode) + )); + case SyntaxKind.InterfaceDeclaration: { + return cleanup(factory.updateInterfaceDeclaration( + input, + /*decorators*/ undefined, + ensureModifiers(input), + input.name, + ensureTypeParams(input, input.typeParameters), + transformHeritageClauses(input.heritageClauses), + visitNodes(input.members, visitDeclarationSubtree) + )); + } + case SyntaxKind.FunctionDeclaration: { + // Generators lose their generator-ness, excepting their return type + const clean = cleanup(factory.updateFunctionDeclaration( + input, + /*decorators*/ undefined, + ensureModifiers(input), + /*asteriskToken*/ undefined, + input.name, + ensureTypeParams(input, input.typeParameters), + updateParamsList(input, input.parameters), + ensureType(input, input.type), + /*body*/ undefined + )); + if (clean && resolver.isExpandoFunctionDeclaration(input) && shouldEmitFunctionProperties(input)) { + const props = resolver.getPropertiesOfContainerFunction(input); + // Use parseNodeFactory so it is usable as an enclosing declaration + const fakespace = parseNodeFactory.createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, clean.name || factory.createIdentifier("_default"), factory.createModuleBlock([]), NodeFlags.Namespace); + setParent(fakespace, enclosingDeclaration as SourceFile | NamespaceDeclaration); + fakespace.locals = createSymbolTable(props); + fakespace.symbol = props[0].parent!; + const exportMappings: [Identifier, string][] = []; + let declarations: (VariableStatement | ExportDeclaration)[] = mapDefined(props, p => { + if (!p.valueDeclaration || !isPropertyAccessExpression(p.valueDeclaration)) { + return undefined; // TODO GH#33569: Handle element access expressions that created late bound names (rather than silently omitting them) } - // With the final list of statements, there are 3 possibilities: - // 1. There's an export assignment or export declaration in the namespace - do nothing - // 2. Everything is exported and there are no export assignments or export declarations - strip all export modifiers - // 3. Some things are exported, some are not, and there's no marker - add an empty marker - if (!isGlobalScopeAugmentation(input) && !hasScopeMarker(lateStatements) && !resultHasScopeMarker) { - if (needsScopeFixMarker) { - lateStatements = factory.createNodeArray([...lateStatements, createEmptyExports(factory)]); - } - else { - lateStatements = visitNodes(lateStatements, stripExportModifiers); - } + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p.valueDeclaration); + const type = resolver.createTypeOfDeclaration(p.valueDeclaration, fakespace, declarationEmitNodeBuilderFlags, symbolTracker); + getSymbolAccessibilityDiagnostic = oldDiag; + const nameStr = unescapeLeadingUnderscores(p.escapedName); + const isNonContextualKeywordName = isStringANonContextualKeyword(nameStr); + const name = isNonContextualKeywordName ? factory.getGeneratedNameForNode(p.valueDeclaration) : factory.createIdentifier(nameStr); + if (isNonContextualKeywordName) { + exportMappings.push([name, nameStr]); } - const body = factory.updateModuleBlock(inner, lateStatements); - needsDeclare = previousNeedsDeclare; - needsScopeFixMarker = oldNeedsScopeFix; - resultHasScopeMarker = oldHasScopeFix; - const mods = ensureModifiers(input); - return cleanup(factory.updateModuleDeclaration( - input, - /*decorators*/ undefined, - mods, - isExternalModuleAugmentation(input) ? rewriteModuleSpecifier(input, input.name) : input.name, - body - )); + const varDecl = factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, /*initializer*/ undefined); + return factory.createVariableStatement(isNonContextualKeywordName ? undefined : [factory.createToken(SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([varDecl])); + }); + if (!exportMappings.length) { + declarations = mapDefined(declarations, declaration => factory.updateModifiers(declaration, ModifierFlags.None)); } else { - needsDeclare = previousNeedsDeclare; - const mods = ensureModifiers(input); - needsDeclare = false; - visitNode(inner, visitDeclarationStatements); - // eagerly transform nested namespaces (the nesting doesn't need any elision or painting done) - const id = getOriginalNodeId(inner!); // TODO: GH#18217 - const body = lateStatementReplacementMap.get(id); - lateStatementReplacementMap.delete(id); - return cleanup(factory.updateModuleDeclaration( - input, + declarations.push(factory.createExportDeclaration( /*decorators*/ undefined, - mods, - input.name, - body as ModuleBody + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(map(exportMappings, ([gen, exp]) => { + return factory.createExportSpecifier(/*isTypeOnly*/ false, gen, exp); + })) )); } - } - case SyntaxKind.ClassDeclaration: { - errorNameNode = input.name; - errorFallbackNode = input; - const modifiers = factory.createNodeArray(ensureModifiers(input)); - const typeParameters = ensureTypeParams(input, input.typeParameters); - const ctor = getFirstConstructorWithBody(input); - let parameterProperties: readonly PropertyDeclaration[] | undefined; - if (ctor) { - const oldDiag = getSymbolAccessibilityDiagnostic; - parameterProperties = compact(flatMap(ctor.parameters, (param) => { - if (!hasSyntacticModifier(param, ModifierFlags.ParameterPropertyModifier) || shouldStripInternal(param)) return; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(param); - if (param.name.kind === SyntaxKind.Identifier) { - return preserveJsDoc(factory.createPropertyDeclaration( - /*decorators*/ undefined, - ensureModifiers(param), - param.name, - param.questionToken, - ensureType(param, param.type), - ensureNoInitializer(param)), param); - } - else { - // Pattern - this is currently an error, but we emit declarations for it somewhat correctly - return walkBindingPattern(param.name); - } - - function walkBindingPattern(pattern: BindingPattern) { - let elems: PropertyDeclaration[] | undefined; - for (const elem of pattern.elements) { - if (isOmittedExpression(elem)) continue; - if (isBindingPattern(elem.name)) { - elems = concatenate(elems, walkBindingPattern(elem.name)); - } - elems = elems || []; - elems.push(factory.createPropertyDeclaration( - /*decorators*/ undefined, - ensureModifiers(param), - elem.name as Identifier, - /*questionToken*/ undefined, - ensureType(elem, /*type*/ undefined), - /*initializer*/ undefined - )); - } - return elems; - } - })); - getSymbolAccessibilityDiagnostic = oldDiag; + const namespaceDecl = factory.createModuleDeclaration(/*decorators*/ undefined, ensureModifiers(input), input.name!, factory.createModuleBlock(declarations), NodeFlags.Namespace); + if (!hasEffectiveModifier(clean, ModifierFlags.Default)) { + return [clean, namespaceDecl]; } - const hasPrivateIdentifier = some(input.members, member => !!member.name && isPrivateIdentifier(member.name)); - // When the class has at least one private identifier, create a unique constant identifier to retain the nominal typing behavior - // Prevents other classes with the same public members from being used in place of the current class - const privateIdentifier = hasPrivateIdentifier ? [ - factory.createPropertyDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - factory.createPrivateIdentifier("#private"), - /*questionToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined - ) - ] : undefined; - const memberNodes = concatenate(concatenate(privateIdentifier, parameterProperties), visitNodes(input.members, visitDeclarationSubtree)); - const members = factory.createNodeArray(memberNodes); - - const extendsClause = getEffectiveBaseTypeNode(input); - if (extendsClause && !isEntityNameExpression(extendsClause.expression) && extendsClause.expression.kind !== SyntaxKind.NullKeyword) { - // We must add a temporary declaration for the extends clause expression - - const oldId = input.name ? unescapeLeadingUnderscores(input.name.escapedText) : "default"; - const newId = factory.createUniqueName(`${oldId}_base`, GeneratedIdentifierFlags.Optimistic); - getSymbolAccessibilityDiagnostic = () => ({ - diagnosticMessage: Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1, - errorNode: extendsClause, - typeName: input.name - }); - const varDecl = factory.createVariableDeclaration(newId, /*exclamationToken*/ undefined, resolver.createTypeOfExpression(extendsClause.expression, input, declarationEmitNodeBuilderFlags, symbolTracker), /*initializer*/ undefined); - const statement = factory.createVariableStatement(needsDeclare ? [factory.createModifier(SyntaxKind.DeclareKeyword)] : [], factory.createVariableDeclarationList([varDecl], NodeFlags.Const)); - const heritageClauses = factory.createNodeArray(map(input.heritageClauses, clause => { - if (clause.token === SyntaxKind.ExtendsKeyword) { - const oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(clause.types[0]); - const newClause = factory.updateHeritageClause(clause, map(clause.types, t => factory.updateExpressionWithTypeArguments(t, newId, visitNodes(t.typeArguments, visitDeclarationSubtree)))); - getSymbolAccessibilityDiagnostic = oldDiag; - return newClause; - } - return factory.updateHeritageClause(clause, visitNodes(factory.createNodeArray(filter(clause.types, t => isEntityNameExpression(t.expression) || t.expression.kind === SyntaxKind.NullKeyword)), visitDeclarationSubtree)); - })); - return [statement, cleanup(factory.updateClassDeclaration( - input, - /*decorators*/ undefined, - modifiers, - input.name, - typeParameters, - heritageClauses, - members - ))!]; // TODO: GH#18217 - } - else { - const heritageClauses = transformHeritageClauses(input.heritageClauses); - return cleanup(factory.updateClassDeclaration( - input, - /*decorators*/ undefined, - modifiers, - input.name, - typeParameters, - heritageClauses, - members - )); + const modifiers = factory.createModifiersFromModifierFlags((getEffectiveModifierFlags(clean) & ~ModifierFlags.ExportDefault) | ModifierFlags.Ambient); + const cleanDeclaration = factory.updateFunctionDeclaration( + clean, + /*decorators*/ undefined, + modifiers, + /*asteriskToken*/ undefined, + clean.name, + clean.typeParameters, + clean.parameters, + clean.type, + /*body*/ undefined + ); + + const namespaceDeclaration = factory.updateModuleDeclaration( + namespaceDecl, + /*decorators*/ undefined, + modifiers, + namespaceDecl.name, + namespaceDecl.body + ); + + const exportDefaultDeclaration = factory.createExportAssignment( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isExportEquals*/ false, + namespaceDecl.name + ); + + if (isSourceFile(input.parent)) { + resultHasExternalModuleIndicator = true; } - } - case SyntaxKind.VariableStatement: { - return cleanup(transformVariableStatement(input)); - } - case SyntaxKind.EnumDeclaration: { - return cleanup(factory.updateEnumDeclaration(input, /*decorators*/ undefined, factory.createNodeArray(ensureModifiers(input)), input.name, factory.createNodeArray(mapDefined(input.members, m => { - if (shouldStripInternal(m)) return; - // Rewrite enum values to their constants, if available - const constValue = resolver.getConstantValue(m); - return preserveJsDoc(factory.updateEnumMember(m, m.name, constValue !== undefined ? typeof constValue === "string" ? factory.createStringLiteral(constValue) : factory.createNumericLiteral(constValue) : undefined), m); - })))); - } - } - // Anything left unhandled is an error, so this should be unreachable - return Debug.assertNever(input, `Unhandled top-level node in declaration emit: ${(ts as any).SyntaxKind[(input as any).kind]}`); + resultHasScopeMarker = true; - function cleanup(node: T | undefined): T | undefined { - if (isEnclosingDeclaration(input)) { - enclosingDeclaration = previousEnclosingDeclaration; + return [cleanDeclaration, namespaceDeclaration, exportDefaultDeclaration]; } - if (canProdiceDiagnostic) { - getSymbolAccessibilityDiagnostic = oldDiag; + else { + return clean; } - if (input.kind === SyntaxKind.ModuleDeclaration) { + } + case SyntaxKind.ModuleDeclaration: { + needsDeclare = false; + const inner = input.body; + if (inner && inner.kind === SyntaxKind.ModuleBlock) { + const oldNeedsScopeFix = needsScopeFixMarker; + const oldHasScopeFix = resultHasScopeMarker; + resultHasScopeMarker = false; + needsScopeFixMarker = false; + const statements = visitNodes(inner.statements, visitDeclarationStatements); + let lateStatements = transformAndReplaceLatePaintedStatements(statements); + if (input.flags & NodeFlags.Ambient) { + needsScopeFixMarker = false; // If it was `declare`'d everything is implicitly exported already, ignore late printed "privates" + } + // With the final list of statements, there are 3 possibilities: + // 1. There's an export assignment or export declaration in the namespace - do nothing + // 2. Everything is exported and there are no export assignments or export declarations - strip all export modifiers + // 3. Some things are exported, some are not, and there's no marker - add an empty marker + if (!isGlobalScopeAugmentation(input) && !hasScopeMarker(lateStatements) && !resultHasScopeMarker) { + if (needsScopeFixMarker) { + lateStatements = factory.createNodeArray([...lateStatements, createEmptyExports(factory)]); + } + else { + lateStatements = visitNodes(lateStatements, stripExportModifiers); + } + } + const body = factory.updateModuleBlock(inner, lateStatements); needsDeclare = previousNeedsDeclare; + needsScopeFixMarker = oldNeedsScopeFix; + resultHasScopeMarker = oldHasScopeFix; + const mods = ensureModifiers(input); + return cleanup(factory.updateModuleDeclaration( + input, + /*decorators*/ undefined, + mods, + isExternalModuleAugmentation(input) ? rewriteModuleSpecifier(input, input.name) : input.name, + body + )); } - if (node as Node === input) { - return node; + else { + needsDeclare = previousNeedsDeclare; + const mods = ensureModifiers(input); + needsDeclare = false; + visitNode(inner, visitDeclarationStatements); + // eagerly transform nested namespaces (the nesting doesn't need any elision or painting done) + const id = getOriginalNodeId(inner!); // TODO: GH#18217 + const body = lateStatementReplacementMap.get(id); + lateStatementReplacementMap.delete(id); + return cleanup(factory.updateModuleDeclaration( + input, + /*decorators*/ undefined, + mods, + input.name, + body as ModuleBody + )); } - errorFallbackNode = undefined; - errorNameNode = undefined; - return node && setOriginalNode(preserveJsDoc(node, input), input); } - } - - function transformVariableStatement(input: VariableStatement) { - if (!forEach(input.declarationList.declarations, getBindingNameVisible)) return; - const nodes = visitNodes(input.declarationList.declarations, visitDeclarationSubtree); - if (!length(nodes)) return; - return factory.updateVariableStatement(input, factory.createNodeArray(ensureModifiers(input)), factory.updateVariableDeclarationList(input.declarationList, nodes)); - } + case SyntaxKind.ClassDeclaration: { + errorNameNode = input.name; + errorFallbackNode = input; + const modifiers = factory.createNodeArray(ensureModifiers(input)); + const typeParameters = ensureTypeParams(input, input.typeParameters); + const ctor = getFirstConstructorWithBody(input); + let parameterProperties: readonly PropertyDeclaration[] | undefined; + if (ctor) { + const oldDiag = getSymbolAccessibilityDiagnostic; + parameterProperties = compact(flatMap(ctor.parameters, (param) => { + if (!hasSyntacticModifier(param, ModifierFlags.ParameterPropertyModifier) || shouldStripInternal(param)) return; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(param); + if (param.name.kind === SyntaxKind.Identifier) { + return preserveJsDoc(factory.createPropertyDeclaration( + /*decorators*/ undefined, + ensureModifiers(param), + param.name, + param.questionToken, + ensureType(param, param.type), + ensureNoInitializer(param)), param); + } + else { + // Pattern - this is currently an error, but we emit declarations for it somewhat correctly + return walkBindingPattern(param.name); + } - function recreateBindingPattern(d: BindingPattern): VariableDeclaration[] { - return flatten(mapDefined(d.elements, e => recreateBindingElement(e))); - } + function walkBindingPattern(pattern: BindingPattern) { + let elems: PropertyDeclaration[] | undefined; + for (const elem of pattern.elements) { + if (isOmittedExpression(elem)) continue; + if (isBindingPattern(elem.name)) { + elems = concatenate(elems, walkBindingPattern(elem.name)); + } + elems = elems || []; + elems.push(factory.createPropertyDeclaration( + /*decorators*/ undefined, + ensureModifiers(param), + elem.name as Identifier, + /*questionToken*/ undefined, + ensureType(elem, /*type*/ undefined), + /*initializer*/ undefined + )); + } + return elems; + } + })); + getSymbolAccessibilityDiagnostic = oldDiag; + } - function recreateBindingElement(e: ArrayBindingElement) { - if (e.kind === SyntaxKind.OmittedExpression) { - return; - } - if (e.name) { - if (!getBindingNameVisible(e)) return; - if (isBindingPattern(e.name)) { - return recreateBindingPattern(e.name); + const hasPrivateIdentifier = some(input.members, member => !!member.name && isPrivateIdentifier(member.name)); + // When the class has at least one private identifier, create a unique constant identifier to retain the nominal typing behavior + // Prevents other classes with the same public members from being used in place of the current class + const privateIdentifier = hasPrivateIdentifier ? [ + factory.createPropertyDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createPrivateIdentifier("#private"), + /*questionToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + ) + ] : undefined; + const memberNodes = concatenate(concatenate(privateIdentifier, parameterProperties), visitNodes(input.members, visitDeclarationSubtree)); + const members = factory.createNodeArray(memberNodes); + + const extendsClause = getEffectiveBaseTypeNode(input); + if (extendsClause && !isEntityNameExpression(extendsClause.expression) && extendsClause.expression.kind !== SyntaxKind.NullKeyword) { + // We must add a temporary declaration for the extends clause expression + + const oldId = input.name ? unescapeLeadingUnderscores(input.name.escapedText) : "default"; + const newId = factory.createUniqueName(`${oldId}_base`, GeneratedIdentifierFlags.Optimistic); + getSymbolAccessibilityDiagnostic = () => ({ + diagnosticMessage: Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1, + errorNode: extendsClause, + typeName: input.name + }); + const varDecl = factory.createVariableDeclaration(newId, /*exclamationToken*/ undefined, resolver.createTypeOfExpression(extendsClause.expression, input, declarationEmitNodeBuilderFlags, symbolTracker), /*initializer*/ undefined); + const statement = factory.createVariableStatement(needsDeclare ? [factory.createModifier(SyntaxKind.DeclareKeyword)] : [], factory.createVariableDeclarationList([varDecl], NodeFlags.Const)); + const heritageClauses = factory.createNodeArray(map(input.heritageClauses, clause => { + if (clause.token === SyntaxKind.ExtendsKeyword) { + const oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(clause.types[0]); + const newClause = factory.updateHeritageClause(clause, map(clause.types, t => factory.updateExpressionWithTypeArguments(t, newId, visitNodes(t.typeArguments, visitDeclarationSubtree)))); + getSymbolAccessibilityDiagnostic = oldDiag; + return newClause; + } + return factory.updateHeritageClause(clause, visitNodes(factory.createNodeArray(filter(clause.types, t => isEntityNameExpression(t.expression) || t.expression.kind === SyntaxKind.NullKeyword)), visitDeclarationSubtree)); + })); + return [statement, cleanup(factory.updateClassDeclaration( + input, + /*decorators*/ undefined, + modifiers, + input.name, + typeParameters, + heritageClauses, + members + ))!]; // TODO: GH#18217 } else { - return factory.createVariableDeclaration(e.name, /*exclamationToken*/ undefined, ensureType(e, /*type*/ undefined), /*initializer*/ undefined); + const heritageClauses = transformHeritageClauses(input.heritageClauses); + return cleanup(factory.updateClassDeclaration( + input, + /*decorators*/ undefined, + modifiers, + input.name, + typeParameters, + heritageClauses, + members + )); } } + case SyntaxKind.VariableStatement: { + return cleanup(transformVariableStatement(input)); + } + case SyntaxKind.EnumDeclaration: { + return cleanup(factory.updateEnumDeclaration(input, /*decorators*/ undefined, factory.createNodeArray(ensureModifiers(input)), input.name, factory.createNodeArray(mapDefined(input.members, m => { + if (shouldStripInternal(m)) return; + // Rewrite enum values to their constants, if available + const constValue = resolver.getConstantValue(m); + return preserveJsDoc(factory.updateEnumMember(m, m.name, constValue !== undefined ? typeof constValue === "string" ? factory.createStringLiteral(constValue) : factory.createNumericLiteral(constValue) : undefined), m); + })))); + } } + // Anything left unhandled is an error, so this should be unreachable + return Debug.assertNever(input, `Unhandled top-level node in declaration emit: ${(ts as any).SyntaxKind[(input as any).kind]}`); - function checkName(node: DeclarationDiagnosticProducing) { - let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined; - if (!suppressNewDiagnosticContexts) { - oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNodeName(node); + function cleanup(node: T | undefined): T | undefined { + if (isEnclosingDeclaration(input)) { + enclosingDeclaration = previousEnclosingDeclaration; } - errorNameNode = (node as NamedDeclaration).name; - Debug.assert(resolver.isLateBound(getParseTreeNode(node) as Declaration)); // Should only be called with dynamic names - const decl = node as NamedDeclaration as LateBoundDeclaration; - const entityName = decl.name.expression; - checkEntityNameVisibility(entityName, enclosingDeclaration); - if (!suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag!; + if (canProdiceDiagnostic) { + getSymbolAccessibilityDiagnostic = oldDiag; } + if (input.kind === SyntaxKind.ModuleDeclaration) { + needsDeclare = previousNeedsDeclare; + } + if (node as Node === input) { + return node; + } + errorFallbackNode = undefined; errorNameNode = undefined; + return node && setOriginalNode(preserveJsDoc(node, input), input); } + } - function shouldStripInternal(node: Node) { - return !!stripInternal && !!node && isInternalDeclaration(node, currentSourceFile); - } + function transformVariableStatement(input: VariableStatement) { + if (!forEach(input.declarationList.declarations, getBindingNameVisible)) return; + const nodes = visitNodes(input.declarationList.declarations, visitDeclarationSubtree); + if (!length(nodes)) return; + return factory.updateVariableStatement(input, factory.createNodeArray(ensureModifiers(input)), factory.updateVariableDeclarationList(input.declarationList, nodes)); + } - function isScopeMarker(node: Node) { - return isExportAssignment(node) || isExportDeclaration(node); - } + function recreateBindingPattern(d: BindingPattern): VariableDeclaration[] { + return flatten(mapDefined(d.elements, e => recreateBindingElement(e))); + } - function hasScopeMarker(statements: readonly Statement[]) { - return some(statements, isScopeMarker); + function recreateBindingElement(e: ArrayBindingElement) { + if (e.kind === SyntaxKind.OmittedExpression) { + return; } - - function ensureModifiers(node: Node): readonly Modifier[] | undefined { - const currentFlags = getEffectiveModifierFlags(node); - const newFlags = ensureModifierFlags(node); - if (currentFlags === newFlags) { - return node.modifiers; + if (e.name) { + if (!getBindingNameVisible(e)) return; + if (isBindingPattern(e.name)) { + return recreateBindingPattern(e.name); } - return factory.createModifiersFromModifierFlags(newFlags); - } - - function ensureModifierFlags(node: Node): ModifierFlags { - let mask = ModifierFlags.All ^ (ModifierFlags.Public | ModifierFlags.Async | ModifierFlags.Override); // No async and override modifiers in declaration files - let additions = (needsDeclare && !isAlwaysType(node)) ? ModifierFlags.Ambient : ModifierFlags.None; - const parentIsFile = node.parent.kind === SyntaxKind.SourceFile; - if (!parentIsFile || (isBundledEmit && parentIsFile && isExternalModule(node.parent as SourceFile))) { - mask ^= ModifierFlags.Ambient; - additions = ModifierFlags.None; + else { + return factory.createVariableDeclaration(e.name, /*exclamationToken*/ undefined, ensureType(e, /*type*/ undefined), /*initializer*/ undefined); } - return maskModifierFlags(node, mask, additions); } + } - function getTypeAnnotationFromAllAccessorDeclarations(node: AccessorDeclaration, accessors: AllAccessorDeclarations) { - let accessorType = getTypeAnnotationFromAccessor(node); - if (!accessorType && node !== accessors.firstAccessor) { - accessorType = getTypeAnnotationFromAccessor(accessors.firstAccessor); - // If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.firstAccessor); - } - if (!accessorType && accessors.secondAccessor && node !== accessors.secondAccessor) { - accessorType = getTypeAnnotationFromAccessor(accessors.secondAccessor); - // If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.secondAccessor); - } - return accessorType; + function checkName(node: DeclarationDiagnosticProducing) { + let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined; + if (!suppressNewDiagnosticContexts) { + oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNodeName(node); } - - function transformHeritageClauses(nodes: NodeArray | undefined) { - return factory.createNodeArray(filter(map(nodes, clause => factory.updateHeritageClause(clause, visitNodes(factory.createNodeArray(filter(clause.types, t => { - return isEntityNameExpression(t.expression) || (clause.token === SyntaxKind.ExtendsKeyword && t.expression.kind === SyntaxKind.NullKeyword); - })), visitDeclarationSubtree))), clause => clause.types && !!clause.types.length)); + errorNameNode = (node as NamedDeclaration).name; + Debug.assert(resolver.isLateBound(getParseTreeNode(node) as Declaration)); // Should only be called with dynamic names + const decl = node as NamedDeclaration as LateBoundDeclaration; + const entityName = decl.name.expression; + checkEntityNameVisibility(entityName, enclosingDeclaration); + if (!suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = oldDiag!; } + errorNameNode = undefined; } - function isAlwaysType(node: Node) { - if (node.kind === SyntaxKind.InterfaceDeclaration) { - return true; - } - return false; + function shouldStripInternal(node: Node) { + return !!stripInternal && !!node && isInternalDeclaration(node, currentSourceFile); } - // Elide "public" modifier, as it is the default - function maskModifiers(node: Node, modifierMask?: ModifierFlags, modifierAdditions?: ModifierFlags): Modifier[] { - return factory.createModifiersFromModifierFlags(maskModifierFlags(node, modifierMask, modifierAdditions)); + function isScopeMarker(node: Node) { + return isExportAssignment(node) || isExportDeclaration(node); } - function maskModifierFlags(node: Node, modifierMask: ModifierFlags = ModifierFlags.All ^ ModifierFlags.Public, modifierAdditions: ModifierFlags = ModifierFlags.None): ModifierFlags { - let flags = (getEffectiveModifierFlags(node) & modifierMask) | modifierAdditions; - if (flags & ModifierFlags.Default && !(flags & ModifierFlags.Export)) { - // A non-exported default is a nonsequitor - we usually try to remove all export modifiers - // from statements in ambient declarations; but a default export must retain its export modifier to be syntactically valid - flags ^= ModifierFlags.Export; - } - if (flags & ModifierFlags.Default && flags & ModifierFlags.Ambient) { - flags ^= ModifierFlags.Ambient; // `declare` is never required alongside `default` (and would be an error if printed) - } - return flags; + function hasScopeMarker(statements: readonly Statement[]) { + return some(statements, isScopeMarker); } - function getTypeAnnotationFromAccessor(accessor: AccessorDeclaration): TypeNode | undefined { - if (accessor) { - return accessor.kind === SyntaxKind.GetAccessor - ? accessor.type // Getter - return type - : accessor.parameters.length > 0 - ? accessor.parameters[0].type // Setter parameter type - : undefined; + function ensureModifiers(node: Node): readonly Modifier[] | undefined { + const currentFlags = getEffectiveModifierFlags(node); + const newFlags = ensureModifierFlags(node); + if (currentFlags === newFlags) { + return node.modifiers; } + return factory.createModifiersFromModifierFlags(newFlags); } - type CanHaveLiteralInitializer = VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration; - function canHaveLiteralInitializer(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return !hasEffectiveModifier(node, ModifierFlags.Private); - case SyntaxKind.Parameter: - case SyntaxKind.VariableDeclaration: - return true; + function ensureModifierFlags(node: Node): ModifierFlags { + let mask = ModifierFlags.All ^ (ModifierFlags.Public | ModifierFlags.Async | ModifierFlags.Override); // No async and override modifiers in declaration files + let additions = (needsDeclare && !isAlwaysType(node)) ? ModifierFlags.Ambient : ModifierFlags.None; + const parentIsFile = node.parent.kind === SyntaxKind.SourceFile; + if (!parentIsFile || (isBundledEmit && parentIsFile && isExternalModule(node.parent as SourceFile))) { + mask ^= ModifierFlags.Ambient; + additions = ModifierFlags.None; } - return false; + return maskModifierFlags(node, mask, additions); } - type ProcessedDeclarationStatement = - | FunctionDeclaration - | ModuleDeclaration - | ImportEqualsDeclaration - | InterfaceDeclaration - | ClassDeclaration - | TypeAliasDeclaration - | EnumDeclaration - | VariableStatement - | ImportDeclaration - | ExportDeclaration - | ExportAssignment; - - function isPreservedDeclarationStatement(node: Node): node is ProcessedDeclarationStatement { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.VariableStatement: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExportAssignment: - return true; + function getTypeAnnotationFromAllAccessorDeclarations(node: AccessorDeclaration, accessors: AllAccessorDeclarations) { + let accessorType = getTypeAnnotationFromAccessor(node); + if (!accessorType && node !== accessors.firstAccessor) { + accessorType = getTypeAnnotationFromAccessor(accessors.firstAccessor); + // If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.firstAccessor); } - return false; + if (!accessorType && accessors.secondAccessor && node !== accessors.secondAccessor) { + accessorType = getTypeAnnotationFromAccessor(accessors.secondAccessor); + // If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.secondAccessor); + } + return accessorType; } - type ProcessedComponent = - | ConstructSignatureDeclaration - | ConstructorDeclaration - | MethodDeclaration - | GetAccessorDeclaration - | SetAccessorDeclaration - | PropertyDeclaration - | PropertySignature - | MethodSignature - | CallSignatureDeclaration - | IndexSignatureDeclaration - | VariableDeclaration - | TypeParameterDeclaration - | ExpressionWithTypeArguments - | TypeReferenceNode - | ConditionalTypeNode - | FunctionTypeNode - | ConstructorTypeNode - | ImportTypeNode; - - function isProcessedComponent(node: Node): node is ProcessedComponent { - switch (node.kind) { - case SyntaxKind.ConstructSignature: - case SyntaxKind.Constructor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.VariableDeclaration: - case SyntaxKind.TypeParameter: - case SyntaxKind.ExpressionWithTypeArguments: - case SyntaxKind.TypeReference: - case SyntaxKind.ConditionalType: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.ImportType: - return true; - } - return false; + function transformHeritageClauses(nodes: NodeArray | undefined) { + return factory.createNodeArray(filter(map(nodes, clause => factory.updateHeritageClause(clause, visitNodes(factory.createNodeArray(filter(clause.types, t => { + return isEntityNameExpression(t.expression) || (clause.token === SyntaxKind.ExtendsKeyword && t.expression.kind === SyntaxKind.NullKeyword); + })), visitDeclarationSubtree))), clause => clause.types && !!clause.types.length)); + } +} + +function isAlwaysType(node: Node) { + if (node.kind === SyntaxKind.InterfaceDeclaration) { + return true; + } + return false; +} + +// Elide "public" modifier, as it is the default +function maskModifiers(node: Node, modifierMask?: ModifierFlags, modifierAdditions?: ModifierFlags): Modifier[] { + return factory.createModifiersFromModifierFlags(maskModifierFlags(node, modifierMask, modifierAdditions)); +} + +function maskModifierFlags(node: Node, modifierMask: ModifierFlags = ModifierFlags.All ^ ModifierFlags.Public, modifierAdditions: ModifierFlags = ModifierFlags.None): ModifierFlags { + let flags = (getEffectiveModifierFlags(node) & modifierMask) | modifierAdditions; + if (flags & ModifierFlags.Default && !(flags & ModifierFlags.Export)) { + // A non-exported default is a nonsequitor - we usually try to remove all export modifiers + // from statements in ambient declarations; but a default export must retain its export modifier to be syntactically valid + flags ^= ModifierFlags.Export; + } + if (flags & ModifierFlags.Default && flags & ModifierFlags.Ambient) { + flags ^= ModifierFlags.Ambient; // `declare` is never required alongside `default` (and would be an error if printed) + } + return flags; +} + +function getTypeAnnotationFromAccessor(accessor: AccessorDeclaration): TypeNode | undefined { + if (accessor) { + return accessor.kind === SyntaxKind.GetAccessor + ? accessor.type // Getter - return type + : accessor.parameters.length > 0 + ? accessor.parameters[0].type // Setter parameter type + : undefined; } } + +type CanHaveLiteralInitializer = VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration; +function canHaveLiteralInitializer(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return !hasEffectiveModifier(node, ModifierFlags.Private); + case SyntaxKind.Parameter: + case SyntaxKind.VariableDeclaration: + return true; + } + return false; +} + +type ProcessedDeclarationStatement = + | FunctionDeclaration + | ModuleDeclaration + | ImportEqualsDeclaration + | InterfaceDeclaration + | ClassDeclaration + | TypeAliasDeclaration + | EnumDeclaration + | VariableStatement + | ImportDeclaration + | ExportDeclaration + | ExportAssignment; + +function isPreservedDeclarationStatement(node: Node): node is ProcessedDeclarationStatement { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.VariableStatement: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExportAssignment: + return true; + } + return false; +} + +type ProcessedComponent = + | ConstructSignatureDeclaration + | ConstructorDeclaration + | MethodDeclaration + | GetAccessorDeclaration + | SetAccessorDeclaration + | PropertyDeclaration + | PropertySignature + | MethodSignature + | CallSignatureDeclaration + | IndexSignatureDeclaration + | VariableDeclaration + | TypeParameterDeclaration + | ExpressionWithTypeArguments + | TypeReferenceNode + | ConditionalTypeNode + | FunctionTypeNode + | ConstructorTypeNode + | ImportTypeNode; + +function isProcessedComponent(node: Node): node is ProcessedComponent { + switch (node.kind) { + case SyntaxKind.ConstructSignature: + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.VariableDeclaration: + case SyntaxKind.TypeParameter: + case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.TypeReference: + case SyntaxKind.ConditionalType: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.ImportType: + return true; + } + return false; +} +} diff --git a/src/compiler/transformers/declarations/diagnostics.ts b/src/compiler/transformers/declarations/diagnostics.ts index 81b00987efdd8..47c7dbda483c7 100644 --- a/src/compiler/transformers/declarations/diagnostics.ts +++ b/src/compiler/transformers/declarations/diagnostics.ts @@ -1,80 +1,178 @@ /* @internal */ namespace ts { - export type GetSymbolAccessibilityDiagnostic = (symbolAccessibilityResult: SymbolAccessibilityResult) => (SymbolAccessibilityDiagnostic | undefined); +export type GetSymbolAccessibilityDiagnostic = (symbolAccessibilityResult: SymbolAccessibilityResult) => (SymbolAccessibilityDiagnostic | undefined); - export interface SymbolAccessibilityDiagnostic { - errorNode: Node; - diagnosticMessage: DiagnosticMessage; - typeName?: DeclarationName | QualifiedName; +export interface SymbolAccessibilityDiagnostic { + errorNode: Node; + diagnosticMessage: DiagnosticMessage; + typeName?: DeclarationName | QualifiedName; +} + +export type DeclarationDiagnosticProducing = + | VariableDeclaration + | PropertyDeclaration + | PropertySignature + | BindingElement + | SetAccessorDeclaration + | GetAccessorDeclaration + | ConstructSignatureDeclaration + | CallSignatureDeclaration + | MethodDeclaration + | MethodSignature + | FunctionDeclaration + | ParameterDeclaration + | TypeParameterDeclaration + | ExpressionWithTypeArguments + | ImportEqualsDeclaration + | TypeAliasDeclaration + | ConstructorDeclaration + | IndexSignatureDeclaration + | PropertyAccessExpression + | JSDocTypedefTag + | JSDocCallbackTag + | JSDocEnumTag; + +export function canProduceDiagnostics(node: Node): node is DeclarationDiagnosticProducing { + return isVariableDeclaration(node) || + isPropertyDeclaration(node) || + isPropertySignature(node) || + isBindingElement(node) || + isSetAccessor(node) || + isGetAccessor(node) || + isConstructSignatureDeclaration(node) || + isCallSignatureDeclaration(node) || + isMethodDeclaration(node) || + isMethodSignature(node) || + isFunctionDeclaration(node) || + isParameter(node) || + isTypeParameterDeclaration(node) || + isExpressionWithTypeArguments(node) || + isImportEqualsDeclaration(node) || + isTypeAliasDeclaration(node) || + isConstructorDeclaration(node) || + isIndexSignatureDeclaration(node) || + isPropertyAccessExpression(node) || + isJSDocTypeAlias(node); +} + +export function createGetSymbolAccessibilityDiagnosticForNodeName(node: DeclarationDiagnosticProducing) { + if (isSetAccessor(node) || isGetAccessor(node)) { + return getAccessorNameVisibilityError; + } + else if (isMethodSignature(node) || isMethodDeclaration(node)) { + return getMethodNameVisibilityError; + } + else { + return createGetSymbolAccessibilityDiagnosticForNode(node); + } + function getAccessorNameVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { + const diagnosticMessage = getAccessorNameVisibilityDiagnosticMessage(symbolAccessibilityResult); + return diagnosticMessage !== undefined ? { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + } : undefined; + } + + function getAccessorNameVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + if (isStatic(node)) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_private_name_1; + } + else if (node.parent.kind === SyntaxKind.ClassDeclaration) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Public_property_0_of_exported_class_has_or_is_using_private_name_1; + } + else { + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Property_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Property_0_of_exported_interface_has_or_is_using_private_name_1; + } } - export type DeclarationDiagnosticProducing = - | VariableDeclaration - | PropertyDeclaration - | PropertySignature - | BindingElement - | SetAccessorDeclaration - | GetAccessorDeclaration - | ConstructSignatureDeclaration - | CallSignatureDeclaration - | MethodDeclaration - | MethodSignature - | FunctionDeclaration - | ParameterDeclaration - | TypeParameterDeclaration - | ExpressionWithTypeArguments - | ImportEqualsDeclaration - | TypeAliasDeclaration - | ConstructorDeclaration - | IndexSignatureDeclaration - | PropertyAccessExpression - | JSDocTypedefTag - | JSDocCallbackTag - | JSDocEnumTag; - - export function canProduceDiagnostics(node: Node): node is DeclarationDiagnosticProducing { - return isVariableDeclaration(node) || - isPropertyDeclaration(node) || - isPropertySignature(node) || - isBindingElement(node) || - isSetAccessor(node) || - isGetAccessor(node) || - isConstructSignatureDeclaration(node) || - isCallSignatureDeclaration(node) || - isMethodDeclaration(node) || - isMethodSignature(node) || - isFunctionDeclaration(node) || - isParameter(node) || - isTypeParameterDeclaration(node) || - isExpressionWithTypeArguments(node) || - isImportEqualsDeclaration(node) || - isTypeAliasDeclaration(node) || - isConstructorDeclaration(node) || - isIndexSignatureDeclaration(node) || - isPropertyAccessExpression(node) || - isJSDocTypeAlias(node); + function getMethodNameVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { + const diagnosticMessage = getMethodNameVisibilityDiagnosticMessage(symbolAccessibilityResult); + return diagnosticMessage !== undefined ? { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + } : undefined; } - export function createGetSymbolAccessibilityDiagnosticForNodeName(node: DeclarationDiagnosticProducing) { - if (isSetAccessor(node) || isGetAccessor(node)) { - return getAccessorNameVisibilityError; + function getMethodNameVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + if (isStatic(node)) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_private_name_1; } - else if (isMethodSignature(node) || isMethodDeclaration(node)) { - return getMethodNameVisibilityError; + else if (node.parent.kind === SyntaxKind.ClassDeclaration) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Public_method_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Public_method_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Public_method_0_of_exported_class_has_or_is_using_private_name_1; } else { - return createGetSymbolAccessibilityDiagnosticForNode(node); + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Method_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Method_0_of_exported_interface_has_or_is_using_private_name_1; } - function getAccessorNameVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { - const diagnosticMessage = getAccessorNameVisibilityDiagnosticMessage(symbolAccessibilityResult); - return diagnosticMessage !== undefined ? { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - } : undefined; + } +} + +export function createGetSymbolAccessibilityDiagnosticForNode(node: DeclarationDiagnosticProducing): GetSymbolAccessibilityDiagnostic { + if (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isPropertyAccessExpression(node) || isBindingElement(node) || isConstructorDeclaration(node)) { + return getVariableDeclarationTypeVisibilityError; + } + else if (isSetAccessor(node) || isGetAccessor(node)) { + return getAccessorDeclarationTypeVisibilityError; + } + else if (isConstructSignatureDeclaration(node) || isCallSignatureDeclaration(node) || isMethodDeclaration(node) || isMethodSignature(node) || isFunctionDeclaration(node) || isIndexSignatureDeclaration(node)) { + return getReturnTypeVisibilityError; + } + else if (isParameter(node)) { + if (isParameterPropertyDeclaration(node, node.parent) && hasSyntacticModifier(node.parent, ModifierFlags.Private)) { + return getVariableDeclarationTypeVisibilityError; } + return getParameterDeclarationTypeVisibilityError; + } + else if (isTypeParameterDeclaration(node)) { + return getTypeParameterConstraintVisibilityError; + } + else if (isExpressionWithTypeArguments(node)) { + return getHeritageClauseVisibilityError; + } + else if (isImportEqualsDeclaration(node)) { + return getImportEntityNameVisibilityError; + } + else if (isTypeAliasDeclaration(node) || isJSDocTypeAlias(node)) { + return getTypeAliasDeclarationVisibilityError; + } + else { + return Debug.assertNever(node, `Attempted to set a declaration diagnostic context for unhandled node kind: ${(ts as any).SyntaxKind[(node as any).kind]}`); + } - function getAccessorNameVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + function getVariableDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Exported_variable_0_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Exported_variable_0_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Exported_variable_0_has_or_is_using_private_name_1; + } + // This check is to ensure we don't report error on constructor parameter property as that error would be reported during parameter emit + // The only exception here is if the constructor was marked as private. we are not emitting the constructor parameters at all. + else if (node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.PropertySignature || + (node.kind === SyntaxKind.Parameter && hasSyntacticModifier(node.parent, ModifierFlags.Private))) { + // TODO(jfreeman): Deal with computed properties in error reporting. if (isStatic(node)) { return symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? @@ -82,7 +180,7 @@ namespace ts { Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_private_name_1; } - else if (node.parent.kind === SyntaxKind.ClassDeclaration) { + else if (node.parent.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.Parameter) { return symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : @@ -90,402 +188,304 @@ namespace ts { Diagnostics.Public_property_0_of_exported_class_has_or_is_using_private_name_1; } else { + // Interfaces cannot have types that cannot be named return symbolAccessibilityResult.errorModuleName ? Diagnostics.Property_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : Diagnostics.Property_0_of_exported_interface_has_or_is_using_private_name_1; } } + } - function getMethodNameVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { - const diagnosticMessage = getMethodNameVisibilityDiagnosticMessage(symbolAccessibilityResult); - return diagnosticMessage !== undefined ? { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - } : undefined; - } + function getVariableDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { + const diagnosticMessage = getVariableDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult); + return diagnosticMessage !== undefined ? { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + } : undefined; + } - function getMethodNameVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + function getAccessorDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { + let diagnosticMessage: DiagnosticMessage; + if (node.kind === SyntaxKind.SetAccessor) { + // Getters can infer the return type from the returned expression, but setters cannot, so the + // "_from_external_module_1_but_cannot_be_named" case cannot occur. if (isStatic(node)) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_private_name_1; - } - else if (node.parent.kind === SyntaxKind.ClassDeclaration) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Public_method_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Public_method_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Public_method_0_of_exported_class_has_or_is_using_private_name_1; + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_type_of_public_static_setter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_type_of_public_static_setter_0_from_exported_class_has_or_is_using_private_name_1; } else { - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Method_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Method_0_of_exported_interface_has_or_is_using_private_name_1; - } - } - } - - export function createGetSymbolAccessibilityDiagnosticForNode(node: DeclarationDiagnosticProducing): GetSymbolAccessibilityDiagnostic { - if (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isPropertyAccessExpression(node) || isBindingElement(node) || isConstructorDeclaration(node)) { - return getVariableDeclarationTypeVisibilityError; - } - else if (isSetAccessor(node) || isGetAccessor(node)) { - return getAccessorDeclarationTypeVisibilityError; - } - else if (isConstructSignatureDeclaration(node) || isCallSignatureDeclaration(node) || isMethodDeclaration(node) || isMethodSignature(node) || isFunctionDeclaration(node) || isIndexSignatureDeclaration(node)) { - return getReturnTypeVisibilityError; - } - else if (isParameter(node)) { - if (isParameterPropertyDeclaration(node, node.parent) && hasSyntacticModifier(node.parent, ModifierFlags.Private)) { - return getVariableDeclarationTypeVisibilityError; + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_type_of_public_setter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_type_of_public_setter_0_from_exported_class_has_or_is_using_private_name_1; } - return getParameterDeclarationTypeVisibilityError; - } - else if (isTypeParameterDeclaration(node)) { - return getTypeParameterConstraintVisibilityError; - } - else if (isExpressionWithTypeArguments(node)) { - return getHeritageClauseVisibilityError; - } - else if (isImportEqualsDeclaration(node)) { - return getImportEntityNameVisibilityError; - } - else if (isTypeAliasDeclaration(node) || isJSDocTypeAlias(node)) { - return getTypeAliasDeclarationVisibilityError; } else { - return Debug.assertNever(node, `Attempted to set a declaration diagnostic context for unhandled node kind: ${(ts as any).SyntaxKind[(node as any).kind]}`); - } - - function getVariableDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { - if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { - return symbolAccessibilityResult.errorModuleName ? + if (isStatic(node)) { + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Exported_variable_0_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Exported_variable_0_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Exported_variable_0_has_or_is_using_private_name_1; + Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_private_name_1; } - // This check is to ensure we don't report error on constructor parameter property as that error would be reported during parameter emit - // The only exception here is if the constructor was marked as private. we are not emitting the constructor parameters at all. - else if (node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.PropertySignature || - (node.kind === SyntaxKind.Parameter && hasSyntacticModifier(node.parent, ModifierFlags.Private))) { - // TODO(jfreeman): Deal with computed properties in error reporting. - if (isStatic(node)) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_private_name_1; - } - else if (node.parent.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.Parameter) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Public_property_0_of_exported_class_has_or_is_using_private_name_1; - } - else { - // Interfaces cannot have types that cannot be named - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Property_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Property_0_of_exported_interface_has_or_is_using_private_name_1; - } + else { + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_private_name_1; } } + return { + diagnosticMessage, + errorNode: (node as NamedDeclaration).name!, + typeName: (node as NamedDeclaration).name + }; + } - function getVariableDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { - const diagnosticMessage = getVariableDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult); - return diagnosticMessage !== undefined ? { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - } : undefined; - } - - function getAccessorDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { - let diagnosticMessage: DiagnosticMessage; - if (node.kind === SyntaxKind.SetAccessor) { - // Getters can infer the return type from the returned expression, but setters cannot, so the - // "_from_external_module_1_but_cannot_be_named" case cannot occur. + function getReturnTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { + let diagnosticMessage: DiagnosticMessage; + switch (node.kind) { + case SyntaxKind.ConstructSignature: + // Interfaces cannot have return types that cannot be named + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_0; + break; + + case SyntaxKind.CallSignature: + // Interfaces cannot have return types that cannot be named + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_private_name_0; + break; + + case SyntaxKind.IndexSignature: + // Interfaces cannot have return types that cannot be named + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_private_name_0; + break; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: if (isStatic(node)) { diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_type_of_public_static_setter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_type_of_public_static_setter_0_from_exported_class_has_or_is_using_private_name_1; - } - else { - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_type_of_public_setter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_type_of_public_setter_0_from_exported_class_has_or_is_using_private_name_1; + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : + Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_private_name_0; } - } - else { - if (isStatic(node)) { + else if (node.parent.kind === SyntaxKind.ClassDeclaration) { diagnosticMessage = symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_private_name_1; + Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : + Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_private_name_0; } else { + // Interfaces cannot have return types that cannot be named diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_private_name_1; + Diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_private_name_0; } - } - return { - diagnosticMessage, - errorNode: (node as NamedDeclaration).name!, - typeName: (node as NamedDeclaration).name - }; + break; + + case SyntaxKind.FunctionDeclaration: + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Return_type_of_exported_function_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : + Diagnostics.Return_type_of_exported_function_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_exported_function_has_or_is_using_private_name_0; + break; + + default: + return Debug.fail("This is unknown kind for signature: " + node.kind); } - function getReturnTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { - let diagnosticMessage: DiagnosticMessage; - switch (node.kind) { - case SyntaxKind.ConstructSignature: - // Interfaces cannot have return types that cannot be named - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_0; - break; + return { + diagnosticMessage, + errorNode: (node as NamedDeclaration).name || node + }; + } - case SyntaxKind.CallSignature: - // Interfaces cannot have return types that cannot be named - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_private_name_0; - break; + function getParameterDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { + const diagnosticMessage: DiagnosticMessage = getParameterDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult); + return diagnosticMessage !== undefined ? { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + } : undefined; + } - case SyntaxKind.IndexSignature: - // Interfaces cannot have return types that cannot be named - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_private_name_0; - break; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (isStatic(node)) { - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : - Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_private_name_0; - } - else if (node.parent.kind === SyntaxKind.ClassDeclaration) { - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : - Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_private_name_0; - } - else { - // Interfaces cannot have return types that cannot be named - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_private_name_0; - } - break; - - case SyntaxKind.FunctionDeclaration: - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_exported_function_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : - Diagnostics.Return_type_of_exported_function_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_exported_function_has_or_is_using_private_name_0; - break; + function getParameterDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult): DiagnosticMessage { + switch (node.parent.kind) { + case SyntaxKind.Constructor: + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_private_name_1; - default: - return Debug.fail("This is unknown kind for signature: " + node.kind); - } + case SyntaxKind.ConstructSignature: + case SyntaxKind.ConstructorType: + // Interfaces cannot have parameter types that cannot be named + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1; - return { - diagnosticMessage, - errorNode: (node as NamedDeclaration).name || node - }; - } + case SyntaxKind.CallSignature: + // Interfaces cannot have parameter types that cannot be named + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_private_name_1; - function getParameterDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { - const diagnosticMessage: DiagnosticMessage = getParameterDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult); - return diagnosticMessage !== undefined ? { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - } : undefined; - } + case SyntaxKind.IndexSignature: + // Interfaces cannot have parameter types that cannot be named + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_private_name_1; - function getParameterDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult): DiagnosticMessage { - switch (node.parent.kind) { - case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + if (isStatic(node.parent)) { return symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_private_name_1; - - case SyntaxKind.ConstructSignature: - case SyntaxKind.ConstructorType: - // Interfaces cannot have parameter types that cannot be named + Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; + } + else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1; - - case SyntaxKind.CallSignature: + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1; + } + else { // Interfaces cannot have parameter types that cannot be named return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_private_name_1; + Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1; + } - case SyntaxKind.IndexSignature: - // Interfaces cannot have parameter types that cannot be named - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_private_name_1; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (isStatic(node.parent)) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; - } - else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1; - } - else { - // Interfaces cannot have parameter types that cannot be named - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1; - } - - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionType: - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_exported_function_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_exported_function_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_exported_function_has_or_is_using_private_name_1; - case SyntaxKind.SetAccessor: - case SyntaxKind.GetAccessor: - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_accessor_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_accessor_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_accessor_has_or_is_using_private_name_1; + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionType: + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Parameter_0_of_exported_function_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_exported_function_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_exported_function_has_or_is_using_private_name_1; + case SyntaxKind.SetAccessor: + case SyntaxKind.GetAccessor: + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Parameter_0_of_accessor_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_accessor_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_accessor_has_or_is_using_private_name_1; - default: - return Debug.fail(`Unknown parent for parameter: ${(ts as any).SyntaxKind[node.parent.kind]}`); - } + default: + return Debug.fail(`Unknown parent for parameter: ${(ts as any).SyntaxKind[node.parent.kind]}`); } + } - function getTypeParameterConstraintVisibilityError(): SymbolAccessibilityDiagnostic { - // Type parameter constraints are named by user so we should always be able to name it - let diagnosticMessage: DiagnosticMessage; - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_class_has_or_is_using_private_name_1; - break; - - case SyntaxKind.InterfaceDeclaration: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1; - break; - - case SyntaxKind.MappedType: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_mapped_object_type_is_using_private_name_1; - break; - - case SyntaxKind.ConstructorType: - case SyntaxKind.ConstructSignature: - diagnosticMessage = Diagnostics.Type_parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1; - break; - - case SyntaxKind.CallSignature: - diagnosticMessage = Diagnostics.Type_parameter_0_of_call_signature_from_exported_interface_has_or_is_using_private_name_1; - break; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (isStatic(node.parent)) { - diagnosticMessage = Diagnostics.Type_parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; - } - else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { - diagnosticMessage = Diagnostics.Type_parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1; - } - else { - diagnosticMessage = Diagnostics.Type_parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1; - } - break; - - case SyntaxKind.FunctionType: - case SyntaxKind.FunctionDeclaration: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_function_has_or_is_using_private_name_1; - break; - - case SyntaxKind.TypeAliasDeclaration: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_type_alias_has_or_is_using_private_name_1; - break; - - default: - return Debug.fail("This is unknown parent for type parameter: " + node.parent.kind); - } + function getTypeParameterConstraintVisibilityError(): SymbolAccessibilityDiagnostic { + // Type parameter constraints are named by user so we should always be able to name it + let diagnosticMessage: DiagnosticMessage; + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_class_has_or_is_using_private_name_1; + break; + + case SyntaxKind.InterfaceDeclaration: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1; + break; + + case SyntaxKind.MappedType: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_mapped_object_type_is_using_private_name_1; + break; + + case SyntaxKind.ConstructorType: + case SyntaxKind.ConstructSignature: + diagnosticMessage = Diagnostics.Type_parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1; + break; + + case SyntaxKind.CallSignature: + diagnosticMessage = Diagnostics.Type_parameter_0_of_call_signature_from_exported_interface_has_or_is_using_private_name_1; + break; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + if (isStatic(node.parent)) { + diagnosticMessage = Diagnostics.Type_parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; + } + else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { + diagnosticMessage = Diagnostics.Type_parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1; + } + else { + diagnosticMessage = Diagnostics.Type_parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1; + } + break; - return { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - }; - } + case SyntaxKind.FunctionType: + case SyntaxKind.FunctionDeclaration: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_function_has_or_is_using_private_name_1; + break; - function getHeritageClauseVisibilityError(): SymbolAccessibilityDiagnostic { - let diagnosticMessage: DiagnosticMessage; - // Heritage clause is written by user so it can always be named - if (isClassDeclaration(node.parent.parent)) { - // Class or Interface implemented/extended is inaccessible - diagnosticMessage = isHeritageClause(node.parent) && node.parent.token === SyntaxKind.ImplementsKeyword ? - Diagnostics.Implements_clause_of_exported_class_0_has_or_is_using_private_name_1 : - node.parent.parent.name ? Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1 : - Diagnostics.extends_clause_of_exported_class_has_or_is_using_private_name_0; - } - else { - // interface is inaccessible - diagnosticMessage = Diagnostics.extends_clause_of_exported_interface_0_has_or_is_using_private_name_1; - } + case SyntaxKind.TypeAliasDeclaration: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_type_alias_has_or_is_using_private_name_1; + break; - return { - diagnosticMessage, - errorNode: node, - typeName: getNameOfDeclaration(node.parent.parent as Declaration) - }; + default: + return Debug.fail("This is unknown parent for type parameter: " + node.parent.kind); } - function getImportEntityNameVisibilityError(): SymbolAccessibilityDiagnostic { - return { - diagnosticMessage: Diagnostics.Import_declaration_0_is_using_private_name_1, - errorNode: node, - typeName: (node as NamedDeclaration).name - }; - } + return { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + }; + } - function getTypeAliasDeclarationVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { - return { - diagnosticMessage: symbolAccessibilityResult.errorModuleName - ? Diagnostics.Exported_type_alias_0_has_or_is_using_private_name_1_from_module_2 - : Diagnostics.Exported_type_alias_0_has_or_is_using_private_name_1, - errorNode: isJSDocTypeAlias(node) ? Debug.checkDefined(node.typeExpression) : (node as TypeAliasDeclaration).type, - typeName: isJSDocTypeAlias(node) ? getNameOfDeclaration(node) : (node as TypeAliasDeclaration).name, - }; + function getHeritageClauseVisibilityError(): SymbolAccessibilityDiagnostic { + let diagnosticMessage: DiagnosticMessage; + // Heritage clause is written by user so it can always be named + if (isClassDeclaration(node.parent.parent)) { + // Class or Interface implemented/extended is inaccessible + diagnosticMessage = isHeritageClause(node.parent) && node.parent.token === SyntaxKind.ImplementsKeyword ? + Diagnostics.Implements_clause_of_exported_class_0_has_or_is_using_private_name_1 : + node.parent.parent.name ? Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1 : + Diagnostics.extends_clause_of_exported_class_has_or_is_using_private_name_0; } + else { + // interface is inaccessible + diagnosticMessage = Diagnostics.extends_clause_of_exported_interface_0_has_or_is_using_private_name_1; + } + + return { + diagnosticMessage, + errorNode: node, + typeName: getNameOfDeclaration(node.parent.parent as Declaration) + }; + } + + function getImportEntityNameVisibilityError(): SymbolAccessibilityDiagnostic { + return { + diagnosticMessage: Diagnostics.Import_declaration_0_is_using_private_name_1, + errorNode: node, + typeName: (node as NamedDeclaration).name + }; + } + + function getTypeAliasDeclarationVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { + return { + diagnosticMessage: symbolAccessibilityResult.errorModuleName + ? Diagnostics.Exported_type_alias_0_has_or_is_using_private_name_1_from_module_2 + : Diagnostics.Exported_type_alias_0_has_or_is_using_private_name_1, + errorNode: isJSDocTypeAlias(node) ? Debug.checkDefined(node.typeExpression) : (node as TypeAliasDeclaration).type, + typeName: isJSDocTypeAlias(node) ? getNameOfDeclaration(node) : (node as TypeAliasDeclaration).name, + }; } } +} diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index 64900c56a887a..72a010dfbcd82 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -1,544 +1,544 @@ /*@internal*/ namespace ts { - interface FlattenContext { - context: TransformationContext; - level: FlattenLevel; - downlevelIteration: boolean; - hoistTempVariables: boolean; - hasTransformedPriorElement?: boolean; // indicates whether we've transformed a prior declaration - emitExpression: (value: Expression) => void; - emitBindingOrAssignment: (target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node | undefined) => void; - createArrayBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ArrayBindingOrAssignmentPattern; - createObjectBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ObjectBindingOrAssignmentPattern; - createArrayBindingOrAssignmentElement: (node: Identifier) => BindingOrAssignmentElement; - visitor?: (node: Node) => VisitResult; - } - - export const enum FlattenLevel { - All, - ObjectRest, - } +interface FlattenContext { + context: TransformationContext; + level: FlattenLevel; + downlevelIteration: boolean; + hoistTempVariables: boolean; + hasTransformedPriorElement?: boolean; // indicates whether we've transformed a prior declaration + emitExpression: (value: Expression) => void; + emitBindingOrAssignment: (target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node | undefined) => void; + createArrayBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ArrayBindingOrAssignmentPattern; + createObjectBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ObjectBindingOrAssignmentPattern; + createArrayBindingOrAssignmentElement: (node: Identifier) => BindingOrAssignmentElement; + visitor?: (node: Node) => VisitResult; +} - /** - * Flattens a DestructuringAssignment or a VariableDeclaration to an expression. - * - * @param node The node to flatten. - * @param visitor An optional visitor used to visit initializers. - * @param context The transformation context. - * @param level Indicates the extent to which flattening should occur. - * @param needsValue An optional value indicating whether the value from the right-hand-side of - * the destructuring assignment is needed as part of a larger expression. - * @param createAssignmentCallback An optional callback used to create the assignment expression. - */ - export function flattenDestructuringAssignment( - node: VariableDeclaration | DestructuringAssignment, - visitor: ((node: Node) => VisitResult) | undefined, - context: TransformationContext, - level: FlattenLevel, - needsValue?: boolean, - createAssignmentCallback?: (name: Identifier, value: Expression, location?: TextRange) => Expression): Expression { - let location: TextRange = node; - let value: Expression | undefined; - if (isDestructuringAssignment(node)) { - value = node.right; - while (isEmptyArrayLiteral(node.left) || isEmptyObjectLiteral(node.left)) { - if (isDestructuringAssignment(value)) { - location = node = value; - value = node.right; - } - else { - return visitNode(value, visitor, isExpression); - } - } - } +export const enum FlattenLevel { + All, + ObjectRest, +} - let expressions: Expression[] | undefined; - const flattenContext: FlattenContext = { - context, - level, - downlevelIteration: !!context.getCompilerOptions().downlevelIteration, - hoistTempVariables: true, - emitExpression, - emitBindingOrAssignment, - createArrayBindingOrAssignmentPattern: elements => makeArrayAssignmentPattern(context.factory, elements), - createObjectBindingOrAssignmentPattern: elements => makeObjectAssignmentPattern(context.factory, elements), - createArrayBindingOrAssignmentElement: makeAssignmentElement, - visitor - }; - - if (value) { - value = visitNode(value, visitor, isExpression); - - if (isIdentifier(value) && bindingOrAssignmentElementAssignsToName(node, value.escapedText) || - bindingOrAssignmentElementContainsNonLiteralComputedName(node)) { - // If the right-hand value of the assignment is also an assignment target then - // we need to cache the right-hand value. - value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ false, location); - } - else if (needsValue) { - // If the right-hand value of the destructuring assignment needs to be preserved (as - // is the case when the destructuring assignment is part of a larger expression), - // then we need to cache the right-hand value. - // - // The source map location for the assignment should point to the entire binary - // expression. - value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); +/** + * Flattens a DestructuringAssignment or a VariableDeclaration to an expression. + * + * @param node The node to flatten. + * @param visitor An optional visitor used to visit initializers. + * @param context The transformation context. + * @param level Indicates the extent to which flattening should occur. + * @param needsValue An optional value indicating whether the value from the right-hand-side of + * the destructuring assignment is needed as part of a larger expression. + * @param createAssignmentCallback An optional callback used to create the assignment expression. + */ +export function flattenDestructuringAssignment( + node: VariableDeclaration | DestructuringAssignment, + visitor: ((node: Node) => VisitResult) | undefined, + context: TransformationContext, + level: FlattenLevel, + needsValue?: boolean, + createAssignmentCallback?: (name: Identifier, value: Expression, location?: TextRange) => Expression): Expression { + let location: TextRange = node; + let value: Expression | undefined; + if (isDestructuringAssignment(node)) { + value = node.right; + while (isEmptyArrayLiteral(node.left) || isEmptyObjectLiteral(node.left)) { + if (isDestructuringAssignment(value)) { + location = node = value; + value = node.right; } - else if (nodeIsSynthesized(node)) { - // Generally, the source map location for a destructuring assignment is the root - // expression. - // - // However, if the root expression is synthesized (as in the case - // of the initializer when transforming a ForOfStatement), then the source map - // location should point to the right-hand value of the expression. - location = value; + else { + return visitNode(value, visitor, isExpression); } } + } - flattenBindingOrAssignmentElement(flattenContext, node, value, location, /*skipInitializer*/ isDestructuringAssignment(node)); + let expressions: Expression[] | undefined; + const flattenContext: FlattenContext = { + context, + level, + downlevelIteration: !!context.getCompilerOptions().downlevelIteration, + hoistTempVariables: true, + emitExpression, + emitBindingOrAssignment, + createArrayBindingOrAssignmentPattern: elements => makeArrayAssignmentPattern(context.factory, elements), + createObjectBindingOrAssignmentPattern: elements => makeObjectAssignmentPattern(context.factory, elements), + createArrayBindingOrAssignmentElement: makeAssignmentElement, + visitor + }; + + if (value) { + value = visitNode(value, visitor, isExpression); + + if (isIdentifier(value) && bindingOrAssignmentElementAssignsToName(node, value.escapedText) || + bindingOrAssignmentElementContainsNonLiteralComputedName(node)) { + // If the right-hand value of the assignment is also an assignment target then + // we need to cache the right-hand value. + value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ false, location); + } + else if (needsValue) { + // If the right-hand value of the destructuring assignment needs to be preserved (as + // is the case when the destructuring assignment is part of a larger expression), + // then we need to cache the right-hand value. + // + // The source map location for the assignment should point to the entire binary + // expression. + value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); + } + else if (nodeIsSynthesized(node)) { + // Generally, the source map location for a destructuring assignment is the root + // expression. + // + // However, if the root expression is synthesized (as in the case + // of the initializer when transforming a ForOfStatement), then the source map + // location should point to the right-hand value of the expression. + location = value; + } + } - if (value && needsValue) { - if (!some(expressions)) { - return value; - } + flattenBindingOrAssignmentElement(flattenContext, node, value, location, /*skipInitializer*/ isDestructuringAssignment(node)); - expressions.push(value); + if (value && needsValue) { + if (!some(expressions)) { + return value; } - return context.factory.inlineExpressions(expressions!) || context.factory.createOmittedExpression(); + expressions.push(value); + } - function emitExpression(expression: Expression) { - expressions = append(expressions, expression); - } + return context.factory.inlineExpressions(expressions!) || context.factory.createOmittedExpression(); - function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node) { - Debug.assertNode(target, createAssignmentCallback ? isIdentifier : isExpression); - const expression = createAssignmentCallback - ? createAssignmentCallback(target as Identifier, value, location) - : setTextRange( - context.factory.createAssignment(visitNode(target as Expression, visitor, isExpression), value), - location - ); - expression.original = original; - emitExpression(expression); - } + function emitExpression(expression: Expression) { + expressions = append(expressions, expression); } - function bindingOrAssignmentElementAssignsToName(element: BindingOrAssignmentElement, escapedName: __String): boolean { - const target = getTargetOfBindingOrAssignmentElement(element)!; // TODO: GH#18217 - if (isBindingOrAssignmentPattern(target)) { - return bindingOrAssignmentPatternAssignsToName(target, escapedName); - } - else if (isIdentifier(target)) { - return target.escapedText === escapedName; - } - return false; + function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node) { + Debug.assertNode(target, createAssignmentCallback ? isIdentifier : isExpression); + const expression = createAssignmentCallback + ? createAssignmentCallback(target as Identifier, value, location) + : setTextRange( + context.factory.createAssignment(visitNode(target as Expression, visitor, isExpression), value), + location + ); + expression.original = original; + emitExpression(expression); } +} - function bindingOrAssignmentPatternAssignsToName(pattern: BindingOrAssignmentPattern, escapedName: __String): boolean { - const elements = getElementsOfBindingOrAssignmentPattern(pattern); - for (const element of elements) { - if (bindingOrAssignmentElementAssignsToName(element, escapedName)) { - return true; - } - } - return false; +function bindingOrAssignmentElementAssignsToName(element: BindingOrAssignmentElement, escapedName: __String): boolean { + const target = getTargetOfBindingOrAssignmentElement(element)!; // TODO: GH#18217 + if (isBindingOrAssignmentPattern(target)) { + return bindingOrAssignmentPatternAssignsToName(target, escapedName); + } + else if (isIdentifier(target)) { + return target.escapedText === escapedName; } + return false; +} - function bindingOrAssignmentElementContainsNonLiteralComputedName(element: BindingOrAssignmentElement): boolean { - const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(element); - if (propertyName && isComputedPropertyName(propertyName) && !isLiteralExpression(propertyName.expression)) { +function bindingOrAssignmentPatternAssignsToName(pattern: BindingOrAssignmentPattern, escapedName: __String): boolean { + const elements = getElementsOfBindingOrAssignmentPattern(pattern); + for (const element of elements) { + if (bindingOrAssignmentElementAssignsToName(element, escapedName)) { return true; } - const target = getTargetOfBindingOrAssignmentElement(element); - return !!target && isBindingOrAssignmentPattern(target) && bindingOrAssignmentPatternContainsNonLiteralComputedName(target); } + return false; +} - function bindingOrAssignmentPatternContainsNonLiteralComputedName(pattern: BindingOrAssignmentPattern): boolean { - return !!forEach(getElementsOfBindingOrAssignmentPattern(pattern), bindingOrAssignmentElementContainsNonLiteralComputedName); +function bindingOrAssignmentElementContainsNonLiteralComputedName(element: BindingOrAssignmentElement): boolean { + const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(element); + if (propertyName && isComputedPropertyName(propertyName) && !isLiteralExpression(propertyName.expression)) { + return true; } + const target = getTargetOfBindingOrAssignmentElement(element); + return !!target && isBindingOrAssignmentPattern(target) && bindingOrAssignmentPatternContainsNonLiteralComputedName(target); +} - /** - * Flattens a VariableDeclaration or ParameterDeclaration to one or more variable declarations. - * - * @param node The node to flatten. - * @param visitor An optional visitor used to visit initializers. - * @param context The transformation context. - * @param boundValue The value bound to the declaration. - * @param skipInitializer A value indicating whether to ignore the initializer of `node`. - * @param hoistTempVariables Indicates whether temporary variables should not be recorded in-line. - * @param level Indicates the extent to which flattening should occur. - */ - export function flattenDestructuringBinding( - node: VariableDeclaration | ParameterDeclaration, - visitor: (node: Node) => VisitResult, - context: TransformationContext, - level: FlattenLevel, - rval?: Expression, - hoistTempVariables = false, - skipInitializer?: boolean): VariableDeclaration[] { - let pendingExpressions: Expression[] | undefined; - const pendingDeclarations: { pendingExpressions?: Expression[], name: BindingName, value: Expression, location?: TextRange, original?: Node; }[] = []; - const declarations: VariableDeclaration[] = []; - const flattenContext: FlattenContext = { - context, - level, - downlevelIteration: !!context.getCompilerOptions().downlevelIteration, - hoistTempVariables, - emitExpression, - emitBindingOrAssignment, - createArrayBindingOrAssignmentPattern: elements => makeArrayBindingPattern(context.factory, elements), - createObjectBindingOrAssignmentPattern: elements => makeObjectBindingPattern(context.factory, elements), - createArrayBindingOrAssignmentElement: name => makeBindingElement(context.factory, name), - visitor - }; - - if (isVariableDeclaration(node)) { - let initializer = getInitializerOfBindingOrAssignmentElement(node); - if (initializer && (isIdentifier(initializer) && bindingOrAssignmentElementAssignsToName(node, initializer.escapedText) || - bindingOrAssignmentElementContainsNonLiteralComputedName(node))) { - // If the right-hand value of the assignment is also an assignment target then - // we need to cache the right-hand value. - initializer = ensureIdentifier(flattenContext, visitNode(initializer, flattenContext.visitor), /*reuseIdentifierExpressions*/ false, initializer); - node = context.factory.updateVariableDeclaration(node, node.name, /*exclamationToken*/ undefined, /*type*/ undefined, initializer); - } +function bindingOrAssignmentPatternContainsNonLiteralComputedName(pattern: BindingOrAssignmentPattern): boolean { + return !!forEach(getElementsOfBindingOrAssignmentPattern(pattern), bindingOrAssignmentElementContainsNonLiteralComputedName); +} + +/** + * Flattens a VariableDeclaration or ParameterDeclaration to one or more variable declarations. + * + * @param node The node to flatten. + * @param visitor An optional visitor used to visit initializers. + * @param context The transformation context. + * @param boundValue The value bound to the declaration. + * @param skipInitializer A value indicating whether to ignore the initializer of `node`. + * @param hoistTempVariables Indicates whether temporary variables should not be recorded in-line. + * @param level Indicates the extent to which flattening should occur. + */ +export function flattenDestructuringBinding( + node: VariableDeclaration | ParameterDeclaration, + visitor: (node: Node) => VisitResult, + context: TransformationContext, + level: FlattenLevel, + rval?: Expression, + hoistTempVariables = false, + skipInitializer?: boolean): VariableDeclaration[] { + let pendingExpressions: Expression[] | undefined; + const pendingDeclarations: { pendingExpressions?: Expression[], name: BindingName, value: Expression, location?: TextRange, original?: Node; }[] = []; + const declarations: VariableDeclaration[] = []; + const flattenContext: FlattenContext = { + context, + level, + downlevelIteration: !!context.getCompilerOptions().downlevelIteration, + hoistTempVariables, + emitExpression, + emitBindingOrAssignment, + createArrayBindingOrAssignmentPattern: elements => makeArrayBindingPattern(context.factory, elements), + createObjectBindingOrAssignmentPattern: elements => makeObjectBindingPattern(context.factory, elements), + createArrayBindingOrAssignmentElement: name => makeBindingElement(context.factory, name), + visitor + }; + + if (isVariableDeclaration(node)) { + let initializer = getInitializerOfBindingOrAssignmentElement(node); + if (initializer && (isIdentifier(initializer) && bindingOrAssignmentElementAssignsToName(node, initializer.escapedText) || + bindingOrAssignmentElementContainsNonLiteralComputedName(node))) { + // If the right-hand value of the assignment is also an assignment target then + // we need to cache the right-hand value. + initializer = ensureIdentifier(flattenContext, visitNode(initializer, flattenContext.visitor), /*reuseIdentifierExpressions*/ false, initializer); + node = context.factory.updateVariableDeclaration(node, node.name, /*exclamationToken*/ undefined, /*type*/ undefined, initializer); } + } - flattenBindingOrAssignmentElement(flattenContext, node, rval, node, skipInitializer); - if (pendingExpressions) { - const temp = context.factory.createTempVariable(/*recordTempVariable*/ undefined); - if (hoistTempVariables) { - const value = context.factory.inlineExpressions(pendingExpressions); - pendingExpressions = undefined; - emitBindingOrAssignment(temp, value, /*location*/ undefined, /*original*/ undefined); - } - else { - context.hoistVariableDeclaration(temp); - const pendingDeclaration = last(pendingDeclarations); - pendingDeclaration.pendingExpressions = append( - pendingDeclaration.pendingExpressions, - context.factory.createAssignment(temp, pendingDeclaration.value) - ); - addRange(pendingDeclaration.pendingExpressions, pendingExpressions); - pendingDeclaration.value = temp; - } + flattenBindingOrAssignmentElement(flattenContext, node, rval, node, skipInitializer); + if (pendingExpressions) { + const temp = context.factory.createTempVariable(/*recordTempVariable*/ undefined); + if (hoistTempVariables) { + const value = context.factory.inlineExpressions(pendingExpressions); + pendingExpressions = undefined; + emitBindingOrAssignment(temp, value, /*location*/ undefined, /*original*/ undefined); } - for (const { pendingExpressions, name, value, location, original } of pendingDeclarations) { - const variable = context.factory.createVariableDeclaration( - name, - /*exclamationToken*/ undefined, - /*type*/ undefined, - pendingExpressions ? context.factory.inlineExpressions(append(pendingExpressions, value)) : value + else { + context.hoistVariableDeclaration(temp); + const pendingDeclaration = last(pendingDeclarations); + pendingDeclaration.pendingExpressions = append( + pendingDeclaration.pendingExpressions, + context.factory.createAssignment(temp, pendingDeclaration.value) ); - variable.original = original; - setTextRange(variable, location); - declarations.push(variable); + addRange(pendingDeclaration.pendingExpressions, pendingExpressions); + pendingDeclaration.value = temp; } - return declarations; + } + for (const { pendingExpressions, name, value, location, original } of pendingDeclarations) { + const variable = context.factory.createVariableDeclaration( + name, + /*exclamationToken*/ undefined, + /*type*/ undefined, + pendingExpressions ? context.factory.inlineExpressions(append(pendingExpressions, value)) : value + ); + variable.original = original; + setTextRange(variable, location); + declarations.push(variable); + } + return declarations; - function emitExpression(value: Expression) { - pendingExpressions = append(pendingExpressions, value); - } + function emitExpression(value: Expression) { + pendingExpressions = append(pendingExpressions, value); + } - function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange | undefined, original: Node | undefined) { - Debug.assertNode(target, isBindingName); - if (pendingExpressions) { - value = context.factory.inlineExpressions(append(pendingExpressions, value)); - pendingExpressions = undefined; - } - pendingDeclarations.push({ pendingExpressions, name: target, value, location, original }); + function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange | undefined, original: Node | undefined) { + Debug.assertNode(target, isBindingName); + if (pendingExpressions) { + value = context.factory.inlineExpressions(append(pendingExpressions, value)); + pendingExpressions = undefined; } + pendingDeclarations.push({ pendingExpressions, name: target, value, location, original }); } +} - /** - * Flattens a BindingOrAssignmentElement into zero or more bindings or assignments. - * - * @param flattenContext Options used to control flattening. - * @param element The element to flatten. - * @param value The current RHS value to assign to the element. - * @param location The location to use for source maps and comments. - * @param skipInitializer An optional value indicating whether to include the initializer - * for the element. - */ - function flattenBindingOrAssignmentElement( - flattenContext: FlattenContext, - element: BindingOrAssignmentElement, - value: Expression | undefined, - location: TextRange, - skipInitializer?: boolean) { - const bindingTarget = getTargetOfBindingOrAssignmentElement(element)!; // TODO: GH#18217 - if (!skipInitializer) { - const initializer = visitNode(getInitializerOfBindingOrAssignmentElement(element), flattenContext.visitor, isExpression); - if (initializer) { - // Combine value and initializer - if (value) { - value = createDefaultValueCheck(flattenContext, value, initializer, location); - // If 'value' is not a simple expression, it could contain side-effecting code that should evaluate before an object or array binding pattern. - if (!isSimpleInlineableExpression(initializer) && isBindingOrAssignmentPattern(bindingTarget)) { - value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); - } - } - else { - value = initializer; +/** + * Flattens a BindingOrAssignmentElement into zero or more bindings or assignments. + * + * @param flattenContext Options used to control flattening. + * @param element The element to flatten. + * @param value The current RHS value to assign to the element. + * @param location The location to use for source maps and comments. + * @param skipInitializer An optional value indicating whether to include the initializer + * for the element. + */ +function flattenBindingOrAssignmentElement( + flattenContext: FlattenContext, + element: BindingOrAssignmentElement, + value: Expression | undefined, + location: TextRange, + skipInitializer?: boolean) { + const bindingTarget = getTargetOfBindingOrAssignmentElement(element)!; // TODO: GH#18217 + if (!skipInitializer) { + const initializer = visitNode(getInitializerOfBindingOrAssignmentElement(element), flattenContext.visitor, isExpression); + if (initializer) { + // Combine value and initializer + if (value) { + value = createDefaultValueCheck(flattenContext, value, initializer, location); + // If 'value' is not a simple expression, it could contain side-effecting code that should evaluate before an object or array binding pattern. + if (!isSimpleInlineableExpression(initializer) && isBindingOrAssignmentPattern(bindingTarget)) { + value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); } } - else if (!value) { - // Use 'void 0' in absence of value and initializer - value = flattenContext.context.factory.createVoidZero(); + else { + value = initializer; } } - if (isObjectBindingOrAssignmentPattern(bindingTarget)) { - flattenObjectBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value!, location); - } - else if (isArrayBindingOrAssignmentPattern(bindingTarget)) { - flattenArrayBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value!, location); - } - else { - flattenContext.emitBindingOrAssignment(bindingTarget, value!, location, /*original*/ element); // TODO: GH#18217 + else if (!value) { + // Use 'void 0' in absence of value and initializer + value = flattenContext.context.factory.createVoidZero(); } } + if (isObjectBindingOrAssignmentPattern(bindingTarget)) { + flattenObjectBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value!, location); + } + else if (isArrayBindingOrAssignmentPattern(bindingTarget)) { + flattenArrayBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value!, location); + } + else { + flattenContext.emitBindingOrAssignment(bindingTarget, value!, location, /*original*/ element); // TODO: GH#18217 + } +} - /** - * Flattens an ObjectBindingOrAssignmentPattern into zero or more bindings or assignments. - * - * @param flattenContext Options used to control flattening. - * @param parent The parent element of the pattern. - * @param pattern The ObjectBindingOrAssignmentPattern to flatten. - * @param value The current RHS value to assign to the element. - * @param location The location to use for source maps and comments. - */ - function flattenObjectBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ObjectBindingOrAssignmentPattern, value: Expression, location: TextRange) { - const elements = getElementsOfBindingOrAssignmentPattern(pattern); - const numElements = elements.length; - if (numElements !== 1) { - // For anything other than a single-element destructuring we need to generate a temporary - // to ensure value is evaluated exactly once. Additionally, if we have zero elements - // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, - // so in that case, we'll intentionally create that temporary. - const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0; - value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location); - } - let bindingElements: BindingOrAssignmentElement[] | undefined; - let computedTempVariables: Expression[] | undefined; - for (let i = 0; i < numElements; i++) { - const element = elements[i]; - if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { - const propertyName = getPropertyNameOfBindingOrAssignmentElement(element)!; - if (flattenContext.level >= FlattenLevel.ObjectRest - && !(element.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread)) - && !(getTargetOfBindingOrAssignmentElement(element)!.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread)) - && !isComputedPropertyName(propertyName)) { - bindingElements = append(bindingElements, visitNode(element, flattenContext.visitor)); - } - else { - if (bindingElements) { - flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); - bindingElements = undefined; - } - const rhsValue = createDestructuringPropertyAccess(flattenContext, value, propertyName); - if (isComputedPropertyName(propertyName)) { - computedTempVariables = append(computedTempVariables, (rhsValue as ElementAccessExpression).argumentExpression); - } - flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); - } +/** + * Flattens an ObjectBindingOrAssignmentPattern into zero or more bindings or assignments. + * + * @param flattenContext Options used to control flattening. + * @param parent The parent element of the pattern. + * @param pattern The ObjectBindingOrAssignmentPattern to flatten. + * @param value The current RHS value to assign to the element. + * @param location The location to use for source maps and comments. + */ +function flattenObjectBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ObjectBindingOrAssignmentPattern, value: Expression, location: TextRange) { + const elements = getElementsOfBindingOrAssignmentPattern(pattern); + const numElements = elements.length; + if (numElements !== 1) { + // For anything other than a single-element destructuring we need to generate a temporary + // to ensure value is evaluated exactly once. Additionally, if we have zero elements + // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, + // so in that case, we'll intentionally create that temporary. + const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0; + value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location); + } + let bindingElements: BindingOrAssignmentElement[] | undefined; + let computedTempVariables: Expression[] | undefined; + for (let i = 0; i < numElements; i++) { + const element = elements[i]; + if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { + const propertyName = getPropertyNameOfBindingOrAssignmentElement(element)!; + if (flattenContext.level >= FlattenLevel.ObjectRest + && !(element.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread)) + && !(getTargetOfBindingOrAssignmentElement(element)!.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread)) + && !isComputedPropertyName(propertyName)) { + bindingElements = append(bindingElements, visitNode(element, flattenContext.visitor)); } - else if (i === numElements - 1) { + else { if (bindingElements) { flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); bindingElements = undefined; } - const rhsValue = flattenContext.context.getEmitHelperFactory().createRestHelper(value, elements, computedTempVariables, pattern); - flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, element); + const rhsValue = createDestructuringPropertyAccess(flattenContext, value, propertyName); + if (isComputedPropertyName(propertyName)) { + computedTempVariables = append(computedTempVariables, (rhsValue as ElementAccessExpression).argumentExpression); + } + flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); } } - if (bindingElements) { - flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); + else if (i === numElements - 1) { + if (bindingElements) { + flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); + bindingElements = undefined; + } + const rhsValue = flattenContext.context.getEmitHelperFactory().createRestHelper(value, elements, computedTempVariables, pattern); + flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, element); } } + if (bindingElements) { + flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); + } +} - /** - * Flattens an ArrayBindingOrAssignmentPattern into zero or more bindings or assignments. - * - * @param flattenContext Options used to control flattening. - * @param parent The parent element of the pattern. - * @param pattern The ArrayBindingOrAssignmentPattern to flatten. - * @param value The current RHS value to assign to the element. - * @param location The location to use for source maps and comments. - */ - function flattenArrayBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ArrayBindingOrAssignmentPattern, value: Expression, location: TextRange) { - const elements = getElementsOfBindingOrAssignmentPattern(pattern); - const numElements = elements.length; - if (flattenContext.level < FlattenLevel.ObjectRest && flattenContext.downlevelIteration) { - // Read the elements of the iterable into an array - value = ensureIdentifier( - flattenContext, - setTextRange( - flattenContext.context.getEmitHelperFactory().createReadHelper( - value, - numElements > 0 && getRestIndicatorOfBindingOrAssignmentElement(elements[numElements - 1]) - ? undefined - : numElements - ), - location +/** + * Flattens an ArrayBindingOrAssignmentPattern into zero or more bindings or assignments. + * + * @param flattenContext Options used to control flattening. + * @param parent The parent element of the pattern. + * @param pattern The ArrayBindingOrAssignmentPattern to flatten. + * @param value The current RHS value to assign to the element. + * @param location The location to use for source maps and comments. + */ +function flattenArrayBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ArrayBindingOrAssignmentPattern, value: Expression, location: TextRange) { + const elements = getElementsOfBindingOrAssignmentPattern(pattern); + const numElements = elements.length; + if (flattenContext.level < FlattenLevel.ObjectRest && flattenContext.downlevelIteration) { + // Read the elements of the iterable into an array + value = ensureIdentifier( + flattenContext, + setTextRange( + flattenContext.context.getEmitHelperFactory().createReadHelper( + value, + numElements > 0 && getRestIndicatorOfBindingOrAssignmentElement(elements[numElements - 1]) + ? undefined + : numElements ), - /*reuseIdentifierExpressions*/ false, location - ); - } - else if (numElements !== 1 && (flattenContext.level < FlattenLevel.ObjectRest || numElements === 0) - || every(elements, isOmittedExpression)) { - // For anything other than a single-element destructuring we need to generate a temporary - // to ensure value is evaluated exactly once. Additionally, if we have zero elements - // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, - // so in that case, we'll intentionally create that temporary. - // Or all the elements of the binding pattern are omitted expression such as "var [,] = [1,2]", - // then we will create temporary variable. - const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0; - value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location); - } - let bindingElements: BindingOrAssignmentElement[] | undefined; - let restContainingElements: [Identifier, BindingOrAssignmentElement][] | undefined; - for (let i = 0; i < numElements; i++) { - const element = elements[i]; - if (flattenContext.level >= FlattenLevel.ObjectRest) { - // If an array pattern contains an ObjectRest, we must cache the result so that we - // can perform the ObjectRest destructuring in a different declaration - if (element.transformFlags & TransformFlags.ContainsObjectRestOrSpread || flattenContext.hasTransformedPriorElement && !isSimpleBindingOrAssignmentElement(element)) { - flattenContext.hasTransformedPriorElement = true; - const temp = flattenContext.context.factory.createTempVariable(/*recordTempVariable*/ undefined); - if (flattenContext.hoistTempVariables) { - flattenContext.context.hoistVariableDeclaration(temp); - } - - restContainingElements = append(restContainingElements, [temp, element] as [Identifier, BindingOrAssignmentElement]); - bindingElements = append(bindingElements, flattenContext.createArrayBindingOrAssignmentElement(temp)); - } - else { - bindingElements = append(bindingElements, element); + ), + /*reuseIdentifierExpressions*/ false, + location + ); + } + else if (numElements !== 1 && (flattenContext.level < FlattenLevel.ObjectRest || numElements === 0) + || every(elements, isOmittedExpression)) { + // For anything other than a single-element destructuring we need to generate a temporary + // to ensure value is evaluated exactly once. Additionally, if we have zero elements + // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, + // so in that case, we'll intentionally create that temporary. + // Or all the elements of the binding pattern are omitted expression such as "var [,] = [1,2]", + // then we will create temporary variable. + const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0; + value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location); + } + let bindingElements: BindingOrAssignmentElement[] | undefined; + let restContainingElements: [Identifier, BindingOrAssignmentElement][] | undefined; + for (let i = 0; i < numElements; i++) { + const element = elements[i]; + if (flattenContext.level >= FlattenLevel.ObjectRest) { + // If an array pattern contains an ObjectRest, we must cache the result so that we + // can perform the ObjectRest destructuring in a different declaration + if (element.transformFlags & TransformFlags.ContainsObjectRestOrSpread || flattenContext.hasTransformedPriorElement && !isSimpleBindingOrAssignmentElement(element)) { + flattenContext.hasTransformedPriorElement = true; + const temp = flattenContext.context.factory.createTempVariable(/*recordTempVariable*/ undefined); + if (flattenContext.hoistTempVariables) { + flattenContext.context.hoistVariableDeclaration(temp); } + + restContainingElements = append(restContainingElements, [temp, element] as [Identifier, BindingOrAssignmentElement]); + bindingElements = append(bindingElements, flattenContext.createArrayBindingOrAssignmentElement(temp)); } - else if (isOmittedExpression(element)) { - continue; - } - else if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { - const rhsValue = flattenContext.context.factory.createElementAccessExpression(value, i); - flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); - } - else if (i === numElements - 1) { - const rhsValue = flattenContext.context.factory.createArraySliceCall(value, i); - flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); + else { + bindingElements = append(bindingElements, element); } } - if (bindingElements) { - flattenContext.emitBindingOrAssignment(flattenContext.createArrayBindingOrAssignmentPattern(bindingElements), value, location, pattern); + else if (isOmittedExpression(element)) { + continue; } - if (restContainingElements) { - for (const [id, element] of restContainingElements) { - flattenBindingOrAssignmentElement(flattenContext, element, id, element); - } + else if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { + const rhsValue = flattenContext.context.factory.createElementAccessExpression(value, i); + flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); + } + else if (i === numElements - 1) { + const rhsValue = flattenContext.context.factory.createArraySliceCall(value, i); + flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); } } - - function isSimpleBindingOrAssignmentElement(element: BindingOrAssignmentElement): boolean { - const target = getTargetOfBindingOrAssignmentElement(element); - if (!target || isOmittedExpression(target)) return true; - const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(element); - if (propertyName && !isPropertyNameLiteral(propertyName)) return false; - const initializer = getInitializerOfBindingOrAssignmentElement(element); - if (initializer && !isSimpleInlineableExpression(initializer)) return false; - if (isBindingOrAssignmentPattern(target)) return every(getElementsOfBindingOrAssignmentPattern(target), isSimpleBindingOrAssignmentElement); - return isIdentifier(target); + if (bindingElements) { + flattenContext.emitBindingOrAssignment(flattenContext.createArrayBindingOrAssignmentPattern(bindingElements), value, location, pattern); } - - /** - * Creates an expression used to provide a default value if a value is `undefined` at runtime. - * - * @param flattenContext Options used to control flattening. - * @param value The RHS value to test. - * @param defaultValue The default value to use if `value` is `undefined` at runtime. - * @param location The location to use for source maps and comments. - */ - function createDefaultValueCheck(flattenContext: FlattenContext, value: Expression, defaultValue: Expression, location: TextRange): Expression { - value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); - return flattenContext.context.factory.createConditionalExpression(flattenContext.context.factory.createTypeCheck(value, "undefined"), /*questionToken*/ undefined, defaultValue, /*colonToken*/ undefined, value); + if (restContainingElements) { + for (const [id, element] of restContainingElements) { + flattenBindingOrAssignmentElement(flattenContext, element, id, element); + } } +} - /** - * Creates either a PropertyAccessExpression or an ElementAccessExpression for the - * right-hand side of a transformed destructuring assignment. - * - * @link https://tc39.github.io/ecma262/#sec-runtime-semantics-keyeddestructuringassignmentevaluation - * - * @param flattenContext Options used to control flattening. - * @param value The RHS value that is the source of the property. - * @param propertyName The destructuring property name. - */ - function createDestructuringPropertyAccess(flattenContext: FlattenContext, value: Expression, propertyName: PropertyName): LeftHandSideExpression { - if (isComputedPropertyName(propertyName)) { - const argumentExpression = ensureIdentifier(flattenContext, visitNode(propertyName.expression, flattenContext.visitor), /*reuseIdentifierExpressions*/ false, /*location*/ propertyName); - return flattenContext.context.factory.createElementAccessExpression(value, argumentExpression); - } - else if (isStringOrNumericLiteralLike(propertyName)) { - const argumentExpression = factory.cloneNode(propertyName); - return flattenContext.context.factory.createElementAccessExpression(value, argumentExpression); - } - else { - const name = flattenContext.context.factory.createIdentifier(idText(propertyName)); - return flattenContext.context.factory.createPropertyAccessExpression(value, name); - } +function isSimpleBindingOrAssignmentElement(element: BindingOrAssignmentElement): boolean { + const target = getTargetOfBindingOrAssignmentElement(element); + if (!target || isOmittedExpression(target)) return true; + const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(element); + if (propertyName && !isPropertyNameLiteral(propertyName)) return false; + const initializer = getInitializerOfBindingOrAssignmentElement(element); + if (initializer && !isSimpleInlineableExpression(initializer)) return false; + if (isBindingOrAssignmentPattern(target)) return every(getElementsOfBindingOrAssignmentPattern(target), isSimpleBindingOrAssignmentElement); + return isIdentifier(target); +} + +/** + * Creates an expression used to provide a default value if a value is `undefined` at runtime. + * + * @param flattenContext Options used to control flattening. + * @param value The RHS value to test. + * @param defaultValue The default value to use if `value` is `undefined` at runtime. + * @param location The location to use for source maps and comments. + */ +function createDefaultValueCheck(flattenContext: FlattenContext, value: Expression, defaultValue: Expression, location: TextRange): Expression { + value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); + return flattenContext.context.factory.createConditionalExpression(flattenContext.context.factory.createTypeCheck(value, "undefined"), /*questionToken*/ undefined, defaultValue, /*colonToken*/ undefined, value); +} + +/** + * Creates either a PropertyAccessExpression or an ElementAccessExpression for the + * right-hand side of a transformed destructuring assignment. + * + * @link https://tc39.github.io/ecma262/#sec-runtime-semantics-keyeddestructuringassignmentevaluation + * + * @param flattenContext Options used to control flattening. + * @param value The RHS value that is the source of the property. + * @param propertyName The destructuring property name. + */ +function createDestructuringPropertyAccess(flattenContext: FlattenContext, value: Expression, propertyName: PropertyName): LeftHandSideExpression { + if (isComputedPropertyName(propertyName)) { + const argumentExpression = ensureIdentifier(flattenContext, visitNode(propertyName.expression, flattenContext.visitor), /*reuseIdentifierExpressions*/ false, /*location*/ propertyName); + return flattenContext.context.factory.createElementAccessExpression(value, argumentExpression); } + else if (isStringOrNumericLiteralLike(propertyName)) { + const argumentExpression = factory.cloneNode(propertyName); + return flattenContext.context.factory.createElementAccessExpression(value, argumentExpression); + } + else { + const name = flattenContext.context.factory.createIdentifier(idText(propertyName)); + return flattenContext.context.factory.createPropertyAccessExpression(value, name); + } +} - /** - * Ensures that there exists a declared identifier whose value holds the given expression. - * This function is useful to ensure that the expression's value can be read from in subsequent expressions. - * Unless 'reuseIdentifierExpressions' is false, 'value' will be returned if it is just an identifier. - * - * @param flattenContext Options used to control flattening. - * @param value the expression whose value needs to be bound. - * @param reuseIdentifierExpressions true if identifier expressions can simply be returned; - * false if it is necessary to always emit an identifier. - * @param location The location to use for source maps and comments. - */ - function ensureIdentifier(flattenContext: FlattenContext, value: Expression, reuseIdentifierExpressions: boolean, location: TextRange) { - if (isIdentifier(value) && reuseIdentifierExpressions) { - return value; +/** + * Ensures that there exists a declared identifier whose value holds the given expression. + * This function is useful to ensure that the expression's value can be read from in subsequent expressions. + * Unless 'reuseIdentifierExpressions' is false, 'value' will be returned if it is just an identifier. + * + * @param flattenContext Options used to control flattening. + * @param value the expression whose value needs to be bound. + * @param reuseIdentifierExpressions true if identifier expressions can simply be returned; + * false if it is necessary to always emit an identifier. + * @param location The location to use for source maps and comments. + */ +function ensureIdentifier(flattenContext: FlattenContext, value: Expression, reuseIdentifierExpressions: boolean, location: TextRange) { + if (isIdentifier(value) && reuseIdentifierExpressions) { + return value; + } + else { + const temp = flattenContext.context.factory.createTempVariable(/*recordTempVariable*/ undefined); + if (flattenContext.hoistTempVariables) { + flattenContext.context.hoistVariableDeclaration(temp); + flattenContext.emitExpression(setTextRange(flattenContext.context.factory.createAssignment(temp, value), location)); } else { - const temp = flattenContext.context.factory.createTempVariable(/*recordTempVariable*/ undefined); - if (flattenContext.hoistTempVariables) { - flattenContext.context.hoistVariableDeclaration(temp); - flattenContext.emitExpression(setTextRange(flattenContext.context.factory.createAssignment(temp, value), location)); - } - else { - flattenContext.emitBindingOrAssignment(temp, value, location, /*original*/ undefined); - } - return temp; + flattenContext.emitBindingOrAssignment(temp, value, location, /*original*/ undefined); } + return temp; } +} - function makeArrayBindingPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { - Debug.assertEachNode(elements, isArrayBindingElement); - return factory.createArrayBindingPattern(elements as ArrayBindingElement[]); - } +function makeArrayBindingPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { + Debug.assertEachNode(elements, isArrayBindingElement); + return factory.createArrayBindingPattern(elements as ArrayBindingElement[]); +} - function makeArrayAssignmentPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { - return factory.createArrayLiteralExpression(map(elements, factory.converters.convertToArrayAssignmentElement)); - } +function makeArrayAssignmentPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { + return factory.createArrayLiteralExpression(map(elements, factory.converters.convertToArrayAssignmentElement)); +} - function makeObjectBindingPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { - Debug.assertEachNode(elements, isBindingElement); - return factory.createObjectBindingPattern(elements as BindingElement[]); - } +function makeObjectBindingPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { + Debug.assertEachNode(elements, isBindingElement); + return factory.createObjectBindingPattern(elements as BindingElement[]); +} - function makeObjectAssignmentPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { - return factory.createObjectLiteralExpression(map(elements, factory.converters.convertToObjectAssignmentElement)); - } +function makeObjectAssignmentPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { + return factory.createObjectLiteralExpression(map(elements, factory.converters.convertToObjectAssignmentElement)); +} - function makeBindingElement(factory: NodeFactory, name: Identifier) { - return factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name); - } +function makeBindingElement(factory: NodeFactory, name: Identifier) { + return factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name); +} - function makeAssignmentElement(name: Identifier) { - return name; - } +function makeAssignmentElement(name: Identifier) { + return name; +} } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 3ad6ce006b034..91ab5b8596410 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -1,4356 +1,4356 @@ /*@internal*/ namespace ts { - const enum ES2015SubstitutionFlags { - /** Enables substitutions for captured `this` */ - CapturedThis = 1 << 0, - /** Enables substitutions for block-scoped bindings. */ - BlockScopedBindings = 1 << 1, - } +const enum ES2015SubstitutionFlags { + /** Enables substitutions for captured `this` */ + CapturedThis = 1 << 0, + /** Enables substitutions for block-scoped bindings. */ + BlockScopedBindings = 1 << 1, +} - /** - * If loop contains block scoped binding captured in some function then loop body is converted to a function. - * Lexical bindings declared in loop initializer will be passed into the loop body function as parameters, - * however if this binding is modified inside the body - this new value should be propagated back to the original binding. - * This is done by declaring new variable (out parameter holder) outside of the loop for every binding that is reassigned inside the body. - * On every iteration this variable is initialized with value of corresponding binding. - * At every point where control flow leaves the loop either explicitly (break/continue) or implicitly (at the end of loop body) - * we copy the value inside the loop to the out parameter holder. - * - * for (let x;;) { - * let a = 1; - * let b = () => a; - * x++ - * if (...) break; - * ... - * } - * - * will be converted to - * - * var out_x; - * var loop = function(x) { - * var a = 1; - * var b = function() { return a; } - * x++; - * if (...) return out_x = x, "break"; - * ... - * out_x = x; - * } - * for (var x;;) { - * out_x = x; - * var state = loop(x); - * x = out_x; - * if (state === "break") break; - * } - * - * NOTE: values to out parameters are not copies if loop is abrupted with 'return' - in this case this will end the entire enclosing function - * so nobody can observe this new value. - */ - interface LoopOutParameter { - flags: LoopOutParameterFlags; - originalName: Identifier; - outParamName: Identifier; - } - - const enum LoopOutParameterFlags { - Body = 1 << 0, // Modified in the body of the iteration statement - Initializer = 1 << 1, // Set in the initializer of a ForStatement - } - - const enum CopyDirection { - ToOriginal, - ToOutParameter - } - - const enum Jump { - Break = 1 << 1, - Continue = 1 << 2, - Return = 1 << 3 - } - - interface ConvertedLoopState { - /* - * set of labels that occurred inside the converted loop - * used to determine if labeled jump can be emitted as is or it should be dispatched to calling code - */ - labels?: ESMap; - /* - * collection of labeled jumps that transfer control outside the converted loop. - * maps store association 'label -> labelMarker' where - * - label - value of label as it appear in code - * - label marker - return value that should be interpreted by calling code as 'jump to
" - const lessThanToken = getTokenAtPosition(sourceFile, pos); - const firstJsxElementOrOpenElement = lessThanToken.parent; - let binaryExpr = firstJsxElementOrOpenElement.parent; - if (!isBinaryExpression(binaryExpr)) { - // In case the start element is a JsxSelfClosingElement, it the end. - // For JsxOpenElement, find one more parent - binaryExpr = binaryExpr.parent; - if (!isBinaryExpression(binaryExpr)) return undefined; - } - if (!nodeIsMissing(binaryExpr.operatorToken)) return undefined; - return binaryExpr; +function findNodeToFix(sourceFile: SourceFile, pos: number): BinaryExpression | undefined { + // The error always at 1st token that is "<" in "" + const lessThanToken = getTokenAtPosition(sourceFile, pos); + const firstJsxElementOrOpenElement = lessThanToken.parent; + let binaryExpr = firstJsxElementOrOpenElement.parent; + if (!isBinaryExpression(binaryExpr)) { + // In case the start element is a JsxSelfClosingElement, it the end. + // For JsxOpenElement, find one more parent + binaryExpr = binaryExpr.parent; + if (!isBinaryExpression(binaryExpr)) return undefined; } + if (!nodeIsMissing(binaryExpr.operatorToken)) return undefined; + return binaryExpr; +} - function doChange(changeTracker: textChanges.ChangeTracker, sf: SourceFile, node: Node) { - const jsx = flattenInvalidBinaryExpr(node); - if (jsx) changeTracker.replaceNode(sf, node, factory.createJsxFragment(factory.createJsxOpeningFragment(), jsx, factory.createJsxJsxClosingFragment())); - } - // The invalid syntax is constructed as - // InvalidJsxTree :: One of - // JsxElement CommaToken InvalidJsxTree - // JsxElement CommaToken JsxElement - function flattenInvalidBinaryExpr(node: Node): JsxChild[] | undefined { - const children: JsxChild[] = []; - let current = node; - while (true) { - if (isBinaryExpression(current) && nodeIsMissing(current.operatorToken) && current.operatorToken.kind === SyntaxKind.CommaToken) { - children.push(current.left as JsxChild); - if (isJsxChild(current.right)) { - children.push(current.right); - // Indicates the tree has go to the bottom - return children; - } - else if (isBinaryExpression(current.right)) { - current = current.right; - continue; - } - // Unreachable case - else return undefined; +function doChange(changeTracker: textChanges.ChangeTracker, sf: SourceFile, node: Node) { + const jsx = flattenInvalidBinaryExpr(node); + if (jsx) changeTracker.replaceNode(sf, node, factory.createJsxFragment(factory.createJsxOpeningFragment(), jsx, factory.createJsxJsxClosingFragment())); +} +// The invalid syntax is constructed as +// InvalidJsxTree :: One of +// JsxElement CommaToken InvalidJsxTree +// JsxElement CommaToken JsxElement +function flattenInvalidBinaryExpr(node: Node): JsxChild[] | undefined { + const children: JsxChild[] = []; + let current = node; + while (true) { + if (isBinaryExpression(current) && nodeIsMissing(current.operatorToken) && current.operatorToken.kind === SyntaxKind.CommaToken) { + children.push(current.left as JsxChild); + if (isJsxChild(current.right)) { + children.push(current.right); + // Indicates the tree has go to the bottom + return children; + } + else if (isBinaryExpression(current.right)) { + current = current.right; + continue; } // Unreachable case else return undefined; } + // Unreachable case + else return undefined; } } +} diff --git a/src/services/completions.ts b/src/services/completions.ts index d40e2dbd05452..489dd2193d13d 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1,3894 +1,3894 @@ /* @internal */ namespace ts.Completions { - // Exported only for tests - export const moduleSpecifierResolutionLimit = 100; - export const moduleSpecifierResolutionCacheAttemptLimit = 1000; - - export type Log = (message: string) => void; - - // NOTE: Make sure that each entry has the exact same number of digits - // since many implementations will sort by string contents, - // where "10" is considered less than "2". - export enum SortText { - LocalDeclarationPriority = "10", - LocationPriority = "11", - OptionalMember = "12", - MemberDeclaredBySpreadAssignment = "13", - SuggestedClassMembers = "14", - GlobalsOrKeywords = "15", - AutoImportSuggestions = "16", - JavascriptIdentifiers = "17", - DeprecatedLocalDeclarationPriority = "18", - DeprecatedLocationPriority = "19", - DeprecatedOptionalMember = "20", - DeprecatedMemberDeclaredBySpreadAssignment = "21", - DeprecatedSuggestedClassMembers = "22", - DeprecatedGlobalsOrKeywords = "23", - DeprecatedAutoImportSuggestions = "24" - } - - const enum SortTextId { - LocalDeclarationPriority = 10, - LocationPriority = 11, - OptionalMember = 12, - MemberDeclaredBySpreadAssignment = 13, - SuggestedClassMembers = 14, - GlobalsOrKeywords = 15, - AutoImportSuggestions = 16, - - // Don't use these directly. - _JavaScriptIdentifiers = 17, - _DeprecatedStart = 18, - _First = LocalDeclarationPriority, - - DeprecatedOffset = _DeprecatedStart - _First, - } - - /** - * Special values for `CompletionInfo['source']` used to disambiguate - * completion items with the same `name`. (Each completion item must - * have a unique name/source combination, because those two fields - * comprise `CompletionEntryIdentifier` in `getCompletionEntryDetails`. - * - * When the completion item is an auto-import suggestion, the source - * is the module specifier of the suggestion. To avoid collisions, - * the values here should not be a module specifier we would ever - * generate for an auto-import. - */ - export enum CompletionSource { - /** Completions that require `this.` insertion text */ - ThisProperty = "ThisProperty/", - /** Auto-import that comes attached to a class member snippet */ - ClassMemberSnippet = "ClassMemberSnippet/", - } - - const enum SymbolOriginInfoKind { - ThisType = 1 << 0, - SymbolMember = 1 << 1, - Export = 1 << 2, - Promise = 1 << 3, - Nullable = 1 << 4, - ResolvedExport = 1 << 5, - - SymbolMemberNoExport = SymbolMember, - SymbolMemberExport = SymbolMember | Export, - } - - interface SymbolOriginInfo { - kind: SymbolOriginInfoKind; - isDefaultExport?: boolean; - isFromPackageJson?: boolean; - fileName?: string; - } +// Exported only for tests +export const moduleSpecifierResolutionLimit = 100; +export const moduleSpecifierResolutionCacheAttemptLimit = 1000; + +export type Log = (message: string) => void; + +// NOTE: Make sure that each entry has the exact same number of digits +// since many implementations will sort by string contents, +// where "10" is considered less than "2". +export enum SortText { + LocalDeclarationPriority = "10", + LocationPriority = "11", + OptionalMember = "12", + MemberDeclaredBySpreadAssignment = "13", + SuggestedClassMembers = "14", + GlobalsOrKeywords = "15", + AutoImportSuggestions = "16", + JavascriptIdentifiers = "17", + DeprecatedLocalDeclarationPriority = "18", + DeprecatedLocationPriority = "19", + DeprecatedOptionalMember = "20", + DeprecatedMemberDeclaredBySpreadAssignment = "21", + DeprecatedSuggestedClassMembers = "22", + DeprecatedGlobalsOrKeywords = "23", + DeprecatedAutoImportSuggestions = "24" +} - interface SymbolOriginInfoExport extends SymbolOriginInfo { - symbolName: string; - moduleSymbol: Symbol; - isDefaultExport: boolean; - exportName: string; - exportMapKey: string; - } +const enum SortTextId { + LocalDeclarationPriority = 10, + LocationPriority = 11, + OptionalMember = 12, + MemberDeclaredBySpreadAssignment = 13, + SuggestedClassMembers = 14, + GlobalsOrKeywords = 15, + AutoImportSuggestions = 16, + + // Don't use these directly. + _JavaScriptIdentifiers = 17, + _DeprecatedStart = 18, + _First = LocalDeclarationPriority, + + DeprecatedOffset = _DeprecatedStart - _First, +} - interface SymbolOriginInfoResolvedExport extends SymbolOriginInfo { - symbolName: string; - moduleSymbol: Symbol; - exportName: string; - moduleSpecifier: string; - } +/** + * Special values for `CompletionInfo['source']` used to disambiguate + * completion items with the same `name`. (Each completion item must + * have a unique name/source combination, because those two fields + * comprise `CompletionEntryIdentifier` in `getCompletionEntryDetails`. + * + * When the completion item is an auto-import suggestion, the source + * is the module specifier of the suggestion. To avoid collisions, + * the values here should not be a module specifier we would ever + * generate for an auto-import. + */ +export enum CompletionSource { + /** Completions that require `this.` insertion text */ + ThisProperty = "ThisProperty/", + /** Auto-import that comes attached to a class member snippet */ + ClassMemberSnippet = "ClassMemberSnippet/", +} - function originIsThisType(origin: SymbolOriginInfo): boolean { - return !!(origin.kind & SymbolOriginInfoKind.ThisType); - } +const enum SymbolOriginInfoKind { + ThisType = 1 << 0, + SymbolMember = 1 << 1, + Export = 1 << 2, + Promise = 1 << 3, + Nullable = 1 << 4, + ResolvedExport = 1 << 5, - function originIsSymbolMember(origin: SymbolOriginInfo): boolean { - return !!(origin.kind & SymbolOriginInfoKind.SymbolMember); - } + SymbolMemberNoExport = SymbolMember, + SymbolMemberExport = SymbolMember | Export, +} - function originIsExport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { - return !!(origin && origin.kind & SymbolOriginInfoKind.Export); - } +interface SymbolOriginInfo { + kind: SymbolOriginInfoKind; + isDefaultExport?: boolean; + isFromPackageJson?: boolean; + fileName?: string; +} - function originIsResolvedExport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoResolvedExport { - return !!(origin && origin.kind === SymbolOriginInfoKind.ResolvedExport); - } +interface SymbolOriginInfoExport extends SymbolOriginInfo { + symbolName: string; + moduleSymbol: Symbol; + isDefaultExport: boolean; + exportName: string; + exportMapKey: string; +} - function originIncludesSymbolName(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport | SymbolOriginInfoResolvedExport { - return originIsExport(origin) || originIsResolvedExport(origin); - } +interface SymbolOriginInfoResolvedExport extends SymbolOriginInfo { + symbolName: string; + moduleSymbol: Symbol; + exportName: string; + moduleSpecifier: string; +} - function originIsPackageJsonImport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { - return (originIsExport(origin) || originIsResolvedExport(origin)) && !!origin.isFromPackageJson; - } +function originIsThisType(origin: SymbolOriginInfo): boolean { + return !!(origin.kind & SymbolOriginInfoKind.ThisType); +} - function originIsPromise(origin: SymbolOriginInfo): boolean { - return !!(origin.kind & SymbolOriginInfoKind.Promise); - } +function originIsSymbolMember(origin: SymbolOriginInfo): boolean { + return !!(origin.kind & SymbolOriginInfoKind.SymbolMember); +} - function originIsNullableMember(origin: SymbolOriginInfo): boolean { - return !!(origin.kind & SymbolOriginInfoKind.Nullable); - } +function originIsExport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { + return !!(origin && origin.kind & SymbolOriginInfoKind.Export); +} - interface UniqueNameSet { - add(name: string): void; - has(name: string): boolean; - } +function originIsResolvedExport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoResolvedExport { + return !!(origin && origin.kind === SymbolOriginInfoKind.ResolvedExport); +} - /** - * Map from symbol index in `symbols` -> SymbolOriginInfo. - * Only populated for symbols that come from other modules. - */ - type SymbolOriginInfoMap = Record; +function originIncludesSymbolName(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport | SymbolOriginInfoResolvedExport { + return originIsExport(origin) || originIsResolvedExport(origin); +} - /** Map from symbol id -> SortTextId. */ - type SymbolSortTextIdMap = (SortTextId | undefined)[]; +function originIsPackageJsonImport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { + return (originIsExport(origin) || originIsResolvedExport(origin)) && !!origin.isFromPackageJson; +} - const enum KeywordCompletionFilters { - None, // No keywords - All, // Every possible keyword (TODO: This is never appropriate) - ClassElementKeywords, // Keywords inside class body - InterfaceElementKeywords, // Keywords inside interface body - ConstructorParameterKeywords, // Keywords at constructor parameter - FunctionLikeBodyKeywords, // Keywords at function like body - TypeAssertionKeywords, - TypeKeywords, - TypeKeyword, // Literally just `type` - Last = TypeKeywords - } +function originIsPromise(origin: SymbolOriginInfo): boolean { + return !!(origin.kind & SymbolOriginInfoKind.Promise); +} - const enum GlobalsSearch { Continue, Success, Fail } +function originIsNullableMember(origin: SymbolOriginInfo): boolean { + return !!(origin.kind & SymbolOriginInfoKind.Nullable); +} - interface ModuleSpecifierResolutioContext { - tryResolve: (exportInfo: readonly SymbolExportInfo[], isFromAmbientModule: boolean) => ModuleSpecifierResolutionResult | undefined; - resolutionLimitExceeded: () => boolean; - } +interface UniqueNameSet { + add(name: string): void; + has(name: string): boolean; +} - interface ModuleSpecifierResolutionResult { - exportInfo?: SymbolExportInfo; - moduleSpecifier: string; - } +/** + * Map from symbol index in `symbols` -> SymbolOriginInfo. + * Only populated for symbols that come from other modules. + */ +type SymbolOriginInfoMap = Record; + +/** Map from symbol id -> SortTextId. */ +type SymbolSortTextIdMap = (SortTextId | undefined)[]; + +const enum KeywordCompletionFilters { + None, // No keywords + All, // Every possible keyword (TODO: This is never appropriate) + ClassElementKeywords, // Keywords inside class body + InterfaceElementKeywords, // Keywords inside interface body + ConstructorParameterKeywords, // Keywords at constructor parameter + FunctionLikeBodyKeywords, // Keywords at function like body + TypeAssertionKeywords, + TypeKeywords, + TypeKeyword, // Literally just `type` + Last = TypeKeywords +} - function resolvingModuleSpecifiers( - logPrefix: string, - host: LanguageServiceHost, - program: Program, - sourceFile: SourceFile, - preferences: UserPreferences, - isForImportStatementCompletion: boolean, - cb: (context: ModuleSpecifierResolutioContext) => TReturn, - ): TReturn { - const start = timestamp(); - let resolutionLimitExceeded = false; - let ambientCount = 0; - let resolvedCount = 0; - let resolvedFromCacheCount = 0; - let cacheAttemptCount = 0; - - const result = cb({ tryResolve, resolutionLimitExceeded: () => resolutionLimitExceeded }); - - const hitRateMessage = cacheAttemptCount ? ` (${(resolvedFromCacheCount / cacheAttemptCount * 100).toFixed(1)}% hit rate)` : ""; - host.log?.(`${logPrefix}: resolved ${resolvedCount} module specifiers, plus ${ambientCount} ambient and ${resolvedFromCacheCount} from cache${hitRateMessage}`); - host.log?.(`${logPrefix}: response is ${resolutionLimitExceeded ? "incomplete" : "complete"}`); - host.log?.(`${logPrefix}: ${timestamp() - start}`); - return result; +const enum GlobalsSearch { Continue, Success, Fail } - function tryResolve(exportInfo: readonly SymbolExportInfo[], isFromAmbientModule: boolean): ModuleSpecifierResolutionResult | undefined { - if (isFromAmbientModule) { - const result = codefix.getModuleSpecifierForBestExportInfo(exportInfo, sourceFile, program, host, preferences); - if (result) { - ambientCount++; - } - return result; - } - const shouldResolveModuleSpecifier = isForImportStatementCompletion || preferences.allowIncompleteCompletions && resolvedCount < moduleSpecifierResolutionLimit; - const shouldGetModuleSpecifierFromCache = !shouldResolveModuleSpecifier && preferences.allowIncompleteCompletions && cacheAttemptCount < moduleSpecifierResolutionCacheAttemptLimit; - const result = (shouldResolveModuleSpecifier || shouldGetModuleSpecifierFromCache) - ? codefix.getModuleSpecifierForBestExportInfo(exportInfo, sourceFile, program, host, preferences, shouldGetModuleSpecifierFromCache) - : undefined; +interface ModuleSpecifierResolutioContext { + tryResolve: (exportInfo: readonly SymbolExportInfo[], isFromAmbientModule: boolean) => ModuleSpecifierResolutionResult | undefined; + resolutionLimitExceeded: () => boolean; +} - if (!shouldResolveModuleSpecifier && !shouldGetModuleSpecifierFromCache || shouldGetModuleSpecifierFromCache && !result) { - resolutionLimitExceeded = true; - } +interface ModuleSpecifierResolutionResult { + exportInfo?: SymbolExportInfo; + moduleSpecifier: string; +} - resolvedCount += result?.computedWithoutCacheCount || 0; - resolvedFromCacheCount += exportInfo.length - resolvedCount; - if (shouldGetModuleSpecifierFromCache) { - cacheAttemptCount++; +function resolvingModuleSpecifiers( + logPrefix: string, + host: LanguageServiceHost, + program: Program, + sourceFile: SourceFile, + preferences: UserPreferences, + isForImportStatementCompletion: boolean, + cb: (context: ModuleSpecifierResolutioContext) => TReturn, +): TReturn { + const start = timestamp(); + let resolutionLimitExceeded = false; + let ambientCount = 0; + let resolvedCount = 0; + let resolvedFromCacheCount = 0; + let cacheAttemptCount = 0; + + const result = cb({ tryResolve, resolutionLimitExceeded: () => resolutionLimitExceeded }); + + const hitRateMessage = cacheAttemptCount ? ` (${(resolvedFromCacheCount / cacheAttemptCount * 100).toFixed(1)}% hit rate)` : ""; + host.log?.(`${logPrefix}: resolved ${resolvedCount} module specifiers, plus ${ambientCount} ambient and ${resolvedFromCacheCount} from cache${hitRateMessage}`); + host.log?.(`${logPrefix}: response is ${resolutionLimitExceeded ? "incomplete" : "complete"}`); + host.log?.(`${logPrefix}: ${timestamp() - start}`); + return result; + + function tryResolve(exportInfo: readonly SymbolExportInfo[], isFromAmbientModule: boolean): ModuleSpecifierResolutionResult | undefined { + if (isFromAmbientModule) { + const result = codefix.getModuleSpecifierForBestExportInfo(exportInfo, sourceFile, program, host, preferences); + if (result) { + ambientCount++; } - return result; } - } + const shouldResolveModuleSpecifier = isForImportStatementCompletion || preferences.allowIncompleteCompletions && resolvedCount < moduleSpecifierResolutionLimit; + const shouldGetModuleSpecifierFromCache = !shouldResolveModuleSpecifier && preferences.allowIncompleteCompletions && cacheAttemptCount < moduleSpecifierResolutionCacheAttemptLimit; + const result = (shouldResolveModuleSpecifier || shouldGetModuleSpecifierFromCache) + ? codefix.getModuleSpecifierForBestExportInfo(exportInfo, sourceFile, program, host, preferences, shouldGetModuleSpecifierFromCache) + : undefined; - export function getCompletionsAtPosition( - host: LanguageServiceHost, - program: Program, - log: Log, - sourceFile: SourceFile, - position: number, - preferences: UserPreferences, - triggerCharacter: CompletionsTriggerCharacter | undefined, - completionKind: CompletionTriggerKind | undefined, - cancellationToken: CancellationToken, - ): CompletionInfo | undefined { - const { previousToken } = getRelevantTokens(position, sourceFile); - if (triggerCharacter && !isInString(sourceFile, position, previousToken) && !isValidTrigger(sourceFile, triggerCharacter, previousToken, position)) { - return undefined; + if (!shouldResolveModuleSpecifier && !shouldGetModuleSpecifierFromCache || shouldGetModuleSpecifierFromCache && !result) { + resolutionLimitExceeded = true; } - if (triggerCharacter === " ") { - // `isValidTrigger` ensures we are at `import |` - if (preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) { - return { isGlobalCompletion: true, isMemberCompletion: false, isNewIdentifierLocation: true, isIncomplete: true, entries: [] }; - } - return undefined; - + resolvedCount += result?.computedWithoutCacheCount || 0; + resolvedFromCacheCount += exportInfo.length - resolvedCount; + if (shouldGetModuleSpecifierFromCache) { + cacheAttemptCount++; } - // If the request is a continuation of an earlier `isIncomplete` response, - // we can continue it from the cached previous response. - const compilerOptions = program.getCompilerOptions(); - const incompleteCompletionsCache = preferences.allowIncompleteCompletions ? host.getIncompleteCompletionsCache?.() : undefined; - if (incompleteCompletionsCache && completionKind === CompletionTriggerKind.TriggerForIncompleteCompletions && previousToken && isIdentifier(previousToken)) { - const incompleteContinuation = continuePreviousIncompleteResponse(incompleteCompletionsCache, sourceFile, previousToken, program, host, preferences, cancellationToken); - if (incompleteContinuation) { - return incompleteContinuation; - } - } - else { - incompleteCompletionsCache?.clear(); - } + return result; + } +} - const stringCompletions = StringCompletions.getStringLiteralCompletions(sourceFile, position, previousToken, compilerOptions, host, program, log, preferences); - if (stringCompletions) { - return stringCompletions; - } +export function getCompletionsAtPosition( + host: LanguageServiceHost, + program: Program, + log: Log, + sourceFile: SourceFile, + position: number, + preferences: UserPreferences, + triggerCharacter: CompletionsTriggerCharacter | undefined, + completionKind: CompletionTriggerKind | undefined, + cancellationToken: CancellationToken, +): CompletionInfo | undefined { + const { previousToken } = getRelevantTokens(position, sourceFile); + if (triggerCharacter && !isInString(sourceFile, position, previousToken) && !isValidTrigger(sourceFile, triggerCharacter, previousToken, position)) { + return undefined; + } - if (previousToken && isBreakOrContinueStatement(previousToken.parent) - && (previousToken.kind === SyntaxKind.BreakKeyword || previousToken.kind === SyntaxKind.ContinueKeyword || previousToken.kind === SyntaxKind.Identifier)) { - return getLabelCompletionAtPosition(previousToken.parent); + if (triggerCharacter === " ") { + // `isValidTrigger` ensures we are at `import |` + if (preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) { + return { isGlobalCompletion: true, isMemberCompletion: false, isNewIdentifierLocation: true, isIncomplete: true, entries: [] }; } + return undefined; - const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, preferences, /*detailsEntryId*/ undefined, host, cancellationToken); - if (!completionData) { - return undefined; - } + } - switch (completionData.kind) { - case CompletionDataKind.Data: - const response = completionInfoFromData(sourceFile, host, program, compilerOptions, log, completionData, preferences); - if (response?.isIncomplete) { - incompleteCompletionsCache?.set(response); - } - return response; - case CompletionDataKind.JsDocTagName: - // If the current position is a jsDoc tag name, only tag names should be provided for completion - return jsdocCompletionInfo(JsDoc.getJSDocTagNameCompletions()); - case CompletionDataKind.JsDocTag: - // If the current position is a jsDoc tag, only tags should be provided for completion - return jsdocCompletionInfo(JsDoc.getJSDocTagCompletions()); - case CompletionDataKind.JsDocParameterName: - return jsdocCompletionInfo(JsDoc.getJSDocParameterNameCompletions(completionData.tag)); - case CompletionDataKind.Keywords: - return specificKeywordCompletionInfo(completionData.keywordCompletions, completionData.isNewIdentifierLocation); - default: - return Debug.assertNever(completionData); + // If the request is a continuation of an earlier `isIncomplete` response, + // we can continue it from the cached previous response. + const compilerOptions = program.getCompilerOptions(); + const incompleteCompletionsCache = preferences.allowIncompleteCompletions ? host.getIncompleteCompletionsCache?.() : undefined; + if (incompleteCompletionsCache && completionKind === CompletionTriggerKind.TriggerForIncompleteCompletions && previousToken && isIdentifier(previousToken)) { + const incompleteContinuation = continuePreviousIncompleteResponse(incompleteCompletionsCache, sourceFile, previousToken, program, host, preferences, cancellationToken); + if (incompleteContinuation) { + return incompleteContinuation; } } + else { + incompleteCompletionsCache?.clear(); + } - function completionEntryDataIsResolved(data: CompletionEntryDataAutoImport | undefined): data is CompletionEntryDataResolved { - return !!data?.moduleSpecifier; + const stringCompletions = StringCompletions.getStringLiteralCompletions(sourceFile, position, previousToken, compilerOptions, host, program, log, preferences); + if (stringCompletions) { + return stringCompletions; } - function continuePreviousIncompleteResponse( - cache: IncompleteCompletionsCache, - file: SourceFile, - location: Identifier, - program: Program, - host: LanguageServiceHost, - preferences: UserPreferences, - cancellationToken: CancellationToken, - ): CompletionInfo | undefined { - const previousResponse = cache.get(); - if (!previousResponse) return undefined; + if (previousToken && isBreakOrContinueStatement(previousToken.parent) + && (previousToken.kind === SyntaxKind.BreakKeyword || previousToken.kind === SyntaxKind.ContinueKeyword || previousToken.kind === SyntaxKind.Identifier)) { + return getLabelCompletionAtPosition(previousToken.parent); + } - const lowerCaseTokenText = location.text.toLowerCase(); - const exportMap = getExportInfoMap(file, host, program, cancellationToken); - const newEntries = resolvingModuleSpecifiers( - "continuePreviousIncompleteResponse", - host, - program, - file, - preferences, - /*isForImportStatementCompletion*/ false, - context => { - const entries = mapDefined(previousResponse.entries, entry => { - if (!entry.hasAction || !entry.source || !entry.data || completionEntryDataIsResolved(entry.data)) { - // Not an auto import or already resolved; keep as is - return entry; - } - if (!charactersFuzzyMatchInString(entry.name, lowerCaseTokenText)) { - // No longer matches typed characters; filter out - return undefined; - } + const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, preferences, /*detailsEntryId*/ undefined, host, cancellationToken); + if (!completionData) { + return undefined; + } - const { origin } = Debug.checkDefined(getAutoImportSymbolFromCompletionEntryData(entry.name, entry.data, program, host)); - const info = exportMap.get(file.path, entry.data.exportMapKey); - - const result = info && context.tryResolve(info, !isExternalModuleNameRelative(stripQuotes(origin.moduleSymbol.name))); - if (!result) return entry; - - const newOrigin: SymbolOriginInfoResolvedExport = { - ...origin, - kind: SymbolOriginInfoKind.ResolvedExport, - moduleSpecifier: result.moduleSpecifier, - }; - // Mutating for performance... feels sketchy but nobody else uses the cache, - // so why bother allocating a bunch of new objects? - entry.data = originToCompletionEntryData(newOrigin); - entry.source = getSourceFromOrigin(newOrigin); - entry.sourceDisplay = [textPart(newOrigin.moduleSpecifier)]; - return entry; - }); + switch (completionData.kind) { + case CompletionDataKind.Data: + const response = completionInfoFromData(sourceFile, host, program, compilerOptions, log, completionData, preferences); + if (response?.isIncomplete) { + incompleteCompletionsCache?.set(response); + } + return response; + case CompletionDataKind.JsDocTagName: + // If the current position is a jsDoc tag name, only tag names should be provided for completion + return jsdocCompletionInfo(JsDoc.getJSDocTagNameCompletions()); + case CompletionDataKind.JsDocTag: + // If the current position is a jsDoc tag, only tags should be provided for completion + return jsdocCompletionInfo(JsDoc.getJSDocTagCompletions()); + case CompletionDataKind.JsDocParameterName: + return jsdocCompletionInfo(JsDoc.getJSDocParameterNameCompletions(completionData.tag)); + case CompletionDataKind.Keywords: + return specificKeywordCompletionInfo(completionData.keywordCompletions, completionData.isNewIdentifierLocation); + default: + return Debug.assertNever(completionData); + } +} + +function completionEntryDataIsResolved(data: CompletionEntryDataAutoImport | undefined): data is CompletionEntryDataResolved { + return !!data?.moduleSpecifier; +} - if (!context.resolutionLimitExceeded()) { - previousResponse.isIncomplete = undefined; +function continuePreviousIncompleteResponse( + cache: IncompleteCompletionsCache, + file: SourceFile, + location: Identifier, + program: Program, + host: LanguageServiceHost, + preferences: UserPreferences, + cancellationToken: CancellationToken, +): CompletionInfo | undefined { + const previousResponse = cache.get(); + if (!previousResponse) return undefined; + + const lowerCaseTokenText = location.text.toLowerCase(); + const exportMap = getExportInfoMap(file, host, program, cancellationToken); + const newEntries = resolvingModuleSpecifiers( + "continuePreviousIncompleteResponse", + host, + program, + file, + preferences, + /*isForImportStatementCompletion*/ false, + context => { + const entries = mapDefined(previousResponse.entries, entry => { + if (!entry.hasAction || !entry.source || !entry.data || completionEntryDataIsResolved(entry.data)) { + // Not an auto import or already resolved; keep as is + return entry; + } + if (!charactersFuzzyMatchInString(entry.name, lowerCaseTokenText)) { + // No longer matches typed characters; filter out + return undefined; } - return entries; - }, - ); + const { origin } = Debug.checkDefined(getAutoImportSymbolFromCompletionEntryData(entry.name, entry.data, program, host)); + const info = exportMap.get(file.path, entry.data.exportMapKey); - previousResponse.entries = newEntries; - return previousResponse; - } + const result = info && context.tryResolve(info, !isExternalModuleNameRelative(stripQuotes(origin.moduleSymbol.name))); + if (!result) return entry; - function jsdocCompletionInfo(entries: CompletionEntry[]): CompletionInfo { - return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; - } + const newOrigin: SymbolOriginInfoResolvedExport = { + ...origin, + kind: SymbolOriginInfoKind.ResolvedExport, + moduleSpecifier: result.moduleSpecifier, + }; + // Mutating for performance... feels sketchy but nobody else uses the cache, + // so why bother allocating a bunch of new objects? + entry.data = originToCompletionEntryData(newOrigin); + entry.source = getSourceFromOrigin(newOrigin); + entry.sourceDisplay = [textPart(newOrigin.moduleSpecifier)]; + return entry; + }); - function keywordToCompletionEntry(keyword: TokenSyntaxKind) { - return { - name: tokenToString(keyword)!, - kind: ScriptElementKind.keyword, - kindModifiers: ScriptElementKindModifier.none, - sortText: SortText.GlobalsOrKeywords, - }; - } + if (!context.resolutionLimitExceeded()) { + previousResponse.isIncomplete = undefined; + } - function specificKeywordCompletionInfo(entries: readonly CompletionEntry[], isNewIdentifierLocation: boolean): CompletionInfo { - return { - isGlobalCompletion: false, - isMemberCompletion: false, - isNewIdentifierLocation, - entries: entries.slice(), - }; - } + return entries; + }, + ); - function keywordCompletionData(keywordFilters: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean, isNewIdentifierLocation: boolean): Request { - return { - kind: CompletionDataKind.Keywords, - keywordCompletions: getKeywordCompletions(keywordFilters, filterOutTsOnlyKeywords), - isNewIdentifierLocation, - }; - } + previousResponse.entries = newEntries; + return previousResponse; +} - function keywordFiltersFromSyntaxKind(keywordCompletion: TokenSyntaxKind): KeywordCompletionFilters { - switch (keywordCompletion) { - case SyntaxKind.TypeKeyword: return KeywordCompletionFilters.TypeKeyword; - default: Debug.fail("Unknown mapping from SyntaxKind to KeywordCompletionFilters"); - } - } +function jsdocCompletionInfo(entries: CompletionEntry[]): CompletionInfo { + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; +} + +function keywordToCompletionEntry(keyword: TokenSyntaxKind) { + return { + name: tokenToString(keyword)!, + kind: ScriptElementKind.keyword, + kindModifiers: ScriptElementKindModifier.none, + sortText: SortText.GlobalsOrKeywords, + }; +} + +function specificKeywordCompletionInfo(entries: readonly CompletionEntry[], isNewIdentifierLocation: boolean): CompletionInfo { + return { + isGlobalCompletion: false, + isMemberCompletion: false, + isNewIdentifierLocation, + entries: entries.slice(), + }; +} + +function keywordCompletionData(keywordFilters: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean, isNewIdentifierLocation: boolean): Request { + return { + kind: CompletionDataKind.Keywords, + keywordCompletions: getKeywordCompletions(keywordFilters, filterOutTsOnlyKeywords), + isNewIdentifierLocation, + }; +} - function getOptionalReplacementSpan(location: Node | undefined) { - // StringLiteralLike locations are handled separately in stringCompletions.ts - return location?.kind === SyntaxKind.Identifier ? createTextSpanFromNode(location) : undefined; +function keywordFiltersFromSyntaxKind(keywordCompletion: TokenSyntaxKind): KeywordCompletionFilters { + switch (keywordCompletion) { + case SyntaxKind.TypeKeyword: return KeywordCompletionFilters.TypeKeyword; + default: Debug.fail("Unknown mapping from SyntaxKind to KeywordCompletionFilters"); } +} + +function getOptionalReplacementSpan(location: Node | undefined) { + // StringLiteralLike locations are handled separately in stringCompletions.ts + return location?.kind === SyntaxKind.Identifier ? createTextSpanFromNode(location) : undefined; +} - function completionInfoFromData( - sourceFile: SourceFile, - host: LanguageServiceHost, - program: Program, - compilerOptions: CompilerOptions, - log: Log, - completionData: CompletionData, - preferences: UserPreferences, - ): CompletionInfo | undefined { - const { +function completionInfoFromData( + sourceFile: SourceFile, + host: LanguageServiceHost, + program: Program, + compilerOptions: CompilerOptions, + log: Log, + completionData: CompletionData, + preferences: UserPreferences, +): CompletionInfo | undefined { + const { + symbols, + contextToken, + completionKind, + isInSnippetScope, + isNewIdentifierLocation, + location, + propertyAccessToConvert, + keywordFilters, + literals, + symbolToOriginInfoMap, + recommendedCompletion, + isJsxInitializer, + isTypeOnlyLocation, + isJsxIdentifierExpected, + importCompletionNode, + insideJsDocTagTypeExpression, + symbolToSortTextIdMap, + hasUnresolvedAutoImports, + } = completionData; + + // Verify if the file is JSX language variant + if (getLanguageVariant(sourceFile.scriptKind) === LanguageVariant.JSX) { + const completionInfo = getJsxClosingTagCompletion(location, sourceFile); + if (completionInfo) { + return completionInfo; + } + } + + const entries: CompletionEntry[] = []; + + if (isUncheckedFile(sourceFile, compilerOptions)) { + const uniqueNames = getCompletionEntriesFromSymbols( symbols, + entries, + /*replacementToken*/ undefined, contextToken, - completionKind, - isInSnippetScope, - isNewIdentifierLocation, location, + sourceFile, + host, + program, + getEmitScriptTarget(compilerOptions), + log, + completionKind, + preferences, + compilerOptions, + isTypeOnlyLocation, propertyAccessToConvert, - keywordFilters, - literals, - symbolToOriginInfoMap, - recommendedCompletion, + isJsxIdentifierExpected, isJsxInitializer, + importCompletionNode, + recommendedCompletion, + symbolToOriginInfoMap, + symbolToSortTextIdMap + ); + getJSCompletionEntries(sourceFile, location.pos, uniqueNames, getEmitScriptTarget(compilerOptions), entries); // TODO: GH#18217 + } + else { + if (!isNewIdentifierLocation && (!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) { + return undefined; + } + + getCompletionEntriesFromSymbols( + symbols, + entries, + /*replacementToken*/ undefined, + contextToken, + location, + sourceFile, + host, + program, + getEmitScriptTarget(compilerOptions), + log, + completionKind, + preferences, + compilerOptions, isTypeOnlyLocation, + propertyAccessToConvert, isJsxIdentifierExpected, + isJsxInitializer, importCompletionNode, - insideJsDocTagTypeExpression, - symbolToSortTextIdMap, - hasUnresolvedAutoImports, - } = completionData; + recommendedCompletion, + symbolToOriginInfoMap, + symbolToSortTextIdMap + ); + } - // Verify if the file is JSX language variant - if (getLanguageVariant(sourceFile.scriptKind) === LanguageVariant.JSX) { - const completionInfo = getJsxClosingTagCompletion(location, sourceFile); - if (completionInfo) { - return completionInfo; + if (keywordFilters !== KeywordCompletionFilters.None) { + const entryNames = new Set(entries.map(e => e.name)); + for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile))) { + if (!entryNames.has(keywordEntry.name)) { + entries.push(keywordEntry); } } + } - const entries: CompletionEntry[] = []; - - if (isUncheckedFile(sourceFile, compilerOptions)) { - const uniqueNames = getCompletionEntriesFromSymbols( - symbols, - entries, - /*replacementToken*/ undefined, - contextToken, - location, - sourceFile, - host, - program, - getEmitScriptTarget(compilerOptions), - log, - completionKind, - preferences, - compilerOptions, - isTypeOnlyLocation, - propertyAccessToConvert, - isJsxIdentifierExpected, - isJsxInitializer, - importCompletionNode, - recommendedCompletion, - symbolToOriginInfoMap, - symbolToSortTextIdMap - ); - getJSCompletionEntries(sourceFile, location.pos, uniqueNames, getEmitScriptTarget(compilerOptions), entries); // TODO: GH#18217 - } - else { - if (!isNewIdentifierLocation && (!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) { - return undefined; - } - - getCompletionEntriesFromSymbols( - symbols, - entries, - /*replacementToken*/ undefined, - contextToken, - location, - sourceFile, - host, - program, - getEmitScriptTarget(compilerOptions), - log, - completionKind, - preferences, - compilerOptions, - isTypeOnlyLocation, - propertyAccessToConvert, - isJsxIdentifierExpected, - isJsxInitializer, - importCompletionNode, - recommendedCompletion, - symbolToOriginInfoMap, - symbolToSortTextIdMap - ); - } - - if (keywordFilters !== KeywordCompletionFilters.None) { - const entryNames = new Set(entries.map(e => e.name)); - for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile))) { - if (!entryNames.has(keywordEntry.name)) { - entries.push(keywordEntry); - } - } - } + for (const literal of literals) { + entries.push(createCompletionEntryForLiteral(sourceFile, preferences, literal)); + } - for (const literal of literals) { - entries.push(createCompletionEntryForLiteral(sourceFile, preferences, literal)); - } + return { + isGlobalCompletion: isInSnippetScope, + isIncomplete: preferences.allowIncompleteCompletions && hasUnresolvedAutoImports ? true : undefined, + isMemberCompletion: isMemberCompletionKind(completionKind), + isNewIdentifierLocation, + optionalReplacementSpan: getOptionalReplacementSpan(location), + entries + }; +} - return { - isGlobalCompletion: isInSnippetScope, - isIncomplete: preferences.allowIncompleteCompletions && hasUnresolvedAutoImports ? true : undefined, - isMemberCompletion: isMemberCompletionKind(completionKind), - isNewIdentifierLocation, - optionalReplacementSpan: getOptionalReplacementSpan(location), - entries - }; - } +function isUncheckedFile(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean { + return isSourceFileJS(sourceFile) && !isCheckJsEnabledForFile(sourceFile, compilerOptions); +} - function isUncheckedFile(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean { - return isSourceFileJS(sourceFile) && !isCheckJsEnabledForFile(sourceFile, compilerOptions); +function isMemberCompletionKind(kind: CompletionKind): boolean { + switch (kind) { + case CompletionKind.ObjectPropertyDeclaration: + case CompletionKind.MemberLike: + case CompletionKind.PropertyAccess: + return true; + default: + return false; } +} - function isMemberCompletionKind(kind: CompletionKind): boolean { - switch (kind) { - case CompletionKind.ObjectPropertyDeclaration: - case CompletionKind.MemberLike: - case CompletionKind.PropertyAccess: +function getJsxClosingTagCompletion(location: Node | undefined, sourceFile: SourceFile): CompletionInfo | undefined { + // We wanna walk up the tree till we find a JSX closing element + const jsxClosingElement = findAncestor(location, node => { + switch (node.kind) { + case SyntaxKind.JsxClosingElement: return true; - default: + case SyntaxKind.SlashToken: + case SyntaxKind.GreaterThanToken: + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: return false; - } + default: + return "quit"; + } + }) as JsxClosingElement | undefined; + + if (jsxClosingElement) { + // In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag, + // instead of simply giving unknown value, the completion will return the tag-name of an associated opening-element. + // For example: + // var x =
" with type any + // And at `
` (with a closing `>`), the completion list will contain "div". + // And at property access expressions ` ` the completion will + // return full closing tag with an optional replacement span + // For example: + // var x = + // var y = + // the completion list at "1" and "2" will contain "MainComponent.Child" with a replacement span of closing tag name + const hasClosingAngleBracket = !!findChildOfKind(jsxClosingElement, SyntaxKind.GreaterThanToken, sourceFile); + const tagName = jsxClosingElement.parent.openingElement.tagName; + const closingTag = tagName.getText(sourceFile); + const fullClosingTag = closingTag + (hasClosingAngleBracket ? "" : ">"); + const replacementSpan = createTextSpanFromNode(jsxClosingElement.tagName); + + const entry: CompletionEntry = { + name: fullClosingTag, + kind: ScriptElementKind.classElement, + kindModifiers: undefined, + sortText: SortText.LocationPriority, + }; + return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, optionalReplacementSpan: replacementSpan, entries: [entry] }; } + return; +} - function getJsxClosingTagCompletion(location: Node | undefined, sourceFile: SourceFile): CompletionInfo | undefined { - // We wanna walk up the tree till we find a JSX closing element - const jsxClosingElement = findAncestor(location, node => { - switch (node.kind) { - case SyntaxKind.JsxClosingElement: - return true; - case SyntaxKind.SlashToken: - case SyntaxKind.GreaterThanToken: - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: - return false; - default: - return "quit"; - } - }) as JsxClosingElement | undefined; - - if (jsxClosingElement) { - // In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag, - // instead of simply giving unknown value, the completion will return the tag-name of an associated opening-element. - // For example: - // var x =
" with type any - // And at `
` (with a closing `>`), the completion list will contain "div". - // And at property access expressions ` ` the completion will - // return full closing tag with an optional replacement span - // For example: - // var x = - // var y = - // the completion list at "1" and "2" will contain "MainComponent.Child" with a replacement span of closing tag name - const hasClosingAngleBracket = !!findChildOfKind(jsxClosingElement, SyntaxKind.GreaterThanToken, sourceFile); - const tagName = jsxClosingElement.parent.openingElement.tagName; - const closingTag = tagName.getText(sourceFile); - const fullClosingTag = closingTag + (hasClosingAngleBracket ? "" : ">"); - const replacementSpan = createTextSpanFromNode(jsxClosingElement.tagName); - - const entry: CompletionEntry = { - name: fullClosingTag, - kind: ScriptElementKind.classElement, - kindModifiers: undefined, - sortText: SortText.LocationPriority, - }; - return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, optionalReplacementSpan: replacementSpan, entries: [entry] }; +function getJSCompletionEntries( + sourceFile: SourceFile, + position: number, + uniqueNames: UniqueNameSet, + target: ScriptTarget, + entries: Push): void { + getNameTable(sourceFile).forEach((pos, name) => { + // Skip identifiers produced only from the current location + if (pos === position) { + return; + } + const realName = unescapeLeadingUnderscores(name); + if (!uniqueNames.has(realName) && isIdentifierText(realName, target)) { + uniqueNames.add(realName); + entries.push({ + name: realName, + kind: ScriptElementKind.warning, + kindModifiers: "", + sortText: SortText.JavascriptIdentifiers, + isFromUncheckedFile: true + }); } - return; - } + }); +} - function getJSCompletionEntries( - sourceFile: SourceFile, - position: number, - uniqueNames: UniqueNameSet, - target: ScriptTarget, - entries: Push): void { - getNameTable(sourceFile).forEach((pos, name) => { - // Skip identifiers produced only from the current location - if (pos === position) { - return; - } - const realName = unescapeLeadingUnderscores(name); - if (!uniqueNames.has(realName) && isIdentifierText(realName, target)) { - uniqueNames.add(realName); - entries.push({ - name: realName, - kind: ScriptElementKind.warning, - kindModifiers: "", - sortText: SortText.JavascriptIdentifiers, - isFromUncheckedFile: true - }); - } - }); - } +function completionNameForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): string { + return typeof literal === "object" ? pseudoBigIntToString(literal) + "n" : + isString(literal) ? quote(sourceFile, preferences, literal) : JSON.stringify(literal); +} - function completionNameForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): string { - return typeof literal === "object" ? pseudoBigIntToString(literal) + "n" : - isString(literal) ? quote(sourceFile, preferences, literal) : JSON.stringify(literal); - } - - function createCompletionEntryForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): CompletionEntry { - return { name: completionNameForLiteral(sourceFile, preferences, literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority }; - } - - function createCompletionEntry( - symbol: Symbol, - sortText: SortText, - replacementToken: Node | undefined, - contextToken: Node | undefined, - location: Node, - sourceFile: SourceFile, - host: LanguageServiceHost, - program: Program, - name: string, - needsConvertPropertyAccess: boolean, - origin: SymbolOriginInfo | undefined, - recommendedCompletion: Symbol | undefined, - propertyAccessToConvert: PropertyAccessExpression | undefined, - isJsxInitializer: IsJsxInitializer | undefined, - importCompletionNode: Node | undefined, - useSemicolons: boolean, - options: CompilerOptions, - preferences: UserPreferences, - completionKind: CompletionKind, - ): CompletionEntry | undefined { - let insertText: string | undefined; - let replacementSpan = getReplacementSpanForContextToken(replacementToken); - let data: CompletionEntryData | undefined; - let isSnippet: true | undefined; - let source = getSourceFromOrigin(origin); - let sourceDisplay; - let hasAction; - - const typeChecker = program.getTypeChecker(); - const insertQuestionDot = origin && originIsNullableMember(origin); - const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; - if (origin && originIsThisType(origin)) { - insertText = needsConvertPropertyAccess - ? `this${insertQuestionDot ? "?." : ""}[${quotePropertyName(sourceFile, preferences, name)}]` - : `this${insertQuestionDot ? "?." : "."}${name}`; - } - // We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790. - // Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro. - else if ((useBraces || insertQuestionDot) && propertyAccessToConvert) { - insertText = useBraces ? needsConvertPropertyAccess ? `[${quotePropertyName(sourceFile, preferences, name)}]` : `[${name}]` : name; - if (insertQuestionDot || propertyAccessToConvert.questionDotToken) { - insertText = `?.${insertText}`; - } - - const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile) || - findChildOfKind(propertyAccessToConvert, SyntaxKind.QuestionDotToken, sourceFile); - if (!dot) { - return undefined; - } - // If the text after the '.' starts with this name, write over it. Else, add new text. - const end = startsWith(name, propertyAccessToConvert.name.text) ? propertyAccessToConvert.name.end : dot.end; - replacementSpan = createTextSpanFromBounds(dot.getStart(sourceFile), end); - } +function createCompletionEntryForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): CompletionEntry { + return { name: completionNameForLiteral(sourceFile, preferences, literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority }; +} - if (isJsxInitializer) { - if (insertText === undefined) insertText = name; - insertText = `{${insertText}}`; - if (typeof isJsxInitializer !== "boolean") { - replacementSpan = createTextSpanFromNode(isJsxInitializer, sourceFile); - } +function createCompletionEntry( + symbol: Symbol, + sortText: SortText, + replacementToken: Node | undefined, + contextToken: Node | undefined, + location: Node, + sourceFile: SourceFile, + host: LanguageServiceHost, + program: Program, + name: string, + needsConvertPropertyAccess: boolean, + origin: SymbolOriginInfo | undefined, + recommendedCompletion: Symbol | undefined, + propertyAccessToConvert: PropertyAccessExpression | undefined, + isJsxInitializer: IsJsxInitializer | undefined, + importCompletionNode: Node | undefined, + useSemicolons: boolean, + options: CompilerOptions, + preferences: UserPreferences, + completionKind: CompletionKind, +): CompletionEntry | undefined { + let insertText: string | undefined; + let replacementSpan = getReplacementSpanForContextToken(replacementToken); + let data: CompletionEntryData | undefined; + let isSnippet: true | undefined; + let source = getSourceFromOrigin(origin); + let sourceDisplay; + let hasAction; + + const typeChecker = program.getTypeChecker(); + const insertQuestionDot = origin && originIsNullableMember(origin); + const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; + if (origin && originIsThisType(origin)) { + insertText = needsConvertPropertyAccess + ? `this${insertQuestionDot ? "?." : ""}[${quotePropertyName(sourceFile, preferences, name)}]` + : `this${insertQuestionDot ? "?." : "."}${name}`; + } + // We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790. + // Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro. + else if ((useBraces || insertQuestionDot) && propertyAccessToConvert) { + insertText = useBraces ? needsConvertPropertyAccess ? `[${quotePropertyName(sourceFile, preferences, name)}]` : `[${name}]` : name; + if (insertQuestionDot || propertyAccessToConvert.questionDotToken) { + insertText = `?.${insertText}`; + } + + const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile) || + findChildOfKind(propertyAccessToConvert, SyntaxKind.QuestionDotToken, sourceFile); + if (!dot) { + return undefined; } - if (origin && originIsPromise(origin) && propertyAccessToConvert) { - if (insertText === undefined) insertText = name; - const precedingToken = findPrecedingToken(propertyAccessToConvert.pos, sourceFile); - let awaitText = ""; - if (precedingToken && positionIsASICandidate(precedingToken.end, precedingToken.parent, sourceFile)) { - awaitText = ";"; - } + // If the text after the '.' starts with this name, write over it. Else, add new text. + const end = startsWith(name, propertyAccessToConvert.name.text) ? propertyAccessToConvert.name.end : dot.end; + replacementSpan = createTextSpanFromBounds(dot.getStart(sourceFile), end); + } - awaitText += `(await ${propertyAccessToConvert.expression.getText()})`; - insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}${insertQuestionDot ? "?." : "."}${insertText}`; - replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end); + if (isJsxInitializer) { + if (insertText === undefined) insertText = name; + insertText = `{${insertText}}`; + if (typeof isJsxInitializer !== "boolean") { + replacementSpan = createTextSpanFromNode(isJsxInitializer, sourceFile); } - - if (originIsResolvedExport(origin)) { - sourceDisplay = [textPart(origin.moduleSpecifier)]; - if (importCompletionNode) { - ({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importCompletionNode, contextToken, origin, useSemicolons, options, preferences)); - isSnippet = preferences.includeCompletionsWithSnippetText ? true : undefined; - } + } + if (origin && originIsPromise(origin) && propertyAccessToConvert) { + if (insertText === undefined) insertText = name; + const precedingToken = findPrecedingToken(propertyAccessToConvert.pos, sourceFile); + let awaitText = ""; + if (precedingToken && positionIsASICandidate(precedingToken.end, precedingToken.parent, sourceFile)) { + awaitText = ";"; } - if (preferences.includeCompletionsWithClassMemberSnippets && - preferences.includeCompletionsWithInsertText && - completionKind === CompletionKind.MemberLike && - isClassLikeMemberCompletion(symbol, location)) { - let importAdder; - ({ insertText, isSnippet, importAdder } = getEntryForMemberCompletion(host, program, options, preferences, name, symbol, location, contextToken)); - if (importAdder?.hasFixes()) { - hasAction = true; - source = CompletionSource.ClassMemberSnippet; - } + awaitText += `(await ${propertyAccessToConvert.expression.getText()})`; + insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}${insertQuestionDot ? "?." : "."}${insertText}`; + replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end); + } + + if (originIsResolvedExport(origin)) { + sourceDisplay = [textPart(origin.moduleSpecifier)]; + if (importCompletionNode) { + ({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importCompletionNode, contextToken, origin, useSemicolons, options, preferences)); + isSnippet = preferences.includeCompletionsWithSnippetText ? true : undefined; } + } - const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location); - if (kind === ScriptElementKind.jsxAttribute && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") { - let useBraces = preferences.jsxAttributeCompletionStyle === "braces"; - const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location); + if (preferences.includeCompletionsWithClassMemberSnippets && + preferences.includeCompletionsWithInsertText && + completionKind === CompletionKind.MemberLike && + isClassLikeMemberCompletion(symbol, location)) { + let importAdder; + ({ insertText, isSnippet, importAdder } = getEntryForMemberCompletion(host, program, options, preferences, name, symbol, location, contextToken)); + if (importAdder?.hasFixes()) { + hasAction = true; + source = CompletionSource.ClassMemberSnippet; + } + } - // If is boolean like or undefined, don't return a snippet we want just to return the completion. - if (preferences.jsxAttributeCompletionStyle === "auto" - && !(type.flags & TypeFlags.BooleanLike) - && !(type.flags & TypeFlags.Union && find((type as UnionType).types, type => !!(type.flags & TypeFlags.BooleanLike))) - ) { - if (type.flags & TypeFlags.StringLike || (type.flags & TypeFlags.Union && every((type as UnionType).types, type => !!(type.flags & (TypeFlags.StringLike | TypeFlags.Undefined))))) { - // If is string like or undefined use quotes - insertText = `${escapeSnippetText(name)}=${quote(sourceFile, preferences, "$1")}`; - isSnippet = true; - } - else { - // Use braces for everything else - useBraces = true; - } - } + const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location); + if (kind === ScriptElementKind.jsxAttribute && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") { + let useBraces = preferences.jsxAttributeCompletionStyle === "braces"; + const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location); - if (useBraces) { - insertText = `${escapeSnippetText(name)}={$1}`; + // If is boolean like or undefined, don't return a snippet we want just to return the completion. + if (preferences.jsxAttributeCompletionStyle === "auto" + && !(type.flags & TypeFlags.BooleanLike) + && !(type.flags & TypeFlags.Union && find((type as UnionType).types, type => !!(type.flags & TypeFlags.BooleanLike))) + ) { + if (type.flags & TypeFlags.StringLike || (type.flags & TypeFlags.Union && every((type as UnionType).types, type => !!(type.flags & (TypeFlags.StringLike | TypeFlags.Undefined))))) { + // If is string like or undefined use quotes + insertText = `${escapeSnippetText(name)}=${quote(sourceFile, preferences, "$1")}`; isSnippet = true; } + else { + // Use braces for everything else + useBraces = true; + } } - if (insertText !== undefined && !preferences.includeCompletionsWithInsertText) { - return undefined; + if (useBraces) { + insertText = `${escapeSnippetText(name)}={$1}`; + isSnippet = true; } + } - if (originIsExport(origin) || originIsResolvedExport(origin)) { - data = originToCompletionEntryData(origin); - hasAction = !importCompletionNode; - } + if (insertText !== undefined && !preferences.includeCompletionsWithInsertText) { + return undefined; + } - // TODO(drosen): Right now we just permit *all* semantic meanings when calling - // 'getSymbolKind' which is permissible given that it is backwards compatible; but - // really we should consider passing the meaning for the node so that we don't report - // that a suggestion for a value is an interface. We COULD also just do what - // 'getSymbolModifiers' does, which is to use the first declaration. + if (originIsExport(origin) || originIsResolvedExport(origin)) { + data = originToCompletionEntryData(origin); + hasAction = !importCompletionNode; + } + + // TODO(drosen): Right now we just permit *all* semantic meanings when calling + // 'getSymbolKind' which is permissible given that it is backwards compatible; but + // really we should consider passing the meaning for the node so that we don't report + // that a suggestion for a value is an interface. We COULD also just do what + // 'getSymbolModifiers' does, which is to use the first declaration. + + // Use a 'sortText' of 0' so that all symbol completion entries come before any other + // entries (like JavaScript identifier entries). + return { + name, + kind, + kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), + sortText, + source, + hasAction: hasAction ? true : undefined, + isRecommended: isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker) || undefined, + insertText, + replacementSpan, + sourceDisplay, + isSnippet, + isPackageJsonImport: originIsPackageJsonImport(origin) || undefined, + isImportStatementCompletion: !!importCompletionNode || undefined, + data, + }; +} - // Use a 'sortText' of 0' so that all symbol completion entries come before any other - // entries (like JavaScript identifier entries). - return { - name, - kind, - kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), - sortText, - source, - hasAction: hasAction ? true : undefined, - isRecommended: isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker) || undefined, - insertText, - replacementSpan, - sourceDisplay, - isSnippet, - isPackageJsonImport: originIsPackageJsonImport(origin) || undefined, - isImportStatementCompletion: !!importCompletionNode || undefined, - data, - }; +function isClassLikeMemberCompletion(symbol: Symbol, location: Node): boolean { + // TODO: support JS files. + if (isInJSFile(location)) { + return false; } - function isClassLikeMemberCompletion(symbol: Symbol, location: Node): boolean { - // TODO: support JS files. - if (isInJSFile(location)) { - return false; - } - - // Completion symbol must be for a class member. - const memberFlags = - SymbolFlags.ClassMember - & SymbolFlags.EnumMemberExcludes; - /* In - `class C { - | - }` - `location` is a class-like declaration. - In - `class C { - m| - }` - `location` is an identifier, - `location.parent` is a class element declaration, - and `location.parent.parent` is a class-like declaration. - In - `abstract class C { - abstract - abstract m| - }` - `location` is a syntax list (with modifiers as children), - and `location.parent` is a class-like declaration. - */ - return !!(symbol.flags & memberFlags) && + // Completion symbol must be for a class member. + const memberFlags = + SymbolFlags.ClassMember + & SymbolFlags.EnumMemberExcludes; + /* In + `class C { + | + }` + `location` is a class-like declaration. + In + `class C { + m| + }` + `location` is an identifier, + `location.parent` is a class element declaration, + and `location.parent.parent` is a class-like declaration. + In + `abstract class C { + abstract + abstract m| + }` + `location` is a syntax list (with modifiers as children), + and `location.parent` is a class-like declaration. + */ + return !!(symbol.flags & memberFlags) && + ( + isClassLike(location) || ( - isClassLike(location) || - ( - location.parent && - location.parent.parent && - isClassElement(location.parent) && - location === location.parent.name && - isClassLike(location.parent.parent) - ) || - ( - location.parent && - isSyntaxList(location) && - isClassLike(location.parent) - ) - ); - } - - function getEntryForMemberCompletion( - host: LanguageServiceHost, - program: Program, - options: CompilerOptions, - preferences: UserPreferences, - name: string, - symbol: Symbol, - location: Node, - contextToken: Node | undefined, - ): { insertText: string, isSnippet?: true, importAdder?: codefix.ImportAdder } { - const classLikeDeclaration = findAncestor(location, isClassLike); - if (!classLikeDeclaration) { - return { insertText: name }; - } - - let isSnippet: true | undefined; - let insertText: string = name; - - const checker = program.getTypeChecker(); - const sourceFile = location.getSourceFile(); - const printer = createSnippetPrinter({ - removeComments: true, - module: options.module, - target: options.target, - omitTrailingSemicolon: false, - newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), - }); - const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); + location.parent && + location.parent.parent && + isClassElement(location.parent) && + location === location.parent.name && + isClassLike(location.parent.parent) + ) || + ( + location.parent && + isSyntaxList(location) && + isClassLike(location.parent) + ) + ); +} - let body; - if (preferences.includeCompletionsWithSnippetText) { - isSnippet = true; - // We are adding a tabstop (i.e. `$0`) in the body of the suggested member, - // if it has one, so that the cursor ends up in the body once the completion is inserted. - // Note: this assumes we won't have more than one body in the completion nodes, which should be the case. - const emptyStatement = factory.createExpressionStatement(factory.createIdentifier("")); - setSnippetElement(emptyStatement, { kind: SnippetKind.TabStop, order: 0 }); - body = factory.createBlock([emptyStatement], /* multiline */ true); - } - else { - body = factory.createBlock([], /* multiline */ true); - } - - let modifiers = ModifierFlags.None; - // Whether the suggested member should be abstract. - // e.g. in `abstract class C { abstract | }`, we should offer abstract method signatures at position `|`. - // Note: We are relying on checking if the context token is `abstract`, - // since other visibility modifiers (e.g. `protected`) should come *before* `abstract`. - // However, that is not true for the e.g. `override` modifier, so this check has its limitations. - const isAbstract = contextToken && isModifierLike(contextToken) === SyntaxKind.AbstractKeyword; - const completionNodes: Node[] = []; - codefix.addNewNodeForMemberSymbol( - symbol, - classLikeDeclaration, - sourceFile, - { program, host }, - preferences, - importAdder, - // `addNewNodeForMemberSymbol` calls this callback function for each new member node - // it adds for the given member symbol. - // We store these member nodes in the `completionNodes` array. - // Note: there might be: - // - No nodes if `addNewNodeForMemberSymbol` cannot figure out a node for the member; - // - One node; - // - More than one node if the member is overloaded (e.g. a method with overload signatures). - node => { - let requiredModifiers = ModifierFlags.None; - if (isAbstract) { - requiredModifiers |= ModifierFlags.Abstract; - } - if (isClassElement(node) - && checker.getMemberOverrideModifierStatus(classLikeDeclaration, node) === MemberOverrideStatus.NeedsOverride) { - requiredModifiers |= ModifierFlags.Override; - } +function getEntryForMemberCompletion( + host: LanguageServiceHost, + program: Program, + options: CompilerOptions, + preferences: UserPreferences, + name: string, + symbol: Symbol, + location: Node, + contextToken: Node | undefined, +): { insertText: string, isSnippet?: true, importAdder?: codefix.ImportAdder } { + const classLikeDeclaration = findAncestor(location, isClassLike); + if (!classLikeDeclaration) { + return { insertText: name }; + } + + let isSnippet: true | undefined; + let insertText: string = name; + + const checker = program.getTypeChecker(); + const sourceFile = location.getSourceFile(); + const printer = createSnippetPrinter({ + removeComments: true, + module: options.module, + target: options.target, + omitTrailingSemicolon: false, + newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), + }); + const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); + + let body; + if (preferences.includeCompletionsWithSnippetText) { + isSnippet = true; + // We are adding a tabstop (i.e. `$0`) in the body of the suggested member, + // if it has one, so that the cursor ends up in the body once the completion is inserted. + // Note: this assumes we won't have more than one body in the completion nodes, which should be the case. + const emptyStatement = factory.createExpressionStatement(factory.createIdentifier("")); + setSnippetElement(emptyStatement, { kind: SnippetKind.TabStop, order: 0 }); + body = factory.createBlock([emptyStatement], /* multiline */ true); + } + else { + body = factory.createBlock([], /* multiline */ true); + } + + let modifiers = ModifierFlags.None; + // Whether the suggested member should be abstract. + // e.g. in `abstract class C { abstract | }`, we should offer abstract method signatures at position `|`. + // Note: We are relying on checking if the context token is `abstract`, + // since other visibility modifiers (e.g. `protected`) should come *before* `abstract`. + // However, that is not true for the e.g. `override` modifier, so this check has its limitations. + const isAbstract = contextToken && isModifierLike(contextToken) === SyntaxKind.AbstractKeyword; + const completionNodes: Node[] = []; + codefix.addNewNodeForMemberSymbol( + symbol, + classLikeDeclaration, + sourceFile, + { program, host }, + preferences, + importAdder, + // `addNewNodeForMemberSymbol` calls this callback function for each new member node + // it adds for the given member symbol. + // We store these member nodes in the `completionNodes` array. + // Note: there might be: + // - No nodes if `addNewNodeForMemberSymbol` cannot figure out a node for the member; + // - One node; + // - More than one node if the member is overloaded (e.g. a method with overload signatures). + node => { + let requiredModifiers = ModifierFlags.None; + if (isAbstract) { + requiredModifiers |= ModifierFlags.Abstract; + } + if (isClassElement(node) + && checker.getMemberOverrideModifierStatus(classLikeDeclaration, node) === MemberOverrideStatus.NeedsOverride) { + requiredModifiers |= ModifierFlags.Override; + } + + let presentModifiers = ModifierFlags.None; + if (!completionNodes.length) { + // Omit already present modifiers from the first completion node/signature. + if (contextToken) { + presentModifiers = getPresentModifiers(contextToken); + } + // Keep track of added missing required modifiers and modifiers already present. + // This is needed when we have overloaded signatures, + // so this callback will be called for multiple nodes/signatures, + // and we need to make sure the modifiers are uniform for all nodes/signatures. + modifiers = node.modifierFlagsCache | requiredModifiers | presentModifiers; + } + node = factory.updateModifiers(node, modifiers & (~presentModifiers)); + + completionNodes.push(node); + }, + body, + codefix.PreserveOptionalFlags.Property, + isAbstract); + + if (completionNodes.length) { + insertText = printer.printSnippetList( + ListFormat.MultiLine | ListFormat.NoTrailingNewLine, + factory.createNodeArray(completionNodes), + sourceFile); + } + + return { insertText, isSnippet, importAdder }; +} - let presentModifiers = ModifierFlags.None; - if (!completionNodes.length) { - // Omit already present modifiers from the first completion node/signature. - if (contextToken) { - presentModifiers = getPresentModifiers(contextToken); - } - // Keep track of added missing required modifiers and modifiers already present. - // This is needed when we have overloaded signatures, - // so this callback will be called for multiple nodes/signatures, - // and we need to make sure the modifiers are uniform for all nodes/signatures. - modifiers = node.modifierFlagsCache | requiredModifiers | presentModifiers; - } - node = factory.updateModifiers(node, modifiers & (~presentModifiers)); - - completionNodes.push(node); - }, - body, - codefix.PreserveOptionalFlags.Property, - isAbstract); - - if (completionNodes.length) { - insertText = printer.printSnippetList( - ListFormat.MultiLine | ListFormat.NoTrailingNewLine, - factory.createNodeArray(completionNodes), - sourceFile); - } - - return { insertText, isSnippet, importAdder }; - } - - function getPresentModifiers(contextToken: Node): ModifierFlags { - let modifiers = ModifierFlags.None; - let contextMod; - /* - Cases supported: - In - `class C { - public abstract | - }` - `contextToken` is ``abstract`` (as an identifier), - `contextToken.parent` is property declaration, - `location` is class declaration ``class C { ... }``. - In - `class C { - protected override m| - }` - `contextToken` is ``override`` (as a keyword), - `contextToken.parent` is property declaration, - `location` is identifier ``m``, - `location.parent` is property declaration ``protected override m``, - `location.parent.parent` is class declaration ``class C { ... }``. - */ - if (contextMod = isModifierLike(contextToken)) { - modifiers |= modifierToFlag(contextMod); - } - if (isPropertyDeclaration(contextToken.parent)) { - modifiers |= modifiersToFlags(contextToken.parent.modifiers); - } - return modifiers; - } - - function isModifierLike(node: Node): ModifierSyntaxKind | undefined { - if (isModifier(node)) { - return node.kind; - } - if (isIdentifier(node) && node.originalKeywordKind && isModifierKind(node.originalKeywordKind)) { - return node.originalKeywordKind; - } - return undefined; +function getPresentModifiers(contextToken: Node): ModifierFlags { + let modifiers = ModifierFlags.None; + let contextMod; + /* + Cases supported: + In + `class C { + public abstract | + }` + `contextToken` is ``abstract`` (as an identifier), + `contextToken.parent` is property declaration, + `location` is class declaration ``class C { ... }``. + In + `class C { + protected override m| + }` + `contextToken` is ``override`` (as a keyword), + `contextToken.parent` is property declaration, + `location` is identifier ``m``, + `location.parent` is property declaration ``protected override m``, + `location.parent.parent` is class declaration ``class C { ... }``. + */ + if (contextMod = isModifierLike(contextToken)) { + modifiers |= modifierToFlag(contextMod); + } + if (isPropertyDeclaration(contextToken.parent)) { + modifiers |= modifiersToFlags(contextToken.parent.modifiers); + } + return modifiers; +} + +function isModifierLike(node: Node): ModifierSyntaxKind | undefined { + if (isModifier(node)) { + return node.kind; } + if (isIdentifier(node) && node.originalKeywordKind && isModifierKind(node.originalKeywordKind)) { + return node.originalKeywordKind; + } + return undefined; +} - function createSnippetPrinter( - printerOptions: PrinterOptions, - ) { - const printer = createPrinter(printerOptions); - const baseWriter = createTextWriter(getNewLineCharacter(printerOptions)); - const writer: EmitTextWriter = { - ...baseWriter, - write: s => baseWriter.write(escapeSnippetText(s)), - nonEscapingWrite: baseWriter.write, - writeLiteral: s => baseWriter.writeLiteral(escapeSnippetText(s)), - writeStringLiteral: s => baseWriter.writeStringLiteral(escapeSnippetText(s)), - writeSymbol: (s, symbol) => baseWriter.writeSymbol(escapeSnippetText(s), symbol), - writeParameter: s => baseWriter.writeParameter(escapeSnippetText(s)), - writeComment: s => baseWriter.writeComment(escapeSnippetText(s)), - writeProperty: s => baseWriter.writeProperty(escapeSnippetText(s)), - }; +function createSnippetPrinter( + printerOptions: PrinterOptions, +) { + const printer = createPrinter(printerOptions); + const baseWriter = createTextWriter(getNewLineCharacter(printerOptions)); + const writer: EmitTextWriter = { + ...baseWriter, + write: s => baseWriter.write(escapeSnippetText(s)), + nonEscapingWrite: baseWriter.write, + writeLiteral: s => baseWriter.writeLiteral(escapeSnippetText(s)), + writeStringLiteral: s => baseWriter.writeStringLiteral(escapeSnippetText(s)), + writeSymbol: (s, symbol) => baseWriter.writeSymbol(escapeSnippetText(s), symbol), + writeParameter: s => baseWriter.writeParameter(escapeSnippetText(s)), + writeComment: s => baseWriter.writeComment(escapeSnippetText(s)), + writeProperty: s => baseWriter.writeProperty(escapeSnippetText(s)), + }; - return { - printSnippetList, - }; + return { + printSnippetList, + }; - /* Snippet-escaping version of `printer.printList`. */ - function printSnippetList( - format: ListFormat, - list: NodeArray, - sourceFile: SourceFile | undefined, - ): string { - writer.clear(); - printer.writeList(format, list, sourceFile, writer); - return writer.getText(); - } - } - - function originToCompletionEntryData(origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport): CompletionEntryData | undefined { - const ambientModuleName = origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name); - const isPackageJsonImport = origin.isFromPackageJson ? true : undefined; - if (originIsResolvedExport(origin)) { - const resolvedData: CompletionEntryDataResolved = { - exportName: origin.exportName, - moduleSpecifier: origin.moduleSpecifier, - ambientModuleName, - fileName: origin.fileName, - isPackageJsonImport, - }; - return resolvedData; - } - const unresolvedData: CompletionEntryDataUnresolved = { + /* Snippet-escaping version of `printer.printList`. */ + function printSnippetList( + format: ListFormat, + list: NodeArray, + sourceFile: SourceFile | undefined, + ): string { + writer.clear(); + printer.writeList(format, list, sourceFile, writer); + return writer.getText(); + } +} + +function originToCompletionEntryData(origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport): CompletionEntryData | undefined { + const ambientModuleName = origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name); + const isPackageJsonImport = origin.isFromPackageJson ? true : undefined; + if (originIsResolvedExport(origin)) { + const resolvedData: CompletionEntryDataResolved = { exportName: origin.exportName, - exportMapKey: origin.exportMapKey, + moduleSpecifier: origin.moduleSpecifier, + ambientModuleName, fileName: origin.fileName, - ambientModuleName: origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name), - isPackageJsonImport: origin.isFromPackageJson ? true : undefined, + isPackageJsonImport, }; - return unresolvedData; - } - - function completionEntryDataToSymbolOriginInfo(data: CompletionEntryData, completionName: string, moduleSymbol: Symbol): SymbolOriginInfoExport | SymbolOriginInfoResolvedExport { - const isDefaultExport = data.exportName === InternalSymbolName.Default; - const isFromPackageJson = !!data.isPackageJsonImport; - if (completionEntryDataIsResolved(data)) { - const resolvedOrigin: SymbolOriginInfoResolvedExport = { - kind: SymbolOriginInfoKind.ResolvedExport, - exportName: data.exportName, - moduleSpecifier: data.moduleSpecifier, - symbolName: completionName, - fileName: data.fileName, - moduleSymbol, - isDefaultExport, - isFromPackageJson, - }; - return resolvedOrigin; - } - const unresolvedOrigin: SymbolOriginInfoExport = { - kind: SymbolOriginInfoKind.Export, + return resolvedData; + } + const unresolvedData: CompletionEntryDataUnresolved = { + exportName: origin.exportName, + exportMapKey: origin.exportMapKey, + fileName: origin.fileName, + ambientModuleName: origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name), + isPackageJsonImport: origin.isFromPackageJson ? true : undefined, + }; + return unresolvedData; +} + +function completionEntryDataToSymbolOriginInfo(data: CompletionEntryData, completionName: string, moduleSymbol: Symbol): SymbolOriginInfoExport | SymbolOriginInfoResolvedExport { + const isDefaultExport = data.exportName === InternalSymbolName.Default; + const isFromPackageJson = !!data.isPackageJsonImport; + if (completionEntryDataIsResolved(data)) { + const resolvedOrigin: SymbolOriginInfoResolvedExport = { + kind: SymbolOriginInfoKind.ResolvedExport, exportName: data.exportName, - exportMapKey: data.exportMapKey, + moduleSpecifier: data.moduleSpecifier, symbolName: completionName, fileName: data.fileName, moduleSymbol, isDefaultExport, isFromPackageJson, }; - return unresolvedOrigin; - } - - function getInsertTextAndReplacementSpanForImportCompletion(name: string, importCompletionNode: Node, contextToken: Node | undefined, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, options: CompilerOptions, preferences: UserPreferences) { - const sourceFile = importCompletionNode.getSourceFile(); - const replacementSpan = createTextSpanFromNode(findAncestor(importCompletionNode, or(isImportDeclaration, isImportEqualsDeclaration)) || importCompletionNode, sourceFile); - const quotedModuleSpecifier = quote(sourceFile, preferences, origin.moduleSpecifier); - const exportKind = - origin.isDefaultExport ? ExportKind.Default : - origin.exportName === InternalSymbolName.ExportEquals ? ExportKind.ExportEquals : - ExportKind.Named; - const tabStop = preferences.includeCompletionsWithSnippetText ? "$1" : ""; - const importKind = codefix.getImportKind(sourceFile, exportKind, options, /*forceImportKeyword*/ true); - const isTopLevelTypeOnly = tryCast(importCompletionNode, isImportDeclaration)?.importClause?.isTypeOnly || tryCast(importCompletionNode, isImportEqualsDeclaration)?.isTypeOnly; - const isImportSpecifierTypeOnly = couldBeTypeOnlyImportSpecifier(importCompletionNode, contextToken); - const topLevelTypeOnlyText = isTopLevelTypeOnly ? ` ${tokenToString(SyntaxKind.TypeKeyword)} ` : " "; - const importSpecifierTypeOnlyText = isImportSpecifierTypeOnly ? `${tokenToString(SyntaxKind.TypeKeyword)} ` : ""; - const suffix = useSemicolons ? ";" : ""; - switch (importKind) { - case ImportKind.CommonJS: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` }; - case ImportKind.Default: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` }; - case ImportKind.Namespace: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}* as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` }; - case ImportKind.Named: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}{ ${importSpecifierTypeOnlyText}${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` }; - } - } - - function quotePropertyName(sourceFile: SourceFile, preferences: UserPreferences, name: string,): string { - if (/^\d+$/.test(name)) { - return name; - } - - return quote(sourceFile, preferences, name); - } - - function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol | undefined, checker: TypeChecker): boolean { - return localSymbol === recommendedCompletion || - !!(localSymbol.flags & SymbolFlags.ExportValue) && checker.getExportSymbolOfSymbol(localSymbol) === recommendedCompletion; - } - - function getSourceFromOrigin(origin: SymbolOriginInfo | undefined): string | undefined { - if (originIsExport(origin)) { - return stripQuotes(origin.moduleSymbol.name); - } - if (originIsResolvedExport(origin)) { - return origin.moduleSpecifier; - } - if (origin?.kind === SymbolOriginInfoKind.ThisType) { - return CompletionSource.ThisProperty; - } - } - - export function getCompletionEntriesFromSymbols( - symbols: readonly Symbol[], - entries: Push, - replacementToken: Node | undefined, - contextToken: Node | undefined, - location: Node, - sourceFile: SourceFile, - host: LanguageServiceHost, - program: Program, - target: ScriptTarget, - log: Log, - kind: CompletionKind, - preferences: UserPreferences, - compilerOptions: CompilerOptions, - isTypeOnlyLocation?: boolean, - propertyAccessToConvert?: PropertyAccessExpression, - jsxIdentifierExpected?: boolean, - isJsxInitializer?: IsJsxInitializer, - importCompletionNode?: Node, - recommendedCompletion?: Symbol, - symbolToOriginInfoMap?: SymbolOriginInfoMap, - symbolToSortTextIdMap?: SymbolSortTextIdMap, - ): UniqueNameSet { - const start = timestamp(); - const variableDeclaration = getVariableDeclaration(location); - const useSemicolons = probablyUsesSemicolons(sourceFile); - const typeChecker = program.getTypeChecker(); - // Tracks unique names. - // Value is set to false for global variables or completions from external module exports, because we can have multiple of those; - // true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports. - // So adding a completion for a local will prevent us from adding completions for external module exports sharing the same name. - const uniques = new Map(); - for (let i = 0; i < symbols.length; i++) { - const symbol = symbols[i]; - const origin = symbolToOriginInfoMap?.[i]; - const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind, !!jsxIdentifierExpected); - if (!info || uniques.get(info.name) || kind === CompletionKind.Global && symbolToSortTextIdMap && !shouldIncludeSymbol(symbol, symbolToSortTextIdMap)) { - continue; - } + return resolvedOrigin; + } + const unresolvedOrigin: SymbolOriginInfoExport = { + kind: SymbolOriginInfoKind.Export, + exportName: data.exportName, + exportMapKey: data.exportMapKey, + symbolName: completionName, + fileName: data.fileName, + moduleSymbol, + isDefaultExport, + isFromPackageJson, + }; + return unresolvedOrigin; +} - const { name, needsConvertPropertyAccess } = info; - const sortTextId = symbolToSortTextIdMap?.[getSymbolId(symbol)] ?? SortTextId.LocationPriority; - const sortText = (isDeprecated(symbol, typeChecker) ? SortTextId.DeprecatedOffset + sortTextId : sortTextId).toString() as SortText; - const entry = createCompletionEntry( - symbol, - sortText, - replacementToken, - contextToken, - location, - sourceFile, - host, - program, - name, - needsConvertPropertyAccess, - origin, - recommendedCompletion, - propertyAccessToConvert, - isJsxInitializer, - importCompletionNode, - useSemicolons, - compilerOptions, - preferences, - kind, - ); - if (!entry) { - continue; - } +function getInsertTextAndReplacementSpanForImportCompletion(name: string, importCompletionNode: Node, contextToken: Node | undefined, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, options: CompilerOptions, preferences: UserPreferences) { + const sourceFile = importCompletionNode.getSourceFile(); + const replacementSpan = createTextSpanFromNode(findAncestor(importCompletionNode, or(isImportDeclaration, isImportEqualsDeclaration)) || importCompletionNode, sourceFile); + const quotedModuleSpecifier = quote(sourceFile, preferences, origin.moduleSpecifier); + const exportKind = + origin.isDefaultExport ? ExportKind.Default : + origin.exportName === InternalSymbolName.ExportEquals ? ExportKind.ExportEquals : + ExportKind.Named; + const tabStop = preferences.includeCompletionsWithSnippetText ? "$1" : ""; + const importKind = codefix.getImportKind(sourceFile, exportKind, options, /*forceImportKeyword*/ true); + const isTopLevelTypeOnly = tryCast(importCompletionNode, isImportDeclaration)?.importClause?.isTypeOnly || tryCast(importCompletionNode, isImportEqualsDeclaration)?.isTypeOnly; + const isImportSpecifierTypeOnly = couldBeTypeOnlyImportSpecifier(importCompletionNode, contextToken); + const topLevelTypeOnlyText = isTopLevelTypeOnly ? ` ${tokenToString(SyntaxKind.TypeKeyword)} ` : " "; + const importSpecifierTypeOnlyText = isImportSpecifierTypeOnly ? `${tokenToString(SyntaxKind.TypeKeyword)} ` : ""; + const suffix = useSemicolons ? ";" : ""; + switch (importKind) { + case ImportKind.CommonJS: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` }; + case ImportKind.Default: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` }; + case ImportKind.Namespace: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}* as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` }; + case ImportKind.Named: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}{ ${importSpecifierTypeOnlyText}${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` }; + } +} - /** True for locals; false for globals, module exports from other files, `this.` completions. */ - const shouldShadowLaterSymbols = !origin && !(symbol.parent === undefined && !some(symbol.declarations, d => d.getSourceFile() === location.getSourceFile())); - uniques.set(name, shouldShadowLaterSymbols); - entries.push(entry); +function quotePropertyName(sourceFile: SourceFile, preferences: UserPreferences, name: string,): string { + if (/^\d+$/.test(name)) { + return name; + } + + return quote(sourceFile, preferences, name); +} + +function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol | undefined, checker: TypeChecker): boolean { + return localSymbol === recommendedCompletion || + !!(localSymbol.flags & SymbolFlags.ExportValue) && checker.getExportSymbolOfSymbol(localSymbol) === recommendedCompletion; +} + +function getSourceFromOrigin(origin: SymbolOriginInfo | undefined): string | undefined { + if (originIsExport(origin)) { + return stripQuotes(origin.moduleSymbol.name); + } + if (originIsResolvedExport(origin)) { + return origin.moduleSpecifier; + } + if (origin?.kind === SymbolOriginInfoKind.ThisType) { + return CompletionSource.ThisProperty; + } +} + +export function getCompletionEntriesFromSymbols( + symbols: readonly Symbol[], + entries: Push, + replacementToken: Node | undefined, + contextToken: Node | undefined, + location: Node, + sourceFile: SourceFile, + host: LanguageServiceHost, + program: Program, + target: ScriptTarget, + log: Log, + kind: CompletionKind, + preferences: UserPreferences, + compilerOptions: CompilerOptions, + isTypeOnlyLocation?: boolean, + propertyAccessToConvert?: PropertyAccessExpression, + jsxIdentifierExpected?: boolean, + isJsxInitializer?: IsJsxInitializer, + importCompletionNode?: Node, + recommendedCompletion?: Symbol, + symbolToOriginInfoMap?: SymbolOriginInfoMap, + symbolToSortTextIdMap?: SymbolSortTextIdMap, +): UniqueNameSet { + const start = timestamp(); + const variableDeclaration = getVariableDeclaration(location); + const useSemicolons = probablyUsesSemicolons(sourceFile); + const typeChecker = program.getTypeChecker(); + // Tracks unique names. + // Value is set to false for global variables or completions from external module exports, because we can have multiple of those; + // true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports. + // So adding a completion for a local will prevent us from adding completions for external module exports sharing the same name. + const uniques = new Map(); + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + const origin = symbolToOriginInfoMap?.[i]; + const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind, !!jsxIdentifierExpected); + if (!info || uniques.get(info.name) || kind === CompletionKind.Global && symbolToSortTextIdMap && !shouldIncludeSymbol(symbol, symbolToSortTextIdMap)) { + continue; + } + + const { name, needsConvertPropertyAccess } = info; + const sortTextId = symbolToSortTextIdMap?.[getSymbolId(symbol)] ?? SortTextId.LocationPriority; + const sortText = (isDeprecated(symbol, typeChecker) ? SortTextId.DeprecatedOffset + sortTextId : sortTextId).toString() as SortText; + const entry = createCompletionEntry( + symbol, + sortText, + replacementToken, + contextToken, + location, + sourceFile, + host, + program, + name, + needsConvertPropertyAccess, + origin, + recommendedCompletion, + propertyAccessToConvert, + isJsxInitializer, + importCompletionNode, + useSemicolons, + compilerOptions, + preferences, + kind, + ); + if (!entry) { + continue; } - log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (timestamp() - start)); + /** True for locals; false for globals, module exports from other files, `this.` completions. */ + const shouldShadowLaterSymbols = !origin && !(symbol.parent === undefined && !some(symbol.declarations, d => d.getSourceFile() === location.getSourceFile())); + uniques.set(name, shouldShadowLaterSymbols); + entries.push(entry); + } - // Prevent consumers of this map from having to worry about - // the boolean value. Externally, it should be seen as the - // set of all names. - return { - has: name => uniques.has(name), - add: name => uniques.set(name, true), - }; + log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (timestamp() - start)); - function shouldIncludeSymbol(symbol: Symbol, symbolToSortTextIdMap: SymbolSortTextIdMap): boolean { - if (!isSourceFile(location)) { - // export = /**/ here we want to get all meanings, so any symbol is ok - if (isExportAssignment(location.parent)) { - return true; - } - // Filter out variables from their own initializers - // `const a = /* no 'a' here */` - if (variableDeclaration && symbol.valueDeclaration === variableDeclaration) { - return false; - } + // Prevent consumers of this map from having to worry about + // the boolean value. Externally, it should be seen as the + // set of all names. + return { + has: name => uniques.has(name), + add: name => uniques.set(name, true), + }; - // External modules can have global export declarations that will be - // available as global keywords in all scopes. But if the external module - // already has an explicit export and user only wants to user explicit - // module imports then the global keywords will be filtered out so auto - // import suggestions will win in the completion - const symbolOrigin = skipAlias(symbol, typeChecker); - // We only want to filter out the global keywords - // Auto Imports are not available for scripts so this conditional is always false - if (!!sourceFile.externalModuleIndicator - && !compilerOptions.allowUmdGlobalAccess - && symbolToSortTextIdMap[getSymbolId(symbol)] === SortTextId.GlobalsOrKeywords - && (symbolToSortTextIdMap[getSymbolId(symbolOrigin)] === SortTextId.AutoImportSuggestions - || symbolToSortTextIdMap[getSymbolId(symbolOrigin)] === SortTextId.LocationPriority)) { - return false; - } - // Continue with origin symbol - symbol = symbolOrigin; + function shouldIncludeSymbol(symbol: Symbol, symbolToSortTextIdMap: SymbolSortTextIdMap): boolean { + if (!isSourceFile(location)) { + // export = /**/ here we want to get all meanings, so any symbol is ok + if (isExportAssignment(location.parent)) { + return true; + } + // Filter out variables from their own initializers + // `const a = /* no 'a' here */` + if (variableDeclaration && symbol.valueDeclaration === variableDeclaration) { + return false; + } - // import m = /**/ <-- It can only access namespace (if typing import = x. this would get member symbols and not namespace) - if (isInRightSideOfInternalImportEqualsDeclaration(location)) { - return !!(symbol.flags & SymbolFlags.Namespace); - } + // External modules can have global export declarations that will be + // available as global keywords in all scopes. But if the external module + // already has an explicit export and user only wants to user explicit + // module imports then the global keywords will be filtered out so auto + // import suggestions will win in the completion + const symbolOrigin = skipAlias(symbol, typeChecker); + // We only want to filter out the global keywords + // Auto Imports are not available for scripts so this conditional is always false + if (!!sourceFile.externalModuleIndicator + && !compilerOptions.allowUmdGlobalAccess + && symbolToSortTextIdMap[getSymbolId(symbol)] === SortTextId.GlobalsOrKeywords + && (symbolToSortTextIdMap[getSymbolId(symbolOrigin)] === SortTextId.AutoImportSuggestions + || symbolToSortTextIdMap[getSymbolId(symbolOrigin)] === SortTextId.LocationPriority)) { + return false; + } + // Continue with origin symbol + symbol = symbolOrigin; - if (isTypeOnlyLocation) { - // It's a type, but you can reach it by namespace.type as well - return symbolCanBeReferencedAtTypeLocation(symbol, typeChecker); - } + // import m = /**/ <-- It can only access namespace (if typing import = x. this would get member symbols and not namespace) + if (isInRightSideOfInternalImportEqualsDeclaration(location)) { + return !!(symbol.flags & SymbolFlags.Namespace); } - // expressions are value space (which includes the value namespaces) - return !!(getCombinedLocalAndExportSymbolFlags(symbol) & SymbolFlags.Value); + if (isTypeOnlyLocation) { + // It's a type, but you can reach it by namespace.type as well + return symbolCanBeReferencedAtTypeLocation(symbol, typeChecker); + } } + + // expressions are value space (which includes the value namespaces) + return !!(getCombinedLocalAndExportSymbolFlags(symbol) & SymbolFlags.Value); } +} - function getLabelCompletionAtPosition(node: BreakOrContinueStatement): CompletionInfo | undefined { - const entries = getLabelStatementCompletions(node); - if (entries.length) { - return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; - } +function getLabelCompletionAtPosition(node: BreakOrContinueStatement): CompletionInfo | undefined { + const entries = getLabelStatementCompletions(node); + if (entries.length) { + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; } +} - function getLabelStatementCompletions(node: Node): CompletionEntry[] { - const entries: CompletionEntry[] = []; - const uniques = new Map(); - let current = node; +function getLabelStatementCompletions(node: Node): CompletionEntry[] { + const entries: CompletionEntry[] = []; + const uniques = new Map(); + let current = node; - while (current) { - if (isFunctionLike(current)) { - break; - } - if (isLabeledStatement(current)) { - const name = current.label.text; - if (!uniques.has(name)) { - uniques.set(name, true); - entries.push({ - name, - kindModifiers: ScriptElementKindModifier.none, - kind: ScriptElementKind.label, - sortText: SortText.LocationPriority - }); - } - } - current = current.parent; - } - return entries; - } - - interface SymbolCompletion { - type: "symbol"; - symbol: Symbol; - location: Node; - origin: SymbolOriginInfo | SymbolOriginInfoExport | SymbolOriginInfoResolvedExport | undefined; - previousToken: Node | undefined; - contextToken: Node | undefined; - readonly isJsxInitializer: IsJsxInitializer; - readonly isTypeOnlyLocation: boolean; - } - function getSymbolCompletionFromEntryId( - program: Program, - log: Log, - sourceFile: SourceFile, - position: number, - entryId: CompletionEntryIdentifier, - host: LanguageServiceHost, - preferences: UserPreferences, - ): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "none" } { - if (entryId.data) { - const autoImport = getAutoImportSymbolFromCompletionEntryData(entryId.name, entryId.data, program, host); - if (autoImport) { - const { contextToken, previousToken } = getRelevantTokens(position, sourceFile); - return { - type: "symbol", - symbol: autoImport.symbol, - location: getTouchingPropertyName(sourceFile, position), - previousToken, - contextToken, - isJsxInitializer: false, - isTypeOnlyLocation: false, - origin: autoImport.origin, - }; + while (current) { + if (isFunctionLike(current)) { + break; + } + if (isLabeledStatement(current)) { + const name = current.label.text; + if (!uniques.has(name)) { + uniques.set(name, true); + entries.push({ + name, + kindModifiers: ScriptElementKindModifier.none, + kind: ScriptElementKind.label, + sortText: SortText.LocationPriority + }); } } + current = current.parent; + } + return entries; +} - const compilerOptions = program.getCompilerOptions(); - const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host); - if (!completionData) { - return { type: "none" }; - } - if (completionData.kind !== CompletionDataKind.Data) { - return { type: "request", request: completionData }; +interface SymbolCompletion { + type: "symbol"; + symbol: Symbol; + location: Node; + origin: SymbolOriginInfo | SymbolOriginInfoExport | SymbolOriginInfoResolvedExport | undefined; + previousToken: Node | undefined; + contextToken: Node | undefined; + readonly isJsxInitializer: IsJsxInitializer; + readonly isTypeOnlyLocation: boolean; +} +function getSymbolCompletionFromEntryId( + program: Program, + log: Log, + sourceFile: SourceFile, + position: number, + entryId: CompletionEntryIdentifier, + host: LanguageServiceHost, + preferences: UserPreferences, +): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "none" } { + if (entryId.data) { + const autoImport = getAutoImportSymbolFromCompletionEntryData(entryId.name, entryId.data, program, host); + if (autoImport) { + const { contextToken, previousToken } = getRelevantTokens(position, sourceFile); + return { + type: "symbol", + symbol: autoImport.symbol, + location: getTouchingPropertyName(sourceFile, position), + previousToken, + contextToken, + isJsxInitializer: false, + isTypeOnlyLocation: false, + origin: autoImport.origin, + }; } + } - const { symbols, literals, location, completionKind, symbolToOriginInfoMap, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } = completionData; + const compilerOptions = program.getCompilerOptions(); + const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host); + if (!completionData) { + return { type: "none" }; + } + if (completionData.kind !== CompletionDataKind.Data) { + return { type: "request", request: completionData }; + } - const literal = find(literals, l => completionNameForLiteral(sourceFile, preferences, l) === entryId.name); - if (literal !== undefined) return { type: "literal", literal }; + const { symbols, literals, location, completionKind, symbolToOriginInfoMap, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } = completionData; - // Find the symbol with the matching entry name. - // We don't need to perform character checks here because we're only comparing the - // name against 'entryName' (which is known to be good), not building a new - // completion entry. - return firstDefined(symbols, (symbol, index): SymbolCompletion | undefined => { - const origin = symbolToOriginInfoMap[index]; - const info = getCompletionEntryDisplayNameForSymbol(symbol, getEmitScriptTarget(compilerOptions), origin, completionKind, completionData.isJsxIdentifierExpected); - return info && info.name === entryId.name && (entryId.source === CompletionSource.ClassMemberSnippet && symbol.flags & SymbolFlags.ClassMember || getSourceFromOrigin(origin) === entryId.source) - ? { type: "symbol" as const, symbol, location, origin, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } - : undefined; - }) || { type: "none" }; - } - - export interface CompletionEntryIdentifier { - name: string; - source?: string; - data?: CompletionEntryData; - } - - export function getCompletionEntryDetails( - program: Program, - log: Log, - sourceFile: SourceFile, - position: number, - entryId: CompletionEntryIdentifier, - host: LanguageServiceHost, - formatContext: formatting.FormatContext, - preferences: UserPreferences, - cancellationToken: CancellationToken, - ): CompletionEntryDetails | undefined { - const typeChecker = program.getTypeChecker(); - const compilerOptions = program.getCompilerOptions(); - const { name, source, data } = entryId; - - const contextToken = findPrecedingToken(position, sourceFile); - if (isInString(sourceFile, position, contextToken)) { - return StringCompletions.getStringLiteralCompletionDetails(name, sourceFile, position, contextToken, typeChecker, compilerOptions, host, cancellationToken, preferences); - } - - // Compute all the completion symbols again. - const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences); - switch (symbolCompletion.type) { - case "request": { - const { request } = symbolCompletion; - switch (request.kind) { - case CompletionDataKind.JsDocTagName: - return JsDoc.getJSDocTagNameCompletionDetails(name); - case CompletionDataKind.JsDocTag: - return JsDoc.getJSDocTagCompletionDetails(name); - case CompletionDataKind.JsDocParameterName: - return JsDoc.getJSDocParameterNameCompletionDetails(name); - case CompletionDataKind.Keywords: - return some(request.keywordCompletions, c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined; - default: - return Debug.assertNever(request); - } - } - case "symbol": { - const { symbol, location, contextToken, origin, previousToken } = symbolCompletion; - const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(name, location, contextToken, origin, symbol, program, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences, data, source); - return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 - } - case "literal": { - const { literal } = symbolCompletion; - return createSimpleDetails(completionNameForLiteral(sourceFile, preferences, literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral); + const literal = find(literals, l => completionNameForLiteral(sourceFile, preferences, l) === entryId.name); + if (literal !== undefined) return { type: "literal", literal }; + + // Find the symbol with the matching entry name. + // We don't need to perform character checks here because we're only comparing the + // name against 'entryName' (which is known to be good), not building a new + // completion entry. + return firstDefined(symbols, (symbol, index): SymbolCompletion | undefined => { + const origin = symbolToOriginInfoMap[index]; + const info = getCompletionEntryDisplayNameForSymbol(symbol, getEmitScriptTarget(compilerOptions), origin, completionKind, completionData.isJsxIdentifierExpected); + return info && info.name === entryId.name && (entryId.source === CompletionSource.ClassMemberSnippet && symbol.flags & SymbolFlags.ClassMember || getSourceFromOrigin(origin) === entryId.source) + ? { type: "symbol" as const, symbol, location, origin, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } + : undefined; + }) || { type: "none" }; +} + +export interface CompletionEntryIdentifier { + name: string; + source?: string; + data?: CompletionEntryData; +} + +export function getCompletionEntryDetails( + program: Program, + log: Log, + sourceFile: SourceFile, + position: number, + entryId: CompletionEntryIdentifier, + host: LanguageServiceHost, + formatContext: formatting.FormatContext, + preferences: UserPreferences, + cancellationToken: CancellationToken, +): CompletionEntryDetails | undefined { + const typeChecker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); + const { name, source, data } = entryId; + + const contextToken = findPrecedingToken(position, sourceFile); + if (isInString(sourceFile, position, contextToken)) { + return StringCompletions.getStringLiteralCompletionDetails(name, sourceFile, position, contextToken, typeChecker, compilerOptions, host, cancellationToken, preferences); + } + + // Compute all the completion symbols again. + const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences); + switch (symbolCompletion.type) { + case "request": { + const { request } = symbolCompletion; + switch (request.kind) { + case CompletionDataKind.JsDocTagName: + return JsDoc.getJSDocTagNameCompletionDetails(name); + case CompletionDataKind.JsDocTag: + return JsDoc.getJSDocTagCompletionDetails(name); + case CompletionDataKind.JsDocParameterName: + return JsDoc.getJSDocParameterNameCompletionDetails(name); + case CompletionDataKind.Keywords: + return some(request.keywordCompletions, c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined; + default: + return Debug.assertNever(request); } - case "none": - // Didn't find a symbol with this name. See if we can find a keyword instead. - return allKeywordsCompletions().some(c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined; - default: - Debug.assertNever(symbolCompletion); } + case "symbol": { + const { symbol, location, contextToken, origin, previousToken } = symbolCompletion; + const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(name, location, contextToken, origin, symbol, program, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences, data, source); + return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 + } + case "literal": { + const { literal } = symbolCompletion; + return createSimpleDetails(completionNameForLiteral(sourceFile, preferences, literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral); + } + case "none": + // Didn't find a symbol with this name. See if we can find a keyword instead. + return allKeywordsCompletions().some(c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined; + default: + Debug.assertNever(symbolCompletion); } +} - function createSimpleDetails(name: string, kind: ScriptElementKind, kind2: SymbolDisplayPartKind): CompletionEntryDetails { - return createCompletionDetails(name, ScriptElementKindModifier.none, kind, [displayPart(name, kind2)]); - } +function createSimpleDetails(name: string, kind: ScriptElementKind, kind2: SymbolDisplayPartKind): CompletionEntryDetails { + return createCompletionDetails(name, ScriptElementKindModifier.none, kind, [displayPart(name, kind2)]); +} + +export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { + const { displayParts, documentation, symbolKind, tags } = + checker.runWithCancellationToken(cancellationToken, checker => + SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All) + ); + return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay); +} - export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { - const { displayParts, documentation, symbolKind, tags } = - checker.runWithCancellationToken(cancellationToken, checker => - SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All) - ); - return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay); +export function createCompletionDetails(name: string, kindModifiers: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], documentation?: SymbolDisplayPart[], tags?: JSDocTagInfo[], codeActions?: CodeAction[], source?: SymbolDisplayPart[]): CompletionEntryDetails { + return { name, kindModifiers, kind, displayParts, documentation, tags, codeActions, source, sourceDisplay: source }; +} + +interface CodeActionsAndSourceDisplay { + readonly codeActions: CodeAction[] | undefined; + readonly sourceDisplay: SymbolDisplayPart[] | undefined; +} +function getCompletionEntryCodeActionsAndSourceDisplay( + name: string, + location: Node, + contextToken: Node | undefined, + origin: SymbolOriginInfo | SymbolOriginInfoExport | SymbolOriginInfoResolvedExport | undefined, + symbol: Symbol, + program: Program, + host: LanguageServiceHost, + compilerOptions: CompilerOptions, + sourceFile: SourceFile, + position: number, + previousToken: Node | undefined, + formatContext: formatting.FormatContext, + preferences: UserPreferences, + data: CompletionEntryData | undefined, + source: string | undefined, +): CodeActionsAndSourceDisplay { + if (data?.moduleSpecifier) { + const { contextToken, previousToken } = getRelevantTokens(position, sourceFile); + if (previousToken && getImportStatementCompletionInfo(contextToken || previousToken).replacementNode) { + // Import statement completion: 'import c|' + return { codeActions: undefined, sourceDisplay: [textPart(data.moduleSpecifier)] }; + } + } + + if (source === CompletionSource.ClassMemberSnippet) { + const { importAdder } = getEntryForMemberCompletion( + host, + program, + compilerOptions, + preferences, + name, + symbol, + location, + contextToken); + if (importAdder) { + const changes = textChanges.ChangeTracker.with( + { host, formatContext, preferences }, + importAdder.writeFixes); + return { + sourceDisplay: undefined, + codeActions: [{ + changes, + description: diagnosticToString([Diagnostics.Includes_imports_of_types_referenced_by_0, name]), + }], + }; + } } - export function createCompletionDetails(name: string, kindModifiers: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], documentation?: SymbolDisplayPart[], tags?: JSDocTagInfo[], codeActions?: CodeAction[], source?: SymbolDisplayPart[]): CompletionEntryDetails { - return { name, kindModifiers, kind, displayParts, documentation, tags, codeActions, source, sourceDisplay: source }; + if (!origin || !(originIsExport(origin) || originIsResolvedExport(origin))) { + return { codeActions: undefined, sourceDisplay: undefined }; } - interface CodeActionsAndSourceDisplay { - readonly codeActions: CodeAction[] | undefined; - readonly sourceDisplay: SymbolDisplayPart[] | undefined; + const checker = origin.isFromPackageJson ? host.getPackageJsonAutoImportProvider!()!.getTypeChecker() : program.getTypeChecker(); + const { moduleSymbol } = origin; + const targetSymbol = checker.getMergedSymbol(skipAlias(symbol.exportSymbol || symbol, checker)); + const { moduleSpecifier, codeAction } = codefix.getImportCompletionAction( + targetSymbol, + moduleSymbol, + sourceFile, + getNameForExportedSymbol(symbol, getEmitScriptTarget(compilerOptions)), + host, + program, + formatContext, + previousToken && isIdentifier(previousToken) ? previousToken.getStart(sourceFile) : position, + preferences); + Debug.assert(!data?.moduleSpecifier || moduleSpecifier === data.moduleSpecifier); + return { sourceDisplay: [textPart(moduleSpecifier)], codeActions: [codeAction] }; +} + +export function getCompletionEntrySymbol( + program: Program, + log: Log, + sourceFile: SourceFile, + position: number, + entryId: CompletionEntryIdentifier, + host: LanguageServiceHost, + preferences: UserPreferences, +): Symbol | undefined { + const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences); + return completion.type === "symbol" ? completion.symbol : undefined; +} + +const enum CompletionDataKind { Data, JsDocTagName, JsDocTag, JsDocParameterName, Keywords } +/** true: after the `=` sign but no identifier has been typed yet. Else is the Identifier after the initializer. */ +type IsJsxInitializer = boolean | Identifier; +interface CompletionData { + readonly kind: CompletionDataKind.Data; + readonly symbols: readonly Symbol[]; + readonly completionKind: CompletionKind; + readonly isInSnippetScope: boolean; + /** Note that the presence of this alone doesn't mean that we need a conversion. Only do that if the completion is not an ordinary identifier. */ + readonly propertyAccessToConvert: PropertyAccessExpression | undefined; + readonly isNewIdentifierLocation: boolean; + readonly location: Node; + readonly keywordFilters: KeywordCompletionFilters; + readonly literals: readonly (string | number | PseudoBigInt)[]; + readonly symbolToOriginInfoMap: SymbolOriginInfoMap; + readonly recommendedCompletion: Symbol | undefined; + readonly previousToken: Node | undefined; + readonly contextToken: Node | undefined; + readonly isJsxInitializer: IsJsxInitializer; + readonly insideJsDocTagTypeExpression: boolean; + readonly symbolToSortTextIdMap: SymbolSortTextIdMap; + readonly isTypeOnlyLocation: boolean; + /** In JSX tag name and attribute names, identifiers like "my-tag" or "aria-name" is valid identifier. */ + readonly isJsxIdentifierExpected: boolean; + readonly importCompletionNode?: Node; + readonly hasUnresolvedAutoImports?: boolean; +} +type Request = + | { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } + | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag } + | { readonly kind: CompletionDataKind.Keywords, keywordCompletions: readonly CompletionEntry[], isNewIdentifierLocation: boolean }; + +export const enum CompletionKind { + ObjectPropertyDeclaration, + Global, + PropertyAccess, + MemberLike, + String, + None, +} + +function getRecommendedCompletion(previousToken: Node, contextualType: Type, checker: TypeChecker): Symbol | undefined { + // For a union, return the first one with a recommended completion. + return firstDefined(contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), type => { + const symbol = type && type.symbol; + // Don't include make a recommended completion for an abstract class + return symbol && (symbol.flags & (SymbolFlags.EnumMember | SymbolFlags.Enum | SymbolFlags.Class) && !isAbstractConstructorSymbol(symbol)) + ? getFirstSymbolInChain(symbol, previousToken, checker) + : undefined; + }); +} + +function getContextualType(previousToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Type | undefined { + const { parent } = previousToken; + switch (previousToken.kind) { + case SyntaxKind.Identifier: + return getContextualTypeFromParent(previousToken as Identifier, checker); + case SyntaxKind.EqualsToken: + switch (parent.kind) { + case SyntaxKind.VariableDeclaration: + return checker.getContextualType((parent as VariableDeclaration).initializer!); // TODO: GH#18217 + case SyntaxKind.BinaryExpression: + return checker.getTypeAtLocation((parent as BinaryExpression).left); + case SyntaxKind.JsxAttribute: + return checker.getContextualTypeForJsxAttribute(parent as JsxAttribute); + default: + return undefined; + } + case SyntaxKind.NewKeyword: + return checker.getContextualType(parent as Expression); + case SyntaxKind.CaseKeyword: + return getSwitchedType(cast(parent, isCaseClause), checker); + case SyntaxKind.OpenBraceToken: + return isJsxExpression(parent) && !isJsxElement(parent.parent) && !isJsxFragment(parent.parent) ? checker.getContextualTypeForJsxAttribute(parent.parent) : undefined; + default: + const argInfo = SignatureHelp.getArgumentInfoForCompletions(previousToken, position, sourceFile); + return argInfo ? + // At `,`, treat this as the next argument after the comma. + checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (previousToken.kind === SyntaxKind.CommaToken ? 1 : 0)) : + isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) ? + // completion at `x ===/**/` should be for the right side + checker.getTypeAtLocation(parent.left) : + checker.getContextualType(previousToken as Expression); } - function getCompletionEntryCodeActionsAndSourceDisplay( - name: string, - location: Node, - contextToken: Node | undefined, - origin: SymbolOriginInfo | SymbolOriginInfoExport | SymbolOriginInfoResolvedExport | undefined, - symbol: Symbol, - program: Program, - host: LanguageServiceHost, - compilerOptions: CompilerOptions, - sourceFile: SourceFile, - position: number, - previousToken: Node | undefined, - formatContext: formatting.FormatContext, - preferences: UserPreferences, - data: CompletionEntryData | undefined, - source: string | undefined, - ): CodeActionsAndSourceDisplay { - if (data?.moduleSpecifier) { - const { contextToken, previousToken } = getRelevantTokens(position, sourceFile); - if (previousToken && getImportStatementCompletionInfo(contextToken || previousToken).replacementNode) { - // Import statement completion: 'import c|' - return { codeActions: undefined, sourceDisplay: [textPart(data.moduleSpecifier)] }; +} + +function getFirstSymbolInChain(symbol: Symbol, enclosingDeclaration: Node, checker: TypeChecker): Symbol | undefined { + const chain = checker.getAccessibleSymbolChain(symbol, enclosingDeclaration, /*meaning*/ SymbolFlags.All, /*useOnlyExternalAliasing*/ false); + if (chain) return first(chain); + return symbol.parent && (isModuleSymbol(symbol.parent) ? symbol : getFirstSymbolInChain(symbol.parent, enclosingDeclaration, checker)); +} + +function isModuleSymbol(symbol: Symbol): boolean { + return !!symbol.declarations?.some(d => d.kind === SyntaxKind.SourceFile); +} + +function getCompletionData( + program: Program, + log: (message: string) => void, + sourceFile: SourceFile, + isUncheckedFile: boolean, + position: number, + preferences: UserPreferences, + detailsEntryId: CompletionEntryIdentifier | undefined, + host: LanguageServiceHost, + cancellationToken?: CancellationToken, +): CompletionData | Request | undefined { + const typeChecker = program.getTypeChecker(); + + let start = timestamp(); + let currentToken = getTokenAtPosition(sourceFile, position); // TODO: GH#15853 + // We will check for jsdoc comments with insideComment and getJsDocTagAtPosition. (TODO: that seems rather inefficient to check the same thing so many times.) + + log("getCompletionData: Get current token: " + (timestamp() - start)); + + start = timestamp(); + const insideComment = isInComment(sourceFile, position, currentToken); + log("getCompletionData: Is inside comment: " + (timestamp() - start)); + + let insideJsDocTagTypeExpression = false; + let isInSnippetScope = false; + if (insideComment) { + if (hasDocComment(sourceFile, position)) { + if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { + // The current position is next to the '@' sign, when no tag name being provided yet. + // Provide a full list of tag names + return { kind: CompletionDataKind.JsDocTagName }; } + else { + // When completion is requested without "@", we will have check to make sure that + // there are no comments prefix the request position. We will only allow "*" and space. + // e.g + // /** |c| /* + // + // /** + // |c| + // */ + // + // /** + // * |c| + // */ + // + // /** + // * |c| + // */ + const lineStart = getLineStartPositionForPosition(position, sourceFile); + if (!/[^\*|\s(/)]/.test(sourceFile.text.substring(lineStart, position))) { + return { kind: CompletionDataKind.JsDocTag }; + } + } + } + + // Completion should work inside certain JsDoc tags. For example: + // /** @type {number | string} */ + // Completion should work in the brackets + const tag = getJsDocTagAtPosition(currentToken, position); + if (tag) { + if (tag.tagName.pos <= position && position <= tag.tagName.end) { + return { kind: CompletionDataKind.JsDocTagName }; + } + if (isTagWithTypeExpression(tag) && tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { + currentToken = getTokenAtPosition(sourceFile, position); + if (!currentToken || + (!isDeclarationName(currentToken) && + (currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag || + (currentToken.parent as JSDocPropertyTag).name !== currentToken))) { + // Use as type location if inside tag's type expression + insideJsDocTagTypeExpression = isCurrentlyEditingNode(tag.typeExpression); + } + } + if (!insideJsDocTagTypeExpression && isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) { + return { kind: CompletionDataKind.JsDocParameterName, tag }; + } + } + + if (!insideJsDocTagTypeExpression) { + // Proceed if the current position is in jsDoc tag expression; otherwise it is a normal + // comment or the plain text part of a jsDoc comment, so no completion should be available + log("Returning an empty list because completion was inside a regular comment or plain text part of a JsDoc comment."); + return undefined; } + } - if (source === CompletionSource.ClassMemberSnippet) { - const { importAdder } = getEntryForMemberCompletion( - host, - program, - compilerOptions, - preferences, - name, - symbol, - location, - contextToken); - if (importAdder) { - const changes = textChanges.ChangeTracker.with( - { host, formatContext, preferences }, - importAdder.writeFixes); + start = timestamp(); + // The decision to provide completion depends on the contextToken, which is determined through the previousToken. + // Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file + const isJsOnlyLocation = !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile); + const tokens = getRelevantTokens(position, sourceFile); + const previousToken = tokens.previousToken!; + let contextToken = tokens.contextToken!; + log("getCompletionData: Get previous token: " + (timestamp() - start)); + + // Find the node where completion is requested on. + // Also determine whether we are trying to complete with members of that node + // or attributes of a JSX tag. + let node = currentToken; + let propertyAccessToConvert: PropertyAccessExpression | undefined; + let isRightOfDot = false; + let isRightOfQuestionDot = false; + let isRightOfOpenTag = false; + let isStartingCloseTag = false; + let isJsxInitializer: IsJsxInitializer = false; + let isJsxIdentifierExpected = false; + let importCompletionNode: Node | undefined; + let location = getTouchingPropertyName(sourceFile, position); + let keywordFilters = KeywordCompletionFilters.None; + let isNewIdentifierLocation = false; + + if (contextToken) { + const importStatementCompletion = getImportStatementCompletionInfo(contextToken); + isNewIdentifierLocation = importStatementCompletion.isNewIdentifierLocation; + if (importStatementCompletion.keywordCompletion) { + if (importStatementCompletion.isKeywordOnlyCompletion) { return { - sourceDisplay: undefined, - codeActions: [{ - changes, - description: diagnosticToString([Diagnostics.Includes_imports_of_types_referenced_by_0, name]), - }], + kind: CompletionDataKind.Keywords, + keywordCompletions: [keywordToCompletionEntry(importStatementCompletion.keywordCompletion)], + isNewIdentifierLocation, }; } + keywordFilters = keywordFiltersFromSyntaxKind(importStatementCompletion.keywordCompletion); } - - if (!origin || !(originIsExport(origin) || originIsResolvedExport(origin))) { - return { codeActions: undefined, sourceDisplay: undefined }; + if (importStatementCompletion.replacementNode && preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) { + // Import statement completions use `insertText`, and also require the `data` property of `CompletionEntryIdentifier` + // added in TypeScript 4.3 to be sent back from the client during `getCompletionEntryDetails`. Since this feature + // is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients + // to opt in with the `includeCompletionsForImportStatements` user preference. + importCompletionNode = importStatementCompletion.replacementNode; } - - const checker = origin.isFromPackageJson ? host.getPackageJsonAutoImportProvider!()!.getTypeChecker() : program.getTypeChecker(); - const { moduleSymbol } = origin; - const targetSymbol = checker.getMergedSymbol(skipAlias(symbol.exportSymbol || symbol, checker)); - const { moduleSpecifier, codeAction } = codefix.getImportCompletionAction( - targetSymbol, - moduleSymbol, - sourceFile, - getNameForExportedSymbol(symbol, getEmitScriptTarget(compilerOptions)), - host, - program, - formatContext, - previousToken && isIdentifier(previousToken) ? previousToken.getStart(sourceFile) : position, - preferences); - Debug.assert(!data?.moduleSpecifier || moduleSpecifier === data.moduleSpecifier); - return { sourceDisplay: [textPart(moduleSpecifier)], codeActions: [codeAction] }; - } - - export function getCompletionEntrySymbol( - program: Program, - log: Log, - sourceFile: SourceFile, - position: number, - entryId: CompletionEntryIdentifier, - host: LanguageServiceHost, - preferences: UserPreferences, - ): Symbol | undefined { - const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences); - return completion.type === "symbol" ? completion.symbol : undefined; - } - - const enum CompletionDataKind { Data, JsDocTagName, JsDocTag, JsDocParameterName, Keywords } - /** true: after the `=` sign but no identifier has been typed yet. Else is the Identifier after the initializer. */ - type IsJsxInitializer = boolean | Identifier; - interface CompletionData { - readonly kind: CompletionDataKind.Data; - readonly symbols: readonly Symbol[]; - readonly completionKind: CompletionKind; - readonly isInSnippetScope: boolean; - /** Note that the presence of this alone doesn't mean that we need a conversion. Only do that if the completion is not an ordinary identifier. */ - readonly propertyAccessToConvert: PropertyAccessExpression | undefined; - readonly isNewIdentifierLocation: boolean; - readonly location: Node; - readonly keywordFilters: KeywordCompletionFilters; - readonly literals: readonly (string | number | PseudoBigInt)[]; - readonly symbolToOriginInfoMap: SymbolOriginInfoMap; - readonly recommendedCompletion: Symbol | undefined; - readonly previousToken: Node | undefined; - readonly contextToken: Node | undefined; - readonly isJsxInitializer: IsJsxInitializer; - readonly insideJsDocTagTypeExpression: boolean; - readonly symbolToSortTextIdMap: SymbolSortTextIdMap; - readonly isTypeOnlyLocation: boolean; - /** In JSX tag name and attribute names, identifiers like "my-tag" or "aria-name" is valid identifier. */ - readonly isJsxIdentifierExpected: boolean; - readonly importCompletionNode?: Node; - readonly hasUnresolvedAutoImports?: boolean; - } - type Request = - | { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } - | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag } - | { readonly kind: CompletionDataKind.Keywords, keywordCompletions: readonly CompletionEntry[], isNewIdentifierLocation: boolean }; - - export const enum CompletionKind { - ObjectPropertyDeclaration, - Global, - PropertyAccess, - MemberLike, - String, - None, - } - - function getRecommendedCompletion(previousToken: Node, contextualType: Type, checker: TypeChecker): Symbol | undefined { - // For a union, return the first one with a recommended completion. - return firstDefined(contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), type => { - const symbol = type && type.symbol; - // Don't include make a recommended completion for an abstract class - return symbol && (symbol.flags & (SymbolFlags.EnumMember | SymbolFlags.Enum | SymbolFlags.Class) && !isAbstractConstructorSymbol(symbol)) - ? getFirstSymbolInChain(symbol, previousToken, checker) + // Bail out if this is a known invalid completion location + if (!importCompletionNode && isCompletionListBlocker(contextToken)) { + log("Returning an empty list because completion was requested in an invalid position."); + return keywordFilters + ? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierDefinitionLocation()) : undefined; - }); - } + } - function getContextualType(previousToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Type | undefined { - const { parent } = previousToken; - switch (previousToken.kind) { - case SyntaxKind.Identifier: - return getContextualTypeFromParent(previousToken as Identifier, checker); - case SyntaxKind.EqualsToken: - switch (parent.kind) { - case SyntaxKind.VariableDeclaration: - return checker.getContextualType((parent as VariableDeclaration).initializer!); // TODO: GH#18217 - case SyntaxKind.BinaryExpression: - return checker.getTypeAtLocation((parent as BinaryExpression).left); - case SyntaxKind.JsxAttribute: - return checker.getContextualTypeForJsxAttribute(parent as JsxAttribute); - default: + let parent = contextToken.parent; + if (contextToken.kind === SyntaxKind.DotToken || contextToken.kind === SyntaxKind.QuestionDotToken) { + isRightOfDot = contextToken.kind === SyntaxKind.DotToken; + isRightOfQuestionDot = contextToken.kind === SyntaxKind.QuestionDotToken; + switch (parent.kind) { + case SyntaxKind.PropertyAccessExpression: + propertyAccessToConvert = parent as PropertyAccessExpression; + node = propertyAccessToConvert.expression; + const leftmostAccessExpression = getLeftmostAccessExpression(propertyAccessToConvert); + if (nodeIsMissing(leftmostAccessExpression) || + ((isCallExpression(node) || isFunctionLike(node)) && + node.end === contextToken.pos && + node.getChildCount(sourceFile) && + last(node.getChildren(sourceFile)).kind !== SyntaxKind.CloseParenToken)) { + // This is likely dot from incorrectly parsed expression and user is starting to write spread + // eg: Math.min(./**/) + // const x = function (./**/) {} + // ({./**/}) return undefined; - } - case SyntaxKind.NewKeyword: - return checker.getContextualType(parent as Expression); - case SyntaxKind.CaseKeyword: - return getSwitchedType(cast(parent, isCaseClause), checker); - case SyntaxKind.OpenBraceToken: - return isJsxExpression(parent) && !isJsxElement(parent.parent) && !isJsxFragment(parent.parent) ? checker.getContextualTypeForJsxAttribute(parent.parent) : undefined; - default: - const argInfo = SignatureHelp.getArgumentInfoForCompletions(previousToken, position, sourceFile); - return argInfo ? - // At `,`, treat this as the next argument after the comma. - checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (previousToken.kind === SyntaxKind.CommaToken ? 1 : 0)) : - isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) ? - // completion at `x ===/**/` should be for the right side - checker.getTypeAtLocation(parent.left) : - checker.getContextualType(previousToken as Expression); - } - } - - function getFirstSymbolInChain(symbol: Symbol, enclosingDeclaration: Node, checker: TypeChecker): Symbol | undefined { - const chain = checker.getAccessibleSymbolChain(symbol, enclosingDeclaration, /*meaning*/ SymbolFlags.All, /*useOnlyExternalAliasing*/ false); - if (chain) return first(chain); - return symbol.parent && (isModuleSymbol(symbol.parent) ? symbol : getFirstSymbolInChain(symbol.parent, enclosingDeclaration, checker)); - } - - function isModuleSymbol(symbol: Symbol): boolean { - return !!symbol.declarations?.some(d => d.kind === SyntaxKind.SourceFile); - } - - function getCompletionData( - program: Program, - log: (message: string) => void, - sourceFile: SourceFile, - isUncheckedFile: boolean, - position: number, - preferences: UserPreferences, - detailsEntryId: CompletionEntryIdentifier | undefined, - host: LanguageServiceHost, - cancellationToken?: CancellationToken, - ): CompletionData | Request | undefined { - const typeChecker = program.getTypeChecker(); - - let start = timestamp(); - let currentToken = getTokenAtPosition(sourceFile, position); // TODO: GH#15853 - // We will check for jsdoc comments with insideComment and getJsDocTagAtPosition. (TODO: that seems rather inefficient to check the same thing so many times.) - - log("getCompletionData: Get current token: " + (timestamp() - start)); - - start = timestamp(); - const insideComment = isInComment(sourceFile, position, currentToken); - log("getCompletionData: Is inside comment: " + (timestamp() - start)); - - let insideJsDocTagTypeExpression = false; - let isInSnippetScope = false; - if (insideComment) { - if (hasDocComment(sourceFile, position)) { - if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { - // The current position is next to the '@' sign, when no tag name being provided yet. - // Provide a full list of tag names - return { kind: CompletionDataKind.JsDocTagName }; - } - else { - // When completion is requested without "@", we will have check to make sure that - // there are no comments prefix the request position. We will only allow "*" and space. - // e.g - // /** |c| /* - // - // /** - // |c| - // */ - // - // /** - // * |c| - // */ - // - // /** - // * |c| - // */ - const lineStart = getLineStartPositionForPosition(position, sourceFile); - if (!/[^\*|\s(/)]/.test(sourceFile.text.substring(lineStart, position))) { - return { kind: CompletionDataKind.JsDocTag }; } - } - } - - // Completion should work inside certain JsDoc tags. For example: - // /** @type {number | string} */ - // Completion should work in the brackets - const tag = getJsDocTagAtPosition(currentToken, position); - if (tag) { - if (tag.tagName.pos <= position && position <= tag.tagName.end) { - return { kind: CompletionDataKind.JsDocTagName }; - } - if (isTagWithTypeExpression(tag) && tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { - currentToken = getTokenAtPosition(sourceFile, position); - if (!currentToken || - (!isDeclarationName(currentToken) && - (currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag || - (currentToken.parent as JSDocPropertyTag).name !== currentToken))) { - // Use as type location if inside tag's type expression - insideJsDocTagTypeExpression = isCurrentlyEditingNode(tag.typeExpression); - } - } - if (!insideJsDocTagTypeExpression && isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) { - return { kind: CompletionDataKind.JsDocParameterName, tag }; - } - } - - if (!insideJsDocTagTypeExpression) { - // Proceed if the current position is in jsDoc tag expression; otherwise it is a normal - // comment or the plain text part of a jsDoc comment, so no completion should be available - log("Returning an empty list because completion was inside a regular comment or plain text part of a JsDoc comment."); - return undefined; + break; + case SyntaxKind.QualifiedName: + node = (parent as QualifiedName).left; + break; + case SyntaxKind.ModuleDeclaration: + node = (parent as ModuleDeclaration).name; + break; + case SyntaxKind.ImportType: + node = parent; + break; + case SyntaxKind.MetaProperty: + node = parent.getFirstToken(sourceFile)!; + Debug.assert(node.kind === SyntaxKind.ImportKeyword || node.kind === SyntaxKind.NewKeyword); + break; + default: + // There is nothing that precedes the dot, so this likely just a stray character + // or leading into a '...' token. Just bail out instead. + return undefined; } } + else if (!importCompletionNode && sourceFile.languageVariant === LanguageVariant.JSX) { + // + // If the tagname is a property access expression, we will then walk up to the top most of property access expression. + // Then, try to get a JSX container and its associated attributes type. + if (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { + contextToken = parent; + parent = parent.parent; + } - start = timestamp(); - // The decision to provide completion depends on the contextToken, which is determined through the previousToken. - // Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file - const isJsOnlyLocation = !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile); - const tokens = getRelevantTokens(position, sourceFile); - const previousToken = tokens.previousToken!; - let contextToken = tokens.contextToken!; - log("getCompletionData: Get previous token: " + (timestamp() - start)); - - // Find the node where completion is requested on. - // Also determine whether we are trying to complete with members of that node - // or attributes of a JSX tag. - let node = currentToken; - let propertyAccessToConvert: PropertyAccessExpression | undefined; - let isRightOfDot = false; - let isRightOfQuestionDot = false; - let isRightOfOpenTag = false; - let isStartingCloseTag = false; - let isJsxInitializer: IsJsxInitializer = false; - let isJsxIdentifierExpected = false; - let importCompletionNode: Node | undefined; - let location = getTouchingPropertyName(sourceFile, position); - let keywordFilters = KeywordCompletionFilters.None; - let isNewIdentifierLocation = false; - - if (contextToken) { - const importStatementCompletion = getImportStatementCompletionInfo(contextToken); - isNewIdentifierLocation = importStatementCompletion.isNewIdentifierLocation; - if (importStatementCompletion.keywordCompletion) { - if (importStatementCompletion.isKeywordOnlyCompletion) { - return { - kind: CompletionDataKind.Keywords, - keywordCompletions: [keywordToCompletionEntry(importStatementCompletion.keywordCompletion)], - isNewIdentifierLocation, - }; - } - keywordFilters = keywordFiltersFromSyntaxKind(importStatementCompletion.keywordCompletion); - } - if (importStatementCompletion.replacementNode && preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) { - // Import statement completions use `insertText`, and also require the `data` property of `CompletionEntryIdentifier` - // added in TypeScript 4.3 to be sent back from the client during `getCompletionEntryDetails`. Since this feature - // is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients - // to opt in with the `includeCompletionsForImportStatements` user preference. - importCompletionNode = importStatementCompletion.replacementNode; - } - // Bail out if this is a known invalid completion location - if (!importCompletionNode && isCompletionListBlocker(contextToken)) { - log("Returning an empty list because completion was requested in an invalid position."); - return keywordFilters - ? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierDefinitionLocation()) - : undefined; - } - - let parent = contextToken.parent; - if (contextToken.kind === SyntaxKind.DotToken || contextToken.kind === SyntaxKind.QuestionDotToken) { - isRightOfDot = contextToken.kind === SyntaxKind.DotToken; - isRightOfQuestionDot = contextToken.kind === SyntaxKind.QuestionDotToken; - switch (parent.kind) { - case SyntaxKind.PropertyAccessExpression: - propertyAccessToConvert = parent as PropertyAccessExpression; - node = propertyAccessToConvert.expression; - const leftmostAccessExpression = getLeftmostAccessExpression(propertyAccessToConvert); - if (nodeIsMissing(leftmostAccessExpression) || - ((isCallExpression(node) || isFunctionLike(node)) && - node.end === contextToken.pos && - node.getChildCount(sourceFile) && - last(node.getChildren(sourceFile)).kind !== SyntaxKind.CloseParenToken)) { - // This is likely dot from incorrectly parsed expression and user is starting to write spread - // eg: Math.min(./**/) - // const x = function (./**/) {} - // ({./**/}) - return undefined; + // Fix location + if (currentToken.parent === location) { + switch (currentToken.kind) { + case SyntaxKind.GreaterThanToken: + if (currentToken.parent.kind === SyntaxKind.JsxElement || currentToken.parent.kind === SyntaxKind.JsxOpeningElement) { + location = currentToken; } break; - case SyntaxKind.QualifiedName: - node = (parent as QualifiedName).left; - break; - case SyntaxKind.ModuleDeclaration: - node = (parent as ModuleDeclaration).name; - break; - case SyntaxKind.ImportType: - node = parent; - break; - case SyntaxKind.MetaProperty: - node = parent.getFirstToken(sourceFile)!; - Debug.assert(node.kind === SyntaxKind.ImportKeyword || node.kind === SyntaxKind.NewKeyword); + + case SyntaxKind.SlashToken: + if (currentToken.parent.kind === SyntaxKind.JsxSelfClosingElement) { + location = currentToken; + } break; - default: - // There is nothing that precedes the dot, so this likely just a stray character - // or leading into a '...' token. Just bail out instead. - return undefined; } } - else if (!importCompletionNode && sourceFile.languageVariant === LanguageVariant.JSX) { - // - // If the tagname is a property access expression, we will then walk up to the top most of property access expression. - // Then, try to get a JSX container and its associated attributes type. - if (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { - contextToken = parent; - parent = parent.parent; - } - // Fix location - if (currentToken.parent === location) { - switch (currentToken.kind) { - case SyntaxKind.GreaterThanToken: - if (currentToken.parent.kind === SyntaxKind.JsxElement || currentToken.parent.kind === SyntaxKind.JsxOpeningElement) { - location = currentToken; - } - break; - - case SyntaxKind.SlashToken: - if (currentToken.parent.kind === SyntaxKind.JsxSelfClosingElement) { - location = currentToken; - } - break; + switch (parent.kind) { + case SyntaxKind.JsxClosingElement: + if (contextToken.kind === SyntaxKind.SlashToken) { + isStartingCloseTag = true; + location = contextToken; } - } + break; - switch (parent.kind) { - case SyntaxKind.JsxClosingElement: - if (contextToken.kind === SyntaxKind.SlashToken) { - isStartingCloseTag = true; - location = contextToken; - } + case SyntaxKind.BinaryExpression: + if (!binaryExpressionMayBeOpenTag(parent as BinaryExpression)) { break; + } + // falls through + + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxElement: + case SyntaxKind.JsxOpeningElement: + isJsxIdentifierExpected = true; + if (contextToken.kind === SyntaxKind.LessThanToken) { + isRightOfOpenTag = true; + location = contextToken; + } + break; - case SyntaxKind.BinaryExpression: - if (!binaryExpressionMayBeOpenTag(parent as BinaryExpression)) { - break; - } - // falls through - - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxElement: - case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxExpression: + case SyntaxKind.JsxSpreadAttribute: + // For `
`, `parent` will be `{true}` and `previousToken` will be `}` + if (previousToken.kind === SyntaxKind.CloseBraceToken && currentToken.kind === SyntaxKind.GreaterThanToken) { isJsxIdentifierExpected = true; - if (contextToken.kind === SyntaxKind.LessThanToken) { - isRightOfOpenTag = true; - location = contextToken; - } - break; + } + break; - case SyntaxKind.JsxExpression: - case SyntaxKind.JsxSpreadAttribute: - // For `
`, `parent` will be `{true}` and `previousToken` will be `}` - if (previousToken.kind === SyntaxKind.CloseBraceToken && currentToken.kind === SyntaxKind.GreaterThanToken) { - isJsxIdentifierExpected = true; - } + case SyntaxKind.JsxAttribute: + // For `
`, `parent` will be JsxAttribute and `previousToken` will be its initializer + if ((parent as JsxAttribute).initializer === previousToken && + previousToken.end < position) { + isJsxIdentifierExpected = true; break; - - case SyntaxKind.JsxAttribute: - // For `
`, `parent` will be JsxAttribute and `previousToken` will be its initializer - if ((parent as JsxAttribute).initializer === previousToken && - previousToken.end < position) { - isJsxIdentifierExpected = true; + } + switch (previousToken.kind) { + case SyntaxKind.EqualsToken: + isJsxInitializer = true; break; - } - switch (previousToken.kind) { - case SyntaxKind.EqualsToken: - isJsxInitializer = true; - break; - case SyntaxKind.Identifier: - isJsxIdentifierExpected = true; - // For `
` we don't want to treat this as a jsx inializer, instead it's the attribute name. - if (parent !== previousToken.parent && - !(parent as JsxAttribute).initializer && - findChildOfKind(parent, SyntaxKind.EqualsToken, sourceFile)) { - isJsxInitializer = previousToken as Identifier; - } - } - break; - } + case SyntaxKind.Identifier: + isJsxIdentifierExpected = true; + // For `
` we don't want to treat this as a jsx inializer, instead it's the attribute name. + if (parent !== previousToken.parent && + !(parent as JsxAttribute).initializer && + findChildOfKind(parent, SyntaxKind.EqualsToken, sourceFile)) { + isJsxInitializer = previousToken as Identifier; + } + } + break; } } + } - const semanticStart = timestamp(); - let completionKind = CompletionKind.None; - let isNonContextualObjectLiteral = false; - let hasUnresolvedAutoImports = false; - // This also gets mutated in nested-functions after the return - let symbols: Symbol[] = []; - const symbolToOriginInfoMap: SymbolOriginInfoMap = []; - const symbolToSortTextIdMap: SymbolSortTextIdMap = []; - const seenPropertySymbols = new Map(); - const isTypeOnlyLocation = isTypeOnlyCompletion(); - const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => { - return createModuleSpecifierResolutionHost(isFromPackageJson ? host.getPackageJsonAutoImportProvider!()! : program, host); - }); + const semanticStart = timestamp(); + let completionKind = CompletionKind.None; + let isNonContextualObjectLiteral = false; + let hasUnresolvedAutoImports = false; + // This also gets mutated in nested-functions after the return + let symbols: Symbol[] = []; + const symbolToOriginInfoMap: SymbolOriginInfoMap = []; + const symbolToSortTextIdMap: SymbolSortTextIdMap = []; + const seenPropertySymbols = new Map(); + const isTypeOnlyLocation = isTypeOnlyCompletion(); + const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => { + return createModuleSpecifierResolutionHost(isFromPackageJson ? host.getPackageJsonAutoImportProvider!()! : program, host); + }); - if (isRightOfDot || isRightOfQuestionDot) { - getTypeScriptMemberSymbols(); - } - else if (isRightOfOpenTag) { - symbols = typeChecker.getJsxIntrinsicTagNamesAt(location); - Debug.assertEachIsDefined(symbols, "getJsxIntrinsicTagNames() should all be defined"); - tryGetGlobalSymbols(); - completionKind = CompletionKind.Global; - keywordFilters = KeywordCompletionFilters.None; - } - else if (isStartingCloseTag) { - const tagName = (contextToken.parent.parent as JsxElement).openingElement.tagName; - const tagSymbol = typeChecker.getSymbolAtLocation(tagName); - if (tagSymbol) { - symbols = [tagSymbol]; - } - completionKind = CompletionKind.Global; - keywordFilters = KeywordCompletionFilters.None; - } - else { - // For JavaScript or TypeScript, if we're not after a dot, then just try to get the - // global symbols in scope. These results should be valid for either language as - // the set of symbols that can be referenced from this location. - if (!tryGetGlobalSymbols()) { - return keywordFilters - ? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierLocation) - : undefined; - } + if (isRightOfDot || isRightOfQuestionDot) { + getTypeScriptMemberSymbols(); + } + else if (isRightOfOpenTag) { + symbols = typeChecker.getJsxIntrinsicTagNamesAt(location); + Debug.assertEachIsDefined(symbols, "getJsxIntrinsicTagNames() should all be defined"); + tryGetGlobalSymbols(); + completionKind = CompletionKind.Global; + keywordFilters = KeywordCompletionFilters.None; + } + else if (isStartingCloseTag) { + const tagName = (contextToken.parent.parent as JsxElement).openingElement.tagName; + const tagSymbol = typeChecker.getSymbolAtLocation(tagName); + if (tagSymbol) { + symbols = [tagSymbol]; + } + completionKind = CompletionKind.Global; + keywordFilters = KeywordCompletionFilters.None; + } + else { + // For JavaScript or TypeScript, if we're not after a dot, then just try to get the + // global symbols in scope. These results should be valid for either language as + // the set of symbols that can be referenced from this location. + if (!tryGetGlobalSymbols()) { + return keywordFilters + ? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierLocation) + : undefined; } + } - log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); - const contextualType = previousToken && getContextualType(previousToken, position, sourceFile, typeChecker); - - const literals = mapDefined( - contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), - t => t.isLiteral() && !(t.flags & TypeFlags.EnumLiteral) ? t.value : undefined); - - const recommendedCompletion = previousToken && contextualType && getRecommendedCompletion(previousToken, contextualType, typeChecker); - return { - kind: CompletionDataKind.Data, - symbols, - completionKind, - isInSnippetScope, - propertyAccessToConvert, - isNewIdentifierLocation, - location, - keywordFilters, - literals, - symbolToOriginInfoMap, - recommendedCompletion, - previousToken, - contextToken, - isJsxInitializer, - insideJsDocTagTypeExpression, - symbolToSortTextIdMap, - isTypeOnlyLocation, - isJsxIdentifierExpected, - importCompletionNode, - hasUnresolvedAutoImports, - }; + log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); + const contextualType = previousToken && getContextualType(previousToken, position, sourceFile, typeChecker); + + const literals = mapDefined( + contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), + t => t.isLiteral() && !(t.flags & TypeFlags.EnumLiteral) ? t.value : undefined); + + const recommendedCompletion = previousToken && contextualType && getRecommendedCompletion(previousToken, contextualType, typeChecker); + return { + kind: CompletionDataKind.Data, + symbols, + completionKind, + isInSnippetScope, + propertyAccessToConvert, + isNewIdentifierLocation, + location, + keywordFilters, + literals, + symbolToOriginInfoMap, + recommendedCompletion, + previousToken, + contextToken, + isJsxInitializer, + insideJsDocTagTypeExpression, + symbolToSortTextIdMap, + isTypeOnlyLocation, + isJsxIdentifierExpected, + importCompletionNode, + hasUnresolvedAutoImports, + }; - type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; + type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; - function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { - switch (tag.kind) { - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - case SyntaxKind.JSDocReturnTag: - case SyntaxKind.JSDocTypeTag: - case SyntaxKind.JSDocTypedefTag: - return true; - default: - return false; - } + function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { + switch (tag.kind) { + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocPropertyTag: + case SyntaxKind.JSDocReturnTag: + case SyntaxKind.JSDocTypeTag: + case SyntaxKind.JSDocTypedefTag: + return true; + default: + return false; } + } - function getTypeScriptMemberSymbols(): void { - // Right of dot member completion list - completionKind = CompletionKind.PropertyAccess; - - // Since this is qualified name check it's a type node location - const isImportType = isLiteralImportTypeNode(node); - const isTypeLocation = insideJsDocTagTypeExpression - || (isImportType && !(node as ImportTypeNode).isTypeOf) - || isPartOfTypeNode(node.parent) - || isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker); - const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node); - if (isEntityName(node) || isImportType || isPropertyAccessExpression(node)) { - const isNamespaceName = isModuleDeclaration(node.parent); - if (isNamespaceName) isNewIdentifierLocation = true; - let symbol = typeChecker.getSymbolAtLocation(node); - if (symbol) { - symbol = skipAlias(symbol, typeChecker); - if (symbol.flags & (SymbolFlags.Module | SymbolFlags.Enum)) { - // Extract module or enum members - const exportedSymbols = typeChecker.getExportsOfModule(symbol); - Debug.assertEachIsDefined(exportedSymbols, "getExportsOfModule() should all be defined"); - const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(isImportType ? node as ImportTypeNode : (node.parent as PropertyAccessExpression), symbol.name); - const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol, typeChecker); - const isValidAccess: (symbol: Symbol) => boolean = - isNamespaceName - // At `namespace N.M/**/`, if this is the only declaration of `M`, don't include `M` as a completion. - ? symbol => !!(symbol.flags & SymbolFlags.Namespace) && !symbol.declarations?.every(d => d.parent === node.parent) - : isRhsOfImportDeclaration ? - // Any kind is allowed when dotting off namespace in internal import equals declaration - symbol => isValidTypeAccess(symbol) || isValidValueAccess(symbol) : - isTypeLocation ? isValidTypeAccess : isValidValueAccess; - for (const exportedSymbol of exportedSymbols) { - if (isValidAccess(exportedSymbol)) { - symbols.push(exportedSymbol); - } + function getTypeScriptMemberSymbols(): void { + // Right of dot member completion list + completionKind = CompletionKind.PropertyAccess; + + // Since this is qualified name check it's a type node location + const isImportType = isLiteralImportTypeNode(node); + const isTypeLocation = insideJsDocTagTypeExpression + || (isImportType && !(node as ImportTypeNode).isTypeOf) + || isPartOfTypeNode(node.parent) + || isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker); + const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node); + if (isEntityName(node) || isImportType || isPropertyAccessExpression(node)) { + const isNamespaceName = isModuleDeclaration(node.parent); + if (isNamespaceName) isNewIdentifierLocation = true; + let symbol = typeChecker.getSymbolAtLocation(node); + if (symbol) { + symbol = skipAlias(symbol, typeChecker); + if (symbol.flags & (SymbolFlags.Module | SymbolFlags.Enum)) { + // Extract module or enum members + const exportedSymbols = typeChecker.getExportsOfModule(symbol); + Debug.assertEachIsDefined(exportedSymbols, "getExportsOfModule() should all be defined"); + const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(isImportType ? node as ImportTypeNode : (node.parent as PropertyAccessExpression), symbol.name); + const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol, typeChecker); + const isValidAccess: (symbol: Symbol) => boolean = + isNamespaceName + // At `namespace N.M/**/`, if this is the only declaration of `M`, don't include `M` as a completion. + ? symbol => !!(symbol.flags & SymbolFlags.Namespace) && !symbol.declarations?.every(d => d.parent === node.parent) + : isRhsOfImportDeclaration ? + // Any kind is allowed when dotting off namespace in internal import equals declaration + symbol => isValidTypeAccess(symbol) || isValidValueAccess(symbol) : + isTypeLocation ? isValidTypeAccess : isValidValueAccess; + for (const exportedSymbol of exportedSymbols) { + if (isValidAccess(exportedSymbol)) { + symbols.push(exportedSymbol); } + } - // If the module is merged with a value, we must get the type of the class and add its propertes (for inherited static methods). - if (!isTypeLocation && - symbol.declarations && - symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { - let type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType(); - let insertQuestionDot = false; - if (type.isNullableType()) { - const canCorrectToQuestionDot = - isRightOfDot && - !isRightOfQuestionDot && - preferences.includeAutomaticOptionalChainCompletions !== false; - - if (canCorrectToQuestionDot || isRightOfQuestionDot) { - type = type.getNonNullableType(); - if (canCorrectToQuestionDot) { - insertQuestionDot = true; - } + // If the module is merged with a value, we must get the type of the class and add its propertes (for inherited static methods). + if (!isTypeLocation && + symbol.declarations && + symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { + let type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType(); + let insertQuestionDot = false; + if (type.isNullableType()) { + const canCorrectToQuestionDot = + isRightOfDot && + !isRightOfQuestionDot && + preferences.includeAutomaticOptionalChainCompletions !== false; + + if (canCorrectToQuestionDot || isRightOfQuestionDot) { + type = type.getNonNullableType(); + if (canCorrectToQuestionDot) { + insertQuestionDot = true; } } - addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } - - return; + addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } - } - } - if (!isTypeLocation) { - // GH#39946. Pulling on the type of a node inside of a function with a contextual `this` parameter can result in a circularity - // if the `node` is part of the exprssion of a `yield` or `return`. This circularity doesn't exist at compile time because - // we will check (and cache) the type of `this` *before* checking the type of the node. - const container = getThisContainer(node, /*includeArrowFunctions*/ false); - if (!isSourceFile(container) && container.parent) typeChecker.getTypeAtLocation(container); - - let type = typeChecker.getTypeAtLocation(node).getNonOptionalType(); - let insertQuestionDot = false; - if (type.isNullableType()) { - const canCorrectToQuestionDot = - isRightOfDot && - !isRightOfQuestionDot && - preferences.includeAutomaticOptionalChainCompletions !== false; - - if (canCorrectToQuestionDot || isRightOfQuestionDot) { - type = type.getNonNullableType(); - if (canCorrectToQuestionDot) { - insertQuestionDot = true; - } - } - } - addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); - } - } - - function addTypeProperties(type: Type, insertAwait: boolean, insertQuestionDot: boolean): void { - isNewIdentifierLocation = !!type.getStringIndexType(); - if (isRightOfQuestionDot && some(type.getCallSignatures())) { - isNewIdentifierLocation = true; - } - - const propertyAccess = node.kind === SyntaxKind.ImportType ? node as ImportTypeNode : node.parent as PropertyAccessExpression | QualifiedName; - if (isUncheckedFile) { - // In javascript files, for union types, we don't just get the members that - // the individual types have in common, we also include all the members that - // each individual type has. This is because we're going to add all identifiers - // anyways. So we might as well elevate the members that were at least part - // of the individual types to a higher status since we know what they are. - symbols.push(...filter(getPropertiesForCompletion(type, typeChecker), s => typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, s))); - } - else { - for (const symbol of type.getApparentProperties()) { - if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, symbol)) { - addPropertySymbol(symbol, /* insertAwait */ false, insertQuestionDot); - } - } - } - - if (insertAwait && preferences.includeCompletionsWithInsertText) { - const promiseType = typeChecker.getPromisedTypeOfPromise(type); - if (promiseType) { - for (const symbol of promiseType.getApparentProperties()) { - if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, promiseType, symbol)) { - addPropertySymbol(symbol, /* insertAwait */ true, insertQuestionDot); - } - } + return; } } } - function addPropertySymbol(symbol: Symbol, insertAwait: boolean, insertQuestionDot: boolean) { - // For a computed property with an accessible name like `Symbol.iterator`, - // we'll add a completion for the *name* `Symbol` instead of for the property. - // If this is e.g. [Symbol.iterator], add a completion for `Symbol`. - const computedPropertyName = firstDefined(symbol.declarations, decl => tryCast(getNameOfDeclaration(decl), isComputedPropertyName)); - if (computedPropertyName) { - const leftMostName = getLeftMostName(computedPropertyName.expression); // The completion is for `Symbol`, not `iterator`. - const nameSymbol = leftMostName && typeChecker.getSymbolAtLocation(leftMostName); - // If this is nested like for `namespace N { export const sym = Symbol(); }`, we'll add the completion for `N`. - const firstAccessibleSymbol = nameSymbol && getFirstSymbolInChain(nameSymbol, contextToken, typeChecker); - if (firstAccessibleSymbol && addToSeen(seenPropertySymbols, getSymbolId(firstAccessibleSymbol))) { - const index = symbols.length; - symbols.push(firstAccessibleSymbol); - const moduleSymbol = firstAccessibleSymbol.parent; - if (!moduleSymbol || - !isExternalModuleSymbol(moduleSymbol) || - typeChecker.tryGetMemberInModuleExportsAndProperties(firstAccessibleSymbol.name, moduleSymbol) !== firstAccessibleSymbol - ) { - symbolToOriginInfoMap[index] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberNoExport) }; - } - else { - const fileName = isExternalModuleNameRelative(stripQuotes(moduleSymbol.name)) ? getSourceFileOfModule(moduleSymbol)?.fileName : undefined; - const { moduleSpecifier } = codefix.getModuleSpecifierForBestExportInfo([{ - exportKind: ExportKind.Named, - moduleFileName: fileName, - isFromPackageJson: false, - moduleSymbol, - symbol: firstAccessibleSymbol, - targetFlags: skipAlias(firstAccessibleSymbol, typeChecker).flags, - }], sourceFile, program, host, preferences) || {}; - - if (moduleSpecifier) { - const origin: SymbolOriginInfoResolvedExport = { - kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport), - moduleSymbol, - isDefaultExport: false, - symbolName: firstAccessibleSymbol.name, - exportName: firstAccessibleSymbol.name, - fileName, - moduleSpecifier, - }; - symbolToOriginInfoMap[index] = origin; - } - } - } - else if (preferences.includeCompletionsWithInsertText) { - addSymbolOriginInfo(symbol); - addSymbolSortInfo(symbol); - symbols.push(symbol); - } - } - else { - addSymbolOriginInfo(symbol); - addSymbolSortInfo(symbol); - symbols.push(symbol); - } + if (!isTypeLocation) { + // GH#39946. Pulling on the type of a node inside of a function with a contextual `this` parameter can result in a circularity + // if the `node` is part of the exprssion of a `yield` or `return`. This circularity doesn't exist at compile time because + // we will check (and cache) the type of `this` *before* checking the type of the node. + const container = getThisContainer(node, /*includeArrowFunctions*/ false); + if (!isSourceFile(container) && container.parent) typeChecker.getTypeAtLocation(container); - function addSymbolSortInfo(symbol: Symbol) { - if (isStaticProperty(symbol)) { - symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.LocalDeclarationPriority; - } - } + let type = typeChecker.getTypeAtLocation(node).getNonOptionalType(); + let insertQuestionDot = false; + if (type.isNullableType()) { + const canCorrectToQuestionDot = + isRightOfDot && + !isRightOfQuestionDot && + preferences.includeAutomaticOptionalChainCompletions !== false; - function addSymbolOriginInfo(symbol: Symbol) { - if (preferences.includeCompletionsWithInsertText) { - if (insertAwait && addToSeen(seenPropertySymbols, getSymbolId(symbol))) { - symbolToOriginInfoMap[symbols.length] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.Promise) }; - } - else if (insertQuestionDot) { - symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.Nullable }; + if (canCorrectToQuestionDot || isRightOfQuestionDot) { + type = type.getNonNullableType(); + if (canCorrectToQuestionDot) { + insertQuestionDot = true; } } } - - function getNullableSymbolOriginInfoKind(kind: SymbolOriginInfoKind) { - return insertQuestionDot ? kind | SymbolOriginInfoKind.Nullable : kind; - } - } - - /** Given 'a.b.c', returns 'a'. */ - function getLeftMostName(e: Expression): Identifier | undefined { - return isIdentifier(e) ? e : isPropertyAccessExpression(e) ? getLeftMostName(e.expression) : undefined; - } - - function tryGetGlobalSymbols(): boolean { - const result: GlobalsSearch = tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols() - || tryGetObjectLikeCompletionSymbols() - || tryGetImportCompletionSymbols() - || tryGetImportOrExportClauseCompletionSymbols() - || tryGetLocalNamedExportCompletionSymbols() - || tryGetConstructorCompletion() - || tryGetClassLikeCompletionSymbols() - || tryGetJsxCompletionSymbols() - || (getGlobalCompletions(), GlobalsSearch.Success); - return result === GlobalsSearch.Success; + addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } + } - function tryGetConstructorCompletion(): GlobalsSearch { - if (!tryGetConstructorLikeCompletionContainer(contextToken)) return GlobalsSearch.Continue; - // no members, only keywords - completionKind = CompletionKind.None; - // Declaring new property/method/accessor + function addTypeProperties(type: Type, insertAwait: boolean, insertQuestionDot: boolean): void { + isNewIdentifierLocation = !!type.getStringIndexType(); + if (isRightOfQuestionDot && some(type.getCallSignatures())) { isNewIdentifierLocation = true; - // Has keywords for constructor parameter - keywordFilters = KeywordCompletionFilters.ConstructorParameterKeywords; - return GlobalsSearch.Success; - } - - function tryGetJsxCompletionSymbols(): GlobalsSearch { - const jsxContainer = tryGetContainingJsxElement(contextToken); - // Cursor is inside a JSX self-closing element or opening element - const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes); - if (!attrsType) return GlobalsSearch.Continue; - const completionsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.Completions); - symbols = concatenate(symbols, filterJsxAttributes(getPropertiesForObjectExpression(attrsType, completionsType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties)); - setSortTextToOptionalMember(); - completionKind = CompletionKind.MemberLike; - isNewIdentifierLocation = false; - return GlobalsSearch.Success; } - function tryGetImportCompletionSymbols(): GlobalsSearch { - if (!importCompletionNode) return GlobalsSearch.Continue; - isNewIdentifierLocation = true; - collectAutoImports(); - return GlobalsSearch.Success; - } - - function getGlobalCompletions(): void { - keywordFilters = tryGetFunctionLikeBodyCompletionContainer(contextToken) ? KeywordCompletionFilters.FunctionLikeBodyKeywords : KeywordCompletionFilters.All; - - // Get all entities in the current scope. - completionKind = CompletionKind.Global; - isNewIdentifierLocation = isNewIdentifierDefinitionLocation(); - - if (previousToken !== contextToken) { - Debug.assert(!!previousToken, "Expected 'contextToken' to be defined when different from 'previousToken'."); - } - // We need to find the node that will give us an appropriate scope to begin - // aggregating completion candidates. This is achieved in 'getScopeNode' - // by finding the first node that encompasses a position, accounting for whether a node - // is "complete" to decide whether a position belongs to the node. - // - // However, at the end of an identifier, we are interested in the scope of the identifier - // itself, but fall outside of the identifier. For instance: - // - // xyz => x$ - // - // the cursor is outside of both the 'x' and the arrow function 'xyz => x', - // so 'xyz' is not returned in our results. - // - // We define 'adjustedPosition' so that we may appropriately account for - // being at the end of an identifier. The intention is that if requesting completion - // at the end of an identifier, it should be effectively equivalent to requesting completion - // anywhere inside/at the beginning of the identifier. So in the previous case, the - // 'adjustedPosition' will work as if requesting completion in the following: - // - // xyz => $x - // - // If previousToken !== contextToken, then - // - 'contextToken' was adjusted to the token prior to 'previousToken' - // because we were at the end of an identifier. - // - 'previousToken' is defined. - const adjustedPosition = previousToken !== contextToken ? - previousToken.getStart() : - position; - - const scopeNode = getScopeNode(contextToken, adjustedPosition, sourceFile) || sourceFile; - isInSnippetScope = isSnippetScope(scopeNode); - - const symbolMeanings = (isTypeOnlyLocation ? SymbolFlags.None : SymbolFlags.Value) | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias; - - symbols = concatenate(symbols, typeChecker.getSymbolsInScope(scopeNode, symbolMeanings)); - Debug.assertEachIsDefined(symbols, "getSymbolsInScope() should all be defined"); - for (const symbol of symbols) { - if (!typeChecker.isArgumentsSymbol(symbol) && - !some(symbol.declarations, d => d.getSourceFile() === sourceFile)) { - symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.GlobalsOrKeywords; - } - } - - // Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions` - if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) { - const thisType = typeChecker.tryGetThisTypeAt(scopeNode, /*includeGlobalThis*/ false); - if (thisType && !isProbablyGlobalType(thisType, sourceFile, typeChecker)) { - for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) { - symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.ThisType }; - symbols.push(symbol); - symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.SuggestedClassMembers; - } + const propertyAccess = node.kind === SyntaxKind.ImportType ? node as ImportTypeNode : node.parent as PropertyAccessExpression | QualifiedName; + if (isUncheckedFile) { + // In javascript files, for union types, we don't just get the members that + // the individual types have in common, we also include all the members that + // each individual type has. This is because we're going to add all identifiers + // anyways. So we might as well elevate the members that were at least part + // of the individual types to a higher status since we know what they are. + symbols.push(...filter(getPropertiesForCompletion(type, typeChecker), s => typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, s))); + } + else { + for (const symbol of type.getApparentProperties()) { + if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, symbol)) { + addPropertySymbol(symbol, /* insertAwait */ false, insertQuestionDot); } } - collectAutoImports(); - if (isTypeOnlyLocation) { - keywordFilters = contextToken && isAssertionExpression(contextToken.parent) - ? KeywordCompletionFilters.TypeAssertionKeywords - : KeywordCompletionFilters.TypeKeywords; - } - } - - function shouldOfferImportCompletions(): boolean { - // If already typing an import statement, provide completions for it. - if (importCompletionNode) return true; - // If current completion is for non-contextual Object literal shortahands, ignore auto-import symbols - if (isNonContextualObjectLiteral) return false; - // If not already a module, must have modules enabled. - if (!preferences.includeCompletionsForModuleExports) return false; - // If already using ES modules, OK to continue using them. - if (sourceFile.externalModuleIndicator || sourceFile.commonJsModuleIndicator) return true; - // If module transpilation is enabled or we're targeting es6 or above, or not emitting, OK. - if (compilerOptionsIndicateEsModules(program.getCompilerOptions())) return true; - // If some file is using ES6 modules, assume that it's OK to add more. - return programContainsModules(program); - } - - function isSnippetScope(scopeNode: Node): boolean { - switch (scopeNode.kind) { - case SyntaxKind.SourceFile: - case SyntaxKind.TemplateExpression: - case SyntaxKind.JsxExpression: - case SyntaxKind.Block: - return true; - default: - return isStatement(scopeNode); - } - } - - function isTypeOnlyCompletion(): boolean { - return insideJsDocTagTypeExpression - || !!importCompletionNode && isTypeOnlyImportOrExportDeclaration(location.parent) - || !isContextTokenValueLocation(contextToken) && - (isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker) - || isPartOfTypeNode(location) - || isContextTokenTypeLocation(contextToken)); - } - - function isContextTokenValueLocation(contextToken: Node) { - return contextToken && - ((contextToken.kind === SyntaxKind.TypeOfKeyword && - (contextToken.parent.kind === SyntaxKind.TypeQuery || isTypeOfExpression(contextToken.parent))) || - (contextToken.kind === SyntaxKind.AssertsKeyword && contextToken.parent.kind === SyntaxKind.TypePredicate)); } - function isContextTokenTypeLocation(contextToken: Node): boolean { - if (contextToken) { - const parentKind = contextToken.parent.kind; - switch (contextToken.kind) { - case SyntaxKind.ColonToken: - return parentKind === SyntaxKind.PropertyDeclaration || - parentKind === SyntaxKind.PropertySignature || - parentKind === SyntaxKind.Parameter || - parentKind === SyntaxKind.VariableDeclaration || - isFunctionLikeKind(parentKind); - - case SyntaxKind.EqualsToken: - return parentKind === SyntaxKind.TypeAliasDeclaration; - - case SyntaxKind.AsKeyword: - return parentKind === SyntaxKind.AsExpression; - - case SyntaxKind.LessThanToken: - return parentKind === SyntaxKind.TypeReference || - parentKind === SyntaxKind.TypeAssertionExpression; - - case SyntaxKind.ExtendsKeyword: - return parentKind === SyntaxKind.TypeParameter; + if (insertAwait && preferences.includeCompletionsWithInsertText) { + const promiseType = typeChecker.getPromisedTypeOfPromise(type); + if (promiseType) { + for (const symbol of promiseType.getApparentProperties()) { + if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, promiseType, symbol)) { + addPropertySymbol(symbol, /* insertAwait */ true, insertQuestionDot); + } } } - return false; } + } - /** Mutates `symbols`, `symbolToOriginInfoMap`, and `symbolToSortTextIdMap` */ - function collectAutoImports() { - if (!shouldOfferImportCompletions()) return; - Debug.assert(!detailsEntryId?.data, "Should not run 'collectAutoImports' when faster path is available via `data`"); - if (detailsEntryId && !detailsEntryId.source) { - // Asking for completion details for an item that is not an auto-import - return; - } - - // import { type | -> token text should be blank - const isAfterTypeOnlyImportSpecifierModifier = previousToken === contextToken - && importCompletionNode - && couldBeTypeOnlyImportSpecifier(importCompletionNode, contextToken); - - const lowerCaseTokenText = - isAfterTypeOnlyImportSpecifierModifier ? "" : - previousToken && isIdentifier(previousToken) ? previousToken.text.toLowerCase() : - ""; - - const moduleSpecifierCache = host.getModuleSpecifierCache?.(); - const exportInfo = getExportInfoMap(sourceFile, host, program, cancellationToken); - const packageJsonAutoImportProvider = host.getPackageJsonAutoImportProvider?.(); - const packageJsonFilter = detailsEntryId ? undefined : createPackageJsonImportFilter(sourceFile, preferences, host); - resolvingModuleSpecifiers( - "collectAutoImports", - host, - program, - sourceFile, - preferences, - !!importCompletionNode, - context => { - exportInfo.forEach(sourceFile.path, (info, symbolName, isFromAmbientModule, exportMapKey) => { - if (!isIdentifierText(symbolName, getEmitScriptTarget(host.getCompilationSettings()))) return; - if (!detailsEntryId && isStringANonContextualKeyword(symbolName)) return; - // `targetFlags` should be the same for each `info` - if (!isTypeOnlyLocation && !importCompletionNode && !(info[0].targetFlags & SymbolFlags.Value)) return; - if (isTypeOnlyLocation && !(info[0].targetFlags & (SymbolFlags.Module | SymbolFlags.Type))) return; - const isCompletionDetailsMatch = detailsEntryId && some(info, i => detailsEntryId.source === stripQuotes(i.moduleSymbol.name)); - if (isCompletionDetailsMatch || !detailsEntryId && charactersFuzzyMatchInString(symbolName, lowerCaseTokenText)) { - const defaultExportInfo = find(info, isImportableExportInfo); - if (!defaultExportInfo) { - return; - } - - // If we don't need to resolve module specifiers, we can use any re-export that is importable at all - // (We need to ensure that at least one is importable to show a completion.) - const { exportInfo = defaultExportInfo, moduleSpecifier } = context.tryResolve(info, isFromAmbientModule) || {}; - const isDefaultExport = exportInfo.exportKind === ExportKind.Default; - const symbol = isDefaultExport && getLocalSymbolForExportDefault(exportInfo.symbol) || exportInfo.symbol; - pushAutoImportSymbol(symbol, { - kind: moduleSpecifier ? SymbolOriginInfoKind.ResolvedExport : SymbolOriginInfoKind.Export, - moduleSpecifier, - symbolName, - exportMapKey, - exportName: exportInfo.exportKind === ExportKind.ExportEquals ? InternalSymbolName.ExportEquals : exportInfo.symbol.name, - fileName: exportInfo.moduleFileName, - isDefaultExport, - moduleSymbol: exportInfo.moduleSymbol, - isFromPackageJson: exportInfo.isFromPackageJson, - }); - } - }); - - hasUnresolvedAutoImports = context.resolutionLimitExceeded(); + function addPropertySymbol(symbol: Symbol, insertAwait: boolean, insertQuestionDot: boolean) { + // For a computed property with an accessible name like `Symbol.iterator`, + // we'll add a completion for the *name* `Symbol` instead of for the property. + // If this is e.g. [Symbol.iterator], add a completion for `Symbol`. + const computedPropertyName = firstDefined(symbol.declarations, decl => tryCast(getNameOfDeclaration(decl), isComputedPropertyName)); + if (computedPropertyName) { + const leftMostName = getLeftMostName(computedPropertyName.expression); // The completion is for `Symbol`, not `iterator`. + const nameSymbol = leftMostName && typeChecker.getSymbolAtLocation(leftMostName); + // If this is nested like for `namespace N { export const sym = Symbol(); }`, we'll add the completion for `N`. + const firstAccessibleSymbol = nameSymbol && getFirstSymbolInChain(nameSymbol, contextToken, typeChecker); + if (firstAccessibleSymbol && addToSeen(seenPropertySymbols, getSymbolId(firstAccessibleSymbol))) { + const index = symbols.length; + symbols.push(firstAccessibleSymbol); + const moduleSymbol = firstAccessibleSymbol.parent; + if (!moduleSymbol || + !isExternalModuleSymbol(moduleSymbol) || + typeChecker.tryGetMemberInModuleExportsAndProperties(firstAccessibleSymbol.name, moduleSymbol) !== firstAccessibleSymbol + ) { + symbolToOriginInfoMap[index] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberNoExport) }; } - ); - - function isImportableExportInfo(info: SymbolExportInfo) { - const moduleFile = tryCast(info.moduleSymbol.valueDeclaration, isSourceFile); - if (!moduleFile) { - const moduleName = stripQuotes(info.moduleSymbol.name); - if (JsTyping.nodeCoreModules.has(moduleName) && startsWith(moduleName, "node:") !== shouldUseUriStyleNodeCoreModules(sourceFile, program)) { - return false; + else { + const fileName = isExternalModuleNameRelative(stripQuotes(moduleSymbol.name)) ? getSourceFileOfModule(moduleSymbol)?.fileName : undefined; + const { moduleSpecifier } = codefix.getModuleSpecifierForBestExportInfo([{ + exportKind: ExportKind.Named, + moduleFileName: fileName, + isFromPackageJson: false, + moduleSymbol, + symbol: firstAccessibleSymbol, + targetFlags: skipAlias(firstAccessibleSymbol, typeChecker).flags, + }], sourceFile, program, host, preferences) || {}; + + if (moduleSpecifier) { + const origin: SymbolOriginInfoResolvedExport = { + kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport), + moduleSymbol, + isDefaultExport: false, + symbolName: firstAccessibleSymbol.name, + exportName: firstAccessibleSymbol.name, + fileName, + moduleSpecifier, + }; + symbolToOriginInfoMap[index] = origin; } - return packageJsonFilter - ? packageJsonFilter.allowsImportingAmbientModule(info.moduleSymbol, getModuleSpecifierResolutionHost(info.isFromPackageJson)) - : true; } - return isImportableFile( - info.isFromPackageJson ? packageJsonAutoImportProvider! : program, - sourceFile, - moduleFile, - preferences, - packageJsonFilter, - getModuleSpecifierResolutionHost(info.isFromPackageJson), - moduleSpecifierCache); } - } - - function pushAutoImportSymbol(symbol: Symbol, origin: SymbolOriginInfoResolvedExport | SymbolOriginInfoExport) { - const symbolId = getSymbolId(symbol); - if (symbolToSortTextIdMap[symbolId] === SortTextId.GlobalsOrKeywords) { - // If an auto-importable symbol is available as a global, don't add the auto import - return; - } - symbolToOriginInfoMap[symbols.length] = origin; - symbolToSortTextIdMap[symbolId] = importCompletionNode ? SortTextId.LocationPriority : SortTextId.AutoImportSuggestions; - symbols.push(symbol); - } - - /** - * Finds the first node that "embraces" the position, so that one may - * accurately aggregate locals from the closest containing scope. - */ - function getScopeNode(initialToken: Node | undefined, position: number, sourceFile: SourceFile) { - let scope: Node | undefined = initialToken; - while (scope && !positionBelongsToNode(scope, position, sourceFile)) { - scope = scope.parent; + else if (preferences.includeCompletionsWithInsertText) { + addSymbolOriginInfo(symbol); + addSymbolSortInfo(symbol); + symbols.push(symbol); } - return scope; } - - function isCompletionListBlocker(contextToken: Node): boolean { - const start = timestamp(); - const result = isInStringOrRegularExpressionOrTemplateLiteral(contextToken) || - isSolelyIdentifierDefinitionLocation(contextToken) || - isDotOfNumericLiteral(contextToken) || - isInJsxText(contextToken) || - isBigIntLiteral(contextToken); - log("getCompletionsAtPosition: isCompletionListBlocker: " + (timestamp() - start)); - return result; + else { + addSymbolOriginInfo(symbol); + addSymbolSortInfo(symbol); + symbols.push(symbol); } - function isInJsxText(contextToken: Node): boolean { - if (contextToken.kind === SyntaxKind.JsxText) { - return true; - } - - if (contextToken.kind === SyntaxKind.GreaterThanToken && contextToken.parent) { - if (contextToken.parent.kind === SyntaxKind.JsxOpeningElement) { - // Two possibilities: - // 1.
/**/ - // - contextToken: GreaterThanToken (before cursor) - // - location: JSXElement - // - different parents (JSXOpeningElement, JSXElement) - // 2. /**/> - // - contextToken: GreaterThanToken (before cursor) - // - location: GreaterThanToken (after cursor) - // - same parent (JSXOpeningElement) - return location.parent.kind !== SyntaxKind.JsxOpeningElement; - } - - if (contextToken.parent.kind === SyntaxKind.JsxClosingElement || contextToken.parent.kind === SyntaxKind.JsxSelfClosingElement) { - return !!contextToken.parent.parent && contextToken.parent.parent.kind === SyntaxKind.JsxElement; - } + function addSymbolSortInfo(symbol: Symbol) { + if (isStaticProperty(symbol)) { + symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.LocalDeclarationPriority; } - return false; } - function isNewIdentifierDefinitionLocation(): boolean { - if (contextToken) { - const containingNodeKind = contextToken.parent.kind; - const tokenKind = keywordForNode(contextToken); - // Previous token may have been a keyword that was converted to an identifier. - switch (tokenKind) { - case SyntaxKind.CommaToken: - return containingNodeKind === SyntaxKind.CallExpression // func( a, | - || containingNodeKind === SyntaxKind.Constructor // constructor( a, | /* public, protected, private keywords are allowed here, so show completion */ - || containingNodeKind === SyntaxKind.NewExpression // new C(a, | - || containingNodeKind === SyntaxKind.ArrayLiteralExpression // [a, | - || containingNodeKind === SyntaxKind.BinaryExpression // const x = (a, | - || containingNodeKind === SyntaxKind.FunctionType // var x: (s: string, list| - || containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { x, | - - case SyntaxKind.OpenParenToken: - return containingNodeKind === SyntaxKind.CallExpression // func( | - || containingNodeKind === SyntaxKind.Constructor // constructor( | - || containingNodeKind === SyntaxKind.NewExpression // new C(a| - || containingNodeKind === SyntaxKind.ParenthesizedExpression // const x = (a| - || containingNodeKind === SyntaxKind.ParenthesizedType; // function F(pred: (a| /* this can become an arrow function, where 'a' is the argument */ - - case SyntaxKind.OpenBracketToken: - return containingNodeKind === SyntaxKind.ArrayLiteralExpression // [ | - || containingNodeKind === SyntaxKind.IndexSignature // [ | : string ] - || containingNodeKind === SyntaxKind.ComputedPropertyName; // [ | /* this can become an index signature */ - - case SyntaxKind.ModuleKeyword: // module | - case SyntaxKind.NamespaceKeyword: // namespace | - case SyntaxKind.ImportKeyword: // import | - return true; - - case SyntaxKind.DotToken: - return containingNodeKind === SyntaxKind.ModuleDeclaration; // module A.| - - case SyntaxKind.OpenBraceToken: - return containingNodeKind === SyntaxKind.ClassDeclaration // class A { | - || containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { | - - case SyntaxKind.EqualsToken: - return containingNodeKind === SyntaxKind.VariableDeclaration // const x = a| - || containingNodeKind === SyntaxKind.BinaryExpression; // x = a| - - case SyntaxKind.TemplateHead: - return containingNodeKind === SyntaxKind.TemplateExpression; // `aa ${| - - case SyntaxKind.TemplateMiddle: - return containingNodeKind === SyntaxKind.TemplateSpan; // `aa ${10} dd ${| - - case SyntaxKind.AsyncKeyword: - return containingNodeKind === SyntaxKind.MethodDeclaration // const obj = { async c|() - || containingNodeKind === SyntaxKind.ShorthandPropertyAssignment; // const obj = { async c| - - case SyntaxKind.AsteriskToken: - return containingNodeKind === SyntaxKind.MethodDeclaration; // const obj = { * c| + function addSymbolOriginInfo(symbol: Symbol) { + if (preferences.includeCompletionsWithInsertText) { + if (insertAwait && addToSeen(seenPropertySymbols, getSymbolId(symbol))) { + symbolToOriginInfoMap[symbols.length] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.Promise) }; } - - if (isClassMemberCompletionKeyword(tokenKind)) { - return true; + else if (insertQuestionDot) { + symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.Nullable }; } } - - return false; } - function isInStringOrRegularExpressionOrTemplateLiteral(contextToken: Node): boolean { - // To be "in" one of these literals, the position has to be: - // 1. entirely within the token text. - // 2. at the end position of an unterminated token. - // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). - return (isRegularExpressionLiteral(contextToken) || isStringTextContainingNode(contextToken)) && ( - rangeContainsPositionExclusive(createTextRangeFromSpan(createTextSpanFromNode(contextToken)), position) || - position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken))); + function getNullableSymbolOriginInfoKind(kind: SymbolOriginInfoKind) { + return insertQuestionDot ? kind | SymbolOriginInfoKind.Nullable : kind; } + } - function tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols(): GlobalsSearch | undefined { - const typeLiteralNode = tryGetTypeLiteralNode(contextToken); - if (!typeLiteralNode) return GlobalsSearch.Continue; - - const intersectionTypeNode = isIntersectionTypeNode(typeLiteralNode.parent) ? typeLiteralNode.parent : undefined; - const containerTypeNode = intersectionTypeNode || typeLiteralNode; + /** Given 'a.b.c', returns 'a'. */ + function getLeftMostName(e: Expression): Identifier | undefined { + return isIdentifier(e) ? e : isPropertyAccessExpression(e) ? getLeftMostName(e.expression) : undefined; + } - const containerExpectedType = getConstraintOfTypeArgumentProperty(containerTypeNode, typeChecker); - if (!containerExpectedType) return GlobalsSearch.Continue; + function tryGetGlobalSymbols(): boolean { + const result: GlobalsSearch = tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols() + || tryGetObjectLikeCompletionSymbols() + || tryGetImportCompletionSymbols() + || tryGetImportOrExportClauseCompletionSymbols() + || tryGetLocalNamedExportCompletionSymbols() + || tryGetConstructorCompletion() + || tryGetClassLikeCompletionSymbols() + || tryGetJsxCompletionSymbols() + || (getGlobalCompletions(), GlobalsSearch.Success); + return result === GlobalsSearch.Success; + } - const containerActualType = typeChecker.getTypeFromTypeNode(containerTypeNode); + function tryGetConstructorCompletion(): GlobalsSearch { + if (!tryGetConstructorLikeCompletionContainer(contextToken)) return GlobalsSearch.Continue; + // no members, only keywords + completionKind = CompletionKind.None; + // Declaring new property/method/accessor + isNewIdentifierLocation = true; + // Has keywords for constructor parameter + keywordFilters = KeywordCompletionFilters.ConstructorParameterKeywords; + return GlobalsSearch.Success; + } - const members = getPropertiesForCompletion(containerExpectedType, typeChecker); - const existingMembers = getPropertiesForCompletion(containerActualType, typeChecker); + function tryGetJsxCompletionSymbols(): GlobalsSearch { + const jsxContainer = tryGetContainingJsxElement(contextToken); + // Cursor is inside a JSX self-closing element or opening element + const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes); + if (!attrsType) return GlobalsSearch.Continue; + const completionsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.Completions); + symbols = concatenate(symbols, filterJsxAttributes(getPropertiesForObjectExpression(attrsType, completionsType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties)); + setSortTextToOptionalMember(); + completionKind = CompletionKind.MemberLike; + isNewIdentifierLocation = false; + return GlobalsSearch.Success; + } - const existingMemberEscapedNames: Set<__String> = new Set(); - existingMembers.forEach(s => existingMemberEscapedNames.add(s.escapedName)); + function tryGetImportCompletionSymbols(): GlobalsSearch { + if (!importCompletionNode) return GlobalsSearch.Continue; + isNewIdentifierLocation = true; + collectAutoImports(); + return GlobalsSearch.Success; + } - symbols = concatenate(symbols, filter(members, s => !existingMemberEscapedNames.has(s.escapedName))); + function getGlobalCompletions(): void { + keywordFilters = tryGetFunctionLikeBodyCompletionContainer(contextToken) ? KeywordCompletionFilters.FunctionLikeBodyKeywords : KeywordCompletionFilters.All; - completionKind = CompletionKind.ObjectPropertyDeclaration; - isNewIdentifierLocation = true; + // Get all entities in the current scope. + completionKind = CompletionKind.Global; + isNewIdentifierLocation = isNewIdentifierDefinitionLocation(); - return GlobalsSearch.Success; + if (previousToken !== contextToken) { + Debug.assert(!!previousToken, "Expected 'contextToken' to be defined when different from 'previousToken'."); } - - /** - * Aggregates relevant symbols for completion in object literals and object binding patterns. - * Relevant symbols are stored in the captured 'symbols' variable. - * - * @returns true if 'symbols' was successfully populated; false otherwise. - */ - function tryGetObjectLikeCompletionSymbols(): GlobalsSearch | undefined { - const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); - if (!objectLikeContainer) return GlobalsSearch.Continue; - - // We're looking up possible property names from contextual/inferred/declared type. - completionKind = CompletionKind.ObjectPropertyDeclaration; - - let typeMembers: Symbol[] | undefined; - let existingMembers: readonly Declaration[] | undefined; - - if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { - const instantiatedType = tryGetObjectLiteralContextualType(objectLikeContainer, typeChecker); - - // Check completions for Object property value shorthand - if (instantiatedType === undefined) { - if (objectLikeContainer.flags & NodeFlags.InWithStatement) { - return GlobalsSearch.Fail; - } - isNonContextualObjectLiteral = true; - return GlobalsSearch.Continue; - } - const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completions); - const hasStringIndexType = (completionsType || instantiatedType).getStringIndexType(); - const hasNumberIndextype = (completionsType || instantiatedType).getNumberIndexType(); - isNewIdentifierLocation = !!hasStringIndexType || !!hasNumberIndextype; - typeMembers = getPropertiesForObjectExpression(instantiatedType, completionsType, objectLikeContainer, typeChecker); - existingMembers = objectLikeContainer.properties; - - if (typeMembers.length === 0) { - // Edge case: If NumberIndexType exists - if (!hasNumberIndextype) { - isNonContextualObjectLiteral = true; - return GlobalsSearch.Continue; - } - } - } - else { - Debug.assert(objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern); - // We are *only* completing on properties from the type being destructured. - isNewIdentifierLocation = false; - - const rootDeclaration = getRootDeclaration(objectLikeContainer.parent); - if (!isVariableLike(rootDeclaration)) return Debug.fail("Root declaration is not variable-like."); - - // We don't want to complete using the type acquired by the shape - // of the binding pattern; we are only interested in types acquired - // through type declaration or inference. - // Also proceed if rootDeclaration is a parameter and if its containing function expression/arrow function is contextually typed - - // type of parameter will flow in from the contextual type of the function - let canGetType = hasInitializer(rootDeclaration) || hasType(rootDeclaration) || rootDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement; - if (!canGetType && rootDeclaration.kind === SyntaxKind.Parameter) { - if (isExpression(rootDeclaration.parent)) { - canGetType = !!typeChecker.getContextualType(rootDeclaration.parent as Expression); - } - else if (rootDeclaration.parent.kind === SyntaxKind.MethodDeclaration || rootDeclaration.parent.kind === SyntaxKind.SetAccessor) { - canGetType = isExpression(rootDeclaration.parent.parent) && !!typeChecker.getContextualType(rootDeclaration.parent.parent as Expression); - } - } - if (canGetType) { - const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); - if (!typeForObject) return GlobalsSearch.Fail; - typeMembers = typeChecker.getPropertiesOfType(typeForObject).filter(propertySymbol => { - return typeChecker.isPropertyAccessible(objectLikeContainer, /*isSuper*/ false, /*writing*/ false, typeForObject, propertySymbol); - }); - existingMembers = objectLikeContainer.elements; + // We need to find the node that will give us an appropriate scope to begin + // aggregating completion candidates. This is achieved in 'getScopeNode' + // by finding the first node that encompasses a position, accounting for whether a node + // is "complete" to decide whether a position belongs to the node. + // + // However, at the end of an identifier, we are interested in the scope of the identifier + // itself, but fall outside of the identifier. For instance: + // + // xyz => x$ + // + // the cursor is outside of both the 'x' and the arrow function 'xyz => x', + // so 'xyz' is not returned in our results. + // + // We define 'adjustedPosition' so that we may appropriately account for + // being at the end of an identifier. The intention is that if requesting completion + // at the end of an identifier, it should be effectively equivalent to requesting completion + // anywhere inside/at the beginning of the identifier. So in the previous case, the + // 'adjustedPosition' will work as if requesting completion in the following: + // + // xyz => $x + // + // If previousToken !== contextToken, then + // - 'contextToken' was adjusted to the token prior to 'previousToken' + // because we were at the end of an identifier. + // - 'previousToken' is defined. + const adjustedPosition = previousToken !== contextToken ? + previousToken.getStart() : + position; + + const scopeNode = getScopeNode(contextToken, adjustedPosition, sourceFile) || sourceFile; + isInSnippetScope = isSnippetScope(scopeNode); + + const symbolMeanings = (isTypeOnlyLocation ? SymbolFlags.None : SymbolFlags.Value) | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias; + + symbols = concatenate(symbols, typeChecker.getSymbolsInScope(scopeNode, symbolMeanings)); + Debug.assertEachIsDefined(symbols, "getSymbolsInScope() should all be defined"); + for (const symbol of symbols) { + if (!typeChecker.isArgumentsSymbol(symbol) && + !some(symbol.declarations, d => d.getSourceFile() === sourceFile)) { + symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.GlobalsOrKeywords; + } + } + + // Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions` + if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) { + const thisType = typeChecker.tryGetThisTypeAt(scopeNode, /*includeGlobalThis*/ false); + if (thisType && !isProbablyGlobalType(thisType, sourceFile, typeChecker)) { + for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) { + symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.ThisType }; + symbols.push(symbol); + symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.SuggestedClassMembers; } } + } + collectAutoImports(); + if (isTypeOnlyLocation) { + keywordFilters = contextToken && isAssertionExpression(contextToken.parent) + ? KeywordCompletionFilters.TypeAssertionKeywords + : KeywordCompletionFilters.TypeKeywords; + } + } - if (typeMembers && typeMembers.length > 0) { - // Add filtered items to the completion list - symbols = concatenate(symbols, filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers))); - } - setSortTextToOptionalMember(); + function shouldOfferImportCompletions(): boolean { + // If already typing an import statement, provide completions for it. + if (importCompletionNode) return true; + // If current completion is for non-contextual Object literal shortahands, ignore auto-import symbols + if (isNonContextualObjectLiteral) return false; + // If not already a module, must have modules enabled. + if (!preferences.includeCompletionsForModuleExports) return false; + // If already using ES modules, OK to continue using them. + if (sourceFile.externalModuleIndicator || sourceFile.commonJsModuleIndicator) return true; + // If module transpilation is enabled or we're targeting es6 or above, or not emitting, OK. + if (compilerOptionsIndicateEsModules(program.getCompilerOptions())) return true; + // If some file is using ES6 modules, assume that it's OK to add more. + return programContainsModules(program); + } - return GlobalsSearch.Success; + function isSnippetScope(scopeNode: Node): boolean { + switch (scopeNode.kind) { + case SyntaxKind.SourceFile: + case SyntaxKind.TemplateExpression: + case SyntaxKind.JsxExpression: + case SyntaxKind.Block: + return true; + default: + return isStatement(scopeNode); } + } - /** - * Aggregates relevant symbols for completion in import clauses and export clauses - * whose declarations have a module specifier; for instance, symbols will be aggregated for - * - * import { | } from "moduleName"; - * export { a as foo, | } from "moduleName"; - * - * but not for - * - * export { | }; - * - * Relevant symbols are stored in the captured 'symbols' variable. - */ - function tryGetImportOrExportClauseCompletionSymbols(): GlobalsSearch { - if (!contextToken) return GlobalsSearch.Continue; + function isTypeOnlyCompletion(): boolean { + return insideJsDocTagTypeExpression + || !!importCompletionNode && isTypeOnlyImportOrExportDeclaration(location.parent) + || !isContextTokenValueLocation(contextToken) && + (isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker) + || isPartOfTypeNode(location) + || isContextTokenTypeLocation(contextToken)); + } - // `import { |` or `import { a as 0, | }` or `import { type | }` - const namedImportsOrExports = - contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken ? tryCast(contextToken.parent, isNamedImportsOrExports) : - isTypeKeywordTokenOrIdentifier(contextToken) ? tryCast(contextToken.parent.parent, isNamedImportsOrExports) : undefined; + function isContextTokenValueLocation(contextToken: Node) { + return contextToken && + ((contextToken.kind === SyntaxKind.TypeOfKeyword && + (contextToken.parent.kind === SyntaxKind.TypeQuery || isTypeOfExpression(contextToken.parent))) || + (contextToken.kind === SyntaxKind.AssertsKeyword && contextToken.parent.kind === SyntaxKind.TypePredicate)); + } - if (!namedImportsOrExports) return GlobalsSearch.Continue; + function isContextTokenTypeLocation(contextToken: Node): boolean { + if (contextToken) { + const parentKind = contextToken.parent.kind; + switch (contextToken.kind) { + case SyntaxKind.ColonToken: + return parentKind === SyntaxKind.PropertyDeclaration || + parentKind === SyntaxKind.PropertySignature || + parentKind === SyntaxKind.Parameter || + parentKind === SyntaxKind.VariableDeclaration || + isFunctionLikeKind(parentKind); - // We can at least offer `type` at `import { |` - if (!isTypeKeywordTokenOrIdentifier(contextToken)) { - keywordFilters = KeywordCompletionFilters.TypeKeyword; - } + case SyntaxKind.EqualsToken: + return parentKind === SyntaxKind.TypeAliasDeclaration; - // try to show exported member for imported/re-exported module - const { moduleSpecifier } = namedImportsOrExports.kind === SyntaxKind.NamedImports ? namedImportsOrExports.parent.parent : namedImportsOrExports.parent; - if (!moduleSpecifier) { - isNewIdentifierLocation = true; - return namedImportsOrExports.kind === SyntaxKind.NamedImports ? GlobalsSearch.Fail : GlobalsSearch.Continue; - } - const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); // TODO: GH#18217 - if (!moduleSpecifierSymbol) { - isNewIdentifierLocation = true; - return GlobalsSearch.Fail; - } + case SyntaxKind.AsKeyword: + return parentKind === SyntaxKind.AsExpression; - completionKind = CompletionKind.MemberLike; - isNewIdentifierLocation = false; - const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol); - const existing = new Set((namedImportsOrExports.elements as NodeArray).filter(n => !isCurrentlyEditingNode(n)).map(n => (n.propertyName || n.name).escapedText)); - const uniques = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName)); - symbols = concatenate(symbols, uniques); - if (!uniques.length) { - // If there's nothing else to import, don't offer `type` either - keywordFilters = KeywordCompletionFilters.None; - } - return GlobalsSearch.Success; - } - - /** - * Adds local declarations for completions in named exports: - * - * export { | }; - * - * Does not check for the absence of a module specifier (`export {} from "./other"`) - * because `tryGetImportOrExportClauseCompletionSymbols` runs first and handles that, - * preventing this function from running. - */ - function tryGetLocalNamedExportCompletionSymbols(): GlobalsSearch { - const namedExports = contextToken && (contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken) - ? tryCast(contextToken.parent, isNamedExports) - : undefined; + case SyntaxKind.LessThanToken: + return parentKind === SyntaxKind.TypeReference || + parentKind === SyntaxKind.TypeAssertionExpression; - if (!namedExports) { - return GlobalsSearch.Continue; + case SyntaxKind.ExtendsKeyword: + return parentKind === SyntaxKind.TypeParameter; } + } + return false; + } - const localsContainer = findAncestor(namedExports, or(isSourceFile, isModuleDeclaration))!; - completionKind = CompletionKind.None; - isNewIdentifierLocation = false; - localsContainer.locals?.forEach((symbol, name) => { - symbols.push(symbol); - if (localsContainer.symbol?.exports?.has(name)) { - symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.OptionalMember; - } - }); - return GlobalsSearch.Success; + /** Mutates `symbols`, `symbolToOriginInfoMap`, and `symbolToSortTextIdMap` */ + function collectAutoImports() { + if (!shouldOfferImportCompletions()) return; + Debug.assert(!detailsEntryId?.data, "Should not run 'collectAutoImports' when faster path is available via `data`"); + if (detailsEntryId && !detailsEntryId.source) { + // Asking for completion details for an item that is not an auto-import + return; } - /** - * Aggregates relevant symbols for completion in class declaration - * Relevant symbols are stored in the captured 'symbols' variable. - */ - function tryGetClassLikeCompletionSymbols(): GlobalsSearch { - const decl = tryGetObjectTypeDeclarationCompletionContainer(sourceFile, contextToken, location, position); - if (!decl) return GlobalsSearch.Continue; + // import { type | -> token text should be blank + const isAfterTypeOnlyImportSpecifierModifier = previousToken === contextToken + && importCompletionNode + && couldBeTypeOnlyImportSpecifier(importCompletionNode, contextToken); - // We're looking up possible property names from parent type. - completionKind = CompletionKind.MemberLike; - // Declaring new property/method/accessor - isNewIdentifierLocation = true; - keywordFilters = contextToken.kind === SyntaxKind.AsteriskToken ? KeywordCompletionFilters.None : - isClassLike(decl) ? KeywordCompletionFilters.ClassElementKeywords : KeywordCompletionFilters.InterfaceElementKeywords; - - // If you're in an interface you don't want to repeat things from super-interface. So just stop here. - if (!isClassLike(decl)) return GlobalsSearch.Success; - - const classElement = contextToken.kind === SyntaxKind.SemicolonToken ? contextToken.parent.parent : contextToken.parent; - let classElementModifierFlags = isClassElement(classElement) ? getEffectiveModifierFlags(classElement) : ModifierFlags.None; - // If this is context token is not something we are editing now, consider if this would lead to be modifier - if (contextToken.kind === SyntaxKind.Identifier && !isCurrentlyEditingNode(contextToken)) { - switch (contextToken.getText()) { - case "private": - classElementModifierFlags = classElementModifierFlags | ModifierFlags.Private; - break; - case "static": - classElementModifierFlags = classElementModifierFlags | ModifierFlags.Static; - break; - case "override": - classElementModifierFlags = classElementModifierFlags | ModifierFlags.Override; - break; - } - } - if (isClassStaticBlockDeclaration(classElement)) { - classElementModifierFlags |= ModifierFlags.Static; - } + const lowerCaseTokenText = + isAfterTypeOnlyImportSpecifierModifier ? "" : + previousToken && isIdentifier(previousToken) ? previousToken.text.toLowerCase() : + ""; + + const moduleSpecifierCache = host.getModuleSpecifierCache?.(); + const exportInfo = getExportInfoMap(sourceFile, host, program, cancellationToken); + const packageJsonAutoImportProvider = host.getPackageJsonAutoImportProvider?.(); + const packageJsonFilter = detailsEntryId ? undefined : createPackageJsonImportFilter(sourceFile, preferences, host); + resolvingModuleSpecifiers( + "collectAutoImports", + host, + program, + sourceFile, + preferences, + !!importCompletionNode, + context => { + exportInfo.forEach(sourceFile.path, (info, symbolName, isFromAmbientModule, exportMapKey) => { + if (!isIdentifierText(symbolName, getEmitScriptTarget(host.getCompilationSettings()))) return; + if (!detailsEntryId && isStringANonContextualKeyword(symbolName)) return; + // `targetFlags` should be the same for each `info` + if (!isTypeOnlyLocation && !importCompletionNode && !(info[0].targetFlags & SymbolFlags.Value)) return; + if (isTypeOnlyLocation && !(info[0].targetFlags & (SymbolFlags.Module | SymbolFlags.Type))) return; + const isCompletionDetailsMatch = detailsEntryId && some(info, i => detailsEntryId.source === stripQuotes(i.moduleSymbol.name)); + if (isCompletionDetailsMatch || !detailsEntryId && charactersFuzzyMatchInString(symbolName, lowerCaseTokenText)) { + const defaultExportInfo = find(info, isImportableExportInfo); + if (!defaultExportInfo) { + return; + } - // No member list for private methods - if (!(classElementModifierFlags & ModifierFlags.Private)) { - // List of property symbols of base type that are not private and already implemented - const baseTypeNodes = isClassLike(decl) && classElementModifierFlags & ModifierFlags.Override ? singleElementArray(getEffectiveBaseTypeNode(decl)) : getAllSuperTypeNodes(decl); - const baseSymbols = flatMap(baseTypeNodes, baseTypeNode => { - const type = typeChecker.getTypeAtLocation(baseTypeNode); - return classElementModifierFlags & ModifierFlags.Static ? - type?.symbol && typeChecker.getPropertiesOfType(typeChecker.getTypeOfSymbolAtLocation(type.symbol, decl)) : - type && typeChecker.getPropertiesOfType(type); + // If we don't need to resolve module specifiers, we can use any re-export that is importable at all + // (We need to ensure that at least one is importable to show a completion.) + const { exportInfo = defaultExportInfo, moduleSpecifier } = context.tryResolve(info, isFromAmbientModule) || {}; + const isDefaultExport = exportInfo.exportKind === ExportKind.Default; + const symbol = isDefaultExport && getLocalSymbolForExportDefault(exportInfo.symbol) || exportInfo.symbol; + pushAutoImportSymbol(symbol, { + kind: moduleSpecifier ? SymbolOriginInfoKind.ResolvedExport : SymbolOriginInfoKind.Export, + moduleSpecifier, + symbolName, + exportMapKey, + exportName: exportInfo.exportKind === ExportKind.ExportEquals ? InternalSymbolName.ExportEquals : exportInfo.symbol.name, + fileName: exportInfo.moduleFileName, + isDefaultExport, + moduleSymbol: exportInfo.moduleSymbol, + isFromPackageJson: exportInfo.isFromPackageJson, + }); + } }); - symbols = concatenate(symbols, filterClassMembersList(baseSymbols, decl.members, classElementModifierFlags)); - } - return GlobalsSearch.Success; - } + hasUnresolvedAutoImports = context.resolutionLimitExceeded(); + } + ); - /** - * Returns the immediate owning object literal or binding pattern of a context token, - * on the condition that one exists and that the context implies completion should be given. - */ - function tryGetObjectLikeCompletionContainer(contextToken: Node): ObjectLiteralExpression | ObjectBindingPattern | undefined { - if (contextToken) { - const { parent } = contextToken; - switch (contextToken.kind) { - case SyntaxKind.OpenBraceToken: // const x = { | - case SyntaxKind.CommaToken: // const x = { a: 0, | - if (isObjectLiteralExpression(parent) || isObjectBindingPattern(parent)) { - return parent; - } - break; - case SyntaxKind.AsteriskToken: - return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined; - case SyntaxKind.Identifier: - return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent) - ? contextToken.parent.parent : undefined; + function isImportableExportInfo(info: SymbolExportInfo) { + const moduleFile = tryCast(info.moduleSymbol.valueDeclaration, isSourceFile); + if (!moduleFile) { + const moduleName = stripQuotes(info.moduleSymbol.name); + if (JsTyping.nodeCoreModules.has(moduleName) && startsWith(moduleName, "node:") !== shouldUseUriStyleNodeCoreModules(sourceFile, program)) { + return false; } + return packageJsonFilter + ? packageJsonFilter.allowsImportingAmbientModule(info.moduleSymbol, getModuleSpecifierResolutionHost(info.isFromPackageJson)) + : true; } + return isImportableFile( + info.isFromPackageJson ? packageJsonAutoImportProvider! : program, + sourceFile, + moduleFile, + preferences, + packageJsonFilter, + getModuleSpecifierResolutionHost(info.isFromPackageJson), + moduleSpecifierCache); + } + } - return undefined; + function pushAutoImportSymbol(symbol: Symbol, origin: SymbolOriginInfoResolvedExport | SymbolOriginInfoExport) { + const symbolId = getSymbolId(symbol); + if (symbolToSortTextIdMap[symbolId] === SortTextId.GlobalsOrKeywords) { + // If an auto-importable symbol is available as a global, don't add the auto import + return; } + symbolToOriginInfoMap[symbols.length] = origin; + symbolToSortTextIdMap[symbolId] = importCompletionNode ? SortTextId.LocationPriority : SortTextId.AutoImportSuggestions; + symbols.push(symbol); + } - function isConstructorParameterCompletion(node: Node): boolean { - return !!node.parent && isParameter(node.parent) && isConstructorDeclaration(node.parent.parent) - && (isParameterPropertyModifier(node.kind) || isDeclarationName(node)); + /** + * Finds the first node that "embraces" the position, so that one may + * accurately aggregate locals from the closest containing scope. + */ + function getScopeNode(initialToken: Node | undefined, position: number, sourceFile: SourceFile) { + let scope: Node | undefined = initialToken; + while (scope && !positionBelongsToNode(scope, position, sourceFile)) { + scope = scope.parent; } + return scope; + } - /** - * Returns the immediate owning class declaration of a context token, - * on the condition that one exists and that the context implies completion should be given. - */ - function tryGetConstructorLikeCompletionContainer(contextToken: Node): ConstructorDeclaration | undefined { - if (contextToken) { - const parent = contextToken.parent; - switch (contextToken.kind) { - case SyntaxKind.OpenParenToken: - case SyntaxKind.CommaToken: - return isConstructorDeclaration(contextToken.parent) ? contextToken.parent : undefined; + function isCompletionListBlocker(contextToken: Node): boolean { + const start = timestamp(); + const result = isInStringOrRegularExpressionOrTemplateLiteral(contextToken) || + isSolelyIdentifierDefinitionLocation(contextToken) || + isDotOfNumericLiteral(contextToken) || + isInJsxText(contextToken) || + isBigIntLiteral(contextToken); + log("getCompletionsAtPosition: isCompletionListBlocker: " + (timestamp() - start)); + return result; + } - default: - if (isConstructorParameterCompletion(contextToken)) { - return parent.parent as ConstructorDeclaration; - } - } - } - return undefined; + function isInJsxText(contextToken: Node): boolean { + if (contextToken.kind === SyntaxKind.JsxText) { + return true; } - function tryGetFunctionLikeBodyCompletionContainer(contextToken: Node): FunctionLikeDeclaration | undefined { - if (contextToken) { - let prev: Node; - const container = findAncestor(contextToken.parent, (node: Node) => { - if (isClassLike(node)) { - return "quit"; - } - if (isFunctionLikeDeclaration(node) && prev === node.body) { - return true; - } - prev = node; - return false; - }); - return container && container as FunctionLikeDeclaration; + if (contextToken.kind === SyntaxKind.GreaterThanToken && contextToken.parent) { + if (contextToken.parent.kind === SyntaxKind.JsxOpeningElement) { + // Two possibilities: + // 1.
/**/ + // - contextToken: GreaterThanToken (before cursor) + // - location: JSXElement + // - different parents (JSXOpeningElement, JSXElement) + // 2. /**/> + // - contextToken: GreaterThanToken (before cursor) + // - location: GreaterThanToken (after cursor) + // - same parent (JSXOpeningElement) + return location.parent.kind !== SyntaxKind.JsxOpeningElement; + } + + if (contextToken.parent.kind === SyntaxKind.JsxClosingElement || contextToken.parent.kind === SyntaxKind.JsxSelfClosingElement) { + return !!contextToken.parent.parent && contextToken.parent.parent.kind === SyntaxKind.JsxElement; } } + return false; + } - function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement | undefined { - if (contextToken) { - const parent = contextToken.parent; - switch (contextToken.kind) { - case SyntaxKind.GreaterThanToken: // End of a type argument list - case SyntaxKind.LessThanSlashToken: - case SyntaxKind.SlashToken: - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.JsxAttributes: - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxSpreadAttribute: - if (parent && (parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxOpeningElement)) { - if (contextToken.kind === SyntaxKind.GreaterThanToken) { - const precedingToken = findPrecedingToken(contextToken.pos, sourceFile, /*startNode*/ undefined); - if (!(parent as JsxOpeningLikeElement).typeArguments || (precedingToken && precedingToken.kind === SyntaxKind.SlashToken)) break; - } - return parent as JsxOpeningLikeElement; - } - else if (parent.kind === SyntaxKind.JsxAttribute) { - // Currently we parse JsxOpeningLikeElement as: - // JsxOpeningLikeElement - // attributes: JsxAttributes - // properties: NodeArray - return parent.parent.parent as JsxOpeningLikeElement; - } - break; + function isNewIdentifierDefinitionLocation(): boolean { + if (contextToken) { + const containingNodeKind = contextToken.parent.kind; + const tokenKind = keywordForNode(contextToken); + // Previous token may have been a keyword that was converted to an identifier. + switch (tokenKind) { + case SyntaxKind.CommaToken: + return containingNodeKind === SyntaxKind.CallExpression // func( a, | + || containingNodeKind === SyntaxKind.Constructor // constructor( a, | /* public, protected, private keywords are allowed here, so show completion */ + || containingNodeKind === SyntaxKind.NewExpression // new C(a, | + || containingNodeKind === SyntaxKind.ArrayLiteralExpression // [a, | + || containingNodeKind === SyntaxKind.BinaryExpression // const x = (a, | + || containingNodeKind === SyntaxKind.FunctionType // var x: (s: string, list| + || containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { x, | - // The context token is the closing } or " of an attribute, which means - // its parent is a JsxExpression, whose parent is a JsxAttribute, - // whose parent is a JsxOpeningLikeElement - case SyntaxKind.StringLiteral: - if (parent && ((parent.kind === SyntaxKind.JsxAttribute) || (parent.kind === SyntaxKind.JsxSpreadAttribute))) { - // Currently we parse JsxOpeningLikeElement as: - // JsxOpeningLikeElement - // attributes: JsxAttributes - // properties: NodeArray - return parent.parent.parent as JsxOpeningLikeElement; - } + case SyntaxKind.OpenParenToken: + return containingNodeKind === SyntaxKind.CallExpression // func( | + || containingNodeKind === SyntaxKind.Constructor // constructor( | + || containingNodeKind === SyntaxKind.NewExpression // new C(a| + || containingNodeKind === SyntaxKind.ParenthesizedExpression // const x = (a| + || containingNodeKind === SyntaxKind.ParenthesizedType; // function F(pred: (a| /* this can become an arrow function, where 'a' is the argument */ - break; + case SyntaxKind.OpenBracketToken: + return containingNodeKind === SyntaxKind.ArrayLiteralExpression // [ | + || containingNodeKind === SyntaxKind.IndexSignature // [ | : string ] + || containingNodeKind === SyntaxKind.ComputedPropertyName; // [ | /* this can become an index signature */ - case SyntaxKind.CloseBraceToken: - if (parent && - parent.kind === SyntaxKind.JsxExpression && - parent.parent && parent.parent.kind === SyntaxKind.JsxAttribute) { - // Currently we parse JsxOpeningLikeElement as: - // JsxOpeningLikeElement - // attributes: JsxAttributes - // properties: NodeArray - // each JsxAttribute can have initializer as JsxExpression - return parent.parent.parent.parent as JsxOpeningLikeElement; - } + case SyntaxKind.ModuleKeyword: // module | + case SyntaxKind.NamespaceKeyword: // namespace | + case SyntaxKind.ImportKeyword: // import | + return true; - if (parent && parent.kind === SyntaxKind.JsxSpreadAttribute) { - // Currently we parse JsxOpeningLikeElement as: - // JsxOpeningLikeElement - // attributes: JsxAttributes - // properties: NodeArray - return parent.parent.parent as JsxOpeningLikeElement; - } + case SyntaxKind.DotToken: + return containingNodeKind === SyntaxKind.ModuleDeclaration; // module A.| - break; - } - } - return undefined; - } + case SyntaxKind.OpenBraceToken: + return containingNodeKind === SyntaxKind.ClassDeclaration // class A { | + || containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { | - /** - * @returns true if we are certain that the currently edited location must define a new location; false otherwise. - */ - function isSolelyIdentifierDefinitionLocation(contextToken: Node): boolean { - const parent = contextToken.parent; - const containingNodeKind = parent.kind; - switch (contextToken.kind) { - case SyntaxKind.CommaToken: - return containingNodeKind === SyntaxKind.VariableDeclaration || - isVariableDeclarationListButNotTypeArgument(contextToken) || - containingNodeKind === SyntaxKind.VariableStatement || - containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, | - isFunctionLikeButNotConstructor(containingNodeKind) || - containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A= contextToken.pos); + case SyntaxKind.EqualsToken: + return containingNodeKind === SyntaxKind.VariableDeclaration // const x = a| + || containingNodeKind === SyntaxKind.BinaryExpression; // x = a| - case SyntaxKind.DotToken: - return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [.| + case SyntaxKind.TemplateHead: + return containingNodeKind === SyntaxKind.TemplateExpression; // `aa ${| - case SyntaxKind.ColonToken: - return containingNodeKind === SyntaxKind.BindingElement; // var {x :html| + case SyntaxKind.TemplateMiddle: + return containingNodeKind === SyntaxKind.TemplateSpan; // `aa ${10} dd ${| - case SyntaxKind.OpenBracketToken: - return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [x| + case SyntaxKind.AsyncKeyword: + return containingNodeKind === SyntaxKind.MethodDeclaration // const obj = { async c|() + || containingNodeKind === SyntaxKind.ShorthandPropertyAssignment; // const obj = { async c| - case SyntaxKind.OpenParenToken: - return containingNodeKind === SyntaxKind.CatchClause || - isFunctionLikeButNotConstructor(containingNodeKind); + case SyntaxKind.AsteriskToken: + return containingNodeKind === SyntaxKind.MethodDeclaration; // const obj = { * c| + } - case SyntaxKind.OpenBraceToken: - return containingNodeKind === SyntaxKind.EnumDeclaration; // enum a { | + if (isClassMemberCompletionKeyword(tokenKind)) { + return true; + } + } - case SyntaxKind.LessThanToken: - return containingNodeKind === SyntaxKind.ClassDeclaration || // class A< | - containingNodeKind === SyntaxKind.ClassExpression || // var C = class D< | - containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A< | - containingNodeKind === SyntaxKind.TypeAliasDeclaration || // type List< | - isFunctionLikeKind(containingNodeKind); + return false; + } - case SyntaxKind.StaticKeyword: - return containingNodeKind === SyntaxKind.PropertyDeclaration && !isClassLike(parent.parent); + function isInStringOrRegularExpressionOrTemplateLiteral(contextToken: Node): boolean { + // To be "in" one of these literals, the position has to be: + // 1. entirely within the token text. + // 2. at the end position of an unterminated token. + // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). + return (isRegularExpressionLiteral(contextToken) || isStringTextContainingNode(contextToken)) && ( + rangeContainsPositionExclusive(createTextRangeFromSpan(createTextSpanFromNode(contextToken)), position) || + position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken))); + } - case SyntaxKind.DotDotDotToken: - return containingNodeKind === SyntaxKind.Parameter || - (!!parent.parent && parent.parent.kind === SyntaxKind.ArrayBindingPattern); // var [...z| + function tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols(): GlobalsSearch | undefined { + const typeLiteralNode = tryGetTypeLiteralNode(contextToken); + if (!typeLiteralNode) return GlobalsSearch.Continue; - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - return containingNodeKind === SyntaxKind.Parameter && !isConstructorDeclaration(parent.parent); + const intersectionTypeNode = isIntersectionTypeNode(typeLiteralNode.parent) ? typeLiteralNode.parent : undefined; + const containerTypeNode = intersectionTypeNode || typeLiteralNode; - case SyntaxKind.AsKeyword: - return containingNodeKind === SyntaxKind.ImportSpecifier || - containingNodeKind === SyntaxKind.ExportSpecifier || - containingNodeKind === SyntaxKind.NamespaceImport; + const containerExpectedType = getConstraintOfTypeArgumentProperty(containerTypeNode, typeChecker); + if (!containerExpectedType) return GlobalsSearch.Continue; - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - return !isFromObjectTypeDeclaration(contextToken); + const containerActualType = typeChecker.getTypeFromTypeNode(containerTypeNode); - case SyntaxKind.Identifier: - if (containingNodeKind === SyntaxKind.ImportSpecifier && - contextToken === (parent as ImportSpecifier).name && - (contextToken as Identifier).text === "type" - ) { - // import { type | } - return false; - } - break; + const members = getPropertiesForCompletion(containerExpectedType, typeChecker); + const existingMembers = getPropertiesForCompletion(containerActualType, typeChecker); - case SyntaxKind.ClassKeyword: - case SyntaxKind.EnumKeyword: - case SyntaxKind.InterfaceKeyword: - case SyntaxKind.FunctionKeyword: - case SyntaxKind.VarKeyword: - case SyntaxKind.ImportKeyword: - case SyntaxKind.LetKeyword: - case SyntaxKind.ConstKeyword: - case SyntaxKind.InferKeyword: - return true; + const existingMemberEscapedNames: Set<__String> = new Set(); + existingMembers.forEach(s => existingMemberEscapedNames.add(s.escapedName)); - case SyntaxKind.TypeKeyword: - // import { type foo| } - return containingNodeKind !== SyntaxKind.ImportSpecifier; + symbols = concatenate(symbols, filter(members, s => !existingMemberEscapedNames.has(s.escapedName))); - case SyntaxKind.AsteriskToken: - return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent); - } + completionKind = CompletionKind.ObjectPropertyDeclaration; + isNewIdentifierLocation = true; - // If the previous token is keyword corresponding to class member completion keyword - // there will be completion available here - if (isClassMemberCompletionKeyword(keywordForNode(contextToken)) && isFromObjectTypeDeclaration(contextToken)) { - return false; - } + return GlobalsSearch.Success; + } - if (isConstructorParameterCompletion(contextToken)) { - // constructor parameter completion is available only if - // - its modifier of the constructor parameter or - // - its name of the parameter and not being edited - // eg. constructor(a |<- this shouldnt show completion - if (!isIdentifier(contextToken) || - isParameterPropertyModifier(keywordForNode(contextToken)) || - isCurrentlyEditingNode(contextToken)) { - return false; + /** + * Aggregates relevant symbols for completion in object literals and object binding patterns. + * Relevant symbols are stored in the captured 'symbols' variable. + * + * @returns true if 'symbols' was successfully populated; false otherwise. + */ + function tryGetObjectLikeCompletionSymbols(): GlobalsSearch | undefined { + const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); + if (!objectLikeContainer) return GlobalsSearch.Continue; + + // We're looking up possible property names from contextual/inferred/declared type. + completionKind = CompletionKind.ObjectPropertyDeclaration; + + let typeMembers: Symbol[] | undefined; + let existingMembers: readonly Declaration[] | undefined; + + if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { + const instantiatedType = tryGetObjectLiteralContextualType(objectLikeContainer, typeChecker); + + // Check completions for Object property value shorthand + if (instantiatedType === undefined) { + if (objectLikeContainer.flags & NodeFlags.InWithStatement) { + return GlobalsSearch.Fail; } + isNonContextualObjectLiteral = true; + return GlobalsSearch.Continue; } + const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completions); + const hasStringIndexType = (completionsType || instantiatedType).getStringIndexType(); + const hasNumberIndextype = (completionsType || instantiatedType).getNumberIndexType(); + isNewIdentifierLocation = !!hasStringIndexType || !!hasNumberIndextype; + typeMembers = getPropertiesForObjectExpression(instantiatedType, completionsType, objectLikeContainer, typeChecker); + existingMembers = objectLikeContainer.properties; - // Previous token may have been a keyword that was converted to an identifier. - switch (keywordForNode(contextToken)) { - case SyntaxKind.AbstractKeyword: - case SyntaxKind.ClassKeyword: - case SyntaxKind.ConstKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.EnumKeyword: - case SyntaxKind.FunctionKeyword: - case SyntaxKind.InterfaceKeyword: - case SyntaxKind.LetKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.PublicKeyword: - case SyntaxKind.StaticKeyword: - case SyntaxKind.VarKeyword: - return true; - case SyntaxKind.AsyncKeyword: - return isPropertyDeclaration(contextToken.parent); + if (typeMembers.length === 0) { + // Edge case: If NumberIndexType exists + if (!hasNumberIndextype) { + isNonContextualObjectLiteral = true; + return GlobalsSearch.Continue; + } } + } + else { + Debug.assert(objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern); + // We are *only* completing on properties from the type being destructured. + isNewIdentifierLocation = false; - // If we are inside a class declaration, and `constructor` is totally not present, - // but we request a completion manually at a whitespace... - const ancestorClassLike = findAncestor(contextToken.parent, isClassLike); - if (ancestorClassLike && contextToken === previousToken && isPreviousPropertyDeclarationTerminated(contextToken, position)) { - return false; // Don't block completions. - } + const rootDeclaration = getRootDeclaration(objectLikeContainer.parent); + if (!isVariableLike(rootDeclaration)) return Debug.fail("Root declaration is not variable-like."); - const ancestorPropertyDeclaraion = getAncestor(contextToken.parent, SyntaxKind.PropertyDeclaration); - // If we are inside a class declaration and typing `constructor` after property declaration... - if (ancestorPropertyDeclaraion - && contextToken !== previousToken - && isClassLike(previousToken.parent.parent) - // And the cursor is at the token... - && position <= previousToken.end) { - // If we are sure that the previous property declaration is terminated according to newline or semicolon... - if (isPreviousPropertyDeclarationTerminated(contextToken, previousToken.end)) { - return false; // Don't block completions. + // We don't want to complete using the type acquired by the shape + // of the binding pattern; we are only interested in types acquired + // through type declaration or inference. + // Also proceed if rootDeclaration is a parameter and if its containing function expression/arrow function is contextually typed - + // type of parameter will flow in from the contextual type of the function + let canGetType = hasInitializer(rootDeclaration) || hasType(rootDeclaration) || rootDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement; + if (!canGetType && rootDeclaration.kind === SyntaxKind.Parameter) { + if (isExpression(rootDeclaration.parent)) { + canGetType = !!typeChecker.getContextualType(rootDeclaration.parent as Expression); } - else if (contextToken.kind !== SyntaxKind.EqualsToken - // Should not block: `class C { blah = c/**/ }` - // But should block: `class C { blah = somewhat c/**/ }` and `class C { blah: SomeType c/**/ }` - && (isInitializedProperty(ancestorPropertyDeclaraion as PropertyDeclaration) - || hasType(ancestorPropertyDeclaraion))) { - return true; + else if (rootDeclaration.parent.kind === SyntaxKind.MethodDeclaration || rootDeclaration.parent.kind === SyntaxKind.SetAccessor) { + canGetType = isExpression(rootDeclaration.parent.parent) && !!typeChecker.getContextualType(rootDeclaration.parent.parent as Expression); } } + if (canGetType) { + const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); + if (!typeForObject) return GlobalsSearch.Fail; + typeMembers = typeChecker.getPropertiesOfType(typeForObject).filter(propertySymbol => { + return typeChecker.isPropertyAccessible(objectLikeContainer, /*isSuper*/ false, /*writing*/ false, typeForObject, propertySymbol); + }); + existingMembers = objectLikeContainer.elements; + } + } + + if (typeMembers && typeMembers.length > 0) { + // Add filtered items to the completion list + symbols = concatenate(symbols, filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers))); + } + setSortTextToOptionalMember(); + + return GlobalsSearch.Success; + } + + /** + * Aggregates relevant symbols for completion in import clauses and export clauses + * whose declarations have a module specifier; for instance, symbols will be aggregated for + * + * import { | } from "moduleName"; + * export { a as foo, | } from "moduleName"; + * + * but not for + * + * export { | }; + * + * Relevant symbols are stored in the captured 'symbols' variable. + */ + function tryGetImportOrExportClauseCompletionSymbols(): GlobalsSearch { + if (!contextToken) return GlobalsSearch.Continue; - return isDeclarationName(contextToken) - && !isShorthandPropertyAssignment(contextToken.parent) - && !isJsxAttribute(contextToken.parent) - // Don't block completions if we're in `class C /**/`, because we're *past* the end of the identifier and might want to complete `extends`. - // If `contextToken !== previousToken`, this is `class C ex/**/`. - && !(isClassLike(contextToken.parent) && (contextToken !== previousToken || position > previousToken.end)); + // `import { |` or `import { a as 0, | }` or `import { type | }` + const namedImportsOrExports = + contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken ? tryCast(contextToken.parent, isNamedImportsOrExports) : + isTypeKeywordTokenOrIdentifier(contextToken) ? tryCast(contextToken.parent.parent, isNamedImportsOrExports) : undefined; + + if (!namedImportsOrExports) return GlobalsSearch.Continue; + + // We can at least offer `type` at `import { |` + if (!isTypeKeywordTokenOrIdentifier(contextToken)) { + keywordFilters = KeywordCompletionFilters.TypeKeyword; } - function isPreviousPropertyDeclarationTerminated(contextToken: Node, position: number) { - return contextToken.kind !== SyntaxKind.EqualsToken && - (contextToken.kind === SyntaxKind.SemicolonToken - || !positionsAreOnSameLine(contextToken.end, position, sourceFile)); + // try to show exported member for imported/re-exported module + const { moduleSpecifier } = namedImportsOrExports.kind === SyntaxKind.NamedImports ? namedImportsOrExports.parent.parent : namedImportsOrExports.parent; + if (!moduleSpecifier) { + isNewIdentifierLocation = true; + return namedImportsOrExports.kind === SyntaxKind.NamedImports ? GlobalsSearch.Fail : GlobalsSearch.Continue; + } + const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); // TODO: GH#18217 + if (!moduleSpecifierSymbol) { + isNewIdentifierLocation = true; + return GlobalsSearch.Fail; } - function isFunctionLikeButNotConstructor(kind: SyntaxKind) { - return isFunctionLikeKind(kind) && kind !== SyntaxKind.Constructor; + completionKind = CompletionKind.MemberLike; + isNewIdentifierLocation = false; + const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol); + const existing = new Set((namedImportsOrExports.elements as NodeArray).filter(n => !isCurrentlyEditingNode(n)).map(n => (n.propertyName || n.name).escapedText)); + const uniques = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName)); + symbols = concatenate(symbols, uniques); + if (!uniques.length) { + // If there's nothing else to import, don't offer `type` either + keywordFilters = KeywordCompletionFilters.None; } + return GlobalsSearch.Success; + } - function isDotOfNumericLiteral(contextToken: Node): boolean { - if (contextToken.kind === SyntaxKind.NumericLiteral) { - const text = contextToken.getFullText(); - return text.charAt(text.length - 1) === "."; - } + /** + * Adds local declarations for completions in named exports: + * + * export { | }; + * + * Does not check for the absence of a module specifier (`export {} from "./other"`) + * because `tryGetImportOrExportClauseCompletionSymbols` runs first and handles that, + * preventing this function from running. + */ + function tryGetLocalNamedExportCompletionSymbols(): GlobalsSearch { + const namedExports = contextToken && (contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken) + ? tryCast(contextToken.parent, isNamedExports) + : undefined; - return false; + if (!namedExports) { + return GlobalsSearch.Continue; } - function isVariableDeclarationListButNotTypeArgument(node: Node): boolean { - return node.parent.kind === SyntaxKind.VariableDeclarationList - && !isPossiblyTypeArgumentPosition(node, sourceFile, typeChecker); - } - - /** - * Filters out completion suggestions for named imports or exports. - * - * @returns Symbols to be suggested in an object binding pattern or object literal expression, barring those whose declarations - * do not occur at the current position and have not otherwise been typed. - */ - function filterObjectMembersList(contextualMemberSymbols: Symbol[], existingMembers: readonly Declaration[]): Symbol[] { - if (existingMembers.length === 0) { - return contextualMemberSymbols; - } - - const membersDeclaredBySpreadAssignment = new Set(); - const existingMemberNames = new Set<__String>(); - for (const m of existingMembers) { - // Ignore omitted expressions for missing members - if (m.kind !== SyntaxKind.PropertyAssignment && - m.kind !== SyntaxKind.ShorthandPropertyAssignment && - m.kind !== SyntaxKind.BindingElement && - m.kind !== SyntaxKind.MethodDeclaration && - m.kind !== SyntaxKind.GetAccessor && - m.kind !== SyntaxKind.SetAccessor && - m.kind !== SyntaxKind.SpreadAssignment) { - continue; - } + const localsContainer = findAncestor(namedExports, or(isSourceFile, isModuleDeclaration))!; + completionKind = CompletionKind.None; + isNewIdentifierLocation = false; + localsContainer.locals?.forEach((symbol, name) => { + symbols.push(symbol); + if (localsContainer.symbol?.exports?.has(name)) { + symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.OptionalMember; + } + }); + return GlobalsSearch.Success; + } - // If this is the current item we are editing right now, do not filter it out - if (isCurrentlyEditingNode(m)) { - continue; - } + /** + * Aggregates relevant symbols for completion in class declaration + * Relevant symbols are stored in the captured 'symbols' variable. + */ + function tryGetClassLikeCompletionSymbols(): GlobalsSearch { + const decl = tryGetObjectTypeDeclarationCompletionContainer(sourceFile, contextToken, location, position); + if (!decl) return GlobalsSearch.Continue; + + // We're looking up possible property names from parent type. + completionKind = CompletionKind.MemberLike; + // Declaring new property/method/accessor + isNewIdentifierLocation = true; + keywordFilters = contextToken.kind === SyntaxKind.AsteriskToken ? KeywordCompletionFilters.None : + isClassLike(decl) ? KeywordCompletionFilters.ClassElementKeywords : KeywordCompletionFilters.InterfaceElementKeywords; + + // If you're in an interface you don't want to repeat things from super-interface. So just stop here. + if (!isClassLike(decl)) return GlobalsSearch.Success; + + const classElement = contextToken.kind === SyntaxKind.SemicolonToken ? contextToken.parent.parent : contextToken.parent; + let classElementModifierFlags = isClassElement(classElement) ? getEffectiveModifierFlags(classElement) : ModifierFlags.None; + // If this is context token is not something we are editing now, consider if this would lead to be modifier + if (contextToken.kind === SyntaxKind.Identifier && !isCurrentlyEditingNode(contextToken)) { + switch (contextToken.getText()) { + case "private": + classElementModifierFlags = classElementModifierFlags | ModifierFlags.Private; + break; + case "static": + classElementModifierFlags = classElementModifierFlags | ModifierFlags.Static; + break; + case "override": + classElementModifierFlags = classElementModifierFlags | ModifierFlags.Override; + break; + } + } + if (isClassStaticBlockDeclaration(classElement)) { + classElementModifierFlags |= ModifierFlags.Static; + } - let existingName: __String | undefined; + // No member list for private methods + if (!(classElementModifierFlags & ModifierFlags.Private)) { + // List of property symbols of base type that are not private and already implemented + const baseTypeNodes = isClassLike(decl) && classElementModifierFlags & ModifierFlags.Override ? singleElementArray(getEffectiveBaseTypeNode(decl)) : getAllSuperTypeNodes(decl); + const baseSymbols = flatMap(baseTypeNodes, baseTypeNode => { + const type = typeChecker.getTypeAtLocation(baseTypeNode); + return classElementModifierFlags & ModifierFlags.Static ? + type?.symbol && typeChecker.getPropertiesOfType(typeChecker.getTypeOfSymbolAtLocation(type.symbol, decl)) : + type && typeChecker.getPropertiesOfType(type); + }); + symbols = concatenate(symbols, filterClassMembersList(baseSymbols, decl.members, classElementModifierFlags)); + } - if (isSpreadAssignment(m)) { - setMembersDeclaredBySpreadAssignment(m, membersDeclaredBySpreadAssignment); - } - else if (isBindingElement(m) && m.propertyName) { - // include only identifiers in completion list - if (m.propertyName.kind === SyntaxKind.Identifier) { - existingName = m.propertyName.escapedText; - } - } - else { - // TODO: Account for computed property name - // NOTE: if one only performs this step when m.name is an identifier, - // things like '__proto__' are not filtered out. - const name = getNameOfDeclaration(m); - existingName = name && isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; - } + return GlobalsSearch.Success; + } - if (existingName !== undefined) { - existingMemberNames.add(existingName); - } + /** + * Returns the immediate owning object literal or binding pattern of a context token, + * on the condition that one exists and that the context implies completion should be given. + */ + function tryGetObjectLikeCompletionContainer(contextToken: Node): ObjectLiteralExpression | ObjectBindingPattern | undefined { + if (contextToken) { + const { parent } = contextToken; + switch (contextToken.kind) { + case SyntaxKind.OpenBraceToken: // const x = { | + case SyntaxKind.CommaToken: // const x = { a: 0, | + if (isObjectLiteralExpression(parent) || isObjectBindingPattern(parent)) { + return parent; + } + break; + case SyntaxKind.AsteriskToken: + return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined; + case SyntaxKind.Identifier: + return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent) + ? contextToken.parent.parent : undefined; } + } - const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.has(m.escapedName)); - setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); + return undefined; + } - return filteredSymbols; - } + function isConstructorParameterCompletion(node: Node): boolean { + return !!node.parent && isParameter(node.parent) && isConstructorDeclaration(node.parent.parent) + && (isParameterPropertyModifier(node.kind) || isDeclarationName(node)); + } - function setMembersDeclaredBySpreadAssignment(declaration: SpreadAssignment | JsxSpreadAttribute, membersDeclaredBySpreadAssignment: Set) { - const expression = declaration.expression; - const symbol = typeChecker.getSymbolAtLocation(expression); - const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); - const properties = type && (type as ObjectType).properties; - if (properties) { - properties.forEach(property => { - membersDeclaredBySpreadAssignment.add(property.name); - }); + /** + * Returns the immediate owning class declaration of a context token, + * on the condition that one exists and that the context implies completion should be given. + */ + function tryGetConstructorLikeCompletionContainer(contextToken: Node): ConstructorDeclaration | undefined { + if (contextToken) { + const parent = contextToken.parent; + switch (contextToken.kind) { + case SyntaxKind.OpenParenToken: + case SyntaxKind.CommaToken: + return isConstructorDeclaration(contextToken.parent) ? contextToken.parent : undefined; + + default: + if (isConstructorParameterCompletion(contextToken)) { + return parent.parent as ConstructorDeclaration; + } } } + return undefined; + } - // Set SortText to OptionalMember if it is an optional member - function setSortTextToOptionalMember() { - symbols.forEach(m => { - if (m.flags & SymbolFlags.Optional) { - const symbolId = getSymbolId(m); - symbolToSortTextIdMap[symbolId] = symbolToSortTextIdMap[symbolId] ?? SortTextId.OptionalMember; + function tryGetFunctionLikeBodyCompletionContainer(contextToken: Node): FunctionLikeDeclaration | undefined { + if (contextToken) { + let prev: Node; + const container = findAncestor(contextToken.parent, (node: Node) => { + if (isClassLike(node)) { + return "quit"; + } + if (isFunctionLikeDeclaration(node) && prev === node.body) { + return true; } + prev = node; + return false; }); + return container && container as FunctionLikeDeclaration; } + } - // Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment - function setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment: Set, contextualMemberSymbols: Symbol[]): void { - if (membersDeclaredBySpreadAssignment.size === 0) { - return; - } - for (const contextualMemberSymbol of contextualMemberSymbols) { - if (membersDeclaredBySpreadAssignment.has(contextualMemberSymbol.name)) { - symbolToSortTextIdMap[getSymbolId(contextualMemberSymbol)] = SortTextId.MemberDeclaredBySpreadAssignment; - } - } - } + function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement | undefined { + if (contextToken) { + const parent = contextToken.parent; + switch (contextToken.kind) { + case SyntaxKind.GreaterThanToken: // End of a type argument list + case SyntaxKind.LessThanSlashToken: + case SyntaxKind.SlashToken: + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.JsxAttributes: + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxSpreadAttribute: + if (parent && (parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxOpeningElement)) { + if (contextToken.kind === SyntaxKind.GreaterThanToken) { + const precedingToken = findPrecedingToken(contextToken.pos, sourceFile, /*startNode*/ undefined); + if (!(parent as JsxOpeningLikeElement).typeArguments || (precedingToken && precedingToken.kind === SyntaxKind.SlashToken)) break; + } + return parent as JsxOpeningLikeElement; + } + else if (parent.kind === SyntaxKind.JsxAttribute) { + // Currently we parse JsxOpeningLikeElement as: + // JsxOpeningLikeElement + // attributes: JsxAttributes + // properties: NodeArray + return parent.parent.parent as JsxOpeningLikeElement; + } + break; - /** - * Filters out completion suggestions for class elements. - * - * @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags - */ - function filterClassMembersList(baseSymbols: readonly Symbol[], existingMembers: readonly ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] { - const existingMemberNames = new Set<__String>(); - for (const m of existingMembers) { - // Ignore omitted expressions for missing members - if (m.kind !== SyntaxKind.PropertyDeclaration && - m.kind !== SyntaxKind.MethodDeclaration && - m.kind !== SyntaxKind.GetAccessor && - m.kind !== SyntaxKind.SetAccessor) { - continue; - } + // The context token is the closing } or " of an attribute, which means + // its parent is a JsxExpression, whose parent is a JsxAttribute, + // whose parent is a JsxOpeningLikeElement + case SyntaxKind.StringLiteral: + if (parent && ((parent.kind === SyntaxKind.JsxAttribute) || (parent.kind === SyntaxKind.JsxSpreadAttribute))) { + // Currently we parse JsxOpeningLikeElement as: + // JsxOpeningLikeElement + // attributes: JsxAttributes + // properties: NodeArray + return parent.parent.parent as JsxOpeningLikeElement; + } - // If this is the current item we are editing right now, do not filter it out - if (isCurrentlyEditingNode(m)) { - continue; - } + break; - // Dont filter member even if the name matches if it is declared private in the list - if (hasEffectiveModifier(m, ModifierFlags.Private)) { - continue; - } + case SyntaxKind.CloseBraceToken: + if (parent && + parent.kind === SyntaxKind.JsxExpression && + parent.parent && parent.parent.kind === SyntaxKind.JsxAttribute) { + // Currently we parse JsxOpeningLikeElement as: + // JsxOpeningLikeElement + // attributes: JsxAttributes + // properties: NodeArray + // each JsxAttribute can have initializer as JsxExpression + return parent.parent.parent.parent as JsxOpeningLikeElement; + } - // do not filter it out if the static presence doesnt match - if (isStatic(m) !== !!(currentClassElementModifierFlags & ModifierFlags.Static)) { - continue; - } + if (parent && parent.kind === SyntaxKind.JsxSpreadAttribute) { + // Currently we parse JsxOpeningLikeElement as: + // JsxOpeningLikeElement + // attributes: JsxAttributes + // properties: NodeArray + return parent.parent.parent as JsxOpeningLikeElement; + } - const existingName = getPropertyNameForPropertyNameNode(m.name!); - if (existingName) { - existingMemberNames.add(existingName); - } + break; } + } + return undefined; + } - return baseSymbols.filter(propertySymbol => - !existingMemberNames.has(propertySymbol.escapedName) && - !!propertySymbol.declarations && - !(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private) && - !(propertySymbol.valueDeclaration && isPrivateIdentifierClassElementDeclaration(propertySymbol.valueDeclaration))); - } - - /** - * Filters out completion suggestions from 'symbols' according to existing JSX attributes. - * - * @returns Symbols to be suggested in a JSX element, barring those whose attributes - * do not occur at the current position and have not otherwise been typed. - */ - function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] { - const seenNames = new Set<__String>(); - const membersDeclaredBySpreadAssignment = new Set(); - for (const attr of attributes) { - // If this is the current item we are editing right now, do not filter it out - if (isCurrentlyEditingNode(attr)) { - continue; - } + /** + * @returns true if we are certain that the currently edited location must define a new location; false otherwise. + */ + function isSolelyIdentifierDefinitionLocation(contextToken: Node): boolean { + const parent = contextToken.parent; + const containingNodeKind = parent.kind; + switch (contextToken.kind) { + case SyntaxKind.CommaToken: + return containingNodeKind === SyntaxKind.VariableDeclaration || + isVariableDeclarationListButNotTypeArgument(contextToken) || + containingNodeKind === SyntaxKind.VariableStatement || + containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, | + isFunctionLikeButNotConstructor(containingNodeKind) || + containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A= contextToken.pos); + + case SyntaxKind.DotToken: + return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [.| + + case SyntaxKind.ColonToken: + return containingNodeKind === SyntaxKind.BindingElement; // var {x :html| + + case SyntaxKind.OpenBracketToken: + return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [x| + + case SyntaxKind.OpenParenToken: + return containingNodeKind === SyntaxKind.CatchClause || + isFunctionLikeButNotConstructor(containingNodeKind); - if (attr.kind === SyntaxKind.JsxAttribute) { - seenNames.add(attr.name.escapedText); - } - else if (isJsxSpreadAttribute(attr)) { - setMembersDeclaredBySpreadAssignment(attr, membersDeclaredBySpreadAssignment); - } - } - const filteredSymbols = symbols.filter(a => !seenNames.has(a.escapedName)); + case SyntaxKind.OpenBraceToken: + return containingNodeKind === SyntaxKind.EnumDeclaration; // enum a { | - setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); + case SyntaxKind.LessThanToken: + return containingNodeKind === SyntaxKind.ClassDeclaration || // class A< | + containingNodeKind === SyntaxKind.ClassExpression || // var C = class D< | + containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A< | + containingNodeKind === SyntaxKind.TypeAliasDeclaration || // type List< | + isFunctionLikeKind(containingNodeKind); - return filteredSymbols; - } + case SyntaxKind.StaticKeyword: + return containingNodeKind === SyntaxKind.PropertyDeclaration && !isClassLike(parent.parent); - function isCurrentlyEditingNode(node: Node): boolean { - return node.getStart(sourceFile) <= position && position <= node.getEnd(); - } - } + case SyntaxKind.DotDotDotToken: + return containingNodeKind === SyntaxKind.Parameter || + (!!parent.parent && parent.parent.kind === SyntaxKind.ArrayBindingPattern); // var [...z| - function getRelevantTokens(position: number, sourceFile: SourceFile): { contextToken: Node, previousToken: Node } | { contextToken: undefined, previousToken: undefined } { - const previousToken = findPrecedingToken(position, sourceFile); - if (previousToken && position <= previousToken.end && (isMemberName(previousToken) || isKeyword(previousToken.kind))) { - const contextToken = findPrecedingToken(previousToken.getFullStart(), sourceFile, /*startNode*/ undefined)!; // TODO: GH#18217 - return { contextToken, previousToken }; - } - return { contextToken: previousToken as Node, previousToken: previousToken as Node }; - } + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + return containingNodeKind === SyntaxKind.Parameter && !isConstructorDeclaration(parent.parent); + + case SyntaxKind.AsKeyword: + return containingNodeKind === SyntaxKind.ImportSpecifier || + containingNodeKind === SyntaxKind.ExportSpecifier || + containingNodeKind === SyntaxKind.NamespaceImport; - function getAutoImportSymbolFromCompletionEntryData(name: string, data: CompletionEntryData, program: Program, host: LanguageServiceHost): { symbol: Symbol, origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport } | undefined { - const containingProgram = data.isPackageJsonImport ? host.getPackageJsonAutoImportProvider!()! : program; - const checker = containingProgram.getTypeChecker(); - const moduleSymbol = - data.ambientModuleName ? checker.tryFindAmbientModule(data.ambientModuleName) : - data.fileName ? checker.getMergedSymbol(Debug.checkDefined(containingProgram.getSourceFile(data.fileName)).symbol) : - undefined; + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + return !isFromObjectTypeDeclaration(contextToken); + + case SyntaxKind.Identifier: + if (containingNodeKind === SyntaxKind.ImportSpecifier && + contextToken === (parent as ImportSpecifier).name && + (contextToken as Identifier).text === "type" + ) { + // import { type | } + return false; + } + break; - if (!moduleSymbol) return undefined; - let symbol = data.exportName === InternalSymbolName.ExportEquals - ? checker.resolveExternalModuleSymbol(moduleSymbol) - : checker.tryGetMemberInModuleExportsAndProperties(data.exportName, moduleSymbol); - if (!symbol) return undefined; - const isDefaultExport = data.exportName === InternalSymbolName.Default; - symbol = isDefaultExport && getLocalSymbolForExportDefault(symbol) || symbol; - return { symbol, origin: completionEntryDataToSymbolOriginInfo(data, name, moduleSymbol) }; - } + case SyntaxKind.ClassKeyword: + case SyntaxKind.EnumKeyword: + case SyntaxKind.InterfaceKeyword: + case SyntaxKind.FunctionKeyword: + case SyntaxKind.VarKeyword: + case SyntaxKind.ImportKeyword: + case SyntaxKind.LetKeyword: + case SyntaxKind.ConstKeyword: + case SyntaxKind.InferKeyword: + return true; - interface CompletionEntryDisplayNameForSymbol { - readonly name: string; - readonly needsConvertPropertyAccess: boolean; - } - function getCompletionEntryDisplayNameForSymbol( - symbol: Symbol, - target: ScriptTarget, - origin: SymbolOriginInfo | undefined, - kind: CompletionKind, - jsxIdentifierExpected: boolean, - ): CompletionEntryDisplayNameForSymbol | undefined { - const name = originIncludesSymbolName(origin) ? origin.symbolName : symbol.name; - if (name === undefined - // If the symbol is external module, don't show it in the completion list - // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there) - || symbol.flags & SymbolFlags.Module && isSingleOrDoubleQuote(name.charCodeAt(0)) - // If the symbol is the internal name of an ES symbol, it is not a valid entry. Internal names for ES symbols start with "__@" - || isKnownSymbol(symbol)) { - return undefined; - } + case SyntaxKind.TypeKeyword: + // import { type foo| } + return containingNodeKind !== SyntaxKind.ImportSpecifier; - const validNameResult: CompletionEntryDisplayNameForSymbol = { name, needsConvertPropertyAccess: false }; - if (isIdentifierText(name, target, jsxIdentifierExpected ? LanguageVariant.JSX : LanguageVariant.Standard) || symbol.valueDeclaration && isPrivateIdentifierClassElementDeclaration(symbol.valueDeclaration)) { - return validNameResult; - } - switch (kind) { - case CompletionKind.MemberLike: - return undefined; - case CompletionKind.ObjectPropertyDeclaration: - // TODO: GH#18169 - return { name: JSON.stringify(name), needsConvertPropertyAccess: false }; - case CompletionKind.PropertyAccess: - case CompletionKind.Global: // For a 'this.' completion it will be in a global context, but may have a non-identifier name. - // Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547 - return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true }; - case CompletionKind.None: - case CompletionKind.String: - return validNameResult; - default: - Debug.assertNever(kind); + case SyntaxKind.AsteriskToken: + return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent); } - } - // A cache of completion entries for keywords, these do not change between sessions - const _keywordCompletions: CompletionEntry[][] = []; - const allKeywordsCompletions: () => readonly CompletionEntry[] = memoize(() => { - const res: CompletionEntry[] = []; - for (let i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) { - res.push({ - name: tokenToString(i)!, - kind: ScriptElementKind.keyword, - kindModifiers: ScriptElementKindModifier.none, - sortText: SortText.GlobalsOrKeywords - }); + // If the previous token is keyword corresponding to class member completion keyword + // there will be completion available here + if (isClassMemberCompletionKeyword(keywordForNode(contextToken)) && isFromObjectTypeDeclaration(contextToken)) { + return false; } - return res; - }); - function getKeywordCompletions(keywordFilter: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean): readonly CompletionEntry[] { - if (!filterOutTsOnlyKeywords) return getTypescriptKeywordCompletions(keywordFilter); - - const index = keywordFilter + KeywordCompletionFilters.Last + 1; - return _keywordCompletions[index] || - (_keywordCompletions[index] = getTypescriptKeywordCompletions(keywordFilter) - .filter(entry => !isTypeScriptOnlyKeyword(stringToToken(entry.name)!)) - ); - } - - function getTypescriptKeywordCompletions(keywordFilter: KeywordCompletionFilters): readonly CompletionEntry[] { - return _keywordCompletions[keywordFilter] || (_keywordCompletions[keywordFilter] = allKeywordsCompletions().filter(entry => { - const kind = stringToToken(entry.name)!; - switch (keywordFilter) { - case KeywordCompletionFilters.None: - return false; - case KeywordCompletionFilters.All: - return isFunctionLikeBodyKeyword(kind) - || kind === SyntaxKind.DeclareKeyword - || kind === SyntaxKind.ModuleKeyword - || kind === SyntaxKind.TypeKeyword - || kind === SyntaxKind.NamespaceKeyword - || kind === SyntaxKind.AbstractKeyword - || isTypeKeyword(kind) && kind !== SyntaxKind.UndefinedKeyword; - case KeywordCompletionFilters.FunctionLikeBodyKeywords: - return isFunctionLikeBodyKeyword(kind); - case KeywordCompletionFilters.ClassElementKeywords: - return isClassMemberCompletionKeyword(kind); - case KeywordCompletionFilters.InterfaceElementKeywords: - return isInterfaceOrTypeLiteralCompletionKeyword(kind); - case KeywordCompletionFilters.ConstructorParameterKeywords: - return isParameterPropertyModifier(kind); - case KeywordCompletionFilters.TypeAssertionKeywords: - return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword; - case KeywordCompletionFilters.TypeKeywords: - return isTypeKeyword(kind); - case KeywordCompletionFilters.TypeKeyword: - return kind === SyntaxKind.TypeKeyword; - default: - return Debug.assertNever(keywordFilter); + if (isConstructorParameterCompletion(contextToken)) { + // constructor parameter completion is available only if + // - its modifier of the constructor parameter or + // - its name of the parameter and not being edited + // eg. constructor(a |<- this shouldnt show completion + if (!isIdentifier(contextToken) || + isParameterPropertyModifier(keywordForNode(contextToken)) || + isCurrentlyEditingNode(contextToken)) { + return false; } - })); - } + } - function isTypeScriptOnlyKeyword(kind: SyntaxKind) { - switch (kind) { + // Previous token may have been a keyword that was converted to an identifier. + switch (keywordForNode(contextToken)) { case SyntaxKind.AbstractKeyword: - case SyntaxKind.AnyKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.BooleanKeyword: + case SyntaxKind.ClassKeyword: + case SyntaxKind.ConstKeyword: case SyntaxKind.DeclareKeyword: case SyntaxKind.EnumKeyword: - case SyntaxKind.GlobalKeyword: - case SyntaxKind.ImplementsKeyword: - case SyntaxKind.InferKeyword: + case SyntaxKind.FunctionKeyword: case SyntaxKind.InterfaceKeyword: - case SyntaxKind.IsKeyword: - case SyntaxKind.KeyOfKeyword: - case SyntaxKind.ModuleKeyword: - case SyntaxKind.NamespaceKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.OverrideKeyword: + case SyntaxKind.LetKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.PublicKeyword: - case SyntaxKind.ReadonlyKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.TypeKeyword: - case SyntaxKind.UniqueKeyword: - case SyntaxKind.UnknownKeyword: + case SyntaxKind.StaticKeyword: + case SyntaxKind.VarKeyword: return true; - default: - return false; + case SyntaxKind.AsyncKeyword: + return isPropertyDeclaration(contextToken.parent); } - } - function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean { - return kind === SyntaxKind.ReadonlyKeyword; - } + // If we are inside a class declaration, and `constructor` is totally not present, + // but we request a completion manually at a whitespace... + const ancestorClassLike = findAncestor(contextToken.parent, isClassLike); + if (ancestorClassLike && contextToken === previousToken && isPreviousPropertyDeclarationTerminated(contextToken, position)) { + return false; // Don't block completions. + } - function isClassMemberCompletionKeyword(kind: SyntaxKind) { - switch (kind) { - case SyntaxKind.AbstractKeyword: - case SyntaxKind.ConstructorKeyword: - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - case SyntaxKind.AsyncKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.OverrideKeyword: + const ancestorPropertyDeclaraion = getAncestor(contextToken.parent, SyntaxKind.PropertyDeclaration); + // If we are inside a class declaration and typing `constructor` after property declaration... + if (ancestorPropertyDeclaraion + && contextToken !== previousToken + && isClassLike(previousToken.parent.parent) + // And the cursor is at the token... + && position <= previousToken.end) { + // If we are sure that the previous property declaration is terminated according to newline or semicolon... + if (isPreviousPropertyDeclarationTerminated(contextToken, previousToken.end)) { + return false; // Don't block completions. + } + else if (contextToken.kind !== SyntaxKind.EqualsToken + // Should not block: `class C { blah = c/**/ }` + // But should block: `class C { blah = somewhat c/**/ }` and `class C { blah: SomeType c/**/ }` + && (isInitializedProperty(ancestorPropertyDeclaraion as PropertyDeclaration) + || hasType(ancestorPropertyDeclaraion))) { return true; - default: - return isClassMemberModifier(kind); + } } - } - function isFunctionLikeBodyKeyword(kind: SyntaxKind) { - return kind === SyntaxKind.AsyncKeyword - || kind === SyntaxKind.AwaitKeyword - || kind === SyntaxKind.AsKeyword - || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind); + return isDeclarationName(contextToken) + && !isShorthandPropertyAssignment(contextToken.parent) + && !isJsxAttribute(contextToken.parent) + // Don't block completions if we're in `class C /**/`, because we're *past* the end of the identifier and might want to complete `extends`. + // If `contextToken !== previousToken`, this is `class C ex/**/`. + && !(isClassLike(contextToken.parent) && (contextToken !== previousToken || position > previousToken.end)); } - function keywordForNode(node: Node): SyntaxKind { - return isIdentifier(node) ? node.originalKeywordKind || SyntaxKind.Unknown : node.kind; + function isPreviousPropertyDeclarationTerminated(contextToken: Node, position: number) { + return contextToken.kind !== SyntaxKind.EqualsToken && + (contextToken.kind === SyntaxKind.SemicolonToken + || !positionsAreOnSameLine(contextToken.end, position, sourceFile)); } - /** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ - function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefined { - const jsdoc = findAncestor(node, isJSDoc); - return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined); + function isFunctionLikeButNotConstructor(kind: SyntaxKind) { + return isFunctionLikeKind(kind) && kind !== SyntaxKind.Constructor; } - export function getPropertiesForObjectExpression(contextualType: Type, completionsType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { - const hasCompletionsType = completionsType && completionsType !== contextualType; - const type = hasCompletionsType && !(completionsType!.flags & TypeFlags.AnyOrUnknown) - ? checker.getUnionType([contextualType, completionsType!]) - : contextualType; - - const properties = getApparentProperties(type, obj, checker); - return type.isClass() && containsNonPublicProperties(properties) ? [] : - hasCompletionsType ? filter(properties, hasDeclarationOtherThanSelf) : properties; - - // Filter out members whose only declaration is the object literal itself to avoid - // self-fulfilling completions like: - // - // function f(x: T) {} - // f({ abc/**/: "" }) // `abc` is a member of `T` but only because it declares itself - function hasDeclarationOtherThanSelf(member: Symbol) { - if (!length(member.declarations)) return true; - return some(member.declarations, decl => decl.parent !== obj); + function isDotOfNumericLiteral(contextToken: Node): boolean { + if (contextToken.kind === SyntaxKind.NumericLiteral) { + const text = contextToken.getFullText(); + return text.charAt(text.length - 1) === "."; } - } - function getApparentProperties(type: Type, node: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker) { - if (!type.isUnion()) return type.getApparentProperties(); - return checker.getAllPossiblePropertiesOfTypes(filter(type.types, memberType => - !(memberType.flags & TypeFlags.Primitive - || checker.isArrayLikeType(memberType) - || checker.isTypeInvalidDueToUnionDiscriminant(memberType, node) - || typeHasCallOrConstructSignatures(memberType, checker) - || memberType.isClass() && containsNonPublicProperties(memberType.getApparentProperties())))); + return false; } - function containsNonPublicProperties(props: Symbol[]) { - return some(props, p => !!(getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.NonPublicAccessibilityModifier)); + function isVariableDeclarationListButNotTypeArgument(node: Node): boolean { + return node.parent.kind === SyntaxKind.VariableDeclarationList + && !isPossiblyTypeArgumentPosition(node, sourceFile, typeChecker); } /** - * Gets all properties on a type, but if that type is a union of several types, - * excludes array-like types or callable/constructable types. + * Filters out completion suggestions for named imports or exports. + * + * @returns Symbols to be suggested in an object binding pattern or object literal expression, barring those whose declarations + * do not occur at the current position and have not otherwise been typed. */ - function getPropertiesForCompletion(type: Type, checker: TypeChecker): Symbol[] { - return type.isUnion() - ? Debug.checkEachDefined(checker.getAllPossiblePropertiesOfTypes(type.types), "getAllPossiblePropertiesOfTypes() should all be defined") - : Debug.checkEachDefined(type.getApparentProperties(), "getApparentProperties() should all be defined"); - } + function filterObjectMembersList(contextualMemberSymbols: Symbol[], existingMembers: readonly Declaration[]): Symbol[] { + if (existingMembers.length === 0) { + return contextualMemberSymbols; + } + + const membersDeclaredBySpreadAssignment = new Set(); + const existingMemberNames = new Set<__String>(); + for (const m of existingMembers) { + // Ignore omitted expressions for missing members + if (m.kind !== SyntaxKind.PropertyAssignment && + m.kind !== SyntaxKind.ShorthandPropertyAssignment && + m.kind !== SyntaxKind.BindingElement && + m.kind !== SyntaxKind.MethodDeclaration && + m.kind !== SyntaxKind.GetAccessor && + m.kind !== SyntaxKind.SetAccessor && + m.kind !== SyntaxKind.SpreadAssignment) { + continue; + } - /** - * Returns the immediate owning class declaration of a context token, - * on the condition that one exists and that the context implies completion should be given. - */ - function tryGetObjectTypeDeclarationCompletionContainer(sourceFile: SourceFile, contextToken: Node | undefined, location: Node, position: number): ObjectTypeDeclaration | undefined { - // class c { method() { } | method2() { } } - switch (location.kind) { - case SyntaxKind.SyntaxList: - return tryCast(location.parent, isObjectTypeDeclaration); - case SyntaxKind.EndOfFileToken: - const cls = tryCast(lastOrUndefined(cast(location.parent, isSourceFile).statements), isObjectTypeDeclaration); - if (cls && !findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)) { - return cls; - } - break; - case SyntaxKind.Identifier: { - // class c { public prop = c| } - if (isPropertyDeclaration(location.parent) && location.parent.initializer === location) { - return undefined; - } - // class c extends React.Component { a: () => 1\n compon| } - if (isFromObjectTypeDeclaration(location)) { - return findAncestor(location, isObjectTypeDeclaration); + // If this is the current item we are editing right now, do not filter it out + if (isCurrentlyEditingNode(m)) { + continue; + } + + let existingName: __String | undefined; + + if (isSpreadAssignment(m)) { + setMembersDeclaredBySpreadAssignment(m, membersDeclaredBySpreadAssignment); + } + else if (isBindingElement(m) && m.propertyName) { + // include only identifiers in completion list + if (m.propertyName.kind === SyntaxKind.Identifier) { + existingName = m.propertyName.escapedText; } } + else { + // TODO: Account for computed property name + // NOTE: if one only performs this step when m.name is an identifier, + // things like '__proto__' are not filtered out. + const name = getNameOfDeclaration(m); + existingName = name && isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; + } + + if (existingName !== undefined) { + existingMemberNames.add(existingName); + } } - if (!contextToken) return undefined; + const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.has(m.escapedName)); + setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); - // class C { blah; constructor/**/ } and so on - if (location.kind === SyntaxKind.ConstructorKeyword - // class C { blah \n constructor/**/ } - || (isIdentifier(contextToken) && isPropertyDeclaration(contextToken.parent) && isClassLike(location))) { - return findAncestor(contextToken, isClassLike) as ObjectTypeDeclaration; + return filteredSymbols; + } + + function setMembersDeclaredBySpreadAssignment(declaration: SpreadAssignment | JsxSpreadAttribute, membersDeclaredBySpreadAssignment: Set) { + const expression = declaration.expression; + const symbol = typeChecker.getSymbolAtLocation(expression); + const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); + const properties = type && (type as ObjectType).properties; + if (properties) { + properties.forEach(property => { + membersDeclaredBySpreadAssignment.add(property.name); + }); } + } - switch (contextToken.kind) { - case SyntaxKind.EqualsToken: // class c { public prop = | /* global completions */ } - return undefined; + // Set SortText to OptionalMember if it is an optional member + function setSortTextToOptionalMember() { + symbols.forEach(m => { + if (m.flags & SymbolFlags.Optional) { + const symbolId = getSymbolId(m); + symbolToSortTextIdMap[symbolId] = symbolToSortTextIdMap[symbolId] ?? SortTextId.OptionalMember; + } + }); + } - case SyntaxKind.SemicolonToken: // class c {getValue(): number; | } - case SyntaxKind.CloseBraceToken: // class c { method() { } | } - // class c { method() { } b| } - return isFromObjectTypeDeclaration(location) && (location.parent as ClassElement | TypeElement).name === location - ? location.parent.parent as ObjectTypeDeclaration - : tryCast(location, isObjectTypeDeclaration); - case SyntaxKind.OpenBraceToken: // class c { | - case SyntaxKind.CommaToken: // class c {getValue(): number, | } - return tryCast(contextToken.parent, isObjectTypeDeclaration); - default: - if (!isFromObjectTypeDeclaration(contextToken)) { - // class c extends React.Component { a: () => 1\n| } - if (getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line && isObjectTypeDeclaration(location)) { - return location; - } - return undefined; - } - const isValidKeyword = isClassLike(contextToken.parent.parent) ? isClassMemberCompletionKeyword : isInterfaceOrTypeLiteralCompletionKeyword; - return (isValidKeyword(contextToken.kind) || contextToken.kind === SyntaxKind.AsteriskToken || isIdentifier(contextToken) && isValidKeyword(stringToToken(contextToken.text)!)) // TODO: GH#18217 - ? contextToken.parent.parent as ObjectTypeDeclaration : undefined; + // Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment + function setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment: Set, contextualMemberSymbols: Symbol[]): void { + if (membersDeclaredBySpreadAssignment.size === 0) { + return; + } + for (const contextualMemberSymbol of contextualMemberSymbols) { + if (membersDeclaredBySpreadAssignment.has(contextualMemberSymbol.name)) { + symbolToSortTextIdMap[getSymbolId(contextualMemberSymbol)] = SortTextId.MemberDeclaredBySpreadAssignment; + } } } - function tryGetTypeLiteralNode(node: Node): TypeLiteralNode | undefined { - if (!node) return undefined; + /** + * Filters out completion suggestions for class elements. + * + * @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags + */ + function filterClassMembersList(baseSymbols: readonly Symbol[], existingMembers: readonly ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] { + const existingMemberNames = new Set<__String>(); + for (const m of existingMembers) { + // Ignore omitted expressions for missing members + if (m.kind !== SyntaxKind.PropertyDeclaration && + m.kind !== SyntaxKind.MethodDeclaration && + m.kind !== SyntaxKind.GetAccessor && + m.kind !== SyntaxKind.SetAccessor) { + continue; + } - const parent = node.parent; + // If this is the current item we are editing right now, do not filter it out + if (isCurrentlyEditingNode(m)) { + continue; + } - switch (node.kind) { - case SyntaxKind.OpenBraceToken: - if (isTypeLiteralNode(parent)) { - return parent; - } - break; - case SyntaxKind.SemicolonToken: - case SyntaxKind.CommaToken: - case SyntaxKind.Identifier: - if (parent.kind === SyntaxKind.PropertySignature && isTypeLiteralNode(parent.parent)) { - return parent.parent; - } - break; + // Dont filter member even if the name matches if it is declared private in the list + if (hasEffectiveModifier(m, ModifierFlags.Private)) { + continue; + } + + // do not filter it out if the static presence doesnt match + if (isStatic(m) !== !!(currentClassElementModifierFlags & ModifierFlags.Static)) { + continue; + } + + const existingName = getPropertyNameForPropertyNameNode(m.name!); + if (existingName) { + existingMemberNames.add(existingName); + } } - return undefined; + return baseSymbols.filter(propertySymbol => + !existingMemberNames.has(propertySymbol.escapedName) && + !!propertySymbol.declarations && + !(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private) && + !(propertySymbol.valueDeclaration && isPrivateIdentifierClassElementDeclaration(propertySymbol.valueDeclaration))); } - function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker): Type | undefined { - if (!node) return undefined; + /** + * Filters out completion suggestions from 'symbols' according to existing JSX attributes. + * + * @returns Symbols to be suggested in a JSX element, barring those whose attributes + * do not occur at the current position and have not otherwise been typed. + */ + function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] { + const seenNames = new Set<__String>(); + const membersDeclaredBySpreadAssignment = new Set(); + for (const attr of attributes) { + // If this is the current item we are editing right now, do not filter it out + if (isCurrentlyEditingNode(attr)) { + continue; + } - if (isTypeNode(node) && isTypeReferenceType(node.parent)) { - return checker.getTypeArgumentConstraint(node); + if (attr.kind === SyntaxKind.JsxAttribute) { + seenNames.add(attr.name.escapedText); + } + else if (isJsxSpreadAttribute(attr)) { + setMembersDeclaredBySpreadAssignment(attr, membersDeclaredBySpreadAssignment); + } } + const filteredSymbols = symbols.filter(a => !seenNames.has(a.escapedName)); - const t = getConstraintOfTypeArgumentProperty(node.parent, checker); - if (!t) return undefined; + setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); - switch (node.kind) { - case SyntaxKind.PropertySignature: - return checker.getTypeOfPropertyOfContextualType(t, node.symbol.escapedName); - case SyntaxKind.IntersectionType: - case SyntaxKind.TypeLiteral: - case SyntaxKind.UnionType: - return t; - } + return filteredSymbols; + } + + function isCurrentlyEditingNode(node: Node): boolean { + return node.getStart(sourceFile) <= position && position <= node.getEnd(); } +} - // TODO: GH#19856 Would like to return `node is Node & { parent: (ClassElement | TypeElement) & { parent: ObjectTypeDeclaration } }` but then compilation takes > 10 minutes - function isFromObjectTypeDeclaration(node: Node): boolean { - return node.parent && isClassOrTypeElement(node.parent) && isObjectTypeDeclaration(node.parent.parent); +function getRelevantTokens(position: number, sourceFile: SourceFile): { contextToken: Node, previousToken: Node } | { contextToken: undefined, previousToken: undefined } { + const previousToken = findPrecedingToken(position, sourceFile); + if (previousToken && position <= previousToken.end && (isMemberName(previousToken) || isKeyword(previousToken.kind))) { + const contextToken = findPrecedingToken(previousToken.getFullStart(), sourceFile, /*startNode*/ undefined)!; // TODO: GH#18217 + return { contextToken, previousToken }; } + return { contextToken: previousToken as Node, previousToken: previousToken as Node }; +} - function isValidTrigger(sourceFile: SourceFile, triggerCharacter: CompletionsTriggerCharacter, contextToken: Node | undefined, position: number): boolean { - switch (triggerCharacter) { - case ".": - case "@": - return true; - case '"': - case "'": - case "`": - // Only automatically bring up completions if this is an opening quote. - return !!contextToken && isStringLiteralOrTemplate(contextToken) && position === contextToken.getStart(sourceFile) + 1; - case "#": - return !!contextToken && isPrivateIdentifier(contextToken) && !!getContainingClass(contextToken); - case "<": - // Opening JSX tag - return !!contextToken && contextToken.kind === SyntaxKind.LessThanToken && (!isBinaryExpression(contextToken.parent) || binaryExpressionMayBeOpenTag(contextToken.parent)); - case "/": - return !!contextToken && (isStringLiteralLike(contextToken) - ? !!tryGetImportFromModuleSpecifier(contextToken) - : contextToken.kind === SyntaxKind.SlashToken && isJsxClosingElement(contextToken.parent)); - case " ": - return !!contextToken && isImportKeyword(contextToken) && contextToken.parent.kind === SyntaxKind.SourceFile; - default: - return Debug.assertNever(triggerCharacter); - } +function getAutoImportSymbolFromCompletionEntryData(name: string, data: CompletionEntryData, program: Program, host: LanguageServiceHost): { symbol: Symbol, origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport } | undefined { + const containingProgram = data.isPackageJsonImport ? host.getPackageJsonAutoImportProvider!()! : program; + const checker = containingProgram.getTypeChecker(); + const moduleSymbol = + data.ambientModuleName ? checker.tryFindAmbientModule(data.ambientModuleName) : + data.fileName ? checker.getMergedSymbol(Debug.checkDefined(containingProgram.getSourceFile(data.fileName)).symbol) : + undefined; + + if (!moduleSymbol) return undefined; + let symbol = data.exportName === InternalSymbolName.ExportEquals + ? checker.resolveExternalModuleSymbol(moduleSymbol) + : checker.tryGetMemberInModuleExportsAndProperties(data.exportName, moduleSymbol); + if (!symbol) return undefined; + const isDefaultExport = data.exportName === InternalSymbolName.Default; + symbol = isDefaultExport && getLocalSymbolForExportDefault(symbol) || symbol; + return { symbol, origin: completionEntryDataToSymbolOriginInfo(data, name, moduleSymbol) }; +} + +interface CompletionEntryDisplayNameForSymbol { + readonly name: string; + readonly needsConvertPropertyAccess: boolean; +} +function getCompletionEntryDisplayNameForSymbol( + symbol: Symbol, + target: ScriptTarget, + origin: SymbolOriginInfo | undefined, + kind: CompletionKind, + jsxIdentifierExpected: boolean, +): CompletionEntryDisplayNameForSymbol | undefined { + const name = originIncludesSymbolName(origin) ? origin.symbolName : symbol.name; + if (name === undefined + // If the symbol is external module, don't show it in the completion list + // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there) + || symbol.flags & SymbolFlags.Module && isSingleOrDoubleQuote(name.charCodeAt(0)) + // If the symbol is the internal name of an ES symbol, it is not a valid entry. Internal names for ES symbols start with "__@" + || isKnownSymbol(symbol)) { + return undefined; } - function binaryExpressionMayBeOpenTag({ left }: BinaryExpression): boolean { - return nodeIsMissing(left); + const validNameResult: CompletionEntryDisplayNameForSymbol = { name, needsConvertPropertyAccess: false }; + if (isIdentifierText(name, target, jsxIdentifierExpected ? LanguageVariant.JSX : LanguageVariant.Standard) || symbol.valueDeclaration && isPrivateIdentifierClassElementDeclaration(symbol.valueDeclaration)) { + return validNameResult; + } + switch (kind) { + case CompletionKind.MemberLike: + return undefined; + case CompletionKind.ObjectPropertyDeclaration: + // TODO: GH#18169 + return { name: JSON.stringify(name), needsConvertPropertyAccess: false }; + case CompletionKind.PropertyAccess: + case CompletionKind.Global: // For a 'this.' completion it will be in a global context, but may have a non-identifier name. + // Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547 + return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true }; + case CompletionKind.None: + case CompletionKind.String: + return validNameResult; + default: + Debug.assertNever(kind); } +} - /** Determines if a type is exactly the same type resolved by the global 'self', 'global', or 'globalThis'. */ - function isProbablyGlobalType(type: Type, sourceFile: SourceFile, checker: TypeChecker) { - // The type of `self` and `window` is the same in lib.dom.d.ts, but `window` does not exist in - // lib.webworker.d.ts, so checking against `self` is also a check against `window` when it exists. - const selfSymbol = checker.resolveName("self", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); - if (selfSymbol && checker.getTypeOfSymbolAtLocation(selfSymbol, sourceFile) === type) { - return true; - } - const globalSymbol = checker.resolveName("global", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); - if (globalSymbol && checker.getTypeOfSymbolAtLocation(globalSymbol, sourceFile) === type) { - return true; +// A cache of completion entries for keywords, these do not change between sessions +const _keywordCompletions: CompletionEntry[][] = []; +const allKeywordsCompletions: () => readonly CompletionEntry[] = memoize(() => { + const res: CompletionEntry[] = []; + for (let i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) { + res.push({ + name: tokenToString(i)!, + kind: ScriptElementKind.keyword, + kindModifiers: ScriptElementKindModifier.none, + sortText: SortText.GlobalsOrKeywords + }); + } + return res; +}); + +function getKeywordCompletions(keywordFilter: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean): readonly CompletionEntry[] { + if (!filterOutTsOnlyKeywords) return getTypescriptKeywordCompletions(keywordFilter); + + const index = keywordFilter + KeywordCompletionFilters.Last + 1; + return _keywordCompletions[index] || + (_keywordCompletions[index] = getTypescriptKeywordCompletions(keywordFilter) + .filter(entry => !isTypeScriptOnlyKeyword(stringToToken(entry.name)!)) + ); +} + +function getTypescriptKeywordCompletions(keywordFilter: KeywordCompletionFilters): readonly CompletionEntry[] { + return _keywordCompletions[keywordFilter] || (_keywordCompletions[keywordFilter] = allKeywordsCompletions().filter(entry => { + const kind = stringToToken(entry.name)!; + switch (keywordFilter) { + case KeywordCompletionFilters.None: + return false; + case KeywordCompletionFilters.All: + return isFunctionLikeBodyKeyword(kind) + || kind === SyntaxKind.DeclareKeyword + || kind === SyntaxKind.ModuleKeyword + || kind === SyntaxKind.TypeKeyword + || kind === SyntaxKind.NamespaceKeyword + || kind === SyntaxKind.AbstractKeyword + || isTypeKeyword(kind) && kind !== SyntaxKind.UndefinedKeyword; + case KeywordCompletionFilters.FunctionLikeBodyKeywords: + return isFunctionLikeBodyKeyword(kind); + case KeywordCompletionFilters.ClassElementKeywords: + return isClassMemberCompletionKeyword(kind); + case KeywordCompletionFilters.InterfaceElementKeywords: + return isInterfaceOrTypeLiteralCompletionKeyword(kind); + case KeywordCompletionFilters.ConstructorParameterKeywords: + return isParameterPropertyModifier(kind); + case KeywordCompletionFilters.TypeAssertionKeywords: + return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword; + case KeywordCompletionFilters.TypeKeywords: + return isTypeKeyword(kind); + case KeywordCompletionFilters.TypeKeyword: + return kind === SyntaxKind.TypeKeyword; + default: + return Debug.assertNever(keywordFilter); } - const globalThisSymbol = checker.resolveName("globalThis", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); - if (globalThisSymbol && checker.getTypeOfSymbolAtLocation(globalThisSymbol, sourceFile) === type) { + })); +} + +function isTypeScriptOnlyKeyword(kind: SyntaxKind) { + switch (kind) { + case SyntaxKind.AbstractKeyword: + case SyntaxKind.AnyKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.EnumKeyword: + case SyntaxKind.GlobalKeyword: + case SyntaxKind.ImplementsKeyword: + case SyntaxKind.InferKeyword: + case SyntaxKind.InterfaceKeyword: + case SyntaxKind.IsKeyword: + case SyntaxKind.KeyOfKeyword: + case SyntaxKind.ModuleKeyword: + case SyntaxKind.NamespaceKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.OverrideKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PublicKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.TypeKeyword: + case SyntaxKind.UniqueKeyword: + case SyntaxKind.UnknownKeyword: return true; - } - return false; + default: + return false; } +} + +function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean { + return kind === SyntaxKind.ReadonlyKeyword; +} - function isStaticProperty(symbol: Symbol) { - return !!(symbol.valueDeclaration && getEffectiveModifierFlags(symbol.valueDeclaration) & ModifierFlags.Static && isClassLike(symbol.valueDeclaration.parent)); +function isClassMemberCompletionKeyword(kind: SyntaxKind) { + switch (kind) { + case SyntaxKind.AbstractKeyword: + case SyntaxKind.ConstructorKeyword: + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + case SyntaxKind.AsyncKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.OverrideKeyword: + return true; + default: + return isClassMemberModifier(kind); } +} - function tryGetObjectLiteralContextualType(node: ObjectLiteralExpression, typeChecker: TypeChecker) { - const type = typeChecker.getContextualType(node); - if (type) { - return type; - } - if (isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken && node === node.parent.left) { - // Object literal is assignment pattern: ({ | } = x) - return typeChecker.getTypeAtLocation(node.parent); - } - return undefined; +function isFunctionLikeBodyKeyword(kind: SyntaxKind) { + return kind === SyntaxKind.AsyncKeyword + || kind === SyntaxKind.AwaitKeyword + || kind === SyntaxKind.AsKeyword + || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind); +} + +function keywordForNode(node: Node): SyntaxKind { + return isIdentifier(node) ? node.originalKeywordKind || SyntaxKind.Unknown : node.kind; +} + +/** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ +function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefined { + const jsdoc = findAncestor(node, isJSDoc); + return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined); +} + +export function getPropertiesForObjectExpression(contextualType: Type, completionsType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { + const hasCompletionsType = completionsType && completionsType !== contextualType; + const type = hasCompletionsType && !(completionsType!.flags & TypeFlags.AnyOrUnknown) + ? checker.getUnionType([contextualType, completionsType!]) + : contextualType; + + const properties = getApparentProperties(type, obj, checker); + return type.isClass() && containsNonPublicProperties(properties) ? [] : + hasCompletionsType ? filter(properties, hasDeclarationOtherThanSelf) : properties; + + // Filter out members whose only declaration is the object literal itself to avoid + // self-fulfilling completions like: + // + // function f(x: T) {} + // f({ abc/**/: "" }) // `abc` is a member of `T` but only because it declares itself + function hasDeclarationOtherThanSelf(member: Symbol) { + if (!length(member.declarations)) return true; + return some(member.declarations, decl => decl.parent !== obj); } +} - interface ImportStatementCompletionInfo { - isKeywordOnlyCompletion: boolean; - keywordCompletion: TokenSyntaxKind | undefined; - isNewIdentifierLocation: boolean; - replacementNode: ImportEqualsDeclaration | ImportDeclaration | ImportSpecifier | Token | undefined; - } - - function getImportStatementCompletionInfo(contextToken: Node): ImportStatementCompletionInfo { - let keywordCompletion: TokenSyntaxKind | undefined; - let isKeywordOnlyCompletion = false; - const candidate = getCandidate(); - return { - isKeywordOnlyCompletion, - keywordCompletion, - isNewIdentifierLocation: !!(candidate || keywordCompletion === SyntaxKind.TypeKeyword), - replacementNode: candidate && rangeIsOnSingleLine(candidate, candidate.getSourceFile()) - ? candidate - : undefined - }; +function getApparentProperties(type: Type, node: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker) { + if (!type.isUnion()) return type.getApparentProperties(); + return checker.getAllPossiblePropertiesOfTypes(filter(type.types, memberType => + !(memberType.flags & TypeFlags.Primitive + || checker.isArrayLikeType(memberType) + || checker.isTypeInvalidDueToUnionDiscriminant(memberType, node) + || typeHasCallOrConstructSignatures(memberType, checker) + || memberType.isClass() && containsNonPublicProperties(memberType.getApparentProperties())))); +} - function getCandidate() { - const parent = contextToken.parent; - if (isImportEqualsDeclaration(parent)) { - keywordCompletion = contextToken.kind === SyntaxKind.TypeKeyword ? undefined : SyntaxKind.TypeKeyword; - return isModuleSpecifierMissingOrEmpty(parent.moduleReference) ? parent : undefined; +function containsNonPublicProperties(props: Symbol[]) { + return some(props, p => !!(getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.NonPublicAccessibilityModifier)); +} + +/** + * Gets all properties on a type, but if that type is a union of several types, + * excludes array-like types or callable/constructable types. + */ +function getPropertiesForCompletion(type: Type, checker: TypeChecker): Symbol[] { + return type.isUnion() + ? Debug.checkEachDefined(checker.getAllPossiblePropertiesOfTypes(type.types), "getAllPossiblePropertiesOfTypes() should all be defined") + : Debug.checkEachDefined(type.getApparentProperties(), "getApparentProperties() should all be defined"); +} + +/** + * Returns the immediate owning class declaration of a context token, + * on the condition that one exists and that the context implies completion should be given. + */ +function tryGetObjectTypeDeclarationCompletionContainer(sourceFile: SourceFile, contextToken: Node | undefined, location: Node, position: number): ObjectTypeDeclaration | undefined { + // class c { method() { } | method2() { } } + switch (location.kind) { + case SyntaxKind.SyntaxList: + return tryCast(location.parent, isObjectTypeDeclaration); + case SyntaxKind.EndOfFileToken: + const cls = tryCast(lastOrUndefined(cast(location.parent, isSourceFile).statements), isObjectTypeDeclaration); + if (cls && !findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)) { + return cls; + } + break; + case SyntaxKind.Identifier: { + // class c { public prop = c| } + if (isPropertyDeclaration(location.parent) && location.parent.initializer === location) { + return undefined; } - if (couldBeTypeOnlyImportSpecifier(parent, contextToken) && canCompleteFromNamedBindings(parent.parent)) { - return parent; + // class c extends React.Component { a: () => 1\n compon| } + if (isFromObjectTypeDeclaration(location)) { + return findAncestor(location, isObjectTypeDeclaration); } - if (isNamedImports(parent) || isNamespaceImport(parent)) { - if (!parent.parent.isTypeOnly && ( - contextToken.kind === SyntaxKind.OpenBraceToken || - contextToken.kind === SyntaxKind.ImportKeyword || - contextToken.kind === SyntaxKind.CommaToken - )) { - keywordCompletion = SyntaxKind.TypeKeyword; - } + } + } - if (canCompleteFromNamedBindings(parent)) { - // At `import { ... } |` or `import * as Foo |`, the only possible completion is `from` - if (contextToken.kind === SyntaxKind.CloseBraceToken || contextToken.kind === SyntaxKind.Identifier) { - isKeywordOnlyCompletion = true; - keywordCompletion = SyntaxKind.FromKeyword; - } - else { - return parent.parent.parent; - } + if (!contextToken) return undefined; + + // class C { blah; constructor/**/ } and so on + if (location.kind === SyntaxKind.ConstructorKeyword + // class C { blah \n constructor/**/ } + || (isIdentifier(contextToken) && isPropertyDeclaration(contextToken.parent) && isClassLike(location))) { + return findAncestor(contextToken, isClassLike) as ObjectTypeDeclaration; + } + + switch (contextToken.kind) { + case SyntaxKind.EqualsToken: // class c { public prop = | /* global completions */ } + return undefined; + + case SyntaxKind.SemicolonToken: // class c {getValue(): number; | } + case SyntaxKind.CloseBraceToken: // class c { method() { } | } + // class c { method() { } b| } + return isFromObjectTypeDeclaration(location) && (location.parent as ClassElement | TypeElement).name === location + ? location.parent.parent as ObjectTypeDeclaration + : tryCast(location, isObjectTypeDeclaration); + case SyntaxKind.OpenBraceToken: // class c { | + case SyntaxKind.CommaToken: // class c {getValue(): number, | } + return tryCast(contextToken.parent, isObjectTypeDeclaration); + default: + if (!isFromObjectTypeDeclaration(contextToken)) { + // class c extends React.Component { a: () => 1\n| } + if (getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line && isObjectTypeDeclaration(location)) { + return location; } return undefined; } - if (isImportKeyword(contextToken) && isSourceFile(parent)) { - // A lone import keyword with nothing following it does not parse as a statement at all - keywordCompletion = SyntaxKind.TypeKeyword; - return contextToken as Token; + const isValidKeyword = isClassLike(contextToken.parent.parent) ? isClassMemberCompletionKeyword : isInterfaceOrTypeLiteralCompletionKeyword; + return (isValidKeyword(contextToken.kind) || contextToken.kind === SyntaxKind.AsteriskToken || isIdentifier(contextToken) && isValidKeyword(stringToToken(contextToken.text)!)) // TODO: GH#18217 + ? contextToken.parent.parent as ObjectTypeDeclaration : undefined; + } +} + +function tryGetTypeLiteralNode(node: Node): TypeLiteralNode | undefined { + if (!node) return undefined; + + const parent = node.parent; + + switch (node.kind) { + case SyntaxKind.OpenBraceToken: + if (isTypeLiteralNode(parent)) { + return parent; } - if (isImportKeyword(contextToken) && isImportDeclaration(parent)) { - // `import s| from` - keywordCompletion = SyntaxKind.TypeKeyword; - return isModuleSpecifierMissingOrEmpty(parent.moduleSpecifier) ? parent : undefined; + break; + case SyntaxKind.SemicolonToken: + case SyntaxKind.CommaToken: + case SyntaxKind.Identifier: + if (parent.kind === SyntaxKind.PropertySignature && isTypeLiteralNode(parent.parent)) { + return parent.parent; } - return undefined; - } + break; } - function couldBeTypeOnlyImportSpecifier(importSpecifier: Node, contextToken: Node | undefined): importSpecifier is ImportSpecifier { - return isImportSpecifier(importSpecifier) - && (importSpecifier.isTypeOnly || contextToken === importSpecifier.name && isTypeKeywordTokenOrIdentifier(contextToken)); - } + return undefined; +} + +function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker): Type | undefined { + if (!node) return undefined; - function canCompleteFromNamedBindings(namedBindings: NamedImportBindings) { - return isModuleSpecifierMissingOrEmpty(namedBindings.parent.parent.moduleSpecifier) - && (isNamespaceImport(namedBindings) || namedBindings.elements.length < 2) - && !namedBindings.parent.name; + if (isTypeNode(node) && isTypeReferenceType(node.parent)) { + return checker.getTypeArgumentConstraint(node); } - function isModuleSpecifierMissingOrEmpty(specifier: ModuleReference | Expression) { - if (nodeIsMissing(specifier)) return true; - return !tryCast(isExternalModuleReference(specifier) ? specifier.expression : specifier, isStringLiteralLike)?.text; + const t = getConstraintOfTypeArgumentProperty(node.parent, checker); + if (!t) return undefined; + + switch (node.kind) { + case SyntaxKind.PropertySignature: + return checker.getTypeOfPropertyOfContextualType(t, node.symbol.escapedName); + case SyntaxKind.IntersectionType: + case SyntaxKind.TypeLiteral: + case SyntaxKind.UnionType: + return t; } +} - function getVariableDeclaration(property: Node): VariableDeclaration | undefined { - const variableDeclaration = findAncestor(property, node => - isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node) - ? "quit" - : isVariableDeclaration(node)); +// TODO: GH#19856 Would like to return `node is Node & { parent: (ClassElement | TypeElement) & { parent: ObjectTypeDeclaration } }` but then compilation takes > 10 minutes +function isFromObjectTypeDeclaration(node: Node): boolean { + return node.parent && isClassOrTypeElement(node.parent) && isObjectTypeDeclaration(node.parent.parent); +} - return variableDeclaration as VariableDeclaration | undefined; +function isValidTrigger(sourceFile: SourceFile, triggerCharacter: CompletionsTriggerCharacter, contextToken: Node | undefined, position: number): boolean { + switch (triggerCharacter) { + case ".": + case "@": + return true; + case '"': + case "'": + case "`": + // Only automatically bring up completions if this is an opening quote. + return !!contextToken && isStringLiteralOrTemplate(contextToken) && position === contextToken.getStart(sourceFile) + 1; + case "#": + return !!contextToken && isPrivateIdentifier(contextToken) && !!getContainingClass(contextToken); + case "<": + // Opening JSX tag + return !!contextToken && contextToken.kind === SyntaxKind.LessThanToken && (!isBinaryExpression(contextToken.parent) || binaryExpressionMayBeOpenTag(contextToken.parent)); + case "/": + return !!contextToken && (isStringLiteralLike(contextToken) + ? !!tryGetImportFromModuleSpecifier(contextToken) + : contextToken.kind === SyntaxKind.SlashToken && isJsxClosingElement(contextToken.parent)); + case " ": + return !!contextToken && isImportKeyword(contextToken) && contextToken.parent.kind === SyntaxKind.SourceFile; + default: + return Debug.assertNever(triggerCharacter); } +} - function isArrowFunctionBody(node: Node) { - return node.parent && isArrowFunction(node.parent) && node.parent.body === node; - }; +function binaryExpressionMayBeOpenTag({ left }: BinaryExpression): boolean { + return nodeIsMissing(left); +} - /** True if symbol is a type or a module containing at least one type. */ - function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, checker: TypeChecker, seenModules = new Map()): boolean { - const sym = skipAlias(symbol.exportSymbol || symbol, checker); - return !!(sym.flags & SymbolFlags.Type) || checker.isUnknownSymbol(sym) || - !!(sym.flags & SymbolFlags.Module) && addToSeen(seenModules, getSymbolId(sym)) && - checker.getExportsOfModule(sym).some(e => symbolCanBeReferencedAtTypeLocation(e, checker, seenModules)); +/** Determines if a type is exactly the same type resolved by the global 'self', 'global', or 'globalThis'. */ +function isProbablyGlobalType(type: Type, sourceFile: SourceFile, checker: TypeChecker) { + // The type of `self` and `window` is the same in lib.dom.d.ts, but `window` does not exist in + // lib.webworker.d.ts, so checking against `self` is also a check against `window` when it exists. + const selfSymbol = checker.resolveName("self", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); + if (selfSymbol && checker.getTypeOfSymbolAtLocation(selfSymbol, sourceFile) === type) { + return true; + } + const globalSymbol = checker.resolveName("global", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); + if (globalSymbol && checker.getTypeOfSymbolAtLocation(globalSymbol, sourceFile) === type) { + return true; } + const globalThisSymbol = checker.resolveName("globalThis", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); + if (globalThisSymbol && checker.getTypeOfSymbolAtLocation(globalThisSymbol, sourceFile) === type) { + return true; + } + return false; +} + +function isStaticProperty(symbol: Symbol) { + return !!(symbol.valueDeclaration && getEffectiveModifierFlags(symbol.valueDeclaration) & ModifierFlags.Static && isClassLike(symbol.valueDeclaration.parent)); +} - function isDeprecated(symbol: Symbol, checker: TypeChecker) { - const declarations = skipAlias(symbol, checker).declarations; - return !!length(declarations) && every(declarations, isDeprecatedDeclaration); +function tryGetObjectLiteralContextualType(node: ObjectLiteralExpression, typeChecker: TypeChecker) { + const type = typeChecker.getContextualType(node); + if (type) { + return type; } + if (isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken && node === node.parent.left) { + // Object literal is assignment pattern: ({ | } = x) + return typeChecker.getTypeAtLocation(node.parent); + } + return undefined; +} - /** - * True if the first character of `lowercaseCharacters` is the first character - * of some "word" in `identiferString` (where the string is split into "words" - * by camelCase and snake_case segments), then if the remaining characters of - * `lowercaseCharacters` appear, in order, in the rest of `identifierString`. - * - * True: - * 'state' in 'useState' - * 'sae' in 'useState' - * 'viable' in 'ENVIRONMENT_VARIABLE' - * - * False: - * 'staet' in 'useState' - * 'tate' in 'useState' - * 'ment' in 'ENVIRONMENT_VARIABLE' - */ - function charactersFuzzyMatchInString(identifierString: string, lowercaseCharacters: string): boolean { - if (lowercaseCharacters.length === 0) { - return true; - } +interface ImportStatementCompletionInfo { + isKeywordOnlyCompletion: boolean; + keywordCompletion: TokenSyntaxKind | undefined; + isNewIdentifierLocation: boolean; + replacementNode: ImportEqualsDeclaration | ImportDeclaration | ImportSpecifier | Token | undefined; +} + +function getImportStatementCompletionInfo(contextToken: Node): ImportStatementCompletionInfo { + let keywordCompletion: TokenSyntaxKind | undefined; + let isKeywordOnlyCompletion = false; + const candidate = getCandidate(); + return { + isKeywordOnlyCompletion, + keywordCompletion, + isNewIdentifierLocation: !!(candidate || keywordCompletion === SyntaxKind.TypeKeyword), + replacementNode: candidate && rangeIsOnSingleLine(candidate, candidate.getSourceFile()) + ? candidate + : undefined + }; + + function getCandidate() { + const parent = contextToken.parent; + if (isImportEqualsDeclaration(parent)) { + keywordCompletion = contextToken.kind === SyntaxKind.TypeKeyword ? undefined : SyntaxKind.TypeKeyword; + return isModuleSpecifierMissingOrEmpty(parent.moduleReference) ? parent : undefined; + } + if (couldBeTypeOnlyImportSpecifier(parent, contextToken) && canCompleteFromNamedBindings(parent.parent)) { + return parent; + } + if (isNamedImports(parent) || isNamespaceImport(parent)) { + if (!parent.parent.isTypeOnly && ( + contextToken.kind === SyntaxKind.OpenBraceToken || + contextToken.kind === SyntaxKind.ImportKeyword || + contextToken.kind === SyntaxKind.CommaToken + )) { + keywordCompletion = SyntaxKind.TypeKeyword; + } - let matchedFirstCharacter = false; - let prevChar: number | undefined; - let characterIndex = 0; - const len = identifierString.length; - for (let strIndex = 0; strIndex < len; strIndex++) { - const strChar = identifierString.charCodeAt(strIndex); - const testChar = lowercaseCharacters.charCodeAt(characterIndex); - if (strChar === testChar || strChar === toUpperCharCode(testChar)) { - matchedFirstCharacter ||= - prevChar === undefined || // Beginning of word - CharacterCodes.a <= prevChar && prevChar <= CharacterCodes.z && CharacterCodes.A <= strChar && strChar <= CharacterCodes.Z || // camelCase transition - prevChar === CharacterCodes._ && strChar !== CharacterCodes._; // snake_case transition - if (matchedFirstCharacter) { - characterIndex++; + if (canCompleteFromNamedBindings(parent)) { + // At `import { ... } |` or `import * as Foo |`, the only possible completion is `from` + if (contextToken.kind === SyntaxKind.CloseBraceToken || contextToken.kind === SyntaxKind.Identifier) { + isKeywordOnlyCompletion = true; + keywordCompletion = SyntaxKind.FromKeyword; } - if (characterIndex === lowercaseCharacters.length) { - return true; + else { + return parent.parent.parent; } } - prevChar = strChar; + return undefined; } - - // Did not find all characters - return false; + if (isImportKeyword(contextToken) && isSourceFile(parent)) { + // A lone import keyword with nothing following it does not parse as a statement at all + keywordCompletion = SyntaxKind.TypeKeyword; + return contextToken as Token; + } + if (isImportKeyword(contextToken) && isImportDeclaration(parent)) { + // `import s| from` + keywordCompletion = SyntaxKind.TypeKeyword; + return isModuleSpecifierMissingOrEmpty(parent.moduleSpecifier) ? parent : undefined; + } + return undefined; } +} + +function couldBeTypeOnlyImportSpecifier(importSpecifier: Node, contextToken: Node | undefined): importSpecifier is ImportSpecifier { + return isImportSpecifier(importSpecifier) + && (importSpecifier.isTypeOnly || contextToken === importSpecifier.name && isTypeKeywordTokenOrIdentifier(contextToken)); +} + +function canCompleteFromNamedBindings(namedBindings: NamedImportBindings) { + return isModuleSpecifierMissingOrEmpty(namedBindings.parent.parent.moduleSpecifier) + && (isNamespaceImport(namedBindings) || namedBindings.elements.length < 2) + && !namedBindings.parent.name; +} + +function isModuleSpecifierMissingOrEmpty(specifier: ModuleReference | Expression) { + if (nodeIsMissing(specifier)) return true; + return !tryCast(isExternalModuleReference(specifier) ? specifier.expression : specifier, isStringLiteralLike)?.text; +} + +function getVariableDeclaration(property: Node): VariableDeclaration | undefined { + const variableDeclaration = findAncestor(property, node => + isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node) + ? "quit" + : isVariableDeclaration(node)); + + return variableDeclaration as VariableDeclaration | undefined; +} + +function isArrowFunctionBody(node: Node) { + return node.parent && isArrowFunction(node.parent) && node.parent.body === node; +}; + +/** True if symbol is a type or a module containing at least one type. */ +function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, checker: TypeChecker, seenModules = new Map()): boolean { + const sym = skipAlias(symbol.exportSymbol || symbol, checker); + return !!(sym.flags & SymbolFlags.Type) || checker.isUnknownSymbol(sym) || + !!(sym.flags & SymbolFlags.Module) && addToSeen(seenModules, getSymbolId(sym)) && + checker.getExportsOfModule(sym).some(e => symbolCanBeReferencedAtTypeLocation(e, checker, seenModules)); +} + +function isDeprecated(symbol: Symbol, checker: TypeChecker) { + const declarations = skipAlias(symbol, checker).declarations; + return !!length(declarations) && every(declarations, isDeprecatedDeclaration); +} - function toUpperCharCode(charCode: number) { - if (CharacterCodes.a <= charCode && charCode <= CharacterCodes.z) { - return charCode - 32; +/** + * True if the first character of `lowercaseCharacters` is the first character + * of some "word" in `identiferString` (where the string is split into "words" + * by camelCase and snake_case segments), then if the remaining characters of + * `lowercaseCharacters` appear, in order, in the rest of `identifierString`. + * + * True: + * 'state' in 'useState' + * 'sae' in 'useState' + * 'viable' in 'ENVIRONMENT_VARIABLE' + * + * False: + * 'staet' in 'useState' + * 'tate' in 'useState' + * 'ment' in 'ENVIRONMENT_VARIABLE' + */ + function charactersFuzzyMatchInString(identifierString: string, lowercaseCharacters: string): boolean { + if (lowercaseCharacters.length === 0) { + return true; + } + + let matchedFirstCharacter = false; + let prevChar: number | undefined; + let characterIndex = 0; + const len = identifierString.length; + for (let strIndex = 0; strIndex < len; strIndex++) { + const strChar = identifierString.charCodeAt(strIndex); + const testChar = lowercaseCharacters.charCodeAt(characterIndex); + if (strChar === testChar || strChar === toUpperCharCode(testChar)) { + matchedFirstCharacter ||= + prevChar === undefined || // Beginning of word + CharacterCodes.a <= prevChar && prevChar <= CharacterCodes.z && CharacterCodes.A <= strChar && strChar <= CharacterCodes.Z || // camelCase transition + prevChar === CharacterCodes._ && strChar !== CharacterCodes._; // snake_case transition + if (matchedFirstCharacter) { + characterIndex++; + } + if (characterIndex === lowercaseCharacters.length) { + return true; + } } - return charCode; + prevChar = strChar; + } + + // Did not find all characters + return false; +} + +function toUpperCharCode(charCode: number) { + if (CharacterCodes.a <= charCode && charCode <= CharacterCodes.z) { + return charCode - 32; } + return charCode; +} } diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index ffcec349cc663..16bfac2be64ed 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -1,519 +1,519 @@ namespace ts { - export interface DocumentHighlights { - fileName: string; - highlightSpans: HighlightSpan[]; - } - - /* @internal */ - export namespace DocumentHighlights { - export function getDocumentHighlights(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { - const node = getTouchingPropertyName(sourceFile, position); - - if (node.parent && (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent))) { - // For a JSX element, just highlight the matching tag, not all references. - const { openingElement, closingElement } = node.parent.parent; - const highlightSpans = [openingElement, closingElement].map(({ tagName }) => getHighlightSpanForNode(tagName, sourceFile)); - return [{ fileName: sourceFile.fileName, highlightSpans }]; - } +export interface DocumentHighlights { + fileName: string; + highlightSpans: HighlightSpan[]; +} - return getSemanticDocumentHighlights(position, node, program, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile); - } +/* @internal */ +export namespace DocumentHighlights { + export function getDocumentHighlights(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { + const node = getTouchingPropertyName(sourceFile, position); - function getHighlightSpanForNode(node: Node, sourceFile: SourceFile): HighlightSpan { - return { - fileName: sourceFile.fileName, - textSpan: createTextSpanFromNode(node, sourceFile), - kind: HighlightSpanKind.none - }; + if (node.parent && (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent))) { + // For a JSX element, just highlight the matching tag, not all references. + const { openingElement, closingElement } = node.parent.parent; + const highlightSpans = [openingElement, closingElement].map(({ tagName }) => getHighlightSpanForNode(tagName, sourceFile)); + return [{ fileName: sourceFile.fileName, highlightSpans }]; } - function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { - const sourceFilesSet = new Set(sourceFilesToSearch.map(f => f.fileName)); - const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*options*/ undefined, sourceFilesSet); - if (!referenceEntries) return undefined; - const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span); - const getCanonicalFileName = createGetCanonicalFileName(program.useCaseSensitiveFileNames()); - return mapDefined(arrayFrom(map.entries()), ([fileName, highlightSpans]) => { - if (!sourceFilesSet.has(fileName)) { - if (!program.redirectTargetsMap.has(toPath(fileName, program.getCurrentDirectory(), getCanonicalFileName))) { - return undefined; - } - const redirectTarget = program.getSourceFile(fileName); - const redirect = find(sourceFilesToSearch, f => !!f.redirectInfo && f.redirectInfo.redirectTarget === redirectTarget)!; - fileName = redirect.fileName; - Debug.assert(sourceFilesSet.has(fileName)); - } - return { fileName, highlightSpans }; - }); - } + return getSemanticDocumentHighlights(position, node, program, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile); + } - function getSyntacticDocumentHighlights(node: Node, sourceFile: SourceFile): DocumentHighlights[] | undefined { - const highlightSpans = getHighlightSpans(node, sourceFile); - return highlightSpans && [{ fileName: sourceFile.fileName, highlightSpans }]; - } + function getHighlightSpanForNode(node: Node, sourceFile: SourceFile): HighlightSpan { + return { + fileName: sourceFile.fileName, + textSpan: createTextSpanFromNode(node, sourceFile), + kind: HighlightSpanKind.none + }; + } - function getHighlightSpans(node: Node, sourceFile: SourceFile): HighlightSpan[] | undefined { - switch (node.kind) { - case SyntaxKind.IfKeyword: - case SyntaxKind.ElseKeyword: - return isIfStatement(node.parent) ? getIfElseOccurrences(node.parent, sourceFile) : undefined; - case SyntaxKind.ReturnKeyword: - return useParent(node.parent, isReturnStatement, getReturnOccurrences); - case SyntaxKind.ThrowKeyword: - return useParent(node.parent, isThrowStatement, getThrowOccurrences); - case SyntaxKind.TryKeyword: - case SyntaxKind.CatchKeyword: - case SyntaxKind.FinallyKeyword: - const tryStatement = node.kind === SyntaxKind.CatchKeyword ? node.parent.parent : node.parent; - return useParent(tryStatement, isTryStatement, getTryCatchFinallyOccurrences); - case SyntaxKind.SwitchKeyword: - return useParent(node.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences); - case SyntaxKind.CaseKeyword: - case SyntaxKind.DefaultKeyword: { - if (isDefaultClause(node.parent) || isCaseClause(node.parent)) { - return useParent(node.parent.parent.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences); - } + function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { + const sourceFilesSet = new Set(sourceFilesToSearch.map(f => f.fileName)); + const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*options*/ undefined, sourceFilesSet); + if (!referenceEntries) return undefined; + const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span); + const getCanonicalFileName = createGetCanonicalFileName(program.useCaseSensitiveFileNames()); + return mapDefined(arrayFrom(map.entries()), ([fileName, highlightSpans]) => { + if (!sourceFilesSet.has(fileName)) { + if (!program.redirectTargetsMap.has(toPath(fileName, program.getCurrentDirectory(), getCanonicalFileName))) { return undefined; } - case SyntaxKind.BreakKeyword: - case SyntaxKind.ContinueKeyword: - return useParent(node.parent, isBreakOrContinueStatement, getBreakOrContinueStatementOccurrences); - case SyntaxKind.ForKeyword: - case SyntaxKind.WhileKeyword: - case SyntaxKind.DoKeyword: - return useParent(node.parent, (n): n is IterationStatement => isIterationStatement(n, /*lookInLabeledStatements*/ true), getLoopBreakContinueOccurrences); - case SyntaxKind.ConstructorKeyword: - return getFromAllDeclarations(isConstructorDeclaration, [SyntaxKind.ConstructorKeyword]); - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - return getFromAllDeclarations(isAccessor, [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword]); - case SyntaxKind.AwaitKeyword: - return useParent(node.parent, isAwaitExpression, getAsyncAndAwaitOccurrences); - case SyntaxKind.AsyncKeyword: - return highlightSpans(getAsyncAndAwaitOccurrences(node)); - case SyntaxKind.YieldKeyword: - return highlightSpans(getYieldOccurrences(node)); - default: - return isModifierKind(node.kind) && (isDeclaration(node.parent) || isVariableStatement(node.parent)) - ? highlightSpans(getModifierOccurrences(node.kind, node.parent)) - : undefined; + const redirectTarget = program.getSourceFile(fileName); + const redirect = find(sourceFilesToSearch, f => !!f.redirectInfo && f.redirectInfo.redirectTarget === redirectTarget)!; + fileName = redirect.fileName; + Debug.assert(sourceFilesSet.has(fileName)); } + return { fileName, highlightSpans }; + }); + } - function getFromAllDeclarations(nodeTest: (node: Node) => node is T, keywords: readonly SyntaxKind[]): HighlightSpan[] | undefined { - return useParent(node.parent, nodeTest, decl => mapDefined(decl.symbol.declarations, d => - nodeTest(d) ? find(d.getChildren(sourceFile), c => contains(keywords, c.kind)) : undefined)); - } + function getSyntacticDocumentHighlights(node: Node, sourceFile: SourceFile): DocumentHighlights[] | undefined { + const highlightSpans = getHighlightSpans(node, sourceFile); + return highlightSpans && [{ fileName: sourceFile.fileName, highlightSpans }]; + } - function useParent(node: Node, nodeTest: (node: Node) => node is T, getNodes: (node: T, sourceFile: SourceFile) => readonly Node[] | undefined): HighlightSpan[] | undefined { - return nodeTest(node) ? highlightSpans(getNodes(node, sourceFile)) : undefined; + function getHighlightSpans(node: Node, sourceFile: SourceFile): HighlightSpan[] | undefined { + switch (node.kind) { + case SyntaxKind.IfKeyword: + case SyntaxKind.ElseKeyword: + return isIfStatement(node.parent) ? getIfElseOccurrences(node.parent, sourceFile) : undefined; + case SyntaxKind.ReturnKeyword: + return useParent(node.parent, isReturnStatement, getReturnOccurrences); + case SyntaxKind.ThrowKeyword: + return useParent(node.parent, isThrowStatement, getThrowOccurrences); + case SyntaxKind.TryKeyword: + case SyntaxKind.CatchKeyword: + case SyntaxKind.FinallyKeyword: + const tryStatement = node.kind === SyntaxKind.CatchKeyword ? node.parent.parent : node.parent; + return useParent(tryStatement, isTryStatement, getTryCatchFinallyOccurrences); + case SyntaxKind.SwitchKeyword: + return useParent(node.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences); + case SyntaxKind.CaseKeyword: + case SyntaxKind.DefaultKeyword: { + if (isDefaultClause(node.parent) || isCaseClause(node.parent)) { + return useParent(node.parent.parent.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences); + } + return undefined; } + case SyntaxKind.BreakKeyword: + case SyntaxKind.ContinueKeyword: + return useParent(node.parent, isBreakOrContinueStatement, getBreakOrContinueStatementOccurrences); + case SyntaxKind.ForKeyword: + case SyntaxKind.WhileKeyword: + case SyntaxKind.DoKeyword: + return useParent(node.parent, (n): n is IterationStatement => isIterationStatement(n, /*lookInLabeledStatements*/ true), getLoopBreakContinueOccurrences); + case SyntaxKind.ConstructorKeyword: + return getFromAllDeclarations(isConstructorDeclaration, [SyntaxKind.ConstructorKeyword]); + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + return getFromAllDeclarations(isAccessor, [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword]); + case SyntaxKind.AwaitKeyword: + return useParent(node.parent, isAwaitExpression, getAsyncAndAwaitOccurrences); + case SyntaxKind.AsyncKeyword: + return highlightSpans(getAsyncAndAwaitOccurrences(node)); + case SyntaxKind.YieldKeyword: + return highlightSpans(getYieldOccurrences(node)); + default: + return isModifierKind(node.kind) && (isDeclaration(node.parent) || isVariableStatement(node.parent)) + ? highlightSpans(getModifierOccurrences(node.kind, node.parent)) + : undefined; + } - function highlightSpans(nodes: readonly Node[] | undefined): HighlightSpan[] | undefined { - return nodes && nodes.map(node => getHighlightSpanForNode(node, sourceFile)); - } + function getFromAllDeclarations(nodeTest: (node: Node) => node is T, keywords: readonly SyntaxKind[]): HighlightSpan[] | undefined { + return useParent(node.parent, nodeTest, decl => mapDefined(decl.symbol.declarations, d => + nodeTest(d) ? find(d.getChildren(sourceFile), c => contains(keywords, c.kind)) : undefined)); } - /** - * Aggregates all throw-statements within this node *without* crossing - * into function boundaries and try-blocks with catch-clauses. - */ - function aggregateOwnedThrowStatements(node: Node): readonly ThrowStatement[] | undefined { - if (isThrowStatement(node)) { - return [node]; - } - else if (isTryStatement(node)) { - // Exceptions thrown within a try block lacking a catch clause are "owned" in the current context. - return concatenate( - node.catchClause ? aggregateOwnedThrowStatements(node.catchClause) : node.tryBlock && aggregateOwnedThrowStatements(node.tryBlock), - node.finallyBlock && aggregateOwnedThrowStatements(node.finallyBlock)); - } - // Do not cross function boundaries. - return isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateOwnedThrowStatements); + function useParent(node: Node, nodeTest: (node: Node) => node is T, getNodes: (node: T, sourceFile: SourceFile) => readonly Node[] | undefined): HighlightSpan[] | undefined { + return nodeTest(node) ? highlightSpans(getNodes(node, sourceFile)) : undefined; } - /** - * For lack of a better name, this function takes a throw statement and returns the - * nearest ancestor that is a try-block (whose try statement has a catch clause), - * function-block, or source file. - */ - function getThrowStatementOwner(throwStatement: ThrowStatement): Node | undefined { - let child: Node = throwStatement; + function highlightSpans(nodes: readonly Node[] | undefined): HighlightSpan[] | undefined { + return nodes && nodes.map(node => getHighlightSpanForNode(node, sourceFile)); + } + } - while (child.parent) { - const parent = child.parent; + /** + * Aggregates all throw-statements within this node *without* crossing + * into function boundaries and try-blocks with catch-clauses. + */ + function aggregateOwnedThrowStatements(node: Node): readonly ThrowStatement[] | undefined { + if (isThrowStatement(node)) { + return [node]; + } + else if (isTryStatement(node)) { + // Exceptions thrown within a try block lacking a catch clause are "owned" in the current context. + return concatenate( + node.catchClause ? aggregateOwnedThrowStatements(node.catchClause) : node.tryBlock && aggregateOwnedThrowStatements(node.tryBlock), + node.finallyBlock && aggregateOwnedThrowStatements(node.finallyBlock)); + } + // Do not cross function boundaries. + return isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateOwnedThrowStatements); + } - if (isFunctionBlock(parent) || parent.kind === SyntaxKind.SourceFile) { - return parent; - } + /** + * For lack of a better name, this function takes a throw statement and returns the + * nearest ancestor that is a try-block (whose try statement has a catch clause), + * function-block, or source file. + */ + function getThrowStatementOwner(throwStatement: ThrowStatement): Node | undefined { + let child: Node = throwStatement; - // A throw-statement is only owned by a try-statement if the try-statement has - // a catch clause, and if the throw-statement occurs within the try block. - if (isTryStatement(parent) && parent.tryBlock === child && parent.catchClause) { - return child; - } + while (child.parent) { + const parent = child.parent; - child = parent; + if (isFunctionBlock(parent) || parent.kind === SyntaxKind.SourceFile) { + return parent; } - return undefined; - } + // A throw-statement is only owned by a try-statement if the try-statement has + // a catch clause, and if the throw-statement occurs within the try block. + if (isTryStatement(parent) && parent.tryBlock === child && parent.catchClause) { + return child; + } - function aggregateAllBreakAndContinueStatements(node: Node): readonly BreakOrContinueStatement[] | undefined { - return isBreakOrContinueStatement(node) ? [node] : isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateAllBreakAndContinueStatements); + child = parent; } - function flatMapChildren(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] { - const result: T[] = []; - node.forEachChild(child => { - const value = cb(child); - if (value !== undefined) { - result.push(...toArray(value)); - } - }); - return result; - } + return undefined; + } - function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean { - const actualOwner = getBreakOrContinueOwner(statement); - return !!actualOwner && actualOwner === owner; - } + function aggregateAllBreakAndContinueStatements(node: Node): readonly BreakOrContinueStatement[] | undefined { + return isBreakOrContinueStatement(node) ? [node] : isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateAllBreakAndContinueStatements); + } - function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node | undefined { - return findAncestor(statement, node => { - switch (node.kind) { - case SyntaxKind.SwitchStatement: - if (statement.kind === SyntaxKind.ContinueStatement) { - return false; - } - // falls through - - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.DoStatement: - return !statement.label || isLabeledBy(node, statement.label.escapedText); - default: - // Don't cross function boundaries. - // TODO: GH#20090 - return isFunctionLike(node) && "quit"; - } - }); - } + function flatMapChildren(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] { + const result: T[] = []; + node.forEachChild(child => { + const value = cb(child); + if (value !== undefined) { + result.push(...toArray(value)); + } + }); + return result; + } - function getModifierOccurrences(modifier: Modifier["kind"], declaration: Node): Node[] { - return mapDefined(getNodesToSearchForModifier(declaration, modifierToFlag(modifier)), node => findModifier(node, modifier)); - } + function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean { + const actualOwner = getBreakOrContinueOwner(statement); + return !!actualOwner && actualOwner === owner; + } - function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): readonly Node[] | undefined { - // Types of node whose children might have modifiers. - const container = declaration.parent as ModuleBlock | SourceFile | Block | CaseClause | DefaultClause | ConstructorDeclaration | MethodDeclaration | FunctionDeclaration | ObjectTypeDeclaration | ObjectLiteralExpression; - switch (container.kind) { - case SyntaxKind.ModuleBlock: - case SyntaxKind.SourceFile: - case SyntaxKind.Block: - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - // Container is either a class declaration or the declaration is a classDeclaration - if (modifierFlag & ModifierFlags.Abstract && isClassDeclaration(declaration)) { - return [...declaration.members, declaration]; - } - else { - return container.statements; - } - case SyntaxKind.Constructor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionDeclaration: - return [...container.parameters, ...(isClassLike(container.parent) ? container.parent.members : [])]; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeLiteral: - const nodes = container.members; - - // If we're an accessibility modifier, we're in an instance member and should search - // the constructor's parameter list for instance members as well. - if (modifierFlag & (ModifierFlags.AccessibilityModifier | ModifierFlags.Readonly)) { - const constructor = find(container.members, isConstructorDeclaration); - if (constructor) { - return [...nodes, ...constructor.parameters]; - } + function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node | undefined { + return findAncestor(statement, node => { + switch (node.kind) { + case SyntaxKind.SwitchStatement: + if (statement.kind === SyntaxKind.ContinueStatement) { + return false; } - else if (modifierFlag & ModifierFlags.Abstract) { - return [...nodes, container]; + // falls through + + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.DoStatement: + return !statement.label || isLabeledBy(node, statement.label.escapedText); + default: + // Don't cross function boundaries. + // TODO: GH#20090 + return isFunctionLike(node) && "quit"; + } + }); + } + + function getModifierOccurrences(modifier: Modifier["kind"], declaration: Node): Node[] { + return mapDefined(getNodesToSearchForModifier(declaration, modifierToFlag(modifier)), node => findModifier(node, modifier)); + } + + function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): readonly Node[] | undefined { + // Types of node whose children might have modifiers. + const container = declaration.parent as ModuleBlock | SourceFile | Block | CaseClause | DefaultClause | ConstructorDeclaration | MethodDeclaration | FunctionDeclaration | ObjectTypeDeclaration | ObjectLiteralExpression; + switch (container.kind) { + case SyntaxKind.ModuleBlock: + case SyntaxKind.SourceFile: + case SyntaxKind.Block: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + // Container is either a class declaration or the declaration is a classDeclaration + if (modifierFlag & ModifierFlags.Abstract && isClassDeclaration(declaration)) { + return [...declaration.members, declaration]; + } + else { + return container.statements; + } + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: + return [...container.parameters, ...(isClassLike(container.parent) ? container.parent.members : [])]; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeLiteral: + const nodes = container.members; + + // If we're an accessibility modifier, we're in an instance member and should search + // the constructor's parameter list for instance members as well. + if (modifierFlag & (ModifierFlags.AccessibilityModifier | ModifierFlags.Readonly)) { + const constructor = find(container.members, isConstructorDeclaration); + if (constructor) { + return [...nodes, ...constructor.parameters]; } - return nodes; + } + else if (modifierFlag & ModifierFlags.Abstract) { + return [...nodes, container]; + } + return nodes; - // Syntactically invalid positions that the parser might produce anyway - case SyntaxKind.ObjectLiteralExpression: - return undefined; + // Syntactically invalid positions that the parser might produce anyway + case SyntaxKind.ObjectLiteralExpression: + return undefined; - default: - Debug.assertNever(container, "Invalid container kind."); - } + default: + Debug.assertNever(container, "Invalid container kind."); } + } - function pushKeywordIf(keywordList: Push, token: Node | undefined, ...expected: SyntaxKind[]): boolean { - if (token && contains(expected, token.kind)) { - keywordList.push(token); - return true; - } - - return false; + function pushKeywordIf(keywordList: Push, token: Node | undefined, ...expected: SyntaxKind[]): boolean { + if (token && contains(expected, token.kind)) { + keywordList.push(token); + return true; } - function getLoopBreakContinueOccurrences(loopNode: IterationStatement): Node[] { - const keywords: Node[] = []; + return false; + } - if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) { - // If we succeeded and got a do-while loop, then start looking for a 'while' keyword. - if (loopNode.kind === SyntaxKind.DoStatement) { - const loopTokens = loopNode.getChildren(); + function getLoopBreakContinueOccurrences(loopNode: IterationStatement): Node[] { + const keywords: Node[] = []; - for (let i = loopTokens.length - 1; i >= 0; i--) { - if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) { - break; - } + if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) { + // If we succeeded and got a do-while loop, then start looking for a 'while' keyword. + if (loopNode.kind === SyntaxKind.DoStatement) { + const loopTokens = loopNode.getChildren(); + + for (let i = loopTokens.length - 1; i >= 0; i--) { + if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) { + break; } } } + } - forEach(aggregateAllBreakAndContinueStatements(loopNode.statement), statement => { - if (ownsBreakOrContinueStatement(loopNode, statement)) { - pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); - } - }); + forEach(aggregateAllBreakAndContinueStatements(loopNode.statement), statement => { + if (ownsBreakOrContinueStatement(loopNode, statement)) { + pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); + } + }); - return keywords; - } + return keywords; + } - function getBreakOrContinueStatementOccurrences(breakOrContinueStatement: BreakOrContinueStatement): Node[] | undefined { - const owner = getBreakOrContinueOwner(breakOrContinueStatement); + function getBreakOrContinueStatementOccurrences(breakOrContinueStatement: BreakOrContinueStatement): Node[] | undefined { + const owner = getBreakOrContinueOwner(breakOrContinueStatement); - if (owner) { - switch (owner.kind) { - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - return getLoopBreakContinueOccurrences(owner as IterationStatement); - case SyntaxKind.SwitchStatement: - return getSwitchCaseDefaultOccurrences(owner as SwitchStatement); + if (owner) { + switch (owner.kind) { + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + return getLoopBreakContinueOccurrences(owner as IterationStatement); + case SyntaxKind.SwitchStatement: + return getSwitchCaseDefaultOccurrences(owner as SwitchStatement); - } } - - return undefined; } - function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement): Node[] { - const keywords: Node[] = []; + return undefined; + } - pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword); + function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement): Node[] { + const keywords: Node[] = []; - // Go through each clause in the switch statement, collecting the 'case'/'default' keywords. - forEach(switchStatement.caseBlock.clauses, clause => { - pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword); + pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword); - forEach(aggregateAllBreakAndContinueStatements(clause), statement => { - if (ownsBreakOrContinueStatement(switchStatement, statement)) { - pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword); - } - }); + // Go through each clause in the switch statement, collecting the 'case'/'default' keywords. + forEach(switchStatement.caseBlock.clauses, clause => { + pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword); + + forEach(aggregateAllBreakAndContinueStatements(clause), statement => { + if (ownsBreakOrContinueStatement(switchStatement, statement)) { + pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword); + } }); + }); - return keywords; - } + return keywords; + } - function getTryCatchFinallyOccurrences(tryStatement: TryStatement, sourceFile: SourceFile): Node[] { - const keywords: Node[] = []; + function getTryCatchFinallyOccurrences(tryStatement: TryStatement, sourceFile: SourceFile): Node[] { + const keywords: Node[] = []; - pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword); + pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword); - if (tryStatement.catchClause) { - pushKeywordIf(keywords, tryStatement.catchClause.getFirstToken(), SyntaxKind.CatchKeyword); - } - - if (tryStatement.finallyBlock) { - const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile)!; - pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword); - } + if (tryStatement.catchClause) { + pushKeywordIf(keywords, tryStatement.catchClause.getFirstToken(), SyntaxKind.CatchKeyword); + } - return keywords; + if (tryStatement.finallyBlock) { + const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile)!; + pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword); } - function getThrowOccurrences(throwStatement: ThrowStatement, sourceFile: SourceFile): Node[] | undefined { - const owner = getThrowStatementOwner(throwStatement); + return keywords; + } + + function getThrowOccurrences(throwStatement: ThrowStatement, sourceFile: SourceFile): Node[] | undefined { + const owner = getThrowStatementOwner(throwStatement); - if (!owner) { - return undefined; - } + if (!owner) { + return undefined; + } - const keywords: Node[] = []; + const keywords: Node[] = []; - forEach(aggregateOwnedThrowStatements(owner), throwStatement => { - keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!); + forEach(aggregateOwnedThrowStatements(owner), throwStatement => { + keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!); + }); + + // If the "owner" is a function, then we equate 'return' and 'throw' statements in their + // ability to "jump out" of the function, and include occurrences for both. + if (isFunctionBlock(owner)) { + forEachReturnStatement(owner as Block, returnStatement => { + keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!); }); + } - // If the "owner" is a function, then we equate 'return' and 'throw' statements in their - // ability to "jump out" of the function, and include occurrences for both. - if (isFunctionBlock(owner)) { - forEachReturnStatement(owner as Block, returnStatement => { - keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!); - }); - } + return keywords; + } - return keywords; + function getReturnOccurrences(returnStatement: ReturnStatement, sourceFile: SourceFile): Node[] | undefined { + const func = getContainingFunction(returnStatement) as FunctionLikeDeclaration; + if (!func) { + return undefined; } - function getReturnOccurrences(returnStatement: ReturnStatement, sourceFile: SourceFile): Node[] | undefined { - const func = getContainingFunction(returnStatement) as FunctionLikeDeclaration; - if (!func) { - return undefined; - } + const keywords: Node[] = []; + forEachReturnStatement(cast(func.body, isBlock), returnStatement => { + keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!); + }); - const keywords: Node[] = []; - forEachReturnStatement(cast(func.body, isBlock), returnStatement => { - keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!); - }); + // Include 'throw' statements that do not occur within a try block. + forEach(aggregateOwnedThrowStatements(func.body!), throwStatement => { + keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!); + }); - // Include 'throw' statements that do not occur within a try block. - forEach(aggregateOwnedThrowStatements(func.body!), throwStatement => { - keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!); - }); + return keywords; + } - return keywords; + function getAsyncAndAwaitOccurrences(node: Node): Node[] | undefined { + const func = getContainingFunction(node) as FunctionLikeDeclaration; + if (!func) { + return undefined; } - function getAsyncAndAwaitOccurrences(node: Node): Node[] | undefined { - const func = getContainingFunction(node) as FunctionLikeDeclaration; - if (!func) { - return undefined; - } - - const keywords: Node[] = []; + const keywords: Node[] = []; - if (func.modifiers) { - func.modifiers.forEach(modifier => { - pushKeywordIf(keywords, modifier, SyntaxKind.AsyncKeyword); - }); - } + if (func.modifiers) { + func.modifiers.forEach(modifier => { + pushKeywordIf(keywords, modifier, SyntaxKind.AsyncKeyword); + }); + } - forEachChild(func, child => { - traverseWithoutCrossingFunction(child, node => { - if (isAwaitExpression(node)) { - pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.AwaitKeyword); - } - }); + forEachChild(func, child => { + traverseWithoutCrossingFunction(child, node => { + if (isAwaitExpression(node)) { + pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.AwaitKeyword); + } }); + }); - return keywords; - } + return keywords; + } - function getYieldOccurrences(node: Node): Node[] | undefined { - const func = getContainingFunction(node) as FunctionDeclaration; - if (!func) { - return undefined; - } + function getYieldOccurrences(node: Node): Node[] | undefined { + const func = getContainingFunction(node) as FunctionDeclaration; + if (!func) { + return undefined; + } - const keywords: Node[] = []; + const keywords: Node[] = []; - forEachChild(func, child => { - traverseWithoutCrossingFunction(child, node => { - if (isYieldExpression(node)) { - pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.YieldKeyword); - } - }); + forEachChild(func, child => { + traverseWithoutCrossingFunction(child, node => { + if (isYieldExpression(node)) { + pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.YieldKeyword); + } }); + }); - return keywords; - } + return keywords; + } - // Do not cross function/class/interface/module/type boundaries. - function traverseWithoutCrossingFunction(node: Node, cb: (node: Node) => void) { - cb(node); - if (!isFunctionLike(node) && !isClassLike(node) && !isInterfaceDeclaration(node) && !isModuleDeclaration(node) && !isTypeAliasDeclaration(node) && !isTypeNode(node)) { - forEachChild(node, child => traverseWithoutCrossingFunction(child, cb)); - } + // Do not cross function/class/interface/module/type boundaries. + function traverseWithoutCrossingFunction(node: Node, cb: (node: Node) => void) { + cb(node); + if (!isFunctionLike(node) && !isClassLike(node) && !isInterfaceDeclaration(node) && !isModuleDeclaration(node) && !isTypeAliasDeclaration(node) && !isTypeNode(node)) { + forEachChild(node, child => traverseWithoutCrossingFunction(child, cb)); } + } - function getIfElseOccurrences(ifStatement: IfStatement, sourceFile: SourceFile): HighlightSpan[] { - const keywords = getIfElseKeywords(ifStatement, sourceFile); - const result: HighlightSpan[] = []; - - // We'd like to highlight else/ifs together if they are only separated by whitespace - // (i.e. the keywords are separated by no comments, no newlines). - for (let i = 0; i < keywords.length; i++) { - if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) { - const elseKeyword = keywords[i]; - const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword. - - let shouldCombineElseAndIf = true; - - // Avoid recalculating getStart() by iterating backwards. - for (let j = ifKeyword.getStart(sourceFile) - 1; j >= elseKeyword.end; j--) { - if (!isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(j))) { - shouldCombineElseAndIf = false; - break; - } - } + function getIfElseOccurrences(ifStatement: IfStatement, sourceFile: SourceFile): HighlightSpan[] { + const keywords = getIfElseKeywords(ifStatement, sourceFile); + const result: HighlightSpan[] = []; + + // We'd like to highlight else/ifs together if they are only separated by whitespace + // (i.e. the keywords are separated by no comments, no newlines). + for (let i = 0; i < keywords.length; i++) { + if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) { + const elseKeyword = keywords[i]; + const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword. + + let shouldCombineElseAndIf = true; - if (shouldCombineElseAndIf) { - result.push({ - fileName: sourceFile.fileName, - textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end), - kind: HighlightSpanKind.reference - }); - i++; // skip the next keyword - continue; + // Avoid recalculating getStart() by iterating backwards. + for (let j = ifKeyword.getStart(sourceFile) - 1; j >= elseKeyword.end; j--) { + if (!isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(j))) { + shouldCombineElseAndIf = false; + break; } } - // Ordinary case: just highlight the keyword. - result.push(getHighlightSpanForNode(keywords[i], sourceFile)); + if (shouldCombineElseAndIf) { + result.push({ + fileName: sourceFile.fileName, + textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end), + kind: HighlightSpanKind.reference + }); + i++; // skip the next keyword + continue; + } } - return result; + // Ordinary case: just highlight the keyword. + result.push(getHighlightSpanForNode(keywords[i], sourceFile)); } - function getIfElseKeywords(ifStatement: IfStatement, sourceFile: SourceFile): Node[] { - const keywords: Node[] = []; + return result; + } - // Traverse upwards through all parent if-statements linked by their else-branches. - while (isIfStatement(ifStatement.parent) && ifStatement.parent.elseStatement === ifStatement) { - ifStatement = ifStatement.parent; - } + function getIfElseKeywords(ifStatement: IfStatement, sourceFile: SourceFile): Node[] { + const keywords: Node[] = []; - // Now traverse back down through the else branches, aggregating if/else keywords of if-statements. - while (true) { - const children = ifStatement.getChildren(sourceFile); - pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword); + // Traverse upwards through all parent if-statements linked by their else-branches. + while (isIfStatement(ifStatement.parent) && ifStatement.parent.elseStatement === ifStatement) { + ifStatement = ifStatement.parent; + } - // Generally the 'else' keyword is second-to-last, so we traverse backwards. - for (let i = children.length - 1; i >= 0; i--) { - if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) { - break; - } - } + // Now traverse back down through the else branches, aggregating if/else keywords of if-statements. + while (true) { + const children = ifStatement.getChildren(sourceFile); + pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword); - if (!ifStatement.elseStatement || !isIfStatement(ifStatement.elseStatement)) { + // Generally the 'else' keyword is second-to-last, so we traverse backwards. + for (let i = children.length - 1; i >= 0; i--) { + if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) { break; } + } - ifStatement = ifStatement.elseStatement; + if (!ifStatement.elseStatement || !isIfStatement(ifStatement.elseStatement)) { + break; } - return keywords; + ifStatement = ifStatement.elseStatement; } - /** - * Whether or not a 'node' is preceded by a label of the given string. - * Note: 'node' cannot be a SourceFile. - */ - function isLabeledBy(node: Node, labelName: __String): boolean { - return !!findAncestor(node.parent, owner => !isLabeledStatement(owner) ? "quit" : owner.label.escapedText === labelName); - } + return keywords; + } + + /** + * Whether or not a 'node' is preceded by a label of the given string. + * Note: 'node' cannot be a SourceFile. + */ + function isLabeledBy(node: Node, labelName: __String): boolean { + return !!findAncestor(node.parent, owner => !isLabeledStatement(owner) ? "quit" : owner.label.escapedText === labelName); } } +} diff --git a/src/services/documentRegistry.ts b/src/services/documentRegistry.ts index 719f219025929..c5edd6fcac54a 100644 --- a/src/services/documentRegistry.ts +++ b/src/services/documentRegistry.ts @@ -1,323 +1,323 @@ namespace ts { +/** + * The document registry represents a store of SourceFile objects that can be shared between + * multiple LanguageService instances. A LanguageService instance holds on the SourceFile (AST) + * of files in the context. + * SourceFile objects account for most of the memory usage by the language service. Sharing + * the same DocumentRegistry instance between different instances of LanguageService allow + * for more efficient memory utilization since all projects will share at least the library + * file (lib.d.ts). + * + * A more advanced use of the document registry is to serialize sourceFile objects to disk + * and re-hydrate them when needed. + * + * To create a default DocumentRegistry, use createDocumentRegistry to create one, and pass it + * to all subsequent createLanguageService calls. + */ +export interface DocumentRegistry { /** - * The document registry represents a store of SourceFile objects that can be shared between - * multiple LanguageService instances. A LanguageService instance holds on the SourceFile (AST) - * of files in the context. - * SourceFile objects account for most of the memory usage by the language service. Sharing - * the same DocumentRegistry instance between different instances of LanguageService allow - * for more efficient memory utilization since all projects will share at least the library - * file (lib.d.ts). + * Request a stored SourceFile with a given fileName and compilationSettings. + * The first call to acquire will call createLanguageServiceSourceFile to generate + * the SourceFile if was not found in the registry. * - * A more advanced use of the document registry is to serialize sourceFile objects to disk - * and re-hydrate them when needed. - * - * To create a default DocumentRegistry, use createDocumentRegistry to create one, and pass it - * to all subsequent createLanguageService calls. + * @param fileName The name of the file requested + * @param compilationSettings Some compilation settings like target affects the + * shape of a the resulting SourceFile. This allows the DocumentRegistry to store + * multiple copies of the same file for different compilation settings. + * @param scriptSnapshot Text of the file. Only used if the file was not found + * in the registry and a new one was created. + * @param version Current version of the file. Only used if the file was not found + * in the registry and a new one was created. */ - export interface DocumentRegistry { - /** - * Request a stored SourceFile with a given fileName and compilationSettings. - * The first call to acquire will call createLanguageServiceSourceFile to generate - * the SourceFile if was not found in the registry. - * - * @param fileName The name of the file requested - * @param compilationSettings Some compilation settings like target affects the - * shape of a the resulting SourceFile. This allows the DocumentRegistry to store - * multiple copies of the same file for different compilation settings. - * @param scriptSnapshot Text of the file. Only used if the file was not found - * in the registry and a new one was created. - * @param version Current version of the file. Only used if the file was not found - * in the registry and a new one was created. - */ - acquireDocument( - fileName: string, - compilationSettings: CompilerOptions, - scriptSnapshot: IScriptSnapshot, - version: string, - scriptKind?: ScriptKind): SourceFile; + acquireDocument( + fileName: string, + compilationSettings: CompilerOptions, + scriptSnapshot: IScriptSnapshot, + version: string, + scriptKind?: ScriptKind): SourceFile; - acquireDocumentWithKey( - fileName: string, - path: Path, - compilationSettings: CompilerOptions, - key: DocumentRegistryBucketKey, - scriptSnapshot: IScriptSnapshot, - version: string, - scriptKind?: ScriptKind): SourceFile; + acquireDocumentWithKey( + fileName: string, + path: Path, + compilationSettings: CompilerOptions, + key: DocumentRegistryBucketKey, + scriptSnapshot: IScriptSnapshot, + version: string, + scriptKind?: ScriptKind): SourceFile; - /** - * Request an updated version of an already existing SourceFile with a given fileName - * and compilationSettings. The update will in-turn call updateLanguageServiceSourceFile - * to get an updated SourceFile. - * - * @param fileName The name of the file requested - * @param compilationSettings Some compilation settings like target affects the - * shape of a the resulting SourceFile. This allows the DocumentRegistry to store - * multiple copies of the same file for different compilation settings. - * @param scriptSnapshot Text of the file. - * @param version Current version of the file. - */ - updateDocument( - fileName: string, - compilationSettings: CompilerOptions, - scriptSnapshot: IScriptSnapshot, - version: string, - scriptKind?: ScriptKind): SourceFile; + /** + * Request an updated version of an already existing SourceFile with a given fileName + * and compilationSettings. The update will in-turn call updateLanguageServiceSourceFile + * to get an updated SourceFile. + * + * @param fileName The name of the file requested + * @param compilationSettings Some compilation settings like target affects the + * shape of a the resulting SourceFile. This allows the DocumentRegistry to store + * multiple copies of the same file for different compilation settings. + * @param scriptSnapshot Text of the file. + * @param version Current version of the file. + */ + updateDocument( + fileName: string, + compilationSettings: CompilerOptions, + scriptSnapshot: IScriptSnapshot, + version: string, + scriptKind?: ScriptKind): SourceFile; - updateDocumentWithKey( - fileName: string, - path: Path, - compilationSettings: CompilerOptions, - key: DocumentRegistryBucketKey, - scriptSnapshot: IScriptSnapshot, - version: string, - scriptKind?: ScriptKind): SourceFile; + updateDocumentWithKey( + fileName: string, + path: Path, + compilationSettings: CompilerOptions, + key: DocumentRegistryBucketKey, + scriptSnapshot: IScriptSnapshot, + version: string, + scriptKind?: ScriptKind): SourceFile; - getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey; - /** - * Informs the DocumentRegistry that a file is not needed any longer. - * - * Note: It is not allowed to call release on a SourceFile that was not acquired from - * this registry originally. - * - * @param fileName The name of the file to be released - * @param compilationSettings The compilation settings used to acquire the file - */ - /**@deprecated pass scriptKind for correctness */ - releaseDocument(fileName: string, compilationSettings: CompilerOptions): void; - /** - * Informs the DocumentRegistry that a file is not needed any longer. - * - * Note: It is not allowed to call release on a SourceFile that was not acquired from - * this registry originally. - * - * @param fileName The name of the file to be released - * @param compilationSettings The compilation settings used to acquire the file - * @param scriptKind The script kind of the file to be released - */ - releaseDocument(fileName: string, compilationSettings: CompilerOptions, scriptKind: ScriptKind): void; // eslint-disable-line @typescript-eslint/unified-signatures - /** - * @deprecated pass scriptKind for correctness */ - releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void; - releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey, scriptKind: ScriptKind): void; // eslint-disable-line @typescript-eslint/unified-signatures + getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey; + /** + * Informs the DocumentRegistry that a file is not needed any longer. + * + * Note: It is not allowed to call release on a SourceFile that was not acquired from + * this registry originally. + * + * @param fileName The name of the file to be released + * @param compilationSettings The compilation settings used to acquire the file + */ + /**@deprecated pass scriptKind for correctness */ + releaseDocument(fileName: string, compilationSettings: CompilerOptions): void; + /** + * Informs the DocumentRegistry that a file is not needed any longer. + * + * Note: It is not allowed to call release on a SourceFile that was not acquired from + * this registry originally. + * + * @param fileName The name of the file to be released + * @param compilationSettings The compilation settings used to acquire the file + * @param scriptKind The script kind of the file to be released + */ + releaseDocument(fileName: string, compilationSettings: CompilerOptions, scriptKind: ScriptKind): void; // eslint-disable-line @typescript-eslint/unified-signatures + /** + * @deprecated pass scriptKind for correctness */ + releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void; + releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey, scriptKind: ScriptKind): void; // eslint-disable-line @typescript-eslint/unified-signatures - /*@internal*/ - getLanguageServiceRefCounts(path: Path, scriptKind: ScriptKind): [string, number | undefined][]; + /*@internal*/ + getLanguageServiceRefCounts(path: Path, scriptKind: ScriptKind): [string, number | undefined][]; - reportStats(): string; - } + reportStats(): string; +} - /*@internal*/ - export interface ExternalDocumentCache { - setDocument(key: DocumentRegistryBucketKey, path: Path, sourceFile: SourceFile): void; - getDocument(key: DocumentRegistryBucketKey, path: Path): SourceFile | undefined; - } +/*@internal*/ +export interface ExternalDocumentCache { + setDocument(key: DocumentRegistryBucketKey, path: Path, sourceFile: SourceFile): void; + getDocument(key: DocumentRegistryBucketKey, path: Path): SourceFile | undefined; +} - export type DocumentRegistryBucketKey = string & { __bucketKey: any }; +export type DocumentRegistryBucketKey = string & { __bucketKey: any }; - interface DocumentRegistryEntry { - sourceFile: SourceFile; +interface DocumentRegistryEntry { + sourceFile: SourceFile; - // The number of language services that this source file is referenced in. When no more - // language services are referencing the file, then the file can be removed from the - // registry. - languageServiceRefCount: number; - } + // The number of language services that this source file is referenced in. When no more + // language services are referencing the file, then the file can be removed from the + // registry. + languageServiceRefCount: number; +} - type BucketEntry = DocumentRegistryEntry | ESMap; - function isDocumentRegistryEntry(entry: BucketEntry): entry is DocumentRegistryEntry { - return !!(entry as DocumentRegistryEntry).sourceFile; - } +type BucketEntry = DocumentRegistryEntry | ESMap; +function isDocumentRegistryEntry(entry: BucketEntry): entry is DocumentRegistryEntry { + return !!(entry as DocumentRegistryEntry).sourceFile; +} - export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory?: string): DocumentRegistry { - return createDocumentRegistryInternal(useCaseSensitiveFileNames, currentDirectory); - } +export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory?: string): DocumentRegistry { + return createDocumentRegistryInternal(useCaseSensitiveFileNames, currentDirectory); +} - /*@internal*/ - export function createDocumentRegistryInternal(useCaseSensitiveFileNames?: boolean, currentDirectory = "", externalCache?: ExternalDocumentCache): DocumentRegistry { - // Maps from compiler setting target (ES3, ES5, etc.) to all the cached documents we have - // for those settings. - const buckets = new Map>(); - const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames); +/*@internal*/ +export function createDocumentRegistryInternal(useCaseSensitiveFileNames?: boolean, currentDirectory = "", externalCache?: ExternalDocumentCache): DocumentRegistry { + // Maps from compiler setting target (ES3, ES5, etc.) to all the cached documents we have + // for those settings. + const buckets = new Map>(); + const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames); - function reportStats() { - const bucketInfoArray = arrayFrom(buckets.keys()).filter(name => name && name.charAt(0) === "_").map(name => { - const entries = buckets.get(name)!; - const sourceFiles: { name: string; scriptKind: ScriptKind, refCount: number; }[] = []; - entries.forEach((entry, name) => { - if (isDocumentRegistryEntry(entry)) { - sourceFiles.push({ - name, - scriptKind: entry.sourceFile.scriptKind, - refCount: entry.languageServiceRefCount - }); - } - else { - entry.forEach((value, scriptKind) => sourceFiles.push({ name, scriptKind, refCount: value.languageServiceRefCount })); - } - }); - sourceFiles.sort((x, y) => y.refCount - x.refCount); - return { - bucket: name, - sourceFiles - }; + function reportStats() { + const bucketInfoArray = arrayFrom(buckets.keys()).filter(name => name && name.charAt(0) === "_").map(name => { + const entries = buckets.get(name)!; + const sourceFiles: { name: string; scriptKind: ScriptKind, refCount: number; }[] = []; + entries.forEach((entry, name) => { + if (isDocumentRegistryEntry(entry)) { + sourceFiles.push({ + name, + scriptKind: entry.sourceFile.scriptKind, + refCount: entry.languageServiceRefCount + }); + } + else { + entry.forEach((value, scriptKind) => sourceFiles.push({ name, scriptKind, refCount: value.languageServiceRefCount })); + } }); - return JSON.stringify(bucketInfoArray, undefined, 2); - } - - function acquireDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const key = getKeyForCompilationSettings(compilationSettings); - return acquireDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind); - } + sourceFiles.sort((x, y) => y.refCount - x.refCount); + return { + bucket: name, + sourceFiles + }; + }); + return JSON.stringify(bucketInfoArray, undefined, 2); + } - function acquireDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { - return acquireOrUpdateDocument(fileName, path, compilationSettings, key, scriptSnapshot, version, /*acquiring*/ true, scriptKind); - } + function acquireDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const key = getKeyForCompilationSettings(compilationSettings); + return acquireDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind); + } - function updateDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const key = getKeyForCompilationSettings(compilationSettings); - return updateDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind); - } + function acquireDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { + return acquireOrUpdateDocument(fileName, path, compilationSettings, key, scriptSnapshot, version, /*acquiring*/ true, scriptKind); + } - function updateDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { - return acquireOrUpdateDocument(fileName, path, compilationSettings, key, scriptSnapshot, version, /*acquiring*/ false, scriptKind); - } + function updateDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const key = getKeyForCompilationSettings(compilationSettings); + return updateDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind); + } - function getDocumentRegistryEntry(bucketEntry: BucketEntry, scriptKind: ScriptKind | undefined) { - const entry = isDocumentRegistryEntry(bucketEntry) ? bucketEntry : bucketEntry.get(Debug.checkDefined(scriptKind, "If there are more than one scriptKind's for same document the scriptKind should be provided")); - Debug.assert(scriptKind === undefined || !entry || entry.sourceFile.scriptKind === scriptKind, `Script kind should match provided ScriptKind:${scriptKind} and sourceFile.scriptKind: ${entry?.sourceFile.scriptKind}, !entry: ${!entry}`); - return entry; - } + function updateDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { + return acquireOrUpdateDocument(fileName, path, compilationSettings, key, scriptSnapshot, version, /*acquiring*/ false, scriptKind); + } - function acquireOrUpdateDocument( - fileName: string, - path: Path, - compilationSettings: CompilerOptions, - key: DocumentRegistryBucketKey, - scriptSnapshot: IScriptSnapshot, - version: string, - acquiring: boolean, - scriptKind?: ScriptKind): SourceFile { - scriptKind = ensureScriptKind(fileName, scriptKind); - const scriptTarget = scriptKind === ScriptKind.JSON ? ScriptTarget.JSON : getEmitScriptTarget(compilationSettings); - const bucket = getOrUpdate(buckets, key, () => new Map()); - const bucketEntry = bucket.get(path); - let entry = bucketEntry && getDocumentRegistryEntry(bucketEntry, scriptKind); - if (!entry && externalCache) { - const sourceFile = externalCache.getDocument(key, path); - if (sourceFile) { - Debug.assert(acquiring); - entry = { - sourceFile, - languageServiceRefCount: 0 - }; - setBucketEntry(); - } - } + function getDocumentRegistryEntry(bucketEntry: BucketEntry, scriptKind: ScriptKind | undefined) { + const entry = isDocumentRegistryEntry(bucketEntry) ? bucketEntry : bucketEntry.get(Debug.checkDefined(scriptKind, "If there are more than one scriptKind's for same document the scriptKind should be provided")); + Debug.assert(scriptKind === undefined || !entry || entry.sourceFile.scriptKind === scriptKind, `Script kind should match provided ScriptKind:${scriptKind} and sourceFile.scriptKind: ${entry?.sourceFile.scriptKind}, !entry: ${!entry}`); + return entry; + } - if (!entry) { - // Have never seen this file with these settings. Create a new source file for it. - const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, scriptTarget, version, /*setNodeParents*/ false, scriptKind); - if (externalCache) { - externalCache.setDocument(key, path, sourceFile); - } + function acquireOrUpdateDocument( + fileName: string, + path: Path, + compilationSettings: CompilerOptions, + key: DocumentRegistryBucketKey, + scriptSnapshot: IScriptSnapshot, + version: string, + acquiring: boolean, + scriptKind?: ScriptKind): SourceFile { + scriptKind = ensureScriptKind(fileName, scriptKind); + const scriptTarget = scriptKind === ScriptKind.JSON ? ScriptTarget.JSON : getEmitScriptTarget(compilationSettings); + const bucket = getOrUpdate(buckets, key, () => new Map()); + const bucketEntry = bucket.get(path); + let entry = bucketEntry && getDocumentRegistryEntry(bucketEntry, scriptKind); + if (!entry && externalCache) { + const sourceFile = externalCache.getDocument(key, path); + if (sourceFile) { + Debug.assert(acquiring); entry = { sourceFile, - languageServiceRefCount: 1, + languageServiceRefCount: 0 }; setBucketEntry(); } - else { - // We have an entry for this file. However, it may be for a different version of - // the script snapshot. If so, update it appropriately. Otherwise, we can just - // return it as is. - if (entry.sourceFile.version !== version) { - entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, - scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot!)); // TODO: GH#18217 - if (externalCache) { - externalCache.setDocument(key, path, entry.sourceFile); - } - } + } - // If we're acquiring, then this is the first time this LS is asking for this document. - // Increase our ref count so we know there's another LS using the document. If we're - // not acquiring, then that means the LS is 'updating' the file instead, and that means - // it has already acquired the document previously. As such, we do not need to increase - // the ref count. - if (acquiring) { - entry.languageServiceRefCount++; + if (!entry) { + // Have never seen this file with these settings. Create a new source file for it. + const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, scriptTarget, version, /*setNodeParents*/ false, scriptKind); + if (externalCache) { + externalCache.setDocument(key, path, sourceFile); + } + entry = { + sourceFile, + languageServiceRefCount: 1, + }; + setBucketEntry(); + } + else { + // We have an entry for this file. However, it may be for a different version of + // the script snapshot. If so, update it appropriately. Otherwise, we can just + // return it as is. + if (entry.sourceFile.version !== version) { + entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, + scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot!)); // TODO: GH#18217 + if (externalCache) { + externalCache.setDocument(key, path, entry.sourceFile); } } - Debug.assert(entry.languageServiceRefCount !== 0); - - return entry.sourceFile; - function setBucketEntry() { - if (!bucketEntry) { - bucket.set(path, entry!); - } - else if (isDocumentRegistryEntry(bucketEntry)) { - const scriptKindMap = new Map(); - scriptKindMap.set(bucketEntry.sourceFile.scriptKind, bucketEntry); - scriptKindMap.set(scriptKind!, entry!); - bucket.set(path, scriptKindMap); - } - else { - bucketEntry.set(scriptKind!, entry!); - } + // If we're acquiring, then this is the first time this LS is asking for this document. + // Increase our ref count so we know there's another LS using the document. If we're + // not acquiring, then that means the LS is 'updating' the file instead, and that means + // it has already acquired the document previously. As such, we do not need to increase + // the ref count. + if (acquiring) { + entry.languageServiceRefCount++; } } + Debug.assert(entry.languageServiceRefCount !== 0); + + return entry.sourceFile; - function releaseDocument(fileName: string, compilationSettings: CompilerOptions, scriptKind?: ScriptKind): void { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const key = getKeyForCompilationSettings(compilationSettings); - return releaseDocumentWithKey(path, key, scriptKind); + function setBucketEntry() { + if (!bucketEntry) { + bucket.set(path, entry!); + } + else if (isDocumentRegistryEntry(bucketEntry)) { + const scriptKindMap = new Map(); + scriptKindMap.set(bucketEntry.sourceFile.scriptKind, bucketEntry); + scriptKindMap.set(scriptKind!, entry!); + bucket.set(path, scriptKindMap); + } + else { + bucketEntry.set(scriptKind!, entry!); + } } + } - function releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey, scriptKind?: ScriptKind): void { - const bucket = Debug.checkDefined(buckets.get(key)); - const bucketEntry = bucket.get(path)!; - const entry = getDocumentRegistryEntry(bucketEntry, scriptKind)!; - entry.languageServiceRefCount--; + function releaseDocument(fileName: string, compilationSettings: CompilerOptions, scriptKind?: ScriptKind): void { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const key = getKeyForCompilationSettings(compilationSettings); + return releaseDocumentWithKey(path, key, scriptKind); + } - Debug.assert(entry.languageServiceRefCount >= 0); - if (entry.languageServiceRefCount === 0) { - if (isDocumentRegistryEntry(bucketEntry)) { - bucket.delete(path); - } - else { - bucketEntry.delete(scriptKind!); - if (bucketEntry.size === 1) { - bucket.set(path, firstDefinedIterator(bucketEntry.values(), identity)!); - } + function releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey, scriptKind?: ScriptKind): void { + const bucket = Debug.checkDefined(buckets.get(key)); + const bucketEntry = bucket.get(path)!; + const entry = getDocumentRegistryEntry(bucketEntry, scriptKind)!; + entry.languageServiceRefCount--; + + Debug.assert(entry.languageServiceRefCount >= 0); + if (entry.languageServiceRefCount === 0) { + if (isDocumentRegistryEntry(bucketEntry)) { + bucket.delete(path); + } + else { + bucketEntry.delete(scriptKind!); + if (bucketEntry.size === 1) { + bucket.set(path, firstDefinedIterator(bucketEntry.values(), identity)!); } } } - - function getLanguageServiceRefCounts(path: Path, scriptKind: ScriptKind) { - return arrayFrom(buckets.entries(), ([key, bucket]): [string, number | undefined] => { - const bucketEntry = bucket.get(path); - const entry = bucketEntry && getDocumentRegistryEntry(bucketEntry, scriptKind); - return [key, entry && entry.languageServiceRefCount]; - }); - } - - return { - acquireDocument, - acquireDocumentWithKey, - updateDocument, - updateDocumentWithKey, - releaseDocument, - releaseDocumentWithKey, - getLanguageServiceRefCounts, - reportStats, - getKeyForCompilationSettings - }; } - function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey { - return sourceFileAffectingCompilerOptions.map(option => getCompilerOptionValue(settings, option)).join("|") as DocumentRegistryBucketKey; + function getLanguageServiceRefCounts(path: Path, scriptKind: ScriptKind) { + return arrayFrom(buckets.entries(), ([key, bucket]): [string, number | undefined] => { + const bucketEntry = bucket.get(path); + const entry = bucketEntry && getDocumentRegistryEntry(bucketEntry, scriptKind); + return [key, entry && entry.languageServiceRefCount]; + }); } + + return { + acquireDocument, + acquireDocumentWithKey, + updateDocument, + updateDocumentWithKey, + releaseDocument, + releaseDocumentWithKey, + getLanguageServiceRefCounts, + reportStats, + getKeyForCompilationSettings + }; +} + +function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey { + return sourceFileAffectingCompilerOptions.map(option => getCompilerOptionValue(settings, option)).join("|") as DocumentRegistryBucketKey; +} } diff --git a/src/services/exportAsModule.ts b/src/services/exportAsModule.ts index 6760e9c38c2d0..e98239c87c3c3 100644 --- a/src/services/exportAsModule.ts +++ b/src/services/exportAsModule.ts @@ -4,4 +4,4 @@ /* @internal */ declare const module: { exports: {} }; if (typeof module !== "undefined" && module.exports) { module.exports = ts; -} \ No newline at end of file +} diff --git a/src/services/exportInfoMap.ts b/src/services/exportInfoMap.ts index 2a43faf326965..8a7830f04ca5b 100644 --- a/src/services/exportInfoMap.ts +++ b/src/services/exportInfoMap.ts @@ -1,420 +1,420 @@ /*@internal*/ namespace ts { - export const enum ImportKind { - Named, - Default, - Namespace, - CommonJS, - } +export const enum ImportKind { + Named, + Default, + Namespace, + CommonJS, +} - export const enum ExportKind { - Named, - Default, - ExportEquals, - UMD, - } +export const enum ExportKind { + Named, + Default, + ExportEquals, + UMD, +} - export interface SymbolExportInfo { - readonly symbol: Symbol; - readonly moduleSymbol: Symbol; - /** Set if `moduleSymbol` is an external module, not an ambient module */ - moduleFileName: string | undefined; - exportKind: ExportKind; - targetFlags: SymbolFlags; - /** True if export was only found via the package.json AutoImportProvider (for telemetry). */ - isFromPackageJson: boolean; - } +export interface SymbolExportInfo { + readonly symbol: Symbol; + readonly moduleSymbol: Symbol; + /** Set if `moduleSymbol` is an external module, not an ambient module */ + moduleFileName: string | undefined; + exportKind: ExportKind; + targetFlags: SymbolFlags; + /** True if export was only found via the package.json AutoImportProvider (for telemetry). */ + isFromPackageJson: boolean; +} - interface CachedSymbolExportInfo { - // Used to rehydrate `symbol` and `moduleSymbol` when transient - id: number; - symbolName: string; - symbolTableKey: __String; - moduleName: string; - moduleFile: SourceFile | undefined; - - // SymbolExportInfo, but optional symbols - readonly symbol: Symbol | undefined; - readonly moduleSymbol: Symbol | undefined; - moduleFileName: string | undefined; - exportKind: ExportKind; - targetFlags: SymbolFlags; - isFromPackageJson: boolean; - } +interface CachedSymbolExportInfo { + // Used to rehydrate `symbol` and `moduleSymbol` when transient + id: number; + symbolName: string; + symbolTableKey: __String; + moduleName: string; + moduleFile: SourceFile | undefined; + + // SymbolExportInfo, but optional symbols + readonly symbol: Symbol | undefined; + readonly moduleSymbol: Symbol | undefined; + moduleFileName: string | undefined; + exportKind: ExportKind; + targetFlags: SymbolFlags; + isFromPackageJson: boolean; +} - export interface ExportInfoMap { - isUsableByFile(importingFile: Path): boolean; - clear(): void; - add(importingFile: Path, symbol: Symbol, key: __String, moduleSymbol: Symbol, moduleFile: SourceFile | undefined, exportKind: ExportKind, isFromPackageJson: boolean, scriptTarget: ScriptTarget, checker: TypeChecker): void; - get(importingFile: Path, key: string): readonly SymbolExportInfo[] | undefined; - forEach(importingFile: Path, action: (info: readonly SymbolExportInfo[], name: string, isFromAmbientModule: boolean, key: string) => void): void; - releaseSymbols(): void; - isEmpty(): boolean; - /** @returns Whether the change resulted in the cache being cleared */ - onFileChanged(oldSourceFile: SourceFile, newSourceFile: SourceFile, typeAcquisitionEnabled: boolean): boolean; - } +export interface ExportInfoMap { + isUsableByFile(importingFile: Path): boolean; + clear(): void; + add(importingFile: Path, symbol: Symbol, key: __String, moduleSymbol: Symbol, moduleFile: SourceFile | undefined, exportKind: ExportKind, isFromPackageJson: boolean, scriptTarget: ScriptTarget, checker: TypeChecker): void; + get(importingFile: Path, key: string): readonly SymbolExportInfo[] | undefined; + forEach(importingFile: Path, action: (info: readonly SymbolExportInfo[], name: string, isFromAmbientModule: boolean, key: string) => void): void; + releaseSymbols(): void; + isEmpty(): boolean; + /** @returns Whether the change resulted in the cache being cleared */ + onFileChanged(oldSourceFile: SourceFile, newSourceFile: SourceFile, typeAcquisitionEnabled: boolean): boolean; +} - export interface CacheableExportInfoMapHost { - getCurrentProgram(): Program | undefined; - getPackageJsonAutoImportProvider(): Program | undefined; - } +export interface CacheableExportInfoMapHost { + getCurrentProgram(): Program | undefined; + getPackageJsonAutoImportProvider(): Program | undefined; +} - export function createCacheableExportInfoMap(host: CacheableExportInfoMapHost): ExportInfoMap { - let exportInfoId = 1; - const exportInfo = createMultiMap(); - const symbols = new Map(); - let usableByFileName: Path | undefined; - const cache: ExportInfoMap = { - isUsableByFile: importingFile => importingFile === usableByFileName, - isEmpty: () => !exportInfo.size, - clear: () => { - exportInfo.clear(); - symbols.clear(); - usableByFileName = undefined; - }, - add: (importingFile, symbol, symbolTableKey, moduleSymbol, moduleFile, exportKind, isFromPackageJson, scriptTarget, checker) => { - if (importingFile !== usableByFileName) { - cache.clear(); - usableByFileName = importingFile; - } - const isDefault = exportKind === ExportKind.Default; - const namedSymbol = isDefault && getLocalSymbolForExportDefault(symbol) || symbol; - // 1. A named export must be imported by its key in `moduleSymbol.exports` or `moduleSymbol.members`. - // 2. A re-export merged with an export from a module augmentation can result in `symbol` - // being an external module symbol; the name it is re-exported by will be `symbolTableKey` - // (which comes from the keys of `moduleSymbol.exports`.) - // 3. Otherwise, we have a default/namespace import that can be imported by any name, and - // `symbolTableKey` will be something undesirable like `export=` or `default`, so we try to - // get a better name. - const importedName = exportKind === ExportKind.Named || isExternalModuleSymbol(namedSymbol) - ? unescapeLeadingUnderscores(symbolTableKey) - : getNameForExportedSymbol(namedSymbol, scriptTarget); - const moduleName = stripQuotes(moduleSymbol.name); - const id = exportInfoId++; - const target = skipAlias(symbol, checker); - const storedSymbol = symbol.flags & SymbolFlags.Transient ? undefined : symbol; - const storedModuleSymbol = moduleSymbol.flags & SymbolFlags.Transient ? undefined : moduleSymbol; - if (!storedSymbol || !storedModuleSymbol) symbols.set(id, [symbol, moduleSymbol]); - - exportInfo.add(key(importedName, symbol, isExternalModuleNameRelative(moduleName) ? undefined : moduleName, checker), { - id, - symbolTableKey, - symbolName: importedName, - moduleName, - moduleFile, - moduleFileName: moduleFile?.fileName, - exportKind, - targetFlags: target.flags, - isFromPackageJson, - symbol: storedSymbol, - moduleSymbol: storedModuleSymbol, - }); - }, - get: (importingFile, key) => { - if (importingFile !== usableByFileName) return; - const result = exportInfo.get(key); - return result?.map(rehydrateCachedInfo); - }, - forEach: (importingFile, action) => { - if (importingFile !== usableByFileName) return; - exportInfo.forEach((info, key) => { - const { symbolName, ambientModuleName } = parseKey(key); - action(info.map(rehydrateCachedInfo), symbolName, !!ambientModuleName, key); - }); - }, - releaseSymbols: () => { - symbols.clear(); - }, - onFileChanged: (oldSourceFile: SourceFile, newSourceFile: SourceFile, typeAcquisitionEnabled: boolean) => { - if (fileIsGlobalOnly(oldSourceFile) && fileIsGlobalOnly(newSourceFile)) { - // File is purely global; doesn't affect export map - return false; - } - if ( - usableByFileName && usableByFileName !== newSourceFile.path || - // If ATA is enabled, auto-imports uses existing imports to guess whether you want auto-imports from node. - // Adding or removing imports from node could change the outcome of that guess, so could change the suggestions list. - typeAcquisitionEnabled && consumesNodeCoreModules(oldSourceFile) !== consumesNodeCoreModules(newSourceFile) || - // Module agumentation and ambient module changes can add or remove exports available to be auto-imported. - // Changes elsewhere in the file can change the *type* of an export in a module augmentation, - // but type info is gathered in getCompletionEntryDetails, which doesn’t use the cache. - !arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations) || - !ambientModuleDeclarationsAreEqual(oldSourceFile, newSourceFile) - ) { - cache.clear(); - return true; - } - usableByFileName = newSourceFile.path; +export function createCacheableExportInfoMap(host: CacheableExportInfoMapHost): ExportInfoMap { + let exportInfoId = 1; + const exportInfo = createMultiMap(); + const symbols = new Map(); + let usableByFileName: Path | undefined; + const cache: ExportInfoMap = { + isUsableByFile: importingFile => importingFile === usableByFileName, + isEmpty: () => !exportInfo.size, + clear: () => { + exportInfo.clear(); + symbols.clear(); + usableByFileName = undefined; + }, + add: (importingFile, symbol, symbolTableKey, moduleSymbol, moduleFile, exportKind, isFromPackageJson, scriptTarget, checker) => { + if (importingFile !== usableByFileName) { + cache.clear(); + usableByFileName = importingFile; + } + const isDefault = exportKind === ExportKind.Default; + const namedSymbol = isDefault && getLocalSymbolForExportDefault(symbol) || symbol; + // 1. A named export must be imported by its key in `moduleSymbol.exports` or `moduleSymbol.members`. + // 2. A re-export merged with an export from a module augmentation can result in `symbol` + // being an external module symbol; the name it is re-exported by will be `symbolTableKey` + // (which comes from the keys of `moduleSymbol.exports`.) + // 3. Otherwise, we have a default/namespace import that can be imported by any name, and + // `symbolTableKey` will be something undesirable like `export=` or `default`, so we try to + // get a better name. + const importedName = exportKind === ExportKind.Named || isExternalModuleSymbol(namedSymbol) + ? unescapeLeadingUnderscores(symbolTableKey) + : getNameForExportedSymbol(namedSymbol, scriptTarget); + const moduleName = stripQuotes(moduleSymbol.name); + const id = exportInfoId++; + const target = skipAlias(symbol, checker); + const storedSymbol = symbol.flags & SymbolFlags.Transient ? undefined : symbol; + const storedModuleSymbol = moduleSymbol.flags & SymbolFlags.Transient ? undefined : moduleSymbol; + if (!storedSymbol || !storedModuleSymbol) symbols.set(id, [symbol, moduleSymbol]); + + exportInfo.add(key(importedName, symbol, isExternalModuleNameRelative(moduleName) ? undefined : moduleName, checker), { + id, + symbolTableKey, + symbolName: importedName, + moduleName, + moduleFile, + moduleFileName: moduleFile?.fileName, + exportKind, + targetFlags: target.flags, + isFromPackageJson, + symbol: storedSymbol, + moduleSymbol: storedModuleSymbol, + }); + }, + get: (importingFile, key) => { + if (importingFile !== usableByFileName) return; + const result = exportInfo.get(key); + return result?.map(rehydrateCachedInfo); + }, + forEach: (importingFile, action) => { + if (importingFile !== usableByFileName) return; + exportInfo.forEach((info, key) => { + const { symbolName, ambientModuleName } = parseKey(key); + action(info.map(rehydrateCachedInfo), symbolName, !!ambientModuleName, key); + }); + }, + releaseSymbols: () => { + symbols.clear(); + }, + onFileChanged: (oldSourceFile: SourceFile, newSourceFile: SourceFile, typeAcquisitionEnabled: boolean) => { + if (fileIsGlobalOnly(oldSourceFile) && fileIsGlobalOnly(newSourceFile)) { + // File is purely global; doesn't affect export map return false; - }, - }; - if (Debug.isDebugging) { - Object.defineProperty(cache, "__cache", { get: () => exportInfo }); - } - return cache; - - function rehydrateCachedInfo(info: CachedSymbolExportInfo): SymbolExportInfo { - if (info.symbol && info.moduleSymbol) return info as SymbolExportInfo; - const { id, exportKind, targetFlags, isFromPackageJson, moduleFileName } = info; - const [cachedSymbol, cachedModuleSymbol] = symbols.get(id) || emptyArray; - if (cachedSymbol && cachedModuleSymbol) { - return { - symbol: cachedSymbol, - moduleSymbol: cachedModuleSymbol, - moduleFileName, - exportKind, - targetFlags, - isFromPackageJson, - }; } - const checker = (isFromPackageJson - ? host.getPackageJsonAutoImportProvider()! - : host.getCurrentProgram()!).getTypeChecker(); - const moduleSymbol = info.moduleSymbol || cachedModuleSymbol || Debug.checkDefined(info.moduleFile - ? checker.getMergedSymbol(info.moduleFile.symbol) - : checker.tryFindAmbientModule(info.moduleName)); - const symbol = info.symbol || cachedSymbol || Debug.checkDefined(exportKind === ExportKind.ExportEquals - ? checker.resolveExternalModuleSymbol(moduleSymbol) - : checker.tryGetMemberInModuleExportsAndProperties(unescapeLeadingUnderscores(info.symbolTableKey), moduleSymbol), - `Could not find symbol '${info.symbolName}' by key '${info.symbolTableKey}' in module ${moduleSymbol.name}`); - symbols.set(id, [symbol, moduleSymbol]); + if ( + usableByFileName && usableByFileName !== newSourceFile.path || + // If ATA is enabled, auto-imports uses existing imports to guess whether you want auto-imports from node. + // Adding or removing imports from node could change the outcome of that guess, so could change the suggestions list. + typeAcquisitionEnabled && consumesNodeCoreModules(oldSourceFile) !== consumesNodeCoreModules(newSourceFile) || + // Module agumentation and ambient module changes can add or remove exports available to be auto-imported. + // Changes elsewhere in the file can change the *type* of an export in a module augmentation, + // but type info is gathered in getCompletionEntryDetails, which doesn’t use the cache. + !arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations) || + !ambientModuleDeclarationsAreEqual(oldSourceFile, newSourceFile) + ) { + cache.clear(); + return true; + } + usableByFileName = newSourceFile.path; + return false; + }, + }; + if (Debug.isDebugging) { + Object.defineProperty(cache, "__cache", { get: () => exportInfo }); + } + return cache; + + function rehydrateCachedInfo(info: CachedSymbolExportInfo): SymbolExportInfo { + if (info.symbol && info.moduleSymbol) return info as SymbolExportInfo; + const { id, exportKind, targetFlags, isFromPackageJson, moduleFileName } = info; + const [cachedSymbol, cachedModuleSymbol] = symbols.get(id) || emptyArray; + if (cachedSymbol && cachedModuleSymbol) { return { - symbol, - moduleSymbol, + symbol: cachedSymbol, + moduleSymbol: cachedModuleSymbol, moduleFileName, exportKind, targetFlags, isFromPackageJson, }; } + const checker = (isFromPackageJson + ? host.getPackageJsonAutoImportProvider()! + : host.getCurrentProgram()!).getTypeChecker(); + const moduleSymbol = info.moduleSymbol || cachedModuleSymbol || Debug.checkDefined(info.moduleFile + ? checker.getMergedSymbol(info.moduleFile.symbol) + : checker.tryFindAmbientModule(info.moduleName)); + const symbol = info.symbol || cachedSymbol || Debug.checkDefined(exportKind === ExportKind.ExportEquals + ? checker.resolveExternalModuleSymbol(moduleSymbol) + : checker.tryGetMemberInModuleExportsAndProperties(unescapeLeadingUnderscores(info.symbolTableKey), moduleSymbol), + `Could not find symbol '${info.symbolName}' by key '${info.symbolTableKey}' in module ${moduleSymbol.name}`); + symbols.set(id, [symbol, moduleSymbol]); + return { + symbol, + moduleSymbol, + moduleFileName, + exportKind, + targetFlags, + isFromPackageJson, + }; + } - function key(importedName: string, symbol: Symbol, ambientModuleName: string | undefined, checker: TypeChecker): string { - const moduleKey = ambientModuleName || ""; - return `${importedName}|${getSymbolId(skipAlias(symbol, checker))}|${moduleKey}`; - } + function key(importedName: string, symbol: Symbol, ambientModuleName: string | undefined, checker: TypeChecker): string { + const moduleKey = ambientModuleName || ""; + return `${importedName}|${getSymbolId(skipAlias(symbol, checker))}|${moduleKey}`; + } - function parseKey(key: string) { - const symbolName = key.substring(0, key.indexOf("|")); - const moduleKey = key.substring(key.lastIndexOf("|") + 1); - const ambientModuleName = moduleKey === "" ? undefined : moduleKey; - return { symbolName, ambientModuleName }; - } + function parseKey(key: string) { + const symbolName = key.substring(0, key.indexOf("|")); + const moduleKey = key.substring(key.lastIndexOf("|") + 1); + const ambientModuleName = moduleKey === "" ? undefined : moduleKey; + return { symbolName, ambientModuleName }; + } - function fileIsGlobalOnly(file: SourceFile) { - return !file.commonJsModuleIndicator && !file.externalModuleIndicator && !file.moduleAugmentations && !file.ambientModuleNames; - } + function fileIsGlobalOnly(file: SourceFile) { + return !file.commonJsModuleIndicator && !file.externalModuleIndicator && !file.moduleAugmentations && !file.ambientModuleNames; + } - function ambientModuleDeclarationsAreEqual(oldSourceFile: SourceFile, newSourceFile: SourceFile) { - if (!arrayIsEqualTo(oldSourceFile.ambientModuleNames, newSourceFile.ambientModuleNames)) { + function ambientModuleDeclarationsAreEqual(oldSourceFile: SourceFile, newSourceFile: SourceFile) { + if (!arrayIsEqualTo(oldSourceFile.ambientModuleNames, newSourceFile.ambientModuleNames)) { + return false; + } + let oldFileStatementIndex = -1; + let newFileStatementIndex = -1; + for (const ambientModuleName of newSourceFile.ambientModuleNames) { + const isMatchingModuleDeclaration = (node: Statement) => isNonGlobalAmbientModule(node) && node.name.text === ambientModuleName; + oldFileStatementIndex = findIndex(oldSourceFile.statements, isMatchingModuleDeclaration, oldFileStatementIndex + 1); + newFileStatementIndex = findIndex(newSourceFile.statements, isMatchingModuleDeclaration, newFileStatementIndex + 1); + if (oldSourceFile.statements[oldFileStatementIndex] !== newSourceFile.statements[newFileStatementIndex]) { return false; } - let oldFileStatementIndex = -1; - let newFileStatementIndex = -1; - for (const ambientModuleName of newSourceFile.ambientModuleNames) { - const isMatchingModuleDeclaration = (node: Statement) => isNonGlobalAmbientModule(node) && node.name.text === ambientModuleName; - oldFileStatementIndex = findIndex(oldSourceFile.statements, isMatchingModuleDeclaration, oldFileStatementIndex + 1); - newFileStatementIndex = findIndex(newSourceFile.statements, isMatchingModuleDeclaration, newFileStatementIndex + 1); - if (oldSourceFile.statements[oldFileStatementIndex] !== newSourceFile.statements[newFileStatementIndex]) { - return false; - } - } - return true; } + return true; } +} - export function isImportableFile( - program: Program, - from: SourceFile, - to: SourceFile, - preferences: UserPreferences, - packageJsonFilter: PackageJsonImportFilter | undefined, - moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost, - moduleSpecifierCache: ModuleSpecifierCache | undefined, - ): boolean { - if (from === to) return false; - const cachedResult = moduleSpecifierCache?.get(from.path, to.path, preferences); - if (cachedResult?.isAutoImportable !== undefined) { - return cachedResult.isAutoImportable; - } - - const getCanonicalFileName = hostGetCanonicalFileName(moduleSpecifierResolutionHost); - const globalTypingsCache = moduleSpecifierResolutionHost.getGlobalTypingsCacheLocation?.(); - const hasImportablePath = !!moduleSpecifiers.forEachFileNameOfModule( - from.fileName, - to.fileName, - moduleSpecifierResolutionHost, - /*preferSymlinks*/ false, - toPath => { - const toFile = program.getSourceFile(toPath); - // Determine to import using toPath only if toPath is what we were looking at - // or there doesnt exist the file in the program by the symlink - return (toFile === to || !toFile) && - isImportablePath(from.fileName, toPath, getCanonicalFileName, globalTypingsCache); - } - ); +export function isImportableFile( + program: Program, + from: SourceFile, + to: SourceFile, + preferences: UserPreferences, + packageJsonFilter: PackageJsonImportFilter | undefined, + moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost, + moduleSpecifierCache: ModuleSpecifierCache | undefined, +): boolean { + if (from === to) return false; + const cachedResult = moduleSpecifierCache?.get(from.path, to.path, preferences); + if (cachedResult?.isAutoImportable !== undefined) { + return cachedResult.isAutoImportable; + } - if (packageJsonFilter) { - const isAutoImportable = hasImportablePath && packageJsonFilter.allowsImportingSourceFile(to, moduleSpecifierResolutionHost); - moduleSpecifierCache?.setIsAutoImportable(from.path, to.path, preferences, isAutoImportable); - return isAutoImportable; + const getCanonicalFileName = hostGetCanonicalFileName(moduleSpecifierResolutionHost); + const globalTypingsCache = moduleSpecifierResolutionHost.getGlobalTypingsCacheLocation?.(); + const hasImportablePath = !!moduleSpecifiers.forEachFileNameOfModule( + from.fileName, + to.fileName, + moduleSpecifierResolutionHost, + /*preferSymlinks*/ false, + toPath => { + const toFile = program.getSourceFile(toPath); + // Determine to import using toPath only if toPath is what we were looking at + // or there doesnt exist the file in the program by the symlink + return (toFile === to || !toFile) && + isImportablePath(from.fileName, toPath, getCanonicalFileName, globalTypingsCache); } + ); - return hasImportablePath; + if (packageJsonFilter) { + const isAutoImportable = hasImportablePath && packageJsonFilter.allowsImportingSourceFile(to, moduleSpecifierResolutionHost); + moduleSpecifierCache?.setIsAutoImportable(from.path, to.path, preferences, isAutoImportable); + return isAutoImportable; } - /** - * Don't include something from a `node_modules` that isn't actually reachable by a global import. - * A relative import to node_modules is usually a bad idea. - */ - function isImportablePath(fromPath: string, toPath: string, getCanonicalFileName: GetCanonicalFileName, globalCachePath?: string): boolean { - // If it's in a `node_modules` but is not reachable from here via a global import, don't bother. - const toNodeModules = forEachAncestorDirectory(toPath, ancestor => getBaseFileName(ancestor) === "node_modules" ? ancestor : undefined); - const toNodeModulesParent = toNodeModules && getDirectoryPath(getCanonicalFileName(toNodeModules)); - return toNodeModulesParent === undefined - || startsWith(getCanonicalFileName(fromPath), toNodeModulesParent) - || (!!globalCachePath && startsWith(getCanonicalFileName(globalCachePath), toNodeModulesParent)); - } + return hasImportablePath; +} - export function forEachExternalModuleToImportFrom( - program: Program, - host: LanguageServiceHost, - useAutoImportProvider: boolean, - cb: (module: Symbol, moduleFile: SourceFile | undefined, program: Program, isFromPackageJson: boolean) => void, - ) { - forEachExternalModule(program.getTypeChecker(), program.getSourceFiles(), (module, file) => cb(module, file, program, /*isFromPackageJson*/ false)); - const autoImportProvider = useAutoImportProvider && host.getPackageJsonAutoImportProvider?.(); - if (autoImportProvider) { - const start = timestamp(); - forEachExternalModule(autoImportProvider.getTypeChecker(), autoImportProvider.getSourceFiles(), (module, file) => cb(module, file, autoImportProvider, /*isFromPackageJson*/ true)); - host.log?.(`forEachExternalModuleToImportFrom autoImportProvider: ${timestamp() - start}`); - } +/** + * Don't include something from a `node_modules` that isn't actually reachable by a global import. + * A relative import to node_modules is usually a bad idea. + */ +function isImportablePath(fromPath: string, toPath: string, getCanonicalFileName: GetCanonicalFileName, globalCachePath?: string): boolean { + // If it's in a `node_modules` but is not reachable from here via a global import, don't bother. + const toNodeModules = forEachAncestorDirectory(toPath, ancestor => getBaseFileName(ancestor) === "node_modules" ? ancestor : undefined); + const toNodeModulesParent = toNodeModules && getDirectoryPath(getCanonicalFileName(toNodeModules)); + return toNodeModulesParent === undefined + || startsWith(getCanonicalFileName(fromPath), toNodeModulesParent) + || (!!globalCachePath && startsWith(getCanonicalFileName(globalCachePath), toNodeModulesParent)); +} + +export function forEachExternalModuleToImportFrom( + program: Program, + host: LanguageServiceHost, + useAutoImportProvider: boolean, + cb: (module: Symbol, moduleFile: SourceFile | undefined, program: Program, isFromPackageJson: boolean) => void, +) { + forEachExternalModule(program.getTypeChecker(), program.getSourceFiles(), (module, file) => cb(module, file, program, /*isFromPackageJson*/ false)); + const autoImportProvider = useAutoImportProvider && host.getPackageJsonAutoImportProvider?.(); + if (autoImportProvider) { + const start = timestamp(); + forEachExternalModule(autoImportProvider.getTypeChecker(), autoImportProvider.getSourceFiles(), (module, file) => cb(module, file, autoImportProvider, /*isFromPackageJson*/ true)); + host.log?.(`forEachExternalModuleToImportFrom autoImportProvider: ${timestamp() - start}`); } +} - function forEachExternalModule(checker: TypeChecker, allSourceFiles: readonly SourceFile[], cb: (module: Symbol, sourceFile: SourceFile | undefined) => void) { - for (const ambient of checker.getAmbientModules()) { - if (!stringContains(ambient.name, "*")) { - cb(ambient, /*sourceFile*/ undefined); - } +function forEachExternalModule(checker: TypeChecker, allSourceFiles: readonly SourceFile[], cb: (module: Symbol, sourceFile: SourceFile | undefined) => void) { + for (const ambient of checker.getAmbientModules()) { + if (!stringContains(ambient.name, "*")) { + cb(ambient, /*sourceFile*/ undefined); } - for (const sourceFile of allSourceFiles) { - if (isExternalOrCommonJsModule(sourceFile)) { - cb(checker.getMergedSymbol(sourceFile.symbol), sourceFile); - } + } + for (const sourceFile of allSourceFiles) { + if (isExternalOrCommonJsModule(sourceFile)) { + cb(checker.getMergedSymbol(sourceFile.symbol), sourceFile); } } +} - export function getExportInfoMap(importingFile: SourceFile, host: LanguageServiceHost, program: Program, cancellationToken: CancellationToken | undefined): ExportInfoMap { - const start = timestamp(); - // Pulling the AutoImportProvider project will trigger its updateGraph if pending, - // which will invalidate the export map cache if things change, so pull it before - // checking the cache. - host.getPackageJsonAutoImportProvider?.(); - const cache = host.getCachedExportInfoMap?.() || createCacheableExportInfoMap({ - getCurrentProgram: () => program, - getPackageJsonAutoImportProvider: () => host.getPackageJsonAutoImportProvider?.(), - }); +export function getExportInfoMap(importingFile: SourceFile, host: LanguageServiceHost, program: Program, cancellationToken: CancellationToken | undefined): ExportInfoMap { + const start = timestamp(); + // Pulling the AutoImportProvider project will trigger its updateGraph if pending, + // which will invalidate the export map cache if things change, so pull it before + // checking the cache. + host.getPackageJsonAutoImportProvider?.(); + const cache = host.getCachedExportInfoMap?.() || createCacheableExportInfoMap({ + getCurrentProgram: () => program, + getPackageJsonAutoImportProvider: () => host.getPackageJsonAutoImportProvider?.(), + }); + + if (cache.isUsableByFile(importingFile.path)) { + host.log?.("getExportInfoMap: cache hit"); + return cache; + } - if (cache.isUsableByFile(importingFile.path)) { - host.log?.("getExportInfoMap: cache hit"); - return cache; + host.log?.("getExportInfoMap: cache miss or empty; calculating new results"); + const compilerOptions = program.getCompilerOptions(); + const scriptTarget = getEmitScriptTarget(compilerOptions); + let moduleCount = 0; + forEachExternalModuleToImportFrom(program, host, /*useAutoImportProvider*/ true, (moduleSymbol, moduleFile, program, isFromPackageJson) => { + if (++moduleCount % 100 === 0) cancellationToken?.throwIfCancellationRequested(); + const seenExports = new Map<__String, true>(); + const checker = program.getTypeChecker(); + const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions); + // Note: I think we shouldn't actually see resolved module symbols here, but weird merges + // can cause it to happen: see 'completionsImport_mergedReExport.ts' + if (defaultInfo && isImportableSymbol(defaultInfo.symbol, checker)) { + cache.add( + importingFile.path, + defaultInfo.symbol, + defaultInfo.exportKind === ExportKind.Default ? InternalSymbolName.Default : InternalSymbolName.ExportEquals, + moduleSymbol, + moduleFile, + defaultInfo.exportKind, + isFromPackageJson, + scriptTarget, + checker); } - - host.log?.("getExportInfoMap: cache miss or empty; calculating new results"); - const compilerOptions = program.getCompilerOptions(); - const scriptTarget = getEmitScriptTarget(compilerOptions); - let moduleCount = 0; - forEachExternalModuleToImportFrom(program, host, /*useAutoImportProvider*/ true, (moduleSymbol, moduleFile, program, isFromPackageJson) => { - if (++moduleCount % 100 === 0) cancellationToken?.throwIfCancellationRequested(); - const seenExports = new Map<__String, true>(); - const checker = program.getTypeChecker(); - const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions); - // Note: I think we shouldn't actually see resolved module symbols here, but weird merges - // can cause it to happen: see 'completionsImport_mergedReExport.ts' - if (defaultInfo && isImportableSymbol(defaultInfo.symbol, checker)) { + checker.forEachExportAndPropertyOfModule(moduleSymbol, (exported, key) => { + if (exported !== defaultInfo?.symbol && isImportableSymbol(exported, checker) && addToSeen(seenExports, key)) { cache.add( importingFile.path, - defaultInfo.symbol, - defaultInfo.exportKind === ExportKind.Default ? InternalSymbolName.Default : InternalSymbolName.ExportEquals, + exported, + key, moduleSymbol, moduleFile, - defaultInfo.exportKind, + ExportKind.Named, isFromPackageJson, scriptTarget, checker); } - checker.forEachExportAndPropertyOfModule(moduleSymbol, (exported, key) => { - if (exported !== defaultInfo?.symbol && isImportableSymbol(exported, checker) && addToSeen(seenExports, key)) { - cache.add( - importingFile.path, - exported, - key, - moduleSymbol, - moduleFile, - ExportKind.Named, - isFromPackageJson, - scriptTarget, - checker); - } - }); }); + }); - host.log?.(`getExportInfoMap: done in ${timestamp() - start} ms`); - return cache; - } - - export function getDefaultLikeExportInfo(moduleSymbol: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions) { - const exported = getDefaultLikeExportWorker(moduleSymbol, checker); - if (!exported) return undefined; - const { symbol, exportKind } = exported; - const info = getDefaultExportInfoWorker(symbol, checker, compilerOptions); - return info && { symbol, exportKind, ...info }; - } + host.log?.(`getExportInfoMap: done in ${timestamp() - start} ms`); + return cache; +} - function isImportableSymbol(symbol: Symbol, checker: TypeChecker) { - return !checker.isUndefinedSymbol(symbol) && !checker.isUnknownSymbol(symbol) && !isKnownSymbol(symbol) && !isPrivateIdentifierSymbol(symbol); - } +export function getDefaultLikeExportInfo(moduleSymbol: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions) { + const exported = getDefaultLikeExportWorker(moduleSymbol, checker); + if (!exported) return undefined; + const { symbol, exportKind } = exported; + const info = getDefaultExportInfoWorker(symbol, checker, compilerOptions); + return info && { symbol, exportKind, ...info }; +} - function getDefaultLikeExportWorker(moduleSymbol: Symbol, checker: TypeChecker): { readonly symbol: Symbol, readonly exportKind: ExportKind } | undefined { - const exportEquals = checker.resolveExternalModuleSymbol(moduleSymbol); - if (exportEquals !== moduleSymbol) return { symbol: exportEquals, exportKind: ExportKind.ExportEquals }; - const defaultExport = checker.tryGetMemberInModuleExports(InternalSymbolName.Default, moduleSymbol); - if (defaultExport) return { symbol: defaultExport, exportKind: ExportKind.Default }; - } +function isImportableSymbol(symbol: Symbol, checker: TypeChecker) { + return !checker.isUndefinedSymbol(symbol) && !checker.isUnknownSymbol(symbol) && !isKnownSymbol(symbol) && !isPrivateIdentifierSymbol(symbol); +} - function getDefaultExportInfoWorker(defaultExport: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions): { readonly symbolForMeaning: Symbol, readonly name: string } | undefined { - const localSymbol = getLocalSymbolForExportDefault(defaultExport); - if (localSymbol) return { symbolForMeaning: localSymbol, name: localSymbol.name }; - - const name = getNameForExportDefault(defaultExport); - if (name !== undefined) return { symbolForMeaning: defaultExport, name }; - - if (defaultExport.flags & SymbolFlags.Alias) { - const aliased = checker.getImmediateAliasedSymbol(defaultExport); - if (aliased && aliased.parent) { - // - `aliased` will be undefined if the module is exporting an unresolvable name, - // but we can still offer completions for it. - // - `aliased.parent` will be undefined if the module is exporting `globalThis.something`, - // or another expression that resolves to a global. - return getDefaultExportInfoWorker(aliased, checker, compilerOptions); - } - } +function getDefaultLikeExportWorker(moduleSymbol: Symbol, checker: TypeChecker): { readonly symbol: Symbol, readonly exportKind: ExportKind } | undefined { + const exportEquals = checker.resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals !== moduleSymbol) return { symbol: exportEquals, exportKind: ExportKind.ExportEquals }; + const defaultExport = checker.tryGetMemberInModuleExports(InternalSymbolName.Default, moduleSymbol); + if (defaultExport) return { symbol: defaultExport, exportKind: ExportKind.Default }; +} - if (defaultExport.escapedName !== InternalSymbolName.Default && - defaultExport.escapedName !== InternalSymbolName.ExportEquals) { - return { symbolForMeaning: defaultExport, name: defaultExport.getName() }; +function getDefaultExportInfoWorker(defaultExport: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions): { readonly symbolForMeaning: Symbol, readonly name: string } | undefined { + const localSymbol = getLocalSymbolForExportDefault(defaultExport); + if (localSymbol) return { symbolForMeaning: localSymbol, name: localSymbol.name }; + + const name = getNameForExportDefault(defaultExport); + if (name !== undefined) return { symbolForMeaning: defaultExport, name }; + + if (defaultExport.flags & SymbolFlags.Alias) { + const aliased = checker.getImmediateAliasedSymbol(defaultExport); + if (aliased && aliased.parent) { + // - `aliased` will be undefined if the module is exporting an unresolvable name, + // but we can still offer completions for it. + // - `aliased.parent` will be undefined if the module is exporting `globalThis.something`, + // or another expression that resolves to a global. + return getDefaultExportInfoWorker(aliased, checker, compilerOptions); } - return { symbolForMeaning: defaultExport, name: getNameForExportedSymbol(defaultExport, compilerOptions.target) }; } - function getNameForExportDefault(symbol: Symbol): string | undefined { - return symbol.declarations && firstDefined(symbol.declarations, declaration => { - if (isExportAssignment(declaration)) { - return tryCast(skipOuterExpressions(declaration.expression), isIdentifier)?.text; - } - else if (isExportSpecifier(declaration)) { - Debug.assert(declaration.name.text === InternalSymbolName.Default, "Expected the specifier to be a default export"); - return declaration.propertyName && declaration.propertyName.text; - } - }); + if (defaultExport.escapedName !== InternalSymbolName.Default && + defaultExport.escapedName !== InternalSymbolName.ExportEquals) { + return { symbolForMeaning: defaultExport, name: defaultExport.getName() }; } + return { symbolForMeaning: defaultExport, name: getNameForExportedSymbol(defaultExport, compilerOptions.target) }; +} + +function getNameForExportDefault(symbol: Symbol): string | undefined { + return symbol.declarations && firstDefined(symbol.declarations, declaration => { + if (isExportAssignment(declaration)) { + return tryCast(skipOuterExpressions(declaration.expression), isIdentifier)?.text; + } + else if (isExportSpecifier(declaration)) { + Debug.assert(declaration.name.text === InternalSymbolName.Default, "Expected the specifier to be a default export"); + return declaration.propertyName && declaration.propertyName.text; + } + }); +} } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index aebc52b2fb0f8..3aa4485633f57 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1,2326 +1,2326 @@ /* @internal */ namespace ts.FindAllReferences { - export interface SymbolAndEntries { - readonly definition: Definition | undefined; - readonly references: readonly Entry[]; - } - - export const enum DefinitionKind { Symbol, Label, Keyword, This, String, TripleSlashReference } - export type Definition = - | { readonly type: DefinitionKind.Symbol; readonly symbol: Symbol } - | { readonly type: DefinitionKind.Label; readonly node: Identifier } - | { readonly type: DefinitionKind.Keyword; readonly node: Node } - | { readonly type: DefinitionKind.This; readonly node: Node } - | { readonly type: DefinitionKind.String; readonly node: StringLiteralLike } - | { readonly type: DefinitionKind.TripleSlashReference; readonly reference: FileReference, readonly file: SourceFile }; - - export const enum EntryKind { Span, Node, StringLiteral, SearchedLocalFoundProperty, SearchedPropertyFoundLocal } - export type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal; - export type Entry = NodeEntry | SpanEntry; - export interface ContextWithStartAndEndNode { - start: Node; - end: Node; - } - export type ContextNode = Node | ContextWithStartAndEndNode; - export interface NodeEntry { - readonly kind: NodeEntryKind; - readonly node: Node; - readonly context?: ContextNode; - } - export interface SpanEntry { - readonly kind: EntryKind.Span; - readonly fileName: string; - readonly textSpan: TextSpan; - } - export function nodeEntry(node: Node, kind: NodeEntryKind = EntryKind.Node): NodeEntry { - return { - kind, - node: (node as NamedDeclaration).name || node, - context: getContextNodeForNodeEntry(node) - }; - } - - export function isContextWithStartAndEndNode(node: ContextNode): node is ContextWithStartAndEndNode { - return node && (node as Node).kind === undefined; - } - - function getContextNodeForNodeEntry(node: Node): ContextNode | undefined { - if (isDeclaration(node)) { - return getContextNode(node); - } - - if (!node.parent) return undefined; - - if (!isDeclaration(node.parent) && !isExportAssignment(node.parent)) { - // Special property assignment in javascript - if (isInJSFile(node)) { - const binaryExpression = isBinaryExpression(node.parent) ? - node.parent : - isAccessExpression(node.parent) && - isBinaryExpression(node.parent.parent) && - node.parent.parent.left === node.parent ? - node.parent.parent : - undefined; - if (binaryExpression && getAssignmentDeclarationKind(binaryExpression) !== AssignmentDeclarationKind.None) { - return getContextNode(binaryExpression); - } - } +export interface SymbolAndEntries { + readonly definition: Definition | undefined; + readonly references: readonly Entry[]; +} - // Jsx Tags - if (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) { - return node.parent.parent; - } - else if (isJsxSelfClosingElement(node.parent) || - isLabeledStatement(node.parent) || - isBreakOrContinueStatement(node.parent)) { - return node.parent; - } - else if (isStringLiteralLike(node)) { - const validImport = tryGetImportFromModuleSpecifier(node); - if (validImport) { - const declOrStatement = findAncestor(validImport, node => - isDeclaration(node) || - isStatement(node) || - isJSDocTag(node) - )! as NamedDeclaration | Statement | JSDocTag; - return isDeclaration(declOrStatement) ? - getContextNode(declOrStatement) : - declOrStatement; - } - } +export const enum DefinitionKind { Symbol, Label, Keyword, This, String, TripleSlashReference } +export type Definition = + | { readonly type: DefinitionKind.Symbol; readonly symbol: Symbol } + | { readonly type: DefinitionKind.Label; readonly node: Identifier } + | { readonly type: DefinitionKind.Keyword; readonly node: Node } + | { readonly type: DefinitionKind.This; readonly node: Node } + | { readonly type: DefinitionKind.String; readonly node: StringLiteralLike } + | { readonly type: DefinitionKind.TripleSlashReference; readonly reference: FileReference, readonly file: SourceFile }; + +export const enum EntryKind { Span, Node, StringLiteral, SearchedLocalFoundProperty, SearchedPropertyFoundLocal } +export type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal; +export type Entry = NodeEntry | SpanEntry; +export interface ContextWithStartAndEndNode { + start: Node; + end: Node; +} +export type ContextNode = Node | ContextWithStartAndEndNode; +export interface NodeEntry { + readonly kind: NodeEntryKind; + readonly node: Node; + readonly context?: ContextNode; +} +export interface SpanEntry { + readonly kind: EntryKind.Span; + readonly fileName: string; + readonly textSpan: TextSpan; +} +export function nodeEntry(node: Node, kind: NodeEntryKind = EntryKind.Node): NodeEntry { + return { + kind, + node: (node as NamedDeclaration).name || node, + context: getContextNodeForNodeEntry(node) + }; +} - // Handle computed property name - const propertyName = findAncestor(node, isComputedPropertyName); - return propertyName ? - getContextNode(propertyName.parent) : - undefined; - } +export function isContextWithStartAndEndNode(node: ContextNode): node is ContextWithStartAndEndNode { + return node && (node as Node).kind === undefined; +} - if (node.parent.name === node || // node is name of declaration, use parent - isConstructorDeclaration(node.parent) || - isExportAssignment(node.parent) || - // Property name of the import export specifier or binding pattern, use parent - ((isImportOrExportSpecifier(node.parent) || isBindingElement(node.parent)) - && node.parent.propertyName === node) || - // Is default export - (node.kind === SyntaxKind.DefaultKeyword && hasSyntacticModifier(node.parent, ModifierFlags.ExportDefault))) { - return getContextNode(node.parent); - } +function getContextNodeForNodeEntry(node: Node): ContextNode | undefined { + if (isDeclaration(node)) { + return getContextNode(node); + } - return undefined; + if (!node.parent) return undefined; + + if (!isDeclaration(node.parent) && !isExportAssignment(node.parent)) { + // Special property assignment in javascript + if (isInJSFile(node)) { + const binaryExpression = isBinaryExpression(node.parent) ? + node.parent : + isAccessExpression(node.parent) && + isBinaryExpression(node.parent.parent) && + node.parent.parent.left === node.parent ? + node.parent.parent : + undefined; + if (binaryExpression && getAssignmentDeclarationKind(binaryExpression) !== AssignmentDeclarationKind.None) { + return getContextNode(binaryExpression); + } + } + + // Jsx Tags + if (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) { + return node.parent.parent; + } + else if (isJsxSelfClosingElement(node.parent) || + isLabeledStatement(node.parent) || + isBreakOrContinueStatement(node.parent)) { + return node.parent; + } + else if (isStringLiteralLike(node)) { + const validImport = tryGetImportFromModuleSpecifier(node); + if (validImport) { + const declOrStatement = findAncestor(validImport, node => + isDeclaration(node) || + isStatement(node) || + isJSDocTag(node) + )! as NamedDeclaration | Statement | JSDocTag; + return isDeclaration(declOrStatement) ? + getContextNode(declOrStatement) : + declOrStatement; + } + } + + // Handle computed property name + const propertyName = findAncestor(node, isComputedPropertyName); + return propertyName ? + getContextNode(propertyName.parent) : + undefined; } - export function getContextNode(node: NamedDeclaration | BinaryExpression | ForInOrOfStatement | undefined): ContextNode | undefined { - if (!node) return undefined; - switch (node.kind) { - case SyntaxKind.VariableDeclaration: - return !isVariableDeclarationList(node.parent) || node.parent.declarations.length !== 1 ? - node : - isVariableStatement(node.parent.parent) ? - node.parent.parent : - isForInOrOfStatement(node.parent.parent) ? - getContextNode(node.parent.parent) : - node.parent; - - case SyntaxKind.BindingElement: - return getContextNode(node.parent.parent as NamedDeclaration); - - case SyntaxKind.ImportSpecifier: - return node.parent.parent.parent; - - case SyntaxKind.ExportSpecifier: - case SyntaxKind.NamespaceImport: - return node.parent.parent; - - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceExport: - return node.parent; - - case SyntaxKind.BinaryExpression: - return isExpressionStatement(node.parent) ? - node.parent : - node; - - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForInStatement: - return { - start: (node as ForInOrOfStatement).initializer, - end: (node as ForInOrOfStatement).expression - }; + if (node.parent.name === node || // node is name of declaration, use parent + isConstructorDeclaration(node.parent) || + isExportAssignment(node.parent) || + // Property name of the import export specifier or binding pattern, use parent + ((isImportOrExportSpecifier(node.parent) || isBindingElement(node.parent)) + && node.parent.propertyName === node) || + // Is default export + (node.kind === SyntaxKind.DefaultKeyword && hasSyntacticModifier(node.parent, ModifierFlags.ExportDefault))) { + return getContextNode(node.parent); + } - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - return isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent) ? - getContextNode( - findAncestor(node.parent, node => - isBinaryExpression(node) || isForInOrOfStatement(node) - ) as BinaryExpression | ForInOrOfStatement - ) : - node; + return undefined; +} - default: - return node; - } - } +export function getContextNode(node: NamedDeclaration | BinaryExpression | ForInOrOfStatement | undefined): ContextNode | undefined { + if (!node) return undefined; + switch (node.kind) { + case SyntaxKind.VariableDeclaration: + return !isVariableDeclarationList(node.parent) || node.parent.declarations.length !== 1 ? + node : + isVariableStatement(node.parent.parent) ? + node.parent.parent : + isForInOrOfStatement(node.parent.parent) ? + getContextNode(node.parent.parent) : + node.parent; + + case SyntaxKind.BindingElement: + return getContextNode(node.parent.parent as NamedDeclaration); + + case SyntaxKind.ImportSpecifier: + return node.parent.parent.parent; + + case SyntaxKind.ExportSpecifier: + case SyntaxKind.NamespaceImport: + return node.parent.parent; + + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceExport: + return node.parent; + + case SyntaxKind.BinaryExpression: + return isExpressionStatement(node.parent) ? + node.parent : + node; + + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForInStatement: + return { + start: (node as ForInOrOfStatement).initializer, + end: (node as ForInOrOfStatement).expression + }; - export function toContextSpan(textSpan: TextSpan, sourceFile: SourceFile, context?: ContextNode): { contextSpan: TextSpan } | undefined { - if (!context) return undefined; - const contextSpan = isContextWithStartAndEndNode(context) ? - getTextSpan(context.start, sourceFile, context.end) : - getTextSpan(context, sourceFile); - return contextSpan.start !== textSpan.start || contextSpan.length !== textSpan.length ? - { contextSpan } : - undefined; + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + return isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent) ? + getContextNode( + findAncestor(node.parent, node => + isBinaryExpression(node) || isForInOrOfStatement(node) + ) as BinaryExpression | ForInOrOfStatement + ) : + node; + + default: + return node; } +} - export const enum FindReferencesUse { - /** - * When searching for references to a symbol, the location will not be adjusted (this is the default behavior when not specified). - */ - Other, - /** - * When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword. - */ - References, - /** - * When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword. - * Unlike `References`, the location will only be adjusted keyword belonged to a declaration with a valid name. - * If set, we will find fewer references -- if it is referenced by several different names, we still only find references for the original name. - */ - Rename, - } +export function toContextSpan(textSpan: TextSpan, sourceFile: SourceFile, context?: ContextNode): { contextSpan: TextSpan } | undefined { + if (!context) return undefined; + const contextSpan = isContextWithStartAndEndNode(context) ? + getTextSpan(context.start, sourceFile, context.end) : + getTextSpan(context, sourceFile); + return contextSpan.start !== textSpan.start || contextSpan.length !== textSpan.length ? + { contextSpan } : + undefined; +} - export interface Options { - readonly findInStrings?: boolean; - readonly findInComments?: boolean; - readonly use?: FindReferencesUse; - /** True if we are searching for implementations. We will have a different method of adding references if so. */ - readonly implementations?: boolean; - /** - * True to opt in for enhanced renaming of shorthand properties and import/export specifiers. - * The options controls the behavior for the whole rename operation; it cannot be changed on a per-file basis. - * Default is false for backwards compatibility. - */ - readonly providePrefixAndSuffixTextForRename?: boolean; - } +export const enum FindReferencesUse { + /** + * When searching for references to a symbol, the location will not be adjusted (this is the default behavior when not specified). + */ + Other, + /** + * When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword. + */ + References, + /** + * When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword. + * Unlike `References`, the location will only be adjusted keyword belonged to a declaration with a valid name. + * If set, we will find fewer references -- if it is referenced by several different names, we still only find references for the original name. + */ + Rename, +} - export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { - const node = getTouchingPropertyName(sourceFile, position); - const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, { use: FindReferencesUse.References }); - const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(node); - return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined(referencedSymbols, ({ definition, references }) => - // Only include referenced symbols that have a valid definition. - definition && { - definition: checker.runWithCancellationToken(cancellationToken, checker => definitionToReferencedSymbolDefinitionInfo(definition, checker, node)), - references: references.map(r => toReferenceEntry(r, symbol)) - }); - } +export interface Options { + readonly findInStrings?: boolean; + readonly findInComments?: boolean; + readonly use?: FindReferencesUse; + /** True if we are searching for implementations. We will have a different method of adding references if so. */ + readonly implementations?: boolean; + /** + * True to opt in for enhanced renaming of shorthand properties and import/export specifiers. + * The options controls the behavior for the whole rename operation; it cannot be changed on a per-file basis. + * Default is false for backwards compatibility. + */ + readonly providePrefixAndSuffixTextForRename?: boolean; +} - export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] | undefined { - const node = getTouchingPropertyName(sourceFile, position); - let referenceEntries: Entry[] | undefined; - const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, node, position); +export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { + const node = getTouchingPropertyName(sourceFile, position); + const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, { use: FindReferencesUse.References }); + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(node); + return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined(referencedSymbols, ({ definition, references }) => + // Only include referenced symbols that have a valid definition. + definition && { + definition: checker.runWithCancellationToken(cancellationToken, checker => definitionToReferencedSymbolDefinitionInfo(definition, checker, node)), + references: references.map(r => toReferenceEntry(r, symbol)) + }); +} - if ( - node.parent.kind === SyntaxKind.PropertyAccessExpression - || node.parent.kind === SyntaxKind.BindingElement - || node.parent.kind === SyntaxKind.ElementAccessExpression - || node.kind === SyntaxKind.SuperKeyword - ) { - referenceEntries = entries && [...entries]; - } - else { - const queue = entries && [...entries]; - const seenNodes = new Map(); - while (queue && queue.length) { - const entry = queue.shift() as NodeEntry; - if (!addToSeen(seenNodes, getNodeId(entry.node))) { - continue; - } - referenceEntries = append(referenceEntries, entry); - const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, entry.node, entry.node.pos); - if (entries) { - queue.push(...entries); - } +export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] | undefined { + const node = getTouchingPropertyName(sourceFile, position); + let referenceEntries: Entry[] | undefined; + const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, node, position); + + if ( + node.parent.kind === SyntaxKind.PropertyAccessExpression + || node.parent.kind === SyntaxKind.BindingElement + || node.parent.kind === SyntaxKind.ElementAccessExpression + || node.kind === SyntaxKind.SuperKeyword + ) { + referenceEntries = entries && [...entries]; + } + else { + const queue = entries && [...entries]; + const seenNodes = new Map(); + while (queue && queue.length) { + const entry = queue.shift() as NodeEntry; + if (!addToSeen(seenNodes, getNodeId(entry.node))) { + continue; + } + referenceEntries = append(referenceEntries, entry); + const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, entry.node, entry.node.pos); + if (entries) { + queue.push(...entries); } } - const checker = program.getTypeChecker(); - return map(referenceEntries, entry => toImplementationLocation(entry, checker)); } + const checker = program.getTypeChecker(); + return map(referenceEntries, entry => toImplementationLocation(entry, checker)); +} - function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number): readonly Entry[] | undefined { - if (node.kind === SyntaxKind.SourceFile) { - return undefined; - } - - const checker = program.getTypeChecker(); - // If invoked directly on a shorthand property assignment, then return - // the declaration of the symbol being assigned (not the symbol being assigned to). - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - const result: NodeEntry[] = []; - Core.getReferenceEntriesForShorthandPropertyAssignment(node, checker, node => result.push(nodeEntry(node))); - return result; - } - else if (node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node.parent)) { - // References to and accesses on the super keyword only have one possible implementation, so no - // need to "Find all References" - const symbol = checker.getSymbolAtLocation(node)!; - return symbol.valueDeclaration && [nodeEntry(symbol.valueDeclaration)]; - } - else { - // Perform "Find all References" and retrieve only those that are implementations - return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true, use: FindReferencesUse.References }); - } +function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number): readonly Entry[] | undefined { + if (node.kind === SyntaxKind.SourceFile) { + return undefined; } - export function findReferenceOrRenameEntries( - program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number, options: Options | undefined, - convertEntry: ToReferenceOrRenameEntry, - ): T[] | undefined { - return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), entry => convertEntry(entry, node, program.getTypeChecker())); + const checker = program.getTypeChecker(); + // If invoked directly on a shorthand property assignment, then return + // the declaration of the symbol being assigned (not the symbol being assigned to). + if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + const result: NodeEntry[] = []; + Core.getReferenceEntriesForShorthandPropertyAssignment(node, checker, node => result.push(nodeEntry(node))); + return result; + } + else if (node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node.parent)) { + // References to and accesses on the super keyword only have one possible implementation, so no + // need to "Find all References" + const symbol = checker.getSymbolAtLocation(node)!; + return symbol.valueDeclaration && [nodeEntry(symbol.valueDeclaration)]; } + else { + // Perform "Find all References" and retrieve only those that are implementations + return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true, use: FindReferencesUse.References }); + } +} - export type ToReferenceOrRenameEntry = (entry: Entry, originalNode: Node, checker: TypeChecker) => T; +export function findReferenceOrRenameEntries( + program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number, options: Options | undefined, + convertEntry: ToReferenceOrRenameEntry, +): T[] | undefined { + return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), entry => convertEntry(entry, node, program.getTypeChecker())); +} - export function getReferenceEntriesForNode( - position: number, - node: Node, - program: Program, - sourceFiles: readonly SourceFile[], - cancellationToken: CancellationToken, - options: Options = {}, - sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName)), - ): readonly Entry[] | undefined { - return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options, sourceFilesSet)); - } - - function flattenEntries(referenceSymbols: readonly SymbolAndEntries[] | undefined): readonly Entry[] | undefined { - return referenceSymbols && flatMap(referenceSymbols, r => r.references); - } - - function definitionToReferencedSymbolDefinitionInfo(def: Definition, checker: TypeChecker, originalNode: Node): ReferencedSymbolDefinitionInfo { - const info = ((): { sourceFile: SourceFile, textSpan: TextSpan, name: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], context?: Node | ContextWithStartAndEndNode } => { - switch (def.type) { - case DefinitionKind.Symbol: { - const { symbol } = def; - const { displayParts, kind } = getDefinitionKindAndDisplayParts(symbol, checker, originalNode); - const name = displayParts.map(p => p.text).join(""); - const declaration = symbol.declarations && firstOrUndefined(symbol.declarations); - const node = declaration ? (getNameOfDeclaration(declaration) || declaration) : originalNode; - return { - ...getFileAndTextSpanFromNode(node), - name, - kind, - displayParts, - context: getContextNode(declaration) - }; - } - case DefinitionKind.Label: { - const { node } = def; - return { ...getFileAndTextSpanFromNode(node), name: node.text, kind: ScriptElementKind.label, displayParts: [displayPart(node.text, SymbolDisplayPartKind.text)] }; - } - case DefinitionKind.Keyword: { - const { node } = def; - const name = tokenToString(node.kind)!; - return { ...getFileAndTextSpanFromNode(node), name, kind: ScriptElementKind.keyword, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] }; - } - case DefinitionKind.This: { - const { node } = def; - const symbol = checker.getSymbolAtLocation(node); - const displayParts = symbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind( - checker, symbol, node.getSourceFile(), getContainerNode(node), node).displayParts || [textPart("this")]; - return { ...getFileAndTextSpanFromNode(node), name: "this", kind: ScriptElementKind.variableElement, displayParts }; - } - case DefinitionKind.String: { - const { node } = def; - return { - ...getFileAndTextSpanFromNode(node), - name: node.text, - kind: ScriptElementKind.variableElement, - displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)] - }; - } - case DefinitionKind.TripleSlashReference: { - return { - textSpan: createTextSpanFromRange(def.reference), - sourceFile: def.file, - name: def.reference.fileName, - kind: ScriptElementKind.string, - displayParts: [displayPart(`"${def.reference.fileName}"`, SymbolDisplayPartKind.stringLiteral)] - }; - } - default: - return Debug.assertNever(def); +export type ToReferenceOrRenameEntry = (entry: Entry, originalNode: Node, checker: TypeChecker) => T; + +export function getReferenceEntriesForNode( + position: number, + node: Node, + program: Program, + sourceFiles: readonly SourceFile[], + cancellationToken: CancellationToken, + options: Options = {}, + sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName)), +): readonly Entry[] | undefined { + return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options, sourceFilesSet)); +} + +function flattenEntries(referenceSymbols: readonly SymbolAndEntries[] | undefined): readonly Entry[] | undefined { + return referenceSymbols && flatMap(referenceSymbols, r => r.references); +} + +function definitionToReferencedSymbolDefinitionInfo(def: Definition, checker: TypeChecker, originalNode: Node): ReferencedSymbolDefinitionInfo { + const info = ((): { sourceFile: SourceFile, textSpan: TextSpan, name: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], context?: Node | ContextWithStartAndEndNode } => { + switch (def.type) { + case DefinitionKind.Symbol: { + const { symbol } = def; + const { displayParts, kind } = getDefinitionKindAndDisplayParts(symbol, checker, originalNode); + const name = displayParts.map(p => p.text).join(""); + const declaration = symbol.declarations && firstOrUndefined(symbol.declarations); + const node = declaration ? (getNameOfDeclaration(declaration) || declaration) : originalNode; + return { + ...getFileAndTextSpanFromNode(node), + name, + kind, + displayParts, + context: getContextNode(declaration) + }; + } + case DefinitionKind.Label: { + const { node } = def; + return { ...getFileAndTextSpanFromNode(node), name: node.text, kind: ScriptElementKind.label, displayParts: [displayPart(node.text, SymbolDisplayPartKind.text)] }; + } + case DefinitionKind.Keyword: { + const { node } = def; + const name = tokenToString(node.kind)!; + return { ...getFileAndTextSpanFromNode(node), name, kind: ScriptElementKind.keyword, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] }; + } + case DefinitionKind.This: { + const { node } = def; + const symbol = checker.getSymbolAtLocation(node); + const displayParts = symbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind( + checker, symbol, node.getSourceFile(), getContainerNode(node), node).displayParts || [textPart("this")]; + return { ...getFileAndTextSpanFromNode(node), name: "this", kind: ScriptElementKind.variableElement, displayParts }; + } + case DefinitionKind.String: { + const { node } = def; + return { + ...getFileAndTextSpanFromNode(node), + name: node.text, + kind: ScriptElementKind.variableElement, + displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)] + }; + } + case DefinitionKind.TripleSlashReference: { + return { + textSpan: createTextSpanFromRange(def.reference), + sourceFile: def.file, + name: def.reference.fileName, + kind: ScriptElementKind.string, + displayParts: [displayPart(`"${def.reference.fileName}"`, SymbolDisplayPartKind.stringLiteral)] + }; } - })(); + default: + return Debug.assertNever(def); + } + })(); + + const { sourceFile, textSpan, name, kind, displayParts, context } = info; + return { + containerKind: ScriptElementKind.unknown, + containerName: "", + fileName: sourceFile.fileName, + kind, + name, + textSpan, + displayParts, + ...toContextSpan(textSpan, sourceFile, context) + }; +} - const { sourceFile, textSpan, name, kind, displayParts, context } = info; - return { - containerKind: ScriptElementKind.unknown, - containerName: "", - fileName: sourceFile.fileName, - kind, - name, - textSpan, - displayParts, - ...toContextSpan(textSpan, sourceFile, context) - }; - } +function getFileAndTextSpanFromNode(node: Node) { + const sourceFile = node.getSourceFile(); + return { + sourceFile, + textSpan: getTextSpan(isComputedPropertyName(node) ? node.expression : node, sourceFile) + }; +} - function getFileAndTextSpanFromNode(node: Node) { - const sourceFile = node.getSourceFile(); - return { - sourceFile, - textSpan: getTextSpan(isComputedPropertyName(node) ? node.expression : node, sourceFile) - }; - } +function getDefinitionKindAndDisplayParts(symbol: Symbol, checker: TypeChecker, node: Node): { displayParts: SymbolDisplayPart[], kind: ScriptElementKind } { + const meaning = Core.getIntersectingMeaningFromDeclarations(node, symbol); + const enclosingDeclaration = symbol.declarations && firstOrUndefined(symbol.declarations) || node; + const { displayParts, symbolKind } = + SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, enclosingDeclaration.getSourceFile(), enclosingDeclaration, enclosingDeclaration, meaning); + return { displayParts, kind: symbolKind }; +} - function getDefinitionKindAndDisplayParts(symbol: Symbol, checker: TypeChecker, node: Node): { displayParts: SymbolDisplayPart[], kind: ScriptElementKind } { - const meaning = Core.getIntersectingMeaningFromDeclarations(node, symbol); - const enclosingDeclaration = symbol.declarations && firstOrUndefined(symbol.declarations) || node; - const { displayParts, symbolKind } = - SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, enclosingDeclaration.getSourceFile(), enclosingDeclaration, enclosingDeclaration, meaning); - return { displayParts, kind: symbolKind }; - } +export function toRenameLocation(entry: Entry, originalNode: Node, checker: TypeChecker, providePrefixAndSuffixText: boolean): RenameLocation { + return { ...entryToDocumentSpan(entry), ...(providePrefixAndSuffixText && getPrefixAndSuffixText(entry, originalNode, checker)) }; +} - export function toRenameLocation(entry: Entry, originalNode: Node, checker: TypeChecker, providePrefixAndSuffixText: boolean): RenameLocation { - return { ...entryToDocumentSpan(entry), ...(providePrefixAndSuffixText && getPrefixAndSuffixText(entry, originalNode, checker)) }; +export function toReferenceEntry(entry: Entry, symbol: Symbol | undefined): ReferenceEntry { + const documentSpan = entryToDocumentSpan(entry); + if (entry.kind === EntryKind.Span) { + return { ...documentSpan, isWriteAccess: false, isDefinition: false }; } + const { kind, node } = entry; + return { + ...documentSpan, + isWriteAccess: isWriteAccessForReference(node), + isDefinition: isDeclarationOfSymbol(node, symbol), + isInString: kind === EntryKind.StringLiteral ? true : undefined, + }; +} - export function toReferenceEntry(entry: Entry, symbol: Symbol | undefined): ReferenceEntry { - const documentSpan = entryToDocumentSpan(entry); - if (entry.kind === EntryKind.Span) { - return { ...documentSpan, isWriteAccess: false, isDefinition: false }; - } - const { kind, node } = entry; +function entryToDocumentSpan(entry: Entry): DocumentSpan { + if (entry.kind === EntryKind.Span) { + return { textSpan: entry.textSpan, fileName: entry.fileName }; + } + else { + const sourceFile = entry.node.getSourceFile(); + const textSpan = getTextSpan(entry.node, sourceFile); return { - ...documentSpan, - isWriteAccess: isWriteAccessForReference(node), - isDefinition: isDeclarationOfSymbol(node, symbol), - isInString: kind === EntryKind.StringLiteral ? true : undefined, + textSpan, + fileName: sourceFile.fileName, + ...toContextSpan(textSpan, sourceFile, entry.context) }; } +} - function entryToDocumentSpan(entry: Entry): DocumentSpan { - if (entry.kind === EntryKind.Span) { - return { textSpan: entry.textSpan, fileName: entry.fileName }; - } - else { - const sourceFile = entry.node.getSourceFile(); - const textSpan = getTextSpan(entry.node, sourceFile); - return { - textSpan, - fileName: sourceFile.fileName, - ...toContextSpan(textSpan, sourceFile, entry.context) - }; - } - } - - interface PrefixAndSuffix { readonly prefixText?: string; readonly suffixText?: string; } - function getPrefixAndSuffixText(entry: Entry, originalNode: Node, checker: TypeChecker): PrefixAndSuffix { - if (entry.kind !== EntryKind.Span && isIdentifier(originalNode)) { - const { node, kind } = entry; - const parent = node.parent; - const name = originalNode.text; - const isShorthandAssignment = isShorthandPropertyAssignment(parent); - if (isShorthandAssignment || (isObjectBindingElementWithoutPropertyName(parent) && parent.name === node && parent.dotDotDotToken === undefined)) { - const prefixColon: PrefixAndSuffix = { prefixText: name + ": " }; - const suffixColon: PrefixAndSuffix = { suffixText: ": " + name }; - if (kind === EntryKind.SearchedLocalFoundProperty) { - return prefixColon; - } - if (kind === EntryKind.SearchedPropertyFoundLocal) { - return suffixColon; - } - - // In `const o = { x }; o.x`, symbolAtLocation at `x` in `{ x }` is the property symbol. - // For a binding element `const { x } = o;`, symbolAtLocation at `x` is the property symbol. - if (isShorthandAssignment) { - const grandParent = parent.parent; - if (isObjectLiteralExpression(grandParent) && - isBinaryExpression(grandParent.parent) && - isModuleExportsAccessExpression(grandParent.parent.left)) { - return prefixColon; - } - return suffixColon; - } - else { +interface PrefixAndSuffix { readonly prefixText?: string; readonly suffixText?: string; } +function getPrefixAndSuffixText(entry: Entry, originalNode: Node, checker: TypeChecker): PrefixAndSuffix { + if (entry.kind !== EntryKind.Span && isIdentifier(originalNode)) { + const { node, kind } = entry; + const parent = node.parent; + const name = originalNode.text; + const isShorthandAssignment = isShorthandPropertyAssignment(parent); + if (isShorthandAssignment || (isObjectBindingElementWithoutPropertyName(parent) && parent.name === node && parent.dotDotDotToken === undefined)) { + const prefixColon: PrefixAndSuffix = { prefixText: name + ": " }; + const suffixColon: PrefixAndSuffix = { suffixText: ": " + name }; + if (kind === EntryKind.SearchedLocalFoundProperty) { + return prefixColon; + } + if (kind === EntryKind.SearchedPropertyFoundLocal) { + return suffixColon; + } + + // In `const o = { x }; o.x`, symbolAtLocation at `x` in `{ x }` is the property symbol. + // For a binding element `const { x } = o;`, symbolAtLocation at `x` is the property symbol. + if (isShorthandAssignment) { + const grandParent = parent.parent; + if (isObjectLiteralExpression(grandParent) && + isBinaryExpression(grandParent.parent) && + isModuleExportsAccessExpression(grandParent.parent.left)) { return prefixColon; } + return suffixColon; } - else if (isImportSpecifier(parent) && !parent.propertyName) { - // If the original symbol was using this alias, just rename the alias. - const originalSymbol = isExportSpecifier(originalNode.parent) ? checker.getExportSpecifierLocalTargetSymbol(originalNode.parent) : checker.getSymbolAtLocation(originalNode); - return contains(originalSymbol!.declarations, parent) ? { prefixText: name + " as " } : emptyOptions; - } - else if (isExportSpecifier(parent) && !parent.propertyName) { - // If the symbol for the node is same as declared node symbol use prefix text - return originalNode === entry.node || checker.getSymbolAtLocation(originalNode) === checker.getSymbolAtLocation(entry.node) ? - { prefixText: name + " as " } : - { suffixText: " as " + name }; + else { + return prefixColon; } } - - return emptyOptions; - } - - function toImplementationLocation(entry: Entry, checker: TypeChecker): ImplementationLocation { - const documentSpan = entryToDocumentSpan(entry); - if (entry.kind !== EntryKind.Span) { - const { node } = entry; - return { - ...documentSpan, - ...implementationKindDisplayParts(node, checker) - }; + else if (isImportSpecifier(parent) && !parent.propertyName) { + // If the original symbol was using this alias, just rename the alias. + const originalSymbol = isExportSpecifier(originalNode.parent) ? checker.getExportSpecifierLocalTargetSymbol(originalNode.parent) : checker.getSymbolAtLocation(originalNode); + return contains(originalSymbol!.declarations, parent) ? { prefixText: name + " as " } : emptyOptions; } - else { - return { ...documentSpan, kind: ScriptElementKind.unknown, displayParts: [] }; + else if (isExportSpecifier(parent) && !parent.propertyName) { + // If the symbol for the node is same as declared node symbol use prefix text + return originalNode === entry.node || checker.getSymbolAtLocation(originalNode) === checker.getSymbolAtLocation(entry.node) ? + { prefixText: name + " as " } : + { suffixText: " as " + name }; } } - function implementationKindDisplayParts(node: Node, checker: TypeChecker): { kind: ScriptElementKind, displayParts: SymbolDisplayPart[] } { - const symbol = checker.getSymbolAtLocation(isDeclaration(node) && node.name ? node.name : node); - if (symbol) { - return getDefinitionKindAndDisplayParts(symbol, checker, node); - } - else if (node.kind === SyntaxKind.ObjectLiteralExpression) { - return { - kind: ScriptElementKind.interfaceElement, - displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("object literal"), punctuationPart(SyntaxKind.CloseParenToken)] - }; - } - else if (node.kind === SyntaxKind.ClassExpression) { - return { - kind: ScriptElementKind.localClassElement, - displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("anonymous local class"), punctuationPart(SyntaxKind.CloseParenToken)] - }; - } - else { - return { kind: getNodeKind(node), displayParts: [] }; - } - } - - export function toHighlightSpan(entry: Entry): { fileName: string, span: HighlightSpan } { - const documentSpan = entryToDocumentSpan(entry); - if (entry.kind === EntryKind.Span) { - return { - fileName: documentSpan.fileName, - span: { - textSpan: documentSpan.textSpan, - kind: HighlightSpanKind.reference - } - }; - } + return emptyOptions; +} - const writeAccess = isWriteAccessForReference(entry.node); - const span: HighlightSpan = { - textSpan: documentSpan.textSpan, - kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference, - isInString: entry.kind === EntryKind.StringLiteral ? true : undefined, - ...documentSpan.contextSpan && { contextSpan: documentSpan.contextSpan } +function toImplementationLocation(entry: Entry, checker: TypeChecker): ImplementationLocation { + const documentSpan = entryToDocumentSpan(entry); + if (entry.kind !== EntryKind.Span) { + const { node } = entry; + return { + ...documentSpan, + ...implementationKindDisplayParts(node, checker) }; - return { fileName: documentSpan.fileName, span }; } - - function getTextSpan(node: Node, sourceFile: SourceFile, endNode?: Node): TextSpan { - let start = node.getStart(sourceFile); - let end = (endNode || node).getEnd(); - if (isStringLiteralLike(node)) { - Debug.assert(endNode === undefined); - start += 1; - end -= 1; - } - return createTextSpanFromBounds(start, end); + else { + return { ...documentSpan, kind: ScriptElementKind.unknown, displayParts: [] }; } +} - export function getTextSpanOfEntry(entry: Entry) { - return entry.kind === EntryKind.Span ? entry.textSpan : - getTextSpan(entry.node, entry.node.getSourceFile()); +function implementationKindDisplayParts(node: Node, checker: TypeChecker): { kind: ScriptElementKind, displayParts: SymbolDisplayPart[] } { + const symbol = checker.getSymbolAtLocation(isDeclaration(node) && node.name ? node.name : node); + if (symbol) { + return getDefinitionKindAndDisplayParts(symbol, checker, node); } - - /** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */ - function isWriteAccessForReference(node: Node): boolean { - const decl = getDeclarationFromName(node); - return !!decl && declarationIsWriteAccess(decl) || node.kind === SyntaxKind.DefaultKeyword || isWriteAccess(node); + else if (node.kind === SyntaxKind.ObjectLiteralExpression) { + return { + kind: ScriptElementKind.interfaceElement, + displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("object literal"), punctuationPart(SyntaxKind.CloseParenToken)] + }; } + else if (node.kind === SyntaxKind.ClassExpression) { + return { + kind: ScriptElementKind.localClassElement, + displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("anonymous local class"), punctuationPart(SyntaxKind.CloseParenToken)] + }; + } + else { + return { kind: getNodeKind(node), displayParts: [] }; + } +} - /** Whether a reference, `node`, is a definition of the `target` symbol */ - function isDeclarationOfSymbol(node: Node, target: Symbol | undefined): boolean { - if (!target) return false; - const source = getDeclarationFromName(node) || - (node.kind === SyntaxKind.DefaultKeyword ? node.parent - : isLiteralComputedPropertyDeclarationName(node) ? node.parent.parent - : node.kind === SyntaxKind.ConstructorKeyword && isConstructorDeclaration(node.parent) ? node.parent.parent - : undefined); - const commonjsSource = source && isBinaryExpression(source) ? source.left as unknown as Declaration : undefined; - return !!(source && target.declarations?.some(d => d === source || d === commonjsSource)); +export function toHighlightSpan(entry: Entry): { fileName: string, span: HighlightSpan } { + const documentSpan = entryToDocumentSpan(entry); + if (entry.kind === EntryKind.Span) { + return { + fileName: documentSpan.fileName, + span: { + textSpan: documentSpan.textSpan, + kind: HighlightSpanKind.reference + } + }; } - /** - * True if 'decl' provides a value, as in `function f() {}`; - * false if 'decl' is just a location for a future write, as in 'let x;' - */ - function declarationIsWriteAccess(decl: Declaration): boolean { - // Consider anything in an ambient declaration to be a write access since it may be coming from JS. - if (!!(decl.flags & NodeFlags.Ambient)) return true; - - switch (decl.kind) { - case SyntaxKind.BinaryExpression: - case SyntaxKind.BindingElement: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.DefaultKeyword: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.EnumMember: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.ImportClause: // default import - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JsxAttribute: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.NamespaceExportDeclaration: - case SyntaxKind.NamespaceImport: - case SyntaxKind.NamespaceExport: - case SyntaxKind.Parameter: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.TypeParameter: - return true; + const writeAccess = isWriteAccessForReference(entry.node); + const span: HighlightSpan = { + textSpan: documentSpan.textSpan, + kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference, + isInString: entry.kind === EntryKind.StringLiteral ? true : undefined, + ...documentSpan.contextSpan && { contextSpan: documentSpan.contextSpan } + }; + return { fileName: documentSpan.fileName, span }; +} - case SyntaxKind.PropertyAssignment: - // In `({ x: y } = 0);`, `x` is not a write access. (Won't call this function for `y`.) - return !isArrayLiteralOrObjectLiteralDestructuringPattern((decl as PropertyAssignment).parent); +function getTextSpan(node: Node, sourceFile: SourceFile, endNode?: Node): TextSpan { + let start = node.getStart(sourceFile); + let end = (endNode || node).getEnd(); + if (isStringLiteralLike(node)) { + Debug.assert(endNode === undefined); + start += 1; + end -= 1; + } + return createTextSpanFromBounds(start, end); +} - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.Constructor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return !!(decl as FunctionDeclaration | FunctionExpression | ConstructorDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration).body; +export function getTextSpanOfEntry(entry: Entry) { + return entry.kind === EntryKind.Span ? entry.textSpan : + getTextSpan(entry.node, entry.node.getSourceFile()); +} - case SyntaxKind.VariableDeclaration: - case SyntaxKind.PropertyDeclaration: - return !!(decl as VariableDeclaration | PropertyDeclaration).initializer || isCatchClause(decl.parent); +/** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */ +function isWriteAccessForReference(node: Node): boolean { + const decl = getDeclarationFromName(node); + return !!decl && declarationIsWriteAccess(decl) || node.kind === SyntaxKind.DefaultKeyword || isWriteAccess(node); +} - case SyntaxKind.MethodSignature: - case SyntaxKind.PropertySignature: - case SyntaxKind.JSDocPropertyTag: - case SyntaxKind.JSDocParameterTag: - return false; +/** Whether a reference, `node`, is a definition of the `target` symbol */ +function isDeclarationOfSymbol(node: Node, target: Symbol | undefined): boolean { + if (!target) return false; + const source = getDeclarationFromName(node) || + (node.kind === SyntaxKind.DefaultKeyword ? node.parent + : isLiteralComputedPropertyDeclarationName(node) ? node.parent.parent + : node.kind === SyntaxKind.ConstructorKeyword && isConstructorDeclaration(node.parent) ? node.parent.parent + : undefined); + const commonjsSource = source && isBinaryExpression(source) ? source.left as unknown as Declaration : undefined; + return !!(source && target.declarations?.some(d => d === source || d === commonjsSource)); +} - default: - return Debug.failBadSyntaxKind(decl); - } +/** + * True if 'decl' provides a value, as in `function f() {}`; + * false if 'decl' is just a location for a future write, as in 'let x;' + */ +function declarationIsWriteAccess(decl: Declaration): boolean { + // Consider anything in an ambient declaration to be a write access since it may be coming from JS. + if (!!(decl.flags & NodeFlags.Ambient)) return true; + + switch (decl.kind) { + case SyntaxKind.BinaryExpression: + case SyntaxKind.BindingElement: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.DefaultKeyword: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.EnumMember: + case SyntaxKind.ExportSpecifier: + case SyntaxKind.ImportClause: // default import + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JsxAttribute: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.NamespaceExportDeclaration: + case SyntaxKind.NamespaceImport: + case SyntaxKind.NamespaceExport: + case SyntaxKind.Parameter: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.TypeParameter: + return true; + + case SyntaxKind.PropertyAssignment: + // In `({ x: y } = 0);`, `x` is not a write access. (Won't call this function for `y`.) + return !isArrayLiteralOrObjectLiteralDestructuringPattern((decl as PropertyAssignment).parent); + + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return !!(decl as FunctionDeclaration | FunctionExpression | ConstructorDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration).body; + + case SyntaxKind.VariableDeclaration: + case SyntaxKind.PropertyDeclaration: + return !!(decl as VariableDeclaration | PropertyDeclaration).initializer || isCatchClause(decl.parent); + + case SyntaxKind.MethodSignature: + case SyntaxKind.PropertySignature: + case SyntaxKind.JSDocPropertyTag: + case SyntaxKind.JSDocParameterTag: + return false; + + default: + return Debug.failBadSyntaxKind(decl); } +} - /** Encapsulates the core find-all-references algorithm. */ - export namespace Core { - /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ - export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly SymbolAndEntries[] | undefined { - if (options.use === FindReferencesUse.References) { - node = getAdjustedReferenceLocation(node); +/** Encapsulates the core find-all-references algorithm. */ +export namespace Core { + /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ + export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly SymbolAndEntries[] | undefined { + if (options.use === FindReferencesUse.References) { + node = getAdjustedReferenceLocation(node); + } + else if (options.use === FindReferencesUse.Rename) { + node = getAdjustedRenameLocation(node); + } + if (isSourceFile(node)) { + const resolvedRef = GoToDefinition.getReferenceAtPosition(node, position, program); + if (!resolvedRef?.file) { + return undefined; } - else if (options.use === FindReferencesUse.Rename) { - node = getAdjustedRenameLocation(node); + const moduleSymbol = program.getTypeChecker().getMergedSymbol(resolvedRef.file.symbol); + if (moduleSymbol) { + return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); } - if (isSourceFile(node)) { - const resolvedRef = GoToDefinition.getReferenceAtPosition(node, position, program); - if (!resolvedRef?.file) { - return undefined; - } - const moduleSymbol = program.getTypeChecker().getMergedSymbol(resolvedRef.file.symbol); - if (moduleSymbol) { - return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); - } - const fileIncludeReasons = program.getFileIncludeReasons(); - if (!fileIncludeReasons) { - return undefined; - } - return [{ - definition: { type: DefinitionKind.TripleSlashReference, reference: resolvedRef.reference, file: node }, - references: getReferencesForNonModule(resolvedRef.file, fileIncludeReasons, program) || emptyArray - }]; + const fileIncludeReasons = program.getFileIncludeReasons(); + if (!fileIncludeReasons) { + return undefined; } + return [{ + definition: { type: DefinitionKind.TripleSlashReference, reference: resolvedRef.reference, file: node }, + references: getReferencesForNonModule(resolvedRef.file, fileIncludeReasons, program) || emptyArray + }]; + } - if (!options.implementations) { - const special = getReferencedSymbolsSpecial(node, sourceFiles, cancellationToken); - if (special) { - return special; - } + if (!options.implementations) { + const special = getReferencedSymbolsSpecial(node, sourceFiles, cancellationToken); + if (special) { + return special; } + } - const checker = program.getTypeChecker(); - // constructors should use the class symbol, detected by name, if present - const symbol = checker.getSymbolAtLocation(isConstructorDeclaration(node) && node.parent.name || node); - - // Could not find a symbol e.g. unknown identifier - if (!symbol) { - // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. - if (!options.implementations && isStringLiteralLike(node)) { - if (isModuleSpecifierLike(node)) { - const fileIncludeReasons = program.getFileIncludeReasons(); - const referencedFileName = node.getSourceFile().resolvedModules?.get(node.text, getModeForUsageLocation(node.getSourceFile(), node))?.resolvedFileName; - const referencedFile = referencedFileName ? program.getSourceFile(referencedFileName) : undefined; - if (referencedFile) { - return [{ definition: { type: DefinitionKind.String, node }, references: getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray }]; - } - // Fall through to string literal references. This is not very likely to return - // anything useful, but I guess it's better than nothing, and there's an existing - // test that expects this to happen (fourslash/cases/untypedModuleImport.ts). + const checker = program.getTypeChecker(); + // constructors should use the class symbol, detected by name, if present + const symbol = checker.getSymbolAtLocation(isConstructorDeclaration(node) && node.parent.name || node); + + // Could not find a symbol e.g. unknown identifier + if (!symbol) { + // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. + if (!options.implementations && isStringLiteralLike(node)) { + if (isModuleSpecifierLike(node)) { + const fileIncludeReasons = program.getFileIncludeReasons(); + const referencedFileName = node.getSourceFile().resolvedModules?.get(node.text, getModeForUsageLocation(node.getSourceFile(), node))?.resolvedFileName; + const referencedFile = referencedFileName ? program.getSourceFile(referencedFileName) : undefined; + if (referencedFile) { + return [{ definition: { type: DefinitionKind.String, node }, references: getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray }]; } - return getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken); + // Fall through to string literal references. This is not very likely to return + // anything useful, but I guess it's better than nothing, and there's an existing + // test that expects this to happen (fourslash/cases/untypedModuleImport.ts). } - return undefined; + return getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken); } + return undefined; + } - if (symbol.escapedName === InternalSymbolName.ExportEquals) { - return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); - } + if (symbol.escapedName === InternalSymbolName.ExportEquals) { + return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); + } - const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); - if (moduleReferences && !(symbol.flags & SymbolFlags.Transient)) { - return moduleReferences; - } + const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); + if (moduleReferences && !(symbol.flags & SymbolFlags.Transient)) { + return moduleReferences; + } + + const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker); + const moduleReferencesOfExportTarget = aliasedSymbol && + getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); - const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker); - const moduleReferencesOfExportTarget = aliasedSymbol && - getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); + const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options); + return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget); + } - const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options); - return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget); + export function getReferencesForFileName(fileName: string, program: Program, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly Entry[] { + const moduleSymbol = program.getSourceFile(fileName)?.symbol; + if (moduleSymbol) { + return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet)[0]?.references || emptyArray; } + const fileIncludeReasons = program.getFileIncludeReasons(); + const referencedFile = program.getSourceFile(fileName); + return referencedFile && fileIncludeReasons && getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray; + } - export function getReferencesForFileName(fileName: string, program: Program, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly Entry[] { - const moduleSymbol = program.getSourceFile(fileName)?.symbol; - if (moduleSymbol) { - return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet)[0]?.references || emptyArray; - } - const fileIncludeReasons = program.getFileIncludeReasons(); - const referencedFile = program.getSourceFile(fileName); - return referencedFile && fileIncludeReasons && getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray; - } - - function getReferencesForNonModule(referencedFile: SourceFile, refFileMap: MultiMap, program: Program): readonly SpanEntry[] | undefined { - let entries: SpanEntry[] | undefined; - const references = refFileMap.get(referencedFile.path) || emptyArray; - for (const ref of references) { - if (isReferencedFile(ref)) { - const referencingFile = program.getSourceFileByPath(ref.file)!; - const location = getReferencedFileLocation(program.getSourceFileByPath, ref); - if (isReferenceFileLocation(location)) { - entries = append(entries, { - kind: EntryKind.Span, - fileName: referencingFile.fileName, - textSpan: createTextSpanFromRange(location) - }); - } + function getReferencesForNonModule(referencedFile: SourceFile, refFileMap: MultiMap, program: Program): readonly SpanEntry[] | undefined { + let entries: SpanEntry[] | undefined; + const references = refFileMap.get(referencedFile.path) || emptyArray; + for (const ref of references) { + if (isReferencedFile(ref)) { + const referencingFile = program.getSourceFileByPath(ref.file)!; + const location = getReferencedFileLocation(program.getSourceFileByPath, ref); + if (isReferenceFileLocation(location)) { + entries = append(entries, { + kind: EntryKind.Span, + fileName: referencingFile.fileName, + textSpan: createTextSpanFromRange(location) + }); } } - return entries; } + return entries; + } - function getMergedAliasedSymbolOfNamespaceExportDeclaration(node: Node, symbol: Symbol, checker: TypeChecker) { - if (node.parent && isNamespaceExportDeclaration(node.parent)) { - const aliasedSymbol = checker.getAliasedSymbol(symbol); - const targetSymbol = checker.getMergedSymbol(aliasedSymbol); - if (aliasedSymbol !== targetSymbol) { - return targetSymbol; - } + function getMergedAliasedSymbolOfNamespaceExportDeclaration(node: Node, symbol: Symbol, checker: TypeChecker) { + if (node.parent && isNamespaceExportDeclaration(node.parent)) { + const aliasedSymbol = checker.getAliasedSymbol(symbol); + const targetSymbol = checker.getMergedSymbol(aliasedSymbol); + if (aliasedSymbol !== targetSymbol) { + return targetSymbol; } - return undefined; } + return undefined; + } - function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlySet) { - const moduleSourceFile = (symbol.flags & SymbolFlags.Module) && symbol.declarations && find(symbol.declarations, isSourceFile); - if (!moduleSourceFile) return undefined; - const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals); - // If !!exportEquals, we're about to add references to `import("mod")` anyway, so don't double-count them. - const moduleReferences = getReferencedSymbolsForModule(program, symbol, !!exportEquals, sourceFiles, sourceFilesSet); - if (!exportEquals || !sourceFilesSet.has(moduleSourceFile.fileName)) return moduleReferences; - // Continue to get references to 'export ='. - const checker = program.getTypeChecker(); - symbol = skipAlias(exportEquals, checker); - return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options)); - } + function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlySet) { + const moduleSourceFile = (symbol.flags & SymbolFlags.Module) && symbol.declarations && find(symbol.declarations, isSourceFile); + if (!moduleSourceFile) return undefined; + const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals); + // If !!exportEquals, we're about to add references to `import("mod")` anyway, so don't double-count them. + const moduleReferences = getReferencedSymbolsForModule(program, symbol, !!exportEquals, sourceFiles, sourceFilesSet); + if (!exportEquals || !sourceFilesSet.has(moduleSourceFile.fileName)) return moduleReferences; + // Continue to get references to 'export ='. + const checker = program.getTypeChecker(); + symbol = skipAlias(exportEquals, checker); + return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options)); + } - /** - * Merges the references by sorting them (by file index in sourceFiles and their location in it) that point to same definition symbol - */ - function mergeReferences(program: Program, ...referencesToMerge: (SymbolAndEntries[] | undefined)[]): SymbolAndEntries[] | undefined { - let result: SymbolAndEntries[] | undefined; - for (const references of referencesToMerge) { - if (!references || !references.length) continue; - if (!result) { - result = references; + /** + * Merges the references by sorting them (by file index in sourceFiles and their location in it) that point to same definition symbol + */ + function mergeReferences(program: Program, ...referencesToMerge: (SymbolAndEntries[] | undefined)[]): SymbolAndEntries[] | undefined { + let result: SymbolAndEntries[] | undefined; + for (const references of referencesToMerge) { + if (!references || !references.length) continue; + if (!result) { + result = references; + continue; + } + for (const entry of references) { + if (!entry.definition || entry.definition.type !== DefinitionKind.Symbol) { + result.push(entry); + continue; + } + const symbol = entry.definition.symbol; + const refIndex = findIndex(result, ref => !!ref.definition && + ref.definition.type === DefinitionKind.Symbol && + ref.definition.symbol === symbol); + if (refIndex === -1) { + result.push(entry); continue; } - for (const entry of references) { - if (!entry.definition || entry.definition.type !== DefinitionKind.Symbol) { - result.push(entry); - continue; - } - const symbol = entry.definition.symbol; - const refIndex = findIndex(result, ref => !!ref.definition && - ref.definition.type === DefinitionKind.Symbol && - ref.definition.symbol === symbol); - if (refIndex === -1) { - result.push(entry); - continue; - } - const reference = result[refIndex]; - result[refIndex] = { - definition: reference.definition, - references: reference.references.concat(entry.references).sort((entry1, entry2) => { - const entry1File = getSourceFileIndexOfEntry(program, entry1); - const entry2File = getSourceFileIndexOfEntry(program, entry2); - if (entry1File !== entry2File) { - return compareValues(entry1File, entry2File); - } + const reference = result[refIndex]; + result[refIndex] = { + definition: reference.definition, + references: reference.references.concat(entry.references).sort((entry1, entry2) => { + const entry1File = getSourceFileIndexOfEntry(program, entry1); + const entry2File = getSourceFileIndexOfEntry(program, entry2); + if (entry1File !== entry2File) { + return compareValues(entry1File, entry2File); + } - const entry1Span = getTextSpanOfEntry(entry1); - const entry2Span = getTextSpanOfEntry(entry2); - return entry1Span.start !== entry2Span.start ? - compareValues(entry1Span.start, entry2Span.start) : - compareValues(entry1Span.length, entry2Span.length); - }) - }; - } + const entry1Span = getTextSpanOfEntry(entry1); + const entry2Span = getTextSpanOfEntry(entry2); + return entry1Span.start !== entry2Span.start ? + compareValues(entry1Span.start, entry2Span.start) : + compareValues(entry1Span.length, entry2Span.length); + }) + }; } - return result; } + return result; + } - function getSourceFileIndexOfEntry(program: Program, entry: Entry) { - const sourceFile = entry.kind === EntryKind.Span ? - program.getSourceFile(entry.fileName)! : - entry.node.getSourceFile(); - return program.getSourceFiles().indexOf(sourceFile); - } + function getSourceFileIndexOfEntry(program: Program, entry: Entry) { + const sourceFile = entry.kind === EntryKind.Span ? + program.getSourceFile(entry.fileName)! : + entry.node.getSourceFile(); + return program.getSourceFiles().indexOf(sourceFile); + } - function getReferencedSymbolsForModule(program: Program, symbol: Symbol, excludeImportTypeOfExportEquals: boolean, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet): SymbolAndEntries[] { - Debug.assert(!!symbol.valueDeclaration); + function getReferencedSymbolsForModule(program: Program, symbol: Symbol, excludeImportTypeOfExportEquals: boolean, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet): SymbolAndEntries[] { + Debug.assert(!!symbol.valueDeclaration); - const references = mapDefined(findModuleReferences(program, sourceFiles, symbol), reference => { - if (reference.kind === "import") { - const parent = reference.literal.parent; - if (isLiteralTypeNode(parent)) { - const importType = cast(parent.parent, isImportTypeNode); - if (excludeImportTypeOfExportEquals && !importType.qualifier) { - return undefined; - } + const references = mapDefined(findModuleReferences(program, sourceFiles, symbol), reference => { + if (reference.kind === "import") { + const parent = reference.literal.parent; + if (isLiteralTypeNode(parent)) { + const importType = cast(parent.parent, isImportTypeNode); + if (excludeImportTypeOfExportEquals && !importType.qualifier) { + return undefined; } - // import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway. - return nodeEntry(reference.literal); - } - else { - return { - kind: EntryKind.Span, - fileName: reference.referencingFile.fileName, - textSpan: createTextSpanFromRange(reference.ref), - }; } - }); + // import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway. + return nodeEntry(reference.literal); + } + else { + return { + kind: EntryKind.Span, + fileName: reference.referencingFile.fileName, + textSpan: createTextSpanFromRange(reference.ref), + }; + } + }); - if (symbol.declarations) { - for (const decl of symbol.declarations) { - switch (decl.kind) { - case SyntaxKind.SourceFile: - // Don't include the source file itself. (This may not be ideal behavior, but awkward to include an entire file as a reference.) - break; - case SyntaxKind.ModuleDeclaration: - if (sourceFilesSet.has(decl.getSourceFile().fileName)) { - references.push(nodeEntry((decl as ModuleDeclaration).name)); - } - break; - default: - // This may be merged with something. - Debug.assert(!!(symbol.flags & SymbolFlags.Transient), "Expected a module symbol to be declared by a SourceFile or ModuleDeclaration."); - } + if (symbol.declarations) { + for (const decl of symbol.declarations) { + switch (decl.kind) { + case SyntaxKind.SourceFile: + // Don't include the source file itself. (This may not be ideal behavior, but awkward to include an entire file as a reference.) + break; + case SyntaxKind.ModuleDeclaration: + if (sourceFilesSet.has(decl.getSourceFile().fileName)) { + references.push(nodeEntry((decl as ModuleDeclaration).name)); + } + break; + default: + // This may be merged with something. + Debug.assert(!!(symbol.flags & SymbolFlags.Transient), "Expected a module symbol to be declared by a SourceFile or ModuleDeclaration."); } } + } - const exported = symbol.exports!.get(InternalSymbolName.ExportEquals); - if (exported?.declarations) { - for (const decl of exported.declarations) { - const sourceFile = decl.getSourceFile(); - if (sourceFilesSet.has(sourceFile.fileName)) { - // At `module.exports = ...`, reference node is `module` - const node = isBinaryExpression(decl) && isPropertyAccessExpression(decl.left) ? decl.left.expression : - isExportAssignment(decl) ? Debug.checkDefined(findChildOfKind(decl, SyntaxKind.ExportKeyword, sourceFile)) : - getNameOfDeclaration(decl) || decl; - references.push(nodeEntry(node)); - } + const exported = symbol.exports!.get(InternalSymbolName.ExportEquals); + if (exported?.declarations) { + for (const decl of exported.declarations) { + const sourceFile = decl.getSourceFile(); + if (sourceFilesSet.has(sourceFile.fileName)) { + // At `module.exports = ...`, reference node is `module` + const node = isBinaryExpression(decl) && isPropertyAccessExpression(decl.left) ? decl.left.expression : + isExportAssignment(decl) ? Debug.checkDefined(findChildOfKind(decl, SyntaxKind.ExportKeyword, sourceFile)) : + getNameOfDeclaration(decl) || decl; + references.push(nodeEntry(node)); } } - - return references.length ? [{ definition: { type: DefinitionKind.Symbol, symbol }, references }] : emptyArray; } - /** As in a `readonly prop: any` or `constructor(readonly prop: any)`, not a `readonly any[]`. */ - function isReadonlyTypeOperator(node: Node): boolean { - return node.kind === SyntaxKind.ReadonlyKeyword - && isTypeOperatorNode(node.parent) - && node.parent.operator === SyntaxKind.ReadonlyKeyword; - } + return references.length ? [{ definition: { type: DefinitionKind.Symbol, symbol }, references }] : emptyArray; + } - /** getReferencedSymbols for special node kinds. */ - function getReferencedSymbolsSpecial(node: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { - if (isTypeKeyword(node.kind)) { - // A void expression (i.e., `void foo()`) is not special, but the `void` type is. - if (node.kind === SyntaxKind.VoidKeyword && isVoidExpression(node.parent)) { - return undefined; - } + /** As in a `readonly prop: any` or `constructor(readonly prop: any)`, not a `readonly any[]`. */ + function isReadonlyTypeOperator(node: Node): boolean { + return node.kind === SyntaxKind.ReadonlyKeyword + && isTypeOperatorNode(node.parent) + && node.parent.operator === SyntaxKind.ReadonlyKeyword; + } - // A modifier readonly (like on a property declaration) is not special; - // a readonly type keyword (like `readonly string[]`) is. - if (node.kind === SyntaxKind.ReadonlyKeyword && !isReadonlyTypeOperator(node)) { - return undefined; - } - // Likewise, when we *are* looking for a special keyword, make sure we - // *don’t* include readonly member modifiers. - return getAllReferencesForKeyword( - sourceFiles, - node.kind, - cancellationToken, - node.kind === SyntaxKind.ReadonlyKeyword ? isReadonlyTypeOperator : undefined); + /** getReferencedSymbols for special node kinds. */ + function getReferencedSymbolsSpecial(node: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { + if (isTypeKeyword(node.kind)) { + // A void expression (i.e., `void foo()`) is not special, but the `void` type is. + if (node.kind === SyntaxKind.VoidKeyword && isVoidExpression(node.parent)) { + return undefined; } - if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) { - return [{ definition: { type: DefinitionKind.Keyword, node }, references: [nodeEntry(node)] }]; + // A modifier readonly (like on a property declaration) is not special; + // a readonly type keyword (like `readonly string[]`) is. + if (node.kind === SyntaxKind.ReadonlyKeyword && !isReadonlyTypeOperator(node)) { + return undefined; } + // Likewise, when we *are* looking for a special keyword, make sure we + // *don’t* include readonly member modifiers. + return getAllReferencesForKeyword( + sourceFiles, + node.kind, + cancellationToken, + node.kind === SyntaxKind.ReadonlyKeyword ? isReadonlyTypeOperator : undefined); + } - // Labels - if (isJumpStatementTarget(node)) { - const labelDefinition = getTargetLabel(node.parent, node.text); - // if we have a label definition, look within its statement for references, if not, then - // the label is undefined and we have no results.. - return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition); - } - else if (isLabelOfLabeledStatement(node)) { - // it is a label definition and not a target, search within the parent labeledStatement - return getLabelReferencesInNode(node.parent, node); - } + if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) { + return [{ definition: { type: DefinitionKind.Keyword, node }, references: [nodeEntry(node)] }]; + } - if (isThis(node)) { - return getReferencesForThisKeyword(node, sourceFiles, cancellationToken); - } + // Labels + if (isJumpStatementTarget(node)) { + const labelDefinition = getTargetLabel(node.parent, node.text); + // if we have a label definition, look within its statement for references, if not, then + // the label is undefined and we have no results.. + return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition); + } + else if (isLabelOfLabeledStatement(node)) { + // it is a label definition and not a target, search within the parent labeledStatement + return getLabelReferencesInNode(node.parent, node); + } - if (node.kind === SyntaxKind.SuperKeyword) { - return getReferencesForSuperKeyword(node); - } + if (isThis(node)) { + return getReferencesForThisKeyword(node, sourceFiles, cancellationToken); + } - return undefined; + if (node.kind === SyntaxKind.SuperKeyword) { + return getReferencesForSuperKeyword(node); } - /** Core find-all-references algorithm for a normal symbol. */ - function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] { - const symbol = node && skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker, /*useLocalSymbolForExportSpecifier*/ !isForRenameWithPrefixAndSuffixText(options)) || originalSymbol; + return undefined; + } - // Compute the meaning from the location and the symbol it references - const searchMeaning = node ? getIntersectingMeaningFromDeclarations(node, symbol) : SemanticMeaning.All; - const result: SymbolAndEntries[] = []; - const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, searchMeaning, options, result); + /** Core find-all-references algorithm for a normal symbol. */ + function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] { + const symbol = node && skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker, /*useLocalSymbolForExportSpecifier*/ !isForRenameWithPrefixAndSuffixText(options)) || originalSymbol; - const exportSpecifier = !isForRenameWithPrefixAndSuffixText(options) || !symbol.declarations ? undefined : find(symbol.declarations, isExportSpecifier); - if (exportSpecifier) { - // When renaming at an export specifier, rename the export and not the thing being exported. - getReferencesAtExportSpecifier(exportSpecifier.name, symbol, exportSpecifier, state.createSearch(node, originalSymbol, /*comingFrom*/ undefined), state, /*addReferencesHere*/ true, /*alwaysGetReferences*/ true); - } - else if (node && node.kind === SyntaxKind.DefaultKeyword && symbol.escapedName === InternalSymbolName.Default && symbol.parent) { - addReference(node, symbol, state); - searchForImportsOfExport(node, symbol, { exportingModuleSymbol: symbol.parent, exportKind: ExportKind.Default }, state); - } - else { - const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, options.use === FindReferencesUse.Rename, !!options.providePrefixAndSuffixTextForRename, !!options.implementations) : [symbol] }); - getReferencesInContainerOrFiles(symbol, state, search); - } + // Compute the meaning from the location and the symbol it references + const searchMeaning = node ? getIntersectingMeaningFromDeclarations(node, symbol) : SemanticMeaning.All; + const result: SymbolAndEntries[] = []; + const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, searchMeaning, options, result); - return result; + const exportSpecifier = !isForRenameWithPrefixAndSuffixText(options) || !symbol.declarations ? undefined : find(symbol.declarations, isExportSpecifier); + if (exportSpecifier) { + // When renaming at an export specifier, rename the export and not the thing being exported. + getReferencesAtExportSpecifier(exportSpecifier.name, symbol, exportSpecifier, state.createSearch(node, originalSymbol, /*comingFrom*/ undefined), state, /*addReferencesHere*/ true, /*alwaysGetReferences*/ true); } - - function getReferencesInContainerOrFiles(symbol: Symbol, state: State, search: Search): void { - // Try to get the smallest valid scope that we can limit our search to; - // otherwise we'll need to search globally (i.e. include each file). - const scope = getSymbolScope(symbol); - if (scope) { - getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(state.sourceFiles, scope))); - } - else { - // Global search - for (const sourceFile of state.sourceFiles) { - state.cancellationToken.throwIfCancellationRequested(); - searchForName(sourceFile, search, state); - } - } + else if (node && node.kind === SyntaxKind.DefaultKeyword && symbol.escapedName === InternalSymbolName.Default && symbol.parent) { + addReference(node, symbol, state); + searchForImportsOfExport(node, symbol, { exportingModuleSymbol: symbol.parent, exportKind: ExportKind.Default }, state); + } + else { + const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, options.use === FindReferencesUse.Rename, !!options.providePrefixAndSuffixTextForRename, !!options.implementations) : [symbol] }); + getReferencesInContainerOrFiles(symbol, state, search); } - function getSpecialSearchKind(node: Node): SpecialSearchKind { - switch (node.kind) { - case SyntaxKind.Constructor: - case SyntaxKind.ConstructorKeyword: - return SpecialSearchKind.Constructor; - case SyntaxKind.Identifier: - if (isClassLike(node.parent)) { - Debug.assert(node.parent.name === node); - return SpecialSearchKind.Class; - } - // falls through - default: - return SpecialSearchKind.None; + return result; + } + + function getReferencesInContainerOrFiles(symbol: Symbol, state: State, search: Search): void { + // Try to get the smallest valid scope that we can limit our search to; + // otherwise we'll need to search globally (i.e. include each file). + const scope = getSymbolScope(symbol); + if (scope) { + getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(state.sourceFiles, scope))); + } + else { + // Global search + for (const sourceFile of state.sourceFiles) { + state.cancellationToken.throwIfCancellationRequested(); + searchForName(sourceFile, search, state); } } + } - /** Handle a few special cases relating to export/import specifiers. */ - function skipPastExportOrImportSpecifierOrUnion(symbol: Symbol, node: Node, checker: TypeChecker, useLocalSymbolForExportSpecifier: boolean): Symbol | undefined { - const { parent } = node; - if (isExportSpecifier(parent) && useLocalSymbolForExportSpecifier) { - return getLocalSymbolForExportSpecifier(node as Identifier, symbol, parent, checker); - } - // If the symbol is declared as part of a declaration like `{ type: "a" } | { type: "b" }`, use the property on the union type to get more references. - return firstDefined(symbol.declarations, decl => { - if (!decl.parent) { - // Ignore UMD module and global merge - if (symbol.flags & SymbolFlags.Transient) return undefined; - // Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here. - Debug.fail(`Unexpected symbol at ${Debug.formatSyntaxKind(node.kind)}: ${Debug.formatSymbol(symbol)}`); - } - return isTypeLiteralNode(decl.parent) && isUnionTypeNode(decl.parent.parent) - ? checker.getPropertyOfType(checker.getTypeFromTypeNode(decl.parent.parent), symbol.name) - : undefined; - }); + function getSpecialSearchKind(node: Node): SpecialSearchKind { + switch (node.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.ConstructorKeyword: + return SpecialSearchKind.Constructor; + case SyntaxKind.Identifier: + if (isClassLike(node.parent)) { + Debug.assert(node.parent.name === node); + return SpecialSearchKind.Class; + } + // falls through + default: + return SpecialSearchKind.None; } + } + + /** Handle a few special cases relating to export/import specifiers. */ + function skipPastExportOrImportSpecifierOrUnion(symbol: Symbol, node: Node, checker: TypeChecker, useLocalSymbolForExportSpecifier: boolean): Symbol | undefined { + const { parent } = node; + if (isExportSpecifier(parent) && useLocalSymbolForExportSpecifier) { + return getLocalSymbolForExportSpecifier(node as Identifier, symbol, parent, checker); + } + // If the symbol is declared as part of a declaration like `{ type: "a" } | { type: "b" }`, use the property on the union type to get more references. + return firstDefined(symbol.declarations, decl => { + if (!decl.parent) { + // Ignore UMD module and global merge + if (symbol.flags & SymbolFlags.Transient) return undefined; + // Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here. + Debug.fail(`Unexpected symbol at ${Debug.formatSyntaxKind(node.kind)}: ${Debug.formatSymbol(symbol)}`); + } + return isTypeLiteralNode(decl.parent) && isUnionTypeNode(decl.parent.parent) + ? checker.getPropertyOfType(checker.getTypeFromTypeNode(decl.parent.parent), symbol.name) + : undefined; + }); + } + + /** + * Symbol that is currently being searched for. + * This will be replaced if we find an alias for the symbol. + */ + interface Search { + /** If coming from an export, we will not recursively search for the imported symbol (since that's where we came from). */ + readonly comingFrom?: ImportExport; + + readonly symbol: Symbol; + readonly text: string; + readonly escapedText: __String; + /** Only set if `options.implementations` is true. These are the symbols checked to get the implementations of a property access. */ + readonly parents: readonly Symbol[] | undefined; + readonly allSearchSymbols: readonly Symbol[]; /** - * Symbol that is currently being searched for. - * This will be replaced if we find an alias for the symbol. + * Whether a symbol is in the search set. + * Do not compare directly to `symbol` because there may be related symbols to search for. See `populateSearchSymbolSet`. */ - interface Search { - /** If coming from an export, we will not recursively search for the imported symbol (since that's where we came from). */ - readonly comingFrom?: ImportExport; + includes(symbol: Symbol): boolean; + } - readonly symbol: Symbol; - readonly text: string; - readonly escapedText: __String; - /** Only set if `options.implementations` is true. These are the symbols checked to get the implementations of a property access. */ - readonly parents: readonly Symbol[] | undefined; - readonly allSearchSymbols: readonly Symbol[]; + const enum SpecialSearchKind { + None, + Constructor, + Class, + } - /** - * Whether a symbol is in the search set. - * Do not compare directly to `symbol` because there may be related symbols to search for. See `populateSearchSymbolSet`. - */ - includes(symbol: Symbol): boolean; - } + function getNonModuleSymbolOfMergedModuleSymbol(symbol: Symbol) { + if (!(symbol.flags & (SymbolFlags.Module | SymbolFlags.Transient))) return undefined; + const decl = symbol.declarations && find(symbol.declarations, d => !isSourceFile(d) && !isModuleDeclaration(d)); + return decl && decl.symbol; + } - const enum SpecialSearchKind { - None, - Constructor, - Class, - } + /** + * Holds all state needed for the finding references. + * Unlike `Search`, there is only one `State`. + */ + class State { + /** Cache for `explicitlyinheritsFrom`. */ + readonly inheritsFromCache = new Map(); - function getNonModuleSymbolOfMergedModuleSymbol(symbol: Symbol) { - if (!(symbol.flags & (SymbolFlags.Module | SymbolFlags.Transient))) return undefined; - const decl = symbol.declarations && find(symbol.declarations, d => !isSourceFile(d) && !isModuleDeclaration(d)); - return decl && decl.symbol; - } + /** + * Type nodes can contain multiple references to the same type. For example: + * let x: Foo & (Foo & Bar) = ... + * Because we are returning the implementation locations and not the identifier locations, + * duplicate entries would be returned here as each of the type references is part of + * the same implementation. For that reason, check before we add a new entry. + */ + readonly markSeenContainingTypeReference = nodeSeenTracker(); /** - * Holds all state needed for the finding references. - * Unlike `Search`, there is only one `State`. + * It's possible that we will encounter the right side of `export { foo as bar } from "x";` more than once. + * For example: + * // b.ts + * export { foo as bar } from "./a"; + * import { bar } from "./b"; + * + * Normally at `foo as bar` we directly add `foo` and do not locally search for it (since it doesn't declare a local). + * But another reference to it may appear in the same source file. + * See `tests/cases/fourslash/transitiveExportImports3.ts`. */ - class State { - /** Cache for `explicitlyinheritsFrom`. */ - readonly inheritsFromCache = new Map(); - - /** - * Type nodes can contain multiple references to the same type. For example: - * let x: Foo & (Foo & Bar) = ... - * Because we are returning the implementation locations and not the identifier locations, - * duplicate entries would be returned here as each of the type references is part of - * the same implementation. For that reason, check before we add a new entry. - */ - readonly markSeenContainingTypeReference = nodeSeenTracker(); - - /** - * It's possible that we will encounter the right side of `export { foo as bar } from "x";` more than once. - * For example: - * // b.ts - * export { foo as bar } from "./a"; - * import { bar } from "./b"; - * - * Normally at `foo as bar` we directly add `foo` and do not locally search for it (since it doesn't declare a local). - * But another reference to it may appear in the same source file. - * See `tests/cases/fourslash/transitiveExportImports3.ts`. - */ - readonly markSeenReExportRHS = nodeSeenTracker(); - - constructor( - readonly sourceFiles: readonly SourceFile[], - readonly sourceFilesSet: ReadonlySet, - readonly specialSearchKind: SpecialSearchKind, - readonly checker: TypeChecker, - readonly cancellationToken: CancellationToken, - readonly searchMeaning: SemanticMeaning, - readonly options: Options, - private readonly result: Push) { - } - - includesSourceFile(sourceFile: SourceFile): boolean { - return this.sourceFilesSet.has(sourceFile.fileName); - } - - private importTracker: ImportTracker | undefined; - /** Gets every place to look for references of an exported symbols. See `ImportsResult` in `importTracker.ts` for more documentation. */ - getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult { - if (!this.importTracker) this.importTracker = createImportTracker(this.sourceFiles, this.sourceFilesSet, this.checker, this.cancellationToken); - return this.importTracker(exportSymbol, exportInfo, this.options.use === FindReferencesUse.Rename); - } - - /** @param allSearchSymbols set of additional symbols for use by `includes`. */ - createSearch(location: Node | undefined, symbol: Symbol, comingFrom: ImportExport | undefined, searchOptions: { text?: string, allSearchSymbols?: Symbol[] } = {}): Search { - // Note: if this is an external module symbol, the name doesn't include quotes. - // Note: getLocalSymbolForExportDefault handles `export default class C {}`, but not `export default C` or `export { C as default }`. - // The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form - // here appears to be intentional). - const { - text = stripQuotes(symbolName(getLocalSymbolForExportDefault(symbol) || getNonModuleSymbolOfMergedModuleSymbol(symbol) || symbol)), - allSearchSymbols = [symbol], - } = searchOptions; - const escapedText = escapeLeadingUnderscores(text); - const parents = this.options.implementations && location ? getParentSymbolsOfPropertyAccess(location, symbol, this.checker) : undefined; - return { symbol, comingFrom, text, escapedText, parents, allSearchSymbols, includes: sym => contains(allSearchSymbols, sym) }; - } - - private readonly symbolIdToReferences: Entry[][] = []; - /** - * Callback to add references for a particular searched symbol. - * This initializes a reference group, so only call this if you will add at least one reference. - */ - referenceAdder(searchSymbol: Symbol): (node: Node, kind?: NodeEntryKind) => void { - const symbolId = getSymbolId(searchSymbol); - let references = this.symbolIdToReferences[symbolId]; - if (!references) { - references = this.symbolIdToReferences[symbolId] = []; - this.result.push({ definition: { type: DefinitionKind.Symbol, symbol: searchSymbol }, references }); - } - return (node, kind) => references.push(nodeEntry(node, kind)); + readonly markSeenReExportRHS = nodeSeenTracker(); + + constructor( + readonly sourceFiles: readonly SourceFile[], + readonly sourceFilesSet: ReadonlySet, + readonly specialSearchKind: SpecialSearchKind, + readonly checker: TypeChecker, + readonly cancellationToken: CancellationToken, + readonly searchMeaning: SemanticMeaning, + readonly options: Options, + private readonly result: Push) { + } + + includesSourceFile(sourceFile: SourceFile): boolean { + return this.sourceFilesSet.has(sourceFile.fileName); + } + + private importTracker: ImportTracker | undefined; + /** Gets every place to look for references of an exported symbols. See `ImportsResult` in `importTracker.ts` for more documentation. */ + getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult { + if (!this.importTracker) this.importTracker = createImportTracker(this.sourceFiles, this.sourceFilesSet, this.checker, this.cancellationToken); + return this.importTracker(exportSymbol, exportInfo, this.options.use === FindReferencesUse.Rename); + } + + /** @param allSearchSymbols set of additional symbols for use by `includes`. */ + createSearch(location: Node | undefined, symbol: Symbol, comingFrom: ImportExport | undefined, searchOptions: { text?: string, allSearchSymbols?: Symbol[] } = {}): Search { + // Note: if this is an external module symbol, the name doesn't include quotes. + // Note: getLocalSymbolForExportDefault handles `export default class C {}`, but not `export default C` or `export { C as default }`. + // The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form + // here appears to be intentional). + const { + text = stripQuotes(symbolName(getLocalSymbolForExportDefault(symbol) || getNonModuleSymbolOfMergedModuleSymbol(symbol) || symbol)), + allSearchSymbols = [symbol], + } = searchOptions; + const escapedText = escapeLeadingUnderscores(text); + const parents = this.options.implementations && location ? getParentSymbolsOfPropertyAccess(location, symbol, this.checker) : undefined; + return { symbol, comingFrom, text, escapedText, parents, allSearchSymbols, includes: sym => contains(allSearchSymbols, sym) }; + } + + private readonly symbolIdToReferences: Entry[][] = []; + /** + * Callback to add references for a particular searched symbol. + * This initializes a reference group, so only call this if you will add at least one reference. + */ + referenceAdder(searchSymbol: Symbol): (node: Node, kind?: NodeEntryKind) => void { + const symbolId = getSymbolId(searchSymbol); + let references = this.symbolIdToReferences[symbolId]; + if (!references) { + references = this.symbolIdToReferences[symbolId] = []; + this.result.push({ definition: { type: DefinitionKind.Symbol, symbol: searchSymbol }, references }); } + return (node, kind) => references.push(nodeEntry(node, kind)); + } - /** Add a reference with no associated definition. */ - addStringOrCommentReference(fileName: string, textSpan: TextSpan): void { - this.result.push({ - definition: undefined, - references: [{ kind: EntryKind.Span, fileName, textSpan }] - }); - } + /** Add a reference with no associated definition. */ + addStringOrCommentReference(fileName: string, textSpan: TextSpan): void { + this.result.push({ + definition: undefined, + references: [{ kind: EntryKind.Span, fileName, textSpan }] + }); + } - // Source file ID → symbol ID → Whether the symbol has been searched for in the source file. - private readonly sourceFileToSeenSymbols: Set[] = []; - /** Returns `true` the first time we search for a symbol in a file and `false` afterwards. */ - markSearchedSymbols(sourceFile: SourceFile, symbols: readonly Symbol[]): boolean { - const sourceId = getNodeId(sourceFile); - const seenSymbols = this.sourceFileToSeenSymbols[sourceId] || (this.sourceFileToSeenSymbols[sourceId] = new Set()); + // Source file ID → symbol ID → Whether the symbol has been searched for in the source file. + private readonly sourceFileToSeenSymbols: Set[] = []; + /** Returns `true` the first time we search for a symbol in a file and `false` afterwards. */ + markSearchedSymbols(sourceFile: SourceFile, symbols: readonly Symbol[]): boolean { + const sourceId = getNodeId(sourceFile); + const seenSymbols = this.sourceFileToSeenSymbols[sourceId] || (this.sourceFileToSeenSymbols[sourceId] = new Set()); - let anyNewSymbols = false; - for (const sym of symbols) { - anyNewSymbols = tryAddToSet(seenSymbols, getSymbolId(sym)) || anyNewSymbols; - } - return anyNewSymbols; + let anyNewSymbols = false; + for (const sym of symbols) { + anyNewSymbols = tryAddToSet(seenSymbols, getSymbolId(sym)) || anyNewSymbols; } + return anyNewSymbols; } + } - /** Search for all imports of a given exported symbol using `State.getImportSearches`. */ - function searchForImportsOfExport(exportLocation: Node, exportSymbol: Symbol, exportInfo: ExportInfo, state: State): void { - const { importSearches, singleReferences, indirectUsers } = state.getImportSearches(exportSymbol, exportInfo); + /** Search for all imports of a given exported symbol using `State.getImportSearches`. */ + function searchForImportsOfExport(exportLocation: Node, exportSymbol: Symbol, exportInfo: ExportInfo, state: State): void { + const { importSearches, singleReferences, indirectUsers } = state.getImportSearches(exportSymbol, exportInfo); - // For `import { foo as bar }` just add the reference to `foo`, and don't otherwise search in the file. - if (singleReferences.length) { - const addRef = state.referenceAdder(exportSymbol); - for (const singleRef of singleReferences) { - if (shouldAddSingleReference(singleRef, state)) addRef(singleRef); - } + // For `import { foo as bar }` just add the reference to `foo`, and don't otherwise search in the file. + if (singleReferences.length) { + const addRef = state.referenceAdder(exportSymbol); + for (const singleRef of singleReferences) { + if (shouldAddSingleReference(singleRef, state)) addRef(singleRef); } + } - // For each import, find all references to that import in its source file. - for (const [importLocation, importSymbol] of importSearches) { - getReferencesInSourceFile(importLocation.getSourceFile(), state.createSearch(importLocation, importSymbol, ImportExport.Export), state); - } + // For each import, find all references to that import in its source file. + for (const [importLocation, importSymbol] of importSearches) { + getReferencesInSourceFile(importLocation.getSourceFile(), state.createSearch(importLocation, importSymbol, ImportExport.Export), state); + } - if (indirectUsers.length) { - let indirectSearch: Search | undefined; - switch (exportInfo.exportKind) { - case ExportKind.Named: - indirectSearch = state.createSearch(exportLocation, exportSymbol, ImportExport.Export); - break; - case ExportKind.Default: - // Search for a property access to '.default'. This can't be renamed. - indirectSearch = state.options.use === FindReferencesUse.Rename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" }); - break; - case ExportKind.ExportEquals: - break; - } - if (indirectSearch) { - for (const indirectUser of indirectUsers) { - searchForName(indirectUser, indirectSearch, state); - } + if (indirectUsers.length) { + let indirectSearch: Search | undefined; + switch (exportInfo.exportKind) { + case ExportKind.Named: + indirectSearch = state.createSearch(exportLocation, exportSymbol, ImportExport.Export); + break; + case ExportKind.Default: + // Search for a property access to '.default'. This can't be renamed. + indirectSearch = state.options.use === FindReferencesUse.Rename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" }); + break; + case ExportKind.ExportEquals: + break; + } + if (indirectSearch) { + for (const indirectUser of indirectUsers) { + searchForName(indirectUser, indirectSearch, state); } } } + } - export function eachExportReference( - sourceFiles: readonly SourceFile[], - checker: TypeChecker, - cancellationToken: CancellationToken | undefined, - exportSymbol: Symbol, - exportingModuleSymbol: Symbol, - exportName: string, - isDefaultExport: boolean, - cb: (ref: Identifier) => void, - ): void { - const importTracker = createImportTracker(sourceFiles, new Set(sourceFiles.map(f => f.fileName)), checker, cancellationToken); - const { importSearches, indirectUsers } = importTracker(exportSymbol, { exportKind: isDefaultExport ? ExportKind.Default : ExportKind.Named, exportingModuleSymbol }, /*isForRename*/ false); - for (const [importLocation] of importSearches) { - cb(importLocation); - } - for (const indirectUser of indirectUsers) { - for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) { - // Import specifiers should be handled by importSearches - const symbol = checker.getSymbolAtLocation(node); - const hasExportAssignmentDeclaration = some(symbol?.declarations, d => tryCast(d, isExportAssignment) ? true : false); - if (isIdentifier(node) && !isImportOrExportSpecifier(node.parent) && (symbol === exportSymbol || hasExportAssignmentDeclaration)) { - cb(node); - } + export function eachExportReference( + sourceFiles: readonly SourceFile[], + checker: TypeChecker, + cancellationToken: CancellationToken | undefined, + exportSymbol: Symbol, + exportingModuleSymbol: Symbol, + exportName: string, + isDefaultExport: boolean, + cb: (ref: Identifier) => void, + ): void { + const importTracker = createImportTracker(sourceFiles, new Set(sourceFiles.map(f => f.fileName)), checker, cancellationToken); + const { importSearches, indirectUsers } = importTracker(exportSymbol, { exportKind: isDefaultExport ? ExportKind.Default : ExportKind.Named, exportingModuleSymbol }, /*isForRename*/ false); + for (const [importLocation] of importSearches) { + cb(importLocation); + } + for (const indirectUser of indirectUsers) { + for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) { + // Import specifiers should be handled by importSearches + const symbol = checker.getSymbolAtLocation(node); + const hasExportAssignmentDeclaration = some(symbol?.declarations, d => tryCast(d, isExportAssignment) ? true : false); + if (isIdentifier(node) && !isImportOrExportSpecifier(node.parent) && (symbol === exportSymbol || hasExportAssignmentDeclaration)) { + cb(node); } } } + } - function shouldAddSingleReference(singleRef: Identifier | StringLiteral, state: State): boolean { - if (!hasMatchingMeaning(singleRef, state)) return false; - if (state.options.use !== FindReferencesUse.Rename) return true; - // Don't rename an import type `import("./module-name")` when renaming `name` in `export = name;` - if (!isIdentifier(singleRef)) return false; - // At `default` in `import { default as x }` or `export { default as x }`, do add a reference, but do not rename. - return !(isImportOrExportSpecifier(singleRef.parent) && singleRef.escapedText === InternalSymbolName.Default); - } + function shouldAddSingleReference(singleRef: Identifier | StringLiteral, state: State): boolean { + if (!hasMatchingMeaning(singleRef, state)) return false; + if (state.options.use !== FindReferencesUse.Rename) return true; + // Don't rename an import type `import("./module-name")` when renaming `name` in `export = name;` + if (!isIdentifier(singleRef)) return false; + // At `default` in `import { default as x }` or `export { default as x }`, do add a reference, but do not rename. + return !(isImportOrExportSpecifier(singleRef.parent) && singleRef.escapedText === InternalSymbolName.Default); + } - // Go to the symbol we imported from and find references for it. - function searchForImportedSymbol(symbol: Symbol, state: State): void { - if (!symbol.declarations) return; + // Go to the symbol we imported from and find references for it. + function searchForImportedSymbol(symbol: Symbol, state: State): void { + if (!symbol.declarations) return; - for (const declaration of symbol.declarations) { - const exportingFile = declaration.getSourceFile(); - // Need to search in the file even if it's not in the search-file set, because it might export the symbol. - getReferencesInSourceFile(exportingFile, state.createSearch(declaration, symbol, ImportExport.Import), state, state.includesSourceFile(exportingFile)); - } + for (const declaration of symbol.declarations) { + const exportingFile = declaration.getSourceFile(); + // Need to search in the file even if it's not in the search-file set, because it might export the symbol. + getReferencesInSourceFile(exportingFile, state.createSearch(declaration, symbol, ImportExport.Import), state, state.includesSourceFile(exportingFile)); } + } - /** Search for all occurrences of an identifier in a source file (and filter out the ones that match). */ - function searchForName(sourceFile: SourceFile, search: Search, state: State): void { - if (getNameTable(sourceFile).get(search.escapedText) !== undefined) { - getReferencesInSourceFile(sourceFile, search, state); - } + /** Search for all occurrences of an identifier in a source file (and filter out the ones that match). */ + function searchForName(sourceFile: SourceFile, search: Search, state: State): void { + if (getNameTable(sourceFile).get(search.escapedText) !== undefined) { + getReferencesInSourceFile(sourceFile, search, state); } + } - function getPropertySymbolOfDestructuringAssignment(location: Node, checker: TypeChecker): Symbol | undefined { - return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) - ? checker.getPropertySymbolOfDestructuringAssignment(location as Identifier) - : undefined; + function getPropertySymbolOfDestructuringAssignment(location: Node, checker: TypeChecker): Symbol | undefined { + return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) + ? checker.getPropertySymbolOfDestructuringAssignment(location as Identifier) + : undefined; + } + + /** + * Determines the smallest scope in which a symbol may have named references. + * Note that not every construct has been accounted for. This function can + * probably be improved. + * + * @returns undefined if the scope cannot be determined, implying that + * a reference to a symbol can occur anywhere. + */ + function getSymbolScope(symbol: Symbol): Node | undefined { + // If this is the symbol of a named function expression or named class expression, + // then named references are limited to its own scope. + const { declarations, flags, parent, valueDeclaration } = symbol; + if (valueDeclaration && (valueDeclaration.kind === SyntaxKind.FunctionExpression || valueDeclaration.kind === SyntaxKind.ClassExpression)) { + return valueDeclaration; } - /** - * Determines the smallest scope in which a symbol may have named references. - * Note that not every construct has been accounted for. This function can - * probably be improved. - * - * @returns undefined if the scope cannot be determined, implying that - * a reference to a symbol can occur anywhere. - */ - function getSymbolScope(symbol: Symbol): Node | undefined { - // If this is the symbol of a named function expression or named class expression, - // then named references are limited to its own scope. - const { declarations, flags, parent, valueDeclaration } = symbol; - if (valueDeclaration && (valueDeclaration.kind === SyntaxKind.FunctionExpression || valueDeclaration.kind === SyntaxKind.ClassExpression)) { - return valueDeclaration; - } + if (!declarations) { + return undefined; + } - if (!declarations) { - return undefined; + // If this is private property or method, the scope is the containing class + if (flags & (SymbolFlags.Property | SymbolFlags.Method)) { + const privateDeclaration = find(declarations, d => hasEffectiveModifier(d, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(d)); + if (privateDeclaration) { + return getAncestor(privateDeclaration, SyntaxKind.ClassDeclaration); } + // Else this is a public property and could be accessed from anywhere. + return undefined; + } - // If this is private property or method, the scope is the containing class - if (flags & (SymbolFlags.Property | SymbolFlags.Method)) { - const privateDeclaration = find(declarations, d => hasEffectiveModifier(d, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(d)); - if (privateDeclaration) { - return getAncestor(privateDeclaration, SyntaxKind.ClassDeclaration); - } - // Else this is a public property and could be accessed from anywhere. - return undefined; - } + // If symbol is of object binding pattern element without property name we would want to + // look for property too and that could be anywhere + if (declarations.some(isObjectBindingElementWithoutPropertyName)) { + return undefined; + } + + /* + If the symbol has a parent, it's globally visible unless: + - It's a private property (handled above). + - It's a type parameter. + - The parent is an external module: then we should only search in the module (and recurse on the export later). + - But if the parent has `export as namespace`, the symbol is globally visible through that namespace. + */ + const exposedByParent = parent && !(symbol.flags & SymbolFlags.TypeParameter); + if (exposedByParent && !(isExternalModuleSymbol(parent!) && !parent!.globalExports)) { + return undefined; + } - // If symbol is of object binding pattern element without property name we would want to - // look for property too and that could be anywhere - if (declarations.some(isObjectBindingElementWithoutPropertyName)) { + let scope: Node | undefined; + for (const declaration of declarations) { + const container = getContainerNode(declaration); + if (scope && scope !== container) { + // Different declarations have different containers, bail out return undefined; } - /* - If the symbol has a parent, it's globally visible unless: - - It's a private property (handled above). - - It's a type parameter. - - The parent is an external module: then we should only search in the module (and recurse on the export later). - - But if the parent has `export as namespace`, the symbol is globally visible through that namespace. - */ - const exposedByParent = parent && !(symbol.flags & SymbolFlags.TypeParameter); - if (exposedByParent && !(isExternalModuleSymbol(parent!) && !parent!.globalExports)) { + if (!container || container.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(container as SourceFile)) { + // This is a global variable and not an external module, any declaration defined + // within this scope is visible outside the file return undefined; } - let scope: Node | undefined; - for (const declaration of declarations) { - const container = getContainerNode(declaration); - if (scope && scope !== container) { - // Different declarations have different containers, bail out - return undefined; + scope = container; + if (isFunctionExpression(scope)) { + let next: Node | undefined; + while (next = getNextJSDocCommentLocation(scope)) { + scope = next; } + } + } - if (!container || container.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(container as SourceFile)) { - // This is a global variable and not an external module, any declaration defined - // within this scope is visible outside the file - return undefined; - } + // If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.) + // For an export of a module, we may be in a declaration file, and it may be accessed elsewhere. E.g.: + // declare module "a" { export type T = number; } + // declare module "b" { import { T } from "a"; export const x: T; } + // So we must search the whole source file. (Because we will mark the source file as seen, we we won't return to it when searching for imports.) + return exposedByParent ? scope!.getSourceFile() : scope; // TODO: GH#18217 + } - scope = container; - if (isFunctionExpression(scope)) { - let next: Node | undefined; - while (next = getNextJSDocCommentLocation(scope)) { - scope = next; - } - } - } + /** Used as a quick check for whether a symbol is used at all in a file (besides its definition). */ + export function isSymbolReferencedInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, searchContainer: Node = sourceFile): boolean { + return eachSymbolReferenceInFile(definition, checker, sourceFile, () => true, searchContainer) || false; + } - // If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.) - // For an export of a module, we may be in a declaration file, and it may be accessed elsewhere. E.g.: - // declare module "a" { export type T = number; } - // declare module "b" { import { T } from "a"; export const x: T; } - // So we must search the whole source file. (Because we will mark the source file as seen, we we won't return to it when searching for imports.) - return exposedByParent ? scope!.getSourceFile() : scope; // TODO: GH#18217 - } - - /** Used as a quick check for whether a symbol is used at all in a file (besides its definition). */ - export function isSymbolReferencedInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, searchContainer: Node = sourceFile): boolean { - return eachSymbolReferenceInFile(definition, checker, sourceFile, () => true, searchContainer) || false; - } - - export function eachSymbolReferenceInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T, searchContainer: Node = sourceFile): T | undefined { - const symbol = isParameterPropertyDeclaration(definition.parent, definition.parent.parent) - ? first(checker.getSymbolsOfParameterPropertyDeclaration(definition.parent, definition.text)) - : checker.getSymbolAtLocation(definition); - if (!symbol) return undefined; - for (const token of getPossibleSymbolReferenceNodes(sourceFile, symbol.name, searchContainer)) { - if (!isIdentifier(token) || token === definition || token.escapedText !== definition.escapedText) continue; - const referenceSymbol = checker.getSymbolAtLocation(token)!; - if (referenceSymbol === symbol - || checker.getShorthandAssignmentValueSymbol(token.parent) === symbol - || isExportSpecifier(token.parent) && getLocalSymbolForExportSpecifier(token, referenceSymbol, token.parent, checker) === symbol) { - const res = cb(token); - if (res) return res; - } + export function eachSymbolReferenceInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T, searchContainer: Node = sourceFile): T | undefined { + const symbol = isParameterPropertyDeclaration(definition.parent, definition.parent.parent) + ? first(checker.getSymbolsOfParameterPropertyDeclaration(definition.parent, definition.text)) + : checker.getSymbolAtLocation(definition); + if (!symbol) return undefined; + for (const token of getPossibleSymbolReferenceNodes(sourceFile, symbol.name, searchContainer)) { + if (!isIdentifier(token) || token === definition || token.escapedText !== definition.escapedText) continue; + const referenceSymbol = checker.getSymbolAtLocation(token)!; + if (referenceSymbol === symbol + || checker.getShorthandAssignmentValueSymbol(token.parent) === symbol + || isExportSpecifier(token.parent) && getLocalSymbolForExportSpecifier(token, referenceSymbol, token.parent, checker) === symbol) { + const res = cb(token); + if (res) return res; } } + } - export function someSignatureUsage( - signature: SignatureDeclaration, - sourceFiles: readonly SourceFile[], - checker: TypeChecker, - cb: (name: Identifier, call?: CallExpression) => boolean - ): boolean { - if (!signature.name || !isIdentifier(signature.name)) return false; - - const symbol = Debug.checkDefined(checker.getSymbolAtLocation(signature.name)); - - for (const sourceFile of sourceFiles) { - for (const name of getPossibleSymbolReferenceNodes(sourceFile, symbol.name)) { - if (!isIdentifier(name) || name === signature.name || name.escapedText !== signature.name.escapedText) continue; - const called = climbPastPropertyAccess(name); - const call = isCallExpression(called.parent) && called.parent.expression === called ? called.parent : undefined; - const referenceSymbol = checker.getSymbolAtLocation(name); - if (referenceSymbol && checker.getRootSymbols(referenceSymbol).some(s => s === symbol)) { - if (cb(name, call)) { - return true; - } + export function someSignatureUsage( + signature: SignatureDeclaration, + sourceFiles: readonly SourceFile[], + checker: TypeChecker, + cb: (name: Identifier, call?: CallExpression) => boolean + ): boolean { + if (!signature.name || !isIdentifier(signature.name)) return false; + + const symbol = Debug.checkDefined(checker.getSymbolAtLocation(signature.name)); + + for (const sourceFile of sourceFiles) { + for (const name of getPossibleSymbolReferenceNodes(sourceFile, symbol.name)) { + if (!isIdentifier(name) || name === signature.name || name.escapedText !== signature.name.escapedText) continue; + const called = climbPastPropertyAccess(name); + const call = isCallExpression(called.parent) && called.parent.expression === called ? called.parent : undefined; + const referenceSymbol = checker.getSymbolAtLocation(name); + if (referenceSymbol && checker.getRootSymbols(referenceSymbol).some(s => s === symbol)) { + if (cb(name, call)) { + return true; } } } - return false; } + return false; + } - function getPossibleSymbolReferenceNodes(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly Node[] { - return getPossibleSymbolReferencePositions(sourceFile, symbolName, container).map(pos => getTouchingPropertyName(sourceFile, pos)); - } + function getPossibleSymbolReferenceNodes(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly Node[] { + return getPossibleSymbolReferencePositions(sourceFile, symbolName, container).map(pos => getTouchingPropertyName(sourceFile, pos)); + } - function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly number[] { - const positions: number[] = []; + function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly number[] { + const positions: number[] = []; - /// TODO: Cache symbol existence for files to save text search - // Also, need to make this work for unicode escapes. + /// TODO: Cache symbol existence for files to save text search + // Also, need to make this work for unicode escapes. - // Be resilient in the face of a symbol with no name or zero length name - if (!symbolName || !symbolName.length) { - return positions; - } + // Be resilient in the face of a symbol with no name or zero length name + if (!symbolName || !symbolName.length) { + return positions; + } - const text = sourceFile.text; - const sourceLength = text.length; - const symbolNameLength = symbolName.length; + const text = sourceFile.text; + const sourceLength = text.length; + const symbolNameLength = symbolName.length; - let position = text.indexOf(symbolName, container.pos); - while (position >= 0) { - // If we are past the end, stop looking - if (position > container.end) break; + let position = text.indexOf(symbolName, container.pos); + while (position >= 0) { + // If we are past the end, stop looking + if (position > container.end) break; - // We found a match. Make sure it's not part of a larger word (i.e. the char - // before and after it have to be a non-identifier char). - const endPosition = position + symbolNameLength; + // We found a match. Make sure it's not part of a larger word (i.e. the char + // before and after it have to be a non-identifier char). + const endPosition = position + symbolNameLength; - if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) && - (endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) { - // Found a real match. Keep searching. - positions.push(position); - } - position = text.indexOf(symbolName, position + symbolNameLength + 1); + if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) && + (endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) { + // Found a real match. Keep searching. + positions.push(position); } - - return positions; + position = text.indexOf(symbolName, position + symbolNameLength + 1); } - function getLabelReferencesInNode(container: Node, targetLabel: Identifier): SymbolAndEntries[] { - const sourceFile = container.getSourceFile(); - const labelName = targetLabel.text; - const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, labelName, container), node => - // Only pick labels that are either the target label, or have a target that is the target label - node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel) ? nodeEntry(node) : undefined); - return [{ definition: { type: DefinitionKind.Label, node: targetLabel }, references }]; - } + return positions; + } - function isValidReferencePosition(node: Node, searchSymbolName: string): boolean { - // Compare the length so we filter out strict superstrings of the symbol we are looking for - switch (node.kind) { - case SyntaxKind.PrivateIdentifier: - if (isJSDocMemberName(node.parent)) { - return true; - } - // falls through I guess - case SyntaxKind.Identifier: - return (node as PrivateIdentifier | Identifier).text.length === searchSymbolName.length; - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.StringLiteral: { - const str = node as StringLiteralLike; - return (isLiteralNameOfPropertyDeclarationOrIndexAccess(str) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || (isCallExpression(node.parent) && isBindableObjectDefinePropertyCall(node.parent) && node.parent.arguments[1] === node)) && - str.text.length === searchSymbolName.length; + function getLabelReferencesInNode(container: Node, targetLabel: Identifier): SymbolAndEntries[] { + const sourceFile = container.getSourceFile(); + const labelName = targetLabel.text; + const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, labelName, container), node => + // Only pick labels that are either the target label, or have a target that is the target label + node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel) ? nodeEntry(node) : undefined); + return [{ definition: { type: DefinitionKind.Label, node: targetLabel }, references }]; + } + + function isValidReferencePosition(node: Node, searchSymbolName: string): boolean { + // Compare the length so we filter out strict superstrings of the symbol we are looking for + switch (node.kind) { + case SyntaxKind.PrivateIdentifier: + if (isJSDocMemberName(node.parent)) { + return true; } + // falls through I guess + case SyntaxKind.Identifier: + return (node as PrivateIdentifier | Identifier).text.length === searchSymbolName.length; + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.StringLiteral: { + const str = node as StringLiteralLike; + return (isLiteralNameOfPropertyDeclarationOrIndexAccess(str) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || (isCallExpression(node.parent) && isBindableObjectDefinePropertyCall(node.parent) && node.parent.arguments[1] === node)) && + str.text.length === searchSymbolName.length; + } - case SyntaxKind.NumericLiteral: - return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral) && (node as NumericLiteral).text.length === searchSymbolName.length; + case SyntaxKind.NumericLiteral: + return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral) && (node as NumericLiteral).text.length === searchSymbolName.length; - case SyntaxKind.DefaultKeyword: - return "default".length === searchSymbolName.length; + case SyntaxKind.DefaultKeyword: + return "default".length === searchSymbolName.length; - default: - return false; - } + default: + return false; } + } - function getAllReferencesForKeyword(sourceFiles: readonly SourceFile[], keywordKind: SyntaxKind, cancellationToken: CancellationToken, filter?: (node: Node) => boolean): SymbolAndEntries[] | undefined { - const references = flatMap(sourceFiles, sourceFile => { - cancellationToken.throwIfCancellationRequested(); - return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, tokenToString(keywordKind)!, sourceFile), referenceLocation => { - if (referenceLocation.kind === keywordKind && (!filter || filter(referenceLocation))) { - return nodeEntry(referenceLocation); - } - }); + function getAllReferencesForKeyword(sourceFiles: readonly SourceFile[], keywordKind: SyntaxKind, cancellationToken: CancellationToken, filter?: (node: Node) => boolean): SymbolAndEntries[] | undefined { + const references = flatMap(sourceFiles, sourceFile => { + cancellationToken.throwIfCancellationRequested(); + return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, tokenToString(keywordKind)!, sourceFile), referenceLocation => { + if (referenceLocation.kind === keywordKind && (!filter || filter(referenceLocation))) { + return nodeEntry(referenceLocation); + } }); - return references.length ? [{ definition: { type: DefinitionKind.Keyword, node: references[0].node }, references }] : undefined; + }); + return references.length ? [{ definition: { type: DefinitionKind.Keyword, node: references[0].node }, references }] : undefined; + } + + function getReferencesInSourceFile(sourceFile: SourceFile, search: Search, state: State, addReferencesHere = true): void { + state.cancellationToken.throwIfCancellationRequested(); + return getReferencesInContainer(sourceFile, sourceFile, search, state, addReferencesHere); + } + + /** + * Search within node "container" for references for a search value, where the search value is defined as a + * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). + * searchLocation: a node where the search value + */ + function getReferencesInContainer(container: Node, sourceFile: SourceFile, search: Search, state: State, addReferencesHere: boolean): void { + if (!state.markSearchedSymbols(sourceFile, search.allSearchSymbols)) { + return; } - function getReferencesInSourceFile(sourceFile: SourceFile, search: Search, state: State, addReferencesHere = true): void { - state.cancellationToken.throwIfCancellationRequested(); - return getReferencesInContainer(sourceFile, sourceFile, search, state, addReferencesHere); + for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container)) { + getReferencesAtLocation(sourceFile, position, search, state, addReferencesHere); } + } - /** - * Search within node "container" for references for a search value, where the search value is defined as a - * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). - * searchLocation: a node where the search value - */ - function getReferencesInContainer(container: Node, sourceFile: SourceFile, search: Search, state: State, addReferencesHere: boolean): void { - if (!state.markSearchedSymbols(sourceFile, search.allSearchSymbols)) { - return; - } + function hasMatchingMeaning(referenceLocation: Node, state: State): boolean { + return !!(getMeaningFromLocation(referenceLocation) & state.searchMeaning); + } - for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container)) { - getReferencesAtLocation(sourceFile, position, search, state, addReferencesHere); + function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State, addReferencesHere: boolean): void { + const referenceLocation = getTouchingPropertyName(sourceFile, position); + + if (!isValidReferencePosition(referenceLocation, search.text)) { + // This wasn't the start of a token. Check to see if it might be a + // match in a comment or string if that's what the caller is asking + // for. + if (!state.options.implementations && (state.options.findInStrings && isInString(sourceFile, position) || state.options.findInComments && isInNonReferenceComment(sourceFile, position))) { + // In the case where we're looking inside comments/strings, we don't have + // an actual definition. So just use 'undefined' here. Features like + // 'Rename' won't care (as they ignore the definitions), and features like + // 'FindReferences' will just filter out these results. + state.addStringOrCommentReference(sourceFile.fileName, createTextSpan(position, search.text.length)); } - } - function hasMatchingMeaning(referenceLocation: Node, state: State): boolean { - return !!(getMeaningFromLocation(referenceLocation) & state.searchMeaning); + return; } - function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State, addReferencesHere: boolean): void { - const referenceLocation = getTouchingPropertyName(sourceFile, position); + if (!hasMatchingMeaning(referenceLocation, state)) return; - if (!isValidReferencePosition(referenceLocation, search.text)) { - // This wasn't the start of a token. Check to see if it might be a - // match in a comment or string if that's what the caller is asking - // for. - if (!state.options.implementations && (state.options.findInStrings && isInString(sourceFile, position) || state.options.findInComments && isInNonReferenceComment(sourceFile, position))) { - // In the case where we're looking inside comments/strings, we don't have - // an actual definition. So just use 'undefined' here. Features like - // 'Rename' won't care (as they ignore the definitions), and features like - // 'FindReferences' will just filter out these results. - state.addStringOrCommentReference(sourceFile.fileName, createTextSpan(position, search.text.length)); - } + let referenceSymbol = state.checker.getSymbolAtLocation(referenceLocation); + if (!referenceSymbol) { + return; + } - return; - } + const parent = referenceLocation.parent; + if (isImportSpecifier(parent) && parent.propertyName === referenceLocation) { + // This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again. + return; + } - if (!hasMatchingMeaning(referenceLocation, state)) return; + if (isExportSpecifier(parent)) { + Debug.assert(referenceLocation.kind === SyntaxKind.Identifier); + getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state, addReferencesHere); + return; + } - let referenceSymbol = state.checker.getSymbolAtLocation(referenceLocation); - if (!referenceSymbol) { - return; - } + const relatedSymbol = getRelatedSymbol(search, referenceSymbol, referenceLocation, state); + if (!relatedSymbol) { + getReferenceForShorthandProperty(referenceSymbol, search, state); + return; + } - const parent = referenceLocation.parent; - if (isImportSpecifier(parent) && parent.propertyName === referenceLocation) { - // This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again. - return; - } + switch (state.specialSearchKind) { + case SpecialSearchKind.None: + if (addReferencesHere) addReference(referenceLocation, relatedSymbol, state); + break; + case SpecialSearchKind.Constructor: + addConstructorReferences(referenceLocation, sourceFile, search, state); + break; + case SpecialSearchKind.Class: + addClassStaticThisReferences(referenceLocation, search, state); + break; + default: + Debug.assertNever(state.specialSearchKind); + } - if (isExportSpecifier(parent)) { - Debug.assert(referenceLocation.kind === SyntaxKind.Identifier); - getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state, addReferencesHere); - return; - } + // Use the parent symbol if the location is commonjs require syntax on javascript files only. + if (isInJSFile(referenceLocation) + && referenceLocation.parent.kind === SyntaxKind.BindingElement + && isRequireVariableDeclaration(referenceLocation.parent)) { + referenceSymbol = referenceLocation.parent.symbol; + // The parent will not have a symbol if it's an ObjectBindingPattern (when destructuring is used). In + // this case, just skip it, since the bound identifiers are not an alias of the import. + if (!referenceSymbol) return; + } - const relatedSymbol = getRelatedSymbol(search, referenceSymbol, referenceLocation, state); - if (!relatedSymbol) { - getReferenceForShorthandProperty(referenceSymbol, search, state); - return; - } + getImportOrExportReferences(referenceLocation, referenceSymbol, search, state); + } - switch (state.specialSearchKind) { - case SpecialSearchKind.None: - if (addReferencesHere) addReference(referenceLocation, relatedSymbol, state); - break; - case SpecialSearchKind.Constructor: - addConstructorReferences(referenceLocation, sourceFile, search, state); - break; - case SpecialSearchKind.Class: - addClassStaticThisReferences(referenceLocation, search, state); - break; - default: - Debug.assertNever(state.specialSearchKind); - } - - // Use the parent symbol if the location is commonjs require syntax on javascript files only. - if (isInJSFile(referenceLocation) - && referenceLocation.parent.kind === SyntaxKind.BindingElement - && isRequireVariableDeclaration(referenceLocation.parent)) { - referenceSymbol = referenceLocation.parent.symbol; - // The parent will not have a symbol if it's an ObjectBindingPattern (when destructuring is used). In - // this case, just skip it, since the bound identifiers are not an alias of the import. - if (!referenceSymbol) return; - } - - getImportOrExportReferences(referenceLocation, referenceSymbol, search, state); - } - - function getReferencesAtExportSpecifier( - referenceLocation: Identifier, - referenceSymbol: Symbol, - exportSpecifier: ExportSpecifier, - search: Search, - state: State, - addReferencesHere: boolean, - alwaysGetReferences?: boolean, - ): void { - Debug.assert(!alwaysGetReferences || !!state.options.providePrefixAndSuffixTextForRename, "If alwaysGetReferences is true, then prefix/suffix text must be enabled"); - - const { parent, propertyName, name } = exportSpecifier; - const exportDeclaration = parent.parent; - const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker); - if (!alwaysGetReferences && !search.includes(localSymbol)) { - return; - } + function getReferencesAtExportSpecifier( + referenceLocation: Identifier, + referenceSymbol: Symbol, + exportSpecifier: ExportSpecifier, + search: Search, + state: State, + addReferencesHere: boolean, + alwaysGetReferences?: boolean, + ): void { + Debug.assert(!alwaysGetReferences || !!state.options.providePrefixAndSuffixTextForRename, "If alwaysGetReferences is true, then prefix/suffix text must be enabled"); - if (!propertyName) { - // Don't rename at `export { default } from "m";`. (but do continue to search for imports of the re-export) - if (!(state.options.use === FindReferencesUse.Rename && (name.escapedText === InternalSymbolName.Default))) { - addRef(); - } - } - else if (referenceLocation === propertyName) { - // For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export. - // For `export { foo as bar };`, where `foo` is a local, so add it now. - if (!exportDeclaration.moduleSpecifier) { - addRef(); - } + const { parent, propertyName, name } = exportSpecifier; + const exportDeclaration = parent.parent; + const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker); + if (!alwaysGetReferences && !search.includes(localSymbol)) { + return; + } - if (addReferencesHere && state.options.use !== FindReferencesUse.Rename && state.markSeenReExportRHS(name)) { - addReference(name, Debug.checkDefined(exportSpecifier.symbol), state); - } + if (!propertyName) { + // Don't rename at `export { default } from "m";`. (but do continue to search for imports of the re-export) + if (!(state.options.use === FindReferencesUse.Rename && (name.escapedText === InternalSymbolName.Default))) { + addRef(); } - else { - if (state.markSeenReExportRHS(referenceLocation)) { - addRef(); - } + } + else if (referenceLocation === propertyName) { + // For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export. + // For `export { foo as bar };`, where `foo` is a local, so add it now. + if (!exportDeclaration.moduleSpecifier) { + addRef(); } - // For `export { foo as bar }`, rename `foo`, but not `bar`. - if (!isForRenameWithPrefixAndSuffixText(state.options) || alwaysGetReferences) { - const isDefaultExport = referenceLocation.originalKeywordKind === SyntaxKind.DefaultKeyword - || exportSpecifier.name.originalKeywordKind === SyntaxKind.DefaultKeyword; - const exportKind = isDefaultExport ? ExportKind.Default : ExportKind.Named; - const exportSymbol = Debug.checkDefined(exportSpecifier.symbol); - const exportInfo = getExportInfo(exportSymbol, exportKind, state.checker); - if (exportInfo) { - searchForImportsOfExport(referenceLocation, exportSymbol, exportInfo, state); - } + if (addReferencesHere && state.options.use !== FindReferencesUse.Rename && state.markSeenReExportRHS(name)) { + addReference(name, Debug.checkDefined(exportSpecifier.symbol), state); } + } + else { + if (state.markSeenReExportRHS(referenceLocation)) { + addRef(); + } + } - // At `export { x } from "foo"`, also search for the imported symbol `"foo".x`. - if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName && !isForRenameWithPrefixAndSuffixText(state.options)) { - const imported = state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier); - if (imported) searchForImportedSymbol(imported, state); + // For `export { foo as bar }`, rename `foo`, but not `bar`. + if (!isForRenameWithPrefixAndSuffixText(state.options) || alwaysGetReferences) { + const isDefaultExport = referenceLocation.originalKeywordKind === SyntaxKind.DefaultKeyword + || exportSpecifier.name.originalKeywordKind === SyntaxKind.DefaultKeyword; + const exportKind = isDefaultExport ? ExportKind.Default : ExportKind.Named; + const exportSymbol = Debug.checkDefined(exportSpecifier.symbol); + const exportInfo = getExportInfo(exportSymbol, exportKind, state.checker); + if (exportInfo) { + searchForImportsOfExport(referenceLocation, exportSymbol, exportInfo, state); } + } - function addRef() { - if (addReferencesHere) addReference(referenceLocation, localSymbol, state); - } + // At `export { x } from "foo"`, also search for the imported symbol `"foo".x`. + if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName && !isForRenameWithPrefixAndSuffixText(state.options)) { + const imported = state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier); + if (imported) searchForImportedSymbol(imported, state); } - function getLocalSymbolForExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, checker: TypeChecker): Symbol { - return isExportSpecifierAlias(referenceLocation, exportSpecifier) && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier) || referenceSymbol; + function addRef() { + if (addReferencesHere) addReference(referenceLocation, localSymbol, state); } + } - function isExportSpecifierAlias(referenceLocation: Identifier, exportSpecifier: ExportSpecifier): boolean { - const { parent, propertyName, name } = exportSpecifier; - Debug.assert(propertyName === referenceLocation || name === referenceLocation); - if (propertyName) { - // Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol. - return propertyName === referenceLocation; - } - else { - // `export { foo } from "foo"` is a re-export. - // `export { foo };` is not a re-export, it creates an alias for the local variable `foo`. - return !parent.parent.moduleSpecifier; - } + function getLocalSymbolForExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, checker: TypeChecker): Symbol { + return isExportSpecifierAlias(referenceLocation, exportSpecifier) && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier) || referenceSymbol; + } + + function isExportSpecifierAlias(referenceLocation: Identifier, exportSpecifier: ExportSpecifier): boolean { + const { parent, propertyName, name } = exportSpecifier; + Debug.assert(propertyName === referenceLocation || name === referenceLocation); + if (propertyName) { + // Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol. + return propertyName === referenceLocation; + } + else { + // `export { foo } from "foo"` is a re-export. + // `export { foo };` is not a re-export, it creates an alias for the local variable `foo`. + return !parent.parent.moduleSpecifier; } + } - function getImportOrExportReferences(referenceLocation: Node, referenceSymbol: Symbol, search: Search, state: State): void { - const importOrExport = getImportOrExportSymbol(referenceLocation, referenceSymbol, state.checker, search.comingFrom === ImportExport.Export); - if (!importOrExport) return; + function getImportOrExportReferences(referenceLocation: Node, referenceSymbol: Symbol, search: Search, state: State): void { + const importOrExport = getImportOrExportSymbol(referenceLocation, referenceSymbol, state.checker, search.comingFrom === ImportExport.Export); + if (!importOrExport) return; - const { symbol } = importOrExport; + const { symbol } = importOrExport; - if (importOrExport.kind === ImportExport.Import) { - if (!(isForRenameWithPrefixAndSuffixText(state.options))) { - searchForImportedSymbol(symbol, state); - } - } - else { - searchForImportsOfExport(referenceLocation, symbol, importOrExport.exportInfo, state); + if (importOrExport.kind === ImportExport.Import) { + if (!(isForRenameWithPrefixAndSuffixText(state.options))) { + searchForImportedSymbol(symbol, state); } } + else { + searchForImportsOfExport(referenceLocation, symbol, importOrExport.exportInfo, state); + } + } - function getReferenceForShorthandProperty({ flags, valueDeclaration }: Symbol, search: Search, state: State): void { - const shorthandValueSymbol = state.checker.getShorthandAssignmentValueSymbol(valueDeclaration)!; - const name = valueDeclaration && getNameOfDeclaration(valueDeclaration); - /* - * Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment - * has two meanings: property name and property value. Therefore when we do findAllReference at the position where - * an identifier is declared, the language service should return the position of the variable declaration as well as - * the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the - * position of property accessing, the referenceEntry of such position will be handled in the first case. - */ - if (!(flags & SymbolFlags.Transient) && name && search.includes(shorthandValueSymbol)) { - addReference(name, shorthandValueSymbol, state); - } + function getReferenceForShorthandProperty({ flags, valueDeclaration }: Symbol, search: Search, state: State): void { + const shorthandValueSymbol = state.checker.getShorthandAssignmentValueSymbol(valueDeclaration)!; + const name = valueDeclaration && getNameOfDeclaration(valueDeclaration); + /* + * Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment + * has two meanings: property name and property value. Therefore when we do findAllReference at the position where + * an identifier is declared, the language service should return the position of the variable declaration as well as + * the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the + * position of property accessing, the referenceEntry of such position will be handled in the first case. + */ + if (!(flags & SymbolFlags.Transient) && name && search.includes(shorthandValueSymbol)) { + addReference(name, shorthandValueSymbol, state); } + } - function addReference(referenceLocation: Node, relatedSymbol: Symbol | RelatedSymbol, state: State): void { - const { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; // eslint-disable-line no-in-operator - const addRef = state.referenceAdder(symbol); - if (state.options.implementations) { - addImplementationReferences(referenceLocation, addRef, state); - } - else { - addRef(referenceLocation, kind); - } + function addReference(referenceLocation: Node, relatedSymbol: Symbol | RelatedSymbol, state: State): void { + const { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; // eslint-disable-line no-in-operator + const addRef = state.referenceAdder(symbol); + if (state.options.implementations) { + addImplementationReferences(referenceLocation, addRef, state); + } + else { + addRef(referenceLocation, kind); } + } - /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ - function addConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void { - if (isNewExpressionTarget(referenceLocation)) { - addReference(referenceLocation, search.symbol, state); - } + /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ + function addConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void { + if (isNewExpressionTarget(referenceLocation)) { + addReference(referenceLocation, search.symbol, state); + } - const pusher = () => state.referenceAdder(search.symbol); + const pusher = () => state.referenceAdder(search.symbol); - if (isClassLike(referenceLocation.parent)) { - Debug.assert(referenceLocation.kind === SyntaxKind.DefaultKeyword || referenceLocation.parent.name === referenceLocation); - // This is the class declaration containing the constructor. - findOwnConstructorReferences(search.symbol, sourceFile, pusher()); - } - else { - // If this class appears in `extends C`, then the extending class' "super" calls are references. - const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); - if (classExtending) { - findSuperConstructorAccesses(classExtending, pusher()); - findInheritedConstructorReferences(classExtending, state); - } + if (isClassLike(referenceLocation.parent)) { + Debug.assert(referenceLocation.kind === SyntaxKind.DefaultKeyword || referenceLocation.parent.name === referenceLocation); + // This is the class declaration containing the constructor. + findOwnConstructorReferences(search.symbol, sourceFile, pusher()); + } + else { + // If this class appears in `extends C`, then the extending class' "super" calls are references. + const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); + if (classExtending) { + findSuperConstructorAccesses(classExtending, pusher()); + findInheritedConstructorReferences(classExtending, state); } } + } - function addClassStaticThisReferences(referenceLocation: Node, search: Search, state: State): void { - addReference(referenceLocation, search.symbol, state); - const classLike = referenceLocation.parent; - if (state.options.use === FindReferencesUse.Rename || !isClassLike(classLike)) return; - Debug.assert(classLike.name === referenceLocation); - const addRef = state.referenceAdder(search.symbol); - for (const member of classLike.members) { - if (!(isMethodOrAccessor(member) && isStatic(member))) { - continue; - } - if (member.body) { - member.body.forEachChild(function cb(node) { - if (node.kind === SyntaxKind.ThisKeyword) { - addRef(node); - } - else if (!isFunctionLike(node) && !isClassLike(node)) { - node.forEachChild(cb); - } - }); - } + function addClassStaticThisReferences(referenceLocation: Node, search: Search, state: State): void { + addReference(referenceLocation, search.symbol, state); + const classLike = referenceLocation.parent; + if (state.options.use === FindReferencesUse.Rename || !isClassLike(classLike)) return; + Debug.assert(classLike.name === referenceLocation); + const addRef = state.referenceAdder(search.symbol); + for (const member of classLike.members) { + if (!(isMethodOrAccessor(member) && isStatic(member))) { + continue; + } + if (member.body) { + member.body.forEachChild(function cb(node) { + if (node.kind === SyntaxKind.ThisKeyword) { + addRef(node); + } + else if (!isFunctionLike(node) && !isClassLike(node)) { + node.forEachChild(cb); + } + }); } } + } - /** - * `classSymbol` is the class where the constructor was defined. - * Reference the constructor and all calls to `new this()`. - */ - function findOwnConstructorReferences(classSymbol: Symbol, sourceFile: SourceFile, addNode: (node: Node) => void): void { - const constructorSymbol = getClassConstructorSymbol(classSymbol); - if (constructorSymbol && constructorSymbol.declarations) { - for (const decl of constructorSymbol.declarations) { - const ctrKeyword = findChildOfKind(decl, SyntaxKind.ConstructorKeyword, sourceFile)!; - Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword); - addNode(ctrKeyword); + /** + * `classSymbol` is the class where the constructor was defined. + * Reference the constructor and all calls to `new this()`. + */ + function findOwnConstructorReferences(classSymbol: Symbol, sourceFile: SourceFile, addNode: (node: Node) => void): void { + const constructorSymbol = getClassConstructorSymbol(classSymbol); + if (constructorSymbol && constructorSymbol.declarations) { + for (const decl of constructorSymbol.declarations) { + const ctrKeyword = findChildOfKind(decl, SyntaxKind.ConstructorKeyword, sourceFile)!; + Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword); + addNode(ctrKeyword); + } + } + + if (classSymbol.exports) { + classSymbol.exports.forEach(member => { + const decl = member.valueDeclaration; + if (decl && decl.kind === SyntaxKind.MethodDeclaration) { + const body = (decl as MethodDeclaration).body; + if (body) { + forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => { + if (isNewExpressionTarget(thisKeyword)) { + addNode(thisKeyword); + } + }); + } } - } + }); + } + } - if (classSymbol.exports) { - classSymbol.exports.forEach(member => { - const decl = member.valueDeclaration; - if (decl && decl.kind === SyntaxKind.MethodDeclaration) { - const body = (decl as MethodDeclaration).body; - if (body) { - forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => { - if (isNewExpressionTarget(thisKeyword)) { - addNode(thisKeyword); - } - }); - } + function getClassConstructorSymbol(classSymbol: Symbol): Symbol | undefined { + return classSymbol.members && classSymbol.members.get(InternalSymbolName.Constructor); + } + + /** Find references to `super` in the constructor of an extending class. */ + function findSuperConstructorAccesses(classDeclaration: ClassLikeDeclaration, addNode: (node: Node) => void): void { + const constructor = getClassConstructorSymbol(classDeclaration.symbol); + if (!(constructor && constructor.declarations)) { + return; + } + + for (const decl of constructor.declarations) { + Debug.assert(decl.kind === SyntaxKind.Constructor); + const body = (decl as ConstructorDeclaration).body; + if (body) { + forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => { + if (isCallExpressionTarget(node)) { + addNode(node); } }); } } + } - function getClassConstructorSymbol(classSymbol: Symbol): Symbol | undefined { - return classSymbol.members && classSymbol.members.get(InternalSymbolName.Constructor); - } + function hasOwnConstructor(classDeclaration: ClassLikeDeclaration): boolean { + return !!getClassConstructorSymbol(classDeclaration.symbol); + } - /** Find references to `super` in the constructor of an extending class. */ - function findSuperConstructorAccesses(classDeclaration: ClassLikeDeclaration, addNode: (node: Node) => void): void { - const constructor = getClassConstructorSymbol(classDeclaration.symbol); - if (!(constructor && constructor.declarations)) { - return; - } + function findInheritedConstructorReferences(classDeclaration: ClassLikeDeclaration, state: State): void { + if (hasOwnConstructor(classDeclaration)) return; + const classSymbol = classDeclaration.symbol; + const search = state.createSearch(/*location*/ undefined, classSymbol, /*comingFrom*/ undefined); + getReferencesInContainerOrFiles(classSymbol, state, search); + } - for (const decl of constructor.declarations) { - Debug.assert(decl.kind === SyntaxKind.Constructor); - const body = (decl as ConstructorDeclaration).body; - if (body) { - forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => { - if (isCallExpressionTarget(node)) { - addNode(node); - } - }); - } - } + function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void { + // Check if we found a function/propertyAssignment/method with an implementation or initializer + if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { + addReference(refNode); + return; } - function hasOwnConstructor(classDeclaration: ClassLikeDeclaration): boolean { - return !!getClassConstructorSymbol(classDeclaration.symbol); + if (refNode.kind !== SyntaxKind.Identifier) { + return; } - function findInheritedConstructorReferences(classDeclaration: ClassLikeDeclaration, state: State): void { - if (hasOwnConstructor(classDeclaration)) return; - const classSymbol = classDeclaration.symbol; - const search = state.createSearch(/*location*/ undefined, classSymbol, /*comingFrom*/ undefined); - getReferencesInContainerOrFiles(classSymbol, state, search); + if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + // Go ahead and dereference the shorthand assignment by going to its definition + getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addReference); } - function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void { - // Check if we found a function/propertyAssignment/method with an implementation or initializer - if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { - addReference(refNode); - return; - } - - if (refNode.kind !== SyntaxKind.Identifier) { - return; - } - - if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - // Go ahead and dereference the shorthand assignment by going to its definition - getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addReference); - } + // Check if the node is within an extends or implements clause + const containingClass = getContainingClassIfInHeritageClause(refNode); + if (containingClass) { + addReference(containingClass); + return; + } - // Check if the node is within an extends or implements clause - const containingClass = getContainingClassIfInHeritageClause(refNode); - if (containingClass) { - addReference(containingClass); - return; + // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface + // Find the first node whose parent isn't a type node -- i.e., the highest type node. + const typeNode = findAncestor(refNode, a => !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent))!; + const typeHavingNode = typeNode.parent; + if (hasType(typeHavingNode) && typeHavingNode.type === typeNode && state.markSeenContainingTypeReference(typeHavingNode)) { + if (hasInitializer(typeHavingNode)) { + addIfImplementation(typeHavingNode.initializer!); } - - // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface - // Find the first node whose parent isn't a type node -- i.e., the highest type node. - const typeNode = findAncestor(refNode, a => !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent))!; - const typeHavingNode = typeNode.parent; - if (hasType(typeHavingNode) && typeHavingNode.type === typeNode && state.markSeenContainingTypeReference(typeHavingNode)) { - if (hasInitializer(typeHavingNode)) { - addIfImplementation(typeHavingNode.initializer!); - } - else if (isFunctionLike(typeHavingNode) && (typeHavingNode as FunctionLikeDeclaration).body) { - const body = (typeHavingNode as FunctionLikeDeclaration).body!; - if (body.kind === SyntaxKind.Block) { - forEachReturnStatement(body as Block, returnStatement => { - if (returnStatement.expression) addIfImplementation(returnStatement.expression); - }); - } - else { - addIfImplementation(body); - } + else if (isFunctionLike(typeHavingNode) && (typeHavingNode as FunctionLikeDeclaration).body) { + const body = (typeHavingNode as FunctionLikeDeclaration).body!; + if (body.kind === SyntaxKind.Block) { + forEachReturnStatement(body as Block, returnStatement => { + if (returnStatement.expression) addIfImplementation(returnStatement.expression); + }); } - else if (isAssertionExpression(typeHavingNode)) { - addIfImplementation(typeHavingNode.expression); + else { + addIfImplementation(body); } } - - function addIfImplementation(e: Expression): void { - if (isImplementationExpression(e)) addReference(e); + else if (isAssertionExpression(typeHavingNode)) { + addIfImplementation(typeHavingNode.expression); } } - function getContainingClassIfInHeritageClause(node: Node): ClassLikeDeclaration | InterfaceDeclaration | undefined { - return isIdentifier(node) || isPropertyAccessExpression(node) ? getContainingClassIfInHeritageClause(node.parent) - : isExpressionWithTypeArguments(node) ? tryCast(node.parent.parent, isClassLike) : undefined; + function addIfImplementation(e: Expression): void { + if (isImplementationExpression(e)) addReference(e); } + } - /** - * Returns true if this is an expression that can be considered an implementation - */ - function isImplementationExpression(node: Expression): boolean { - switch (node.kind) { - case SyntaxKind.ParenthesizedExpression: - return isImplementationExpression((node as ParenthesizedExpression).expression); - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ClassExpression: - case SyntaxKind.ArrayLiteralExpression: - return true; - default: - return false; - } - } + function getContainingClassIfInHeritageClause(node: Node): ClassLikeDeclaration | InterfaceDeclaration | undefined { + return isIdentifier(node) || isPropertyAccessExpression(node) ? getContainingClassIfInHeritageClause(node.parent) + : isExpressionWithTypeArguments(node) ? tryCast(node.parent.parent, isClassLike) : undefined; + } - /** - * Determines if the parent symbol occurs somewhere in the child's ancestry. If the parent symbol - * is an interface, determines if some ancestor of the child symbol extends or inherits from it. - * Also takes in a cache of previous results which makes this slightly more efficient and is - * necessary to avoid potential loops like so: - * class A extends B { } - * class B extends A { } - * - * We traverse the AST rather than using the type checker because users are typically only interested - * in explicit implementations of an interface/class when calling "Go to Implementation". Sibling - * implementations of types that share a common ancestor with the type whose implementation we are - * searching for need to be filtered out of the results. The type checker doesn't let us make the - * distinction between structurally compatible implementations and explicit implementations, so we - * must use the AST. - * - * @param symbol A class or interface Symbol - * @param parent Another class or interface Symbol - * @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results - */ - function explicitlyInheritsFrom(symbol: Symbol, parent: Symbol, cachedResults: ESMap, checker: TypeChecker): boolean { - if (symbol === parent) { + /** + * Returns true if this is an expression that can be considered an implementation + */ + function isImplementationExpression(node: Expression): boolean { + switch (node.kind) { + case SyntaxKind.ParenthesizedExpression: + return isImplementationExpression((node as ParenthesizedExpression).expression); + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ClassExpression: + case SyntaxKind.ArrayLiteralExpression: return true; - } + default: + return false; + } + } - const key = getSymbolId(symbol) + "," + getSymbolId(parent); - const cached = cachedResults.get(key); - if (cached !== undefined) { - return cached; - } + /** + * Determines if the parent symbol occurs somewhere in the child's ancestry. If the parent symbol + * is an interface, determines if some ancestor of the child symbol extends or inherits from it. + * Also takes in a cache of previous results which makes this slightly more efficient and is + * necessary to avoid potential loops like so: + * class A extends B { } + * class B extends A { } + * + * We traverse the AST rather than using the type checker because users are typically only interested + * in explicit implementations of an interface/class when calling "Go to Implementation". Sibling + * implementations of types that share a common ancestor with the type whose implementation we are + * searching for need to be filtered out of the results. The type checker doesn't let us make the + * distinction between structurally compatible implementations and explicit implementations, so we + * must use the AST. + * + * @param symbol A class or interface Symbol + * @param parent Another class or interface Symbol + * @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results + */ + function explicitlyInheritsFrom(symbol: Symbol, parent: Symbol, cachedResults: ESMap, checker: TypeChecker): boolean { + if (symbol === parent) { + return true; + } + + const key = getSymbolId(symbol) + "," + getSymbolId(parent); + const cached = cachedResults.get(key); + if (cached !== undefined) { + return cached; + } + + // Set the key so that we don't infinitely recurse + cachedResults.set(key, false); - // Set the key so that we don't infinitely recurse - cachedResults.set(key, false); + const inherits = !!symbol.declarations && symbol.declarations.some(declaration => + getAllSuperTypeNodes(declaration).some(typeReference => { + const type = checker.getTypeAtLocation(typeReference); + return !!type && !!type.symbol && explicitlyInheritsFrom(type.symbol, parent, cachedResults, checker); + })); + cachedResults.set(key, inherits); + return inherits; + } - const inherits = !!symbol.declarations && symbol.declarations.some(declaration => - getAllSuperTypeNodes(declaration).some(typeReference => { - const type = checker.getTypeAtLocation(typeReference); - return !!type && !!type.symbol && explicitlyInheritsFrom(type.symbol, parent, cachedResults, checker); - })); - cachedResults.set(key, inherits); - return inherits; + function getReferencesForSuperKeyword(superKeyword: Node): SymbolAndEntries[] | undefined { + let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); + if (!searchSpaceNode) { + return undefined; } + // Whether 'super' occurs in a static context within a class. + let staticFlag = ModifierFlags.Static; - function getReferencesForSuperKeyword(superKeyword: Node): SymbolAndEntries[] | undefined { - let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); - if (!searchSpaceNode) { + switch (searchSpaceNode.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + staticFlag &= getSyntacticModifierFlags(searchSpaceNode); + searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class + break; + default: return undefined; - } - // Whether 'super' occurs in a static context within a class. - let staticFlag = ModifierFlags.Static; - - switch (searchSpaceNode.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - staticFlag &= getSyntacticModifierFlags(searchSpaceNode); - searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class - break; - default: - return undefined; - } + } - const sourceFile = searchSpaceNode.getSourceFile(); - const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), node => { - if (node.kind !== SyntaxKind.SuperKeyword) { - return; - } + const sourceFile = searchSpaceNode.getSourceFile(); + const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), node => { + if (node.kind !== SyntaxKind.SuperKeyword) { + return; + } - const container = getSuperContainer(node, /*stopOnFunctions*/ false); + const container = getSuperContainer(node, /*stopOnFunctions*/ false); - // If we have a 'super' container, we must have an enclosing class. - // Now make sure the owning class is the same as the search-space - // and has the same static qualifier as the original 'super's owner. - return container && isStatic(container) === !!staticFlag && container.parent.symbol === searchSpaceNode.symbol ? nodeEntry(node) : undefined; - }); + // If we have a 'super' container, we must have an enclosing class. + // Now make sure the owning class is the same as the search-space + // and has the same static qualifier as the original 'super's owner. + return container && isStatic(container) === !!staticFlag && container.parent.symbol === searchSpaceNode.symbol ? nodeEntry(node) : undefined; + }); - return [{ definition: { type: DefinitionKind.Symbol, symbol: searchSpaceNode.symbol }, references }]; - } + return [{ definition: { type: DefinitionKind.Symbol, symbol: searchSpaceNode.symbol }, references }]; + } - function isParameterName(node: Node) { - return node.kind === SyntaxKind.Identifier && node.parent.kind === SyntaxKind.Parameter && (node.parent as ParameterDeclaration).name === node; - } + function isParameterName(node: Node) { + return node.kind === SyntaxKind.Identifier && node.parent.kind === SyntaxKind.Parameter && (node.parent as ParameterDeclaration).name === node; + } - function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { - let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); + function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { + let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); - // Whether 'this' occurs in a static context within a class. - let staticFlag = ModifierFlags.Static; + // Whether 'this' occurs in a static context within a class. + let staticFlag = ModifierFlags.Static; - switch (searchSpaceNode.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (isObjectLiteralMethod(searchSpaceNode)) { - staticFlag &= getSyntacticModifierFlags(searchSpaceNode); - searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning object literals - break; - } - // falls through - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: + switch (searchSpaceNode.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + if (isObjectLiteralMethod(searchSpaceNode)) { staticFlag &= getSyntacticModifierFlags(searchSpaceNode); - searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class - break; - case SyntaxKind.SourceFile: - if (isExternalModule(searchSpaceNode as SourceFile) || isParameterName(thisOrSuperKeyword)) { - return undefined; - } - // falls through - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: + searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning object literals break; - // Computed properties in classes are not handled here because references to this are illegal, - // so there is no point finding references to them. - default: + } + // falls through + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + staticFlag &= getSyntacticModifierFlags(searchSpaceNode); + searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class + break; + case SyntaxKind.SourceFile: + if (isExternalModule(searchSpaceNode as SourceFile) || isParameterName(thisOrSuperKeyword)) { return undefined; - } + } + // falls through + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + break; + // Computed properties in classes are not handled here because references to this are illegal, + // so there is no point finding references to them. + default: + return undefined; + } - const references = flatMap(searchSpaceNode.kind === SyntaxKind.SourceFile ? sourceFiles : [searchSpaceNode.getSourceFile()], sourceFile => { - cancellationToken.throwIfCancellationRequested(); - return getPossibleSymbolReferenceNodes(sourceFile, "this", isSourceFile(searchSpaceNode) ? sourceFile : searchSpaceNode).filter(node => { - if (!isThis(node)) { - return false; - } - const container = getThisContainer(node, /* includeArrowFunctions */ false); - switch (searchSpaceNode.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - return searchSpaceNode.symbol === container.symbol; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol; - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ObjectLiteralExpression: - // Make sure the container belongs to the same class/object literals - // and has the appropriate static modifier from the original container. - return container.parent && searchSpaceNode.symbol === container.parent.symbol && isStatic(container) === !!staticFlag; - case SyntaxKind.SourceFile: - return container.kind === SyntaxKind.SourceFile && !isExternalModule(container as SourceFile) && !isParameterName(node); - } - }); - }).map(n => nodeEntry(n)); + const references = flatMap(searchSpaceNode.kind === SyntaxKind.SourceFile ? sourceFiles : [searchSpaceNode.getSourceFile()], sourceFile => { + cancellationToken.throwIfCancellationRequested(); + return getPossibleSymbolReferenceNodes(sourceFile, "this", isSourceFile(searchSpaceNode) ? sourceFile : searchSpaceNode).filter(node => { + if (!isThis(node)) { + return false; + } + const container = getThisContainer(node, /* includeArrowFunctions */ false); + switch (searchSpaceNode.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + return searchSpaceNode.symbol === container.symbol; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + return isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol; + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ObjectLiteralExpression: + // Make sure the container belongs to the same class/object literals + // and has the appropriate static modifier from the original container. + return container.parent && searchSpaceNode.symbol === container.parent.symbol && isStatic(container) === !!staticFlag; + case SyntaxKind.SourceFile: + return container.kind === SyntaxKind.SourceFile && !isExternalModule(container as SourceFile) && !isParameterName(node); + } + }); + }).map(n => nodeEntry(n)); - const thisParameter = firstDefined(references, r => isParameter(r.node.parent) ? r.node : undefined); - return [{ - definition: { type: DefinitionKind.This, node: thisParameter || thisOrSuperKeyword }, - references - }]; - } + const thisParameter = firstDefined(references, r => isParameter(r.node.parent) ? r.node : undefined); + return [{ + definition: { type: DefinitionKind.This, node: thisParameter || thisOrSuperKeyword }, + references + }]; + } - function getReferencesForStringLiteral(node: StringLiteralLike, sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): SymbolAndEntries[] { - const type = getContextualTypeFromParentOrAncestorTypeNode(node, checker); - const references = flatMap(sourceFiles, sourceFile => { - cancellationToken.throwIfCancellationRequested(); - return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref => { - if (isStringLiteralLike(ref) && ref.text === node.text) { - if (type) { - const refType = getContextualTypeFromParentOrAncestorTypeNode(ref, checker); - if (type !== checker.getStringType() && type === refType) { - return nodeEntry(ref, EntryKind.StringLiteral); - } - } - else { - return isNoSubstitutionTemplateLiteral(ref) && !rangeIsOnSingleLine(ref, sourceFile) ? undefined : - nodeEntry(ref, EntryKind.StringLiteral); + function getReferencesForStringLiteral(node: StringLiteralLike, sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): SymbolAndEntries[] { + const type = getContextualTypeFromParentOrAncestorTypeNode(node, checker); + const references = flatMap(sourceFiles, sourceFile => { + cancellationToken.throwIfCancellationRequested(); + return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref => { + if (isStringLiteralLike(ref) && ref.text === node.text) { + if (type) { + const refType = getContextualTypeFromParentOrAncestorTypeNode(ref, checker); + if (type !== checker.getStringType() && type === refType) { + return nodeEntry(ref, EntryKind.StringLiteral); } } - }); + else { + return isNoSubstitutionTemplateLiteral(ref) && !rangeIsOnSingleLine(ref, sourceFile) ? undefined : + nodeEntry(ref, EntryKind.StringLiteral); + } + } }); + }); - return [{ - definition: { type: DefinitionKind.String, node }, - references - }]; - } + return [{ + definition: { type: DefinitionKind.String, node }, + references + }]; + } - // For certain symbol kinds, we need to include other symbols in the search set. - // This is not needed when searching for re-exports. - function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, isForRename: boolean, providePrefixAndSuffixText: boolean, implementations: boolean): Symbol[] { - const result: Symbol[] = []; - forEachRelatedSymbol(symbol, location, checker, isForRename, !(isForRename && providePrefixAndSuffixText), - (sym, root, base) => { - // static method/property and instance method/property might have the same name. Only include static or only include instance. - if (base) { - if (isStaticSymbol(symbol) !== isStaticSymbol(base)) { - base = undefined; - } + // For certain symbol kinds, we need to include other symbols in the search set. + // This is not needed when searching for re-exports. + function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, isForRename: boolean, providePrefixAndSuffixText: boolean, implementations: boolean): Symbol[] { + const result: Symbol[] = []; + forEachRelatedSymbol(symbol, location, checker, isForRename, !(isForRename && providePrefixAndSuffixText), + (sym, root, base) => { + // static method/property and instance method/property might have the same name. Only include static or only include instance. + if (base) { + if (isStaticSymbol(symbol) !== isStaticSymbol(base)) { + base = undefined; } - result.push(base || root || sym); - }, - // when try to find implementation, implementations is true, and not allowed to find base class - /*allowBaseTypes*/() => !implementations); - return result; - } + } + result.push(base || root || sym); + }, + // when try to find implementation, implementations is true, and not allowed to find base class + /*allowBaseTypes*/() => !implementations); + return result; + } + /** + * @param allowBaseTypes return true means it would try to find in base class or interface. + */ + function forEachRelatedSymbol( + symbol: Symbol, location: Node, checker: TypeChecker, isForRenamePopulateSearchSymbolSet: boolean, onlyIncludeBindingElementAtReferenceLocation: boolean, /** - * @param allowBaseTypes return true means it would try to find in base class or interface. + * @param baseSymbol This symbol means one property/mehtod from base class or interface when it is not null or undefined, */ - function forEachRelatedSymbol( - symbol: Symbol, location: Node, checker: TypeChecker, isForRenamePopulateSearchSymbolSet: boolean, onlyIncludeBindingElementAtReferenceLocation: boolean, - /** - * @param baseSymbol This symbol means one property/mehtod from base class or interface when it is not null or undefined, - */ - cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol, kind?: NodeEntryKind) => T | undefined, - allowBaseTypes: (rootSymbol: Symbol) => boolean, - ): T | undefined { - const containingObjectLiteralElement = getContainingObjectLiteralElement(location); - if (containingObjectLiteralElement) { - /* Because in short-hand property assignment, location has two meaning : property name and as value of the property - * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of - * property name and variable declaration of the identifier. - * Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service - * should show both 'name' in 'obj' and 'name' in variable declaration - * const name = "Foo"; - * const obj = { name }; - * In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment - * so that when matching with potential reference symbol, both symbols from property declaration and variable declaration - * will be included correctly. - */ - const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent); // gets the local symbol - if (shorthandValueSymbol && isForRenamePopulateSearchSymbolSet) { - // When renaming 'x' in `const o = { x }`, just rename the local variable, not the property. - return cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); - } + cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol, kind?: NodeEntryKind) => T | undefined, + allowBaseTypes: (rootSymbol: Symbol) => boolean, + ): T | undefined { + const containingObjectLiteralElement = getContainingObjectLiteralElement(location); + if (containingObjectLiteralElement) { + /* Because in short-hand property assignment, location has two meaning : property name and as value of the property + * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of + * property name and variable declaration of the identifier. + * Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service + * should show both 'name' in 'obj' and 'name' in variable declaration + * const name = "Foo"; + * const obj = { name }; + * In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment + * so that when matching with potential reference symbol, both symbols from property declaration and variable declaration + * will be included correctly. + */ + const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent); // gets the local symbol + if (shorthandValueSymbol && isForRenamePopulateSearchSymbolSet) { + // When renaming 'x' in `const o = { x }`, just rename the local variable, not the property. + return cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); + } + + // If the location is in a context sensitive location (i.e. in an object literal) try + // to get a contextual type for it, and add the property symbol from the contextual + // type to the search set + const contextualType = checker.getContextualType(containingObjectLiteralElement.parent); + const res = contextualType && firstDefined( + getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true), + sym => fromRoot(sym, EntryKind.SearchedPropertyFoundLocal)); + if (res) return res; - // If the location is in a context sensitive location (i.e. in an object literal) try - // to get a contextual type for it, and add the property symbol from the contextual - // type to the search set - const contextualType = checker.getContextualType(containingObjectLiteralElement.parent); - const res = contextualType && firstDefined( - getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true), - sym => fromRoot(sym, EntryKind.SearchedPropertyFoundLocal)); - if (res) return res; + // If the location is name of property symbol from object literal destructuring pattern + // Search the property symbol + // for ( { property: p2 } of elems) { } + const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker); + const res1 = propertySymbol && cbSymbol(propertySymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedPropertyFoundLocal); + if (res1) return res1; - // If the location is name of property symbol from object literal destructuring pattern - // Search the property symbol - // for ( { property: p2 } of elems) { } - const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker); - const res1 = propertySymbol && cbSymbol(propertySymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedPropertyFoundLocal); - if (res1) return res1; + const res2 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); + if (res2) return res2; + } - const res2 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); - if (res2) return res2; - } + const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(location, symbol, checker); + if (aliasedSymbol) { + // In case of UMD module and global merging, search for global as well + const res = cbSymbol(aliasedSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node); + if (res) return res; + } - const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(location, symbol, checker); - if (aliasedSymbol) { - // In case of UMD module and global merging, search for global as well - const res = cbSymbol(aliasedSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node); - if (res) return res; - } + const res = fromRoot(symbol); + if (res) return res; - const res = fromRoot(symbol); - if (res) return res; + if (symbol.valueDeclaration && isParameterPropertyDeclaration(symbol.valueDeclaration, symbol.valueDeclaration.parent)) { + // For a parameter property, now try on the other symbol (property if this was a parameter, parameter if this was a property). + const paramProps = checker.getSymbolsOfParameterPropertyDeclaration(cast(symbol.valueDeclaration, isParameter), symbol.name); + Debug.assert(paramProps.length === 2 && !!(paramProps[0].flags & SymbolFlags.FunctionScopedVariable) && !!(paramProps[1].flags & SymbolFlags.Property)); // is [parameter, property] + return fromRoot(symbol.flags & SymbolFlags.FunctionScopedVariable ? paramProps[1] : paramProps[0]); + } - if (symbol.valueDeclaration && isParameterPropertyDeclaration(symbol.valueDeclaration, symbol.valueDeclaration.parent)) { - // For a parameter property, now try on the other symbol (property if this was a parameter, parameter if this was a property). - const paramProps = checker.getSymbolsOfParameterPropertyDeclaration(cast(symbol.valueDeclaration, isParameter), symbol.name); - Debug.assert(paramProps.length === 2 && !!(paramProps[0].flags & SymbolFlags.FunctionScopedVariable) && !!(paramProps[1].flags & SymbolFlags.Property)); // is [parameter, property] - return fromRoot(symbol.flags & SymbolFlags.FunctionScopedVariable ? paramProps[1] : paramProps[0]); + const exportSpecifier = getDeclarationOfKind(symbol, SyntaxKind.ExportSpecifier); + if (!isForRenamePopulateSearchSymbolSet || exportSpecifier && !exportSpecifier.propertyName) { + const localSymbol = exportSpecifier && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier); + if (localSymbol) { + const res = cbSymbol(localSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node); + if (res) return res; } + } - const exportSpecifier = getDeclarationOfKind(symbol, SyntaxKind.ExportSpecifier); - if (!isForRenamePopulateSearchSymbolSet || exportSpecifier && !exportSpecifier.propertyName) { - const localSymbol = exportSpecifier && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier); - if (localSymbol) { - const res = cbSymbol(localSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node); - if (res) return res; - } + // symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property. + // Don't do this when populating search set for a rename when prefix and suffix text will be provided -- just rename the local. + if (!isForRenamePopulateSearchSymbolSet) { + let bindingElementPropertySymbol: Symbol | undefined; + if (onlyIncludeBindingElementAtReferenceLocation) { + bindingElementPropertySymbol = isObjectBindingElementWithoutPropertyName(location.parent) ? getPropertySymbolFromBindingElement(checker, location.parent) : undefined; } - - // symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property. - // Don't do this when populating search set for a rename when prefix and suffix text will be provided -- just rename the local. - if (!isForRenamePopulateSearchSymbolSet) { - let bindingElementPropertySymbol: Symbol | undefined; - if (onlyIncludeBindingElementAtReferenceLocation) { - bindingElementPropertySymbol = isObjectBindingElementWithoutPropertyName(location.parent) ? getPropertySymbolFromBindingElement(checker, location.parent) : undefined; - } - else { - bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); - } - return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); - } - - Debug.assert(isForRenamePopulateSearchSymbolSet); - // due to the above assert and the arguments at the uses of this function, - // (onlyIncludeBindingElementAtReferenceLocation <=> !providePrefixAndSuffixTextForRename) holds - const includeOriginalSymbolOfBindingElement = onlyIncludeBindingElementAtReferenceLocation; - - if (includeOriginalSymbolOfBindingElement) { - const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); - return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); - } - - function fromRoot(sym: Symbol, kind?: NodeEntryKind): T | undefined { - // If this is a union property: - // - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types. - // - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search. - // If the symbol is an instantiation from a another symbol (e.g. widened symbol): - // - In populateSearchSymbolsSet, add the root the list - // - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.) - return firstDefined(checker.getRootSymbols(sym), rootSymbol => - cbSymbol(sym, rootSymbol, /*baseSymbol*/ undefined, kind) - // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions - || (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface) && allowBaseTypes(rootSymbol) - ? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base, kind)) - : undefined)); - } - - function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, checker: TypeChecker): Symbol | undefined { - const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement); - if (bindingElement && isObjectBindingElementWithoutPropertyName(bindingElement)) { - return getPropertySymbolFromBindingElement(checker, bindingElement); - } + else { + bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); } + return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); } - /** - * Find symbol of the given property-name and add the symbol to the given result array - * @param symbol a symbol to start searching for the given propertyName - * @param propertyName a name of property to search for - * @param result an array of symbol of found property symbols - * @param previousIterationSymbolsCache a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol. - * The value of previousIterationSymbol is undefined when the function is first called. - */ - function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, checker: TypeChecker, cb: (symbol: Symbol) => T | undefined): T | undefined { - const seen = new Map(); - return recur(symbol); - - function recur(symbol: Symbol): T | undefined { - // Use `addToSeen` to ensure we don't infinitely recurse in this situation: - // interface C extends C { - // /*findRef*/propName: string; - // } - if (!(symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) || !addToSeen(seen, getSymbolId(symbol))) return; - - return firstDefined(symbol.declarations, declaration => firstDefined(getAllSuperTypeNodes(declaration), typeReference => { - const type = checker.getTypeAtLocation(typeReference); - const propertySymbol = type && type.symbol && checker.getPropertyOfType(type, propertyName); - // Visit the typeReference as well to see if it directly or indirectly uses that property - return type && propertySymbol && (firstDefined(checker.getRootSymbols(propertySymbol), cb) || recur(type.symbol)); - })); - } - } - - interface RelatedSymbol { - readonly symbol: Symbol; - readonly kind: NodeEntryKind | undefined; - } - - function isStaticSymbol(symbol: Symbol): boolean { - if (!symbol.valueDeclaration) return false; - const modifierFlags = getEffectiveModifierFlags(symbol.valueDeclaration); - return !!(modifierFlags & ModifierFlags.Static); - } - - function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): RelatedSymbol | undefined { - const { checker } = state; - return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker, /*isForRenamePopulateSearchSymbolSet*/ false, - /*onlyIncludeBindingElementAtReferenceLocation*/ state.options.use !== FindReferencesUse.Rename || !!state.options.providePrefixAndSuffixTextForRename, - (sym, rootSymbol, baseSymbol, kind): RelatedSymbol | undefined => { - // check whether the symbol used to search itself is just the searched one. - if (baseSymbol) { - // static method/property and instance method/property might have the same name. Only check static or only check instance. - if (isStaticSymbol(referenceSymbol) !== isStaticSymbol(baseSymbol)) { - baseSymbol = undefined; - } - } - return search.includes(baseSymbol || rootSymbol || sym) - // For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol. - ? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym, kind } - : undefined; - }, - /*allowBaseTypes*/ rootSymbol => - !(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent!, parent, state.inheritsFromCache, checker))) - ); + Debug.assert(isForRenamePopulateSearchSymbolSet); + // due to the above assert and the arguments at the uses of this function, + // (onlyIncludeBindingElementAtReferenceLocation <=> !providePrefixAndSuffixTextForRename) holds + const includeOriginalSymbolOfBindingElement = onlyIncludeBindingElementAtReferenceLocation; + + if (includeOriginalSymbolOfBindingElement) { + const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); + return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); } - /** - * Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations - * of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class - * then we need to widen the search to include type positions as well. - * On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated - * module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module) - * do not intersect in any of the three spaces. - */ - export function getIntersectingMeaningFromDeclarations(node: Node, symbol: Symbol): SemanticMeaning { - let meaning = getMeaningFromLocation(node); - const { declarations } = symbol; - if (declarations) { - let lastIterationMeaning: SemanticMeaning; - do { - // The result is order-sensitive, for instance if initialMeaning === Namespace, and declarations = [class, instantiated module] - // we need to consider both as they initialMeaning intersects with the module in the namespace space, and the module - // intersects with the class in the value space. - // To achieve that we will keep iterating until the result stabilizes. - - // Remember the last meaning - lastIterationMeaning = meaning; - - for (const declaration of declarations) { - const declarationMeaning = getMeaningFromDeclaration(declaration); - - if (declarationMeaning & meaning) { - meaning |= declarationMeaning; - } - } - } - while (meaning !== lastIterationMeaning); + function fromRoot(sym: Symbol, kind?: NodeEntryKind): T | undefined { + // If this is a union property: + // - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types. + // - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search. + // If the symbol is an instantiation from a another symbol (e.g. widened symbol): + // - In populateSearchSymbolsSet, add the root the list + // - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.) + return firstDefined(checker.getRootSymbols(sym), rootSymbol => + cbSymbol(sym, rootSymbol, /*baseSymbol*/ undefined, kind) + // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions + || (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface) && allowBaseTypes(rootSymbol) + ? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base, kind)) + : undefined)); + } + + function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, checker: TypeChecker): Symbol | undefined { + const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement); + if (bindingElement && isObjectBindingElementWithoutPropertyName(bindingElement)) { + return getPropertySymbolFromBindingElement(checker, bindingElement); } - return meaning; } + } - function isImplementation(node: Node): boolean { - return !!(node.flags & NodeFlags.Ambient) ? !(isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)) : - (isVariableLike(node) ? hasInitializer(node) : - isFunctionLikeDeclaration(node) ? !!node.body : - isClassLike(node) || isModuleOrEnumDeclaration(node)); + /** + * Find symbol of the given property-name and add the symbol to the given result array + * @param symbol a symbol to start searching for the given propertyName + * @param propertyName a name of property to search for + * @param result an array of symbol of found property symbols + * @param previousIterationSymbolsCache a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol. + * The value of previousIterationSymbol is undefined when the function is first called. + */ + function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, checker: TypeChecker, cb: (symbol: Symbol) => T | undefined): T | undefined { + const seen = new Map(); + return recur(symbol); + + function recur(symbol: Symbol): T | undefined { + // Use `addToSeen` to ensure we don't infinitely recurse in this situation: + // interface C extends C { + // /*findRef*/propName: string; + // } + if (!(symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) || !addToSeen(seen, getSymbolId(symbol))) return; + + return firstDefined(symbol.declarations, declaration => firstDefined(getAllSuperTypeNodes(declaration), typeReference => { + const type = checker.getTypeAtLocation(typeReference); + const propertySymbol = type && type.symbol && checker.getPropertyOfType(type, propertyName); + // Visit the typeReference as well to see if it directly or indirectly uses that property + return type && propertySymbol && (firstDefined(checker.getRootSymbols(propertySymbol), cb) || recur(type.symbol)); + })); } + } + + interface RelatedSymbol { + readonly symbol: Symbol; + readonly kind: NodeEntryKind | undefined; + } - export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void { - const refSymbol = checker.getSymbolAtLocation(node)!; - const shorthandSymbol = checker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); + function isStaticSymbol(symbol: Symbol): boolean { + if (!symbol.valueDeclaration) return false; + const modifierFlags = getEffectiveModifierFlags(symbol.valueDeclaration); + return !!(modifierFlags & ModifierFlags.Static); + } + + function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): RelatedSymbol | undefined { + const { checker } = state; + return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker, /*isForRenamePopulateSearchSymbolSet*/ false, + /*onlyIncludeBindingElementAtReferenceLocation*/ state.options.use !== FindReferencesUse.Rename || !!state.options.providePrefixAndSuffixTextForRename, + (sym, rootSymbol, baseSymbol, kind): RelatedSymbol | undefined => { + // check whether the symbol used to search itself is just the searched one. + if (baseSymbol) { + // static method/property and instance method/property might have the same name. Only check static or only check instance. + if (isStaticSymbol(referenceSymbol) !== isStaticSymbol(baseSymbol)) { + baseSymbol = undefined; + } + } + return search.includes(baseSymbol || rootSymbol || sym) + // For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol. + ? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym, kind } + : undefined; + }, + /*allowBaseTypes*/ rootSymbol => + !(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent!, parent, state.inheritsFromCache, checker))) + ); + } - if (shorthandSymbol) { - for (const declaration of shorthandSymbol.getDeclarations()!) { - if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { - addReference(declaration); + /** + * Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations + * of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class + * then we need to widen the search to include type positions as well. + * On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated + * module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module) + * do not intersect in any of the three spaces. + */ + export function getIntersectingMeaningFromDeclarations(node: Node, symbol: Symbol): SemanticMeaning { + let meaning = getMeaningFromLocation(node); + const { declarations } = symbol; + if (declarations) { + let lastIterationMeaning: SemanticMeaning; + do { + // The result is order-sensitive, for instance if initialMeaning === Namespace, and declarations = [class, instantiated module] + // we need to consider both as they initialMeaning intersects with the module in the namespace space, and the module + // intersects with the class in the value space. + // To achieve that we will keep iterating until the result stabilizes. + + // Remember the last meaning + lastIterationMeaning = meaning; + + for (const declaration of declarations) { + const declarationMeaning = getMeaningFromDeclaration(declaration); + + if (declarationMeaning & meaning) { + meaning |= declarationMeaning; } } } + while (meaning !== lastIterationMeaning); } + return meaning; + } + + function isImplementation(node: Node): boolean { + return !!(node.flags & NodeFlags.Ambient) ? !(isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)) : + (isVariableLike(node) ? hasInitializer(node) : + isFunctionLikeDeclaration(node) ? !!node.body : + isClassLike(node) || isModuleOrEnumDeclaration(node)); + } + + export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void { + const refSymbol = checker.getSymbolAtLocation(node)!; + const shorthandSymbol = checker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); - function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void): void { - forEachChild(node, child => { - if (child.kind === kind) { - action(child); + if (shorthandSymbol) { + for (const declaration of shorthandSymbol.getDeclarations()!) { + if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { + addReference(declaration); } - forEachDescendantOfKind(child, kind, action); - }); + } } + } - /** Get `C` given `N` if `N` is in the position `class C extends N` or `class C extends foo.N` where `N` is an identifier. */ - function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined { - return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent); - } + function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void): void { + forEachChild(node, child => { + if (child.kind === kind) { + action(child); + } + forEachDescendantOfKind(child, kind, action); + }); + } - /** - * If we are just looking for implementations and this is a property access expression, we need to get the - * symbol of the local type of the symbol the property is being accessed on. This is because our search - * symbol may have a different parent symbol if the local type's symbol does not declare the property - * being accessed (i.e. it is declared in some parent class or interface) - */ - function getParentSymbolsOfPropertyAccess(location: Node, symbol: Symbol, checker: TypeChecker): readonly Symbol[] | undefined { - const propertyAccessExpression = isRightSideOfPropertyAccess(location) ? location.parent as PropertyAccessExpression : undefined; - const lhsType = propertyAccessExpression && checker.getTypeAtLocation(propertyAccessExpression.expression); - const res = mapDefined(lhsType && (lhsType.isUnionOrIntersection() ? lhsType.types : lhsType.symbol === symbol.parent ? undefined : [lhsType]), t => - t.symbol && t.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? t.symbol : undefined); - return res.length === 0 ? undefined : res; - } + /** Get `C` given `N` if `N` is in the position `class C extends N` or `class C extends foo.N` where `N` is an identifier. */ + function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined { + return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent); + } - function isForRenameWithPrefixAndSuffixText(options: Options) { - return options.use === FindReferencesUse.Rename && options.providePrefixAndSuffixTextForRename; - } + /** + * If we are just looking for implementations and this is a property access expression, we need to get the + * symbol of the local type of the symbol the property is being accessed on. This is because our search + * symbol may have a different parent symbol if the local type's symbol does not declare the property + * being accessed (i.e. it is declared in some parent class or interface) + */ + function getParentSymbolsOfPropertyAccess(location: Node, symbol: Symbol, checker: TypeChecker): readonly Symbol[] | undefined { + const propertyAccessExpression = isRightSideOfPropertyAccess(location) ? location.parent as PropertyAccessExpression : undefined; + const lhsType = propertyAccessExpression && checker.getTypeAtLocation(propertyAccessExpression.expression); + const res = mapDefined(lhsType && (lhsType.isUnionOrIntersection() ? lhsType.types : lhsType.symbol === symbol.parent ? undefined : [lhsType]), t => + t.symbol && t.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? t.symbol : undefined); + return res.length === 0 ? undefined : res; + } + + function isForRenameWithPrefixAndSuffixText(options: Options) { + return options.use === FindReferencesUse.Rename && options.providePrefixAndSuffixTextForRename; } } +} diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 1a8542741ea46..fbb81daf1215d 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -1,1387 +1,1387 @@ /* @internal */ namespace ts.formatting { - export interface FormatContext { - readonly options: FormatCodeSettings; - readonly getRules: RulesMap; - readonly host: FormattingHost; - } +export interface FormatContext { + readonly options: FormatCodeSettings; + readonly getRules: RulesMap; + readonly host: FormattingHost; +} - export interface TextRangeWithKind extends TextRange { - kind: T; - } +export interface TextRangeWithKind extends TextRange { + kind: T; +} - export type TextRangeWithTriviaKind = TextRangeWithKind; +export type TextRangeWithTriviaKind = TextRangeWithKind; - export interface TokenInfo { - leadingTrivia: TextRangeWithTriviaKind[] | undefined; - token: TextRangeWithKind; - trailingTrivia: TextRangeWithTriviaKind[] | undefined; - } +export interface TokenInfo { + leadingTrivia: TextRangeWithTriviaKind[] | undefined; + token: TextRangeWithKind; + trailingTrivia: TextRangeWithTriviaKind[] | undefined; +} - export function createTextRangeWithKind(pos: number, end: number, kind: T): TextRangeWithKind { - const textRangeWithKind: TextRangeWithKind = { pos, end, kind }; - if (Debug.isDebugging) { - Object.defineProperty(textRangeWithKind, "__debugKind", { - get: () => Debug.formatSyntaxKind(kind), - }); - } - return textRangeWithKind; +export function createTextRangeWithKind(pos: number, end: number, kind: T): TextRangeWithKind { + const textRangeWithKind: TextRangeWithKind = { pos, end, kind }; + if (Debug.isDebugging) { + Object.defineProperty(textRangeWithKind, "__debugKind", { + get: () => Debug.formatSyntaxKind(kind), + }); } + return textRangeWithKind; +} - const enum Constants { - Unknown = -1 - } +const enum Constants { + Unknown = -1 +} - /* - * Indentation for the scope that can be dynamically recomputed. - * i.e - * while(true) - * { let x; - * } - * Normally indentation is applied only to the first token in line so at glance 'let' should not be touched. - * However if some format rule adds new line between '}' and 'let' 'let' will become - * the first token in line so it should be indented +/* + * Indentation for the scope that can be dynamically recomputed. + * i.e + * while(true) + * { let x; + * } + * Normally indentation is applied only to the first token in line so at glance 'let' should not be touched. + * However if some format rule adds new line between '}' and 'let' 'let' will become + * the first token in line so it should be indented + */ +interface DynamicIndentation { + getIndentationForToken(tokenLine: number, tokenKind: SyntaxKind, container: Node, suppressDelta: boolean): number; + getIndentationForComment(owningToken: SyntaxKind, tokenIndentation: number, container: Node): number; + /** + * Indentation for open and close tokens of the node if it is block or another node that needs special indentation + * ... { + * ......... + * ....} + * ____ - indentation + * ____ - delta */ - interface DynamicIndentation { - getIndentationForToken(tokenLine: number, tokenKind: SyntaxKind, container: Node, suppressDelta: boolean): number; - getIndentationForComment(owningToken: SyntaxKind, tokenIndentation: number, container: Node): number; - /** - * Indentation for open and close tokens of the node if it is block or another node that needs special indentation - * ... { - * ......... - * ....} - * ____ - indentation - * ____ - delta - */ - getIndentation(): number; - /** - * Prefered relative indentation for child nodes. - * Delta is used to carry the indentation info - * foo(bar({ - * $ - * })) - * Both 'foo', 'bar' introduce new indentation with delta = 4, but total indentation in $ is not 8. - * foo: { indentation: 0, delta: 4 } - * bar: { indentation: foo.indentation + foo.delta = 4, delta: 4} however 'foo' and 'bar' are on the same line - * so bar inherits indentation from foo and bar.delta will be 4 - * - */ - getDelta(child: TextRangeWithKind): number; - /** - * Formatter calls this function when rule adds or deletes new lines from the text - * so indentation scope can adjust values of indentation and delta. - */ - recomputeIndentation(lineAddedByFormatting: boolean, parent: Node): void; - } - - export function formatOnEnter(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { - const line = sourceFile.getLineAndCharacterOfPosition(position).line; - if (line === 0) { - return []; - } - // After the enter key, the cursor is now at a new line. The new line may or may not contain non-whitespace characters. - // If the new line has only whitespaces, we won't want to format this line, because that would remove the indentation as - // trailing whitespaces. So the end of the formatting span should be the later one between: - // 1. the end of the previous line - // 2. the last non-whitespace character in the current line - let endOfFormatSpan = getEndLinePosition(line, sourceFile); - while (isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(endOfFormatSpan))) { - endOfFormatSpan--; - } - // if the character at the end of the span is a line break, we shouldn't include it, because it indicates we don't want to - // touch the current line at all. Also, on some OSes the line break consists of two characters (\r\n), we should test if the - // previous character before the end of format span is line break character as well. - if (isLineBreak(sourceFile.text.charCodeAt(endOfFormatSpan))) { - endOfFormatSpan--; - } - const span = { - // get start position for the previous line - pos: getStartPositionOfLine(line - 1, sourceFile), - // end value is exclusive so add 1 to the result - end: endOfFormatSpan + 1 - }; - return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatOnEnter); - } - - export function formatOnSemicolon(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { - const semicolon = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.SemicolonToken, sourceFile); - return formatNodeLines(findOutermostNodeWithinListLevel(semicolon), sourceFile, formatContext, FormattingRequestKind.FormatOnSemicolon); - } - - export function formatOnOpeningCurly(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { - const openingCurly = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.OpenBraceToken, sourceFile); - if (!openingCurly) { - return []; - } - const curlyBraceRange = openingCurly.parent; - const outermostNode = findOutermostNodeWithinListLevel(curlyBraceRange); - - /** - * We limit the span to end at the opening curly to handle the case where - * the brace matched to that just typed will be incorrect after further edits. - * For example, we could type the opening curly for the following method - * body without brace-matching activated: - * ``` - * class C { - * foo() - * } - * ``` - * and we wouldn't want to move the closing brace. - */ - const textRange: TextRange = { - pos: getLineStartPositionForPosition(outermostNode!.getStart(sourceFile), sourceFile), // TODO: GH#18217 - end: position - }; - - return formatSpan(textRange, sourceFile, formatContext, FormattingRequestKind.FormatOnOpeningCurlyBrace); - } + getIndentation(): number; + /** + * Prefered relative indentation for child nodes. + * Delta is used to carry the indentation info + * foo(bar({ + * $ + * })) + * Both 'foo', 'bar' introduce new indentation with delta = 4, but total indentation in $ is not 8. + * foo: { indentation: 0, delta: 4 } + * bar: { indentation: foo.indentation + foo.delta = 4, delta: 4} however 'foo' and 'bar' are on the same line + * so bar inherits indentation from foo and bar.delta will be 4 + * + */ + getDelta(child: TextRangeWithKind): number; + /** + * Formatter calls this function when rule adds or deletes new lines from the text + * so indentation scope can adjust values of indentation and delta. + */ + recomputeIndentation(lineAddedByFormatting: boolean, parent: Node): void; +} - export function formatOnClosingCurly(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { - const precedingToken = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.CloseBraceToken, sourceFile); - return formatNodeLines(findOutermostNodeWithinListLevel(precedingToken), sourceFile, formatContext, FormattingRequestKind.FormatOnClosingCurlyBrace); +export function formatOnEnter(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { + const line = sourceFile.getLineAndCharacterOfPosition(position).line; + if (line === 0) { + return []; } - - export function formatDocument(sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { - const span = { - pos: 0, - end: sourceFile.text.length - }; - return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatDocument); + // After the enter key, the cursor is now at a new line. The new line may or may not contain non-whitespace characters. + // If the new line has only whitespaces, we won't want to format this line, because that would remove the indentation as + // trailing whitespaces. So the end of the formatting span should be the later one between: + // 1. the end of the previous line + // 2. the last non-whitespace character in the current line + let endOfFormatSpan = getEndLinePosition(line, sourceFile); + while (isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(endOfFormatSpan))) { + endOfFormatSpan--; } - - export function formatSelection(start: number, end: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { - // format from the beginning of the line - const span = { - pos: getLineStartPositionForPosition(start, sourceFile), - end, - }; - return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatSelection); + // if the character at the end of the span is a line break, we shouldn't include it, because it indicates we don't want to + // touch the current line at all. Also, on some OSes the line break consists of two characters (\r\n), we should test if the + // previous character before the end of format span is line break character as well. + if (isLineBreak(sourceFile.text.charCodeAt(endOfFormatSpan))) { + endOfFormatSpan--; } + const span = { + // get start position for the previous line + pos: getStartPositionOfLine(line - 1, sourceFile), + // end value is exclusive so add 1 to the result + end: endOfFormatSpan + 1 + }; + return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatOnEnter); +} - /** - * Validating `expectedTokenKind` ensures the token was typed in the context we expect (eg: not a comment). - * @param expectedTokenKind The kind of the last token constituting the desired parent node. - */ - function findImmediatelyPrecedingTokenOfKind(end: number, expectedTokenKind: SyntaxKind, sourceFile: SourceFile): Node | undefined { - const precedingToken = findPrecedingToken(end, sourceFile); +export function formatOnSemicolon(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { + const semicolon = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.SemicolonToken, sourceFile); + return formatNodeLines(findOutermostNodeWithinListLevel(semicolon), sourceFile, formatContext, FormattingRequestKind.FormatOnSemicolon); +} - return precedingToken && precedingToken.kind === expectedTokenKind && end === precedingToken.getEnd() ? - precedingToken : - undefined; +export function formatOnOpeningCurly(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { + const openingCurly = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.OpenBraceToken, sourceFile); + if (!openingCurly) { + return []; } + const curlyBraceRange = openingCurly.parent; + const outermostNode = findOutermostNodeWithinListLevel(curlyBraceRange); /** - * Finds the highest node enclosing `node` at the same list level as `node` - * and whose end does not exceed `node.end`. - * - * Consider typing the following + * We limit the span to end at the opening curly to handle the case where + * the brace matched to that just typed will be incorrect after further edits. + * For example, we could type the opening curly for the following method + * body without brace-matching activated: * ``` - * let x = 1; - * while (true) { + * class C { + * foo() * } * ``` - * Upon typing the closing curly, we want to format the entire `while`-statement, but not the preceding - * variable declaration. + * and we wouldn't want to move the closing brace. */ - function findOutermostNodeWithinListLevel(node: Node | undefined) { - let current = node; - while (current && - current.parent && - current.parent.end === node!.end && - !isListElement(current.parent, current)) { - current = current.parent; - } - - return current; - } - - // Returns true if node is a element in some list in parent - // i.e. parent is class declaration with the list of members and node is one of members. - function isListElement(parent: Node, node: Node): boolean { - switch (parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - return rangeContainsRange((parent as InterfaceDeclaration).members, node); - case SyntaxKind.ModuleDeclaration: - const body = (parent as ModuleDeclaration).body; - return !!body && body.kind === SyntaxKind.ModuleBlock && rangeContainsRange(body.statements, node); - case SyntaxKind.SourceFile: - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - return rangeContainsRange((parent as Block).statements, node); - case SyntaxKind.CatchClause: - return rangeContainsRange((parent as CatchClause).block.statements, node); - } + const textRange: TextRange = { + pos: getLineStartPositionForPosition(outermostNode!.getStart(sourceFile), sourceFile), // TODO: GH#18217 + end: position + }; - return false; - } + return formatSpan(textRange, sourceFile, formatContext, FormattingRequestKind.FormatOnOpeningCurlyBrace); +} - /** find node that fully contains given text range */ - function findEnclosingNode(range: TextRange, sourceFile: SourceFile): Node { - return find(sourceFile); +export function formatOnClosingCurly(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { + const precedingToken = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.CloseBraceToken, sourceFile); + return formatNodeLines(findOutermostNodeWithinListLevel(precedingToken), sourceFile, formatContext, FormattingRequestKind.FormatOnClosingCurlyBrace); +} - function find(n: Node): Node { - const candidate = forEachChild(n, c => startEndContainsRange(c.getStart(sourceFile), c.end, range) && c); - if (candidate) { - const result = find(candidate); - if (result) { - return result; - } - } +export function formatDocument(sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { + const span = { + pos: 0, + end: sourceFile.text.length + }; + return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatDocument); +} - return n; - } - } +export function formatSelection(start: number, end: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { + // format from the beginning of the line + const span = { + pos: getLineStartPositionForPosition(start, sourceFile), + end, + }; + return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatSelection); +} - /** formatting is not applied to ranges that contain parse errors. - * This function will return a predicate that for a given text range will tell - * if there are any parse errors that overlap with the range. - */ - function prepareRangeContainsErrorFunction(errors: readonly Diagnostic[], originalRange: TextRange): (r: TextRange) => boolean { - if (!errors.length) { - return rangeHasNoErrors; - } +/** + * Validating `expectedTokenKind` ensures the token was typed in the context we expect (eg: not a comment). + * @param expectedTokenKind The kind of the last token constituting the desired parent node. + */ +function findImmediatelyPrecedingTokenOfKind(end: number, expectedTokenKind: SyntaxKind, sourceFile: SourceFile): Node | undefined { + const precedingToken = findPrecedingToken(end, sourceFile); - // pick only errors that fall in range - const sorted = errors - .filter(d => rangeOverlapsWithStartEnd(originalRange, d.start!, d.start! + d.length!)) // TODO: GH#18217 - .sort((e1, e2) => e1.start! - e2.start!); + return precedingToken && precedingToken.kind === expectedTokenKind && end === precedingToken.getEnd() ? + precedingToken : + undefined; +} - if (!sorted.length) { - return rangeHasNoErrors; - } +/** + * Finds the highest node enclosing `node` at the same list level as `node` + * and whose end does not exceed `node.end`. + * + * Consider typing the following + * ``` + * let x = 1; + * while (true) { + * } + * ``` + * Upon typing the closing curly, we want to format the entire `while`-statement, but not the preceding + * variable declaration. + */ +function findOutermostNodeWithinListLevel(node: Node | undefined) { + let current = node; + while (current && + current.parent && + current.parent.end === node!.end && + !isListElement(current.parent, current)) { + current = current.parent; + } - let index = 0; + return current; +} - return r => { - // in current implementation sequence of arguments [r1, r2...] is monotonically increasing. - // 'index' tracks the index of the most recent error that was checked. - while (true) { - if (index >= sorted.length) { - // all errors in the range were already checked -> no error in specified range - return false; - } +// Returns true if node is a element in some list in parent +// i.e. parent is class declaration with the list of members and node is one of members. +function isListElement(parent: Node, node: Node): boolean { + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + return rangeContainsRange((parent as InterfaceDeclaration).members, node); + case SyntaxKind.ModuleDeclaration: + const body = (parent as ModuleDeclaration).body; + return !!body && body.kind === SyntaxKind.ModuleBlock && rangeContainsRange(body.statements, node); + case SyntaxKind.SourceFile: + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + return rangeContainsRange((parent as Block).statements, node); + case SyntaxKind.CatchClause: + return rangeContainsRange((parent as CatchClause).block.statements, node); + } - const error = sorted[index]; - if (r.end <= error.start!) { - // specified range ends before the error referred by 'index' - no error in range - return false; - } + return false; +} - if (startEndOverlapsWithStartEnd(r.pos, r.end, error.start!, error.start! + error.length!)) { - // specified range overlaps with error range - return true; - } +/** find node that fully contains given text range */ +function findEnclosingNode(range: TextRange, sourceFile: SourceFile): Node { + return find(sourceFile); - index++; + function find(n: Node): Node { + const candidate = forEachChild(n, c => startEndContainsRange(c.getStart(sourceFile), c.end, range) && c); + if (candidate) { + const result = find(candidate); + if (result) { + return result; } - }; - - function rangeHasNoErrors(): boolean { - return false; } - } - /** - * Start of the original range might fall inside the comment - scanner will not yield appropriate results - * This function will look for token that is located before the start of target range - * and return its end as start position for the scanner. - */ - function getScanStartPosition(enclosingNode: Node, originalRange: TextRange, sourceFile: SourceFile): number { - const start = enclosingNode.getStart(sourceFile); - if (start === originalRange.pos && enclosingNode.end === originalRange.end) { - return start; - } + return n; + } +} - const precedingToken = findPrecedingToken(originalRange.pos, sourceFile); - if (!precedingToken) { - // no preceding token found - start from the beginning of enclosing node - return enclosingNode.pos; - } +/** formatting is not applied to ranges that contain parse errors. + * This function will return a predicate that for a given text range will tell + * if there are any parse errors that overlap with the range. + */ +function prepareRangeContainsErrorFunction(errors: readonly Diagnostic[], originalRange: TextRange): (r: TextRange) => boolean { + if (!errors.length) { + return rangeHasNoErrors; + } - // preceding token ends after the start of original range (i.e when originalRange.pos falls in the middle of literal) - // start from the beginning of enclosingNode to handle the entire 'originalRange' - if (precedingToken.end >= originalRange.pos) { - return enclosingNode.pos; - } + // pick only errors that fall in range + const sorted = errors + .filter(d => rangeOverlapsWithStartEnd(originalRange, d.start!, d.start! + d.length!)) // TODO: GH#18217 + .sort((e1, e2) => e1.start! - e2.start!); - return precedingToken.end; + if (!sorted.length) { + return rangeHasNoErrors; } - /* - * For cases like - * if (a || - * b ||$ - * c) {...} - * If we hit Enter at $ we want line ' b ||' to be indented. - * Formatting will be applied to the last two lines. - * Node that fully encloses these lines is binary expression 'a ||...'. - * Initial indentation for this node will be 0. - * Binary expressions don't introduce new indentation scopes, however it is possible - * that some parent node on the same line does - like if statement in this case. - * Note that we are considering parents only from the same line with initial node - - * if parent is on the different line - its delta was already contributed - * to the initial indentation. - */ - function getOwnOrInheritedDelta(n: Node, options: FormatCodeSettings, sourceFile: SourceFile): number { - let previousLine = Constants.Unknown; - let child: Node | undefined; - while (n) { - const line = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)).line; - if (previousLine !== Constants.Unknown && line !== previousLine) { - break; + let index = 0; + + return r => { + // in current implementation sequence of arguments [r1, r2...] is monotonically increasing. + // 'index' tracks the index of the most recent error that was checked. + while (true) { + if (index >= sorted.length) { + // all errors in the range were already checked -> no error in specified range + return false; + } + + const error = sorted[index]; + if (r.end <= error.start!) { + // specified range ends before the error referred by 'index' - no error in range + return false; } - if (SmartIndenter.shouldIndentChildNode(options, n, child, sourceFile)) { - return options.indentSize!; + if (startEndOverlapsWithStartEnd(r.pos, r.end, error.start!, error.start! + error.length!)) { + // specified range overlaps with error range + return true; } - previousLine = line; - child = n; - n = n.parent; + index++; } - return 0; + }; + + function rangeHasNoErrors(): boolean { + return false; } +} - export function formatNodeGivenIndentation(node: Node, sourceFileLike: SourceFileLike, languageVariant: LanguageVariant, initialIndentation: number, delta: number, formatContext: FormatContext): TextChange[] { - const range = { pos: 0, end: sourceFileLike.text.length }; - return getFormattingScanner(sourceFileLike.text, languageVariant, range.pos, range.end, scanner => formatSpanWorker( - range, - node, - initialIndentation, - delta, - scanner, - formatContext, - FormattingRequestKind.FormatSelection, - _ => false, // assume that node does not have any errors - sourceFileLike)); +/** + * Start of the original range might fall inside the comment - scanner will not yield appropriate results + * This function will look for token that is located before the start of target range + * and return its end as start position for the scanner. + */ +function getScanStartPosition(enclosingNode: Node, originalRange: TextRange, sourceFile: SourceFile): number { + const start = enclosingNode.getStart(sourceFile); + if (start === originalRange.pos && enclosingNode.end === originalRange.end) { + return start; + } + + const precedingToken = findPrecedingToken(originalRange.pos, sourceFile); + if (!precedingToken) { + // no preceding token found - start from the beginning of enclosing node + return enclosingNode.pos; } - function formatNodeLines(node: Node | undefined, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] { - if (!node) { - return []; + // preceding token ends after the start of original range (i.e when originalRange.pos falls in the middle of literal) + // start from the beginning of enclosingNode to handle the entire 'originalRange' + if (precedingToken.end >= originalRange.pos) { + return enclosingNode.pos; + } + + return precedingToken.end; +} + +/* + * For cases like + * if (a || + * b ||$ + * c) {...} + * If we hit Enter at $ we want line ' b ||' to be indented. + * Formatting will be applied to the last two lines. + * Node that fully encloses these lines is binary expression 'a ||...'. + * Initial indentation for this node will be 0. + * Binary expressions don't introduce new indentation scopes, however it is possible + * that some parent node on the same line does - like if statement in this case. + * Note that we are considering parents only from the same line with initial node - + * if parent is on the different line - its delta was already contributed + * to the initial indentation. + */ +function getOwnOrInheritedDelta(n: Node, options: FormatCodeSettings, sourceFile: SourceFile): number { + let previousLine = Constants.Unknown; + let child: Node | undefined; + while (n) { + const line = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)).line; + if (previousLine !== Constants.Unknown && line !== previousLine) { + break; } - const span = { - pos: getLineStartPositionForPosition(node.getStart(sourceFile), sourceFile), - end: node.end - }; + if (SmartIndenter.shouldIndentChildNode(options, n, child, sourceFile)) { + return options.indentSize!; + } - return formatSpan(span, sourceFile, formatContext, requestKind); + previousLine = line; + child = n; + n = n.parent; } + return 0; +} + +export function formatNodeGivenIndentation(node: Node, sourceFileLike: SourceFileLike, languageVariant: LanguageVariant, initialIndentation: number, delta: number, formatContext: FormatContext): TextChange[] { + const range = { pos: 0, end: sourceFileLike.text.length }; + return getFormattingScanner(sourceFileLike.text, languageVariant, range.pos, range.end, scanner => formatSpanWorker( + range, + node, + initialIndentation, + delta, + scanner, + formatContext, + FormattingRequestKind.FormatSelection, + _ => false, // assume that node does not have any errors + sourceFileLike)); +} - function formatSpan(originalRange: TextRange, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] { - // find the smallest node that fully wraps the range and compute the initial indentation for the node - const enclosingNode = findEnclosingNode(originalRange, sourceFile); - return getFormattingScanner( - sourceFile.text, - sourceFile.languageVariant, - getScanStartPosition(enclosingNode, originalRange, sourceFile), - originalRange.end, - scanner => formatSpanWorker( - originalRange, - enclosingNode, - SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, formatContext.options), - getOwnOrInheritedDelta(enclosingNode, formatContext.options, sourceFile), - scanner, - formatContext, - requestKind, - prepareRangeContainsErrorFunction(sourceFile.parseDiagnostics, originalRange), - sourceFile)); +function formatNodeLines(node: Node | undefined, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] { + if (!node) { + return []; } - function formatSpanWorker( - originalRange: TextRange, - enclosingNode: Node, - initialIndentation: number, - delta: number, - formattingScanner: FormattingScanner, - { options, getRules, host }: FormatContext, - requestKind: FormattingRequestKind, - rangeContainsError: (r: TextRange) => boolean, - sourceFile: SourceFileLike): TextChange[] { - - // formatting context is used by rules provider - const formattingContext = new FormattingContext(sourceFile, requestKind, options); - let previousRange: TextRangeWithKind; - let previousParent: Node; - let previousRangeStartLine: number; - - let lastIndentedLine: number; - let indentationOnLastIndentedLine = Constants.Unknown; - - const edits: TextChange[] = []; - - formattingScanner.advance(); - - if (formattingScanner.isOnToken()) { - const startLine = sourceFile.getLineAndCharacterOfPosition(enclosingNode.getStart(sourceFile)).line; - let undecoratedStartLine = startLine; - if (enclosingNode.decorators) { - undecoratedStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(enclosingNode, sourceFile)).line; - } + const span = { + pos: getLineStartPositionForPosition(node.getStart(sourceFile), sourceFile), + end: node.end + }; - processNode(enclosingNode, enclosingNode, startLine, undecoratedStartLine, initialIndentation, delta); + return formatSpan(span, sourceFile, formatContext, requestKind); +} + +function formatSpan(originalRange: TextRange, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] { + // find the smallest node that fully wraps the range and compute the initial indentation for the node + const enclosingNode = findEnclosingNode(originalRange, sourceFile); + return getFormattingScanner( + sourceFile.text, + sourceFile.languageVariant, + getScanStartPosition(enclosingNode, originalRange, sourceFile), + originalRange.end, + scanner => formatSpanWorker( + originalRange, + enclosingNode, + SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, formatContext.options), + getOwnOrInheritedDelta(enclosingNode, formatContext.options, sourceFile), + scanner, + formatContext, + requestKind, + prepareRangeContainsErrorFunction(sourceFile.parseDiagnostics, originalRange), + sourceFile)); +} + +function formatSpanWorker( + originalRange: TextRange, + enclosingNode: Node, + initialIndentation: number, + delta: number, + formattingScanner: FormattingScanner, + { options, getRules, host }: FormatContext, + requestKind: FormattingRequestKind, + rangeContainsError: (r: TextRange) => boolean, + sourceFile: SourceFileLike): TextChange[] { + + // formatting context is used by rules provider + const formattingContext = new FormattingContext(sourceFile, requestKind, options); + let previousRange: TextRangeWithKind; + let previousParent: Node; + let previousRangeStartLine: number; + + let lastIndentedLine: number; + let indentationOnLastIndentedLine = Constants.Unknown; + + const edits: TextChange[] = []; + + formattingScanner.advance(); + + if (formattingScanner.isOnToken()) { + const startLine = sourceFile.getLineAndCharacterOfPosition(enclosingNode.getStart(sourceFile)).line; + let undecoratedStartLine = startLine; + if (enclosingNode.decorators) { + undecoratedStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(enclosingNode, sourceFile)).line; } - if (!formattingScanner.isOnToken()) { - const indentation = SmartIndenter.nodeWillIndentChild(options, enclosingNode, /*child*/ undefined, sourceFile, /*indentByDefault*/ false) - ? initialIndentation + options.indentSize! - : initialIndentation; - const leadingTrivia = formattingScanner.getCurrentLeadingTrivia(); - if (leadingTrivia) { - indentTriviaItems(leadingTrivia, indentation, /*indentNextTokenOrTrivia*/ false, - item => processRange(item, sourceFile.getLineAndCharacterOfPosition(item.pos), enclosingNode, enclosingNode, /*dynamicIndentation*/ undefined!)); - if (options.trimTrailingWhitespace !== false) { - trimTrailingWhitespacesForRemainingRange(leadingTrivia); - } + processNode(enclosingNode, enclosingNode, startLine, undecoratedStartLine, initialIndentation, delta); + } + + if (!formattingScanner.isOnToken()) { + const indentation = SmartIndenter.nodeWillIndentChild(options, enclosingNode, /*child*/ undefined, sourceFile, /*indentByDefault*/ false) + ? initialIndentation + options.indentSize! + : initialIndentation; + const leadingTrivia = formattingScanner.getCurrentLeadingTrivia(); + if (leadingTrivia) { + indentTriviaItems(leadingTrivia, indentation, /*indentNextTokenOrTrivia*/ false, + item => processRange(item, sourceFile.getLineAndCharacterOfPosition(item.pos), enclosingNode, enclosingNode, /*dynamicIndentation*/ undefined!)); + if (options.trimTrailingWhitespace !== false) { + trimTrailingWhitespacesForRemainingRange(leadingTrivia); } } + } - return edits; + return edits; - // local functions + // local functions - /** Tries to compute the indentation for a list element. - * If list element is not in range then - * function will pick its actual indentation - * so it can be pushed downstream as inherited indentation. - * If list element is in the range - its indentation will be equal - * to inherited indentation from its predecessors. - */ - function tryComputeIndentationForListItem(startPos: number, - endPos: number, - parentStartLine: number, - range: TextRange, - inheritedIndentation: number): number { + /** Tries to compute the indentation for a list element. + * If list element is not in range then + * function will pick its actual indentation + * so it can be pushed downstream as inherited indentation. + * If list element is in the range - its indentation will be equal + * to inherited indentation from its predecessors. + */ + function tryComputeIndentationForListItem(startPos: number, + endPos: number, + parentStartLine: number, + range: TextRange, + inheritedIndentation: number): number { - if (rangeOverlapsWithStartEnd(range, startPos, endPos) || - rangeContainsStartEnd(range, startPos, endPos) /* Not to miss zero-range nodes e.g. JsxText */) { + if (rangeOverlapsWithStartEnd(range, startPos, endPos) || + rangeContainsStartEnd(range, startPos, endPos) /* Not to miss zero-range nodes e.g. JsxText */) { - if (inheritedIndentation !== Constants.Unknown) { - return inheritedIndentation; - } + if (inheritedIndentation !== Constants.Unknown) { + return inheritedIndentation; } - else { - const startLine = sourceFile.getLineAndCharacterOfPosition(startPos).line; - const startLinePosition = getLineStartPositionForPosition(startPos, sourceFile); - const column = SmartIndenter.findFirstNonWhitespaceColumn(startLinePosition, startPos, sourceFile, options); - if (startLine !== parentStartLine || startPos === column) { - // Use the base indent size if it is greater than - // the indentation of the inherited predecessor. - const baseIndentSize = SmartIndenter.getBaseIndentation(options); - return baseIndentSize > column ? baseIndentSize : column; - } + } + else { + const startLine = sourceFile.getLineAndCharacterOfPosition(startPos).line; + const startLinePosition = getLineStartPositionForPosition(startPos, sourceFile); + const column = SmartIndenter.findFirstNonWhitespaceColumn(startLinePosition, startPos, sourceFile, options); + if (startLine !== parentStartLine || startPos === column) { + // Use the base indent size if it is greater than + // the indentation of the inherited predecessor. + const baseIndentSize = SmartIndenter.getBaseIndentation(options); + return baseIndentSize > column ? baseIndentSize : column; } - - return Constants.Unknown; } - function computeIndentation( - node: TextRangeWithKind, - startLine: number, - inheritedIndentation: number, - parent: Node, - parentDynamicIndentation: DynamicIndentation, - effectiveParentStartLine: number - ): { indentation: number, delta: number; } { - const delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0; - - if (effectiveParentStartLine === startLine) { - // if node is located on the same line with the parent - // - inherit indentation from the parent - // - push children if either parent of node itself has non-zero delta - return { - indentation: startLine === lastIndentedLine ? indentationOnLastIndentedLine : parentDynamicIndentation.getIndentation(), - delta: Math.min(options.indentSize!, parentDynamicIndentation.getDelta(node) + delta) - }; + return Constants.Unknown; + } + + function computeIndentation( + node: TextRangeWithKind, + startLine: number, + inheritedIndentation: number, + parent: Node, + parentDynamicIndentation: DynamicIndentation, + effectiveParentStartLine: number + ): { indentation: number, delta: number; } { + const delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0; + + if (effectiveParentStartLine === startLine) { + // if node is located on the same line with the parent + // - inherit indentation from the parent + // - push children if either parent of node itself has non-zero delta + return { + indentation: startLine === lastIndentedLine ? indentationOnLastIndentedLine : parentDynamicIndentation.getIndentation(), + delta: Math.min(options.indentSize!, parentDynamicIndentation.getDelta(node) + delta) + }; + } + else if (inheritedIndentation === Constants.Unknown) { + if (node.kind === SyntaxKind.OpenParenToken && startLine === lastIndentedLine) { + // the is used for chaining methods formatting + // - we need to get the indentation on last line and the delta of parent + return { indentation: indentationOnLastIndentedLine, delta: parentDynamicIndentation.getDelta(node) }; } - else if (inheritedIndentation === Constants.Unknown) { - if (node.kind === SyntaxKind.OpenParenToken && startLine === lastIndentedLine) { - // the is used for chaining methods formatting - // - we need to get the indentation on last line and the delta of parent - return { indentation: indentationOnLastIndentedLine, delta: parentDynamicIndentation.getDelta(node) }; - } - else if ( - SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(parent, node, startLine, sourceFile) || - SmartIndenter.childIsUnindentedBranchOfConditionalExpression(parent, node, startLine, sourceFile) || - SmartIndenter.argumentStartsOnSameLineAsPreviousArgument(parent, node, startLine, sourceFile) - ) { - return { indentation: parentDynamicIndentation.getIndentation(), delta }; - } - else { - return { indentation: parentDynamicIndentation.getIndentation() + parentDynamicIndentation.getDelta(node), delta }; - } + else if ( + SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(parent, node, startLine, sourceFile) || + SmartIndenter.childIsUnindentedBranchOfConditionalExpression(parent, node, startLine, sourceFile) || + SmartIndenter.argumentStartsOnSameLineAsPreviousArgument(parent, node, startLine, sourceFile) + ) { + return { indentation: parentDynamicIndentation.getIndentation(), delta }; } else { - return { indentation: inheritedIndentation, delta }; + return { indentation: parentDynamicIndentation.getIndentation() + parentDynamicIndentation.getDelta(node), delta }; } } + else { + return { indentation: inheritedIndentation, delta }; + } + } - function getFirstNonDecoratorTokenOfNode(node: Node) { - if (node.modifiers && node.modifiers.length) { - return node.modifiers[0].kind; - } - switch (node.kind) { - case SyntaxKind.ClassDeclaration: return SyntaxKind.ClassKeyword; - case SyntaxKind.InterfaceDeclaration: return SyntaxKind.InterfaceKeyword; - case SyntaxKind.FunctionDeclaration: return SyntaxKind.FunctionKeyword; - case SyntaxKind.EnumDeclaration: return SyntaxKind.EnumDeclaration; - case SyntaxKind.GetAccessor: return SyntaxKind.GetKeyword; - case SyntaxKind.SetAccessor: return SyntaxKind.SetKeyword; - case SyntaxKind.MethodDeclaration: - if ((node as MethodDeclaration).asteriskToken) { - return SyntaxKind.AsteriskToken; - } - // falls through - - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.Parameter: - const name = getNameOfDeclaration(node as Declaration); - if (name) { - return name.kind; - } - } + function getFirstNonDecoratorTokenOfNode(node: Node) { + if (node.modifiers && node.modifiers.length) { + return node.modifiers[0].kind; } + switch (node.kind) { + case SyntaxKind.ClassDeclaration: return SyntaxKind.ClassKeyword; + case SyntaxKind.InterfaceDeclaration: return SyntaxKind.InterfaceKeyword; + case SyntaxKind.FunctionDeclaration: return SyntaxKind.FunctionKeyword; + case SyntaxKind.EnumDeclaration: return SyntaxKind.EnumDeclaration; + case SyntaxKind.GetAccessor: return SyntaxKind.GetKeyword; + case SyntaxKind.SetAccessor: return SyntaxKind.SetKeyword; + case SyntaxKind.MethodDeclaration: + if ((node as MethodDeclaration).asteriskToken) { + return SyntaxKind.AsteriskToken; + } + // falls through - function getDynamicIndentation(node: Node, nodeStartLine: number, indentation: number, delta: number): DynamicIndentation { - return { - getIndentationForComment: (kind, tokenIndentation, container) => { - switch (kind) { - // preceding comment to the token that closes the indentation scope inherits the indentation from the scope - // .. { - // // comment - // } - case SyntaxKind.CloseBraceToken: - case SyntaxKind.CloseBracketToken: - case SyntaxKind.CloseParenToken: - return indentation + getDelta(container); - } - return tokenIndentation !== Constants.Unknown ? tokenIndentation : indentation; - }, - // if list end token is LessThanToken '>' then its delta should be explicitly suppressed - // so that LessThanToken as a binary operator can still be indented. - // foo.then - // < - // number, - // string, - // >(); - // vs - // var a = xValue - // > yValue; - getIndentationForToken: (line, kind, container, suppressDelta) => - !suppressDelta && shouldAddDelta(line, kind, container) ? indentation + getDelta(container) : indentation, - getIndentation: () => indentation, - getDelta, - recomputeIndentation: (lineAdded, parent) => { - if (SmartIndenter.shouldIndentChildNode(options, parent, node, sourceFile)) { - indentation += lineAdded ? options.indentSize! : -options.indentSize!; - delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0; - } + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.Parameter: + const name = getNameOfDeclaration(node as Declaration); + if (name) { + return name.kind; } - }; + } + } - function shouldAddDelta(line: number, kind: SyntaxKind, container: Node): boolean { + function getDynamicIndentation(node: Node, nodeStartLine: number, indentation: number, delta: number): DynamicIndentation { + return { + getIndentationForComment: (kind, tokenIndentation, container) => { switch (kind) { - // open and close brace, 'else' and 'while' (in do statement) tokens has indentation of the parent - case SyntaxKind.OpenBraceToken: + // preceding comment to the token that closes the indentation scope inherits the indentation from the scope + // .. { + // // comment + // } case SyntaxKind.CloseBraceToken: - case SyntaxKind.CloseParenToken: - case SyntaxKind.ElseKeyword: - case SyntaxKind.WhileKeyword: - case SyntaxKind.AtToken: - return false; - case SyntaxKind.SlashToken: - case SyntaxKind.GreaterThanToken: - switch (container.kind) { - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxClosingElement: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.ExpressionWithTypeArguments: - return false; - } - break; - case SyntaxKind.OpenBracketToken: case SyntaxKind.CloseBracketToken: - if (container.kind !== SyntaxKind.MappedType) { - return false; - } - break; + case SyntaxKind.CloseParenToken: + return indentation + getDelta(container); + } + return tokenIndentation !== Constants.Unknown ? tokenIndentation : indentation; + }, + // if list end token is LessThanToken '>' then its delta should be explicitly suppressed + // so that LessThanToken as a binary operator can still be indented. + // foo.then + // < + // number, + // string, + // >(); + // vs + // var a = xValue + // > yValue; + getIndentationForToken: (line, kind, container, suppressDelta) => + !suppressDelta && shouldAddDelta(line, kind, container) ? indentation + getDelta(container) : indentation, + getIndentation: () => indentation, + getDelta, + recomputeIndentation: (lineAdded, parent) => { + if (SmartIndenter.shouldIndentChildNode(options, parent, node, sourceFile)) { + indentation += lineAdded ? options.indentSize! : -options.indentSize!; + delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0; } - // if token line equals to the line of containing node (this is a first token in the node) - use node indentation - return nodeStartLine !== line - // if this token is the first token following the list of decorators, we do not need to indent - && !(node.decorators && kind === getFirstNonDecoratorTokenOfNode(node)); } + }; - function getDelta(child: TextRangeWithKind) { - // Delta value should be zero when the node explicitly prevents indentation of the child node - return SmartIndenter.nodeWillIndentChild(options, node, child, sourceFile, /*indentByDefault*/ true) ? delta : 0; + function shouldAddDelta(line: number, kind: SyntaxKind, container: Node): boolean { + switch (kind) { + // open and close brace, 'else' and 'while' (in do statement) tokens has indentation of the parent + case SyntaxKind.OpenBraceToken: + case SyntaxKind.CloseBraceToken: + case SyntaxKind.CloseParenToken: + case SyntaxKind.ElseKeyword: + case SyntaxKind.WhileKeyword: + case SyntaxKind.AtToken: + return false; + case SyntaxKind.SlashToken: + case SyntaxKind.GreaterThanToken: + switch (container.kind) { + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxClosingElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.ExpressionWithTypeArguments: + return false; + } + break; + case SyntaxKind.OpenBracketToken: + case SyntaxKind.CloseBracketToken: + if (container.kind !== SyntaxKind.MappedType) { + return false; + } + break; } + // if token line equals to the line of containing node (this is a first token in the node) - use node indentation + return nodeStartLine !== line + // if this token is the first token following the list of decorators, we do not need to indent + && !(node.decorators && kind === getFirstNonDecoratorTokenOfNode(node)); } - function processNode(node: Node, contextNode: Node, nodeStartLine: number, undecoratedNodeStartLine: number, indentation: number, delta: number) { - if (!rangeOverlapsWithStartEnd(originalRange, node.getStart(sourceFile), node.getEnd())) { - return; - } + function getDelta(child: TextRangeWithKind) { + // Delta value should be zero when the node explicitly prevents indentation of the child node + return SmartIndenter.nodeWillIndentChild(options, node, child, sourceFile, /*indentByDefault*/ true) ? delta : 0; + } + } - const nodeDynamicIndentation = getDynamicIndentation(node, nodeStartLine, indentation, delta); - - // a useful observations when tracking context node - // / - // [a] - // / | \ - // [b] [c] [d] - // node 'a' is a context node for nodes 'b', 'c', 'd' - // except for the leftmost leaf token in [b] - in this case context node ('e') is located somewhere above 'a' - // this rule can be applied recursively to child nodes of 'a'. - // - // context node is set to parent node value after processing every child node - // context node is set to parent of the token after processing every token - - let childContextNode = contextNode; - - // if there are any tokens that logically belong to node and interleave child nodes - // such tokens will be consumed in processChildNode for the child that follows them - forEachChild( - node, - child => { - processChildNode(child, /*inheritedIndentation*/ Constants.Unknown, node, nodeDynamicIndentation, nodeStartLine, undecoratedNodeStartLine, /*isListItem*/ false); - }, - nodes => { - processChildNodes(nodes, node, nodeStartLine, nodeDynamicIndentation); - }); - - // proceed any tokens in the node that are located after child nodes - while (formattingScanner.isOnToken()) { - const tokenInfo = formattingScanner.readTokenInfo(node); - if (tokenInfo.token.end > node.end) { - break; - } - consumeTokenAndAdvanceScanner(tokenInfo, node, nodeDynamicIndentation, node); + function processNode(node: Node, contextNode: Node, nodeStartLine: number, undecoratedNodeStartLine: number, indentation: number, delta: number) { + if (!rangeOverlapsWithStartEnd(originalRange, node.getStart(sourceFile), node.getEnd())) { + return; + } + + const nodeDynamicIndentation = getDynamicIndentation(node, nodeStartLine, indentation, delta); + + // a useful observations when tracking context node + // / + // [a] + // / | \ + // [b] [c] [d] + // node 'a' is a context node for nodes 'b', 'c', 'd' + // except for the leftmost leaf token in [b] - in this case context node ('e') is located somewhere above 'a' + // this rule can be applied recursively to child nodes of 'a'. + // + // context node is set to parent node value after processing every child node + // context node is set to parent of the token after processing every token + + let childContextNode = contextNode; + + // if there are any tokens that logically belong to node and interleave child nodes + // such tokens will be consumed in processChildNode for the child that follows them + forEachChild( + node, + child => { + processChildNode(child, /*inheritedIndentation*/ Constants.Unknown, node, nodeDynamicIndentation, nodeStartLine, undecoratedNodeStartLine, /*isListItem*/ false); + }, + nodes => { + processChildNodes(nodes, node, nodeStartLine, nodeDynamicIndentation); + }); + + // proceed any tokens in the node that are located after child nodes + while (formattingScanner.isOnToken()) { + const tokenInfo = formattingScanner.readTokenInfo(node); + if (tokenInfo.token.end > node.end) { + break; } + consumeTokenAndAdvanceScanner(tokenInfo, node, nodeDynamicIndentation, node); + } - if (!node.parent && formattingScanner.isOnEOF()) { - const token = formattingScanner.readEOFTokenRange(); - if (token.end <= node.end && previousRange) { - processPair( - token, - sourceFile.getLineAndCharacterOfPosition(token.pos).line, - node, - previousRange, - previousRangeStartLine, - previousParent, - contextNode, - nodeDynamicIndentation); - } + if (!node.parent && formattingScanner.isOnEOF()) { + const token = formattingScanner.readEOFTokenRange(); + if (token.end <= node.end && previousRange) { + processPair( + token, + sourceFile.getLineAndCharacterOfPosition(token.pos).line, + node, + previousRange, + previousRangeStartLine, + previousParent, + contextNode, + nodeDynamicIndentation); } + } - function processChildNode( - child: Node, - inheritedIndentation: number, - parent: Node, - parentDynamicIndentation: DynamicIndentation, - parentStartLine: number, - undecoratedParentStartLine: number, - isListItem: boolean, - isFirstListItem?: boolean): number { + function processChildNode( + child: Node, + inheritedIndentation: number, + parent: Node, + parentDynamicIndentation: DynamicIndentation, + parentStartLine: number, + undecoratedParentStartLine: number, + isListItem: boolean, + isFirstListItem?: boolean): number { - const childStartPos = child.getStart(sourceFile); + const childStartPos = child.getStart(sourceFile); - const childStartLine = sourceFile.getLineAndCharacterOfPosition(childStartPos).line; + const childStartLine = sourceFile.getLineAndCharacterOfPosition(childStartPos).line; - let undecoratedChildStartLine = childStartLine; - if (child.decorators) { - undecoratedChildStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(child, sourceFile)).line; - } + let undecoratedChildStartLine = childStartLine; + if (child.decorators) { + undecoratedChildStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(child, sourceFile)).line; + } - // if child is a list item - try to get its indentation, only if parent is within the original range. - let childIndentationAmount = Constants.Unknown; + // if child is a list item - try to get its indentation, only if parent is within the original range. + let childIndentationAmount = Constants.Unknown; - if (isListItem && rangeContainsRange(originalRange, parent)) { - childIndentationAmount = tryComputeIndentationForListItem(childStartPos, child.end, parentStartLine, originalRange, inheritedIndentation); - if (childIndentationAmount !== Constants.Unknown) { - inheritedIndentation = childIndentationAmount; - } + if (isListItem && rangeContainsRange(originalRange, parent)) { + childIndentationAmount = tryComputeIndentationForListItem(childStartPos, child.end, parentStartLine, originalRange, inheritedIndentation); + if (childIndentationAmount !== Constants.Unknown) { + inheritedIndentation = childIndentationAmount; } + } - // child node is outside the target range - do not dive inside - if (!rangeOverlapsWithStartEnd(originalRange, child.pos, child.end)) { - if (child.end < originalRange.pos) { - formattingScanner.skipToEndOf(child); - } - return inheritedIndentation; + // child node is outside the target range - do not dive inside + if (!rangeOverlapsWithStartEnd(originalRange, child.pos, child.end)) { + if (child.end < originalRange.pos) { + formattingScanner.skipToEndOf(child); } + return inheritedIndentation; + } - if (child.getFullWidth() === 0) { - return inheritedIndentation; - } + if (child.getFullWidth() === 0) { + return inheritedIndentation; + } - while (formattingScanner.isOnToken()) { - // proceed any parent tokens that are located prior to child.getStart() - const tokenInfo = formattingScanner.readTokenInfo(node); - if (tokenInfo.token.end > childStartPos) { - if (tokenInfo.token.pos > childStartPos) { - formattingScanner.skipToStartOf(child); - } - // stop when formatting scanner advances past the beginning of the child - break; + while (formattingScanner.isOnToken()) { + // proceed any parent tokens that are located prior to child.getStart() + const tokenInfo = formattingScanner.readTokenInfo(node); + if (tokenInfo.token.end > childStartPos) { + if (tokenInfo.token.pos > childStartPos) { + formattingScanner.skipToStartOf(child); } - - consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, node); + // stop when formatting scanner advances past the beginning of the child + break; } - if (!formattingScanner.isOnToken()) { - return inheritedIndentation; - } + consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, node); + } - if (isToken(child)) { - // if child node is a token, it does not impact indentation, proceed it using parent indentation scope rules - const tokenInfo = formattingScanner.readTokenInfo(child); - // JSX text shouldn't affect indenting - if (child.kind !== SyntaxKind.JsxText) { - Debug.assert(tokenInfo.token.end === child.end, "Token end is child end"); - consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, child); - return inheritedIndentation; - } - } + if (!formattingScanner.isOnToken()) { + return inheritedIndentation; + } - const effectiveParentStartLine = child.kind === SyntaxKind.Decorator ? childStartLine : undecoratedParentStartLine; - const childIndentation = computeIndentation(child, childStartLine, childIndentationAmount, node, parentDynamicIndentation, effectiveParentStartLine); + if (isToken(child)) { + // if child node is a token, it does not impact indentation, proceed it using parent indentation scope rules + const tokenInfo = formattingScanner.readTokenInfo(child); + // JSX text shouldn't affect indenting + if (child.kind !== SyntaxKind.JsxText) { + Debug.assert(tokenInfo.token.end === child.end, "Token end is child end"); + consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, child); + return inheritedIndentation; + } + } - processNode(child, childContextNode, childStartLine, undecoratedChildStartLine, childIndentation.indentation, childIndentation.delta); + const effectiveParentStartLine = child.kind === SyntaxKind.Decorator ? childStartLine : undecoratedParentStartLine; + const childIndentation = computeIndentation(child, childStartLine, childIndentationAmount, node, parentDynamicIndentation, effectiveParentStartLine); - childContextNode = node; + processNode(child, childContextNode, childStartLine, undecoratedChildStartLine, childIndentation.indentation, childIndentation.delta); - if (isFirstListItem && parent.kind === SyntaxKind.ArrayLiteralExpression && inheritedIndentation === Constants.Unknown) { - inheritedIndentation = childIndentation.indentation; - } + childContextNode = node; - return inheritedIndentation; + if (isFirstListItem && parent.kind === SyntaxKind.ArrayLiteralExpression && inheritedIndentation === Constants.Unknown) { + inheritedIndentation = childIndentation.indentation; } - function processChildNodes(nodes: NodeArray, - parent: Node, - parentStartLine: number, - parentDynamicIndentation: DynamicIndentation): void { - Debug.assert(isNodeArray(nodes)); + return inheritedIndentation; + } - const listStartToken = getOpenTokenForList(parent, nodes); + function processChildNodes(nodes: NodeArray, + parent: Node, + parentStartLine: number, + parentDynamicIndentation: DynamicIndentation): void { + Debug.assert(isNodeArray(nodes)); - let listDynamicIndentation = parentDynamicIndentation; - let startLine = parentStartLine; + const listStartToken = getOpenTokenForList(parent, nodes); - if (listStartToken !== SyntaxKind.Unknown) { - // introduce a new indentation scope for lists (including list start and end tokens) - while (formattingScanner.isOnToken()) { - const tokenInfo = formattingScanner.readTokenInfo(parent); - if (tokenInfo.token.end > nodes.pos) { - // stop when formatting scanner moves past the beginning of node list - break; - } - else if (tokenInfo.token.kind === listStartToken) { - // consume list start token - startLine = sourceFile.getLineAndCharacterOfPosition(tokenInfo.token.pos).line; - - consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent); - - let indentationOnListStartToken: number; - if (indentationOnLastIndentedLine !== Constants.Unknown) { - // scanner just processed list start token so consider last indentation as list indentation - // function foo(): { // last indentation was 0, list item will be indented based on this value - // foo: number; - // }: {}; - indentationOnListStartToken = indentationOnLastIndentedLine; - } - else { - const startLinePosition = getLineStartPositionForPosition(tokenInfo.token.pos, sourceFile); - indentationOnListStartToken = SmartIndenter.findFirstNonWhitespaceColumn(startLinePosition, tokenInfo.token.pos, sourceFile, options); - } - - listDynamicIndentation = getDynamicIndentation(parent, parentStartLine, indentationOnListStartToken, options.indentSize!); // TODO: GH#18217 + let listDynamicIndentation = parentDynamicIndentation; + let startLine = parentStartLine; + + if (listStartToken !== SyntaxKind.Unknown) { + // introduce a new indentation scope for lists (including list start and end tokens) + while (formattingScanner.isOnToken()) { + const tokenInfo = formattingScanner.readTokenInfo(parent); + if (tokenInfo.token.end > nodes.pos) { + // stop when formatting scanner moves past the beginning of node list + break; + } + else if (tokenInfo.token.kind === listStartToken) { + // consume list start token + startLine = sourceFile.getLineAndCharacterOfPosition(tokenInfo.token.pos).line; + + consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent); + + let indentationOnListStartToken: number; + if (indentationOnLastIndentedLine !== Constants.Unknown) { + // scanner just processed list start token so consider last indentation as list indentation + // function foo(): { // last indentation was 0, list item will be indented based on this value + // foo: number; + // }: {}; + indentationOnListStartToken = indentationOnLastIndentedLine; } else { - // consume any tokens that precede the list as child elements of 'node' using its indentation scope - consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent); + const startLinePosition = getLineStartPositionForPosition(tokenInfo.token.pos, sourceFile); + indentationOnListStartToken = SmartIndenter.findFirstNonWhitespaceColumn(startLinePosition, tokenInfo.token.pos, sourceFile, options); } - } - } - let inheritedIndentation = Constants.Unknown; - for (let i = 0; i < nodes.length; i++) { - const child = nodes[i]; - inheritedIndentation = processChildNode(child, inheritedIndentation, node, listDynamicIndentation, startLine, startLine, /*isListItem*/ true, /*isFirstListItem*/ i === 0); - } - - const listEndToken = getCloseTokenForOpenToken(listStartToken); - if (listEndToken !== SyntaxKind.Unknown && formattingScanner.isOnToken()) { - let tokenInfo: TokenInfo | undefined = formattingScanner.readTokenInfo(parent); - if (tokenInfo.token.kind === SyntaxKind.CommaToken && isCallLikeExpression(parent)) { - const commaTokenLine = sourceFile.getLineAndCharacterOfPosition(tokenInfo.token.pos).line; - if (startLine !== commaTokenLine) { - formattingScanner.advance(); - tokenInfo = formattingScanner.isOnToken() ? formattingScanner.readTokenInfo(parent) : undefined; - } + listDynamicIndentation = getDynamicIndentation(parent, parentStartLine, indentationOnListStartToken, options.indentSize!); // TODO: GH#18217 } - - // consume the list end token only if it is still belong to the parent - // there might be the case when current token matches end token but does not considered as one - // function (x: function) <-- - // without this check close paren will be interpreted as list end token for function expression which is wrong - if (tokenInfo && tokenInfo.token.kind === listEndToken && rangeContainsRange(parent, tokenInfo.token)) { - // consume list end token - consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent, /*isListEndToken*/ true); + else { + // consume any tokens that precede the list as child elements of 'node' using its indentation scope + consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent); } } } - function consumeTokenAndAdvanceScanner(currentTokenInfo: TokenInfo, parent: Node, dynamicIndentation: DynamicIndentation, container: Node, isListEndToken?: boolean): void { - Debug.assert(rangeContainsRange(parent, currentTokenInfo.token)); - - const lastTriviaWasNewLine = formattingScanner.lastTrailingTriviaWasNewLine(); - let indentToken = false; - - if (currentTokenInfo.leadingTrivia) { - processTrivia(currentTokenInfo.leadingTrivia, parent, childContextNode, dynamicIndentation); - } + let inheritedIndentation = Constants.Unknown; + for (let i = 0; i < nodes.length; i++) { + const child = nodes[i]; + inheritedIndentation = processChildNode(child, inheritedIndentation, node, listDynamicIndentation, startLine, startLine, /*isListItem*/ true, /*isFirstListItem*/ i === 0); + } - let lineAction = LineAction.None; - const isTokenInRange = rangeContainsRange(originalRange, currentTokenInfo.token); - - const tokenStart = sourceFile.getLineAndCharacterOfPosition(currentTokenInfo.token.pos); - if (isTokenInRange) { - const rangeHasError = rangeContainsError(currentTokenInfo.token); - // save previousRange since processRange will overwrite this value with current one - const savePreviousRange = previousRange; - lineAction = processRange(currentTokenInfo.token, tokenStart, parent, childContextNode, dynamicIndentation); - // do not indent comments\token if token range overlaps with some error - if (!rangeHasError) { - if (lineAction === LineAction.None) { - // indent token only if end line of previous range does not match start line of the token - const prevEndLine = savePreviousRange && sourceFile.getLineAndCharacterOfPosition(savePreviousRange.end).line; - indentToken = lastTriviaWasNewLine && tokenStart.line !== prevEndLine; - } - else { - indentToken = lineAction === LineAction.LineAdded; - } + const listEndToken = getCloseTokenForOpenToken(listStartToken); + if (listEndToken !== SyntaxKind.Unknown && formattingScanner.isOnToken()) { + let tokenInfo: TokenInfo | undefined = formattingScanner.readTokenInfo(parent); + if (tokenInfo.token.kind === SyntaxKind.CommaToken && isCallLikeExpression(parent)) { + const commaTokenLine = sourceFile.getLineAndCharacterOfPosition(tokenInfo.token.pos).line; + if (startLine !== commaTokenLine) { + formattingScanner.advance(); + tokenInfo = formattingScanner.isOnToken() ? formattingScanner.readTokenInfo(parent) : undefined; } } - if (currentTokenInfo.trailingTrivia) { - processTrivia(currentTokenInfo.trailingTrivia, parent, childContextNode, dynamicIndentation); + // consume the list end token only if it is still belong to the parent + // there might be the case when current token matches end token but does not considered as one + // function (x: function) <-- + // without this check close paren will be interpreted as list end token for function expression which is wrong + if (tokenInfo && tokenInfo.token.kind === listEndToken && rangeContainsRange(parent, tokenInfo.token)) { + // consume list end token + consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent, /*isListEndToken*/ true); } + } + } - if (indentToken) { - const tokenIndentation = (isTokenInRange && !rangeContainsError(currentTokenInfo.token)) ? - dynamicIndentation.getIndentationForToken(tokenStart.line, currentTokenInfo.token.kind, container, !!isListEndToken) : - Constants.Unknown; + function consumeTokenAndAdvanceScanner(currentTokenInfo: TokenInfo, parent: Node, dynamicIndentation: DynamicIndentation, container: Node, isListEndToken?: boolean): void { + Debug.assert(rangeContainsRange(parent, currentTokenInfo.token)); - let indentNextTokenOrTrivia = true; - if (currentTokenInfo.leadingTrivia) { - const commentIndentation = dynamicIndentation.getIndentationForComment(currentTokenInfo.token.kind, tokenIndentation, container); - indentNextTokenOrTrivia = indentTriviaItems(currentTokenInfo.leadingTrivia, commentIndentation, indentNextTokenOrTrivia, - item => insertIndentation(item.pos, commentIndentation, /*lineAdded*/ false)); - } + const lastTriviaWasNewLine = formattingScanner.lastTrailingTriviaWasNewLine(); + let indentToken = false; - // indent token only if is it is in target range and does not overlap with any error ranges - if (tokenIndentation !== Constants.Unknown && indentNextTokenOrTrivia) { - insertIndentation(currentTokenInfo.token.pos, tokenIndentation, lineAction === LineAction.LineAdded); + if (currentTokenInfo.leadingTrivia) { + processTrivia(currentTokenInfo.leadingTrivia, parent, childContextNode, dynamicIndentation); + } - lastIndentedLine = tokenStart.line; - indentationOnLastIndentedLine = tokenIndentation; + let lineAction = LineAction.None; + const isTokenInRange = rangeContainsRange(originalRange, currentTokenInfo.token); + + const tokenStart = sourceFile.getLineAndCharacterOfPosition(currentTokenInfo.token.pos); + if (isTokenInRange) { + const rangeHasError = rangeContainsError(currentTokenInfo.token); + // save previousRange since processRange will overwrite this value with current one + const savePreviousRange = previousRange; + lineAction = processRange(currentTokenInfo.token, tokenStart, parent, childContextNode, dynamicIndentation); + // do not indent comments\token if token range overlaps with some error + if (!rangeHasError) { + if (lineAction === LineAction.None) { + // indent token only if end line of previous range does not match start line of the token + const prevEndLine = savePreviousRange && sourceFile.getLineAndCharacterOfPosition(savePreviousRange.end).line; + indentToken = lastTriviaWasNewLine && tokenStart.line !== prevEndLine; + } + else { + indentToken = lineAction === LineAction.LineAdded; } } - - formattingScanner.advance(); - - childContextNode = parent; } - } - function indentTriviaItems( - trivia: TextRangeWithKind[], - commentIndentation: number, - indentNextTokenOrTrivia: boolean, - indentSingleLine: (item: TextRangeWithKind) => void) { - for (const triviaItem of trivia) { - const triviaInRange = rangeContainsRange(originalRange, triviaItem); - switch (triviaItem.kind) { - case SyntaxKind.MultiLineCommentTrivia: - if (triviaInRange) { - indentMultilineComment(triviaItem, commentIndentation, /*firstLineIsIndented*/ !indentNextTokenOrTrivia); - } - indentNextTokenOrTrivia = false; - break; - case SyntaxKind.SingleLineCommentTrivia: - if (indentNextTokenOrTrivia && triviaInRange) { - indentSingleLine(triviaItem); - } - indentNextTokenOrTrivia = false; - break; - case SyntaxKind.NewLineTrivia: - indentNextTokenOrTrivia = true; - break; - } + if (currentTokenInfo.trailingTrivia) { + processTrivia(currentTokenInfo.trailingTrivia, parent, childContextNode, dynamicIndentation); } - return indentNextTokenOrTrivia; - } - function processTrivia(trivia: TextRangeWithKind[], parent: Node, contextNode: Node, dynamicIndentation: DynamicIndentation): void { - for (const triviaItem of trivia) { - if (isComment(triviaItem.kind) && rangeContainsRange(originalRange, triviaItem)) { - const triviaItemStart = sourceFile.getLineAndCharacterOfPosition(triviaItem.pos); - processRange(triviaItem, triviaItemStart, parent, contextNode, dynamicIndentation); + if (indentToken) { + const tokenIndentation = (isTokenInRange && !rangeContainsError(currentTokenInfo.token)) ? + dynamicIndentation.getIndentationForToken(tokenStart.line, currentTokenInfo.token.kind, container, !!isListEndToken) : + Constants.Unknown; + + let indentNextTokenOrTrivia = true; + if (currentTokenInfo.leadingTrivia) { + const commentIndentation = dynamicIndentation.getIndentationForComment(currentTokenInfo.token.kind, tokenIndentation, container); + indentNextTokenOrTrivia = indentTriviaItems(currentTokenInfo.leadingTrivia, commentIndentation, indentNextTokenOrTrivia, + item => insertIndentation(item.pos, commentIndentation, /*lineAdded*/ false)); } - } - } - function processRange(range: TextRangeWithKind, - rangeStart: LineAndCharacter, - parent: Node, - contextNode: Node, - dynamicIndentation: DynamicIndentation): LineAction { + // indent token only if is it is in target range and does not overlap with any error ranges + if (tokenIndentation !== Constants.Unknown && indentNextTokenOrTrivia) { + insertIndentation(currentTokenInfo.token.pos, tokenIndentation, lineAction === LineAction.LineAdded); - const rangeHasError = rangeContainsError(range); - let lineAction = LineAction.None; - if (!rangeHasError) { - if (!previousRange) { - // trim whitespaces starting from the beginning of the span up to the current line - const originalStart = sourceFile.getLineAndCharacterOfPosition(originalRange.pos); - trimTrailingWhitespacesForLines(originalStart.line, rangeStart.line); - } - else { - lineAction = - processPair(range, rangeStart.line, parent, previousRange, previousRangeStartLine, previousParent, contextNode, dynamicIndentation); + lastIndentedLine = tokenStart.line; + indentationOnLastIndentedLine = tokenIndentation; } } - previousRange = range; - previousParent = parent; - previousRangeStartLine = rangeStart.line; + formattingScanner.advance(); - return lineAction; + childContextNode = parent; } + } - function processPair(currentItem: TextRangeWithKind, - currentStartLine: number, - currentParent: Node, - previousItem: TextRangeWithKind, - previousStartLine: number, - previousParent: Node, - contextNode: Node, - dynamicIndentation: DynamicIndentation): LineAction { - - formattingContext.updateContext(previousItem, previousParent, currentItem, currentParent, contextNode); - - const rules = getRules(formattingContext); - - let trimTrailingWhitespaces = formattingContext.options.trimTrailingWhitespace !== false; - let lineAction = LineAction.None; - if (rules) { - // Apply rules in reverse order so that higher priority rules (which are first in the array) - // win in a conflict with lower priority rules. - forEachRight(rules, rule => { - lineAction = applyRuleEdits(rule, previousItem, previousStartLine, currentItem, currentStartLine); - switch (lineAction) { - case LineAction.LineRemoved: - // Handle the case where the next line is moved to be the end of this line. - // In this case we don't indent the next line in the next pass. - if (currentParent.getStart(sourceFile) === currentItem.pos) { - dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ false, contextNode); - } - break; - case LineAction.LineAdded: - // Handle the case where token2 is moved to the new line. - // In this case we indent token2 in the next pass but we set - // sameLineIndent flag to notify the indenter that the indentation is within the line. - if (currentParent.getStart(sourceFile) === currentItem.pos) { - dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ true, contextNode); - } - break; - default: - Debug.assert(lineAction === LineAction.None); + function indentTriviaItems( + trivia: TextRangeWithKind[], + commentIndentation: number, + indentNextTokenOrTrivia: boolean, + indentSingleLine: (item: TextRangeWithKind) => void) { + for (const triviaItem of trivia) { + const triviaInRange = rangeContainsRange(originalRange, triviaItem); + switch (triviaItem.kind) { + case SyntaxKind.MultiLineCommentTrivia: + if (triviaInRange) { + indentMultilineComment(triviaItem, commentIndentation, /*firstLineIsIndented*/ !indentNextTokenOrTrivia); } - - // We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line - trimTrailingWhitespaces = trimTrailingWhitespaces && !(rule.action & RuleAction.DeleteSpace) && rule.flags !== RuleFlags.CanDeleteNewLines; - }); - } - else { - trimTrailingWhitespaces = trimTrailingWhitespaces && currentItem.kind !== SyntaxKind.EndOfFileToken; + indentNextTokenOrTrivia = false; + break; + case SyntaxKind.SingleLineCommentTrivia: + if (indentNextTokenOrTrivia && triviaInRange) { + indentSingleLine(triviaItem); + } + indentNextTokenOrTrivia = false; + break; + case SyntaxKind.NewLineTrivia: + indentNextTokenOrTrivia = true; + break; } + } + return indentNextTokenOrTrivia; + } - if (currentStartLine !== previousStartLine && trimTrailingWhitespaces) { - // We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line - trimTrailingWhitespacesForLines(previousStartLine, currentStartLine, previousItem); + function processTrivia(trivia: TextRangeWithKind[], parent: Node, contextNode: Node, dynamicIndentation: DynamicIndentation): void { + for (const triviaItem of trivia) { + if (isComment(triviaItem.kind) && rangeContainsRange(originalRange, triviaItem)) { + const triviaItemStart = sourceFile.getLineAndCharacterOfPosition(triviaItem.pos); + processRange(triviaItem, triviaItemStart, parent, contextNode, dynamicIndentation); } - - return lineAction; } + } - function insertIndentation(pos: number, indentation: number, lineAdded: boolean | undefined): void { - const indentationString = getIndentationString(indentation, options); - if (lineAdded) { - // new line is added before the token by the formatting rules - // insert indentation string at the very beginning of the token - recordReplace(pos, 0, indentationString); + function processRange(range: TextRangeWithKind, + rangeStart: LineAndCharacter, + parent: Node, + contextNode: Node, + dynamicIndentation: DynamicIndentation): LineAction { + + const rangeHasError = rangeContainsError(range); + let lineAction = LineAction.None; + if (!rangeHasError) { + if (!previousRange) { + // trim whitespaces starting from the beginning of the span up to the current line + const originalStart = sourceFile.getLineAndCharacterOfPosition(originalRange.pos); + trimTrailingWhitespacesForLines(originalStart.line, rangeStart.line); } else { - const tokenStart = sourceFile.getLineAndCharacterOfPosition(pos); - const startLinePosition = getStartPositionOfLine(tokenStart.line, sourceFile); - if (indentation !== characterToColumn(startLinePosition, tokenStart.character) || indentationIsDifferent(indentationString, startLinePosition)) { - recordReplace(startLinePosition, tokenStart.character, indentationString); - } + lineAction = + processPair(range, rangeStart.line, parent, previousRange, previousRangeStartLine, previousParent, contextNode, dynamicIndentation); } } - function characterToColumn(startLinePosition: number, characterInLine: number): number { - let column = 0; - for (let i = 0; i < characterInLine; i++) { - if (sourceFile.text.charCodeAt(startLinePosition + i) === CharacterCodes.tab) { - column += options.tabSize! - column % options.tabSize!; - } - else { - column++; + previousRange = range; + previousParent = parent; + previousRangeStartLine = rangeStart.line; + + return lineAction; + } + + function processPair(currentItem: TextRangeWithKind, + currentStartLine: number, + currentParent: Node, + previousItem: TextRangeWithKind, + previousStartLine: number, + previousParent: Node, + contextNode: Node, + dynamicIndentation: DynamicIndentation): LineAction { + + formattingContext.updateContext(previousItem, previousParent, currentItem, currentParent, contextNode); + + const rules = getRules(formattingContext); + + let trimTrailingWhitespaces = formattingContext.options.trimTrailingWhitespace !== false; + let lineAction = LineAction.None; + if (rules) { + // Apply rules in reverse order so that higher priority rules (which are first in the array) + // win in a conflict with lower priority rules. + forEachRight(rules, rule => { + lineAction = applyRuleEdits(rule, previousItem, previousStartLine, currentItem, currentStartLine); + switch (lineAction) { + case LineAction.LineRemoved: + // Handle the case where the next line is moved to be the end of this line. + // In this case we don't indent the next line in the next pass. + if (currentParent.getStart(sourceFile) === currentItem.pos) { + dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ false, contextNode); + } + break; + case LineAction.LineAdded: + // Handle the case where token2 is moved to the new line. + // In this case we indent token2 in the next pass but we set + // sameLineIndent flag to notify the indenter that the indentation is within the line. + if (currentParent.getStart(sourceFile) === currentItem.pos) { + dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ true, contextNode); + } + break; + default: + Debug.assert(lineAction === LineAction.None); } - } - return column; + + // We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line + trimTrailingWhitespaces = trimTrailingWhitespaces && !(rule.action & RuleAction.DeleteSpace) && rule.flags !== RuleFlags.CanDeleteNewLines; + }); + } + else { + trimTrailingWhitespaces = trimTrailingWhitespaces && currentItem.kind !== SyntaxKind.EndOfFileToken; } - function indentationIsDifferent(indentationString: string, startLinePosition: number): boolean { - return indentationString !== sourceFile.text.substr(startLinePosition, indentationString.length); + if (currentStartLine !== previousStartLine && trimTrailingWhitespaces) { + // We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line + trimTrailingWhitespacesForLines(previousStartLine, currentStartLine, previousItem); } - function indentMultilineComment(commentRange: TextRange, indentation: number, firstLineIsIndented: boolean, indentFinalLine = true) { - // split comment in lines - let startLine = sourceFile.getLineAndCharacterOfPosition(commentRange.pos).line; - const endLine = sourceFile.getLineAndCharacterOfPosition(commentRange.end).line; - if (startLine === endLine) { - if (!firstLineIsIndented) { - // treat as single line comment - insertIndentation(commentRange.pos, indentation, /*lineAdded*/ false); - } - return; - } + return lineAction; + } - const parts: TextRange[] = []; - let startPos = commentRange.pos; - for (let line = startLine; line < endLine; line++) { - const endOfLine = getEndLinePosition(line, sourceFile); - parts.push({ pos: startPos, end: endOfLine }); - startPos = getStartPositionOfLine(line + 1, sourceFile); + function insertIndentation(pos: number, indentation: number, lineAdded: boolean | undefined): void { + const indentationString = getIndentationString(indentation, options); + if (lineAdded) { + // new line is added before the token by the formatting rules + // insert indentation string at the very beginning of the token + recordReplace(pos, 0, indentationString); + } + else { + const tokenStart = sourceFile.getLineAndCharacterOfPosition(pos); + const startLinePosition = getStartPositionOfLine(tokenStart.line, sourceFile); + if (indentation !== characterToColumn(startLinePosition, tokenStart.character) || indentationIsDifferent(indentationString, startLinePosition)) { + recordReplace(startLinePosition, tokenStart.character, indentationString); } + } + } - if (indentFinalLine) { - parts.push({ pos: startPos, end: commentRange.end }); + function characterToColumn(startLinePosition: number, characterInLine: number): number { + let column = 0; + for (let i = 0; i < characterInLine; i++) { + if (sourceFile.text.charCodeAt(startLinePosition + i) === CharacterCodes.tab) { + column += options.tabSize! - column % options.tabSize!; } + else { + column++; + } + } + return column; + } - if (parts.length === 0) return; - - const startLinePos = getStartPositionOfLine(startLine, sourceFile); - - const nonWhitespaceColumnInFirstPart = - SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(startLinePos, parts[0].pos, sourceFile, options); + function indentationIsDifferent(indentationString: string, startLinePosition: number): boolean { + return indentationString !== sourceFile.text.substr(startLinePosition, indentationString.length); + } - let startIndex = 0; - if (firstLineIsIndented) { - startIndex = 1; - startLine++; + function indentMultilineComment(commentRange: TextRange, indentation: number, firstLineIsIndented: boolean, indentFinalLine = true) { + // split comment in lines + let startLine = sourceFile.getLineAndCharacterOfPosition(commentRange.pos).line; + const endLine = sourceFile.getLineAndCharacterOfPosition(commentRange.end).line; + if (startLine === endLine) { + if (!firstLineIsIndented) { + // treat as single line comment + insertIndentation(commentRange.pos, indentation, /*lineAdded*/ false); } + return; + } - // shift all parts on the delta size - const delta = indentation - nonWhitespaceColumnInFirstPart.column; - for (let i = startIndex; i < parts.length; i++ , startLine++) { - const startLinePos = getStartPositionOfLine(startLine, sourceFile); - const nonWhitespaceCharacterAndColumn = - i === 0 - ? nonWhitespaceColumnInFirstPart - : SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(parts[i].pos, parts[i].end, sourceFile, options); - const newIndentation = nonWhitespaceCharacterAndColumn.column + delta; - if (newIndentation > 0) { - const indentationString = getIndentationString(newIndentation, options); - recordReplace(startLinePos, nonWhitespaceCharacterAndColumn.character, indentationString); - } - else { - recordDelete(startLinePos, nonWhitespaceCharacterAndColumn.character); - } - } + const parts: TextRange[] = []; + let startPos = commentRange.pos; + for (let line = startLine; line < endLine; line++) { + const endOfLine = getEndLinePosition(line, sourceFile); + parts.push({ pos: startPos, end: endOfLine }); + startPos = getStartPositionOfLine(line + 1, sourceFile); } - function trimTrailingWhitespacesForLines(line1: number, line2: number, range?: TextRangeWithKind) { - for (let line = line1; line < line2; line++) { - const lineStartPosition = getStartPositionOfLine(line, sourceFile); - const lineEndPosition = getEndLinePosition(line, sourceFile); + if (indentFinalLine) { + parts.push({ pos: startPos, end: commentRange.end }); + } - // do not trim whitespaces in comments or template expression - if (range && (isComment(range.kind) || isStringOrRegularExpressionOrTemplateLiteral(range.kind)) && range.pos <= lineEndPosition && range.end > lineEndPosition) { - continue; - } + if (parts.length === 0) return; - const whitespaceStart = getTrailingWhitespaceStartPosition(lineStartPosition, lineEndPosition); - if (whitespaceStart !== -1) { - Debug.assert(whitespaceStart === lineStartPosition || !isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(whitespaceStart - 1))); - recordDelete(whitespaceStart, lineEndPosition + 1 - whitespaceStart); - } - } + const startLinePos = getStartPositionOfLine(startLine, sourceFile); + + const nonWhitespaceColumnInFirstPart = + SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(startLinePos, parts[0].pos, sourceFile, options); + + let startIndex = 0; + if (firstLineIsIndented) { + startIndex = 1; + startLine++; } - /** - * @param start The position of the first character in range - * @param end The position of the last character in range - */ - function getTrailingWhitespaceStartPosition(start: number, end: number) { - let pos = end; - while (pos >= start && isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(pos))) { - pos--; + // shift all parts on the delta size + const delta = indentation - nonWhitespaceColumnInFirstPart.column; + for (let i = startIndex; i < parts.length; i++ , startLine++) { + const startLinePos = getStartPositionOfLine(startLine, sourceFile); + const nonWhitespaceCharacterAndColumn = + i === 0 + ? nonWhitespaceColumnInFirstPart + : SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(parts[i].pos, parts[i].end, sourceFile, options); + const newIndentation = nonWhitespaceCharacterAndColumn.column + delta; + if (newIndentation > 0) { + const indentationString = getIndentationString(newIndentation, options); + recordReplace(startLinePos, nonWhitespaceCharacterAndColumn.character, indentationString); } - if (pos !== end) { - return pos + 1; + else { + recordDelete(startLinePos, nonWhitespaceCharacterAndColumn.character); } - return -1; } + } - /** - * Trimming will be done for lines after the previous range. - * Exclude comments as they had been previously processed. - */ - function trimTrailingWhitespacesForRemainingRange(trivias: TextRangeWithKind[]) { - let startPos = previousRange ? previousRange.end : originalRange.pos; - for (const trivia of trivias) { - if (isComment(trivia.kind)) { - if (startPos < trivia.pos) { - trimTrailingWitespacesForPositions(startPos, trivia.pos - 1, previousRange); - } + function trimTrailingWhitespacesForLines(line1: number, line2: number, range?: TextRangeWithKind) { + for (let line = line1; line < line2; line++) { + const lineStartPosition = getStartPositionOfLine(line, sourceFile); + const lineEndPosition = getEndLinePosition(line, sourceFile); - startPos = trivia.end + 1; - } + // do not trim whitespaces in comments or template expression + if (range && (isComment(range.kind) || isStringOrRegularExpressionOrTemplateLiteral(range.kind)) && range.pos <= lineEndPosition && range.end > lineEndPosition) { + continue; } - if (startPos < originalRange.end) { - trimTrailingWitespacesForPositions(startPos, originalRange.end, previousRange); + const whitespaceStart = getTrailingWhitespaceStartPosition(lineStartPosition, lineEndPosition); + if (whitespaceStart !== -1) { + Debug.assert(whitespaceStart === lineStartPosition || !isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(whitespaceStart - 1))); + recordDelete(whitespaceStart, lineEndPosition + 1 - whitespaceStart); } } + } - function trimTrailingWitespacesForPositions(startPos: number, endPos: number, previousRange: TextRangeWithKind) { - const startLine = sourceFile.getLineAndCharacterOfPosition(startPos).line; - const endLine = sourceFile.getLineAndCharacterOfPosition(endPos).line; - - trimTrailingWhitespacesForLines(startLine, endLine + 1, previousRange); + /** + * @param start The position of the first character in range + * @param end The position of the last character in range + */ + function getTrailingWhitespaceStartPosition(start: number, end: number) { + let pos = end; + while (pos >= start && isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(pos))) { + pos--; } - - function recordDelete(start: number, len: number) { - if (len) { - edits.push(createTextChangeFromStartLength(start, len, "")); - } + if (pos !== end) { + return pos + 1; } + return -1; + } - function recordReplace(start: number, len: number, newText: string) { - if (len || newText) { - edits.push(createTextChangeFromStartLength(start, len, newText)); + /** + * Trimming will be done for lines after the previous range. + * Exclude comments as they had been previously processed. + */ + function trimTrailingWhitespacesForRemainingRange(trivias: TextRangeWithKind[]) { + let startPos = previousRange ? previousRange.end : originalRange.pos; + for (const trivia of trivias) { + if (isComment(trivia.kind)) { + if (startPos < trivia.pos) { + trimTrailingWitespacesForPositions(startPos, trivia.pos - 1, previousRange); + } + + startPos = trivia.end + 1; } } - function recordInsert(start: number, text: string) { - if (text) { - edits.push(createTextChangeFromStartLength(start, 0, text)); - } + if (startPos < originalRange.end) { + trimTrailingWitespacesForPositions(startPos, originalRange.end, previousRange); } + } - function applyRuleEdits(rule: Rule, - previousRange: TextRangeWithKind, - previousStartLine: number, - currentRange: TextRangeWithKind, - currentStartLine: number - ): LineAction { - const onLaterLine = currentStartLine !== previousStartLine; - switch (rule.action) { - case RuleAction.StopProcessingSpaceActions: - // no action required - return LineAction.None; - case RuleAction.DeleteSpace: - if (previousRange.end !== currentRange.pos) { - // delete characters starting from t1.end up to t2.pos exclusive - recordDelete(previousRange.end, currentRange.pos - previousRange.end); - return onLaterLine ? LineAction.LineRemoved : LineAction.None; - } - break; - case RuleAction.DeleteToken: - recordDelete(previousRange.pos, previousRange.end - previousRange.pos); - break; - case RuleAction.InsertNewLine: - // exit early if we on different lines and rule cannot change number of newlines - // if line1 and line2 are on subsequent lines then no edits are required - ok to exit - // if line1 and line2 are separated with more than one newline - ok to exit since we cannot delete extra new lines - if (rule.flags !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { - return LineAction.None; - } + function trimTrailingWitespacesForPositions(startPos: number, endPos: number, previousRange: TextRangeWithKind) { + const startLine = sourceFile.getLineAndCharacterOfPosition(startPos).line; + const endLine = sourceFile.getLineAndCharacterOfPosition(endPos).line; - // edit should not be applied if we have one line feed between elements - const lineDelta = currentStartLine - previousStartLine; - if (lineDelta !== 1) { - recordReplace(previousRange.end, currentRange.pos - previousRange.end, getNewLineOrDefaultFromHost(host, options)); - return onLaterLine ? LineAction.None : LineAction.LineAdded; - } - break; - case RuleAction.InsertSpace: - // exit early if we on different lines and rule cannot change number of newlines - if (rule.flags !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { - return LineAction.None; - } + trimTrailingWhitespacesForLines(startLine, endLine + 1, previousRange); + } - const posDelta = currentRange.pos - previousRange.end; - if (posDelta !== 1 || sourceFile.text.charCodeAt(previousRange.end) !== CharacterCodes.space) { - recordReplace(previousRange.end, currentRange.pos - previousRange.end, " "); - return onLaterLine ? LineAction.LineRemoved : LineAction.None; - } - break; - case RuleAction.InsertTrailingSemicolon: - recordInsert(previousRange.end, ";"); - } - return LineAction.None; + function recordDelete(start: number, len: number) { + if (len) { + edits.push(createTextChangeFromStartLength(start, len, "")); } } - const enum LineAction { None, LineAdded, LineRemoved } - - /** - * @param precedingToken pass `null` if preceding token was already computed and result was `undefined`. - */ - export function getRangeOfEnclosingComment( - sourceFile: SourceFile, - position: number, - precedingToken?: Node | null, - tokenAtPosition = getTokenAtPosition(sourceFile, position), - ): CommentRange | undefined { - const jsdoc = findAncestor(tokenAtPosition, isJSDoc); - if (jsdoc) tokenAtPosition = jsdoc.parent; - const tokenStart = tokenAtPosition.getStart(sourceFile); - if (tokenStart <= position && position < tokenAtPosition.getEnd()) { - return undefined; + function recordReplace(start: number, len: number, newText: string) { + if (len || newText) { + edits.push(createTextChangeFromStartLength(start, len, newText)); } + } - // eslint-disable-next-line no-null/no-null - precedingToken = precedingToken === null ? undefined : precedingToken === undefined ? findPrecedingToken(position, sourceFile) : precedingToken; - - // Between two consecutive tokens, all comments are either trailing on the former - // or leading on the latter (and none are in both lists). - const trailingRangesOfPreviousToken = precedingToken && getTrailingCommentRanges(sourceFile.text, precedingToken.end); - const leadingCommentRangesOfNextToken = getLeadingCommentRangesOfNode(tokenAtPosition, sourceFile); - const commentRanges = concatenate(trailingRangesOfPreviousToken, leadingCommentRangesOfNextToken); - return commentRanges && find(commentRanges, range => rangeContainsPositionExclusive(range, position) || - // The end marker of a single-line comment does not include the newline character. - // With caret at `^`, in the following case, we are inside a comment (^ denotes the cursor position): - // - // // asdf ^\n - // - // But for closed multi-line comments, we don't want to be inside the comment in the following case: - // - // /* asdf */^ - // - // However, unterminated multi-line comments *do* contain their end. - // - // Internally, we represent the end of the comment at the newline and closing '/', respectively. - // - position === range.end && (range.kind === SyntaxKind.SingleLineCommentTrivia || position === sourceFile.getFullWidth())); + function recordInsert(start: number, text: string) { + if (text) { + edits.push(createTextChangeFromStartLength(start, 0, text)); + } } - function getOpenTokenForList(node: Node, list: readonly Node[]) { - switch (node.kind) { - case SyntaxKind.Constructor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.ArrowFunction: - if ((node as FunctionDeclaration).typeParameters === list) { - return SyntaxKind.LessThanToken; - } - else if ((node as FunctionDeclaration).parameters === list) { - return SyntaxKind.OpenParenToken; + function applyRuleEdits(rule: Rule, + previousRange: TextRangeWithKind, + previousStartLine: number, + currentRange: TextRangeWithKind, + currentStartLine: number + ): LineAction { + const onLaterLine = currentStartLine !== previousStartLine; + switch (rule.action) { + case RuleAction.StopProcessingSpaceActions: + // no action required + return LineAction.None; + case RuleAction.DeleteSpace: + if (previousRange.end !== currentRange.pos) { + // delete characters starting from t1.end up to t2.pos exclusive + recordDelete(previousRange.end, currentRange.pos - previousRange.end); + return onLaterLine ? LineAction.LineRemoved : LineAction.None; } break; - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - if ((node as CallExpression).typeArguments === list) { - return SyntaxKind.LessThanToken; + case RuleAction.DeleteToken: + recordDelete(previousRange.pos, previousRange.end - previousRange.pos); + break; + case RuleAction.InsertNewLine: + // exit early if we on different lines and rule cannot change number of newlines + // if line1 and line2 are on subsequent lines then no edits are required - ok to exit + // if line1 and line2 are separated with more than one newline - ok to exit since we cannot delete extra new lines + if (rule.flags !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { + return LineAction.None; } - else if ((node as CallExpression).arguments === list) { - return SyntaxKind.OpenParenToken; + + // edit should not be applied if we have one line feed between elements + const lineDelta = currentStartLine - previousStartLine; + if (lineDelta !== 1) { + recordReplace(previousRange.end, currentRange.pos - previousRange.end, getNewLineOrDefaultFromHost(host, options)); + return onLaterLine ? LineAction.None : LineAction.LineAdded; } break; - case SyntaxKind.TypeReference: - if ((node as TypeReferenceNode).typeArguments === list) { - return SyntaxKind.LessThanToken; + case RuleAction.InsertSpace: + // exit early if we on different lines and rule cannot change number of newlines + if (rule.flags !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { + return LineAction.None; + } + + const posDelta = currentRange.pos - previousRange.end; + if (posDelta !== 1 || sourceFile.text.charCodeAt(previousRange.end) !== CharacterCodes.space) { + recordReplace(previousRange.end, currentRange.pos - previousRange.end, " "); + return onLaterLine ? LineAction.LineRemoved : LineAction.None; } break; - case SyntaxKind.TypeLiteral: - return SyntaxKind.OpenBraceToken; + case RuleAction.InsertTrailingSemicolon: + recordInsert(previousRange.end, ";"); } + return LineAction.None; + } +} - return SyntaxKind.Unknown; +const enum LineAction { None, LineAdded, LineRemoved } + +/** + * @param precedingToken pass `null` if preceding token was already computed and result was `undefined`. + */ +export function getRangeOfEnclosingComment( + sourceFile: SourceFile, + position: number, + precedingToken?: Node | null, + tokenAtPosition = getTokenAtPosition(sourceFile, position), +): CommentRange | undefined { + const jsdoc = findAncestor(tokenAtPosition, isJSDoc); + if (jsdoc) tokenAtPosition = jsdoc.parent; + const tokenStart = tokenAtPosition.getStart(sourceFile); + if (tokenStart <= position && position < tokenAtPosition.getEnd()) { + return undefined; } - function getCloseTokenForOpenToken(kind: SyntaxKind) { - switch (kind) { - case SyntaxKind.OpenParenToken: - return SyntaxKind.CloseParenToken; - case SyntaxKind.LessThanToken: - return SyntaxKind.GreaterThanToken; - case SyntaxKind.OpenBraceToken: - return SyntaxKind.CloseBraceToken; - } + // eslint-disable-next-line no-null/no-null + precedingToken = precedingToken === null ? undefined : precedingToken === undefined ? findPrecedingToken(position, sourceFile) : precedingToken; + + // Between two consecutive tokens, all comments are either trailing on the former + // or leading on the latter (and none are in both lists). + const trailingRangesOfPreviousToken = precedingToken && getTrailingCommentRanges(sourceFile.text, precedingToken.end); + const leadingCommentRangesOfNextToken = getLeadingCommentRangesOfNode(tokenAtPosition, sourceFile); + const commentRanges = concatenate(trailingRangesOfPreviousToken, leadingCommentRangesOfNextToken); + return commentRanges && find(commentRanges, range => rangeContainsPositionExclusive(range, position) || + // The end marker of a single-line comment does not include the newline character. + // With caret at `^`, in the following case, we are inside a comment (^ denotes the cursor position): + // + // // asdf ^\n + // + // But for closed multi-line comments, we don't want to be inside the comment in the following case: + // + // /* asdf */^ + // + // However, unterminated multi-line comments *do* contain their end. + // + // Internally, we represent the end of the comment at the newline and closing '/', respectively. + // + position === range.end && (range.kind === SyntaxKind.SingleLineCommentTrivia || position === sourceFile.getFullWidth())); +} + +function getOpenTokenForList(node: Node, list: readonly Node[]) { + switch (node.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.ArrowFunction: + if ((node as FunctionDeclaration).typeParameters === list) { + return SyntaxKind.LessThanToken; + } + else if ((node as FunctionDeclaration).parameters === list) { + return SyntaxKind.OpenParenToken; + } + break; + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + if ((node as CallExpression).typeArguments === list) { + return SyntaxKind.LessThanToken; + } + else if ((node as CallExpression).arguments === list) { + return SyntaxKind.OpenParenToken; + } + break; + case SyntaxKind.TypeReference: + if ((node as TypeReferenceNode).typeArguments === list) { + return SyntaxKind.LessThanToken; + } + break; + case SyntaxKind.TypeLiteral: + return SyntaxKind.OpenBraceToken; + } - return SyntaxKind.Unknown; + return SyntaxKind.Unknown; +} + +function getCloseTokenForOpenToken(kind: SyntaxKind) { + switch (kind) { + case SyntaxKind.OpenParenToken: + return SyntaxKind.CloseParenToken; + case SyntaxKind.LessThanToken: + return SyntaxKind.GreaterThanToken; + case SyntaxKind.OpenBraceToken: + return SyntaxKind.CloseBraceToken; } - let internedSizes: { tabSize: number; indentSize: number; }; - let internedTabsIndentation: string[] | undefined; - let internedSpacesIndentation: string[] | undefined; + return SyntaxKind.Unknown; +} - export function getIndentationString(indentation: number, options: EditorSettings): string { - // reset interned strings if FormatCodeOptions were changed - const resetInternedStrings = - !internedSizes || (internedSizes.tabSize !== options.tabSize || internedSizes.indentSize !== options.indentSize); +let internedSizes: { tabSize: number; indentSize: number; }; +let internedTabsIndentation: string[] | undefined; +let internedSpacesIndentation: string[] | undefined; - if (resetInternedStrings) { - internedSizes = { tabSize: options.tabSize!, indentSize: options.indentSize! }; - internedTabsIndentation = internedSpacesIndentation = undefined; - } +export function getIndentationString(indentation: number, options: EditorSettings): string { + // reset interned strings if FormatCodeOptions were changed + const resetInternedStrings = + !internedSizes || (internedSizes.tabSize !== options.tabSize || internedSizes.indentSize !== options.indentSize); - if (!options.convertTabsToSpaces) { - const tabs = Math.floor(indentation / options.tabSize!); - const spaces = indentation - tabs * options.tabSize!; + if (resetInternedStrings) { + internedSizes = { tabSize: options.tabSize!, indentSize: options.indentSize! }; + internedTabsIndentation = internedSpacesIndentation = undefined; + } - let tabString: string; - if (!internedTabsIndentation) { - internedTabsIndentation = []; - } + if (!options.convertTabsToSpaces) { + const tabs = Math.floor(indentation / options.tabSize!); + const spaces = indentation - tabs * options.tabSize!; - if (internedTabsIndentation[tabs] === undefined) { - internedTabsIndentation[tabs] = tabString = repeatString("\t", tabs); - } - else { - tabString = internedTabsIndentation[tabs]; - } + let tabString: string; + if (!internedTabsIndentation) { + internedTabsIndentation = []; + } - return spaces ? tabString + repeatString(" ", spaces) : tabString; + if (internedTabsIndentation[tabs] === undefined) { + internedTabsIndentation[tabs] = tabString = repeatString("\t", tabs); } else { - let spacesString: string; - const quotient = Math.floor(indentation / options.indentSize!); - const remainder = indentation % options.indentSize!; - if (!internedSpacesIndentation) { - internedSpacesIndentation = []; - } + tabString = internedTabsIndentation[tabs]; + } - if (internedSpacesIndentation[quotient] === undefined) { - spacesString = repeatString(" ", options.indentSize! * quotient); - internedSpacesIndentation[quotient] = spacesString; - } - else { - spacesString = internedSpacesIndentation[quotient]; - } + return spaces ? tabString + repeatString(" ", spaces) : tabString; + } + else { + let spacesString: string; + const quotient = Math.floor(indentation / options.indentSize!); + const remainder = indentation % options.indentSize!; + if (!internedSpacesIndentation) { + internedSpacesIndentation = []; + } - return remainder ? spacesString + repeatString(" ", remainder) : spacesString; + if (internedSpacesIndentation[quotient] === undefined) { + spacesString = repeatString(" ", options.indentSize! * quotient); + internedSpacesIndentation[quotient] = spacesString; + } + else { + spacesString = internedSpacesIndentation[quotient]; } + + return remainder ? spacesString + repeatString(" ", remainder) : spacesString; } } +} diff --git a/src/services/formatting/formattingContext.ts b/src/services/formatting/formattingContext.ts index 5701f0cd0ad0e..1fecd25e3aaa7 100644 --- a/src/services/formatting/formattingContext.ts +++ b/src/services/formatting/formattingContext.ts @@ -1,102 +1,102 @@ /* @internal */ namespace ts.formatting { - export const enum FormattingRequestKind { - FormatDocument, - FormatSelection, - FormatOnEnter, - FormatOnSemicolon, - FormatOnOpeningCurlyBrace, - FormatOnClosingCurlyBrace - } +export const enum FormattingRequestKind { + FormatDocument, + FormatSelection, + FormatOnEnter, + FormatOnSemicolon, + FormatOnOpeningCurlyBrace, + FormatOnClosingCurlyBrace +} - export class FormattingContext { - public currentTokenSpan!: TextRangeWithKind; - public nextTokenSpan!: TextRangeWithKind; - public contextNode!: Node; - public currentTokenParent!: Node; - public nextTokenParent!: Node; +export class FormattingContext { + public currentTokenSpan!: TextRangeWithKind; + public nextTokenSpan!: TextRangeWithKind; + public contextNode!: Node; + public currentTokenParent!: Node; + public nextTokenParent!: Node; - private contextNodeAllOnSameLine: boolean | undefined; - private nextNodeAllOnSameLine: boolean | undefined; - private tokensAreOnSameLine: boolean | undefined; - private contextNodeBlockIsOnOneLine: boolean | undefined; - private nextNodeBlockIsOnOneLine: boolean | undefined; + private contextNodeAllOnSameLine: boolean | undefined; + private nextNodeAllOnSameLine: boolean | undefined; + private tokensAreOnSameLine: boolean | undefined; + private contextNodeBlockIsOnOneLine: boolean | undefined; + private nextNodeBlockIsOnOneLine: boolean | undefined; - constructor(public readonly sourceFile: SourceFileLike, public formattingRequestKind: FormattingRequestKind, public options: FormatCodeSettings) { - } + constructor(public readonly sourceFile: SourceFileLike, public formattingRequestKind: FormattingRequestKind, public options: FormatCodeSettings) { + } - public updateContext(currentRange: TextRangeWithKind, currentTokenParent: Node, nextRange: TextRangeWithKind, nextTokenParent: Node, commonParent: Node) { - this.currentTokenSpan = Debug.checkDefined(currentRange); - this.currentTokenParent = Debug.checkDefined(currentTokenParent); - this.nextTokenSpan = Debug.checkDefined(nextRange); - this.nextTokenParent = Debug.checkDefined(nextTokenParent); - this.contextNode = Debug.checkDefined(commonParent); - - // drop cached results - this.contextNodeAllOnSameLine = undefined; - this.nextNodeAllOnSameLine = undefined; - this.tokensAreOnSameLine = undefined; - this.contextNodeBlockIsOnOneLine = undefined; - this.nextNodeBlockIsOnOneLine = undefined; + public updateContext(currentRange: TextRangeWithKind, currentTokenParent: Node, nextRange: TextRangeWithKind, nextTokenParent: Node, commonParent: Node) { + this.currentTokenSpan = Debug.checkDefined(currentRange); + this.currentTokenParent = Debug.checkDefined(currentTokenParent); + this.nextTokenSpan = Debug.checkDefined(nextRange); + this.nextTokenParent = Debug.checkDefined(nextTokenParent); + this.contextNode = Debug.checkDefined(commonParent); + + // drop cached results + this.contextNodeAllOnSameLine = undefined; + this.nextNodeAllOnSameLine = undefined; + this.tokensAreOnSameLine = undefined; + this.contextNodeBlockIsOnOneLine = undefined; + this.nextNodeBlockIsOnOneLine = undefined; + } + + public ContextNodeAllOnSameLine(): boolean { + if (this.contextNodeAllOnSameLine === undefined) { + this.contextNodeAllOnSameLine = this.NodeIsOnOneLine(this.contextNode); } - public ContextNodeAllOnSameLine(): boolean { - if (this.contextNodeAllOnSameLine === undefined) { - this.contextNodeAllOnSameLine = this.NodeIsOnOneLine(this.contextNode); - } + return this.contextNodeAllOnSameLine; + } - return this.contextNodeAllOnSameLine; + public NextNodeAllOnSameLine(): boolean { + if (this.nextNodeAllOnSameLine === undefined) { + this.nextNodeAllOnSameLine = this.NodeIsOnOneLine(this.nextTokenParent); } - public NextNodeAllOnSameLine(): boolean { - if (this.nextNodeAllOnSameLine === undefined) { - this.nextNodeAllOnSameLine = this.NodeIsOnOneLine(this.nextTokenParent); - } + return this.nextNodeAllOnSameLine; + } - return this.nextNodeAllOnSameLine; + public TokensAreOnSameLine(): boolean { + if (this.tokensAreOnSameLine === undefined) { + const startLine = this.sourceFile.getLineAndCharacterOfPosition(this.currentTokenSpan.pos).line; + const endLine = this.sourceFile.getLineAndCharacterOfPosition(this.nextTokenSpan.pos).line; + this.tokensAreOnSameLine = (startLine === endLine); } - public TokensAreOnSameLine(): boolean { - if (this.tokensAreOnSameLine === undefined) { - const startLine = this.sourceFile.getLineAndCharacterOfPosition(this.currentTokenSpan.pos).line; - const endLine = this.sourceFile.getLineAndCharacterOfPosition(this.nextTokenSpan.pos).line; - this.tokensAreOnSameLine = (startLine === endLine); - } + return this.tokensAreOnSameLine; + } - return this.tokensAreOnSameLine; + public ContextNodeBlockIsOnOneLine() { + if (this.contextNodeBlockIsOnOneLine === undefined) { + this.contextNodeBlockIsOnOneLine = this.BlockIsOnOneLine(this.contextNode); } - public ContextNodeBlockIsOnOneLine() { - if (this.contextNodeBlockIsOnOneLine === undefined) { - this.contextNodeBlockIsOnOneLine = this.BlockIsOnOneLine(this.contextNode); - } + return this.contextNodeBlockIsOnOneLine; + } - return this.contextNodeBlockIsOnOneLine; + public NextNodeBlockIsOnOneLine() { + if (this.nextNodeBlockIsOnOneLine === undefined) { + this.nextNodeBlockIsOnOneLine = this.BlockIsOnOneLine(this.nextTokenParent); } - public NextNodeBlockIsOnOneLine() { - if (this.nextNodeBlockIsOnOneLine === undefined) { - this.nextNodeBlockIsOnOneLine = this.BlockIsOnOneLine(this.nextTokenParent); - } + return this.nextNodeBlockIsOnOneLine; + } - return this.nextNodeBlockIsOnOneLine; - } + private NodeIsOnOneLine(node: Node): boolean { + const startLine = this.sourceFile.getLineAndCharacterOfPosition(node.getStart(this.sourceFile)).line; + const endLine = this.sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line; + return startLine === endLine; + } - private NodeIsOnOneLine(node: Node): boolean { - const startLine = this.sourceFile.getLineAndCharacterOfPosition(node.getStart(this.sourceFile)).line; - const endLine = this.sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line; + private BlockIsOnOneLine(node: Node): boolean { + const openBrace = findChildOfKind(node, SyntaxKind.OpenBraceToken, this.sourceFile); + const closeBrace = findChildOfKind(node, SyntaxKind.CloseBraceToken, this.sourceFile); + if (openBrace && closeBrace) { + const startLine = this.sourceFile.getLineAndCharacterOfPosition(openBrace.getEnd()).line; + const endLine = this.sourceFile.getLineAndCharacterOfPosition(closeBrace.getStart(this.sourceFile)).line; return startLine === endLine; } - - private BlockIsOnOneLine(node: Node): boolean { - const openBrace = findChildOfKind(node, SyntaxKind.OpenBraceToken, this.sourceFile); - const closeBrace = findChildOfKind(node, SyntaxKind.CloseBraceToken, this.sourceFile); - if (openBrace && closeBrace) { - const startLine = this.sourceFile.getLineAndCharacterOfPosition(openBrace.getEnd()).line; - const endLine = this.sourceFile.getLineAndCharacterOfPosition(closeBrace.getStart(this.sourceFile)).line; - return startLine === endLine; - } - return false; - } + return false; } } +} diff --git a/src/services/formatting/formattingScanner.ts b/src/services/formatting/formattingScanner.ts index b619f98eb2b85..f1b0f6b105f0b 100644 --- a/src/services/formatting/formattingScanner.ts +++ b/src/services/formatting/formattingScanner.ts @@ -1,308 +1,308 @@ /* @internal */ namespace ts.formatting { - const standardScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard); - const jsxScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.JSX); - - export interface FormattingScanner { - advance(): void; - isOnToken(): boolean; - isOnEOF(): boolean; - readTokenInfo(n: Node): TokenInfo; - readEOFTokenRange(): TextRangeWithKind; - getCurrentLeadingTrivia(): TextRangeWithKind[] | undefined; - lastTrailingTriviaWasNewLine(): boolean; - skipToEndOf(node: Node): void; - skipToStartOf(node: Node): void; - } +const standardScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard); +const jsxScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.JSX); + +export interface FormattingScanner { + advance(): void; + isOnToken(): boolean; + isOnEOF(): boolean; + readTokenInfo(n: Node): TokenInfo; + readEOFTokenRange(): TextRangeWithKind; + getCurrentLeadingTrivia(): TextRangeWithKind[] | undefined; + lastTrailingTriviaWasNewLine(): boolean; + skipToEndOf(node: Node): void; + skipToStartOf(node: Node): void; +} - const enum ScanAction { - Scan, - RescanGreaterThanToken, - RescanSlashToken, - RescanTemplateToken, - RescanJsxIdentifier, - RescanJsxText, - RescanJsxAttributeValue, - } +const enum ScanAction { + Scan, + RescanGreaterThanToken, + RescanSlashToken, + RescanTemplateToken, + RescanJsxIdentifier, + RescanJsxText, + RescanJsxAttributeValue, +} + +export function getFormattingScanner(text: string, languageVariant: LanguageVariant, startPos: number, endPos: number, cb: (scanner: FormattingScanner) => T): T { + const scanner = languageVariant === LanguageVariant.JSX ? jsxScanner : standardScanner; - export function getFormattingScanner(text: string, languageVariant: LanguageVariant, startPos: number, endPos: number, cb: (scanner: FormattingScanner) => T): T { - const scanner = languageVariant === LanguageVariant.JSX ? jsxScanner : standardScanner; + scanner.setText(text); + scanner.setTextPos(startPos); - scanner.setText(text); - scanner.setTextPos(startPos); + let wasNewLine = true; + let leadingTrivia: TextRangeWithTriviaKind[] | undefined; + let trailingTrivia: TextRangeWithTriviaKind[] | undefined; - let wasNewLine = true; - let leadingTrivia: TextRangeWithTriviaKind[] | undefined; - let trailingTrivia: TextRangeWithTriviaKind[] | undefined; + let savedPos: number; + let lastScanAction: ScanAction | undefined; + let lastTokenInfo: TokenInfo | undefined; - let savedPos: number; - let lastScanAction: ScanAction | undefined; - let lastTokenInfo: TokenInfo | undefined; + const res = cb({ + advance, + readTokenInfo, + readEOFTokenRange, + isOnToken, + isOnEOF, + getCurrentLeadingTrivia: () => leadingTrivia, + lastTrailingTriviaWasNewLine: () => wasNewLine, + skipToEndOf, + skipToStartOf, + }); - const res = cb({ - advance, - readTokenInfo, - readEOFTokenRange, - isOnToken, - isOnEOF, - getCurrentLeadingTrivia: () => leadingTrivia, - lastTrailingTriviaWasNewLine: () => wasNewLine, - skipToEndOf, - skipToStartOf, - }); + lastTokenInfo = undefined; + scanner.setText(undefined); + return res; + + function advance(): void { lastTokenInfo = undefined; - scanner.setText(undefined); + const isStarted = scanner.getStartPos() !== startPos; - return res; + if (isStarted) { + wasNewLine = !!trailingTrivia && last(trailingTrivia).kind === SyntaxKind.NewLineTrivia; + } + else { + scanner.scan(); + } - function advance(): void { - lastTokenInfo = undefined; - const isStarted = scanner.getStartPos() !== startPos; + leadingTrivia = undefined; + trailingTrivia = undefined; - if (isStarted) { - wasNewLine = !!trailingTrivia && last(trailingTrivia).kind === SyntaxKind.NewLineTrivia; - } - else { - scanner.scan(); + let pos = scanner.getStartPos(); + + // Read leading trivia and token + while (pos < endPos) { + const t = scanner.getToken(); + if (!isTrivia(t)) { + break; } - leadingTrivia = undefined; - trailingTrivia = undefined; + // consume leading trivia + scanner.scan(); + const item: TextRangeWithTriviaKind = { + pos, + end: scanner.getStartPos(), + kind: t + }; - let pos = scanner.getStartPos(); + pos = scanner.getStartPos(); - // Read leading trivia and token - while (pos < endPos) { - const t = scanner.getToken(); - if (!isTrivia(t)) { - break; - } + leadingTrivia = append(leadingTrivia, item); + } - // consume leading trivia - scanner.scan(); - const item: TextRangeWithTriviaKind = { - pos, - end: scanner.getStartPos(), - kind: t - }; + savedPos = scanner.getStartPos(); + } - pos = scanner.getStartPos(); + function shouldRescanGreaterThanToken(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.GreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanToken: + return true; + } - leadingTrivia = append(leadingTrivia, item); - } + return false; + } - savedPos = scanner.getStartPos(); + function shouldRescanJsxIdentifier(node: Node): boolean { + if (node.parent) { + switch (node.parent.kind) { + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxClosingElement: + case SyntaxKind.JsxSelfClosingElement: + // May parse an identifier like `module-layout`; that will be scanned as a keyword at first, but we should parse the whole thing to get an identifier. + return isKeyword(node.kind) || node.kind === SyntaxKind.Identifier; + } } - function shouldRescanGreaterThanToken(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.GreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - case SyntaxKind.GreaterThanGreaterThanToken: - return true; - } + return false; + } - return false; - } + function shouldRescanJsxText(node: Node): boolean { + return isJsxText(node); + } - function shouldRescanJsxIdentifier(node: Node): boolean { - if (node.parent) { - switch (node.parent.kind) { - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxClosingElement: - case SyntaxKind.JsxSelfClosingElement: - // May parse an identifier like `module-layout`; that will be scanned as a keyword at first, but we should parse the whole thing to get an identifier. - return isKeyword(node.kind) || node.kind === SyntaxKind.Identifier; - } - } + function shouldRescanSlashToken(container: Node): boolean { + return container.kind === SyntaxKind.RegularExpressionLiteral; + } - return false; - } + function shouldRescanTemplateToken(container: Node): boolean { + return container.kind === SyntaxKind.TemplateMiddle || + container.kind === SyntaxKind.TemplateTail; + } - function shouldRescanJsxText(node: Node): boolean { - return isJsxText(node); - } + function shouldRescanJsxAttributeValue(node: Node): boolean { + return node.parent && isJsxAttribute(node.parent) && node.parent.initializer === node; + } - function shouldRescanSlashToken(container: Node): boolean { - return container.kind === SyntaxKind.RegularExpressionLiteral; - } + function startsWithSlashToken(t: SyntaxKind): boolean { + return t === SyntaxKind.SlashToken || t === SyntaxKind.SlashEqualsToken; + } - function shouldRescanTemplateToken(container: Node): boolean { - return container.kind === SyntaxKind.TemplateMiddle || - container.kind === SyntaxKind.TemplateTail; + function readTokenInfo(n: Node): TokenInfo { + Debug.assert(isOnToken()); + + // normally scanner returns the smallest available token + // check the kind of context node to determine if scanner should have more greedy behavior and consume more text. + const expectedScanAction = shouldRescanGreaterThanToken(n) ? ScanAction.RescanGreaterThanToken : + shouldRescanSlashToken(n) ? ScanAction.RescanSlashToken : + shouldRescanTemplateToken(n) ? ScanAction.RescanTemplateToken : + shouldRescanJsxIdentifier(n) ? ScanAction.RescanJsxIdentifier : + shouldRescanJsxText(n) ? ScanAction.RescanJsxText : + shouldRescanJsxAttributeValue(n) ? ScanAction.RescanJsxAttributeValue : + ScanAction.Scan; + + if (lastTokenInfo && expectedScanAction === lastScanAction) { + // readTokenInfo was called before with the same expected scan action. + // No need to re-scan text, return existing 'lastTokenInfo' + // it is ok to call fixTokenKind here since it does not affect + // what portion of text is consumed. In contrast rescanning can change it, + // i.e. for '>=' when originally scanner eats just one character + // and rescanning forces it to consume more. + return fixTokenKind(lastTokenInfo, n); } - function shouldRescanJsxAttributeValue(node: Node): boolean { - return node.parent && isJsxAttribute(node.parent) && node.parent.initializer === node; + if (scanner.getStartPos() !== savedPos) { + Debug.assert(lastTokenInfo !== undefined); + // readTokenInfo was called before but scan action differs - rescan text + scanner.setTextPos(savedPos); + scanner.scan(); } - function startsWithSlashToken(t: SyntaxKind): boolean { - return t === SyntaxKind.SlashToken || t === SyntaxKind.SlashEqualsToken; - } + let currentToken = getNextToken(n, expectedScanAction); - function readTokenInfo(n: Node): TokenInfo { - Debug.assert(isOnToken()); - - // normally scanner returns the smallest available token - // check the kind of context node to determine if scanner should have more greedy behavior and consume more text. - const expectedScanAction = shouldRescanGreaterThanToken(n) ? ScanAction.RescanGreaterThanToken : - shouldRescanSlashToken(n) ? ScanAction.RescanSlashToken : - shouldRescanTemplateToken(n) ? ScanAction.RescanTemplateToken : - shouldRescanJsxIdentifier(n) ? ScanAction.RescanJsxIdentifier : - shouldRescanJsxText(n) ? ScanAction.RescanJsxText : - shouldRescanJsxAttributeValue(n) ? ScanAction.RescanJsxAttributeValue : - ScanAction.Scan; - - if (lastTokenInfo && expectedScanAction === lastScanAction) { - // readTokenInfo was called before with the same expected scan action. - // No need to re-scan text, return existing 'lastTokenInfo' - // it is ok to call fixTokenKind here since it does not affect - // what portion of text is consumed. In contrast rescanning can change it, - // i.e. for '>=' when originally scanner eats just one character - // and rescanning forces it to consume more. - return fixTokenKind(lastTokenInfo, n); - } + const token = createTextRangeWithKind( + scanner.getStartPos(), + scanner.getTextPos(), + currentToken, + ); - if (scanner.getStartPos() !== savedPos) { - Debug.assert(lastTokenInfo !== undefined); - // readTokenInfo was called before but scan action differs - rescan text - scanner.setTextPos(savedPos); - scanner.scan(); + // consume trailing trivia + if (trailingTrivia) { + trailingTrivia = undefined; + } + while (scanner.getStartPos() < endPos) { + currentToken = scanner.scan(); + if (!isTrivia(currentToken)) { + break; } - - let currentToken = getNextToken(n, expectedScanAction); - - const token = createTextRangeWithKind( + const trivia = createTextRangeWithKind( scanner.getStartPos(), scanner.getTextPos(), currentToken, ); - // consume trailing trivia - if (trailingTrivia) { - trailingTrivia = undefined; + if (!trailingTrivia) { + trailingTrivia = []; } - while (scanner.getStartPos() < endPos) { - currentToken = scanner.scan(); - if (!isTrivia(currentToken)) { - break; - } - const trivia = createTextRangeWithKind( - scanner.getStartPos(), - scanner.getTextPos(), - currentToken, - ); - - if (!trailingTrivia) { - trailingTrivia = []; - } - trailingTrivia.push(trivia); + trailingTrivia.push(trivia); - if (currentToken === SyntaxKind.NewLineTrivia) { - // move past new line - scanner.scan(); - break; - } + if (currentToken === SyntaxKind.NewLineTrivia) { + // move past new line + scanner.scan(); + break; } + } - lastTokenInfo = { leadingTrivia, trailingTrivia, token }; + lastTokenInfo = { leadingTrivia, trailingTrivia, token }; - return fixTokenKind(lastTokenInfo, n); - } + return fixTokenKind(lastTokenInfo, n); + } - function getNextToken(n: Node, expectedScanAction: ScanAction): SyntaxKind { - const token = scanner.getToken(); - lastScanAction = ScanAction.Scan; - switch (expectedScanAction) { - case ScanAction.RescanGreaterThanToken: - if (token === SyntaxKind.GreaterThanToken) { - lastScanAction = ScanAction.RescanGreaterThanToken; - const newToken = scanner.reScanGreaterToken(); - Debug.assert(n.kind === newToken); - return newToken; - } - break; - case ScanAction.RescanSlashToken: - if (startsWithSlashToken(token)) { - lastScanAction = ScanAction.RescanSlashToken; - const newToken = scanner.reScanSlashToken(); - Debug.assert(n.kind === newToken); - return newToken; - } - break; - case ScanAction.RescanTemplateToken: - if (token === SyntaxKind.CloseBraceToken) { - lastScanAction = ScanAction.RescanTemplateToken; - return scanner.reScanTemplateToken(/* isTaggedTemplate */ false); - } - break; - case ScanAction.RescanJsxIdentifier: - lastScanAction = ScanAction.RescanJsxIdentifier; - return scanner.scanJsxIdentifier(); - case ScanAction.RescanJsxText: - lastScanAction = ScanAction.RescanJsxText; - return scanner.reScanJsxToken(/* allowMultilineJsxText */ false); - case ScanAction.RescanJsxAttributeValue: - lastScanAction = ScanAction.RescanJsxAttributeValue; - return scanner.reScanJsxAttributeValue(); - case ScanAction.Scan: - break; - default: - Debug.assertNever(expectedScanAction); - } - return token; + function getNextToken(n: Node, expectedScanAction: ScanAction): SyntaxKind { + const token = scanner.getToken(); + lastScanAction = ScanAction.Scan; + switch (expectedScanAction) { + case ScanAction.RescanGreaterThanToken: + if (token === SyntaxKind.GreaterThanToken) { + lastScanAction = ScanAction.RescanGreaterThanToken; + const newToken = scanner.reScanGreaterToken(); + Debug.assert(n.kind === newToken); + return newToken; + } + break; + case ScanAction.RescanSlashToken: + if (startsWithSlashToken(token)) { + lastScanAction = ScanAction.RescanSlashToken; + const newToken = scanner.reScanSlashToken(); + Debug.assert(n.kind === newToken); + return newToken; + } + break; + case ScanAction.RescanTemplateToken: + if (token === SyntaxKind.CloseBraceToken) { + lastScanAction = ScanAction.RescanTemplateToken; + return scanner.reScanTemplateToken(/* isTaggedTemplate */ false); + } + break; + case ScanAction.RescanJsxIdentifier: + lastScanAction = ScanAction.RescanJsxIdentifier; + return scanner.scanJsxIdentifier(); + case ScanAction.RescanJsxText: + lastScanAction = ScanAction.RescanJsxText; + return scanner.reScanJsxToken(/* allowMultilineJsxText */ false); + case ScanAction.RescanJsxAttributeValue: + lastScanAction = ScanAction.RescanJsxAttributeValue; + return scanner.reScanJsxAttributeValue(); + case ScanAction.Scan: + break; + default: + Debug.assertNever(expectedScanAction); } + return token; + } - function readEOFTokenRange(): TextRangeWithKind { - Debug.assert(isOnEOF()); - return createTextRangeWithKind(scanner.getStartPos(), scanner.getTextPos(), SyntaxKind.EndOfFileToken); - } + function readEOFTokenRange(): TextRangeWithKind { + Debug.assert(isOnEOF()); + return createTextRangeWithKind(scanner.getStartPos(), scanner.getTextPos(), SyntaxKind.EndOfFileToken); + } - function isOnToken(): boolean { - const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); - const startPos = lastTokenInfo ? lastTokenInfo.token.pos : scanner.getStartPos(); - return startPos < endPos && current !== SyntaxKind.EndOfFileToken && !isTrivia(current); - } + function isOnToken(): boolean { + const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); + const startPos = lastTokenInfo ? lastTokenInfo.token.pos : scanner.getStartPos(); + return startPos < endPos && current !== SyntaxKind.EndOfFileToken && !isTrivia(current); + } - function isOnEOF(): boolean { - const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); - return current === SyntaxKind.EndOfFileToken; - } + function isOnEOF(): boolean { + const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); + return current === SyntaxKind.EndOfFileToken; + } - // when containing node in the tree is token - // but its kind differs from the kind that was returned by the scanner, - // then kind needs to be fixed. This might happen in cases - // when parser interprets token differently, i.e keyword treated as identifier - function fixTokenKind(tokenInfo: TokenInfo, container: Node): TokenInfo { - if (isToken(container) && tokenInfo.token.kind !== container.kind) { - tokenInfo.token.kind = container.kind; - } - return tokenInfo; + // when containing node in the tree is token + // but its kind differs from the kind that was returned by the scanner, + // then kind needs to be fixed. This might happen in cases + // when parser interprets token differently, i.e keyword treated as identifier + function fixTokenKind(tokenInfo: TokenInfo, container: Node): TokenInfo { + if (isToken(container) && tokenInfo.token.kind !== container.kind) { + tokenInfo.token.kind = container.kind; } + return tokenInfo; + } - function skipToEndOf(node: Node): void { - scanner.setTextPos(node.end); - savedPos = scanner.getStartPos(); - lastScanAction = undefined; - lastTokenInfo = undefined; - wasNewLine = false; - leadingTrivia = undefined; - trailingTrivia = undefined; - } + function skipToEndOf(node: Node): void { + scanner.setTextPos(node.end); + savedPos = scanner.getStartPos(); + lastScanAction = undefined; + lastTokenInfo = undefined; + wasNewLine = false; + leadingTrivia = undefined; + trailingTrivia = undefined; + } - function skipToStartOf(node: Node): void { - scanner.setTextPos(node.pos); - savedPos = scanner.getStartPos(); - lastScanAction = undefined; - lastTokenInfo = undefined; - wasNewLine = false; - leadingTrivia = undefined; - trailingTrivia = undefined; - } + function skipToStartOf(node: Node): void { + scanner.setTextPos(node.pos); + savedPos = scanner.getStartPos(); + lastScanAction = undefined; + lastTokenInfo = undefined; + wasNewLine = false; + leadingTrivia = undefined; + trailingTrivia = undefined; } } +} diff --git a/src/services/formatting/rule.ts b/src/services/formatting/rule.ts index ef98461738891..6a7c4fc3a2692 100644 --- a/src/services/formatting/rule.ts +++ b/src/services/formatting/rule.ts @@ -1,37 +1,37 @@ /* @internal */ namespace ts.formatting { - export interface Rule { - // Used for debugging to identify each rule based on the property name it's assigned to. - readonly debugName: string; - readonly context: readonly ContextPredicate[]; - readonly action: RuleAction; - readonly flags: RuleFlags; - } +export interface Rule { + // Used for debugging to identify each rule based on the property name it's assigned to. + readonly debugName: string; + readonly context: readonly ContextPredicate[]; + readonly action: RuleAction; + readonly flags: RuleFlags; +} - export type ContextPredicate = (context: FormattingContext) => boolean; - export const anyContext: readonly ContextPredicate[] = emptyArray; +export type ContextPredicate = (context: FormattingContext) => boolean; +export const anyContext: readonly ContextPredicate[] = emptyArray; - export const enum RuleAction { - StopProcessingSpaceActions = 1 << 0, - StopProcessingTokenActions = 1 << 1, - InsertSpace = 1 << 2, - InsertNewLine = 1 << 3, - DeleteSpace = 1 << 4, - DeleteToken = 1 << 5, - InsertTrailingSemicolon = 1 << 6, +export const enum RuleAction { + StopProcessingSpaceActions = 1 << 0, + StopProcessingTokenActions = 1 << 1, + InsertSpace = 1 << 2, + InsertNewLine = 1 << 3, + DeleteSpace = 1 << 4, + DeleteToken = 1 << 5, + InsertTrailingSemicolon = 1 << 6, - StopAction = StopProcessingSpaceActions | StopProcessingTokenActions, - ModifySpaceAction = InsertSpace | InsertNewLine | DeleteSpace, - ModifyTokenAction = DeleteToken | InsertTrailingSemicolon, - } + StopAction = StopProcessingSpaceActions | StopProcessingTokenActions, + ModifySpaceAction = InsertSpace | InsertNewLine | DeleteSpace, + ModifyTokenAction = DeleteToken | InsertTrailingSemicolon, +} - export const enum RuleFlags { - None, - CanDeleteNewLines, - } +export const enum RuleFlags { + None, + CanDeleteNewLines, +} - export interface TokenRange { - readonly tokens: readonly SyntaxKind[]; - readonly isSpecific: boolean; - } +export interface TokenRange { + readonly tokens: readonly SyntaxKind[]; + readonly isSpecific: boolean; +} } diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index 76a0d34853153..6932aa739c50f 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -1,895 +1,895 @@ /* @internal */ namespace ts.formatting { - export interface RuleSpec { - readonly leftTokenRange: TokenRange; - readonly rightTokenRange: TokenRange; - readonly rule: Rule; - } +export interface RuleSpec { + readonly leftTokenRange: TokenRange; + readonly rightTokenRange: TokenRange; + readonly rule: Rule; +} - export function getAllRules(): RuleSpec[] { - const allTokens: SyntaxKind[] = []; - for (let token = SyntaxKind.FirstToken; token <= SyntaxKind.LastToken; token++) { - if (token !== SyntaxKind.EndOfFileToken) { - allTokens.push(token); - } +export function getAllRules(): RuleSpec[] { + const allTokens: SyntaxKind[] = []; + for (let token = SyntaxKind.FirstToken; token <= SyntaxKind.LastToken; token++) { + if (token !== SyntaxKind.EndOfFileToken) { + allTokens.push(token); } - function anyTokenExcept(...tokens: SyntaxKind[]): TokenRange { - return { tokens: allTokens.filter(t => !tokens.some(t2 => t2 === t)), isSpecific: false }; - } - - const anyToken: TokenRange = { tokens: allTokens, isSpecific: false }; - const anyTokenIncludingMultilineComments = tokenRangeFrom([...allTokens, SyntaxKind.MultiLineCommentTrivia]); - const anyTokenIncludingEOF = tokenRangeFrom([...allTokens, SyntaxKind.EndOfFileToken]); - const keywords = tokenRangeFromRange(SyntaxKind.FirstKeyword, SyntaxKind.LastKeyword); - const binaryOperators = tokenRangeFromRange(SyntaxKind.FirstBinaryOperator, SyntaxKind.LastBinaryOperator); - const binaryKeywordOperators = [SyntaxKind.InKeyword, SyntaxKind.InstanceOfKeyword, SyntaxKind.OfKeyword, SyntaxKind.AsKeyword, SyntaxKind.IsKeyword]; - const unaryPrefixOperators = [SyntaxKind.PlusPlusToken, SyntaxKind.MinusMinusToken, SyntaxKind.TildeToken, SyntaxKind.ExclamationToken]; - const unaryPrefixExpressions = [ - SyntaxKind.NumericLiteral, SyntaxKind.BigIntLiteral, SyntaxKind.Identifier, SyntaxKind.OpenParenToken, - SyntaxKind.OpenBracketToken, SyntaxKind.OpenBraceToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; - const unaryPreincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; - const unaryPostincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]; - const unaryPredecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; - const unaryPostdecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]; - const comments = [SyntaxKind.SingleLineCommentTrivia, SyntaxKind.MultiLineCommentTrivia]; - const typeNames = [SyntaxKind.Identifier, ...typeKeywords]; - - // Place a space before open brace in a function declaration - // TypeScript: Function can have return types, which can be made of tons of different token kinds - const functionOpenBraceLeftTokenRange = anyTokenIncludingMultilineComments; - - // Place a space before open brace in a TypeScript declaration that has braces as children (class, module, enum, etc) - const typeScriptOpenBraceLeftTokenRange = tokenRangeFrom([SyntaxKind.Identifier, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.ClassKeyword, SyntaxKind.ExportKeyword, SyntaxKind.ImportKeyword]); - - // Place a space before open brace in a control flow construct - const controlOpenBraceLeftTokenRange = tokenRangeFrom([SyntaxKind.CloseParenToken, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.DoKeyword, SyntaxKind.TryKeyword, SyntaxKind.FinallyKeyword, SyntaxKind.ElseKeyword]); - - // These rules are higher in priority than user-configurable - const highPriorityCommonRules = [ - // Leave comments alone - rule("IgnoreBeforeComment", anyToken, comments, anyContext, RuleAction.StopProcessingSpaceActions), - rule("IgnoreAfterLineComment", SyntaxKind.SingleLineCommentTrivia, anyToken, anyContext, RuleAction.StopProcessingSpaceActions), - - rule("NotSpaceBeforeColon", anyToken, SyntaxKind.ColonToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.DeleteSpace), - rule("SpaceAfterColon", SyntaxKind.ColonToken, anyToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.InsertSpace), - rule("NoSpaceBeforeQuestionMark", anyToken, SyntaxKind.QuestionToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.DeleteSpace), - // insert space after '?' only when it is used in conditional operator - rule("SpaceAfterQuestionMarkInConditionalOperator", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext, isConditionalOperatorContext], RuleAction.InsertSpace), - - // in other cases there should be no space between '?' and next token - rule("NoSpaceAfterQuestionMark", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - rule("NoSpaceBeforeDot", anyToken, [SyntaxKind.DotToken, SyntaxKind.QuestionDotToken], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterDot", [SyntaxKind.DotToken, SyntaxKind.QuestionDotToken], anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - rule("NoSpaceBetweenImportParenInImportType", SyntaxKind.ImportKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isImportTypeContext], RuleAction.DeleteSpace), - - // Special handling of unary operators. - // Prefix operators generally shouldn't have a space between - // them and their target unary expression. - rule("NoSpaceAfterUnaryPrefixOperator", unaryPrefixOperators, unaryPrefixExpressions, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterUnaryPreincrementOperator", SyntaxKind.PlusPlusToken, unaryPreincrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterUnaryPredecrementOperator", SyntaxKind.MinusMinusToken, unaryPredecrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeUnaryPostincrementOperator", unaryPostincrementExpressions, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isNotStatementConditionContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeUnaryPostdecrementOperator", unaryPostdecrementExpressions, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isNotStatementConditionContext], RuleAction.DeleteSpace), - - // More unary operator special-casing. - // DevDiv 181814: Be careful when removing leading whitespace - // around unary operators. Examples: - // 1 - -2 --X--> 1--2 - // a + ++b --X--> a+++b - rule("SpaceAfterPostincrementWhenFollowedByAdd", SyntaxKind.PlusPlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterAddWhenFollowedByUnaryPlus", SyntaxKind.PlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterAddWhenFollowedByPreincrement", SyntaxKind.PlusToken, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterPostdecrementWhenFollowedBySubtract", SyntaxKind.MinusMinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterSubtractWhenFollowedByUnaryMinus", SyntaxKind.MinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterSubtractWhenFollowedByPredecrement", SyntaxKind.MinusToken, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - - rule("NoSpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, [SyntaxKind.CommaToken, SyntaxKind.SemicolonToken], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - // For functions and control block place } on a new line [multi-line rule] - rule("NewLineBeforeCloseBraceInBlockContext", anyTokenIncludingMultilineComments, SyntaxKind.CloseBraceToken, [isMultilineBlockContext], RuleAction.InsertNewLine), - - // Space/new line after }. - rule("SpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, anyTokenExcept(SyntaxKind.CloseParenToken), [isNonJsxSameLineTokenContext, isAfterCodeBlockContext], RuleAction.InsertSpace), - // Special case for (}, else) and (}, while) since else & while tokens are not part of the tree which makes SpaceAfterCloseBrace rule not applied - // Also should not apply to }) - rule("SpaceBetweenCloseBraceAndElse", SyntaxKind.CloseBraceToken, SyntaxKind.ElseKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceBetweenCloseBraceAndWhile", SyntaxKind.CloseBraceToken, SyntaxKind.WhileKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.DeleteSpace), - - // Add a space after control dec context if the next character is an open bracket ex: 'if (false)[a, b] = [1, 2];' -> 'if (false) [a, b] = [1, 2];' - rule("SpaceAfterConditionalClosingParen", SyntaxKind.CloseParenToken, SyntaxKind.OpenBracketToken, [isControlDeclContext], RuleAction.InsertSpace), - - rule("NoSpaceBetweenFunctionKeywordAndStar", SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.DeleteSpace), - rule("SpaceAfterStarInGeneratorDeclaration", SyntaxKind.AsteriskToken, SyntaxKind.Identifier, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.InsertSpace), - - rule("SpaceAfterFunctionInFuncDecl", SyntaxKind.FunctionKeyword, anyToken, [isFunctionDeclContext], RuleAction.InsertSpace), - // Insert new line after { and before } in multi-line contexts. - rule("NewLineAfterOpenBraceInBlockContext", SyntaxKind.OpenBraceToken, anyToken, [isMultilineBlockContext], RuleAction.InsertNewLine), - - // For get/set members, we check for (identifier,identifier) since get/set don't have tokens and they are represented as just an identifier token. - // Though, we do extra check on the context to make sure we are dealing with get/set node. Example: - // get x() {} - // set x(val) {} - rule("SpaceAfterGetSetInMember", [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword], SyntaxKind.Identifier, [isFunctionDeclContext], RuleAction.InsertSpace), - - rule("NoSpaceBetweenYieldKeywordAndStar", SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.DeleteSpace), - rule("SpaceBetweenYieldOrYieldStarAndOperand", [SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken], anyToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.InsertSpace), - - rule("NoSpaceBetweenReturnAndSemicolon", SyntaxKind.ReturnKeyword, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("SpaceAfterCertainKeywords", [SyntaxKind.VarKeyword, SyntaxKind.ThrowKeyword, SyntaxKind.NewKeyword, SyntaxKind.DeleteKeyword, SyntaxKind.ReturnKeyword, SyntaxKind.TypeOfKeyword, SyntaxKind.AwaitKeyword], anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceAfterLetConstInVariableDeclaration", [SyntaxKind.LetKeyword, SyntaxKind.ConstKeyword], anyToken, [isNonJsxSameLineTokenContext, isStartOfVariableDeclarationList], RuleAction.InsertSpace), - rule("NoSpaceBeforeOpenParenInFuncCall", anyToken, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isFunctionCallOrNewContext, isPreviousTokenNotComma], RuleAction.DeleteSpace), - - // Special case for binary operators (that are keywords). For these we have to add a space and shouldn't follow any user options. - rule("SpaceBeforeBinaryKeywordOperator", anyToken, binaryKeywordOperators, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterBinaryKeywordOperator", binaryKeywordOperators, anyToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - - rule("SpaceAfterVoidOperator", SyntaxKind.VoidKeyword, anyToken, [isNonJsxSameLineTokenContext, isVoidOpContext], RuleAction.InsertSpace), - - // Async-await - rule("SpaceBetweenAsyncAndOpenParen", SyntaxKind.AsyncKeyword, SyntaxKind.OpenParenToken, [isArrowFunctionContext, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceBetweenAsyncAndFunctionKeyword", SyntaxKind.AsyncKeyword, [SyntaxKind.FunctionKeyword, SyntaxKind.Identifier], [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - - // Template string - rule("NoSpaceBetweenTagAndTemplateString", [SyntaxKind.Identifier, SyntaxKind.CloseParenToken], [SyntaxKind.NoSubstitutionTemplateLiteral, SyntaxKind.TemplateHead], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // JSX opening elements - rule("SpaceBeforeJsxAttribute", anyToken, SyntaxKind.Identifier, [isNextTokenParentJsxAttribute, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceBeforeSlashInJsxOpeningElement", anyToken, SyntaxKind.SlashToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceBeforeGreaterThanTokenInJsxOpeningElement", SyntaxKind.SlashToken, SyntaxKind.GreaterThanToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeEqualInJsxAttribute", anyToken, SyntaxKind.EqualsToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterEqualInJsxAttribute", SyntaxKind.EqualsToken, anyToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // TypeScript-specific rules - // Use of module as a function call. e.g.: import m2 = module("m2"); - rule("NoSpaceAfterModuleImport", [SyntaxKind.ModuleKeyword, SyntaxKind.RequireKeyword], SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - // Add a space around certain TypeScript keywords - rule( - "SpaceAfterCertainTypeScriptKeywords", - [ - SyntaxKind.AbstractKeyword, - SyntaxKind.ClassKeyword, - SyntaxKind.DeclareKeyword, - SyntaxKind.DefaultKeyword, - SyntaxKind.EnumKeyword, - SyntaxKind.ExportKeyword, - SyntaxKind.ExtendsKeyword, - SyntaxKind.GetKeyword, - SyntaxKind.ImplementsKeyword, - SyntaxKind.ImportKeyword, - SyntaxKind.InterfaceKeyword, - SyntaxKind.ModuleKeyword, - SyntaxKind.NamespaceKeyword, - SyntaxKind.PrivateKeyword, - SyntaxKind.PublicKeyword, - SyntaxKind.ProtectedKeyword, - SyntaxKind.ReadonlyKeyword, - SyntaxKind.SetKeyword, - SyntaxKind.StaticKeyword, - SyntaxKind.TypeKeyword, - SyntaxKind.FromKeyword, - SyntaxKind.KeyOfKeyword, - SyntaxKind.InferKeyword, - ], - anyToken, - [isNonJsxSameLineTokenContext], - RuleAction.InsertSpace), - rule( - "SpaceBeforeCertainTypeScriptKeywords", - anyToken, - [SyntaxKind.ExtendsKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.FromKeyword], - [isNonJsxSameLineTokenContext], - RuleAction.InsertSpace), - // Treat string literals in module names as identifiers, and add a space between the literal and the opening Brace braces, e.g.: module "m2" { - rule("SpaceAfterModuleName", SyntaxKind.StringLiteral, SyntaxKind.OpenBraceToken, [isModuleDeclContext], RuleAction.InsertSpace), - - // Lambda expressions - rule("SpaceBeforeArrow", anyToken, SyntaxKind.EqualsGreaterThanToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceAfterArrow", SyntaxKind.EqualsGreaterThanToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - - // Optional parameters and let args - rule("NoSpaceAfterEllipsis", SyntaxKind.DotDotDotToken, SyntaxKind.Identifier, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterOptionalParameters", SyntaxKind.QuestionToken, [SyntaxKind.CloseParenToken, SyntaxKind.CommaToken], [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace), - - // Remove spaces in empty interface literals. e.g.: x: {} - rule("NoSpaceBetweenEmptyInterfaceBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectTypeContext], RuleAction.DeleteSpace), - - // generics and type assertions - rule("NoSpaceBeforeOpenAngularBracket", typeNames, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), - rule("NoSpaceBetweenCloseParenAndAngularBracket", SyntaxKind.CloseParenToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterOpenAngularBracket", SyntaxKind.LessThanToken, anyToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeCloseAngularBracket", anyToken, SyntaxKind.GreaterThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterCloseAngularBracket", - SyntaxKind.GreaterThanToken, - [SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, SyntaxKind.GreaterThanToken, SyntaxKind.CommaToken], - [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext, isNotFunctionDeclContext /*To prevent an interference with the SpaceBeforeOpenParenInFuncDecl rule*/], - RuleAction.DeleteSpace), - - // decorators - rule("SpaceBeforeAt", [SyntaxKind.CloseParenToken, SyntaxKind.Identifier], SyntaxKind.AtToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceAfterAt", SyntaxKind.AtToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - // Insert space after @ in decorator - rule("SpaceAfterDecorator", - anyToken, - [ - SyntaxKind.AbstractKeyword, - SyntaxKind.Identifier, - SyntaxKind.ExportKeyword, - SyntaxKind.DefaultKeyword, - SyntaxKind.ClassKeyword, - SyntaxKind.StaticKeyword, - SyntaxKind.PublicKeyword, - SyntaxKind.PrivateKeyword, - SyntaxKind.ProtectedKeyword, - SyntaxKind.GetKeyword, - SyntaxKind.SetKeyword, - SyntaxKind.OpenBracketToken, - SyntaxKind.AsteriskToken, - ], - [isEndOfDecoratorContextOnSameLine], - RuleAction.InsertSpace), - - rule("NoSpaceBeforeNonNullAssertionOperator", anyToken, SyntaxKind.ExclamationToken, [isNonJsxSameLineTokenContext, isNonNullAssertionContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterNewKeywordOnConstructorSignature", SyntaxKind.NewKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isConstructorSignatureContext], RuleAction.DeleteSpace), - rule("SpaceLessThanAndNonJSXTypeAnnotation", SyntaxKind.LessThanToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - ]; - - // These rules are applied after high priority - const userConfigurableRules = [ - // Treat constructor as an identifier in a function declaration, and remove spaces between constructor and following left parentheses - rule("SpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - rule("SpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionEnabled("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNextTokenNotCloseBracket, isNextTokenNotCloseParen], RuleAction.InsertSpace), - rule("NoSpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext], RuleAction.DeleteSpace), - - // Insert space after function keyword for anonymous functions - rule("SpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.InsertSpace), - rule("NoSpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.DeleteSpace), - - // Insert space after keywords in control flow statements - rule("SpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.InsertSpace), - rule("NoSpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.DeleteSpace), - - // Insert space after opening and before closing nonempty parenthesis - rule("SpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceBetweenOpenParens", SyntaxKind.OpenParenToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceBetweenParens", SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // Insert space after opening and before closing nonempty brackets - rule("SpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceBetweenBrackets", SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}. - rule("SpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.InsertSpace), - rule("SpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.InsertSpace), - rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // Insert a space after opening and before closing empty brace brackets - rule("SpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingEmptyBraces")], RuleAction.InsertSpace), - rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingEmptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // Insert space after opening and before closing template string braces - rule("SpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxTextContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), - rule("SpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxTextContext], RuleAction.DeleteSpace, RuleFlags.CanDeleteNewLines), - rule("NoSpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // No space after { and before } in JSX expression - rule("SpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.InsertSpace), - rule("SpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.InsertSpace), - rule("NoSpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.DeleteSpace), - - // Insert space after semicolon in for statement - rule("SpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionEnabled("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.InsertSpace), - rule("NoSpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.DeleteSpace), - - // Insert space before and after binary operators - rule("SpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("NoSpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.DeleteSpace), - - rule("SpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.InsertSpace), - rule("NoSpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.DeleteSpace), - - // Open Brace braces after control block - rule("NewLineBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), - - // Open Brace braces after function - // TypeScript: Function can have return types, which can be made of tons of different token kinds - rule("NewLineBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), - // Open Brace braces after TypeScript module/class/interface - rule("NewLineBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), - - rule("SpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionEnabled("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.InsertSpace), - rule("NoSpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.DeleteSpace), - - rule("SpaceBeforeTypeAnnotation", anyToken, [SyntaxKind.QuestionToken, SyntaxKind.ColonToken], [isOptionEnabled("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.InsertSpace), - rule("NoSpaceBeforeTypeAnnotation", anyToken, [SyntaxKind.QuestionToken, SyntaxKind.ColonToken], [isOptionDisabledOrUndefined("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.DeleteSpace), - - rule("NoOptionalSemicolon", SyntaxKind.SemicolonToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Remove), isSemicolonDeletionContext], RuleAction.DeleteToken), - rule("OptionalSemicolon", anyToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Insert), isSemicolonInsertionContext], RuleAction.InsertTrailingSemicolon), - ]; - - // These rules are lower in priority than user-configurable. Rules earlier in this list have priority over rules later in the list. - const lowPriorityCommonRules = [ - // Space after keyword but not before ; or : or ? - rule("NoSpaceBeforeSemicolon", anyToken, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - rule("SpaceBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), - rule("SpaceBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), - rule("SpaceBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), - - rule("NoSpaceBeforeComma", anyToken, SyntaxKind.CommaToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // No space before and after indexer `x[]` - rule("NoSpaceBeforeOpenBracket", anyTokenExcept(SyntaxKind.AsyncKeyword, SyntaxKind.CaseKeyword), SyntaxKind.OpenBracketToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterCloseBracket", SyntaxKind.CloseBracketToken, anyToken, [isNonJsxSameLineTokenContext, isNotBeforeBlockInFunctionDeclarationContext], RuleAction.DeleteSpace), - rule("SpaceAfterSemicolon", SyntaxKind.SemicolonToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - - // Remove extra space between for and await - rule("SpaceBetweenForAndAwaitKeyword", SyntaxKind.ForKeyword, SyntaxKind.AwaitKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - - // Add a space between statements. All keywords except (do,else,case) has open/close parens after them. - // So, we have a rule to add a space for [),Any], [do,Any], [else,Any], and [case,Any] - rule( - "SpaceBetweenStatements", - [SyntaxKind.CloseParenToken, SyntaxKind.DoKeyword, SyntaxKind.ElseKeyword, SyntaxKind.CaseKeyword], - anyToken, - [isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNotForContext], - RuleAction.InsertSpace), - // This low-pri rule takes care of "try {", "catch {" and "finally {" in case the rule SpaceBeforeOpenBraceInControl didn't execute on FormatOnEnter. - rule("SpaceAfterTryCatchFinally", [SyntaxKind.TryKeyword, SyntaxKind.CatchKeyword, SyntaxKind.FinallyKeyword], SyntaxKind.OpenBraceToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - ]; - - return [ - ...highPriorityCommonRules, - ...userConfigurableRules, - ...lowPriorityCommonRules, - ]; } + function anyTokenExcept(...tokens: SyntaxKind[]): TokenRange { + return { tokens: allTokens.filter(t => !tokens.some(t2 => t2 === t)), isSpecific: false }; + } + + const anyToken: TokenRange = { tokens: allTokens, isSpecific: false }; + const anyTokenIncludingMultilineComments = tokenRangeFrom([...allTokens, SyntaxKind.MultiLineCommentTrivia]); + const anyTokenIncludingEOF = tokenRangeFrom([...allTokens, SyntaxKind.EndOfFileToken]); + const keywords = tokenRangeFromRange(SyntaxKind.FirstKeyword, SyntaxKind.LastKeyword); + const binaryOperators = tokenRangeFromRange(SyntaxKind.FirstBinaryOperator, SyntaxKind.LastBinaryOperator); + const binaryKeywordOperators = [SyntaxKind.InKeyword, SyntaxKind.InstanceOfKeyword, SyntaxKind.OfKeyword, SyntaxKind.AsKeyword, SyntaxKind.IsKeyword]; + const unaryPrefixOperators = [SyntaxKind.PlusPlusToken, SyntaxKind.MinusMinusToken, SyntaxKind.TildeToken, SyntaxKind.ExclamationToken]; + const unaryPrefixExpressions = [ + SyntaxKind.NumericLiteral, SyntaxKind.BigIntLiteral, SyntaxKind.Identifier, SyntaxKind.OpenParenToken, + SyntaxKind.OpenBracketToken, SyntaxKind.OpenBraceToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; + const unaryPreincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; + const unaryPostincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]; + const unaryPredecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; + const unaryPostdecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]; + const comments = [SyntaxKind.SingleLineCommentTrivia, SyntaxKind.MultiLineCommentTrivia]; + const typeNames = [SyntaxKind.Identifier, ...typeKeywords]; + + // Place a space before open brace in a function declaration + // TypeScript: Function can have return types, which can be made of tons of different token kinds + const functionOpenBraceLeftTokenRange = anyTokenIncludingMultilineComments; + + // Place a space before open brace in a TypeScript declaration that has braces as children (class, module, enum, etc) + const typeScriptOpenBraceLeftTokenRange = tokenRangeFrom([SyntaxKind.Identifier, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.ClassKeyword, SyntaxKind.ExportKeyword, SyntaxKind.ImportKeyword]); + + // Place a space before open brace in a control flow construct + const controlOpenBraceLeftTokenRange = tokenRangeFrom([SyntaxKind.CloseParenToken, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.DoKeyword, SyntaxKind.TryKeyword, SyntaxKind.FinallyKeyword, SyntaxKind.ElseKeyword]); + + // These rules are higher in priority than user-configurable + const highPriorityCommonRules = [ + // Leave comments alone + rule("IgnoreBeforeComment", anyToken, comments, anyContext, RuleAction.StopProcessingSpaceActions), + rule("IgnoreAfterLineComment", SyntaxKind.SingleLineCommentTrivia, anyToken, anyContext, RuleAction.StopProcessingSpaceActions), + + rule("NotSpaceBeforeColon", anyToken, SyntaxKind.ColonToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.DeleteSpace), + rule("SpaceAfterColon", SyntaxKind.ColonToken, anyToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeQuestionMark", anyToken, SyntaxKind.QuestionToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.DeleteSpace), + // insert space after '?' only when it is used in conditional operator + rule("SpaceAfterQuestionMarkInConditionalOperator", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext, isConditionalOperatorContext], RuleAction.InsertSpace), + + // in other cases there should be no space between '?' and next token + rule("NoSpaceAfterQuestionMark", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + rule("NoSpaceBeforeDot", anyToken, [SyntaxKind.DotToken, SyntaxKind.QuestionDotToken], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterDot", [SyntaxKind.DotToken, SyntaxKind.QuestionDotToken], anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + rule("NoSpaceBetweenImportParenInImportType", SyntaxKind.ImportKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isImportTypeContext], RuleAction.DeleteSpace), + + // Special handling of unary operators. + // Prefix operators generally shouldn't have a space between + // them and their target unary expression. + rule("NoSpaceAfterUnaryPrefixOperator", unaryPrefixOperators, unaryPrefixExpressions, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterUnaryPreincrementOperator", SyntaxKind.PlusPlusToken, unaryPreincrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterUnaryPredecrementOperator", SyntaxKind.MinusMinusToken, unaryPredecrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeUnaryPostincrementOperator", unaryPostincrementExpressions, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isNotStatementConditionContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeUnaryPostdecrementOperator", unaryPostdecrementExpressions, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isNotStatementConditionContext], RuleAction.DeleteSpace), + + // More unary operator special-casing. + // DevDiv 181814: Be careful when removing leading whitespace + // around unary operators. Examples: + // 1 - -2 --X--> 1--2 + // a + ++b --X--> a+++b + rule("SpaceAfterPostincrementWhenFollowedByAdd", SyntaxKind.PlusPlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterAddWhenFollowedByUnaryPlus", SyntaxKind.PlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterAddWhenFollowedByPreincrement", SyntaxKind.PlusToken, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterPostdecrementWhenFollowedBySubtract", SyntaxKind.MinusMinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterSubtractWhenFollowedByUnaryMinus", SyntaxKind.MinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterSubtractWhenFollowedByPredecrement", SyntaxKind.MinusToken, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + + rule("NoSpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, [SyntaxKind.CommaToken, SyntaxKind.SemicolonToken], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + // For functions and control block place } on a new line [multi-line rule] + rule("NewLineBeforeCloseBraceInBlockContext", anyTokenIncludingMultilineComments, SyntaxKind.CloseBraceToken, [isMultilineBlockContext], RuleAction.InsertNewLine), + + // Space/new line after }. + rule("SpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, anyTokenExcept(SyntaxKind.CloseParenToken), [isNonJsxSameLineTokenContext, isAfterCodeBlockContext], RuleAction.InsertSpace), + // Special case for (}, else) and (}, while) since else & while tokens are not part of the tree which makes SpaceAfterCloseBrace rule not applied + // Also should not apply to }) + rule("SpaceBetweenCloseBraceAndElse", SyntaxKind.CloseBraceToken, SyntaxKind.ElseKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBetweenCloseBraceAndWhile", SyntaxKind.CloseBraceToken, SyntaxKind.WhileKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.DeleteSpace), + + // Add a space after control dec context if the next character is an open bracket ex: 'if (false)[a, b] = [1, 2];' -> 'if (false) [a, b] = [1, 2];' + rule("SpaceAfterConditionalClosingParen", SyntaxKind.CloseParenToken, SyntaxKind.OpenBracketToken, [isControlDeclContext], RuleAction.InsertSpace), + + rule("NoSpaceBetweenFunctionKeywordAndStar", SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.DeleteSpace), + rule("SpaceAfterStarInGeneratorDeclaration", SyntaxKind.AsteriskToken, SyntaxKind.Identifier, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.InsertSpace), + + rule("SpaceAfterFunctionInFuncDecl", SyntaxKind.FunctionKeyword, anyToken, [isFunctionDeclContext], RuleAction.InsertSpace), + // Insert new line after { and before } in multi-line contexts. + rule("NewLineAfterOpenBraceInBlockContext", SyntaxKind.OpenBraceToken, anyToken, [isMultilineBlockContext], RuleAction.InsertNewLine), + + // For get/set members, we check for (identifier,identifier) since get/set don't have tokens and they are represented as just an identifier token. + // Though, we do extra check on the context to make sure we are dealing with get/set node. Example: + // get x() {} + // set x(val) {} + rule("SpaceAfterGetSetInMember", [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword], SyntaxKind.Identifier, [isFunctionDeclContext], RuleAction.InsertSpace), + + rule("NoSpaceBetweenYieldKeywordAndStar", SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.DeleteSpace), + rule("SpaceBetweenYieldOrYieldStarAndOperand", [SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken], anyToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.InsertSpace), + + rule("NoSpaceBetweenReturnAndSemicolon", SyntaxKind.ReturnKeyword, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("SpaceAfterCertainKeywords", [SyntaxKind.VarKeyword, SyntaxKind.ThrowKeyword, SyntaxKind.NewKeyword, SyntaxKind.DeleteKeyword, SyntaxKind.ReturnKeyword, SyntaxKind.TypeOfKeyword, SyntaxKind.AwaitKeyword], anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceAfterLetConstInVariableDeclaration", [SyntaxKind.LetKeyword, SyntaxKind.ConstKeyword], anyToken, [isNonJsxSameLineTokenContext, isStartOfVariableDeclarationList], RuleAction.InsertSpace), + rule("NoSpaceBeforeOpenParenInFuncCall", anyToken, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isFunctionCallOrNewContext, isPreviousTokenNotComma], RuleAction.DeleteSpace), + + // Special case for binary operators (that are keywords). For these we have to add a space and shouldn't follow any user options. + rule("SpaceBeforeBinaryKeywordOperator", anyToken, binaryKeywordOperators, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterBinaryKeywordOperator", binaryKeywordOperators, anyToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + + rule("SpaceAfterVoidOperator", SyntaxKind.VoidKeyword, anyToken, [isNonJsxSameLineTokenContext, isVoidOpContext], RuleAction.InsertSpace), + + // Async-await + rule("SpaceBetweenAsyncAndOpenParen", SyntaxKind.AsyncKeyword, SyntaxKind.OpenParenToken, [isArrowFunctionContext, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBetweenAsyncAndFunctionKeyword", SyntaxKind.AsyncKeyword, [SyntaxKind.FunctionKeyword, SyntaxKind.Identifier], [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + + // Template string + rule("NoSpaceBetweenTagAndTemplateString", [SyntaxKind.Identifier, SyntaxKind.CloseParenToken], [SyntaxKind.NoSubstitutionTemplateLiteral, SyntaxKind.TemplateHead], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // JSX opening elements + rule("SpaceBeforeJsxAttribute", anyToken, SyntaxKind.Identifier, [isNextTokenParentJsxAttribute, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBeforeSlashInJsxOpeningElement", anyToken, SyntaxKind.SlashToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeGreaterThanTokenInJsxOpeningElement", SyntaxKind.SlashToken, SyntaxKind.GreaterThanToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeEqualInJsxAttribute", anyToken, SyntaxKind.EqualsToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterEqualInJsxAttribute", SyntaxKind.EqualsToken, anyToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // TypeScript-specific rules + // Use of module as a function call. e.g.: import m2 = module("m2"); + rule("NoSpaceAfterModuleImport", [SyntaxKind.ModuleKeyword, SyntaxKind.RequireKeyword], SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + // Add a space around certain TypeScript keywords + rule( + "SpaceAfterCertainTypeScriptKeywords", + [ + SyntaxKind.AbstractKeyword, + SyntaxKind.ClassKeyword, + SyntaxKind.DeclareKeyword, + SyntaxKind.DefaultKeyword, + SyntaxKind.EnumKeyword, + SyntaxKind.ExportKeyword, + SyntaxKind.ExtendsKeyword, + SyntaxKind.GetKeyword, + SyntaxKind.ImplementsKeyword, + SyntaxKind.ImportKeyword, + SyntaxKind.InterfaceKeyword, + SyntaxKind.ModuleKeyword, + SyntaxKind.NamespaceKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.ReadonlyKeyword, + SyntaxKind.SetKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.TypeKeyword, + SyntaxKind.FromKeyword, + SyntaxKind.KeyOfKeyword, + SyntaxKind.InferKeyword, + ], + anyToken, + [isNonJsxSameLineTokenContext], + RuleAction.InsertSpace), + rule( + "SpaceBeforeCertainTypeScriptKeywords", + anyToken, + [SyntaxKind.ExtendsKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.FromKeyword], + [isNonJsxSameLineTokenContext], + RuleAction.InsertSpace), + // Treat string literals in module names as identifiers, and add a space between the literal and the opening Brace braces, e.g.: module "m2" { + rule("SpaceAfterModuleName", SyntaxKind.StringLiteral, SyntaxKind.OpenBraceToken, [isModuleDeclContext], RuleAction.InsertSpace), + + // Lambda expressions + rule("SpaceBeforeArrow", anyToken, SyntaxKind.EqualsGreaterThanToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceAfterArrow", SyntaxKind.EqualsGreaterThanToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + + // Optional parameters and let args + rule("NoSpaceAfterEllipsis", SyntaxKind.DotDotDotToken, SyntaxKind.Identifier, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOptionalParameters", SyntaxKind.QuestionToken, [SyntaxKind.CloseParenToken, SyntaxKind.CommaToken], [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace), + + // Remove spaces in empty interface literals. e.g.: x: {} + rule("NoSpaceBetweenEmptyInterfaceBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectTypeContext], RuleAction.DeleteSpace), + + // generics and type assertions + rule("NoSpaceBeforeOpenAngularBracket", typeNames, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceBetweenCloseParenAndAngularBracket", SyntaxKind.CloseParenToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOpenAngularBracket", SyntaxKind.LessThanToken, anyToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseAngularBracket", anyToken, SyntaxKind.GreaterThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterCloseAngularBracket", + SyntaxKind.GreaterThanToken, + [SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, SyntaxKind.GreaterThanToken, SyntaxKind.CommaToken], + [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext, isNotFunctionDeclContext /*To prevent an interference with the SpaceBeforeOpenParenInFuncDecl rule*/], + RuleAction.DeleteSpace), + + // decorators + rule("SpaceBeforeAt", [SyntaxKind.CloseParenToken, SyntaxKind.Identifier], SyntaxKind.AtToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceAfterAt", SyntaxKind.AtToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + // Insert space after @ in decorator + rule("SpaceAfterDecorator", + anyToken, + [ + SyntaxKind.AbstractKeyword, + SyntaxKind.Identifier, + SyntaxKind.ExportKeyword, + SyntaxKind.DefaultKeyword, + SyntaxKind.ClassKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.GetKeyword, + SyntaxKind.SetKeyword, + SyntaxKind.OpenBracketToken, + SyntaxKind.AsteriskToken, + ], + [isEndOfDecoratorContextOnSameLine], + RuleAction.InsertSpace), + + rule("NoSpaceBeforeNonNullAssertionOperator", anyToken, SyntaxKind.ExclamationToken, [isNonJsxSameLineTokenContext, isNonNullAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterNewKeywordOnConstructorSignature", SyntaxKind.NewKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isConstructorSignatureContext], RuleAction.DeleteSpace), + rule("SpaceLessThanAndNonJSXTypeAnnotation", SyntaxKind.LessThanToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + ]; + + // These rules are applied after high priority + const userConfigurableRules = [ + // Treat constructor as an identifier in a function declaration, and remove spaces between constructor and following left parentheses + rule("SpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + rule("SpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionEnabled("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNextTokenNotCloseBracket, isNextTokenNotCloseParen], RuleAction.InsertSpace), + rule("NoSpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext], RuleAction.DeleteSpace), + + // Insert space after function keyword for anonymous functions + rule("SpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.InsertSpace), + rule("NoSpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.DeleteSpace), + + // Insert space after keywords in control flow statements + rule("SpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.InsertSpace), + rule("NoSpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.DeleteSpace), + + // Insert space after opening and before closing nonempty parenthesis + rule("SpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBetweenOpenParens", SyntaxKind.OpenParenToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceBetweenParens", SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // Insert space after opening and before closing nonempty brackets + rule("SpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceBetweenBrackets", SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}. + rule("SpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.InsertSpace), + rule("SpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.InsertSpace), + rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // Insert a space after opening and before closing empty brace brackets + rule("SpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingEmptyBraces")], RuleAction.InsertSpace), + rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingEmptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // Insert space after opening and before closing template string braces + rule("SpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxTextContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), + rule("SpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxTextContext], RuleAction.DeleteSpace, RuleFlags.CanDeleteNewLines), + rule("NoSpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // No space after { and before } in JSX expression + rule("SpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.InsertSpace), + rule("SpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.InsertSpace), + rule("NoSpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.DeleteSpace), + + // Insert space after semicolon in for statement + rule("SpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionEnabled("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.InsertSpace), + rule("NoSpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.DeleteSpace), + + // Insert space before and after binary operators + rule("SpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.DeleteSpace), + + rule("SpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.DeleteSpace), + + // Open Brace braces after control block + rule("NewLineBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), + + // Open Brace braces after function + // TypeScript: Function can have return types, which can be made of tons of different token kinds + rule("NewLineBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), + // Open Brace braces after TypeScript module/class/interface + rule("NewLineBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), + + rule("SpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionEnabled("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.InsertSpace), + rule("NoSpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.DeleteSpace), + + rule("SpaceBeforeTypeAnnotation", anyToken, [SyntaxKind.QuestionToken, SyntaxKind.ColonToken], [isOptionEnabled("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeTypeAnnotation", anyToken, [SyntaxKind.QuestionToken, SyntaxKind.ColonToken], [isOptionDisabledOrUndefined("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.DeleteSpace), + + rule("NoOptionalSemicolon", SyntaxKind.SemicolonToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Remove), isSemicolonDeletionContext], RuleAction.DeleteToken), + rule("OptionalSemicolon", anyToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Insert), isSemicolonInsertionContext], RuleAction.InsertTrailingSemicolon), + ]; + + // These rules are lower in priority than user-configurable. Rules earlier in this list have priority over rules later in the list. + const lowPriorityCommonRules = [ + // Space after keyword but not before ; or : or ? + rule("NoSpaceBeforeSemicolon", anyToken, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + rule("SpaceBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), + rule("SpaceBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), + rule("SpaceBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), + + rule("NoSpaceBeforeComma", anyToken, SyntaxKind.CommaToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // No space before and after indexer `x[]` + rule("NoSpaceBeforeOpenBracket", anyTokenExcept(SyntaxKind.AsyncKeyword, SyntaxKind.CaseKeyword), SyntaxKind.OpenBracketToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterCloseBracket", SyntaxKind.CloseBracketToken, anyToken, [isNonJsxSameLineTokenContext, isNotBeforeBlockInFunctionDeclarationContext], RuleAction.DeleteSpace), + rule("SpaceAfterSemicolon", SyntaxKind.SemicolonToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + + // Remove extra space between for and await + rule("SpaceBetweenForAndAwaitKeyword", SyntaxKind.ForKeyword, SyntaxKind.AwaitKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + + // Add a space between statements. All keywords except (do,else,case) has open/close parens after them. + // So, we have a rule to add a space for [),Any], [do,Any], [else,Any], and [case,Any] + rule( + "SpaceBetweenStatements", + [SyntaxKind.CloseParenToken, SyntaxKind.DoKeyword, SyntaxKind.ElseKeyword, SyntaxKind.CaseKeyword], + anyToken, + [isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNotForContext], + RuleAction.InsertSpace), + // This low-pri rule takes care of "try {", "catch {" and "finally {" in case the rule SpaceBeforeOpenBraceInControl didn't execute on FormatOnEnter. + rule("SpaceAfterTryCatchFinally", [SyntaxKind.TryKeyword, SyntaxKind.CatchKeyword, SyntaxKind.FinallyKeyword], SyntaxKind.OpenBraceToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + ]; + + return [ + ...highPriorityCommonRules, + ...userConfigurableRules, + ...lowPriorityCommonRules, + ]; +} - /** - * A rule takes a two tokens (left/right) and a particular context - * for which you're meant to look at them. You then declare what should the - * whitespace annotation be between these tokens via the action param. - * - * @param debugName Name to print - * @param left The left side of the comparison - * @param right The right side of the comparison - * @param context A set of filters to narrow down the space in which this formatter rule applies - * @param action a declaration of the expected whitespace - * @param flags whether the rule deletes a line or not, defaults to no-op - */ - function rule( - debugName: string, - left: SyntaxKind | readonly SyntaxKind[] | TokenRange, - right: SyntaxKind | readonly SyntaxKind[] | TokenRange, - context: readonly ContextPredicate[], - action: RuleAction, - flags: RuleFlags = RuleFlags.None, - ): RuleSpec { - return { leftTokenRange: toTokenRange(left), rightTokenRange: toTokenRange(right), rule: { debugName, context, action, flags } }; - } +/** + * A rule takes a two tokens (left/right) and a particular context + * for which you're meant to look at them. You then declare what should the + * whitespace annotation be between these tokens via the action param. + * + * @param debugName Name to print + * @param left The left side of the comparison + * @param right The right side of the comparison + * @param context A set of filters to narrow down the space in which this formatter rule applies + * @param action a declaration of the expected whitespace + * @param flags whether the rule deletes a line or not, defaults to no-op + */ +function rule( + debugName: string, + left: SyntaxKind | readonly SyntaxKind[] | TokenRange, + right: SyntaxKind | readonly SyntaxKind[] | TokenRange, + context: readonly ContextPredicate[], + action: RuleAction, + flags: RuleFlags = RuleFlags.None, +): RuleSpec { + return { leftTokenRange: toTokenRange(left), rightTokenRange: toTokenRange(right), rule: { debugName, context, action, flags } }; +} - function tokenRangeFrom(tokens: readonly SyntaxKind[]): TokenRange { - return { tokens, isSpecific: true }; - } +function tokenRangeFrom(tokens: readonly SyntaxKind[]): TokenRange { + return { tokens, isSpecific: true }; +} - function toTokenRange(arg: SyntaxKind | readonly SyntaxKind[] | TokenRange): TokenRange { - return typeof arg === "number" ? tokenRangeFrom([arg]) : isArray(arg) ? tokenRangeFrom(arg) : arg; - } +function toTokenRange(arg: SyntaxKind | readonly SyntaxKind[] | TokenRange): TokenRange { + return typeof arg === "number" ? tokenRangeFrom([arg]) : isArray(arg) ? tokenRangeFrom(arg) : arg; +} - function tokenRangeFromRange(from: SyntaxKind, to: SyntaxKind, except: readonly SyntaxKind[] = []): TokenRange { - const tokens: SyntaxKind[] = []; - for (let token = from; token <= to; token++) { - if (!contains(except, token)) { - tokens.push(token); - } +function tokenRangeFromRange(from: SyntaxKind, to: SyntaxKind, except: readonly SyntaxKind[] = []): TokenRange { + const tokens: SyntaxKind[] = []; + for (let token = from; token <= to; token++) { + if (!contains(except, token)) { + tokens.push(token); } - return tokenRangeFrom(tokens); } + return tokenRangeFrom(tokens); +} - /// - /// Contexts - /// +/// +/// Contexts +/// - function optionEquals(optionName: K, optionValue: FormatCodeSettings[K]): (context: FormattingContext) => boolean { - return (context) => context.options && context.options[optionName] === optionValue; - } +function optionEquals(optionName: K, optionValue: FormatCodeSettings[K]): (context: FormattingContext) => boolean { + return (context) => context.options && context.options[optionName] === optionValue; +} - function isOptionEnabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => context.options && context.options.hasOwnProperty(optionName) && !!context.options[optionName]; - } +function isOptionEnabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => context.options && context.options.hasOwnProperty(optionName) && !!context.options[optionName]; +} - function isOptionDisabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => context.options && context.options.hasOwnProperty(optionName) && !context.options[optionName]; - } +function isOptionDisabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => context.options && context.options.hasOwnProperty(optionName) && !context.options[optionName]; +} - function isOptionDisabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !context.options[optionName]; - } +function isOptionDisabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !context.options[optionName]; +} - function isOptionDisabledOrUndefinedOrTokensOnSameLine(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !context.options[optionName] || context.TokensAreOnSameLine(); - } +function isOptionDisabledOrUndefinedOrTokensOnSameLine(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !context.options[optionName] || context.TokensAreOnSameLine(); +} - function isOptionEnabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !!context.options[optionName]; - } +function isOptionEnabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !!context.options[optionName]; +} - function isForContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ForStatement; - } +function isForContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ForStatement; +} - function isNotForContext(context: FormattingContext): boolean { - return !isForContext(context); - } +function isNotForContext(context: FormattingContext): boolean { + return !isForContext(context); +} - function isBinaryOpContext(context: FormattingContext): boolean { - switch (context.contextNode.kind) { - case SyntaxKind.BinaryExpression: - return (context.contextNode as BinaryExpression).operatorToken.kind !== SyntaxKind.CommaToken; - case SyntaxKind.ConditionalExpression: - case SyntaxKind.ConditionalType: - case SyntaxKind.AsExpression: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.TypePredicate: - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - return true; +function isBinaryOpContext(context: FormattingContext): boolean { + switch (context.contextNode.kind) { + case SyntaxKind.BinaryExpression: + return (context.contextNode as BinaryExpression).operatorToken.kind !== SyntaxKind.CommaToken; + case SyntaxKind.ConditionalExpression: + case SyntaxKind.ConditionalType: + case SyntaxKind.AsExpression: + case SyntaxKind.ExportSpecifier: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.TypePredicate: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + return true; - // equals in binding elements: function foo([[x, y] = [1, 2]]) - case SyntaxKind.BindingElement: - // equals in type X = ... - // falls through - case SyntaxKind.TypeAliasDeclaration: - // equal in import a = module('a'); - // falls through - case SyntaxKind.ImportEqualsDeclaration: - // equal in export = 1 - // falls through - case SyntaxKind.ExportAssignment: - // equal in let a = 0 - // falls through - case SyntaxKind.VariableDeclaration: - // equal in p = 0 - // falls through - case SyntaxKind.Parameter: - case SyntaxKind.EnumMember: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken; - // "in" keyword in for (let x in []) { } - case SyntaxKind.ForInStatement: - // "in" keyword in [P in keyof T]: T[P] - // falls through - case SyntaxKind.TypeParameter: - return context.currentTokenSpan.kind === SyntaxKind.InKeyword || context.nextTokenSpan.kind === SyntaxKind.InKeyword || context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken; - // Technically, "of" is not a binary operator, but format it the same way as "in" - case SyntaxKind.ForOfStatement: - return context.currentTokenSpan.kind === SyntaxKind.OfKeyword || context.nextTokenSpan.kind === SyntaxKind.OfKeyword; - } - return false; - } + // equals in binding elements: function foo([[x, y] = [1, 2]]) + case SyntaxKind.BindingElement: + // equals in type X = ... + // falls through + case SyntaxKind.TypeAliasDeclaration: + // equal in import a = module('a'); + // falls through + case SyntaxKind.ImportEqualsDeclaration: + // equal in export = 1 + // falls through + case SyntaxKind.ExportAssignment: + // equal in let a = 0 + // falls through + case SyntaxKind.VariableDeclaration: + // equal in p = 0 + // falls through + case SyntaxKind.Parameter: + case SyntaxKind.EnumMember: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken; + // "in" keyword in for (let x in []) { } + case SyntaxKind.ForInStatement: + // "in" keyword in [P in keyof T]: T[P] + // falls through + case SyntaxKind.TypeParameter: + return context.currentTokenSpan.kind === SyntaxKind.InKeyword || context.nextTokenSpan.kind === SyntaxKind.InKeyword || context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken; + // Technically, "of" is not a binary operator, but format it the same way as "in" + case SyntaxKind.ForOfStatement: + return context.currentTokenSpan.kind === SyntaxKind.OfKeyword || context.nextTokenSpan.kind === SyntaxKind.OfKeyword; + } + return false; +} - function isNotBinaryOpContext(context: FormattingContext): boolean { - return !isBinaryOpContext(context); - } +function isNotBinaryOpContext(context: FormattingContext): boolean { + return !isBinaryOpContext(context); +} - function isNotTypeAnnotationContext(context: FormattingContext): boolean { - return !isTypeAnnotationContext(context); - } +function isNotTypeAnnotationContext(context: FormattingContext): boolean { + return !isTypeAnnotationContext(context); +} - function isTypeAnnotationContext(context: FormattingContext): boolean { - const contextKind = context.contextNode.kind; - return contextKind === SyntaxKind.PropertyDeclaration || - contextKind === SyntaxKind.PropertySignature || - contextKind === SyntaxKind.Parameter || - contextKind === SyntaxKind.VariableDeclaration || - isFunctionLikeKind(contextKind); - } +function isTypeAnnotationContext(context: FormattingContext): boolean { + const contextKind = context.contextNode.kind; + return contextKind === SyntaxKind.PropertyDeclaration || + contextKind === SyntaxKind.PropertySignature || + contextKind === SyntaxKind.Parameter || + contextKind === SyntaxKind.VariableDeclaration || + isFunctionLikeKind(contextKind); +} - function isConditionalOperatorContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ConditionalExpression || - context.contextNode.kind === SyntaxKind.ConditionalType; - } +function isConditionalOperatorContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ConditionalExpression || + context.contextNode.kind === SyntaxKind.ConditionalType; +} - function isSameLineTokenOrBeforeBlockContext(context: FormattingContext): boolean { - return context.TokensAreOnSameLine() || isBeforeBlockContext(context); - } +function isSameLineTokenOrBeforeBlockContext(context: FormattingContext): boolean { + return context.TokensAreOnSameLine() || isBeforeBlockContext(context); +} - function isBraceWrappedContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ObjectBindingPattern || - context.contextNode.kind === SyntaxKind.MappedType || - isSingleLineBlockContext(context); - } +function isBraceWrappedContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ObjectBindingPattern || + context.contextNode.kind === SyntaxKind.MappedType || + isSingleLineBlockContext(context); +} - // This check is done before an open brace in a control construct, a function, or a typescript block declaration - function isBeforeMultilineBlockContext(context: FormattingContext): boolean { - return isBeforeBlockContext(context) && !(context.NextNodeAllOnSameLine() || context.NextNodeBlockIsOnOneLine()); - } +// This check is done before an open brace in a control construct, a function, or a typescript block declaration +function isBeforeMultilineBlockContext(context: FormattingContext): boolean { + return isBeforeBlockContext(context) && !(context.NextNodeAllOnSameLine() || context.NextNodeBlockIsOnOneLine()); +} - function isMultilineBlockContext(context: FormattingContext): boolean { - return isBlockContext(context) && !(context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine()); - } +function isMultilineBlockContext(context: FormattingContext): boolean { + return isBlockContext(context) && !(context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine()); +} - function isSingleLineBlockContext(context: FormattingContext): boolean { - return isBlockContext(context) && (context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine()); - } +function isSingleLineBlockContext(context: FormattingContext): boolean { + return isBlockContext(context) && (context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine()); +} - function isBlockContext(context: FormattingContext): boolean { - return nodeIsBlockContext(context.contextNode); - } +function isBlockContext(context: FormattingContext): boolean { + return nodeIsBlockContext(context.contextNode); +} - function isBeforeBlockContext(context: FormattingContext): boolean { - return nodeIsBlockContext(context.nextTokenParent); +function isBeforeBlockContext(context: FormattingContext): boolean { + return nodeIsBlockContext(context.nextTokenParent); +} + +// IMPORTANT!!! This method must return true ONLY for nodes with open and close braces as immediate children +function nodeIsBlockContext(node: Node): boolean { + if (nodeIsTypeScriptDeclWithBlockContext(node)) { + // This means we are in a context that looks like a block to the user, but in the grammar is actually not a node (it's a class, module, enum, object type literal, etc). + return true; } - // IMPORTANT!!! This method must return true ONLY for nodes with open and close braces as immediate children - function nodeIsBlockContext(node: Node): boolean { - if (nodeIsTypeScriptDeclWithBlockContext(node)) { - // This means we are in a context that looks like a block to the user, but in the grammar is actually not a node (it's a class, module, enum, object type literal, etc). + switch (node.kind) { + case SyntaxKind.Block: + case SyntaxKind.CaseBlock: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ModuleBlock: return true; - } - - switch (node.kind) { - case SyntaxKind.Block: - case SyntaxKind.CaseBlock: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ModuleBlock: - return true; - } - - return false; } - function isFunctionDeclContext(context: FormattingContext): boolean { - switch (context.contextNode.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - // case SyntaxKind.MemberFunctionDeclaration: - // falls through - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // case SyntaxKind.MethodSignature: - // falls through - case SyntaxKind.CallSignature: - case SyntaxKind.FunctionExpression: - case SyntaxKind.Constructor: - case SyntaxKind.ArrowFunction: - // case SyntaxKind.ConstructorDeclaration: - // case SyntaxKind.SimpleArrowFunctionExpression: - // case SyntaxKind.ParenthesizedArrowFunctionExpression: - // falls through - case SyntaxKind.InterfaceDeclaration: // This one is not truly a function, but for formatting purposes, it acts just like one - return true; - } + return false; +} - return false; +function isFunctionDeclContext(context: FormattingContext): boolean { + switch (context.contextNode.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + // case SyntaxKind.MemberFunctionDeclaration: + // falls through + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // case SyntaxKind.MethodSignature: + // falls through + case SyntaxKind.CallSignature: + case SyntaxKind.FunctionExpression: + case SyntaxKind.Constructor: + case SyntaxKind.ArrowFunction: + // case SyntaxKind.ConstructorDeclaration: + // case SyntaxKind.SimpleArrowFunctionExpression: + // case SyntaxKind.ParenthesizedArrowFunctionExpression: + // falls through + case SyntaxKind.InterfaceDeclaration: // This one is not truly a function, but for formatting purposes, it acts just like one + return true; } - function isNotFunctionDeclContext(context: FormattingContext): boolean { - return !isFunctionDeclContext(context); - } + return false; +} - function isFunctionDeclarationOrFunctionExpressionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.FunctionDeclaration || context.contextNode.kind === SyntaxKind.FunctionExpression; - } +function isNotFunctionDeclContext(context: FormattingContext): boolean { + return !isFunctionDeclContext(context); +} - function isTypeScriptDeclWithBlockContext(context: FormattingContext): boolean { - return nodeIsTypeScriptDeclWithBlockContext(context.contextNode); - } +function isFunctionDeclarationOrFunctionExpressionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.FunctionDeclaration || context.contextNode.kind === SyntaxKind.FunctionExpression; +} - function nodeIsTypeScriptDeclWithBlockContext(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeLiteral: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.NamedExports: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.NamedImports: - return true; - } +function isTypeScriptDeclWithBlockContext(context: FormattingContext): boolean { + return nodeIsTypeScriptDeclWithBlockContext(context.contextNode); +} - return false; +function nodeIsTypeScriptDeclWithBlockContext(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeLiteral: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.NamedExports: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.NamedImports: + return true; } - function isAfterCodeBlockContext(context: FormattingContext): boolean { - switch (context.currentTokenParent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.CatchClause: - case SyntaxKind.ModuleBlock: - case SyntaxKind.SwitchStatement: - return true; - case SyntaxKind.Block: { - const blockParent = context.currentTokenParent.parent; - // In a codefix scenario, we can't rely on parents being set. So just always return true. - if (!blockParent || blockParent.kind !== SyntaxKind.ArrowFunction && blockParent.kind !== SyntaxKind.FunctionExpression) { - return true; - } - } - } - return false; - } + return false; +} - function isControlDeclContext(context: FormattingContext): boolean { - switch (context.contextNode.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.SwitchStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.TryStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WithStatement: - // TODO - // case SyntaxKind.ElseClause: - // falls through - case SyntaxKind.CatchClause: +function isAfterCodeBlockContext(context: FormattingContext): boolean { + switch (context.currentTokenParent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.CatchClause: + case SyntaxKind.ModuleBlock: + case SyntaxKind.SwitchStatement: + return true; + case SyntaxKind.Block: { + const blockParent = context.currentTokenParent.parent; + // In a codefix scenario, we can't rely on parents being set. So just always return true. + if (!blockParent || blockParent.kind !== SyntaxKind.ArrowFunction && blockParent.kind !== SyntaxKind.FunctionExpression) { return true; - - default: - return false; + } } } + return false; +} - function isObjectContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ObjectLiteralExpression; - } - - function isFunctionCallContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.CallExpression; - } +function isControlDeclContext(context: FormattingContext): boolean { + switch (context.contextNode.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WithStatement: + // TODO + // case SyntaxKind.ElseClause: + // falls through + case SyntaxKind.CatchClause: + return true; - function isNewContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.NewExpression; + default: + return false; } +} - function isFunctionCallOrNewContext(context: FormattingContext): boolean { - return isFunctionCallContext(context) || isNewContext(context); - } +function isObjectContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ObjectLiteralExpression; +} - function isPreviousTokenNotComma(context: FormattingContext): boolean { - return context.currentTokenSpan.kind !== SyntaxKind.CommaToken; - } +function isFunctionCallContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.CallExpression; +} - function isNextTokenNotCloseBracket(context: FormattingContext): boolean { - return context.nextTokenSpan.kind !== SyntaxKind.CloseBracketToken; - } +function isNewContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.NewExpression; +} - function isNextTokenNotCloseParen(context: FormattingContext): boolean { - return context.nextTokenSpan.kind !== SyntaxKind.CloseParenToken; - } +function isFunctionCallOrNewContext(context: FormattingContext): boolean { + return isFunctionCallContext(context) || isNewContext(context); +} - function isArrowFunctionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ArrowFunction; - } +function isPreviousTokenNotComma(context: FormattingContext): boolean { + return context.currentTokenSpan.kind !== SyntaxKind.CommaToken; +} - function isImportTypeContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ImportType; - } +function isNextTokenNotCloseBracket(context: FormattingContext): boolean { + return context.nextTokenSpan.kind !== SyntaxKind.CloseBracketToken; +} - function isNonJsxSameLineTokenContext(context: FormattingContext): boolean { - return context.TokensAreOnSameLine() && context.contextNode.kind !== SyntaxKind.JsxText; - } +function isNextTokenNotCloseParen(context: FormattingContext): boolean { + return context.nextTokenSpan.kind !== SyntaxKind.CloseParenToken; +} - function isNonJsxTextContext(context: FormattingContext): boolean { - return context.contextNode.kind !== SyntaxKind.JsxText; - } +function isArrowFunctionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ArrowFunction; +} - function isNonJsxElementOrFragmentContext(context: FormattingContext): boolean { - return context.contextNode.kind !== SyntaxKind.JsxElement && context.contextNode.kind !== SyntaxKind.JsxFragment; - } +function isImportTypeContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ImportType; +} - function isJsxExpressionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.JsxExpression || context.contextNode.kind === SyntaxKind.JsxSpreadAttribute; - } +function isNonJsxSameLineTokenContext(context: FormattingContext): boolean { + return context.TokensAreOnSameLine() && context.contextNode.kind !== SyntaxKind.JsxText; +} - function isNextTokenParentJsxAttribute(context: FormattingContext): boolean { - return context.nextTokenParent.kind === SyntaxKind.JsxAttribute; - } +function isNonJsxTextContext(context: FormattingContext): boolean { + return context.contextNode.kind !== SyntaxKind.JsxText; +} - function isJsxAttributeContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.JsxAttribute; - } +function isNonJsxElementOrFragmentContext(context: FormattingContext): boolean { + return context.contextNode.kind !== SyntaxKind.JsxElement && context.contextNode.kind !== SyntaxKind.JsxFragment; +} - function isJsxSelfClosingElementContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.JsxSelfClosingElement; - } +function isJsxExpressionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.JsxExpression || context.contextNode.kind === SyntaxKind.JsxSpreadAttribute; +} - function isNotBeforeBlockInFunctionDeclarationContext(context: FormattingContext): boolean { - return !isFunctionDeclContext(context) && !isBeforeBlockContext(context); - } +function isNextTokenParentJsxAttribute(context: FormattingContext): boolean { + return context.nextTokenParent.kind === SyntaxKind.JsxAttribute; +} - function isEndOfDecoratorContextOnSameLine(context: FormattingContext): boolean { - return context.TokensAreOnSameLine() && - !!context.contextNode.decorators && - nodeIsInDecoratorContext(context.currentTokenParent) && - !nodeIsInDecoratorContext(context.nextTokenParent); - } +function isJsxAttributeContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.JsxAttribute; +} - function nodeIsInDecoratorContext(node: Node): boolean { - while (isExpressionNode(node)) { - node = node.parent; - } - return node.kind === SyntaxKind.Decorator; - } +function isJsxSelfClosingElementContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.JsxSelfClosingElement; +} - function isStartOfVariableDeclarationList(context: FormattingContext): boolean { - return context.currentTokenParent.kind === SyntaxKind.VariableDeclarationList && - context.currentTokenParent.getStart(context.sourceFile) === context.currentTokenSpan.pos; - } +function isNotBeforeBlockInFunctionDeclarationContext(context: FormattingContext): boolean { + return !isFunctionDeclContext(context) && !isBeforeBlockContext(context); +} - function isNotFormatOnEnter(context: FormattingContext): boolean { - return context.formattingRequestKind !== FormattingRequestKind.FormatOnEnter; - } +function isEndOfDecoratorContextOnSameLine(context: FormattingContext): boolean { + return context.TokensAreOnSameLine() && + !!context.contextNode.decorators && + nodeIsInDecoratorContext(context.currentTokenParent) && + !nodeIsInDecoratorContext(context.nextTokenParent); +} - function isModuleDeclContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ModuleDeclaration; +function nodeIsInDecoratorContext(node: Node): boolean { + while (isExpressionNode(node)) { + node = node.parent; } + return node.kind === SyntaxKind.Decorator; +} - function isObjectTypeContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.TypeLiteral; // && context.contextNode.parent.kind !== SyntaxKind.InterfaceDeclaration; - } +function isStartOfVariableDeclarationList(context: FormattingContext): boolean { + return context.currentTokenParent.kind === SyntaxKind.VariableDeclarationList && + context.currentTokenParent.getStart(context.sourceFile) === context.currentTokenSpan.pos; +} - function isConstructorSignatureContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ConstructSignature; - } +function isNotFormatOnEnter(context: FormattingContext): boolean { + return context.formattingRequestKind !== FormattingRequestKind.FormatOnEnter; +} - function isTypeArgumentOrParameterOrAssertion(token: TextRangeWithKind, parent: Node): boolean { - if (token.kind !== SyntaxKind.LessThanToken && token.kind !== SyntaxKind.GreaterThanToken) { - return false; - } - switch (parent.kind) { - case SyntaxKind.TypeReference: - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.ExpressionWithTypeArguments: - return true; - default: - return false; +function isModuleDeclContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ModuleDeclaration; +} - } - } +function isObjectTypeContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.TypeLiteral; // && context.contextNode.parent.kind !== SyntaxKind.InterfaceDeclaration; +} - function isTypeArgumentOrParameterOrAssertionContext(context: FormattingContext): boolean { - return isTypeArgumentOrParameterOrAssertion(context.currentTokenSpan, context.currentTokenParent) || - isTypeArgumentOrParameterOrAssertion(context.nextTokenSpan, context.nextTokenParent); - } +function isConstructorSignatureContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ConstructSignature; +} - function isTypeAssertionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.TypeAssertionExpression; +function isTypeArgumentOrParameterOrAssertion(token: TextRangeWithKind, parent: Node): boolean { + if (token.kind !== SyntaxKind.LessThanToken && token.kind !== SyntaxKind.GreaterThanToken) { + return false; } + switch (parent.kind) { + case SyntaxKind.TypeReference: + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.ExpressionWithTypeArguments: + return true; + default: + return false; - function isVoidOpContext(context: FormattingContext): boolean { - return context.currentTokenSpan.kind === SyntaxKind.VoidKeyword && context.currentTokenParent.kind === SyntaxKind.VoidExpression; } +} - function isYieldOrYieldStarWithOperand(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.YieldExpression && (context.contextNode as YieldExpression).expression !== undefined; - } +function isTypeArgumentOrParameterOrAssertionContext(context: FormattingContext): boolean { + return isTypeArgumentOrParameterOrAssertion(context.currentTokenSpan, context.currentTokenParent) || + isTypeArgumentOrParameterOrAssertion(context.nextTokenSpan, context.nextTokenParent); +} - function isNonNullAssertionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.NonNullExpression; - } +function isTypeAssertionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.TypeAssertionExpression; +} - function isNotStatementConditionContext(context: FormattingContext): boolean { - return !isStatementConditionContext(context); - } +function isVoidOpContext(context: FormattingContext): boolean { + return context.currentTokenSpan.kind === SyntaxKind.VoidKeyword && context.currentTokenParent.kind === SyntaxKind.VoidExpression; +} - function isStatementConditionContext(context: FormattingContext): boolean { - switch (context.contextNode.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - return true; +function isYieldOrYieldStarWithOperand(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.YieldExpression && (context.contextNode as YieldExpression).expression !== undefined; +} - default: - return false; - } - } +function isNonNullAssertionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.NonNullExpression; +} - function isSemicolonDeletionContext(context: FormattingContext): boolean { - let nextTokenKind = context.nextTokenSpan.kind; - let nextTokenStart = context.nextTokenSpan.pos; - if (isTrivia(nextTokenKind)) { - const nextRealToken = context.nextTokenParent === context.currentTokenParent - ? findNextToken( - context.currentTokenParent, - findAncestor(context.currentTokenParent, a => !a.parent)!, - context.sourceFile) - : context.nextTokenParent.getFirstToken(context.sourceFile); - if (!nextRealToken) { - return true; - } - nextTokenKind = nextRealToken.kind; - nextTokenStart = nextRealToken.getStart(context.sourceFile); - } +function isNotStatementConditionContext(context: FormattingContext): boolean { + return !isStatementConditionContext(context); +} - const startLine = context.sourceFile.getLineAndCharacterOfPosition(context.currentTokenSpan.pos).line; - const endLine = context.sourceFile.getLineAndCharacterOfPosition(nextTokenStart).line; - if (startLine === endLine) { - return nextTokenKind === SyntaxKind.CloseBraceToken - || nextTokenKind === SyntaxKind.EndOfFileToken; - } +function isStatementConditionContext(context: FormattingContext): boolean { + switch (context.contextNode.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + return true; - if (nextTokenKind === SyntaxKind.SemicolonClassElement || - nextTokenKind === SyntaxKind.SemicolonToken - ) { + default: return false; - } - - if (context.contextNode.kind === SyntaxKind.InterfaceDeclaration || - context.contextNode.kind === SyntaxKind.TypeAliasDeclaration - ) { - // Can’t remove semicolon after `foo`; it would parse as a method declaration: - // - // interface I { - // foo; - // (): void - // } - return !isPropertySignature(context.currentTokenParent) - || !!context.currentTokenParent.type - || nextTokenKind !== SyntaxKind.OpenParenToken; - } + } +} - if (isPropertyDeclaration(context.currentTokenParent)) { - return !context.currentTokenParent.initializer; +function isSemicolonDeletionContext(context: FormattingContext): boolean { + let nextTokenKind = context.nextTokenSpan.kind; + let nextTokenStart = context.nextTokenSpan.pos; + if (isTrivia(nextTokenKind)) { + const nextRealToken = context.nextTokenParent === context.currentTokenParent + ? findNextToken( + context.currentTokenParent, + findAncestor(context.currentTokenParent, a => !a.parent)!, + context.sourceFile) + : context.nextTokenParent.getFirstToken(context.sourceFile); + if (!nextRealToken) { + return true; } + nextTokenKind = nextRealToken.kind; + nextTokenStart = nextRealToken.getStart(context.sourceFile); + } - return context.currentTokenParent.kind !== SyntaxKind.ForStatement - && context.currentTokenParent.kind !== SyntaxKind.EmptyStatement - && context.currentTokenParent.kind !== SyntaxKind.SemicolonClassElement - && nextTokenKind !== SyntaxKind.OpenBracketToken - && nextTokenKind !== SyntaxKind.OpenParenToken - && nextTokenKind !== SyntaxKind.PlusToken - && nextTokenKind !== SyntaxKind.MinusToken - && nextTokenKind !== SyntaxKind.SlashToken - && nextTokenKind !== SyntaxKind.RegularExpressionLiteral - && nextTokenKind !== SyntaxKind.CommaToken - && nextTokenKind !== SyntaxKind.TemplateExpression - && nextTokenKind !== SyntaxKind.TemplateHead - && nextTokenKind !== SyntaxKind.NoSubstitutionTemplateLiteral - && nextTokenKind !== SyntaxKind.DotToken; + const startLine = context.sourceFile.getLineAndCharacterOfPosition(context.currentTokenSpan.pos).line; + const endLine = context.sourceFile.getLineAndCharacterOfPosition(nextTokenStart).line; + if (startLine === endLine) { + return nextTokenKind === SyntaxKind.CloseBraceToken + || nextTokenKind === SyntaxKind.EndOfFileToken; } - function isSemicolonInsertionContext(context: FormattingContext): boolean { - return positionIsASICandidate(context.currentTokenSpan.end, context.currentTokenParent, context.sourceFile); + if (nextTokenKind === SyntaxKind.SemicolonClassElement || + nextTokenKind === SyntaxKind.SemicolonToken + ) { + return false; } + + if (context.contextNode.kind === SyntaxKind.InterfaceDeclaration || + context.contextNode.kind === SyntaxKind.TypeAliasDeclaration + ) { + // Can’t remove semicolon after `foo`; it would parse as a method declaration: + // + // interface I { + // foo; + // (): void + // } + return !isPropertySignature(context.currentTokenParent) + || !!context.currentTokenParent.type + || nextTokenKind !== SyntaxKind.OpenParenToken; + } + + if (isPropertyDeclaration(context.currentTokenParent)) { + return !context.currentTokenParent.initializer; + } + + return context.currentTokenParent.kind !== SyntaxKind.ForStatement + && context.currentTokenParent.kind !== SyntaxKind.EmptyStatement + && context.currentTokenParent.kind !== SyntaxKind.SemicolonClassElement + && nextTokenKind !== SyntaxKind.OpenBracketToken + && nextTokenKind !== SyntaxKind.OpenParenToken + && nextTokenKind !== SyntaxKind.PlusToken + && nextTokenKind !== SyntaxKind.MinusToken + && nextTokenKind !== SyntaxKind.SlashToken + && nextTokenKind !== SyntaxKind.RegularExpressionLiteral + && nextTokenKind !== SyntaxKind.CommaToken + && nextTokenKind !== SyntaxKind.TemplateExpression + && nextTokenKind !== SyntaxKind.TemplateHead + && nextTokenKind !== SyntaxKind.NoSubstitutionTemplateLiteral + && nextTokenKind !== SyntaxKind.DotToken; +} + +function isSemicolonInsertionContext(context: FormattingContext): boolean { + return positionIsASICandidate(context.currentTokenSpan.end, context.currentTokenParent, context.sourceFile); +} } diff --git a/src/services/formatting/rulesMap.ts b/src/services/formatting/rulesMap.ts index ccee491040fc6..a0d337012a4f6 100644 --- a/src/services/formatting/rulesMap.ts +++ b/src/services/formatting/rulesMap.ts @@ -1,140 +1,140 @@ /* @internal */ namespace ts.formatting { - export function getFormatContext(options: FormatCodeSettings, host: FormattingHost): FormatContext { - return { options, getRules: getRulesMap(), host }; - } +export function getFormatContext(options: FormatCodeSettings, host: FormattingHost): FormatContext { + return { options, getRules: getRulesMap(), host }; +} - let rulesMapCache: RulesMap | undefined; +let rulesMapCache: RulesMap | undefined; - function getRulesMap(): RulesMap { - if (rulesMapCache === undefined) { - rulesMapCache = createRulesMap(getAllRules()); - } - return rulesMapCache; +function getRulesMap(): RulesMap { + if (rulesMapCache === undefined) { + rulesMapCache = createRulesMap(getAllRules()); } + return rulesMapCache; +} - /** - * For a given rule action, gets a mask of other rule actions that - * cannot be applied at the same position. - */ - function getRuleActionExclusion(ruleAction: RuleAction): RuleAction { - let mask: RuleAction = 0; - if (ruleAction & RuleAction.StopProcessingSpaceActions) { - mask |= RuleAction.ModifySpaceAction; - } - if (ruleAction & RuleAction.StopProcessingTokenActions) { - mask |= RuleAction.ModifyTokenAction; - } - if (ruleAction & RuleAction.ModifySpaceAction) { - mask |= RuleAction.ModifySpaceAction; - } - if (ruleAction & RuleAction.ModifyTokenAction) { - mask |= RuleAction.ModifyTokenAction; - } - return mask; +/** + * For a given rule action, gets a mask of other rule actions that + * cannot be applied at the same position. + */ +function getRuleActionExclusion(ruleAction: RuleAction): RuleAction { + let mask: RuleAction = 0; + if (ruleAction & RuleAction.StopProcessingSpaceActions) { + mask |= RuleAction.ModifySpaceAction; + } + if (ruleAction & RuleAction.StopProcessingTokenActions) { + mask |= RuleAction.ModifyTokenAction; + } + if (ruleAction & RuleAction.ModifySpaceAction) { + mask |= RuleAction.ModifySpaceAction; + } + if (ruleAction & RuleAction.ModifyTokenAction) { + mask |= RuleAction.ModifyTokenAction; } + return mask; +} - export type RulesMap = (context: FormattingContext) => readonly Rule[] | undefined; - function createRulesMap(rules: readonly RuleSpec[]): RulesMap { - const map = buildMap(rules); - return context => { - const bucket = map[getRuleBucketIndex(context.currentTokenSpan.kind, context.nextTokenSpan.kind)]; - if (bucket) { - const rules: Rule[] = []; - let ruleActionMask: RuleAction = 0; - for (const rule of bucket) { - const acceptRuleActions = ~getRuleActionExclusion(ruleActionMask); - if (rule.action & acceptRuleActions && every(rule.context, c => c(context))) { - rules.push(rule); - ruleActionMask |= rule.action; - } - } - if (rules.length) { - return rules; +export type RulesMap = (context: FormattingContext) => readonly Rule[] | undefined; +function createRulesMap(rules: readonly RuleSpec[]): RulesMap { + const map = buildMap(rules); + return context => { + const bucket = map[getRuleBucketIndex(context.currentTokenSpan.kind, context.nextTokenSpan.kind)]; + if (bucket) { + const rules: Rule[] = []; + let ruleActionMask: RuleAction = 0; + for (const rule of bucket) { + const acceptRuleActions = ~getRuleActionExclusion(ruleActionMask); + if (rule.action & acceptRuleActions && every(rule.context, c => c(context))) { + rules.push(rule); + ruleActionMask |= rule.action; } } - }; - } + if (rules.length) { + return rules; + } + } + }; +} - function buildMap(rules: readonly RuleSpec[]): readonly (readonly Rule[])[] { - // Map from bucket index to array of rules - const map: Rule[][] = new Array(mapRowLength * mapRowLength); - // This array is used only during construction of the rulesbucket in the map - const rulesBucketConstructionStateList = new Array(map.length); - for (const rule of rules) { - const specificRule = rule.leftTokenRange.isSpecific && rule.rightTokenRange.isSpecific; +function buildMap(rules: readonly RuleSpec[]): readonly (readonly Rule[])[] { + // Map from bucket index to array of rules + const map: Rule[][] = new Array(mapRowLength * mapRowLength); + // This array is used only during construction of the rulesbucket in the map + const rulesBucketConstructionStateList = new Array(map.length); + for (const rule of rules) { + const specificRule = rule.leftTokenRange.isSpecific && rule.rightTokenRange.isSpecific; - for (const left of rule.leftTokenRange.tokens) { - for (const right of rule.rightTokenRange.tokens) { - const index = getRuleBucketIndex(left, right); - let rulesBucket = map[index]; - if (rulesBucket === undefined) { - rulesBucket = map[index] = []; - } - addRule(rulesBucket, rule.rule, specificRule, rulesBucketConstructionStateList, index); + for (const left of rule.leftTokenRange.tokens) { + for (const right of rule.rightTokenRange.tokens) { + const index = getRuleBucketIndex(left, right); + let rulesBucket = map[index]; + if (rulesBucket === undefined) { + rulesBucket = map[index] = []; } + addRule(rulesBucket, rule.rule, specificRule, rulesBucketConstructionStateList, index); } } - return map; } + return map; +} - function getRuleBucketIndex(row: number, column: number): number { - Debug.assert(row <= SyntaxKind.LastKeyword && column <= SyntaxKind.LastKeyword, "Must compute formatting context from tokens"); - return (row * mapRowLength) + column; - } +function getRuleBucketIndex(row: number, column: number): number { + Debug.assert(row <= SyntaxKind.LastKeyword && column <= SyntaxKind.LastKeyword, "Must compute formatting context from tokens"); + return (row * mapRowLength) + column; +} - const maskBitSize = 5; - const mask = 0b11111; // MaskBitSize bits - const mapRowLength = SyntaxKind.LastToken + 1; +const maskBitSize = 5; +const mask = 0b11111; // MaskBitSize bits +const mapRowLength = SyntaxKind.LastToken + 1; - enum RulesPosition { - StopRulesSpecific = 0, - StopRulesAny = maskBitSize * 1, - ContextRulesSpecific = maskBitSize * 2, - ContextRulesAny = maskBitSize * 3, - NoContextRulesSpecific = maskBitSize * 4, - NoContextRulesAny = maskBitSize * 5 - } +enum RulesPosition { + StopRulesSpecific = 0, + StopRulesAny = maskBitSize * 1, + ContextRulesSpecific = maskBitSize * 2, + ContextRulesAny = maskBitSize * 3, + NoContextRulesSpecific = maskBitSize * 4, + NoContextRulesAny = maskBitSize * 5 +} - // The Rules list contains all the inserted rules into a rulebucket in the following order: - // 1- Ignore rules with specific token combination - // 2- Ignore rules with any token combination - // 3- Context rules with specific token combination - // 4- Context rules with any token combination - // 5- Non-context rules with specific token combination - // 6- Non-context rules with any token combination - // - // The member rulesInsertionIndexBitmap is used to describe the number of rules - // in each sub-bucket (above) hence can be used to know the index of where to insert - // the next rule. It's a bitmap which contains 6 different sections each is given 5 bits. - // - // Example: - // In order to insert a rule to the end of sub-bucket (3), we get the index by adding - // the values in the bitmap segments 3rd, 2nd, and 1st. - function addRule(rules: Rule[], rule: Rule, specificTokens: boolean, constructionState: number[], rulesBucketIndex: number): void { - const position = rule.action & RuleAction.StopAction ? - specificTokens ? RulesPosition.StopRulesSpecific : RulesPosition.StopRulesAny : - rule.context !== anyContext ? - specificTokens ? RulesPosition.ContextRulesSpecific : RulesPosition.ContextRulesAny : - specificTokens ? RulesPosition.NoContextRulesSpecific : RulesPosition.NoContextRulesAny; +// The Rules list contains all the inserted rules into a rulebucket in the following order: +// 1- Ignore rules with specific token combination +// 2- Ignore rules with any token combination +// 3- Context rules with specific token combination +// 4- Context rules with any token combination +// 5- Non-context rules with specific token combination +// 6- Non-context rules with any token combination +// +// The member rulesInsertionIndexBitmap is used to describe the number of rules +// in each sub-bucket (above) hence can be used to know the index of where to insert +// the next rule. It's a bitmap which contains 6 different sections each is given 5 bits. +// +// Example: +// In order to insert a rule to the end of sub-bucket (3), we get the index by adding +// the values in the bitmap segments 3rd, 2nd, and 1st. +function addRule(rules: Rule[], rule: Rule, specificTokens: boolean, constructionState: number[], rulesBucketIndex: number): void { + const position = rule.action & RuleAction.StopAction ? + specificTokens ? RulesPosition.StopRulesSpecific : RulesPosition.StopRulesAny : + rule.context !== anyContext ? + specificTokens ? RulesPosition.ContextRulesSpecific : RulesPosition.ContextRulesAny : + specificTokens ? RulesPosition.NoContextRulesSpecific : RulesPosition.NoContextRulesAny; - const state = constructionState[rulesBucketIndex] || 0; - rules.splice(getInsertionIndex(state, position), 0, rule); - constructionState[rulesBucketIndex] = increaseInsertionIndex(state, position); - } + const state = constructionState[rulesBucketIndex] || 0; + rules.splice(getInsertionIndex(state, position), 0, rule); + constructionState[rulesBucketIndex] = increaseInsertionIndex(state, position); +} - function getInsertionIndex(indexBitmap: number, maskPosition: RulesPosition) { - let index = 0; - for (let pos = 0; pos <= maskPosition; pos += maskBitSize) { - index += indexBitmap & mask; - indexBitmap >>= maskBitSize; - } - return index; +function getInsertionIndex(indexBitmap: number, maskPosition: RulesPosition) { + let index = 0; + for (let pos = 0; pos <= maskPosition; pos += maskBitSize) { + index += indexBitmap & mask; + indexBitmap >>= maskBitSize; } + return index; +} - function increaseInsertionIndex(indexBitmap: number, maskPosition: RulesPosition): number { - const value = ((indexBitmap >> maskPosition) & mask) + 1; - Debug.assert((value & mask) === value, "Adding more rules into the sub-bucket than allowed. Maximum allowed is 32 rules."); - return (indexBitmap & ~(mask << maskPosition)) | (value << maskPosition); - } +function increaseInsertionIndex(indexBitmap: number, maskPosition: RulesPosition): number { + const value = ((indexBitmap >> maskPosition) & mask) + 1; + Debug.assert((value & mask) === value, "Adding more rules into the sub-bucket than allowed. Maximum allowed is 32 rules."); + return (indexBitmap & ~(mask << maskPosition)) | (value << maskPosition); +} } diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index de22396383207..97dbbbbddc8f9 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -1,684 +1,684 @@ /* @internal */ namespace ts.formatting { - export namespace SmartIndenter { - - const enum Value { - Unknown = -1 - } - - /** - * @param assumeNewLineBeforeCloseBrace - * `false` when called on text from a real source file. - * `true` when we need to assume `position` is on a newline. - * - * This is useful for codefixes. Consider - * ``` - * function f() { - * |} - * ``` - * with `position` at `|`. - * - * When inserting some text after an open brace, we would like to get indentation as if a newline was already there. - * By default indentation at `position` will be 0 so 'assumeNewLineBeforeCloseBrace' overrides this behavior. - */ - export function getIndentation(position: number, sourceFile: SourceFile, options: EditorSettings, assumeNewLineBeforeCloseBrace = false): number { - if (position > sourceFile.text.length) { - return getBaseIndentation(options); // past EOF - } +export namespace SmartIndenter { - // no indentation when the indent style is set to none, - // so we can return fast - if (options.indentStyle === IndentStyle.None) { - return 0; - } + const enum Value { + Unknown = -1 + } - const precedingToken = findPrecedingToken(position, sourceFile, /*startNode*/ undefined, /*excludeJsdoc*/ true); + /** + * @param assumeNewLineBeforeCloseBrace + * `false` when called on text from a real source file. + * `true` when we need to assume `position` is on a newline. + * + * This is useful for codefixes. Consider + * ``` + * function f() { + * |} + * ``` + * with `position` at `|`. + * + * When inserting some text after an open brace, we would like to get indentation as if a newline was already there. + * By default indentation at `position` will be 0 so 'assumeNewLineBeforeCloseBrace' overrides this behavior. + */ + export function getIndentation(position: number, sourceFile: SourceFile, options: EditorSettings, assumeNewLineBeforeCloseBrace = false): number { + if (position > sourceFile.text.length) { + return getBaseIndentation(options); // past EOF + } + + // no indentation when the indent style is set to none, + // so we can return fast + if (options.indentStyle === IndentStyle.None) { + return 0; + } + + const precedingToken = findPrecedingToken(position, sourceFile, /*startNode*/ undefined, /*excludeJsdoc*/ true); + + // eslint-disable-next-line no-null/no-null + const enclosingCommentRange = getRangeOfEnclosingComment(sourceFile, position, precedingToken || null); + if (enclosingCommentRange && enclosingCommentRange.kind === SyntaxKind.MultiLineCommentTrivia) { + return getCommentIndent(sourceFile, position, options, enclosingCommentRange); + } + + if (!precedingToken) { + return getBaseIndentation(options); + } - // eslint-disable-next-line no-null/no-null - const enclosingCommentRange = getRangeOfEnclosingComment(sourceFile, position, precedingToken || null); - if (enclosingCommentRange && enclosingCommentRange.kind === SyntaxKind.MultiLineCommentTrivia) { - return getCommentIndent(sourceFile, position, options, enclosingCommentRange); - } + // no indentation in string \regex\template literals + const precedingTokenIsLiteral = isStringOrRegularExpressionOrTemplateLiteral(precedingToken.kind); + if (precedingTokenIsLiteral && precedingToken.getStart(sourceFile) <= position && position < precedingToken.end) { + return 0; + } - if (!precedingToken) { - return getBaseIndentation(options); - } + const lineAtPosition = sourceFile.getLineAndCharacterOfPosition(position).line; - // no indentation in string \regex\template literals - const precedingTokenIsLiteral = isStringOrRegularExpressionOrTemplateLiteral(precedingToken.kind); - if (precedingTokenIsLiteral && precedingToken.getStart(sourceFile) <= position && position < precedingToken.end) { - return 0; + // indentation is first non-whitespace character in a previous line + // for block indentation, we should look for a line which contains something that's not + // whitespace. + if (options.indentStyle === IndentStyle.Block) { + return getBlockIndent(sourceFile, position, options); + } + + if (precedingToken.kind === SyntaxKind.CommaToken && precedingToken.parent.kind !== SyntaxKind.BinaryExpression) { + // previous token is comma that separates items in list - find the previous item and try to derive indentation from it + const actualIndentation = getActualIndentationForListItemBeforeComma(precedingToken, sourceFile, options); + if (actualIndentation !== Value.Unknown) { + return actualIndentation; } + } - const lineAtPosition = sourceFile.getLineAndCharacterOfPosition(position).line; + const containerList = getListByPosition(position, precedingToken.parent, sourceFile); + // use list position if the preceding token is before any list items + if (containerList && !rangeContainsRange(containerList, precedingToken)) { + return getActualIndentationForListStartLine(containerList, sourceFile, options) + options.indentSize!; // TODO: GH#18217 + } - // indentation is first non-whitespace character in a previous line - // for block indentation, we should look for a line which contains something that's not - // whitespace. - if (options.indentStyle === IndentStyle.Block) { - return getBlockIndent(sourceFile, position, options); - } + return getSmartIndent(sourceFile, position, precedingToken, lineAtPosition, assumeNewLineBeforeCloseBrace, options); + } - if (precedingToken.kind === SyntaxKind.CommaToken && precedingToken.parent.kind !== SyntaxKind.BinaryExpression) { - // previous token is comma that separates items in list - find the previous item and try to derive indentation from it - const actualIndentation = getActualIndentationForListItemBeforeComma(precedingToken, sourceFile, options); - if (actualIndentation !== Value.Unknown) { - return actualIndentation; - } - } + function getCommentIndent(sourceFile: SourceFile, position: number, options: EditorSettings, enclosingCommentRange: CommentRange): number { + const previousLine = getLineAndCharacterOfPosition(sourceFile, position).line - 1; + const commentStartLine = getLineAndCharacterOfPosition(sourceFile, enclosingCommentRange.pos).line; - const containerList = getListByPosition(position, precedingToken.parent, sourceFile); - // use list position if the preceding token is before any list items - if (containerList && !rangeContainsRange(containerList, precedingToken)) { - return getActualIndentationForListStartLine(containerList, sourceFile, options) + options.indentSize!; // TODO: GH#18217 - } + Debug.assert(commentStartLine >= 0); - return getSmartIndent(sourceFile, position, precedingToken, lineAtPosition, assumeNewLineBeforeCloseBrace, options); + if (previousLine <= commentStartLine) { + return findFirstNonWhitespaceColumn(getStartPositionOfLine(commentStartLine, sourceFile), position, sourceFile, options); } - function getCommentIndent(sourceFile: SourceFile, position: number, options: EditorSettings, enclosingCommentRange: CommentRange): number { - const previousLine = getLineAndCharacterOfPosition(sourceFile, position).line - 1; - const commentStartLine = getLineAndCharacterOfPosition(sourceFile, enclosingCommentRange.pos).line; + const startPositionOfLine = getStartPositionOfLine(previousLine, sourceFile); + const { column, character } = findFirstNonWhitespaceCharacterAndColumn(startPositionOfLine, position, sourceFile, options); - Debug.assert(commentStartLine >= 0); + if (column === 0) { + return column; + } + + const firstNonWhitespaceCharacterCode = sourceFile.text.charCodeAt(startPositionOfLine + character); + return firstNonWhitespaceCharacterCode === CharacterCodes.asterisk ? column - 1 : column; + } - if (previousLine <= commentStartLine) { - return findFirstNonWhitespaceColumn(getStartPositionOfLine(commentStartLine, sourceFile), position, sourceFile, options); + function getBlockIndent(sourceFile: SourceFile, position: number, options: EditorSettings): number { + // move backwards until we find a line with a non-whitespace character, + // then find the first non-whitespace character for that line. + let current = position; + while (current > 0) { + const char = sourceFile.text.charCodeAt(current); + if (!isWhiteSpaceLike(char)) { + break; } + current--; + } - const startPositionOfLine = getStartPositionOfLine(previousLine, sourceFile); - const { column, character } = findFirstNonWhitespaceCharacterAndColumn(startPositionOfLine, position, sourceFile, options); + const lineStart = getLineStartPositionForPosition(current, sourceFile); + return findFirstNonWhitespaceColumn(lineStart, current, sourceFile, options); + } - if (column === 0) { - return column; - } + function getSmartIndent(sourceFile: SourceFile, position: number, precedingToken: Node, lineAtPosition: number, assumeNewLineBeforeCloseBrace: boolean, options: EditorSettings): number { + // try to find node that can contribute to indentation and includes 'position' starting from 'precedingToken' + // if such node is found - compute initial indentation for 'position' inside this node + let previous: Node | undefined; + let current = precedingToken; + + while (current) { + if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(options, current, previous, sourceFile, /*isNextChild*/ true)) { + const currentStart = getStartLineAndCharacterForNode(current, sourceFile); + const nextTokenKind = nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile); + const indentationDelta = nextTokenKind !== NextTokenKind.Unknown + // handle cases when codefix is about to be inserted before the close brace + ? assumeNewLineBeforeCloseBrace && nextTokenKind === NextTokenKind.CloseBrace ? options.indentSize : 0 + : lineAtPosition !== currentStart.line ? options.indentSize : 0; + return getIndentationForNodeWorker(current, currentStart, /*ignoreActualIndentationRange*/ undefined, indentationDelta!, sourceFile, /*isNextChild*/ true, options); // TODO: GH#18217 + } + + // check if current node is a list item - if yes, take indentation from it + // do not consider parent-child line sharing yet: + // function foo(a + // | preceding node 'a' does share line with its parent but indentation is expected + const actualIndentation = getActualIndentationForListItem(current, sourceFile, options, /*listIndentsChild*/ true); + if (actualIndentation !== Value.Unknown) { + return actualIndentation; + } + + previous = current; + current = current.parent; + } + // no parent was found - return the base indentation of the SourceFile + return getBaseIndentation(options); + } - const firstNonWhitespaceCharacterCode = sourceFile.text.charCodeAt(startPositionOfLine + character); - return firstNonWhitespaceCharacterCode === CharacterCodes.asterisk ? column - 1 : column; - } + export function getIndentationForNode(n: Node, ignoreActualIndentationRange: TextRange, sourceFile: SourceFile, options: EditorSettings): number { + const start = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)); + return getIndentationForNodeWorker(n, start, ignoreActualIndentationRange, /*indentationDelta*/ 0, sourceFile, /*isNextChild*/ false, options); + } - function getBlockIndent(sourceFile: SourceFile, position: number, options: EditorSettings): number { - // move backwards until we find a line with a non-whitespace character, - // then find the first non-whitespace character for that line. - let current = position; - while (current > 0) { - const char = sourceFile.text.charCodeAt(current); - if (!isWhiteSpaceLike(char)) { - break; - } - current--; - } + export function getBaseIndentation(options: EditorSettings) { + return options.baseIndentSize || 0; + } - const lineStart = getLineStartPositionForPosition(current, sourceFile); - return findFirstNonWhitespaceColumn(lineStart, current, sourceFile, options); - } - - function getSmartIndent(sourceFile: SourceFile, position: number, precedingToken: Node, lineAtPosition: number, assumeNewLineBeforeCloseBrace: boolean, options: EditorSettings): number { - // try to find node that can contribute to indentation and includes 'position' starting from 'precedingToken' - // if such node is found - compute initial indentation for 'position' inside this node - let previous: Node | undefined; - let current = precedingToken; - - while (current) { - if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(options, current, previous, sourceFile, /*isNextChild*/ true)) { - const currentStart = getStartLineAndCharacterForNode(current, sourceFile); - const nextTokenKind = nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile); - const indentationDelta = nextTokenKind !== NextTokenKind.Unknown - // handle cases when codefix is about to be inserted before the close brace - ? assumeNewLineBeforeCloseBrace && nextTokenKind === NextTokenKind.CloseBrace ? options.indentSize : 0 - : lineAtPosition !== currentStart.line ? options.indentSize : 0; - return getIndentationForNodeWorker(current, currentStart, /*ignoreActualIndentationRange*/ undefined, indentationDelta!, sourceFile, /*isNextChild*/ true, options); // TODO: GH#18217 + function getIndentationForNodeWorker( + current: Node, + currentStart: LineAndCharacter, + ignoreActualIndentationRange: TextRange | undefined, + indentationDelta: number, + sourceFile: SourceFile, + isNextChild: boolean, + options: EditorSettings): number { + let parent = current.parent; + + // Walk up the tree and collect indentation for parent-child node pairs. Indentation is not added if + // * parent and child nodes start on the same line, or + // * parent is an IfStatement and child starts on the same line as an 'else clause'. + while (parent) { + let useActualIndentation = true; + if (ignoreActualIndentationRange) { + const start = current.getStart(sourceFile); + useActualIndentation = start < ignoreActualIndentationRange.pos || start > ignoreActualIndentationRange.end; + } + + const containingListOrParentStart = getContainingListOrParentStart(parent, current, sourceFile); + const parentAndChildShareLine = + containingListOrParentStart.line === currentStart.line || + childStartsOnTheSameLineWithElseInIfStatement(parent, current, currentStart.line, sourceFile); + + if (useActualIndentation) { + // check if current node is a list item - if yes, take indentation from it + const firstListChild = getContainingList(current, sourceFile)?.[0]; + // A list indents its children if the children begin on a later line than the list itself: + // + // f1( L0 - List start + // { L1 - First child start: indented, along with all other children + // prop: 0 + // }, + // { + // prop: 1 + // } + // ) + // + // f2({ L0 - List start and first child start: children are not indented. + // prop: 0 Object properties are indented only one level, because the list + // }, { itself contributes nothing. + // prop: 1 L3 - The indentation of the second object literal is best understood by + // }) looking at the relationship between the list and *first* list item. + const listIndentsChild = !!firstListChild && getStartLineAndCharacterForNode(firstListChild, sourceFile).line > containingListOrParentStart.line; + let actualIndentation = getActualIndentationForListItem(current, sourceFile, options, listIndentsChild); + if (actualIndentation !== Value.Unknown) { + return actualIndentation + indentationDelta; } - // check if current node is a list item - if yes, take indentation from it - // do not consider parent-child line sharing yet: - // function foo(a - // | preceding node 'a' does share line with its parent but indentation is expected - const actualIndentation = getActualIndentationForListItem(current, sourceFile, options, /*listIndentsChild*/ true); + // try to fetch actual indentation for current node from source text + actualIndentation = getActualIndentationForNode(current, parent, currentStart, parentAndChildShareLine, sourceFile, options); if (actualIndentation !== Value.Unknown) { - return actualIndentation; + return actualIndentation + indentationDelta; } + } - previous = current; - current = current.parent; + // increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line + if (shouldIndentChildNode(options, parent, current, sourceFile, isNextChild) && !parentAndChildShareLine) { + indentationDelta += options.indentSize!; } - // no parent was found - return the base indentation of the SourceFile - return getBaseIndentation(options); - } - export function getIndentationForNode(n: Node, ignoreActualIndentationRange: TextRange, sourceFile: SourceFile, options: EditorSettings): number { - const start = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)); - return getIndentationForNodeWorker(n, start, ignoreActualIndentationRange, /*indentationDelta*/ 0, sourceFile, /*isNextChild*/ false, options); - } + // In our AST, a call argument's `parent` is the call-expression, not the argument list. + // We would like to increase indentation based on the relationship between an argument and its argument-list, + // so we spoof the starting position of the (parent) call-expression to match the (non-parent) argument-list. + // But, the spoofed start-value could then cause a problem when comparing the start position of the call-expression + // to *its* parent (in the case of an iife, an expression statement), adding an extra level of indentation. + // + // Instead, when at an argument, we unspoof the starting position of the enclosing call expression + // *after* applying indentation for the argument. - export function getBaseIndentation(options: EditorSettings) { - return options.baseIndentSize || 0; - } + const useTrueStart = + isArgumentAndStartLineOverlapsExpressionBeingCalled(parent, current, currentStart.line, sourceFile); - function getIndentationForNodeWorker( - current: Node, - currentStart: LineAndCharacter, - ignoreActualIndentationRange: TextRange | undefined, - indentationDelta: number, - sourceFile: SourceFile, - isNextChild: boolean, - options: EditorSettings): number { - let parent = current.parent; + current = parent; + parent = current.parent; + currentStart = useTrueStart ? sourceFile.getLineAndCharacterOfPosition(current.getStart(sourceFile)) : containingListOrParentStart; + } - // Walk up the tree and collect indentation for parent-child node pairs. Indentation is not added if - // * parent and child nodes start on the same line, or - // * parent is an IfStatement and child starts on the same line as an 'else clause'. - while (parent) { - let useActualIndentation = true; - if (ignoreActualIndentationRange) { - const start = current.getStart(sourceFile); - useActualIndentation = start < ignoreActualIndentationRange.pos || start > ignoreActualIndentationRange.end; - } + return indentationDelta + getBaseIndentation(options); + } - const containingListOrParentStart = getContainingListOrParentStart(parent, current, sourceFile); - const parentAndChildShareLine = - containingListOrParentStart.line === currentStart.line || - childStartsOnTheSameLineWithElseInIfStatement(parent, current, currentStart.line, sourceFile); - - if (useActualIndentation) { - // check if current node is a list item - if yes, take indentation from it - const firstListChild = getContainingList(current, sourceFile)?.[0]; - // A list indents its children if the children begin on a later line than the list itself: - // - // f1( L0 - List start - // { L1 - First child start: indented, along with all other children - // prop: 0 - // }, - // { - // prop: 1 - // } - // ) - // - // f2({ L0 - List start and first child start: children are not indented. - // prop: 0 Object properties are indented only one level, because the list - // }, { itself contributes nothing. - // prop: 1 L3 - The indentation of the second object literal is best understood by - // }) looking at the relationship between the list and *first* list item. - const listIndentsChild = !!firstListChild && getStartLineAndCharacterForNode(firstListChild, sourceFile).line > containingListOrParentStart.line; - let actualIndentation = getActualIndentationForListItem(current, sourceFile, options, listIndentsChild); - if (actualIndentation !== Value.Unknown) { - return actualIndentation + indentationDelta; - } - - // try to fetch actual indentation for current node from source text - actualIndentation = getActualIndentationForNode(current, parent, currentStart, parentAndChildShareLine, sourceFile, options); - if (actualIndentation !== Value.Unknown) { - return actualIndentation + indentationDelta; - } - } + function getContainingListOrParentStart(parent: Node, child: Node, sourceFile: SourceFile): LineAndCharacter { + const containingList = getContainingList(child, sourceFile); + const startPos = containingList ? containingList.pos : parent.getStart(sourceFile); + return sourceFile.getLineAndCharacterOfPosition(startPos); + } - // increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line - if (shouldIndentChildNode(options, parent, current, sourceFile, isNextChild) && !parentAndChildShareLine) { - indentationDelta += options.indentSize!; - } + /* + * Function returns Value.Unknown if indentation cannot be determined + */ + function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: EditorSettings): number { + // previous token is comma that separates items in list - find the previous item and try to derive indentation from it + const commaItemInfo = findListItemInfo(commaToken); + if (commaItemInfo && commaItemInfo.listItemIndex > 0) { + return deriveActualIndentationFromList(commaItemInfo.list.getChildren(), commaItemInfo.listItemIndex - 1, sourceFile, options); + } + else { + // handle broken code gracefully + return Value.Unknown; + } + } - // In our AST, a call argument's `parent` is the call-expression, not the argument list. - // We would like to increase indentation based on the relationship between an argument and its argument-list, - // so we spoof the starting position of the (parent) call-expression to match the (non-parent) argument-list. - // But, the spoofed start-value could then cause a problem when comparing the start position of the call-expression - // to *its* parent (in the case of an iife, an expression statement), adding an extra level of indentation. - // - // Instead, when at an argument, we unspoof the starting position of the enclosing call expression - // *after* applying indentation for the argument. + /* + * Function returns Value.Unknown if actual indentation for node should not be used (i.e because node is nested expression) + */ + function getActualIndentationForNode(current: Node, + parent: Node, + currentLineAndChar: LineAndCharacter, + parentAndChildShareLine: boolean, + sourceFile: SourceFile, + options: EditorSettings): number { + + // actual indentation is used for statements\declarations if one of cases below is true: + // - parent is SourceFile - by default immediate children of SourceFile are not indented except when user indents them manually + // - parent and child are not on the same line + const useActualIndentation = + (isDeclaration(current) || isStatementButNotDeclaration(current)) && + (parent.kind === SyntaxKind.SourceFile || !parentAndChildShareLine); + + if (!useActualIndentation) { + return Value.Unknown; + } - const useTrueStart = - isArgumentAndStartLineOverlapsExpressionBeingCalled(parent, current, currentStart.line, sourceFile); + return findColumnForFirstNonWhitespaceCharacterInLine(currentLineAndChar, sourceFile, options); + } - current = parent; - parent = current.parent; - currentStart = useTrueStart ? sourceFile.getLineAndCharacterOfPosition(current.getStart(sourceFile)) : containingListOrParentStart; - } + const enum NextTokenKind { + Unknown, + OpenBrace, + CloseBrace + } - return indentationDelta + getBaseIndentation(options); + function nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken: Node, current: Node, lineAtPosition: number, sourceFile: SourceFile): NextTokenKind { + const nextToken = findNextToken(precedingToken, current, sourceFile); + if (!nextToken) { + return NextTokenKind.Unknown; } - function getContainingListOrParentStart(parent: Node, child: Node, sourceFile: SourceFile): LineAndCharacter { - const containingList = getContainingList(child, sourceFile); - const startPos = containingList ? containingList.pos : parent.getStart(sourceFile); - return sourceFile.getLineAndCharacterOfPosition(startPos); + if (nextToken.kind === SyntaxKind.OpenBraceToken) { + // open braces are always indented at the parent level + return NextTokenKind.OpenBrace; } + else if (nextToken.kind === SyntaxKind.CloseBraceToken) { + // close braces are indented at the parent level if they are located on the same line with cursor + // this means that if new line will be added at $ position, this case will be indented + // class A { + // $ + // } + /// and this one - not + // class A { + // $} - /* - * Function returns Value.Unknown if indentation cannot be determined - */ - function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: EditorSettings): number { - // previous token is comma that separates items in list - find the previous item and try to derive indentation from it - const commaItemInfo = findListItemInfo(commaToken); - if (commaItemInfo && commaItemInfo.listItemIndex > 0) { - return deriveActualIndentationFromList(commaItemInfo.list.getChildren(), commaItemInfo.listItemIndex - 1, sourceFile, options); - } - else { - // handle broken code gracefully - return Value.Unknown; - } + const nextTokenStartLine = getStartLineAndCharacterForNode(nextToken, sourceFile).line; + return lineAtPosition === nextTokenStartLine ? NextTokenKind.CloseBrace : NextTokenKind.Unknown; } - /* - * Function returns Value.Unknown if actual indentation for node should not be used (i.e because node is nested expression) - */ - function getActualIndentationForNode(current: Node, - parent: Node, - currentLineAndChar: LineAndCharacter, - parentAndChildShareLine: boolean, - sourceFile: SourceFile, - options: EditorSettings): number { - - // actual indentation is used for statements\declarations if one of cases below is true: - // - parent is SourceFile - by default immediate children of SourceFile are not indented except when user indents them manually - // - parent and child are not on the same line - const useActualIndentation = - (isDeclaration(current) || isStatementButNotDeclaration(current)) && - (parent.kind === SyntaxKind.SourceFile || !parentAndChildShareLine); - - if (!useActualIndentation) { - return Value.Unknown; - } + return NextTokenKind.Unknown; + } - return findColumnForFirstNonWhitespaceCharacterInLine(currentLineAndChar, sourceFile, options); - } + function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFileLike): LineAndCharacter { + return sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)); + } - const enum NextTokenKind { - Unknown, - OpenBrace, - CloseBrace + export function isArgumentAndStartLineOverlapsExpressionBeingCalled(parent: Node, child: Node, childStartLine: number, sourceFile: SourceFileLike): boolean { + if (!(isCallExpression(parent) && contains(parent.arguments, child))) { + return false; } - function nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken: Node, current: Node, lineAtPosition: number, sourceFile: SourceFile): NextTokenKind { - const nextToken = findNextToken(precedingToken, current, sourceFile); - if (!nextToken) { - return NextTokenKind.Unknown; - } + const expressionOfCallExpressionEnd = parent.expression.getEnd(); + const expressionOfCallExpressionEndLine = getLineAndCharacterOfPosition(sourceFile, expressionOfCallExpressionEnd).line; + return expressionOfCallExpressionEndLine === childStartLine; + } - if (nextToken.kind === SyntaxKind.OpenBraceToken) { - // open braces are always indented at the parent level - return NextTokenKind.OpenBrace; - } - else if (nextToken.kind === SyntaxKind.CloseBraceToken) { - // close braces are indented at the parent level if they are located on the same line with cursor - // this means that if new line will be added at $ position, this case will be indented - // class A { - // $ - // } - /// and this one - not - // class A { - // $} - - const nextTokenStartLine = getStartLineAndCharacterForNode(nextToken, sourceFile).line; - return lineAtPosition === nextTokenStartLine ? NextTokenKind.CloseBrace : NextTokenKind.Unknown; - } + export function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { + if (parent.kind === SyntaxKind.IfStatement && (parent as IfStatement).elseStatement === child) { + const elseKeyword = findChildOfKind(parent, SyntaxKind.ElseKeyword, sourceFile)!; + Debug.assert(elseKeyword !== undefined); - return NextTokenKind.Unknown; + const elseKeywordStartLine = getStartLineAndCharacterForNode(elseKeyword, sourceFile).line; + return elseKeywordStartLine === childStartLine; } - function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFileLike): LineAndCharacter { - return sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)); - } + return false; + } - export function isArgumentAndStartLineOverlapsExpressionBeingCalled(parent: Node, child: Node, childStartLine: number, sourceFile: SourceFileLike): boolean { - if (!(isCallExpression(parent) && contains(parent.arguments, child))) { - return false; + // A multiline conditional typically increases the indentation of its whenTrue and whenFalse children: + // + // condition + // ? whenTrue + // : whenFalse; + // + // However, that indentation does not apply if the subexpressions themselves span multiple lines, + // applying their own indentation: + // + // (() => { + // return complexCalculationForCondition(); + // })() ? { + // whenTrue: 'multiline object literal' + // } : ( + // whenFalse('multiline parenthesized expression') + // ); + // + // In these cases, we must discard the indentation increase that would otherwise be applied to the + // whenTrue and whenFalse children to avoid double-indenting their contents. To identify this scenario, + // we check for the whenTrue branch beginning on the line that the condition ends, and the whenFalse + // branch beginning on the line that the whenTrue branch ends. + export function childIsUnindentedBranchOfConditionalExpression(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { + if (isConditionalExpression(parent) && (child === parent.whenTrue || child === parent.whenFalse)) { + const conditionEndLine = getLineAndCharacterOfPosition(sourceFile, parent.condition.end).line; + if (child === parent.whenTrue) { + return childStartLine === conditionEndLine; + } + else { + // On the whenFalse side, we have to look at the whenTrue side, because if that one was + // indented, whenFalse must also be indented: + // + // const y = true + // ? 1 : ( L1: whenTrue indented because it's on a new line + // 0 L2: indented two stops, one because whenTrue was indented + // ); and one because of the parentheses spanning multiple lines + const trueStartLine = getStartLineAndCharacterForNode(parent.whenTrue, sourceFile).line; + const trueEndLine = getLineAndCharacterOfPosition(sourceFile, parent.whenTrue.end).line; + return conditionEndLine === trueStartLine && trueEndLine === childStartLine; } - - const expressionOfCallExpressionEnd = parent.expression.getEnd(); - const expressionOfCallExpressionEndLine = getLineAndCharacterOfPosition(sourceFile, expressionOfCallExpressionEnd).line; - return expressionOfCallExpressionEndLine === childStartLine; } + return false; + } - export function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { - if (parent.kind === SyntaxKind.IfStatement && (parent as IfStatement).elseStatement === child) { - const elseKeyword = findChildOfKind(parent, SyntaxKind.ElseKeyword, sourceFile)!; - Debug.assert(elseKeyword !== undefined); - - const elseKeywordStartLine = getStartLineAndCharacterForNode(elseKeyword, sourceFile).line; - return elseKeywordStartLine === childStartLine; - } + export function argumentStartsOnSameLineAsPreviousArgument(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { + if (isCallOrNewExpression(parent)) { + if (!parent.arguments) return false; + const currentNode = find(parent.arguments, arg => arg.pos === child.pos); + // If it's not one of the arguments, don't look past this + if (!currentNode) return false; + const currentIndex = parent.arguments.indexOf(currentNode); + if (currentIndex === 0) return false; // Can't look at previous node if first - return false; - } + const previousNode = parent.arguments[currentIndex - 1]; + const lineOfPreviousNode = getLineAndCharacterOfPosition(sourceFile, previousNode.getEnd()).line; - // A multiline conditional typically increases the indentation of its whenTrue and whenFalse children: - // - // condition - // ? whenTrue - // : whenFalse; - // - // However, that indentation does not apply if the subexpressions themselves span multiple lines, - // applying their own indentation: - // - // (() => { - // return complexCalculationForCondition(); - // })() ? { - // whenTrue: 'multiline object literal' - // } : ( - // whenFalse('multiline parenthesized expression') - // ); - // - // In these cases, we must discard the indentation increase that would otherwise be applied to the - // whenTrue and whenFalse children to avoid double-indenting their contents. To identify this scenario, - // we check for the whenTrue branch beginning on the line that the condition ends, and the whenFalse - // branch beginning on the line that the whenTrue branch ends. - export function childIsUnindentedBranchOfConditionalExpression(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { - if (isConditionalExpression(parent) && (child === parent.whenTrue || child === parent.whenFalse)) { - const conditionEndLine = getLineAndCharacterOfPosition(sourceFile, parent.condition.end).line; - if (child === parent.whenTrue) { - return childStartLine === conditionEndLine; - } - else { - // On the whenFalse side, we have to look at the whenTrue side, because if that one was - // indented, whenFalse must also be indented: - // - // const y = true - // ? 1 : ( L1: whenTrue indented because it's on a new line - // 0 L2: indented two stops, one because whenTrue was indented - // ); and one because of the parentheses spanning multiple lines - const trueStartLine = getStartLineAndCharacterForNode(parent.whenTrue, sourceFile).line; - const trueEndLine = getLineAndCharacterOfPosition(sourceFile, parent.whenTrue.end).line; - return conditionEndLine === trueStartLine && trueEndLine === childStartLine; - } + if (childStartLine === lineOfPreviousNode) { + return true; } - return false; } - export function argumentStartsOnSameLineAsPreviousArgument(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { - if (isCallOrNewExpression(parent)) { - if (!parent.arguments) return false; - const currentNode = find(parent.arguments, arg => arg.pos === child.pos); - // If it's not one of the arguments, don't look past this - if (!currentNode) return false; - const currentIndex = parent.arguments.indexOf(currentNode); - if (currentIndex === 0) return false; // Can't look at previous node if first + return false; + } - const previousNode = parent.arguments[currentIndex - 1]; - const lineOfPreviousNode = getLineAndCharacterOfPosition(sourceFile, previousNode.getEnd()).line; + export function getContainingList(node: Node, sourceFile: SourceFile): NodeArray | undefined { + return node.parent && getListByRange(node.getStart(sourceFile), node.getEnd(), node.parent, sourceFile); + } - if (childStartLine === lineOfPreviousNode) { - return true; - } - } + function getListByPosition(pos: number, node: Node, sourceFile: SourceFile): NodeArray | undefined { + return node && getListByRange(pos, pos, node, sourceFile); + } - return false; + function getListByRange(start: number, end: number, node: Node, sourceFile: SourceFile): NodeArray | undefined { + switch (node.kind) { + case SyntaxKind.TypeReference: + return getList((node as TypeReferenceNode).typeArguments); + case SyntaxKind.ObjectLiteralExpression: + return getList((node as ObjectLiteralExpression).properties); + case SyntaxKind.ArrayLiteralExpression: + return getList((node as ArrayLiteralExpression).elements); + case SyntaxKind.TypeLiteral: + return getList((node as TypeLiteralNode).members); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.Constructor: + case SyntaxKind.ConstructorType: + case SyntaxKind.ConstructSignature: + return getList((node as SignatureDeclaration).typeParameters) || getList((node as SignatureDeclaration).parameters); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocTemplateTag: + return getList((node as ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag).typeParameters); + case SyntaxKind.NewExpression: + case SyntaxKind.CallExpression: + return getList((node as CallExpression).typeArguments) || getList((node as CallExpression).arguments); + case SyntaxKind.VariableDeclarationList: + return getList((node as VariableDeclarationList).declarations); + case SyntaxKind.NamedImports: + case SyntaxKind.NamedExports: + return getList((node as NamedImportsOrExports).elements); + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + return getList((node as ObjectBindingPattern | ArrayBindingPattern).elements); + } + + function getList(list: NodeArray | undefined): NodeArray | undefined { + return list && rangeContainsStartEnd(getVisualListRange(node, list, sourceFile), start, end) ? list : undefined; } + } - export function getContainingList(node: Node, sourceFile: SourceFile): NodeArray | undefined { - return node.parent && getListByRange(node.getStart(sourceFile), node.getEnd(), node.parent, sourceFile); - } - - function getListByPosition(pos: number, node: Node, sourceFile: SourceFile): NodeArray | undefined { - return node && getListByRange(pos, pos, node, sourceFile); - } - - function getListByRange(start: number, end: number, node: Node, sourceFile: SourceFile): NodeArray | undefined { - switch (node.kind) { - case SyntaxKind.TypeReference: - return getList((node as TypeReferenceNode).typeArguments); - case SyntaxKind.ObjectLiteralExpression: - return getList((node as ObjectLiteralExpression).properties); - case SyntaxKind.ArrayLiteralExpression: - return getList((node as ArrayLiteralExpression).elements); - case SyntaxKind.TypeLiteral: - return getList((node as TypeLiteralNode).members); - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.Constructor: - case SyntaxKind.ConstructorType: - case SyntaxKind.ConstructSignature: - return getList((node as SignatureDeclaration).typeParameters) || getList((node as SignatureDeclaration).parameters); - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.JSDocTemplateTag: - return getList((node as ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag).typeParameters); - case SyntaxKind.NewExpression: - case SyntaxKind.CallExpression: - return getList((node as CallExpression).typeArguments) || getList((node as CallExpression).arguments); - case SyntaxKind.VariableDeclarationList: - return getList((node as VariableDeclarationList).declarations); - case SyntaxKind.NamedImports: - case SyntaxKind.NamedExports: - return getList((node as NamedImportsOrExports).elements); - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - return getList((node as ObjectBindingPattern | ArrayBindingPattern).elements); + function getVisualListRange(node: Node, list: TextRange, sourceFile: SourceFile): TextRange { + const children = node.getChildren(sourceFile); + for (let i = 1; i < children.length - 1; i++) { + if (children[i].pos === list.pos && children[i].end === list.end) { + return { pos: children[i - 1].end, end: children[i + 1].getStart(sourceFile) }; } + } + return list; + } - function getList(list: NodeArray | undefined): NodeArray | undefined { - return list && rangeContainsStartEnd(getVisualListRange(node, list, sourceFile), start, end) ? list : undefined; - } + function getActualIndentationForListStartLine(list: NodeArray, sourceFile: SourceFile, options: EditorSettings): number { + if (!list) { + return Value.Unknown; } + return findColumnForFirstNonWhitespaceCharacterInLine(sourceFile.getLineAndCharacterOfPosition(list.pos), sourceFile, options); + } - function getVisualListRange(node: Node, list: TextRange, sourceFile: SourceFile): TextRange { - const children = node.getChildren(sourceFile); - for (let i = 1; i < children.length - 1; i++) { - if (children[i].pos === list.pos && children[i].end === list.end) { - return { pos: children[i - 1].end, end: children[i + 1].getStart(sourceFile) }; + function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorSettings, listIndentsChild: boolean): number { + if (node.parent && node.parent.kind === SyntaxKind.VariableDeclarationList) { + // VariableDeclarationList has no wrapping tokens + return Value.Unknown; + } + const containingList = getContainingList(node, sourceFile); + if (containingList) { + const index = containingList.indexOf(node); + if (index !== -1) { + const result = deriveActualIndentationFromList(containingList, index, sourceFile, options); + if (result !== Value.Unknown) { + return result; } } - return list; + return getActualIndentationForListStartLine(containingList, sourceFile, options) + (listIndentsChild ? options.indentSize! : 0); // TODO: GH#18217 } + return Value.Unknown; + } - function getActualIndentationForListStartLine(list: NodeArray, sourceFile: SourceFile, options: EditorSettings): number { - if (!list) { - return Value.Unknown; - } - return findColumnForFirstNonWhitespaceCharacterInLine(sourceFile.getLineAndCharacterOfPosition(list.pos), sourceFile, options); - } + function deriveActualIndentationFromList(list: readonly Node[], index: number, sourceFile: SourceFile, options: EditorSettings): number { + Debug.assert(index >= 0 && index < list.length); + const node = list[index]; - function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorSettings, listIndentsChild: boolean): number { - if (node.parent && node.parent.kind === SyntaxKind.VariableDeclarationList) { - // VariableDeclarationList has no wrapping tokens - return Value.Unknown; + // walk toward the start of the list starting from current node and check if the line is the same for all items. + // if end line for item [i - 1] differs from the start line for item [i] - find column of the first non-whitespace character on the line of item [i] + let lineAndCharacter = getStartLineAndCharacterForNode(node, sourceFile); + for (let i = index - 1; i >= 0; i--) { + if (list[i].kind === SyntaxKind.CommaToken) { + continue; } - const containingList = getContainingList(node, sourceFile); - if (containingList) { - const index = containingList.indexOf(node); - if (index !== -1) { - const result = deriveActualIndentationFromList(containingList, index, sourceFile, options); - if (result !== Value.Unknown) { - return result; - } - } - return getActualIndentationForListStartLine(containingList, sourceFile, options) + (listIndentsChild ? options.indentSize! : 0); // TODO: GH#18217 + // skip list items that ends on the same line with the current list element + const prevEndLine = sourceFile.getLineAndCharacterOfPosition(list[i].end).line; + if (prevEndLine !== lineAndCharacter.line) { + return findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter, sourceFile, options); } - return Value.Unknown; - } - function deriveActualIndentationFromList(list: readonly Node[], index: number, sourceFile: SourceFile, options: EditorSettings): number { - Debug.assert(index >= 0 && index < list.length); - const node = list[index]; + lineAndCharacter = getStartLineAndCharacterForNode(list[i], sourceFile); + } + return Value.Unknown; + } - // walk toward the start of the list starting from current node and check if the line is the same for all items. - // if end line for item [i - 1] differs from the start line for item [i] - find column of the first non-whitespace character on the line of item [i] - let lineAndCharacter = getStartLineAndCharacterForNode(node, sourceFile); - for (let i = index - 1; i >= 0; i--) { - if (list[i].kind === SyntaxKind.CommaToken) { - continue; - } - // skip list items that ends on the same line with the current list element - const prevEndLine = sourceFile.getLineAndCharacterOfPosition(list[i].end).line; - if (prevEndLine !== lineAndCharacter.line) { - return findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter, sourceFile, options); - } + function findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter: LineAndCharacter, sourceFile: SourceFile, options: EditorSettings): number { + const lineStart = sourceFile.getPositionOfLineAndCharacter(lineAndCharacter.line, 0); + return findFirstNonWhitespaceColumn(lineStart, lineStart + lineAndCharacter.character, sourceFile, options); + } - lineAndCharacter = getStartLineAndCharacterForNode(list[i], sourceFile); + /** + * Character is the actual index of the character since the beginning of the line. + * Column - position of the character after expanding tabs to spaces. + * "0\t2$" + * value of 'character' for '$' is 3 + * value of 'column' for '$' is 6 (assuming that tab size is 4) + */ + export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings) { + let character = 0; + let column = 0; + for (let pos = startPos; pos < endPos; pos++) { + const ch = sourceFile.text.charCodeAt(pos); + if (!isWhiteSpaceSingleLine(ch)) { + break; + } + + if (ch === CharacterCodes.tab) { + column += options.tabSize! + (column % options.tabSize!); } - return Value.Unknown; + else { + column++; + } + + character++; } + return { column, character }; + } - function findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter: LineAndCharacter, sourceFile: SourceFile, options: EditorSettings): number { - const lineStart = sourceFile.getPositionOfLineAndCharacter(lineAndCharacter.line, 0); - return findFirstNonWhitespaceColumn(lineStart, lineStart + lineAndCharacter.character, sourceFile, options); - } - - /** - * Character is the actual index of the character since the beginning of the line. - * Column - position of the character after expanding tabs to spaces. - * "0\t2$" - * value of 'character' for '$' is 3 - * value of 'column' for '$' is 6 (assuming that tab size is 4) - */ - export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings) { - let character = 0; - let column = 0; - for (let pos = startPos; pos < endPos; pos++) { - const ch = sourceFile.text.charCodeAt(pos); - if (!isWhiteSpaceSingleLine(ch)) { - break; - } + export function findFirstNonWhitespaceColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings): number { + return findFirstNonWhitespaceCharacterAndColumn(startPos, endPos, sourceFile, options).column; + } - if (ch === CharacterCodes.tab) { - column += options.tabSize! + (column % options.tabSize!); + export function nodeWillIndentChild(settings: FormatCodeSettings, parent: TextRangeWithKind, child: TextRangeWithKind | undefined, sourceFile: SourceFileLike | undefined, indentByDefault: boolean): boolean { + const childKind = child ? child.kind : SyntaxKind.Unknown; + + switch (parent.kind) { + case SyntaxKind.ExpressionStatement: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.TypeLiteral: + case SyntaxKind.MappedType: + case SyntaxKind.TupleType: + case SyntaxKind.CaseBlock: + case SyntaxKind.DefaultClause: + case SyntaxKind.CaseClause: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.VariableStatement: + case SyntaxKind.ExportAssignment: + case SyntaxKind.ReturnStatement: + case SyntaxKind.ConditionalExpression: + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxOpeningFragment: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxExpression: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.Parameter: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.ParenthesizedType: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.AwaitExpression: + case SyntaxKind.NamedExports: + case SyntaxKind.NamedImports: + case SyntaxKind.ExportSpecifier: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.PropertyDeclaration: + return true; + case SyntaxKind.VariableDeclaration: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.BinaryExpression: + if (!settings.indentMultiLineObjectLiteralBeginningOnBlankLine && sourceFile && childKind === SyntaxKind.ObjectLiteralExpression) { // TODO: GH#18217 + return rangeIsOnOneLine(sourceFile, child!); } - else { - column++; + if (parent.kind === SyntaxKind.BinaryExpression && sourceFile && child && childKind === SyntaxKind.JsxElement) { + const parentStartLine = sourceFile.getLineAndCharacterOfPosition(skipTrivia(sourceFile.text, parent.pos)).line; + const childStartLine = sourceFile.getLineAndCharacterOfPosition(skipTrivia(sourceFile.text, child.pos)).line; + return parentStartLine !== childStartLine; } - - character++; - } - return { column, character }; - } - - export function findFirstNonWhitespaceColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings): number { - return findFirstNonWhitespaceCharacterAndColumn(startPos, endPos, sourceFile, options).column; - } - - export function nodeWillIndentChild(settings: FormatCodeSettings, parent: TextRangeWithKind, child: TextRangeWithKind | undefined, sourceFile: SourceFileLike | undefined, indentByDefault: boolean): boolean { - const childKind = child ? child.kind : SyntaxKind.Unknown; - - switch (parent.kind) { - case SyntaxKind.ExpressionStatement: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.TypeLiteral: - case SyntaxKind.MappedType: - case SyntaxKind.TupleType: - case SyntaxKind.CaseBlock: - case SyntaxKind.DefaultClause: - case SyntaxKind.CaseClause: - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.VariableStatement: - case SyntaxKind.ExportAssignment: - case SyntaxKind.ReturnStatement: - case SyntaxKind.ConditionalExpression: - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxOpeningFragment: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxExpression: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.Parameter: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.ParenthesizedType: - case SyntaxKind.TaggedTemplateExpression: - case SyntaxKind.AwaitExpression: - case SyntaxKind.NamedExports: - case SyntaxKind.NamedImports: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.PropertyDeclaration: + if (parent.kind !== SyntaxKind.BinaryExpression) { return true; - case SyntaxKind.VariableDeclaration: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.BinaryExpression: - if (!settings.indentMultiLineObjectLiteralBeginningOnBlankLine && sourceFile && childKind === SyntaxKind.ObjectLiteralExpression) { // TODO: GH#18217 - return rangeIsOnOneLine(sourceFile, child!); - } - if (parent.kind === SyntaxKind.BinaryExpression && sourceFile && child && childKind === SyntaxKind.JsxElement) { - const parentStartLine = sourceFile.getLineAndCharacterOfPosition(skipTrivia(sourceFile.text, parent.pos)).line; - const childStartLine = sourceFile.getLineAndCharacterOfPosition(skipTrivia(sourceFile.text, child.pos)).line; - return parentStartLine !== childStartLine; - } - if (parent.kind !== SyntaxKind.BinaryExpression) { - return true; - } - break; - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.IfStatement: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return childKind !== SyntaxKind.Block; - case SyntaxKind.ArrowFunction: - if (sourceFile && childKind === SyntaxKind.ParenthesizedExpression) { - return rangeIsOnOneLine(sourceFile, child!); - } - return childKind !== SyntaxKind.Block; - case SyntaxKind.ExportDeclaration: - return childKind !== SyntaxKind.NamedExports; - case SyntaxKind.ImportDeclaration: - return childKind !== SyntaxKind.ImportClause || - (!!(child as ImportClause).namedBindings && (child as ImportClause).namedBindings!.kind !== SyntaxKind.NamedImports); - case SyntaxKind.JsxElement: - return childKind !== SyntaxKind.JsxClosingElement; - case SyntaxKind.JsxFragment: - return childKind !== SyntaxKind.JsxClosingFragment; - case SyntaxKind.IntersectionType: - case SyntaxKind.UnionType: - if (childKind === SyntaxKind.TypeLiteral || childKind === SyntaxKind.TupleType) { - return false; - } - break; - } - // No explicit rule for given nodes so the result will follow the default value argument - return indentByDefault; - } - - function isControlFlowEndingStatement(kind: SyntaxKind, parent: TextRangeWithKind): boolean { - switch (kind) { - case SyntaxKind.ReturnStatement: - case SyntaxKind.ThrowStatement: - case SyntaxKind.ContinueStatement: - case SyntaxKind.BreakStatement: - return parent.kind !== SyntaxKind.Block; - default: + } + break; + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return childKind !== SyntaxKind.Block; + case SyntaxKind.ArrowFunction: + if (sourceFile && childKind === SyntaxKind.ParenthesizedExpression) { + return rangeIsOnOneLine(sourceFile, child!); + } + return childKind !== SyntaxKind.Block; + case SyntaxKind.ExportDeclaration: + return childKind !== SyntaxKind.NamedExports; + case SyntaxKind.ImportDeclaration: + return childKind !== SyntaxKind.ImportClause || + (!!(child as ImportClause).namedBindings && (child as ImportClause).namedBindings!.kind !== SyntaxKind.NamedImports); + case SyntaxKind.JsxElement: + return childKind !== SyntaxKind.JsxClosingElement; + case SyntaxKind.JsxFragment: + return childKind !== SyntaxKind.JsxClosingFragment; + case SyntaxKind.IntersectionType: + case SyntaxKind.UnionType: + if (childKind === SyntaxKind.TypeLiteral || childKind === SyntaxKind.TupleType) { return false; - } + } + break; } + // No explicit rule for given nodes so the result will follow the default value argument + return indentByDefault; + } - /** - * True when the parent node should indent the given child by an explicit rule. - * @param isNextChild If true, we are judging indent of a hypothetical child *after* this one, not the current child. - */ - export function shouldIndentChildNode(settings: FormatCodeSettings, parent: TextRangeWithKind, child?: Node, sourceFile?: SourceFileLike, isNextChild = false): boolean { - return nodeWillIndentChild(settings, parent, child, sourceFile, /*indentByDefault*/ false) - && !(isNextChild && child && isControlFlowEndingStatement(child.kind, parent)); + function isControlFlowEndingStatement(kind: SyntaxKind, parent: TextRangeWithKind): boolean { + switch (kind) { + case SyntaxKind.ReturnStatement: + case SyntaxKind.ThrowStatement: + case SyntaxKind.ContinueStatement: + case SyntaxKind.BreakStatement: + return parent.kind !== SyntaxKind.Block; + default: + return false; } + } - function rangeIsOnOneLine(sourceFile: SourceFileLike, range: TextRangeWithKind) { - const rangeStart = skipTrivia(sourceFile.text, range.pos); - const startLine = sourceFile.getLineAndCharacterOfPosition(rangeStart).line; - const endLine = sourceFile.getLineAndCharacterOfPosition(range.end).line; - return startLine === endLine; - } + /** + * True when the parent node should indent the given child by an explicit rule. + * @param isNextChild If true, we are judging indent of a hypothetical child *after* this one, not the current child. + */ + export function shouldIndentChildNode(settings: FormatCodeSettings, parent: TextRangeWithKind, child?: Node, sourceFile?: SourceFileLike, isNextChild = false): boolean { + return nodeWillIndentChild(settings, parent, child, sourceFile, /*indentByDefault*/ false) + && !(isNextChild && child && isControlFlowEndingStatement(child.kind, parent)); + } + + function rangeIsOnOneLine(sourceFile: SourceFileLike, range: TextRangeWithKind) { + const rangeStart = skipTrivia(sourceFile.text, range.pos); + const startLine = sourceFile.getLineAndCharacterOfPosition(rangeStart).line; + const endLine = sourceFile.getLineAndCharacterOfPosition(range.end).line; + return startLine === endLine; } } +} diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index fa0749df0cecc..d91015b8ad9be 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -1,261 +1,261 @@ /* @internal */ namespace ts { - export function getEditsForFileRename( - program: Program, - oldFileOrDirPath: string, - newFileOrDirPath: string, - host: LanguageServiceHost, - formatContext: formatting.FormatContext, - preferences: UserPreferences, - sourceMapper: SourceMapper, - ): readonly FileTextChanges[] { - const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - const oldToNew = getPathUpdater(oldFileOrDirPath, newFileOrDirPath, getCanonicalFileName, sourceMapper); - const newToOld = getPathUpdater(newFileOrDirPath, oldFileOrDirPath, getCanonicalFileName, sourceMapper); - return textChanges.ChangeTracker.with({ host, formatContext, preferences }, changeTracker => { - updateTsconfigFiles(program, changeTracker, oldToNew, oldFileOrDirPath, newFileOrDirPath, host.getCurrentDirectory(), useCaseSensitiveFileNames); - updateImports(program, changeTracker, oldToNew, newToOld, host, getCanonicalFileName); - }); - } +export function getEditsForFileRename( + program: Program, + oldFileOrDirPath: string, + newFileOrDirPath: string, + host: LanguageServiceHost, + formatContext: formatting.FormatContext, + preferences: UserPreferences, + sourceMapper: SourceMapper, +): readonly FileTextChanges[] { + const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + const oldToNew = getPathUpdater(oldFileOrDirPath, newFileOrDirPath, getCanonicalFileName, sourceMapper); + const newToOld = getPathUpdater(newFileOrDirPath, oldFileOrDirPath, getCanonicalFileName, sourceMapper); + return textChanges.ChangeTracker.with({ host, formatContext, preferences }, changeTracker => { + updateTsconfigFiles(program, changeTracker, oldToNew, oldFileOrDirPath, newFileOrDirPath, host.getCurrentDirectory(), useCaseSensitiveFileNames); + updateImports(program, changeTracker, oldToNew, newToOld, host, getCanonicalFileName); + }); +} - /** If 'path' refers to an old directory, returns path in the new directory. */ - type PathUpdater = (path: string) => string | undefined; - // exported for tests - export function getPathUpdater(oldFileOrDirPath: string, newFileOrDirPath: string, getCanonicalFileName: GetCanonicalFileName, sourceMapper: SourceMapper | undefined): PathUpdater { - const canonicalOldPath = getCanonicalFileName(oldFileOrDirPath); - return path => { - const originalPath = sourceMapper && sourceMapper.tryGetSourcePosition({ fileName: path, pos: 0 }); - const updatedPath = getUpdatedPath(originalPath ? originalPath.fileName : path); - return originalPath - ? updatedPath === undefined ? undefined : makeCorrespondingRelativeChange(originalPath.fileName, updatedPath, path, getCanonicalFileName) - : updatedPath; - }; +/** If 'path' refers to an old directory, returns path in the new directory. */ +type PathUpdater = (path: string) => string | undefined; +// exported for tests +export function getPathUpdater(oldFileOrDirPath: string, newFileOrDirPath: string, getCanonicalFileName: GetCanonicalFileName, sourceMapper: SourceMapper | undefined): PathUpdater { + const canonicalOldPath = getCanonicalFileName(oldFileOrDirPath); + return path => { + const originalPath = sourceMapper && sourceMapper.tryGetSourcePosition({ fileName: path, pos: 0 }); + const updatedPath = getUpdatedPath(originalPath ? originalPath.fileName : path); + return originalPath + ? updatedPath === undefined ? undefined : makeCorrespondingRelativeChange(originalPath.fileName, updatedPath, path, getCanonicalFileName) + : updatedPath; + }; - function getUpdatedPath(pathToUpdate: string): string | undefined { - if (getCanonicalFileName(pathToUpdate) === canonicalOldPath) return newFileOrDirPath; - const suffix = tryRemoveDirectoryPrefix(pathToUpdate, canonicalOldPath, getCanonicalFileName); - return suffix === undefined ? undefined : newFileOrDirPath + "/" + suffix; - } + function getUpdatedPath(pathToUpdate: string): string | undefined { + if (getCanonicalFileName(pathToUpdate) === canonicalOldPath) return newFileOrDirPath; + const suffix = tryRemoveDirectoryPrefix(pathToUpdate, canonicalOldPath, getCanonicalFileName); + return suffix === undefined ? undefined : newFileOrDirPath + "/" + suffix; } +} - // Relative path from a0 to b0 should be same as relative path from a1 to b1. Returns b1. - function makeCorrespondingRelativeChange(a0: string, b0: string, a1: string, getCanonicalFileName: GetCanonicalFileName): string { - const rel = getRelativePathFromFile(a0, b0, getCanonicalFileName); - return combinePathsSafe(getDirectoryPath(a1), rel); - } +// Relative path from a0 to b0 should be same as relative path from a1 to b1. Returns b1. +function makeCorrespondingRelativeChange(a0: string, b0: string, a1: string, getCanonicalFileName: GetCanonicalFileName): string { + const rel = getRelativePathFromFile(a0, b0, getCanonicalFileName); + return combinePathsSafe(getDirectoryPath(a1), rel); +} - function updateTsconfigFiles(program: Program, changeTracker: textChanges.ChangeTracker, oldToNew: PathUpdater, oldFileOrDirPath: string, newFileOrDirPath: string, currentDirectory: string, useCaseSensitiveFileNames: boolean): void { - const { configFile } = program.getCompilerOptions(); - if (!configFile) return; - const configDir = getDirectoryPath(configFile.fileName); +function updateTsconfigFiles(program: Program, changeTracker: textChanges.ChangeTracker, oldToNew: PathUpdater, oldFileOrDirPath: string, newFileOrDirPath: string, currentDirectory: string, useCaseSensitiveFileNames: boolean): void { + const { configFile } = program.getCompilerOptions(); + if (!configFile) return; + const configDir = getDirectoryPath(configFile.fileName); - const jsonObjectLiteral = getTsConfigObjectLiteralExpression(configFile); - if (!jsonObjectLiteral) return; + const jsonObjectLiteral = getTsConfigObjectLiteralExpression(configFile); + if (!jsonObjectLiteral) return; - forEachProperty(jsonObjectLiteral, (property, propertyName) => { - switch (propertyName) { - case "files": - case "include": - case "exclude": { - const foundExactMatch = updatePaths(property); - if (foundExactMatch || propertyName !== "include" || !isArrayLiteralExpression(property.initializer)) return; - const includes = mapDefined(property.initializer.elements, e => isStringLiteral(e) ? e.text : undefined); - if (includes.length === 0) return; - const matchers = getFileMatcherPatterns(configDir, /*excludes*/ [], includes, useCaseSensitiveFileNames, currentDirectory); - // If there isn't some include for this, add a new one. - if (getRegexFromPattern(Debug.checkDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(oldFileOrDirPath) && - !getRegexFromPattern(Debug.checkDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(newFileOrDirPath)) { - changeTracker.insertNodeAfter(configFile, last(property.initializer.elements), factory.createStringLiteral(relativePath(newFileOrDirPath))); - } - return; + forEachProperty(jsonObjectLiteral, (property, propertyName) => { + switch (propertyName) { + case "files": + case "include": + case "exclude": { + const foundExactMatch = updatePaths(property); + if (foundExactMatch || propertyName !== "include" || !isArrayLiteralExpression(property.initializer)) return; + const includes = mapDefined(property.initializer.elements, e => isStringLiteral(e) ? e.text : undefined); + if (includes.length === 0) return; + const matchers = getFileMatcherPatterns(configDir, /*excludes*/ [], includes, useCaseSensitiveFileNames, currentDirectory); + // If there isn't some include for this, add a new one. + if (getRegexFromPattern(Debug.checkDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(oldFileOrDirPath) && + !getRegexFromPattern(Debug.checkDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(newFileOrDirPath)) { + changeTracker.insertNodeAfter(configFile, last(property.initializer.elements), factory.createStringLiteral(relativePath(newFileOrDirPath))); } - case "compilerOptions": - forEachProperty(property.initializer, (property, propertyName) => { - const option = getOptionFromName(propertyName); - if (option && (option.isFilePath || option.type === "list" && option.element.isFilePath)) { - updatePaths(property); - } - else if (propertyName === "paths") { - forEachProperty(property.initializer, (pathsProperty) => { - if (!isArrayLiteralExpression(pathsProperty.initializer)) return; - for (const e of pathsProperty.initializer.elements) { - tryUpdateString(e); - } - }); - } - }); - return; + return; } - }); + case "compilerOptions": + forEachProperty(property.initializer, (property, propertyName) => { + const option = getOptionFromName(propertyName); + if (option && (option.isFilePath || option.type === "list" && option.element.isFilePath)) { + updatePaths(property); + } + else if (propertyName === "paths") { + forEachProperty(property.initializer, (pathsProperty) => { + if (!isArrayLiteralExpression(pathsProperty.initializer)) return; + for (const e of pathsProperty.initializer.elements) { + tryUpdateString(e); + } + }); + } + }); + return; + } + }); - function updatePaths(property: PropertyAssignment): boolean { - const elements = isArrayLiteralExpression(property.initializer) ? property.initializer.elements : [property.initializer]; - let foundExactMatch = false; - for (const element of elements) { - foundExactMatch = tryUpdateString(element) || foundExactMatch; - } - return foundExactMatch; + function updatePaths(property: PropertyAssignment): boolean { + const elements = isArrayLiteralExpression(property.initializer) ? property.initializer.elements : [property.initializer]; + let foundExactMatch = false; + for (const element of elements) { + foundExactMatch = tryUpdateString(element) || foundExactMatch; } + return foundExactMatch; + } - function tryUpdateString(element: Expression): boolean { - if (!isStringLiteral(element)) return false; - const elementFileName = combinePathsSafe(configDir, element.text); + function tryUpdateString(element: Expression): boolean { + if (!isStringLiteral(element)) return false; + const elementFileName = combinePathsSafe(configDir, element.text); - const updated = oldToNew(elementFileName); - if (updated !== undefined) { - changeTracker.replaceRangeWithText(configFile!, createStringRange(element, configFile!), relativePath(updated)); - return true; - } - return false; + const updated = oldToNew(elementFileName); + if (updated !== undefined) { + changeTracker.replaceRangeWithText(configFile!, createStringRange(element, configFile!), relativePath(updated)); + return true; } + return false; + } - function relativePath(path: string): string { - return getRelativePathFromDirectory(configDir, path, /*ignoreCase*/ !useCaseSensitiveFileNames); - } + function relativePath(path: string): string { + return getRelativePathFromDirectory(configDir, path, /*ignoreCase*/ !useCaseSensitiveFileNames); } +} - function updateImports( - program: Program, - changeTracker: textChanges.ChangeTracker, - oldToNew: PathUpdater, - newToOld: PathUpdater, - host: LanguageServiceHost, - getCanonicalFileName: GetCanonicalFileName, - ): void { - const allFiles = program.getSourceFiles(); - for (const sourceFile of allFiles) { - const newFromOld = oldToNew(sourceFile.fileName); - const newImportFromPath = newFromOld ?? sourceFile.fileName; - const newImportFromDirectory = getDirectoryPath(newImportFromPath); +function updateImports( + program: Program, + changeTracker: textChanges.ChangeTracker, + oldToNew: PathUpdater, + newToOld: PathUpdater, + host: LanguageServiceHost, + getCanonicalFileName: GetCanonicalFileName, +): void { + const allFiles = program.getSourceFiles(); + for (const sourceFile of allFiles) { + const newFromOld = oldToNew(sourceFile.fileName); + const newImportFromPath = newFromOld ?? sourceFile.fileName; + const newImportFromDirectory = getDirectoryPath(newImportFromPath); - const oldFromNew: string | undefined = newToOld(sourceFile.fileName); - const oldImportFromPath: string = oldFromNew || sourceFile.fileName; - const oldImportFromDirectory = getDirectoryPath(oldImportFromPath); + const oldFromNew: string | undefined = newToOld(sourceFile.fileName); + const oldImportFromPath: string = oldFromNew || sourceFile.fileName; + const oldImportFromDirectory = getDirectoryPath(oldImportFromPath); - const importingSourceFileMoved = newFromOld !== undefined || oldFromNew !== undefined; + const importingSourceFileMoved = newFromOld !== undefined || oldFromNew !== undefined; - updateImportsWorker(sourceFile, changeTracker, - referenceText => { - if (!pathIsRelative(referenceText)) return undefined; - const oldAbsolute = combinePathsSafe(oldImportFromDirectory, referenceText); - const newAbsolute = oldToNew(oldAbsolute); - return newAbsolute === undefined ? undefined : ensurePathIsNonModuleName(getRelativePathFromDirectory(newImportFromDirectory, newAbsolute, getCanonicalFileName)); - }, - importLiteral => { - const importedModuleSymbol = program.getTypeChecker().getSymbolAtLocation(importLiteral); - // No need to update if it's an ambient module^M - if (importedModuleSymbol?.declarations && importedModuleSymbol.declarations.some(d => isAmbientModule(d))) return undefined; + updateImportsWorker(sourceFile, changeTracker, + referenceText => { + if (!pathIsRelative(referenceText)) return undefined; + const oldAbsolute = combinePathsSafe(oldImportFromDirectory, referenceText); + const newAbsolute = oldToNew(oldAbsolute); + return newAbsolute === undefined ? undefined : ensurePathIsNonModuleName(getRelativePathFromDirectory(newImportFromDirectory, newAbsolute, getCanonicalFileName)); + }, + importLiteral => { + const importedModuleSymbol = program.getTypeChecker().getSymbolAtLocation(importLiteral); + // No need to update if it's an ambient module^M + if (importedModuleSymbol?.declarations && importedModuleSymbol.declarations.some(d => isAmbientModule(d))) return undefined; - const toImport = oldFromNew !== undefined - // If we're at the new location (file was already renamed), need to redo module resolution starting from the old location. - // TODO:GH#18217 - ? getSourceFileToImportFromResolved(importLiteral, resolveModuleName(importLiteral.text, oldImportFromPath, program.getCompilerOptions(), host as ModuleResolutionHost), - oldToNew, allFiles) - : getSourceFileToImport(importedModuleSymbol, importLiteral, sourceFile, program, host, oldToNew); + const toImport = oldFromNew !== undefined + // If we're at the new location (file was already renamed), need to redo module resolution starting from the old location. + // TODO:GH#18217 + ? getSourceFileToImportFromResolved(importLiteral, resolveModuleName(importLiteral.text, oldImportFromPath, program.getCompilerOptions(), host as ModuleResolutionHost), + oldToNew, allFiles) + : getSourceFileToImport(importedModuleSymbol, importLiteral, sourceFile, program, host, oldToNew); - // Need an update if the imported file moved, or the importing file moved and was using a relative path. - return toImport !== undefined && (toImport.updated || (importingSourceFileMoved && pathIsRelative(importLiteral.text))) - ? moduleSpecifiers.updateModuleSpecifier(program.getCompilerOptions(), getCanonicalFileName(newImportFromPath) as Path, toImport.newFileName, createModuleSpecifierResolutionHost(program, host), importLiteral.text) - : undefined; - }); - } + // Need an update if the imported file moved, or the importing file moved and was using a relative path. + return toImport !== undefined && (toImport.updated || (importingSourceFileMoved && pathIsRelative(importLiteral.text))) + ? moduleSpecifiers.updateModuleSpecifier(program.getCompilerOptions(), getCanonicalFileName(newImportFromPath) as Path, toImport.newFileName, createModuleSpecifierResolutionHost(program, host), importLiteral.text) + : undefined; + }); } +} - function combineNormal(pathA: string, pathB: string): string { - return normalizePath(combinePaths(pathA, pathB)); - } - function combinePathsSafe(pathA: string, pathB: string): string { - return ensurePathIsNonModuleName(combineNormal(pathA, pathB)); - } +function combineNormal(pathA: string, pathB: string): string { + return normalizePath(combinePaths(pathA, pathB)); +} +function combinePathsSafe(pathA: string, pathB: string): string { + return ensurePathIsNonModuleName(combineNormal(pathA, pathB)); +} - interface ToImport { - readonly newFileName: string; - /** True if the imported file was renamed. */ - readonly updated: boolean; +interface ToImport { + readonly newFileName: string; + /** True if the imported file was renamed. */ + readonly updated: boolean; +} +function getSourceFileToImport( + importedModuleSymbol: Symbol | undefined, + importLiteral: StringLiteralLike, + importingSourceFile: SourceFile, + program: Program, + host: LanguageServiceHost, + oldToNew: PathUpdater, +): ToImport | undefined { + if (importedModuleSymbol) { + // `find` should succeed because we checked for ambient modules before calling this function. + const oldFileName = find(importedModuleSymbol.declarations!, isSourceFile)!.fileName; + const newFileName = oldToNew(oldFileName); + return newFileName === undefined ? { newFileName: oldFileName, updated: false } : { newFileName, updated: true }; } - function getSourceFileToImport( - importedModuleSymbol: Symbol | undefined, - importLiteral: StringLiteralLike, - importingSourceFile: SourceFile, - program: Program, - host: LanguageServiceHost, - oldToNew: PathUpdater, - ): ToImport | undefined { - if (importedModuleSymbol) { - // `find` should succeed because we checked for ambient modules before calling this function. - const oldFileName = find(importedModuleSymbol.declarations!, isSourceFile)!.fileName; - const newFileName = oldToNew(oldFileName); - return newFileName === undefined ? { newFileName: oldFileName, updated: false } : { newFileName, updated: true }; - } - else { - const mode = getModeForUsageLocation(importingSourceFile, importLiteral); - const resolved = host.resolveModuleNames - ? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName, mode) - : program.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName, mode); - return getSourceFileToImportFromResolved(importLiteral, resolved, oldToNew, program.getSourceFiles()); - } + else { + const mode = getModeForUsageLocation(importingSourceFile, importLiteral); + const resolved = host.resolveModuleNames + ? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName, mode) + : program.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName, mode); + return getSourceFileToImportFromResolved(importLiteral, resolved, oldToNew, program.getSourceFiles()); } +} - function getSourceFileToImportFromResolved(importLiteral: StringLiteralLike, resolved: ResolvedModuleWithFailedLookupLocations | undefined, oldToNew: PathUpdater, sourceFiles: readonly SourceFile[]): ToImport | undefined { - // Search through all locations looking for a moved file, and only then test already existing files. - // This is because if `a.ts` is compiled to `a.js` and `a.ts` is moved, we don't want to resolve anything to `a.js`, but to `a.ts`'s new location. - if (!resolved) return undefined; - - // First try resolved module - if (resolved.resolvedModule) { - const result = tryChange(resolved.resolvedModule.resolvedFileName); - if (result) return result; - } +function getSourceFileToImportFromResolved(importLiteral: StringLiteralLike, resolved: ResolvedModuleWithFailedLookupLocations | undefined, oldToNew: PathUpdater, sourceFiles: readonly SourceFile[]): ToImport | undefined { + // Search through all locations looking for a moved file, and only then test already existing files. + // This is because if `a.ts` is compiled to `a.js` and `a.ts` is moved, we don't want to resolve anything to `a.js`, but to `a.ts`'s new location. + if (!resolved) return undefined; - // Then failed lookups that are in the list of sources - const result = forEach(resolved.failedLookupLocations, tryChangeWithIgnoringPackageJsonExisting) - // Then failed lookups except package.json since we dont want to touch them (only included ts/js files). - // At this point, the confidence level of this fix being correct is too low to change bare specifiers or absolute paths. - || pathIsRelative(importLiteral.text) && forEach(resolved.failedLookupLocations, tryChangeWithIgnoringPackageJson); + // First try resolved module + if (resolved.resolvedModule) { + const result = tryChange(resolved.resolvedModule.resolvedFileName); if (result) return result; + } - // If nothing changed, then result is resolved module file thats not updated - return resolved.resolvedModule && { newFileName: resolved.resolvedModule.resolvedFileName, updated: false }; + // Then failed lookups that are in the list of sources + const result = forEach(resolved.failedLookupLocations, tryChangeWithIgnoringPackageJsonExisting) + // Then failed lookups except package.json since we dont want to touch them (only included ts/js files). + // At this point, the confidence level of this fix being correct is too low to change bare specifiers or absolute paths. + || pathIsRelative(importLiteral.text) && forEach(resolved.failedLookupLocations, tryChangeWithIgnoringPackageJson); + if (result) return result; - function tryChangeWithIgnoringPackageJsonExisting(oldFileName: string) { - const newFileName = oldToNew(oldFileName); - return newFileName && find(sourceFiles, src => src.fileName === newFileName) - ? tryChangeWithIgnoringPackageJson(oldFileName) : undefined; - } + // If nothing changed, then result is resolved module file thats not updated + return resolved.resolvedModule && { newFileName: resolved.resolvedModule.resolvedFileName, updated: false }; - function tryChangeWithIgnoringPackageJson(oldFileName: string) { - return !endsWith(oldFileName, "/package.json") ? tryChange(oldFileName) : undefined; - } + function tryChangeWithIgnoringPackageJsonExisting(oldFileName: string) { + const newFileName = oldToNew(oldFileName); + return newFileName && find(sourceFiles, src => src.fileName === newFileName) + ? tryChangeWithIgnoringPackageJson(oldFileName) : undefined; + } - function tryChange(oldFileName: string) { - const newFileName = oldToNew(oldFileName); - return newFileName && { newFileName, updated: true }; - } + function tryChangeWithIgnoringPackageJson(oldFileName: string) { + return !endsWith(oldFileName, "/package.json") ? tryChange(oldFileName) : undefined; } - function updateImportsWorker(sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker, updateRef: (refText: string) => string | undefined, updateImport: (importLiteral: StringLiteralLike) => string | undefined) { - for (const ref of sourceFile.referencedFiles || emptyArray) { // TODO: GH#26162 - const updated = updateRef(ref.fileName); - if (updated !== undefined && updated !== sourceFile.text.slice(ref.pos, ref.end)) changeTracker.replaceRangeWithText(sourceFile, ref, updated); - } + function tryChange(oldFileName: string) { + const newFileName = oldToNew(oldFileName); + return newFileName && { newFileName, updated: true }; + } +} - for (const importStringLiteral of sourceFile.imports) { - const updated = updateImport(importStringLiteral); - if (updated !== undefined && updated !== importStringLiteral.text) changeTracker.replaceRangeWithText(sourceFile, createStringRange(importStringLiteral, sourceFile), updated); - } +function updateImportsWorker(sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker, updateRef: (refText: string) => string | undefined, updateImport: (importLiteral: StringLiteralLike) => string | undefined) { + for (const ref of sourceFile.referencedFiles || emptyArray) { // TODO: GH#26162 + const updated = updateRef(ref.fileName); + if (updated !== undefined && updated !== sourceFile.text.slice(ref.pos, ref.end)) changeTracker.replaceRangeWithText(sourceFile, ref, updated); } - function createStringRange(node: StringLiteralLike, sourceFile: SourceFileLike): TextRange { - return createRange(node.getStart(sourceFile) + 1, node.end - 1); + for (const importStringLiteral of sourceFile.imports) { + const updated = updateImport(importStringLiteral); + if (updated !== undefined && updated !== importStringLiteral.text) changeTracker.replaceRangeWithText(sourceFile, createStringRange(importStringLiteral, sourceFile), updated); } +} - function forEachProperty(objectLiteral: Expression, cb: (property: PropertyAssignment, propertyName: string) => void) { - if (!isObjectLiteralExpression(objectLiteral)) return; - for (const property of objectLiteral.properties) { - if (isPropertyAssignment(property) && isStringLiteral(property.name)) { - cb(property, property.name.text); - } +function createStringRange(node: StringLiteralLike, sourceFile: SourceFileLike): TextRange { + return createRange(node.getStart(sourceFile) + 1, node.end - 1); +} + +function forEachProperty(objectLiteral: Expression, cb: (property: PropertyAssignment, propertyName: string) => void) { + if (!isObjectLiteralExpression(objectLiteral)) return; + for (const property of objectLiteral.properties) { + if (isPropertyAssignment(property) && isStringLiteral(property.name)) { + cb(property, property.name.text); } } } +} diff --git a/src/services/globalThisShim.ts b/src/services/globalThisShim.ts index 899bb009895c0..f14fe778b302e 100644 --- a/src/services/globalThisShim.ts +++ b/src/services/globalThisShim.ts @@ -56,4 +56,4 @@ if (typeof process === "undefined" || process.browser) { //@ts-ignore globalThis.toolsVersion = ts.versionMajorMinor; -} \ No newline at end of file +} diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 9ae8dda40f84c..79d4967a395f6 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -1,445 +1,445 @@ /* @internal */ namespace ts.GoToDefinition { - export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): readonly DefinitionInfo[] | undefined { - const resolvedRef = getReferenceAtPosition(sourceFile, position, program); - const fileReferenceDefinition = resolvedRef && [getDefinitionInfoForFileReference(resolvedRef.reference.fileName, resolvedRef.fileName, resolvedRef.unverified)] || emptyArray; - if (resolvedRef?.file) { - // If `file` is missing, do a symbol-based lookup as well - return fileReferenceDefinition; - } - - const node = getTouchingPropertyName(sourceFile, position); - if (node === sourceFile) { - return undefined; - } +export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): readonly DefinitionInfo[] | undefined { + const resolvedRef = getReferenceAtPosition(sourceFile, position, program); + const fileReferenceDefinition = resolvedRef && [getDefinitionInfoForFileReference(resolvedRef.reference.fileName, resolvedRef.fileName, resolvedRef.unverified)] || emptyArray; + if (resolvedRef?.file) { + // If `file` is missing, do a symbol-based lookup as well + return fileReferenceDefinition; + } - const { parent } = node; - const typeChecker = program.getTypeChecker(); + const node = getTouchingPropertyName(sourceFile, position); + if (node === sourceFile) { + return undefined; + } - if (node.kind === SyntaxKind.OverrideKeyword || (isJSDocOverrideTag(node) && rangeContainsPosition(node.tagName, position))) { - return getDefinitionFromOverriddenMember(typeChecker, node) || emptyArray; - } + const { parent } = node; + const typeChecker = program.getTypeChecker(); - // Labels - if (isJumpStatementTarget(node)) { - const label = getTargetLabel(node.parent, node.text); - return label ? [createDefinitionInfoFromName(typeChecker, label, ScriptElementKind.label, node.text, /*containerName*/ undefined!)] : undefined; // TODO: GH#18217 - } + if (node.kind === SyntaxKind.OverrideKeyword || (isJSDocOverrideTag(node) && rangeContainsPosition(node.tagName, position))) { + return getDefinitionFromOverriddenMember(typeChecker, node) || emptyArray; + } - if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) { - const classDecl = node.parent.parent; - const symbol = getSymbol(classDecl, typeChecker); - const staticBlocks = filter(classDecl.members, isClassStaticBlockDeclaration); - const containerName = symbol ? typeChecker.symbolToString(symbol, classDecl) : ""; - const sourceFile = node.getSourceFile(); - return map(staticBlocks, staticBlock => { - let { pos } = moveRangePastModifiers(staticBlock); - pos = skipTrivia(sourceFile.text, pos); - return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, { start: pos, length: "static".length }); - }); - } + // Labels + if (isJumpStatementTarget(node)) { + const label = getTargetLabel(node.parent, node.text); + return label ? [createDefinitionInfoFromName(typeChecker, label, ScriptElementKind.label, node.text, /*containerName*/ undefined!)] : undefined; // TODO: GH#18217 + } - const symbol = getSymbol(node, typeChecker); + if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) { + const classDecl = node.parent.parent; + const symbol = getSymbol(classDecl, typeChecker); + const staticBlocks = filter(classDecl.members, isClassStaticBlockDeclaration); + const containerName = symbol ? typeChecker.symbolToString(symbol, classDecl) : ""; + const sourceFile = node.getSourceFile(); + return map(staticBlocks, staticBlock => { + let { pos } = moveRangePastModifiers(staticBlock); + pos = skipTrivia(sourceFile.text, pos); + return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, { start: pos, length: "static".length }); + }); + } - // Could not find a symbol e.g. node is string or number keyword, - // or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol - if (!symbol) { - return concatenate(fileReferenceDefinition, getDefinitionInfoForIndexSignatures(node, typeChecker)); - } + const symbol = getSymbol(node, typeChecker); - const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node); - // Don't go to the component constructor definition for a JSX element, just go to the component definition. - if (calledDeclaration && !(isJsxOpeningLikeElement(node.parent) && isConstructorLike(calledDeclaration))) { - const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration); - // For a function, if this is the original function definition, return just sigInfo. - // If this is the original constructor definition, parent is the class. - if (typeChecker.getRootSymbols(symbol).some(s => symbolMatchesSignature(s, calledDeclaration))) { - return [sigInfo]; - } - else { - const defs = getDefinitionFromSymbol(typeChecker, symbol, node, calledDeclaration) || emptyArray; - // For a 'super()' call, put the signature first, else put the variable first. - return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo]; - } - } + // Could not find a symbol e.g. node is string or number keyword, + // or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol + if (!symbol) { + return concatenate(fileReferenceDefinition, getDefinitionInfoForIndexSignatures(node, typeChecker)); + } - // Because name in short-hand property assignment has two different meanings: property name and property value, - // using go-to-definition at such position should go to the variable declaration of the property value rather than - // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition - // is performed at the location of property access, we would like to go to definition of the property in the short-hand - // assignment. This case and others are handled by the following code. - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration); - const definitions = shorthandSymbol?.declarations ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node)) : emptyArray; - return concatenate(definitions, getDefinitionFromObjectLiteralElement(typeChecker, node) || emptyArray); + const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node); + // Don't go to the component constructor definition for a JSX element, just go to the component definition. + if (calledDeclaration && !(isJsxOpeningLikeElement(node.parent) && isConstructorLike(calledDeclaration))) { + const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration); + // For a function, if this is the original function definition, return just sigInfo. + // If this is the original constructor definition, parent is the class. + if (typeChecker.getRootSymbols(symbol).some(s => symbolMatchesSignature(s, calledDeclaration))) { + return [sigInfo]; } - - // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the - // declaration the symbol (which is itself), we should try to get to the original type of the ObjectBindingPattern - // and return the property declaration for the referenced property. - // For example: - // import('./foo').then(({ b/*goto*/ar }) => undefined); => should get use to the declaration in file "./foo" - // - // function bar(onfulfilled: (value: T) => void) { //....} - // interface Test { - // pr/*destination*/op1: number - // } - // bar(({pr/*goto*/op1})=>{}); - if (isPropertyName(node) && isBindingElement(parent) && isObjectBindingPattern(parent.parent) && - (node === (parent.propertyName || parent.name))) { - const name = getNameFromPropertyName(node); - const type = typeChecker.getTypeAtLocation(parent.parent); - return name === undefined ? emptyArray : flatMap(type.isUnion() ? type.types : [type], t => { - const prop = t.getProperty(name); - return prop && getDefinitionFromSymbol(typeChecker, prop, node); - }); + else { + const defs = getDefinitionFromSymbol(typeChecker, symbol, node, calledDeclaration) || emptyArray; + // For a 'super()' call, put the signature first, else put the variable first. + return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo]; } - - return concatenate(fileReferenceDefinition, getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node)); } - /** - * True if we should not add definitions for both the signature symbol and the definition symbol. - * True for `const |f = |() => 0`, false for `function |f() {} const |g = f;`. - * Also true for any assignment RHS. - */ - function symbolMatchesSignature(s: Symbol, calledDeclaration: SignatureDeclaration) { - return s === calledDeclaration.symbol - || s === calledDeclaration.symbol.parent - || isAssignmentExpression(calledDeclaration.parent) - || (!isCallLikeExpression(calledDeclaration.parent) && s === calledDeclaration.parent.symbol); + // Because name in short-hand property assignment has two different meanings: property name and property value, + // using go-to-definition at such position should go to the variable declaration of the property value rather than + // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition + // is performed at the location of property access, we would like to go to definition of the property in the short-hand + // assignment. This case and others are handled by the following code. + if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration); + const definitions = shorthandSymbol?.declarations ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node)) : emptyArray; + return concatenate(definitions, getDefinitionFromObjectLiteralElement(typeChecker, node) || emptyArray); } - // If the current location we want to find its definition is in an object literal, try to get the contextual type for the - // object literal, lookup the property symbol in the contextual type, and use this for goto-definition. - // For example - // interface Props{ - // /*first*/prop1: number - // prop2: boolean + // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the + // declaration the symbol (which is itself), we should try to get to the original type of the ObjectBindingPattern + // and return the property declaration for the referenced property. + // For example: + // import('./foo').then(({ b/*goto*/ar }) => undefined); => should get use to the declaration in file "./foo" + // + // function bar(onfulfilled: (value: T) => void) { //....} + // interface Test { + // pr/*destination*/op1: number // } - // function Foo(arg: Props) {} - // Foo( { pr/*1*/op1: 10, prop2: true }) - function getDefinitionFromObjectLiteralElement(typeChecker: TypeChecker, node: Node) { - const element = getContainingObjectLiteralElement(node); - if (element) { - const contextualType = element && typeChecker.getContextualType(element.parent); - if (contextualType) { - return flatMap(getPropertySymbolsFromContextualType(element, typeChecker, contextualType, /*unionSymbolOk*/ false), propertySymbol => - getDefinitionFromSymbol(typeChecker, propertySymbol, node)); - } + // bar(({pr/*goto*/op1})=>{}); + if (isPropertyName(node) && isBindingElement(parent) && isObjectBindingPattern(parent.parent) && + (node === (parent.propertyName || parent.name))) { + const name = getNameFromPropertyName(node); + const type = typeChecker.getTypeAtLocation(parent.parent); + return name === undefined ? emptyArray : flatMap(type.isUnion() ? type.types : [type], t => { + const prop = t.getProperty(name); + return prop && getDefinitionFromSymbol(typeChecker, prop, node); + }); + } + + return concatenate(fileReferenceDefinition, getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node)); +} + +/** + * True if we should not add definitions for both the signature symbol and the definition symbol. + * True for `const |f = |() => 0`, false for `function |f() {} const |g = f;`. + * Also true for any assignment RHS. + */ +function symbolMatchesSignature(s: Symbol, calledDeclaration: SignatureDeclaration) { + return s === calledDeclaration.symbol + || s === calledDeclaration.symbol.parent + || isAssignmentExpression(calledDeclaration.parent) + || (!isCallLikeExpression(calledDeclaration.parent) && s === calledDeclaration.parent.symbol); +} + +// If the current location we want to find its definition is in an object literal, try to get the contextual type for the +// object literal, lookup the property symbol in the contextual type, and use this for goto-definition. +// For example +// interface Props{ +// /*first*/prop1: number +// prop2: boolean +// } +// function Foo(arg: Props) {} +// Foo( { pr/*1*/op1: 10, prop2: true }) +function getDefinitionFromObjectLiteralElement(typeChecker: TypeChecker, node: Node) { + const element = getContainingObjectLiteralElement(node); + if (element) { + const contextualType = element && typeChecker.getContextualType(element.parent); + if (contextualType) { + return flatMap(getPropertySymbolsFromContextualType(element, typeChecker, contextualType, /*unionSymbolOk*/ false), propertySymbol => + getDefinitionFromSymbol(typeChecker, propertySymbol, node)); } } +} - function getDefinitionFromOverriddenMember(typeChecker: TypeChecker, node: Node) { - const classElement = findAncestor(node, isClassElement); - if (!(classElement && classElement.name)) return; +function getDefinitionFromOverriddenMember(typeChecker: TypeChecker, node: Node) { + const classElement = findAncestor(node, isClassElement); + if (!(classElement && classElement.name)) return; - const baseDeclaration = findAncestor(classElement, isClassLike); - if (!baseDeclaration) return; + const baseDeclaration = findAncestor(classElement, isClassLike); + if (!baseDeclaration) return; - const baseTypeNode = getEffectiveBaseTypeNode(baseDeclaration); - const baseType = baseTypeNode ? typeChecker.getTypeAtLocation(baseTypeNode) : undefined; - if (!baseType) return; + const baseTypeNode = getEffectiveBaseTypeNode(baseDeclaration); + const baseType = baseTypeNode ? typeChecker.getTypeAtLocation(baseTypeNode) : undefined; + if (!baseType) return; - const name = unescapeLeadingUnderscores(getTextOfPropertyName(classElement.name)); - const symbol = hasStaticModifier(classElement) - ? typeChecker.getPropertyOfType(typeChecker.getTypeOfSymbolAtLocation(baseType.symbol, baseDeclaration), name) - : typeChecker.getPropertyOfType(baseType, name); - if (!symbol) return; + const name = unescapeLeadingUnderscores(getTextOfPropertyName(classElement.name)); + const symbol = hasStaticModifier(classElement) + ? typeChecker.getPropertyOfType(typeChecker.getTypeOfSymbolAtLocation(baseType.symbol, baseDeclaration), name) + : typeChecker.getPropertyOfType(baseType, name); + if (!symbol) return; - return getDefinitionFromSymbol(typeChecker, symbol, node); + return getDefinitionFromSymbol(typeChecker, symbol, node); +} + +export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { reference: FileReference, fileName: string, unverified: boolean, file?: SourceFile } | undefined { + const referencePath = findReferenceInPosition(sourceFile.referencedFiles, position); + if (referencePath) { + const file = program.getSourceFileFromReference(sourceFile, referencePath); + return file && { reference: referencePath, fileName: file.fileName, file, unverified: false }; } - export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { reference: FileReference, fileName: string, unverified: boolean, file?: SourceFile } | undefined { - const referencePath = findReferenceInPosition(sourceFile.referencedFiles, position); - if (referencePath) { - const file = program.getSourceFileFromReference(sourceFile, referencePath); - return file && { reference: referencePath, fileName: file.fileName, file, unverified: false }; - } + const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position); + if (typeReferenceDirective) { + const reference = program.getResolvedTypeReferenceDirectives().get(typeReferenceDirective.fileName); + const file = reference && program.getSourceFile(reference.resolvedFileName!); // TODO:GH#18217 + return file && { reference: typeReferenceDirective, fileName: file.fileName, file, unverified: false }; + } - const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position); - if (typeReferenceDirective) { - const reference = program.getResolvedTypeReferenceDirectives().get(typeReferenceDirective.fileName); - const file = reference && program.getSourceFile(reference.resolvedFileName!); // TODO:GH#18217 - return file && { reference: typeReferenceDirective, fileName: file.fileName, file, unverified: false }; - } + const libReferenceDirective = findReferenceInPosition(sourceFile.libReferenceDirectives, position); + if (libReferenceDirective) { + const file = program.getLibFileFromReference(libReferenceDirective); + return file && { reference: libReferenceDirective, fileName: file.fileName, file, unverified: false }; + } - const libReferenceDirective = findReferenceInPosition(sourceFile.libReferenceDirectives, position); - if (libReferenceDirective) { - const file = program.getLibFileFromReference(libReferenceDirective); - return file && { reference: libReferenceDirective, fileName: file.fileName, file, unverified: false }; + if (sourceFile.resolvedModules?.size()) { + const node = getTokenAtPosition(sourceFile, position); + if (isModuleSpecifierLike(node) && isExternalModuleNameRelative(node.text) && sourceFile.resolvedModules.has(node.text, getModeForUsageLocation(sourceFile, node))) { + const verifiedFileName = sourceFile.resolvedModules.get(node.text, getModeForUsageLocation(sourceFile, node))?.resolvedFileName; + const fileName = verifiedFileName || resolvePath(getDirectoryPath(sourceFile.fileName), node.text); + return { + file: program.getSourceFile(fileName), + fileName, + reference: { + pos: node.getStart(), + end: node.getEnd(), + fileName: node.text + }, + unverified: !verifiedFileName, + }; } + } - if (sourceFile.resolvedModules?.size()) { - const node = getTokenAtPosition(sourceFile, position); - if (isModuleSpecifierLike(node) && isExternalModuleNameRelative(node.text) && sourceFile.resolvedModules.has(node.text, getModeForUsageLocation(sourceFile, node))) { - const verifiedFileName = sourceFile.resolvedModules.get(node.text, getModeForUsageLocation(sourceFile, node))?.resolvedFileName; - const fileName = verifiedFileName || resolvePath(getDirectoryPath(sourceFile.fileName), node.text); - return { - file: program.getSourceFile(fileName), - fileName, - reference: { - pos: node.getStart(), - end: node.getEnd(), - fileName: node.text - }, - unverified: !verifiedFileName, - }; - } - } + return undefined; +} +/// Goto type +export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): readonly DefinitionInfo[] | undefined { + const node = getTouchingPropertyName(sourceFile, position); + if (node === sourceFile) { return undefined; } - /// Goto type - export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): readonly DefinitionInfo[] | undefined { - const node = getTouchingPropertyName(sourceFile, position); - if (node === sourceFile) { - return undefined; - } + const symbol = getSymbol(node, typeChecker); + if (!symbol) return undefined; + + const typeAtLocation = typeChecker.getTypeOfSymbolAtLocation(symbol, node); + const returnType = tryGetReturnTypeOfFunction(symbol, typeAtLocation, typeChecker); + const fromReturnType = returnType && definitionFromType(returnType, typeChecker, node); + // If a function returns 'void' or some other type with no definition, just return the function definition. + const typeDefinitions = fromReturnType && fromReturnType.length !== 0 ? fromReturnType : definitionFromType(typeAtLocation, typeChecker, node); + return typeDefinitions.length ? typeDefinitions + : !(symbol.flags & SymbolFlags.Value) && symbol.flags & SymbolFlags.Type ? getDefinitionFromSymbol(typeChecker, skipAlias(symbol, typeChecker), node) + : undefined; +} - const symbol = getSymbol(node, typeChecker); - if (!symbol) return undefined; +function definitionFromType(type: Type, checker: TypeChecker, node: Node): readonly DefinitionInfo[] { + return flatMap(type.isUnion() && !(type.flags & TypeFlags.Enum) ? type.types : [type], t => + t.symbol && getDefinitionFromSymbol(checker, t.symbol, node)); +} - const typeAtLocation = typeChecker.getTypeOfSymbolAtLocation(symbol, node); - const returnType = tryGetReturnTypeOfFunction(symbol, typeAtLocation, typeChecker); - const fromReturnType = returnType && definitionFromType(returnType, typeChecker, node); - // If a function returns 'void' or some other type with no definition, just return the function definition. - const typeDefinitions = fromReturnType && fromReturnType.length !== 0 ? fromReturnType : definitionFromType(typeAtLocation, typeChecker, node); - return typeDefinitions.length ? typeDefinitions - : !(symbol.flags & SymbolFlags.Value) && symbol.flags & SymbolFlags.Type ? getDefinitionFromSymbol(typeChecker, skipAlias(symbol, typeChecker), node) - : undefined; +function tryGetReturnTypeOfFunction(symbol: Symbol, type: Type, checker: TypeChecker): Type | undefined { + // If the type is just a function's inferred type, + // go-to-type should go to the return type instead, since go-to-definition takes you to the function anyway. + if (type.symbol === symbol || + // At `const f = () => {}`, the symbol is `f` and the type symbol is at `() => {}` + symbol.valueDeclaration && type.symbol && isVariableDeclaration(symbol.valueDeclaration) && symbol.valueDeclaration.initializer === type.symbol.valueDeclaration as Node) { + const sigs = type.getCallSignatures(); + if (sigs.length === 1) return checker.getReturnTypeOfSignature(first(sigs)); } + return undefined; +} - function definitionFromType(type: Type, checker: TypeChecker, node: Node): readonly DefinitionInfo[] { - return flatMap(type.isUnion() && !(type.flags & TypeFlags.Enum) ? type.types : [type], t => - t.symbol && getDefinitionFromSymbol(checker, t.symbol, node)); - } +export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number): DefinitionInfoAndBoundSpan | undefined { + const definitions = getDefinitionAtPosition(program, sourceFile, position); - function tryGetReturnTypeOfFunction(symbol: Symbol, type: Type, checker: TypeChecker): Type | undefined { - // If the type is just a function's inferred type, - // go-to-type should go to the return type instead, since go-to-definition takes you to the function anyway. - if (type.symbol === symbol || - // At `const f = () => {}`, the symbol is `f` and the type symbol is at `() => {}` - symbol.valueDeclaration && type.symbol && isVariableDeclaration(symbol.valueDeclaration) && symbol.valueDeclaration.initializer === type.symbol.valueDeclaration as Node) { - const sigs = type.getCallSignatures(); - if (sigs.length === 1) return checker.getReturnTypeOfSignature(first(sigs)); - } + if (!definitions || definitions.length === 0) { return undefined; } - export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number): DefinitionInfoAndBoundSpan | undefined { - const definitions = getDefinitionAtPosition(program, sourceFile, position); + // Check if position is on triple slash reference. + const comment = findReferenceInPosition(sourceFile.referencedFiles, position) || + findReferenceInPosition(sourceFile.typeReferenceDirectives, position) || + findReferenceInPosition(sourceFile.libReferenceDirectives, position); - if (!definitions || definitions.length === 0) { - return undefined; - } + if (comment) { + return { definitions, textSpan: createTextSpanFromRange(comment) }; + } - // Check if position is on triple slash reference. - const comment = findReferenceInPosition(sourceFile.referencedFiles, position) || - findReferenceInPosition(sourceFile.typeReferenceDirectives, position) || - findReferenceInPosition(sourceFile.libReferenceDirectives, position); + const node = getTouchingPropertyName(sourceFile, position); + const textSpan = createTextSpan(node.getStart(), node.getWidth()); - if (comment) { - return { definitions, textSpan: createTextSpanFromRange(comment) }; - } + return { definitions, textSpan }; +} - const node = getTouchingPropertyName(sourceFile, position); - const textSpan = createTextSpan(node.getStart(), node.getWidth()); +// At 'x.foo', see if the type of 'x' has an index signature, and if so find its declarations. +function getDefinitionInfoForIndexSignatures(node: Node, checker: TypeChecker): DefinitionInfo[] | undefined { + return mapDefined(checker.getIndexInfosAtLocation(node), info => info.declaration && createDefinitionFromSignatureDeclaration(checker, info.declaration)); +} - return { definitions, textSpan }; +function getSymbol(node: Node, checker: TypeChecker): Symbol | undefined { + const symbol = checker.getSymbolAtLocation(node); + // If this is an alias, and the request came at the declaration location + // get the aliased symbol instead. This allows for goto def on an import e.g. + // import {A, B} from "mod"; + // to jump to the implementation directly. + if (symbol?.declarations && symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) { + const aliased = checker.getAliasedSymbol(symbol); + if (aliased.declarations) { + return aliased; + } } + return symbol; +} - // At 'x.foo', see if the type of 'x' has an index signature, and if so find its declarations. - function getDefinitionInfoForIndexSignatures(node: Node, checker: TypeChecker): DefinitionInfo[] | undefined { - return mapDefined(checker.getIndexInfosAtLocation(node), info => info.declaration && createDefinitionFromSignatureDeclaration(checker, info.declaration)); +// Go to the original declaration for cases: +// +// (1) when the aliased symbol was declared in the location(parent). +// (2) when the aliased symbol is originating from an import. +// +function shouldSkipAlias(node: Node, declaration: Node): boolean { + if (node.kind !== SyntaxKind.Identifier) { + return false; } - - function getSymbol(node: Node, checker: TypeChecker): Symbol | undefined { - const symbol = checker.getSymbolAtLocation(node); - // If this is an alias, and the request came at the declaration location - // get the aliased symbol instead. This allows for goto def on an import e.g. - // import {A, B} from "mod"; - // to jump to the implementation directly. - if (symbol?.declarations && symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) { - const aliased = checker.getAliasedSymbol(symbol); - if (aliased.declarations) { - return aliased; - } - } - return symbol; + if (node.parent === declaration) { + return true; } - - // Go to the original declaration for cases: - // - // (1) when the aliased symbol was declared in the location(parent). - // (2) when the aliased symbol is originating from an import. - // - function shouldSkipAlias(node: Node, declaration: Node): boolean { - if (node.kind !== SyntaxKind.Identifier) { - return false; - } - if (node.parent === declaration) { + switch (declaration.kind) { + case SyntaxKind.ImportClause: + case SyntaxKind.ImportEqualsDeclaration: return true; - } - switch (declaration.kind) { - case SyntaxKind.ImportClause: - case SyntaxKind.ImportEqualsDeclaration: - return true; - case SyntaxKind.ImportSpecifier: - return declaration.parent.kind === SyntaxKind.NamedImports; - case SyntaxKind.BindingElement: - case SyntaxKind.VariableDeclaration: - return isInJSFile(declaration) && isRequireVariableDeclaration(declaration); - default: - return false; - } + case SyntaxKind.ImportSpecifier: + return declaration.parent.kind === SyntaxKind.NamedImports; + case SyntaxKind.BindingElement: + case SyntaxKind.VariableDeclaration: + return isInJSFile(declaration) && isRequireVariableDeclaration(declaration); + default: + return false; } +} - function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, declarationNode?: Node): DefinitionInfo[] | undefined { - // There are cases when you extend a function by adding properties to it afterwards, - // we want to strip those extra properties. - // For deduping purposes, we also want to exclude any declarationNodes if provided. - const filteredDeclarations = - filter(symbol.declarations, d => d !== declarationNode && (!isAssignmentDeclaration(d) || d === symbol.valueDeclaration)) - || undefined; - return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(filteredDeclarations, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node)); - - function getConstructSignatureDefinition(): DefinitionInfo[] | undefined { - // Applicable only if we are in a new expression, or we are on a constructor declaration - // and in either case the symbol has a construct signature definition, i.e. class - if (symbol.flags & SymbolFlags.Class && !(symbol.flags & (SymbolFlags.Function | SymbolFlags.Variable)) && (isNewExpressionTarget(node) || node.kind === SyntaxKind.ConstructorKeyword)) { - const cls = find(filteredDeclarations!, isClassLike) || Debug.fail("Expected declaration to have at least one class-like declaration"); - return getSignatureDefinition(cls.members, /*selectConstructors*/ true); - } - } - - function getCallSignatureDefinition(): DefinitionInfo[] | undefined { - return isCallOrNewExpressionTarget(node) || isNameOfFunctionDeclaration(node) - ? getSignatureDefinition(filteredDeclarations, /*selectConstructors*/ false) - : undefined; - } - - function getSignatureDefinition(signatureDeclarations: readonly Declaration[] | undefined, selectConstructors: boolean): DefinitionInfo[] | undefined { - if (!signatureDeclarations) { - return undefined; - } - const declarations = signatureDeclarations.filter(selectConstructors ? isConstructorDeclaration : isFunctionLike); - const declarationsWithBody = declarations.filter(d => !!(d as FunctionLikeDeclaration).body); - - // declarations defined on the global scope can be defined on multiple files. Get all of them. - return declarations.length - ? declarationsWithBody.length !== 0 - ? declarationsWithBody.map(x => createDefinitionInfo(x, typeChecker, symbol, node)) - : [createDefinitionInfo(last(declarations), typeChecker, symbol, node)] - : undefined; +function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, declarationNode?: Node): DefinitionInfo[] | undefined { + // There are cases when you extend a function by adding properties to it afterwards, + // we want to strip those extra properties. + // For deduping purposes, we also want to exclude any declarationNodes if provided. + const filteredDeclarations = + filter(symbol.declarations, d => d !== declarationNode && (!isAssignmentDeclaration(d) || d === symbol.valueDeclaration)) + || undefined; + return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(filteredDeclarations, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node)); + + function getConstructSignatureDefinition(): DefinitionInfo[] | undefined { + // Applicable only if we are in a new expression, or we are on a constructor declaration + // and in either case the symbol has a construct signature definition, i.e. class + if (symbol.flags & SymbolFlags.Class && !(symbol.flags & (SymbolFlags.Function | SymbolFlags.Variable)) && (isNewExpressionTarget(node) || node.kind === SyntaxKind.ConstructorKeyword)) { + const cls = find(filteredDeclarations!, isClassLike) || Debug.fail("Expected declaration to have at least one class-like declaration"); + return getSignatureDefinition(cls.members, /*selectConstructors*/ true); } } - /** Creates a DefinitionInfo from a Declaration, using the declaration's name if possible. */ - function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node): DefinitionInfo { - const symbolName = checker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol - const symbolKind = SymbolDisplay.getSymbolKind(checker, symbol, node); - const containerName = symbol.parent ? checker.symbolToString(symbol.parent, node) : ""; - return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName); + function getCallSignatureDefinition(): DefinitionInfo[] | undefined { + return isCallOrNewExpressionTarget(node) || isNameOfFunctionDeclaration(node) + ? getSignatureDefinition(filteredDeclarations, /*selectConstructors*/ false) + : undefined; } - /** Creates a DefinitionInfo directly from the name of a declaration. */ - function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, textSpan?: TextSpan): DefinitionInfo { - const sourceFile = declaration.getSourceFile(); - if (!textSpan) { - const name = getNameOfDeclaration(declaration) || declaration; - textSpan = createTextSpanFromNode(name, sourceFile); + function getSignatureDefinition(signatureDeclarations: readonly Declaration[] | undefined, selectConstructors: boolean): DefinitionInfo[] | undefined { + if (!signatureDeclarations) { + return undefined; } - return { - fileName: sourceFile.fileName, - textSpan, - kind: symbolKind, - name: symbolName, - containerKind: undefined!, // TODO: GH#18217 - containerName, - ...FindAllReferences.toContextSpan( - textSpan, - sourceFile, - FindAllReferences.getContextNode(declaration) - ), - isLocal: !isDefinitionVisible(checker, declaration) - }; + const declarations = signatureDeclarations.filter(selectConstructors ? isConstructorDeclaration : isFunctionLike); + const declarationsWithBody = declarations.filter(d => !!(d as FunctionLikeDeclaration).body); + + // declarations defined on the global scope can be defined on multiple files. Get all of them. + return declarations.length + ? declarationsWithBody.length !== 0 + ? declarationsWithBody.map(x => createDefinitionInfo(x, typeChecker, symbol, node)) + : [createDefinitionInfo(last(declarations), typeChecker, symbol, node)] + : undefined; } +} - function isDefinitionVisible(checker: TypeChecker, declaration: Declaration): boolean { - if (checker.isDeclarationVisible(declaration)) return true; - if (!declaration.parent) return false; - - // Variable initializers are visible if variable is visible - if (hasInitializer(declaration.parent) && declaration.parent.initializer === declaration) return isDefinitionVisible(checker, declaration.parent as Declaration); - - // Handle some exceptions here like arrow function, members of class and object literal expression which are technically not visible but we want the definition to be determined by its parent - switch (declaration.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - // Private/protected properties/methods are not visible - if (hasEffectiveModifier(declaration, ModifierFlags.Private)) return false; - // Public properties/methods are visible if its parents are visible, so: - // falls through - - case SyntaxKind.Constructor: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ClassExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionExpression: - return isDefinitionVisible(checker, declaration.parent as Declaration); - default: - return false; - } - } +/** Creates a DefinitionInfo from a Declaration, using the declaration's name if possible. */ +function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node): DefinitionInfo { + const symbolName = checker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol + const symbolKind = SymbolDisplay.getSymbolKind(checker, symbol, node); + const containerName = symbol.parent ? checker.symbolToString(symbol.parent, node) : ""; + return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName); +} - function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration): DefinitionInfo { - return createDefinitionInfo(decl, typeChecker, decl.symbol, decl); +/** Creates a DefinitionInfo directly from the name of a declaration. */ +function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, textSpan?: TextSpan): DefinitionInfo { + const sourceFile = declaration.getSourceFile(); + if (!textSpan) { + const name = getNameOfDeclaration(declaration) || declaration; + textSpan = createTextSpanFromNode(name, sourceFile); } + return { + fileName: sourceFile.fileName, + textSpan, + kind: symbolKind, + name: symbolName, + containerKind: undefined!, // TODO: GH#18217 + containerName, + ...FindAllReferences.toContextSpan( + textSpan, + sourceFile, + FindAllReferences.getContextNode(declaration) + ), + isLocal: !isDefinitionVisible(checker, declaration) + }; +} - export function findReferenceInPosition(refs: readonly FileReference[], pos: number): FileReference | undefined { - return find(refs, ref => textRangeContainsPositionInclusive(ref, pos)); +function isDefinitionVisible(checker: TypeChecker, declaration: Declaration): boolean { + if (checker.isDeclarationVisible(declaration)) return true; + if (!declaration.parent) return false; + + // Variable initializers are visible if variable is visible + if (hasInitializer(declaration.parent) && declaration.parent.initializer === declaration) return isDefinitionVisible(checker, declaration.parent as Declaration); + + // Handle some exceptions here like arrow function, members of class and object literal expression which are technically not visible but we want the definition to be determined by its parent + switch (declaration.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + // Private/protected properties/methods are not visible + if (hasEffectiveModifier(declaration, ModifierFlags.Private)) return false; + // Public properties/methods are visible if its parents are visible, so: + // falls through + + case SyntaxKind.Constructor: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ClassExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + return isDefinitionVisible(checker, declaration.parent as Declaration); + default: + return false; } +} - function getDefinitionInfoForFileReference(name: string, targetFileName: string, unverified: boolean): DefinitionInfo { - return { - fileName: targetFileName, - textSpan: createTextSpanFromBounds(0, 0), - kind: ScriptElementKind.scriptElement, - name, - containerName: undefined!, - containerKind: undefined!, // TODO: GH#18217 - unverified, - }; - } +function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration): DefinitionInfo { + return createDefinitionInfo(decl, typeChecker, decl.symbol, decl); +} - /** Returns a CallLikeExpression where `node` is the target being invoked. */ - function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined { - const target = findAncestor(node, n => !isRightSideOfPropertyAccess(n)); - const callLike = target?.parent; - return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target ? callLike : undefined; - } +export function findReferenceInPosition(refs: readonly FileReference[], pos: number): FileReference | undefined { + return find(refs, ref => textRangeContainsPositionInclusive(ref, pos)); +} - function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined { - const callLike = getAncestorCallLikeExpression(node); - const signature = callLike && typeChecker.getResolvedSignature(callLike); - // Don't go to a function type, go to the value having that type. - return tryCast(signature && signature.declaration, (d): d is SignatureDeclaration => isFunctionLike(d) && !isFunctionTypeNode(d)); - } +function getDefinitionInfoForFileReference(name: string, targetFileName: string, unverified: boolean): DefinitionInfo { + return { + fileName: targetFileName, + textSpan: createTextSpanFromBounds(0, 0), + kind: ScriptElementKind.scriptElement, + name, + containerName: undefined!, + containerKind: undefined!, // TODO: GH#18217 + unverified, + }; +} - function isConstructorLike(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.Constructor: - case SyntaxKind.ConstructorType: - case SyntaxKind.ConstructSignature: - return true; - default: - return false; - } +/** Returns a CallLikeExpression where `node` is the target being invoked. */ +function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined { + const target = findAncestor(node, n => !isRightSideOfPropertyAccess(n)); + const callLike = target?.parent; + return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target ? callLike : undefined; +} + +function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined { + const callLike = getAncestorCallLikeExpression(node); + const signature = callLike && typeChecker.getResolvedSignature(callLike); + // Don't go to a function type, go to the value having that type. + return tryCast(signature && signature.declaration, (d): d is SignatureDeclaration => isFunctionLike(d) && !isFunctionTypeNode(d)); +} + +function isConstructorLike(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.ConstructorType: + case SyntaxKind.ConstructSignature: + return true; + default: + return false; } } +} diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index d3c95854f9bbe..c2618d7723c3c 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -1,684 +1,684 @@ /* Code for finding imports of an exported symbol. Used only by FindAllReferences. */ /* @internal */ namespace ts.FindAllReferences { - export interface ImportsResult { - /** For every import of the symbol, the location and local symbol for the import. */ - importSearches: readonly [Identifier, Symbol][]; - /** For rename imports/exports `{ foo as bar }`, `foo` is not a local, so it may be added as a reference immediately without further searching. */ - singleReferences: readonly (Identifier | StringLiteral)[]; - /** List of source files that may (or may not) use the symbol via a namespace. (For UMD modules this is every file.) */ - indirectUsers: readonly SourceFile[]; - } - export type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult; - - /** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */ - export function createImportTracker(sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken | undefined): ImportTracker { - const allDirectImports = getDirectImportsMap(sourceFiles, checker, cancellationToken); - return (exportSymbol, exportInfo, isForRename) => { - const { directImports, indirectUsers } = getImportersForExport(sourceFiles, sourceFilesSet, allDirectImports, exportInfo, checker, cancellationToken); - return { indirectUsers, ...getSearchesFromDirectImports(directImports, exportSymbol, exportInfo.exportKind, checker, isForRename) }; - }; - } +export interface ImportsResult { + /** For every import of the symbol, the location and local symbol for the import. */ + importSearches: readonly [Identifier, Symbol][]; + /** For rename imports/exports `{ foo as bar }`, `foo` is not a local, so it may be added as a reference immediately without further searching. */ + singleReferences: readonly (Identifier | StringLiteral)[]; + /** List of source files that may (or may not) use the symbol via a namespace. (For UMD modules this is every file.) */ + indirectUsers: readonly SourceFile[]; +} +export type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult; + +/** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */ +export function createImportTracker(sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken | undefined): ImportTracker { + const allDirectImports = getDirectImportsMap(sourceFiles, checker, cancellationToken); + return (exportSymbol, exportInfo, isForRename) => { + const { directImports, indirectUsers } = getImportersForExport(sourceFiles, sourceFilesSet, allDirectImports, exportInfo, checker, cancellationToken); + return { indirectUsers, ...getSearchesFromDirectImports(directImports, exportSymbol, exportInfo.exportKind, checker, isForRename) }; + }; +} - /** Info about an exported symbol to perform recursive search on. */ - export interface ExportInfo { - exportingModuleSymbol: Symbol; - exportKind: ExportKind; - } +/** Info about an exported symbol to perform recursive search on. */ +export interface ExportInfo { + exportingModuleSymbol: Symbol; + exportKind: ExportKind; +} - export const enum ExportKind { Named, Default, ExportEquals } - - export const enum ImportExport { Import, Export } - - interface AmbientModuleDeclaration extends ModuleDeclaration { body?: ModuleBlock; } - type SourceFileLike = SourceFile | AmbientModuleDeclaration; - // Identifier for the case of `const x = require("y")`. - type Importer = AnyImportOrReExport | ValidImportTypeNode | Identifier; - type ImporterOrCallExpression = Importer | CallExpression; - - /** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */ - function getImportersForExport( - sourceFiles: readonly SourceFile[], - sourceFilesSet: ReadonlySet, - allDirectImports: ESMap, - { exportingModuleSymbol, exportKind }: ExportInfo, - checker: TypeChecker, - cancellationToken: CancellationToken | undefined, - ): { directImports: Importer[], indirectUsers: readonly SourceFile[] } { - const markSeenDirectImport = nodeSeenTracker(); - const markSeenIndirectUser = nodeSeenTracker(); - const directImports: Importer[] = []; - const isAvailableThroughGlobal = !!exportingModuleSymbol.globalExports; - const indirectUserDeclarations: SourceFileLike[] | undefined = isAvailableThroughGlobal ? undefined : []; - - handleDirectImports(exportingModuleSymbol); - - return { directImports, indirectUsers: getIndirectUsers() }; - - function getIndirectUsers(): readonly SourceFile[] { - if (isAvailableThroughGlobal) { - // It has `export as namespace`, so anything could potentially use it. - return sourceFiles; - } +export const enum ExportKind { Named, Default, ExportEquals } + +export const enum ImportExport { Import, Export } + +interface AmbientModuleDeclaration extends ModuleDeclaration { body?: ModuleBlock; } +type SourceFileLike = SourceFile | AmbientModuleDeclaration; +// Identifier for the case of `const x = require("y")`. +type Importer = AnyImportOrReExport | ValidImportTypeNode | Identifier; +type ImporterOrCallExpression = Importer | CallExpression; + +/** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */ +function getImportersForExport( + sourceFiles: readonly SourceFile[], + sourceFilesSet: ReadonlySet, + allDirectImports: ESMap, + { exportingModuleSymbol, exportKind }: ExportInfo, + checker: TypeChecker, + cancellationToken: CancellationToken | undefined, +): { directImports: Importer[], indirectUsers: readonly SourceFile[] } { + const markSeenDirectImport = nodeSeenTracker(); + const markSeenIndirectUser = nodeSeenTracker(); + const directImports: Importer[] = []; + const isAvailableThroughGlobal = !!exportingModuleSymbol.globalExports; + const indirectUserDeclarations: SourceFileLike[] | undefined = isAvailableThroughGlobal ? undefined : []; + + handleDirectImports(exportingModuleSymbol); + + return { directImports, indirectUsers: getIndirectUsers() }; + + function getIndirectUsers(): readonly SourceFile[] { + if (isAvailableThroughGlobal) { + // It has `export as namespace`, so anything could potentially use it. + return sourceFiles; + } - // Module augmentations may use this module's exports without importing it. - if (exportingModuleSymbol.declarations) { - for (const decl of exportingModuleSymbol.declarations) { - if (isExternalModuleAugmentation(decl) && sourceFilesSet.has(decl.getSourceFile().fileName)) { - addIndirectUser(decl); - } + // Module augmentations may use this module's exports without importing it. + if (exportingModuleSymbol.declarations) { + for (const decl of exportingModuleSymbol.declarations) { + if (isExternalModuleAugmentation(decl) && sourceFilesSet.has(decl.getSourceFile().fileName)) { + addIndirectUser(decl); } } - - // This may return duplicates (if there are multiple module declarations in a single source file, all importing the same thing as a namespace), but `State.markSearchedSymbol` will handle that. - return indirectUserDeclarations!.map(getSourceFileOfNode); } - function handleDirectImports(exportingModuleSymbol: Symbol): void { - const theseDirectImports = getDirectImports(exportingModuleSymbol); - if (theseDirectImports) { - for (const direct of theseDirectImports) { - if (!markSeenDirectImport(direct)) { - continue; - } + // This may return duplicates (if there are multiple module declarations in a single source file, all importing the same thing as a namespace), but `State.markSearchedSymbol` will handle that. + return indirectUserDeclarations!.map(getSourceFileOfNode); + } + + function handleDirectImports(exportingModuleSymbol: Symbol): void { + const theseDirectImports = getDirectImports(exportingModuleSymbol); + if (theseDirectImports) { + for (const direct of theseDirectImports) { + if (!markSeenDirectImport(direct)) { + continue; + } - if (cancellationToken) cancellationToken.throwIfCancellationRequested(); + if (cancellationToken) cancellationToken.throwIfCancellationRequested(); - switch (direct.kind) { - case SyntaxKind.CallExpression: - if (isImportCall(direct)) { - handleImportCall(direct); - break; - } - if (!isAvailableThroughGlobal) { - const parent = direct.parent; - if (exportKind === ExportKind.ExportEquals && parent.kind === SyntaxKind.VariableDeclaration) { - const { name } = parent as VariableDeclaration; - if (name.kind === SyntaxKind.Identifier) { - directImports.push(name); - break; - } + switch (direct.kind) { + case SyntaxKind.CallExpression: + if (isImportCall(direct)) { + handleImportCall(direct); + break; + } + if (!isAvailableThroughGlobal) { + const parent = direct.parent; + if (exportKind === ExportKind.ExportEquals && parent.kind === SyntaxKind.VariableDeclaration) { + const { name } = parent as VariableDeclaration; + if (name.kind === SyntaxKind.Identifier) { + directImports.push(name); + break; } } - break; - - case SyntaxKind.Identifier: // for 'const x = require("y"); - break; // TODO: GH#23879 + } + break; - case SyntaxKind.ImportEqualsDeclaration: - handleNamespaceImport(direct, direct.name, hasSyntacticModifier(direct, ModifierFlags.Export), /*alreadyAddedDirect*/ false); - break; + case SyntaxKind.Identifier: // for 'const x = require("y"); + break; // TODO: GH#23879 - case SyntaxKind.ImportDeclaration: - directImports.push(direct); - const namedBindings = direct.importClause && direct.importClause.namedBindings; - if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) { - handleNamespaceImport(direct, namedBindings.name, /*isReExport*/ false, /*alreadyAddedDirect*/ true); - } - else if (!isAvailableThroughGlobal && isDefaultImport(direct)) { - addIndirectUser(getSourceFileLikeForImportDeclaration(direct)); // Add a check for indirect uses to handle synthetic default imports - } - break; + case SyntaxKind.ImportEqualsDeclaration: + handleNamespaceImport(direct, direct.name, hasSyntacticModifier(direct, ModifierFlags.Export), /*alreadyAddedDirect*/ false); + break; - case SyntaxKind.ExportDeclaration: - if (!direct.exportClause) { - // This is `export * from "foo"`, so imports of this module may import the export too. - handleDirectImports(getContainingModuleSymbol(direct, checker)); - } - else if (direct.exportClause.kind === SyntaxKind.NamespaceExport) { - // `export * as foo from "foo"` add to indirect uses - addIndirectUser(getSourceFileLikeForImportDeclaration(direct), /** addTransitiveDependencies */ true); - } - else { - // This is `export { foo } from "foo"` and creates an alias symbol, so recursive search will get handle re-exports. - directImports.push(direct); - } - break; + case SyntaxKind.ImportDeclaration: + directImports.push(direct); + const namedBindings = direct.importClause && direct.importClause.namedBindings; + if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) { + handleNamespaceImport(direct, namedBindings.name, /*isReExport*/ false, /*alreadyAddedDirect*/ true); + } + else if (!isAvailableThroughGlobal && isDefaultImport(direct)) { + addIndirectUser(getSourceFileLikeForImportDeclaration(direct)); // Add a check for indirect uses to handle synthetic default imports + } + break; - case SyntaxKind.ImportType: - // Only check for typeof import('xyz') - if (direct.isTypeOf && !direct.qualifier && isExported(direct)) { - addIndirectUser(direct.getSourceFile(), /** addTransitiveDependencies */ true); - } + case SyntaxKind.ExportDeclaration: + if (!direct.exportClause) { + // This is `export * from "foo"`, so imports of this module may import the export too. + handleDirectImports(getContainingModuleSymbol(direct, checker)); + } + else if (direct.exportClause.kind === SyntaxKind.NamespaceExport) { + // `export * as foo from "foo"` add to indirect uses + addIndirectUser(getSourceFileLikeForImportDeclaration(direct), /** addTransitiveDependencies */ true); + } + else { + // This is `export { foo } from "foo"` and creates an alias symbol, so recursive search will get handle re-exports. directImports.push(direct); - break; + } + break; - default: - Debug.failBadSyntaxKind(direct, "Unexpected import kind."); - } + case SyntaxKind.ImportType: + // Only check for typeof import('xyz') + if (direct.isTypeOf && !direct.qualifier && isExported(direct)) { + addIndirectUser(direct.getSourceFile(), /** addTransitiveDependencies */ true); + } + directImports.push(direct); + break; + + default: + Debug.failBadSyntaxKind(direct, "Unexpected import kind."); } } } + } - function handleImportCall(importCall: ImportCall) { - const top = findAncestor(importCall, isAmbientModuleDeclaration) || importCall.getSourceFile(); - addIndirectUser(top, /** addTransitiveDependencies */ !!isExported(importCall, /** stopAtAmbientModule */ true)); - } + function handleImportCall(importCall: ImportCall) { + const top = findAncestor(importCall, isAmbientModuleDeclaration) || importCall.getSourceFile(); + addIndirectUser(top, /** addTransitiveDependencies */ !!isExported(importCall, /** stopAtAmbientModule */ true)); + } - function isExported(node: Node, stopAtAmbientModule = false) { - return findAncestor(node, node => { - if (stopAtAmbientModule && isAmbientModuleDeclaration(node)) return "quit"; - return some(node.modifiers, mod => mod.kind === SyntaxKind.ExportKeyword); - }); - } + function isExported(node: Node, stopAtAmbientModule = false) { + return findAncestor(node, node => { + if (stopAtAmbientModule && isAmbientModuleDeclaration(node)) return "quit"; + return some(node.modifiers, mod => mod.kind === SyntaxKind.ExportKeyword); + }); + } - function handleNamespaceImport(importDeclaration: ImportEqualsDeclaration | ImportDeclaration, name: Identifier, isReExport: boolean, alreadyAddedDirect: boolean): void { - if (exportKind === ExportKind.ExportEquals) { - // This is a direct import, not import-as-namespace. - if (!alreadyAddedDirect) directImports.push(importDeclaration); + function handleNamespaceImport(importDeclaration: ImportEqualsDeclaration | ImportDeclaration, name: Identifier, isReExport: boolean, alreadyAddedDirect: boolean): void { + if (exportKind === ExportKind.ExportEquals) { + // This is a direct import, not import-as-namespace. + if (!alreadyAddedDirect) directImports.push(importDeclaration); + } + else if (!isAvailableThroughGlobal) { + const sourceFileLike = getSourceFileLikeForImportDeclaration(importDeclaration); + Debug.assert(sourceFileLike.kind === SyntaxKind.SourceFile || sourceFileLike.kind === SyntaxKind.ModuleDeclaration); + if (isReExport || findNamespaceReExports(sourceFileLike, name, checker)) { + addIndirectUser(sourceFileLike, /** addTransitiveDependencies */ true); } - else if (!isAvailableThroughGlobal) { - const sourceFileLike = getSourceFileLikeForImportDeclaration(importDeclaration); - Debug.assert(sourceFileLike.kind === SyntaxKind.SourceFile || sourceFileLike.kind === SyntaxKind.ModuleDeclaration); - if (isReExport || findNamespaceReExports(sourceFileLike, name, checker)) { - addIndirectUser(sourceFileLike, /** addTransitiveDependencies */ true); - } - else { - addIndirectUser(sourceFileLike); - } + else { + addIndirectUser(sourceFileLike); } } + } - /** Adds a module and all of its transitive dependencies as possible indirect users. */ - function addIndirectUser(sourceFileLike: SourceFileLike, addTransitiveDependencies = false): void { - Debug.assert(!isAvailableThroughGlobal); - const isNew = markSeenIndirectUser(sourceFileLike); - if (!isNew) return; - indirectUserDeclarations!.push(sourceFileLike); // TODO: GH#18217 - - if (!addTransitiveDependencies) return; - const moduleSymbol = checker.getMergedSymbol(sourceFileLike.symbol); - if (!moduleSymbol) return; - Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module)); - const directImports = getDirectImports(moduleSymbol); - if (directImports) { - for (const directImport of directImports) { - if (!isImportTypeNode(directImport)) { - addIndirectUser(getSourceFileLikeForImportDeclaration(directImport), /** addTransitiveDependencies */ true); - } + /** Adds a module and all of its transitive dependencies as possible indirect users. */ + function addIndirectUser(sourceFileLike: SourceFileLike, addTransitiveDependencies = false): void { + Debug.assert(!isAvailableThroughGlobal); + const isNew = markSeenIndirectUser(sourceFileLike); + if (!isNew) return; + indirectUserDeclarations!.push(sourceFileLike); // TODO: GH#18217 + + if (!addTransitiveDependencies) return; + const moduleSymbol = checker.getMergedSymbol(sourceFileLike.symbol); + if (!moduleSymbol) return; + Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module)); + const directImports = getDirectImports(moduleSymbol); + if (directImports) { + for (const directImport of directImports) { + if (!isImportTypeNode(directImport)) { + addIndirectUser(getSourceFileLikeForImportDeclaration(directImport), /** addTransitiveDependencies */ true); } } } + } - function getDirectImports(moduleSymbol: Symbol): ImporterOrCallExpression[] | undefined { - return allDirectImports.get(getSymbolId(moduleSymbol).toString()); - } + function getDirectImports(moduleSymbol: Symbol): ImporterOrCallExpression[] | undefined { + return allDirectImports.get(getSymbolId(moduleSymbol).toString()); } +} - /** - * Given the set of direct imports of a module, we need to find which ones import the particular exported symbol. - * The returned `importSearches` will result in the entire source file being searched. - * But re-exports will be placed in 'singleReferences' since they cannot be locally referenced. - */ - function getSearchesFromDirectImports(directImports: Importer[], exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker, isForRename: boolean): Pick { - const importSearches: [Identifier, Symbol][] = []; - const singleReferences: (Identifier | StringLiteral)[] = []; - function addSearch(location: Identifier, symbol: Symbol): void { - importSearches.push([location, symbol]); +/** + * Given the set of direct imports of a module, we need to find which ones import the particular exported symbol. + * The returned `importSearches` will result in the entire source file being searched. + * But re-exports will be placed in 'singleReferences' since they cannot be locally referenced. + */ +function getSearchesFromDirectImports(directImports: Importer[], exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker, isForRename: boolean): Pick { + const importSearches: [Identifier, Symbol][] = []; + const singleReferences: (Identifier | StringLiteral)[] = []; + function addSearch(location: Identifier, symbol: Symbol): void { + importSearches.push([location, symbol]); + } + + if (directImports) { + for (const decl of directImports) { + handleImport(decl); } + } - if (directImports) { - for (const decl of directImports) { - handleImport(decl); + return { importSearches, singleReferences }; + + function handleImport(decl: Importer): void { + if (decl.kind === SyntaxKind.ImportEqualsDeclaration) { + if (isExternalModuleImportEquals(decl)) { + handleNamespaceImportLike(decl.name); } + return; } - return { importSearches, singleReferences }; + if (decl.kind === SyntaxKind.Identifier) { + handleNamespaceImportLike(decl); + return; + } - function handleImport(decl: Importer): void { - if (decl.kind === SyntaxKind.ImportEqualsDeclaration) { - if (isExternalModuleImportEquals(decl)) { - handleNamespaceImportLike(decl.name); + if (decl.kind === SyntaxKind.ImportType) { + if (decl.qualifier) { + const firstIdentifier = getFirstIdentifier(decl.qualifier); + if (firstIdentifier.escapedText === symbolName(exportSymbol)) { + singleReferences.push(firstIdentifier); } - return; } - - if (decl.kind === SyntaxKind.Identifier) { - handleNamespaceImportLike(decl); - return; + else if (exportKind === ExportKind.ExportEquals) { + singleReferences.push(decl.argument.literal); } + return; + } - if (decl.kind === SyntaxKind.ImportType) { - if (decl.qualifier) { - const firstIdentifier = getFirstIdentifier(decl.qualifier); - if (firstIdentifier.escapedText === symbolName(exportSymbol)) { - singleReferences.push(firstIdentifier); - } - } - else if (exportKind === ExportKind.ExportEquals) { - singleReferences.push(decl.argument.literal); - } - return; - } + // Ignore if there's a grammar error + if (decl.moduleSpecifier!.kind !== SyntaxKind.StringLiteral) { + return; + } - // Ignore if there's a grammar error - if (decl.moduleSpecifier!.kind !== SyntaxKind.StringLiteral) { - return; + if (decl.kind === SyntaxKind.ExportDeclaration) { + if (decl.exportClause && isNamedExports(decl.exportClause)) { + searchForNamedImport(decl.exportClause); } + return; + } - if (decl.kind === SyntaxKind.ExportDeclaration) { - if (decl.exportClause && isNamedExports(decl.exportClause)) { - searchForNamedImport(decl.exportClause); - } - return; + const { name, namedBindings } = decl.importClause || { name: undefined, namedBindings: undefined }; + + if (namedBindings) { + switch (namedBindings.kind) { + case SyntaxKind.NamespaceImport: + handleNamespaceImportLike(namedBindings.name); + break; + case SyntaxKind.NamedImports: + // 'default' might be accessed as a named import `{ default as foo }`. + if (exportKind === ExportKind.Named || exportKind === ExportKind.Default) { + searchForNamedImport(namedBindings); + } + break; + default: + Debug.assertNever(namedBindings); } + } - const { name, namedBindings } = decl.importClause || { name: undefined, namedBindings: undefined }; - - if (namedBindings) { - switch (namedBindings.kind) { - case SyntaxKind.NamespaceImport: - handleNamespaceImportLike(namedBindings.name); - break; - case SyntaxKind.NamedImports: - // 'default' might be accessed as a named import `{ default as foo }`. - if (exportKind === ExportKind.Named || exportKind === ExportKind.Default) { - searchForNamedImport(namedBindings); - } - break; - default: - Debug.assertNever(namedBindings); - } - } + // `export =` might be imported by a default import if `--allowSyntheticDefaultImports` is on, so this handles both ExportKind.Default and ExportKind.ExportEquals. + // If a default import has the same name as the default export, allow to rename it. + // Given `import f` and `export default function f`, we will rename both, but for `import g` we will rename just that. + if (name && (exportKind === ExportKind.Default || exportKind === ExportKind.ExportEquals) && (!isForRename || name.escapedText === symbolEscapedNameNoDefault(exportSymbol))) { + const defaultImportAlias = checker.getSymbolAtLocation(name)!; + addSearch(name, defaultImportAlias); + } + } - // `export =` might be imported by a default import if `--allowSyntheticDefaultImports` is on, so this handles both ExportKind.Default and ExportKind.ExportEquals. - // If a default import has the same name as the default export, allow to rename it. - // Given `import f` and `export default function f`, we will rename both, but for `import g` we will rename just that. - if (name && (exportKind === ExportKind.Default || exportKind === ExportKind.ExportEquals) && (!isForRename || name.escapedText === symbolEscapedNameNoDefault(exportSymbol))) { - const defaultImportAlias = checker.getSymbolAtLocation(name)!; - addSearch(name, defaultImportAlias); - } + /** + * `import x = require("./x")` or `import * as x from "./x"`. + * An `export =` may be imported by this syntax, so it may be a direct import. + * If it's not a direct import, it will be in `indirectUsers`, so we don't have to do anything here. + */ + function handleNamespaceImportLike(importName: Identifier): void { + // Don't rename an import that already has a different name than the export. + if (exportKind === ExportKind.ExportEquals && (!isForRename || isNameMatch(importName.escapedText))) { + addSearch(importName, checker.getSymbolAtLocation(importName)!); } + } - /** - * `import x = require("./x")` or `import * as x from "./x"`. - * An `export =` may be imported by this syntax, so it may be a direct import. - * If it's not a direct import, it will be in `indirectUsers`, so we don't have to do anything here. - */ - function handleNamespaceImportLike(importName: Identifier): void { - // Don't rename an import that already has a different name than the export. - if (exportKind === ExportKind.ExportEquals && (!isForRename || isNameMatch(importName.escapedText))) { - addSearch(importName, checker.getSymbolAtLocation(importName)!); - } + function searchForNamedImport(namedBindings: NamedImportsOrExports | undefined): void { + if (!namedBindings) { + return; } - function searchForNamedImport(namedBindings: NamedImportsOrExports | undefined): void { - if (!namedBindings) { - return; + for (const element of namedBindings.elements) { + const { name, propertyName } = element; + if (!isNameMatch((propertyName || name).escapedText)) { + continue; } - for (const element of namedBindings.elements) { - const { name, propertyName } = element; - if (!isNameMatch((propertyName || name).escapedText)) { - continue; - } - - if (propertyName) { - // This is `import { foo as bar } from "./a"` or `export { foo as bar } from "./a"`. `foo` isn't a local in the file, so just add it as a single reference. - singleReferences.push(propertyName); - // If renaming `{ foo as bar }`, don't touch `bar`, just `foo`. - // But do rename `foo` in ` { default as foo }` if that's the original export name. - if (!isForRename || name.escapedText === exportSymbol.escapedName) { - // Search locally for `bar`. - addSearch(name, checker.getSymbolAtLocation(name)!); - } - } - else { - const localSymbol = element.kind === SyntaxKind.ExportSpecifier && element.propertyName - ? checker.getExportSpecifierLocalTargetSymbol(element)! // For re-exporting under a different name, we want to get the re-exported symbol. - : checker.getSymbolAtLocation(name)!; - addSearch(name, localSymbol); + if (propertyName) { + // This is `import { foo as bar } from "./a"` or `export { foo as bar } from "./a"`. `foo` isn't a local in the file, so just add it as a single reference. + singleReferences.push(propertyName); + // If renaming `{ foo as bar }`, don't touch `bar`, just `foo`. + // But do rename `foo` in ` { default as foo }` if that's the original export name. + if (!isForRename || name.escapedText === exportSymbol.escapedName) { + // Search locally for `bar`. + addSearch(name, checker.getSymbolAtLocation(name)!); } } + else { + const localSymbol = element.kind === SyntaxKind.ExportSpecifier && element.propertyName + ? checker.getExportSpecifierLocalTargetSymbol(element)! // For re-exporting under a different name, we want to get the re-exported symbol. + : checker.getSymbolAtLocation(name)!; + addSearch(name, localSymbol); + } } + } - function isNameMatch(name: __String): boolean { - // Use name of "default" even in `export =` case because we may have allowSyntheticDefaultImports - return name === exportSymbol.escapedName || exportKind !== ExportKind.Named && name === InternalSymbolName.Default; - } + function isNameMatch(name: __String): boolean { + // Use name of "default" even in `export =` case because we may have allowSyntheticDefaultImports + return name === exportSymbol.escapedName || exportKind !== ExportKind.Named && name === InternalSymbolName.Default; } +} - /** Returns 'true' is the namespace 'name' is re-exported from this module, and 'false' if it is only used locally. */ - function findNamespaceReExports(sourceFileLike: SourceFileLike, name: Identifier, checker: TypeChecker): boolean { - const namespaceImportSymbol = checker.getSymbolAtLocation(name); +/** Returns 'true' is the namespace 'name' is re-exported from this module, and 'false' if it is only used locally. */ +function findNamespaceReExports(sourceFileLike: SourceFileLike, name: Identifier, checker: TypeChecker): boolean { + const namespaceImportSymbol = checker.getSymbolAtLocation(name); - return !!forEachPossibleImportOrExportStatement(sourceFileLike, statement => { - if (!isExportDeclaration(statement)) return; - const { exportClause, moduleSpecifier } = statement; - return !moduleSpecifier && exportClause && isNamedExports(exportClause) && - exportClause.elements.some(element => checker.getExportSpecifierLocalTargetSymbol(element) === namespaceImportSymbol); - }); - } + return !!forEachPossibleImportOrExportStatement(sourceFileLike, statement => { + if (!isExportDeclaration(statement)) return; + const { exportClause, moduleSpecifier } = statement; + return !moduleSpecifier && exportClause && isNamedExports(exportClause) && + exportClause.elements.some(element => checker.getExportSpecifierLocalTargetSymbol(element) === namespaceImportSymbol); + }); +} - export type ModuleReference = - /** "import" also includes require() calls. */ - | { kind: "import", literal: StringLiteralLike } - /** or */ - | { kind: "reference", referencingFile: SourceFile, ref: FileReference }; - export function findModuleReferences(program: Program, sourceFiles: readonly SourceFile[], searchModuleSymbol: Symbol): ModuleReference[] { - const refs: ModuleReference[] = []; - const checker = program.getTypeChecker(); - for (const referencingFile of sourceFiles) { - const searchSourceFile = searchModuleSymbol.valueDeclaration; - if (searchSourceFile?.kind === SyntaxKind.SourceFile) { - for (const ref of referencingFile.referencedFiles) { - if (program.getSourceFileFromReference(referencingFile, ref) === searchSourceFile) { - refs.push({ kind: "reference", referencingFile, ref }); - } - } - for (const ref of referencingFile.typeReferenceDirectives) { - const referenced = program.getResolvedTypeReferenceDirectives().get(ref.fileName); - if (referenced !== undefined && referenced.resolvedFileName === (searchSourceFile as SourceFile).fileName) { - refs.push({ kind: "reference", referencingFile, ref }); - } +export type ModuleReference = + /** "import" also includes require() calls. */ + | { kind: "import", literal: StringLiteralLike } + /** or */ + | { kind: "reference", referencingFile: SourceFile, ref: FileReference }; +export function findModuleReferences(program: Program, sourceFiles: readonly SourceFile[], searchModuleSymbol: Symbol): ModuleReference[] { + const refs: ModuleReference[] = []; + const checker = program.getTypeChecker(); + for (const referencingFile of sourceFiles) { + const searchSourceFile = searchModuleSymbol.valueDeclaration; + if (searchSourceFile?.kind === SyntaxKind.SourceFile) { + for (const ref of referencingFile.referencedFiles) { + if (program.getSourceFileFromReference(referencingFile, ref) === searchSourceFile) { + refs.push({ kind: "reference", referencingFile, ref }); } } - - forEachImport(referencingFile, (_importDecl, moduleSpecifier) => { - const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); - if (moduleSymbol === searchModuleSymbol) { - refs.push({ kind: "import", literal: moduleSpecifier }); + for (const ref of referencingFile.typeReferenceDirectives) { + const referenced = program.getResolvedTypeReferenceDirectives().get(ref.fileName); + if (referenced !== undefined && referenced.resolvedFileName === (searchSourceFile as SourceFile).fileName) { + refs.push({ kind: "reference", referencingFile, ref }); } - }); + } } - return refs; + + forEachImport(referencingFile, (_importDecl, moduleSpecifier) => { + const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); + if (moduleSymbol === searchModuleSymbol) { + refs.push({ kind: "import", literal: moduleSpecifier }); + } + }); } + return refs; +} - /** Returns a map from a module symbol Id to all import statements that directly reference the module. */ - function getDirectImportsMap(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken | undefined): ESMap { - const map = new Map(); - - for (const sourceFile of sourceFiles) { - if (cancellationToken) cancellationToken.throwIfCancellationRequested(); - forEachImport(sourceFile, (importDecl, moduleSpecifier) => { - const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); - if (moduleSymbol) { - const id = getSymbolId(moduleSymbol).toString(); - let imports = map.get(id); - if (!imports) { - map.set(id, imports = []); - } - imports.push(importDecl); +/** Returns a map from a module symbol Id to all import statements that directly reference the module. */ +function getDirectImportsMap(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken | undefined): ESMap { + const map = new Map(); + + for (const sourceFile of sourceFiles) { + if (cancellationToken) cancellationToken.throwIfCancellationRequested(); + forEachImport(sourceFile, (importDecl, moduleSpecifier) => { + const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); + if (moduleSymbol) { + const id = getSymbolId(moduleSymbol).toString(); + let imports = map.get(id); + if (!imports) { + map.set(id, imports = []); } - }); - } - - return map; + imports.push(importDecl); + } + }); } - /** Iterates over all statements at the top level or in module declarations. Returns the first truthy result. */ - function forEachPossibleImportOrExportStatement(sourceFileLike: SourceFileLike, action: (statement: Statement) => T) { - return forEach(sourceFileLike.kind === SyntaxKind.SourceFile ? sourceFileLike.statements : sourceFileLike.body!.statements, statement => // TODO: GH#18217 - action(statement) || (isAmbientModuleDeclaration(statement) && forEach(statement.body && statement.body.statements, action))); - } + return map; +} - /** Calls `action` for each import, re-export, or require() in a file. */ - function forEachImport(sourceFile: SourceFile, action: (importStatement: ImporterOrCallExpression, imported: StringLiteralLike) => void): void { - if (sourceFile.externalModuleIndicator || sourceFile.imports !== undefined) { - for (const i of sourceFile.imports) { - action(importFromModuleSpecifier(i), i); - } +/** Iterates over all statements at the top level or in module declarations. Returns the first truthy result. */ +function forEachPossibleImportOrExportStatement(sourceFileLike: SourceFileLike, action: (statement: Statement) => T) { + return forEach(sourceFileLike.kind === SyntaxKind.SourceFile ? sourceFileLike.statements : sourceFileLike.body!.statements, statement => // TODO: GH#18217 + action(statement) || (isAmbientModuleDeclaration(statement) && forEach(statement.body && statement.body.statements, action))); +} + +/** Calls `action` for each import, re-export, or require() in a file. */ +function forEachImport(sourceFile: SourceFile, action: (importStatement: ImporterOrCallExpression, imported: StringLiteralLike) => void): void { + if (sourceFile.externalModuleIndicator || sourceFile.imports !== undefined) { + for (const i of sourceFile.imports) { + action(importFromModuleSpecifier(i), i); } - else { - forEachPossibleImportOrExportStatement(sourceFile, statement => { - switch (statement.kind) { - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ImportDeclaration: { - const decl = statement as ImportDeclaration | ExportDeclaration; - if (decl.moduleSpecifier && isStringLiteral(decl.moduleSpecifier)) { - action(decl, decl.moduleSpecifier); - } - break; + } + else { + forEachPossibleImportOrExportStatement(sourceFile, statement => { + switch (statement.kind) { + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ImportDeclaration: { + const decl = statement as ImportDeclaration | ExportDeclaration; + if (decl.moduleSpecifier && isStringLiteral(decl.moduleSpecifier)) { + action(decl, decl.moduleSpecifier); } + break; + } - case SyntaxKind.ImportEqualsDeclaration: { - const decl = statement as ImportEqualsDeclaration; - if (isExternalModuleImportEquals(decl)) { - action(decl, decl.moduleReference.expression); - } - break; + case SyntaxKind.ImportEqualsDeclaration: { + const decl = statement as ImportEqualsDeclaration; + if (isExternalModuleImportEquals(decl)) { + action(decl, decl.moduleReference.expression); } + break; } - }); - } + } + }); } +} - export interface ImportedSymbol { - kind: ImportExport.Import; - symbol: Symbol; - } - export interface ExportedSymbol { - kind: ImportExport.Export; - symbol: Symbol; - exportInfo: ExportInfo; - } +export interface ImportedSymbol { + kind: ImportExport.Import; + symbol: Symbol; +} +export interface ExportedSymbol { + kind: ImportExport.Export; + symbol: Symbol; + exportInfo: ExportInfo; +} - /** - * Given a local reference, we might notice that it's an import/export and recursively search for references of that. - * If at an import, look locally for the symbol it imports. - * If at an export, look for all imports of it. - * This doesn't handle export specifiers; that is done in `getReferencesAtExportSpecifier`. - * @param comingFromExport If we are doing a search for all exports, don't bother looking backwards for the imported symbol, since that's the reason we're here. - */ - export function getImportOrExportSymbol(node: Node, symbol: Symbol, checker: TypeChecker, comingFromExport: boolean): ImportedSymbol | ExportedSymbol | undefined { - return comingFromExport ? getExport() : getExport() || getImport(); - - function getExport(): ExportedSymbol | ImportedSymbol | undefined { - const { parent } = node; - const grandparent = parent.parent; - if (symbol.exportSymbol) { - if (parent.kind === SyntaxKind.PropertyAccessExpression) { - // When accessing an export of a JS module, there's no alias. The symbol will still be flagged as an export even though we're at the use. - // So check that we are at the declaration. - return symbol.declarations?.some(d => d === parent) && isBinaryExpression(grandparent) - ? getSpecialPropertyExport(grandparent, /*useLhsSymbol*/ false) - : undefined; - } - else { - return exportInfo(symbol.exportSymbol, getExportKindForDeclaration(parent)); - } +/** + * Given a local reference, we might notice that it's an import/export and recursively search for references of that. + * If at an import, look locally for the symbol it imports. + * If at an export, look for all imports of it. + * This doesn't handle export specifiers; that is done in `getReferencesAtExportSpecifier`. + * @param comingFromExport If we are doing a search for all exports, don't bother looking backwards for the imported symbol, since that's the reason we're here. + */ +export function getImportOrExportSymbol(node: Node, symbol: Symbol, checker: TypeChecker, comingFromExport: boolean): ImportedSymbol | ExportedSymbol | undefined { + return comingFromExport ? getExport() : getExport() || getImport(); + + function getExport(): ExportedSymbol | ImportedSymbol | undefined { + const { parent } = node; + const grandparent = parent.parent; + if (symbol.exportSymbol) { + if (parent.kind === SyntaxKind.PropertyAccessExpression) { + // When accessing an export of a JS module, there's no alias. The symbol will still be flagged as an export even though we're at the use. + // So check that we are at the declaration. + return symbol.declarations?.some(d => d === parent) && isBinaryExpression(grandparent) + ? getSpecialPropertyExport(grandparent, /*useLhsSymbol*/ false) + : undefined; } else { - const exportNode = getExportNode(parent, node); - if (exportNode && hasSyntacticModifier(exportNode, ModifierFlags.Export)) { - if (isImportEqualsDeclaration(exportNode) && exportNode.moduleReference === node) { - // We're at `Y` in `export import X = Y`. This is not the exported symbol, the left-hand-side is. So treat this as an import statement. - if (comingFromExport) { - return undefined; - } - - const lhsSymbol = checker.getSymbolAtLocation(exportNode.name)!; - return { kind: ImportExport.Import, symbol: lhsSymbol }; - } - else { - return exportInfo(symbol, getExportKindForDeclaration(exportNode)); + return exportInfo(symbol.exportSymbol, getExportKindForDeclaration(parent)); + } + } + else { + const exportNode = getExportNode(parent, node); + if (exportNode && hasSyntacticModifier(exportNode, ModifierFlags.Export)) { + if (isImportEqualsDeclaration(exportNode) && exportNode.moduleReference === node) { + // We're at `Y` in `export import X = Y`. This is not the exported symbol, the left-hand-side is. So treat this as an import statement. + if (comingFromExport) { + return undefined; } + + const lhsSymbol = checker.getSymbolAtLocation(exportNode.name)!; + return { kind: ImportExport.Import, symbol: lhsSymbol }; } - else if (isNamespaceExport(parent)) { - return exportInfo(symbol, ExportKind.Named); - } - // If we are in `export = a;` or `export default a;`, `parent` is the export assignment. - else if (isExportAssignment(parent)) { - return getExportAssignmentExport(parent); - } - // If we are in `export = class A {};` (or `export = class A {};`) at `A`, `parent.parent` is the export assignment. - else if (isExportAssignment(grandparent)) { - return getExportAssignmentExport(grandparent); - } - // Similar for `module.exports =` and `exports.A =`. - else if (isBinaryExpression(parent)) { - return getSpecialPropertyExport(parent, /*useLhsSymbol*/ true); - } - else if (isBinaryExpression(grandparent)) { - return getSpecialPropertyExport(grandparent, /*useLhsSymbol*/ true); - } - else if (isJSDocTypedefTag(parent)) { - return exportInfo(symbol, ExportKind.Named); + else { + return exportInfo(symbol, getExportKindForDeclaration(exportNode)); } } - - function getExportAssignmentExport(ex: ExportAssignment): ExportedSymbol | undefined { - // Get the symbol for the `export =` node; its parent is the module it's the export of. - if (!ex.symbol.parent) return undefined; - const exportKind = ex.isExportEquals ? ExportKind.ExportEquals : ExportKind.Default; - return { kind: ImportExport.Export, symbol, exportInfo: { exportingModuleSymbol: ex.symbol.parent, exportKind } }; + else if (isNamespaceExport(parent)) { + return exportInfo(symbol, ExportKind.Named); } - - function getSpecialPropertyExport(node: BinaryExpression, useLhsSymbol: boolean): ExportedSymbol | undefined { - let kind: ExportKind; - switch (getAssignmentDeclarationKind(node)) { - case AssignmentDeclarationKind.ExportsProperty: - kind = ExportKind.Named; - break; - case AssignmentDeclarationKind.ModuleExports: - kind = ExportKind.ExportEquals; - break; - default: - return undefined; - } - - const sym = useLhsSymbol ? checker.getSymbolAtLocation(getNameOfAccessExpression(cast(node.left, isAccessExpression))) : symbol; - return sym && exportInfo(sym, kind); + // If we are in `export = a;` or `export default a;`, `parent` is the export assignment. + else if (isExportAssignment(parent)) { + return getExportAssignmentExport(parent); } - } - - function getImport(): ImportedSymbol | undefined { - const isImport = isNodeImport(node); - if (!isImport) return undefined; - - // A symbol being imported is always an alias. So get what that aliases to find the local symbol. - let importedSymbol = checker.getImmediateAliasedSymbol(symbol); - if (!importedSymbol) return undefined; - - // Search on the local symbol in the exporting module, not the exported symbol. - importedSymbol = skipExportSpecifierSymbol(importedSymbol, checker); - // Similarly, skip past the symbol for 'export =' - if (importedSymbol.escapedName === "export=") { - importedSymbol = getExportEqualsLocalSymbol(importedSymbol, checker); + // If we are in `export = class A {};` (or `export = class A {};`) at `A`, `parent.parent` is the export assignment. + else if (isExportAssignment(grandparent)) { + return getExportAssignmentExport(grandparent); } - - // If the import has a different name than the export, do not continue searching. - // If `importedName` is undefined, do continue searching as the export is anonymous. - // (All imports returned from this function will be ignored anyway if we are in rename and this is a not a named export.) - const importedName = symbolEscapedNameNoDefault(importedSymbol); - if (importedName === undefined || importedName === InternalSymbolName.Default || importedName === symbol.escapedName) { - return { kind: ImportExport.Import, symbol: importedSymbol }; + // Similar for `module.exports =` and `exports.A =`. + else if (isBinaryExpression(parent)) { + return getSpecialPropertyExport(parent, /*useLhsSymbol*/ true); + } + else if (isBinaryExpression(grandparent)) { + return getSpecialPropertyExport(grandparent, /*useLhsSymbol*/ true); + } + else if (isJSDocTypedefTag(parent)) { + return exportInfo(symbol, ExportKind.Named); } } - function exportInfo(symbol: Symbol, kind: ExportKind): ExportedSymbol | undefined { - const exportInfo = getExportInfo(symbol, kind, checker); - return exportInfo && { kind: ImportExport.Export, symbol, exportInfo }; + function getExportAssignmentExport(ex: ExportAssignment): ExportedSymbol | undefined { + // Get the symbol for the `export =` node; its parent is the module it's the export of. + if (!ex.symbol.parent) return undefined; + const exportKind = ex.isExportEquals ? ExportKind.ExportEquals : ExportKind.Default; + return { kind: ImportExport.Export, symbol, exportInfo: { exportingModuleSymbol: ex.symbol.parent, exportKind } }; } - // Not meant for use with export specifiers or export assignment. - function getExportKindForDeclaration(node: Node): ExportKind { - return hasSyntacticModifier(node, ModifierFlags.Default) ? ExportKind.Default : ExportKind.Named; + function getSpecialPropertyExport(node: BinaryExpression, useLhsSymbol: boolean): ExportedSymbol | undefined { + let kind: ExportKind; + switch (getAssignmentDeclarationKind(node)) { + case AssignmentDeclarationKind.ExportsProperty: + kind = ExportKind.Named; + break; + case AssignmentDeclarationKind.ModuleExports: + kind = ExportKind.ExportEquals; + break; + default: + return undefined; + } + + const sym = useLhsSymbol ? checker.getSymbolAtLocation(getNameOfAccessExpression(cast(node.left, isAccessExpression))) : symbol; + return sym && exportInfo(sym, kind); } } - function getExportEqualsLocalSymbol(importedSymbol: Symbol, checker: TypeChecker): Symbol { - if (importedSymbol.flags & SymbolFlags.Alias) { - return Debug.checkDefined(checker.getImmediateAliasedSymbol(importedSymbol)); - } + function getImport(): ImportedSymbol | undefined { + const isImport = isNodeImport(node); + if (!isImport) return undefined; - const decl = Debug.checkDefined(importedSymbol.valueDeclaration); - if (isExportAssignment(decl)) { // `export = class {}` - return Debug.checkDefined(decl.expression.symbol); - } - else if (isBinaryExpression(decl)) { // `module.exports = class {}` - return Debug.checkDefined(decl.right.symbol); + // A symbol being imported is always an alias. So get what that aliases to find the local symbol. + let importedSymbol = checker.getImmediateAliasedSymbol(symbol); + if (!importedSymbol) return undefined; + + // Search on the local symbol in the exporting module, not the exported symbol. + importedSymbol = skipExportSpecifierSymbol(importedSymbol, checker); + // Similarly, skip past the symbol for 'export =' + if (importedSymbol.escapedName === "export=") { + importedSymbol = getExportEqualsLocalSymbol(importedSymbol, checker); } - else if (isSourceFile(decl)) { // json module - return Debug.checkDefined(decl.symbol); + + // If the import has a different name than the export, do not continue searching. + // If `importedName` is undefined, do continue searching as the export is anonymous. + // (All imports returned from this function will be ignored anyway if we are in rename and this is a not a named export.) + const importedName = symbolEscapedNameNoDefault(importedSymbol); + if (importedName === undefined || importedName === InternalSymbolName.Default || importedName === symbol.escapedName) { + return { kind: ImportExport.Import, symbol: importedSymbol }; } - return Debug.fail(); } - // If a reference is a class expression, the exported node would be its parent. - // If a reference is a variable declaration, the exported node would be the variable statement. - function getExportNode(parent: Node, node: Node): Node | undefined { - const declaration = isVariableDeclaration(parent) ? parent : isBindingElement(parent) ? walkUpBindingElementsAndPatterns(parent) : undefined; - if (declaration) { - return (parent as VariableDeclaration | BindingElement).name !== node ? undefined : - isCatchClause(declaration.parent) ? undefined : isVariableStatement(declaration.parent.parent) ? declaration.parent.parent : undefined; - } - else { - return parent; - } + function exportInfo(symbol: Symbol, kind: ExportKind): ExportedSymbol | undefined { + const exportInfo = getExportInfo(symbol, kind, checker); + return exportInfo && { kind: ImportExport.Export, symbol, exportInfo }; } - function isNodeImport(node: Node): boolean { - const { parent } = node; - switch (parent.kind) { - case SyntaxKind.ImportEqualsDeclaration: - return (parent as ImportEqualsDeclaration).name === node && isExternalModuleImportEquals(parent as ImportEqualsDeclaration); - case SyntaxKind.ImportSpecifier: - // For a rename import `{ foo as bar }`, don't search for the imported symbol. Just find local uses of `bar`. - return !(parent as ImportSpecifier).propertyName; - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - Debug.assert((parent as ImportClause | NamespaceImport).name === node); - return true; - case SyntaxKind.BindingElement: - return isInJSFile(node) && isRequireVariableDeclaration(parent); - default: - return false; - } + // Not meant for use with export specifiers or export assignment. + function getExportKindForDeclaration(node: Node): ExportKind { + return hasSyntacticModifier(node, ModifierFlags.Default) ? ExportKind.Default : ExportKind.Named; } +} - export function getExportInfo(exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker): ExportInfo | undefined { - const moduleSymbol = exportSymbol.parent; - if (!moduleSymbol) return undefined; // This can happen if an `export` is not at the top-level (which is a compile error). - const exportingModuleSymbol = checker.getMergedSymbol(moduleSymbol); // Need to get merged symbol in case there's an augmentation. - // `export` may appear in a namespace. In that case, just rely on global search. - return isExternalModuleSymbol(exportingModuleSymbol) ? { exportingModuleSymbol, exportKind } : undefined; +function getExportEqualsLocalSymbol(importedSymbol: Symbol, checker: TypeChecker): Symbol { + if (importedSymbol.flags & SymbolFlags.Alias) { + return Debug.checkDefined(checker.getImmediateAliasedSymbol(importedSymbol)); } - /** If at an export specifier, go to the symbol it refers to. */ - function skipExportSpecifierSymbol(symbol: Symbol, checker: TypeChecker): Symbol { - // For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does. - if (symbol.declarations) { - for (const declaration of symbol.declarations) { - if (isExportSpecifier(declaration) && !declaration.propertyName && !declaration.parent.parent.moduleSpecifier) { - return checker.getExportSpecifierLocalTargetSymbol(declaration)!; - } - else if (isPropertyAccessExpression(declaration) && isModuleExportsAccessExpression(declaration.expression) && !isPrivateIdentifier(declaration.name)) { - // Export of form 'module.exports.propName = expr'; - return checker.getSymbolAtLocation(declaration)!; - } - else if (isShorthandPropertyAssignment(declaration) - && isBinaryExpression(declaration.parent.parent) - && getAssignmentDeclarationKind(declaration.parent.parent) === AssignmentDeclarationKind.ModuleExports) { - return checker.getExportSpecifierLocalTargetSymbol(declaration.name)!; - } - } - } - return symbol; + const decl = Debug.checkDefined(importedSymbol.valueDeclaration); + if (isExportAssignment(decl)) { // `export = class {}` + return Debug.checkDefined(decl.expression.symbol); + } + else if (isBinaryExpression(decl)) { // `module.exports = class {}` + return Debug.checkDefined(decl.right.symbol); + } + else if (isSourceFile(decl)) { // json module + return Debug.checkDefined(decl.symbol); } + return Debug.fail(); +} - function getContainingModuleSymbol(importer: Importer, checker: TypeChecker): Symbol { - return checker.getMergedSymbol(getSourceFileLikeForImportDeclaration(importer).symbol); +// If a reference is a class expression, the exported node would be its parent. +// If a reference is a variable declaration, the exported node would be the variable statement. +function getExportNode(parent: Node, node: Node): Node | undefined { + const declaration = isVariableDeclaration(parent) ? parent : isBindingElement(parent) ? walkUpBindingElementsAndPatterns(parent) : undefined; + if (declaration) { + return (parent as VariableDeclaration | BindingElement).name !== node ? undefined : + isCatchClause(declaration.parent) ? undefined : isVariableStatement(declaration.parent.parent) ? declaration.parent.parent : undefined; + } + else { + return parent; } +} - function getSourceFileLikeForImportDeclaration(node: ImporterOrCallExpression): SourceFileLike { - if (node.kind === SyntaxKind.CallExpression) { - return node.getSourceFile(); - } +function isNodeImport(node: Node): boolean { + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return (parent as ImportEqualsDeclaration).name === node && isExternalModuleImportEquals(parent as ImportEqualsDeclaration); + case SyntaxKind.ImportSpecifier: + // For a rename import `{ foo as bar }`, don't search for the imported symbol. Just find local uses of `bar`. + return !(parent as ImportSpecifier).propertyName; + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + Debug.assert((parent as ImportClause | NamespaceImport).name === node); + return true; + case SyntaxKind.BindingElement: + return isInJSFile(node) && isRequireVariableDeclaration(parent); + default: + return false; + } +} - const { parent } = node; - if (parent.kind === SyntaxKind.SourceFile) { - return parent as SourceFile; +export function getExportInfo(exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker): ExportInfo | undefined { + const moduleSymbol = exportSymbol.parent; + if (!moduleSymbol) return undefined; // This can happen if an `export` is not at the top-level (which is a compile error). + const exportingModuleSymbol = checker.getMergedSymbol(moduleSymbol); // Need to get merged symbol in case there's an augmentation. + // `export` may appear in a namespace. In that case, just rely on global search. + return isExternalModuleSymbol(exportingModuleSymbol) ? { exportingModuleSymbol, exportKind } : undefined; +} + +/** If at an export specifier, go to the symbol it refers to. */ +function skipExportSpecifierSymbol(symbol: Symbol, checker: TypeChecker): Symbol { + // For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does. + if (symbol.declarations) { + for (const declaration of symbol.declarations) { + if (isExportSpecifier(declaration) && !declaration.propertyName && !declaration.parent.parent.moduleSpecifier) { + return checker.getExportSpecifierLocalTargetSymbol(declaration)!; + } + else if (isPropertyAccessExpression(declaration) && isModuleExportsAccessExpression(declaration.expression) && !isPrivateIdentifier(declaration.name)) { + // Export of form 'module.exports.propName = expr'; + return checker.getSymbolAtLocation(declaration)!; + } + else if (isShorthandPropertyAssignment(declaration) + && isBinaryExpression(declaration.parent.parent) + && getAssignmentDeclarationKind(declaration.parent.parent) === AssignmentDeclarationKind.ModuleExports) { + return checker.getExportSpecifierLocalTargetSymbol(declaration.name)!; + } } - Debug.assert(parent.kind === SyntaxKind.ModuleBlock); - return cast(parent.parent, isAmbientModuleDeclaration); } + return symbol; +} - function isAmbientModuleDeclaration(node: Node): node is AmbientModuleDeclaration { - return node.kind === SyntaxKind.ModuleDeclaration && (node as ModuleDeclaration).name.kind === SyntaxKind.StringLiteral; +function getContainingModuleSymbol(importer: Importer, checker: TypeChecker): Symbol { + return checker.getMergedSymbol(getSourceFileLikeForImportDeclaration(importer).symbol); +} + +function getSourceFileLikeForImportDeclaration(node: ImporterOrCallExpression): SourceFileLike { + if (node.kind === SyntaxKind.CallExpression) { + return node.getSourceFile(); } - function isExternalModuleImportEquals(eq: ImportEqualsDeclaration): eq is ImportEqualsDeclaration & { moduleReference: { expression: StringLiteral } } { - return eq.moduleReference.kind === SyntaxKind.ExternalModuleReference && eq.moduleReference.expression.kind === SyntaxKind.StringLiteral; + const { parent } = node; + if (parent.kind === SyntaxKind.SourceFile) { + return parent as SourceFile; } + Debug.assert(parent.kind === SyntaxKind.ModuleBlock); + return cast(parent.parent, isAmbientModuleDeclaration); +} + +function isAmbientModuleDeclaration(node: Node): node is AmbientModuleDeclaration { + return node.kind === SyntaxKind.ModuleDeclaration && (node as ModuleDeclaration).name.kind === SyntaxKind.StringLiteral; +} + +function isExternalModuleImportEquals(eq: ImportEqualsDeclaration): eq is ImportEqualsDeclaration & { moduleReference: { expression: StringLiteral } } { + return eq.moduleReference.kind === SyntaxKind.ExternalModuleReference && eq.moduleReference.expression.kind === SyntaxKind.StringLiteral; +} } diff --git a/src/services/inlayHints.ts b/src/services/inlayHints.ts index 59add59b0aedf..b0dba1ecb2299 100644 --- a/src/services/inlayHints.ts +++ b/src/services/inlayHints.ts @@ -1,323 +1,323 @@ /* @internal */ namespace ts.InlayHints { - const maxHintsLength = 30; +const maxHintsLength = 30; - const leadingParameterNameCommentRegexFactory = (name: string) => { - return new RegExp(`^\\s?/\\*\\*?\\s?${name}\\s?\\*\\/\\s?$`); - }; +const leadingParameterNameCommentRegexFactory = (name: string) => { + return new RegExp(`^\\s?/\\*\\*?\\s?${name}\\s?\\*\\/\\s?$`); +}; - function shouldShowParameterNameHints(preferences: InlayHintsOptions) { - return preferences.includeInlayParameterNameHints === "literals" || preferences.includeInlayParameterNameHints === "all"; - } +function shouldShowParameterNameHints(preferences: InlayHintsOptions) { + return preferences.includeInlayParameterNameHints === "literals" || preferences.includeInlayParameterNameHints === "all"; +} - function shouldShowLiteralParameterNameHintsOnly(preferences: InlayHintsOptions) { - return preferences.includeInlayParameterNameHints === "literals"; - } +function shouldShowLiteralParameterNameHintsOnly(preferences: InlayHintsOptions) { + return preferences.includeInlayParameterNameHints === "literals"; +} - export function provideInlayHints(context: InlayHintsContext): InlayHint[] { - const { file, program, span, cancellationToken, preferences } = context; - const sourceFileText = file.text; - const compilerOptions = program.getCompilerOptions(); +export function provideInlayHints(context: InlayHintsContext): InlayHint[] { + const { file, program, span, cancellationToken, preferences } = context; + const sourceFileText = file.text; + const compilerOptions = program.getCompilerOptions(); - const checker = program.getTypeChecker(); - const result: InlayHint[] = []; + const checker = program.getTypeChecker(); + const result: InlayHint[] = []; - visitor(file); - return result; + visitor(file); + return result; - function visitor(node: Node): true | undefined { - if (!node || node.getFullWidth() === 0) { - return; - } + function visitor(node: Node): true | undefined { + if (!node || node.getFullWidth() === 0) { + return; + } - switch (node.kind) { - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.ArrowFunction: - cancellationToken.throwIfCancellationRequested(); - } + switch (node.kind) { + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.ArrowFunction: + cancellationToken.throwIfCancellationRequested(); + } - if (!textSpanIntersectsWith(span, node.pos, node.getFullWidth())) { - return; - } + if (!textSpanIntersectsWith(span, node.pos, node.getFullWidth())) { + return; + } - if (isTypeNode(node)) { - return; - } + if (isTypeNode(node)) { + return; + } - if (preferences.includeInlayVariableTypeHints && isVariableDeclaration(node)) { - visitVariableLikeDeclaration(node); - } - else if (preferences.includeInlayPropertyDeclarationTypeHints && isPropertyDeclaration(node)) { - visitVariableLikeDeclaration(node); - } - else if (preferences.includeInlayEnumMemberValueHints && isEnumMember(node)) { - visitEnumMember(node); - } - else if (shouldShowParameterNameHints(preferences) && (isCallExpression(node) || isNewExpression(node))) { - visitCallOrNewExpression(node); + if (preferences.includeInlayVariableTypeHints && isVariableDeclaration(node)) { + visitVariableLikeDeclaration(node); + } + else if (preferences.includeInlayPropertyDeclarationTypeHints && isPropertyDeclaration(node)) { + visitVariableLikeDeclaration(node); + } + else if (preferences.includeInlayEnumMemberValueHints && isEnumMember(node)) { + visitEnumMember(node); + } + else if (shouldShowParameterNameHints(preferences) && (isCallExpression(node) || isNewExpression(node))) { + visitCallOrNewExpression(node); + } + else { + if (preferences.includeInlayFunctionParameterTypeHints && isFunctionLikeDeclaration(node) && hasContextSensitiveParameters(node)) { + visitFunctionLikeForParameterType(node); } - else { - if (preferences.includeInlayFunctionParameterTypeHints && isFunctionLikeDeclaration(node) && hasContextSensitiveParameters(node)) { - visitFunctionLikeForParameterType(node); - } - if (preferences.includeInlayFunctionLikeReturnTypeHints && isSignatureSupportingReturnAnnotation(node)) { - visitFunctionDeclarationLikeForReturnType(node); - } + if (preferences.includeInlayFunctionLikeReturnTypeHints && isSignatureSupportingReturnAnnotation(node)) { + visitFunctionDeclarationLikeForReturnType(node); } - return forEachChild(node, visitor); } + return forEachChild(node, visitor); + } - function isSignatureSupportingReturnAnnotation(node: Node): node is FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration { - return isArrowFunction(node) || isFunctionExpression(node) || isFunctionDeclaration(node) || isMethodDeclaration(node) || isGetAccessorDeclaration(node); - } + function isSignatureSupportingReturnAnnotation(node: Node): node is FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration { + return isArrowFunction(node) || isFunctionExpression(node) || isFunctionDeclaration(node) || isMethodDeclaration(node) || isGetAccessorDeclaration(node); + } - function addParameterHints(text: string, position: number, isFirstVariadicArgument: boolean) { - result.push({ - text: `${isFirstVariadicArgument ? "..." : ""}${truncation(text, maxHintsLength)}:`, - position, - kind: InlayHintKind.Parameter, - whitespaceAfter: true, - }); - } + function addParameterHints(text: string, position: number, isFirstVariadicArgument: boolean) { + result.push({ + text: `${isFirstVariadicArgument ? "..." : ""}${truncation(text, maxHintsLength)}:`, + position, + kind: InlayHintKind.Parameter, + whitespaceAfter: true, + }); + } + + function addTypeHints(text: string, position: number) { + result.push({ + text: `: ${truncation(text, maxHintsLength)}`, + position, + kind: InlayHintKind.Type, + whitespaceBefore: true, + }); + } - function addTypeHints(text: string, position: number) { - result.push({ - text: `: ${truncation(text, maxHintsLength)}`, - position, - kind: InlayHintKind.Type, - whitespaceBefore: true, - }); + function addEnumMemberValueHints(text: string, position: number) { + result.push({ + text: `= ${truncation(text, maxHintsLength)}`, + position, + kind: InlayHintKind.Enum, + whitespaceBefore: true, + }); + } + + function visitEnumMember(member: EnumMember) { + if (member.initializer) { + return; } - function addEnumMemberValueHints(text: string, position: number) { - result.push({ - text: `= ${truncation(text, maxHintsLength)}`, - position, - kind: InlayHintKind.Enum, - whitespaceBefore: true, - }); + const enumValue = checker.getConstantValue(member); + if (enumValue !== undefined) { + addEnumMemberValueHints(enumValue.toString(), member.end); } + } - function visitEnumMember(member: EnumMember) { - if (member.initializer) { - return; - } + function isModuleReferenceType(type: Type) { + return type.symbol && (type.symbol.flags & SymbolFlags.Module); + } - const enumValue = checker.getConstantValue(member); - if (enumValue !== undefined) { - addEnumMemberValueHints(enumValue.toString(), member.end); - } + function visitVariableLikeDeclaration(decl: VariableDeclaration | PropertyDeclaration) { + if (!decl.initializer || isBindingPattern(decl.name)) { + return; } - function isModuleReferenceType(type: Type) { - return type.symbol && (type.symbol.flags & SymbolFlags.Module); + const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(decl); + if (effectiveTypeAnnotation) { + return; } - function visitVariableLikeDeclaration(decl: VariableDeclaration | PropertyDeclaration) { - if (!decl.initializer || isBindingPattern(decl.name)) { - return; - } - - const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(decl); - if (effectiveTypeAnnotation) { - return; - } + const declarationType = checker.getTypeAtLocation(decl); + if (isModuleReferenceType(declarationType)) { + return; + } - const declarationType = checker.getTypeAtLocation(decl); - if (isModuleReferenceType(declarationType)) { - return; - } + const typeDisplayString = printTypeInSingleLine(declarationType); + if (typeDisplayString) { + addTypeHints(typeDisplayString, decl.name.end); + } + } - const typeDisplayString = printTypeInSingleLine(declarationType); - if (typeDisplayString) { - addTypeHints(typeDisplayString, decl.name.end); - } + function visitCallOrNewExpression(expr: CallExpression | NewExpression) { + const args = expr.arguments; + if (!args || !args.length) { + return; } - function visitCallOrNewExpression(expr: CallExpression | NewExpression) { - const args = expr.arguments; - if (!args || !args.length) { - return; - } + const candidates: Signature[] = []; + const signature = checker.getResolvedSignatureForSignatureHelp(expr, candidates); + if (!signature || !candidates.length) { + return; + } - const candidates: Signature[] = []; - const signature = checker.getResolvedSignatureForSignatureHelp(expr, candidates); - if (!signature || !candidates.length) { - return; + for (let i = 0; i < args.length; ++i) { + const originalArg = args[i]; + const arg = skipParentheses(originalArg); + if (shouldShowLiteralParameterNameHintsOnly(preferences) && !isHintableLiteral(arg)) { + continue; } - for (let i = 0; i < args.length; ++i) { - const originalArg = args[i]; - const arg = skipParentheses(originalArg); - if (shouldShowLiteralParameterNameHintsOnly(preferences) && !isHintableLiteral(arg)) { + const identifierNameInfo = checker.getParameterIdentifierNameAtPosition(signature, i); + if (identifierNameInfo) { + const [parameterName, isFirstVariadicArgument] = identifierNameInfo; + const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName); + if (!isParameterNameNotSameAsArgument && !isFirstVariadicArgument) { continue; } - const identifierNameInfo = checker.getParameterIdentifierNameAtPosition(signature, i); - if (identifierNameInfo) { - const [parameterName, isFirstVariadicArgument] = identifierNameInfo; - const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName); - if (!isParameterNameNotSameAsArgument && !isFirstVariadicArgument) { - continue; - } - - const name = unescapeLeadingUnderscores(parameterName); - if (leadingCommentsContainsParameterName(arg, name)) { - continue; - } - - addParameterHints(name, originalArg.getStart(), isFirstVariadicArgument); + const name = unescapeLeadingUnderscores(parameterName); + if (leadingCommentsContainsParameterName(arg, name)) { + continue; } - } - } - function identifierOrAccessExpressionPostfixMatchesParameterName(expr: Expression, parameterName: __String) { - if (isIdentifier(expr)) { - return expr.text === parameterName; - } - if (isPropertyAccessExpression(expr)) { - return expr.name.text === parameterName; + addParameterHints(name, originalArg.getStart(), isFirstVariadicArgument); } - return false; } + } - function leadingCommentsContainsParameterName(node: Node, name: string) { - if (!isIdentifierText(name, compilerOptions.target, getLanguageVariant(file.scriptKind))) { - return false; - } - - const ranges = getLeadingCommentRanges(sourceFileText, node.pos); - if (!ranges?.length) { - return false; - } + function identifierOrAccessExpressionPostfixMatchesParameterName(expr: Expression, parameterName: __String) { + if (isIdentifier(expr)) { + return expr.text === parameterName; + } + if (isPropertyAccessExpression(expr)) { + return expr.name.text === parameterName; + } + return false; + } - const regex = leadingParameterNameCommentRegexFactory(name); - return some(ranges, range => regex.test(sourceFileText.substring(range.pos, range.end))); + function leadingCommentsContainsParameterName(node: Node, name: string) { + if (!isIdentifierText(name, compilerOptions.target, getLanguageVariant(file.scriptKind))) { + return false; } - function isHintableLiteral(node: Node) { - switch (node.kind) { - case SyntaxKind.PrefixUnaryExpression: { - const operand = (node as PrefixUnaryExpression).operand; - return isLiteralExpression(operand) || isIdentifier(operand) && isInfinityOrNaNString(operand.escapedText); - } - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateExpression: - return true; - case SyntaxKind.Identifier: { - const name = (node as Identifier).escapedText; - return isUndefined(name) || isInfinityOrNaNString(name); - } - } - return isLiteralExpression(node); + const ranges = getLeadingCommentRanges(sourceFileText, node.pos); + if (!ranges?.length) { + return false; } - function visitFunctionDeclarationLikeForReturnType(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) { - if (isArrowFunction(decl)) { - if (!findChildOfKind(decl, SyntaxKind.OpenParenToken, file)) { - return; - } - } + const regex = leadingParameterNameCommentRegexFactory(name); + return some(ranges, range => regex.test(sourceFileText.substring(range.pos, range.end))); + } - const effectiveTypeAnnotation = getEffectiveReturnTypeNode(decl); - if (effectiveTypeAnnotation || !decl.body) { - return; + function isHintableLiteral(node: Node) { + switch (node.kind) { + case SyntaxKind.PrefixUnaryExpression: { + const operand = (node as PrefixUnaryExpression).operand; + return isLiteralExpression(operand) || isIdentifier(operand) && isInfinityOrNaNString(operand.escapedText); + } + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateExpression: + return true; + case SyntaxKind.Identifier: { + const name = (node as Identifier).escapedText; + return isUndefined(name) || isInfinityOrNaNString(name); } + } + return isLiteralExpression(node); + } - const signature = checker.getSignatureFromDeclaration(decl); - if (!signature) { + function visitFunctionDeclarationLikeForReturnType(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) { + if (isArrowFunction(decl)) { + if (!findChildOfKind(decl, SyntaxKind.OpenParenToken, file)) { return; } + } - const returnType = checker.getReturnTypeOfSignature(signature); - if (isModuleReferenceType(returnType)) { - return; - } + const effectiveTypeAnnotation = getEffectiveReturnTypeNode(decl); + if (effectiveTypeAnnotation || !decl.body) { + return; + } - const typeDisplayString = printTypeInSingleLine(returnType); - if (!typeDisplayString) { - return; - } + const signature = checker.getSignatureFromDeclaration(decl); + if (!signature) { + return; + } - addTypeHints(typeDisplayString, getTypeAnnotationPosition(decl)); + const returnType = checker.getReturnTypeOfSignature(signature); + if (isModuleReferenceType(returnType)) { + return; } - function getTypeAnnotationPosition(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) { - const closeParenToken = findChildOfKind(decl, SyntaxKind.CloseParenToken, file); - if (closeParenToken) { - return closeParenToken.end; - } - return decl.parameters.end; + const typeDisplayString = printTypeInSingleLine(returnType); + if (!typeDisplayString) { + return; } - function visitFunctionLikeForParameterType(node: FunctionLikeDeclaration) { - const signature = checker.getSignatureFromDeclaration(node); - if (!signature) { - return; - } + addTypeHints(typeDisplayString, getTypeAnnotationPosition(decl)); + } - for (let i = 0; i < node.parameters.length && i < signature.parameters.length; ++i) { - const param = node.parameters[i]; - const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(param); + function getTypeAnnotationPosition(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) { + const closeParenToken = findChildOfKind(decl, SyntaxKind.CloseParenToken, file); + if (closeParenToken) { + return closeParenToken.end; + } + return decl.parameters.end; + } - if (effectiveTypeAnnotation) { - continue; - } + function visitFunctionLikeForParameterType(node: FunctionLikeDeclaration) { + const signature = checker.getSignatureFromDeclaration(node); + if (!signature) { + return; + } - const typeDisplayString = getParameterDeclarationTypeDisplayString(signature.parameters[i]); - if (!typeDisplayString) { - continue; - } + for (let i = 0; i < node.parameters.length && i < signature.parameters.length; ++i) { + const param = node.parameters[i]; + const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(param); - addTypeHints(typeDisplayString, param.name.end); + if (effectiveTypeAnnotation) { + continue; } - } - function getParameterDeclarationTypeDisplayString(symbol: Symbol) { - const valueDeclaration = symbol.valueDeclaration; - if (!valueDeclaration || !isParameter(valueDeclaration)) { - return undefined; + const typeDisplayString = getParameterDeclarationTypeDisplayString(signature.parameters[i]); + if (!typeDisplayString) { + continue; } - const signatureParamType = checker.getTypeOfSymbolAtLocation(symbol, valueDeclaration); - if (isModuleReferenceType(signatureParamType)) { - return undefined; - } + addTypeHints(typeDisplayString, param.name.end); + } + } - return printTypeInSingleLine(signatureParamType); + function getParameterDeclarationTypeDisplayString(symbol: Symbol) { + const valueDeclaration = symbol.valueDeclaration; + if (!valueDeclaration || !isParameter(valueDeclaration)) { + return undefined; } - function truncation(text: string, maxLength: number) { - if (text.length > maxLength) { - return text.substr(0, maxLength - "...".length) + "..."; - } - return text; + const signatureParamType = checker.getTypeOfSymbolAtLocation(symbol, valueDeclaration); + if (isModuleReferenceType(signatureParamType)) { + return undefined; } - function printTypeInSingleLine(type: Type) { - const flags = NodeBuilderFlags.IgnoreErrors | TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope; - const options: PrinterOptions = { removeComments: true }; - const printer = createPrinter(options); + return printTypeInSingleLine(signatureParamType); + } - return usingSingleLineStringWriter(writer => { - const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags, writer); - Debug.assertIsDefined(typeNode, "should always get typenode"); - printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ file, writer); - }); + function truncation(text: string, maxLength: number) { + if (text.length > maxLength) { + return text.substr(0, maxLength - "...".length) + "..."; } + return text; + } - function isUndefined(name: __String) { - return name === "undefined"; - } + function printTypeInSingleLine(type: Type) { + const flags = NodeBuilderFlags.IgnoreErrors | TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope; + const options: PrinterOptions = { removeComments: true }; + const printer = createPrinter(options); + + return usingSingleLineStringWriter(writer => { + const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags, writer); + Debug.assertIsDefined(typeNode, "should always get typenode"); + printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ file, writer); + }); } + + function isUndefined(name: __String) { + return name === "undefined"; + } +} } diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 2e2bc61136a3f..f26120157e1bf 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -1,473 +1,473 @@ /* @internal */ namespace ts.JsDoc { - const jsDocTagNames = [ - "abstract", - "access", - "alias", - "argument", - "async", - "augments", - "author", - "borrows", - "callback", - "class", - "classdesc", - "constant", - "constructor", - "constructs", - "copyright", - "default", - "deprecated", - "description", - "emits", - "enum", - "event", - "example", - "exports", - "extends", - "external", - "field", - "file", - "fileoverview", - "fires", - "function", - "generator", - "global", - "hideconstructor", - "host", - "ignore", - "implements", - "inheritdoc", - "inner", - "instance", - "interface", - "kind", - "lends", - "license", - "link", - "listens", - "member", - "memberof", - "method", - "mixes", - "module", - "name", - "namespace", - "override", - "package", - "param", - "private", - "property", - "protected", - "public", - "readonly", - "requires", - "returns", - "see", - "since", - "static", - "summary", - "template", - "this", - "throws", - "todo", - "tutorial", - "type", - "typedef", - "var", - "variation", - "version", - "virtual", - "yields" - ]; - let jsDocTagNameCompletionEntries: CompletionEntry[]; - let jsDocTagCompletionEntries: CompletionEntry[]; - - export function getJsDocCommentsFromDeclarations(declarations: readonly Declaration[], checker?: TypeChecker): SymbolDisplayPart[] { - // Only collect doc comments from duplicate declarations once: - // In case of a union property there might be same declaration multiple times - // which only varies in type parameter - // Eg. const a: Array | Array; a.length - // The property length will have two declarations of property length coming - // from Array - Array and Array - const parts: SymbolDisplayPart[][] = []; - forEachUnique(declarations, declaration => { - for (const jsdoc of getCommentHavingNodes(declaration)) { - // skip comments containing @typedefs since they're not associated with particular declarations - // Exceptions: - // - @typedefs are themselves declarations with associated comments - // - @param or @return indicate that the author thinks of it as a 'local' @typedef that's part of the function documentation - if (jsdoc.comment === undefined - || isJSDoc(jsdoc) - && declaration.kind !== SyntaxKind.JSDocTypedefTag && declaration.kind !== SyntaxKind.JSDocCallbackTag - && jsdoc.tags - && jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag) - && !jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) { - continue; - } - const newparts = getDisplayPartsFromComment(jsdoc.comment, checker); - if (!contains(parts, newparts, isIdenticalListOfDisplayParts)) { - parts.push(newparts); - } - } - }); - return flatten(intersperse(parts, [lineBreakPart()])); - } - - function isIdenticalListOfDisplayParts(parts1: SymbolDisplayPart[], parts2: SymbolDisplayPart[]) { - return arraysEqual(parts1, parts2, (p1, p2) => p1.kind === p2.kind && p1.text === p2.text); - } - - function getCommentHavingNodes(declaration: Declaration): readonly (JSDoc | JSDocTag)[] { - switch (declaration.kind) { - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - return [declaration as JSDocPropertyTag]; - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocTypedefTag: - return [(declaration as JSDocTypedefTag), (declaration as JSDocTypedefTag).parent]; - default: - return getJSDocCommentsAndTags(declaration); - } - } - - export function getJsDocTagsFromDeclarations(declarations?: Declaration[], checker?: TypeChecker): JSDocTagInfo[] { - // Only collect doc comments from duplicate declarations once. - const infos: JSDocTagInfo[] = []; - forEachUnique(declarations, declaration => { - const tags = getJSDocTags(declaration); +const jsDocTagNames = [ + "abstract", + "access", + "alias", + "argument", + "async", + "augments", + "author", + "borrows", + "callback", + "class", + "classdesc", + "constant", + "constructor", + "constructs", + "copyright", + "default", + "deprecated", + "description", + "emits", + "enum", + "event", + "example", + "exports", + "extends", + "external", + "field", + "file", + "fileoverview", + "fires", + "function", + "generator", + "global", + "hideconstructor", + "host", + "ignore", + "implements", + "inheritdoc", + "inner", + "instance", + "interface", + "kind", + "lends", + "license", + "link", + "listens", + "member", + "memberof", + "method", + "mixes", + "module", + "name", + "namespace", + "override", + "package", + "param", + "private", + "property", + "protected", + "public", + "readonly", + "requires", + "returns", + "see", + "since", + "static", + "summary", + "template", + "this", + "throws", + "todo", + "tutorial", + "type", + "typedef", + "var", + "variation", + "version", + "virtual", + "yields" +]; +let jsDocTagNameCompletionEntries: CompletionEntry[]; +let jsDocTagCompletionEntries: CompletionEntry[]; + +export function getJsDocCommentsFromDeclarations(declarations: readonly Declaration[], checker?: TypeChecker): SymbolDisplayPart[] { + // Only collect doc comments from duplicate declarations once: + // In case of a union property there might be same declaration multiple times + // which only varies in type parameter + // Eg. const a: Array | Array; a.length + // The property length will have two declarations of property length coming + // from Array - Array and Array + const parts: SymbolDisplayPart[][] = []; + forEachUnique(declarations, declaration => { + for (const jsdoc of getCommentHavingNodes(declaration)) { // skip comments containing @typedefs since they're not associated with particular declarations // Exceptions: + // - @typedefs are themselves declarations with associated comments // - @param or @return indicate that the author thinks of it as a 'local' @typedef that's part of the function documentation - if (tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag) - && !tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) { - return; + if (jsdoc.comment === undefined + || isJSDoc(jsdoc) + && declaration.kind !== SyntaxKind.JSDocTypedefTag && declaration.kind !== SyntaxKind.JSDocCallbackTag + && jsdoc.tags + && jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag) + && !jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) { + continue; } - for (const tag of tags) { - infos.push({ name: tag.tagName.text, text: getCommentDisplayParts(tag, checker) }); + const newparts = getDisplayPartsFromComment(jsdoc.comment, checker); + if (!contains(parts, newparts, isIdenticalListOfDisplayParts)) { + parts.push(newparts); } - }); - return infos; + } + }); + return flatten(intersperse(parts, [lineBreakPart()])); +} + +function isIdenticalListOfDisplayParts(parts1: SymbolDisplayPart[], parts2: SymbolDisplayPart[]) { + return arraysEqual(parts1, parts2, (p1, p2) => p1.kind === p2.kind && p1.text === p2.text); +} + +function getCommentHavingNodes(declaration: Declaration): readonly (JSDoc | JSDocTag)[] { + switch (declaration.kind) { + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocPropertyTag: + return [declaration as JSDocPropertyTag]; + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: + return [(declaration as JSDocTypedefTag), (declaration as JSDocTypedefTag).parent]; + default: + return getJSDocCommentsAndTags(declaration); } +} - function getDisplayPartsFromComment(comment: string | readonly JSDocComment[], checker: TypeChecker | undefined): SymbolDisplayPart[] { - if (typeof comment === "string") { - return [textPart(comment)]; +export function getJsDocTagsFromDeclarations(declarations?: Declaration[], checker?: TypeChecker): JSDocTagInfo[] { + // Only collect doc comments from duplicate declarations once. + const infos: JSDocTagInfo[] = []; + forEachUnique(declarations, declaration => { + const tags = getJSDocTags(declaration); + // skip comments containing @typedefs since they're not associated with particular declarations + // Exceptions: + // - @param or @return indicate that the author thinks of it as a 'local' @typedef that's part of the function documentation + if (tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag) + && !tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) { + return; } - return flatMap( - comment, - node => node.kind === SyntaxKind.JSDocText ? [textPart(node.text)] : buildLinkParts(node, checker) - ) as SymbolDisplayPart[]; + for (const tag of tags) { + infos.push({ name: tag.tagName.text, text: getCommentDisplayParts(tag, checker) }); + } + }); + return infos; +} + +function getDisplayPartsFromComment(comment: string | readonly JSDocComment[], checker: TypeChecker | undefined): SymbolDisplayPart[] { + if (typeof comment === "string") { + return [textPart(comment)]; } + return flatMap( + comment, + node => node.kind === SyntaxKind.JSDocText ? [textPart(node.text)] : buildLinkParts(node, checker) + ) as SymbolDisplayPart[]; +} - function getCommentDisplayParts(tag: JSDocTag, checker?: TypeChecker): SymbolDisplayPart[] | undefined { - const { comment, kind } = tag; - const namePart = getTagNameDisplayPart(kind); - switch (kind) { - case SyntaxKind.JSDocImplementsTag: - return withNode((tag as JSDocImplementsTag).class); - case SyntaxKind.JSDocAugmentsTag: - return withNode((tag as JSDocAugmentsTag).class); - case SyntaxKind.JSDocTemplateTag: - return addComment((tag as JSDocTemplateTag).typeParameters.map(tp => tp.getText()).join(", ")); - case SyntaxKind.JSDocTypeTag: - return withNode((tag as JSDocTypeTag).typeExpression); - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocPropertyTag: - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocSeeTag: - const { name } = tag as JSDocTypedefTag | JSDocCallbackTag | JSDocPropertyTag | JSDocParameterTag | JSDocSeeTag; - return name ? withNode(name) - : comment === undefined ? undefined - : getDisplayPartsFromComment(comment, checker); - default: - return comment === undefined ? undefined : getDisplayPartsFromComment(comment, checker); - } +function getCommentDisplayParts(tag: JSDocTag, checker?: TypeChecker): SymbolDisplayPart[] | undefined { + const { comment, kind } = tag; + const namePart = getTagNameDisplayPart(kind); + switch (kind) { + case SyntaxKind.JSDocImplementsTag: + return withNode((tag as JSDocImplementsTag).class); + case SyntaxKind.JSDocAugmentsTag: + return withNode((tag as JSDocAugmentsTag).class); + case SyntaxKind.JSDocTemplateTag: + return addComment((tag as JSDocTemplateTag).typeParameters.map(tp => tp.getText()).join(", ")); + case SyntaxKind.JSDocTypeTag: + return withNode((tag as JSDocTypeTag).typeExpression); + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocPropertyTag: + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocSeeTag: + const { name } = tag as JSDocTypedefTag | JSDocCallbackTag | JSDocPropertyTag | JSDocParameterTag | JSDocSeeTag; + return name ? withNode(name) + : comment === undefined ? undefined + : getDisplayPartsFromComment(comment, checker); + default: + return comment === undefined ? undefined : getDisplayPartsFromComment(comment, checker); + } - function withNode(node: Node) { - return addComment(node.getText()); - } + function withNode(node: Node) { + return addComment(node.getText()); + } - function addComment(s: string) { - if (comment) { - if (s.match(/^https?$/)) { - return [textPart(s), ...getDisplayPartsFromComment(comment, checker)]; - } - else { - return [namePart(s), spacePart(), ...getDisplayPartsFromComment(comment, checker)]; - } + function addComment(s: string) { + if (comment) { + if (s.match(/^https?$/)) { + return [textPart(s), ...getDisplayPartsFromComment(comment, checker)]; } else { - return [textPart(s)]; + return [namePart(s), spacePart(), ...getDisplayPartsFromComment(comment, checker)]; } } - } - - function getTagNameDisplayPart(kind: SyntaxKind): (text: string) => SymbolDisplayPart { - switch (kind) { - case SyntaxKind.JSDocParameterTag: - return parameterNamePart; - case SyntaxKind.JSDocPropertyTag: - return propertyNamePart; - case SyntaxKind.JSDocTemplateTag: - return typeParameterNamePart; - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - return typeAliasNamePart; - default: - return textPart; + else { + return [textPart(s)]; } } +} - export function getJSDocTagNameCompletions(): CompletionEntry[] { - return jsDocTagNameCompletionEntries || (jsDocTagNameCompletionEntries = map(jsDocTagNames, tagName => { - return { - name: tagName, - kind: ScriptElementKind.keyword, - kindModifiers: "", - sortText: Completions.SortText.LocationPriority, - }; - })); - } - - export const getJSDocTagNameCompletionDetails = getJSDocTagCompletionDetails; - - export function getJSDocTagCompletions(): CompletionEntry[] { - return jsDocTagCompletionEntries || (jsDocTagCompletionEntries = map(jsDocTagNames, tagName => { - return { - name: `@${tagName}`, - kind: ScriptElementKind.keyword, - kindModifiers: "", - sortText: Completions.SortText.LocationPriority - }; - })); +function getTagNameDisplayPart(kind: SyntaxKind): (text: string) => SymbolDisplayPart { + switch (kind) { + case SyntaxKind.JSDocParameterTag: + return parameterNamePart; + case SyntaxKind.JSDocPropertyTag: + return propertyNamePart; + case SyntaxKind.JSDocTemplateTag: + return typeParameterNamePart; + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + return typeAliasNamePart; + default: + return textPart; } +} - export function getJSDocTagCompletionDetails(name: string): CompletionEntryDetails { +export function getJSDocTagNameCompletions(): CompletionEntry[] { + return jsDocTagNameCompletionEntries || (jsDocTagNameCompletionEntries = map(jsDocTagNames, tagName => { return { - name, - kind: ScriptElementKind.unknown, // TODO: should have its own kind? + name: tagName, + kind: ScriptElementKind.keyword, kindModifiers: "", - displayParts: [textPart(name)], - documentation: emptyArray, - tags: undefined, - codeActions: undefined, + sortText: Completions.SortText.LocationPriority, }; - } - - export function getJSDocParameterNameCompletions(tag: JSDocParameterTag): CompletionEntry[] { - if (!isIdentifier(tag.name)) { - return emptyArray; - } - const nameThusFar = tag.name.text; - const jsdoc = tag.parent; - const fn = jsdoc.parent; - if (!isFunctionLike(fn)) return []; - - return mapDefined(fn.parameters, param => { - if (!isIdentifier(param.name)) return undefined; - - const name = param.name.text; - if (jsdoc.tags!.some(t => t !== tag && isJSDocParameterTag(t) && isIdentifier(t.name) && t.name.escapedText === name) // TODO: GH#18217 - || nameThusFar !== undefined && !startsWith(name, nameThusFar)) { - return undefined; - } + })); +} - return { name, kind: ScriptElementKind.parameterElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority }; - }); - } +export const getJSDocTagNameCompletionDetails = getJSDocTagCompletionDetails; - export function getJSDocParameterNameCompletionDetails(name: string): CompletionEntryDetails { +export function getJSDocTagCompletions(): CompletionEntry[] { + return jsDocTagCompletionEntries || (jsDocTagCompletionEntries = map(jsDocTagNames, tagName => { return { - name, - kind: ScriptElementKind.parameterElement, + name: `@${tagName}`, + kind: ScriptElementKind.keyword, kindModifiers: "", - displayParts: [textPart(name)], - documentation: emptyArray, - tags: undefined, - codeActions: undefined, + sortText: Completions.SortText.LocationPriority }; + })); +} + +export function getJSDocTagCompletionDetails(name: string): CompletionEntryDetails { + return { + name, + kind: ScriptElementKind.unknown, // TODO: should have its own kind? + kindModifiers: "", + displayParts: [textPart(name)], + documentation: emptyArray, + tags: undefined, + codeActions: undefined, + }; +} + +export function getJSDocParameterNameCompletions(tag: JSDocParameterTag): CompletionEntry[] { + if (!isIdentifier(tag.name)) { + return emptyArray; } + const nameThusFar = tag.name.text; + const jsdoc = tag.parent; + const fn = jsdoc.parent; + if (!isFunctionLike(fn)) return []; - /** - * Checks if position points to a valid position to add JSDoc comments, and if so, - * returns the appropriate template. Otherwise returns an empty string. - * Valid positions are - * - outside of comments, statements, and expressions, and - * - preceding a: - * - function/constructor/method declaration - * - class declarations - * - variable statements - * - namespace declarations - * - interface declarations - * - method signatures - * - type alias declarations - * - * Hosts should ideally check that: - * - The line is all whitespace up to 'position' before performing the insertion. - * - If the keystroke sequence "/\*\*" induced the call, we also check that the next - * non-whitespace character is '*', which (approximately) indicates whether we added - * the second '*' to complete an existing (JSDoc) comment. - * @param fileName The file in which to perform the check. - * @param position The (character-indexed) position in the file where the check should - * be performed. - */ - export function getDocCommentTemplateAtPosition(newLine: string, sourceFile: SourceFile, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined { - const tokenAtPos = getTokenAtPosition(sourceFile, position); - const existingDocComment = findAncestor(tokenAtPos, isJSDoc); - if (existingDocComment && (existingDocComment.comment !== undefined || length(existingDocComment.tags))) { - // Non-empty comment already exists. - return undefined; - } + return mapDefined(fn.parameters, param => { + if (!isIdentifier(param.name)) return undefined; - const tokenStart = tokenAtPos.getStart(sourceFile); - // Don't provide a doc comment template based on a *previous* node. (But an existing empty jsdoc comment will likely start before `position`.) - if (!existingDocComment && tokenStart < position) { + const name = param.name.text; + if (jsdoc.tags!.some(t => t !== tag && isJSDocParameterTag(t) && isIdentifier(t.name) && t.name.escapedText === name) // TODO: GH#18217 + || nameThusFar !== undefined && !startsWith(name, nameThusFar)) { return undefined; } - const commentOwnerInfo = getCommentOwnerInfo(tokenAtPos, options); - if (!commentOwnerInfo) { - return undefined; - } + return { name, kind: ScriptElementKind.parameterElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority }; + }); +} - const { commentOwner, parameters, hasReturn } = commentOwnerInfo; - if (commentOwner.getStart(sourceFile) < position) { - return undefined; - } +export function getJSDocParameterNameCompletionDetails(name: string): CompletionEntryDetails { + return { + name, + kind: ScriptElementKind.parameterElement, + kindModifiers: "", + displayParts: [textPart(name)], + documentation: emptyArray, + tags: undefined, + codeActions: undefined, + }; +} - const indentationStr = getIndentationStringAtPosition(sourceFile, position); - const isJavaScriptFile = hasJSFileExtension(sourceFile.fileName); - const tags = - (parameters ? parameterDocComments(parameters || [], isJavaScriptFile, indentationStr, newLine) : "") + - (hasReturn ? returnsDocComment(indentationStr, newLine) : ""); - - // A doc comment consists of the following - // * The opening comment line - // * the first line (without a param) for the object's untagged info (this is also where the caret ends up) - // * the '@param'-tagged lines - // * the '@returns'-tag - // * TODO: other tags. - // * the closing comment line - // * if the caret was directly in front of the object, then we add an extra line and indentation. - const openComment = "/**"; - const closeComment = " */"; - if (tags) { - const preamble = openComment + newLine + indentationStr + " * "; - const endLine = tokenStart === position ? newLine + indentationStr : ""; - const result = preamble + newLine + tags + indentationStr + closeComment + endLine; - return { newText: result, caretOffset: preamble.length }; - } - return { newText: openComment + closeComment, caretOffset: 3 }; +/** + * Checks if position points to a valid position to add JSDoc comments, and if so, + * returns the appropriate template. Otherwise returns an empty string. + * Valid positions are + * - outside of comments, statements, and expressions, and + * - preceding a: + * - function/constructor/method declaration + * - class declarations + * - variable statements + * - namespace declarations + * - interface declarations + * - method signatures + * - type alias declarations + * + * Hosts should ideally check that: + * - The line is all whitespace up to 'position' before performing the insertion. + * - If the keystroke sequence "/\*\*" induced the call, we also check that the next + * non-whitespace character is '*', which (approximately) indicates whether we added + * the second '*' to complete an existing (JSDoc) comment. + * @param fileName The file in which to perform the check. + * @param position The (character-indexed) position in the file where the check should + * be performed. + */ +export function getDocCommentTemplateAtPosition(newLine: string, sourceFile: SourceFile, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined { + const tokenAtPos = getTokenAtPosition(sourceFile, position); + const existingDocComment = findAncestor(tokenAtPos, isJSDoc); + if (existingDocComment && (existingDocComment.comment !== undefined || length(existingDocComment.tags))) { + // Non-empty comment already exists. + return undefined; } - function getIndentationStringAtPosition(sourceFile: SourceFile, position: number): string { - const { text } = sourceFile; - const lineStart = getLineStartPositionForPosition(position, sourceFile); - let pos = lineStart; - for (; pos <= position && isWhiteSpaceSingleLine(text.charCodeAt(pos)); pos++); - return text.slice(lineStart, pos); + const tokenStart = tokenAtPos.getStart(sourceFile); + // Don't provide a doc comment template based on a *previous* node. (But an existing empty jsdoc comment will likely start before `position`.) + if (!existingDocComment && tokenStart < position) { + return undefined; } - function parameterDocComments(parameters: readonly ParameterDeclaration[], isJavaScriptFile: boolean, indentationStr: string, newLine: string): string { - return parameters.map(({ name, dotDotDotToken }, i) => { - const paramName = name.kind === SyntaxKind.Identifier ? name.text : "param" + i; - const type = isJavaScriptFile ? (dotDotDotToken ? "{...any} " : "{any} ") : ""; - return `${indentationStr} * @param ${type}${paramName}${newLine}`; - }).join(""); + const commentOwnerInfo = getCommentOwnerInfo(tokenAtPos, options); + if (!commentOwnerInfo) { + return undefined; } - function returnsDocComment(indentationStr: string, newLine: string) { - return `${indentationStr} * @returns${newLine}`; + const { commentOwner, parameters, hasReturn } = commentOwnerInfo; + if (commentOwner.getStart(sourceFile) < position) { + return undefined; } - interface CommentOwnerInfo { - readonly commentOwner: Node; - readonly parameters?: readonly ParameterDeclaration[]; - readonly hasReturn?: boolean; - } - function getCommentOwnerInfo(tokenAtPos: Node, options: DocCommentTemplateOptions | undefined): CommentOwnerInfo | undefined { - return forEachAncestor(tokenAtPos, n => getCommentOwnerInfoWorker(n, options)); + const indentationStr = getIndentationStringAtPosition(sourceFile, position); + const isJavaScriptFile = hasJSFileExtension(sourceFile.fileName); + const tags = + (parameters ? parameterDocComments(parameters || [], isJavaScriptFile, indentationStr, newLine) : "") + + (hasReturn ? returnsDocComment(indentationStr, newLine) : ""); + + // A doc comment consists of the following + // * The opening comment line + // * the first line (without a param) for the object's untagged info (this is also where the caret ends up) + // * the '@param'-tagged lines + // * the '@returns'-tag + // * TODO: other tags. + // * the closing comment line + // * if the caret was directly in front of the object, then we add an extra line and indentation. + const openComment = "/**"; + const closeComment = " */"; + if (tags) { + const preamble = openComment + newLine + indentationStr + " * "; + const endLine = tokenStart === position ? newLine + indentationStr : ""; + const result = preamble + newLine + tags + indentationStr + closeComment + endLine; + return { newText: result, caretOffset: preamble.length }; } - function getCommentOwnerInfoWorker(commentOwner: Node, options: DocCommentTemplateOptions | undefined): CommentOwnerInfo | undefined | "quit" { - switch (commentOwner.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.MethodSignature: - case SyntaxKind.ArrowFunction: - const host = commentOwner as ArrowFunction | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature; - return { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host, options) }; - - case SyntaxKind.PropertyAssignment: - return getCommentOwnerInfoWorker((commentOwner as PropertyAssignment).initializer, options); - - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.EnumMember: - case SyntaxKind.TypeAliasDeclaration: - return { commentOwner }; - - case SyntaxKind.VariableStatement: { - const varStatement = commentOwner as VariableStatement; - const varDeclarations = varStatement.declarationList.declarations; - const host = varDeclarations.length === 1 && varDeclarations[0].initializer - ? getRightHandSideOfAssignment(varDeclarations[0].initializer) - : undefined; - return host - ? { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host, options) } - : { commentOwner }; - } + return { newText: openComment + closeComment, caretOffset: 3 }; +} - case SyntaxKind.SourceFile: - return "quit"; +function getIndentationStringAtPosition(sourceFile: SourceFile, position: number): string { + const { text } = sourceFile; + const lineStart = getLineStartPositionForPosition(position, sourceFile); + let pos = lineStart; + for (; pos <= position && isWhiteSpaceSingleLine(text.charCodeAt(pos)); pos++); + return text.slice(lineStart, pos); +} + +function parameterDocComments(parameters: readonly ParameterDeclaration[], isJavaScriptFile: boolean, indentationStr: string, newLine: string): string { + return parameters.map(({ name, dotDotDotToken }, i) => { + const paramName = name.kind === SyntaxKind.Identifier ? name.text : "param" + i; + const type = isJavaScriptFile ? (dotDotDotToken ? "{...any} " : "{any} ") : ""; + return `${indentationStr} * @param ${type}${paramName}${newLine}`; + }).join(""); +} - case SyntaxKind.ModuleDeclaration: - // If in walking up the tree, we hit a a nested namespace declaration, - // then we must be somewhere within a dotted namespace name; however we don't - // want to give back a JSDoc template for the 'b' or 'c' in 'namespace a.b.c { }'. - return commentOwner.parent.kind === SyntaxKind.ModuleDeclaration ? undefined : { commentOwner }; - - case SyntaxKind.ExpressionStatement: - return getCommentOwnerInfoWorker((commentOwner as ExpressionStatement).expression, options); - case SyntaxKind.BinaryExpression: { - const be = commentOwner as BinaryExpression; - if (getAssignmentDeclarationKind(be) === AssignmentDeclarationKind.None) { - return "quit"; - } - return isFunctionLike(be.right) - ? { commentOwner, parameters: be.right.parameters, hasReturn: hasReturn(be.right, options) } - : { commentOwner }; +function returnsDocComment(indentationStr: string, newLine: string) { + return `${indentationStr} * @returns${newLine}`; +} + +interface CommentOwnerInfo { + readonly commentOwner: Node; + readonly parameters?: readonly ParameterDeclaration[]; + readonly hasReturn?: boolean; +} +function getCommentOwnerInfo(tokenAtPos: Node, options: DocCommentTemplateOptions | undefined): CommentOwnerInfo | undefined { + return forEachAncestor(tokenAtPos, n => getCommentOwnerInfoWorker(n, options)); +} +function getCommentOwnerInfoWorker(commentOwner: Node, options: DocCommentTemplateOptions | undefined): CommentOwnerInfo | undefined | "quit" { + switch (commentOwner.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.MethodSignature: + case SyntaxKind.ArrowFunction: + const host = commentOwner as ArrowFunction | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature; + return { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host, options) }; + + case SyntaxKind.PropertyAssignment: + return getCommentOwnerInfoWorker((commentOwner as PropertyAssignment).initializer, options); + + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.EnumMember: + case SyntaxKind.TypeAliasDeclaration: + return { commentOwner }; + + case SyntaxKind.VariableStatement: { + const varStatement = commentOwner as VariableStatement; + const varDeclarations = varStatement.declarationList.declarations; + const host = varDeclarations.length === 1 && varDeclarations[0].initializer + ? getRightHandSideOfAssignment(varDeclarations[0].initializer) + : undefined; + return host + ? { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host, options) } + : { commentOwner }; + } + + case SyntaxKind.SourceFile: + return "quit"; + + case SyntaxKind.ModuleDeclaration: + // If in walking up the tree, we hit a a nested namespace declaration, + // then we must be somewhere within a dotted namespace name; however we don't + // want to give back a JSDoc template for the 'b' or 'c' in 'namespace a.b.c { }'. + return commentOwner.parent.kind === SyntaxKind.ModuleDeclaration ? undefined : { commentOwner }; + + case SyntaxKind.ExpressionStatement: + return getCommentOwnerInfoWorker((commentOwner as ExpressionStatement).expression, options); + case SyntaxKind.BinaryExpression: { + const be = commentOwner as BinaryExpression; + if (getAssignmentDeclarationKind(be) === AssignmentDeclarationKind.None) { + return "quit"; } - case SyntaxKind.PropertyDeclaration: - const init = (commentOwner as PropertyDeclaration).initializer; - if (init && (isFunctionExpression(init) || isArrowFunction(init))) { - return { commentOwner, parameters: init.parameters, hasReturn: hasReturn(init, options) }; - } + return isFunctionLike(be.right) + ? { commentOwner, parameters: be.right.parameters, hasReturn: hasReturn(be.right, options) } + : { commentOwner }; } + case SyntaxKind.PropertyDeclaration: + const init = (commentOwner as PropertyDeclaration).initializer; + if (init && (isFunctionExpression(init) || isArrowFunction(init))) { + return { commentOwner, parameters: init.parameters, hasReturn: hasReturn(init, options) }; + } } +} - function hasReturn(node: Node, options: DocCommentTemplateOptions | undefined) { - return !!options?.generateReturnInDocTemplate && - (isArrowFunction(node) && isExpression(node.body) - || isFunctionLikeDeclaration(node) && node.body && isBlock(node.body) && !!forEachReturnStatement(node.body, n => n)); - } +function hasReturn(node: Node, options: DocCommentTemplateOptions | undefined) { + return !!options?.generateReturnInDocTemplate && + (isArrowFunction(node) && isExpression(node.body) + || isFunctionLikeDeclaration(node) && node.body && isBlock(node.body) && !!forEachReturnStatement(node.body, n => n)); +} - function getRightHandSideOfAssignment(rightHandSide: Expression): FunctionExpression | ArrowFunction | ConstructorDeclaration | undefined { - while (rightHandSide.kind === SyntaxKind.ParenthesizedExpression) { - rightHandSide = (rightHandSide as ParenthesizedExpression).expression; - } +function getRightHandSideOfAssignment(rightHandSide: Expression): FunctionExpression | ArrowFunction | ConstructorDeclaration | undefined { + while (rightHandSide.kind === SyntaxKind.ParenthesizedExpression) { + rightHandSide = (rightHandSide as ParenthesizedExpression).expression; + } - switch (rightHandSide.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return (rightHandSide as FunctionExpression); - case SyntaxKind.ClassExpression: - return find((rightHandSide as ClassExpression).members, isConstructorDeclaration); - } + switch (rightHandSide.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return (rightHandSide as FunctionExpression); + case SyntaxKind.ClassExpression: + return find((rightHandSide as ClassExpression).members, isConstructorDeclaration); } } +} diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts index 709dd80eb90a3..9602cef56b65e 100644 --- a/src/services/navigateTo.ts +++ b/src/services/navigateTo.ts @@ -1,136 +1,136 @@ /* @internal */ namespace ts.NavigateTo { - interface RawNavigateToItem { - readonly name: string; - readonly fileName: string; - readonly matchKind: PatternMatchKind; - readonly isCaseSensitive: boolean; - readonly declaration: Declaration; - } - - export function getNavigateToItems(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number | undefined, excludeDtsFiles: boolean): NavigateToItem[] { - const patternMatcher = createPatternMatcher(searchValue); - if (!patternMatcher) return emptyArray; - const rawItems: RawNavigateToItem[] = []; +interface RawNavigateToItem { + readonly name: string; + readonly fileName: string; + readonly matchKind: PatternMatchKind; + readonly isCaseSensitive: boolean; + readonly declaration: Declaration; +} - // Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[] - for (const sourceFile of sourceFiles) { - cancellationToken.throwIfCancellationRequested(); +export function getNavigateToItems(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number | undefined, excludeDtsFiles: boolean): NavigateToItem[] { + const patternMatcher = createPatternMatcher(searchValue); + if (!patternMatcher) return emptyArray; + const rawItems: RawNavigateToItem[] = []; - if (excludeDtsFiles && sourceFile.isDeclarationFile) { - continue; - } + // Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[] + for (const sourceFile of sourceFiles) { + cancellationToken.throwIfCancellationRequested(); - sourceFile.getNamedDeclarations().forEach((declarations, name) => { - getItemsFromNamedDeclaration(patternMatcher, name, declarations, checker, sourceFile.fileName, rawItems); - }); + if (excludeDtsFiles && sourceFile.isDeclarationFile) { + continue; } - rawItems.sort(compareNavigateToItems); - return (maxResultCount === undefined ? rawItems : rawItems.slice(0, maxResultCount)).map(createNavigateToItem); + sourceFile.getNamedDeclarations().forEach((declarations, name) => { + getItemsFromNamedDeclaration(patternMatcher, name, declarations, checker, sourceFile.fileName, rawItems); + }); } - function getItemsFromNamedDeclaration(patternMatcher: PatternMatcher, name: string, declarations: readonly Declaration[], checker: TypeChecker, fileName: string, rawItems: Push): void { - // First do a quick check to see if the name of the declaration matches the - // last portion of the (possibly) dotted name they're searching for. - const match = patternMatcher.getMatchForLastSegmentOfPattern(name); - if (!match) { - return; // continue to next named declarations - } + rawItems.sort(compareNavigateToItems); + return (maxResultCount === undefined ? rawItems : rawItems.slice(0, maxResultCount)).map(createNavigateToItem); +} - for (const declaration of declarations) { - if (!shouldKeepItem(declaration, checker)) continue; +function getItemsFromNamedDeclaration(patternMatcher: PatternMatcher, name: string, declarations: readonly Declaration[], checker: TypeChecker, fileName: string, rawItems: Push): void { + // First do a quick check to see if the name of the declaration matches the + // last portion of the (possibly) dotted name they're searching for. + const match = patternMatcher.getMatchForLastSegmentOfPattern(name); + if (!match) { + return; // continue to next named declarations + } - if (patternMatcher.patternContainsDots) { - // If the pattern has dots in it, then also see if the declaration container matches as well. - const fullMatch = patternMatcher.getFullMatch(getContainers(declaration), name); - if (fullMatch) { - rawItems.push({ name, fileName, matchKind: fullMatch.kind, isCaseSensitive: fullMatch.isCaseSensitive, declaration }); - } - } - else { - rawItems.push({ name, fileName, matchKind: match.kind, isCaseSensitive: match.isCaseSensitive, declaration }); + for (const declaration of declarations) { + if (!shouldKeepItem(declaration, checker)) continue; + + if (patternMatcher.patternContainsDots) { + // If the pattern has dots in it, then also see if the declaration container matches as well. + const fullMatch = patternMatcher.getFullMatch(getContainers(declaration), name); + if (fullMatch) { + rawItems.push({ name, fileName, matchKind: fullMatch.kind, isCaseSensitive: fullMatch.isCaseSensitive, declaration }); } } - } - - function shouldKeepItem(declaration: Declaration, checker: TypeChecker): boolean { - switch (declaration.kind) { - case SyntaxKind.ImportClause: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportEqualsDeclaration: - const importer = checker.getSymbolAtLocation((declaration as ImportClause | ImportSpecifier | ImportEqualsDeclaration).name!)!; // TODO: GH#18217 - const imported = checker.getAliasedSymbol(importer); - return importer.escapedName !== imported.escapedName; - default: - return true; + else { + rawItems.push({ name, fileName, matchKind: match.kind, isCaseSensitive: match.isCaseSensitive, declaration }); } } +} - function tryAddSingleDeclarationName(declaration: Declaration, containers: Push): boolean { - const name = getNameOfDeclaration(declaration); - return !!name && (pushLiteral(name, containers) || name.kind === SyntaxKind.ComputedPropertyName && tryAddComputedPropertyName(name.expression, containers)); +function shouldKeepItem(declaration: Declaration, checker: TypeChecker): boolean { + switch (declaration.kind) { + case SyntaxKind.ImportClause: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportEqualsDeclaration: + const importer = checker.getSymbolAtLocation((declaration as ImportClause | ImportSpecifier | ImportEqualsDeclaration).name!)!; // TODO: GH#18217 + const imported = checker.getAliasedSymbol(importer); + return importer.escapedName !== imported.escapedName; + default: + return true; } +} - // Only added the names of computed properties if they're simple dotted expressions, like: - // - // [X.Y.Z]() { } - function tryAddComputedPropertyName(expression: Expression, containers: Push): boolean { - return pushLiteral(expression, containers) - || isPropertyAccessExpression(expression) && (containers.push(expression.name.text), true) && tryAddComputedPropertyName(expression.expression, containers); - } +function tryAddSingleDeclarationName(declaration: Declaration, containers: Push): boolean { + const name = getNameOfDeclaration(declaration); + return !!name && (pushLiteral(name, containers) || name.kind === SyntaxKind.ComputedPropertyName && tryAddComputedPropertyName(name.expression, containers)); +} - function pushLiteral(node: Node, containers: Push): boolean { - return isPropertyNameLiteral(node) && (containers.push(getTextOfIdentifierOrLiteral(node)), true); - } +// Only added the names of computed properties if they're simple dotted expressions, like: +// +// [X.Y.Z]() { } +function tryAddComputedPropertyName(expression: Expression, containers: Push): boolean { + return pushLiteral(expression, containers) + || isPropertyAccessExpression(expression) && (containers.push(expression.name.text), true) && tryAddComputedPropertyName(expression.expression, containers); +} - function getContainers(declaration: Declaration): readonly string[] { - const containers: string[] = []; +function pushLiteral(node: Node, containers: Push): boolean { + return isPropertyNameLiteral(node) && (containers.push(getTextOfIdentifierOrLiteral(node)), true); +} - // First, if we started with a computed property name, then add all but the last - // portion into the container array. - const name = getNameOfDeclaration(declaration); - if (name && name.kind === SyntaxKind.ComputedPropertyName && !tryAddComputedPropertyName(name.expression, containers)) { - return emptyArray; - } - // Don't include the last portion. - containers.shift(); +function getContainers(declaration: Declaration): readonly string[] { + const containers: string[] = []; - // Now, walk up our containers, adding all their names to the container array. - let container = getContainerNode(declaration); + // First, if we started with a computed property name, then add all but the last + // portion into the container array. + const name = getNameOfDeclaration(declaration); + if (name && name.kind === SyntaxKind.ComputedPropertyName && !tryAddComputedPropertyName(name.expression, containers)) { + return emptyArray; + } + // Don't include the last portion. + containers.shift(); - while (container) { - if (!tryAddSingleDeclarationName(container, containers)) { - return emptyArray; - } + // Now, walk up our containers, adding all their names to the container array. + let container = getContainerNode(declaration); - container = getContainerNode(container); + while (container) { + if (!tryAddSingleDeclarationName(container, containers)) { + return emptyArray; } - return containers.reverse(); + container = getContainerNode(container); } - function compareNavigateToItems(i1: RawNavigateToItem, i2: RawNavigateToItem) { - // TODO(cyrusn): get the gamut of comparisons that VS already uses here. - return compareValues(i1.matchKind, i2.matchKind) - || compareStringsCaseSensitiveUI(i1.name, i2.name); - } + return containers.reverse(); +} - function createNavigateToItem(rawItem: RawNavigateToItem): NavigateToItem { - const declaration = rawItem.declaration; - const container = getContainerNode(declaration); - const containerName = container && getNameOfDeclaration(container); - return { - name: rawItem.name, - kind: getNodeKind(declaration), - kindModifiers: getNodeModifiers(declaration), - matchKind: PatternMatchKind[rawItem.matchKind] as keyof typeof PatternMatchKind, - isCaseSensitive: rawItem.isCaseSensitive, - fileName: rawItem.fileName, - textSpan: createTextSpanFromNode(declaration), - // TODO(jfreeman): What should be the containerName when the container has a computed name? - containerName: containerName ? (containerName as Identifier).text : "", - containerKind: containerName ? getNodeKind(container!) : ScriptElementKind.unknown, // TODO: GH#18217 Just use `container ? ...` - }; - } +function compareNavigateToItems(i1: RawNavigateToItem, i2: RawNavigateToItem) { + // TODO(cyrusn): get the gamut of comparisons that VS already uses here. + return compareValues(i1.matchKind, i2.matchKind) + || compareStringsCaseSensitiveUI(i1.name, i2.name); +} + +function createNavigateToItem(rawItem: RawNavigateToItem): NavigateToItem { + const declaration = rawItem.declaration; + const container = getContainerNode(declaration); + const containerName = container && getNameOfDeclaration(container); + return { + name: rawItem.name, + kind: getNodeKind(declaration), + kindModifiers: getNodeModifiers(declaration), + matchKind: PatternMatchKind[rawItem.matchKind] as keyof typeof PatternMatchKind, + isCaseSensitive: rawItem.isCaseSensitive, + fileName: rawItem.fileName, + textSpan: createTextSpanFromNode(declaration), + // TODO(jfreeman): What should be the containerName when the container has a computed name? + containerName: containerName ? (containerName as Identifier).text : "", + containerKind: containerName ? getNodeKind(container!) : ScriptElementKind.unknown, // TODO: GH#18217 Just use `container ? ...` + }; +} } diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 6f9cdd55950c3..d1a7aa51c391c 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -1,985 +1,985 @@ /* @internal */ namespace ts.NavigationBar { - /** - * Matches all whitespace characters in a string. Eg: - * - * "app. - * - * onactivated" - * - * matches because of the newline, whereas - * - * "app.onactivated" - * - * does not match. - */ - const whiteSpaceRegex = /\s+/g; - - /** - * Maximum amount of characters to return - * The amount was chosen arbitrarily. - */ - const maxLength = 150; - - // Keep sourceFile handy so we don't have to search for it every time we need to call `getText`. - let curCancellationToken: CancellationToken; - let curSourceFile: SourceFile; - - /** - * For performance, we keep navigation bar parents on a stack rather than passing them through each recursion. - * `parent` is the current parent and is *not* stored in parentsStack. - * `startNode` sets a new parent and `endNode` returns to the previous parent. - */ - let parentsStack: NavigationBarNode[] = []; - let parent: NavigationBarNode; - - const trackedEs5ClassesStack: (ESMap | undefined)[] = []; - let trackedEs5Classes: ESMap | undefined; - - // NavigationBarItem requires an array, but will not mutate it, so just give it this for performance. - let emptyChildItemArray: NavigationBarItem[] = []; - - /** - * Represents a navigation bar item and its children. - * The returned NavigationBarItem is more complicated and doesn't include 'parent', so we use these to do work before converting. - */ - interface NavigationBarNode { - node: Node; - name: DeclarationName | undefined; - additionalNodes: Node[] | undefined; - parent: NavigationBarNode | undefined; // Present for all but root node - children: NavigationBarNode[] | undefined; - indent: number; // # of parents - } - - export function getNavigationBarItems(sourceFile: SourceFile, cancellationToken: CancellationToken): NavigationBarItem[] { - curCancellationToken = cancellationToken; - curSourceFile = sourceFile; - try { - return map(primaryNavBarMenuItems(rootNavigationBarNode(sourceFile)), convertToPrimaryNavBarMenuItem); - } - finally { - reset(); - } - } +/** + * Matches all whitespace characters in a string. Eg: + * + * "app. + * + * onactivated" + * + * matches because of the newline, whereas + * + * "app.onactivated" + * + * does not match. + */ +const whiteSpaceRegex = /\s+/g; + +/** + * Maximum amount of characters to return + * The amount was chosen arbitrarily. + */ +const maxLength = 150; + +// Keep sourceFile handy so we don't have to search for it every time we need to call `getText`. +let curCancellationToken: CancellationToken; +let curSourceFile: SourceFile; + +/** + * For performance, we keep navigation bar parents on a stack rather than passing them through each recursion. + * `parent` is the current parent and is *not* stored in parentsStack. + * `startNode` sets a new parent and `endNode` returns to the previous parent. + */ +let parentsStack: NavigationBarNode[] = []; +let parent: NavigationBarNode; + +const trackedEs5ClassesStack: (ESMap | undefined)[] = []; +let trackedEs5Classes: ESMap | undefined; + +// NavigationBarItem requires an array, but will not mutate it, so just give it this for performance. +let emptyChildItemArray: NavigationBarItem[] = []; + +/** + * Represents a navigation bar item and its children. + * The returned NavigationBarItem is more complicated and doesn't include 'parent', so we use these to do work before converting. + */ +interface NavigationBarNode { + node: Node; + name: DeclarationName | undefined; + additionalNodes: Node[] | undefined; + parent: NavigationBarNode | undefined; // Present for all but root node + children: NavigationBarNode[] | undefined; + indent: number; // # of parents +} - export function getNavigationTree(sourceFile: SourceFile, cancellationToken: CancellationToken): NavigationTree { - curCancellationToken = cancellationToken; - curSourceFile = sourceFile; - try { - return convertToTree(rootNavigationBarNode(sourceFile)); - } - finally { - reset(); - } +export function getNavigationBarItems(sourceFile: SourceFile, cancellationToken: CancellationToken): NavigationBarItem[] { + curCancellationToken = cancellationToken; + curSourceFile = sourceFile; + try { + return map(primaryNavBarMenuItems(rootNavigationBarNode(sourceFile)), convertToPrimaryNavBarMenuItem); } - - function reset() { - curSourceFile = undefined!; - curCancellationToken = undefined!; - parentsStack = []; - parent = undefined!; - emptyChildItemArray = []; + finally { + reset(); } +} - function nodeText(node: Node): string { - return cleanText(node.getText(curSourceFile)); +export function getNavigationTree(sourceFile: SourceFile, cancellationToken: CancellationToken): NavigationTree { + curCancellationToken = cancellationToken; + curSourceFile = sourceFile; + try { + return convertToTree(rootNavigationBarNode(sourceFile)); } - - function navigationBarNodeKind(n: NavigationBarNode): SyntaxKind { - return n.node.kind; + finally { + reset(); } +} - function pushChild(parent: NavigationBarNode, child: NavigationBarNode): void { - if (parent.children) { - parent.children.push(child); - } - else { - parent.children = [child]; - } - } +function reset() { + curSourceFile = undefined!; + curCancellationToken = undefined!; + parentsStack = []; + parent = undefined!; + emptyChildItemArray = []; +} - function rootNavigationBarNode(sourceFile: SourceFile): NavigationBarNode { - Debug.assert(!parentsStack.length); - const root: NavigationBarNode = { node: sourceFile, name: undefined, additionalNodes: undefined, parent: undefined, children: undefined, indent: 0 }; - parent = root; - for (const statement of sourceFile.statements) { - addChildrenRecursively(statement); - } - endNode(); - Debug.assert(!parent && !parentsStack.length); - return root; - } +function nodeText(node: Node): string { + return cleanText(node.getText(curSourceFile)); +} - function addLeafNode(node: Node, name?: DeclarationName): void { - pushChild(parent, emptyNavigationBarNode(node, name)); - } +function navigationBarNodeKind(n: NavigationBarNode): SyntaxKind { + return n.node.kind; +} - function emptyNavigationBarNode(node: Node, name?: DeclarationName): NavigationBarNode { - return { - node, - name: name || (isDeclaration(node) || isExpression(node) ? getNameOfDeclaration(node) : undefined), - additionalNodes: undefined, - parent, - children: undefined, - indent: parent.indent + 1 - }; +function pushChild(parent: NavigationBarNode, child: NavigationBarNode): void { + if (parent.children) { + parent.children.push(child); } + else { + parent.children = [child]; + } +} - function addTrackedEs5Class(name: string) { - if (!trackedEs5Classes) { - trackedEs5Classes = new Map(); - } - trackedEs5Classes.set(name, true); - } - function endNestedNodes(depth: number): void { - for (let i = 0; i < depth; i++) endNode(); - } - function startNestedNodes(targetNode: Node, entityName: BindableStaticNameExpression) { - const names: PropertyNameLiteral[] = []; - while (!isPropertyNameLiteral(entityName)) { - const name = getNameOrArgument(entityName); - const nameText = getElementOrPropertyAccessName(entityName); - entityName = entityName.expression; - if (nameText === "prototype" || isPrivateIdentifier(name)) continue; - names.push(name); - } - names.push(entityName); - for (let i = names.length - 1; i > 0; i--) { - const name = names[i]; - startNode(targetNode, name); - } - return [names.length - 1, names[0]] as const; +function rootNavigationBarNode(sourceFile: SourceFile): NavigationBarNode { + Debug.assert(!parentsStack.length); + const root: NavigationBarNode = { node: sourceFile, name: undefined, additionalNodes: undefined, parent: undefined, children: undefined, indent: 0 }; + parent = root; + for (const statement of sourceFile.statements) { + addChildrenRecursively(statement); } + endNode(); + Debug.assert(!parent && !parentsStack.length); + return root; +} + +function addLeafNode(node: Node, name?: DeclarationName): void { + pushChild(parent, emptyNavigationBarNode(node, name)); +} - /** - * Add a new level of NavigationBarNodes. - * This pushes to the stack, so you must call `endNode` when you are done adding to this node. - */ - function startNode(node: Node, name?: DeclarationName): void { - const navNode: NavigationBarNode = emptyNavigationBarNode(node, name); - pushChild(parent, navNode); +function emptyNavigationBarNode(node: Node, name?: DeclarationName): NavigationBarNode { + return { + node, + name: name || (isDeclaration(node) || isExpression(node) ? getNameOfDeclaration(node) : undefined), + additionalNodes: undefined, + parent, + children: undefined, + indent: parent.indent + 1 + }; +} - // Save the old parent - parentsStack.push(parent); - trackedEs5ClassesStack.push(trackedEs5Classes); - trackedEs5Classes = undefined; - parent = navNode; +function addTrackedEs5Class(name: string) { + if (!trackedEs5Classes) { + trackedEs5Classes = new Map(); } + trackedEs5Classes.set(name, true); +} +function endNestedNodes(depth: number): void { + for (let i = 0; i < depth; i++) endNode(); +} +function startNestedNodes(targetNode: Node, entityName: BindableStaticNameExpression) { + const names: PropertyNameLiteral[] = []; + while (!isPropertyNameLiteral(entityName)) { + const name = getNameOrArgument(entityName); + const nameText = getElementOrPropertyAccessName(entityName); + entityName = entityName.expression; + if (nameText === "prototype" || isPrivateIdentifier(name)) continue; + names.push(name); + } + names.push(entityName); + for (let i = names.length - 1; i > 0; i--) { + const name = names[i]; + startNode(targetNode, name); + } + return [names.length - 1, names[0]] as const; +} - /** Call after calling `startNode` and adding children to it. */ - function endNode(): void { - if (parent.children) { - mergeChildren(parent.children, parent); - sortChildren(parent.children); - } - parent = parentsStack.pop()!; - trackedEs5Classes = trackedEs5ClassesStack.pop(); +/** + * Add a new level of NavigationBarNodes. + * This pushes to the stack, so you must call `endNode` when you are done adding to this node. + */ +function startNode(node: Node, name?: DeclarationName): void { + const navNode: NavigationBarNode = emptyNavigationBarNode(node, name); + pushChild(parent, navNode); + + // Save the old parent + parentsStack.push(parent); + trackedEs5ClassesStack.push(trackedEs5Classes); + trackedEs5Classes = undefined; + parent = navNode; +} + +/** Call after calling `startNode` and adding children to it. */ +function endNode(): void { + if (parent.children) { + mergeChildren(parent.children, parent); + sortChildren(parent.children); } + parent = parentsStack.pop()!; + trackedEs5Classes = trackedEs5ClassesStack.pop(); +} - function addNodeWithRecursiveChild(node: Node, child: Node | undefined, name?: DeclarationName): void { - startNode(node, name); - addChildrenRecursively(child); +function addNodeWithRecursiveChild(node: Node, child: Node | undefined, name?: DeclarationName): void { + startNode(node, name); + addChildrenRecursively(child); + endNode(); +} + +function addNodeWithRecursiveInitializer(node: VariableDeclaration | PropertyAssignment | BindingElement | PropertyDeclaration): void { + if (node.initializer && isFunctionOrClassExpression(node.initializer)) { + startNode(node); + forEachChild(node.initializer, addChildrenRecursively); endNode(); } - - function addNodeWithRecursiveInitializer(node: VariableDeclaration | PropertyAssignment | BindingElement | PropertyDeclaration): void { - if (node.initializer && isFunctionOrClassExpression(node.initializer)) { - startNode(node); - forEachChild(node.initializer, addChildrenRecursively); - endNode(); - } - else { - addNodeWithRecursiveChild(node, node.initializer); - } + else { + addNodeWithRecursiveChild(node, node.initializer); } +} - /** - * Historically, we've elided dynamic names from the nav tree (including late bound names), - * but included certain "well known" symbol names. While we no longer distinguish those well-known - * symbols from other unique symbols, we do the below to retain those members in the nav tree. - */ - function hasNavigationBarName(node: Declaration) { - return !hasDynamicName(node) || - ( - node.kind !== SyntaxKind.BinaryExpression && - isPropertyAccessExpression(node.name.expression) && - isIdentifier(node.name.expression.expression) && - idText(node.name.expression.expression) === "Symbol" - ); - } +/** + * Historically, we've elided dynamic names from the nav tree (including late bound names), + * but included certain "well known" symbol names. While we no longer distinguish those well-known + * symbols from other unique symbols, we do the below to retain those members in the nav tree. + */ +function hasNavigationBarName(node: Declaration) { + return !hasDynamicName(node) || + ( + node.kind !== SyntaxKind.BinaryExpression && + isPropertyAccessExpression(node.name.expression) && + isIdentifier(node.name.expression.expression) && + idText(node.name.expression.expression) === "Symbol" + ); +} - /** Look for navigation bar items in node's subtree, adding them to the current `parent`. */ - function addChildrenRecursively(node: Node | undefined): void { - curCancellationToken.throwIfCancellationRequested(); +/** Look for navigation bar items in node's subtree, adding them to the current `parent`. */ +function addChildrenRecursively(node: Node | undefined): void { + curCancellationToken.throwIfCancellationRequested(); - if (!node || isToken(node)) { - return; - } + if (!node || isToken(node)) { + return; + } - switch (node.kind) { - case SyntaxKind.Constructor: - // Get parameter properties, and treat them as being on the *same* level as the constructor, not under it. - const ctr = node as ConstructorDeclaration; - addNodeWithRecursiveChild(ctr, ctr.body); + switch (node.kind) { + case SyntaxKind.Constructor: + // Get parameter properties, and treat them as being on the *same* level as the constructor, not under it. + const ctr = node as ConstructorDeclaration; + addNodeWithRecursiveChild(ctr, ctr.body); - // Parameter properties are children of the class, not the constructor. - for (const param of ctr.parameters) { - if (isParameterPropertyDeclaration(param, ctr)) { - addLeafNode(param); - } - } - break; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodSignature: - if (hasNavigationBarName(node as ClassElement | TypeElement)) { - addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body); + // Parameter properties are children of the class, not the constructor. + for (const param of ctr.parameters) { + if (isParameterPropertyDeclaration(param, ctr)) { + addLeafNode(param); } - break; + } + break; - case SyntaxKind.PropertyDeclaration: - if (hasNavigationBarName(node as ClassElement)) { - addNodeWithRecursiveInitializer(node as PropertyDeclaration); - } - break; - case SyntaxKind.PropertySignature: - if (hasNavigationBarName(node as TypeElement)) { - addLeafNode(node); - } - break; - - case SyntaxKind.ImportClause: - const importClause = node as ImportClause; - // Handle default import case e.g.: - // import d from "mod"; - if (importClause.name) { - addLeafNode(importClause.name); - } + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodSignature: + if (hasNavigationBarName(node as ClassElement | TypeElement)) { + addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body); + } + break; - // Handle named bindings in imports e.g.: - // import * as NS from "mod"; - // import {a, b as B} from "mod"; - const { namedBindings } = importClause; - if (namedBindings) { - if (namedBindings.kind === SyntaxKind.NamespaceImport) { - addLeafNode(namedBindings); - } - else { - for (const element of namedBindings.elements) { - addLeafNode(element); - } - } - } - break; - - case SyntaxKind.ShorthandPropertyAssignment: - addNodeWithRecursiveChild(node, (node as ShorthandPropertyAssignment).name); - break; - case SyntaxKind.SpreadAssignment: - const { expression } = node as SpreadAssignment; - // Use the expression as the name of the SpreadAssignment, otherwise show as . - isIdentifier(expression) ? addLeafNode(node, expression) : addLeafNode(node); - break; - case SyntaxKind.BindingElement: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.VariableDeclaration: { - const child = node as VariableDeclaration | PropertyAssignment | BindingElement; - if (isBindingPattern(child.name)) { - addChildrenRecursively(child.name); + case SyntaxKind.PropertyDeclaration: + if (hasNavigationBarName(node as ClassElement)) { + addNodeWithRecursiveInitializer(node as PropertyDeclaration); + } + break; + case SyntaxKind.PropertySignature: + if (hasNavigationBarName(node as TypeElement)) { + addLeafNode(node); + } + break; + + case SyntaxKind.ImportClause: + const importClause = node as ImportClause; + // Handle default import case e.g.: + // import d from "mod"; + if (importClause.name) { + addLeafNode(importClause.name); + } + + // Handle named bindings in imports e.g.: + // import * as NS from "mod"; + // import {a, b as B} from "mod"; + const { namedBindings } = importClause; + if (namedBindings) { + if (namedBindings.kind === SyntaxKind.NamespaceImport) { + addLeafNode(namedBindings); } else { - addNodeWithRecursiveInitializer(child); + for (const element of namedBindings.elements) { + addLeafNode(element); + } } - break; } - case SyntaxKind.FunctionDeclaration: - const nameNode = (node as FunctionLikeDeclaration).name; - // If we see a function declaration track as a possible ES5 class - if (nameNode && isIdentifier(nameNode)) { - addTrackedEs5Class(nameNode.text); + break; + + case SyntaxKind.ShorthandPropertyAssignment: + addNodeWithRecursiveChild(node, (node as ShorthandPropertyAssignment).name); + break; + case SyntaxKind.SpreadAssignment: + const { expression } = node as SpreadAssignment; + // Use the expression as the name of the SpreadAssignment, otherwise show as . + isIdentifier(expression) ? addLeafNode(node, expression) : addLeafNode(node); + break; + case SyntaxKind.BindingElement: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.VariableDeclaration: { + const child = node as VariableDeclaration | PropertyAssignment | BindingElement; + if (isBindingPattern(child.name)) { + addChildrenRecursively(child.name); + } + else { + addNodeWithRecursiveInitializer(child); + } + break; + } + case SyntaxKind.FunctionDeclaration: + const nameNode = (node as FunctionLikeDeclaration).name; + // If we see a function declaration track as a possible ES5 class + if (nameNode && isIdentifier(nameNode)) { + addTrackedEs5Class(nameNode.text); + } + addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body); + break; + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body); + break; + + case SyntaxKind.EnumDeclaration: + startNode(node); + for (const member of (node as EnumDeclaration).members) { + if (!isComputedProperty(member)) { + addLeafNode(member); } - addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body); - break; - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionExpression: - addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body); - break; + } + endNode(); + break; - case SyntaxKind.EnumDeclaration: - startNode(node); - for (const member of (node as EnumDeclaration).members) { - if (!isComputedProperty(member)) { - addLeafNode(member); - } - } - endNode(); - break; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + startNode(node); + for (const member of (node as InterfaceDeclaration).members) { + addChildrenRecursively(member); + } + endNode(); + break; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ModuleDeclaration: + addNodeWithRecursiveChild(node, getInteriorModule(node as ModuleDeclaration).body); + break; + + case SyntaxKind.ExportAssignment: { + const expression = (node as ExportAssignment).expression; + const child = isObjectLiteralExpression(expression) || isCallExpression(expression) ? expression : + isArrowFunction(expression) || isFunctionExpression(expression) ? expression.body : undefined; + if (child) { startNode(node); - for (const member of (node as InterfaceDeclaration).members) { - addChildrenRecursively(member); - } + addChildrenRecursively(child); endNode(); - break; - - case SyntaxKind.ModuleDeclaration: - addNodeWithRecursiveChild(node, getInteriorModule(node as ModuleDeclaration).body); - break; - - case SyntaxKind.ExportAssignment: { - const expression = (node as ExportAssignment).expression; - const child = isObjectLiteralExpression(expression) || isCallExpression(expression) ? expression : - isArrowFunction(expression) || isFunctionExpression(expression) ? expression.body : undefined; - if (child) { - startNode(node); - addChildrenRecursively(child); - endNode(); - } - else { - addLeafNode(node); - } - break; } - case SyntaxKind.ExportSpecifier: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.IndexSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.TypeAliasDeclaration: + else { addLeafNode(node); - break; - - case SyntaxKind.CallExpression: - case SyntaxKind.BinaryExpression: { - const special = getAssignmentDeclarationKind(node as BinaryExpression); - switch (special) { - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.ModuleExports: - addNodeWithRecursiveChild(node, (node as BinaryExpression).right); - return; - case AssignmentDeclarationKind.Prototype: - case AssignmentDeclarationKind.PrototypeProperty: { - const binaryExpression = (node as BinaryExpression); - const assignmentTarget = binaryExpression.left as PropertyAccessExpression; - - const prototypeAccess = special === AssignmentDeclarationKind.PrototypeProperty ? - assignmentTarget.expression as PropertyAccessExpression : - assignmentTarget; - - let depth = 0; - let className: PropertyNameLiteral; - // If we see a prototype assignment, start tracking the target as a class - // This is only done for simple classes not nested assignments. - if (isIdentifier(prototypeAccess.expression)) { - addTrackedEs5Class(prototypeAccess.expression.text); - className = prototypeAccess.expression; - } - else { - [depth, className] = startNestedNodes(binaryExpression, prototypeAccess.expression as EntityNameExpression); - } - if (special === AssignmentDeclarationKind.Prototype) { - if (isObjectLiteralExpression(binaryExpression.right)) { - if (binaryExpression.right.properties.length > 0) { - startNode(binaryExpression, className); - forEachChild(binaryExpression.right, addChildrenRecursively); - endNode(); - } + } + break; + } + case SyntaxKind.ExportSpecifier: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.IndexSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.TypeAliasDeclaration: + addLeafNode(node); + break; + + case SyntaxKind.CallExpression: + case SyntaxKind.BinaryExpression: { + const special = getAssignmentDeclarationKind(node as BinaryExpression); + switch (special) { + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.ModuleExports: + addNodeWithRecursiveChild(node, (node as BinaryExpression).right); + return; + case AssignmentDeclarationKind.Prototype: + case AssignmentDeclarationKind.PrototypeProperty: { + const binaryExpression = (node as BinaryExpression); + const assignmentTarget = binaryExpression.left as PropertyAccessExpression; + + const prototypeAccess = special === AssignmentDeclarationKind.PrototypeProperty ? + assignmentTarget.expression as PropertyAccessExpression : + assignmentTarget; + + let depth = 0; + let className: PropertyNameLiteral; + // If we see a prototype assignment, start tracking the target as a class + // This is only done for simple classes not nested assignments. + if (isIdentifier(prototypeAccess.expression)) { + addTrackedEs5Class(prototypeAccess.expression.text); + className = prototypeAccess.expression; + } + else { + [depth, className] = startNestedNodes(binaryExpression, prototypeAccess.expression as EntityNameExpression); + } + if (special === AssignmentDeclarationKind.Prototype) { + if (isObjectLiteralExpression(binaryExpression.right)) { + if (binaryExpression.right.properties.length > 0) { + startNode(binaryExpression, className); + forEachChild(binaryExpression.right, addChildrenRecursively); + endNode(); } } - else if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) { - addNodeWithRecursiveChild(node, - binaryExpression.right, - className); - } - else { - startNode(binaryExpression, className); - addNodeWithRecursiveChild(node, binaryExpression.right, assignmentTarget.name); + } + else if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) { + addNodeWithRecursiveChild(node, + binaryExpression.right, + className); + } + else { + startNode(binaryExpression, className); + addNodeWithRecursiveChild(node, binaryExpression.right, assignmentTarget.name); + endNode(); + } + endNestedNodes(depth); + return; + } + case AssignmentDeclarationKind.ObjectDefinePropertyValue: + case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: { + const defineCall = node as BindableObjectDefinePropertyCall; + const className = special === AssignmentDeclarationKind.ObjectDefinePropertyValue ? + defineCall.arguments[0] : + (defineCall.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression; + + const memberName = defineCall.arguments[1]; + const [depth, classNameIdentifier] = startNestedNodes(node, className); + startNode(node, classNameIdentifier); + startNode(node, setTextRange(factory.createIdentifier(memberName.text), memberName)); + addChildrenRecursively((node as CallExpression).arguments[2]); endNode(); + endNode(); + endNestedNodes(depth); + return; + } + case AssignmentDeclarationKind.Property: { + const binaryExpression = (node as BinaryExpression); + const assignmentTarget = binaryExpression.left as PropertyAccessExpression | BindableElementAccessExpression; + const targetFunction = assignmentTarget.expression; + if (isIdentifier(targetFunction) && getElementOrPropertyAccessName(assignmentTarget) !== "prototype" && + trackedEs5Classes && trackedEs5Classes.has(targetFunction.text)) { + if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) { + addNodeWithRecursiveChild(node, binaryExpression.right, targetFunction); } - endNestedNodes(depth); - return; - } - case AssignmentDeclarationKind.ObjectDefinePropertyValue: - case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: { - const defineCall = node as BindableObjectDefinePropertyCall; - const className = special === AssignmentDeclarationKind.ObjectDefinePropertyValue ? - defineCall.arguments[0] : - (defineCall.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression; - - const memberName = defineCall.arguments[1]; - const [depth, classNameIdentifier] = startNestedNodes(node, className); - startNode(node, classNameIdentifier); - startNode(node, setTextRange(factory.createIdentifier(memberName.text), memberName)); - addChildrenRecursively((node as CallExpression).arguments[2]); - endNode(); + else if (isBindableStaticAccessExpression(assignmentTarget)) { + startNode(binaryExpression, targetFunction); + addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, getNameOrArgument(assignmentTarget)); endNode(); - endNestedNodes(depth); - return; - } - case AssignmentDeclarationKind.Property: { - const binaryExpression = (node as BinaryExpression); - const assignmentTarget = binaryExpression.left as PropertyAccessExpression | BindableElementAccessExpression; - const targetFunction = assignmentTarget.expression; - if (isIdentifier(targetFunction) && getElementOrPropertyAccessName(assignmentTarget) !== "prototype" && - trackedEs5Classes && trackedEs5Classes.has(targetFunction.text)) { - if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) { - addNodeWithRecursiveChild(node, binaryExpression.right, targetFunction); - } - else if (isBindableStaticAccessExpression(assignmentTarget)) { - startNode(binaryExpression, targetFunction); - addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, getNameOrArgument(assignmentTarget)); - endNode(); - } - return; } - break; + return; } - case AssignmentDeclarationKind.ThisProperty: - case AssignmentDeclarationKind.None: - case AssignmentDeclarationKind.ObjectDefinePropertyExports: - break; - default: - Debug.assertNever(special); + break; } + case AssignmentDeclarationKind.ThisProperty: + case AssignmentDeclarationKind.None: + case AssignmentDeclarationKind.ObjectDefinePropertyExports: + break; + default: + Debug.assertNever(special); } - // falls through + } + // falls through - default: - if (hasJSDocNodes(node)) { - forEach(node.jsDoc, jsDoc => { - forEach(jsDoc.tags, tag => { - if (isJSDocTypeAlias(tag)) { - addLeafNode(tag); - } - }); + default: + if (hasJSDocNodes(node)) { + forEach(node.jsDoc, jsDoc => { + forEach(jsDoc.tags, tag => { + if (isJSDocTypeAlias(tag)) { + addLeafNode(tag); + } }); - } + }); + } - forEachChild(node, addChildrenRecursively); - } + forEachChild(node, addChildrenRecursively); } +} - /** Merge declarations of the same kind. */ - function mergeChildren(children: NavigationBarNode[], node: NavigationBarNode): void { - const nameToItems = new Map(); - filterMutate(children, (child, index) => { - const declName = child.name || getNameOfDeclaration(child.node as Declaration); - const name = declName && nodeText(declName); - if (!name) { - // Anonymous items are never merged. - return true; - } +/** Merge declarations of the same kind. */ +function mergeChildren(children: NavigationBarNode[], node: NavigationBarNode): void { + const nameToItems = new Map(); + filterMutate(children, (child, index) => { + const declName = child.name || getNameOfDeclaration(child.node as Declaration); + const name = declName && nodeText(declName); + if (!name) { + // Anonymous items are never merged. + return true; + } - const itemsWithSameName = nameToItems.get(name); - if (!itemsWithSameName) { - nameToItems.set(name, child); - return true; - } + const itemsWithSameName = nameToItems.get(name); + if (!itemsWithSameName) { + nameToItems.set(name, child); + return true; + } - if (itemsWithSameName instanceof Array) { - for (const itemWithSameName of itemsWithSameName) { - if (tryMerge(itemWithSameName, child, index, node)) { - return false; - } - } - itemsWithSameName.push(child); - return true; - } - else { - const itemWithSameName = itemsWithSameName; + if (itemsWithSameName instanceof Array) { + for (const itemWithSameName of itemsWithSameName) { if (tryMerge(itemWithSameName, child, index, node)) { return false; } - nameToItems.set(name, [itemWithSameName, child]); - return true; } - }); - } - const isEs5ClassMember: Record = { - [AssignmentDeclarationKind.Property]: true, - [AssignmentDeclarationKind.PrototypeProperty]: true, - [AssignmentDeclarationKind.ObjectDefinePropertyValue]: true, - [AssignmentDeclarationKind.ObjectDefinePrototypeProperty]: true, - [AssignmentDeclarationKind.None]: false, - [AssignmentDeclarationKind.ExportsProperty]: false, - [AssignmentDeclarationKind.ModuleExports]: false, - [AssignmentDeclarationKind.ObjectDefinePropertyExports]: false, - [AssignmentDeclarationKind.Prototype]: true, - [AssignmentDeclarationKind.ThisProperty]: false, - }; - function tryMergeEs5Class(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean | undefined { - function isPossibleConstructor(node: Node) { - return isFunctionExpression(node) || isFunctionDeclaration(node) || isVariableDeclaration(node); + itemsWithSameName.push(child); + return true; + } + else { + const itemWithSameName = itemsWithSameName; + if (tryMerge(itemWithSameName, child, index, node)) { + return false; + } + nameToItems.set(name, [itemWithSameName, child]); + return true; } - const bAssignmentDeclarationKind = isBinaryExpression(b.node) || isCallExpression(b.node) ? - getAssignmentDeclarationKind(b.node) : - AssignmentDeclarationKind.None; - - const aAssignmentDeclarationKind = isBinaryExpression(a.node) || isCallExpression(a.node) ? - getAssignmentDeclarationKind(a.node) : - AssignmentDeclarationKind.None; - - // We treat this as an es5 class and merge the nodes in in one of several cases - if ((isEs5ClassMember[bAssignmentDeclarationKind] && isEs5ClassMember[aAssignmentDeclarationKind]) // merge two class elements - || (isPossibleConstructor(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // ctor function & member - || (isPossibleConstructor(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & ctor function - || (isClassDeclaration(a.node) && isSynthesized(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // class (generated) & member - || (isClassDeclaration(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & class (generated) - || (isClassDeclaration(a.node) && isSynthesized(a.node) && isPossibleConstructor(b.node)) // class (generated) & ctor - || (isClassDeclaration(b.node) && isPossibleConstructor(a.node) && isSynthesized(a.node)) // ctor & class (generated) + }); +} +const isEs5ClassMember: Record = { + [AssignmentDeclarationKind.Property]: true, + [AssignmentDeclarationKind.PrototypeProperty]: true, + [AssignmentDeclarationKind.ObjectDefinePropertyValue]: true, + [AssignmentDeclarationKind.ObjectDefinePrototypeProperty]: true, + [AssignmentDeclarationKind.None]: false, + [AssignmentDeclarationKind.ExportsProperty]: false, + [AssignmentDeclarationKind.ModuleExports]: false, + [AssignmentDeclarationKind.ObjectDefinePropertyExports]: false, + [AssignmentDeclarationKind.Prototype]: true, + [AssignmentDeclarationKind.ThisProperty]: false, +}; +function tryMergeEs5Class(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean | undefined { + function isPossibleConstructor(node: Node) { + return isFunctionExpression(node) || isFunctionDeclaration(node) || isVariableDeclaration(node); + } + const bAssignmentDeclarationKind = isBinaryExpression(b.node) || isCallExpression(b.node) ? + getAssignmentDeclarationKind(b.node) : + AssignmentDeclarationKind.None; + + const aAssignmentDeclarationKind = isBinaryExpression(a.node) || isCallExpression(a.node) ? + getAssignmentDeclarationKind(a.node) : + AssignmentDeclarationKind.None; + + // We treat this as an es5 class and merge the nodes in in one of several cases + if ((isEs5ClassMember[bAssignmentDeclarationKind] && isEs5ClassMember[aAssignmentDeclarationKind]) // merge two class elements + || (isPossibleConstructor(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // ctor function & member + || (isPossibleConstructor(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & ctor function + || (isClassDeclaration(a.node) && isSynthesized(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // class (generated) & member + || (isClassDeclaration(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & class (generated) + || (isClassDeclaration(a.node) && isSynthesized(a.node) && isPossibleConstructor(b.node)) // class (generated) & ctor + || (isClassDeclaration(b.node) && isPossibleConstructor(a.node) && isSynthesized(a.node)) // ctor & class (generated) + ) { + + let lastANode = a.additionalNodes && lastOrUndefined(a.additionalNodes) || a.node; + + if ((!isClassDeclaration(a.node) && !isClassDeclaration(b.node)) // If neither outline node is a class + || isPossibleConstructor(a.node) || isPossibleConstructor(b.node) // If either function is a constructor function ) { - - let lastANode = a.additionalNodes && lastOrUndefined(a.additionalNodes) || a.node; - - if ((!isClassDeclaration(a.node) && !isClassDeclaration(b.node)) // If neither outline node is a class - || isPossibleConstructor(a.node) || isPossibleConstructor(b.node) // If either function is a constructor function - ) { - const ctorFunction = isPossibleConstructor(a.node) ? a.node : - isPossibleConstructor(b.node) ? b.node : - undefined; - - if (ctorFunction !== undefined) { - const ctorNode = setTextRange( - factory.createConstructorDeclaration(/* decorators */ undefined, /* modifiers */ undefined, [], /* body */ undefined), - ctorFunction); - const ctor = emptyNavigationBarNode(ctorNode); - ctor.indent = a.indent + 1; - ctor.children = a.node === ctorFunction ? a.children : b.children; - a.children = a.node === ctorFunction ? concatenate([ctor], b.children || [b]) : concatenate(a.children || [{ ...a }], [ctor]); - } - else { - if (a.children || b.children) { - a.children = concatenate(a.children || [{ ...a }], b.children || [b]); - if (a.children) { - mergeChildren(a.children, a); - sortChildren(a.children); - } - } - } - - lastANode = a.node = setTextRange(factory.createClassDeclaration( - /* decorators */ undefined, - /* modifiers */ undefined, - a.name as Identifier || factory.createIdentifier("__class__"), - /* typeParameters */ undefined, - /* heritageClauses */ undefined, - [] - ), a.node); + const ctorFunction = isPossibleConstructor(a.node) ? a.node : + isPossibleConstructor(b.node) ? b.node : + undefined; + + if (ctorFunction !== undefined) { + const ctorNode = setTextRange( + factory.createConstructorDeclaration(/* decorators */ undefined, /* modifiers */ undefined, [], /* body */ undefined), + ctorFunction); + const ctor = emptyNavigationBarNode(ctorNode); + ctor.indent = a.indent + 1; + ctor.children = a.node === ctorFunction ? a.children : b.children; + a.children = a.node === ctorFunction ? concatenate([ctor], b.children || [b]) : concatenate(a.children || [{ ...a }], [ctor]); } else { - a.children = concatenate(a.children, b.children); - if (a.children) { - mergeChildren(a.children, a); + if (a.children || b.children) { + a.children = concatenate(a.children || [{ ...a }], b.children || [b]); + if (a.children) { + mergeChildren(a.children, a); + sortChildren(a.children); + } } } - const bNode = b.node; - // We merge if the outline node previous to b (bIndex - 1) is already part of the current class - // We do this so that statements between class members that do not generate outline nodes do not split up the class outline: - // Ex This should produce one outline node C: - // function C() {}; a = 1; C.prototype.m = function () {} - // Ex This will produce 3 outline nodes: C, a, C - // function C() {}; let a = 1; C.prototype.m = function () {} - if (parent.children![bIndex - 1].node.end === lastANode.end) { - setTextRange(lastANode, { pos: lastANode.pos, end: bNode.end }); - } - else { - if (!a.additionalNodes) a.additionalNodes = []; - a.additionalNodes.push(setTextRange(factory.createClassDeclaration( - /* decorators */ undefined, - /* modifiers */ undefined, - a.name as Identifier || factory.createIdentifier("__class__"), - /* typeParameters */ undefined, - /* heritageClauses */ undefined, - [] - ), b.node)); + lastANode = a.node = setTextRange(factory.createClassDeclaration( + /* decorators */ undefined, + /* modifiers */ undefined, + a.name as Identifier || factory.createIdentifier("__class__"), + /* typeParameters */ undefined, + /* heritageClauses */ undefined, + [] + ), a.node); + } + else { + a.children = concatenate(a.children, b.children); + if (a.children) { + mergeChildren(a.children, a); } - return true; } - return bAssignmentDeclarationKind === AssignmentDeclarationKind.None ? false : true; - } - function tryMerge(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean { - // const v = false as boolean; - if (tryMergeEs5Class(a, b, bIndex, parent)) { - return true; + const bNode = b.node; + // We merge if the outline node previous to b (bIndex - 1) is already part of the current class + // We do this so that statements between class members that do not generate outline nodes do not split up the class outline: + // Ex This should produce one outline node C: + // function C() {}; a = 1; C.prototype.m = function () {} + // Ex This will produce 3 outline nodes: C, a, C + // function C() {}; let a = 1; C.prototype.m = function () {} + if (parent.children![bIndex - 1].node.end === lastANode.end) { + setTextRange(lastANode, { pos: lastANode.pos, end: bNode.end }); } - if (shouldReallyMerge(a.node, b.node, parent)) { - merge(a, b); - return true; - } - return false; - } + else { + if (!a.additionalNodes) a.additionalNodes = []; + a.additionalNodes.push(setTextRange(factory.createClassDeclaration( + /* decorators */ undefined, + /* modifiers */ undefined, + a.name as Identifier || factory.createIdentifier("__class__"), + /* typeParameters */ undefined, + /* heritageClauses */ undefined, + [] + ), b.node)); + } + return true; + } + return bAssignmentDeclarationKind === AssignmentDeclarationKind.None ? false : true; +} - /** a and b have the same name, but they may not be mergeable. */ - function shouldReallyMerge(a: Node, b: Node, parent: NavigationBarNode): boolean { - if (a.kind !== b.kind || a.parent !== b.parent && !(isOwnChild(a, parent) && isOwnChild(b, parent))) { - return false; - } - switch (a.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return isStatic(a) === isStatic(b); - case SyntaxKind.ModuleDeclaration: - return areSameModule(a as ModuleDeclaration, b as ModuleDeclaration) - && getFullyQualifiedModuleName(a as ModuleDeclaration) === getFullyQualifiedModuleName(b as ModuleDeclaration); - default: - return true; - } +function tryMerge(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean { + // const v = false as boolean; + if (tryMergeEs5Class(a, b, bIndex, parent)) { + return true; + } + if (shouldReallyMerge(a.node, b.node, parent)) { + merge(a, b); + return true; } + return false; +} - function isSynthesized(node: Node) { - return !!(node.flags & NodeFlags.Synthesized); +/** a and b have the same name, but they may not be mergeable. */ +function shouldReallyMerge(a: Node, b: Node, parent: NavigationBarNode): boolean { + if (a.kind !== b.kind || a.parent !== b.parent && !(isOwnChild(a, parent) && isOwnChild(b, parent))) { + return false; } + switch (a.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return isStatic(a) === isStatic(b); + case SyntaxKind.ModuleDeclaration: + return areSameModule(a as ModuleDeclaration, b as ModuleDeclaration) + && getFullyQualifiedModuleName(a as ModuleDeclaration) === getFullyQualifiedModuleName(b as ModuleDeclaration); + default: + return true; + } +} - // We want to merge own children like `I` in in `module A { interface I {} } module A { interface I {} }` - // We don't want to merge unrelated children like `m` in `const o = { a: { m() {} }, b: { m() {} } };` - function isOwnChild(n: Node, parent: NavigationBarNode): boolean { - const par = isModuleBlock(n.parent) ? n.parent.parent : n.parent; - return par === parent.node || contains(parent.additionalNodes, par); +function isSynthesized(node: Node) { + return !!(node.flags & NodeFlags.Synthesized); +} + +// We want to merge own children like `I` in in `module A { interface I {} } module A { interface I {} }` +// We don't want to merge unrelated children like `m` in `const o = { a: { m() {} }, b: { m() {} } };` +function isOwnChild(n: Node, parent: NavigationBarNode): boolean { + const par = isModuleBlock(n.parent) ? n.parent.parent : n.parent; + return par === parent.node || contains(parent.additionalNodes, par); +} + +// We use 1 NavNode to represent 'A.B.C', but there are multiple source nodes. +// Only merge module nodes that have the same chain. Don't merge 'A.B.C' with 'A'! +function areSameModule(a: ModuleDeclaration, b: ModuleDeclaration): boolean { + return a.body!.kind === b.body!.kind && (a.body!.kind !== SyntaxKind.ModuleDeclaration || areSameModule(a.body as ModuleDeclaration, b.body as ModuleDeclaration)); +} + +/** Merge source into target. Source should be thrown away after this is called. */ +function merge(target: NavigationBarNode, source: NavigationBarNode): void { + target.additionalNodes = target.additionalNodes || []; + target.additionalNodes.push(source.node); + if (source.additionalNodes) { + target.additionalNodes.push(...source.additionalNodes); } - // We use 1 NavNode to represent 'A.B.C', but there are multiple source nodes. - // Only merge module nodes that have the same chain. Don't merge 'A.B.C' with 'A'! - function areSameModule(a: ModuleDeclaration, b: ModuleDeclaration): boolean { - return a.body!.kind === b.body!.kind && (a.body!.kind !== SyntaxKind.ModuleDeclaration || areSameModule(a.body as ModuleDeclaration, b.body as ModuleDeclaration)); + target.children = concatenate(target.children, source.children); + if (target.children) { + mergeChildren(target.children, target); + sortChildren(target.children); } +} - /** Merge source into target. Source should be thrown away after this is called. */ - function merge(target: NavigationBarNode, source: NavigationBarNode): void { - target.additionalNodes = target.additionalNodes || []; - target.additionalNodes.push(source.node); - if (source.additionalNodes) { - target.additionalNodes.push(...source.additionalNodes); - } +/** Recursively ensure that each NavNode's children are in sorted order. */ +function sortChildren(children: NavigationBarNode[]): void { + children.sort(compareChildren); +} - target.children = concatenate(target.children, source.children); - if (target.children) { - mergeChildren(target.children, target); - sortChildren(target.children); - } - } +function compareChildren(child1: NavigationBarNode, child2: NavigationBarNode) { + return compareStringsCaseSensitiveUI(tryGetName(child1.node)!, tryGetName(child2.node)!) // TODO: GH#18217 + || compareValues(navigationBarNodeKind(child1), navigationBarNodeKind(child2)); +} - /** Recursively ensure that each NavNode's children are in sorted order. */ - function sortChildren(children: NavigationBarNode[]): void { - children.sort(compareChildren); +/** + * This differs from getItemName because this is just used for sorting. + * We only sort nodes by name that have a more-or-less "direct" name, as opposed to `new()` and the like. + * So `new()` can still come before an `aardvark` method. + */ +function tryGetName(node: Node): string | undefined { + if (node.kind === SyntaxKind.ModuleDeclaration) { + return getModuleName(node as ModuleDeclaration); + } + + const declName = getNameOfDeclaration(node as Declaration); + if (declName && isPropertyName(declName)) { + const propertyName = getPropertyNameForPropertyNameNode(declName); + return propertyName && unescapeLeadingUnderscores(propertyName); + } + switch (node.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.ClassExpression: + return getFunctionOrClassName(node as FunctionExpression | ArrowFunction | ClassExpression); + default: + return undefined; } +} - function compareChildren(child1: NavigationBarNode, child2: NavigationBarNode) { - return compareStringsCaseSensitiveUI(tryGetName(child1.node)!, tryGetName(child2.node)!) // TODO: GH#18217 - || compareValues(navigationBarNodeKind(child1), navigationBarNodeKind(child2)); +function getItemName(node: Node, name: Node | undefined): string { + if (node.kind === SyntaxKind.ModuleDeclaration) { + return cleanText(getModuleName(node as ModuleDeclaration)); } - /** - * This differs from getItemName because this is just used for sorting. - * We only sort nodes by name that have a more-or-less "direct" name, as opposed to `new()` and the like. - * So `new()` can still come before an `aardvark` method. - */ - function tryGetName(node: Node): string | undefined { - if (node.kind === SyntaxKind.ModuleDeclaration) { - return getModuleName(node as ModuleDeclaration); + if (name) { + const text = isIdentifier(name) ? name.text + : isElementAccessExpression(name) ? `[${nodeText(name.argumentExpression)}]` + : nodeText(name); + if (text.length > 0) { + return cleanText(text); } + } - const declName = getNameOfDeclaration(node as Declaration); - if (declName && isPropertyName(declName)) { - const propertyName = getPropertyNameForPropertyNameNode(declName); - return propertyName && unescapeLeadingUnderscores(propertyName); - } - switch (node.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.ClassExpression: - return getFunctionOrClassName(node as FunctionExpression | ArrowFunction | ClassExpression); - default: - return undefined; - } + switch (node.kind) { + case SyntaxKind.SourceFile: + const sourceFile = node as SourceFile; + return isExternalModule(sourceFile) + ? `"${escapeString(getBaseFileName(removeFileExtension(normalizePath(sourceFile.fileName))))}"` + : ""; + case SyntaxKind.ExportAssignment: + return isExportAssignment(node) && node.isExportEquals ? InternalSymbolName.ExportEquals : InternalSymbolName.Default; + + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + if (getSyntacticModifierFlags(node) & ModifierFlags.Default) { + return "default"; + } + // We may get a string with newlines or other whitespace in the case of an object dereference + // (eg: "app\n.onactivated"), so we should remove the whitespace for readability in the + // navigation bar. + return getFunctionOrClassName(node as ArrowFunction | FunctionExpression | ClassExpression); + case SyntaxKind.Constructor: + return "constructor"; + case SyntaxKind.ConstructSignature: + return "new()"; + case SyntaxKind.CallSignature: + return "()"; + case SyntaxKind.IndexSignature: + return "[]"; + default: + return ""; } +} - function getItemName(node: Node, name: Node | undefined): string { - if (node.kind === SyntaxKind.ModuleDeclaration) { - return cleanText(getModuleName(node as ModuleDeclaration)); +/** Flattens the NavNode tree to a list of items to appear in the primary navbar menu. */ +function primaryNavBarMenuItems(root: NavigationBarNode): NavigationBarNode[] { + // The primary (middle) navbar menu displays the general code navigation hierarchy, similar to the navtree. + // The secondary (right) navbar menu displays the child items of whichever primary item is selected. + // Some less interesting items without their own child navigation items (e.g. a local variable declaration) only show up in the secondary menu. + const primaryNavBarMenuItems: NavigationBarNode[] = []; + function recur(item: NavigationBarNode) { + if (shouldAppearInPrimaryNavBarMenu(item)) { + primaryNavBarMenuItems.push(item); + if (item.children) { + for (const child of item.children) { + recur(child); + } + } } + } + recur(root); + return primaryNavBarMenuItems; - if (name) { - const text = isIdentifier(name) ? name.text - : isElementAccessExpression(name) ? `[${nodeText(name.argumentExpression)}]` - : nodeText(name); - if (text.length > 0) { - return cleanText(text); - } + /** Determines if a node should appear in the primary navbar menu. */ + function shouldAppearInPrimaryNavBarMenu(item: NavigationBarNode): boolean { + // Items with children should always appear in the primary navbar menu. + if (item.children) { + return true; } - switch (node.kind) { + // Some nodes are otherwise important enough to always include in the primary navigation menu. + switch (navigationBarNodeKind(item)) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ModuleDeclaration: case SyntaxKind.SourceFile: - const sourceFile = node as SourceFile; - return isExternalModule(sourceFile) - ? `"${escapeString(getBaseFileName(removeFileExtension(normalizePath(sourceFile.fileName))))}"` - : ""; - case SyntaxKind.ExportAssignment: - return isExportAssignment(node) && node.isExportEquals ? InternalSymbolName.ExportEquals : InternalSymbolName.Default; + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + return true; case SyntaxKind.ArrowFunction: case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - if (getSyntacticModifierFlags(node) & ModifierFlags.Default) { - return "default"; - } - // We may get a string with newlines or other whitespace in the case of an object dereference - // (eg: "app\n.onactivated"), so we should remove the whitespace for readability in the - // navigation bar. - return getFunctionOrClassName(node as ArrowFunction | FunctionExpression | ClassExpression); - case SyntaxKind.Constructor: - return "constructor"; - case SyntaxKind.ConstructSignature: - return "new()"; - case SyntaxKind.CallSignature: - return "()"; - case SyntaxKind.IndexSignature: - return "[]"; - default: - return ""; - } - } + return isTopLevelFunctionDeclaration(item); - /** Flattens the NavNode tree to a list of items to appear in the primary navbar menu. */ - function primaryNavBarMenuItems(root: NavigationBarNode): NavigationBarNode[] { - // The primary (middle) navbar menu displays the general code navigation hierarchy, similar to the navtree. - // The secondary (right) navbar menu displays the child items of whichever primary item is selected. - // Some less interesting items without their own child navigation items (e.g. a local variable declaration) only show up in the secondary menu. - const primaryNavBarMenuItems: NavigationBarNode[] = []; - function recur(item: NavigationBarNode) { - if (shouldAppearInPrimaryNavBarMenu(item)) { - primaryNavBarMenuItems.push(item); - if (item.children) { - for (const child of item.children) { - recur(child); - } - } - } + default: + return false; } - recur(root); - return primaryNavBarMenuItems; - - /** Determines if a node should appear in the primary navbar menu. */ - function shouldAppearInPrimaryNavBarMenu(item: NavigationBarNode): boolean { - // Items with children should always appear in the primary navbar menu. - if (item.children) { - return true; + function isTopLevelFunctionDeclaration(item: NavigationBarNode): boolean { + if (!(item.node as FunctionDeclaration).body) { + return false; } - // Some nodes are otherwise important enough to always include in the primary navigation menu. - switch (navigationBarNodeKind(item)) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ModuleDeclaration: + switch (navigationBarNodeKind(item.parent!)) { + case SyntaxKind.ModuleBlock: case SyntaxKind.SourceFile: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: return true; - - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - return isTopLevelFunctionDeclaration(item); - default: return false; } - function isTopLevelFunctionDeclaration(item: NavigationBarNode): boolean { - if (!(item.node as FunctionDeclaration).body) { - return false; - } - - switch (navigationBarNodeKind(item.parent!)) { - case SyntaxKind.ModuleBlock: - case SyntaxKind.SourceFile: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - return true; - default: - return false; - } - } } } +} - function convertToTree(n: NavigationBarNode): NavigationTree { - return { - text: getItemName(n.node, n.name), - kind: getNodeKind(n.node), - kindModifiers: getModifiers(n.node), - spans: getSpans(n), - nameSpan: n.name && getNodeSpan(n.name), - childItems: map(n.children, convertToTree) - }; - } +function convertToTree(n: NavigationBarNode): NavigationTree { + return { + text: getItemName(n.node, n.name), + kind: getNodeKind(n.node), + kindModifiers: getModifiers(n.node), + spans: getSpans(n), + nameSpan: n.name && getNodeSpan(n.name), + childItems: map(n.children, convertToTree) + }; +} + +function convertToPrimaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem { + return { + text: getItemName(n.node, n.name), + kind: getNodeKind(n.node), + kindModifiers: getModifiers(n.node), + spans: getSpans(n), + childItems: map(n.children, convertToSecondaryNavBarMenuItem) || emptyChildItemArray, + indent: n.indent, + bolded: false, + grayed: false + }; - function convertToPrimaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem { + function convertToSecondaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem { return { text: getItemName(n.node, n.name), kind: getNodeKind(n.node), - kindModifiers: getModifiers(n.node), + kindModifiers: getNodeModifiers(n.node), spans: getSpans(n), - childItems: map(n.children, convertToSecondaryNavBarMenuItem) || emptyChildItemArray, - indent: n.indent, + childItems: emptyChildItemArray, + indent: 0, bolded: false, grayed: false }; + } +} - function convertToSecondaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem { - return { - text: getItemName(n.node, n.name), - kind: getNodeKind(n.node), - kindModifiers: getNodeModifiers(n.node), - spans: getSpans(n), - childItems: emptyChildItemArray, - indent: 0, - bolded: false, - grayed: false - }; +function getSpans(n: NavigationBarNode): TextSpan[] { + const spans = [getNodeSpan(n.node)]; + if (n.additionalNodes) { + for (const node of n.additionalNodes) { + spans.push(getNodeSpan(node)); } } + return spans; +} - function getSpans(n: NavigationBarNode): TextSpan[] { - const spans = [getNodeSpan(n.node)]; - if (n.additionalNodes) { - for (const node of n.additionalNodes) { - spans.push(getNodeSpan(node)); - } - } - return spans; +function getModuleName(moduleDeclaration: ModuleDeclaration): string { + // We want to maintain quotation marks. + if (isAmbientModule(moduleDeclaration)) { + return getTextOfNode(moduleDeclaration.name); } - function getModuleName(moduleDeclaration: ModuleDeclaration): string { - // We want to maintain quotation marks. - if (isAmbientModule(moduleDeclaration)) { - return getTextOfNode(moduleDeclaration.name); - } + return getFullyQualifiedModuleName(moduleDeclaration); +} - return getFullyQualifiedModuleName(moduleDeclaration); +function getFullyQualifiedModuleName(moduleDeclaration: ModuleDeclaration): string { + // Otherwise, we need to aggregate each identifier to build up the qualified name. + const result = [getTextOfIdentifierOrLiteral(moduleDeclaration.name)]; + while (moduleDeclaration.body && moduleDeclaration.body.kind === SyntaxKind.ModuleDeclaration) { + moduleDeclaration = moduleDeclaration.body; + result.push(getTextOfIdentifierOrLiteral(moduleDeclaration.name)); } + return result.join("."); +} - function getFullyQualifiedModuleName(moduleDeclaration: ModuleDeclaration): string { - // Otherwise, we need to aggregate each identifier to build up the qualified name. - const result = [getTextOfIdentifierOrLiteral(moduleDeclaration.name)]; - while (moduleDeclaration.body && moduleDeclaration.body.kind === SyntaxKind.ModuleDeclaration) { - moduleDeclaration = moduleDeclaration.body; - result.push(getTextOfIdentifierOrLiteral(moduleDeclaration.name)); - } - return result.join("."); - } +/** + * For 'module A.B.C', we want to get the node for 'C'. + * We store 'A' as associated with a NavNode, and use getModuleName to traverse down again. + */ +function getInteriorModule(decl: ModuleDeclaration): ModuleDeclaration { + return decl.body && isModuleDeclaration(decl.body) ? getInteriorModule(decl.body) : decl; +} - /** - * For 'module A.B.C', we want to get the node for 'C'. - * We store 'A' as associated with a NavNode, and use getModuleName to traverse down again. - */ - function getInteriorModule(decl: ModuleDeclaration): ModuleDeclaration { - return decl.body && isModuleDeclaration(decl.body) ? getInteriorModule(decl.body) : decl; - } +function isComputedProperty(member: EnumMember): boolean { + return !member.name || member.name.kind === SyntaxKind.ComputedPropertyName; +} - function isComputedProperty(member: EnumMember): boolean { - return !member.name || member.name.kind === SyntaxKind.ComputedPropertyName; - } +function getNodeSpan(node: Node): TextSpan { + return node.kind === SyntaxKind.SourceFile ? createTextSpanFromRange(node) : createTextSpanFromNode(node, curSourceFile); +} - function getNodeSpan(node: Node): TextSpan { - return node.kind === SyntaxKind.SourceFile ? createTextSpanFromRange(node) : createTextSpanFromNode(node, curSourceFile); +function getModifiers(node: Node): string { + if (node.parent && node.parent.kind === SyntaxKind.VariableDeclaration) { + node = node.parent; } + return getNodeModifiers(node); +} - function getModifiers(node: Node): string { - if (node.parent && node.parent.kind === SyntaxKind.VariableDeclaration) { - node = node.parent; - } - return getNodeModifiers(node); +function getFunctionOrClassName(node: FunctionExpression | FunctionDeclaration | ArrowFunction | ClassLikeDeclaration): string { + const { parent } = node; + if (node.name && getFullWidth(node.name) > 0) { + return cleanText(declarationNameToString(node.name)); } + // See if it is a var initializer. If so, use the var name. + else if (isVariableDeclaration(parent)) { + return cleanText(declarationNameToString(parent.name)); + } + // See if it is of the form " = function(){...}". If so, use the text from the left-hand side. + else if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken) { + return nodeText(parent.left).replace(whiteSpaceRegex, ""); + } + // See if it is a property assignment, and if so use the property name + else if (isPropertyAssignment(parent)) { + return nodeText(parent.name); + } + // Default exports are named "default" + else if (getSyntacticModifierFlags(node) & ModifierFlags.Default) { + return "default"; + } + else if (isClassLike(node)) { + return ""; + } + else if (isCallExpression(parent)) { + let name = getCalledExpressionName(parent.expression); + if (name !== undefined) { + name = cleanText(name); - function getFunctionOrClassName(node: FunctionExpression | FunctionDeclaration | ArrowFunction | ClassLikeDeclaration): string { - const { parent } = node; - if (node.name && getFullWidth(node.name) > 0) { - return cleanText(declarationNameToString(node.name)); - } - // See if it is a var initializer. If so, use the var name. - else if (isVariableDeclaration(parent)) { - return cleanText(declarationNameToString(parent.name)); - } - // See if it is of the form " = function(){...}". If so, use the text from the left-hand side. - else if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken) { - return nodeText(parent.left).replace(whiteSpaceRegex, ""); - } - // See if it is a property assignment, and if so use the property name - else if (isPropertyAssignment(parent)) { - return nodeText(parent.name); - } - // Default exports are named "default" - else if (getSyntacticModifierFlags(node) & ModifierFlags.Default) { - return "default"; - } - else if (isClassLike(node)) { - return ""; - } - else if (isCallExpression(parent)) { - let name = getCalledExpressionName(parent.expression); - if (name !== undefined) { - name = cleanText(name); - - if (name.length > maxLength) { - return `${name} callback`; - } - - const args = cleanText(mapDefined(parent.arguments, a => isStringLiteralLike(a) ? a.getText(curSourceFile) : undefined).join(", ")); - return `${name}(${args}) callback`; + if (name.length > maxLength) { + return `${name} callback`; } + + const args = cleanText(mapDefined(parent.arguments, a => isStringLiteralLike(a) ? a.getText(curSourceFile) : undefined).join(", ")); + return `${name}(${args}) callback`; } - return ""; } + return ""; +} - // See also 'tryGetPropertyAccessOrIdentifierToString' - function getCalledExpressionName(expr: Expression): string | undefined { - if (isIdentifier(expr)) { - return expr.text; - } - else if (isPropertyAccessExpression(expr)) { - const left = getCalledExpressionName(expr.expression); - const right = expr.name.text; - return left === undefined ? right : `${left}.${right}`; - } - else { - return undefined; - } +// See also 'tryGetPropertyAccessOrIdentifierToString' +function getCalledExpressionName(expr: Expression): string | undefined { + if (isIdentifier(expr)) { + return expr.text; + } + else if (isPropertyAccessExpression(expr)) { + const left = getCalledExpressionName(expr.expression); + const right = expr.name.text; + return left === undefined ? right : `${left}.${right}`; } + else { + return undefined; + } +} - function isFunctionOrClassExpression(node: Node): node is ArrowFunction | FunctionExpression | ClassExpression { - switch (node.kind) { - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ClassExpression: - return true; - default: - return false; - } +function isFunctionOrClassExpression(node: Node): node is ArrowFunction | FunctionExpression | ClassExpression { + switch (node.kind) { + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ClassExpression: + return true; + default: + return false; } +} - function cleanText(text: string): string { - // Truncate to maximum amount of characters as we don't want to do a big replace operation. - text = text.length > maxLength ? text.substring(0, maxLength) + "..." : text; +function cleanText(text: string): string { + // Truncate to maximum amount of characters as we don't want to do a big replace operation. + text = text.length > maxLength ? text.substring(0, maxLength) + "..." : text; - // Replaces ECMAScript line terminators and removes the trailing `\` from each line: - // \n - Line Feed - // \r - Carriage Return - // \u2028 - Line separator - // \u2029 - Paragraph separator - return text.replace(/\\?(\r?\n|\r|\u2028|\u2029)/g, ""); - } + // Replaces ECMAScript line terminators and removes the trailing `\` from each line: + // \n - Line Feed + // \r - Carriage Return + // \u2028 - Line separator + // \u2029 - Paragraph separator + return text.replace(/\\?(\r?\n|\r|\u2028|\u2029)/g, ""); +} } diff --git a/src/services/organizeImports.ts b/src/services/organizeImports.ts index 5463cf00585d4..5f6361a98f456 100644 --- a/src/services/organizeImports.ts +++ b/src/services/organizeImports.ts @@ -1,495 +1,495 @@ /* @internal */ namespace ts.OrganizeImports { - /** - * Organize imports by: - * 1) Removing unused imports - * 2) Coalescing imports from the same module - * 3) Sorting imports - */ - export function organizeImports( - sourceFile: SourceFile, - formatContext: formatting.FormatContext, - host: LanguageServiceHost, - program: Program, - preferences: UserPreferences, - skipDestructiveCodeActions?: boolean - ) { - - const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext, preferences }); - - const coalesceAndOrganizeImports = (importGroup: readonly ImportDeclaration[]) => stableSort( - coalesceImports(removeUnusedImports(importGroup, sourceFile, program, skipDestructiveCodeActions)), - (s1, s2) => compareImportsOrRequireStatements(s1, s2)); - - // All of the old ImportDeclarations in the file, in syntactic order. - const topLevelImportDecls = sourceFile.statements.filter(isImportDeclaration); - organizeImportsWorker(topLevelImportDecls, coalesceAndOrganizeImports); - - // All of the old ExportDeclarations in the file, in syntactic order. - const topLevelExportDecls = sourceFile.statements.filter(isExportDeclaration); - organizeImportsWorker(topLevelExportDecls, coalesceExports); +/** + * Organize imports by: + * 1) Removing unused imports + * 2) Coalescing imports from the same module + * 3) Sorting imports + */ +export function organizeImports( + sourceFile: SourceFile, + formatContext: formatting.FormatContext, + host: LanguageServiceHost, + program: Program, + preferences: UserPreferences, + skipDestructiveCodeActions?: boolean +) { + + const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext, preferences }); + + const coalesceAndOrganizeImports = (importGroup: readonly ImportDeclaration[]) => stableSort( + coalesceImports(removeUnusedImports(importGroup, sourceFile, program, skipDestructiveCodeActions)), + (s1, s2) => compareImportsOrRequireStatements(s1, s2)); + + // All of the old ImportDeclarations in the file, in syntactic order. + const topLevelImportDecls = sourceFile.statements.filter(isImportDeclaration); + organizeImportsWorker(topLevelImportDecls, coalesceAndOrganizeImports); + + // All of the old ExportDeclarations in the file, in syntactic order. + const topLevelExportDecls = sourceFile.statements.filter(isExportDeclaration); + organizeImportsWorker(topLevelExportDecls, coalesceExports); + + for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) { + if (!ambientModule.body) continue; + + const ambientModuleImportDecls = ambientModule.body.statements.filter(isImportDeclaration); + organizeImportsWorker(ambientModuleImportDecls, coalesceAndOrganizeImports); + + const ambientModuleExportDecls = ambientModule.body.statements.filter(isExportDeclaration); + organizeImportsWorker(ambientModuleExportDecls, coalesceExports); + } - for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) { - if (!ambientModule.body) continue; + return changeTracker.getChanges(); - const ambientModuleImportDecls = ambientModule.body.statements.filter(isImportDeclaration); - organizeImportsWorker(ambientModuleImportDecls, coalesceAndOrganizeImports); + function organizeImportsWorker( + oldImportDecls: readonly T[], + coalesce: (group: readonly T[]) => readonly T[]) { - const ambientModuleExportDecls = ambientModule.body.statements.filter(isExportDeclaration); - organizeImportsWorker(ambientModuleExportDecls, coalesceExports); + if (length(oldImportDecls) === 0) { + return; } - return changeTracker.getChanges(); - - function organizeImportsWorker( - oldImportDecls: readonly T[], - coalesce: (group: readonly T[]) => readonly T[]) { - - if (length(oldImportDecls) === 0) { - return; - } - - // Special case: normally, we'd expect leading and trailing trivia to follow each import - // around as it's sorted. However, we do not want this to happen for leading trivia - // on the first import because it is probably the header comment for the file. - // Consider: we could do a more careful check that this trivia is actually a header, - // but the consequences of being wrong are very minor. - suppressLeadingTrivia(oldImportDecls[0]); - - const oldImportGroups = group(oldImportDecls, importDecl => getExternalModuleName(importDecl.moduleSpecifier!)!); - const sortedImportGroups = stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiers(group1[0].moduleSpecifier, group2[0].moduleSpecifier)); - const newImportDecls = flatMap(sortedImportGroups, importGroup => - getExternalModuleName(importGroup[0].moduleSpecifier!) - ? coalesce(importGroup) - : importGroup); - - // Delete all nodes if there are no imports. - if (newImportDecls.length === 0) { - // Consider the first node to have trailingTrivia as we want to exclude the - // "header" comment. - changeTracker.deleteNodes(sourceFile, oldImportDecls, { - trailingTriviaOption: textChanges.TrailingTriviaOption.Include, - }, /*hasTrailingComment*/ true); - } - else { - // Note: Delete the surrounding trivia because it will have been retained in newImportDecls. - const replaceOptions = { - leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, // Leave header comment in place - trailingTriviaOption: textChanges.TrailingTriviaOption.Include, - suffix: getNewLineOrDefaultFromHost(host, formatContext.options), - }; - changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, replaceOptions); - const hasTrailingComment = changeTracker.nodeHasTrailingComment(sourceFile, oldImportDecls[0], replaceOptions); - changeTracker.deleteNodes(sourceFile, oldImportDecls.slice(1), { - trailingTriviaOption: textChanges.TrailingTriviaOption.Include, - }, hasTrailingComment); - } + // Special case: normally, we'd expect leading and trailing trivia to follow each import + // around as it's sorted. However, we do not want this to happen for leading trivia + // on the first import because it is probably the header comment for the file. + // Consider: we could do a more careful check that this trivia is actually a header, + // but the consequences of being wrong are very minor. + suppressLeadingTrivia(oldImportDecls[0]); + + const oldImportGroups = group(oldImportDecls, importDecl => getExternalModuleName(importDecl.moduleSpecifier!)!); + const sortedImportGroups = stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiers(group1[0].moduleSpecifier, group2[0].moduleSpecifier)); + const newImportDecls = flatMap(sortedImportGroups, importGroup => + getExternalModuleName(importGroup[0].moduleSpecifier!) + ? coalesce(importGroup) + : importGroup); + + // Delete all nodes if there are no imports. + if (newImportDecls.length === 0) { + // Consider the first node to have trailingTrivia as we want to exclude the + // "header" comment. + changeTracker.deleteNodes(sourceFile, oldImportDecls, { + trailingTriviaOption: textChanges.TrailingTriviaOption.Include, + }, /*hasTrailingComment*/ true); + } + else { + // Note: Delete the surrounding trivia because it will have been retained in newImportDecls. + const replaceOptions = { + leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, // Leave header comment in place + trailingTriviaOption: textChanges.TrailingTriviaOption.Include, + suffix: getNewLineOrDefaultFromHost(host, formatContext.options), + }; + changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, replaceOptions); + const hasTrailingComment = changeTracker.nodeHasTrailingComment(sourceFile, oldImportDecls[0], replaceOptions); + changeTracker.deleteNodes(sourceFile, oldImportDecls.slice(1), { + trailingTriviaOption: textChanges.TrailingTriviaOption.Include, + }, hasTrailingComment); } } +} - function removeUnusedImports(oldImports: readonly ImportDeclaration[], sourceFile: SourceFile, program: Program, skipDestructiveCodeActions: boolean | undefined) { - // As a precaution, consider unused import detection to be destructive (GH #43051) - if (skipDestructiveCodeActions) { - return oldImports; - } +function removeUnusedImports(oldImports: readonly ImportDeclaration[], sourceFile: SourceFile, program: Program, skipDestructiveCodeActions: boolean | undefined) { + // As a precaution, consider unused import detection to be destructive (GH #43051) + if (skipDestructiveCodeActions) { + return oldImports; + } - const typeChecker = program.getTypeChecker(); - const jsxNamespace = typeChecker.getJsxNamespace(sourceFile); - const jsxFragmentFactory = typeChecker.getJsxFragmentFactory(sourceFile); - const jsxElementsPresent = !!(sourceFile.transformFlags & TransformFlags.ContainsJsx); + const typeChecker = program.getTypeChecker(); + const jsxNamespace = typeChecker.getJsxNamespace(sourceFile); + const jsxFragmentFactory = typeChecker.getJsxFragmentFactory(sourceFile); + const jsxElementsPresent = !!(sourceFile.transformFlags & TransformFlags.ContainsJsx); - const usedImports: ImportDeclaration[] = []; + const usedImports: ImportDeclaration[] = []; - for (const importDecl of oldImports) { - const { importClause, moduleSpecifier } = importDecl; + for (const importDecl of oldImports) { + const { importClause, moduleSpecifier } = importDecl; - if (!importClause) { - // Imports without import clauses are assumed to be included for their side effects and are not removed. - usedImports.push(importDecl); - continue; - } + if (!importClause) { + // Imports without import clauses are assumed to be included for their side effects and are not removed. + usedImports.push(importDecl); + continue; + } - let { name, namedBindings } = importClause; + let { name, namedBindings } = importClause; - // Default import - if (name && !isDeclarationUsed(name)) { - name = undefined; - } + // Default import + if (name && !isDeclarationUsed(name)) { + name = undefined; + } - if (namedBindings) { - if (isNamespaceImport(namedBindings)) { - // Namespace import - if (!isDeclarationUsed(namedBindings.name)) { - namedBindings = undefined; - } + if (namedBindings) { + if (isNamespaceImport(namedBindings)) { + // Namespace import + if (!isDeclarationUsed(namedBindings.name)) { + namedBindings = undefined; } - else { - // List of named imports - const newElements = namedBindings.elements.filter(e => isDeclarationUsed(e.name)); - if (newElements.length < namedBindings.elements.length) { - namedBindings = newElements.length - ? factory.updateNamedImports(namedBindings, newElements) - : undefined; - } + } + else { + // List of named imports + const newElements = namedBindings.elements.filter(e => isDeclarationUsed(e.name)); + if (newElements.length < namedBindings.elements.length) { + namedBindings = newElements.length + ? factory.updateNamedImports(namedBindings, newElements) + : undefined; } } + } - if (name || namedBindings) { - usedImports.push(updateImportDeclarationAndClause(importDecl, name, namedBindings)); + if (name || namedBindings) { + usedImports.push(updateImportDeclarationAndClause(importDecl, name, namedBindings)); + } + // If a module is imported to be augmented, it’s used + else if (hasModuleDeclarationMatchingSpecifier(sourceFile, moduleSpecifier)) { + // If we’re in a declaration file, it’s safe to remove the import clause from it + if (sourceFile.isDeclarationFile) { + usedImports.push(factory.createImportDeclaration( + importDecl.decorators, + importDecl.modifiers, + /*importClause*/ undefined, + moduleSpecifier, + /*assertClause*/ undefined)); } - // If a module is imported to be augmented, it’s used - else if (hasModuleDeclarationMatchingSpecifier(sourceFile, moduleSpecifier)) { - // If we’re in a declaration file, it’s safe to remove the import clause from it - if (sourceFile.isDeclarationFile) { - usedImports.push(factory.createImportDeclaration( - importDecl.decorators, - importDecl.modifiers, - /*importClause*/ undefined, - moduleSpecifier, - /*assertClause*/ undefined)); - } - // If we’re not in a declaration file, we can’t remove the import clause even though - // the imported symbols are unused, because removing them makes it look like the import - // declaration has side effects, which will cause it to be preserved in the JS emit. - else { - usedImports.push(importDecl); - } + // If we’re not in a declaration file, we can’t remove the import clause even though + // the imported symbols are unused, because removing them makes it look like the import + // declaration has side effects, which will cause it to be preserved in the JS emit. + else { + usedImports.push(importDecl); } } + } - return usedImports; + return usedImports; - function isDeclarationUsed(identifier: Identifier) { - // The JSX factory symbol is always used if JSX elements are present - even if they are not allowed. - return jsxElementsPresent && (identifier.text === jsxNamespace || jsxFragmentFactory && identifier.text === jsxFragmentFactory) || - FindAllReferences.Core.isSymbolReferencedInFile(identifier, typeChecker, sourceFile); - } + function isDeclarationUsed(identifier: Identifier) { + // The JSX factory symbol is always used if JSX elements are present - even if they are not allowed. + return jsxElementsPresent && (identifier.text === jsxNamespace || jsxFragmentFactory && identifier.text === jsxFragmentFactory) || + FindAllReferences.Core.isSymbolReferencedInFile(identifier, typeChecker, sourceFile); } +} + +function hasModuleDeclarationMatchingSpecifier(sourceFile: SourceFile, moduleSpecifier: Expression) { + const moduleSpecifierText = isStringLiteral(moduleSpecifier) && moduleSpecifier.text; + return isString(moduleSpecifierText) && some(sourceFile.moduleAugmentations, moduleName => + isStringLiteral(moduleName) + && moduleName.text === moduleSpecifierText); +} + +function getExternalModuleName(specifier: Expression) { + return specifier !== undefined && isStringLiteralLike(specifier) + ? specifier.text + : undefined; +} - function hasModuleDeclarationMatchingSpecifier(sourceFile: SourceFile, moduleSpecifier: Expression) { - const moduleSpecifierText = isStringLiteral(moduleSpecifier) && moduleSpecifier.text; - return isString(moduleSpecifierText) && some(sourceFile.moduleAugmentations, moduleName => - isStringLiteral(moduleName) - && moduleName.text === moduleSpecifierText); +// Internal for testing +/** + * @param importGroup a list of ImportDeclarations, all with the same module name. + */ +export function coalesceImports(importGroup: readonly ImportDeclaration[]) { + if (importGroup.length === 0) { + return importGroup; } - function getExternalModuleName(specifier: Expression) { - return specifier !== undefined && isStringLiteralLike(specifier) - ? specifier.text - : undefined; + const { importWithoutClause, typeOnlyImports, regularImports } = getCategorizedImports(importGroup); + + const coalescedImports: ImportDeclaration[] = []; + + if (importWithoutClause) { + coalescedImports.push(importWithoutClause); } - // Internal for testing - /** - * @param importGroup a list of ImportDeclarations, all with the same module name. - */ - export function coalesceImports(importGroup: readonly ImportDeclaration[]) { - if (importGroup.length === 0) { - return importGroup; + for (const group of [regularImports, typeOnlyImports]) { + const isTypeOnly = group === typeOnlyImports; + const { defaultImports, namespaceImports, namedImports } = group; + // Normally, we don't combine default and namespace imports, but it would be silly to + // produce two import declarations in this special case. + if (!isTypeOnly && defaultImports.length === 1 && namespaceImports.length === 1 && namedImports.length === 0) { + // Add the namespace import to the existing default ImportDeclaration. + const defaultImport = defaultImports[0]; + coalescedImports.push( + updateImportDeclarationAndClause(defaultImport, defaultImport.importClause!.name, namespaceImports[0].importClause!.namedBindings)); // TODO: GH#18217 + + continue; } - const { importWithoutClause, typeOnlyImports, regularImports } = getCategorizedImports(importGroup); + const sortedNamespaceImports = stableSort(namespaceImports, (i1, i2) => + compareIdentifiers((i1.importClause!.namedBindings as NamespaceImport).name, (i2.importClause!.namedBindings as NamespaceImport).name)); // TODO: GH#18217 - const coalescedImports: ImportDeclaration[] = []; + for (const namespaceImport of sortedNamespaceImports) { + // Drop the name, if any + coalescedImports.push( + updateImportDeclarationAndClause(namespaceImport, /*name*/ undefined, namespaceImport.importClause!.namedBindings)); // TODO: GH#18217 + } - if (importWithoutClause) { - coalescedImports.push(importWithoutClause); + if (defaultImports.length === 0 && namedImports.length === 0) { + continue; } - for (const group of [regularImports, typeOnlyImports]) { - const isTypeOnly = group === typeOnlyImports; - const { defaultImports, namespaceImports, namedImports } = group; - // Normally, we don't combine default and namespace imports, but it would be silly to - // produce two import declarations in this special case. - if (!isTypeOnly && defaultImports.length === 1 && namespaceImports.length === 1 && namedImports.length === 0) { - // Add the namespace import to the existing default ImportDeclaration. - const defaultImport = defaultImports[0]; - coalescedImports.push( - updateImportDeclarationAndClause(defaultImport, defaultImport.importClause!.name, namespaceImports[0].importClause!.namedBindings)); // TODO: GH#18217 - - continue; + let newDefaultImport: Identifier | undefined; + const newImportSpecifiers: ImportSpecifier[] = []; + if (defaultImports.length === 1) { + newDefaultImport = defaultImports[0].importClause!.name; + } + else { + for (const defaultImport of defaultImports) { + newImportSpecifiers.push( + factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("default"), defaultImport.importClause!.name!)); // TODO: GH#18217 } + } + + newImportSpecifiers.push(...flatMap(namedImports, i => (i.importClause!.namedBindings as NamedImports).elements)); // TODO: GH#18217 + + const sortedImportSpecifiers = sortSpecifiers(newImportSpecifiers); + + const importDecl = defaultImports.length > 0 + ? defaultImports[0] + : namedImports[0]; + + const newNamedImports = sortedImportSpecifiers.length === 0 + ? newDefaultImport + ? undefined + : factory.createNamedImports(emptyArray) + : namedImports.length === 0 + ? factory.createNamedImports(sortedImportSpecifiers) + : factory.updateNamedImports(namedImports[0].importClause!.namedBindings as NamedImports, sortedImportSpecifiers); // TODO: GH#18217 + + // Type-only imports are not allowed to mix default, namespace, and named imports in any combination. + // We could rewrite a default import as a named import (`import { default as name }`), but we currently + // choose not to as a stylistic preference. + if (isTypeOnly && newDefaultImport && newNamedImports) { + coalescedImports.push( + updateImportDeclarationAndClause(importDecl, newDefaultImport, /*namedBindings*/ undefined)); + coalescedImports.push( + updateImportDeclarationAndClause(namedImports[0] ?? importDecl, /*name*/ undefined, newNamedImports)); + } + else { + coalescedImports.push( + updateImportDeclarationAndClause(importDecl, newDefaultImport, newNamedImports)); + } + } - const sortedNamespaceImports = stableSort(namespaceImports, (i1, i2) => - compareIdentifiers((i1.importClause!.namedBindings as NamespaceImport).name, (i2.importClause!.namedBindings as NamespaceImport).name)); // TODO: GH#18217 + return coalescedImports; - for (const namespaceImport of sortedNamespaceImports) { - // Drop the name, if any - coalescedImports.push( - updateImportDeclarationAndClause(namespaceImport, /*name*/ undefined, namespaceImport.importClause!.namedBindings)); // TODO: GH#18217 - } +} - if (defaultImports.length === 0 && namedImports.length === 0) { - continue; - } +interface ImportGroup { + defaultImports: ImportDeclaration[]; + namespaceImports: ImportDeclaration[]; + namedImports: ImportDeclaration[]; +} - let newDefaultImport: Identifier | undefined; - const newImportSpecifiers: ImportSpecifier[] = []; - if (defaultImports.length === 1) { - newDefaultImport = defaultImports[0].importClause!.name; - } - else { - for (const defaultImport of defaultImports) { - newImportSpecifiers.push( - factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("default"), defaultImport.importClause!.name!)); // TODO: GH#18217 - } - } +/* + * Returns entire import declarations because they may already have been rewritten and + * may lack parent pointers. The desired parts can easily be recovered based on the + * categorization. + * + * NB: There may be overlap between `defaultImports` and `namespaceImports`/`namedImports`. + */ +function getCategorizedImports(importGroup: readonly ImportDeclaration[]) { + let importWithoutClause: ImportDeclaration | undefined; + const typeOnlyImports: ImportGroup = { defaultImports: [], namespaceImports: [], namedImports: [] }; + const regularImports: ImportGroup = { defaultImports: [], namespaceImports: [], namedImports: [] }; + + for (const importDeclaration of importGroup) { + if (importDeclaration.importClause === undefined) { + // Only the first such import is interesting - the others are redundant. + // Note: Unfortunately, we will lose trivia that was on this node. + importWithoutClause = importWithoutClause || importDeclaration; + continue; + } + + const group = importDeclaration.importClause.isTypeOnly ? typeOnlyImports : regularImports; + const { name, namedBindings } = importDeclaration.importClause; - newImportSpecifiers.push(...flatMap(namedImports, i => (i.importClause!.namedBindings as NamedImports).elements)); // TODO: GH#18217 - - const sortedImportSpecifiers = sortSpecifiers(newImportSpecifiers); - - const importDecl = defaultImports.length > 0 - ? defaultImports[0] - : namedImports[0]; - - const newNamedImports = sortedImportSpecifiers.length === 0 - ? newDefaultImport - ? undefined - : factory.createNamedImports(emptyArray) - : namedImports.length === 0 - ? factory.createNamedImports(sortedImportSpecifiers) - : factory.updateNamedImports(namedImports[0].importClause!.namedBindings as NamedImports, sortedImportSpecifiers); // TODO: GH#18217 - - // Type-only imports are not allowed to mix default, namespace, and named imports in any combination. - // We could rewrite a default import as a named import (`import { default as name }`), but we currently - // choose not to as a stylistic preference. - if (isTypeOnly && newDefaultImport && newNamedImports) { - coalescedImports.push( - updateImportDeclarationAndClause(importDecl, newDefaultImport, /*namedBindings*/ undefined)); - coalescedImports.push( - updateImportDeclarationAndClause(namedImports[0] ?? importDecl, /*name*/ undefined, newNamedImports)); + if (name) { + group.defaultImports.push(importDeclaration); + } + + if (namedBindings) { + if (isNamespaceImport(namedBindings)) { + group.namespaceImports.push(importDeclaration); } else { - coalescedImports.push( - updateImportDeclarationAndClause(importDecl, newDefaultImport, newNamedImports)); + group.namedImports.push(importDeclaration); } } + } - return coalescedImports; + return { + importWithoutClause, + typeOnlyImports, + regularImports, + }; +} + +// Internal for testing +/** + * @param exportGroup a list of ExportDeclarations, all with the same module name. + */ +export function coalesceExports(exportGroup: readonly ExportDeclaration[]) { + if (exportGroup.length === 0) { + return exportGroup; + } + + const { exportWithoutClause, namedExports, typeOnlyExports } = getCategorizedExports(exportGroup); + const coalescedExports: ExportDeclaration[] = []; + + if (exportWithoutClause) { + coalescedExports.push(exportWithoutClause); } - interface ImportGroup { - defaultImports: ImportDeclaration[]; - namespaceImports: ImportDeclaration[]; - namedImports: ImportDeclaration[]; + for (const exportGroup of [namedExports, typeOnlyExports]) { + if (exportGroup.length === 0) { + continue; + } + const newExportSpecifiers: ExportSpecifier[] = []; + newExportSpecifiers.push(...flatMap(exportGroup, i => i.exportClause && isNamedExports(i.exportClause) ? i.exportClause.elements : emptyArray)); + + const sortedExportSpecifiers = sortSpecifiers(newExportSpecifiers); + + const exportDecl = exportGroup[0]; + coalescedExports.push( + factory.updateExportDeclaration( + exportDecl, + exportDecl.decorators, + exportDecl.modifiers, + exportDecl.isTypeOnly, + exportDecl.exportClause && ( + isNamedExports(exportDecl.exportClause) ? + factory.updateNamedExports(exportDecl.exportClause, sortedExportSpecifiers) : + factory.updateNamespaceExport(exportDecl.exportClause, exportDecl.exportClause.name) + ), + exportDecl.moduleSpecifier, + exportDecl.assertClause)); } + return coalescedExports; + /* - * Returns entire import declarations because they may already have been rewritten and + * Returns entire export declarations because they may already have been rewritten and * may lack parent pointers. The desired parts can easily be recovered based on the * categorization. - * - * NB: There may be overlap between `defaultImports` and `namespaceImports`/`namedImports`. */ - function getCategorizedImports(importGroup: readonly ImportDeclaration[]) { - let importWithoutClause: ImportDeclaration | undefined; - const typeOnlyImports: ImportGroup = { defaultImports: [], namespaceImports: [], namedImports: [] }; - const regularImports: ImportGroup = { defaultImports: [], namespaceImports: [], namedImports: [] }; - - for (const importDeclaration of importGroup) { - if (importDeclaration.importClause === undefined) { - // Only the first such import is interesting - the others are redundant. + function getCategorizedExports(exportGroup: readonly ExportDeclaration[]) { + let exportWithoutClause: ExportDeclaration | undefined; + const namedExports: ExportDeclaration[] = []; + const typeOnlyExports: ExportDeclaration[] = []; + + for (const exportDeclaration of exportGroup) { + if (exportDeclaration.exportClause === undefined) { + // Only the first such export is interesting - the others are redundant. // Note: Unfortunately, we will lose trivia that was on this node. - importWithoutClause = importWithoutClause || importDeclaration; - continue; + exportWithoutClause = exportWithoutClause || exportDeclaration; } - - const group = importDeclaration.importClause.isTypeOnly ? typeOnlyImports : regularImports; - const { name, namedBindings } = importDeclaration.importClause; - - if (name) { - group.defaultImports.push(importDeclaration); + else if (exportDeclaration.isTypeOnly) { + typeOnlyExports.push(exportDeclaration); } - - if (namedBindings) { - if (isNamespaceImport(namedBindings)) { - group.namespaceImports.push(importDeclaration); - } - else { - group.namedImports.push(importDeclaration); - } + else { + namedExports.push(exportDeclaration); } } return { - importWithoutClause, - typeOnlyImports, - regularImports, + exportWithoutClause, + namedExports, + typeOnlyExports, }; } +} - // Internal for testing - /** - * @param exportGroup a list of ExportDeclarations, all with the same module name. - */ - export function coalesceExports(exportGroup: readonly ExportDeclaration[]) { - if (exportGroup.length === 0) { - return exportGroup; - } - - const { exportWithoutClause, namedExports, typeOnlyExports } = getCategorizedExports(exportGroup); - - const coalescedExports: ExportDeclaration[] = []; - - if (exportWithoutClause) { - coalescedExports.push(exportWithoutClause); - } - - for (const exportGroup of [namedExports, typeOnlyExports]) { - if (exportGroup.length === 0) { - continue; - } - const newExportSpecifiers: ExportSpecifier[] = []; - newExportSpecifiers.push(...flatMap(exportGroup, i => i.exportClause && isNamedExports(i.exportClause) ? i.exportClause.elements : emptyArray)); - - const sortedExportSpecifiers = sortSpecifiers(newExportSpecifiers); - - const exportDecl = exportGroup[0]; - coalescedExports.push( - factory.updateExportDeclaration( - exportDecl, - exportDecl.decorators, - exportDecl.modifiers, - exportDecl.isTypeOnly, - exportDecl.exportClause && ( - isNamedExports(exportDecl.exportClause) ? - factory.updateNamedExports(exportDecl.exportClause, sortedExportSpecifiers) : - factory.updateNamespaceExport(exportDecl.exportClause, exportDecl.exportClause.name) - ), - exportDecl.moduleSpecifier, - exportDecl.assertClause)); - } - - return coalescedExports; - - /* - * Returns entire export declarations because they may already have been rewritten and - * may lack parent pointers. The desired parts can easily be recovered based on the - * categorization. - */ - function getCategorizedExports(exportGroup: readonly ExportDeclaration[]) { - let exportWithoutClause: ExportDeclaration | undefined; - const namedExports: ExportDeclaration[] = []; - const typeOnlyExports: ExportDeclaration[] = []; - - for (const exportDeclaration of exportGroup) { - if (exportDeclaration.exportClause === undefined) { - // Only the first such export is interesting - the others are redundant. - // Note: Unfortunately, we will lose trivia that was on this node. - exportWithoutClause = exportWithoutClause || exportDeclaration; - } - else if (exportDeclaration.isTypeOnly) { - typeOnlyExports.push(exportDeclaration); - } - else { - namedExports.push(exportDeclaration); - } - } - - return { - exportWithoutClause, - namedExports, - typeOnlyExports, - }; - } - } - - function updateImportDeclarationAndClause( - importDeclaration: ImportDeclaration, - name: Identifier | undefined, - namedBindings: NamedImportBindings | undefined) { - - return factory.updateImportDeclaration( - importDeclaration, - importDeclaration.decorators, - importDeclaration.modifiers, - factory.updateImportClause(importDeclaration.importClause!, importDeclaration.importClause!.isTypeOnly, name, namedBindings), // TODO: GH#18217 - importDeclaration.moduleSpecifier, - importDeclaration.assertClause); - } +function updateImportDeclarationAndClause( + importDeclaration: ImportDeclaration, + name: Identifier | undefined, + namedBindings: NamedImportBindings | undefined) { + + return factory.updateImportDeclaration( + importDeclaration, + importDeclaration.decorators, + importDeclaration.modifiers, + factory.updateImportClause(importDeclaration.importClause!, importDeclaration.importClause!.isTypeOnly, name, namedBindings), // TODO: GH#18217 + importDeclaration.moduleSpecifier, + importDeclaration.assertClause); +} - function sortSpecifiers(specifiers: readonly T[]) { - return stableSort(specifiers, compareImportOrExportSpecifiers); - } +function sortSpecifiers(specifiers: readonly T[]) { + return stableSort(specifiers, compareImportOrExportSpecifiers); +} - export function compareImportOrExportSpecifiers(s1: T, s2: T) { - return compareBooleans(s1.isTypeOnly, s2.isTypeOnly) - || compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name) - || compareIdentifiers(s1.name, s2.name); - } +export function compareImportOrExportSpecifiers(s1: T, s2: T) { + return compareBooleans(s1.isTypeOnly, s2.isTypeOnly) + || compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name) + || compareIdentifiers(s1.name, s2.name); +} - /* internal */ // Exported for testing - export function compareModuleSpecifiers(m1: Expression | undefined, m2: Expression | undefined) { - const name1 = m1 === undefined ? undefined : getExternalModuleName(m1); - const name2 = m2 === undefined ? undefined : getExternalModuleName(m2); - return compareBooleans(name1 === undefined, name2 === undefined) || - compareBooleans(isExternalModuleNameRelative(name1!), isExternalModuleNameRelative(name2!)) || - compareStringsCaseInsensitive(name1!, name2!); - } +/* internal */ // Exported for testing +export function compareModuleSpecifiers(m1: Expression | undefined, m2: Expression | undefined) { + const name1 = m1 === undefined ? undefined : getExternalModuleName(m1); + const name2 = m2 === undefined ? undefined : getExternalModuleName(m2); + return compareBooleans(name1 === undefined, name2 === undefined) || + compareBooleans(isExternalModuleNameRelative(name1!), isExternalModuleNameRelative(name2!)) || + compareStringsCaseInsensitive(name1!, name2!); +} - function compareIdentifiers(s1: Identifier, s2: Identifier) { - return compareStringsCaseInsensitive(s1.text, s2.text); - } +function compareIdentifiers(s1: Identifier, s2: Identifier) { + return compareStringsCaseInsensitive(s1.text, s2.text); +} - function getModuleSpecifierExpression(declaration: AnyImportOrRequireStatement): Expression | undefined { - switch (declaration.kind) { - case SyntaxKind.ImportEqualsDeclaration: - return tryCast(declaration.moduleReference, isExternalModuleReference)?.expression; - case SyntaxKind.ImportDeclaration: - return declaration.moduleSpecifier; - case SyntaxKind.VariableStatement: - return declaration.declarationList.declarations[0].initializer.arguments[0]; - } +function getModuleSpecifierExpression(declaration: AnyImportOrRequireStatement): Expression | undefined { + switch (declaration.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return tryCast(declaration.moduleReference, isExternalModuleReference)?.expression; + case SyntaxKind.ImportDeclaration: + return declaration.moduleSpecifier; + case SyntaxKind.VariableStatement: + return declaration.declarationList.declarations[0].initializer.arguments[0]; } +} - export function importsAreSorted(imports: readonly AnyImportOrRequireStatement[]): imports is SortedReadonlyArray { - return arrayIsSorted(imports, compareImportsOrRequireStatements); - } +export function importsAreSorted(imports: readonly AnyImportOrRequireStatement[]): imports is SortedReadonlyArray { + return arrayIsSorted(imports, compareImportsOrRequireStatements); +} - export function importSpecifiersAreSorted(imports: readonly ImportSpecifier[]): imports is SortedReadonlyArray { - return arrayIsSorted(imports, compareImportOrExportSpecifiers); - } +export function importSpecifiersAreSorted(imports: readonly ImportSpecifier[]): imports is SortedReadonlyArray { + return arrayIsSorted(imports, compareImportOrExportSpecifiers); +} - export function getImportDeclarationInsertionIndex(sortedImports: SortedReadonlyArray, newImport: AnyImportOrRequireStatement) { - const index = binarySearch(sortedImports, newImport, identity, compareImportsOrRequireStatements); - return index < 0 ? ~index : index; - } +export function getImportDeclarationInsertionIndex(sortedImports: SortedReadonlyArray, newImport: AnyImportOrRequireStatement) { + const index = binarySearch(sortedImports, newImport, identity, compareImportsOrRequireStatements); + return index < 0 ? ~index : index; +} - export function getImportSpecifierInsertionIndex(sortedImports: SortedReadonlyArray, newImport: ImportSpecifier) { - const index = binarySearch(sortedImports, newImport, identity, compareImportOrExportSpecifiers); - return index < 0 ? ~index : index; - } +export function getImportSpecifierInsertionIndex(sortedImports: SortedReadonlyArray, newImport: ImportSpecifier) { + const index = binarySearch(sortedImports, newImport, identity, compareImportOrExportSpecifiers); + return index < 0 ? ~index : index; +} - export function compareImportsOrRequireStatements(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) { - return compareModuleSpecifiers(getModuleSpecifierExpression(s1), getModuleSpecifierExpression(s2)) || compareImportKind(s1, s2); - } +export function compareImportsOrRequireStatements(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) { + return compareModuleSpecifiers(getModuleSpecifierExpression(s1), getModuleSpecifierExpression(s2)) || compareImportKind(s1, s2); +} - function compareImportKind(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) { - return compareValues(getImportKindOrder(s1), getImportKindOrder(s2)); - } +function compareImportKind(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) { + return compareValues(getImportKindOrder(s1), getImportKindOrder(s2)); +} - // 1. Side-effect imports - // 2. Type-only imports - // 3. Namespace imports - // 4. Default imports - // 5. Named imports - // 6. ImportEqualsDeclarations - // 7. Require variable statements - function getImportKindOrder(s1: AnyImportOrRequireStatement) { - switch (s1.kind) { - case SyntaxKind.ImportDeclaration: - if (!s1.importClause) return 0; - if (s1.importClause.isTypeOnly) return 1; - if (s1.importClause.namedBindings?.kind === SyntaxKind.NamespaceImport) return 2; - if (s1.importClause.name) return 3; - return 4; - case SyntaxKind.ImportEqualsDeclaration: - return 5; - case SyntaxKind.VariableStatement: - return 6; - } +// 1. Side-effect imports +// 2. Type-only imports +// 3. Namespace imports +// 4. Default imports +// 5. Named imports +// 6. ImportEqualsDeclarations +// 7. Require variable statements +function getImportKindOrder(s1: AnyImportOrRequireStatement) { + switch (s1.kind) { + case SyntaxKind.ImportDeclaration: + if (!s1.importClause) return 0; + if (s1.importClause.isTypeOnly) return 1; + if (s1.importClause.namedBindings?.kind === SyntaxKind.NamespaceImport) return 2; + if (s1.importClause.name) return 3; + return 4; + case SyntaxKind.ImportEqualsDeclaration: + return 5; + case SyntaxKind.VariableStatement: + return 6; } } +} diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index c071fb145cc18..78e51ccb73e86 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -1,336 +1,336 @@ /* @internal */ namespace ts.OutliningElementsCollector { - export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] { - const res: OutliningSpan[] = []; - addNodeOutliningSpans(sourceFile, cancellationToken, res); - addRegionOutliningSpans(sourceFile, res); - return res.sort((span1, span2) => span1.textSpan.start - span2.textSpan.start); - } +export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] { + const res: OutliningSpan[] = []; + addNodeOutliningSpans(sourceFile, cancellationToken, res); + addRegionOutliningSpans(sourceFile, res); + return res.sort((span1, span2) => span1.textSpan.start - span2.textSpan.start); +} - function addNodeOutliningSpans(sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { - let depthRemaining = 40; - let current = 0; - // Includes the EOF Token so that comments which aren't attached to statements are included - const statements = [...sourceFile.statements, sourceFile.endOfFileToken]; - const n = statements.length; - while (current < n) { - while (current < n && !isAnyImportSyntax(statements[current])) { - visitNonImportNode(statements[current]); - current++; - } - if (current === n) break; - const firstImport = current; - while (current < n && isAnyImportSyntax(statements[current])) { - addOutliningForLeadingCommentsForNode(statements[current], sourceFile, cancellationToken, out); - current++; - } - const lastImport = current - 1; - if (lastImport !== firstImport) { - out.push(createOutliningSpanFromBounds(findChildOfKind(statements[firstImport], SyntaxKind.ImportKeyword, sourceFile)!.getStart(sourceFile), statements[lastImport].getEnd(), OutliningSpanKind.Imports)); - } +function addNodeOutliningSpans(sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { + let depthRemaining = 40; + let current = 0; + // Includes the EOF Token so that comments which aren't attached to statements are included + const statements = [...sourceFile.statements, sourceFile.endOfFileToken]; + const n = statements.length; + while (current < n) { + while (current < n && !isAnyImportSyntax(statements[current])) { + visitNonImportNode(statements[current]); + current++; } + if (current === n) break; + const firstImport = current; + while (current < n && isAnyImportSyntax(statements[current])) { + addOutliningForLeadingCommentsForNode(statements[current], sourceFile, cancellationToken, out); + current++; + } + const lastImport = current - 1; + if (lastImport !== firstImport) { + out.push(createOutliningSpanFromBounds(findChildOfKind(statements[firstImport], SyntaxKind.ImportKeyword, sourceFile)!.getStart(sourceFile), statements[lastImport].getEnd(), OutliningSpanKind.Imports)); + } + } - function visitNonImportNode(n: Node) { - if (depthRemaining === 0) return; - cancellationToken.throwIfCancellationRequested(); + function visitNonImportNode(n: Node) { + if (depthRemaining === 0) return; + cancellationToken.throwIfCancellationRequested(); - if (isDeclaration(n) || isVariableStatement(n) || isReturnStatement(n) || isCallOrNewExpression(n) || n.kind === SyntaxKind.EndOfFileToken) { - addOutliningForLeadingCommentsForNode(n, sourceFile, cancellationToken, out); - } + if (isDeclaration(n) || isVariableStatement(n) || isReturnStatement(n) || isCallOrNewExpression(n) || n.kind === SyntaxKind.EndOfFileToken) { + addOutliningForLeadingCommentsForNode(n, sourceFile, cancellationToken, out); + } - if (isFunctionLike(n) && isBinaryExpression(n.parent) && isPropertyAccessExpression(n.parent.left)) { - addOutliningForLeadingCommentsForNode(n.parent.left, sourceFile, cancellationToken, out); - } + if (isFunctionLike(n) && isBinaryExpression(n.parent) && isPropertyAccessExpression(n.parent.left)) { + addOutliningForLeadingCommentsForNode(n.parent.left, sourceFile, cancellationToken, out); + } - if (isBlock(n) || isModuleBlock(n)) { - addOutliningForLeadingCommentsForPos(n.statements.end, sourceFile, cancellationToken, out); - } + if (isBlock(n) || isModuleBlock(n)) { + addOutliningForLeadingCommentsForPos(n.statements.end, sourceFile, cancellationToken, out); + } - if (isClassLike(n) || isInterfaceDeclaration(n)) { - addOutliningForLeadingCommentsForPos(n.members.end, sourceFile, cancellationToken, out); - } + if (isClassLike(n) || isInterfaceDeclaration(n)) { + addOutliningForLeadingCommentsForPos(n.members.end, sourceFile, cancellationToken, out); + } - const span = getOutliningSpanForNode(n, sourceFile); - if (span) out.push(span); + const span = getOutliningSpanForNode(n, sourceFile); + if (span) out.push(span); + depthRemaining--; + if (isCallExpression(n)) { + depthRemaining++; + visitNonImportNode(n.expression); depthRemaining--; - if (isCallExpression(n)) { - depthRemaining++; - visitNonImportNode(n.expression); - depthRemaining--; - n.arguments.forEach(visitNonImportNode); - n.typeArguments?.forEach(visitNonImportNode); - } - else if (isIfStatement(n) && n.elseStatement && isIfStatement(n.elseStatement)) { - // Consider an 'else if' to be on the same depth as the 'if'. - visitNonImportNode(n.expression); - visitNonImportNode(n.thenStatement); - depthRemaining++; - visitNonImportNode(n.elseStatement); - depthRemaining--; - } - else { - n.forEachChild(visitNonImportNode); - } + n.arguments.forEach(visitNonImportNode); + n.typeArguments?.forEach(visitNonImportNode); + } + else if (isIfStatement(n) && n.elseStatement && isIfStatement(n.elseStatement)) { + // Consider an 'else if' to be on the same depth as the 'if'. + visitNonImportNode(n.expression); + visitNonImportNode(n.thenStatement); depthRemaining++; + visitNonImportNode(n.elseStatement); + depthRemaining--; + } + else { + n.forEachChild(visitNonImportNode); } + depthRemaining++; } +} - function addRegionOutliningSpans(sourceFile: SourceFile, out: Push): void { - const regions: OutliningSpan[] = []; - const lineStarts = sourceFile.getLineStarts(); - for (const currentLineStart of lineStarts) { - const lineEnd = sourceFile.getLineEndOfPosition(currentLineStart); - const lineText = sourceFile.text.substring(currentLineStart, lineEnd); - const result = isRegionDelimiter(lineText); - if (!result || isInComment(sourceFile, currentLineStart)) { - continue; - } +function addRegionOutliningSpans(sourceFile: SourceFile, out: Push): void { + const regions: OutliningSpan[] = []; + const lineStarts = sourceFile.getLineStarts(); + for (const currentLineStart of lineStarts) { + const lineEnd = sourceFile.getLineEndOfPosition(currentLineStart); + const lineText = sourceFile.text.substring(currentLineStart, lineEnd); + const result = isRegionDelimiter(lineText); + if (!result || isInComment(sourceFile, currentLineStart)) { + continue; + } - if (!result[1]) { - const span = createTextSpanFromBounds(sourceFile.text.indexOf("//", currentLineStart), lineEnd); - regions.push(createOutliningSpan(span, OutliningSpanKind.Region, span, /*autoCollapse*/ false, result[2] || "#region")); - } - else { - const region = regions.pop(); - if (region) { - region.textSpan.length = lineEnd - region.textSpan.start; - region.hintSpan.length = lineEnd - region.textSpan.start; - out.push(region); - } + if (!result[1]) { + const span = createTextSpanFromBounds(sourceFile.text.indexOf("//", currentLineStart), lineEnd); + regions.push(createOutliningSpan(span, OutliningSpanKind.Region, span, /*autoCollapse*/ false, result[2] || "#region")); + } + else { + const region = regions.pop(); + if (region) { + region.textSpan.length = lineEnd - region.textSpan.start; + region.hintSpan.length = lineEnd - region.textSpan.start; + out.push(region); } } } +} - const regionDelimiterRegExp = /^#(end)?region(?:\s+(.*))?(?:\r)?$/; - function isRegionDelimiter(lineText: string) { - // We trim the leading whitespace and // without the regex since the - // multiple potential whitespace matches can make for some gnarly backtracking behavior - lineText = trimStringStart(lineText); - if (!startsWith(lineText, "\/\/")) { - return null; // eslint-disable-line no-null/no-null - } - lineText = trimString(lineText.slice(2)); - return regionDelimiterRegExp.exec(lineText); +const regionDelimiterRegExp = /^#(end)?region(?:\s+(.*))?(?:\r)?$/; +function isRegionDelimiter(lineText: string) { + // We trim the leading whitespace and // without the regex since the + // multiple potential whitespace matches can make for some gnarly backtracking behavior + lineText = trimStringStart(lineText); + if (!startsWith(lineText, "\/\/")) { + return null; // eslint-disable-line no-null/no-null } + lineText = trimString(lineText.slice(2)); + return regionDelimiterRegExp.exec(lineText); +} - function addOutliningForLeadingCommentsForPos(pos: number, sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { - const comments = getLeadingCommentRanges(sourceFile.text, pos); - if (!comments) return; - - let firstSingleLineCommentStart = -1; - let lastSingleLineCommentEnd = -1; - let singleLineCommentCount = 0; - const sourceText = sourceFile.getFullText(); - for (const { kind, pos, end } of comments) { - cancellationToken.throwIfCancellationRequested(); - switch (kind) { - case SyntaxKind.SingleLineCommentTrivia: - // never fold region delimiters into single-line comment regions - const commentText = sourceText.slice(pos, end); - if (isRegionDelimiter(commentText)) { - combineAndAddMultipleSingleLineComments(); - singleLineCommentCount = 0; - break; - } - - // For single line comments, combine consecutive ones (2 or more) into - // a single span from the start of the first till the end of the last - if (singleLineCommentCount === 0) { - firstSingleLineCommentStart = pos; - } - lastSingleLineCommentEnd = end; - singleLineCommentCount++; - break; - case SyntaxKind.MultiLineCommentTrivia: +function addOutliningForLeadingCommentsForPos(pos: number, sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { + const comments = getLeadingCommentRanges(sourceFile.text, pos); + if (!comments) return; + + let firstSingleLineCommentStart = -1; + let lastSingleLineCommentEnd = -1; + let singleLineCommentCount = 0; + const sourceText = sourceFile.getFullText(); + for (const { kind, pos, end } of comments) { + cancellationToken.throwIfCancellationRequested(); + switch (kind) { + case SyntaxKind.SingleLineCommentTrivia: + // never fold region delimiters into single-line comment regions + const commentText = sourceText.slice(pos, end); + if (isRegionDelimiter(commentText)) { combineAndAddMultipleSingleLineComments(); - out.push(createOutliningSpanFromBounds(pos, end, OutliningSpanKind.Comment)); singleLineCommentCount = 0; break; - default: - Debug.assertNever(kind); - } - } - combineAndAddMultipleSingleLineComments(); + } - function combineAndAddMultipleSingleLineComments(): void { - // Only outline spans of two or more consecutive single line comments - if (singleLineCommentCount > 1) { - out.push(createOutliningSpanFromBounds(firstSingleLineCommentStart, lastSingleLineCommentEnd, OutliningSpanKind.Comment)); - } + // For single line comments, combine consecutive ones (2 or more) into + // a single span from the start of the first till the end of the last + if (singleLineCommentCount === 0) { + firstSingleLineCommentStart = pos; + } + lastSingleLineCommentEnd = end; + singleLineCommentCount++; + break; + case SyntaxKind.MultiLineCommentTrivia: + combineAndAddMultipleSingleLineComments(); + out.push(createOutliningSpanFromBounds(pos, end, OutliningSpanKind.Comment)); + singleLineCommentCount = 0; + break; + default: + Debug.assertNever(kind); } } + combineAndAddMultipleSingleLineComments(); - function addOutliningForLeadingCommentsForNode(n: Node, sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { - if (isJsxText(n)) return; - addOutliningForLeadingCommentsForPos(n.pos, sourceFile, cancellationToken, out); + function combineAndAddMultipleSingleLineComments(): void { + // Only outline spans of two or more consecutive single line comments + if (singleLineCommentCount > 1) { + out.push(createOutliningSpanFromBounds(firstSingleLineCommentStart, lastSingleLineCommentEnd, OutliningSpanKind.Comment)); + } } +} - function createOutliningSpanFromBounds(pos: number, end: number, kind: OutliningSpanKind): OutliningSpan { - return createOutliningSpan(createTextSpanFromBounds(pos, end), kind); - } +function addOutliningForLeadingCommentsForNode(n: Node, sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { + if (isJsxText(n)) return; + addOutliningForLeadingCommentsForPos(n.pos, sourceFile, cancellationToken, out); +} - function getOutliningSpanForNode(n: Node, sourceFile: SourceFile): OutliningSpan | undefined { - switch (n.kind) { - case SyntaxKind.Block: - if (isFunctionLike(n.parent)) { - return functionSpan(n.parent, n as Block, sourceFile); - } - // Check if the block is standalone, or 'attached' to some parent statement. - // If the latter, we want to collapse the block, but consider its hint span - // to be the entire span of the parent. - switch (n.parent.kind) { - case SyntaxKind.DoStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.IfStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.CatchClause: - return spanForNode(n.parent); - case SyntaxKind.TryStatement: - // Could be the try-block, or the finally-block. - const tryStatement = n.parent as TryStatement; - if (tryStatement.tryBlock === n) { - return spanForNode(n.parent); - } - else if (tryStatement.finallyBlock === n) { - const node = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile); - if (node) return spanForNode(node); - } - // falls through - default: - // Block was a standalone block. In this case we want to only collapse - // the span of the block, independent of any parent span. - return createOutliningSpan(createTextSpanFromNode(n, sourceFile), OutliningSpanKind.Code); - } - case SyntaxKind.ModuleBlock: - return spanForNode(n.parent); - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.CaseBlock: - case SyntaxKind.TypeLiteral: - case SyntaxKind.ObjectBindingPattern: - return spanForNode(n); - case SyntaxKind.TupleType: - return spanForNode(n, /*autoCollapse*/ false, /*useFullStart*/ !isTupleTypeNode(n.parent), SyntaxKind.OpenBracketToken); - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - return spanForNodeArray((n as CaseClause | DefaultClause).statements); - case SyntaxKind.ObjectLiteralExpression: - return spanForObjectOrArrayLiteral(n); - case SyntaxKind.ArrayLiteralExpression: - return spanForObjectOrArrayLiteral(n, SyntaxKind.OpenBracketToken); - case SyntaxKind.JsxElement: - return spanForJSXElement(n as JsxElement); - case SyntaxKind.JsxFragment: - return spanForJSXFragment(n as JsxFragment); - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxOpeningElement: - return spanForJSXAttributes((n as JsxOpeningLikeElement).attributes); - case SyntaxKind.TemplateExpression: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return spanForTemplateLiteral(n as TemplateExpression | NoSubstitutionTemplateLiteral); - case SyntaxKind.ArrayBindingPattern: - return spanForNode(n, /*autoCollapse*/ false, /*useFullStart*/ !isBindingElement(n.parent), SyntaxKind.OpenBracketToken); - case SyntaxKind.ArrowFunction: - return spanForArrowFunction(n as ArrowFunction); - case SyntaxKind.CallExpression: - return spanForCallExpression(n as CallExpression); - } +function createOutliningSpanFromBounds(pos: number, end: number, kind: OutliningSpanKind): OutliningSpan { + return createOutliningSpan(createTextSpanFromBounds(pos, end), kind); +} - function spanForCallExpression(node: CallExpression): OutliningSpan | undefined { - if (!node.arguments.length) { - return undefined; +function getOutliningSpanForNode(n: Node, sourceFile: SourceFile): OutliningSpan | undefined { + switch (n.kind) { + case SyntaxKind.Block: + if (isFunctionLike(n.parent)) { + return functionSpan(n.parent, n as Block, sourceFile); } - const openToken = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile); - const closeToken = findChildOfKind(node, SyntaxKind.CloseParenToken, sourceFile); - if (!openToken || !closeToken || positionsAreOnSameLine(openToken.pos, closeToken.pos, sourceFile)) { - return undefined; + // Check if the block is standalone, or 'attached' to some parent statement. + // If the latter, we want to collapse the block, but consider its hint span + // to be the entire span of the parent. + switch (n.parent.kind) { + case SyntaxKind.DoStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.CatchClause: + return spanForNode(n.parent); + case SyntaxKind.TryStatement: + // Could be the try-block, or the finally-block. + const tryStatement = n.parent as TryStatement; + if (tryStatement.tryBlock === n) { + return spanForNode(n.parent); + } + else if (tryStatement.finallyBlock === n) { + const node = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile); + if (node) return spanForNode(node); + } + // falls through + default: + // Block was a standalone block. In this case we want to only collapse + // the span of the block, independent of any parent span. + return createOutliningSpan(createTextSpanFromNode(n, sourceFile), OutliningSpanKind.Code); } + case SyntaxKind.ModuleBlock: + return spanForNode(n.parent); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.CaseBlock: + case SyntaxKind.TypeLiteral: + case SyntaxKind.ObjectBindingPattern: + return spanForNode(n); + case SyntaxKind.TupleType: + return spanForNode(n, /*autoCollapse*/ false, /*useFullStart*/ !isTupleTypeNode(n.parent), SyntaxKind.OpenBracketToken); + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + return spanForNodeArray((n as CaseClause | DefaultClause).statements); + case SyntaxKind.ObjectLiteralExpression: + return spanForObjectOrArrayLiteral(n); + case SyntaxKind.ArrayLiteralExpression: + return spanForObjectOrArrayLiteral(n, SyntaxKind.OpenBracketToken); + case SyntaxKind.JsxElement: + return spanForJSXElement(n as JsxElement); + case SyntaxKind.JsxFragment: + return spanForJSXFragment(n as JsxFragment); + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxOpeningElement: + return spanForJSXAttributes((n as JsxOpeningLikeElement).attributes); + case SyntaxKind.TemplateExpression: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return spanForTemplateLiteral(n as TemplateExpression | NoSubstitutionTemplateLiteral); + case SyntaxKind.ArrayBindingPattern: + return spanForNode(n, /*autoCollapse*/ false, /*useFullStart*/ !isBindingElement(n.parent), SyntaxKind.OpenBracketToken); + case SyntaxKind.ArrowFunction: + return spanForArrowFunction(n as ArrowFunction); + case SyntaxKind.CallExpression: + return spanForCallExpression(n as CallExpression); + } - return spanBetweenTokens(openToken, closeToken, node, sourceFile, /*autoCollapse*/ false, /*useFullStart*/ true); + function spanForCallExpression(node: CallExpression): OutliningSpan | undefined { + if (!node.arguments.length) { + return undefined; } - - function spanForArrowFunction(node: ArrowFunction): OutliningSpan | undefined { - if (isBlock(node.body) || positionsAreOnSameLine(node.body.getFullStart(), node.body.getEnd(), sourceFile)) { - return undefined; - } - const textSpan = createTextSpanFromBounds(node.body.getFullStart(), node.body.getEnd()); - return createOutliningSpan(textSpan, OutliningSpanKind.Code, createTextSpanFromNode(node)); + const openToken = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile); + const closeToken = findChildOfKind(node, SyntaxKind.CloseParenToken, sourceFile); + if (!openToken || !closeToken || positionsAreOnSameLine(openToken.pos, closeToken.pos, sourceFile)) { + return undefined; } - function spanForJSXElement(node: JsxElement): OutliningSpan | undefined { - const textSpan = createTextSpanFromBounds(node.openingElement.getStart(sourceFile), node.closingElement.getEnd()); - const tagName = node.openingElement.tagName.getText(sourceFile); - const bannerText = "<" + tagName + ">..."; - return createOutliningSpan(textSpan, OutliningSpanKind.Code, textSpan, /*autoCollapse*/ false, bannerText); - } + return spanBetweenTokens(openToken, closeToken, node, sourceFile, /*autoCollapse*/ false, /*useFullStart*/ true); + } - function spanForJSXFragment(node: JsxFragment): OutliningSpan | undefined { - const textSpan = createTextSpanFromBounds(node.openingFragment.getStart(sourceFile), node.closingFragment.getEnd()); - const bannerText = "<>..."; - return createOutliningSpan(textSpan, OutliningSpanKind.Code, textSpan, /*autoCollapse*/ false, bannerText); + function spanForArrowFunction(node: ArrowFunction): OutliningSpan | undefined { + if (isBlock(node.body) || positionsAreOnSameLine(node.body.getFullStart(), node.body.getEnd(), sourceFile)) { + return undefined; } + const textSpan = createTextSpanFromBounds(node.body.getFullStart(), node.body.getEnd()); + return createOutliningSpan(textSpan, OutliningSpanKind.Code, createTextSpanFromNode(node)); + } - function spanForJSXAttributes(node: JsxAttributes): OutliningSpan | undefined { - if (node.properties.length === 0) { - return undefined; - } - - return createOutliningSpanFromBounds(node.getStart(sourceFile), node.getEnd(), OutliningSpanKind.Code); - } + function spanForJSXElement(node: JsxElement): OutliningSpan | undefined { + const textSpan = createTextSpanFromBounds(node.openingElement.getStart(sourceFile), node.closingElement.getEnd()); + const tagName = node.openingElement.tagName.getText(sourceFile); + const bannerText = "<" + tagName + ">..."; + return createOutliningSpan(textSpan, OutliningSpanKind.Code, textSpan, /*autoCollapse*/ false, bannerText); + } - function spanForTemplateLiteral(node: TemplateExpression | NoSubstitutionTemplateLiteral) { - if (node.kind === SyntaxKind.NoSubstitutionTemplateLiteral && node.text.length === 0) { - return undefined; - } - return createOutliningSpanFromBounds(node.getStart(sourceFile), node.getEnd(), OutliningSpanKind.Code); - } + function spanForJSXFragment(node: JsxFragment): OutliningSpan | undefined { + const textSpan = createTextSpanFromBounds(node.openingFragment.getStart(sourceFile), node.closingFragment.getEnd()); + const bannerText = "<>..."; + return createOutliningSpan(textSpan, OutliningSpanKind.Code, textSpan, /*autoCollapse*/ false, bannerText); + } - function spanForObjectOrArrayLiteral(node: Node, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken): OutliningSpan | undefined { - // If the block has no leading keywords and is inside an array literal or call expression, - // we only want to collapse the span of the block. - // Otherwise, the collapsed section will include the end of the previous line. - return spanForNode(node, /*autoCollapse*/ false, /*useFullStart*/ !isArrayLiteralExpression(node.parent) && !isCallExpression(node.parent), open); + function spanForJSXAttributes(node: JsxAttributes): OutliningSpan | undefined { + if (node.properties.length === 0) { + return undefined; } - function spanForNode(hintSpanNode: Node, autoCollapse = false, useFullStart = true, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken, close: SyntaxKind = open === SyntaxKind.OpenBraceToken ? SyntaxKind.CloseBraceToken : SyntaxKind.CloseBracketToken): OutliningSpan | undefined { - const openToken = findChildOfKind(n, open, sourceFile); - const closeToken = findChildOfKind(n, close, sourceFile); - return openToken && closeToken && spanBetweenTokens(openToken, closeToken, hintSpanNode, sourceFile, autoCollapse, useFullStart); - } + return createOutliningSpanFromBounds(node.getStart(sourceFile), node.getEnd(), OutliningSpanKind.Code); + } - function spanForNodeArray(nodeArray: NodeArray): OutliningSpan | undefined { - return nodeArray.length ? createOutliningSpan(createTextSpanFromRange(nodeArray), OutliningSpanKind.Code) : undefined; + function spanForTemplateLiteral(node: TemplateExpression | NoSubstitutionTemplateLiteral) { + if (node.kind === SyntaxKind.NoSubstitutionTemplateLiteral && node.text.length === 0) { + return undefined; } + return createOutliningSpanFromBounds(node.getStart(sourceFile), node.getEnd(), OutliningSpanKind.Code); } - function functionSpan(node: SignatureDeclaration, body: Block, sourceFile: SourceFile): OutliningSpan | undefined { - const openToken = tryGetFunctionOpenToken(node, body, sourceFile); - const closeToken = findChildOfKind(body, SyntaxKind.CloseBraceToken, sourceFile); - return openToken && closeToken && spanBetweenTokens(openToken, closeToken, node, sourceFile, /*autoCollapse*/ node.kind !== SyntaxKind.ArrowFunction); + function spanForObjectOrArrayLiteral(node: Node, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken): OutliningSpan | undefined { + // If the block has no leading keywords and is inside an array literal or call expression, + // we only want to collapse the span of the block. + // Otherwise, the collapsed section will include the end of the previous line. + return spanForNode(node, /*autoCollapse*/ false, /*useFullStart*/ !isArrayLiteralExpression(node.parent) && !isCallExpression(node.parent), open); } - function spanBetweenTokens(openToken: Node, closeToken: Node, hintSpanNode: Node, sourceFile: SourceFile, autoCollapse = false, useFullStart = true): OutliningSpan { - const textSpan = createTextSpanFromBounds(useFullStart ? openToken.getFullStart() : openToken.getStart(sourceFile), closeToken.getEnd()); - return createOutliningSpan(textSpan, OutliningSpanKind.Code, createTextSpanFromNode(hintSpanNode, sourceFile), autoCollapse); + function spanForNode(hintSpanNode: Node, autoCollapse = false, useFullStart = true, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken, close: SyntaxKind = open === SyntaxKind.OpenBraceToken ? SyntaxKind.CloseBraceToken : SyntaxKind.CloseBracketToken): OutliningSpan | undefined { + const openToken = findChildOfKind(n, open, sourceFile); + const closeToken = findChildOfKind(n, close, sourceFile); + return openToken && closeToken && spanBetweenTokens(openToken, closeToken, hintSpanNode, sourceFile, autoCollapse, useFullStart); } - function createOutliningSpan(textSpan: TextSpan, kind: OutliningSpanKind, hintSpan: TextSpan = textSpan, autoCollapse = false, bannerText = "..."): OutliningSpan { - return { textSpan, kind, hintSpan, bannerText, autoCollapse }; + function spanForNodeArray(nodeArray: NodeArray): OutliningSpan | undefined { + return nodeArray.length ? createOutliningSpan(createTextSpanFromRange(nodeArray), OutliningSpanKind.Code) : undefined; } +} - function tryGetFunctionOpenToken(node: SignatureDeclaration, body: Block, sourceFile: SourceFile): Node | undefined { - if (isNodeArrayMultiLine(node.parameters, sourceFile)) { - const openParenToken = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile); - if (openParenToken) { - return openParenToken; - } +function functionSpan(node: SignatureDeclaration, body: Block, sourceFile: SourceFile): OutliningSpan | undefined { + const openToken = tryGetFunctionOpenToken(node, body, sourceFile); + const closeToken = findChildOfKind(body, SyntaxKind.CloseBraceToken, sourceFile); + return openToken && closeToken && spanBetweenTokens(openToken, closeToken, node, sourceFile, /*autoCollapse*/ node.kind !== SyntaxKind.ArrowFunction); +} + +function spanBetweenTokens(openToken: Node, closeToken: Node, hintSpanNode: Node, sourceFile: SourceFile, autoCollapse = false, useFullStart = true): OutliningSpan { + const textSpan = createTextSpanFromBounds(useFullStart ? openToken.getFullStart() : openToken.getStart(sourceFile), closeToken.getEnd()); + return createOutliningSpan(textSpan, OutliningSpanKind.Code, createTextSpanFromNode(hintSpanNode, sourceFile), autoCollapse); +} + +function createOutliningSpan(textSpan: TextSpan, kind: OutliningSpanKind, hintSpan: TextSpan = textSpan, autoCollapse = false, bannerText = "..."): OutliningSpan { + return { textSpan, kind, hintSpan, bannerText, autoCollapse }; +} + +function tryGetFunctionOpenToken(node: SignatureDeclaration, body: Block, sourceFile: SourceFile): Node | undefined { + if (isNodeArrayMultiLine(node.parameters, sourceFile)) { + const openParenToken = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile); + if (openParenToken) { + return openParenToken; } - return findChildOfKind(body, SyntaxKind.OpenBraceToken, sourceFile); } + return findChildOfKind(body, SyntaxKind.OpenBraceToken, sourceFile); +} } diff --git a/src/services/patternMatcher.ts b/src/services/patternMatcher.ts index b0071f7124098..a130cb2b41206 100644 --- a/src/services/patternMatcher.ts +++ b/src/services/patternMatcher.ts @@ -1,593 +1,593 @@ /* @internal */ namespace ts { - // Note(cyrusn): this enum is ordered from strongest match type to weakest match type. - export enum PatternMatchKind { - exact, - prefix, - substring, - camelCase - } +// Note(cyrusn): this enum is ordered from strongest match type to weakest match type. +export enum PatternMatchKind { + exact, + prefix, + substring, + camelCase +} - // Information about a match made by the pattern matcher between a candidate and the - // search pattern. - export interface PatternMatch { - // What kind of match this was. Exact matches are better than prefix matches which are - // better than substring matches which are better than CamelCase matches. - kind: PatternMatchKind; - - // If this was a match where all constituent parts of the candidate and search pattern - // matched case sensitively or case insensitively. Case sensitive matches of the kind - // are better matches than insensitive matches. - isCaseSensitive: boolean; - } +// Information about a match made by the pattern matcher between a candidate and the +// search pattern. +export interface PatternMatch { + // What kind of match this was. Exact matches are better than prefix matches which are + // better than substring matches which are better than CamelCase matches. + kind: PatternMatchKind; + + // If this was a match where all constituent parts of the candidate and search pattern + // matched case sensitively or case insensitively. Case sensitive matches of the kind + // are better matches than insensitive matches. + isCaseSensitive: boolean; +} - // The pattern matcher maintains an internal cache of information as it is used. Therefore, - // you should not keep it around forever and should get and release the matcher appropriately - // once you no longer need it. - export interface PatternMatcher { - // Used to match a candidate against the last segment of a possibly dotted pattern. This - // is useful as a quick check to prevent having to compute a container before calling - // "getMatches". - // - // For example, if the search pattern is "ts.c.SK" and the candidate is "SyntaxKind", then - // this will return a successful match, having only tested "SK" against "SyntaxKind". At - // that point a call can be made to 'getMatches("SyntaxKind", "ts.compiler")', with the - // work to create 'ts.compiler' only being done once the first match succeeded. - getMatchForLastSegmentOfPattern(candidate: string): PatternMatch | undefined; - - // Fully checks a candidate, with an dotted container, against the search pattern. - // The candidate must match the last part of the search pattern, and the dotted container - // must match the preceding segments of the pattern. - getFullMatch(candidateContainers: readonly string[], candidate: string): PatternMatch | undefined; - - // Whether or not the pattern contained dots or not. Clients can use this to determine - // If they should call getMatches, or if getMatchesForLastSegmentOfPattern is sufficient. - patternContainsDots: boolean; - } +// The pattern matcher maintains an internal cache of information as it is used. Therefore, +// you should not keep it around forever and should get and release the matcher appropriately +// once you no longer need it. +export interface PatternMatcher { + // Used to match a candidate against the last segment of a possibly dotted pattern. This + // is useful as a quick check to prevent having to compute a container before calling + // "getMatches". + // + // For example, if the search pattern is "ts.c.SK" and the candidate is "SyntaxKind", then + // this will return a successful match, having only tested "SK" against "SyntaxKind". At + // that point a call can be made to 'getMatches("SyntaxKind", "ts.compiler")', with the + // work to create 'ts.compiler' only being done once the first match succeeded. + getMatchForLastSegmentOfPattern(candidate: string): PatternMatch | undefined; + + // Fully checks a candidate, with an dotted container, against the search pattern. + // The candidate must match the last part of the search pattern, and the dotted container + // must match the preceding segments of the pattern. + getFullMatch(candidateContainers: readonly string[], candidate: string): PatternMatch | undefined; + + // Whether or not the pattern contained dots or not. Clients can use this to determine + // If they should call getMatches, or if getMatchesForLastSegmentOfPattern is sufficient. + patternContainsDots: boolean; +} - // First we break up the pattern given by dots. Each portion of the pattern between the - // dots is a 'Segment'. The 'Segment' contains information about the entire section of - // text between the dots, as well as information about any individual 'Words' that we - // can break the segment into. A 'Word' is simply a contiguous sequence of characters - // that can appear in a typescript identifier. So "GetKeyword" would be one word, while - // "Get Keyword" would be two words. Once we have the individual 'words', we break those - // into constituent 'character spans' of interest. For example, while 'UIElement' is one - // word, it make character spans corresponding to "U", "I" and "Element". These spans - // are then used when doing camel cased matches against candidate patterns. - interface Segment { - // Information about the entire piece of text between the dots. For example, if the - // text between the dots is 'GetKeyword', then TotalTextChunk.Text will be 'GetKeyword' and - // TotalTextChunk.CharacterSpans will correspond to 'Get', 'Keyword'. - totalTextChunk: TextChunk; - - // Information about the subwords compromising the total word. For example, if the - // text between the dots is 'GetFoo KeywordBar', then the subwords will be 'GetFoo' - // and 'KeywordBar'. Those individual words will have CharacterSpans of ('Get' and - // 'Foo') and('Keyword' and 'Bar') respectively. - subWordTextChunks: TextChunk[]; - } +// First we break up the pattern given by dots. Each portion of the pattern between the +// dots is a 'Segment'. The 'Segment' contains information about the entire section of +// text between the dots, as well as information about any individual 'Words' that we +// can break the segment into. A 'Word' is simply a contiguous sequence of characters +// that can appear in a typescript identifier. So "GetKeyword" would be one word, while +// "Get Keyword" would be two words. Once we have the individual 'words', we break those +// into constituent 'character spans' of interest. For example, while 'UIElement' is one +// word, it make character spans corresponding to "U", "I" and "Element". These spans +// are then used when doing camel cased matches against candidate patterns. +interface Segment { + // Information about the entire piece of text between the dots. For example, if the + // text between the dots is 'GetKeyword', then TotalTextChunk.Text will be 'GetKeyword' and + // TotalTextChunk.CharacterSpans will correspond to 'Get', 'Keyword'. + totalTextChunk: TextChunk; + + // Information about the subwords compromising the total word. For example, if the + // text between the dots is 'GetFoo KeywordBar', then the subwords will be 'GetFoo' + // and 'KeywordBar'. Those individual words will have CharacterSpans of ('Get' and + // 'Foo') and('Keyword' and 'Bar') respectively. + subWordTextChunks: TextChunk[]; +} - // Information about a chunk of text from the pattern. The chunk is a piece of text, with - // cached information about the character spans within in. Character spans are used for - // camel case matching. - interface TextChunk { - // The text of the chunk. This should be a contiguous sequence of character that could - // occur in a symbol name. - text: string; - - // The text of a chunk in lower case. Cached because it is needed often to check for - // case insensitive matches. - textLowerCase: string; - - // Whether or not this chunk is entirely lowercase. We have different rules when searching - // for something entirely lowercase or not. - isLowerCase: boolean; - - // The spans in this text chunk that we think are of interest and should be matched - // independently. For example, if the chunk is for "UIElement" the the spans of interest - // correspond to "U", "I" and "Element". If "UIElement" isn't found as an exact, prefix. - // or substring match, then the character spans will be used to attempt a camel case match. - characterSpans: TextSpan[]; - } +// Information about a chunk of text from the pattern. The chunk is a piece of text, with +// cached information about the character spans within in. Character spans are used for +// camel case matching. +interface TextChunk { + // The text of the chunk. This should be a contiguous sequence of character that could + // occur in a symbol name. + text: string; + + // The text of a chunk in lower case. Cached because it is needed often to check for + // case insensitive matches. + textLowerCase: string; + + // Whether or not this chunk is entirely lowercase. We have different rules when searching + // for something entirely lowercase or not. + isLowerCase: boolean; + + // The spans in this text chunk that we think are of interest and should be matched + // independently. For example, if the chunk is for "UIElement" the the spans of interest + // correspond to "U", "I" and "Element". If "UIElement" isn't found as an exact, prefix. + // or substring match, then the character spans will be used to attempt a camel case match. + characterSpans: TextSpan[]; +} - function createPatternMatch(kind: PatternMatchKind, isCaseSensitive: boolean): PatternMatch { - return { - kind, - isCaseSensitive - }; - } +function createPatternMatch(kind: PatternMatchKind, isCaseSensitive: boolean): PatternMatch { + return { + kind, + isCaseSensitive + }; +} - export function createPatternMatcher(pattern: string): PatternMatcher | undefined { - // We'll often see the same candidate string many times when searching (For example, when - // we see the name of a module that is used everywhere, or the name of an overload). As - // such, we cache the information we compute about the candidate for the life of this - // pattern matcher so we don't have to compute it multiple times. - const stringToWordSpans = new Map(); - - const dotSeparatedSegments = pattern.trim().split(".").map(p => createSegment(p.trim())); - // A segment is considered invalid if we couldn't find any words in it. - if (dotSeparatedSegments.some(segment => !segment.subWordTextChunks.length)) return undefined; - - return { - getFullMatch: (containers, candidate) => getFullMatch(containers, candidate, dotSeparatedSegments, stringToWordSpans), - getMatchForLastSegmentOfPattern: candidate => matchSegment(candidate, last(dotSeparatedSegments), stringToWordSpans), - patternContainsDots: dotSeparatedSegments.length > 1 - }; - } +export function createPatternMatcher(pattern: string): PatternMatcher | undefined { + // We'll often see the same candidate string many times when searching (For example, when + // we see the name of a module that is used everywhere, or the name of an overload). As + // such, we cache the information we compute about the candidate for the life of this + // pattern matcher so we don't have to compute it multiple times. + const stringToWordSpans = new Map(); + + const dotSeparatedSegments = pattern.trim().split(".").map(p => createSegment(p.trim())); + // A segment is considered invalid if we couldn't find any words in it. + if (dotSeparatedSegments.some(segment => !segment.subWordTextChunks.length)) return undefined; + + return { + getFullMatch: (containers, candidate) => getFullMatch(containers, candidate, dotSeparatedSegments, stringToWordSpans), + getMatchForLastSegmentOfPattern: candidate => matchSegment(candidate, last(dotSeparatedSegments), stringToWordSpans), + patternContainsDots: dotSeparatedSegments.length > 1 + }; +} - function getFullMatch(candidateContainers: readonly string[], candidate: string, dotSeparatedSegments: readonly Segment[], stringToWordSpans: ESMap): PatternMatch | undefined { - // First, check that the last part of the dot separated pattern matches the name of the - // candidate. If not, then there's no point in proceeding and doing the more - // expensive work. - const candidateMatch = matchSegment(candidate, last(dotSeparatedSegments), stringToWordSpans); - if (!candidateMatch) { - return undefined; - } +function getFullMatch(candidateContainers: readonly string[], candidate: string, dotSeparatedSegments: readonly Segment[], stringToWordSpans: ESMap): PatternMatch | undefined { + // First, check that the last part of the dot separated pattern matches the name of the + // candidate. If not, then there's no point in proceeding and doing the more + // expensive work. + const candidateMatch = matchSegment(candidate, last(dotSeparatedSegments), stringToWordSpans); + if (!candidateMatch) { + return undefined; + } - // -1 because the last part was checked against the name, and only the rest - // of the parts are checked against the container. - if (dotSeparatedSegments.length - 1 > candidateContainers.length) { - // There weren't enough container parts to match against the pattern parts. - // So this definitely doesn't match. - return undefined; - } + // -1 because the last part was checked against the name, and only the rest + // of the parts are checked against the container. + if (dotSeparatedSegments.length - 1 > candidateContainers.length) { + // There weren't enough container parts to match against the pattern parts. + // So this definitely doesn't match. + return undefined; + } - let bestMatch: PatternMatch | undefined; - for (let i = dotSeparatedSegments.length - 2, j = candidateContainers.length - 1; - i >= 0; - i -= 1, j -= 1) { - bestMatch = betterMatch(bestMatch, matchSegment(candidateContainers[j], dotSeparatedSegments[i], stringToWordSpans)); - } - return bestMatch; + let bestMatch: PatternMatch | undefined; + for (let i = dotSeparatedSegments.length - 2, j = candidateContainers.length - 1; + i >= 0; + i -= 1, j -= 1) { + bestMatch = betterMatch(bestMatch, matchSegment(candidateContainers[j], dotSeparatedSegments[i], stringToWordSpans)); } + return bestMatch; +} - function getWordSpans(word: string, stringToWordSpans: ESMap): TextSpan[] { - let spans = stringToWordSpans.get(word); - if (!spans) { - stringToWordSpans.set(word, spans = breakIntoWordSpans(word)); - } - return spans; +function getWordSpans(word: string, stringToWordSpans: ESMap): TextSpan[] { + let spans = stringToWordSpans.get(word); + if (!spans) { + stringToWordSpans.set(word, spans = breakIntoWordSpans(word)); } + return spans; +} - function matchTextChunk(candidate: string, chunk: TextChunk, stringToWordSpans: ESMap): PatternMatch | undefined { - const index = indexOfIgnoringCase(candidate, chunk.textLowerCase); - if (index === 0) { - // a) Check if the word is a prefix of the candidate, in a case insensitive or - // sensitive manner. If it does, return that there was an exact match if the word and candidate are the same length, else a prefix match. - return createPatternMatch(chunk.text.length === candidate.length ? PatternMatchKind.exact : PatternMatchKind.prefix, /*isCaseSensitive:*/ startsWith(candidate, chunk.text)); - } +function matchTextChunk(candidate: string, chunk: TextChunk, stringToWordSpans: ESMap): PatternMatch | undefined { + const index = indexOfIgnoringCase(candidate, chunk.textLowerCase); + if (index === 0) { + // a) Check if the word is a prefix of the candidate, in a case insensitive or + // sensitive manner. If it does, return that there was an exact match if the word and candidate are the same length, else a prefix match. + return createPatternMatch(chunk.text.length === candidate.length ? PatternMatchKind.exact : PatternMatchKind.prefix, /*isCaseSensitive:*/ startsWith(candidate, chunk.text)); + } - if (chunk.isLowerCase) { - if (index === -1) return undefined; - // b) If the part is entirely lowercase, then check if it is contained anywhere in the - // candidate in a case insensitive manner. If so, return that there was a substring - // match. - // - // Note: We only have a substring match if the lowercase part is prefix match of some - // word part. That way we don't match something like 'Class' when the user types 'a'. - // But we would match 'FooAttribute' (since 'Attribute' starts with 'a'). - const wordSpans = getWordSpans(candidate, stringToWordSpans); - for (const span of wordSpans) { - if (partStartsWith(candidate, span, chunk.text, /*ignoreCase:*/ true)) { - return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ partStartsWith(candidate, span, chunk.text, /*ignoreCase:*/ false)); - } - } - // c) Is the pattern a substring of the candidate starting on one of the candidate's word boundaries? - // We could check every character boundary start of the candidate for the pattern. However, that's - // an m * n operation in the wost case. Instead, find the first instance of the pattern - // substring, and see if it starts on a capital letter. It seems unlikely that the user will try to - // filter the list based on a substring that starts on a capital letter and also with a lowercase one. - // (Pattern: fogbar, Candidate: quuxfogbarFogBar). - if (chunk.text.length < candidate.length && isUpperCaseLetter(candidate.charCodeAt(index))) { - return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ false); + if (chunk.isLowerCase) { + if (index === -1) return undefined; + // b) If the part is entirely lowercase, then check if it is contained anywhere in the + // candidate in a case insensitive manner. If so, return that there was a substring + // match. + // + // Note: We only have a substring match if the lowercase part is prefix match of some + // word part. That way we don't match something like 'Class' when the user types 'a'. + // But we would match 'FooAttribute' (since 'Attribute' starts with 'a'). + const wordSpans = getWordSpans(candidate, stringToWordSpans); + for (const span of wordSpans) { + if (partStartsWith(candidate, span, chunk.text, /*ignoreCase:*/ true)) { + return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ partStartsWith(candidate, span, chunk.text, /*ignoreCase:*/ false)); } } - else { - // d) If the part was not entirely lowercase, then check if it is contained in the - // candidate in a case *sensitive* manner. If so, return that there was a substring - // match. - if (candidate.indexOf(chunk.text) > 0) { - return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ true); - } - // e) If the part was not entirely lowercase, then attempt a camel cased match as well. - if (chunk.characterSpans.length > 0) { - const candidateParts = getWordSpans(candidate, stringToWordSpans); - const isCaseSensitive = tryCamelCaseMatch(candidate, candidateParts, chunk, /*ignoreCase:*/ false) ? true - : tryCamelCaseMatch(candidate, candidateParts, chunk, /*ignoreCase:*/ true) ? false : undefined; - if (isCaseSensitive !== undefined) { - return createPatternMatch(PatternMatchKind.camelCase, isCaseSensitive); - } - } + // c) Is the pattern a substring of the candidate starting on one of the candidate's word boundaries? + // We could check every character boundary start of the candidate for the pattern. However, that's + // an m * n operation in the wost case. Instead, find the first instance of the pattern + // substring, and see if it starts on a capital letter. It seems unlikely that the user will try to + // filter the list based on a substring that starts on a capital letter and also with a lowercase one. + // (Pattern: fogbar, Candidate: quuxfogbarFogBar). + if (chunk.text.length < candidate.length && isUpperCaseLetter(candidate.charCodeAt(index))) { + return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ false); } } - - function matchSegment(candidate: string, segment: Segment, stringToWordSpans: ESMap): PatternMatch | undefined { - // First check if the segment matches as is. This is also useful if the segment contains - // characters we would normally strip when splitting into parts that we also may want to - // match in the candidate. For example if the segment is "@int" and the candidate is - // "@int", then that will show up as an exact match here. - // - // Note: if the segment contains a space or an asterisk then we must assume that it's a - // multi-word segment. - if (every(segment.totalTextChunk.text, ch => ch !== CharacterCodes.space && ch !== CharacterCodes.asterisk)) { - const match = matchTextChunk(candidate, segment.totalTextChunk, stringToWordSpans); - if (match) return match; + else { + // d) If the part was not entirely lowercase, then check if it is contained in the + // candidate in a case *sensitive* manner. If so, return that there was a substring + // match. + if (candidate.indexOf(chunk.text) > 0) { + return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ true); } - - // The logic for pattern matching is now as follows: - // - // 1) Break the segment passed in into words. Breaking is rather simple and a - // good way to think about it that if gives you all the individual alphanumeric words - // of the pattern. - // - // 2) For each word try to match the word against the candidate value. - // - // 3) Matching is as follows: - // - // a) Check if the word is a prefix of the candidate, in a case insensitive or - // sensitive manner. If it does, return that there was an exact match if the word and candidate are the same length, else a prefix match. - // - // If the word is entirely lowercase: - // b) Then check if it is contained anywhere in the - // candidate in a case insensitive manner. If so, return that there was a substring - // match. - // - // Note: We only have a substring match if the lowercase part is prefix match of - // some word part. That way we don't match something like 'Class' when the user - // types 'a'. But we would match 'FooAttribute' (since 'Attribute' starts with - // 'a'). - // - // c) The word is all lower case. Is it a case insensitive substring of the candidate starting - // on a part boundary of the candidate? - // - // Else: - // d) If the word was not entirely lowercase, then check if it is contained in the - // candidate in a case *sensitive* manner. If so, return that there was a substring - // match. - // - // e) If the word was not entirely lowercase, then attempt a camel cased match as - // well. - // - // Only if all words have some sort of match is the pattern considered matched. - - const subWordTextChunks = segment.subWordTextChunks; - let bestMatch: PatternMatch | undefined; - for (const subWordTextChunk of subWordTextChunks) { - bestMatch = betterMatch(bestMatch, matchTextChunk(candidate, subWordTextChunk, stringToWordSpans)); + // e) If the part was not entirely lowercase, then attempt a camel cased match as well. + if (chunk.characterSpans.length > 0) { + const candidateParts = getWordSpans(candidate, stringToWordSpans); + const isCaseSensitive = tryCamelCaseMatch(candidate, candidateParts, chunk, /*ignoreCase:*/ false) ? true + : tryCamelCaseMatch(candidate, candidateParts, chunk, /*ignoreCase:*/ true) ? false : undefined; + if (isCaseSensitive !== undefined) { + return createPatternMatch(PatternMatchKind.camelCase, isCaseSensitive); + } } - return bestMatch; } +} - function betterMatch(a: PatternMatch | undefined, b: PatternMatch | undefined): PatternMatch | undefined { - return min(a, b, compareMatches); - } - function compareMatches(a: PatternMatch | undefined, b: PatternMatch | undefined): Comparison { - return a === undefined ? Comparison.GreaterThan : b === undefined ? Comparison.LessThan - : compareValues(a.kind, b.kind) || compareBooleans(!a.isCaseSensitive, !b.isCaseSensitive); - } +function matchSegment(candidate: string, segment: Segment, stringToWordSpans: ESMap): PatternMatch | undefined { + // First check if the segment matches as is. This is also useful if the segment contains + // characters we would normally strip when splitting into parts that we also may want to + // match in the candidate. For example if the segment is "@int" and the candidate is + // "@int", then that will show up as an exact match here. + // + // Note: if the segment contains a space or an asterisk then we must assume that it's a + // multi-word segment. + if (every(segment.totalTextChunk.text, ch => ch !== CharacterCodes.space && ch !== CharacterCodes.asterisk)) { + const match = matchTextChunk(candidate, segment.totalTextChunk, stringToWordSpans); + if (match) return match; + } + + // The logic for pattern matching is now as follows: + // + // 1) Break the segment passed in into words. Breaking is rather simple and a + // good way to think about it that if gives you all the individual alphanumeric words + // of the pattern. + // + // 2) For each word try to match the word against the candidate value. + // + // 3) Matching is as follows: + // + // a) Check if the word is a prefix of the candidate, in a case insensitive or + // sensitive manner. If it does, return that there was an exact match if the word and candidate are the same length, else a prefix match. + // + // If the word is entirely lowercase: + // b) Then check if it is contained anywhere in the + // candidate in a case insensitive manner. If so, return that there was a substring + // match. + // + // Note: We only have a substring match if the lowercase part is prefix match of + // some word part. That way we don't match something like 'Class' when the user + // types 'a'. But we would match 'FooAttribute' (since 'Attribute' starts with + // 'a'). + // + // c) The word is all lower case. Is it a case insensitive substring of the candidate starting + // on a part boundary of the candidate? + // + // Else: + // d) If the word was not entirely lowercase, then check if it is contained in the + // candidate in a case *sensitive* manner. If so, return that there was a substring + // match. + // + // e) If the word was not entirely lowercase, then attempt a camel cased match as + // well. + // + // Only if all words have some sort of match is the pattern considered matched. + + const subWordTextChunks = segment.subWordTextChunks; + let bestMatch: PatternMatch | undefined; + for (const subWordTextChunk of subWordTextChunks) { + bestMatch = betterMatch(bestMatch, matchTextChunk(candidate, subWordTextChunk, stringToWordSpans)); + } + return bestMatch; +} - function partStartsWith(candidate: string, candidateSpan: TextSpan, pattern: string, ignoreCase: boolean, patternSpan: TextSpan = { start: 0, length: pattern.length }): boolean { - return patternSpan.length <= candidateSpan.length // If pattern part is longer than the candidate part there can never be a match. - && everyInRange(0, patternSpan.length, i => equalChars(pattern.charCodeAt(patternSpan.start + i), candidate.charCodeAt(candidateSpan.start + i), ignoreCase)); - } +function betterMatch(a: PatternMatch | undefined, b: PatternMatch | undefined): PatternMatch | undefined { + return min(a, b, compareMatches); +} +function compareMatches(a: PatternMatch | undefined, b: PatternMatch | undefined): Comparison { + return a === undefined ? Comparison.GreaterThan : b === undefined ? Comparison.LessThan + : compareValues(a.kind, b.kind) || compareBooleans(!a.isCaseSensitive, !b.isCaseSensitive); +} - function equalChars(ch1: number, ch2: number, ignoreCase: boolean): boolean { - return ignoreCase ? toLowerCase(ch1) === toLowerCase(ch2) : ch1 === ch2; - } +function partStartsWith(candidate: string, candidateSpan: TextSpan, pattern: string, ignoreCase: boolean, patternSpan: TextSpan = { start: 0, length: pattern.length }): boolean { + return patternSpan.length <= candidateSpan.length // If pattern part is longer than the candidate part there can never be a match. + && everyInRange(0, patternSpan.length, i => equalChars(pattern.charCodeAt(patternSpan.start + i), candidate.charCodeAt(candidateSpan.start + i), ignoreCase)); +} - function tryCamelCaseMatch(candidate: string, candidateParts: TextSpan[], chunk: TextChunk, ignoreCase: boolean): boolean { - const chunkCharacterSpans = chunk.characterSpans; +function equalChars(ch1: number, ch2: number, ignoreCase: boolean): boolean { + return ignoreCase ? toLowerCase(ch1) === toLowerCase(ch2) : ch1 === ch2; +} - // Note: we may have more pattern parts than candidate parts. This is because multiple - // pattern parts may match a candidate part. For example "SiUI" against "SimpleUI". - // We'll have 3 pattern parts Si/U/I against two candidate parts Simple/UI. However, U - // and I will both match in UI. +function tryCamelCaseMatch(candidate: string, candidateParts: TextSpan[], chunk: TextChunk, ignoreCase: boolean): boolean { + const chunkCharacterSpans = chunk.characterSpans; - let currentCandidate = 0; - let currentChunkSpan = 0; - let firstMatch: number | undefined; - let contiguous: boolean | undefined; + // Note: we may have more pattern parts than candidate parts. This is because multiple + // pattern parts may match a candidate part. For example "SiUI" against "SimpleUI". + // We'll have 3 pattern parts Si/U/I against two candidate parts Simple/UI. However, U + // and I will both match in UI. - while (true) { - // Let's consider our termination cases - if (currentChunkSpan === chunkCharacterSpans.length) { - return true; - } - else if (currentCandidate === candidateParts.length) { - // No match, since we still have more of the pattern to hit - return false; - } + let currentCandidate = 0; + let currentChunkSpan = 0; + let firstMatch: number | undefined; + let contiguous: boolean | undefined; - let candidatePart = candidateParts[currentCandidate]; - let gotOneMatchThisCandidate = false; - - // Consider the case of matching SiUI against SimpleUIElement. The candidate parts - // will be Simple/UI/Element, and the pattern parts will be Si/U/I. We'll match 'Si' - // against 'Simple' first. Then we'll match 'U' against 'UI'. However, we want to - // still keep matching pattern parts against that candidate part. - for (; currentChunkSpan < chunkCharacterSpans.length; currentChunkSpan++) { - const chunkCharacterSpan = chunkCharacterSpans[currentChunkSpan]; - - if (gotOneMatchThisCandidate) { - // We've already gotten one pattern part match in this candidate. We will - // only continue trying to consumer pattern parts if the last part and this - // part are both upper case. - if (!isUpperCaseLetter(chunk.text.charCodeAt(chunkCharacterSpans[currentChunkSpan - 1].start)) || - !isUpperCaseLetter(chunk.text.charCodeAt(chunkCharacterSpans[currentChunkSpan].start))) { - break; - } - } + while (true) { + // Let's consider our termination cases + if (currentChunkSpan === chunkCharacterSpans.length) { + return true; + } + else if (currentCandidate === candidateParts.length) { + // No match, since we still have more of the pattern to hit + return false; + } - if (!partStartsWith(candidate, candidatePart, chunk.text, ignoreCase, chunkCharacterSpan)) { + let candidatePart = candidateParts[currentCandidate]; + let gotOneMatchThisCandidate = false; + + // Consider the case of matching SiUI against SimpleUIElement. The candidate parts + // will be Simple/UI/Element, and the pattern parts will be Si/U/I. We'll match 'Si' + // against 'Simple' first. Then we'll match 'U' against 'UI'. However, we want to + // still keep matching pattern parts against that candidate part. + for (; currentChunkSpan < chunkCharacterSpans.length; currentChunkSpan++) { + const chunkCharacterSpan = chunkCharacterSpans[currentChunkSpan]; + + if (gotOneMatchThisCandidate) { + // We've already gotten one pattern part match in this candidate. We will + // only continue trying to consumer pattern parts if the last part and this + // part are both upper case. + if (!isUpperCaseLetter(chunk.text.charCodeAt(chunkCharacterSpans[currentChunkSpan - 1].start)) || + !isUpperCaseLetter(chunk.text.charCodeAt(chunkCharacterSpans[currentChunkSpan].start))) { break; } + } - gotOneMatchThisCandidate = true; + if (!partStartsWith(candidate, candidatePart, chunk.text, ignoreCase, chunkCharacterSpan)) { + break; + } - firstMatch = firstMatch === undefined ? currentCandidate : firstMatch; + gotOneMatchThisCandidate = true; - // If we were contiguous, then keep that value. If we weren't, then keep that - // value. If we don't know, then set the value to 'true' as an initial match is - // obviously contiguous. - contiguous = contiguous === undefined ? true : contiguous; + firstMatch = firstMatch === undefined ? currentCandidate : firstMatch; - candidatePart = createTextSpan(candidatePart.start + chunkCharacterSpan.length, candidatePart.length - chunkCharacterSpan.length); - } + // If we were contiguous, then keep that value. If we weren't, then keep that + // value. If we don't know, then set the value to 'true' as an initial match is + // obviously contiguous. + contiguous = contiguous === undefined ? true : contiguous; - // Check if we matched anything at all. If we didn't, then we need to unset the - // contiguous bit if we currently had it set. - // If we haven't set the bit yet, then that means we haven't matched anything so - // far, and we don't want to change that. - if (!gotOneMatchThisCandidate && contiguous !== undefined) { - contiguous = false; - } + candidatePart = createTextSpan(candidatePart.start + chunkCharacterSpan.length, candidatePart.length - chunkCharacterSpan.length); + } - // Move onto the next candidate. - currentCandidate++; + // Check if we matched anything at all. If we didn't, then we need to unset the + // contiguous bit if we currently had it set. + // If we haven't set the bit yet, then that means we haven't matched anything so + // far, and we don't want to change that. + if (!gotOneMatchThisCandidate && contiguous !== undefined) { + contiguous = false; } - } - function createSegment(text: string): Segment { - return { - totalTextChunk: createTextChunk(text), - subWordTextChunks: breakPatternIntoTextChunks(text) - }; + // Move onto the next candidate. + currentCandidate++; } +} - function isUpperCaseLetter(ch: number) { - // Fast check for the ascii range. - if (ch >= CharacterCodes.A && ch <= CharacterCodes.Z) { - return true; - } - - if (ch < CharacterCodes.maxAsciiCharacter || !isUnicodeIdentifierStart(ch, ScriptTarget.Latest)) { - return false; - } +function createSegment(text: string): Segment { + return { + totalTextChunk: createTextChunk(text), + subWordTextChunks: breakPatternIntoTextChunks(text) + }; +} - // TODO: find a way to determine this for any unicode characters in a - // non-allocating manner. - const str = String.fromCharCode(ch); - return str === str.toUpperCase(); +function isUpperCaseLetter(ch: number) { + // Fast check for the ascii range. + if (ch >= CharacterCodes.A && ch <= CharacterCodes.Z) { + return true; } - function isLowerCaseLetter(ch: number) { - // Fast check for the ascii range. - if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) { - return true; - } + if (ch < CharacterCodes.maxAsciiCharacter || !isUnicodeIdentifierStart(ch, ScriptTarget.Latest)) { + return false; + } - if (ch < CharacterCodes.maxAsciiCharacter || !isUnicodeIdentifierStart(ch, ScriptTarget.Latest)) { - return false; - } + // TODO: find a way to determine this for any unicode characters in a + // non-allocating manner. + const str = String.fromCharCode(ch); + return str === str.toUpperCase(); +} +function isLowerCaseLetter(ch: number) { + // Fast check for the ascii range. + if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) { + return true; + } - // TODO: find a way to determine this for any unicode characters in a - // non-allocating manner. - const str = String.fromCharCode(ch); - return str === str.toLowerCase(); + if (ch < CharacterCodes.maxAsciiCharacter || !isUnicodeIdentifierStart(ch, ScriptTarget.Latest)) { + return false; } - // Assumes 'value' is already lowercase. - function indexOfIgnoringCase(str: string, value: string): number { - const n = str.length - value.length; - for (let start = 0; start <= n; start++) { - if (every(value, (valueChar, i) => toLowerCase(str.charCodeAt(i + start)) === valueChar)) { - return start; - } - } - return -1; - } + // TODO: find a way to determine this for any unicode characters in a + // non-allocating manner. + const str = String.fromCharCode(ch); + return str === str.toLowerCase(); +} - function toLowerCase(ch: number): number { - // Fast convert for the ascii range. - if (ch >= CharacterCodes.A && ch <= CharacterCodes.Z) { - return CharacterCodes.a + (ch - CharacterCodes.A); +// Assumes 'value' is already lowercase. +function indexOfIgnoringCase(str: string, value: string): number { + const n = str.length - value.length; + for (let start = 0; start <= n; start++) { + if (every(value, (valueChar, i) => toLowerCase(str.charCodeAt(i + start)) === valueChar)) { + return start; } + } - if (ch < CharacterCodes.maxAsciiCharacter) { - return ch; - } + return -1; +} - // TODO: find a way to compute this for any unicode characters in a - // non-allocating manner. - return String.fromCharCode(ch).toLowerCase().charCodeAt(0); +function toLowerCase(ch: number): number { + // Fast convert for the ascii range. + if (ch >= CharacterCodes.A && ch <= CharacterCodes.Z) { + return CharacterCodes.a + (ch - CharacterCodes.A); } - function isDigit(ch: number) { - // TODO(cyrusn): Find a way to support this for unicode digits. - return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; + if (ch < CharacterCodes.maxAsciiCharacter) { + return ch; } - function isWordChar(ch: number) { - return isUpperCaseLetter(ch) || isLowerCaseLetter(ch) || isDigit(ch) || ch === CharacterCodes._ || ch === CharacterCodes.$; - } + // TODO: find a way to compute this for any unicode characters in a + // non-allocating manner. + return String.fromCharCode(ch).toLowerCase().charCodeAt(0); +} - function breakPatternIntoTextChunks(pattern: string): TextChunk[] { - const result: TextChunk[] = []; - let wordStart = 0; - let wordLength = 0; +function isDigit(ch: number) { + // TODO(cyrusn): Find a way to support this for unicode digits. + return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; +} - for (let i = 0; i < pattern.length; i++) { - const ch = pattern.charCodeAt(i); - if (isWordChar(ch)) { - if (wordLength === 0) { - wordStart = i; - } - wordLength++; - } - else { - if (wordLength > 0) { - result.push(createTextChunk(pattern.substr(wordStart, wordLength))); - wordLength = 0; - } +function isWordChar(ch: number) { + return isUpperCaseLetter(ch) || isLowerCaseLetter(ch) || isDigit(ch) || ch === CharacterCodes._ || ch === CharacterCodes.$; +} + +function breakPatternIntoTextChunks(pattern: string): TextChunk[] { + const result: TextChunk[] = []; + let wordStart = 0; + let wordLength = 0; + + for (let i = 0; i < pattern.length; i++) { + const ch = pattern.charCodeAt(i); + if (isWordChar(ch)) { + if (wordLength === 0) { + wordStart = i; } + wordLength++; } - - if (wordLength > 0) { - result.push(createTextChunk(pattern.substr(wordStart, wordLength))); + else { + if (wordLength > 0) { + result.push(createTextChunk(pattern.substr(wordStart, wordLength))); + wordLength = 0; + } } - - return result; } - function createTextChunk(text: string): TextChunk { - const textLowerCase = text.toLowerCase(); - return { - text, - textLowerCase, - isLowerCase: text === textLowerCase, - characterSpans: breakIntoCharacterSpans(text) - }; + if (wordLength > 0) { + result.push(createTextChunk(pattern.substr(wordStart, wordLength))); } - export function breakIntoCharacterSpans(identifier: string): TextSpan[] { - return breakIntoSpans(identifier, /*word:*/ false); - } + return result; +} - export function breakIntoWordSpans(identifier: string): TextSpan[] { - return breakIntoSpans(identifier, /*word:*/ true); - } +function createTextChunk(text: string): TextChunk { + const textLowerCase = text.toLowerCase(); + return { + text, + textLowerCase, + isLowerCase: text === textLowerCase, + characterSpans: breakIntoCharacterSpans(text) + }; +} - function breakIntoSpans(identifier: string, word: boolean): TextSpan[] { - const result: TextSpan[] = []; +export function breakIntoCharacterSpans(identifier: string): TextSpan[] { + return breakIntoSpans(identifier, /*word:*/ false); +} - let wordStart = 0; - for (let i = 1; i < identifier.length; i++) { - const lastIsDigit = isDigit(identifier.charCodeAt(i - 1)); - const currentIsDigit = isDigit(identifier.charCodeAt(i)); +export function breakIntoWordSpans(identifier: string): TextSpan[] { + return breakIntoSpans(identifier, /*word:*/ true); +} - const hasTransitionFromLowerToUpper = transitionFromLowerToUpper(identifier, word, i); - const hasTransitionFromUpperToLower = word && transitionFromUpperToLower(identifier, i, wordStart); +function breakIntoSpans(identifier: string, word: boolean): TextSpan[] { + const result: TextSpan[] = []; - if (charIsPunctuation(identifier.charCodeAt(i - 1)) || - charIsPunctuation(identifier.charCodeAt(i)) || - lastIsDigit !== currentIsDigit || - hasTransitionFromLowerToUpper || - hasTransitionFromUpperToLower) { + let wordStart = 0; + for (let i = 1; i < identifier.length; i++) { + const lastIsDigit = isDigit(identifier.charCodeAt(i - 1)); + const currentIsDigit = isDigit(identifier.charCodeAt(i)); - if (!isAllPunctuation(identifier, wordStart, i)) { - result.push(createTextSpan(wordStart, i - wordStart)); - } + const hasTransitionFromLowerToUpper = transitionFromLowerToUpper(identifier, word, i); + const hasTransitionFromUpperToLower = word && transitionFromUpperToLower(identifier, i, wordStart); - wordStart = i; + if (charIsPunctuation(identifier.charCodeAt(i - 1)) || + charIsPunctuation(identifier.charCodeAt(i)) || + lastIsDigit !== currentIsDigit || + hasTransitionFromLowerToUpper || + hasTransitionFromUpperToLower) { + + if (!isAllPunctuation(identifier, wordStart, i)) { + result.push(createTextSpan(wordStart, i - wordStart)); } - } - if (!isAllPunctuation(identifier, wordStart, identifier.length)) { - result.push(createTextSpan(wordStart, identifier.length - wordStart)); + wordStart = i; } + } - return result; + if (!isAllPunctuation(identifier, wordStart, identifier.length)) { + result.push(createTextSpan(wordStart, identifier.length - wordStart)); } - function charIsPunctuation(ch: number) { - switch (ch) { - case CharacterCodes.exclamation: - case CharacterCodes.doubleQuote: - case CharacterCodes.hash: - case CharacterCodes.percent: - case CharacterCodes.ampersand: - case CharacterCodes.singleQuote: - case CharacterCodes.openParen: - case CharacterCodes.closeParen: - case CharacterCodes.asterisk: - case CharacterCodes.comma: - case CharacterCodes.minus: - case CharacterCodes.dot: - case CharacterCodes.slash: - case CharacterCodes.colon: - case CharacterCodes.semicolon: - case CharacterCodes.question: - case CharacterCodes.at: - case CharacterCodes.openBracket: - case CharacterCodes.backslash: - case CharacterCodes.closeBracket: - case CharacterCodes._: - case CharacterCodes.openBrace: - case CharacterCodes.closeBrace: - return true; - } + return result; +} - return false; +function charIsPunctuation(ch: number) { + switch (ch) { + case CharacterCodes.exclamation: + case CharacterCodes.doubleQuote: + case CharacterCodes.hash: + case CharacterCodes.percent: + case CharacterCodes.ampersand: + case CharacterCodes.singleQuote: + case CharacterCodes.openParen: + case CharacterCodes.closeParen: + case CharacterCodes.asterisk: + case CharacterCodes.comma: + case CharacterCodes.minus: + case CharacterCodes.dot: + case CharacterCodes.slash: + case CharacterCodes.colon: + case CharacterCodes.semicolon: + case CharacterCodes.question: + case CharacterCodes.at: + case CharacterCodes.openBracket: + case CharacterCodes.backslash: + case CharacterCodes.closeBracket: + case CharacterCodes._: + case CharacterCodes.openBrace: + case CharacterCodes.closeBrace: + return true; } - function isAllPunctuation(identifier: string, start: number, end: number): boolean { - return every(identifier, ch => charIsPunctuation(ch) && ch !== CharacterCodes._, start, end); - } + return false; +} - function transitionFromUpperToLower(identifier: string, index: number, wordStart: number): boolean { - // Cases this supports: - // 1) IDisposable -> I, Disposable - // 2) UIElement -> UI, Element - // 3) HTMLDocument -> HTML, Document - // - // etc. - // We have a transition from an upper to a lower letter here. But we only - // want to break if all the letters that preceded are uppercase. i.e. if we - // have "Foo" we don't want to break that into "F, oo". But if we have - // "IFoo" or "UIFoo", then we want to break that into "I, Foo" and "UI, - // Foo". i.e. the last uppercase letter belongs to the lowercase letters - // that follows. Note: this will make the following not split properly: - // "HELLOthere". However, these sorts of names do not show up in .Net - // programs. - return index !== wordStart - && index + 1 < identifier.length - && isUpperCaseLetter(identifier.charCodeAt(index)) - && isLowerCaseLetter(identifier.charCodeAt(index + 1)) - && every(identifier, isUpperCaseLetter, wordStart, index); - } +function isAllPunctuation(identifier: string, start: number, end: number): boolean { + return every(identifier, ch => charIsPunctuation(ch) && ch !== CharacterCodes._, start, end); +} - function transitionFromLowerToUpper(identifier: string, word: boolean, index: number): boolean { - const lastIsUpper = isUpperCaseLetter(identifier.charCodeAt(index - 1)); - const currentIsUpper = isUpperCaseLetter(identifier.charCodeAt(index)); +function transitionFromUpperToLower(identifier: string, index: number, wordStart: number): boolean { + // Cases this supports: + // 1) IDisposable -> I, Disposable + // 2) UIElement -> UI, Element + // 3) HTMLDocument -> HTML, Document + // + // etc. + // We have a transition from an upper to a lower letter here. But we only + // want to break if all the letters that preceded are uppercase. i.e. if we + // have "Foo" we don't want to break that into "F, oo". But if we have + // "IFoo" or "UIFoo", then we want to break that into "I, Foo" and "UI, + // Foo". i.e. the last uppercase letter belongs to the lowercase letters + // that follows. Note: this will make the following not split properly: + // "HELLOthere". However, these sorts of names do not show up in .Net + // programs. + return index !== wordStart + && index + 1 < identifier.length + && isUpperCaseLetter(identifier.charCodeAt(index)) + && isLowerCaseLetter(identifier.charCodeAt(index + 1)) + && every(identifier, isUpperCaseLetter, wordStart, index); +} - // See if the casing indicates we're starting a new word. Note: if we're breaking on - // words, then just seeing an upper case character isn't enough. Instead, it has to - // be uppercase and the previous character can't be uppercase. - // - // For example, breaking "AddMetadata" on words would make: Add Metadata - // - // on characters would be: A dd M etadata - // - // Break "AM" on words would be: AM - // - // on characters would be: A M - // - // We break the search string on characters. But we break the symbol name on words. - return currentIsUpper && (!word || !lastIsUpper); - } +function transitionFromLowerToUpper(identifier: string, word: boolean, index: number): boolean { + const lastIsUpper = isUpperCaseLetter(identifier.charCodeAt(index - 1)); + const currentIsUpper = isUpperCaseLetter(identifier.charCodeAt(index)); + + // See if the casing indicates we're starting a new word. Note: if we're breaking on + // words, then just seeing an upper case character isn't enough. Instead, it has to + // be uppercase and the previous character can't be uppercase. + // + // For example, breaking "AddMetadata" on words would make: Add Metadata + // + // on characters would be: A dd M etadata + // + // Break "AM" on words would be: AM + // + // on characters would be: A M + // + // We break the search string on characters. But we break the symbol name on words. + return currentIsUpper && (!word || !lastIsUpper); +} - function everyInRange(start: number, end: number, pred: (n: number) => boolean): boolean { - for (let i = start; i < end; i++) { - if (!pred(i)) { - return false; - } +function everyInRange(start: number, end: number, pred: (n: number) => boolean): boolean { + for (let i = start; i < end; i++) { + if (!pred(i)) { + return false; } - return true; } + return true; +} - function every(s: string, pred: (ch: number, index: number) => boolean, start = 0, end = s.length): boolean { - return everyInRange(start, end, i => pred(s.charCodeAt(i), i)); - } +function every(s: string, pred: (ch: number, index: number) => boolean, start = 0, end = s.length): boolean { + return everyInRange(start, end, i => pred(s.charCodeAt(i), i)); +} } diff --git a/src/services/preProcess.ts b/src/services/preProcess.ts index 41845616bbe4b..6cf9f765030f3 100644 --- a/src/services/preProcess.ts +++ b/src/services/preProcess.ts @@ -1,210 +1,150 @@ namespace ts { - export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo { - const pragmaContext: PragmaContext = { - languageVersion: ScriptTarget.ES5, // controls whether the token scanner considers unicode identifiers or not - shouldn't matter, since we're only using it for trivia - pragmas: undefined, - checkJsDirective: undefined, - referencedFiles: [], - typeReferenceDirectives: [], - libReferenceDirectives: [], - amdDependencies: [], - hasNoDefaultLib: undefined, - moduleName: undefined - }; - const importedFiles: FileReference[] = []; - let ambientExternalModules: { ref: FileReference, depth: number }[] | undefined; - let lastToken: SyntaxKind; - let currentToken: SyntaxKind; - let braceNesting = 0; - // assume that text represent an external module if it contains at least one top level import/export - // ambient modules that are found inside external modules are interpreted as module augmentations - let externalModule = false; - - function nextToken() { - lastToken = currentToken; - currentToken = scanner.scan(); - if (currentToken === SyntaxKind.OpenBraceToken) { - braceNesting++; - } - else if (currentToken === SyntaxKind.CloseBraceToken) { - braceNesting--; - } - return currentToken; +export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo { + const pragmaContext: PragmaContext = { + languageVersion: ScriptTarget.ES5, // controls whether the token scanner considers unicode identifiers or not - shouldn't matter, since we're only using it for trivia + pragmas: undefined, + checkJsDirective: undefined, + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + amdDependencies: [], + hasNoDefaultLib: undefined, + moduleName: undefined + }; + const importedFiles: FileReference[] = []; + let ambientExternalModules: { ref: FileReference, depth: number }[] | undefined; + let lastToken: SyntaxKind; + let currentToken: SyntaxKind; + let braceNesting = 0; + // assume that text represent an external module if it contains at least one top level import/export + // ambient modules that are found inside external modules are interpreted as module augmentations + let externalModule = false; + + function nextToken() { + lastToken = currentToken; + currentToken = scanner.scan(); + if (currentToken === SyntaxKind.OpenBraceToken) { + braceNesting++; } - - function getFileReference() { - const fileName = scanner.getTokenValue(); - const pos = scanner.getTokenPos(); - return { fileName, pos, end: pos + fileName.length }; + else if (currentToken === SyntaxKind.CloseBraceToken) { + braceNesting--; } + return currentToken; + } - function recordAmbientExternalModule(): void { - if (!ambientExternalModules) { - ambientExternalModules = []; - } - ambientExternalModules.push({ ref: getFileReference(), depth: braceNesting }); + function getFileReference() { + const fileName = scanner.getTokenValue(); + const pos = scanner.getTokenPos(); + return { fileName, pos, end: pos + fileName.length }; + } + + function recordAmbientExternalModule(): void { + if (!ambientExternalModules) { + ambientExternalModules = []; } + ambientExternalModules.push({ ref: getFileReference(), depth: braceNesting }); + } - function recordModuleName() { - importedFiles.push(getFileReference()); + function recordModuleName() { + importedFiles.push(getFileReference()); - markAsExternalModuleIfTopLevel(); - } + markAsExternalModuleIfTopLevel(); + } - function markAsExternalModuleIfTopLevel() { - if (braceNesting === 0) { - externalModule = true; - } + function markAsExternalModuleIfTopLevel() { + if (braceNesting === 0) { + externalModule = true; } + } - /** - * Returns true if at least one token was consumed from the stream - */ - function tryConsumeDeclare(): boolean { - let token = scanner.getToken(); - if (token === SyntaxKind.DeclareKeyword) { - // declare module "mod" + /** + * Returns true if at least one token was consumed from the stream + */ + function tryConsumeDeclare(): boolean { + let token = scanner.getToken(); + if (token === SyntaxKind.DeclareKeyword) { + // declare module "mod" + token = nextToken(); + if (token === SyntaxKind.ModuleKeyword) { token = nextToken(); - if (token === SyntaxKind.ModuleKeyword) { - token = nextToken(); - if (token === SyntaxKind.StringLiteral) { - recordAmbientExternalModule(); - } + if (token === SyntaxKind.StringLiteral) { + recordAmbientExternalModule(); } - return true; } + return true; + } + + return false; + } + /** + * Returns true if at least one token was consumed from the stream + */ + function tryConsumeImport(): boolean { + if (lastToken === SyntaxKind.DotToken) { return false; } - - /** - * Returns true if at least one token was consumed from the stream - */ - function tryConsumeImport(): boolean { - if (lastToken === SyntaxKind.DotToken) { - return false; - } - let token = scanner.getToken(); - if (token === SyntaxKind.ImportKeyword) { + let token = scanner.getToken(); + if (token === SyntaxKind.ImportKeyword) { + token = nextToken(); + if (token === SyntaxKind.OpenParenToken) { token = nextToken(); - if (token === SyntaxKind.OpenParenToken) { - token = nextToken(); - if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) { - // import("mod"); - recordModuleName(); - return true; - } - } - else if (token === SyntaxKind.StringLiteral) { - // import "mod"; + if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) { + // import("mod"); recordModuleName(); return true; } - else { - if (token === SyntaxKind.TypeKeyword) { - const skipTypeKeyword = scanner.lookAhead(() => { - const token = scanner.scan(); - return token !== SyntaxKind.FromKeyword && ( - token === SyntaxKind.AsteriskToken || - token === SyntaxKind.OpenBraceToken || - token === SyntaxKind.Identifier || - isKeyword(token) - ); - }); - if (skipTypeKeyword) { - token = nextToken(); - } + } + else if (token === SyntaxKind.StringLiteral) { + // import "mod"; + recordModuleName(); + return true; + } + else { + if (token === SyntaxKind.TypeKeyword) { + const skipTypeKeyword = scanner.lookAhead(() => { + const token = scanner.scan(); + return token !== SyntaxKind.FromKeyword && ( + token === SyntaxKind.AsteriskToken || + token === SyntaxKind.OpenBraceToken || + token === SyntaxKind.Identifier || + isKeyword(token) + ); + }); + if (skipTypeKeyword) { + token = nextToken(); } + } - if (token === SyntaxKind.Identifier || isKeyword(token)) { + if (token === SyntaxKind.Identifier || isKeyword(token)) { + token = nextToken(); + if (token === SyntaxKind.FromKeyword) { token = nextToken(); - if (token === SyntaxKind.FromKeyword) { - token = nextToken(); - if (token === SyntaxKind.StringLiteral) { - // import d from "mod"; - recordModuleName(); - return true; - } - } - else if (token === SyntaxKind.EqualsToken) { - if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) { - return true; - } - } - else if (token === SyntaxKind.CommaToken) { - // consume comma and keep going - token = nextToken(); - } - else { - // unknown syntax + if (token === SyntaxKind.StringLiteral) { + // import d from "mod"; + recordModuleName(); return true; } } - - if (token === SyntaxKind.OpenBraceToken) { - token = nextToken(); - // consume "{ a as B, c, d as D}" clauses - // make sure that it stops on EOF - while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) { - token = nextToken(); - } - - if (token === SyntaxKind.CloseBraceToken) { - token = nextToken(); - if (token === SyntaxKind.FromKeyword) { - token = nextToken(); - if (token === SyntaxKind.StringLiteral) { - // import {a as A} from "mod"; - // import d, {a, b as B} from "mod" - recordModuleName(); - } - } + else if (token === SyntaxKind.EqualsToken) { + if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) { + return true; } } - else if (token === SyntaxKind.AsteriskToken) { + else if (token === SyntaxKind.CommaToken) { + // consume comma and keep going token = nextToken(); - if (token === SyntaxKind.AsKeyword) { - token = nextToken(); - if (token === SyntaxKind.Identifier || isKeyword(token)) { - token = nextToken(); - if (token === SyntaxKind.FromKeyword) { - token = nextToken(); - if (token === SyntaxKind.StringLiteral) { - // import * as NS from "mod" - // import d, * as NS from "mod" - recordModuleName(); - } - } - } - } } - } - - return true; - } - - return false; - } - - function tryConsumeExport(): boolean { - let token = scanner.getToken(); - if (token === SyntaxKind.ExportKeyword) { - markAsExternalModuleIfTopLevel(); - token = nextToken(); - if (token === SyntaxKind.TypeKeyword) { - const skipTypeKeyword = scanner.lookAhead(() => { - const token = scanner.scan(); - return token === SyntaxKind.AsteriskToken || - token === SyntaxKind.OpenBraceToken; - }); - if (skipTypeKeyword) { - token = nextToken(); + else { + // unknown syntax + return true; } } + if (token === SyntaxKind.OpenBraceToken) { token = nextToken(); // consume "{ a as B, c, d as D}" clauses - // make sure it stops on EOF + // make sure that it stops on EOF while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) { token = nextToken(); } @@ -214,187 +154,247 @@ namespace ts { if (token === SyntaxKind.FromKeyword) { token = nextToken(); if (token === SyntaxKind.StringLiteral) { - // export {a as A} from "mod"; - // export {a, b as B} from "mod" + // import {a as A} from "mod"; + // import d, {a, b as B} from "mod" recordModuleName(); } } } } else if (token === SyntaxKind.AsteriskToken) { + token = nextToken(); + if (token === SyntaxKind.AsKeyword) { + token = nextToken(); + if (token === SyntaxKind.Identifier || isKeyword(token)) { + token = nextToken(); + if (token === SyntaxKind.FromKeyword) { + token = nextToken(); + if (token === SyntaxKind.StringLiteral) { + // import * as NS from "mod" + // import d, * as NS from "mod" + recordModuleName(); + } + } + } + } + } + } + + return true; + } + + return false; + } + + function tryConsumeExport(): boolean { + let token = scanner.getToken(); + if (token === SyntaxKind.ExportKeyword) { + markAsExternalModuleIfTopLevel(); + token = nextToken(); + if (token === SyntaxKind.TypeKeyword) { + const skipTypeKeyword = scanner.lookAhead(() => { + const token = scanner.scan(); + return token === SyntaxKind.AsteriskToken || + token === SyntaxKind.OpenBraceToken; + }); + if (skipTypeKeyword) { + token = nextToken(); + } + } + if (token === SyntaxKind.OpenBraceToken) { + token = nextToken(); + // consume "{ a as B, c, d as D}" clauses + // make sure it stops on EOF + while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) { + token = nextToken(); + } + + if (token === SyntaxKind.CloseBraceToken) { token = nextToken(); if (token === SyntaxKind.FromKeyword) { token = nextToken(); if (token === SyntaxKind.StringLiteral) { - // export * from "mod" + // export {a as A} from "mod"; + // export {a, b as B} from "mod" recordModuleName(); } } } - else if (token === SyntaxKind.ImportKeyword) { + } + else if (token === SyntaxKind.AsteriskToken) { + token = nextToken(); + if (token === SyntaxKind.FromKeyword) { token = nextToken(); - if (token === SyntaxKind.TypeKeyword) { - const skipTypeKeyword = scanner.lookAhead(() => { - const token = scanner.scan(); - return token === SyntaxKind.Identifier || - isKeyword(token); - }); - if (skipTypeKeyword) { - token = nextToken(); - } + if (token === SyntaxKind.StringLiteral) { + // export * from "mod" + recordModuleName(); } - if (token === SyntaxKind.Identifier || isKeyword(token)) { + } + } + else if (token === SyntaxKind.ImportKeyword) { + token = nextToken(); + if (token === SyntaxKind.TypeKeyword) { + const skipTypeKeyword = scanner.lookAhead(() => { + const token = scanner.scan(); + return token === SyntaxKind.Identifier || + isKeyword(token); + }); + if (skipTypeKeyword) { token = nextToken(); - if (token === SyntaxKind.EqualsToken) { - if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) { - return true; - } + } + } + if (token === SyntaxKind.Identifier || isKeyword(token)) { + token = nextToken(); + if (token === SyntaxKind.EqualsToken) { + if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) { + return true; } } } - - return true; } - return false; + return true; } - function tryConsumeRequireCall(skipCurrentToken: boolean, allowTemplateLiterals = false): boolean { - let token = skipCurrentToken ? nextToken() : scanner.getToken(); - if (token === SyntaxKind.RequireKeyword) { + return false; + } + + function tryConsumeRequireCall(skipCurrentToken: boolean, allowTemplateLiterals = false): boolean { + let token = skipCurrentToken ? nextToken() : scanner.getToken(); + if (token === SyntaxKind.RequireKeyword) { + token = nextToken(); + if (token === SyntaxKind.OpenParenToken) { token = nextToken(); - if (token === SyntaxKind.OpenParenToken) { - token = nextToken(); - if (token === SyntaxKind.StringLiteral || - allowTemplateLiterals && token === SyntaxKind.NoSubstitutionTemplateLiteral) { - // require("mod"); - recordModuleName(); - } + if (token === SyntaxKind.StringLiteral || + allowTemplateLiterals && token === SyntaxKind.NoSubstitutionTemplateLiteral) { + // require("mod"); + recordModuleName(); } - return true; } - return false; + return true; } + return false; + } - function tryConsumeDefine(): boolean { - let token = scanner.getToken(); - if (token === SyntaxKind.Identifier && scanner.getTokenValue() === "define") { - token = nextToken(); - if (token !== SyntaxKind.OpenParenToken) { - return true; - } + function tryConsumeDefine(): boolean { + let token = scanner.getToken(); + if (token === SyntaxKind.Identifier && scanner.getTokenValue() === "define") { + token = nextToken(); + if (token !== SyntaxKind.OpenParenToken) { + return true; + } + token = nextToken(); + if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) { + // looks like define ("modname", ... - skip string literal and comma token = nextToken(); - if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) { - // looks like define ("modname", ... - skip string literal and comma + if (token === SyntaxKind.CommaToken) { token = nextToken(); - if (token === SyntaxKind.CommaToken) { - token = nextToken(); - } - else { - // unexpected token - return true; - } } - - // should be start of dependency list - if (token !== SyntaxKind.OpenBracketToken) { + else { + // unexpected token return true; } + } - // skip open bracket - token = nextToken(); - // scan until ']' or EOF - while (token !== SyntaxKind.CloseBracketToken && token !== SyntaxKind.EndOfFileToken) { - // record string literals as module names - if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) { - recordModuleName(); - } - - token = nextToken(); - } + // should be start of dependency list + if (token !== SyntaxKind.OpenBracketToken) { return true; - } - return false; - } - function processImports(): void { - scanner.setText(sourceText); - nextToken(); - // Look for: - // import "mod"; - // import d from "mod" - // import {a as A } from "mod"; - // import * as NS from "mod" - // import d, {a, b as B} from "mod" - // import i = require("mod"); - // import("mod"); - - // export * from "mod" - // export {a as b} from "mod" - // export import i = require("mod") - // (for JavaScript files) require("mod") - - // Do not look for: - // AnySymbol.import("mod") - // AnySymbol.nested.import("mod") - - while (true) { - if (scanner.getToken() === SyntaxKind.EndOfFileToken) { - break; + // skip open bracket + token = nextToken(); + // scan until ']' or EOF + while (token !== SyntaxKind.CloseBracketToken && token !== SyntaxKind.EndOfFileToken) { + // record string literals as module names + if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) { + recordModuleName(); } - // check if at least one of alternative have moved scanner forward - if (tryConsumeDeclare() || - tryConsumeImport() || - tryConsumeExport() || - (detectJavaScriptImports && ( - tryConsumeRequireCall(/*skipCurrentToken*/ false, /*allowTemplateLiterals*/ true) || - tryConsumeDefine() - ))) { - continue; - } - else { - nextToken(); - } + token = nextToken(); } + return true; - scanner.setText(undefined); } + return false; + } - if (readImportFiles) { - processImports(); + function processImports(): void { + scanner.setText(sourceText); + nextToken(); + // Look for: + // import "mod"; + // import d from "mod" + // import {a as A } from "mod"; + // import * as NS from "mod" + // import d, {a, b as B} from "mod" + // import i = require("mod"); + // import("mod"); + + // export * from "mod" + // export {a as b} from "mod" + // export import i = require("mod") + // (for JavaScript files) require("mod") + + // Do not look for: + // AnySymbol.import("mod") + // AnySymbol.nested.import("mod") + + while (true) { + if (scanner.getToken() === SyntaxKind.EndOfFileToken) { + break; + } + + // check if at least one of alternative have moved scanner forward + if (tryConsumeDeclare() || + tryConsumeImport() || + tryConsumeExport() || + (detectJavaScriptImports && ( + tryConsumeRequireCall(/*skipCurrentToken*/ false, /*allowTemplateLiterals*/ true) || + tryConsumeDefine() + ))) { + continue; + } + else { + nextToken(); + } } - processCommentPragmas(pragmaContext, sourceText); - processPragmasIntoFields(pragmaContext, noop); - if (externalModule) { - // for external modules module all nested ambient modules are augmentations - if (ambientExternalModules) { - // move all detected ambient modules to imported files since they need to be resolved - for (const decl of ambientExternalModules) { - importedFiles.push(decl.ref); - } + + scanner.setText(undefined); + } + + if (readImportFiles) { + processImports(); + } + processCommentPragmas(pragmaContext, sourceText); + processPragmasIntoFields(pragmaContext, noop); + if (externalModule) { + // for external modules module all nested ambient modules are augmentations + if (ambientExternalModules) { + // move all detected ambient modules to imported files since they need to be resolved + for (const decl of ambientExternalModules) { + importedFiles.push(decl.ref); } - return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: undefined }; } - else { - // for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0 - let ambientModuleNames: string[] | undefined; - if (ambientExternalModules) { - for (const decl of ambientExternalModules) { - if (decl.depth === 0) { - if (!ambientModuleNames) { - ambientModuleNames = []; - } - ambientModuleNames.push(decl.ref.fileName); - } - else { - importedFiles.push(decl.ref); + return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: undefined }; + } + else { + // for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0 + let ambientModuleNames: string[] | undefined; + if (ambientExternalModules) { + for (const decl of ambientExternalModules) { + if (decl.depth === 0) { + if (!ambientModuleNames) { + ambientModuleNames = []; } + ambientModuleNames.push(decl.ref.fileName); + } + else { + importedFiles.push(decl.ref); } } - return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: ambientModuleNames }; } + return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: ambientModuleNames }; } } +} diff --git a/src/services/refactorProvider.ts b/src/services/refactorProvider.ts index ccc6f81d2924d..772ba3e9b8f41 100644 --- a/src/services/refactorProvider.ts +++ b/src/services/refactorProvider.ts @@ -1,23 +1,23 @@ /* @internal */ namespace ts.refactor { - // A map with the refactor code as key, the refactor itself as value - // e.g. nonSuggestableRefactors[refactorCode] -> the refactor you want - const refactors = new Map(); +// A map with the refactor code as key, the refactor itself as value +// e.g. nonSuggestableRefactors[refactorCode] -> the refactor you want +const refactors = new Map(); - /** @param name An unique code associated with each refactor. Does not have to be human-readable. */ - export function registerRefactor(name: string, refactor: Refactor) { - refactors.set(name, refactor); - } +/** @param name An unique code associated with each refactor. Does not have to be human-readable. */ +export function registerRefactor(name: string, refactor: Refactor) { + refactors.set(name, refactor); +} - export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] { - return arrayFrom(flatMapIterator(refactors.values(), refactor => - context.cancellationToken && context.cancellationToken.isCancellationRequested() || - !refactor.kinds?.some(kind => refactorKindBeginsWith(kind, context.kind)) ? undefined : - refactor.getAvailableActions(context))); - } +export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] { + return arrayFrom(flatMapIterator(refactors.values(), refactor => + context.cancellationToken && context.cancellationToken.isCancellationRequested() || + !refactor.kinds?.some(kind => refactorKindBeginsWith(kind, context.kind)) ? undefined : + refactor.getAvailableActions(context))); +} - export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined { - const refactor = refactors.get(refactorName); - return refactor && refactor.getEditsForAction(context, actionName); - } +export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined { + const refactor = refactors.get(refactorName); + return refactor && refactor.getEditsForAction(context, actionName); +} } diff --git a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts index fd75154c56273..2a15c051cafe4 100644 --- a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts +++ b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts @@ -1,120 +1,120 @@ /* @internal */ namespace ts.refactor.addOrRemoveBracesToArrowFunction { - const refactorName = "Add or remove braces in an arrow function"; - const refactorDescription = Diagnostics.Add_or_remove_braces_in_an_arrow_function.message; - - const addBracesAction = { - name: "Add braces to arrow function", - description: Diagnostics.Add_braces_to_arrow_function.message, - kind: "refactor.rewrite.arrow.braces.add", - }; - const removeBracesAction = { - name: "Remove braces from arrow function", - description: Diagnostics.Remove_braces_from_arrow_function.message, - kind: "refactor.rewrite.arrow.braces.remove" - }; - registerRefactor(refactorName, { - kinds: [removeBracesAction.kind], - getEditsForAction, - getAvailableActions }); - - interface FunctionBracesInfo { - func: ArrowFunction; - expression: Expression | undefined; - returnStatement?: ReturnStatement; - addBraces: boolean; - } - - function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const { file, startPosition, triggerReason } = context; - const info = getConvertibleArrowFunctionAtPosition(file, startPosition, triggerReason === "invoked"); - if (!info) return emptyArray; - - if (!isRefactorErrorInfo(info)) { - return [{ - name: refactorName, - description: refactorDescription, - actions: [ - info.addBraces ? addBracesAction : removeBracesAction - ] - }]; - } - - if (context.preferences.provideRefactorNotApplicableReason) { - return [{ - name: refactorName, - description: refactorDescription, - actions: [ - { ...addBracesAction, notApplicableReason: info.error }, - { ...removeBracesAction, notApplicableReason: info.error }, - ] - }]; - } +const refactorName = "Add or remove braces in an arrow function"; +const refactorDescription = Diagnostics.Add_or_remove_braces_in_an_arrow_function.message; + +const addBracesAction = { + name: "Add braces to arrow function", + description: Diagnostics.Add_braces_to_arrow_function.message, + kind: "refactor.rewrite.arrow.braces.add", +}; +const removeBracesAction = { + name: "Remove braces from arrow function", + description: Diagnostics.Remove_braces_from_arrow_function.message, + kind: "refactor.rewrite.arrow.braces.remove" +}; +registerRefactor(refactorName, { + kinds: [removeBracesAction.kind], + getEditsForAction, + getAvailableActions }); + +interface FunctionBracesInfo { + func: ArrowFunction; + expression: Expression | undefined; + returnStatement?: ReturnStatement; + addBraces: boolean; +} - return emptyArray; +function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const { file, startPosition, triggerReason } = context; + const info = getConvertibleArrowFunctionAtPosition(file, startPosition, triggerReason === "invoked"); + if (!info) return emptyArray; + + if (!isRefactorErrorInfo(info)) { + return [{ + name: refactorName, + description: refactorDescription, + actions: [ + info.addBraces ? addBracesAction : removeBracesAction + ] + }]; } - function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const { file, startPosition } = context; - const info = getConvertibleArrowFunctionAtPosition(file, startPosition); - Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ + name: refactorName, + description: refactorDescription, + actions: [ + { ...addBracesAction, notApplicableReason: info.error }, + { ...removeBracesAction, notApplicableReason: info.error }, + ] + }]; + } - const { expression, returnStatement, func } = info; + return emptyArray; +} - let body: ConciseBody; +function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + const { file, startPosition } = context; + const info = getConvertibleArrowFunctionAtPosition(file, startPosition); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); - if (actionName === addBracesAction.name) { - const returnStatement = factory.createReturnStatement(expression); - body = factory.createBlock([returnStatement], /* multiLine */ true); - copyLeadingComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true); - } - else if (actionName === removeBracesAction.name && returnStatement) { - const actualExpression = expression || factory.createVoidZero(); - body = needsParentheses(actualExpression) ? factory.createParenthesizedExpression(actualExpression) : actualExpression; - copyTrailingAsLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - copyLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - copyTrailingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - } - else { - Debug.fail("invalid action"); - } + const { expression, returnStatement, func } = info; - const edits = textChanges.ChangeTracker.with(context, t => { - t.replaceNode(file, func.body, body); - }); + let body: ConciseBody; - return { renameFilename: undefined, renameLocation: undefined, edits }; + if (actionName === addBracesAction.name) { + const returnStatement = factory.createReturnStatement(expression); + body = factory.createBlock([returnStatement], /* multiLine */ true); + copyLeadingComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true); + } + else if (actionName === removeBracesAction.name && returnStatement) { + const actualExpression = expression || factory.createVoidZero(); + body = needsParentheses(actualExpression) ? factory.createParenthesizedExpression(actualExpression) : actualExpression; + copyTrailingAsLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); + copyLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); + copyTrailingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); + } + else { + Debug.fail("invalid action"); } - function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, considerFunctionBodies = true, kind?: string): FunctionBracesInfo | RefactorErrorInfo | undefined { - const node = getTokenAtPosition(file, startPosition); - const func = getContainingFunction(node); + const edits = textChanges.ChangeTracker.with(context, t => { + t.replaceNode(file, func.body, body); + }); - if (!func) { - return { - error: getLocaleSpecificMessage(Diagnostics.Could_not_find_a_containing_arrow_function) - }; - } + return { renameFilename: undefined, renameLocation: undefined, edits }; +} - if (!isArrowFunction(func)) { - return { - error: getLocaleSpecificMessage(Diagnostics.Containing_function_is_not_an_arrow_function) - }; - } +function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, considerFunctionBodies = true, kind?: string): FunctionBracesInfo | RefactorErrorInfo | undefined { + const node = getTokenAtPosition(file, startPosition); + const func = getContainingFunction(node); - if ((!rangeContainsRange(func, node) || rangeContainsRange(func.body, node) && !considerFunctionBodies)) { - return undefined; - } + if (!func) { + return { + error: getLocaleSpecificMessage(Diagnostics.Could_not_find_a_containing_arrow_function) + }; + } - if (refactorKindBeginsWith(addBracesAction.kind, kind) && isExpression(func.body)) { - return { func, addBraces: true, expression: func.body }; - } - else if (refactorKindBeginsWith(removeBracesAction.kind, kind) && isBlock(func.body) && func.body.statements.length === 1) { - const firstStatement = first(func.body.statements); - if (isReturnStatement(firstStatement)) { - return { func, addBraces: false, expression: firstStatement.expression, returnStatement: firstStatement }; - } - } + if (!isArrowFunction(func)) { + return { + error: getLocaleSpecificMessage(Diagnostics.Containing_function_is_not_an_arrow_function) + }; + } + + if ((!rangeContainsRange(func, node) || rangeContainsRange(func.body, node) && !considerFunctionBodies)) { return undefined; } + + if (refactorKindBeginsWith(addBracesAction.kind, kind) && isExpression(func.body)) { + return { func, addBraces: true, expression: func.body }; + } + else if (refactorKindBeginsWith(removeBracesAction.kind, kind) && isBlock(func.body) && func.body.statements.length === 1) { + const firstStatement = first(func.body.statements); + if (isReturnStatement(firstStatement)) { + return { func, addBraces: false, expression: firstStatement.expression, returnStatement: firstStatement }; + } + } + return undefined; +} } diff --git a/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts b/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts index 4165cbfcc07ed..cdec4a71fda17 100644 --- a/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts +++ b/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts @@ -1,258 +1,258 @@ /* @internal */ namespace ts.refactor.convertArrowFunctionOrFunctionExpression { - const refactorName = "Convert arrow function or function expression"; - const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_arrow_function_or_function_expression); - - const toAnonymousFunctionAction = { - name: "Convert to anonymous function", - description: getLocaleSpecificMessage(Diagnostics.Convert_to_anonymous_function), - kind: "refactor.rewrite.function.anonymous", - }; - const toNamedFunctionAction = { - name: "Convert to named function", - description: getLocaleSpecificMessage(Diagnostics.Convert_to_named_function), - kind: "refactor.rewrite.function.named", - }; - const toArrowFunctionAction = { - name: "Convert to arrow function", - description: getLocaleSpecificMessage(Diagnostics.Convert_to_arrow_function), - kind: "refactor.rewrite.function.arrow", - }; - registerRefactor(refactorName, { - kinds: [ - toAnonymousFunctionAction.kind, - toNamedFunctionAction.kind, - toArrowFunctionAction.kind - ], - getEditsForAction, - getAvailableActions - }); +const refactorName = "Convert arrow function or function expression"; +const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_arrow_function_or_function_expression); + +const toAnonymousFunctionAction = { + name: "Convert to anonymous function", + description: getLocaleSpecificMessage(Diagnostics.Convert_to_anonymous_function), + kind: "refactor.rewrite.function.anonymous", +}; +const toNamedFunctionAction = { + name: "Convert to named function", + description: getLocaleSpecificMessage(Diagnostics.Convert_to_named_function), + kind: "refactor.rewrite.function.named", +}; +const toArrowFunctionAction = { + name: "Convert to arrow function", + description: getLocaleSpecificMessage(Diagnostics.Convert_to_arrow_function), + kind: "refactor.rewrite.function.arrow", +}; +registerRefactor(refactorName, { + kinds: [ + toAnonymousFunctionAction.kind, + toNamedFunctionAction.kind, + toArrowFunctionAction.kind + ], + getEditsForAction, + getAvailableActions +}); + +interface FunctionInfo { + readonly selectedVariableDeclaration: boolean; + readonly func: FunctionExpression | ArrowFunction; +} - interface FunctionInfo { - readonly selectedVariableDeclaration: boolean; - readonly func: FunctionExpression | ArrowFunction; - } +interface VariableInfo { + readonly variableDeclaration: VariableDeclaration; + readonly variableDeclarationList: VariableDeclarationList; + readonly statement: VariableStatement; + readonly name: Identifier; +} - interface VariableInfo { - readonly variableDeclaration: VariableDeclaration; - readonly variableDeclarationList: VariableDeclarationList; - readonly statement: VariableStatement; - readonly name: Identifier; +function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const { file, startPosition, program, kind } = context; + const info = getFunctionInfo(file, startPosition, program); + + if (!info) return emptyArray; + const { selectedVariableDeclaration, func } = info; + const possibleActions: RefactorActionInfo[] = []; + const errors: RefactorActionInfo[] = []; + if (refactorKindBeginsWith(toNamedFunctionAction.kind, kind)) { + const error = selectedVariableDeclaration || (isArrowFunction(func) && isVariableDeclaration(func.parent)) ? + undefined : getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_named_function); + if (error) { + errors.push({ ...toNamedFunctionAction, notApplicableReason: error }); + } + else { + possibleActions.push(toNamedFunctionAction); + } } - function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const { file, startPosition, program, kind } = context; - const info = getFunctionInfo(file, startPosition, program); - - if (!info) return emptyArray; - const { selectedVariableDeclaration, func } = info; - const possibleActions: RefactorActionInfo[] = []; - const errors: RefactorActionInfo[] = []; - if (refactorKindBeginsWith(toNamedFunctionAction.kind, kind)) { - const error = selectedVariableDeclaration || (isArrowFunction(func) && isVariableDeclaration(func.parent)) ? - undefined : getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_named_function); - if (error) { - errors.push({ ...toNamedFunctionAction, notApplicableReason: error }); - } - else { - possibleActions.push(toNamedFunctionAction); - } + if (refactorKindBeginsWith(toAnonymousFunctionAction.kind, kind)) { + const error = !selectedVariableDeclaration && isArrowFunction(func) ? + undefined: getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_anonymous_function); + if (error) { + errors.push({ ...toAnonymousFunctionAction, notApplicableReason: error }); } - - if (refactorKindBeginsWith(toAnonymousFunctionAction.kind, kind)) { - const error = !selectedVariableDeclaration && isArrowFunction(func) ? - undefined: getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_anonymous_function); - if (error) { - errors.push({ ...toAnonymousFunctionAction, notApplicableReason: error }); - } - else { - possibleActions.push(toAnonymousFunctionAction); - } + else { + possibleActions.push(toAnonymousFunctionAction); } + } - if (refactorKindBeginsWith(toArrowFunctionAction.kind, kind)) { - const error = isFunctionExpression(func) ? undefined : getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_arrow_function); - if (error) { - errors.push({ ...toArrowFunctionAction, notApplicableReason: error }); - } - else { - possibleActions.push(toArrowFunctionAction); - } + if (refactorKindBeginsWith(toArrowFunctionAction.kind, kind)) { + const error = isFunctionExpression(func) ? undefined : getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_arrow_function); + if (error) { + errors.push({ ...toArrowFunctionAction, notApplicableReason: error }); + } + else { + possibleActions.push(toArrowFunctionAction); } - - return [{ - name: refactorName, - description: refactorDescription, - actions: possibleActions.length === 0 && context.preferences.provideRefactorNotApplicableReason ? - errors : possibleActions - }]; } - function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const { file, startPosition, program } = context; - const info = getFunctionInfo(file, startPosition, program); + return [{ + name: refactorName, + description: refactorDescription, + actions: possibleActions.length === 0 && context.preferences.provideRefactorNotApplicableReason ? + errors : possibleActions + }]; +} - if (!info) return undefined; - const { func } = info; - const edits: FileTextChanges[] = []; +function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + const { file, startPosition, program } = context; + const info = getFunctionInfo(file, startPosition, program); - switch (actionName) { - case toAnonymousFunctionAction.name: - edits.push(...getEditInfoForConvertToAnonymousFunction(context, func)); - break; + if (!info) return undefined; + const { func } = info; + const edits: FileTextChanges[] = []; - case toNamedFunctionAction.name: - const variableInfo = getVariableInfo(func); - if (!variableInfo) return undefined; + switch (actionName) { + case toAnonymousFunctionAction.name: + edits.push(...getEditInfoForConvertToAnonymousFunction(context, func)); + break; - edits.push(...getEditInfoForConvertToNamedFunction(context, func, variableInfo)); - break; + case toNamedFunctionAction.name: + const variableInfo = getVariableInfo(func); + if (!variableInfo) return undefined; - case toArrowFunctionAction.name: - if (!isFunctionExpression(func)) return undefined; - edits.push(...getEditInfoForConvertToArrowFunction(context, func)); - break; + edits.push(...getEditInfoForConvertToNamedFunction(context, func, variableInfo)); + break; - default: - return Debug.fail("invalid action"); - } + case toArrowFunctionAction.name: + if (!isFunctionExpression(func)) return undefined; + edits.push(...getEditInfoForConvertToArrowFunction(context, func)); + break; - return { renameFilename: undefined, renameLocation: undefined, edits }; + default: + return Debug.fail("invalid action"); } - function containingThis(node: Node): boolean { - let containsThis = false; - node.forEachChild(function checkThis(child) { - - if (isThis(child)) { - containsThis = true; - return; - } + return { renameFilename: undefined, renameLocation: undefined, edits }; +} - if (!isClassLike(child) && !isFunctionDeclaration(child) && !isFunctionExpression(child)) { - forEachChild(child, checkThis); - } - }); +function containingThis(node: Node): boolean { + let containsThis = false; + node.forEachChild(function checkThis(child) { - return containsThis; - } - - function getFunctionInfo(file: SourceFile, startPosition: number, program: Program): FunctionInfo | undefined { - const token = getTokenAtPosition(file, startPosition); - const typeChecker = program.getTypeChecker(); - const func = tryGetFunctionFromVariableDeclaration(file, typeChecker, token.parent); - if (func && !containingThis(func.body) && !typeChecker.containsArgumentsReference(func)) { - return { selectedVariableDeclaration: true, func }; + if (isThis(child)) { + containsThis = true; + return; } - const maybeFunc = getContainingFunction(token); - if ( - maybeFunc && - (isFunctionExpression(maybeFunc) || isArrowFunction(maybeFunc)) && - !rangeContainsRange(maybeFunc.body, token) && - !containingThis(maybeFunc.body) && - !typeChecker.containsArgumentsReference(maybeFunc) - ) { - if (isFunctionExpression(maybeFunc) && isFunctionReferencedInFile(file, typeChecker, maybeFunc)) return undefined; - return { selectedVariableDeclaration: false, func: maybeFunc }; + if (!isClassLike(child) && !isFunctionDeclaration(child) && !isFunctionExpression(child)) { + forEachChild(child, checkThis); } + }); - return undefined; + return containsThis; +} + +function getFunctionInfo(file: SourceFile, startPosition: number, program: Program): FunctionInfo | undefined { + const token = getTokenAtPosition(file, startPosition); + const typeChecker = program.getTypeChecker(); + const func = tryGetFunctionFromVariableDeclaration(file, typeChecker, token.parent); + if (func && !containingThis(func.body) && !typeChecker.containsArgumentsReference(func)) { + return { selectedVariableDeclaration: true, func }; } - function isSingleVariableDeclaration(parent: Node): parent is VariableDeclarationList { - return isVariableDeclaration(parent) || (isVariableDeclarationList(parent) && parent.declarations.length === 1); + const maybeFunc = getContainingFunction(token); + if ( + maybeFunc && + (isFunctionExpression(maybeFunc) || isArrowFunction(maybeFunc)) && + !rangeContainsRange(maybeFunc.body, token) && + !containingThis(maybeFunc.body) && + !typeChecker.containsArgumentsReference(maybeFunc) + ) { + if (isFunctionExpression(maybeFunc) && isFunctionReferencedInFile(file, typeChecker, maybeFunc)) return undefined; + return { selectedVariableDeclaration: false, func: maybeFunc }; } - function tryGetFunctionFromVariableDeclaration(sourceFile: SourceFile, typeChecker: TypeChecker, parent: Node): ArrowFunction | FunctionExpression | undefined { - if (!isSingleVariableDeclaration(parent)) { - return undefined; - } - const variableDeclaration = isVariableDeclaration(parent) ? parent : first(parent.declarations); - const initializer = variableDeclaration.initializer; - if (initializer && (isArrowFunction(initializer) || isFunctionExpression(initializer) && !isFunctionReferencedInFile(sourceFile, typeChecker, initializer))) { - return initializer; - } + return undefined; +} + +function isSingleVariableDeclaration(parent: Node): parent is VariableDeclarationList { + return isVariableDeclaration(parent) || (isVariableDeclarationList(parent) && parent.declarations.length === 1); +} + +function tryGetFunctionFromVariableDeclaration(sourceFile: SourceFile, typeChecker: TypeChecker, parent: Node): ArrowFunction | FunctionExpression | undefined { + if (!isSingleVariableDeclaration(parent)) { return undefined; } + const variableDeclaration = isVariableDeclaration(parent) ? parent : first(parent.declarations); + const initializer = variableDeclaration.initializer; + if (initializer && (isArrowFunction(initializer) || isFunctionExpression(initializer) && !isFunctionReferencedInFile(sourceFile, typeChecker, initializer))) { + return initializer; + } + return undefined; +} - function convertToBlock(body: ConciseBody): Block { - if (isExpression(body)) { - const returnStatement = factory.createReturnStatement(body); - const file = body.getSourceFile(); - suppressLeadingAndTrailingTrivia(returnStatement); - copyTrailingAsLeadingComments(body, returnStatement, file, /* commentKind */ undefined, /* hasTrailingNewLine */ true); - return factory.createBlock([returnStatement], /* multiLine */ true); - } - else { - return body; - } +function convertToBlock(body: ConciseBody): Block { + if (isExpression(body)) { + const returnStatement = factory.createReturnStatement(body); + const file = body.getSourceFile(); + suppressLeadingAndTrailingTrivia(returnStatement); + copyTrailingAsLeadingComments(body, returnStatement, file, /* commentKind */ undefined, /* hasTrailingNewLine */ true); + return factory.createBlock([returnStatement], /* multiLine */ true); } + else { + return body; + } +} - function getVariableInfo(func: FunctionExpression | ArrowFunction): VariableInfo | undefined { - const variableDeclaration = func.parent; - if (!isVariableDeclaration(variableDeclaration) || !isVariableDeclarationInVariableStatement(variableDeclaration)) return undefined; +function getVariableInfo(func: FunctionExpression | ArrowFunction): VariableInfo | undefined { + const variableDeclaration = func.parent; + if (!isVariableDeclaration(variableDeclaration) || !isVariableDeclarationInVariableStatement(variableDeclaration)) return undefined; - const variableDeclarationList = variableDeclaration.parent; - const statement = variableDeclarationList.parent; - if (!isVariableDeclarationList(variableDeclarationList) || !isVariableStatement(statement) || !isIdentifier(variableDeclaration.name)) return undefined; + const variableDeclarationList = variableDeclaration.parent; + const statement = variableDeclarationList.parent; + if (!isVariableDeclarationList(variableDeclarationList) || !isVariableStatement(statement) || !isIdentifier(variableDeclaration.name)) return undefined; - return { variableDeclaration, variableDeclarationList, statement, name: variableDeclaration.name }; - } + return { variableDeclaration, variableDeclarationList, statement, name: variableDeclaration.name }; +} - function getEditInfoForConvertToAnonymousFunction(context: RefactorContext, func: FunctionExpression | ArrowFunction): FileTextChanges[] { - const { file } = context; - const body = convertToBlock(func.body); - const newNode = factory.createFunctionExpression(func.modifiers, func.asteriskToken, /* name */ undefined, func.typeParameters, func.parameters, func.type, body); - return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, func, newNode)); - } +function getEditInfoForConvertToAnonymousFunction(context: RefactorContext, func: FunctionExpression | ArrowFunction): FileTextChanges[] { + const { file } = context; + const body = convertToBlock(func.body); + const newNode = factory.createFunctionExpression(func.modifiers, func.asteriskToken, /* name */ undefined, func.typeParameters, func.parameters, func.type, body); + return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, func, newNode)); +} - function getEditInfoForConvertToNamedFunction(context: RefactorContext, func: FunctionExpression | ArrowFunction, variableInfo: VariableInfo): FileTextChanges[] { - const { file } = context; - const body = convertToBlock(func.body); +function getEditInfoForConvertToNamedFunction(context: RefactorContext, func: FunctionExpression | ArrowFunction, variableInfo: VariableInfo): FileTextChanges[] { + const { file } = context; + const body = convertToBlock(func.body); - const { variableDeclaration, variableDeclarationList, statement, name } = variableInfo; - suppressLeadingTrivia(statement); + const { variableDeclaration, variableDeclarationList, statement, name } = variableInfo; + suppressLeadingTrivia(statement); - const modifiersFlags = (getCombinedModifierFlags(variableDeclaration) & ModifierFlags.Export) | getEffectiveModifierFlags(func); - const modifiers = factory.createModifiersFromModifierFlags(modifiersFlags); - const newNode = factory.createFunctionDeclaration(func.decorators, length(modifiers) ? modifiers : undefined, func.asteriskToken, name, func.typeParameters, func.parameters, func.type, body); + const modifiersFlags = (getCombinedModifierFlags(variableDeclaration) & ModifierFlags.Export) | getEffectiveModifierFlags(func); + const modifiers = factory.createModifiersFromModifierFlags(modifiersFlags); + const newNode = factory.createFunctionDeclaration(func.decorators, length(modifiers) ? modifiers : undefined, func.asteriskToken, name, func.typeParameters, func.parameters, func.type, body); - if (variableDeclarationList.declarations.length === 1) { - return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, statement, newNode)); - } - else { - return textChanges.ChangeTracker.with(context, t => { - t.delete(file, variableDeclaration); - t.insertNodeAfter(file, statement, newNode); - }); - } + if (variableDeclarationList.declarations.length === 1) { + return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, statement, newNode)); } + else { + return textChanges.ChangeTracker.with(context, t => { + t.delete(file, variableDeclaration); + t.insertNodeAfter(file, statement, newNode); + }); + } +} - function getEditInfoForConvertToArrowFunction(context: RefactorContext, func: FunctionExpression): FileTextChanges[] { - const { file } = context; - const statements = func.body.statements; - const head = statements[0]; - let body: ConciseBody; - - if (canBeConvertedToExpression(func.body, head)) { - body = head.expression!; - suppressLeadingAndTrailingTrivia(body); - copyComments(head, body); - } - else { - body = func.body; - } +function getEditInfoForConvertToArrowFunction(context: RefactorContext, func: FunctionExpression): FileTextChanges[] { + const { file } = context; + const statements = func.body.statements; + const head = statements[0]; + let body: ConciseBody; - const newNode = factory.createArrowFunction(func.modifiers, func.typeParameters, func.parameters, func.type, factory.createToken(SyntaxKind.EqualsGreaterThanToken), body); - return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, func, newNode)); + if (canBeConvertedToExpression(func.body, head)) { + body = head.expression!; + suppressLeadingAndTrailingTrivia(body); + copyComments(head, body); } - - function canBeConvertedToExpression(body: Block, head: Statement): head is ReturnStatement { - return body.statements.length === 1 && ((isReturnStatement(head) && !!head.expression)); + else { + body = func.body; } - function isFunctionReferencedInFile(sourceFile: SourceFile, typeChecker: TypeChecker, node: FunctionExpression): boolean { - return !!node.name && FindAllReferences.Core.isSymbolReferencedInFile(node.name, typeChecker, sourceFile); - } + const newNode = factory.createArrowFunction(func.modifiers, func.typeParameters, func.parameters, func.type, factory.createToken(SyntaxKind.EqualsGreaterThanToken), body); + return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, func, newNode)); +} + +function canBeConvertedToExpression(body: Block, head: Statement): head is ReturnStatement { + return body.statements.length === 1 && ((isReturnStatement(head) && !!head.expression)); +} + +function isFunctionReferencedInFile(sourceFile: SourceFile, typeChecker: TypeChecker, node: FunctionExpression): boolean { + return !!node.name && FindAllReferences.Core.isSymbolReferencedInFile(node.name, typeChecker, sourceFile); +} } diff --git a/src/services/refactors/convertExport.ts b/src/services/refactors/convertExport.ts index 3737f74a09620..ba4007be44ea3 100644 --- a/src/services/refactors/convertExport.ts +++ b/src/services/refactors/convertExport.ts @@ -1,261 +1,261 @@ /* @internal */ namespace ts.refactor { - const refactorName = "Convert export"; +const refactorName = "Convert export"; - const defaultToNamedAction = { - name: "Convert default export to named export", - description: Diagnostics.Convert_default_export_to_named_export.message, - kind: "refactor.rewrite.export.named" - }; - const namedToDefaultAction = { - name: "Convert named export to default export", - description: Diagnostics.Convert_named_export_to_default_export.message, - kind: "refactor.rewrite.export.default" - }; +const defaultToNamedAction = { + name: "Convert default export to named export", + description: Diagnostics.Convert_default_export_to_named_export.message, + kind: "refactor.rewrite.export.named" +}; +const namedToDefaultAction = { + name: "Convert named export to default export", + description: Diagnostics.Convert_named_export_to_default_export.message, + kind: "refactor.rewrite.export.default" +}; - registerRefactor(refactorName, { - kinds: [ - defaultToNamedAction.kind, - namedToDefaultAction.kind - ], - getAvailableActions(context): readonly ApplicableRefactorInfo[] { - const info = getInfo(context, context.triggerReason === "invoked"); - if (!info) return emptyArray; +registerRefactor(refactorName, { + kinds: [ + defaultToNamedAction.kind, + namedToDefaultAction.kind + ], + getAvailableActions(context): readonly ApplicableRefactorInfo[] { + const info = getInfo(context, context.triggerReason === "invoked"); + if (!info) return emptyArray; - if (!isRefactorErrorInfo(info)) { - const action = info.wasDefault ? defaultToNamedAction : namedToDefaultAction; - return [{ name: refactorName, description: action.description, actions: [action] }]; - } + if (!isRefactorErrorInfo(info)) { + const action = info.wasDefault ? defaultToNamedAction : namedToDefaultAction; + return [{ name: refactorName, description: action.description, actions: [action] }]; + } - if (context.preferences.provideRefactorNotApplicableReason) { - return [ - { name: refactorName, description: Diagnostics.Convert_default_export_to_named_export.message, actions: [ - { ...defaultToNamedAction, notApplicableReason: info.error }, - { ...namedToDefaultAction, notApplicableReason: info.error }, - ]} - ]; - } + if (context.preferences.provideRefactorNotApplicableReason) { + return [ + { name: refactorName, description: Diagnostics.Convert_default_export_to_named_export.message, actions: [ + { ...defaultToNamedAction, notApplicableReason: info.error }, + { ...namedToDefaultAction, notApplicableReason: info.error }, + ]} + ]; + } - return emptyArray; - }, - getEditsForAction(context, actionName): RefactorEditInfo { - Debug.assert(actionName === defaultToNamedAction.name || actionName === namedToDefaultAction.name, "Unexpected action name"); - const info = getInfo(context); - Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); - const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, info, t, context.cancellationToken)); - return { edits, renameFilename: undefined, renameLocation: undefined }; - }, - }); + return emptyArray; + }, + getEditsForAction(context, actionName): RefactorEditInfo { + Debug.assert(actionName === defaultToNamedAction.name || actionName === namedToDefaultAction.name, "Unexpected action name"); + const info = getInfo(context); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); + const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, info, t, context.cancellationToken)); + return { edits, renameFilename: undefined, renameLocation: undefined }; + }, +}); - // If a VariableStatement, will have exactly one VariableDeclaration, with an Identifier for a name. - type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement | ExportAssignment; - interface ExportInfo { - readonly exportNode: ExportToConvert; - readonly exportName: Identifier; // This is exportNode.name except for VariableStatement_s. - readonly wasDefault: boolean; - readonly exportingModuleSymbol: Symbol; - }; +// If a VariableStatement, will have exactly one VariableDeclaration, with an Identifier for a name. +type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement | ExportAssignment; +interface ExportInfo { + readonly exportNode: ExportToConvert; + readonly exportName: Identifier; // This is exportNode.name except for VariableStatement_s. + readonly wasDefault: boolean; + readonly exportingModuleSymbol: Symbol; +}; - function getInfo(context: RefactorContext, considerPartialSpans = true): ExportInfo | RefactorErrorInfo | undefined { - const { file, program } = context; - const span = getRefactorContextSpan(context); - const token = getTokenAtPosition(file, span.start); - const exportNode = !!(token.parent && getSyntacticModifierFlags(token.parent) & ModifierFlags.Export) && considerPartialSpans ? token.parent : getParentNodeInSpan(token, file, span); - if (!exportNode || (!isSourceFile(exportNode.parent) && !(isModuleBlock(exportNode.parent) && isAmbientModule(exportNode.parent.parent)))) { - return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_export_statement) }; - } +function getInfo(context: RefactorContext, considerPartialSpans = true): ExportInfo | RefactorErrorInfo | undefined { + const { file, program } = context; + const span = getRefactorContextSpan(context); + const token = getTokenAtPosition(file, span.start); + const exportNode = !!(token.parent && getSyntacticModifierFlags(token.parent) & ModifierFlags.Export) && considerPartialSpans ? token.parent : getParentNodeInSpan(token, file, span); + if (!exportNode || (!isSourceFile(exportNode.parent) && !(isModuleBlock(exportNode.parent) && isAmbientModule(exportNode.parent.parent)))) { + return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_export_statement) }; + } + + const exportingModuleSymbol = isSourceFile(exportNode.parent) ? exportNode.parent.symbol : exportNode.parent.parent.symbol; + + const flags = getSyntacticModifierFlags(exportNode) || ((isExportAssignment(exportNode) && !exportNode.isExportEquals) ? ModifierFlags.ExportDefault : ModifierFlags.None); - const exportingModuleSymbol = isSourceFile(exportNode.parent) ? exportNode.parent.symbol : exportNode.parent.parent.symbol; + const wasDefault = !!(flags & ModifierFlags.Default); + // If source file already has a default export, don't offer refactor. + if (!(flags & ModifierFlags.Export) || !wasDefault && exportingModuleSymbol.exports!.has(InternalSymbolName.Default)) { + return { error: getLocaleSpecificMessage(Diagnostics.This_file_already_has_a_default_export) }; + } - const flags = getSyntacticModifierFlags(exportNode) || ((isExportAssignment(exportNode) && !exportNode.isExportEquals) ? ModifierFlags.ExportDefault : ModifierFlags.None); + const checker = program.getTypeChecker(); + const noSymbolError = (id: Node) => + (isIdentifier(id) && checker.getSymbolAtLocation(id)) ? undefined + : { error: getLocaleSpecificMessage(Diagnostics.Can_only_convert_named_export) }; - const wasDefault = !!(flags & ModifierFlags.Default); - // If source file already has a default export, don't offer refactor. - if (!(flags & ModifierFlags.Export) || !wasDefault && exportingModuleSymbol.exports!.has(InternalSymbolName.Default)) { - return { error: getLocaleSpecificMessage(Diagnostics.This_file_already_has_a_default_export) }; + switch (exportNode.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ModuleDeclaration: { + const node = exportNode as FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | TypeAliasDeclaration | NamespaceDeclaration; + if (!node.name) return undefined; + return noSymbolError(node.name) + || { exportNode: node, exportName: node.name, wasDefault, exportingModuleSymbol }; + } + case SyntaxKind.VariableStatement: { + const vs = exportNode as VariableStatement; + // Must be `export const x = something;`. + if (!(vs.declarationList.flags & NodeFlags.Const) || vs.declarationList.declarations.length !== 1) { + return undefined; + } + const decl = first(vs.declarationList.declarations); + if (!decl.initializer) return undefined; + Debug.assert(!wasDefault, "Can't have a default flag here"); + return noSymbolError(decl.name) + || { exportNode: vs, exportName: decl.name as Identifier, wasDefault, exportingModuleSymbol }; + } + case SyntaxKind.ExportAssignment: { + const node = exportNode as ExportAssignment; + if (node.isExportEquals) return undefined; + return noSymbolError(node.expression) + || { exportNode: node, exportName: node.expression as Identifier, wasDefault, exportingModuleSymbol }; } + default: + return undefined; + } +} - const checker = program.getTypeChecker(); - const noSymbolError = (id: Node) => - (isIdentifier(id) && checker.getSymbolAtLocation(id)) ? undefined - : { error: getLocaleSpecificMessage(Diagnostics.Can_only_convert_named_export) }; +function doChange(exportingSourceFile: SourceFile, program: Program, info: ExportInfo, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { + changeExport(exportingSourceFile, info, changes, program.getTypeChecker()); + changeImports(program, info, changes, cancellationToken); +} +function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: ExportInfo, changes: textChanges.ChangeTracker, checker: TypeChecker): void { + if (wasDefault) { + if (isExportAssignment(exportNode) && !exportNode.isExportEquals) { + const exp = exportNode.expression as Identifier; + const spec = makeExportSpecifier(exp.text, exp.text); + changes.replaceNode(exportingSourceFile, exportNode, factory.createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([spec]))); + } + else { + changes.delete(exportingSourceFile, Debug.checkDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword), "Should find a default keyword in modifier list")); + } + } + else { + const exportKeyword = Debug.checkDefined(findModifier(exportNode, SyntaxKind.ExportKeyword), "Should find an export keyword in modifier list"); switch (exportNode.kind) { case SyntaxKind.FunctionDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: + changes.insertNodeAfter(exportingSourceFile, exportKeyword, factory.createToken(SyntaxKind.DefaultKeyword)); + break; + case SyntaxKind.VariableStatement: + // If 'x' isn't used in this file and doesn't have type definition, `export const x = 0;` --> `export default 0;` + const decl = first(exportNode.declarationList.declarations); + if (!FindAllReferences.Core.isSymbolReferencedInFile(exportName, checker, exportingSourceFile) && !decl.type) { + // We checked in `getInfo` that an initializer exists. + changes.replaceNode(exportingSourceFile, exportNode, factory.createExportDefault(Debug.checkDefined(decl.initializer, "Initializer was previously known to be present"))); + break; + } + // falls through case SyntaxKind.EnumDeclaration: case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ModuleDeclaration: { - const node = exportNode as FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | TypeAliasDeclaration | NamespaceDeclaration; - if (!node.name) return undefined; - return noSymbolError(node.name) - || { exportNode: node, exportName: node.name, wasDefault, exportingModuleSymbol }; - } - case SyntaxKind.VariableStatement: { - const vs = exportNode as VariableStatement; - // Must be `export const x = something;`. - if (!(vs.declarationList.flags & NodeFlags.Const) || vs.declarationList.declarations.length !== 1) { - return undefined; - } - const decl = first(vs.declarationList.declarations); - if (!decl.initializer) return undefined; - Debug.assert(!wasDefault, "Can't have a default flag here"); - return noSymbolError(decl.name) - || { exportNode: vs, exportName: decl.name as Identifier, wasDefault, exportingModuleSymbol }; - } - case SyntaxKind.ExportAssignment: { - const node = exportNode as ExportAssignment; - if (node.isExportEquals) return undefined; - return noSymbolError(node.expression) - || { exportNode: node, exportName: node.expression as Identifier, wasDefault, exportingModuleSymbol }; - } + case SyntaxKind.ModuleDeclaration: + // `export type T = number;` -> `type T = number; export default T;` + changes.deleteModifier(exportingSourceFile, exportKeyword); + changes.insertNodeAfter(exportingSourceFile, exportNode, factory.createExportDefault(factory.createIdentifier(exportName.text))); + break; default: - return undefined; + Debug.fail(`Unexpected exportNode kind ${(exportNode as ExportToConvert).kind}`); } } +} - function doChange(exportingSourceFile: SourceFile, program: Program, info: ExportInfo, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { - changeExport(exportingSourceFile, info, changes, program.getTypeChecker()); - changeImports(program, info, changes, cancellationToken); - } - - function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: ExportInfo, changes: textChanges.ChangeTracker, checker: TypeChecker): void { +function changeImports(program: Program, { wasDefault, exportName, exportingModuleSymbol }: ExportInfo, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { + const checker = program.getTypeChecker(); + const exportSymbol = Debug.checkDefined(checker.getSymbolAtLocation(exportName), "Export name should resolve to a symbol"); + FindAllReferences.Core.eachExportReference(program.getSourceFiles(), checker, cancellationToken, exportSymbol, exportingModuleSymbol, exportName.text, wasDefault, ref => { + const importingSourceFile = ref.getSourceFile(); if (wasDefault) { - if (isExportAssignment(exportNode) && !exportNode.isExportEquals) { - const exp = exportNode.expression as Identifier; - const spec = makeExportSpecifier(exp.text, exp.text); - changes.replaceNode(exportingSourceFile, exportNode, factory.createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([spec]))); - } - else { - changes.delete(exportingSourceFile, Debug.checkDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword), "Should find a default keyword in modifier list")); - } + changeDefaultToNamedImport(importingSourceFile, ref, changes, exportName.text); } else { - const exportKeyword = Debug.checkDefined(findModifier(exportNode, SyntaxKind.ExportKeyword), "Should find an export keyword in modifier list"); - switch (exportNode.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - changes.insertNodeAfter(exportingSourceFile, exportKeyword, factory.createToken(SyntaxKind.DefaultKeyword)); - break; - case SyntaxKind.VariableStatement: - // If 'x' isn't used in this file and doesn't have type definition, `export const x = 0;` --> `export default 0;` - const decl = first(exportNode.declarationList.declarations); - if (!FindAllReferences.Core.isSymbolReferencedInFile(exportName, checker, exportingSourceFile) && !decl.type) { - // We checked in `getInfo` that an initializer exists. - changes.replaceNode(exportingSourceFile, exportNode, factory.createExportDefault(Debug.checkDefined(decl.initializer, "Initializer was previously known to be present"))); - break; - } - // falls through - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ModuleDeclaration: - // `export type T = number;` -> `type T = number; export default T;` - changes.deleteModifier(exportingSourceFile, exportKeyword); - changes.insertNodeAfter(exportingSourceFile, exportNode, factory.createExportDefault(factory.createIdentifier(exportName.text))); - break; - default: - Debug.fail(`Unexpected exportNode kind ${(exportNode as ExportToConvert).kind}`); - } + changeNamedToDefaultImport(importingSourceFile, ref, changes); } - } + }); +} - function changeImports(program: Program, { wasDefault, exportName, exportingModuleSymbol }: ExportInfo, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { - const checker = program.getTypeChecker(); - const exportSymbol = Debug.checkDefined(checker.getSymbolAtLocation(exportName), "Export name should resolve to a symbol"); - FindAllReferences.Core.eachExportReference(program.getSourceFiles(), checker, cancellationToken, exportSymbol, exportingModuleSymbol, exportName.text, wasDefault, ref => { - const importingSourceFile = ref.getSourceFile(); - if (wasDefault) { - changeDefaultToNamedImport(importingSourceFile, ref, changes, exportName.text); - } - else { - changeNamedToDefaultImport(importingSourceFile, ref, changes); +function changeDefaultToNamedImport(importingSourceFile: SourceFile, ref: Identifier, changes: textChanges.ChangeTracker, exportName: string): void { + const { parent } = ref; + switch (parent.kind) { + case SyntaxKind.PropertyAccessExpression: + // `a.default` --> `a.foo` + changes.replaceNode(importingSourceFile, ref, factory.createIdentifier(exportName)); + break; + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: { + const spec = parent as ImportSpecifier | ExportSpecifier; + // `default as foo` --> `foo`, `default as bar` --> `foo as bar` + changes.replaceNode(importingSourceFile, spec, makeImportSpecifier(exportName, spec.name.text)); + break; + } + case SyntaxKind.ImportClause: { + const clause = parent as ImportClause; + Debug.assert(clause.name === ref, "Import clause name should match provided ref"); + const spec = makeImportSpecifier(exportName, ref.text); + const { namedBindings } = clause; + if (!namedBindings) { + // `import foo from "./a";` --> `import { foo } from "./a";` + changes.replaceNode(importingSourceFile, ref, factory.createNamedImports([spec])); } - }); - } - - function changeDefaultToNamedImport(importingSourceFile: SourceFile, ref: Identifier, changes: textChanges.ChangeTracker, exportName: string): void { - const { parent } = ref; - switch (parent.kind) { - case SyntaxKind.PropertyAccessExpression: - // `a.default` --> `a.foo` - changes.replaceNode(importingSourceFile, ref, factory.createIdentifier(exportName)); - break; - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: { - const spec = parent as ImportSpecifier | ExportSpecifier; - // `default as foo` --> `foo`, `default as bar` --> `foo as bar` - changes.replaceNode(importingSourceFile, spec, makeImportSpecifier(exportName, spec.name.text)); - break; + else if (namedBindings.kind === SyntaxKind.NamespaceImport) { + // `import foo, * as a from "./a";` --> `import * as a from ".a/"; import { foo } from "./a";` + changes.deleteRange(importingSourceFile, { pos: ref.getStart(importingSourceFile), end: namedBindings.getStart(importingSourceFile) }); + const quotePreference = isStringLiteral(clause.parent.moduleSpecifier) ? quotePreferenceFromString(clause.parent.moduleSpecifier, importingSourceFile) : QuotePreference.Double; + const newImport = makeImport(/*default*/ undefined, [makeImportSpecifier(exportName, ref.text)], clause.parent.moduleSpecifier, quotePreference); + changes.insertNodeAfter(importingSourceFile, clause.parent, newImport); } - case SyntaxKind.ImportClause: { - const clause = parent as ImportClause; - Debug.assert(clause.name === ref, "Import clause name should match provided ref"); - const spec = makeImportSpecifier(exportName, ref.text); - const { namedBindings } = clause; - if (!namedBindings) { - // `import foo from "./a";` --> `import { foo } from "./a";` - changes.replaceNode(importingSourceFile, ref, factory.createNamedImports([spec])); - } - else if (namedBindings.kind === SyntaxKind.NamespaceImport) { - // `import foo, * as a from "./a";` --> `import * as a from ".a/"; import { foo } from "./a";` - changes.deleteRange(importingSourceFile, { pos: ref.getStart(importingSourceFile), end: namedBindings.getStart(importingSourceFile) }); - const quotePreference = isStringLiteral(clause.parent.moduleSpecifier) ? quotePreferenceFromString(clause.parent.moduleSpecifier, importingSourceFile) : QuotePreference.Double; - const newImport = makeImport(/*default*/ undefined, [makeImportSpecifier(exportName, ref.text)], clause.parent.moduleSpecifier, quotePreference); - changes.insertNodeAfter(importingSourceFile, clause.parent, newImport); - } - else { - // `import foo, { bar } from "./a"` --> `import { bar, foo } from "./a";` - changes.delete(importingSourceFile, ref); - changes.insertNodeAtEndOfList(importingSourceFile, namedBindings.elements, spec); - } - break; + else { + // `import foo, { bar } from "./a"` --> `import { bar, foo } from "./a";` + changes.delete(importingSourceFile, ref); + changes.insertNodeAtEndOfList(importingSourceFile, namedBindings.elements, spec); } - default: - Debug.failBadSyntaxKind(parent); + break; } + default: + Debug.failBadSyntaxKind(parent); } +} - function changeNamedToDefaultImport(importingSourceFile: SourceFile, ref: Identifier, changes: textChanges.ChangeTracker): void { - const parent = ref.parent as PropertyAccessExpression | ImportSpecifier | ExportSpecifier; - switch (parent.kind) { - case SyntaxKind.PropertyAccessExpression: - // `a.foo` --> `a.default` - changes.replaceNode(importingSourceFile, ref, factory.createIdentifier("default")); - break; - case SyntaxKind.ImportSpecifier: { - // `import { foo } from "./a";` --> `import foo from "./a";` - // `import { foo as bar } from "./a";` --> `import bar from "./a";` - const defaultImport = factory.createIdentifier(parent.name.text); - if (parent.parent.elements.length === 1) { - changes.replaceNode(importingSourceFile, parent.parent, defaultImport); - } - else { - changes.delete(importingSourceFile, parent); - changes.insertNodeBefore(importingSourceFile, parent.parent, defaultImport); - } - break; +function changeNamedToDefaultImport(importingSourceFile: SourceFile, ref: Identifier, changes: textChanges.ChangeTracker): void { + const parent = ref.parent as PropertyAccessExpression | ImportSpecifier | ExportSpecifier; + switch (parent.kind) { + case SyntaxKind.PropertyAccessExpression: + // `a.foo` --> `a.default` + changes.replaceNode(importingSourceFile, ref, factory.createIdentifier("default")); + break; + case SyntaxKind.ImportSpecifier: { + // `import { foo } from "./a";` --> `import foo from "./a";` + // `import { foo as bar } from "./a";` --> `import bar from "./a";` + const defaultImport = factory.createIdentifier(parent.name.text); + if (parent.parent.elements.length === 1) { + changes.replaceNode(importingSourceFile, parent.parent, defaultImport); } - case SyntaxKind.ExportSpecifier: { - // `export { foo } from "./a";` --> `export { default as foo } from "./a";` - // `export { foo as bar } from "./a";` --> `export { default as bar } from "./a";` - // `export { foo as default } from "./a";` --> `export { default } from "./a";` - // (Because `export foo from "./a";` isn't valid syntax.) - changes.replaceNode(importingSourceFile, parent, makeExportSpecifier("default", parent.name.text)); - break; + else { + changes.delete(importingSourceFile, parent); + changes.insertNodeBefore(importingSourceFile, parent.parent, defaultImport); } - default: - Debug.assertNever(parent, `Unexpected parent kind ${(parent as Node).kind}`); + break; } - + case SyntaxKind.ExportSpecifier: { + // `export { foo } from "./a";` --> `export { default as foo } from "./a";` + // `export { foo as bar } from "./a";` --> `export { default as bar } from "./a";` + // `export { foo as default } from "./a";` --> `export { default } from "./a";` + // (Because `export foo from "./a";` isn't valid syntax.) + changes.replaceNode(importingSourceFile, parent, makeExportSpecifier("default", parent.name.text)); + break; + } + default: + Debug.assertNever(parent, `Unexpected parent kind ${(parent as Node).kind}`); } - function makeImportSpecifier(propertyName: string, name: string): ImportSpecifier { - return factory.createImportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name)); - } +} - function makeExportSpecifier(propertyName: string, name: string): ExportSpecifier { - return factory.createExportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name)); - } +function makeImportSpecifier(propertyName: string, name: string): ImportSpecifier { + return factory.createImportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name)); +} + +function makeExportSpecifier(propertyName: string, name: string): ExportSpecifier { + return factory.createExportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name)); +} } diff --git a/src/services/refactors/convertImport.ts b/src/services/refactors/convertImport.ts index 65a684d8c46d5..3d27986c7caf4 100644 --- a/src/services/refactors/convertImport.ts +++ b/src/services/refactors/convertImport.ts @@ -1,203 +1,203 @@ /* @internal */ namespace ts.refactor { - const refactorName = "Convert import"; - - const namespaceToNamedAction = { - name: "Convert namespace import to named imports", - description: Diagnostics.Convert_namespace_import_to_named_imports.message, - kind: "refactor.rewrite.import.named", - }; - const namedToNamespaceAction = { - name: "Convert named imports to namespace import", - description: Diagnostics.Convert_named_imports_to_namespace_import.message, - kind: "refactor.rewrite.import.namespace", - }; - - registerRefactor(refactorName, { - kinds: [ - namespaceToNamedAction.kind, - namedToNamespaceAction.kind - ], - getAvailableActions(context): readonly ApplicableRefactorInfo[] { - const info = getImportToConvert(context, context.triggerReason === "invoked"); - if (!info) return emptyArray; - - if (!isRefactorErrorInfo(info)) { - const namespaceImport = info.kind === SyntaxKind.NamespaceImport; - const action = namespaceImport ? namespaceToNamedAction : namedToNamespaceAction; - return [{ name: refactorName, description: action.description, actions: [action] }]; - } - - if (context.preferences.provideRefactorNotApplicableReason) { - return [ - { name: refactorName, description: namespaceToNamedAction.description, - actions: [{ ...namespaceToNamedAction, notApplicableReason: info.error }] }, - { name: refactorName, description: namedToNamespaceAction.description, - actions: [{ ...namedToNamespaceAction, notApplicableReason: info.error }] } - ]; - } - - return emptyArray; - }, - getEditsForAction(context, actionName): RefactorEditInfo { - Debug.assert(actionName === namespaceToNamedAction.name || actionName === namedToNamespaceAction.name, "Unexpected action name"); - const info = getImportToConvert(context); - Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); - const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, t, info)); - return { edits, renameFilename: undefined, renameLocation: undefined }; - } - }); - - // Can convert imports of the form `import * as m from "m";` or `import d, { x, y } from "m";`. - function getImportToConvert(context: RefactorContext, considerPartialSpans = true): NamedImportBindings | RefactorErrorInfo | undefined { - const { file } = context; - const span = getRefactorContextSpan(context); - const token = getTokenAtPosition(file, span.start); - const importDecl = considerPartialSpans ? findAncestor(token, isImportDeclaration) : getParentNodeInSpan(token, file, span); - if (!importDecl || !isImportDeclaration(importDecl)) return { error: "Selection is not an import declaration." }; - - const end = span.start + span.length; - const nextToken = findNextToken(importDecl, importDecl.parent, file); - if (nextToken && end > nextToken.getStart()) return undefined; - - const { importClause } = importDecl; - if (!importClause) { - return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_import_clause) }; +const refactorName = "Convert import"; + +const namespaceToNamedAction = { + name: "Convert namespace import to named imports", + description: Diagnostics.Convert_namespace_import_to_named_imports.message, + kind: "refactor.rewrite.import.named", +}; +const namedToNamespaceAction = { + name: "Convert named imports to namespace import", + description: Diagnostics.Convert_named_imports_to_namespace_import.message, + kind: "refactor.rewrite.import.namespace", +}; + +registerRefactor(refactorName, { + kinds: [ + namespaceToNamedAction.kind, + namedToNamespaceAction.kind + ], + getAvailableActions(context): readonly ApplicableRefactorInfo[] { + const info = getImportToConvert(context, context.triggerReason === "invoked"); + if (!info) return emptyArray; + + if (!isRefactorErrorInfo(info)) { + const namespaceImport = info.kind === SyntaxKind.NamespaceImport; + const action = namespaceImport ? namespaceToNamedAction : namedToNamespaceAction; + return [{ name: refactorName, description: action.description, actions: [action] }]; } - if (!importClause.namedBindings) { - return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_namespace_import_or_named_imports) }; + if (context.preferences.provideRefactorNotApplicableReason) { + return [ + { name: refactorName, description: namespaceToNamedAction.description, + actions: [{ ...namespaceToNamedAction, notApplicableReason: info.error }] }, + { name: refactorName, description: namedToNamespaceAction.description, + actions: [{ ...namedToNamespaceAction, notApplicableReason: info.error }] } + ]; } - return importClause.namedBindings; + return emptyArray; + }, + getEditsForAction(context, actionName): RefactorEditInfo { + Debug.assert(actionName === namespaceToNamedAction.name || actionName === namedToNamespaceAction.name, "Unexpected action name"); + const info = getImportToConvert(context); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); + const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, t, info)); + return { edits, renameFilename: undefined, renameLocation: undefined }; + } +}); + +// Can convert imports of the form `import * as m from "m";` or `import d, { x, y } from "m";`. +function getImportToConvert(context: RefactorContext, considerPartialSpans = true): NamedImportBindings | RefactorErrorInfo | undefined { + const { file } = context; + const span = getRefactorContextSpan(context); + const token = getTokenAtPosition(file, span.start); + const importDecl = considerPartialSpans ? findAncestor(token, isImportDeclaration) : getParentNodeInSpan(token, file, span); + if (!importDecl || !isImportDeclaration(importDecl)) return { error: "Selection is not an import declaration." }; + + const end = span.start + span.length; + const nextToken = findNextToken(importDecl, importDecl.parent, file); + if (nextToken && end > nextToken.getStart()) return undefined; + + const { importClause } = importDecl; + if (!importClause) { + return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_import_clause) }; } - function doChange(sourceFile: SourceFile, program: Program, changes: textChanges.ChangeTracker, toConvert: NamedImportBindings): void { - const checker = program.getTypeChecker(); - if (toConvert.kind === SyntaxKind.NamespaceImport) { - doChangeNamespaceToNamed(sourceFile, checker, changes, toConvert, getAllowSyntheticDefaultImports(program.getCompilerOptions())); - } - else { - doChangeNamedToNamespace(sourceFile, checker, changes, toConvert); - } + if (!importClause.namedBindings) { + return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_namespace_import_or_named_imports) }; } - function doChangeNamespaceToNamed(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, toConvert: NamespaceImport, allowSyntheticDefaultImports: boolean): void { - let usedAsNamespaceOrDefault = false; + return importClause.namedBindings; +} - const nodesToReplace: (PropertyAccessExpression | QualifiedName)[] = []; - const conflictingNames = new Map(); +function doChange(sourceFile: SourceFile, program: Program, changes: textChanges.ChangeTracker, toConvert: NamedImportBindings): void { + const checker = program.getTypeChecker(); + if (toConvert.kind === SyntaxKind.NamespaceImport) { + doChangeNamespaceToNamed(sourceFile, checker, changes, toConvert, getAllowSyntheticDefaultImports(program.getCompilerOptions())); + } + else { + doChangeNamedToNamespace(sourceFile, checker, changes, toConvert); + } +} - FindAllReferences.Core.eachSymbolReferenceInFile(toConvert.name, checker, sourceFile, id => { - if (!isPropertyAccessOrQualifiedName(id.parent)) { - usedAsNamespaceOrDefault = true; - } - else { - const exportName = getRightOfPropertyAccessOrQualifiedName(id.parent).text; - if (checker.resolveName(exportName, id, SymbolFlags.All, /*excludeGlobals*/ true)) { - conflictingNames.set(exportName, true); - } - Debug.assert(getLeftOfPropertyAccessOrQualifiedName(id.parent) === id, "Parent expression should match id"); - nodesToReplace.push(id.parent); - } - }); +function doChangeNamespaceToNamed(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, toConvert: NamespaceImport, allowSyntheticDefaultImports: boolean): void { + let usedAsNamespaceOrDefault = false; - // We may need to change `mod.x` to `_x` to avoid a name conflict. - const exportNameToImportName = new Map(); + const nodesToReplace: (PropertyAccessExpression | QualifiedName)[] = []; + const conflictingNames = new Map(); - for (const propertyAccessOrQualifiedName of nodesToReplace) { - const exportName = getRightOfPropertyAccessOrQualifiedName(propertyAccessOrQualifiedName).text; - let importName = exportNameToImportName.get(exportName); - if (importName === undefined) { - exportNameToImportName.set(exportName, importName = conflictingNames.has(exportName) ? getUniqueName(exportName, sourceFile) : exportName); + FindAllReferences.Core.eachSymbolReferenceInFile(toConvert.name, checker, sourceFile, id => { + if (!isPropertyAccessOrQualifiedName(id.parent)) { + usedAsNamespaceOrDefault = true; + } + else { + const exportName = getRightOfPropertyAccessOrQualifiedName(id.parent).text; + if (checker.resolveName(exportName, id, SymbolFlags.All, /*excludeGlobals*/ true)) { + conflictingNames.set(exportName, true); } - changes.replaceNode(sourceFile, propertyAccessOrQualifiedName, factory.createIdentifier(importName)); + Debug.assert(getLeftOfPropertyAccessOrQualifiedName(id.parent) === id, "Parent expression should match id"); + nodesToReplace.push(id.parent); } + }); - const importSpecifiers: ImportSpecifier[] = []; - exportNameToImportName.forEach((name, propertyName) => { - importSpecifiers.push(factory.createImportSpecifier(/*isTypeOnly*/ false, name === propertyName ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name))); - }); + // We may need to change `mod.x` to `_x` to avoid a name conflict. + const exportNameToImportName = new Map(); - const importDecl = toConvert.parent.parent; - if (usedAsNamespaceOrDefault && !allowSyntheticDefaultImports) { - // Need to leave the namespace import alone - changes.insertNodeAfter(sourceFile, importDecl, updateImport(importDecl, /*defaultImportName*/ undefined, importSpecifiers)); - } - else { - changes.replaceNode(sourceFile, importDecl, updateImport(importDecl, usedAsNamespaceOrDefault ? factory.createIdentifier(toConvert.name.text) : undefined, importSpecifiers)); + for (const propertyAccessOrQualifiedName of nodesToReplace) { + const exportName = getRightOfPropertyAccessOrQualifiedName(propertyAccessOrQualifiedName).text; + let importName = exportNameToImportName.get(exportName); + if (importName === undefined) { + exportNameToImportName.set(exportName, importName = conflictingNames.has(exportName) ? getUniqueName(exportName, sourceFile) : exportName); } + changes.replaceNode(sourceFile, propertyAccessOrQualifiedName, factory.createIdentifier(importName)); } - function getRightOfPropertyAccessOrQualifiedName(propertyAccessOrQualifiedName: PropertyAccessExpression | QualifiedName) { - return isPropertyAccessExpression(propertyAccessOrQualifiedName) ? propertyAccessOrQualifiedName.name : propertyAccessOrQualifiedName.right; - } + const importSpecifiers: ImportSpecifier[] = []; + exportNameToImportName.forEach((name, propertyName) => { + importSpecifiers.push(factory.createImportSpecifier(/*isTypeOnly*/ false, name === propertyName ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name))); + }); - function getLeftOfPropertyAccessOrQualifiedName(propertyAccessOrQualifiedName: PropertyAccessExpression | QualifiedName) { - return isPropertyAccessExpression(propertyAccessOrQualifiedName) ? propertyAccessOrQualifiedName.expression : propertyAccessOrQualifiedName.left; + const importDecl = toConvert.parent.parent; + if (usedAsNamespaceOrDefault && !allowSyntheticDefaultImports) { + // Need to leave the namespace import alone + changes.insertNodeAfter(sourceFile, importDecl, updateImport(importDecl, /*defaultImportName*/ undefined, importSpecifiers)); + } + else { + changes.replaceNode(sourceFile, importDecl, updateImport(importDecl, usedAsNamespaceOrDefault ? factory.createIdentifier(toConvert.name.text) : undefined, importSpecifiers)); } +} - function doChangeNamedToNamespace(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, toConvert: NamedImports): void { - const importDecl = toConvert.parent.parent; - const { moduleSpecifier } = importDecl; +function getRightOfPropertyAccessOrQualifiedName(propertyAccessOrQualifiedName: PropertyAccessExpression | QualifiedName) { + return isPropertyAccessExpression(propertyAccessOrQualifiedName) ? propertyAccessOrQualifiedName.name : propertyAccessOrQualifiedName.right; +} - const toConvertSymbols: Set = new Set(); - toConvert.elements.forEach(namedImport => { - const symbol = checker.getSymbolAtLocation(namedImport.name); - if (symbol) { - toConvertSymbols.add(symbol); - } - }); - const preferredName = moduleSpecifier && isStringLiteral(moduleSpecifier) ? codefix.moduleSpecifierToValidIdentifier(moduleSpecifier.text, ScriptTarget.ESNext) : "module"; - function hasNamespaceNameConflict(namedImport: ImportSpecifier): boolean { - // We need to check if the preferred namespace name (`preferredName`) we'd like to use in the refactored code will present a name conflict. - // A name conflict means that, in a scope where we would like to use the preferred namespace name, there already exists a symbol with that name in that scope. - // We are going to use the namespace name in the scopes the named imports being refactored are referenced, - // so we look for conflicts by looking at every reference to those named imports. - return !!FindAllReferences.Core.eachSymbolReferenceInFile(namedImport.name, checker, sourceFile, id => { - const symbol = checker.resolveName(preferredName, id, SymbolFlags.All, /*excludeGlobals*/ true); - if (symbol) { // There already is a symbol with the same name as the preferred namespace name. - if (toConvertSymbols.has(symbol)) { // `preferredName` resolves to a symbol for one of the named import references we are going to transform into namespace import references... - return isExportSpecifier(id.parent); // ...but if this reference is an export specifier, it will not be transformed, so it is a conflict; otherwise, it will be renamed and is not a conflict. - } - return true; // `preferredName` resolves to any other symbol, which will be present in the refactored code and so poses a name conflict. - } - return false; // There is no symbol with the same name as the preferred namespace name, so no conflict. - }); - } - const namespaceNameConflicts = toConvert.elements.some(hasNamespaceNameConflict); - const namespaceImportName = namespaceNameConflicts ? getUniqueName(preferredName, sourceFile) : preferredName; - - // Imports that need to be kept as named imports in the refactored code, to avoid changing the semantics. - // More specifically, those are named imports that appear in named exports in the original code, e.g. `a` in `import { a } from "m"; export { a }`. - const neededNamedImports: Set = new Set(); - - for (const element of toConvert.elements) { - const propertyName = (element.propertyName || element.name).text; - FindAllReferences.Core.eachSymbolReferenceInFile(element.name, checker, sourceFile, id => { - const access = factory.createPropertyAccessExpression(factory.createIdentifier(namespaceImportName), propertyName); - if (isShorthandPropertyAssignment(id.parent)) { - changes.replaceNode(sourceFile, id.parent, factory.createPropertyAssignment(id.text, access)); - } - else if (isExportSpecifier(id.parent)) { - neededNamedImports.add(element); - } - else { - changes.replaceNode(sourceFile, id, access); - } - }); - } +function getLeftOfPropertyAccessOrQualifiedName(propertyAccessOrQualifiedName: PropertyAccessExpression | QualifiedName) { + return isPropertyAccessExpression(propertyAccessOrQualifiedName) ? propertyAccessOrQualifiedName.expression : propertyAccessOrQualifiedName.left; +} + +function doChangeNamedToNamespace(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, toConvert: NamedImports): void { + const importDecl = toConvert.parent.parent; + const { moduleSpecifier } = importDecl; - changes.replaceNode(sourceFile, toConvert, factory.createNamespaceImport(factory.createIdentifier(namespaceImportName))); - if (neededNamedImports.size) { - const newNamedImports: ImportSpecifier[] = arrayFrom(neededNamedImports.values()).map(element => - factory.createImportSpecifier(element.isTypeOnly, element.propertyName && factory.createIdentifier(element.propertyName.text), factory.createIdentifier(element.name.text))); - changes.insertNodeAfter(sourceFile, toConvert.parent.parent, updateImport(importDecl, /*defaultImportName*/ undefined, newNamedImports)); + const toConvertSymbols: Set = new Set(); + toConvert.elements.forEach(namedImport => { + const symbol = checker.getSymbolAtLocation(namedImport.name); + if (symbol) { + toConvertSymbols.add(symbol); } + }); + const preferredName = moduleSpecifier && isStringLiteral(moduleSpecifier) ? codefix.moduleSpecifierToValidIdentifier(moduleSpecifier.text, ScriptTarget.ESNext) : "module"; + function hasNamespaceNameConflict(namedImport: ImportSpecifier): boolean { + // We need to check if the preferred namespace name (`preferredName`) we'd like to use in the refactored code will present a name conflict. + // A name conflict means that, in a scope where we would like to use the preferred namespace name, there already exists a symbol with that name in that scope. + // We are going to use the namespace name in the scopes the named imports being refactored are referenced, + // so we look for conflicts by looking at every reference to those named imports. + return !!FindAllReferences.Core.eachSymbolReferenceInFile(namedImport.name, checker, sourceFile, id => { + const symbol = checker.resolveName(preferredName, id, SymbolFlags.All, /*excludeGlobals*/ true); + if (symbol) { // There already is a symbol with the same name as the preferred namespace name. + if (toConvertSymbols.has(symbol)) { // `preferredName` resolves to a symbol for one of the named import references we are going to transform into namespace import references... + return isExportSpecifier(id.parent); // ...but if this reference is an export specifier, it will not be transformed, so it is a conflict; otherwise, it will be renamed and is not a conflict. + } + return true; // `preferredName` resolves to any other symbol, which will be present in the refactored code and so poses a name conflict. + } + return false; // There is no symbol with the same name as the preferred namespace name, so no conflict. + }); + } + const namespaceNameConflicts = toConvert.elements.some(hasNamespaceNameConflict); + const namespaceImportName = namespaceNameConflicts ? getUniqueName(preferredName, sourceFile) : preferredName; + + // Imports that need to be kept as named imports in the refactored code, to avoid changing the semantics. + // More specifically, those are named imports that appear in named exports in the original code, e.g. `a` in `import { a } from "m"; export { a }`. + const neededNamedImports: Set = new Set(); + + for (const element of toConvert.elements) { + const propertyName = (element.propertyName || element.name).text; + FindAllReferences.Core.eachSymbolReferenceInFile(element.name, checker, sourceFile, id => { + const access = factory.createPropertyAccessExpression(factory.createIdentifier(namespaceImportName), propertyName); + if (isShorthandPropertyAssignment(id.parent)) { + changes.replaceNode(sourceFile, id.parent, factory.createPropertyAssignment(id.text, access)); + } + else if (isExportSpecifier(id.parent)) { + neededNamedImports.add(element); + } + else { + changes.replaceNode(sourceFile, id, access); + } + }); } - function updateImport(old: ImportDeclaration, defaultImportName: Identifier | undefined, elements: readonly ImportSpecifier[] | undefined): ImportDeclaration { - return factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, - factory.createImportClause(/*isTypeOnly*/ false, defaultImportName, elements && elements.length ? factory.createNamedImports(elements) : undefined), old.moduleSpecifier, /*assertClause*/ undefined); + changes.replaceNode(sourceFile, toConvert, factory.createNamespaceImport(factory.createIdentifier(namespaceImportName))); + if (neededNamedImports.size) { + const newNamedImports: ImportSpecifier[] = arrayFrom(neededNamedImports.values()).map(element => + factory.createImportSpecifier(element.isTypeOnly, element.propertyName && factory.createIdentifier(element.propertyName.text), factory.createIdentifier(element.name.text))); + changes.insertNodeAfter(sourceFile, toConvert.parent.parent, updateImport(importDecl, /*defaultImportName*/ undefined, newNamedImports)); } } + +function updateImport(old: ImportDeclaration, defaultImportName: Identifier | undefined, elements: readonly ImportSpecifier[] | undefined): ImportDeclaration { + return factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, + factory.createImportClause(/*isTypeOnly*/ false, defaultImportName, elements && elements.length ? factory.createNamedImports(elements) : undefined), old.moduleSpecifier, /*assertClause*/ undefined); +} +} diff --git a/src/services/refactors/convertOverloadListToSingleSignature.ts b/src/services/refactors/convertOverloadListToSingleSignature.ts index 5c49bf06d0970..8e8b085728e61 100644 --- a/src/services/refactors/convertOverloadListToSingleSignature.ts +++ b/src/services/refactors/convertOverloadListToSingleSignature.ts @@ -1,226 +1,226 @@ /* @internal */ namespace ts.refactor.addOrRemoveBracesToArrowFunction { - const refactorName = "Convert overload list to single signature"; - const refactorDescription = Diagnostics.Convert_overload_list_to_single_signature.message; - - const functionOverloadAction = { +const refactorName = "Convert overload list to single signature"; +const refactorDescription = Diagnostics.Convert_overload_list_to_single_signature.message; + +const functionOverloadAction = { + name: refactorName, + description: refactorDescription, + kind: "refactor.rewrite.function.overloadList", +}; +registerRefactor(refactorName, { + kinds: [functionOverloadAction.kind], + getEditsForAction, + getAvailableActions +}); + +function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const { file, startPosition, program } = context; + const info = getConvertableOverloadListAtPosition(file, startPosition, program); + if (!info) return emptyArray; + + return [{ name: refactorName, description: refactorDescription, - kind: "refactor.rewrite.function.overloadList", - }; - registerRefactor(refactorName, { - kinds: [functionOverloadAction.kind], - getEditsForAction, - getAvailableActions - }); - - function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const { file, startPosition, program } = context; - const info = getConvertableOverloadListAtPosition(file, startPosition, program); - if (!info) return emptyArray; - - return [{ - name: refactorName, - description: refactorDescription, - actions: [functionOverloadAction] - }]; - } + actions: [functionOverloadAction] + }]; +} - function getEditsForAction(context: RefactorContext): RefactorEditInfo | undefined { - const { file, startPosition, program } = context; - const signatureDecls = getConvertableOverloadListAtPosition(file, startPosition, program); - if (!signatureDecls) return undefined; - - const checker = program.getTypeChecker(); - - const lastDeclaration = signatureDecls[signatureDecls.length - 1]; - let updated = lastDeclaration; - switch (lastDeclaration.kind) { - case SyntaxKind.MethodSignature: { - updated = factory.updateMethodSignature( - lastDeclaration, - lastDeclaration.modifiers, - lastDeclaration.name, - lastDeclaration.questionToken, - lastDeclaration.typeParameters, - getNewParametersForCombinedSignature(signatureDecls), - lastDeclaration.type, - ); - break; - } - case SyntaxKind.MethodDeclaration: { - updated = factory.updateMethodDeclaration( - lastDeclaration, - lastDeclaration.decorators, - lastDeclaration.modifiers, - lastDeclaration.asteriskToken, - lastDeclaration.name, - lastDeclaration.questionToken, - lastDeclaration.typeParameters, - getNewParametersForCombinedSignature(signatureDecls), - lastDeclaration.type, - lastDeclaration.body - ); - break; - } - case SyntaxKind.CallSignature: { - updated = factory.updateCallSignature( - lastDeclaration, - lastDeclaration.typeParameters, - getNewParametersForCombinedSignature(signatureDecls), - lastDeclaration.type, - ); - break; - } - case SyntaxKind.Constructor: { - updated = factory.updateConstructorDeclaration( - lastDeclaration, - lastDeclaration.decorators, - lastDeclaration.modifiers, - getNewParametersForCombinedSignature(signatureDecls), - lastDeclaration.body - ); - break; - } - case SyntaxKind.ConstructSignature: { - updated = factory.updateConstructSignature( - lastDeclaration, - lastDeclaration.typeParameters, - getNewParametersForCombinedSignature(signatureDecls), - lastDeclaration.type, - ); - break; - } - case SyntaxKind.FunctionDeclaration: { - updated = factory.updateFunctionDeclaration( - lastDeclaration, - lastDeclaration.decorators, - lastDeclaration.modifiers, - lastDeclaration.asteriskToken, - lastDeclaration.name, - lastDeclaration.typeParameters, - getNewParametersForCombinedSignature(signatureDecls), - lastDeclaration.type, - lastDeclaration.body - ); - break; - } - default: return Debug.failBadSyntaxKind(lastDeclaration, "Unhandled signature kind in overload list conversion refactoring"); +function getEditsForAction(context: RefactorContext): RefactorEditInfo | undefined { + const { file, startPosition, program } = context; + const signatureDecls = getConvertableOverloadListAtPosition(file, startPosition, program); + if (!signatureDecls) return undefined; + + const checker = program.getTypeChecker(); + + const lastDeclaration = signatureDecls[signatureDecls.length - 1]; + let updated = lastDeclaration; + switch (lastDeclaration.kind) { + case SyntaxKind.MethodSignature: { + updated = factory.updateMethodSignature( + lastDeclaration, + lastDeclaration.modifiers, + lastDeclaration.name, + lastDeclaration.questionToken, + lastDeclaration.typeParameters, + getNewParametersForCombinedSignature(signatureDecls), + lastDeclaration.type, + ); + break; } - - if (updated === lastDeclaration) { - return; // No edits to apply, do nothing + case SyntaxKind.MethodDeclaration: { + updated = factory.updateMethodDeclaration( + lastDeclaration, + lastDeclaration.decorators, + lastDeclaration.modifiers, + lastDeclaration.asteriskToken, + lastDeclaration.name, + lastDeclaration.questionToken, + lastDeclaration.typeParameters, + getNewParametersForCombinedSignature(signatureDecls), + lastDeclaration.type, + lastDeclaration.body + ); + break; } + case SyntaxKind.CallSignature: { + updated = factory.updateCallSignature( + lastDeclaration, + lastDeclaration.typeParameters, + getNewParametersForCombinedSignature(signatureDecls), + lastDeclaration.type, + ); + break; + } + case SyntaxKind.Constructor: { + updated = factory.updateConstructorDeclaration( + lastDeclaration, + lastDeclaration.decorators, + lastDeclaration.modifiers, + getNewParametersForCombinedSignature(signatureDecls), + lastDeclaration.body + ); + break; + } + case SyntaxKind.ConstructSignature: { + updated = factory.updateConstructSignature( + lastDeclaration, + lastDeclaration.typeParameters, + getNewParametersForCombinedSignature(signatureDecls), + lastDeclaration.type, + ); + break; + } + case SyntaxKind.FunctionDeclaration: { + updated = factory.updateFunctionDeclaration( + lastDeclaration, + lastDeclaration.decorators, + lastDeclaration.modifiers, + lastDeclaration.asteriskToken, + lastDeclaration.name, + lastDeclaration.typeParameters, + getNewParametersForCombinedSignature(signatureDecls), + lastDeclaration.type, + lastDeclaration.body + ); + break; + } + default: return Debug.failBadSyntaxKind(lastDeclaration, "Unhandled signature kind in overload list conversion refactoring"); + } + + if (updated === lastDeclaration) { + return; // No edits to apply, do nothing + } - const edits = textChanges.ChangeTracker.with(context, t => { - t.replaceNodeRange(file, signatureDecls[0], signatureDecls[signatureDecls.length - 1], updated); - }); + const edits = textChanges.ChangeTracker.with(context, t => { + t.replaceNodeRange(file, signatureDecls[0], signatureDecls[signatureDecls.length - 1], updated); + }); - return { renameFilename: undefined, renameLocation: undefined, edits }; + return { renameFilename: undefined, renameLocation: undefined, edits }; - function getNewParametersForCombinedSignature(signatureDeclarations: (MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]): NodeArray { - const lastSig = signatureDeclarations[signatureDeclarations.length - 1]; - if (isFunctionLikeDeclaration(lastSig) && lastSig.body) { - // Trim away implementation signature arguments (they should already be compatible with overloads, but are likely less precise to guarantee compatability with the overloads) - signatureDeclarations = signatureDeclarations.slice(0, signatureDeclarations.length - 1); - } - return factory.createNodeArray([ - factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - factory.createToken(SyntaxKind.DotDotDotToken), - "args", - /*questionToken*/ undefined, - factory.createUnionTypeNode(map(signatureDeclarations, convertSignatureParametersToTuple)) - ) - ]); + function getNewParametersForCombinedSignature(signatureDeclarations: (MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]): NodeArray { + const lastSig = signatureDeclarations[signatureDeclarations.length - 1]; + if (isFunctionLikeDeclaration(lastSig) && lastSig.body) { + // Trim away implementation signature arguments (they should already be compatible with overloads, but are likely less precise to guarantee compatability with the overloads) + signatureDeclarations = signatureDeclarations.slice(0, signatureDeclarations.length - 1); } + return factory.createNodeArray([ + factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createToken(SyntaxKind.DotDotDotToken), + "args", + /*questionToken*/ undefined, + factory.createUnionTypeNode(map(signatureDeclarations, convertSignatureParametersToTuple)) + ) + ]); + } - function convertSignatureParametersToTuple(decl: MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration): TupleTypeNode { - const members = map(decl.parameters, convertParameterToNamedTupleMember); - return setEmitFlags(factory.createTupleTypeNode(members), some(members, m => !!length(getSyntheticLeadingComments(m))) ? EmitFlags.None : EmitFlags.SingleLine); - } + function convertSignatureParametersToTuple(decl: MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration): TupleTypeNode { + const members = map(decl.parameters, convertParameterToNamedTupleMember); + return setEmitFlags(factory.createTupleTypeNode(members), some(members, m => !!length(getSyntheticLeadingComments(m))) ? EmitFlags.None : EmitFlags.SingleLine); + } - function convertParameterToNamedTupleMember(p: ParameterDeclaration): NamedTupleMember { - Debug.assert(isIdentifier(p.name)); // This is checked during refactoring applicability checking - const result = setTextRange(factory.createNamedTupleMember( - p.dotDotDotToken, - p.name, - p.questionToken, - p.type || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ), p); - const parameterDocComment = p.symbol && p.symbol.getDocumentationComment(checker); - if (parameterDocComment) { - const newComment = displayPartsToString(parameterDocComment); - if (newComment.length) { - setSyntheticLeadingComments(result, [{ - text: `* + function convertParameterToNamedTupleMember(p: ParameterDeclaration): NamedTupleMember { + Debug.assert(isIdentifier(p.name)); // This is checked during refactoring applicability checking + const result = setTextRange(factory.createNamedTupleMember( + p.dotDotDotToken, + p.name, + p.questionToken, + p.type || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ), p); + const parameterDocComment = p.symbol && p.symbol.getDocumentationComment(checker); + if (parameterDocComment) { + const newComment = displayPartsToString(parameterDocComment); + if (newComment.length) { + setSyntheticLeadingComments(result, [{ + text: `* ${newComment.split("\n").map(c => ` * ${c}`).join("\n")} `, - kind: SyntaxKind.MultiLineCommentTrivia, - pos: -1, - end: -1, - hasTrailingNewLine: true, - hasLeadingNewline: true, - }]); - } + kind: SyntaxKind.MultiLineCommentTrivia, + pos: -1, + end: -1, + hasTrailingNewLine: true, + hasLeadingNewline: true, + }]); } - return result; } - + return result; } - function isConvertableSignatureDeclaration(d: Node): d is MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration { - switch (d.kind) { - case SyntaxKind.MethodSignature: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.CallSignature: - case SyntaxKind.Constructor: - case SyntaxKind.ConstructSignature: - case SyntaxKind.FunctionDeclaration: - return true; - } - return false; - } +} - function getConvertableOverloadListAtPosition(file: SourceFile, startPosition: number, program: Program) { - const node = getTokenAtPosition(file, startPosition); - const containingDecl = findAncestor(node, isConvertableSignatureDeclaration); - if (!containingDecl) { - return; - } - const checker = program.getTypeChecker(); - const signatureSymbol = containingDecl.symbol; - if (!signatureSymbol) { - return; - } - const decls = signatureSymbol.declarations; - if (length(decls) <= 1) { - return; - } - if (!every(decls, d => getSourceFileOfNode(d) === file)) { - return; - } - if (!isConvertableSignatureDeclaration(decls![0])) { - return; - } - const kindOne = decls![0].kind; - if (!every(decls, d => d.kind === kindOne)) { - return; - } - const signatureDecls = decls as (MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]; - if (some(signatureDecls, d => !!d.typeParameters || some(d.parameters, p => !!p.decorators || !!p.modifiers || !isIdentifier(p.name)))) { - return; - } - const signatures = mapDefined(signatureDecls, d => checker.getSignatureFromDeclaration(d)); - if (length(signatures) !== length(decls)) { - return; - } - const returnOne = checker.getReturnTypeOfSignature(signatures[0]); - if (!every(signatures, s => checker.getReturnTypeOfSignature(s) === returnOne)) { - return; - } +function isConvertableSignatureDeclaration(d: Node): d is MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration { + switch (d.kind) { + case SyntaxKind.MethodSignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.CallSignature: + case SyntaxKind.Constructor: + case SyntaxKind.ConstructSignature: + case SyntaxKind.FunctionDeclaration: + return true; + } + return false; +} - return signatureDecls; +function getConvertableOverloadListAtPosition(file: SourceFile, startPosition: number, program: Program) { + const node = getTokenAtPosition(file, startPosition); + const containingDecl = findAncestor(node, isConvertableSignatureDeclaration); + if (!containingDecl) { + return; + } + const checker = program.getTypeChecker(); + const signatureSymbol = containingDecl.symbol; + if (!signatureSymbol) { + return; + } + const decls = signatureSymbol.declarations; + if (length(decls) <= 1) { + return; + } + if (!every(decls, d => getSourceFileOfNode(d) === file)) { + return; + } + if (!isConvertableSignatureDeclaration(decls![0])) { + return; } + const kindOne = decls![0].kind; + if (!every(decls, d => d.kind === kindOne)) { + return; + } + const signatureDecls = decls as (MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]; + if (some(signatureDecls, d => !!d.typeParameters || some(d.parameters, p => !!p.decorators || !!p.modifiers || !isIdentifier(p.name)))) { + return; + } + const signatures = mapDefined(signatureDecls, d => checker.getSignatureFromDeclaration(d)); + if (length(signatures) !== length(decls)) { + return; + } + const returnOne = checker.getReturnTypeOfSignature(signatures[0]); + if (!every(signatures, s => checker.getReturnTypeOfSignature(s) === returnOne)) { + return; + } + + return signatureDecls; +} } diff --git a/src/services/refactors/convertParamsToDestructuredObject.ts b/src/services/refactors/convertParamsToDestructuredObject.ts index cec2503f83fde..981406f76b72e 100644 --- a/src/services/refactors/convertParamsToDestructuredObject.ts +++ b/src/services/refactors/convertParamsToDestructuredObject.ts @@ -1,672 +1,672 @@ /* @internal */ namespace ts.refactor.convertParamsToDestructuredObject { - const refactorName = "Convert parameters to destructured object"; - const minimumParameterLength = 2; - const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_parameters_to_destructured_object); - - const toDestructuredAction = { +const refactorName = "Convert parameters to destructured object"; +const minimumParameterLength = 2; +const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_parameters_to_destructured_object); + +const toDestructuredAction = { + name: refactorName, + description: refactorDescription, + kind: "refactor.rewrite.parameters.toDestructured" +}; +registerRefactor(refactorName, { + kinds: [toDestructuredAction.kind], + getEditsForAction, + getAvailableActions +}); + +function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const { file, startPosition } = context; + const isJSFile = isSourceFileJS(file); + if (isJSFile) return emptyArray; // TODO: GH#30113 + const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, context.program.getTypeChecker()); + if (!functionDeclaration) return emptyArray; + + return [{ name: refactorName, description: refactorDescription, - kind: "refactor.rewrite.parameters.toDestructured" - }; - registerRefactor(refactorName, { - kinds: [toDestructuredAction.kind], - getEditsForAction, - getAvailableActions - }); + actions: [toDestructuredAction] + }]; +} - function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const { file, startPosition } = context; - const isJSFile = isSourceFileJS(file); - if (isJSFile) return emptyArray; // TODO: GH#30113 - const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, context.program.getTypeChecker()); - if (!functionDeclaration) return emptyArray; - - return [{ - name: refactorName, - description: refactorDescription, - actions: [toDestructuredAction] - }]; +function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + Debug.assert(actionName === refactorName, "Unexpected action name"); + const { file, startPosition, program, cancellationToken, host } = context; + const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, program.getTypeChecker()); + if (!functionDeclaration || !cancellationToken) return undefined; + + const groupedReferences = getGroupedReferences(functionDeclaration, program, cancellationToken); + if (groupedReferences.valid) { + const edits = textChanges.ChangeTracker.with(context, t => doChange(file, program, host, t, functionDeclaration, groupedReferences)); + return { renameFilename: undefined, renameLocation: undefined, edits }; } - function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - Debug.assert(actionName === refactorName, "Unexpected action name"); - const { file, startPosition, program, cancellationToken, host } = context; - const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, program.getTypeChecker()); - if (!functionDeclaration || !cancellationToken) return undefined; + return { edits: [] }; // TODO: GH#30113 +} - const groupedReferences = getGroupedReferences(functionDeclaration, program, cancellationToken); - if (groupedReferences.valid) { - const edits = textChanges.ChangeTracker.with(context, t => doChange(file, program, host, t, functionDeclaration, groupedReferences)); - return { renameFilename: undefined, renameLocation: undefined, edits }; +function doChange( + sourceFile: SourceFile, + program: Program, + host: LanguageServiceHost, + changes: textChanges.ChangeTracker, + functionDeclaration: ValidFunctionDeclaration, + groupedReferences: GroupedReferences): void { + const signature = groupedReferences.signature; + const newFunctionDeclarationParams = map(createNewParameters(functionDeclaration, program, host), param => getSynthesizedDeepClone(param)); + + if (signature) { + const newSignatureParams = map(createNewParameters(signature, program, host), param => getSynthesizedDeepClone(param)); + replaceParameters(signature, newSignatureParams); + } + replaceParameters(functionDeclaration, newFunctionDeclarationParams); + + const functionCalls = sortAndDeduplicate(groupedReferences.functionCalls, /*comparer*/ (a, b) => compareValues(a.pos, b.pos)); + for (const call of functionCalls) { + if (call.arguments && call.arguments.length) { + const newArgument = getSynthesizedDeepClone(createNewArgument(functionDeclaration, call.arguments), /*includeTrivia*/ true); + changes.replaceNodeRange( + getSourceFileOfNode(call), + first(call.arguments), + last(call.arguments), + newArgument, + { leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, trailingTriviaOption: textChanges.TrailingTriviaOption.Include }); } + } - return { edits: [] }; // TODO: GH#30113 + function replaceParameters(declarationOrSignature: ValidFunctionDeclaration | ValidMethodSignature, parameterDeclarations: ParameterDeclaration[]) { + changes.replaceNodeRangeWithNodes( + sourceFile, + first(declarationOrSignature.parameters), + last(declarationOrSignature.parameters), + parameterDeclarations, + { + joiner: ", ", + // indentation is set to 0 because otherwise the object parameter will be indented if there is a `this` parameter + indentation: 0, + leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, + trailingTriviaOption: textChanges.TrailingTriviaOption.Include + }); } +} - function doChange( - sourceFile: SourceFile, - program: Program, - host: LanguageServiceHost, - changes: textChanges.ChangeTracker, - functionDeclaration: ValidFunctionDeclaration, - groupedReferences: GroupedReferences): void { - const signature = groupedReferences.signature; - const newFunctionDeclarationParams = map(createNewParameters(functionDeclaration, program, host), param => getSynthesizedDeepClone(param)); - - if (signature) { - const newSignatureParams = map(createNewParameters(signature, program, host), param => getSynthesizedDeepClone(param)); - replaceParameters(signature, newSignatureParams); - } - replaceParameters(functionDeclaration, newFunctionDeclarationParams); - - const functionCalls = sortAndDeduplicate(groupedReferences.functionCalls, /*comparer*/ (a, b) => compareValues(a.pos, b.pos)); - for (const call of functionCalls) { - if (call.arguments && call.arguments.length) { - const newArgument = getSynthesizedDeepClone(createNewArgument(functionDeclaration, call.arguments), /*includeTrivia*/ true); - changes.replaceNodeRange( - getSourceFileOfNode(call), - first(call.arguments), - last(call.arguments), - newArgument, - { leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, trailingTriviaOption: textChanges.TrailingTriviaOption.Include }); - } - } +function getGroupedReferences(functionDeclaration: ValidFunctionDeclaration, program: Program, cancellationToken: CancellationToken): GroupedReferences { + const functionNames = getFunctionNames(functionDeclaration); + const classNames = isConstructorDeclaration(functionDeclaration) ? getClassNames(functionDeclaration) : []; + const names = deduplicate([...functionNames, ...classNames], equateValues); + const checker = program.getTypeChecker(); - function replaceParameters(declarationOrSignature: ValidFunctionDeclaration | ValidMethodSignature, parameterDeclarations: ParameterDeclaration[]) { - changes.replaceNodeRangeWithNodes( - sourceFile, - first(declarationOrSignature.parameters), - last(declarationOrSignature.parameters), - parameterDeclarations, - { - joiner: ", ", - // indentation is set to 0 because otherwise the object parameter will be indented if there is a `this` parameter - indentation: 0, - leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, - trailingTriviaOption: textChanges.TrailingTriviaOption.Include - }); - } - } + const references = flatMap(names, /*mapfn*/ name => FindAllReferences.getReferenceEntriesForNode(-1, name, program, program.getSourceFiles(), cancellationToken)); + const groupedReferences = groupReferences(references); - function getGroupedReferences(functionDeclaration: ValidFunctionDeclaration, program: Program, cancellationToken: CancellationToken): GroupedReferences { - const functionNames = getFunctionNames(functionDeclaration); - const classNames = isConstructorDeclaration(functionDeclaration) ? getClassNames(functionDeclaration) : []; - const names = deduplicate([...functionNames, ...classNames], equateValues); - const checker = program.getTypeChecker(); + if (!every(groupedReferences.declarations, /*callback*/ decl => contains(names, decl))) { + groupedReferences.valid = false; + } - const references = flatMap(names, /*mapfn*/ name => FindAllReferences.getReferenceEntriesForNode(-1, name, program, program.getSourceFiles(), cancellationToken)); - const groupedReferences = groupReferences(references); + return groupedReferences; - if (!every(groupedReferences.declarations, /*callback*/ decl => contains(names, decl))) { - groupedReferences.valid = false; - } + function groupReferences(referenceEntries: readonly FindAllReferences.Entry[]): GroupedReferences { + const classReferences: ClassReferences = { accessExpressions: [], typeUsages: [] }; + const groupedReferences: GroupedReferences = { functionCalls: [], declarations: [], classReferences, valid: true }; + const functionSymbols = map(functionNames, getSymbolTargetAtLocation); + const classSymbols = map(classNames, getSymbolTargetAtLocation); + const isConstructor = isConstructorDeclaration(functionDeclaration); + const contextualSymbols = map(functionNames, name => getSymbolForContextualType(name, checker)); - return groupedReferences; + for (const entry of referenceEntries) { + if (entry.kind === FindAllReferences.EntryKind.Span) { + groupedReferences.valid = false; + continue; + } - function groupReferences(referenceEntries: readonly FindAllReferences.Entry[]): GroupedReferences { - const classReferences: ClassReferences = { accessExpressions: [], typeUsages: [] }; - const groupedReferences: GroupedReferences = { functionCalls: [], declarations: [], classReferences, valid: true }; - const functionSymbols = map(functionNames, getSymbolTargetAtLocation); - const classSymbols = map(classNames, getSymbolTargetAtLocation); - const isConstructor = isConstructorDeclaration(functionDeclaration); - const contextualSymbols = map(functionNames, name => getSymbolForContextualType(name, checker)); - - for (const entry of referenceEntries) { - if (entry.kind === FindAllReferences.EntryKind.Span) { - groupedReferences.valid = false; + /* Declarations in object literals may be implementations of method signatures which have a different symbol from the declaration + For example: + interface IFoo { m(a: number): void } + const foo: IFoo = { m(a: number): void {} } + In these cases we get the symbol for the signature from the contextual type. + */ + if (contains(contextualSymbols, getSymbolTargetAtLocation(entry.node))) { + if (isValidMethodSignature(entry.node.parent)) { + groupedReferences.signature = entry.node.parent; + continue; + } + const call = entryToFunctionCall(entry); + if (call) { + groupedReferences.functionCalls.push(call); continue; } + } - /* Declarations in object literals may be implementations of method signatures which have a different symbol from the declaration - For example: - interface IFoo { m(a: number): void } - const foo: IFoo = { m(a: number): void {} } - In these cases we get the symbol for the signature from the contextual type. - */ - if (contains(contextualSymbols, getSymbolTargetAtLocation(entry.node))) { - if (isValidMethodSignature(entry.node.parent)) { - groupedReferences.signature = entry.node.parent; - continue; - } - const call = entryToFunctionCall(entry); - if (call) { - groupedReferences.functionCalls.push(call); - continue; - } + const contextualSymbol = getSymbolForContextualType(entry.node, checker); + if (contextualSymbol && contains(contextualSymbols, contextualSymbol)) { + const decl = entryToDeclaration(entry); + if (decl) { + groupedReferences.declarations.push(decl); + continue; } + } - const contextualSymbol = getSymbolForContextualType(entry.node, checker); - if (contextualSymbol && contains(contextualSymbols, contextualSymbol)) { - const decl = entryToDeclaration(entry); - if (decl) { - groupedReferences.declarations.push(decl); - continue; - } + /* We compare symbols because in some cases find all references wil return a reference that may or may not be to the refactored function. + Example from the refactorConvertParamsToDestructuredObject_methodCallUnion.ts test: + class A { foo(a: number, b: number) { return a + b; } } + class B { foo(c: number, d: number) { return c + d; } } + declare const ab: A | B; + ab.foo(1, 2); + Find all references will return `ab.foo(1, 2)` as a reference to A's `foo` but we could be calling B's `foo`. + When looking for constructor calls, however, the symbol on the constructor call reference is going to be the corresponding class symbol. + So we need to add a special case for this because when calling a constructor of a class through one of its subclasses, + the symbols are going to be different. + */ + if (contains(functionSymbols, getSymbolTargetAtLocation(entry.node)) || isNewExpressionTarget(entry.node)) { + const importOrExportReference = entryToImportOrExport(entry); + if (importOrExportReference) { + continue; + } + const decl = entryToDeclaration(entry); + if (decl) { + groupedReferences.declarations.push(decl); + continue; } - /* We compare symbols because in some cases find all references wil return a reference that may or may not be to the refactored function. - Example from the refactorConvertParamsToDestructuredObject_methodCallUnion.ts test: - class A { foo(a: number, b: number) { return a + b; } } - class B { foo(c: number, d: number) { return c + d; } } - declare const ab: A | B; - ab.foo(1, 2); - Find all references will return `ab.foo(1, 2)` as a reference to A's `foo` but we could be calling B's `foo`. - When looking for constructor calls, however, the symbol on the constructor call reference is going to be the corresponding class symbol. - So we need to add a special case for this because when calling a constructor of a class through one of its subclasses, - the symbols are going to be different. - */ - if (contains(functionSymbols, getSymbolTargetAtLocation(entry.node)) || isNewExpressionTarget(entry.node)) { - const importOrExportReference = entryToImportOrExport(entry); - if (importOrExportReference) { - continue; - } - const decl = entryToDeclaration(entry); - if (decl) { - groupedReferences.declarations.push(decl); - continue; - } + const call = entryToFunctionCall(entry); + if (call) { + groupedReferences.functionCalls.push(call); + continue; + } + } + // if the refactored function is a constructor, we must also check if the references to its class are valid + if (isConstructor && contains(classSymbols, getSymbolTargetAtLocation(entry.node))) { + const importOrExportReference = entryToImportOrExport(entry); + if (importOrExportReference) { + continue; + } - const call = entryToFunctionCall(entry); - if (call) { - groupedReferences.functionCalls.push(call); - continue; - } + const decl = entryToDeclaration(entry); + if (decl) { + groupedReferences.declarations.push(decl); + continue; } - // if the refactored function is a constructor, we must also check if the references to its class are valid - if (isConstructor && contains(classSymbols, getSymbolTargetAtLocation(entry.node))) { - const importOrExportReference = entryToImportOrExport(entry); - if (importOrExportReference) { - continue; - } - const decl = entryToDeclaration(entry); - if (decl) { - groupedReferences.declarations.push(decl); - continue; - } + const accessExpression = entryToAccessExpression(entry); + if (accessExpression) { + classReferences.accessExpressions.push(accessExpression); + continue; + } - const accessExpression = entryToAccessExpression(entry); - if (accessExpression) { - classReferences.accessExpressions.push(accessExpression); + // Only class declarations are allowed to be used as a type (in a heritage clause), + // otherwise `findAllReferences` might not be able to track constructor calls. + if (isClassDeclaration(functionDeclaration.parent)) { + const type = entryToType(entry); + if (type) { + classReferences.typeUsages.push(type); continue; } - - // Only class declarations are allowed to be used as a type (in a heritage clause), - // otherwise `findAllReferences` might not be able to track constructor calls. - if (isClassDeclaration(functionDeclaration.parent)) { - const type = entryToType(entry); - if (type) { - classReferences.typeUsages.push(type); - continue; - } - } } - groupedReferences.valid = false; } - - return groupedReferences; + groupedReferences.valid = false; } - function getSymbolTargetAtLocation(node: Node) { - const symbol = checker.getSymbolAtLocation(node); - return symbol && getSymbolTarget(symbol, checker); - } + return groupedReferences; } - /** - * Gets the symbol for the contextual type of the node if it is not a union or intersection. - */ - function getSymbolForContextualType(node: Node, checker: TypeChecker): Symbol | undefined { - const element = getContainingObjectLiteralElement(node); - if (element) { - const contextualType = checker.getContextualTypeForObjectLiteralElement(element as ObjectLiteralElementLike); - const symbol = contextualType?.getSymbol(); - if (symbol && !(getCheckFlags(symbol) & CheckFlags.Synthetic)) { - return symbol; - } + function getSymbolTargetAtLocation(node: Node) { + const symbol = checker.getSymbolAtLocation(node); + return symbol && getSymbolTarget(symbol, checker); + } +} + +/** + * Gets the symbol for the contextual type of the node if it is not a union or intersection. + */ +function getSymbolForContextualType(node: Node, checker: TypeChecker): Symbol | undefined { + const element = getContainingObjectLiteralElement(node); + if (element) { + const contextualType = checker.getContextualTypeForObjectLiteralElement(element as ObjectLiteralElementLike); + const symbol = contextualType?.getSymbol(); + if (symbol && !(getCheckFlags(symbol) & CheckFlags.Synthetic)) { + return symbol; } } +} - function entryToImportOrExport(entry: FindAllReferences.NodeEntry): Node | undefined { - const node = entry.node; +function entryToImportOrExport(entry: FindAllReferences.NodeEntry): Node | undefined { + const node = entry.node; - if (isImportSpecifier(node.parent) - || isImportClause(node.parent) - || isImportEqualsDeclaration(node.parent) - || isNamespaceImport(node.parent)) { - return node; - } + if (isImportSpecifier(node.parent) + || isImportClause(node.parent) + || isImportEqualsDeclaration(node.parent) + || isNamespaceImport(node.parent)) { + return node; + } - if (isExportSpecifier(node.parent) || isExportAssignment(node.parent)) { - return node; - } - return undefined; + if (isExportSpecifier(node.parent) || isExportAssignment(node.parent)) { + return node; } + return undefined; +} - function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | undefined { - if (isDeclaration(entry.node.parent)) { - return entry.node; - } - return undefined; +function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | undefined { + if (isDeclaration(entry.node.parent)) { + return entry.node; } + return undefined; +} - function entryToFunctionCall(entry: FindAllReferences.NodeEntry): CallExpression | NewExpression | undefined { - if (entry.node.parent) { - const functionReference = entry.node; - const parent = functionReference.parent; - switch (parent.kind) { - // foo(...) or super(...) or new Foo(...) - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - const callOrNewExpression = tryCast(parent, isCallOrNewExpression); - if (callOrNewExpression && callOrNewExpression.expression === functionReference) { +function entryToFunctionCall(entry: FindAllReferences.NodeEntry): CallExpression | NewExpression | undefined { + if (entry.node.parent) { + const functionReference = entry.node; + const parent = functionReference.parent; + switch (parent.kind) { + // foo(...) or super(...) or new Foo(...) + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + const callOrNewExpression = tryCast(parent, isCallOrNewExpression); + if (callOrNewExpression && callOrNewExpression.expression === functionReference) { + return callOrNewExpression; + } + break; + // x.foo(...) + case SyntaxKind.PropertyAccessExpression: + const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression); + if (propertyAccessExpression && propertyAccessExpression.parent && propertyAccessExpression.name === functionReference) { + const callOrNewExpression = tryCast(propertyAccessExpression.parent, isCallOrNewExpression); + if (callOrNewExpression && callOrNewExpression.expression === propertyAccessExpression) { return callOrNewExpression; } - break; - // x.foo(...) - case SyntaxKind.PropertyAccessExpression: - const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression); - if (propertyAccessExpression && propertyAccessExpression.parent && propertyAccessExpression.name === functionReference) { - const callOrNewExpression = tryCast(propertyAccessExpression.parent, isCallOrNewExpression); - if (callOrNewExpression && callOrNewExpression.expression === propertyAccessExpression) { - return callOrNewExpression; - } - } - break; - // x["foo"](...) - case SyntaxKind.ElementAccessExpression: - const elementAccessExpression = tryCast(parent, isElementAccessExpression); - if (elementAccessExpression && elementAccessExpression.parent && elementAccessExpression.argumentExpression === functionReference) { - const callOrNewExpression = tryCast(elementAccessExpression.parent, isCallOrNewExpression); - if (callOrNewExpression && callOrNewExpression.expression === elementAccessExpression) { - return callOrNewExpression; - } + } + break; + // x["foo"](...) + case SyntaxKind.ElementAccessExpression: + const elementAccessExpression = tryCast(parent, isElementAccessExpression); + if (elementAccessExpression && elementAccessExpression.parent && elementAccessExpression.argumentExpression === functionReference) { + const callOrNewExpression = tryCast(elementAccessExpression.parent, isCallOrNewExpression); + if (callOrNewExpression && callOrNewExpression.expression === elementAccessExpression) { + return callOrNewExpression; } - break; - } + } + break; } - return undefined; } + return undefined; +} - function entryToAccessExpression(entry: FindAllReferences.NodeEntry): ElementAccessExpression | PropertyAccessExpression | undefined { - if (entry.node.parent) { - const reference = entry.node; - const parent = reference.parent; - switch (parent.kind) { - // `C.foo` - case SyntaxKind.PropertyAccessExpression: - const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression); - if (propertyAccessExpression && propertyAccessExpression.expression === reference) { - return propertyAccessExpression; - } - break; - // `C["foo"]` - case SyntaxKind.ElementAccessExpression: - const elementAccessExpression = tryCast(parent, isElementAccessExpression); - if (elementAccessExpression && elementAccessExpression.expression === reference) { - return elementAccessExpression; - } - break; - } +function entryToAccessExpression(entry: FindAllReferences.NodeEntry): ElementAccessExpression | PropertyAccessExpression | undefined { + if (entry.node.parent) { + const reference = entry.node; + const parent = reference.parent; + switch (parent.kind) { + // `C.foo` + case SyntaxKind.PropertyAccessExpression: + const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression); + if (propertyAccessExpression && propertyAccessExpression.expression === reference) { + return propertyAccessExpression; + } + break; + // `C["foo"]` + case SyntaxKind.ElementAccessExpression: + const elementAccessExpression = tryCast(parent, isElementAccessExpression); + if (elementAccessExpression && elementAccessExpression.expression === reference) { + return elementAccessExpression; + } + break; } - return undefined; } + return undefined; +} - function entryToType(entry: FindAllReferences.NodeEntry): Node | undefined { - const reference = entry.node; - if (getMeaningFromLocation(reference) === SemanticMeaning.Type || isExpressionWithTypeArgumentsInClassExtendsClause(reference.parent)) { - return reference; - } - return undefined; +function entryToType(entry: FindAllReferences.NodeEntry): Node | undefined { + const reference = entry.node; + if (getMeaningFromLocation(reference) === SemanticMeaning.Type || isExpressionWithTypeArgumentsInClassExtendsClause(reference.parent)) { + return reference; } + return undefined; +} - function getFunctionDeclarationAtPosition(file: SourceFile, startPosition: number, checker: TypeChecker): ValidFunctionDeclaration | undefined { - const node = getTouchingToken(file, startPosition); - const functionDeclaration = getContainingFunctionDeclaration(node); +function getFunctionDeclarationAtPosition(file: SourceFile, startPosition: number, checker: TypeChecker): ValidFunctionDeclaration | undefined { + const node = getTouchingToken(file, startPosition); + const functionDeclaration = getContainingFunctionDeclaration(node); - // don't offer refactor on top-level JSDoc - if (isTopLevelJSDoc(node)) return undefined; + // don't offer refactor on top-level JSDoc + if (isTopLevelJSDoc(node)) return undefined; - if (functionDeclaration - && isValidFunctionDeclaration(functionDeclaration, checker) - && rangeContainsRange(functionDeclaration, node) - && !(functionDeclaration.body && rangeContainsRange(functionDeclaration.body, node))) return functionDeclaration; + if (functionDeclaration + && isValidFunctionDeclaration(functionDeclaration, checker) + && rangeContainsRange(functionDeclaration, node) + && !(functionDeclaration.body && rangeContainsRange(functionDeclaration.body, node))) return functionDeclaration; - return undefined; - } + return undefined; +} - function isTopLevelJSDoc(node: Node): boolean { - const containingJSDoc = findAncestor(node, isJSDocNode); - if (containingJSDoc) { - const containingNonJSDoc = findAncestor(containingJSDoc, n => !isJSDocNode(n)); - return !!containingNonJSDoc && isFunctionLikeDeclaration(containingNonJSDoc); - } - return false; +function isTopLevelJSDoc(node: Node): boolean { + const containingJSDoc = findAncestor(node, isJSDocNode); + if (containingJSDoc) { + const containingNonJSDoc = findAncestor(containingJSDoc, n => !isJSDocNode(n)); + return !!containingNonJSDoc && isFunctionLikeDeclaration(containingNonJSDoc); } + return false; +} - function isValidMethodSignature(node: Node): node is ValidMethodSignature { - return isMethodSignature(node) && (isInterfaceDeclaration(node.parent) || isTypeLiteralNode(node.parent)); - } +function isValidMethodSignature(node: Node): node is ValidMethodSignature { + return isMethodSignature(node) && (isInterfaceDeclaration(node.parent) || isTypeLiteralNode(node.parent)); +} - function isValidFunctionDeclaration( - functionDeclaration: FunctionLikeDeclaration, - checker: TypeChecker): functionDeclaration is ValidFunctionDeclaration { - if (!isValidParameterNodeArray(functionDeclaration.parameters, checker)) return false; - switch (functionDeclaration.kind) { - case SyntaxKind.FunctionDeclaration: - return hasNameOrDefault(functionDeclaration) && isSingleImplementation(functionDeclaration, checker); - case SyntaxKind.MethodDeclaration: - if (isObjectLiteralExpression(functionDeclaration.parent)) { - const contextualSymbol = getSymbolForContextualType(functionDeclaration.name, checker); - // don't offer the refactor when there are multiple signatures since we won't know which ones the user wants to change - return contextualSymbol?.declarations?.length === 1 && isSingleImplementation(functionDeclaration, checker); - } - return isSingleImplementation(functionDeclaration, checker); - case SyntaxKind.Constructor: - if (isClassDeclaration(functionDeclaration.parent)) { - return hasNameOrDefault(functionDeclaration.parent) && isSingleImplementation(functionDeclaration, checker); - } - else { - return isValidVariableDeclaration(functionDeclaration.parent.parent) - && isSingleImplementation(functionDeclaration, checker); - } - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return isValidVariableDeclaration(functionDeclaration.parent); - } - return false; +function isValidFunctionDeclaration( + functionDeclaration: FunctionLikeDeclaration, + checker: TypeChecker): functionDeclaration is ValidFunctionDeclaration { + if (!isValidParameterNodeArray(functionDeclaration.parameters, checker)) return false; + switch (functionDeclaration.kind) { + case SyntaxKind.FunctionDeclaration: + return hasNameOrDefault(functionDeclaration) && isSingleImplementation(functionDeclaration, checker); + case SyntaxKind.MethodDeclaration: + if (isObjectLiteralExpression(functionDeclaration.parent)) { + const contextualSymbol = getSymbolForContextualType(functionDeclaration.name, checker); + // don't offer the refactor when there are multiple signatures since we won't know which ones the user wants to change + return contextualSymbol?.declarations?.length === 1 && isSingleImplementation(functionDeclaration, checker); + } + return isSingleImplementation(functionDeclaration, checker); + case SyntaxKind.Constructor: + if (isClassDeclaration(functionDeclaration.parent)) { + return hasNameOrDefault(functionDeclaration.parent) && isSingleImplementation(functionDeclaration, checker); + } + else { + return isValidVariableDeclaration(functionDeclaration.parent.parent) + && isSingleImplementation(functionDeclaration, checker); + } + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return isValidVariableDeclaration(functionDeclaration.parent); } + return false; +} - function isSingleImplementation(functionDeclaration: FunctionLikeDeclaration, checker: TypeChecker): boolean { - return !!functionDeclaration.body && !checker.isImplementationOfOverload(functionDeclaration); - } +function isSingleImplementation(functionDeclaration: FunctionLikeDeclaration, checker: TypeChecker): boolean { + return !!functionDeclaration.body && !checker.isImplementationOfOverload(functionDeclaration); +} - function hasNameOrDefault(functionOrClassDeclaration: FunctionDeclaration | ClassDeclaration): boolean { - if (!functionOrClassDeclaration.name) { - const defaultKeyword = findModifier(functionOrClassDeclaration, SyntaxKind.DefaultKeyword); - return !!defaultKeyword; - } - return true; +function hasNameOrDefault(functionOrClassDeclaration: FunctionDeclaration | ClassDeclaration): boolean { + if (!functionOrClassDeclaration.name) { + const defaultKeyword = findModifier(functionOrClassDeclaration, SyntaxKind.DefaultKeyword); + return !!defaultKeyword; } + return true; +} - function isValidParameterNodeArray( - parameters: NodeArray, - checker: TypeChecker): parameters is ValidParameterNodeArray { - return getRefactorableParametersLength(parameters) >= minimumParameterLength - && every(parameters, /*callback*/ paramDecl => isValidParameterDeclaration(paramDecl, checker)); - } +function isValidParameterNodeArray( + parameters: NodeArray, + checker: TypeChecker): parameters is ValidParameterNodeArray { + return getRefactorableParametersLength(parameters) >= minimumParameterLength + && every(parameters, /*callback*/ paramDecl => isValidParameterDeclaration(paramDecl, checker)); +} - function isValidParameterDeclaration( - parameterDeclaration: ParameterDeclaration, - checker: TypeChecker): parameterDeclaration is ValidParameterDeclaration { - if (isRestParameter(parameterDeclaration)) { - const type = checker.getTypeAtLocation(parameterDeclaration); - if (!checker.isArrayType(type) && !checker.isTupleType(type)) return false; - } - return !parameterDeclaration.modifiers && !parameterDeclaration.decorators && isIdentifier(parameterDeclaration.name); +function isValidParameterDeclaration( + parameterDeclaration: ParameterDeclaration, + checker: TypeChecker): parameterDeclaration is ValidParameterDeclaration { + if (isRestParameter(parameterDeclaration)) { + const type = checker.getTypeAtLocation(parameterDeclaration); + if (!checker.isArrayType(type) && !checker.isTupleType(type)) return false; } + return !parameterDeclaration.modifiers && !parameterDeclaration.decorators && isIdentifier(parameterDeclaration.name); +} - function isValidVariableDeclaration(node: Node): node is ValidVariableDeclaration { - return isVariableDeclaration(node) && isVarConst(node) && isIdentifier(node.name) && !node.type; // TODO: GH#30113 - } +function isValidVariableDeclaration(node: Node): node is ValidVariableDeclaration { + return isVariableDeclaration(node) && isVarConst(node) && isIdentifier(node.name) && !node.type; // TODO: GH#30113 +} - function hasThisParameter(parameters: NodeArray): boolean { - return parameters.length > 0 && isThis(parameters[0].name); - } +function hasThisParameter(parameters: NodeArray): boolean { + return parameters.length > 0 && isThis(parameters[0].name); +} - function getRefactorableParametersLength(parameters: NodeArray): number { - if (hasThisParameter(parameters)) { - return parameters.length - 1; - } - return parameters.length; +function getRefactorableParametersLength(parameters: NodeArray): number { + if (hasThisParameter(parameters)) { + return parameters.length - 1; } + return parameters.length; +} - function getRefactorableParameters(parameters: NodeArray): NodeArray { - if (hasThisParameter(parameters)) { - parameters = factory.createNodeArray(parameters.slice(1), parameters.hasTrailingComma); - } - return parameters; +function getRefactorableParameters(parameters: NodeArray): NodeArray { + if (hasThisParameter(parameters)) { + parameters = factory.createNodeArray(parameters.slice(1), parameters.hasTrailingComma); } + return parameters; +} - function createPropertyOrShorthandAssignment(name: string, initializer: Expression): PropertyAssignment | ShorthandPropertyAssignment { - if (isIdentifier(initializer) && getTextOfIdentifierOrLiteral(initializer) === name) { - return factory.createShorthandPropertyAssignment(name); - } - return factory.createPropertyAssignment(name, initializer); +function createPropertyOrShorthandAssignment(name: string, initializer: Expression): PropertyAssignment | ShorthandPropertyAssignment { + if (isIdentifier(initializer) && getTextOfIdentifierOrLiteral(initializer) === name) { + return factory.createShorthandPropertyAssignment(name); } + return factory.createPropertyAssignment(name, initializer); +} - function createNewArgument(functionDeclaration: ValidFunctionDeclaration, functionArguments: NodeArray): ObjectLiteralExpression { - const parameters = getRefactorableParameters(functionDeclaration.parameters); - const hasRestParameter = isRestParameter(last(parameters)); - const nonRestArguments = hasRestParameter ? functionArguments.slice(0, parameters.length - 1) : functionArguments; - const properties = map(nonRestArguments, (arg, i) => { - const parameterName = getParameterName(parameters[i]); - const property = createPropertyOrShorthandAssignment(parameterName, arg); - - suppressLeadingAndTrailingTrivia(property.name); - if (isPropertyAssignment(property)) suppressLeadingAndTrailingTrivia(property.initializer); - copyComments(arg, property); - return property; - }); - - if (hasRestParameter && functionArguments.length >= parameters.length) { - const restArguments = functionArguments.slice(parameters.length - 1); - const restProperty = factory.createPropertyAssignment(getParameterName(last(parameters)), factory.createArrayLiteralExpression(restArguments)); - properties.push(restProperty); - } +function createNewArgument(functionDeclaration: ValidFunctionDeclaration, functionArguments: NodeArray): ObjectLiteralExpression { + const parameters = getRefactorableParameters(functionDeclaration.parameters); + const hasRestParameter = isRestParameter(last(parameters)); + const nonRestArguments = hasRestParameter ? functionArguments.slice(0, parameters.length - 1) : functionArguments; + const properties = map(nonRestArguments, (arg, i) => { + const parameterName = getParameterName(parameters[i]); + const property = createPropertyOrShorthandAssignment(parameterName, arg); + + suppressLeadingAndTrailingTrivia(property.name); + if (isPropertyAssignment(property)) suppressLeadingAndTrailingTrivia(property.initializer); + copyComments(arg, property); + return property; + }); - const objectLiteral = factory.createObjectLiteralExpression(properties, /*multiLine*/ false); - return objectLiteral; + if (hasRestParameter && functionArguments.length >= parameters.length) { + const restArguments = functionArguments.slice(parameters.length - 1); + const restProperty = factory.createPropertyAssignment(getParameterName(last(parameters)), factory.createArrayLiteralExpression(restArguments)); + properties.push(restProperty); } - function createNewParameters(functionDeclaration: ValidFunctionDeclaration | ValidMethodSignature, program: Program, host: LanguageServiceHost): NodeArray { - const checker = program.getTypeChecker(); - const refactorableParameters = getRefactorableParameters(functionDeclaration.parameters); - const bindingElements = map(refactorableParameters, createBindingElementFromParameterDeclaration); - const objectParameterName = factory.createObjectBindingPattern(bindingElements); - const objectParameterType = createParameterTypeNode(refactorableParameters); - - let objectInitializer: Expression | undefined; - // If every parameter in the original function was optional, add an empty object initializer to the new object parameter - if (every(refactorableParameters, isOptionalParameter)) { - objectInitializer = factory.createObjectLiteralExpression(); - } + const objectLiteral = factory.createObjectLiteralExpression(properties, /*multiLine*/ false); + return objectLiteral; +} - const objectParameter = factory.createParameterDeclaration( +function createNewParameters(functionDeclaration: ValidFunctionDeclaration | ValidMethodSignature, program: Program, host: LanguageServiceHost): NodeArray { + const checker = program.getTypeChecker(); + const refactorableParameters = getRefactorableParameters(functionDeclaration.parameters); + const bindingElements = map(refactorableParameters, createBindingElementFromParameterDeclaration); + const objectParameterName = factory.createObjectBindingPattern(bindingElements); + const objectParameterType = createParameterTypeNode(refactorableParameters); + + let objectInitializer: Expression | undefined; + // If every parameter in the original function was optional, add an empty object initializer to the new object parameter + if (every(refactorableParameters, isOptionalParameter)) { + objectInitializer = factory.createObjectLiteralExpression(); + } + + const objectParameter = factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + objectParameterName, + /*questionToken*/ undefined, + objectParameterType, + objectInitializer); + + if (hasThisParameter(functionDeclaration.parameters)) { + const thisParameter = functionDeclaration.parameters[0]; + const newThisParameter = factory.createParameterDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, - objectParameterName, + thisParameter.name, /*questionToken*/ undefined, - objectParameterType, - objectInitializer); - - if (hasThisParameter(functionDeclaration.parameters)) { - const thisParameter = functionDeclaration.parameters[0]; - const newThisParameter = factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - thisParameter.name, - /*questionToken*/ undefined, - thisParameter.type); - - suppressLeadingAndTrailingTrivia(newThisParameter.name); - copyComments(thisParameter.name, newThisParameter.name); - if (thisParameter.type) { - suppressLeadingAndTrailingTrivia(newThisParameter.type!); - copyComments(thisParameter.type, newThisParameter.type!); - } + thisParameter.type); - return factory.createNodeArray([newThisParameter, objectParameter]); - } - return factory.createNodeArray([objectParameter]); - - function createBindingElementFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): BindingElement { - const element = factory.createBindingElement( - /*dotDotDotToken*/ undefined, - /*propertyName*/ undefined, - getParameterName(parameterDeclaration), - isRestParameter(parameterDeclaration) && isOptionalParameter(parameterDeclaration) ? factory.createArrayLiteralExpression() : parameterDeclaration.initializer); - - suppressLeadingAndTrailingTrivia(element); - if (parameterDeclaration.initializer && element.initializer) { - copyComments(parameterDeclaration.initializer, element.initializer); - } - return element; + suppressLeadingAndTrailingTrivia(newThisParameter.name); + copyComments(thisParameter.name, newThisParameter.name); + if (thisParameter.type) { + suppressLeadingAndTrailingTrivia(newThisParameter.type!); + copyComments(thisParameter.type, newThisParameter.type!); } - function createParameterTypeNode(parameters: NodeArray): TypeLiteralNode { - const members = map(parameters, createPropertySignatureFromParameterDeclaration); - const typeNode = addEmitFlags(factory.createTypeLiteralNode(members), EmitFlags.SingleLine); - return typeNode; - } + return factory.createNodeArray([newThisParameter, objectParameter]); + } + return factory.createNodeArray([objectParameter]); - function createPropertySignatureFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): PropertySignature { - let parameterType = parameterDeclaration.type; - if (!parameterType && (parameterDeclaration.initializer || isRestParameter(parameterDeclaration))) { - parameterType = getTypeNode(parameterDeclaration); - } + function createBindingElementFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): BindingElement { + const element = factory.createBindingElement( + /*dotDotDotToken*/ undefined, + /*propertyName*/ undefined, + getParameterName(parameterDeclaration), + isRestParameter(parameterDeclaration) && isOptionalParameter(parameterDeclaration) ? factory.createArrayLiteralExpression() : parameterDeclaration.initializer); - const propertySignature = factory.createPropertySignature( - /*modifiers*/ undefined, - getParameterName(parameterDeclaration), - isOptionalParameter(parameterDeclaration) ? factory.createToken(SyntaxKind.QuestionToken) : parameterDeclaration.questionToken, - parameterType); + suppressLeadingAndTrailingTrivia(element); + if (parameterDeclaration.initializer && element.initializer) { + copyComments(parameterDeclaration.initializer, element.initializer); + } + return element; + } - suppressLeadingAndTrailingTrivia(propertySignature); - copyComments(parameterDeclaration.name, propertySignature.name); - if (parameterDeclaration.type && propertySignature.type) { - copyComments(parameterDeclaration.type, propertySignature.type); - } + function createParameterTypeNode(parameters: NodeArray): TypeLiteralNode { + const members = map(parameters, createPropertySignatureFromParameterDeclaration); + const typeNode = addEmitFlags(factory.createTypeLiteralNode(members), EmitFlags.SingleLine); + return typeNode; + } - return propertySignature; + function createPropertySignatureFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): PropertySignature { + let parameterType = parameterDeclaration.type; + if (!parameterType && (parameterDeclaration.initializer || isRestParameter(parameterDeclaration))) { + parameterType = getTypeNode(parameterDeclaration); } - function getTypeNode(node: Node): TypeNode | undefined { - const type = checker.getTypeAtLocation(node); - return getTypeNodeIfAccessible(type, node, program, host); + const propertySignature = factory.createPropertySignature( + /*modifiers*/ undefined, + getParameterName(parameterDeclaration), + isOptionalParameter(parameterDeclaration) ? factory.createToken(SyntaxKind.QuestionToken) : parameterDeclaration.questionToken, + parameterType); + + suppressLeadingAndTrailingTrivia(propertySignature); + copyComments(parameterDeclaration.name, propertySignature.name); + if (parameterDeclaration.type && propertySignature.type) { + copyComments(parameterDeclaration.type, propertySignature.type); } - function isOptionalParameter(parameterDeclaration: ValidParameterDeclaration): boolean { - if (isRestParameter(parameterDeclaration)) { - const type = checker.getTypeAtLocation(parameterDeclaration); - return !checker.isTupleType(type); - } - return checker.isOptionalParameter(parameterDeclaration); - } + return propertySignature; } - function getParameterName(paramDeclaration: ValidParameterDeclaration) { - return getTextOfIdentifierOrLiteral(paramDeclaration.name); + function getTypeNode(node: Node): TypeNode | undefined { + const type = checker.getTypeAtLocation(node); + return getTypeNodeIfAccessible(type, node, program, host); } - function getClassNames(constructorDeclaration: ValidConstructor): (Identifier | Modifier)[] { - switch (constructorDeclaration.parent.kind) { - case SyntaxKind.ClassDeclaration: - const classDeclaration = constructorDeclaration.parent; - if (classDeclaration.name) return [classDeclaration.name]; - // If the class declaration doesn't have a name, it should have a default modifier. - // We validated this in `isValidFunctionDeclaration` through `hasNameOrDefault` - const defaultModifier = Debug.checkDefined( - findModifier(classDeclaration, SyntaxKind.DefaultKeyword), - "Nameless class declaration should be a default export"); - return [defaultModifier]; - case SyntaxKind.ClassExpression: - const classExpression = constructorDeclaration.parent; - const variableDeclaration = constructorDeclaration.parent.parent; - const className = classExpression.name; - if (className) return [className, variableDeclaration.name]; - return [variableDeclaration.name]; - } - } - - function getFunctionNames(functionDeclaration: ValidFunctionDeclaration): Node[] { - switch (functionDeclaration.kind) { - case SyntaxKind.FunctionDeclaration: - if (functionDeclaration.name) return [functionDeclaration.name]; - // If the function declaration doesn't have a name, it should have a default modifier. - // We validated this in `isValidFunctionDeclaration` through `hasNameOrDefault` - const defaultModifier = Debug.checkDefined( - findModifier(functionDeclaration, SyntaxKind.DefaultKeyword), - "Nameless function declaration should be a default export"); - return [defaultModifier]; - case SyntaxKind.MethodDeclaration: - return [functionDeclaration.name]; - case SyntaxKind.Constructor: - const ctrKeyword = Debug.checkDefined( - findChildOfKind(functionDeclaration, SyntaxKind.ConstructorKeyword, functionDeclaration.getSourceFile()), - "Constructor declaration should have constructor keyword"); - if (functionDeclaration.parent.kind === SyntaxKind.ClassExpression) { - const variableDeclaration = functionDeclaration.parent.parent; - return [variableDeclaration.name, ctrKeyword]; - } - return [ctrKeyword]; - case SyntaxKind.ArrowFunction: - return [functionDeclaration.parent.name]; - case SyntaxKind.FunctionExpression: - if (functionDeclaration.name) return [functionDeclaration.name, functionDeclaration.parent.name]; - return [functionDeclaration.parent.name]; - default: - return Debug.assertNever(functionDeclaration, `Unexpected function declaration kind ${(functionDeclaration as ValidFunctionDeclaration).kind}`); + function isOptionalParameter(parameterDeclaration: ValidParameterDeclaration): boolean { + if (isRestParameter(parameterDeclaration)) { + const type = checker.getTypeAtLocation(parameterDeclaration); + return !checker.isTupleType(type); } + return checker.isOptionalParameter(parameterDeclaration); } +} - type ValidParameterNodeArray = NodeArray; +function getParameterName(paramDeclaration: ValidParameterDeclaration) { + return getTextOfIdentifierOrLiteral(paramDeclaration.name); +} - interface ValidVariableDeclaration extends VariableDeclaration { - name: Identifier; - type: undefined; +function getClassNames(constructorDeclaration: ValidConstructor): (Identifier | Modifier)[] { + switch (constructorDeclaration.parent.kind) { + case SyntaxKind.ClassDeclaration: + const classDeclaration = constructorDeclaration.parent; + if (classDeclaration.name) return [classDeclaration.name]; + // If the class declaration doesn't have a name, it should have a default modifier. + // We validated this in `isValidFunctionDeclaration` through `hasNameOrDefault` + const defaultModifier = Debug.checkDefined( + findModifier(classDeclaration, SyntaxKind.DefaultKeyword), + "Nameless class declaration should be a default export"); + return [defaultModifier]; + case SyntaxKind.ClassExpression: + const classExpression = constructorDeclaration.parent; + const variableDeclaration = constructorDeclaration.parent.parent; + const className = classExpression.name; + if (className) return [className, variableDeclaration.name]; + return [variableDeclaration.name]; } +} - interface ValidConstructor extends ConstructorDeclaration { - parent: ClassDeclaration | (ClassExpression & { parent: ValidVariableDeclaration }); - parameters: NodeArray; - body: FunctionBody; +function getFunctionNames(functionDeclaration: ValidFunctionDeclaration): Node[] { + switch (functionDeclaration.kind) { + case SyntaxKind.FunctionDeclaration: + if (functionDeclaration.name) return [functionDeclaration.name]; + // If the function declaration doesn't have a name, it should have a default modifier. + // We validated this in `isValidFunctionDeclaration` through `hasNameOrDefault` + const defaultModifier = Debug.checkDefined( + findModifier(functionDeclaration, SyntaxKind.DefaultKeyword), + "Nameless function declaration should be a default export"); + return [defaultModifier]; + case SyntaxKind.MethodDeclaration: + return [functionDeclaration.name]; + case SyntaxKind.Constructor: + const ctrKeyword = Debug.checkDefined( + findChildOfKind(functionDeclaration, SyntaxKind.ConstructorKeyword, functionDeclaration.getSourceFile()), + "Constructor declaration should have constructor keyword"); + if (functionDeclaration.parent.kind === SyntaxKind.ClassExpression) { + const variableDeclaration = functionDeclaration.parent.parent; + return [variableDeclaration.name, ctrKeyword]; + } + return [ctrKeyword]; + case SyntaxKind.ArrowFunction: + return [functionDeclaration.parent.name]; + case SyntaxKind.FunctionExpression: + if (functionDeclaration.name) return [functionDeclaration.name, functionDeclaration.parent.name]; + return [functionDeclaration.parent.name]; + default: + return Debug.assertNever(functionDeclaration, `Unexpected function declaration kind ${(functionDeclaration as ValidFunctionDeclaration).kind}`); } +} - interface ValidFunction extends FunctionDeclaration { - parameters: NodeArray; - body: FunctionBody; - } +type ValidParameterNodeArray = NodeArray; - interface ValidMethod extends MethodDeclaration { - parameters: NodeArray; - body: FunctionBody; - } +interface ValidVariableDeclaration extends VariableDeclaration { + name: Identifier; + type: undefined; +} - interface ValidFunctionExpression extends FunctionExpression { - parent: ValidVariableDeclaration; - parameters: NodeArray; - } +interface ValidConstructor extends ConstructorDeclaration { + parent: ClassDeclaration | (ClassExpression & { parent: ValidVariableDeclaration }); + parameters: NodeArray; + body: FunctionBody; +} - interface ValidArrowFunction extends ArrowFunction { - parent: ValidVariableDeclaration; - parameters: NodeArray; - } +interface ValidFunction extends FunctionDeclaration { + parameters: NodeArray; + body: FunctionBody; +} - interface ValidMethodSignature extends MethodSignature { - parameters: NodeArray; - } +interface ValidMethod extends MethodDeclaration { + parameters: NodeArray; + body: FunctionBody; +} - type ValidFunctionDeclaration = ValidConstructor | ValidFunction | ValidMethod | ValidArrowFunction | ValidFunctionExpression; +interface ValidFunctionExpression extends FunctionExpression { + parent: ValidVariableDeclaration; + parameters: NodeArray; +} - interface ValidParameterDeclaration extends ParameterDeclaration { - name: Identifier; - modifiers: undefined; - decorators: undefined; - } +interface ValidArrowFunction extends ArrowFunction { + parent: ValidVariableDeclaration; + parameters: NodeArray; +} - interface GroupedReferences { - functionCalls: (CallExpression | NewExpression)[]; - declarations: Node[]; - signature?: ValidMethodSignature; - classReferences?: ClassReferences; - valid: boolean; - } - interface ClassReferences { - accessExpressions: Node[]; - typeUsages: Node[]; - } +interface ValidMethodSignature extends MethodSignature { + parameters: NodeArray; +} + +type ValidFunctionDeclaration = ValidConstructor | ValidFunction | ValidMethod | ValidArrowFunction | ValidFunctionExpression; + +interface ValidParameterDeclaration extends ParameterDeclaration { + name: Identifier; + modifiers: undefined; + decorators: undefined; +} + +interface GroupedReferences { + functionCalls: (CallExpression | NewExpression)[]; + declarations: Node[]; + signature?: ValidMethodSignature; + classReferences?: ClassReferences; + valid: boolean; +} +interface ClassReferences { + accessExpressions: Node[]; + typeUsages: Node[]; +} } diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index e417a0f2eae41..48e1a0b32b1d9 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -1,251 +1,251 @@ /* @internal */ namespace ts.refactor.convertStringOrTemplateLiteral { - const refactorName = "Convert to template string"; - const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_string); - - const convertStringAction = { - name: refactorName, - description: refactorDescription, - kind: "refactor.rewrite.string" - }; - registerRefactor(refactorName, { - kinds: [convertStringAction.kind], - getEditsForAction, - getAvailableActions - }); - - function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const { file, startPosition } = context; - const node = getNodeOrParentOfParentheses(file, startPosition); - const maybeBinary = getParentBinaryExpression(node); - const refactorInfo: ApplicableRefactorInfo = { name: refactorName, description: refactorDescription, actions: [] }; - - if (isBinaryExpression(maybeBinary) && treeToArray(maybeBinary).isValidConcatenation) { - refactorInfo.actions.push(convertStringAction); - return [refactorInfo]; - } - else if (context.preferences.provideRefactorNotApplicableReason) { - refactorInfo.actions.push({ ...convertStringAction, - notApplicableReason: getLocaleSpecificMessage(Diagnostics.Can_only_convert_string_concatenation) - }); - return [refactorInfo]; - } - return emptyArray; +const refactorName = "Convert to template string"; +const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_string); + +const convertStringAction = { + name: refactorName, + description: refactorDescription, + kind: "refactor.rewrite.string" +}; +registerRefactor(refactorName, { + kinds: [convertStringAction.kind], + getEditsForAction, + getAvailableActions +}); + +function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const { file, startPosition } = context; + const node = getNodeOrParentOfParentheses(file, startPosition); + const maybeBinary = getParentBinaryExpression(node); + const refactorInfo: ApplicableRefactorInfo = { name: refactorName, description: refactorDescription, actions: [] }; + + if (isBinaryExpression(maybeBinary) && treeToArray(maybeBinary).isValidConcatenation) { + refactorInfo.actions.push(convertStringAction); + return [refactorInfo]; } + else if (context.preferences.provideRefactorNotApplicableReason) { + refactorInfo.actions.push({ ...convertStringAction, + notApplicableReason: getLocaleSpecificMessage(Diagnostics.Can_only_convert_string_concatenation) + }); + return [refactorInfo]; + } + return emptyArray; +} - function getNodeOrParentOfParentheses(file: SourceFile, startPosition: number) { - const node = getTokenAtPosition(file, startPosition); - const nestedBinary = getParentBinaryExpression(node); - const isNonStringBinary = !treeToArray(nestedBinary).isValidConcatenation; - - if ( - isNonStringBinary && - isParenthesizedExpression(nestedBinary.parent) && - isBinaryExpression(nestedBinary.parent.parent) - ) { - return nestedBinary.parent.parent; - } - return node; +function getNodeOrParentOfParentheses(file: SourceFile, startPosition: number) { + const node = getTokenAtPosition(file, startPosition); + const nestedBinary = getParentBinaryExpression(node); + const isNonStringBinary = !treeToArray(nestedBinary).isValidConcatenation; + + if ( + isNonStringBinary && + isParenthesizedExpression(nestedBinary.parent) && + isBinaryExpression(nestedBinary.parent.parent) + ) { + return nestedBinary.parent.parent; } + return node; +} - function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const { file, startPosition } = context; - const node = getNodeOrParentOfParentheses(file, startPosition); +function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + const { file, startPosition } = context; + const node = getNodeOrParentOfParentheses(file, startPosition); - switch (actionName) { - case refactorDescription: - return { edits: getEditsForToTemplateLiteral(context, node) }; - default: - return Debug.fail("invalid action"); - } + switch (actionName) { + case refactorDescription: + return { edits: getEditsForToTemplateLiteral(context, node) }; + default: + return Debug.fail("invalid action"); } +} - function getEditsForToTemplateLiteral(context: RefactorContext, node: Node) { - const maybeBinary = getParentBinaryExpression(node); - const file = context.file; +function getEditsForToTemplateLiteral(context: RefactorContext, node: Node) { + const maybeBinary = getParentBinaryExpression(node); + const file = context.file; - const templateLiteral = nodesToTemplate(treeToArray(maybeBinary), file); - const trailingCommentRanges = getTrailingCommentRanges(file.text, maybeBinary.end); + const templateLiteral = nodesToTemplate(treeToArray(maybeBinary), file); + const trailingCommentRanges = getTrailingCommentRanges(file.text, maybeBinary.end); - if (trailingCommentRanges) { - const lastComment = trailingCommentRanges[trailingCommentRanges.length - 1]; - const trailingRange = { pos: trailingCommentRanges[0].pos, end: lastComment.end }; + if (trailingCommentRanges) { + const lastComment = trailingCommentRanges[trailingCommentRanges.length - 1]; + const trailingRange = { pos: trailingCommentRanges[0].pos, end: lastComment.end }; - // since suppressTrailingTrivia(maybeBinary) does not work, the trailing comment is removed manually - // otherwise it would have the trailing comment twice - return textChanges.ChangeTracker.with(context, t => { - t.deleteRange(file, trailingRange); - t.replaceNode(file, maybeBinary, templateLiteral); - }); - } - else { - return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); - } + // since suppressTrailingTrivia(maybeBinary) does not work, the trailing comment is removed manually + // otherwise it would have the trailing comment twice + return textChanges.ChangeTracker.with(context, t => { + t.deleteRange(file, trailingRange); + t.replaceNode(file, maybeBinary, templateLiteral); + }); } - - function isNotEqualsOperator(node: BinaryExpression) { - return node.operatorToken.kind !== SyntaxKind.EqualsToken; + else { + return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); } +} - function getParentBinaryExpression(expr: Node) { - const container = findAncestor(expr.parent, n => { - switch (n.kind) { - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return false; - case SyntaxKind.TemplateExpression: - case SyntaxKind.BinaryExpression: - return !(isBinaryExpression(n.parent) && isNotEqualsOperator(n.parent)); - default: - return "quit"; - } - }); - - return (container || expr) as Expression; - } +function isNotEqualsOperator(node: BinaryExpression) { + return node.operatorToken.kind !== SyntaxKind.EqualsToken; +} - function treeToArray(current: Expression) { - const loop = (current: Node): { nodes: Expression[], operators: Token[], hasString: boolean, validOperators: boolean} => { - if (!isBinaryExpression(current)) { - return { nodes: [current as Expression], operators: [], validOperators: true, - hasString: isStringLiteral(current) || isNoSubstitutionTemplateLiteral(current) }; - } - const { nodes, operators, hasString: leftHasString, validOperators: leftOperatorValid } = loop(current.left); +function getParentBinaryExpression(expr: Node) { + const container = findAncestor(expr.parent, n => { + switch (n.kind) { + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return false; + case SyntaxKind.TemplateExpression: + case SyntaxKind.BinaryExpression: + return !(isBinaryExpression(n.parent) && isNotEqualsOperator(n.parent)); + default: + return "quit"; + } + }); - if (!(leftHasString || isStringLiteral(current.right) || isTemplateExpression(current.right))) { - return { nodes: [current], operators: [], hasString: false, validOperators: true }; - } + return (container || expr) as Expression; +} - const currentOperatorValid = current.operatorToken.kind === SyntaxKind.PlusToken; - const validOperators = leftOperatorValid && currentOperatorValid; +function treeToArray(current: Expression) { + const loop = (current: Node): { nodes: Expression[], operators: Token[], hasString: boolean, validOperators: boolean} => { + if (!isBinaryExpression(current)) { + return { nodes: [current as Expression], operators: [], validOperators: true, + hasString: isStringLiteral(current) || isNoSubstitutionTemplateLiteral(current) }; + } + const { nodes, operators, hasString: leftHasString, validOperators: leftOperatorValid } = loop(current.left); - nodes.push(current.right); - operators.push(current.operatorToken); + if (!(leftHasString || isStringLiteral(current.right) || isTemplateExpression(current.right))) { + return { nodes: [current], operators: [], hasString: false, validOperators: true }; + } - return { nodes, operators, hasString: true, validOperators }; - }; - const { nodes, operators, validOperators, hasString } = loop(current); - return { nodes, operators, isValidConcatenation: validOperators && hasString }; - } + const currentOperatorValid = current.operatorToken.kind === SyntaxKind.PlusToken; + const validOperators = leftOperatorValid && currentOperatorValid; - // to copy comments following the operator - // "foo" + /* comment */ "bar" - const copyTrailingOperatorComments = (operators: Token[], file: SourceFile) => (index: number, targetNode: Node) => { - if (index < operators.length) { - copyTrailingComments(operators[index], targetNode, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - } - }; + nodes.push(current.right); + operators.push(current.operatorToken); - // to copy comments following the string - // "foo" /* comment */ + "bar" /* comment */ + "bar2" - const copyCommentFromMultiNode = (nodes: readonly Expression[], file: SourceFile, copyOperatorComments: (index: number, targetNode: Node) => void) => - (indexes: number[], targetNode: Node) => { - while (indexes.length > 0) { - const index = indexes.shift()!; - copyTrailingComments(nodes[index], targetNode, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - copyOperatorComments(index, targetNode); - } + return { nodes, operators, hasString: true, validOperators }; }; + const { nodes, operators, validOperators, hasString } = loop(current); + return { nodes, operators, isValidConcatenation: validOperators && hasString }; +} - function escapeRawStringForTemplate(s: string) { - // Escaping for $s in strings that are to be used in template strings - // Naive implementation: replace \x by itself and otherwise $ and ` by \$ and \`. - // But to complicate it a bit, this should work for raw strings too. - return s.replace(/\\.|[$`]/g, m => m[0] === "\\" ? m : "\\" + m); - // Finally, a less-backslash-happy version can work too, doing only ${ instead of all $s: - // s.replace(/\\.|\${|`/g, m => m[0] === "\\" ? m : "\\" + m); - // but `\$${foo}` is likely more clear than the more-confusing-but-still-working `$${foo}`. +// to copy comments following the operator +// "foo" + /* comment */ "bar" +const copyTrailingOperatorComments = (operators: Token[], file: SourceFile) => (index: number, targetNode: Node) => { + if (index < operators.length) { + copyTrailingComments(operators[index], targetNode, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); } - - function getRawTextOfTemplate(node: TemplateHead | TemplateMiddle | TemplateTail) { - // in these cases the right side is ${ - const rightShaving = isTemplateHead(node) || isTemplateMiddle(node) ? -2 : -1; - return getTextOfNode(node).slice(1, rightShaving); +}; + +// to copy comments following the string +// "foo" /* comment */ + "bar" /* comment */ + "bar2" +const copyCommentFromMultiNode = (nodes: readonly Expression[], file: SourceFile, copyOperatorComments: (index: number, targetNode: Node) => void) => +(indexes: number[], targetNode: Node) => { + while (indexes.length > 0) { + const index = indexes.shift()!; + copyTrailingComments(nodes[index], targetNode, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); + copyOperatorComments(index, targetNode); } +}; + +function escapeRawStringForTemplate(s: string) { + // Escaping for $s in strings that are to be used in template strings + // Naive implementation: replace \x by itself and otherwise $ and ` by \$ and \`. + // But to complicate it a bit, this should work for raw strings too. + return s.replace(/\\.|[$`]/g, m => m[0] === "\\" ? m : "\\" + m); + // Finally, a less-backslash-happy version can work too, doing only ${ instead of all $s: + // s.replace(/\\.|\${|`/g, m => m[0] === "\\" ? m : "\\" + m); + // but `\$${foo}` is likely more clear than the more-confusing-but-still-working `$${foo}`. +} - function concatConsecutiveString(index: number, nodes: readonly Expression[]): [nextIndex: number, text: string, rawText: string, usedIndexes: number[]] { - const indexes = []; - let text = "", rawText = ""; - while (index < nodes.length) { - const node = nodes[index]; - if (isStringLiteralLike(node)) { // includes isNoSubstitutionTemplateLiteral(node) - text += node.text; - rawText += escapeRawStringForTemplate(getTextOfNode(node).slice(1, -1)); - indexes.push(index); - index++; - } - else if (isTemplateExpression(node)) { - text += node.head.text; - rawText += getRawTextOfTemplate(node.head); - break; - } - else { - break; - } +function getRawTextOfTemplate(node: TemplateHead | TemplateMiddle | TemplateTail) { + // in these cases the right side is ${ + const rightShaving = isTemplateHead(node) || isTemplateMiddle(node) ? -2 : -1; + return getTextOfNode(node).slice(1, rightShaving); +} + +function concatConsecutiveString(index: number, nodes: readonly Expression[]): [nextIndex: number, text: string, rawText: string, usedIndexes: number[]] { + const indexes = []; + let text = "", rawText = ""; + while (index < nodes.length) { + const node = nodes[index]; + if (isStringLiteralLike(node)) { // includes isNoSubstitutionTemplateLiteral(node) + text += node.text; + rawText += escapeRawStringForTemplate(getTextOfNode(node).slice(1, -1)); + indexes.push(index); + index++; + } + else if (isTemplateExpression(node)) { + text += node.head.text; + rawText += getRawTextOfTemplate(node.head); + break; + } + else { + break; } - return [index, text, rawText, indexes]; } + return [index, text, rawText, indexes]; +} - function nodesToTemplate({ nodes, operators }: { nodes: readonly Expression[], operators: Token[] }, file: SourceFile) { - const copyOperatorComments = copyTrailingOperatorComments(operators, file); - const copyCommentFromStringLiterals = copyCommentFromMultiNode(nodes, file, copyOperatorComments); - const [begin, headText, rawHeadText, headIndexes] = concatConsecutiveString(0, nodes); +function nodesToTemplate({ nodes, operators }: { nodes: readonly Expression[], operators: Token[] }, file: SourceFile) { + const copyOperatorComments = copyTrailingOperatorComments(operators, file); + const copyCommentFromStringLiterals = copyCommentFromMultiNode(nodes, file, copyOperatorComments); + const [begin, headText, rawHeadText, headIndexes] = concatConsecutiveString(0, nodes); - if (begin === nodes.length) { - const noSubstitutionTemplateLiteral = factory.createNoSubstitutionTemplateLiteral(headText, rawHeadText); - copyCommentFromStringLiterals(headIndexes, noSubstitutionTemplateLiteral); - return noSubstitutionTemplateLiteral; - } + if (begin === nodes.length) { + const noSubstitutionTemplateLiteral = factory.createNoSubstitutionTemplateLiteral(headText, rawHeadText); + copyCommentFromStringLiterals(headIndexes, noSubstitutionTemplateLiteral); + return noSubstitutionTemplateLiteral; + } - const templateSpans: TemplateSpan[] = []; - const templateHead = factory.createTemplateHead(headText, rawHeadText); - copyCommentFromStringLiterals(headIndexes, templateHead); - - for (let i = begin; i < nodes.length; i++) { - const currentNode = getExpressionFromParenthesesOrExpression(nodes[i]); - copyOperatorComments(i, currentNode); - - const [newIndex, subsequentText, rawSubsequentText, stringIndexes] = concatConsecutiveString(i + 1, nodes); - i = newIndex - 1; - const isLast = i === nodes.length - 1; - - if (isTemplateExpression(currentNode)) { - const spans = map(currentNode.templateSpans, (span, index) => { - copyExpressionComments(span); - const isLastSpan = index === currentNode.templateSpans.length - 1; - const text = span.literal.text + (isLastSpan ? subsequentText : ""); - const rawText = getRawTextOfTemplate(span.literal) + (isLastSpan ? rawSubsequentText : ""); - return factory.createTemplateSpan(span.expression, isLast - ? factory.createTemplateTail(text, rawText) - : factory.createTemplateMiddle(text, rawText)); - }); - templateSpans.push(...spans); - } - else { - const templatePart = isLast - ? factory.createTemplateTail(subsequentText, rawSubsequentText) - : factory.createTemplateMiddle(subsequentText, rawSubsequentText); - copyCommentFromStringLiterals(stringIndexes, templatePart); - templateSpans.push(factory.createTemplateSpan(currentNode, templatePart)); - } + const templateSpans: TemplateSpan[] = []; + const templateHead = factory.createTemplateHead(headText, rawHeadText); + copyCommentFromStringLiterals(headIndexes, templateHead); + + for (let i = begin; i < nodes.length; i++) { + const currentNode = getExpressionFromParenthesesOrExpression(nodes[i]); + copyOperatorComments(i, currentNode); + + const [newIndex, subsequentText, rawSubsequentText, stringIndexes] = concatConsecutiveString(i + 1, nodes); + i = newIndex - 1; + const isLast = i === nodes.length - 1; + + if (isTemplateExpression(currentNode)) { + const spans = map(currentNode.templateSpans, (span, index) => { + copyExpressionComments(span); + const isLastSpan = index === currentNode.templateSpans.length - 1; + const text = span.literal.text + (isLastSpan ? subsequentText : ""); + const rawText = getRawTextOfTemplate(span.literal) + (isLastSpan ? rawSubsequentText : ""); + return factory.createTemplateSpan(span.expression, isLast + ? factory.createTemplateTail(text, rawText) + : factory.createTemplateMiddle(text, rawText)); + }); + templateSpans.push(...spans); + } + else { + const templatePart = isLast + ? factory.createTemplateTail(subsequentText, rawSubsequentText) + : factory.createTemplateMiddle(subsequentText, rawSubsequentText); + copyCommentFromStringLiterals(stringIndexes, templatePart); + templateSpans.push(factory.createTemplateSpan(currentNode, templatePart)); } - - return factory.createTemplateExpression(templateHead, templateSpans); } - // to copy comments following the opening & closing parentheses - // "foo" + ( /* comment */ 5 + 5 ) /* comment */ + "bar" - function copyExpressionComments(node: ParenthesizedExpression | TemplateSpan) { - const file = node.getSourceFile(); - copyTrailingComments(node, node.expression, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - copyTrailingAsLeadingComments(node.expression, node.expression, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - } + return factory.createTemplateExpression(templateHead, templateSpans); +} - function getExpressionFromParenthesesOrExpression(node: Expression) { - if (isParenthesizedExpression(node)) { - copyExpressionComments(node); - node = node.expression; - } - return node; +// to copy comments following the opening & closing parentheses +// "foo" + ( /* comment */ 5 + 5 ) /* comment */ + "bar" +function copyExpressionComments(node: ParenthesizedExpression | TemplateSpan) { + const file = node.getSourceFile(); + copyTrailingComments(node, node.expression, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); + copyTrailingAsLeadingComments(node.expression, node.expression, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); +} + +function getExpressionFromParenthesesOrExpression(node: Expression) { + if (isParenthesizedExpression(node)) { + copyExpressionComments(node); + node = node.expression; } + return node; +} } diff --git a/src/services/refactors/convertToOptionalChainExpression.ts b/src/services/refactors/convertToOptionalChainExpression.ts index 8bfb222c8bf0f..5cc182117d5f2 100644 --- a/src/services/refactors/convertToOptionalChainExpression.ts +++ b/src/services/refactors/convertToOptionalChainExpression.ts @@ -1,300 +1,300 @@ /* @internal */ namespace ts.refactor.convertToOptionalChainExpression { - const refactorName = "Convert to optional chain expression"; - const convertToOptionalChainExpressionMessage = getLocaleSpecificMessage(Diagnostics.Convert_to_optional_chain_expression); - - const toOptionalChainAction = { - name: refactorName, - description: convertToOptionalChainExpressionMessage, - kind: "refactor.rewrite.expression.optionalChain", - }; - registerRefactor(refactorName, { - kinds: [toOptionalChainAction.kind], - getAvailableActions, - getEditsForAction - }); - - function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const info = getInfo(context, context.triggerReason === "invoked"); - if (!info) return emptyArray; - - if (!isRefactorErrorInfo(info)) { - return [{ - name: refactorName, - description: convertToOptionalChainExpressionMessage, - actions: [toOptionalChainAction], - }]; - } - - if (context.preferences.provideRefactorNotApplicableReason) { - return [{ - name: refactorName, - description: convertToOptionalChainExpressionMessage, - actions: [{ ...toOptionalChainAction, notApplicableReason: info.error }], - }]; - } - return emptyArray; +const refactorName = "Convert to optional chain expression"; +const convertToOptionalChainExpressionMessage = getLocaleSpecificMessage(Diagnostics.Convert_to_optional_chain_expression); + +const toOptionalChainAction = { + name: refactorName, + description: convertToOptionalChainExpressionMessage, + kind: "refactor.rewrite.expression.optionalChain", +}; +registerRefactor(refactorName, { + kinds: [toOptionalChainAction.kind], + getAvailableActions, + getEditsForAction +}); + +function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const info = getInfo(context, context.triggerReason === "invoked"); + if (!info) return emptyArray; + + if (!isRefactorErrorInfo(info)) { + return [{ + name: refactorName, + description: convertToOptionalChainExpressionMessage, + actions: [toOptionalChainAction], + }]; } - function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const info = getInfo(context); - Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); - const edits = textChanges.ChangeTracker.with(context, t => - doChange(context.file, context.program.getTypeChecker(), t, info, actionName) - ); - return { edits, renameFilename: undefined, renameLocation: undefined }; + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ + name: refactorName, + description: convertToOptionalChainExpressionMessage, + actions: [{ ...toOptionalChainAction, notApplicableReason: info.error }], + }]; } + return emptyArray; +} - type Occurrence = PropertyAccessExpression | ElementAccessExpression | Identifier; +function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + const info = getInfo(context); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); + const edits = textChanges.ChangeTracker.with(context, t => + doChange(context.file, context.program.getTypeChecker(), t, info, actionName) + ); + return { edits, renameFilename: undefined, renameLocation: undefined }; +} - interface OptionalChainInfo { - finalExpression: PropertyAccessExpression | ElementAccessExpression | CallExpression, - occurrences: Occurrence[], - expression: ValidExpression, - }; +type Occurrence = PropertyAccessExpression | ElementAccessExpression | Identifier; - type ValidExpressionOrStatement = ValidExpression | ValidStatement; +interface OptionalChainInfo { + finalExpression: PropertyAccessExpression | ElementAccessExpression | CallExpression, + occurrences: Occurrence[], + expression: ValidExpression, +}; - /** - * Types for which a "Convert to optional chain refactor" are offered. - */ - type ValidExpression = BinaryExpression | ConditionalExpression; +type ValidExpressionOrStatement = ValidExpression | ValidStatement; - /** - * Types of statements which are likely to include a valid expression for extraction. - */ - type ValidStatement = ExpressionStatement | ReturnStatement | VariableStatement; +/** + * Types for which a "Convert to optional chain refactor" are offered. + */ +type ValidExpression = BinaryExpression | ConditionalExpression; - function isValidExpression(node: Node): node is ValidExpression { - return isBinaryExpression(node) || isConditionalExpression(node); - } +/** + * Types of statements which are likely to include a valid expression for extraction. + */ +type ValidStatement = ExpressionStatement | ReturnStatement | VariableStatement; - function isValidStatement(node: Node): node is ValidStatement { - return isExpressionStatement(node) || isReturnStatement(node) || isVariableStatement(node); - } +function isValidExpression(node: Node): node is ValidExpression { + return isBinaryExpression(node) || isConditionalExpression(node); +} - function isValidExpressionOrStatement(node: Node): node is ValidExpressionOrStatement { - return isValidExpression(node) || isValidStatement(node); - } +function isValidStatement(node: Node): node is ValidStatement { + return isExpressionStatement(node) || isReturnStatement(node) || isVariableStatement(node); +} - function getInfo(context: RefactorContext, considerEmptySpans = true): OptionalChainInfo | RefactorErrorInfo | undefined { - const { file, program } = context; - const span = getRefactorContextSpan(context); +function isValidExpressionOrStatement(node: Node): node is ValidExpressionOrStatement { + return isValidExpression(node) || isValidStatement(node); +} - const forEmptySpan = span.length === 0; - if (forEmptySpan && !considerEmptySpans) return undefined; +function getInfo(context: RefactorContext, considerEmptySpans = true): OptionalChainInfo | RefactorErrorInfo | undefined { + const { file, program } = context; + const span = getRefactorContextSpan(context); - // selecting fo[|o && foo.ba|]r should be valid, so adjust span to fit start and end tokens - const startToken = getTokenAtPosition(file, span.start); - const endToken = findTokenOnLeftOfPosition(file, span.start + span.length); - const adjustedSpan = createTextSpanFromBounds(startToken.pos, endToken && endToken.end >= startToken.pos ? endToken.getEnd() : startToken.getEnd()); + const forEmptySpan = span.length === 0; + if (forEmptySpan && !considerEmptySpans) return undefined; - const parent = forEmptySpan ? getValidParentNodeOfEmptySpan(startToken) : getValidParentNodeContainingSpan(startToken, adjustedSpan); - const expression = parent && isValidExpressionOrStatement(parent) ? getExpression(parent) : undefined; - if (!expression) return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; + // selecting fo[|o && foo.ba|]r should be valid, so adjust span to fit start and end tokens + const startToken = getTokenAtPosition(file, span.start); + const endToken = findTokenOnLeftOfPosition(file, span.start + span.length); + const adjustedSpan = createTextSpanFromBounds(startToken.pos, endToken && endToken.end >= startToken.pos ? endToken.getEnd() : startToken.getEnd()); - const checker = program.getTypeChecker(); - return isConditionalExpression(expression) ? getConditionalInfo(expression, checker) : getBinaryInfo(expression); - } + const parent = forEmptySpan ? getValidParentNodeOfEmptySpan(startToken) : getValidParentNodeContainingSpan(startToken, adjustedSpan); + const expression = parent && isValidExpressionOrStatement(parent) ? getExpression(parent) : undefined; + if (!expression) return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; - function getConditionalInfo(expression: ConditionalExpression, checker: TypeChecker): OptionalChainInfo | RefactorErrorInfo | undefined { - const condition = expression.condition; - const finalExpression = getFinalExpressionInChain(expression.whenTrue); + const checker = program.getTypeChecker(); + return isConditionalExpression(expression) ? getConditionalInfo(expression, checker) : getBinaryInfo(expression); +} - if (!finalExpression || checker.isNullableType(checker.getTypeAtLocation(finalExpression))) { - return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; - } +function getConditionalInfo(expression: ConditionalExpression, checker: TypeChecker): OptionalChainInfo | RefactorErrorInfo | undefined { + const condition = expression.condition; + const finalExpression = getFinalExpressionInChain(expression.whenTrue); - if ((isPropertyAccessExpression(condition) || isIdentifier(condition)) - && getMatchingStart(condition, finalExpression.expression)) { - return { finalExpression, occurrences: [condition], expression }; - } - else if (isBinaryExpression(condition)) { - const occurrences = getOccurrencesInExpression(finalExpression.expression, condition); - return occurrences ? { finalExpression, occurrences, expression } : - { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_matching_access_expressions) }; - } + if (!finalExpression || checker.isNullableType(checker.getTypeAtLocation(finalExpression))) { + return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; } - function getBinaryInfo(expression: BinaryExpression): OptionalChainInfo | RefactorErrorInfo | undefined { - if (expression.operatorToken.kind !== SyntaxKind.AmpersandAmpersandToken) { - return { error: getLocaleSpecificMessage(Diagnostics.Can_only_convert_logical_AND_access_chains) }; - }; - const finalExpression = getFinalExpressionInChain(expression.right); - - if (!finalExpression) return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; - - const occurrences = getOccurrencesInExpression(finalExpression.expression, expression.left); + if ((isPropertyAccessExpression(condition) || isIdentifier(condition)) + && getMatchingStart(condition, finalExpression.expression)) { + return { finalExpression, occurrences: [condition], expression }; + } + else if (isBinaryExpression(condition)) { + const occurrences = getOccurrencesInExpression(finalExpression.expression, condition); return occurrences ? { finalExpression, occurrences, expression } : { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_matching_access_expressions) }; } +} - /** - * Gets a list of property accesses that appear in matchTo and occur in sequence in expression. - */ - function getOccurrencesInExpression(matchTo: Expression, expression: Expression): Occurrence[] | undefined { - const occurrences: Occurrence[] = []; - while (isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { - const match = getMatchingStart(skipParentheses(matchTo), skipParentheses(expression.right)); - if (!match) { - break; - } - occurrences.push(match); - matchTo = match; - expression = expression.left; - } - const finalMatch = getMatchingStart(matchTo, expression); - if (finalMatch) { - occurrences.push(finalMatch); - } - return occurrences.length > 0 ? occurrences: undefined; - } +function getBinaryInfo(expression: BinaryExpression): OptionalChainInfo | RefactorErrorInfo | undefined { + if (expression.operatorToken.kind !== SyntaxKind.AmpersandAmpersandToken) { + return { error: getLocaleSpecificMessage(Diagnostics.Can_only_convert_logical_AND_access_chains) }; + }; + const finalExpression = getFinalExpressionInChain(expression.right); - /** - * Returns subchain if chain begins with subchain syntactically. - */ - function getMatchingStart(chain: Expression, subchain: Expression): PropertyAccessExpression | ElementAccessExpression | Identifier | undefined { - if (!isIdentifier(subchain) && !isPropertyAccessExpression(subchain) && !isElementAccessExpression(subchain)) { - return undefined; - } - return chainStartsWith(chain, subchain) ? subchain : undefined; - } + if (!finalExpression) return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; - /** - * Returns true if chain begins with subchain syntactically. - */ - function chainStartsWith(chain: Node, subchain: Node): boolean { - // skip until we find a matching identifier. - while (isCallExpression(chain) || isPropertyAccessExpression(chain) || isElementAccessExpression(chain)) { - if (getTextOfChainNode(chain) === getTextOfChainNode(subchain)) break; - chain = chain.expression; - } - // check that the chains match at each access. Call chains in subchain are not valid. - while ((isPropertyAccessExpression(chain) && isPropertyAccessExpression(subchain)) || - (isElementAccessExpression(chain) && isElementAccessExpression(subchain))) { - if (getTextOfChainNode(chain) !== getTextOfChainNode(subchain)) return false; - chain = chain.expression; - subchain = subchain.expression; + const occurrences = getOccurrencesInExpression(finalExpression.expression, expression.left); + return occurrences ? { finalExpression, occurrences, expression } : + { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_matching_access_expressions) }; +} + +/** + * Gets a list of property accesses that appear in matchTo and occur in sequence in expression. + */ +function getOccurrencesInExpression(matchTo: Expression, expression: Expression): Occurrence[] | undefined { + const occurrences: Occurrence[] = []; + while (isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + const match = getMatchingStart(skipParentheses(matchTo), skipParentheses(expression.right)); + if (!match) { + break; } - // check if we have reached a final identifier. - return isIdentifier(chain) && isIdentifier(subchain) && chain.getText() === subchain.getText(); + occurrences.push(match); + matchTo = match; + expression = expression.left; + } + const finalMatch = getMatchingStart(matchTo, expression); + if (finalMatch) { + occurrences.push(finalMatch); } + return occurrences.length > 0 ? occurrences: undefined; +} - function getTextOfChainNode(node: Node): string | undefined { - if (isIdentifier(node) || isStringOrNumericLiteralLike(node)) { - return node.getText(); - } - if (isPropertyAccessExpression(node)) { - return getTextOfChainNode(node.name); - } - if (isElementAccessExpression(node)) { - return getTextOfChainNode(node.argumentExpression); - } +/** + * Returns subchain if chain begins with subchain syntactically. + */ +function getMatchingStart(chain: Expression, subchain: Expression): PropertyAccessExpression | ElementAccessExpression | Identifier | undefined { + if (!isIdentifier(subchain) && !isPropertyAccessExpression(subchain) && !isElementAccessExpression(subchain)) { return undefined; } + return chainStartsWith(chain, subchain) ? subchain : undefined; +} - /** - * Find the least ancestor of the input node that is a valid type for extraction and contains the input span. - */ - function getValidParentNodeContainingSpan(node: Node, span: TextSpan): ValidExpressionOrStatement | undefined { - while (node.parent) { - if (isValidExpressionOrStatement(node) && span.length !== 0 && node.end >= span.start + span.length) { - return node; - } - node = node.parent; - } - return undefined; +/** + * Returns true if chain begins with subchain syntactically. + */ +function chainStartsWith(chain: Node, subchain: Node): boolean { + // skip until we find a matching identifier. + while (isCallExpression(chain) || isPropertyAccessExpression(chain) || isElementAccessExpression(chain)) { + if (getTextOfChainNode(chain) === getTextOfChainNode(subchain)) break; + chain = chain.expression; + } + // check that the chains match at each access. Call chains in subchain are not valid. + while ((isPropertyAccessExpression(chain) && isPropertyAccessExpression(subchain)) || + (isElementAccessExpression(chain) && isElementAccessExpression(subchain))) { + if (getTextOfChainNode(chain) !== getTextOfChainNode(subchain)) return false; + chain = chain.expression; + subchain = subchain.expression; } + // check if we have reached a final identifier. + return isIdentifier(chain) && isIdentifier(subchain) && chain.getText() === subchain.getText(); +} - /** - * Finds an ancestor of the input node that is a valid type for extraction, skipping subexpressions. - */ - function getValidParentNodeOfEmptySpan(node: Node): ValidExpressionOrStatement | undefined { - while (node.parent) { - if (isValidExpressionOrStatement(node) && !isValidExpressionOrStatement(node.parent)) { - return node; - } - node = node.parent; - } - return undefined; +function getTextOfChainNode(node: Node): string | undefined { + if (isIdentifier(node) || isStringOrNumericLiteralLike(node)) { + return node.getText(); + } + if (isPropertyAccessExpression(node)) { + return getTextOfChainNode(node.name); } + if (isElementAccessExpression(node)) { + return getTextOfChainNode(node.argumentExpression); + } + return undefined; +} - /** - * Gets an expression of valid extraction type from a valid statement or expression. - */ - function getExpression(node: ValidExpressionOrStatement): ValidExpression | undefined { - if (isValidExpression(node)) { +/** + * Find the least ancestor of the input node that is a valid type for extraction and contains the input span. + */ +function getValidParentNodeContainingSpan(node: Node, span: TextSpan): ValidExpressionOrStatement | undefined { + while (node.parent) { + if (isValidExpressionOrStatement(node) && span.length !== 0 && node.end >= span.start + span.length) { return node; } - if (isVariableStatement(node)) { - const variable = getSingleVariableOfVariableStatement(node); - const initializer = variable?.initializer; - return initializer && isValidExpression(initializer) ? initializer : undefined; - } - return node.expression && isValidExpression(node.expression) ? node.expression : undefined; + node = node.parent; } + return undefined; +} - /** - * Gets a property access expression which may be nested inside of a binary expression. The final - * expression in an && chain will occur as the right child of the parent binary expression, unless - * it is followed by a different binary operator. - * @param node the right child of a binary expression or a call expression. - */ - function getFinalExpressionInChain(node: Expression): CallExpression | PropertyAccessExpression | ElementAccessExpression | undefined { - // foo && |foo.bar === 1|; - here the right child of the && binary expression is another binary expression. - // the rightmost member of the && chain should be the leftmost child of that expression. - node = skipParentheses(node); - if (isBinaryExpression(node)) { - return getFinalExpressionInChain(node.left); - } - // foo && |foo.bar()()| - nested calls are treated like further accesses. - else if ((isPropertyAccessExpression(node) || isElementAccessExpression(node) || isCallExpression(node)) && !isOptionalChain(node)) { +/** + * Finds an ancestor of the input node that is a valid type for extraction, skipping subexpressions. + */ +function getValidParentNodeOfEmptySpan(node: Node): ValidExpressionOrStatement | undefined { + while (node.parent) { + if (isValidExpressionOrStatement(node) && !isValidExpressionOrStatement(node.parent)) { return node; } - return undefined; + node = node.parent; + } + return undefined; +} + +/** + * Gets an expression of valid extraction type from a valid statement or expression. + */ +function getExpression(node: ValidExpressionOrStatement): ValidExpression | undefined { + if (isValidExpression(node)) { + return node; + } + if (isVariableStatement(node)) { + const variable = getSingleVariableOfVariableStatement(node); + const initializer = variable?.initializer; + return initializer && isValidExpression(initializer) ? initializer : undefined; + } + return node.expression && isValidExpression(node.expression) ? node.expression : undefined; +} + +/** + * Gets a property access expression which may be nested inside of a binary expression. The final + * expression in an && chain will occur as the right child of the parent binary expression, unless + * it is followed by a different binary operator. + * @param node the right child of a binary expression or a call expression. + */ +function getFinalExpressionInChain(node: Expression): CallExpression | PropertyAccessExpression | ElementAccessExpression | undefined { + // foo && |foo.bar === 1|; - here the right child of the && binary expression is another binary expression. + // the rightmost member of the && chain should be the leftmost child of that expression. + node = skipParentheses(node); + if (isBinaryExpression(node)) { + return getFinalExpressionInChain(node.left); + } + // foo && |foo.bar()()| - nested calls are treated like further accesses. + else if ((isPropertyAccessExpression(node) || isElementAccessExpression(node) || isCallExpression(node)) && !isOptionalChain(node)) { + return node; } + return undefined; +} - /** - * Creates an access chain from toConvert with '?.' accesses at expressions appearing in occurrences. - */ - function convertOccurrences(checker: TypeChecker, toConvert: Expression, occurrences: Occurrence[]): Expression { - if (isPropertyAccessExpression(toConvert) || isElementAccessExpression(toConvert) || isCallExpression(toConvert)) { - const chain = convertOccurrences(checker, toConvert.expression, occurrences); - const lastOccurrence = occurrences.length > 0 ? occurrences[occurrences.length - 1] : undefined; - const isOccurrence = lastOccurrence?.getText() === toConvert.expression.getText(); - if (isOccurrence) occurrences.pop(); - if (isCallExpression(toConvert)) { - return isOccurrence ? - factory.createCallChain(chain, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.typeArguments, toConvert.arguments) : - factory.createCallChain(chain, toConvert.questionDotToken, toConvert.typeArguments, toConvert.arguments); - } - else if (isPropertyAccessExpression(toConvert)) { - return isOccurrence ? - factory.createPropertyAccessChain(chain, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.name) : - factory.createPropertyAccessChain(chain, toConvert.questionDotToken, toConvert.name); - } - else if (isElementAccessExpression(toConvert)) { - return isOccurrence ? - factory.createElementAccessChain(chain, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.argumentExpression) : - factory.createElementAccessChain(chain, toConvert.questionDotToken, toConvert.argumentExpression); - } +/** + * Creates an access chain from toConvert with '?.' accesses at expressions appearing in occurrences. + */ +function convertOccurrences(checker: TypeChecker, toConvert: Expression, occurrences: Occurrence[]): Expression { + if (isPropertyAccessExpression(toConvert) || isElementAccessExpression(toConvert) || isCallExpression(toConvert)) { + const chain = convertOccurrences(checker, toConvert.expression, occurrences); + const lastOccurrence = occurrences.length > 0 ? occurrences[occurrences.length - 1] : undefined; + const isOccurrence = lastOccurrence?.getText() === toConvert.expression.getText(); + if (isOccurrence) occurrences.pop(); + if (isCallExpression(toConvert)) { + return isOccurrence ? + factory.createCallChain(chain, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.typeArguments, toConvert.arguments) : + factory.createCallChain(chain, toConvert.questionDotToken, toConvert.typeArguments, toConvert.arguments); + } + else if (isPropertyAccessExpression(toConvert)) { + return isOccurrence ? + factory.createPropertyAccessChain(chain, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.name) : + factory.createPropertyAccessChain(chain, toConvert.questionDotToken, toConvert.name); + } + else if (isElementAccessExpression(toConvert)) { + return isOccurrence ? + factory.createElementAccessChain(chain, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.argumentExpression) : + factory.createElementAccessChain(chain, toConvert.questionDotToken, toConvert.argumentExpression); } - return toConvert; } + return toConvert; +} - function doChange(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, info: OptionalChainInfo, _actionName: string): void { - const { finalExpression, occurrences, expression } = info; - const firstOccurrence = occurrences[occurrences.length - 1]; - const convertedChain = convertOccurrences(checker, finalExpression, occurrences); - if (convertedChain && (isPropertyAccessExpression(convertedChain) || isElementAccessExpression(convertedChain) || isCallExpression(convertedChain))) { - if (isBinaryExpression(expression)) { - changes.replaceNodeRange(sourceFile, firstOccurrence, finalExpression, convertedChain); - } - else if (isConditionalExpression(expression)) { - changes.replaceNode(sourceFile, expression, - factory.createBinaryExpression(convertedChain, factory.createToken(SyntaxKind.QuestionQuestionToken), expression.whenFalse) - ); - } +function doChange(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, info: OptionalChainInfo, _actionName: string): void { + const { finalExpression, occurrences, expression } = info; + const firstOccurrence = occurrences[occurrences.length - 1]; + const convertedChain = convertOccurrences(checker, finalExpression, occurrences); + if (convertedChain && (isPropertyAccessExpression(convertedChain) || isElementAccessExpression(convertedChain) || isCallExpression(convertedChain))) { + if (isBinaryExpression(expression)) { + changes.replaceNodeRange(sourceFile, firstOccurrence, finalExpression, convertedChain); + } + else if (isConditionalExpression(expression)) { + changes.replaceNode(sourceFile, expression, + factory.createBinaryExpression(convertedChain, factory.createToken(SyntaxKind.QuestionQuestionToken), expression.whenFalse) + ); } } } +} diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index df6da8c097e9c..64a198121a73f 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -1,2002 +1,2002 @@ /* @internal */ namespace ts.refactor.extractSymbol { - const refactorName = "Extract Symbol"; - - const extractConstantAction = { - name: "Extract Constant", - description: getLocaleSpecificMessage(Diagnostics.Extract_constant), - kind: "refactor.extract.constant", - }; - const extractFunctionAction = { - name: "Extract Function", - description: getLocaleSpecificMessage(Diagnostics.Extract_function), - kind: "refactor.extract.function", - }; - registerRefactor(refactorName, { - kinds: [ - extractConstantAction.kind, - extractFunctionAction.kind - ], - getAvailableActions, - getEditsForAction - }); - - /** - * Compute the associated code actions - * Exported for tests. - */ - export function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const requestedRefactor = context.kind; - const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context), context.triggerReason === "invoked"); - const targetRange = rangeToExtract.targetRange; - - if (targetRange === undefined) { - if (!rangeToExtract.errors || rangeToExtract.errors.length === 0 || !context.preferences.provideRefactorNotApplicableReason) { - return emptyArray; - } - - const errors = []; - if (refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)) { - errors.push({ - name: refactorName, - description: extractFunctionAction.description, - actions: [{ ...extractFunctionAction, notApplicableReason: getStringError(rangeToExtract.errors) }] - }); - } - if (refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) { - errors.push({ - name: refactorName, - description: extractConstantAction.description, - actions: [{ ...extractConstantAction, notApplicableReason: getStringError(rangeToExtract.errors) }] - }); - } - return errors; - } - - const extractions = getPossibleExtractions(targetRange, context); - if (extractions === undefined) { - // No extractions possible +const refactorName = "Extract Symbol"; + +const extractConstantAction = { + name: "Extract Constant", + description: getLocaleSpecificMessage(Diagnostics.Extract_constant), + kind: "refactor.extract.constant", +}; +const extractFunctionAction = { + name: "Extract Function", + description: getLocaleSpecificMessage(Diagnostics.Extract_function), + kind: "refactor.extract.function", +}; +registerRefactor(refactorName, { + kinds: [ + extractConstantAction.kind, + extractFunctionAction.kind + ], + getAvailableActions, + getEditsForAction +}); + +/** + * Compute the associated code actions + * Exported for tests. + */ +export function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const requestedRefactor = context.kind; + const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context), context.triggerReason === "invoked"); + const targetRange = rangeToExtract.targetRange; + + if (targetRange === undefined) { + if (!rangeToExtract.errors || rangeToExtract.errors.length === 0 || !context.preferences.provideRefactorNotApplicableReason) { return emptyArray; } - const functionActions: RefactorActionInfo[] = []; - const usedFunctionNames = new Map(); - let innermostErrorFunctionAction: RefactorActionInfo | undefined; + const errors = []; + if (refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)) { + errors.push({ + name: refactorName, + description: extractFunctionAction.description, + actions: [{ ...extractFunctionAction, notApplicableReason: getStringError(rangeToExtract.errors) }] + }); + } + if (refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) { + errors.push({ + name: refactorName, + description: extractConstantAction.description, + actions: [{ ...extractConstantAction, notApplicableReason: getStringError(rangeToExtract.errors) }] + }); + } + return errors; + } - const constantActions: RefactorActionInfo[] = []; - const usedConstantNames = new Map(); - let innermostErrorConstantAction: RefactorActionInfo | undefined; + const extractions = getPossibleExtractions(targetRange, context); + if (extractions === undefined) { + // No extractions possible + return emptyArray; + } - let i = 0; - for (const { functionExtraction, constantExtraction } of extractions) { - const description = functionExtraction.description; - if (refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)) { - if (functionExtraction.errors.length === 0) { - // Don't issue refactorings with duplicated names. - // Scopes come back in "innermost first" order, so extractions will - // preferentially go into nearer scopes - if (!usedFunctionNames.has(description)) { - usedFunctionNames.set(description, true); - functionActions.push({ - description, - name: `function_scope_${i}`, - kind: extractFunctionAction.kind - }); - } - } - else if (!innermostErrorFunctionAction) { - innermostErrorFunctionAction = { + const functionActions: RefactorActionInfo[] = []; + const usedFunctionNames = new Map(); + let innermostErrorFunctionAction: RefactorActionInfo | undefined; + + const constantActions: RefactorActionInfo[] = []; + const usedConstantNames = new Map(); + let innermostErrorConstantAction: RefactorActionInfo | undefined; + + let i = 0; + for (const { functionExtraction, constantExtraction } of extractions) { + const description = functionExtraction.description; + if (refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)) { + if (functionExtraction.errors.length === 0) { + // Don't issue refactorings with duplicated names. + // Scopes come back in "innermost first" order, so extractions will + // preferentially go into nearer scopes + if (!usedFunctionNames.has(description)) { + usedFunctionNames.set(description, true); + functionActions.push({ description, name: `function_scope_${i}`, - notApplicableReason: getStringError(functionExtraction.errors), kind: extractFunctionAction.kind - }; + }); } } - - if (refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) { - if (constantExtraction.errors.length === 0) { - // Don't issue refactorings with duplicated names. - // Scopes come back in "innermost first" order, so extractions will - // preferentially go into nearer scopes - const description = constantExtraction.description; - if (!usedConstantNames.has(description)) { - usedConstantNames.set(description, true); - constantActions.push({ - description, - name: `constant_scope_${i}`, - kind: extractConstantAction.kind - }); - } - } - else if (!innermostErrorConstantAction) { - innermostErrorConstantAction = { + else if (!innermostErrorFunctionAction) { + innermostErrorFunctionAction = { + description, + name: `function_scope_${i}`, + notApplicableReason: getStringError(functionExtraction.errors), + kind: extractFunctionAction.kind + }; + } + } + + if (refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) { + if (constantExtraction.errors.length === 0) { + // Don't issue refactorings with duplicated names. + // Scopes come back in "innermost first" order, so extractions will + // preferentially go into nearer scopes + const description = constantExtraction.description; + if (!usedConstantNames.has(description)) { + usedConstantNames.set(description, true); + constantActions.push({ description, name: `constant_scope_${i}`, - notApplicableReason: getStringError(constantExtraction.errors), kind: extractConstantAction.kind - }; + }); } } - - // *do* increment i anyway because we'll look for the i-th scope - // later when actually doing the refactoring if the user requests it - i++; + else if (!innermostErrorConstantAction) { + innermostErrorConstantAction = { + description, + name: `constant_scope_${i}`, + notApplicableReason: getStringError(constantExtraction.errors), + kind: extractConstantAction.kind + }; + } } - const infos: ApplicableRefactorInfo[] = []; + // *do* increment i anyway because we'll look for the i-th scope + // later when actually doing the refactoring if the user requests it + i++; + } - if (functionActions.length) { - infos.push({ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_function), - actions: functionActions, - }); - } - else if (context.preferences.provideRefactorNotApplicableReason && innermostErrorFunctionAction) { - infos.push({ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_function), - actions: [ innermostErrorFunctionAction ] - }); - } + const infos: ApplicableRefactorInfo[] = []; - if (constantActions.length) { - infos.push({ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_constant), - actions: constantActions - }); - } - else if (context.preferences.provideRefactorNotApplicableReason && innermostErrorConstantAction) { - infos.push({ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_constant), - actions: [ innermostErrorConstantAction ] - }); + if (functionActions.length) { + infos.push({ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_function), + actions: functionActions, + }); + } + else if (context.preferences.provideRefactorNotApplicableReason && innermostErrorFunctionAction) { + infos.push({ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_function), + actions: [ innermostErrorFunctionAction ] + }); + } + + if (constantActions.length) { + infos.push({ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_constant), + actions: constantActions + }); + } + else if (context.preferences.provideRefactorNotApplicableReason && innermostErrorConstantAction) { + infos.push({ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_constant), + actions: [ innermostErrorConstantAction ] + }); + } + + return infos.length ? infos : emptyArray; + + function getStringError(errors: readonly Diagnostic[]) { + let error = errors[0].messageText; + if (typeof error !== "string") { + error = error.messageText; } + return error; + } +} - return infos.length ? infos : emptyArray; +/* Exported for tests */ +export function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context)); + const targetRange = rangeToExtract.targetRange!; // TODO:GH#18217 - function getStringError(errors: readonly Diagnostic[]) { - let error = errors[0].messageText; - if (typeof error !== "string") { - error = error.messageText; - } - return error; - } - } - - /* Exported for tests */ - export function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context)); - const targetRange = rangeToExtract.targetRange!; // TODO:GH#18217 - - const parsedFunctionIndexMatch = /^function_scope_(\d+)$/.exec(actionName); - if (parsedFunctionIndexMatch) { - const index = +parsedFunctionIndexMatch[1]; - Debug.assert(isFinite(index), "Expected to parse a finite number from the function scope index"); - return getFunctionExtractionAtIndex(targetRange, context, index); - } - - const parsedConstantIndexMatch = /^constant_scope_(\d+)$/.exec(actionName); - if (parsedConstantIndexMatch) { - const index = +parsedConstantIndexMatch[1]; - Debug.assert(isFinite(index), "Expected to parse a finite number from the constant scope index"); - return getConstantExtractionAtIndex(targetRange, context, index); - } - - Debug.fail("Unrecognized action name"); - } - - // Move these into diagnostic messages if they become user-facing - export namespace Messages { - function createMessage(message: string): DiagnosticMessage { - return { message, code: 0, category: DiagnosticCategory.Message, key: message }; - } - - export const cannotExtractRange: DiagnosticMessage = createMessage("Cannot extract range."); - export const cannotExtractImport: DiagnosticMessage = createMessage("Cannot extract import statement."); - export const cannotExtractSuper: DiagnosticMessage = createMessage("Cannot extract super call."); - export const cannotExtractJSDoc: DiagnosticMessage = createMessage("Cannot extract JSDoc."); - export const cannotExtractEmpty: DiagnosticMessage = createMessage("Cannot extract empty range."); - export const expressionExpected: DiagnosticMessage = createMessage("expression expected."); - export const uselessConstantType: DiagnosticMessage = createMessage("No reason to extract constant of type."); - export const statementOrExpressionExpected: DiagnosticMessage = createMessage("Statement or expression expected."); - export const cannotExtractRangeContainingConditionalBreakOrContinueStatements: DiagnosticMessage = createMessage("Cannot extract range containing conditional break or continue statements."); - export const cannotExtractRangeContainingConditionalReturnStatement: DiagnosticMessage = createMessage("Cannot extract range containing conditional return statement."); - export const cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange: DiagnosticMessage = createMessage("Cannot extract range containing labeled break or continue with target outside of the range."); - export const cannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators: DiagnosticMessage = createMessage("Cannot extract range containing writes to references located outside of the target range in generators."); - export const typeWillNotBeVisibleInTheNewScope = createMessage("Type will not visible in the new scope."); - export const functionWillNotBeVisibleInTheNewScope = createMessage("Function will not visible in the new scope."); - export const cannotExtractIdentifier = createMessage("Select more than a single identifier."); - export const cannotExtractExportedEntity = createMessage("Cannot extract exported declaration"); - export const cannotWriteInExpression = createMessage("Cannot write back side-effects when extracting an expression"); - export const cannotExtractReadonlyPropertyInitializerOutsideConstructor = createMessage("Cannot move initialization of read-only class property outside of the constructor"); - export const cannotExtractAmbientBlock = createMessage("Cannot extract code from ambient contexts"); - export const cannotAccessVariablesFromNestedScopes = createMessage("Cannot access variables from nested scopes"); - export const cannotExtractToJSClass = createMessage("Cannot extract constant to a class scope in JS"); - export const cannotExtractToExpressionArrowFunction = createMessage("Cannot extract constant to an arrow function without a block"); - } - - enum RangeFacts { - None = 0, - HasReturn = 1 << 0, - IsGenerator = 1 << 1, - IsAsyncFunction = 1 << 2, - UsesThis = 1 << 3, - /** - * The range is in a function which needs the 'static' modifier in a class - */ - InStaticRegion = 1 << 4 + const parsedFunctionIndexMatch = /^function_scope_(\d+)$/.exec(actionName); + if (parsedFunctionIndexMatch) { + const index = +parsedFunctionIndexMatch[1]; + Debug.assert(isFinite(index), "Expected to parse a finite number from the function scope index"); + return getFunctionExtractionAtIndex(targetRange, context, index); } - /** - * Represents an expression or a list of statements that should be extracted with some extra information - */ - interface TargetRange { - readonly range: Expression | Statement[]; - readonly facts: RangeFacts; - /** - * A list of symbols that are declared in the selected range which are visible in the containing lexical scope - * Used to ensure we don't turn something used outside the range free (or worse, resolve to a different entity). - */ - readonly declarations: Symbol[]; + const parsedConstantIndexMatch = /^constant_scope_(\d+)$/.exec(actionName); + if (parsedConstantIndexMatch) { + const index = +parsedConstantIndexMatch[1]; + Debug.assert(isFinite(index), "Expected to parse a finite number from the constant scope index"); + return getConstantExtractionAtIndex(targetRange, context, index); } + Debug.fail("Unrecognized action name"); +} + +// Move these into diagnostic messages if they become user-facing +export namespace Messages { + function createMessage(message: string): DiagnosticMessage { + return { message, code: 0, category: DiagnosticCategory.Message, key: message }; + } + + export const cannotExtractRange: DiagnosticMessage = createMessage("Cannot extract range."); + export const cannotExtractImport: DiagnosticMessage = createMessage("Cannot extract import statement."); + export const cannotExtractSuper: DiagnosticMessage = createMessage("Cannot extract super call."); + export const cannotExtractJSDoc: DiagnosticMessage = createMessage("Cannot extract JSDoc."); + export const cannotExtractEmpty: DiagnosticMessage = createMessage("Cannot extract empty range."); + export const expressionExpected: DiagnosticMessage = createMessage("expression expected."); + export const uselessConstantType: DiagnosticMessage = createMessage("No reason to extract constant of type."); + export const statementOrExpressionExpected: DiagnosticMessage = createMessage("Statement or expression expected."); + export const cannotExtractRangeContainingConditionalBreakOrContinueStatements: DiagnosticMessage = createMessage("Cannot extract range containing conditional break or continue statements."); + export const cannotExtractRangeContainingConditionalReturnStatement: DiagnosticMessage = createMessage("Cannot extract range containing conditional return statement."); + export const cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange: DiagnosticMessage = createMessage("Cannot extract range containing labeled break or continue with target outside of the range."); + export const cannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators: DiagnosticMessage = createMessage("Cannot extract range containing writes to references located outside of the target range in generators."); + export const typeWillNotBeVisibleInTheNewScope = createMessage("Type will not visible in the new scope."); + export const functionWillNotBeVisibleInTheNewScope = createMessage("Function will not visible in the new scope."); + export const cannotExtractIdentifier = createMessage("Select more than a single identifier."); + export const cannotExtractExportedEntity = createMessage("Cannot extract exported declaration"); + export const cannotWriteInExpression = createMessage("Cannot write back side-effects when extracting an expression"); + export const cannotExtractReadonlyPropertyInitializerOutsideConstructor = createMessage("Cannot move initialization of read-only class property outside of the constructor"); + export const cannotExtractAmbientBlock = createMessage("Cannot extract code from ambient contexts"); + export const cannotAccessVariablesFromNestedScopes = createMessage("Cannot access variables from nested scopes"); + export const cannotExtractToJSClass = createMessage("Cannot extract constant to a class scope in JS"); + export const cannotExtractToExpressionArrowFunction = createMessage("Cannot extract constant to an arrow function without a block"); +} + +enum RangeFacts { + None = 0, + HasReturn = 1 << 0, + IsGenerator = 1 << 1, + IsAsyncFunction = 1 << 2, + UsesThis = 1 << 3, /** - * Result of 'getRangeToExtract' operation: contains either a range or a list of errors + * The range is in a function which needs the 'static' modifier in a class */ - type RangeToExtract = { - readonly targetRange?: never; - readonly errors: readonly Diagnostic[]; - } | { - readonly targetRange: TargetRange; - readonly errors?: never; - }; - - /* - * Scopes that can store newly extracted method - */ - type Scope = FunctionLikeDeclaration | SourceFile | ModuleBlock | ClassLikeDeclaration; + InStaticRegion = 1 << 4 +} +/** + * Represents an expression or a list of statements that should be extracted with some extra information + */ +interface TargetRange { + readonly range: Expression | Statement[]; + readonly facts: RangeFacts; /** - * getRangeToExtract takes a span inside a text file and returns either an expression or an array - * of statements representing the minimum set of nodes needed to extract the entire span. This - * process may fail, in which case a set of errors is returned instead. These errors are shown to - * users if they have the provideRefactorNotApplicableReason option set. + * A list of symbols that are declared in the selected range which are visible in the containing lexical scope + * Used to ensure we don't turn something used outside the range free (or worse, resolve to a different entity). */ - // exported only for tests - export function getRangeToExtract(sourceFile: SourceFile, span: TextSpan, invoked = true): RangeToExtract { - const { length } = span; - if (length === 0 && !invoked) { - return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractEmpty)] }; - } - const cursorRequest = length === 0 && invoked; + readonly declarations: Symbol[]; +} - const startToken = findFirstNonJsxWhitespaceToken(sourceFile, span.start); - const endToken = findTokenOnLeftOfPosition(sourceFile, textSpanEnd(span)); - /* If the refactoring command is invoked through a keyboard action it's safe to assume that the user is actively looking for - refactoring actions at the span location. As they may not know the exact range that will trigger a refactoring, we expand the - searched span to cover a real node range making it more likely that something useful will show up. */ - const adjustedSpan = startToken && endToken && invoked ? getAdjustedSpanFromNodes(startToken, endToken, sourceFile) : span; +/** + * Result of 'getRangeToExtract' operation: contains either a range or a list of errors + */ +type RangeToExtract = { + readonly targetRange?: never; + readonly errors: readonly Diagnostic[]; +} | { + readonly targetRange: TargetRange; + readonly errors?: never; +}; + +/* + * Scopes that can store newly extracted method + */ +type Scope = FunctionLikeDeclaration | SourceFile | ModuleBlock | ClassLikeDeclaration; + +/** + * getRangeToExtract takes a span inside a text file and returns either an expression or an array + * of statements representing the minimum set of nodes needed to extract the entire span. This + * process may fail, in which case a set of errors is returned instead. These errors are shown to + * users if they have the provideRefactorNotApplicableReason option set. + */ +// exported only for tests +export function getRangeToExtract(sourceFile: SourceFile, span: TextSpan, invoked = true): RangeToExtract { + const { length } = span; + if (length === 0 && !invoked) { + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractEmpty)] }; + } + const cursorRequest = length === 0 && invoked; - // Walk up starting from the the start position until we find a non-SourceFile node that subsumes the selected span. - // This may fail (e.g. you select two statements in the root of a source file) - const start = cursorRequest ? getExtractableParent(startToken): getParentNodeInSpan(startToken, sourceFile, adjustedSpan); + const startToken = findFirstNonJsxWhitespaceToken(sourceFile, span.start); + const endToken = findTokenOnLeftOfPosition(sourceFile, textSpanEnd(span)); + /* If the refactoring command is invoked through a keyboard action it's safe to assume that the user is actively looking for + refactoring actions at the span location. As they may not know the exact range that will trigger a refactoring, we expand the + searched span to cover a real node range making it more likely that something useful will show up. */ + const adjustedSpan = startToken && endToken && invoked ? getAdjustedSpanFromNodes(startToken, endToken, sourceFile) : span; - // Do the same for the ending position - const end = cursorRequest ? start : getParentNodeInSpan(endToken, sourceFile, adjustedSpan); + // Walk up starting from the the start position until we find a non-SourceFile node that subsumes the selected span. + // This may fail (e.g. you select two statements in the root of a source file) + const start = cursorRequest ? getExtractableParent(startToken): getParentNodeInSpan(startToken, sourceFile, adjustedSpan); - const declarations: Symbol[] = []; + // Do the same for the ending position + const end = cursorRequest ? start : getParentNodeInSpan(endToken, sourceFile, adjustedSpan); - // We'll modify these flags as we walk the tree to collect data - // about what things need to be done as part of the extraction. - let rangeFacts = RangeFacts.None; + const declarations: Symbol[] = []; - if (!start || !end) { - // cannot find either start or end node - return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; - } + // We'll modify these flags as we walk the tree to collect data + // about what things need to be done as part of the extraction. + let rangeFacts = RangeFacts.None; - if (isJSDoc(start)) { - return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractJSDoc)] }; - } + if (!start || !end) { + // cannot find either start or end node + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; + } - if (start.parent !== end.parent) { - // start and end nodes belong to different subtrees + if (isJSDoc(start)) { + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractJSDoc)] }; + } + + if (start.parent !== end.parent) { + // start and end nodes belong to different subtrees + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; + } + + if (start !== end) { + // start and end should be statements and parent should be either block or a source file + if (!isBlockLike(start.parent)) { return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; } - - if (start !== end) { - // start and end should be statements and parent should be either block or a source file - if (!isBlockLike(start.parent)) { - return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; - } - const statements: Statement[] = []; - for (const statement of start.parent.statements) { - if (statement === start || statements.length) { - const errors = checkNode(statement); - if (errors) { - return { errors }; - } - statements.push(statement); - } - if (statement === end) { - break; + const statements: Statement[] = []; + for (const statement of start.parent.statements) { + if (statement === start || statements.length) { + const errors = checkNode(statement); + if (errors) { + return { errors }; } + statements.push(statement); } - - if (!statements.length) { - // https://github.com/Microsoft/TypeScript/issues/20559 - // Ranges like [|case 1: break;|] will fail to populate `statements` because - // they will never find `start` in `start.parent.statements`. - // Consider: We could support ranges like [|case 1:|] by refining them to just - // the expression. - return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; + if (statement === end) { + break; } - - return { targetRange: { range: statements, facts: rangeFacts, declarations } }; } - if (isReturnStatement(start) && !start.expression) { - // Makes no sense to extract an expression-less return statement. + if (!statements.length) { + // https://github.com/Microsoft/TypeScript/issues/20559 + // Ranges like [|case 1: break;|] will fail to populate `statements` because + // they will never find `start` in `start.parent.statements`. + // Consider: We could support ranges like [|case 1:|] by refining them to just + // the expression. return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; } - // We have a single node (start) - const node = refineNode(start); + return { targetRange: { range: statements, facts: rangeFacts, declarations } }; + } - const errors = checkRootNode(node) || checkNode(node); - if (errors) { - return { errors }; - } - return { targetRange: { range: getStatementOrExpressionRange(node)!, facts: rangeFacts, declarations } }; // TODO: GH#18217 + if (isReturnStatement(start) && !start.expression) { + // Makes no sense to extract an expression-less return statement. + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; + } - /** - * Attempt to refine the extraction node (generally, by shrinking it) to produce better results. - * @param node The unrefined extraction node. - */ - function refineNode(node: Node): Node { - if (isReturnStatement(node)) { - if (node.expression) { - return node.expression; + // We have a single node (start) + const node = refineNode(start); + + const errors = checkRootNode(node) || checkNode(node); + if (errors) { + return { errors }; + } + return { targetRange: { range: getStatementOrExpressionRange(node)!, facts: rangeFacts, declarations } }; // TODO: GH#18217 + + /** + * Attempt to refine the extraction node (generally, by shrinking it) to produce better results. + * @param node The unrefined extraction node. + */ + function refineNode(node: Node): Node { + if (isReturnStatement(node)) { + if (node.expression) { + return node.expression; + } + } + else if (isVariableStatement(node) || isVariableDeclarationList(node)) { + const declarations = isVariableStatement(node) ? node.declarationList.declarations : node.declarations; + let numInitializers = 0; + let lastInitializer: Expression | undefined; + for (const declaration of declarations) { + if (declaration.initializer) { + numInitializers++; + lastInitializer = declaration.initializer; } } - else if (isVariableStatement(node) || isVariableDeclarationList(node)) { - const declarations = isVariableStatement(node) ? node.declarationList.declarations : node.declarations; - let numInitializers = 0; - let lastInitializer: Expression | undefined; - for (const declaration of declarations) { - if (declaration.initializer) { - numInitializers++; - lastInitializer = declaration.initializer; - } - } - if (numInitializers === 1) { - return lastInitializer!; - } - // No special handling if there are multiple initializers. + if (numInitializers === 1) { + return lastInitializer!; } - else if (isVariableDeclaration(node)) { - if (node.initializer) { - return node.initializer; - } + // No special handling if there are multiple initializers. + } + else if (isVariableDeclaration(node)) { + if (node.initializer) { + return node.initializer; } - return node; } + return node; + } - function checkRootNode(node: Node): Diagnostic[] | undefined { - if (isIdentifier(isExpressionStatement(node) ? node.expression : node)) { - return [createDiagnosticForNode(node, Messages.cannotExtractIdentifier)]; - } - return undefined; + function checkRootNode(node: Node): Diagnostic[] | undefined { + if (isIdentifier(isExpressionStatement(node) ? node.expression : node)) { + return [createDiagnosticForNode(node, Messages.cannotExtractIdentifier)]; } + return undefined; + } - function checkForStaticContext(nodeToCheck: Node, containingClass: Node) { - let current: Node = nodeToCheck; - while (current !== containingClass) { - if (current.kind === SyntaxKind.PropertyDeclaration) { - if (isStatic(current)) { - rangeFacts |= RangeFacts.InStaticRegion; - } - break; + function checkForStaticContext(nodeToCheck: Node, containingClass: Node) { + let current: Node = nodeToCheck; + while (current !== containingClass) { + if (current.kind === SyntaxKind.PropertyDeclaration) { + if (isStatic(current)) { + rangeFacts |= RangeFacts.InStaticRegion; } - else if (current.kind === SyntaxKind.Parameter) { - const ctorOrMethod = getContainingFunction(current)!; - if (ctorOrMethod.kind === SyntaxKind.Constructor) { - rangeFacts |= RangeFacts.InStaticRegion; - } - break; + break; + } + else if (current.kind === SyntaxKind.Parameter) { + const ctorOrMethod = getContainingFunction(current)!; + if (ctorOrMethod.kind === SyntaxKind.Constructor) { + rangeFacts |= RangeFacts.InStaticRegion; } - else if (current.kind === SyntaxKind.MethodDeclaration) { - if (isStatic(current)) { - rangeFacts |= RangeFacts.InStaticRegion; - } + break; + } + else if (current.kind === SyntaxKind.MethodDeclaration) { + if (isStatic(current)) { + rangeFacts |= RangeFacts.InStaticRegion; } - current = current.parent; } + current = current.parent; } + } - // Verifies whether we can actually extract this node or not. - function checkNode(nodeToCheck: Node): Diagnostic[] | undefined { - const enum PermittedJumps { - None = 0, - Break = 1 << 0, - Continue = 1 << 1, - Return = 1 << 2 - } - - // We believe it's true because the node is from the (unmodified) tree. - Debug.assert(nodeToCheck.pos <= nodeToCheck.end, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (1)"); + // Verifies whether we can actually extract this node or not. + function checkNode(nodeToCheck: Node): Diagnostic[] | undefined { + const enum PermittedJumps { + None = 0, + Break = 1 << 0, + Continue = 1 << 1, + Return = 1 << 2 + } - // For understanding how skipTrivia functioned: - Debug.assert(!positionIsSynthesized(nodeToCheck.pos), "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (2)"); + // We believe it's true because the node is from the (unmodified) tree. + Debug.assert(nodeToCheck.pos <= nodeToCheck.end, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (1)"); - if (!isStatement(nodeToCheck) && !(isExpressionNode(nodeToCheck) && isExtractableExpression(nodeToCheck))) { - return [createDiagnosticForNode(nodeToCheck, Messages.statementOrExpressionExpected)]; - } + // For understanding how skipTrivia functioned: + Debug.assert(!positionIsSynthesized(nodeToCheck.pos), "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (2)"); - if (nodeToCheck.flags & NodeFlags.Ambient) { - return [createDiagnosticForNode(nodeToCheck, Messages.cannotExtractAmbientBlock)]; - } + if (!isStatement(nodeToCheck) && !(isExpressionNode(nodeToCheck) && isExtractableExpression(nodeToCheck))) { + return [createDiagnosticForNode(nodeToCheck, Messages.statementOrExpressionExpected)]; + } - // If we're in a class, see whether we're in a static region (static property initializer, static method, class constructor parameter default) - const containingClass = getContainingClass(nodeToCheck); - if (containingClass) { - checkForStaticContext(nodeToCheck, containingClass); - } + if (nodeToCheck.flags & NodeFlags.Ambient) { + return [createDiagnosticForNode(nodeToCheck, Messages.cannotExtractAmbientBlock)]; + } - let errors: Diagnostic[] | undefined; - let permittedJumps = PermittedJumps.Return; - let seenLabels: __String[]; + // If we're in a class, see whether we're in a static region (static property initializer, static method, class constructor parameter default) + const containingClass = getContainingClass(nodeToCheck); + if (containingClass) { + checkForStaticContext(nodeToCheck, containingClass); + } - visit(nodeToCheck); + let errors: Diagnostic[] | undefined; + let permittedJumps = PermittedJumps.Return; + let seenLabels: __String[]; - return errors; + visit(nodeToCheck); - function visit(node: Node) { - if (errors) { - // already found an error - can stop now - return true; - } + return errors; - if (isDeclaration(node)) { - const declaringNode = (node.kind === SyntaxKind.VariableDeclaration) ? node.parent.parent : node; - if (hasSyntacticModifier(declaringNode, ModifierFlags.Export)) { - // TODO: GH#18217 Silly to use `errors ||` since it's definitely not defined (see top of `visit`) - // Also, if we're only pushing one error, just use `let error: Diagnostic | undefined`! - // Also TODO: GH#19956 - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractExportedEntity)); - return true; - } - declarations.push(node.symbol); - } + function visit(node: Node) { + if (errors) { + // already found an error - can stop now + return true; + } - // Some things can't be extracted in certain situations - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractImport)); - return true; - case SyntaxKind.ExportAssignment: - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractExportedEntity)); - return true; - case SyntaxKind.SuperKeyword: - // For a super *constructor call*, we have to be extracting the entire class, - // but a super *method call* simply implies a 'this' reference - if (node.parent.kind === SyntaxKind.CallExpression) { - // Super constructor call - const containingClass = getContainingClass(node)!; // TODO:GH#18217 - if (containingClass.pos < span.start || containingClass.end >= (span.start + span.length)) { - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractSuper)); - return true; - } - } - else { - rangeFacts |= RangeFacts.UsesThis; - } - break; - case SyntaxKind.ArrowFunction: - // check if arrow function uses this - forEachChild(node, function check(n) { - if (isThis(n)) { - rangeFacts |= RangeFacts.UsesThis; - } - else if (isClassLike(n) || (isFunctionLike(n) && !isArrowFunction(n))) { - return false; - } - else { - forEachChild(n, check); - } - }); - // falls through - case SyntaxKind.ClassDeclaration: - case SyntaxKind.FunctionDeclaration: - if (isSourceFile(node.parent) && node.parent.externalModuleIndicator === undefined) { - // You cannot extract global declarations - (errors ||= []).push(createDiagnosticForNode(node, Messages.functionWillNotBeVisibleInTheNewScope)); - } - // falls through - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // do not dive into functions or classes - return false; + if (isDeclaration(node)) { + const declaringNode = (node.kind === SyntaxKind.VariableDeclaration) ? node.parent.parent : node; + if (hasSyntacticModifier(declaringNode, ModifierFlags.Export)) { + // TODO: GH#18217 Silly to use `errors ||` since it's definitely not defined (see top of `visit`) + // Also, if we're only pushing one error, just use `let error: Diagnostic | undefined`! + // Also TODO: GH#19956 + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractExportedEntity)); + return true; } + declarations.push(node.symbol); + } - const savedPermittedJumps = permittedJumps; - switch (node.kind) { - case SyntaxKind.IfStatement: - permittedJumps = PermittedJumps.None; - break; - case SyntaxKind.TryStatement: - // forbid all jumps inside try blocks - permittedJumps = PermittedJumps.None; - break; - case SyntaxKind.Block: - if (node.parent && node.parent.kind === SyntaxKind.TryStatement && (node.parent as TryStatement).finallyBlock === node) { - // allow unconditional returns from finally blocks - permittedJumps = PermittedJumps.Return; - } - break; - case SyntaxKind.DefaultClause: - case SyntaxKind.CaseClause: - // allow unlabeled break inside case clauses - permittedJumps |= PermittedJumps.Break; - break; - default: - if (isIterationStatement(node, /*lookInLabeledStatements*/ false)) { - // allow unlabeled break/continue inside loops - permittedJumps |= PermittedJumps.Break | PermittedJumps.Continue; + // Some things can't be extracted in certain situations + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractImport)); + return true; + case SyntaxKind.ExportAssignment: + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractExportedEntity)); + return true; + case SyntaxKind.SuperKeyword: + // For a super *constructor call*, we have to be extracting the entire class, + // but a super *method call* simply implies a 'this' reference + if (node.parent.kind === SyntaxKind.CallExpression) { + // Super constructor call + const containingClass = getContainingClass(node)!; // TODO:GH#18217 + if (containingClass.pos < span.start || containingClass.end >= (span.start + span.length)) { + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractSuper)); + return true; } - break; - } - - switch (node.kind) { - case SyntaxKind.ThisType: - case SyntaxKind.ThisKeyword: + } + else { rangeFacts |= RangeFacts.UsesThis; - break; - case SyntaxKind.LabeledStatement: { - const label = (node as LabeledStatement).label; - (seenLabels || (seenLabels = [])).push(label.escapedText); - forEachChild(node, visit); - seenLabels.pop(); - break; } - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: { - const label = (node as BreakStatement | ContinueStatement).label; - if (label) { - if (!contains(seenLabels, label.escapedText)) { - // attempts to jump to label that is not in range to be extracted - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange)); - } + break; + case SyntaxKind.ArrowFunction: + // check if arrow function uses this + forEachChild(node, function check(n) { + if (isThis(n)) { + rangeFacts |= RangeFacts.UsesThis; + } + else if (isClassLike(n) || (isFunctionLike(n) && !isArrowFunction(n))) { + return false; } else { - if (!(permittedJumps & (node.kind === SyntaxKind.BreakStatement ? PermittedJumps.Break : PermittedJumps.Continue))) { - // attempt to break or continue in a forbidden context - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements)); - } + forEachChild(n, check); } - break; + }); + // falls through + case SyntaxKind.ClassDeclaration: + case SyntaxKind.FunctionDeclaration: + if (isSourceFile(node.parent) && node.parent.externalModuleIndicator === undefined) { + // You cannot extract global declarations + (errors ||= []).push(createDiagnosticForNode(node, Messages.functionWillNotBeVisibleInTheNewScope)); } - case SyntaxKind.AwaitExpression: - rangeFacts |= RangeFacts.IsAsyncFunction; - break; - case SyntaxKind.YieldExpression: - rangeFacts |= RangeFacts.IsGenerator; - break; - case SyntaxKind.ReturnStatement: - if (permittedJumps & PermittedJumps.Return) { - rangeFacts |= RangeFacts.HasReturn; + // falls through + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // do not dive into functions or classes + return false; + } + + const savedPermittedJumps = permittedJumps; + switch (node.kind) { + case SyntaxKind.IfStatement: + permittedJumps = PermittedJumps.None; + break; + case SyntaxKind.TryStatement: + // forbid all jumps inside try blocks + permittedJumps = PermittedJumps.None; + break; + case SyntaxKind.Block: + if (node.parent && node.parent.kind === SyntaxKind.TryStatement && (node.parent as TryStatement).finallyBlock === node) { + // allow unconditional returns from finally blocks + permittedJumps = PermittedJumps.Return; + } + break; + case SyntaxKind.DefaultClause: + case SyntaxKind.CaseClause: + // allow unlabeled break inside case clauses + permittedJumps |= PermittedJumps.Break; + break; + default: + if (isIterationStatement(node, /*lookInLabeledStatements*/ false)) { + // allow unlabeled break/continue inside loops + permittedJumps |= PermittedJumps.Break | PermittedJumps.Continue; + } + break; + } + + switch (node.kind) { + case SyntaxKind.ThisType: + case SyntaxKind.ThisKeyword: + rangeFacts |= RangeFacts.UsesThis; + break; + case SyntaxKind.LabeledStatement: { + const label = (node as LabeledStatement).label; + (seenLabels || (seenLabels = [])).push(label.escapedText); + forEachChild(node, visit); + seenLabels.pop(); + break; + } + case SyntaxKind.BreakStatement: + case SyntaxKind.ContinueStatement: { + const label = (node as BreakStatement | ContinueStatement).label; + if (label) { + if (!contains(seenLabels, label.escapedText)) { + // attempts to jump to label that is not in range to be extracted + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange)); } - else { - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingConditionalReturnStatement)); + } + else { + if (!(permittedJumps & (node.kind === SyntaxKind.BreakStatement ? PermittedJumps.Break : PermittedJumps.Continue))) { + // attempt to break or continue in a forbidden context + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements)); } - break; - default: - forEachChild(node, visit); - break; + } + break; } - - permittedJumps = savedPermittedJumps; + case SyntaxKind.AwaitExpression: + rangeFacts |= RangeFacts.IsAsyncFunction; + break; + case SyntaxKind.YieldExpression: + rangeFacts |= RangeFacts.IsGenerator; + break; + case SyntaxKind.ReturnStatement: + if (permittedJumps & PermittedJumps.Return) { + rangeFacts |= RangeFacts.HasReturn; + } + else { + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingConditionalReturnStatement)); + } + break; + default: + forEachChild(node, visit); + break; } - } - } - /** - * Includes the final semicolon so that the span covers statements in cases where it would otherwise - * only cover the declaration list. - */ - function getAdjustedSpanFromNodes(startNode: Node, endNode: Node, sourceFile: SourceFile): TextSpan { - const start = startNode.getStart(sourceFile); - let end = endNode.getEnd(); - if (sourceFile.text.charCodeAt(end) === CharacterCodes.semicolon) { - end++; + permittedJumps = savedPermittedJumps; } - return { start, length: end - start }; } +} - function getStatementOrExpressionRange(node: Node): Statement[] | Expression | undefined { - if (isStatement(node)) { - return [node]; - } - else if (isExpressionNode(node)) { - // If our selection is the expression in an ExpressionStatement, expand - // the selection to include the enclosing Statement (this stops us - // from trying to care about the return value of the extracted function - // and eliminates double semicolon insertion in certain scenarios) - return isExpressionStatement(node.parent) ? [node.parent] : node as Expression; - } - return undefined; +/** + * Includes the final semicolon so that the span covers statements in cases where it would otherwise + * only cover the declaration list. + */ +function getAdjustedSpanFromNodes(startNode: Node, endNode: Node, sourceFile: SourceFile): TextSpan { + const start = startNode.getStart(sourceFile); + let end = endNode.getEnd(); + if (sourceFile.text.charCodeAt(end) === CharacterCodes.semicolon) { + end++; } + return { start, length: end - start }; +} - function isScope(node: Node): node is Scope { - return isFunctionLikeDeclaration(node) || isSourceFile(node) || isModuleBlock(node) || isClassLike(node); +function getStatementOrExpressionRange(node: Node): Statement[] | Expression | undefined { + if (isStatement(node)) { + return [node]; + } + else if (isExpressionNode(node)) { + // If our selection is the expression in an ExpressionStatement, expand + // the selection to include the enclosing Statement (this stops us + // from trying to care about the return value of the extracted function + // and eliminates double semicolon insertion in certain scenarios) + return isExpressionStatement(node.parent) ? [node.parent] : node as Expression; } + return undefined; +} - /** - * Computes possible places we could extract the function into. For example, - * you may be able to extract into a class method *or* local closure *or* namespace function, - * depending on what's in the extracted body. - */ - function collectEnclosingScopes(range: TargetRange): Scope[] { - let current: Node = isReadonlyArray(range.range) ? first(range.range) : range.range; - if (range.facts & RangeFacts.UsesThis) { - // if range uses this as keyword or as type inside the class then it can only be extracted to a method of the containing class - const containingClass = getContainingClass(current); - if (containingClass) { - const containingFunction = findAncestor(current, isFunctionLikeDeclaration); - return containingFunction - ? [containingFunction, containingClass] - : [containingClass]; - } +function isScope(node: Node): node is Scope { + return isFunctionLikeDeclaration(node) || isSourceFile(node) || isModuleBlock(node) || isClassLike(node); +} + +/** + * Computes possible places we could extract the function into. For example, + * you may be able to extract into a class method *or* local closure *or* namespace function, + * depending on what's in the extracted body. + */ +function collectEnclosingScopes(range: TargetRange): Scope[] { + let current: Node = isReadonlyArray(range.range) ? first(range.range) : range.range; + if (range.facts & RangeFacts.UsesThis) { + // if range uses this as keyword or as type inside the class then it can only be extracted to a method of the containing class + const containingClass = getContainingClass(current); + if (containingClass) { + const containingFunction = findAncestor(current, isFunctionLikeDeclaration); + return containingFunction + ? [containingFunction, containingClass] + : [containingClass]; } + } - const scopes: Scope[] = []; - while (true) { - current = current.parent; - // A function parameter's initializer is actually in the outer scope, not the function declaration - if (current.kind === SyntaxKind.Parameter) { - // Skip all the way to the outer scope of the function that declared this parameter - current = findAncestor(current, parent => isFunctionLikeDeclaration(parent))!.parent; - } + const scopes: Scope[] = []; + while (true) { + current = current.parent; + // A function parameter's initializer is actually in the outer scope, not the function declaration + if (current.kind === SyntaxKind.Parameter) { + // Skip all the way to the outer scope of the function that declared this parameter + current = findAncestor(current, parent => isFunctionLikeDeclaration(parent))!.parent; + } - // We want to find the nearest parent where we can place an "equivalent" sibling to the node we're extracting out of. - // Walk up to the closest parent of a place where we can logically put a sibling: - // * Function declaration - // * Class declaration or expression - // * Module/namespace or source file - if (isScope(current)) { - scopes.push(current); - if (current.kind === SyntaxKind.SourceFile) { - return scopes; - } + // We want to find the nearest parent where we can place an "equivalent" sibling to the node we're extracting out of. + // Walk up to the closest parent of a place where we can logically put a sibling: + // * Function declaration + // * Class declaration or expression + // * Module/namespace or source file + if (isScope(current)) { + scopes.push(current); + if (current.kind === SyntaxKind.SourceFile) { + return scopes; } } } +} - function getFunctionExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo { - const { scopes, readsAndWrites: { target, usagesPerScope, functionErrorsPerScope, exposedVariableDeclarations } } = getPossibleExtractionsWorker(targetRange, context); - Debug.assert(!functionErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); - context.cancellationToken!.throwIfCancellationRequested(); // TODO: GH#18217 - return extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], exposedVariableDeclarations, targetRange, context); - } +function getFunctionExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo { + const { scopes, readsAndWrites: { target, usagesPerScope, functionErrorsPerScope, exposedVariableDeclarations } } = getPossibleExtractionsWorker(targetRange, context); + Debug.assert(!functionErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); + context.cancellationToken!.throwIfCancellationRequested(); // TODO: GH#18217 + return extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], exposedVariableDeclarations, targetRange, context); +} - function getConstantExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo { - const { scopes, readsAndWrites: { target, usagesPerScope, constantErrorsPerScope, exposedVariableDeclarations } } = getPossibleExtractionsWorker(targetRange, context); - Debug.assert(!constantErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); - Debug.assert(exposedVariableDeclarations.length === 0, "Extract constant accepted a range containing a variable declaration?"); - context.cancellationToken!.throwIfCancellationRequested(); - const expression = isExpression(target) - ? target - : (target.statements[0] as ExpressionStatement).expression; - return extractConstantInScope(expression, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange.facts, context); - } +function getConstantExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo { + const { scopes, readsAndWrites: { target, usagesPerScope, constantErrorsPerScope, exposedVariableDeclarations } } = getPossibleExtractionsWorker(targetRange, context); + Debug.assert(!constantErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); + Debug.assert(exposedVariableDeclarations.length === 0, "Extract constant accepted a range containing a variable declaration?"); + context.cancellationToken!.throwIfCancellationRequested(); + const expression = isExpression(target) + ? target + : (target.statements[0] as ExpressionStatement).expression; + return extractConstantInScope(expression, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange.facts, context); +} - interface Extraction { - readonly description: string; - readonly errors: readonly Diagnostic[]; - } +interface Extraction { + readonly description: string; + readonly errors: readonly Diagnostic[]; +} - interface ScopeExtractions { - readonly functionExtraction: Extraction; - readonly constantExtraction: Extraction; - } +interface ScopeExtractions { + readonly functionExtraction: Extraction; + readonly constantExtraction: Extraction; +} - /** - * Given a piece of text to extract ('targetRange'), computes a list of possible extractions. - * Each returned ExtractResultForScope corresponds to a possible target scope and is either a set of changes - * or an error explaining why we can't extract into that scope. - */ - function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext): readonly ScopeExtractions[] | undefined { - const { scopes, readsAndWrites: { functionErrorsPerScope, constantErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context); - // Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547 - const extractions = scopes.map((scope, i): ScopeExtractions => { - const functionDescriptionPart = getDescriptionForFunctionInScope(scope); - const constantDescriptionPart = getDescriptionForConstantInScope(scope); - - const scopeDescription = isFunctionLikeDeclaration(scope) - ? getDescriptionForFunctionLikeDeclaration(scope) - : isClassLike(scope) - ? getDescriptionForClassLikeDeclaration(scope) - : getDescriptionForModuleLikeDeclaration(scope); - - let functionDescription: string; - let constantDescription: string; - if (scopeDescription === SpecialScope.Global) { - functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [functionDescriptionPart, "global"]); - constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [constantDescriptionPart, "global"]); - } - else if (scopeDescription === SpecialScope.Module) { - functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [functionDescriptionPart, "module"]); - constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [constantDescriptionPart, "module"]); - } - else { - functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1), [functionDescriptionPart, scopeDescription]); - constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1), [constantDescriptionPart, scopeDescription]); - } +/** + * Given a piece of text to extract ('targetRange'), computes a list of possible extractions. + * Each returned ExtractResultForScope corresponds to a possible target scope and is either a set of changes + * or an error explaining why we can't extract into that scope. + */ +function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext): readonly ScopeExtractions[] | undefined { + const { scopes, readsAndWrites: { functionErrorsPerScope, constantErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context); + // Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547 + const extractions = scopes.map((scope, i): ScopeExtractions => { + const functionDescriptionPart = getDescriptionForFunctionInScope(scope); + const constantDescriptionPart = getDescriptionForConstantInScope(scope); + + const scopeDescription = isFunctionLikeDeclaration(scope) + ? getDescriptionForFunctionLikeDeclaration(scope) + : isClassLike(scope) + ? getDescriptionForClassLikeDeclaration(scope) + : getDescriptionForModuleLikeDeclaration(scope); - // Customize the phrasing for the innermost scope to increase clarity. - if (i === 0 && !isClassLike(scope)) { - constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_enclosing_scope), [constantDescriptionPart]); - } + let functionDescription: string; + let constantDescription: string; + if (scopeDescription === SpecialScope.Global) { + functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [functionDescriptionPart, "global"]); + constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [constantDescriptionPart, "global"]); + } + else if (scopeDescription === SpecialScope.Module) { + functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [functionDescriptionPart, "module"]); + constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [constantDescriptionPart, "module"]); + } + else { + functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1), [functionDescriptionPart, scopeDescription]); + constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1), [constantDescriptionPart, scopeDescription]); + } - return { - functionExtraction: { - description: functionDescription, - errors: functionErrorsPerScope[i], - }, - constantExtraction: { - description: constantDescription, - errors: constantErrorsPerScope[i], - }, - }; - }); - return extractions; - } + // Customize the phrasing for the innermost scope to increase clarity. + if (i === 0 && !isClassLike(scope)) { + constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_enclosing_scope), [constantDescriptionPart]); + } - function getPossibleExtractionsWorker(targetRange: TargetRange, context: RefactorContext): { readonly scopes: Scope[], readonly readsAndWrites: ReadsAndWrites } { - const { file: sourceFile } = context; + return { + functionExtraction: { + description: functionDescription, + errors: functionErrorsPerScope[i], + }, + constantExtraction: { + description: constantDescription, + errors: constantErrorsPerScope[i], + }, + }; + }); + return extractions; +} - const scopes = collectEnclosingScopes(targetRange); - const enclosingTextRange = getEnclosingTextRange(targetRange, sourceFile); - const readsAndWrites = collectReadsAndWrites( - targetRange, - scopes, - enclosingTextRange, - sourceFile, - context.program.getTypeChecker(), - context.cancellationToken!); - return { scopes, readsAndWrites }; - } +function getPossibleExtractionsWorker(targetRange: TargetRange, context: RefactorContext): { readonly scopes: Scope[], readonly readsAndWrites: ReadsAndWrites } { + const { file: sourceFile } = context; + + const scopes = collectEnclosingScopes(targetRange); + const enclosingTextRange = getEnclosingTextRange(targetRange, sourceFile); + const readsAndWrites = collectReadsAndWrites( + targetRange, + scopes, + enclosingTextRange, + sourceFile, + context.program.getTypeChecker(), + context.cancellationToken!); + return { scopes, readsAndWrites }; +} - function getDescriptionForFunctionInScope(scope: Scope): string { - return isFunctionLikeDeclaration(scope) - ? "inner function" - : isClassLike(scope) - ? "method" - : "function"; - } - function getDescriptionForConstantInScope(scope: Scope): string { - return isClassLike(scope) - ? "readonly field" - : "constant"; - } - function getDescriptionForFunctionLikeDeclaration(scope: FunctionLikeDeclaration): string { - switch (scope.kind) { - case SyntaxKind.Constructor: - return "constructor"; - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - return scope.name - ? `function '${scope.name.text}'` - : ANONYMOUS; - case SyntaxKind.ArrowFunction: - return "arrow function"; - case SyntaxKind.MethodDeclaration: - return `method '${scope.name.getText()}'`; - case SyntaxKind.GetAccessor: - return `'get ${scope.name.getText()}'`; - case SyntaxKind.SetAccessor: - return `'set ${scope.name.getText()}'`; - default: - throw Debug.assertNever(scope, `Unexpected scope kind ${(scope as FunctionLikeDeclaration).kind}`); - } - } - function getDescriptionForClassLikeDeclaration(scope: ClassLikeDeclaration): string { - return scope.kind === SyntaxKind.ClassDeclaration - ? scope.name ? `class '${scope.name.text}'` : "anonymous class declaration" - : scope.name ? `class expression '${scope.name.text}'` : "anonymous class expression"; - } - function getDescriptionForModuleLikeDeclaration(scope: SourceFile | ModuleBlock): string | SpecialScope { - return scope.kind === SyntaxKind.ModuleBlock - ? `namespace '${scope.parent.name.getText()}'` - : scope.externalModuleIndicator ? SpecialScope.Module : SpecialScope.Global; - } - - const enum SpecialScope { - Module, - Global, +function getDescriptionForFunctionInScope(scope: Scope): string { + return isFunctionLikeDeclaration(scope) + ? "inner function" + : isClassLike(scope) + ? "method" + : "function"; +} +function getDescriptionForConstantInScope(scope: Scope): string { + return isClassLike(scope) + ? "readonly field" + : "constant"; +} +function getDescriptionForFunctionLikeDeclaration(scope: FunctionLikeDeclaration): string { + switch (scope.kind) { + case SyntaxKind.Constructor: + return "constructor"; + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + return scope.name + ? `function '${scope.name.text}'` + : ANONYMOUS; + case SyntaxKind.ArrowFunction: + return "arrow function"; + case SyntaxKind.MethodDeclaration: + return `method '${scope.name.getText()}'`; + case SyntaxKind.GetAccessor: + return `'get ${scope.name.getText()}'`; + case SyntaxKind.SetAccessor: + return `'set ${scope.name.getText()}'`; + default: + throw Debug.assertNever(scope, `Unexpected scope kind ${(scope as FunctionLikeDeclaration).kind}`); } +} +function getDescriptionForClassLikeDeclaration(scope: ClassLikeDeclaration): string { + return scope.kind === SyntaxKind.ClassDeclaration + ? scope.name ? `class '${scope.name.text}'` : "anonymous class declaration" + : scope.name ? `class expression '${scope.name.text}'` : "anonymous class expression"; +} +function getDescriptionForModuleLikeDeclaration(scope: SourceFile | ModuleBlock): string | SpecialScope { + return scope.kind === SyntaxKind.ModuleBlock + ? `namespace '${scope.parent.name.getText()}'` + : scope.externalModuleIndicator ? SpecialScope.Module : SpecialScope.Global; +} - /** - * Result of 'extractRange' operation for a specific scope. - * Stores either a list of changes that should be applied to extract a range or a list of errors - */ - function extractFunctionInScope( - node: Statement | Expression | Block, - scope: Scope, - { usages: usagesInScope, typeParameterUsages, substitutions }: ScopeUsages, - exposedVariableDeclarations: readonly VariableDeclaration[], - range: TargetRange, - context: RefactorContext): RefactorEditInfo { - - const checker = context.program.getTypeChecker(); - const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions()); - const importAdder = codefix.createImportAdder(context.file, context.program, context.preferences, context.host); - - // Make a unique name for the extracted function - const file = scope.getSourceFile(); - const functionNameText = getUniqueName(isClassLike(scope) ? "newMethod" : "newFunction", file); - const isJS = isInJSFile(scope); - - const functionName = factory.createIdentifier(functionNameText); - - let returnType: TypeNode | undefined; - const parameters: ParameterDeclaration[] = []; - const callArguments: Identifier[] = []; - let writes: UsageEntry[] | undefined; - usagesInScope.forEach((usage, name) => { - let typeNode: TypeNode | undefined; - if (!isJS) { - let type = checker.getTypeOfSymbolAtLocation(usage.symbol, usage.node); - // Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {" - type = checker.getBaseTypeOfLiteralType(type); - typeNode = codefix.typeToAutoImportableTypeNode(checker, importAdder, type, scope, scriptTarget, NodeBuilderFlags.NoTruncation); - } - - const paramDecl = factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - /*name*/ name, - /*questionToken*/ undefined, - typeNode - ); - parameters.push(paramDecl); - if (usage.usage === Usage.Write) { - (writes || (writes = [])).push(usage); - } - callArguments.push(factory.createIdentifier(name)); - }); - - const typeParametersAndDeclarations = arrayFrom(typeParameterUsages.values()).map(type => ({ type, declaration: getFirstDeclaration(type) })); - const sortedTypeParametersAndDeclarations = typeParametersAndDeclarations.sort(compareTypesByDeclarationOrder); - - const typeParameters: readonly TypeParameterDeclaration[] | undefined = sortedTypeParametersAndDeclarations.length === 0 - ? undefined - : sortedTypeParametersAndDeclarations.map(t => t.declaration as TypeParameterDeclaration); +const enum SpecialScope { + Module, + Global, +} - // Strictly speaking, we should check whether each name actually binds to the appropriate type - // parameter. In cases of shadowing, they may not. - const callTypeArguments: readonly TypeNode[] | undefined = typeParameters !== undefined - ? typeParameters.map(decl => factory.createTypeReferenceNode(decl.name, /*typeArguments*/ undefined)) - : undefined; +/** + * Result of 'extractRange' operation for a specific scope. + * Stores either a list of changes that should be applied to extract a range or a list of errors + */ +function extractFunctionInScope( + node: Statement | Expression | Block, + scope: Scope, + { usages: usagesInScope, typeParameterUsages, substitutions }: ScopeUsages, + exposedVariableDeclarations: readonly VariableDeclaration[], + range: TargetRange, + context: RefactorContext): RefactorEditInfo { + + const checker = context.program.getTypeChecker(); + const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions()); + const importAdder = codefix.createImportAdder(context.file, context.program, context.preferences, context.host); + + // Make a unique name for the extracted function + const file = scope.getSourceFile(); + const functionNameText = getUniqueName(isClassLike(scope) ? "newMethod" : "newFunction", file); + const isJS = isInJSFile(scope); + + const functionName = factory.createIdentifier(functionNameText); + + let returnType: TypeNode | undefined; + const parameters: ParameterDeclaration[] = []; + const callArguments: Identifier[] = []; + let writes: UsageEntry[] | undefined; + usagesInScope.forEach((usage, name) => { + let typeNode: TypeNode | undefined; + if (!isJS) { + let type = checker.getTypeOfSymbolAtLocation(usage.symbol, usage.node); + // Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {" + type = checker.getBaseTypeOfLiteralType(type); + typeNode = codefix.typeToAutoImportableTypeNode(checker, importAdder, type, scope, scriptTarget, NodeBuilderFlags.NoTruncation); + } + + const paramDecl = factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + /*name*/ name, + /*questionToken*/ undefined, + typeNode + ); + parameters.push(paramDecl); + if (usage.usage === Usage.Write) { + (writes || (writes = [])).push(usage); + } + callArguments.push(factory.createIdentifier(name)); + }); - // Provide explicit return types for contextually-typed functions - // to avoid problems when there are literal types present - if (isExpression(node) && !isJS) { - const contextualType = checker.getContextualType(node); - returnType = checker.typeToTypeNode(contextualType!, scope, NodeBuilderFlags.NoTruncation); // TODO: GH#18217 - } + const typeParametersAndDeclarations = arrayFrom(typeParameterUsages.values()).map(type => ({ type, declaration: getFirstDeclaration(type) })); + const sortedTypeParametersAndDeclarations = typeParametersAndDeclarations.sort(compareTypesByDeclarationOrder); - const { body, returnValueProperty } = transformFunctionBody(node, exposedVariableDeclarations, writes, substitutions, !!(range.facts & RangeFacts.HasReturn)); - suppressLeadingAndTrailingTrivia(body); + const typeParameters: readonly TypeParameterDeclaration[] | undefined = sortedTypeParametersAndDeclarations.length === 0 + ? undefined + : sortedTypeParametersAndDeclarations.map(t => t.declaration as TypeParameterDeclaration); - let newFunction: MethodDeclaration | FunctionDeclaration; + // Strictly speaking, we should check whether each name actually binds to the appropriate type + // parameter. In cases of shadowing, they may not. + const callTypeArguments: readonly TypeNode[] | undefined = typeParameters !== undefined + ? typeParameters.map(decl => factory.createTypeReferenceNode(decl.name, /*typeArguments*/ undefined)) + : undefined; - if (isClassLike(scope)) { - // always create private method in TypeScript files - const modifiers: Modifier[] = isJS ? [] : [factory.createModifier(SyntaxKind.PrivateKeyword)]; - if (range.facts & RangeFacts.InStaticRegion) { - modifiers.push(factory.createModifier(SyntaxKind.StaticKeyword)); - } - if (range.facts & RangeFacts.IsAsyncFunction) { - modifiers.push(factory.createModifier(SyntaxKind.AsyncKeyword)); - } - newFunction = factory.createMethodDeclaration( - /*decorators*/ undefined, - modifiers.length ? modifiers : undefined, - range.facts & RangeFacts.IsGenerator ? factory.createToken(SyntaxKind.AsteriskToken) : undefined, - functionName, - /*questionToken*/ undefined, - typeParameters, - parameters, - returnType, - body - ); - } - else { - newFunction = factory.createFunctionDeclaration( - /*decorators*/ undefined, - range.facts & RangeFacts.IsAsyncFunction ? [factory.createToken(SyntaxKind.AsyncKeyword)] : undefined, - range.facts & RangeFacts.IsGenerator ? factory.createToken(SyntaxKind.AsteriskToken) : undefined, - functionName, - typeParameters, - parameters, - returnType, - body - ); - } + // Provide explicit return types for contextually-typed functions + // to avoid problems when there are literal types present + if (isExpression(node) && !isJS) { + const contextualType = checker.getContextualType(node); + returnType = checker.typeToTypeNode(contextualType!, scope, NodeBuilderFlags.NoTruncation); // TODO: GH#18217 + } - const changeTracker = textChanges.ChangeTracker.fromContext(context); - const minInsertionPos = (isReadonlyArray(range.range) ? last(range.range) : range.range).end; - const nodeToInsertBefore = getNodeToInsertFunctionBefore(minInsertionPos, scope); - if (nodeToInsertBefore) { - changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newFunction, /*blankLineBetween*/ true); - } - else { - changeTracker.insertNodeAtEndOfScope(context.file, scope, newFunction); - } - importAdder.writeFixes(changeTracker); + const { body, returnValueProperty } = transformFunctionBody(node, exposedVariableDeclarations, writes, substitutions, !!(range.facts & RangeFacts.HasReturn)); + suppressLeadingAndTrailingTrivia(body); - const newNodes: Node[] = []; - // replace range with function call - const called = getCalledExpression(scope, range, functionNameText); + let newFunction: MethodDeclaration | FunctionDeclaration; - let call: Expression = factory.createCallExpression( - called, - callTypeArguments, // Note that no attempt is made to take advantage of type argument inference - callArguments); - if (range.facts & RangeFacts.IsGenerator) { - call = factory.createYieldExpression(factory.createToken(SyntaxKind.AsteriskToken), call); + if (isClassLike(scope)) { + // always create private method in TypeScript files + const modifiers: Modifier[] = isJS ? [] : [factory.createModifier(SyntaxKind.PrivateKeyword)]; + if (range.facts & RangeFacts.InStaticRegion) { + modifiers.push(factory.createModifier(SyntaxKind.StaticKeyword)); } if (range.facts & RangeFacts.IsAsyncFunction) { - call = factory.createAwaitExpression(call); - } - if (isInJSXContent(node)) { - call = factory.createJsxExpression(/*dotDotDotToken*/ undefined, call); - } + modifiers.push(factory.createModifier(SyntaxKind.AsyncKeyword)); + } + newFunction = factory.createMethodDeclaration( + /*decorators*/ undefined, + modifiers.length ? modifiers : undefined, + range.facts & RangeFacts.IsGenerator ? factory.createToken(SyntaxKind.AsteriskToken) : undefined, + functionName, + /*questionToken*/ undefined, + typeParameters, + parameters, + returnType, + body + ); + } + else { + newFunction = factory.createFunctionDeclaration( + /*decorators*/ undefined, + range.facts & RangeFacts.IsAsyncFunction ? [factory.createToken(SyntaxKind.AsyncKeyword)] : undefined, + range.facts & RangeFacts.IsGenerator ? factory.createToken(SyntaxKind.AsteriskToken) : undefined, + functionName, + typeParameters, + parameters, + returnType, + body + ); + } + + const changeTracker = textChanges.ChangeTracker.fromContext(context); + const minInsertionPos = (isReadonlyArray(range.range) ? last(range.range) : range.range).end; + const nodeToInsertBefore = getNodeToInsertFunctionBefore(minInsertionPos, scope); + if (nodeToInsertBefore) { + changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newFunction, /*blankLineBetween*/ true); + } + else { + changeTracker.insertNodeAtEndOfScope(context.file, scope, newFunction); + } + importAdder.writeFixes(changeTracker); + + const newNodes: Node[] = []; + // replace range with function call + const called = getCalledExpression(scope, range, functionNameText); + + let call: Expression = factory.createCallExpression( + called, + callTypeArguments, // Note that no attempt is made to take advantage of type argument inference + callArguments); + if (range.facts & RangeFacts.IsGenerator) { + call = factory.createYieldExpression(factory.createToken(SyntaxKind.AsteriskToken), call); + } + if (range.facts & RangeFacts.IsAsyncFunction) { + call = factory.createAwaitExpression(call); + } + if (isInJSXContent(node)) { + call = factory.createJsxExpression(/*dotDotDotToken*/ undefined, call); + } - if (exposedVariableDeclarations.length && !writes) { - // No need to mix declarations and writes. + if (exposedVariableDeclarations.length && !writes) { + // No need to mix declarations and writes. - // How could any variables be exposed if there's a return statement? - Debug.assert(!returnValueProperty, "Expected no returnValueProperty"); - Debug.assert(!(range.facts & RangeFacts.HasReturn), "Expected RangeFacts.HasReturn flag to be unset"); + // How could any variables be exposed if there's a return statement? + Debug.assert(!returnValueProperty, "Expected no returnValueProperty"); + Debug.assert(!(range.facts & RangeFacts.HasReturn), "Expected RangeFacts.HasReturn flag to be unset"); - if (exposedVariableDeclarations.length === 1) { - // Declaring exactly one variable: let x = newFunction(); - const variableDeclaration = exposedVariableDeclarations[0]; - newNodes.push(factory.createVariableStatement( + if (exposedVariableDeclarations.length === 1) { + // Declaring exactly one variable: let x = newFunction(); + const variableDeclaration = exposedVariableDeclarations[0]; + newNodes.push(factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration(getSynthesizedDeepClone(variableDeclaration.name), /*exclamationToken*/ undefined, /*type*/ getSynthesizedDeepClone(variableDeclaration.type), /*initializer*/ call)], // TODO (acasey): test binding patterns + variableDeclaration.parent.flags))); + } + else { + // Declaring multiple variables / return properties: + // let {x, y} = newFunction(); + const bindingElements: BindingElement[] = []; + const typeElements: TypeElement[] = []; + let commonNodeFlags = exposedVariableDeclarations[0].parent.flags; + let sawExplicitType = false; + for (const variableDeclaration of exposedVariableDeclarations) { + bindingElements.push(factory.createBindingElement( + /*dotDotDotToken*/ undefined, + /*propertyName*/ undefined, + /*name*/ getSynthesizedDeepClone(variableDeclaration.name))); + + // Being returned through an object literal will have widened the type. + const variableType: TypeNode | undefined = checker.typeToTypeNode( + checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(variableDeclaration)), + scope, + NodeBuilderFlags.NoTruncation); + + typeElements.push(factory.createPropertySignature( /*modifiers*/ undefined, - factory.createVariableDeclarationList( - [factory.createVariableDeclaration(getSynthesizedDeepClone(variableDeclaration.name), /*exclamationToken*/ undefined, /*type*/ getSynthesizedDeepClone(variableDeclaration.type), /*initializer*/ call)], // TODO (acasey): test binding patterns - variableDeclaration.parent.flags))); + /*name*/ variableDeclaration.symbol.name, + /*questionToken*/ undefined, + /*type*/ variableType)); + sawExplicitType = sawExplicitType || variableDeclaration.type !== undefined; + commonNodeFlags = commonNodeFlags & variableDeclaration.parent.flags; } - else { - // Declaring multiple variables / return properties: - // let {x, y} = newFunction(); - const bindingElements: BindingElement[] = []; - const typeElements: TypeElement[] = []; - let commonNodeFlags = exposedVariableDeclarations[0].parent.flags; - let sawExplicitType = false; - for (const variableDeclaration of exposedVariableDeclarations) { - bindingElements.push(factory.createBindingElement( - /*dotDotDotToken*/ undefined, - /*propertyName*/ undefined, - /*name*/ getSynthesizedDeepClone(variableDeclaration.name))); - - // Being returned through an object literal will have widened the type. - const variableType: TypeNode | undefined = checker.typeToTypeNode( - checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(variableDeclaration)), - scope, - NodeBuilderFlags.NoTruncation); - - typeElements.push(factory.createPropertySignature( - /*modifiers*/ undefined, - /*name*/ variableDeclaration.symbol.name, - /*questionToken*/ undefined, - /*type*/ variableType)); - sawExplicitType = sawExplicitType || variableDeclaration.type !== undefined; - commonNodeFlags = commonNodeFlags & variableDeclaration.parent.flags; - } - - const typeLiteral: TypeLiteralNode | undefined = sawExplicitType ? factory.createTypeLiteralNode(typeElements) : undefined; - if (typeLiteral) { - setEmitFlags(typeLiteral, EmitFlags.SingleLine); - } - newNodes.push(factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList( - [factory.createVariableDeclaration( - factory.createObjectBindingPattern(bindingElements), - /*exclamationToken*/ undefined, - /*type*/ typeLiteral, - /*initializer*/call)], - commonNodeFlags))); + const typeLiteral: TypeLiteralNode | undefined = sawExplicitType ? factory.createTypeLiteralNode(typeElements) : undefined; + if (typeLiteral) { + setEmitFlags(typeLiteral, EmitFlags.SingleLine); } - } - else if (exposedVariableDeclarations.length || writes) { - if (exposedVariableDeclarations.length) { - // CONSIDER: we're going to create one statement per variable, but we could actually preserve their original grouping. - for (const variableDeclaration of exposedVariableDeclarations) { - let flags: NodeFlags = variableDeclaration.parent.flags; - if (flags & NodeFlags.Const) { - flags = (flags & ~NodeFlags.Const) | NodeFlags.Let; - } - newNodes.push(factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList( - [factory.createVariableDeclaration(variableDeclaration.symbol.name, /*exclamationToken*/ undefined, getTypeDeepCloneUnionUndefined(variableDeclaration.type))], - flags))); + newNodes.push(factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration( + factory.createObjectBindingPattern(bindingElements), + /*exclamationToken*/ undefined, + /*type*/ typeLiteral, + /*initializer*/call)], + commonNodeFlags))); + } + } + else if (exposedVariableDeclarations.length || writes) { + if (exposedVariableDeclarations.length) { + // CONSIDER: we're going to create one statement per variable, but we could actually preserve their original grouping. + for (const variableDeclaration of exposedVariableDeclarations) { + let flags: NodeFlags = variableDeclaration.parent.flags; + if (flags & NodeFlags.Const) { + flags = (flags & ~NodeFlags.Const) | NodeFlags.Let; } - } - if (returnValueProperty) { - // has both writes and return, need to create variable declaration to hold return value; newNodes.push(factory.createVariableStatement( /*modifiers*/ undefined, factory.createVariableDeclarationList( - [factory.createVariableDeclaration(returnValueProperty, /*exclamationToken*/ undefined, getTypeDeepCloneUnionUndefined(returnType))], - NodeFlags.Let))); + [factory.createVariableDeclaration(variableDeclaration.symbol.name, /*exclamationToken*/ undefined, getTypeDeepCloneUnionUndefined(variableDeclaration.type))], + flags))); } + } - const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes); - if (returnValueProperty) { - assignments.unshift(factory.createShorthandPropertyAssignment(returnValueProperty)); - } + if (returnValueProperty) { + // has both writes and return, need to create variable declaration to hold return value; + newNodes.push(factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration(returnValueProperty, /*exclamationToken*/ undefined, getTypeDeepCloneUnionUndefined(returnType))], + NodeFlags.Let))); + } - // propagate writes back - if (assignments.length === 1) { - // We would only have introduced a return value property if there had been - // other assignments to make. - Debug.assert(!returnValueProperty, "Shouldn't have returnValueProperty here"); + const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes); + if (returnValueProperty) { + assignments.unshift(factory.createShorthandPropertyAssignment(returnValueProperty)); + } - newNodes.push(factory.createExpressionStatement(factory.createAssignment(assignments[0].name, call))); + // propagate writes back + if (assignments.length === 1) { + // We would only have introduced a return value property if there had been + // other assignments to make. + Debug.assert(!returnValueProperty, "Shouldn't have returnValueProperty here"); - if (range.facts & RangeFacts.HasReturn) { - newNodes.push(factory.createReturnStatement()); - } - } - else { - // emit e.g. - // { a, b, __return } = newFunction(a, b); - // return __return; - newNodes.push(factory.createExpressionStatement(factory.createAssignment(factory.createObjectLiteralExpression(assignments), call))); - if (returnValueProperty) { - newNodes.push(factory.createReturnStatement(factory.createIdentifier(returnValueProperty))); - } + newNodes.push(factory.createExpressionStatement(factory.createAssignment(assignments[0].name, call))); + + if (range.facts & RangeFacts.HasReturn) { + newNodes.push(factory.createReturnStatement()); } } else { - if (range.facts & RangeFacts.HasReturn) { - newNodes.push(factory.createReturnStatement(call)); - } - else if (isReadonlyArray(range.range)) { - newNodes.push(factory.createExpressionStatement(call)); - } - else { - newNodes.push(call); + // emit e.g. + // { a, b, __return } = newFunction(a, b); + // return __return; + newNodes.push(factory.createExpressionStatement(factory.createAssignment(factory.createObjectLiteralExpression(assignments), call))); + if (returnValueProperty) { + newNodes.push(factory.createReturnStatement(factory.createIdentifier(returnValueProperty))); } } - - if (isReadonlyArray(range.range)) { - changeTracker.replaceNodeRangeWithNodes(context.file, first(range.range), last(range.range), newNodes); + } + else { + if (range.facts & RangeFacts.HasReturn) { + newNodes.push(factory.createReturnStatement(call)); + } + else if (isReadonlyArray(range.range)) { + newNodes.push(factory.createExpressionStatement(call)); } else { - changeTracker.replaceNodeWithNodes(context.file, range.range, newNodes); + newNodes.push(call); } + } - const edits = changeTracker.getChanges(); - const renameRange = isReadonlyArray(range.range) ? first(range.range) : range.range; + if (isReadonlyArray(range.range)) { + changeTracker.replaceNodeRangeWithNodes(context.file, first(range.range), last(range.range), newNodes); + } + else { + changeTracker.replaceNodeWithNodes(context.file, range.range, newNodes); + } - const renameFilename = renameRange.getSourceFile().fileName; - const renameLocation = getRenameLocation(edits, renameFilename, functionNameText, /*isDeclaredBeforeUse*/ false); - return { renameFilename, renameLocation, edits }; + const edits = changeTracker.getChanges(); + const renameRange = isReadonlyArray(range.range) ? first(range.range) : range.range; - function getTypeDeepCloneUnionUndefined(typeNode: TypeNode | undefined): TypeNode | undefined { - if (typeNode === undefined) { - return undefined; - } + const renameFilename = renameRange.getSourceFile().fileName; + const renameLocation = getRenameLocation(edits, renameFilename, functionNameText, /*isDeclaredBeforeUse*/ false); + return { renameFilename, renameLocation, edits }; - const clone = getSynthesizedDeepClone(typeNode); - let withoutParens = clone; - while (isParenthesizedTypeNode(withoutParens)) { - withoutParens = withoutParens.type; - } - return isUnionTypeNode(withoutParens) && find(withoutParens.types, t => t.kind === SyntaxKind.UndefinedKeyword) - ? clone - : factory.createUnionTypeNode([clone, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + function getTypeDeepCloneUnionUndefined(typeNode: TypeNode | undefined): TypeNode | undefined { + if (typeNode === undefined) { + return undefined; } + + const clone = getSynthesizedDeepClone(typeNode); + let withoutParens = clone; + while (isParenthesizedTypeNode(withoutParens)) { + withoutParens = withoutParens.type; + } + return isUnionTypeNode(withoutParens) && find(withoutParens.types, t => t.kind === SyntaxKind.UndefinedKeyword) + ? clone + : factory.createUnionTypeNode([clone, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); } +} + +/** + * Result of 'extractRange' operation for a specific scope. + * Stores either a list of changes that should be applied to extract a range or a list of errors + */ +function extractConstantInScope( + node: Expression, + scope: Scope, + { substitutions }: ScopeUsages, + rangeFacts: RangeFacts, + context: RefactorContext): RefactorEditInfo { + + const checker = context.program.getTypeChecker(); - /** - * Result of 'extractRange' operation for a specific scope. - * Stores either a list of changes that should be applied to extract a range or a list of errors - */ - function extractConstantInScope( - node: Expression, - scope: Scope, - { substitutions }: ScopeUsages, - rangeFacts: RangeFacts, - context: RefactorContext): RefactorEditInfo { + // Make a unique name for the extracted variable + const file = scope.getSourceFile(); + const localNameText = getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file); + const isJS = isInJSFile(scope); - const checker = context.program.getTypeChecker(); + let variableType = isJS || !checker.isContextSensitive(node) + ? undefined + : checker.typeToTypeNode(checker.getContextualType(node)!, scope, NodeBuilderFlags.NoTruncation); // TODO: GH#18217 - // Make a unique name for the extracted variable - const file = scope.getSourceFile(); - const localNameText = getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file); - const isJS = isInJSFile(scope); + let initializer = transformConstantInitializer(skipParentheses(node), substitutions); - let variableType = isJS || !checker.isContextSensitive(node) - ? undefined - : checker.typeToTypeNode(checker.getContextualType(node)!, scope, NodeBuilderFlags.NoTruncation); // TODO: GH#18217 + ({ variableType, initializer } = transformFunctionInitializerAndType(variableType, initializer)); - let initializer = transformConstantInitializer(skipParentheses(node), substitutions); + suppressLeadingAndTrailingTrivia(initializer); - ({ variableType, initializer } = transformFunctionInitializerAndType(variableType, initializer)); + const changeTracker = textChanges.ChangeTracker.fromContext(context); - suppressLeadingAndTrailingTrivia(initializer); + if (isClassLike(scope)) { + Debug.assert(!isJS, "Cannot extract to a JS class"); // See CannotExtractToJSClass + const modifiers: Modifier[] = []; + modifiers.push(factory.createModifier(SyntaxKind.PrivateKeyword)); + if (rangeFacts & RangeFacts.InStaticRegion) { + modifiers.push(factory.createModifier(SyntaxKind.StaticKeyword)); + } + modifiers.push(factory.createModifier(SyntaxKind.ReadonlyKeyword)); - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const newVariable = factory.createPropertyDeclaration( + /*decorators*/ undefined, + modifiers, + localNameText, + /*questionToken*/ undefined, + variableType, + initializer); - if (isClassLike(scope)) { - Debug.assert(!isJS, "Cannot extract to a JS class"); // See CannotExtractToJSClass - const modifiers: Modifier[] = []; - modifiers.push(factory.createModifier(SyntaxKind.PrivateKeyword)); - if (rangeFacts & RangeFacts.InStaticRegion) { - modifiers.push(factory.createModifier(SyntaxKind.StaticKeyword)); - } - modifiers.push(factory.createModifier(SyntaxKind.ReadonlyKeyword)); - - const newVariable = factory.createPropertyDeclaration( - /*decorators*/ undefined, - modifiers, - localNameText, - /*questionToken*/ undefined, - variableType, - initializer); - - let localReference: Expression = factory.createPropertyAccessExpression( - rangeFacts & RangeFacts.InStaticRegion - ? factory.createIdentifier(scope.name!.getText()) // TODO: GH#18217 - : factory.createThis(), - factory.createIdentifier(localNameText)); - - if (isInJSXContent(node)) { - localReference = factory.createJsxExpression(/*dotDotDotToken*/ undefined, localReference); - } + let localReference: Expression = factory.createPropertyAccessExpression( + rangeFacts & RangeFacts.InStaticRegion + ? factory.createIdentifier(scope.name!.getText()) // TODO: GH#18217 + : factory.createThis(), + factory.createIdentifier(localNameText)); + + if (isInJSXContent(node)) { + localReference = factory.createJsxExpression(/*dotDotDotToken*/ undefined, localReference); + } + // Declare + const maxInsertionPos = node.pos; + const nodeToInsertBefore = getNodeToInsertPropertyBefore(maxInsertionPos, scope); + changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, /*blankLineBetween*/ true); + + // Consume + changeTracker.replaceNode(context.file, node, localReference); + } + else { + const newVariableDeclaration = factory.createVariableDeclaration(localNameText, /*exclamationToken*/ undefined, variableType, initializer); + + // If the node is part of an initializer in a list of variable declarations, insert a new + // variable declaration into the list (in case it depends on earlier ones). + // CONSIDER: If the declaration list isn't const, we might want to split it into multiple + // lists so that the newly extracted one can be const. + const oldVariableDeclaration = getContainingVariableDeclarationIfInList(node, scope); + if (oldVariableDeclaration) { // Declare - const maxInsertionPos = node.pos; - const nodeToInsertBefore = getNodeToInsertPropertyBefore(maxInsertionPos, scope); - changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, /*blankLineBetween*/ true); + // CONSIDER: could detect that each is on a separate line (See `extractConstant_VariableList_MultipleLines` in `extractConstants.ts`) + changeTracker.insertNodeBefore(context.file, oldVariableDeclaration, newVariableDeclaration); // Consume + const localReference = factory.createIdentifier(localNameText); changeTracker.replaceNode(context.file, node, localReference); } + else if (node.parent.kind === SyntaxKind.ExpressionStatement && scope === findAncestor(node, isScope)) { + // If the parent is an expression statement and the target scope is the immediately enclosing one, + // replace the statement with the declaration. + const newVariableStatement = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const)); + changeTracker.replaceNode(context.file, node.parent, newVariableStatement); + } else { - const newVariableDeclaration = factory.createVariableDeclaration(localNameText, /*exclamationToken*/ undefined, variableType, initializer); - - // If the node is part of an initializer in a list of variable declarations, insert a new - // variable declaration into the list (in case it depends on earlier ones). - // CONSIDER: If the declaration list isn't const, we might want to split it into multiple - // lists so that the newly extracted one can be const. - const oldVariableDeclaration = getContainingVariableDeclarationIfInList(node, scope); - if (oldVariableDeclaration) { - // Declare - // CONSIDER: could detect that each is on a separate line (See `extractConstant_VariableList_MultipleLines` in `extractConstants.ts`) - changeTracker.insertNodeBefore(context.file, oldVariableDeclaration, newVariableDeclaration); - - // Consume - const localReference = factory.createIdentifier(localNameText); - changeTracker.replaceNode(context.file, node, localReference); - } - else if (node.parent.kind === SyntaxKind.ExpressionStatement && scope === findAncestor(node, isScope)) { - // If the parent is an expression statement and the target scope is the immediately enclosing one, - // replace the statement with the declaration. - const newVariableStatement = factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const)); - changeTracker.replaceNode(context.file, node.parent, newVariableStatement); + const newVariableStatement = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const)); + + // Declare + const nodeToInsertBefore = getNodeToInsertConstantBefore(node, scope); + if (nodeToInsertBefore.pos === 0) { + changeTracker.insertNodeAtTopOfFile(context.file, newVariableStatement, /*blankLineBetween*/ false); } else { - const newVariableStatement = factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const)); - - // Declare - const nodeToInsertBefore = getNodeToInsertConstantBefore(node, scope); - if (nodeToInsertBefore.pos === 0) { - changeTracker.insertNodeAtTopOfFile(context.file, newVariableStatement, /*blankLineBetween*/ false); - } - else { - changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariableStatement, /*blankLineBetween*/ false); - } + changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariableStatement, /*blankLineBetween*/ false); + } - // Consume - if (node.parent.kind === SyntaxKind.ExpressionStatement) { - // If the parent is an expression statement, delete it. - changeTracker.delete(context.file, node.parent); - } - else { - let localReference: Expression = factory.createIdentifier(localNameText); - // When extract to a new variable in JSX content, need to wrap a {} out of the new variable - // or it will become a plain text - if (isInJSXContent(node)) { - localReference = factory.createJsxExpression(/*dotDotDotToken*/ undefined, localReference); - } - changeTracker.replaceNode(context.file, node, localReference); + // Consume + if (node.parent.kind === SyntaxKind.ExpressionStatement) { + // If the parent is an expression statement, delete it. + changeTracker.delete(context.file, node.parent); + } + else { + let localReference: Expression = factory.createIdentifier(localNameText); + // When extract to a new variable in JSX content, need to wrap a {} out of the new variable + // or it will become a plain text + if (isInJSXContent(node)) { + localReference = factory.createJsxExpression(/*dotDotDotToken*/ undefined, localReference); } + changeTracker.replaceNode(context.file, node, localReference); } } + } - const edits = changeTracker.getChanges(); - - const renameFilename = node.getSourceFile().fileName; - const renameLocation = getRenameLocation(edits, renameFilename, localNameText, /*isDeclaredBeforeUse*/ true); - return { renameFilename, renameLocation, edits }; + const edits = changeTracker.getChanges(); - function transformFunctionInitializerAndType(variableType: TypeNode | undefined, initializer: Expression): { variableType: TypeNode | undefined, initializer: Expression } { - // If no contextual type exists there is nothing to transfer to the function signature - if (variableType === undefined) return { variableType, initializer }; - // Only do this for function expressions and arrow functions that are not generic - if (!isFunctionExpression(initializer) && !isArrowFunction(initializer) || !!initializer.typeParameters) return { variableType, initializer }; - const functionType = checker.getTypeAtLocation(node); - const functionSignature = singleOrUndefined(checker.getSignaturesOfType(functionType, SignatureKind.Call)); + const renameFilename = node.getSourceFile().fileName; + const renameLocation = getRenameLocation(edits, renameFilename, localNameText, /*isDeclaredBeforeUse*/ true); + return { renameFilename, renameLocation, edits }; - // If no function signature, maybe there was an error, do nothing - if (!functionSignature) return { variableType, initializer }; - // If the function signature has generic type parameters we don't attempt to move the parameters - if (!!functionSignature.getTypeParameters()) return { variableType, initializer }; + function transformFunctionInitializerAndType(variableType: TypeNode | undefined, initializer: Expression): { variableType: TypeNode | undefined, initializer: Expression } { + // If no contextual type exists there is nothing to transfer to the function signature + if (variableType === undefined) return { variableType, initializer }; + // Only do this for function expressions and arrow functions that are not generic + if (!isFunctionExpression(initializer) && !isArrowFunction(initializer) || !!initializer.typeParameters) return { variableType, initializer }; + const functionType = checker.getTypeAtLocation(node); + const functionSignature = singleOrUndefined(checker.getSignaturesOfType(functionType, SignatureKind.Call)); - // We add parameter types if needed - const parameters: ParameterDeclaration[] = []; - let hasAny = false; - for (const p of initializer.parameters) { - if (p.type) { - parameters.push(p); - } - else { - const paramType = checker.getTypeAtLocation(p); - if (paramType === checker.getAnyType()) hasAny = true; + // If no function signature, maybe there was an error, do nothing + if (!functionSignature) return { variableType, initializer }; + // If the function signature has generic type parameters we don't attempt to move the parameters + if (!!functionSignature.getTypeParameters()) return { variableType, initializer }; - parameters.push(factory.updateParameterDeclaration(p, - p.decorators, p.modifiers, p.dotDotDotToken, - p.name, p.questionToken, p.type || checker.typeToTypeNode(paramType, scope, NodeBuilderFlags.NoTruncation), p.initializer)); - } - } - // If a parameter was inferred as any we skip adding function parameters at all. - // Turning an implicit any (which under common settings is a error) to an explicit - // is probably actually a worse refactor outcome. - if (hasAny) return { variableType, initializer }; - variableType = undefined; - if (isArrowFunction(initializer)) { - initializer = factory.updateArrowFunction(initializer, node.modifiers, initializer.typeParameters, - parameters, - initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation), - initializer.equalsGreaterThanToken, - initializer.body); + // We add parameter types if needed + const parameters: ParameterDeclaration[] = []; + let hasAny = false; + for (const p of initializer.parameters) { + if (p.type) { + parameters.push(p); } else { - if (functionSignature && !!functionSignature.thisParameter) { - const firstParameter = firstOrUndefined(parameters); - // If the function signature has a this parameter and if the first defined parameter is not the this parameter, we must add it - // Note: If this parameter was already there, it would have been previously updated with the type if not type was present - if ((!firstParameter || (isIdentifier(firstParameter.name) && firstParameter.name.escapedText !== "this"))) { - const thisType = checker.getTypeOfSymbolAtLocation(functionSignature.thisParameter, node); - parameters.splice(0, 0, factory.createParameterDeclaration( - /* decorators */ undefined, - /* modifiers */ undefined, - /* dotDotDotToken */ undefined, - "this", - /* questionToken */ undefined, - checker.typeToTypeNode(thisType, scope, NodeBuilderFlags.NoTruncation) - )); - } + const paramType = checker.getTypeAtLocation(p); + if (paramType === checker.getAnyType()) hasAny = true; + + parameters.push(factory.updateParameterDeclaration(p, + p.decorators, p.modifiers, p.dotDotDotToken, + p.name, p.questionToken, p.type || checker.typeToTypeNode(paramType, scope, NodeBuilderFlags.NoTruncation), p.initializer)); + } + } + // If a parameter was inferred as any we skip adding function parameters at all. + // Turning an implicit any (which under common settings is a error) to an explicit + // is probably actually a worse refactor outcome. + if (hasAny) return { variableType, initializer }; + variableType = undefined; + if (isArrowFunction(initializer)) { + initializer = factory.updateArrowFunction(initializer, node.modifiers, initializer.typeParameters, + parameters, + initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation), + initializer.equalsGreaterThanToken, + initializer.body); + } + else { + if (functionSignature && !!functionSignature.thisParameter) { + const firstParameter = firstOrUndefined(parameters); + // If the function signature has a this parameter and if the first defined parameter is not the this parameter, we must add it + // Note: If this parameter was already there, it would have been previously updated with the type if not type was present + if ((!firstParameter || (isIdentifier(firstParameter.name) && firstParameter.name.escapedText !== "this"))) { + const thisType = checker.getTypeOfSymbolAtLocation(functionSignature.thisParameter, node); + parameters.splice(0, 0, factory.createParameterDeclaration( + /* decorators */ undefined, + /* modifiers */ undefined, + /* dotDotDotToken */ undefined, + "this", + /* questionToken */ undefined, + checker.typeToTypeNode(thisType, scope, NodeBuilderFlags.NoTruncation) + )); } - initializer = factory.updateFunctionExpression(initializer, node.modifiers, initializer.asteriskToken, - initializer.name, initializer.typeParameters, - parameters, - initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation), - initializer.body); } - return { variableType, initializer }; + initializer = factory.updateFunctionExpression(initializer, node.modifiers, initializer.asteriskToken, + initializer.name, initializer.typeParameters, + parameters, + initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation), + initializer.body); } + return { variableType, initializer }; } +} - function getContainingVariableDeclarationIfInList(node: Node, scope: Scope) { - let prevNode; - while (node !== undefined && node !== scope) { - if (isVariableDeclaration(node) && - node.initializer === prevNode && - isVariableDeclarationList(node.parent) && - node.parent.declarations.length > 1) { - - return node; - } +function getContainingVariableDeclarationIfInList(node: Node, scope: Scope) { + let prevNode; + while (node !== undefined && node !== scope) { + if (isVariableDeclaration(node) && + node.initializer === prevNode && + isVariableDeclarationList(node.parent) && + node.parent.declarations.length > 1) { - prevNode = node; - node = node.parent; + return node; } + + prevNode = node; + node = node.parent; } +} - function getFirstDeclaration(type: Type): Declaration | undefined { - let firstDeclaration; +function getFirstDeclaration(type: Type): Declaration | undefined { + let firstDeclaration; - const symbol = type.symbol; - if (symbol && symbol.declarations) { - for (const declaration of symbol.declarations) { - if (firstDeclaration === undefined || declaration.pos < firstDeclaration.pos) { - firstDeclaration = declaration; - } + const symbol = type.symbol; + if (symbol && symbol.declarations) { + for (const declaration of symbol.declarations) { + if (firstDeclaration === undefined || declaration.pos < firstDeclaration.pos) { + firstDeclaration = declaration; } } - - return firstDeclaration; } - function compareTypesByDeclarationOrder( - { type: type1, declaration: declaration1 }: { type: Type, declaration?: Declaration }, - { type: type2, declaration: declaration2 }: { type: Type, declaration?: Declaration }) { + return firstDeclaration; +} + +function compareTypesByDeclarationOrder( + { type: type1, declaration: declaration1 }: { type: Type, declaration?: Declaration }, + { type: type2, declaration: declaration2 }: { type: Type, declaration?: Declaration }) { - return compareProperties(declaration1, declaration2, "pos", compareValues) - || compareStringsCaseSensitive( - type1.symbol ? type1.symbol.getName() : "", - type2.symbol ? type2.symbol.getName() : "") - || compareValues(type1.id, type2.id); + return compareProperties(declaration1, declaration2, "pos", compareValues) + || compareStringsCaseSensitive( + type1.symbol ? type1.symbol.getName() : "", + type2.symbol ? type2.symbol.getName() : "") + || compareValues(type1.id, type2.id); +} + +function getCalledExpression(scope: Node, range: TargetRange, functionNameText: string): Expression { + const functionReference = factory.createIdentifier(functionNameText); + if (isClassLike(scope)) { + const lhs = range.facts & RangeFacts.InStaticRegion ? factory.createIdentifier(scope.name!.text) : factory.createThis(); // TODO: GH#18217 + return factory.createPropertyAccessExpression(lhs, functionReference); } + else { + return functionReference; + } +} - function getCalledExpression(scope: Node, range: TargetRange, functionNameText: string): Expression { - const functionReference = factory.createIdentifier(functionNameText); - if (isClassLike(scope)) { - const lhs = range.facts & RangeFacts.InStaticRegion ? factory.createIdentifier(scope.name!.text) : factory.createThis(); // TODO: GH#18217 - return factory.createPropertyAccessExpression(lhs, functionReference); - } - else { - return functionReference; - } - } - - function transformFunctionBody(body: Node, exposedVariableDeclarations: readonly VariableDeclaration[], writes: readonly UsageEntry[] | undefined, substitutions: ReadonlyESMap, hasReturn: boolean): { body: Block, returnValueProperty: string | undefined } { - const hasWritesOrVariableDeclarations = writes !== undefined || exposedVariableDeclarations.length > 0; - if (isBlock(body) && !hasWritesOrVariableDeclarations && substitutions.size === 0) { - // already block, no declarations or writes to propagate back, no substitutions - can use node as is - return { body: factory.createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined }; - } - let returnValueProperty: string | undefined; - let ignoreReturns = false; - const statements = factory.createNodeArray(isBlock(body) ? body.statements.slice(0) : [isStatement(body) ? body : factory.createReturnStatement(skipParentheses(body as Expression))]); - // rewrite body if either there are writes that should be propagated back via return statements or there are substitutions - if (hasWritesOrVariableDeclarations || substitutions.size) { - const rewrittenStatements = visitNodes(statements, visitor).slice(); - if (hasWritesOrVariableDeclarations && !hasReturn && isStatement(body)) { - // add return at the end to propagate writes back in case if control flow falls out of the function body - // it is ok to know that range has at least one return since it we only allow unconditional returns - const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes); - if (assignments.length === 1) { - rewrittenStatements.push(factory.createReturnStatement(assignments[0].name)); - } - else { - rewrittenStatements.push(factory.createReturnStatement(factory.createObjectLiteralExpression(assignments))); - } +function transformFunctionBody(body: Node, exposedVariableDeclarations: readonly VariableDeclaration[], writes: readonly UsageEntry[] | undefined, substitutions: ReadonlyESMap, hasReturn: boolean): { body: Block, returnValueProperty: string | undefined } { + const hasWritesOrVariableDeclarations = writes !== undefined || exposedVariableDeclarations.length > 0; + if (isBlock(body) && !hasWritesOrVariableDeclarations && substitutions.size === 0) { + // already block, no declarations or writes to propagate back, no substitutions - can use node as is + return { body: factory.createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined }; + } + let returnValueProperty: string | undefined; + let ignoreReturns = false; + const statements = factory.createNodeArray(isBlock(body) ? body.statements.slice(0) : [isStatement(body) ? body : factory.createReturnStatement(skipParentheses(body as Expression))]); + // rewrite body if either there are writes that should be propagated back via return statements or there are substitutions + if (hasWritesOrVariableDeclarations || substitutions.size) { + const rewrittenStatements = visitNodes(statements, visitor).slice(); + if (hasWritesOrVariableDeclarations && !hasReturn && isStatement(body)) { + // add return at the end to propagate writes back in case if control flow falls out of the function body + // it is ok to know that range has at least one return since it we only allow unconditional returns + const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes); + if (assignments.length === 1) { + rewrittenStatements.push(factory.createReturnStatement(assignments[0].name)); + } + else { + rewrittenStatements.push(factory.createReturnStatement(factory.createObjectLiteralExpression(assignments))); } - return { body: factory.createBlock(rewrittenStatements, /*multiLine*/ true), returnValueProperty }; - } - else { - return { body: factory.createBlock(statements, /*multiLine*/ true), returnValueProperty: undefined }; } + return { body: factory.createBlock(rewrittenStatements, /*multiLine*/ true), returnValueProperty }; + } + else { + return { body: factory.createBlock(statements, /*multiLine*/ true), returnValueProperty: undefined }; + } - function visitor(node: Node): VisitResult { - if (!ignoreReturns && isReturnStatement(node) && hasWritesOrVariableDeclarations) { - const assignments: ObjectLiteralElementLike[] = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes); - if (node.expression) { - if (!returnValueProperty) { - returnValueProperty = "__return"; - } - assignments.unshift(factory.createPropertyAssignment(returnValueProperty, visitNode(node.expression, visitor))); - } - if (assignments.length === 1) { - return factory.createReturnStatement(assignments[0].name as Expression); - } - else { - return factory.createReturnStatement(factory.createObjectLiteralExpression(assignments)); + function visitor(node: Node): VisitResult { + if (!ignoreReturns && isReturnStatement(node) && hasWritesOrVariableDeclarations) { + const assignments: ObjectLiteralElementLike[] = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes); + if (node.expression) { + if (!returnValueProperty) { + returnValueProperty = "__return"; } + assignments.unshift(factory.createPropertyAssignment(returnValueProperty, visitNode(node.expression, visitor))); + } + if (assignments.length === 1) { + return factory.createReturnStatement(assignments[0].name as Expression); } else { - const oldIgnoreReturns = ignoreReturns; - ignoreReturns = ignoreReturns || isFunctionLikeDeclaration(node) || isClassLike(node); - const substitution = substitutions.get(getNodeId(node).toString()); - const result = substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext); - ignoreReturns = oldIgnoreReturns; - return result; + return factory.createReturnStatement(factory.createObjectLiteralExpression(assignments)); } } - } - - function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyESMap): Expression { - return substitutions.size - ? visitor(initializer) as Expression - : initializer; - - function visitor(node: Node): VisitResult { + else { + const oldIgnoreReturns = ignoreReturns; + ignoreReturns = ignoreReturns || isFunctionLikeDeclaration(node) || isClassLike(node); const substitution = substitutions.get(getNodeId(node).toString()); - return substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext); + const result = substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext); + ignoreReturns = oldIgnoreReturns; + return result; } } +} - function getStatementsOrClassElements(scope: Scope): readonly Statement[] | readonly ClassElement[] { - if (isFunctionLikeDeclaration(scope)) { - const body = scope.body!; // TODO: GH#18217 - if (isBlock(body)) { - return body.statements; - } - } - else if (isModuleBlock(scope) || isSourceFile(scope)) { - return scope.statements; - } - else if (isClassLike(scope)) { - return scope.members; - } - else { - assertType(scope); - } +function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyESMap): Expression { + return substitutions.size + ? visitor(initializer) as Expression + : initializer; - return emptyArray; + function visitor(node: Node): VisitResult { + const substitution = substitutions.get(getNodeId(node).toString()); + return substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext); } +} - /** - * If `scope` contains a function after `minPos`, then return the first such function. - * Otherwise, return `undefined`. - */ - function getNodeToInsertFunctionBefore(minPos: number, scope: Scope): Statement | ClassElement | undefined { - return find(getStatementsOrClassElements(scope), child => - child.pos >= minPos && isFunctionLikeDeclaration(child) && !isConstructorDeclaration(child)); +function getStatementsOrClassElements(scope: Scope): readonly Statement[] | readonly ClassElement[] { + if (isFunctionLikeDeclaration(scope)) { + const body = scope.body!; // TODO: GH#18217 + if (isBlock(body)) { + return body.statements; + } + } + else if (isModuleBlock(scope) || isSourceFile(scope)) { + return scope.statements; + } + else if (isClassLike(scope)) { + return scope.members; + } + else { + assertType(scope); } - function getNodeToInsertPropertyBefore(maxPos: number, scope: ClassLikeDeclaration): ClassElement { - const members = scope.members; - Debug.assert(members.length > 0, "Found no members"); // There must be at least one child, since we extracted from one. + return emptyArray; +} - let prevMember: ClassElement | undefined; - let allProperties = true; - for (const member of members) { - if (member.pos > maxPos) { - return prevMember || members[0]; - } - if (allProperties && !isPropertyDeclaration(member)) { - // If it is non-vacuously true that all preceding members are properties, - // insert before the current member (i.e. at the end of the list of properties). - if (prevMember !== undefined) { - return member; - } +/** + * If `scope` contains a function after `minPos`, then return the first such function. + * Otherwise, return `undefined`. + */ +function getNodeToInsertFunctionBefore(minPos: number, scope: Scope): Statement | ClassElement | undefined { + return find(getStatementsOrClassElements(scope), child => + child.pos >= minPos && isFunctionLikeDeclaration(child) && !isConstructorDeclaration(child)); +} - allProperties = false; - } - prevMember = member; +function getNodeToInsertPropertyBefore(maxPos: number, scope: ClassLikeDeclaration): ClassElement { + const members = scope.members; + Debug.assert(members.length > 0, "Found no members"); // There must be at least one child, since we extracted from one. + + let prevMember: ClassElement | undefined; + let allProperties = true; + for (const member of members) { + if (member.pos > maxPos) { + return prevMember || members[0]; } + if (allProperties && !isPropertyDeclaration(member)) { + // If it is non-vacuously true that all preceding members are properties, + // insert before the current member (i.e. at the end of the list of properties). + if (prevMember !== undefined) { + return member; + } - if (prevMember === undefined) return Debug.fail(); // If the loop didn't return, then it did set prevMember. - return prevMember; + allProperties = false; + } + prevMember = member; } - function getNodeToInsertConstantBefore(node: Node, scope: Scope): Statement { - Debug.assert(!isClassLike(scope)); + if (prevMember === undefined) return Debug.fail(); // If the loop didn't return, then it did set prevMember. + return prevMember; +} - let prevScope: Scope | undefined; - for (let curr = node; curr !== scope; curr = curr.parent) { - if (isScope(curr)) { - prevScope = curr; - } - } +function getNodeToInsertConstantBefore(node: Node, scope: Scope): Statement { + Debug.assert(!isClassLike(scope)); - for (let curr = (prevScope || node).parent; ; curr = curr.parent) { - if (isBlockLike(curr)) { - let prevStatement: Statement | undefined; - for (const statement of curr.statements) { - if (statement.pos > node.pos) { - break; - } - prevStatement = statement; - } + let prevScope: Scope | undefined; + for (let curr = node; curr !== scope; curr = curr.parent) { + if (isScope(curr)) { + prevScope = curr; + } + } - if (!prevStatement && isCaseClause(curr)) { - // We must have been in the expression of the case clause. - Debug.assert(isSwitchStatement(curr.parent.parent), "Grandparent isn't a switch statement"); - return curr.parent.parent; + for (let curr = (prevScope || node).parent; ; curr = curr.parent) { + if (isBlockLike(curr)) { + let prevStatement: Statement | undefined; + for (const statement of curr.statements) { + if (statement.pos > node.pos) { + break; } + prevStatement = statement; + } - // There must be at least one statement since we started in one. - return Debug.checkDefined(prevStatement, "prevStatement failed to get set"); + if (!prevStatement && isCaseClause(curr)) { + // We must have been in the expression of the case clause. + Debug.assert(isSwitchStatement(curr.parent.parent), "Grandparent isn't a switch statement"); + return curr.parent.parent; } - Debug.assert(curr !== scope, "Didn't encounter a block-like before encountering scope"); + // There must be at least one statement since we started in one. + return Debug.checkDefined(prevStatement, "prevStatement failed to get set"); } + + Debug.assert(curr !== scope, "Didn't encounter a block-like before encountering scope"); } +} - function getPropertyAssignmentsForWritesAndVariableDeclarations( - exposedVariableDeclarations: readonly VariableDeclaration[], - writes: readonly UsageEntry[] | undefined - ): ShorthandPropertyAssignment[] { - const variableAssignments = map(exposedVariableDeclarations, v => factory.createShorthandPropertyAssignment(v.symbol.name)); - const writeAssignments = map(writes, w => factory.createShorthandPropertyAssignment(w.symbol.name)); +function getPropertyAssignmentsForWritesAndVariableDeclarations( + exposedVariableDeclarations: readonly VariableDeclaration[], + writes: readonly UsageEntry[] | undefined +): ShorthandPropertyAssignment[] { + const variableAssignments = map(exposedVariableDeclarations, v => factory.createShorthandPropertyAssignment(v.symbol.name)); + const writeAssignments = map(writes, w => factory.createShorthandPropertyAssignment(w.symbol.name)); + + // TODO: GH#18217 `variableAssignments` not possibly undefined! + return variableAssignments === undefined + ? writeAssignments! + : writeAssignments === undefined + ? variableAssignments + : variableAssignments.concat(writeAssignments); +} - // TODO: GH#18217 `variableAssignments` not possibly undefined! - return variableAssignments === undefined - ? writeAssignments! - : writeAssignments === undefined - ? variableAssignments - : variableAssignments.concat(writeAssignments); - } +function isReadonlyArray(v: any): v is readonly any[] { + return isArray(v); +} + +/** + * Produces a range that spans the entirety of nodes, given a selection + * that might start/end in the middle of nodes. + * + * For example, when the user makes a selection like this + * v---v + * var someThing = foo + bar; + * this returns ^-------^ + */ +function getEnclosingTextRange(targetRange: TargetRange, sourceFile: SourceFile): TextRange { + return isReadonlyArray(targetRange.range) + ? { pos: first(targetRange.range).getStart(sourceFile), end: last(targetRange.range).getEnd() } + : targetRange.range; +} + +const enum Usage { + // value should be passed to extracted method + Read = 1, + // value should be passed to extracted method and propagated back + Write = 2 +} + +interface UsageEntry { + readonly usage: Usage; + readonly symbol: Symbol; + readonly node: Node; +} + +interface ScopeUsages { + readonly usages: ESMap; + readonly typeParameterUsages: ESMap; // Key is type ID + readonly substitutions: ESMap; +} + +interface ReadsAndWrites { + readonly target: Expression | Block; + readonly usagesPerScope: readonly ScopeUsages[]; + readonly functionErrorsPerScope: readonly (readonly Diagnostic[])[]; + readonly constantErrorsPerScope: readonly (readonly Diagnostic[])[]; + readonly exposedVariableDeclarations: readonly VariableDeclaration[]; +} +function collectReadsAndWrites( + targetRange: TargetRange, + scopes: Scope[], + enclosingTextRange: TextRange, + sourceFile: SourceFile, + checker: TypeChecker, + cancellationToken: CancellationToken): ReadsAndWrites { + + const allTypeParameterUsages = new Map(); // Key is type ID + const usagesPerScope: ScopeUsages[] = []; + const substitutionsPerScope: ESMap[] = []; + const functionErrorsPerScope: Diagnostic[][] = []; + const constantErrorsPerScope: Diagnostic[][] = []; + const visibleDeclarationsInExtractedRange: NamedDeclaration[] = []; + const exposedVariableSymbolSet = new Map(); // Key is symbol ID + const exposedVariableDeclarations: VariableDeclaration[] = []; + let firstExposedNonVariableDeclaration: NamedDeclaration | undefined; + + const expression = !isReadonlyArray(targetRange.range) + ? targetRange.range + : targetRange.range.length === 1 && isExpressionStatement(targetRange.range[0]) + ? targetRange.range[0].expression + : undefined; - function isReadonlyArray(v: any): v is readonly any[] { - return isArray(v); + let expressionDiagnostic: Diagnostic | undefined; + if (expression === undefined) { + const statements = targetRange.range as readonly Statement[]; + const start = first(statements).getStart(); + const end = last(statements).end; + expressionDiagnostic = createFileDiagnostic(sourceFile, start, end - start, Messages.expressionExpected); + } + else if (checker.getTypeAtLocation(expression).flags & (TypeFlags.Void | TypeFlags.Never)) { + expressionDiagnostic = createDiagnosticForNode(expression, Messages.uselessConstantType); } - /** - * Produces a range that spans the entirety of nodes, given a selection - * that might start/end in the middle of nodes. - * - * For example, when the user makes a selection like this - * v---v - * var someThing = foo + bar; - * this returns ^-------^ - */ - function getEnclosingTextRange(targetRange: TargetRange, sourceFile: SourceFile): TextRange { - return isReadonlyArray(targetRange.range) - ? { pos: first(targetRange.range).getStart(sourceFile), end: last(targetRange.range).getEnd() } - : targetRange.range; - } - - const enum Usage { - // value should be passed to extracted method - Read = 1, - // value should be passed to extracted method and propagated back - Write = 2 - } - - interface UsageEntry { - readonly usage: Usage; - readonly symbol: Symbol; - readonly node: Node; - } - - interface ScopeUsages { - readonly usages: ESMap; - readonly typeParameterUsages: ESMap; // Key is type ID - readonly substitutions: ESMap; - } - - interface ReadsAndWrites { - readonly target: Expression | Block; - readonly usagesPerScope: readonly ScopeUsages[]; - readonly functionErrorsPerScope: readonly (readonly Diagnostic[])[]; - readonly constantErrorsPerScope: readonly (readonly Diagnostic[])[]; - readonly exposedVariableDeclarations: readonly VariableDeclaration[]; - } - function collectReadsAndWrites( - targetRange: TargetRange, - scopes: Scope[], - enclosingTextRange: TextRange, - sourceFile: SourceFile, - checker: TypeChecker, - cancellationToken: CancellationToken): ReadsAndWrites { - - const allTypeParameterUsages = new Map(); // Key is type ID - const usagesPerScope: ScopeUsages[] = []; - const substitutionsPerScope: ESMap[] = []; - const functionErrorsPerScope: Diagnostic[][] = []; - const constantErrorsPerScope: Diagnostic[][] = []; - const visibleDeclarationsInExtractedRange: NamedDeclaration[] = []; - const exposedVariableSymbolSet = new Map(); // Key is symbol ID - const exposedVariableDeclarations: VariableDeclaration[] = []; - let firstExposedNonVariableDeclaration: NamedDeclaration | undefined; - - const expression = !isReadonlyArray(targetRange.range) - ? targetRange.range - : targetRange.range.length === 1 && isExpressionStatement(targetRange.range[0]) - ? targetRange.range[0].expression - : undefined; - - let expressionDiagnostic: Diagnostic | undefined; - if (expression === undefined) { - const statements = targetRange.range as readonly Statement[]; - const start = first(statements).getStart(); - const end = last(statements).end; - expressionDiagnostic = createFileDiagnostic(sourceFile, start, end - start, Messages.expressionExpected); - } - else if (checker.getTypeAtLocation(expression).flags & (TypeFlags.Void | TypeFlags.Never)) { - expressionDiagnostic = createDiagnosticForNode(expression, Messages.uselessConstantType); - } - - // initialize results - for (const scope of scopes) { - usagesPerScope.push({ usages: new Map(), typeParameterUsages: new Map(), substitutions: new Map() }); - substitutionsPerScope.push(new Map()); - - functionErrorsPerScope.push([]); - - const constantErrors = []; - if (expressionDiagnostic) { - constantErrors.push(expressionDiagnostic); - } - if (isClassLike(scope) && isInJSFile(scope)) { - constantErrors.push(createDiagnosticForNode(scope, Messages.cannotExtractToJSClass)); - } - if (isArrowFunction(scope) && !isBlock(scope.body)) { - // TODO (https://github.com/Microsoft/TypeScript/issues/18924): allow this - constantErrors.push(createDiagnosticForNode(scope, Messages.cannotExtractToExpressionArrowFunction)); - } - constantErrorsPerScope.push(constantErrors); + // initialize results + for (const scope of scopes) { + usagesPerScope.push({ usages: new Map(), typeParameterUsages: new Map(), substitutions: new Map() }); + substitutionsPerScope.push(new Map()); + + functionErrorsPerScope.push([]); + + const constantErrors = []; + if (expressionDiagnostic) { + constantErrors.push(expressionDiagnostic); } + if (isClassLike(scope) && isInJSFile(scope)) { + constantErrors.push(createDiagnosticForNode(scope, Messages.cannotExtractToJSClass)); + } + if (isArrowFunction(scope) && !isBlock(scope.body)) { + // TODO (https://github.com/Microsoft/TypeScript/issues/18924): allow this + constantErrors.push(createDiagnosticForNode(scope, Messages.cannotExtractToExpressionArrowFunction)); + } + constantErrorsPerScope.push(constantErrors); + } - const seenUsages = new Map(); - const target = isReadonlyArray(targetRange.range) ? factory.createBlock(targetRange.range) : targetRange.range; + const seenUsages = new Map(); + const target = isReadonlyArray(targetRange.range) ? factory.createBlock(targetRange.range) : targetRange.range; - const unmodifiedNode = isReadonlyArray(targetRange.range) ? first(targetRange.range) : targetRange.range; - const inGenericContext = isInGenericContext(unmodifiedNode); + const unmodifiedNode = isReadonlyArray(targetRange.range) ? first(targetRange.range) : targetRange.range; + const inGenericContext = isInGenericContext(unmodifiedNode); - collectUsages(target); + collectUsages(target); - // Unfortunately, this code takes advantage of the knowledge that the generated method - // will use the contextual type of an expression as the return type of the extracted - // method (and will therefore "use" all the types involved). - if (inGenericContext && !isReadonlyArray(targetRange.range)) { - const contextualType = checker.getContextualType(targetRange.range)!; // TODO: GH#18217 - recordTypeParameterUsages(contextualType); - } + // Unfortunately, this code takes advantage of the knowledge that the generated method + // will use the contextual type of an expression as the return type of the extracted + // method (and will therefore "use" all the types involved). + if (inGenericContext && !isReadonlyArray(targetRange.range)) { + const contextualType = checker.getContextualType(targetRange.range)!; // TODO: GH#18217 + recordTypeParameterUsages(contextualType); + } - if (allTypeParameterUsages.size > 0) { - const seenTypeParameterUsages = new Map(); // Key is type ID + if (allTypeParameterUsages.size > 0) { + const seenTypeParameterUsages = new Map(); // Key is type ID - let i = 0; - for (let curr: Node = unmodifiedNode; curr !== undefined && i < scopes.length; curr = curr.parent) { - if (curr === scopes[i]) { - // Copy current contents of seenTypeParameterUsages into scope. - seenTypeParameterUsages.forEach((typeParameter, id) => { - usagesPerScope[i].typeParameterUsages.set(id, typeParameter); - }); + let i = 0; + for (let curr: Node = unmodifiedNode; curr !== undefined && i < scopes.length; curr = curr.parent) { + if (curr === scopes[i]) { + // Copy current contents of seenTypeParameterUsages into scope. + seenTypeParameterUsages.forEach((typeParameter, id) => { + usagesPerScope[i].typeParameterUsages.set(id, typeParameter); + }); - i++; - } + i++; + } - // Note that we add the current node's type parameters *after* updating the corresponding scope. - if (isDeclarationWithTypeParameters(curr)) { - for (const typeParameterDecl of getEffectiveTypeParameterDeclarations(curr)) { - const typeParameter = checker.getTypeAtLocation(typeParameterDecl) as TypeParameter; - if (allTypeParameterUsages.has(typeParameter.id.toString())) { - seenTypeParameterUsages.set(typeParameter.id.toString(), typeParameter); - } + // Note that we add the current node's type parameters *after* updating the corresponding scope. + if (isDeclarationWithTypeParameters(curr)) { + for (const typeParameterDecl of getEffectiveTypeParameterDeclarations(curr)) { + const typeParameter = checker.getTypeAtLocation(typeParameterDecl) as TypeParameter; + if (allTypeParameterUsages.has(typeParameter.id.toString())) { + seenTypeParameterUsages.set(typeParameter.id.toString(), typeParameter); } } } - - // If we didn't get through all the scopes, then there were some that weren't in our - // parent chain (impossible at time of writing). A conservative solution would be to - // copy allTypeParameterUsages into all remaining scopes. - Debug.assert(i === scopes.length, "Should have iterated all scopes"); } - // If there are any declarations in the extracted block that are used in the same enclosing - // lexical scope, we can't move the extraction "up" as those declarations will become unreachable - if (visibleDeclarationsInExtractedRange.length) { - const containingLexicalScopeOfExtraction = isBlockScope(scopes[0], scopes[0].parent) - ? scopes[0] - : getEnclosingBlockScopeContainer(scopes[0]); - forEachChild(containingLexicalScopeOfExtraction, checkForUsedDeclarations); - } + // If we didn't get through all the scopes, then there were some that weren't in our + // parent chain (impossible at time of writing). A conservative solution would be to + // copy allTypeParameterUsages into all remaining scopes. + Debug.assert(i === scopes.length, "Should have iterated all scopes"); + } - for (let i = 0; i < scopes.length; i++) { - const scopeUsages = usagesPerScope[i]; - // Special case: in the innermost scope, all usages are available. - // (The computed value reflects the value at the top-level of the scope, but the - // local will actually be declared at the same level as the extracted expression). - if (i > 0 && (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0)) { - const errorNode = isReadonlyArray(targetRange.range) ? targetRange.range[0] : targetRange.range; - constantErrorsPerScope[i].push(createDiagnosticForNode(errorNode, Messages.cannotAccessVariablesFromNestedScopes)); - } + // If there are any declarations in the extracted block that are used in the same enclosing + // lexical scope, we can't move the extraction "up" as those declarations will become unreachable + if (visibleDeclarationsInExtractedRange.length) { + const containingLexicalScopeOfExtraction = isBlockScope(scopes[0], scopes[0].parent) + ? scopes[0] + : getEnclosingBlockScopeContainer(scopes[0]); + forEachChild(containingLexicalScopeOfExtraction, checkForUsedDeclarations); + } - let hasWrite = false; - let readonlyClassPropertyWrite: Declaration | undefined; - usagesPerScope[i].usages.forEach(value => { - if (value.usage === Usage.Write) { - hasWrite = true; - if (value.symbol.flags & SymbolFlags.ClassMember && - value.symbol.valueDeclaration && - hasEffectiveModifier(value.symbol.valueDeclaration, ModifierFlags.Readonly)) { - readonlyClassPropertyWrite = value.symbol.valueDeclaration; - } + for (let i = 0; i < scopes.length; i++) { + const scopeUsages = usagesPerScope[i]; + // Special case: in the innermost scope, all usages are available. + // (The computed value reflects the value at the top-level of the scope, but the + // local will actually be declared at the same level as the extracted expression). + if (i > 0 && (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0)) { + const errorNode = isReadonlyArray(targetRange.range) ? targetRange.range[0] : targetRange.range; + constantErrorsPerScope[i].push(createDiagnosticForNode(errorNode, Messages.cannotAccessVariablesFromNestedScopes)); + } + + let hasWrite = false; + let readonlyClassPropertyWrite: Declaration | undefined; + usagesPerScope[i].usages.forEach(value => { + if (value.usage === Usage.Write) { + hasWrite = true; + if (value.symbol.flags & SymbolFlags.ClassMember && + value.symbol.valueDeclaration && + hasEffectiveModifier(value.symbol.valueDeclaration, ModifierFlags.Readonly)) { + readonlyClassPropertyWrite = value.symbol.valueDeclaration; } - }); + } + }); - // If an expression was extracted, then there shouldn't have been any variable declarations. - Debug.assert(isReadonlyArray(targetRange.range) || exposedVariableDeclarations.length === 0, "No variable declarations expected if something was extracted"); + // If an expression was extracted, then there shouldn't have been any variable declarations. + Debug.assert(isReadonlyArray(targetRange.range) || exposedVariableDeclarations.length === 0, "No variable declarations expected if something was extracted"); - if (hasWrite && !isReadonlyArray(targetRange.range)) { - const diag = createDiagnosticForNode(targetRange.range, Messages.cannotWriteInExpression); - functionErrorsPerScope[i].push(diag); - constantErrorsPerScope[i].push(diag); - } - else if (readonlyClassPropertyWrite && i > 0) { - const diag = createDiagnosticForNode(readonlyClassPropertyWrite, Messages.cannotExtractReadonlyPropertyInitializerOutsideConstructor); - functionErrorsPerScope[i].push(diag); - constantErrorsPerScope[i].push(diag); - } - else if (firstExposedNonVariableDeclaration) { - const diag = createDiagnosticForNode(firstExposedNonVariableDeclaration, Messages.cannotExtractExportedEntity); - functionErrorsPerScope[i].push(diag); - constantErrorsPerScope[i].push(diag); - } + if (hasWrite && !isReadonlyArray(targetRange.range)) { + const diag = createDiagnosticForNode(targetRange.range, Messages.cannotWriteInExpression); + functionErrorsPerScope[i].push(diag); + constantErrorsPerScope[i].push(diag); } + else if (readonlyClassPropertyWrite && i > 0) { + const diag = createDiagnosticForNode(readonlyClassPropertyWrite, Messages.cannotExtractReadonlyPropertyInitializerOutsideConstructor); + functionErrorsPerScope[i].push(diag); + constantErrorsPerScope[i].push(diag); + } + else if (firstExposedNonVariableDeclaration) { + const diag = createDiagnosticForNode(firstExposedNonVariableDeclaration, Messages.cannotExtractExportedEntity); + functionErrorsPerScope[i].push(diag); + constantErrorsPerScope[i].push(diag); + } + } - return { target, usagesPerScope, functionErrorsPerScope, constantErrorsPerScope, exposedVariableDeclarations }; + return { target, usagesPerScope, functionErrorsPerScope, constantErrorsPerScope, exposedVariableDeclarations }; - function isInGenericContext(node: Node) { - return !!findAncestor(node, n => isDeclarationWithTypeParameters(n) && getEffectiveTypeParameterDeclarations(n).length !== 0); - } + function isInGenericContext(node: Node) { + return !!findAncestor(node, n => isDeclarationWithTypeParameters(n) && getEffectiveTypeParameterDeclarations(n).length !== 0); + } - function recordTypeParameterUsages(type: Type) { - // PERF: This is potentially very expensive. `type` could be a library type with - // a lot of properties, each of which the walker will visit. Unfortunately, the - // solution isn't as trivial as filtering to user types because of (e.g.) Array. - const symbolWalker = checker.getSymbolWalker(() => (cancellationToken.throwIfCancellationRequested(), true)); - const { visitedTypes } = symbolWalker.walkType(type); + function recordTypeParameterUsages(type: Type) { + // PERF: This is potentially very expensive. `type` could be a library type with + // a lot of properties, each of which the walker will visit. Unfortunately, the + // solution isn't as trivial as filtering to user types because of (e.g.) Array. + const symbolWalker = checker.getSymbolWalker(() => (cancellationToken.throwIfCancellationRequested(), true)); + const { visitedTypes } = symbolWalker.walkType(type); - for (const visitedType of visitedTypes) { - if (visitedType.isTypeParameter()) { - allTypeParameterUsages.set(visitedType.id.toString(), visitedType); - } + for (const visitedType of visitedTypes) { + if (visitedType.isTypeParameter()) { + allTypeParameterUsages.set(visitedType.id.toString(), visitedType); } } + } - function collectUsages(node: Node, valueUsage = Usage.Read) { - if (inGenericContext) { - const type = checker.getTypeAtLocation(node); - recordTypeParameterUsages(type); - } + function collectUsages(node: Node, valueUsage = Usage.Read) { + if (inGenericContext) { + const type = checker.getTypeAtLocation(node); + recordTypeParameterUsages(type); + } - if (isDeclaration(node) && node.symbol) { - visibleDeclarationsInExtractedRange.push(node); - } + if (isDeclaration(node) && node.symbol) { + visibleDeclarationsInExtractedRange.push(node); + } - if (isAssignmentExpression(node)) { - // use 'write' as default usage for values - collectUsages(node.left, Usage.Write); - collectUsages(node.right); - } - else if (isUnaryExpressionWithWrite(node)) { - collectUsages(node.operand, Usage.Write); - } - else if (isPropertyAccessExpression(node) || isElementAccessExpression(node)) { - // use 'write' as default usage for values - forEachChild(node, collectUsages); + if (isAssignmentExpression(node)) { + // use 'write' as default usage for values + collectUsages(node.left, Usage.Write); + collectUsages(node.right); + } + else if (isUnaryExpressionWithWrite(node)) { + collectUsages(node.operand, Usage.Write); + } + else if (isPropertyAccessExpression(node) || isElementAccessExpression(node)) { + // use 'write' as default usage for values + forEachChild(node, collectUsages); + } + else if (isIdentifier(node)) { + if (!node.parent) { + return; } - else if (isIdentifier(node)) { - if (!node.parent) { - return; - } - if (isQualifiedName(node.parent) && node !== node.parent.left) { - return; - } - if (isPropertyAccessExpression(node.parent) && node !== node.parent.expression) { - return; - } - recordUsage(node, valueUsage, /*isTypeNode*/ isPartOfTypeNode(node)); + if (isQualifiedName(node.parent) && node !== node.parent.left) { + return; } - else { - forEachChild(node, collectUsages); + if (isPropertyAccessExpression(node.parent) && node !== node.parent.expression) { + return; } + recordUsage(node, valueUsage, /*isTypeNode*/ isPartOfTypeNode(node)); + } + else { + forEachChild(node, collectUsages); } + } - function recordUsage(n: Identifier, usage: Usage, isTypeNode: boolean) { - const symbolId = recordUsagebySymbol(n, usage, isTypeNode); - if (symbolId) { - for (let i = 0; i < scopes.length; i++) { - // push substitution from map to map to simplify rewriting - const substitution = substitutionsPerScope[i].get(symbolId); - if (substitution) { - usagesPerScope[i].substitutions.set(getNodeId(n).toString(), substitution); - } + function recordUsage(n: Identifier, usage: Usage, isTypeNode: boolean) { + const symbolId = recordUsagebySymbol(n, usage, isTypeNode); + if (symbolId) { + for (let i = 0; i < scopes.length; i++) { + // push substitution from map to map to simplify rewriting + const substitution = substitutionsPerScope[i].get(symbolId); + if (substitution) { + usagesPerScope[i].substitutions.set(getNodeId(n).toString(), substitution); } } } + } - function recordUsagebySymbol(identifier: Identifier, usage: Usage, isTypeName: boolean) { - const symbol = getSymbolReferencedByIdentifier(identifier); - if (!symbol) { - // cannot find symbol - do nothing - return undefined; - } - const symbolId = getSymbolId(symbol).toString(); - const lastUsage = seenUsages.get(symbolId); - // there are two kinds of value usages - // - reads - if range contains a read from the value located outside of the range then value should be passed as a parameter - // - writes - if range contains a write to a value located outside the range the value should be passed as a parameter and - // returned as a return value - // 'write' case is a superset of 'read' so if we already have processed 'write' of some symbol there is not need to handle 'read' - // since all information is already recorded - if (lastUsage && lastUsage >= usage) { - return symbolId; - } + function recordUsagebySymbol(identifier: Identifier, usage: Usage, isTypeName: boolean) { + const symbol = getSymbolReferencedByIdentifier(identifier); + if (!symbol) { + // cannot find symbol - do nothing + return undefined; + } + const symbolId = getSymbolId(symbol).toString(); + const lastUsage = seenUsages.get(symbolId); + // there are two kinds of value usages + // - reads - if range contains a read from the value located outside of the range then value should be passed as a parameter + // - writes - if range contains a write to a value located outside the range the value should be passed as a parameter and + // returned as a return value + // 'write' case is a superset of 'read' so if we already have processed 'write' of some symbol there is not need to handle 'read' + // since all information is already recorded + if (lastUsage && lastUsage >= usage) { + return symbolId; + } - seenUsages.set(symbolId, usage); - if (lastUsage) { - // if we get here this means that we are trying to handle 'write' and 'read' was already processed - // walk scopes and update existing records. - for (const perScope of usagesPerScope) { - const prevEntry = perScope.usages.get(identifier.text); - if (prevEntry) { - perScope.usages.set(identifier.text, { usage, symbol, node: identifier }); - } + seenUsages.set(symbolId, usage); + if (lastUsage) { + // if we get here this means that we are trying to handle 'write' and 'read' was already processed + // walk scopes and update existing records. + for (const perScope of usagesPerScope) { + const prevEntry = perScope.usages.get(identifier.text); + if (prevEntry) { + perScope.usages.set(identifier.text, { usage, symbol, node: identifier }); } - return symbolId; } - // find first declaration in this file - const decls = symbol.getDeclarations(); - const declInFile = decls && find(decls, d => d.getSourceFile() === sourceFile); - if (!declInFile) { - return undefined; - } - if (rangeContainsStartEnd(enclosingTextRange, declInFile.getStart(), declInFile.end)) { - // declaration is located in range to be extracted - do nothing - return undefined; + return symbolId; + } + // find first declaration in this file + const decls = symbol.getDeclarations(); + const declInFile = decls && find(decls, d => d.getSourceFile() === sourceFile); + if (!declInFile) { + return undefined; + } + if (rangeContainsStartEnd(enclosingTextRange, declInFile.getStart(), declInFile.end)) { + // declaration is located in range to be extracted - do nothing + return undefined; + } + if (targetRange.facts & RangeFacts.IsGenerator && usage === Usage.Write) { + // this is write to a reference located outside of the target scope and range is extracted into generator + // currently this is unsupported scenario + const diag = createDiagnosticForNode(identifier, Messages.cannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators); + for (const errors of functionErrorsPerScope) { + errors.push(diag); } - if (targetRange.facts & RangeFacts.IsGenerator && usage === Usage.Write) { - // this is write to a reference located outside of the target scope and range is extracted into generator - // currently this is unsupported scenario - const diag = createDiagnosticForNode(identifier, Messages.cannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators); - for (const errors of functionErrorsPerScope) { - errors.push(diag); - } - for (const errors of constantErrorsPerScope) { - errors.push(diag); - } + for (const errors of constantErrorsPerScope) { + errors.push(diag); } - for (let i = 0; i < scopes.length; i++) { - const scope = scopes[i]; - const resolvedSymbol = checker.resolveName(symbol.name, scope, symbol.flags, /*excludeGlobals*/ false); - if (resolvedSymbol === symbol) { - continue; + } + for (let i = 0; i < scopes.length; i++) { + const scope = scopes[i]; + const resolvedSymbol = checker.resolveName(symbol.name, scope, symbol.flags, /*excludeGlobals*/ false); + if (resolvedSymbol === symbol) { + continue; + } + if (!substitutionsPerScope[i].has(symbolId)) { + const substitution = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.exportSymbol || symbol, scope, isTypeName); + if (substitution) { + substitutionsPerScope[i].set(symbolId, substitution); } - if (!substitutionsPerScope[i].has(symbolId)) { - const substitution = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.exportSymbol || symbol, scope, isTypeName); - if (substitution) { - substitutionsPerScope[i].set(symbolId, substitution); - } - else if (isTypeName) { - // If the symbol is a type parameter that won't be in scope, we'll pass it as a type argument - // so there's no problem. - if (!(symbol.flags & SymbolFlags.TypeParameter)) { - const diag = createDiagnosticForNode(identifier, Messages.typeWillNotBeVisibleInTheNewScope); - functionErrorsPerScope[i].push(diag); - constantErrorsPerScope[i].push(diag); - } - } - else { - usagesPerScope[i].usages.set(identifier.text, { usage, symbol, node: identifier }); + else if (isTypeName) { + // If the symbol is a type parameter that won't be in scope, we'll pass it as a type argument + // so there's no problem. + if (!(symbol.flags & SymbolFlags.TypeParameter)) { + const diag = createDiagnosticForNode(identifier, Messages.typeWillNotBeVisibleInTheNewScope); + functionErrorsPerScope[i].push(diag); + constantErrorsPerScope[i].push(diag); } } + else { + usagesPerScope[i].usages.set(identifier.text, { usage, symbol, node: identifier }); + } } - return symbolId; } + return symbolId; + } - function checkForUsedDeclarations(node: Node) { - // If this node is entirely within the original extraction range, we don't need to do anything. - if (node === targetRange.range || (isReadonlyArray(targetRange.range) && targetRange.range.indexOf(node as Statement) >= 0)) { - return; - } - - // Otherwise check and recurse. - const sym = isIdentifier(node) - ? getSymbolReferencedByIdentifier(node) - : checker.getSymbolAtLocation(node); - if (sym) { - const decl = find(visibleDeclarationsInExtractedRange, d => d.symbol === sym); - if (decl) { - if (isVariableDeclaration(decl)) { - const idString = decl.symbol.id!.toString(); - if (!exposedVariableSymbolSet.has(idString)) { - exposedVariableDeclarations.push(decl); - exposedVariableSymbolSet.set(idString, true); - } - } - else { - // CONSIDER: this includes binding elements, which we could - // expose in the same way as variables. - firstExposedNonVariableDeclaration = firstExposedNonVariableDeclaration || decl; + function checkForUsedDeclarations(node: Node) { + // If this node is entirely within the original extraction range, we don't need to do anything. + if (node === targetRange.range || (isReadonlyArray(targetRange.range) && targetRange.range.indexOf(node as Statement) >= 0)) { + return; + } + + // Otherwise check and recurse. + const sym = isIdentifier(node) + ? getSymbolReferencedByIdentifier(node) + : checker.getSymbolAtLocation(node); + if (sym) { + const decl = find(visibleDeclarationsInExtractedRange, d => d.symbol === sym); + if (decl) { + if (isVariableDeclaration(decl)) { + const idString = decl.symbol.id!.toString(); + if (!exposedVariableSymbolSet.has(idString)) { + exposedVariableDeclarations.push(decl); + exposedVariableSymbolSet.set(idString, true); } } + else { + // CONSIDER: this includes binding elements, which we could + // expose in the same way as variables. + firstExposedNonVariableDeclaration = firstExposedNonVariableDeclaration || decl; + } } - - forEachChild(node, checkForUsedDeclarations); - } - - /** - * Return the symbol referenced by an identifier (even if it declares a different symbol). - */ - function getSymbolReferencedByIdentifier(identifier: Identifier) { - // If the identifier is both a property name and its value, we're only interested in its value - // (since the name is a declaration and will be included in the extracted range). - return identifier.parent && isShorthandPropertyAssignment(identifier.parent) && identifier.parent.name === identifier - ? checker.getShorthandAssignmentValueSymbol(identifier.parent) - : checker.getSymbolAtLocation(identifier); - } - - function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol | undefined, scopeDecl: Node, isTypeNode: boolean): PropertyAccessExpression | EntityName | undefined { - if (!symbol) { - return undefined; - } - const decls = symbol.getDeclarations(); - if (decls && decls.some(d => d.parent === scopeDecl)) { - return factory.createIdentifier(symbol.name); - } - const prefix = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.parent, scopeDecl, isTypeNode); - if (prefix === undefined) { - return undefined; - } - return isTypeNode - ? factory.createQualifiedName(prefix as EntityName, factory.createIdentifier(symbol.name)) - : factory.createPropertyAccessExpression(prefix as Expression, symbol.name); } - } - function getExtractableParent(node: Node | undefined): Node | undefined { - return findAncestor(node, node => node.parent && isExtractableExpression(node) && !isBinaryExpression(node.parent)); + forEachChild(node, checkForUsedDeclarations); } /** - * Computes whether or not a node represents an expression in a position where it could - * be extracted. - * The isExpression() in utilities.ts returns some false positives we need to handle, - * such as `import x from 'y'` -- the 'y' is a StringLiteral but is *not* an expression - * in the sense of something that you could extract on + * Return the symbol referenced by an identifier (even if it declares a different symbol). */ - function isExtractableExpression(node: Node): boolean { - const { parent } = node; - switch (parent.kind) { - case SyntaxKind.EnumMember: - return false; - } + function getSymbolReferencedByIdentifier(identifier: Identifier) { + // If the identifier is both a property name and its value, we're only interested in its value + // (since the name is a declaration and will be included in the extracted range). + return identifier.parent && isShorthandPropertyAssignment(identifier.parent) && identifier.parent.name === identifier + ? checker.getShorthandAssignmentValueSymbol(identifier.parent) + : checker.getSymbolAtLocation(identifier); + } - switch (node.kind) { - case SyntaxKind.StringLiteral: - return parent.kind !== SyntaxKind.ImportDeclaration && - parent.kind !== SyntaxKind.ImportSpecifier; + function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol | undefined, scopeDecl: Node, isTypeNode: boolean): PropertyAccessExpression | EntityName | undefined { + if (!symbol) { + return undefined; + } + const decls = symbol.getDeclarations(); + if (decls && decls.some(d => d.parent === scopeDecl)) { + return factory.createIdentifier(symbol.name); + } + const prefix = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.parent, scopeDecl, isTypeNode); + if (prefix === undefined) { + return undefined; + } + return isTypeNode + ? factory.createQualifiedName(prefix as EntityName, factory.createIdentifier(symbol.name)) + : factory.createPropertyAccessExpression(prefix as Expression, symbol.name); + } +} - case SyntaxKind.SpreadElement: - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.BindingElement: - return false; +function getExtractableParent(node: Node | undefined): Node | undefined { + return findAncestor(node, node => node.parent && isExtractableExpression(node) && !isBinaryExpression(node.parent)); +} - case SyntaxKind.Identifier: - return parent.kind !== SyntaxKind.BindingElement && - parent.kind !== SyntaxKind.ImportSpecifier && - parent.kind !== SyntaxKind.ExportSpecifier; - } - return true; +/** + * Computes whether or not a node represents an expression in a position where it could + * be extracted. + * The isExpression() in utilities.ts returns some false positives we need to handle, + * such as `import x from 'y'` -- the 'y' is a StringLiteral but is *not* an expression + * in the sense of something that you could extract on + */ +function isExtractableExpression(node: Node): boolean { + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.EnumMember: + return false; } - function isBlockLike(node: Node): node is BlockLike { - switch (node.kind) { - case SyntaxKind.Block: - case SyntaxKind.SourceFile: - case SyntaxKind.ModuleBlock: - case SyntaxKind.CaseClause: - return true; - default: - return false; - } + switch (node.kind) { + case SyntaxKind.StringLiteral: + return parent.kind !== SyntaxKind.ImportDeclaration && + parent.kind !== SyntaxKind.ImportSpecifier; + + case SyntaxKind.SpreadElement: + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.BindingElement: + return false; + + case SyntaxKind.Identifier: + return parent.kind !== SyntaxKind.BindingElement && + parent.kind !== SyntaxKind.ImportSpecifier && + parent.kind !== SyntaxKind.ExportSpecifier; } + return true; +} - function isInJSXContent(node: Node) { - return (isJsxElement(node) || isJsxSelfClosingElement(node) || isJsxFragment(node)) && (isJsxElement(node.parent) || isJsxFragment(node.parent)); +function isBlockLike(node: Node): node is BlockLike { + switch (node.kind) { + case SyntaxKind.Block: + case SyntaxKind.SourceFile: + case SyntaxKind.ModuleBlock: + case SyntaxKind.CaseClause: + return true; + default: + return false; } } + +function isInJSXContent(node: Node) { + return (isJsxElement(node) || isJsxSelfClosingElement(node) || isJsxFragment(node)) && (isJsxElement(node.parent) || isJsxFragment(node.parent)); +} +} diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 549ff99f4d7ac..dc71270c66c2e 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -1,243 +1,243 @@ /* @internal */ namespace ts.refactor { - const refactorName = "Extract type"; - - const extractToTypeAliasAction = { - name: "Extract to type alias", - description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias), - kind: "refactor.extract.type", - }; - const extractToInterfaceAction = { - name: "Extract to interface", - description: getLocaleSpecificMessage(Diagnostics.Extract_to_interface), - kind: "refactor.extract.interface", - }; - const extractToTypeDefAction = { - name: "Extract to typedef", - description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef), - kind: "refactor.extract.typedef" - }; - - registerRefactor(refactorName, { - kinds: [ - extractToTypeAliasAction.kind, - extractToInterfaceAction.kind, - extractToTypeDefAction.kind - ], - getAvailableActions(context): readonly ApplicableRefactorInfo[] { - const info = getRangeToExtract(context, context.triggerReason === "invoked"); - if (!info) return emptyArray; - - if (!isRefactorErrorInfo(info)) { - return [{ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_type), - actions: info.isJS ? - [extractToTypeDefAction] : append([extractToTypeAliasAction], info.typeElements && extractToInterfaceAction) - }]; - } - - if (context.preferences.provideRefactorNotApplicableReason) { - return [{ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_type), - actions: [ - { ...extractToTypeDefAction, notApplicableReason: info.error }, - { ...extractToTypeAliasAction, notApplicableReason: info.error }, - { ...extractToInterfaceAction, notApplicableReason: info.error }, - ] - }]; - } - - return emptyArray; - }, - getEditsForAction(context, actionName): RefactorEditInfo { - const { file, } = context; - const info = getRangeToExtract(context); - Debug.assert(info && !isRefactorErrorInfo(info), "Expected to find a range to extract"); - - const name = getUniqueName("NewType", file); - const edits = textChanges.ChangeTracker.with(context, changes => { - switch (actionName) { - case extractToTypeAliasAction.name: - Debug.assert(!info.isJS, "Invalid actionName/JS combo"); - return doTypeAliasChange(changes, file, name, info); - case extractToTypeDefAction.name: - Debug.assert(info.isJS, "Invalid actionName/JS combo"); - return doTypedefChange(changes, file, name, info); - case extractToInterfaceAction.name: - Debug.assert(!info.isJS && !!info.typeElements, "Invalid actionName/JS combo"); - return doInterfaceChange(changes, file, name, info as InterfaceInfo); - default: - Debug.fail("Unexpected action name"); - } - }); +const refactorName = "Extract type"; + +const extractToTypeAliasAction = { + name: "Extract to type alias", + description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias), + kind: "refactor.extract.type", +}; +const extractToInterfaceAction = { + name: "Extract to interface", + description: getLocaleSpecificMessage(Diagnostics.Extract_to_interface), + kind: "refactor.extract.interface", +}; +const extractToTypeDefAction = { + name: "Extract to typedef", + description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef), + kind: "refactor.extract.typedef" +}; + +registerRefactor(refactorName, { + kinds: [ + extractToTypeAliasAction.kind, + extractToInterfaceAction.kind, + extractToTypeDefAction.kind + ], + getAvailableActions(context): readonly ApplicableRefactorInfo[] { + const info = getRangeToExtract(context, context.triggerReason === "invoked"); + if (!info) return emptyArray; + + if (!isRefactorErrorInfo(info)) { + return [{ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_type), + actions: info.isJS ? + [extractToTypeDefAction] : append([extractToTypeAliasAction], info.typeElements && extractToInterfaceAction) + }]; + } - const renameFilename = file.fileName; - const renameLocation = getRenameLocation(edits, renameFilename, name, /*preferLastLocation*/ false); - return { edits, renameFilename, renameLocation }; + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_type), + actions: [ + { ...extractToTypeDefAction, notApplicableReason: info.error }, + { ...extractToTypeAliasAction, notApplicableReason: info.error }, + { ...extractToInterfaceAction, notApplicableReason: info.error }, + ] + }]; } - }); - interface TypeAliasInfo { - isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; typeElements?: readonly TypeElement[]; - } + return emptyArray; + }, + getEditsForAction(context, actionName): RefactorEditInfo { + const { file, } = context; + const info = getRangeToExtract(context); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected to find a range to extract"); + + const name = getUniqueName("NewType", file); + const edits = textChanges.ChangeTracker.with(context, changes => { + switch (actionName) { + case extractToTypeAliasAction.name: + Debug.assert(!info.isJS, "Invalid actionName/JS combo"); + return doTypeAliasChange(changes, file, name, info); + case extractToTypeDefAction.name: + Debug.assert(info.isJS, "Invalid actionName/JS combo"); + return doTypedefChange(changes, file, name, info); + case extractToInterfaceAction.name: + Debug.assert(!info.isJS && !!info.typeElements, "Invalid actionName/JS combo"); + return doInterfaceChange(changes, file, name, info as InterfaceInfo); + default: + Debug.fail("Unexpected action name"); + } + }); - interface InterfaceInfo { - isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; typeElements: readonly TypeElement[]; + const renameFilename = file.fileName; + const renameLocation = getRenameLocation(edits, renameFilename, name, /*preferLastLocation*/ false); + return { edits, renameFilename, renameLocation }; } +}); - type ExtractInfo = TypeAliasInfo | InterfaceInfo; +interface TypeAliasInfo { + isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; typeElements?: readonly TypeElement[]; +} - function getRangeToExtract(context: RefactorContext, considerEmptySpans = true): ExtractInfo | RefactorErrorInfo | undefined { - const { file, startPosition } = context; - const isJS = isSourceFileJS(file); - const current = getTokenAtPosition(file, startPosition); - const range = createTextRangeFromSpan(getRefactorContextSpan(context)); - const cursorRequest = range.pos === range.end && considerEmptySpans; +interface InterfaceInfo { + isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; typeElements: readonly TypeElement[]; +} - const selection = findAncestor(current, (node => node.parent && isTypeNode(node) && !rangeContainsSkipTrivia(range, node.parent, file) && - (cursorRequest || nodeOverlapsWithStartEnd(current, file, range.pos, range.end)))); - if (!selection || !isTypeNode(selection)) return { error: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_type_node) }; +type ExtractInfo = TypeAliasInfo | InterfaceInfo; - const checker = context.program.getTypeChecker(); - const firstStatement = Debug.checkDefined(findAncestor(selection, isStatement), "Should find a statement"); - const typeParameters = collectTypeParameters(checker, selection, firstStatement, file); - if (!typeParameters) return { error: getLocaleSpecificMessage(Diagnostics.No_type_could_be_extracted_from_this_type_node) }; +function getRangeToExtract(context: RefactorContext, considerEmptySpans = true): ExtractInfo | RefactorErrorInfo | undefined { + const { file, startPosition } = context; + const isJS = isSourceFileJS(file); + const current = getTokenAtPosition(file, startPosition); + const range = createTextRangeFromSpan(getRefactorContextSpan(context)); + const cursorRequest = range.pos === range.end && considerEmptySpans; - const typeElements = flattenTypeLiteralNodeReference(checker, selection); - return { isJS, selection, firstStatement, typeParameters, typeElements }; - } + const selection = findAncestor(current, (node => node.parent && isTypeNode(node) && !rangeContainsSkipTrivia(range, node.parent, file) && + (cursorRequest || nodeOverlapsWithStartEnd(current, file, range.pos, range.end)))); + if (!selection || !isTypeNode(selection)) return { error: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_type_node) }; - function flattenTypeLiteralNodeReference(checker: TypeChecker, node: TypeNode | undefined): readonly TypeElement[] | undefined { - if (!node) return undefined; - if (isIntersectionTypeNode(node)) { - const result: TypeElement[] = []; - const seen = new Map(); - for (const type of node.types) { - const flattenedTypeMembers = flattenTypeLiteralNodeReference(checker, type); - if (!flattenedTypeMembers || !flattenedTypeMembers.every(type => type.name && addToSeen(seen, getNameFromPropertyName(type.name) as string))) { - return undefined; - } + const checker = context.program.getTypeChecker(); + const firstStatement = Debug.checkDefined(findAncestor(selection, isStatement), "Should find a statement"); + const typeParameters = collectTypeParameters(checker, selection, firstStatement, file); + if (!typeParameters) return { error: getLocaleSpecificMessage(Diagnostics.No_type_could_be_extracted_from_this_type_node) }; + + const typeElements = flattenTypeLiteralNodeReference(checker, selection); + return { isJS, selection, firstStatement, typeParameters, typeElements }; +} - addRange(result, flattenedTypeMembers); +function flattenTypeLiteralNodeReference(checker: TypeChecker, node: TypeNode | undefined): readonly TypeElement[] | undefined { + if (!node) return undefined; + if (isIntersectionTypeNode(node)) { + const result: TypeElement[] = []; + const seen = new Map(); + for (const type of node.types) { + const flattenedTypeMembers = flattenTypeLiteralNodeReference(checker, type); + if (!flattenedTypeMembers || !flattenedTypeMembers.every(type => type.name && addToSeen(seen, getNameFromPropertyName(type.name) as string))) { + return undefined; } - return result; - } - else if (isParenthesizedTypeNode(node)) { - return flattenTypeLiteralNodeReference(checker, node.type); - } - else if (isTypeLiteralNode(node)) { - return node.members; + + addRange(result, flattenedTypeMembers); } - return undefined; + return result; } - - function rangeContainsSkipTrivia(r1: TextRange, node: Node, file: SourceFile): boolean { - return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); + else if (isParenthesizedTypeNode(node)) { + return flattenTypeLiteralNodeReference(checker, node.type); + } + else if (isTypeLiteralNode(node)) { + return node.members; } + return undefined; +} - function collectTypeParameters(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): TypeParameterDeclaration[] | undefined { - const result: TypeParameterDeclaration[] = []; - return visitor(selection) ? undefined : result; - - function visitor(node: Node): true | undefined { - if (isTypeReferenceNode(node)) { - if (isIdentifier(node.typeName)) { - const symbol = checker.resolveName(node.typeName.text, node.typeName, SymbolFlags.TypeParameter, /* excludeGlobals */ true); - if (symbol?.declarations) { - const declaration = cast(first(symbol.declarations), isTypeParameterDeclaration); - if (rangeContainsSkipTrivia(statement, declaration, file) && !rangeContainsSkipTrivia(selection, declaration, file)) { - pushIfUnique(result, declaration); - } +function rangeContainsSkipTrivia(r1: TextRange, node: Node, file: SourceFile): boolean { + return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); +} + +function collectTypeParameters(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): TypeParameterDeclaration[] | undefined { + const result: TypeParameterDeclaration[] = []; + return visitor(selection) ? undefined : result; + + function visitor(node: Node): true | undefined { + if (isTypeReferenceNode(node)) { + if (isIdentifier(node.typeName)) { + const symbol = checker.resolveName(node.typeName.text, node.typeName, SymbolFlags.TypeParameter, /* excludeGlobals */ true); + if (symbol?.declarations) { + const declaration = cast(first(symbol.declarations), isTypeParameterDeclaration); + if (rangeContainsSkipTrivia(statement, declaration, file) && !rangeContainsSkipTrivia(selection, declaration, file)) { + pushIfUnique(result, declaration); } } } - else if (isInferTypeNode(node)) { - const conditionalTypeNode = findAncestor(node, n => isConditionalTypeNode(n) && rangeContainsSkipTrivia(n.extendsType, node, file)); - if (!conditionalTypeNode || !rangeContainsSkipTrivia(selection, conditionalTypeNode, file)) { - return true; - } + } + else if (isInferTypeNode(node)) { + const conditionalTypeNode = findAncestor(node, n => isConditionalTypeNode(n) && rangeContainsSkipTrivia(n.extendsType, node, file)); + if (!conditionalTypeNode || !rangeContainsSkipTrivia(selection, conditionalTypeNode, file)) { + return true; + } + } + else if ((isTypePredicateNode(node) || isThisTypeNode(node))) { + const functionLikeNode = findAncestor(node.parent, isFunctionLike); + if (functionLikeNode && functionLikeNode.type && rangeContainsSkipTrivia(functionLikeNode.type, node, file) && !rangeContainsSkipTrivia(selection, functionLikeNode, file)) { + return true; } - else if ((isTypePredicateNode(node) || isThisTypeNode(node))) { - const functionLikeNode = findAncestor(node.parent, isFunctionLike); - if (functionLikeNode && functionLikeNode.type && rangeContainsSkipTrivia(functionLikeNode.type, node, file) && !rangeContainsSkipTrivia(selection, functionLikeNode, file)) { + } + else if (isTypeQueryNode(node)) { + if (isIdentifier(node.exprName)) { + const symbol = checker.resolveName(node.exprName.text, node.exprName, SymbolFlags.Value, /* excludeGlobals */ false); + if (symbol?.valueDeclaration && rangeContainsSkipTrivia(statement, symbol.valueDeclaration, file) && !rangeContainsSkipTrivia(selection, symbol.valueDeclaration, file)) { return true; } } - else if (isTypeQueryNode(node)) { - if (isIdentifier(node.exprName)) { - const symbol = checker.resolveName(node.exprName.text, node.exprName, SymbolFlags.Value, /* excludeGlobals */ false); - if (symbol?.valueDeclaration && rangeContainsSkipTrivia(statement, symbol.valueDeclaration, file) && !rangeContainsSkipTrivia(selection, symbol.valueDeclaration, file)) { - return true; - } - } - else { - if (isThisIdentifier(node.exprName.left) && !rangeContainsSkipTrivia(selection, node.parent, file)) { - return true; - } + else { + if (isThisIdentifier(node.exprName.left) && !rangeContainsSkipTrivia(selection, node.parent, file)) { + return true; } } - - if (file && isTupleTypeNode(node) && (getLineAndCharacterOfPosition(file, node.pos).line === getLineAndCharacterOfPosition(file, node.end).line)) { - setEmitFlags(node, EmitFlags.SingleLine); - } - - return forEachChild(node, visitor); } - } - function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: TypeAliasInfo) { - const { firstStatement, selection, typeParameters } = info; + if (file && isTupleTypeNode(node) && (getLineAndCharacterOfPosition(file, node.pos).line === getLineAndCharacterOfPosition(file, node.end).line)) { + setEmitFlags(node, EmitFlags.SingleLine); + } - const newTypeNode = factory.createTypeAliasDeclaration( - /* decorators */ undefined, - /* modifiers */ undefined, - name, - typeParameters.map(id => factory.updateTypeParameterDeclaration(id, id.name, id.constraint, /* defaultType */ undefined)), - selection - ); - changes.insertNodeBefore(file, firstStatement, ignoreSourceNewlines(newTypeNode), /* blankLineBetween */ true); - changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace }); + return forEachChild(node, visitor); } +} - function doInterfaceChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: InterfaceInfo) { - const { firstStatement, selection, typeParameters, typeElements } = info; +function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: TypeAliasInfo) { + const { firstStatement, selection, typeParameters } = info; + + const newTypeNode = factory.createTypeAliasDeclaration( + /* decorators */ undefined, + /* modifiers */ undefined, + name, + typeParameters.map(id => factory.updateTypeParameterDeclaration(id, id.name, id.constraint, /* defaultType */ undefined)), + selection + ); + changes.insertNodeBefore(file, firstStatement, ignoreSourceNewlines(newTypeNode), /* blankLineBetween */ true); + changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace }); +} - const newTypeNode = factory.createInterfaceDeclaration( - /* decorators */ undefined, - /* modifiers */ undefined, - name, - typeParameters, - /* heritageClauses */ undefined, - typeElements - ); - setTextRange(newTypeNode, typeElements[0]?.parent); - changes.insertNodeBefore(file, firstStatement, ignoreSourceNewlines(newTypeNode), /* blankLineBetween */ true); - changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace }); - } +function doInterfaceChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: InterfaceInfo) { + const { firstStatement, selection, typeParameters, typeElements } = info; + + const newTypeNode = factory.createInterfaceDeclaration( + /* decorators */ undefined, + /* modifiers */ undefined, + name, + typeParameters, + /* heritageClauses */ undefined, + typeElements + ); + setTextRange(newTypeNode, typeElements[0]?.parent); + changes.insertNodeBefore(file, firstStatement, ignoreSourceNewlines(newTypeNode), /* blankLineBetween */ true); + changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace }); +} - function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: ExtractInfo) { - const { firstStatement, selection, typeParameters } = info; - - const node = factory.createJSDocTypedefTag( - factory.createIdentifier("typedef"), - factory.createJSDocTypeExpression(selection), - factory.createIdentifier(name)); - - const templates: JSDocTemplateTag[] = []; - forEach(typeParameters, typeParameter => { - const constraint = getEffectiveConstraintOfTypeParameter(typeParameter); - const parameter = factory.createTypeParameterDeclaration(typeParameter.name); - const template = factory.createJSDocTemplateTag( - factory.createIdentifier("template"), - constraint && cast(constraint, isJSDocTypeExpression), - [parameter] - ); - templates.push(template); - }); +function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: ExtractInfo) { + const { firstStatement, selection, typeParameters } = info; + + const node = factory.createJSDocTypedefTag( + factory.createIdentifier("typedef"), + factory.createJSDocTypeExpression(selection), + factory.createIdentifier(name)); + + const templates: JSDocTemplateTag[] = []; + forEach(typeParameters, typeParameter => { + const constraint = getEffectiveConstraintOfTypeParameter(typeParameter); + const parameter = factory.createTypeParameterDeclaration(typeParameter.name); + const template = factory.createJSDocTemplateTag( + factory.createIdentifier("template"), + constraint && cast(constraint, isJSDocTypeExpression), + [parameter] + ); + templates.push(template); + }); - changes.insertNodeBefore(file, firstStatement, factory.createJSDocComment(/* comment */ undefined, factory.createNodeArray(concatenate(templates, [node]))), /* blankLineBetween */ true); - changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined)))); - } + changes.insertNodeBefore(file, firstStatement, factory.createJSDocComment(/* comment */ undefined, factory.createNodeArray(concatenate(templates, [node]))), /* blankLineBetween */ true); + changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined)))); +} } diff --git a/src/services/refactors/generateGetAccessorAndSetAccessor.ts b/src/services/refactors/generateGetAccessorAndSetAccessor.ts index 76de5a7f4eaff..73dbcfdd5a765 100644 --- a/src/services/refactors/generateGetAccessorAndSetAccessor.ts +++ b/src/services/refactors/generateGetAccessorAndSetAccessor.ts @@ -1,51 +1,51 @@ /* @internal */ namespace ts.refactor.generateGetAccessorAndSetAccessor { - const actionName = "Generate 'get' and 'set' accessors"; - const actionDescription = Diagnostics.Generate_get_and_set_accessors.message; +const actionName = "Generate 'get' and 'set' accessors"; +const actionDescription = Diagnostics.Generate_get_and_set_accessors.message; - const generateGetSetAction = { - name: actionName, - description: actionDescription, - kind: "refactor.rewrite.property.generateAccessors", - }; - registerRefactor(actionName, { - kinds: [generateGetSetAction.kind], - getEditsForAction(context, actionName) { - if (!context.endPosition) return undefined; - const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.program, context.startPosition, context.endPosition); - Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); - const edits = codefix.generateAccessorFromProperty(context.file, context.program, context.startPosition, context.endPosition, context, actionName); - if (!edits) return undefined; +const generateGetSetAction = { + name: actionName, + description: actionDescription, + kind: "refactor.rewrite.property.generateAccessors", +}; +registerRefactor(actionName, { + kinds: [generateGetSetAction.kind], + getEditsForAction(context, actionName) { + if (!context.endPosition) return undefined; + const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.program, context.startPosition, context.endPosition); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); + const edits = codefix.generateAccessorFromProperty(context.file, context.program, context.startPosition, context.endPosition, context, actionName); + if (!edits) return undefined; - const renameFilename = context.file.fileName; - const nameNeedRename = info.renameAccessor ? info.accessorName : info.fieldName; - const renameLocationOffset = isIdentifier(nameNeedRename) ? 0 : -1; - const renameLocation = renameLocationOffset + getRenameLocation(edits, renameFilename, nameNeedRename.text, /*preferLastLocation*/ isParameter(info.declaration)); + const renameFilename = context.file.fileName; + const nameNeedRename = info.renameAccessor ? info.accessorName : info.fieldName; + const renameLocationOffset = isIdentifier(nameNeedRename) ? 0 : -1; + const renameLocation = renameLocationOffset + getRenameLocation(edits, renameFilename, nameNeedRename.text, /*preferLastLocation*/ isParameter(info.declaration)); - return { renameFilename, renameLocation, edits }; - }, - getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - if (!context.endPosition) return emptyArray; - const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.program, context.startPosition, context.endPosition, context.triggerReason === "invoked"); - if (!info) return emptyArray; + return { renameFilename, renameLocation, edits }; + }, + getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + if (!context.endPosition) return emptyArray; + const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.program, context.startPosition, context.endPosition, context.triggerReason === "invoked"); + if (!info) return emptyArray; - if (!isRefactorErrorInfo(info)) { - return [{ - name: actionName, - description: actionDescription, - actions: [generateGetSetAction], - }]; - } - - if (context.preferences.provideRefactorNotApplicableReason) { - return [{ - name: actionName, - description: actionDescription, - actions: [{ ...generateGetSetAction, notApplicableReason: info.error }], - }]; - } + if (!isRefactorErrorInfo(info)) { + return [{ + name: actionName, + description: actionDescription, + actions: [generateGetSetAction], + }]; + } - return emptyArray; + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ + name: actionName, + description: actionDescription, + actions: [{ ...generateGetSetAction, notApplicableReason: info.error }], + }]; } - }); + + return emptyArray; + } +}); } diff --git a/src/services/refactors/helpers.ts b/src/services/refactors/helpers.ts index e86229af915d1..0046086119147 100644 --- a/src/services/refactors/helpers.ts +++ b/src/services/refactors/helpers.ts @@ -1,25 +1,25 @@ /* @internal */ namespace ts.refactor { - /** - * Returned by refactor functions when some error message needs to be surfaced to users. - */ - export interface RefactorErrorInfo { - error: string; - }; +/** + * Returned by refactor functions when some error message needs to be surfaced to users. + */ +export interface RefactorErrorInfo { + error: string; +}; - /** - * Checks if some refactor info has refactor error info. - */ - export function isRefactorErrorInfo(info: unknown): info is RefactorErrorInfo { - return (info as RefactorErrorInfo).error !== undefined; - } +/** + * Checks if some refactor info has refactor error info. + */ +export function isRefactorErrorInfo(info: unknown): info is RefactorErrorInfo { + return (info as RefactorErrorInfo).error !== undefined; +} - /** - * Checks if string "known" begins with string "requested". - * Used to match requested kinds with a known kind. - */ - export function refactorKindBeginsWith(known: string, requested: string | undefined): boolean { - if(!requested) return true; - return known.substr(0, requested.length) === requested; - } +/** + * Checks if string "known" begins with string "requested". + * Used to match requested kinds with a known kind. + */ +export function refactorKindBeginsWith(known: string, requested: string | undefined): boolean { + if(!requested) return true; + return known.substr(0, requested.length) === requested; +} } diff --git a/src/services/refactors/inferFunctionReturnType.ts b/src/services/refactors/inferFunctionReturnType.ts index 919aaf5413dfa..2300ae4d87832 100644 --- a/src/services/refactors/inferFunctionReturnType.ts +++ b/src/services/refactors/inferFunctionReturnType.ts @@ -1,117 +1,117 @@ /* @internal */ namespace ts.refactor.inferFunctionReturnType { - const refactorName = "Infer function return type"; - const refactorDescription = Diagnostics.Infer_function_return_type.message; +const refactorName = "Infer function return type"; +const refactorDescription = Diagnostics.Infer_function_return_type.message; - const inferReturnTypeAction = { - name: refactorName, - description: refactorDescription, - kind: "refactor.rewrite.function.returnType" - }; - registerRefactor(refactorName, { - kinds: [inferReturnTypeAction.kind], - getEditsForAction, - getAvailableActions - }); +const inferReturnTypeAction = { + name: refactorName, + description: refactorDescription, + kind: "refactor.rewrite.function.returnType" +}; +registerRefactor(refactorName, { + kinds: [inferReturnTypeAction.kind], + getEditsForAction, + getAvailableActions +}); - function getEditsForAction(context: RefactorContext): RefactorEditInfo | undefined { - const info = getInfo(context); - if (info && !isRefactorErrorInfo(info)) { - const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, t, info.declaration, info.returnTypeNode)); - return { renameFilename: undefined, renameLocation: undefined, edits }; - } - return undefined; +function getEditsForAction(context: RefactorContext): RefactorEditInfo | undefined { + const info = getInfo(context); + if (info && !isRefactorErrorInfo(info)) { + const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, t, info.declaration, info.returnTypeNode)); + return { renameFilename: undefined, renameLocation: undefined, edits }; } + return undefined; +} - function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const info = getInfo(context); - if (!info) return emptyArray; - if (!isRefactorErrorInfo(info)) { - return [{ - name: refactorName, - description: refactorDescription, - actions: [inferReturnTypeAction] - }]; - } - if (context.preferences.provideRefactorNotApplicableReason) { - return [{ - name: refactorName, - description: refactorDescription, - actions: [{ ...inferReturnTypeAction, notApplicableReason: info.error }] - }]; - } - return emptyArray; +function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const info = getInfo(context); + if (!info) return emptyArray; + if (!isRefactorErrorInfo(info)) { + return [{ + name: refactorName, + description: refactorDescription, + actions: [inferReturnTypeAction] + }]; + } + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ + name: refactorName, + description: refactorDescription, + actions: [{ ...inferReturnTypeAction, notApplicableReason: info.error }] + }]; } + return emptyArray; +} - type ConvertibleDeclaration = - | FunctionDeclaration - | FunctionExpression - | ArrowFunction - | MethodDeclaration; +type ConvertibleDeclaration = + | FunctionDeclaration + | FunctionExpression + | ArrowFunction + | MethodDeclaration; - interface FunctionInfo { - declaration: ConvertibleDeclaration; - returnTypeNode: TypeNode; - } +interface FunctionInfo { + declaration: ConvertibleDeclaration; + returnTypeNode: TypeNode; +} - function doChange(sourceFile: SourceFile, changes: textChanges.ChangeTracker, declaration: ConvertibleDeclaration, typeNode: TypeNode) { - const closeParen = findChildOfKind(declaration, SyntaxKind.CloseParenToken, sourceFile); - const needParens = isArrowFunction(declaration) && closeParen === undefined; - const endNode = needParens ? first(declaration.parameters) : closeParen; - if (endNode) { - if (needParens) { - changes.insertNodeBefore(sourceFile, endNode, factory.createToken(SyntaxKind.OpenParenToken)); - changes.insertNodeAfter(sourceFile, endNode, factory.createToken(SyntaxKind.CloseParenToken)); - } - changes.insertNodeAt(sourceFile, endNode.end, typeNode, { prefix: ": " }); +function doChange(sourceFile: SourceFile, changes: textChanges.ChangeTracker, declaration: ConvertibleDeclaration, typeNode: TypeNode) { + const closeParen = findChildOfKind(declaration, SyntaxKind.CloseParenToken, sourceFile); + const needParens = isArrowFunction(declaration) && closeParen === undefined; + const endNode = needParens ? first(declaration.parameters) : closeParen; + if (endNode) { + if (needParens) { + changes.insertNodeBefore(sourceFile, endNode, factory.createToken(SyntaxKind.OpenParenToken)); + changes.insertNodeAfter(sourceFile, endNode, factory.createToken(SyntaxKind.CloseParenToken)); } + changes.insertNodeAt(sourceFile, endNode.end, typeNode, { prefix: ": " }); } +} - function getInfo(context: RefactorContext): FunctionInfo | RefactorErrorInfo | undefined { - if (isInJSFile(context.file) || !refactorKindBeginsWith(inferReturnTypeAction.kind, context.kind)) return; +function getInfo(context: RefactorContext): FunctionInfo | RefactorErrorInfo | undefined { + if (isInJSFile(context.file) || !refactorKindBeginsWith(inferReturnTypeAction.kind, context.kind)) return; - const token = getTokenAtPosition(context.file, context.startPosition); - const declaration = findAncestor(token, n => - isBlock(n) || n.parent && isArrowFunction(n.parent) && (n.kind === SyntaxKind.EqualsGreaterThanToken || n.parent.body === n) ? "quit" : - isConvertibleDeclaration(n)) as ConvertibleDeclaration | undefined; - if (!declaration || !declaration.body || declaration.type) { - return { error: getLocaleSpecificMessage(Diagnostics.Return_type_must_be_inferred_from_a_function) }; - } + const token = getTokenAtPosition(context.file, context.startPosition); + const declaration = findAncestor(token, n => + isBlock(n) || n.parent && isArrowFunction(n.parent) && (n.kind === SyntaxKind.EqualsGreaterThanToken || n.parent.body === n) ? "quit" : + isConvertibleDeclaration(n)) as ConvertibleDeclaration | undefined; + if (!declaration || !declaration.body || declaration.type) { + return { error: getLocaleSpecificMessage(Diagnostics.Return_type_must_be_inferred_from_a_function) }; + } - const typeChecker = context.program.getTypeChecker(); - const returnType = tryGetReturnType(typeChecker, declaration); - if (!returnType) { - return { error: getLocaleSpecificMessage(Diagnostics.Could_not_determine_function_return_type) }; - } + const typeChecker = context.program.getTypeChecker(); + const returnType = tryGetReturnType(typeChecker, declaration); + if (!returnType) { + return { error: getLocaleSpecificMessage(Diagnostics.Could_not_determine_function_return_type) }; + } - const returnTypeNode = typeChecker.typeToTypeNode(returnType, declaration, NodeBuilderFlags.NoTruncation); - if (returnTypeNode) { - return { declaration, returnTypeNode }; - } + const returnTypeNode = typeChecker.typeToTypeNode(returnType, declaration, NodeBuilderFlags.NoTruncation); + if (returnTypeNode) { + return { declaration, returnTypeNode }; } +} - function isConvertibleDeclaration(node: Node): node is ConvertibleDeclaration { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - return true; - default: - return false; - } +function isConvertibleDeclaration(node: Node): node is ConvertibleDeclaration { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + return true; + default: + return false; } +} - function tryGetReturnType(typeChecker: TypeChecker, node: ConvertibleDeclaration): Type | undefined { - if (typeChecker.isImplementationOfOverload(node)) { - const signatures = typeChecker.getTypeAtLocation(node).getCallSignatures(); - if (signatures.length > 1) { - return typeChecker.getUnionType(mapDefined(signatures, s => s.getReturnType())); - } - } - const signature = typeChecker.getSignatureFromDeclaration(node); - if (signature) { - return typeChecker.getReturnTypeOfSignature(signature); +function tryGetReturnType(typeChecker: TypeChecker, node: ConvertibleDeclaration): Type | undefined { + if (typeChecker.isImplementationOfOverload(node)) { + const signatures = typeChecker.getTypeAtLocation(node).getCallSignatures(); + if (signatures.length > 1) { + return typeChecker.getUnionType(mapDefined(signatures, s => s.getReturnType())); } } + const signature = typeChecker.getSignatureFromDeclaration(node); + if (signature) { + return typeChecker.getReturnTypeOfSignature(signature); + } +} } diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index 271a08b20f4ab..c4e33d140299c 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -1,840 +1,840 @@ /* @internal */ namespace ts.refactor { - const refactorName = "Move to a new file"; - const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file); - - const moveToNewFileAction = { - name: refactorName, - description, - kind: "refactor.move.newFile", +const refactorName = "Move to a new file"; +const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file); + +const moveToNewFileAction = { + name: refactorName, + description, + kind: "refactor.move.newFile", +}; +registerRefactor(refactorName, { + kinds: [moveToNewFileAction.kind], + getAvailableActions(context): readonly ApplicableRefactorInfo[] { + const statements = getStatementsToMove(context); + if (context.preferences.allowTextChangesInNewFiles && statements) { + return [{ name: refactorName, description, actions: [moveToNewFileAction] }]; + } + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ name: refactorName, description, actions: + [{ ...moveToNewFileAction, notApplicableReason: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_statement_or_statements) }] + }]; + } + return emptyArray; + }, + getEditsForAction(context, actionName): RefactorEditInfo { + Debug.assert(actionName === refactorName, "Wrong refactor invoked"); + const statements = Debug.checkDefined(getStatementsToMove(context)); + const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, statements, t, context.host, context.preferences)); + return { edits, renameFilename: undefined, renameLocation: undefined }; + } +}); + +interface RangeToMove { readonly toMove: readonly Statement[]; readonly afterLast: Statement | undefined; } +function getRangeToMove(context: RefactorContext): RangeToMove | undefined { + const { file } = context; + const range = createTextRangeFromSpan(getRefactorContextSpan(context)); + const { statements } = file; + + const startNodeIndex = findIndex(statements, s => s.end > range.pos); + if (startNodeIndex === -1) return undefined; + + const startStatement = statements[startNodeIndex]; + if (isNamedDeclaration(startStatement) && startStatement.name && rangeContainsRange(startStatement.name, range)) { + return { toMove: [statements[startNodeIndex]], afterLast: statements[startNodeIndex + 1] }; + } + + // Can't only partially include the start node or be partially into the next node + if (range.pos > startStatement.getStart(file)) return undefined; + const afterEndNodeIndex = findIndex(statements, s => s.end > range.end, startNodeIndex); + // Can't be partially into the next node + if (afterEndNodeIndex !== -1 && (afterEndNodeIndex === 0 || statements[afterEndNodeIndex].getStart(file) < range.end)) return undefined; + + return { + toMove: statements.slice(startNodeIndex, afterEndNodeIndex === -1 ? statements.length : afterEndNodeIndex), + afterLast: afterEndNodeIndex === -1 ? undefined : statements[afterEndNodeIndex], }; - registerRefactor(refactorName, { - kinds: [moveToNewFileAction.kind], - getAvailableActions(context): readonly ApplicableRefactorInfo[] { - const statements = getStatementsToMove(context); - if (context.preferences.allowTextChangesInNewFiles && statements) { - return [{ name: refactorName, description, actions: [moveToNewFileAction] }]; - } - if (context.preferences.provideRefactorNotApplicableReason) { - return [{ name: refactorName, description, actions: - [{ ...moveToNewFileAction, notApplicableReason: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_statement_or_statements) }] - }]; - } - return emptyArray; - }, - getEditsForAction(context, actionName): RefactorEditInfo { - Debug.assert(actionName === refactorName, "Wrong refactor invoked"); - const statements = Debug.checkDefined(getStatementsToMove(context)); - const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, statements, t, context.host, context.preferences)); - return { edits, renameFilename: undefined, renameLocation: undefined }; - } - }); - - interface RangeToMove { readonly toMove: readonly Statement[]; readonly afterLast: Statement | undefined; } - function getRangeToMove(context: RefactorContext): RangeToMove | undefined { - const { file } = context; - const range = createTextRangeFromSpan(getRefactorContextSpan(context)); - const { statements } = file; - - const startNodeIndex = findIndex(statements, s => s.end > range.pos); - if (startNodeIndex === -1) return undefined; +} - const startStatement = statements[startNodeIndex]; - if (isNamedDeclaration(startStatement) && startStatement.name && rangeContainsRange(startStatement.name, range)) { - return { toMove: [statements[startNodeIndex]], afterLast: statements[startNodeIndex + 1] }; - } +function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes: textChanges.ChangeTracker, host: LanguageServiceHost, preferences: UserPreferences): void { + const checker = program.getTypeChecker(); + const usage = getUsageInfo(oldFile, toMove.all, checker); - // Can't only partially include the start node or be partially into the next node - if (range.pos > startStatement.getStart(file)) return undefined; - const afterEndNodeIndex = findIndex(statements, s => s.end > range.end, startNodeIndex); - // Can't be partially into the next node - if (afterEndNodeIndex !== -1 && (afterEndNodeIndex === 0 || statements[afterEndNodeIndex].getStart(file) < range.end)) return undefined; + const currentDirectory = getDirectoryPath(oldFile.fileName); + const extension = extensionFromPath(oldFile.fileName); + const newModuleName = makeUniqueModuleName(getNewModuleName(usage.movedSymbols), extension, currentDirectory, host); + const newFileNameWithExtension = newModuleName + extension; - return { - toMove: statements.slice(startNodeIndex, afterEndNodeIndex === -1 ? statements.length : afterEndNodeIndex), - afterLast: afterEndNodeIndex === -1 ? undefined : statements[afterEndNodeIndex], - }; - } + // If previous file was global, this is easy. + changes.createNewFile(oldFile, combinePaths(currentDirectory, newFileNameWithExtension), getNewStatementsAndRemoveFromOldFile(oldFile, usage, changes, toMove, program, newModuleName, preferences)); - function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes: textChanges.ChangeTracker, host: LanguageServiceHost, preferences: UserPreferences): void { - const checker = program.getTypeChecker(); - const usage = getUsageInfo(oldFile, toMove.all, checker); - - const currentDirectory = getDirectoryPath(oldFile.fileName); - const extension = extensionFromPath(oldFile.fileName); - const newModuleName = makeUniqueModuleName(getNewModuleName(usage.movedSymbols), extension, currentDirectory, host); - const newFileNameWithExtension = newModuleName + extension; + addNewFileToTsconfig(program, changes, oldFile.fileName, newFileNameWithExtension, hostGetCanonicalFileName(host)); +} - // If previous file was global, this is easy. - changes.createNewFile(oldFile, combinePaths(currentDirectory, newFileNameWithExtension), getNewStatementsAndRemoveFromOldFile(oldFile, usage, changes, toMove, program, newModuleName, preferences)); +interface StatementRange { + readonly first: Statement; + readonly afterLast: Statement | undefined; +} +interface ToMove { + readonly all: readonly Statement[]; + readonly ranges: readonly StatementRange[]; +} - addNewFileToTsconfig(program, changes, oldFile.fileName, newFileNameWithExtension, hostGetCanonicalFileName(host)); - } +function getStatementsToMove(context: RefactorContext): ToMove | undefined { + const rangeToMove = getRangeToMove(context); + if (rangeToMove === undefined) return undefined; + const all: Statement[] = []; + const ranges: StatementRange[] = []; + const { toMove, afterLast } = rangeToMove; + getRangesWhere(toMove, isAllowedStatementToMove, (start, afterEndIndex) => { + for (let i = start; i < afterEndIndex; i++) all.push(toMove[i]); + ranges.push({ first: toMove[start], afterLast }); + }); + return all.length === 0 ? undefined : { all, ranges }; +} - interface StatementRange { - readonly first: Statement; - readonly afterLast: Statement | undefined; - } - interface ToMove { - readonly all: readonly Statement[]; - readonly ranges: readonly StatementRange[]; - } +function isAllowedStatementToMove(statement: Statement): boolean { + // Filters imports and prologue directives out of the range of statements to move. + // Imports will be copied to the new file anyway, and may still be needed in the old file. + // Prologue directives will be copied to the new file and should be left in the old file. + return !isPureImport(statement) && !isPrologueDirective(statement);; +} - function getStatementsToMove(context: RefactorContext): ToMove | undefined { - const rangeToMove = getRangeToMove(context); - if (rangeToMove === undefined) return undefined; - const all: Statement[] = []; - const ranges: StatementRange[] = []; - const { toMove, afterLast } = rangeToMove; - getRangesWhere(toMove, isAllowedStatementToMove, (start, afterEndIndex) => { - for (let i = start; i < afterEndIndex; i++) all.push(toMove[i]); - ranges.push({ first: toMove[start], afterLast }); - }); - return all.length === 0 ? undefined : { all, ranges }; - } - - function isAllowedStatementToMove(statement: Statement): boolean { - // Filters imports and prologue directives out of the range of statements to move. - // Imports will be copied to the new file anyway, and may still be needed in the old file. - // Prologue directives will be copied to the new file and should be left in the old file. - return !isPureImport(statement) && !isPrologueDirective(statement);; - } - - function isPureImport(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - return true; - case SyntaxKind.ImportEqualsDeclaration: - return !hasSyntacticModifier(node, ModifierFlags.Export); - case SyntaxKind.VariableStatement: - return (node as VariableStatement).declarationList.declarations.every(d => !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ true)); - default: - return false; - } +function isPureImport(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return true; + case SyntaxKind.ImportEqualsDeclaration: + return !hasSyntacticModifier(node, ModifierFlags.Export); + case SyntaxKind.VariableStatement: + return (node as VariableStatement).declarationList.declarations.every(d => !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ true)); + default: + return false; } +} - function addNewFileToTsconfig(program: Program, changes: textChanges.ChangeTracker, oldFileName: string, newFileNameWithExtension: string, getCanonicalFileName: GetCanonicalFileName): void { - const cfg = program.getCompilerOptions().configFile; - if (!cfg) return; +function addNewFileToTsconfig(program: Program, changes: textChanges.ChangeTracker, oldFileName: string, newFileNameWithExtension: string, getCanonicalFileName: GetCanonicalFileName): void { + const cfg = program.getCompilerOptions().configFile; + if (!cfg) return; - const newFileAbsolutePath = normalizePath(combinePaths(oldFileName, "..", newFileNameWithExtension)); - const newFilePath = getRelativePathFromFile(cfg.fileName, newFileAbsolutePath, getCanonicalFileName); + const newFileAbsolutePath = normalizePath(combinePaths(oldFileName, "..", newFileNameWithExtension)); + const newFilePath = getRelativePathFromFile(cfg.fileName, newFileAbsolutePath, getCanonicalFileName); - const cfgObject = cfg.statements[0] && tryCast(cfg.statements[0].expression, isObjectLiteralExpression); - const filesProp = cfgObject && find(cfgObject.properties, (prop): prop is PropertyAssignment => - isPropertyAssignment(prop) && isStringLiteral(prop.name) && prop.name.text === "files"); - if (filesProp && isArrayLiteralExpression(filesProp.initializer)) { - changes.insertNodeInListAfter(cfg, last(filesProp.initializer.elements), factory.createStringLiteral(newFilePath), filesProp.initializer.elements); - } + const cfgObject = cfg.statements[0] && tryCast(cfg.statements[0].expression, isObjectLiteralExpression); + const filesProp = cfgObject && find(cfgObject.properties, (prop): prop is PropertyAssignment => + isPropertyAssignment(prop) && isStringLiteral(prop.name) && prop.name.text === "files"); + if (filesProp && isArrayLiteralExpression(filesProp.initializer)) { + changes.insertNodeInListAfter(cfg, last(filesProp.initializer.elements), factory.createStringLiteral(newFilePath), filesProp.initializer.elements); } +} - function getNewStatementsAndRemoveFromOldFile( - oldFile: SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ToMove, program: Program, newModuleName: string, preferences: UserPreferences, - ) { - const checker = program.getTypeChecker(); - const prologueDirectives = takeWhile(oldFile.statements, isPrologueDirective); - if (!oldFile.externalModuleIndicator && !oldFile.commonJsModuleIndicator) { - deleteMovedStatements(oldFile, toMove.ranges, changes); - return [...prologueDirectives, ...toMove.all]; - } +function getNewStatementsAndRemoveFromOldFile( + oldFile: SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ToMove, program: Program, newModuleName: string, preferences: UserPreferences, +) { + const checker = program.getTypeChecker(); + const prologueDirectives = takeWhile(oldFile.statements, isPrologueDirective); + if (!oldFile.externalModuleIndicator && !oldFile.commonJsModuleIndicator) { + deleteMovedStatements(oldFile, toMove.ranges, changes); + return [...prologueDirectives, ...toMove.all]; + } - const useEsModuleSyntax = !!oldFile.externalModuleIndicator; - const quotePreference = getQuotePreference(oldFile, preferences); - const importsFromNewFile = createOldFileImportsFromNewFile(usage.oldFileImportsFromNewFile, newModuleName, useEsModuleSyntax, quotePreference); - if (importsFromNewFile) { - insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true); - } + const useEsModuleSyntax = !!oldFile.externalModuleIndicator; + const quotePreference = getQuotePreference(oldFile, preferences); + const importsFromNewFile = createOldFileImportsFromNewFile(usage.oldFileImportsFromNewFile, newModuleName, useEsModuleSyntax, quotePreference); + if (importsFromNewFile) { + insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true); + } - deleteUnusedOldImports(oldFile, toMove.all, changes, usage.unusedImportsFromOldFile, checker); - deleteMovedStatements(oldFile, toMove.ranges, changes); - updateImportsInOtherFiles(changes, program, oldFile, usage.movedSymbols, newModuleName); - - const imports = getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, useEsModuleSyntax, quotePreference); - const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromNewFile, useEsModuleSyntax); - if (imports.length && body.length) { - return [ - ...prologueDirectives, - ...imports, - SyntaxKind.NewLineTrivia as const, - ...body - ]; - } + deleteUnusedOldImports(oldFile, toMove.all, changes, usage.unusedImportsFromOldFile, checker); + deleteMovedStatements(oldFile, toMove.ranges, changes); + updateImportsInOtherFiles(changes, program, oldFile, usage.movedSymbols, newModuleName); + const imports = getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, useEsModuleSyntax, quotePreference); + const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromNewFile, useEsModuleSyntax); + if (imports.length && body.length) { return [ ...prologueDirectives, ...imports, - ...body, + SyntaxKind.NewLineTrivia as const, + ...body ]; } - function deleteMovedStatements(sourceFile: SourceFile, moved: readonly StatementRange[], changes: textChanges.ChangeTracker) { - for (const { first, afterLast } of moved) { - changes.deleteNodeRangeExcludingEnd(sourceFile, first, afterLast); - } - } + return [ + ...prologueDirectives, + ...imports, + ...body, + ]; +} - function deleteUnusedOldImports(oldFile: SourceFile, toMove: readonly Statement[], changes: textChanges.ChangeTracker, toDelete: ReadonlySymbolSet, checker: TypeChecker) { - for (const statement of oldFile.statements) { - if (contains(toMove, statement)) continue; - forEachImportInStatement(statement, i => deleteUnusedImports(oldFile, i, changes, name => toDelete.has(checker.getSymbolAtLocation(name)!))); - } +function deleteMovedStatements(sourceFile: SourceFile, moved: readonly StatementRange[], changes: textChanges.ChangeTracker) { + for (const { first, afterLast } of moved) { + changes.deleteNodeRangeExcludingEnd(sourceFile, first, afterLast); } +} - function updateImportsInOtherFiles(changes: textChanges.ChangeTracker, program: Program, oldFile: SourceFile, movedSymbols: ReadonlySymbolSet, newModuleName: string): void { - const checker = program.getTypeChecker(); - for (const sourceFile of program.getSourceFiles()) { - if (sourceFile === oldFile) continue; - for (const statement of sourceFile.statements) { - forEachImportInStatement(statement, importNode => { - if (checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)) !== oldFile.symbol) return; - - const shouldMove = (name: Identifier): boolean => { - const symbol = isBindingElement(name.parent) - ? getPropertySymbolFromBindingElement(checker, name.parent as ObjectBindingElementWithoutPropertyName) - : skipAlias(checker.getSymbolAtLocation(name)!, checker); // TODO: GH#18217 - return !!symbol && movedSymbols.has(symbol); - }; - deleteUnusedImports(sourceFile, importNode, changes, shouldMove); // These will be changed to imports from the new file - const newModuleSpecifier = combinePaths(getDirectoryPath(moduleSpecifierFromImport(importNode).text), newModuleName); - const newImportDeclaration = filterImport(importNode, factory.createStringLiteral(newModuleSpecifier), shouldMove); - if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration); - - const ns = getNamespaceLikeImport(importNode); - if (ns) updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleName, newModuleSpecifier, ns, importNode); - }); - } - } +function deleteUnusedOldImports(oldFile: SourceFile, toMove: readonly Statement[], changes: textChanges.ChangeTracker, toDelete: ReadonlySymbolSet, checker: TypeChecker) { + for (const statement of oldFile.statements) { + if (contains(toMove, statement)) continue; + forEachImportInStatement(statement, i => deleteUnusedImports(oldFile, i, changes, name => toDelete.has(checker.getSymbolAtLocation(name)!))); } +} - function getNamespaceLikeImport(node: SupportedImport): Identifier | undefined { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - return node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? - node.importClause.namedBindings.name : undefined; - case SyntaxKind.ImportEqualsDeclaration: - return node.name; - case SyntaxKind.VariableDeclaration: - return tryCast(node.name, isIdentifier); - default: - return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); +function updateImportsInOtherFiles(changes: textChanges.ChangeTracker, program: Program, oldFile: SourceFile, movedSymbols: ReadonlySymbolSet, newModuleName: string): void { + const checker = program.getTypeChecker(); + for (const sourceFile of program.getSourceFiles()) { + if (sourceFile === oldFile) continue; + for (const statement of sourceFile.statements) { + forEachImportInStatement(statement, importNode => { + if (checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)) !== oldFile.symbol) return; + + const shouldMove = (name: Identifier): boolean => { + const symbol = isBindingElement(name.parent) + ? getPropertySymbolFromBindingElement(checker, name.parent as ObjectBindingElementWithoutPropertyName) + : skipAlias(checker.getSymbolAtLocation(name)!, checker); // TODO: GH#18217 + return !!symbol && movedSymbols.has(symbol); + }; + deleteUnusedImports(sourceFile, importNode, changes, shouldMove); // These will be changed to imports from the new file + const newModuleSpecifier = combinePaths(getDirectoryPath(moduleSpecifierFromImport(importNode).text), newModuleName); + const newImportDeclaration = filterImport(importNode, factory.createStringLiteral(newModuleSpecifier), shouldMove); + if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration); + + const ns = getNamespaceLikeImport(importNode); + if (ns) updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleName, newModuleSpecifier, ns, importNode); + }); } } +} - function updateNamespaceLikeImport( - changes: textChanges.ChangeTracker, - sourceFile: SourceFile, - checker: TypeChecker, - movedSymbols: ReadonlySymbolSet, - newModuleName: string, - newModuleSpecifier: string, - oldImportId: Identifier, - oldImportNode: SupportedImport, - ): void { - const preferredNewNamespaceName = codefix.moduleSpecifierToValidIdentifier(newModuleName, ScriptTarget.ESNext); - let needUniqueName = false; - const toChange: Identifier[] = []; - FindAllReferences.Core.eachSymbolReferenceInFile(oldImportId, checker, sourceFile, ref => { - if (!isPropertyAccessExpression(ref.parent)) return; - needUniqueName = needUniqueName || !!checker.resolveName(preferredNewNamespaceName, ref, SymbolFlags.All, /*excludeGlobals*/ true); - if (movedSymbols.has(checker.getSymbolAtLocation(ref.parent.name)!)) { - toChange.push(ref); - } - }); +function getNamespaceLikeImport(node: SupportedImport): Identifier | undefined { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? + node.importClause.namedBindings.name : undefined; + case SyntaxKind.ImportEqualsDeclaration: + return node.name; + case SyntaxKind.VariableDeclaration: + return tryCast(node.name, isIdentifier); + default: + return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); + } +} - if (toChange.length) { - const newNamespaceName = needUniqueName ? getUniqueName(preferredNewNamespaceName, sourceFile) : preferredNewNamespaceName; - for (const ref of toChange) { - changes.replaceNode(sourceFile, ref, factory.createIdentifier(newNamespaceName)); - } - changes.insertNodeAfter(sourceFile, oldImportNode, updateNamespaceLikeImportNode(oldImportNode, newModuleName, newModuleSpecifier)); +function updateNamespaceLikeImport( + changes: textChanges.ChangeTracker, + sourceFile: SourceFile, + checker: TypeChecker, + movedSymbols: ReadonlySymbolSet, + newModuleName: string, + newModuleSpecifier: string, + oldImportId: Identifier, + oldImportNode: SupportedImport, +): void { + const preferredNewNamespaceName = codefix.moduleSpecifierToValidIdentifier(newModuleName, ScriptTarget.ESNext); + let needUniqueName = false; + const toChange: Identifier[] = []; + FindAllReferences.Core.eachSymbolReferenceInFile(oldImportId, checker, sourceFile, ref => { + if (!isPropertyAccessExpression(ref.parent)) return; + needUniqueName = needUniqueName || !!checker.resolveName(preferredNewNamespaceName, ref, SymbolFlags.All, /*excludeGlobals*/ true); + if (movedSymbols.has(checker.getSymbolAtLocation(ref.parent.name)!)) { + toChange.push(ref); } - } + }); - function updateNamespaceLikeImportNode(node: SupportedImport, newNamespaceName: string, newModuleSpecifier: string): Node { - const newNamespaceId = factory.createIdentifier(newNamespaceName); - const newModuleString = factory.createStringLiteral(newModuleSpecifier); - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - return factory.createImportDeclaration( - /*decorators*/ undefined, /*modifiers*/ undefined, - factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamespaceImport(newNamespaceId)), - newModuleString, - /*assertClause*/ undefined); - case SyntaxKind.ImportEqualsDeclaration: - return factory.createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, newNamespaceId, factory.createExternalModuleReference(newModuleString)); - case SyntaxKind.VariableDeclaration: - return factory.createVariableDeclaration(newNamespaceId, /*exclamationToken*/ undefined, /*type*/ undefined, createRequireCall(newModuleString)); - default: - return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); + if (toChange.length) { + const newNamespaceName = needUniqueName ? getUniqueName(preferredNewNamespaceName, sourceFile) : preferredNewNamespaceName; + for (const ref of toChange) { + changes.replaceNode(sourceFile, ref, factory.createIdentifier(newNamespaceName)); } + changes.insertNodeAfter(sourceFile, oldImportNode, updateNamespaceLikeImportNode(oldImportNode, newModuleName, newModuleSpecifier)); } +} - function moduleSpecifierFromImport(i: SupportedImport): StringLiteralLike { - return (i.kind === SyntaxKind.ImportDeclaration ? i.moduleSpecifier - : i.kind === SyntaxKind.ImportEqualsDeclaration ? i.moduleReference.expression - : i.initializer.arguments[0]); +function updateNamespaceLikeImportNode(node: SupportedImport, newNamespaceName: string, newModuleSpecifier: string): Node { + const newNamespaceId = factory.createIdentifier(newNamespaceName); + const newModuleString = factory.createStringLiteral(newModuleSpecifier); + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return factory.createImportDeclaration( + /*decorators*/ undefined, /*modifiers*/ undefined, + factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamespaceImport(newNamespaceId)), + newModuleString, + /*assertClause*/ undefined); + case SyntaxKind.ImportEqualsDeclaration: + return factory.createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, newNamespaceId, factory.createExternalModuleReference(newModuleString)); + case SyntaxKind.VariableDeclaration: + return factory.createVariableDeclaration(newNamespaceId, /*exclamationToken*/ undefined, /*type*/ undefined, createRequireCall(newModuleString)); + default: + return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); } +} - function forEachImportInStatement(statement: Statement, cb: (importNode: SupportedImport) => void): void { - if (isImportDeclaration(statement)) { - if (isStringLiteral(statement.moduleSpecifier)) cb(statement as SupportedImport); - } - else if (isImportEqualsDeclaration(statement)) { - if (isExternalModuleReference(statement.moduleReference) && isStringLiteralLike(statement.moduleReference.expression)) { - cb(statement as SupportedImport); - } - } - else if (isVariableStatement(statement)) { - for (const decl of statement.declarationList.declarations) { - if (decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true)) { - cb(decl as SupportedImport); - } - } +function moduleSpecifierFromImport(i: SupportedImport): StringLiteralLike { + return (i.kind === SyntaxKind.ImportDeclaration ? i.moduleSpecifier + : i.kind === SyntaxKind.ImportEqualsDeclaration ? i.moduleReference.expression + : i.initializer.arguments[0]); +} + +function forEachImportInStatement(statement: Statement, cb: (importNode: SupportedImport) => void): void { + if (isImportDeclaration(statement)) { + if (isStringLiteral(statement.moduleSpecifier)) cb(statement as SupportedImport); + } + else if (isImportEqualsDeclaration(statement)) { + if (isExternalModuleReference(statement.moduleReference) && isStringLiteralLike(statement.moduleReference.expression)) { + cb(statement as SupportedImport); } } - - type SupportedImport = - | ImportDeclaration & { moduleSpecifier: StringLiteralLike } - | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteralLike } } - | VariableDeclaration & { initializer: RequireOrImportCall }; - type SupportedImportStatement = - | ImportDeclaration - | ImportEqualsDeclaration - | VariableStatement; - - function createOldFileImportsFromNewFile(newFileNeedExport: ReadonlySymbolSet, newFileNameWithExtension: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { - let defaultImport: Identifier | undefined; - const imports: string[] = []; - newFileNeedExport.forEach(symbol => { - if (symbol.escapedName === InternalSymbolName.Default) { - defaultImport = factory.createIdentifier(symbolNameNoDefault(symbol)!); // TODO: GH#18217 - } - else { - imports.push(symbol.name); + else if (isVariableStatement(statement)) { + for (const decl of statement.declarationList.declarations) { + if (decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true)) { + cb(decl as SupportedImport); } - }); - return makeImportOrRequire(defaultImport, imports, newFileNameWithExtension, useEs6Imports, quotePreference); + } } +} - function makeImportOrRequire(defaultImport: Identifier | undefined, imports: readonly string[], path: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { - path = ensurePathIsNonModuleName(path); - if (useEs6Imports) { - const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i))); - return makeImportIfNecessary(defaultImport, specifiers, path, quotePreference); +type SupportedImport = + | ImportDeclaration & { moduleSpecifier: StringLiteralLike } + | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteralLike } } + | VariableDeclaration & { initializer: RequireOrImportCall }; +type SupportedImportStatement = + | ImportDeclaration + | ImportEqualsDeclaration + | VariableStatement; + +function createOldFileImportsFromNewFile(newFileNeedExport: ReadonlySymbolSet, newFileNameWithExtension: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { + let defaultImport: Identifier | undefined; + const imports: string[] = []; + newFileNeedExport.forEach(symbol => { + if (symbol.escapedName === InternalSymbolName.Default) { + defaultImport = factory.createIdentifier(symbolNameNoDefault(symbol)!); // TODO: GH#18217 } else { - Debug.assert(!defaultImport, "No default import should exist"); // If there's a default export, it should have been an es6 module. - const bindingElements = imports.map(i => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, i)); - return bindingElements.length - ? makeVariableStatement(factory.createObjectBindingPattern(bindingElements), /*type*/ undefined, createRequireCall(factory.createStringLiteral(path))) as RequireVariableStatement - : undefined; + imports.push(symbol.name); } - } + }); + return makeImportOrRequire(defaultImport, imports, newFileNameWithExtension, useEs6Imports, quotePreference); +} - function makeVariableStatement(name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined, flags: NodeFlags = NodeFlags.Const) { - return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, initializer)], flags)); +function makeImportOrRequire(defaultImport: Identifier | undefined, imports: readonly string[], path: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { + path = ensurePathIsNonModuleName(path); + if (useEs6Imports) { + const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i))); + return makeImportIfNecessary(defaultImport, specifiers, path, quotePreference); } - - function createRequireCall(moduleSpecifier: StringLiteralLike): CallExpression { - return factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [moduleSpecifier]); + else { + Debug.assert(!defaultImport, "No default import should exist"); // If there's a default export, it should have been an es6 module. + const bindingElements = imports.map(i => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, i)); + return bindingElements.length + ? makeVariableStatement(factory.createObjectBindingPattern(bindingElements), /*type*/ undefined, createRequireCall(factory.createStringLiteral(path))) as RequireVariableStatement + : undefined; } +} + +function makeVariableStatement(name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined, flags: NodeFlags = NodeFlags.Const) { + return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, initializer)], flags)); +} + +function createRequireCall(moduleSpecifier: StringLiteralLike): CallExpression { + return factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [moduleSpecifier]); +} + +function addExports(sourceFile: SourceFile, toMove: readonly Statement[], needExport: ReadonlySymbolSet, useEs6Exports: boolean): readonly Statement[] { + return flatMap(toMove, statement => { + if (isTopLevelDeclarationStatement(statement) && + !isExported(sourceFile, statement, useEs6Exports) && + forEachTopLevelDeclaration(statement, d => needExport.has(Debug.checkDefined(d.symbol)))) { + const exports = addExport(statement, useEs6Exports); + if (exports) return exports; + } + return statement; + }); +} - function addExports(sourceFile: SourceFile, toMove: readonly Statement[], needExport: ReadonlySymbolSet, useEs6Exports: boolean): readonly Statement[] { - return flatMap(toMove, statement => { - if (isTopLevelDeclarationStatement(statement) && - !isExported(sourceFile, statement, useEs6Exports) && - forEachTopLevelDeclaration(statement, d => needExport.has(Debug.checkDefined(d.symbol)))) { - const exports = addExport(statement, useEs6Exports); - if (exports) return exports; +function deleteUnusedImports(sourceFile: SourceFile, importDecl: SupportedImport, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void { + switch (importDecl.kind) { + case SyntaxKind.ImportDeclaration: + deleteUnusedImportsInDeclaration(sourceFile, importDecl, changes, isUnused); + break; + case SyntaxKind.ImportEqualsDeclaration: + if (isUnused(importDecl.name)) { + changes.delete(sourceFile, importDecl); } - return statement; - }); + break; + case SyntaxKind.VariableDeclaration: + deleteUnusedImportsInVariableDeclaration(sourceFile, importDecl, changes, isUnused); + break; + default: + Debug.assertNever(importDecl, `Unexpected import decl kind ${(importDecl as SupportedImport).kind}`); } - - function deleteUnusedImports(sourceFile: SourceFile, importDecl: SupportedImport, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void { - switch (importDecl.kind) { - case SyntaxKind.ImportDeclaration: - deleteUnusedImportsInDeclaration(sourceFile, importDecl, changes, isUnused); - break; - case SyntaxKind.ImportEqualsDeclaration: - if (isUnused(importDecl.name)) { - changes.delete(sourceFile, importDecl); +} +function deleteUnusedImportsInDeclaration(sourceFile: SourceFile, importDecl: ImportDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void { + if (!importDecl.importClause) return; + const { name, namedBindings } = importDecl.importClause; + const defaultUnused = !name || isUnused(name); + const namedBindingsUnused = !namedBindings || + (namedBindings.kind === SyntaxKind.NamespaceImport ? isUnused(namedBindings.name) : namedBindings.elements.length !== 0 && namedBindings.elements.every(e => isUnused(e.name))); + if (defaultUnused && namedBindingsUnused) { + changes.delete(sourceFile, importDecl); + } + else { + if (name && defaultUnused) { + changes.delete(sourceFile, name); + } + if (namedBindings) { + if (namedBindingsUnused) { + changes.replaceNode( + sourceFile, + importDecl.importClause, + factory.updateImportClause(importDecl.importClause, importDecl.importClause.isTypeOnly, name, /*namedBindings*/ undefined) + ); + } + else if (namedBindings.kind === SyntaxKind.NamedImports) { + for (const element of namedBindings.elements) { + if (isUnused(element.name)) changes.delete(sourceFile, element); } - break; - case SyntaxKind.VariableDeclaration: - deleteUnusedImportsInVariableDeclaration(sourceFile, importDecl, changes, isUnused); - break; - default: - Debug.assertNever(importDecl, `Unexpected import decl kind ${(importDecl as SupportedImport).kind}`); + } } } - function deleteUnusedImportsInDeclaration(sourceFile: SourceFile, importDecl: ImportDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void { - if (!importDecl.importClause) return; - const { name, namedBindings } = importDecl.importClause; - const defaultUnused = !name || isUnused(name); - const namedBindingsUnused = !namedBindings || - (namedBindings.kind === SyntaxKind.NamespaceImport ? isUnused(namedBindings.name) : namedBindings.elements.length !== 0 && namedBindings.elements.every(e => isUnused(e.name))); - if (defaultUnused && namedBindingsUnused) { - changes.delete(sourceFile, importDecl); - } - else { - if (name && defaultUnused) { +} +function deleteUnusedImportsInVariableDeclaration(sourceFile: SourceFile, varDecl: VariableDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean) { + const { name } = varDecl; + switch (name.kind) { + case SyntaxKind.Identifier: + if (isUnused(name)) { changes.delete(sourceFile, name); } - if (namedBindings) { - if (namedBindingsUnused) { - changes.replaceNode( - sourceFile, - importDecl.importClause, - factory.updateImportClause(importDecl.importClause, importDecl.importClause.isTypeOnly, name, /*namedBindings*/ undefined) - ); - } - else if (namedBindings.kind === SyntaxKind.NamedImports) { - for (const element of namedBindings.elements) { - if (isUnused(element.name)) changes.delete(sourceFile, element); + break; + case SyntaxKind.ArrayBindingPattern: + break; + case SyntaxKind.ObjectBindingPattern: + if (name.elements.every(e => isIdentifier(e.name) && isUnused(e.name))) { + changes.delete(sourceFile, + isVariableDeclarationList(varDecl.parent) && varDecl.parent.declarations.length === 1 ? varDecl.parent.parent : varDecl); + } + else { + for (const element of name.elements) { + if (isIdentifier(element.name) && isUnused(element.name)) { + changes.delete(sourceFile, element.name); } } } - } + break; } - function deleteUnusedImportsInVariableDeclaration(sourceFile: SourceFile, varDecl: VariableDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean) { - const { name } = varDecl; - switch (name.kind) { - case SyntaxKind.Identifier: - if (isUnused(name)) { - changes.delete(sourceFile, name); - } - break; - case SyntaxKind.ArrayBindingPattern: - break; - case SyntaxKind.ObjectBindingPattern: - if (name.elements.every(e => isIdentifier(e.name) && isUnused(e.name))) { - changes.delete(sourceFile, - isVariableDeclarationList(varDecl.parent) && varDecl.parent.declarations.length === 1 ? varDecl.parent.parent : varDecl); - } - else { - for (const element of name.elements) { - if (isIdentifier(element.name) && isUnused(element.name)) { - changes.delete(sourceFile, element.name); - } - } - } - break; - } +} + +function getNewFileImportsAndAddExportInOldFile( + oldFile: SourceFile, + importsToCopy: ReadonlySymbolSet, + newFileImportsFromOldFile: ReadonlySymbolSet, + changes: textChanges.ChangeTracker, + checker: TypeChecker, + useEsModuleSyntax: boolean, + quotePreference: QuotePreference, +): readonly SupportedImportStatement[] { + const copiedOldImports: SupportedImportStatement[] = []; + for (const oldStatement of oldFile.statements) { + forEachImportInStatement(oldStatement, i => { + append(copiedOldImports, filterImport(i, moduleSpecifierFromImport(i), name => importsToCopy.has(checker.getSymbolAtLocation(name)!))); + }); } - function getNewFileImportsAndAddExportInOldFile( - oldFile: SourceFile, - importsToCopy: ReadonlySymbolSet, - newFileImportsFromOldFile: ReadonlySymbolSet, - changes: textChanges.ChangeTracker, - checker: TypeChecker, - useEsModuleSyntax: boolean, - quotePreference: QuotePreference, - ): readonly SupportedImportStatement[] { - const copiedOldImports: SupportedImportStatement[] = []; - for (const oldStatement of oldFile.statements) { - forEachImportInStatement(oldStatement, i => { - append(copiedOldImports, filterImport(i, moduleSpecifierFromImport(i), name => importsToCopy.has(checker.getSymbolAtLocation(name)!))); - }); + // Also, import things used from the old file, and insert 'export' modifiers as necessary in the old file. + let oldFileDefault: Identifier | undefined; + const oldFileNamedImports: string[] = []; + const markSeenTop = nodeSeenTracker(); // Needed because multiple declarations may appear in `const x = 0, y = 1;`. + newFileImportsFromOldFile.forEach(symbol => { + if (!symbol.declarations) { + return; } + for (const decl of symbol.declarations) { + if (!isTopLevelDeclaration(decl)) continue; + const name = nameOfTopLevelDeclaration(decl); + if (!name) continue; - // Also, import things used from the old file, and insert 'export' modifiers as necessary in the old file. - let oldFileDefault: Identifier | undefined; - const oldFileNamedImports: string[] = []; - const markSeenTop = nodeSeenTracker(); // Needed because multiple declarations may appear in `const x = 0, y = 1;`. - newFileImportsFromOldFile.forEach(symbol => { - if (!symbol.declarations) { - return; + const top = getTopLevelDeclarationStatement(decl); + if (markSeenTop(top)) { + addExportToChanges(oldFile, top, name, changes, useEsModuleSyntax); } - for (const decl of symbol.declarations) { - if (!isTopLevelDeclaration(decl)) continue; - const name = nameOfTopLevelDeclaration(decl); - if (!name) continue; - - const top = getTopLevelDeclarationStatement(decl); - if (markSeenTop(top)) { - addExportToChanges(oldFile, top, name, changes, useEsModuleSyntax); - } - if (hasSyntacticModifier(decl, ModifierFlags.Default)) { - oldFileDefault = name; - } - else { - oldFileNamedImports.push(name.text); - } + if (hasSyntacticModifier(decl, ModifierFlags.Default)) { + oldFileDefault = name; } - }); + else { + oldFileNamedImports.push(name.text); + } + } + }); - append(copiedOldImports, makeImportOrRequire(oldFileDefault, oldFileNamedImports, removeFileExtension(getBaseFileName(oldFile.fileName)), useEsModuleSyntax, quotePreference)); - return copiedOldImports; - } + append(copiedOldImports, makeImportOrRequire(oldFileDefault, oldFileNamedImports, removeFileExtension(getBaseFileName(oldFile.fileName)), useEsModuleSyntax, quotePreference)); + return copiedOldImports; +} - function makeUniqueModuleName(moduleName: string, extension: string, inDirectory: string, host: LanguageServiceHost): string { - let newModuleName = moduleName; - for (let i = 1; ; i++) { - const name = combinePaths(inDirectory, newModuleName + extension); - if (!host.fileExists!(name)) return newModuleName; // TODO: GH#18217 - newModuleName = `${moduleName}.${i}`; - } +function makeUniqueModuleName(moduleName: string, extension: string, inDirectory: string, host: LanguageServiceHost): string { + let newModuleName = moduleName; + for (let i = 1; ; i++) { + const name = combinePaths(inDirectory, newModuleName + extension); + if (!host.fileExists!(name)) return newModuleName; // TODO: GH#18217 + newModuleName = `${moduleName}.${i}`; } +} - function getNewModuleName(movedSymbols: ReadonlySymbolSet): string { - return movedSymbols.forEachEntry(symbolNameNoDefault) || "newFile"; - } +function getNewModuleName(movedSymbols: ReadonlySymbolSet): string { + return movedSymbols.forEachEntry(symbolNameNoDefault) || "newFile"; +} - interface UsageInfo { - // Symbols whose declarations are moved from the old file to the new file. - readonly movedSymbols: ReadonlySymbolSet; +interface UsageInfo { + // Symbols whose declarations are moved from the old file to the new file. + readonly movedSymbols: ReadonlySymbolSet; - // Symbols declared in the old file that must be imported by the new file. (May not already be exported.) - readonly newFileImportsFromOldFile: ReadonlySymbolSet; - // Subset of movedSymbols that are still used elsewhere in the old file and must be imported back. - readonly oldFileImportsFromNewFile: ReadonlySymbolSet; + // Symbols declared in the old file that must be imported by the new file. (May not already be exported.) + readonly newFileImportsFromOldFile: ReadonlySymbolSet; + // Subset of movedSymbols that are still used elsewhere in the old file and must be imported back. + readonly oldFileImportsFromNewFile: ReadonlySymbolSet; - readonly oldImportsNeededByNewFile: ReadonlySymbolSet; - // Subset of oldImportsNeededByNewFile that are will no longer be used in the old file. - readonly unusedImportsFromOldFile: ReadonlySymbolSet; - } - function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker): UsageInfo { - const movedSymbols = new SymbolSet(); - const oldImportsNeededByNewFile = new SymbolSet(); - const newFileImportsFromOldFile = new SymbolSet(); + readonly oldImportsNeededByNewFile: ReadonlySymbolSet; + // Subset of oldImportsNeededByNewFile that are will no longer be used in the old file. + readonly unusedImportsFromOldFile: ReadonlySymbolSet; +} +function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker): UsageInfo { + const movedSymbols = new SymbolSet(); + const oldImportsNeededByNewFile = new SymbolSet(); + const newFileImportsFromOldFile = new SymbolSet(); - const containsJsx = find(toMove, statement => !!(statement.transformFlags & TransformFlags.ContainsJsx)); - const jsxNamespaceSymbol = getJsxNamespaceSymbol(containsJsx); - if (jsxNamespaceSymbol) { // Might not exist (e.g. in non-compiling code) - oldImportsNeededByNewFile.add(jsxNamespaceSymbol); - } + const containsJsx = find(toMove, statement => !!(statement.transformFlags & TransformFlags.ContainsJsx)); + const jsxNamespaceSymbol = getJsxNamespaceSymbol(containsJsx); + if (jsxNamespaceSymbol) { // Might not exist (e.g. in non-compiling code) + oldImportsNeededByNewFile.add(jsxNamespaceSymbol); + } - for (const statement of toMove) { - forEachTopLevelDeclaration(statement, decl => { - movedSymbols.add(Debug.checkDefined(isExpressionStatement(decl) ? checker.getSymbolAtLocation(decl.expression.left) : decl.symbol, "Need a symbol here")); - }); - } - for (const statement of toMove) { - forEachReference(statement, checker, symbol => { - if (!symbol.declarations) return; - for (const decl of symbol.declarations) { - if (isInImport(decl)) { - oldImportsNeededByNewFile.add(symbol); - } - else if (isTopLevelDeclaration(decl) && sourceFileOfTopLevelDeclaration(decl) === oldFile && !movedSymbols.has(symbol)) { - newFileImportsFromOldFile.add(symbol); - } + for (const statement of toMove) { + forEachTopLevelDeclaration(statement, decl => { + movedSymbols.add(Debug.checkDefined(isExpressionStatement(decl) ? checker.getSymbolAtLocation(decl.expression.left) : decl.symbol, "Need a symbol here")); + }); + } + for (const statement of toMove) { + forEachReference(statement, checker, symbol => { + if (!symbol.declarations) return; + for (const decl of symbol.declarations) { + if (isInImport(decl)) { + oldImportsNeededByNewFile.add(symbol); } - }); - } + else if (isTopLevelDeclaration(decl) && sourceFileOfTopLevelDeclaration(decl) === oldFile && !movedSymbols.has(symbol)) { + newFileImportsFromOldFile.add(symbol); + } + } + }); + } - const unusedImportsFromOldFile = oldImportsNeededByNewFile.clone(); + const unusedImportsFromOldFile = oldImportsNeededByNewFile.clone(); - const oldFileImportsFromNewFile = new SymbolSet(); - for (const statement of oldFile.statements) { - if (contains(toMove, statement)) continue; + const oldFileImportsFromNewFile = new SymbolSet(); + for (const statement of oldFile.statements) { + if (contains(toMove, statement)) continue; - // jsxNamespaceSymbol will only be set iff it is in oldImportsNeededByNewFile. - if (jsxNamespaceSymbol && !!(statement.transformFlags & TransformFlags.ContainsJsx)) { - unusedImportsFromOldFile.delete(jsxNamespaceSymbol); - } + // jsxNamespaceSymbol will only be set iff it is in oldImportsNeededByNewFile. + if (jsxNamespaceSymbol && !!(statement.transformFlags & TransformFlags.ContainsJsx)) { + unusedImportsFromOldFile.delete(jsxNamespaceSymbol); + } - forEachReference(statement, checker, symbol => { - if (movedSymbols.has(symbol)) oldFileImportsFromNewFile.add(symbol); - unusedImportsFromOldFile.delete(symbol); - }); + forEachReference(statement, checker, symbol => { + if (movedSymbols.has(symbol)) oldFileImportsFromNewFile.add(symbol); + unusedImportsFromOldFile.delete(symbol); + }); + } + + return { movedSymbols, newFileImportsFromOldFile, oldFileImportsFromNewFile, oldImportsNeededByNewFile, unusedImportsFromOldFile }; + + function getJsxNamespaceSymbol(containsJsx: Node | undefined) { + if (containsJsx === undefined) { + return undefined; } - return { movedSymbols, newFileImportsFromOldFile, oldFileImportsFromNewFile, oldImportsNeededByNewFile, unusedImportsFromOldFile }; + const jsxNamespace = checker.getJsxNamespace(containsJsx); - function getJsxNamespaceSymbol(containsJsx: Node | undefined) { - if (containsJsx === undefined) { - return undefined; - } + // Strictly speaking, this could resolve to a symbol other than the JSX namespace. + // This will produce erroneous output (probably, an incorrectly copied import) but + // is expected to be very rare and easily reversible. + const jsxNamespaceSymbol = checker.resolveName(jsxNamespace, containsJsx, SymbolFlags.Namespace, /*excludeGlobals*/ true); - const jsxNamespace = checker.getJsxNamespace(containsJsx); + return !!jsxNamespaceSymbol && some(jsxNamespaceSymbol.declarations, isInImport) + ? jsxNamespaceSymbol + : undefined; + } +} - // Strictly speaking, this could resolve to a symbol other than the JSX namespace. - // This will produce erroneous output (probably, an incorrectly copied import) but - // is expected to be very rare and easily reversible. - const jsxNamespaceSymbol = checker.resolveName(jsxNamespace, containsJsx, SymbolFlags.Namespace, /*excludeGlobals*/ true); +// Below should all be utilities + +function isInImport(decl: Declaration) { + switch (decl.kind) { + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + return true; + case SyntaxKind.VariableDeclaration: + return isVariableDeclarationInImport(decl as VariableDeclaration); + case SyntaxKind.BindingElement: + return isVariableDeclaration(decl.parent.parent) && isVariableDeclarationInImport(decl.parent.parent); + default: + return false; + } +} +function isVariableDeclarationInImport(decl: VariableDeclaration) { + return isSourceFile(decl.parent.parent.parent) && + !!decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true); +} - return !!jsxNamespaceSymbol && some(jsxNamespaceSymbol.declarations, isInImport) - ? jsxNamespaceSymbol +function filterImport(i: SupportedImport, moduleSpecifier: StringLiteralLike, keep: (name: Identifier) => boolean): SupportedImportStatement | undefined { + switch (i.kind) { + case SyntaxKind.ImportDeclaration: { + const clause = i.importClause; + if (!clause) return undefined; + const defaultImport = clause.name && keep(clause.name) ? clause.name : undefined; + const namedBindings = clause.namedBindings && filterNamedBindings(clause.namedBindings, keep); + return defaultImport || namedBindings + ? factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, defaultImport, namedBindings), moduleSpecifier, /*assertClause*/ undefined) : undefined; } - } - - // Below should all be utilities - - function isInImport(decl: Declaration) { - switch (decl.kind) { - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - return true; - case SyntaxKind.VariableDeclaration: - return isVariableDeclarationInImport(decl as VariableDeclaration); - case SyntaxKind.BindingElement: - return isVariableDeclaration(decl.parent.parent) && isVariableDeclarationInImport(decl.parent.parent); - default: - return false; + case SyntaxKind.ImportEqualsDeclaration: + return keep(i.name) ? i : undefined; + case SyntaxKind.VariableDeclaration: { + const name = filterBindingName(i.name, keep); + return name ? makeVariableStatement(name, i.type, createRequireCall(moduleSpecifier), i.parent.flags) : undefined; } + default: + return Debug.assertNever(i, `Unexpected import kind ${(i as SupportedImport).kind}`); } - function isVariableDeclarationInImport(decl: VariableDeclaration) { - return isSourceFile(decl.parent.parent.parent) && - !!decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true); - } - - function filterImport(i: SupportedImport, moduleSpecifier: StringLiteralLike, keep: (name: Identifier) => boolean): SupportedImportStatement | undefined { - switch (i.kind) { - case SyntaxKind.ImportDeclaration: { - const clause = i.importClause; - if (!clause) return undefined; - const defaultImport = clause.name && keep(clause.name) ? clause.name : undefined; - const namedBindings = clause.namedBindings && filterNamedBindings(clause.namedBindings, keep); - return defaultImport || namedBindings - ? factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, defaultImport, namedBindings), moduleSpecifier, /*assertClause*/ undefined) - : undefined; - } - case SyntaxKind.ImportEqualsDeclaration: - return keep(i.name) ? i : undefined; - case SyntaxKind.VariableDeclaration: { - const name = filterBindingName(i.name, keep); - return name ? makeVariableStatement(name, i.type, createRequireCall(moduleSpecifier), i.parent.flags) : undefined; - } - default: - return Debug.assertNever(i, `Unexpected import kind ${(i as SupportedImport).kind}`); +} +function filterNamedBindings(namedBindings: NamedImportBindings, keep: (name: Identifier) => boolean): NamedImportBindings | undefined { + if (namedBindings.kind === SyntaxKind.NamespaceImport) { + return keep(namedBindings.name) ? namedBindings : undefined; + } + else { + const newElements = namedBindings.elements.filter(e => keep(e.name)); + return newElements.length ? factory.createNamedImports(newElements) : undefined; + } +} +function filterBindingName(name: BindingName, keep: (name: Identifier) => boolean): BindingName | undefined { + switch (name.kind) { + case SyntaxKind.Identifier: + return keep(name) ? name : undefined; + case SyntaxKind.ArrayBindingPattern: + return name; + case SyntaxKind.ObjectBindingPattern: { + // We can't handle nested destructurings or property names well here, so just copy them all. + const newElements = name.elements.filter(prop => prop.propertyName || !isIdentifier(prop.name) || keep(prop.name)); + return newElements.length ? factory.createObjectBindingPattern(newElements) : undefined; } } - function filterNamedBindings(namedBindings: NamedImportBindings, keep: (name: Identifier) => boolean): NamedImportBindings | undefined { - if (namedBindings.kind === SyntaxKind.NamespaceImport) { - return keep(namedBindings.name) ? namedBindings : undefined; +} + +function forEachReference(node: Node, checker: TypeChecker, onReference: (s: Symbol) => void) { + node.forEachChild(function cb(node) { + if (isIdentifier(node) && !isDeclarationName(node)) { + const sym = checker.getSymbolAtLocation(node); + if (sym) onReference(sym); } else { - const newElements = namedBindings.elements.filter(e => keep(e.name)); - return newElements.length ? factory.createNamedImports(newElements) : undefined; + node.forEachChild(cb); } + }); +} + +interface ReadonlySymbolSet { + has(symbol: Symbol): boolean; + forEach(cb: (symbol: Symbol) => void): void; + forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined; +} +class SymbolSet implements ReadonlySymbolSet { + private map = new Map(); + add(symbol: Symbol): void { + this.map.set(String(getSymbolId(symbol)), symbol); } - function filterBindingName(name: BindingName, keep: (name: Identifier) => boolean): BindingName | undefined { - switch (name.kind) { - case SyntaxKind.Identifier: - return keep(name) ? name : undefined; - case SyntaxKind.ArrayBindingPattern: - return name; - case SyntaxKind.ObjectBindingPattern: { - // We can't handle nested destructurings or property names well here, so just copy them all. - const newElements = name.elements.filter(prop => prop.propertyName || !isIdentifier(prop.name) || keep(prop.name)); - return newElements.length ? factory.createObjectBindingPattern(newElements) : undefined; - } - } + has(symbol: Symbol): boolean { + return this.map.has(String(getSymbolId(symbol))); } - - function forEachReference(node: Node, checker: TypeChecker, onReference: (s: Symbol) => void) { - node.forEachChild(function cb(node) { - if (isIdentifier(node) && !isDeclarationName(node)) { - const sym = checker.getSymbolAtLocation(node); - if (sym) onReference(sym); - } - else { - node.forEachChild(cb); - } - }); + delete(symbol: Symbol): void { + this.map.delete(String(getSymbolId(symbol))); } - - interface ReadonlySymbolSet { - has(symbol: Symbol): boolean; - forEach(cb: (symbol: Symbol) => void): void; - forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined; + forEach(cb: (symbol: Symbol) => void): void { + this.map.forEach(cb); } - class SymbolSet implements ReadonlySymbolSet { - private map = new Map(); - add(symbol: Symbol): void { - this.map.set(String(getSymbolId(symbol)), symbol); - } - has(symbol: Symbol): boolean { - return this.map.has(String(getSymbolId(symbol))); - } - delete(symbol: Symbol): void { - this.map.delete(String(getSymbolId(symbol))); - } - forEach(cb: (symbol: Symbol) => void): void { - this.map.forEach(cb); - } - forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined { - return forEachEntry(this.map, cb); - } - clone(): SymbolSet { - const clone = new SymbolSet(); - copyEntries(this.map, clone.map); - return clone; - } + forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined { + return forEachEntry(this.map, cb); } + clone(): SymbolSet { + const clone = new SymbolSet(); + copyEntries(this.map, clone.map); + return clone; + } +} - type TopLevelExpressionStatement = ExpressionStatement & { expression: BinaryExpression & { left: PropertyAccessExpression } }; // 'exports.x = ...' - type NonVariableTopLevelDeclaration = - | FunctionDeclaration - | ClassDeclaration - | EnumDeclaration - | TypeAliasDeclaration - | InterfaceDeclaration - | ModuleDeclaration - | TopLevelExpressionStatement - | ImportEqualsDeclaration; - type TopLevelDeclarationStatement = NonVariableTopLevelDeclaration | VariableStatement; - interface TopLevelVariableDeclaration extends VariableDeclaration { parent: VariableDeclarationList & { parent: VariableStatement; }; } - type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration | BindingElement; - function isTopLevelDeclaration(node: Node): node is TopLevelDeclaration { - return isNonVariableTopLevelDeclaration(node) && isSourceFile(node.parent) || isVariableDeclaration(node) && isSourceFile(node.parent.parent.parent); - } - - function sourceFileOfTopLevelDeclaration(node: TopLevelDeclaration): Node { - return isVariableDeclaration(node) ? node.parent.parent.parent : node.parent; - } - - function isTopLevelDeclarationStatement(node: Node): node is TopLevelDeclarationStatement { - Debug.assert(isSourceFile(node.parent), "Node parent should be a SourceFile"); - return isNonVariableTopLevelDeclaration(node) || isVariableStatement(node); - } - - function isNonVariableTopLevelDeclaration(node: Node): node is NonVariableTopLevelDeclaration { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - return true; - default: - return false; - } +type TopLevelExpressionStatement = ExpressionStatement & { expression: BinaryExpression & { left: PropertyAccessExpression } }; // 'exports.x = ...' +type NonVariableTopLevelDeclaration = + | FunctionDeclaration + | ClassDeclaration + | EnumDeclaration + | TypeAliasDeclaration + | InterfaceDeclaration + | ModuleDeclaration + | TopLevelExpressionStatement + | ImportEqualsDeclaration; +type TopLevelDeclarationStatement = NonVariableTopLevelDeclaration | VariableStatement; +interface TopLevelVariableDeclaration extends VariableDeclaration { parent: VariableDeclarationList & { parent: VariableStatement; }; } +type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration | BindingElement; +function isTopLevelDeclaration(node: Node): node is TopLevelDeclaration { + return isNonVariableTopLevelDeclaration(node) && isSourceFile(node.parent) || isVariableDeclaration(node) && isSourceFile(node.parent.parent.parent); +} + +function sourceFileOfTopLevelDeclaration(node: TopLevelDeclaration): Node { + return isVariableDeclaration(node) ? node.parent.parent.parent : node.parent; +} + +function isTopLevelDeclarationStatement(node: Node): node is TopLevelDeclarationStatement { + Debug.assert(isSourceFile(node.parent), "Node parent should be a SourceFile"); + return isNonVariableTopLevelDeclaration(node) || isVariableStatement(node); +} + +function isNonVariableTopLevelDeclaration(node: Node): node is NonVariableTopLevelDeclaration { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return true; + default: + return false; } +} - function forEachTopLevelDeclaration(statement: Statement, cb: (node: TopLevelDeclaration) => T): T | undefined { - switch (statement.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - return cb(statement as FunctionDeclaration | ClassDeclaration | EnumDeclaration | ModuleDeclaration | TypeAliasDeclaration | InterfaceDeclaration | ImportEqualsDeclaration); - - case SyntaxKind.VariableStatement: - return firstDefined((statement as VariableStatement).declarationList.declarations, decl => forEachTopLevelDeclarationInBindingName(decl.name, cb)); - - case SyntaxKind.ExpressionStatement: { - const { expression } = statement as ExpressionStatement; - return isBinaryExpression(expression) && getAssignmentDeclarationKind(expression) === AssignmentDeclarationKind.ExportsProperty - ? cb(statement as TopLevelExpressionStatement) - : undefined; - } +function forEachTopLevelDeclaration(statement: Statement, cb: (node: TopLevelDeclaration) => T): T | undefined { + switch (statement.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return cb(statement as FunctionDeclaration | ClassDeclaration | EnumDeclaration | ModuleDeclaration | TypeAliasDeclaration | InterfaceDeclaration | ImportEqualsDeclaration); + + case SyntaxKind.VariableStatement: + return firstDefined((statement as VariableStatement).declarationList.declarations, decl => forEachTopLevelDeclarationInBindingName(decl.name, cb)); + + case SyntaxKind.ExpressionStatement: { + const { expression } = statement as ExpressionStatement; + return isBinaryExpression(expression) && getAssignmentDeclarationKind(expression) === AssignmentDeclarationKind.ExportsProperty + ? cb(statement as TopLevelExpressionStatement) + : undefined; } } - function forEachTopLevelDeclarationInBindingName(name: BindingName, cb: (node: TopLevelDeclaration) => T): T | undefined { - switch (name.kind) { - case SyntaxKind.Identifier: - return cb(cast(name.parent, (x): x is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(x) || isBindingElement(x))); - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ObjectBindingPattern: - return firstDefined(name.elements, em => isOmittedExpression(em) ? undefined : forEachTopLevelDeclarationInBindingName(em.name, cb)); - default: - return Debug.assertNever(name, `Unexpected name kind ${(name as BindingName).kind}`); - } +} +function forEachTopLevelDeclarationInBindingName(name: BindingName, cb: (node: TopLevelDeclaration) => T): T | undefined { + switch (name.kind) { + case SyntaxKind.Identifier: + return cb(cast(name.parent, (x): x is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(x) || isBindingElement(x))); + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ObjectBindingPattern: + return firstDefined(name.elements, em => isOmittedExpression(em) ? undefined : forEachTopLevelDeclarationInBindingName(em.name, cb)); + default: + return Debug.assertNever(name, `Unexpected name kind ${(name as BindingName).kind}`); } +} - function nameOfTopLevelDeclaration(d: TopLevelDeclaration): Identifier | undefined { - return isExpressionStatement(d) ? tryCast(d.expression.left.name, isIdentifier) : tryCast(d.name, isIdentifier); +function nameOfTopLevelDeclaration(d: TopLevelDeclaration): Identifier | undefined { + return isExpressionStatement(d) ? tryCast(d.expression.left.name, isIdentifier) : tryCast(d.name, isIdentifier); +} + +function getTopLevelDeclarationStatement(d: TopLevelDeclaration): TopLevelDeclarationStatement { + switch (d.kind) { + case SyntaxKind.VariableDeclaration: + return d.parent.parent; + case SyntaxKind.BindingElement: + return getTopLevelDeclarationStatement( + cast(d.parent.parent, (p): p is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(p) || isBindingElement(p))); + default: + return d; } +} - function getTopLevelDeclarationStatement(d: TopLevelDeclaration): TopLevelDeclarationStatement { - switch (d.kind) { - case SyntaxKind.VariableDeclaration: - return d.parent.parent; - case SyntaxKind.BindingElement: - return getTopLevelDeclarationStatement( - cast(d.parent.parent, (p): p is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(p) || isBindingElement(p))); - default: - return d; - } +function addExportToChanges(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, name: Identifier, changes: textChanges.ChangeTracker, useEs6Exports: boolean): void { + if (isExported(sourceFile, decl, useEs6Exports, name)) return; + if (useEs6Exports) { + if (!isExpressionStatement(decl)) changes.insertExportModifier(sourceFile, decl); } + else { + const names = getNamesToExportInCommonJS(decl); + if (names.length !== 0) changes.insertNodesAfter(sourceFile, decl, names.map(createExportAssignment)); + } +} - function addExportToChanges(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, name: Identifier, changes: textChanges.ChangeTracker, useEs6Exports: boolean): void { - if (isExported(sourceFile, decl, useEs6Exports, name)) return; - if (useEs6Exports) { - if (!isExpressionStatement(decl)) changes.insertExportModifier(sourceFile, decl); - } - else { - const names = getNamesToExportInCommonJS(decl); - if (names.length !== 0) changes.insertNodesAfter(sourceFile, decl, names.map(createExportAssignment)); - } +function isExported(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, useEs6Exports: boolean, name?: Identifier): boolean { + if (useEs6Exports) { + return !isExpressionStatement(decl) && hasSyntacticModifier(decl, ModifierFlags.Export) || !!(name && sourceFile.symbol.exports?.has(name.escapedText)); } + return getNamesToExportInCommonJS(decl).some(name => sourceFile.symbol.exports!.has(escapeLeadingUnderscores(name))); +} - function isExported(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, useEs6Exports: boolean, name?: Identifier): boolean { - if (useEs6Exports) { - return !isExpressionStatement(decl) && hasSyntacticModifier(decl, ModifierFlags.Export) || !!(name && sourceFile.symbol.exports?.has(name.escapedText)); - } - return getNamesToExportInCommonJS(decl).some(name => sourceFile.symbol.exports!.has(escapeLeadingUnderscores(name))); - } - - function addExport(decl: TopLevelDeclarationStatement, useEs6Exports: boolean): readonly Statement[] | undefined { - return useEs6Exports ? [addEs6Export(decl)] : addCommonjsExport(decl); - } - function addEs6Export(d: TopLevelDeclarationStatement): TopLevelDeclarationStatement { - const modifiers = concatenate([factory.createModifier(SyntaxKind.ExportKeyword)], d.modifiers); - switch (d.kind) { - case SyntaxKind.FunctionDeclaration: - return factory.updateFunctionDeclaration(d, d.decorators, modifiers, d.asteriskToken, d.name, d.typeParameters, d.parameters, d.type, d.body); - case SyntaxKind.ClassDeclaration: - return factory.updateClassDeclaration(d, d.decorators, modifiers, d.name, d.typeParameters, d.heritageClauses, d.members); - case SyntaxKind.VariableStatement: - return factory.updateVariableStatement(d, modifiers, d.declarationList); - case SyntaxKind.ModuleDeclaration: - return factory.updateModuleDeclaration(d, d.decorators, modifiers, d.name, d.body); - case SyntaxKind.EnumDeclaration: - return factory.updateEnumDeclaration(d, d.decorators, modifiers, d.name, d.members); - case SyntaxKind.TypeAliasDeclaration: - return factory.updateTypeAliasDeclaration(d, d.decorators, modifiers, d.name, d.typeParameters, d.type); - case SyntaxKind.InterfaceDeclaration: - return factory.updateInterfaceDeclaration(d, d.decorators, modifiers, d.name, d.typeParameters, d.heritageClauses, d.members); - case SyntaxKind.ImportEqualsDeclaration: - return factory.updateImportEqualsDeclaration(d, d.decorators, modifiers, d.isTypeOnly, d.name, d.moduleReference); - case SyntaxKind.ExpressionStatement: - return Debug.fail(); // Shouldn't try to add 'export' keyword to `exports.x = ...` - default: - return Debug.assertNever(d, `Unexpected declaration kind ${(d as DeclarationStatement).kind}`); - } +function addExport(decl: TopLevelDeclarationStatement, useEs6Exports: boolean): readonly Statement[] | undefined { + return useEs6Exports ? [addEs6Export(decl)] : addCommonjsExport(decl); +} +function addEs6Export(d: TopLevelDeclarationStatement): TopLevelDeclarationStatement { + const modifiers = concatenate([factory.createModifier(SyntaxKind.ExportKeyword)], d.modifiers); + switch (d.kind) { + case SyntaxKind.FunctionDeclaration: + return factory.updateFunctionDeclaration(d, d.decorators, modifiers, d.asteriskToken, d.name, d.typeParameters, d.parameters, d.type, d.body); + case SyntaxKind.ClassDeclaration: + return factory.updateClassDeclaration(d, d.decorators, modifiers, d.name, d.typeParameters, d.heritageClauses, d.members); + case SyntaxKind.VariableStatement: + return factory.updateVariableStatement(d, modifiers, d.declarationList); + case SyntaxKind.ModuleDeclaration: + return factory.updateModuleDeclaration(d, d.decorators, modifiers, d.name, d.body); + case SyntaxKind.EnumDeclaration: + return factory.updateEnumDeclaration(d, d.decorators, modifiers, d.name, d.members); + case SyntaxKind.TypeAliasDeclaration: + return factory.updateTypeAliasDeclaration(d, d.decorators, modifiers, d.name, d.typeParameters, d.type); + case SyntaxKind.InterfaceDeclaration: + return factory.updateInterfaceDeclaration(d, d.decorators, modifiers, d.name, d.typeParameters, d.heritageClauses, d.members); + case SyntaxKind.ImportEqualsDeclaration: + return factory.updateImportEqualsDeclaration(d, d.decorators, modifiers, d.isTypeOnly, d.name, d.moduleReference); + case SyntaxKind.ExpressionStatement: + return Debug.fail(); // Shouldn't try to add 'export' keyword to `exports.x = ...` + default: + return Debug.assertNever(d, `Unexpected declaration kind ${(d as DeclarationStatement).kind}`); } - function addCommonjsExport(decl: TopLevelDeclarationStatement): readonly Statement[] | undefined { - return [decl, ...getNamesToExportInCommonJS(decl).map(createExportAssignment)]; - } - function getNamesToExportInCommonJS(decl: TopLevelDeclarationStatement): readonly string[] { - switch (decl.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - return [decl.name!.text]; // TODO: GH#18217 - case SyntaxKind.VariableStatement: - return mapDefined(decl.declarationList.declarations, d => isIdentifier(d.name) ? d.name.text : undefined); - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - return emptyArray; - case SyntaxKind.ExpressionStatement: - return Debug.fail("Can't export an ExpressionStatement"); // Shouldn't try to add 'export' keyword to `exports.x = ...` - default: - return Debug.assertNever(decl, `Unexpected decl kind ${(decl as TopLevelDeclarationStatement).kind}`); - } +} +function addCommonjsExport(decl: TopLevelDeclarationStatement): readonly Statement[] | undefined { + return [decl, ...getNamesToExportInCommonJS(decl).map(createExportAssignment)]; +} +function getNamesToExportInCommonJS(decl: TopLevelDeclarationStatement): readonly string[] { + switch (decl.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + return [decl.name!.text]; // TODO: GH#18217 + case SyntaxKind.VariableStatement: + return mapDefined(decl.declarationList.declarations, d => isIdentifier(d.name) ? d.name.text : undefined); + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return emptyArray; + case SyntaxKind.ExpressionStatement: + return Debug.fail("Can't export an ExpressionStatement"); // Shouldn't try to add 'export' keyword to `exports.x = ...` + default: + return Debug.assertNever(decl, `Unexpected decl kind ${(decl as TopLevelDeclarationStatement).kind}`); } +} - /** Creates `exports.x = x;` */ - function createExportAssignment(name: string): Statement { - return factory.createExpressionStatement( - factory.createBinaryExpression( - factory.createPropertyAccessExpression(factory.createIdentifier("exports"), factory.createIdentifier(name)), - SyntaxKind.EqualsToken, - factory.createIdentifier(name))); - } +/** Creates `exports.x = x;` */ +function createExportAssignment(name: string): Statement { + return factory.createExpressionStatement( + factory.createBinaryExpression( + factory.createPropertyAccessExpression(factory.createIdentifier("exports"), factory.createIdentifier(name)), + SyntaxKind.EqualsToken, + factory.createIdentifier(name))); +} } diff --git a/src/services/rename.ts b/src/services/rename.ts index 0d33f1f2152f9..ca429ae4abbe8 100644 --- a/src/services/rename.ts +++ b/src/services/rename.ts @@ -1,128 +1,128 @@ /* @internal */ namespace ts.Rename { - export function getRenameInfo(program: Program, sourceFile: SourceFile, position: number, options?: RenameInfoOptions): RenameInfo { - const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position)); - if (nodeIsEligibleForRename(node)) { - const renameInfo = getRenameInfoForNode(node, program.getTypeChecker(), sourceFile, program, options); - if (renameInfo) { - return renameInfo; - } +export function getRenameInfo(program: Program, sourceFile: SourceFile, position: number, options?: RenameInfoOptions): RenameInfo { + const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position)); + if (nodeIsEligibleForRename(node)) { + const renameInfo = getRenameInfoForNode(node, program.getTypeChecker(), sourceFile, program, options); + if (renameInfo) { + return renameInfo; } - return getRenameInfoError(Diagnostics.You_cannot_rename_this_element); } + return getRenameInfoError(Diagnostics.You_cannot_rename_this_element); +} - function getRenameInfoForNode(node: Node, typeChecker: TypeChecker, sourceFile: SourceFile, program: Program, options?: RenameInfoOptions): RenameInfo | undefined { - const symbol = typeChecker.getSymbolAtLocation(node); - if (!symbol) { - if (isStringLiteralLike(node)) { - const type = getContextualTypeFromParentOrAncestorTypeNode(node, typeChecker); - if (type && ((type.flags & TypeFlags.StringLiteral) || ( - (type.flags & TypeFlags.Union) && every((type as UnionType).types, type => !!(type.flags & TypeFlags.StringLiteral)) - ))) { - return getRenameInfoSuccess(node.text, node.text, ScriptElementKind.string, "", node, sourceFile); - } - } - else if (isLabelName(node)) { - const name = getTextOfNode(node); - return getRenameInfoSuccess(name, name, ScriptElementKind.label, ScriptElementKindModifier.none, node, sourceFile); +function getRenameInfoForNode(node: Node, typeChecker: TypeChecker, sourceFile: SourceFile, program: Program, options?: RenameInfoOptions): RenameInfo | undefined { + const symbol = typeChecker.getSymbolAtLocation(node); + if (!symbol) { + if (isStringLiteralLike(node)) { + const type = getContextualTypeFromParentOrAncestorTypeNode(node, typeChecker); + if (type && ((type.flags & TypeFlags.StringLiteral) || ( + (type.flags & TypeFlags.Union) && every((type as UnionType).types, type => !!(type.flags & TypeFlags.StringLiteral)) + ))) { + return getRenameInfoSuccess(node.text, node.text, ScriptElementKind.string, "", node, sourceFile); } - return undefined; } - // Only allow a symbol to be renamed if it actually has at least one declaration. - const { declarations } = symbol; - if (!declarations || declarations.length === 0) return; - - // Disallow rename for elements that are defined in the standard TypeScript library. - if (declarations.some(declaration => isDefinedInLibraryFile(program, declaration))) { - return getRenameInfoError(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library); - } - - // Cannot rename `default` as in `import { default as foo } from "./someModule"; - if (isIdentifier(node) && node.originalKeywordKind === SyntaxKind.DefaultKeyword && symbol.parent && symbol.parent.flags & SymbolFlags.Module) { - return undefined; + else if (isLabelName(node)) { + const name = getTextOfNode(node); + return getRenameInfoSuccess(name, name, ScriptElementKind.label, ScriptElementKindModifier.none, node, sourceFile); } + return undefined; + } + // Only allow a symbol to be renamed if it actually has at least one declaration. + const { declarations } = symbol; + if (!declarations || declarations.length === 0) return; - if (isStringLiteralLike(node) && tryGetImportFromModuleSpecifier(node)) { - return options && options.allowRenameOfImportPath ? getRenameInfoForModule(node, sourceFile, symbol) : undefined; - } + // Disallow rename for elements that are defined in the standard TypeScript library. + if (declarations.some(declaration => isDefinedInLibraryFile(program, declaration))) { + return getRenameInfoError(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library); + } - const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node); - const specifierName = (isImportOrExportSpecifierName(node) || isStringOrNumericLiteralLike(node) && node.parent.kind === SyntaxKind.ComputedPropertyName) - ? stripQuotes(getTextOfIdentifierOrLiteral(node)) - : undefined; - const displayName = specifierName || typeChecker.symbolToString(symbol); - const fullDisplayName = specifierName || typeChecker.getFullyQualifiedName(symbol); - return getRenameInfoSuccess(displayName, fullDisplayName, kind, SymbolDisplay.getSymbolModifiers(typeChecker,symbol), node, sourceFile); + // Cannot rename `default` as in `import { default as foo } from "./someModule"; + if (isIdentifier(node) && node.originalKeywordKind === SyntaxKind.DefaultKeyword && symbol.parent && symbol.parent.flags & SymbolFlags.Module) { + return undefined; } - function isDefinedInLibraryFile(program: Program, declaration: Node) { - const sourceFile = declaration.getSourceFile(); - return program.isSourceFileDefaultLibrary(sourceFile) && fileExtensionIs(sourceFile.fileName, Extension.Dts); + if (isStringLiteralLike(node) && tryGetImportFromModuleSpecifier(node)) { + return options && options.allowRenameOfImportPath ? getRenameInfoForModule(node, sourceFile, symbol) : undefined; } - function getRenameInfoForModule(node: StringLiteralLike, sourceFile: SourceFile, moduleSymbol: Symbol): RenameInfo | undefined { - if (!isExternalModuleNameRelative(node.text)) { - return getRenameInfoError(Diagnostics.You_cannot_rename_a_module_via_a_global_import); - } + const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node); + const specifierName = (isImportOrExportSpecifierName(node) || isStringOrNumericLiteralLike(node) && node.parent.kind === SyntaxKind.ComputedPropertyName) + ? stripQuotes(getTextOfIdentifierOrLiteral(node)) + : undefined; + const displayName = specifierName || typeChecker.symbolToString(symbol); + const fullDisplayName = specifierName || typeChecker.getFullyQualifiedName(symbol); + return getRenameInfoSuccess(displayName, fullDisplayName, kind, SymbolDisplay.getSymbolModifiers(typeChecker,symbol), node, sourceFile); +} - const moduleSourceFile = moduleSymbol.declarations && find(moduleSymbol.declarations, isSourceFile); - if (!moduleSourceFile) return undefined; - const withoutIndex = endsWith(node.text, "/index") || endsWith(node.text, "/index.js") ? undefined : tryRemoveSuffix(removeFileExtension(moduleSourceFile.fileName), "/index"); - const name = withoutIndex === undefined ? moduleSourceFile.fileName : withoutIndex; - const kind = withoutIndex === undefined ? ScriptElementKind.moduleElement : ScriptElementKind.directory; - const indexAfterLastSlash = node.text.lastIndexOf("/") + 1; - // Span should only be the last component of the path. + 1 to account for the quote character. - const triggerSpan = createTextSpan(node.getStart(sourceFile) + 1 + indexAfterLastSlash, node.text.length - indexAfterLastSlash); - return { - canRename: true, - fileToRename: name, - kind, - displayName: name, - fullDisplayName: name, - kindModifiers: ScriptElementKindModifier.none, - triggerSpan, - }; - } +function isDefinedInLibraryFile(program: Program, declaration: Node) { + const sourceFile = declaration.getSourceFile(); + return program.isSourceFileDefaultLibrary(sourceFile) && fileExtensionIs(sourceFile.fileName, Extension.Dts); +} - function getRenameInfoSuccess(displayName: string, fullDisplayName: string, kind: ScriptElementKind, kindModifiers: string, node: Node, sourceFile: SourceFile): RenameInfoSuccess { - return { - canRename: true, - fileToRename: undefined, - kind, - displayName, - fullDisplayName, - kindModifiers, - triggerSpan: createTriggerSpanForNode(node, sourceFile) - }; +function getRenameInfoForModule(node: StringLiteralLike, sourceFile: SourceFile, moduleSymbol: Symbol): RenameInfo | undefined { + if (!isExternalModuleNameRelative(node.text)) { + return getRenameInfoError(Diagnostics.You_cannot_rename_a_module_via_a_global_import); } - function getRenameInfoError(diagnostic: DiagnosticMessage): RenameInfoFailure { - return { canRename: false, localizedErrorMessage: getLocaleSpecificMessage(diagnostic) }; - } + const moduleSourceFile = moduleSymbol.declarations && find(moduleSymbol.declarations, isSourceFile); + if (!moduleSourceFile) return undefined; + const withoutIndex = endsWith(node.text, "/index") || endsWith(node.text, "/index.js") ? undefined : tryRemoveSuffix(removeFileExtension(moduleSourceFile.fileName), "/index"); + const name = withoutIndex === undefined ? moduleSourceFile.fileName : withoutIndex; + const kind = withoutIndex === undefined ? ScriptElementKind.moduleElement : ScriptElementKind.directory; + const indexAfterLastSlash = node.text.lastIndexOf("/") + 1; + // Span should only be the last component of the path. + 1 to account for the quote character. + const triggerSpan = createTextSpan(node.getStart(sourceFile) + 1 + indexAfterLastSlash, node.text.length - indexAfterLastSlash); + return { + canRename: true, + fileToRename: name, + kind, + displayName: name, + fullDisplayName: name, + kindModifiers: ScriptElementKindModifier.none, + triggerSpan, + }; +} - function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) { - let start = node.getStart(sourceFile); - let width = node.getWidth(sourceFile); - if (isStringLiteralLike(node)) { - // Exclude the quotes - start += 1; - width -= 2; - } - return createTextSpan(start, width); +function getRenameInfoSuccess(displayName: string, fullDisplayName: string, kind: ScriptElementKind, kindModifiers: string, node: Node, sourceFile: SourceFile): RenameInfoSuccess { + return { + canRename: true, + fileToRename: undefined, + kind, + displayName, + fullDisplayName, + kindModifiers, + triggerSpan: createTriggerSpanForNode(node, sourceFile) + }; +} + +function getRenameInfoError(diagnostic: DiagnosticMessage): RenameInfoFailure { + return { canRename: false, localizedErrorMessage: getLocaleSpecificMessage(diagnostic) }; +} + +function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) { + let start = node.getStart(sourceFile); + let width = node.getWidth(sourceFile); + if (isStringLiteralLike(node)) { + // Exclude the quotes + start += 1; + width -= 2; } + return createTextSpan(start, width); +} - export function nodeIsEligibleForRename(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PrivateIdentifier: - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.ThisKeyword: - return true; - case SyntaxKind.NumericLiteral: - return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral); - default: - return false; - } +export function nodeIsEligibleForRename(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.ThisKeyword: + return true; + case SyntaxKind.NumericLiteral: + return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral); + default: + return false; } } +} diff --git a/src/services/services.ts b/src/services/services.ts index c9c54c0234ec6..06367be07f3ae 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1,2823 +1,2823 @@ namespace ts { - /** The version of the language service API */ - export const servicesVersion = "0.8"; - - function createNode(kind: TKind, pos: number, end: number, parent: Node): NodeObject | TokenObject | IdentifierObject | PrivateIdentifierObject { - const node = isNodeKind(kind) ? new NodeObject(kind, pos, end) : - kind === SyntaxKind.Identifier ? new IdentifierObject(SyntaxKind.Identifier, pos, end) : - kind === SyntaxKind.PrivateIdentifier ? new PrivateIdentifierObject(SyntaxKind.PrivateIdentifier, pos, end) : - new TokenObject(kind, pos, end); - node.parent = parent; - node.flags = parent.flags & NodeFlags.ContextFlags; - return node; - } - - class NodeObject implements Node { - public kind: SyntaxKind; - public pos: number; - public end: number; - public flags: NodeFlags; - public modifierFlagsCache: ModifierFlags; - public transformFlags: TransformFlags; - public parent: Node; - public symbol!: Symbol; // Actually optional, but it was too annoying to access `node.symbol!` everywhere since in many cases we know it must be defined - public jsDoc?: JSDoc[]; - public original?: Node; - private _children: Node[] | undefined; - - constructor(kind: SyntaxKind, pos: number, end: number) { - this.pos = pos; - this.end = end; - this.flags = NodeFlags.None; - this.modifierFlagsCache = ModifierFlags.None; - this.transformFlags = TransformFlags.None; - this.parent = undefined!; - this.kind = kind; - } +/** The version of the language service API */ +export const servicesVersion = "0.8"; + +function createNode(kind: TKind, pos: number, end: number, parent: Node): NodeObject | TokenObject | IdentifierObject | PrivateIdentifierObject { + const node = isNodeKind(kind) ? new NodeObject(kind, pos, end) : + kind === SyntaxKind.Identifier ? new IdentifierObject(SyntaxKind.Identifier, pos, end) : + kind === SyntaxKind.PrivateIdentifier ? new PrivateIdentifierObject(SyntaxKind.PrivateIdentifier, pos, end) : + new TokenObject(kind, pos, end); + node.parent = parent; + node.flags = parent.flags & NodeFlags.ContextFlags; + return node; +} - private assertHasRealPosition(message?: string) { - // eslint-disable-next-line debug-assert - Debug.assert(!positionIsSynthesized(this.pos) && !positionIsSynthesized(this.end), message || "Node must have a real position for this operation"); - } +class NodeObject implements Node { + public kind: SyntaxKind; + public pos: number; + public end: number; + public flags: NodeFlags; + public modifierFlagsCache: ModifierFlags; + public transformFlags: TransformFlags; + public parent: Node; + public symbol!: Symbol; // Actually optional, but it was too annoying to access `node.symbol!` everywhere since in many cases we know it must be defined + public jsDoc?: JSDoc[]; + public original?: Node; + private _children: Node[] | undefined; + + constructor(kind: SyntaxKind, pos: number, end: number) { + this.pos = pos; + this.end = end; + this.flags = NodeFlags.None; + this.modifierFlagsCache = ModifierFlags.None; + this.transformFlags = TransformFlags.None; + this.parent = undefined!; + this.kind = kind; + } - public getSourceFile(): SourceFile { - return getSourceFileOfNode(this); - } + private assertHasRealPosition(message?: string) { + // eslint-disable-next-line debug-assert + Debug.assert(!positionIsSynthesized(this.pos) && !positionIsSynthesized(this.end), message || "Node must have a real position for this operation"); + } - public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { - this.assertHasRealPosition(); - return getTokenPosOfNode(this, sourceFile, includeJsDocComment); - } + public getSourceFile(): SourceFile { + return getSourceFileOfNode(this); + } - public getFullStart(): number { - this.assertHasRealPosition(); - return this.pos; - } + public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { + this.assertHasRealPosition(); + return getTokenPosOfNode(this, sourceFile, includeJsDocComment); + } - public getEnd(): number { - this.assertHasRealPosition(); - return this.end; - } + public getFullStart(): number { + this.assertHasRealPosition(); + return this.pos; + } - public getWidth(sourceFile?: SourceFile): number { - this.assertHasRealPosition(); - return this.getEnd() - this.getStart(sourceFile); - } + public getEnd(): number { + this.assertHasRealPosition(); + return this.end; + } - public getFullWidth(): number { - this.assertHasRealPosition(); - return this.end - this.pos; - } + public getWidth(sourceFile?: SourceFile): number { + this.assertHasRealPosition(); + return this.getEnd() - this.getStart(sourceFile); + } - public getLeadingTriviaWidth(sourceFile?: SourceFile): number { - this.assertHasRealPosition(); - return this.getStart(sourceFile) - this.pos; - } + public getFullWidth(): number { + this.assertHasRealPosition(); + return this.end - this.pos; + } - public getFullText(sourceFile?: SourceFile): string { - this.assertHasRealPosition(); - return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end); - } + public getLeadingTriviaWidth(sourceFile?: SourceFile): number { + this.assertHasRealPosition(); + return this.getStart(sourceFile) - this.pos; + } - public getText(sourceFile?: SourceFile): string { - this.assertHasRealPosition(); - if (!sourceFile) { - sourceFile = this.getSourceFile(); - } - return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd()); - } + public getFullText(sourceFile?: SourceFile): string { + this.assertHasRealPosition(); + return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end); + } - public getChildCount(sourceFile?: SourceFile): number { - return this.getChildren(sourceFile).length; + public getText(sourceFile?: SourceFile): string { + this.assertHasRealPosition(); + if (!sourceFile) { + sourceFile = this.getSourceFile(); } + return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd()); + } - public getChildAt(index: number, sourceFile?: SourceFile): Node { - return this.getChildren(sourceFile)[index]; - } + public getChildCount(sourceFile?: SourceFile): number { + return this.getChildren(sourceFile).length; + } - public getChildren(sourceFile?: SourceFileLike): Node[] { - this.assertHasRealPosition("Node without a real position cannot be scanned and thus has no token nodes - use forEachChild and collect the result if that's fine"); - return this._children || (this._children = createChildren(this, sourceFile)); - } + public getChildAt(index: number, sourceFile?: SourceFile): Node { + return this.getChildren(sourceFile)[index]; + } - public getFirstToken(sourceFile?: SourceFileLike): Node | undefined { - this.assertHasRealPosition(); - const children = this.getChildren(sourceFile); - if (!children.length) { - return undefined; - } + public getChildren(sourceFile?: SourceFileLike): Node[] { + this.assertHasRealPosition("Node without a real position cannot be scanned and thus has no token nodes - use forEachChild and collect the result if that's fine"); + return this._children || (this._children = createChildren(this, sourceFile)); + } - const child = find(children, kid => kid.kind < SyntaxKind.FirstJSDocNode || kid.kind > SyntaxKind.LastJSDocNode)!; - return child.kind < SyntaxKind.FirstNode ? - child : - child.getFirstToken(sourceFile); + public getFirstToken(sourceFile?: SourceFileLike): Node | undefined { + this.assertHasRealPosition(); + const children = this.getChildren(sourceFile); + if (!children.length) { + return undefined; } - public getLastToken(sourceFile?: SourceFileLike): Node | undefined { - this.assertHasRealPosition(); - const children = this.getChildren(sourceFile); + const child = find(children, kid => kid.kind < SyntaxKind.FirstJSDocNode || kid.kind > SyntaxKind.LastJSDocNode)!; + return child.kind < SyntaxKind.FirstNode ? + child : + child.getFirstToken(sourceFile); + } - const child = lastOrUndefined(children); - if (!child) { - return undefined; - } + public getLastToken(sourceFile?: SourceFileLike): Node | undefined { + this.assertHasRealPosition(); + const children = this.getChildren(sourceFile); - return child.kind < SyntaxKind.FirstNode ? child : child.getLastToken(sourceFile); + const child = lastOrUndefined(children); + if (!child) { + return undefined; } - public forEachChild(cbNode: (node: Node) => T, cbNodeArray?: (nodes: NodeArray) => T): T | undefined { - return forEachChild(this, cbNode, cbNodeArray); - } + return child.kind < SyntaxKind.FirstNode ? child : child.getLastToken(sourceFile); } - function createChildren(node: Node, sourceFile: SourceFileLike | undefined): Node[] { - if (!isNodeKind(node.kind)) { - return emptyArray; - } + public forEachChild(cbNode: (node: Node) => T, cbNodeArray?: (nodes: NodeArray) => T): T | undefined { + return forEachChild(this, cbNode, cbNodeArray); + } +} - const children: Node[] = []; +function createChildren(node: Node, sourceFile: SourceFileLike | undefined): Node[] { + if (!isNodeKind(node.kind)) { + return emptyArray; + } - if (isJSDocCommentContainingNode(node)) { - /** Don't add trivia for "tokens" since this is in a comment. */ - node.forEachChild(child => { - children.push(child); - }); - return children; - } + const children: Node[] = []; - scanner.setText((sourceFile || node.getSourceFile()).text); - let pos = node.pos; - const processNode = (child: Node) => { - addSyntheticNodes(children, pos, child.pos, node); + if (isJSDocCommentContainingNode(node)) { + /** Don't add trivia for "tokens" since this is in a comment. */ + node.forEachChild(child => { children.push(child); - pos = child.end; - }; - const processNodes = (nodes: NodeArray) => { - addSyntheticNodes(children, pos, nodes.pos, node); - children.push(createSyntaxList(nodes, node)); - pos = nodes.end; - }; - // jsDocComments need to be the first children - forEach((node as JSDocContainer).jsDoc, processNode); - // For syntactic classifications, all trivia are classified together, including jsdoc comments. - // For that to work, the jsdoc comments should still be the leading trivia of the first child. - // Restoring the scanner position ensures that. - pos = node.pos; - node.forEachChild(processNode, processNodes); - addSyntheticNodes(children, pos, node.end, node); - scanner.setText(undefined); + }); return children; } - function addSyntheticNodes(nodes: Push, pos: number, end: number, parent: Node): void { - scanner.setTextPos(pos); - while (pos < end) { - const token = scanner.scan(); - const textPos = scanner.getTextPos(); - if (textPos <= end) { - if (token === SyntaxKind.Identifier) { - Debug.fail(`Did not expect ${Debug.formatSyntaxKind(parent.kind)} to have an Identifier in its trivia`); - } - nodes.push(createNode(token, pos, textPos, parent)); - } - pos = textPos; - if (token === SyntaxKind.EndOfFileToken) { - break; + scanner.setText((sourceFile || node.getSourceFile()).text); + let pos = node.pos; + const processNode = (child: Node) => { + addSyntheticNodes(children, pos, child.pos, node); + children.push(child); + pos = child.end; + }; + const processNodes = (nodes: NodeArray) => { + addSyntheticNodes(children, pos, nodes.pos, node); + children.push(createSyntaxList(nodes, node)); + pos = nodes.end; + }; + // jsDocComments need to be the first children + forEach((node as JSDocContainer).jsDoc, processNode); + // For syntactic classifications, all trivia are classified together, including jsdoc comments. + // For that to work, the jsdoc comments should still be the leading trivia of the first child. + // Restoring the scanner position ensures that. + pos = node.pos; + node.forEachChild(processNode, processNodes); + addSyntheticNodes(children, pos, node.end, node); + scanner.setText(undefined); + return children; +} + +function addSyntheticNodes(nodes: Push, pos: number, end: number, parent: Node): void { + scanner.setTextPos(pos); + while (pos < end) { + const token = scanner.scan(); + const textPos = scanner.getTextPos(); + if (textPos <= end) { + if (token === SyntaxKind.Identifier) { + Debug.fail(`Did not expect ${Debug.formatSyntaxKind(parent.kind)} to have an Identifier in its trivia`); } + nodes.push(createNode(token, pos, textPos, parent)); } - } - - function createSyntaxList(nodes: NodeArray, parent: Node): Node { - const list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, parent) as any as SyntaxList; - list._children = []; - let pos = nodes.pos; - for (const node of nodes) { - addSyntheticNodes(list._children, pos, node.pos, parent); - list._children.push(node); - pos = node.end; + pos = textPos; + if (token === SyntaxKind.EndOfFileToken) { + break; } - addSyntheticNodes(list._children, pos, nodes.end, parent); - return list; } +} - class TokenOrIdentifierObject implements Node { - public kind!: SyntaxKind; - public pos: number; - public end: number; - public flags: NodeFlags; - public modifierFlagsCache: ModifierFlags; - public transformFlags: TransformFlags; - public parent: Node; - public symbol!: Symbol; - public jsDocComments?: JSDoc[]; +function createSyntaxList(nodes: NodeArray, parent: Node): Node { + const list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, parent) as any as SyntaxList; + list._children = []; + let pos = nodes.pos; + for (const node of nodes) { + addSyntheticNodes(list._children, pos, node.pos, parent); + list._children.push(node); + pos = node.end; + } + addSyntheticNodes(list._children, pos, nodes.end, parent); + return list; +} - constructor(pos: number, end: number) { - // Set properties in same order as NodeObject - this.pos = pos; - this.end = end; - this.flags = NodeFlags.None; - this.modifierFlagsCache = ModifierFlags.None; - this.transformFlags = TransformFlags.None; - this.parent = undefined!; - } +class TokenOrIdentifierObject implements Node { + public kind!: SyntaxKind; + public pos: number; + public end: number; + public flags: NodeFlags; + public modifierFlagsCache: ModifierFlags; + public transformFlags: TransformFlags; + public parent: Node; + public symbol!: Symbol; + public jsDocComments?: JSDoc[]; + + constructor(pos: number, end: number) { + // Set properties in same order as NodeObject + this.pos = pos; + this.end = end; + this.flags = NodeFlags.None; + this.modifierFlagsCache = ModifierFlags.None; + this.transformFlags = TransformFlags.None; + this.parent = undefined!; + } - public getSourceFile(): SourceFile { - return getSourceFileOfNode(this); - } + public getSourceFile(): SourceFile { + return getSourceFileOfNode(this); + } - public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { - return getTokenPosOfNode(this, sourceFile, includeJsDocComment); - } + public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { + return getTokenPosOfNode(this, sourceFile, includeJsDocComment); + } - public getFullStart(): number { - return this.pos; - } + public getFullStart(): number { + return this.pos; + } - public getEnd(): number { - return this.end; - } + public getEnd(): number { + return this.end; + } - public getWidth(sourceFile?: SourceFile): number { - return this.getEnd() - this.getStart(sourceFile); - } + public getWidth(sourceFile?: SourceFile): number { + return this.getEnd() - this.getStart(sourceFile); + } - public getFullWidth(): number { - return this.end - this.pos; - } + public getFullWidth(): number { + return this.end - this.pos; + } - public getLeadingTriviaWidth(sourceFile?: SourceFile): number { - return this.getStart(sourceFile) - this.pos; - } + public getLeadingTriviaWidth(sourceFile?: SourceFile): number { + return this.getStart(sourceFile) - this.pos; + } - public getFullText(sourceFile?: SourceFile): string { - return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end); - } + public getFullText(sourceFile?: SourceFile): string { + return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end); + } - public getText(sourceFile?: SourceFile): string { - if (!sourceFile) { - sourceFile = this.getSourceFile(); - } - return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd()); + public getText(sourceFile?: SourceFile): string { + if (!sourceFile) { + sourceFile = this.getSourceFile(); } + return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd()); + } - public getChildCount(): number { - return this.getChildren().length; - } + public getChildCount(): number { + return this.getChildren().length; + } - public getChildAt(index: number): Node { - return this.getChildren()[index]; - } + public getChildAt(index: number): Node { + return this.getChildren()[index]; + } - public getChildren(): Node[] { - return this.kind === SyntaxKind.EndOfFileToken ? (this as EndOfFileToken).jsDoc || emptyArray : emptyArray; - } + public getChildren(): Node[] { + return this.kind === SyntaxKind.EndOfFileToken ? (this as EndOfFileToken).jsDoc || emptyArray : emptyArray; + } - public getFirstToken(): Node | undefined { - return undefined; - } + public getFirstToken(): Node | undefined { + return undefined; + } - public getLastToken(): Node | undefined { - return undefined; - } + public getLastToken(): Node | undefined { + return undefined; + } - public forEachChild(): T | undefined { - return undefined; - } + public forEachChild(): T | undefined { + return undefined; } +} - class SymbolObject implements Symbol { - flags: SymbolFlags; - escapedName: __String; - declarations!: Declaration[]; - valueDeclaration!: Declaration; +class SymbolObject implements Symbol { + flags: SymbolFlags; + escapedName: __String; + declarations!: Declaration[]; + valueDeclaration!: Declaration; - // Undefined is used to indicate the value has not been computed. If, after computing, the - // symbol has no doc comment, then the empty array will be returned. - documentationComment?: SymbolDisplayPart[]; - tags?: JSDocTagInfo[]; // same + // Undefined is used to indicate the value has not been computed. If, after computing, the + // symbol has no doc comment, then the empty array will be returned. + documentationComment?: SymbolDisplayPart[]; + tags?: JSDocTagInfo[]; // same - contextualGetAccessorDocumentationComment?: SymbolDisplayPart[]; - contextualSetAccessorDocumentationComment?: SymbolDisplayPart[]; + contextualGetAccessorDocumentationComment?: SymbolDisplayPart[]; + contextualSetAccessorDocumentationComment?: SymbolDisplayPart[]; - constructor(flags: SymbolFlags, name: __String) { - this.flags = flags; - this.escapedName = name; - } + constructor(flags: SymbolFlags, name: __String) { + this.flags = flags; + this.escapedName = name; + } - getFlags(): SymbolFlags { - return this.flags; - } + getFlags(): SymbolFlags { + return this.flags; + } - get name(): string { - return symbolName(this); - } + get name(): string { + return symbolName(this); + } - getEscapedName(): __String { - return this.escapedName; - } + getEscapedName(): __String { + return this.escapedName; + } - getName(): string { - return this.name; - } + getName(): string { + return this.name; + } - getDeclarations(): Declaration[] | undefined { - return this.declarations; - } + getDeclarations(): Declaration[] | undefined { + return this.declarations; + } - getDocumentationComment(checker: TypeChecker | undefined): SymbolDisplayPart[] { - if (!this.documentationComment) { - this.documentationComment = emptyArray; // Set temporarily to avoid an infinite loop finding inherited docs + getDocumentationComment(checker: TypeChecker | undefined): SymbolDisplayPart[] { + if (!this.documentationComment) { + this.documentationComment = emptyArray; // Set temporarily to avoid an infinite loop finding inherited docs - if (!this.declarations && (this as Symbol as TransientSymbol).target && ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) { - const labelDecl = ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!; - this.documentationComment = getDocumentationComment([labelDecl], checker); - } - else { - this.documentationComment = getDocumentationComment(this.declarations, checker); - } + if (!this.declarations && (this as Symbol as TransientSymbol).target && ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) { + const labelDecl = ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!; + this.documentationComment = getDocumentationComment([labelDecl], checker); } - return this.documentationComment; - } - - getContextualDocumentationComment(context: Node | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { - switch (context?.kind) { - case SyntaxKind.GetAccessor: - if (!this.contextualGetAccessorDocumentationComment) { - this.contextualGetAccessorDocumentationComment = emptyArray; - this.contextualGetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isGetAccessor), checker); - } - return this.contextualGetAccessorDocumentationComment; - case SyntaxKind.SetAccessor: - if (!this.contextualSetAccessorDocumentationComment) { - this.contextualSetAccessorDocumentationComment = emptyArray; - this.contextualSetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isSetAccessor), checker); - } - return this.contextualSetAccessorDocumentationComment; - default: - return this.getDocumentationComment(checker); + else { + this.documentationComment = getDocumentationComment(this.declarations, checker); } } + return this.documentationComment; + } - getJsDocTags(checker?: TypeChecker): JSDocTagInfo[] { - if (this.tags === undefined) { - this.tags = JsDoc.getJsDocTagsFromDeclarations(this.declarations, checker); - } + getContextualDocumentationComment(context: Node | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { + switch (context?.kind) { + case SyntaxKind.GetAccessor: + if (!this.contextualGetAccessorDocumentationComment) { + this.contextualGetAccessorDocumentationComment = emptyArray; + this.contextualGetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isGetAccessor), checker); + } + return this.contextualGetAccessorDocumentationComment; + case SyntaxKind.SetAccessor: + if (!this.contextualSetAccessorDocumentationComment) { + this.contextualSetAccessorDocumentationComment = emptyArray; + this.contextualSetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isSetAccessor), checker); + } + return this.contextualSetAccessorDocumentationComment; + default: + return this.getDocumentationComment(checker); + } + } - return this.tags; + getJsDocTags(checker?: TypeChecker): JSDocTagInfo[] { + if (this.tags === undefined) { + this.tags = JsDoc.getJsDocTagsFromDeclarations(this.declarations, checker); } + + return this.tags; } +} - class TokenObject extends TokenOrIdentifierObject implements Token { - public kind: TKind; +class TokenObject extends TokenOrIdentifierObject implements Token { + public kind: TKind; - constructor(kind: TKind, pos: number, end: number) { - super(pos, end); - this.kind = kind; - } + constructor(kind: TKind, pos: number, end: number) { + super(pos, end); + this.kind = kind; } +} - class IdentifierObject extends TokenOrIdentifierObject implements Identifier { - public kind: SyntaxKind.Identifier = SyntaxKind.Identifier; - public escapedText!: __String; - public autoGenerateFlags!: GeneratedIdentifierFlags; - _primaryExpressionBrand: any; - _memberExpressionBrand: any; - _leftHandSideExpressionBrand: any; - _updateExpressionBrand: any; - _unaryExpressionBrand: any; - _expressionBrand: any; - _declarationBrand: any; - /*@internal*/typeArguments!: NodeArray; - constructor(_kind: SyntaxKind.Identifier, pos: number, end: number) { - super(pos, end); - } +class IdentifierObject extends TokenOrIdentifierObject implements Identifier { + public kind: SyntaxKind.Identifier = SyntaxKind.Identifier; + public escapedText!: __String; + public autoGenerateFlags!: GeneratedIdentifierFlags; + _primaryExpressionBrand: any; + _memberExpressionBrand: any; + _leftHandSideExpressionBrand: any; + _updateExpressionBrand: any; + _unaryExpressionBrand: any; + _expressionBrand: any; + _declarationBrand: any; + /*@internal*/typeArguments!: NodeArray; + constructor(_kind: SyntaxKind.Identifier, pos: number, end: number) { + super(pos, end); + } - get text(): string { - return idText(this); - } + get text(): string { + return idText(this); + } +} +IdentifierObject.prototype.kind = SyntaxKind.Identifier; +class PrivateIdentifierObject extends TokenOrIdentifierObject implements PrivateIdentifier { + public kind!: SyntaxKind.PrivateIdentifier; + public escapedText!: __String; + public symbol!: Symbol; + _primaryExpressionBrand: any; + _memberExpressionBrand: any; + _leftHandSideExpressionBrand: any; + _updateExpressionBrand: any; + _unaryExpressionBrand: any; + _expressionBrand: any; + constructor(_kind: SyntaxKind.PrivateIdentifier, pos: number, end: number) { + super(pos, end); } - IdentifierObject.prototype.kind = SyntaxKind.Identifier; - class PrivateIdentifierObject extends TokenOrIdentifierObject implements PrivateIdentifier { - public kind!: SyntaxKind.PrivateIdentifier; - public escapedText!: __String; - public symbol!: Symbol; - _primaryExpressionBrand: any; - _memberExpressionBrand: any; - _leftHandSideExpressionBrand: any; - _updateExpressionBrand: any; - _unaryExpressionBrand: any; - _expressionBrand: any; - constructor(_kind: SyntaxKind.PrivateIdentifier, pos: number, end: number) { - super(pos, end); - } - get text(): string { - return idText(this); - } + get text(): string { + return idText(this); + } +} +PrivateIdentifierObject.prototype.kind = SyntaxKind.PrivateIdentifier; + +class TypeObject implements Type { + checker: TypeChecker; + flags: TypeFlags; + objectFlags?: ObjectFlags; + id!: number; + symbol!: Symbol; + constructor(checker: TypeChecker, flags: TypeFlags) { + this.checker = checker; + this.flags = flags; + } + getFlags(): TypeFlags { + return this.flags; + } + getSymbol(): Symbol | undefined { + return this.symbol; + } + getProperties(): Symbol[] { + return this.checker.getPropertiesOfType(this); + } + getProperty(propertyName: string): Symbol | undefined { + return this.checker.getPropertyOfType(this, propertyName); + } + getApparentProperties(): Symbol[] { + return this.checker.getAugmentedPropertiesOfType(this); + } + getCallSignatures(): readonly Signature[] { + return this.checker.getSignaturesOfType(this, SignatureKind.Call); + } + getConstructSignatures(): readonly Signature[] { + return this.checker.getSignaturesOfType(this, SignatureKind.Construct); + } + getStringIndexType(): Type | undefined { + return this.checker.getIndexTypeOfType(this, IndexKind.String); + } + getNumberIndexType(): Type | undefined { + return this.checker.getIndexTypeOfType(this, IndexKind.Number); + } + getBaseTypes(): BaseType[] | undefined { + return this.isClassOrInterface() ? this.checker.getBaseTypes(this) : undefined; + } + isNullableType(): boolean { + return this.checker.isNullableType(this); + } + getNonNullableType(): Type { + return this.checker.getNonNullableType(this); + } + getNonOptionalType(): Type { + return this.checker.getNonOptionalType(this); + } + getConstraint(): Type | undefined { + return this.checker.getBaseConstraintOfType(this); + } + getDefault(): Type | undefined { + return this.checker.getDefaultFromTypeParameter(this); } - PrivateIdentifierObject.prototype.kind = SyntaxKind.PrivateIdentifier; - class TypeObject implements Type { - checker: TypeChecker; - flags: TypeFlags; - objectFlags?: ObjectFlags; - id!: number; - symbol!: Symbol; - constructor(checker: TypeChecker, flags: TypeFlags) { - this.checker = checker; - this.flags = flags; - } - getFlags(): TypeFlags { - return this.flags; - } - getSymbol(): Symbol | undefined { - return this.symbol; - } - getProperties(): Symbol[] { - return this.checker.getPropertiesOfType(this); - } - getProperty(propertyName: string): Symbol | undefined { - return this.checker.getPropertyOfType(this, propertyName); - } - getApparentProperties(): Symbol[] { - return this.checker.getAugmentedPropertiesOfType(this); - } - getCallSignatures(): readonly Signature[] { - return this.checker.getSignaturesOfType(this, SignatureKind.Call); - } - getConstructSignatures(): readonly Signature[] { - return this.checker.getSignaturesOfType(this, SignatureKind.Construct); - } - getStringIndexType(): Type | undefined { - return this.checker.getIndexTypeOfType(this, IndexKind.String); - } - getNumberIndexType(): Type | undefined { - return this.checker.getIndexTypeOfType(this, IndexKind.Number); - } - getBaseTypes(): BaseType[] | undefined { - return this.isClassOrInterface() ? this.checker.getBaseTypes(this) : undefined; - } - isNullableType(): boolean { - return this.checker.isNullableType(this); - } - getNonNullableType(): Type { - return this.checker.getNonNullableType(this); - } - getNonOptionalType(): Type { - return this.checker.getNonOptionalType(this); - } - getConstraint(): Type | undefined { - return this.checker.getBaseConstraintOfType(this); - } - getDefault(): Type | undefined { - return this.checker.getDefaultFromTypeParameter(this); + isUnion(): this is UnionType { + return !!(this.flags & TypeFlags.Union); + } + isIntersection(): this is IntersectionType { + return !!(this.flags & TypeFlags.Intersection); + } + isUnionOrIntersection(): this is UnionOrIntersectionType { + return !!(this.flags & TypeFlags.UnionOrIntersection); + } + isLiteral(): this is LiteralType { + return !!(this.flags & TypeFlags.StringOrNumberLiteral); + } + isStringLiteral(): this is StringLiteralType { + return !!(this.flags & TypeFlags.StringLiteral); + } + isNumberLiteral(): this is NumberLiteralType { + return !!(this.flags & TypeFlags.NumberLiteral); + } + isTypeParameter(): this is TypeParameter { + return !!(this.flags & TypeFlags.TypeParameter); + } + isClassOrInterface(): this is InterfaceType { + return !!(getObjectFlags(this) & ObjectFlags.ClassOrInterface); + } + isClass(): this is InterfaceType { + return !!(getObjectFlags(this) & ObjectFlags.Class); + } + /** + * This polyfills `referenceType.typeArguments` for API consumers + */ + get typeArguments() { + if (getObjectFlags(this) & ObjectFlags.Reference) { + return this.checker.getTypeArguments(this as Type as TypeReference); } + return undefined; + } +} - isUnion(): this is UnionType { - return !!(this.flags & TypeFlags.Union); - } - isIntersection(): this is IntersectionType { - return !!(this.flags & TypeFlags.Intersection); - } - isUnionOrIntersection(): this is UnionOrIntersectionType { - return !!(this.flags & TypeFlags.UnionOrIntersection); - } - isLiteral(): this is LiteralType { - return !!(this.flags & TypeFlags.StringOrNumberLiteral); - } - isStringLiteral(): this is StringLiteralType { - return !!(this.flags & TypeFlags.StringLiteral); - } - isNumberLiteral(): this is NumberLiteralType { - return !!(this.flags & TypeFlags.NumberLiteral); - } - isTypeParameter(): this is TypeParameter { - return !!(this.flags & TypeFlags.TypeParameter); - } - isClassOrInterface(): this is InterfaceType { - return !!(getObjectFlags(this) & ObjectFlags.ClassOrInterface); - } - isClass(): this is InterfaceType { - return !!(getObjectFlags(this) & ObjectFlags.Class); - } - /** - * This polyfills `referenceType.typeArguments` for API consumers - */ - get typeArguments() { - if (getObjectFlags(this) & ObjectFlags.Reference) { - return this.checker.getTypeArguments(this as Type as TypeReference); - } - return undefined; - } +class SignatureObject implements Signature { + flags: SignatureFlags; + checker: TypeChecker; + declaration!: SignatureDeclaration; + typeParameters?: TypeParameter[]; + parameters!: Symbol[]; + thisParameter!: Symbol; + resolvedReturnType!: Type; + resolvedTypePredicate: TypePredicate | undefined; + minTypeArgumentCount!: number; + minArgumentCount!: number; + + // Undefined is used to indicate the value has not been computed. If, after computing, the + // symbol has no doc comment, then the empty array will be returned. + documentationComment?: SymbolDisplayPart[]; + jsDocTags?: JSDocTagInfo[]; // same + + constructor(checker: TypeChecker, flags: SignatureFlags) { + this.checker = checker; + this.flags = flags; } - class SignatureObject implements Signature { - flags: SignatureFlags; - checker: TypeChecker; - declaration!: SignatureDeclaration; - typeParameters?: TypeParameter[]; - parameters!: Symbol[]; - thisParameter!: Symbol; - resolvedReturnType!: Type; - resolvedTypePredicate: TypePredicate | undefined; - minTypeArgumentCount!: number; - minArgumentCount!: number; + getDeclaration(): SignatureDeclaration { + return this.declaration; + } + getTypeParameters(): TypeParameter[] | undefined { + return this.typeParameters; + } + getParameters(): Symbol[] { + return this.parameters; + } + getReturnType(): Type { + return this.checker.getReturnTypeOfSignature(this); + } - // Undefined is used to indicate the value has not been computed. If, after computing, the - // symbol has no doc comment, then the empty array will be returned. - documentationComment?: SymbolDisplayPart[]; - jsDocTags?: JSDocTagInfo[]; // same + getDocumentationComment(): SymbolDisplayPart[] { + return this.documentationComment || (this.documentationComment = getDocumentationComment(singleElementArray(this.declaration), this.checker)); + } - constructor(checker: TypeChecker, flags: SignatureFlags) { - this.checker = checker; - this.flags = flags; + getJsDocTags(): JSDocTagInfo[] { + if (this.jsDocTags === undefined) { + this.jsDocTags = this.declaration ? getJsDocTagsOfSignature(this.declaration, this.checker) : []; } + return this.jsDocTags; + } +} - getDeclaration(): SignatureDeclaration { - return this.declaration; - } - getTypeParameters(): TypeParameter[] | undefined { - return this.typeParameters; - } - getParameters(): Symbol[] { - return this.parameters; - } - getReturnType(): Type { - return this.checker.getReturnTypeOfSignature(this); - } +/** + * Returns whether or not the given node has a JSDoc "inheritDoc" tag on it. + * @param node the Node in question. + * @returns `true` if `node` has a JSDoc "inheritDoc" tag on it, otherwise `false`. + */ +function hasJSDocInheritDocTag(node: Node) { + return getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc"); +} - getDocumentationComment(): SymbolDisplayPart[] { - return this.documentationComment || (this.documentationComment = getDocumentationComment(singleElementArray(this.declaration), this.checker)); +function getJsDocTagsOfSignature(declaration: Declaration, checker: TypeChecker): JSDocTagInfo[] { + let tags = JsDoc.getJsDocTagsFromDeclarations([declaration], checker); + if (tags.length === 0 || hasJSDocInheritDocTag(declaration)) { + const inheritedTags = findBaseOfDeclaration(checker, declaration, symbol => symbol.declarations?.length === 1 ? symbol.getJsDocTags() : undefined); + if (inheritedTags) { + tags = [...inheritedTags, ...tags]; } + } + return tags; +} - getJsDocTags(): JSDocTagInfo[] { - if (this.jsDocTags === undefined) { - this.jsDocTags = this.declaration ? getJsDocTagsOfSignature(this.declaration, this.checker) : []; - } - return this.jsDocTags; +function getDocumentationComment(declarations: readonly Declaration[] | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { + if (!declarations) return emptyArray; + + let doc = JsDoc.getJsDocCommentsFromDeclarations(declarations, checker); + if (checker && (doc.length === 0 || declarations.some(hasJSDocInheritDocTag))) { + const seenSymbols = new Set(); + for (const declaration of declarations) { + const inheritedDocs = findBaseOfDeclaration(checker, declaration, symbol => { + if (!seenSymbols.has(symbol)) { + seenSymbols.add(symbol); + return symbol.getDocumentationComment(checker); + } + }); + // TODO: GH#16312 Return a ReadonlyArray, avoid copying inheritedDocs + if (inheritedDocs) doc = doc.length === 0 ? inheritedDocs.slice() : inheritedDocs.concat(lineBreakPart(), doc); } } + return doc; +} - /** - * Returns whether or not the given node has a JSDoc "inheritDoc" tag on it. - * @param node the Node in question. - * @returns `true` if `node` has a JSDoc "inheritDoc" tag on it, otherwise `false`. - */ - function hasJSDocInheritDocTag(node: Node) { - return getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc"); - } +function findBaseOfDeclaration(checker: TypeChecker, declaration: Declaration, cb: (symbol: Symbol) => T[] | undefined): T[] | undefined { + if (hasStaticModifier(declaration)) return; - function getJsDocTagsOfSignature(declaration: Declaration, checker: TypeChecker): JSDocTagInfo[] { - let tags = JsDoc.getJsDocTagsFromDeclarations([declaration], checker); - if (tags.length === 0 || hasJSDocInheritDocTag(declaration)) { - const inheritedTags = findBaseOfDeclaration(checker, declaration, symbol => symbol.declarations?.length === 1 ? symbol.getJsDocTags() : undefined); - if (inheritedTags) { - tags = [...inheritedTags, ...tags]; - } - } - return tags; - } + const classOrInterfaceDeclaration = declaration.parent?.kind === SyntaxKind.Constructor ? declaration.parent.parent : declaration.parent; + if (!classOrInterfaceDeclaration) return; - function getDocumentationComment(declarations: readonly Declaration[] | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { - if (!declarations) return emptyArray; + return firstDefined(getAllSuperTypeNodes(classOrInterfaceDeclaration), superTypeNode => { + const symbol = checker.getPropertyOfType(checker.getTypeAtLocation(superTypeNode), declaration.symbol.name); + return symbol ? cb(symbol) : undefined; + }); +} - let doc = JsDoc.getJsDocCommentsFromDeclarations(declarations, checker); - if (checker && (doc.length === 0 || declarations.some(hasJSDocInheritDocTag))) { - const seenSymbols = new Set(); - for (const declaration of declarations) { - const inheritedDocs = findBaseOfDeclaration(checker, declaration, symbol => { - if (!seenSymbols.has(symbol)) { - seenSymbols.add(symbol); - return symbol.getDocumentationComment(checker); - } - }); - // TODO: GH#16312 Return a ReadonlyArray, avoid copying inheritedDocs - if (inheritedDocs) doc = doc.length === 0 ? inheritedDocs.slice() : inheritedDocs.concat(lineBreakPart(), doc); - } - } - return doc; +class SourceFileObject extends NodeObject implements SourceFile { + public kind: SyntaxKind.SourceFile = SyntaxKind.SourceFile; + public _declarationBrand: any; + public fileName!: string; + public path!: Path; + public resolvedPath!: Path; + public originalFileName!: string; + public text!: string; + public scriptSnapshot!: IScriptSnapshot; + public lineMap!: readonly number[]; + + public statements!: NodeArray; + public endOfFileToken!: Token; + + public amdDependencies!: { name: string; path: string }[]; + public moduleName!: string; + public referencedFiles!: FileReference[]; + public typeReferenceDirectives!: FileReference[]; + public libReferenceDirectives!: FileReference[]; + + public syntacticDiagnostics!: DiagnosticWithLocation[]; + public parseDiagnostics!: DiagnosticWithLocation[]; + public bindDiagnostics!: DiagnosticWithLocation[]; + public bindSuggestionDiagnostics?: DiagnosticWithLocation[]; + + public isDeclarationFile!: boolean; + public isDefaultLib!: boolean; + public hasNoDefaultLib!: boolean; + public externalModuleIndicator!: Node; // The first node that causes this file to be an external module + public commonJsModuleIndicator!: Node; // The first node that causes this file to be a CommonJS module + public nodeCount!: number; + public identifierCount!: number; + public symbolCount!: number; + public version!: string; + public scriptKind!: ScriptKind; + public languageVersion!: ScriptTarget; + public languageVariant!: LanguageVariant; + public identifiers!: ESMap; + public nameTable: UnderscoreEscapedMap | undefined; + public resolvedModules: ModeAwareCache | undefined; + public resolvedTypeReferenceDirectiveNames!: ModeAwareCache; + public imports!: readonly StringLiteralLike[]; + public moduleAugmentations!: StringLiteral[]; + private namedDeclarations: ESMap | undefined; + public ambientModuleNames!: string[]; + public checkJsDirective: CheckJsDirective | undefined; + public errorExpectations: TextRange[] | undefined; + public possiblyContainDynamicImport?: boolean; + public pragmas!: PragmaMap; + public localJsxFactory: EntityName | undefined; + public localJsxNamespace: __String | undefined; + + constructor(kind: SyntaxKind, pos: number, end: number) { + super(kind, pos, end); + } + + public update(newText: string, textChangeRange: TextChangeRange): SourceFile { + return updateSourceFile(this, newText, textChangeRange); } - function findBaseOfDeclaration(checker: TypeChecker, declaration: Declaration, cb: (symbol: Symbol) => T[] | undefined): T[] | undefined { - if (hasStaticModifier(declaration)) return; + public getLineAndCharacterOfPosition(position: number): LineAndCharacter { + return getLineAndCharacterOfPosition(this, position); + } - const classOrInterfaceDeclaration = declaration.parent?.kind === SyntaxKind.Constructor ? declaration.parent.parent : declaration.parent; - if (!classOrInterfaceDeclaration) return; + public getLineStarts(): readonly number[] { + return getLineStarts(this); + } - return firstDefined(getAllSuperTypeNodes(classOrInterfaceDeclaration), superTypeNode => { - const symbol = checker.getPropertyOfType(checker.getTypeAtLocation(superTypeNode), declaration.symbol.name); - return symbol ? cb(symbol) : undefined; - }); + public getPositionOfLineAndCharacter(line: number, character: number, allowEdits?: true): number { + return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text, allowEdits); } - class SourceFileObject extends NodeObject implements SourceFile { - public kind: SyntaxKind.SourceFile = SyntaxKind.SourceFile; - public _declarationBrand: any; - public fileName!: string; - public path!: Path; - public resolvedPath!: Path; - public originalFileName!: string; - public text!: string; - public scriptSnapshot!: IScriptSnapshot; - public lineMap!: readonly number[]; - - public statements!: NodeArray; - public endOfFileToken!: Token; - - public amdDependencies!: { name: string; path: string }[]; - public moduleName!: string; - public referencedFiles!: FileReference[]; - public typeReferenceDirectives!: FileReference[]; - public libReferenceDirectives!: FileReference[]; - - public syntacticDiagnostics!: DiagnosticWithLocation[]; - public parseDiagnostics!: DiagnosticWithLocation[]; - public bindDiagnostics!: DiagnosticWithLocation[]; - public bindSuggestionDiagnostics?: DiagnosticWithLocation[]; - - public isDeclarationFile!: boolean; - public isDefaultLib!: boolean; - public hasNoDefaultLib!: boolean; - public externalModuleIndicator!: Node; // The first node that causes this file to be an external module - public commonJsModuleIndicator!: Node; // The first node that causes this file to be a CommonJS module - public nodeCount!: number; - public identifierCount!: number; - public symbolCount!: number; - public version!: string; - public scriptKind!: ScriptKind; - public languageVersion!: ScriptTarget; - public languageVariant!: LanguageVariant; - public identifiers!: ESMap; - public nameTable: UnderscoreEscapedMap | undefined; - public resolvedModules: ModeAwareCache | undefined; - public resolvedTypeReferenceDirectiveNames!: ModeAwareCache; - public imports!: readonly StringLiteralLike[]; - public moduleAugmentations!: StringLiteral[]; - private namedDeclarations: ESMap | undefined; - public ambientModuleNames!: string[]; - public checkJsDirective: CheckJsDirective | undefined; - public errorExpectations: TextRange[] | undefined; - public possiblyContainDynamicImport?: boolean; - public pragmas!: PragmaMap; - public localJsxFactory: EntityName | undefined; - public localJsxNamespace: __String | undefined; - - constructor(kind: SyntaxKind, pos: number, end: number) { - super(kind, pos, end); - } - - public update(newText: string, textChangeRange: TextChangeRange): SourceFile { - return updateSourceFile(this, newText, textChangeRange); - } - - public getLineAndCharacterOfPosition(position: number): LineAndCharacter { - return getLineAndCharacterOfPosition(this, position); - } - - public getLineStarts(): readonly number[] { - return getLineStarts(this); - } - - public getPositionOfLineAndCharacter(line: number, character: number, allowEdits?: true): number { - return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text, allowEdits); - } - - public getLineEndOfPosition(pos: number): number { - const { line } = this.getLineAndCharacterOfPosition(pos); - const lineStarts = this.getLineStarts(); - - let lastCharPos: number | undefined; - if (line + 1 >= lineStarts.length) { - lastCharPos = this.getEnd(); - } - if (!lastCharPos) { - lastCharPos = lineStarts[line + 1] - 1; - } + public getLineEndOfPosition(pos: number): number { + const { line } = this.getLineAndCharacterOfPosition(pos); + const lineStarts = this.getLineStarts(); - const fullText = this.getFullText(); - // if the new line is "\r\n", we should return the last non-new-line-character position - return fullText[lastCharPos] === "\n" && fullText[lastCharPos - 1] === "\r" ? lastCharPos - 1 : lastCharPos; + let lastCharPos: number | undefined; + if (line + 1 >= lineStarts.length) { + lastCharPos = this.getEnd(); + } + if (!lastCharPos) { + lastCharPos = lineStarts[line + 1] - 1; } - public getNamedDeclarations(): ESMap { - if (!this.namedDeclarations) { - this.namedDeclarations = this.computeNamedDeclarations(); - } + const fullText = this.getFullText(); + // if the new line is "\r\n", we should return the last non-new-line-character position + return fullText[lastCharPos] === "\n" && fullText[lastCharPos - 1] === "\r" ? lastCharPos - 1 : lastCharPos; + } - return this.namedDeclarations; + public getNamedDeclarations(): ESMap { + if (!this.namedDeclarations) { + this.namedDeclarations = this.computeNamedDeclarations(); } - private computeNamedDeclarations(): ESMap { - const result = createMultiMap(); + return this.namedDeclarations; + } - this.forEachChild(visit); + private computeNamedDeclarations(): ESMap { + const result = createMultiMap(); - return result; + this.forEachChild(visit); - function addDeclaration(declaration: Declaration) { - const name = getDeclarationName(declaration); - if (name) { - result.add(name, declaration); - } - } + return result; - function getDeclarations(name: string) { - let declarations = result.get(name); - if (!declarations) { - result.set(name, declarations = []); - } - return declarations; + function addDeclaration(declaration: Declaration) { + const name = getDeclarationName(declaration); + if (name) { + result.add(name, declaration); } + } - function getDeclarationName(declaration: Declaration) { - const name = getNonAssignedNameOfDeclaration(declaration); - return name && (isComputedPropertyName(name) && isPropertyAccessExpression(name.expression) ? name.expression.name.text - : isPropertyName(name) ? getNameFromPropertyName(name) : undefined); + function getDeclarations(name: string) { + let declarations = result.get(name); + if (!declarations) { + result.set(name, declarations = []); } + return declarations; + } - function visit(node: Node): void { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - const functionDeclaration = node as FunctionLikeDeclaration; - const declarationName = getDeclarationName(functionDeclaration); - - if (declarationName) { - const declarations = getDeclarations(declarationName); - const lastDeclaration = lastOrUndefined(declarations); - - // Check whether this declaration belongs to an "overload group". - if (lastDeclaration && functionDeclaration.parent === lastDeclaration.parent && functionDeclaration.symbol === lastDeclaration.symbol) { - // Overwrite the last declaration if it was an overload - // and this one is an implementation. - if (functionDeclaration.body && !(lastDeclaration as FunctionLikeDeclaration).body) { - declarations[declarations.length - 1] = functionDeclaration; - } - } - else { - declarations.push(functionDeclaration); + function getDeclarationName(declaration: Declaration) { + const name = getNonAssignedNameOfDeclaration(declaration); + return name && (isComputedPropertyName(name) && isPropertyAccessExpression(name.expression) ? name.expression.name.text + : isPropertyName(name) ? getNameFromPropertyName(name) : undefined); + } + + function visit(node: Node): void { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + const functionDeclaration = node as FunctionLikeDeclaration; + const declarationName = getDeclarationName(functionDeclaration); + + if (declarationName) { + const declarations = getDeclarations(declarationName); + const lastDeclaration = lastOrUndefined(declarations); + + // Check whether this declaration belongs to an "overload group". + if (lastDeclaration && functionDeclaration.parent === lastDeclaration.parent && functionDeclaration.symbol === lastDeclaration.symbol) { + // Overwrite the last declaration if it was an overload + // and this one is an implementation. + if (functionDeclaration.body && !(lastDeclaration as FunctionLikeDeclaration).body) { + declarations[declarations.length - 1] = functionDeclaration; } } - forEachChild(node, visit); + else { + declarations.push(functionDeclaration); + } + } + forEachChild(node, visit); + break; + + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ExportSpecifier: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.TypeLiteral: + addDeclaration(node as Declaration); + forEachChild(node, visit); + break; + + case SyntaxKind.Parameter: + // Only consider parameter properties + if (!hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) { break; + } + // falls through - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.TypeLiteral: - addDeclaration(node as Declaration); - forEachChild(node, visit); + case SyntaxKind.VariableDeclaration: + case SyntaxKind.BindingElement: { + const decl = node as VariableDeclaration; + if (isBindingPattern(decl.name)) { + forEachChild(decl.name, visit); break; + } + if (decl.initializer) { + visit(decl.initializer); + } + } + // falls through + case SyntaxKind.EnumMember: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + addDeclaration(node as Declaration); + break; - case SyntaxKind.Parameter: - // Only consider parameter properties - if (!hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) { - break; - } - // falls through - - case SyntaxKind.VariableDeclaration: - case SyntaxKind.BindingElement: { - const decl = node as VariableDeclaration; - if (isBindingPattern(decl.name)) { - forEachChild(decl.name, visit); - break; + case SyntaxKind.ExportDeclaration: + // Handle named exports case e.g.: + // export {a, b as B} from "mod"; + const exportDeclaration = node as ExportDeclaration; + if (exportDeclaration.exportClause) { + if (isNamedExports(exportDeclaration.exportClause)) { + forEach(exportDeclaration.exportClause.elements, visit); } - if (decl.initializer) { - visit(decl.initializer); + else { + visit(exportDeclaration.exportClause.name); } } - // falls through - case SyntaxKind.EnumMember: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - addDeclaration(node as Declaration); - break; + break; - case SyntaxKind.ExportDeclaration: - // Handle named exports case e.g.: - // export {a, b as B} from "mod"; - const exportDeclaration = node as ExportDeclaration; - if (exportDeclaration.exportClause) { - if (isNamedExports(exportDeclaration.exportClause)) { - forEach(exportDeclaration.exportClause.elements, visit); - } - else { - visit(exportDeclaration.exportClause.name); - } + case SyntaxKind.ImportDeclaration: + const importClause = (node as ImportDeclaration).importClause; + if (importClause) { + // Handle default import case e.g.: + // import d from "mod"; + if (importClause.name) { + addDeclaration(importClause.name); } - break; - case SyntaxKind.ImportDeclaration: - const importClause = (node as ImportDeclaration).importClause; - if (importClause) { - // Handle default import case e.g.: - // import d from "mod"; - if (importClause.name) { - addDeclaration(importClause.name); + // Handle named bindings in imports e.g.: + // import * as NS from "mod"; + // import {a, b as B} from "mod"; + if (importClause.namedBindings) { + if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + addDeclaration(importClause.namedBindings); } - - // Handle named bindings in imports e.g.: - // import * as NS from "mod"; - // import {a, b as B} from "mod"; - if (importClause.namedBindings) { - if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - addDeclaration(importClause.namedBindings); - } - else { - forEach(importClause.namedBindings.elements, visit); - } + else { + forEach(importClause.namedBindings.elements, visit); } } - break; + } + break; - case SyntaxKind.BinaryExpression: - if (getAssignmentDeclarationKind(node as BinaryExpression) !== AssignmentDeclarationKind.None) { - addDeclaration(node as BinaryExpression); - } - // falls through + case SyntaxKind.BinaryExpression: + if (getAssignmentDeclarationKind(node as BinaryExpression) !== AssignmentDeclarationKind.None) { + addDeclaration(node as BinaryExpression); + } + // falls through - default: - forEachChild(node, visit); - } + default: + forEachChild(node, visit); } } } +} - class SourceMapSourceObject implements SourceMapSource { - lineMap!: number[]; - constructor(public fileName: string, public text: string, public skipTrivia?: (pos: number) => number) { } +class SourceMapSourceObject implements SourceMapSource { + lineMap!: number[]; + constructor(public fileName: string, public text: string, public skipTrivia?: (pos: number) => number) { } - public getLineAndCharacterOfPosition(pos: number): LineAndCharacter { - return getLineAndCharacterOfPosition(this, pos); - } + public getLineAndCharacterOfPosition(pos: number): LineAndCharacter { + return getLineAndCharacterOfPosition(this, pos); } +} - function getServicesObjectAllocator(): ObjectAllocator { - return { - getNodeConstructor: () => NodeObject, - getTokenConstructor: () => TokenObject, - - getIdentifierConstructor: () => IdentifierObject, - getPrivateIdentifierConstructor: () => PrivateIdentifierObject, - getSourceFileConstructor: () => SourceFileObject, - getSymbolConstructor: () => SymbolObject, - getTypeConstructor: () => TypeObject, - getSignatureConstructor: () => SignatureObject, - getSourceMapSourceConstructor: () => SourceMapSourceObject, - }; - } +function getServicesObjectAllocator(): ObjectAllocator { + return { + getNodeConstructor: () => NodeObject, + getTokenConstructor: () => TokenObject, + + getIdentifierConstructor: () => IdentifierObject, + getPrivateIdentifierConstructor: () => PrivateIdentifierObject, + getSourceFileConstructor: () => SourceFileObject, + getSymbolConstructor: () => SymbolObject, + getTypeConstructor: () => TypeObject, + getSignatureConstructor: () => SignatureObject, + getSourceMapSourceConstructor: () => SourceMapSourceObject, + }; +} - /// Language Service +/// Language Service - // Information about a specific host file. - interface HostFileInformation { - hostFileName: string; - version: string; - scriptSnapshot: IScriptSnapshot; - scriptKind: ScriptKind; - } +// Information about a specific host file. +interface HostFileInformation { + hostFileName: string; + version: string; + scriptSnapshot: IScriptSnapshot; + scriptKind: ScriptKind; +} - /* @internal */ - export interface DisplayPartsSymbolWriter extends EmitTextWriter { - displayParts(): SymbolDisplayPart[]; - } +/* @internal */ +export interface DisplayPartsSymbolWriter extends EmitTextWriter { + displayParts(): SymbolDisplayPart[]; +} - /* @internal */ - export function toEditorSettings(options: FormatCodeOptions | FormatCodeSettings): FormatCodeSettings; - export function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings; - export function toEditorSettings(optionsAsMap: MapLike): MapLike { - let allPropertiesAreCamelCased = true; - for (const key in optionsAsMap) { - if (hasProperty(optionsAsMap, key) && !isCamelCase(key)) { - allPropertiesAreCamelCased = false; - break; - } - } - if (allPropertiesAreCamelCased) { - return optionsAsMap; +/* @internal */ +export function toEditorSettings(options: FormatCodeOptions | FormatCodeSettings): FormatCodeSettings; +export function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings; +export function toEditorSettings(optionsAsMap: MapLike): MapLike { + let allPropertiesAreCamelCased = true; + for (const key in optionsAsMap) { + if (hasProperty(optionsAsMap, key) && !isCamelCase(key)) { + allPropertiesAreCamelCased = false; + break; } - const settings: MapLike = {}; - for (const key in optionsAsMap) { - if (hasProperty(optionsAsMap, key)) { - const newKey = isCamelCase(key) ? key : key.charAt(0).toLowerCase() + key.substr(1); - settings[newKey] = optionsAsMap[key]; - } + } + if (allPropertiesAreCamelCased) { + return optionsAsMap; + } + const settings: MapLike = {}; + for (const key in optionsAsMap) { + if (hasProperty(optionsAsMap, key)) { + const newKey = isCamelCase(key) ? key : key.charAt(0).toLowerCase() + key.substr(1); + settings[newKey] = optionsAsMap[key]; } - return settings; } + return settings; +} + +function isCamelCase(s: string) { + return !s.length || s.charAt(0) === s.charAt(0).toLowerCase(); +} - function isCamelCase(s: string) { - return !s.length || s.charAt(0) === s.charAt(0).toLowerCase(); +export function displayPartsToString(displayParts: SymbolDisplayPart[] | undefined) { + if (displayParts) { + return map(displayParts, displayPart => displayPart.text).join(""); } - export function displayPartsToString(displayParts: SymbolDisplayPart[] | undefined) { - if (displayParts) { - return map(displayParts, displayPart => displayPart.text).join(""); - } + return ""; +} - return ""; - } +export function getDefaultCompilerOptions(): CompilerOptions { + // Always default to "ScriptTarget.ES5" for the language service + return { + target: ScriptTarget.ES5, + jsx: JsxEmit.Preserve + }; +} - export function getDefaultCompilerOptions(): CompilerOptions { - // Always default to "ScriptTarget.ES5" for the language service - return { - target: ScriptTarget.ES5, - jsx: JsxEmit.Preserve - }; - } +export function getSupportedCodeFixes() { + return codefix.getSupportedErrorCodes(); +} - export function getSupportedCodeFixes() { - return codefix.getSupportedErrorCodes(); - } +// Either it will be file name if host doesnt have file or it will be the host's file information +type CachedHostFileInformation = HostFileInformation | string; - // Either it will be file name if host doesnt have file or it will be the host's file information - type CachedHostFileInformation = HostFileInformation | string; +// Cache host information about script Should be refreshed +// at each language service public entry point, since we don't know when +// the set of scripts handled by the host changes. +class HostCache { + private fileNameToEntry: ESMap; + private currentDirectory: string; - // Cache host information about script Should be refreshed - // at each language service public entry point, since we don't know when - // the set of scripts handled by the host changes. - class HostCache { - private fileNameToEntry: ESMap; - private currentDirectory: string; + constructor(private host: LanguageServiceHost, getCanonicalFileName: GetCanonicalFileName) { + // script id => script index + this.currentDirectory = host.getCurrentDirectory(); + this.fileNameToEntry = new Map(); - constructor(private host: LanguageServiceHost, getCanonicalFileName: GetCanonicalFileName) { - // script id => script index - this.currentDirectory = host.getCurrentDirectory(); - this.fileNameToEntry = new Map(); + // Initialize the list with the root file names + const rootFileNames = host.getScriptFileNames(); + for (const fileName of rootFileNames) { + this.createEntry(fileName, toPath(fileName, this.currentDirectory, getCanonicalFileName)); + } + } - // Initialize the list with the root file names - const rootFileNames = host.getScriptFileNames(); - for (const fileName of rootFileNames) { - this.createEntry(fileName, toPath(fileName, this.currentDirectory, getCanonicalFileName)); - } + private createEntry(fileName: string, path: Path) { + let entry: CachedHostFileInformation; + const scriptSnapshot = this.host.getScriptSnapshot(fileName); + if (scriptSnapshot) { + entry = { + hostFileName: fileName, + version: this.host.getScriptVersion(fileName), + scriptSnapshot, + scriptKind: getScriptKind(fileName, this.host) + }; + } + else { + entry = fileName; } - private createEntry(fileName: string, path: Path) { - let entry: CachedHostFileInformation; - const scriptSnapshot = this.host.getScriptSnapshot(fileName); - if (scriptSnapshot) { - entry = { - hostFileName: fileName, - version: this.host.getScriptVersion(fileName), - scriptSnapshot, - scriptKind: getScriptKind(fileName, this.host) - }; + this.fileNameToEntry.set(path, entry); + return entry; + } + + public getEntryByPath(path: Path): CachedHostFileInformation | undefined { + return this.fileNameToEntry.get(path); + } + + public getHostFileInformation(path: Path): HostFileInformation | undefined { + const entry = this.fileNameToEntry.get(path); + return !isString(entry) ? entry : undefined; + } + + public getOrCreateEntryByPath(fileName: string, path: Path): HostFileInformation { + const info = this.getEntryByPath(path) || this.createEntry(fileName, path); + return isString(info) ? undefined! : info; // TODO: GH#18217 + } + + public getRootFileNames(): string[] { + const names: string[] = []; + this.fileNameToEntry.forEach(entry => { + if (isString(entry)) { + names.push(entry); } else { - entry = fileName; + names.push(entry.hostFileName); } + }); + return names; + } + + public getScriptSnapshot(path: Path): IScriptSnapshot { + const file = this.getHostFileInformation(path); + return (file && file.scriptSnapshot)!; // TODO: GH#18217 + } +} + +class SyntaxTreeCache { + // For our syntactic only features, we also keep a cache of the syntax tree for the + // currently edited file. + private currentFileName: string | undefined; + private currentFileVersion: string | undefined; + private currentFileScriptSnapshot: IScriptSnapshot | undefined; + private currentSourceFile: SourceFile | undefined; + + constructor(private host: LanguageServiceHost) { + } - this.fileNameToEntry.set(path, entry); - return entry; + public getCurrentSourceFile(fileName: string): SourceFile { + const scriptSnapshot = this.host.getScriptSnapshot(fileName); + if (!scriptSnapshot) { + // The host does not know about this file. + throw new Error("Could not find file: '" + fileName + "'."); } - public getEntryByPath(path: Path): CachedHostFileInformation | undefined { - return this.fileNameToEntry.get(path); - } + const scriptKind = getScriptKind(fileName, this.host); + const version = this.host.getScriptVersion(fileName); + let sourceFile: SourceFile | undefined; - public getHostFileInformation(path: Path): HostFileInformation | undefined { - const entry = this.fileNameToEntry.get(path); - return !isString(entry) ? entry : undefined; + if (this.currentFileName !== fileName) { + // This is a new file, just parse it + sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, ScriptTarget.Latest, version, /*setNodeParents*/ true, scriptKind); } - - public getOrCreateEntryByPath(fileName: string, path: Path): HostFileInformation { - const info = this.getEntryByPath(path) || this.createEntry(fileName, path); - return isString(info) ? undefined! : info; // TODO: GH#18217 + else if (this.currentFileVersion !== version) { + // This is the same file, just a newer version. Incrementally parse the file. + const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot!); + sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile!, scriptSnapshot, version, editRange); } - public getRootFileNames(): string[] { - const names: string[] = []; - this.fileNameToEntry.forEach(entry => { - if (isString(entry)) { - names.push(entry); - } - else { - names.push(entry.hostFileName); - } - }); - return names; + if (sourceFile) { + // All done, ensure state is up to date + this.currentFileVersion = version; + this.currentFileName = fileName; + this.currentFileScriptSnapshot = scriptSnapshot; + this.currentSourceFile = sourceFile; } - public getScriptSnapshot(path: Path): IScriptSnapshot { - const file = this.getHostFileInformation(path); - return (file && file.scriptSnapshot)!; // TODO: GH#18217 - } + return this.currentSourceFile!; } +} - class SyntaxTreeCache { - // For our syntactic only features, we also keep a cache of the syntax tree for the - // currently edited file. - private currentFileName: string | undefined; - private currentFileVersion: string | undefined; - private currentFileScriptSnapshot: IScriptSnapshot | undefined; - private currentSourceFile: SourceFile | undefined; +function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string) { + sourceFile.version = version; + sourceFile.scriptSnapshot = scriptSnapshot; +} - constructor(private host: LanguageServiceHost) { - } +export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile { + const sourceFile = createSourceFile(fileName, getSnapshotText(scriptSnapshot), scriptTarget, setNodeParents, scriptKind); + setSourceFileFields(sourceFile, scriptSnapshot, version); + return sourceFile; +} - public getCurrentSourceFile(fileName: string): SourceFile { - const scriptSnapshot = this.host.getScriptSnapshot(fileName); - if (!scriptSnapshot) { - // The host does not know about this file. - throw new Error("Could not find file: '" + fileName + "'."); - } +export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile { + // If we were given a text change range, and our version or open-ness changed, then + // incrementally parse this file. + if (textChangeRange) { + if (version !== sourceFile.version) { + let newText: string; - const scriptKind = getScriptKind(fileName, this.host); - const version = this.host.getScriptVersion(fileName); - let sourceFile: SourceFile | undefined; + // grab the fragment from the beginning of the original text to the beginning of the span + const prefix = textChangeRange.span.start !== 0 + ? sourceFile.text.substr(0, textChangeRange.span.start) + : ""; - if (this.currentFileName !== fileName) { - // This is a new file, just parse it - sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, ScriptTarget.Latest, version, /*setNodeParents*/ true, scriptKind); - } - else if (this.currentFileVersion !== version) { - // This is the same file, just a newer version. Incrementally parse the file. - const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot!); - sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile!, scriptSnapshot, version, editRange); + // grab the fragment from the end of the span till the end of the original text + const suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length + ? sourceFile.text.substr(textSpanEnd(textChangeRange.span)) + : ""; + + if (textChangeRange.newLength === 0) { + // edit was a deletion - just combine prefix and suffix + newText = prefix && suffix ? prefix + suffix : prefix || suffix; } + else { + // it was actual edit, fetch the fragment of new text that correspond to new span + const changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength); + // combine prefix, changed text and suffix + newText = prefix && suffix + ? prefix + changedText + suffix + : prefix + ? (prefix + changedText) + : (changedText + suffix); + } + + const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); + setSourceFileFields(newSourceFile, scriptSnapshot, version); + // after incremental parsing nameTable might not be up-to-date + // drop it so it can be lazily recreated later + newSourceFile.nameTable = undefined; + + // dispose all resources held by old script snapshot + if (sourceFile !== newSourceFile && sourceFile.scriptSnapshot) { + if (sourceFile.scriptSnapshot.dispose) { + sourceFile.scriptSnapshot.dispose(); + } - if (sourceFile) { - // All done, ensure state is up to date - this.currentFileVersion = version; - this.currentFileName = fileName; - this.currentFileScriptSnapshot = scriptSnapshot; - this.currentSourceFile = sourceFile; + sourceFile.scriptSnapshot = undefined; } - return this.currentSourceFile!; + return newSourceFile; } } - function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string) { - sourceFile.version = version; - sourceFile.scriptSnapshot = scriptSnapshot; - } + // Otherwise, just create a new source file. + return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, sourceFile.languageVersion, version, /*setNodeParents*/ true, sourceFile.scriptKind); +} - export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile { - const sourceFile = createSourceFile(fileName, getSnapshotText(scriptSnapshot), scriptTarget, setNodeParents, scriptKind); - setSourceFileFields(sourceFile, scriptSnapshot, version); - return sourceFile; - } +const NoopCancellationToken: CancellationToken = { + isCancellationRequested: returnFalse, + throwIfCancellationRequested: noop, +}; - export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile { - // If we were given a text change range, and our version or open-ness changed, then - // incrementally parse this file. - if (textChangeRange) { - if (version !== sourceFile.version) { - let newText: string; +class CancellationTokenObject implements CancellationToken { + constructor(private cancellationToken: HostCancellationToken) { + } - // grab the fragment from the beginning of the original text to the beginning of the span - const prefix = textChangeRange.span.start !== 0 - ? sourceFile.text.substr(0, textChangeRange.span.start) - : ""; + public isCancellationRequested(): boolean { + return this.cancellationToken.isCancellationRequested(); + } - // grab the fragment from the end of the span till the end of the original text - const suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length - ? sourceFile.text.substr(textSpanEnd(textChangeRange.span)) - : ""; + public throwIfCancellationRequested(): void { + if (this.isCancellationRequested()) { + tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "CancellationTokenObject" }); + throw new OperationCanceledException(); + } + } +} - if (textChangeRange.newLength === 0) { - // edit was a deletion - just combine prefix and suffix - newText = prefix && suffix ? prefix + suffix : prefix || suffix; - } - else { - // it was actual edit, fetch the fragment of new text that correspond to new span - const changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength); - // combine prefix, changed text and suffix - newText = prefix && suffix - ? prefix + changedText + suffix - : prefix - ? (prefix + changedText) - : (changedText + suffix); - } +/* @internal */ +/** A cancellation that throttles calls to the host */ +export class ThrottledCancellationToken implements CancellationToken { + // Store when we last tried to cancel. Checking cancellation can be expensive (as we have + // to marshall over to the host layer). So we only bother actually checking once enough + // time has passed. + private lastCancellationCheckTime = 0; - const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); - setSourceFileFields(newSourceFile, scriptSnapshot, version); - // after incremental parsing nameTable might not be up-to-date - // drop it so it can be lazily recreated later - newSourceFile.nameTable = undefined; + constructor(private hostCancellationToken: HostCancellationToken, private readonly throttleWaitMilliseconds = 20) { + } - // dispose all resources held by old script snapshot - if (sourceFile !== newSourceFile && sourceFile.scriptSnapshot) { - if (sourceFile.scriptSnapshot.dispose) { - sourceFile.scriptSnapshot.dispose(); - } + public isCancellationRequested(): boolean { + const time = timestamp(); + const duration = Math.abs(time - this.lastCancellationCheckTime); + if (duration >= this.throttleWaitMilliseconds) { + // Check no more than once every throttle wait milliseconds + this.lastCancellationCheckTime = time; + return this.hostCancellationToken.isCancellationRequested(); + } - sourceFile.scriptSnapshot = undefined; - } + return false; + } - return newSourceFile; - } + public throwIfCancellationRequested(): void { + if (this.isCancellationRequested()) { + tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "ThrottledCancellationToken" }); + throw new OperationCanceledException(); } + } +} - // Otherwise, just create a new source file. - return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, sourceFile.languageVersion, version, /*setNodeParents*/ true, sourceFile.scriptKind); +const invalidOperationsInPartialSemanticMode: readonly (keyof LanguageService)[] = [ + "getSemanticDiagnostics", + "getSuggestionDiagnostics", + "getCompilerOptionsDiagnostics", + "getSemanticClassifications", + "getEncodedSemanticClassifications", + "getCodeFixesAtPosition", + "getCombinedCodeFix", + "applyCodeActionCommand", + "organizeImports", + "getEditsForFileRename", + "getEmitOutput", + "getApplicableRefactors", + "getEditsForRefactor", + "prepareCallHierarchy", + "provideCallHierarchyIncomingCalls", + "provideCallHierarchyOutgoingCalls", + "provideInlayHints" +]; + +const invalidOperationsInSyntacticMode: readonly (keyof LanguageService)[] = [ + ...invalidOperationsInPartialSemanticMode, + "getCompletionsAtPosition", + "getCompletionEntryDetails", + "getCompletionEntrySymbol", + "getSignatureHelpItems", + "getQuickInfoAtPosition", + "getDefinitionAtPosition", + "getDefinitionAndBoundSpan", + "getImplementationAtPosition", + "getTypeDefinitionAtPosition", + "getReferencesAtPosition", + "findReferences", + "getOccurrencesAtPosition", + "getDocumentHighlights", + "getNavigateToItems", + "getRenameInfo", + "findRenameLocations", + "getApplicableRefactors", +]; +export function createLanguageService( + host: LanguageServiceHost, + documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()), + syntaxOnlyOrLanguageServiceMode?: boolean | LanguageServiceMode, +): LanguageService { + let languageServiceMode: LanguageServiceMode; + if (syntaxOnlyOrLanguageServiceMode === undefined) { + languageServiceMode = LanguageServiceMode.Semantic; + } + else if (typeof syntaxOnlyOrLanguageServiceMode === "boolean") { + // languageServiceMode = SyntaxOnly + languageServiceMode = syntaxOnlyOrLanguageServiceMode ? LanguageServiceMode.Syntactic : LanguageServiceMode.Semantic; + } + else { + languageServiceMode = syntaxOnlyOrLanguageServiceMode; } - const NoopCancellationToken: CancellationToken = { - isCancellationRequested: returnFalse, - throwIfCancellationRequested: noop, - }; + const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); + let program: Program; + let lastProjectVersion: string; + let lastTypesRootVersion = 0; - class CancellationTokenObject implements CancellationToken { - constructor(private cancellationToken: HostCancellationToken) { - } + const cancellationToken = host.getCancellationToken + ? new CancellationTokenObject(host.getCancellationToken()) + : NoopCancellationToken; - public isCancellationRequested(): boolean { - return this.cancellationToken.isCancellationRequested(); - } + const currentDirectory = host.getCurrentDirectory(); + // Check if the localized messages json is set, otherwise query the host for it + if (!localizedDiagnosticMessages && host.getLocalizedDiagnosticMessages) { + setLocalizedDiagnosticMessages(host.getLocalizedDiagnosticMessages()); + } - public throwIfCancellationRequested(): void { - if (this.isCancellationRequested()) { - tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "CancellationTokenObject" }); - throw new OperationCanceledException(); - } + function log(message: string) { + if (host.log) { + host.log(message); } } - /* @internal */ - /** A cancellation that throttles calls to the host */ - export class ThrottledCancellationToken implements CancellationToken { - // Store when we last tried to cancel. Checking cancellation can be expensive (as we have - // to marshall over to the host layer). So we only bother actually checking once enough - // time has passed. - private lastCancellationCheckTime = 0; + const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - constructor(private hostCancellationToken: HostCancellationToken, private readonly throttleWaitMilliseconds = 20) { - } + const sourceMapper = getSourceMapper({ + useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, + getCurrentDirectory: () => currentDirectory, + getProgram, + fileExists: maybeBind(host, host.fileExists), + readFile: maybeBind(host, host.readFile), + getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper), + getSourceFileLike: maybeBind(host, host.getSourceFileLike), + log + }); - public isCancellationRequested(): boolean { - const time = timestamp(); - const duration = Math.abs(time - this.lastCancellationCheckTime); - if (duration >= this.throttleWaitMilliseconds) { - // Check no more than once every throttle wait milliseconds - this.lastCancellationCheckTime = time; - return this.hostCancellationToken.isCancellationRequested(); - } + function getValidSourceFile(fileName: string): SourceFile { + const sourceFile = program.getSourceFile(fileName); + if (!sourceFile) { + const error: Error & PossibleProgramFileInfo = new Error(`Could not find source file: '${fileName}'.`); - return false; - } + // We've been having trouble debugging this, so attach sidecar data for the tsserver log. + // See https://github.com/microsoft/TypeScript/issues/30180. + error.ProgramFiles = program.getSourceFiles().map(f => f.fileName); - public throwIfCancellationRequested(): void { - if (this.isCancellationRequested()) { - tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "ThrottledCancellationToken" }); - throw new OperationCanceledException(); - } + throw error; } + return sourceFile; } - const invalidOperationsInPartialSemanticMode: readonly (keyof LanguageService)[] = [ - "getSemanticDiagnostics", - "getSuggestionDiagnostics", - "getCompilerOptionsDiagnostics", - "getSemanticClassifications", - "getEncodedSemanticClassifications", - "getCodeFixesAtPosition", - "getCombinedCodeFix", - "applyCodeActionCommand", - "organizeImports", - "getEditsForFileRename", - "getEmitOutput", - "getApplicableRefactors", - "getEditsForRefactor", - "prepareCallHierarchy", - "provideCallHierarchyIncomingCalls", - "provideCallHierarchyOutgoingCalls", - "provideInlayHints" - ]; - - const invalidOperationsInSyntacticMode: readonly (keyof LanguageService)[] = [ - ...invalidOperationsInPartialSemanticMode, - "getCompletionsAtPosition", - "getCompletionEntryDetails", - "getCompletionEntrySymbol", - "getSignatureHelpItems", - "getQuickInfoAtPosition", - "getDefinitionAtPosition", - "getDefinitionAndBoundSpan", - "getImplementationAtPosition", - "getTypeDefinitionAtPosition", - "getReferencesAtPosition", - "findReferences", - "getOccurrencesAtPosition", - "getDocumentHighlights", - "getNavigateToItems", - "getRenameInfo", - "findRenameLocations", - "getApplicableRefactors", - ]; - export function createLanguageService( - host: LanguageServiceHost, - documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()), - syntaxOnlyOrLanguageServiceMode?: boolean | LanguageServiceMode, - ): LanguageService { - let languageServiceMode: LanguageServiceMode; - if (syntaxOnlyOrLanguageServiceMode === undefined) { - languageServiceMode = LanguageServiceMode.Semantic; - } - else if (typeof syntaxOnlyOrLanguageServiceMode === "boolean") { - // languageServiceMode = SyntaxOnly - languageServiceMode = syntaxOnlyOrLanguageServiceMode ? LanguageServiceMode.Syntactic : LanguageServiceMode.Semantic; - } - else { - languageServiceMode = syntaxOnlyOrLanguageServiceMode; - } - - const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); - let program: Program; - let lastProjectVersion: string; - let lastTypesRootVersion = 0; - - const cancellationToken = host.getCancellationToken - ? new CancellationTokenObject(host.getCancellationToken()) - : NoopCancellationToken; - - const currentDirectory = host.getCurrentDirectory(); - // Check if the localized messages json is set, otherwise query the host for it - if (!localizedDiagnosticMessages && host.getLocalizedDiagnosticMessages) { - setLocalizedDiagnosticMessages(host.getLocalizedDiagnosticMessages()); - } + function synchronizeHostData(): void { + Debug.assert(languageServiceMode !== LanguageServiceMode.Syntactic); + // perform fast check if host supports it + if (host.getProjectVersion) { + const hostProjectVersion = host.getProjectVersion(); + if (hostProjectVersion) { + if (lastProjectVersion === hostProjectVersion && !host.hasChangedAutomaticTypeDirectiveNames?.()) { + return; + } - function log(message: string) { - if (host.log) { - host.log(message); + lastProjectVersion = hostProjectVersion; } } - const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - - const sourceMapper = getSourceMapper({ - useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, + const typeRootsVersion = host.getTypeRootsVersion ? host.getTypeRootsVersion() : 0; + if (lastTypesRootVersion !== typeRootsVersion) { + log("TypeRoots version has changed; provide new program"); + program = undefined!; // TODO: GH#18217 + lastTypesRootVersion = typeRootsVersion; + } + + // Get a fresh cache of the host information + let hostCache: HostCache | undefined = new HostCache(host, getCanonicalFileName); + const rootFileNames = hostCache.getRootFileNames(); + const newSettings = host.getCompilationSettings() || getDefaultCompilerOptions(); + const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; + const hasChangedAutomaticTypeDirectiveNames = maybeBind(host, host.hasChangedAutomaticTypeDirectiveNames); + const projectReferences = host.getProjectReferences?.(); + let parsedCommandLines: ESMap | undefined; + const parseConfigHost: ParseConfigFileHost = { + useCaseSensitiveFileNames, + fileExists, + readFile, + readDirectory, + trace: maybeBind(host, host.trace), getCurrentDirectory: () => currentDirectory, - getProgram, - fileExists: maybeBind(host, host.fileExists), - readFile: maybeBind(host, host.readFile), - getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper), - getSourceFileLike: maybeBind(host, host.getSourceFileLike), - log - }); + onUnRecoverableConfigFileDiagnostic: noop, + }; - function getValidSourceFile(fileName: string): SourceFile { - const sourceFile = program.getSourceFile(fileName); - if (!sourceFile) { - const error: Error & PossibleProgramFileInfo = new Error(`Could not find source file: '${fileName}'.`); + // If the program is already up-to-date, we can reuse it + if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { + return; + } - // We've been having trouble debugging this, so attach sidecar data for the tsserver log. - // See https://github.com/microsoft/TypeScript/issues/30180. - error.ProgramFiles = program.getSourceFiles().map(f => f.fileName); + // IMPORTANT - It is critical from this moment onward that we do not check + // cancellation tokens. We are about to mutate source files from a previous program + // instance. If we cancel midway through, we may end up in an inconsistent state where + // the program points to old source files that have been invalidated because of + // incremental parsing. - throw error; - } - return sourceFile; + // Now create a new compiler + const compilerHost: CompilerHost = { + getSourceFile: getOrCreateSourceFile, + getSourceFileByPath: getOrCreateSourceFileByPath, + getCancellationToken: () => cancellationToken, + getCanonicalFileName, + useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, + getNewLine: () => getNewLineCharacter(newSettings, () => getNewLineOrDefaultFromHost(host)), + getDefaultLibFileName: options => host.getDefaultLibFileName(options), + writeFile: noop, + getCurrentDirectory: () => currentDirectory, + fileExists, + readFile, + getSymlinkCache: maybeBind(host, host.getSymlinkCache), + realpath: maybeBind(host, host.realpath), + directoryExists: directoryName => { + return directoryProbablyExists(directoryName, host); + }, + getDirectories: path => { + return host.getDirectories ? host.getDirectories(path) : []; + }, + readDirectory, + onReleaseOldSourceFile, + onReleaseParsedCommandLine, + hasInvalidatedResolution, + hasChangedAutomaticTypeDirectiveNames, + trace: parseConfigHost.trace, + resolveModuleNames: maybeBind(host, host.resolveModuleNames), + resolveTypeReferenceDirectives: maybeBind(host, host.resolveTypeReferenceDirectives), + useSourceOfProjectReferenceRedirect: maybeBind(host, host.useSourceOfProjectReferenceRedirect), + getParsedCommandLine, + }; + host.setCompilerHost?.(compilerHost); + + const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); + const options: CreateProgramOptions = { + rootNames: rootFileNames, + options: newSettings, + host: compilerHost, + oldProgram: program, + projectReferences + }; + program = createProgram(options); + + // hostCache is captured in the closure for 'getOrCreateSourceFile' but it should not be used past this point. + // It needs to be cleared to allow all collected snapshots to be released + hostCache = undefined; + parsedCommandLines = undefined; + + // We reset this cache on structure invalidation so we don't hold on to outdated files for long; however we can't use the `compilerHost` above, + // Because it only functions until `hostCache` is cleared, while we'll potentially need the functionality to lazily read sourcemap files during + // the course of whatever called `synchronizeHostData` + sourceMapper.clearCache(); + + // Make sure all the nodes in the program are both bound, and have their parent + // pointers set property. + program.getTypeChecker(); + return; + + function getParsedCommandLine(fileName: string): ParsedCommandLine | undefined { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const existing = parsedCommandLines?.get(path); + if (existing !== undefined) return existing || undefined; + + const result = host.getParsedCommandLine ? + host.getParsedCommandLine(fileName) : + getParsedCommandLineOfConfigFileUsingSourceFile(fileName); + (parsedCommandLines ||= new Map()).set(path, result || false); + return result; } - function synchronizeHostData(): void { - Debug.assert(languageServiceMode !== LanguageServiceMode.Syntactic); - // perform fast check if host supports it - if (host.getProjectVersion) { - const hostProjectVersion = host.getProjectVersion(); - if (hostProjectVersion) { - if (lastProjectVersion === hostProjectVersion && !host.hasChangedAutomaticTypeDirectiveNames?.()) { - return; - } + function getParsedCommandLineOfConfigFileUsingSourceFile(configFileName: string): ParsedCommandLine | undefined { + const result = getOrCreateSourceFile(configFileName, ScriptTarget.JSON) as JsonSourceFile | undefined; + if (!result) return undefined; + result.path = toPath(configFileName, currentDirectory, getCanonicalFileName); + result.resolvedPath = result.path; + result.originalFileName = result.fileName; + return parseJsonSourceFileConfigFileContent( + result, + parseConfigHost, + getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory), + /*optionsToExtend*/ undefined, + getNormalizedAbsolutePath(configFileName, currentDirectory), + ); + } - lastProjectVersion = hostProjectVersion; - } + function onReleaseParsedCommandLine(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, oldOptions: CompilerOptions) { + if (host.getParsedCommandLine) { + host.onReleaseParsedCommandLine?.(configFileName, oldResolvedRef, oldOptions); } - - const typeRootsVersion = host.getTypeRootsVersion ? host.getTypeRootsVersion() : 0; - if (lastTypesRootVersion !== typeRootsVersion) { - log("TypeRoots version has changed; provide new program"); - program = undefined!; // TODO: GH#18217 - lastTypesRootVersion = typeRootsVersion; + else if (oldResolvedRef) { + onReleaseOldSourceFile(oldResolvedRef.sourceFile, oldOptions); } + } - // Get a fresh cache of the host information - let hostCache: HostCache | undefined = new HostCache(host, getCanonicalFileName); - const rootFileNames = hostCache.getRootFileNames(); - const newSettings = host.getCompilationSettings() || getDefaultCompilerOptions(); - const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; - const hasChangedAutomaticTypeDirectiveNames = maybeBind(host, host.hasChangedAutomaticTypeDirectiveNames); - const projectReferences = host.getProjectReferences?.(); - let parsedCommandLines: ESMap | undefined; - const parseConfigHost: ParseConfigFileHost = { - useCaseSensitiveFileNames, - fileExists, - readFile, - readDirectory, - trace: maybeBind(host, host.trace), - getCurrentDirectory: () => currentDirectory, - onUnRecoverableConfigFileDiagnostic: noop, - }; + function fileExists(fileName: string): boolean { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const entry = hostCache && hostCache.getEntryByPath(path); + return entry ? + !isString(entry) : + (!!host.fileExists && host.fileExists(fileName)); + } - // If the program is already up-to-date, we can reuse it - if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { - return; + function readFile(fileName: string) { + // stub missing host functionality + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const entry = hostCache && hostCache.getEntryByPath(path); + if (entry) { + return isString(entry) ? undefined : getSnapshotText(entry.scriptSnapshot); } + return host.readFile && host.readFile(fileName); + } - // IMPORTANT - It is critical from this moment onward that we do not check - // cancellation tokens. We are about to mutate source files from a previous program - // instance. If we cancel midway through, we may end up in an inconsistent state where - // the program points to old source files that have been invalidated because of - // incremental parsing. - - // Now create a new compiler - const compilerHost: CompilerHost = { - getSourceFile: getOrCreateSourceFile, - getSourceFileByPath: getOrCreateSourceFileByPath, - getCancellationToken: () => cancellationToken, - getCanonicalFileName, - useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, - getNewLine: () => getNewLineCharacter(newSettings, () => getNewLineOrDefaultFromHost(host)), - getDefaultLibFileName: options => host.getDefaultLibFileName(options), - writeFile: noop, - getCurrentDirectory: () => currentDirectory, - fileExists, - readFile, - getSymlinkCache: maybeBind(host, host.getSymlinkCache), - realpath: maybeBind(host, host.realpath), - directoryExists: directoryName => { - return directoryProbablyExists(directoryName, host); - }, - getDirectories: path => { - return host.getDirectories ? host.getDirectories(path) : []; - }, - readDirectory, - onReleaseOldSourceFile, - onReleaseParsedCommandLine, - hasInvalidatedResolution, - hasChangedAutomaticTypeDirectiveNames, - trace: parseConfigHost.trace, - resolveModuleNames: maybeBind(host, host.resolveModuleNames), - resolveTypeReferenceDirectives: maybeBind(host, host.resolveTypeReferenceDirectives), - useSourceOfProjectReferenceRedirect: maybeBind(host, host.useSourceOfProjectReferenceRedirect), - getParsedCommandLine, - }; - host.setCompilerHost?.(compilerHost); - - const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); - const options: CreateProgramOptions = { - rootNames: rootFileNames, - options: newSettings, - host: compilerHost, - oldProgram: program, - projectReferences - }; - program = createProgram(options); - - // hostCache is captured in the closure for 'getOrCreateSourceFile' but it should not be used past this point. - // It needs to be cleared to allow all collected snapshots to be released - hostCache = undefined; - parsedCommandLines = undefined; - - // We reset this cache on structure invalidation so we don't hold on to outdated files for long; however we can't use the `compilerHost` above, - // Because it only functions until `hostCache` is cleared, while we'll potentially need the functionality to lazily read sourcemap files during - // the course of whatever called `synchronizeHostData` - sourceMapper.clearCache(); - - // Make sure all the nodes in the program are both bound, and have their parent - // pointers set property. - program.getTypeChecker(); - return; - - function getParsedCommandLine(fileName: string): ParsedCommandLine | undefined { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const existing = parsedCommandLines?.get(path); - if (existing !== undefined) return existing || undefined; - - const result = host.getParsedCommandLine ? - host.getParsedCommandLine(fileName) : - getParsedCommandLineOfConfigFileUsingSourceFile(fileName); - (parsedCommandLines ||= new Map()).set(path, result || false); - return result; - } + function readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number) { + Debug.checkDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'"); + return host.readDirectory!(path, extensions, exclude, include, depth); + } - function getParsedCommandLineOfConfigFileUsingSourceFile(configFileName: string): ParsedCommandLine | undefined { - const result = getOrCreateSourceFile(configFileName, ScriptTarget.JSON) as JsonSourceFile | undefined; - if (!result) return undefined; - result.path = toPath(configFileName, currentDirectory, getCanonicalFileName); - result.resolvedPath = result.path; - result.originalFileName = result.fileName; - return parseJsonSourceFileConfigFileContent( - result, - parseConfigHost, - getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory), - /*optionsToExtend*/ undefined, - getNormalizedAbsolutePath(configFileName, currentDirectory), - ); - } + // Release any files we have acquired in the old program but are + // not part of the new program. + function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) { + const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions); + documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, oldSettingsKey, oldSourceFile.scriptKind); + } - function onReleaseParsedCommandLine(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, oldOptions: CompilerOptions) { - if (host.getParsedCommandLine) { - host.onReleaseParsedCommandLine?.(configFileName, oldResolvedRef, oldOptions); - } - else if (oldResolvedRef) { - onReleaseOldSourceFile(oldResolvedRef.sourceFile, oldOptions); - } - } + function getOrCreateSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { + return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile); + } - function fileExists(fileName: string): boolean { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const entry = hostCache && hostCache.getEntryByPath(path); - return entry ? - !isString(entry) : - (!!host.fileExists && host.fileExists(fileName)); + function getOrCreateSourceFileByPath(fileName: string, path: Path, _languageVersion: ScriptTarget, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { + Debug.assert(hostCache !== undefined, "getOrCreateSourceFileByPath called after typical CompilerHost lifetime, check the callstack something with a reference to an old host."); + // The program is asking for this file, check first if the host can locate it. + // If the host can not locate the file, then it does not exist. return undefined + // to the program to allow reporting of errors for missing files. + const hostFileInformation = hostCache && hostCache.getOrCreateEntryByPath(fileName, path); + if (!hostFileInformation) { + return undefined; } - function readFile(fileName: string) { - // stub missing host functionality - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const entry = hostCache && hostCache.getEntryByPath(path); - if (entry) { - return isString(entry) ? undefined : getSnapshotText(entry.scriptSnapshot); + // Check if the language version has changed since we last created a program; if they are the same, + // it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile + // can not be reused. we have to dump all syntax trees and create new ones. + if (!shouldCreateNewSourceFile) { + // Check if the old program had this file already + const oldSourceFile = program && program.getSourceFileByPath(path); + if (oldSourceFile) { + // We already had a source file for this file name. Go to the registry to + // ensure that we get the right up to date version of it. We need this to + // address the following race-condition. Specifically, say we have the following: + // + // LS1 + // \ + // DocumentRegistry + // / + // LS2 + // + // Each LS has a reference to file 'foo.ts' at version 1. LS2 then updates + // it's version of 'foo.ts' to version 2. This will cause LS2 and the + // DocumentRegistry to have version 2 of the document. However, LS1 will + // have version 1. And *importantly* this source file will be *corrupt*. + // The act of creating version 2 of the file irrevocably damages the version + // 1 file. + // + // So, later when we call into LS1, we need to make sure that it doesn't use + // it's source file any more, and instead defers to DocumentRegistry to get + // either version 1, version 2 (or some other version) depending on what the + // host says should be used. + + // We do not support the scenario where a host can modify a registered + // file's script kind, i.e. in one project some file is treated as ".ts" + // and in another as ".js" + if (hostFileInformation.scriptKind === oldSourceFile.scriptKind) { + return documentRegistry.updateDocumentWithKey(fileName, path, newSettings, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind); + } + else { + // Release old source file and fall through to aquire new file with new script kind + documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()), oldSourceFile.scriptKind); + } } - return host.readFile && host.readFile(fileName); - } - function readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number) { - Debug.checkDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'"); - return host.readDirectory!(path, extensions, exclude, include, depth); + // We didn't already have the file. Fall through and acquire it from the registry. } - // Release any files we have acquired in the old program but are - // not part of the new program. - function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) { - const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions); - documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, oldSettingsKey, oldSourceFile.scriptKind); - } - - function getOrCreateSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { - return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile); - } - - function getOrCreateSourceFileByPath(fileName: string, path: Path, _languageVersion: ScriptTarget, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { - Debug.assert(hostCache !== undefined, "getOrCreateSourceFileByPath called after typical CompilerHost lifetime, check the callstack something with a reference to an old host."); - // The program is asking for this file, check first if the host can locate it. - // If the host can not locate the file, then it does not exist. return undefined - // to the program to allow reporting of errors for missing files. - const hostFileInformation = hostCache && hostCache.getOrCreateEntryByPath(fileName, path); - if (!hostFileInformation) { - return undefined; - } - - // Check if the language version has changed since we last created a program; if they are the same, - // it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile - // can not be reused. we have to dump all syntax trees and create new ones. - if (!shouldCreateNewSourceFile) { - // Check if the old program had this file already - const oldSourceFile = program && program.getSourceFileByPath(path); - if (oldSourceFile) { - // We already had a source file for this file name. Go to the registry to - // ensure that we get the right up to date version of it. We need this to - // address the following race-condition. Specifically, say we have the following: - // - // LS1 - // \ - // DocumentRegistry - // / - // LS2 - // - // Each LS has a reference to file 'foo.ts' at version 1. LS2 then updates - // it's version of 'foo.ts' to version 2. This will cause LS2 and the - // DocumentRegistry to have version 2 of the document. However, LS1 will - // have version 1. And *importantly* this source file will be *corrupt*. - // The act of creating version 2 of the file irrevocably damages the version - // 1 file. - // - // So, later when we call into LS1, we need to make sure that it doesn't use - // it's source file any more, and instead defers to DocumentRegistry to get - // either version 1, version 2 (or some other version) depending on what the - // host says should be used. - - // We do not support the scenario where a host can modify a registered - // file's script kind, i.e. in one project some file is treated as ".ts" - // and in another as ".js" - if (hostFileInformation.scriptKind === oldSourceFile.scriptKind) { - return documentRegistry.updateDocumentWithKey(fileName, path, newSettings, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind); - } - else { - // Release old source file and fall through to aquire new file with new script kind - documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()), oldSourceFile.scriptKind); - } - } - - // We didn't already have the file. Fall through and acquire it from the registry. - } + // Could not find this file in the old program, create a new SourceFile for it. + return documentRegistry.acquireDocumentWithKey(fileName, path, newSettings, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind); + } + } - // Could not find this file in the old program, create a new SourceFile for it. - return documentRegistry.acquireDocumentWithKey(fileName, path, newSettings, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind); - } + // TODO: GH#18217 frequently asserted as defined + function getProgram(): Program | undefined { + if (languageServiceMode === LanguageServiceMode.Syntactic) { + Debug.assert(program === undefined); + return undefined; } - // TODO: GH#18217 frequently asserted as defined - function getProgram(): Program | undefined { - if (languageServiceMode === LanguageServiceMode.Syntactic) { - Debug.assert(program === undefined); - return undefined; - } + synchronizeHostData(); - synchronizeHostData(); + return program; + } - return program; - } + function getAutoImportProvider(): Program | undefined { + return host.getPackageJsonAutoImportProvider?.(); + } - function getAutoImportProvider(): Program | undefined { - return host.getPackageJsonAutoImportProvider?.(); - } + function cleanupSemanticCache(): void { + program = undefined!; // TODO: GH#18217 + } - function cleanupSemanticCache(): void { + function dispose(): void { + if (program) { + // Use paths to ensure we are using correct key and paths as document registry could be created with different current directory than host + const key = documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()); + forEach(program.getSourceFiles(), f => + documentRegistry.releaseDocumentWithKey(f.resolvedPath, key, f.scriptKind)); program = undefined!; // TODO: GH#18217 } + host = undefined!; + } - function dispose(): void { - if (program) { - // Use paths to ensure we are using correct key and paths as document registry could be created with different current directory than host - const key = documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()); - forEach(program.getSourceFiles(), f => - documentRegistry.releaseDocumentWithKey(f.resolvedPath, key, f.scriptKind)); - program = undefined!; // TODO: GH#18217 - } - host = undefined!; - } - - /// Diagnostics - function getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[] { - synchronizeHostData(); - - return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken).slice(); - } + /// Diagnostics + function getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[] { + synchronizeHostData(); - /** - * getSemanticDiagnostics return array of Diagnostics. If '-d' is not enabled, only report semantic errors - * If '-d' enabled, report both semantic and emitter errors - */ - function getSemanticDiagnostics(fileName: string): Diagnostic[] { - synchronizeHostData(); + return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken).slice(); + } - const targetSourceFile = getValidSourceFile(fileName); + /** + * getSemanticDiagnostics return array of Diagnostics. If '-d' is not enabled, only report semantic errors + * If '-d' enabled, report both semantic and emitter errors + */ + function getSemanticDiagnostics(fileName: string): Diagnostic[] { + synchronizeHostData(); - // Only perform the action per file regardless of '-out' flag as LanguageServiceHost is expected to call this function per file. - // Therefore only get diagnostics for given file. + const targetSourceFile = getValidSourceFile(fileName); - const semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken); - if (!getEmitDeclarations(program.getCompilerOptions())) { - return semanticDiagnostics.slice(); - } + // Only perform the action per file regardless of '-out' flag as LanguageServiceHost is expected to call this function per file. + // Therefore only get diagnostics for given file. - // If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface - const declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile, cancellationToken); - return [...semanticDiagnostics, ...declarationDiagnostics]; + const semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken); + if (!getEmitDeclarations(program.getCompilerOptions())) { + return semanticDiagnostics.slice(); } - function getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[] { - synchronizeHostData(); - return computeSuggestionDiagnostics(getValidSourceFile(fileName), program, cancellationToken); - } + // If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface + const declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile, cancellationToken); + return [...semanticDiagnostics, ...declarationDiagnostics]; + } - function getCompilerOptionsDiagnostics() { - synchronizeHostData(); - return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)]; - } + function getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[] { + synchronizeHostData(); + return computeSuggestionDiagnostics(getValidSourceFile(fileName), program, cancellationToken); + } - function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = emptyOptions): CompletionInfo | undefined { - // Convert from deprecated options names to new names - const fullPreferences: UserPreferences = { - ...identity(options), // avoid excess property check - includeCompletionsForModuleExports: options.includeCompletionsForModuleExports || options.includeExternalModuleExports, - includeCompletionsWithInsertText: options.includeCompletionsWithInsertText || options.includeInsertTextCompletions, - }; - synchronizeHostData(); - return Completions.getCompletionsAtPosition( - host, - program, - log, - getValidSourceFile(fileName), - position, - fullPreferences, - options.triggerCharacter, - options.triggerKind, - cancellationToken); - } - - function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions, data?: CompletionEntryData): CompletionEntryDetails | undefined { - synchronizeHostData(); - return Completions.getCompletionEntryDetails( - program, - log, - getValidSourceFile(fileName), - position, - { name, source, data }, - host, - (formattingOptions && formatting.getFormatContext(formattingOptions, host))!, // TODO: GH#18217 - preferences, - cancellationToken, - ); - } + function getCompilerOptionsDiagnostics() { + synchronizeHostData(); + return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)]; + } - function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string, preferences: UserPreferences = emptyOptions): Symbol | undefined { - synchronizeHostData(); - return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host, preferences); - } + function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = emptyOptions): CompletionInfo | undefined { + // Convert from deprecated options names to new names + const fullPreferences: UserPreferences = { + ...identity(options), // avoid excess property check + includeCompletionsForModuleExports: options.includeCompletionsForModuleExports || options.includeExternalModuleExports, + includeCompletionsWithInsertText: options.includeCompletionsWithInsertText || options.includeInsertTextCompletions, + }; + synchronizeHostData(); + return Completions.getCompletionsAtPosition( + host, + program, + log, + getValidSourceFile(fileName), + position, + fullPreferences, + options.triggerCharacter, + options.triggerKind, + cancellationToken); + } - function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined { - synchronizeHostData(); + function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions, data?: CompletionEntryData): CompletionEntryDetails | undefined { + synchronizeHostData(); + return Completions.getCompletionEntryDetails( + program, + log, + getValidSourceFile(fileName), + position, + { name, source, data }, + host, + (formattingOptions && formatting.getFormatContext(formattingOptions, host))!, // TODO: GH#18217 + preferences, + cancellationToken, + ); + } - const sourceFile = getValidSourceFile(fileName); - const node = getTouchingPropertyName(sourceFile, position); - if (node === sourceFile) { - // Avoid giving quickInfo for the sourceFile as a whole. - return undefined; - } + function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string, preferences: UserPreferences = emptyOptions): Symbol | undefined { + synchronizeHostData(); + return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host, preferences); + } - const typeChecker = program.getTypeChecker(); - const nodeForQuickInfo = getNodeForQuickInfo(node); - const symbol = getSymbolAtLocationForQuickInfo(nodeForQuickInfo, typeChecker); - - if (!symbol || typeChecker.isUnknownSymbol(symbol)) { - const type = shouldGetType(sourceFile, nodeForQuickInfo, position) ? typeChecker.getTypeAtLocation(nodeForQuickInfo) : undefined; - return type && { - kind: ScriptElementKind.unknown, - kindModifiers: ScriptElementKindModifier.none, - textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile), - displayParts: typeChecker.runWithCancellationToken(cancellationToken, typeChecker => typeToDisplayParts(typeChecker, type, getContainerNode(nodeForQuickInfo))), - documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined, - tags: type.symbol ? type.symbol.getJsDocTags(typeChecker) : undefined - }; - } + function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined { + synchronizeHostData(); - const { symbolKind, displayParts, documentation, tags } = typeChecker.runWithCancellationToken(cancellationToken, typeChecker => - SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(nodeForQuickInfo), nodeForQuickInfo) - ); - return { - kind: symbolKind, - kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), - textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile), - displayParts, - documentation, - tags, - }; + const sourceFile = getValidSourceFile(fileName); + const node = getTouchingPropertyName(sourceFile, position); + if (node === sourceFile) { + // Avoid giving quickInfo for the sourceFile as a whole. + return undefined; } - function getNodeForQuickInfo(node: Node): Node { - if (isNewExpression(node.parent) && node.pos === node.parent.pos) { - return node.parent.expression; - } - if (isNamedTupleMember(node.parent) && node.pos === node.parent.pos) { - return node.parent; - } - return node; - } + const typeChecker = program.getTypeChecker(); + const nodeForQuickInfo = getNodeForQuickInfo(node); + const symbol = getSymbolAtLocationForQuickInfo(nodeForQuickInfo, typeChecker); - function shouldGetType(sourceFile: SourceFile, node: Node, position: number): boolean { - switch (node.kind) { - case SyntaxKind.Identifier: - return !isLabelName(node) && !isTagName(node) && !isConstTypeReference(node.parent); - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.QualifiedName: - // Don't return quickInfo if inside the comment in `a/**/.b` - return !isInComment(sourceFile, position); - case SyntaxKind.ThisKeyword: - case SyntaxKind.ThisType: - case SyntaxKind.SuperKeyword: - case SyntaxKind.NamedTupleMember: - return true; - default: - return false; - } + if (!symbol || typeChecker.isUnknownSymbol(symbol)) { + const type = shouldGetType(sourceFile, nodeForQuickInfo, position) ? typeChecker.getTypeAtLocation(nodeForQuickInfo) : undefined; + return type && { + kind: ScriptElementKind.unknown, + kindModifiers: ScriptElementKindModifier.none, + textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile), + displayParts: typeChecker.runWithCancellationToken(cancellationToken, typeChecker => typeToDisplayParts(typeChecker, type, getContainerNode(nodeForQuickInfo))), + documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined, + tags: type.symbol ? type.symbol.getJsDocTags(typeChecker) : undefined + }; } - /// Goto definition - function getDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined { - synchronizeHostData(); - return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position); - } + const { symbolKind, displayParts, documentation, tags } = typeChecker.runWithCancellationToken(cancellationToken, typeChecker => + SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(nodeForQuickInfo), nodeForQuickInfo) + ); + return { + kind: symbolKind, + kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), + textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile), + displayParts, + documentation, + tags, + }; + } - function getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined { - synchronizeHostData(); - return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position); + function getNodeForQuickInfo(node: Node): Node { + if (isNewExpression(node.parent) && node.pos === node.parent.pos) { + return node.parent.expression; } - - function getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined { - synchronizeHostData(); - return GoToDefinition.getTypeDefinitionAtPosition(program.getTypeChecker(), getValidSourceFile(fileName), position); + if (isNamedTupleMember(node.parent) && node.pos === node.parent.pos) { + return node.parent; } + return node; + } - /// Goto implementation - - function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] | undefined { - synchronizeHostData(); - return FindAllReferences.getImplementationsAtPosition(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); + function shouldGetType(sourceFile: SourceFile, node: Node, position: number): boolean { + switch (node.kind) { + case SyntaxKind.Identifier: + return !isLabelName(node) && !isTagName(node) && !isConstTypeReference(node.parent); + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.QualifiedName: + // Don't return quickInfo if inside the comment in `a/**/.b` + return !isInComment(sourceFile, position); + case SyntaxKind.ThisKeyword: + case SyntaxKind.ThisType: + case SyntaxKind.SuperKeyword: + case SyntaxKind.NamedTupleMember: + return true; + default: + return false; } + } - /// References and Occurrences - function getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined { - return flatMap( - getDocumentHighlights(fileName, position, [fileName]), - entry => entry.highlightSpans.map(highlightSpan => ({ - fileName: entry.fileName, - textSpan: highlightSpan.textSpan, - isWriteAccess: highlightSpan.kind === HighlightSpanKind.writtenReference, - isDefinition: false, - ...highlightSpan.isInString && { isInString: true }, - ...highlightSpan.contextSpan && { contextSpan: highlightSpan.contextSpan } - })) - ); - } + /// Goto definition + function getDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined { + synchronizeHostData(); + return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position); + } - function getDocumentHighlights(fileName: string, position: number, filesToSearch: readonly string[]): DocumentHighlights[] | undefined { - const normalizedFileName = normalizePath(fileName); - Debug.assert(filesToSearch.some(f => normalizePath(f) === normalizedFileName)); - synchronizeHostData(); - const sourceFilesToSearch = mapDefined(filesToSearch, fileName => program.getSourceFile(fileName)); - const sourceFile = getValidSourceFile(fileName); - return DocumentHighlights.getDocumentHighlights(program, cancellationToken, sourceFile, position, sourceFilesToSearch); - } - - function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] | undefined { - synchronizeHostData(); - const sourceFile = getValidSourceFile(fileName); - const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position)); - if (!Rename.nodeIsEligibleForRename(node)) return undefined; - if (isIdentifier(node) && (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) && isIntrinsicJsxName(node.escapedText)) { - const { openingElement, closingElement } = node.parent.parent; - return [openingElement, closingElement].map((node): RenameLocation => { - const textSpan = createTextSpanFromNode(node.tagName, sourceFile); - return { - fileName: sourceFile.fileName, - textSpan, - ...FindAllReferences.toContextSpan(textSpan, sourceFile, node.parent) - }; - }); - } - else { - return getReferencesWorker(node, position, { findInStrings, findInComments, providePrefixAndSuffixTextForRename, use: FindAllReferences.FindReferencesUse.Rename }, - (entry, originalNode, checker) => FindAllReferences.toRenameLocation(entry, originalNode, checker, providePrefixAndSuffixTextForRename || false)); - } - } + function getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined { + synchronizeHostData(); + return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position); + } - function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined { - synchronizeHostData(); - return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, { use: FindAllReferences.FindReferencesUse.References }, (entry, node, checker) => FindAllReferences.toReferenceEntry(entry, checker.getSymbolAtLocation(node))); - } + function getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined { + synchronizeHostData(); + return GoToDefinition.getTypeDefinitionAtPosition(program.getTypeChecker(), getValidSourceFile(fileName), position); + } - function getReferencesWorker(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry): T[] | undefined { - synchronizeHostData(); + /// Goto implementation - // Exclude default library when renaming as commonly user don't want to change that file. - const sourceFiles = options && options.use === FindAllReferences.FindReferencesUse.Rename - ? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile)) - : program.getSourceFiles(); + function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] | undefined { + synchronizeHostData(); + return FindAllReferences.getImplementationsAtPosition(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); + } - return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, options, cb); - } + /// References and Occurrences + function getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined { + return flatMap( + getDocumentHighlights(fileName, position, [fileName]), + entry => entry.highlightSpans.map(highlightSpan => ({ + fileName: entry.fileName, + textSpan: highlightSpan.textSpan, + isWriteAccess: highlightSpan.kind === HighlightSpanKind.writtenReference, + isDefinition: false, + ...highlightSpan.isInString && { isInString: true }, + ...highlightSpan.contextSpan && { contextSpan: highlightSpan.contextSpan } + })) + ); + } - function findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined { - synchronizeHostData(); - return FindAllReferences.findReferencedSymbols(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); - } + function getDocumentHighlights(fileName: string, position: number, filesToSearch: readonly string[]): DocumentHighlights[] | undefined { + const normalizedFileName = normalizePath(fileName); + Debug.assert(filesToSearch.some(f => normalizePath(f) === normalizedFileName)); + synchronizeHostData(); + const sourceFilesToSearch = mapDefined(filesToSearch, fileName => program.getSourceFile(fileName)); + const sourceFile = getValidSourceFile(fileName); + return DocumentHighlights.getDocumentHighlights(program, cancellationToken, sourceFile, position, sourceFilesToSearch); + } - function getFileReferences(fileName: string): ReferenceEntry[] { - synchronizeHostData(); - const moduleSymbol = program.getSourceFile(fileName)?.symbol; - return FindAllReferences.Core.getReferencesForFileName(fileName, program, program.getSourceFiles()).map(r => FindAllReferences.toReferenceEntry(r, moduleSymbol)); + function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] | undefined { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position)); + if (!Rename.nodeIsEligibleForRename(node)) return undefined; + if (isIdentifier(node) && (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) && isIntrinsicJsxName(node.escapedText)) { + const { openingElement, closingElement } = node.parent.parent; + return [openingElement, closingElement].map((node): RenameLocation => { + const textSpan = createTextSpanFromNode(node.tagName, sourceFile); + return { + fileName: sourceFile.fileName, + textSpan, + ...FindAllReferences.toContextSpan(textSpan, sourceFile, node.parent) + }; + }); } - - function getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles = false): NavigateToItem[] { - synchronizeHostData(); - const sourceFiles = fileName ? [getValidSourceFile(fileName)] : program.getSourceFiles(); - return NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles); + else { + return getReferencesWorker(node, position, { findInStrings, findInComments, providePrefixAndSuffixTextForRename, use: FindAllReferences.FindReferencesUse.Rename }, + (entry, originalNode, checker) => FindAllReferences.toRenameLocation(entry, originalNode, checker, providePrefixAndSuffixTextForRename || false)); } + } - function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean) { - synchronizeHostData(); - - const sourceFile = getValidSourceFile(fileName); - const customTransformers = host.getCustomTransformers && host.getCustomTransformers(); - return getFileEmitOutput(program, sourceFile, !!emitOnlyDtsFiles, cancellationToken, customTransformers, forceDtsEmit); - } + function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined { + synchronizeHostData(); + return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, { use: FindAllReferences.FindReferencesUse.References }, (entry, node, checker) => FindAllReferences.toReferenceEntry(entry, checker.getSymbolAtLocation(node))); + } - // Signature help - /** - * This is a semantic operation. - */ - function getSignatureHelpItems(fileName: string, position: number, { triggerReason }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined { - synchronizeHostData(); + function getReferencesWorker(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry): T[] | undefined { + synchronizeHostData(); - const sourceFile = getValidSourceFile(fileName); + // Exclude default library when renaming as commonly user don't want to change that file. + const sourceFiles = options && options.use === FindAllReferences.FindReferencesUse.Rename + ? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile)) + : program.getSourceFiles(); - return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerReason, cancellationToken); - } + return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, options, cb); + } - /// Syntactic features - function getNonBoundSourceFile(fileName: string): SourceFile { - return syntaxTreeCache.getCurrentSourceFile(fileName); - } + function findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined { + synchronizeHostData(); + return FindAllReferences.findReferencedSymbols(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); + } - function getNameOrDottedNameSpan(fileName: string, startPos: number, _endPos: number): TextSpan | undefined { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + function getFileReferences(fileName: string): ReferenceEntry[] { + synchronizeHostData(); + const moduleSymbol = program.getSourceFile(fileName)?.symbol; + return FindAllReferences.Core.getReferencesForFileName(fileName, program, program.getSourceFiles()).map(r => FindAllReferences.toReferenceEntry(r, moduleSymbol)); + } - // Get node at the location - const node = getTouchingPropertyName(sourceFile, startPos); + function getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles = false): NavigateToItem[] { + synchronizeHostData(); + const sourceFiles = fileName ? [getValidSourceFile(fileName)] : program.getSourceFiles(); + return NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles); + } - if (node === sourceFile) { - return undefined; - } + function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean) { + synchronizeHostData(); - switch (node.kind) { - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.QualifiedName: - case SyntaxKind.StringLiteral: - case SyntaxKind.FalseKeyword: - case SyntaxKind.TrueKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.SuperKeyword: - case SyntaxKind.ThisKeyword: - case SyntaxKind.ThisType: - case SyntaxKind.Identifier: - break; + const sourceFile = getValidSourceFile(fileName); + const customTransformers = host.getCustomTransformers && host.getCustomTransformers(); + return getFileEmitOutput(program, sourceFile, !!emitOnlyDtsFiles, cancellationToken, customTransformers, forceDtsEmit); + } - // Cant create the text span - default: - return undefined; - } + // Signature help + /** + * This is a semantic operation. + */ + function getSignatureHelpItems(fileName: string, position: number, { triggerReason }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined { + synchronizeHostData(); - let nodeForStartPos = node; - while (true) { - if (isRightSideOfPropertyAccess(nodeForStartPos) || isRightSideOfQualifiedName(nodeForStartPos)) { - // If on the span is in right side of the the property or qualified name, return the span from the qualified name pos to end of this node - nodeForStartPos = nodeForStartPos.parent; - } - else if (isNameOfModuleDeclaration(nodeForStartPos)) { - // If this is name of a module declarations, check if this is right side of dotted module name - // If parent of the module declaration which is parent of this node is module declaration and its body is the module declaration that this node is name of - // Then this name is name from dotted module - if (nodeForStartPos.parent.parent.kind === SyntaxKind.ModuleDeclaration && - (nodeForStartPos.parent.parent as ModuleDeclaration).body === nodeForStartPos.parent) { - // Use parent module declarations name for start pos - nodeForStartPos = (nodeForStartPos.parent.parent as ModuleDeclaration).name; - } - else { - // We have to use this name for start pos - break; - } - } - else { - // Is not a member expression so we have found the node for start pos - break; - } - } + const sourceFile = getValidSourceFile(fileName); - return createTextSpanFromBounds(nodeForStartPos.getStart(), node.getEnd()); - } + return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerReason, cancellationToken); + } - function getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined { - // doesn't use compiler - no need to synchronize with host - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + /// Syntactic features + function getNonBoundSourceFile(fileName: string): SourceFile { + return syntaxTreeCache.getCurrentSourceFile(fileName); + } - return BreakpointResolver.spanInSourceFileAtLocation(sourceFile, position); - } + function getNameOrDottedNameSpan(fileName: string, startPos: number, _endPos: number): TextSpan | undefined { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - function getNavigationBarItems(fileName: string): NavigationBarItem[] { - return NavigationBar.getNavigationBarItems(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken); - } + // Get node at the location + const node = getTouchingPropertyName(sourceFile, startPos); - function getNavigationTree(fileName: string): NavigationTree { - return NavigationBar.getNavigationTree(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken); + if (node === sourceFile) { + return undefined; } - function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; - function getSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[] { - synchronizeHostData(); + switch (node.kind) { + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.QualifiedName: + case SyntaxKind.StringLiteral: + case SyntaxKind.FalseKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.ThisKeyword: + case SyntaxKind.ThisType: + case SyntaxKind.Identifier: + break; - const responseFormat = format || SemanticClassificationFormat.Original; - if (responseFormat === SemanticClassificationFormat.TwentyTwenty) { - return classifier.v2020.getSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span); - } - else { - return ts.getSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span); - } + // Cant create the text span + default: + return undefined; } - function getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications { - synchronizeHostData(); - - const responseFormat = format || SemanticClassificationFormat.Original; - if (responseFormat === SemanticClassificationFormat.Original) { - return ts.getEncodedSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span); + let nodeForStartPos = node; + while (true) { + if (isRightSideOfPropertyAccess(nodeForStartPos) || isRightSideOfQualifiedName(nodeForStartPos)) { + // If on the span is in right side of the the property or qualified name, return the span from the qualified name pos to end of this node + nodeForStartPos = nodeForStartPos.parent; + } + else if (isNameOfModuleDeclaration(nodeForStartPos)) { + // If this is name of a module declarations, check if this is right side of dotted module name + // If parent of the module declaration which is parent of this node is module declaration and its body is the module declaration that this node is name of + // Then this name is name from dotted module + if (nodeForStartPos.parent.parent.kind === SyntaxKind.ModuleDeclaration && + (nodeForStartPos.parent.parent as ModuleDeclaration).body === nodeForStartPos.parent) { + // Use parent module declarations name for start pos + nodeForStartPos = (nodeForStartPos.parent.parent as ModuleDeclaration).name; + } + else { + // We have to use this name for start pos + break; + } } else { - return classifier.v2020.getEncodedSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span); + // Is not a member expression so we have found the node for start pos + break; } } - function getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] { - // doesn't use compiler - no need to synchronize with host - return ts.getSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span); - } - - function getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications { - // doesn't use compiler - no need to synchronize with host - return ts.getEncodedSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span); - } - - function getOutliningSpans(fileName: string): OutliningSpan[] { - // doesn't use compiler - no need to synchronize with host - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - return OutliningElementsCollector.collectElements(sourceFile, cancellationToken); - } - - const braceMatching = new Map(getEntries({ - [SyntaxKind.OpenBraceToken]: SyntaxKind.CloseBraceToken, - [SyntaxKind.OpenParenToken]: SyntaxKind.CloseParenToken, - [SyntaxKind.OpenBracketToken]: SyntaxKind.CloseBracketToken, - [SyntaxKind.GreaterThanToken]: SyntaxKind.LessThanToken, - })); - braceMatching.forEach((value, key) => braceMatching.set(value.toString(), Number(key) as SyntaxKind)); + return createTextSpanFromBounds(nodeForStartPos.getStart(), node.getEnd()); + } - function getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const token = getTouchingToken(sourceFile, position); - const matchKind = token.getStart(sourceFile) === position ? braceMatching.get(token.kind.toString()) : undefined; - const match = matchKind && findChildOfKind(token.parent, matchKind, sourceFile); - // We want to order the braces when we return the result. - return match ? [createTextSpanFromNode(token, sourceFile), createTextSpanFromNode(match, sourceFile)].sort((a, b) => a.start - b.start) : emptyArray; - } + function getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined { + // doesn't use compiler - no need to synchronize with host + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - function getIndentationAtPosition(fileName: string, position: number, editorOptions: EditorOptions | EditorSettings) { - let start = timestamp(); - const settings = toEditorSettings(editorOptions); - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - log("getIndentationAtPosition: getCurrentSourceFile: " + (timestamp() - start)); + return BreakpointResolver.spanInSourceFileAtLocation(sourceFile, position); + } - start = timestamp(); + function getNavigationBarItems(fileName: string): NavigationBarItem[] { + return NavigationBar.getNavigationBarItems(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken); + } - const result = formatting.SmartIndenter.getIndentation(position, sourceFile, settings); - log("getIndentationAtPosition: computeIndentation : " + (timestamp() - start)); + function getNavigationTree(fileName: string): NavigationTree { + return NavigationBar.getNavigationTree(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken); + } - return result; - } + function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; + function getSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[] { + synchronizeHostData(); - function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - return formatting.formatSelection(start, end, sourceFile, formatting.getFormatContext(toEditorSettings(options), host)); + const responseFormat = format || SemanticClassificationFormat.Original; + if (responseFormat === SemanticClassificationFormat.TwentyTwenty) { + return classifier.v2020.getSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span); } - - function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { - return formatting.formatDocument(syntaxTreeCache.getCurrentSourceFile(fileName), formatting.getFormatContext(toEditorSettings(options), host)); + else { + return ts.getSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span); } + } - function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const formatContext = formatting.getFormatContext(toEditorSettings(options), host); - - if (!isInComment(sourceFile, position)) { - switch (key) { - case "{": - return formatting.formatOnOpeningCurly(position, sourceFile, formatContext); - case "}": - return formatting.formatOnClosingCurly(position, sourceFile, formatContext); - case ";": - return formatting.formatOnSemicolon(position, sourceFile, formatContext); - case "\n": - return formatting.formatOnEnter(position, sourceFile, formatContext); - } - } + function getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications { + synchronizeHostData(); - return []; + const responseFormat = format || SemanticClassificationFormat.Original; + if (responseFormat === SemanticClassificationFormat.Original) { + return ts.getEncodedSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span); } - - function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly CodeFixAction[] { - synchronizeHostData(); - const sourceFile = getValidSourceFile(fileName); - const span = createTextSpanFromBounds(start, end); - const formatContext = formatting.getFormatContext(formatOptions, host); - - return flatMap(deduplicate(errorCodes, equateValues, compareValues), errorCode => { - cancellationToken.throwIfCancellationRequested(); - return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext, preferences }); - }); + else { + return classifier.v2020.getEncodedSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span); } + } - function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): CombinedCodeActions { - synchronizeHostData(); - Debug.assert(scope.type === "file"); - const sourceFile = getValidSourceFile(scope.fileName); - const formatContext = formatting.getFormatContext(formatOptions, host); + function getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] { + // doesn't use compiler - no need to synchronize with host + return ts.getSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span); + } - return codefix.getAllFixes({ fixId, sourceFile, program, host, cancellationToken, formatContext, preferences }); - } + function getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications { + // doesn't use compiler - no need to synchronize with host + return ts.getEncodedSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span); + } - function organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] { - synchronizeHostData(); - Debug.assert(args.type === "file"); - const sourceFile = getValidSourceFile(args.fileName); - const formatContext = formatting.getFormatContext(formatOptions, host); + function getOutliningSpans(fileName: string): OutliningSpan[] { + // doesn't use compiler - no need to synchronize with host + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + return OutliningElementsCollector.collectElements(sourceFile, cancellationToken); + } - return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences, args.skipDestructiveCodeActions); - } + const braceMatching = new Map(getEntries({ + [SyntaxKind.OpenBraceToken]: SyntaxKind.CloseBraceToken, + [SyntaxKind.OpenParenToken]: SyntaxKind.CloseParenToken, + [SyntaxKind.OpenBracketToken]: SyntaxKind.CloseBracketToken, + [SyntaxKind.GreaterThanToken]: SyntaxKind.LessThanToken, + })); + braceMatching.forEach((value, key) => braceMatching.set(value.toString(), Number(key) as SyntaxKind)); + + function getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const token = getTouchingToken(sourceFile, position); + const matchKind = token.getStart(sourceFile) === position ? braceMatching.get(token.kind.toString()) : undefined; + const match = matchKind && findChildOfKind(token.parent, matchKind, sourceFile); + // We want to order the braces when we return the result. + return match ? [createTextSpanFromNode(token, sourceFile), createTextSpanFromNode(match, sourceFile)].sort((a, b) => a.start - b.start) : emptyArray; + } - function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] { - return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions, host), preferences, sourceMapper); - } + function getIndentationAtPosition(fileName: string, position: number, editorOptions: EditorOptions | EditorSettings) { + let start = timestamp(); + const settings = toEditorSettings(editorOptions); + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + log("getIndentationAtPosition: getCurrentSourceFile: " + (timestamp() - start)); - function applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise; - function applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; - function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; - function applyCodeActionCommand(fileName: Path, action: CodeActionCommand): Promise; - function applyCodeActionCommand(fileName: Path, action: CodeActionCommand[]): Promise; - function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrFormatSettingsOrUndefined?: CodeActionCommand | CodeActionCommand[] | FormatCodeSettings): Promise { - const action = typeof fileName === "string" ? actionOrFormatSettingsOrUndefined as CodeActionCommand | CodeActionCommand[] : fileName as CodeActionCommand[]; - return isArray(action) ? Promise.all(action.map(a => applySingleCodeActionCommand(a))) : applySingleCodeActionCommand(action); - } + start = timestamp(); - function applySingleCodeActionCommand(action: CodeActionCommand): Promise { - const getPath = (path: string): Path => toPath(path, currentDirectory, getCanonicalFileName); - Debug.assertEqual(action.type, "install package"); - return host.installPackage - ? host.installPackage({ fileName: getPath(action.file), packageName: action.packageName }) - : Promise.reject("Host does not implement `installPackage`"); - } + const result = formatting.SmartIndenter.getIndentation(position, sourceFile, settings); + log("getIndentationAtPosition: computeIndentation : " + (timestamp() - start)); - function getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined { - return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position, options); - } + return result; + } - function isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { - // '<' is currently not supported, figuring out if we're in a Generic Type vs. a comparison is too - // expensive to do during typing scenarios - // i.e. whether we're dealing with: - // var x = new foo<| ( with class foo{} ) - // or - // var y = 3 <| - if (openingBrace === CharacterCodes.lessThan) { - return false; - } + function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + return formatting.formatSelection(start, end, sourceFile, formatting.getFormatContext(toEditorSettings(options), host)); + } - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { + return formatting.formatDocument(syntaxTreeCache.getCurrentSourceFile(fileName), formatting.getFormatContext(toEditorSettings(options), host)); + } - // Check if in a context where we don't want to perform any insertion - if (isInString(sourceFile, position)) { - return false; - } + function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const formatContext = formatting.getFormatContext(toEditorSettings(options), host); - if (isInsideJsxElementOrAttribute(sourceFile, position)) { - return openingBrace === CharacterCodes.openBrace; + if (!isInComment(sourceFile, position)) { + switch (key) { + case "{": + return formatting.formatOnOpeningCurly(position, sourceFile, formatContext); + case "}": + return formatting.formatOnClosingCurly(position, sourceFile, formatContext); + case ";": + return formatting.formatOnSemicolon(position, sourceFile, formatContext); + case "\n": + return formatting.formatOnEnter(position, sourceFile, formatContext); } + } - if (isInTemplateString(sourceFile, position)) { - return false; - } + return []; + } - switch (openingBrace) { - case CharacterCodes.singleQuote: - case CharacterCodes.doubleQuote: - case CharacterCodes.backtick: - return !isInComment(sourceFile, position); - } + function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly CodeFixAction[] { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + const span = createTextSpanFromBounds(start, end); + const formatContext = formatting.getFormatContext(formatOptions, host); - return true; - } + return flatMap(deduplicate(errorCodes, equateValues, compareValues), errorCode => { + cancellationToken.throwIfCancellationRequested(); + return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext, preferences }); + }); + } - function getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const token = findPrecedingToken(position, sourceFile); - if (!token) return undefined; - const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent - : isJsxText(token) && isJsxElement(token.parent) ? token.parent : undefined; - if (element && isUnclosedTag(element)) { - return { newText: `` }; - } - const fragment = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningFragment(token.parent) ? token.parent.parent - : isJsxText(token) && isJsxFragment(token.parent) ? token.parent : undefined; - if (fragment && isUnclosedFragment(fragment)) { - return { newText: "" }; - } - } + function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): CombinedCodeActions { + synchronizeHostData(); + Debug.assert(scope.type === "file"); + const sourceFile = getValidSourceFile(scope.fileName); + const formatContext = formatting.getFormatContext(formatOptions, host); - function getLinesForRange(sourceFile: SourceFile, textRange: TextRange) { - return { - lineStarts: sourceFile.getLineStarts(), - firstLine: sourceFile.getLineAndCharacterOfPosition(textRange.pos).line, - lastLine: sourceFile.getLineAndCharacterOfPosition(textRange.end).line - }; - } + return codefix.getAllFixes({ fixId, sourceFile, program, host, cancellationToken, formatContext, preferences }); + } - function toggleLineComment(fileName: string, textRange: TextRange, insertComment?: boolean): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const textChanges: TextChange[] = []; - const { lineStarts, firstLine, lastLine } = getLinesForRange(sourceFile, textRange); + function organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] { + synchronizeHostData(); + Debug.assert(args.type === "file"); + const sourceFile = getValidSourceFile(args.fileName); + const formatContext = formatting.getFormatContext(formatOptions, host); - let isCommenting = insertComment || false; - let leftMostPosition = Number.MAX_VALUE; - const lineTextStarts = new Map(); - const firstNonWhitespaceCharacterRegex = new RegExp(/\S/); - const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]); - const openComment = isJsx ? "{/*" : "//"; + return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences, args.skipDestructiveCodeActions); + } - // Check each line before any text changes. - for (let i = firstLine; i <= lastLine; i++) { - const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i])); + function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] { + return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions, host), preferences, sourceMapper); + } - // Find the start of text and the left-most character. No-op on empty lines. - const regExec = firstNonWhitespaceCharacterRegex.exec(lineText); - if (regExec) { - leftMostPosition = Math.min(leftMostPosition, regExec.index); - lineTextStarts.set(i.toString(), regExec.index); + function applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise; + function applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; + function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; + function applyCodeActionCommand(fileName: Path, action: CodeActionCommand): Promise; + function applyCodeActionCommand(fileName: Path, action: CodeActionCommand[]): Promise; + function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrFormatSettingsOrUndefined?: CodeActionCommand | CodeActionCommand[] | FormatCodeSettings): Promise { + const action = typeof fileName === "string" ? actionOrFormatSettingsOrUndefined as CodeActionCommand | CodeActionCommand[] : fileName as CodeActionCommand[]; + return isArray(action) ? Promise.all(action.map(a => applySingleCodeActionCommand(a))) : applySingleCodeActionCommand(action); + } - if (lineText.substr(regExec.index, openComment.length) !== openComment) { - isCommenting = insertComment === undefined || insertComment; - } - } - } + function applySingleCodeActionCommand(action: CodeActionCommand): Promise { + const getPath = (path: string): Path => toPath(path, currentDirectory, getCanonicalFileName); + Debug.assertEqual(action.type, "install package"); + return host.installPackage + ? host.installPackage({ fileName: getPath(action.file), packageName: action.packageName }) + : Promise.reject("Host does not implement `installPackage`"); + } - // Push all text changes. - for (let i = firstLine; i <= lastLine; i++) { - // If the range is multiline and ends on a beginning of a line, don't comment/uncomment. - if (firstLine !== lastLine && lineStarts[i] === textRange.end) { - continue; - } + function getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined { + return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position, options); + } - const lineTextStart = lineTextStarts.get(i.toString()); + function isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { + // '<' is currently not supported, figuring out if we're in a Generic Type vs. a comparison is too + // expensive to do during typing scenarios + // i.e. whether we're dealing with: + // var x = new foo<| ( with class foo{} ) + // or + // var y = 3 <| + if (openingBrace === CharacterCodes.lessThan) { + return false; + } - // If the line is not an empty line; otherwise no-op. - if (lineTextStart !== undefined) { - if (isJsx) { - textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { pos: lineStarts[i] + leftMostPosition, end: sourceFile.getLineEndOfPosition(lineStarts[i]) }, isCommenting, isJsx)); - } - else if (isCommenting) { - textChanges.push({ - newText: openComment, - span: { - length: 0, - start: lineStarts[i] + leftMostPosition - } - }); - } - else if (sourceFile.text.substr(lineStarts[i] + lineTextStart, openComment.length) === openComment) { - textChanges.push({ - newText: "", - span: { - length: openComment.length, - start: lineStarts[i] + lineTextStart - } - }); - } - } - } + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - return textChanges; + // Check if in a context where we don't want to perform any insertion + if (isInString(sourceFile, position)) { + return false; } - function toggleMultilineComment(fileName: string, textRange: TextRange, insertComment?: boolean, isInsideJsx?: boolean): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const textChanges: TextChange[] = []; - const { text } = sourceFile; - - let hasComment = false; - let isCommenting = insertComment || false; - const positions = [] as number[] as SortedArray; + if (isInsideJsxElementOrAttribute(sourceFile, position)) { + return openingBrace === CharacterCodes.openBrace; + } - let { pos } = textRange; - const isJsx = isInsideJsx !== undefined ? isInsideJsx : isInsideJsxElement(sourceFile, pos); + if (isInTemplateString(sourceFile, position)) { + return false; + } - const openMultiline = isJsx ? "{/*" : "/*"; - const closeMultiline = isJsx ? "*/}" : "*/"; - const openMultilineRegex = isJsx ? "\\{\\/\\*" : "\\/\\*"; - const closeMultilineRegex = isJsx ? "\\*\\/\\}" : "\\*\\/"; + switch (openingBrace) { + case CharacterCodes.singleQuote: + case CharacterCodes.doubleQuote: + case CharacterCodes.backtick: + return !isInComment(sourceFile, position); + } - // Get all comment positions - while (pos <= textRange.end) { - // Start of comment is considered inside comment. - const offset = text.substr(pos, openMultiline.length) === openMultiline ? openMultiline.length : 0; - const commentRange = isInComment(sourceFile, pos + offset); + return true; + } - // If position is in a comment add it to the positions array. - if (commentRange) { - // Comment range doesn't include the brace character. Increase it to include them. - if (isJsx) { - commentRange.pos--; - commentRange.end++; - } + function getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const token = findPrecedingToken(position, sourceFile); + if (!token) return undefined; + const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent + : isJsxText(token) && isJsxElement(token.parent) ? token.parent : undefined; + if (element && isUnclosedTag(element)) { + return { newText: `` }; + } + const fragment = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningFragment(token.parent) ? token.parent.parent + : isJsxText(token) && isJsxFragment(token.parent) ? token.parent : undefined; + if (fragment && isUnclosedFragment(fragment)) { + return { newText: "" }; + } + } - positions.push(commentRange.pos); - if (commentRange.kind === SyntaxKind.MultiLineCommentTrivia) { - positions.push(commentRange.end); - } + function getLinesForRange(sourceFile: SourceFile, textRange: TextRange) { + return { + lineStarts: sourceFile.getLineStarts(), + firstLine: sourceFile.getLineAndCharacterOfPosition(textRange.pos).line, + lastLine: sourceFile.getLineAndCharacterOfPosition(textRange.end).line + }; + } - hasComment = true; - pos = commentRange.end + 1; + function toggleLineComment(fileName: string, textRange: TextRange, insertComment?: boolean): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const textChanges: TextChange[] = []; + const { lineStarts, firstLine, lastLine } = getLinesForRange(sourceFile, textRange); + + let isCommenting = insertComment || false; + let leftMostPosition = Number.MAX_VALUE; + const lineTextStarts = new Map(); + const firstNonWhitespaceCharacterRegex = new RegExp(/\S/); + const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]); + const openComment = isJsx ? "{/*" : "//"; + + // Check each line before any text changes. + for (let i = firstLine; i <= lastLine; i++) { + const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i])); + + // Find the start of text and the left-most character. No-op on empty lines. + const regExec = firstNonWhitespaceCharacterRegex.exec(lineText); + if (regExec) { + leftMostPosition = Math.min(leftMostPosition, regExec.index); + lineTextStarts.set(i.toString(), regExec.index); + + if (lineText.substr(regExec.index, openComment.length) !== openComment) { + isCommenting = insertComment === undefined || insertComment; } - else { // If it's not in a comment range, then we need to comment the uncommented portions. - const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); + } + } - isCommenting = insertComment !== undefined - ? insertComment - : isCommenting || !isTextWhiteSpaceLike(text, pos, newPos === -1 ? textRange.end : pos + newPos); // If isCommenting is already true we don't need to check whitespace again. - pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length; - } + // Push all text changes. + for (let i = firstLine; i <= lastLine; i++) { + // If the range is multiline and ends on a beginning of a line, don't comment/uncomment. + if (firstLine !== lastLine && lineStarts[i] === textRange.end) { + continue; } - // If it didn't found a comment and isCommenting is false means is only empty space. - // We want to insert comment in this scenario. - if (isCommenting || !hasComment) { - if (isInComment(sourceFile, textRange.pos)?.kind !== SyntaxKind.SingleLineCommentTrivia) { - insertSorted(positions, textRange.pos, compareValues); - } - insertSorted(positions, textRange.end, compareValues); + const lineTextStart = lineTextStarts.get(i.toString()); - // Insert open comment if the first position is not a comment already. - const firstPos = positions[0]; - if (text.substr(firstPos, openMultiline.length) !== openMultiline) { + // If the line is not an empty line; otherwise no-op. + if (lineTextStart !== undefined) { + if (isJsx) { + textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { pos: lineStarts[i] + leftMostPosition, end: sourceFile.getLineEndOfPosition(lineStarts[i]) }, isCommenting, isJsx)); + } + else if (isCommenting) { textChanges.push({ - newText: openMultiline, + newText: openComment, span: { length: 0, - start: firstPos + start: lineStarts[i] + leftMostPosition + } + }); + } + else if (sourceFile.text.substr(lineStarts[i] + lineTextStart, openComment.length) === openComment) { + textChanges.push({ + newText: "", + span: { + length: openComment.length, + start: lineStarts[i] + lineTextStart } }); } + } + } - // Insert open and close comment to all positions between first and last. Exclusive. - for (let i = 1; i < positions.length - 1; i++) { - if (text.substr(positions[i] - closeMultiline.length, closeMultiline.length) !== closeMultiline) { - textChanges.push({ - newText: closeMultiline, - span: { - length: 0, - start: positions[i] - } - }); - } + return textChanges; + } - if (text.substr(positions[i], openMultiline.length) !== openMultiline) { - textChanges.push({ - newText: openMultiline, - span: { - length: 0, - start: positions[i] - } - }); - } + function toggleMultilineComment(fileName: string, textRange: TextRange, insertComment?: boolean, isInsideJsx?: boolean): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const textChanges: TextChange[] = []; + const { text } = sourceFile; + + let hasComment = false; + let isCommenting = insertComment || false; + const positions = [] as number[] as SortedArray; + + let { pos } = textRange; + const isJsx = isInsideJsx !== undefined ? isInsideJsx : isInsideJsxElement(sourceFile, pos); + + const openMultiline = isJsx ? "{/*" : "/*"; + const closeMultiline = isJsx ? "*/}" : "*/"; + const openMultilineRegex = isJsx ? "\\{\\/\\*" : "\\/\\*"; + const closeMultilineRegex = isJsx ? "\\*\\/\\}" : "\\*\\/"; + + // Get all comment positions + while (pos <= textRange.end) { + // Start of comment is considered inside comment. + const offset = text.substr(pos, openMultiline.length) === openMultiline ? openMultiline.length : 0; + const commentRange = isInComment(sourceFile, pos + offset); + + // If position is in a comment add it to the positions array. + if (commentRange) { + // Comment range doesn't include the brace character. Increase it to include them. + if (isJsx) { + commentRange.pos--; + commentRange.end++; + } + + positions.push(commentRange.pos); + if (commentRange.kind === SyntaxKind.MultiLineCommentTrivia) { + positions.push(commentRange.end); } - // Insert open comment if the last position is not a comment already. - if (textChanges.length % 2 !== 0) { + hasComment = true; + pos = commentRange.end + 1; + } + else { // If it's not in a comment range, then we need to comment the uncommented portions. + const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); + + isCommenting = insertComment !== undefined + ? insertComment + : isCommenting || !isTextWhiteSpaceLike(text, pos, newPos === -1 ? textRange.end : pos + newPos); // If isCommenting is already true we don't need to check whitespace again. + pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length; + } + } + + // If it didn't found a comment and isCommenting is false means is only empty space. + // We want to insert comment in this scenario. + if (isCommenting || !hasComment) { + if (isInComment(sourceFile, textRange.pos)?.kind !== SyntaxKind.SingleLineCommentTrivia) { + insertSorted(positions, textRange.pos, compareValues); + } + insertSorted(positions, textRange.end, compareValues); + + // Insert open comment if the first position is not a comment already. + const firstPos = positions[0]; + if (text.substr(firstPos, openMultiline.length) !== openMultiline) { + textChanges.push({ + newText: openMultiline, + span: { + length: 0, + start: firstPos + } + }); + } + + // Insert open and close comment to all positions between first and last. Exclusive. + for (let i = 1; i < positions.length - 1; i++) { + if (text.substr(positions[i] - closeMultiline.length, closeMultiline.length) !== closeMultiline) { textChanges.push({ newText: closeMultiline, span: { length: 0, - start: positions[positions.length - 1] + start: positions[i] } }); } - } - else { - // If is not commenting then remove all comments found. - for (const pos of positions) { - const from = pos - closeMultiline.length > 0 ? pos - closeMultiline.length : 0; - const offset = text.substr(from, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; + + if (text.substr(positions[i], openMultiline.length) !== openMultiline) { textChanges.push({ - newText: "", + newText: openMultiline, span: { - length: openMultiline.length, - start: pos - offset + length: 0, + start: positions[i] } }); } } - return textChanges; + // Insert open comment if the last position is not a comment already. + if (textChanges.length % 2 !== 0) { + textChanges.push({ + newText: closeMultiline, + span: { + length: 0, + start: positions[positions.length - 1] + } + }); + } } - - function commentSelection(fileName: string, textRange: TextRange): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const { firstLine, lastLine } = getLinesForRange(sourceFile, textRange); - - // If there is a selection that is on the same line, add multiline. - return firstLine === lastLine && textRange.pos !== textRange.end - ? toggleMultilineComment(fileName, textRange, /*insertComment*/ true) - : toggleLineComment(fileName, textRange, /*insertComment*/ true); + else { + // If is not commenting then remove all comments found. + for (const pos of positions) { + const from = pos - closeMultiline.length > 0 ? pos - closeMultiline.length : 0; + const offset = text.substr(from, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; + textChanges.push({ + newText: "", + span: { + length: openMultiline.length, + start: pos - offset + } + }); + } } - function uncommentSelection(fileName: string, textRange: TextRange): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const textChanges: TextChange[] = []; - const { pos } = textRange; - let { end } = textRange; - - // If cursor is not a selection we need to increase the end position - // to include the start of the comment. - if (pos === end) { - end += isInsideJsxElement(sourceFile, pos) ? 2 : 1; - } + return textChanges; + } - for (let i = pos; i <= end; i++) { - const commentRange = isInComment(sourceFile, i); - if (commentRange) { - switch (commentRange.kind) { - case SyntaxKind.SingleLineCommentTrivia: - textChanges.push.apply(textChanges, toggleLineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); - break; - case SyntaxKind.MultiLineCommentTrivia: - textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); - } + function commentSelection(fileName: string, textRange: TextRange): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const { firstLine, lastLine } = getLinesForRange(sourceFile, textRange); - i = commentRange.end + 1; - } - } + // If there is a selection that is on the same line, add multiline. + return firstLine === lastLine && textRange.pos !== textRange.end + ? toggleMultilineComment(fileName, textRange, /*insertComment*/ true) + : toggleLineComment(fileName, textRange, /*insertComment*/ true); + } - return textChanges; - } + function uncommentSelection(fileName: string, textRange: TextRange): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const textChanges: TextChange[] = []; + const { pos } = textRange; + let { end } = textRange; - function isUnclosedTag({ openingElement, closingElement, parent }: JsxElement): boolean { - return !tagNamesAreEquivalent(openingElement.tagName, closingElement.tagName) || - isJsxElement(parent) && tagNamesAreEquivalent(openingElement.tagName, parent.openingElement.tagName) && isUnclosedTag(parent); + // If cursor is not a selection we need to increase the end position + // to include the start of the comment. + if (pos === end) { + end += isInsideJsxElement(sourceFile, pos) ? 2 : 1; } - function isUnclosedFragment({ closingFragment, parent }: JsxFragment): boolean { - return !!(closingFragment.flags & NodeFlags.ThisNodeHasError) || (isJsxFragment(parent) && isUnclosedFragment(parent)); - } + for (let i = pos; i <= end; i++) { + const commentRange = isInComment(sourceFile, i); + if (commentRange) { + switch (commentRange.kind) { + case SyntaxKind.SingleLineCommentTrivia: + textChanges.push.apply(textChanges, toggleLineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); + break; + case SyntaxKind.MultiLineCommentTrivia: + textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); + } - function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const range = formatting.getRangeOfEnclosingComment(sourceFile, position); - return range && (!onlyMultiLine || range.kind === SyntaxKind.MultiLineCommentTrivia) ? createTextSpanFromRange(range) : undefined; + i = commentRange.end + 1; + } } - function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] { - // Note: while getting todo comments seems like a syntactic operation, we actually - // treat it as a semantic operation here. This is because we expect our host to call - // this on every single file. If we treat this syntactically, then that will cause - // us to populate and throw away the tree in our syntax tree cache for each file. By - // treating this as a semantic operation, we can access any tree without throwing - // anything away. - synchronizeHostData(); - - const sourceFile = getValidSourceFile(fileName); - - cancellationToken.throwIfCancellationRequested(); - - const fileContents = sourceFile.text; - const result: TodoComment[] = []; + return textChanges; + } - // Exclude node_modules files as we don't want to show the todos of external libraries. - if (descriptors.length > 0 && !isNodeModulesFile(sourceFile.fileName)) { - const regExp = getTodoCommentsRegExp(); + function isUnclosedTag({ openingElement, closingElement, parent }: JsxElement): boolean { + return !tagNamesAreEquivalent(openingElement.tagName, closingElement.tagName) || + isJsxElement(parent) && tagNamesAreEquivalent(openingElement.tagName, parent.openingElement.tagName) && isUnclosedTag(parent); + } - let matchArray: RegExpExecArray | null; - while (matchArray = regExp.exec(fileContents)) { - cancellationToken.throwIfCancellationRequested(); + function isUnclosedFragment({ closingFragment, parent }: JsxFragment): boolean { + return !!(closingFragment.flags & NodeFlags.ThisNodeHasError) || (isJsxFragment(parent) && isUnclosedFragment(parent)); + } - // If we got a match, here is what the match array will look like. Say the source text is: - // - // " // hack 1" - // - // The result array with the regexp: will be: - // - // ["// hack 1", "// ", "hack 1", undefined, "hack"] - // - // Here are the relevant capture groups: - // 0) The full match for the entire regexp. - // 1) The preamble to the message portion. - // 2) The message portion. - // 3...N) The descriptor that was matched - by index. 'undefined' for each - // descriptor that didn't match. an actual value if it did match. - // - // i.e. 'undefined' in position 3 above means TODO(jason) didn't match. - // "hack" in position 4 means HACK did match. - const firstDescriptorCaptureIndex = 3; - Debug.assert(matchArray.length === descriptors.length + firstDescriptorCaptureIndex); - - const preamble = matchArray[1]; - const matchPosition = matchArray.index + preamble.length; - - // OK, we have found a match in the file. This is only an acceptable match if - // it is contained within a comment. - if (!isInComment(sourceFile, matchPosition)) { - continue; - } + function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const range = formatting.getRangeOfEnclosingComment(sourceFile, position); + return range && (!onlyMultiLine || range.kind === SyntaxKind.MultiLineCommentTrivia) ? createTextSpanFromRange(range) : undefined; + } - let descriptor: TodoCommentDescriptor | undefined; - for (let i = 0; i < descriptors.length; i++) { - if (matchArray[i + firstDescriptorCaptureIndex]) { - descriptor = descriptors[i]; - } - } - if (descriptor === undefined) return Debug.fail(); + function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] { + // Note: while getting todo comments seems like a syntactic operation, we actually + // treat it as a semantic operation here. This is because we expect our host to call + // this on every single file. If we treat this syntactically, then that will cause + // us to populate and throw away the tree in our syntax tree cache for each file. By + // treating this as a semantic operation, we can access any tree without throwing + // anything away. + synchronizeHostData(); - // We don't want to match something like 'TODOBY', so we make sure a non - // letter/digit follows the match. - if (isLetterOrDigit(fileContents.charCodeAt(matchPosition + descriptor.text.length))) { - continue; - } + const sourceFile = getValidSourceFile(fileName); - const message = matchArray[2]; - result.push({ descriptor, message, position: matchPosition }); - } - } + cancellationToken.throwIfCancellationRequested(); - return result; + const fileContents = sourceFile.text; + const result: TodoComment[] = []; - function escapeRegExp(str: string): string { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - } + // Exclude node_modules files as we don't want to show the todos of external libraries. + if (descriptors.length > 0 && !isNodeModulesFile(sourceFile.fileName)) { + const regExp = getTodoCommentsRegExp(); - function getTodoCommentsRegExp(): RegExp { - // NOTE: `?:` means 'non-capture group'. It allows us to have groups without having to - // filter them out later in the final result array. + let matchArray: RegExpExecArray | null; + while (matchArray = regExp.exec(fileContents)) { + cancellationToken.throwIfCancellationRequested(); - // TODO comments can appear in one of the following forms: - // - // 1) // TODO or /////////// TODO - // - // 2) /* TODO or /********** TODO - // - // 3) /* - // * TODO - // */ + // If we got a match, here is what the match array will look like. Say the source text is: // - // The following three regexps are used to match the start of the text up to the TODO - // comment portion. - const singleLineCommentStart = /(?:\/\/+\s*)/.source; - const multiLineCommentStart = /(?:\/\*+\s*)/.source; - const anyNumberOfSpacesAndAsterisksAtStartOfLine = /(?:^(?:\s|\*)*)/.source; - - // Match any of the above three TODO comment start regexps. - // Note that the outermost group *is* a capture group. We want to capture the preamble - // so that we can determine the starting position of the TODO comment match. - const preamble = "(" + anyNumberOfSpacesAndAsterisksAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")"; - - // Takes the descriptors and forms a regexp that matches them as if they were literals. - // For example, if the descriptors are "TODO(jason)" and "HACK", then this will be: + // " // hack 1" // - // (?:(TODO\(jason\))|(HACK)) + // The result array with the regexp: will be: // - // Note that the outermost group is *not* a capture group, but the innermost groups - // *are* capture groups. By capturing the inner literals we can determine after - // matching which descriptor we are dealing with. - const literals = "(?:" + map(descriptors, d => "(" + escapeRegExp(d.text) + ")").join("|") + ")"; - - // After matching a descriptor literal, the following regexp matches the rest of the - // text up to the end of the line (or */). - const endOfLineOrEndOfComment = /(?:$|\*\/)/.source; - const messageRemainder = /(?:.*?)/.source; - - // This is the portion of the match we'll return as part of the TODO comment result. We - // match the literal portion up to the end of the line or end of comment. - const messagePortion = "(" + literals + messageRemainder + ")"; - const regExpString = preamble + messagePortion + endOfLineOrEndOfComment; - - // The final regexp will look like this: - // /((?:\/\/+\s*)|(?:\/\*+\s*)|(?:^(?:\s|\*)*))((?:(TODO\(jason\))|(HACK))(?:.*?))(?:$|\*\/)/gim - - // The flags of the regexp are important here. - // 'g' is so that we are doing a global search and can find matches several times - // in the input. + // ["// hack 1", "// ", "hack 1", undefined, "hack"] // - // 'i' is for case insensitivity (We do this to match C# TODO comment code). + // Here are the relevant capture groups: + // 0) The full match for the entire regexp. + // 1) The preamble to the message portion. + // 2) The message portion. + // 3...N) The descriptor that was matched - by index. 'undefined' for each + // descriptor that didn't match. an actual value if it did match. // - // 'm' is so we can find matches in a multi-line input. - return new RegExp(regExpString, "gim"); - } + // i.e. 'undefined' in position 3 above means TODO(jason) didn't match. + // "hack" in position 4 means HACK did match. + const firstDescriptorCaptureIndex = 3; + Debug.assert(matchArray.length === descriptors.length + firstDescriptorCaptureIndex); - function isLetterOrDigit(char: number): boolean { - return (char >= CharacterCodes.a && char <= CharacterCodes.z) || - (char >= CharacterCodes.A && char <= CharacterCodes.Z) || - (char >= CharacterCodes._0 && char <= CharacterCodes._9); - } + const preamble = matchArray[1]; + const matchPosition = matchArray.index + preamble.length; + + // OK, we have found a match in the file. This is only an acceptable match if + // it is contained within a comment. + if (!isInComment(sourceFile, matchPosition)) { + continue; + } + + let descriptor: TodoCommentDescriptor | undefined; + for (let i = 0; i < descriptors.length; i++) { + if (matchArray[i + firstDescriptorCaptureIndex]) { + descriptor = descriptors[i]; + } + } + if (descriptor === undefined) return Debug.fail(); - function isNodeModulesFile(path: string): boolean { - return stringContains(path, "/node_modules/"); + // We don't want to match something like 'TODOBY', so we make sure a non + // letter/digit follows the match. + if (isLetterOrDigit(fileContents.charCodeAt(matchPosition + descriptor.text.length))) { + continue; + } + + const message = matchArray[2]; + result.push({ descriptor, message, position: matchPosition }); } } - function getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): RenameInfo { - synchronizeHostData(); - return Rename.getRenameInfo(program, getValidSourceFile(fileName), position, options); - } - - function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, preferences: UserPreferences, formatOptions?: FormatCodeSettings, triggerReason?: RefactorTriggerReason, kind?: string): RefactorContext { - const [startPosition, endPosition] = typeof positionOrRange === "number" ? [positionOrRange, undefined] : [positionOrRange.pos, positionOrRange.end]; - return { - file, - startPosition, - endPosition, - program: getProgram()!, - host, - formatContext: formatting.getFormatContext(formatOptions!, host), // TODO: GH#18217 - cancellationToken, - preferences, - triggerReason, - kind - }; - } + return result; - function getInlayHintsContext(file: SourceFile, span: TextSpan, preferences: UserPreferences): InlayHintsContext { - return { - file, - program: getProgram()!, - host, - span, - preferences, - cancellationToken, - }; + function escapeRegExp(str: string): string { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); } - function getSmartSelectionRange(fileName: string, position: number): SelectionRange { - return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName)); + function getTodoCommentsRegExp(): RegExp { + // NOTE: `?:` means 'non-capture group'. It allows us to have groups without having to + // filter them out later in the final result array. + + // TODO comments can appear in one of the following forms: + // + // 1) // TODO or /////////// TODO + // + // 2) /* TODO or /********** TODO + // + // 3) /* + // * TODO + // */ + // + // The following three regexps are used to match the start of the text up to the TODO + // comment portion. + const singleLineCommentStart = /(?:\/\/+\s*)/.source; + const multiLineCommentStart = /(?:\/\*+\s*)/.source; + const anyNumberOfSpacesAndAsterisksAtStartOfLine = /(?:^(?:\s|\*)*)/.source; + + // Match any of the above three TODO comment start regexps. + // Note that the outermost group *is* a capture group. We want to capture the preamble + // so that we can determine the starting position of the TODO comment match. + const preamble = "(" + anyNumberOfSpacesAndAsterisksAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")"; + + // Takes the descriptors and forms a regexp that matches them as if they were literals. + // For example, if the descriptors are "TODO(jason)" and "HACK", then this will be: + // + // (?:(TODO\(jason\))|(HACK)) + // + // Note that the outermost group is *not* a capture group, but the innermost groups + // *are* capture groups. By capturing the inner literals we can determine after + // matching which descriptor we are dealing with. + const literals = "(?:" + map(descriptors, d => "(" + escapeRegExp(d.text) + ")").join("|") + ")"; + + // After matching a descriptor literal, the following regexp matches the rest of the + // text up to the end of the line (or */). + const endOfLineOrEndOfComment = /(?:$|\*\/)/.source; + const messageRemainder = /(?:.*?)/.source; + + // This is the portion of the match we'll return as part of the TODO comment result. We + // match the literal portion up to the end of the line or end of comment. + const messagePortion = "(" + literals + messageRemainder + ")"; + const regExpString = preamble + messagePortion + endOfLineOrEndOfComment; + + // The final regexp will look like this: + // /((?:\/\/+\s*)|(?:\/\*+\s*)|(?:^(?:\s|\*)*))((?:(TODO\(jason\))|(HACK))(?:.*?))(?:$|\*\/)/gim + + // The flags of the regexp are important here. + // 'g' is so that we are doing a global search and can find matches several times + // in the input. + // + // 'i' is for case insensitivity (We do this to match C# TODO comment code). + // + // 'm' is so we can find matches in a multi-line input. + return new RegExp(regExpString, "gim"); } - function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason, kind: string): ApplicableRefactorInfo[] { - synchronizeHostData(); - const file = getValidSourceFile(fileName); - return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind)); + function isLetterOrDigit(char: number): boolean { + return (char >= CharacterCodes.a && char <= CharacterCodes.z) || + (char >= CharacterCodes.A && char <= CharacterCodes.Z) || + (char >= CharacterCodes._0 && char <= CharacterCodes._9); } - function getEditsForRefactor( - fileName: string, - formatOptions: FormatCodeSettings, - positionOrRange: number | TextRange, - refactorName: string, - actionName: string, - preferences: UserPreferences = emptyOptions, - ): RefactorEditInfo | undefined { - synchronizeHostData(); - const file = getValidSourceFile(fileName); - return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName); + function isNodeModulesFile(path: string): boolean { + return stringContains(path, "/node_modules/"); } + } - function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { - // Go to Definition supports returning a zero-length span at position 0 for - // non-existent files. We need to special-case the conversion of position 0 - // to avoid a crash trying to get the text for that file, since this function - // otherwise assumes that 'fileName' is the name of a file that exists. - if (position === 0) { - return { line: 0, character: 0 }; - } - return sourceMapper.toLineColumnOffset(fileName, position); - } - - function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { - synchronizeHostData(); - const declarations = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position)); - return declarations && mapOneOrMany(declarations, declaration => CallHierarchy.createCallHierarchyItem(program, declaration)); - } - - function provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[] { - synchronizeHostData(); - const sourceFile = getValidSourceFile(fileName); - const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); - return declaration ? CallHierarchy.getIncomingCalls(program, declaration, cancellationToken) : []; - } - - function provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[] { - synchronizeHostData(); - const sourceFile = getValidSourceFile(fileName); - const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); - return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : []; - } - - function provideInlayHints(fileName: string, span: TextSpan, preferences: InlayHintsOptions = emptyOptions): InlayHint[] { - synchronizeHostData(); - const sourceFile = getValidSourceFile(fileName); - return InlayHints.provideInlayHints(getInlayHintsContext(sourceFile, span, preferences)); - } - - const ls: LanguageService = { - dispose, - cleanupSemanticCache, - getSyntacticDiagnostics, - getSemanticDiagnostics, - getSuggestionDiagnostics, - getCompilerOptionsDiagnostics, - getSyntacticClassifications, - getSemanticClassifications, - getEncodedSyntacticClassifications, - getEncodedSemanticClassifications, - getCompletionsAtPosition, - getCompletionEntryDetails, - getCompletionEntrySymbol, - getSignatureHelpItems, - getQuickInfoAtPosition, - getDefinitionAtPosition, - getDefinitionAndBoundSpan, - getImplementationAtPosition, - getTypeDefinitionAtPosition, - getReferencesAtPosition, - findReferences, - getFileReferences, - getOccurrencesAtPosition, - getDocumentHighlights, - getNameOrDottedNameSpan, - getBreakpointStatementAtPosition, - getNavigateToItems, - getRenameInfo, - getSmartSelectionRange, - findRenameLocations, - getNavigationBarItems, - getNavigationTree, - getOutliningSpans, - getTodoComments, - getBraceMatchingAtPosition, - getIndentationAtPosition, - getFormattingEditsForRange, - getFormattingEditsForDocument, - getFormattingEditsAfterKeystroke, - getDocCommentTemplateAtPosition, - isValidBraceCompletionAtPosition, - getJsxClosingTagAtPosition, - getSpanOfEnclosingComment, - getCodeFixesAtPosition, - getCombinedCodeFix, - applyCodeActionCommand, - organizeImports, - getEditsForFileRename, - getEmitOutput, - getNonBoundSourceFile, - getProgram, - getAutoImportProvider, - getApplicableRefactors, - getEditsForRefactor, - toLineColumnOffset, - getSourceMapper: () => sourceMapper, - clearSourceMapperCache: () => sourceMapper.clearCache(), - prepareCallHierarchy, - provideCallHierarchyIncomingCalls, - provideCallHierarchyOutgoingCalls, - toggleLineComment, - toggleMultilineComment, - commentSelection, - uncommentSelection, - provideInlayHints, + function getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): RenameInfo { + synchronizeHostData(); + return Rename.getRenameInfo(program, getValidSourceFile(fileName), position, options); + } + + function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, preferences: UserPreferences, formatOptions?: FormatCodeSettings, triggerReason?: RefactorTriggerReason, kind?: string): RefactorContext { + const [startPosition, endPosition] = typeof positionOrRange === "number" ? [positionOrRange, undefined] : [positionOrRange.pos, positionOrRange.end]; + return { + file, + startPosition, + endPosition, + program: getProgram()!, + host, + formatContext: formatting.getFormatContext(formatOptions!, host), // TODO: GH#18217 + cancellationToken, + preferences, + triggerReason, + kind }; + } - switch (languageServiceMode) { - case LanguageServiceMode.Semantic: - break; - case LanguageServiceMode.PartialSemantic: - invalidOperationsInPartialSemanticMode.forEach(key => - ls[key] = () => { - throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.PartialSemantic`); - } - ); - break; - case LanguageServiceMode.Syntactic: - invalidOperationsInSyntacticMode.forEach(key => - ls[key] = () => { - throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.Syntactic`); - } - ); - break; - default: - Debug.assertNever(languageServiceMode); - } - return ls; + function getInlayHintsContext(file: SourceFile, span: TextSpan, preferences: UserPreferences): InlayHintsContext { + return { + file, + program: getProgram()!, + host, + span, + preferences, + cancellationToken, + }; } - /* @internal */ - /** Names in the name table are escaped, so an identifier `__foo` will have a name table entry `___foo`. */ - export function getNameTable(sourceFile: SourceFile): UnderscoreEscapedMap { - if (!sourceFile.nameTable) { - initializeNameTable(sourceFile); - } + function getSmartSelectionRange(fileName: string, position: number): SelectionRange { + return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName)); + } - return sourceFile.nameTable!; // TODO: GH#18217 + function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason, kind: string): ApplicableRefactorInfo[] { + synchronizeHostData(); + const file = getValidSourceFile(fileName); + return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind)); } - function initializeNameTable(sourceFile: SourceFile): void { - const nameTable = sourceFile.nameTable = new Map(); - sourceFile.forEachChild(function walk(node) { - if (isIdentifier(node) && !isTagName(node) && node.escapedText || isStringOrNumericLiteralLike(node) && literalIsName(node)) { - const text = getEscapedTextOfIdentifierOrLiteral(node); - nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); - } - else if (isPrivateIdentifier(node)) { - const text = node.escapedText; - nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); - } + function getEditsForRefactor( + fileName: string, + formatOptions: FormatCodeSettings, + positionOrRange: number | TextRange, + refactorName: string, + actionName: string, + preferences: UserPreferences = emptyOptions, + ): RefactorEditInfo | undefined { + synchronizeHostData(); + const file = getValidSourceFile(fileName); + return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName); + } - forEachChild(node, walk); - if (hasJSDocNodes(node)) { - for (const jsDoc of node.jsDoc!) { - forEachChild(jsDoc, walk); - } - } - }); + function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { + // Go to Definition supports returning a zero-length span at position 0 for + // non-existent files. We need to special-case the conversion of position 0 + // to avoid a crash trying to get the text for that file, since this function + // otherwise assumes that 'fileName' is the name of a file that exists. + if (position === 0) { + return { line: 0, character: 0 }; + } + return sourceMapper.toLineColumnOffset(fileName, position); } - /** - * We want to store any numbers/strings if they were a name that could be - * related to a declaration. So, if we have 'import x = require("something")' - * then we want 'something' to be in the name table. Similarly, if we have - * "a['propname']" then we want to store "propname" in the name table. - */ - function literalIsName(node: StringLiteralLike | NumericLiteral): boolean { - return isDeclarationName(node) || - node.parent.kind === SyntaxKind.ExternalModuleReference || - isArgumentOfElementAccessExpression(node) || - isLiteralComputedPropertyDeclarationName(node); + function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { + synchronizeHostData(); + const declarations = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position)); + return declarations && mapOneOrMany(declarations, declaration => CallHierarchy.createCallHierarchyItem(program, declaration)); } - /** - * Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 } - */ - /* @internal */ - export function getContainingObjectLiteralElement(node: Node): ObjectLiteralElementWithName | undefined { - const element = getContainingObjectLiteralElementWorker(node); - return element && (isObjectLiteralExpression(element.parent) || isJsxAttributes(element.parent)) ? element as ObjectLiteralElementWithName : undefined; + function provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[] { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); + return declaration ? CallHierarchy.getIncomingCalls(program, declaration, cancellationToken) : []; } - function getContainingObjectLiteralElementWorker(node: Node): ObjectLiteralElement | undefined { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.NumericLiteral: - if (node.parent.kind === SyntaxKind.ComputedPropertyName) { - return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined; + + function provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[] { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); + return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : []; + } + + function provideInlayHints(fileName: string, span: TextSpan, preferences: InlayHintsOptions = emptyOptions): InlayHint[] { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + return InlayHints.provideInlayHints(getInlayHintsContext(sourceFile, span, preferences)); + } + + const ls: LanguageService = { + dispose, + cleanupSemanticCache, + getSyntacticDiagnostics, + getSemanticDiagnostics, + getSuggestionDiagnostics, + getCompilerOptionsDiagnostics, + getSyntacticClassifications, + getSemanticClassifications, + getEncodedSyntacticClassifications, + getEncodedSemanticClassifications, + getCompletionsAtPosition, + getCompletionEntryDetails, + getCompletionEntrySymbol, + getSignatureHelpItems, + getQuickInfoAtPosition, + getDefinitionAtPosition, + getDefinitionAndBoundSpan, + getImplementationAtPosition, + getTypeDefinitionAtPosition, + getReferencesAtPosition, + findReferences, + getFileReferences, + getOccurrencesAtPosition, + getDocumentHighlights, + getNameOrDottedNameSpan, + getBreakpointStatementAtPosition, + getNavigateToItems, + getRenameInfo, + getSmartSelectionRange, + findRenameLocations, + getNavigationBarItems, + getNavigationTree, + getOutliningSpans, + getTodoComments, + getBraceMatchingAtPosition, + getIndentationAtPosition, + getFormattingEditsForRange, + getFormattingEditsForDocument, + getFormattingEditsAfterKeystroke, + getDocCommentTemplateAtPosition, + isValidBraceCompletionAtPosition, + getJsxClosingTagAtPosition, + getSpanOfEnclosingComment, + getCodeFixesAtPosition, + getCombinedCodeFix, + applyCodeActionCommand, + organizeImports, + getEditsForFileRename, + getEmitOutput, + getNonBoundSourceFile, + getProgram, + getAutoImportProvider, + getApplicableRefactors, + getEditsForRefactor, + toLineColumnOffset, + getSourceMapper: () => sourceMapper, + clearSourceMapperCache: () => sourceMapper.clearCache(), + prepareCallHierarchy, + provideCallHierarchyIncomingCalls, + provideCallHierarchyOutgoingCalls, + toggleLineComment, + toggleMultilineComment, + commentSelection, + uncommentSelection, + provideInlayHints, + }; + + switch (languageServiceMode) { + case LanguageServiceMode.Semantic: + break; + case LanguageServiceMode.PartialSemantic: + invalidOperationsInPartialSemanticMode.forEach(key => + ls[key] = () => { + throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.PartialSemantic`); + } + ); + break; + case LanguageServiceMode.Syntactic: + invalidOperationsInSyntacticMode.forEach(key => + ls[key] = () => { + throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.Syntactic`); } - // falls through + ); + break; + default: + Debug.assertNever(languageServiceMode); + } + return ls; +} - case SyntaxKind.Identifier: - return isObjectLiteralElement(node.parent) && - (node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === SyntaxKind.JsxAttributes) && - node.parent.name === node ? node.parent : undefined; - } - return undefined; +/* @internal */ +/** Names in the name table are escaped, so an identifier `__foo` will have a name table entry `___foo`. */ +export function getNameTable(sourceFile: SourceFile): UnderscoreEscapedMap { + if (!sourceFile.nameTable) { + initializeNameTable(sourceFile); } - /* @internal */ - export type ObjectLiteralElementWithName = ObjectLiteralElement & { name: PropertyName; parent: ObjectLiteralExpression | JsxAttributes }; + return sourceFile.nameTable!; // TODO: GH#18217 +} + +function initializeNameTable(sourceFile: SourceFile): void { + const nameTable = sourceFile.nameTable = new Map(); + sourceFile.forEachChild(function walk(node) { + if (isIdentifier(node) && !isTagName(node) && node.escapedText || isStringOrNumericLiteralLike(node) && literalIsName(node)) { + const text = getEscapedTextOfIdentifierOrLiteral(node); + nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); + } + else if (isPrivateIdentifier(node)) { + const text = node.escapedText; + nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); + } - function getSymbolAtLocationForQuickInfo(node: Node, checker: TypeChecker): Symbol | undefined { - const object = getContainingObjectLiteralElement(node); - if (object) { - const contextualType = checker.getContextualType(object.parent); - const properties = contextualType && getPropertySymbolsFromContextualType(object, checker, contextualType, /*unionSymbolOk*/ false); - if (properties && properties.length === 1) { - return first(properties); + forEachChild(node, walk); + if (hasJSDocNodes(node)) { + for (const jsDoc of node.jsDoc!) { + forEachChild(jsDoc, walk); } } - return checker.getSymbolAtLocation(node); + }); +} + +/** + * We want to store any numbers/strings if they were a name that could be + * related to a declaration. So, if we have 'import x = require("something")' + * then we want 'something' to be in the name table. Similarly, if we have + * "a['propname']" then we want to store "propname" in the name table. + */ +function literalIsName(node: StringLiteralLike | NumericLiteral): boolean { + return isDeclarationName(node) || + node.parent.kind === SyntaxKind.ExternalModuleReference || + isArgumentOfElementAccessExpression(node) || + isLiteralComputedPropertyDeclarationName(node); +} + +/** + * Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 } + */ +/* @internal */ +export function getContainingObjectLiteralElement(node: Node): ObjectLiteralElementWithName | undefined { + const element = getContainingObjectLiteralElementWorker(node); + return element && (isObjectLiteralExpression(element.parent) || isJsxAttributes(element.parent)) ? element as ObjectLiteralElementWithName : undefined; +} +function getContainingObjectLiteralElementWorker(node: Node): ObjectLiteralElement | undefined { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.NumericLiteral: + if (node.parent.kind === SyntaxKind.ComputedPropertyName) { + return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined; + } + // falls through + + case SyntaxKind.Identifier: + return isObjectLiteralElement(node.parent) && + (node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === SyntaxKind.JsxAttributes) && + node.parent.name === node ? node.parent : undefined; } + return undefined; +} - /** Gets all symbols for one property. Does not get symbols for every property. */ - /* @internal */ - export function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: TypeChecker, contextualType: Type, unionSymbolOk: boolean): readonly Symbol[] { - const name = getNameFromPropertyName(node.name); - if (!name) return emptyArray; - if (!contextualType.isUnion()) { - const symbol = contextualType.getProperty(name); - return symbol ? [symbol] : emptyArray; - } +/* @internal */ +export type ObjectLiteralElementWithName = ObjectLiteralElement & { name: PropertyName; parent: ObjectLiteralExpression | JsxAttributes }; - const discriminatedPropertySymbols = mapDefined(contextualType.types, t => (isObjectLiteralExpression(node.parent)|| isJsxAttributes(node.parent)) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name)); - if (unionSymbolOk && (discriminatedPropertySymbols.length === 0 || discriminatedPropertySymbols.length === contextualType.types.length)) { - const symbol = contextualType.getProperty(name); - if (symbol) return [symbol]; - } - if (discriminatedPropertySymbols.length === 0) { - // Bad discriminant -- do again without discriminating - return mapDefined(contextualType.types, t => t.getProperty(name)); +function getSymbolAtLocationForQuickInfo(node: Node, checker: TypeChecker): Symbol | undefined { + const object = getContainingObjectLiteralElement(node); + if (object) { + const contextualType = checker.getContextualType(object.parent); + const properties = contextualType && getPropertySymbolsFromContextualType(object, checker, contextualType, /*unionSymbolOk*/ false); + if (properties && properties.length === 1) { + return first(properties); } - return discriminatedPropertySymbols; } + return checker.getSymbolAtLocation(node); +} - function isArgumentOfElementAccessExpression(node: Node) { - return node && - node.parent && - node.parent.kind === SyntaxKind.ElementAccessExpression && - (node.parent as ElementAccessExpression).argumentExpression === node; +/** Gets all symbols for one property. Does not get symbols for every property. */ +/* @internal */ +export function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: TypeChecker, contextualType: Type, unionSymbolOk: boolean): readonly Symbol[] { + const name = getNameFromPropertyName(node.name); + if (!name) return emptyArray; + if (!contextualType.isUnion()) { + const symbol = contextualType.getProperty(name); + return symbol ? [symbol] : emptyArray; } - /// getDefaultLibraryFilePath - declare const __dirname: string; + const discriminatedPropertySymbols = mapDefined(contextualType.types, t => (isObjectLiteralExpression(node.parent)|| isJsxAttributes(node.parent)) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name)); + if (unionSymbolOk && (discriminatedPropertySymbols.length === 0 || discriminatedPropertySymbols.length === contextualType.types.length)) { + const symbol = contextualType.getProperty(name); + if (symbol) return [symbol]; + } + if (discriminatedPropertySymbols.length === 0) { + // Bad discriminant -- do again without discriminating + return mapDefined(contextualType.types, t => t.getProperty(name)); + } + return discriminatedPropertySymbols; +} - /** - * Get the path of the default library files (lib.d.ts) as distributed with the typescript - * node package. - * The functionality is not supported if the ts module is consumed outside of a node module. - */ - export function getDefaultLibFilePath(options: CompilerOptions): string { - // Check __dirname is defined and that we are on a node.js system. - if (typeof __dirname !== "undefined") { - return __dirname + directorySeparator + getDefaultLibFileName(options); - } +function isArgumentOfElementAccessExpression(node: Node) { + return node && + node.parent && + node.parent.kind === SyntaxKind.ElementAccessExpression && + (node.parent as ElementAccessExpression).argumentExpression === node; +} - throw new Error("getDefaultLibFilePath is only supported when consumed as a node module. "); +/// getDefaultLibraryFilePath +declare const __dirname: string; + +/** + * Get the path of the default library files (lib.d.ts) as distributed with the typescript + * node package. + * The functionality is not supported if the ts module is consumed outside of a node module. + */ +export function getDefaultLibFilePath(options: CompilerOptions): string { + // Check __dirname is defined and that we are on a node.js system. + if (typeof __dirname !== "undefined") { + return __dirname + directorySeparator + getDefaultLibFileName(options); } - setObjectAllocator(getServicesObjectAllocator()); + throw new Error("getDefaultLibFilePath is only supported when consumed as a node module. "); +} + +setObjectAllocator(getServicesObjectAllocator()); } diff --git a/src/services/shims.ts b/src/services/shims.ts index 942e943f68f2c..2d5a39f63a9ae 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -23,1336 +23,1336 @@ let debugObjectHost: { CollectGarbage(): void } = (function (this: any) { // esl /* @internal */ namespace ts { - interface DiscoverTypingsInfo { - fileNames: string[]; // The file names that belong to the same project. - projectRootPath: string; // The path to the project root directory - safeListPath: string; // The path used to retrieve the safe list - packageNameToTypingLocation: ESMap; // The map of package names to their cached typing locations and installed versions - typeAcquisition: TypeAcquisition; // Used to customize the type acquisition process - compilerOptions: CompilerOptions; // Used as a source for typing inference - unresolvedImports: readonly string[]; // List of unresolved module ids from imports - typesRegistry: ReadonlyESMap>; // The map of available typings in npm to maps of TS versions to their latest supported versions - } - - export interface ScriptSnapshotShim { - /** Gets a portion of the script snapshot specified by [start, end). */ - getText(start: number, end: number): string; - - /** Gets the length of this script snapshot. */ - getLength(): number; - - /** - * Returns a JSON-encoded value of the type: - * { span: { start: number; length: number }; newLength: number } - * - * Or undefined value if there was no change. - */ - getChangeRange(oldSnapshot: ScriptSnapshotShim): string | undefined; - - /** Releases all resources held by this script snapshot */ - dispose?(): void; - } - - export interface Logger { - log(s: string): void; - trace(s: string): void; - error(s: string): void; - } - - /** Public interface of the host of a language service shim instance. */ - export interface LanguageServiceShimHost extends Logger { - getCompilationSettings(): string; - - /** Returns a JSON-encoded value of the type: string[] */ - getScriptFileNames(): string; - getScriptKind?(fileName: string): ScriptKind; - getScriptVersion(fileName: string): string; - getScriptSnapshot(fileName: string): ScriptSnapshotShim; - getLocalizedDiagnosticMessages(): string; - getCancellationToken(): HostCancellationToken; - getCurrentDirectory(): string; - getDirectories(path: string): string; - getDefaultLibFileName(options: string): string; - getNewLine?(): string; - getProjectVersion?(): string; - useCaseSensitiveFileNames?(): boolean; - - getTypeRootsVersion?(): number; - readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; - readFile(path: string, encoding?: string): string | undefined; - fileExists(path: string): boolean; - - getModuleResolutionsForFile?(fileName: string): string; - getTypeReferenceDirectiveResolutionsForFile?(fileName: string): string; - directoryExists(directoryName: string): boolean; - } - - /** Public interface of the core-services host instance used in managed side */ - export interface CoreServicesShimHost extends Logger { - directoryExists(directoryName: string): boolean; - fileExists(fileName: string): boolean; - getCurrentDirectory(): string; - getDirectories(path: string): string; - - /** - * Returns a JSON-encoded value of the type: string[] - * - * @param exclude A JSON encoded string[] containing the paths to exclude - * when enumerating the directory. - */ - readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; - - /** - * Read arbitrary text files on disk, i.e. when resolution procedure needs the content of 'package.json' to determine location of bundled typings for node modules - */ - readFile(fileName: string): string | undefined; - realpath?(path: string): string; - trace(s: string): void; - useCaseSensitiveFileNames?(): boolean; - } - - /// - /// Pre-processing - /// - // Note: This is being using by the host (VS) and is marshaled back and forth. - // When changing this make sure the changes are reflected in the managed side as well - export interface ShimsFileReference { - path: string; - position: number; - length: number; - } - - /** Public interface of a language service instance shim. */ - export interface ShimFactory { - registerShim(shim: Shim): void; - unregisterShim(shim: Shim): void; - } - - export interface Shim { - dispose(_dummy: {}): void; - } - - export interface LanguageServiceShim extends Shim { - languageService: LanguageService; - - dispose(_dummy: {}): void; - - refresh(throwOnError: boolean): void; - - cleanupSemanticCache(): void; - - getSyntacticDiagnostics(fileName: string): string; - getSemanticDiagnostics(fileName: string): string; - getSuggestionDiagnostics(fileName: string): string; - getCompilerOptionsDiagnostics(): string; - - getSyntacticClassifications(fileName: string, start: number, length: number): string; - getSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string; - getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string; - getEncodedSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string; - - getCompletionsAtPosition(fileName: string, position: number, preferences: UserPreferences | undefined): string; - getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined): string; - - getQuickInfoAtPosition(fileName: string, position: number): string; - - getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string; - getBreakpointStatementAtPosition(fileName: string, position: number): string; - - getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string; - - /** - * Returns a JSON-encoded value of the type: - * { canRename: boolean, localizedErrorMessage: string, displayName: string, fullDisplayName: string, kind: string, kindModifiers: string, triggerSpan: { start; length } } - */ - getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): string; - getSmartSelectionRange(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string, textSpan: { start: number, length: number } }[] - */ - findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string } - * - * Or undefined value if no definition can be found. - */ - getDefinitionAtPosition(fileName: string, position: number): string; - - getDefinitionAndBoundSpan(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string } - * - * Or undefined value if no definition can be found. - */ - getTypeDefinitionAtPosition(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; }[] - */ - getImplementationAtPosition(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[] - */ - getReferencesAtPosition(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { definition: ; references: [] }[] - */ - findReferences(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[] - */ - getFileReferences(fileName: string): string; - - /** - * @deprecated - * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean }[] - */ - getOccurrencesAtPosition(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string; highlights: { start: number; length: number, isDefinition: boolean }[] }[] - * - * @param fileToSearch A JSON encoded string[] containing the file names that should be - * considered when searching. - */ - getDocumentHighlights(fileName: string, position: number, filesToSearch: string): string; - - /** - * Returns a JSON-encoded value of the type: - * { name: string; kind: string; kindModifiers: string; containerName: string; containerKind: string; matchKind: string; fileName: string; textSpan: { start: number; length: number}; } [] = []; - */ - getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): string; - - /** - * Returns a JSON-encoded value of the type: - * { text: string; kind: string; kindModifiers: string; bolded: boolean; grayed: boolean; indent: number; spans: { start: number; length: number; }[]; childItems: [] } [] = []; - */ - getNavigationBarItems(fileName: string): string; - - /** Returns a JSON-encoded value of the type ts.NavigationTree. */ - getNavigationTree(fileName: string): string; - - /** - * Returns a JSON-encoded value of the type: - * { textSpan: { start: number, length: number }; hintSpan: { start: number, length: number }; bannerText: string; autoCollapse: boolean } [] = []; - */ - getOutliningSpans(fileName: string): string; - - getTodoComments(fileName: string, todoCommentDescriptors: string): string; - - getBraceMatchingAtPosition(fileName: string, position: number): string; - getIndentationAtPosition(fileName: string, position: number, options: string/*Services.EditorOptions*/): string; - - getFormattingEditsForRange(fileName: string, start: number, end: number, options: string/*Services.FormatCodeOptions*/): string; - getFormattingEditsForDocument(fileName: string, options: string/*Services.FormatCodeOptions*/): string; - getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: string/*Services.FormatCodeOptions*/): string; - - /** - * Returns JSON-encoded value of the type TextInsertion. - */ - getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): string; - - /** - * Returns JSON-encoded boolean to indicate whether we should support brace location - * at the current position. - * E.g. we don't want brace completion inside string-literals, comments, etc. - */ - isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string; - - /** - * Returns a JSON-encoded TextSpan | undefined indicating the range of the enclosing comment, if it exists. - */ - getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string; - - prepareCallHierarchy(fileName: string, position: number): string; - provideCallHierarchyIncomingCalls(fileName: string, position: number): string; - provideCallHierarchyOutgoingCalls(fileName: string, position: number): string; - provideInlayHints(fileName: string, span: TextSpan, preference: InlayHintsOptions | undefined): string; - getEmitOutput(fileName: string): string; - getEmitOutputObject(fileName: string): EmitOutput; - - toggleLineComment(fileName: string, textChange: TextRange): string; - toggleMultilineComment(fileName: string, textChange: TextRange): string; - commentSelection(fileName: string, textChange: TextRange): string; - uncommentSelection(fileName: string, textChange: TextRange): string; - } - - export interface ClassifierShim extends Shim { - getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent?: boolean): string; - getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent?: boolean): string; - } - - export interface CoreServicesShim extends Shim { - getAutomaticTypeDirectiveNames(compilerOptionsJson: string): string; - getPreProcessedFileInfo(fileName: string, sourceText: IScriptSnapshot): string; - getTSConfigFileInfo(fileName: string, sourceText: IScriptSnapshot): string; - getDefaultCompilationSettings(): string; - discoverTypings(discoverTypingsJson: string): string; - } - - function logInternalError(logger: Logger, err: Error) { - if (logger) { - logger.log("*INTERNAL ERROR* - Exception in typescript services: " + err.message); - } - } +interface DiscoverTypingsInfo { + fileNames: string[]; // The file names that belong to the same project. + projectRootPath: string; // The path to the project root directory + safeListPath: string; // The path used to retrieve the safe list + packageNameToTypingLocation: ESMap; // The map of package names to their cached typing locations and installed versions + typeAcquisition: TypeAcquisition; // Used to customize the type acquisition process + compilerOptions: CompilerOptions; // Used as a source for typing inference + unresolvedImports: readonly string[]; // List of unresolved module ids from imports + typesRegistry: ReadonlyESMap>; // The map of available typings in npm to maps of TS versions to their latest supported versions +} - class ScriptSnapshotShimAdapter implements IScriptSnapshot { - constructor(private scriptSnapshotShim: ScriptSnapshotShim) { - } +export interface ScriptSnapshotShim { + /** Gets a portion of the script snapshot specified by [start, end). */ + getText(start: number, end: number): string; - public getText(start: number, end: number): string { - return this.scriptSnapshotShim.getText(start, end); - } + /** Gets the length of this script snapshot. */ + getLength(): number; - public getLength(): number { - return this.scriptSnapshotShim.getLength(); - } + /** + * Returns a JSON-encoded value of the type: + * { span: { start: number; length: number }; newLength: number } + * + * Or undefined value if there was no change. + */ + getChangeRange(oldSnapshot: ScriptSnapshotShim): string | undefined; - public getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined { - const oldSnapshotShim = oldSnapshot as ScriptSnapshotShimAdapter; - const encoded = this.scriptSnapshotShim.getChangeRange(oldSnapshotShim.scriptSnapshotShim); - /* eslint-disable no-null/no-null */ - if (encoded === null) { - return null!; // TODO: GH#18217 - } - /* eslint-enable no-null/no-null */ + /** Releases all resources held by this script snapshot */ + dispose?(): void; +} - const decoded: { span: { start: number; length: number; }; newLength: number; } = JSON.parse(encoded!); // TODO: GH#18217 - return createTextChangeRange( - createTextSpan(decoded.span.start, decoded.span.length), decoded.newLength); - } +export interface Logger { + log(s: string): void; + trace(s: string): void; + error(s: string): void; +} - public dispose(): void { - // if scriptSnapshotShim is a COM object then property check becomes method call with no arguments - // 'in' does not have this effect - if ("dispose" in this.scriptSnapshotShim) { - this.scriptSnapshotShim.dispose!(); // TODO: GH#18217 Can we just use `if (this.scriptSnapshotShim.dispose)`? - } - } +/** Public interface of the host of a language service shim instance. */ +export interface LanguageServiceShimHost extends Logger { + getCompilationSettings(): string; + + /** Returns a JSON-encoded value of the type: string[] */ + getScriptFileNames(): string; + getScriptKind?(fileName: string): ScriptKind; + getScriptVersion(fileName: string): string; + getScriptSnapshot(fileName: string): ScriptSnapshotShim; + getLocalizedDiagnosticMessages(): string; + getCancellationToken(): HostCancellationToken; + getCurrentDirectory(): string; + getDirectories(path: string): string; + getDefaultLibFileName(options: string): string; + getNewLine?(): string; + getProjectVersion?(): string; + useCaseSensitiveFileNames?(): boolean; + + getTypeRootsVersion?(): number; + readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; + readFile(path: string, encoding?: string): string | undefined; + fileExists(path: string): boolean; + + getModuleResolutionsForFile?(fileName: string): string; + getTypeReferenceDirectiveResolutionsForFile?(fileName: string): string; + directoryExists(directoryName: string): boolean; +} + +/** Public interface of the core-services host instance used in managed side */ +export interface CoreServicesShimHost extends Logger { + directoryExists(directoryName: string): boolean; + fileExists(fileName: string): boolean; + getCurrentDirectory(): string; + getDirectories(path: string): string; + + /** + * Returns a JSON-encoded value of the type: string[] + * + * @param exclude A JSON encoded string[] containing the paths to exclude + * when enumerating the directory. + */ + readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; + + /** + * Read arbitrary text files on disk, i.e. when resolution procedure needs the content of 'package.json' to determine location of bundled typings for node modules + */ + readFile(fileName: string): string | undefined; + realpath?(path: string): string; + trace(s: string): void; + useCaseSensitiveFileNames?(): boolean; +} + +/// +/// Pre-processing +/// +// Note: This is being using by the host (VS) and is marshaled back and forth. +// When changing this make sure the changes are reflected in the managed side as well +export interface ShimsFileReference { + path: string; + position: number; + length: number; +} + +/** Public interface of a language service instance shim. */ +export interface ShimFactory { + registerShim(shim: Shim): void; + unregisterShim(shim: Shim): void; +} + +export interface Shim { + dispose(_dummy: {}): void; +} + +export interface LanguageServiceShim extends Shim { + languageService: LanguageService; + + dispose(_dummy: {}): void; + + refresh(throwOnError: boolean): void; + + cleanupSemanticCache(): void; + + getSyntacticDiagnostics(fileName: string): string; + getSemanticDiagnostics(fileName: string): string; + getSuggestionDiagnostics(fileName: string): string; + getCompilerOptionsDiagnostics(): string; + + getSyntacticClassifications(fileName: string, start: number, length: number): string; + getSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string; + getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string; + getEncodedSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string; + + getCompletionsAtPosition(fileName: string, position: number, preferences: UserPreferences | undefined): string; + getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined): string; + + getQuickInfoAtPosition(fileName: string, position: number): string; + + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string; + getBreakpointStatementAtPosition(fileName: string, position: number): string; + + getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string; + + /** + * Returns a JSON-encoded value of the type: + * { canRename: boolean, localizedErrorMessage: string, displayName: string, fullDisplayName: string, kind: string, kindModifiers: string, triggerSpan: { start; length } } + */ + getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): string; + getSmartSelectionRange(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string, textSpan: { start: number, length: number } }[] + */ + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string } + * + * Or undefined value if no definition can be found. + */ + getDefinitionAtPosition(fileName: string, position: number): string; + + getDefinitionAndBoundSpan(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string } + * + * Or undefined value if no definition can be found. + */ + getTypeDefinitionAtPosition(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; }[] + */ + getImplementationAtPosition(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[] + */ + getReferencesAtPosition(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { definition: ; references: [] }[] + */ + findReferences(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[] + */ + getFileReferences(fileName: string): string; + + /** + * @deprecated + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean }[] + */ + getOccurrencesAtPosition(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; highlights: { start: number; length: number, isDefinition: boolean }[] }[] + * + * @param fileToSearch A JSON encoded string[] containing the file names that should be + * considered when searching. + */ + getDocumentHighlights(fileName: string, position: number, filesToSearch: string): string; + + /** + * Returns a JSON-encoded value of the type: + * { name: string; kind: string; kindModifiers: string; containerName: string; containerKind: string; matchKind: string; fileName: string; textSpan: { start: number; length: number}; } [] = []; + */ + getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): string; + + /** + * Returns a JSON-encoded value of the type: + * { text: string; kind: string; kindModifiers: string; bolded: boolean; grayed: boolean; indent: number; spans: { start: number; length: number; }[]; childItems: [] } [] = []; + */ + getNavigationBarItems(fileName: string): string; + + /** Returns a JSON-encoded value of the type ts.NavigationTree. */ + getNavigationTree(fileName: string): string; + + /** + * Returns a JSON-encoded value of the type: + * { textSpan: { start: number, length: number }; hintSpan: { start: number, length: number }; bannerText: string; autoCollapse: boolean } [] = []; + */ + getOutliningSpans(fileName: string): string; + + getTodoComments(fileName: string, todoCommentDescriptors: string): string; + + getBraceMatchingAtPosition(fileName: string, position: number): string; + getIndentationAtPosition(fileName: string, position: number, options: string/*Services.EditorOptions*/): string; + + getFormattingEditsForRange(fileName: string, start: number, end: number, options: string/*Services.FormatCodeOptions*/): string; + getFormattingEditsForDocument(fileName: string, options: string/*Services.FormatCodeOptions*/): string; + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: string/*Services.FormatCodeOptions*/): string; + + /** + * Returns JSON-encoded value of the type TextInsertion. + */ + getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): string; + + /** + * Returns JSON-encoded boolean to indicate whether we should support brace location + * at the current position. + * E.g. we don't want brace completion inside string-literals, comments, etc. + */ + isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string; + + /** + * Returns a JSON-encoded TextSpan | undefined indicating the range of the enclosing comment, if it exists. + */ + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string; + + prepareCallHierarchy(fileName: string, position: number): string; + provideCallHierarchyIncomingCalls(fileName: string, position: number): string; + provideCallHierarchyOutgoingCalls(fileName: string, position: number): string; + provideInlayHints(fileName: string, span: TextSpan, preference: InlayHintsOptions | undefined): string; + getEmitOutput(fileName: string): string; + getEmitOutputObject(fileName: string): EmitOutput; + + toggleLineComment(fileName: string, textChange: TextRange): string; + toggleMultilineComment(fileName: string, textChange: TextRange): string; + commentSelection(fileName: string, textChange: TextRange): string; + uncommentSelection(fileName: string, textChange: TextRange): string; +} + +export interface ClassifierShim extends Shim { + getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent?: boolean): string; + getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent?: boolean): string; +} + +export interface CoreServicesShim extends Shim { + getAutomaticTypeDirectiveNames(compilerOptionsJson: string): string; + getPreProcessedFileInfo(fileName: string, sourceText: IScriptSnapshot): string; + getTSConfigFileInfo(fileName: string, sourceText: IScriptSnapshot): string; + getDefaultCompilationSettings(): string; + discoverTypings(discoverTypingsJson: string): string; +} + +function logInternalError(logger: Logger, err: Error) { + if (logger) { + logger.log("*INTERNAL ERROR* - Exception in typescript services: " + err.message); } +} - export class LanguageServiceShimHostAdapter implements LanguageServiceHost { - private loggingEnabled = false; - private tracingEnabled = false; - - public resolveModuleNames: ((moduleName: string[], containingFile: string) => (ResolvedModuleFull | undefined)[]) | undefined; - public resolveTypeReferenceDirectives: ((typeDirectiveNames: string[], containingFile: string) => (ResolvedTypeReferenceDirective | undefined)[]) | undefined; - public directoryExists: ((directoryName: string) => boolean) | undefined; - - constructor(private shimHost: LanguageServiceShimHost) { - // if shimHost is a COM object then property check will become method call with no arguments. - // 'in' does not have this effect. - if ("getModuleResolutionsForFile" in this.shimHost) { - this.resolveModuleNames = (moduleNames, containingFile) => { - const resolutionsInFile = JSON.parse(this.shimHost.getModuleResolutionsForFile!(containingFile)) as MapLike; // TODO: GH#18217 - return map(moduleNames, name => { - const result = getProperty(resolutionsInFile, name); - return result ? { resolvedFileName: result, extension: extensionFromPath(result), isExternalLibraryImport: false } : undefined; - }); - }; - } - if ("directoryExists" in this.shimHost) { - this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName); - } - if ("getTypeReferenceDirectiveResolutionsForFile" in this.shimHost) { - this.resolveTypeReferenceDirectives = (typeDirectiveNames, containingFile) => { - const typeDirectivesForFile = JSON.parse(this.shimHost.getTypeReferenceDirectiveResolutionsForFile!(containingFile)) as MapLike; // TODO: GH#18217 - return map(typeDirectiveNames, name => getProperty(typeDirectivesForFile, name)); - }; - } - } +class ScriptSnapshotShimAdapter implements IScriptSnapshot { + constructor(private scriptSnapshotShim: ScriptSnapshotShim) { + } - public log(s: string): void { - if (this.loggingEnabled) { - this.shimHost.log(s); - } - } + public getText(start: number, end: number): string { + return this.scriptSnapshotShim.getText(start, end); + } - public trace(s: string): void { - if (this.tracingEnabled) { - this.shimHost.trace(s); - } - } + public getLength(): number { + return this.scriptSnapshotShim.getLength(); + } - public error(s: string): void { - this.shimHost.error(s); + public getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined { + const oldSnapshotShim = oldSnapshot as ScriptSnapshotShimAdapter; + const encoded = this.scriptSnapshotShim.getChangeRange(oldSnapshotShim.scriptSnapshotShim); + /* eslint-disable no-null/no-null */ + if (encoded === null) { + return null!; // TODO: GH#18217 } + /* eslint-enable no-null/no-null */ - public getProjectVersion(): string { - if (!this.shimHost.getProjectVersion) { - // shimmed host does not support getProjectVersion - return undefined!; // TODO: GH#18217 - } + const decoded: { span: { start: number; length: number; }; newLength: number; } = JSON.parse(encoded!); // TODO: GH#18217 + return createTextChangeRange( + createTextSpan(decoded.span.start, decoded.span.length), decoded.newLength); + } - return this.shimHost.getProjectVersion(); + public dispose(): void { + // if scriptSnapshotShim is a COM object then property check becomes method call with no arguments + // 'in' does not have this effect + if ("dispose" in this.scriptSnapshotShim) { + this.scriptSnapshotShim.dispose!(); // TODO: GH#18217 Can we just use `if (this.scriptSnapshotShim.dispose)`? } + } +} - public getTypeRootsVersion(): number { - if (!this.shimHost.getTypeRootsVersion) { - return 0; - } - return this.shimHost.getTypeRootsVersion(); +export class LanguageServiceShimHostAdapter implements LanguageServiceHost { + private loggingEnabled = false; + private tracingEnabled = false; + + public resolveModuleNames: ((moduleName: string[], containingFile: string) => (ResolvedModuleFull | undefined)[]) | undefined; + public resolveTypeReferenceDirectives: ((typeDirectiveNames: string[], containingFile: string) => (ResolvedTypeReferenceDirective | undefined)[]) | undefined; + public directoryExists: ((directoryName: string) => boolean) | undefined; + + constructor(private shimHost: LanguageServiceShimHost) { + // if shimHost is a COM object then property check will become method call with no arguments. + // 'in' does not have this effect. + if ("getModuleResolutionsForFile" in this.shimHost) { + this.resolveModuleNames = (moduleNames, containingFile) => { + const resolutionsInFile = JSON.parse(this.shimHost.getModuleResolutionsForFile!(containingFile)) as MapLike; // TODO: GH#18217 + return map(moduleNames, name => { + const result = getProperty(resolutionsInFile, name); + return result ? { resolvedFileName: result, extension: extensionFromPath(result), isExternalLibraryImport: false } : undefined; + }); + }; } - - public useCaseSensitiveFileNames(): boolean { - return this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false; + if ("directoryExists" in this.shimHost) { + this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName); } - - public getCompilationSettings(): CompilerOptions { - const settingsJson = this.shimHost.getCompilationSettings(); - // eslint-disable-next-line no-null/no-null - if (settingsJson === null || settingsJson === "") { - throw Error("LanguageServiceShimHostAdapter.getCompilationSettings: empty compilationSettings"); - } - const compilerOptions = JSON.parse(settingsJson) as CompilerOptions; - // permit language service to handle all files (filtering should be performed on the host side) - compilerOptions.allowNonTsExtensions = true; - return compilerOptions; + if ("getTypeReferenceDirectiveResolutionsForFile" in this.shimHost) { + this.resolveTypeReferenceDirectives = (typeDirectiveNames, containingFile) => { + const typeDirectivesForFile = JSON.parse(this.shimHost.getTypeReferenceDirectiveResolutionsForFile!(containingFile)) as MapLike; // TODO: GH#18217 + return map(typeDirectiveNames, name => getProperty(typeDirectivesForFile, name)); + }; } + } - public getScriptFileNames(): string[] { - const encoded = this.shimHost.getScriptFileNames(); - return JSON.parse(encoded); + public log(s: string): void { + if (this.loggingEnabled) { + this.shimHost.log(s); } + } - public getScriptSnapshot(fileName: string): IScriptSnapshot | undefined { - const scriptSnapshot = this.shimHost.getScriptSnapshot(fileName); - return scriptSnapshot && new ScriptSnapshotShimAdapter(scriptSnapshot); + public trace(s: string): void { + if (this.tracingEnabled) { + this.shimHost.trace(s); } + } - public getScriptKind(fileName: string): ScriptKind { - if ("getScriptKind" in this.shimHost) { - return this.shimHost.getScriptKind!(fileName); // TODO: GH#18217 - } - else { - return ScriptKind.Unknown; - } - } + public error(s: string): void { + this.shimHost.error(s); + } - public getScriptVersion(fileName: string): string { - return this.shimHost.getScriptVersion(fileName); + public getProjectVersion(): string { + if (!this.shimHost.getProjectVersion) { + // shimmed host does not support getProjectVersion + return undefined!; // TODO: GH#18217 } - public getLocalizedDiagnosticMessages() { - /* eslint-disable no-null/no-null */ - const diagnosticMessagesJson = this.shimHost.getLocalizedDiagnosticMessages(); - if (diagnosticMessagesJson === null || diagnosticMessagesJson === "") { - return null; - } + return this.shimHost.getProjectVersion(); + } - try { - return JSON.parse(diagnosticMessagesJson); - } - catch (e) { - this.log(e.description || "diagnosticMessages.generated.json has invalid JSON format"); - return null; - } - /* eslint-enable no-null/no-null */ + public getTypeRootsVersion(): number { + if (!this.shimHost.getTypeRootsVersion) { + return 0; } + return this.shimHost.getTypeRootsVersion(); + } - public getCancellationToken(): HostCancellationToken { - const hostCancellationToken = this.shimHost.getCancellationToken(); - return new ThrottledCancellationToken(hostCancellationToken); - } + public useCaseSensitiveFileNames(): boolean { + return this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false; + } - public getCurrentDirectory(): string { - return this.shimHost.getCurrentDirectory(); + public getCompilationSettings(): CompilerOptions { + const settingsJson = this.shimHost.getCompilationSettings(); + // eslint-disable-next-line no-null/no-null + if (settingsJson === null || settingsJson === "") { + throw Error("LanguageServiceShimHostAdapter.getCompilationSettings: empty compilationSettings"); } + const compilerOptions = JSON.parse(settingsJson) as CompilerOptions; + // permit language service to handle all files (filtering should be performed on the host side) + compilerOptions.allowNonTsExtensions = true; + return compilerOptions; + } - public getDirectories(path: string): string[] { - return JSON.parse(this.shimHost.getDirectories(path)); - } + public getScriptFileNames(): string[] { + const encoded = this.shimHost.getScriptFileNames(); + return JSON.parse(encoded); + } - public getDefaultLibFileName(options: CompilerOptions): string { - return this.shimHost.getDefaultLibFileName(JSON.stringify(options)); - } + public getScriptSnapshot(fileName: string): IScriptSnapshot | undefined { + const scriptSnapshot = this.shimHost.getScriptSnapshot(fileName); + return scriptSnapshot && new ScriptSnapshotShimAdapter(scriptSnapshot); + } - public readDirectory(path: string, extensions?: readonly string[], exclude?: string[], include?: string[], depth?: number): string[] { - const pattern = getFileMatcherPatterns(path, exclude, include, - this.shimHost.useCaseSensitiveFileNames!(), this.shimHost.getCurrentDirectory()); // TODO: GH#18217 - return JSON.parse(this.shimHost.readDirectory( - path, - JSON.stringify(extensions), - JSON.stringify(pattern.basePaths), - pattern.excludePattern, - pattern.includeFilePattern, - pattern.includeDirectoryPattern, - depth - )); + public getScriptKind(fileName: string): ScriptKind { + if ("getScriptKind" in this.shimHost) { + return this.shimHost.getScriptKind!(fileName); // TODO: GH#18217 + } + else { + return ScriptKind.Unknown; } + } + + public getScriptVersion(fileName: string): string { + return this.shimHost.getScriptVersion(fileName); + } - public readFile(path: string, encoding?: string): string | undefined { - return this.shimHost.readFile(path, encoding); + public getLocalizedDiagnosticMessages() { + /* eslint-disable no-null/no-null */ + const diagnosticMessagesJson = this.shimHost.getLocalizedDiagnosticMessages(); + if (diagnosticMessagesJson === null || diagnosticMessagesJson === "") { + return null; } - public fileExists(path: string): boolean { - return this.shimHost.fileExists(path); + try { + return JSON.parse(diagnosticMessagesJson); + } + catch (e) { + this.log(e.description || "diagnosticMessages.generated.json has invalid JSON format"); + return null; } + /* eslint-enable no-null/no-null */ } - export class CoreServicesShimHostAdapter implements ParseConfigHost, ModuleResolutionHost, JsTyping.TypingResolutionHost { + public getCancellationToken(): HostCancellationToken { + const hostCancellationToken = this.shimHost.getCancellationToken(); + return new ThrottledCancellationToken(hostCancellationToken); + } - public directoryExists: (directoryName: string) => boolean; - public realpath: (path: string) => string; - public useCaseSensitiveFileNames: boolean; + public getCurrentDirectory(): string { + return this.shimHost.getCurrentDirectory(); + } - constructor(private shimHost: CoreServicesShimHost) { - this.useCaseSensitiveFileNames = this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false; - if ("directoryExists" in this.shimHost) { - this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName); - } - else { - this.directoryExists = undefined!; // TODO: GH#18217 - } - if ("realpath" in this.shimHost) { - this.realpath = path => this.shimHost.realpath!(path); // TODO: GH#18217 - } - else { - this.realpath = undefined!; // TODO: GH#18217 - } - } + public getDirectories(path: string): string[] { + return JSON.parse(this.shimHost.getDirectories(path)); + } - public readDirectory(rootDir: string, extensions: readonly string[], exclude: readonly string[], include: readonly string[], depth?: number): string[] { - const pattern = getFileMatcherPatterns(rootDir, exclude, include, - this.shimHost.useCaseSensitiveFileNames!(), this.shimHost.getCurrentDirectory()); // TODO: GH#18217 - return JSON.parse(this.shimHost.readDirectory( - rootDir, - JSON.stringify(extensions), - JSON.stringify(pattern.basePaths), - pattern.excludePattern, - pattern.includeFilePattern, - pattern.includeDirectoryPattern, - depth - )); - } + public getDefaultLibFileName(options: CompilerOptions): string { + return this.shimHost.getDefaultLibFileName(JSON.stringify(options)); + } - public fileExists(fileName: string): boolean { - return this.shimHost.fileExists(fileName); - } + public readDirectory(path: string, extensions?: readonly string[], exclude?: string[], include?: string[], depth?: number): string[] { + const pattern = getFileMatcherPatterns(path, exclude, include, + this.shimHost.useCaseSensitiveFileNames!(), this.shimHost.getCurrentDirectory()); // TODO: GH#18217 + return JSON.parse(this.shimHost.readDirectory( + path, + JSON.stringify(extensions), + JSON.stringify(pattern.basePaths), + pattern.excludePattern, + pattern.includeFilePattern, + pattern.includeDirectoryPattern, + depth + )); + } - public readFile(fileName: string): string | undefined { - return this.shimHost.readFile(fileName); - } + public readFile(path: string, encoding?: string): string | undefined { + return this.shimHost.readFile(path, encoding); + } - public getDirectories(path: string): string[] { - return JSON.parse(this.shimHost.getDirectories(path)); - } + public fileExists(path: string): boolean { + return this.shimHost.fileExists(path); } +} - function simpleForwardCall(logger: Logger, actionDescription: string, action: () => {}, logPerformance: boolean): {} { - let start: number | undefined; - if (logPerformance) { - logger.log(actionDescription); - start = timestamp(); - } +export class CoreServicesShimHostAdapter implements ParseConfigHost, ModuleResolutionHost, JsTyping.TypingResolutionHost { - const result = action(); - - if (logPerformance) { - const end = timestamp(); - logger.log(`${actionDescription} completed in ${end - start!} msec`); - if (isString(result)) { - let str = result; - if (str.length > 128) { - str = str.substring(0, 128) + "..."; - } - logger.log(` result.length=${str.length}, result='${JSON.stringify(str)}'`); - } + public directoryExists: (directoryName: string) => boolean; + public realpath: (path: string) => string; + public useCaseSensitiveFileNames: boolean; + + constructor(private shimHost: CoreServicesShimHost) { + this.useCaseSensitiveFileNames = this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false; + if ("directoryExists" in this.shimHost) { + this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName); } + else { + this.directoryExists = undefined!; // TODO: GH#18217 + } + if ("realpath" in this.shimHost) { + this.realpath = path => this.shimHost.realpath!(path); // TODO: GH#18217 + } + else { + this.realpath = undefined!; // TODO: GH#18217 + } + } - return result; + public readDirectory(rootDir: string, extensions: readonly string[], exclude: readonly string[], include: readonly string[], depth?: number): string[] { + const pattern = getFileMatcherPatterns(rootDir, exclude, include, + this.shimHost.useCaseSensitiveFileNames!(), this.shimHost.getCurrentDirectory()); // TODO: GH#18217 + return JSON.parse(this.shimHost.readDirectory( + rootDir, + JSON.stringify(extensions), + JSON.stringify(pattern.basePaths), + pattern.excludePattern, + pattern.includeFilePattern, + pattern.includeDirectoryPattern, + depth + )); } - function forwardJSONCall(logger: Logger, actionDescription: string, action: () => {} | null | undefined, logPerformance: boolean): string { - return forwardCall(logger, actionDescription, /*returnJson*/ true, action, logPerformance) as string; + public fileExists(fileName: string): boolean { + return this.shimHost.fileExists(fileName); } - function forwardCall(logger: Logger, actionDescription: string, returnJson: boolean, action: () => T, logPerformance: boolean): T | string { - try { - const result = simpleForwardCall(logger, actionDescription, action, logPerformance); - return returnJson ? JSON.stringify({ result }) : result as T; - } - catch (err) { - if (err instanceof OperationCanceledException) { - return JSON.stringify({ canceled: true }); - } - logInternalError(logger, err); - err.description = actionDescription; - return JSON.stringify({ error: err }); - } + public readFile(fileName: string): string | undefined { + return this.shimHost.readFile(fileName); } + public getDirectories(path: string): string[] { + return JSON.parse(this.shimHost.getDirectories(path)); + } +} - class ShimBase implements Shim { - constructor(private factory: ShimFactory) { - factory.registerShim(this); - } - public dispose(_dummy: {}): void { - this.factory.unregisterShim(this); - } +function simpleForwardCall(logger: Logger, actionDescription: string, action: () => {}, logPerformance: boolean): {} { + let start: number | undefined; + if (logPerformance) { + logger.log(actionDescription); + start = timestamp(); } - export interface RealizedDiagnostic { - message: string; - start: number; - length: number; - category: string; - code: number; - reportsUnnecessary?: {}; - reportsDeprecated?: {}; - } - export function realizeDiagnostics(diagnostics: readonly Diagnostic[], newLine: string): RealizedDiagnostic[] { - return diagnostics.map(d => realizeDiagnostic(d, newLine)); - } - - function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): RealizedDiagnostic { - return { - message: flattenDiagnosticMessageText(diagnostic.messageText, newLine), - start: diagnostic.start!, // TODO: GH#18217 - length: diagnostic.length!, // TODO: GH#18217 - category: diagnosticCategoryName(diagnostic), - code: diagnostic.code, - reportsUnnecessary: diagnostic.reportsUnnecessary, - reportsDeprecated: diagnostic.reportsDeprecated - }; - } - - class LanguageServiceShimObject extends ShimBase implements LanguageServiceShim { - private logger: Logger; - private logPerformance = false; - - constructor(factory: ShimFactory, - private host: LanguageServiceShimHost, - public languageService: LanguageService) { - super(factory); - this.logger = this.host; - } + const result = action(); - public forwardJSONCall(actionDescription: string, action: () => {} | null | undefined): string { - return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance); + if (logPerformance) { + const end = timestamp(); + logger.log(`${actionDescription} completed in ${end - start!} msec`); + if (isString(result)) { + let str = result; + if (str.length > 128) { + str = str.substring(0, 128) + "..."; + } + logger.log(` result.length=${str.length}, result='${JSON.stringify(str)}'`); } + } - /// DISPOSE - - /** - * Ensure (almost) deterministic release of internal Javascript resources when - * some external native objects holds onto us (e.g. Com/Interop). - */ - public dispose(dummy: {}): void { - this.logger.log("dispose()"); - this.languageService.dispose(); - this.languageService = null!; // eslint-disable-line no-null/no-null - - // force a GC - if (debugObjectHost && debugObjectHost.CollectGarbage) { - debugObjectHost.CollectGarbage(); - this.logger.log("CollectGarbage()"); - } + return result; +} - this.logger = null!; // eslint-disable-line no-null/no-null +function forwardJSONCall(logger: Logger, actionDescription: string, action: () => {} | null | undefined, logPerformance: boolean): string { + return forwardCall(logger, actionDescription, /*returnJson*/ true, action, logPerformance) as string; +} - super.dispose(dummy); +function forwardCall(logger: Logger, actionDescription: string, returnJson: boolean, action: () => T, logPerformance: boolean): T | string { + try { + const result = simpleForwardCall(logger, actionDescription, action, logPerformance); + return returnJson ? JSON.stringify({ result }) : result as T; + } + catch (err) { + if (err instanceof OperationCanceledException) { + return JSON.stringify({ canceled: true }); } + logInternalError(logger, err); + err.description = actionDescription; + return JSON.stringify({ error: err }); + } +} - /// REFRESH - /** - * Update the list of scripts known to the compiler - */ - public refresh(throwOnError: boolean): void { - this.forwardJSONCall( - `refresh(${throwOnError})`, - () => null // eslint-disable-line no-null/no-null - ); - } +class ShimBase implements Shim { + constructor(private factory: ShimFactory) { + factory.registerShim(this); + } + public dispose(_dummy: {}): void { + this.factory.unregisterShim(this); + } +} - public cleanupSemanticCache(): void { - this.forwardJSONCall( - "cleanupSemanticCache()", - () => { - this.languageService.cleanupSemanticCache(); - return null; // eslint-disable-line no-null/no-null - }); - } +export interface RealizedDiagnostic { + message: string; + start: number; + length: number; + category: string; + code: number; + reportsUnnecessary?: {}; + reportsDeprecated?: {}; +} +export function realizeDiagnostics(diagnostics: readonly Diagnostic[], newLine: string): RealizedDiagnostic[] { + return diagnostics.map(d => realizeDiagnostic(d, newLine)); +} - private realizeDiagnostics(diagnostics: readonly Diagnostic[]): { message: string; start: number; length: number; category: string; }[] { - const newLine = getNewLineOrDefaultFromHost(this.host); - return realizeDiagnostics(diagnostics, newLine); - } +function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): RealizedDiagnostic { + return { + message: flattenDiagnosticMessageText(diagnostic.messageText, newLine), + start: diagnostic.start!, // TODO: GH#18217 + length: diagnostic.length!, // TODO: GH#18217 + category: diagnosticCategoryName(diagnostic), + code: diagnostic.code, + reportsUnnecessary: diagnostic.reportsUnnecessary, + reportsDeprecated: diagnostic.reportsDeprecated + }; +} - public getSyntacticClassifications(fileName: string, start: number, length: number): string { - return this.forwardJSONCall( - `getSyntacticClassifications('${fileName}', ${start}, ${length})`, - () => this.languageService.getSyntacticClassifications(fileName, createTextSpan(start, length)) - ); - } +class LanguageServiceShimObject extends ShimBase implements LanguageServiceShim { + private logger: Logger; + private logPerformance = false; - public getSemanticClassifications(fileName: string, start: number, length: number): string { - return this.forwardJSONCall( - `getSemanticClassifications('${fileName}', ${start}, ${length})`, - () => this.languageService.getSemanticClassifications(fileName, createTextSpan(start, length)) - ); - } + constructor(factory: ShimFactory, + private host: LanguageServiceShimHost, + public languageService: LanguageService) { + super(factory); + this.logger = this.host; + } - public getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string { - return this.forwardJSONCall( - `getEncodedSyntacticClassifications('${fileName}', ${start}, ${length})`, - // directly serialize the spans out to a string. This is much faster to decode - // on the managed side versus a full JSON array. - () => convertClassifications(this.languageService.getEncodedSyntacticClassifications(fileName, createTextSpan(start, length))) - ); - } + public forwardJSONCall(actionDescription: string, action: () => {} | null | undefined): string { + return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance); + } - public getEncodedSemanticClassifications(fileName: string, start: number, length: number): string { - return this.forwardJSONCall( - `getEncodedSemanticClassifications('${fileName}', ${start}, ${length})`, - // directly serialize the spans out to a string. This is much faster to decode - // on the managed side versus a full JSON array. - () => convertClassifications(this.languageService.getEncodedSemanticClassifications(fileName, createTextSpan(start, length))) - ); - } + /// DISPOSE - public getSyntacticDiagnostics(fileName: string): string { - return this.forwardJSONCall( - `getSyntacticDiagnostics('${fileName}')`, - () => { - const diagnostics = this.languageService.getSyntacticDiagnostics(fileName); - return this.realizeDiagnostics(diagnostics); - }); - } + /** + * Ensure (almost) deterministic release of internal Javascript resources when + * some external native objects holds onto us (e.g. Com/Interop). + */ + public dispose(dummy: {}): void { + this.logger.log("dispose()"); + this.languageService.dispose(); + this.languageService = null!; // eslint-disable-line no-null/no-null - public getSemanticDiagnostics(fileName: string): string { - return this.forwardJSONCall( - `getSemanticDiagnostics('${fileName}')`, - () => { - const diagnostics = this.languageService.getSemanticDiagnostics(fileName); - return this.realizeDiagnostics(diagnostics); - }); + // force a GC + if (debugObjectHost && debugObjectHost.CollectGarbage) { + debugObjectHost.CollectGarbage(); + this.logger.log("CollectGarbage()"); } - public getSuggestionDiagnostics(fileName: string): string { - return this.forwardJSONCall(`getSuggestionDiagnostics('${fileName}')`, () => this.realizeDiagnostics(this.languageService.getSuggestionDiagnostics(fileName))); - } + this.logger = null!; // eslint-disable-line no-null/no-null - public getCompilerOptionsDiagnostics(): string { - return this.forwardJSONCall( - "getCompilerOptionsDiagnostics()", - () => { - const diagnostics = this.languageService.getCompilerOptionsDiagnostics(); - return this.realizeDiagnostics(diagnostics); - }); - } + super.dispose(dummy); + } - /// QUICKINFO - - /** - * Computes a string representation of the type at the requested position - * in the active file. - */ - public getQuickInfoAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getQuickInfoAtPosition('${fileName}', ${position})`, - () => this.languageService.getQuickInfoAtPosition(fileName, position) - ); - } + /// REFRESH + /** + * Update the list of scripts known to the compiler + */ + public refresh(throwOnError: boolean): void { + this.forwardJSONCall( + `refresh(${throwOnError})`, + () => null // eslint-disable-line no-null/no-null + ); + } - /// NAMEORDOTTEDNAMESPAN + public cleanupSemanticCache(): void { + this.forwardJSONCall( + "cleanupSemanticCache()", + () => { + this.languageService.cleanupSemanticCache(); + return null; // eslint-disable-line no-null/no-null + }); + } - /** - * Computes span information of the name or dotted name at the requested position - * in the active file. - */ - public getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string { - return this.forwardJSONCall( - `getNameOrDottedNameSpan('${fileName}', ${startPos}, ${endPos})`, - () => this.languageService.getNameOrDottedNameSpan(fileName, startPos, endPos) - ); - } + private realizeDiagnostics(diagnostics: readonly Diagnostic[]): { message: string; start: number; length: number; category: string; }[] { + const newLine = getNewLineOrDefaultFromHost(this.host); + return realizeDiagnostics(diagnostics, newLine); + } - /** - * STATEMENTSPAN - * Computes span information of statement at the requested position in the active file. - */ - public getBreakpointStatementAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getBreakpointStatementAtPosition('${fileName}', ${position})`, - () => this.languageService.getBreakpointStatementAtPosition(fileName, position) - ); - } + public getSyntacticClassifications(fileName: string, start: number, length: number): string { + return this.forwardJSONCall( + `getSyntacticClassifications('${fileName}', ${start}, ${length})`, + () => this.languageService.getSyntacticClassifications(fileName, createTextSpan(start, length)) + ); + } - /// SIGNATUREHELP + public getSemanticClassifications(fileName: string, start: number, length: number): string { + return this.forwardJSONCall( + `getSemanticClassifications('${fileName}', ${start}, ${length})`, + () => this.languageService.getSemanticClassifications(fileName, createTextSpan(start, length)) + ); + } - public getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string { - return this.forwardJSONCall( - `getSignatureHelpItems('${fileName}', ${position})`, - () => this.languageService.getSignatureHelpItems(fileName, position, options) - ); - } + public getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string { + return this.forwardJSONCall( + `getEncodedSyntacticClassifications('${fileName}', ${start}, ${length})`, + // directly serialize the spans out to a string. This is much faster to decode + // on the managed side versus a full JSON array. + () => convertClassifications(this.languageService.getEncodedSyntacticClassifications(fileName, createTextSpan(start, length))) + ); + } - /// GOTO DEFINITION - - /** - * Computes the definition location and file for the symbol - * at the requested position. - */ - public getDefinitionAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getDefinitionAtPosition('${fileName}', ${position})`, - () => this.languageService.getDefinitionAtPosition(fileName, position) - ); - } + public getEncodedSemanticClassifications(fileName: string, start: number, length: number): string { + return this.forwardJSONCall( + `getEncodedSemanticClassifications('${fileName}', ${start}, ${length})`, + // directly serialize the spans out to a string. This is much faster to decode + // on the managed side versus a full JSON array. + () => convertClassifications(this.languageService.getEncodedSemanticClassifications(fileName, createTextSpan(start, length))) + ); + } - /** - * Computes the definition location and file for the symbol - * at the requested position. - */ - public getDefinitionAndBoundSpan(fileName: string, position: number): string { - return this.forwardJSONCall( - `getDefinitionAndBoundSpan('${fileName}', ${position})`, - () => this.languageService.getDefinitionAndBoundSpan(fileName, position) - ); - } + public getSyntacticDiagnostics(fileName: string): string { + return this.forwardJSONCall( + `getSyntacticDiagnostics('${fileName}')`, + () => { + const diagnostics = this.languageService.getSyntacticDiagnostics(fileName); + return this.realizeDiagnostics(diagnostics); + }); + } - /// GOTO Type - - /** - * Computes the definition location of the type of the symbol - * at the requested position. - */ - public getTypeDefinitionAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getTypeDefinitionAtPosition('${fileName}', ${position})`, - () => this.languageService.getTypeDefinitionAtPosition(fileName, position) - ); - } + public getSemanticDiagnostics(fileName: string): string { + return this.forwardJSONCall( + `getSemanticDiagnostics('${fileName}')`, + () => { + const diagnostics = this.languageService.getSemanticDiagnostics(fileName); + return this.realizeDiagnostics(diagnostics); + }); + } - /// GOTO Implementation - - /** - * Computes the implementation location of the symbol - * at the requested position. - */ - public getImplementationAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getImplementationAtPosition('${fileName}', ${position})`, - () => this.languageService.getImplementationAtPosition(fileName, position) - ); - } + public getSuggestionDiagnostics(fileName: string): string { + return this.forwardJSONCall(`getSuggestionDiagnostics('${fileName}')`, () => this.realizeDiagnostics(this.languageService.getSuggestionDiagnostics(fileName))); + } - public getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): string { - return this.forwardJSONCall( - `getRenameInfo('${fileName}', ${position})`, - () => this.languageService.getRenameInfo(fileName, position, options) - ); - } + public getCompilerOptionsDiagnostics(): string { + return this.forwardJSONCall( + "getCompilerOptionsDiagnostics()", + () => { + const diagnostics = this.languageService.getCompilerOptionsDiagnostics(); + return this.realizeDiagnostics(diagnostics); + }); + } - public getSmartSelectionRange(fileName: string, position: number): string { - return this.forwardJSONCall( - `getSmartSelectionRange('${fileName}', ${position})`, - () => this.languageService.getSmartSelectionRange(fileName, position) - ); - } + /// QUICKINFO + + /** + * Computes a string representation of the type at the requested position + * in the active file. + */ + public getQuickInfoAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getQuickInfoAtPosition('${fileName}', ${position})`, + () => this.languageService.getQuickInfoAtPosition(fileName, position) + ); + } - public findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string { - return this.forwardJSONCall( - `findRenameLocations('${fileName}', ${position}, ${findInStrings}, ${findInComments}, ${providePrefixAndSuffixTextForRename})`, - () => this.languageService.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename) - ); - } - /// GET BRACE MATCHING - public getBraceMatchingAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getBraceMatchingAtPosition('${fileName}', ${position})`, - () => this.languageService.getBraceMatchingAtPosition(fileName, position) - ); - } + /// NAMEORDOTTEDNAMESPAN - public isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string { - return this.forwardJSONCall( - `isValidBraceCompletionAtPosition('${fileName}', ${position}, ${openingBrace})`, - () => this.languageService.isValidBraceCompletionAtPosition(fileName, position, openingBrace) - ); - } + /** + * Computes span information of the name or dotted name at the requested position + * in the active file. + */ + public getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string { + return this.forwardJSONCall( + `getNameOrDottedNameSpan('${fileName}', ${startPos}, ${endPos})`, + () => this.languageService.getNameOrDottedNameSpan(fileName, startPos, endPos) + ); + } - public getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string { - return this.forwardJSONCall( - `getSpanOfEnclosingComment('${fileName}', ${position})`, - () => this.languageService.getSpanOfEnclosingComment(fileName, position, onlyMultiLine) - ); - } + /** + * STATEMENTSPAN + * Computes span information of statement at the requested position in the active file. + */ + public getBreakpointStatementAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getBreakpointStatementAtPosition('${fileName}', ${position})`, + () => this.languageService.getBreakpointStatementAtPosition(fileName, position) + ); + } - /// GET SMART INDENT - public getIndentationAtPosition(fileName: string, position: number, options: string /*Services.EditorOptions*/): string { - return this.forwardJSONCall( - `getIndentationAtPosition('${fileName}', ${position})`, - () => { - const localOptions: EditorOptions = JSON.parse(options); - return this.languageService.getIndentationAtPosition(fileName, position, localOptions); - }); - } + /// SIGNATUREHELP - /// GET REFERENCES + public getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string { + return this.forwardJSONCall( + `getSignatureHelpItems('${fileName}', ${position})`, + () => this.languageService.getSignatureHelpItems(fileName, position, options) + ); + } - public getReferencesAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getReferencesAtPosition('${fileName}', ${position})`, - () => this.languageService.getReferencesAtPosition(fileName, position) - ); - } + /// GOTO DEFINITION + + /** + * Computes the definition location and file for the symbol + * at the requested position. + */ + public getDefinitionAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getDefinitionAtPosition('${fileName}', ${position})`, + () => this.languageService.getDefinitionAtPosition(fileName, position) + ); + } - public findReferences(fileName: string, position: number): string { - return this.forwardJSONCall( - `findReferences('${fileName}', ${position})`, - () => this.languageService.findReferences(fileName, position) - ); - } + /** + * Computes the definition location and file for the symbol + * at the requested position. + */ + public getDefinitionAndBoundSpan(fileName: string, position: number): string { + return this.forwardJSONCall( + `getDefinitionAndBoundSpan('${fileName}', ${position})`, + () => this.languageService.getDefinitionAndBoundSpan(fileName, position) + ); + } - public getFileReferences(fileName: string) { - return this.forwardJSONCall( - `getFileReferences('${fileName})`, - () => this.languageService.getFileReferences(fileName) - ); - } + /// GOTO Type + + /** + * Computes the definition location of the type of the symbol + * at the requested position. + */ + public getTypeDefinitionAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getTypeDefinitionAtPosition('${fileName}', ${position})`, + () => this.languageService.getTypeDefinitionAtPosition(fileName, position) + ); + } - public getOccurrencesAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getOccurrencesAtPosition('${fileName}', ${position})`, - () => this.languageService.getOccurrencesAtPosition(fileName, position) - ); - } + /// GOTO Implementation + + /** + * Computes the implementation location of the symbol + * at the requested position. + */ + public getImplementationAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getImplementationAtPosition('${fileName}', ${position})`, + () => this.languageService.getImplementationAtPosition(fileName, position) + ); + } - public getDocumentHighlights(fileName: string, position: number, filesToSearch: string): string { - return this.forwardJSONCall( - `getDocumentHighlights('${fileName}', ${position})`, - () => { - const results = this.languageService.getDocumentHighlights(fileName, position, JSON.parse(filesToSearch)); - // workaround for VS document highlighting issue - keep only items from the initial file - const normalizedName = toFileNameLowerCase(normalizeSlashes(fileName)); - return filter(results, r => toFileNameLowerCase(normalizeSlashes(r.fileName)) === normalizedName); - }); - } + public getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): string { + return this.forwardJSONCall( + `getRenameInfo('${fileName}', ${position})`, + () => this.languageService.getRenameInfo(fileName, position, options) + ); + } - /// COMPLETION LISTS - - /** - * Get a string based representation of the completions - * to provide at the given source position and providing a member completion - * list if requested. - */ - public getCompletionsAtPosition(fileName: string, position: number, preferences: GetCompletionsAtPositionOptions | undefined) { - return this.forwardJSONCall( - `getCompletionsAtPosition('${fileName}', ${position}, ${preferences})`, - () => this.languageService.getCompletionsAtPosition(fileName, position, preferences) - ); - } + public getSmartSelectionRange(fileName: string, position: number): string { + return this.forwardJSONCall( + `getSmartSelectionRange('${fileName}', ${position})`, + () => this.languageService.getSmartSelectionRange(fileName, position) + ); + } - /** Get a string based representation of a completion list entry details */ - public getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined) { - return this.forwardJSONCall( - `getCompletionEntryDetails('${fileName}', ${position}, '${entryName}')`, - () => { - const localOptions: FormatCodeOptions = formatOptions === undefined ? undefined : JSON.parse(formatOptions); - return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions, source, preferences, data); - } - ); - } + public findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string { + return this.forwardJSONCall( + `findRenameLocations('${fileName}', ${position}, ${findInStrings}, ${findInComments}, ${providePrefixAndSuffixTextForRename})`, + () => this.languageService.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename) + ); + } - public getFormattingEditsForRange(fileName: string, start: number, end: number, options: string/*Services.FormatCodeOptions*/): string { - return this.forwardJSONCall( - `getFormattingEditsForRange('${fileName}', ${start}, ${end})`, - () => { - const localOptions: FormatCodeOptions = JSON.parse(options); - return this.languageService.getFormattingEditsForRange(fileName, start, end, localOptions); - }); - } + /// GET BRACE MATCHING + public getBraceMatchingAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getBraceMatchingAtPosition('${fileName}', ${position})`, + () => this.languageService.getBraceMatchingAtPosition(fileName, position) + ); + } - public getFormattingEditsForDocument(fileName: string, options: string/*Services.FormatCodeOptions*/): string { - return this.forwardJSONCall( - `getFormattingEditsForDocument('${fileName}')`, - () => { - const localOptions: FormatCodeOptions = JSON.parse(options); - return this.languageService.getFormattingEditsForDocument(fileName, localOptions); - }); - } + public isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string { + return this.forwardJSONCall( + `isValidBraceCompletionAtPosition('${fileName}', ${position}, ${openingBrace})`, + () => this.languageService.isValidBraceCompletionAtPosition(fileName, position, openingBrace) + ); + } - public getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: string/*Services.FormatCodeOptions*/): string { - return this.forwardJSONCall( - `getFormattingEditsAfterKeystroke('${fileName}', ${position}, '${key}')`, - () => { - const localOptions: FormatCodeOptions = JSON.parse(options); - return this.languageService.getFormattingEditsAfterKeystroke(fileName, position, key, localOptions); - }); - } + public getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string { + return this.forwardJSONCall( + `getSpanOfEnclosingComment('${fileName}', ${position})`, + () => this.languageService.getSpanOfEnclosingComment(fileName, position, onlyMultiLine) + ); + } - public getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): string { - return this.forwardJSONCall( - `getDocCommentTemplateAtPosition('${fileName}', ${position})`, - () => this.languageService.getDocCommentTemplateAtPosition(fileName, position, options) - ); - } + /// GET SMART INDENT + public getIndentationAtPosition(fileName: string, position: number, options: string /*Services.EditorOptions*/): string { + return this.forwardJSONCall( + `getIndentationAtPosition('${fileName}', ${position})`, + () => { + const localOptions: EditorOptions = JSON.parse(options); + return this.languageService.getIndentationAtPosition(fileName, position, localOptions); + }); + } - /// NAVIGATE TO + /// GET REFERENCES - /** Return a list of symbols that are interesting to navigate to */ - public getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): string { - return this.forwardJSONCall( - `getNavigateToItems('${searchValue}', ${maxResultCount}, ${fileName})`, - () => this.languageService.getNavigateToItems(searchValue, maxResultCount, fileName) - ); - } + public getReferencesAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getReferencesAtPosition('${fileName}', ${position})`, + () => this.languageService.getReferencesAtPosition(fileName, position) + ); + } - public getNavigationBarItems(fileName: string): string { - return this.forwardJSONCall( - `getNavigationBarItems('${fileName}')`, - () => this.languageService.getNavigationBarItems(fileName) - ); - } + public findReferences(fileName: string, position: number): string { + return this.forwardJSONCall( + `findReferences('${fileName}', ${position})`, + () => this.languageService.findReferences(fileName, position) + ); + } - public getNavigationTree(fileName: string): string { - return this.forwardJSONCall( - `getNavigationTree('${fileName}')`, - () => this.languageService.getNavigationTree(fileName) - ); - } + public getFileReferences(fileName: string) { + return this.forwardJSONCall( + `getFileReferences('${fileName})`, + () => this.languageService.getFileReferences(fileName) + ); + } - public getOutliningSpans(fileName: string): string { - return this.forwardJSONCall( - `getOutliningSpans('${fileName}')`, - () => this.languageService.getOutliningSpans(fileName) - ); - } + public getOccurrencesAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getOccurrencesAtPosition('${fileName}', ${position})`, + () => this.languageService.getOccurrencesAtPosition(fileName, position) + ); + } - public getTodoComments(fileName: string, descriptors: string): string { - return this.forwardJSONCall( - `getTodoComments('${fileName}')`, - () => this.languageService.getTodoComments(fileName, JSON.parse(descriptors)) - ); - } + public getDocumentHighlights(fileName: string, position: number, filesToSearch: string): string { + return this.forwardJSONCall( + `getDocumentHighlights('${fileName}', ${position})`, + () => { + const results = this.languageService.getDocumentHighlights(fileName, position, JSON.parse(filesToSearch)); + // workaround for VS document highlighting issue - keep only items from the initial file + const normalizedName = toFileNameLowerCase(normalizeSlashes(fileName)); + return filter(results, r => toFileNameLowerCase(normalizeSlashes(r.fileName)) === normalizedName); + }); + } - /// CALL HIERARCHY + /// COMPLETION LISTS + + /** + * Get a string based representation of the completions + * to provide at the given source position and providing a member completion + * list if requested. + */ + public getCompletionsAtPosition(fileName: string, position: number, preferences: GetCompletionsAtPositionOptions | undefined) { + return this.forwardJSONCall( + `getCompletionsAtPosition('${fileName}', ${position}, ${preferences})`, + () => this.languageService.getCompletionsAtPosition(fileName, position, preferences) + ); + } - public prepareCallHierarchy(fileName: string, position: number): string { - return this.forwardJSONCall( - `prepareCallHierarchy('${fileName}', ${position})`, - () => this.languageService.prepareCallHierarchy(fileName, position) - ); - } + /** Get a string based representation of a completion list entry details */ + public getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined) { + return this.forwardJSONCall( + `getCompletionEntryDetails('${fileName}', ${position}, '${entryName}')`, + () => { + const localOptions: FormatCodeOptions = formatOptions === undefined ? undefined : JSON.parse(formatOptions); + return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions, source, preferences, data); + } + ); + } - public provideCallHierarchyIncomingCalls(fileName: string, position: number): string { - return this.forwardJSONCall( - `provideCallHierarchyIncomingCalls('${fileName}', ${position})`, - () => this.languageService.provideCallHierarchyIncomingCalls(fileName, position) - ); - } + public getFormattingEditsForRange(fileName: string, start: number, end: number, options: string/*Services.FormatCodeOptions*/): string { + return this.forwardJSONCall( + `getFormattingEditsForRange('${fileName}', ${start}, ${end})`, + () => { + const localOptions: FormatCodeOptions = JSON.parse(options); + return this.languageService.getFormattingEditsForRange(fileName, start, end, localOptions); + }); + } - public provideCallHierarchyOutgoingCalls(fileName: string, position: number): string { - return this.forwardJSONCall( - `provideCallHierarchyOutgoingCalls('${fileName}', ${position})`, - () => this.languageService.provideCallHierarchyOutgoingCalls(fileName, position) - ); - } + public getFormattingEditsForDocument(fileName: string, options: string/*Services.FormatCodeOptions*/): string { + return this.forwardJSONCall( + `getFormattingEditsForDocument('${fileName}')`, + () => { + const localOptions: FormatCodeOptions = JSON.parse(options); + return this.languageService.getFormattingEditsForDocument(fileName, localOptions); + }); + } - public provideInlayHints(fileName: string, span: TextSpan, preference: InlayHintsOptions | undefined): string { - return this.forwardJSONCall( - `provideInlayHints('${fileName}', '${JSON.stringify(span)}', ${JSON.stringify(preference)})`, - () => this.languageService.provideInlayHints(fileName, span, preference) - ); - } + public getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: string/*Services.FormatCodeOptions*/): string { + return this.forwardJSONCall( + `getFormattingEditsAfterKeystroke('${fileName}', ${position}, '${key}')`, + () => { + const localOptions: FormatCodeOptions = JSON.parse(options); + return this.languageService.getFormattingEditsAfterKeystroke(fileName, position, key, localOptions); + }); + } - /// Emit - public getEmitOutput(fileName: string): string { - return this.forwardJSONCall( - `getEmitOutput('${fileName}')`, - () => { - const { diagnostics, ...rest } = this.languageService.getEmitOutput(fileName); - return { ...rest, diagnostics: this.realizeDiagnostics(diagnostics) }; - } - ); - } + public getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): string { + return this.forwardJSONCall( + `getDocCommentTemplateAtPosition('${fileName}', ${position})`, + () => this.languageService.getDocCommentTemplateAtPosition(fileName, position, options) + ); + } - public getEmitOutputObject(fileName: string): EmitOutput { - return forwardCall( - this.logger, - `getEmitOutput('${fileName}')`, - /*returnJson*/ false, - () => this.languageService.getEmitOutput(fileName), - this.logPerformance) as EmitOutput; - } + /// NAVIGATE TO - public toggleLineComment(fileName: string, textRange: TextRange): string { - return this.forwardJSONCall( - `toggleLineComment('${fileName}', '${JSON.stringify(textRange)}')`, - () => this.languageService.toggleLineComment(fileName, textRange) - ); - } + /** Return a list of symbols that are interesting to navigate to */ + public getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): string { + return this.forwardJSONCall( + `getNavigateToItems('${searchValue}', ${maxResultCount}, ${fileName})`, + () => this.languageService.getNavigateToItems(searchValue, maxResultCount, fileName) + ); + } - public toggleMultilineComment(fileName: string, textRange: TextRange): string { - return this.forwardJSONCall( - `toggleMultilineComment('${fileName}', '${JSON.stringify(textRange)}')`, - () => this.languageService.toggleMultilineComment(fileName, textRange) - ); - } + public getNavigationBarItems(fileName: string): string { + return this.forwardJSONCall( + `getNavigationBarItems('${fileName}')`, + () => this.languageService.getNavigationBarItems(fileName) + ); + } - public commentSelection(fileName: string, textRange: TextRange): string { - return this.forwardJSONCall( - `commentSelection('${fileName}', '${JSON.stringify(textRange)}')`, - () => this.languageService.commentSelection(fileName, textRange) - ); - } + public getNavigationTree(fileName: string): string { + return this.forwardJSONCall( + `getNavigationTree('${fileName}')`, + () => this.languageService.getNavigationTree(fileName) + ); + } - public uncommentSelection(fileName: string, textRange: TextRange): string { - return this.forwardJSONCall( - `uncommentSelection('${fileName}', '${JSON.stringify(textRange)}')`, - () => this.languageService.uncommentSelection(fileName, textRange) - ); - } + public getOutliningSpans(fileName: string): string { + return this.forwardJSONCall( + `getOutliningSpans('${fileName}')`, + () => this.languageService.getOutliningSpans(fileName) + ); } - function convertClassifications(classifications: Classifications): { spans: string, endOfLineState: EndOfLineState } { - return { spans: classifications.spans.join(","), endOfLineState: classifications.endOfLineState }; + public getTodoComments(fileName: string, descriptors: string): string { + return this.forwardJSONCall( + `getTodoComments('${fileName}')`, + () => this.languageService.getTodoComments(fileName, JSON.parse(descriptors)) + ); } - class ClassifierShimObject extends ShimBase implements ClassifierShim { - public classifier: Classifier; - private logPerformance = false; + /// CALL HIERARCHY - constructor(factory: ShimFactory, private logger: Logger) { - super(factory); - this.classifier = createClassifier(); - } + public prepareCallHierarchy(fileName: string, position: number): string { + return this.forwardJSONCall( + `prepareCallHierarchy('${fileName}', ${position})`, + () => this.languageService.prepareCallHierarchy(fileName, position) + ); + } - public getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent = false): string { - return forwardJSONCall(this.logger, "getEncodedLexicalClassifications", - () => convertClassifications(this.classifier.getEncodedLexicalClassifications(text, lexState, syntacticClassifierAbsent)), - this.logPerformance); - } + public provideCallHierarchyIncomingCalls(fileName: string, position: number): string { + return this.forwardJSONCall( + `provideCallHierarchyIncomingCalls('${fileName}', ${position})`, + () => this.languageService.provideCallHierarchyIncomingCalls(fileName, position) + ); + } + + public provideCallHierarchyOutgoingCalls(fileName: string, position: number): string { + return this.forwardJSONCall( + `provideCallHierarchyOutgoingCalls('${fileName}', ${position})`, + () => this.languageService.provideCallHierarchyOutgoingCalls(fileName, position) + ); + } + + public provideInlayHints(fileName: string, span: TextSpan, preference: InlayHintsOptions | undefined): string { + return this.forwardJSONCall( + `provideInlayHints('${fileName}', '${JSON.stringify(span)}', ${JSON.stringify(preference)})`, + () => this.languageService.provideInlayHints(fileName, span, preference) + ); + } - /// COLORIZATION - public getClassificationsForLine(text: string, lexState: EndOfLineState, classifyKeywordsInGenerics = false): string { - const classification = this.classifier.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics); - let result = ""; - for (const item of classification.entries) { - result += item.length + "\n"; - result += item.classification + "\n"; + /// Emit + public getEmitOutput(fileName: string): string { + return this.forwardJSONCall( + `getEmitOutput('${fileName}')`, + () => { + const { diagnostics, ...rest } = this.languageService.getEmitOutput(fileName); + return { ...rest, diagnostics: this.realizeDiagnostics(diagnostics) }; } - result += classification.finalLexState; - return result; - } + ); } - class CoreServicesShimObject extends ShimBase implements CoreServicesShim { - private logPerformance = false; - private safeList: JsTyping.SafeList | undefined; + public getEmitOutputObject(fileName: string): EmitOutput { + return forwardCall( + this.logger, + `getEmitOutput('${fileName}')`, + /*returnJson*/ false, + () => this.languageService.getEmitOutput(fileName), + this.logPerformance) as EmitOutput; + } - constructor(factory: ShimFactory, public readonly logger: Logger, private readonly host: CoreServicesShimHostAdapter) { - super(factory); - } + public toggleLineComment(fileName: string, textRange: TextRange): string { + return this.forwardJSONCall( + `toggleLineComment('${fileName}', '${JSON.stringify(textRange)}')`, + () => this.languageService.toggleLineComment(fileName, textRange) + ); + } - private forwardJSONCall(actionDescription: string, action: () => {}): string { - return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance); + public toggleMultilineComment(fileName: string, textRange: TextRange): string { + return this.forwardJSONCall( + `toggleMultilineComment('${fileName}', '${JSON.stringify(textRange)}')`, + () => this.languageService.toggleMultilineComment(fileName, textRange) + ); + } + + public commentSelection(fileName: string, textRange: TextRange): string { + return this.forwardJSONCall( + `commentSelection('${fileName}', '${JSON.stringify(textRange)}')`, + () => this.languageService.commentSelection(fileName, textRange) + ); + } + + public uncommentSelection(fileName: string, textRange: TextRange): string { + return this.forwardJSONCall( + `uncommentSelection('${fileName}', '${JSON.stringify(textRange)}')`, + () => this.languageService.uncommentSelection(fileName, textRange) + ); + } +} + +function convertClassifications(classifications: Classifications): { spans: string, endOfLineState: EndOfLineState } { + return { spans: classifications.spans.join(","), endOfLineState: classifications.endOfLineState }; +} + +class ClassifierShimObject extends ShimBase implements ClassifierShim { + public classifier: Classifier; + private logPerformance = false; + + constructor(factory: ShimFactory, private logger: Logger) { + super(factory); + this.classifier = createClassifier(); + } + + public getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent = false): string { + return forwardJSONCall(this.logger, "getEncodedLexicalClassifications", + () => convertClassifications(this.classifier.getEncodedLexicalClassifications(text, lexState, syntacticClassifierAbsent)), + this.logPerformance); + } + + /// COLORIZATION + public getClassificationsForLine(text: string, lexState: EndOfLineState, classifyKeywordsInGenerics = false): string { + const classification = this.classifier.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics); + let result = ""; + for (const item of classification.entries) { + result += item.length + "\n"; + result += item.classification + "\n"; } + result += classification.finalLexState; + return result; + } +} - public resolveModuleName(fileName: string, moduleName: string, compilerOptionsJson: string): string { - return this.forwardJSONCall(`resolveModuleName('${fileName}')`, () => { - const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions; - const result = resolveModuleName(moduleName, normalizeSlashes(fileName), compilerOptions, this.host); - let resolvedFileName = result.resolvedModule ? result.resolvedModule.resolvedFileName : undefined; - if (result.resolvedModule && result.resolvedModule.extension !== Extension.Ts && result.resolvedModule.extension !== Extension.Tsx && result.resolvedModule.extension !== Extension.Dts) { - resolvedFileName = undefined; - } +class CoreServicesShimObject extends ShimBase implements CoreServicesShim { + private logPerformance = false; + private safeList: JsTyping.SafeList | undefined; + + constructor(factory: ShimFactory, public readonly logger: Logger, private readonly host: CoreServicesShimHostAdapter) { + super(factory); + } + + private forwardJSONCall(actionDescription: string, action: () => {}): string { + return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance); + } + public resolveModuleName(fileName: string, moduleName: string, compilerOptionsJson: string): string { + return this.forwardJSONCall(`resolveModuleName('${fileName}')`, () => { + const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions; + const result = resolveModuleName(moduleName, normalizeSlashes(fileName), compilerOptions, this.host); + let resolvedFileName = result.resolvedModule ? result.resolvedModule.resolvedFileName : undefined; + if (result.resolvedModule && result.resolvedModule.extension !== Extension.Ts && result.resolvedModule.extension !== Extension.Tsx && result.resolvedModule.extension !== Extension.Dts) { + resolvedFileName = undefined; + } + + return { + resolvedFileName, + failedLookupLocations: result.failedLookupLocations + }; + }); + } + + public resolveTypeReferenceDirective(fileName: string, typeReferenceDirective: string, compilerOptionsJson: string): string { + return this.forwardJSONCall(`resolveTypeReferenceDirective(${fileName})`, () => { + const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions; + const result = resolveTypeReferenceDirective(typeReferenceDirective, normalizeSlashes(fileName), compilerOptions, this.host); + return { + resolvedFileName: result.resolvedTypeReferenceDirective ? result.resolvedTypeReferenceDirective.resolvedFileName : undefined, + primary: result.resolvedTypeReferenceDirective ? result.resolvedTypeReferenceDirective.primary : true, + failedLookupLocations: result.failedLookupLocations + }; + }); + } + + public getPreProcessedFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string { + return this.forwardJSONCall( + `getPreProcessedFileInfo('${fileName}')`, + () => { + // for now treat files as JavaScript + const result = preProcessFile(getSnapshotText(sourceTextSnapshot), /* readImportFiles */ true, /* detectJavaScriptImports */ true); return { - resolvedFileName, - failedLookupLocations: result.failedLookupLocations + referencedFiles: this.convertFileReferences(result.referencedFiles), + importedFiles: this.convertFileReferences(result.importedFiles), + ambientExternalModules: result.ambientExternalModules, + isLibFile: result.isLibFile, + typeReferenceDirectives: this.convertFileReferences(result.typeReferenceDirectives), + libReferenceDirectives: this.convertFileReferences(result.libReferenceDirectives) }; }); - } + } - public resolveTypeReferenceDirective(fileName: string, typeReferenceDirective: string, compilerOptionsJson: string): string { - return this.forwardJSONCall(`resolveTypeReferenceDirective(${fileName})`, () => { + public getAutomaticTypeDirectiveNames(compilerOptionsJson: string): string { + return this.forwardJSONCall( + `getAutomaticTypeDirectiveNames('${compilerOptionsJson}')`, + () => { const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions; - const result = resolveTypeReferenceDirective(typeReferenceDirective, normalizeSlashes(fileName), compilerOptions, this.host); - return { - resolvedFileName: result.resolvedTypeReferenceDirective ? result.resolvedTypeReferenceDirective.resolvedFileName : undefined, - primary: result.resolvedTypeReferenceDirective ? result.resolvedTypeReferenceDirective.primary : true, - failedLookupLocations: result.failedLookupLocations - }; - }); - } + return getAutomaticTypeDirectiveNames(compilerOptions, this.host); + } + ); + } - public getPreProcessedFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string { - return this.forwardJSONCall( - `getPreProcessedFileInfo('${fileName}')`, - () => { - // for now treat files as JavaScript - const result = preProcessFile(getSnapshotText(sourceTextSnapshot), /* readImportFiles */ true, /* detectJavaScriptImports */ true); - return { - referencedFiles: this.convertFileReferences(result.referencedFiles), - importedFiles: this.convertFileReferences(result.importedFiles), - ambientExternalModules: result.ambientExternalModules, - isLibFile: result.isLibFile, - typeReferenceDirectives: this.convertFileReferences(result.typeReferenceDirectives), - libReferenceDirectives: this.convertFileReferences(result.libReferenceDirectives) - }; - }); + private convertFileReferences(refs: FileReference[]): ShimsFileReference[] | undefined { + if (!refs) { + return undefined; } - - public getAutomaticTypeDirectiveNames(compilerOptionsJson: string): string { - return this.forwardJSONCall( - `getAutomaticTypeDirectiveNames('${compilerOptionsJson}')`, - () => { - const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions; - return getAutomaticTypeDirectiveNames(compilerOptions, this.host); - } - ); + const result: ShimsFileReference[] = []; + for (const ref of refs) { + result.push({ + path: normalizeSlashes(ref.fileName), + position: ref.pos, + length: ref.end - ref.pos + }); } + return result; + } - private convertFileReferences(refs: FileReference[]): ShimsFileReference[] | undefined { - if (!refs) { - return undefined; - } - const result: ShimsFileReference[] = []; - for (const ref of refs) { - result.push({ - path: normalizeSlashes(ref.fileName), - position: ref.pos, - length: ref.end - ref.pos - }); - } - return result; - } + public getTSConfigFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string { + return this.forwardJSONCall( + `getTSConfigFileInfo('${fileName}')`, + () => { + const result = parseJsonText(fileName, getSnapshotText(sourceTextSnapshot)); + const normalizedFileName = normalizeSlashes(fileName); + const configFile = parseJsonSourceFileConfigFileContent(result, this.host, getDirectoryPath(normalizedFileName), /*existingOptions*/ {}, normalizedFileName); - public getTSConfigFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string { - return this.forwardJSONCall( - `getTSConfigFileInfo('${fileName}')`, - () => { - const result = parseJsonText(fileName, getSnapshotText(sourceTextSnapshot)); - const normalizedFileName = normalizeSlashes(fileName); - const configFile = parseJsonSourceFileConfigFileContent(result, this.host, getDirectoryPath(normalizedFileName), /*existingOptions*/ {}, normalizedFileName); - - return { - options: configFile.options, - typeAcquisition: configFile.typeAcquisition, - files: configFile.fileNames, - raw: configFile.raw, - errors: realizeDiagnostics([...result.parseDiagnostics, ...configFile.errors], "\r\n") - }; - }); - } + return { + options: configFile.options, + typeAcquisition: configFile.typeAcquisition, + files: configFile.fileNames, + raw: configFile.raw, + errors: realizeDiagnostics([...result.parseDiagnostics, ...configFile.errors], "\r\n") + }; + }); + } - public getDefaultCompilationSettings(): string { - return this.forwardJSONCall( - "getDefaultCompilationSettings()", - () => getDefaultCompilerOptions() - ); - } + public getDefaultCompilationSettings(): string { + return this.forwardJSONCall( + "getDefaultCompilationSettings()", + () => getDefaultCompilerOptions() + ); + } - public discoverTypings(discoverTypingsJson: string): string { - const getCanonicalFileName = createGetCanonicalFileName(/*useCaseSensitivefileNames:*/ false); - return this.forwardJSONCall("discoverTypings()", () => { - const info = JSON.parse(discoverTypingsJson) as DiscoverTypingsInfo; - if (this.safeList === undefined) { - this.safeList = JsTyping.loadSafeList(this.host, toPath(info.safeListPath, info.safeListPath, getCanonicalFileName)); - } - return JsTyping.discoverTypings( - this.host, - msg => this.logger.log(msg), - info.fileNames, - toPath(info.projectRootPath, info.projectRootPath, getCanonicalFileName), - this.safeList, - info.packageNameToTypingLocation, - info.typeAcquisition, - info.unresolvedImports, - info.typesRegistry); - }); - } + public discoverTypings(discoverTypingsJson: string): string { + const getCanonicalFileName = createGetCanonicalFileName(/*useCaseSensitivefileNames:*/ false); + return this.forwardJSONCall("discoverTypings()", () => { + const info = JSON.parse(discoverTypingsJson) as DiscoverTypingsInfo; + if (this.safeList === undefined) { + this.safeList = JsTyping.loadSafeList(this.host, toPath(info.safeListPath, info.safeListPath, getCanonicalFileName)); + } + return JsTyping.discoverTypings( + this.host, + msg => this.logger.log(msg), + info.fileNames, + toPath(info.projectRootPath, info.projectRootPath, getCanonicalFileName), + this.safeList, + info.packageNameToTypingLocation, + info.typeAcquisition, + info.unresolvedImports, + info.typesRegistry); + }); } +} - export class TypeScriptServicesFactory implements ShimFactory { - private _shims: Shim[] = []; - private documentRegistry: DocumentRegistry | undefined; +export class TypeScriptServicesFactory implements ShimFactory { + private _shims: Shim[] = []; + private documentRegistry: DocumentRegistry | undefined; - /* - * Returns script API version. - */ - public getServicesVersion(): string { - return servicesVersion; - } + /* + * Returns script API version. + */ + public getServicesVersion(): string { + return servicesVersion; + } - public createLanguageServiceShim(host: LanguageServiceShimHost): LanguageServiceShim { - try { - if (this.documentRegistry === undefined) { - this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()); - } - const hostAdapter = new LanguageServiceShimHostAdapter(host); - const languageService = createLanguageService(hostAdapter, this.documentRegistry, /*syntaxOnly*/ false); - return new LanguageServiceShimObject(this, host, languageService); - } - catch (err) { - logInternalError(host, err); - throw err; + public createLanguageServiceShim(host: LanguageServiceShimHost): LanguageServiceShim { + try { + if (this.documentRegistry === undefined) { + this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()); } + const hostAdapter = new LanguageServiceShimHostAdapter(host); + const languageService = createLanguageService(hostAdapter, this.documentRegistry, /*syntaxOnly*/ false); + return new LanguageServiceShimObject(this, host, languageService); } - - public createClassifierShim(logger: Logger): ClassifierShim { - try { - return new ClassifierShimObject(this, logger); - } - catch (err) { - logInternalError(logger, err); - throw err; - } + catch (err) { + logInternalError(host, err); + throw err; } + } - public createCoreServicesShim(host: CoreServicesShimHost): CoreServicesShim { - try { - const adapter = new CoreServicesShimHostAdapter(host); - return new CoreServicesShimObject(this, host as Logger, adapter); - } - catch (err) { - logInternalError(host as Logger, err); - throw err; - } + public createClassifierShim(logger: Logger): ClassifierShim { + try { + return new ClassifierShimObject(this, logger); } - - public close(): void { - // Forget all the registered shims - clear(this._shims); - this.documentRegistry = undefined; + catch (err) { + logInternalError(logger, err); + throw err; } + } - public registerShim(shim: Shim): void { - this._shims.push(shim); + public createCoreServicesShim(host: CoreServicesShimHost): CoreServicesShim { + try { + const adapter = new CoreServicesShimHostAdapter(host); + return new CoreServicesShimObject(this, host as Logger, adapter); } + catch (err) { + logInternalError(host as Logger, err); + throw err; + } + } - public unregisterShim(shim: Shim): void { - for (let i = 0; i < this._shims.length; i++) { - if (this._shims[i] === shim) { - delete this._shims[i]; - return; - } - } + public close(): void { + // Forget all the registered shims + clear(this._shims); + this.documentRegistry = undefined; + } - throw new Error("Invalid operation"); + public registerShim(shim: Shim): void { + this._shims.push(shim); + } + + public unregisterShim(shim: Shim): void { + for (let i = 0; i < this._shims.length; i++) { + if (this._shims[i] === shim) { + delete this._shims[i]; + return; + } } + + throw new Error("Invalid operation"); } } +} /* eslint-enable no-in-operator */ diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index ea72661a8687c..1fcf5c955d99a 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -1,664 +1,664 @@ /* @internal */ namespace ts.SignatureHelp { - const enum InvocationKind { Call, TypeArgs, Contextual } - interface CallInvocation { readonly kind: InvocationKind.Call; readonly node: CallLikeExpression; } - interface TypeArgsInvocation { readonly kind: InvocationKind.TypeArgs; readonly called: Identifier; } - interface ContextualInvocation { - readonly kind: InvocationKind.Contextual; - readonly signature: Signature; - readonly node: Node; // Just for enclosingDeclaration for printing types - readonly symbol: Symbol; - } - type Invocation = CallInvocation | TypeArgsInvocation | ContextualInvocation; - - interface ArgumentListInfo { - readonly isTypeParameterList: boolean; - readonly invocation: Invocation; - readonly argumentsSpan: TextSpan; - readonly argumentIndex: number; - /** argumentCount is the *apparent* number of arguments. */ - readonly argumentCount: number; - } +const enum InvocationKind { Call, TypeArgs, Contextual } +interface CallInvocation { readonly kind: InvocationKind.Call; readonly node: CallLikeExpression; } +interface TypeArgsInvocation { readonly kind: InvocationKind.TypeArgs; readonly called: Identifier; } +interface ContextualInvocation { + readonly kind: InvocationKind.Contextual; + readonly signature: Signature; + readonly node: Node; // Just for enclosingDeclaration for printing types + readonly symbol: Symbol; +} +type Invocation = CallInvocation | TypeArgsInvocation | ContextualInvocation; + +interface ArgumentListInfo { + readonly isTypeParameterList: boolean; + readonly invocation: Invocation; + readonly argumentsSpan: TextSpan; + readonly argumentIndex: number; + /** argumentCount is the *apparent* number of arguments. */ + readonly argumentCount: number; +} - export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, triggerReason: SignatureHelpTriggerReason | undefined, cancellationToken: CancellationToken): SignatureHelpItems | undefined { - const typeChecker = program.getTypeChecker(); +export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, triggerReason: SignatureHelpTriggerReason | undefined, cancellationToken: CancellationToken): SignatureHelpItems | undefined { + const typeChecker = program.getTypeChecker(); - // Decide whether to show signature help - const startingToken = findTokenOnLeftOfPosition(sourceFile, position); - if (!startingToken) { - // We are at the beginning of the file - return undefined; - } - - // Only need to be careful if the user typed a character and signature help wasn't showing. - const onlyUseSyntacticOwners = !!triggerReason && triggerReason.kind === "characterTyped"; + // Decide whether to show signature help + const startingToken = findTokenOnLeftOfPosition(sourceFile, position); + if (!startingToken) { + // We are at the beginning of the file + return undefined; + } - // Bail out quickly in the middle of a string or comment, don't provide signature help unless the user explicitly requested it. - if (onlyUseSyntacticOwners && (isInString(sourceFile, position, startingToken) || isInComment(sourceFile, position))) { - return undefined; - } + // Only need to be careful if the user typed a character and signature help wasn't showing. + const onlyUseSyntacticOwners = !!triggerReason && triggerReason.kind === "characterTyped"; - const isManuallyInvoked = !!triggerReason && triggerReason.kind === "invoked"; - const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile, typeChecker, isManuallyInvoked); - if (!argumentInfo) return undefined; + // Bail out quickly in the middle of a string or comment, don't provide signature help unless the user explicitly requested it. + if (onlyUseSyntacticOwners && (isInString(sourceFile, position, startingToken) || isInComment(sourceFile, position))) { + return undefined; + } - cancellationToken.throwIfCancellationRequested(); + const isManuallyInvoked = !!triggerReason && triggerReason.kind === "invoked"; + const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile, typeChecker, isManuallyInvoked); + if (!argumentInfo) return undefined; - // Extra syntactic and semantic filtering of signature help - const candidateInfo = getCandidateOrTypeInfo(argumentInfo, typeChecker, sourceFile, startingToken, onlyUseSyntacticOwners); - cancellationToken.throwIfCancellationRequested(); + cancellationToken.throwIfCancellationRequested(); - if (!candidateInfo) { - // We didn't have any sig help items produced by the TS compiler. If this is a JS - // file, then see if we can figure out anything better. - return isSourceFileJS(sourceFile) ? createJSSignatureHelpItems(argumentInfo, program, cancellationToken) : undefined; - } + // Extra syntactic and semantic filtering of signature help + const candidateInfo = getCandidateOrTypeInfo(argumentInfo, typeChecker, sourceFile, startingToken, onlyUseSyntacticOwners); + cancellationToken.throwIfCancellationRequested(); - return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => - candidateInfo.kind === CandidateOrTypeKind.Candidate - ? createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker) - : createTypeHelpItems(candidateInfo.symbol, argumentInfo, sourceFile, typeChecker)); + if (!candidateInfo) { + // We didn't have any sig help items produced by the TS compiler. If this is a JS + // file, then see if we can figure out anything better. + return isSourceFileJS(sourceFile) ? createJSSignatureHelpItems(argumentInfo, program, cancellationToken) : undefined; } - const enum CandidateOrTypeKind { Candidate, Type } - interface CandidateInfo { - readonly kind: CandidateOrTypeKind.Candidate; - readonly candidates: readonly Signature[]; - readonly resolvedSignature: Signature; - } - interface TypeInfo { - readonly kind: CandidateOrTypeKind.Type; - readonly symbol: Symbol; - } + return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => + candidateInfo.kind === CandidateOrTypeKind.Candidate + ? createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker) + : createTypeHelpItems(candidateInfo.symbol, argumentInfo, sourceFile, typeChecker)); +} - function getCandidateOrTypeInfo({ invocation, argumentCount }: ArgumentListInfo, checker: TypeChecker, sourceFile: SourceFile, startingToken: Node, onlyUseSyntacticOwners: boolean): CandidateInfo | TypeInfo | undefined { - switch (invocation.kind) { - case InvocationKind.Call: { - if (onlyUseSyntacticOwners && !isSyntacticOwner(startingToken, invocation.node, sourceFile)) { - return undefined; - } - const candidates: Signature[] = []; - const resolvedSignature = checker.getResolvedSignatureForSignatureHelp(invocation.node, candidates, argumentCount)!; // TODO: GH#18217 - return candidates.length === 0 ? undefined : { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature }; - } - case InvocationKind.TypeArgs: { - const { called } = invocation; - if (onlyUseSyntacticOwners && !containsPrecedingToken(startingToken, sourceFile, isIdentifier(called) ? called.parent : called)) { - return undefined; - } - const candidates = getPossibleGenericSignatures(called, argumentCount, checker); - if (candidates.length !== 0) return { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature: first(candidates) }; +const enum CandidateOrTypeKind { Candidate, Type } +interface CandidateInfo { + readonly kind: CandidateOrTypeKind.Candidate; + readonly candidates: readonly Signature[]; + readonly resolvedSignature: Signature; +} +interface TypeInfo { + readonly kind: CandidateOrTypeKind.Type; + readonly symbol: Symbol; +} - const symbol = checker.getSymbolAtLocation(called); - return symbol && { kind: CandidateOrTypeKind.Type, symbol }; +function getCandidateOrTypeInfo({ invocation, argumentCount }: ArgumentListInfo, checker: TypeChecker, sourceFile: SourceFile, startingToken: Node, onlyUseSyntacticOwners: boolean): CandidateInfo | TypeInfo | undefined { + switch (invocation.kind) { + case InvocationKind.Call: { + if (onlyUseSyntacticOwners && !isSyntacticOwner(startingToken, invocation.node, sourceFile)) { + return undefined; } - case InvocationKind.Contextual: - return { kind: CandidateOrTypeKind.Candidate, candidates: [invocation.signature], resolvedSignature: invocation.signature }; - default: - return Debug.assertNever(invocation); + const candidates: Signature[] = []; + const resolvedSignature = checker.getResolvedSignatureForSignatureHelp(invocation.node, candidates, argumentCount)!; // TODO: GH#18217 + return candidates.length === 0 ? undefined : { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature }; } - } - - function isSyntacticOwner(startingToken: Node, node: CallLikeExpression, sourceFile: SourceFile): boolean { - if (!isCallOrNewExpression(node)) return false; - const invocationChildren = node.getChildren(sourceFile); - switch (startingToken.kind) { - case SyntaxKind.OpenParenToken: - return contains(invocationChildren, startingToken); - case SyntaxKind.CommaToken: { - const containingList = findContainingList(startingToken); - return !!containingList && contains(invocationChildren, containingList); + case InvocationKind.TypeArgs: { + const { called } = invocation; + if (onlyUseSyntacticOwners && !containsPrecedingToken(startingToken, sourceFile, isIdentifier(called) ? called.parent : called)) { + return undefined; } - case SyntaxKind.LessThanToken: - return containsPrecedingToken(startingToken, sourceFile, node.expression); - default: - return false; + const candidates = getPossibleGenericSignatures(called, argumentCount, checker); + if (candidates.length !== 0) return { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature: first(candidates) }; + + const symbol = checker.getSymbolAtLocation(called); + return symbol && { kind: CandidateOrTypeKind.Type, symbol }; } + case InvocationKind.Contextual: + return { kind: CandidateOrTypeKind.Candidate, candidates: [invocation.signature], resolvedSignature: invocation.signature }; + default: + return Debug.assertNever(invocation); } +} - function createJSSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined { - if (argumentInfo.invocation.kind === InvocationKind.Contextual) return undefined; - // See if we can find some symbol with the call expression name that has call signatures. - const expression = getExpressionFromInvocation(argumentInfo.invocation); - const name = isPropertyAccessExpression(expression) ? expression.name.text : undefined; - const typeChecker = program.getTypeChecker(); - return name === undefined ? undefined : firstDefined(program.getSourceFiles(), sourceFile => - firstDefined(sourceFile.getNamedDeclarations().get(name), declaration => { - const type = declaration.symbol && typeChecker.getTypeOfSymbolAtLocation(declaration.symbol, declaration); - const callSignatures = type && type.getCallSignatures(); - if (callSignatures && callSignatures.length) { - return typeChecker.runWithCancellationToken( - cancellationToken, - typeChecker => createSignatureHelpItems( - callSignatures, - callSignatures[0], - argumentInfo, - sourceFile, - typeChecker, - /*useFullPrefix*/ true)); - } - })); +function isSyntacticOwner(startingToken: Node, node: CallLikeExpression, sourceFile: SourceFile): boolean { + if (!isCallOrNewExpression(node)) return false; + const invocationChildren = node.getChildren(sourceFile); + switch (startingToken.kind) { + case SyntaxKind.OpenParenToken: + return contains(invocationChildren, startingToken); + case SyntaxKind.CommaToken: { + const containingList = findContainingList(startingToken); + return !!containingList && contains(invocationChildren, containingList); + } + case SyntaxKind.LessThanToken: + return containsPrecedingToken(startingToken, sourceFile, node.expression); + default: + return false; } +} - function containsPrecedingToken(startingToken: Node, sourceFile: SourceFile, container: Node) { - const pos = startingToken.getFullStart(); - // There’s a possibility that `startingToken.parent` contains only `startingToken` and - // missing nodes, none of which are valid to be returned by `findPrecedingToken`. In that - // case, the preceding token we want is actually higher up the tree—almost definitely the - // next parent, but theoretically the situation with missing nodes might be happening on - // multiple nested levels. - let currentParent: Node | undefined = startingToken.parent; - while (currentParent) { - const precedingToken = findPrecedingToken(pos, sourceFile, currentParent, /*excludeJsdoc*/ true); - if (precedingToken) { - return rangeContainsRange(container, precedingToken); +function createJSSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined { + if (argumentInfo.invocation.kind === InvocationKind.Contextual) return undefined; + // See if we can find some symbol with the call expression name that has call signatures. + const expression = getExpressionFromInvocation(argumentInfo.invocation); + const name = isPropertyAccessExpression(expression) ? expression.name.text : undefined; + const typeChecker = program.getTypeChecker(); + return name === undefined ? undefined : firstDefined(program.getSourceFiles(), sourceFile => + firstDefined(sourceFile.getNamedDeclarations().get(name), declaration => { + const type = declaration.symbol && typeChecker.getTypeOfSymbolAtLocation(declaration.symbol, declaration); + const callSignatures = type && type.getCallSignatures(); + if (callSignatures && callSignatures.length) { + return typeChecker.runWithCancellationToken( + cancellationToken, + typeChecker => createSignatureHelpItems( + callSignatures, + callSignatures[0], + argumentInfo, + sourceFile, + typeChecker, + /*useFullPrefix*/ true)); } - currentParent = currentParent.parent; + })); +} + +function containsPrecedingToken(startingToken: Node, sourceFile: SourceFile, container: Node) { + const pos = startingToken.getFullStart(); + // There’s a possibility that `startingToken.parent` contains only `startingToken` and + // missing nodes, none of which are valid to be returned by `findPrecedingToken`. In that + // case, the preceding token we want is actually higher up the tree—almost definitely the + // next parent, but theoretically the situation with missing nodes might be happening on + // multiple nested levels. + let currentParent: Node | undefined = startingToken.parent; + while (currentParent) { + const precedingToken = findPrecedingToken(pos, sourceFile, currentParent, /*excludeJsdoc*/ true); + if (precedingToken) { + return rangeContainsRange(container, precedingToken); } - return Debug.fail("Could not find preceding token"); + currentParent = currentParent.parent; } + return Debug.fail("Could not find preceding token"); +} + +export interface ArgumentInfoForCompletions { + readonly invocation: CallLikeExpression; + readonly argumentIndex: number; + readonly argumentCount: number; +} +export function getArgumentInfoForCompletions(node: Node, position: number, sourceFile: SourceFile): ArgumentInfoForCompletions | undefined { + const info = getImmediatelyContainingArgumentInfo(node, position, sourceFile); + return !info || info.isTypeParameterList || info.invocation.kind !== InvocationKind.Call ? undefined + : { invocation: info.invocation.node, argumentCount: info.argumentCount, argumentIndex: info.argumentIndex }; +} + +function getArgumentOrParameterListInfo(node: Node, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number, readonly argumentCount: number, readonly argumentsSpan: TextSpan } | undefined { + const info = getArgumentOrParameterListAndIndex(node, sourceFile); + if (!info) return undefined; + const { list, argumentIndex } = info; - export interface ArgumentInfoForCompletions { - readonly invocation: CallLikeExpression; - readonly argumentIndex: number; - readonly argumentCount: number; + const argumentCount = getArgumentCount(list); + if (argumentIndex !== 0) { + Debug.assertLessThan(argumentIndex, argumentCount); } - export function getArgumentInfoForCompletions(node: Node, position: number, sourceFile: SourceFile): ArgumentInfoForCompletions | undefined { - const info = getImmediatelyContainingArgumentInfo(node, position, sourceFile); - return !info || info.isTypeParameterList || info.invocation.kind !== InvocationKind.Call ? undefined - : { invocation: info.invocation.node, argumentCount: info.argumentCount, argumentIndex: info.argumentIndex }; + const argumentsSpan = getApplicableSpanForArguments(list, sourceFile); + return { list, argumentIndex, argumentCount, argumentsSpan }; +} +function getArgumentOrParameterListAndIndex(node: Node, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number } | undefined { + if (node.kind === SyntaxKind.LessThanToken || node.kind === SyntaxKind.OpenParenToken) { + // Find the list that starts right *after* the < or ( token. + // If the user has just opened a list, consider this item 0. + return { list: getChildListThatStartsWithOpenerToken(node.parent, node, sourceFile), argumentIndex: 0 }; + } + else { + // findListItemInfo can return undefined if we are not in parent's argument list + // or type argument list. This includes cases where the cursor is: + // - To the right of the closing parenthesis, non-substitution template, or template tail. + // - Between the type arguments and the arguments (greater than token) + // - On the target of the call (parent.func) + // - On the 'new' keyword in a 'new' expression + const list = findContainingList(node); + return list && { list, argumentIndex: getArgumentIndex(list, node) }; } +} - function getArgumentOrParameterListInfo(node: Node, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number, readonly argumentCount: number, readonly argumentsSpan: TextSpan } | undefined { - const info = getArgumentOrParameterListAndIndex(node, sourceFile); +/** + * Returns relevant information for the argument list and the current argument if we are + * in the argument of an invocation; returns undefined otherwise. + */ +function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined { + const { parent } = node; + if (isCallOrNewExpression(parent)) { + const invocation = parent; + + // There are 3 cases to handle: + // 1. The token introduces a list, and should begin a signature help session + // 2. The token is either not associated with a list, or ends a list, so the session should end + // 3. The token is buried inside a list, and should give signature help + // + // The following are examples of each: + // + // Case 1: + // foo<#T, U>(#a, b) -> The token introduces a list, and should begin a signature help session + // Case 2: + // fo#o#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end + // Case 3: + // foo(a#, #b#) -> The token is buried inside a list, and should give signature help + // Find out if 'node' is an argument, a type argument, or neither + const info = getArgumentOrParameterListInfo(node, sourceFile); if (!info) return undefined; - const { list, argumentIndex } = info; - - const argumentCount = getArgumentCount(list); - if (argumentIndex !== 0) { - Debug.assertLessThan(argumentIndex, argumentCount); - } - const argumentsSpan = getApplicableSpanForArguments(list, sourceFile); - return { list, argumentIndex, argumentCount, argumentsSpan }; - } - function getArgumentOrParameterListAndIndex(node: Node, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number } | undefined { - if (node.kind === SyntaxKind.LessThanToken || node.kind === SyntaxKind.OpenParenToken) { - // Find the list that starts right *after* the < or ( token. - // If the user has just opened a list, consider this item 0. - return { list: getChildListThatStartsWithOpenerToken(node.parent, node, sourceFile), argumentIndex: 0 }; - } - else { - // findListItemInfo can return undefined if we are not in parent's argument list - // or type argument list. This includes cases where the cursor is: - // - To the right of the closing parenthesis, non-substitution template, or template tail. - // - Between the type arguments and the arguments (greater than token) - // - On the target of the call (parent.func) - // - On the 'new' keyword in a 'new' expression - const list = findContainingList(node); - return list && { list, argumentIndex: getArgumentIndex(list, node) }; + const { list, argumentIndex, argumentCount, argumentsSpan } = info; + const isTypeParameterList = !!parent.typeArguments && parent.typeArguments.pos === list.pos; + return { isTypeParameterList, invocation: { kind: InvocationKind.Call, node: invocation }, argumentsSpan, argumentIndex, argumentCount }; + } + else if (isNoSubstitutionTemplateLiteral(node) && isTaggedTemplateExpression(parent)) { + // Check if we're actually inside the template; + // otherwise we'll fall out and return undefined. + if (isInsideTemplateLiteral(node, position, sourceFile)) { + return getArgumentListInfoForTemplate(parent, /*argumentIndex*/ 0, sourceFile); } + return undefined; } + else if (isTemplateHead(node) && parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { + const templateExpression = parent as TemplateExpression; + const tagExpression = templateExpression.parent as TaggedTemplateExpression; + Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); - /** - * Returns relevant information for the argument list and the current argument if we are - * in the argument of an invocation; returns undefined otherwise. - */ - function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined { - const { parent } = node; - if (isCallOrNewExpression(parent)) { - const invocation = parent; - - // There are 3 cases to handle: - // 1. The token introduces a list, and should begin a signature help session - // 2. The token is either not associated with a list, or ends a list, so the session should end - // 3. The token is buried inside a list, and should give signature help - // - // The following are examples of each: - // - // Case 1: - // foo<#T, U>(#a, b) -> The token introduces a list, and should begin a signature help session - // Case 2: - // fo#o#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end - // Case 3: - // foo(a#, #b#) -> The token is buried inside a list, and should give signature help - // Find out if 'node' is an argument, a type argument, or neither - const info = getArgumentOrParameterListInfo(node, sourceFile); - if (!info) return undefined; - const { list, argumentIndex, argumentCount, argumentsSpan } = info; - const isTypeParameterList = !!parent.typeArguments && parent.typeArguments.pos === list.pos; - return { isTypeParameterList, invocation: { kind: InvocationKind.Call, node: invocation }, argumentsSpan, argumentIndex, argumentCount }; - } - else if (isNoSubstitutionTemplateLiteral(node) && isTaggedTemplateExpression(parent)) { - // Check if we're actually inside the template; - // otherwise we'll fall out and return undefined. - if (isInsideTemplateLiteral(node, position, sourceFile)) { - return getArgumentListInfoForTemplate(parent, /*argumentIndex*/ 0, sourceFile); - } - return undefined; - } - else if (isTemplateHead(node) && parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { - const templateExpression = parent as TemplateExpression; - const tagExpression = templateExpression.parent as TaggedTemplateExpression; - Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); + const argumentIndex = isInsideTemplateLiteral(node, position, sourceFile) ? 0 : 1; - const argumentIndex = isInsideTemplateLiteral(node, position, sourceFile) ? 0 : 1; + return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); + } + else if (isTemplateSpan(parent) && isTaggedTemplateExpression(parent.parent.parent)) { + const templateSpan = parent; + const tagExpression = parent.parent.parent; - return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); + // If we're just after a template tail, don't show signature help. + if (isTemplateTail(node) && !isInsideTemplateLiteral(node, position, sourceFile)) { + return undefined; } - else if (isTemplateSpan(parent) && isTaggedTemplateExpression(parent.parent.parent)) { - const templateSpan = parent; - const tagExpression = parent.parent.parent; - // If we're just after a template tail, don't show signature help. - if (isTemplateTail(node) && !isInsideTemplateLiteral(node, position, sourceFile)) { - return undefined; - } - - const spanIndex = templateSpan.parent.templateSpans.indexOf(templateSpan); - const argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position, sourceFile); + const spanIndex = templateSpan.parent.templateSpans.indexOf(templateSpan); + const argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position, sourceFile); - return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); - } - else if (isJsxOpeningLikeElement(parent)) { - // Provide a signature help for JSX opening element or JSX self-closing element. - // This is not guarantee that JSX tag-name is resolved into stateless function component. (that is done in "getSignatureHelpItems") - // i.e - // export function MainButton(props: ButtonProps, context: any): JSX.Element { ... } - // isFunctionTypeNode(d) ? d.parent.symbol : undefined) || s - : s; - } +// The type of a function type node has a symbol at that node, but it's better to use the symbol for a parameter or type alias. +function chooseBetterSymbol(s: Symbol): Symbol { + return s.name === InternalSymbolName.Type + ? firstDefined(s.declarations, d => isFunctionTypeNode(d) ? d.parent.symbol : undefined) || s + : s; +} - function getArgumentIndex(argumentsList: Node, node: Node) { - // The list we got back can include commas. In the presence of errors it may - // also just have nodes without commas. For example "Foo(a b c)" will have 3 - // args without commas. We want to find what index we're at. So we count - // forward until we hit ourselves, only incrementing the index if it isn't a - // comma. - // - // Note: the subtlety around trailing commas (in getArgumentCount) does not apply - // here. That's because we're only walking forward until we hit the node we're - // on. In that case, even if we're after the trailing comma, we'll still see - // that trailing comma in the list, and we'll have generated the appropriate - // arg index. - let argumentIndex = 0; - for (const child of argumentsList.getChildren()) { - if (child === node) { - break; - } - if (child.kind !== SyntaxKind.CommaToken) { - argumentIndex++; - } +function getArgumentIndex(argumentsList: Node, node: Node) { + // The list we got back can include commas. In the presence of errors it may + // also just have nodes without commas. For example "Foo(a b c)" will have 3 + // args without commas. We want to find what index we're at. So we count + // forward until we hit ourselves, only incrementing the index if it isn't a + // comma. + // + // Note: the subtlety around trailing commas (in getArgumentCount) does not apply + // here. That's because we're only walking forward until we hit the node we're + // on. In that case, even if we're after the trailing comma, we'll still see + // that trailing comma in the list, and we'll have generated the appropriate + // arg index. + let argumentIndex = 0; + for (const child of argumentsList.getChildren()) { + if (child === node) { + break; + } + if (child.kind !== SyntaxKind.CommaToken) { + argumentIndex++; } - - return argumentIndex; } - function getArgumentCount(argumentsList: Node) { - // The argument count for a list is normally the number of non-comma children it has. - // For example, if you have "Foo(a,b)" then there will be three children of the arg - // list 'a' '' 'b'. So, in this case the arg count will be 2. However, there - // is a small subtlety. If you have "Foo(a,)", then the child list will just have - // 'a' ''. So, in the case where the last child is a comma, we increase the - // arg count by one to compensate. - // - // Note: this subtlety only applies to the last comma. If you had "Foo(a,," then - // we'll have: 'a' '' '' - // That will give us 2 non-commas. We then add one for the last comma, giving us an - // arg count of 3. - const listChildren = argumentsList.getChildren(); - - let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken); - if (listChildren.length > 0 && last(listChildren).kind === SyntaxKind.CommaToken) { - argumentCount++; - } + return argumentIndex; +} - return argumentCount; - } +function getArgumentCount(argumentsList: Node) { + // The argument count for a list is normally the number of non-comma children it has. + // For example, if you have "Foo(a,b)" then there will be three children of the arg + // list 'a' '' 'b'. So, in this case the arg count will be 2. However, there + // is a small subtlety. If you have "Foo(a,)", then the child list will just have + // 'a' ''. So, in the case where the last child is a comma, we increase the + // arg count by one to compensate. + // + // Note: this subtlety only applies to the last comma. If you had "Foo(a,," then + // we'll have: 'a' '' '' + // That will give us 2 non-commas. We then add one for the last comma, giving us an + // arg count of 3. + const listChildren = argumentsList.getChildren(); + + let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken); + if (listChildren.length > 0 && last(listChildren).kind === SyntaxKind.CommaToken) { + argumentCount++; + } + + return argumentCount; +} - // spanIndex is either the index for a given template span. - // This does not give appropriate results for a NoSubstitutionTemplateLiteral - function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node, position: number, sourceFile: SourceFile): number { - // Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1. - // There are three cases we can encounter: - // 1. We are precisely in the template literal (argIndex = 0). - // 2. We are in or to the right of the substitution expression (argIndex = spanIndex + 1). - // 3. We are directly to the right of the template literal, but because we look for the token on the left, - // not enough to put us in the substitution expression; we should consider ourselves part of - // the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1). - // - /* eslint-disable no-double-space */ - // Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # ` - // ^ ^ ^ ^ ^ ^ ^ ^ ^ - // Case: 1 1 3 2 1 3 2 2 1 - /* eslint-enable no-double-space */ - Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node."); - if (isTemplateLiteralToken(node)) { - if (isInsideTemplateLiteral(node, position, sourceFile)) { - return 0; - } - return spanIndex + 2; +// spanIndex is either the index for a given template span. +// This does not give appropriate results for a NoSubstitutionTemplateLiteral +function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node, position: number, sourceFile: SourceFile): number { + // Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1. + // There are three cases we can encounter: + // 1. We are precisely in the template literal (argIndex = 0). + // 2. We are in or to the right of the substitution expression (argIndex = spanIndex + 1). + // 3. We are directly to the right of the template literal, but because we look for the token on the left, + // not enough to put us in the substitution expression; we should consider ourselves part of + // the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1). + // + /* eslint-disable no-double-space */ + // Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # ` + // ^ ^ ^ ^ ^ ^ ^ ^ ^ + // Case: 1 1 3 2 1 3 2 2 1 + /* eslint-enable no-double-space */ + Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node."); + if (isTemplateLiteralToken(node)) { + if (isInsideTemplateLiteral(node, position, sourceFile)) { + return 0; } - return spanIndex + 1; - } - - function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number, sourceFile: SourceFile): ArgumentListInfo { - // argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument. - const argumentCount = isNoSubstitutionTemplateLiteral(tagExpression.template) ? 1 : tagExpression.template.templateSpans.length + 1; - if (argumentIndex !== 0) { - Debug.assertLessThan(argumentIndex, argumentCount); - } - return { - isTypeParameterList: false, - invocation: { kind: InvocationKind.Call, node: tagExpression }, - argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression, sourceFile), - argumentIndex, - argumentCount - }; + return spanIndex + 2; } + return spanIndex + 1; +} - function getApplicableSpanForArguments(argumentsList: Node, sourceFile: SourceFile): TextSpan { - // We use full start and skip trivia on the end because we want to include trivia on - // both sides. For example, - // - // foo( /*comment */ a, b, c /*comment*/ ) - // | | - // - // The applicable span is from the first bar to the second bar (inclusive, - // but not including parentheses) - const applicableSpanStart = argumentsList.getFullStart(); - const applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false); - return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); - } +function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number, sourceFile: SourceFile): ArgumentListInfo { + // argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument. + const argumentCount = isNoSubstitutionTemplateLiteral(tagExpression.template) ? 1 : tagExpression.template.templateSpans.length + 1; + if (argumentIndex !== 0) { + Debug.assertLessThan(argumentIndex, argumentCount); + } + return { + isTypeParameterList: false, + invocation: { kind: InvocationKind.Call, node: tagExpression }, + argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression, sourceFile), + argumentIndex, + argumentCount + }; +} - function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression, sourceFile: SourceFile): TextSpan { - const template = taggedTemplate.template; - const applicableSpanStart = template.getStart(); - let applicableSpanEnd = template.getEnd(); +function getApplicableSpanForArguments(argumentsList: Node, sourceFile: SourceFile): TextSpan { + // We use full start and skip trivia on the end because we want to include trivia on + // both sides. For example, + // + // foo( /*comment */ a, b, c /*comment*/ ) + // | | + // + // The applicable span is from the first bar to the second bar (inclusive, + // but not including parentheses) + const applicableSpanStart = argumentsList.getFullStart(); + const applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false); + return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); +} - // We need to adjust the end position for the case where the template does not have a tail. - // Otherwise, we will not show signature help past the expression. - // For example, - // - // ` ${ 1 + 1 foo(10) - // | | - // This is because a Missing node has no width. However, what we actually want is to include trivia - // leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail. - if (template.kind === SyntaxKind.TemplateExpression) { - const lastSpan = last(template.templateSpans); - if (lastSpan.literal.getFullWidth() === 0) { - applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false); - } +function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression, sourceFile: SourceFile): TextSpan { + const template = taggedTemplate.template; + const applicableSpanStart = template.getStart(); + let applicableSpanEnd = template.getEnd(); + + // We need to adjust the end position for the case where the template does not have a tail. + // Otherwise, we will not show signature help past the expression. + // For example, + // + // ` ${ 1 + 1 foo(10) + // | | + // This is because a Missing node has no width. However, what we actually want is to include trivia + // leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail. + if (template.kind === SyntaxKind.TemplateExpression) { + const lastSpan = last(template.templateSpans); + if (lastSpan.literal.getFullWidth() === 0) { + applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false); } - - return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); } - function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile, checker: TypeChecker, isManuallyInvoked: boolean): ArgumentListInfo | undefined { - for (let n = node; !isSourceFile(n) && (isManuallyInvoked || !isBlock(n)); n = n.parent) { - // If the node is not a subspan of its parent, this is a big problem. - // There have been crashes that might be caused by this violation. - Debug.assert(rangeContainsRange(n.parent, n), "Not a subspan", () => `Child: ${Debug.formatSyntaxKind(n.kind)}, parent: ${Debug.formatSyntaxKind(n.parent.kind)}`); - const argumentInfo = getImmediatelyContainingArgumentOrContextualParameterInfo(n, position, sourceFile, checker); - if (argumentInfo) { - return argumentInfo; - } + return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); +} + +function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile, checker: TypeChecker, isManuallyInvoked: boolean): ArgumentListInfo | undefined { + for (let n = node; !isSourceFile(n) && (isManuallyInvoked || !isBlock(n)); n = n.parent) { + // If the node is not a subspan of its parent, this is a big problem. + // There have been crashes that might be caused by this violation. + Debug.assert(rangeContainsRange(n.parent, n), "Not a subspan", () => `Child: ${Debug.formatSyntaxKind(n.kind)}, parent: ${Debug.formatSyntaxKind(n.parent.kind)}`); + const argumentInfo = getImmediatelyContainingArgumentOrContextualParameterInfo(n, position, sourceFile, checker); + if (argumentInfo) { + return argumentInfo; } - return undefined; } + return undefined; +} - function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node { - const children = parent.getChildren(sourceFile); - const indexOfOpenerToken = children.indexOf(openerToken); - Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1); - return children[indexOfOpenerToken + 1]; - } +function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node { + const children = parent.getChildren(sourceFile); + const indexOfOpenerToken = children.indexOf(openerToken); + Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1); + return children[indexOfOpenerToken + 1]; +} - function getExpressionFromInvocation(invocation: CallInvocation | TypeArgsInvocation): Expression { - return invocation.kind === InvocationKind.Call ? getInvokedExpression(invocation.node) : invocation.called; - } +function getExpressionFromInvocation(invocation: CallInvocation | TypeArgsInvocation): Expression { + return invocation.kind === InvocationKind.Call ? getInvokedExpression(invocation.node) : invocation.called; +} - function getEnclosingDeclarationFromInvocation(invocation: Invocation): Node { - return invocation.kind === InvocationKind.Call ? invocation.node : invocation.kind === InvocationKind.TypeArgs ? invocation.called : invocation.node; - } +function getEnclosingDeclarationFromInvocation(invocation: Invocation): Node { + return invocation.kind === InvocationKind.Call ? invocation.node : invocation.kind === InvocationKind.TypeArgs ? invocation.called : invocation.node; +} - const signatureHelpNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; - function createSignatureHelpItems( - candidates: readonly Signature[], - resolvedSignature: Signature, - { isTypeParameterList, argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo, - sourceFile: SourceFile, - typeChecker: TypeChecker, - useFullPrefix?: boolean, - ): SignatureHelpItems { - const enclosingDeclaration = getEnclosingDeclarationFromInvocation(invocation); - const callTargetSymbol = invocation.kind === InvocationKind.Contextual ? invocation.symbol : (typeChecker.getSymbolAtLocation(getExpressionFromInvocation(invocation)) || useFullPrefix && resolvedSignature.declaration?.symbol); - const callTargetDisplayParts = callTargetSymbol ? symbolToDisplayParts(typeChecker, callTargetSymbol, useFullPrefix ? sourceFile : undefined, /*meaning*/ undefined) : emptyArray; - const items = map(candidates, candidateSignature => getSignatureHelpItem(candidateSignature, callTargetDisplayParts, isTypeParameterList, typeChecker, enclosingDeclaration, sourceFile)); - - if (argumentIndex !== 0) { - Debug.assertLessThan(argumentIndex, argumentCount); - } - let selectedItemIndex = 0; - let itemsSeen = 0; - for (let i = 0; i < items.length; i++) { - const item = items[i]; - if (candidates[i] === resolvedSignature) { - selectedItemIndex = itemsSeen; - if (item.length > 1) { - // check to see if any items in the list better match than the first one, as the checker isn't filtering the nested lists - // (those come from tuple parameter expansion) - let count = 0; - for (const i of item) { - if (i.isVariadic || i.parameters.length >= argumentCount) { - selectedItemIndex = itemsSeen + count; - break; - } - count++; +const signatureHelpNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; +function createSignatureHelpItems( + candidates: readonly Signature[], + resolvedSignature: Signature, + { isTypeParameterList, argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo, + sourceFile: SourceFile, + typeChecker: TypeChecker, + useFullPrefix?: boolean, +): SignatureHelpItems { + const enclosingDeclaration = getEnclosingDeclarationFromInvocation(invocation); + const callTargetSymbol = invocation.kind === InvocationKind.Contextual ? invocation.symbol : (typeChecker.getSymbolAtLocation(getExpressionFromInvocation(invocation)) || useFullPrefix && resolvedSignature.declaration?.symbol); + const callTargetDisplayParts = callTargetSymbol ? symbolToDisplayParts(typeChecker, callTargetSymbol, useFullPrefix ? sourceFile : undefined, /*meaning*/ undefined) : emptyArray; + const items = map(candidates, candidateSignature => getSignatureHelpItem(candidateSignature, callTargetDisplayParts, isTypeParameterList, typeChecker, enclosingDeclaration, sourceFile)); + + if (argumentIndex !== 0) { + Debug.assertLessThan(argumentIndex, argumentCount); + } + let selectedItemIndex = 0; + let itemsSeen = 0; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (candidates[i] === resolvedSignature) { + selectedItemIndex = itemsSeen; + if (item.length > 1) { + // check to see if any items in the list better match than the first one, as the checker isn't filtering the nested lists + // (those come from tuple parameter expansion) + let count = 0; + for (const i of item) { + if (i.isVariadic || i.parameters.length >= argumentCount) { + selectedItemIndex = itemsSeen + count; + break; } + count++; } } - itemsSeen += item.length; - } - - Debug.assert(selectedItemIndex !== -1); // If candidates is non-empty it should always include bestSignature. We check for an empty candidates before calling this function. - const help = { items: flatMapToMutable(items, identity), applicableSpan, selectedItemIndex, argumentIndex, argumentCount }; - const selected = help.items[selectedItemIndex]; - if (selected.isVariadic) { - const firstRest = findIndex(selected.parameters, p => !!p.isRest); - if (-1 < firstRest && firstRest < selected.parameters.length - 1) { - // We don't have any code to get this correct; instead, don't highlight a current parameter AT ALL - help.argumentIndex = selected.parameters.length; - } - else { - help.argumentIndex = Math.min(help.argumentIndex, selected.parameters.length - 1); - } } - return help; + itemsSeen += item.length; } - function createTypeHelpItems( - symbol: Symbol, - { argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo, - sourceFile: SourceFile, - checker: TypeChecker - ): SignatureHelpItems | undefined { - const typeParameters = checker.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - if (!typeParameters) return undefined; - const items = [getTypeHelpItem(symbol, typeParameters, checker, getEnclosingDeclarationFromInvocation(invocation), sourceFile)]; - return { items, applicableSpan, selectedItemIndex: 0, argumentIndex, argumentCount }; + Debug.assert(selectedItemIndex !== -1); // If candidates is non-empty it should always include bestSignature. We check for an empty candidates before calling this function. + const help = { items: flatMapToMutable(items, identity), applicableSpan, selectedItemIndex, argumentIndex, argumentCount }; + const selected = help.items[selectedItemIndex]; + if (selected.isVariadic) { + const firstRest = findIndex(selected.parameters, p => !!p.isRest); + if (-1 < firstRest && firstRest < selected.parameters.length - 1) { + // We don't have any code to get this correct; instead, don't highlight a current parameter AT ALL + help.argumentIndex = selected.parameters.length; + } + else { + help.argumentIndex = Math.min(help.argumentIndex, selected.parameters.length - 1); + } } + return help; +} - function getTypeHelpItem(symbol: Symbol, typeParameters: readonly TypeParameter[], checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem { - const typeSymbolDisplay = symbolToDisplayParts(checker, symbol); +function createTypeHelpItems( + symbol: Symbol, + { argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo, + sourceFile: SourceFile, + checker: TypeChecker +): SignatureHelpItems | undefined { + const typeParameters = checker.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + if (!typeParameters) return undefined; + const items = [getTypeHelpItem(symbol, typeParameters, checker, getEnclosingDeclarationFromInvocation(invocation), sourceFile)]; + return { items, applicableSpan, selectedItemIndex: 0, argumentIndex, argumentCount }; +} - const printer = createPrinter({ removeComments: true }); - const parameters = typeParameters.map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer)); +function getTypeHelpItem(symbol: Symbol, typeParameters: readonly TypeParameter[], checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem { + const typeSymbolDisplay = symbolToDisplayParts(checker, symbol); - const documentation = symbol.getDocumentationComment(checker); - const tags = symbol.getJsDocTags(checker); - const prefixDisplayParts = [...typeSymbolDisplay, punctuationPart(SyntaxKind.LessThanToken)]; - return { isVariadic: false, prefixDisplayParts, suffixDisplayParts: [punctuationPart(SyntaxKind.GreaterThanToken)], separatorDisplayParts, parameters, documentation, tags }; - } + const printer = createPrinter({ removeComments: true }); + const parameters = typeParameters.map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer)); - const separatorDisplayParts: SymbolDisplayPart[] = [punctuationPart(SyntaxKind.CommaToken), spacePart()]; + const documentation = symbol.getDocumentationComment(checker); + const tags = symbol.getJsDocTags(checker); + const prefixDisplayParts = [...typeSymbolDisplay, punctuationPart(SyntaxKind.LessThanToken)]; + return { isVariadic: false, prefixDisplayParts, suffixDisplayParts: [punctuationPart(SyntaxKind.GreaterThanToken)], separatorDisplayParts, parameters, documentation, tags }; +} - function getSignatureHelpItem(candidateSignature: Signature, callTargetDisplayParts: readonly SymbolDisplayPart[], isTypeParameterList: boolean, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem[] { - const infos = (isTypeParameterList ? itemInfoForTypeParameters : itemInfoForParameters)(candidateSignature, checker, enclosingDeclaration, sourceFile); - return map(infos, ({ isVariadic, parameters, prefix, suffix }) => { - const prefixDisplayParts = [...callTargetDisplayParts, ...prefix]; - const suffixDisplayParts = [...suffix, ...returnTypeToDisplayParts(candidateSignature, enclosingDeclaration, checker)]; - const documentation = candidateSignature.getDocumentationComment(checker); - const tags = candidateSignature.getJsDocTags(); - return { isVariadic, prefixDisplayParts, suffixDisplayParts, separatorDisplayParts, parameters, documentation, tags }; - }); - } +const separatorDisplayParts: SymbolDisplayPart[] = [punctuationPart(SyntaxKind.CommaToken), spacePart()]; + +function getSignatureHelpItem(candidateSignature: Signature, callTargetDisplayParts: readonly SymbolDisplayPart[], isTypeParameterList: boolean, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem[] { + const infos = (isTypeParameterList ? itemInfoForTypeParameters : itemInfoForParameters)(candidateSignature, checker, enclosingDeclaration, sourceFile); + return map(infos, ({ isVariadic, parameters, prefix, suffix }) => { + const prefixDisplayParts = [...callTargetDisplayParts, ...prefix]; + const suffixDisplayParts = [...suffix, ...returnTypeToDisplayParts(candidateSignature, enclosingDeclaration, checker)]; + const documentation = candidateSignature.getDocumentationComment(checker); + const tags = candidateSignature.getJsDocTags(); + return { isVariadic, prefixDisplayParts, suffixDisplayParts, separatorDisplayParts, parameters, documentation, tags }; + }); +} - function returnTypeToDisplayParts(candidateSignature: Signature, enclosingDeclaration: Node, checker: TypeChecker): readonly SymbolDisplayPart[] { - return mapToDisplayParts(writer => { - writer.writePunctuation(":"); - writer.writeSpace(" "); - const predicate = checker.getTypePredicateOfSignature(candidateSignature); - if (predicate) { - checker.writeTypePredicate(predicate, enclosingDeclaration, /*flags*/ undefined, writer); - } - else { - checker.writeType(checker.getReturnTypeOfSignature(candidateSignature), enclosingDeclaration, /*flags*/ undefined, writer); - } - }); - } +function returnTypeToDisplayParts(candidateSignature: Signature, enclosingDeclaration: Node, checker: TypeChecker): readonly SymbolDisplayPart[] { + return mapToDisplayParts(writer => { + writer.writePunctuation(":"); + writer.writeSpace(" "); + const predicate = checker.getTypePredicateOfSignature(candidateSignature); + if (predicate) { + checker.writeTypePredicate(predicate, enclosingDeclaration, /*flags*/ undefined, writer); + } + else { + checker.writeType(checker.getReturnTypeOfSignature(candidateSignature), enclosingDeclaration, /*flags*/ undefined, writer); + } + }); +} - interface SignatureHelpItemInfo { readonly isVariadic: boolean; readonly parameters: SignatureHelpParameter[]; readonly prefix: readonly SymbolDisplayPart[]; readonly suffix: readonly SymbolDisplayPart[]; } +interface SignatureHelpItemInfo { readonly isVariadic: boolean; readonly parameters: SignatureHelpParameter[]; readonly prefix: readonly SymbolDisplayPart[]; readonly suffix: readonly SymbolDisplayPart[]; } - function itemInfoForTypeParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo[] { - const typeParameters = (candidateSignature.target || candidateSignature).typeParameters; - const printer = createPrinter({ removeComments: true }); - const parameters = (typeParameters || emptyArray).map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer)); - const thisParameter = candidateSignature.thisParameter ? [checker.symbolToParameterDeclaration(candidateSignature.thisParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!] : []; +function itemInfoForTypeParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo[] { + const typeParameters = (candidateSignature.target || candidateSignature).typeParameters; + const printer = createPrinter({ removeComments: true }); + const parameters = (typeParameters || emptyArray).map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer)); + const thisParameter = candidateSignature.thisParameter ? [checker.symbolToParameterDeclaration(candidateSignature.thisParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!] : []; - return checker.getExpandedParameters(candidateSignature).map(paramList => { - const params = factory.createNodeArray([...thisParameter, ...map(paramList, param => checker.symbolToParameterDeclaration(param, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)]); - const parameterParts = mapToDisplayParts(writer => { - printer.writeList(ListFormat.CallExpressionArguments, params, sourceFile, writer); - }); - return { isVariadic: false, parameters, prefix: [punctuationPart(SyntaxKind.LessThanToken)], suffix: [punctuationPart(SyntaxKind.GreaterThanToken), ...parameterParts] }; + return checker.getExpandedParameters(candidateSignature).map(paramList => { + const params = factory.createNodeArray([...thisParameter, ...map(paramList, param => checker.symbolToParameterDeclaration(param, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)]); + const parameterParts = mapToDisplayParts(writer => { + printer.writeList(ListFormat.CallExpressionArguments, params, sourceFile, writer); }); - } + return { isVariadic: false, parameters, prefix: [punctuationPart(SyntaxKind.LessThanToken)], suffix: [punctuationPart(SyntaxKind.GreaterThanToken), ...parameterParts] }; + }); +} - function itemInfoForParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo[] { - const printer = createPrinter({ removeComments: true }); - const typeParameterParts = mapToDisplayParts(writer => { - if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) { - const args = factory.createNodeArray(candidateSignature.typeParameters.map(p => checker.typeParameterToDeclaration(p, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)); - printer.writeList(ListFormat.TypeParameters, args, sourceFile, writer); - } - }); - const lists = checker.getExpandedParameters(candidateSignature); - const isVariadic: (parameterList: readonly Symbol[]) => boolean = - !checker.hasEffectiveRestParameter(candidateSignature) ? _ => false - : lists.length === 1 ? _ => true - : pList => !!(pList.length && (pList[pList.length - 1] as TransientSymbol).checkFlags & CheckFlags.RestParameter); - return lists.map(parameterList => ({ - isVariadic: isVariadic(parameterList), - parameters: parameterList.map(p => createSignatureHelpParameterForParameter(p, checker, enclosingDeclaration, sourceFile, printer)), - prefix: [...typeParameterParts, punctuationPart(SyntaxKind.OpenParenToken)], - suffix: [punctuationPart(SyntaxKind.CloseParenToken)] - })); - } +function itemInfoForParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo[] { + const printer = createPrinter({ removeComments: true }); + const typeParameterParts = mapToDisplayParts(writer => { + if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) { + const args = factory.createNodeArray(candidateSignature.typeParameters.map(p => checker.typeParameterToDeclaration(p, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)); + printer.writeList(ListFormat.TypeParameters, args, sourceFile, writer); + } + }); + const lists = checker.getExpandedParameters(candidateSignature); + const isVariadic: (parameterList: readonly Symbol[]) => boolean = + !checker.hasEffectiveRestParameter(candidateSignature) ? _ => false + : lists.length === 1 ? _ => true + : pList => !!(pList.length && (pList[pList.length - 1] as TransientSymbol).checkFlags & CheckFlags.RestParameter); + return lists.map(parameterList => ({ + isVariadic: isVariadic(parameterList), + parameters: parameterList.map(p => createSignatureHelpParameterForParameter(p, checker, enclosingDeclaration, sourceFile, printer)), + prefix: [...typeParameterParts, punctuationPart(SyntaxKind.OpenParenToken)], + suffix: [punctuationPart(SyntaxKind.CloseParenToken)] + })); +} - function createSignatureHelpParameterForParameter(parameter: Symbol, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter { - const displayParts = mapToDisplayParts(writer => { - const param = checker.symbolToParameterDeclaration(parameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!; - printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer); - }); - const isOptional = checker.isOptionalParameter(parameter.valueDeclaration as ParameterDeclaration); - const isRest = !!((parameter as TransientSymbol).checkFlags & CheckFlags.RestParameter); - return { name: parameter.name, documentation: parameter.getDocumentationComment(checker), displayParts, isOptional, isRest }; - } +function createSignatureHelpParameterForParameter(parameter: Symbol, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter { + const displayParts = mapToDisplayParts(writer => { + const param = checker.symbolToParameterDeclaration(parameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!; + printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer); + }); + const isOptional = checker.isOptionalParameter(parameter.valueDeclaration as ParameterDeclaration); + const isRest = !!((parameter as TransientSymbol).checkFlags & CheckFlags.RestParameter); + return { name: parameter.name, documentation: parameter.getDocumentationComment(checker), displayParts, isOptional, isRest }; +} - function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter { - const displayParts = mapToDisplayParts(writer => { - const param = checker.typeParameterToDeclaration(typeParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!; - printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer); - }); - return { name: typeParameter.symbol.name, documentation: typeParameter.symbol.getDocumentationComment(checker), displayParts, isOptional: false, isRest: false }; - } +function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter { + const displayParts = mapToDisplayParts(writer => { + const param = checker.typeParameterToDeclaration(typeParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!; + printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer); + }); + return { name: typeParameter.symbol.name, documentation: typeParameter.symbol.getDocumentationComment(checker), displayParts, isOptional: false, isRest: false }; +} } diff --git a/src/services/smartSelection.ts b/src/services/smartSelection.ts index 6cd5c8f94d854..47593aade42b8 100644 --- a/src/services/smartSelection.ts +++ b/src/services/smartSelection.ts @@ -1,301 +1,301 @@ /* @internal */ namespace ts.SmartSelectionRange { - export function getSmartSelectionRange(pos: number, sourceFile: SourceFile): SelectionRange { - let selectionRange: SelectionRange = { - textSpan: createTextSpanFromBounds(sourceFile.getFullStart(), sourceFile.getEnd()) - }; +export function getSmartSelectionRange(pos: number, sourceFile: SourceFile): SelectionRange { + let selectionRange: SelectionRange = { + textSpan: createTextSpanFromBounds(sourceFile.getFullStart(), sourceFile.getEnd()) + }; - let parentNode: Node = sourceFile; - outer: while (true) { - const children = getSelectionChildren(parentNode); - if (!children.length) break; - for (let i = 0; i < children.length; i++) { - const prevNode: Node | undefined = children[i - 1]; - const node: Node = children[i]; - const nextNode: Node | undefined = children[i + 1]; + let parentNode: Node = sourceFile; + outer: while (true) { + const children = getSelectionChildren(parentNode); + if (!children.length) break; + for (let i = 0; i < children.length; i++) { + const prevNode: Node | undefined = children[i - 1]; + const node: Node = children[i]; + const nextNode: Node | undefined = children[i + 1]; - if (getTokenPosOfNode(node, sourceFile, /*includeJsDoc*/ true) > pos) { - break outer; - } - - const comment = singleOrUndefined(getTrailingCommentRanges(sourceFile.text, node.end)); - if (comment && comment.kind === SyntaxKind.SingleLineCommentTrivia) { - pushSelectionCommentRange(comment.pos, comment.end); - } - - if (positionShouldSnapToNode(sourceFile, pos, node)) { - // 1. Blocks are effectively redundant with SyntaxLists. - // 2. TemplateSpans, along with the SyntaxLists containing them, are a somewhat unintuitive grouping - // of things that should be considered independently. - // 3. A VariableStatement’s children are just a VaraiableDeclarationList and a semicolon. - // 4. A lone VariableDeclaration in a VaraibleDeclaration feels redundant with the VariableStatement. - // Dive in without pushing a selection range. - if (isBlock(node) - || isTemplateSpan(node) || isTemplateHead(node) || isTemplateTail(node) - || prevNode && isTemplateHead(prevNode) - || isVariableDeclarationList(node) && isVariableStatement(parentNode) - || isSyntaxList(node) && isVariableDeclarationList(parentNode) - || isVariableDeclaration(node) && isSyntaxList(parentNode) && children.length === 1 - || isJSDocTypeExpression(node) || isJSDocSignature(node) || isJSDocTypeLiteral(node)) { - parentNode = node; - break; - } - - // Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings. - if (isTemplateSpan(parentNode) && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) { - const start = node.getFullStart() - "${".length; - const end = nextNode.getStart() + "}".length; - pushSelectionRange(start, end); - } - - // Blocks with braces, brackets, parens, or JSX tags on separate lines should be - // selected from open to close, including whitespace but not including the braces/etc. themselves. - const isBetweenMultiLineBookends = isSyntaxList(node) && isListOpener(prevNode) && isListCloser(nextNode) - && !positionsAreOnSameLine(prevNode.getStart(), nextNode.getStart(), sourceFile); - const start = isBetweenMultiLineBookends ? prevNode.getEnd() : node.getStart(); - const end = isBetweenMultiLineBookends ? nextNode.getStart() : getEndPos(sourceFile, node); - - if (hasJSDocNodes(node) && node.jsDoc?.length) { - pushSelectionRange(first(node.jsDoc).getStart(), end); - } - - pushSelectionRange(start, end); + if (getTokenPosOfNode(node, sourceFile, /*includeJsDoc*/ true) > pos) { + break outer; + } - // String literals should have a stop both inside and outside their quotes. - if (isStringLiteral(node) || isTemplateLiteral(node)) { - pushSelectionRange(start + 1, end - 1); - } + const comment = singleOrUndefined(getTrailingCommentRanges(sourceFile.text, node.end)); + if (comment && comment.kind === SyntaxKind.SingleLineCommentTrivia) { + pushSelectionCommentRange(comment.pos, comment.end); + } + if (positionShouldSnapToNode(sourceFile, pos, node)) { + // 1. Blocks are effectively redundant with SyntaxLists. + // 2. TemplateSpans, along with the SyntaxLists containing them, are a somewhat unintuitive grouping + // of things that should be considered independently. + // 3. A VariableStatement’s children are just a VaraiableDeclarationList and a semicolon. + // 4. A lone VariableDeclaration in a VaraibleDeclaration feels redundant with the VariableStatement. + // Dive in without pushing a selection range. + if (isBlock(node) + || isTemplateSpan(node) || isTemplateHead(node) || isTemplateTail(node) + || prevNode && isTemplateHead(prevNode) + || isVariableDeclarationList(node) && isVariableStatement(parentNode) + || isSyntaxList(node) && isVariableDeclarationList(parentNode) + || isVariableDeclaration(node) && isSyntaxList(parentNode) && children.length === 1 + || isJSDocTypeExpression(node) || isJSDocSignature(node) || isJSDocTypeLiteral(node)) { parentNode = node; break; } - // If we made it to the end of the for loop, we’re done. - // In practice, I’ve only seen this happen at the very end - // of a SourceFile. - if (i === children.length - 1) { - break outer; + // Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings. + if (isTemplateSpan(parentNode) && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) { + const start = node.getFullStart() - "${".length; + const end = nextNode.getStart() + "}".length; + pushSelectionRange(start, end); } - } - } - return selectionRange; + // Blocks with braces, brackets, parens, or JSX tags on separate lines should be + // selected from open to close, including whitespace but not including the braces/etc. themselves. + const isBetweenMultiLineBookends = isSyntaxList(node) && isListOpener(prevNode) && isListCloser(nextNode) + && !positionsAreOnSameLine(prevNode.getStart(), nextNode.getStart(), sourceFile); + const start = isBetweenMultiLineBookends ? prevNode.getEnd() : node.getStart(); + const end = isBetweenMultiLineBookends ? nextNode.getStart() : getEndPos(sourceFile, node); - function pushSelectionRange(start: number, end: number): void { - // Skip empty ranges - if (start !== end) { - const textSpan = createTextSpanFromBounds(start, end); - if (!selectionRange || ( - // Skip ranges that are identical to the parent - !textSpansEqual(textSpan, selectionRange.textSpan) && - // Skip ranges that don’t contain the original position - textSpanIntersectsWithPosition(textSpan, pos) - )) { - selectionRange = { textSpan, ...selectionRange && { parent: selectionRange } }; + if (hasJSDocNodes(node) && node.jsDoc?.length) { + pushSelectionRange(first(node.jsDoc).getStart(), end); } - } - } - function pushSelectionCommentRange(start: number, end: number): void { - pushSelectionRange(start, end); + pushSelectionRange(start, end); + + // String literals should have a stop both inside and outside their quotes. + if (isStringLiteral(node) || isTemplateLiteral(node)) { + pushSelectionRange(start + 1, end - 1); + } - let pos = start; - while (sourceFile.text.charCodeAt(pos) === CharacterCodes.slash) { - pos++; + parentNode = node; + break; + } + + // If we made it to the end of the for loop, we’re done. + // In practice, I’ve only seen this happen at the very end + // of a SourceFile. + if (i === children.length - 1) { + break outer; } - pushSelectionRange(pos, end); } } - /** - * Like `ts.positionBelongsToNode`, except positions immediately after nodes - * count too, unless that position belongs to the next node. In effect, makes - * selections able to snap to preceding tokens when the cursor is on the tail - * end of them with only whitespace ahead. - * @param sourceFile The source file containing the nodes. - * @param pos The position to check. - * @param node The candidate node to snap to. - */ - function positionShouldSnapToNode(sourceFile: SourceFile, pos: number, node: Node) { - // Can’t use 'ts.positionBelongsToNode()' here because it cleverly accounts - // for missing nodes, which can’t really be considered when deciding what - // to select. - Debug.assert(node.pos <= pos); - if (pos < node.end) { - return true; - } - const nodeEnd = node.getEnd(); - if (nodeEnd === pos) { - return getTouchingPropertyName(sourceFile, pos).pos < node.end; + return selectionRange; + + function pushSelectionRange(start: number, end: number): void { + // Skip empty ranges + if (start !== end) { + const textSpan = createTextSpanFromBounds(start, end); + if (!selectionRange || ( + // Skip ranges that are identical to the parent + !textSpansEqual(textSpan, selectionRange.textSpan) && + // Skip ranges that don’t contain the original position + textSpanIntersectsWithPosition(textSpan, pos) + )) { + selectionRange = { textSpan, ...selectionRange && { parent: selectionRange } }; + } } - return false; } - const isImport = or(isImportDeclaration, isImportEqualsDeclaration); + function pushSelectionCommentRange(start: number, end: number): void { + pushSelectionRange(start, end); - /** - * Gets the children of a node to be considered for selection ranging, - * transforming them into an artificial tree according to their intuitive - * grouping where no grouping actually exists in the parse tree. For example, - * top-level imports are grouped into their own SyntaxList so they can be - * selected all together, even though in the AST they’re just siblings of each - * other as well as of other top-level statements and declarations. - */ - function getSelectionChildren(node: Node): readonly Node[] { - // Group top-level imports - if (isSourceFile(node)) { - return groupChildren(node.getChildAt(0).getChildren(), isImport); + let pos = start; + while (sourceFile.text.charCodeAt(pos) === CharacterCodes.slash) { + pos++; } + pushSelectionRange(pos, end); + } +} - // Mapped types _look_ like ObjectTypes with a single member, - // but in fact don’t contain a SyntaxList or a node containing - // the “key/value” pair like ObjectTypes do, but it seems intuitive - // that the selection would snap to those points. The philosophy - // of choosing a selection range is not so much about what the - // syntax currently _is_ as what the syntax might easily become - // if the user is making a selection; e.g., we synthesize a selection - // around the “key/value” pair not because there’s a node there, but - // because it allows the mapped type to become an object type with a - // few keystrokes. - if (isMappedTypeNode(node)) { - const [openBraceToken, ...children] = node.getChildren(); - const closeBraceToken = Debug.checkDefined(children.pop()); - Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken); - Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken); - // Group `-/+readonly` and `-/+?` - const groupedWithPlusMinusTokens = groupChildren(children, child => - child === node.readonlyToken || child.kind === SyntaxKind.ReadonlyKeyword || - child === node.questionToken || child.kind === SyntaxKind.QuestionToken); - // Group type parameter with surrounding brackets - const groupedWithBrackets = groupChildren(groupedWithPlusMinusTokens, ({ kind }) => - kind === SyntaxKind.OpenBracketToken || - kind === SyntaxKind.TypeParameter || - kind === SyntaxKind.CloseBracketToken - ); - return [ - openBraceToken, - // Pivot on `:` - createSyntaxList(splitChildren(groupedWithBrackets, ({ kind }) => kind === SyntaxKind.ColonToken)), - closeBraceToken, - ]; - } +/** + * Like `ts.positionBelongsToNode`, except positions immediately after nodes + * count too, unless that position belongs to the next node. In effect, makes + * selections able to snap to preceding tokens when the cursor is on the tail + * end of them with only whitespace ahead. + * @param sourceFile The source file containing the nodes. + * @param pos The position to check. + * @param node The candidate node to snap to. + */ +function positionShouldSnapToNode(sourceFile: SourceFile, pos: number, node: Node) { + // Can’t use 'ts.positionBelongsToNode()' here because it cleverly accounts + // for missing nodes, which can’t really be considered when deciding what + // to select. + Debug.assert(node.pos <= pos); + if (pos < node.end) { + return true; + } + const nodeEnd = node.getEnd(); + if (nodeEnd === pos) { + return getTouchingPropertyName(sourceFile, pos).pos < node.end; + } + return false; +} - // Group modifiers and property name, then pivot on `:`. - if (isPropertySignature(node)) { - const children = groupChildren(node.getChildren(), child => - child === node.name || contains(node.modifiers, child)); - return splitChildren(children, ({ kind }) => kind === SyntaxKind.ColonToken); - } +const isImport = or(isImportDeclaration, isImportEqualsDeclaration); - // Group the parameter name with its `...`, then that group with its `?`, then pivot on `=`. - if (isParameter(node)) { - const groupedDotDotDotAndName = groupChildren(node.getChildren(), child => - child === node.dotDotDotToken || child === node.name); - const groupedWithQuestionToken = groupChildren(groupedDotDotDotAndName, child => - child === groupedDotDotDotAndName[0] || child === node.questionToken); - return splitChildren(groupedWithQuestionToken, ({ kind }) => kind === SyntaxKind.EqualsToken); - } +/** + * Gets the children of a node to be considered for selection ranging, + * transforming them into an artificial tree according to their intuitive + * grouping where no grouping actually exists in the parse tree. For example, + * top-level imports are grouped into their own SyntaxList so they can be + * selected all together, even though in the AST they’re just siblings of each + * other as well as of other top-level statements and declarations. + */ +function getSelectionChildren(node: Node): readonly Node[] { + // Group top-level imports + if (isSourceFile(node)) { + return groupChildren(node.getChildAt(0).getChildren(), isImport); + } - // Pivot on '=' - if (isBindingElement(node)) { - return splitChildren(node.getChildren(), ({ kind }) => kind === SyntaxKind.EqualsToken); - } + // Mapped types _look_ like ObjectTypes with a single member, + // but in fact don’t contain a SyntaxList or a node containing + // the “key/value” pair like ObjectTypes do, but it seems intuitive + // that the selection would snap to those points. The philosophy + // of choosing a selection range is not so much about what the + // syntax currently _is_ as what the syntax might easily become + // if the user is making a selection; e.g., we synthesize a selection + // around the “key/value” pair not because there’s a node there, but + // because it allows the mapped type to become an object type with a + // few keystrokes. + if (isMappedTypeNode(node)) { + const [openBraceToken, ...children] = node.getChildren(); + const closeBraceToken = Debug.checkDefined(children.pop()); + Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken); + Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken); + // Group `-/+readonly` and `-/+?` + const groupedWithPlusMinusTokens = groupChildren(children, child => + child === node.readonlyToken || child.kind === SyntaxKind.ReadonlyKeyword || + child === node.questionToken || child.kind === SyntaxKind.QuestionToken); + // Group type parameter with surrounding brackets + const groupedWithBrackets = groupChildren(groupedWithPlusMinusTokens, ({ kind }) => + kind === SyntaxKind.OpenBracketToken || + kind === SyntaxKind.TypeParameter || + kind === SyntaxKind.CloseBracketToken + ); + return [ + openBraceToken, + // Pivot on `:` + createSyntaxList(splitChildren(groupedWithBrackets, ({ kind }) => kind === SyntaxKind.ColonToken)), + closeBraceToken, + ]; + } - return node.getChildren(); + // Group modifiers and property name, then pivot on `:`. + if (isPropertySignature(node)) { + const children = groupChildren(node.getChildren(), child => + child === node.name || contains(node.modifiers, child)); + return splitChildren(children, ({ kind }) => kind === SyntaxKind.ColonToken); } - /** - * Groups sibling nodes together into their own SyntaxList if they - * a) are adjacent, AND b) match a predicate function. - */ - function groupChildren(children: Node[], groupOn: (child: Node) => boolean): Node[] { - const result: Node[] = []; - let group: Node[] | undefined; - for (const child of children) { - if (groupOn(child)) { - group = group || []; - group.push(child); - } - else { - if (group) { - result.push(createSyntaxList(group)); - group = undefined; - } - result.push(child); - } - } - if (group) { - result.push(createSyntaxList(group)); - } + // Group the parameter name with its `...`, then that group with its `?`, then pivot on `=`. + if (isParameter(node)) { + const groupedDotDotDotAndName = groupChildren(node.getChildren(), child => + child === node.dotDotDotToken || child === node.name); + const groupedWithQuestionToken = groupChildren(groupedDotDotDotAndName, child => + child === groupedDotDotDotAndName[0] || child === node.questionToken); + return splitChildren(groupedWithQuestionToken, ({ kind }) => kind === SyntaxKind.EqualsToken); + } - return result; + // Pivot on '=' + if (isBindingElement(node)) { + return splitChildren(node.getChildren(), ({ kind }) => kind === SyntaxKind.EqualsToken); } - /** - * Splits sibling nodes into up to four partitions: - * 1) everything left of the first node matched by `pivotOn`, - * 2) the first node matched by `pivotOn`, - * 3) everything right of the first node matched by `pivotOn`, - * 4) a trailing semicolon, if `separateTrailingSemicolon` is enabled. - * The left and right groups, if not empty, will each be grouped into their own containing SyntaxList. - * @param children The sibling nodes to split. - * @param pivotOn The predicate function to match the node to be the pivot. The first node that matches - * the predicate will be used; any others that may match will be included into the right-hand group. - * @param separateTrailingSemicolon If the last token is a semicolon, it will be returned as a separate - * child rather than be included in the right-hand group. - */ - function splitChildren(children: Node[], pivotOn: (child: Node) => boolean, separateTrailingSemicolon = true): Node[] { - if (children.length < 2) { - return children; + return node.getChildren(); +} + +/** + * Groups sibling nodes together into their own SyntaxList if they + * a) are adjacent, AND b) match a predicate function. + */ +function groupChildren(children: Node[], groupOn: (child: Node) => boolean): Node[] { + const result: Node[] = []; + let group: Node[] | undefined; + for (const child of children) { + if (groupOn(child)) { + group = group || []; + group.push(child); } - const splitTokenIndex = findIndex(children, pivotOn); - if (splitTokenIndex === -1) { - return children; + else { + if (group) { + result.push(createSyntaxList(group)); + group = undefined; + } + result.push(child); } - const leftChildren = children.slice(0, splitTokenIndex); - const splitToken = children[splitTokenIndex]; - const lastToken = last(children); - const separateLastToken = separateTrailingSemicolon && lastToken.kind === SyntaxKind.SemicolonToken; - const rightChildren = children.slice(splitTokenIndex + 1, separateLastToken ? children.length - 1 : undefined); - const result = compact([ - leftChildren.length ? createSyntaxList(leftChildren) : undefined, - splitToken, - rightChildren.length ? createSyntaxList(rightChildren) : undefined, - ]); - return separateLastToken ? result.concat(lastToken) : result; } - - function createSyntaxList(children: Node[]): SyntaxList { - Debug.assertGreaterThanOrEqual(children.length, 1); - return setTextRangePosEnd(parseNodeFactory.createSyntaxList(children), children[0].pos, last(children).end); + if (group) { + result.push(createSyntaxList(group)); } - function isListOpener(token: Node | undefined): token is Node { - const kind = token && token.kind; - return kind === SyntaxKind.OpenBraceToken - || kind === SyntaxKind.OpenBracketToken - || kind === SyntaxKind.OpenParenToken - || kind === SyntaxKind.JsxOpeningElement; - } + return result; +} - function isListCloser(token: Node | undefined): token is Node { - const kind = token && token.kind; - return kind === SyntaxKind.CloseBraceToken - || kind === SyntaxKind.CloseBracketToken - || kind === SyntaxKind.CloseParenToken - || kind === SyntaxKind.JsxClosingElement; +/** + * Splits sibling nodes into up to four partitions: + * 1) everything left of the first node matched by `pivotOn`, + * 2) the first node matched by `pivotOn`, + * 3) everything right of the first node matched by `pivotOn`, + * 4) a trailing semicolon, if `separateTrailingSemicolon` is enabled. + * The left and right groups, if not empty, will each be grouped into their own containing SyntaxList. + * @param children The sibling nodes to split. + * @param pivotOn The predicate function to match the node to be the pivot. The first node that matches + * the predicate will be used; any others that may match will be included into the right-hand group. + * @param separateTrailingSemicolon If the last token is a semicolon, it will be returned as a separate + * child rather than be included in the right-hand group. + */ +function splitChildren(children: Node[], pivotOn: (child: Node) => boolean, separateTrailingSemicolon = true): Node[] { + if (children.length < 2) { + return children; } + const splitTokenIndex = findIndex(children, pivotOn); + if (splitTokenIndex === -1) { + return children; + } + const leftChildren = children.slice(0, splitTokenIndex); + const splitToken = children[splitTokenIndex]; + const lastToken = last(children); + const separateLastToken = separateTrailingSemicolon && lastToken.kind === SyntaxKind.SemicolonToken; + const rightChildren = children.slice(splitTokenIndex + 1, separateLastToken ? children.length - 1 : undefined); + const result = compact([ + leftChildren.length ? createSyntaxList(leftChildren) : undefined, + splitToken, + rightChildren.length ? createSyntaxList(rightChildren) : undefined, + ]); + return separateLastToken ? result.concat(lastToken) : result; +} - function getEndPos(sourceFile: SourceFile, node: Node): number { - switch (node.kind) { - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocPropertyTag: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocThisTag: - return sourceFile.getLineEndOfPosition(node.getStart()); - default: - return node.getEnd(); - } +function createSyntaxList(children: Node[]): SyntaxList { + Debug.assertGreaterThanOrEqual(children.length, 1); + return setTextRangePosEnd(parseNodeFactory.createSyntaxList(children), children[0].pos, last(children).end); +} + +function isListOpener(token: Node | undefined): token is Node { + const kind = token && token.kind; + return kind === SyntaxKind.OpenBraceToken + || kind === SyntaxKind.OpenBracketToken + || kind === SyntaxKind.OpenParenToken + || kind === SyntaxKind.JsxOpeningElement; +} + +function isListCloser(token: Node | undefined): token is Node { + const kind = token && token.kind; + return kind === SyntaxKind.CloseBraceToken + || kind === SyntaxKind.CloseBracketToken + || kind === SyntaxKind.CloseParenToken + || kind === SyntaxKind.JsxClosingElement; +} + +function getEndPos(sourceFile: SourceFile, node: Node): number { + switch (node.kind) { + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocPropertyTag: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocThisTag: + return sourceFile.getLineEndOfPosition(node.getStart()); + default: + return node.getEnd(); } } +} diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index ed4bb7c892822..bd9829de542e2 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -1,199 +1,199 @@ /* @internal */ namespace ts { - const base64UrlRegExp = /^data:(?:application\/json(?:;charset=[uU][tT][fF]-8);base64,([A-Za-z0-9+\/=]+)$)?/; +const base64UrlRegExp = /^data:(?:application\/json(?:;charset=[uU][tT][fF]-8);base64,([A-Za-z0-9+\/=]+)$)?/; - export interface SourceMapper { - toLineColumnOffset(fileName: string, position: number): LineAndCharacter; - tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined; - tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined; - clearCache(): void; - } +export interface SourceMapper { + toLineColumnOffset(fileName: string, position: number): LineAndCharacter; + tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined; + tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined; + clearCache(): void; +} - export interface SourceMapperHost { - useCaseSensitiveFileNames(): boolean; - getCurrentDirectory(): string; - getProgram(): Program | undefined; - fileExists?(path: string): boolean; - readFile?(path: string, encoding?: string): string | undefined; - getSourceFileLike?(fileName: string): SourceFileLike | undefined; - getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; - log(s: string): void; - } +export interface SourceMapperHost { + useCaseSensitiveFileNames(): boolean; + getCurrentDirectory(): string; + getProgram(): Program | undefined; + fileExists?(path: string): boolean; + readFile?(path: string, encoding?: string): string | undefined; + getSourceFileLike?(fileName: string): SourceFileLike | undefined; + getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; + log(s: string): void; +} - export function getSourceMapper(host: SourceMapperHost): SourceMapper { - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - const currentDirectory = host.getCurrentDirectory(); - const sourceFileLike = new Map(); - const documentPositionMappers = new Map(); - return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache }; +export function getSourceMapper(host: SourceMapperHost): SourceMapper { + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + const currentDirectory = host.getCurrentDirectory(); + const sourceFileLike = new Map(); + const documentPositionMappers = new Map(); + return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache }; - function toPath(fileName: string) { - return ts.toPath(fileName, currentDirectory, getCanonicalFileName); - } + function toPath(fileName: string) { + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + } - function getDocumentPositionMapper(generatedFileName: string, sourceFileName?: string) { - const path = toPath(generatedFileName); - const value = documentPositionMappers.get(path); - if (value) return value; + function getDocumentPositionMapper(generatedFileName: string, sourceFileName?: string) { + const path = toPath(generatedFileName); + const value = documentPositionMappers.get(path); + if (value) return value; - let mapper: DocumentPositionMapper | undefined; - if (host.getDocumentPositionMapper) { - mapper = host.getDocumentPositionMapper(generatedFileName, sourceFileName); - } - else if (host.readFile) { - const file = getSourceFileLike(generatedFileName); - mapper = file && ts.getDocumentPositionMapper( - { getSourceFileLike, getCanonicalFileName, log: s => host.log(s) }, - generatedFileName, - getLineInfo(file.text, getLineStarts(file)), - f => !host.fileExists || host.fileExists(f) ? host.readFile!(f) : undefined - ); - } - documentPositionMappers.set(path, mapper || identitySourceMapConsumer); - return mapper || identitySourceMapConsumer; + let mapper: DocumentPositionMapper | undefined; + if (host.getDocumentPositionMapper) { + mapper = host.getDocumentPositionMapper(generatedFileName, sourceFileName); } - - function tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined { - if (!isDeclarationFileName(info.fileName)) return undefined; - - const file = getSourceFile(info.fileName); - if (!file) return undefined; - - const newLoc = getDocumentPositionMapper(info.fileName).getSourcePosition(info); - return !newLoc || newLoc === info ? undefined : tryGetSourcePosition(newLoc) || newLoc; + else if (host.readFile) { + const file = getSourceFileLike(generatedFileName); + mapper = file && ts.getDocumentPositionMapper( + { getSourceFileLike, getCanonicalFileName, log: s => host.log(s) }, + generatedFileName, + getLineInfo(file.text, getLineStarts(file)), + f => !host.fileExists || host.fileExists(f) ? host.readFile!(f) : undefined + ); } + documentPositionMappers.set(path, mapper || identitySourceMapConsumer); + return mapper || identitySourceMapConsumer; + } - function tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined { - if (isDeclarationFileName(info.fileName)) return undefined; + function tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined { + if (!isDeclarationFileName(info.fileName)) return undefined; - const sourceFile = getSourceFile(info.fileName); - if (!sourceFile) return undefined; + const file = getSourceFile(info.fileName); + if (!file) return undefined; - const program = host.getProgram()!; - // If this is source file of project reference source (instead of redirect) there is no generated position - if (program.isSourceOfProjectReferenceRedirect(sourceFile.fileName)) { - return undefined; - } + const newLoc = getDocumentPositionMapper(info.fileName).getSourcePosition(info); + return !newLoc || newLoc === info ? undefined : tryGetSourcePosition(newLoc) || newLoc; + } - const options = program.getCompilerOptions(); - const outPath = outFile(options); + function tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined { + if (isDeclarationFileName(info.fileName)) return undefined; - const declarationPath = outPath ? - removeFileExtension(outPath) + Extension.Dts : - getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName); - if (declarationPath === undefined) return undefined; + const sourceFile = getSourceFile(info.fileName); + if (!sourceFile) return undefined; - const newLoc = getDocumentPositionMapper(declarationPath, info.fileName).getGeneratedPosition(info); - return newLoc === info ? undefined : newLoc; + const program = host.getProgram()!; + // If this is source file of project reference source (instead of redirect) there is no generated position + if (program.isSourceOfProjectReferenceRedirect(sourceFile.fileName)) { + return undefined; } - function getSourceFile(fileName: string) { - const program = host.getProgram(); - if (!program) return undefined; + const options = program.getCompilerOptions(); + const outPath = outFile(options); - const path = toPath(fileName); - // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file - const file = program.getSourceFileByPath(path); - return file && file.resolvedPath === path ? file : undefined; - } + const declarationPath = outPath ? + removeFileExtension(outPath) + Extension.Dts : + getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName); + if (declarationPath === undefined) return undefined; - function getOrCreateSourceFileLike(fileName: string): SourceFileLike | undefined { - const path = toPath(fileName); - const fileFromCache = sourceFileLike.get(path); - if (fileFromCache !== undefined) return fileFromCache ? fileFromCache : undefined; + const newLoc = getDocumentPositionMapper(declarationPath, info.fileName).getGeneratedPosition(info); + return newLoc === info ? undefined : newLoc; + } - if (!host.readFile || host.fileExists && !host.fileExists(path)) { - sourceFileLike.set(path, false); - return undefined; - } + function getSourceFile(fileName: string) { + const program = host.getProgram(); + if (!program) return undefined; - // And failing that, check the disk - const text = host.readFile(path); - const file = text ? createSourceFileLike(text) : false; - sourceFileLike.set(path, file); - return file ? file : undefined; - } + const path = toPath(fileName); + // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file + const file = program.getSourceFileByPath(path); + return file && file.resolvedPath === path ? file : undefined; + } - // This can be called from source mapper in either source program or program that includes generated file - function getSourceFileLike(fileName: string) { - return !host.getSourceFileLike ? - getSourceFile(fileName) || getOrCreateSourceFileLike(fileName) : - host.getSourceFileLike(fileName); - } + function getOrCreateSourceFileLike(fileName: string): SourceFileLike | undefined { + const path = toPath(fileName); + const fileFromCache = sourceFileLike.get(path); + if (fileFromCache !== undefined) return fileFromCache ? fileFromCache : undefined; - function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { - const file = getSourceFileLike(fileName)!; // TODO: GH#18217 - return file.getLineAndCharacterOfPosition(position); + if (!host.readFile || host.fileExists && !host.fileExists(path)) { + sourceFileLike.set(path, false); + return undefined; } - function clearCache(): void { - sourceFileLike.clear(); - documentPositionMappers.clear(); - } + // And failing that, check the disk + const text = host.readFile(path); + const file = text ? createSourceFileLike(text) : false; + sourceFileLike.set(path, file); + return file ? file : undefined; } - /** - * string | undefined to contents of map file to create DocumentPositionMapper from it - * DocumentPositionMapper | false to give back cached DocumentPositionMapper - */ - export type ReadMapFile = (mapFileName: string, mapFileNameFromDts: string | undefined) => string | undefined | DocumentPositionMapper | false; - - export function getDocumentPositionMapper( - host: DocumentPositionMapperHost, - generatedFileName: string, - generatedFileLineInfo: LineInfo, - readMapFile: ReadMapFile) { - let mapFileName = tryGetSourceMappingURL(generatedFileLineInfo); - if (mapFileName) { - const match = base64UrlRegExp.exec(mapFileName); - if (match) { - if (match[1]) { - const base64Object = match[1]; - return convertDocumentToSourceMapper(host, base64decode(sys, base64Object), generatedFileName); - } - // Not a data URL we can parse, skip it - mapFileName = undefined; + // This can be called from source mapper in either source program or program that includes generated file + function getSourceFileLike(fileName: string) { + return !host.getSourceFileLike ? + getSourceFile(fileName) || getOrCreateSourceFileLike(fileName) : + host.getSourceFileLike(fileName); + } + + function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { + const file = getSourceFileLike(fileName)!; // TODO: GH#18217 + return file.getLineAndCharacterOfPosition(position); + } + + function clearCache(): void { + sourceFileLike.clear(); + documentPositionMappers.clear(); + } +} + +/** + * string | undefined to contents of map file to create DocumentPositionMapper from it + * DocumentPositionMapper | false to give back cached DocumentPositionMapper + */ +export type ReadMapFile = (mapFileName: string, mapFileNameFromDts: string | undefined) => string | undefined | DocumentPositionMapper | false; + +export function getDocumentPositionMapper( + host: DocumentPositionMapperHost, + generatedFileName: string, + generatedFileLineInfo: LineInfo, + readMapFile: ReadMapFile) { + let mapFileName = tryGetSourceMappingURL(generatedFileLineInfo); + if (mapFileName) { + const match = base64UrlRegExp.exec(mapFileName); + if (match) { + if (match[1]) { + const base64Object = match[1]; + return convertDocumentToSourceMapper(host, base64decode(sys, base64Object), generatedFileName); } + // Not a data URL we can parse, skip it + mapFileName = undefined; } - const possibleMapLocations: string[] = []; - if (mapFileName) { - possibleMapLocations.push(mapFileName); + } + const possibleMapLocations: string[] = []; + if (mapFileName) { + possibleMapLocations.push(mapFileName); + } + possibleMapLocations.push(generatedFileName + ".map"); + const originalMapFileName = mapFileName && getNormalizedAbsolutePath(mapFileName, getDirectoryPath(generatedFileName)); + for (const location of possibleMapLocations) { + const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(generatedFileName)); + const mapFileContents = readMapFile(mapFileName, originalMapFileName); + if (isString(mapFileContents)) { + return convertDocumentToSourceMapper(host, mapFileContents, mapFileName); } - possibleMapLocations.push(generatedFileName + ".map"); - const originalMapFileName = mapFileName && getNormalizedAbsolutePath(mapFileName, getDirectoryPath(generatedFileName)); - for (const location of possibleMapLocations) { - const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(generatedFileName)); - const mapFileContents = readMapFile(mapFileName, originalMapFileName); - if (isString(mapFileContents)) { - return convertDocumentToSourceMapper(host, mapFileContents, mapFileName); - } - if (mapFileContents !== undefined) { - return mapFileContents || undefined; - } + if (mapFileContents !== undefined) { + return mapFileContents || undefined; } - return undefined; } + return undefined; +} - function convertDocumentToSourceMapper(host: DocumentPositionMapperHost, contents: string, mapFileName: string) { - const map = tryParseRawSourceMap(contents); - if (!map || !map.sources || !map.file || !map.mappings) { - // obviously invalid map - return undefined; - } +function convertDocumentToSourceMapper(host: DocumentPositionMapperHost, contents: string, mapFileName: string) { + const map = tryParseRawSourceMap(contents); + if (!map || !map.sources || !map.file || !map.mappings) { + // obviously invalid map + return undefined; + } - // Dont support sourcemaps that contain inlined sources - if (map.sourcesContent && map.sourcesContent.some(isString)) return undefined; + // Dont support sourcemaps that contain inlined sources + if (map.sourcesContent && map.sourcesContent.some(isString)) return undefined; - return createDocumentPositionMapper(host, map, mapFileName); - } + return createDocumentPositionMapper(host, map, mapFileName); +} - function createSourceFileLike(text: string, lineMap?: SourceFileLike["lineMap"]): SourceFileLike { - return { - text, - lineMap, - getLineAndCharacterOfPosition(pos: number) { - return computeLineAndCharacterOfPosition(getLineStarts(this), pos); - } - }; - } +function createSourceFileLike(text: string, lineMap?: SourceFileLike["lineMap"]): SourceFileLike { + return { + text, + lineMap, + getLineAndCharacterOfPosition(pos: number) { + return computeLineAndCharacterOfPosition(getLineStarts(this), pos); + } + }; +} } diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 2c1a453df6d1e..1450b3520096c 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -1,801 +1,801 @@ /* @internal */ namespace ts.Completions.StringCompletions { - export function getStringLiteralCompletions( - sourceFile: SourceFile, - position: number, - contextToken: Node | undefined, - options: CompilerOptions, - host: LanguageServiceHost, - program: Program, - log: Log, - preferences: UserPreferences): CompletionInfo | undefined { - if (isInReferenceComment(sourceFile, position)) { - const entries = getTripleSlashReferenceCompletion(sourceFile, position, options, host); - return entries && convertPathCompletions(entries); - } - if (isInString(sourceFile, position, contextToken)) { - if (!contextToken || !isStringLiteralLike(contextToken)) return undefined; - const entries = getStringLiteralCompletionEntries(sourceFile, contextToken, position, program.getTypeChecker(), options, host, preferences); - return convertStringLiteralCompletions(entries, contextToken, sourceFile, host, program, log, options, preferences); - } +export function getStringLiteralCompletions( + sourceFile: SourceFile, + position: number, + contextToken: Node | undefined, + options: CompilerOptions, + host: LanguageServiceHost, + program: Program, + log: Log, + preferences: UserPreferences): CompletionInfo | undefined { + if (isInReferenceComment(sourceFile, position)) { + const entries = getTripleSlashReferenceCompletion(sourceFile, position, options, host); + return entries && convertPathCompletions(entries); + } + if (isInString(sourceFile, position, contextToken)) { + if (!contextToken || !isStringLiteralLike(contextToken)) return undefined; + const entries = getStringLiteralCompletionEntries(sourceFile, contextToken, position, program.getTypeChecker(), options, host, preferences); + return convertStringLiteralCompletions(entries, contextToken, sourceFile, host, program, log, options, preferences); } +} - function convertStringLiteralCompletions( - completion: StringLiteralCompletion | undefined, - contextToken: StringLiteralLike, - sourceFile: SourceFile, - host: LanguageServiceHost, - program: Program, - log: Log, - options: CompilerOptions, - preferences: UserPreferences, - ): CompletionInfo | undefined { - if (completion === undefined) { - return undefined; - } - - const optionalReplacementSpan = createTextSpanFromStringLiteralLikeContent(contextToken); - switch (completion.kind) { - case StringLiteralCompletionKind.Paths: - return convertPathCompletions(completion.paths); - case StringLiteralCompletionKind.Properties: { - const entries: CompletionEntry[] = []; - getCompletionEntriesFromSymbols( - completion.symbols, - entries, - contextToken, - contextToken, - sourceFile, - sourceFile, - host, - program, - ScriptTarget.ESNext, - log, - CompletionKind.String, - preferences, - options, - ); // Target will not be used, so arbitrary - return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, optionalReplacementSpan, entries }; - } - case StringLiteralCompletionKind.Types: { - const entries = completion.types.map(type => ({ - name: type.value, - kindModifiers: ScriptElementKindModifier.none, - kind: ScriptElementKind.string, - sortText: SortText.LocationPriority, - replacementSpan: getReplacementSpanForContextToken(contextToken) - })); - return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, optionalReplacementSpan, entries }; - } - default: - return Debug.assertNever(completion); - } +function convertStringLiteralCompletions( + completion: StringLiteralCompletion | undefined, + contextToken: StringLiteralLike, + sourceFile: SourceFile, + host: LanguageServiceHost, + program: Program, + log: Log, + options: CompilerOptions, + preferences: UserPreferences, +): CompletionInfo | undefined { + if (completion === undefined) { + return undefined; + } + + const optionalReplacementSpan = createTextSpanFromStringLiteralLikeContent(contextToken); + switch (completion.kind) { + case StringLiteralCompletionKind.Paths: + return convertPathCompletions(completion.paths); + case StringLiteralCompletionKind.Properties: { + const entries: CompletionEntry[] = []; + getCompletionEntriesFromSymbols( + completion.symbols, + entries, + contextToken, + contextToken, + sourceFile, + sourceFile, + host, + program, + ScriptTarget.ESNext, + log, + CompletionKind.String, + preferences, + options, + ); // Target will not be used, so arbitrary + return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, optionalReplacementSpan, entries }; + } + case StringLiteralCompletionKind.Types: { + const entries = completion.types.map(type => ({ + name: type.value, + kindModifiers: ScriptElementKindModifier.none, + kind: ScriptElementKind.string, + sortText: SortText.LocationPriority, + replacementSpan: getReplacementSpanForContextToken(contextToken) + })); + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, optionalReplacementSpan, entries }; + } + default: + return Debug.assertNever(completion); } +} - export function getStringLiteralCompletionDetails(name: string, sourceFile: SourceFile, position: number, contextToken: Node | undefined, checker: TypeChecker, options: CompilerOptions, host: LanguageServiceHost, cancellationToken: CancellationToken, preferences: UserPreferences) { - if (!contextToken || !isStringLiteralLike(contextToken)) return undefined; - const completions = getStringLiteralCompletionEntries(sourceFile, contextToken, position, checker, options, host, preferences); - return completions && stringLiteralCompletionDetails(name, contextToken, completions, sourceFile, checker, cancellationToken); - } +export function getStringLiteralCompletionDetails(name: string, sourceFile: SourceFile, position: number, contextToken: Node | undefined, checker: TypeChecker, options: CompilerOptions, host: LanguageServiceHost, cancellationToken: CancellationToken, preferences: UserPreferences) { + if (!contextToken || !isStringLiteralLike(contextToken)) return undefined; + const completions = getStringLiteralCompletionEntries(sourceFile, contextToken, position, checker, options, host, preferences); + return completions && stringLiteralCompletionDetails(name, contextToken, completions, sourceFile, checker, cancellationToken); +} - function stringLiteralCompletionDetails(name: string, location: Node, completion: StringLiteralCompletion, sourceFile: SourceFile, checker: TypeChecker, cancellationToken: CancellationToken): CompletionEntryDetails | undefined { - switch (completion.kind) { - case StringLiteralCompletionKind.Paths: { - const match = find(completion.paths, p => p.name === name); - return match && createCompletionDetails(name, kindModifiersFromExtension(match.extension), match.kind, [textPart(name)]); - } - case StringLiteralCompletionKind.Properties: { - const match = find(completion.symbols, s => s.name === name); - return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location, cancellationToken); - } - case StringLiteralCompletionKind.Types: - return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.typeElement, [textPart(name)]) : undefined; - default: - return Debug.assertNever(completion); +function stringLiteralCompletionDetails(name: string, location: Node, completion: StringLiteralCompletion, sourceFile: SourceFile, checker: TypeChecker, cancellationToken: CancellationToken): CompletionEntryDetails | undefined { + switch (completion.kind) { + case StringLiteralCompletionKind.Paths: { + const match = find(completion.paths, p => p.name === name); + return match && createCompletionDetails(name, kindModifiersFromExtension(match.extension), match.kind, [textPart(name)]); + } + case StringLiteralCompletionKind.Properties: { + const match = find(completion.symbols, s => s.name === name); + return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location, cancellationToken); } + case StringLiteralCompletionKind.Types: + return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.typeElement, [textPart(name)]) : undefined; + default: + return Debug.assertNever(completion); } +} - function convertPathCompletions(pathCompletions: readonly PathCompletion[]): CompletionInfo { - const isGlobalCompletion = false; // We don't want the editor to offer any other completions, such as snippets, inside a comment. - const isNewIdentifierLocation = true; // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of. - const entries = pathCompletions.map(({ name, kind, span, extension }): CompletionEntry => - ({ name, kind, kindModifiers: kindModifiersFromExtension(extension), sortText: SortText.LocationPriority, replacementSpan: span })); - return { isGlobalCompletion, isMemberCompletion: false, isNewIdentifierLocation, entries }; - } - function kindModifiersFromExtension(extension: Extension | undefined): ScriptElementKindModifier { - switch (extension) { - case Extension.Dts: return ScriptElementKindModifier.dtsModifier; - case Extension.Js: return ScriptElementKindModifier.jsModifier; - case Extension.Json: return ScriptElementKindModifier.jsonModifier; - case Extension.Jsx: return ScriptElementKindModifier.jsxModifier; - case Extension.Ts: return ScriptElementKindModifier.tsModifier; - case Extension.Tsx: return ScriptElementKindModifier.tsxModifier; - case Extension.Dmts: return ScriptElementKindModifier.dmtsModifier; - case Extension.Mjs: return ScriptElementKindModifier.mjsModifier; - case Extension.Mts: return ScriptElementKindModifier.mtsModifier; - case Extension.Dcts: return ScriptElementKindModifier.dctsModifier; - case Extension.Cjs: return ScriptElementKindModifier.cjsModifier; - case Extension.Cts: return ScriptElementKindModifier.ctsModifier; - case Extension.TsBuildInfo: return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported.`); - case undefined: return ScriptElementKindModifier.none; - default: - return Debug.assertNever(extension); - } +function convertPathCompletions(pathCompletions: readonly PathCompletion[]): CompletionInfo { + const isGlobalCompletion = false; // We don't want the editor to offer any other completions, such as snippets, inside a comment. + const isNewIdentifierLocation = true; // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of. + const entries = pathCompletions.map(({ name, kind, span, extension }): CompletionEntry => + ({ name, kind, kindModifiers: kindModifiersFromExtension(extension), sortText: SortText.LocationPriority, replacementSpan: span })); + return { isGlobalCompletion, isMemberCompletion: false, isNewIdentifierLocation, entries }; +} +function kindModifiersFromExtension(extension: Extension | undefined): ScriptElementKindModifier { + switch (extension) { + case Extension.Dts: return ScriptElementKindModifier.dtsModifier; + case Extension.Js: return ScriptElementKindModifier.jsModifier; + case Extension.Json: return ScriptElementKindModifier.jsonModifier; + case Extension.Jsx: return ScriptElementKindModifier.jsxModifier; + case Extension.Ts: return ScriptElementKindModifier.tsModifier; + case Extension.Tsx: return ScriptElementKindModifier.tsxModifier; + case Extension.Dmts: return ScriptElementKindModifier.dmtsModifier; + case Extension.Mjs: return ScriptElementKindModifier.mjsModifier; + case Extension.Mts: return ScriptElementKindModifier.mtsModifier; + case Extension.Dcts: return ScriptElementKindModifier.dctsModifier; + case Extension.Cjs: return ScriptElementKindModifier.cjsModifier; + case Extension.Cts: return ScriptElementKindModifier.ctsModifier; + case Extension.TsBuildInfo: return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported.`); + case undefined: return ScriptElementKindModifier.none; + default: + return Debug.assertNever(extension); } +} - const enum StringLiteralCompletionKind { Paths, Properties, Types } - interface StringLiteralCompletionsFromProperties { - readonly kind: StringLiteralCompletionKind.Properties; - readonly symbols: readonly Symbol[]; - readonly hasIndexSignature: boolean; - } - interface StringLiteralCompletionsFromTypes { - readonly kind: StringLiteralCompletionKind.Types; - readonly types: readonly StringLiteralType[]; - readonly isNewIdentifier: boolean; - } - type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: readonly PathCompletion[] } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes; - function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, preferences: UserPreferences): StringLiteralCompletion | undefined { - const parent = walkUpParentheses(node.parent); - switch (parent.kind) { - case SyntaxKind.LiteralType: { - const grandParent = walkUpParentheses(parent.parent); - switch (grandParent.kind) { - case SyntaxKind.TypeReference: { - const typeReference = grandParent as TypeReferenceNode; - const typeArgument = findAncestor(parent, n => n.parent === typeReference) as LiteralTypeNode; - if (typeArgument) { - return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false }; - } - return undefined; +const enum StringLiteralCompletionKind { Paths, Properties, Types } +interface StringLiteralCompletionsFromProperties { + readonly kind: StringLiteralCompletionKind.Properties; + readonly symbols: readonly Symbol[]; + readonly hasIndexSignature: boolean; +} +interface StringLiteralCompletionsFromTypes { + readonly kind: StringLiteralCompletionKind.Types; + readonly types: readonly StringLiteralType[]; + readonly isNewIdentifier: boolean; +} +type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: readonly PathCompletion[] } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes; +function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, preferences: UserPreferences): StringLiteralCompletion | undefined { + const parent = walkUpParentheses(node.parent); + switch (parent.kind) { + case SyntaxKind.LiteralType: { + const grandParent = walkUpParentheses(parent.parent); + switch (grandParent.kind) { + case SyntaxKind.TypeReference: { + const typeReference = grandParent as TypeReferenceNode; + const typeArgument = findAncestor(parent, n => n.parent === typeReference) as LiteralTypeNode; + if (typeArgument) { + return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false }; } - case SyntaxKind.IndexedAccessType: - // Get all apparent property names - // i.e. interface Foo { - // foo: string; - // bar: string; - // } - // let x: Foo["/*completion position*/"] - const { indexType, objectType } = grandParent as IndexedAccessTypeNode; - if (!rangeContainsPosition(indexType, position)) { - return undefined; - } - return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode(objectType)); - case SyntaxKind.ImportType: - return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) }; - case SyntaxKind.UnionType: { - if (!isTypeReferenceNode(grandParent.parent)) { - return undefined; - } - const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(grandParent as UnionTypeNode, parent as LiteralTypeNode); - const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(grandParent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value)); - return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false }; + return undefined; + } + case SyntaxKind.IndexedAccessType: + // Get all apparent property names + // i.e. interface Foo { + // foo: string; + // bar: string; + // } + // let x: Foo["/*completion position*/"] + const { indexType, objectType } = grandParent as IndexedAccessTypeNode; + if (!rangeContainsPosition(indexType, position)) { + return undefined; } - default: + return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode(objectType)); + case SyntaxKind.ImportType: + return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) }; + case SyntaxKind.UnionType: { + if (!isTypeReferenceNode(grandParent.parent)) { return undefined; + } + const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(grandParent as UnionTypeNode, parent as LiteralTypeNode); + const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(grandParent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value)); + return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false }; } + default: + return undefined; } - case SyntaxKind.PropertyAssignment: - if (isObjectLiteralExpression(parent.parent) && (parent as PropertyAssignment).name === node) { - // Get quoted name of properties of the object literal expression - // i.e. interface ConfigFiles { - // 'jspm:dev': string - // } - // let files: ConfigFiles = { - // '/*completion position*/' - // } - // - // function foo(c: ConfigFiles) {} - // foo({ - // '/*completion position*/' - // }); - return stringLiteralCompletionsForObjectLiteral(typeChecker, parent.parent); - } - return fromContextualType(); - - case SyntaxKind.ElementAccessExpression: { - const { expression, argumentExpression } = parent as ElementAccessExpression; - if (node === skipParentheses(argumentExpression)) { - // Get all names of properties on the expression - // i.e. interface A { - // 'prop1': string - // } - // let a: A; - // a['/*completion position*/'] - return stringLiteralCompletionsFromProperties(typeChecker.getTypeAtLocation(expression)); - } - return undefined; + } + case SyntaxKind.PropertyAssignment: + if (isObjectLiteralExpression(parent.parent) && (parent as PropertyAssignment).name === node) { + // Get quoted name of properties of the object literal expression + // i.e. interface ConfigFiles { + // 'jspm:dev': string + // } + // let files: ConfigFiles = { + // '/*completion position*/' + // } + // + // function foo(c: ConfigFiles) {} + // foo({ + // '/*completion position*/' + // }); + return stringLiteralCompletionsForObjectLiteral(typeChecker, parent.parent); } - - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - if (!isRequireCallArgument(node) && !isImportCall(parent)) { - const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(node, position, sourceFile); - // Get string literal completions from specialized signatures of the target - // i.e. declare function f(a: 'A'); - // f("/*completion position*/") - return argumentInfo ? getStringLiteralCompletionsFromSignature(argumentInfo, typeChecker) : fromContextualType(); - } - // falls through (is `require("")` or `require(""` or `import("")`) - - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExternalModuleReference: - // Get all known external module names or complete a path to a module - // i.e. import * as ns from "/*completion position*/"; - // var y = import("/*completion position*/"); - // import x = require("/*completion position*/"); - // var y = require("/*completion position*/"); - // export * from "/*completion position*/"; - return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) }; - - default: - return fromContextualType(); + return fromContextualType(); + + case SyntaxKind.ElementAccessExpression: { + const { expression, argumentExpression } = parent as ElementAccessExpression; + if (node === skipParentheses(argumentExpression)) { + // Get all names of properties on the expression + // i.e. interface A { + // 'prop1': string + // } + // let a: A; + // a['/*completion position*/'] + return stringLiteralCompletionsFromProperties(typeChecker.getTypeAtLocation(expression)); + } + return undefined; } - function fromContextualType(): StringLiteralCompletion { - // Get completion for string literal from string literal type - // i.e. var x: "hi" | "hello" = "/*completion position*/" - return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker)), isNewIdentifier: false }; - } + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + if (!isRequireCallArgument(node) && !isImportCall(parent)) { + const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(node, position, sourceFile); + // Get string literal completions from specialized signatures of the target + // i.e. declare function f(a: 'A'); + // f("/*completion position*/") + return argumentInfo ? getStringLiteralCompletionsFromSignature(argumentInfo, typeChecker) : fromContextualType(); + } + // falls through (is `require("")` or `require(""` or `import("")`) + + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExternalModuleReference: + // Get all known external module names or complete a path to a module + // i.e. import * as ns from "/*completion position*/"; + // var y = import("/*completion position*/"); + // import x = require("/*completion position*/"); + // var y = require("/*completion position*/"); + // export * from "/*completion position*/"; + return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) }; + + default: + return fromContextualType(); } - function walkUpParentheses(node: Node) { - switch (node.kind) { - case SyntaxKind.ParenthesizedType: - return walkUpParenthesizedTypes(node); - case SyntaxKind.ParenthesizedExpression: - return walkUpParenthesizedExpressions(node); - default: - return node; - } + function fromContextualType(): StringLiteralCompletion { + // Get completion for string literal from string literal type + // i.e. var x: "hi" | "hello" = "/*completion position*/" + return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker)), isNewIdentifier: false }; } +} - function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: LiteralTypeNode): readonly string[] { - return mapDefined(union.types, type => - type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined); +function walkUpParentheses(node: Node) { + switch (node.kind) { + case SyntaxKind.ParenthesizedType: + return walkUpParenthesizedTypes(node); + case SyntaxKind.ParenthesizedExpression: + return walkUpParenthesizedExpressions(node); + default: + return node; } +} - function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes { - let isNewIdentifier = false; +function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: LiteralTypeNode): readonly string[] { + return mapDefined(union.types, type => + type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined); +} - const uniques = new Map(); - const candidates: Signature[] = []; - checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount); - const types = flatMap(candidates, candidate => { - if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return; - const type = checker.getParameterType(candidate, argumentInfo.argumentIndex); - isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String); - return getStringLiteralTypes(type, uniques); - }); +function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes { + let isNewIdentifier = false; - return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier }; - } + const uniques = new Map(); + const candidates: Signature[] = []; + checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount); + const types = flatMap(candidates, candidate => { + if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return; + const type = checker.getParameterType(candidate, argumentInfo.argumentIndex); + isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String); + return getStringLiteralTypes(type, uniques); + }); - function stringLiteralCompletionsFromProperties(type: Type | undefined): StringLiteralCompletionsFromProperties | undefined { - return type && { - kind: StringLiteralCompletionKind.Properties, - symbols: filter(type.getApparentProperties(), prop => !(prop.valueDeclaration && isPrivateIdentifierClassElementDeclaration(prop.valueDeclaration))), - hasIndexSignature: hasIndexSignature(type) - }; - } - - function stringLiteralCompletionsForObjectLiteral(checker: TypeChecker, objectLiteralExpression: ObjectLiteralExpression): StringLiteralCompletionsFromProperties | undefined { - const contextualType = checker.getContextualType(objectLiteralExpression); - if (!contextualType) return undefined; + return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier }; +} - const completionsType = checker.getContextualType(objectLiteralExpression, ContextFlags.Completions); - const symbols = getPropertiesForObjectExpression( - contextualType, - completionsType, - objectLiteralExpression, - checker - ); +function stringLiteralCompletionsFromProperties(type: Type | undefined): StringLiteralCompletionsFromProperties | undefined { + return type && { + kind: StringLiteralCompletionKind.Properties, + symbols: filter(type.getApparentProperties(), prop => !(prop.valueDeclaration && isPrivateIdentifierClassElementDeclaration(prop.valueDeclaration))), + hasIndexSignature: hasIndexSignature(type) + }; +} - return { - kind: StringLiteralCompletionKind.Properties, - symbols, - hasIndexSignature: hasIndexSignature(contextualType) - }; - } +function stringLiteralCompletionsForObjectLiteral(checker: TypeChecker, objectLiteralExpression: ObjectLiteralExpression): StringLiteralCompletionsFromProperties | undefined { + const contextualType = checker.getContextualType(objectLiteralExpression); + if (!contextualType) return undefined; + + const completionsType = checker.getContextualType(objectLiteralExpression, ContextFlags.Completions); + const symbols = getPropertiesForObjectExpression( + contextualType, + completionsType, + objectLiteralExpression, + checker + ); + + return { + kind: StringLiteralCompletionKind.Properties, + symbols, + hasIndexSignature: hasIndexSignature(contextualType) + }; +} - function getStringLiteralTypes(type: Type | undefined, uniques = new Map()): readonly StringLiteralType[] { - if (!type) return emptyArray; - type = skipConstraint(type); - return type.isUnion() ? flatMap(type.types, t => getStringLiteralTypes(t, uniques)) : - type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) ? [type] : emptyArray; - } +function getStringLiteralTypes(type: Type | undefined, uniques = new Map()): readonly StringLiteralType[] { + if (!type) return emptyArray; + type = skipConstraint(type); + return type.isUnion() ? flatMap(type.types, t => getStringLiteralTypes(t, uniques)) : + type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) ? [type] : emptyArray; +} - interface NameAndKind { - readonly name: string; - readonly kind: ScriptElementKind.scriptElement | ScriptElementKind.directory | ScriptElementKind.externalModuleName; - readonly extension: Extension | undefined; - } - interface PathCompletion extends NameAndKind { - readonly span: TextSpan | undefined; - } +interface NameAndKind { + readonly name: string; + readonly kind: ScriptElementKind.scriptElement | ScriptElementKind.directory | ScriptElementKind.externalModuleName; + readonly extension: Extension | undefined; +} +interface PathCompletion extends NameAndKind { + readonly span: TextSpan | undefined; +} - function nameAndKind(name: string, kind: NameAndKind["kind"], extension: Extension | undefined): NameAndKind { - return { name, kind, extension }; - } - function directoryResult(name: string): NameAndKind { - return nameAndKind(name, ScriptElementKind.directory, /*extension*/ undefined); - } +function nameAndKind(name: string, kind: NameAndKind["kind"], extension: Extension | undefined): NameAndKind { + return { name, kind, extension }; +} +function directoryResult(name: string): NameAndKind { + return nameAndKind(name, ScriptElementKind.directory, /*extension*/ undefined); +} - function addReplacementSpans(text: string, textStart: number, names: readonly NameAndKind[]): readonly PathCompletion[] { - const span = getDirectoryFragmentTextSpan(text, textStart); - const wholeSpan = text.length === 0 ? undefined : createTextSpan(textStart, text.length); - return names.map(({ name, kind, extension }): PathCompletion => - Math.max(name.indexOf(directorySeparator), name.indexOf(altDirectorySeparator)) !== -1 ? { name, kind, extension, span: wholeSpan } : { name, kind, extension, span }); - } +function addReplacementSpans(text: string, textStart: number, names: readonly NameAndKind[]): readonly PathCompletion[] { + const span = getDirectoryFragmentTextSpan(text, textStart); + const wholeSpan = text.length === 0 ? undefined : createTextSpan(textStart, text.length); + return names.map(({ name, kind, extension }): PathCompletion => + Math.max(name.indexOf(directorySeparator), name.indexOf(altDirectorySeparator)) !== -1 ? { name, kind, extension, span: wholeSpan } : { name, kind, extension, span }); +} - function getStringLiteralCompletionsFromModuleNames(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker, preferences: UserPreferences): readonly PathCompletion[] { - return addReplacementSpans(node.text, node.getStart(sourceFile) + 1, getStringLiteralCompletionsFromModuleNamesWorker(sourceFile, node, compilerOptions, host, typeChecker, preferences)); - } +function getStringLiteralCompletionsFromModuleNames(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker, preferences: UserPreferences): readonly PathCompletion[] { + return addReplacementSpans(node.text, node.getStart(sourceFile) + 1, getStringLiteralCompletionsFromModuleNamesWorker(sourceFile, node, compilerOptions, host, typeChecker, preferences)); +} - function getStringLiteralCompletionsFromModuleNamesWorker(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker, preferences: UserPreferences): readonly NameAndKind[] { - const literalValue = normalizeSlashes(node.text); +function getStringLiteralCompletionsFromModuleNamesWorker(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker, preferences: UserPreferences): readonly NameAndKind[] { + const literalValue = normalizeSlashes(node.text); - const scriptPath = sourceFile.path; - const scriptDirectory = getDirectoryPath(scriptPath); + const scriptPath = sourceFile.path; + const scriptDirectory = getDirectoryPath(scriptPath); - return isPathRelativeToScript(literalValue) || !compilerOptions.baseUrl && (isRootedDiskPath(literalValue) || isUrl(literalValue)) - ? getCompletionEntriesForRelativeModules(literalValue, scriptDirectory, compilerOptions, host, scriptPath, getIncludeExtensionOption()) - : getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, compilerOptions, host, typeChecker); + return isPathRelativeToScript(literalValue) || !compilerOptions.baseUrl && (isRootedDiskPath(literalValue) || isUrl(literalValue)) + ? getCompletionEntriesForRelativeModules(literalValue, scriptDirectory, compilerOptions, host, scriptPath, getIncludeExtensionOption()) + : getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, compilerOptions, host, typeChecker); - function getIncludeExtensionOption() { - const mode = isStringLiteralLike(node) ? getModeForUsageLocation(sourceFile, node) : undefined; - return preferences.importModuleSpecifierEnding === "js" || mode === ModuleKind.ESNext ? IncludeExtensionsOption.ModuleSpecifierCompletion : IncludeExtensionsOption.Exclude; - } + function getIncludeExtensionOption() { + const mode = isStringLiteralLike(node) ? getModeForUsageLocation(sourceFile, node) : undefined; + return preferences.importModuleSpecifierEnding === "js" || mode === ModuleKind.ESNext ? IncludeExtensionsOption.ModuleSpecifierCompletion : IncludeExtensionsOption.Exclude; } +} - interface ExtensionOptions { - readonly extensions: readonly Extension[]; - readonly includeExtensionsOption: IncludeExtensionsOption; - } - function getExtensionOptions(compilerOptions: CompilerOptions, includeExtensionsOption = IncludeExtensionsOption.Exclude): ExtensionOptions { - return { extensions: flatten(getSupportedExtensionsForModuleResolution(compilerOptions)), includeExtensionsOption }; +interface ExtensionOptions { + readonly extensions: readonly Extension[]; + readonly includeExtensionsOption: IncludeExtensionsOption; +} +function getExtensionOptions(compilerOptions: CompilerOptions, includeExtensionsOption = IncludeExtensionsOption.Exclude): ExtensionOptions { + return { extensions: flatten(getSupportedExtensionsForModuleResolution(compilerOptions)), includeExtensionsOption }; +} +function getCompletionEntriesForRelativeModules(literalValue: string, scriptDirectory: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, scriptPath: Path, includeExtensions: IncludeExtensionsOption) { + const extensionOptions = getExtensionOptions(compilerOptions, includeExtensions); + if (compilerOptions.rootDirs) { + return getCompletionEntriesForDirectoryFragmentWithRootDirs( + compilerOptions.rootDirs, literalValue, scriptDirectory, extensionOptions, compilerOptions, host, scriptPath); } - function getCompletionEntriesForRelativeModules(literalValue: string, scriptDirectory: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, scriptPath: Path, includeExtensions: IncludeExtensionsOption) { - const extensionOptions = getExtensionOptions(compilerOptions, includeExtensions); - if (compilerOptions.rootDirs) { - return getCompletionEntriesForDirectoryFragmentWithRootDirs( - compilerOptions.rootDirs, literalValue, scriptDirectory, extensionOptions, compilerOptions, host, scriptPath); - } - else { - return getCompletionEntriesForDirectoryFragment(literalValue, scriptDirectory, extensionOptions, host, scriptPath); - } + else { + return getCompletionEntriesForDirectoryFragment(literalValue, scriptDirectory, extensionOptions, host, scriptPath); } +} - function getSupportedExtensionsForModuleResolution(compilerOptions: CompilerOptions): readonly Extension[][] { - const extensions = getSupportedExtensions(compilerOptions); - return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs ? - getSupportedExtensionsWithJsonIfResolveJsonModule(compilerOptions, extensions) : - extensions; - } +function getSupportedExtensionsForModuleResolution(compilerOptions: CompilerOptions): readonly Extension[][] { + const extensions = getSupportedExtensions(compilerOptions); + return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs ? + getSupportedExtensionsWithJsonIfResolveJsonModule(compilerOptions, extensions) : + extensions; +} - /** - * Takes a script path and returns paths for all potential folders that could be merged with its - * containing folder via the "rootDirs" compiler option - */ - function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptDirectory: string, ignoreCase: boolean): readonly string[] { - // Make all paths absolute/normalized if they are not already - rootDirs = rootDirs.map(rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory))); +/** + * Takes a script path and returns paths for all potential folders that could be merged with its + * containing folder via the "rootDirs" compiler option + */ +function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptDirectory: string, ignoreCase: boolean): readonly string[] { + // Make all paths absolute/normalized if they are not already + rootDirs = rootDirs.map(rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory))); + + // Determine the path to the directory containing the script relative to the root directory it is contained within + const relativeDirectory = firstDefined(rootDirs, rootDirectory => + containsPath(rootDirectory, scriptDirectory, basePath, ignoreCase) ? scriptDirectory.substr(rootDirectory.length) : undefined)!; // TODO: GH#18217 + + // Now find a path for each potential directory that is to be merged with the one containing the script + return deduplicate( + [...rootDirs.map(rootDirectory => combinePaths(rootDirectory, relativeDirectory)), scriptDirectory], + equateStringsCaseSensitive, + compareStringsCaseSensitive); +} - // Determine the path to the directory containing the script relative to the root directory it is contained within - const relativeDirectory = firstDefined(rootDirs, rootDirectory => - containsPath(rootDirectory, scriptDirectory, basePath, ignoreCase) ? scriptDirectory.substr(rootDirectory.length) : undefined)!; // TODO: GH#18217 +function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptDirectory: string, extensionOptions: ExtensionOptions, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude: string): readonly NameAndKind[] { + const basePath = compilerOptions.project || host.getCurrentDirectory(); + const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); + const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptDirectory, ignoreCase); + return flatMap(baseDirectories, baseDirectory => getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensionOptions, host, exclude)); +} - // Now find a path for each potential directory that is to be merged with the one containing the script - return deduplicate( - [...rootDirs.map(rootDirectory => combinePaths(rootDirectory, relativeDirectory)), scriptDirectory], - equateStringsCaseSensitive, - compareStringsCaseSensitive); +const enum IncludeExtensionsOption { + Exclude, + Include, + ModuleSpecifierCompletion, +} +/** + * Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename. + */ +function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, { extensions, includeExtensionsOption }: ExtensionOptions, host: LanguageServiceHost, exclude?: string, result: NameAndKind[] = []): NameAndKind[] { + if (fragment === undefined) { + fragment = ""; } - function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptDirectory: string, extensionOptions: ExtensionOptions, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude: string): readonly NameAndKind[] { - const basePath = compilerOptions.project || host.getCurrentDirectory(); - const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); - const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptDirectory, ignoreCase); - return flatMap(baseDirectories, baseDirectory => getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensionOptions, host, exclude)); - } + fragment = normalizeSlashes(fragment); - const enum IncludeExtensionsOption { - Exclude, - Include, - ModuleSpecifierCompletion, - } /** - * Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename. + * Remove the basename from the path. Note that we don't use the basename to filter completions; + * the client is responsible for refining completions. */ - function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, { extensions, includeExtensionsOption }: ExtensionOptions, host: LanguageServiceHost, exclude?: string, result: NameAndKind[] = []): NameAndKind[] { - if (fragment === undefined) { - fragment = ""; - } + if (!hasTrailingDirectorySeparator(fragment)) { + fragment = getDirectoryPath(fragment); + } - fragment = normalizeSlashes(fragment); + if (fragment === "") { + fragment = "." + directorySeparator; + } - /** - * Remove the basename from the path. Note that we don't use the basename to filter completions; - * the client is responsible for refining completions. - */ - if (!hasTrailingDirectorySeparator(fragment)) { - fragment = getDirectoryPath(fragment); - } + fragment = ensureTrailingDirectorySeparator(fragment); - if (fragment === "") { - fragment = "." + directorySeparator; - } + // const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)); // TODO(rbuckton): should use resolvePaths + const absolutePath = resolvePath(scriptPath, fragment); + const baseDirectory = hasTrailingDirectorySeparator(absolutePath) ? absolutePath : getDirectoryPath(absolutePath); - fragment = ensureTrailingDirectorySeparator(fragment); - - // const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)); // TODO(rbuckton): should use resolvePaths - const absolutePath = resolvePath(scriptPath, fragment); - const baseDirectory = hasTrailingDirectorySeparator(absolutePath) ? absolutePath : getDirectoryPath(absolutePath); - - const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); - if (!tryDirectoryExists(host, baseDirectory)) return result; - - // Enumerate the available files if possible - const files = tryReadDirectory(host, baseDirectory, extensions, /*exclude*/ undefined, /*include*/ ["./*"]); - - if (files) { - /** - * Multiple file entries might map to the same truncated name once we remove extensions - * (happens iff includeExtensionsOption === includeExtensionsOption.Exclude) so we use a set-like data structure. Eg: - * - * both foo.ts and foo.tsx become foo - */ - const foundFiles = new Map(); // maps file to its extension - for (let filePath of files) { - filePath = normalizePath(filePath); - if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { - continue; - } + const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); + if (!tryDirectoryExists(host, baseDirectory)) return result; - let foundFileName: string; - const outputExtension = moduleSpecifiers.tryGetJSExtensionForFile(filePath, host.getCompilationSettings()); - if (includeExtensionsOption === IncludeExtensionsOption.Exclude && !fileExtensionIsOneOf(filePath, [Extension.Json, Extension.Mts, Extension.Cts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Cjs])) { - foundFileName = removeFileExtension(getBaseFileName(filePath)); - foundFiles.set(foundFileName, tryGetExtensionFromPath(filePath)); - } - else if ((fileExtensionIsOneOf(filePath, [Extension.Mts, Extension.Cts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Cjs]) || includeExtensionsOption === IncludeExtensionsOption.ModuleSpecifierCompletion) && outputExtension) { - foundFileName = changeExtension(getBaseFileName(filePath), outputExtension); - foundFiles.set(foundFileName, outputExtension); - } - else { - foundFileName = getBaseFileName(filePath); - foundFiles.set(foundFileName, tryGetExtensionFromPath(filePath)); - } + // Enumerate the available files if possible + const files = tryReadDirectory(host, baseDirectory, extensions, /*exclude*/ undefined, /*include*/ ["./*"]); + + if (files) { + /** + * Multiple file entries might map to the same truncated name once we remove extensions + * (happens iff includeExtensionsOption === includeExtensionsOption.Exclude) so we use a set-like data structure. Eg: + * + * both foo.ts and foo.tsx become foo + */ + const foundFiles = new Map(); // maps file to its extension + for (let filePath of files) { + filePath = normalizePath(filePath); + if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { + continue; } - foundFiles.forEach((ext, foundFile) => { - result.push(nameAndKind(foundFile, ScriptElementKind.scriptElement, ext)); - }); + let foundFileName: string; + const outputExtension = moduleSpecifiers.tryGetJSExtensionForFile(filePath, host.getCompilationSettings()); + if (includeExtensionsOption === IncludeExtensionsOption.Exclude && !fileExtensionIsOneOf(filePath, [Extension.Json, Extension.Mts, Extension.Cts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Cjs])) { + foundFileName = removeFileExtension(getBaseFileName(filePath)); + foundFiles.set(foundFileName, tryGetExtensionFromPath(filePath)); + } + else if ((fileExtensionIsOneOf(filePath, [Extension.Mts, Extension.Cts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Cjs]) || includeExtensionsOption === IncludeExtensionsOption.ModuleSpecifierCompletion) && outputExtension) { + foundFileName = changeExtension(getBaseFileName(filePath), outputExtension); + foundFiles.set(foundFileName, outputExtension); + } + else { + foundFileName = getBaseFileName(filePath); + foundFiles.set(foundFileName, tryGetExtensionFromPath(filePath)); + } } - // If possible, get folder completion as well - const directories = tryGetDirectories(host, baseDirectory); + foundFiles.forEach((ext, foundFile) => { + result.push(nameAndKind(foundFile, ScriptElementKind.scriptElement, ext)); + }); + } - if (directories) { - for (const directory of directories) { - const directoryName = getBaseFileName(normalizePath(directory)); - if (directoryName !== "@types") { - result.push(directoryResult(directoryName)); - } + // If possible, get folder completion as well + const directories = tryGetDirectories(host, baseDirectory); + + if (directories) { + for (const directory of directories) { + const directoryName = getBaseFileName(normalizePath(directory)); + if (directoryName !== "@types") { + result.push(directoryResult(directoryName)); } } + } - // check for a version redirect - const packageJsonPath = findPackageJson(baseDirectory, host); - if (packageJsonPath) { - const packageJson = readJson(packageJsonPath, host as { readFile: (filename: string) => string | undefined }); - const typesVersions = (packageJson as any).typesVersions; - if (typeof typesVersions === "object") { - const versionResult = getPackageJsonTypesVersionsPaths(typesVersions); - const versionPaths = versionResult && versionResult.paths; - const rest = absolutePath.slice(ensureTrailingDirectorySeparator(baseDirectory).length); - if (versionPaths) { - addCompletionEntriesFromPaths(result, rest, baseDirectory, extensions, versionPaths, host); - } + // check for a version redirect + const packageJsonPath = findPackageJson(baseDirectory, host); + if (packageJsonPath) { + const packageJson = readJson(packageJsonPath, host as { readFile: (filename: string) => string | undefined }); + const typesVersions = (packageJson as any).typesVersions; + if (typeof typesVersions === "object") { + const versionResult = getPackageJsonTypesVersionsPaths(typesVersions); + const versionPaths = versionResult && versionResult.paths; + const rest = absolutePath.slice(ensureTrailingDirectorySeparator(baseDirectory).length); + if (versionPaths) { + addCompletionEntriesFromPaths(result, rest, baseDirectory, extensions, versionPaths, host); } } - - return result; } - function addCompletionEntriesFromPaths(result: NameAndKind[], fragment: string, baseDirectory: string, fileExtensions: readonly string[], paths: MapLike, host: LanguageServiceHost) { - for (const path in paths) { - if (!hasProperty(paths, path)) continue; - const patterns = paths[path]; - if (patterns) { - for (const { name, kind, extension } of getCompletionsForPathMapping(path, patterns, fragment, baseDirectory, fileExtensions, host)) { - // Path mappings may provide a duplicate way to get to something we've already added, so don't add again. - if (!result.some(entry => entry.name === name)) { - result.push(nameAndKind(name, kind, extension)); - } + return result; +} + +function addCompletionEntriesFromPaths(result: NameAndKind[], fragment: string, baseDirectory: string, fileExtensions: readonly string[], paths: MapLike, host: LanguageServiceHost) { + for (const path in paths) { + if (!hasProperty(paths, path)) continue; + const patterns = paths[path]; + if (patterns) { + for (const { name, kind, extension } of getCompletionsForPathMapping(path, patterns, fragment, baseDirectory, fileExtensions, host)) { + // Path mappings may provide a duplicate way to get to something we've already added, so don't add again. + if (!result.some(entry => entry.name === name)) { + result.push(nameAndKind(name, kind, extension)); } } } } +} - /** - * Check all of the declared modules and those in node modules. Possible sources of modules: - * Modules that are found by the type checker - * Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option) - * Modules from node_modules (i.e. those listed in package.json) - * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions - */ - function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): readonly NameAndKind[] { - const { baseUrl, paths } = compilerOptions; - - const result: NameAndKind[] = []; - - const extensionOptions = getExtensionOptions(compilerOptions); - if (baseUrl) { - const projectDir = compilerOptions.project || host.getCurrentDirectory(); - const absolute = normalizePath(combinePaths(projectDir, baseUrl)); - getCompletionEntriesForDirectoryFragment(fragment, absolute, extensionOptions, host, /*exclude*/ undefined, result); - if (paths) { - addCompletionEntriesFromPaths(result, fragment, absolute, extensionOptions.extensions, paths, host); - } - } +/** + * Check all of the declared modules and those in node modules. Possible sources of modules: + * Modules that are found by the type checker + * Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option) + * Modules from node_modules (i.e. those listed in package.json) + * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions + */ +function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): readonly NameAndKind[] { + const { baseUrl, paths } = compilerOptions; - const fragmentDirectory = getFragmentDirectory(fragment); - for (const ambientName of getAmbientModuleCompletions(fragment, fragmentDirectory, typeChecker)) { - result.push(nameAndKind(ambientName, ScriptElementKind.externalModuleName, /*extension*/ undefined)); + const result: NameAndKind[] = []; + + const extensionOptions = getExtensionOptions(compilerOptions); + if (baseUrl) { + const projectDir = compilerOptions.project || host.getCurrentDirectory(); + const absolute = normalizePath(combinePaths(projectDir, baseUrl)); + getCompletionEntriesForDirectoryFragment(fragment, absolute, extensionOptions, host, /*exclude*/ undefined, result); + if (paths) { + addCompletionEntriesFromPaths(result, fragment, absolute, extensionOptions.extensions, paths, host); } + } + + const fragmentDirectory = getFragmentDirectory(fragment); + for (const ambientName of getAmbientModuleCompletions(fragment, fragmentDirectory, typeChecker)) { + result.push(nameAndKind(ambientName, ScriptElementKind.externalModuleName, /*extension*/ undefined)); + } - getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, fragmentDirectory, extensionOptions, result); + getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, fragmentDirectory, extensionOptions, result); - if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs) { - // If looking for a global package name, don't just include everything in `node_modules` because that includes dependencies' own dependencies. - // (But do if we didn't find anything, e.g. 'package.json' missing.) - let foundGlobal = false; - if (fragmentDirectory === undefined) { - for (const moduleName of enumerateNodeModulesVisibleToScript(host, scriptPath)) { - if (!result.some(entry => entry.name === moduleName)) { - foundGlobal = true; - result.push(nameAndKind(moduleName, ScriptElementKind.externalModuleName, /*extension*/ undefined)); - } + if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs) { + // If looking for a global package name, don't just include everything in `node_modules` because that includes dependencies' own dependencies. + // (But do if we didn't find anything, e.g. 'package.json' missing.) + let foundGlobal = false; + if (fragmentDirectory === undefined) { + for (const moduleName of enumerateNodeModulesVisibleToScript(host, scriptPath)) { + if (!result.some(entry => entry.name === moduleName)) { + foundGlobal = true; + result.push(nameAndKind(moduleName, ScriptElementKind.externalModuleName, /*extension*/ undefined)); } } - if (!foundGlobal) { - forEachAncestorDirectory(scriptPath, ancestor => { - const nodeModules = combinePaths(ancestor, "node_modules"); - if (tryDirectoryExists(host, nodeModules)) { - getCompletionEntriesForDirectoryFragment(fragment, nodeModules, extensionOptions, host, /*exclude*/ undefined, result); - } - }); - } } - - return result; - } - - function getFragmentDirectory(fragment: string): string | undefined { - return containsSlash(fragment) ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined; + if (!foundGlobal) { + forEachAncestorDirectory(scriptPath, ancestor => { + const nodeModules = combinePaths(ancestor, "node_modules"); + if (tryDirectoryExists(host, nodeModules)) { + getCompletionEntriesForDirectoryFragment(fragment, nodeModules, extensionOptions, host, /*exclude*/ undefined, result); + } + }); + } } - function getCompletionsForPathMapping( - path: string, patterns: readonly string[], fragment: string, baseUrl: string, fileExtensions: readonly string[], host: LanguageServiceHost, - ): readonly NameAndKind[] { - if (!endsWith(path, "*")) { - // For a path mapping "foo": ["/x/y/z.ts"], add "foo" itself as a completion. - return !stringContains(path, "*") ? justPathMappingName(path) : emptyArray; - } + return result; +} - const pathPrefix = path.slice(0, path.length - 1); - const remainingFragment = tryRemovePrefix(fragment, pathPrefix); - return remainingFragment === undefined ? justPathMappingName(pathPrefix) : flatMap(patterns, pattern => - getModulesForPathsPattern(remainingFragment, baseUrl, pattern, fileExtensions, host)); +function getFragmentDirectory(fragment: string): string | undefined { + return containsSlash(fragment) ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined; +} - function justPathMappingName(name: string): readonly NameAndKind[] { - return startsWith(name, fragment) ? [directoryResult(name)] : emptyArray; - } +function getCompletionsForPathMapping( + path: string, patterns: readonly string[], fragment: string, baseUrl: string, fileExtensions: readonly string[], host: LanguageServiceHost, +): readonly NameAndKind[] { + if (!endsWith(path, "*")) { + // For a path mapping "foo": ["/x/y/z.ts"], add "foo" itself as a completion. + return !stringContains(path, "*") ? justPathMappingName(path) : emptyArray; } - function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: readonly string[], host: LanguageServiceHost): readonly NameAndKind[] | undefined { - if (!host.readDirectory) { - return undefined; - } + const pathPrefix = path.slice(0, path.length - 1); + const remainingFragment = tryRemovePrefix(fragment, pathPrefix); + return remainingFragment === undefined ? justPathMappingName(pathPrefix) : flatMap(patterns, pattern => + getModulesForPathsPattern(remainingFragment, baseUrl, pattern, fileExtensions, host)); - const parsed = tryParsePattern(pattern); - if (parsed === undefined || isString(parsed)) { - return undefined; - } + function justPathMappingName(name: string): readonly NameAndKind[] { + return startsWith(name, fragment) ? [directoryResult(name)] : emptyArray; + } +} - // The prefix has two effective parts: the directory path and the base component after the filepath that is not a - // full directory component. For example: directory/path/of/prefix/base* - const normalizedPrefix = resolvePath(parsed.prefix); - const normalizedPrefixDirectory = hasTrailingDirectorySeparator(parsed.prefix) ? normalizedPrefix : getDirectoryPath(normalizedPrefix); - const normalizedPrefixBase = hasTrailingDirectorySeparator(parsed.prefix) ? "" : getBaseFileName(normalizedPrefix); +function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: readonly string[], host: LanguageServiceHost): readonly NameAndKind[] | undefined { + if (!host.readDirectory) { + return undefined; + } - const fragmentHasPath = containsSlash(fragment); - const fragmentDirectory = fragmentHasPath ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined; + const parsed = tryParsePattern(pattern); + if (parsed === undefined || isString(parsed)) { + return undefined; + } - // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call - const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + fragmentDirectory) : normalizedPrefixDirectory; + // The prefix has two effective parts: the directory path and the base component after the filepath that is not a + // full directory component. For example: directory/path/of/prefix/base* + const normalizedPrefix = resolvePath(parsed.prefix); + const normalizedPrefixDirectory = hasTrailingDirectorySeparator(parsed.prefix) ? normalizedPrefix : getDirectoryPath(normalizedPrefix); + const normalizedPrefixBase = hasTrailingDirectorySeparator(parsed.prefix) ? "" : getBaseFileName(normalizedPrefix); - const normalizedSuffix = normalizePath(parsed.suffix); - // Need to normalize after combining: If we combinePaths("a", "../b"), we want "b" and not "a/../b". - const baseDirectory = normalizePath(combinePaths(baseUrl, expandedPrefixDirectory)); - const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; + const fragmentHasPath = containsSlash(fragment); + const fragmentDirectory = fragmentHasPath ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined; - // If we have a suffix, then we need to read the directory all the way down. We could create a glob - // that encodes the suffix, but we would have to escape the character "?" which readDirectory - // doesn't support. For now, this is safer but slower - const includeGlob = normalizedSuffix ? "**/*" : "./*"; + // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call + const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + fragmentDirectory) : normalizedPrefixDirectory; - const matches = mapDefined(tryReadDirectory(host, baseDirectory, fileExtensions, /*exclude*/ undefined, [includeGlob]), match => { - const extension = tryGetExtensionFromPath(match); - const name = trimPrefixAndSuffix(match); - return name === undefined ? undefined : nameAndKind(removeFileExtension(name), ScriptElementKind.scriptElement, extension); - }); - const directories = mapDefined(tryGetDirectories(host, baseDirectory).map(d => combinePaths(baseDirectory, d)), dir => { - const name = trimPrefixAndSuffix(dir); - return name === undefined ? undefined : directoryResult(name); - }); - return [...matches, ...directories]; + const normalizedSuffix = normalizePath(parsed.suffix); + // Need to normalize after combining: If we combinePaths("a", "../b"), we want "b" and not "a/../b". + const baseDirectory = normalizePath(combinePaths(baseUrl, expandedPrefixDirectory)); + const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; - function trimPrefixAndSuffix(path: string): string | undefined { - const inner = withoutStartAndEnd(normalizePath(path), completePrefix, normalizedSuffix); - return inner === undefined ? undefined : removeLeadingDirectorySeparator(inner); - } - } + // If we have a suffix, then we need to read the directory all the way down. We could create a glob + // that encodes the suffix, but we would have to escape the character "?" which readDirectory + // doesn't support. For now, this is safer but slower + const includeGlob = normalizedSuffix ? "**/*" : "./*"; - function withoutStartAndEnd(s: string, start: string, end: string): string | undefined { - return startsWith(s, start) && endsWith(s, end) ? s.slice(start.length, s.length - end.length) : undefined; - } + const matches = mapDefined(tryReadDirectory(host, baseDirectory, fileExtensions, /*exclude*/ undefined, [includeGlob]), match => { + const extension = tryGetExtensionFromPath(match); + const name = trimPrefixAndSuffix(match); + return name === undefined ? undefined : nameAndKind(removeFileExtension(name), ScriptElementKind.scriptElement, extension); + }); + const directories = mapDefined(tryGetDirectories(host, baseDirectory).map(d => combinePaths(baseDirectory, d)), dir => { + const name = trimPrefixAndSuffix(dir); + return name === undefined ? undefined : directoryResult(name); + }); + return [...matches, ...directories]; - function removeLeadingDirectorySeparator(path: string): string { - return path[0] === directorySeparator ? path.slice(1) : path; + function trimPrefixAndSuffix(path: string): string | undefined { + const inner = withoutStartAndEnd(normalizePath(path), completePrefix, normalizedSuffix); + return inner === undefined ? undefined : removeLeadingDirectorySeparator(inner); } +} - function getAmbientModuleCompletions(fragment: string, fragmentDirectory: string | undefined, checker: TypeChecker): readonly string[] { - // Get modules that the type checker picked up - const ambientModules = checker.getAmbientModules().map(sym => stripQuotes(sym.name)); - const nonRelativeModuleNames = ambientModules.filter(moduleName => startsWith(moduleName, fragment)); +function withoutStartAndEnd(s: string, start: string, end: string): string | undefined { + return startsWith(s, start) && endsWith(s, end) ? s.slice(start.length, s.length - end.length) : undefined; +} - // Nested modules of the form "module-name/sub" need to be adjusted to only return the string - // after the last '/' that appears in the fragment because that's where the replacement span - // starts - if (fragmentDirectory !== undefined) { - const moduleNameWithSeparator = ensureTrailingDirectorySeparator(fragmentDirectory); - return nonRelativeModuleNames.map(nonRelativeModuleName => removePrefix(nonRelativeModuleName, moduleNameWithSeparator)); - } - return nonRelativeModuleNames; - } +function removeLeadingDirectorySeparator(path: string): string { + return path[0] === directorySeparator ? path.slice(1) : path; +} - function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): readonly PathCompletion[] | undefined { - const token = getTokenAtPosition(sourceFile, position); - const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos); - const range = commentRanges && find(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end); - if (!range) { - return undefined; - } - const text = sourceFile.text.slice(range.pos, position); - const match = tripleSlashDirectiveFragmentRegex.exec(text); - if (!match) { - return undefined; - } +function getAmbientModuleCompletions(fragment: string, fragmentDirectory: string | undefined, checker: TypeChecker): readonly string[] { + // Get modules that the type checker picked up + const ambientModules = checker.getAmbientModules().map(sym => stripQuotes(sym.name)); + const nonRelativeModuleNames = ambientModules.filter(moduleName => startsWith(moduleName, fragment)); - const [, prefix, kind, toComplete] = match; - const scriptPath = getDirectoryPath(sourceFile.path); - const names = kind === "path" ? getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getExtensionOptions(compilerOptions, IncludeExtensionsOption.Include), host, sourceFile.path) - : kind === "types" ? getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, getFragmentDirectory(toComplete), getExtensionOptions(compilerOptions)) - : Debug.fail(); - return addReplacementSpans(toComplete, range.pos + prefix.length, names); + // Nested modules of the form "module-name/sub" need to be adjusted to only return the string + // after the last '/' that appears in the fragment because that's where the replacement span + // starts + if (fragmentDirectory !== undefined) { + const moduleNameWithSeparator = ensureTrailingDirectorySeparator(fragmentDirectory); + return nonRelativeModuleNames.map(nonRelativeModuleName => removePrefix(nonRelativeModuleName, moduleNameWithSeparator)); } + return nonRelativeModuleNames; +} - function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, fragmentDirectory: string | undefined, extensionOptions: ExtensionOptions, result: NameAndKind[] = []): readonly NameAndKind[] { - // Check for typings specified in compiler options - const seen = new Map(); +function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): readonly PathCompletion[] | undefined { + const token = getTokenAtPosition(sourceFile, position); + const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos); + const range = commentRanges && find(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end); + if (!range) { + return undefined; + } + const text = sourceFile.text.slice(range.pos, position); + const match = tripleSlashDirectiveFragmentRegex.exec(text); + if (!match) { + return undefined; + } + + const [, prefix, kind, toComplete] = match; + const scriptPath = getDirectoryPath(sourceFile.path); + const names = kind === "path" ? getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getExtensionOptions(compilerOptions, IncludeExtensionsOption.Include), host, sourceFile.path) + : kind === "types" ? getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, getFragmentDirectory(toComplete), getExtensionOptions(compilerOptions)) + : Debug.fail(); + return addReplacementSpans(toComplete, range.pos + prefix.length, names); +} - const typeRoots = tryAndIgnoreErrors(() => getEffectiveTypeRoots(options, host)) || emptyArray; +function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, fragmentDirectory: string | undefined, extensionOptions: ExtensionOptions, result: NameAndKind[] = []): readonly NameAndKind[] { + // Check for typings specified in compiler options + const seen = new Map(); - for (const root of typeRoots) { - getCompletionEntriesFromDirectories(root); - } + const typeRoots = tryAndIgnoreErrors(() => getEffectiveTypeRoots(options, host)) || emptyArray; - // Also get all @types typings installed in visible node_modules directories - for (const packageJson of findPackageJsons(scriptPath, host)) { - const typesDir = combinePaths(getDirectoryPath(packageJson), "node_modules/@types"); - getCompletionEntriesFromDirectories(typesDir); - } + for (const root of typeRoots) { + getCompletionEntriesFromDirectories(root); + } + + // Also get all @types typings installed in visible node_modules directories + for (const packageJson of findPackageJsons(scriptPath, host)) { + const typesDir = combinePaths(getDirectoryPath(packageJson), "node_modules/@types"); + getCompletionEntriesFromDirectories(typesDir); + } - return result; + return result; - function getCompletionEntriesFromDirectories(directory: string): void { - if (!tryDirectoryExists(host, directory)) return; + function getCompletionEntriesFromDirectories(directory: string): void { + if (!tryDirectoryExists(host, directory)) return; - for (const typeDirectoryName of tryGetDirectories(host, directory)) { - const packageName = unmangleScopedPackageName(typeDirectoryName); - if (options.types && !contains(options.types, packageName)) continue; + for (const typeDirectoryName of tryGetDirectories(host, directory)) { + const packageName = unmangleScopedPackageName(typeDirectoryName); + if (options.types && !contains(options.types, packageName)) continue; - if (fragmentDirectory === undefined) { - if (!seen.has(packageName)) { - result.push(nameAndKind(packageName, ScriptElementKind.externalModuleName, /*extension*/ undefined)); - seen.set(packageName, true); - } + if (fragmentDirectory === undefined) { + if (!seen.has(packageName)) { + result.push(nameAndKind(packageName, ScriptElementKind.externalModuleName, /*extension*/ undefined)); + seen.set(packageName, true); } - else { - const baseDirectory = combinePaths(directory, typeDirectoryName); - const remainingFragment = tryRemoveDirectoryPrefix(fragmentDirectory, packageName, hostGetCanonicalFileName(host)); - if (remainingFragment !== undefined) { - getCompletionEntriesForDirectoryFragment(remainingFragment, baseDirectory, extensionOptions, host, /*exclude*/ undefined, result); - } + } + else { + const baseDirectory = combinePaths(directory, typeDirectoryName); + const remainingFragment = tryRemoveDirectoryPrefix(fragmentDirectory, packageName, hostGetCanonicalFileName(host)); + if (remainingFragment !== undefined) { + getCompletionEntriesForDirectoryFragment(remainingFragment, baseDirectory, extensionOptions, host, /*exclude*/ undefined, result); } } } } +} - function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string): readonly string[] { - if (!host.readFile || !host.fileExists) return emptyArray; - - const result: string[] = []; - for (const packageJson of findPackageJsons(scriptPath, host)) { - const contents = readJson(packageJson, host as { readFile: (filename: string) => string | undefined }); // Cast to assert that readFile is defined - // Provide completions for all non @types dependencies - for (const key of nodeModulesDependencyKeys) { - const dependencies: object | undefined = (contents as any)[key]; - if (!dependencies) continue; - for (const dep in dependencies) { - if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types/")) { - result.push(dep); - } +function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string): readonly string[] { + if (!host.readFile || !host.fileExists) return emptyArray; + + const result: string[] = []; + for (const packageJson of findPackageJsons(scriptPath, host)) { + const contents = readJson(packageJson, host as { readFile: (filename: string) => string | undefined }); // Cast to assert that readFile is defined + // Provide completions for all non @types dependencies + for (const key of nodeModulesDependencyKeys) { + const dependencies: object | undefined = (contents as any)[key]; + if (!dependencies) continue; + for (const dep in dependencies) { + if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types/")) { + result.push(dep); } } } - return result; } + return result; +} - // Replace everything after the last directory separator that appears - function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan | undefined { - const index = Math.max(text.lastIndexOf(directorySeparator), text.lastIndexOf(altDirectorySeparator)); - const offset = index !== -1 ? index + 1 : 0; - // If the range is an identifier, span is unnecessary. - const length = text.length - offset; - return length === 0 || isIdentifierText(text.substr(offset, length), ScriptTarget.ESNext) ? undefined : createTextSpan(textStart + offset, length); - } +// Replace everything after the last directory separator that appears +function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan | undefined { + const index = Math.max(text.lastIndexOf(directorySeparator), text.lastIndexOf(altDirectorySeparator)); + const offset = index !== -1 ? index + 1 : 0; + // If the range is an identifier, span is unnecessary. + const length = text.length - offset; + return length === 0 || isIdentifierText(text.substr(offset, length), ScriptTarget.ESNext) ? undefined : createTextSpan(textStart + offset, length); +} - // Returns true if the path is explicitly relative to the script (i.e. relative to . or ..) - function isPathRelativeToScript(path: string) { - if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) { - const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1; - const slashCharCode = path.charCodeAt(slashIndex); - return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash; - } - return false; +// Returns true if the path is explicitly relative to the script (i.e. relative to . or ..) +function isPathRelativeToScript(path: string) { + if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) { + const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1; + const slashCharCode = path.charCodeAt(slashIndex); + return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash; } + return false; +} - /** - * Matches a triple slash reference directive with an incomplete string literal for its path. Used - * to determine if the caret is currently within the string literal and capture the literal fragment - * for completions. - * For example, this matches - * - * /// (); - - export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): DiagnosticWithLocation[] { - program.getSemanticDiagnostics(sourceFile, cancellationToken); - const diags: DiagnosticWithLocation[] = []; - const checker = program.getTypeChecker(); - const isCommonJSFile = sourceFile.impliedNodeFormat === ModuleKind.CommonJS || fileExtensionIsOneOf(sourceFile.fileName, [Extension.Cts, Extension.Cjs]) ; - - if (!isCommonJSFile && - sourceFile.commonJsModuleIndicator && - (programContainsEsModules(program) || compilerOptionsIndicateEsModules(program.getCompilerOptions())) && - containsTopLevelCommonjs(sourceFile)) { - diags.push(createDiagnosticForNode(getErrorNodeFromCommonJsIndicator(sourceFile.commonJsModuleIndicator), Diagnostics.File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module)); +const visitedNestedConvertibleFunctions = new Map(); + +export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): DiagnosticWithLocation[] { + program.getSemanticDiagnostics(sourceFile, cancellationToken); + const diags: DiagnosticWithLocation[] = []; + const checker = program.getTypeChecker(); + const isCommonJSFile = sourceFile.impliedNodeFormat === ModuleKind.CommonJS || fileExtensionIsOneOf(sourceFile.fileName, [Extension.Cts, Extension.Cjs]) ; + + if (!isCommonJSFile && + sourceFile.commonJsModuleIndicator && + (programContainsEsModules(program) || compilerOptionsIndicateEsModules(program.getCompilerOptions())) && + containsTopLevelCommonjs(sourceFile)) { + diags.push(createDiagnosticForNode(getErrorNodeFromCommonJsIndicator(sourceFile.commonJsModuleIndicator), Diagnostics.File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module)); + } + + const isJsFile = isSourceFileJS(sourceFile); + + visitedNestedConvertibleFunctions.clear(); + check(sourceFile); + + if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) { + for (const moduleSpecifier of sourceFile.imports) { + const importNode = importFromModuleSpecifier(moduleSpecifier); + const name = importNameForConvertToDefaultImport(importNode); + if (!name) continue; + const module = getResolvedModule(sourceFile, moduleSpecifier.text, getModeForUsageLocation(sourceFile, moduleSpecifier)); + const resolvedFile = module && program.getSourceFile(module.resolvedFileName); + if (resolvedFile && resolvedFile.externalModuleIndicator && isExportAssignment(resolvedFile.externalModuleIndicator) && resolvedFile.externalModuleIndicator.isExportEquals) { + diags.push(createDiagnosticForNode(name, Diagnostics.Import_may_be_converted_to_a_default_import)); + } } + } - const isJsFile = isSourceFileJS(sourceFile); - - visitedNestedConvertibleFunctions.clear(); - check(sourceFile); + addRange(diags, sourceFile.bindSuggestionDiagnostics); + addRange(diags, program.getSuggestionDiagnostics(sourceFile, cancellationToken)); + return diags.sort((d1, d2) => d1.start - d2.start); - if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) { - for (const moduleSpecifier of sourceFile.imports) { - const importNode = importFromModuleSpecifier(moduleSpecifier); - const name = importNameForConvertToDefaultImport(importNode); - if (!name) continue; - const module = getResolvedModule(sourceFile, moduleSpecifier.text, getModeForUsageLocation(sourceFile, moduleSpecifier)); - const resolvedFile = module && program.getSourceFile(module.resolvedFileName); - if (resolvedFile && resolvedFile.externalModuleIndicator && isExportAssignment(resolvedFile.externalModuleIndicator) && resolvedFile.externalModuleIndicator.isExportEquals) { - diags.push(createDiagnosticForNode(name, Diagnostics.Import_may_be_converted_to_a_default_import)); - } + function check(node: Node) { + if (isJsFile) { + if (canBeConvertedToClass(node, checker)) { + diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration)); } } - - addRange(diags, sourceFile.bindSuggestionDiagnostics); - addRange(diags, program.getSuggestionDiagnostics(sourceFile, cancellationToken)); - return diags.sort((d1, d2) => d1.start - d2.start); - - function check(node: Node) { - if (isJsFile) { - if (canBeConvertedToClass(node, checker)) { - diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration)); + else { + if (isVariableStatement(node) && + node.parent === sourceFile && + node.declarationList.flags & NodeFlags.Const && + node.declarationList.declarations.length === 1) { + const init = node.declarationList.declarations[0].initializer; + if (init && isRequireCall(init, /*checkArgumentIsStringLiteralLike*/ true)) { + diags.push(createDiagnosticForNode(init, Diagnostics.require_call_may_be_converted_to_an_import)); } } - else { - if (isVariableStatement(node) && - node.parent === sourceFile && - node.declarationList.flags & NodeFlags.Const && - node.declarationList.declarations.length === 1) { - const init = node.declarationList.declarations[0].initializer; - if (init && isRequireCall(init, /*checkArgumentIsStringLiteralLike*/ true)) { - diags.push(createDiagnosticForNode(init, Diagnostics.require_call_may_be_converted_to_an_import)); - } - } - if (codefix.parameterShouldGetTypeFromJSDoc(node)) { - diags.push(createDiagnosticForNode(node.name || node, Diagnostics.JSDoc_types_may_be_moved_to_TypeScript_types)); - } + if (codefix.parameterShouldGetTypeFromJSDoc(node)) { + diags.push(createDiagnosticForNode(node.name || node, Diagnostics.JSDoc_types_may_be_moved_to_TypeScript_types)); } + } - if (canBeConvertedToAsync(node)) { - addConvertToAsyncFunctionDiagnostics(node, checker, diags); - } - node.forEachChild(check); + if (canBeConvertedToAsync(node)) { + addConvertToAsyncFunctionDiagnostics(node, checker, diags); } + node.forEachChild(check); } +} - // convertToEsModule only works on top-level, so don't trigger it if commonjs code only appears in nested scopes. - function containsTopLevelCommonjs(sourceFile: SourceFile): boolean { - return sourceFile.statements.some(statement => { - switch (statement.kind) { - case SyntaxKind.VariableStatement: - return (statement as VariableStatement).declarationList.declarations.some(decl => - !!decl.initializer && isRequireCall(propertyAccessLeftHandSide(decl.initializer), /*checkArgumentIsStringLiteralLike*/ true)); - case SyntaxKind.ExpressionStatement: { - const { expression } = statement as ExpressionStatement; - if (!isBinaryExpression(expression)) return isRequireCall(expression, /*checkArgumentIsStringLiteralLike*/ true); - const kind = getAssignmentDeclarationKind(expression); - return kind === AssignmentDeclarationKind.ExportsProperty || kind === AssignmentDeclarationKind.ModuleExports; - } - default: - return false; +// convertToEsModule only works on top-level, so don't trigger it if commonjs code only appears in nested scopes. +function containsTopLevelCommonjs(sourceFile: SourceFile): boolean { + return sourceFile.statements.some(statement => { + switch (statement.kind) { + case SyntaxKind.VariableStatement: + return (statement as VariableStatement).declarationList.declarations.some(decl => + !!decl.initializer && isRequireCall(propertyAccessLeftHandSide(decl.initializer), /*checkArgumentIsStringLiteralLike*/ true)); + case SyntaxKind.ExpressionStatement: { + const { expression } = statement as ExpressionStatement; + if (!isBinaryExpression(expression)) return isRequireCall(expression, /*checkArgumentIsStringLiteralLike*/ true); + const kind = getAssignmentDeclarationKind(expression); + return kind === AssignmentDeclarationKind.ExportsProperty || kind === AssignmentDeclarationKind.ModuleExports; } - }); - } - - function propertyAccessLeftHandSide(node: Expression): Expression { - return isPropertyAccessExpression(node) ? propertyAccessLeftHandSide(node.expression) : node; - } - - function importNameForConvertToDefaultImport(node: AnyValidImportOrReExport): Identifier | undefined { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - const { importClause, moduleSpecifier } = node; - return importClause && !importClause.name && importClause.namedBindings && importClause.namedBindings.kind === SyntaxKind.NamespaceImport && isStringLiteral(moduleSpecifier) - ? importClause.namedBindings.name - : undefined; - case SyntaxKind.ImportEqualsDeclaration: - return node.name; default: - return undefined; + return false; } - } + }); +} - function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: Push): void { - // need to check function before checking map so that deeper levels of nested callbacks are checked - if (isConvertibleFunction(node, checker) && !visitedNestedConvertibleFunctions.has(getKeyFromNode(node))) { - diags.push(createDiagnosticForNode( - !node.name && isVariableDeclaration(node.parent) && isIdentifier(node.parent.name) ? node.parent.name : node, - Diagnostics.This_may_be_converted_to_an_async_function)); - } - } +function propertyAccessLeftHandSide(node: Expression): Expression { + return isPropertyAccessExpression(node) ? propertyAccessLeftHandSide(node.expression) : node; +} - function isConvertibleFunction(node: FunctionLikeDeclaration, checker: TypeChecker) { - return !isAsyncFunction(node) && - node.body && - isBlock(node.body) && - hasReturnStatementWithPromiseHandler(node.body, checker) && - returnsPromise(node, checker); +function importNameForConvertToDefaultImport(node: AnyValidImportOrReExport): Identifier | undefined { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + const { importClause, moduleSpecifier } = node; + return importClause && !importClause.name && importClause.namedBindings && importClause.namedBindings.kind === SyntaxKind.NamespaceImport && isStringLiteral(moduleSpecifier) + ? importClause.namedBindings.name + : undefined; + case SyntaxKind.ImportEqualsDeclaration: + return node.name; + default: + return undefined; } +} - export function returnsPromise(node: FunctionLikeDeclaration, checker: TypeChecker): boolean { - const signature = checker.getSignatureFromDeclaration(node); - const returnType = signature ? checker.getReturnTypeOfSignature(signature) : undefined; - return !!returnType && !!checker.getPromisedTypeOfPromise(returnType); +function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: Push): void { + // need to check function before checking map so that deeper levels of nested callbacks are checked + if (isConvertibleFunction(node, checker) && !visitedNestedConvertibleFunctions.has(getKeyFromNode(node))) { + diags.push(createDiagnosticForNode( + !node.name && isVariableDeclaration(node.parent) && isIdentifier(node.parent.name) ? node.parent.name : node, + Diagnostics.This_may_be_converted_to_an_async_function)); } +} - function getErrorNodeFromCommonJsIndicator(commonJsModuleIndicator: Node): Node { - return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; - } +function isConvertibleFunction(node: FunctionLikeDeclaration, checker: TypeChecker) { + return !isAsyncFunction(node) && + node.body && + isBlock(node.body) && + hasReturnStatementWithPromiseHandler(node.body, checker) && + returnsPromise(node, checker); +} - function hasReturnStatementWithPromiseHandler(body: Block, checker: TypeChecker): boolean { - return !!forEachReturnStatement(body, statement => isReturnStatementWithFixablePromiseHandler(statement, checker)); - } +export function returnsPromise(node: FunctionLikeDeclaration, checker: TypeChecker): boolean { + const signature = checker.getSignatureFromDeclaration(node); + const returnType = signature ? checker.getReturnTypeOfSignature(signature) : undefined; + return !!returnType && !!checker.getPromisedTypeOfPromise(returnType); +} - export function isReturnStatementWithFixablePromiseHandler(node: Node, checker: TypeChecker): node is ReturnStatement & { expression: CallExpression } { - return isReturnStatement(node) && !!node.expression && isFixablePromiseHandler(node.expression, checker); - } +function getErrorNodeFromCommonJsIndicator(commonJsModuleIndicator: Node): Node { + return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; +} - // Should be kept up to date with transformExpression in convertToAsyncFunction.ts - export function isFixablePromiseHandler(node: Node, checker: TypeChecker): boolean { - // ensure outermost call exists and is a promise handler - if (!isPromiseHandler(node) || !hasSupportedNumberOfArguments(node) || !node.arguments.every(arg => isFixablePromiseArgument(arg, checker))) { - return false; - } +function hasReturnStatementWithPromiseHandler(body: Block, checker: TypeChecker): boolean { + return !!forEachReturnStatement(body, statement => isReturnStatementWithFixablePromiseHandler(statement, checker)); +} - // ensure all chained calls are valid - let currentNode = node.expression.expression; - while (isPromiseHandler(currentNode) || isPropertyAccessExpression(currentNode)) { - if (isCallExpression(currentNode)) { - if (!hasSupportedNumberOfArguments(currentNode) || !currentNode.arguments.every(arg => isFixablePromiseArgument(arg, checker))) { - return false; - } - currentNode = currentNode.expression.expression; - } - else { - currentNode = currentNode.expression; +export function isReturnStatementWithFixablePromiseHandler(node: Node, checker: TypeChecker): node is ReturnStatement & { expression: CallExpression } { + return isReturnStatement(node) && !!node.expression && isFixablePromiseHandler(node.expression, checker); +} + +// Should be kept up to date with transformExpression in convertToAsyncFunction.ts +export function isFixablePromiseHandler(node: Node, checker: TypeChecker): boolean { + // ensure outermost call exists and is a promise handler + if (!isPromiseHandler(node) || !hasSupportedNumberOfArguments(node) || !node.arguments.every(arg => isFixablePromiseArgument(arg, checker))) { + return false; + } + + // ensure all chained calls are valid + let currentNode = node.expression.expression; + while (isPromiseHandler(currentNode) || isPropertyAccessExpression(currentNode)) { + if (isCallExpression(currentNode)) { + if (!hasSupportedNumberOfArguments(currentNode) || !currentNode.arguments.every(arg => isFixablePromiseArgument(arg, checker))) { + return false; } + currentNode = currentNode.expression.expression; + } + else { + currentNode = currentNode.expression; } - return true; } + return true; +} - function isPromiseHandler(node: Node): node is CallExpression & { readonly expression: PropertyAccessExpression } { - return isCallExpression(node) && ( - hasPropertyAccessExpressionWithName(node, "then") || - hasPropertyAccessExpressionWithName(node, "catch") || - hasPropertyAccessExpressionWithName(node, "finally")); - } +function isPromiseHandler(node: Node): node is CallExpression & { readonly expression: PropertyAccessExpression } { + return isCallExpression(node) && ( + hasPropertyAccessExpressionWithName(node, "then") || + hasPropertyAccessExpressionWithName(node, "catch") || + hasPropertyAccessExpressionWithName(node, "finally")); +} - function hasSupportedNumberOfArguments(node: CallExpression & { readonly expression: PropertyAccessExpression }) { - const name = node.expression.name.text; - const maxArguments = name === "then" ? 2 : name === "catch" ? 1 : name === "finally" ? 1 : 0; - if (node.arguments.length > maxArguments) return false; - if (node.arguments.length < maxArguments) return true; - return maxArguments === 1 || some(node.arguments, arg => { - return arg.kind === SyntaxKind.NullKeyword || isIdentifier(arg) && arg.text === "undefined"; - }); - } +function hasSupportedNumberOfArguments(node: CallExpression & { readonly expression: PropertyAccessExpression }) { + const name = node.expression.name.text; + const maxArguments = name === "then" ? 2 : name === "catch" ? 1 : name === "finally" ? 1 : 0; + if (node.arguments.length > maxArguments) return false; + if (node.arguments.length < maxArguments) return true; + return maxArguments === 1 || some(node.arguments, arg => { + return arg.kind === SyntaxKind.NullKeyword || isIdentifier(arg) && arg.text === "undefined"; + }); +} - // should be kept up to date with getTransformationBody in convertToAsyncFunction.ts - function isFixablePromiseArgument(arg: Expression, checker: TypeChecker): boolean { - switch (arg.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - const functionFlags = getFunctionFlags(arg as FunctionDeclaration | FunctionExpression); - if (functionFlags & FunctionFlags.Generator) { - return false; - } - // falls through - case SyntaxKind.ArrowFunction: - visitedNestedConvertibleFunctions.set(getKeyFromNode(arg as FunctionLikeDeclaration), true); - // falls through - case SyntaxKind.NullKeyword: - return true; - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: { - const symbol = checker.getSymbolAtLocation(arg); - if (!symbol) { - return false; - } - return checker.isUndefinedSymbol(symbol) || - some(skipAlias(symbol, checker).declarations, d => isFunctionLike(d) || hasInitializer(d) && !!d.initializer && isFunctionLike(d.initializer)); +// should be kept up to date with getTransformationBody in convertToAsyncFunction.ts +function isFixablePromiseArgument(arg: Expression, checker: TypeChecker): boolean { + switch (arg.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + const functionFlags = getFunctionFlags(arg as FunctionDeclaration | FunctionExpression); + if (functionFlags & FunctionFlags.Generator) { + return false; } - default: + // falls through + case SyntaxKind.ArrowFunction: + visitedNestedConvertibleFunctions.set(getKeyFromNode(arg as FunctionLikeDeclaration), true); + // falls through + case SyntaxKind.NullKeyword: + return true; + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: { + const symbol = checker.getSymbolAtLocation(arg); + if (!symbol) { return false; + } + return checker.isUndefinedSymbol(symbol) || + some(skipAlias(symbol, checker).declarations, d => isFunctionLike(d) || hasInitializer(d) && !!d.initializer && isFunctionLike(d.initializer)); } + default: + return false; } +} - function getKeyFromNode(exp: FunctionLikeDeclaration) { - return `${exp.pos.toString()}:${exp.end.toString()}`; - } - - function canBeConvertedToClass(node: Node, checker: TypeChecker): boolean { - if (node.kind === SyntaxKind.FunctionExpression) { - if (isVariableDeclaration(node.parent) && node.symbol.members?.size) { - return true; - } +function getKeyFromNode(exp: FunctionLikeDeclaration) { + return `${exp.pos.toString()}:${exp.end.toString()}`; +} - const symbol = checker.getSymbolOfExpando(node, /*allowDeclaration*/ false); - return !!(symbol && (symbol.exports?.size || symbol.members?.size)); +function canBeConvertedToClass(node: Node, checker: TypeChecker): boolean { + if (node.kind === SyntaxKind.FunctionExpression) { + if (isVariableDeclaration(node.parent) && node.symbol.members?.size) { + return true; } - if (node.kind === SyntaxKind.FunctionDeclaration) { - return !!node.symbol.members?.size; - } + const symbol = checker.getSymbolOfExpando(node, /*allowDeclaration*/ false); + return !!(symbol && (symbol.exports?.size || symbol.members?.size)); + } - return false; + if (node.kind === SyntaxKind.FunctionDeclaration) { + return !!node.symbol.members?.size; } - export function canBeConvertedToAsync(node: Node): node is FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return true; - default: - return false; - } + return false; +} + +export function canBeConvertedToAsync(node: Node): node is FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return true; + default: + return false; } } +} diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index 83508dbf9363d..15832a5baaa63 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -1,220 +1,249 @@ /* @internal */ namespace ts.SymbolDisplay { - const symbolDisplayNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; - - // TODO(drosen): use contextual SemanticMeaning. - export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { - const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location); - if (result !== ScriptElementKind.unknown) { - return result; - } - - const flags = getCombinedLocalAndExportSymbolFlags(symbol); - if (flags & SymbolFlags.Class) { - return getDeclarationOfKind(symbol, SyntaxKind.ClassExpression) ? - ScriptElementKind.localClassElement : ScriptElementKind.classElement; - } - if (flags & SymbolFlags.Enum) return ScriptElementKind.enumElement; - if (flags & SymbolFlags.TypeAlias) return ScriptElementKind.typeElement; - if (flags & SymbolFlags.Interface) return ScriptElementKind.interfaceElement; - if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement; - if (flags & SymbolFlags.EnumMember) return ScriptElementKind.enumMemberElement; - if (flags & SymbolFlags.Alias) return ScriptElementKind.alias; - if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement; +const symbolDisplayNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; +// TODO(drosen): use contextual SemanticMeaning. +export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { + const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location); + if (result !== ScriptElementKind.unknown) { return result; } - function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { - const roots = typeChecker.getRootSymbols(symbol); - // If this is a method from a mapped type, leave as a method so long as it still has a call signature. - if (roots.length === 1 - && first(roots).flags & SymbolFlags.Method - // Ensure the mapped version is still a method, as opposed to `{ [K in keyof I]: number }`. - && typeChecker.getTypeOfSymbolAtLocation(symbol, location).getNonNullableType().getCallSignatures().length !== 0) { - return ScriptElementKind.memberFunctionElement; - } + const flags = getCombinedLocalAndExportSymbolFlags(symbol); + if (flags & SymbolFlags.Class) { + return getDeclarationOfKind(symbol, SyntaxKind.ClassExpression) ? + ScriptElementKind.localClassElement : ScriptElementKind.classElement; + } + if (flags & SymbolFlags.Enum) return ScriptElementKind.enumElement; + if (flags & SymbolFlags.TypeAlias) return ScriptElementKind.typeElement; + if (flags & SymbolFlags.Interface) return ScriptElementKind.interfaceElement; + if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement; + if (flags & SymbolFlags.EnumMember) return ScriptElementKind.enumMemberElement; + if (flags & SymbolFlags.Alias) return ScriptElementKind.alias; + if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement; + + return result; +} + +function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { + const roots = typeChecker.getRootSymbols(symbol); + // If this is a method from a mapped type, leave as a method so long as it still has a call signature. + if (roots.length === 1 + && first(roots).flags & SymbolFlags.Method + // Ensure the mapped version is still a method, as opposed to `{ [K in keyof I]: number }`. + && typeChecker.getTypeOfSymbolAtLocation(symbol, location).getNonNullableType().getCallSignatures().length !== 0) { + return ScriptElementKind.memberFunctionElement; + } - if (typeChecker.isUndefinedSymbol(symbol)) { - return ScriptElementKind.variableElement; + if (typeChecker.isUndefinedSymbol(symbol)) { + return ScriptElementKind.variableElement; + } + if (typeChecker.isArgumentsSymbol(symbol)) { + return ScriptElementKind.localVariableElement; + } + if (location.kind === SyntaxKind.ThisKeyword && isExpression(location)) { + return ScriptElementKind.parameterElement; + } + const flags = getCombinedLocalAndExportSymbolFlags(symbol); + if (flags & SymbolFlags.Variable) { + if (isFirstDeclarationOfSymbolParameter(symbol)) { + return ScriptElementKind.parameterElement; } - if (typeChecker.isArgumentsSymbol(symbol)) { - return ScriptElementKind.localVariableElement; + else if (symbol.valueDeclaration && isVarConst(symbol.valueDeclaration as VariableDeclaration)) { + return ScriptElementKind.constElement; } - if (location.kind === SyntaxKind.ThisKeyword && isExpression(location)) { - return ScriptElementKind.parameterElement; + else if (forEach(symbol.declarations, isLet)) { + return ScriptElementKind.letElement; } - const flags = getCombinedLocalAndExportSymbolFlags(symbol); - if (flags & SymbolFlags.Variable) { - if (isFirstDeclarationOfSymbolParameter(symbol)) { - return ScriptElementKind.parameterElement; - } - else if (symbol.valueDeclaration && isVarConst(symbol.valueDeclaration as VariableDeclaration)) { - return ScriptElementKind.constElement; - } - else if (forEach(symbol.declarations, isLet)) { - return ScriptElementKind.letElement; - } - return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localVariableElement : ScriptElementKind.variableElement; - } - if (flags & SymbolFlags.Function) return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localFunctionElement : ScriptElementKind.functionElement; - if (flags & SymbolFlags.GetAccessor) return ScriptElementKind.memberGetAccessorElement; - if (flags & SymbolFlags.SetAccessor) return ScriptElementKind.memberSetAccessorElement; - if (flags & SymbolFlags.Method) return ScriptElementKind.memberFunctionElement; - if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement; - - if (flags & SymbolFlags.Property) { - if (flags & SymbolFlags.Transient && (symbol as TransientSymbol).checkFlags & CheckFlags.Synthetic) { - // If union property is result of union of non method (property/accessors/variables), it is labeled as property - const unionPropertyKind = forEach(typeChecker.getRootSymbols(symbol), rootSymbol => { - const rootSymbolFlags = rootSymbol.getFlags(); - if (rootSymbolFlags & (SymbolFlags.PropertyOrAccessor | SymbolFlags.Variable)) { - return ScriptElementKind.memberVariableElement; - } - }); - if (!unionPropertyKind) { - // If this was union of all methods, - // make sure it has call signatures before we can label it as method - const typeOfUnionProperty = typeChecker.getTypeOfSymbolAtLocation(symbol, location); - if (typeOfUnionProperty.getCallSignatures().length) { - return ScriptElementKind.memberFunctionElement; - } + return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localVariableElement : ScriptElementKind.variableElement; + } + if (flags & SymbolFlags.Function) return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localFunctionElement : ScriptElementKind.functionElement; + if (flags & SymbolFlags.GetAccessor) return ScriptElementKind.memberGetAccessorElement; + if (flags & SymbolFlags.SetAccessor) return ScriptElementKind.memberSetAccessorElement; + if (flags & SymbolFlags.Method) return ScriptElementKind.memberFunctionElement; + if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement; + + if (flags & SymbolFlags.Property) { + if (flags & SymbolFlags.Transient && (symbol as TransientSymbol).checkFlags & CheckFlags.Synthetic) { + // If union property is result of union of non method (property/accessors/variables), it is labeled as property + const unionPropertyKind = forEach(typeChecker.getRootSymbols(symbol), rootSymbol => { + const rootSymbolFlags = rootSymbol.getFlags(); + if (rootSymbolFlags & (SymbolFlags.PropertyOrAccessor | SymbolFlags.Variable)) { return ScriptElementKind.memberVariableElement; } - return unionPropertyKind; - } - // If we requested completions after `x.` at the top-level, we may be at a source file location. - switch (location.parent && location.parent.kind) { - // If we've typed a character of the attribute name, will be 'JsxAttribute', else will be 'JsxOpeningElement'. - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxElement: - case SyntaxKind.JsxSelfClosingElement: - return location.kind === SyntaxKind.Identifier ? ScriptElementKind.memberVariableElement : ScriptElementKind.jsxAttribute; - case SyntaxKind.JsxAttribute: - return ScriptElementKind.jsxAttribute; - default: - return ScriptElementKind.memberVariableElement; - } + }); + if (!unionPropertyKind) { + // If this was union of all methods, + // make sure it has call signatures before we can label it as method + const typeOfUnionProperty = typeChecker.getTypeOfSymbolAtLocation(symbol, location); + if (typeOfUnionProperty.getCallSignatures().length) { + return ScriptElementKind.memberFunctionElement; + } + return ScriptElementKind.memberVariableElement; + } + return unionPropertyKind; + } + // If we requested completions after `x.` at the top-level, we may be at a source file location. + switch (location.parent && location.parent.kind) { + // If we've typed a character of the attribute name, will be 'JsxAttribute', else will be 'JsxOpeningElement'. + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + return location.kind === SyntaxKind.Identifier ? ScriptElementKind.memberVariableElement : ScriptElementKind.jsxAttribute; + case SyntaxKind.JsxAttribute: + return ScriptElementKind.jsxAttribute; + default: + return ScriptElementKind.memberVariableElement; } - - return ScriptElementKind.unknown; } - function getNormalizedSymbolModifiers(symbol: Symbol) { - if (symbol.declarations && symbol.declarations.length) { - const [declaration, ...declarations] = symbol.declarations; - // omit deprecated flag if some declarations are not deprecated - const excludeFlags = length(declarations) && isDeprecatedDeclaration(declaration) && some(declarations, d => !isDeprecatedDeclaration(d)) - ? ModifierFlags.Deprecated - : ModifierFlags.None; - const modifiers = getNodeModifiers(declaration, excludeFlags); - if (modifiers) { - return modifiers.split(","); - } + return ScriptElementKind.unknown; +} + +function getNormalizedSymbolModifiers(symbol: Symbol) { + if (symbol.declarations && symbol.declarations.length) { + const [declaration, ...declarations] = symbol.declarations; + // omit deprecated flag if some declarations are not deprecated + const excludeFlags = length(declarations) && isDeprecatedDeclaration(declaration) && some(declarations, d => !isDeprecatedDeclaration(d)) + ? ModifierFlags.Deprecated + : ModifierFlags.None; + const modifiers = getNodeModifiers(declaration, excludeFlags); + if (modifiers) { + return modifiers.split(","); } - return []; + } + return []; +} + +export function getSymbolModifiers(typeChecker: TypeChecker, symbol: Symbol): string { + if (!symbol) { + return ScriptElementKindModifier.none; } - export function getSymbolModifiers(typeChecker: TypeChecker, symbol: Symbol): string { - if (!symbol) { - return ScriptElementKindModifier.none; + const modifiers = new Set(getNormalizedSymbolModifiers(symbol)); + if (symbol.flags & SymbolFlags.Alias) { + const resolvedSymbol = typeChecker.getAliasedSymbol(symbol); + if (resolvedSymbol !== symbol) { + forEach(getNormalizedSymbolModifiers(resolvedSymbol), modifier => { + modifiers.add(modifier); + }); } + } + if (symbol.flags & SymbolFlags.Optional) { + modifiers.add(ScriptElementKindModifier.optionalModifier); + } + return modifiers.size > 0 ? arrayFrom(modifiers.values()).join(",") : ScriptElementKindModifier.none; +} - const modifiers = new Set(getNormalizedSymbolModifiers(symbol)); - if (symbol.flags & SymbolFlags.Alias) { - const resolvedSymbol = typeChecker.getAliasedSymbol(symbol); - if (resolvedSymbol !== symbol) { - forEach(getNormalizedSymbolModifiers(resolvedSymbol), modifier => { - modifiers.add(modifier); - }); - } +interface SymbolDisplayPartsDocumentationAndSymbolKind { + displayParts: SymbolDisplayPart[]; + documentation: SymbolDisplayPart[]; + symbolKind: ScriptElementKind; + tags: JSDocTagInfo[] | undefined; +} + +// TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location +export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node | undefined, + location: Node, semanticMeaning = getMeaningFromLocation(location), alias?: Symbol): SymbolDisplayPartsDocumentationAndSymbolKind { + const displayParts: SymbolDisplayPart[] = []; + let documentation: SymbolDisplayPart[] = []; + let tags: JSDocTagInfo[] = []; + const symbolFlags = getCombinedLocalAndExportSymbolFlags(symbol); + let symbolKind = semanticMeaning & SemanticMeaning.Value ? getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location) : ScriptElementKind.unknown; + let hasAddedSymbolInfo = false; + const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isInExpressionContext(location); + let type: Type | undefined; + let printer: Printer; + let documentationFromAlias: SymbolDisplayPart[] | undefined; + let tagsFromAlias: JSDocTagInfo[] | undefined; + let hasMultipleSignatures = false; + + if (location.kind === SyntaxKind.ThisKeyword && !isThisExpression) { + return { displayParts: [keywordPart(SyntaxKind.ThisKeyword)], documentation: [], symbolKind: ScriptElementKind.primitiveType, tags: undefined }; + } + + // Class at constructor site need to be shown as constructor apart from property,method, vars + if (symbolKind !== ScriptElementKind.unknown || symbolFlags & SymbolFlags.Class || symbolFlags & SymbolFlags.Alias) { + // If it is accessor they are allowed only if location is at name of the accessor + if (symbolKind === ScriptElementKind.memberGetAccessorElement || symbolKind === ScriptElementKind.memberSetAccessorElement) { + symbolKind = ScriptElementKind.memberVariableElement; } - if (symbol.flags & SymbolFlags.Optional) { - modifiers.add(ScriptElementKindModifier.optionalModifier); - } - return modifiers.size > 0 ? arrayFrom(modifiers.values()).join(",") : ScriptElementKindModifier.none; - } - - interface SymbolDisplayPartsDocumentationAndSymbolKind { - displayParts: SymbolDisplayPart[]; - documentation: SymbolDisplayPart[]; - symbolKind: ScriptElementKind; - tags: JSDocTagInfo[] | undefined; - } - - // TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location - export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node | undefined, - location: Node, semanticMeaning = getMeaningFromLocation(location), alias?: Symbol): SymbolDisplayPartsDocumentationAndSymbolKind { - const displayParts: SymbolDisplayPart[] = []; - let documentation: SymbolDisplayPart[] = []; - let tags: JSDocTagInfo[] = []; - const symbolFlags = getCombinedLocalAndExportSymbolFlags(symbol); - let symbolKind = semanticMeaning & SemanticMeaning.Value ? getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location) : ScriptElementKind.unknown; - let hasAddedSymbolInfo = false; - const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isInExpressionContext(location); - let type: Type | undefined; - let printer: Printer; - let documentationFromAlias: SymbolDisplayPart[] | undefined; - let tagsFromAlias: JSDocTagInfo[] | undefined; - let hasMultipleSignatures = false; - - if (location.kind === SyntaxKind.ThisKeyword && !isThisExpression) { - return { displayParts: [keywordPart(SyntaxKind.ThisKeyword)], documentation: [], symbolKind: ScriptElementKind.primitiveType, tags: undefined }; - } - - // Class at constructor site need to be shown as constructor apart from property,method, vars - if (symbolKind !== ScriptElementKind.unknown || symbolFlags & SymbolFlags.Class || symbolFlags & SymbolFlags.Alias) { - // If it is accessor they are allowed only if location is at name of the accessor - if (symbolKind === ScriptElementKind.memberGetAccessorElement || symbolKind === ScriptElementKind.memberSetAccessorElement) { - symbolKind = ScriptElementKind.memberVariableElement; - } - let signature: Signature | undefined; - type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location); + let signature: Signature | undefined; + type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location); - if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) { - const right = (location.parent as PropertyAccessExpression).name; - // Either the location is on the right of a property access, or on the left and the right is missing - if (right === location || (right && right.getFullWidth() === 0)) { - location = location.parent; - } + if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) { + const right = (location.parent as PropertyAccessExpression).name; + // Either the location is on the right of a property access, or on the left and the right is missing + if (right === location || (right && right.getFullWidth() === 0)) { + location = location.parent; } + } - // try get the call/construct signature from the type if it matches - let callExpressionLike: CallExpression | NewExpression | JsxOpeningLikeElement | TaggedTemplateExpression | undefined; - if (isCallOrNewExpression(location)) { - callExpressionLike = location; - } - else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) { - callExpressionLike = location.parent as CallExpression | NewExpression; - } - else if (location.parent && (isJsxOpeningLikeElement(location.parent) || isTaggedTemplateExpression(location.parent)) && isFunctionLike(symbol.valueDeclaration)) { - callExpressionLike = location.parent; - } + // try get the call/construct signature from the type if it matches + let callExpressionLike: CallExpression | NewExpression | JsxOpeningLikeElement | TaggedTemplateExpression | undefined; + if (isCallOrNewExpression(location)) { + callExpressionLike = location; + } + else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) { + callExpressionLike = location.parent as CallExpression | NewExpression; + } + else if (location.parent && (isJsxOpeningLikeElement(location.parent) || isTaggedTemplateExpression(location.parent)) && isFunctionLike(symbol.valueDeclaration)) { + callExpressionLike = location.parent; + } - if (callExpressionLike) { - signature = typeChecker.getResolvedSignature(callExpressionLike); // TODO: GH#18217 + if (callExpressionLike) { + signature = typeChecker.getResolvedSignature(callExpressionLike); // TODO: GH#18217 - const useConstructSignatures = callExpressionLike.kind === SyntaxKind.NewExpression || (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword); + const useConstructSignatures = callExpressionLike.kind === SyntaxKind.NewExpression || (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword); - const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures(); + const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures(); - if (signature && !contains(allSignatures, signature.target) && !contains(allSignatures, signature)) { - // Get the first signature if there is one -- allSignatures may contain - // either the original signature or its target, so check for either - signature = allSignatures.length ? allSignatures[0] : undefined; - } + if (signature && !contains(allSignatures, signature.target) && !contains(allSignatures, signature)) { + // Get the first signature if there is one -- allSignatures may contain + // either the original signature or its target, so check for either + signature = allSignatures.length ? allSignatures[0] : undefined; + } - if (signature) { - if (useConstructSignatures && (symbolFlags & SymbolFlags.Class)) { - // Constructor - symbolKind = ScriptElementKind.constructorImplementationElement; - addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); + if (signature) { + if (useConstructSignatures && (symbolFlags & SymbolFlags.Class)) { + // Constructor + symbolKind = ScriptElementKind.constructorImplementationElement; + addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); + } + else if (symbolFlags & SymbolFlags.Alias) { + symbolKind = ScriptElementKind.alias; + pushSymbolKind(symbolKind); + displayParts.push(spacePart()); + if (useConstructSignatures) { + if (signature.flags & SignatureFlags.Abstract) { + displayParts.push(keywordPart(SyntaxKind.AbstractKeyword)); + displayParts.push(spacePart()); + } + displayParts.push(keywordPart(SyntaxKind.NewKeyword)); + displayParts.push(spacePart()); } - else if (symbolFlags & SymbolFlags.Alias) { - symbolKind = ScriptElementKind.alias; - pushSymbolKind(symbolKind); + addFullSymbolName(symbol); + } + else { + addPrefixForAnyFunctionOrVar(symbol, symbolKind); + } + + switch (symbolKind) { + case ScriptElementKind.jsxAttribute: + case ScriptElementKind.memberVariableElement: + case ScriptElementKind.variableElement: + case ScriptElementKind.constElement: + case ScriptElementKind.letElement: + case ScriptElementKind.parameterElement: + case ScriptElementKind.localVariableElement: + // If it is call or construct signature of lambda's write type name + displayParts.push(punctuationPart(SyntaxKind.ColonToken)); displayParts.push(spacePart()); + if (!(getObjectFlags(type) & ObjectFlags.Anonymous) && type.symbol) { + addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteTypeParametersOrArguments)); + displayParts.push(lineBreakPart()); + } if (useConstructSignatures) { if (signature.flags & SignatureFlags.Abstract) { displayParts.push(keywordPart(SyntaxKind.AbstractKeyword)); @@ -223,494 +252,465 @@ namespace ts.SymbolDisplay { displayParts.push(keywordPart(SyntaxKind.NewKeyword)); displayParts.push(spacePart()); } - addFullSymbolName(symbol); - } - else { - addPrefixForAnyFunctionOrVar(symbol, symbolKind); - } - - switch (symbolKind) { - case ScriptElementKind.jsxAttribute: - case ScriptElementKind.memberVariableElement: - case ScriptElementKind.variableElement: - case ScriptElementKind.constElement: - case ScriptElementKind.letElement: - case ScriptElementKind.parameterElement: - case ScriptElementKind.localVariableElement: - // If it is call or construct signature of lambda's write type name - displayParts.push(punctuationPart(SyntaxKind.ColonToken)); - displayParts.push(spacePart()); - if (!(getObjectFlags(type) & ObjectFlags.Anonymous) && type.symbol) { - addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteTypeParametersOrArguments)); - displayParts.push(lineBreakPart()); - } - if (useConstructSignatures) { - if (signature.flags & SignatureFlags.Abstract) { - displayParts.push(keywordPart(SyntaxKind.AbstractKeyword)); - displayParts.push(spacePart()); - } - displayParts.push(keywordPart(SyntaxKind.NewKeyword)); - displayParts.push(spacePart()); - } - addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature); - break; + addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature); + break; - default: - // Just signature - addSignatureDisplayParts(signature, allSignatures); - } - hasAddedSymbolInfo = true; - hasMultipleSignatures = allSignatures.length > 1; + default: + // Just signature + addSignatureDisplayParts(signature, allSignatures); + } + hasAddedSymbolInfo = true; + hasMultipleSignatures = allSignatures.length > 1; + } + } + else if ((isNameOfFunctionDeclaration(location) && !(symbolFlags & SymbolFlags.Accessor)) || // name of function declaration + (location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration + // get the signature from the declaration and write it + const functionDeclaration = location.parent as SignatureDeclaration; + // Use function declaration to write the signatures only if the symbol corresponding to this declaration + const locationIsSymbolDeclaration = symbol.declarations && find(symbol.declarations, declaration => + declaration === (location.kind === SyntaxKind.ConstructorKeyword ? functionDeclaration.parent : functionDeclaration)); + + if (locationIsSymbolDeclaration) { + const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures(); + if (!typeChecker.isImplementationOfOverload(functionDeclaration)) { + signature = typeChecker.getSignatureFromDeclaration(functionDeclaration); // TODO: GH#18217 + } + else { + signature = allSignatures[0]; } - } - else if ((isNameOfFunctionDeclaration(location) && !(symbolFlags & SymbolFlags.Accessor)) || // name of function declaration - (location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration - // get the signature from the declaration and write it - const functionDeclaration = location.parent as SignatureDeclaration; - // Use function declaration to write the signatures only if the symbol corresponding to this declaration - const locationIsSymbolDeclaration = symbol.declarations && find(symbol.declarations, declaration => - declaration === (location.kind === SyntaxKind.ConstructorKeyword ? functionDeclaration.parent : functionDeclaration)); - - if (locationIsSymbolDeclaration) { - const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures(); - if (!typeChecker.isImplementationOfOverload(functionDeclaration)) { - signature = typeChecker.getSignatureFromDeclaration(functionDeclaration); // TODO: GH#18217 - } - else { - signature = allSignatures[0]; - } - if (functionDeclaration.kind === SyntaxKind.Constructor) { - // show (constructor) Type(...) signature - symbolKind = ScriptElementKind.constructorImplementationElement; - addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); - } - else { - // (function/method) symbol(..signature) - addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature && - !(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind); - } - if (signature) { - addSignatureDisplayParts(signature, allSignatures); - } - hasAddedSymbolInfo = true; - hasMultipleSignatures = allSignatures.length > 1; + if (functionDeclaration.kind === SyntaxKind.Constructor) { + // show (constructor) Type(...) signature + symbolKind = ScriptElementKind.constructorImplementationElement; + addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); } + else { + // (function/method) symbol(..signature) + addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature && + !(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind); + } + if (signature) { + addSignatureDisplayParts(signature, allSignatures); + } + hasAddedSymbolInfo = true; + hasMultipleSignatures = allSignatures.length > 1; } } - if (symbolFlags & SymbolFlags.Class && !hasAddedSymbolInfo && !isThisExpression) { - addAliasPrefixIfNecessary(); - if (getDeclarationOfKind(symbol, SyntaxKind.ClassExpression)) { - // Special case for class expressions because we would like to indicate that - // the class name is local to the class body (similar to function expression) - // (local class) class - pushSymbolKind(ScriptElementKind.localClassElement); - } - else { - // Class declaration has name which is not local. - displayParts.push(keywordPart(SyntaxKind.ClassKeyword)); - } - displayParts.push(spacePart()); - addFullSymbolName(symbol); - writeTypeParametersOfSymbol(symbol, sourceFile); - } - if ((symbolFlags & SymbolFlags.Interface) && (semanticMeaning & SemanticMeaning.Type)) { - prefixNextMeaning(); - displayParts.push(keywordPart(SyntaxKind.InterfaceKeyword)); - displayParts.push(spacePart()); - addFullSymbolName(symbol); - writeTypeParametersOfSymbol(symbol, sourceFile); - } - if ((symbolFlags & SymbolFlags.TypeAlias) && (semanticMeaning & SemanticMeaning.Type)) { - prefixNextMeaning(); - displayParts.push(keywordPart(SyntaxKind.TypeKeyword)); - displayParts.push(spacePart()); - addFullSymbolName(symbol); - writeTypeParametersOfSymbol(symbol, sourceFile); - displayParts.push(spacePart()); - displayParts.push(operatorPart(SyntaxKind.EqualsToken)); - displayParts.push(spacePart()); - addRange(displayParts, typeToDisplayParts(typeChecker, typeChecker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, TypeFormatFlags.InTypeAlias)); - } - if (symbolFlags & SymbolFlags.Enum) { - prefixNextMeaning(); - if (some(symbol.declarations, d => isEnumDeclaration(d) && isEnumConst(d))) { - displayParts.push(keywordPart(SyntaxKind.ConstKeyword)); - displayParts.push(spacePart()); - } - displayParts.push(keywordPart(SyntaxKind.EnumKeyword)); - displayParts.push(spacePart()); - addFullSymbolName(symbol); - } - if (symbolFlags & SymbolFlags.Module && !isThisExpression) { - prefixNextMeaning(); - const declaration = getDeclarationOfKind(symbol, SyntaxKind.ModuleDeclaration); - const isNamespace = declaration && declaration.name && declaration.name.kind === SyntaxKind.Identifier; - displayParts.push(keywordPart(isNamespace ? SyntaxKind.NamespaceKeyword : SyntaxKind.ModuleKeyword)); + } + if (symbolFlags & SymbolFlags.Class && !hasAddedSymbolInfo && !isThisExpression) { + addAliasPrefixIfNecessary(); + if (getDeclarationOfKind(symbol, SyntaxKind.ClassExpression)) { + // Special case for class expressions because we would like to indicate that + // the class name is local to the class body (similar to function expression) + // (local class) class + pushSymbolKind(ScriptElementKind.localClassElement); + } + else { + // Class declaration has name which is not local. + displayParts.push(keywordPart(SyntaxKind.ClassKeyword)); + } + displayParts.push(spacePart()); + addFullSymbolName(symbol); + writeTypeParametersOfSymbol(symbol, sourceFile); + } + if ((symbolFlags & SymbolFlags.Interface) && (semanticMeaning & SemanticMeaning.Type)) { + prefixNextMeaning(); + displayParts.push(keywordPart(SyntaxKind.InterfaceKeyword)); + displayParts.push(spacePart()); + addFullSymbolName(symbol); + writeTypeParametersOfSymbol(symbol, sourceFile); + } + if ((symbolFlags & SymbolFlags.TypeAlias) && (semanticMeaning & SemanticMeaning.Type)) { + prefixNextMeaning(); + displayParts.push(keywordPart(SyntaxKind.TypeKeyword)); + displayParts.push(spacePart()); + addFullSymbolName(symbol); + writeTypeParametersOfSymbol(symbol, sourceFile); + displayParts.push(spacePart()); + displayParts.push(operatorPart(SyntaxKind.EqualsToken)); + displayParts.push(spacePart()); + addRange(displayParts, typeToDisplayParts(typeChecker, typeChecker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, TypeFormatFlags.InTypeAlias)); + } + if (symbolFlags & SymbolFlags.Enum) { + prefixNextMeaning(); + if (some(symbol.declarations, d => isEnumDeclaration(d) && isEnumConst(d))) { + displayParts.push(keywordPart(SyntaxKind.ConstKeyword)); displayParts.push(spacePart()); - addFullSymbolName(symbol); } - if ((symbolFlags & SymbolFlags.TypeParameter) && (semanticMeaning & SemanticMeaning.Type)) { - prefixNextMeaning(); - displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); - displayParts.push(textPart("type parameter")); - displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); - displayParts.push(spacePart()); - addFullSymbolName(symbol); - if (symbol.parent) { - // Class/Interface type parameter - addInPrefix(); - addFullSymbolName(symbol.parent, enclosingDeclaration); - writeTypeParametersOfSymbol(symbol.parent, enclosingDeclaration); - } - else { - // Method/function type parameter - const decl = getDeclarationOfKind(symbol, SyntaxKind.TypeParameter); - if (decl === undefined) return Debug.fail(); - const declaration = decl.parent; - - if (declaration) { - if (isFunctionLikeKind(declaration.kind)) { - addInPrefix(); - const signature = typeChecker.getSignatureFromDeclaration(declaration as SignatureDeclaration)!; // TODO: GH#18217 - if (declaration.kind === SyntaxKind.ConstructSignature) { - displayParts.push(keywordPart(SyntaxKind.NewKeyword)); - displayParts.push(spacePart()); - } - else if (declaration.kind !== SyntaxKind.CallSignature && (declaration as SignatureDeclaration).name) { - addFullSymbolName(declaration.symbol); - } - addRange(displayParts, signatureToDisplayParts(typeChecker, signature, sourceFile, TypeFormatFlags.WriteTypeArgumentsOfSignature)); - } - else if (declaration.kind === SyntaxKind.TypeAliasDeclaration) { - // Type alias type parameter - // For example - // type list = T[]; // Both T will go through same code path - addInPrefix(); - displayParts.push(keywordPart(SyntaxKind.TypeKeyword)); + displayParts.push(keywordPart(SyntaxKind.EnumKeyword)); + displayParts.push(spacePart()); + addFullSymbolName(symbol); + } + if (symbolFlags & SymbolFlags.Module && !isThisExpression) { + prefixNextMeaning(); + const declaration = getDeclarationOfKind(symbol, SyntaxKind.ModuleDeclaration); + const isNamespace = declaration && declaration.name && declaration.name.kind === SyntaxKind.Identifier; + displayParts.push(keywordPart(isNamespace ? SyntaxKind.NamespaceKeyword : SyntaxKind.ModuleKeyword)); + displayParts.push(spacePart()); + addFullSymbolName(symbol); + } + if ((symbolFlags & SymbolFlags.TypeParameter) && (semanticMeaning & SemanticMeaning.Type)) { + prefixNextMeaning(); + displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); + displayParts.push(textPart("type parameter")); + displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); + displayParts.push(spacePart()); + addFullSymbolName(symbol); + if (symbol.parent) { + // Class/Interface type parameter + addInPrefix(); + addFullSymbolName(symbol.parent, enclosingDeclaration); + writeTypeParametersOfSymbol(symbol.parent, enclosingDeclaration); + } + else { + // Method/function type parameter + const decl = getDeclarationOfKind(symbol, SyntaxKind.TypeParameter); + if (decl === undefined) return Debug.fail(); + const declaration = decl.parent; + + if (declaration) { + if (isFunctionLikeKind(declaration.kind)) { + addInPrefix(); + const signature = typeChecker.getSignatureFromDeclaration(declaration as SignatureDeclaration)!; // TODO: GH#18217 + if (declaration.kind === SyntaxKind.ConstructSignature) { + displayParts.push(keywordPart(SyntaxKind.NewKeyword)); displayParts.push(spacePart()); + } + else if (declaration.kind !== SyntaxKind.CallSignature && (declaration as SignatureDeclaration).name) { addFullSymbolName(declaration.symbol); - writeTypeParametersOfSymbol(declaration.symbol, sourceFile); } + addRange(displayParts, signatureToDisplayParts(typeChecker, signature, sourceFile, TypeFormatFlags.WriteTypeArgumentsOfSignature)); } - } - } - if (symbolFlags & SymbolFlags.EnumMember) { - symbolKind = ScriptElementKind.enumMemberElement; - addPrefixForAnyFunctionOrVar(symbol, "enum member"); - const declaration = symbol.declarations?.[0]; - if (declaration?.kind === SyntaxKind.EnumMember) { - const constantValue = typeChecker.getConstantValue(declaration as EnumMember); - if (constantValue !== undefined) { + else if (declaration.kind === SyntaxKind.TypeAliasDeclaration) { + // Type alias type parameter + // For example + // type list = T[]; // Both T will go through same code path + addInPrefix(); + displayParts.push(keywordPart(SyntaxKind.TypeKeyword)); displayParts.push(spacePart()); - displayParts.push(operatorPart(SyntaxKind.EqualsToken)); - displayParts.push(spacePart()); - displayParts.push(displayPart(getTextOfConstantValue(constantValue), - typeof constantValue === "number" ? SymbolDisplayPartKind.numericLiteral : SymbolDisplayPartKind.stringLiteral)); + addFullSymbolName(declaration.symbol); + writeTypeParametersOfSymbol(declaration.symbol, sourceFile); } } } - // don't use symbolFlags since getAliasedSymbol requires the flag on the symbol itself - if (symbol.flags & SymbolFlags.Alias) { - prefixNextMeaning(); - if (!hasAddedSymbolInfo) { - const resolvedSymbol = typeChecker.getAliasedSymbol(symbol); - if (resolvedSymbol !== symbol && resolvedSymbol.declarations && resolvedSymbol.declarations.length > 0) { - const resolvedNode = resolvedSymbol.declarations[0]; - const declarationName = getNameOfDeclaration(resolvedNode); - if (declarationName) { - const isExternalModuleDeclaration = - isModuleWithStringLiteralName(resolvedNode) && - hasSyntacticModifier(resolvedNode, ModifierFlags.Ambient); - const shouldUseAliasName = symbol.name !== "default" && !isExternalModuleDeclaration; - const resolvedInfo = getSymbolDisplayPartsDocumentationAndSymbolKind( - typeChecker, - resolvedSymbol, - getSourceFileOfNode(resolvedNode), - resolvedNode, - declarationName, - semanticMeaning, - shouldUseAliasName ? symbol : resolvedSymbol); - displayParts.push(...resolvedInfo.displayParts); - displayParts.push(lineBreakPart()); - documentationFromAlias = resolvedInfo.documentation; - tagsFromAlias = resolvedInfo.tags; - } - else { - documentationFromAlias = resolvedSymbol.getContextualDocumentationComment(resolvedNode, typeChecker); - tagsFromAlias = resolvedSymbol.getJsDocTags(typeChecker); - } + } + if (symbolFlags & SymbolFlags.EnumMember) { + symbolKind = ScriptElementKind.enumMemberElement; + addPrefixForAnyFunctionOrVar(symbol, "enum member"); + const declaration = symbol.declarations?.[0]; + if (declaration?.kind === SyntaxKind.EnumMember) { + const constantValue = typeChecker.getConstantValue(declaration as EnumMember); + if (constantValue !== undefined) { + displayParts.push(spacePart()); + displayParts.push(operatorPart(SyntaxKind.EqualsToken)); + displayParts.push(spacePart()); + displayParts.push(displayPart(getTextOfConstantValue(constantValue), + typeof constantValue === "number" ? SymbolDisplayPartKind.numericLiteral : SymbolDisplayPartKind.stringLiteral)); + } + } + } + // don't use symbolFlags since getAliasedSymbol requires the flag on the symbol itself + if (symbol.flags & SymbolFlags.Alias) { + prefixNextMeaning(); + if (!hasAddedSymbolInfo) { + const resolvedSymbol = typeChecker.getAliasedSymbol(symbol); + if (resolvedSymbol !== symbol && resolvedSymbol.declarations && resolvedSymbol.declarations.length > 0) { + const resolvedNode = resolvedSymbol.declarations[0]; + const declarationName = getNameOfDeclaration(resolvedNode); + if (declarationName) { + const isExternalModuleDeclaration = + isModuleWithStringLiteralName(resolvedNode) && + hasSyntacticModifier(resolvedNode, ModifierFlags.Ambient); + const shouldUseAliasName = symbol.name !== "default" && !isExternalModuleDeclaration; + const resolvedInfo = getSymbolDisplayPartsDocumentationAndSymbolKind( + typeChecker, + resolvedSymbol, + getSourceFileOfNode(resolvedNode), + resolvedNode, + declarationName, + semanticMeaning, + shouldUseAliasName ? symbol : resolvedSymbol); + displayParts.push(...resolvedInfo.displayParts); + displayParts.push(lineBreakPart()); + documentationFromAlias = resolvedInfo.documentation; + tagsFromAlias = resolvedInfo.tags; + } + else { + documentationFromAlias = resolvedSymbol.getContextualDocumentationComment(resolvedNode, typeChecker); + tagsFromAlias = resolvedSymbol.getJsDocTags(typeChecker); } } + } - if (symbol.declarations) { - switch (symbol.declarations[0].kind) { - case SyntaxKind.NamespaceExportDeclaration: - displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); - displayParts.push(spacePart()); - displayParts.push(keywordPart(SyntaxKind.NamespaceKeyword)); - break; - case SyntaxKind.ExportAssignment: - displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); - displayParts.push(spacePart()); - displayParts.push(keywordPart((symbol.declarations[0] as ExportAssignment).isExportEquals ? SyntaxKind.EqualsToken : SyntaxKind.DefaultKeyword)); - break; - case SyntaxKind.ExportSpecifier: - displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); - break; - default: - displayParts.push(keywordPart(SyntaxKind.ImportKeyword)); - } + if (symbol.declarations) { + switch (symbol.declarations[0].kind) { + case SyntaxKind.NamespaceExportDeclaration: + displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); + displayParts.push(spacePart()); + displayParts.push(keywordPart(SyntaxKind.NamespaceKeyword)); + break; + case SyntaxKind.ExportAssignment: + displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); + displayParts.push(spacePart()); + displayParts.push(keywordPart((symbol.declarations[0] as ExportAssignment).isExportEquals ? SyntaxKind.EqualsToken : SyntaxKind.DefaultKeyword)); + break; + case SyntaxKind.ExportSpecifier: + displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); + break; + default: + displayParts.push(keywordPart(SyntaxKind.ImportKeyword)); } - displayParts.push(spacePart()); - addFullSymbolName(symbol); - forEach(symbol.declarations, declaration => { - if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) { - const importEqualsDeclaration = declaration as ImportEqualsDeclaration; - if (isExternalModuleImportEqualsDeclaration(importEqualsDeclaration)) { + } + displayParts.push(spacePart()); + addFullSymbolName(symbol); + forEach(symbol.declarations, declaration => { + if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) { + const importEqualsDeclaration = declaration as ImportEqualsDeclaration; + if (isExternalModuleImportEqualsDeclaration(importEqualsDeclaration)) { + displayParts.push(spacePart()); + displayParts.push(operatorPart(SyntaxKind.EqualsToken)); + displayParts.push(spacePart()); + displayParts.push(keywordPart(SyntaxKind.RequireKeyword)); + displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); + displayParts.push(displayPart(getTextOfNode(getExternalModuleImportEqualsDeclarationExpression(importEqualsDeclaration)), SymbolDisplayPartKind.stringLiteral)); + displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); + } + else { + const internalAliasSymbol = typeChecker.getSymbolAtLocation(importEqualsDeclaration.moduleReference); + if (internalAliasSymbol) { displayParts.push(spacePart()); displayParts.push(operatorPart(SyntaxKind.EqualsToken)); displayParts.push(spacePart()); - displayParts.push(keywordPart(SyntaxKind.RequireKeyword)); - displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); - displayParts.push(displayPart(getTextOfNode(getExternalModuleImportEqualsDeclarationExpression(importEqualsDeclaration)), SymbolDisplayPartKind.stringLiteral)); - displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); + addFullSymbolName(internalAliasSymbol, enclosingDeclaration); } - else { - const internalAliasSymbol = typeChecker.getSymbolAtLocation(importEqualsDeclaration.moduleReference); - if (internalAliasSymbol) { - displayParts.push(spacePart()); - displayParts.push(operatorPart(SyntaxKind.EqualsToken)); - displayParts.push(spacePart()); - addFullSymbolName(internalAliasSymbol, enclosingDeclaration); - } - } - return true; } - }); - } - if (!hasAddedSymbolInfo) { - if (symbolKind !== ScriptElementKind.unknown) { - if (type) { - if (isThisExpression) { - prefixNextMeaning(); - displayParts.push(keywordPart(SyntaxKind.ThisKeyword)); + return true; + } + }); + } + if (!hasAddedSymbolInfo) { + if (symbolKind !== ScriptElementKind.unknown) { + if (type) { + if (isThisExpression) { + prefixNextMeaning(); + displayParts.push(keywordPart(SyntaxKind.ThisKeyword)); + } + else { + addPrefixForAnyFunctionOrVar(symbol, symbolKind); + } + + // For properties, variables and local vars: show the type + if (symbolKind === ScriptElementKind.memberVariableElement || + symbolKind === ScriptElementKind.jsxAttribute || + symbolFlags & SymbolFlags.Variable || + symbolKind === ScriptElementKind.localVariableElement || + isThisExpression) { + displayParts.push(punctuationPart(SyntaxKind.ColonToken)); + displayParts.push(spacePart()); + // If the type is type parameter, format it specially + if (type.symbol && type.symbol.flags & SymbolFlags.TypeParameter) { + const typeParameterParts = mapToDisplayParts(writer => { + const param = typeChecker.typeParameterToDeclaration(type as TypeParameter, enclosingDeclaration, symbolDisplayNodeBuilderFlags)!; + getPrinter().writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer); + }); + addRange(displayParts, typeParameterParts); } else { - addPrefixForAnyFunctionOrVar(symbol, symbolKind); + addRange(displayParts, typeToDisplayParts(typeChecker, type, enclosingDeclaration)); } - - // For properties, variables and local vars: show the type - if (symbolKind === ScriptElementKind.memberVariableElement || - symbolKind === ScriptElementKind.jsxAttribute || - symbolFlags & SymbolFlags.Variable || - symbolKind === ScriptElementKind.localVariableElement || - isThisExpression) { - displayParts.push(punctuationPart(SyntaxKind.ColonToken)); + if ((symbol as TransientSymbol).target && ((symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) { + const labelDecl = ((symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!; + Debug.assertNode(labelDecl.name, isIdentifier); displayParts.push(spacePart()); - // If the type is type parameter, format it specially - if (type.symbol && type.symbol.flags & SymbolFlags.TypeParameter) { - const typeParameterParts = mapToDisplayParts(writer => { - const param = typeChecker.typeParameterToDeclaration(type as TypeParameter, enclosingDeclaration, symbolDisplayNodeBuilderFlags)!; - getPrinter().writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer); - }); - addRange(displayParts, typeParameterParts); - } - else { - addRange(displayParts, typeToDisplayParts(typeChecker, type, enclosingDeclaration)); - } - if ((symbol as TransientSymbol).target && ((symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) { - const labelDecl = ((symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!; - Debug.assertNode(labelDecl.name, isIdentifier); - displayParts.push(spacePart()); - displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); - displayParts.push(textPart(idText(labelDecl.name))); - displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); - } + displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); + displayParts.push(textPart(idText(labelDecl.name))); + displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); } - else if (symbolFlags & SymbolFlags.Function || - symbolFlags & SymbolFlags.Method || - symbolFlags & SymbolFlags.Constructor || - symbolFlags & SymbolFlags.Signature || - symbolFlags & SymbolFlags.Accessor || - symbolKind === ScriptElementKind.memberFunctionElement) { - const allSignatures = type.getNonNullableType().getCallSignatures(); - if (allSignatures.length) { - addSignatureDisplayParts(allSignatures[0], allSignatures); - hasMultipleSignatures = allSignatures.length > 1; - } + } + else if (symbolFlags & SymbolFlags.Function || + symbolFlags & SymbolFlags.Method || + symbolFlags & SymbolFlags.Constructor || + symbolFlags & SymbolFlags.Signature || + symbolFlags & SymbolFlags.Accessor || + symbolKind === ScriptElementKind.memberFunctionElement) { + const allSignatures = type.getNonNullableType().getCallSignatures(); + if (allSignatures.length) { + addSignatureDisplayParts(allSignatures[0], allSignatures); + hasMultipleSignatures = allSignatures.length > 1; } } } - else { - symbolKind = getSymbolKind(typeChecker, symbol, location); - } } - - if (documentation.length === 0 && !hasMultipleSignatures) { - documentation = symbol.getContextualDocumentationComment(enclosingDeclaration, typeChecker); + else { + symbolKind = getSymbolKind(typeChecker, symbol, location); } + } - if (documentation.length === 0 && symbolFlags & SymbolFlags.Property) { - // For some special property access expressions like `exports.foo = foo` or `module.exports.foo = foo` - // there documentation comments might be attached to the right hand side symbol of their declarations. - // The pattern of such special property access is that the parent symbol is the symbol of the file. - if (symbol.parent && symbol.declarations && forEach(symbol.parent.declarations, declaration => declaration.kind === SyntaxKind.SourceFile)) { - for (const declaration of symbol.declarations) { - if (!declaration.parent || declaration.parent.kind !== SyntaxKind.BinaryExpression) { - continue; - } + if (documentation.length === 0 && !hasMultipleSignatures) { + documentation = symbol.getContextualDocumentationComment(enclosingDeclaration, typeChecker); + } - const rhsSymbol = typeChecker.getSymbolAtLocation((declaration.parent as BinaryExpression).right); - if (!rhsSymbol) { - continue; - } + if (documentation.length === 0 && symbolFlags & SymbolFlags.Property) { + // For some special property access expressions like `exports.foo = foo` or `module.exports.foo = foo` + // there documentation comments might be attached to the right hand side symbol of their declarations. + // The pattern of such special property access is that the parent symbol is the symbol of the file. + if (symbol.parent && symbol.declarations && forEach(symbol.parent.declarations, declaration => declaration.kind === SyntaxKind.SourceFile)) { + for (const declaration of symbol.declarations) { + if (!declaration.parent || declaration.parent.kind !== SyntaxKind.BinaryExpression) { + continue; + } - documentation = rhsSymbol.getDocumentationComment(typeChecker); - tags = rhsSymbol.getJsDocTags(typeChecker); - if (documentation.length > 0) { - break; - } + const rhsSymbol = typeChecker.getSymbolAtLocation((declaration.parent as BinaryExpression).right); + if (!rhsSymbol) { + continue; } - } - } - if (tags.length === 0 && !hasMultipleSignatures) { - tags = symbol.getJsDocTags(typeChecker); + documentation = rhsSymbol.getDocumentationComment(typeChecker); + tags = rhsSymbol.getJsDocTags(typeChecker); + if (documentation.length > 0) { + break; + } + } } + } - if (documentation.length === 0 && documentationFromAlias) { - documentation = documentationFromAlias; - } + if (tags.length === 0 && !hasMultipleSignatures) { + tags = symbol.getJsDocTags(typeChecker); + } - if (tags.length === 0 && tagsFromAlias) { - tags = tagsFromAlias; - } + if (documentation.length === 0 && documentationFromAlias) { + documentation = documentationFromAlias; + } - return { displayParts, documentation, symbolKind, tags: tags.length === 0 ? undefined : tags }; + if (tags.length === 0 && tagsFromAlias) { + tags = tagsFromAlias; + } - function getPrinter() { - if (!printer) { - printer = createPrinter({ removeComments: true }); - } - return printer; - } + return { displayParts, documentation, symbolKind, tags: tags.length === 0 ? undefined : tags }; - function prefixNextMeaning() { - if (displayParts.length) { - displayParts.push(lineBreakPart()); - } - addAliasPrefixIfNecessary(); + function getPrinter() { + if (!printer) { + printer = createPrinter({ removeComments: true }); } + return printer; + } - function addAliasPrefixIfNecessary() { - if (alias) { - pushSymbolKind(ScriptElementKind.alias); - displayParts.push(spacePart()); - } + function prefixNextMeaning() { + if (displayParts.length) { + displayParts.push(lineBreakPart()); } + addAliasPrefixIfNecessary(); + } - function addInPrefix() { - displayParts.push(spacePart()); - displayParts.push(keywordPart(SyntaxKind.InKeyword)); + function addAliasPrefixIfNecessary() { + if (alias) { + pushSymbolKind(ScriptElementKind.alias); displayParts.push(spacePart()); } + } - function addFullSymbolName(symbolToDisplay: Symbol, enclosingDeclaration?: Node) { - if (alias && symbolToDisplay === symbol) { - symbolToDisplay = alias; - } - const fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbolToDisplay, enclosingDeclaration || sourceFile, /*meaning*/ undefined, - SymbolFormatFlags.WriteTypeParametersOrArguments | SymbolFormatFlags.UseOnlyExternalAliasing | SymbolFormatFlags.AllowAnyNodeKind); - addRange(displayParts, fullSymbolDisplayParts); + function addInPrefix() { + displayParts.push(spacePart()); + displayParts.push(keywordPart(SyntaxKind.InKeyword)); + displayParts.push(spacePart()); + } - if (symbol.flags & SymbolFlags.Optional) { - displayParts.push(punctuationPart(SyntaxKind.QuestionToken)); - } + function addFullSymbolName(symbolToDisplay: Symbol, enclosingDeclaration?: Node) { + if (alias && symbolToDisplay === symbol) { + symbolToDisplay = alias; } + const fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbolToDisplay, enclosingDeclaration || sourceFile, /*meaning*/ undefined, + SymbolFormatFlags.WriteTypeParametersOrArguments | SymbolFormatFlags.UseOnlyExternalAliasing | SymbolFormatFlags.AllowAnyNodeKind); + addRange(displayParts, fullSymbolDisplayParts); - function addPrefixForAnyFunctionOrVar(symbol: Symbol, symbolKind: string) { - prefixNextMeaning(); - if (symbolKind) { - pushSymbolKind(symbolKind); - if (symbol && !some(symbol.declarations, d => isArrowFunction(d) || (isFunctionExpression(d) || isClassExpression(d)) && !d.name)) { - displayParts.push(spacePart()); - addFullSymbolName(symbol); - } - } + if (symbol.flags & SymbolFlags.Optional) { + displayParts.push(punctuationPart(SyntaxKind.QuestionToken)); } + } - function pushSymbolKind(symbolKind: string) { - switch (symbolKind) { - case ScriptElementKind.variableElement: - case ScriptElementKind.functionElement: - case ScriptElementKind.letElement: - case ScriptElementKind.constElement: - case ScriptElementKind.constructorImplementationElement: - displayParts.push(textOrKeywordPart(symbolKind)); - return; - default: - displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); - displayParts.push(textOrKeywordPart(symbolKind)); - displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); - return; + function addPrefixForAnyFunctionOrVar(symbol: Symbol, symbolKind: string) { + prefixNextMeaning(); + if (symbolKind) { + pushSymbolKind(symbolKind); + if (symbol && !some(symbol.declarations, d => isArrowFunction(d) || (isFunctionExpression(d) || isClassExpression(d)) && !d.name)) { + displayParts.push(spacePart()); + addFullSymbolName(symbol); } } + } - function addSignatureDisplayParts(signature: Signature, allSignatures: readonly Signature[], flags = TypeFormatFlags.None) { - addRange(displayParts, signatureToDisplayParts(typeChecker, signature, enclosingDeclaration, flags | TypeFormatFlags.WriteTypeArgumentsOfSignature)); - if (allSignatures.length > 1) { - displayParts.push(spacePart()); + function pushSymbolKind(symbolKind: string) { + switch (symbolKind) { + case ScriptElementKind.variableElement: + case ScriptElementKind.functionElement: + case ScriptElementKind.letElement: + case ScriptElementKind.constElement: + case ScriptElementKind.constructorImplementationElement: + displayParts.push(textOrKeywordPart(symbolKind)); + return; + default: displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); - displayParts.push(operatorPart(SyntaxKind.PlusToken)); - displayParts.push(displayPart((allSignatures.length - 1).toString(), SymbolDisplayPartKind.numericLiteral)); - displayParts.push(spacePart()); - displayParts.push(textPart(allSignatures.length === 2 ? "overload" : "overloads")); + displayParts.push(textOrKeywordPart(symbolKind)); displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); - } - documentation = signature.getDocumentationComment(typeChecker); - tags = signature.getJsDocTags(); + return; + } + } - if (allSignatures.length > 1 && documentation.length === 0 && tags.length === 0) { - documentation = allSignatures[0].getDocumentationComment(typeChecker); - tags = allSignatures[0].getJsDocTags(); - } + function addSignatureDisplayParts(signature: Signature, allSignatures: readonly Signature[], flags = TypeFormatFlags.None) { + addRange(displayParts, signatureToDisplayParts(typeChecker, signature, enclosingDeclaration, flags | TypeFormatFlags.WriteTypeArgumentsOfSignature)); + if (allSignatures.length > 1) { + displayParts.push(spacePart()); + displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); + displayParts.push(operatorPart(SyntaxKind.PlusToken)); + displayParts.push(displayPart((allSignatures.length - 1).toString(), SymbolDisplayPartKind.numericLiteral)); + displayParts.push(spacePart()); + displayParts.push(textPart(allSignatures.length === 2 ? "overload" : "overloads")); + displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); } + documentation = signature.getDocumentationComment(typeChecker); + tags = signature.getJsDocTags(); - function writeTypeParametersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined) { - const typeParameterParts = mapToDisplayParts(writer => { - const params = typeChecker.symbolToTypeParameterDeclarations(symbol, enclosingDeclaration, symbolDisplayNodeBuilderFlags); - getPrinter().writeList(ListFormat.TypeParameters, params, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer); - }); - addRange(displayParts, typeParameterParts); + if (allSignatures.length > 1 && documentation.length === 0 && tags.length === 0) { + documentation = allSignatures[0].getDocumentationComment(typeChecker); + tags = allSignatures[0].getJsDocTags(); } } - function isLocalVariableOrFunction(symbol: Symbol) { - if (symbol.parent) { - return false; // This is exported symbol + function writeTypeParametersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined) { + const typeParameterParts = mapToDisplayParts(writer => { + const params = typeChecker.symbolToTypeParameterDeclarations(symbol, enclosingDeclaration, symbolDisplayNodeBuilderFlags); + getPrinter().writeList(ListFormat.TypeParameters, params, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer); + }); + addRange(displayParts, typeParameterParts); + } +} + +function isLocalVariableOrFunction(symbol: Symbol) { + if (symbol.parent) { + return false; // This is exported symbol + } + + return forEach(symbol.declarations, declaration => { + // Function expressions are local + if (declaration.kind === SyntaxKind.FunctionExpression) { + return true; } - return forEach(symbol.declarations, declaration => { - // Function expressions are local - if (declaration.kind === SyntaxKind.FunctionExpression) { - return true; - } + if (declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.FunctionDeclaration) { + return false; + } - if (declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.FunctionDeclaration) { + // If the parent is not sourceFile or module block it is local variable + for (let parent = declaration.parent; !isFunctionBlock(parent); parent = parent.parent) { + // Reached source file or module block + if (parent.kind === SyntaxKind.SourceFile || parent.kind === SyntaxKind.ModuleBlock) { return false; } + } - // If the parent is not sourceFile or module block it is local variable - for (let parent = declaration.parent; !isFunctionBlock(parent); parent = parent.parent) { - // Reached source file or module block - if (parent.kind === SyntaxKind.SourceFile || parent.kind === SyntaxKind.ModuleBlock) { - return false; - } - } - - // parent is in function block - return true; - }); - } + // parent is in function block + return true; + }); +} } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 44ac36ab2046d..6c993f175565a 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -1,1579 +1,1579 @@ /* @internal */ namespace ts.textChanges { +/** + * Currently for simplicity we store recovered positions on the node itself. + * It can be changed to side-table later if we decide that current design is too invasive. + */ +function getPos(n: TextRange): number { + const result = (n as any).__pos; + Debug.assert(typeof result === "number"); + return result; +} + +function setPos(n: TextRange, pos: number): void { + Debug.assert(typeof pos === "number"); + (n as any).__pos = pos; +} + +function getEnd(n: TextRange): number { + const result = (n as any).__end; + Debug.assert(typeof result === "number"); + return result; +} + +function setEnd(n: TextRange, end: number): void { + Debug.assert(typeof end === "number"); + (n as any).__end = end; +} + +export interface ConfigurableStart { + leadingTriviaOption?: LeadingTriviaOption; +} +export interface ConfigurableEnd { + trailingTriviaOption?: TrailingTriviaOption; +} + +export enum LeadingTriviaOption { + /** Exclude all leading trivia (use getStart()) */ + Exclude, + /** Include leading trivia and, + * if there are no line breaks between the node and the previous token, + * include all trivia between the node and the previous token + */ + IncludeAll, /** - * Currently for simplicity we store recovered positions on the node itself. - * It can be changed to side-table later if we decide that current design is too invasive. + * Include attached JSDoc comments */ - function getPos(n: TextRange): number { - const result = (n as any).__pos; - Debug.assert(typeof result === "number"); - return result; + JSDoc, + /** + * Only delete trivia on the same line as getStart(). + * Used to avoid deleting leading comments + */ + StartLine, +} + +export enum TrailingTriviaOption { + /** Exclude all trailing trivia (use getEnd()) */ + Exclude, + /** Doesn't include whitespace, but does strip comments */ + ExcludeWhitespace, + /** Include trailing trivia */ + Include, +} + +function skipWhitespacesAndLineBreaks(text: string, start: number) { + return skipTrivia(text, start, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); +} + +function hasCommentsBeforeLineBreak(text: string, start: number) { + let i = start; + while (i < text.length) { + const ch = text.charCodeAt(i); + if (isWhiteSpaceSingleLine(ch)) { + i++; + continue; + } + return ch === CharacterCodes.slash; } + return false; +} + +/** + * Usually node.pos points to a position immediately after the previous token. + * If this position is used as a beginning of the span to remove - it might lead to removing the trailing trivia of the previous node, i.e: + * const x; // this is x + * ^ - pos for the next variable declaration will point here + * const y; // this is y + * ^ - end for previous variable declaration + * Usually leading trivia of the variable declaration 'y' should not include trailing trivia (whitespace, comment 'this is x' and newline) from the preceding + * variable declaration and trailing trivia for 'y' should include (whitespace, comment 'this is y', newline). + * By default when removing nodes we adjust start and end positions to respect specification of the trivia above. + * If pos\end should be interpreted literally (that is, withouth including leading and trailing trivia), `leadingTriviaOption` should be set to `LeadingTriviaOption.Exclude` + * and `trailingTriviaOption` to `TrailingTriviaOption.Exclude`. + */ +export interface ConfigurableStartEnd extends ConfigurableStart, ConfigurableEnd {} + +const useNonAdjustedPositions: ConfigurableStartEnd = { + leadingTriviaOption: LeadingTriviaOption.Exclude, + trailingTriviaOption: TrailingTriviaOption.Exclude, +}; + +export interface InsertNodeOptions { + /** + * Text to be inserted before the new node + */ + prefix?: string; + /** + * Text to be inserted after the new node + */ + suffix?: string; + /** + * Text of inserted node will be formatted with this indentation, otherwise indentation will be inferred from the old node + */ + indentation?: number; + /** + * Text of inserted node will be formatted with this delta, otherwise delta will be inferred from the new node kind + */ + delta?: number; + /** + * Do not trim leading white spaces in the edit range + */ + preserveLeadingWhitespace?: boolean; +} + +export interface ReplaceWithMultipleNodesOptions extends InsertNodeOptions { + readonly joiner?: string; +} + +enum ChangeKind { + Remove, + ReplaceWithSingleNode, + ReplaceWithMultipleNodes, + Text, +} + +type Change = ReplaceWithSingleNode | ReplaceWithMultipleNodes | RemoveNode | ChangeText; + +interface BaseChange { + readonly sourceFile: SourceFile; + readonly range: TextRange; +} + +export interface ChangeNodeOptions extends ConfigurableStartEnd, InsertNodeOptions {} +interface ReplaceWithSingleNode extends BaseChange { + readonly kind: ChangeKind.ReplaceWithSingleNode; + readonly node: Node; + readonly options?: InsertNodeOptions; +} + +interface RemoveNode extends BaseChange { + readonly kind: ChangeKind.Remove; + readonly node?: never; + readonly options?: never; +} + +interface ReplaceWithMultipleNodes extends BaseChange { + readonly kind: ChangeKind.ReplaceWithMultipleNodes; + readonly nodes: readonly Node[]; + readonly options?: ReplaceWithMultipleNodesOptions; +} + +interface ChangeText extends BaseChange { + readonly kind: ChangeKind.Text; + readonly text: string; +} + +function getAdjustedRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd): TextRange { + return { pos: getAdjustedStartPosition(sourceFile, startNode, options), end: getAdjustedEndPosition(sourceFile, endNode, options) }; +} - function setPos(n: TextRange, pos: number): void { - Debug.assert(typeof pos === "number"); - (n as any).__pos = pos; +function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd, hasTrailingComment = false) { + const { leadingTriviaOption } = options; + if (leadingTriviaOption === LeadingTriviaOption.Exclude) { + return node.getStart(sourceFile); + } + if (leadingTriviaOption === LeadingTriviaOption.StartLine) { + const startPos = node.getStart(sourceFile); + const pos = getLineStartPositionForPosition(startPos, sourceFile); + return rangeContainsPosition(node, pos) ? pos : startPos; + } + if (leadingTriviaOption === LeadingTriviaOption.JSDoc) { + const JSDocComments = getJSDocCommentRanges(node, sourceFile.text); + if (JSDocComments?.length) { + return getLineStartPositionForPosition(JSDocComments[0].pos, sourceFile); + } + } + const fullStart = node.getFullStart(); + const start = node.getStart(sourceFile); + if (fullStart === start) { + return start; + } + const fullStartLine = getLineStartPositionForPosition(fullStart, sourceFile); + const startLine = getLineStartPositionForPosition(start, sourceFile); + if (startLine === fullStartLine) { + // full start and start of the node are on the same line + // a, b; + // ^ ^ + // | start + // fullstart + // when b is replaced - we usually want to keep the leading trvia + // when b is deleted - we delete it + return leadingTriviaOption === LeadingTriviaOption.IncludeAll ? fullStart : start; } - function getEnd(n: TextRange): number { - const result = (n as any).__end; - Debug.assert(typeof result === "number"); - return result; + // if node has a trailing comments, use comment end position as the text has already been included. + if (hasTrailingComment) { + // Check first for leading comments as if the node is the first import, we want to exclude the trivia; + // otherwise we get the trailing comments. + const comment = getLeadingCommentRanges(sourceFile.text, fullStart)?.[0] || getTrailingCommentRanges(sourceFile.text, fullStart)?.[0]; + if (comment) { + return skipTrivia(sourceFile.text, comment.end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ true); + } } - function setEnd(n: TextRange, end: number): void { - Debug.assert(typeof end === "number"); - (n as any).__end = end; + // get start position of the line following the line that contains fullstart position + // (but only if the fullstart isn't the very beginning of the file) + const nextLineStart = fullStart > 0 ? 1 : 0; + let adjustedStartPosition = getStartPositionOfLine(getLineOfLocalPosition(sourceFile, fullStartLine) + nextLineStart, sourceFile); + // skip whitespaces/newlines + adjustedStartPosition = skipWhitespacesAndLineBreaks(sourceFile.text, adjustedStartPosition); + return getStartPositionOfLine(getLineOfLocalPosition(sourceFile, adjustedStartPosition), sourceFile); +} + +/** Return the end position of a multiline comment of it is on another line; otherwise returns `undefined`; */ +function getEndPositionOfMultilineTrailingComment(sourceFile: SourceFile, node: Node, options: ConfigurableEnd): number | undefined { + const { end } = node; + const { trailingTriviaOption } = options; + if (trailingTriviaOption === TrailingTriviaOption.Include) { + // If the trailing comment is a multiline comment that extends to the next lines, + // return the end of the comment and track it for the next nodes to adjust. + const comments = getTrailingCommentRanges(sourceFile.text, end); + if (comments) { + const nodeEndLine = getLineOfLocalPosition(sourceFile, node.end); + for (const comment of comments) { + // Single line can break the loop as trivia will only be this line. + // Comments on subsequest lines are also ignored. + if (comment.kind === SyntaxKind.SingleLineCommentTrivia || getLineOfLocalPosition(sourceFile, comment.pos) > nodeEndLine) { + break; + } + + // Get the end line of the comment and compare against the end line of the node. + // If the comment end line position and the multiline comment extends to multiple lines, + // then is safe to return the end position. + const commentEndLine = getLineOfLocalPosition(sourceFile, comment.end); + if (commentEndLine > nodeEndLine) { + return skipTrivia(sourceFile.text, comment.end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ true); + } + } + } } - export interface ConfigurableStart { - leadingTriviaOption?: LeadingTriviaOption; + return undefined; +} + +function getAdjustedEndPosition(sourceFile: SourceFile, node: Node, options: ConfigurableEnd): number { + const { end } = node; + const { trailingTriviaOption } = options; + if (trailingTriviaOption === TrailingTriviaOption.Exclude) { + return end; } - export interface ConfigurableEnd { - trailingTriviaOption?: TrailingTriviaOption; + if (trailingTriviaOption === TrailingTriviaOption.ExcludeWhitespace) { + const comments = concatenate(getTrailingCommentRanges(sourceFile.text, end), getLeadingCommentRanges(sourceFile.text, end)); + const realEnd = comments?.[comments.length - 1]?.end; + if (realEnd) { + return realEnd; + } + return end; } - export enum LeadingTriviaOption { - /** Exclude all leading trivia (use getStart()) */ - Exclude, - /** Include leading trivia and, - * if there are no line breaks between the node and the previous token, - * include all trivia between the node and the previous token - */ - IncludeAll, - /** - * Include attached JSDoc comments - */ - JSDoc, - /** - * Only delete trivia on the same line as getStart(). - * Used to avoid deleting leading comments - */ - StartLine, + const multilineEndPosition = getEndPositionOfMultilineTrailingComment(sourceFile, node, options); + if (multilineEndPosition) { + return multilineEndPosition; } - export enum TrailingTriviaOption { - /** Exclude all trailing trivia (use getEnd()) */ - Exclude, - /** Doesn't include whitespace, but does strip comments */ - ExcludeWhitespace, - /** Include trailing trivia */ - Include, + const newEnd = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true); + + return newEnd !== end && (trailingTriviaOption === TrailingTriviaOption.Include || isLineBreak(sourceFile.text.charCodeAt(newEnd - 1))) + ? newEnd + : end; +} + +/** + * Checks if 'candidate' argument is a legal separator in the list that contains 'node' as an element + */ +function isSeparator(node: Node, candidate: Node | undefined): candidate is Token { + return !!candidate && !!node.parent && (candidate.kind === SyntaxKind.CommaToken || (candidate.kind === SyntaxKind.SemicolonToken && node.parent.kind === SyntaxKind.ObjectLiteralExpression)); +} + +export interface TextChangesContext { + host: LanguageServiceHost; + formatContext: formatting.FormatContext; + preferences: UserPreferences; +} + +export type TypeAnnotatable = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature; + +export type ThisTypeAnnotatable = FunctionDeclaration | FunctionExpression; + +export function isThisTypeAnnotatable(containingFunction: SignatureDeclaration): containingFunction is ThisTypeAnnotatable { + return isFunctionExpression(containingFunction) || isFunctionDeclaration(containingFunction); +} + +export class ChangeTracker { + private readonly changes: Change[] = []; + private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: readonly (Statement | SyntaxKind.NewLineTrivia)[] }[] = []; + private readonly classesWithNodesInsertedAtStart = new Map(); // Set implemented as Map + private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node | NodeArray }[] = []; + + public static fromContext(context: TextChangesContext): ChangeTracker { + return new ChangeTracker(getNewLineOrDefaultFromHost(context.host, context.formatContext.options), context.formatContext); } - function skipWhitespacesAndLineBreaks(text: string, start: number) { - return skipTrivia(text, start, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + public static with(context: TextChangesContext, cb: (tracker: ChangeTracker) => void): FileTextChanges[] { + const tracker = ChangeTracker.fromContext(context); + cb(tracker); + return tracker.getChanges(); } - function hasCommentsBeforeLineBreak(text: string, start: number) { - let i = start; - while (i < text.length) { - const ch = text.charCodeAt(i); - if (isWhiteSpaceSingleLine(ch)) { - i++; - continue; - } - return ch === CharacterCodes.slash; + /** Public for tests only. Other callers should use `ChangeTracker.with`. */ + constructor(private readonly newLineCharacter: string, private readonly formatContext: formatting.FormatContext) {} + + public pushRaw(sourceFile: SourceFile, change: FileTextChanges) { + Debug.assertEqual(sourceFile.fileName, change.fileName); + for (const c of change.textChanges) { + this.changes.push({ + kind: ChangeKind.Text, + sourceFile, + text: c.newText, + range: createTextRangeFromSpan(c.span), + }); } - return false; } - /** - * Usually node.pos points to a position immediately after the previous token. - * If this position is used as a beginning of the span to remove - it might lead to removing the trailing trivia of the previous node, i.e: - * const x; // this is x - * ^ - pos for the next variable declaration will point here - * const y; // this is y - * ^ - end for previous variable declaration - * Usually leading trivia of the variable declaration 'y' should not include trailing trivia (whitespace, comment 'this is x' and newline) from the preceding - * variable declaration and trailing trivia for 'y' should include (whitespace, comment 'this is y', newline). - * By default when removing nodes we adjust start and end positions to respect specification of the trivia above. - * If pos\end should be interpreted literally (that is, withouth including leading and trailing trivia), `leadingTriviaOption` should be set to `LeadingTriviaOption.Exclude` - * and `trailingTriviaOption` to `TrailingTriviaOption.Exclude`. - */ - export interface ConfigurableStartEnd extends ConfigurableStart, ConfigurableEnd {} - - const useNonAdjustedPositions: ConfigurableStartEnd = { - leadingTriviaOption: LeadingTriviaOption.Exclude, - trailingTriviaOption: TrailingTriviaOption.Exclude, - }; + public deleteRange(sourceFile: SourceFile, range: TextRange): void { + this.changes.push({ kind: ChangeKind.Remove, sourceFile, range }); + } - export interface InsertNodeOptions { - /** - * Text to be inserted before the new node - */ - prefix?: string; - /** - * Text to be inserted after the new node - */ - suffix?: string; - /** - * Text of inserted node will be formatted with this indentation, otherwise indentation will be inferred from the old node - */ - indentation?: number; - /** - * Text of inserted node will be formatted with this delta, otherwise delta will be inferred from the new node kind - */ - delta?: number; - /** - * Do not trim leading white spaces in the edit range - */ - preserveLeadingWhitespace?: boolean; + delete(sourceFile: SourceFile, node: Node | NodeArray): void { + this.deletedNodes.push({ sourceFile, node }); } - export interface ReplaceWithMultipleNodesOptions extends InsertNodeOptions { - readonly joiner?: string; + public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { + this.deleteRange(sourceFile, getAdjustedRange(sourceFile, node, node, options)); } - enum ChangeKind { - Remove, - ReplaceWithSingleNode, - ReplaceWithMultipleNodes, - Text, + public deleteNodes(sourceFile: SourceFile, nodes: readonly Node[], options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }, hasTrailingComment: boolean): void { + // When deleting multiple nodes we need to track if the end position is including multiline trailing comments. + for (const node of nodes) { + const pos = getAdjustedStartPosition(sourceFile, node, options, hasTrailingComment); + const end = getAdjustedEndPosition(sourceFile, node, options); + + this.deleteRange(sourceFile, { pos, end }); + + hasTrailingComment = !!getEndPositionOfMultilineTrailingComment(sourceFile, node, options); + } } - type Change = ReplaceWithSingleNode | ReplaceWithMultipleNodes | RemoveNode | ChangeText; + public deleteModifier(sourceFile: SourceFile, modifier: Modifier): void { + this.deleteRange(sourceFile, { pos: modifier.getStart(sourceFile), end: skipTrivia(sourceFile.text, modifier.end, /*stopAfterLineBreak*/ true) }); + } - interface BaseChange { - readonly sourceFile: SourceFile; - readonly range: TextRange; + public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { + const startPosition = getAdjustedStartPosition(sourceFile, startNode, options); + const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); + this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); } - export interface ChangeNodeOptions extends ConfigurableStartEnd, InsertNodeOptions {} - interface ReplaceWithSingleNode extends BaseChange { - readonly kind: ChangeKind.ReplaceWithSingleNode; - readonly node: Node; - readonly options?: InsertNodeOptions; + public deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { + const startPosition = getAdjustedStartPosition(sourceFile, startNode, options); + const endPosition = afterEndNode === undefined ? sourceFile.text.length : getAdjustedStartPosition(sourceFile, afterEndNode, options); + this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); } - interface RemoveNode extends BaseChange { - readonly kind: ChangeKind.Remove; - readonly node?: never; - readonly options?: never; + public replaceRange(sourceFile: SourceFile, range: TextRange, newNode: Node, options: InsertNodeOptions = {}): void { + this.changes.push({ kind: ChangeKind.ReplaceWithSingleNode, sourceFile, range, options, node: newNode }); } - interface ReplaceWithMultipleNodes extends BaseChange { - readonly kind: ChangeKind.ReplaceWithMultipleNodes; - readonly nodes: readonly Node[]; - readonly options?: ReplaceWithMultipleNodesOptions; + public replaceNode(sourceFile: SourceFile, oldNode: Node, newNode: Node, options: ChangeNodeOptions = useNonAdjustedPositions): void { + this.replaceRange(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, options), newNode, options); } - interface ChangeText extends BaseChange { - readonly kind: ChangeKind.Text; - readonly text: string; + public replaceNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, newNode: Node, options: ChangeNodeOptions = useNonAdjustedPositions): void { + this.replaceRange(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNode, options); } - function getAdjustedRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd): TextRange { - return { pos: getAdjustedStartPosition(sourceFile, startNode, options), end: getAdjustedEndPosition(sourceFile, endNode, options) }; + private replaceRangeWithNodes(sourceFile: SourceFile, range: TextRange, newNodes: readonly Node[], options: ReplaceWithMultipleNodesOptions & ConfigurableStartEnd = {}): void { + this.changes.push({ kind: ChangeKind.ReplaceWithMultipleNodes, sourceFile, range, options, nodes: newNodes }); } - function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd, hasTrailingComment = false) { - const { leadingTriviaOption } = options; - if (leadingTriviaOption === LeadingTriviaOption.Exclude) { - return node.getStart(sourceFile); - } - if (leadingTriviaOption === LeadingTriviaOption.StartLine) { - const startPos = node.getStart(sourceFile); - const pos = getLineStartPositionForPosition(startPos, sourceFile); - return rangeContainsPosition(node, pos) ? pos : startPos; - } - if (leadingTriviaOption === LeadingTriviaOption.JSDoc) { - const JSDocComments = getJSDocCommentRanges(node, sourceFile.text); - if (JSDocComments?.length) { - return getLineStartPositionForPosition(JSDocComments[0].pos, sourceFile); - } - } - const fullStart = node.getFullStart(); - const start = node.getStart(sourceFile); - if (fullStart === start) { - return start; - } - const fullStartLine = getLineStartPositionForPosition(fullStart, sourceFile); - const startLine = getLineStartPositionForPosition(start, sourceFile); - if (startLine === fullStartLine) { - // full start and start of the node are on the same line - // a, b; - // ^ ^ - // | start - // fullstart - // when b is replaced - we usually want to keep the leading trvia - // when b is deleted - we delete it - return leadingTriviaOption === LeadingTriviaOption.IncludeAll ? fullStart : start; - } + public replaceNodeWithNodes(sourceFile: SourceFile, oldNode: Node, newNodes: readonly Node[], options: ChangeNodeOptions = useNonAdjustedPositions): void { + this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, options), newNodes, options); + } - // if node has a trailing comments, use comment end position as the text has already been included. - if (hasTrailingComment) { - // Check first for leading comments as if the node is the first import, we want to exclude the trivia; - // otherwise we get the trailing comments. - const comment = getLeadingCommentRanges(sourceFile.text, fullStart)?.[0] || getTrailingCommentRanges(sourceFile.text, fullStart)?.[0]; - if (comment) { - return skipTrivia(sourceFile.text, comment.end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ true); - } - } + public replaceNodeWithText(sourceFile: SourceFile, oldNode: Node, text: string): void { + this.replaceRangeWithText(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, useNonAdjustedPositions), text); + } - // get start position of the line following the line that contains fullstart position - // (but only if the fullstart isn't the very beginning of the file) - const nextLineStart = fullStart > 0 ? 1 : 0; - let adjustedStartPosition = getStartPositionOfLine(getLineOfLocalPosition(sourceFile, fullStartLine) + nextLineStart, sourceFile); - // skip whitespaces/newlines - adjustedStartPosition = skipWhitespacesAndLineBreaks(sourceFile.text, adjustedStartPosition); - return getStartPositionOfLine(getLineOfLocalPosition(sourceFile, adjustedStartPosition), sourceFile); - } - - /** Return the end position of a multiline comment of it is on another line; otherwise returns `undefined`; */ - function getEndPositionOfMultilineTrailingComment(sourceFile: SourceFile, node: Node, options: ConfigurableEnd): number | undefined { - const { end } = node; - const { trailingTriviaOption } = options; - if (trailingTriviaOption === TrailingTriviaOption.Include) { - // If the trailing comment is a multiline comment that extends to the next lines, - // return the end of the comment and track it for the next nodes to adjust. - const comments = getTrailingCommentRanges(sourceFile.text, end); - if (comments) { - const nodeEndLine = getLineOfLocalPosition(sourceFile, node.end); - for (const comment of comments) { - // Single line can break the loop as trivia will only be this line. - // Comments on subsequest lines are also ignored. - if (comment.kind === SyntaxKind.SingleLineCommentTrivia || getLineOfLocalPosition(sourceFile, comment.pos) > nodeEndLine) { - break; - } - - // Get the end line of the comment and compare against the end line of the node. - // If the comment end line position and the multiline comment extends to multiple lines, - // then is safe to return the end position. - const commentEndLine = getLineOfLocalPosition(sourceFile, comment.end); - if (commentEndLine > nodeEndLine) { - return skipTrivia(sourceFile.text, comment.end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ true); - } - } - } - } + public replaceNodeRangeWithNodes(sourceFile: SourceFile, startNode: Node, endNode: Node, newNodes: readonly Node[], options: ReplaceWithMultipleNodesOptions & ConfigurableStartEnd = useNonAdjustedPositions): void { + this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNodes, options); + } - return undefined; + public nodeHasTrailingComment(sourceFile: SourceFile, oldNode: Node, configurableEnd: ConfigurableEnd = useNonAdjustedPositions): boolean { + return !!getEndPositionOfMultilineTrailingComment(sourceFile, oldNode, configurableEnd); } - function getAdjustedEndPosition(sourceFile: SourceFile, node: Node, options: ConfigurableEnd): number { - const { end } = node; - const { trailingTriviaOption } = options; - if (trailingTriviaOption === TrailingTriviaOption.Exclude) { - return end; - } - if (trailingTriviaOption === TrailingTriviaOption.ExcludeWhitespace) { - const comments = concatenate(getTrailingCommentRanges(sourceFile.text, end), getLeadingCommentRanges(sourceFile.text, end)); - const realEnd = comments?.[comments.length - 1]?.end; - if (realEnd) { - return realEnd; - } - return end; - } + private nextCommaToken(sourceFile: SourceFile, node: Node): Node | undefined { + const next = findNextToken(node, node.parent, sourceFile); + return next && next.kind === SyntaxKind.CommaToken ? next : undefined; + } - const multilineEndPosition = getEndPositionOfMultilineTrailingComment(sourceFile, node, options); - if (multilineEndPosition) { - return multilineEndPosition; - } + public replacePropertyAssignment(sourceFile: SourceFile, oldNode: PropertyAssignment, newNode: PropertyAssignment): void { + const suffix = this.nextCommaToken(sourceFile, oldNode) ? "" : ("," + this.newLineCharacter); + this.replaceNode(sourceFile, oldNode, newNode, { suffix }); + } - const newEnd = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true); + public insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}): void { + this.replaceRange(sourceFile, createRange(pos), newNode, options); + } - return newEnd !== end && (trailingTriviaOption === TrailingTriviaOption.Include || isLineBreak(sourceFile.text.charCodeAt(newEnd - 1))) - ? newEnd - : end; + private insertNodesAt(sourceFile: SourceFile, pos: number, newNodes: readonly Node[], options: ReplaceWithMultipleNodesOptions = {}): void { + this.replaceRangeWithNodes(sourceFile, createRange(pos), newNodes, options); } - /** - * Checks if 'candidate' argument is a legal separator in the list that contains 'node' as an element - */ - function isSeparator(node: Node, candidate: Node | undefined): candidate is Token { - return !!candidate && !!node.parent && (candidate.kind === SyntaxKind.CommaToken || (candidate.kind === SyntaxKind.SemicolonToken && node.parent.kind === SyntaxKind.ObjectLiteralExpression)); + public insertNodeAtTopOfFile(sourceFile: SourceFile, newNode: Statement, blankLineBetween: boolean): void { + this.insertAtTopOfFile(sourceFile, newNode, blankLineBetween); } - export interface TextChangesContext { - host: LanguageServiceHost; - formatContext: formatting.FormatContext; - preferences: UserPreferences; + public insertNodesAtTopOfFile(sourceFile: SourceFile, newNodes: readonly Statement[], blankLineBetween: boolean): void { + this.insertAtTopOfFile(sourceFile, newNodes, blankLineBetween); } - export type TypeAnnotatable = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature; + private insertAtTopOfFile(sourceFile: SourceFile, insert: Statement | readonly Statement[], blankLineBetween: boolean): void { + const pos = getInsertionPositionAtSourceFileTop(sourceFile); + const options = { + prefix: pos === 0 ? undefined : this.newLineCharacter, + suffix: (isLineBreak(sourceFile.text.charCodeAt(pos)) ? "" : this.newLineCharacter) + (blankLineBetween ? this.newLineCharacter : ""), + }; + if (isArray(insert)) { + this.insertNodesAt(sourceFile, pos, insert, options); + } + else { + this.insertNodeAt(sourceFile, pos, insert, options); + } + } - export type ThisTypeAnnotatable = FunctionDeclaration | FunctionExpression; + public insertFirstParameter(sourceFile: SourceFile, parameters: NodeArray, newParam: ParameterDeclaration): void { + const p0 = firstOrUndefined(parameters); + if (p0) { + this.insertNodeBefore(sourceFile, p0, newParam); + } + else { + this.insertNodeAt(sourceFile, parameters.pos, newParam); + } + } - export function isThisTypeAnnotatable(containingFunction: SignatureDeclaration): containingFunction is ThisTypeAnnotatable { - return isFunctionExpression(containingFunction) || isFunctionDeclaration(containingFunction); + public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, blankLineBetween = false, options: ConfigurableStartEnd = {}): void { + this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, options), newNode, this.getOptionsForInsertNodeBefore(before, newNode, blankLineBetween)); } - export class ChangeTracker { - private readonly changes: Change[] = []; - private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: readonly (Statement | SyntaxKind.NewLineTrivia)[] }[] = []; - private readonly classesWithNodesInsertedAtStart = new Map(); // Set implemented as Map - private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node | NodeArray }[] = []; + public insertModifierAt(sourceFile: SourceFile, pos: number, modifier: SyntaxKind, options: InsertNodeOptions = {}): void { + this.insertNodeAt(sourceFile, pos, factory.createToken(modifier), options); + } - public static fromContext(context: TextChangesContext): ChangeTracker { - return new ChangeTracker(getNewLineOrDefaultFromHost(context.host, context.formatContext.options), context.formatContext); - } + public insertModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void { + return this.insertModifierAt(sourceFile, before.getStart(sourceFile), modifier, { suffix: " " }); + } - public static with(context: TextChangesContext, cb: (tracker: ChangeTracker) => void): FileTextChanges[] { - const tracker = ChangeTracker.fromContext(context); - cb(tracker); - return tracker.getChanges(); - } + public insertCommentBeforeLine(sourceFile: SourceFile, lineNumber: number, position: number, commentText: string): void { + const lineStartPosition = getStartPositionOfLine(lineNumber, sourceFile); + const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition); + // First try to see if we can put the comment on the previous line. + // We need to make sure that we are not in the middle of a string literal or a comment. + // If so, we do not want to separate the node from its comment if we can. + // Otherwise, add an extra new line immediately before the error span. + const insertAtLineStart = isValidLocationToAddComment(sourceFile, startPosition); + const token = getTouchingToken(sourceFile, insertAtLineStart ? startPosition : position); + const indent = sourceFile.text.slice(lineStartPosition, startPosition); + const text = `${insertAtLineStart ? "" : this.newLineCharacter}//${commentText}${this.newLineCharacter}${indent}`; + this.insertText(sourceFile, token.getStart(sourceFile), text); + } - /** Public for tests only. Other callers should use `ChangeTracker.with`. */ - constructor(private readonly newLineCharacter: string, private readonly formatContext: formatting.FormatContext) {} - - public pushRaw(sourceFile: SourceFile, change: FileTextChanges) { - Debug.assertEqual(sourceFile.fileName, change.fileName); - for (const c of change.textChanges) { - this.changes.push({ - kind: ChangeKind.Text, - sourceFile, - text: c.newText, - range: createTextRangeFromSpan(c.span), + public insertJsdocCommentBefore(sourceFile: SourceFile, node: HasJSDoc, tag: JSDoc): void { + const fnStart = node.getStart(sourceFile); + if (node.jsDoc) { + for (const jsdoc of node.jsDoc) { + this.deleteRange(sourceFile, { + pos: getLineStartPositionForPosition(jsdoc.getStart(sourceFile), sourceFile), + end: getAdjustedEndPosition(sourceFile, jsdoc, /*options*/ {}) }); } } + const startPosition = getPrecedingNonSpaceCharacterPosition(sourceFile.text, fnStart - 1); + const indent = sourceFile.text.slice(startPosition, fnStart); + this.insertNodeAt(sourceFile, fnStart, tag, { preserveLeadingWhitespace: false, suffix: this.newLineCharacter + indent }); + } - public deleteRange(sourceFile: SourceFile, range: TextRange): void { - this.changes.push({ kind: ChangeKind.Remove, sourceFile, range }); - } - - delete(sourceFile: SourceFile, node: Node | NodeArray): void { - this.deletedNodes.push({ sourceFile, node }); - } + public addJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, newTags: readonly JSDocTag[]): void { + const comments = flatMap(parent.jsDoc, j => typeof j.comment === "string" ? factory.createJSDocText(j.comment) : j.comment) as JSDocComment[]; + const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags); + const unmergedNewTags = newTags.filter(newTag => !oldTags.some((tag, i) => { + const merged = tryMergeJsdocTags(tag, newTag); + if (merged) oldTags[i] = merged; + return !!merged; + })); + const tag = factory.createJSDocComment(factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))), factory.createNodeArray([...oldTags, ...unmergedNewTags])); + const host = updateJSDocHost(parent); + this.insertJsdocCommentBefore(sourceFile, host, tag); + } - public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { - this.deleteRange(sourceFile, getAdjustedRange(sourceFile, node, node, options)); - } + public filterJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, predicate: (tag: JSDocTag) => boolean): void { + const comments = flatMap(parent.jsDoc, j => typeof j.comment === "string" ? factory.createJSDocText(j.comment) : j.comment) as JSDocComment[]; + const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags); + const tag = factory.createJSDocComment(factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))), factory.createNodeArray([...(filter(oldTags, predicate) || emptyArray)])); + const host = updateJSDocHost(parent); + this.insertJsdocCommentBefore(sourceFile, host, tag); + } - public deleteNodes(sourceFile: SourceFile, nodes: readonly Node[], options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }, hasTrailingComment: boolean): void { - // When deleting multiple nodes we need to track if the end position is including multiline trailing comments. - for (const node of nodes) { - const pos = getAdjustedStartPosition(sourceFile, node, options, hasTrailingComment); - const end = getAdjustedEndPosition(sourceFile, node, options); + public replaceRangeWithText(sourceFile: SourceFile, range: TextRange, text: string): void { + this.changes.push({ kind: ChangeKind.Text, sourceFile, range, text }); + } - this.deleteRange(sourceFile, { pos, end }); + public insertText(sourceFile: SourceFile, pos: number, text: string): void { + this.replaceRangeWithText(sourceFile, createRange(pos), text); + } - hasTrailingComment = !!getEndPositionOfMultilineTrailingComment(sourceFile, node, options); + /** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */ + public tryInsertTypeAnnotation(sourceFile: SourceFile, node: TypeAnnotatable, type: TypeNode): boolean { + let endNode: Node | undefined; + if (isFunctionLike(node)) { + endNode = findChildOfKind(node, SyntaxKind.CloseParenToken, sourceFile); + if (!endNode) { + if (!isArrowFunction(node)) return false; // Function missing parentheses, give up + // If no `)`, is an arrow function `x => x`, so use the end of the first parameter + endNode = first(node.parameters); } } - - public deleteModifier(sourceFile: SourceFile, modifier: Modifier): void { - this.deleteRange(sourceFile, { pos: modifier.getStart(sourceFile), end: skipTrivia(sourceFile.text, modifier.end, /*stopAfterLineBreak*/ true) }); + else { + endNode = (node.kind === SyntaxKind.VariableDeclaration ? node.exclamationToken : node.questionToken) ?? node.name; } - public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { - const startPosition = getAdjustedStartPosition(sourceFile, startNode, options); - const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); - this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); - } + this.insertNodeAt(sourceFile, endNode.end, type, { prefix: ": " }); + return true; + } - public deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { - const startPosition = getAdjustedStartPosition(sourceFile, startNode, options); - const endPosition = afterEndNode === undefined ? sourceFile.text.length : getAdjustedStartPosition(sourceFile, afterEndNode, options); - this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); - } + public tryInsertThisTypeAnnotation(sourceFile: SourceFile, node: ThisTypeAnnotatable, type: TypeNode): void { + const start = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile)!.getStart(sourceFile) + 1; + const suffix = node.parameters.length ? ", " : ""; - public replaceRange(sourceFile: SourceFile, range: TextRange, newNode: Node, options: InsertNodeOptions = {}): void { - this.changes.push({ kind: ChangeKind.ReplaceWithSingleNode, sourceFile, range, options, node: newNode }); - } + this.insertNodeAt(sourceFile, start, type, { prefix: "this: ", suffix }); + } - public replaceNode(sourceFile: SourceFile, oldNode: Node, newNode: Node, options: ChangeNodeOptions = useNonAdjustedPositions): void { - this.replaceRange(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, options), newNode, options); - } + public insertTypeParameters(sourceFile: SourceFile, node: SignatureDeclaration, typeParameters: readonly TypeParameterDeclaration[]): void { + // If no `(`, is an arrow function `x => x`, so use the pos of the first parameter + const start = (findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile) || first(node.parameters)).getStart(sourceFile); + this.insertNodesAt(sourceFile, start, typeParameters, { prefix: "<", suffix: ">", joiner: ", " }); + } - public replaceNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, newNode: Node, options: ChangeNodeOptions = useNonAdjustedPositions): void { - this.replaceRange(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNode, options); + private getOptionsForInsertNodeBefore(before: Node, inserted: Node, blankLineBetween: boolean): InsertNodeOptions { + if (isStatement(before) || isClassElement(before)) { + return { suffix: blankLineBetween ? this.newLineCharacter + this.newLineCharacter : this.newLineCharacter }; } - - private replaceRangeWithNodes(sourceFile: SourceFile, range: TextRange, newNodes: readonly Node[], options: ReplaceWithMultipleNodesOptions & ConfigurableStartEnd = {}): void { - this.changes.push({ kind: ChangeKind.ReplaceWithMultipleNodes, sourceFile, range, options, nodes: newNodes }); + else if (isVariableDeclaration(before)) { // insert `x = 1, ` into `const x = 1, y = 2; + return { suffix: ", " }; } - - public replaceNodeWithNodes(sourceFile: SourceFile, oldNode: Node, newNodes: readonly Node[], options: ChangeNodeOptions = useNonAdjustedPositions): void { - this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, options), newNodes, options); + else if (isParameter(before)) { + return isParameter(inserted) ? { suffix: ", " } : {}; } - - public replaceNodeWithText(sourceFile: SourceFile, oldNode: Node, text: string): void { - this.replaceRangeWithText(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, useNonAdjustedPositions), text); + else if (isStringLiteral(before) && isImportDeclaration(before.parent) || isNamedImports(before)) { + return { suffix: ", " }; } - - public replaceNodeRangeWithNodes(sourceFile: SourceFile, startNode: Node, endNode: Node, newNodes: readonly Node[], options: ReplaceWithMultipleNodesOptions & ConfigurableStartEnd = useNonAdjustedPositions): void { - this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNodes, options); + else if (isImportSpecifier(before)) { + return { suffix: "," + (blankLineBetween ? this.newLineCharacter : " ") }; } + return Debug.failBadSyntaxKind(before); // We haven't handled this kind of node yet -- add it + } - public nodeHasTrailingComment(sourceFile: SourceFile, oldNode: Node, configurableEnd: ConfigurableEnd = useNonAdjustedPositions): boolean { - return !!getEndPositionOfMultilineTrailingComment(sourceFile, oldNode, configurableEnd); + public insertNodeAtConstructorStart(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { + const firstStatement = firstOrUndefined(ctr.body!.statements); + if (!firstStatement || !ctr.body!.multiLine) { + this.replaceConstructorBody(sourceFile, ctr, [newStatement, ...ctr.body!.statements]); } - - private nextCommaToken(sourceFile: SourceFile, node: Node): Node | undefined { - const next = findNextToken(node, node.parent, sourceFile); - return next && next.kind === SyntaxKind.CommaToken ? next : undefined; + else { + this.insertNodeBefore(sourceFile, firstStatement, newStatement); } + } - public replacePropertyAssignment(sourceFile: SourceFile, oldNode: PropertyAssignment, newNode: PropertyAssignment): void { - const suffix = this.nextCommaToken(sourceFile, oldNode) ? "" : ("," + this.newLineCharacter); - this.replaceNode(sourceFile, oldNode, newNode, { suffix }); + public insertNodeAtConstructorStartAfterSuperCall(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { + const superCallStatement = find(ctr.body!.statements, stmt => isExpressionStatement(stmt) && isSuperCall(stmt.expression)); + if (!superCallStatement || !ctr.body!.multiLine) { + this.replaceConstructorBody(sourceFile, ctr, [...ctr.body!.statements, newStatement]); } - - public insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}): void { - this.replaceRange(sourceFile, createRange(pos), newNode, options); + else { + this.insertNodeAfter(sourceFile, superCallStatement, newStatement); } + } - private insertNodesAt(sourceFile: SourceFile, pos: number, newNodes: readonly Node[], options: ReplaceWithMultipleNodesOptions = {}): void { - this.replaceRangeWithNodes(sourceFile, createRange(pos), newNodes, options); + public insertNodeAtConstructorEnd(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { + const lastStatement = lastOrUndefined(ctr.body!.statements); + if (!lastStatement || !ctr.body!.multiLine) { + this.replaceConstructorBody(sourceFile, ctr, [...ctr.body!.statements, newStatement]); } - - public insertNodeAtTopOfFile(sourceFile: SourceFile, newNode: Statement, blankLineBetween: boolean): void { - this.insertAtTopOfFile(sourceFile, newNode, blankLineBetween); + else { + this.insertNodeAfter(sourceFile, lastStatement, newStatement); } + } - public insertNodesAtTopOfFile(sourceFile: SourceFile, newNodes: readonly Statement[], blankLineBetween: boolean): void { - this.insertAtTopOfFile(sourceFile, newNodes, blankLineBetween); - } + private replaceConstructorBody(sourceFile: SourceFile, ctr: ConstructorDeclaration, statements: readonly Statement[]): void { + this.replaceNode(sourceFile, ctr.body!, factory.createBlock(statements, /*multiLine*/ true)); + } - private insertAtTopOfFile(sourceFile: SourceFile, insert: Statement | readonly Statement[], blankLineBetween: boolean): void { - const pos = getInsertionPositionAtSourceFileTop(sourceFile); - const options = { - prefix: pos === 0 ? undefined : this.newLineCharacter, - suffix: (isLineBreak(sourceFile.text.charCodeAt(pos)) ? "" : this.newLineCharacter) + (blankLineBetween ? this.newLineCharacter : ""), - }; - if (isArray(insert)) { - this.insertNodesAt(sourceFile, pos, insert, options); - } - else { - this.insertNodeAt(sourceFile, pos, insert, options); - } - } + public insertNodeAtEndOfScope(sourceFile: SourceFile, scope: Node, newNode: Node): void { + const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {}); + this.insertNodeAt(sourceFile, pos, newNode, { + prefix: isLineBreak(sourceFile.text.charCodeAt(scope.getLastToken()!.pos)) ? this.newLineCharacter : this.newLineCharacter + this.newLineCharacter, + suffix: this.newLineCharacter + }); + } + + public insertNodeAtClassStart(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration, newElement: ClassElement): void { + this.insertNodeAtStartWorker(sourceFile, cls, newElement); + } + + public insertNodeAtObjectStart(sourceFile: SourceFile, obj: ObjectLiteralExpression, newElement: ObjectLiteralElementLike): void { + this.insertNodeAtStartWorker(sourceFile, obj, newElement); + } + + private insertNodeAtStartWorker(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, newElement: ClassElement | ObjectLiteralElementLike): void { + const indentation = this.guessIndentationFromExistingMembers(sourceFile, cls) ?? this.computeIndentationForNewMember(sourceFile, cls); + this.insertNodeAt(sourceFile, getMembersOrProperties(cls).pos, newElement, this.getInsertNodeAtStartInsertOptions(sourceFile, cls, indentation)); + } - public insertFirstParameter(sourceFile: SourceFile, parameters: NodeArray, newParam: ParameterDeclaration): void { - const p0 = firstOrUndefined(parameters); - if (p0) { - this.insertNodeBefore(sourceFile, p0, newParam); + /** + * Tries to guess the indentation from the existing members of a class/interface/object. All members must be on + * new lines and must share the same indentation. + */ + private guessIndentationFromExistingMembers(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression) { + let indentation: number | undefined; + let lastRange: TextRange = cls; + for (const member of getMembersOrProperties(cls)) { + if (rangeStartPositionsAreOnSameLine(lastRange, member, sourceFile)) { + // each indented member must be on a new line + return undefined; } - else { - this.insertNodeAt(sourceFile, parameters.pos, newParam); + const memberStart = member.getStart(sourceFile); + const memberIndentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(getLineStartPositionForPosition(memberStart, sourceFile), memberStart, sourceFile, this.formatContext.options); + if (indentation === undefined) { + indentation = memberIndentation; } + else if (memberIndentation !== indentation) { + // indentation of multiple members is not consistent + return undefined; + } + lastRange = member; } + return indentation; + } - public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, blankLineBetween = false, options: ConfigurableStartEnd = {}): void { - this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, options), newNode, this.getOptionsForInsertNodeBefore(before, newNode, blankLineBetween)); - } + private computeIndentationForNewMember(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression) { + const clsStart = cls.getStart(sourceFile); + return formatting.SmartIndenter.findFirstNonWhitespaceColumn(getLineStartPositionForPosition(clsStart, sourceFile), clsStart, sourceFile, this.formatContext.options) + + (this.formatContext.options.indentSize ?? 4); + } - public insertModifierAt(sourceFile: SourceFile, pos: number, modifier: SyntaxKind, options: InsertNodeOptions = {}): void { - this.insertNodeAt(sourceFile, pos, factory.createToken(modifier), options); - } + private getInsertNodeAtStartInsertOptions(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, indentation: number): InsertNodeOptions { + // Rules: + // - Always insert leading newline. + // - For object literals: + // - Add a trailing comma if there are existing members in the node, or the source file is not a JSON file + // (because trailing commas are generally illegal in a JSON file). + // - Add a leading comma if the source file is not a JSON file, there are existing insertions, + // and the node is empty (because we didn't add a trailing comma per the previous rule). + // - Only insert a trailing newline if body is single-line and there are no other insertions for the node. + // NOTE: This is handled in `finishClassesWithNodesInsertedAtStart`. + + const members = getMembersOrProperties(cls); + const isEmpty = members.length === 0; + const isFirstInsertion = addToSeen(this.classesWithNodesInsertedAtStart, getNodeId(cls), { node: cls, sourceFile }); + const insertTrailingComma = isObjectLiteralExpression(cls) && (!isJsonSourceFile(sourceFile) || !isEmpty); + const insertLeadingComma = isObjectLiteralExpression(cls) && isJsonSourceFile(sourceFile) && isEmpty && !isFirstInsertion; + return { + indentation, + prefix: (insertLeadingComma ? "," : "") + this.newLineCharacter, + suffix: insertTrailingComma ? "," : "" + }; + } - public insertModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void { - return this.insertModifierAt(sourceFile, before.getStart(sourceFile), modifier, { suffix: " " }); - } + public insertNodeAfterComma(sourceFile: SourceFile, after: Node, newNode: Node): void { + const endPosition = this.insertNodeAfterWorker(sourceFile, this.nextCommaToken(sourceFile, after) || after, newNode); + this.insertNodeAt(sourceFile, endPosition, newNode, this.getInsertNodeAfterOptions(sourceFile, after)); + } - public insertCommentBeforeLine(sourceFile: SourceFile, lineNumber: number, position: number, commentText: string): void { - const lineStartPosition = getStartPositionOfLine(lineNumber, sourceFile); - const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition); - // First try to see if we can put the comment on the previous line. - // We need to make sure that we are not in the middle of a string literal or a comment. - // If so, we do not want to separate the node from its comment if we can. - // Otherwise, add an extra new line immediately before the error span. - const insertAtLineStart = isValidLocationToAddComment(sourceFile, startPosition); - const token = getTouchingToken(sourceFile, insertAtLineStart ? startPosition : position); - const indent = sourceFile.text.slice(lineStartPosition, startPosition); - const text = `${insertAtLineStart ? "" : this.newLineCharacter}//${commentText}${this.newLineCharacter}${indent}`; - this.insertText(sourceFile, token.getStart(sourceFile), text); - } + public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node): void { + const endPosition = this.insertNodeAfterWorker(sourceFile, after, newNode); + this.insertNodeAt(sourceFile, endPosition, newNode, this.getInsertNodeAfterOptions(sourceFile, after)); + } - public insertJsdocCommentBefore(sourceFile: SourceFile, node: HasJSDoc, tag: JSDoc): void { - const fnStart = node.getStart(sourceFile); - if (node.jsDoc) { - for (const jsdoc of node.jsDoc) { - this.deleteRange(sourceFile, { - pos: getLineStartPositionForPosition(jsdoc.getStart(sourceFile), sourceFile), - end: getAdjustedEndPosition(sourceFile, jsdoc, /*options*/ {}) - }); - } + public insertNodeAtEndOfList(sourceFile: SourceFile, list: NodeArray, newNode: Node): void { + this.insertNodeAt(sourceFile, list.end, newNode, { prefix: ", " }); + } + + public insertNodesAfter(sourceFile: SourceFile, after: Node, newNodes: readonly Node[]): void { + const endPosition = this.insertNodeAfterWorker(sourceFile, after, first(newNodes)); + this.insertNodesAt(sourceFile, endPosition, newNodes, this.getInsertNodeAfterOptions(sourceFile, after)); + } + + private insertNodeAfterWorker(sourceFile: SourceFile, after: Node, newNode: Node): number { + if (needSemicolonBetween(after, newNode)) { + // check if previous statement ends with semicolon + // if not - insert semicolon to preserve the code from changing the meaning due to ASI + if (sourceFile.text.charCodeAt(after.end - 1) !== CharacterCodes.semicolon) { + this.replaceRange(sourceFile, createRange(after.end), factory.createToken(SyntaxKind.SemicolonToken)); } - const startPosition = getPrecedingNonSpaceCharacterPosition(sourceFile.text, fnStart - 1); - const indent = sourceFile.text.slice(startPosition, fnStart); - this.insertNodeAt(sourceFile, fnStart, tag, { preserveLeadingWhitespace: false, suffix: this.newLineCharacter + indent }); } + const endPosition = getAdjustedEndPosition(sourceFile, after, {}); + return endPosition; + } - public addJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, newTags: readonly JSDocTag[]): void { - const comments = flatMap(parent.jsDoc, j => typeof j.comment === "string" ? factory.createJSDocText(j.comment) : j.comment) as JSDocComment[]; - const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags); - const unmergedNewTags = newTags.filter(newTag => !oldTags.some((tag, i) => { - const merged = tryMergeJsdocTags(tag, newTag); - if (merged) oldTags[i] = merged; - return !!merged; - })); - const tag = factory.createJSDocComment(factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))), factory.createNodeArray([...oldTags, ...unmergedNewTags])); - const host = updateJSDocHost(parent); - this.insertJsdocCommentBefore(sourceFile, host, tag); - } + private getInsertNodeAfterOptions(sourceFile: SourceFile, after: Node): InsertNodeOptions { + const options = this.getInsertNodeAfterOptionsWorker(after); + return { + ...options, + prefix: after.end === sourceFile.end && isStatement(after) ? (options.prefix ? `\n${options.prefix}` : "\n") : options.prefix, + }; + } - public filterJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, predicate: (tag: JSDocTag) => boolean): void { - const comments = flatMap(parent.jsDoc, j => typeof j.comment === "string" ? factory.createJSDocText(j.comment) : j.comment) as JSDocComment[]; - const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags); - const tag = factory.createJSDocComment(factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))), factory.createNodeArray([...(filter(oldTags, predicate) || emptyArray)])); - const host = updateJSDocHost(parent); - this.insertJsdocCommentBefore(sourceFile, host, tag); - } + private getInsertNodeAfterOptionsWorker(node: Node): InsertNodeOptions { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + return { prefix: this.newLineCharacter, suffix: this.newLineCharacter }; - public replaceRangeWithText(sourceFile: SourceFile, range: TextRange, text: string): void { - this.changes.push({ kind: ChangeKind.Text, sourceFile, range, text }); - } + case SyntaxKind.VariableDeclaration: + case SyntaxKind.StringLiteral: + case SyntaxKind.Identifier: + return { prefix: ", " }; + + case SyntaxKind.PropertyAssignment: + return { suffix: "," + this.newLineCharacter }; + + case SyntaxKind.ExportKeyword: + return { prefix: " " }; - public insertText(sourceFile: SourceFile, pos: number, text: string): void { - this.replaceRangeWithText(sourceFile, createRange(pos), text); + case SyntaxKind.Parameter: + return {}; + + default: + Debug.assert(isStatement(node) || isClassOrTypeElement(node)); // Else we haven't handled this kind of node yet -- add it + return { suffix: this.newLineCharacter }; } + } - /** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */ - public tryInsertTypeAnnotation(sourceFile: SourceFile, node: TypeAnnotatable, type: TypeNode): boolean { - let endNode: Node | undefined; - if (isFunctionLike(node)) { - endNode = findChildOfKind(node, SyntaxKind.CloseParenToken, sourceFile); - if (!endNode) { - if (!isArrowFunction(node)) return false; // Function missing parentheses, give up - // If no `)`, is an arrow function `x => x`, so use the end of the first parameter - endNode = first(node.parameters); - } + public insertName(sourceFile: SourceFile, node: FunctionExpression | ClassExpression | ArrowFunction, name: string): void { + Debug.assert(!node.name); + if (node.kind === SyntaxKind.ArrowFunction) { + const arrow = findChildOfKind(node, SyntaxKind.EqualsGreaterThanToken, sourceFile)!; + const lparen = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile); + if (lparen) { + // `() => {}` --> `function f() {}` + this.insertNodesAt(sourceFile, lparen.getStart(sourceFile), [factory.createToken(SyntaxKind.FunctionKeyword), factory.createIdentifier(name)], { joiner: " " }); + deleteNode(this, sourceFile, arrow); } else { - endNode = (node.kind === SyntaxKind.VariableDeclaration ? node.exclamationToken : node.questionToken) ?? node.name; + // `x => {}` -> `function f(x) {}` + this.insertText(sourceFile, first(node.parameters).getStart(sourceFile), `function ${name}(`); + // Replacing full range of arrow to get rid of the leading space -- replace ` =>` with `)` + this.replaceRange(sourceFile, arrow, factory.createToken(SyntaxKind.CloseParenToken)); } - this.insertNodeAt(sourceFile, endNode.end, type, { prefix: ": " }); - return true; + if (node.body.kind !== SyntaxKind.Block) { + // `() => 0` => `function f() { return 0; }` + this.insertNodesAt(sourceFile, node.body.getStart(sourceFile), [factory.createToken(SyntaxKind.OpenBraceToken), factory.createToken(SyntaxKind.ReturnKeyword)], { joiner: " ", suffix: " " }); + this.insertNodesAt(sourceFile, node.body.end, [factory.createToken(SyntaxKind.SemicolonToken), factory.createToken(SyntaxKind.CloseBraceToken)], { joiner: " " }); + } } + else { + const pos = findChildOfKind(node, node.kind === SyntaxKind.FunctionExpression ? SyntaxKind.FunctionKeyword : SyntaxKind.ClassKeyword, sourceFile)!.end; + this.insertNodeAt(sourceFile, pos, factory.createIdentifier(name), { prefix: " " }); + } + } - public tryInsertThisTypeAnnotation(sourceFile: SourceFile, node: ThisTypeAnnotatable, type: TypeNode): void { - const start = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile)!.getStart(sourceFile) + 1; - const suffix = node.parameters.length ? ", " : ""; + public insertExportModifier(sourceFile: SourceFile, node: DeclarationStatement | VariableStatement): void { + this.insertText(sourceFile, node.getStart(sourceFile), "export "); + } - this.insertNodeAt(sourceFile, start, type, { prefix: "this: ", suffix }); + /** + * This function should be used to insert nodes in lists when nodes don't carry separators as the part of the node range, + * i.e. arguments in arguments lists, parameters in parameter lists etc. + * Note that separators are part of the node in statements and class elements. + */ + public insertNodeInListAfter(sourceFile: SourceFile, after: Node, newNode: Node, containingList = formatting.SmartIndenter.getContainingList(after, sourceFile)): void { + if (!containingList) { + Debug.fail("node is not a list element"); + return; } - - public insertTypeParameters(sourceFile: SourceFile, node: SignatureDeclaration, typeParameters: readonly TypeParameterDeclaration[]): void { - // If no `(`, is an arrow function `x => x`, so use the pos of the first parameter - const start = (findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile) || first(node.parameters)).getStart(sourceFile); - this.insertNodesAt(sourceFile, start, typeParameters, { prefix: "<", suffix: ">", joiner: ", " }); + const index = indexOfNode(containingList, after); + if (index < 0) { + return; } - - private getOptionsForInsertNodeBefore(before: Node, inserted: Node, blankLineBetween: boolean): InsertNodeOptions { - if (isStatement(before) || isClassElement(before)) { - return { suffix: blankLineBetween ? this.newLineCharacter + this.newLineCharacter : this.newLineCharacter }; - } - else if (isVariableDeclaration(before)) { // insert `x = 1, ` into `const x = 1, y = 2; - return { suffix: ", " }; - } - else if (isParameter(before)) { - return isParameter(inserted) ? { suffix: ", " } : {}; - } - else if (isStringLiteral(before) && isImportDeclaration(before.parent) || isNamedImports(before)) { - return { suffix: ", " }; + const end = after.getEnd(); + if (index !== containingList.length - 1) { + // any element except the last one + // use next sibling as an anchor + const nextToken = getTokenAtPosition(sourceFile, after.end); + if (nextToken && isSeparator(after, nextToken)) { + // for list + // a, b, c + // create change for adding 'e' after 'a' as + // - find start of next element after a (it is b) + // - use next element start as start and end position in final change + // - build text of change by formatting the text of node + whitespace trivia of b + + // in multiline case it will work as + // a, + // b, + // c, + // result - '*' denotes leading trivia that will be inserted after new text (displayed as '#') + // a, + // insertedtext# + // ###b, + // c, + const nextNode = containingList[index + 1]; + const startPos = skipWhitespacesAndLineBreaks(sourceFile.text, nextNode.getFullStart()); + + // write separator and leading trivia of the next element as suffix + const suffix = `${tokenToString(nextToken.kind)}${sourceFile.text.substring(nextToken.end, startPos)}`; + this.insertNodesAt(sourceFile, startPos, [newNode], { suffix }); } - else if (isImportSpecifier(before)) { - return { suffix: "," + (blankLineBetween ? this.newLineCharacter : " ") }; - } - return Debug.failBadSyntaxKind(before); // We haven't handled this kind of node yet -- add it } - - public insertNodeAtConstructorStart(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { - const firstStatement = firstOrUndefined(ctr.body!.statements); - if (!firstStatement || !ctr.body!.multiLine) { - this.replaceConstructorBody(sourceFile, ctr, [newStatement, ...ctr.body!.statements]); + else { + const afterStart = after.getStart(sourceFile); + const afterStartLinePosition = getLineStartPositionForPosition(afterStart, sourceFile); + + let separator: SyntaxKind.CommaToken | SyntaxKind.SemicolonToken | undefined; + let multilineList = false; + + // insert element after the last element in the list that has more than one item + // pick the element preceding the after element to: + // - pick the separator + // - determine if list is a multiline + if (containingList.length === 1) { + // if list has only one element then we'll format is as multiline if node has comment in trailing trivia, or as singleline otherwise + // i.e. var x = 1 // this is x + // | new element will be inserted at this position + separator = SyntaxKind.CommaToken; } else { - this.insertNodeBefore(sourceFile, firstStatement, newStatement); - } - } - - public insertNodeAtConstructorStartAfterSuperCall(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { - const superCallStatement = find(ctr.body!.statements, stmt => isExpressionStatement(stmt) && isSuperCall(stmt.expression)); - if (!superCallStatement || !ctr.body!.multiLine) { - this.replaceConstructorBody(sourceFile, ctr, [...ctr.body!.statements, newStatement]); + // element has more than one element, pick separator from the list + const tokenBeforeInsertPosition = findPrecedingToken(after.pos, sourceFile); + separator = isSeparator(after, tokenBeforeInsertPosition) ? tokenBeforeInsertPosition.kind : SyntaxKind.CommaToken; + // determine if list is multiline by checking lines of after element and element that precedes it. + const afterMinusOneStartLinePosition = getLineStartPositionForPosition(containingList[index - 1].getStart(sourceFile), sourceFile); + multilineList = afterMinusOneStartLinePosition !== afterStartLinePosition; } - else { - this.insertNodeAfter(sourceFile, superCallStatement, newStatement); + if (hasCommentsBeforeLineBreak(sourceFile.text, after.end)) { + // in this case we'll always treat containing list as multiline + multilineList = true; } - } - - public insertNodeAtConstructorEnd(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { - const lastStatement = lastOrUndefined(ctr.body!.statements); - if (!lastStatement || !ctr.body!.multiLine) { - this.replaceConstructorBody(sourceFile, ctr, [...ctr.body!.statements, newStatement]); + if (multilineList) { + // insert separator immediately following the 'after' node to preserve comments in trailing trivia + this.replaceRange(sourceFile, createRange(end), factory.createToken(separator)); + // use the same indentation as 'after' item + const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, this.formatContext.options); + // insert element before the line break on the line that contains 'after' element + let insertPos = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ false); + // find position before "\n" or "\r\n" + while (insertPos !== end && isLineBreak(sourceFile.text.charCodeAt(insertPos - 1))) { + insertPos--; + } + this.replaceRange(sourceFile, createRange(insertPos), newNode, { indentation, prefix: this.newLineCharacter }); } else { - this.insertNodeAfter(sourceFile, lastStatement, newStatement); + this.replaceRange(sourceFile, createRange(end), newNode, { prefix: `${tokenToString(separator)} ` }); } } + } - private replaceConstructorBody(sourceFile: SourceFile, ctr: ConstructorDeclaration, statements: readonly Statement[]): void { - this.replaceNode(sourceFile, ctr.body!, factory.createBlock(statements, /*multiLine*/ true)); - } - - public insertNodeAtEndOfScope(sourceFile: SourceFile, scope: Node, newNode: Node): void { - const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {}); - this.insertNodeAt(sourceFile, pos, newNode, { - prefix: isLineBreak(sourceFile.text.charCodeAt(scope.getLastToken()!.pos)) ? this.newLineCharacter : this.newLineCharacter + this.newLineCharacter, - suffix: this.newLineCharacter - }); - } - - public insertNodeAtClassStart(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration, newElement: ClassElement): void { - this.insertNodeAtStartWorker(sourceFile, cls, newElement); - } - - public insertNodeAtObjectStart(sourceFile: SourceFile, obj: ObjectLiteralExpression, newElement: ObjectLiteralElementLike): void { - this.insertNodeAtStartWorker(sourceFile, obj, newElement); - } - - private insertNodeAtStartWorker(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, newElement: ClassElement | ObjectLiteralElementLike): void { - const indentation = this.guessIndentationFromExistingMembers(sourceFile, cls) ?? this.computeIndentationForNewMember(sourceFile, cls); - this.insertNodeAt(sourceFile, getMembersOrProperties(cls).pos, newElement, this.getInsertNodeAtStartInsertOptions(sourceFile, cls, indentation)); - } + public parenthesizeExpression(sourceFile: SourceFile, expression: Expression) { + this.replaceRange(sourceFile, rangeOfNode(expression), factory.createParenthesizedExpression(expression)); + } - /** - * Tries to guess the indentation from the existing members of a class/interface/object. All members must be on - * new lines and must share the same indentation. - */ - private guessIndentationFromExistingMembers(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression) { - let indentation: number | undefined; - let lastRange: TextRange = cls; - for (const member of getMembersOrProperties(cls)) { - if (rangeStartPositionsAreOnSameLine(lastRange, member, sourceFile)) { - // each indented member must be on a new line - return undefined; + private finishClassesWithNodesInsertedAtStart(): void { + this.classesWithNodesInsertedAtStart.forEach(({ node, sourceFile }) => { + const [openBraceEnd, closeBraceEnd] = getClassOrObjectBraceEnds(node, sourceFile); + if (openBraceEnd !== undefined && closeBraceEnd !== undefined) { + const isEmpty = getMembersOrProperties(node).length === 0; + const isSingleLine = positionsAreOnSameLine(openBraceEnd, closeBraceEnd, sourceFile); + if (isEmpty && isSingleLine && openBraceEnd !== closeBraceEnd - 1) { + // For `class C { }` remove the whitespace inside the braces. + this.deleteRange(sourceFile, createRange(openBraceEnd, closeBraceEnd - 1)); } - const memberStart = member.getStart(sourceFile); - const memberIndentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(getLineStartPositionForPosition(memberStart, sourceFile), memberStart, sourceFile, this.formatContext.options); - if (indentation === undefined) { - indentation = memberIndentation; - } - else if (memberIndentation !== indentation) { - // indentation of multiple members is not consistent - return undefined; + if (isSingleLine) { + this.insertText(sourceFile, closeBraceEnd - 1, this.newLineCharacter); } - lastRange = member; } - return indentation; - } + }); + } - private computeIndentationForNewMember(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression) { - const clsStart = cls.getStart(sourceFile); - return formatting.SmartIndenter.findFirstNonWhitespaceColumn(getLineStartPositionForPosition(clsStart, sourceFile), clsStart, sourceFile, this.formatContext.options) - + (this.formatContext.options.indentSize ?? 4); + private finishDeleteDeclarations(): void { + const deletedNodesInLists = new Set(); // Stores nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`. + for (const { sourceFile, node } of this.deletedNodes) { + if (!this.deletedNodes.some(d => d.sourceFile === sourceFile && rangeContainsRangeExclusive(d.node, node))) { + if (isArray(node)) { + this.deleteRange(sourceFile, rangeOfTypeParameters(sourceFile, node)); + } + else { + deleteDeclaration.deleteDeclaration(this, deletedNodesInLists, sourceFile, node); + } + } } - private getInsertNodeAtStartInsertOptions(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, indentation: number): InsertNodeOptions { - // Rules: - // - Always insert leading newline. - // - For object literals: - // - Add a trailing comma if there are existing members in the node, or the source file is not a JSON file - // (because trailing commas are generally illegal in a JSON file). - // - Add a leading comma if the source file is not a JSON file, there are existing insertions, - // and the node is empty (because we didn't add a trailing comma per the previous rule). - // - Only insert a trailing newline if body is single-line and there are no other insertions for the node. - // NOTE: This is handled in `finishClassesWithNodesInsertedAtStart`. - - const members = getMembersOrProperties(cls); - const isEmpty = members.length === 0; - const isFirstInsertion = addToSeen(this.classesWithNodesInsertedAtStart, getNodeId(cls), { node: cls, sourceFile }); - const insertTrailingComma = isObjectLiteralExpression(cls) && (!isJsonSourceFile(sourceFile) || !isEmpty); - const insertLeadingComma = isObjectLiteralExpression(cls) && isJsonSourceFile(sourceFile) && isEmpty && !isFirstInsertion; - return { - indentation, - prefix: (insertLeadingComma ? "," : "") + this.newLineCharacter, - suffix: insertTrailingComma ? "," : "" - }; - } + deletedNodesInLists.forEach(node => { + const sourceFile = node.getSourceFile(); + const list = formatting.SmartIndenter.getContainingList(node, sourceFile)!; + if (node !== last(list)) return; - public insertNodeAfterComma(sourceFile: SourceFile, after: Node, newNode: Node): void { - const endPosition = this.insertNodeAfterWorker(sourceFile, this.nextCommaToken(sourceFile, after) || after, newNode); - this.insertNodeAt(sourceFile, endPosition, newNode, this.getInsertNodeAfterOptions(sourceFile, after)); - } + const lastNonDeletedIndex = findLastIndex(list, n => !deletedNodesInLists.has(n), list.length - 2); + if (lastNonDeletedIndex !== -1) { + this.deleteRange(sourceFile, { pos: list[lastNonDeletedIndex].end, end: startPositionToDeleteNodeInList(sourceFile, list[lastNonDeletedIndex + 1]) }); + } + }); + } - public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node): void { - const endPosition = this.insertNodeAfterWorker(sourceFile, after, newNode); - this.insertNodeAt(sourceFile, endPosition, newNode, this.getInsertNodeAfterOptions(sourceFile, after)); - } + /** + * Note: after calling this, the TextChanges object must be discarded! + * @param validate only for tests + * The reason we must validate as part of this method is that `getNonFormattedText` changes the node's positions, + * so we can only call this once and can't get the non-formatted text separately. + */ + public getChanges(validate?: ValidateNonFormattedText): FileTextChanges[] { + this.finishDeleteDeclarations(); + this.finishClassesWithNodesInsertedAtStart(); + const changes = changesToText.getTextChangesFromChanges(this.changes, this.newLineCharacter, this.formatContext, validate); + for (const { oldFile, fileName, statements } of this.newFiles) { + changes.push(changesToText.newFileChanges(oldFile, fileName, statements, this.newLineCharacter, this.formatContext)); + } + return changes; + } - public insertNodeAtEndOfList(sourceFile: SourceFile, list: NodeArray, newNode: Node): void { - this.insertNodeAt(sourceFile, list.end, newNode, { prefix: ", " }); - } - - public insertNodesAfter(sourceFile: SourceFile, after: Node, newNodes: readonly Node[]): void { - const endPosition = this.insertNodeAfterWorker(sourceFile, after, first(newNodes)); - this.insertNodesAt(sourceFile, endPosition, newNodes, this.getInsertNodeAfterOptions(sourceFile, after)); - } - - private insertNodeAfterWorker(sourceFile: SourceFile, after: Node, newNode: Node): number { - if (needSemicolonBetween(after, newNode)) { - // check if previous statement ends with semicolon - // if not - insert semicolon to preserve the code from changing the meaning due to ASI - if (sourceFile.text.charCodeAt(after.end - 1) !== CharacterCodes.semicolon) { - this.replaceRange(sourceFile, createRange(after.end), factory.createToken(SyntaxKind.SemicolonToken)); - } - } - const endPosition = getAdjustedEndPosition(sourceFile, after, {}); - return endPosition; - } - - private getInsertNodeAfterOptions(sourceFile: SourceFile, after: Node): InsertNodeOptions { - const options = this.getInsertNodeAfterOptionsWorker(after); - return { - ...options, - prefix: after.end === sourceFile.end && isStatement(after) ? (options.prefix ? `\n${options.prefix}` : "\n") : options.prefix, - }; - } - - private getInsertNodeAfterOptionsWorker(node: Node): InsertNodeOptions { - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - return { prefix: this.newLineCharacter, suffix: this.newLineCharacter }; + public createNewFile(oldFile: SourceFile | undefined, fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[]): void { + this.newFiles.push({ oldFile, fileName, statements }); + } +} - case SyntaxKind.VariableDeclaration: - case SyntaxKind.StringLiteral: - case SyntaxKind.Identifier: - return { prefix: ", " }; +function updateJSDocHost(parent: HasJSDoc): HasJSDoc { + if (parent.kind !== SyntaxKind.ArrowFunction) { + return parent; + } + const jsDocNode = parent.parent.kind === SyntaxKind.PropertyDeclaration ? + parent.parent as HasJSDoc : + parent.parent.parent as HasJSDoc; + jsDocNode.jsDoc = parent.jsDoc; + jsDocNode.jsDocCache = parent.jsDocCache; + return jsDocNode; +} - case SyntaxKind.PropertyAssignment: - return { suffix: "," + this.newLineCharacter }; +function tryMergeJsdocTags(oldTag: JSDocTag, newTag: JSDocTag): JSDocTag | undefined { + if (oldTag.kind !== newTag.kind) { + return undefined; + } + switch (oldTag.kind) { + case SyntaxKind.JSDocParameterTag: { + const oldParam = oldTag as JSDocParameterTag; + const newParam = newTag as JSDocParameterTag; + return isIdentifier(oldParam.name) && isIdentifier(newParam.name) && oldParam.name.escapedText === newParam.name.escapedText + ? factory.createJSDocParameterTag(/*tagName*/ undefined, newParam.name, /*isBracketed*/ false, newParam.typeExpression, newParam.isNameFirst, oldParam.comment) + : undefined; + } + case SyntaxKind.JSDocReturnTag: + return factory.createJSDocReturnTag(/*tagName*/ undefined, (newTag as JSDocReturnTag).typeExpression, oldTag.comment); + } +} - case SyntaxKind.ExportKeyword: - return { prefix: " " }; +// find first non-whitespace position in the leading trivia of the node +function startPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node): number { + return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.IncludeAll }), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); +} - case SyntaxKind.Parameter: - return {}; +function getClassOrObjectBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, sourceFile: SourceFile): [number | undefined, number | undefined] { + const open = findChildOfKind(cls, SyntaxKind.OpenBraceToken, sourceFile); + const close = findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile); + return [open?.end, close?.end]; +} +function getMembersOrProperties(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression): NodeArray { + return isObjectLiteralExpression(cls) ? cls.properties : cls.members; +} - default: - Debug.assert(isStatement(node) || isClassOrTypeElement(node)); // Else we haven't handled this kind of node yet -- add it - return { suffix: this.newLineCharacter }; - } - } +export type ValidateNonFormattedText = (node: Node, text: string) => void; - public insertName(sourceFile: SourceFile, node: FunctionExpression | ClassExpression | ArrowFunction, name: string): void { - Debug.assert(!node.name); - if (node.kind === SyntaxKind.ArrowFunction) { - const arrow = findChildOfKind(node, SyntaxKind.EqualsGreaterThanToken, sourceFile)!; - const lparen = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile); - if (lparen) { - // `() => {}` --> `function f() {}` - this.insertNodesAt(sourceFile, lparen.getStart(sourceFile), [factory.createToken(SyntaxKind.FunctionKeyword), factory.createIdentifier(name)], { joiner: " " }); - deleteNode(this, sourceFile, arrow); - } - else { - // `x => {}` -> `function f(x) {}` - this.insertText(sourceFile, first(node.parameters).getStart(sourceFile), `function ${name}(`); - // Replacing full range of arrow to get rid of the leading space -- replace ` =>` with `)` - this.replaceRange(sourceFile, arrow, factory.createToken(SyntaxKind.CloseParenToken)); - } +export function getNewFileText(statements: readonly Statement[], scriptKind: ScriptKind, newLineCharacter: string, formatContext: formatting.FormatContext): string { + return changesToText.newFileChangesWorker(/*oldFile*/ undefined, scriptKind, statements, newLineCharacter, formatContext); +} - if (node.body.kind !== SyntaxKind.Block) { - // `() => 0` => `function f() { return 0; }` - this.insertNodesAt(sourceFile, node.body.getStart(sourceFile), [factory.createToken(SyntaxKind.OpenBraceToken), factory.createToken(SyntaxKind.ReturnKeyword)], { joiner: " ", suffix: " " }); - this.insertNodesAt(sourceFile, node.body.end, [factory.createToken(SyntaxKind.SemicolonToken), factory.createToken(SyntaxKind.CloseBraceToken)], { joiner: " " }); - } - } - else { - const pos = findChildOfKind(node, node.kind === SyntaxKind.FunctionExpression ? SyntaxKind.FunctionKeyword : SyntaxKind.ClassKeyword, sourceFile)!.end; - this.insertNodeAt(sourceFile, pos, factory.createIdentifier(name), { prefix: " " }); +namespace changesToText { + export function getTextChangesFromChanges(changes: readonly Change[], newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): FileTextChanges[] { + return mapDefined(group(changes, c => c.sourceFile.path), changesInFile => { + const sourceFile = changesInFile[0].sourceFile; + // order changes by start position + // If the start position is the same, put the shorter range first, since an empty range (x, x) may precede (x, y) but not vice-versa. + const normalized = stableSort(changesInFile, (a, b) => (a.range.pos - b.range.pos) || (a.range.end - b.range.end)); + // verify that change intervals do not overlap, except possibly at end points. + for (let i = 0; i < normalized.length - 1; i++) { + Debug.assert(normalized[i].range.end <= normalized[i + 1].range.pos, "Changes overlap", () => + `${JSON.stringify(normalized[i].range)} and ${JSON.stringify(normalized[i + 1].range)}`); } - } - public insertExportModifier(sourceFile: SourceFile, node: DeclarationStatement | VariableStatement): void { - this.insertText(sourceFile, node.getStart(sourceFile), "export "); - } + const textChanges = mapDefined(normalized, c => { + const span = createTextSpanFromRange(c.range); + const newText = computeNewText(c, sourceFile, newLineCharacter, formatContext, validate); - /** - * This function should be used to insert nodes in lists when nodes don't carry separators as the part of the node range, - * i.e. arguments in arguments lists, parameters in parameter lists etc. - * Note that separators are part of the node in statements and class elements. - */ - public insertNodeInListAfter(sourceFile: SourceFile, after: Node, newNode: Node, containingList = formatting.SmartIndenter.getContainingList(after, sourceFile)): void { - if (!containingList) { - Debug.fail("node is not a list element"); - return; - } - const index = indexOfNode(containingList, after); - if (index < 0) { - return; - } - const end = after.getEnd(); - if (index !== containingList.length - 1) { - // any element except the last one - // use next sibling as an anchor - const nextToken = getTokenAtPosition(sourceFile, after.end); - if (nextToken && isSeparator(after, nextToken)) { - // for list - // a, b, c - // create change for adding 'e' after 'a' as - // - find start of next element after a (it is b) - // - use next element start as start and end position in final change - // - build text of change by formatting the text of node + whitespace trivia of b - - // in multiline case it will work as - // a, - // b, - // c, - // result - '*' denotes leading trivia that will be inserted after new text (displayed as '#') - // a, - // insertedtext# - // ###b, - // c, - const nextNode = containingList[index + 1]; - const startPos = skipWhitespacesAndLineBreaks(sourceFile.text, nextNode.getFullStart()); - - // write separator and leading trivia of the next element as suffix - const suffix = `${tokenToString(nextToken.kind)}${sourceFile.text.substring(nextToken.end, startPos)}`; - this.insertNodesAt(sourceFile, startPos, [newNode], { suffix }); - } - } - else { - const afterStart = after.getStart(sourceFile); - const afterStartLinePosition = getLineStartPositionForPosition(afterStart, sourceFile); - - let separator: SyntaxKind.CommaToken | SyntaxKind.SemicolonToken | undefined; - let multilineList = false; - - // insert element after the last element in the list that has more than one item - // pick the element preceding the after element to: - // - pick the separator - // - determine if list is a multiline - if (containingList.length === 1) { - // if list has only one element then we'll format is as multiline if node has comment in trailing trivia, or as singleline otherwise - // i.e. var x = 1 // this is x - // | new element will be inserted at this position - separator = SyntaxKind.CommaToken; - } - else { - // element has more than one element, pick separator from the list - const tokenBeforeInsertPosition = findPrecedingToken(after.pos, sourceFile); - separator = isSeparator(after, tokenBeforeInsertPosition) ? tokenBeforeInsertPosition.kind : SyntaxKind.CommaToken; - // determine if list is multiline by checking lines of after element and element that precedes it. - const afterMinusOneStartLinePosition = getLineStartPositionForPosition(containingList[index - 1].getStart(sourceFile), sourceFile); - multilineList = afterMinusOneStartLinePosition !== afterStartLinePosition; - } - if (hasCommentsBeforeLineBreak(sourceFile.text, after.end)) { - // in this case we'll always treat containing list as multiline - multilineList = true; - } - if (multilineList) { - // insert separator immediately following the 'after' node to preserve comments in trailing trivia - this.replaceRange(sourceFile, createRange(end), factory.createToken(separator)); - // use the same indentation as 'after' item - const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, this.formatContext.options); - // insert element before the line break on the line that contains 'after' element - let insertPos = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ false); - // find position before "\n" or "\r\n" - while (insertPos !== end && isLineBreak(sourceFile.text.charCodeAt(insertPos - 1))) { - insertPos--; - } - this.replaceRange(sourceFile, createRange(insertPos), newNode, { indentation, prefix: this.newLineCharacter }); - } - else { - this.replaceRange(sourceFile, createRange(end), newNode, { prefix: `${tokenToString(separator)} ` }); + // Filter out redundant changes. + if (span.length === newText.length && stringContainsAt(sourceFile.text, newText, span.start)) { + return undefined; } - } - } - - public parenthesizeExpression(sourceFile: SourceFile, expression: Expression) { - this.replaceRange(sourceFile, rangeOfNode(expression), factory.createParenthesizedExpression(expression)); - } - private finishClassesWithNodesInsertedAtStart(): void { - this.classesWithNodesInsertedAtStart.forEach(({ node, sourceFile }) => { - const [openBraceEnd, closeBraceEnd] = getClassOrObjectBraceEnds(node, sourceFile); - if (openBraceEnd !== undefined && closeBraceEnd !== undefined) { - const isEmpty = getMembersOrProperties(node).length === 0; - const isSingleLine = positionsAreOnSameLine(openBraceEnd, closeBraceEnd, sourceFile); - if (isEmpty && isSingleLine && openBraceEnd !== closeBraceEnd - 1) { - // For `class C { }` remove the whitespace inside the braces. - this.deleteRange(sourceFile, createRange(openBraceEnd, closeBraceEnd - 1)); - } - if (isSingleLine) { - this.insertText(sourceFile, closeBraceEnd - 1, this.newLineCharacter); - } - } + return createTextChange(span, newText); }); - } - private finishDeleteDeclarations(): void { - const deletedNodesInLists = new Set(); // Stores nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`. - for (const { sourceFile, node } of this.deletedNodes) { - if (!this.deletedNodes.some(d => d.sourceFile === sourceFile && rangeContainsRangeExclusive(d.node, node))) { - if (isArray(node)) { - this.deleteRange(sourceFile, rangeOfTypeParameters(sourceFile, node)); - } - else { - deleteDeclaration.deleteDeclaration(this, deletedNodesInLists, sourceFile, node); - } - } - } + return textChanges.length > 0 ? { fileName: sourceFile.fileName, textChanges } : undefined; + }); + } - deletedNodesInLists.forEach(node => { - const sourceFile = node.getSourceFile(); - const list = formatting.SmartIndenter.getContainingList(node, sourceFile)!; - if (node !== last(list)) return; + export function newFileChanges(oldFile: SourceFile | undefined, fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], newLineCharacter: string, formatContext: formatting.FormatContext): FileTextChanges { + const text = newFileChangesWorker(oldFile, getScriptKindFromFileName(fileName), statements, newLineCharacter, formatContext); + return { fileName, textChanges: [createTextChange(createTextSpan(0, 0), text)], isNewFile: true }; + } - const lastNonDeletedIndex = findLastIndex(list, n => !deletedNodesInLists.has(n), list.length - 2); - if (lastNonDeletedIndex !== -1) { - this.deleteRange(sourceFile, { pos: list[lastNonDeletedIndex].end, end: startPositionToDeleteNodeInList(sourceFile, list[lastNonDeletedIndex + 1]) }); - } - }); - } + export function newFileChangesWorker(oldFile: SourceFile | undefined, scriptKind: ScriptKind, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], newLineCharacter: string, formatContext: formatting.FormatContext): string { + // TODO: this emits the file, parses it back, then formats it that -- may be a less roundabout way to do this + const nonFormattedText = statements.map(s => s === SyntaxKind.NewLineTrivia ? "" : getNonformattedText(s, oldFile, newLineCharacter).text).join(newLineCharacter); + const sourceFile = createSourceFile("any file name", nonFormattedText, ScriptTarget.ESNext, /*setParentNodes*/ true, scriptKind); + const changes = formatting.formatDocument(sourceFile, formatContext); + return applyChanges(nonFormattedText, changes) + newLineCharacter; + } - /** - * Note: after calling this, the TextChanges object must be discarded! - * @param validate only for tests - * The reason we must validate as part of this method is that `getNonFormattedText` changes the node's positions, - * so we can only call this once and can't get the non-formatted text separately. - */ - public getChanges(validate?: ValidateNonFormattedText): FileTextChanges[] { - this.finishDeleteDeclarations(); - this.finishClassesWithNodesInsertedAtStart(); - const changes = changesToText.getTextChangesFromChanges(this.changes, this.newLineCharacter, this.formatContext, validate); - for (const { oldFile, fileName, statements } of this.newFiles) { - changes.push(changesToText.newFileChanges(oldFile, fileName, statements, this.newLineCharacter, this.formatContext)); - } - return changes; + function computeNewText(change: Change, sourceFile: SourceFile, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string { + if (change.kind === ChangeKind.Remove) { + return ""; } - - public createNewFile(oldFile: SourceFile | undefined, fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[]): void { - this.newFiles.push({ oldFile, fileName, statements }); + if (change.kind === ChangeKind.Text) { + return change.text; } + + const { options = {}, range: { pos } } = change; + const format = (n: Node) => getFormattedTextOfNode(n, sourceFile, pos, options, newLineCharacter, formatContext, validate); + const text = change.kind === ChangeKind.ReplaceWithMultipleNodes + ? change.nodes.map(n => removeSuffix(format(n), newLineCharacter)).join(change.options?.joiner || newLineCharacter) + : format(change.node); + // strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line + const noIndent = (options.preserveLeadingWhitespace || options.indentation !== undefined || getLineStartPositionForPosition(pos, sourceFile) === pos) ? text : text.replace(/^\s+/, ""); + return (options.prefix || "") + noIndent + + ((!options.suffix || endsWith(noIndent, options.suffix)) + ? "" : options.suffix); } - function updateJSDocHost(parent: HasJSDoc): HasJSDoc { - if (parent.kind !== SyntaxKind.ArrowFunction) { - return parent; - } - const jsDocNode = parent.parent.kind === SyntaxKind.PropertyDeclaration ? - parent.parent as HasJSDoc : - parent.parent.parent as HasJSDoc; - jsDocNode.jsDoc = parent.jsDoc; - jsDocNode.jsDocCache = parent.jsDocCache; - return jsDocNode; + function getFormatCodeSettingsForWriting({ options }: formatting.FormatContext, sourceFile: SourceFile): FormatCodeSettings { + const shouldAutoDetectSemicolonPreference = !options.semicolons || options.semicolons === SemicolonPreference.Ignore; + const shouldRemoveSemicolons = options.semicolons === SemicolonPreference.Remove || shouldAutoDetectSemicolonPreference && !probablyUsesSemicolons(sourceFile); + return { + ...options, + semicolons: shouldRemoveSemicolons ? SemicolonPreference.Remove : SemicolonPreference.Ignore, + }; } - function tryMergeJsdocTags(oldTag: JSDocTag, newTag: JSDocTag): JSDocTag | undefined { - if (oldTag.kind !== newTag.kind) { - return undefined; - } - switch (oldTag.kind) { - case SyntaxKind.JSDocParameterTag: { - const oldParam = oldTag as JSDocParameterTag; - const newParam = newTag as JSDocParameterTag; - return isIdentifier(oldParam.name) && isIdentifier(newParam.name) && oldParam.name.escapedText === newParam.name.escapedText - ? factory.createJSDocParameterTag(/*tagName*/ undefined, newParam.name, /*isBracketed*/ false, newParam.typeExpression, newParam.isNameFirst, oldParam.comment) - : undefined; + /** Note: this may mutate `nodeIn`. */ + function getFormattedTextOfNode(nodeIn: Node, sourceFile: SourceFile, pos: number, { indentation, prefix, delta }: InsertNodeOptions, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string { + const { node, text } = getNonformattedText(nodeIn, sourceFile, newLineCharacter); + if (validate) validate(node, text); + const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); + const initialIndentation = + indentation !== undefined + ? indentation + : formatting.SmartIndenter.getIndentation(pos, sourceFile, formatOptions, prefix === newLineCharacter || getLineStartPositionForPosition(pos, sourceFile) === pos); + if (delta === undefined) { + delta = formatting.SmartIndenter.shouldIndentChildNode(formatOptions, nodeIn) ? (formatOptions.indentSize || 0) : 0; + } + + const file: SourceFileLike = { + text, + getLineAndCharacterOfPosition(pos) { + return getLineAndCharacterOfPosition(this, pos); } - case SyntaxKind.JSDocReturnTag: - return factory.createJSDocReturnTag(/*tagName*/ undefined, (newTag as JSDocReturnTag).typeExpression, oldTag.comment); - } + }; + const changes = formatting.formatNodeGivenIndentation(node, file, sourceFile.languageVariant, initialIndentation, delta, { ...formatContext, options: formatOptions }); + return applyChanges(text, changes); } - // find first non-whitespace position in the leading trivia of the node - function startPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node): number { - return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.IncludeAll }), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + /** Note: output node may be mutated input node. */ + export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } { + const writer = createWriter(newLineCharacter); + const newLine = getNewLineKind(newLineCharacter); + createPrinter({ + newLine, + neverAsciiEscape: true, + preserveSourceNewlines: true, + terminateUnterminatedLiterals: true + }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer); + return { text: writer.getText(), node: assignPositionsToNode(node) }; } +} - function getClassOrObjectBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, sourceFile: SourceFile): [number | undefined, number | undefined] { - const open = findChildOfKind(cls, SyntaxKind.OpenBraceToken, sourceFile); - const close = findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile); - return [open?.end, close?.end]; - } - function getMembersOrProperties(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression): NodeArray { - return isObjectLiteralExpression(cls) ? cls.properties : cls.members; +export function applyChanges(text: string, changes: readonly TextChange[]): string { + for (let i = changes.length - 1; i >= 0; i--) { + const { span, newText } = changes[i]; + text = `${text.substring(0, span.start)}${newText}${text.substring(textSpanEnd(span))}`; } + return text; +} - export type ValidateNonFormattedText = (node: Node, text: string) => void; - - export function getNewFileText(statements: readonly Statement[], scriptKind: ScriptKind, newLineCharacter: string, formatContext: formatting.FormatContext): string { - return changesToText.newFileChangesWorker(/*oldFile*/ undefined, scriptKind, statements, newLineCharacter, formatContext); - } +function isTrivia(s: string) { + return skipTrivia(s, 0) === s.length; +} - namespace changesToText { - export function getTextChangesFromChanges(changes: readonly Change[], newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): FileTextChanges[] { - return mapDefined(group(changes, c => c.sourceFile.path), changesInFile => { - const sourceFile = changesInFile[0].sourceFile; - // order changes by start position - // If the start position is the same, put the shorter range first, since an empty range (x, x) may precede (x, y) but not vice-versa. - const normalized = stableSort(changesInFile, (a, b) => (a.range.pos - b.range.pos) || (a.range.end - b.range.end)); - // verify that change intervals do not overlap, except possibly at end points. - for (let i = 0; i < normalized.length - 1; i++) { - Debug.assert(normalized[i].range.end <= normalized[i + 1].range.pos, "Changes overlap", () => - `${JSON.stringify(normalized[i].range)} and ${JSON.stringify(normalized[i + 1].range)}`); - } +function assignPositionsToNode(node: Node): Node { + const visited = visitEachChild(node, assignPositionsToNode, nullTransformationContext, assignPositionsToNodeArray, assignPositionsToNode); + // create proxy node for non synthesized nodes + const newNode = nodeIsSynthesized(visited) ? visited : Object.create(visited) as Node; + setTextRangePosEnd(newNode, getPos(node), getEnd(node)); + return newNode; +} - const textChanges = mapDefined(normalized, c => { - const span = createTextSpanFromRange(c.range); - const newText = computeNewText(c, sourceFile, newLineCharacter, formatContext, validate); +function assignPositionsToNodeArray(nodes: NodeArray, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number) { + const visited = visitNodes(nodes, visitor, test, start, count); + if (!visited) { + return visited; + } + // clone nodearray if necessary + const nodeArray = visited === nodes ? factory.createNodeArray(visited.slice(0)) : visited; + setTextRangePosEnd(nodeArray, getPos(nodes), getEnd(nodes)); + return nodeArray; +} - // Filter out redundant changes. - if (span.length === newText.length && stringContainsAt(sourceFile.text, newText, span.start)) { - return undefined; - } +interface TextChangesWriter extends EmitTextWriter, PrintHandlers {} - return createTextChange(span, newText); - }); +function createWriter(newLine: string): TextChangesWriter { + let lastNonTriviaPosition = 0; - return textChanges.length > 0 ? { fileName: sourceFile.fileName, textChanges } : undefined; - }); + const writer = createTextWriter(newLine); + const onBeforeEmitNode: PrintHandlers["onBeforeEmitNode"] = node => { + if (node) { + setPos(node, lastNonTriviaPosition); } - - export function newFileChanges(oldFile: SourceFile | undefined, fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], newLineCharacter: string, formatContext: formatting.FormatContext): FileTextChanges { - const text = newFileChangesWorker(oldFile, getScriptKindFromFileName(fileName), statements, newLineCharacter, formatContext); - return { fileName, textChanges: [createTextChange(createTextSpan(0, 0), text)], isNewFile: true }; + }; + const onAfterEmitNode: PrintHandlers["onAfterEmitNode"] = node => { + if (node) { + setEnd(node, lastNonTriviaPosition); } - - export function newFileChangesWorker(oldFile: SourceFile | undefined, scriptKind: ScriptKind, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], newLineCharacter: string, formatContext: formatting.FormatContext): string { - // TODO: this emits the file, parses it back, then formats it that -- may be a less roundabout way to do this - const nonFormattedText = statements.map(s => s === SyntaxKind.NewLineTrivia ? "" : getNonformattedText(s, oldFile, newLineCharacter).text).join(newLineCharacter); - const sourceFile = createSourceFile("any file name", nonFormattedText, ScriptTarget.ESNext, /*setParentNodes*/ true, scriptKind); - const changes = formatting.formatDocument(sourceFile, formatContext); - return applyChanges(nonFormattedText, changes) + newLineCharacter; + }; + const onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"] = nodes => { + if (nodes) { + setPos(nodes, lastNonTriviaPosition); } - - function computeNewText(change: Change, sourceFile: SourceFile, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string { - if (change.kind === ChangeKind.Remove) { - return ""; - } - if (change.kind === ChangeKind.Text) { - return change.text; - } - - const { options = {}, range: { pos } } = change; - const format = (n: Node) => getFormattedTextOfNode(n, sourceFile, pos, options, newLineCharacter, formatContext, validate); - const text = change.kind === ChangeKind.ReplaceWithMultipleNodes - ? change.nodes.map(n => removeSuffix(format(n), newLineCharacter)).join(change.options?.joiner || newLineCharacter) - : format(change.node); - // strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line - const noIndent = (options.preserveLeadingWhitespace || options.indentation !== undefined || getLineStartPositionForPosition(pos, sourceFile) === pos) ? text : text.replace(/^\s+/, ""); - return (options.prefix || "") + noIndent - + ((!options.suffix || endsWith(noIndent, options.suffix)) - ? "" : options.suffix); + }; + const onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"] = nodes => { + if (nodes) { + setEnd(nodes, lastNonTriviaPosition); } - - function getFormatCodeSettingsForWriting({ options }: formatting.FormatContext, sourceFile: SourceFile): FormatCodeSettings { - const shouldAutoDetectSemicolonPreference = !options.semicolons || options.semicolons === SemicolonPreference.Ignore; - const shouldRemoveSemicolons = options.semicolons === SemicolonPreference.Remove || shouldAutoDetectSemicolonPreference && !probablyUsesSemicolons(sourceFile); - return { - ...options, - semicolons: shouldRemoveSemicolons ? SemicolonPreference.Remove : SemicolonPreference.Ignore, - }; + }; + const onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"] = node => { + if (node) { + setPos(node, lastNonTriviaPosition); } - - /** Note: this may mutate `nodeIn`. */ - function getFormattedTextOfNode(nodeIn: Node, sourceFile: SourceFile, pos: number, { indentation, prefix, delta }: InsertNodeOptions, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string { - const { node, text } = getNonformattedText(nodeIn, sourceFile, newLineCharacter); - if (validate) validate(node, text); - const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); - const initialIndentation = - indentation !== undefined - ? indentation - : formatting.SmartIndenter.getIndentation(pos, sourceFile, formatOptions, prefix === newLineCharacter || getLineStartPositionForPosition(pos, sourceFile) === pos); - if (delta === undefined) { - delta = formatting.SmartIndenter.shouldIndentChildNode(formatOptions, nodeIn) ? (formatOptions.indentSize || 0) : 0; - } - - const file: SourceFileLike = { - text, - getLineAndCharacterOfPosition(pos) { - return getLineAndCharacterOfPosition(this, pos); - } - }; - const changes = formatting.formatNodeGivenIndentation(node, file, sourceFile.languageVariant, initialIndentation, delta, { ...formatContext, options: formatOptions }); - return applyChanges(text, changes); + }; + const onAfterEmitToken: PrintHandlers["onAfterEmitToken"] = node => { + if (node) { + setEnd(node, lastNonTriviaPosition); } + }; - /** Note: output node may be mutated input node. */ - export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } { - const writer = createWriter(newLineCharacter); - const newLine = getNewLineKind(newLineCharacter); - createPrinter({ - newLine, - neverAsciiEscape: true, - preserveSourceNewlines: true, - terminateUnterminatedLiterals: true - }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer); - return { text: writer.getText(), node: assignPositionsToNode(node) }; + function setLastNonTriviaPosition(s: string, force: boolean) { + if (force || !isTrivia(s)) { + lastNonTriviaPosition = writer.getTextPos(); + let i = 0; + while (isWhiteSpaceLike(s.charCodeAt(s.length - i - 1))) { + i++; + } + // trim trailing whitespaces + lastNonTriviaPosition -= i; } } - export function applyChanges(text: string, changes: readonly TextChange[]): string { - for (let i = changes.length - 1; i >= 0; i--) { - const { span, newText } = changes[i]; - text = `${text.substring(0, span.start)}${newText}${text.substring(textSpanEnd(span))}`; - } - return text; + function write(s: string): void { + writer.write(s); + setLastNonTriviaPosition(s, /*force*/ false); } - - function isTrivia(s: string) { - return skipTrivia(s, 0) === s.length; + function writeComment(s: string): void { + writer.writeComment(s); } - - function assignPositionsToNode(node: Node): Node { - const visited = visitEachChild(node, assignPositionsToNode, nullTransformationContext, assignPositionsToNodeArray, assignPositionsToNode); - // create proxy node for non synthesized nodes - const newNode = nodeIsSynthesized(visited) ? visited : Object.create(visited) as Node; - setTextRangePosEnd(newNode, getPos(node), getEnd(node)); - return newNode; + function writeKeyword(s: string): void { + writer.writeKeyword(s); + setLastNonTriviaPosition(s, /*force*/ false); } - - function assignPositionsToNodeArray(nodes: NodeArray, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number) { - const visited = visitNodes(nodes, visitor, test, start, count); - if (!visited) { - return visited; - } - // clone nodearray if necessary - const nodeArray = visited === nodes ? factory.createNodeArray(visited.slice(0)) : visited; - setTextRangePosEnd(nodeArray, getPos(nodes), getEnd(nodes)); - return nodeArray; + function writeOperator(s: string): void { + writer.writeOperator(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writePunctuation(s: string): void { + writer.writePunctuation(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeTrailingSemicolon(s: string): void { + writer.writeTrailingSemicolon(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeParameter(s: string): void { + writer.writeParameter(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeProperty(s: string): void { + writer.writeProperty(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeSpace(s: string): void { + writer.writeSpace(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeStringLiteral(s: string): void { + writer.writeStringLiteral(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeSymbol(s: string, sym: Symbol): void { + writer.writeSymbol(s, sym); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeLine(force?: boolean): void { + writer.writeLine(force); + } + function increaseIndent(): void { + writer.increaseIndent(); + } + function decreaseIndent(): void { + writer.decreaseIndent(); + } + function getText(): string { + return writer.getText(); + } + function rawWrite(s: string): void { + writer.rawWrite(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeLiteral(s: string): void { + writer.writeLiteral(s); + setLastNonTriviaPosition(s, /*force*/ true); + } + function getTextPos(): number { + return writer.getTextPos(); + } + function getLine(): number { + return writer.getLine(); + } + function getColumn(): number { + return writer.getColumn(); + } + function getIndent(): number { + return writer.getIndent(); + } + function isAtStartOfLine(): boolean { + return writer.isAtStartOfLine(); + } + function clear(): void { + writer.clear(); + lastNonTriviaPosition = 0; } - interface TextChangesWriter extends EmitTextWriter, PrintHandlers {} - - function createWriter(newLine: string): TextChangesWriter { - let lastNonTriviaPosition = 0; - - const writer = createTextWriter(newLine); - const onBeforeEmitNode: PrintHandlers["onBeforeEmitNode"] = node => { - if (node) { - setPos(node, lastNonTriviaPosition); - } - }; - const onAfterEmitNode: PrintHandlers["onAfterEmitNode"] = node => { - if (node) { - setEnd(node, lastNonTriviaPosition); - } - }; - const onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"] = nodes => { - if (nodes) { - setPos(nodes, lastNonTriviaPosition); - } - }; - const onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"] = nodes => { - if (nodes) { - setEnd(nodes, lastNonTriviaPosition); - } - }; - const onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"] = node => { - if (node) { - setPos(node, lastNonTriviaPosition); - } - }; - const onAfterEmitToken: PrintHandlers["onAfterEmitToken"] = node => { - if (node) { - setEnd(node, lastNonTriviaPosition); - } - }; - - function setLastNonTriviaPosition(s: string, force: boolean) { - if (force || !isTrivia(s)) { - lastNonTriviaPosition = writer.getTextPos(); - let i = 0; - while (isWhiteSpaceLike(s.charCodeAt(s.length - i - 1))) { - i++; - } - // trim trailing whitespaces - lastNonTriviaPosition -= i; - } - } + return { + onBeforeEmitNode, + onAfterEmitNode, + onBeforeEmitNodeArray, + onAfterEmitNodeArray, + onBeforeEmitToken, + onAfterEmitToken, + write, + writeComment, + writeKeyword, + writeOperator, + writePunctuation, + writeTrailingSemicolon, + writeParameter, + writeProperty, + writeSpace, + writeStringLiteral, + writeSymbol, + writeLine, + increaseIndent, + decreaseIndent, + getText, + rawWrite, + writeLiteral, + getTextPos, + getLine, + getColumn, + getIndent, + isAtStartOfLine, + hasTrailingComment: () => writer.hasTrailingComment(), + hasTrailingWhitespace: () => writer.hasTrailingWhitespace(), + clear + }; +} - function write(s: string): void { - writer.write(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeComment(s: string): void { - writer.writeComment(s); - } - function writeKeyword(s: string): void { - writer.writeKeyword(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeOperator(s: string): void { - writer.writeOperator(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writePunctuation(s: string): void { - writer.writePunctuation(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeTrailingSemicolon(s: string): void { - writer.writeTrailingSemicolon(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeParameter(s: string): void { - writer.writeParameter(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeProperty(s: string): void { - writer.writeProperty(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeSpace(s: string): void { - writer.writeSpace(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeStringLiteral(s: string): void { - writer.writeStringLiteral(s); - setLastNonTriviaPosition(s, /*force*/ false); +function getInsertionPositionAtSourceFileTop(sourceFile: SourceFile): number { + let lastPrologue: PrologueDirective | undefined; + for (const node of sourceFile.statements) { + if (isPrologueDirective(node)) { + lastPrologue = node; } - function writeSymbol(s: string, sym: Symbol): void { - writer.writeSymbol(s, sym); - setLastNonTriviaPosition(s, /*force*/ false); + else { + break; } - function writeLine(force?: boolean): void { - writer.writeLine(force); - } - function increaseIndent(): void { - writer.increaseIndent(); - } - function decreaseIndent(): void { - writer.decreaseIndent(); - } - function getText(): string { - return writer.getText(); - } - function rawWrite(s: string): void { - writer.rawWrite(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeLiteral(s: string): void { - writer.writeLiteral(s); - setLastNonTriviaPosition(s, /*force*/ true); - } - function getTextPos(): number { - return writer.getTextPos(); - } - function getLine(): number { - return writer.getLine(); - } - function getColumn(): number { - return writer.getColumn(); - } - function getIndent(): number { - return writer.getIndent(); - } - function isAtStartOfLine(): boolean { - return writer.isAtStartOfLine(); - } - function clear(): void { - writer.clear(); - lastNonTriviaPosition = 0; - } - - return { - onBeforeEmitNode, - onAfterEmitNode, - onBeforeEmitNodeArray, - onAfterEmitNodeArray, - onBeforeEmitToken, - onAfterEmitToken, - write, - writeComment, - writeKeyword, - writeOperator, - writePunctuation, - writeTrailingSemicolon, - writeParameter, - writeProperty, - writeSpace, - writeStringLiteral, - writeSymbol, - writeLine, - increaseIndent, - decreaseIndent, - getText, - rawWrite, - writeLiteral, - getTextPos, - getLine, - getColumn, - getIndent, - isAtStartOfLine, - hasTrailingComment: () => writer.hasTrailingComment(), - hasTrailingWhitespace: () => writer.hasTrailingWhitespace(), - clear - }; } - function getInsertionPositionAtSourceFileTop(sourceFile: SourceFile): number { - let lastPrologue: PrologueDirective | undefined; - for (const node of sourceFile.statements) { - if (isPrologueDirective(node)) { - lastPrologue = node; - } - else { - break; - } - } + let position = 0; + const text = sourceFile.text; + if (lastPrologue) { + position = lastPrologue.end; + advancePastLineBreak(); + return position; + } - let position = 0; - const text = sourceFile.text; - if (lastPrologue) { - position = lastPrologue.end; - advancePastLineBreak(); - return position; - } + const shebang = getShebang(text); + if (shebang !== undefined) { + position = shebang.length; + advancePastLineBreak(); + } - const shebang = getShebang(text); - if (shebang !== undefined) { - position = shebang.length; - advancePastLineBreak(); - } + const ranges = getLeadingCommentRanges(text, position); + if (!ranges) return position; - const ranges = getLeadingCommentRanges(text, position); - if (!ranges) return position; - - // Find the first attached comment to the first node and add before it - let lastComment: { range: CommentRange; pinnedOrTripleSlash: boolean; } | undefined; - let firstNodeLine: number | undefined; - for (const range of ranges) { - if (range.kind === SyntaxKind.MultiLineCommentTrivia) { - if (isPinnedComment(text, range.pos)) { - lastComment = { range, pinnedOrTripleSlash: true }; - continue; - } - } - else if (isRecognizedTripleSlashComment(text, range.pos, range.end)) { + // Find the first attached comment to the first node and add before it + let lastComment: { range: CommentRange; pinnedOrTripleSlash: boolean; } | undefined; + let firstNodeLine: number | undefined; + for (const range of ranges) { + if (range.kind === SyntaxKind.MultiLineCommentTrivia) { + if (isPinnedComment(text, range.pos)) { lastComment = { range, pinnedOrTripleSlash: true }; continue; } + } + else if (isRecognizedTripleSlashComment(text, range.pos, range.end)) { + lastComment = { range, pinnedOrTripleSlash: true }; + continue; + } - if (lastComment) { - // Always insert after pinned or triple slash comments - if (lastComment.pinnedOrTripleSlash) break; - - // There was a blank line between the last comment and this comment. - // This comment is not part of the copyright comments - const commentLine = sourceFile.getLineAndCharacterOfPosition(range.pos).line; - const lastCommentEndLine = sourceFile.getLineAndCharacterOfPosition(lastComment.range.end).line; - if (commentLine >= lastCommentEndLine + 2) break; - } + if (lastComment) { + // Always insert after pinned or triple slash comments + if (lastComment.pinnedOrTripleSlash) break; - if (sourceFile.statements.length) { - if (firstNodeLine === undefined) firstNodeLine = sourceFile.getLineAndCharacterOfPosition(sourceFile.statements[0].getStart()).line; - const commentEndLine = sourceFile.getLineAndCharacterOfPosition(range.end).line; - if (firstNodeLine < commentEndLine + 2) break; - } - lastComment = { range, pinnedOrTripleSlash: false }; + // There was a blank line between the last comment and this comment. + // This comment is not part of the copyright comments + const commentLine = sourceFile.getLineAndCharacterOfPosition(range.pos).line; + const lastCommentEndLine = sourceFile.getLineAndCharacterOfPosition(lastComment.range.end).line; + if (commentLine >= lastCommentEndLine + 2) break; } - if (lastComment) { - position = lastComment.range.end; - advancePastLineBreak(); + if (sourceFile.statements.length) { + if (firstNodeLine === undefined) firstNodeLine = sourceFile.getLineAndCharacterOfPosition(sourceFile.statements[0].getStart()).line; + const commentEndLine = sourceFile.getLineAndCharacterOfPosition(range.end).line; + if (firstNodeLine < commentEndLine + 2) break; } - return position; + lastComment = { range, pinnedOrTripleSlash: false }; + } - function advancePastLineBreak() { - if (position < text.length) { - const charCode = text.charCodeAt(position); - if (isLineBreak(charCode)) { - position++; + if (lastComment) { + position = lastComment.range.end; + advancePastLineBreak(); + } + return position; - if (position < text.length && charCode === CharacterCodes.carriageReturn && text.charCodeAt(position) === CharacterCodes.lineFeed) { - position++; - } + function advancePastLineBreak() { + if (position < text.length) { + const charCode = text.charCodeAt(position); + if (isLineBreak(charCode)) { + position++; + + if (position < text.length && charCode === CharacterCodes.carriageReturn && text.charCodeAt(position) === CharacterCodes.lineFeed) { + position++; } } } } +} - export function isValidLocationToAddComment(sourceFile: SourceFile, position: number) { - return !isInComment(sourceFile, position) && !isInString(sourceFile, position) && !isInTemplateString(sourceFile, position) && !isInJSXText(sourceFile, position); - } +export function isValidLocationToAddComment(sourceFile: SourceFile, position: number) { + return !isInComment(sourceFile, position) && !isInString(sourceFile, position) && !isInTemplateString(sourceFile, position) && !isInJSXText(sourceFile, position); +} - function needSemicolonBetween(a: Node, b: Node): boolean { - return (isPropertySignature(a) || isPropertyDeclaration(a)) && isClassOrTypeElement(b) && b.name!.kind === SyntaxKind.ComputedPropertyName - || isStatementButNotDeclaration(a) && isStatementButNotDeclaration(b); // TODO: only if b would start with a `(` or `[` - } +function needSemicolonBetween(a: Node, b: Node): boolean { + return (isPropertySignature(a) || isPropertyDeclaration(a)) && isClassOrTypeElement(b) && b.name!.kind === SyntaxKind.ComputedPropertyName + || isStatementButNotDeclaration(a) && isStatementButNotDeclaration(b); // TODO: only if b would start with a `(` or `[` +} - namespace deleteDeclaration { - export function deleteDeclaration(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: Node): void { - switch (node.kind) { - case SyntaxKind.Parameter: { - const oldFunction = node.parent; - if (isArrowFunction(oldFunction) && - oldFunction.parameters.length === 1 && - !findChildOfKind(oldFunction, SyntaxKind.OpenParenToken, sourceFile)) { - // Lambdas with exactly one parameter are special because, after removal, there - // must be an empty parameter list (i.e. `()`) and this won't necessarily be the - // case if the parameter is simply removed (e.g. in `x => 1`). - changes.replaceNodeWithText(sourceFile, node, "()"); - } - else { - deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); - } - break; +namespace deleteDeclaration { + export function deleteDeclaration(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: Node): void { + switch (node.kind) { + case SyntaxKind.Parameter: { + const oldFunction = node.parent; + if (isArrowFunction(oldFunction) && + oldFunction.parameters.length === 1 && + !findChildOfKind(oldFunction, SyntaxKind.OpenParenToken, sourceFile)) { + // Lambdas with exactly one parameter are special because, after removal, there + // must be an empty parameter list (i.e. `()`) and this won't necessarily be the + // case if the parameter is simply removed (e.g. in `x => 1`). + changes.replaceNodeWithText(sourceFile, node, "()"); } + else { + deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); + } + break; + } - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - const isFirstImport = sourceFile.imports.length && node === first(sourceFile.imports).parent || node === find(sourceFile.statements, isAnyImportSyntax); - // For first import, leave header comment in place, otherwise only delete JSDoc comments - deleteNode(changes, sourceFile, node, { - leadingTriviaOption: isFirstImport ? LeadingTriviaOption.Exclude : hasJSDocNodes(node) ? LeadingTriviaOption.JSDoc : LeadingTriviaOption.StartLine, - }); - break; - - case SyntaxKind.BindingElement: - const pattern = (node as BindingElement).parent; - const preserveComma = pattern.kind === SyntaxKind.ArrayBindingPattern && node !== last(pattern.elements); - if (preserveComma) { - deleteNode(changes, sourceFile, node); - } - else { - deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); - } - break; - - case SyntaxKind.VariableDeclaration: - deleteVariableDeclaration(changes, deletedNodesInLists, sourceFile, node as VariableDeclaration); - break; + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + const isFirstImport = sourceFile.imports.length && node === first(sourceFile.imports).parent || node === find(sourceFile.statements, isAnyImportSyntax); + // For first import, leave header comment in place, otherwise only delete JSDoc comments + deleteNode(changes, sourceFile, node, { + leadingTriviaOption: isFirstImport ? LeadingTriviaOption.Exclude : hasJSDocNodes(node) ? LeadingTriviaOption.JSDoc : LeadingTriviaOption.StartLine, + }); + break; - case SyntaxKind.TypeParameter: + case SyntaxKind.BindingElement: + const pattern = (node as BindingElement).parent; + const preserveComma = pattern.kind === SyntaxKind.ArrayBindingPattern && node !== last(pattern.elements); + if (preserveComma) { + deleteNode(changes, sourceFile, node); + } + else { deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); - break; + } + break; - case SyntaxKind.ImportSpecifier: - const namedImports = (node as ImportSpecifier).parent; - if (namedImports.elements.length === 1) { - deleteImportBinding(changes, sourceFile, namedImports); - } - else { - deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); - } - break; + case SyntaxKind.VariableDeclaration: + deleteVariableDeclaration(changes, deletedNodesInLists, sourceFile, node as VariableDeclaration); + break; - case SyntaxKind.NamespaceImport: - deleteImportBinding(changes, sourceFile, node as NamespaceImport); - break; + case SyntaxKind.TypeParameter: + deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); + break; - case SyntaxKind.SemicolonToken: - deleteNode(changes, sourceFile, node, { trailingTriviaOption: TrailingTriviaOption.Exclude }); - break; + case SyntaxKind.ImportSpecifier: + const namedImports = (node as ImportSpecifier).parent; + if (namedImports.elements.length === 1) { + deleteImportBinding(changes, sourceFile, namedImports); + } + else { + deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); + } + break; - case SyntaxKind.FunctionKeyword: - deleteNode(changes, sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.Exclude }); - break; + case SyntaxKind.NamespaceImport: + deleteImportBinding(changes, sourceFile, node as NamespaceImport); + break; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.FunctionDeclaration: - deleteNode(changes, sourceFile, node, { leadingTriviaOption: hasJSDocNodes(node) ? LeadingTriviaOption.JSDoc : LeadingTriviaOption.StartLine }); - break; + case SyntaxKind.SemicolonToken: + deleteNode(changes, sourceFile, node, { trailingTriviaOption: TrailingTriviaOption.Exclude }); + break; - default: - if (!node.parent) { - // a misbehaving client can reach here with the SourceFile node - deleteNode(changes, sourceFile, node); - } - else if (isImportClause(node.parent) && node.parent.name === node) { - deleteDefaultImport(changes, sourceFile, node.parent); - } - else if (isCallExpression(node.parent) && contains(node.parent.arguments, node)) { - deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); - } - else { - deleteNode(changes, sourceFile, node); - } - } - } + case SyntaxKind.FunctionKeyword: + deleteNode(changes, sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.Exclude }); + break; - function deleteDefaultImport(changes: ChangeTracker, sourceFile: SourceFile, importClause: ImportClause): void { - if (!importClause.namedBindings) { - // Delete the whole import - deleteNode(changes, sourceFile, importClause.parent); - } - else { - // import |d,| * as ns from './file' - const start = importClause.name!.getStart(sourceFile); - const nextToken = getTokenAtPosition(sourceFile, importClause.name!.end); - if (nextToken && nextToken.kind === SyntaxKind.CommaToken) { - // shift first non-whitespace position after comma to the start position of the node - const end = skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true); - changes.deleteRange(sourceFile, { pos: start, end }); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.FunctionDeclaration: + deleteNode(changes, sourceFile, node, { leadingTriviaOption: hasJSDocNodes(node) ? LeadingTriviaOption.JSDoc : LeadingTriviaOption.StartLine }); + break; + + default: + if (!node.parent) { + // a misbehaving client can reach here with the SourceFile node + deleteNode(changes, sourceFile, node); + } + else if (isImportClause(node.parent) && node.parent.name === node) { + deleteDefaultImport(changes, sourceFile, node.parent); + } + else if (isCallExpression(node.parent) && contains(node.parent.arguments, node)) { + deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); } else { - deleteNode(changes, sourceFile, importClause.name!); + deleteNode(changes, sourceFile, node); } - } } + } - function deleteImportBinding(changes: ChangeTracker, sourceFile: SourceFile, node: NamedImportBindings): void { - if (node.parent.name) { - // Delete named imports while preserving the default import - // import d|, * as ns| from './file' - // import d|, { a }| from './file' - const previousToken = Debug.checkDefined(getTokenAtPosition(sourceFile, node.pos - 1)); - changes.deleteRange(sourceFile, { pos: previousToken.getStart(sourceFile), end: node.end }); + function deleteDefaultImport(changes: ChangeTracker, sourceFile: SourceFile, importClause: ImportClause): void { + if (!importClause.namedBindings) { + // Delete the whole import + deleteNode(changes, sourceFile, importClause.parent); + } + else { + // import |d,| * as ns from './file' + const start = importClause.name!.getStart(sourceFile); + const nextToken = getTokenAtPosition(sourceFile, importClause.name!.end); + if (nextToken && nextToken.kind === SyntaxKind.CommaToken) { + // shift first non-whitespace position after comma to the start position of the node + const end = skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true); + changes.deleteRange(sourceFile, { pos: start, end }); } else { - // Delete the entire import declaration - // |import * as ns from './file'| - // |import { a } from './file'| - const importDecl = getAncestor(node, SyntaxKind.ImportDeclaration)!; - deleteNode(changes, sourceFile, importDecl); + deleteNode(changes, sourceFile, importClause.name!); } } + } - function deleteVariableDeclaration(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: VariableDeclaration): void { - const { parent } = node; + function deleteImportBinding(changes: ChangeTracker, sourceFile: SourceFile, node: NamedImportBindings): void { + if (node.parent.name) { + // Delete named imports while preserving the default import + // import d|, * as ns| from './file' + // import d|, { a }| from './file' + const previousToken = Debug.checkDefined(getTokenAtPosition(sourceFile, node.pos - 1)); + changes.deleteRange(sourceFile, { pos: previousToken.getStart(sourceFile), end: node.end }); + } + else { + // Delete the entire import declaration + // |import * as ns from './file'| + // |import { a } from './file'| + const importDecl = getAncestor(node, SyntaxKind.ImportDeclaration)!; + deleteNode(changes, sourceFile, importDecl); + } + } - if (parent.kind === SyntaxKind.CatchClause) { - // TODO: There's currently no unused diagnostic for this, could be a suggestion - changes.deleteNodeRange(sourceFile, findChildOfKind(parent, SyntaxKind.OpenParenToken, sourceFile)!, findChildOfKind(parent, SyntaxKind.CloseParenToken, sourceFile)!); - return; - } + function deleteVariableDeclaration(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: VariableDeclaration): void { + const { parent } = node; - if (parent.declarations.length !== 1) { - deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); - return; - } + if (parent.kind === SyntaxKind.CatchClause) { + // TODO: There's currently no unused diagnostic for this, could be a suggestion + changes.deleteNodeRange(sourceFile, findChildOfKind(parent, SyntaxKind.OpenParenToken, sourceFile)!, findChildOfKind(parent, SyntaxKind.CloseParenToken, sourceFile)!); + return; + } - const gp = parent.parent; - switch (gp.kind) { - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForInStatement: - changes.replaceNode(sourceFile, node, factory.createObjectLiteralExpression()); - break; + if (parent.declarations.length !== 1) { + deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); + return; + } - case SyntaxKind.ForStatement: - deleteNode(changes, sourceFile, parent); - break; + const gp = parent.parent; + switch (gp.kind) { + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForInStatement: + changes.replaceNode(sourceFile, node, factory.createObjectLiteralExpression()); + break; - case SyntaxKind.VariableStatement: - deleteNode(changes, sourceFile, gp, { leadingTriviaOption: hasJSDocNodes(gp) ? LeadingTriviaOption.JSDoc : LeadingTriviaOption.StartLine }); - break; + case SyntaxKind.ForStatement: + deleteNode(changes, sourceFile, parent); + break; - default: - Debug.assertNever(gp); - } - } - } + case SyntaxKind.VariableStatement: + deleteNode(changes, sourceFile, gp, { leadingTriviaOption: hasJSDocNodes(gp) ? LeadingTriviaOption.JSDoc : LeadingTriviaOption.StartLine }); + break; - /** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */ - // Exported for tests only! (TODO: improve tests to not need this) - export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { - const startPosition = getAdjustedStartPosition(sourceFile, node, options); - const endPosition = getAdjustedEndPosition(sourceFile, node, options); - changes.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); + default: + Debug.assertNever(gp); + } } +} - function deleteNodeInList(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: Node): void { - const containingList = Debug.checkDefined(formatting.SmartIndenter.getContainingList(node, sourceFile)); - const index = indexOfNode(containingList, node); - Debug.assert(index !== -1); - if (containingList.length === 1) { - deleteNode(changes, sourceFile, node); - return; - } +/** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */ +// Exported for tests only! (TODO: improve tests to not need this) +export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { + const startPosition = getAdjustedStartPosition(sourceFile, node, options); + const endPosition = getAdjustedEndPosition(sourceFile, node, options); + changes.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); +} - // Note: We will only delete a comma *after* a node. This will leave a trailing comma if we delete the last node. - // That's handled in the end by `finishTrailingCommaAfterDeletingNodesInList`. - Debug.assert(!deletedNodesInLists.has(node), "Deleting a node twice"); - deletedNodesInLists.add(node); - changes.deleteRange(sourceFile, { - pos: startPositionToDeleteNodeInList(sourceFile, node), - end: index === containingList.length - 1 ? getAdjustedEndPosition(sourceFile, node, {}) : startPositionToDeleteNodeInList(sourceFile, containingList[index + 1]), - }); +function deleteNodeInList(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: Node): void { + const containingList = Debug.checkDefined(formatting.SmartIndenter.getContainingList(node, sourceFile)); + const index = indexOfNode(containingList, node); + Debug.assert(index !== -1); + if (containingList.length === 1) { + deleteNode(changes, sourceFile, node); + return; } + + // Note: We will only delete a comma *after* a node. This will leave a trailing comma if we delete the last node. + // That's handled in the end by `finishTrailingCommaAfterDeletingNodesInList`. + Debug.assert(!deletedNodesInLists.has(node), "Deleting a node twice"); + deletedNodesInLists.add(node); + changes.deleteRange(sourceFile, { + pos: startPositionToDeleteNodeInList(sourceFile, node), + end: index === containingList.length - 1 ? getAdjustedEndPosition(sourceFile, node, {}) : startPositionToDeleteNodeInList(sourceFile, containingList[index + 1]), + }); +} } diff --git a/src/services/transform.ts b/src/services/transform.ts index c0d87f67b6f1e..e1240410ce070 100644 --- a/src/services/transform.ts +++ b/src/services/transform.ts @@ -1,16 +1,16 @@ namespace ts { - /** - * Transform one or more nodes using the supplied transformers. - * @param source A single `Node` or an array of `Node` objects. - * @param transformers An array of `TransformerFactory` callbacks used to process the transformation. - * @param compilerOptions Optional compiler options. - */ - export function transform(source: T | T[], transformers: TransformerFactory[], compilerOptions?: CompilerOptions) { - const diagnostics: DiagnosticWithLocation[] = []; - compilerOptions = fixupCompilerOptions(compilerOptions!, diagnostics); // TODO: GH#18217 - const nodes = isArray(source) ? source : [source]; - const result = transformNodes(/*resolver*/ undefined, /*emitHost*/ undefined, factory, compilerOptions, nodes, transformers, /*allowDtsFiles*/ true); - result.diagnostics = concatenate(result.diagnostics, diagnostics); - return result; - } -} \ No newline at end of file +/** + * Transform one or more nodes using the supplied transformers. + * @param source A single `Node` or an array of `Node` objects. + * @param transformers An array of `TransformerFactory` callbacks used to process the transformation. + * @param compilerOptions Optional compiler options. + */ +export function transform(source: T | T[], transformers: TransformerFactory[], compilerOptions?: CompilerOptions) { + const diagnostics: DiagnosticWithLocation[] = []; + compilerOptions = fixupCompilerOptions(compilerOptions!, diagnostics); // TODO: GH#18217 + const nodes = isArray(source) ? source : [source]; + const result = transformNodes(/*resolver*/ undefined, /*emitHost*/ undefined, factory, compilerOptions, nodes, transformers, /*allowDtsFiles*/ true); + result.diagnostics = concatenate(result.diagnostics, diagnostics); + return result; +} +} diff --git a/src/services/transpile.ts b/src/services/transpile.ts index 5b73b91bd7f47..6cbf9199b6226 100644 --- a/src/services/transpile.ts +++ b/src/services/transpile.ts @@ -1,146 +1,146 @@ namespace ts { - export interface TranspileOptions { - compilerOptions?: CompilerOptions; - fileName?: string; - reportDiagnostics?: boolean; - moduleName?: string; - renamedDependencies?: MapLike; - transformers?: CustomTransformers; - } +export interface TranspileOptions { + compilerOptions?: CompilerOptions; + fileName?: string; + reportDiagnostics?: boolean; + moduleName?: string; + renamedDependencies?: MapLike; + transformers?: CustomTransformers; +} - export interface TranspileOutput { - outputText: string; - diagnostics?: Diagnostic[]; - sourceMapText?: string; - } +export interface TranspileOutput { + outputText: string; + diagnostics?: Diagnostic[]; + sourceMapText?: string; +} - /* - * This function will compile source text from 'input' argument using specified compiler options. - * If not options are provided - it will use a set of default compiler options. - * Extra compiler options that will unconditionally be used by this function are: - * - isolatedModules = true - * - allowNonTsExtensions = true - * - noLib = true - * - noResolve = true - */ - export function transpileModule(input: string, transpileOptions: TranspileOptions): TranspileOutput { - const diagnostics: Diagnostic[] = []; - - const options: CompilerOptions = transpileOptions.compilerOptions ? fixupCompilerOptions(transpileOptions.compilerOptions, diagnostics) : {}; - - // mix in default options - const defaultOptions = getDefaultCompilerOptions(); - for (const key in defaultOptions) { - if (hasProperty(defaultOptions, key) && options[key] === undefined) { - options[key] = defaultOptions[key]; - } +/* + * This function will compile source text from 'input' argument using specified compiler options. + * If not options are provided - it will use a set of default compiler options. + * Extra compiler options that will unconditionally be used by this function are: + * - isolatedModules = true + * - allowNonTsExtensions = true + * - noLib = true + * - noResolve = true + */ +export function transpileModule(input: string, transpileOptions: TranspileOptions): TranspileOutput { + const diagnostics: Diagnostic[] = []; + + const options: CompilerOptions = transpileOptions.compilerOptions ? fixupCompilerOptions(transpileOptions.compilerOptions, diagnostics) : {}; + + // mix in default options + const defaultOptions = getDefaultCompilerOptions(); + for (const key in defaultOptions) { + if (hasProperty(defaultOptions, key) && options[key] === undefined) { + options[key] = defaultOptions[key]; } + } - for (const option of transpileOptionValueCompilerOptions) { - options[option.name] = option.transpileOptionValue; - } + for (const option of transpileOptionValueCompilerOptions) { + options[option.name] = option.transpileOptionValue; + } - // transpileModule does not write anything to disk so there is no need to verify that there are no conflicts between input and output paths. - options.suppressOutputPathCheck = true; + // transpileModule does not write anything to disk so there is no need to verify that there are no conflicts between input and output paths. + options.suppressOutputPathCheck = true; - // Filename can be non-ts file. - options.allowNonTsExtensions = true; + // Filename can be non-ts file. + options.allowNonTsExtensions = true; - // if jsx is specified then treat file as .tsx - const inputFileName = transpileOptions.fileName || (transpileOptions.compilerOptions && transpileOptions.compilerOptions.jsx ? "module.tsx" : "module.ts"); - const sourceFile = createSourceFile(inputFileName, input, getEmitScriptTarget(options)); - if (transpileOptions.moduleName) { - sourceFile.moduleName = transpileOptions.moduleName; - } + // if jsx is specified then treat file as .tsx + const inputFileName = transpileOptions.fileName || (transpileOptions.compilerOptions && transpileOptions.compilerOptions.jsx ? "module.tsx" : "module.ts"); + const sourceFile = createSourceFile(inputFileName, input, getEmitScriptTarget(options)); + if (transpileOptions.moduleName) { + sourceFile.moduleName = transpileOptions.moduleName; + } - if (transpileOptions.renamedDependencies) { - sourceFile.renamedDependencies = new Map(getEntries(transpileOptions.renamedDependencies)); - } + if (transpileOptions.renamedDependencies) { + sourceFile.renamedDependencies = new Map(getEntries(transpileOptions.renamedDependencies)); + } - const newLine = getNewLineCharacter(options); - - // Output - let outputText: string | undefined; - let sourceMapText: string | undefined; - - // Create a compilerHost object to allow the compiler to read and write files - const compilerHost: CompilerHost = { - getSourceFile: (fileName) => fileName === normalizePath(inputFileName) ? sourceFile : undefined, - writeFile: (name, text) => { - if (fileExtensionIs(name, ".map")) { - Debug.assertEqual(sourceMapText, undefined, "Unexpected multiple source map outputs, file:", name); - sourceMapText = text; - } - else { - Debug.assertEqual(outputText, undefined, "Unexpected multiple outputs, file:", name); - outputText = text; - } - }, - getDefaultLibFileName: () => "lib.d.ts", - useCaseSensitiveFileNames: () => false, - getCanonicalFileName: fileName => fileName, - getCurrentDirectory: () => "", - getNewLine: () => newLine, - fileExists: (fileName): boolean => fileName === inputFileName, - readFile: () => "", - directoryExists: () => true, - getDirectories: () => [] - }; - - const program = createProgram([inputFileName], options, compilerHost); - - if (transpileOptions.reportDiagnostics) { - addRange(/*to*/ diagnostics, /*from*/ program.getSyntacticDiagnostics(sourceFile)); - addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics()); - } - // Emit - program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, transpileOptions.transformers); + const newLine = getNewLineCharacter(options); - if (outputText === undefined) return Debug.fail("Output generation failed"); + // Output + let outputText: string | undefined; + let sourceMapText: string | undefined; - return { outputText, diagnostics, sourceMapText }; + // Create a compilerHost object to allow the compiler to read and write files + const compilerHost: CompilerHost = { + getSourceFile: (fileName) => fileName === normalizePath(inputFileName) ? sourceFile : undefined, + writeFile: (name, text) => { + if (fileExtensionIs(name, ".map")) { + Debug.assertEqual(sourceMapText, undefined, "Unexpected multiple source map outputs, file:", name); + sourceMapText = text; + } + else { + Debug.assertEqual(outputText, undefined, "Unexpected multiple outputs, file:", name); + outputText = text; + } + }, + getDefaultLibFileName: () => "lib.d.ts", + useCaseSensitiveFileNames: () => false, + getCanonicalFileName: fileName => fileName, + getCurrentDirectory: () => "", + getNewLine: () => newLine, + fileExists: (fileName): boolean => fileName === inputFileName, + readFile: () => "", + directoryExists: () => true, + getDirectories: () => [] + }; + + const program = createProgram([inputFileName], options, compilerHost); + + if (transpileOptions.reportDiagnostics) { + addRange(/*to*/ diagnostics, /*from*/ program.getSyntacticDiagnostics(sourceFile)); + addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics()); } + // Emit + program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, transpileOptions.transformers); - /* - * This is a shortcut function for transpileModule - it accepts transpileOptions as parameters and returns only outputText part of the result. - */ - export function transpile(input: string, compilerOptions?: CompilerOptions, fileName?: string, diagnostics?: Diagnostic[], moduleName?: string): string { - const output = transpileModule(input, { compilerOptions, fileName, reportDiagnostics: !!diagnostics, moduleName }); - // addRange correctly handles cases when wither 'from' or 'to' argument is missing - addRange(diagnostics, output.diagnostics); - return output.outputText; - } + if (outputText === undefined) return Debug.fail("Output generation failed"); - let commandLineOptionsStringToEnum: CommandLineOptionOfCustomType[]; + return { outputText, diagnostics, sourceMapText }; +} - /** JS users may pass in string values for enum compiler options (such as ModuleKind), so convert. */ - /*@internal*/ - export function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions { - // Lazily create this value to fix module loading errors. - commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || - filter(optionDeclarations, o => typeof o.type === "object" && !forEachEntry(o.type, v => typeof v !== "number")) as CommandLineOptionOfCustomType[]; +/* + * This is a shortcut function for transpileModule - it accepts transpileOptions as parameters and returns only outputText part of the result. + */ +export function transpile(input: string, compilerOptions?: CompilerOptions, fileName?: string, diagnostics?: Diagnostic[], moduleName?: string): string { + const output = transpileModule(input, { compilerOptions, fileName, reportDiagnostics: !!diagnostics, moduleName }); + // addRange correctly handles cases when wither 'from' or 'to' argument is missing + addRange(diagnostics, output.diagnostics); + return output.outputText; +} - options = cloneCompilerOptions(options); +let commandLineOptionsStringToEnum: CommandLineOptionOfCustomType[]; - for (const opt of commandLineOptionsStringToEnum) { - if (!hasProperty(options, opt.name)) { - continue; - } +/** JS users may pass in string values for enum compiler options (such as ModuleKind), so convert. */ +/*@internal*/ +export function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions { + // Lazily create this value to fix module loading errors. + commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || + filter(optionDeclarations, o => typeof o.type === "object" && !forEachEntry(o.type, v => typeof v !== "number")) as CommandLineOptionOfCustomType[]; - const value = options[opt.name]; - // Value should be a key of opt.type - if (isString(value)) { - // If value is not a string, this will fail - options[opt.name] = parseCustomTypeOption(opt, value, diagnostics); - } - else { - if (!forEachEntry(opt.type, v => v === value)) { - // Supplied value isn't a valid enum value. - diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt)); - } - } + options = cloneCompilerOptions(options); + + for (const opt of commandLineOptionsStringToEnum) { + if (!hasProperty(options, opt.name)) { + continue; } - return options; + const value = options[opt.name]; + // Value should be a key of opt.type + if (isString(value)) { + // If value is not a string, this will fail + options[opt.name] = parseCustomTypeOption(opt, value, diagnostics); + } + else { + if (!forEachEntry(opt.type, v => v === value)) { + // Supplied value isn't a valid enum value. + diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt)); + } + } } + + return options; +} } diff --git a/src/services/types.ts b/src/services/types.ts index 0659e6295e608..72f6666e8752e 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -1,1617 +1,1617 @@ namespace ts { - export interface Node { - getSourceFile(): SourceFile; - getChildCount(sourceFile?: SourceFile): number; - getChildAt(index: number, sourceFile?: SourceFile): Node; - getChildren(sourceFile?: SourceFile): Node[]; - /* @internal */ - getChildren(sourceFile?: SourceFileLike): Node[]; // eslint-disable-line @typescript-eslint/unified-signatures - getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number; - /* @internal */ - getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number; // eslint-disable-line @typescript-eslint/unified-signatures - getFullStart(): number; - getEnd(): number; - getWidth(sourceFile?: SourceFileLike): number; - getFullWidth(): number; - getLeadingTriviaWidth(sourceFile?: SourceFile): number; - getFullText(sourceFile?: SourceFile): string; - getText(sourceFile?: SourceFile): string; - getFirstToken(sourceFile?: SourceFile): Node | undefined; - /* @internal */ - getFirstToken(sourceFile?: SourceFileLike): Node | undefined; // eslint-disable-line @typescript-eslint/unified-signatures - getLastToken(sourceFile?: SourceFile): Node | undefined; - /* @internal */ - getLastToken(sourceFile?: SourceFileLike): Node | undefined; // eslint-disable-line @typescript-eslint/unified-signatures - // See ts.forEachChild for documentation. - forEachChild(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray) => T | undefined): T | undefined; - } +export interface Node { + getSourceFile(): SourceFile; + getChildCount(sourceFile?: SourceFile): number; + getChildAt(index: number, sourceFile?: SourceFile): Node; + getChildren(sourceFile?: SourceFile): Node[]; + /* @internal */ + getChildren(sourceFile?: SourceFileLike): Node[]; // eslint-disable-line @typescript-eslint/unified-signatures + getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number; + /* @internal */ + getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number; // eslint-disable-line @typescript-eslint/unified-signatures + getFullStart(): number; + getEnd(): number; + getWidth(sourceFile?: SourceFileLike): number; + getFullWidth(): number; + getLeadingTriviaWidth(sourceFile?: SourceFile): number; + getFullText(sourceFile?: SourceFile): string; + getText(sourceFile?: SourceFile): string; + getFirstToken(sourceFile?: SourceFile): Node | undefined; + /* @internal */ + getFirstToken(sourceFile?: SourceFileLike): Node | undefined; // eslint-disable-line @typescript-eslint/unified-signatures + getLastToken(sourceFile?: SourceFile): Node | undefined; + /* @internal */ + getLastToken(sourceFile?: SourceFileLike): Node | undefined; // eslint-disable-line @typescript-eslint/unified-signatures + // See ts.forEachChild for documentation. + forEachChild(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray) => T | undefined): T | undefined; +} - export interface Identifier { - readonly text: string; - } +export interface Identifier { + readonly text: string; +} - export interface PrivateIdentifier { - readonly text: string; - } +export interface PrivateIdentifier { + readonly text: string; +} - export interface Symbol { - readonly name: string; - getFlags(): SymbolFlags; - getEscapedName(): __String; - getName(): string; - getDeclarations(): Declaration[] | undefined; - getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[]; - /* @internal */ - getContextualDocumentationComment(context: Node | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] - getJsDocTags(checker?: TypeChecker): JSDocTagInfo[]; - } +export interface Symbol { + readonly name: string; + getFlags(): SymbolFlags; + getEscapedName(): __String; + getName(): string; + getDeclarations(): Declaration[] | undefined; + getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[]; + /* @internal */ + getContextualDocumentationComment(context: Node | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] + getJsDocTags(checker?: TypeChecker): JSDocTagInfo[]; +} - export interface Type { - getFlags(): TypeFlags; - getSymbol(): Symbol | undefined; - getProperties(): Symbol[]; - getProperty(propertyName: string): Symbol | undefined; - getApparentProperties(): Symbol[]; - getCallSignatures(): readonly Signature[]; - getConstructSignatures(): readonly Signature[]; - getStringIndexType(): Type | undefined; - getNumberIndexType(): Type | undefined; - getBaseTypes(): BaseType[] | undefined; - getNonNullableType(): Type; - /*@internal*/ getNonOptionalType(): Type; - /*@internal*/ isNullableType(): boolean; - getConstraint(): Type | undefined; - getDefault(): Type | undefined; - - isUnion(): this is UnionType; - isIntersection(): this is IntersectionType; - isUnionOrIntersection(): this is UnionOrIntersectionType; - isLiteral(): this is LiteralType; - isStringLiteral(): this is StringLiteralType; - isNumberLiteral(): this is NumberLiteralType; - isTypeParameter(): this is TypeParameter; - isClassOrInterface(): this is InterfaceType; - isClass(): this is InterfaceType; - } +export interface Type { + getFlags(): TypeFlags; + getSymbol(): Symbol | undefined; + getProperties(): Symbol[]; + getProperty(propertyName: string): Symbol | undefined; + getApparentProperties(): Symbol[]; + getCallSignatures(): readonly Signature[]; + getConstructSignatures(): readonly Signature[]; + getStringIndexType(): Type | undefined; + getNumberIndexType(): Type | undefined; + getBaseTypes(): BaseType[] | undefined; + getNonNullableType(): Type; + /*@internal*/ getNonOptionalType(): Type; + /*@internal*/ isNullableType(): boolean; + getConstraint(): Type | undefined; + getDefault(): Type | undefined; + + isUnion(): this is UnionType; + isIntersection(): this is IntersectionType; + isUnionOrIntersection(): this is UnionOrIntersectionType; + isLiteral(): this is LiteralType; + isStringLiteral(): this is StringLiteralType; + isNumberLiteral(): this is NumberLiteralType; + isTypeParameter(): this is TypeParameter; + isClassOrInterface(): this is InterfaceType; + isClass(): this is InterfaceType; +} - export interface TypeReference { - typeArguments?: readonly Type[]; - } +export interface TypeReference { + typeArguments?: readonly Type[]; +} - export interface Signature { - getDeclaration(): SignatureDeclaration; - getTypeParameters(): TypeParameter[] | undefined; - getParameters(): Symbol[]; - getReturnType(): Type; - getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[]; - getJsDocTags(): JSDocTagInfo[]; - } +export interface Signature { + getDeclaration(): SignatureDeclaration; + getTypeParameters(): TypeParameter[] | undefined; + getParameters(): Symbol[]; + getReturnType(): Type; + getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[]; + getJsDocTags(): JSDocTagInfo[]; +} - export interface SourceFile { - /* @internal */ version: string; - /* @internal */ scriptSnapshot: IScriptSnapshot | undefined; - /* @internal */ nameTable: UnderscoreEscapedMap | undefined; +export interface SourceFile { + /* @internal */ version: string; + /* @internal */ scriptSnapshot: IScriptSnapshot | undefined; + /* @internal */ nameTable: UnderscoreEscapedMap | undefined; - /* @internal */ getNamedDeclarations(): ESMap; + /* @internal */ getNamedDeclarations(): ESMap; - getLineAndCharacterOfPosition(pos: number): LineAndCharacter; - getLineEndOfPosition(pos: number): number; - getLineStarts(): readonly number[]; - getPositionOfLineAndCharacter(line: number, character: number): number; - update(newText: string, textChangeRange: TextChangeRange): SourceFile; + getLineAndCharacterOfPosition(pos: number): LineAndCharacter; + getLineEndOfPosition(pos: number): number; + getLineStarts(): readonly number[]; + getPositionOfLineAndCharacter(line: number, character: number): number; + update(newText: string, textChangeRange: TextChangeRange): SourceFile; - /* @internal */ sourceMapper?: DocumentPositionMapper; - } + /* @internal */ sourceMapper?: DocumentPositionMapper; +} - export interface SourceFileLike { - getLineAndCharacterOfPosition(pos: number): LineAndCharacter; - } +export interface SourceFileLike { + getLineAndCharacterOfPosition(pos: number): LineAndCharacter; +} - export interface SourceMapSource { - getLineAndCharacterOfPosition(pos: number): LineAndCharacter; - } +export interface SourceMapSource { + getLineAndCharacterOfPosition(pos: number): LineAndCharacter; +} + +/** + * Represents an immutable snapshot of a script at a specified time.Once acquired, the + * snapshot is observably immutable. i.e. the same calls with the same parameters will return + * the same values. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export interface IScriptSnapshot { + /** Gets a portion of the script snapshot specified by [start, end). */ + getText(start: number, end: number): string; + + /** Gets the length of this script snapshot. */ + getLength(): number; /** - * Represents an immutable snapshot of a script at a specified time.Once acquired, the - * snapshot is observably immutable. i.e. the same calls with the same parameters will return - * the same values. + * Gets the TextChangeRange that describe how the text changed between this text and + * an older version. This information is used by the incremental parser to determine + * what sections of the script need to be re-parsed. 'undefined' can be returned if the + * change range cannot be determined. However, in that case, incremental parsing will + * not happen and the entire document will be re - parsed. */ - // eslint-disable-next-line @typescript-eslint/naming-convention - export interface IScriptSnapshot { - /** Gets a portion of the script snapshot specified by [start, end). */ - getText(start: number, end: number): string; - - /** Gets the length of this script snapshot. */ - getLength(): number; - - /** - * Gets the TextChangeRange that describe how the text changed between this text and - * an older version. This information is used by the incremental parser to determine - * what sections of the script need to be re-parsed. 'undefined' can be returned if the - * change range cannot be determined. However, in that case, incremental parsing will - * not happen and the entire document will be re - parsed. - */ - getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined; - - /** Releases all resources held by this script snapshot */ - dispose?(): void; - } - - export namespace ScriptSnapshot { - class StringScriptSnapshot implements IScriptSnapshot { + getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined; - constructor(private text: string) { - } + /** Releases all resources held by this script snapshot */ + dispose?(): void; +} - public getText(start: number, end: number): string { - return start === 0 && end === this.text.length - ? this.text - : this.text.substring(start, end); - } +export namespace ScriptSnapshot { + class StringScriptSnapshot implements IScriptSnapshot { - public getLength(): number { - return this.text.length; - } + constructor(private text: string) { + } - public getChangeRange(): TextChangeRange | undefined { - // Text-based snapshots do not support incremental parsing. Return undefined - // to signal that to the caller. - return undefined; - } + public getText(start: number, end: number): string { + return start === 0 && end === this.text.length + ? this.text + : this.text.substring(start, end); } - export function fromString(text: string): IScriptSnapshot { - return new StringScriptSnapshot(text); + public getLength(): number { + return this.text.length; } - } - export interface PreProcessedFileInfo { - referencedFiles: FileReference[]; - typeReferenceDirectives: FileReference[]; - libReferenceDirectives: FileReference[]; - importedFiles: FileReference[]; - ambientExternalModules?: string[]; - isLibFile: boolean; + public getChangeRange(): TextChangeRange | undefined { + // Text-based snapshots do not support incremental parsing. Return undefined + // to signal that to the caller. + return undefined; + } } - export interface HostCancellationToken { - isCancellationRequested(): boolean; + export function fromString(text: string): IScriptSnapshot { + return new StringScriptSnapshot(text); } +} - export interface InstallPackageOptions { - fileName: Path; - packageName: string; - } +export interface PreProcessedFileInfo { + referencedFiles: FileReference[]; + typeReferenceDirectives: FileReference[]; + libReferenceDirectives: FileReference[]; + importedFiles: FileReference[]; + ambientExternalModules?: string[]; + isLibFile: boolean; +} - /* @internal */ - export const enum PackageJsonDependencyGroup { - Dependencies = 1 << 0, - DevDependencies = 1 << 1, - PeerDependencies = 1 << 2, - OptionalDependencies = 1 << 3, - All = Dependencies | DevDependencies | PeerDependencies | OptionalDependencies, - } +export interface HostCancellationToken { + isCancellationRequested(): boolean; +} - /* @internal */ - export interface PackageJsonInfo { - fileName: string; - parseable: boolean; - dependencies?: ESMap; - devDependencies?: ESMap; - peerDependencies?: ESMap; - optionalDependencies?: ESMap; - get(dependencyName: string, inGroups?: PackageJsonDependencyGroup): string | undefined; - has(dependencyName: string, inGroups?: PackageJsonDependencyGroup): boolean; - } +export interface InstallPackageOptions { + fileName: Path; + packageName: string; +} - /* @internal */ - export interface FormattingHost { - getNewLine?(): string; - } +/* @internal */ +export const enum PackageJsonDependencyGroup { + Dependencies = 1 << 0, + DevDependencies = 1 << 1, + PeerDependencies = 1 << 2, + OptionalDependencies = 1 << 3, + All = Dependencies | DevDependencies | PeerDependencies | OptionalDependencies, +} - /* @internal */ - export const enum PackageJsonAutoImportPreference { - Off, - On, - Auto, - } +/* @internal */ +export interface PackageJsonInfo { + fileName: string; + parseable: boolean; + dependencies?: ESMap; + devDependencies?: ESMap; + peerDependencies?: ESMap; + optionalDependencies?: ESMap; + get(dependencyName: string, inGroups?: PackageJsonDependencyGroup): string | undefined; + has(dependencyName: string, inGroups?: PackageJsonDependencyGroup): boolean; +} - export interface PerformanceEvent { - kind: "UpdateGraph" | "CreatePackageJsonAutoImportProvider"; - durationMs: number; - } +/* @internal */ +export interface FormattingHost { + getNewLine?(): string; +} - export enum LanguageServiceMode { - Semantic, - PartialSemantic, - Syntactic, - } +/* @internal */ +export const enum PackageJsonAutoImportPreference { + Off, + On, + Auto, +} - export interface IncompleteCompletionsCache { - get(): CompletionInfo | undefined; - set(response: CompletionInfo): void; - clear(): void; - } +export interface PerformanceEvent { + kind: "UpdateGraph" | "CreatePackageJsonAutoImportProvider"; + durationMs: number; +} - // - // Public interface of the host of a language service instance. - // - export interface LanguageServiceHost extends GetEffectiveTypeRootsHost { - getCompilationSettings(): CompilerOptions; - getNewLine?(): string; - getProjectVersion?(): string; - getScriptFileNames(): string[]; - getScriptKind?(fileName: string): ScriptKind; - getScriptVersion(fileName: string): string; - getScriptSnapshot(fileName: string): IScriptSnapshot | undefined; - getProjectReferences?(): readonly ProjectReference[] | undefined; - getLocalizedDiagnosticMessages?(): any; - getCancellationToken?(): HostCancellationToken; - getCurrentDirectory(): string; - getDefaultLibFileName(options: CompilerOptions): string; - log?(s: string): void; - trace?(s: string): void; - error?(s: string): void; - useCaseSensitiveFileNames?(): boolean; - - /* - * LS host can optionally implement these methods to support completions for module specifiers. - * Without these methods, only completions for ambient modules will be provided. - */ - readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; - readFile?(path: string, encoding?: string): string | undefined; - realpath?(path: string): string; - fileExists?(path: string): boolean; - - /* - * LS host can optionally implement these methods to support automatic updating when new type libraries are installed - */ - getTypeRootsVersion?(): number; - - /* - * LS host can optionally implement this method if it wants to be completely in charge of module name resolution. - * if implementation is omitted then language service will use built-in module resolution logic and get answers to - * host specific questions using 'getScriptSnapshot'. - * - * If this is implemented, `getResolvedModuleWithFailedLookupLocationsFromCache` should be too. - */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; - getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined; - resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedTypeReferenceDirective | undefined)[]; - /* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution; - /* @internal */ hasChangedAutomaticTypeDirectiveNames?: HasChangedAutomaticTypeDirectiveNames; - /* @internal */ getGlobalTypingsCacheLocation?(): string | undefined; - /* @internal */ getSymlinkCache?(files?: readonly SourceFile[]): SymlinkCache; - - /* - * Required for full import and type reference completions. - * These should be unprefixed names. E.g. `getDirectories("/foo/bar")` should return `["a", "b"]`, not `["/foo/bar/a", "/foo/bar/b"]`. - */ - getDirectories?(directoryName: string): string[]; - - /** - * Gets a set of custom transformers to use during emit. - */ - getCustomTransformers?(): CustomTransformers | undefined; - - isKnownTypesPackageName?(name: string): boolean; - installPackage?(options: InstallPackageOptions): Promise; - writeFile?(fileName: string, content: string): void; - - /* @internal */ getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; - /* @internal */ getSourceFileLike?(fileName: string): SourceFileLike | undefined; - /* @internal */ getPackageJsonsVisibleToFile?(fileName: string, rootDir?: string): readonly PackageJsonInfo[]; - /* @internal */ getNearestAncestorDirectoryWithPackageJson?(fileName: string): string | undefined; - /* @internal */ getPackageJsonsForAutoImport?(rootDir?: string): readonly PackageJsonInfo[]; - /* @internal */ getCachedExportInfoMap?(): ExportInfoMap; - /* @internal */ getModuleSpecifierCache?(): ModuleSpecifierCache; - /* @internal */ setCompilerHost?(host: CompilerHost): void; - /* @internal */ useSourceOfProjectReferenceRedirect?(): boolean; - /* @internal */ getPackageJsonAutoImportProvider?(): Program | undefined; - /* @internal */ sendPerformanceEvent?(kind: PerformanceEvent["kind"], durationMs: number): void; - getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; - /* @internal */ onReleaseParsedCommandLine?(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, optionOptions: CompilerOptions): void; - /* @internal */ getIncompleteCompletionsCache?(): IncompleteCompletionsCache; - } +export enum LanguageServiceMode { + Semantic, + PartialSemantic, + Syntactic, +} - /* @internal */ - export const emptyOptions = {}; +export interface IncompleteCompletionsCache { + get(): CompletionInfo | undefined; + set(response: CompletionInfo): void; + clear(): void; +} - export type WithMetadata = T & { metadata?: unknown; }; +// +// Public interface of the host of a language service instance. +// +export interface LanguageServiceHost extends GetEffectiveTypeRootsHost { + getCompilationSettings(): CompilerOptions; + getNewLine?(): string; + getProjectVersion?(): string; + getScriptFileNames(): string[]; + getScriptKind?(fileName: string): ScriptKind; + getScriptVersion(fileName: string): string; + getScriptSnapshot(fileName: string): IScriptSnapshot | undefined; + getProjectReferences?(): readonly ProjectReference[] | undefined; + getLocalizedDiagnosticMessages?(): any; + getCancellationToken?(): HostCancellationToken; + getCurrentDirectory(): string; + getDefaultLibFileName(options: CompilerOptions): string; + log?(s: string): void; + trace?(s: string): void; + error?(s: string): void; + useCaseSensitiveFileNames?(): boolean; + + /* + * LS host can optionally implement these methods to support completions for module specifiers. + * Without these methods, only completions for ambient modules will be provided. + */ + readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; + readFile?(path: string, encoding?: string): string | undefined; + realpath?(path: string): string; + fileExists?(path: string): boolean; - export const enum SemanticClassificationFormat { - Original = "original", - TwentyTwenty = "2020" - } + /* + * LS host can optionally implement these methods to support automatic updating when new type libraries are installed + */ + getTypeRootsVersion?(): number; + + /* + * LS host can optionally implement this method if it wants to be completely in charge of module name resolution. + * if implementation is omitted then language service will use built-in module resolution logic and get answers to + * host specific questions using 'getScriptSnapshot'. + * + * If this is implemented, `getResolvedModuleWithFailedLookupLocationsFromCache` should be too. + */ + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined; + resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedTypeReferenceDirective | undefined)[]; + /* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution; + /* @internal */ hasChangedAutomaticTypeDirectiveNames?: HasChangedAutomaticTypeDirectiveNames; + /* @internal */ getGlobalTypingsCacheLocation?(): string | undefined; + /* @internal */ getSymlinkCache?(files?: readonly SourceFile[]): SymlinkCache; + + /* + * Required for full import and type reference completions. + * These should be unprefixed names. E.g. `getDirectories("/foo/bar")` should return `["a", "b"]`, not `["/foo/bar/a", "/foo/bar/b"]`. + */ + getDirectories?(directoryName: string): string[]; - // - // Public services of a language service instance associated - // with a language service host instance - // - export interface LanguageService { - /** This is used as a part of restarting the language service. */ - cleanupSemanticCache(): void; - - /** - * Gets errors indicating invalid syntax in a file. - * - * In English, "this cdeo have, erorrs" is syntactically invalid because it has typos, - * grammatical errors, and misplaced punctuation. Likewise, examples of syntax - * errors in TypeScript are missing parentheses in an `if` statement, mismatched - * curly braces, and using a reserved keyword as a variable name. - * - * These diagnostics are inexpensive to compute and don't require knowledge of - * other files. Note that a non-empty result increases the likelihood of false positives - * from `getSemanticDiagnostics`. - * - * While these represent the majority of syntax-related diagnostics, there are some - * that require the type system, which will be present in `getSemanticDiagnostics`. - * - * @param fileName A path to the file you want syntactic diagnostics for - */ - getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[]; - - /** - * Gets warnings or errors indicating type system issues in a given file. - * Requesting semantic diagnostics may start up the type system and - * run deferred work, so the first call may take longer than subsequent calls. - * - * Unlike the other get*Diagnostics functions, these diagnostics can potentially not - * include a reference to a source file. Specifically, the first time this is called, - * it will return global diagnostics with no associated location. - * - * To contrast the differences between semantic and syntactic diagnostics, consider the - * sentence: "The sun is green." is syntactically correct; those are real English words with - * correct sentence structure. However, it is semantically invalid, because it is not true. - * - * @param fileName A path to the file you want semantic diagnostics for - */ - getSemanticDiagnostics(fileName: string): Diagnostic[]; - - /** - * Gets suggestion diagnostics for a specific file. These diagnostics tend to - * proactively suggest refactors, as opposed to diagnostics that indicate - * potentially incorrect runtime behavior. - * - * @param fileName A path to the file you want semantic diagnostics for - */ - getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[]; - - // TODO: Rename this to getProgramDiagnostics to better indicate that these are any - // diagnostics present for the program level, and not just 'options' diagnostics. - - /** - * Gets global diagnostics related to the program configuration and compiler options. - */ - getCompilerOptionsDiagnostics(): Diagnostic[]; - - /** @deprecated Use getEncodedSyntacticClassifications instead. */ - getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; - getSyntacticClassifications(fileName: string, span: TextSpan, format: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[]; - - /** @deprecated Use getEncodedSemanticClassifications instead. */ - getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; - getSemanticClassifications(fileName: string, span: TextSpan, format: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[]; - - /** Encoded as triples of [start, length, ClassificationType]. */ - getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications; - - /** - * Gets semantic highlights information for a particular file. Has two formats, an older - * version used by VS and a format used by VS Code. - * - * @param fileName The path to the file - * @param position A text span to return results within - * @param format Which format to use, defaults to "original" - * @returns a number array encoded as triples of [start, length, ClassificationType, ...]. - */ - getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications; - - /** - * Gets completion entries at a particular position in a file. - * - * @param fileName The path to the file - * @param position A zero-based index of the character where you want the entries - * @param options An object describing how the request was triggered and what kinds - * of code actions can be returned with the completions. - */ - getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined): WithMetadata | undefined; - - /** - * Gets the extended details for a completion entry retrieved from `getCompletionsAtPosition`. - * - * @param fileName The path to the file - * @param position A zero based index of the character where you want the entries - * @param entryName The `name` from an existing completion which came from `getCompletionsAtPosition` - * @param formatOptions How should code samples in the completions be formatted, can be undefined for backwards compatibility - * @param source `source` property from the completion entry - * @param preferences User settings, can be undefined for backwards compatibility - * @param data `data` property from the completion entry - */ - getCompletionEntryDetails( - fileName: string, - position: number, - entryName: string, - formatOptions: FormatCodeOptions | FormatCodeSettings | undefined, - source: string | undefined, - preferences: UserPreferences | undefined, - data: CompletionEntryData | undefined, - ): CompletionEntryDetails | undefined; - - getCompletionEntrySymbol(fileName: string, position: number, name: string, source: string | undefined): Symbol | undefined; - - /** - * Gets semantic information about the identifier at a particular position in a - * file. Quick info is what you typically see when you hover in an editor. - * - * @param fileName The path to the file - * @param position A zero-based index of the character where you want the quick info - */ - getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined; - - getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan | undefined; - - getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined; - - getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): SignatureHelpItems | undefined; - - getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): RenameInfo; - findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): readonly RenameLocation[] | undefined; - - getSmartSelectionRange(fileName: string, position: number): SelectionRange; - - getDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined; - getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined; - getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined; - getImplementationAtPosition(fileName: string, position: number): readonly ImplementationLocation[] | undefined; - - getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined; - findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined; - getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] | undefined; - getFileReferences(fileName: string): ReferenceEntry[]; - - /** @deprecated */ - getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined; - - getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[]; - getNavigationBarItems(fileName: string): NavigationBarItem[]; - getNavigationTree(fileName: string): NavigationTree; - - prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined; - provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; - provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; - - provideInlayHints(fileName: string, span: TextSpan, preferences: UserPreferences | undefined): InlayHint[] - - getOutliningSpans(fileName: string): OutliningSpan[]; - getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; - getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; - getIndentationAtPosition(fileName: string, position: number, options: EditorOptions | EditorSettings): number; - - getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; - getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; - getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; - - getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined; - - isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; - /** - * This will return a defined result if the position is after the `>` of the opening tag, or somewhere in the text, of a JSXElement with no closing tag. - * Editors should call this after `>` is typed. - */ - getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined; - - getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined; - - toLineColumnOffset?(fileName: string, position: number): LineAndCharacter; - /** @internal */ - getSourceMapper(): SourceMapper; - /** @internal */ - clearSourceMapperCache(): void; - - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: FormatCodeSettings, preferences: UserPreferences): readonly CodeFixAction[]; - getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences): CombinedCodeActions; - - applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise; - applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; - applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; - /** @deprecated `fileName` will be ignored */ - applyCodeActionCommand(fileName: string, action: CodeActionCommand): Promise; - /** @deprecated `fileName` will be ignored */ - applyCodeActionCommand(fileName: string, action: CodeActionCommand[]): Promise; - /** @deprecated `fileName` will be ignored */ - applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise; - - getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[]; - getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; - organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; - getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; - - getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): EmitOutput; - - getProgram(): Program | undefined; + /** + * Gets a set of custom transformers to use during emit. + */ + getCustomTransformers?(): CustomTransformers | undefined; + + isKnownTypesPackageName?(name: string): boolean; + installPackage?(options: InstallPackageOptions): Promise; + writeFile?(fileName: string, content: string): void; + + /* @internal */ getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; + /* @internal */ getSourceFileLike?(fileName: string): SourceFileLike | undefined; + /* @internal */ getPackageJsonsVisibleToFile?(fileName: string, rootDir?: string): readonly PackageJsonInfo[]; + /* @internal */ getNearestAncestorDirectoryWithPackageJson?(fileName: string): string | undefined; + /* @internal */ getPackageJsonsForAutoImport?(rootDir?: string): readonly PackageJsonInfo[]; + /* @internal */ getCachedExportInfoMap?(): ExportInfoMap; + /* @internal */ getModuleSpecifierCache?(): ModuleSpecifierCache; + /* @internal */ setCompilerHost?(host: CompilerHost): void; + /* @internal */ useSourceOfProjectReferenceRedirect?(): boolean; + /* @internal */ getPackageJsonAutoImportProvider?(): Program | undefined; + /* @internal */ sendPerformanceEvent?(kind: PerformanceEvent["kind"], durationMs: number): void; + getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; + /* @internal */ onReleaseParsedCommandLine?(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, optionOptions: CompilerOptions): void; + /* @internal */ getIncompleteCompletionsCache?(): IncompleteCompletionsCache; +} - /* @internal */ getNonBoundSourceFile(fileName: string): SourceFile; - /* @internal */ getAutoImportProvider(): Program | undefined; +/* @internal */ +export const emptyOptions = {}; - toggleLineComment(fileName: string, textRange: TextRange): TextChange[]; - toggleMultilineComment(fileName: string, textRange: TextRange): TextChange[]; - commentSelection(fileName: string, textRange: TextRange): TextChange[]; - uncommentSelection(fileName: string, textRange: TextRange): TextChange[]; +export type WithMetadata = T & { metadata?: unknown; }; - dispose(): void; - } +export const enum SemanticClassificationFormat { + Original = "original", + TwentyTwenty = "2020" +} - export interface JsxClosingTagInfo { - readonly newText: string; - } +// +// Public services of a language service instance associated +// with a language service host instance +// +export interface LanguageService { + /** This is used as a part of restarting the language service. */ + cleanupSemanticCache(): void; - export interface CombinedCodeFixScope { type: "file"; fileName: string; } + /** + * Gets errors indicating invalid syntax in a file. + * + * In English, "this cdeo have, erorrs" is syntactically invalid because it has typos, + * grammatical errors, and misplaced punctuation. Likewise, examples of syntax + * errors in TypeScript are missing parentheses in an `if` statement, mismatched + * curly braces, and using a reserved keyword as a variable name. + * + * These diagnostics are inexpensive to compute and don't require knowledge of + * other files. Note that a non-empty result increases the likelihood of false positives + * from `getSemanticDiagnostics`. + * + * While these represent the majority of syntax-related diagnostics, there are some + * that require the type system, which will be present in `getSemanticDiagnostics`. + * + * @param fileName A path to the file you want syntactic diagnostics for + */ + getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[]; - export interface OrganizeImportsArgs extends CombinedCodeFixScope { - skipDestructiveCodeActions?: boolean; - } + /** + * Gets warnings or errors indicating type system issues in a given file. + * Requesting semantic diagnostics may start up the type system and + * run deferred work, so the first call may take longer than subsequent calls. + * + * Unlike the other get*Diagnostics functions, these diagnostics can potentially not + * include a reference to a source file. Specifically, the first time this is called, + * it will return global diagnostics with no associated location. + * + * To contrast the differences between semantic and syntactic diagnostics, consider the + * sentence: "The sun is green." is syntactically correct; those are real English words with + * correct sentence structure. However, it is semantically invalid, because it is not true. + * + * @param fileName A path to the file you want semantic diagnostics for + */ + getSemanticDiagnostics(fileName: string): Diagnostic[]; - export type CompletionsTriggerCharacter = "." | '"' | "'" | "`" | "/" | "@" | "<" | "#" | " "; + /** + * Gets suggestion diagnostics for a specific file. These diagnostics tend to + * proactively suggest refactors, as opposed to diagnostics that indicate + * potentially incorrect runtime behavior. + * + * @param fileName A path to the file you want semantic diagnostics for + */ + getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[]; - export const enum CompletionTriggerKind { - /** Completion was triggered by typing an identifier, manual invocation (e.g Ctrl+Space) or via API. */ - Invoked = 1, + // TODO: Rename this to getProgramDiagnostics to better indicate that these are any + // diagnostics present for the program level, and not just 'options' diagnostics. - /** Completion was triggered by a trigger character. */ - TriggerCharacter = 2, + /** + * Gets global diagnostics related to the program configuration and compiler options. + */ + getCompilerOptionsDiagnostics(): Diagnostic[]; - /** Completion was re-triggered as the current completion list is incomplete. */ - TriggerForIncompleteCompletions = 3, - } + /** @deprecated Use getEncodedSyntacticClassifications instead. */ + getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; + getSyntacticClassifications(fileName: string, span: TextSpan, format: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[]; - export interface GetCompletionsAtPositionOptions extends UserPreferences { - /** - * If the editor is asking for completions because a certain character was typed - * (as opposed to when the user explicitly requested them) this should be set. - */ - triggerCharacter?: CompletionsTriggerCharacter; - triggerKind?: CompletionTriggerKind; - /** @deprecated Use includeCompletionsForModuleExports */ - includeExternalModuleExports?: boolean; - /** @deprecated Use includeCompletionsWithInsertText */ - includeInsertTextCompletions?: boolean; - } + /** @deprecated Use getEncodedSemanticClassifications instead. */ + getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; + getSemanticClassifications(fileName: string, span: TextSpan, format: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[]; - export interface InlayHintsOptions extends UserPreferences { - readonly includeInlayParameterNameHints?: "none" | "literals" | "all"; - readonly includeInlayParameterNameHintsWhenArgumentMatchesName?: boolean; - readonly includeInlayFunctionParameterTypeHints?: boolean, - readonly includeInlayVariableTypeHints?: boolean; - readonly includeInlayPropertyDeclarationTypeHints?: boolean; - readonly includeInlayFunctionLikeReturnTypeHints?: boolean; - readonly includeInlayEnumMemberValueHints?: boolean; - } + /** Encoded as triples of [start, length, ClassificationType]. */ + getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications; - export type SignatureHelpTriggerCharacter = "," | "(" | "<"; - export type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")"; + /** + * Gets semantic highlights information for a particular file. Has two formats, an older + * version used by VS and a format used by VS Code. + * + * @param fileName The path to the file + * @param position A text span to return results within + * @param format Which format to use, defaults to "original" + * @returns a number array encoded as triples of [start, length, ClassificationType, ...]. + */ + getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications; - export interface SignatureHelpItemsOptions { - triggerReason?: SignatureHelpTriggerReason; - } + /** + * Gets completion entries at a particular position in a file. + * + * @param fileName The path to the file + * @param position A zero-based index of the character where you want the entries + * @param options An object describing how the request was triggered and what kinds + * of code actions can be returned with the completions. + */ + getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined): WithMetadata | undefined; - export type SignatureHelpTriggerReason = - | SignatureHelpInvokedReason - | SignatureHelpCharacterTypedReason - | SignatureHelpRetriggeredReason; + /** + * Gets the extended details for a completion entry retrieved from `getCompletionsAtPosition`. + * + * @param fileName The path to the file + * @param position A zero based index of the character where you want the entries + * @param entryName The `name` from an existing completion which came from `getCompletionsAtPosition` + * @param formatOptions How should code samples in the completions be formatted, can be undefined for backwards compatibility + * @param source `source` property from the completion entry + * @param preferences User settings, can be undefined for backwards compatibility + * @param data `data` property from the completion entry + */ + getCompletionEntryDetails( + fileName: string, + position: number, + entryName: string, + formatOptions: FormatCodeOptions | FormatCodeSettings | undefined, + source: string | undefined, + preferences: UserPreferences | undefined, + data: CompletionEntryData | undefined, + ): CompletionEntryDetails | undefined; + + getCompletionEntrySymbol(fileName: string, position: number, name: string, source: string | undefined): Symbol | undefined; /** - * Signals that the user manually requested signature help. - * The language service will unconditionally attempt to provide a result. + * Gets semantic information about the identifier at a particular position in a + * file. Quick info is what you typically see when you hover in an editor. + * + * @param fileName The path to the file + * @param position A zero-based index of the character where you want the quick info */ - export interface SignatureHelpInvokedReason { - kind: "invoked"; - triggerCharacter?: undefined; - } + getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined; + + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan | undefined; + getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined; + + getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): SignatureHelpItems | undefined; + + getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): RenameInfo; + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): readonly RenameLocation[] | undefined; + + getSmartSelectionRange(fileName: string, position: number): SelectionRange; + + getDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined; + getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined; + getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined; + getImplementationAtPosition(fileName: string, position: number): readonly ImplementationLocation[] | undefined; + + getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined; + findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined; + getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] | undefined; + getFileReferences(fileName: string): ReferenceEntry[]; + + /** @deprecated */ + getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined; + + getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[]; + getNavigationBarItems(fileName: string): NavigationBarItem[]; + getNavigationTree(fileName: string): NavigationTree; + + prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined; + provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; + provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; + + provideInlayHints(fileName: string, span: TextSpan, preferences: UserPreferences | undefined): InlayHint[] + + getOutliningSpans(fileName: string): OutliningSpan[]; + getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; + getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; + getIndentationAtPosition(fileName: string, position: number, options: EditorOptions | EditorSettings): number; + + getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; + getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; + + getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined; + + isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; /** - * Signals that the signature help request came from a user typing a character. - * Depending on the character and the syntactic context, the request may or may not be served a result. + * This will return a defined result if the position is after the `>` of the opening tag, or somewhere in the text, of a JSXElement with no closing tag. + * Editors should call this after `>` is typed. */ - export interface SignatureHelpCharacterTypedReason { - kind: "characterTyped"; - /** - * Character that was responsible for triggering signature help. - */ - triggerCharacter: SignatureHelpTriggerCharacter; - } + getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined; + + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined; + + toLineColumnOffset?(fileName: string, position: number): LineAndCharacter; + /** @internal */ + getSourceMapper(): SourceMapper; + /** @internal */ + clearSourceMapperCache(): void; + + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: FormatCodeSettings, preferences: UserPreferences): readonly CodeFixAction[]; + getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences): CombinedCodeActions; + + applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise; + applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; + applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; + /** @deprecated `fileName` will be ignored */ + applyCodeActionCommand(fileName: string, action: CodeActionCommand): Promise; + /** @deprecated `fileName` will be ignored */ + applyCodeActionCommand(fileName: string, action: CodeActionCommand[]): Promise; + /** @deprecated `fileName` will be ignored */ + applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise; + + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[]; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; + organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; + getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; + + getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): EmitOutput; + + getProgram(): Program | undefined; + /* @internal */ getNonBoundSourceFile(fileName: string): SourceFile; + /* @internal */ getAutoImportProvider(): Program | undefined; + + toggleLineComment(fileName: string, textRange: TextRange): TextChange[]; + toggleMultilineComment(fileName: string, textRange: TextRange): TextChange[]; + commentSelection(fileName: string, textRange: TextRange): TextChange[]; + uncommentSelection(fileName: string, textRange: TextRange): TextChange[]; + + dispose(): void; +} + +export interface JsxClosingTagInfo { + readonly newText: string; +} + +export interface CombinedCodeFixScope { type: "file"; fileName: string; } + +export interface OrganizeImportsArgs extends CombinedCodeFixScope { + skipDestructiveCodeActions?: boolean; +} + +export type CompletionsTriggerCharacter = "." | '"' | "'" | "`" | "/" | "@" | "<" | "#" | " "; + +export const enum CompletionTriggerKind { + /** Completion was triggered by typing an identifier, manual invocation (e.g Ctrl+Space) or via API. */ + Invoked = 1, + + /** Completion was triggered by a trigger character. */ + TriggerCharacter = 2, + + /** Completion was re-triggered as the current completion list is incomplete. */ + TriggerForIncompleteCompletions = 3, +} + +export interface GetCompletionsAtPositionOptions extends UserPreferences { /** - * Signals that this signature help request came from typing a character or moving the cursor. - * This should only occur if a signature help session was already active and the editor needs to see if it should adjust. - * The language service will unconditionally attempt to provide a result. - * `triggerCharacter` can be `undefined` for a retrigger caused by a cursor move. + * If the editor is asking for completions because a certain character was typed + * (as opposed to when the user explicitly requested them) this should be set. */ - export interface SignatureHelpRetriggeredReason { - kind: "retrigger"; - /** - * Character that was responsible for triggering signature help. - */ - triggerCharacter?: SignatureHelpRetriggerCharacter; - } + triggerCharacter?: CompletionsTriggerCharacter; + triggerKind?: CompletionTriggerKind; + /** @deprecated Use includeCompletionsForModuleExports */ + includeExternalModuleExports?: boolean; + /** @deprecated Use includeCompletionsWithInsertText */ + includeInsertTextCompletions?: boolean; +} - export interface ApplyCodeActionCommandResult { - successMessage: string; - } +export interface InlayHintsOptions extends UserPreferences { + readonly includeInlayParameterNameHints?: "none" | "literals" | "all"; + readonly includeInlayParameterNameHintsWhenArgumentMatchesName?: boolean; + readonly includeInlayFunctionParameterTypeHints?: boolean, + readonly includeInlayVariableTypeHints?: boolean; + readonly includeInlayPropertyDeclarationTypeHints?: boolean; + readonly includeInlayFunctionLikeReturnTypeHints?: boolean; + readonly includeInlayEnumMemberValueHints?: boolean; +} - export interface Classifications { - spans: number[]; - endOfLineState: EndOfLineState; - } +export type SignatureHelpTriggerCharacter = "," | "(" | "<"; +export type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")"; - export interface ClassifiedSpan { - textSpan: TextSpan; - classificationType: ClassificationTypeNames; - } +export interface SignatureHelpItemsOptions { + triggerReason?: SignatureHelpTriggerReason; +} - export interface ClassifiedSpan2020 { - textSpan: TextSpan; - classificationType: number; - } +export type SignatureHelpTriggerReason = + | SignatureHelpInvokedReason + | SignatureHelpCharacterTypedReason + | SignatureHelpRetriggeredReason; + +/** + * Signals that the user manually requested signature help. + * The language service will unconditionally attempt to provide a result. + */ +export interface SignatureHelpInvokedReason { + kind: "invoked"; + triggerCharacter?: undefined; +} +/** + * Signals that the signature help request came from a user typing a character. + * Depending on the character and the syntactic context, the request may or may not be served a result. + */ +export interface SignatureHelpCharacterTypedReason { + kind: "characterTyped"; /** - * Navigation bar interface designed for visual studio's dual-column layout. - * This does not form a proper tree. - * The navbar is returned as a list of top-level items, each of which has a list of child items. - * Child items always have an empty array for their `childItems`. + * Character that was responsible for triggering signature help. */ - export interface NavigationBarItem { - text: string; - kind: ScriptElementKind; - kindModifiers: string; - spans: TextSpan[]; - childItems: NavigationBarItem[]; - indent: number; - bolded: boolean; - grayed: boolean; - } + triggerCharacter: SignatureHelpTriggerCharacter; +} +/** + * Signals that this signature help request came from typing a character or moving the cursor. + * This should only occur if a signature help session was already active and the editor needs to see if it should adjust. + * The language service will unconditionally attempt to provide a result. + * `triggerCharacter` can be `undefined` for a retrigger caused by a cursor move. + */ +export interface SignatureHelpRetriggeredReason { + kind: "retrigger"; /** - * Node in a tree of nested declarations in a file. - * The top node is always a script or module node. + * Character that was responsible for triggering signature help. */ - export interface NavigationTree { - /** Name of the declaration, or a short description, e.g. "". */ - text: string; - kind: ScriptElementKind; - /** ScriptElementKindModifier separated by commas, e.g. "public,abstract" */ - kindModifiers: string; - /** - * Spans of the nodes that generated this declaration. - * There will be more than one if this is the result of merging. - */ - spans: TextSpan[]; - nameSpan: TextSpan | undefined; - /** Present if non-empty */ - childItems?: NavigationTree[]; - } + triggerCharacter?: SignatureHelpRetriggerCharacter; +} - export interface CallHierarchyItem { - name: string; - kind: ScriptElementKind; - kindModifiers?: string; - file: string; - span: TextSpan; - selectionSpan: TextSpan; - containerName?: string; - } +export interface ApplyCodeActionCommandResult { + successMessage: string; +} - export interface CallHierarchyIncomingCall { - from: CallHierarchyItem; - fromSpans: TextSpan[]; - } +export interface Classifications { + spans: number[]; + endOfLineState: EndOfLineState; +} - export interface CallHierarchyOutgoingCall { - to: CallHierarchyItem; - fromSpans: TextSpan[]; - } +export interface ClassifiedSpan { + textSpan: TextSpan; + classificationType: ClassificationTypeNames; +} - export const enum InlayHintKind { - Type = "Type", - Parameter = "Parameter", - Enum = "Enum", - } +export interface ClassifiedSpan2020 { + textSpan: TextSpan; + classificationType: number; +} - export interface InlayHint { - text: string; - position: number; - kind: InlayHintKind; - whitespaceBefore?: boolean; - whitespaceAfter?: boolean; - } +/** + * Navigation bar interface designed for visual studio's dual-column layout. + * This does not form a proper tree. + * The navbar is returned as a list of top-level items, each of which has a list of child items. + * Child items always have an empty array for their `childItems`. + */ +export interface NavigationBarItem { + text: string; + kind: ScriptElementKind; + kindModifiers: string; + spans: TextSpan[]; + childItems: NavigationBarItem[]; + indent: number; + bolded: boolean; + grayed: boolean; +} - export interface TodoCommentDescriptor { - text: string; - priority: number; - } +/** + * Node in a tree of nested declarations in a file. + * The top node is always a script or module node. + */ +export interface NavigationTree { + /** Name of the declaration, or a short description, e.g. "". */ + text: string; + kind: ScriptElementKind; + /** ScriptElementKindModifier separated by commas, e.g. "public,abstract" */ + kindModifiers: string; + /** + * Spans of the nodes that generated this declaration. + * There will be more than one if this is the result of merging. + */ + spans: TextSpan[]; + nameSpan: TextSpan | undefined; + /** Present if non-empty */ + childItems?: NavigationTree[]; +} - export interface TodoComment { - descriptor: TodoCommentDescriptor; - message: string; - position: number; - } +export interface CallHierarchyItem { + name: string; + kind: ScriptElementKind; + kindModifiers?: string; + file: string; + span: TextSpan; + selectionSpan: TextSpan; + containerName?: string; +} - export interface TextChange { - span: TextSpan; - newText: string; - } +export interface CallHierarchyIncomingCall { + from: CallHierarchyItem; + fromSpans: TextSpan[]; +} - export interface FileTextChanges { - fileName: string; - textChanges: readonly TextChange[]; - isNewFile?: boolean; - } +export interface CallHierarchyOutgoingCall { + to: CallHierarchyItem; + fromSpans: TextSpan[]; +} - export interface CodeAction { - /** Description of the code action to display in the UI of the editor */ - description: string; - /** Text changes to apply to each file as part of the code action */ - changes: FileTextChanges[]; - /** - * If the user accepts the code fix, the editor should send the action back in a `applyAction` request. - * This allows the language service to have side effects (e.g. installing dependencies) upon a code fix. - */ - commands?: CodeActionCommand[]; - } +export const enum InlayHintKind { + Type = "Type", + Parameter = "Parameter", + Enum = "Enum", +} - export interface CodeFixAction extends CodeAction { - /** Short name to identify the fix, for use by telemetry. */ - fixName: string; - /** - * If present, one may call 'getCombinedCodeFix' with this fixId. - * This may be omitted to indicate that the code fix can't be applied in a group. - */ - fixId?: {}; - fixAllDescription?: string; - } +export interface InlayHint { + text: string; + position: number; + kind: InlayHintKind; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; +} - export interface CombinedCodeActions { - changes: readonly FileTextChanges[]; - commands?: readonly CodeActionCommand[]; - } +export interface TodoCommentDescriptor { + text: string; + priority: number; +} - // Publicly, this type is just `{}`. Internally it is a union of all the actions we use. - // See `commands?: {}[]` in protocol.ts - export type CodeActionCommand = InstallPackageAction; +export interface TodoComment { + descriptor: TodoCommentDescriptor; + message: string; + position: number; +} - export interface InstallPackageAction { - /* @internal */ readonly type: "install package"; - /* @internal */ readonly file: string; - /* @internal */ readonly packageName: string; - } +export interface TextChange { + span: TextSpan; + newText: string; +} + +export interface FileTextChanges { + fileName: string; + textChanges: readonly TextChange[]; + isNewFile?: boolean; +} +export interface CodeAction { + /** Description of the code action to display in the UI of the editor */ + description: string; + /** Text changes to apply to each file as part of the code action */ + changes: FileTextChanges[]; /** - * A set of one or more available refactoring actions, grouped under a parent refactoring. + * If the user accepts the code fix, the editor should send the action back in a `applyAction` request. + * This allows the language service to have side effects (e.g. installing dependencies) upon a code fix. */ - export interface ApplicableRefactorInfo { - /** - * The programmatic name of the refactoring - */ - name: string; - /** - * A description of this refactoring category to show to the user. - * If the refactoring gets inlined (see below), this text will not be visible. - */ - description: string; - /** - * Inlineable refactorings can have their actions hoisted out to the top level - * of a context menu. Non-inlineanable refactorings should always be shown inside - * their parent grouping. - * - * If not specified, this value is assumed to be 'true' - */ - inlineable?: boolean; - - actions: RefactorActionInfo[]; - } + commands?: CodeActionCommand[]; +} +export interface CodeFixAction extends CodeAction { + /** Short name to identify the fix, for use by telemetry. */ + fixName: string; /** - * Represents a single refactoring action - for example, the "Extract Method..." refactor might - * offer several actions, each corresponding to a surround class or closure to extract into. + * If present, one may call 'getCombinedCodeFix' with this fixId. + * This may be omitted to indicate that the code fix can't be applied in a group. */ - export interface RefactorActionInfo { - /** - * The programmatic name of the refactoring action - */ - name: string; - - /** - * A description of this refactoring action to show to the user. - * If the parent refactoring is inlined away, this will be the only text shown, - * so this description should make sense by itself if the parent is inlineable=true - */ - description: string; - - /** - * A message to show to the user if the refactoring cannot be applied in - * the current context. - */ - notApplicableReason?: string; - - /** - * The hierarchical dotted name of the refactor action. - */ - kind?: string; - } + fixId?: {}; + fixAllDescription?: string; +} + +export interface CombinedCodeActions { + changes: readonly FileTextChanges[]; + commands?: readonly CodeActionCommand[]; +} +// Publicly, this type is just `{}`. Internally it is a union of all the actions we use. +// See `commands?: {}[]` in protocol.ts +export type CodeActionCommand = InstallPackageAction; + +export interface InstallPackageAction { + /* @internal */ readonly type: "install package"; + /* @internal */ readonly file: string; + /* @internal */ readonly packageName: string; +} + +/** + * A set of one or more available refactoring actions, grouped under a parent refactoring. + */ +export interface ApplicableRefactorInfo { /** - * A set of edits to make in response to a refactor action, plus an optional - * location where renaming should be invoked from + * The programmatic name of the refactoring */ - export interface RefactorEditInfo { - edits: FileTextChanges[]; - renameFilename?: string ; - renameLocation?: number; - commands?: CodeActionCommand[]; - } + name: string; + /** + * A description of this refactoring category to show to the user. + * If the refactoring gets inlined (see below), this text will not be visible. + */ + description: string; + /** + * Inlineable refactorings can have their actions hoisted out to the top level + * of a context menu. Non-inlineanable refactorings should always be shown inside + * their parent grouping. + * + * If not specified, this value is assumed to be 'true' + */ + inlineable?: boolean; - export type RefactorTriggerReason = "implicit" | "invoked"; + actions: RefactorActionInfo[]; +} - export interface TextInsertion { - newText: string; - /** The position in newText the caret should point to after the insertion. */ - caretOffset: number; - } +/** + * Represents a single refactoring action - for example, the "Extract Method..." refactor might + * offer several actions, each corresponding to a surround class or closure to extract into. + */ +export interface RefactorActionInfo { + /** + * The programmatic name of the refactoring action + */ + name: string; - export interface DocumentSpan { - textSpan: TextSpan; - fileName: string; - - /** - * If the span represents a location that was remapped (e.g. via a .d.ts.map file), - * then the original filename and span will be specified here - */ - originalTextSpan?: TextSpan; - originalFileName?: string; - - /** - * If DocumentSpan.textSpan is the span for name of the declaration, - * then this is the span for relevant declaration - */ - contextSpan?: TextSpan; - originalContextSpan?: TextSpan; - } + /** + * A description of this refactoring action to show to the user. + * If the parent refactoring is inlined away, this will be the only text shown, + * so this description should make sense by itself if the parent is inlineable=true + */ + description: string; - export interface RenameLocation extends DocumentSpan { - readonly prefixText?: string; - readonly suffixText?: string; - } + /** + * A message to show to the user if the refactoring cannot be applied in + * the current context. + */ + notApplicableReason?: string; - export interface ReferenceEntry extends DocumentSpan { - isWriteAccess: boolean; - isDefinition: boolean; - isInString?: true; - } + /** + * The hierarchical dotted name of the refactor action. + */ + kind?: string; +} - export interface ImplementationLocation extends DocumentSpan { - kind: ScriptElementKind; - displayParts: SymbolDisplayPart[]; - } +/** + * A set of edits to make in response to a refactor action, plus an optional + * location where renaming should be invoked from + */ +export interface RefactorEditInfo { + edits: FileTextChanges[]; + renameFilename?: string ; + renameLocation?: number; + commands?: CodeActionCommand[]; +} - export const enum HighlightSpanKind { - none = "none", - definition = "definition", - reference = "reference", - writtenReference = "writtenReference", - } +export type RefactorTriggerReason = "implicit" | "invoked"; - export interface HighlightSpan { - fileName?: string; - isInString?: true; - textSpan: TextSpan; - contextSpan?: TextSpan; - kind: HighlightSpanKind; - } +export interface TextInsertion { + newText: string; + /** The position in newText the caret should point to after the insertion. */ + caretOffset: number; +} - export interface NavigateToItem { - name: string; - kind: ScriptElementKind; - kindModifiers: string; - matchKind: "exact" | "prefix" | "substring" | "camelCase"; - isCaseSensitive: boolean; - fileName: string; - textSpan: TextSpan; - containerName: string; - containerKind: ScriptElementKind; - } +export interface DocumentSpan { + textSpan: TextSpan; + fileName: string; - export enum IndentStyle { - None = 0, - Block = 1, - Smart = 2, - } + /** + * If the span represents a location that was remapped (e.g. via a .d.ts.map file), + * then the original filename and span will be specified here + */ + originalTextSpan?: TextSpan; + originalFileName?: string; - export enum SemicolonPreference { - Ignore = "ignore", - Insert = "insert", - Remove = "remove", - } + /** + * If DocumentSpan.textSpan is the span for name of the declaration, + * then this is the span for relevant declaration + */ + contextSpan?: TextSpan; + originalContextSpan?: TextSpan; +} - /* @deprecated - consider using EditorSettings instead */ - export interface EditorOptions { - BaseIndentSize?: number; - IndentSize: number; - TabSize: number; - NewLineCharacter: string; - ConvertTabsToSpaces: boolean; - IndentStyle: IndentStyle; - } +export interface RenameLocation extends DocumentSpan { + readonly prefixText?: string; + readonly suffixText?: string; +} - // TODO: GH#18217 These are frequently asserted as defined - export interface EditorSettings { - baseIndentSize?: number; - indentSize?: number; - tabSize?: number; - newLineCharacter?: string; - convertTabsToSpaces?: boolean; - indentStyle?: IndentStyle; - trimTrailingWhitespace?: boolean; - } +export interface ReferenceEntry extends DocumentSpan { + isWriteAccess: boolean; + isDefinition: boolean; + isInString?: true; +} - /* @deprecated - consider using FormatCodeSettings instead */ - export interface FormatCodeOptions extends EditorOptions { - InsertSpaceAfterCommaDelimiter: boolean; - InsertSpaceAfterSemicolonInForStatements: boolean; - InsertSpaceBeforeAndAfterBinaryOperators: boolean; - InsertSpaceAfterConstructor?: boolean; - InsertSpaceAfterKeywordsInControlFlowStatements: boolean; - InsertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean; - InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; - InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean; - InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; - InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean; - InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; - InsertSpaceAfterTypeAssertion?: boolean; - InsertSpaceBeforeFunctionParenthesis?: boolean; - PlaceOpenBraceOnNewLineForFunctions: boolean; - PlaceOpenBraceOnNewLineForControlBlocks: boolean; - insertSpaceBeforeTypeAnnotation?: boolean; - } +export interface ImplementationLocation extends DocumentSpan { + kind: ScriptElementKind; + displayParts: SymbolDisplayPart[]; +} - export interface FormatCodeSettings extends EditorSettings { - readonly insertSpaceAfterCommaDelimiter?: boolean; - readonly insertSpaceAfterSemicolonInForStatements?: boolean; - readonly insertSpaceBeforeAndAfterBinaryOperators?: boolean; - readonly insertSpaceAfterConstructor?: boolean; - readonly insertSpaceAfterKeywordsInControlFlowStatements?: boolean; - readonly insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean; - readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean; - readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean; - readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; - readonly insertSpaceAfterOpeningAndBeforeClosingEmptyBraces?: boolean; - readonly insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean; - readonly insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; - readonly insertSpaceAfterTypeAssertion?: boolean; - readonly insertSpaceBeforeFunctionParenthesis?: boolean; - readonly placeOpenBraceOnNewLineForFunctions?: boolean; - readonly placeOpenBraceOnNewLineForControlBlocks?: boolean; - readonly insertSpaceBeforeTypeAnnotation?: boolean; - readonly indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; - readonly semicolons?: SemicolonPreference; - } +export const enum HighlightSpanKind { + none = "none", + definition = "definition", + reference = "reference", + writtenReference = "writtenReference", +} - export function getDefaultFormatCodeSettings(newLineCharacter?: string): FormatCodeSettings { - return { - indentSize: 4, - tabSize: 4, - newLineCharacter: newLineCharacter || "\n", - convertTabsToSpaces: true, - indentStyle: IndentStyle.Smart, - insertSpaceAfterConstructor: false, - insertSpaceAfterCommaDelimiter: true, - insertSpaceAfterSemicolonInForStatements: true, - insertSpaceBeforeAndAfterBinaryOperators: true, - insertSpaceAfterKeywordsInControlFlowStatements: true, - insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, - insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, - insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, - insertSpaceBeforeFunctionParenthesis: false, - placeOpenBraceOnNewLineForFunctions: false, - placeOpenBraceOnNewLineForControlBlocks: false, - semicolons: SemicolonPreference.Ignore, - trimTrailingWhitespace: true - }; - } +export interface HighlightSpan { + fileName?: string; + isInString?: true; + textSpan: TextSpan; + contextSpan?: TextSpan; + kind: HighlightSpanKind; +} - /* @internal */ - export const testFormatSettings = getDefaultFormatCodeSettings("\n"); - - export interface DefinitionInfo extends DocumentSpan { - kind: ScriptElementKind; - name: string; - containerKind: ScriptElementKind; - containerName: string; - unverified?: boolean; - /* @internal */ isLocal?: boolean; - } +export interface NavigateToItem { + name: string; + kind: ScriptElementKind; + kindModifiers: string; + matchKind: "exact" | "prefix" | "substring" | "camelCase"; + isCaseSensitive: boolean; + fileName: string; + textSpan: TextSpan; + containerName: string; + containerKind: ScriptElementKind; +} - export interface DefinitionInfoAndBoundSpan { - definitions?: readonly DefinitionInfo[]; - textSpan: TextSpan; - } +export enum IndentStyle { + None = 0, + Block = 1, + Smart = 2, +} - export interface ReferencedSymbolDefinitionInfo extends DefinitionInfo { - displayParts: SymbolDisplayPart[]; - } +export enum SemicolonPreference { + Ignore = "ignore", + Insert = "insert", + Remove = "remove", +} - export interface ReferencedSymbol { - definition: ReferencedSymbolDefinitionInfo; - references: ReferenceEntry[]; - } +/* @deprecated - consider using EditorSettings instead */ +export interface EditorOptions { + BaseIndentSize?: number; + IndentSize: number; + TabSize: number; + NewLineCharacter: string; + ConvertTabsToSpaces: boolean; + IndentStyle: IndentStyle; +} - export enum SymbolDisplayPartKind { - aliasName, - className, - enumName, - fieldName, - interfaceName, - keyword, - lineBreak, - numericLiteral, - stringLiteral, - localName, - methodName, - moduleName, - operator, - parameterName, - propertyName, - punctuation, - space, - text, - typeParameterName, - enumMemberName, - functionName, - regularExpressionLiteral, - link, - linkName, - linkText, - } +// TODO: GH#18217 These are frequently asserted as defined +export interface EditorSettings { + baseIndentSize?: number; + indentSize?: number; + tabSize?: number; + newLineCharacter?: string; + convertTabsToSpaces?: boolean; + indentStyle?: IndentStyle; + trimTrailingWhitespace?: boolean; +} - export interface SymbolDisplayPart { - text: string; - kind: string; - } +/* @deprecated - consider using FormatCodeSettings instead */ +export interface FormatCodeOptions extends EditorOptions { + InsertSpaceAfterCommaDelimiter: boolean; + InsertSpaceAfterSemicolonInForStatements: boolean; + InsertSpaceBeforeAndAfterBinaryOperators: boolean; + InsertSpaceAfterConstructor?: boolean; + InsertSpaceAfterKeywordsInControlFlowStatements: boolean; + InsertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; + InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean; + InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; + InsertSpaceAfterTypeAssertion?: boolean; + InsertSpaceBeforeFunctionParenthesis?: boolean; + PlaceOpenBraceOnNewLineForFunctions: boolean; + PlaceOpenBraceOnNewLineForControlBlocks: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; +} - export interface JSDocLinkDisplayPart extends SymbolDisplayPart { - target: DocumentSpan; - } +export interface FormatCodeSettings extends EditorSettings { + readonly insertSpaceAfterCommaDelimiter?: boolean; + readonly insertSpaceAfterSemicolonInForStatements?: boolean; + readonly insertSpaceBeforeAndAfterBinaryOperators?: boolean; + readonly insertSpaceAfterConstructor?: boolean; + readonly insertSpaceAfterKeywordsInControlFlowStatements?: boolean; + readonly insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingEmptyBraces?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; + readonly insertSpaceAfterTypeAssertion?: boolean; + readonly insertSpaceBeforeFunctionParenthesis?: boolean; + readonly placeOpenBraceOnNewLineForFunctions?: boolean; + readonly placeOpenBraceOnNewLineForControlBlocks?: boolean; + readonly insertSpaceBeforeTypeAnnotation?: boolean; + readonly indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; + readonly semicolons?: SemicolonPreference; +} - export interface JSDocTagInfo { - name: string; - text?: SymbolDisplayPart[]; - } +export function getDefaultFormatCodeSettings(newLineCharacter?: string): FormatCodeSettings { + return { + indentSize: 4, + tabSize: 4, + newLineCharacter: newLineCharacter || "\n", + convertTabsToSpaces: true, + indentStyle: IndentStyle.Smart, + insertSpaceAfterConstructor: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, + insertSpaceBeforeFunctionParenthesis: false, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, + semicolons: SemicolonPreference.Ignore, + trimTrailingWhitespace: true + }; +} - export interface QuickInfo { - kind: ScriptElementKind; - kindModifiers: string; - textSpan: TextSpan; - displayParts?: SymbolDisplayPart[]; - documentation?: SymbolDisplayPart[]; - tags?: JSDocTagInfo[]; - } +/* @internal */ +export const testFormatSettings = getDefaultFormatCodeSettings("\n"); - export type RenameInfo = RenameInfoSuccess | RenameInfoFailure; - export interface RenameInfoSuccess { - canRename: true; - /** - * File or directory to rename. - * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. - */ - fileToRename?: string; - displayName: string; - fullDisplayName: string; - kind: ScriptElementKind; - kindModifiers: string; - triggerSpan: TextSpan; - } - export interface RenameInfoFailure { - canRename: false; - localizedErrorMessage: string; - } +export interface DefinitionInfo extends DocumentSpan { + kind: ScriptElementKind; + name: string; + containerKind: ScriptElementKind; + containerName: string; + unverified?: boolean; + /* @internal */ isLocal?: boolean; +} - export interface RenameInfoOptions { - readonly allowRenameOfImportPath?: boolean; - } +export interface DefinitionInfoAndBoundSpan { + definitions?: readonly DefinitionInfo[]; + textSpan: TextSpan; +} - export interface DocCommentTemplateOptions { - readonly generateReturnInDocTemplate?: boolean; - } +export interface ReferencedSymbolDefinitionInfo extends DefinitionInfo { + displayParts: SymbolDisplayPart[]; +} - export interface SignatureHelpParameter { - name: string; - documentation: SymbolDisplayPart[]; - displayParts: SymbolDisplayPart[]; - isOptional: boolean; - isRest?: boolean; - } +export interface ReferencedSymbol { + definition: ReferencedSymbolDefinitionInfo; + references: ReferenceEntry[]; +} - export interface SelectionRange { - textSpan: TextSpan; - parent?: SelectionRange; - } +export enum SymbolDisplayPartKind { + aliasName, + className, + enumName, + fieldName, + interfaceName, + keyword, + lineBreak, + numericLiteral, + stringLiteral, + localName, + methodName, + moduleName, + operator, + parameterName, + propertyName, + punctuation, + space, + text, + typeParameterName, + enumMemberName, + functionName, + regularExpressionLiteral, + link, + linkName, + linkText, +} + +export interface SymbolDisplayPart { + text: string; + kind: string; +} + +export interface JSDocLinkDisplayPart extends SymbolDisplayPart { + target: DocumentSpan; +} + +export interface JSDocTagInfo { + name: string; + text?: SymbolDisplayPart[]; +} +export interface QuickInfo { + kind: ScriptElementKind; + kindModifiers: string; + textSpan: TextSpan; + displayParts?: SymbolDisplayPart[]; + documentation?: SymbolDisplayPart[]; + tags?: JSDocTagInfo[]; +} + +export type RenameInfo = RenameInfoSuccess | RenameInfoFailure; +export interface RenameInfoSuccess { + canRename: true; /** - * Represents a single signature to show in signature help. - * The id is used for subsequent calls into the language service to ask questions about the - * signature help item in the context of any documents that have been updated. i.e. after - * an edit has happened, while signature help is still active, the host can ask important - * questions like 'what parameter is the user currently contained within?'. + * File or directory to rename. + * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. */ - export interface SignatureHelpItem { - isVariadic: boolean; - prefixDisplayParts: SymbolDisplayPart[]; - suffixDisplayParts: SymbolDisplayPart[]; - separatorDisplayParts: SymbolDisplayPart[]; - parameters: SignatureHelpParameter[]; - documentation: SymbolDisplayPart[]; - tags: JSDocTagInfo[]; - } + fileToRename?: string; + displayName: string; + fullDisplayName: string; + kind: ScriptElementKind; + kindModifiers: string; + triggerSpan: TextSpan; +} +export interface RenameInfoFailure { + canRename: false; + localizedErrorMessage: string; +} + +export interface RenameInfoOptions { + readonly allowRenameOfImportPath?: boolean; +} + +export interface DocCommentTemplateOptions { + readonly generateReturnInDocTemplate?: boolean; +} + +export interface SignatureHelpParameter { + name: string; + documentation: SymbolDisplayPart[]; + displayParts: SymbolDisplayPart[]; + isOptional: boolean; + isRest?: boolean; +} +export interface SelectionRange { + textSpan: TextSpan; + parent?: SelectionRange; +} + +/** + * Represents a single signature to show in signature help. + * The id is used for subsequent calls into the language service to ask questions about the + * signature help item in the context of any documents that have been updated. i.e. after + * an edit has happened, while signature help is still active, the host can ask important + * questions like 'what parameter is the user currently contained within?'. + */ +export interface SignatureHelpItem { + isVariadic: boolean; + prefixDisplayParts: SymbolDisplayPart[]; + suffixDisplayParts: SymbolDisplayPart[]; + separatorDisplayParts: SymbolDisplayPart[]; + parameters: SignatureHelpParameter[]; + documentation: SymbolDisplayPart[]; + tags: JSDocTagInfo[]; +} + +/** + * Represents a set of signature help items, and the preferred item that should be selected. + */ +export interface SignatureHelpItems { + items: SignatureHelpItem[]; + applicableSpan: TextSpan; + selectedItemIndex: number; + argumentIndex: number; + argumentCount: number; +} + +export interface CompletionInfo { + /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ + isGlobalCompletion: boolean; + isMemberCompletion: boolean; /** - * Represents a set of signature help items, and the preferred item that should be selected. + * In the absence of `CompletionEntry["replacementSpan"], the editor may choose whether to use + * this span or its default one. If `CompletionEntry["replacementSpan"]` is defined, that span + * must be used to commit that completion entry. */ - export interface SignatureHelpItems { - items: SignatureHelpItem[]; - applicableSpan: TextSpan; - selectedItemIndex: number; - argumentIndex: number; - argumentCount: number; - } + optionalReplacementSpan?: TextSpan; + /** + * true when the current location also allows for a new identifier + */ + isNewIdentifierLocation: boolean; + /** + * Indicates to client to continue requesting completions on subsequent keystrokes. + */ + isIncomplete?: true; + entries: CompletionEntry[]; +} - export interface CompletionInfo { - /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ - isGlobalCompletion: boolean; - isMemberCompletion: boolean; - /** - * In the absence of `CompletionEntry["replacementSpan"], the editor may choose whether to use - * this span or its default one. If `CompletionEntry["replacementSpan"]` is defined, that span - * must be used to commit that completion entry. - */ - optionalReplacementSpan?: TextSpan; - /** - * true when the current location also allows for a new identifier - */ - isNewIdentifierLocation: boolean; - /** - * Indicates to client to continue requesting completions on subsequent keystrokes. - */ - isIncomplete?: true; - entries: CompletionEntry[]; - } +export interface CompletionEntryDataAutoImport { + /** + * The name of the property or export in the module's symbol table. Differs from the completion name + * in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default. + */ + exportName: string; + moduleSpecifier?: string; + /** The file name declaring the export's module symbol, if it was an external module */ + fileName?: string; + /** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */ + ambientModuleName?: string; + /** True if the export was found in the package.json AutoImportProvider */ + isPackageJsonImport?: true; +} - export interface CompletionEntryDataAutoImport { - /** - * The name of the property or export in the module's symbol table. Differs from the completion name - * in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default. - */ - exportName: string; - moduleSpecifier?: string; - /** The file name declaring the export's module symbol, if it was an external module */ - fileName?: string; - /** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */ - ambientModuleName?: string; - /** True if the export was found in the package.json AutoImportProvider */ - isPackageJsonImport?: true; - } +export interface CompletionEntryDataUnresolved extends CompletionEntryDataAutoImport { + /** The key in the `ExportMapCache` where the completion entry's `SymbolExportInfo[]` is found */ + exportMapKey: string; +} - export interface CompletionEntryDataUnresolved extends CompletionEntryDataAutoImport { - /** The key in the `ExportMapCache` where the completion entry's `SymbolExportInfo[]` is found */ - exportMapKey: string; - } +export interface CompletionEntryDataResolved extends CompletionEntryDataAutoImport { + moduleSpecifier: string; +} - export interface CompletionEntryDataResolved extends CompletionEntryDataAutoImport { - moduleSpecifier: string; - } +export type CompletionEntryData = CompletionEntryDataUnresolved | CompletionEntryDataResolved; - export type CompletionEntryData = CompletionEntryDataUnresolved | CompletionEntryDataResolved; - - // see comments in protocol.ts - export interface CompletionEntry { - name: string; - kind: ScriptElementKind; - kindModifiers?: string; // see ScriptElementKindModifier, comma separated - sortText: string; - insertText?: string; - isSnippet?: true; - /** - * An optional span that indicates the text to be replaced by this completion item. - * If present, this span should be used instead of the default one. - * It will be set if the required span differs from the one generated by the default replacement behavior. - */ - replacementSpan?: TextSpan; - hasAction?: true; - source?: string; - sourceDisplay?: SymbolDisplayPart[]; - isRecommended?: true; - isFromUncheckedFile?: true; - isPackageJsonImport?: true; - isImportStatementCompletion?: true; - /** - * A property to be sent back to TS Server in the CompletionDetailsRequest, along with `name`, - * that allows TS Server to look up the symbol represented by the completion item, disambiguating - * items with the same name. Currently only defined for auto-import completions, but the type is - * `unknown` in the protocol, so it can be changed as needed to support other kinds of completions. - * The presence of this property should generally not be used to assume that this completion entry - * is an auto-import. - */ - data?: CompletionEntryData; - } +// see comments in protocol.ts +export interface CompletionEntry { + name: string; + kind: ScriptElementKind; + kindModifiers?: string; // see ScriptElementKindModifier, comma separated + sortText: string; + insertText?: string; + isSnippet?: true; + /** + * An optional span that indicates the text to be replaced by this completion item. + * If present, this span should be used instead of the default one. + * It will be set if the required span differs from the one generated by the default replacement behavior. + */ + replacementSpan?: TextSpan; + hasAction?: true; + source?: string; + sourceDisplay?: SymbolDisplayPart[]; + isRecommended?: true; + isFromUncheckedFile?: true; + isPackageJsonImport?: true; + isImportStatementCompletion?: true; + /** + * A property to be sent back to TS Server in the CompletionDetailsRequest, along with `name`, + * that allows TS Server to look up the symbol represented by the completion item, disambiguating + * items with the same name. Currently only defined for auto-import completions, but the type is + * `unknown` in the protocol, so it can be changed as needed to support other kinds of completions. + * The presence of this property should generally not be used to assume that this completion entry + * is an auto-import. + */ + data?: CompletionEntryData; +} - export interface CompletionEntryDetails { - name: string; - kind: ScriptElementKind; - kindModifiers: string; // see ScriptElementKindModifier, comma separated - displayParts: SymbolDisplayPart[]; - documentation?: SymbolDisplayPart[]; - tags?: JSDocTagInfo[]; - codeActions?: CodeAction[]; - /** @deprecated Use `sourceDisplay` instead. */ - source?: SymbolDisplayPart[]; - sourceDisplay?: SymbolDisplayPart[]; - } +export interface CompletionEntryDetails { + name: string; + kind: ScriptElementKind; + kindModifiers: string; // see ScriptElementKindModifier, comma separated + displayParts: SymbolDisplayPart[]; + documentation?: SymbolDisplayPart[]; + tags?: JSDocTagInfo[]; + codeActions?: CodeAction[]; + /** @deprecated Use `sourceDisplay` instead. */ + source?: SymbolDisplayPart[]; + sourceDisplay?: SymbolDisplayPart[]; +} - export interface OutliningSpan { - /** The span of the document to actually collapse. */ - textSpan: TextSpan; +export interface OutliningSpan { + /** The span of the document to actually collapse. */ + textSpan: TextSpan; - /** The span of the document to display when the user hovers over the collapsed span. */ - hintSpan: TextSpan; + /** The span of the document to display when the user hovers over the collapsed span. */ + hintSpan: TextSpan; - /** The text to display in the editor for the collapsed region. */ - bannerText: string; + /** The text to display in the editor for the collapsed region. */ + bannerText: string; - /** - * Whether or not this region should be automatically collapsed when - * the 'Collapse to Definitions' command is invoked. - */ - autoCollapse: boolean; + /** + * Whether or not this region should be automatically collapsed when + * the 'Collapse to Definitions' command is invoked. + */ + autoCollapse: boolean; - /** - * Classification of the contents of the span - */ - kind: OutliningSpanKind; - } + /** + * Classification of the contents of the span + */ + kind: OutliningSpanKind; +} - export const enum OutliningSpanKind { - /** Single or multi-line comments */ - Comment = "comment", +export const enum OutliningSpanKind { + /** Single or multi-line comments */ + Comment = "comment", - /** Sections marked by '// #region' and '// #endregion' comments */ - Region = "region", + /** Sections marked by '// #region' and '// #endregion' comments */ + Region = "region", - /** Declarations and expressions */ - Code = "code", + /** Declarations and expressions */ + Code = "code", - /** Contiguous blocks of import declarations */ - Imports = "imports" - } + /** Contiguous blocks of import declarations */ + Imports = "imports" +} - export const enum OutputFileType { - JavaScript, - SourceMap, - Declaration - } +export const enum OutputFileType { + JavaScript, + SourceMap, + Declaration +} - export const enum EndOfLineState { - None, - InMultiLineCommentTrivia, - InSingleQuoteStringLiteral, - InDoubleQuoteStringLiteral, - InTemplateHeadOrNoSubstitutionTemplate, - InTemplateMiddleOrTail, - InTemplateSubstitutionPosition, - } +export const enum EndOfLineState { + None, + InMultiLineCommentTrivia, + InSingleQuoteStringLiteral, + InDoubleQuoteStringLiteral, + InTemplateHeadOrNoSubstitutionTemplate, + InTemplateMiddleOrTail, + InTemplateSubstitutionPosition, +} - export enum TokenClass { - Punctuation, - Keyword, - Operator, - Comment, - Whitespace, - Identifier, - NumberLiteral, - BigIntLiteral, - StringLiteral, - RegExpLiteral, - } +export enum TokenClass { + Punctuation, + Keyword, + Operator, + Comment, + Whitespace, + Identifier, + NumberLiteral, + BigIntLiteral, + StringLiteral, + RegExpLiteral, +} - export interface ClassificationResult { - finalLexState: EndOfLineState; - entries: ClassificationInfo[]; - } +export interface ClassificationResult { + finalLexState: EndOfLineState; + entries: ClassificationInfo[]; +} - export interface ClassificationInfo { - length: number; - classification: TokenClass; - } +export interface ClassificationInfo { + length: number; + classification: TokenClass; +} - export interface Classifier { - /** - * Gives lexical classifications of tokens on a line without any syntactic context. - * For instance, a token consisting of the text 'string' can be either an identifier - * named 'string' or the keyword 'string', however, because this classifier is not aware, - * it relies on certain heuristics to give acceptable results. For classifications where - * speed trumps accuracy, this function is preferable; however, for true accuracy, the - * syntactic classifier is ideal. In fact, in certain editing scenarios, combining the - * lexical, syntactic, and semantic classifiers may issue the best user experience. - * - * @param text The text of a line to classify. - * @param lexState The state of the lexical classifier at the end of the previous line. - * @param syntacticClassifierAbsent Whether the client is *not* using a syntactic classifier. - * If there is no syntactic classifier (syntacticClassifierAbsent=true), - * certain heuristics may be used in its place; however, if there is a - * syntactic classifier (syntacticClassifierAbsent=false), certain - * classifications which may be incorrectly categorized will be given - * back as Identifiers in order to allow the syntactic classifier to - * subsume the classification. - * @deprecated Use getLexicalClassifications instead. - */ - getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): ClassificationResult; - getEncodedLexicalClassifications(text: string, endOfLineState: EndOfLineState, syntacticClassifierAbsent: boolean): Classifications; - } +export interface Classifier { + /** + * Gives lexical classifications of tokens on a line without any syntactic context. + * For instance, a token consisting of the text 'string' can be either an identifier + * named 'string' or the keyword 'string', however, because this classifier is not aware, + * it relies on certain heuristics to give acceptable results. For classifications where + * speed trumps accuracy, this function is preferable; however, for true accuracy, the + * syntactic classifier is ideal. In fact, in certain editing scenarios, combining the + * lexical, syntactic, and semantic classifiers may issue the best user experience. + * + * @param text The text of a line to classify. + * @param lexState The state of the lexical classifier at the end of the previous line. + * @param syntacticClassifierAbsent Whether the client is *not* using a syntactic classifier. + * If there is no syntactic classifier (syntacticClassifierAbsent=true), + * certain heuristics may be used in its place; however, if there is a + * syntactic classifier (syntacticClassifierAbsent=false), certain + * classifications which may be incorrectly categorized will be given + * back as Identifiers in order to allow the syntactic classifier to + * subsume the classification. + * @deprecated Use getLexicalClassifications instead. + */ + getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): ClassificationResult; + getEncodedLexicalClassifications(text: string, endOfLineState: EndOfLineState, syntacticClassifierAbsent: boolean): Classifications; +} - export const enum ScriptElementKind { - unknown = "", - warning = "warning", +export const enum ScriptElementKind { + unknown = "", + warning = "warning", - /** predefined type (void) or keyword (class) */ - keyword = "keyword", + /** predefined type (void) or keyword (class) */ + keyword = "keyword", - /** top level script node */ - scriptElement = "script", + /** top level script node */ + scriptElement = "script", - /** module foo {} */ - moduleElement = "module", + /** module foo {} */ + moduleElement = "module", - /** class X {} */ - classElement = "class", + /** class X {} */ + classElement = "class", - /** var x = class X {} */ - localClassElement = "local class", + /** var x = class X {} */ + localClassElement = "local class", - /** interface Y {} */ - interfaceElement = "interface", + /** interface Y {} */ + interfaceElement = "interface", - /** type T = ... */ - typeElement = "type", + /** type T = ... */ + typeElement = "type", - /** enum E */ - enumElement = "enum", - enumMemberElement = "enum member", + /** enum E */ + enumElement = "enum", + enumMemberElement = "enum member", - /** - * Inside module and script only - * const v = .. - */ - variableElement = "var", + /** + * Inside module and script only + * const v = .. + */ + variableElement = "var", - /** Inside function */ - localVariableElement = "local var", + /** Inside function */ + localVariableElement = "local var", - /** - * Inside module and script only - * function f() { } - */ - functionElement = "function", + /** + * Inside module and script only + * function f() { } + */ + functionElement = "function", - /** Inside function */ - localFunctionElement = "local function", + /** Inside function */ + localFunctionElement = "local function", - /** class X { [public|private]* foo() {} } */ - memberFunctionElement = "method", + /** class X { [public|private]* foo() {} } */ + memberFunctionElement = "method", - /** class X { [public|private]* [get|set] foo:number; } */ - memberGetAccessorElement = "getter", - memberSetAccessorElement = "setter", + /** class X { [public|private]* [get|set] foo:number; } */ + memberGetAccessorElement = "getter", + memberSetAccessorElement = "setter", - /** - * class X { [public|private]* foo:number; } - * interface Y { foo:number; } - */ - memberVariableElement = "property", + /** + * class X { [public|private]* foo:number; } + * interface Y { foo:number; } + */ + memberVariableElement = "property", - /** - * class X { constructor() { } } - * class X { static { } } - */ - constructorImplementationElement = "constructor", + /** + * class X { constructor() { } } + * class X { static { } } + */ + constructorImplementationElement = "constructor", - /** interface Y { ():number; } */ - callSignatureElement = "call", + /** interface Y { ():number; } */ + callSignatureElement = "call", - /** interface Y { []:number; } */ - indexSignatureElement = "index", + /** interface Y { []:number; } */ + indexSignatureElement = "index", - /** interface Y { new():Y; } */ - constructSignatureElement = "construct", + /** interface Y { new():Y; } */ + constructSignatureElement = "construct", - /** function foo(*Y*: string) */ - parameterElement = "parameter", + /** function foo(*Y*: string) */ + parameterElement = "parameter", - typeParameterElement = "type parameter", + typeParameterElement = "type parameter", - primitiveType = "primitive type", + primitiveType = "primitive type", - label = "label", + label = "label", - alias = "alias", + alias = "alias", - constElement = "const", + constElement = "const", - letElement = "let", + letElement = "let", - directory = "directory", + directory = "directory", - externalModuleName = "external module name", + externalModuleName = "external module name", - /** - * - */ - jsxAttribute = "JSX attribute", + /** + * + */ + jsxAttribute = "JSX attribute", - /** String literal */ - string = "string", + /** String literal */ + string = "string", - /** Jsdoc @link: in `{@link C link text}`, the before and after text "{@link " and "}" */ - link = "link", + /** Jsdoc @link: in `{@link C link text}`, the before and after text "{@link " and "}" */ + link = "link", - /** Jsdoc @link: in `{@link C link text}`, the entity name "C" */ - linkName = "link name", + /** Jsdoc @link: in `{@link C link text}`, the entity name "C" */ + linkName = "link name", - /** Jsdoc @link: in `{@link C link text}`, the link text "link text" */ - linkText = "link text", - } + /** Jsdoc @link: in `{@link C link text}`, the link text "link text" */ + linkText = "link text", +} - export const enum ScriptElementKindModifier { - none = "", - publicMemberModifier = "public", - privateMemberModifier = "private", - protectedMemberModifier = "protected", - exportedModifier = "export", - ambientModifier = "declare", - staticModifier = "static", - abstractModifier = "abstract", - optionalModifier = "optional", - - deprecatedModifier = "deprecated", - - dtsModifier = ".d.ts", - tsModifier = ".ts", - tsxModifier = ".tsx", - jsModifier = ".js", - jsxModifier = ".jsx", - jsonModifier = ".json", - dmtsModifier = ".d.mts", - mtsModifier = ".mts", - mjsModifier = ".mjs", - dctsModifier = ".d.cts", - ctsModifier = ".cts", - cjsModifier = ".cjs", - } +export const enum ScriptElementKindModifier { + none = "", + publicMemberModifier = "public", + privateMemberModifier = "private", + protectedMemberModifier = "protected", + exportedModifier = "export", + ambientModifier = "declare", + staticModifier = "static", + abstractModifier = "abstract", + optionalModifier = "optional", + + deprecatedModifier = "deprecated", + + dtsModifier = ".d.ts", + tsModifier = ".ts", + tsxModifier = ".tsx", + jsModifier = ".js", + jsxModifier = ".jsx", + jsonModifier = ".json", + dmtsModifier = ".d.mts", + mtsModifier = ".mts", + mjsModifier = ".mjs", + dctsModifier = ".d.cts", + ctsModifier = ".cts", + cjsModifier = ".cjs", +} - export const enum ClassificationTypeNames { - comment = "comment", - identifier = "identifier", - keyword = "keyword", - numericLiteral = "number", - bigintLiteral = "bigint", - operator = "operator", - stringLiteral = "string", - whiteSpace = "whitespace", - text = "text", - - punctuation = "punctuation", - - className = "class name", - enumName = "enum name", - interfaceName = "interface name", - moduleName = "module name", - typeParameterName = "type parameter name", - typeAliasName = "type alias name", - parameterName = "parameter name", - docCommentTagName = "doc comment tag name", - jsxOpenTagName = "jsx open tag name", - jsxCloseTagName = "jsx close tag name", - jsxSelfClosingTagName = "jsx self closing tag name", - jsxAttribute = "jsx attribute", - jsxText = "jsx text", - jsxAttributeStringLiteralValue = "jsx attribute string literal value", - } +export const enum ClassificationTypeNames { + comment = "comment", + identifier = "identifier", + keyword = "keyword", + numericLiteral = "number", + bigintLiteral = "bigint", + operator = "operator", + stringLiteral = "string", + whiteSpace = "whitespace", + text = "text", + + punctuation = "punctuation", + + className = "class name", + enumName = "enum name", + interfaceName = "interface name", + moduleName = "module name", + typeParameterName = "type parameter name", + typeAliasName = "type alias name", + parameterName = "parameter name", + docCommentTagName = "doc comment tag name", + jsxOpenTagName = "jsx open tag name", + jsxCloseTagName = "jsx close tag name", + jsxSelfClosingTagName = "jsx self closing tag name", + jsxAttribute = "jsx attribute", + jsxText = "jsx text", + jsxAttributeStringLiteralValue = "jsx attribute string literal value", +} - export const enum ClassificationType { - comment = 1, - identifier = 2, - keyword = 3, - numericLiteral = 4, - operator = 5, - stringLiteral = 6, - regularExpressionLiteral = 7, - whiteSpace = 8, - text = 9, - punctuation = 10, - className = 11, - enumName = 12, - interfaceName = 13, - moduleName = 14, - typeParameterName = 15, - typeAliasName = 16, - parameterName = 17, - docCommentTagName = 18, - jsxOpenTagName = 19, - jsxCloseTagName = 20, - jsxSelfClosingTagName = 21, - jsxAttribute = 22, - jsxText = 23, - jsxAttributeStringLiteralValue = 24, - bigintLiteral = 25, - } +export const enum ClassificationType { + comment = 1, + identifier = 2, + keyword = 3, + numericLiteral = 4, + operator = 5, + stringLiteral = 6, + regularExpressionLiteral = 7, + whiteSpace = 8, + text = 9, + punctuation = 10, + className = 11, + enumName = 12, + interfaceName = 13, + moduleName = 14, + typeParameterName = 15, + typeAliasName = 16, + parameterName = 17, + docCommentTagName = 18, + jsxOpenTagName = 19, + jsxCloseTagName = 20, + jsxSelfClosingTagName = 21, + jsxAttribute = 22, + jsxText = 23, + jsxAttributeStringLiteralValue = 24, + bigintLiteral = 25, +} - /** @internal */ - export interface CodeFixRegistration { - errorCodes: readonly number[]; - getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined; - fixIds?: readonly string[]; - getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions; - } +/** @internal */ +export interface CodeFixRegistration { + errorCodes: readonly number[]; + getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined; + fixIds?: readonly string[]; + getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions; +} - /** @internal */ - export interface CodeFixContextBase extends textChanges.TextChangesContext { - sourceFile: SourceFile; - program: Program; - cancellationToken: CancellationToken; - preferences: UserPreferences; - } +/** @internal */ +export interface CodeFixContextBase extends textChanges.TextChangesContext { + sourceFile: SourceFile; + program: Program; + cancellationToken: CancellationToken; + preferences: UserPreferences; +} - /** @internal */ - export interface CodeFixAllContext extends CodeFixContextBase { - fixId: {}; - } +/** @internal */ +export interface CodeFixAllContext extends CodeFixContextBase { + fixId: {}; +} - /** @internal */ - export interface CodeFixContext extends CodeFixContextBase { - errorCode: number; - span: TextSpan; - } +/** @internal */ +export interface CodeFixContext extends CodeFixContextBase { + errorCode: number; + span: TextSpan; +} - /** @internal */ - export interface Refactor { - /** List of action kinds a refactor can provide. - * Used to skip unnecessary calculation when specific refactors are requested. */ - kinds?: string[]; +/** @internal */ +export interface Refactor { + /** List of action kinds a refactor can provide. + * Used to skip unnecessary calculation when specific refactors are requested. */ + kinds?: string[]; - /** Compute the associated code actions */ - getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined; + /** Compute the associated code actions */ + getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined; - /** Compute (quickly) which actions are available here */ - getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[]; - } + /** Compute (quickly) which actions are available here */ + getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[]; +} - /** @internal */ - export interface RefactorContext extends textChanges.TextChangesContext { - file: SourceFile; - startPosition: number; - endPosition?: number; - program: Program; - cancellationToken?: CancellationToken; - preferences: UserPreferences; - triggerReason?: RefactorTriggerReason; - kind?: string; - } +/** @internal */ +export interface RefactorContext extends textChanges.TextChangesContext { + file: SourceFile; + startPosition: number; + endPosition?: number; + program: Program; + cancellationToken?: CancellationToken; + preferences: UserPreferences; + triggerReason?: RefactorTriggerReason; + kind?: string; +} - export interface InlayHintsContext { - file: SourceFile; - program: Program; - cancellationToken: CancellationToken; - host: LanguageServiceHost; - span: TextSpan; - preferences: InlayHintsOptions; - } +export interface InlayHintsContext { + file: SourceFile; + program: Program; + cancellationToken: CancellationToken; + host: LanguageServiceHost; + span: TextSpan; + preferences: InlayHintsOptions; +} } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 30552aa9b9d3b..387f4d489ea3f 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -10,3285 +10,3285 @@ declare var Promise: PromiseConstructor; // eslint-disable-line no-var /* @internal */ namespace ts { - // These utilities are common to multiple language service features. - //#region - export const scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); - - export const enum SemanticMeaning { - None = 0x0, - Value = 0x1, - Type = 0x2, - Namespace = 0x4, - All = Value | Type | Namespace - } - - export function getMeaningFromDeclaration(node: Node): SemanticMeaning { - switch (node.kind) { - case SyntaxKind.VariableDeclaration: - return isInJSFile(node) && getJSDocEnumTag(node) ? SemanticMeaning.All : SemanticMeaning.Value; - - case SyntaxKind.Parameter: - case SyntaxKind.BindingElement: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.CatchClause: - case SyntaxKind.JsxAttribute: - return SemanticMeaning.Value; +// These utilities are common to multiple language service features. +//#region +export const scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); + +export const enum SemanticMeaning { + None = 0x0, + Value = 0x1, + Type = 0x2, + Namespace = 0x4, + All = Value | Type | Namespace +} - case SyntaxKind.TypeParameter: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.TypeLiteral: - return SemanticMeaning.Type; +export function getMeaningFromDeclaration(node: Node): SemanticMeaning { + switch (node.kind) { + case SyntaxKind.VariableDeclaration: + return isInJSFile(node) && getJSDocEnumTag(node) ? SemanticMeaning.All : SemanticMeaning.Value; + + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.CatchClause: + case SyntaxKind.JsxAttribute: + return SemanticMeaning.Value; - case SyntaxKind.JSDocTypedefTag: - // If it has no name node, it shares the name with the value declaration below it. - return (node as JSDocTypedefTag).name === undefined ? SemanticMeaning.Value | SemanticMeaning.Type : SemanticMeaning.Type; + case SyntaxKind.TypeParameter: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.TypeLiteral: + return SemanticMeaning.Type; - case SyntaxKind.EnumMember: - case SyntaxKind.ClassDeclaration: - return SemanticMeaning.Value | SemanticMeaning.Type; + case SyntaxKind.JSDocTypedefTag: + // If it has no name node, it shares the name with the value declaration below it. + return (node as JSDocTypedefTag).name === undefined ? SemanticMeaning.Value | SemanticMeaning.Type : SemanticMeaning.Type; - case SyntaxKind.ModuleDeclaration: - if (isAmbientModule(node as ModuleDeclaration)) { - return SemanticMeaning.Namespace | SemanticMeaning.Value; - } - else if (getModuleInstanceState(node as ModuleDeclaration) === ModuleInstanceState.Instantiated) { - return SemanticMeaning.Namespace | SemanticMeaning.Value; - } - else { - return SemanticMeaning.Namespace; - } + case SyntaxKind.EnumMember: + case SyntaxKind.ClassDeclaration: + return SemanticMeaning.Value | SemanticMeaning.Type; - case SyntaxKind.EnumDeclaration: - case SyntaxKind.NamedImports: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportAssignment: - case SyntaxKind.ExportDeclaration: - return SemanticMeaning.All; - - // An external module can be a Value - case SyntaxKind.SourceFile: + case SyntaxKind.ModuleDeclaration: + if (isAmbientModule(node as ModuleDeclaration)) { return SemanticMeaning.Namespace | SemanticMeaning.Value; - } - - return SemanticMeaning.All; - } - - export function getMeaningFromLocation(node: Node): SemanticMeaning { - node = getAdjustedReferenceLocation(node); - const parent = node.parent; - if (node.kind === SyntaxKind.SourceFile) { - return SemanticMeaning.Value; - } - else if (isExportAssignment(parent) - || isExportSpecifier(parent) - || isExternalModuleReference(parent) - || isImportSpecifier(parent) - || isImportClause(parent) - || isImportEqualsDeclaration(parent) && node === parent.name) { - let decl: Node = parent; - while (decl) { - if (isImportEqualsDeclaration(decl) || isImportClause(decl) || isExportDeclaration(decl)) { - return decl.isTypeOnly ? SemanticMeaning.Type : SemanticMeaning.All; - } - decl = decl.parent; } + else if (getModuleInstanceState(node as ModuleDeclaration) === ModuleInstanceState.Instantiated) { + return SemanticMeaning.Namespace | SemanticMeaning.Value; + } + else { + return SemanticMeaning.Namespace; + } + + case SyntaxKind.EnumDeclaration: + case SyntaxKind.NamedImports: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportAssignment: + case SyntaxKind.ExportDeclaration: return SemanticMeaning.All; - } - else if (isInRightSideOfInternalImportEqualsDeclaration(node)) { - return getMeaningFromRightHandSideOfImportEquals(node as Identifier); - } - else if (isDeclarationName(node)) { - return getMeaningFromDeclaration(parent); - } - else if (isEntityName(node) && findAncestor(node, or(isJSDocNameReference, isJSDocLinkLike, isJSDocMemberName))) { - return SemanticMeaning.All; - } - else if (isTypeReference(node)) { - return SemanticMeaning.Type; - } - else if (isNamespaceReference(node)) { - return SemanticMeaning.Namespace; - } - else if (isTypeParameterDeclaration(parent)) { - Debug.assert(isJSDocTemplateTag(parent.parent)); // Else would be handled by isDeclarationName - return SemanticMeaning.Type; - } - else if (isLiteralTypeNode(parent)) { - // This might be T["name"], which is actually referencing a property and not a type. So allow both meanings. - return SemanticMeaning.Type | SemanticMeaning.Value; - } - else { - return SemanticMeaning.Value; - } - } - function getMeaningFromRightHandSideOfImportEquals(node: Node): SemanticMeaning { - // import a = |b|; // Namespace - // import a = |b.c|; // Value, type, namespace - // import a = |b.c|.d; // Namespace - const name = node.kind === SyntaxKind.QualifiedName ? node : isQualifiedName(node.parent) && node.parent.right === node ? node.parent : undefined; - return name && name.parent.kind === SyntaxKind.ImportEqualsDeclaration ? SemanticMeaning.All : SemanticMeaning.Namespace; + // An external module can be a Value + case SyntaxKind.SourceFile: + return SemanticMeaning.Namespace | SemanticMeaning.Value; } - export function isInRightSideOfInternalImportEqualsDeclaration(node: Node) { - while (node.parent.kind === SyntaxKind.QualifiedName) { - node = node.parent; + return SemanticMeaning.All; +} + +export function getMeaningFromLocation(node: Node): SemanticMeaning { + node = getAdjustedReferenceLocation(node); + const parent = node.parent; + if (node.kind === SyntaxKind.SourceFile) { + return SemanticMeaning.Value; + } + else if (isExportAssignment(parent) + || isExportSpecifier(parent) + || isExternalModuleReference(parent) + || isImportSpecifier(parent) + || isImportClause(parent) + || isImportEqualsDeclaration(parent) && node === parent.name) { + let decl: Node = parent; + while (decl) { + if (isImportEqualsDeclaration(decl) || isImportClause(decl) || isExportDeclaration(decl)) { + return decl.isTypeOnly ? SemanticMeaning.Type : SemanticMeaning.All; + } + decl = decl.parent; } - return isInternalModuleImportEqualsDeclaration(node.parent) && node.parent.moduleReference === node; + return SemanticMeaning.All; } - - function isNamespaceReference(node: Node): boolean { - return isQualifiedNameNamespaceReference(node) || isPropertyAccessNamespaceReference(node); + else if (isInRightSideOfInternalImportEqualsDeclaration(node)) { + return getMeaningFromRightHandSideOfImportEquals(node as Identifier); } + else if (isDeclarationName(node)) { + return getMeaningFromDeclaration(parent); + } + else if (isEntityName(node) && findAncestor(node, or(isJSDocNameReference, isJSDocLinkLike, isJSDocMemberName))) { + return SemanticMeaning.All; + } + else if (isTypeReference(node)) { + return SemanticMeaning.Type; + } + else if (isNamespaceReference(node)) { + return SemanticMeaning.Namespace; + } + else if (isTypeParameterDeclaration(parent)) { + Debug.assert(isJSDocTemplateTag(parent.parent)); // Else would be handled by isDeclarationName + return SemanticMeaning.Type; + } + else if (isLiteralTypeNode(parent)) { + // This might be T["name"], which is actually referencing a property and not a type. So allow both meanings. + return SemanticMeaning.Type | SemanticMeaning.Value; + } + else { + return SemanticMeaning.Value; + } +} - function isQualifiedNameNamespaceReference(node: Node): boolean { - let root = node; - let isLastClause = true; - if (root.parent.kind === SyntaxKind.QualifiedName) { - while (root.parent && root.parent.kind === SyntaxKind.QualifiedName) { - root = root.parent; - } - - isLastClause = (root as QualifiedName).right === node; - } +function getMeaningFromRightHandSideOfImportEquals(node: Node): SemanticMeaning { + // import a = |b|; // Namespace + // import a = |b.c|; // Value, type, namespace + // import a = |b.c|.d; // Namespace + const name = node.kind === SyntaxKind.QualifiedName ? node : isQualifiedName(node.parent) && node.parent.right === node ? node.parent : undefined; + return name && name.parent.kind === SyntaxKind.ImportEqualsDeclaration ? SemanticMeaning.All : SemanticMeaning.Namespace; +} - return root.parent.kind === SyntaxKind.TypeReference && !isLastClause; +export function isInRightSideOfInternalImportEqualsDeclaration(node: Node) { + while (node.parent.kind === SyntaxKind.QualifiedName) { + node = node.parent; } + return isInternalModuleImportEqualsDeclaration(node.parent) && node.parent.moduleReference === node; +} - function isPropertyAccessNamespaceReference(node: Node): boolean { - let root = node; - let isLastClause = true; - if (root.parent.kind === SyntaxKind.PropertyAccessExpression) { - while (root.parent && root.parent.kind === SyntaxKind.PropertyAccessExpression) { - root = root.parent; - } - - isLastClause = (root as PropertyAccessExpression).name === node; - } +function isNamespaceReference(node: Node): boolean { + return isQualifiedNameNamespaceReference(node) || isPropertyAccessNamespaceReference(node); +} - if (!isLastClause && root.parent.kind === SyntaxKind.ExpressionWithTypeArguments && root.parent.parent.kind === SyntaxKind.HeritageClause) { - const decl = root.parent.parent.parent; - return (decl.kind === SyntaxKind.ClassDeclaration && (root.parent.parent as HeritageClause).token === SyntaxKind.ImplementsKeyword) || - (decl.kind === SyntaxKind.InterfaceDeclaration && (root.parent.parent as HeritageClause).token === SyntaxKind.ExtendsKeyword); +function isQualifiedNameNamespaceReference(node: Node): boolean { + let root = node; + let isLastClause = true; + if (root.parent.kind === SyntaxKind.QualifiedName) { + while (root.parent && root.parent.kind === SyntaxKind.QualifiedName) { + root = root.parent; } - return false; + isLastClause = (root as QualifiedName).right === node; } - function isTypeReference(node: Node): boolean { - if (isRightSideOfQualifiedNameOrPropertyAccess(node)) { - node = node.parent; - } - - switch (node.kind) { - case SyntaxKind.ThisKeyword: - return !isExpressionNode(node); - case SyntaxKind.ThisType: - return true; - } + return root.parent.kind === SyntaxKind.TypeReference && !isLastClause; +} - switch (node.parent.kind) { - case SyntaxKind.TypeReference: - return true; - case SyntaxKind.ImportType: - return !(node.parent as ImportTypeNode).isTypeOf; - case SyntaxKind.ExpressionWithTypeArguments: - return !isExpressionWithTypeArgumentsInClassExtendsClause(node.parent as ExpressionWithTypeArguments); +function isPropertyAccessNamespaceReference(node: Node): boolean { + let root = node; + let isLastClause = true; + if (root.parent.kind === SyntaxKind.PropertyAccessExpression) { + while (root.parent && root.parent.kind === SyntaxKind.PropertyAccessExpression) { + root = root.parent; } - return false; + isLastClause = (root as PropertyAccessExpression).name === node; } - export function isCallExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { - return isCalleeWorker(node, isCallExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); + if (!isLastClause && root.parent.kind === SyntaxKind.ExpressionWithTypeArguments && root.parent.parent.kind === SyntaxKind.HeritageClause) { + const decl = root.parent.parent.parent; + return (decl.kind === SyntaxKind.ClassDeclaration && (root.parent.parent as HeritageClause).token === SyntaxKind.ImplementsKeyword) || + (decl.kind === SyntaxKind.InterfaceDeclaration && (root.parent.parent as HeritageClause).token === SyntaxKind.ExtendsKeyword); } - export function isNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { - return isCalleeWorker(node, isNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); - } + return false; +} - export function isCallOrNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { - return isCalleeWorker(node, isCallOrNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); +function isTypeReference(node: Node): boolean { + if (isRightSideOfQualifiedNameOrPropertyAccess(node)) { + node = node.parent; } - export function isTaggedTemplateTag(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { - return isCalleeWorker(node, isTaggedTemplateExpression, selectTagOfTaggedTemplateExpression, includeElementAccess, skipPastOuterExpressions); + switch (node.kind) { + case SyntaxKind.ThisKeyword: + return !isExpressionNode(node); + case SyntaxKind.ThisType: + return true; } - export function isDecoratorTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { - return isCalleeWorker(node, isDecorator, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); + switch (node.parent.kind) { + case SyntaxKind.TypeReference: + return true; + case SyntaxKind.ImportType: + return !(node.parent as ImportTypeNode).isTypeOf; + case SyntaxKind.ExpressionWithTypeArguments: + return !isExpressionWithTypeArgumentsInClassExtendsClause(node.parent as ExpressionWithTypeArguments); } - export function isJsxOpeningLikeElementTagName(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { - return isCalleeWorker(node, isJsxOpeningLikeElement, selectTagNameOfJsxOpeningLikeElement, includeElementAccess, skipPastOuterExpressions); - } + return false; +} - function selectExpressionOfCallOrNewExpressionOrDecorator(node: CallExpression | NewExpression | Decorator) { - return node.expression; - } +export function isCallExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isCallExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); +} - function selectTagOfTaggedTemplateExpression(node: TaggedTemplateExpression) { - return node.tag; - } +export function isNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); +} + +export function isCallOrNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isCallOrNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); +} + +export function isTaggedTemplateTag(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isTaggedTemplateExpression, selectTagOfTaggedTemplateExpression, includeElementAccess, skipPastOuterExpressions); +} - function selectTagNameOfJsxOpeningLikeElement(node: JsxOpeningLikeElement) { - return node.tagName; +export function isDecoratorTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isDecorator, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); +} + +export function isJsxOpeningLikeElementTagName(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isJsxOpeningLikeElement, selectTagNameOfJsxOpeningLikeElement, includeElementAccess, skipPastOuterExpressions); +} + +function selectExpressionOfCallOrNewExpressionOrDecorator(node: CallExpression | NewExpression | Decorator) { + return node.expression; +} + +function selectTagOfTaggedTemplateExpression(node: TaggedTemplateExpression) { + return node.tag; +} + +function selectTagNameOfJsxOpeningLikeElement(node: JsxOpeningLikeElement) { + return node.tagName; +} + +function isCalleeWorker(node: Node, pred: (node: Node) => node is T, calleeSelector: (node: T) => Expression, includeElementAccess: boolean, skipPastOuterExpressions: boolean) { + let target = includeElementAccess ? climbPastPropertyOrElementAccess(node) : climbPastPropertyAccess(node); + if (skipPastOuterExpressions) { + target = skipOuterExpressions(target); } + return !!target && !!target.parent && pred(target.parent) && calleeSelector(target.parent) === target; +} + +export function climbPastPropertyAccess(node: Node) { + return isRightSideOfPropertyAccess(node) ? node.parent : node; +} + +export function climbPastPropertyOrElementAccess(node: Node) { + return isRightSideOfPropertyAccess(node) || isArgumentExpressionOfElementAccess(node) ? node.parent : node; +} - function isCalleeWorker(node: Node, pred: (node: Node) => node is T, calleeSelector: (node: T) => Expression, includeElementAccess: boolean, skipPastOuterExpressions: boolean) { - let target = includeElementAccess ? climbPastPropertyOrElementAccess(node) : climbPastPropertyAccess(node); - if (skipPastOuterExpressions) { - target = skipOuterExpressions(target); +export function getTargetLabel(referenceNode: Node, labelName: string): Identifier | undefined { + while (referenceNode) { + if (referenceNode.kind === SyntaxKind.LabeledStatement && (referenceNode as LabeledStatement).label.escapedText === labelName) { + return (referenceNode as LabeledStatement).label; } - return !!target && !!target.parent && pred(target.parent) && calleeSelector(target.parent) === target; + referenceNode = referenceNode.parent; } + return undefined; +} - export function climbPastPropertyAccess(node: Node) { - return isRightSideOfPropertyAccess(node) ? node.parent : node; +export function hasPropertyAccessExpressionWithName(node: CallExpression, funcName: string): boolean { + if (!isPropertyAccessExpression(node.expression)) { + return false; } - export function climbPastPropertyOrElementAccess(node: Node) { - return isRightSideOfPropertyAccess(node) || isArgumentExpressionOfElementAccess(node) ? node.parent : node; - } + return node.expression.name.text === funcName; +} - export function getTargetLabel(referenceNode: Node, labelName: string): Identifier | undefined { - while (referenceNode) { - if (referenceNode.kind === SyntaxKind.LabeledStatement && (referenceNode as LabeledStatement).label.escapedText === labelName) { - return (referenceNode as LabeledStatement).label; - } - referenceNode = referenceNode.parent; - } - return undefined; - } +export function isJumpStatementTarget(node: Node): node is Identifier & { parent: BreakOrContinueStatement } { + return isIdentifier(node) && tryCast(node.parent, isBreakOrContinueStatement)?.label === node; +} - export function hasPropertyAccessExpressionWithName(node: CallExpression, funcName: string): boolean { - if (!isPropertyAccessExpression(node.expression)) { - return false; - } +export function isLabelOfLabeledStatement(node: Node): node is Identifier { + return isIdentifier(node) && tryCast(node.parent, isLabeledStatement)?.label === node; +} - return node.expression.name.text === funcName; - } +export function isLabelName(node: Node): boolean { + return isLabelOfLabeledStatement(node) || isJumpStatementTarget(node); +} - export function isJumpStatementTarget(node: Node): node is Identifier & { parent: BreakOrContinueStatement } { - return isIdentifier(node) && tryCast(node.parent, isBreakOrContinueStatement)?.label === node; - } +export function isTagName(node: Node): boolean { + return tryCast(node.parent, isJSDocTag)?.tagName === node; +} - export function isLabelOfLabeledStatement(node: Node): node is Identifier { - return isIdentifier(node) && tryCast(node.parent, isLabeledStatement)?.label === node; - } +export function isRightSideOfQualifiedName(node: Node) { + return tryCast(node.parent, isQualifiedName)?.right === node; +} - export function isLabelName(node: Node): boolean { - return isLabelOfLabeledStatement(node) || isJumpStatementTarget(node); - } +export function isRightSideOfPropertyAccess(node: Node) { + return tryCast(node.parent, isPropertyAccessExpression)?.name === node; +} - export function isTagName(node: Node): boolean { - return tryCast(node.parent, isJSDocTag)?.tagName === node; - } +export function isArgumentExpressionOfElementAccess(node: Node) { + return tryCast(node.parent, isElementAccessExpression)?.argumentExpression === node; +} - export function isRightSideOfQualifiedName(node: Node) { - return tryCast(node.parent, isQualifiedName)?.right === node; - } +export function isNameOfModuleDeclaration(node: Node) { + return tryCast(node.parent, isModuleDeclaration)?.name === node; +} - export function isRightSideOfPropertyAccess(node: Node) { - return tryCast(node.parent, isPropertyAccessExpression)?.name === node; - } +export function isNameOfFunctionDeclaration(node: Node): boolean { + return isIdentifier(node) && tryCast(node.parent, isFunctionLike)?.name === node; +} - export function isArgumentExpressionOfElementAccess(node: Node) { - return tryCast(node.parent, isElementAccessExpression)?.argumentExpression === node; +export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: StringLiteral | NumericLiteral | NoSubstitutionTemplateLiteral): boolean { + switch (node.parent.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.EnumMember: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.ModuleDeclaration: + return getNameOfDeclaration(node.parent as Declaration) === node; + case SyntaxKind.ElementAccessExpression: + return (node.parent as ElementAccessExpression).argumentExpression === node; + case SyntaxKind.ComputedPropertyName: + return true; + case SyntaxKind.LiteralType: + return node.parent.parent.kind === SyntaxKind.IndexedAccessType; + default: + return false; } +} - export function isNameOfModuleDeclaration(node: Node) { - return tryCast(node.parent, isModuleDeclaration)?.name === node; - } +export function isExpressionOfExternalModuleImportEqualsDeclaration(node: Node) { + return isExternalModuleImportEqualsDeclaration(node.parent.parent) && + getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node; +} - export function isNameOfFunctionDeclaration(node: Node): boolean { - return isIdentifier(node) && tryCast(node.parent, isFunctionLike)?.name === node; +export function getContainerNode(node: Node): Declaration | undefined { + if (isJSDocTypeAlias(node)) { + // This doesn't just apply to the node immediately under the comment, but to everything in its parent's scope. + // node.parent = the JSDoc comment, node.parent.parent = the node having the comment. + // Then we get parent again in the loop. + node = node.parent.parent; } - export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: StringLiteral | NumericLiteral | NoSubstitutionTemplateLiteral): boolean { - switch (node.parent.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.EnumMember: + while (true) { + node = node.parent; + if (!node) { + return undefined; + } + switch (node.kind) { + case SyntaxKind.SourceFile: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: case SyntaxKind.ModuleDeclaration: - return getNameOfDeclaration(node.parent as Declaration) === node; - case SyntaxKind.ElementAccessExpression: - return (node.parent as ElementAccessExpression).argumentExpression === node; - case SyntaxKind.ComputedPropertyName: - return true; - case SyntaxKind.LiteralType: - return node.parent.parent.kind === SyntaxKind.IndexedAccessType; - default: - return false; + return node as Declaration; } } +} - export function isExpressionOfExternalModuleImportEqualsDeclaration(node: Node) { - return isExternalModuleImportEqualsDeclaration(node.parent.parent) && - getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node; +export function getNodeKind(node: Node): ScriptElementKind { + switch (node.kind) { + case SyntaxKind.SourceFile: + return isExternalModule(node as SourceFile) ? ScriptElementKind.moduleElement : ScriptElementKind.scriptElement; + case SyntaxKind.ModuleDeclaration: + return ScriptElementKind.moduleElement; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return ScriptElementKind.classElement; + case SyntaxKind.InterfaceDeclaration: return ScriptElementKind.interfaceElement; + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: + return ScriptElementKind.typeElement; + case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement; + case SyntaxKind.VariableDeclaration: + return getKindOfVariableDeclaration(node as VariableDeclaration); + case SyntaxKind.BindingElement: + return getKindOfVariableDeclaration(getRootDeclaration(node) as VariableDeclaration); + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + return ScriptElementKind.functionElement; + case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement; + case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + return ScriptElementKind.memberFunctionElement; + case SyntaxKind.PropertyAssignment: + const { initializer } = node as PropertyAssignment; + return isFunctionLike(initializer) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.SpreadAssignment: + return ScriptElementKind.memberVariableElement; + case SyntaxKind.IndexSignature: return ScriptElementKind.indexSignatureElement; + case SyntaxKind.ConstructSignature: return ScriptElementKind.constructSignatureElement; + case SyntaxKind.CallSignature: return ScriptElementKind.callSignatureElement; + case SyntaxKind.Constructor: + case SyntaxKind.ClassStaticBlockDeclaration: + return ScriptElementKind.constructorImplementationElement; + case SyntaxKind.TypeParameter: return ScriptElementKind.typeParameterElement; + case SyntaxKind.EnumMember: return ScriptElementKind.enumMemberElement; + case SyntaxKind.Parameter: return hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier) ? ScriptElementKind.memberVariableElement : ScriptElementKind.parameterElement; + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + case SyntaxKind.NamespaceImport: + case SyntaxKind.NamespaceExport: + return ScriptElementKind.alias; + case SyntaxKind.BinaryExpression: + const kind = getAssignmentDeclarationKind(node as BinaryExpression); + const { right } = node as BinaryExpression; + switch (kind) { + case AssignmentDeclarationKind.ObjectDefinePropertyValue: + case AssignmentDeclarationKind.ObjectDefinePropertyExports: + case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: + case AssignmentDeclarationKind.None: + return ScriptElementKind.unknown; + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.ModuleExports: + const rightKind = getNodeKind(right); + return rightKind === ScriptElementKind.unknown ? ScriptElementKind.constElement : rightKind; + case AssignmentDeclarationKind.PrototypeProperty: + return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; + case AssignmentDeclarationKind.ThisProperty: + return ScriptElementKind.memberVariableElement; // property + case AssignmentDeclarationKind.Property: + // static method / property + return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; + case AssignmentDeclarationKind.Prototype: + return ScriptElementKind.localClassElement; + default: { + assertType(kind); + return ScriptElementKind.unknown; + } + } + case SyntaxKind.Identifier: + return isImportClause(node.parent) ? ScriptElementKind.alias : ScriptElementKind.unknown; + case SyntaxKind.ExportAssignment: + const scriptKind = getNodeKind((node as ExportAssignment).expression); + // If the expression didn't come back with something (like it does for an identifiers) + return scriptKind === ScriptElementKind.unknown ? ScriptElementKind.constElement : scriptKind; + default: + return ScriptElementKind.unknown; } - export function getContainerNode(node: Node): Declaration | undefined { - if (isJSDocTypeAlias(node)) { - // This doesn't just apply to the node immediately under the comment, but to everything in its parent's scope. - // node.parent = the JSDoc comment, node.parent.parent = the node having the comment. - // Then we get parent again in the loop. - node = node.parent.parent; - } + function getKindOfVariableDeclaration(v: VariableDeclaration): ScriptElementKind { + return isVarConst(v) + ? ScriptElementKind.constElement + : isLet(v) + ? ScriptElementKind.letElement + : ScriptElementKind.variableElement; + } +} - while (true) { - node = node.parent; - if (!node) { - return undefined; - } - switch (node.kind) { - case SyntaxKind.SourceFile: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ModuleDeclaration: - return node as Declaration; - } - } +export function isThis(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ThisKeyword: + // case SyntaxKind.ThisType: TODO: GH#9267 + return true; + case SyntaxKind.Identifier: + // 'this' as a parameter + return identifierIsThisKeyword(node as Identifier) && node.parent.kind === SyntaxKind.Parameter; + default: + return false; } +} - export function getNodeKind(node: Node): ScriptElementKind { - switch (node.kind) { - case SyntaxKind.SourceFile: - return isExternalModule(node as SourceFile) ? ScriptElementKind.moduleElement : ScriptElementKind.scriptElement; - case SyntaxKind.ModuleDeclaration: - return ScriptElementKind.moduleElement; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return ScriptElementKind.classElement; - case SyntaxKind.InterfaceDeclaration: return ScriptElementKind.interfaceElement; - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocTypedefTag: - return ScriptElementKind.typeElement; - case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement; - case SyntaxKind.VariableDeclaration: - return getKindOfVariableDeclaration(node as VariableDeclaration); - case SyntaxKind.BindingElement: - return getKindOfVariableDeclaration(getRootDeclaration(node) as VariableDeclaration); - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - return ScriptElementKind.functionElement; - case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement; - case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return ScriptElementKind.memberFunctionElement; - case SyntaxKind.PropertyAssignment: - const { initializer } = node as PropertyAssignment; - return isFunctionLike(initializer) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.SpreadAssignment: - return ScriptElementKind.memberVariableElement; - case SyntaxKind.IndexSignature: return ScriptElementKind.indexSignatureElement; - case SyntaxKind.ConstructSignature: return ScriptElementKind.constructSignatureElement; - case SyntaxKind.CallSignature: return ScriptElementKind.callSignatureElement; - case SyntaxKind.Constructor: - case SyntaxKind.ClassStaticBlockDeclaration: - return ScriptElementKind.constructorImplementationElement; - case SyntaxKind.TypeParameter: return ScriptElementKind.typeParameterElement; - case SyntaxKind.EnumMember: return ScriptElementKind.enumMemberElement; - case SyntaxKind.Parameter: return hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier) ? ScriptElementKind.memberVariableElement : ScriptElementKind.parameterElement; - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.NamespaceImport: - case SyntaxKind.NamespaceExport: - return ScriptElementKind.alias; - case SyntaxKind.BinaryExpression: - const kind = getAssignmentDeclarationKind(node as BinaryExpression); - const { right } = node as BinaryExpression; - switch (kind) { - case AssignmentDeclarationKind.ObjectDefinePropertyValue: - case AssignmentDeclarationKind.ObjectDefinePropertyExports: - case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: - case AssignmentDeclarationKind.None: - return ScriptElementKind.unknown; - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.ModuleExports: - const rightKind = getNodeKind(right); - return rightKind === ScriptElementKind.unknown ? ScriptElementKind.constElement : rightKind; - case AssignmentDeclarationKind.PrototypeProperty: - return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; - case AssignmentDeclarationKind.ThisProperty: - return ScriptElementKind.memberVariableElement; // property - case AssignmentDeclarationKind.Property: - // static method / property - return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; - case AssignmentDeclarationKind.Prototype: - return ScriptElementKind.localClassElement; - default: { - assertType(kind); - return ScriptElementKind.unknown; - } - } - case SyntaxKind.Identifier: - return isImportClause(node.parent) ? ScriptElementKind.alias : ScriptElementKind.unknown; - case SyntaxKind.ExportAssignment: - const scriptKind = getNodeKind((node as ExportAssignment).expression); - // If the expression didn't come back with something (like it does for an identifiers) - return scriptKind === ScriptElementKind.unknown ? ScriptElementKind.constElement : scriptKind; - default: - return ScriptElementKind.unknown; - } +// Matches the beginning of a triple slash directive +const tripleSlashDirectivePrefixRegex = /^\/\/\/\s*= range.end; +} - export function rangeContainsPosition(r: TextRange, pos: number): boolean { - return r.pos <= pos && pos <= r.end; - } +export function rangeContainsStartEnd(range: TextRange, start: number, end: number): boolean { + return range.pos <= start && range.end >= end; +} - export function rangeContainsPositionExclusive(r: TextRange, pos: number) { - return r.pos < pos && pos < r.end; - } +export function rangeOverlapsWithStartEnd(r1: TextRange, start: number, end: number) { + return startEndOverlapsWithStartEnd(r1.pos, r1.end, start, end); +} - export function startEndContainsRange(start: number, end: number, range: TextRange): boolean { - return start <= range.pos && end >= range.end; - } +export function nodeOverlapsWithStartEnd(node: Node, sourceFile: SourceFile, start: number, end: number) { + return startEndOverlapsWithStartEnd(node.getStart(sourceFile), node.end, start, end); +} - export function rangeContainsStartEnd(range: TextRange, start: number, end: number): boolean { - return range.pos <= start && range.end >= end; - } +export function startEndOverlapsWithStartEnd(start1: number, end1: number, start2: number, end2: number) { + const start = Math.max(start1, start2); + const end = Math.min(end1, end2); + return start < end; +} - export function rangeOverlapsWithStartEnd(r1: TextRange, start: number, end: number) { - return startEndOverlapsWithStartEnd(r1.pos, r1.end, start, end); - } +/** + * Assumes `candidate.start <= position` holds. + */ +export function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean { + Debug.assert(candidate.pos <= position); + return position < candidate.end || !isCompletedNode(candidate, sourceFile); +} - export function nodeOverlapsWithStartEnd(node: Node, sourceFile: SourceFile, start: number, end: number) { - return startEndOverlapsWithStartEnd(node.getStart(sourceFile), node.end, start, end); +function isCompletedNode(n: Node | undefined, sourceFile: SourceFile): boolean { + if (n === undefined || nodeIsMissing(n)) { + return false; } - export function startEndOverlapsWithStartEnd(start1: number, end1: number, start2: number, end2: number) { - const start = Math.max(start1, start2); - const end = Math.min(end1, end2); - return start < end; - } + switch (n.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.TypeLiteral: + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + case SyntaxKind.CaseBlock: + case SyntaxKind.NamedImports: + case SyntaxKind.NamedExports: + return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile); + case SyntaxKind.CatchClause: + return isCompletedNode((n as CatchClause).block, sourceFile); + case SyntaxKind.NewExpression: + if (!(n as NewExpression).arguments) { + return true; + } + // falls through - /** - * Assumes `candidate.start <= position` holds. - */ - export function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean { - Debug.assert(candidate.pos <= position); - return position < candidate.end || !isCompletedNode(candidate, sourceFile); - } + case SyntaxKind.CallExpression: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.ParenthesizedType: + return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile); - function isCompletedNode(n: Node | undefined, sourceFile: SourceFile): boolean { - if (n === undefined || nodeIsMissing(n)) { - return false; - } + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + return isCompletedNode((n as SignatureDeclaration).type, sourceFile); - switch (n.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.TypeLiteral: - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - case SyntaxKind.CaseBlock: - case SyntaxKind.NamedImports: - case SyntaxKind.NamedExports: - return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile); - case SyntaxKind.CatchClause: - return isCompletedNode((n as CatchClause).block, sourceFile); - case SyntaxKind.NewExpression: - if (!(n as NewExpression).arguments) { - return true; - } - // falls through + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ArrowFunction: + if ((n as FunctionLikeDeclaration).body) { + return isCompletedNode((n as FunctionLikeDeclaration).body, sourceFile); + } - case SyntaxKind.CallExpression: - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.ParenthesizedType: - return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile); + if ((n as FunctionLikeDeclaration).type) { + return isCompletedNode((n as FunctionLikeDeclaration).type, sourceFile); + } - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - return isCompletedNode((n as SignatureDeclaration).type, sourceFile); + // Even though type parameters can be unclosed, we can get away with + // having at least a closing paren. + return hasChildOfKind(n, SyntaxKind.CloseParenToken, sourceFile); - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ArrowFunction: - if ((n as FunctionLikeDeclaration).body) { - return isCompletedNode((n as FunctionLikeDeclaration).body, sourceFile); - } + case SyntaxKind.ModuleDeclaration: + return !!(n as ModuleDeclaration).body && isCompletedNode((n as ModuleDeclaration).body, sourceFile); - if ((n as FunctionLikeDeclaration).type) { - return isCompletedNode((n as FunctionLikeDeclaration).type, sourceFile); - } + case SyntaxKind.IfStatement: + if ((n as IfStatement).elseStatement) { + return isCompletedNode((n as IfStatement).elseStatement, sourceFile); + } + return isCompletedNode((n as IfStatement).thenStatement, sourceFile); - // Even though type parameters can be unclosed, we can get away with - // having at least a closing paren. - return hasChildOfKind(n, SyntaxKind.CloseParenToken, sourceFile); + case SyntaxKind.ExpressionStatement: + return isCompletedNode((n as ExpressionStatement).expression, sourceFile) || + hasChildOfKind(n, SyntaxKind.SemicolonToken, sourceFile); - case SyntaxKind.ModuleDeclaration: - return !!(n as ModuleDeclaration).body && isCompletedNode((n as ModuleDeclaration).body, sourceFile); + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.ComputedPropertyName: + case SyntaxKind.TupleType: + return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile); - case SyntaxKind.IfStatement: - if ((n as IfStatement).elseStatement) { - return isCompletedNode((n as IfStatement).elseStatement, sourceFile); - } - return isCompletedNode((n as IfStatement).thenStatement, sourceFile); - - case SyntaxKind.ExpressionStatement: - return isCompletedNode((n as ExpressionStatement).expression, sourceFile) || - hasChildOfKind(n, SyntaxKind.SemicolonToken, sourceFile); - - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ElementAccessExpression: - case SyntaxKind.ComputedPropertyName: - case SyntaxKind.TupleType: - return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile); - - case SyntaxKind.IndexSignature: - if ((n as IndexSignatureDeclaration).type) { - return isCompletedNode((n as IndexSignatureDeclaration).type, sourceFile); - } + case SyntaxKind.IndexSignature: + if ((n as IndexSignatureDeclaration).type) { + return isCompletedNode((n as IndexSignatureDeclaration).type, sourceFile); + } - return hasChildOfKind(n, SyntaxKind.CloseBracketToken, sourceFile); + return hasChildOfKind(n, SyntaxKind.CloseBracketToken, sourceFile); - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - // there is no such thing as terminator token for CaseClause/DefaultClause so for simplicity always consider them non-completed - return false; + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + // there is no such thing as terminator token for CaseClause/DefaultClause so for simplicity always consider them non-completed + return false; - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.WhileStatement: - return isCompletedNode((n as IterationStatement).statement, sourceFile); - case SyntaxKind.DoStatement: - // rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')'; - return hasChildOfKind(n, SyntaxKind.WhileKeyword, sourceFile) - ? nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile) - : isCompletedNode((n as DoStatement).statement, sourceFile); - - case SyntaxKind.TypeQuery: - return isCompletedNode((n as TypeQueryNode).exprName, sourceFile); - - case SyntaxKind.TypeOfExpression: - case SyntaxKind.DeleteExpression: - case SyntaxKind.VoidExpression: - case SyntaxKind.YieldExpression: - case SyntaxKind.SpreadElement: - const unaryWordExpression = n as (TypeOfExpression | DeleteExpression | VoidExpression | YieldExpression | SpreadElement); - return isCompletedNode(unaryWordExpression.expression, sourceFile); - - case SyntaxKind.TaggedTemplateExpression: - return isCompletedNode((n as TaggedTemplateExpression).template, sourceFile); - case SyntaxKind.TemplateExpression: - const lastSpan = lastOrUndefined((n as TemplateExpression).templateSpans); - return isCompletedNode(lastSpan, sourceFile); - case SyntaxKind.TemplateSpan: - return nodeIsPresent((n as TemplateSpan).literal); - - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ImportDeclaration: - return nodeIsPresent((n as ExportDeclaration | ImportDeclaration).moduleSpecifier); - - case SyntaxKind.PrefixUnaryExpression: - return isCompletedNode((n as PrefixUnaryExpression).operand, sourceFile); - case SyntaxKind.BinaryExpression: - return isCompletedNode((n as BinaryExpression).right, sourceFile); - case SyntaxKind.ConditionalExpression: - return isCompletedNode((n as ConditionalExpression).whenFalse, sourceFile); + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.WhileStatement: + return isCompletedNode((n as IterationStatement).statement, sourceFile); + case SyntaxKind.DoStatement: + // rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')'; + return hasChildOfKind(n, SyntaxKind.WhileKeyword, sourceFile) + ? nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile) + : isCompletedNode((n as DoStatement).statement, sourceFile); + + case SyntaxKind.TypeQuery: + return isCompletedNode((n as TypeQueryNode).exprName, sourceFile); + + case SyntaxKind.TypeOfExpression: + case SyntaxKind.DeleteExpression: + case SyntaxKind.VoidExpression: + case SyntaxKind.YieldExpression: + case SyntaxKind.SpreadElement: + const unaryWordExpression = n as (TypeOfExpression | DeleteExpression | VoidExpression | YieldExpression | SpreadElement); + return isCompletedNode(unaryWordExpression.expression, sourceFile); + + case SyntaxKind.TaggedTemplateExpression: + return isCompletedNode((n as TaggedTemplateExpression).template, sourceFile); + case SyntaxKind.TemplateExpression: + const lastSpan = lastOrUndefined((n as TemplateExpression).templateSpans); + return isCompletedNode(lastSpan, sourceFile); + case SyntaxKind.TemplateSpan: + return nodeIsPresent((n as TemplateSpan).literal); + + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ImportDeclaration: + return nodeIsPresent((n as ExportDeclaration | ImportDeclaration).moduleSpecifier); + + case SyntaxKind.PrefixUnaryExpression: + return isCompletedNode((n as PrefixUnaryExpression).operand, sourceFile); + case SyntaxKind.BinaryExpression: + return isCompletedNode((n as BinaryExpression).right, sourceFile); + case SyntaxKind.ConditionalExpression: + return isCompletedNode((n as ConditionalExpression).whenFalse, sourceFile); + + default: + return true; + } +} - default: - return true; +/* + * Checks if node ends with 'expectedLastToken'. + * If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'. + */ +function nodeEndsWith(n: Node, expectedLastToken: SyntaxKind, sourceFile: SourceFile): boolean { + const children = n.getChildren(sourceFile); + if (children.length) { + const lastChild = last(children); + if (lastChild.kind === expectedLastToken) { + return true; + } + else if (lastChild.kind === SyntaxKind.SemicolonToken && children.length !== 1) { + return children[children.length - 2].kind === expectedLastToken; } } + return false; +} - /* - * Checks if node ends with 'expectedLastToken'. - * If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'. - */ - function nodeEndsWith(n: Node, expectedLastToken: SyntaxKind, sourceFile: SourceFile): boolean { - const children = n.getChildren(sourceFile); - if (children.length) { - const lastChild = last(children); - if (lastChild.kind === expectedLastToken) { - return true; - } - else if (lastChild.kind === SyntaxKind.SemicolonToken && children.length !== 1) { - return children[children.length - 2].kind === expectedLastToken; - } - } - return false; +export function findListItemInfo(node: Node): ListItemInfo | undefined { + const list = findContainingList(node); + + // It is possible at this point for syntaxList to be undefined, either if + // node.parent had no list child, or if none of its list children contained + // the span of node. If this happens, return undefined. The caller should + // handle this case. + if (!list) { + return undefined; } - export function findListItemInfo(node: Node): ListItemInfo | undefined { - const list = findContainingList(node); + const children = list.getChildren(); + const listItemIndex = indexOfNode(children, node); - // It is possible at this point for syntaxList to be undefined, either if - // node.parent had no list child, or if none of its list children contained - // the span of node. If this happens, return undefined. The caller should - // handle this case. - if (!list) { - return undefined; - } + return { + listItemIndex, + list + }; +} - const children = list.getChildren(); - const listItemIndex = indexOfNode(children, node); +export function hasChildOfKind(n: Node, kind: SyntaxKind, sourceFile: SourceFile): boolean { + return !!findChildOfKind(n, kind, sourceFile); +} - return { - listItemIndex, - list - }; - } +export function findChildOfKind(n: Node, kind: T["kind"], sourceFile: SourceFileLike): T | undefined { + return find(n.getChildren(sourceFile), (c): c is T => c.kind === kind); +} - export function hasChildOfKind(n: Node, kind: SyntaxKind, sourceFile: SourceFile): boolean { - return !!findChildOfKind(n, kind, sourceFile); - } +export function findContainingList(node: Node): SyntaxList | undefined { + // The node might be a list element (nonsynthetic) or a comma (synthetic). Either way, it will + // be parented by the container of the SyntaxList, not the SyntaxList itself. + // In order to find the list item index, we first need to locate SyntaxList itself and then search + // for the position of the relevant node (or comma). + const syntaxList = find(node.parent.getChildren(), (c): c is SyntaxList => isSyntaxList(c) && rangeContainsRange(c, node)); + // Either we didn't find an appropriate list, or the list must contain us. + Debug.assert(!syntaxList || contains(syntaxList.getChildren(), node)); + return syntaxList; +} - export function findChildOfKind(n: Node, kind: T["kind"], sourceFile: SourceFileLike): T | undefined { - return find(n.getChildren(sourceFile), (c): c is T => c.kind === kind); - } +function isDefaultModifier(node: Node) { + return node.kind === SyntaxKind.DefaultKeyword; +} - export function findContainingList(node: Node): SyntaxList | undefined { - // The node might be a list element (nonsynthetic) or a comma (synthetic). Either way, it will - // be parented by the container of the SyntaxList, not the SyntaxList itself. - // In order to find the list item index, we first need to locate SyntaxList itself and then search - // for the position of the relevant node (or comma). - const syntaxList = find(node.parent.getChildren(), (c): c is SyntaxList => isSyntaxList(c) && rangeContainsRange(c, node)); - // Either we didn't find an appropriate list, or the list must contain us. - Debug.assert(!syntaxList || contains(syntaxList.getChildren(), node)); - return syntaxList; - } +function isClassKeyword(node: Node) { + return node.kind === SyntaxKind.ClassKeyword; +} - function isDefaultModifier(node: Node) { - return node.kind === SyntaxKind.DefaultKeyword; - } +function isFunctionKeyword(node: Node) { + return node.kind === SyntaxKind.FunctionKeyword; +} - function isClassKeyword(node: Node) { - return node.kind === SyntaxKind.ClassKeyword; +function getAdjustedLocationForClass(node: ClassDeclaration | ClassExpression) { + if (isNamedDeclaration(node)) { + return node.name; } - - function isFunctionKeyword(node: Node) { - return node.kind === SyntaxKind.FunctionKeyword; + if (isClassDeclaration(node)) { + // for class and function declarations, use the `default` modifier + // when the declaration is unnamed. + const defaultModifier = node.modifiers && find(node.modifiers, isDefaultModifier); + if (defaultModifier) return defaultModifier; } - - function getAdjustedLocationForClass(node: ClassDeclaration | ClassExpression) { - if (isNamedDeclaration(node)) { - return node.name; - } - if (isClassDeclaration(node)) { - // for class and function declarations, use the `default` modifier - // when the declaration is unnamed. - const defaultModifier = node.modifiers && find(node.modifiers, isDefaultModifier); - if (defaultModifier) return defaultModifier; - } - if (isClassExpression(node)) { - // for class expressions, use the `class` keyword when the class is unnamed - const classKeyword = find(node.getChildren(), isClassKeyword); - if (classKeyword) return classKeyword; - } + if (isClassExpression(node)) { + // for class expressions, use the `class` keyword when the class is unnamed + const classKeyword = find(node.getChildren(), isClassKeyword); + if (classKeyword) return classKeyword; } +} - function getAdjustedLocationForFunction(node: FunctionDeclaration | FunctionExpression) { - if (isNamedDeclaration(node)) { - return node.name; - } - if (isFunctionDeclaration(node)) { - // for class and function declarations, use the `default` modifier - // when the declaration is unnamed. - const defaultModifier = find(node.modifiers!, isDefaultModifier); - if (defaultModifier) return defaultModifier; - } - if (isFunctionExpression(node)) { - // for function expressions, use the `function` keyword when the function is unnamed - const functionKeyword = find(node.getChildren(), isFunctionKeyword); - if (functionKeyword) return functionKeyword; - } +function getAdjustedLocationForFunction(node: FunctionDeclaration | FunctionExpression) { + if (isNamedDeclaration(node)) { + return node.name; } - - function getAncestorTypeNode(node: Node) { - let lastTypeNode: TypeNode | undefined; - findAncestor(node, a => { - if (isTypeNode(a)) { - lastTypeNode = a; - } - return !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent); - }); - return lastTypeNode; + if (isFunctionDeclaration(node)) { + // for class and function declarations, use the `default` modifier + // when the declaration is unnamed. + const defaultModifier = find(node.modifiers!, isDefaultModifier); + if (defaultModifier) return defaultModifier; } - - export function getContextualTypeFromParentOrAncestorTypeNode(node: Expression, checker: TypeChecker): Type | undefined { - const contextualType = getContextualTypeFromParent(node, checker); - if (contextualType) return contextualType; - - const ancestorTypeNode = getAncestorTypeNode(node); - return ancestorTypeNode && checker.getTypeAtLocation(ancestorTypeNode); + if (isFunctionExpression(node)) { + // for function expressions, use the `function` keyword when the function is unnamed + const functionKeyword = find(node.getChildren(), isFunctionKeyword); + if (functionKeyword) return functionKeyword; } +} - function getAdjustedLocationForDeclaration(node: Node, forRename: boolean) { - if (!forRename) { - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return getAdjustedLocationForClass(node as ClassDeclaration | ClassExpression); - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - return getAdjustedLocationForFunction(node as FunctionDeclaration | FunctionExpression); - } - } - if (isNamedDeclaration(node)) { - return node.name; +function getAncestorTypeNode(node: Node) { + let lastTypeNode: TypeNode | undefined; + findAncestor(node, a => { + if (isTypeNode(a)) { + lastTypeNode = a; } - } + return !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent); + }); + return lastTypeNode; +} - function getAdjustedLocationForImportDeclaration(node: ImportDeclaration, forRename: boolean) { - if (node.importClause) { - if (node.importClause.name && node.importClause.namedBindings) { - // do not adjust if we have both a name and named bindings - return; - } +export function getContextualTypeFromParentOrAncestorTypeNode(node: Expression, checker: TypeChecker): Type | undefined { + const contextualType = getContextualTypeFromParent(node, checker); + if (contextualType) return contextualType; - // /**/import [|name|] from ...; - // import /**/type [|name|] from ...; - if (node.importClause.name) { - return node.importClause.name; - } + const ancestorTypeNode = getAncestorTypeNode(node); + return ancestorTypeNode && checker.getTypeAtLocation(ancestorTypeNode); +} - // /**/import { [|name|] } from ...; - // /**/import { propertyName as [|name|] } from ...; - // /**/import * as [|name|] from ...; - // import /**/type { [|name|] } from ...; - // import /**/type { propertyName as [|name|] } from ...; - // import /**/type * as [|name|] from ...; - if (node.importClause.namedBindings) { - if (isNamedImports(node.importClause.namedBindings)) { - // do nothing if there is more than one binding - const onlyBinding = singleOrUndefined(node.importClause.namedBindings.elements); - if (!onlyBinding) { - return; - } - return onlyBinding.name; - } - else if (isNamespaceImport(node.importClause.namedBindings)) { - return node.importClause.namedBindings.name; - } - } - } - if (!forRename) { - // /**/import "[|module|]"; - // /**/import ... from "[|module|]"; - // import /**/type ... from "[|module|]"; - return node.moduleSpecifier; +function getAdjustedLocationForDeclaration(node: Node, forRename: boolean) { + if (!forRename) { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return getAdjustedLocationForClass(node as ClassDeclaration | ClassExpression); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + return getAdjustedLocationForFunction(node as FunctionDeclaration | FunctionExpression); } } + if (isNamedDeclaration(node)) { + return node.name; + } +} - function getAdjustedLocationForExportDeclaration(node: ExportDeclaration, forRename: boolean) { - if (node.exportClause) { - // /**/export { [|name|] } ... - // /**/export { propertyName as [|name|] } ... - // /**/export * as [|name|] ... - // export /**/type { [|name|] } from ... - // export /**/type { propertyName as [|name|] } from ... - // export /**/type * as [|name|] ... - if (isNamedExports(node.exportClause)) { +function getAdjustedLocationForImportDeclaration(node: ImportDeclaration, forRename: boolean) { + if (node.importClause) { + if (node.importClause.name && node.importClause.namedBindings) { + // do not adjust if we have both a name and named bindings + return; + } + + // /**/import [|name|] from ...; + // import /**/type [|name|] from ...; + if (node.importClause.name) { + return node.importClause.name; + } + + // /**/import { [|name|] } from ...; + // /**/import { propertyName as [|name|] } from ...; + // /**/import * as [|name|] from ...; + // import /**/type { [|name|] } from ...; + // import /**/type { propertyName as [|name|] } from ...; + // import /**/type * as [|name|] from ...; + if (node.importClause.namedBindings) { + if (isNamedImports(node.importClause.namedBindings)) { // do nothing if there is more than one binding - const onlyBinding = singleOrUndefined(node.exportClause.elements); + const onlyBinding = singleOrUndefined(node.importClause.namedBindings.elements); if (!onlyBinding) { return; } - return node.exportClause.elements[0].name; - } - else if (isNamespaceExport(node.exportClause)) { - return node.exportClause.name; + return onlyBinding.name; } - } - if (!forRename) { - // /**/export * from "[|module|]"; - // export /**/type * from "[|module|]"; - return node.moduleSpecifier; - } - } - - function getAdjustedLocationForHeritageClause(node: HeritageClause) { - // /**/extends [|name|] - // /**/implements [|name|] - if (node.types.length === 1) { - return node.types[0].expression; - } - - // /**/extends name1, name2 ... - // /**/implements name1, name2 ... - } - - function getAdjustedLocation(node: Node, forRename: boolean): Node { - const { parent } = node; - // /**/ [|name|] ... - // /**/ [|name|] ... - // /**/ [|name|] ... - // /**/import [|name|] = ... - // - // NOTE: If the node is a modifier, we don't adjust its location if it is the `default` modifier as that is handled - // specially by `getSymbolAtLocation`. - if (isModifier(node) && (forRename || node.kind !== SyntaxKind.DefaultKeyword) ? contains(parent.modifiers, node) : - node.kind === SyntaxKind.ClassKeyword ? isClassDeclaration(parent) || isClassExpression(node) : - node.kind === SyntaxKind.FunctionKeyword ? isFunctionDeclaration(parent) || isFunctionExpression(node) : - node.kind === SyntaxKind.InterfaceKeyword ? isInterfaceDeclaration(parent) : - node.kind === SyntaxKind.EnumKeyword ? isEnumDeclaration(parent) : - node.kind === SyntaxKind.TypeKeyword ? isTypeAliasDeclaration(parent) : - node.kind === SyntaxKind.NamespaceKeyword || node.kind === SyntaxKind.ModuleKeyword ? isModuleDeclaration(parent) : - node.kind === SyntaxKind.ImportKeyword ? isImportEqualsDeclaration(parent) : - node.kind === SyntaxKind.GetKeyword ? isGetAccessorDeclaration(parent) : - node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(parent)) { - const location = getAdjustedLocationForDeclaration(parent, forRename); - if (location) { - return location; + else if (isNamespaceImport(node.importClause.namedBindings)) { + return node.importClause.namedBindings.name; } } - // /**/ [|name|] ... - if ((node.kind === SyntaxKind.VarKeyword || node.kind === SyntaxKind.ConstKeyword || node.kind === SyntaxKind.LetKeyword) && - isVariableDeclarationList(parent) && parent.declarations.length === 1) { - const decl = parent.declarations[0]; - if (isIdentifier(decl.name)) { - return decl.name; - } - } - if (node.kind === SyntaxKind.TypeKeyword) { - // import /**/type [|name|] from ...; - // import /**/type { [|name|] } from ...; - // import /**/type { propertyName as [|name|] } from ...; - // import /**/type ... from "[|module|]"; - if (isImportClause(parent) && parent.isTypeOnly) { - const location = getAdjustedLocationForImportDeclaration(parent.parent, forRename); - if (location) { - return location; - } - } - // export /**/type { [|name|] } from ...; - // export /**/type { propertyName as [|name|] } from ...; - // export /**/type * from "[|module|]"; - // export /**/type * as ... from "[|module|]"; - if (isExportDeclaration(parent) && parent.isTypeOnly) { - const location = getAdjustedLocationForExportDeclaration(parent, forRename); - if (location) { - return location; - } + } + if (!forRename) { + // /**/import "[|module|]"; + // /**/import ... from "[|module|]"; + // import /**/type ... from "[|module|]"; + return node.moduleSpecifier; + } +} + +function getAdjustedLocationForExportDeclaration(node: ExportDeclaration, forRename: boolean) { + if (node.exportClause) { + // /**/export { [|name|] } ... + // /**/export { propertyName as [|name|] } ... + // /**/export * as [|name|] ... + // export /**/type { [|name|] } from ... + // export /**/type { propertyName as [|name|] } from ... + // export /**/type * as [|name|] ... + if (isNamedExports(node.exportClause)) { + // do nothing if there is more than one binding + const onlyBinding = singleOrUndefined(node.exportClause.elements); + if (!onlyBinding) { + return; } + return node.exportClause.elements[0].name; } - // import { propertyName /**/as [|name|] } ... - // import * /**/as [|name|] ... - // export { propertyName /**/as [|name|] } ... - // export * /**/as [|name|] ... - if (node.kind === SyntaxKind.AsKeyword) { - if (isImportSpecifier(parent) && parent.propertyName || - isExportSpecifier(parent) && parent.propertyName || - isNamespaceImport(parent) || - isNamespaceExport(parent)) { - return parent.name; - } - if (isExportDeclaration(parent) && parent.exportClause && isNamespaceExport(parent.exportClause)) { - return parent.exportClause.name; - } + else if (isNamespaceExport(node.exportClause)) { + return node.exportClause.name; } - // /**/import [|name|] from ...; - // /**/import { [|name|] } from ...; - // /**/import { propertyName as [|name|] } from ...; - // /**/import ... from "[|module|]"; - // /**/import "[|module|]"; - if (node.kind === SyntaxKind.ImportKeyword && isImportDeclaration(parent)) { - const location = getAdjustedLocationForImportDeclaration(parent, forRename); + } + if (!forRename) { + // /**/export * from "[|module|]"; + // export /**/type * from "[|module|]"; + return node.moduleSpecifier; + } +} + +function getAdjustedLocationForHeritageClause(node: HeritageClause) { + // /**/extends [|name|] + // /**/implements [|name|] + if (node.types.length === 1) { + return node.types[0].expression; + } + + // /**/extends name1, name2 ... + // /**/implements name1, name2 ... +} + +function getAdjustedLocation(node: Node, forRename: boolean): Node { + const { parent } = node; + // /**/ [|name|] ... + // /**/ [|name|] ... + // /**/ [|name|] ... + // /**/import [|name|] = ... + // + // NOTE: If the node is a modifier, we don't adjust its location if it is the `default` modifier as that is handled + // specially by `getSymbolAtLocation`. + if (isModifier(node) && (forRename || node.kind !== SyntaxKind.DefaultKeyword) ? contains(parent.modifiers, node) : + node.kind === SyntaxKind.ClassKeyword ? isClassDeclaration(parent) || isClassExpression(node) : + node.kind === SyntaxKind.FunctionKeyword ? isFunctionDeclaration(parent) || isFunctionExpression(node) : + node.kind === SyntaxKind.InterfaceKeyword ? isInterfaceDeclaration(parent) : + node.kind === SyntaxKind.EnumKeyword ? isEnumDeclaration(parent) : + node.kind === SyntaxKind.TypeKeyword ? isTypeAliasDeclaration(parent) : + node.kind === SyntaxKind.NamespaceKeyword || node.kind === SyntaxKind.ModuleKeyword ? isModuleDeclaration(parent) : + node.kind === SyntaxKind.ImportKeyword ? isImportEqualsDeclaration(parent) : + node.kind === SyntaxKind.GetKeyword ? isGetAccessorDeclaration(parent) : + node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(parent)) { + const location = getAdjustedLocationForDeclaration(parent, forRename); + if (location) { + return location; + } + } + // /**/ [|name|] ... + if ((node.kind === SyntaxKind.VarKeyword || node.kind === SyntaxKind.ConstKeyword || node.kind === SyntaxKind.LetKeyword) && + isVariableDeclarationList(parent) && parent.declarations.length === 1) { + const decl = parent.declarations[0]; + if (isIdentifier(decl.name)) { + return decl.name; + } + } + if (node.kind === SyntaxKind.TypeKeyword) { + // import /**/type [|name|] from ...; + // import /**/type { [|name|] } from ...; + // import /**/type { propertyName as [|name|] } from ...; + // import /**/type ... from "[|module|]"; + if (isImportClause(parent) && parent.isTypeOnly) { + const location = getAdjustedLocationForImportDeclaration(parent.parent, forRename); if (location) { return location; } } - if (node.kind === SyntaxKind.ExportKeyword) { - // /**/export { [|name|] } ...; - // /**/export { propertyName as [|name|] } ...; - // /**/export * from "[|module|]"; - // /**/export * as ... from "[|module|]"; - if (isExportDeclaration(parent)) { - const location = getAdjustedLocationForExportDeclaration(parent, forRename); - if (location) { - return location; - } - } - // NOTE: We don't adjust the location of the `default` keyword as that is handled specially by `getSymbolAtLocation`. - // /**/export default [|name|]; - // /**/export = [|name|]; - if (isExportAssignment(parent)) { - return skipOuterExpressions(parent.expression); - } - } - // import name = /**/require("[|module|]"); - if (node.kind === SyntaxKind.RequireKeyword && isExternalModuleReference(parent)) { - return parent.expression; - } - // import ... /**/from "[|module|]"; - // export ... /**/from "[|module|]"; - if (node.kind === SyntaxKind.FromKeyword && (isImportDeclaration(parent) || isExportDeclaration(parent)) && parent.moduleSpecifier) { - return parent.moduleSpecifier; - } - // class ... /**/extends [|name|] ... - // class ... /**/implements [|name|] ... - // class ... /**/implements name1, name2 ... - // interface ... /**/extends [|name|] ... - // interface ... /**/extends name1, name2 ... - if ((node.kind === SyntaxKind.ExtendsKeyword || node.kind === SyntaxKind.ImplementsKeyword) && isHeritageClause(parent) && parent.token === node.kind) { - const location = getAdjustedLocationForHeritageClause(parent); + // export /**/type { [|name|] } from ...; + // export /**/type { propertyName as [|name|] } from ...; + // export /**/type * from "[|module|]"; + // export /**/type * as ... from "[|module|]"; + if (isExportDeclaration(parent) && parent.isTypeOnly) { + const location = getAdjustedLocationForExportDeclaration(parent, forRename); if (location) { return location; } } - if (node.kind === SyntaxKind.ExtendsKeyword) { - // ... ... - if (isTypeParameterDeclaration(parent) && parent.constraint && isTypeReferenceNode(parent.constraint)) { - return parent.constraint.typeName; - } - // ... T /**/extends [|U|] ? ... - if (isConditionalTypeNode(parent) && isTypeReferenceNode(parent.extendsType)) { - return parent.extendsType.typeName; - } - } - // ... T extends /**/infer [|U|] ? ... - if (node.kind === SyntaxKind.InferKeyword && isInferTypeNode(parent)) { - return parent.typeParameter.name; - } - // { [ [|K|] /**/in keyof T]: ... } - if (node.kind === SyntaxKind.InKeyword && isTypeParameterDeclaration(parent) && isMappedTypeNode(parent.parent)) { + } + // import { propertyName /**/as [|name|] } ... + // import * /**/as [|name|] ... + // export { propertyName /**/as [|name|] } ... + // export * /**/as [|name|] ... + if (node.kind === SyntaxKind.AsKeyword) { + if (isImportSpecifier(parent) && parent.propertyName || + isExportSpecifier(parent) && parent.propertyName || + isNamespaceImport(parent) || + isNamespaceExport(parent)) { return parent.name; } - // /**/keyof [|T|] - if (node.kind === SyntaxKind.KeyOfKeyword && isTypeOperatorNode(parent) && parent.operator === SyntaxKind.KeyOfKeyword && - isTypeReferenceNode(parent.type)) { - return parent.type.typeName; + if (isExportDeclaration(parent) && parent.exportClause && isNamespaceExport(parent.exportClause)) { + return parent.exportClause.name; } - // /**/readonly [|name|][] - if (node.kind === SyntaxKind.ReadonlyKeyword && isTypeOperatorNode(parent) && parent.operator === SyntaxKind.ReadonlyKeyword && - isArrayTypeNode(parent.type) && isTypeReferenceNode(parent.type.elementType)) { - return parent.type.elementType.typeName; - } - if (!forRename) { - // /**/new [|name|] - // /**/void [|name|] - // /**/void obj.[|name|] - // /**/typeof [|name|] - // /**/typeof obj.[|name|] - // /**/await [|name|] - // /**/await obj.[|name|] - // /**/yield [|name|] - // /**/yield obj.[|name|] - // /**/delete obj.[|name|] - if (node.kind === SyntaxKind.NewKeyword && isNewExpression(parent) || - node.kind === SyntaxKind.VoidKeyword && isVoidExpression(parent) || - node.kind === SyntaxKind.TypeOfKeyword && isTypeOfExpression(parent) || - node.kind === SyntaxKind.AwaitKeyword && isAwaitExpression(parent) || - node.kind === SyntaxKind.YieldKeyword && isYieldExpression(parent) || - node.kind === SyntaxKind.DeleteKeyword && isDeleteExpression(parent)) { - if (parent.expression) { - return skipOuterExpressions(parent.expression); - } - } - // left /**/in [|name|] - // left /**/instanceof [|name|] - if ((node.kind === SyntaxKind.InKeyword || node.kind === SyntaxKind.InstanceOfKeyword) && isBinaryExpression(parent) && parent.operatorToken === node) { - return skipOuterExpressions(parent.right); - } - // left /**/as [|name|] - if (node.kind === SyntaxKind.AsKeyword && isAsExpression(parent) && isTypeReferenceNode(parent.type)) { - return parent.type.typeName; + } + // /**/import [|name|] from ...; + // /**/import { [|name|] } from ...; + // /**/import { propertyName as [|name|] } from ...; + // /**/import ... from "[|module|]"; + // /**/import "[|module|]"; + if (node.kind === SyntaxKind.ImportKeyword && isImportDeclaration(parent)) { + const location = getAdjustedLocationForImportDeclaration(parent, forRename); + if (location) { + return location; + } + } + if (node.kind === SyntaxKind.ExportKeyword) { + // /**/export { [|name|] } ...; + // /**/export { propertyName as [|name|] } ...; + // /**/export * from "[|module|]"; + // /**/export * as ... from "[|module|]"; + if (isExportDeclaration(parent)) { + const location = getAdjustedLocationForExportDeclaration(parent, forRename); + if (location) { + return location; } - // for (... /**/in [|name|]) - // for (... /**/of [|name|]) - if (node.kind === SyntaxKind.InKeyword && isForInStatement(parent) || - node.kind === SyntaxKind.OfKeyword && isForOfStatement(parent)) { + } + // NOTE: We don't adjust the location of the `default` keyword as that is handled specially by `getSymbolAtLocation`. + // /**/export default [|name|]; + // /**/export = [|name|]; + if (isExportAssignment(parent)) { + return skipOuterExpressions(parent.expression); + } + } + // import name = /**/require("[|module|]"); + if (node.kind === SyntaxKind.RequireKeyword && isExternalModuleReference(parent)) { + return parent.expression; + } + // import ... /**/from "[|module|]"; + // export ... /**/from "[|module|]"; + if (node.kind === SyntaxKind.FromKeyword && (isImportDeclaration(parent) || isExportDeclaration(parent)) && parent.moduleSpecifier) { + return parent.moduleSpecifier; + } + // class ... /**/extends [|name|] ... + // class ... /**/implements [|name|] ... + // class ... /**/implements name1, name2 ... + // interface ... /**/extends [|name|] ... + // interface ... /**/extends name1, name2 ... + if ((node.kind === SyntaxKind.ExtendsKeyword || node.kind === SyntaxKind.ImplementsKeyword) && isHeritageClause(parent) && parent.token === node.kind) { + const location = getAdjustedLocationForHeritageClause(parent); + if (location) { + return location; + } + } + if (node.kind === SyntaxKind.ExtendsKeyword) { + // ... ... + if (isTypeParameterDeclaration(parent) && parent.constraint && isTypeReferenceNode(parent.constraint)) { + return parent.constraint.typeName; + } + // ... T /**/extends [|U|] ? ... + if (isConditionalTypeNode(parent) && isTypeReferenceNode(parent.extendsType)) { + return parent.extendsType.typeName; + } + } + // ... T extends /**/infer [|U|] ? ... + if (node.kind === SyntaxKind.InferKeyword && isInferTypeNode(parent)) { + return parent.typeParameter.name; + } + // { [ [|K|] /**/in keyof T]: ... } + if (node.kind === SyntaxKind.InKeyword && isTypeParameterDeclaration(parent) && isMappedTypeNode(parent.parent)) { + return parent.name; + } + // /**/keyof [|T|] + if (node.kind === SyntaxKind.KeyOfKeyword && isTypeOperatorNode(parent) && parent.operator === SyntaxKind.KeyOfKeyword && + isTypeReferenceNode(parent.type)) { + return parent.type.typeName; + } + // /**/readonly [|name|][] + if (node.kind === SyntaxKind.ReadonlyKeyword && isTypeOperatorNode(parent) && parent.operator === SyntaxKind.ReadonlyKeyword && + isArrayTypeNode(parent.type) && isTypeReferenceNode(parent.type.elementType)) { + return parent.type.elementType.typeName; + } + if (!forRename) { + // /**/new [|name|] + // /**/void [|name|] + // /**/void obj.[|name|] + // /**/typeof [|name|] + // /**/typeof obj.[|name|] + // /**/await [|name|] + // /**/await obj.[|name|] + // /**/yield [|name|] + // /**/yield obj.[|name|] + // /**/delete obj.[|name|] + if (node.kind === SyntaxKind.NewKeyword && isNewExpression(parent) || + node.kind === SyntaxKind.VoidKeyword && isVoidExpression(parent) || + node.kind === SyntaxKind.TypeOfKeyword && isTypeOfExpression(parent) || + node.kind === SyntaxKind.AwaitKeyword && isAwaitExpression(parent) || + node.kind === SyntaxKind.YieldKeyword && isYieldExpression(parent) || + node.kind === SyntaxKind.DeleteKeyword && isDeleteExpression(parent)) { + if (parent.expression) { return skipOuterExpressions(parent.expression); } } - return node; - } - - /** - * Adjusts the location used for "find references" and "go to definition" when the cursor was not - * on a property name. - */ - export function getAdjustedReferenceLocation(node: Node): Node { - return getAdjustedLocation(node, /*forRename*/ false); - } - - /** - * Adjusts the location used for "rename" when the cursor was not on a property name. - */ - export function getAdjustedRenameLocation(node: Node): Node { - return getAdjustedLocation(node, /*forRename*/ true); - } - - /** - * Gets the token whose text has range [start, end) and - * position >= start and (position < end or (position === end && token is literal or keyword or identifier)) - */ - export function getTouchingPropertyName(sourceFile: SourceFile, position: number): Node { - return getTouchingToken(sourceFile, position, n => isPropertyNameLiteral(n) || isKeyword(n.kind) || isPrivateIdentifier(n)); + // left /**/in [|name|] + // left /**/instanceof [|name|] + if ((node.kind === SyntaxKind.InKeyword || node.kind === SyntaxKind.InstanceOfKeyword) && isBinaryExpression(parent) && parent.operatorToken === node) { + return skipOuterExpressions(parent.right); + } + // left /**/as [|name|] + if (node.kind === SyntaxKind.AsKeyword && isAsExpression(parent) && isTypeReferenceNode(parent.type)) { + return parent.type.typeName; + } + // for (... /**/in [|name|]) + // for (... /**/of [|name|]) + if (node.kind === SyntaxKind.InKeyword && isForInStatement(parent) || + node.kind === SyntaxKind.OfKeyword && isForOfStatement(parent)) { + return skipOuterExpressions(parent.expression); + } } + return node; +} - /** - * Returns the token if position is in [start, end). - * If position === end, returns the preceding token if includeItemAtEndPosition(previousToken) === true - */ - export function getTouchingToken(sourceFile: SourceFile, position: number, includePrecedingTokenAtEndPosition?: (n: Node) => boolean): Node { - return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includePrecedingTokenAtEndPosition, /*includeEndPosition*/ false); - } - - /** Returns a token if position is in [start-of-leading-trivia, end) */ - export function getTokenAtPosition(sourceFile: SourceFile, position: number): Node { - return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includePrecedingTokenAtEndPosition*/ undefined, /*includeEndPosition*/ false); - } - - /** Get the token whose text contains the position */ - function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includePrecedingTokenAtEndPosition: ((n: Node) => boolean) | undefined, includeEndPosition: boolean): Node { - let current: Node = sourceFile; - let foundToken: Node | undefined; - outer: while (true) { - // find the child that contains 'position' - - const children = current.getChildren(sourceFile); - const i = binarySearchKey(children, position, (_, i) => i, (middle, _) => { - // This last callback is more of a selector than a comparator - - // `EqualTo` causes the `middle` result to be returned - // `GreaterThan` causes recursion on the left of the middle - // `LessThan` causes recursion on the right of the middle - - // Let's say you have 3 nodes, spanning positons - // pos: 1, end: 3 - // pos: 3, end: 3 - // pos: 3, end: 5 - // and you're looking for the token at positon 3 - all 3 of these nodes are overlapping with position 3. - // In fact, there's a _good argument_ that node 2 shouldn't even be allowed to exist - depending on if - // the start or end of the ranges are considered inclusive, it's either wholly subsumed by the first or the last node. - // Unfortunately, such nodes do exist. :( - See fourslash/completionsImport_tsx.tsx - empty jsx attributes create - // a zero-length node. - // What also you may not expect is that which node we return depends on the includePrecedingTokenAtEndPosition flag. - // Specifically, if includePrecedingTokenAtEndPosition is set, we return the 1-3 node, while if it's unset, we - // return the 3-5 node. (The zero length node is never correct.) This is because the includePrecedingTokenAtEndPosition - // flag causes us to return the first node whose end position matches the position and which produces and acceptable token - // kind. Meanwhile, if includePrecedingTokenAtEndPosition is unset, we look for the first node whose start is <= the - // position and whose end is greater than the position. - - - const start = allowPositionInLeadingTrivia ? children[middle].getFullStart() : children[middle].getStart(sourceFile, /*includeJsDoc*/ true); - if (start > position) { - return Comparison.GreaterThan; - } +/** + * Adjusts the location used for "find references" and "go to definition" when the cursor was not + * on a property name. + */ +export function getAdjustedReferenceLocation(node: Node): Node { + return getAdjustedLocation(node, /*forRename*/ false); +} - // first element whose start position is before the input and whose end position is after or equal to the input - if (nodeContainsPosition(children[middle])) { - if (children[middle - 1]) { - // we want the _first_ element that contains the position, so left-recur if the prior node also contains the position - if (nodeContainsPosition(children[middle - 1])) { - return Comparison.GreaterThan; - } - } - return Comparison.EqualTo; - } +/** + * Adjusts the location used for "rename" when the cursor was not on a property name. + */ +export function getAdjustedRenameLocation(node: Node): Node { + return getAdjustedLocation(node, /*forRename*/ true); +} - // this complex condition makes us left-recur around a zero-length node when includePrecedingTokenAtEndPosition is set, rather than right-recur on it - if (includePrecedingTokenAtEndPosition && start === position && children[middle - 1] && children[middle - 1].getEnd() === position && nodeContainsPosition(children[middle - 1])) { - return Comparison.GreaterThan; - } - return Comparison.LessThan; - }); +/** + * Gets the token whose text has range [start, end) and + * position >= start and (position < end or (position === end && token is literal or keyword or identifier)) + */ +export function getTouchingPropertyName(sourceFile: SourceFile, position: number): Node { + return getTouchingToken(sourceFile, position, n => isPropertyNameLiteral(n) || isKeyword(n.kind) || isPrivateIdentifier(n)); +} - if (foundToken) { - return foundToken; - } - if (i >= 0 && children[i]) { - current = children[i]; - continue outer; - } +/** + * Returns the token if position is in [start, end). + * If position === end, returns the preceding token if includeItemAtEndPosition(previousToken) === true + */ +export function getTouchingToken(sourceFile: SourceFile, position: number, includePrecedingTokenAtEndPosition?: (n: Node) => boolean): Node { + return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includePrecedingTokenAtEndPosition, /*includeEndPosition*/ false); +} - return current; - } +/** Returns a token if position is in [start-of-leading-trivia, end) */ +export function getTokenAtPosition(sourceFile: SourceFile, position: number): Node { + return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includePrecedingTokenAtEndPosition*/ undefined, /*includeEndPosition*/ false); +} - function nodeContainsPosition(node: Node) { - const start = allowPositionInLeadingTrivia ? node.getFullStart() : node.getStart(sourceFile, /*includeJsDoc*/ true); +/** Get the token whose text contains the position */ +function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includePrecedingTokenAtEndPosition: ((n: Node) => boolean) | undefined, includeEndPosition: boolean): Node { + let current: Node = sourceFile; + let foundToken: Node | undefined; + outer: while (true) { + // find the child that contains 'position' + + const children = current.getChildren(sourceFile); + const i = binarySearchKey(children, position, (_, i) => i, (middle, _) => { + // This last callback is more of a selector than a comparator - + // `EqualTo` causes the `middle` result to be returned + // `GreaterThan` causes recursion on the left of the middle + // `LessThan` causes recursion on the right of the middle + + // Let's say you have 3 nodes, spanning positons + // pos: 1, end: 3 + // pos: 3, end: 3 + // pos: 3, end: 5 + // and you're looking for the token at positon 3 - all 3 of these nodes are overlapping with position 3. + // In fact, there's a _good argument_ that node 2 shouldn't even be allowed to exist - depending on if + // the start or end of the ranges are considered inclusive, it's either wholly subsumed by the first or the last node. + // Unfortunately, such nodes do exist. :( - See fourslash/completionsImport_tsx.tsx - empty jsx attributes create + // a zero-length node. + // What also you may not expect is that which node we return depends on the includePrecedingTokenAtEndPosition flag. + // Specifically, if includePrecedingTokenAtEndPosition is set, we return the 1-3 node, while if it's unset, we + // return the 3-5 node. (The zero length node is never correct.) This is because the includePrecedingTokenAtEndPosition + // flag causes us to return the first node whose end position matches the position and which produces and acceptable token + // kind. Meanwhile, if includePrecedingTokenAtEndPosition is unset, we look for the first node whose start is <= the + // position and whose end is greater than the position. + + + const start = allowPositionInLeadingTrivia ? children[middle].getFullStart() : children[middle].getStart(sourceFile, /*includeJsDoc*/ true); if (start > position) { - // If this child begins after position, then all subsequent children will as well. - return false; - } - const end = node.getEnd(); - if (position < end || (position === end && (node.kind === SyntaxKind.EndOfFileToken || includeEndPosition))) { - return true; + return Comparison.GreaterThan; } - else if (includePrecedingTokenAtEndPosition && end === position) { - const previousToken = findPrecedingToken(position, sourceFile, node); - if (previousToken && includePrecedingTokenAtEndPosition(previousToken)) { - foundToken = previousToken; - return true; + + // first element whose start position is before the input and whose end position is after or equal to the input + if (nodeContainsPosition(children[middle])) { + if (children[middle - 1]) { + // we want the _first_ element that contains the position, so left-recur if the prior node also contains the position + if (nodeContainsPosition(children[middle - 1])) { + return Comparison.GreaterThan; + } } + return Comparison.EqualTo; } - return false; - } - } - /** - * Returns the first token where position is in [start, end), - * excluding `JsxText` tokens containing only whitespace. - */ - export function findFirstNonJsxWhitespaceToken(sourceFile: SourceFile, position: number): Node | undefined { - let tokenAtPosition = getTokenAtPosition(sourceFile, position); - while (isWhiteSpaceOnlyJsxText(tokenAtPosition)) { - const nextToken = findNextToken(tokenAtPosition, tokenAtPosition.parent, sourceFile); - if (!nextToken) return; - tokenAtPosition = nextToken; - } - return tokenAtPosition; - } + // this complex condition makes us left-recur around a zero-length node when includePrecedingTokenAtEndPosition is set, rather than right-recur on it + if (includePrecedingTokenAtEndPosition && start === position && children[middle - 1] && children[middle - 1].getEnd() === position && nodeContainsPosition(children[middle - 1])) { + return Comparison.GreaterThan; + } + return Comparison.LessThan; + }); - /** - * The token on the left of the position is the token that strictly includes the position - * or sits to the left of the cursor if it is on a boundary. For example - * - * fo|o -> will return foo - * foo |bar -> will return foo - * - */ - export function findTokenOnLeftOfPosition(file: SourceFile, position: number): Node | undefined { - // Ideally, getTokenAtPosition should return a token. However, it is currently - // broken, so we do a check to make sure the result was indeed a token. - const tokenAtPosition = getTokenAtPosition(file, position); - if (isToken(tokenAtPosition) && position > tokenAtPosition.getStart(file) && position < tokenAtPosition.getEnd()) { - return tokenAtPosition; + if (foundToken) { + return foundToken; + } + if (i >= 0 && children[i]) { + current = children[i]; + continue outer; } - return findPrecedingToken(position, file); + return current; } - export function findNextToken(previousToken: Node, parent: Node, sourceFile: SourceFileLike): Node | undefined { - return find(parent); - - function find(n: Node): Node | undefined { - if (isToken(n) && n.pos === previousToken.end) { - // this is token that starts at the end of previous token - return it - return n; + function nodeContainsPosition(node: Node) { + const start = allowPositionInLeadingTrivia ? node.getFullStart() : node.getStart(sourceFile, /*includeJsDoc*/ true); + if (start > position) { + // If this child begins after position, then all subsequent children will as well. + return false; + } + const end = node.getEnd(); + if (position < end || (position === end && (node.kind === SyntaxKind.EndOfFileToken || includeEndPosition))) { + return true; + } + else if (includePrecedingTokenAtEndPosition && end === position) { + const previousToken = findPrecedingToken(position, sourceFile, node); + if (previousToken && includePrecedingTokenAtEndPosition(previousToken)) { + foundToken = previousToken; + return true; } - return firstDefined(n.getChildren(sourceFile), child => { - const shouldDiveInChildNode = - // previous token is enclosed somewhere in the child - (child.pos <= previousToken.pos && child.end > previousToken.end) || - // previous token ends exactly at the beginning of child - (child.pos === previousToken.end); - return shouldDiveInChildNode && nodeHasTokens(child, sourceFile) ? find(child) : undefined; - }); } + return false; } +} - /** - * Finds the rightmost token satisfying `token.end <= position`, - * excluding `JsxText` tokens containing only whitespace. - */ - export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, excludeJsdoc?: boolean): Node | undefined { - const result = find(startNode || sourceFile); - Debug.assert(!(result && isWhiteSpaceOnlyJsxText(result))); - return result; - - function find(n: Node): Node | undefined { - if (isNonWhitespaceToken(n) && n.kind !== SyntaxKind.EndOfFileToken) { - return n; - } +/** + * Returns the first token where position is in [start, end), + * excluding `JsxText` tokens containing only whitespace. + */ +export function findFirstNonJsxWhitespaceToken(sourceFile: SourceFile, position: number): Node | undefined { + let tokenAtPosition = getTokenAtPosition(sourceFile, position); + while (isWhiteSpaceOnlyJsxText(tokenAtPosition)) { + const nextToken = findNextToken(tokenAtPosition, tokenAtPosition.parent, sourceFile); + if (!nextToken) return; + tokenAtPosition = nextToken; + } + return tokenAtPosition; +} - const children = n.getChildren(sourceFile); - const i = binarySearchKey(children, position, (_, i) => i, (middle, _) => { - // This last callback is more of a selector than a comparator - - // `EqualTo` causes the `middle` result to be returned - // `GreaterThan` causes recursion on the left of the middle - // `LessThan` causes recursion on the right of the middle - if (position < children[middle].end) { - // first element whose end position is greater than the input position - if (!children[middle - 1] || position >= children[middle - 1].end) { - return Comparison.EqualTo; - } - return Comparison.GreaterThan; - } - return Comparison.LessThan; - }); - if (i >= 0 && children[i]) { - const child = children[i]; - // Note that the span of a node's tokens is [node.getStart(...), node.end). - // Given that `position < child.end` and child has constituent tokens, we distinguish these cases: - // 1) `position` precedes `child`'s tokens or `child` has no tokens (ie: in a comment or whitespace preceding `child`): - // we need to find the last token in a previous child. - // 2) `position` is within the same span: we recurse on `child`. - if (position < child.end) { - const start = child.getStart(sourceFile, /*includeJsDoc*/ !excludeJsdoc); - const lookInPreviousChild = - (start >= position) || // cursor in the leading trivia - !nodeHasTokens(child, sourceFile) || - isWhiteSpaceOnlyJsxText(child); - - if (lookInPreviousChild) { - // actual start of the node is past the position - previous token should be at the end of previous child - const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i, sourceFile); - return candidate && findRightmostToken(candidate, sourceFile); - } - else { - // candidate should be in this node - return find(child); - } - } - } +/** + * The token on the left of the position is the token that strictly includes the position + * or sits to the left of the cursor if it is on a boundary. For example + * + * fo|o -> will return foo + * foo |bar -> will return foo + * + */ +export function findTokenOnLeftOfPosition(file: SourceFile, position: number): Node | undefined { + // Ideally, getTokenAtPosition should return a token. However, it is currently + // broken, so we do a check to make sure the result was indeed a token. + const tokenAtPosition = getTokenAtPosition(file, position); + if (isToken(tokenAtPosition) && position > tokenAtPosition.getStart(file) && position < tokenAtPosition.getEnd()) { + return tokenAtPosition; + } - Debug.assert(startNode !== undefined || n.kind === SyntaxKind.SourceFile || n.kind === SyntaxKind.EndOfFileToken || isJSDocCommentContainingNode(n)); + return findPrecedingToken(position, file); +} - // Here we know that none of child token nodes embrace the position, - // the only known case is when position is at the end of the file. - // Try to find the rightmost token in the file without filtering. - // Namely we are skipping the check: 'position < node.end' - const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length, sourceFile); - return candidate && findRightmostToken(candidate, sourceFile); - } - } +export function findNextToken(previousToken: Node, parent: Node, sourceFile: SourceFileLike): Node | undefined { + return find(parent); - function isNonWhitespaceToken(n: Node): boolean { - return isToken(n) && !isWhiteSpaceOnlyJsxText(n); + function find(n: Node): Node | undefined { + if (isToken(n) && n.pos === previousToken.end) { + // this is token that starts at the end of previous token - return it + return n; + } + return firstDefined(n.getChildren(sourceFile), child => { + const shouldDiveInChildNode = + // previous token is enclosed somewhere in the child + (child.pos <= previousToken.pos && child.end > previousToken.end) || + // previous token ends exactly at the beginning of child + (child.pos === previousToken.end); + return shouldDiveInChildNode && nodeHasTokens(child, sourceFile) ? find(child) : undefined; + }); } +} - function findRightmostToken(n: Node, sourceFile: SourceFile): Node | undefined { - if (isNonWhitespaceToken(n)) { +/** + * Finds the rightmost token satisfying `token.end <= position`, + * excluding `JsxText` tokens containing only whitespace. + */ +export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, excludeJsdoc?: boolean): Node | undefined { + const result = find(startNode || sourceFile); + Debug.assert(!(result && isWhiteSpaceOnlyJsxText(result))); + return result; + + function find(n: Node): Node | undefined { + if (isNonWhitespaceToken(n) && n.kind !== SyntaxKind.EndOfFileToken) { return n; } const children = n.getChildren(sourceFile); - if (children.length === 0) { - return n; + const i = binarySearchKey(children, position, (_, i) => i, (middle, _) => { + // This last callback is more of a selector than a comparator - + // `EqualTo` causes the `middle` result to be returned + // `GreaterThan` causes recursion on the left of the middle + // `LessThan` causes recursion on the right of the middle + if (position < children[middle].end) { + // first element whose end position is greater than the input position + if (!children[middle - 1] || position >= children[middle - 1].end) { + return Comparison.EqualTo; + } + return Comparison.GreaterThan; + } + return Comparison.LessThan; + }); + if (i >= 0 && children[i]) { + const child = children[i]; + // Note that the span of a node's tokens is [node.getStart(...), node.end). + // Given that `position < child.end` and child has constituent tokens, we distinguish these cases: + // 1) `position` precedes `child`'s tokens or `child` has no tokens (ie: in a comment or whitespace preceding `child`): + // we need to find the last token in a previous child. + // 2) `position` is within the same span: we recurse on `child`. + if (position < child.end) { + const start = child.getStart(sourceFile, /*includeJsDoc*/ !excludeJsdoc); + const lookInPreviousChild = + (start >= position) || // cursor in the leading trivia + !nodeHasTokens(child, sourceFile) || + isWhiteSpaceOnlyJsxText(child); + + if (lookInPreviousChild) { + // actual start of the node is past the position - previous token should be at the end of previous child + const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i, sourceFile); + return candidate && findRightmostToken(candidate, sourceFile); + } + else { + // candidate should be in this node + return find(child); + } + } } + Debug.assert(startNode !== undefined || n.kind === SyntaxKind.SourceFile || n.kind === SyntaxKind.EndOfFileToken || isJSDocCommentContainingNode(n)); + + // Here we know that none of child token nodes embrace the position, + // the only known case is when position is at the end of the file. + // Try to find the rightmost token in the file without filtering. + // Namely we are skipping the check: 'position < node.end' const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length, sourceFile); return candidate && findRightmostToken(candidate, sourceFile); } +} - /** - * Finds the rightmost child to the left of `children[exclusiveStartPosition]` which is a non-all-whitespace token or has constituent tokens. - */ - function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number, sourceFile: SourceFile): Node | undefined { - for (let i = exclusiveStartPosition - 1; i >= 0; i--) { - const child = children[i]; +function isNonWhitespaceToken(n: Node): boolean { + return isToken(n) && !isWhiteSpaceOnlyJsxText(n); +} - if (isWhiteSpaceOnlyJsxText(child)) { - Debug.assert(i > 0, "`JsxText` tokens should not be the first child of `JsxElement | JsxSelfClosingElement`"); - } - else if (nodeHasTokens(children[i], sourceFile)) { - return children[i]; - } - } +function findRightmostToken(n: Node, sourceFile: SourceFile): Node | undefined { + if (isNonWhitespaceToken(n)) { + return n; } - export function isInString(sourceFile: SourceFile, position: number, previousToken = findPrecedingToken(position, sourceFile)): boolean { - if (previousToken && isStringTextContainingNode(previousToken)) { - const start = previousToken.getStart(sourceFile); - const end = previousToken.getEnd(); - - // To be "in" one of these literals, the position has to be: - // 1. entirely within the token text. - // 2. at the end position of an unterminated token. - // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). - if (start < position && position < end) { - return true; - } - - if (position === end) { - return !!(previousToken as LiteralExpression).isUnterminated; - } - } - - return false; + const children = n.getChildren(sourceFile); + if (children.length === 0) { + return n; } - /** - * returns true if the position is in between the open and close elements of an JSX expression. - */ - export function isInsideJsxElementOrAttribute(sourceFile: SourceFile, position: number) { - const token = getTokenAtPosition(sourceFile, position); + const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length, sourceFile); + return candidate && findRightmostToken(candidate, sourceFile); +} - if (!token) { - return false; - } +/** + * Finds the rightmost child to the left of `children[exclusiveStartPosition]` which is a non-all-whitespace token or has constituent tokens. + */ +function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number, sourceFile: SourceFile): Node | undefined { + for (let i = exclusiveStartPosition - 1; i >= 0; i--) { + const child = children[i]; - if (token.kind === SyntaxKind.JsxText) { - return true; + if (isWhiteSpaceOnlyJsxText(child)) { + Debug.assert(i > 0, "`JsxText` tokens should not be the first child of `JsxElement | JsxSelfClosingElement`"); } - - //
Hello |
- if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxText) { - return true; + else if (nodeHasTokens(children[i], sourceFile)) { + return children[i]; } + } +} - //
{ |
or
- if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxExpression) { - return true; - } +export function isInString(sourceFile: SourceFile, position: number, previousToken = findPrecedingToken(position, sourceFile)): boolean { + if (previousToken && isStringTextContainingNode(previousToken)) { + const start = previousToken.getStart(sourceFile); + const end = previousToken.getEnd(); - //
{ - // | - // } < /div> - if (token && token.kind === SyntaxKind.CloseBraceToken && token.parent.kind === SyntaxKind.JsxExpression) { + // To be "in" one of these literals, the position has to be: + // 1. entirely within the token text. + // 2. at the end position of an unterminated token. + // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). + if (start < position && position < end) { return true; } - //
|
- if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxClosingElement) { - return true; + if (position === end) { + return !!(previousToken as LiteralExpression).isUnterminated; } + } + + return false; +} +/** + * returns true if the position is in between the open and close elements of an JSX expression. + */ +export function isInsideJsxElementOrAttribute(sourceFile: SourceFile, position: number) { + const token = getTokenAtPosition(sourceFile, position); + + if (!token) { return false; } - function isWhiteSpaceOnlyJsxText(node: Node): boolean { - return isJsxText(node) && node.containsOnlyTriviaWhiteSpaces; + if (token.kind === SyntaxKind.JsxText) { + return true; } - export function isInTemplateString(sourceFile: SourceFile, position: number) { - const token = getTokenAtPosition(sourceFile, position); - return isTemplateLiteralKind(token.kind) && position > token.getStart(sourceFile); + //
Hello |
+ if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxText) { + return true; } - export function isInJSXText(sourceFile: SourceFile, position: number) { - const token = getTokenAtPosition(sourceFile, position); - if (isJsxText(token)) { - return true; - } - if (token.kind === SyntaxKind.OpenBraceToken && isJsxExpression(token.parent) && isJsxElement(token.parent.parent)) { - return true; - } - if (token.kind === SyntaxKind.LessThanToken && isJsxOpeningLikeElement(token.parent) && isJsxElement(token.parent.parent)) { - return true; - } - return false; + //
{ |
or
+ if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxExpression) { + return true; } - export function isInsideJsxElement(sourceFile: SourceFile, position: number): boolean { - function isInsideJsxElementTraversal(node: Node): boolean { - while (node) { - if (node.kind >= SyntaxKind.JsxSelfClosingElement && node.kind <= SyntaxKind.JsxExpression - || node.kind === SyntaxKind.JsxText - || node.kind === SyntaxKind.LessThanToken - || node.kind === SyntaxKind.GreaterThanToken - || node.kind === SyntaxKind.Identifier - || node.kind === SyntaxKind.CloseBraceToken - || node.kind === SyntaxKind.OpenBraceToken - || node.kind === SyntaxKind.SlashToken) { - node = node.parent; - } - else if (node.kind === SyntaxKind.JsxElement) { - if (position > node.getStart(sourceFile)) return true; + //
{ + // | + // } < /div> + if (token && token.kind === SyntaxKind.CloseBraceToken && token.parent.kind === SyntaxKind.JsxExpression) { + return true; + } - node = node.parent; - } - else { - return false; - } - } + //
|
+ if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxClosingElement) { + return true; + } - return false; - } + return false; +} + +function isWhiteSpaceOnlyJsxText(node: Node): boolean { + return isJsxText(node) && node.containsOnlyTriviaWhiteSpaces; +} + +export function isInTemplateString(sourceFile: SourceFile, position: number) { + const token = getTokenAtPosition(sourceFile, position); + return isTemplateLiteralKind(token.kind) && position > token.getStart(sourceFile); +} - return isInsideJsxElementTraversal(getTokenAtPosition(sourceFile, position)); +export function isInJSXText(sourceFile: SourceFile, position: number) { + const token = getTokenAtPosition(sourceFile, position); + if (isJsxText(token)) { + return true; } + if (token.kind === SyntaxKind.OpenBraceToken && isJsxExpression(token.parent) && isJsxElement(token.parent.parent)) { + return true; + } + if (token.kind === SyntaxKind.LessThanToken && isJsxOpeningLikeElement(token.parent) && isJsxElement(token.parent.parent)) { + return true; + } + return false; +} - export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind.OpenBraceToken | SyntaxKind.OpenParenToken | SyntaxKind.OpenBracketToken, sourceFile: SourceFile) { - const closeTokenText = tokenToString(token.kind)!; - const matchingTokenText = tokenToString(matchingTokenKind)!; - const tokenFullStart = token.getFullStart(); - // Text-scan based fast path - can be bamboozled by comments and other trivia, but often provides - // a good, fast approximation without too much extra work in the cases where it fails. - const bestGuessIndex = sourceFile.text.lastIndexOf(matchingTokenText, tokenFullStart); - if (bestGuessIndex === -1) { - return undefined; // if the token text doesn't appear in the file, there can't be a match - super fast bail - } - // we can only use the textual result directly if we didn't have to count any close tokens within the range - if (sourceFile.text.lastIndexOf(closeTokenText, tokenFullStart - 1) < bestGuessIndex) { - const nodeAtGuess = findPrecedingToken(bestGuessIndex + 1, sourceFile); - if (nodeAtGuess && nodeAtGuess.kind === matchingTokenKind) { - return nodeAtGuess; - } - } - const tokenKind = token.kind; - let remainingMatchingTokens = 0; - while (true) { - const preceding = findPrecedingToken(token.getFullStart(), sourceFile); - if (!preceding) { - return undefined; +export function isInsideJsxElement(sourceFile: SourceFile, position: number): boolean { + function isInsideJsxElementTraversal(node: Node): boolean { + while (node) { + if (node.kind >= SyntaxKind.JsxSelfClosingElement && node.kind <= SyntaxKind.JsxExpression + || node.kind === SyntaxKind.JsxText + || node.kind === SyntaxKind.LessThanToken + || node.kind === SyntaxKind.GreaterThanToken + || node.kind === SyntaxKind.Identifier + || node.kind === SyntaxKind.CloseBraceToken + || node.kind === SyntaxKind.OpenBraceToken + || node.kind === SyntaxKind.SlashToken) { + node = node.parent; } - token = preceding; - - if (token.kind === matchingTokenKind) { - if (remainingMatchingTokens === 0) { - return token; - } + else if (node.kind === SyntaxKind.JsxElement) { + if (position > node.getStart(sourceFile)) return true; - remainingMatchingTokens--; + node = node.parent; } - else if (token.kind === tokenKind) { - remainingMatchingTokens++; + else { + return false; } } - } - export function removeOptionality(type: Type, isOptionalExpression: boolean, isOptionalChain: boolean) { - return isOptionalExpression ? type.getNonNullableType() : - isOptionalChain ? type.getNonOptionalType() : - type; + return false; } - export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile, checker: TypeChecker): boolean { - const info = getPossibleTypeArgumentsInfo(token, sourceFile); - return info !== undefined && (isPartOfTypeNode(info.called) || - getPossibleGenericSignatures(info.called, info.nTypeArguments, checker).length !== 0 || - isPossiblyTypeArgumentPosition(info.called, sourceFile, checker)); - } + return isInsideJsxElementTraversal(getTokenAtPosition(sourceFile, position)); +} - export function getPossibleGenericSignatures(called: Expression, typeArgumentCount: number, checker: TypeChecker): readonly Signature[] { - let type = checker.getTypeAtLocation(called); - if (isOptionalChain(called.parent)) { - type = removeOptionality(type, isOptionalChainRoot(called.parent), /*isOptionalChain*/ true); +export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind.OpenBraceToken | SyntaxKind.OpenParenToken | SyntaxKind.OpenBracketToken, sourceFile: SourceFile) { + const closeTokenText = tokenToString(token.kind)!; + const matchingTokenText = tokenToString(matchingTokenKind)!; + const tokenFullStart = token.getFullStart(); + // Text-scan based fast path - can be bamboozled by comments and other trivia, but often provides + // a good, fast approximation without too much extra work in the cases where it fails. + const bestGuessIndex = sourceFile.text.lastIndexOf(matchingTokenText, tokenFullStart); + if (bestGuessIndex === -1) { + return undefined; // if the token text doesn't appear in the file, there can't be a match - super fast bail + } + // we can only use the textual result directly if we didn't have to count any close tokens within the range + if (sourceFile.text.lastIndexOf(closeTokenText, tokenFullStart - 1) < bestGuessIndex) { + const nodeAtGuess = findPrecedingToken(bestGuessIndex + 1, sourceFile); + if (nodeAtGuess && nodeAtGuess.kind === matchingTokenKind) { + return nodeAtGuess; + } + } + const tokenKind = token.kind; + let remainingMatchingTokens = 0; + while (true) { + const preceding = findPrecedingToken(token.getFullStart(), sourceFile); + if (!preceding) { + return undefined; } + token = preceding; - const signatures = isNewExpression(called.parent) ? type.getConstructSignatures() : type.getCallSignatures(); - return signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= typeArgumentCount); - } + if (token.kind === matchingTokenKind) { + if (remainingMatchingTokens === 0) { + return token; + } - export interface PossibleTypeArgumentInfo { - readonly called: Identifier; - readonly nTypeArguments: number; + remainingMatchingTokens--; + } + else if (token.kind === tokenKind) { + remainingMatchingTokens++; + } } +} + +export function removeOptionality(type: Type, isOptionalExpression: boolean, isOptionalChain: boolean) { + return isOptionalExpression ? type.getNonNullableType() : + isOptionalChain ? type.getNonOptionalType() : + type; +} + +export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile, checker: TypeChecker): boolean { + const info = getPossibleTypeArgumentsInfo(token, sourceFile); + return info !== undefined && (isPartOfTypeNode(info.called) || + getPossibleGenericSignatures(info.called, info.nTypeArguments, checker).length !== 0 || + isPossiblyTypeArgumentPosition(info.called, sourceFile, checker)); +} - export interface PossibleProgramFileInfo { - ProgramFiles?: string[]; +export function getPossibleGenericSignatures(called: Expression, typeArgumentCount: number, checker: TypeChecker): readonly Signature[] { + let type = checker.getTypeAtLocation(called); + if (isOptionalChain(called.parent)) { + type = removeOptionality(type, isOptionalChainRoot(called.parent), /*isOptionalChain*/ true); } - // Get info for an expression like `f <` that may be the start of type arguments. - export function getPossibleTypeArgumentsInfo(tokenIn: Node | undefined, sourceFile: SourceFile): PossibleTypeArgumentInfo | undefined { - // This is a rare case, but one that saves on a _lot_ of work if true - if the source file has _no_ `<` character, - // then there obviously can't be any type arguments - no expensive brace-matching backwards scanning required + const signatures = isNewExpression(called.parent) ? type.getConstructSignatures() : type.getCallSignatures(); + return signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= typeArgumentCount); +} - if (sourceFile.text.lastIndexOf("<", tokenIn ? tokenIn.pos : sourceFile.text.length) === -1) { - return undefined; - } +export interface PossibleTypeArgumentInfo { + readonly called: Identifier; + readonly nTypeArguments: number; +} - let token: Node | undefined = tokenIn; - // This function determines if the node could be type argument position - // Since during editing, when type argument list is not complete, - // the tree could be of any shape depending on the tokens parsed before current node, - // scanning of the previous identifier followed by "<" before current node would give us better result - // Note that we also balance out the already provided type arguments, arrays, object literals while doing so - let remainingLessThanTokens = 0; - let nTypeArguments = 0; - while (token) { - switch (token.kind) { - case SyntaxKind.LessThanToken: - // Found the beginning of the generic argument expression - token = findPrecedingToken(token.getFullStart(), sourceFile); - if (token && token.kind === SyntaxKind.QuestionDotToken) { - token = findPrecedingToken(token.getFullStart(), sourceFile); - } - if (!token || !isIdentifier(token)) return undefined; - if (!remainingLessThanTokens) { - return isDeclarationName(token) ? undefined : { called: token, nTypeArguments }; - } - remainingLessThanTokens--; - break; +export interface PossibleProgramFileInfo { + ProgramFiles?: string[]; +} - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - remainingLessThanTokens = + 3; - break; +// Get info for an expression like `f <` that may be the start of type arguments. +export function getPossibleTypeArgumentsInfo(tokenIn: Node | undefined, sourceFile: SourceFile): PossibleTypeArgumentInfo | undefined { + // This is a rare case, but one that saves on a _lot_ of work if true - if the source file has _no_ `<` character, + // then there obviously can't be any type arguments - no expensive brace-matching backwards scanning required - case SyntaxKind.GreaterThanGreaterThanToken: - remainingLessThanTokens = + 2; - break; + if (sourceFile.text.lastIndexOf("<", tokenIn ? tokenIn.pos : sourceFile.text.length) === -1) { + return undefined; + } - case SyntaxKind.GreaterThanToken: - remainingLessThanTokens++; - break; + let token: Node | undefined = tokenIn; + // This function determines if the node could be type argument position + // Since during editing, when type argument list is not complete, + // the tree could be of any shape depending on the tokens parsed before current node, + // scanning of the previous identifier followed by "<" before current node would give us better result + // Note that we also balance out the already provided type arguments, arrays, object literals while doing so + let remainingLessThanTokens = 0; + let nTypeArguments = 0; + while (token) { + switch (token.kind) { + case SyntaxKind.LessThanToken: + // Found the beginning of the generic argument expression + token = findPrecedingToken(token.getFullStart(), sourceFile); + if (token && token.kind === SyntaxKind.QuestionDotToken) { + token = findPrecedingToken(token.getFullStart(), sourceFile); + } + if (!token || !isIdentifier(token)) return undefined; + if (!remainingLessThanTokens) { + return isDeclarationName(token) ? undefined : { called: token, nTypeArguments }; + } + remainingLessThanTokens--; + break; - case SyntaxKind.CloseBraceToken: - // This can be object type, skip until we find the matching open brace token - // Skip until the matching open brace token - token = findPrecedingMatchingToken(token, SyntaxKind.OpenBraceToken, sourceFile); - if (!token) return undefined; - break; + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + remainingLessThanTokens = + 3; + break; - case SyntaxKind.CloseParenToken: - // This can be object type, skip until we find the matching open brace token - // Skip until the matching open brace token - token = findPrecedingMatchingToken(token, SyntaxKind.OpenParenToken, sourceFile); - if (!token) return undefined; - break; + case SyntaxKind.GreaterThanGreaterThanToken: + remainingLessThanTokens = + 2; + break; - case SyntaxKind.CloseBracketToken: - // This can be object type, skip until we find the matching open brace token - // Skip until the matching open brace token - token = findPrecedingMatchingToken(token, SyntaxKind.OpenBracketToken, sourceFile); - if (!token) return undefined; - break; + case SyntaxKind.GreaterThanToken: + remainingLessThanTokens++; + break; - // Valid tokens in a type name. Skip. - case SyntaxKind.CommaToken: - nTypeArguments++; - break; + case SyntaxKind.CloseBraceToken: + // This can be object type, skip until we find the matching open brace token + // Skip until the matching open brace token + token = findPrecedingMatchingToken(token, SyntaxKind.OpenBraceToken, sourceFile); + if (!token) return undefined; + break; - case SyntaxKind.EqualsGreaterThanToken: - // falls through - - case SyntaxKind.Identifier: - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - // falls through - - case SyntaxKind.TypeOfKeyword: - case SyntaxKind.ExtendsKeyword: - case SyntaxKind.KeyOfKeyword: - case SyntaxKind.DotToken: - case SyntaxKind.BarToken: - case SyntaxKind.QuestionToken: - case SyntaxKind.ColonToken: - break; + case SyntaxKind.CloseParenToken: + // This can be object type, skip until we find the matching open brace token + // Skip until the matching open brace token + token = findPrecedingMatchingToken(token, SyntaxKind.OpenParenToken, sourceFile); + if (!token) return undefined; + break; - default: - if (isTypeNode(token)) { - break; - } + case SyntaxKind.CloseBracketToken: + // This can be object type, skip until we find the matching open brace token + // Skip until the matching open brace token + token = findPrecedingMatchingToken(token, SyntaxKind.OpenBracketToken, sourceFile); + if (!token) return undefined; + break; - // Invalid token in type - return undefined; - } + // Valid tokens in a type name. Skip. + case SyntaxKind.CommaToken: + nTypeArguments++; + break; - token = findPrecedingToken(token.getFullStart(), sourceFile); - } + case SyntaxKind.EqualsGreaterThanToken: + // falls through - return undefined; - } + case SyntaxKind.Identifier: + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + // falls through - /** - * Returns true if the cursor at position in sourceFile is within a comment. - * - * @param tokenAtPosition Must equal `getTokenAtPosition(sourceFile, position) - * @param predicate Additional predicate to test on the comment range. - */ - export function isInComment(sourceFile: SourceFile, position: number, tokenAtPosition?: Node): CommentRange | undefined { - return formatting.getRangeOfEnclosingComment(sourceFile, position, /*precedingToken*/ undefined, tokenAtPosition); - } + case SyntaxKind.TypeOfKeyword: + case SyntaxKind.ExtendsKeyword: + case SyntaxKind.KeyOfKeyword: + case SyntaxKind.DotToken: + case SyntaxKind.BarToken: + case SyntaxKind.QuestionToken: + case SyntaxKind.ColonToken: + break; - export function hasDocComment(sourceFile: SourceFile, position: number): boolean { - const token = getTokenAtPosition(sourceFile, position); - return !!findAncestor(token, isJSDoc); - } + default: + if (isTypeNode(token)) { + break; + } + + // Invalid token in type + return undefined; + } - function nodeHasTokens(n: Node, sourceFile: SourceFileLike): boolean { - // If we have a token or node that has a non-zero width, it must have tokens. - // Note: getWidth() does not take trivia into account. - return n.kind === SyntaxKind.EndOfFileToken ? !!(n as EndOfFileToken).jsDoc : n.getWidth(sourceFile) !== 0; + token = findPrecedingToken(token.getFullStart(), sourceFile); } - export function getNodeModifiers(node: Node, excludeFlags = ModifierFlags.None): string { - const result: string[] = []; - const flags = isDeclaration(node) - ? getCombinedNodeFlagsAlwaysIncludeJSDoc(node) & ~excludeFlags - : ModifierFlags.None; + return undefined; +} - if (flags & ModifierFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier); - if (flags & ModifierFlags.Protected) result.push(ScriptElementKindModifier.protectedMemberModifier); - if (flags & ModifierFlags.Public) result.push(ScriptElementKindModifier.publicMemberModifier); - if (flags & ModifierFlags.Static || isClassStaticBlockDeclaration(node)) result.push(ScriptElementKindModifier.staticModifier); - if (flags & ModifierFlags.Abstract) result.push(ScriptElementKindModifier.abstractModifier); - if (flags & ModifierFlags.Export) result.push(ScriptElementKindModifier.exportedModifier); - if (flags & ModifierFlags.Deprecated) result.push(ScriptElementKindModifier.deprecatedModifier); - if (node.flags & NodeFlags.Ambient) result.push(ScriptElementKindModifier.ambientModifier); - if (node.kind === SyntaxKind.ExportAssignment) result.push(ScriptElementKindModifier.exportedModifier); +/** + * Returns true if the cursor at position in sourceFile is within a comment. + * + * @param tokenAtPosition Must equal `getTokenAtPosition(sourceFile, position) + * @param predicate Additional predicate to test on the comment range. + */ +export function isInComment(sourceFile: SourceFile, position: number, tokenAtPosition?: Node): CommentRange | undefined { + return formatting.getRangeOfEnclosingComment(sourceFile, position, /*precedingToken*/ undefined, tokenAtPosition); +} - return result.length > 0 ? result.join(",") : ScriptElementKindModifier.none; - } +export function hasDocComment(sourceFile: SourceFile, position: number): boolean { + const token = getTokenAtPosition(sourceFile, position); + return !!findAncestor(token, isJSDoc); +} - export function getTypeArgumentOrTypeParameterList(node: Node): NodeArray | undefined { - if (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.CallExpression) { - return (node as CallExpression).typeArguments; - } +function nodeHasTokens(n: Node, sourceFile: SourceFileLike): boolean { + // If we have a token or node that has a non-zero width, it must have tokens. + // Note: getWidth() does not take trivia into account. + return n.kind === SyntaxKind.EndOfFileToken ? !!(n as EndOfFileToken).jsDoc : n.getWidth(sourceFile) !== 0; +} - if (isFunctionLike(node) || node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.InterfaceDeclaration) { - return (node as FunctionLikeDeclaration).typeParameters; - } +export function getNodeModifiers(node: Node, excludeFlags = ModifierFlags.None): string { + const result: string[] = []; + const flags = isDeclaration(node) + ? getCombinedNodeFlagsAlwaysIncludeJSDoc(node) & ~excludeFlags + : ModifierFlags.None; + + if (flags & ModifierFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier); + if (flags & ModifierFlags.Protected) result.push(ScriptElementKindModifier.protectedMemberModifier); + if (flags & ModifierFlags.Public) result.push(ScriptElementKindModifier.publicMemberModifier); + if (flags & ModifierFlags.Static || isClassStaticBlockDeclaration(node)) result.push(ScriptElementKindModifier.staticModifier); + if (flags & ModifierFlags.Abstract) result.push(ScriptElementKindModifier.abstractModifier); + if (flags & ModifierFlags.Export) result.push(ScriptElementKindModifier.exportedModifier); + if (flags & ModifierFlags.Deprecated) result.push(ScriptElementKindModifier.deprecatedModifier); + if (node.flags & NodeFlags.Ambient) result.push(ScriptElementKindModifier.ambientModifier); + if (node.kind === SyntaxKind.ExportAssignment) result.push(ScriptElementKindModifier.exportedModifier); + + return result.length > 0 ? result.join(",") : ScriptElementKindModifier.none; +} - return undefined; +export function getTypeArgumentOrTypeParameterList(node: Node): NodeArray | undefined { + if (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.CallExpression) { + return (node as CallExpression).typeArguments; } - export function isComment(kind: SyntaxKind): boolean { - return kind === SyntaxKind.SingleLineCommentTrivia || kind === SyntaxKind.MultiLineCommentTrivia; + if (isFunctionLike(node) || node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.InterfaceDeclaration) { + return (node as FunctionLikeDeclaration).typeParameters; } - export function isStringOrRegularExpressionOrTemplateLiteral(kind: SyntaxKind): boolean { - if (kind === SyntaxKind.StringLiteral - || kind === SyntaxKind.RegularExpressionLiteral - || isTemplateLiteralKind(kind)) { - return true; - } - return false; - } + return undefined; +} - export function isPunctuation(kind: SyntaxKind): boolean { - return SyntaxKind.FirstPunctuation <= kind && kind <= SyntaxKind.LastPunctuation; - } +export function isComment(kind: SyntaxKind): boolean { + return kind === SyntaxKind.SingleLineCommentTrivia || kind === SyntaxKind.MultiLineCommentTrivia; +} - export function isInsideTemplateLiteral(node: TemplateLiteralToken, position: number, sourceFile: SourceFile): boolean { - return isTemplateLiteralKind(node.kind) - && (node.getStart(sourceFile) < position && position < node.end) || (!!node.isUnterminated && position === node.end); +export function isStringOrRegularExpressionOrTemplateLiteral(kind: SyntaxKind): boolean { + if (kind === SyntaxKind.StringLiteral + || kind === SyntaxKind.RegularExpressionLiteral + || isTemplateLiteralKind(kind)) { + return true; } + return false; +} - export function isAccessibilityModifier(kind: SyntaxKind) { - switch (kind) { - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - return true; - } +export function isPunctuation(kind: SyntaxKind): boolean { + return SyntaxKind.FirstPunctuation <= kind && kind <= SyntaxKind.LastPunctuation; +} - return false; - } +export function isInsideTemplateLiteral(node: TemplateLiteralToken, position: number, sourceFile: SourceFile): boolean { + return isTemplateLiteralKind(node.kind) + && (node.getStart(sourceFile) < position && position < node.end) || (!!node.isUnterminated && position === node.end); +} - export function cloneCompilerOptions(options: CompilerOptions): CompilerOptions { - const result = clone(options); - setConfigFileInOptions(result, options && options.configFile); - return result; +export function isAccessibilityModifier(kind: SyntaxKind) { + switch (kind) { + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + return true; } - export function isArrayLiteralOrObjectLiteralDestructuringPattern(node: Node) { - if (node.kind === SyntaxKind.ArrayLiteralExpression || - node.kind === SyntaxKind.ObjectLiteralExpression) { - // [a,b,c] from: - // [a, b, c] = someExpression; - if (node.parent.kind === SyntaxKind.BinaryExpression && - (node.parent as BinaryExpression).left === node && - (node.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - return true; - } + return false; +} - // [a, b, c] from: - // for([a, b, c] of expression) - if (node.parent.kind === SyntaxKind.ForOfStatement && - (node.parent as ForOfStatement).initializer === node) { - return true; - } +export function cloneCompilerOptions(options: CompilerOptions): CompilerOptions { + const result = clone(options); + setConfigFileInOptions(result, options && options.configFile); + return result; +} - // [a, b, c] of - // [x, [a, b, c] ] = someExpression - // or - // {x, a: {a, b, c} } = someExpression - if (isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent.kind === SyntaxKind.PropertyAssignment ? node.parent.parent : node.parent)) { - return true; - } +export function isArrayLiteralOrObjectLiteralDestructuringPattern(node: Node) { + if (node.kind === SyntaxKind.ArrayLiteralExpression || + node.kind === SyntaxKind.ObjectLiteralExpression) { + // [a,b,c] from: + // [a, b, c] = someExpression; + if (node.parent.kind === SyntaxKind.BinaryExpression && + (node.parent as BinaryExpression).left === node && + (node.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + return true; } - return false; - } + // [a, b, c] from: + // for([a, b, c] of expression) + if (node.parent.kind === SyntaxKind.ForOfStatement && + (node.parent as ForOfStatement).initializer === node) { + return true; + } - export function isInReferenceComment(sourceFile: SourceFile, position: number): boolean { - return isInReferenceCommentWorker(sourceFile, position, /*shouldBeReference*/ true); + // [a, b, c] of + // [x, [a, b, c] ] = someExpression + // or + // {x, a: {a, b, c} } = someExpression + if (isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent.kind === SyntaxKind.PropertyAssignment ? node.parent.parent : node.parent)) { + return true; + } } - export function isInNonReferenceComment(sourceFile: SourceFile, position: number): boolean { - return isInReferenceCommentWorker(sourceFile, position, /*shouldBeReference*/ false); - } + return false; +} + +export function isInReferenceComment(sourceFile: SourceFile, position: number): boolean { + return isInReferenceCommentWorker(sourceFile, position, /*shouldBeReference*/ true); +} - function isInReferenceCommentWorker(sourceFile: SourceFile, position: number, shouldBeReference: boolean): boolean { - const range = isInComment(sourceFile, position, /*tokenAtPosition*/ undefined); - return !!range && shouldBeReference === tripleSlashDirectivePrefixRegex.test(sourceFile.text.substring(range.pos, range.end)); - } +export function isInNonReferenceComment(sourceFile: SourceFile, position: number): boolean { + return isInReferenceCommentWorker(sourceFile, position, /*shouldBeReference*/ false); +} - export function getReplacementSpanForContextToken(contextToken: Node | undefined) { - if (!contextToken) return undefined; +function isInReferenceCommentWorker(sourceFile: SourceFile, position: number, shouldBeReference: boolean): boolean { + const range = isInComment(sourceFile, position, /*tokenAtPosition*/ undefined); + return !!range && shouldBeReference === tripleSlashDirectivePrefixRegex.test(sourceFile.text.substring(range.pos, range.end)); +} - switch (contextToken.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return createTextSpanFromStringLiteralLikeContent(contextToken as StringLiteralLike); - default: - return createTextSpanFromNode(contextToken); - } - } +export function getReplacementSpanForContextToken(contextToken: Node | undefined) { + if (!contextToken) return undefined; - export function createTextSpanFromNode(node: Node, sourceFile?: SourceFile, endNode?: Node): TextSpan { - return createTextSpanFromBounds(node.getStart(sourceFile), (endNode || node).getEnd()); + switch (contextToken.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return createTextSpanFromStringLiteralLikeContent(contextToken as StringLiteralLike); + default: + return createTextSpanFromNode(contextToken); } +} - export function createTextSpanFromStringLiteralLikeContent(node: StringLiteralLike) { - if (node.isUnterminated) return undefined; - return createTextSpanFromBounds(node.getStart() + 1, node.getEnd() - 1); - } +export function createTextSpanFromNode(node: Node, sourceFile?: SourceFile, endNode?: Node): TextSpan { + return createTextSpanFromBounds(node.getStart(sourceFile), (endNode || node).getEnd()); +} - export function createTextRangeFromNode(node: Node, sourceFile: SourceFile): TextRange { - return createRange(node.getStart(sourceFile), node.end); - } +export function createTextSpanFromStringLiteralLikeContent(node: StringLiteralLike) { + if (node.isUnterminated) return undefined; + return createTextSpanFromBounds(node.getStart() + 1, node.getEnd() - 1); +} - export function createTextSpanFromRange(range: TextRange): TextSpan { - return createTextSpanFromBounds(range.pos, range.end); - } +export function createTextRangeFromNode(node: Node, sourceFile: SourceFile): TextRange { + return createRange(node.getStart(sourceFile), node.end); +} - export function createTextRangeFromSpan(span: TextSpan): TextRange { - return createRange(span.start, span.start + span.length); - } +export function createTextSpanFromRange(range: TextRange): TextSpan { + return createTextSpanFromBounds(range.pos, range.end); +} - export function createTextChangeFromStartLength(start: number, length: number, newText: string): TextChange { - return createTextChange(createTextSpan(start, length), newText); - } +export function createTextRangeFromSpan(span: TextSpan): TextRange { + return createRange(span.start, span.start + span.length); +} - export function createTextChange(span: TextSpan, newText: string): TextChange { - return { span, newText }; - } +export function createTextChangeFromStartLength(start: number, length: number, newText: string): TextChange { + return createTextChange(createTextSpan(start, length), newText); +} - export const typeKeywords: readonly SyntaxKind[] = [ - SyntaxKind.AnyKeyword, - SyntaxKind.AssertsKeyword, - SyntaxKind.BigIntKeyword, - SyntaxKind.BooleanKeyword, - SyntaxKind.FalseKeyword, - SyntaxKind.InferKeyword, - SyntaxKind.KeyOfKeyword, - SyntaxKind.NeverKeyword, - SyntaxKind.NullKeyword, - SyntaxKind.NumberKeyword, - SyntaxKind.ObjectKeyword, - SyntaxKind.ReadonlyKeyword, - SyntaxKind.StringKeyword, - SyntaxKind.SymbolKeyword, - SyntaxKind.TrueKeyword, - SyntaxKind.VoidKeyword, - SyntaxKind.UndefinedKeyword, - SyntaxKind.UniqueKeyword, - SyntaxKind.UnknownKeyword, - ]; +export function createTextChange(span: TextSpan, newText: string): TextChange { + return { span, newText }; +} - export function isTypeKeyword(kind: SyntaxKind): boolean { - return contains(typeKeywords, kind); - } +export const typeKeywords: readonly SyntaxKind[] = [ + SyntaxKind.AnyKeyword, + SyntaxKind.AssertsKeyword, + SyntaxKind.BigIntKeyword, + SyntaxKind.BooleanKeyword, + SyntaxKind.FalseKeyword, + SyntaxKind.InferKeyword, + SyntaxKind.KeyOfKeyword, + SyntaxKind.NeverKeyword, + SyntaxKind.NullKeyword, + SyntaxKind.NumberKeyword, + SyntaxKind.ObjectKeyword, + SyntaxKind.ReadonlyKeyword, + SyntaxKind.StringKeyword, + SyntaxKind.SymbolKeyword, + SyntaxKind.TrueKeyword, + SyntaxKind.VoidKeyword, + SyntaxKind.UndefinedKeyword, + SyntaxKind.UniqueKeyword, + SyntaxKind.UnknownKeyword, +]; + +export function isTypeKeyword(kind: SyntaxKind): boolean { + return contains(typeKeywords, kind); +} - export function isTypeKeywordToken(node: Node): node is Token { - return node.kind === SyntaxKind.TypeKeyword; - } +export function isTypeKeywordToken(node: Node): node is Token { + return node.kind === SyntaxKind.TypeKeyword; +} - export function isTypeKeywordTokenOrIdentifier(node: Node) { - return isTypeKeywordToken(node) || isIdentifier(node) && node.text === "type"; - } +export function isTypeKeywordTokenOrIdentifier(node: Node) { + return isTypeKeywordToken(node) || isIdentifier(node) && node.text === "type"; +} - /** True if the symbol is for an external module, as opposed to a namespace. */ - export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean { - return !!(moduleSymbol.flags & SymbolFlags.Module) && moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote; - } +/** True if the symbol is for an external module, as opposed to a namespace. */ +export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean { + return !!(moduleSymbol.flags & SymbolFlags.Module) && moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote; +} - /** Returns `true` the first time it encounters a node and `false` afterwards. */ - export type NodeSeenTracker = (node: T) => boolean; - export function nodeSeenTracker(): NodeSeenTracker { - const seen: true[] = []; - return node => { - const id = getNodeId(node); - return !seen[id] && (seen[id] = true); - }; - } +/** Returns `true` the first time it encounters a node and `false` afterwards. */ +export type NodeSeenTracker = (node: T) => boolean; +export function nodeSeenTracker(): NodeSeenTracker { + const seen: true[] = []; + return node => { + const id = getNodeId(node); + return !seen[id] && (seen[id] = true); + }; +} - export function getSnapshotText(snap: IScriptSnapshot): string { - return snap.getText(0, snap.getLength()); - } +export function getSnapshotText(snap: IScriptSnapshot): string { + return snap.getText(0, snap.getLength()); +} - export function repeatString(str: string, count: number): string { - let result = ""; - for (let i = 0; i < count; i++) { - result += str; - } - return result; +export function repeatString(str: string, count: number): string { + let result = ""; + for (let i = 0; i < count; i++) { + result += str; } + return result; +} - export function skipConstraint(type: Type): Type { - return type.isTypeParameter() ? type.getConstraint() || type : type; - } +export function skipConstraint(type: Type): Type { + return type.isTypeParameter() ? type.getConstraint() || type : type; +} - export function getNameFromPropertyName(name: PropertyName): string | undefined { - return name.kind === SyntaxKind.ComputedPropertyName - // treat computed property names where expression is string/numeric literal as just string/numeric literal - ? isStringOrNumericLiteralLike(name.expression) ? name.expression.text : undefined - : isPrivateIdentifier(name) ? idText(name) : getTextOfIdentifierOrLiteral(name); - } +export function getNameFromPropertyName(name: PropertyName): string | undefined { + return name.kind === SyntaxKind.ComputedPropertyName + // treat computed property names where expression is string/numeric literal as just string/numeric literal + ? isStringOrNumericLiteralLike(name.expression) ? name.expression.text : undefined + : isPrivateIdentifier(name) ? idText(name) : getTextOfIdentifierOrLiteral(name); +} - export function programContainsModules(program: Program): boolean { - return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!(s.externalModuleIndicator || s.commonJsModuleIndicator)); - } - export function programContainsEsModules(program: Program): boolean { - return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!s.externalModuleIndicator); - } - export function compilerOptionsIndicateEsModules(compilerOptions: CompilerOptions): boolean { - return !!compilerOptions.module || getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015 || !!compilerOptions.noEmit; - } +export function programContainsModules(program: Program): boolean { + return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!(s.externalModuleIndicator || s.commonJsModuleIndicator)); +} +export function programContainsEsModules(program: Program): boolean { + return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!s.externalModuleIndicator); +} +export function compilerOptionsIndicateEsModules(compilerOptions: CompilerOptions): boolean { + return !!compilerOptions.module || getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015 || !!compilerOptions.noEmit; +} - export function createModuleSpecifierResolutionHost(program: Program, host: LanguageServiceHost): ModuleSpecifierResolutionHost { - // Mix in `getSymlinkCache` from Program when host doesn't have it - // in order for non-Project hosts to have a symlinks cache. - return { - fileExists: fileName => program.fileExists(fileName), - getCurrentDirectory: () => host.getCurrentDirectory(), - readFile: maybeBind(host, host.readFile), - useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), - getSymlinkCache: maybeBind(host, host.getSymlinkCache) || program.getSymlinkCache, - getModuleSpecifierCache: maybeBind(host, host.getModuleSpecifierCache), - getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation), - redirectTargetsMap: program.redirectTargetsMap, - getProjectReferenceRedirect: fileName => program.getProjectReferenceRedirect(fileName), - isSourceOfProjectReferenceRedirect: fileName => program.isSourceOfProjectReferenceRedirect(fileName), - getNearestAncestorDirectoryWithPackageJson: maybeBind(host, host.getNearestAncestorDirectoryWithPackageJson), - getFileIncludeReasons: () => program.getFileIncludeReasons(), - }; - } +export function createModuleSpecifierResolutionHost(program: Program, host: LanguageServiceHost): ModuleSpecifierResolutionHost { + // Mix in `getSymlinkCache` from Program when host doesn't have it + // in order for non-Project hosts to have a symlinks cache. + return { + fileExists: fileName => program.fileExists(fileName), + getCurrentDirectory: () => host.getCurrentDirectory(), + readFile: maybeBind(host, host.readFile), + useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), + getSymlinkCache: maybeBind(host, host.getSymlinkCache) || program.getSymlinkCache, + getModuleSpecifierCache: maybeBind(host, host.getModuleSpecifierCache), + getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation), + redirectTargetsMap: program.redirectTargetsMap, + getProjectReferenceRedirect: fileName => program.getProjectReferenceRedirect(fileName), + isSourceOfProjectReferenceRedirect: fileName => program.isSourceOfProjectReferenceRedirect(fileName), + getNearestAncestorDirectoryWithPackageJson: maybeBind(host, host.getNearestAncestorDirectoryWithPackageJson), + getFileIncludeReasons: () => program.getFileIncludeReasons(), + }; +} - export function getModuleSpecifierResolverHost(program: Program, host: LanguageServiceHost): SymbolTracker["moduleResolverHost"] { - return { - ...createModuleSpecifierResolutionHost(program, host), - getCommonSourceDirectory: () => program.getCommonSourceDirectory(), - }; - } +export function getModuleSpecifierResolverHost(program: Program, host: LanguageServiceHost): SymbolTracker["moduleResolverHost"] { + return { + ...createModuleSpecifierResolutionHost(program, host), + getCommonSourceDirectory: () => program.getCommonSourceDirectory(), + }; +} - export function makeImportIfNecessary(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string, quotePreference: QuotePreference): ImportDeclaration | undefined { - return defaultImport || namedImports && namedImports.length ? makeImport(defaultImport, namedImports, moduleSpecifier, quotePreference) : undefined; - } +export function makeImportIfNecessary(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string, quotePreference: QuotePreference): ImportDeclaration | undefined { + return defaultImport || namedImports && namedImports.length ? makeImport(defaultImport, namedImports, moduleSpecifier, quotePreference) : undefined; +} - export function makeImport(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string | Expression, quotePreference: QuotePreference, isTypeOnly?: boolean): ImportDeclaration { - return factory.createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - defaultImport || namedImports - ? factory.createImportClause(!!isTypeOnly, defaultImport, namedImports && namedImports.length ? factory.createNamedImports(namedImports) : undefined) - : undefined, - typeof moduleSpecifier === "string" ? makeStringLiteral(moduleSpecifier, quotePreference) : moduleSpecifier, - /*assertClause*/ undefined); - } +export function makeImport(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string | Expression, quotePreference: QuotePreference, isTypeOnly?: boolean): ImportDeclaration { + return factory.createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + defaultImport || namedImports + ? factory.createImportClause(!!isTypeOnly, defaultImport, namedImports && namedImports.length ? factory.createNamedImports(namedImports) : undefined) + : undefined, + typeof moduleSpecifier === "string" ? makeStringLiteral(moduleSpecifier, quotePreference) : moduleSpecifier, + /*assertClause*/ undefined); +} - export function makeStringLiteral(text: string, quotePreference: QuotePreference): StringLiteral { - return factory.createStringLiteral(text, quotePreference === QuotePreference.Single); - } +export function makeStringLiteral(text: string, quotePreference: QuotePreference): StringLiteral { + return factory.createStringLiteral(text, quotePreference === QuotePreference.Single); +} - export const enum QuotePreference { Single, Double } +export const enum QuotePreference { Single, Double } - export function quotePreferenceFromString(str: StringLiteral, sourceFile: SourceFile): QuotePreference { - return isStringDoubleQuoted(str, sourceFile) ? QuotePreference.Double : QuotePreference.Single; - } +export function quotePreferenceFromString(str: StringLiteral, sourceFile: SourceFile): QuotePreference { + return isStringDoubleQuoted(str, sourceFile) ? QuotePreference.Double : QuotePreference.Single; +} - export function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference { - if (preferences.quotePreference && preferences.quotePreference !== "auto") { - return preferences.quotePreference === "single" ? QuotePreference.Single : QuotePreference.Double; - } - else { - // ignore synthetic import added when importHelpers: true - const firstModuleSpecifier = sourceFile.imports && - find(sourceFile.imports, n => isStringLiteral(n) && !nodeIsSynthesized(n.parent)) as StringLiteral; - return firstModuleSpecifier ? quotePreferenceFromString(firstModuleSpecifier, sourceFile) : QuotePreference.Double; - } +export function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference { + if (preferences.quotePreference && preferences.quotePreference !== "auto") { + return preferences.quotePreference === "single" ? QuotePreference.Single : QuotePreference.Double; } - - export function getQuoteFromPreference(qp: QuotePreference): string { - switch (qp) { - case QuotePreference.Single: return "'"; - case QuotePreference.Double: return '"'; - default: return Debug.assertNever(qp); - } + else { + // ignore synthetic import added when importHelpers: true + const firstModuleSpecifier = sourceFile.imports && + find(sourceFile.imports, n => isStringLiteral(n) && !nodeIsSynthesized(n.parent)) as StringLiteral; + return firstModuleSpecifier ? quotePreferenceFromString(firstModuleSpecifier, sourceFile) : QuotePreference.Double; } +} - export function symbolNameNoDefault(symbol: Symbol): string | undefined { - const escaped = symbolEscapedNameNoDefault(symbol); - return escaped === undefined ? undefined : unescapeLeadingUnderscores(escaped); +export function getQuoteFromPreference(qp: QuotePreference): string { + switch (qp) { + case QuotePreference.Single: return "'"; + case QuotePreference.Double: return '"'; + default: return Debug.assertNever(qp); } +} - export function symbolEscapedNameNoDefault(symbol: Symbol): __String | undefined { - if (symbol.escapedName !== InternalSymbolName.Default) { - return symbol.escapedName; - } +export function symbolNameNoDefault(symbol: Symbol): string | undefined { + const escaped = symbolEscapedNameNoDefault(symbol); + return escaped === undefined ? undefined : unescapeLeadingUnderscores(escaped); +} - return firstDefined(symbol.declarations, decl => { - const name = getNameOfDeclaration(decl); - return name && name.kind === SyntaxKind.Identifier ? name.escapedText : undefined; - }); +export function symbolEscapedNameNoDefault(symbol: Symbol): __String | undefined { + if (symbol.escapedName !== InternalSymbolName.Default) { + return symbol.escapedName; } - export function isModuleSpecifierLike(node: Node): node is StringLiteralLike { - return isStringLiteralLike(node) && ( - isExternalModuleReference(node.parent) || - isImportDeclaration(node.parent) || - isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ false) && node.parent.arguments[0] === node || - isImportCall(node.parent) && node.parent.arguments[0] === node); - } + return firstDefined(symbol.declarations, decl => { + const name = getNameOfDeclaration(decl); + return name && name.kind === SyntaxKind.Identifier ? name.escapedText : undefined; + }); +} - export type ObjectBindingElementWithoutPropertyName = BindingElement & { name: Identifier }; +export function isModuleSpecifierLike(node: Node): node is StringLiteralLike { + return isStringLiteralLike(node) && ( + isExternalModuleReference(node.parent) || + isImportDeclaration(node.parent) || + isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ false) && node.parent.arguments[0] === node || + isImportCall(node.parent) && node.parent.arguments[0] === node); +} - export function isObjectBindingElementWithoutPropertyName(bindingElement: Node): bindingElement is ObjectBindingElementWithoutPropertyName { - return isBindingElement(bindingElement) && - isObjectBindingPattern(bindingElement.parent) && - isIdentifier(bindingElement.name) && - !bindingElement.propertyName; - } +export type ObjectBindingElementWithoutPropertyName = BindingElement & { name: Identifier }; - export function getPropertySymbolFromBindingElement(checker: TypeChecker, bindingElement: ObjectBindingElementWithoutPropertyName): Symbol | undefined { - const typeOfPattern = checker.getTypeAtLocation(bindingElement.parent); - return typeOfPattern && checker.getPropertyOfType(typeOfPattern, bindingElement.name.text); - } +export function isObjectBindingElementWithoutPropertyName(bindingElement: Node): bindingElement is ObjectBindingElementWithoutPropertyName { + return isBindingElement(bindingElement) && + isObjectBindingPattern(bindingElement.parent) && + isIdentifier(bindingElement.name) && + !bindingElement.propertyName; +} - export function getParentNodeInSpan(node: Node | undefined, file: SourceFile, span: TextSpan): Node | undefined { - if (!node) return undefined; +export function getPropertySymbolFromBindingElement(checker: TypeChecker, bindingElement: ObjectBindingElementWithoutPropertyName): Symbol | undefined { + const typeOfPattern = checker.getTypeAtLocation(bindingElement.parent); + return typeOfPattern && checker.getPropertyOfType(typeOfPattern, bindingElement.name.text); +} - while (node.parent) { - if (isSourceFile(node.parent) || !spanContainsNode(span, node.parent, file)) { - return node; - } +export function getParentNodeInSpan(node: Node | undefined, file: SourceFile, span: TextSpan): Node | undefined { + if (!node) return undefined; - node = node.parent; + while (node.parent) { + if (isSourceFile(node.parent) || !spanContainsNode(span, node.parent, file)) { + return node; } - } - function spanContainsNode(span: TextSpan, node: Node, file: SourceFile): boolean { - return textSpanContainsPosition(span, node.getStart(file)) && - node.getEnd() <= textSpanEnd(span); + node = node.parent; } +} - export function findModifier(node: Node, kind: Modifier["kind"]): Modifier | undefined { - return node.modifiers && find(node.modifiers, m => m.kind === kind); - } +function spanContainsNode(span: TextSpan, node: Node, file: SourceFile): boolean { + return textSpanContainsPosition(span, node.getStart(file)) && + node.getEnd() <= textSpanEnd(span); +} - export function insertImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, imports: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[], blankLineBetween: boolean): void { - const decl = isArray(imports) ? imports[0] : imports; - const importKindPredicate: (node: Node) => node is AnyImportOrRequireStatement = decl.kind === SyntaxKind.VariableStatement ? isRequireVariableStatement : isAnyImportSyntax; - const existingImportStatements = filter(sourceFile.statements, importKindPredicate); - const sortedNewImports = isArray(imports) ? stableSort(imports, OrganizeImports.compareImportsOrRequireStatements) : [imports]; - if (!existingImportStatements.length) { - changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween); - } - else if (existingImportStatements && OrganizeImports.importsAreSorted(existingImportStatements)) { - for (const newImport of sortedNewImports) { - const insertionIndex = OrganizeImports.getImportDeclarationInsertionIndex(existingImportStatements, newImport); - if (insertionIndex === 0) { - // If the first import is top-of-file, insert after the leading comment which is likely the header. - const options = existingImportStatements[0] === sourceFile.statements[0] ? - { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude } : {}; - changes.insertNodeBefore(sourceFile, existingImportStatements[0], newImport, /*blankLineBetween*/ false, options); - } - else { - const prevImport = existingImportStatements[insertionIndex - 1]; - changes.insertNodeAfter(sourceFile, prevImport, newImport); - } - } - } - else { - const lastExistingImport = lastOrUndefined(existingImportStatements); - if (lastExistingImport) { - changes.insertNodesAfter(sourceFile, lastExistingImport, sortedNewImports); +export function findModifier(node: Node, kind: Modifier["kind"]): Modifier | undefined { + return node.modifiers && find(node.modifiers, m => m.kind === kind); +} + +export function insertImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, imports: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[], blankLineBetween: boolean): void { + const decl = isArray(imports) ? imports[0] : imports; + const importKindPredicate: (node: Node) => node is AnyImportOrRequireStatement = decl.kind === SyntaxKind.VariableStatement ? isRequireVariableStatement : isAnyImportSyntax; + const existingImportStatements = filter(sourceFile.statements, importKindPredicate); + const sortedNewImports = isArray(imports) ? stableSort(imports, OrganizeImports.compareImportsOrRequireStatements) : [imports]; + if (!existingImportStatements.length) { + changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween); + } + else if (existingImportStatements && OrganizeImports.importsAreSorted(existingImportStatements)) { + for (const newImport of sortedNewImports) { + const insertionIndex = OrganizeImports.getImportDeclarationInsertionIndex(existingImportStatements, newImport); + if (insertionIndex === 0) { + // If the first import is top-of-file, insert after the leading comment which is likely the header. + const options = existingImportStatements[0] === sourceFile.statements[0] ? + { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude } : {}; + changes.insertNodeBefore(sourceFile, existingImportStatements[0], newImport, /*blankLineBetween*/ false, options); } else { - changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween); + const prevImport = existingImportStatements[insertionIndex - 1]; + changes.insertNodeAfter(sourceFile, prevImport, newImport); } } } - - export function getTypeKeywordOfTypeOnlyImport(importClause: ImportClause, sourceFile: SourceFile): Token { - Debug.assert(importClause.isTypeOnly); - return cast(importClause.getChildAt(0, sourceFile), isTypeKeywordToken); + else { + const lastExistingImport = lastOrUndefined(existingImportStatements); + if (lastExistingImport) { + changes.insertNodesAfter(sourceFile, lastExistingImport, sortedNewImports); + } + else { + changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween); + } } +} - export function textSpansEqual(a: TextSpan | undefined, b: TextSpan | undefined): boolean { - return !!a && !!b && a.start === b.start && a.length === b.length; - } - export function documentSpansEqual(a: DocumentSpan, b: DocumentSpan): boolean { - return a.fileName === b.fileName && textSpansEqual(a.textSpan, b.textSpan); - } +export function getTypeKeywordOfTypeOnlyImport(importClause: ImportClause, sourceFile: SourceFile): Token { + Debug.assert(importClause.isTypeOnly); + return cast(importClause.getChildAt(0, sourceFile), isTypeKeywordToken); +} - /** - * Iterates through 'array' by index and performs the callback on each element of array until the callback - * returns a truthy value, then returns that value. - * If no such value is found, the callback is applied to each element of array and undefined is returned. - */ - export function forEachUnique(array: readonly T[] | undefined, callback: (element: T, index: number) => U): U | undefined { - if (array) { - for (let i = 0; i < array.length; i++) { - if (array.indexOf(array[i]) === i) { - const result = callback(array[i], i); - if (result) { - return result; - } +export function textSpansEqual(a: TextSpan | undefined, b: TextSpan | undefined): boolean { + return !!a && !!b && a.start === b.start && a.length === b.length; +} +export function documentSpansEqual(a: DocumentSpan, b: DocumentSpan): boolean { + return a.fileName === b.fileName && textSpansEqual(a.textSpan, b.textSpan); +} + +/** + * Iterates through 'array' by index and performs the callback on each element of array until the callback + * returns a truthy value, then returns that value. + * If no such value is found, the callback is applied to each element of array and undefined is returned. + */ +export function forEachUnique(array: readonly T[] | undefined, callback: (element: T, index: number) => U): U | undefined { + if (array) { + for (let i = 0; i < array.length; i++) { + if (array.indexOf(array[i]) === i) { + const result = callback(array[i], i); + if (result) { + return result; } } } - return undefined; } + return undefined; +} - export function isTextWhiteSpaceLike(text: string, startPos: number, endPos: number): boolean { - for (let i = startPos; i < endPos; i++) { - if (!isWhiteSpaceLike(text.charCodeAt(i))) { - return false; - } +export function isTextWhiteSpaceLike(text: string, startPos: number, endPos: number): boolean { + for (let i = startPos; i < endPos; i++) { + if (!isWhiteSpaceLike(text.charCodeAt(i))) { + return false; } - - return true; } - // #endregion - - // Display-part writer helpers - // #region - export function isFirstDeclarationOfSymbolParameter(symbol: Symbol) { - const declaration = symbol.declarations ? firstOrUndefined(symbol.declarations) : undefined; - return !!findAncestor(declaration, n => - isParameter(n) ? true : isBindingElement(n) || isObjectBindingPattern(n) || isArrayBindingPattern(n) ? false : "quit"); - } - - const displayPartWriter = getDisplayPartWriter(); - function getDisplayPartWriter(): DisplayPartsSymbolWriter { - const absoluteMaximumLength = defaultMaximumTruncationLength * 10; // A hard cutoff to avoid overloading the messaging channel in worst-case scenarios - let displayParts: SymbolDisplayPart[]; - let lineStart: boolean; - let indent: number; - let length: number; - - resetWriter(); - const unknownWrite = (text: string) => writeKind(text, SymbolDisplayPartKind.text); - return { - displayParts: () => { - const finalText = displayParts.length && displayParts[displayParts.length - 1].text; - if (length > absoluteMaximumLength && finalText && finalText !== "...") { - if (!isWhiteSpaceLike(finalText.charCodeAt(finalText.length - 1))) { - displayParts.push(displayPart(" ", SymbolDisplayPartKind.space)); - } - displayParts.push(displayPart("...", SymbolDisplayPartKind.punctuation)); - } - return displayParts; - }, - writeKeyword: text => writeKind(text, SymbolDisplayPartKind.keyword), - writeOperator: text => writeKind(text, SymbolDisplayPartKind.operator), - writePunctuation: text => writeKind(text, SymbolDisplayPartKind.punctuation), - writeTrailingSemicolon: text => writeKind(text, SymbolDisplayPartKind.punctuation), - writeSpace: text => writeKind(text, SymbolDisplayPartKind.space), - writeStringLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), - writeParameter: text => writeKind(text, SymbolDisplayPartKind.parameterName), - writeProperty: text => writeKind(text, SymbolDisplayPartKind.propertyName), - writeLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), - writeSymbol, - writeLine, - write: unknownWrite, - writeComment: unknownWrite, - getText: () => "", - getTextPos: () => 0, - getColumn: () => 0, - getLine: () => 0, - isAtStartOfLine: () => false, - hasTrailingWhitespace: () => false, - hasTrailingComment: () => false, - rawWrite: notImplemented, - getIndent: () => indent, - increaseIndent: () => { indent++; }, - decreaseIndent: () => { indent--; }, - clear: resetWriter, - trackSymbol: () => false, - reportInaccessibleThisError: noop, - reportInaccessibleUniqueSymbolError: noop, - reportPrivateInBaseOfClassExpression: noop, - }; - - function writeIndent() { - if (length > absoluteMaximumLength) return; - if (lineStart) { - const indentString = getIndentString(indent); - if (indentString) { - length += indentString.length; - displayParts.push(displayPart(indentString, SymbolDisplayPartKind.space)); - } - lineStart = false; - } - } - - function writeKind(text: string, kind: SymbolDisplayPartKind) { - if (length > absoluteMaximumLength) return; - writeIndent(); - length += text.length; - displayParts.push(displayPart(text, kind)); - } + return true; +} - function writeSymbol(text: string, symbol: Symbol) { - if (length > absoluteMaximumLength) return; - writeIndent(); - length += text.length; - displayParts.push(symbolPart(text, symbol)); - } +// #endregion - function writeLine() { - if (length > absoluteMaximumLength) return; - length += 1; - displayParts.push(lineBreakPart()); - lineStart = true; - } +// Display-part writer helpers +// #region +export function isFirstDeclarationOfSymbolParameter(symbol: Symbol) { + const declaration = symbol.declarations ? firstOrUndefined(symbol.declarations) : undefined; + return !!findAncestor(declaration, n => + isParameter(n) ? true : isBindingElement(n) || isObjectBindingPattern(n) || isArrayBindingPattern(n) ? false : "quit"); +} - function resetWriter() { - displayParts = []; - lineStart = true; - indent = 0; - length = 0; - } +const displayPartWriter = getDisplayPartWriter(); +function getDisplayPartWriter(): DisplayPartsSymbolWriter { + const absoluteMaximumLength = defaultMaximumTruncationLength * 10; // A hard cutoff to avoid overloading the messaging channel in worst-case scenarios + let displayParts: SymbolDisplayPart[]; + let lineStart: boolean; + let indent: number; + let length: number; + + resetWriter(); + const unknownWrite = (text: string) => writeKind(text, SymbolDisplayPartKind.text); + return { + displayParts: () => { + const finalText = displayParts.length && displayParts[displayParts.length - 1].text; + if (length > absoluteMaximumLength && finalText && finalText !== "...") { + if (!isWhiteSpaceLike(finalText.charCodeAt(finalText.length - 1))) { + displayParts.push(displayPart(" ", SymbolDisplayPartKind.space)); + } + displayParts.push(displayPart("...", SymbolDisplayPartKind.punctuation)); + } + return displayParts; + }, + writeKeyword: text => writeKind(text, SymbolDisplayPartKind.keyword), + writeOperator: text => writeKind(text, SymbolDisplayPartKind.operator), + writePunctuation: text => writeKind(text, SymbolDisplayPartKind.punctuation), + writeTrailingSemicolon: text => writeKind(text, SymbolDisplayPartKind.punctuation), + writeSpace: text => writeKind(text, SymbolDisplayPartKind.space), + writeStringLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), + writeParameter: text => writeKind(text, SymbolDisplayPartKind.parameterName), + writeProperty: text => writeKind(text, SymbolDisplayPartKind.propertyName), + writeLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), + writeSymbol, + writeLine, + write: unknownWrite, + writeComment: unknownWrite, + getText: () => "", + getTextPos: () => 0, + getColumn: () => 0, + getLine: () => 0, + isAtStartOfLine: () => false, + hasTrailingWhitespace: () => false, + hasTrailingComment: () => false, + rawWrite: notImplemented, + getIndent: () => indent, + increaseIndent: () => { indent++; }, + decreaseIndent: () => { indent--; }, + clear: resetWriter, + trackSymbol: () => false, + reportInaccessibleThisError: noop, + reportInaccessibleUniqueSymbolError: noop, + reportPrivateInBaseOfClassExpression: noop, + }; + + function writeIndent() { + if (length > absoluteMaximumLength) return; + if (lineStart) { + const indentString = getIndentString(indent); + if (indentString) { + length += indentString.length; + displayParts.push(displayPart(indentString, SymbolDisplayPartKind.space)); + } + lineStart = false; + } + } + + function writeKind(text: string, kind: SymbolDisplayPartKind) { + if (length > absoluteMaximumLength) return; + writeIndent(); + length += text.length; + displayParts.push(displayPart(text, kind)); + } + + function writeSymbol(text: string, symbol: Symbol) { + if (length > absoluteMaximumLength) return; + writeIndent(); + length += text.length; + displayParts.push(symbolPart(text, symbol)); + } + + function writeLine() { + if (length > absoluteMaximumLength) return; + length += 1; + displayParts.push(lineBreakPart()); + lineStart = true; + } + + function resetWriter() { + displayParts = []; + lineStart = true; + indent = 0; + length = 0; } +} - export function symbolPart(text: string, symbol: Symbol) { - return displayPart(text, displayPartKind(symbol)); - - function displayPartKind(symbol: Symbol): SymbolDisplayPartKind { - const flags = symbol.flags; +export function symbolPart(text: string, symbol: Symbol) { + return displayPart(text, displayPartKind(symbol)); - if (flags & SymbolFlags.Variable) { - return isFirstDeclarationOfSymbolParameter(symbol) ? SymbolDisplayPartKind.parameterName : SymbolDisplayPartKind.localName; - } - if (flags & SymbolFlags.Property) return SymbolDisplayPartKind.propertyName; - if (flags & SymbolFlags.GetAccessor) return SymbolDisplayPartKind.propertyName; - if (flags & SymbolFlags.SetAccessor) return SymbolDisplayPartKind.propertyName; - if (flags & SymbolFlags.EnumMember) return SymbolDisplayPartKind.enumMemberName; - if (flags & SymbolFlags.Function) return SymbolDisplayPartKind.functionName; - if (flags & SymbolFlags.Class) return SymbolDisplayPartKind.className; - if (flags & SymbolFlags.Interface) return SymbolDisplayPartKind.interfaceName; - if (flags & SymbolFlags.Enum) return SymbolDisplayPartKind.enumName; - if (flags & SymbolFlags.Module) return SymbolDisplayPartKind.moduleName; - if (flags & SymbolFlags.Method) return SymbolDisplayPartKind.methodName; - if (flags & SymbolFlags.TypeParameter) return SymbolDisplayPartKind.typeParameterName; - if (flags & SymbolFlags.TypeAlias) return SymbolDisplayPartKind.aliasName; - if (flags & SymbolFlags.Alias) return SymbolDisplayPartKind.aliasName; + function displayPartKind(symbol: Symbol): SymbolDisplayPartKind { + const flags = symbol.flags; - return SymbolDisplayPartKind.text; + if (flags & SymbolFlags.Variable) { + return isFirstDeclarationOfSymbolParameter(symbol) ? SymbolDisplayPartKind.parameterName : SymbolDisplayPartKind.localName; } - } + if (flags & SymbolFlags.Property) return SymbolDisplayPartKind.propertyName; + if (flags & SymbolFlags.GetAccessor) return SymbolDisplayPartKind.propertyName; + if (flags & SymbolFlags.SetAccessor) return SymbolDisplayPartKind.propertyName; + if (flags & SymbolFlags.EnumMember) return SymbolDisplayPartKind.enumMemberName; + if (flags & SymbolFlags.Function) return SymbolDisplayPartKind.functionName; + if (flags & SymbolFlags.Class) return SymbolDisplayPartKind.className; + if (flags & SymbolFlags.Interface) return SymbolDisplayPartKind.interfaceName; + if (flags & SymbolFlags.Enum) return SymbolDisplayPartKind.enumName; + if (flags & SymbolFlags.Module) return SymbolDisplayPartKind.moduleName; + if (flags & SymbolFlags.Method) return SymbolDisplayPartKind.methodName; + if (flags & SymbolFlags.TypeParameter) return SymbolDisplayPartKind.typeParameterName; + if (flags & SymbolFlags.TypeAlias) return SymbolDisplayPartKind.aliasName; + if (flags & SymbolFlags.Alias) return SymbolDisplayPartKind.aliasName; - export function displayPart(text: string, kind: SymbolDisplayPartKind): SymbolDisplayPart { - return { text, kind: SymbolDisplayPartKind[kind] }; + return SymbolDisplayPartKind.text; } +} - export function spacePart() { - return displayPart(" ", SymbolDisplayPartKind.space); - } +export function displayPart(text: string, kind: SymbolDisplayPartKind): SymbolDisplayPart { + return { text, kind: SymbolDisplayPartKind[kind] }; +} - export function keywordPart(kind: SyntaxKind) { - return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.keyword); - } +export function spacePart() { + return displayPart(" ", SymbolDisplayPartKind.space); +} - export function punctuationPart(kind: SyntaxKind) { - return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.punctuation); - } +export function keywordPart(kind: SyntaxKind) { + return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.keyword); +} - export function operatorPart(kind: SyntaxKind) { - return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.operator); - } +export function punctuationPart(kind: SyntaxKind) { + return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.punctuation); +} - export function parameterNamePart(text: string) { - return displayPart(text, SymbolDisplayPartKind.parameterName); - } +export function operatorPart(kind: SyntaxKind) { + return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.operator); +} - export function propertyNamePart(text: string) { - return displayPart(text, SymbolDisplayPartKind.propertyName); - } +export function parameterNamePart(text: string) { + return displayPart(text, SymbolDisplayPartKind.parameterName); +} - export function textOrKeywordPart(text: string) { - const kind = stringToToken(text); - return kind === undefined - ? textPart(text) - : keywordPart(kind); - } +export function propertyNamePart(text: string) { + return displayPart(text, SymbolDisplayPartKind.propertyName); +} - export function textPart(text: string) { - return displayPart(text, SymbolDisplayPartKind.text); - } +export function textOrKeywordPart(text: string) { + const kind = stringToToken(text); + return kind === undefined + ? textPart(text) + : keywordPart(kind); +} - export function typeAliasNamePart(text: string) { - return displayPart(text, SymbolDisplayPartKind.aliasName); - } +export function textPart(text: string) { + return displayPart(text, SymbolDisplayPartKind.text); +} - export function typeParameterNamePart(text: string) { - return displayPart(text, SymbolDisplayPartKind.typeParameterName); - } +export function typeAliasNamePart(text: string) { + return displayPart(text, SymbolDisplayPartKind.aliasName); +} - export function linkTextPart(text: string) { - return displayPart(text, SymbolDisplayPartKind.linkText); - } +export function typeParameterNamePart(text: string) { + return displayPart(text, SymbolDisplayPartKind.typeParameterName); +} - export function linkNamePart(text: string, target: Declaration): JSDocLinkDisplayPart { - return { - text, - kind: SymbolDisplayPartKind[SymbolDisplayPartKind.linkName], - target: { - fileName: getSourceFileOfNode(target).fileName, - textSpan: createTextSpanFromNode(target), - }, - }; - } +export function linkTextPart(text: string) { + return displayPart(text, SymbolDisplayPartKind.linkText); +} - export function linkPart(text: string) { - return displayPart(text, SymbolDisplayPartKind.link); - } +export function linkNamePart(text: string, target: Declaration): JSDocLinkDisplayPart { + return { + text, + kind: SymbolDisplayPartKind[SymbolDisplayPartKind.linkName], + target: { + fileName: getSourceFileOfNode(target).fileName, + textSpan: createTextSpanFromNode(target), + }, + }; +} - export function buildLinkParts(link: JSDocLink | JSDocLinkCode | JSDocLinkPlain, checker?: TypeChecker): SymbolDisplayPart[] { - const prefix = isJSDocLink(link) ? "link" - : isJSDocLinkCode(link) ? "linkcode" - : "linkplain"; - const parts = [linkPart(`{@${prefix} `)]; - if (!link.name) { - if (link.text) parts.push(linkTextPart(link.text)); +export function linkPart(text: string) { + return displayPart(text, SymbolDisplayPartKind.link); +} + +export function buildLinkParts(link: JSDocLink | JSDocLinkCode | JSDocLinkPlain, checker?: TypeChecker): SymbolDisplayPart[] { + const prefix = isJSDocLink(link) ? "link" + : isJSDocLinkCode(link) ? "linkcode" + : "linkplain"; + const parts = [linkPart(`{@${prefix} `)]; + if (!link.name) { + if (link.text) parts.push(linkTextPart(link.text)); + } + else { + const symbol = checker?.getSymbolAtLocation(link.name); + const suffix = findLinkNameEnd(link.text); + const name = getTextOfNode(link.name) + link.text.slice(0, suffix); + const text = link.text.slice(suffix); + const decl = symbol?.valueDeclaration || symbol?.declarations?.[0]; + if (decl) { + parts.push(linkNamePart(name, decl)); + if (text) parts.push(linkTextPart(text)); } else { - const symbol = checker?.getSymbolAtLocation(link.name); - const suffix = findLinkNameEnd(link.text); - const name = getTextOfNode(link.name) + link.text.slice(0, suffix); - const text = link.text.slice(suffix); - const decl = symbol?.valueDeclaration || symbol?.declarations?.[0]; - if (decl) { - parts.push(linkNamePart(name, decl)); - if (text) parts.push(linkTextPart(text)); - } - else { - parts.push(linkTextPart(name + (suffix ? "" : " ") + text)); - } + parts.push(linkTextPart(name + (suffix ? "" : " ") + text)); } - parts.push(linkPart("}")); - return parts; } + parts.push(linkPart("}")); + return parts; +} - function findLinkNameEnd(text: string) { - if (text.indexOf("()") === 0) return 2; - if (text[0] !== "<") return 0; - let brackets = 0; - let i = 0; - while (i < text.length) { - if (text[i] === "<") brackets++; - if (text[i] === ">") brackets--; - i++; - if (!brackets) return i; - } - return 0; - } +function findLinkNameEnd(text: string) { + if (text.indexOf("()") === 0) return 2; + if (text[0] !== "<") return 0; + let brackets = 0; + let i = 0; + while (i < text.length) { + if (text[i] === "<") brackets++; + if (text[i] === ">") brackets--; + i++; + if (!brackets) return i; + } + return 0; +} - const carriageReturnLineFeed = "\r\n"; - /** - * The default is CRLF. - */ - export function getNewLineOrDefaultFromHost(host: FormattingHost, formatSettings?: FormatCodeSettings) { - return formatSettings?.newLineCharacter || - host.getNewLine?.() || - carriageReturnLineFeed; - } +const carriageReturnLineFeed = "\r\n"; +/** + * The default is CRLF. + */ +export function getNewLineOrDefaultFromHost(host: FormattingHost, formatSettings?: FormatCodeSettings) { + return formatSettings?.newLineCharacter || + host.getNewLine?.() || + carriageReturnLineFeed; +} + +export function lineBreakPart() { + return displayPart("\n", SymbolDisplayPartKind.lineBreak); +} - export function lineBreakPart() { - return displayPart("\n", SymbolDisplayPartKind.lineBreak); +export function mapToDisplayParts(writeDisplayParts: (writer: DisplayPartsSymbolWriter) => void): SymbolDisplayPart[] { + try { + writeDisplayParts(displayPartWriter); + return displayPartWriter.displayParts(); + } + finally { + displayPartWriter.clear(); } +} + +export function typeToDisplayParts(typechecker: TypeChecker, type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.None): SymbolDisplayPart[] { + return mapToDisplayParts(writer => { + typechecker.writeType(type, enclosingDeclaration, flags | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer); + }); +} + +export function symbolToDisplayParts(typeChecker: TypeChecker, symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.None): SymbolDisplayPart[] { + return mapToDisplayParts(writer => { + typeChecker.writeSymbol(symbol, enclosingDeclaration, meaning, flags | SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope, writer); + }); +} + +export function signatureToDisplayParts(typechecker: TypeChecker, signature: Signature, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.None): SymbolDisplayPart[] { + flags |= TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.WriteTypeArgumentsOfSignature | TypeFormatFlags.OmitParameterModifiers; + return mapToDisplayParts(writer => { + typechecker.writeSignature(signature, enclosingDeclaration, flags, /*signatureKind*/ undefined, writer); + }); +} + +export function isImportOrExportSpecifierName(location: Node): location is Identifier { + return !!location.parent && isImportOrExportSpecifier(location.parent) && location.parent.propertyName === location; +} + +export function getScriptKind(fileName: string, host: LanguageServiceHost): ScriptKind { + // First check to see if the script kind was specified by the host. Chances are the host + // may override the default script kind for the file extension. + return ensureScriptKind(fileName, host.getScriptKind && host.getScriptKind(fileName)); +} - export function mapToDisplayParts(writeDisplayParts: (writer: DisplayPartsSymbolWriter) => void): SymbolDisplayPart[] { - try { - writeDisplayParts(displayPartWriter); - return displayPartWriter.displayParts(); +export function getSymbolTarget(symbol: Symbol, checker: TypeChecker): Symbol { + let next: Symbol = symbol; + while (isAliasSymbol(next) || (isTransientSymbol(next) && next.target)) { + if (isTransientSymbol(next) && next.target) { + next = next.target; } - finally { - displayPartWriter.clear(); + else { + next = skipAlias(next, checker); } } + return next; +} - export function typeToDisplayParts(typechecker: TypeChecker, type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.None): SymbolDisplayPart[] { - return mapToDisplayParts(writer => { - typechecker.writeType(type, enclosingDeclaration, flags | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer); - }); - } +function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol { + return (symbol.flags & SymbolFlags.Transient) !== 0; +} - export function symbolToDisplayParts(typeChecker: TypeChecker, symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.None): SymbolDisplayPart[] { - return mapToDisplayParts(writer => { - typeChecker.writeSymbol(symbol, enclosingDeclaration, meaning, flags | SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope, writer); - }); - } +function isAliasSymbol(symbol: Symbol): boolean { + return (symbol.flags & SymbolFlags.Alias) !== 0; +} - export function signatureToDisplayParts(typechecker: TypeChecker, signature: Signature, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.None): SymbolDisplayPart[] { - flags |= TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.WriteTypeArgumentsOfSignature | TypeFormatFlags.OmitParameterModifiers; - return mapToDisplayParts(writer => { - typechecker.writeSignature(signature, enclosingDeclaration, flags, /*signatureKind*/ undefined, writer); - }); - } +export function getUniqueSymbolId(symbol: Symbol, checker: TypeChecker) { + return getSymbolId(skipAlias(symbol, checker)); +} - export function isImportOrExportSpecifierName(location: Node): location is Identifier { - return !!location.parent && isImportOrExportSpecifier(location.parent) && location.parent.propertyName === location; +export function getFirstNonSpaceCharacterPosition(text: string, position: number) { + while (isWhiteSpaceLike(text.charCodeAt(position))) { + position += 1; } + return position; +} - export function getScriptKind(fileName: string, host: LanguageServiceHost): ScriptKind { - // First check to see if the script kind was specified by the host. Chances are the host - // may override the default script kind for the file extension. - return ensureScriptKind(fileName, host.getScriptKind && host.getScriptKind(fileName)); +export function getPrecedingNonSpaceCharacterPosition(text: string, position: number) { + while (position > -1 && isWhiteSpaceSingleLine(text.charCodeAt(position))) { + position -= 1; } + return position + 1; +} - export function getSymbolTarget(symbol: Symbol, checker: TypeChecker): Symbol { - let next: Symbol = symbol; - while (isAliasSymbol(next) || (isTransientSymbol(next) && next.target)) { - if (isTransientSymbol(next) && next.target) { - next = next.target; - } - else { - next = skipAlias(next, checker); - } - } - return next; - } +/** + * Creates a deep, memberwise clone of a node with no source map location. + * + * WARNING: This is an expensive operation and is only intended to be used in refactorings + * and code fixes (because those are triggered by explicit user actions). + */ +export function getSynthesizedDeepClone(node: T, includeTrivia = true): T { + const clone = node && getSynthesizedDeepCloneWorker(node as NonNullable); + if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); + return clone; +} - function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol { - return (symbol.flags & SymbolFlags.Transient) !== 0; +export function getSynthesizedDeepCloneWithReplacements( + node: T, + includeTrivia: boolean, + replaceNode: (node: Node) => Node | undefined +): T { + let clone = replaceNode(node); + if (clone) { + setOriginalNode(clone, node); } - - function isAliasSymbol(symbol: Symbol): boolean { - return (symbol.flags & SymbolFlags.Alias) !== 0; + else { + clone = getSynthesizedDeepCloneWorker(node as NonNullable, replaceNode); } - export function getUniqueSymbolId(symbol: Symbol, checker: TypeChecker) { - return getSymbolId(skipAlias(symbol, checker)); - } + if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); + return clone as T; +} - export function getFirstNonSpaceCharacterPosition(text: string, position: number) { - while (isWhiteSpaceLike(text.charCodeAt(position))) { - position += 1; - } - return position; +function getSynthesizedDeepCloneWorker(node: T, replaceNode?: (node: Node) => Node | undefined): T { + const nodeClone: (n: T) => T = replaceNode + ? n => getSynthesizedDeepCloneWithReplacements(n, /*includeTrivia*/ true, replaceNode) + : getSynthesizedDeepClone; + const nodesClone: (ns: NodeArray) => NodeArray = replaceNode + ? ns => ns && getSynthesizedDeepClonesWithReplacements(ns, /*includeTrivia*/ true, replaceNode) + : ns => ns && getSynthesizedDeepClones(ns); + const visited = + visitEachChild(node, nodeClone, nullTransformationContext, nodesClone, nodeClone); + + if (visited === node) { + // This only happens for leaf nodes - internal nodes always see their children change. + const clone = + isStringLiteral(node) ? setOriginalNode(factory.createStringLiteralFromNode(node), node) as Node as T : + isNumericLiteral(node) ? setOriginalNode(factory.createNumericLiteral(node.text, node.numericLiteralFlags), node) as Node as T : + factory.cloneNode(node); + return setTextRange(clone, node); + } + + // PERF: As an optimization, rather than calling factory.cloneNode, we'll update + // the new node created by visitEachChild with the extra changes factory.cloneNode + // would have made. + (visited as Mutable).parent = undefined!; + return visited; +} + +export function getSynthesizedDeepClones(nodes: NodeArray, includeTrivia?: boolean): NodeArray; +export function getSynthesizedDeepClones(nodes: NodeArray | undefined, includeTrivia?: boolean): NodeArray | undefined; +export function getSynthesizedDeepClones(nodes: NodeArray | undefined, includeTrivia = true): NodeArray | undefined { + return nodes && factory.createNodeArray(nodes.map(n => getSynthesizedDeepClone(n, includeTrivia)), nodes.hasTrailingComma); +} + +export function getSynthesizedDeepClonesWithReplacements( + nodes: NodeArray, + includeTrivia: boolean, + replaceNode: (node: Node) => Node | undefined +): NodeArray { + return factory.createNodeArray(nodes.map(n => getSynthesizedDeepCloneWithReplacements(n, includeTrivia, replaceNode)), nodes.hasTrailingComma); +} + +/** + * Sets EmitFlags to suppress leading and trailing trivia on the node. + */ +export function suppressLeadingAndTrailingTrivia(node: Node) { + suppressLeadingTrivia(node); + suppressTrailingTrivia(node); +} + +/** + * Sets EmitFlags to suppress leading trivia on the node. + */ +export function suppressLeadingTrivia(node: Node) { + addEmitFlagsRecursively(node, EmitFlags.NoLeadingComments, getFirstChild); +} + +/** + * Sets EmitFlags to suppress trailing trivia on the node. + */ +export function suppressTrailingTrivia(node: Node) { + addEmitFlagsRecursively(node, EmitFlags.NoTrailingComments, getLastChild); +} + +export function copyComments(sourceNode: Node, targetNode: Node) { + const sourceFile = sourceNode.getSourceFile(); + const text = sourceFile.text; + if (hasLeadingLineBreak(sourceNode, text)) { + copyLeadingComments(sourceNode, targetNode, sourceFile); } + else { + copyTrailingAsLeadingComments(sourceNode, targetNode, sourceFile); + } + copyTrailingComments(sourceNode, targetNode, sourceFile); +} - export function getPrecedingNonSpaceCharacterPosition(text: string, position: number) { - while (position > -1 && isWhiteSpaceSingleLine(text.charCodeAt(position))) { - position -= 1; - } - return position + 1; +function hasLeadingLineBreak(node: Node, text: string) { + const start = node.getFullStart(); + const end = node.getStart(); + for (let i = start; i < end; i++) { + if (text.charCodeAt(i) === CharacterCodes.lineFeed) return true; } + return false; +} - /** - * Creates a deep, memberwise clone of a node with no source map location. - * - * WARNING: This is an expensive operation and is only intended to be used in refactorings - * and code fixes (because those are triggered by explicit user actions). - */ - export function getSynthesizedDeepClone(node: T, includeTrivia = true): T { - const clone = node && getSynthesizedDeepCloneWorker(node as NonNullable); - if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); - return clone; +function addEmitFlagsRecursively(node: Node, flag: EmitFlags, getChild: (n: Node) => Node | undefined) { + addEmitFlags(node, flag); + const child = getChild(node); + if (child) addEmitFlagsRecursively(child, flag, getChild); +} + +function getFirstChild(node: Node): Node | undefined { + return node.forEachChild(child => child); +} + +export function getUniqueName(baseName: string, sourceFile: SourceFile): string { + let nameText = baseName; + for (let i = 1; !isFileLevelUniqueName(sourceFile, nameText); i++) { + nameText = `${baseName}_${i}`; } + return nameText; +} - export function getSynthesizedDeepCloneWithReplacements( - node: T, - includeTrivia: boolean, - replaceNode: (node: Node) => Node | undefined - ): T { - let clone = replaceNode(node); - if (clone) { - setOriginalNode(clone, node); - } - else { - clone = getSynthesizedDeepCloneWorker(node as NonNullable, replaceNode); +/** + * @return The index of the (only) reference to the extracted symbol. We want the cursor + * to be on the reference, rather than the declaration, because it's closer to where the + * user was before extracting it. + */ +export function getRenameLocation(edits: readonly FileTextChanges[], renameFilename: string, name: string, preferLastLocation: boolean): number { + let delta = 0; + let lastPos = -1; + for (const { fileName, textChanges } of edits) { + Debug.assert(fileName === renameFilename); + for (const change of textChanges) { + const { span, newText } = change; + const index = indexInTextChange(newText, name); + if (index !== -1) { + lastPos = span.start + delta + index; + + // If the reference comes first, return immediately. + if (!preferLastLocation) { + return lastPos; + } + } + delta += newText.length - span.length; } - - if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); - return clone as T; } - function getSynthesizedDeepCloneWorker(node: T, replaceNode?: (node: Node) => Node | undefined): T { - const nodeClone: (n: T) => T = replaceNode - ? n => getSynthesizedDeepCloneWithReplacements(n, /*includeTrivia*/ true, replaceNode) - : getSynthesizedDeepClone; - const nodesClone: (ns: NodeArray) => NodeArray = replaceNode - ? ns => ns && getSynthesizedDeepClonesWithReplacements(ns, /*includeTrivia*/ true, replaceNode) - : ns => ns && getSynthesizedDeepClones(ns); - const visited = - visitEachChild(node, nodeClone, nullTransformationContext, nodesClone, nodeClone); + // If the declaration comes first, return the position of the last occurrence. + Debug.assert(preferLastLocation); + Debug.assert(lastPos >= 0); + return lastPos; +} - if (visited === node) { - // This only happens for leaf nodes - internal nodes always see their children change. - const clone = - isStringLiteral(node) ? setOriginalNode(factory.createStringLiteralFromNode(node), node) as Node as T : - isNumericLiteral(node) ? setOriginalNode(factory.createNumericLiteral(node.text, node.numericLiteralFlags), node) as Node as T : - factory.cloneNode(node); - return setTextRange(clone, node); - } +export function copyLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { + forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment)); +} - // PERF: As an optimization, rather than calling factory.cloneNode, we'll update - // the new node created by visitEachChild with the extra changes factory.cloneNode - // would have made. - (visited as Mutable).parent = undefined!; - return visited; - } - export function getSynthesizedDeepClones(nodes: NodeArray, includeTrivia?: boolean): NodeArray; - export function getSynthesizedDeepClones(nodes: NodeArray | undefined, includeTrivia?: boolean): NodeArray | undefined; - export function getSynthesizedDeepClones(nodes: NodeArray | undefined, includeTrivia = true): NodeArray | undefined { - return nodes && factory.createNodeArray(nodes.map(n => getSynthesizedDeepClone(n, includeTrivia)), nodes.hasTrailingComma); - } +export function copyTrailingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { + forEachTrailingCommentRange(sourceFile.text, sourceNode.end, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticTrailingComment)); +} - export function getSynthesizedDeepClonesWithReplacements( - nodes: NodeArray, - includeTrivia: boolean, - replaceNode: (node: Node) => Node | undefined - ): NodeArray { - return factory.createNodeArray(nodes.map(n => getSynthesizedDeepCloneWithReplacements(n, includeTrivia, replaceNode)), nodes.hasTrailingComma); - } +/** + * This function copies the trailing comments for the token that comes before `sourceNode`, as leading comments of `targetNode`. + * This is useful because sometimes a comment that refers to `sourceNode` will be a leading comment for `sourceNode`, according to the + * notion of trivia ownership, and instead will be a trailing comment for the token before `sourceNode`, e.g.: + * `function foo(\* not leading comment for a *\ a: string) {}` + * The comment refers to `a` but belongs to the `(` token, but we might want to copy it. + */ +export function copyTrailingAsLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { + forEachTrailingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment)); +} - /** - * Sets EmitFlags to suppress leading and trailing trivia on the node. - */ - export function suppressLeadingAndTrailingTrivia(node: Node) { - suppressLeadingTrivia(node); - suppressTrailingTrivia(node); - } +function getAddCommentsFunction(targetNode: Node, sourceFile: SourceFile, commentKind: CommentKind | undefined, hasTrailingNewLine: boolean | undefined, cb: (node: Node, kind: CommentKind, text: string, hasTrailingNewLine?: boolean) => void) { + return (pos: number, end: number, kind: CommentKind, htnl: boolean) => { + if (kind === SyntaxKind.MultiLineCommentTrivia) { + // Remove leading /* + pos += 2; + // Remove trailing */ + end -= 2; + } + else { + // Remove leading // + pos += 2; + } + cb(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl); + }; +} - /** - * Sets EmitFlags to suppress leading trivia on the node. - */ - export function suppressLeadingTrivia(node: Node) { - addEmitFlagsRecursively(node, EmitFlags.NoLeadingComments, getFirstChild); - } +function indexInTextChange(change: string, name: string): number { + if (startsWith(change, name)) return 0; + // Add a " " to avoid references inside words + let idx = change.indexOf(" " + name); + if (idx === -1) idx = change.indexOf("." + name); + if (idx === -1) idx = change.indexOf('"' + name); + return idx === -1 ? -1 : idx + 1; +} + +/* @internal */ +export function needsParentheses(expression: Expression): boolean { + return isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.CommaToken + || isObjectLiteralExpression(expression) + || isAsExpression(expression) && isObjectLiteralExpression(expression.expression); +} - /** - * Sets EmitFlags to suppress trailing trivia on the node. - */ - export function suppressTrailingTrivia(node: Node) { - addEmitFlagsRecursively(node, EmitFlags.NoTrailingComments, getLastChild); +export function getContextualTypeFromParent(node: Expression, checker: TypeChecker): Type | undefined { + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.NewExpression: + return checker.getContextualType(parent as NewExpression); + case SyntaxKind.BinaryExpression: { + const { left, operatorToken, right } = parent as BinaryExpression; + return isEqualityOperatorKind(operatorToken.kind) + ? checker.getTypeAtLocation(node === right ? left : right) + : checker.getContextualType(node); + } + case SyntaxKind.CaseClause: + return (parent as CaseClause).expression === node ? getSwitchedType(parent as CaseClause, checker) : undefined; + default: + return checker.getContextualType(node); } +} - export function copyComments(sourceNode: Node, targetNode: Node) { - const sourceFile = sourceNode.getSourceFile(); - const text = sourceFile.text; - if (hasLeadingLineBreak(sourceNode, text)) { - copyLeadingComments(sourceNode, targetNode, sourceFile); - } - else { - copyTrailingAsLeadingComments(sourceNode, targetNode, sourceFile); - } - copyTrailingComments(sourceNode, targetNode, sourceFile); - } +export function quote(sourceFile: SourceFile, preferences: UserPreferences, text: string): string { + // Editors can pass in undefined or empty string - we want to infer the preference in those cases. + const quotePreference = getQuotePreference(sourceFile, preferences); + const quoted = JSON.stringify(text); + return quotePreference === QuotePreference.Single ? `'${stripQuotes(quoted).replace(/'/g, "\\'").replace(/\\"/g, '"')}'` : quoted; +} - function hasLeadingLineBreak(node: Node, text: string) { - const start = node.getFullStart(); - const end = node.getStart(); - for (let i = start; i < end; i++) { - if (text.charCodeAt(i) === CharacterCodes.lineFeed) return true; - } - return false; +export function isEqualityOperatorKind(kind: SyntaxKind): kind is EqualityOperator { + switch (kind) { + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + return true; + default: + return false; } +} - function addEmitFlagsRecursively(node: Node, flag: EmitFlags, getChild: (n: Node) => Node | undefined) { - addEmitFlags(node, flag); - const child = getChild(node); - if (child) addEmitFlagsRecursively(child, flag, getChild); +export function isStringLiteralOrTemplate(node: Node): node is StringLiteralLike | TemplateExpression | TaggedTemplateExpression { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateExpression: + case SyntaxKind.TaggedTemplateExpression: + return true; + default: + return false; } +} - function getFirstChild(node: Node): Node | undefined { - return node.forEachChild(child => child); - } +export function hasIndexSignature(type: Type): boolean { + return !!type.getStringIndexType() || !!type.getNumberIndexType(); +} - export function getUniqueName(baseName: string, sourceFile: SourceFile): string { - let nameText = baseName; - for (let i = 1; !isFileLevelUniqueName(sourceFile, nameText); i++) { - nameText = `${baseName}_${i}`; - } - return nameText; - } +export function getSwitchedType(caseClause: CaseClause, checker: TypeChecker): Type | undefined { + return checker.getTypeAtLocation(caseClause.parent.parent.expression); +} - /** - * @return The index of the (only) reference to the extracted symbol. We want the cursor - * to be on the reference, rather than the declaration, because it's closer to where the - * user was before extracting it. - */ - export function getRenameLocation(edits: readonly FileTextChanges[], renameFilename: string, name: string, preferLastLocation: boolean): number { - let delta = 0; - let lastPos = -1; - for (const { fileName, textChanges } of edits) { - Debug.assert(fileName === renameFilename); - for (const change of textChanges) { - const { span, newText } = change; - const index = indexInTextChange(newText, name); - if (index !== -1) { - lastPos = span.start + delta + index; - - // If the reference comes first, return immediately. - if (!preferLastLocation) { - return lastPos; - } - } - delta += newText.length - span.length; - } - } +export const ANONYMOUS = "anonymous function"; + +export function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined { + const checker = program.getTypeChecker(); + let typeIsAccessible = true; + const notAccessible = () => typeIsAccessible = false; + const res = checker.typeToTypeNode(type, enclosingScope, NodeBuilderFlags.NoTruncation, { + trackSymbol: (symbol, declaration, meaning) => { + typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible; + return !typeIsAccessible; + }, + reportInaccessibleThisError: notAccessible, + reportPrivateInBaseOfClassExpression: notAccessible, + reportInaccessibleUniqueSymbolError: notAccessible, + moduleResolverHost: getModuleSpecifierResolverHost(program, host) + }); + return typeIsAccessible ? res : undefined; +} - // If the declaration comes first, return the position of the last occurrence. - Debug.assert(preferLastLocation); - Debug.assert(lastPos >= 0); - return lastPos; - } +export function syntaxRequiresTrailingCommaOrSemicolonOrASI(kind: SyntaxKind) { + return kind === SyntaxKind.CallSignature + || kind === SyntaxKind.ConstructSignature + || kind === SyntaxKind.IndexSignature + || kind === SyntaxKind.PropertySignature + || kind === SyntaxKind.MethodSignature; +} - export function copyLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { - forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment)); - } +export function syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(kind: SyntaxKind) { + return kind === SyntaxKind.FunctionDeclaration + || kind === SyntaxKind.Constructor + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor; +} +export function syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(kind: SyntaxKind) { + return kind === SyntaxKind.ModuleDeclaration; +} - export function copyTrailingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { - forEachTrailingCommentRange(sourceFile.text, sourceNode.end, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticTrailingComment)); - } +export function syntaxRequiresTrailingSemicolonOrASI(kind: SyntaxKind) { + return kind === SyntaxKind.VariableStatement + || kind === SyntaxKind.ExpressionStatement + || kind === SyntaxKind.DoStatement + || kind === SyntaxKind.ContinueStatement + || kind === SyntaxKind.BreakStatement + || kind === SyntaxKind.ReturnStatement + || kind === SyntaxKind.ThrowStatement + || kind === SyntaxKind.DebuggerStatement + || kind === SyntaxKind.PropertyDeclaration + || kind === SyntaxKind.TypeAliasDeclaration + || kind === SyntaxKind.ImportDeclaration + || kind === SyntaxKind.ImportEqualsDeclaration + || kind === SyntaxKind.ExportDeclaration + || kind === SyntaxKind.NamespaceExportDeclaration + || kind === SyntaxKind.ExportAssignment; +} - /** - * This function copies the trailing comments for the token that comes before `sourceNode`, as leading comments of `targetNode`. - * This is useful because sometimes a comment that refers to `sourceNode` will be a leading comment for `sourceNode`, according to the - * notion of trivia ownership, and instead will be a trailing comment for the token before `sourceNode`, e.g.: - * `function foo(\* not leading comment for a *\ a: string) {}` - * The comment refers to `a` but belongs to the `(` token, but we might want to copy it. - */ - export function copyTrailingAsLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { - forEachTrailingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment)); - } +export const syntaxMayBeASICandidate = or( + syntaxRequiresTrailingCommaOrSemicolonOrASI, + syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI, + syntaxRequiresTrailingModuleBlockOrSemicolonOrASI, + syntaxRequiresTrailingSemicolonOrASI); - function getAddCommentsFunction(targetNode: Node, sourceFile: SourceFile, commentKind: CommentKind | undefined, hasTrailingNewLine: boolean | undefined, cb: (node: Node, kind: CommentKind, text: string, hasTrailingNewLine?: boolean) => void) { - return (pos: number, end: number, kind: CommentKind, htnl: boolean) => { - if (kind === SyntaxKind.MultiLineCommentTrivia) { - // Remove leading /* - pos += 2; - // Remove trailing */ - end -= 2; - } - else { - // Remove leading // - pos += 2; - } - cb(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl); - }; - } - - function indexInTextChange(change: string, name: string): number { - if (startsWith(change, name)) return 0; - // Add a " " to avoid references inside words - let idx = change.indexOf(" " + name); - if (idx === -1) idx = change.indexOf("." + name); - if (idx === -1) idx = change.indexOf('"' + name); - return idx === -1 ? -1 : idx + 1; - } - - /* @internal */ - export function needsParentheses(expression: Expression): boolean { - return isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.CommaToken - || isObjectLiteralExpression(expression) - || isAsExpression(expression) && isObjectLiteralExpression(expression.expression); - } - - export function getContextualTypeFromParent(node: Expression, checker: TypeChecker): Type | undefined { - const { parent } = node; - switch (parent.kind) { - case SyntaxKind.NewExpression: - return checker.getContextualType(parent as NewExpression); - case SyntaxKind.BinaryExpression: { - const { left, operatorToken, right } = parent as BinaryExpression; - return isEqualityOperatorKind(operatorToken.kind) - ? checker.getTypeAtLocation(node === right ? left : right) - : checker.getContextualType(node); - } - case SyntaxKind.CaseClause: - return (parent as CaseClause).expression === node ? getSwitchedType(parent as CaseClause, checker) : undefined; - default: - return checker.getContextualType(node); - } +function nodeIsASICandidate(node: Node, sourceFile: SourceFileLike): boolean { + const lastToken = node.getLastToken(sourceFile); + if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) { + return false; } - export function quote(sourceFile: SourceFile, preferences: UserPreferences, text: string): string { - // Editors can pass in undefined or empty string - we want to infer the preference in those cases. - const quotePreference = getQuotePreference(sourceFile, preferences); - const quoted = JSON.stringify(text); - return quotePreference === QuotePreference.Single ? `'${stripQuotes(quoted).replace(/'/g, "\\'").replace(/\\"/g, '"')}'` : quoted; + if (syntaxRequiresTrailingCommaOrSemicolonOrASI(node.kind)) { + if (lastToken && lastToken.kind === SyntaxKind.CommaToken) { + return false; + } } - - export function isEqualityOperatorKind(kind: SyntaxKind): kind is EqualityOperator { - switch (kind) { - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - return true; - default: - return false; + else if (syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(node.kind)) { + const lastChild = last(node.getChildren(sourceFile)); + if (lastChild && isModuleBlock(lastChild)) { + return false; } } - - export function isStringLiteralOrTemplate(node: Node): node is StringLiteralLike | TemplateExpression | TaggedTemplateExpression { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateExpression: - case SyntaxKind.TaggedTemplateExpression: - return true; - default: - return false; + else if (syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(node.kind)) { + const lastChild = last(node.getChildren(sourceFile)); + if (lastChild && isFunctionBlock(lastChild)) { + return false; } } + else if (!syntaxRequiresTrailingSemicolonOrASI(node.kind)) { + return false; + } - export function hasIndexSignature(type: Type): boolean { - return !!type.getStringIndexType() || !!type.getNumberIndexType(); + // See comment in parser’s `parseDoStatement` + if (node.kind === SyntaxKind.DoStatement) { + return true; } - export function getSwitchedType(caseClause: CaseClause, checker: TypeChecker): Type | undefined { - return checker.getTypeAtLocation(caseClause.parent.parent.expression); + const topNode = findAncestor(node, ancestor => !ancestor.parent)!; + const nextToken = findNextToken(node, topNode, sourceFile); + if (!nextToken || nextToken.kind === SyntaxKind.CloseBraceToken) { + return true; } - export const ANONYMOUS = "anonymous function"; + const startLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line; + const endLine = sourceFile.getLineAndCharacterOfPosition(nextToken.getStart(sourceFile)).line; + return startLine !== endLine; +} - export function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined { - const checker = program.getTypeChecker(); - let typeIsAccessible = true; - const notAccessible = () => typeIsAccessible = false; - const res = checker.typeToTypeNode(type, enclosingScope, NodeBuilderFlags.NoTruncation, { - trackSymbol: (symbol, declaration, meaning) => { - typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible; - return !typeIsAccessible; - }, - reportInaccessibleThisError: notAccessible, - reportPrivateInBaseOfClassExpression: notAccessible, - reportInaccessibleUniqueSymbolError: notAccessible, - moduleResolverHost: getModuleSpecifierResolverHost(program, host) - }); - return typeIsAccessible ? res : undefined; - } - - export function syntaxRequiresTrailingCommaOrSemicolonOrASI(kind: SyntaxKind) { - return kind === SyntaxKind.CallSignature - || kind === SyntaxKind.ConstructSignature - || kind === SyntaxKind.IndexSignature - || kind === SyntaxKind.PropertySignature - || kind === SyntaxKind.MethodSignature; - } - - export function syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(kind: SyntaxKind) { - return kind === SyntaxKind.FunctionDeclaration - || kind === SyntaxKind.Constructor - || kind === SyntaxKind.MethodDeclaration - || kind === SyntaxKind.GetAccessor - || kind === SyntaxKind.SetAccessor; - } - - export function syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(kind: SyntaxKind) { - return kind === SyntaxKind.ModuleDeclaration; - } - - export function syntaxRequiresTrailingSemicolonOrASI(kind: SyntaxKind) { - return kind === SyntaxKind.VariableStatement - || kind === SyntaxKind.ExpressionStatement - || kind === SyntaxKind.DoStatement - || kind === SyntaxKind.ContinueStatement - || kind === SyntaxKind.BreakStatement - || kind === SyntaxKind.ReturnStatement - || kind === SyntaxKind.ThrowStatement - || kind === SyntaxKind.DebuggerStatement - || kind === SyntaxKind.PropertyDeclaration - || kind === SyntaxKind.TypeAliasDeclaration - || kind === SyntaxKind.ImportDeclaration - || kind === SyntaxKind.ImportEqualsDeclaration - || kind === SyntaxKind.ExportDeclaration - || kind === SyntaxKind.NamespaceExportDeclaration - || kind === SyntaxKind.ExportAssignment; - } - - export const syntaxMayBeASICandidate = or( - syntaxRequiresTrailingCommaOrSemicolonOrASI, - syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI, - syntaxRequiresTrailingModuleBlockOrSemicolonOrASI, - syntaxRequiresTrailingSemicolonOrASI); - - function nodeIsASICandidate(node: Node, sourceFile: SourceFileLike): boolean { - const lastToken = node.getLastToken(sourceFile); - if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) { - return false; +export function positionIsASICandidate(pos: number, context: Node, sourceFile: SourceFileLike): boolean { + const contextAncestor = findAncestor(context, ancestor => { + if (ancestor.end !== pos) { + return "quit"; } + return syntaxMayBeASICandidate(ancestor.kind); + }); - if (syntaxRequiresTrailingCommaOrSemicolonOrASI(node.kind)) { - if (lastToken && lastToken.kind === SyntaxKind.CommaToken) { - return false; - } - } - else if (syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(node.kind)) { - const lastChild = last(node.getChildren(sourceFile)); - if (lastChild && isModuleBlock(lastChild)) { - return false; + return !!contextAncestor && nodeIsASICandidate(contextAncestor, sourceFile); +} + +export function probablyUsesSemicolons(sourceFile: SourceFile): boolean { + let withSemicolon = 0; + let withoutSemicolon = 0; + const nStatementsToObserve = 5; + forEachChild(sourceFile, function visit(node): boolean | undefined { + if (syntaxRequiresTrailingSemicolonOrASI(node.kind)) { + const lastToken = node.getLastToken(sourceFile); + if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) { + withSemicolon++; } - } - else if (syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(node.kind)) { - const lastChild = last(node.getChildren(sourceFile)); - if (lastChild && isFunctionBlock(lastChild)) { - return false; + else { + withoutSemicolon++; } } - else if (!syntaxRequiresTrailingSemicolonOrASI(node.kind)) { - return false; - } - - // See comment in parser’s `parseDoStatement` - if (node.kind === SyntaxKind.DoStatement) { - return true; - } - - const topNode = findAncestor(node, ancestor => !ancestor.parent)!; - const nextToken = findNextToken(node, topNode, sourceFile); - if (!nextToken || nextToken.kind === SyntaxKind.CloseBraceToken) { + if (withSemicolon + withoutSemicolon >= nStatementsToObserve) { return true; } - const startLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line; - const endLine = sourceFile.getLineAndCharacterOfPosition(nextToken.getStart(sourceFile)).line; - return startLine !== endLine; - } - - export function positionIsASICandidate(pos: number, context: Node, sourceFile: SourceFileLike): boolean { - const contextAncestor = findAncestor(context, ancestor => { - if (ancestor.end !== pos) { - return "quit"; - } - return syntaxMayBeASICandidate(ancestor.kind); - }); + return forEachChild(node, visit); + }); - return !!contextAncestor && nodeIsASICandidate(contextAncestor, sourceFile); + // One statement missing a semicolon isn’t sufficient evidence to say the user + // doesn’t want semicolons, because they may not even be done writing that statement. + if (withSemicolon === 0 && withoutSemicolon <= 1) { + return true; } - export function probablyUsesSemicolons(sourceFile: SourceFile): boolean { - let withSemicolon = 0; - let withoutSemicolon = 0; - const nStatementsToObserve = 5; - forEachChild(sourceFile, function visit(node): boolean | undefined { - if (syntaxRequiresTrailingSemicolonOrASI(node.kind)) { - const lastToken = node.getLastToken(sourceFile); - if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) { - withSemicolon++; - } - else { - withoutSemicolon++; - } - } - if (withSemicolon + withoutSemicolon >= nStatementsToObserve) { - return true; - } + // If even 2/5 places have a semicolon, the user probably wants semicolons + return withSemicolon / withoutSemicolon > 1 / nStatementsToObserve; +} - return forEachChild(node, visit); - }); +export function tryGetDirectories(host: Pick, directoryName: string): string[] { + return tryIOAndConsumeErrors(host, host.getDirectories, directoryName) || []; +} - // One statement missing a semicolon isn’t sufficient evidence to say the user - // doesn’t want semicolons, because they may not even be done writing that statement. - if (withSemicolon === 0 && withoutSemicolon <= 1) { - return true; - } +export function tryReadDirectory(host: Pick, path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[]): readonly string[] { + return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include) || emptyArray; +} - // If even 2/5 places have a semicolon, the user probably wants semicolons - return withSemicolon / withoutSemicolon > 1 / nStatementsToObserve; - } +export function tryFileExists(host: Pick, path: string): boolean { + return tryIOAndConsumeErrors(host, host.fileExists, path); +} - export function tryGetDirectories(host: Pick, directoryName: string): string[] { - return tryIOAndConsumeErrors(host, host.getDirectories, directoryName) || []; - } +export function tryDirectoryExists(host: LanguageServiceHost, path: string): boolean { + return tryAndIgnoreErrors(() => directoryProbablyExists(path, host)) || false; +} - export function tryReadDirectory(host: Pick, path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[]): readonly string[] { - return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include) || emptyArray; +export function tryAndIgnoreErrors(cb: () => T): T | undefined { + try { + return cb(); } - - export function tryFileExists(host: Pick, path: string): boolean { - return tryIOAndConsumeErrors(host, host.fileExists, path); + catch { + return undefined; } +} - export function tryDirectoryExists(host: LanguageServiceHost, path: string): boolean { - return tryAndIgnoreErrors(() => directoryProbablyExists(path, host)) || false; - } +export function tryIOAndConsumeErrors(host: unknown, toApply: ((...a: any[]) => T) | undefined, ...args: any[]) { + return tryAndIgnoreErrors(() => toApply && toApply.apply(host, args)); +} - export function tryAndIgnoreErrors(cb: () => T): T | undefined { - try { - return cb(); +export function findPackageJsons(startDirectory: string, host: Pick, stopDirectory?: string): string[] { + const paths: string[] = []; + forEachAncestorDirectory(startDirectory, ancestor => { + if (ancestor === stopDirectory) { + return true; } - catch { - return undefined; + const currentConfigPath = combinePaths(ancestor, "package.json"); + if (tryFileExists(host, currentConfigPath)) { + paths.push(currentConfigPath); } - } + }); + return paths; +} - export function tryIOAndConsumeErrors(host: unknown, toApply: ((...a: any[]) => T) | undefined, ...args: any[]) { - return tryAndIgnoreErrors(() => toApply && toApply.apply(host, args)); - } +export function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined { + let packageJson: string | undefined; + forEachAncestorDirectory(directory, ancestor => { + if (ancestor === "node_modules") return true; + packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json"); + if (packageJson) { + return true; // break out + } + }); + return packageJson; +} - export function findPackageJsons(startDirectory: string, host: Pick, stopDirectory?: string): string[] { - const paths: string[] = []; - forEachAncestorDirectory(startDirectory, ancestor => { - if (ancestor === stopDirectory) { - return true; - } - const currentConfigPath = combinePaths(ancestor, "package.json"); - if (tryFileExists(host, currentConfigPath)) { - paths.push(currentConfigPath); - } - }); - return paths; +export function getPackageJsonsVisibleToFile(fileName: string, host: LanguageServiceHost): readonly PackageJsonInfo[] { + if (!host.fileExists) { + return []; } - export function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined { - let packageJson: string | undefined; - forEachAncestorDirectory(directory, ancestor => { - if (ancestor === "node_modules") return true; - packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json"); - if (packageJson) { - return true; // break out + const packageJsons: PackageJsonInfo[] = []; + forEachAncestorDirectory(getDirectoryPath(fileName), ancestor => { + const packageJsonFileName = combinePaths(ancestor, "package.json"); + if (host.fileExists!(packageJsonFileName)) { + const info = createPackageJsonInfo(packageJsonFileName, host); + if (info) { + packageJsons.push(info); } - }); - return packageJson; - } - - export function getPackageJsonsVisibleToFile(fileName: string, host: LanguageServiceHost): readonly PackageJsonInfo[] { - if (!host.fileExists) { - return []; } + }); - const packageJsons: PackageJsonInfo[] = []; - forEachAncestorDirectory(getDirectoryPath(fileName), ancestor => { - const packageJsonFileName = combinePaths(ancestor, "package.json"); - if (host.fileExists!(packageJsonFileName)) { - const info = createPackageJsonInfo(packageJsonFileName, host); - if (info) { - packageJsons.push(info); - } - } - }); + return packageJsons; +} - return packageJsons; +export function createPackageJsonInfo(fileName: string, host: { readFile?(fileName: string): string | undefined }): PackageJsonInfo | undefined { + if (!host.readFile) { + return undefined; } - export function createPackageJsonInfo(fileName: string, host: { readFile?(fileName: string): string | undefined }): PackageJsonInfo | undefined { - if (!host.readFile) { - return undefined; - } - - type PackageJsonRaw = Record | undefined>; - const dependencyKeys = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"] as const; - const stringContent = host.readFile(fileName) || ""; - const content = tryParseJson(stringContent) as PackageJsonRaw | undefined; - const info: Pick = {}; - if (content) { - for (const key of dependencyKeys) { - const dependencies = content[key]; - if (!dependencies) { - continue; - } - const dependencyMap = new Map(); - for (const packageName in dependencies) { - dependencyMap.set(packageName, dependencies[packageName]); - } - info[key] = dependencyMap; - } - } - - const dependencyGroups = [ - [PackageJsonDependencyGroup.Dependencies, info.dependencies], - [PackageJsonDependencyGroup.DevDependencies, info.devDependencies], - [PackageJsonDependencyGroup.OptionalDependencies, info.optionalDependencies], - [PackageJsonDependencyGroup.PeerDependencies, info.peerDependencies], - ] as const; - - return { - ...info, - parseable: !!content, - fileName, - get, - has(dependencyName, inGroups) { - return !!get(dependencyName, inGroups); - }, - }; - - function get(dependencyName: string, inGroups = PackageJsonDependencyGroup.All) { - for (const [group, deps] of dependencyGroups) { - if (deps && (inGroups & group)) { - const dep = deps.get(dependencyName); - if (dep !== undefined) { - return dep; - } + type PackageJsonRaw = Record | undefined>; + const dependencyKeys = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"] as const; + const stringContent = host.readFile(fileName) || ""; + const content = tryParseJson(stringContent) as PackageJsonRaw | undefined; + const info: Pick = {}; + if (content) { + for (const key of dependencyKeys) { + const dependencies = content[key]; + if (!dependencies) { + continue; + } + const dependencyMap = new Map(); + for (const packageName in dependencies) { + dependencyMap.set(packageName, dependencies[packageName]); + } + info[key] = dependencyMap; + } + } + + const dependencyGroups = [ + [PackageJsonDependencyGroup.Dependencies, info.dependencies], + [PackageJsonDependencyGroup.DevDependencies, info.devDependencies], + [PackageJsonDependencyGroup.OptionalDependencies, info.optionalDependencies], + [PackageJsonDependencyGroup.PeerDependencies, info.peerDependencies], + ] as const; + + return { + ...info, + parseable: !!content, + fileName, + get, + has(dependencyName, inGroups) { + return !!get(dependencyName, inGroups); + }, + }; + + function get(dependencyName: string, inGroups = PackageJsonDependencyGroup.All) { + for (const [group, deps] of dependencyGroups) { + if (deps && (inGroups & group)) { + const dep = deps.get(dependencyName); + if (dep !== undefined) { + return dep; } } } } +} - export interface PackageJsonImportFilter { - allowsImportingAmbientModule: (moduleSymbol: Symbol, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost) => boolean; - allowsImportingSourceFile: (sourceFile: SourceFile, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost) => boolean; - /** - * Use for a specific module specifier that has already been resolved. - * Use `allowsImportingAmbientModule` or `allowsImportingSourceFile` to resolve - * the best module specifier for a given module _and_ determine if it’s importable. - */ - allowsImportingSpecifier: (moduleSpecifier: string) => boolean; - } +export interface PackageJsonImportFilter { + allowsImportingAmbientModule: (moduleSymbol: Symbol, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost) => boolean; + allowsImportingSourceFile: (sourceFile: SourceFile, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost) => boolean; + /** + * Use for a specific module specifier that has already been resolved. + * Use `allowsImportingAmbientModule` or `allowsImportingSourceFile` to resolve + * the best module specifier for a given module _and_ determine if it’s importable. + */ + allowsImportingSpecifier: (moduleSpecifier: string) => boolean; +} - export function createPackageJsonImportFilter(fromFile: SourceFile, preferences: UserPreferences, host: LanguageServiceHost): PackageJsonImportFilter { - const packageJsons = ( - (host.getPackageJsonsVisibleToFile && host.getPackageJsonsVisibleToFile(fromFile.fileName)) || getPackageJsonsVisibleToFile(fromFile.fileName, host) - ).filter(p => p.parseable); +export function createPackageJsonImportFilter(fromFile: SourceFile, preferences: UserPreferences, host: LanguageServiceHost): PackageJsonImportFilter { + const packageJsons = ( + (host.getPackageJsonsVisibleToFile && host.getPackageJsonsVisibleToFile(fromFile.fileName)) || getPackageJsonsVisibleToFile(fromFile.fileName, host) + ).filter(p => p.parseable); - let usesNodeCoreModules: boolean | undefined; - return { allowsImportingAmbientModule, allowsImportingSourceFile, allowsImportingSpecifier }; + let usesNodeCoreModules: boolean | undefined; + return { allowsImportingAmbientModule, allowsImportingSourceFile, allowsImportingSpecifier }; - function moduleSpecifierIsCoveredByPackageJson(specifier: string) { - const packageName = getNodeModuleRootSpecifier(specifier); - for (const packageJson of packageJsons) { - if (packageJson.has(packageName) || packageJson.has(getTypesPackageName(packageName))) { - return true; - } + function moduleSpecifierIsCoveredByPackageJson(specifier: string) { + const packageName = getNodeModuleRootSpecifier(specifier); + for (const packageJson of packageJsons) { + if (packageJson.has(packageName) || packageJson.has(getTypesPackageName(packageName))) { + return true; } - return false; + } + return false; + } + + function allowsImportingAmbientModule(moduleSymbol: Symbol, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost): boolean { + if (!packageJsons.length || !moduleSymbol.valueDeclaration) { + return true; } - function allowsImportingAmbientModule(moduleSymbol: Symbol, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost): boolean { - if (!packageJsons.length || !moduleSymbol.valueDeclaration) { - return true; - } + const declaringSourceFile = moduleSymbol.valueDeclaration.getSourceFile(); + const declaringNodeModuleName = getNodeModulesPackageNameFromFileName(declaringSourceFile.fileName, moduleSpecifierResolutionHost); + if (typeof declaringNodeModuleName === "undefined") { + return true; + } - const declaringSourceFile = moduleSymbol.valueDeclaration.getSourceFile(); - const declaringNodeModuleName = getNodeModulesPackageNameFromFileName(declaringSourceFile.fileName, moduleSpecifierResolutionHost); - if (typeof declaringNodeModuleName === "undefined") { - return true; - } + const declaredModuleSpecifier = stripQuotes(moduleSymbol.getName()); + if (isAllowedCoreNodeModulesImport(declaredModuleSpecifier)) { + return true; + } - const declaredModuleSpecifier = stripQuotes(moduleSymbol.getName()); - if (isAllowedCoreNodeModulesImport(declaredModuleSpecifier)) { - return true; - } + return moduleSpecifierIsCoveredByPackageJson(declaringNodeModuleName) + || moduleSpecifierIsCoveredByPackageJson(declaredModuleSpecifier); + } - return moduleSpecifierIsCoveredByPackageJson(declaringNodeModuleName) - || moduleSpecifierIsCoveredByPackageJson(declaredModuleSpecifier); + function allowsImportingSourceFile(sourceFile: SourceFile, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost): boolean { + if (!packageJsons.length) { + return true; } - function allowsImportingSourceFile(sourceFile: SourceFile, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost): boolean { - if (!packageJsons.length) { - return true; - } + const moduleSpecifier = getNodeModulesPackageNameFromFileName(sourceFile.fileName, moduleSpecifierResolutionHost); + if (!moduleSpecifier) { + return true; + } - const moduleSpecifier = getNodeModulesPackageNameFromFileName(sourceFile.fileName, moduleSpecifierResolutionHost); - if (!moduleSpecifier) { - return true; - } + return moduleSpecifierIsCoveredByPackageJson(moduleSpecifier); + } - return moduleSpecifierIsCoveredByPackageJson(moduleSpecifier); + function allowsImportingSpecifier(moduleSpecifier: string) { + if (!packageJsons.length || isAllowedCoreNodeModulesImport(moduleSpecifier)) { + return true; } + if (pathIsRelative(moduleSpecifier) || isRootedDiskPath(moduleSpecifier)) { + return true; + } + return moduleSpecifierIsCoveredByPackageJson(moduleSpecifier); + } - function allowsImportingSpecifier(moduleSpecifier: string) { - if (!packageJsons.length || isAllowedCoreNodeModulesImport(moduleSpecifier)) { - return true; + function isAllowedCoreNodeModulesImport(moduleSpecifier: string) { + // If we’re in JavaScript, it can be difficult to tell whether the user wants to import + // from Node core modules or not. We can start by seeing if the user is actually using + // any node core modules, as opposed to simply having @types/node accidentally as a + // dependency of a dependency. + if (isSourceFileJS(fromFile) && JsTyping.nodeCoreModules.has(moduleSpecifier)) { + if (usesNodeCoreModules === undefined) { + usesNodeCoreModules = consumesNodeCoreModules(fromFile); } - if (pathIsRelative(moduleSpecifier) || isRootedDiskPath(moduleSpecifier)) { + if (usesNodeCoreModules) { return true; } - return moduleSpecifierIsCoveredByPackageJson(moduleSpecifier); } + return false; + } - function isAllowedCoreNodeModulesImport(moduleSpecifier: string) { - // If we’re in JavaScript, it can be difficult to tell whether the user wants to import - // from Node core modules or not. We can start by seeing if the user is actually using - // any node core modules, as opposed to simply having @types/node accidentally as a - // dependency of a dependency. - if (isSourceFileJS(fromFile) && JsTyping.nodeCoreModules.has(moduleSpecifier)) { - if (usesNodeCoreModules === undefined) { - usesNodeCoreModules = consumesNodeCoreModules(fromFile); - } - if (usesNodeCoreModules) { - return true; - } - } - return false; + function getNodeModulesPackageNameFromFileName(importedFileName: string, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost): string | undefined { + if (!stringContains(importedFileName, "node_modules")) { + return undefined; } + const specifier = moduleSpecifiers.getNodeModulesPackageName( + host.getCompilationSettings(), + fromFile.path, + importedFileName, + moduleSpecifierResolutionHost, + preferences, + ); - function getNodeModulesPackageNameFromFileName(importedFileName: string, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost): string | undefined { - if (!stringContains(importedFileName, "node_modules")) { - return undefined; - } - const specifier = moduleSpecifiers.getNodeModulesPackageName( - host.getCompilationSettings(), - fromFile.path, - importedFileName, - moduleSpecifierResolutionHost, - preferences, - ); - - if (!specifier) { - return undefined; - } - // Paths here are not node_modules, so we don’t care about them; - // returning anything will trigger a lookup in package.json. - if (!pathIsRelative(specifier) && !isRootedDiskPath(specifier)) { - return getNodeModuleRootSpecifier(specifier); - } + if (!specifier) { + return undefined; } - - function getNodeModuleRootSpecifier(fullSpecifier: string): string { - const components = getPathComponents(getPackageNameFromTypesPackageName(fullSpecifier)).slice(1); - // Scoped packages - if (startsWith(components[0], "@")) { - return `${components[0]}/${components[1]}`; - } - return components[0]; + // Paths here are not node_modules, so we don’t care about them; + // returning anything will trigger a lookup in package.json. + if (!pathIsRelative(specifier) && !isRootedDiskPath(specifier)) { + return getNodeModuleRootSpecifier(specifier); } } - function tryParseJson(text: string) { - try { - return JSON.parse(text); - } - catch { - return undefined; + function getNodeModuleRootSpecifier(fullSpecifier: string): string { + const components = getPathComponents(getPackageNameFromTypesPackageName(fullSpecifier)).slice(1); + // Scoped packages + if (startsWith(components[0], "@")) { + return `${components[0]}/${components[1]}`; } + return components[0]; } +} - export function consumesNodeCoreModules(sourceFile: SourceFile): boolean { - return some(sourceFile.imports, ({ text }) => JsTyping.nodeCoreModules.has(text)); +function tryParseJson(text: string) { + try { + return JSON.parse(text); } - - export function isInsideNodeModules(fileOrDirectory: string): boolean { - return contains(getPathComponents(fileOrDirectory), "node_modules"); + catch { + return undefined; } +} + +export function consumesNodeCoreModules(sourceFile: SourceFile): boolean { + return some(sourceFile.imports, ({ text }) => JsTyping.nodeCoreModules.has(text)); +} + +export function isInsideNodeModules(fileOrDirectory: string): boolean { + return contains(getPathComponents(fileOrDirectory), "node_modules"); +} + +export function isDiagnosticWithLocation(diagnostic: Diagnostic): diagnostic is DiagnosticWithLocation { + return diagnostic.file !== undefined && diagnostic.start !== undefined && diagnostic.length !== undefined; +} - export function isDiagnosticWithLocation(diagnostic: Diagnostic): diagnostic is DiagnosticWithLocation { - return diagnostic.file !== undefined && diagnostic.start !== undefined && diagnostic.length !== undefined; +export function findDiagnosticForNode(node: Node, sortedFileDiagnostics: readonly Diagnostic[]): DiagnosticWithLocation | undefined { + const span: Partial = createTextSpanFromNode(node); + const index = binarySearchKey(sortedFileDiagnostics, span, identity, compareTextSpans); + if (index >= 0) { + const diagnostic = sortedFileDiagnostics[index]; + Debug.assertEqual(diagnostic.file, node.getSourceFile(), "Diagnostics proided to 'findDiagnosticForNode' must be from a single SourceFile"); + return cast(diagnostic, isDiagnosticWithLocation); } +} - export function findDiagnosticForNode(node: Node, sortedFileDiagnostics: readonly Diagnostic[]): DiagnosticWithLocation | undefined { - const span: Partial = createTextSpanFromNode(node); - const index = binarySearchKey(sortedFileDiagnostics, span, identity, compareTextSpans); - if (index >= 0) { - const diagnostic = sortedFileDiagnostics[index]; - Debug.assertEqual(diagnostic.file, node.getSourceFile(), "Diagnostics proided to 'findDiagnosticForNode' must be from a single SourceFile"); - return cast(diagnostic, isDiagnosticWithLocation); - } +export function getDiagnosticsWithinSpan(span: TextSpan, sortedFileDiagnostics: readonly Diagnostic[]): readonly DiagnosticWithLocation[] { + let index = binarySearchKey(sortedFileDiagnostics, span.start, diag => diag.start, compareValues); + if (index < 0) { + index = ~index; + } + while (sortedFileDiagnostics[index - 1]?.start === span.start) { + index--; } - export function getDiagnosticsWithinSpan(span: TextSpan, sortedFileDiagnostics: readonly Diagnostic[]): readonly DiagnosticWithLocation[] { - let index = binarySearchKey(sortedFileDiagnostics, span.start, diag => diag.start, compareValues); - if (index < 0) { - index = ~index; + const result: DiagnosticWithLocation[] = []; + const end = textSpanEnd(span); + while (true) { + const diagnostic = tryCast(sortedFileDiagnostics[index], isDiagnosticWithLocation); + if (!diagnostic || diagnostic.start > end) { + break; } - while (sortedFileDiagnostics[index - 1]?.start === span.start) { - index--; - } - - const result: DiagnosticWithLocation[] = []; - const end = textSpanEnd(span); - while (true) { - const diagnostic = tryCast(sortedFileDiagnostics[index], isDiagnosticWithLocation); - if (!diagnostic || diagnostic.start > end) { - break; - } - if (textSpanContainsTextSpan(span, diagnostic)) { - result.push(diagnostic); - } - index++; + if (textSpanContainsTextSpan(span, diagnostic)) { + result.push(diagnostic); } - - return result; + index++; } - /* @internal */ - export function getRefactorContextSpan({ startPosition, endPosition }: RefactorContext): TextSpan { - return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition); - } + return result; +} - /* @internal */ - export function getFixableErrorSpanExpression(sourceFile: SourceFile, span: TextSpan): Expression | undefined { - const token = getTokenAtPosition(sourceFile, span.start); - // Checker has already done work to determine that await might be possible, and has attached - // related info to the node, so start by finding the expression that exactly matches up - // with the diagnostic range. - const expression = findAncestor(token, node => { - if (node.getStart(sourceFile) < span.start || node.getEnd() > textSpanEnd(span)) { - return "quit"; - } - return isExpression(node) && textSpansEqual(span, createTextSpanFromNode(node, sourceFile)); - }) as Expression | undefined; +/* @internal */ +export function getRefactorContextSpan({ startPosition, endPosition }: RefactorContext): TextSpan { + return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition); +} - return expression; - } +/* @internal */ +export function getFixableErrorSpanExpression(sourceFile: SourceFile, span: TextSpan): Expression | undefined { + const token = getTokenAtPosition(sourceFile, span.start); + // Checker has already done work to determine that await might be possible, and has attached + // related info to the node, so start by finding the expression that exactly matches up + // with the diagnostic range. + const expression = findAncestor(token, node => { + if (node.getStart(sourceFile) < span.start || node.getEnd() > textSpanEnd(span)) { + return "quit"; + } + return isExpression(node) && textSpansEqual(span, createTextSpanFromNode(node, sourceFile)); + }) as Expression | undefined; + + return expression; +} - /** - * If the provided value is an array, the mapping function is applied to each element; otherwise, the mapping function is applied - * to the provided value itself. - */ - export function mapOneOrMany(valueOrArray: T | readonly T[], f: (x: T, i: number) => U): U | U[]; - export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U): U | U[] | undefined; - export function mapOneOrMany(valueOrArray: T | readonly T[], f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U; - export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U | undefined; - export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U | U[] = identity): U | U[] | undefined { - return valueOrArray ? isArray(valueOrArray) ? resultSelector(map(valueOrArray, f)) : f(valueOrArray, 0) : undefined; - } +/** + * If the provided value is an array, the mapping function is applied to each element; otherwise, the mapping function is applied + * to the provided value itself. + */ +export function mapOneOrMany(valueOrArray: T | readonly T[], f: (x: T, i: number) => U): U | U[]; +export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U): U | U[] | undefined; +export function mapOneOrMany(valueOrArray: T | readonly T[], f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U; +export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U | undefined; +export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U | U[] = identity): U | U[] | undefined { + return valueOrArray ? isArray(valueOrArray) ? resultSelector(map(valueOrArray, f)) : f(valueOrArray, 0) : undefined; +} - /** - * If the provided value is an array, the first element of the array is returned; otherwise, the provided value is returned instead. - */ - export function firstOrOnly(valueOrArray: T | readonly T[]): T { - return isArray(valueOrArray) ? first(valueOrArray) : valueOrArray; - } +/** + * If the provided value is an array, the first element of the array is returned; otherwise, the provided value is returned instead. + */ +export function firstOrOnly(valueOrArray: T | readonly T[]): T { + return isArray(valueOrArray) ? first(valueOrArray) : valueOrArray; +} - export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined) { - if (!(symbol.flags & SymbolFlags.Transient) && (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default)) { - // Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase. - return firstDefined(symbol.declarations, d => isExportAssignment(d) ? tryCast(skipOuterExpressions(d.expression), isIdentifier)?.text : undefined) - || codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget); - } - return symbol.name; +export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined) { + if (!(symbol.flags & SymbolFlags.Transient) && (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default)) { + // Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase. + return firstDefined(symbol.declarations, d => isExportAssignment(d) ? tryCast(skipOuterExpressions(d.expression), isIdentifier)?.text : undefined) + || codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget); } + return symbol.name; +} - function getSymbolParentOrFail(symbol: Symbol) { - return Debug.checkDefined( - symbol.parent, - `Symbol parent was undefined. Flags: ${Debug.formatSymbolFlags(symbol.flags)}. ` + - `Declarations: ${symbol.declarations?.map(d => { - const kind = Debug.formatSyntaxKind(d.kind); - const inJS = isInJSFile(d); - const { expression } = d as any; - return (inJS ? "[JS]" : "") + kind + (expression ? ` (expression: ${Debug.formatSyntaxKind(expression.kind)})` : ""); - }).join(", ")}.`); - } +function getSymbolParentOrFail(symbol: Symbol) { + return Debug.checkDefined( + symbol.parent, + `Symbol parent was undefined. Flags: ${Debug.formatSymbolFlags(symbol.flags)}. ` + + `Declarations: ${symbol.declarations?.map(d => { + const kind = Debug.formatSyntaxKind(d.kind); + const inJS = isInJSFile(d); + const { expression } = d as any; + return (inJS ? "[JS]" : "") + kind + (expression ? ` (expression: ${Debug.formatSyntaxKind(expression.kind)})` : ""); + }).join(", ")}.`); +} - /** - * Useful to check whether a string contains another string at a specific index - * without allocating another string or traversing the entire contents of the outer string. - * - * This function is useful in place of either of the following: - * - * ```ts - * // Allocates - * haystack.substr(startIndex, needle.length) === needle - * - * // Full traversal - * haystack.indexOf(needle, startIndex) === startIndex - * ``` - * - * @param haystack The string that potentially contains `needle`. - * @param needle The string whose content might sit within `haystack`. - * @param startIndex The index within `haystack` to start searching for `needle`. - */ - export function stringContainsAt(haystack: string, needle: string, startIndex: number) { - const needleLength = needle.length; - if (needleLength + startIndex > haystack.length) { - return false; - } - for (let i = 0; i < needleLength; i++) { - if (needle.charCodeAt(i) !== haystack.charCodeAt(i + startIndex)) return false; - } - return true; +/** + * Useful to check whether a string contains another string at a specific index + * without allocating another string or traversing the entire contents of the outer string. + * + * This function is useful in place of either of the following: + * + * ```ts + * // Allocates + * haystack.substr(startIndex, needle.length) === needle + * + * // Full traversal + * haystack.indexOf(needle, startIndex) === startIndex + * ``` + * + * @param haystack The string that potentially contains `needle`. + * @param needle The string whose content might sit within `haystack`. + * @param startIndex The index within `haystack` to start searching for `needle`. + */ +export function stringContainsAt(haystack: string, needle: string, startIndex: number) { + const needleLength = needle.length; + if (needleLength + startIndex > haystack.length) { + return false; } - - export function startsWithUnderscore(name: string): boolean { - return name.charCodeAt(0) === CharacterCodes._; + for (let i = 0; i < needleLength; i++) { + if (needle.charCodeAt(i) !== haystack.charCodeAt(i + startIndex)) return false; } + return true; +} - export function isGlobalDeclaration(declaration: Declaration) { - return !isNonGlobalDeclaration(declaration); - } +export function startsWithUnderscore(name: string): boolean { + return name.charCodeAt(0) === CharacterCodes._; +} - export function isNonGlobalDeclaration(declaration: Declaration) { - const sourceFile = declaration.getSourceFile(); - // If the file is not a module, the declaration is global - if (!sourceFile.externalModuleIndicator && !sourceFile.commonJsModuleIndicator) { - return false; - } - // If the file is a module written in TypeScript, it still might be in a `declare global` augmentation - return isInJSFile(declaration) || !findAncestor(declaration, isGlobalScopeAugmentation); - } +export function isGlobalDeclaration(declaration: Declaration) { + return !isNonGlobalDeclaration(declaration); +} - export function isDeprecatedDeclaration(decl: Declaration) { - return !!(getCombinedNodeFlagsAlwaysIncludeJSDoc(decl) & ModifierFlags.Deprecated); +export function isNonGlobalDeclaration(declaration: Declaration) { + const sourceFile = declaration.getSourceFile(); + // If the file is not a module, the declaration is global + if (!sourceFile.externalModuleIndicator && !sourceFile.commonJsModuleIndicator) { + return false; } + // If the file is a module written in TypeScript, it still might be in a `declare global` augmentation + return isInJSFile(declaration) || !findAncestor(declaration, isGlobalScopeAugmentation); +} - export function shouldUseUriStyleNodeCoreModules(file: SourceFile, program: Program): boolean { - const decisionFromFile = firstDefined(file.imports, node => { - if (JsTyping.nodeCoreModules.has(node.text)) { - return startsWith(node.text, "node:"); - } - }); - return decisionFromFile ?? program.usesUriStyleNodeCoreModules; - } +export function isDeprecatedDeclaration(decl: Declaration) { + return !!(getCombinedNodeFlagsAlwaysIncludeJSDoc(decl) & ModifierFlags.Deprecated); +} - export function getNewLineKind(newLineCharacter: string): NewLineKind { - return newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed; - } +export function shouldUseUriStyleNodeCoreModules(file: SourceFile, program: Program): boolean { + const decisionFromFile = firstDefined(file.imports, node => { + if (JsTyping.nodeCoreModules.has(node.text)) { + return startsWith(node.text, "node:"); + } + }); + return decisionFromFile ?? program.usesUriStyleNodeCoreModules; +} - export type DiagnosticAndArguments = DiagnosticMessage | [DiagnosticMessage, string] | [DiagnosticMessage, string, string]; - export function diagnosticToString(diag: DiagnosticAndArguments): string { - return isArray(diag) - ? formatStringFromArgs(getLocaleSpecificMessage(diag[0]), diag.slice(1) as readonly string[]) - : getLocaleSpecificMessage(diag); - } +export function getNewLineKind(newLineCharacter: string): NewLineKind { + return newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed; +} + +export type DiagnosticAndArguments = DiagnosticMessage | [DiagnosticMessage, string] | [DiagnosticMessage, string, string]; +export function diagnosticToString(diag: DiagnosticAndArguments): string { + return isArray(diag) + ? formatStringFromArgs(getLocaleSpecificMessage(diag[0]), diag.slice(1) as readonly string[]) + : getLocaleSpecificMessage(diag); +} - // #endregion +// #endregion } diff --git a/src/shims/collectionShims.ts b/src/shims/collectionShims.ts index 6b868e8714e08..b338ddfec1200 100644 --- a/src/shims/collectionShims.ts +++ b/src/shims/collectionShims.ts @@ -1,267 +1,267 @@ /* @internal */ namespace ts { - type GetIteratorCallback = | ReadonlyMapShim | undefined>(iterable: I) => IteratorShim< - I extends ReadonlyMapShim ? [K, V] : - I extends ReadonlySetShim ? T : - I extends readonly (infer T)[] ? T : - I extends undefined ? undefined : - never>; +type GetIteratorCallback = | ReadonlyMapShim | undefined>(iterable: I) => IteratorShim< + I extends ReadonlyMapShim ? [K, V] : + I extends ReadonlySetShim ? T : + I extends readonly (infer T)[] ? T : + I extends undefined ? undefined : + never>; - type IteratorResultShim = - | { value: T, done?: false } - | { value: void, done: true }; +type IteratorResultShim = + | { value: T, done?: false } + | { value: void, done: true }; - interface IteratorShim { - next(): IteratorResultShim; - } +interface IteratorShim { + next(): IteratorResultShim; +} - interface ReadonlyMapShim { - readonly size: number; - get(key: K): V | undefined; - has(key: K): boolean; - keys(): IteratorShim; - values(): IteratorShim; - entries(): IteratorShim<[K, V]>; - forEach(action: (value: V, key: K) => void): void; - } +interface ReadonlyMapShim { + readonly size: number; + get(key: K): V | undefined; + has(key: K): boolean; + keys(): IteratorShim; + values(): IteratorShim; + entries(): IteratorShim<[K, V]>; + forEach(action: (value: V, key: K) => void): void; +} - interface MapShim extends ReadonlyMapShim { - set(key: K, value: V): this; - delete(key: K): boolean; - clear(): void; - } +interface MapShim extends ReadonlyMapShim { + set(key: K, value: V): this; + delete(key: K): boolean; + clear(): void; +} - type MapShimConstructor = new (iterable?: readonly (readonly [K, V])[] | ReadonlyMapShim) => MapShim; +type MapShimConstructor = new (iterable?: readonly (readonly [K, V])[] | ReadonlyMapShim) => MapShim; - interface ReadonlySetShim { - readonly size: number; - has(value: T): boolean; - keys(): IteratorShim; - values(): IteratorShim; - entries(): IteratorShim<[T, T]>; - forEach(action: (value: T, key: T) => void): void; - } +interface ReadonlySetShim { + readonly size: number; + has(value: T): boolean; + keys(): IteratorShim; + values(): IteratorShim; + entries(): IteratorShim<[T, T]>; + forEach(action: (value: T, key: T) => void): void; +} - interface SetShim extends ReadonlySetShim { - add(value: T): this; - delete(value: T): boolean; - clear(): void; - } +interface SetShim extends ReadonlySetShim { + add(value: T): this; + delete(value: T): boolean; + clear(): void; +} - type SetShimConstructor = new (iterable?: readonly T[] | ReadonlySetShim) => SetShim; +type SetShimConstructor = new (iterable?: readonly T[] | ReadonlySetShim) => SetShim; - interface MapData { - size: number; - readonly head: MapEntry; - tail: MapEntry; - } +interface MapData { + size: number; + readonly head: MapEntry; + tail: MapEntry; +} - interface MapEntry { - readonly key?: K; - value?: V; - /** - * Specifies the next entry in the linked list. - */ - next?: MapEntry; - /** - * Specifies the previous entry in the linked list. - * Must be set when the entry is part of a Map/Set. - * When 'undefined', iterators should skip the next entry. - * This will be set to 'undefined' when an entry is deleted. - * See https://github.com/Microsoft/TypeScript/pull/27292 for more information. - */ - prev?: MapEntry; - } +interface MapEntry { + readonly key?: K; + value?: V; + /** + * Specifies the next entry in the linked list. + */ + next?: MapEntry; + /** + * Specifies the previous entry in the linked list. + * Must be set when the entry is part of a Map/Set. + * When 'undefined', iterators should skip the next entry. + * This will be set to 'undefined' when an entry is deleted. + * See https://github.com/Microsoft/TypeScript/pull/27292 for more information. + */ + prev?: MapEntry; +} - interface IteratorData { - current?: MapEntry; - selector: (key: K, value: V) => U; - } +interface IteratorData { + current?: MapEntry; + selector: (key: K, value: V) => U; +} - function createMapData(): MapData { - const sentinel: MapEntry = {}; - sentinel.prev = sentinel; - return { head: sentinel, tail: sentinel, size: 0 }; - } +function createMapData(): MapData { + const sentinel: MapEntry = {}; + sentinel.prev = sentinel; + return { head: sentinel, tail: sentinel, size: 0 }; +} - function createMapEntry(key: K, value: V): MapEntry { - return { key, value, next: undefined, prev: undefined }; - } +function createMapEntry(key: K, value: V): MapEntry { + return { key, value, next: undefined, prev: undefined }; +} - function sameValueZero(x: unknown, y: unknown) { - // Treats -0 === 0 and NaN === NaN - return x === y || x !== x && y !== y; - } +function sameValueZero(x: unknown, y: unknown) { + // Treats -0 === 0 and NaN === NaN + return x === y || x !== x && y !== y; +} - function getPrev(entry: MapEntry) { - const prev = entry.prev; - // Entries without a 'prev' have been removed from the map. - // An entry whose 'prev' points to itself is the head of the list and is invalid here. - if (!prev || prev === entry) throw new Error("Illegal state"); - return prev; - } +function getPrev(entry: MapEntry) { + const prev = entry.prev; + // Entries without a 'prev' have been removed from the map. + // An entry whose 'prev' points to itself is the head of the list and is invalid here. + if (!prev || prev === entry) throw new Error("Illegal state"); + return prev; +} - function getNext(entry: MapEntry | undefined) { - while (entry) { - // Entries without a 'prev' have been removed from the map. Their 'next' - // pointer should point to the previous entry prior to deletion and - // that entry should be skipped to resume iteration. - const skipNext = !entry.prev; - entry = entry.next; - if (skipNext) { - continue; - } - return entry; +function getNext(entry: MapEntry | undefined) { + while (entry) { + // Entries without a 'prev' have been removed from the map. Their 'next' + // pointer should point to the previous entry prior to deletion and + // that entry should be skipped to resume iteration. + const skipNext = !entry.prev; + entry = entry.next; + if (skipNext) { + continue; } + return entry; } +} - function getEntry(data: MapData, key: K): MapEntry | undefined { - // We walk backwards from 'tail' to prioritize recently added entries. - // We skip 'head' because it is an empty entry used to track iteration start. - for (let entry = data.tail; entry !== data.head; entry = getPrev(entry)) { - if (sameValueZero(entry.key, key)) { - return entry; - } +function getEntry(data: MapData, key: K): MapEntry | undefined { + // We walk backwards from 'tail' to prioritize recently added entries. + // We skip 'head' because it is an empty entry used to track iteration start. + for (let entry = data.tail; entry !== data.head; entry = getPrev(entry)) { + if (sameValueZero(entry.key, key)) { + return entry; } } +} - function addOrUpdateEntry(data: MapData, key: K, value: V): MapEntry | undefined { - const existing = getEntry(data, key); - if (existing) { - existing.value = value; - return; - } - - const entry = createMapEntry(key, value); - entry.prev = data.tail; - data.tail.next = entry; - data.tail = entry; - data.size++; - return entry; +function addOrUpdateEntry(data: MapData, key: K, value: V): MapEntry | undefined { + const existing = getEntry(data, key); + if (existing) { + existing.value = value; + return; } - function deleteEntry(data: MapData, key: K): MapEntry | undefined { - // We walk backwards from 'tail' to prioritize recently added entries. - // We skip 'head' because it is an empty entry used to track iteration start. - for (let entry = data.tail; entry !== data.head; entry = getPrev(entry)) { - // all entries in the map should have a 'prev' pointer. - if (entry.prev === undefined) throw new Error("Illegal state"); - if (sameValueZero(entry.key, key)) { - if (entry.next) { - entry.next.prev = entry.prev; - } - else { - // an entry in the map without a 'next' pointer must be the 'tail'. - if (data.tail !== entry) throw new Error("Illegal state"); - data.tail = entry.prev; - } + const entry = createMapEntry(key, value); + entry.prev = data.tail; + data.tail.next = entry; + data.tail = entry; + data.size++; + return entry; +} - entry.prev.next = entry.next; - entry.next = entry.prev; - entry.prev = undefined; - data.size--; - return entry; +function deleteEntry(data: MapData, key: K): MapEntry | undefined { + // We walk backwards from 'tail' to prioritize recently added entries. + // We skip 'head' because it is an empty entry used to track iteration start. + for (let entry = data.tail; entry !== data.head; entry = getPrev(entry)) { + // all entries in the map should have a 'prev' pointer. + if (entry.prev === undefined) throw new Error("Illegal state"); + if (sameValueZero(entry.key, key)) { + if (entry.next) { + entry.next.prev = entry.prev; } + else { + // an entry in the map without a 'next' pointer must be the 'tail'. + if (data.tail !== entry) throw new Error("Illegal state"); + data.tail = entry.prev; + } + + entry.prev.next = entry.next; + entry.next = entry.prev; + entry.prev = undefined; + data.size--; + return entry; } } +} - function clearEntries(data: MapData) { - let node = data.tail; - while (node !== data.head) { - const prev = getPrev(node); - node.next = data.head; - node.prev = undefined; - node = prev; - } - data.head.next = undefined; - data.tail = data.head; - data.size = 0; +function clearEntries(data: MapData) { + let node = data.tail; + while (node !== data.head) { + const prev = getPrev(node); + node.next = data.head; + node.prev = undefined; + node = prev; } + data.head.next = undefined; + data.tail = data.head; + data.size = 0; +} - function forEachEntry(data: MapData, action: (value: V, key: K) => void) { - let entry: MapEntry | undefined = data.head; - while (entry) { - entry = getNext(entry); - if (entry) { - action(entry.value!, entry.key!); - } +function forEachEntry(data: MapData, action: (value: V, key: K) => void) { + let entry: MapEntry | undefined = data.head; + while (entry) { + entry = getNext(entry); + if (entry) { + action(entry.value!, entry.key!); } } +} - function forEachIteration(iterator: IteratorShim | undefined, action: (value: any) => void) { - if (iterator) { - for (let step = iterator.next(); !step.done; step = iterator.next()) { - action(step.value); - } +function forEachIteration(iterator: IteratorShim | undefined, action: (value: any) => void) { + if (iterator) { + for (let step = iterator.next(); !step.done; step = iterator.next()) { + action(step.value); } } +} - function createIteratorData(data: MapData, selector: (key: K, value: V) => U): IteratorData { - return { current: data.head, selector }; - } +function createIteratorData(data: MapData, selector: (key: K, value: V) => U): IteratorData { + return { current: data.head, selector }; +} - function iteratorNext(data: IteratorData): IteratorResultShim { - // Navigate to the next entry. - data.current = getNext(data.current); - if (data.current) { - return { value: data.selector(data.current.key!, data.current.value!), done: false }; - } - else { - return { value: undefined as never, done: true }; - } +function iteratorNext(data: IteratorData): IteratorResultShim { + // Navigate to the next entry. + data.current = getNext(data.current); + if (data.current) { + return { value: data.selector(data.current.key!, data.current.value!), done: false }; } + else { + return { value: undefined as never, done: true }; + } +} - /* @internal */ - export namespace ShimCollections { - export function createMapShim(getIterator: GetIteratorCallback): MapShimConstructor { - class MapIterator { - private _data: IteratorData; - constructor(data: MapData, selector: (key: K, value: V) => U) { - this._data = createIteratorData(data, selector); - } - next() { return iteratorNext(this._data); } +/* @internal */ +export namespace ShimCollections { + export function createMapShim(getIterator: GetIteratorCallback): MapShimConstructor { + class MapIterator { + private _data: IteratorData; + constructor(data: MapData, selector: (key: K, value: V) => U) { + this._data = createIteratorData(data, selector); } - return class Map implements MapShim { - private _mapData = createMapData(); - constructor(iterable?: readonly (readonly [K, V])[] | ReadonlyMapShim) { - forEachIteration(getIterator(iterable), ([key, value]) => this.set(key, value)); - } - get size() { return this._mapData.size; } - get(key: K): V | undefined { return getEntry(this._mapData, key)?.value; } - set(key: K, value: V): this { return addOrUpdateEntry(this._mapData, key, value), this; } - has(key: K): boolean { return !!getEntry(this._mapData, key); } - delete(key: K): boolean { return !!deleteEntry(this._mapData, key); } - clear(): void { clearEntries(this._mapData); } - keys(): IteratorShim { return new MapIterator(this._mapData, (key, _value) => key); } - values(): IteratorShim { return new MapIterator(this._mapData, (_key, value) => value); } - entries(): IteratorShim<[K, V]> { return new MapIterator(this._mapData, (key, value) => [key, value]); } - forEach(action: (value: V, key: K) => void): void { forEachEntry(this._mapData, action); } - }; + next() { return iteratorNext(this._data); } } + return class Map implements MapShim { + private _mapData = createMapData(); + constructor(iterable?: readonly (readonly [K, V])[] | ReadonlyMapShim) { + forEachIteration(getIterator(iterable), ([key, value]) => this.set(key, value)); + } + get size() { return this._mapData.size; } + get(key: K): V | undefined { return getEntry(this._mapData, key)?.value; } + set(key: K, value: V): this { return addOrUpdateEntry(this._mapData, key, value), this; } + has(key: K): boolean { return !!getEntry(this._mapData, key); } + delete(key: K): boolean { return !!deleteEntry(this._mapData, key); } + clear(): void { clearEntries(this._mapData); } + keys(): IteratorShim { return new MapIterator(this._mapData, (key, _value) => key); } + values(): IteratorShim { return new MapIterator(this._mapData, (_key, value) => value); } + entries(): IteratorShim<[K, V]> { return new MapIterator(this._mapData, (key, value) => [key, value]); } + forEach(action: (value: V, key: K) => void): void { forEachEntry(this._mapData, action); } + }; + } - export function createSetShim(getIterator: GetIteratorCallback): SetShimConstructor { - class SetIterator { - private _data: IteratorData; - constructor(data: MapData, selector: (key: K, value: V) => U) { - this._data = createIteratorData(data, selector); - } - next() { return iteratorNext(this._data); } + export function createSetShim(getIterator: GetIteratorCallback): SetShimConstructor { + class SetIterator { + private _data: IteratorData; + constructor(data: MapData, selector: (key: K, value: V) => U) { + this._data = createIteratorData(data, selector); } - return class Set implements SetShim { - private _mapData = createMapData(); - constructor(iterable?: readonly T[] | ReadonlySetShim) { - forEachIteration(getIterator(iterable), value => this.add(value)); - } - get size() { return this._mapData.size; } - add(value: T): this { return addOrUpdateEntry(this._mapData, value, value), this; } - has(value: T): boolean { return !!getEntry(this._mapData, value); } - delete(value: T): boolean { return !!deleteEntry(this._mapData, value); } - clear(): void { clearEntries(this._mapData); } - keys(): IteratorShim { return new SetIterator(this._mapData, (key, _value) => key); } - values(): IteratorShim { return new SetIterator(this._mapData, (_key, value) => value); } - entries(): IteratorShim<[T, T]> { return new SetIterator(this._mapData, (key, value) => [key, value]); } - forEach(action: (value: T, key: T) => void): void { forEachEntry(this._mapData, action); } - }; + next() { return iteratorNext(this._data); } } + return class Set implements SetShim { + private _mapData = createMapData(); + constructor(iterable?: readonly T[] | ReadonlySetShim) { + forEachIteration(getIterator(iterable), value => this.add(value)); + } + get size() { return this._mapData.size; } + add(value: T): this { return addOrUpdateEntry(this._mapData, value, value), this; } + has(value: T): boolean { return !!getEntry(this._mapData, value); } + delete(value: T): boolean { return !!deleteEntry(this._mapData, value); } + clear(): void { clearEntries(this._mapData); } + keys(): IteratorShim { return new SetIterator(this._mapData, (key, _value) => key); } + values(): IteratorShim { return new SetIterator(this._mapData, (_key, value) => value); } + entries(): IteratorShim<[T, T]> { return new SetIterator(this._mapData, (key, value) => [key, value]); } + forEach(action: (value: T, key: T) => void): void { forEachEntry(this._mapData, action); } + }; } } +} diff --git a/src/testRunner/compilerRef.ts b/src/testRunner/compilerRef.ts index b76e4e72fbb88..245705db5ddac 100644 --- a/src/testRunner/compilerRef.ts +++ b/src/testRunner/compilerRef.ts @@ -1,2 +1,2 @@ // empty ref to compiler so it can be referenced by unittests -namespace compiler {} \ No newline at end of file +namespace compiler {} diff --git a/src/testRunner/compilerRunner.ts b/src/testRunner/compilerRunner.ts index 166ed164f77dd..1847d5b6bd7b7 100644 --- a/src/testRunner/compilerRunner.ts +++ b/src/testRunner/compilerRunner.ts @@ -1,339 +1,339 @@ namespace Harness { - export const enum CompilerTestType { - Conformance, - Regressions, - Test262 - } +export const enum CompilerTestType { + Conformance, + Regressions, + Test262 +} - interface CompilerFileBasedTest extends FileBasedTest { - readonly content?: string; - } +interface CompilerFileBasedTest extends FileBasedTest { + readonly content?: string; +} - export class CompilerBaselineRunner extends RunnerBase { - private basePath = "tests/cases"; - private testSuiteName: TestRunnerKind; - private emit: boolean; +export class CompilerBaselineRunner extends RunnerBase { + private basePath = "tests/cases"; + private testSuiteName: TestRunnerKind; + private emit: boolean; - public options: string | undefined; + public options: string | undefined; - constructor(public testType: CompilerTestType) { - super(); - this.emit = true; - if (testType === CompilerTestType.Conformance) { - this.testSuiteName = "conformance"; - } - else if (testType === CompilerTestType.Regressions) { - this.testSuiteName = "compiler"; - } - else if (testType === CompilerTestType.Test262) { - this.testSuiteName = "test262"; - } - else { - this.testSuiteName = "compiler"; // default to this for historical reasons - } - this.basePath += "/" + this.testSuiteName; + constructor(public testType: CompilerTestType) { + super(); + this.emit = true; + if (testType === CompilerTestType.Conformance) { + this.testSuiteName = "conformance"; } - - public kind() { - return this.testSuiteName; + else if (testType === CompilerTestType.Regressions) { + this.testSuiteName = "compiler"; } - - public enumerateTestFiles() { - // see also: `enumerateTestFiles` in tests/webTestServer.ts - return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true }).map(CompilerTest.getConfigurations); + else if (testType === CompilerTestType.Test262) { + this.testSuiteName = "test262"; + } + else { + this.testSuiteName = "compiler"; // default to this for historical reasons } + this.basePath += "/" + this.testSuiteName; + } - public initializeTests() { - describe(this.testSuiteName + " tests", () => { - describe("Setup compiler for compiler baselines", () => { - this.parseOptions(); - }); + public kind() { + return this.testSuiteName; + } - // this will set up a series of describe/it blocks to run between the setup and cleanup phases - const files = this.tests.length > 0 ? this.tests : IO.enumerateTestFiles(this); - files.forEach(test => { - const file = typeof test === "string" ? test : test.file; - this.checkTestCodeOutput(vpath.normalizeSeparators(file), typeof test === "string" ? CompilerTest.getConfigurations(test) : test); - }); + public enumerateTestFiles() { + // see also: `enumerateTestFiles` in tests/webTestServer.ts + return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true }).map(CompilerTest.getConfigurations); + } + + public initializeTests() { + describe(this.testSuiteName + " tests", () => { + describe("Setup compiler for compiler baselines", () => { + this.parseOptions(); }); - } - public checkTestCodeOutput(fileName: string, test?: CompilerFileBasedTest) { - if (test && ts.some(test.configurations)) { - test.configurations.forEach(configuration => { - describe(`${this.testSuiteName} tests for ${fileName}${configuration ? ` (${getFileBasedTestConfigurationDescription(configuration)})` : ``}`, () => { - this.runSuite(fileName, test, configuration); - }); - }); - } - else { - describe(`${this.testSuiteName} tests for ${fileName}`, () => { - this.runSuite(fileName, test); - }); - } - } + // this will set up a series of describe/it blocks to run between the setup and cleanup phases + const files = this.tests.length > 0 ? this.tests : IO.enumerateTestFiles(this); + files.forEach(test => { + const file = typeof test === "string" ? test : test.file; + this.checkTestCodeOutput(vpath.normalizeSeparators(file), typeof test === "string" ? CompilerTest.getConfigurations(test) : test); + }); + }); + } - private runSuite(fileName: string, test?: CompilerFileBasedTest, configuration?: FileBasedTestConfiguration) { - // Mocha holds onto the closure environment of the describe callback even after the test is done. - // Everything declared here should be cleared out in the "after" callback. - let compilerTest!: CompilerTest; - before(() => { - let payload; - if (test && test.content) { - const rootDir = test.file.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(test.file) + "/"; - payload = TestCaseParser.makeUnitsFromTest(test.content, test.file, rootDir); - } - compilerTest = new CompilerTest(fileName, payload, configuration); + public checkTestCodeOutput(fileName: string, test?: CompilerFileBasedTest) { + if (test && ts.some(test.configurations)) { + test.configurations.forEach(configuration => { + describe(`${this.testSuiteName} tests for ${fileName}${configuration ? ` (${getFileBasedTestConfigurationDescription(configuration)})` : ``}`, () => { + this.runSuite(fileName, test, configuration); + }); }); - it(`Correct errors for ${fileName}`, () => compilerTest.verifyDiagnostics()); - it(`Correct module resolution tracing for ${fileName}`, () => compilerTest.verifyModuleResolution()); - it(`Correct sourcemap content for ${fileName}`, () => compilerTest.verifySourceMapRecord()); - it(`Correct JS output for ${fileName}`, () => (this.emit && compilerTest.verifyJavaScriptOutput())); - it(`Correct Sourcemap output for ${fileName}`, () => compilerTest.verifySourceMapOutput()); - it(`Correct type/symbol baselines for ${fileName}`, () => compilerTest.verifyTypesAndSymbols()); - after(() => { - compilerTest = undefined!; + } + else { + describe(`${this.testSuiteName} tests for ${fileName}`, () => { + this.runSuite(fileName, test); }); } + } + + private runSuite(fileName: string, test?: CompilerFileBasedTest, configuration?: FileBasedTestConfiguration) { + // Mocha holds onto the closure environment of the describe callback even after the test is done. + // Everything declared here should be cleared out in the "after" callback. + let compilerTest!: CompilerTest; + before(() => { + let payload; + if (test && test.content) { + const rootDir = test.file.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(test.file) + "/"; + payload = TestCaseParser.makeUnitsFromTest(test.content, test.file, rootDir); + } + compilerTest = new CompilerTest(fileName, payload, configuration); + }); + it(`Correct errors for ${fileName}`, () => compilerTest.verifyDiagnostics()); + it(`Correct module resolution tracing for ${fileName}`, () => compilerTest.verifyModuleResolution()); + it(`Correct sourcemap content for ${fileName}`, () => compilerTest.verifySourceMapRecord()); + it(`Correct JS output for ${fileName}`, () => (this.emit && compilerTest.verifyJavaScriptOutput())); + it(`Correct Sourcemap output for ${fileName}`, () => compilerTest.verifySourceMapOutput()); + it(`Correct type/symbol baselines for ${fileName}`, () => compilerTest.verifyTypesAndSymbols()); + after(() => { + compilerTest = undefined!; + }); + } - private parseOptions() { - if (this.options && this.options.length > 0) { - this.emit = false; - - const opts = this.options.split(","); - for (const opt of opts) { - switch (opt) { - case "emit": - this.emit = true; - break; - default: - throw new Error("unsupported flag"); - } + private parseOptions() { + if (this.options && this.options.length > 0) { + this.emit = false; + + const opts = this.options.split(","); + for (const opt of opts) { + switch (opt) { + case "emit": + this.emit = true; + break; + default: + throw new Error("unsupported flag"); } } } } +} - class CompilerTest { - private static varyBy: readonly string[] = [ - "module", - "moduleResolution", - "target", - "jsx", - "removeComments", - "importHelpers", - "importHelpers", - "downlevelIteration", - "isolatedModules", - "strict", - "noImplicitAny", - "strictNullChecks", - "strictFunctionTypes", - "strictBindCallApply", - "strictPropertyInitialization", - "noImplicitThis", - "alwaysStrict", - "allowSyntheticDefaultImports", - "esModuleInterop", - "emitDecoratorMetadata", - "skipDefaultLibCheck", - "preserveConstEnums", - "skipLibCheck", - "exactOptionalPropertyTypes", - "useUnknownInCatchVariables" - ]; - private fileName: string; - private justName: string; - private configuredName: string; - private lastUnit: TestCaseParser.TestUnitData; - private harnessSettings: TestCaseParser.CompilerSettings; - private hasNonDtsFiles: boolean; - private result: compiler.CompilationResult; - private options: ts.CompilerOptions; - private tsConfigFiles: Compiler.TestFile[]; - // equivalent to the files that will be passed on the command line - private toBeCompiled: Compiler.TestFile[]; - // equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files) - private otherFiles: Compiler.TestFile[]; - - constructor(fileName: string, testCaseContent?: TestCaseParser.TestCaseContent, configurationOverrides?: TestCaseParser.CompilerSettings) { - this.fileName = fileName; - this.justName = vpath.basename(fileName); - this.configuredName = this.justName; - if (configurationOverrides) { - let configuredName = ""; - const keys = Object - .keys(configurationOverrides) - .sort(); - for (const key of keys) { - if (configuredName) { - configuredName += ","; - } - configuredName += `${key.toLowerCase()}=${configurationOverrides[key].toLowerCase()}`; - } +class CompilerTest { + private static varyBy: readonly string[] = [ + "module", + "moduleResolution", + "target", + "jsx", + "removeComments", + "importHelpers", + "importHelpers", + "downlevelIteration", + "isolatedModules", + "strict", + "noImplicitAny", + "strictNullChecks", + "strictFunctionTypes", + "strictBindCallApply", + "strictPropertyInitialization", + "noImplicitThis", + "alwaysStrict", + "allowSyntheticDefaultImports", + "esModuleInterop", + "emitDecoratorMetadata", + "skipDefaultLibCheck", + "preserveConstEnums", + "skipLibCheck", + "exactOptionalPropertyTypes", + "useUnknownInCatchVariables" + ]; + private fileName: string; + private justName: string; + private configuredName: string; + private lastUnit: TestCaseParser.TestUnitData; + private harnessSettings: TestCaseParser.CompilerSettings; + private hasNonDtsFiles: boolean; + private result: compiler.CompilationResult; + private options: ts.CompilerOptions; + private tsConfigFiles: Compiler.TestFile[]; + // equivalent to the files that will be passed on the command line + private toBeCompiled: Compiler.TestFile[]; + // equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files) + private otherFiles: Compiler.TestFile[]; + + constructor(fileName: string, testCaseContent?: TestCaseParser.TestCaseContent, configurationOverrides?: TestCaseParser.CompilerSettings) { + this.fileName = fileName; + this.justName = vpath.basename(fileName); + this.configuredName = this.justName; + if (configurationOverrides) { + let configuredName = ""; + const keys = Object + .keys(configurationOverrides) + .sort(); + for (const key of keys) { if (configuredName) { - const extname = vpath.extname(this.justName); - const basename = vpath.basename(this.justName, extname, /*ignoreCase*/ true); - this.configuredName = `${basename}(${configuredName})${extname}`; + configuredName += ","; } + configuredName += `${key.toLowerCase()}=${configurationOverrides[key].toLowerCase()}`; } - - const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/"; - - if (testCaseContent === undefined) { - testCaseContent = TestCaseParser.makeUnitsFromTest(IO.readFile(fileName)!, fileName, rootDir); + if (configuredName) { + const extname = vpath.extname(this.justName); + const basename = vpath.basename(this.justName, extname, /*ignoreCase*/ true); + this.configuredName = `${basename}(${configuredName})${extname}`; } + } - if (configurationOverrides) { - testCaseContent = { ...testCaseContent, settings: { ...testCaseContent.settings, ...configurationOverrides } }; - } + const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/"; - const units = testCaseContent.testUnitData; - this.harnessSettings = testCaseContent.settings; - let tsConfigOptions: ts.CompilerOptions | undefined; - this.tsConfigFiles = []; - if (testCaseContent.tsConfig) { - assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`); - assert.equal(testCaseContent.tsConfig.raw.exclude, undefined, `exclude in tsconfig is not currently supported`); + if (testCaseContent === undefined) { + testCaseContent = TestCaseParser.makeUnitsFromTest(IO.readFile(fileName)!, fileName, rootDir); + } - tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options); - this.tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData!, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath))); - } - else { - const baseUrl = this.harnessSettings.baseUrl; - if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) { - this.harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir); - } - } + if (configurationOverrides) { + testCaseContent = { ...testCaseContent, settings: { ...testCaseContent.settings, ...configurationOverrides } }; + } - this.lastUnit = units[units.length - 1]; - this.hasNonDtsFiles = units.some(unit => !ts.fileExtensionIs(unit.name, ts.Extension.Dts)); - // We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test) - // If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references, - // otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another. - this.toBeCompiled = []; - this.otherFiles = []; - - if (testCaseContent.settings.noImplicitReferences || /require\(/.test(this.lastUnit.content) || /reference\spath/.test(this.lastUnit.content)) { - this.toBeCompiled.push(this.createHarnessTestFile(this.lastUnit, rootDir)); - units.forEach(unit => { - if (unit.name !== this.lastUnit.name) { - this.otherFiles.push(this.createHarnessTestFile(unit, rootDir)); - } - }); - } - else { - this.toBeCompiled = units.map(unit => { - return this.createHarnessTestFile(unit, rootDir); - }); - } + const units = testCaseContent.testUnitData; + this.harnessSettings = testCaseContent.settings; + let tsConfigOptions: ts.CompilerOptions | undefined; + this.tsConfigFiles = []; + if (testCaseContent.tsConfig) { + assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`); + assert.equal(testCaseContent.tsConfig.raw.exclude, undefined, `exclude in tsconfig is not currently supported`); - if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) { - tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath); - tsConfigOptions.configFile!.fileName = tsConfigOptions.configFilePath; + tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options); + this.tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData!, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath))); + } + else { + const baseUrl = this.harnessSettings.baseUrl; + if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) { + this.harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir); } - - this.result = Compiler.compileFiles( - this.toBeCompiled, - this.otherFiles, - this.harnessSettings, - /*options*/ tsConfigOptions, - /*currentDirectory*/ this.harnessSettings.currentDirectory, - testCaseContent.symlinks - ); - - this.options = this.result.options; } - public static getConfigurations(file: string): CompilerFileBasedTest { - // also see `parseCompilerTestConfigurations` in tests/webTestServer.ts - const content = IO.readFile(file)!; - const settings = TestCaseParser.extractCompilerSettings(content); - const configurations = getFileBasedTestConfigurations(settings, CompilerTest.varyBy); - return { file, configurations, content }; + this.lastUnit = units[units.length - 1]; + this.hasNonDtsFiles = units.some(unit => !ts.fileExtensionIs(unit.name, ts.Extension.Dts)); + // We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test) + // If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references, + // otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another. + this.toBeCompiled = []; + this.otherFiles = []; + + if (testCaseContent.settings.noImplicitReferences || /require\(/.test(this.lastUnit.content) || /reference\spath/.test(this.lastUnit.content)) { + this.toBeCompiled.push(this.createHarnessTestFile(this.lastUnit, rootDir)); + units.forEach(unit => { + if (unit.name !== this.lastUnit.name) { + this.otherFiles.push(this.createHarnessTestFile(unit, rootDir)); + } + }); } - - public verifyDiagnostics() { - // check errors - Compiler.doErrorBaseline( - this.configuredName, - this.tsConfigFiles.concat(this.toBeCompiled, this.otherFiles), - this.result.diagnostics, - !!this.options.pretty); + else { + this.toBeCompiled = units.map(unit => { + return this.createHarnessTestFile(unit, rootDir); + }); } - public verifyModuleResolution() { - if (this.options.traceResolution) { - Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".trace.json"), - JSON.stringify(this.result.traces.map(Utils.sanitizeTraceResolutionLogEntry), undefined, 4)); - } + if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) { + tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath); + tsConfigOptions.configFile!.fileName = tsConfigOptions.configFilePath; } - public verifySourceMapRecord() { - if (this.options.sourceMap || this.options.inlineSourceMap || this.options.declarationMap) { - const record = Utils.removeTestPathPrefixes(this.result.getSourceMapRecord()!); - const baseline = (this.options.noEmitOnError && this.result.diagnostics.length !== 0) || record === undefined - // Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required. - ? null // eslint-disable-line no-null/no-null - : record; - Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".sourcemap.txt"), baseline); - } + this.result = Compiler.compileFiles( + this.toBeCompiled, + this.otherFiles, + this.harnessSettings, + /*options*/ tsConfigOptions, + /*currentDirectory*/ this.harnessSettings.currentDirectory, + testCaseContent.symlinks + ); + + this.options = this.result.options; + } + + public static getConfigurations(file: string): CompilerFileBasedTest { + // also see `parseCompilerTestConfigurations` in tests/webTestServer.ts + const content = IO.readFile(file)!; + const settings = TestCaseParser.extractCompilerSettings(content); + const configurations = getFileBasedTestConfigurations(settings, CompilerTest.varyBy); + return { file, configurations, content }; + } + + public verifyDiagnostics() { + // check errors + Compiler.doErrorBaseline( + this.configuredName, + this.tsConfigFiles.concat(this.toBeCompiled, this.otherFiles), + this.result.diagnostics, + !!this.options.pretty); + } + + public verifyModuleResolution() { + if (this.options.traceResolution) { + Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".trace.json"), + JSON.stringify(this.result.traces.map(Utils.sanitizeTraceResolutionLogEntry), undefined, 4)); } + } - public verifyJavaScriptOutput() { - if (this.hasNonDtsFiles) { - Compiler.doJsEmitBaseline( - this.configuredName, - this.fileName, - this.options, - this.result, - this.tsConfigFiles, - this.toBeCompiled, - this.otherFiles, - this.harnessSettings); - } + public verifySourceMapRecord() { + if (this.options.sourceMap || this.options.inlineSourceMap || this.options.declarationMap) { + const record = Utils.removeTestPathPrefixes(this.result.getSourceMapRecord()!); + const baseline = (this.options.noEmitOnError && this.result.diagnostics.length !== 0) || record === undefined + // Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required. + ? null // eslint-disable-line no-null/no-null + : record; + Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".sourcemap.txt"), baseline); } + } - public verifySourceMapOutput() { - Compiler.doSourcemapBaseline( + public verifyJavaScriptOutput() { + if (this.hasNonDtsFiles) { + Compiler.doJsEmitBaseline( this.configuredName, + this.fileName, this.options, this.result, + this.tsConfigFiles, + this.toBeCompiled, + this.otherFiles, this.harnessSettings); } + } - public verifyTypesAndSymbols() { - if (this.fileName.indexOf("APISample") >= 0) { - return; - } - - const noTypesAndSymbols = - this.harnessSettings.noTypesAndSymbols && - this.harnessSettings.noTypesAndSymbols.toLowerCase() === "true"; - if (noTypesAndSymbols) { - return; - } + public verifySourceMapOutput() { + Compiler.doSourcemapBaseline( + this.configuredName, + this.options, + this.result, + this.harnessSettings); + } - Compiler.doTypeAndSymbolBaseline( - this.configuredName, - this.result.program!, - this.toBeCompiled.concat(this.otherFiles).filter(file => !!this.result.program!.getSourceFile(file.unitName)), - /*opts*/ undefined, - /*multifile*/ undefined, - /*skipTypeBaselines*/ undefined, - /*skipSymbolBaselines*/ undefined, - !!ts.length(this.result.diagnostics) - ); + public verifyTypesAndSymbols() { + if (this.fileName.indexOf("APISample") >= 0) { + return; } - private makeUnitName(name: string, root: string) { - const path = ts.toPath(name, root, ts.identity); - const pathStart = ts.toPath(IO.getCurrentDirectory(), "", ts.identity); - return pathStart ? path.replace(pathStart, "/") : path; + const noTypesAndSymbols = + this.harnessSettings.noTypesAndSymbols && + this.harnessSettings.noTypesAndSymbols.toLowerCase() === "true"; + if (noTypesAndSymbols) { + return; } - private createHarnessTestFile(lastUnit: TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Compiler.TestFile { - return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions }; - } + Compiler.doTypeAndSymbolBaseline( + this.configuredName, + this.result.program!, + this.toBeCompiled.concat(this.otherFiles).filter(file => !!this.result.program!.getSourceFile(file.unitName)), + /*opts*/ undefined, + /*multifile*/ undefined, + /*skipTypeBaselines*/ undefined, + /*skipSymbolBaselines*/ undefined, + !!ts.length(this.result.diagnostics) + ); } + + private makeUnitName(name: string, root: string) { + const path = ts.toPath(name, root, ts.identity); + const pathStart = ts.toPath(IO.getCurrentDirectory(), "", ts.identity); + return pathStart ? path.replace(pathStart, "/") : path; + } + + private createHarnessTestFile(lastUnit: TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Compiler.TestFile { + return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions }; + } +} } diff --git a/src/testRunner/documentsRef.ts b/src/testRunner/documentsRef.ts index d3d92746b4ed7..c1f869b7bd190 100644 --- a/src/testRunner/documentsRef.ts +++ b/src/testRunner/documentsRef.ts @@ -1,2 +1,2 @@ // empty ref to documents so it can be referenced by unittests -namespace documents {} \ No newline at end of file +namespace documents {} diff --git a/src/testRunner/evaluatorRef.ts b/src/testRunner/evaluatorRef.ts index cc81ec2402db5..a034813f9699f 100644 --- a/src/testRunner/evaluatorRef.ts +++ b/src/testRunner/evaluatorRef.ts @@ -1,2 +1,2 @@ // empty ref to evaluator so it can be referenced by unittests -namespace evaluator {} \ No newline at end of file +namespace evaluator {} diff --git a/src/testRunner/externalCompileRunner.ts b/src/testRunner/externalCompileRunner.ts index 2b3a2ef9957dc..8703663b82eb1 100644 --- a/src/testRunner/externalCompileRunner.ts +++ b/src/testRunner/externalCompileRunner.ts @@ -1,349 +1,349 @@ namespace Harness { - const fs: typeof import("fs") = require("fs"); - const path: typeof import("path") = require("path"); - const del: typeof import("del") = require("del"); - - interface ExecResult { - stdout: Buffer; - stderr: Buffer; - status: number | null; - } +const fs: typeof import("fs") = require("fs"); +const path: typeof import("path") = require("path"); +const del: typeof import("del") = require("del"); + +interface ExecResult { + stdout: Buffer; + stderr: Buffer; + status: number | null; +} + +interface UserConfig { + types: string[]; + cloneUrl: string; + path?: string; +} - interface UserConfig { - types: string[]; - cloneUrl: string; - path?: string; +abstract class ExternalCompileRunnerBase extends RunnerBase { + abstract testDir: string; + abstract report(result: ExecResult, cwd: string): string | null; + enumerateTestFiles() { + return IO.getDirectories(this.testDir); } + /** Setup the runner's tests so that they are ready to be executed by the harness + * The first test should be a describe/it block that sets up the harness's compiler instance appropriately + */ + initializeTests(): void { + // Read in and evaluate the test list + const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles(); + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const cls = this; + describe(`${this.kind()} code samples`, function (this: Mocha.Suite) { + this.timeout(600_000); // 10 minutes + for (const test of testList) { + cls.runTest(typeof test === "string" ? test : test.file); + } + }); + } + private runTest(directoryName: string) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const cls = this; + const timeout = 600_000; // 10 minutes + describe(directoryName, function (this: Mocha.Suite) { + this.timeout(timeout); + const cp: typeof import("child_process") = require("child_process"); - abstract class ExternalCompileRunnerBase extends RunnerBase { - abstract testDir: string; - abstract report(result: ExecResult, cwd: string): string | null; - enumerateTestFiles() { - return IO.getDirectories(this.testDir); - } - /** Setup the runner's tests so that they are ready to be executed by the harness - * The first test should be a describe/it block that sets up the harness's compiler instance appropriately - */ - initializeTests(): void { - // Read in and evaluate the test list - const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles(); - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const cls = this; - describe(`${this.kind()} code samples`, function (this: Mocha.Suite) { - this.timeout(600_000); // 10 minutes - for (const test of testList) { - cls.runTest(typeof test === "string" ? test : test.file); + it("should build successfully", () => { + let cwd = path.join(IO.getWorkspaceRoot(), cls.testDir, directoryName); + const originalCwd = cwd; + const stdio = isWorker ? "pipe" : "inherit"; + let types: string[] | undefined; + if (fs.existsSync(path.join(cwd, "test.json"))) { + const config = JSON.parse(fs.readFileSync(path.join(cwd, "test.json"), { encoding: "utf8" })) as UserConfig; + ts.Debug.assert(!!config.types, "Bad format from test.json: Types field must be present."); + ts.Debug.assert(!!config.cloneUrl, "Bad format from test.json: cloneUrl field must be present."); + const submoduleDir = path.join(cwd, directoryName); + if (!fs.existsSync(submoduleDir)) { + exec("git", ["--work-tree", submoduleDir, "clone", config.cloneUrl, path.join(submoduleDir, ".git")], { cwd }); + } + else { + exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "reset", "HEAD", "--hard"], { cwd: submoduleDir }); + exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "clean", "-f"], { cwd: submoduleDir }); + exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "pull", "-f"], { cwd: submoduleDir }); + } + + types = config.types; + + cwd = config.path ? path.join(cwd, config.path) : submoduleDir; } - }); - } - private runTest(directoryName: string) { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const cls = this; - const timeout = 600_000; // 10 minutes - describe(directoryName, function (this: Mocha.Suite) { - this.timeout(timeout); - const cp: typeof import("child_process") = require("child_process"); - - it("should build successfully", () => { - let cwd = path.join(IO.getWorkspaceRoot(), cls.testDir, directoryName); - const originalCwd = cwd; - const stdio = isWorker ? "pipe" : "inherit"; - let types: string[] | undefined; - if (fs.existsSync(path.join(cwd, "test.json"))) { - const config = JSON.parse(fs.readFileSync(path.join(cwd, "test.json"), { encoding: "utf8" })) as UserConfig; - ts.Debug.assert(!!config.types, "Bad format from test.json: Types field must be present."); - ts.Debug.assert(!!config.cloneUrl, "Bad format from test.json: cloneUrl field must be present."); - const submoduleDir = path.join(cwd, directoryName); - if (!fs.existsSync(submoduleDir)) { - exec("git", ["--work-tree", submoduleDir, "clone", config.cloneUrl, path.join(submoduleDir, ".git")], { cwd }); - } - else { - exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "reset", "HEAD", "--hard"], { cwd: submoduleDir }); - exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "clean", "-f"], { cwd: submoduleDir }); - exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "pull", "-f"], { cwd: submoduleDir }); - } - - types = config.types; - - cwd = config.path ? path.join(cwd, config.path) : submoduleDir; + const npmVersionText = exec("npm", ["--version"], { cwd, stdio: "pipe" })?.trim(); + const npmVersion = npmVersionText ? ts.Version.tryParse(npmVersionText.trim()) : undefined; + const isV7OrLater = !!npmVersion && npmVersion.major >= 7; + if (fs.existsSync(path.join(cwd, "package.json"))) { + if (fs.existsSync(path.join(cwd, "package-lock.json"))) { + fs.unlinkSync(path.join(cwd, "package-lock.json")); } - const npmVersionText = exec("npm", ["--version"], { cwd, stdio: "pipe" })?.trim(); - const npmVersion = npmVersionText ? ts.Version.tryParse(npmVersionText.trim()) : undefined; - const isV7OrLater = !!npmVersion && npmVersion.major >= 7; - if (fs.existsSync(path.join(cwd, "package.json"))) { - if (fs.existsSync(path.join(cwd, "package-lock.json"))) { - fs.unlinkSync(path.join(cwd, "package-lock.json")); - } - if (fs.existsSync(path.join(cwd, "node_modules"))) { - del.sync(path.join(cwd, "node_modules"), { force: true }); - } - exec("npm", ["i", "--ignore-scripts", ...(isV7OrLater ? ["--legacy-peer-deps"] : [])], { cwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure + if (fs.existsSync(path.join(cwd, "node_modules"))) { + del.sync(path.join(cwd, "node_modules"), { force: true }); } - const args = [path.join(IO.getWorkspaceRoot(), "built/local/tsc.js")]; - if (types) { - args.push("--types", types.join(",")); - // Also actually install those types (for, eg, the js projects which need node) - if (types.length) { - exec("npm", ["i", ...types.map(t => `@types/${t}`), "--no-save", "--ignore-scripts", ...(isV7OrLater ? ["--legacy-peer-deps"] : [])], { cwd: originalCwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure - } + exec("npm", ["i", "--ignore-scripts", ...(isV7OrLater ? ["--legacy-peer-deps"] : [])], { cwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure + } + const args = [path.join(IO.getWorkspaceRoot(), "built/local/tsc.js")]; + if (types) { + args.push("--types", types.join(",")); + // Also actually install those types (for, eg, the js projects which need node) + if (types.length) { + exec("npm", ["i", ...types.map(t => `@types/${t}`), "--no-save", "--ignore-scripts", ...(isV7OrLater ? ["--legacy-peer-deps"] : [])], { cwd: originalCwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure } - args.push("--noEmit"); - Baseline.runBaseline(`${cls.kind()}/${directoryName}.log`, cls.report(cp.spawnSync(`node`, args, { cwd, timeout, shell: true }), cwd)); - - function exec(command: string, args: string[], options: { cwd: string, timeout?: number, stdio?: import("child_process").StdioOptions }): string | undefined { - const res = cp.spawnSync(isWorker ? `${command} 2>&1` : command, args, { shell: true, stdio, ...options }); - if (res.status !== 0) { - throw new Error(`${command} ${args.join(" ")} for ${directoryName} failed: ${res.stdout && res.stdout.toString()}`); - } - return options.stdio === "pipe" ? res.stdout.toString("utf8") : undefined; + } + args.push("--noEmit"); + Baseline.runBaseline(`${cls.kind()}/${directoryName}.log`, cls.report(cp.spawnSync(`node`, args, { cwd, timeout, shell: true }), cwd)); + + function exec(command: string, args: string[], options: { cwd: string, timeout?: number, stdio?: import("child_process").StdioOptions }): string | undefined { + const res = cp.spawnSync(isWorker ? `${command} 2>&1` : command, args, { shell: true, stdio, ...options }); + if (res.status !== 0) { + throw new Error(`${command} ${args.join(" ")} for ${directoryName} failed: ${res.stdout && res.stdout.toString()}`); } - }); + return options.stdio === "pipe" ? res.stdout.toString("utf8") : undefined; + } }); - } + }); } +} - export class UserCodeRunner extends ExternalCompileRunnerBase { - readonly testDir = "tests/cases/user/"; - kind(): TestRunnerKind { - return "user"; - } - report(result: ExecResult) { - // eslint-disable-next-line no-null/no-null - return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} +export class UserCodeRunner extends ExternalCompileRunnerBase { + readonly testDir = "tests/cases/user/"; + kind(): TestRunnerKind { + return "user"; + } + report(result: ExecResult) { + // eslint-disable-next-line no-null/no-null + return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} Standard output: ${sortErrors(stripAbsoluteImportPaths(result.stdout.toString().replace(/\r\n/g, "\n")))} Standard error: ${stripAbsoluteImportPaths(result.stderr.toString().replace(/\r\n/g, "\n"))}`; - } } +} - export class DockerfileRunner extends ExternalCompileRunnerBase { - readonly testDir = "tests/cases/docker/"; - kind(): TestRunnerKind { - return "docker"; - } - initializeTests(): void { - // Read in and evaluate the test list - const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles(); - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const cls = this; - describe(`${this.kind()} code samples`, function (this: Mocha.Suite) { - this.timeout(cls.timeout); // 20 minutes - before(() => { - cls.exec("docker", ["build", ".", "-t", "typescript/typescript"], { cwd: IO.getWorkspaceRoot() }); // cached because workspace is hashed to determine cacheability - }); - for (const test of testList) { - const directory = typeof test === "string" ? test : test.file; - const cwd = path.join(IO.getWorkspaceRoot(), cls.testDir, directory); - it(`should build ${directory} successfully`, () => { - const imageName = `tstest/${directory}`; - cls.exec("docker", ["build", "--no-cache", ".", "-t", imageName], { cwd }); // --no-cache so the latest version of the repos referenced is always fetched - const cp: typeof import("child_process") = require("child_process"); - Baseline.runBaseline(`${cls.kind()}/${directory}.log`, cls.report(cp.spawnSync(`docker`, ["run", imageName], { cwd, timeout: cls.timeout, shell: true }))); - }); - } +export class DockerfileRunner extends ExternalCompileRunnerBase { + readonly testDir = "tests/cases/docker/"; + kind(): TestRunnerKind { + return "docker"; + } + initializeTests(): void { + // Read in and evaluate the test list + const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles(); + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const cls = this; + describe(`${this.kind()} code samples`, function (this: Mocha.Suite) { + this.timeout(cls.timeout); // 20 minutes + before(() => { + cls.exec("docker", ["build", ".", "-t", "typescript/typescript"], { cwd: IO.getWorkspaceRoot() }); // cached because workspace is hashed to determine cacheability }); - } - - private timeout = 1_200_000; // 20 minutes; - private exec(command: string, args: string[], options: { cwd: string }): void { - const cp: typeof import("child_process") = require("child_process"); - const stdio = isWorker ? "pipe" : "inherit"; - const res = cp.spawnSync(isWorker ? `${command} 2>&1` : command, args, { timeout: this.timeout, shell: true, stdio, ...options }); - if (res.status !== 0) { - throw new Error(`${command} ${args.join(" ")} for ${options.cwd} failed: ${res.stdout && res.stdout.toString()}`); + for (const test of testList) { + const directory = typeof test === "string" ? test : test.file; + const cwd = path.join(IO.getWorkspaceRoot(), cls.testDir, directory); + it(`should build ${directory} successfully`, () => { + const imageName = `tstest/${directory}`; + cls.exec("docker", ["build", "--no-cache", ".", "-t", imageName], { cwd }); // --no-cache so the latest version of the repos referenced is always fetched + const cp: typeof import("child_process") = require("child_process"); + Baseline.runBaseline(`${cls.kind()}/${directory}.log`, cls.report(cp.spawnSync(`docker`, ["run", imageName], { cwd, timeout: cls.timeout, shell: true }))); + }); } + }); + } + + private timeout = 1_200_000; // 20 minutes; + private exec(command: string, args: string[], options: { cwd: string }): void { + const cp: typeof import("child_process") = require("child_process"); + const stdio = isWorker ? "pipe" : "inherit"; + const res = cp.spawnSync(isWorker ? `${command} 2>&1` : command, args, { timeout: this.timeout, shell: true, stdio, ...options }); + if (res.status !== 0) { + throw new Error(`${command} ${args.join(" ")} for ${options.cwd} failed: ${res.stdout && res.stdout.toString()}`); } - report(result: ExecResult) { - // eslint-disable-next-line no-null/no-null - return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} + } + report(result: ExecResult) { + // eslint-disable-next-line no-null/no-null + return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} Standard output: ${sanitizeDockerfileOutput(result.stdout.toString())} Standard error: ${sanitizeDockerfileOutput(result.stderr.toString())}`; - } } +} - function sanitizeDockerfileOutput(result: string): string { - return [ - normalizeNewlines, - stripANSIEscapes, - stripRushStageNumbers, - stripWebpackHash, - sanitizeVersionSpecifiers, - sanitizeTimestamps, - sanitizeSizes, - sanitizeUnimportantGulpOutput, - stripAbsoluteImportPaths, - ].reduce((result, f) => f(result), result); - } +function sanitizeDockerfileOutput(result: string): string { + return [ + normalizeNewlines, + stripANSIEscapes, + stripRushStageNumbers, + stripWebpackHash, + sanitizeVersionSpecifiers, + sanitizeTimestamps, + sanitizeSizes, + sanitizeUnimportantGulpOutput, + stripAbsoluteImportPaths, + ].reduce((result, f) => f(result), result); +} - function normalizeNewlines(result: string): string { - return result.replace(/\r\n/g, "\n"); - } +function normalizeNewlines(result: string): string { + return result.replace(/\r\n/g, "\n"); +} - function stripANSIEscapes(result: string): string { - return result.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ""); - } +function stripANSIEscapes(result: string): string { + return result.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ""); +} - function stripRushStageNumbers(result: string): string { - return result.replace(/\d+ of \d+:/g, "XX of XX:"); - } +function stripRushStageNumbers(result: string): string { + return result.replace(/\d+ of \d+:/g, "XX of XX:"); +} - function stripWebpackHash(result: string): string { - return result.replace(/Hash: \w+/g, "Hash: [redacted]"); - } +function stripWebpackHash(result: string): string { + return result.replace(/Hash: \w+/g, "Hash: [redacted]"); +} - function sanitizeSizes(result: string): string { - return result.replace(/\d+(\.\d+)? ((Ki|M)B|bytes)/g, "X KiB"); - } +function sanitizeSizes(result: string): string { + return result.replace(/\d+(\.\d+)? ((Ki|M)B|bytes)/g, "X KiB"); +} - /** - * Gulp's output order within a `parallel` block is nondeterministic (and there's no way to configure it to execute in series), - * so we purge as much of the gulp output as we can - */ - function sanitizeUnimportantGulpOutput(result: string): string { - return result.replace(/^.*(\] (Starting)|(Finished)).*$/gm, "") // "gulp" task start/end messages (nondeterministic order) - .replace(/^.*(\] . (finished)|(started)).*$/gm, "") // "just" task start/end messages (nondeterministic order) - .replace(/^.*\] Respawned to PID: \d+.*$/gm, "") // PID of child is OS and system-load dependent (likely stableish in a container but still dangerous) - .replace(/\n+/g, "\n") - .replace(/\/tmp\/yarn--.*?\/node/g, ""); - } +/** + * Gulp's output order within a `parallel` block is nondeterministic (and there's no way to configure it to execute in series), + * so we purge as much of the gulp output as we can + */ +function sanitizeUnimportantGulpOutput(result: string): string { + return result.replace(/^.*(\] (Starting)|(Finished)).*$/gm, "") // "gulp" task start/end messages (nondeterministic order) + .replace(/^.*(\] . (finished)|(started)).*$/gm, "") // "just" task start/end messages (nondeterministic order) + .replace(/^.*\] Respawned to PID: \d+.*$/gm, "") // PID of child is OS and system-load dependent (likely stableish in a container but still dangerous) + .replace(/\n+/g, "\n") + .replace(/\/tmp\/yarn--.*?\/node/g, ""); +} - function sanitizeTimestamps(result: string): string { - return result.replace(/\[\d?\d:\d\d:\d\d (A|P)M\]/g, "[XX:XX:XX XM]") - .replace(/\[\d?\d:\d\d:\d\d\]/g, "[XX:XX:XX]") - .replace(/\/\d+-\d+-[\d_TZ]+-debug.log/g, "\/XXXX-XX-XXXXXXXXX-debug.log") - .replace(/\d+\/\d+\/\d+ \d+:\d+:\d+ (AM|PM)/g, "XX/XX/XX XX:XX:XX XM") - .replace(/\d+(\.\d+)? sec(onds?)?/g, "? seconds") - .replace(/\d+(\.\d+)? min(utes?)?/g, "") - .replace(/\d+(\.\d+)? ?m?s/g, "?s") - .replace(/ \(\?s\)/g, ""); - } +function sanitizeTimestamps(result: string): string { + return result.replace(/\[\d?\d:\d\d:\d\d (A|P)M\]/g, "[XX:XX:XX XM]") + .replace(/\[\d?\d:\d\d:\d\d\]/g, "[XX:XX:XX]") + .replace(/\/\d+-\d+-[\d_TZ]+-debug.log/g, "\/XXXX-XX-XXXXXXXXX-debug.log") + .replace(/\d+\/\d+\/\d+ \d+:\d+:\d+ (AM|PM)/g, "XX/XX/XX XX:XX:XX XM") + .replace(/\d+(\.\d+)? sec(onds?)?/g, "? seconds") + .replace(/\d+(\.\d+)? min(utes?)?/g, "") + .replace(/\d+(\.\d+)? ?m?s/g, "?s") + .replace(/ \(\?s\)/g, ""); +} - function sanitizeVersionSpecifiers(result: string): string { - return result - .replace(/\d+.\d+.\d+-insiders.\d\d\d\d\d\d\d\d/g, "X.X.X-insiders.xxxxxxxx") - .replace(/Rush Multi-Project Build Tool (\d+)\.\d+\.\d+/g, "Rush Multi-Project Build Tool $1.X.X") - .replace(/([@v\()])\d+\.\d+\.\d+/g, "$1X.X.X") - .replace(/webpack (\d+)\.\d+\.\d+/g, "webpack $1.X.X") - .replace(/Webpack version: (\d+)\.\d+\.\d+/g, "Webpack version: $1.X.X"); - } +function sanitizeVersionSpecifiers(result: string): string { + return result + .replace(/\d+.\d+.\d+-insiders.\d\d\d\d\d\d\d\d/g, "X.X.X-insiders.xxxxxxxx") + .replace(/Rush Multi-Project Build Tool (\d+)\.\d+\.\d+/g, "Rush Multi-Project Build Tool $1.X.X") + .replace(/([@v\()])\d+\.\d+\.\d+/g, "$1X.X.X") + .replace(/webpack (\d+)\.\d+\.\d+/g, "webpack $1.X.X") + .replace(/Webpack version: (\d+)\.\d+\.\d+/g, "Webpack version: $1.X.X"); +} - /** - * Import types and some other error messages use absolute paths in errors as they have no context to be written relative to; - * This is problematic for error baselines, so we grep for them and strip them out. - */ - function stripAbsoluteImportPaths(result: string) { - const workspaceRegexp = new RegExp(IO.getWorkspaceRoot().replace(/\\/g, "\\\\"), "g"); - return result - .replace(/import\(".*?\/tests\/cases\/user\//g, `import("/`) - .replace(/Module '".*?\/tests\/cases\/user\//g, `Module '"/`) - .replace(workspaceRegexp, "../../.."); - } +/** + * Import types and some other error messages use absolute paths in errors as they have no context to be written relative to; + * This is problematic for error baselines, so we grep for them and strip them out. + */ +function stripAbsoluteImportPaths(result: string) { + const workspaceRegexp = new RegExp(IO.getWorkspaceRoot().replace(/\\/g, "\\\\"), "g"); + return result + .replace(/import\(".*?\/tests\/cases\/user\//g, `import("/`) + .replace(/Module '".*?\/tests\/cases\/user\//g, `Module '"/`) + .replace(workspaceRegexp, "../../.."); +} - function sortErrors(result: string) { - return ts.flatten(splitBy(result.split("\n"), s => /^\S+/.test(s)).sort(compareErrorStrings)).join("\n"); - } +function sortErrors(result: string) { + return ts.flatten(splitBy(result.split("\n"), s => /^\S+/.test(s)).sort(compareErrorStrings)).join("\n"); +} - const errorRegexp = /^(.+\.[tj]sx?)\((\d+),(\d+)\)(: error TS.*)/; - function compareErrorStrings(a: string[], b: string[]) { - ts.Debug.assertGreaterThanOrEqual(a.length, 1); - ts.Debug.assertGreaterThanOrEqual(b.length, 1); - const matchA = a[0].match(errorRegexp); - if (!matchA) { - return -1; - } - const matchB = b[0].match(errorRegexp); - if (!matchB) { - return 1; - } - const [, errorFileA, lineNumberStringA, columnNumberStringA, remainderA] = matchA; - const [, errorFileB, lineNumberStringB, columnNumberStringB, remainderB] = matchB; - return ts.comparePathsCaseSensitive(errorFileA, errorFileB) || - ts.compareValues(parseInt(lineNumberStringA), parseInt(lineNumberStringB)) || - ts.compareValues(parseInt(columnNumberStringA), parseInt(columnNumberStringB)) || - ts.compareStringsCaseSensitive(remainderA, remainderB) || - ts.compareStringsCaseSensitive(a.slice(1).join("\n"), b.slice(1).join("\n")); +const errorRegexp = /^(.+\.[tj]sx?)\((\d+),(\d+)\)(: error TS.*)/; +function compareErrorStrings(a: string[], b: string[]) { + ts.Debug.assertGreaterThanOrEqual(a.length, 1); + ts.Debug.assertGreaterThanOrEqual(b.length, 1); + const matchA = a[0].match(errorRegexp); + if (!matchA) { + return -1; + } + const matchB = b[0].match(errorRegexp); + if (!matchB) { + return 1; } + const [, errorFileA, lineNumberStringA, columnNumberStringA, remainderA] = matchA; + const [, errorFileB, lineNumberStringB, columnNumberStringB, remainderB] = matchB; + return ts.comparePathsCaseSensitive(errorFileA, errorFileB) || + ts.compareValues(parseInt(lineNumberStringA), parseInt(lineNumberStringB)) || + ts.compareValues(parseInt(columnNumberStringA), parseInt(columnNumberStringB)) || + ts.compareStringsCaseSensitive(remainderA, remainderB) || + ts.compareStringsCaseSensitive(a.slice(1).join("\n"), b.slice(1).join("\n")); +} - export class DefinitelyTypedRunner extends ExternalCompileRunnerBase { - readonly testDir = "../DefinitelyTyped/types/"; - workingDirectory = this.testDir; - kind(): TestRunnerKind { - return "dt"; - } - report(result: ExecResult, cwd: string) { - const stdout = removeExpectedErrors(result.stdout.toString(), cwd); - const stderr = result.stderr.toString(); +export class DefinitelyTypedRunner extends ExternalCompileRunnerBase { + readonly testDir = "../DefinitelyTyped/types/"; + workingDirectory = this.testDir; + kind(): TestRunnerKind { + return "dt"; + } + report(result: ExecResult, cwd: string) { + const stdout = removeExpectedErrors(result.stdout.toString(), cwd); + const stderr = result.stderr.toString(); - // eslint-disable-next-line no-null/no-null - return !stdout.length && !stderr.length ? null : `Exit Code: ${result.status} + // eslint-disable-next-line no-null/no-null + return !stdout.length && !stderr.length ? null : `Exit Code: ${result.status} Standard output: ${stdout.replace(/\r\n/g, "\n")} Standard error: ${stderr.replace(/\r\n/g, "\n")}`; - } } +} - function removeExpectedErrors(errors: string, cwd: string): string { - return ts.flatten(splitBy(errors.split("\n"), s => /^\S+/.test(s)).filter(isUnexpectedError(cwd))).join("\n"); - } - /** - * Returns true if the line that caused the error contains '$ExpectError', - * or if the line before that one contains '$ExpectError'. - * '$ExpectError' is a marker used in Definitely Typed tests, - * meaning that the error should not contribute toward our error baslines. - */ - function isUnexpectedError(cwd: string) { - return (error: string[]) => { - ts.Debug.assertGreaterThanOrEqual(error.length, 1); - const match = error[0].match(/(.+\.tsx?)\((\d+),\d+\): error TS/); - if (!match) { - return true; - } - const [, errorFile, lineNumberString] = match; - const lines = fs.readFileSync(path.join(cwd, errorFile), { encoding: "utf8" }).split("\n"); - const lineNumber = parseInt(lineNumberString) - 1; - ts.Debug.assertGreaterThanOrEqual(lineNumber, 0); - ts.Debug.assertLessThan(lineNumber, lines.length); - const previousLine = lineNumber - 1 > 0 ? lines[lineNumber - 1] : ""; - return !ts.stringContains(lines[lineNumber], "$ExpectError") && !ts.stringContains(previousLine, "$ExpectError"); - }; - } - /** - * Split an array into multiple arrays whenever `isStart` returns true. - * @example - * splitBy([1,2,3,4,5,6], isOdd) - * ==> [[1, 2], [3, 4], [5, 6]] - * where - * const isOdd = n => !!(n % 2) - */ - function splitBy(xs: T[], isStart: (x: T) => boolean): T[][] { - const result = []; - let group: T[] = []; - for (const x of xs) { - if (isStart(x)) { - if (group.length) { - result.push(group); - } - group = [x]; - } - else { - group.push(x); +function removeExpectedErrors(errors: string, cwd: string): string { + return ts.flatten(splitBy(errors.split("\n"), s => /^\S+/.test(s)).filter(isUnexpectedError(cwd))).join("\n"); +} +/** + * Returns true if the line that caused the error contains '$ExpectError', + * or if the line before that one contains '$ExpectError'. + * '$ExpectError' is a marker used in Definitely Typed tests, + * meaning that the error should not contribute toward our error baslines. + */ +function isUnexpectedError(cwd: string) { + return (error: string[]) => { + ts.Debug.assertGreaterThanOrEqual(error.length, 1); + const match = error[0].match(/(.+\.tsx?)\((\d+),\d+\): error TS/); + if (!match) { + return true; + } + const [, errorFile, lineNumberString] = match; + const lines = fs.readFileSync(path.join(cwd, errorFile), { encoding: "utf8" }).split("\n"); + const lineNumber = parseInt(lineNumberString) - 1; + ts.Debug.assertGreaterThanOrEqual(lineNumber, 0); + ts.Debug.assertLessThan(lineNumber, lines.length); + const previousLine = lineNumber - 1 > 0 ? lines[lineNumber - 1] : ""; + return !ts.stringContains(lines[lineNumber], "$ExpectError") && !ts.stringContains(previousLine, "$ExpectError"); + }; +} +/** + * Split an array into multiple arrays whenever `isStart` returns true. + * @example + * splitBy([1,2,3,4,5,6], isOdd) + * ==> [[1, 2], [3, 4], [5, 6]] + * where + * const isOdd = n => !!(n % 2) + */ +function splitBy(xs: T[], isStart: (x: T) => boolean): T[][] { + const result = []; + let group: T[] = []; + for (const x of xs) { + if (isStart(x)) { + if (group.length) { + result.push(group); } + group = [x]; } - if (group.length) { - result.push(group); + else { + group.push(x); } - return result; } + if (group.length) { + result.push(group); + } + return result; +} } diff --git a/src/testRunner/fakesRef.ts b/src/testRunner/fakesRef.ts index b19d4cc8c80ed..4743acab2db42 100644 --- a/src/testRunner/fakesRef.ts +++ b/src/testRunner/fakesRef.ts @@ -1,2 +1,2 @@ // empty ref to fakes so it can be referenced by unittests -namespace fakes {} \ No newline at end of file +namespace fakes {} diff --git a/src/testRunner/fourslashRef.ts b/src/testRunner/fourslashRef.ts index 23a50810a46af..6eae77e2bd25f 100644 --- a/src/testRunner/fourslashRef.ts +++ b/src/testRunner/fourslashRef.ts @@ -1,2 +1,2 @@ // empty ref to FourSlash so it can be referenced by unittests -namespace FourSlash {} \ No newline at end of file +namespace FourSlash {} diff --git a/src/testRunner/fourslashRunner.ts b/src/testRunner/fourslashRunner.ts index 4916ec6693f6e..8ecc485b99962 100644 --- a/src/testRunner/fourslashRunner.ts +++ b/src/testRunner/fourslashRunner.ts @@ -1,72 +1,72 @@ namespace Harness { - export class FourSlashRunner extends RunnerBase { - protected basePath: string; - protected testSuiteName: TestRunnerKind; +export class FourSlashRunner extends RunnerBase { + protected basePath: string; + protected testSuiteName: TestRunnerKind; - constructor(private testType: FourSlash.FourSlashTestType) { - super(); - switch (testType) { - case FourSlash.FourSlashTestType.Native: - this.basePath = "tests/cases/fourslash"; - this.testSuiteName = "fourslash"; - break; - case FourSlash.FourSlashTestType.Shims: - this.basePath = "tests/cases/fourslash/shims"; - this.testSuiteName = "fourslash-shims"; - break; - case FourSlash.FourSlashTestType.ShimsWithPreprocess: - this.basePath = "tests/cases/fourslash/shims-pp"; - this.testSuiteName = "fourslash-shims-pp"; - break; - case FourSlash.FourSlashTestType.Server: - this.basePath = "tests/cases/fourslash/server"; - this.testSuiteName = "fourslash-server"; - break; - default: - throw ts.Debug.assertNever(testType); - } + constructor(private testType: FourSlash.FourSlashTestType) { + super(); + switch (testType) { + case FourSlash.FourSlashTestType.Native: + this.basePath = "tests/cases/fourslash"; + this.testSuiteName = "fourslash"; + break; + case FourSlash.FourSlashTestType.Shims: + this.basePath = "tests/cases/fourslash/shims"; + this.testSuiteName = "fourslash-shims"; + break; + case FourSlash.FourSlashTestType.ShimsWithPreprocess: + this.basePath = "tests/cases/fourslash/shims-pp"; + this.testSuiteName = "fourslash-shims-pp"; + break; + case FourSlash.FourSlashTestType.Server: + this.basePath = "tests/cases/fourslash/server"; + this.testSuiteName = "fourslash-server"; + break; + default: + throw ts.Debug.assertNever(testType); } + } - public enumerateTestFiles() { - // see also: `enumerateTestFiles` in tests/webTestServer.ts - return this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false }); - } + public enumerateTestFiles() { + // see also: `enumerateTestFiles` in tests/webTestServer.ts + return this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false }); + } - public kind() { - return this.testSuiteName; - } + public kind() { + return this.testSuiteName; + } - public initializeTests() { - if (this.tests.length === 0) { - this.tests = IO.enumerateTestFiles(this); - } + public initializeTests() { + if (this.tests.length === 0) { + this.tests = IO.enumerateTestFiles(this); + } - describe(this.testSuiteName + " tests", () => { - this.tests.forEach(test => { - const file = typeof test === "string" ? test : test.file; - describe(file, () => { - let fn = ts.normalizeSlashes(file); - const justName = fn.replace(/^.*[\\\/]/, ""); + describe(this.testSuiteName + " tests", () => { + this.tests.forEach(test => { + const file = typeof test === "string" ? test : test.file; + describe(file, () => { + let fn = ts.normalizeSlashes(file); + const justName = fn.replace(/^.*[\\\/]/, ""); - // Convert to relative path - const testIndex = fn.indexOf("tests/"); - if (testIndex >= 0) fn = fn.substr(testIndex); + // Convert to relative path + const testIndex = fn.indexOf("tests/"); + if (testIndex >= 0) fn = fn.substr(testIndex); - if (justName && !justName.match(/fourslash\.ts$/i) && !justName.match(/\.d\.ts$/i)) { - it(this.testSuiteName + " test " + justName + " runs correctly", () => { - FourSlash.runFourSlashTest(this.basePath, this.testType, fn); - }); - } - }); + if (justName && !justName.match(/fourslash\.ts$/i) && !justName.match(/\.d\.ts$/i)) { + it(this.testSuiteName + " test " + justName + " runs correctly", () => { + FourSlash.runFourSlashTest(this.basePath, this.testType, fn); + }); + } }); }); - } + }); } +} - export class GeneratedFourslashRunner extends FourSlashRunner { - constructor(testType: FourSlash.FourSlashTestType) { - super(testType); - this.basePath += "/generated/"; - } +export class GeneratedFourslashRunner extends FourSlashRunner { + constructor(testType: FourSlash.FourSlashTestType) { + super(testType); + this.basePath += "/generated/"; } } +} diff --git a/src/testRunner/parallel/host.ts b/src/testRunner/parallel/host.ts index 819325bc85cf3..7e5f532660b4e 100644 --- a/src/testRunner/parallel/host.ts +++ b/src/testRunner/parallel/host.ts @@ -1,629 +1,629 @@ namespace Harness.Parallel.Host { - export function start() { - const Mocha = require("mocha") as typeof import("mocha"); - const Base = Mocha.reporters.Base; - const color = Base.color; - const cursor = Base.cursor; - const ms = require("ms") as typeof import("ms"); - const readline = require("readline") as typeof import("readline"); - const os = require("os") as typeof import("os"); - const tty = require("tty") as typeof import("tty"); - const isatty = tty.isatty(1) && tty.isatty(2); - const path = require("path") as typeof import("path"); - const { fork } = require("child_process") as typeof import("child_process"); - const { statSync } = require("fs") as typeof import("fs"); - - // NOTE: paths for module and types for FailedTestReporter _do not_ line up due to our use of --outFile for run.js - const FailedTestReporter = require(Utils.findUpFile("scripts/failed-tests.js")) as typeof import("../../../scripts/failed-tests"); - - const perfdataFileNameFragment = ".parallelperf"; - const perfData = readSavedPerfData(configOption); - const newTasks: Task[] = []; - let tasks: Task[] = []; - let unknownValue: string | undefined; - let totalCost = 0; - - class RemoteSuite extends Mocha.Suite { - suiteMap = new ts.Map(); - constructor(title: string) { - super(title); - this.pending = false; - this.delayed = false; - } - addSuite(suite: RemoteSuite) { - super.addSuite(suite); - this.suiteMap.set(suite.title, suite); - return this; - } - addTest(test: RemoteTest) { - return super.addTest(test); - } +export function start() { + const Mocha = require("mocha") as typeof import("mocha"); + const Base = Mocha.reporters.Base; + const color = Base.color; + const cursor = Base.cursor; + const ms = require("ms") as typeof import("ms"); + const readline = require("readline") as typeof import("readline"); + const os = require("os") as typeof import("os"); + const tty = require("tty") as typeof import("tty"); + const isatty = tty.isatty(1) && tty.isatty(2); + const path = require("path") as typeof import("path"); + const { fork } = require("child_process") as typeof import("child_process"); + const { statSync } = require("fs") as typeof import("fs"); + + // NOTE: paths for module and types for FailedTestReporter _do not_ line up due to our use of --outFile for run.js + const FailedTestReporter = require(Utils.findUpFile("scripts/failed-tests.js")) as typeof import("../../../scripts/failed-tests"); + + const perfdataFileNameFragment = ".parallelperf"; + const perfData = readSavedPerfData(configOption); + const newTasks: Task[] = []; + let tasks: Task[] = []; + let unknownValue: string | undefined; + let totalCost = 0; + + class RemoteSuite extends Mocha.Suite { + suiteMap = new ts.Map(); + constructor(title: string) { + super(title); + this.pending = false; + this.delayed = false; } - - class RemoteTest extends Mocha.Test { - info: ErrorInfo | TestInfo; - constructor(info: ErrorInfo | TestInfo) { - super(info.name[info.name.length - 1]); - this.info = info; - this.state = "error" in info ? "failed" : "passed"; // eslint-disable-line no-in-operator - this.pending = false; - } + addSuite(suite: RemoteSuite) { + super.addSuite(suite); + this.suiteMap.set(suite.title, suite); + return this; } - - interface Worker { - process: import("child_process").ChildProcess; - accumulatedOutput: string; - currentTasks?: { file: string }[]; - timer?: any; + addTest(test: RemoteTest) { + return super.addTest(test); } + } - interface ProgressBarsOptions { - open: string; - close: string; - complete: string; - incomplete: string; - width: number; - noColors: boolean; + class RemoteTest extends Mocha.Test { + info: ErrorInfo | TestInfo; + constructor(info: ErrorInfo | TestInfo) { + super(info.name[info.name.length - 1]); + this.info = info; + this.state = "error" in info ? "failed" : "passed"; // eslint-disable-line no-in-operator + this.pending = false; } + } - interface ProgressBar { - lastN?: number; - title?: string; - progressColor?: string; - text?: string; - } + interface Worker { + process: import("child_process").ChildProcess; + accumulatedOutput: string; + currentTasks?: { file: string }[]; + timer?: any; + } - class ProgressBars { - public readonly _options: Readonly; - private _enabled: boolean; - private _lineCount: number; - private _progressBars: ProgressBar[]; - constructor(options?: Partial) { - if (!options) options = {}; - const open = options.open || "["; - const close = options.close || "]"; - const complete = options.complete || "▬"; - const incomplete = options.incomplete || Base.symbols.dot; - const maxWidth = Base.window.width - open.length - close.length - 34; - const width = minMax(options.width || maxWidth, 10, maxWidth); - this._options = { - open, - complete, - incomplete, - close, - width, - noColors: options.noColors || false - }; - - this._progressBars = []; - this._lineCount = 0; - this._enabled = false; + interface ProgressBarsOptions { + open: string; + close: string; + complete: string; + incomplete: string; + width: number; + noColors: boolean; + } + + interface ProgressBar { + lastN?: number; + title?: string; + progressColor?: string; + text?: string; + } + + class ProgressBars { + public readonly _options: Readonly; + private _enabled: boolean; + private _lineCount: number; + private _progressBars: ProgressBar[]; + constructor(options?: Partial) { + if (!options) options = {}; + const open = options.open || "["; + const close = options.close || "]"; + const complete = options.complete || "▬"; + const incomplete = options.incomplete || Base.symbols.dot; + const maxWidth = Base.window.width - open.length - close.length - 34; + const width = minMax(options.width || maxWidth, 10, maxWidth); + this._options = { + open, + complete, + incomplete, + close, + width, + noColors: options.noColors || false + }; + + this._progressBars = []; + this._lineCount = 0; + this._enabled = false; + } + enable() { + if (!this._enabled) { + process.stdout.write(os.EOL); + this._enabled = true; } - enable() { - if (!this._enabled) { - process.stdout.write(os.EOL); - this._enabled = true; - } + } + disable() { + if (this._enabled) { + process.stdout.write(os.EOL); + this._enabled = false; } - disable() { - if (this._enabled) { - process.stdout.write(os.EOL); - this._enabled = false; - } + } + update(index: number, percentComplete: number, color: string, title: string | undefined, titleColor?: string) { + percentComplete = minMax(percentComplete, 0, 1); + + const progressBar = this._progressBars[index] || (this._progressBars[index] = {}); + const width = this._options.width; + const n = Math.floor(width * percentComplete); + const i = width - n; + if (n === progressBar.lastN && title === progressBar.title && color === progressBar.progressColor) { + return; } - update(index: number, percentComplete: number, color: string, title: string | undefined, titleColor?: string) { - percentComplete = minMax(percentComplete, 0, 1); - - const progressBar = this._progressBars[index] || (this._progressBars[index] = {}); - const width = this._options.width; - const n = Math.floor(width * percentComplete); - const i = width - n; - if (n === progressBar.lastN && title === progressBar.title && color === progressBar.progressColor) { - return; - } - - progressBar.lastN = n; - progressBar.title = title; - progressBar.progressColor = color; - let progress = " "; - progress += this._color("progress", this._options.open); - progress += this._color(color, fill(this._options.complete, n)); - progress += this._color("progress", fill(this._options.incomplete, i)); - progress += this._color("progress", this._options.close); + progressBar.lastN = n; + progressBar.title = title; + progressBar.progressColor = color; - if (title) { - progress += this._color(titleColor || "progress", " " + title); - } + let progress = " "; + progress += this._color("progress", this._options.open); + progress += this._color(color, fill(this._options.complete, n)); + progress += this._color("progress", fill(this._options.incomplete, i)); + progress += this._color("progress", this._options.close); - if (progressBar.text !== progress) { - progressBar.text = progress; - this._render(index); - } + if (title) { + progress += this._color(titleColor || "progress", " " + title); } - private _render(index: number) { - if (!this._enabled || !isatty) { - return; - } - cursor.hide(); - readline.moveCursor(process.stdout, -process.stdout.columns, -this._lineCount); - let lineCount = 0; - const numProgressBars = this._progressBars.length; - for (let i = 0; i < numProgressBars; i++) { - if (i === index) { - readline.clearLine(process.stdout, 1); - process.stdout.write(this._progressBars[i].text + os.EOL); - } - else { - readline.moveCursor(process.stdout, -process.stdout.columns, +1); - } + if (progressBar.text !== progress) { + progressBar.text = progress; + this._render(index); + } + } + private _render(index: number) { + if (!this._enabled || !isatty) { + return; + } - lineCount++; + cursor.hide(); + readline.moveCursor(process.stdout, -process.stdout.columns, -this._lineCount); + let lineCount = 0; + const numProgressBars = this._progressBars.length; + for (let i = 0; i < numProgressBars; i++) { + if (i === index) { + readline.clearLine(process.stdout, 1); + process.stdout.write(this._progressBars[i].text + os.EOL); + } + else { + readline.moveCursor(process.stdout, -process.stdout.columns, +1); } - this._lineCount = lineCount; - cursor.show(); + lineCount++; } - private _color(type: string, text: string) { - return type && !this._options.noColors ? color(type, text) : text; - } - } - function perfdataFileName(target?: string) { - return `${perfdataFileNameFragment}${target ? `.${target}` : ""}.json`; + this._lineCount = lineCount; + cursor.show(); } - - function readSavedPerfData(target?: string): { [testHash: string]: number } | undefined { - const perfDataContents = IO.readFile(perfdataFileName(target)); - if (perfDataContents) { - return JSON.parse(perfDataContents); - } - return undefined; + private _color(type: string, text: string) { + return type && !this._options.noColors ? color(type, text) : text; } + } + + function perfdataFileName(target?: string) { + return `${perfdataFileNameFragment}${target ? `.${target}` : ""}.json`; + } - function hashName(runner: TestRunnerKind | "unittest", test: string) { - return `tsrunner-${runner}://${test}`; + function readSavedPerfData(target?: string): { [testHash: string]: number } | undefined { + const perfDataContents = IO.readFile(perfdataFileName(target)); + if (perfDataContents) { + return JSON.parse(perfDataContents); } + return undefined; + } + + function hashName(runner: TestRunnerKind | "unittest", test: string) { + return `tsrunner-${runner}://${test}`; + } - function startDelayed(perfData: { [testHash: string]: number } | undefined, totalCost: number) { - console.log(`Discovered ${tasks.length} unittest suites` + (newTasks.length ? ` and ${newTasks.length} new suites.` : ".")); - console.log("Discovering runner-based tests..."); - const discoverStart = +(new Date()); - for (const runner of runners) { - for (const test of runner.getTestFiles()) { - const file = typeof test === "string" ? test : test.file; - let size: number; - if (!perfData) { + function startDelayed(perfData: { [testHash: string]: number } | undefined, totalCost: number) { + console.log(`Discovered ${tasks.length} unittest suites` + (newTasks.length ? ` and ${newTasks.length} new suites.` : ".")); + console.log("Discovering runner-based tests..."); + const discoverStart = +(new Date()); + for (const runner of runners) { + for (const test of runner.getTestFiles()) { + const file = typeof test === "string" ? test : test.file; + let size: number; + if (!perfData) { + try { + size = statSync(path.join(runner.workingDirectory, file)).size; + } + catch { + // May be a directory try { - size = statSync(path.join(runner.workingDirectory, file)).size; + size = IO.listFiles(path.join(runner.workingDirectory, file), /.*/g, { recursive: true }).reduce((acc, elem) => acc + statSync(elem).size, 0); } catch { - // May be a directory - try { - size = IO.listFiles(path.join(runner.workingDirectory, file), /.*/g, { recursive: true }).reduce((acc, elem) => acc + statSync(elem).size, 0); - } - catch { - // Unknown test kind, just return 0 and let the historical analysis take over after one run - size = 0; - } - } - } - else { - const hashedName = hashName(runner.kind(), file); - size = perfData[hashedName]; - if (size === undefined) { + // Unknown test kind, just return 0 and let the historical analysis take over after one run size = 0; - unknownValue = hashedName; - newTasks.push({ runner: runner.kind(), file, size }); - continue; } } - tasks.push({ runner: runner.kind(), file, size }); - totalCost += size; } + else { + const hashedName = hashName(runner.kind(), file); + size = perfData[hashedName]; + if (size === undefined) { + size = 0; + unknownValue = hashedName; + newTasks.push({ runner: runner.kind(), file, size }); + continue; + } + } + tasks.push({ runner: runner.kind(), file, size }); + totalCost += size; } - tasks.sort((a, b) => a.size - b.size); - tasks = tasks.concat(newTasks); - const batchCount = workerCount; - const packfraction = 0.9; - const chunkSize = 1000; // ~1KB or 1s for sending batches near the end of a test - const batchSize = (totalCost / workerCount) * packfraction; // Keep spare tests for unittest thread in reserve - console.log(`Discovered ${tasks.length} test files in ${+(new Date()) - discoverStart}ms.`); - console.log(`Starting to run tests using ${workerCount} threads...`); - - const totalFiles = tasks.length; - let passingFiles = 0; - let failingFiles = 0; - let errorResults: ErrorInfo[] = []; - let passingResults: { name: string[] }[] = []; - let totalPassing = 0; - const startDate = new Date(); - - const progressBars = new ProgressBars({ noColors: Harness.noColors }); // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier - const progressUpdateInterval = 1 / progressBars._options.width; - let nextProgress = progressUpdateInterval; - - const newPerfData: { [testHash: string]: number } = {}; - - const workers: Worker[] = []; - let closedWorkers = 0; - for (let i = 0; i < workerCount; i++) { - // TODO: Just send the config over the IPC channel or in the command line arguments - const config: TestConfig = { light: lightMode, listenForWork: true, runUnitTests: Harness.runUnitTests, stackTraceLimit: Harness.stackTraceLimit, timeout: globalTimeout }; // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier - const configPath = ts.combinePaths(taskConfigsFolder, `task-config${i}.json`); - IO.writeFile(configPath, JSON.stringify(config)); - const worker: Worker = { - process: fork(__filename, [`--config="${configPath}"`], { stdio: ["pipe", "pipe", "pipe", "ipc"] }), - accumulatedOutput: "", - currentTasks: undefined, - timer: undefined - }; - const appendOutput = (d: Buffer) => { - worker.accumulatedOutput += d.toString(); - console.log(`[Worker ${i}]`, d.toString()); - }; - worker.process.stderr!.on("data", appendOutput); - worker.process.stdout!.on("data", appendOutput); - const killChild = (timeout: TaskTimeout) => { - worker.process.kill(); - console.error(`Worker exceeded ${timeout.duration}ms timeout ${worker.currentTasks && worker.currentTasks.length ? `while running test '${worker.currentTasks[0].file}'.` : `during test setup.`}`); - return process.exit(2); - }; - worker.process.on("error", err => { - console.error("Unexpected error in child process:"); - console.error(err); - return process.exit(2); - }); - worker.process.on("exit", (code, _signal) => { - if (code !== 0) { - console.error(`Test worker process exited with nonzero exit code! Output: + } + tasks.sort((a, b) => a.size - b.size); + tasks = tasks.concat(newTasks); + const batchCount = workerCount; + const packfraction = 0.9; + const chunkSize = 1000; // ~1KB or 1s for sending batches near the end of a test + const batchSize = (totalCost / workerCount) * packfraction; // Keep spare tests for unittest thread in reserve + console.log(`Discovered ${tasks.length} test files in ${+(new Date()) - discoverStart}ms.`); + console.log(`Starting to run tests using ${workerCount} threads...`); + + const totalFiles = tasks.length; + let passingFiles = 0; + let failingFiles = 0; + let errorResults: ErrorInfo[] = []; + let passingResults: { name: string[] }[] = []; + let totalPassing = 0; + const startDate = new Date(); + + const progressBars = new ProgressBars({ noColors: Harness.noColors }); // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier + const progressUpdateInterval = 1 / progressBars._options.width; + let nextProgress = progressUpdateInterval; + + const newPerfData: { [testHash: string]: number } = {}; + + const workers: Worker[] = []; + let closedWorkers = 0; + for (let i = 0; i < workerCount; i++) { + // TODO: Just send the config over the IPC channel or in the command line arguments + const config: TestConfig = { light: lightMode, listenForWork: true, runUnitTests: Harness.runUnitTests, stackTraceLimit: Harness.stackTraceLimit, timeout: globalTimeout }; // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier + const configPath = ts.combinePaths(taskConfigsFolder, `task-config${i}.json`); + IO.writeFile(configPath, JSON.stringify(config)); + const worker: Worker = { + process: fork(__filename, [`--config="${configPath}"`], { stdio: ["pipe", "pipe", "pipe", "ipc"] }), + accumulatedOutput: "", + currentTasks: undefined, + timer: undefined + }; + const appendOutput = (d: Buffer) => { + worker.accumulatedOutput += d.toString(); + console.log(`[Worker ${i}]`, d.toString()); + }; + worker.process.stderr!.on("data", appendOutput); + worker.process.stdout!.on("data", appendOutput); + const killChild = (timeout: TaskTimeout) => { + worker.process.kill(); + console.error(`Worker exceeded ${timeout.duration}ms timeout ${worker.currentTasks && worker.currentTasks.length ? `while running test '${worker.currentTasks[0].file}'.` : `during test setup.`}`); + return process.exit(2); + }; + worker.process.on("error", err => { + console.error("Unexpected error in child process:"); + console.error(err); + return process.exit(2); + }); + worker.process.on("exit", (code, _signal) => { + if (code !== 0) { + console.error(`Test worker process exited with nonzero exit code! Output: ${worker.accumulatedOutput}`); - return process.exit(2); - } - }); - worker.process.on("message", (data: ParallelClientMessage) => { - switch (data.type) { - case "error": { - console.error(`Test worker encountered unexpected error${data.payload.name ? ` during the execution of test ${data.payload.name}` : ""} and was forced to close: + return process.exit(2); + } + }); + worker.process.on("message", (data: ParallelClientMessage) => { + switch (data.type) { + case "error": { + console.error(`Test worker encountered unexpected error${data.payload.name ? ` during the execution of test ${data.payload.name}` : ""} and was forced to close: Message: ${data.payload.error} Stack: ${data.payload.stack}`); - return process.exit(2); + return process.exit(2); + } + case "timeout": { + if (worker.timer) { + // eslint-disable-next-line no-restricted-globals + clearTimeout(worker.timer); } - case "timeout": { - if (worker.timer) { - // eslint-disable-next-line no-restricted-globals - clearTimeout(worker.timer); - } - if (data.payload.duration === "reset") { - worker.timer = undefined; - } - else { - // eslint-disable-next-line no-restricted-globals - worker.timer = setTimeout(killChild, data.payload.duration, data.payload); - } - break; + if (data.payload.duration === "reset") { + worker.timer = undefined; } - case "progress": - case "result": { - if (worker.currentTasks) { - worker.currentTasks.shift(); - } - totalPassing += data.payload.passing; - if (data.payload.errors.length) { - errorResults = errorResults.concat(data.payload.errors); - passingResults = passingResults.concat(data.payload.passes); - failingFiles++; - } - else { - passingResults = passingResults.concat(data.payload.passes); - passingFiles++; - } - newPerfData[hashName(data.payload.task.runner, data.payload.task.file)] = data.payload.duration; + else { + // eslint-disable-next-line no-restricted-globals + worker.timer = setTimeout(killChild, data.payload.duration, data.payload); + } + break; + } + case "progress": + case "result": { + if (worker.currentTasks) { + worker.currentTasks.shift(); + } + totalPassing += data.payload.passing; + if (data.payload.errors.length) { + errorResults = errorResults.concat(data.payload.errors); + passingResults = passingResults.concat(data.payload.passes); + failingFiles++; + } + else { + passingResults = passingResults.concat(data.payload.passes); + passingFiles++; + } + newPerfData[hashName(data.payload.task.runner, data.payload.task.file)] = data.payload.duration; - const progress = (failingFiles + passingFiles) / totalFiles; - if (progress >= nextProgress) { - while (nextProgress < progress) { - nextProgress += progressUpdateInterval; - } - updateProgress(progress, errorResults.length ? `${errorResults.length} failing` : `${totalPassing} passing`, errorResults.length ? "fail" : undefined); + const progress = (failingFiles + passingFiles) / totalFiles; + if (progress >= nextProgress) { + while (nextProgress < progress) { + nextProgress += progressUpdateInterval; } + updateProgress(progress, errorResults.length ? `${errorResults.length} failing` : `${totalPassing} passing`, errorResults.length ? "fail" : undefined); + } - if (data.type === "result") { - if (tasks.length === 0) { - // No more tasks to distribute - worker.process.send({ type: "close" }); - closedWorkers++; - if (closedWorkers === workerCount) { - outputFinalResult(); - } - return; - } - // Send tasks in blocks if the tasks are small - const taskList = [tasks.pop()!]; - while (tasks.length && taskList.reduce((p, c) => p + c.size, 0) < chunkSize) { - taskList.push(tasks.pop()!); - } - worker.currentTasks = taskList; - if (taskList.length === 1) { - worker.process.send({ type: "test", payload: taskList[0] } as ParallelHostMessage); // TODO: GH#18217 - } - else { - worker.process.send({ type: "batch", payload: taskList } as ParallelHostMessage); // TODO: GH#18217 + if (data.type === "result") { + if (tasks.length === 0) { + // No more tasks to distribute + worker.process.send({ type: "close" }); + closedWorkers++; + if (closedWorkers === workerCount) { + outputFinalResult(); } + return; + } + // Send tasks in blocks if the tasks are small + const taskList = [tasks.pop()!]; + while (tasks.length && taskList.reduce((p, c) => p + c.size, 0) < chunkSize) { + taskList.push(tasks.pop()!); + } + worker.currentTasks = taskList; + if (taskList.length === 1) { + worker.process.send({ type: "test", payload: taskList[0] } as ParallelHostMessage); // TODO: GH#18217 + } + else { + worker.process.send({ type: "batch", payload: taskList } as ParallelHostMessage); // TODO: GH#18217 } } } - }); - workers.push(worker); - } + } + }); + workers.push(worker); + } - // It's only really worth doing an initial batching if there are a ton of files to go through (and they have estimates) - if (totalFiles > 1000 && batchSize > 0) { - console.log("Batching initial test lists..."); - const batches: { runner: TestRunnerKind | "unittest", file: string, size: number }[][] = new Array(batchCount); - const doneBatching = new Array(batchCount); - let scheduledTotal = 0; - batcher: while (true) { - for (let i = 0; i < batchCount; i++) { - if (tasks.length <= workerCount) { // Keep a small reserve even in the suboptimally packed case - console.log(`Suboptimal packing detected: no tests remain to be stolen. Reduce packing fraction from ${packfraction} to fix.`); - break batcher; - } - if (doneBatching[i]) { - continue; - } - if (!batches[i]) { - batches[i] = []; - } - const total = batches[i].reduce((p, c) => p + c.size, 0); - if (total >= batchSize) { - doneBatching[i] = true; - continue; - } - const task = tasks.pop()!; - batches[i].push(task); - scheduledTotal += task.size; + // It's only really worth doing an initial batching if there are a ton of files to go through (and they have estimates) + if (totalFiles > 1000 && batchSize > 0) { + console.log("Batching initial test lists..."); + const batches: { runner: TestRunnerKind | "unittest", file: string, size: number }[][] = new Array(batchCount); + const doneBatching = new Array(batchCount); + let scheduledTotal = 0; + batcher: while (true) { + for (let i = 0; i < batchCount; i++) { + if (tasks.length <= workerCount) { // Keep a small reserve even in the suboptimally packed case + console.log(`Suboptimal packing detected: no tests remain to be stolen. Reduce packing fraction from ${packfraction} to fix.`); + break batcher; } - for (let j = 0; j < batchCount; j++) { - if (!doneBatching[j]) { - continue batcher; - } + if (doneBatching[i]) { + continue; } - break; - } - const prefix = `Batched into ${batchCount} groups`; - if (unknownValue) { - console.log(`${prefix}. Unprofiled tests including ${unknownValue} will be run first.`); - } - else { - console.log(`${prefix} with approximate total ${perfData ? "time" : "file sizes"} of ${perfData ? ms(batchSize) : `${Math.floor(batchSize)} bytes`} in each group. (${(scheduledTotal / totalCost * 100).toFixed(1)}% of total tests batched)`); - } - for (const worker of workers) { - const payload = batches.pop(); - if (payload) { - worker.currentTasks = payload; - worker.process.send({ type: "batch", payload }); + if (!batches[i]) { + batches[i] = []; } - else { // Out of batches, send off just one test - const payload = tasks.pop()!; - ts.Debug.assert(!!payload); // The reserve kept above should ensure there is always an initial task available, even in suboptimal scenarios - worker.currentTasks = [payload]; - worker.process.send({ type: "test", payload }); + const total = batches[i].reduce((p, c) => p + c.size, 0); + if (total >= batchSize) { + doneBatching[i] = true; + continue; + } + const task = tasks.pop()!; + batches[i].push(task); + scheduledTotal += task.size; + } + for (let j = 0; j < batchCount; j++) { + if (!doneBatching[j]) { + continue batcher; } } + break; + } + const prefix = `Batched into ${batchCount} groups`; + if (unknownValue) { + console.log(`${prefix}. Unprofiled tests including ${unknownValue} will be run first.`); } else { - for (let i = 0; i < workerCount; i++) { - const task = tasks.pop()!; - workers[i].currentTasks = [task]; - workers[i].process.send({ type: "test", payload: task }); + console.log(`${prefix} with approximate total ${perfData ? "time" : "file sizes"} of ${perfData ? ms(batchSize) : `${Math.floor(batchSize)} bytes`} in each group. (${(scheduledTotal / totalCost * 100).toFixed(1)}% of total tests batched)`); + } + for (const worker of workers) { + const payload = batches.pop(); + if (payload) { + worker.currentTasks = payload; + worker.process.send({ type: "batch", payload }); + } + else { // Out of batches, send off just one test + const payload = tasks.pop()!; + ts.Debug.assert(!!payload); // The reserve kept above should ensure there is always an initial task available, even in suboptimal scenarios + worker.currentTasks = [payload]; + worker.process.send({ type: "test", payload }); } } + } + else { + for (let i = 0; i < workerCount; i++) { + const task = tasks.pop()!; + workers[i].currentTasks = [task]; + workers[i].process.send({ type: "test", payload: task }); + } + } - progressBars.enable(); - updateProgress(0); - let duration: number; - let endDate: Date; - - function completeBar() { - const isPartitionFail = failingFiles !== 0; - const summaryColor = isPartitionFail ? "fail" : "green"; - const summarySymbol = isPartitionFail ? Base.symbols.err : Base.symbols.ok; + progressBars.enable(); + updateProgress(0); + let duration: number; + let endDate: Date; - const summaryTests = (isPartitionFail ? totalPassing + "/" + (errorResults.length + totalPassing) : totalPassing) + " passing"; - const summaryDuration = "(" + ms(duration) + ")"; - const savedUseColors = Base.useColors; - Base.useColors = !noColors; + function completeBar() { + const isPartitionFail = failingFiles !== 0; + const summaryColor = isPartitionFail ? "fail" : "green"; + const summarySymbol = isPartitionFail ? Base.symbols.err : Base.symbols.ok; - const summary = color(summaryColor, summarySymbol + " " + summaryTests) + " " + color("light", summaryDuration); - Base.useColors = savedUseColors; + const summaryTests = (isPartitionFail ? totalPassing + "/" + (errorResults.length + totalPassing) : totalPassing) + " passing"; + const summaryDuration = "(" + ms(duration) + ")"; + const savedUseColors = Base.useColors; + Base.useColors = !noColors; - updateProgress(1, summary); - } + const summary = color(summaryColor, summarySymbol + " " + summaryTests) + " " + color("light", summaryDuration); + Base.useColors = savedUseColors; - function updateProgress(percentComplete: number, title?: string, titleColor?: string) { - let progressColor = "pending"; - if (failingFiles) { - progressColor = "fail"; - } + updateProgress(1, summary); + } - progressBars.update( - 0, - percentComplete, - progressColor, - title, - titleColor - ); + function updateProgress(percentComplete: number, title?: string, titleColor?: string) { + let progressColor = "pending"; + if (failingFiles) { + progressColor = "fail"; } - function outputFinalResult() { - function patchStats(stats: Mocha.Stats) { - Object.defineProperties(stats, { - start: { - configurable: true, enumerable: true, - get() { return startDate; }, - set(_: Date) { /*do nothing*/ } - }, - end: { - configurable: true, enumerable: true, - get() { return endDate; }, - set(_: Date) { /*do nothing*/ } - }, - duration: { - configurable: true, enumerable: true, - get() { return duration; }, - set(_: number) { /*do nothing*/ } - } - }); - } + progressBars.update( + 0, + percentComplete, + progressColor, + title, + titleColor + ); + } - function rebuildSuite(failures: ErrorInfo[], passes: TestInfo[]) { - const root = new RemoteSuite(""); - for (const result of [...failures, ...passes] as (ErrorInfo | TestInfo)[]) { - getSuite(root, result.name.slice(0, -1)).addTest(new RemoteTest(result)); + function outputFinalResult() { + function patchStats(stats: Mocha.Stats) { + Object.defineProperties(stats, { + start: { + configurable: true, enumerable: true, + get() { return startDate; }, + set(_: Date) { /*do nothing*/ } + }, + end: { + configurable: true, enumerable: true, + get() { return endDate; }, + set(_: Date) { /*do nothing*/ } + }, + duration: { + configurable: true, enumerable: true, + get() { return duration; }, + set(_: number) { /*do nothing*/ } } - return root; - function getSuite(parent: RemoteSuite, titlePath: string[]): Mocha.Suite { - const title = titlePath[0]; - let suite = parent.suiteMap.get(title); - if (!suite) parent.addSuite(suite = new RemoteSuite(title)); - return titlePath.length === 1 ? suite : getSuite(suite, titlePath.slice(1)); - } - } + }); + } - function rebuildError(result: ErrorInfo) { - const error = new Error(result.error); - error.stack = result.stack; - return error; + function rebuildSuite(failures: ErrorInfo[], passes: TestInfo[]) { + const root = new RemoteSuite(""); + for (const result of [...failures, ...passes] as (ErrorInfo | TestInfo)[]) { + getSuite(root, result.name.slice(0, -1)).addTest(new RemoteTest(result)); } - - function replaySuite(runner: Mocha.Runner, suite: RemoteSuite) { - runner.emit("suite", suite); - for (const test of suite.tests) { - replayTest(runner, test as RemoteTest); - } - for (const child of suite.suites) { - replaySuite(runner, child as RemoteSuite); - } - runner.emit("suite end", suite); + return root; + function getSuite(parent: RemoteSuite, titlePath: string[]): Mocha.Suite { + const title = titlePath[0]; + let suite = parent.suiteMap.get(title); + if (!suite) parent.addSuite(suite = new RemoteSuite(title)); + return titlePath.length === 1 ? suite : getSuite(suite, titlePath.slice(1)); } + } - function replayTest(runner: Mocha.Runner, test: RemoteTest) { - runner.emit("test", test); - if (test.isFailed()) { - runner.emit("fail", test, "error" in test.info ? rebuildError(test.info) : new Error("Unknown error")); // eslint-disable-line no-in-operator - } - else { - runner.emit("pass", test); - } - runner.emit("test end", test); - } + function rebuildError(result: ErrorInfo) { + const error = new Error(result.error); + error.stack = result.stack; + return error; + } - endDate = new Date(); - duration = +endDate - +startDate; - completeBar(); - progressBars.disable(); - - const replayRunner = new Mocha.Runner(new Mocha.Suite(""), { delay: false }); - replayRunner.started = true; - const createStatsCollector = require("mocha/lib/stats-collector"); - createStatsCollector(replayRunner); // manually init stats collector like mocha.run would - - const consoleReporter = new Base(replayRunner); - patchStats(consoleReporter.stats); - - let xunitReporter: import("mocha").reporters.XUnit | undefined; - let failedTestReporter: import("../../../scripts/failed-tests") | undefined; - if (process.env.CI === "true") { - xunitReporter = new Mocha.reporters.XUnit(replayRunner, { - reporterOptions: { - suiteName: "Tests", - output: "./TEST-results.xml" - } - }); - patchStats(xunitReporter.stats); - xunitReporter.write(`\n`); + function replaySuite(runner: Mocha.Runner, suite: RemoteSuite) { + runner.emit("suite", suite); + for (const test of suite.tests) { + replayTest(runner, test as RemoteTest); } - else { - failedTestReporter = new FailedTestReporter(replayRunner, { - reporterOptions: { - file: path.resolve(".failed-tests"), - keepFailed: Harness.keepFailed // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier - } - }); + for (const child of suite.suites) { + replaySuite(runner, child as RemoteSuite); } + runner.emit("suite end", suite); + } - const savedUseColors = Base.useColors; - if (noColors) Base.useColors = false; - replayRunner.started = true; - replayRunner.emit("start"); - replaySuite(replayRunner, rebuildSuite(errorResults, passingResults)); - replayRunner.emit("end"); - consoleReporter.epilogue(); - if (noColors) Base.useColors = savedUseColors; - - // eslint-disable-next-line no-null/no-null - IO.writeFile(perfdataFileName(configOption), JSON.stringify(newPerfData, null, 4)); - - if (xunitReporter) { - xunitReporter.done(errorResults.length, failures => process.exit(failures)); - } - else if (failedTestReporter) { - failedTestReporter.done(errorResults.length, failures => process.exit(failures)); + function replayTest(runner: Mocha.Runner, test: RemoteTest) { + runner.emit("test", test); + if (test.isFailed()) { + runner.emit("fail", test, "error" in test.info ? rebuildError(test.info) : new Error("Unknown error")); // eslint-disable-line no-in-operator } else { - process.exit(errorResults.length); + runner.emit("pass", test); } + runner.emit("test end", test); } - } - function fill(ch: string, size: number) { - let s = ""; - while (s.length < size) { - s += ch; + endDate = new Date(); + duration = +endDate - +startDate; + completeBar(); + progressBars.disable(); + + const replayRunner = new Mocha.Runner(new Mocha.Suite(""), { delay: false }); + replayRunner.started = true; + const createStatsCollector = require("mocha/lib/stats-collector"); + createStatsCollector(replayRunner); // manually init stats collector like mocha.run would + + const consoleReporter = new Base(replayRunner); + patchStats(consoleReporter.stats); + + let xunitReporter: import("mocha").reporters.XUnit | undefined; + let failedTestReporter: import("../../../scripts/failed-tests") | undefined; + if (process.env.CI === "true") { + xunitReporter = new Mocha.reporters.XUnit(replayRunner, { + reporterOptions: { + suiteName: "Tests", + output: "./TEST-results.xml" + } + }); + patchStats(xunitReporter.stats); + xunitReporter.write(`\n`); + } + else { + failedTestReporter = new FailedTestReporter(replayRunner, { + reporterOptions: { + file: path.resolve(".failed-tests"), + keepFailed: Harness.keepFailed // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier + } + }); } - return s.length > size ? s.substr(0, size) : s; + const savedUseColors = Base.useColors; + if (noColors) Base.useColors = false; + replayRunner.started = true; + replayRunner.emit("start"); + replaySuite(replayRunner, rebuildSuite(errorResults, passingResults)); + replayRunner.emit("end"); + consoleReporter.epilogue(); + if (noColors) Base.useColors = savedUseColors; + + // eslint-disable-next-line no-null/no-null + IO.writeFile(perfdataFileName(configOption), JSON.stringify(newPerfData, null, 4)); + + if (xunitReporter) { + xunitReporter.done(errorResults.length, failures => process.exit(failures)); + } + else if (failedTestReporter) { + failedTestReporter.done(errorResults.length, failures => process.exit(failures)); + } + else { + process.exit(errorResults.length); + } } + } - function minMax(value: number, min: number, max: number) { - if (value < min) return min; - if (value > max) return max; - return value; + function fill(ch: string, size: number) { + let s = ""; + while (s.length < size) { + s += ch; } - function shimDiscoveryInterface(context: Mocha.MochaGlobals) { - shimNoopTestInterface(context); + return s.length > size ? s.substr(0, size) : s; + } - const perfData = readSavedPerfData(configOption); - context.describe = addSuite as Mocha.SuiteFunction; - context.it = addSuite as Mocha.TestFunction; + function minMax(value: number, min: number, max: number) { + if (value < min) return min; + if (value > max) return max; + return value; + } - function addSuite(title: string) { - // Note, sub-suites are not indexed (we assume such granularity is not required) - let size = 0; - if (perfData) { - size = perfData[hashName("unittest", title)]; - if (size === undefined) { - newTasks.push({ runner: "unittest", file: title, size: 0 }); - unknownValue = title; - return; - } + function shimDiscoveryInterface(context: Mocha.MochaGlobals) { + shimNoopTestInterface(context); + + const perfData = readSavedPerfData(configOption); + context.describe = addSuite as Mocha.SuiteFunction; + context.it = addSuite as Mocha.TestFunction; + + function addSuite(title: string) { + // Note, sub-suites are not indexed (we assume such granularity is not required) + let size = 0; + if (perfData) { + size = perfData[hashName("unittest", title)]; + if (size === undefined) { + newTasks.push({ runner: "unittest", file: title, size: 0 }); + unknownValue = title; + return; } - tasks.push({ runner: "unittest", file: title, size }); - totalCost += size; } + tasks.push({ runner: "unittest", file: title, size }); + totalCost += size; } + } - if (runUnitTests) { - shimDiscoveryInterface(global); - } - else { - shimNoopTestInterface(global); - } - - // eslint-disable-next-line no-restricted-globals - setTimeout(() => startDelayed(perfData, totalCost), 0); // Do real startup on next tick, so all unit tests have been collected + if (runUnitTests) { + shimDiscoveryInterface(global); + } + else { + shimNoopTestInterface(global); } + + // eslint-disable-next-line no-restricted-globals + setTimeout(() => startDelayed(perfData, totalCost), 0); // Do real startup on next tick, so all unit tests have been collected +} } diff --git a/src/testRunner/parallel/shared.ts b/src/testRunner/parallel/shared.ts index bfcef09b4de3f..0fc9f0b1179b4 100644 --- a/src/testRunner/parallel/shared.ts +++ b/src/testRunner/parallel/shared.ts @@ -1,88 +1,88 @@ namespace Harness.Parallel { - export interface RunnerTask { - runner: TestRunnerKind; - file: string; - size: number; - } - - export interface UnitTestTask { - runner: "unittest"; - file: string; - size: number; - } - - export type Task = RunnerTask | UnitTestTask; - - export interface TestInfo { - name: string[]; - } - - export interface ErrorInfo { - name: string[]; - error: string; - stack: string; - } - - export interface TaskTimeout { - duration: number | "reset"; - } - - export interface TaskResult { - passing: number; - errors: ErrorInfo[]; - passes: TestInfo[]; - duration: number; - task: Task; - } - - export interface ParallelTestMessage { - type: "test"; - payload: Task; - } - - export interface ParallelBatchMessage { - type: "batch"; - payload: Task[]; - } - - export interface ParallelCloseMessage { - type: "close"; - } - - export type ParallelHostMessage = ParallelTestMessage | ParallelCloseMessage | ParallelBatchMessage; - - export interface ParallelErrorMessage { - type: "error"; - payload: { error: string, stack: string, name?: string[] }; - } - - export interface ParallelResultMessage { - type: "result"; - payload: TaskResult; - } - - export interface ParallelBatchProgressMessage { - type: "progress"; - payload: TaskResult; - } - - export interface ParallelTimeoutChangeMessage { - type: "timeout"; - payload: TaskTimeout; - } - - export type ParallelClientMessage = ParallelErrorMessage | ParallelResultMessage | ParallelBatchProgressMessage | ParallelTimeoutChangeMessage; - - export function shimNoopTestInterface(global: Mocha.MochaGlobals) { - global.before = ts.noop; - global.after = ts.noop; - global.beforeEach = ts.noop; - global.afterEach = ts.noop; - global.describe = global.context = ((_: any, __: any) => { /*empty*/ }) as Mocha.SuiteFunction; - global.describe.skip = global.xdescribe = global.xcontext = ts.noop as Mocha.PendingSuiteFunction; - global.describe.only = ts.noop as Mocha.ExclusiveSuiteFunction; - global.it = global.specify = ((_: any, __: any) => { /*empty*/ }) as Mocha.TestFunction; - global.it.skip = global.xit = global.xspecify = ts.noop as Mocha.PendingTestFunction; - global.it.only = ts.noop as Mocha.ExclusiveTestFunction; - } +export interface RunnerTask { + runner: TestRunnerKind; + file: string; + size: number; +} + +export interface UnitTestTask { + runner: "unittest"; + file: string; + size: number; +} + +export type Task = RunnerTask | UnitTestTask; + +export interface TestInfo { + name: string[]; +} + +export interface ErrorInfo { + name: string[]; + error: string; + stack: string; +} + +export interface TaskTimeout { + duration: number | "reset"; +} + +export interface TaskResult { + passing: number; + errors: ErrorInfo[]; + passes: TestInfo[]; + duration: number; + task: Task; +} + +export interface ParallelTestMessage { + type: "test"; + payload: Task; +} + +export interface ParallelBatchMessage { + type: "batch"; + payload: Task[]; +} + +export interface ParallelCloseMessage { + type: "close"; +} + +export type ParallelHostMessage = ParallelTestMessage | ParallelCloseMessage | ParallelBatchMessage; + +export interface ParallelErrorMessage { + type: "error"; + payload: { error: string, stack: string, name?: string[] }; +} + +export interface ParallelResultMessage { + type: "result"; + payload: TaskResult; +} + +export interface ParallelBatchProgressMessage { + type: "progress"; + payload: TaskResult; +} + +export interface ParallelTimeoutChangeMessage { + type: "timeout"; + payload: TaskTimeout; +} + +export type ParallelClientMessage = ParallelErrorMessage | ParallelResultMessage | ParallelBatchProgressMessage | ParallelTimeoutChangeMessage; + +export function shimNoopTestInterface(global: Mocha.MochaGlobals) { + global.before = ts.noop; + global.after = ts.noop; + global.beforeEach = ts.noop; + global.afterEach = ts.noop; + global.describe = global.context = ((_: any, __: any) => { /*empty*/ }) as Mocha.SuiteFunction; + global.describe.skip = global.xdescribe = global.xcontext = ts.noop as Mocha.PendingSuiteFunction; + global.describe.only = ts.noop as Mocha.ExclusiveSuiteFunction; + global.it = global.specify = ((_: any, __: any) => { /*empty*/ }) as Mocha.TestFunction; + global.it.skip = global.xit = global.xspecify = ts.noop as Mocha.PendingTestFunction; + global.it.only = ts.noop as Mocha.ExclusiveTestFunction; +} } diff --git a/src/testRunner/parallel/worker.ts b/src/testRunner/parallel/worker.ts index ed91b3c285fd6..ae48154b27f5f 100644 --- a/src/testRunner/parallel/worker.ts +++ b/src/testRunner/parallel/worker.ts @@ -1,318 +1,318 @@ namespace Harness.Parallel.Worker { - export function start() { - function hookUncaughtExceptions() { - if (!exceptionsHooked) { - process.on("uncaughtException", handleUncaughtException); - process.on("unhandledRejection", handleUncaughtException); - exceptionsHooked = true; - } +export function start() { + function hookUncaughtExceptions() { + if (!exceptionsHooked) { + process.on("uncaughtException", handleUncaughtException); + process.on("unhandledRejection", handleUncaughtException); + exceptionsHooked = true; } + } - function unhookUncaughtExceptions() { - if (exceptionsHooked) { - process.removeListener("uncaughtException", handleUncaughtException); - process.removeListener("unhandledRejection", handleUncaughtException); - exceptionsHooked = false; - } + function unhookUncaughtExceptions() { + if (exceptionsHooked) { + process.removeListener("uncaughtException", handleUncaughtException); + process.removeListener("unhandledRejection", handleUncaughtException); + exceptionsHooked = false; } + } - let exceptionsHooked = false; - hookUncaughtExceptions(); + let exceptionsHooked = false; + hookUncaughtExceptions(); - // Capitalization is aligned with the global `Mocha` namespace for typespace/namespace references. - const Mocha = require("mocha") as typeof import("mocha"); + // Capitalization is aligned with the global `Mocha` namespace for typespace/namespace references. + const Mocha = require("mocha") as typeof import("mocha"); - /** - * Mixin helper. - * @param base The base class constructor. - * @param mixins The mixins to apply to the constructor. - */ - function mixin any>(base: T, ...mixins: ((klass: T) => T)[]) { - for (const mixin of mixins) { - base = mixin(base); - } - return base; + /** + * Mixin helper. + * @param base The base class constructor. + * @param mixins The mixins to apply to the constructor. + */ + function mixin any>(base: T, ...mixins: ((klass: T) => T)[]) { + for (const mixin of mixins) { + base = mixin(base); } + return base; + } - /** - * Mixes in overrides for `resetTimeout` and `clearTimeout` to support parallel test execution in a worker. - */ - function Timeout(base: T) { - return class extends (base as typeof Mocha.Runnable) { - resetTimeout() { - this.clearTimeout(); - if (this.timeout() > 0) { - sendMessage({ type: "timeout", payload: { duration: this.timeout() || 1e9 } }); - this.timer = true; - } - } - clearTimeout() { - if (this.timer) { - sendMessage({ type: "timeout", payload: { duration: "reset" } }); - this.timer = false; - } + /** + * Mixes in overrides for `resetTimeout` and `clearTimeout` to support parallel test execution in a worker. + */ + function Timeout(base: T) { + return class extends (base as typeof Mocha.Runnable) { + resetTimeout() { + this.clearTimeout(); + if (this.timeout() > 0) { + sendMessage({ type: "timeout", payload: { duration: this.timeout() || 1e9 } }); + this.timer = true; } - } as T; - } - - /** - * Mixes in an override for `clone` to support parallel test execution in a worker. - */ - function Clone(base: T) { - return class extends (base as new (...args: any[]) => { clone(): any; }) { - clone() { - const cloned = super.clone(); - Object.setPrototypeOf(cloned, this.constructor.prototype); - return cloned; + } + clearTimeout() { + if (this.timer) { + sendMessage({ type: "timeout", payload: { duration: "reset" } }); + this.timer = false; } - } as T; - } + } + } as T; + } - /** - * A `Mocha.Suite` subclass to support parallel test execution in a worker. - */ - class Suite extends mixin(Mocha.Suite, Clone) { - _createHook(title: string, fn?: Mocha.Func | Mocha.AsyncFunc) { - const hook = super._createHook(title, fn); - Object.setPrototypeOf(hook, Hook.prototype); - return hook; + /** + * Mixes in an override for `clone` to support parallel test execution in a worker. + */ + function Clone(base: T) { + return class extends (base as new (...args: any[]) => { clone(): any; }) { + clone() { + const cloned = super.clone(); + Object.setPrototypeOf(cloned, this.constructor.prototype); + return cloned; } - } + } as T; + } - /** - * A `Mocha.Hook` subclass to support parallel test execution in a worker. - */ - class Hook extends mixin(Mocha.Hook, Timeout) { + /** + * A `Mocha.Suite` subclass to support parallel test execution in a worker. + */ + class Suite extends mixin(Mocha.Suite, Clone) { + _createHook(title: string, fn?: Mocha.Func | Mocha.AsyncFunc) { + const hook = super._createHook(title, fn); + Object.setPrototypeOf(hook, Hook.prototype); + return hook; } + } - /** - * A `Mocha.Test` subclass to support parallel test execution in a worker. - */ - class Test extends mixin(Mocha.Test, Timeout, Clone) { - } + /** + * A `Mocha.Hook` subclass to support parallel test execution in a worker. + */ + class Hook extends mixin(Mocha.Hook, Timeout) { + } - /** - * Shims a 'bdd'-style test interface to support parallel test execution in a worker. - * @param rootSuite The root suite. - * @param context The test context (usually the NodeJS `global` object). - */ - function shimTestInterface(rootSuite: Mocha.Suite, context: Mocha.MochaGlobals) { - const suites = [rootSuite]; - context.before = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].beforeAll(title as string, fn); - context.after = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].afterAll(title as string, fn); - context.beforeEach = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].beforeEach(title as string, fn); - context.afterEach = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].afterEach(title as string, fn); - context.describe = context.context = ((title: string, fn: (this: Mocha.Suite) => void) => addSuite(title, fn)) as Mocha.SuiteFunction; - context.describe.skip = context.xdescribe = context.xcontext = (title: string) => addSuite(title, /*fn*/ undefined); - context.describe.only = (title: string, fn?: (this: Mocha.Suite) => void) => addSuite(title, fn); - context.it = context.specify = ((title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => addTest(title, fn)) as Mocha.TestFunction; - context.it.skip = context.xit = context.xspecify = (title: string | Mocha.Func | Mocha.AsyncFunc) => addTest(typeof title === "function" ? title.name : title, /*fn*/ undefined); - context.it.only = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => addTest(title, fn); - - function addSuite(title: string, fn: ((this: Mocha.Suite) => void) | undefined): Mocha.Suite { - const suite = new Suite(title, suites[0].ctx); - suites[0].addSuite(suite); - suite.pending = !fn; - suites.unshift(suite); - if (fn) { - fn.call(suite); - } - suites.shift(); - return suite; - } + /** + * A `Mocha.Test` subclass to support parallel test execution in a worker. + */ + class Test extends mixin(Mocha.Test, Timeout, Clone) { + } - function addTest(title: string | Mocha.Func | Mocha.AsyncFunc, fn: Mocha.Func | Mocha.AsyncFunc | undefined): Mocha.Test { - if (typeof title === "function") { - fn = title; - title = fn.name; - } - const test = new Test(title, suites[0].pending ? undefined : fn); - suites[0].addTest(test); - return test; + /** + * Shims a 'bdd'-style test interface to support parallel test execution in a worker. + * @param rootSuite The root suite. + * @param context The test context (usually the NodeJS `global` object). + */ + function shimTestInterface(rootSuite: Mocha.Suite, context: Mocha.MochaGlobals) { + const suites = [rootSuite]; + context.before = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].beforeAll(title as string, fn); + context.after = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].afterAll(title as string, fn); + context.beforeEach = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].beforeEach(title as string, fn); + context.afterEach = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].afterEach(title as string, fn); + context.describe = context.context = ((title: string, fn: (this: Mocha.Suite) => void) => addSuite(title, fn)) as Mocha.SuiteFunction; + context.describe.skip = context.xdescribe = context.xcontext = (title: string) => addSuite(title, /*fn*/ undefined); + context.describe.only = (title: string, fn?: (this: Mocha.Suite) => void) => addSuite(title, fn); + context.it = context.specify = ((title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => addTest(title, fn)) as Mocha.TestFunction; + context.it.skip = context.xit = context.xspecify = (title: string | Mocha.Func | Mocha.AsyncFunc) => addTest(typeof title === "function" ? title.name : title, /*fn*/ undefined); + context.it.only = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => addTest(title, fn); + + function addSuite(title: string, fn: ((this: Mocha.Suite) => void) | undefined): Mocha.Suite { + const suite = new Suite(title, suites[0].ctx); + suites[0].addSuite(suite); + suite.pending = !fn; + suites.unshift(suite); + if (fn) { + fn.call(suite); } + suites.shift(); + return suite; } - /** - * Run the tests in the requested task. - */ - function runTests(task: Task, fn: (payload: TaskResult) => void) { - if (task.runner === "unittest") { - return executeUnitTests(task, fn); - } - else { - return runFileTests(task, fn); + function addTest(title: string | Mocha.Func | Mocha.AsyncFunc, fn: Mocha.Func | Mocha.AsyncFunc | undefined): Mocha.Test { + if (typeof title === "function") { + fn = title; + title = fn.name; } + const test = new Test(title, suites[0].pending ? undefined : fn); + suites[0].addTest(test); + return test; } + } - function executeUnitTests(task: UnitTestTask, fn: (payload: TaskResult) => void) { - if (!unitTestSuiteMap && unitTestSuite.suites.length) { - unitTestSuiteMap = new ts.Map(); - for (const suite of unitTestSuite.suites) { - unitTestSuiteMap.set(suite.title, suite); - } - } - if (!unitTestTestMap && unitTestSuite.tests.length) { - unitTestTestMap = new ts.Map(); - for (const test of unitTestSuite.tests) { - unitTestTestMap.set(test.title, test); - } - } + /** + * Run the tests in the requested task. + */ + function runTests(task: Task, fn: (payload: TaskResult) => void) { + if (task.runner === "unittest") { + return executeUnitTests(task, fn); + } + else { + return runFileTests(task, fn); + } + } - if (!unitTestSuiteMap && !unitTestTestMap) { - throw new Error(`Asked to run unit test ${task.file}, but no unit tests were discovered!`); + function executeUnitTests(task: UnitTestTask, fn: (payload: TaskResult) => void) { + if (!unitTestSuiteMap && unitTestSuite.suites.length) { + unitTestSuiteMap = new ts.Map(); + for (const suite of unitTestSuite.suites) { + unitTestSuiteMap.set(suite.title, suite); } - - let suite = unitTestSuiteMap.get(task.file); - const test = unitTestTestMap.get(task.file); - if (!suite && !test) { - throw new Error(`Unit test with name "${task.file}" was asked to be run, but such a test does not exist!`); + } + if (!unitTestTestMap && unitTestSuite.tests.length) { + unitTestTestMap = new ts.Map(); + for (const test of unitTestSuite.tests) { + unitTestTestMap.set(test.title, test); } + } - const root = new Suite("", new Mocha.Context()); - root.timeout(globalTimeout || 40_000); - if (suite) { - root.addSuite(suite); - Object.setPrototypeOf(suite.ctx, root.ctx); - } - else if (test) { - const newSuite = new Suite("", new Mocha.Context()); - newSuite.addTest(test); - root.addSuite(newSuite); - Object.setPrototypeOf(newSuite.ctx, root.ctx); - Object.setPrototypeOf(test.ctx, root.ctx); - test.parent = newSuite; - suite = newSuite; - } + if (!unitTestSuiteMap && !unitTestTestMap) { + throw new Error(`Asked to run unit test ${task.file}, but no unit tests were discovered!`); + } - runSuite(task, suite!, payload => { - suite!.parent = unitTestSuite; - Object.setPrototypeOf(suite!.ctx, unitTestSuite.ctx); - fn(payload); - }); + let suite = unitTestSuiteMap.get(task.file); + const test = unitTestTestMap.get(task.file); + if (!suite && !test) { + throw new Error(`Unit test with name "${task.file}" was asked to be run, but such a test does not exist!`); } - function runFileTests(task: RunnerTask, fn: (result: TaskResult) => void) { - let instance = runners.get(task.runner); - if (!instance) runners.set(task.runner, instance = createRunner(task.runner)); - instance.tests = [task.file]; + const root = new Suite("", new Mocha.Context()); + root.timeout(globalTimeout || 40_000); + if (suite) { + root.addSuite(suite); + Object.setPrototypeOf(suite.ctx, root.ctx); + } + else if (test) { + const newSuite = new Suite("", new Mocha.Context()); + newSuite.addTest(test); + root.addSuite(newSuite); + Object.setPrototypeOf(newSuite.ctx, root.ctx); + Object.setPrototypeOf(test.ctx, root.ctx); + test.parent = newSuite; + suite = newSuite; + } - const suite = new Suite("", new Mocha.Context()); - suite.timeout(globalTimeout || 40_000); + runSuite(task, suite!, payload => { + suite!.parent = unitTestSuite; + Object.setPrototypeOf(suite!.ctx, unitTestSuite.ctx); + fn(payload); + }); + } - shimTestInterface(suite, global); - instance.initializeTests(); + function runFileTests(task: RunnerTask, fn: (result: TaskResult) => void) { + let instance = runners.get(task.runner); + if (!instance) runners.set(task.runner, instance = createRunner(task.runner)); + instance.tests = [task.file]; - runSuite(task, suite, fn); - } + const suite = new Suite("", new Mocha.Context()); + suite.timeout(globalTimeout || 40_000); - function runSuite(task: Task, suite: Mocha.Suite, fn: (result: TaskResult) => void) { - const errors: ErrorInfo[] = []; - const passes: TestInfo[] = []; - const start = +new Date(); - const runner = new Mocha.Runner(suite, { delay: false }); - - runner - .on("start", () => { - unhookUncaughtExceptions(); // turn off global uncaught handling - }) - .on("pass", (test: Mocha.Test) => { - passes.push({ name: test.titlePath() }); - }) - .on("fail", (test: Mocha.Test | Mocha.Hook, err: any) => { - errors.push({ name: test.titlePath(), error: err.message, stack: err.stack }); - }) - .on("end", () => { - hookUncaughtExceptions(); - runner.dispose(); - }) - .run(() => { - fn({ task, errors, passes, passing: passes.length, duration: +new Date() - start }); - }); - } + shimTestInterface(suite, global); + instance.initializeTests(); - /** - * Validates a message received from the host is well-formed. - */ - function validateHostMessage(message: ParallelHostMessage) { - switch (message.type) { - case "test": return validateTest(message.payload); - case "batch": return validateBatch(message.payload); - case "close": return true; - default: return false; - } - } + runSuite(task, suite, fn); + } - /** - * Validates a test task is well formed. - */ - function validateTest(task: Task) { - return !!task && !!task.runner && !!task.file; - } + function runSuite(task: Task, suite: Mocha.Suite, fn: (result: TaskResult) => void) { + const errors: ErrorInfo[] = []; + const passes: TestInfo[] = []; + const start = +new Date(); + const runner = new Mocha.Runner(suite, { delay: false }); + + runner + .on("start", () => { + unhookUncaughtExceptions(); // turn off global uncaught handling + }) + .on("pass", (test: Mocha.Test) => { + passes.push({ name: test.titlePath() }); + }) + .on("fail", (test: Mocha.Test | Mocha.Hook, err: any) => { + errors.push({ name: test.titlePath(), error: err.message, stack: err.stack }); + }) + .on("end", () => { + hookUncaughtExceptions(); + runner.dispose(); + }) + .run(() => { + fn({ task, errors, passes, passing: passes.length, duration: +new Date() - start }); + }); + } - /** - * Validates a batch of test tasks are well formed. - */ - function validateBatch(tasks: Task[]) { - return !!tasks && Array.isArray(tasks) && tasks.length > 0 && tasks.every(validateTest); + /** + * Validates a message received from the host is well-formed. + */ + function validateHostMessage(message: ParallelHostMessage) { + switch (message.type) { + case "test": return validateTest(message.payload); + case "batch": return validateBatch(message.payload); + case "close": return true; + default: return false; } + } - function processHostMessage(message: ParallelHostMessage) { - if (!validateHostMessage(message)) { - console.log("Invalid message:", message); - return; - } + /** + * Validates a test task is well formed. + */ + function validateTest(task: Task) { + return !!task && !!task.runner && !!task.file; + } - switch (message.type) { - case "test": return processTest(message.payload, /*last*/ true); - case "batch": return processBatch(message.payload); - case "close": return process.exit(0); - } - } + /** + * Validates a batch of test tasks are well formed. + */ + function validateBatch(tasks: Task[]) { + return !!tasks && Array.isArray(tasks) && tasks.length > 0 && tasks.every(validateTest); + } - function processTest(task: Task, last: boolean, fn?: () => void) { - runTests(task, payload => { - sendMessage(last ? { type: "result", payload } : { type: "progress", payload }); - if (fn) fn(); - }); + function processHostMessage(message: ParallelHostMessage) { + if (!validateHostMessage(message)) { + console.log("Invalid message:", message); + return; } - function processBatch(tasks: Task[], fn?: () => void) { - const next = () => { - const task = tasks.shift(); - if (task) return processTest(task, tasks.length === 0, next); - if (fn) fn(); - }; - next(); + switch (message.type) { + case "test": return processTest(message.payload, /*last*/ true); + case "batch": return processBatch(message.payload); + case "close": return process.exit(0); } + } - function handleUncaughtException(err: any) { - const error = err instanceof Error ? err : new Error("" + err); - sendMessage({ type: "error", payload: { error: error.message, stack: error.stack! } }); - } + function processTest(task: Task, last: boolean, fn?: () => void) { + runTests(task, payload => { + sendMessage(last ? { type: "result", payload } : { type: "progress", payload }); + if (fn) fn(); + }); + } - function sendMessage(message: ParallelClientMessage) { - process.send!(message); - } + function processBatch(tasks: Task[], fn?: () => void) { + const next = () => { + const task = tasks.shift(); + if (task) return processTest(task, tasks.length === 0, next); + if (fn) fn(); + }; + next(); + } - // A cache of test harness Runner instances. - const runners = new ts.Map(); + function handleUncaughtException(err: any) { + const error = err instanceof Error ? err : new Error("" + err); + sendMessage({ type: "error", payload: { error: error.message, stack: error.stack! } }); + } - // The root suite for all unit tests. - let unitTestSuite: Suite; - let unitTestSuiteMap: ts.ESMap; - // (Unit) Tests directly within the root suite - let unitTestTestMap: ts.ESMap; + function sendMessage(message: ParallelClientMessage) { + process.send!(message); + } - if (runUnitTests) { - unitTestSuite = new Suite("", new Mocha.Context()); - unitTestSuite.timeout(globalTimeout || 40_000); - shimTestInterface(unitTestSuite, global); - } - else { - // ensure unit tests do not get run - shimNoopTestInterface(global); - } + // A cache of test harness Runner instances. + const runners = new ts.Map(); - process.on("message", processHostMessage); + // The root suite for all unit tests. + let unitTestSuite: Suite; + let unitTestSuiteMap: ts.ESMap; + // (Unit) Tests directly within the root suite + let unitTestTestMap: ts.ESMap; + + if (runUnitTests) { + unitTestSuite = new Suite("", new Mocha.Context()); + unitTestSuite.timeout(globalTimeout || 40_000); + shimTestInterface(unitTestSuite, global); + } + else { + // ensure unit tests do not get run + shimNoopTestInterface(global); } + + process.on("message", processHostMessage); +} } diff --git a/src/testRunner/playbackRef.ts b/src/testRunner/playbackRef.ts index 8fcb9965e0923..91e14ed52a582 100644 --- a/src/testRunner/playbackRef.ts +++ b/src/testRunner/playbackRef.ts @@ -1,2 +1,2 @@ // empty ref to Playback so it can be referenced by unittests -namespace Playback {} \ No newline at end of file +namespace Playback {} diff --git a/src/testRunner/projectsRunner.ts b/src/testRunner/projectsRunner.ts index 580fca81c222f..d43d1ec835381 100644 --- a/src/testRunner/projectsRunner.ts +++ b/src/testRunner/projectsRunner.ts @@ -1,466 +1,466 @@ namespace project { - // Test case is json of below type in tests/cases/project/ - interface ProjectRunnerTestCase { - scenario: string; - projectRoot: string; // project where it lives - this also is the current directory when compiling - inputFiles: readonly string[]; // list of input files to be given to program - resolveMapRoot?: boolean; // should we resolve this map root and give compiler the absolute disk path as map root? - resolveSourceRoot?: boolean; // should we resolve this source root and give compiler the absolute disk path as map root? - baselineCheck?: boolean; // Verify the baselines of output files, if this is false, we will write to output to the disk but there is no verification of baselines - runTest?: boolean; // Run the resulting test - bug?: string; // If there is any bug associated with this test case - } - - interface ProjectRunnerTestCaseResolutionInfo extends ProjectRunnerTestCase { - // Apart from actual test case the results of the resolution - resolvedInputFiles: readonly string[]; // List of files that were asked to read by compiler - emittedFiles: readonly string[]; // List of files that were emitted by the compiler - } +// Test case is json of below type in tests/cases/project/ +interface ProjectRunnerTestCase { + scenario: string; + projectRoot: string; // project where it lives - this also is the current directory when compiling + inputFiles: readonly string[]; // list of input files to be given to program + resolveMapRoot?: boolean; // should we resolve this map root and give compiler the absolute disk path as map root? + resolveSourceRoot?: boolean; // should we resolve this source root and give compiler the absolute disk path as map root? + baselineCheck?: boolean; // Verify the baselines of output files, if this is false, we will write to output to the disk but there is no verification of baselines + runTest?: boolean; // Run the resulting test + bug?: string; // If there is any bug associated with this test case +} - interface CompileProjectFilesResult { - configFileSourceFiles: readonly ts.SourceFile[]; - moduleKind: ts.ModuleKind; - program?: ts.Program; - compilerOptions?: ts.CompilerOptions; - errors: readonly ts.Diagnostic[]; - sourceMapData?: readonly ts.SourceMapEmitResult[]; - } +interface ProjectRunnerTestCaseResolutionInfo extends ProjectRunnerTestCase { + // Apart from actual test case the results of the resolution + resolvedInputFiles: readonly string[]; // List of files that were asked to read by compiler + emittedFiles: readonly string[]; // List of files that were emitted by the compiler +} - interface BatchCompileProjectTestCaseResult extends CompileProjectFilesResult { - outputFiles?: readonly documents.TextDocument[]; - } +interface CompileProjectFilesResult { + configFileSourceFiles: readonly ts.SourceFile[]; + moduleKind: ts.ModuleKind; + program?: ts.Program; + compilerOptions?: ts.CompilerOptions; + errors: readonly ts.Diagnostic[]; + sourceMapData?: readonly ts.SourceMapEmitResult[]; +} - export class ProjectRunner extends Harness.RunnerBase { - public enumerateTestFiles() { - const all = this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true }); - if (Harness.shards === 1) { - return all; - } - return all.filter((_val, idx) => idx % Harness.shards === (Harness.shardId - 1)); - } +interface BatchCompileProjectTestCaseResult extends CompileProjectFilesResult { + outputFiles?: readonly documents.TextDocument[]; +} - public kind(): Harness.TestRunnerKind { - return "project"; +export class ProjectRunner extends Harness.RunnerBase { + public enumerateTestFiles() { + const all = this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true }); + if (Harness.shards === 1) { + return all; } + return all.filter((_val, idx) => idx % Harness.shards === (Harness.shardId - 1)); + } - public initializeTests() { - describe("projects tests", () => { - const tests = this.tests.length === 0 ? this.enumerateTestFiles() : this.tests; - for (const test of tests) { - this.runProjectTestCase(typeof test === "string" ? test : test.file); - } - }); - } + public kind(): Harness.TestRunnerKind { + return "project"; + } - private runProjectTestCase(testCaseFileName: string) { - for (const { name, payload } of ProjectTestCase.getConfigurations(testCaseFileName)) { - describe("Compiling project for " + payload.testCase.scenario + ": testcase " + testCaseFileName + (name ? ` (${name})` : ``), () => { - let projectTestCase: ProjectTestCase | undefined; - before(() => { - projectTestCase = new ProjectTestCase(testCaseFileName, payload); - }); - it(`Correct module resolution tracing for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyResolution()); - it(`Correct errors for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyDiagnostics()); - it(`Correct JS output for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyJavaScriptOutput()); - // NOTE: This check was commented out in previous code. Leaving this here to eventually be restored if needed. - // it(`Correct sourcemap content for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifySourceMapRecord()); - it(`Correct declarations for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyDeclarations()); - after(() => { - projectTestCase = undefined; - }); - }); + public initializeTests() { + describe("projects tests", () => { + const tests = this.tests.length === 0 ? this.enumerateTestFiles() : this.tests; + for (const test of tests) { + this.runProjectTestCase(typeof test === "string" ? test : test.file); } - } + }); } - class ProjectCompilerHost extends fakes.CompilerHost { - private _testCase: ProjectRunnerTestCase & ts.CompilerOptions; - private _projectParseConfigHost: ProjectParseConfigHost | undefined; - - constructor(sys: fakes.System | vfs.FileSystem, compilerOptions: ts.CompilerOptions, _testCaseJustName: string, testCase: ProjectRunnerTestCase & ts.CompilerOptions, _moduleKind: ts.ModuleKind) { - super(sys, compilerOptions); - this._testCase = testCase; + private runProjectTestCase(testCaseFileName: string) { + for (const { name, payload } of ProjectTestCase.getConfigurations(testCaseFileName)) { + describe("Compiling project for " + payload.testCase.scenario + ": testcase " + testCaseFileName + (name ? ` (${name})` : ``), () => { + let projectTestCase: ProjectTestCase | undefined; + before(() => { + projectTestCase = new ProjectTestCase(testCaseFileName, payload); + }); + it(`Correct module resolution tracing for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyResolution()); + it(`Correct errors for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyDiagnostics()); + it(`Correct JS output for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyJavaScriptOutput()); + // NOTE: This check was commented out in previous code. Leaving this here to eventually be restored if needed. + // it(`Correct sourcemap content for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifySourceMapRecord()); + it(`Correct declarations for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyDeclarations()); + after(() => { + projectTestCase = undefined; + }); + }); } + } +} - public get parseConfigHost(): fakes.ParseConfigHost { - return this._projectParseConfigHost || (this._projectParseConfigHost = new ProjectParseConfigHost(this.sys, this._testCase)); - } +class ProjectCompilerHost extends fakes.CompilerHost { + private _testCase: ProjectRunnerTestCase & ts.CompilerOptions; + private _projectParseConfigHost: ProjectParseConfigHost | undefined; - public getDefaultLibFileName(_options: ts.CompilerOptions) { - return vpath.resolve(this.getDefaultLibLocation(), "lib.es5.d.ts"); - } + constructor(sys: fakes.System | vfs.FileSystem, compilerOptions: ts.CompilerOptions, _testCaseJustName: string, testCase: ProjectRunnerTestCase & ts.CompilerOptions, _moduleKind: ts.ModuleKind) { + super(sys, compilerOptions); + this._testCase = testCase; } - class ProjectParseConfigHost extends fakes.ParseConfigHost { - private _testCase: ProjectRunnerTestCase & ts.CompilerOptions; - - constructor(sys: fakes.System, testCase: ProjectRunnerTestCase & ts.CompilerOptions) { - super(sys); - this._testCase = testCase; - } + public get parseConfigHost(): fakes.ParseConfigHost { + return this._projectParseConfigHost || (this._projectParseConfigHost = new ProjectParseConfigHost(this.sys, this._testCase)); + } - public readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { - const result = super.readDirectory(path, extensions, excludes, includes, depth); - const projectRoot = vpath.resolve(vfs.srcFolder, this._testCase.projectRoot); - return result.map(item => vpath.relative( - projectRoot, - vpath.resolve(projectRoot, item), - this.vfs.ignoreCase - )); - } + public getDefaultLibFileName(_options: ts.CompilerOptions) { + return vpath.resolve(this.getDefaultLibLocation(), "lib.es5.d.ts"); } +} - interface ProjectTestConfiguration { - name: string; - payload: ProjectTestPayload; +class ProjectParseConfigHost extends fakes.ParseConfigHost { + private _testCase: ProjectRunnerTestCase & ts.CompilerOptions; + + constructor(sys: fakes.System, testCase: ProjectRunnerTestCase & ts.CompilerOptions) { + super(sys); + this._testCase = testCase; } - interface ProjectTestPayload { - testCase: ProjectRunnerTestCase & ts.CompilerOptions; - moduleKind: ts.ModuleKind; - vfs: vfs.FileSystem; + public readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { + const result = super.readDirectory(path, extensions, excludes, includes, depth); + const projectRoot = vpath.resolve(vfs.srcFolder, this._testCase.projectRoot); + return result.map(item => vpath.relative( + projectRoot, + vpath.resolve(projectRoot, item), + this.vfs.ignoreCase + )); } +} - class ProjectTestCase { - private testCase: ProjectRunnerTestCase & ts.CompilerOptions; - private testCaseJustName: string; - private sys: fakes.System; - private compilerOptions: ts.CompilerOptions; - private compilerResult: BatchCompileProjectTestCaseResult; - - constructor(testCaseFileName: string, { testCase, moduleKind, vfs }: ProjectTestPayload) { - this.testCase = testCase; - this.testCaseJustName = testCaseFileName.replace(/^.*[\\\/]/, "").replace(/\.json/, ""); - this.compilerOptions = createCompilerOptions(testCase, moduleKind); - this.sys = new fakes.System(vfs); - - let configFileName: string | undefined; - let inputFiles = testCase.inputFiles; - if (this.compilerOptions.project) { - // Parse project - configFileName = ts.normalizePath(ts.combinePaths(this.compilerOptions.project, "tsconfig.json")); - assert(!inputFiles || inputFiles.length === 0, "cannot specify input files and project option together"); - } - else if (!inputFiles || inputFiles.length === 0) { - configFileName = ts.findConfigFile("", path => this.sys.fileExists(path)); - } +interface ProjectTestConfiguration { + name: string; + payload: ProjectTestPayload; +} - let errors: ts.Diagnostic[] | undefined; - const configFileSourceFiles: ts.SourceFile[] = []; - if (configFileName) { - const result = ts.readJsonConfigFile(configFileName, path => this.sys.readFile(path)); - configFileSourceFiles.push(result); - const configParseHost = new ProjectParseConfigHost(this.sys, this.testCase); - const configParseResult = ts.parseJsonSourceFileConfigFileContent(result, configParseHost, ts.getDirectoryPath(configFileName), this.compilerOptions); - inputFiles = configParseResult.fileNames; - this.compilerOptions = configParseResult.options; - errors = [...result.parseDiagnostics, ...configParseResult.errors]; - } +interface ProjectTestPayload { + testCase: ProjectRunnerTestCase & ts.CompilerOptions; + moduleKind: ts.ModuleKind; + vfs: vfs.FileSystem; +} - const compilerHost = new ProjectCompilerHost(this.sys, this.compilerOptions, this.testCaseJustName, this.testCase, moduleKind); - const projectCompilerResult = this.compileProjectFiles(moduleKind, configFileSourceFiles, () => inputFiles, compilerHost, this.compilerOptions); - - this.compilerResult = { - configFileSourceFiles, - moduleKind, - program: projectCompilerResult.program, - compilerOptions: this.compilerOptions, - sourceMapData: projectCompilerResult.sourceMapData, - outputFiles: compilerHost.outputs, - errors: errors ? ts.concatenate(errors, projectCompilerResult.errors) : projectCompilerResult.errors, - }; +class ProjectTestCase { + private testCase: ProjectRunnerTestCase & ts.CompilerOptions; + private testCaseJustName: string; + private sys: fakes.System; + private compilerOptions: ts.CompilerOptions; + private compilerResult: BatchCompileProjectTestCaseResult; + + constructor(testCaseFileName: string, { testCase, moduleKind, vfs }: ProjectTestPayload) { + this.testCase = testCase; + this.testCaseJustName = testCaseFileName.replace(/^.*[\\\/]/, "").replace(/\.json/, ""); + this.compilerOptions = createCompilerOptions(testCase, moduleKind); + this.sys = new fakes.System(vfs); + + let configFileName: string | undefined; + let inputFiles = testCase.inputFiles; + if (this.compilerOptions.project) { + // Parse project + configFileName = ts.normalizePath(ts.combinePaths(this.compilerOptions.project, "tsconfig.json")); + assert(!inputFiles || inputFiles.length === 0, "cannot specify input files and project option together"); } - - private get vfs() { - return this.sys.vfs; + else if (!inputFiles || inputFiles.length === 0) { + configFileName = ts.findConfigFile("", path => this.sys.fileExists(path)); } - public static getConfigurations(testCaseFileName: string): ProjectTestConfiguration[] { - let testCase: ProjectRunnerTestCase & ts.CompilerOptions; + let errors: ts.Diagnostic[] | undefined; + const configFileSourceFiles: ts.SourceFile[] = []; + if (configFileName) { + const result = ts.readJsonConfigFile(configFileName, path => this.sys.readFile(path)); + configFileSourceFiles.push(result); + const configParseHost = new ProjectParseConfigHost(this.sys, this.testCase); + const configParseResult = ts.parseJsonSourceFileConfigFileContent(result, configParseHost, ts.getDirectoryPath(configFileName), this.compilerOptions); + inputFiles = configParseResult.fileNames; + this.compilerOptions = configParseResult.options; + errors = [...result.parseDiagnostics, ...configParseResult.errors]; + } - let testFileText: string | undefined; - try { - testFileText = Harness.IO.readFile(testCaseFileName); - } - catch (e) { - assert(false, "Unable to open testcase file: " + testCaseFileName + ": " + e.message); - } + const compilerHost = new ProjectCompilerHost(this.sys, this.compilerOptions, this.testCaseJustName, this.testCase, moduleKind); + const projectCompilerResult = this.compileProjectFiles(moduleKind, configFileSourceFiles, () => inputFiles, compilerHost, this.compilerOptions); + + this.compilerResult = { + configFileSourceFiles, + moduleKind, + program: projectCompilerResult.program, + compilerOptions: this.compilerOptions, + sourceMapData: projectCompilerResult.sourceMapData, + outputFiles: compilerHost.outputs, + errors: errors ? ts.concatenate(errors, projectCompilerResult.errors) : projectCompilerResult.errors, + }; + } - try { - testCase = JSON.parse(testFileText!) as ProjectRunnerTestCase & ts.CompilerOptions; - } - catch (e) { - throw assert(false, "Testcase: " + testCaseFileName + " does not contain valid json format: " + e.message); - } + private get vfs() { + return this.sys.vfs; + } - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false); - fs.mountSync(vpath.resolve(Harness.IO.getWorkspaceRoot(), "tests"), vpath.combine(vfs.srcFolder, "tests"), vfs.createResolver(Harness.IO)); - fs.mkdirpSync(vpath.combine(vfs.srcFolder, testCase.projectRoot)); - fs.chdir(vpath.combine(vfs.srcFolder, testCase.projectRoot)); - fs.makeReadonly(); + public static getConfigurations(testCaseFileName: string): ProjectTestConfiguration[] { + let testCase: ProjectRunnerTestCase & ts.CompilerOptions; - return [ - { name: `@module: commonjs`, payload: { testCase, moduleKind: ts.ModuleKind.CommonJS, vfs: fs } }, - { name: `@module: amd`, payload: { testCase, moduleKind: ts.ModuleKind.AMD, vfs: fs } } - ]; + let testFileText: string | undefined; + try { + testFileText = Harness.IO.readFile(testCaseFileName); + } + catch (e) { + assert(false, "Unable to open testcase file: " + testCaseFileName + ": " + e.message); } - public verifyResolution() { - const cwd = this.vfs.cwd(); - const ignoreCase = this.vfs.ignoreCase; - const resolutionInfo: ProjectRunnerTestCaseResolutionInfo & ts.CompilerOptions = JSON.parse(JSON.stringify(this.testCase)); - resolutionInfo.resolvedInputFiles = this.compilerResult.program!.getSourceFiles() - .map(({ fileName: input }) => - vpath.beneath(vfs.builtFolder, input, this.vfs.ignoreCase) || vpath.beneath(vfs.testLibFolder, input, this.vfs.ignoreCase) ? Utils.removeTestPathPrefixes(input) : - vpath.isAbsolute(input) ? vpath.relative(cwd, input, ignoreCase) : - input); - - resolutionInfo.emittedFiles = this.compilerResult.outputFiles! - .map(output => output.meta.get("fileName") || output.file) - .map(output => Utils.removeTestPathPrefixes(vpath.isAbsolute(output) ? vpath.relative(cwd, output, ignoreCase) : output)); - - const content = JSON.stringify(resolutionInfo, undefined, " "); - Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".json", content); + try { + testCase = JSON.parse(testFileText!) as ProjectRunnerTestCase & ts.CompilerOptions; + } + catch (e) { + throw assert(false, "Testcase: " + testCaseFileName + " does not contain valid json format: " + e.message); } - public verifyDiagnostics() { - if (this.compilerResult.errors.length) { - Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".errors.txt", getErrorsBaseline(this.compilerResult)); - } + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false); + fs.mountSync(vpath.resolve(Harness.IO.getWorkspaceRoot(), "tests"), vpath.combine(vfs.srcFolder, "tests"), vfs.createResolver(Harness.IO)); + fs.mkdirpSync(vpath.combine(vfs.srcFolder, testCase.projectRoot)); + fs.chdir(vpath.combine(vfs.srcFolder, testCase.projectRoot)); + fs.makeReadonly(); + + return [ + { name: `@module: commonjs`, payload: { testCase, moduleKind: ts.ModuleKind.CommonJS, vfs: fs } }, + { name: `@module: amd`, payload: { testCase, moduleKind: ts.ModuleKind.AMD, vfs: fs } } + ]; + } + + public verifyResolution() { + const cwd = this.vfs.cwd(); + const ignoreCase = this.vfs.ignoreCase; + const resolutionInfo: ProjectRunnerTestCaseResolutionInfo & ts.CompilerOptions = JSON.parse(JSON.stringify(this.testCase)); + resolutionInfo.resolvedInputFiles = this.compilerResult.program!.getSourceFiles() + .map(({ fileName: input }) => + vpath.beneath(vfs.builtFolder, input, this.vfs.ignoreCase) || vpath.beneath(vfs.testLibFolder, input, this.vfs.ignoreCase) ? Utils.removeTestPathPrefixes(input) : + vpath.isAbsolute(input) ? vpath.relative(cwd, input, ignoreCase) : + input); + + resolutionInfo.emittedFiles = this.compilerResult.outputFiles! + .map(output => output.meta.get("fileName") || output.file) + .map(output => Utils.removeTestPathPrefixes(vpath.isAbsolute(output) ? vpath.relative(cwd, output, ignoreCase) : output)); + + const content = JSON.stringify(resolutionInfo, undefined, " "); + Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".json", content); + } + + public verifyDiagnostics() { + if (this.compilerResult.errors.length) { + Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".errors.txt", getErrorsBaseline(this.compilerResult)); } + } - public verifyJavaScriptOutput() { - if (this.testCase.baselineCheck) { - const errs: Error[] = []; - let nonSubfolderDiskFiles = 0; - for (const output of this.compilerResult.outputFiles!) { - try { - // convert file name to rooted name - // if filename is not rooted - concat it with project root and then expand project root relative to current directory - const fileName = output.meta.get("fileName") || output.file; - const diskFileName = vpath.isAbsolute(fileName) ? fileName : vpath.resolve(this.vfs.cwd(), fileName); - - // compute file name relative to current directory (expanded project root) - let diskRelativeName = vpath.relative(this.vfs.cwd(), diskFileName, this.vfs.ignoreCase); - if (vpath.isAbsolute(diskRelativeName) || diskRelativeName.startsWith("../")) { - // If the generated output file resides in the parent folder or is rooted path, - // we need to instead create files that can live in the project reference folder - // but make sure extension of these files matches with the fileName the compiler asked to write - diskRelativeName = `diskFile${nonSubfolderDiskFiles}${vpath.extname(fileName, [".js.map", ".js", ".d.ts"], this.vfs.ignoreCase)}`; - nonSubfolderDiskFiles++; - } - - const content = Utils.removeTestPathPrefixes(output.text, /*retainTrailingDirectorySeparator*/ true); - Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + diskRelativeName, content as string | null); // TODO: GH#18217 - } - catch (e) { - errs.push(e); + public verifyJavaScriptOutput() { + if (this.testCase.baselineCheck) { + const errs: Error[] = []; + let nonSubfolderDiskFiles = 0; + for (const output of this.compilerResult.outputFiles!) { + try { + // convert file name to rooted name + // if filename is not rooted - concat it with project root and then expand project root relative to current directory + const fileName = output.meta.get("fileName") || output.file; + const diskFileName = vpath.isAbsolute(fileName) ? fileName : vpath.resolve(this.vfs.cwd(), fileName); + + // compute file name relative to current directory (expanded project root) + let diskRelativeName = vpath.relative(this.vfs.cwd(), diskFileName, this.vfs.ignoreCase); + if (vpath.isAbsolute(diskRelativeName) || diskRelativeName.startsWith("../")) { + // If the generated output file resides in the parent folder or is rooted path, + // we need to instead create files that can live in the project reference folder + // but make sure extension of these files matches with the fileName the compiler asked to write + diskRelativeName = `diskFile${nonSubfolderDiskFiles}${vpath.extname(fileName, [".js.map", ".js", ".d.ts"], this.vfs.ignoreCase)}`; + nonSubfolderDiskFiles++; } - } - if (errs.length) { - throw Error(errs.join("\n ")); + const content = Utils.removeTestPathPrefixes(output.text, /*retainTrailingDirectorySeparator*/ true); + Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + diskRelativeName, content as string | null); // TODO: GH#18217 + } + catch (e) { + errs.push(e); } } - } - public verifySourceMapRecord() { - // NOTE: This check was commented out in previous code. Leaving this here to eventually be restored if needed. - // if (compilerResult.sourceMapData) { - // Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".sourcemap.txt", () => { - // return Harness.SourceMapRecorder.getSourceMapRecord(compilerResult.sourceMapData, compilerResult.program, - // ts.filter(compilerResult.outputFiles, outputFile => Harness.Compiler.isJS(outputFile.emittedFileName))); - // }); - // } + if (errs.length) { + throw Error(errs.join("\n ")); + } } + } - public verifyDeclarations() { - if (!this.compilerResult.errors.length && this.testCase.declaration) { - const dTsCompileResult = this.compileDeclarations(this.compilerResult); - if (dTsCompileResult && dTsCompileResult.errors.length) { - Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".dts.errors.txt", getErrorsBaseline(dTsCompileResult)); - } + public verifySourceMapRecord() { + // NOTE: This check was commented out in previous code. Leaving this here to eventually be restored if needed. + // if (compilerResult.sourceMapData) { + // Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".sourcemap.txt", () => { + // return Harness.SourceMapRecorder.getSourceMapRecord(compilerResult.sourceMapData, compilerResult.program, + // ts.filter(compilerResult.outputFiles, outputFile => Harness.Compiler.isJS(outputFile.emittedFileName))); + // }); + // } + } + + public verifyDeclarations() { + if (!this.compilerResult.errors.length && this.testCase.declaration) { + const dTsCompileResult = this.compileDeclarations(this.compilerResult); + if (dTsCompileResult && dTsCompileResult.errors.length) { + Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".dts.errors.txt", getErrorsBaseline(dTsCompileResult)); } } + } - // Project baselines verified go in project/testCaseName/moduleKind/ - private getBaselineFolder(moduleKind: ts.ModuleKind) { - return "project/" + this.testCaseJustName + "/" + moduleNameToString(moduleKind) + "/"; - } + // Project baselines verified go in project/testCaseName/moduleKind/ + private getBaselineFolder(moduleKind: ts.ModuleKind) { + return "project/" + this.testCaseJustName + "/" + moduleNameToString(moduleKind) + "/"; + } - private cleanProjectUrl(url: string) { - let diskProjectPath = ts.normalizeSlashes(Harness.IO.resolvePath(this.testCase.projectRoot)!); - let projectRootUrl = "file:///" + diskProjectPath; - const normalizedProjectRoot = ts.normalizeSlashes(this.testCase.projectRoot); - diskProjectPath = diskProjectPath.substr(0, diskProjectPath.lastIndexOf(normalizedProjectRoot)); - projectRootUrl = projectRootUrl.substr(0, projectRootUrl.lastIndexOf(normalizedProjectRoot)); - if (url && url.length) { - if (url.indexOf(projectRootUrl) === 0) { - // replace the disk specific project url path into project root url - url = "file:///" + url.substr(projectRootUrl.length); - } - else if (url.indexOf(diskProjectPath) === 0) { - // Replace the disk specific path into the project root path - url = url.substr(diskProjectPath.length); - if (url.charCodeAt(0) !== ts.CharacterCodes.slash) { - url = "/" + url; - } + private cleanProjectUrl(url: string) { + let diskProjectPath = ts.normalizeSlashes(Harness.IO.resolvePath(this.testCase.projectRoot)!); + let projectRootUrl = "file:///" + diskProjectPath; + const normalizedProjectRoot = ts.normalizeSlashes(this.testCase.projectRoot); + diskProjectPath = diskProjectPath.substr(0, diskProjectPath.lastIndexOf(normalizedProjectRoot)); + projectRootUrl = projectRootUrl.substr(0, projectRootUrl.lastIndexOf(normalizedProjectRoot)); + if (url && url.length) { + if (url.indexOf(projectRootUrl) === 0) { + // replace the disk specific project url path into project root url + url = "file:///" + url.substr(projectRootUrl.length); + } + else if (url.indexOf(diskProjectPath) === 0) { + // Replace the disk specific path into the project root path + url = url.substr(diskProjectPath.length); + if (url.charCodeAt(0) !== ts.CharacterCodes.slash) { + url = "/" + url; } } - - return url; } - private compileProjectFiles(moduleKind: ts.ModuleKind, configFileSourceFiles: readonly ts.SourceFile[], - getInputFiles: () => readonly string[], - compilerHost: ts.CompilerHost, - compilerOptions: ts.CompilerOptions): CompileProjectFilesResult { + return url; + } - const program = ts.createProgram(getInputFiles(), compilerOptions, compilerHost); - const errors = ts.getPreEmitDiagnostics(program); + private compileProjectFiles(moduleKind: ts.ModuleKind, configFileSourceFiles: readonly ts.SourceFile[], + getInputFiles: () => readonly string[], + compilerHost: ts.CompilerHost, + compilerOptions: ts.CompilerOptions): CompileProjectFilesResult { - const { sourceMaps: sourceMapData, diagnostics: emitDiagnostics } = program.emit(); + const program = ts.createProgram(getInputFiles(), compilerOptions, compilerHost); + const errors = ts.getPreEmitDiagnostics(program); - // Clean up source map data that will be used in baselining - if (sourceMapData) { - for (const data of sourceMapData) { - data.sourceMap = { - ...data.sourceMap, - sources: data.sourceMap.sources.map(source => this.cleanProjectUrl(source)), - sourceRoot: data.sourceMap.sourceRoot && this.cleanProjectUrl(data.sourceMap.sourceRoot) - }; - } + const { sourceMaps: sourceMapData, diagnostics: emitDiagnostics } = program.emit(); + + // Clean up source map data that will be used in baselining + if (sourceMapData) { + for (const data of sourceMapData) { + data.sourceMap = { + ...data.sourceMap, + sources: data.sourceMap.sources.map(source => this.cleanProjectUrl(source)), + sourceRoot: data.sourceMap.sourceRoot && this.cleanProjectUrl(data.sourceMap.sourceRoot) + }; } + } + + return { + configFileSourceFiles, + moduleKind, + program, + errors: ts.concatenate(errors, emitDiagnostics), + sourceMapData + }; + } - return { - configFileSourceFiles, - moduleKind, - program, - errors: ts.concatenate(errors, emitDiagnostics), - sourceMapData - }; + private compileDeclarations(compilerResult: BatchCompileProjectTestCaseResult) { + if (!compilerResult.program) { + return; } - private compileDeclarations(compilerResult: BatchCompileProjectTestCaseResult) { - if (!compilerResult.program) { - return; + const compilerOptions = compilerResult.program.getCompilerOptions(); + const allInputFiles: documents.TextDocument[] = []; + const rootFiles: string[] = []; + ts.forEach(compilerResult.program.getSourceFiles(), sourceFile => { + if (sourceFile.isDeclarationFile) { + if (!vpath.isDefaultLibrary(sourceFile.fileName)) { + allInputFiles.unshift(new documents.TextDocument(sourceFile.fileName, sourceFile.text)); + } + rootFiles.unshift(sourceFile.fileName); } - - const compilerOptions = compilerResult.program.getCompilerOptions(); - const allInputFiles: documents.TextDocument[] = []; - const rootFiles: string[] = []; - ts.forEach(compilerResult.program.getSourceFiles(), sourceFile => { - if (sourceFile.isDeclarationFile) { - if (!vpath.isDefaultLibrary(sourceFile.fileName)) { - allInputFiles.unshift(new documents.TextDocument(sourceFile.fileName, sourceFile.text)); - } - rootFiles.unshift(sourceFile.fileName); + else if (!(compilerOptions.outFile || compilerOptions.out)) { + let emitOutputFilePathWithoutExtension: string | undefined; + if (compilerOptions.outDir) { + let sourceFilePath = ts.getNormalizedAbsolutePath(sourceFile.fileName, compilerResult.program!.getCurrentDirectory()); + sourceFilePath = sourceFilePath.replace(compilerResult.program!.getCommonSourceDirectory(), ""); + emitOutputFilePathWithoutExtension = ts.removeFileExtension(ts.combinePaths(compilerOptions.outDir, sourceFilePath)); + } + else { + emitOutputFilePathWithoutExtension = ts.removeFileExtension(sourceFile.fileName); } - else if (!(compilerOptions.outFile || compilerOptions.out)) { - let emitOutputFilePathWithoutExtension: string | undefined; - if (compilerOptions.outDir) { - let sourceFilePath = ts.getNormalizedAbsolutePath(sourceFile.fileName, compilerResult.program!.getCurrentDirectory()); - sourceFilePath = sourceFilePath.replace(compilerResult.program!.getCommonSourceDirectory(), ""); - emitOutputFilePathWithoutExtension = ts.removeFileExtension(ts.combinePaths(compilerOptions.outDir, sourceFilePath)); - } - else { - emitOutputFilePathWithoutExtension = ts.removeFileExtension(sourceFile.fileName); - } - const outputDtsFileName = emitOutputFilePathWithoutExtension + ts.Extension.Dts; - const file = findOutputDtsFile(outputDtsFileName); - if (file) { - allInputFiles.unshift(file); - rootFiles.unshift(file.meta.get("fileName") || file.file); - } + const outputDtsFileName = emitOutputFilePathWithoutExtension + ts.Extension.Dts; + const file = findOutputDtsFile(outputDtsFileName); + if (file) { + allInputFiles.unshift(file); + rootFiles.unshift(file.meta.get("fileName") || file.file); } - else { - const outputDtsFileName = ts.removeFileExtension(compilerOptions.outFile || compilerOptions.out!) + ts.Extension.Dts; - const outputDtsFile = findOutputDtsFile(outputDtsFileName)!; - if (!ts.contains(allInputFiles, outputDtsFile)) { - allInputFiles.unshift(outputDtsFile); - rootFiles.unshift(outputDtsFile.meta.get("fileName") || outputDtsFile.file); - } + } + else { + const outputDtsFileName = ts.removeFileExtension(compilerOptions.outFile || compilerOptions.out!) + ts.Extension.Dts; + const outputDtsFile = findOutputDtsFile(outputDtsFileName)!; + if (!ts.contains(allInputFiles, outputDtsFile)) { + allInputFiles.unshift(outputDtsFile); + rootFiles.unshift(outputDtsFile.meta.get("fileName") || outputDtsFile.file); } - }); + } + }); - const _vfs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { - documents: allInputFiles, - cwd: vpath.combine(vfs.srcFolder, this.testCase.projectRoot) - }); + const _vfs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { + documents: allInputFiles, + cwd: vpath.combine(vfs.srcFolder, this.testCase.projectRoot) + }); - // Dont allow config files since we are compiling existing source options - const compilerHost = new ProjectCompilerHost(_vfs, compilerResult.compilerOptions!, this.testCaseJustName, this.testCase, compilerResult.moduleKind); - return this.compileProjectFiles(compilerResult.moduleKind, compilerResult.configFileSourceFiles, () => rootFiles, compilerHost, compilerResult.compilerOptions!); + // Dont allow config files since we are compiling existing source options + const compilerHost = new ProjectCompilerHost(_vfs, compilerResult.compilerOptions!, this.testCaseJustName, this.testCase, compilerResult.moduleKind); + return this.compileProjectFiles(compilerResult.moduleKind, compilerResult.configFileSourceFiles, () => rootFiles, compilerHost, compilerResult.compilerOptions!); - function findOutputDtsFile(fileName: string) { - return ts.forEach(compilerResult.outputFiles, outputFile => outputFile.meta.get("fileName") === fileName ? outputFile : undefined); - } + function findOutputDtsFile(fileName: string) { + return ts.forEach(compilerResult.outputFiles, outputFile => outputFile.meta.get("fileName") === fileName ? outputFile : undefined); } } +} - function moduleNameToString(moduleKind: ts.ModuleKind) { - return moduleKind === ts.ModuleKind.AMD ? "amd" : - moduleKind === ts.ModuleKind.CommonJS ? "node" : "none"; - } +function moduleNameToString(moduleKind: ts.ModuleKind) { + return moduleKind === ts.ModuleKind.AMD ? "amd" : + moduleKind === ts.ModuleKind.CommonJS ? "node" : "none"; +} - function getErrorsBaseline(compilerResult: CompileProjectFilesResult) { - const inputSourceFiles = compilerResult.configFileSourceFiles.slice(); - if (compilerResult.program) { - for (const sourceFile of compilerResult.program.getSourceFiles()) { - if (!Harness.isDefaultLibraryFile(sourceFile.fileName)) { - inputSourceFiles.push(sourceFile); - } +function getErrorsBaseline(compilerResult: CompileProjectFilesResult) { + const inputSourceFiles = compilerResult.configFileSourceFiles.slice(); + if (compilerResult.program) { + for (const sourceFile of compilerResult.program.getSourceFiles()) { + if (!Harness.isDefaultLibraryFile(sourceFile.fileName)) { + inputSourceFiles.push(sourceFile); } } - - const inputFiles = inputSourceFiles.map(sourceFile => ({ - unitName: ts.isRootedDiskPath(sourceFile.fileName) ? - Harness.RunnerBase.removeFullPaths(sourceFile.fileName) : - sourceFile.fileName, - content: sourceFile.text - })); - - return Harness.Compiler.getErrorBaseline(inputFiles, compilerResult.errors); } - function createCompilerOptions(testCase: ProjectRunnerTestCase & ts.CompilerOptions, moduleKind: ts.ModuleKind) { - // Set the special options that depend on other testcase options - const compilerOptions: ts.CompilerOptions = { - noErrorTruncation: false, - skipDefaultLibCheck: false, - moduleResolution: ts.ModuleResolutionKind.Classic, - module: moduleKind, - newLine: ts.NewLineKind.CarriageReturnLineFeed, - mapRoot: testCase.resolveMapRoot && testCase.mapRoot - ? vpath.resolve(vfs.srcFolder, testCase.mapRoot) - : testCase.mapRoot, - - sourceRoot: testCase.resolveSourceRoot && testCase.sourceRoot - ? vpath.resolve(vfs.srcFolder, testCase.sourceRoot) - : testCase.sourceRoot - }; + const inputFiles = inputSourceFiles.map(sourceFile => ({ + unitName: ts.isRootedDiskPath(sourceFile.fileName) ? + Harness.RunnerBase.removeFullPaths(sourceFile.fileName) : + sourceFile.fileName, + content: sourceFile.text + })); + + return Harness.Compiler.getErrorBaseline(inputFiles, compilerResult.errors); +} - // Set the values specified using json - const optionNameMap = ts.arrayToMap(ts.optionDeclarations, option => option.name); - for (const name in testCase) { - if (name !== "mapRoot" && name !== "sourceRoot") { - const option = optionNameMap.get(name); - if (option) { - const optType = option.type; - let value = testCase[name] as any; - if (!ts.isString(optType)) { - const key = value.toLowerCase(); - const optTypeValue = optType.get(key); - if (optTypeValue) { - value = optTypeValue; - } +function createCompilerOptions(testCase: ProjectRunnerTestCase & ts.CompilerOptions, moduleKind: ts.ModuleKind) { + // Set the special options that depend on other testcase options + const compilerOptions: ts.CompilerOptions = { + noErrorTruncation: false, + skipDefaultLibCheck: false, + moduleResolution: ts.ModuleResolutionKind.Classic, + module: moduleKind, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + mapRoot: testCase.resolveMapRoot && testCase.mapRoot + ? vpath.resolve(vfs.srcFolder, testCase.mapRoot) + : testCase.mapRoot, + + sourceRoot: testCase.resolveSourceRoot && testCase.sourceRoot + ? vpath.resolve(vfs.srcFolder, testCase.sourceRoot) + : testCase.sourceRoot + }; + + // Set the values specified using json + const optionNameMap = ts.arrayToMap(ts.optionDeclarations, option => option.name); + for (const name in testCase) { + if (name !== "mapRoot" && name !== "sourceRoot") { + const option = optionNameMap.get(name); + if (option) { + const optType = option.type; + let value = testCase[name] as any; + if (!ts.isString(optType)) { + const key = value.toLowerCase(); + const optTypeValue = optType.get(key); + if (optTypeValue) { + value = optTypeValue; } - compilerOptions[option.name] = value; } + compilerOptions[option.name] = value; } } - - return compilerOptions; } + + return compilerOptions; +} } diff --git a/src/testRunner/runner.ts b/src/testRunner/runner.ts index 4427390d7f398..34c48b4ff71aa 100644 --- a/src/testRunner/runner.ts +++ b/src/testRunner/runner.ts @@ -1,284 +1,284 @@ namespace Harness { - /* eslint-disable prefer-const */ - export let runners: RunnerBase[] = []; - export let iterations = 1; - /* eslint-enable prefer-const */ +/* eslint-disable prefer-const */ +export let runners: RunnerBase[] = []; +export let iterations = 1; +/* eslint-enable prefer-const */ - function runTests(runners: RunnerBase[]) { - for (let i = iterations; i > 0; i--) { - const seen = new Map(); - const dupes: [string, string][] = []; - for (const runner of runners) { - if (runner instanceof CompilerBaselineRunner || runner instanceof FourSlashRunner) { - for (const sf of runner.enumerateTestFiles()) { - const full = typeof sf === "string" ? sf : sf.file; - const base = vpath.basename(full).toLowerCase(); - // allow existing dupes in fourslash/shims and fourslash/server - if (seen.has(base) && !/fourslash\/(shim|server)/.test(full)) { - dupes.push([seen.get(base)!, full]); - } - else { - seen.set(base, full); - } +function runTests(runners: RunnerBase[]) { + for (let i = iterations; i > 0; i--) { + const seen = new Map(); + const dupes: [string, string][] = []; + for (const runner of runners) { + if (runner instanceof CompilerBaselineRunner || runner instanceof FourSlashRunner) { + for (const sf of runner.enumerateTestFiles()) { + const full = typeof sf === "string" ? sf : sf.file; + const base = vpath.basename(full).toLowerCase(); + // allow existing dupes in fourslash/shims and fourslash/server + if (seen.has(base) && !/fourslash\/(shim|server)/.test(full)) { + dupes.push([seen.get(base)!, full]); + } + else { + seen.set(base, full); } } - runner.initializeTests(); } - if (dupes.length) { - throw new Error(`${dupes.length} Tests with duplicate baseline names: + runner.initializeTests(); + } + if (dupes.length) { + throw new Error(`${dupes.length} Tests with duplicate baseline names: ${JSON.stringify(dupes, undefined, 2)}`); - } } } +} - function tryGetConfig(args: string[]) { - const prefix = "--config="; - const configPath = ts.forEach(args, arg => arg.lastIndexOf(prefix, 0) === 0 && arg.substr(prefix.length)); - // strip leading and trailing quotes from the path (necessary on Windows since shell does not do it automatically) - return configPath && configPath.replace(/(^[\"'])|([\"']$)/g, ""); - } +function tryGetConfig(args: string[]) { + const prefix = "--config="; + const configPath = ts.forEach(args, arg => arg.lastIndexOf(prefix, 0) === 0 && arg.substr(prefix.length)); + // strip leading and trailing quotes from the path (necessary on Windows since shell does not do it automatically) + return configPath && configPath.replace(/(^[\"'])|([\"']$)/g, ""); +} - export function createRunner(kind: TestRunnerKind): RunnerBase { - switch (kind) { - case "conformance": - return new CompilerBaselineRunner(CompilerTestType.Conformance); - case "compiler": - return new CompilerBaselineRunner(CompilerTestType.Regressions); - case "fourslash": - return new FourSlashRunner(FourSlash.FourSlashTestType.Native); - case "fourslash-shims": - return new FourSlashRunner(FourSlash.FourSlashTestType.Shims); - case "fourslash-shims-pp": - return new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess); - case "fourslash-server": - return new FourSlashRunner(FourSlash.FourSlashTestType.Server); - case "project": - return new project.ProjectRunner(); - case "rwc": - return new RWC.RWCRunner(); - case "test262": - return new Test262BaselineRunner(); - case "user": - return new UserCodeRunner(); - case "dt": - return new DefinitelyTypedRunner(); - case "docker": - return new DockerfileRunner(); - } - return ts.Debug.fail(`Unknown runner kind ${kind}`); +export function createRunner(kind: TestRunnerKind): RunnerBase { + switch (kind) { + case "conformance": + return new CompilerBaselineRunner(CompilerTestType.Conformance); + case "compiler": + return new CompilerBaselineRunner(CompilerTestType.Regressions); + case "fourslash": + return new FourSlashRunner(FourSlash.FourSlashTestType.Native); + case "fourslash-shims": + return new FourSlashRunner(FourSlash.FourSlashTestType.Shims); + case "fourslash-shims-pp": + return new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess); + case "fourslash-server": + return new FourSlashRunner(FourSlash.FourSlashTestType.Server); + case "project": + return new project.ProjectRunner(); + case "rwc": + return new RWC.RWCRunner(); + case "test262": + return new Test262BaselineRunner(); + case "user": + return new UserCodeRunner(); + case "dt": + return new DefinitelyTypedRunner(); + case "docker": + return new DockerfileRunner(); } + return ts.Debug.fail(`Unknown runner kind ${kind}`); +} - // users can define tests to run in mytest.config that will override cmd line args, otherwise use cmd line args (test.config), otherwise no options +// users can define tests to run in mytest.config that will override cmd line args, otherwise use cmd line args (test.config), otherwise no options - const mytestconfigFileName = "mytest.config"; - const testconfigFileName = "test.config"; +const mytestconfigFileName = "mytest.config"; +const testconfigFileName = "test.config"; - const customConfig = tryGetConfig(IO.args()); - const testConfigContent = - customConfig && IO.fileExists(customConfig) - ? IO.readFile(customConfig)! - : IO.fileExists(mytestconfigFileName) - ? IO.readFile(mytestconfigFileName)! - : IO.fileExists(testconfigFileName) ? IO.readFile(testconfigFileName)! : ""; +const customConfig = tryGetConfig(IO.args()); +const testConfigContent = + customConfig && IO.fileExists(customConfig) + ? IO.readFile(customConfig)! + : IO.fileExists(mytestconfigFileName) + ? IO.readFile(mytestconfigFileName)! + : IO.fileExists(testconfigFileName) ? IO.readFile(testconfigFileName)! : ""; - export let taskConfigsFolder: string; - export let workerCount: number; - export let runUnitTests: boolean | undefined; - export let stackTraceLimit: number | "full" | undefined; - export let noColors = false; - export let keepFailed = false; +export let taskConfigsFolder: string; +export let workerCount: number; +export let runUnitTests: boolean | undefined; +export let stackTraceLimit: number | "full" | undefined; +export let noColors = false; +export let keepFailed = false; - export interface TestConfig { - light?: boolean; - taskConfigsFolder?: string; - listenForWork?: boolean; - workerCount?: number; - stackTraceLimit?: number | "full"; - test?: string[]; - runners?: string[]; - runUnitTests?: boolean; - noColors?: boolean; - timeout?: number; - keepFailed?: boolean; - shardId?: number; - shards?: number; - } +export interface TestConfig { + light?: boolean; + taskConfigsFolder?: string; + listenForWork?: boolean; + workerCount?: number; + stackTraceLimit?: number | "full"; + test?: string[]; + runners?: string[]; + runUnitTests?: boolean; + noColors?: boolean; + timeout?: number; + keepFailed?: boolean; + shardId?: number; + shards?: number; +} - export interface TaskSet { - runner: TestRunnerKind; - files: string[]; - } +export interface TaskSet { + runner: TestRunnerKind; + files: string[]; +} - export let configOption: string; - export let globalTimeout: number; - function handleTestConfig() { - if (testConfigContent !== "") { - const testConfig = JSON.parse(testConfigContent) as TestConfig; - if (testConfig.light) { - setLightMode(true); - } - if (testConfig.timeout) { - globalTimeout = testConfig.timeout; - } - runUnitTests = testConfig.runUnitTests; - if (testConfig.workerCount) { - workerCount = +testConfig.workerCount; - } - if (testConfig.taskConfigsFolder) { - taskConfigsFolder = testConfig.taskConfigsFolder; - } - if (testConfig.noColors !== undefined) { - noColors = testConfig.noColors; - } - if (testConfig.keepFailed) { - keepFailed = true; - } - if (testConfig.shardId) { - setShardId(testConfig.shardId); - } - if (testConfig.shards) { - setShards(testConfig.shards); - } +export let configOption: string; +export let globalTimeout: number; +function handleTestConfig() { + if (testConfigContent !== "") { + const testConfig = JSON.parse(testConfigContent) as TestConfig; + if (testConfig.light) { + setLightMode(true); + } + if (testConfig.timeout) { + globalTimeout = testConfig.timeout; + } + runUnitTests = testConfig.runUnitTests; + if (testConfig.workerCount) { + workerCount = +testConfig.workerCount; + } + if (testConfig.taskConfigsFolder) { + taskConfigsFolder = testConfig.taskConfigsFolder; + } + if (testConfig.noColors !== undefined) { + noColors = testConfig.noColors; + } + if (testConfig.keepFailed) { + keepFailed = true; + } + if (testConfig.shardId) { + setShardId(testConfig.shardId); + } + if (testConfig.shards) { + setShards(testConfig.shards); + } - if (testConfig.stackTraceLimit === "full") { - (Error as any).stackTraceLimit = Infinity; - stackTraceLimit = testConfig.stackTraceLimit; - } - else if ((+testConfig.stackTraceLimit! | 0) > 0) { - (Error as any).stackTraceLimit = +testConfig.stackTraceLimit! | 0; - stackTraceLimit = +testConfig.stackTraceLimit! | 0; - } - if (testConfig.listenForWork) { - return true; - } + if (testConfig.stackTraceLimit === "full") { + (Error as any).stackTraceLimit = Infinity; + stackTraceLimit = testConfig.stackTraceLimit; + } + else if ((+testConfig.stackTraceLimit! | 0) > 0) { + (Error as any).stackTraceLimit = +testConfig.stackTraceLimit! | 0; + stackTraceLimit = +testConfig.stackTraceLimit! | 0; + } + if (testConfig.listenForWork) { + return true; + } - const runnerConfig = testConfig.runners || testConfig.test; - if (runnerConfig && runnerConfig.length > 0) { - if (testConfig.runners) { - runUnitTests = runnerConfig.indexOf("unittest") !== -1; + const runnerConfig = testConfig.runners || testConfig.test; + if (runnerConfig && runnerConfig.length > 0) { + if (testConfig.runners) { + runUnitTests = runnerConfig.indexOf("unittest") !== -1; + } + for (const option of runnerConfig) { + if (!option) { + continue; } - for (const option of runnerConfig) { - if (!option) { - continue; - } - if (!configOption) { - configOption = option; - } - else { - configOption += "+" + option; - } + if (!configOption) { + configOption = option; + } + else { + configOption += "+" + option; + } - switch (option) { - case "compiler": - runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); - runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions)); - break; - case "conformance": - runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); - break; - case "project": - runners.push(new project.ProjectRunner()); - break; - case "fourslash": - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native)); - break; - case "fourslash-shims": - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims)); - break; - case "fourslash-shims-pp": - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess)); - break; - case "fourslash-server": - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server)); - break; - case "fourslash-generated": - runners.push(new GeneratedFourslashRunner(FourSlash.FourSlashTestType.Native)); - break; - case "rwc": - runners.push(new RWC.RWCRunner()); - break; - case "test262": - runners.push(new Test262BaselineRunner()); - break; - case "user": - runners.push(new UserCodeRunner()); - break; - case "dt": - runners.push(new DefinitelyTypedRunner()); - break; - case "docker": - runners.push(new DockerfileRunner()); - break; - } + switch (option) { + case "compiler": + runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); + runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions)); + break; + case "conformance": + runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); + break; + case "project": + runners.push(new project.ProjectRunner()); + break; + case "fourslash": + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native)); + break; + case "fourslash-shims": + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims)); + break; + case "fourslash-shims-pp": + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess)); + break; + case "fourslash-server": + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server)); + break; + case "fourslash-generated": + runners.push(new GeneratedFourslashRunner(FourSlash.FourSlashTestType.Native)); + break; + case "rwc": + runners.push(new RWC.RWCRunner()); + break; + case "test262": + runners.push(new Test262BaselineRunner()); + break; + case "user": + runners.push(new UserCodeRunner()); + break; + case "dt": + runners.push(new DefinitelyTypedRunner()); + break; + case "docker": + runners.push(new DockerfileRunner()); + break; } } } + } - if (runners.length === 0) { - // compiler - runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); - runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions)); + if (runners.length === 0) { + // compiler + runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); + runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions)); - runners.push(new project.ProjectRunner()); + runners.push(new project.ProjectRunner()); - // language services - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native)); - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims)); - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess)); - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server)); - // runners.push(new GeneratedFourslashRunner()); + // language services + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native)); + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims)); + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess)); + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server)); + // runners.push(new GeneratedFourslashRunner()); - // CRON-only tests - if (process.env.TRAVIS_EVENT_TYPE === "cron") { - runners.push(new UserCodeRunner()); - runners.push(new DockerfileRunner()); - } - } - if (runUnitTests === undefined) { - runUnitTests = runners.length !== 1; // Don't run unit tests when running only one runner if unit tests were not explicitly asked for + // CRON-only tests + if (process.env.TRAVIS_EVENT_TYPE === "cron") { + runners.push(new UserCodeRunner()); + runners.push(new DockerfileRunner()); } - return false; } + if (runUnitTests === undefined) { + runUnitTests = runners.length !== 1; // Don't run unit tests when running only one runner if unit tests were not explicitly asked for + } + return false; +} - function beginTests() { - ts.Debug.loggingHost = { - log(_level, s) { - console.log(s || ""); - } - }; - - if (ts.Debug.isDebugging) { - ts.Debug.enableDebugInfo(); +function beginTests() { + ts.Debug.loggingHost = { + log(_level, s) { + console.log(s || ""); } + }; + + if (ts.Debug.isDebugging) { + ts.Debug.enableDebugInfo(); + } - // run tests in en-US by default. - let savedUILocale: string | undefined; - beforeEach(() => { - savedUILocale = ts.getUILocale(); - ts.setUILocale("en-US"); - }); - afterEach(() => ts.setUILocale(savedUILocale)); + // run tests in en-US by default. + let savedUILocale: string | undefined; + beforeEach(() => { + savedUILocale = ts.getUILocale(); + ts.setUILocale("en-US"); + }); + afterEach(() => ts.setUILocale(savedUILocale)); - runTests(runners); + runTests(runners); - if (!runUnitTests) { - // patch `describe` to skip unit tests - (global as any).describe = ts.noop; - } + if (!runUnitTests) { + // patch `describe` to skip unit tests + (global as any).describe = ts.noop; } +} - export let isWorker: boolean; - function startTestEnvironment() { - isWorker = handleTestConfig(); - if (isWorker) { - return Parallel.Worker.start(); - } - else if (taskConfigsFolder && workerCount && workerCount > 1) { - return Parallel.Host.start(); - } - beginTests(); +export let isWorker: boolean; +function startTestEnvironment() { + isWorker = handleTestConfig(); + if (isWorker) { + return Parallel.Worker.start(); + } + else if (taskConfigsFolder && workerCount && workerCount > 1) { + return Parallel.Host.start(); } + beginTests(); +} - startTestEnvironment(); +startTestEnvironment(); } diff --git a/src/testRunner/rwcRunner.ts b/src/testRunner/rwcRunner.ts index 29e86382aff34..ab6c0c69cd94b 100644 --- a/src/testRunner/rwcRunner.ts +++ b/src/testRunner/rwcRunner.ts @@ -1,235 +1,235 @@ // In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`. namespace RWC { - function runWithIOLog(ioLog: Playback.IoLog, fn: (oldIO: Harness.IO) => void) { - const oldIO = Harness.IO; +function runWithIOLog(ioLog: Playback.IoLog, fn: (oldIO: Harness.IO) => void) { + const oldIO = Harness.IO; - const wrappedIO = Playback.wrapIO(oldIO); - wrappedIO.startReplayFromData(ioLog); - Harness.setHarnessIO(wrappedIO); + const wrappedIO = Playback.wrapIO(oldIO); + wrappedIO.startReplayFromData(ioLog); + Harness.setHarnessIO(wrappedIO); - try { - fn(oldIO); - } - finally { - wrappedIO.endReplay(); - Harness.setHarnessIO(oldIO); - } + try { + fn(oldIO); } + finally { + wrappedIO.endReplay(); + Harness.setHarnessIO(oldIO); + } +} - export function runRWCTest(jsonPath: string) { - describe("Testing a rwc project: " + jsonPath, () => { - let inputFiles: Harness.Compiler.TestFile[] = []; - let otherFiles: Harness.Compiler.TestFile[] = []; - let tsconfigFiles: Harness.Compiler.TestFile[] = []; - let compilerResult: compiler.CompilationResult; - let compilerOptions: ts.CompilerOptions; - const baselineOpts: Harness.Baseline.BaselineOptions = { - Subfolder: "rwc", - Baselinefolder: "internal/baselines" - }; - const baseName = ts.getBaseFileName(jsonPath); - let currentDirectory: string; - let useCustomLibraryFile: boolean; - let caseSensitive: boolean; - after(() => { - // Mocha holds onto the closure environment of the describe callback even after the test is done. - // Therefore we have to clean out large objects after the test is done. - inputFiles = []; - otherFiles = []; - tsconfigFiles = []; - compilerResult = undefined!; - compilerOptions = undefined!; - currentDirectory = undefined!; - // useCustomLibraryFile is a flag specified in the json object to indicate whether to use built/local/lib.d.ts - // or to use lib.d.ts inside the json object. If the flag is true, use the lib.d.ts inside json file - // otherwise use the lib.d.ts from built/local - useCustomLibraryFile = false; - caseSensitive = false; - }); +export function runRWCTest(jsonPath: string) { + describe("Testing a rwc project: " + jsonPath, () => { + let inputFiles: Harness.Compiler.TestFile[] = []; + let otherFiles: Harness.Compiler.TestFile[] = []; + let tsconfigFiles: Harness.Compiler.TestFile[] = []; + let compilerResult: compiler.CompilationResult; + let compilerOptions: ts.CompilerOptions; + const baselineOpts: Harness.Baseline.BaselineOptions = { + Subfolder: "rwc", + Baselinefolder: "internal/baselines" + }; + const baseName = ts.getBaseFileName(jsonPath); + let currentDirectory: string; + let useCustomLibraryFile: boolean; + let caseSensitive: boolean; + after(() => { + // Mocha holds onto the closure environment of the describe callback even after the test is done. + // Therefore we have to clean out large objects after the test is done. + inputFiles = []; + otherFiles = []; + tsconfigFiles = []; + compilerResult = undefined!; + compilerOptions = undefined!; + currentDirectory = undefined!; + // useCustomLibraryFile is a flag specified in the json object to indicate whether to use built/local/lib.d.ts + // or to use lib.d.ts inside the json object. If the flag is true, use the lib.d.ts inside json file + // otherwise use the lib.d.ts from built/local + useCustomLibraryFile = false; + caseSensitive = false; + }); - it("can compile", function (this: Mocha.Context) { - this.timeout(800_000); // Allow long timeouts for RWC compilations - let opts!: ts.ParsedCommandLine; - - const ioLog: Playback.IoLog = Playback.newStyleLogIntoOldStyleLog(JSON.parse(Harness.IO.readFile(`internal/cases/rwc/${jsonPath}/test.json`)!), Harness.IO, `internal/cases/rwc/${baseName}`); - currentDirectory = ioLog.currentDirectory; - useCustomLibraryFile = !!ioLog.useCustomLibraryFile; - runWithIOLog(ioLog, () => { - opts = ts.parseCommandLine(ioLog.arguments, fileName => Harness.IO.readFile(fileName)); - assert.equal(opts.errors.length, 0); - - // To provide test coverage of output javascript file, - // we will set noEmitOnError flag to be false. - opts.options.noEmitOnError = false; - }); - let fileNames = opts.fileNames; - - runWithIOLog(ioLog, () => { - const tsconfigFile = ts.forEach(ioLog.filesRead, f => vpath.isTsConfigFile(f.path) ? f : undefined); - if (tsconfigFile) { - const tsconfigFileContents = getHarnessCompilerInputUnit(tsconfigFile.path); - tsconfigFiles.push({ unitName: tsconfigFile.path, content: tsconfigFileContents.content }); - const parsedTsconfigFileContents = ts.parseJsonText(tsconfigFile.path, tsconfigFileContents.content); - const configParseHost: ts.ParseConfigHost = { - useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), - fileExists: Harness.IO.fileExists, - readDirectory: Harness.IO.readDirectory, - readFile: Harness.IO.readFile - }; - const configParseResult = ts.parseJsonSourceFileConfigFileContent(parsedTsconfigFileContents, configParseHost, ts.getDirectoryPath(tsconfigFile.path)); - fileNames = configParseResult.fileNames; - opts.options = ts.extend(opts.options, configParseResult.options); - ts.setConfigFileInOptions(opts.options, configParseResult.options.configFile); - } + it("can compile", function (this: Mocha.Context) { + this.timeout(800_000); // Allow long timeouts for RWC compilations + let opts!: ts.ParsedCommandLine; - // Deduplicate files so they are only printed once in baselines (they are deduplicated within the compiler already) - const uniqueNames = new ts.Map(); - for (const fileName of fileNames) { - // Must maintain order, build result list while checking map - const normalized = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!); - if (!uniqueNames.has(normalized)) { - uniqueNames.set(normalized, true); - // Load the file - inputFiles.push(getHarnessCompilerInputUnit(fileName)); - } - } + const ioLog: Playback.IoLog = Playback.newStyleLogIntoOldStyleLog(JSON.parse(Harness.IO.readFile(`internal/cases/rwc/${jsonPath}/test.json`)!), Harness.IO, `internal/cases/rwc/${baseName}`); + currentDirectory = ioLog.currentDirectory; + useCustomLibraryFile = !!ioLog.useCustomLibraryFile; + runWithIOLog(ioLog, () => { + opts = ts.parseCommandLine(ioLog.arguments, fileName => Harness.IO.readFile(fileName)); + assert.equal(opts.errors.length, 0); - // Add files to compilation - for (const fileRead of ioLog.filesRead) { - const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileRead.path)!); - if (!uniqueNames.has(unitName) && !Harness.isDefaultLibraryFile(fileRead.path)) { - uniqueNames.set(unitName, true); - otherFiles.push(getHarnessCompilerInputUnit(fileRead.path)); - } - else if (!opts.options.noLib && Harness.isDefaultLibraryFile(fileRead.path) && !uniqueNames.has(unitName) && useCustomLibraryFile) { - // If useCustomLibraryFile is true, we will use lib.d.ts from json object - // otherwise use the lib.d.ts from built/local - // Majority of RWC code will be using built/local/lib.d.ts instead of - // lib.d.ts inside json file. However, some RWC cases will still use - // their own version of lib.d.ts because they have customized lib.d.ts - uniqueNames.set(unitName, true); - inputFiles.push(getHarnessCompilerInputUnit(fileRead.path)); - } - } - }); + // To provide test coverage of output javascript file, + // we will set noEmitOnError flag to be false. + opts.options.noEmitOnError = false; + }); + let fileNames = opts.fileNames; + + runWithIOLog(ioLog, () => { + const tsconfigFile = ts.forEach(ioLog.filesRead, f => vpath.isTsConfigFile(f.path) ? f : undefined); + if (tsconfigFile) { + const tsconfigFileContents = getHarnessCompilerInputUnit(tsconfigFile.path); + tsconfigFiles.push({ unitName: tsconfigFile.path, content: tsconfigFileContents.content }); + const parsedTsconfigFileContents = ts.parseJsonText(tsconfigFile.path, tsconfigFileContents.content); + const configParseHost: ts.ParseConfigHost = { + useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), + fileExists: Harness.IO.fileExists, + readDirectory: Harness.IO.readDirectory, + readFile: Harness.IO.readFile + }; + const configParseResult = ts.parseJsonSourceFileConfigFileContent(parsedTsconfigFileContents, configParseHost, ts.getDirectoryPath(tsconfigFile.path)); + fileNames = configParseResult.fileNames; + opts.options = ts.extend(opts.options, configParseResult.options); + ts.setConfigFileInOptions(opts.options, configParseResult.options.configFile); + } - if (useCustomLibraryFile) { - // do not use lib since we already read it in above - opts.options.lib = undefined; - opts.options.noLib = true; + // Deduplicate files so they are only printed once in baselines (they are deduplicated within the compiler already) + const uniqueNames = new ts.Map(); + for (const fileName of fileNames) { + // Must maintain order, build result list while checking map + const normalized = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!); + if (!uniqueNames.has(normalized)) { + uniqueNames.set(normalized, true); + // Load the file + inputFiles.push(getHarnessCompilerInputUnit(fileName)); + } } - caseSensitive = ioLog.useCaseSensitiveFileNames || false; - // Emit the results - compilerResult = Harness.Compiler.compileFiles( - inputFiles, - otherFiles, - { useCaseSensitiveFileNames: "" + caseSensitive }, - opts.options, - // Since each RWC json file specifies its current directory in its json file, we need - // to pass this information in explicitly instead of acquiring it from the process. - currentDirectory); - compilerOptions = compilerResult.options; - - function getHarnessCompilerInputUnit(fileName: string): Harness.Compiler.TestFile { - const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!); - let content: string; - try { - content = Harness.IO.readFile(unitName)!; + // Add files to compilation + for (const fileRead of ioLog.filesRead) { + const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileRead.path)!); + if (!uniqueNames.has(unitName) && !Harness.isDefaultLibraryFile(fileRead.path)) { + uniqueNames.set(unitName, true); + otherFiles.push(getHarnessCompilerInputUnit(fileRead.path)); } - catch (e) { - content = Harness.IO.readFile(fileName)!; + else if (!opts.options.noLib && Harness.isDefaultLibraryFile(fileRead.path) && !uniqueNames.has(unitName) && useCustomLibraryFile) { + // If useCustomLibraryFile is true, we will use lib.d.ts from json object + // otherwise use the lib.d.ts from built/local + // Majority of RWC code will be using built/local/lib.d.ts instead of + // lib.d.ts inside json file. However, some RWC cases will still use + // their own version of lib.d.ts because they have customized lib.d.ts + uniqueNames.set(unitName, true); + inputFiles.push(getHarnessCompilerInputUnit(fileRead.path)); } - return { unitName, content }; } }); + if (useCustomLibraryFile) { + // do not use lib since we already read it in above + opts.options.lib = undefined; + opts.options.noLib = true; + } - it("has the expected emitted code", function (this: Mocha.Context) { - this.timeout(100_000); // Allow longer timeouts for RWC js verification - Harness.Baseline.runMultifileBaseline(baseName, "", () => { - return Harness.Compiler.iterateOutputs(compilerResult.js.values()); - }, baselineOpts, [".js", ".jsx"]); - }); + caseSensitive = ioLog.useCaseSensitiveFileNames || false; + // Emit the results + compilerResult = Harness.Compiler.compileFiles( + inputFiles, + otherFiles, + { useCaseSensitiveFileNames: "" + caseSensitive }, + opts.options, + // Since each RWC json file specifies its current directory in its json file, we need + // to pass this information in explicitly instead of acquiring it from the process. + currentDirectory); + compilerOptions = compilerResult.options; + + function getHarnessCompilerInputUnit(fileName: string): Harness.Compiler.TestFile { + const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!); + let content: string; + try { + content = Harness.IO.readFile(unitName)!; + } + catch (e) { + content = Harness.IO.readFile(fileName)!; + } + return { unitName, content }; + } + }); - it("has the expected declaration file content", () => { - Harness.Baseline.runMultifileBaseline(baseName, "", () => { - if (!compilerResult.dts.size) { - return null; // eslint-disable-line no-null/no-null - } - return Harness.Compiler.iterateOutputs(compilerResult.dts.values()); - }, baselineOpts, [".d.ts"]); - }); + it("has the expected emitted code", function (this: Mocha.Context) { + this.timeout(100_000); // Allow longer timeouts for RWC js verification + Harness.Baseline.runMultifileBaseline(baseName, "", () => { + return Harness.Compiler.iterateOutputs(compilerResult.js.values()); + }, baselineOpts, [".js", ".jsx"]); + }); - it("has the expected source maps", () => { - Harness.Baseline.runMultifileBaseline(baseName, "", () => { - if (!compilerResult.maps.size) { - return null; // eslint-disable-line no-null/no-null - } + it("has the expected declaration file content", () => { + Harness.Baseline.runMultifileBaseline(baseName, "", () => { + if (!compilerResult.dts.size) { + return null; // eslint-disable-line no-null/no-null + } - return Harness.Compiler.iterateOutputs(compilerResult.maps.values()); - }, baselineOpts, [".map"]); - }); + return Harness.Compiler.iterateOutputs(compilerResult.dts.values()); + }, baselineOpts, [".d.ts"]); + }); + + it("has the expected source maps", () => { + Harness.Baseline.runMultifileBaseline(baseName, "", () => { + if (!compilerResult.maps.size) { + return null; // eslint-disable-line no-null/no-null + } - it("has the expected errors", () => { - Harness.Baseline.runMultifileBaseline(baseName, ".errors.txt", () => { + return Harness.Compiler.iterateOutputs(compilerResult.maps.values()); + }, baselineOpts, [".map"]); + }); + + it("has the expected errors", () => { + Harness.Baseline.runMultifileBaseline(baseName, ".errors.txt", () => { + if (compilerResult.diagnostics.length === 0) { + return null; // eslint-disable-line no-null/no-null + } + // Do not include the library in the baselines to avoid noise + const baselineFiles = tsconfigFiles.concat(inputFiles, otherFiles).filter(f => !Harness.isDefaultLibraryFile(f.unitName)); + const errors = compilerResult.diagnostics.filter(e => !e.file || !Harness.isDefaultLibraryFile(e.file.fileName)); + return Harness.Compiler.iterateErrorBaseline(baselineFiles, errors, { caseSensitive, currentDirectory }); + }, baselineOpts); + }); + + // Ideally, a generated declaration file will have no errors. But we allow generated + // declaration file errors as part of the baseline. + it("has the expected errors in generated declaration files", () => { + if (compilerOptions.declaration && !compilerResult.diagnostics.length) { + Harness.Baseline.runMultifileBaseline(baseName, ".dts.errors.txt", () => { if (compilerResult.diagnostics.length === 0) { return null; // eslint-disable-line no-null/no-null } - // Do not include the library in the baselines to avoid noise - const baselineFiles = tsconfigFiles.concat(inputFiles, otherFiles).filter(f => !Harness.isDefaultLibraryFile(f.unitName)); - const errors = compilerResult.diagnostics.filter(e => !e.file || !Harness.isDefaultLibraryFile(e.file.fileName)); - return Harness.Compiler.iterateErrorBaseline(baselineFiles, errors, { caseSensitive, currentDirectory }); - }, baselineOpts); - }); - // Ideally, a generated declaration file will have no errors. But we allow generated - // declaration file errors as part of the baseline. - it("has the expected errors in generated declaration files", () => { - if (compilerOptions.declaration && !compilerResult.diagnostics.length) { - Harness.Baseline.runMultifileBaseline(baseName, ".dts.errors.txt", () => { - if (compilerResult.diagnostics.length === 0) { - return null; // eslint-disable-line no-null/no-null - } - - const declContext = Harness.Compiler.prepareDeclarationCompilationContext( - inputFiles, otherFiles, compilerResult, /*harnessSettings*/ undefined!, compilerOptions, currentDirectory // TODO: GH#18217 - ); - // Reset compilerResult before calling into `compileDeclarationFiles` so the memory from the original compilation can be freed - const links = compilerResult.symlinks; - compilerResult = undefined!; - const declFileCompilationResult = Harness.Compiler.compileDeclarationFiles(declContext, links)!; - - return Harness.Compiler.iterateErrorBaseline(tsconfigFiles.concat(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.diagnostics, { caseSensitive, currentDirectory }); - }, baselineOpts); - } - }); + const declContext = Harness.Compiler.prepareDeclarationCompilationContext( + inputFiles, otherFiles, compilerResult, /*harnessSettings*/ undefined!, compilerOptions, currentDirectory // TODO: GH#18217 + ); + // Reset compilerResult before calling into `compileDeclarationFiles` so the memory from the original compilation can be freed + const links = compilerResult.symlinks; + compilerResult = undefined!; + const declFileCompilationResult = Harness.Compiler.compileDeclarationFiles(declContext, links)!; + + return Harness.Compiler.iterateErrorBaseline(tsconfigFiles.concat(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.diagnostics, { caseSensitive, currentDirectory }); + }, baselineOpts); + } }); - } + }); +} - export class RWCRunner extends Harness.RunnerBase { - public enumerateTestFiles() { - // see also: `enumerateTestFiles` in tests/webTestServer.ts - return Harness.IO.getDirectories("internal/cases/rwc/"); - } +export class RWCRunner extends Harness.RunnerBase { + public enumerateTestFiles() { + // see also: `enumerateTestFiles` in tests/webTestServer.ts + return Harness.IO.getDirectories("internal/cases/rwc/"); + } - public kind(): Harness.TestRunnerKind { - return "rwc"; - } + public kind(): Harness.TestRunnerKind { + return "rwc"; + } - /** Setup the runner's tests so that they are ready to be executed by the harness - * The first test should be a describe/it block that sets up the harness's compiler instance appropriately - */ - public initializeTests(): void { - // Read in and evaluate the test list - for (const test of this.tests && this.tests.length ? this.tests : this.getTestFiles()) { - this.runTest(typeof test === "string" ? test : test.file); - } + /** Setup the runner's tests so that they are ready to be executed by the harness + * The first test should be a describe/it block that sets up the harness's compiler instance appropriately + */ + public initializeTests(): void { + // Read in and evaluate the test list + for (const test of this.tests && this.tests.length ? this.tests : this.getTestFiles()) { + this.runTest(typeof test === "string" ? test : test.file); } + } - private runTest(jsonFileName: string) { - runRWCTest(jsonFileName); - } + private runTest(jsonFileName: string) { + runRWCTest(jsonFileName); } } +} diff --git a/src/testRunner/test262Runner.ts b/src/testRunner/test262Runner.ts index 728d3fb322790..d8de0eb3ec737 100644 --- a/src/testRunner/test262Runner.ts +++ b/src/testRunner/test262Runner.ts @@ -1,110 +1,110 @@ namespace Harness { - // In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`. - export class Test262BaselineRunner extends RunnerBase { - private static readonly basePath = "internal/cases/test262"; - private static readonly helpersFilePath = "tests/cases/test262-harness/helpers.d.ts"; - private static readonly helperFile: Compiler.TestFile = { - unitName: Test262BaselineRunner.helpersFilePath, - content: IO.readFile(Test262BaselineRunner.helpersFilePath)!, - }; - private static readonly testFileExtensionRegex = /\.js$/; - private static readonly options: ts.CompilerOptions = { - allowNonTsExtensions: true, - target: ts.ScriptTarget.Latest, - module: ts.ModuleKind.CommonJS - }; - private static readonly baselineOptions: Baseline.BaselineOptions = { - Subfolder: "test262", - Baselinefolder: "internal/baselines" - }; +// In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`. +export class Test262BaselineRunner extends RunnerBase { + private static readonly basePath = "internal/cases/test262"; + private static readonly helpersFilePath = "tests/cases/test262-harness/helpers.d.ts"; + private static readonly helperFile: Compiler.TestFile = { + unitName: Test262BaselineRunner.helpersFilePath, + content: IO.readFile(Test262BaselineRunner.helpersFilePath)!, + }; + private static readonly testFileExtensionRegex = /\.js$/; + private static readonly options: ts.CompilerOptions = { + allowNonTsExtensions: true, + target: ts.ScriptTarget.Latest, + module: ts.ModuleKind.CommonJS + }; + private static readonly baselineOptions: Baseline.BaselineOptions = { + Subfolder: "test262", + Baselinefolder: "internal/baselines" + }; - private static getTestFilePath(filename: string): string { - return Test262BaselineRunner.basePath + "/" + filename; - } + private static getTestFilePath(filename: string): string { + return Test262BaselineRunner.basePath + "/" + filename; + } - private runTest(filePath: string) { - describe("test262 test for " + filePath, () => { - // Mocha holds onto the closure environment of the describe callback even after the test is done. - // Everything declared here should be cleared out in the "after" callback. - let testState: { - filename: string; - compilerResult: compiler.CompilationResult; - inputFiles: Compiler.TestFile[]; - }; + private runTest(filePath: string) { + describe("test262 test for " + filePath, () => { + // Mocha holds onto the closure environment of the describe callback even after the test is done. + // Everything declared here should be cleared out in the "after" callback. + let testState: { + filename: string; + compilerResult: compiler.CompilationResult; + inputFiles: Compiler.TestFile[]; + }; - before(() => { - const content = IO.readFile(filePath)!; - const testFilename = ts.removeFileExtension(filePath).replace(/\//g, "_") + ".test"; - const testCaseContent = TestCaseParser.makeUnitsFromTest(content, testFilename); + before(() => { + const content = IO.readFile(filePath)!; + const testFilename = ts.removeFileExtension(filePath).replace(/\//g, "_") + ".test"; + const testCaseContent = TestCaseParser.makeUnitsFromTest(content, testFilename); - const inputFiles: Compiler.TestFile[] = testCaseContent.testUnitData.map(unit => { - const unitName = Test262BaselineRunner.getTestFilePath(unit.name); - return { unitName, content: unit.content }; - }); + const inputFiles: Compiler.TestFile[] = testCaseContent.testUnitData.map(unit => { + const unitName = Test262BaselineRunner.getTestFilePath(unit.name); + return { unitName, content: unit.content }; + }); - // Emit the results - testState = { - filename: testFilename, - inputFiles, - compilerResult: undefined!, // TODO: GH#18217 - }; + // Emit the results + testState = { + filename: testFilename, + inputFiles, + compilerResult: undefined!, // TODO: GH#18217 + }; - testState.compilerResult = Compiler.compileFiles( - [Test262BaselineRunner.helperFile].concat(inputFiles), - /*otherFiles*/ [], - /* harnessOptions */ undefined, - Test262BaselineRunner.options, - /* currentDirectory */ undefined); - }); + testState.compilerResult = Compiler.compileFiles( + [Test262BaselineRunner.helperFile].concat(inputFiles), + /*otherFiles*/ [], + /* harnessOptions */ undefined, + Test262BaselineRunner.options, + /* currentDirectory */ undefined); + }); - after(() => { - testState = undefined!; - }); + after(() => { + testState = undefined!; + }); - it("has the expected emitted code", () => { - const files = Array.from(testState.compilerResult.js.values()).filter(f => f.file !== Test262BaselineRunner.helpersFilePath); - Baseline.runBaseline(testState.filename + ".output.js", Compiler.collateOutputs(files), Test262BaselineRunner.baselineOptions); - }); + it("has the expected emitted code", () => { + const files = Array.from(testState.compilerResult.js.values()).filter(f => f.file !== Test262BaselineRunner.helpersFilePath); + Baseline.runBaseline(testState.filename + ".output.js", Compiler.collateOutputs(files), Test262BaselineRunner.baselineOptions); + }); - it("has the expected errors", () => { - const errors = testState.compilerResult.diagnostics; - // eslint-disable-next-line no-null/no-null - const baseline = errors.length === 0 ? null : Compiler.getErrorBaseline(testState.inputFiles, errors); - Baseline.runBaseline(testState.filename + ".errors.txt", baseline, Test262BaselineRunner.baselineOptions); - }); + it("has the expected errors", () => { + const errors = testState.compilerResult.diagnostics; + // eslint-disable-next-line no-null/no-null + const baseline = errors.length === 0 ? null : Compiler.getErrorBaseline(testState.inputFiles, errors); + Baseline.runBaseline(testState.filename + ".errors.txt", baseline, Test262BaselineRunner.baselineOptions); + }); - it("satisfies invariants", () => { - const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename)); - Utils.assertInvariants(sourceFile, /*parent:*/ undefined); - }); + it("satisfies invariants", () => { + const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename)); + Utils.assertInvariants(sourceFile, /*parent:*/ undefined); + }); - it("has the expected AST", () => { - const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename))!; - Baseline.runBaseline(testState.filename + ".AST.txt", Utils.sourceFileToJSON(sourceFile), Test262BaselineRunner.baselineOptions); - }); + it("has the expected AST", () => { + const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename))!; + Baseline.runBaseline(testState.filename + ".AST.txt", Utils.sourceFileToJSON(sourceFile), Test262BaselineRunner.baselineOptions); }); - } + }); + } - public kind(): TestRunnerKind { - return "test262"; - } + public kind(): TestRunnerKind { + return "test262"; + } - public enumerateTestFiles() { - // see also: `enumerateTestFiles` in tests/webTestServer.ts - return ts.map(this.enumerateFiles(Test262BaselineRunner.basePath, Test262BaselineRunner.testFileExtensionRegex, { recursive: true }), ts.normalizePath); - } + public enumerateTestFiles() { + // see also: `enumerateTestFiles` in tests/webTestServer.ts + return ts.map(this.enumerateFiles(Test262BaselineRunner.basePath, Test262BaselineRunner.testFileExtensionRegex, { recursive: true }), ts.normalizePath); + } - public initializeTests() { - // this will set up a series of describe/it blocks to run between the setup and cleanup phases - if (this.tests.length === 0) { - const testFiles = this.getTestFiles(); - testFiles.forEach(fn => { - this.runTest(fn); - }); - } - else { - this.tests.forEach(test => this.runTest(typeof test === "string" ? test : test.file)); - } + public initializeTests() { + // this will set up a series of describe/it blocks to run between the setup and cleanup phases + if (this.tests.length === 0) { + const testFiles = this.getTestFiles(); + testFiles.forEach(fn => { + this.runTest(fn); + }); + } + else { + this.tests.forEach(test => this.runTest(typeof test === "string" ? test : test.file)); } } -} \ No newline at end of file +} +} diff --git a/src/testRunner/unittests/asserts.ts b/src/testRunner/unittests/asserts.ts index a0cde51a0f841..ca9f77dadd1e7 100644 --- a/src/testRunner/unittests/asserts.ts +++ b/src/testRunner/unittests/asserts.ts @@ -1,12 +1,12 @@ namespace ts { - describe("unittests:: assert", () => { - it("deepEqual", () => { - assert.throws(() => assert.deepEqual(factory.createNodeArray([factory.createIdentifier("A")]), factory.createNodeArray([factory.createIdentifier("B")]))); - assert.throws(() => assert.deepEqual(factory.createNodeArray([], /*hasTrailingComma*/ true), factory.createNodeArray([], /*hasTrailingComma*/ false))); - assert.deepEqual(factory.createNodeArray([factory.createIdentifier("A")], /*hasTrailingComma*/ true), factory.createNodeArray([factory.createIdentifier("A")], /*hasTrailingComma*/ true)); - }); - it("assertNever on string has correct error", () => { - assert.throws(() => Debug.assertNever("hi" as never), "Debug Failure. Illegal value: \"hi\""); - }); +describe("unittests:: assert", () => { + it("deepEqual", () => { + assert.throws(() => assert.deepEqual(factory.createNodeArray([factory.createIdentifier("A")]), factory.createNodeArray([factory.createIdentifier("B")]))); + assert.throws(() => assert.deepEqual(factory.createNodeArray([], /*hasTrailingComma*/ true), factory.createNodeArray([], /*hasTrailingComma*/ false))); + assert.deepEqual(factory.createNodeArray([factory.createIdentifier("A")], /*hasTrailingComma*/ true), factory.createNodeArray([factory.createIdentifier("A")], /*hasTrailingComma*/ true)); }); + it("assertNever on string has correct error", () => { + assert.throws(() => Debug.assertNever("hi" as never), "Debug Failure. Illegal value: \"hi\""); + }); +}); } diff --git a/src/testRunner/unittests/base64.ts b/src/testRunner/unittests/base64.ts index 1dc51a8fbf5ae..46d7ecaf352da 100644 --- a/src/testRunner/unittests/base64.ts +++ b/src/testRunner/unittests/base64.ts @@ -1,22 +1,22 @@ namespace ts { - describe("unittests:: base64", () => { - describe("base64decode", () => { - it("can decode input strings correctly without needing a host implementation", () => { - const tests = [ - // "a", - // "this is a test", - // " !\"#$ %&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", - "日本語", - "🐱", - "\x00\x01", - "\t\n\r\\\"\'\u0062", - "====", - "", - ]; - for (const test of tests) { - assert.equal(base64decode({}, convertToBase64(test)), test); - } - }); +describe("unittests:: base64", () => { + describe("base64decode", () => { + it("can decode input strings correctly without needing a host implementation", () => { + const tests = [ + // "a", + // "this is a test", + // " !\"#$ %&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", + "日本語", + "🐱", + "\x00\x01", + "\t\n\r\\\"\'\u0062", + "====", + "", + ]; + for (const test of tests) { + assert.equal(base64decode({}, convertToBase64(test)), test); + } }); }); +}); } diff --git a/src/testRunner/unittests/builder.ts b/src/testRunner/unittests/builder.ts index bf0415f13598c..0c761baf5e7d4 100644 --- a/src/testRunner/unittests/builder.ts +++ b/src/testRunner/unittests/builder.ts @@ -1,130 +1,130 @@ namespace ts { - describe("unittests:: builder", () => { - it("emits dependent files", () => { - const files: NamedSourceText[] = [ - { name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") }, - { name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") }, - { name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") }, - ]; - - let program = newProgram(files, ["/a.ts"], {}); - const assertChanges = makeAssertChanges(() => program); - - assertChanges(["/c.js", "/b.js", "/a.js"]); - - program = updateProgramFile(program, "/a.ts", "//comment"); - assertChanges(["/a.js"]); - - program = updateProgramFile(program, "/b.ts", "export const b = c + 1;"); - assertChanges(["/b.js", "/a.js"]); - - program = updateProgramFile(program, "/c.ts", "export const c = 1;"); - assertChanges(["/c.js", "/b.js"]); - }); - - it("if emitting all files, emits the changed file first", () => { - const files: NamedSourceText[] = [ - { name: "/a.ts", text: SourceText.New("", "", "namespace A { export const x = 0; }") }, - { name: "/b.ts", text: SourceText.New("", "", "namespace B { export const x = 0; }") }, - ]; - - let program = newProgram(files, ["/a.ts", "/b.ts"], {}); - const assertChanges = makeAssertChanges(() => program); - - assertChanges(["/a.js", "/b.js"]); - - program = updateProgramFile(program, "/a.ts", "namespace A { export const x = 1; }"); - assertChanges(["/a.js", "/b.js"]); - - program = updateProgramFile(program, "/b.ts", "namespace B { export const x = 1; }"); - assertChanges(["/b.js", "/a.js"]); - }); - - it("keeps the file in affected files if cancellation token throws during the operation", () => { - const files: NamedSourceText[] = [ - { name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") }, - { name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") }, - { name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") }, - { name: "/d.ts", text: SourceText.New("", "", "export const dd = 0;") }, - { name: "/e.ts", text: SourceText.New("", "", "export const ee = 0;") }, - ]; - - let program = newProgram(files, ["/d.ts", "/e.ts", "/a.ts"], {}); - const assertChanges = makeAssertChangesWithCancellationToken(() => program); - // No cancellation - assertChanges(["/d.js", "/e.js", "/c.js", "/b.js", "/a.js"]); - - // cancel when emitting a.ts - program = updateProgramFile(program, "/a.ts", "export function foo() { }"); - assertChanges(["/a.js"], 0); - // Change d.ts and verify previously pending a.ts is emitted as well - program = updateProgramFile(program, "/d.ts", "export function bar() { }"); - assertChanges(["/a.js", "/d.js"]); - - // Cancel when emitting b.js - program = updateProgramFile(program, "/b.ts", "export class b { foo() { c + 1; } }"); - program = updateProgramFile(program, "/d.ts", "export function bar2() { }"); - assertChanges(["/d.js", "/b.js", "/a.js"], 1); - // Change e.ts and verify previously b.js as well as a.js get emitted again since previous change was consumed completely but not d.ts - program = updateProgramFile(program, "/e.ts", "export function bar3() { }"); - assertChanges(["/b.js", "/a.js", "/e.js"]); - }); +describe("unittests:: builder", () => { + it("emits dependent files", () => { + const files: NamedSourceText[] = [ + { name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") }, + { name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") }, + { name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") }, + ]; + + let program = newProgram(files, ["/a.ts"], {}); + const assertChanges = makeAssertChanges(() => program); + + assertChanges(["/c.js", "/b.js", "/a.js"]); + + program = updateProgramFile(program, "/a.ts", "//comment"); + assertChanges(["/a.js"]); + + program = updateProgramFile(program, "/b.ts", "export const b = c + 1;"); + assertChanges(["/b.js", "/a.js"]); + + program = updateProgramFile(program, "/c.ts", "export const c = 1;"); + assertChanges(["/c.js", "/b.js"]); + }); + + it("if emitting all files, emits the changed file first", () => { + const files: NamedSourceText[] = [ + { name: "/a.ts", text: SourceText.New("", "", "namespace A { export const x = 0; }") }, + { name: "/b.ts", text: SourceText.New("", "", "namespace B { export const x = 0; }") }, + ]; + + let program = newProgram(files, ["/a.ts", "/b.ts"], {}); + const assertChanges = makeAssertChanges(() => program); + + assertChanges(["/a.js", "/b.js"]); + + program = updateProgramFile(program, "/a.ts", "namespace A { export const x = 1; }"); + assertChanges(["/a.js", "/b.js"]); + + program = updateProgramFile(program, "/b.ts", "namespace B { export const x = 1; }"); + assertChanges(["/b.js", "/a.js"]); }); - function makeAssertChanges(getProgram: () => Program): (fileNames: readonly string[]) => void { - const host: BuilderProgramHost = { useCaseSensitiveFileNames: returnTrue }; - let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; - return fileNames => { - const program = getProgram(); - builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); - const outputFileNames: string[] = []; - // eslint-disable-next-line no-empty - while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName))) { + it("keeps the file in affected files if cancellation token throws during the operation", () => { + const files: NamedSourceText[] = [ + { name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") }, + { name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") }, + { name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") }, + { name: "/d.ts", text: SourceText.New("", "", "export const dd = 0;") }, + { name: "/e.ts", text: SourceText.New("", "", "export const ee = 0;") }, + ]; + + let program = newProgram(files, ["/d.ts", "/e.ts", "/a.ts"], {}); + const assertChanges = makeAssertChangesWithCancellationToken(() => program); + // No cancellation + assertChanges(["/d.js", "/e.js", "/c.js", "/b.js", "/a.js"]); + + // cancel when emitting a.ts + program = updateProgramFile(program, "/a.ts", "export function foo() { }"); + assertChanges(["/a.js"], 0); + // Change d.ts and verify previously pending a.ts is emitted as well + program = updateProgramFile(program, "/d.ts", "export function bar() { }"); + assertChanges(["/a.js", "/d.js"]); + + // Cancel when emitting b.js + program = updateProgramFile(program, "/b.ts", "export class b { foo() { c + 1; } }"); + program = updateProgramFile(program, "/d.ts", "export function bar2() { }"); + assertChanges(["/d.js", "/b.js", "/a.js"], 1); + // Change e.ts and verify previously b.js as well as a.js get emitted again since previous change was consumed completely but not d.ts + program = updateProgramFile(program, "/e.ts", "export function bar3() { }"); + assertChanges(["/b.js", "/a.js", "/e.js"]); + }); +}); + +function makeAssertChanges(getProgram: () => Program): (fileNames: readonly string[]) => void { + const host: BuilderProgramHost = { useCaseSensitiveFileNames: returnTrue }; + let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; + return fileNames => { + const program = getProgram(); + builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); + const outputFileNames: string[] = []; + // eslint-disable-next-line no-empty + while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName))) { + } + assert.deepEqual(outputFileNames, fileNames); + }; +} + +function makeAssertChangesWithCancellationToken(getProgram: () => Program): (fileNames: readonly string[], cancelAfterEmitLength?: number) => void { + const host: BuilderProgramHost = { useCaseSensitiveFileNames: returnTrue }; + let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; + let cancel = false; + const cancellationToken: CancellationToken = { + isCancellationRequested: () => cancel, + throwIfCancellationRequested: () => { + if (cancel) { + throw new OperationCanceledException(); } - assert.deepEqual(outputFileNames, fileNames); - }; - } - - function makeAssertChangesWithCancellationToken(getProgram: () => Program): (fileNames: readonly string[], cancelAfterEmitLength?: number) => void { - const host: BuilderProgramHost = { useCaseSensitiveFileNames: returnTrue }; - let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; - let cancel = false; - const cancellationToken: CancellationToken = { - isCancellationRequested: () => cancel, - throwIfCancellationRequested: () => { - if (cancel) { - throw new OperationCanceledException(); + }, + }; + return (fileNames, cancelAfterEmitLength?: number) => { + cancel = false; + let operationWasCancelled = false; + const program = getProgram(); + builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); + const outputFileNames: string[] = []; + try { + do { + assert.isFalse(cancel); + if (outputFileNames.length === cancelAfterEmitLength) { + cancel = true; } - }, - }; - return (fileNames, cancelAfterEmitLength?: number) => { - cancel = false; - let operationWasCancelled = false; - const program = getProgram(); - builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); - const outputFileNames: string[] = []; - try { - do { - assert.isFalse(cancel); - if (outputFileNames.length === cancelAfterEmitLength) { - cancel = true; - } - } while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName), cancellationToken)); - } - catch (e) { - assert.isFalse(operationWasCancelled); - assert(e instanceof OperationCanceledException, e.toString()); - operationWasCancelled = true; - } - assert.equal(cancel, operationWasCancelled); - assert.equal(operationWasCancelled, fileNames.length > cancelAfterEmitLength!); - assert.deepEqual(outputFileNames, fileNames.slice(0, cancelAfterEmitLength)); - }; - } - - function updateProgramFile(program: ProgramWithSourceTexts, fileName: string, fileContent: string): ProgramWithSourceTexts { - return updateProgram(program, program.getRootFileNames(), program.getCompilerOptions(), files => { - updateProgramText(files, fileName, fileContent); - }); - } + } while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName), cancellationToken)); + } + catch (e) { + assert.isFalse(operationWasCancelled); + assert(e instanceof OperationCanceledException, e.toString()); + operationWasCancelled = true; + } + assert.equal(cancel, operationWasCancelled); + assert.equal(operationWasCancelled, fileNames.length > cancelAfterEmitLength!); + assert.deepEqual(outputFileNames, fileNames.slice(0, cancelAfterEmitLength)); + }; +} + +function updateProgramFile(program: ProgramWithSourceTexts, fileName: string, fileContent: string): ProgramWithSourceTexts { + return updateProgram(program, program.getRootFileNames(), program.getCompilerOptions(), files => { + updateProgramText(files, fileName, fileContent); + }); +} } diff --git a/src/testRunner/unittests/comments.ts b/src/testRunner/unittests/comments.ts index 59f3020c112bc..ac4144eda5ca9 100644 --- a/src/testRunner/unittests/comments.ts +++ b/src/testRunner/unittests/comments.ts @@ -1,32 +1,32 @@ namespace ts { - describe("comment parsing", () => { - const withShebang = `#! node +describe("comment parsing", () => { + const withShebang = `#! node /** comment */ // another one ;`; - const noShebang = `/** comment */ + const noShebang = `/** comment */ // another one ;`; - const withTrailing = `;/* comment */ + const withTrailing = `;/* comment */ // another one `; - it("skips shebang", () => { - const result = getLeadingCommentRanges(withShebang, 0); - assert.isDefined(result); - assert.strictEqual(result!.length, 2); - }); + it("skips shebang", () => { + const result = getLeadingCommentRanges(withShebang, 0); + assert.isDefined(result); + assert.strictEqual(result!.length, 2); + }); - it("treats all comments at start of file as leading comments", () => { - const result = getLeadingCommentRanges(noShebang, 0); - assert.isDefined(result); - assert.strictEqual(result!.length, 2); - }); + it("treats all comments at start of file as leading comments", () => { + const result = getLeadingCommentRanges(noShebang, 0); + assert.isDefined(result); + assert.strictEqual(result!.length, 2); + }); - it("returns leading comments if position is not 0", () => { - const result = getLeadingCommentRanges(withTrailing, 1); - assert.isDefined(result); - assert.strictEqual(result!.length, 1); - assert.strictEqual(result![0].kind, SyntaxKind.SingleLineCommentTrivia); - }); + it("returns leading comments if position is not 0", () => { + const result = getLeadingCommentRanges(withTrailing, 1); + assert.isDefined(result); + assert.strictEqual(result!.length, 1); + assert.strictEqual(result![0].kind, SyntaxKind.SingleLineCommentTrivia); }); +}); } diff --git a/src/testRunner/unittests/compilerCore.ts b/src/testRunner/unittests/compilerCore.ts index 4c3918c3149f3..e81d5d30e2de2 100644 --- a/src/testRunner/unittests/compilerCore.ts +++ b/src/testRunner/unittests/compilerCore.ts @@ -1,33 +1,33 @@ namespace ts { - describe("unittests:: compilerCore", () => { - describe("equalOwnProperties", () => { - it("correctly equates objects", () => { - assert.isTrue(equalOwnProperties({}, {})); - assert.isTrue(equalOwnProperties({ a: 1 }, { a: 1 })); - assert.isTrue(equalOwnProperties({ a: 1, b: 2 }, { b: 2, a: 1 })); - }); - it("correctly identifies unmatched objects", () => { - assert.isFalse(equalOwnProperties({}, { a: 1 }), "missing left property"); - assert.isFalse(equalOwnProperties({ a: 1 }, {}), "missing right property"); - assert.isFalse(equalOwnProperties({ a: 1 }, { a: 2 }), "differing property"); - }); - it("correctly identifies undefined vs hasOwnProperty", () => { - assert.isFalse(equalOwnProperties({}, { a: undefined }), "missing left property"); - assert.isFalse(equalOwnProperties({ a: undefined }, {}), "missing right property"); - }); - it("truthiness", () => { - const trythyTest = (l: any, r: any) => !!l === !!r; - assert.isFalse(equalOwnProperties({}, { a: 1 }, trythyTest), "missing left truthy property"); - assert.isFalse(equalOwnProperties({}, { a: 0 }, trythyTest), "missing left falsey property"); - assert.isFalse(equalOwnProperties({ a: 1 }, {}, trythyTest), "missing right truthy property"); - assert.isFalse(equalOwnProperties({ a: 0 }, {}, trythyTest), "missing right falsey property"); - assert.isTrue(equalOwnProperties({ a: 1 }, { a: "foo" }, trythyTest), "valid equality"); - }); - it("all equal", () => { - assert.isFalse(equalOwnProperties({}, { a: 1 }, () => true), "missing left property"); - assert.isFalse(equalOwnProperties({ a: 1 }, {}, () => true), "missing right property"); - assert.isTrue(equalOwnProperties({ a: 1 }, { a: 2 }, () => true), "valid equality"); - }); +describe("unittests:: compilerCore", () => { + describe("equalOwnProperties", () => { + it("correctly equates objects", () => { + assert.isTrue(equalOwnProperties({}, {})); + assert.isTrue(equalOwnProperties({ a: 1 }, { a: 1 })); + assert.isTrue(equalOwnProperties({ a: 1, b: 2 }, { b: 2, a: 1 })); + }); + it("correctly identifies unmatched objects", () => { + assert.isFalse(equalOwnProperties({}, { a: 1 }), "missing left property"); + assert.isFalse(equalOwnProperties({ a: 1 }, {}), "missing right property"); + assert.isFalse(equalOwnProperties({ a: 1 }, { a: 2 }), "differing property"); + }); + it("correctly identifies undefined vs hasOwnProperty", () => { + assert.isFalse(equalOwnProperties({}, { a: undefined }), "missing left property"); + assert.isFalse(equalOwnProperties({ a: undefined }, {}), "missing right property"); + }); + it("truthiness", () => { + const trythyTest = (l: any, r: any) => !!l === !!r; + assert.isFalse(equalOwnProperties({}, { a: 1 }, trythyTest), "missing left truthy property"); + assert.isFalse(equalOwnProperties({}, { a: 0 }, trythyTest), "missing left falsey property"); + assert.isFalse(equalOwnProperties({ a: 1 }, {}, trythyTest), "missing right truthy property"); + assert.isFalse(equalOwnProperties({ a: 0 }, {}, trythyTest), "missing right falsey property"); + assert.isTrue(equalOwnProperties({ a: 1 }, { a: "foo" }, trythyTest), "valid equality"); + }); + it("all equal", () => { + assert.isFalse(equalOwnProperties({}, { a: 1 }, () => true), "missing left property"); + assert.isFalse(equalOwnProperties({ a: 1 }, {}, () => true), "missing right property"); + assert.isTrue(equalOwnProperties({ a: 1 }, { a: 2 }, () => true), "valid equality"); }); }); +}); } diff --git a/src/testRunner/unittests/config/commandLineParsing.ts b/src/testRunner/unittests/config/commandLineParsing.ts index 34644a0976640..cf5a581d73a9c 100644 --- a/src/testRunner/unittests/config/commandLineParsing.ts +++ b/src/testRunner/unittests/config/commandLineParsing.ts @@ -1,1059 +1,1059 @@ namespace ts { - describe("unittests:: config:: commandLineParsing:: parseCommandLine", () => { - - function assertParseResult(commandLine: string[], expectedParsedCommandLine: ParsedCommandLine, workerDiagnostic?: () => ParseCommandLineWorkerDiagnostics) { - const parsed = parseCommandLineWorker(workerDiagnostic?.() || compilerOptionsDidYouMeanDiagnostics, commandLine); - assert.deepEqual(parsed.options, expectedParsedCommandLine.options); - assert.deepEqual(parsed.watchOptions, expectedParsedCommandLine.watchOptions); - - const parsedErrors = parsed.errors; - const expectedErrors = expectedParsedCommandLine.errors; - assert.isTrue(parsedErrors.length === expectedErrors.length, `Expected error: ${JSON.stringify(expectedErrors)}. Actual error: ${JSON.stringify(parsedErrors)}.`); - for (let i = 0; i < parsedErrors.length; i++) { - const parsedError = parsedErrors[i]; - const expectedError = expectedErrors[i]; - assert.equal(parsedError.code, expectedError.code); - assert.equal(parsedError.category, expectedError.category); - // Allow matching a prefix of the error message - if (typeof expectedError.messageText === "string" && expectedError.messageText.includes("[...]")) { - const prefix = expectedError.messageText.split("[...]")[0]; - assert(expectedError.messageText.startsWith(prefix)); - } - else { - assert.equal(parsedError.messageText, expectedError.messageText); - } +describe("unittests:: config:: commandLineParsing:: parseCommandLine", () => { + + function assertParseResult(commandLine: string[], expectedParsedCommandLine: ParsedCommandLine, workerDiagnostic?: () => ParseCommandLineWorkerDiagnostics) { + const parsed = parseCommandLineWorker(workerDiagnostic?.() || compilerOptionsDidYouMeanDiagnostics, commandLine); + assert.deepEqual(parsed.options, expectedParsedCommandLine.options); + assert.deepEqual(parsed.watchOptions, expectedParsedCommandLine.watchOptions); + + const parsedErrors = parsed.errors; + const expectedErrors = expectedParsedCommandLine.errors; + assert.isTrue(parsedErrors.length === expectedErrors.length, `Expected error: ${JSON.stringify(expectedErrors)}. Actual error: ${JSON.stringify(parsedErrors)}.`); + for (let i = 0; i < parsedErrors.length; i++) { + const parsedError = parsedErrors[i]; + const expectedError = expectedErrors[i]; + assert.equal(parsedError.code, expectedError.code); + assert.equal(parsedError.category, expectedError.category); + // Allow matching a prefix of the error message + if (typeof expectedError.messageText === "string" && expectedError.messageText.includes("[...]")) { + const prefix = expectedError.messageText.split("[...]")[0]; + assert(expectedError.messageText.startsWith(prefix)); } - - const parsedFileNames = parsed.fileNames; - const expectedFileNames = expectedParsedCommandLine.fileNames; - assert.isTrue(parsedFileNames.length === expectedFileNames.length, `Expected fileNames: [${JSON.stringify(expectedFileNames)}]. Actual fileNames: [${JSON.stringify(parsedFileNames)}].`); - for (let i = 0; i < parsedFileNames.length; i++) { - const parsedFileName = parsedFileNames[i]; - const expectedFileName = expectedFileNames[i]; - assert.equal(parsedFileName, expectedFileName); + else { + assert.equal(parsedError.messageText, expectedError.messageText); } } - it("Parse single option of library flag ", () => { - // --lib es6 0.ts - assertParseResult(["--lib", "es6", "0.ts"], - { - errors: [], - fileNames: ["0.ts"], - options: { - lib: ["lib.es2015.d.ts"] - } - }); - }); + const parsedFileNames = parsed.fileNames; + const expectedFileNames = expectedParsedCommandLine.fileNames; + assert.isTrue(parsedFileNames.length === expectedFileNames.length, `Expected fileNames: [${JSON.stringify(expectedFileNames)}]. Actual fileNames: [${JSON.stringify(parsedFileNames)}].`); + for (let i = 0; i < parsedFileNames.length; i++) { + const parsedFileName = parsedFileNames[i]; + const expectedFileName = expectedFileNames[i]; + assert.equal(parsedFileName, expectedFileName); + } + } + + it("Parse single option of library flag ", () => { + // --lib es6 0.ts + assertParseResult(["--lib", "es6", "0.ts"], + { + errors: [], + fileNames: ["0.ts"], + options: { + lib: ["lib.es2015.d.ts"] + } + }); + }); - it("Handles 'may only be used with --build' flags", () => { - const buildFlags = ["--clean", "--dry", "--force", "--verbose"]; + it("Handles 'may only be used with --build' flags", () => { + const buildFlags = ["--clean", "--dry", "--force", "--verbose"]; + + assertParseResult(buildFlags, { + errors: buildFlags.map(buildFlag => ({ + messageText: `Compiler option '${buildFlag}' may only be used with '--build'.`, + category: Diagnostics.Compiler_option_0_may_only_be_used_with_build.category, + code: Diagnostics.Compiler_option_0_may_only_be_used_with_build.code, + file: undefined, + start: undefined, + length: undefined + })), + fileNames: [], + options: {} + }); + }); - assertParseResult(buildFlags, { - errors: buildFlags.map(buildFlag => ({ - messageText: `Compiler option '${buildFlag}' may only be used with '--build'.`, - category: Diagnostics.Compiler_option_0_may_only_be_used_with_build.category, - code: Diagnostics.Compiler_option_0_may_only_be_used_with_build.code, + it("Handles 'did you mean?' for misspelt flags", () => { + // --declarations --allowTS + assertParseResult(["--declarations", "--allowTS"], { + errors: [ + { + messageText: "Unknown compiler option '--declarations'. Did you mean 'declaration'?", + category: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.category, + code: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.code, file: undefined, start: undefined, length: undefined - })), - fileNames: [], - options: {} - }); + }, + { + messageText: "Unknown compiler option '--allowTS'. Did you mean 'allowJs'?", + category: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.category, + code: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.code, + file: undefined, + start: undefined, + length: undefined + } + ], + fileNames: [], + options: {} }); + }); - it("Handles 'did you mean?' for misspelt flags", () => { - // --declarations --allowTS - assertParseResult(["--declarations", "--allowTS"], { - errors: [ - { - messageText: "Unknown compiler option '--declarations'. Did you mean 'declaration'?", - category: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.category, - code: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.code, - file: undefined, - start: undefined, - length: undefined - }, - { - messageText: "Unknown compiler option '--allowTS'. Did you mean 'allowJs'?", - category: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.category, - code: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.code, - file: undefined, - start: undefined, - length: undefined - } - ], - fileNames: [], - options: {} - }); - }); + it("Parse multiple options of library flags ", () => { + // --lib es5,es2015.symbol.wellknown 0.ts + assertParseResult(["--lib", "es5,es2015.symbol.wellknown", "0.ts"], + { + errors: [], + fileNames: ["0.ts"], + options: { + lib: ["lib.es5.d.ts", "lib.es2015.symbol.wellknown.d.ts"] + } + }); + }); - it("Parse multiple options of library flags ", () => { - // --lib es5,es2015.symbol.wellknown 0.ts - assertParseResult(["--lib", "es5,es2015.symbol.wellknown", "0.ts"], - { - errors: [], - fileNames: ["0.ts"], - options: { - lib: ["lib.es5.d.ts", "lib.es2015.symbol.wellknown.d.ts"] - } - }); - }); + it("Parse invalid option of library flags ", () => { + // --lib es5,invalidOption 0.ts + assertParseResult(["--lib", "es5,invalidOption", "0.ts"], + { + errors: [{ + messageText: "Argument for '--lib' option must be: 'es5', 'es6' [...]", + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: { + lib: ["lib.es5.d.ts"] + } + }); + }); + it("Parse empty options of --jsx ", () => { + // 0.ts --jsx + assertParseResult(["0.ts", "--jsx"], + { + errors: [{ + messageText: "Compiler option 'jsx' expects an argument.", + category: Diagnostics.Compiler_option_0_expects_an_argument.category, + code: Diagnostics.Compiler_option_0_expects_an_argument.code, - it("Parse invalid option of library flags ", () => { - // --lib es5,invalidOption 0.ts - assertParseResult(["--lib", "es5,invalidOption", "0.ts"], - { - errors: [{ - messageText: "Argument for '--lib' option must be: 'es5', 'es6' [...]", - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - file: undefined, - start: undefined, - length: undefined, - }], - fileNames: ["0.ts"], - options: { - lib: ["lib.es5.d.ts"] - } - }); - }); - it("Parse empty options of --jsx ", () => { - // 0.ts --jsx - assertParseResult(["0.ts", "--jsx"], - { - errors: [{ - messageText: "Compiler option 'jsx' expects an argument.", - category: Diagnostics.Compiler_option_0_expects_an_argument.category, - code: Diagnostics.Compiler_option_0_expects_an_argument.code, - - file: undefined, - start: undefined, - length: undefined, - }, { - messageText: "Argument for '--jsx' option must be: 'preserve', 'react-native', 'react', 'react-jsx', 'react-jsxdev'.", - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - - file: undefined, - start: undefined, - length: undefined, - }], - fileNames: ["0.ts"], - options: { jsx: undefined } - }); - }); + file: undefined, + start: undefined, + length: undefined, + }, { + messageText: "Argument for '--jsx' option must be: 'preserve', 'react-native', 'react', 'react-jsx', 'react-jsxdev'.", + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - it("Parse empty options of --module ", () => { - // 0.ts -- - assertParseResult(["0.ts", "--module"], - { - errors: [{ - messageText: "Compiler option 'module' expects an argument.", - category: Diagnostics.Compiler_option_0_expects_an_argument.category, - code: Diagnostics.Compiler_option_0_expects_an_argument.code, - - file: undefined, - start: undefined, - length: undefined, - }, { - messageText: "Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node12', 'nodenext'.", - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - - file: undefined, - start: undefined, - length: undefined, - }], - fileNames: ["0.ts"], - options: { module: undefined } - }); - }); + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: { jsx: undefined } + }); + }); - it("Parse empty options of --newLine ", () => { - // 0.ts --newLine - assertParseResult(["0.ts", "--newLine"], - { - errors: [{ - messageText: "Compiler option 'newLine' expects an argument.", - category: Diagnostics.Compiler_option_0_expects_an_argument.category, - code: Diagnostics.Compiler_option_0_expects_an_argument.code, - - file: undefined, - start: undefined, - length: undefined, - }, { - messageText: "Argument for '--newLine' option must be: 'crlf', 'lf'.", - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - - file: undefined, - start: undefined, - length: undefined, - }], - fileNames: ["0.ts"], - options: { newLine: undefined } - }); - }); + it("Parse empty options of --module ", () => { + // 0.ts -- + assertParseResult(["0.ts", "--module"], + { + errors: [{ + messageText: "Compiler option 'module' expects an argument.", + category: Diagnostics.Compiler_option_0_expects_an_argument.category, + code: Diagnostics.Compiler_option_0_expects_an_argument.code, - it("Parse empty options of --target ", () => { - // 0.ts --target - assertParseResult(["0.ts", "--target"], - { - errors: [{ - messageText: "Compiler option 'target' expects an argument.", - category: Diagnostics.Compiler_option_0_expects_an_argument.category, - code: Diagnostics.Compiler_option_0_expects_an_argument.code, - - file: undefined, - start: undefined, - length: undefined, - }, { - messageText: "Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'esnext'.", - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - - file: undefined, - start: undefined, - length: undefined, - }], - fileNames: ["0.ts"], - options: { target: undefined } - }); - }); + file: undefined, + start: undefined, + length: undefined, + }, { + messageText: "Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node12', 'nodenext'.", + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - it("Parse empty options of --moduleResolution ", () => { - // 0.ts --moduleResolution - assertParseResult(["0.ts", "--moduleResolution"], - { - errors: [{ - messageText: "Compiler option 'moduleResolution' expects an argument.", - category: Diagnostics.Compiler_option_0_expects_an_argument.category, - code: Diagnostics.Compiler_option_0_expects_an_argument.code, - - file: undefined, - start: undefined, - length: undefined, - }, { - messageText: "Argument for '--moduleResolution' option must be: 'node', 'classic', 'node12', 'nodenext'.", - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - - file: undefined, - start: undefined, - length: undefined, - }], - fileNames: ["0.ts"], - options: { moduleResolution: undefined } - }); - }); + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: { module: undefined } + }); + }); - it("Parse empty options of --lib ", () => { - // 0.ts --lib - assertParseResult(["0.ts", "--lib"], - { - errors: [{ - messageText: "Compiler option 'lib' expects an argument.", - category: Diagnostics.Compiler_option_0_expects_an_argument.category, - code: Diagnostics.Compiler_option_0_expects_an_argument.code, - - file: undefined, - start: undefined, - length: undefined, - }], - fileNames: ["0.ts"], - options: { - lib: [] - } - }); - }); + it("Parse empty options of --newLine ", () => { + // 0.ts --newLine + assertParseResult(["0.ts", "--newLine"], + { + errors: [{ + messageText: "Compiler option 'newLine' expects an argument.", + category: Diagnostics.Compiler_option_0_expects_an_argument.category, + code: Diagnostics.Compiler_option_0_expects_an_argument.code, - it("Parse empty string of --lib ", () => { - // 0.ts --lib - // This test is an error because the empty string is falsey - assertParseResult(["0.ts", "--lib", ""], - { - errors: [{ - messageText: "Compiler option 'lib' expects an argument.", - category: Diagnostics.Compiler_option_0_expects_an_argument.category, - code: Diagnostics.Compiler_option_0_expects_an_argument.code, - - file: undefined, - start: undefined, - length: undefined, - }], - fileNames: ["0.ts"], - options: { - lib: [] - } - }); - }); + file: undefined, + start: undefined, + length: undefined, + }, { + messageText: "Argument for '--newLine' option must be: 'crlf', 'lf'.", + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - it("Parse immediately following command line argument of --lib ", () => { - // 0.ts --lib - assertParseResult(["0.ts", "--lib", "--sourcemap"], - { - errors: [], - fileNames: ["0.ts"], - options: { - lib: [], - sourceMap: true - } - }); - }); + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: { newLine: undefined } + }); + }); - it("Parse --lib option with extra comma ", () => { - // --lib es5, es7 0.ts - assertParseResult(["--lib", "es5,", "es7", "0.ts"], - { - errors: [{ - messageText: "Argument for '--lib' option must be: 'es5', 'es6' [...].", - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - file: undefined, - start: undefined, - length: undefined, - }], - fileNames: ["es7", "0.ts"], - options: { - lib: ["lib.es5.d.ts"] - } - }); - }); + it("Parse empty options of --target ", () => { + // 0.ts --target + assertParseResult(["0.ts", "--target"], + { + errors: [{ + messageText: "Compiler option 'target' expects an argument.", + category: Diagnostics.Compiler_option_0_expects_an_argument.category, + code: Diagnostics.Compiler_option_0_expects_an_argument.code, - it("Parse --lib option with trailing white-space ", () => { - // --lib es5, es7 0.ts - assertParseResult(["--lib", "es5, ", "es7", "0.ts"], - { - errors: [{ - messageText: "Argument for '--lib' option must be: 'es5', 'es6', [...]", - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - file: undefined, - start: undefined, - length: undefined, - }], - fileNames: ["es7", "0.ts"], - options: { - lib: ["lib.es5.d.ts"] - } - }); - }); + file: undefined, + start: undefined, + length: undefined, + }, { + messageText: "Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'esnext'.", + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - it("Parse multiple compiler flags with input files at the end", () => { - // --lib es5,es2015.symbol.wellknown --target es5 0.ts - assertParseResult(["--lib", "es5,es2015.symbol.wellknown", "--target", "es5", "0.ts"], - { - errors: [], - fileNames: ["0.ts"], - options: { - lib: ["lib.es5.d.ts", "lib.es2015.symbol.wellknown.d.ts"], - target: ScriptTarget.ES5, - } - }); - }); + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: { target: undefined } + }); + }); - it("Parse multiple compiler flags with input files in the middle", () => { - // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown - assertParseResult(["--module", "commonjs", "--target", "es5", "0.ts", "--lib", "es5,es2015.symbol.wellknown"], - { - errors: [], - fileNames: ["0.ts"], - options: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - lib: ["lib.es5.d.ts", "lib.es2015.symbol.wellknown.d.ts"], - } - }); - }); + it("Parse empty options of --moduleResolution ", () => { + // 0.ts --moduleResolution + assertParseResult(["0.ts", "--moduleResolution"], + { + errors: [{ + messageText: "Compiler option 'moduleResolution' expects an argument.", + category: Diagnostics.Compiler_option_0_expects_an_argument.category, + code: Diagnostics.Compiler_option_0_expects_an_argument.code, - it("Parse multiple library compiler flags ", () => { - // --module commonjs --target es5 --lib es5 0.ts --library es2015.array,es2015.symbol.wellknown - assertParseResult(["--module", "commonjs", "--target", "es5", "--lib", "es5", "0.ts", "--lib", "es2015.core, es2015.symbol.wellknown "], - { - errors: [], - fileNames: ["0.ts"], - options: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - lib: ["lib.es2015.core.d.ts", "lib.es2015.symbol.wellknown.d.ts"], - } - }); - }); + file: undefined, + start: undefined, + length: undefined, + }, { + messageText: "Argument for '--moduleResolution' option must be: 'node', 'classic', 'node12', 'nodenext'.", + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - it("Parse explicit boolean flag value", () => { - assertParseResult(["--strictNullChecks", "false", "0.ts"], - { - errors: [], - fileNames: ["0.ts"], - options: { - strictNullChecks: false, - } - }); - }); + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: { moduleResolution: undefined } + }); + }); - it("Parse non boolean argument after boolean flag", () => { - assertParseResult(["--noImplicitAny", "t", "0.ts"], - { - errors: [], - fileNames: ["t", "0.ts"], - options: { - noImplicitAny: true, - } - }); - }); + it("Parse empty options of --lib ", () => { + // 0.ts --lib + assertParseResult(["0.ts", "--lib"], + { + errors: [{ + messageText: "Compiler option 'lib' expects an argument.", + category: Diagnostics.Compiler_option_0_expects_an_argument.category, + code: Diagnostics.Compiler_option_0_expects_an_argument.code, - it("Parse implicit boolean flag value", () => { - assertParseResult(["--strictNullChecks"], - { - errors: [], - fileNames: [], - options: { - strictNullChecks: true, - } - }); - }); + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: { + lib: [] + } + }); + }); - it("parse --incremental", () => { - // --lib es6 0.ts - assertParseResult(["--incremental", "0.ts"], - { - errors: [], - fileNames: ["0.ts"], - options: { incremental: true } - }); - }); + it("Parse empty string of --lib ", () => { + // 0.ts --lib + // This test is an error because the empty string is falsey + assertParseResult(["0.ts", "--lib", ""], + { + errors: [{ + messageText: "Compiler option 'lib' expects an argument.", + category: Diagnostics.Compiler_option_0_expects_an_argument.category, + code: Diagnostics.Compiler_option_0_expects_an_argument.code, - it("parse --tsBuildInfoFile", () => { - // --lib es6 0.ts - assertParseResult(["--tsBuildInfoFile", "build.tsbuildinfo", "0.ts"], - { - errors: [], - fileNames: ["0.ts"], - options: { tsBuildInfoFile: "build.tsbuildinfo" } - }); - }); + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: { + lib: [] + } + }); + }); - describe("parses command line null for tsconfig only option", () => { - interface VerifyNull { - optionName: string; - nonNullValue?: string; - workerDiagnostic?: () => ParseCommandLineWorkerDiagnostics; - diagnosticMessage: DiagnosticMessage; - } - function verifyNull({ optionName, nonNullValue, workerDiagnostic, diagnosticMessage }: VerifyNull) { - it("allows setting it to null", () => { - assertParseResult( - [`--${optionName}`, "null", "0.ts"], - { - errors: [], - fileNames: ["0.ts"], - options: { [optionName]: undefined } - }, - workerDiagnostic - ); - }); + it("Parse immediately following command line argument of --lib ", () => { + // 0.ts --lib + assertParseResult(["0.ts", "--lib", "--sourcemap"], + { + errors: [], + fileNames: ["0.ts"], + options: { + lib: [], + sourceMap: true + } + }); + }); - if (nonNullValue) { - it("errors if non null value is passed", () => { - assertParseResult( - [`--${optionName}`, nonNullValue, "0.ts"], - { - errors: [{ - messageText: formatStringFromArgs(diagnosticMessage.message, [optionName]), - category: diagnosticMessage.category, - code: diagnosticMessage.code, - file: undefined, - start: undefined, - length: undefined - }], - fileNames: ["0.ts"], - options: {} - }, - workerDiagnostic - ); - }); + it("Parse --lib option with extra comma ", () => { + // --lib es5, es7 0.ts + assertParseResult(["--lib", "es5,", "es7", "0.ts"], + { + errors: [{ + messageText: "Argument for '--lib' option must be: 'es5', 'es6' [...].", + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["es7", "0.ts"], + options: { + lib: ["lib.es5.d.ts"] } + }); + }); - it("errors if its followed by another option", () => { - assertParseResult( - ["0.ts", "--strictNullChecks", `--${optionName}`], - { - errors: [{ - messageText: formatStringFromArgs(diagnosticMessage.message, [optionName]), - category: diagnosticMessage.category, - code: diagnosticMessage.code, - file: undefined, - start: undefined, - length: undefined - }], - fileNames: ["0.ts"], - options: { strictNullChecks: true } - }, - workerDiagnostic - ); - }); + it("Parse --lib option with trailing white-space ", () => { + // --lib es5, es7 0.ts + assertParseResult(["--lib", "es5, ", "es7", "0.ts"], + { + errors: [{ + messageText: "Argument for '--lib' option must be: 'es5', 'es6', [...]", + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["es7", "0.ts"], + options: { + lib: ["lib.es5.d.ts"] + } + }); + }); - it("errors if its last option", () => { - assertParseResult( - ["0.ts", `--${optionName}`], - { - errors: [{ - messageText: formatStringFromArgs(diagnosticMessage.message, [optionName]), - category: diagnosticMessage.category, - code: diagnosticMessage.code, - file: undefined, - start: undefined, - length: undefined - }], - fileNames: ["0.ts"], - options: {} - }, - workerDiagnostic - ); - }); - } + it("Parse multiple compiler flags with input files at the end", () => { + // --lib es5,es2015.symbol.wellknown --target es5 0.ts + assertParseResult(["--lib", "es5,es2015.symbol.wellknown", "--target", "es5", "0.ts"], + { + errors: [], + fileNames: ["0.ts"], + options: { + lib: ["lib.es5.d.ts", "lib.es2015.symbol.wellknown.d.ts"], + target: ScriptTarget.ES5, + } + }); + }); - interface VerifyNullNonIncludedOption { - type: () => "string" | "number" | ESMap; - nonNullValue?: string; - } - function verifyNullNonIncludedOption({ type, nonNullValue }: VerifyNullNonIncludedOption) { - verifyNull({ - optionName: "optionName", - nonNullValue, - diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line, - workerDiagnostic: () => { - const optionDeclarations: CommandLineOption[] = [ - ...compilerOptionsDidYouMeanDiagnostics.optionDeclarations, - { - name: "optionName", - type: type(), - isTSConfigOnly: true, - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.Enable_project_compilation, - defaultValueDescription: "undefined", - } - ]; - return { - ...compilerOptionsDidYouMeanDiagnostics, - optionDeclarations, - getOptionsNameMap: () => createOptionNameMap(optionDeclarations) - }; - } - }); - } + it("Parse multiple compiler flags with input files in the middle", () => { + // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown + assertParseResult(["--module", "commonjs", "--target", "es5", "0.ts", "--lib", "es5,es2015.symbol.wellknown"], + { + errors: [], + fileNames: ["0.ts"], + options: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + lib: ["lib.es5.d.ts", "lib.es2015.symbol.wellknown.d.ts"], + } + }); + }); - describe("option of type boolean", () => { - it("allows setting it to false", () => { - assertParseResult( - ["--composite", "false", "0.ts"], - { - errors: [], - fileNames: ["0.ts"], - options: { composite: false } - } - ); - }); + it("Parse multiple library compiler flags ", () => { + // --module commonjs --target es5 --lib es5 0.ts --library es2015.array,es2015.symbol.wellknown + assertParseResult(["--module", "commonjs", "--target", "es5", "--lib", "es5", "0.ts", "--lib", "es2015.core, es2015.symbol.wellknown "], + { + errors: [], + fileNames: ["0.ts"], + options: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + lib: ["lib.es2015.core.d.ts", "lib.es2015.symbol.wellknown.d.ts"], + } + }); + }); - verifyNull({ - optionName: "composite", - nonNullValue: "true", - diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_false_or_null_on_command_line - }); + it("Parse explicit boolean flag value", () => { + assertParseResult(["--strictNullChecks", "false", "0.ts"], + { + errors: [], + fileNames: ["0.ts"], + options: { + strictNullChecks: false, + } }); + }); - describe("option of type object", () => { - verifyNull({ - optionName: "paths", - diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line - }); + it("Parse non boolean argument after boolean flag", () => { + assertParseResult(["--noImplicitAny", "t", "0.ts"], + { + errors: [], + fileNames: ["t", "0.ts"], + options: { + noImplicitAny: true, + } }); + }); - describe("option of type list", () => { - verifyNull({ - optionName: "rootDirs", - nonNullValue: "abc,xyz", - diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line - }); + it("Parse implicit boolean flag value", () => { + assertParseResult(["--strictNullChecks"], + { + errors: [], + fileNames: [], + options: { + strictNullChecks: true, + } }); + }); - describe("option of type string", () => { - verifyNullNonIncludedOption({ - type: () => "string", - nonNullValue: "hello" - }); + it("parse --incremental", () => { + // --lib es6 0.ts + assertParseResult(["--incremental", "0.ts"], + { + errors: [], + fileNames: ["0.ts"], + options: { incremental: true } }); + }); - describe("option of type number", () => { - verifyNullNonIncludedOption({ - type: () => "number", - nonNullValue: "10" - }); + it("parse --tsBuildInfoFile", () => { + // --lib es6 0.ts + assertParseResult(["--tsBuildInfoFile", "build.tsbuildinfo", "0.ts"], + { + errors: [], + fileNames: ["0.ts"], + options: { tsBuildInfoFile: "build.tsbuildinfo" } }); + }); - describe("option of type Map", () => { - verifyNullNonIncludedOption({ - type: () => new Map(getEntries({ - node: ModuleResolutionKind.NodeJs, - classic: ModuleResolutionKind.Classic, - })), - nonNullValue: "node" - }); + describe("parses command line null for tsconfig only option", () => { + interface VerifyNull { + optionName: string; + nonNullValue?: string; + workerDiagnostic?: () => ParseCommandLineWorkerDiagnostics; + diagnosticMessage: DiagnosticMessage; + } + function verifyNull({ optionName, nonNullValue, workerDiagnostic, diagnosticMessage }: VerifyNull) { + it("allows setting it to null", () => { + assertParseResult( + [`--${optionName}`, "null", "0.ts"], + { + errors: [], + fileNames: ["0.ts"], + options: { [optionName]: undefined } + }, + workerDiagnostic + ); }); - }); - it("allows tsconfig only option to be set to null", () => { - assertParseResult(["--composite", "null", "-tsBuildInfoFile", "null", "0.ts"], - { - errors: [], - fileNames: ["0.ts"], - options: { composite: undefined, tsBuildInfoFile: undefined } + if (nonNullValue) { + it("errors if non null value is passed", () => { + assertParseResult( + [`--${optionName}`, nonNullValue, "0.ts"], + { + errors: [{ + messageText: formatStringFromArgs(diagnosticMessage.message, [optionName]), + category: diagnosticMessage.category, + code: diagnosticMessage.code, + file: undefined, + start: undefined, + length: undefined + }], + fileNames: ["0.ts"], + options: {} + }, + workerDiagnostic + ); }); - }); + } - describe("Watch options", () => { - it("parse --watchFile", () => { - assertParseResult(["--watchFile", "UseFsEvents", "0.ts"], + it("errors if its followed by another option", () => { + assertParseResult( + ["0.ts", "--strictNullChecks", `--${optionName}`], { - errors: [], + errors: [{ + messageText: formatStringFromArgs(diagnosticMessage.message, [optionName]), + category: diagnosticMessage.category, + code: diagnosticMessage.code, + file: undefined, + start: undefined, + length: undefined + }], fileNames: ["0.ts"], - options: {}, - watchOptions: { watchFile: WatchFileKind.UseFsEvents } - }); + options: { strictNullChecks: true } + }, + workerDiagnostic + ); }); - it("parse --watchDirectory", () => { - assertParseResult(["--watchDirectory", "FixedPollingInterval", "0.ts"], + it("errors if its last option", () => { + assertParseResult( + ["0.ts", `--${optionName}`], { - errors: [], + errors: [{ + messageText: formatStringFromArgs(diagnosticMessage.message, [optionName]), + category: diagnosticMessage.category, + code: diagnosticMessage.code, + file: undefined, + start: undefined, + length: undefined + }], fileNames: ["0.ts"], - options: {}, - watchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval } - }); + options: {} + }, + workerDiagnostic + ); }); + } - it("parse --fallbackPolling", () => { - assertParseResult(["--fallbackPolling", "PriorityInterval", "0.ts"], - { - errors: [], - fileNames: ["0.ts"], - options: {}, - watchOptions: { fallbackPolling: PollingWatchKind.PriorityInterval } - }); + interface VerifyNullNonIncludedOption { + type: () => "string" | "number" | ESMap; + nonNullValue?: string; + } + function verifyNullNonIncludedOption({ type, nonNullValue }: VerifyNullNonIncludedOption) { + verifyNull({ + optionName: "optionName", + nonNullValue, + diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line, + workerDiagnostic: () => { + const optionDeclarations: CommandLineOption[] = [ + ...compilerOptionsDidYouMeanDiagnostics.optionDeclarations, + { + name: "optionName", + type: type(), + isTSConfigOnly: true, + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.Enable_project_compilation, + defaultValueDescription: "undefined", + } + ]; + return { + ...compilerOptionsDidYouMeanDiagnostics, + optionDeclarations, + getOptionsNameMap: () => createOptionNameMap(optionDeclarations) + }; + } }); + } - it("parse --synchronousWatchDirectory", () => { - assertParseResult(["--synchronousWatchDirectory", "0.ts"], + describe("option of type boolean", () => { + it("allows setting it to false", () => { + assertParseResult( + ["--composite", "false", "0.ts"], { errors: [], fileNames: ["0.ts"], - options: {}, - watchOptions: { synchronousWatchDirectory: true } - }); + options: { composite: false } + } + ); }); - it("errors on missing argument to --fallbackPolling", () => { - assertParseResult(["0.ts", "--fallbackPolling"], - { - errors: [ - { - messageText: "Watch option 'fallbackPolling' requires a value of type string.", - category: Diagnostics.Watch_option_0_requires_a_value_of_type_1.category, - code: Diagnostics.Watch_option_0_requires_a_value_of_type_1.code, - file: undefined, - start: undefined, - length: undefined - }, - { - messageText: "Argument for '--fallbackPolling' option must be: 'fixedinterval', 'priorityinterval', 'dynamicpriority', 'fixedchunksize'.", - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - file: undefined, - start: undefined, - length: undefined - } - ], - fileNames: ["0.ts"], - options: {}, - watchOptions: { fallbackPolling: undefined } - }); + verifyNull({ + optionName: "composite", + nonNullValue: "true", + diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_false_or_null_on_command_line }); + }); - it("parse --excludeDirectories", () => { - assertParseResult(["--excludeDirectories", "**/temp", "0.ts"], - { - errors: [], - fileNames: ["0.ts"], - options: {}, - watchOptions: { excludeDirectories: ["**/temp"] } - }); + describe("option of type object", () => { + verifyNull({ + optionName: "paths", + diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line }); + }); - it("errors on invalid excludeDirectories", () => { - assertParseResult(["--excludeDirectories", "**/../*", "0.ts"], - { - errors: [ - { - messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, - category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, - code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, - file: undefined, - start: undefined, - length: undefined - } - ], - fileNames: ["0.ts"], - options: {}, - watchOptions: { excludeDirectories: [] } - }); + describe("option of type list", () => { + verifyNull({ + optionName: "rootDirs", + nonNullValue: "abc,xyz", + diagnosticMessage: Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line }); + }); - it("parse --excludeFiles", () => { - assertParseResult(["--excludeFiles", "**/temp/*.ts", "0.ts"], - { - errors: [], - fileNames: ["0.ts"], - options: {}, - watchOptions: { excludeFiles: ["**/temp/*.ts"] } - }); + describe("option of type string", () => { + verifyNullNonIncludedOption({ + type: () => "string", + nonNullValue: "hello" }); + }); - it("errors on invalid excludeFiles", () => { - assertParseResult(["--excludeFiles", "**/../*", "0.ts"], - { - errors: [ - { - messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, - category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, - code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, - file: undefined, - start: undefined, - length: undefined - } - ], - fileNames: ["0.ts"], - options: {}, - watchOptions: { excludeFiles: [] } - }); + describe("option of type number", () => { + verifyNullNonIncludedOption({ + type: () => "number", + nonNullValue: "10" + }); + }); + + describe("option of type Map", () => { + verifyNullNonIncludedOption({ + type: () => new Map(getEntries({ + node: ModuleResolutionKind.NodeJs, + classic: ModuleResolutionKind.Classic, + })), + nonNullValue: "node" }); }); }); - describe("unittests:: config:: commandLineParsing:: parseBuildOptions", () => { - function assertParseResult(commandLine: string[], expectedParsedBuildCommand: ParsedBuildCommand) { - const parsed = parseBuildCommand(commandLine); - assert.deepEqual(parsed.buildOptions, expectedParsedBuildCommand.buildOptions); - assert.deepEqual(parsed.watchOptions, expectedParsedBuildCommand.watchOptions); - - const parsedErrors = parsed.errors; - const expectedErrors = expectedParsedBuildCommand.errors; - assert.isTrue(parsedErrors.length === expectedErrors.length, `Expected error: ${JSON.stringify(expectedErrors)}. Actual error: ${JSON.stringify(parsedErrors)}.`); - for (let i = 0; i < parsedErrors.length; i++) { - const parsedError = parsedErrors[i]; - const expectedError = expectedErrors[i]; - assert.equal(parsedError.code, expectedError.code); - assert.equal(parsedError.category, expectedError.category); - assert.equal(parsedError.messageText, expectedError.messageText); - } + it("allows tsconfig only option to be set to null", () => { + assertParseResult(["--composite", "null", "-tsBuildInfoFile", "null", "0.ts"], + { + errors: [], + fileNames: ["0.ts"], + options: { composite: undefined, tsBuildInfoFile: undefined } + }); + }); - const parsedProjects = parsed.projects; - const expectedProjects = expectedParsedBuildCommand.projects; - assert.deepEqual(parsedProjects, expectedProjects, `Expected projects: [${JSON.stringify(expectedProjects)}]. Actual projects: [${JSON.stringify(parsedProjects)}].`); - } - it("parse build without any options ", () => { - // --lib es6 0.ts - assertParseResult([], + describe("Watch options", () => { + it("parse --watchFile", () => { + assertParseResult(["--watchFile", "UseFsEvents", "0.ts"], { errors: [], - projects: ["."], - buildOptions: {}, - watchOptions: undefined + fileNames: ["0.ts"], + options: {}, + watchOptions: { watchFile: WatchFileKind.UseFsEvents } }); }); - it("Parse multiple options", () => { - // --lib es5,es2015.symbol.wellknown 0.ts - assertParseResult(["--verbose", "--force", "tests"], + it("parse --watchDirectory", () => { + assertParseResult(["--watchDirectory", "FixedPollingInterval", "0.ts"], { errors: [], - projects: ["tests"], - buildOptions: { verbose: true, force: true }, - watchOptions: undefined - }); - }); - - it("Parse option with invalid option ", () => { - // --lib es5,invalidOption 0.ts - assertParseResult(["--verbose", "--invalidOption"], - { - errors: [{ - messageText: "Unknown build option '--invalidOption'.", - category: Diagnostics.Unknown_build_option_0.category, - code: Diagnostics.Unknown_build_option_0.code, - file: undefined, - start: undefined, - length: undefined, - }], - projects: ["."], - buildOptions: { verbose: true }, - watchOptions: undefined + fileNames: ["0.ts"], + options: {}, + watchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval } }); }); - it("parse build with listFilesOnly ", () => { - // --lib es6 0.ts - assertParseResult(["--listFilesOnly"], + it("parse --fallbackPolling", () => { + assertParseResult(["--fallbackPolling", "PriorityInterval", "0.ts"], { - errors: [{ - messageText: "Compiler option '--listFilesOnly' may not be used with '--build'.", - category: Diagnostics.Compiler_option_0_may_not_be_used_with_build.category, - code: Diagnostics.Compiler_option_0_may_not_be_used_with_build.code, - file: undefined, - start: undefined, - length: undefined, - }], - projects: ["."], - buildOptions: {}, - watchOptions: undefined, + errors: [], + fileNames: ["0.ts"], + options: {}, + watchOptions: { fallbackPolling: PollingWatchKind.PriorityInterval } }); }); - it("Parse multiple flags with input projects at the end", () => { - // --lib es5,es2015.symbol.wellknown --target es5 0.ts - assertParseResult(["--force", "--verbose", "src", "tests"], + it("parse --synchronousWatchDirectory", () => { + assertParseResult(["--synchronousWatchDirectory", "0.ts"], { errors: [], - projects: ["src", "tests"], - buildOptions: { force: true, verbose: true }, - watchOptions: undefined, + fileNames: ["0.ts"], + options: {}, + watchOptions: { synchronousWatchDirectory: true } }); }); - it("Parse multiple flags with input projects in the middle", () => { - // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown - assertParseResult(["--force", "src", "tests", "--verbose"], + it("errors on missing argument to --fallbackPolling", () => { + assertParseResult(["0.ts", "--fallbackPolling"], { - errors: [], - projects: ["src", "tests"], - buildOptions: { force: true, verbose: true }, - watchOptions: undefined, + errors: [ + { + messageText: "Watch option 'fallbackPolling' requires a value of type string.", + category: Diagnostics.Watch_option_0_requires_a_value_of_type_1.category, + code: Diagnostics.Watch_option_0_requires_a_value_of_type_1.code, + file: undefined, + start: undefined, + length: undefined + }, + { + messageText: "Argument for '--fallbackPolling' option must be: 'fixedinterval', 'priorityinterval', 'dynamicpriority', 'fixedchunksize'.", + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + file: undefined, + start: undefined, + length: undefined + } + ], + fileNames: ["0.ts"], + options: {}, + watchOptions: { fallbackPolling: undefined } }); }); - it("Parse multiple flags with input projects in the beginning", () => { - // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown - assertParseResult(["src", "tests", "--force", "--verbose"], + it("parse --excludeDirectories", () => { + assertParseResult(["--excludeDirectories", "**/temp", "0.ts"], { errors: [], - projects: ["src", "tests"], - buildOptions: { force: true, verbose: true }, - watchOptions: undefined, + fileNames: ["0.ts"], + options: {}, + watchOptions: { excludeDirectories: ["**/temp"] } }); }); - it("parse build with --incremental", () => { - // --lib es6 0.ts - assertParseResult(["--incremental", "tests"], + it("errors on invalid excludeDirectories", () => { + assertParseResult(["--excludeDirectories", "**/../*", "0.ts"], { - errors: [], - projects: ["tests"], - buildOptions: { incremental: true }, - watchOptions: undefined, + errors: [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: undefined, + start: undefined, + length: undefined + } + ], + fileNames: ["0.ts"], + options: {}, + watchOptions: { excludeDirectories: [] } }); }); - it("parse build with --locale en-us", () => { - // --lib es6 0.ts - assertParseResult(["--locale", "en-us", "src"], + it("parse --excludeFiles", () => { + assertParseResult(["--excludeFiles", "**/temp/*.ts", "0.ts"], { errors: [], - projects: ["src"], - buildOptions: { locale: "en-us" }, - watchOptions: undefined, + fileNames: ["0.ts"], + options: {}, + watchOptions: { excludeFiles: ["**/temp/*.ts"] } }); }); - it("parse build with --tsBuildInfoFile", () => { - // --lib es6 0.ts - assertParseResult(["--tsBuildInfoFile", "build.tsbuildinfo", "tests"], + it("errors on invalid excludeFiles", () => { + assertParseResult(["--excludeFiles", "**/../*", "0.ts"], { - errors: [{ - messageText: "Compiler option '--tsBuildInfoFile' may not be used with '--build'.", - category: Diagnostics.Compiler_option_0_may_not_be_used_with_build.category, - code: Diagnostics.Compiler_option_0_may_not_be_used_with_build.code, - file: undefined, - start: undefined, - length: undefined - }], - projects: ["build.tsbuildinfo", "tests"], - buildOptions: {}, - watchOptions: undefined, + errors: [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: undefined, + start: undefined, + length: undefined + } + ], + fileNames: ["0.ts"], + options: {}, + watchOptions: { excludeFiles: [] } }); }); + }); +}); + +describe("unittests:: config:: commandLineParsing:: parseBuildOptions", () => { + function assertParseResult(commandLine: string[], expectedParsedBuildCommand: ParsedBuildCommand) { + const parsed = parseBuildCommand(commandLine); + assert.deepEqual(parsed.buildOptions, expectedParsedBuildCommand.buildOptions); + assert.deepEqual(parsed.watchOptions, expectedParsedBuildCommand.watchOptions); + + const parsedErrors = parsed.errors; + const expectedErrors = expectedParsedBuildCommand.errors; + assert.isTrue(parsedErrors.length === expectedErrors.length, `Expected error: ${JSON.stringify(expectedErrors)}. Actual error: ${JSON.stringify(parsedErrors)}.`); + for (let i = 0; i < parsedErrors.length; i++) { + const parsedError = parsedErrors[i]; + const expectedError = expectedErrors[i]; + assert.equal(parsedError.code, expectedError.code); + assert.equal(parsedError.category, expectedError.category); + assert.equal(parsedError.messageText, expectedError.messageText); + } + + const parsedProjects = parsed.projects; + const expectedProjects = expectedParsedBuildCommand.projects; + assert.deepEqual(parsedProjects, expectedProjects, `Expected projects: [${JSON.stringify(expectedProjects)}]. Actual projects: [${JSON.stringify(parsedProjects)}].`); + } + it("parse build without any options ", () => { + // --lib es6 0.ts + assertParseResult([], + { + errors: [], + projects: ["."], + buildOptions: {}, + watchOptions: undefined + }); + }); + + it("Parse multiple options", () => { + // --lib es5,es2015.symbol.wellknown 0.ts + assertParseResult(["--verbose", "--force", "tests"], + { + errors: [], + projects: ["tests"], + buildOptions: { verbose: true, force: true }, + watchOptions: undefined + }); + }); - it("reports other common 'may not be used with --build' flags", () => { - const buildFlags = ["--declaration", "--strict"]; + it("Parse option with invalid option ", () => { + // --lib es5,invalidOption 0.ts + assertParseResult(["--verbose", "--invalidOption"], + { + errors: [{ + messageText: "Unknown build option '--invalidOption'.", + category: Diagnostics.Unknown_build_option_0.category, + code: Diagnostics.Unknown_build_option_0.code, + file: undefined, + start: undefined, + length: undefined, + }], + projects: ["."], + buildOptions: { verbose: true }, + watchOptions: undefined + }); + }); - assertParseResult(buildFlags, { - errors: buildFlags.map(buildFlag => ({ - messageText: `Compiler option '${buildFlag}' may not be used with '--build'.`, + it("parse build with listFilesOnly ", () => { + // --lib es6 0.ts + assertParseResult(["--listFilesOnly"], + { + errors: [{ + messageText: "Compiler option '--listFilesOnly' may not be used with '--build'.", category: Diagnostics.Compiler_option_0_may_not_be_used_with_build.category, code: Diagnostics.Compiler_option_0_may_not_be_used_with_build.code, file: undefined, start: undefined, - length: undefined - })), - buildOptions: {}, + length: undefined, + }], projects: ["."], + buildOptions: {}, watchOptions: undefined, }); - }); - - describe("Combining options that make no sense together", () => { - function verifyInvalidCombination(flag1: keyof BuildOptions, flag2: keyof BuildOptions) { - it(`--${flag1} and --${flag2} together is invalid`, () => { - // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown - assertParseResult([`--${flag1}`, `--${flag2}`], - { - errors: [{ - messageText: `Options '${flag1}' and '${flag2}' cannot be combined.`, - category: Diagnostics.Options_0_and_1_cannot_be_combined.category, - code: Diagnostics.Options_0_and_1_cannot_be_combined.code, - file: undefined, - start: undefined, - length: undefined, - }], - projects: ["."], - buildOptions: { [flag1]: true, [flag2]: true }, - watchOptions: undefined, - }); - }); - } - - verifyInvalidCombination("clean", "force"); - verifyInvalidCombination("clean", "verbose"); - verifyInvalidCombination("clean", "watch"); - verifyInvalidCombination("watch", "dry"); - }); + }); - describe("Watch options", () => { - it("parse --watchFile", () => { - assertParseResult(["--watchFile", "UseFsEvents", "--verbose"], - { - errors: [], - projects: ["."], - buildOptions: { verbose: true }, - watchOptions: { watchFile: WatchFileKind.UseFsEvents } - }); + it("Parse multiple flags with input projects at the end", () => { + // --lib es5,es2015.symbol.wellknown --target es5 0.ts + assertParseResult(["--force", "--verbose", "src", "tests"], + { + errors: [], + projects: ["src", "tests"], + buildOptions: { force: true, verbose: true }, + watchOptions: undefined, }); + }); - it("parse --watchDirectory", () => { - assertParseResult(["--watchDirectory", "FixedPollingInterval", "--verbose"], - { - errors: [], - projects: ["."], - buildOptions: { verbose: true }, - watchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval } - }); + it("Parse multiple flags with input projects in the middle", () => { + // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown + assertParseResult(["--force", "src", "tests", "--verbose"], + { + errors: [], + projects: ["src", "tests"], + buildOptions: { force: true, verbose: true }, + watchOptions: undefined, }); + }); - it("parse --fallbackPolling", () => { - assertParseResult(["--fallbackPolling", "PriorityInterval", "--verbose"], - { - errors: [], - projects: ["."], - buildOptions: { verbose: true }, - watchOptions: { fallbackPolling: PollingWatchKind.PriorityInterval } - }); + it("Parse multiple flags with input projects in the beginning", () => { + // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown + assertParseResult(["src", "tests", "--force", "--verbose"], + { + errors: [], + projects: ["src", "tests"], + buildOptions: { force: true, verbose: true }, + watchOptions: undefined, }); + }); - it("parse --synchronousWatchDirectory", () => { - assertParseResult(["--synchronousWatchDirectory", "--verbose"], - { - errors: [], - projects: ["."], - buildOptions: { verbose: true }, - watchOptions: { synchronousWatchDirectory: true } - }); + it("parse build with --incremental", () => { + // --lib es6 0.ts + assertParseResult(["--incremental", "tests"], + { + errors: [], + projects: ["tests"], + buildOptions: { incremental: true }, + watchOptions: undefined, }); + }); - it("errors on missing argument", () => { - assertParseResult(["--verbose", "--fallbackPolling"], - { - errors: [ - { - messageText: "Watch option 'fallbackPolling' requires a value of type string.", - category: Diagnostics.Watch_option_0_requires_a_value_of_type_1.category, - code: Diagnostics.Watch_option_0_requires_a_value_of_type_1.code, - file: undefined, - start: undefined, - length: undefined - }, - { - messageText: "Argument for '--fallbackPolling' option must be: 'fixedinterval', 'priorityinterval', 'dynamicpriority', 'fixedchunksize'.", - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - file: undefined, - start: undefined, - length: undefined - } - ], - projects: ["."], - buildOptions: { verbose: true }, - watchOptions: { fallbackPolling: undefined } - }); + it("parse build with --locale en-us", () => { + // --lib es6 0.ts + assertParseResult(["--locale", "en-us", "src"], + { + errors: [], + projects: ["src"], + buildOptions: { locale: "en-us" }, + watchOptions: undefined, }); + }); - it("errors on invalid excludeDirectories", () => { - assertParseResult(["--excludeDirectories", "**/../*"], - { - errors: [ - { - messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, - category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, - code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, - file: undefined, - start: undefined, - length: undefined - } - ], - projects: ["."], - buildOptions: {}, - watchOptions: { excludeDirectories: [] } - }); + it("parse build with --tsBuildInfoFile", () => { + // --lib es6 0.ts + assertParseResult(["--tsBuildInfoFile", "build.tsbuildinfo", "tests"], + { + errors: [{ + messageText: "Compiler option '--tsBuildInfoFile' may not be used with '--build'.", + category: Diagnostics.Compiler_option_0_may_not_be_used_with_build.category, + code: Diagnostics.Compiler_option_0_may_not_be_used_with_build.code, + file: undefined, + start: undefined, + length: undefined + }], + projects: ["build.tsbuildinfo", "tests"], + buildOptions: {}, + watchOptions: undefined, }); + }); - it("parse --excludeFiles", () => { - assertParseResult(["--excludeFiles", "**/temp/*.ts"], - { - errors: [], - projects: ["."], - buildOptions: {}, - watchOptions: { excludeFiles: ["**/temp/*.ts"] } - }); - }); + it("reports other common 'may not be used with --build' flags", () => { + const buildFlags = ["--declaration", "--strict"]; + + assertParseResult(buildFlags, { + errors: buildFlags.map(buildFlag => ({ + messageText: `Compiler option '${buildFlag}' may not be used with '--build'.`, + category: Diagnostics.Compiler_option_0_may_not_be_used_with_build.category, + code: Diagnostics.Compiler_option_0_may_not_be_used_with_build.code, + file: undefined, + start: undefined, + length: undefined + })), + buildOptions: {}, + projects: ["."], + watchOptions: undefined, + }); + }); - it("errors on invalid excludeFiles", () => { - assertParseResult(["--excludeFiles", "**/../*"], + describe("Combining options that make no sense together", () => { + function verifyInvalidCombination(flag1: keyof BuildOptions, flag2: keyof BuildOptions) { + it(`--${flag1} and --${flag2} together is invalid`, () => { + // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown + assertParseResult([`--${flag1}`, `--${flag2}`], { - errors: [ - { - messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, - category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, - code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, - file: undefined, - start: undefined, - length: undefined - } - ], + errors: [{ + messageText: `Options '${flag1}' and '${flag2}' cannot be combined.`, + category: Diagnostics.Options_0_and_1_cannot_be_combined.category, + code: Diagnostics.Options_0_and_1_cannot_be_combined.code, + file: undefined, + start: undefined, + length: undefined, + }], projects: ["."], - buildOptions: {}, - watchOptions: { excludeFiles: [] } + buildOptions: { [flag1]: true, [flag2]: true }, + watchOptions: undefined, }); }); + } + + verifyInvalidCombination("clean", "force"); + verifyInvalidCombination("clean", "verbose"); + verifyInvalidCombination("clean", "watch"); + verifyInvalidCombination("watch", "dry"); + }); + + describe("Watch options", () => { + it("parse --watchFile", () => { + assertParseResult(["--watchFile", "UseFsEvents", "--verbose"], + { + errors: [], + projects: ["."], + buildOptions: { verbose: true }, + watchOptions: { watchFile: WatchFileKind.UseFsEvents } + }); + }); + + it("parse --watchDirectory", () => { + assertParseResult(["--watchDirectory", "FixedPollingInterval", "--verbose"], + { + errors: [], + projects: ["."], + buildOptions: { verbose: true }, + watchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval } + }); + }); + + it("parse --fallbackPolling", () => { + assertParseResult(["--fallbackPolling", "PriorityInterval", "--verbose"], + { + errors: [], + projects: ["."], + buildOptions: { verbose: true }, + watchOptions: { fallbackPolling: PollingWatchKind.PriorityInterval } + }); + }); + + it("parse --synchronousWatchDirectory", () => { + assertParseResult(["--synchronousWatchDirectory", "--verbose"], + { + errors: [], + projects: ["."], + buildOptions: { verbose: true }, + watchOptions: { synchronousWatchDirectory: true } + }); + }); + + it("errors on missing argument", () => { + assertParseResult(["--verbose", "--fallbackPolling"], + { + errors: [ + { + messageText: "Watch option 'fallbackPolling' requires a value of type string.", + category: Diagnostics.Watch_option_0_requires_a_value_of_type_1.category, + code: Diagnostics.Watch_option_0_requires_a_value_of_type_1.code, + file: undefined, + start: undefined, + length: undefined + }, + { + messageText: "Argument for '--fallbackPolling' option must be: 'fixedinterval', 'priorityinterval', 'dynamicpriority', 'fixedchunksize'.", + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + file: undefined, + start: undefined, + length: undefined + } + ], + projects: ["."], + buildOptions: { verbose: true }, + watchOptions: { fallbackPolling: undefined } + }); + }); + + it("errors on invalid excludeDirectories", () => { + assertParseResult(["--excludeDirectories", "**/../*"], + { + errors: [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: undefined, + start: undefined, + length: undefined + } + ], + projects: ["."], + buildOptions: {}, + watchOptions: { excludeDirectories: [] } + }); + }); + + it("parse --excludeFiles", () => { + assertParseResult(["--excludeFiles", "**/temp/*.ts"], + { + errors: [], + projects: ["."], + buildOptions: {}, + watchOptions: { excludeFiles: ["**/temp/*.ts"] } + }); + }); + + it("errors on invalid excludeFiles", () => { + assertParseResult(["--excludeFiles", "**/../*"], + { + errors: [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: undefined, + start: undefined, + length: undefined + } + ], + projects: ["."], + buildOptions: {}, + watchOptions: { excludeFiles: [] } + }); }); }); +}); } diff --git a/src/testRunner/unittests/config/configurationExtension.ts b/src/testRunner/unittests/config/configurationExtension.ts index ad906411ffbca..c63bac572ae5a 100644 --- a/src/testRunner/unittests/config/configurationExtension.ts +++ b/src/testRunner/unittests/config/configurationExtension.ts @@ -1,354 +1,354 @@ namespace ts { - function createFileSystem(ignoreCase: boolean, cwd: string, root: string) { - return new vfs.FileSystem(ignoreCase, { - cwd, - files: { - [root]: { - "dev/node_modules/config-box/package.json": JSON.stringify({ - name: "config-box", - version: "1.0.0", - tsconfig: "./strict.json" - }), - "dev/node_modules/config-box/strict.json": JSON.stringify({ - compilerOptions: { - strict: true, - } - }), - "dev/node_modules/config-box/unstrict.json": JSON.stringify({ - compilerOptions: { - strict: false, - } - }), - "dev/tsconfig.extendsBox.json": JSON.stringify({ - extends: "config-box", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.extendsStrict.json": JSON.stringify({ - extends: "config-box/strict", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.extendsUnStrict.json": JSON.stringify({ - extends: "config-box/unstrict", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.extendsStrictExtension.json": JSON.stringify({ - extends: "config-box/strict.json", - files: [ - "main.ts", - ] - }), - "dev/node_modules/config-box-implied/package.json": JSON.stringify({ - name: "config-box-implied", - version: "1.0.0", - }), - "dev/node_modules/config-box-implied/tsconfig.json": JSON.stringify({ - compilerOptions: { - strict: true, - } - }), - "dev/node_modules/config-box-implied/unstrict/tsconfig.json": JSON.stringify({ - compilerOptions: { - strict: false, - } - }), - "dev/tsconfig.extendsBoxImplied.json": JSON.stringify({ - extends: "config-box-implied", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.extendsBoxImpliedUnstrict.json": JSON.stringify({ - extends: "config-box-implied/unstrict", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.extendsBoxImpliedUnstrictExtension.json": JSON.stringify({ - extends: "config-box-implied/unstrict/tsconfig", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.extendsBoxImpliedPath.json": JSON.stringify({ - extends: "config-box-implied/tsconfig.json", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.json": JSON.stringify({ - extends: "./configs/base", - files: [ - "main.ts", - "supplemental.ts" - ] - }), - "dev/tsconfig.nostrictnull.json": JSON.stringify({ - extends: "./tsconfig", - compilerOptions: { - strictNullChecks: false - } - }), - "dev/configs/base.json": JSON.stringify({ - compilerOptions: { - allowJs: true, - noImplicitAny: true, - strictNullChecks: true - } - }), - "dev/configs/tests.json": JSON.stringify({ - compilerOptions: { - preserveConstEnums: true, - removeComments: false, - sourceMap: true - }, - exclude: [ - "../tests/baselines", - "../tests/scenarios" - ], - include: [ - "../tests/**/*.ts" - ] - }), - "dev/circular.json": JSON.stringify({ - extends: "./circular2", - compilerOptions: { - module: "amd" - } - }), - "dev/circular2.json": JSON.stringify({ - extends: "./circular", - compilerOptions: { - module: "commonjs" - } - }), - "dev/missing.json": JSON.stringify({ - extends: "./missing2", - compilerOptions: { - types: [] - } - }), - "dev/failure.json": JSON.stringify({ - extends: "./failure2.json", - compilerOptions: { - typeRoots: [] - } - }), - "dev/failure2.json": JSON.stringify({ - excludes: ["*.js"] - }), - "dev/configs/first.json": JSON.stringify({ - extends: "./base", - compilerOptions: { - module: "commonjs" - }, - files: ["../main.ts"] - }), - "dev/configs/second.json": JSON.stringify({ - extends: "./base", - compilerOptions: { - module: "amd" - }, - include: ["../supplemental.*"] - }), - "dev/configs/third.json": JSON.stringify({ - extends: "./second", - compilerOptions: { - module: null // eslint-disable-line no-null/no-null - }, - include: ["../supplemental.*"] - }), - "dev/configs/fourth.json": JSON.stringify({ - extends: "./third", - compilerOptions: { - module: "system" - }, - include: null, // eslint-disable-line no-null/no-null - files: ["../main.ts"] - }), - "dev/configs/fifth.json": JSON.stringify({ - extends: "./fourth", - include: ["../tests/utils.ts"], - files: [] - }), - "dev/extends.json": JSON.stringify({ extends: 42 }), - "dev/extends2.json": JSON.stringify({ extends: "configs/base" }), - "dev/main.ts": "", - "dev/supplemental.ts": "", - "dev/tests/unit/spec.ts": "", - "dev/tests/utils.ts": "", - "dev/tests/scenarios/first.json": "", - "dev/tests/baselines/first/output.ts": "" - } +function createFileSystem(ignoreCase: boolean, cwd: string, root: string) { + return new vfs.FileSystem(ignoreCase, { + cwd, + files: { + [root]: { + "dev/node_modules/config-box/package.json": JSON.stringify({ + name: "config-box", + version: "1.0.0", + tsconfig: "./strict.json" + }), + "dev/node_modules/config-box/strict.json": JSON.stringify({ + compilerOptions: { + strict: true, + } + }), + "dev/node_modules/config-box/unstrict.json": JSON.stringify({ + compilerOptions: { + strict: false, + } + }), + "dev/tsconfig.extendsBox.json": JSON.stringify({ + extends: "config-box", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.extendsStrict.json": JSON.stringify({ + extends: "config-box/strict", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.extendsUnStrict.json": JSON.stringify({ + extends: "config-box/unstrict", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.extendsStrictExtension.json": JSON.stringify({ + extends: "config-box/strict.json", + files: [ + "main.ts", + ] + }), + "dev/node_modules/config-box-implied/package.json": JSON.stringify({ + name: "config-box-implied", + version: "1.0.0", + }), + "dev/node_modules/config-box-implied/tsconfig.json": JSON.stringify({ + compilerOptions: { + strict: true, + } + }), + "dev/node_modules/config-box-implied/unstrict/tsconfig.json": JSON.stringify({ + compilerOptions: { + strict: false, + } + }), + "dev/tsconfig.extendsBoxImplied.json": JSON.stringify({ + extends: "config-box-implied", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.extendsBoxImpliedUnstrict.json": JSON.stringify({ + extends: "config-box-implied/unstrict", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.extendsBoxImpliedUnstrictExtension.json": JSON.stringify({ + extends: "config-box-implied/unstrict/tsconfig", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.extendsBoxImpliedPath.json": JSON.stringify({ + extends: "config-box-implied/tsconfig.json", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.json": JSON.stringify({ + extends: "./configs/base", + files: [ + "main.ts", + "supplemental.ts" + ] + }), + "dev/tsconfig.nostrictnull.json": JSON.stringify({ + extends: "./tsconfig", + compilerOptions: { + strictNullChecks: false + } + }), + "dev/configs/base.json": JSON.stringify({ + compilerOptions: { + allowJs: true, + noImplicitAny: true, + strictNullChecks: true + } + }), + "dev/configs/tests.json": JSON.stringify({ + compilerOptions: { + preserveConstEnums: true, + removeComments: false, + sourceMap: true + }, + exclude: [ + "../tests/baselines", + "../tests/scenarios" + ], + include: [ + "../tests/**/*.ts" + ] + }), + "dev/circular.json": JSON.stringify({ + extends: "./circular2", + compilerOptions: { + module: "amd" + } + }), + "dev/circular2.json": JSON.stringify({ + extends: "./circular", + compilerOptions: { + module: "commonjs" + } + }), + "dev/missing.json": JSON.stringify({ + extends: "./missing2", + compilerOptions: { + types: [] + } + }), + "dev/failure.json": JSON.stringify({ + extends: "./failure2.json", + compilerOptions: { + typeRoots: [] + } + }), + "dev/failure2.json": JSON.stringify({ + excludes: ["*.js"] + }), + "dev/configs/first.json": JSON.stringify({ + extends: "./base", + compilerOptions: { + module: "commonjs" + }, + files: ["../main.ts"] + }), + "dev/configs/second.json": JSON.stringify({ + extends: "./base", + compilerOptions: { + module: "amd" + }, + include: ["../supplemental.*"] + }), + "dev/configs/third.json": JSON.stringify({ + extends: "./second", + compilerOptions: { + module: null // eslint-disable-line no-null/no-null + }, + include: ["../supplemental.*"] + }), + "dev/configs/fourth.json": JSON.stringify({ + extends: "./third", + compilerOptions: { + module: "system" + }, + include: null, // eslint-disable-line no-null/no-null + files: ["../main.ts"] + }), + "dev/configs/fifth.json": JSON.stringify({ + extends: "./fourth", + include: ["../tests/utils.ts"], + files: [] + }), + "dev/extends.json": JSON.stringify({ extends: 42 }), + "dev/extends2.json": JSON.stringify({ extends: "configs/base" }), + "dev/main.ts": "", + "dev/supplemental.ts": "", + "dev/tests/unit/spec.ts": "", + "dev/tests/utils.ts": "", + "dev/tests/scenarios/first.json": "", + "dev/tests/baselines/first/output.ts": "" } - }); - } + } + }); +} - const caseInsensitiveBasePath = "c:/dev/"; - const caseInsensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ true, caseInsensitiveBasePath, "c:/")); +const caseInsensitiveBasePath = "c:/dev/"; +const caseInsensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ true, caseInsensitiveBasePath, "c:/")); - const caseSensitiveBasePath = "/dev/"; - const caseSensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ false, caseSensitiveBasePath, "/")); +const caseSensitiveBasePath = "/dev/"; +const caseSensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ false, caseSensitiveBasePath, "/")); - function verifyDiagnostics(actual: Diagnostic[], expected: { code: number; messageText: string; }[]) { - assert.isTrue(expected.length === actual.length, `Expected error: ${JSON.stringify(expected)}. Actual error: ${JSON.stringify(actual)}.`); - for (let i = 0; i < actual.length; i++) { - const actualError = actual[i]; - const expectedError = expected[i]; - assert.equal(actualError.code, expectedError.code, "Error code mismatch"); - assert.equal(actualError.category, DiagnosticCategory.Error, "Category mismatch"); // Should always be error - assert.equal(flattenDiagnosticMessageText(actualError.messageText, "\n"), expectedError.messageText); - } +function verifyDiagnostics(actual: Diagnostic[], expected: { code: number; messageText: string; }[]) { + assert.isTrue(expected.length === actual.length, `Expected error: ${JSON.stringify(expected)}. Actual error: ${JSON.stringify(actual)}.`); + for (let i = 0; i < actual.length; i++) { + const actualError = actual[i]; + const expectedError = expected[i]; + assert.equal(actualError.code, expectedError.code, "Error code mismatch"); + assert.equal(actualError.category, DiagnosticCategory.Error, "Category mismatch"); // Should always be error + assert.equal(flattenDiagnosticMessageText(actualError.messageText, "\n"), expectedError.messageText); } +} - describe("unittests:: config:: configurationExtension", () => { - forEach<[string, string, fakes.ParseConfigHost], void>([ - ["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost], - ["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost] - ], ([testName, basePath, host]) => { - function getParseCommandLine(entry: string) { - const {config, error} = readConfigFile(entry, name => host.readFile(name)); - assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n")); - return parseJsonConfigFileContent(config, host, basePath, {}, entry); - } +describe("unittests:: config:: configurationExtension", () => { + forEach<[string, string, fakes.ParseConfigHost], void>([ + ["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost], + ["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost] + ], ([testName, basePath, host]) => { + function getParseCommandLine(entry: string) { + const {config, error} = readConfigFile(entry, name => host.readFile(name)); + assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n")); + return parseJsonConfigFileContent(config, host, basePath, {}, entry); + } - function getParseCommandLineJsonSourceFile(entry: string) { - const jsonSourceFile = readJsonConfigFile(entry, name => host.readFile(name)); - assert(jsonSourceFile.endOfFileToken && !jsonSourceFile.parseDiagnostics.length, flattenDiagnosticMessageText(jsonSourceFile.parseDiagnostics[0] && jsonSourceFile.parseDiagnostics[0].messageText, "\n")); - return { - jsonSourceFile, - parsed: parseJsonSourceFileConfigFileContent(jsonSourceFile, host, basePath, {}, entry) - }; - } + function getParseCommandLineJsonSourceFile(entry: string) { + const jsonSourceFile = readJsonConfigFile(entry, name => host.readFile(name)); + assert(jsonSourceFile.endOfFileToken && !jsonSourceFile.parseDiagnostics.length, flattenDiagnosticMessageText(jsonSourceFile.parseDiagnostics[0] && jsonSourceFile.parseDiagnostics[0].messageText, "\n")); + return { + jsonSourceFile, + parsed: parseJsonSourceFileConfigFileContent(jsonSourceFile, host, basePath, {}, entry) + }; + } - function testSuccess(name: string, entry: string, expected: CompilerOptions, expectedFiles: string[]) { - expected.configFilePath = entry; - it(name, () => { - const parsed = getParseCommandLine(entry); - assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); - assert.deepEqual(parsed.options, expected); - assert.deepEqual(parsed.fileNames, expectedFiles); - }); + function testSuccess(name: string, entry: string, expected: CompilerOptions, expectedFiles: string[]) { + expected.configFilePath = entry; + it(name, () => { + const parsed = getParseCommandLine(entry); + assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); + assert.deepEqual(parsed.options, expected); + assert.deepEqual(parsed.fileNames, expectedFiles); + }); - it(name + " with jsonSourceFile", () => { - const { parsed, jsonSourceFile } = getParseCommandLineJsonSourceFile(entry); - assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); - assert.deepEqual(parsed.options, expected); - assert.equal(parsed.options.configFile, jsonSourceFile); - assert.deepEqual(parsed.fileNames, expectedFiles); - }); - } + it(name + " with jsonSourceFile", () => { + const { parsed, jsonSourceFile } = getParseCommandLineJsonSourceFile(entry); + assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); + assert.deepEqual(parsed.options, expected); + assert.equal(parsed.options.configFile, jsonSourceFile); + assert.deepEqual(parsed.fileNames, expectedFiles); + }); + } - function testFailure(name: string, entry: string, expectedDiagnostics: { code: number; messageText: string; }[]) { - it(name, () => { - const parsed = getParseCommandLine(entry); - verifyDiagnostics(parsed.errors, expectedDiagnostics); - }); + function testFailure(name: string, entry: string, expectedDiagnostics: { code: number; messageText: string; }[]) { + it(name, () => { + const parsed = getParseCommandLine(entry); + verifyDiagnostics(parsed.errors, expectedDiagnostics); + }); - it(name + " with jsonSourceFile", () => { - const { parsed } = getParseCommandLineJsonSourceFile(entry); - verifyDiagnostics(parsed.errors, expectedDiagnostics); - }); - } + it(name + " with jsonSourceFile", () => { + const { parsed } = getParseCommandLineJsonSourceFile(entry); + verifyDiagnostics(parsed.errors, expectedDiagnostics); + }); + } - describe(testName, () => { - testSuccess("can resolve an extension with a base extension", "tsconfig.json", { - allowJs: true, - noImplicitAny: true, - strictNullChecks: true, - }, [ - combinePaths(basePath, "main.ts"), - combinePaths(basePath, "supplemental.ts"), - ]); + describe(testName, () => { + testSuccess("can resolve an extension with a base extension", "tsconfig.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: true, + }, [ + combinePaths(basePath, "main.ts"), + combinePaths(basePath, "supplemental.ts"), + ]); - testSuccess("can resolve an extension with a base extension that overrides options", "tsconfig.nostrictnull.json", { - allowJs: true, - noImplicitAny: true, - strictNullChecks: false, - }, [ - combinePaths(basePath, "main.ts"), - combinePaths(basePath, "supplemental.ts"), - ]); + testSuccess("can resolve an extension with a base extension that overrides options", "tsconfig.nostrictnull.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: false, + }, [ + combinePaths(basePath, "main.ts"), + combinePaths(basePath, "supplemental.ts"), + ]); - testFailure("can report errors on circular imports", "circular.json", [ - { - code: 18000, - messageText: `Circularity detected while resolving configuration: ${[combinePaths(basePath, "circular.json"), combinePaths(basePath, "circular2.json"), combinePaths(basePath, "circular.json")].join(" -> ")}` - } - ]); + testFailure("can report errors on circular imports", "circular.json", [ + { + code: 18000, + messageText: `Circularity detected while resolving configuration: ${[combinePaths(basePath, "circular.json"), combinePaths(basePath, "circular2.json"), combinePaths(basePath, "circular.json")].join(" -> ")}` + } + ]); - testFailure("can report missing configurations", "missing.json", [{ - code: 6053, - messageText: `File './missing2' not found.` - }]); + testFailure("can report missing configurations", "missing.json", [{ + code: 6053, + messageText: `File './missing2' not found.` + }]); - testFailure("can report errors in extended configs", "failure.json", [{ - code: 6114, - messageText: `Unknown option 'excludes'. Did you mean 'exclude'?` - }]); + testFailure("can report errors in extended configs", "failure.json", [{ + code: 6114, + messageText: `Unknown option 'excludes'. Did you mean 'exclude'?` + }]); - testFailure("can error when 'extends' is not a string", "extends.json", [{ - code: 5024, - messageText: `Compiler option 'extends' requires a value of type string.` - }]); + testFailure("can error when 'extends' is not a string", "extends.json", [{ + code: 5024, + messageText: `Compiler option 'extends' requires a value of type string.` + }]); - testSuccess("can overwrite compiler options using extended 'null'", "configs/third.json", { - allowJs: true, - noImplicitAny: true, - strictNullChecks: true, - module: undefined // Technically, this is distinct from the key never being set; but within the compiler we don't make the distinction - }, [ - combinePaths(basePath, "supplemental.ts") - ]); + testSuccess("can overwrite compiler options using extended 'null'", "configs/third.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: true, + module: undefined // Technically, this is distinct from the key never being set; but within the compiler we don't make the distinction + }, [ + combinePaths(basePath, "supplemental.ts") + ]); - testSuccess("can overwrite top-level options using extended 'null'", "configs/fourth.json", { - allowJs: true, - noImplicitAny: true, - strictNullChecks: true, - module: ModuleKind.System - }, [ - combinePaths(basePath, "main.ts") - ]); + testSuccess("can overwrite top-level options using extended 'null'", "configs/fourth.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: true, + module: ModuleKind.System + }, [ + combinePaths(basePath, "main.ts") + ]); - testSuccess("can overwrite top-level files using extended []", "configs/fifth.json", { - allowJs: true, - noImplicitAny: true, - strictNullChecks: true, - module: ModuleKind.System - }, [ - combinePaths(basePath, "tests/utils.ts") - ]); + testSuccess("can overwrite top-level files using extended []", "configs/fifth.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: true, + module: ModuleKind.System + }, [ + combinePaths(basePath, "tests/utils.ts") + ]); - describe("finding extended configs from node_modules", () => { - testSuccess("can lookup via tsconfig field", "tsconfig.extendsBox.json", { strict: true }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via package-relative path", "tsconfig.extendsStrict.json", { strict: true }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via non-redirected-to package-relative path", "tsconfig.extendsUnStrict.json", { strict: false }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via package-relative path with extension", "tsconfig.extendsStrictExtension.json", { strict: true }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via an implicit tsconfig", "tsconfig.extendsBoxImplied.json", { strict: true }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via an implicit tsconfig in a package-relative directory", "tsconfig.extendsBoxImpliedUnstrict.json", { strict: false }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via an implicit tsconfig in a package-relative directory with name", "tsconfig.extendsBoxImpliedUnstrictExtension.json", { strict: false }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via an implicit tsconfig in a package-relative directory with extension", "tsconfig.extendsBoxImpliedPath.json", { strict: true }, [combinePaths(basePath, "main.ts")]); - }); + describe("finding extended configs from node_modules", () => { + testSuccess("can lookup via tsconfig field", "tsconfig.extendsBox.json", { strict: true }, [combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via package-relative path", "tsconfig.extendsStrict.json", { strict: true }, [combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via non-redirected-to package-relative path", "tsconfig.extendsUnStrict.json", { strict: false }, [combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via package-relative path with extension", "tsconfig.extendsStrictExtension.json", { strict: true }, [combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via an implicit tsconfig", "tsconfig.extendsBoxImplied.json", { strict: true }, [combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via an implicit tsconfig in a package-relative directory", "tsconfig.extendsBoxImpliedUnstrict.json", { strict: false }, [combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via an implicit tsconfig in a package-relative directory with name", "tsconfig.extendsBoxImpliedUnstrictExtension.json", { strict: false }, [combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via an implicit tsconfig in a package-relative directory with extension", "tsconfig.extendsBoxImpliedPath.json", { strict: true }, [combinePaths(basePath, "main.ts")]); + }); - it("adds extendedSourceFiles only once", () => { - const sourceFile = readJsonConfigFile("configs/fourth.json", (path) => host.readFile(path)); - const dir = combinePaths(basePath, "configs"); - const expected = [ - combinePaths(dir, "third.json"), - combinePaths(dir, "second.json"), - combinePaths(dir, "base.json"), - ]; - parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json"); - assert.deepEqual(sourceFile.extendedSourceFiles, expected); - parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json"); - assert.deepEqual(sourceFile.extendedSourceFiles, expected); - }); + it("adds extendedSourceFiles only once", () => { + const sourceFile = readJsonConfigFile("configs/fourth.json", (path) => host.readFile(path)); + const dir = combinePaths(basePath, "configs"); + const expected = [ + combinePaths(dir, "third.json"), + combinePaths(dir, "second.json"), + combinePaths(dir, "base.json"), + ]; + parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json"); + assert.deepEqual(sourceFile.extendedSourceFiles, expected); + parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json"); + assert.deepEqual(sourceFile.extendedSourceFiles, expected); }); }); }); +}); } diff --git a/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts b/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts index 96da312068abd..d38bd91dbbe57 100644 --- a/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts +++ b/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts @@ -1,579 +1,579 @@ namespace ts { - describe("unittests:: config:: convertCompilerOptionsFromJson", () => { - const formatDiagnosticHost: FormatDiagnosticsHost = { - getCurrentDirectory: () => "/apath/", - getCanonicalFileName: createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ true), - getNewLine: () => "\n" - }; - - interface ExpectedResultWithParsingSuccess { - compilerOptions: CompilerOptions; - errors: readonly Diagnostic[]; +describe("unittests:: config:: convertCompilerOptionsFromJson", () => { + const formatDiagnosticHost: FormatDiagnosticsHost = { + getCurrentDirectory: () => "/apath/", + getCanonicalFileName: createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ true), + getNewLine: () => "\n" + }; + + interface ExpectedResultWithParsingSuccess { + compilerOptions: CompilerOptions; + errors: readonly Diagnostic[]; + } + + interface ExpectedResultWithParsingFailure { + compilerOptions: CompilerOptions; + hasParseErrors: true; + } + + type ExpectedResult = ExpectedResultWithParsingSuccess | ExpectedResultWithParsingFailure; + + function isExpectedResultWithParsingFailure(expectedResult: ExpectedResult): expectedResult is ExpectedResultWithParsingFailure { + return !!(expectedResult as ExpectedResultWithParsingFailure).hasParseErrors; + } + + function assertCompilerOptions(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) { + assertCompilerOptionsWithJson(json, configFileName, expectedResult); + assertCompilerOptionsWithJsonNode(json, configFileName, expectedResult); + } + + function assertCompilerOptionsWithJson(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) { + const { options: actualCompilerOptions, errors: actualErrors } = convertCompilerOptionsFromJson(json.compilerOptions, "/apath/", configFileName); + + const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); + const expectedCompilerOptions = JSON.stringify({ ...expectedResult.compilerOptions, configFilePath: configFileName }); + assert.equal(parsedCompilerOptions, expectedCompilerOptions); + + verifyErrors(actualErrors, expectedResult.errors, /*ignoreLocation*/ true); + } + + function assertCompilerOptionsWithJsonNode(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) { + assertCompilerOptionsWithJsonText(JSON.stringify(json), configFileName, expectedResult); + } + + function assertCompilerOptionsWithJsonText(fileText: string, configFileName: string, expectedResult: ExpectedResult) { + const result = parseJsonText(configFileName, fileText); + assert(!!result.endOfFileToken); + assert.equal(!!result.parseDiagnostics.length, isExpectedResultWithParsingFailure(expectedResult)); + const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" })); + const { options: actualCompilerOptions, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); + expectedResult.compilerOptions.configFilePath = configFileName; + + const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); + const expectedCompilerOptions = JSON.stringify(expectedResult.compilerOptions); + assert.equal(parsedCompilerOptions, expectedCompilerOptions); + assert.equal(actualCompilerOptions.configFile, result); + + if (!isExpectedResultWithParsingFailure(expectedResult)) { + verifyErrors(actualParseErrors.filter(error => error.code !== Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code), expectedResult.errors); } - - interface ExpectedResultWithParsingFailure { - compilerOptions: CompilerOptions; - hasParseErrors: true; - } - - type ExpectedResult = ExpectedResultWithParsingSuccess | ExpectedResultWithParsingFailure; - - function isExpectedResultWithParsingFailure(expectedResult: ExpectedResult): expectedResult is ExpectedResultWithParsingFailure { - return !!(expectedResult as ExpectedResultWithParsingFailure).hasParseErrors; - } - - function assertCompilerOptions(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) { - assertCompilerOptionsWithJson(json, configFileName, expectedResult); - assertCompilerOptionsWithJsonNode(json, configFileName, expectedResult); - } - - function assertCompilerOptionsWithJson(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) { - const { options: actualCompilerOptions, errors: actualErrors } = convertCompilerOptionsFromJson(json.compilerOptions, "/apath/", configFileName); - - const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); - const expectedCompilerOptions = JSON.stringify({ ...expectedResult.compilerOptions, configFilePath: configFileName }); - assert.equal(parsedCompilerOptions, expectedCompilerOptions); - - verifyErrors(actualErrors, expectedResult.errors, /*ignoreLocation*/ true); - } - - function assertCompilerOptionsWithJsonNode(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) { - assertCompilerOptionsWithJsonText(JSON.stringify(json), configFileName, expectedResult); + } + + function verifyErrors(actualErrors: Diagnostic[], expectedErrors: readonly Diagnostic[], ignoreLocation?: boolean) { + assert.isTrue(expectedErrors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedErrors.map(getDiagnosticString), undefined, " ")}. Actual error: ${JSON.stringify(actualErrors.map(getDiagnosticString), undefined, " ")}.`); + for (let i = 0; i < actualErrors.length; i++) { + const actualError = actualErrors[i]; + const expectedError = expectedErrors[i]; + + assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`); + assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`); + if (!ignoreLocation) { + assert(actualError.file); + assert.isDefined(actualError.start); + assert(actualError.length); + } } - function assertCompilerOptionsWithJsonText(fileText: string, configFileName: string, expectedResult: ExpectedResult) { - const result = parseJsonText(configFileName, fileText); - assert(!!result.endOfFileToken); - assert.equal(!!result.parseDiagnostics.length, isExpectedResultWithParsingFailure(expectedResult)); - const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" })); - const { options: actualCompilerOptions, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); - expectedResult.compilerOptions.configFilePath = configFileName; - - const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); - const expectedCompilerOptions = JSON.stringify(expectedResult.compilerOptions); - assert.equal(parsedCompilerOptions, expectedCompilerOptions); - assert.equal(actualCompilerOptions.configFile, result); - - if (!isExpectedResultWithParsingFailure(expectedResult)) { - verifyErrors(actualParseErrors.filter(error => error.code !== Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code), expectedResult.errors); + function getDiagnosticString(diagnostic: Diagnostic) { + if (ignoreLocation) { + const { file, ...rest } = diagnostic; + diagnostic = { file: undefined, ...rest }; } + return formatDiagnostic(diagnostic, formatDiagnosticHost); } + } - function verifyErrors(actualErrors: Diagnostic[], expectedErrors: readonly Diagnostic[], ignoreLocation?: boolean) { - assert.isTrue(expectedErrors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedErrors.map(getDiagnosticString), undefined, " ")}. Actual error: ${JSON.stringify(actualErrors.map(getDiagnosticString), undefined, " ")}.`); - for (let i = 0; i < actualErrors.length; i++) { - const actualError = actualErrors[i]; - const expectedError = expectedErrors[i]; - - assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`); - assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`); - if (!ignoreLocation) { - assert(actualError.file); - assert.isDefined(actualError.start); - assert(actualError.length); + // tsconfig.json tests + it("Convert correctly format tsconfig.json to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: ["es5", "es2015.core", "es2015.symbol"] } + }, "tsconfig.json", + { + compilerOptions: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] + }, + errors: [] } + ); + }); - function getDiagnosticString(diagnostic: Diagnostic) { - if (ignoreLocation) { - const { file, ...rest } = diagnostic; - diagnostic = { file: undefined, ...rest }; + it("Convert correctly format tsconfig.json with allowJs is false to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + allowJs: false, + lib: ["es5", "es2015.core", "es2015.symbol"] } - return formatDiagnostic(diagnostic, formatDiagnosticHost); + }, "tsconfig.json", + { + compilerOptions: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + allowJs: false, + lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] + }, + errors: [] } - } - - // tsconfig.json tests - it("Convert correctly format tsconfig.json to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: ["es5", "es2015.core", "es2015.symbol"] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] - }, - errors: [] - } - ); - }); + ); + }); - it("Convert correctly format tsconfig.json with allowJs is false to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - allowJs: false, - lib: ["es5", "es2015.core", "es2015.symbol"] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - allowJs: false, - lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] - }, - errors: [] + it("Convert incorrect option of jsx to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + jsx: "" } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--jsx' option must be: 'preserve', 'react-native', 'react'.", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert incorrect option of jsx to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - jsx: "" - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--jsx' option must be: 'preserve', 'react-native', 'react'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert incorrect option of module to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "", + target: "es5", + noImplicitAny: false, + sourceMap: false, } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext'.", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert incorrect option of module to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "", - target: "es5", - noImplicitAny: false, - sourceMap: false, - } - }, "tsconfig.json", - { - compilerOptions: { - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert incorrect option of newLine to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + newLine: "", + target: "es5", + noImplicitAny: false, + sourceMap: false, } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--newLine' option must be: 'crlf', 'lf'.", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert incorrect option of newLine to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - newLine: "", - target: "es5", - noImplicitAny: false, - sourceMap: false, - } - }, "tsconfig.json", - { - compilerOptions: { - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--newLine' option must be: 'crlf', 'lf'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert incorrect option of target to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + target: "", + noImplicitAny: false, + sourceMap: false, } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'esnext'.", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert incorrect option of target to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - target: "", - noImplicitAny: false, - sourceMap: false, - } - }, "tsconfig.json", - { - compilerOptions: { - noImplicitAny: false, - sourceMap: false, - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'esnext'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert incorrect option of module-resolution to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + moduleResolution: "", + noImplicitAny: false, + sourceMap: false, } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--moduleResolution' option must be: 'node', 'classic'.", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert incorrect option of module-resolution to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - moduleResolution: "", - noImplicitAny: false, - sourceMap: false, - } - }, "tsconfig.json", - { - compilerOptions: { - noImplicitAny: false, - sourceMap: false, - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--moduleResolution' option must be: 'node', 'classic'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert incorrect option of libs to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: ["es5", "es2015.core", "incorrectLib"] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts"] + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert incorrect option of libs to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: ["es5", "es2015.core", "incorrectLib"] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts"] - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert empty string option of libs to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: ["es5", ""] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: ["lib.es5.d.ts"] + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert empty string option of libs to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: ["es5", ""] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: ["lib.es5.d.ts"] - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert empty string option of libs to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: [""] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: [] + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert empty string option of libs to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: [""] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: [] - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert trailing-whitespace string option of libs to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: [" "] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: [] + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert trailing-whitespace string option of libs to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: [" "] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: [] - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert empty option of libs to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: [] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: [] + }, + errors: [] + } + ); + }); - it("Convert empty option of libs to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: [] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: [] - }, - errors: [] + it("Convert incorrectly format tsconfig.json to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + modu: "commonjs", } - ); - }); + }, "tsconfig.json", + { + compilerOptions: {}, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Unknown compiler option 'modu'.", + code: Diagnostics.Unknown_compiler_option_0.code, + category: Diagnostics.Unknown_compiler_option_0.category + }] + } + ); + }); - it("Convert incorrectly format tsconfig.json to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - modu: "commonjs", - } - }, "tsconfig.json", - { - compilerOptions: {}, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Unknown compiler option 'modu'.", - code: Diagnostics.Unknown_compiler_option_0.code, - category: Diagnostics.Unknown_compiler_option_0.category - }] - } - ); - }); + it("Convert default tsconfig.json to compiler-options ", () => { + assertCompilerOptions({}, "tsconfig.json", + { + compilerOptions: {}, + errors: [] + } + ); + }); - it("Convert default tsconfig.json to compiler-options ", () => { - assertCompilerOptions({}, "tsconfig.json", - { - compilerOptions: {}, - errors: [] + it("Convert negative numbers in tsconfig.json ", () => { + assertCompilerOptions( + { + compilerOptions: { + allowJs: true, + maxNodeModuleJsDepth: -1 } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + allowJs: true, + maxNodeModuleJsDepth: -1 + }, + errors: [] + } + ); + }); - it("Convert negative numbers in tsconfig.json ", () => { - assertCompilerOptions( - { - compilerOptions: { - allowJs: true, - maxNodeModuleJsDepth: -1 - } - }, "tsconfig.json", - { - compilerOptions: { - allowJs: true, - maxNodeModuleJsDepth: -1 - }, - errors: [] + // jsconfig.json + it("Convert correctly format jsconfig.json to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: ["es5", "es2015.core", "es2015.symbol"] } - ); - }); + }, "jsconfig.json", + { + compilerOptions: { + allowJs: true, + maxNodeModuleJsDepth: 2, + allowSyntheticDefaultImports: true, + skipLibCheck: true, + noEmit: true, + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] + }, + errors: [] + } + ); + }); - // jsconfig.json - it("Convert correctly format jsconfig.json to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: ["es5", "es2015.core", "es2015.symbol"] - } - }, "jsconfig.json", - { - compilerOptions: { - allowJs: true, - maxNodeModuleJsDepth: 2, - allowSyntheticDefaultImports: true, - skipLibCheck: true, - noEmit: true, - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] - }, - errors: [] + it("Convert correctly format jsconfig.json with allowJs is false to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + allowJs: false, + lib: ["es5", "es2015.core", "es2015.symbol"] } - ); - }); + }, "jsconfig.json", + { + compilerOptions: { + allowJs: false, + maxNodeModuleJsDepth: 2, + allowSyntheticDefaultImports: true, + skipLibCheck: true, + noEmit: true, + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] + }, + errors: [] + } + ); + }); - it("Convert correctly format jsconfig.json with allowJs is false to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - allowJs: false, - lib: ["es5", "es2015.core", "es2015.symbol"] - } - }, "jsconfig.json", - { - compilerOptions: { - allowJs: false, - maxNodeModuleJsDepth: 2, - allowSyntheticDefaultImports: true, - skipLibCheck: true, - noEmit: true, - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] - }, - errors: [] + it("Convert incorrectly format jsconfig.json to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + modu: "commonjs", } - ); - }); - - it("Convert incorrectly format jsconfig.json to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - modu: "commonjs", - } - }, "jsconfig.json", + }, "jsconfig.json", + { + compilerOptions: { - compilerOptions: - { - allowJs: true, - maxNodeModuleJsDepth: 2, - allowSyntheticDefaultImports: true, - skipLibCheck: true, - noEmit: true - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Unknown compiler option 'modu'.", - code: Diagnostics.Unknown_compiler_option_0.code, - category: Diagnostics.Unknown_compiler_option_0.category - }] - } - ); - }); + allowJs: true, + maxNodeModuleJsDepth: 2, + allowSyntheticDefaultImports: true, + skipLibCheck: true, + noEmit: true + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Unknown compiler option 'modu'.", + code: Diagnostics.Unknown_compiler_option_0.code, + category: Diagnostics.Unknown_compiler_option_0.category + }] + } + ); + }); - it("Convert default jsconfig.json to compiler-options ", () => { - assertCompilerOptions({}, "jsconfig.json", + it("Convert default jsconfig.json to compiler-options ", () => { + assertCompilerOptions({}, "jsconfig.json", + { + compilerOptions: { - compilerOptions: - { - allowJs: true, - maxNodeModuleJsDepth: 2, - allowSyntheticDefaultImports: true, - skipLibCheck: true, - noEmit: true - }, - errors: [] - } - ); - }); + allowJs: true, + maxNodeModuleJsDepth: 2, + allowSyntheticDefaultImports: true, + skipLibCheck: true, + noEmit: true + }, + errors: [] + } + ); + }); - it("Convert tsconfig options when there are multiple invalid strings", () => { - assertCompilerOptionsWithJsonText(`{ + it("Convert tsconfig options when there are multiple invalid strings", () => { + assertCompilerOptionsWithJsonText(`{ "compilerOptions": { "target": "<%- options.useTsWithBabel ? 'esnext' : 'es5' %>", "module": "esnext", @@ -593,123 +593,123 @@ namespace ts { } } `, - "tsconfig.json", - { - compilerOptions: { - target: undefined, - module: ModuleKind.ESNext, - experimentalDecorators: true, - }, - hasParseErrors: true - }); + "tsconfig.json", + { + compilerOptions: { + target: undefined, + module: ModuleKind.ESNext, + experimentalDecorators: true, + }, + hasParseErrors: true }); + }); - it("Convert a tsconfig file with stray trailing characters", () => { - assertCompilerOptionsWithJsonText(`{ + it("Convert a tsconfig file with stray trailing characters", () => { + assertCompilerOptionsWithJsonText(`{ "compilerOptions": { "target": "esnext" } } blah`, "tsconfig.json", { - compilerOptions: { - target: ScriptTarget.ESNext - }, - hasParseErrors: true, - errors: [{ - ...Diagnostics.The_root_value_of_a_0_file_must_be_an_object, - messageText: "The root value of a 'tsconfig.json' file must be an object.", - file: undefined, - start: 0, - length: 0 - }] - }); + compilerOptions: { + target: ScriptTarget.ESNext + }, + hasParseErrors: true, + errors: [{ + ...Diagnostics.The_root_value_of_a_0_file_must_be_an_object, + messageText: "The root value of a 'tsconfig.json' file must be an object.", + file: undefined, + start: 0, + length: 0 + }] }); + }); - it("Convert a tsconfig file with stray leading characters", () => { - assertCompilerOptionsWithJsonText(`blah { + it("Convert a tsconfig file with stray leading characters", () => { + assertCompilerOptionsWithJsonText(`blah { "compilerOptions": { "target": "esnext" } }`, "tsconfig.json", { - compilerOptions: { - target: ScriptTarget.ESNext - }, - hasParseErrors: true, - errors: [{ - ...Diagnostics.The_root_value_of_a_0_file_must_be_an_object, - messageText: "The root value of a 'tsconfig.json' file must be an object.", - file: undefined, - start: 0, - length: 0 - }] - }); + compilerOptions: { + target: ScriptTarget.ESNext + }, + hasParseErrors: true, + errors: [{ + ...Diagnostics.The_root_value_of_a_0_file_must_be_an_object, + messageText: "The root value of a 'tsconfig.json' file must be an object.", + file: undefined, + start: 0, + length: 0 + }] }); + }); - it("Convert a tsconfig file as an array", () => { - assertCompilerOptionsWithJsonText(`[{ + it("Convert a tsconfig file as an array", () => { + assertCompilerOptionsWithJsonText(`[{ "compilerOptions": { "target": "esnext" } }]`, "tsconfig.json", { - compilerOptions: { - target: ScriptTarget.ESNext - }, - errors: [{ - ...Diagnostics.The_root_value_of_a_0_file_must_be_an_object, - messageText: "The root value of a 'tsconfig.json' file must be an object.", - file: undefined, - start: 0, - length: 0 - }] - }); + compilerOptions: { + target: ScriptTarget.ESNext + }, + errors: [{ + ...Diagnostics.The_root_value_of_a_0_file_must_be_an_object, + messageText: "The root value of a 'tsconfig.json' file must be an object.", + file: undefined, + start: 0, + length: 0 + }] }); + }); - it("raises an error if you've set a compiler flag in the root without including 'compilerOptions'", () => { - assertCompilerOptionsWithJsonText(`{ + it("raises an error if you've set a compiler flag in the root without including 'compilerOptions'", () => { + assertCompilerOptionsWithJsonText(`{ "module": "esnext", }`, "tsconfig.json", { - compilerOptions: {}, - errors: [{ - ...Diagnostics._0_should_be_set_inside_the_compilerOptions_object_of_the_config_json_file, - messageText: "'module' should be set inside the 'compilerOptions' object of the config json file.", - file: undefined, - start: 0, - length: 0 - }] - }); + compilerOptions: {}, + errors: [{ + ...Diagnostics._0_should_be_set_inside_the_compilerOptions_object_of_the_config_json_file, + messageText: "'module' should be set inside the 'compilerOptions' object of the config json file.", + file: undefined, + start: 0, + length: 0 + }] }); + }); - it("does not raise an error if you've set a compiler flag in the root when you have included 'compilerOptions'", () => { - assertCompilerOptionsWithJsonText(`{ + it("does not raise an error if you've set a compiler flag in the root when you have included 'compilerOptions'", () => { + assertCompilerOptionsWithJsonText(`{ "target": "esnext", "compilerOptions": { "module": "esnext" } }`, "tsconfig.json", { - compilerOptions: { - module: ModuleKind.ESNext - }, - errors: [] - }); + compilerOptions: { + module: ModuleKind.ESNext + }, + errors: [] }); + }); - it("Don't crash when root expression is not object at all", () => { - assertCompilerOptionsWithJsonText(`42`, "tsconfig.json", { - compilerOptions: {}, - errors: [{ - ...Diagnostics.The_root_value_of_a_0_file_must_be_an_object, - messageText: "The root value of a 'tsconfig.json' file must be an object.", - file: undefined, - start: 0, - length: 0 - }] - }); + it("Don't crash when root expression is not object at all", () => { + assertCompilerOptionsWithJsonText(`42`, "tsconfig.json", { + compilerOptions: {}, + errors: [{ + ...Diagnostics.The_root_value_of_a_0_file_must_be_an_object, + messageText: "The root value of a 'tsconfig.json' file must be an object.", + file: undefined, + start: 0, + length: 0 + }] }); + }); - it("Allow trailing comments", () => { - assertCompilerOptionsWithJsonText(`{} // no options`, "tsconfig.json", { - compilerOptions: {}, - errors: [] - }); + it("Allow trailing comments", () => { + assertCompilerOptionsWithJsonText(`{} // no options`, "tsconfig.json", { + compilerOptions: {}, + errors: [] }); }); +}); } diff --git a/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts b/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts index a985c4838dd24..1f1704d3f618e 100644 --- a/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts +++ b/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts @@ -1,239 +1,239 @@ namespace ts { - interface ExpectedResult { typeAcquisition: TypeAcquisition; errors: Diagnostic[]; } - describe("unittests:: config:: convertTypeAcquisitionFromJson", () => { - function assertTypeAcquisition(json: any, configFileName: string, expectedResult: ExpectedResult) { - assertTypeAcquisitionWithJson(json, configFileName, expectedResult); - assertTypeAcquisitionWithJsonNode(json, configFileName, expectedResult); - } +interface ExpectedResult { typeAcquisition: TypeAcquisition; errors: Diagnostic[]; } +describe("unittests:: config:: convertTypeAcquisitionFromJson", () => { + function assertTypeAcquisition(json: any, configFileName: string, expectedResult: ExpectedResult) { + assertTypeAcquisitionWithJson(json, configFileName, expectedResult); + assertTypeAcquisitionWithJsonNode(json, configFileName, expectedResult); + } - function verifyAcquisition(actualTypeAcquisition: TypeAcquisition | undefined, expectedResult: ExpectedResult) { - const parsedTypeAcquisition = JSON.stringify(actualTypeAcquisition); - const expectedTypeAcquisition = JSON.stringify(expectedResult.typeAcquisition); - assert.equal(parsedTypeAcquisition, expectedTypeAcquisition); - } + function verifyAcquisition(actualTypeAcquisition: TypeAcquisition | undefined, expectedResult: ExpectedResult) { + const parsedTypeAcquisition = JSON.stringify(actualTypeAcquisition); + const expectedTypeAcquisition = JSON.stringify(expectedResult.typeAcquisition); + assert.equal(parsedTypeAcquisition, expectedTypeAcquisition); + } - function verifyErrors(actualErrors: Diagnostic[], expectedResult: ExpectedResult, hasLocation?: boolean) { - const expectedErrors = expectedResult.errors; - assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`); - for (let i = 0; i < actualErrors.length; i++) { - const actualError = actualErrors[i]; - const expectedError = expectedErrors[i]; - assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`); - assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`); - if (hasLocation) { - assert(actualError.file); - assert(actualError.start); - assert(actualError.length); - } + function verifyErrors(actualErrors: Diagnostic[], expectedResult: ExpectedResult, hasLocation?: boolean) { + const expectedErrors = expectedResult.errors; + assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`); + for (let i = 0; i < actualErrors.length; i++) { + const actualError = actualErrors[i]; + const expectedError = expectedErrors[i]; + assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`); + assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`); + if (hasLocation) { + assert(actualError.file); + assert(actualError.start); + assert(actualError.length); } } + } - function assertTypeAcquisitionWithJson(json: any, configFileName: string, expectedResult: ExpectedResult) { - const jsonOptions = json.typeAcquisition || json.typingOptions; - const { options: actualTypeAcquisition, errors: actualErrors } = convertTypeAcquisitionFromJson(jsonOptions, "/apath/", configFileName); - verifyAcquisition(actualTypeAcquisition, expectedResult); - verifyErrors(actualErrors, expectedResult); - } + function assertTypeAcquisitionWithJson(json: any, configFileName: string, expectedResult: ExpectedResult) { + const jsonOptions = json.typeAcquisition || json.typingOptions; + const { options: actualTypeAcquisition, errors: actualErrors } = convertTypeAcquisitionFromJson(jsonOptions, "/apath/", configFileName); + verifyAcquisition(actualTypeAcquisition, expectedResult); + verifyErrors(actualErrors, expectedResult); + } - function assertTypeAcquisitionWithJsonNode(json: any, configFileName: string, expectedResult: ExpectedResult) { - const fileText = JSON.stringify(json); - const result = parseJsonText(configFileName, fileText); - assert(!result.parseDiagnostics.length); - assert(!!result.endOfFileToken); - const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" })); - const { typeAcquisition: actualTypeAcquisition, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); - verifyAcquisition(actualTypeAcquisition, expectedResult); + function assertTypeAcquisitionWithJsonNode(json: any, configFileName: string, expectedResult: ExpectedResult) { + const fileText = JSON.stringify(json); + const result = parseJsonText(configFileName, fileText); + assert(!result.parseDiagnostics.length); + assert(!!result.endOfFileToken); + const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" })); + const { typeAcquisition: actualTypeAcquisition, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); + verifyAcquisition(actualTypeAcquisition, expectedResult); - const actualErrors = filter(actualParseErrors, error => error.code !== Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); - verifyErrors(actualErrors, expectedResult, /*hasLocation*/ true); - } + const actualErrors = filter(actualParseErrors, error => error.code !== Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); + verifyErrors(actualErrors, expectedResult, /*hasLocation*/ true); + } - // tsconfig.json - it("Convert deprecated typingOptions.enableAutoDiscovery format tsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition( - { - typingOptions: - { - enableAutoDiscovery: true, - include: ["0.d.ts", "1.d.ts"], - exclude: ["0.js", "1.js"] - } - }, - "tsconfig.json", - { - typeAcquisition: - { - enable: true, - include: ["0.d.ts", "1.d.ts"], - exclude: ["0.js", "1.js"] - }, - errors: [] as Diagnostic[] + // tsconfig.json + it("Convert deprecated typingOptions.enableAutoDiscovery format tsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition( + { + typingOptions: + { + enableAutoDiscovery: true, + include: ["0.d.ts", "1.d.ts"], + exclude: ["0.js", "1.js"] } - ); - }); + }, + "tsconfig.json", + { + typeAcquisition: + { + enable: true, + include: ["0.d.ts", "1.d.ts"], + exclude: ["0.js", "1.js"] + }, + errors: [] as Diagnostic[] + } + ); + }); - it("Convert correctly format tsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition( - { - typeAcquisition: - { - enable: true, - include: ["0.d.ts", "1.d.ts"], - exclude: ["0.js", "1.js"] - } + it("Convert correctly format tsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition( + { + typeAcquisition: + { + enable: true, + include: ["0.d.ts", "1.d.ts"], + exclude: ["0.js", "1.js"] + } + }, + "tsconfig.json", + { + typeAcquisition: + { + enable: true, + include: ["0.d.ts", "1.d.ts"], + exclude: ["0.js", "1.js"] }, - "tsconfig.json", - { - typeAcquisition: - { - enable: true, - include: ["0.d.ts", "1.d.ts"], - exclude: ["0.js", "1.js"] - }, - errors: [] as Diagnostic[] - }); - }); + errors: [] as Diagnostic[] + }); + }); - it("Convert incorrect format tsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition( + it("Convert incorrect format tsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition( + { + typeAcquisition: { - typeAcquisition: - { - enableAutoDiscovy: true, - } - }, "tsconfig.json", + enableAutoDiscovy: true, + } + }, "tsconfig.json", + { + typeAcquisition: { - typeAcquisition: - { - enable: false, - include: [], - exclude: [] - }, - errors: [ - { - category: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.category, - code: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.code, - file: undefined, - start: 0, - length: 0, - messageText: undefined!, // TODO: GH#18217 - } - ] - }); - }); + enable: false, + include: [], + exclude: [] + }, + errors: [ + { + category: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.category, + code: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.code, + file: undefined, + start: 0, + length: 0, + messageText: undefined!, // TODO: GH#18217 + } + ] + }); + }); - it("Convert default tsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition({}, "tsconfig.json", - { - typeAcquisition: - { - enable: false, - include: [], - exclude: [] - }, - errors: [] as Diagnostic[] - }); - }); + it("Convert default tsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition({}, "tsconfig.json", + { + typeAcquisition: + { + enable: false, + include: [], + exclude: [] + }, + errors: [] as Diagnostic[] + }); + }); - it("Convert tsconfig.json with only enable property to typeAcquisition ", () => { - assertTypeAcquisition( + it("Convert tsconfig.json with only enable property to typeAcquisition ", () => { + assertTypeAcquisition( + { + typeAcquisition: { - typeAcquisition: - { - enable: true - } - }, "tsconfig.json", + enable: true + } + }, "tsconfig.json", + { + typeAcquisition: { - typeAcquisition: - { - enable: true, - include: [], - exclude: [] - }, - errors: [] as Diagnostic[] - }); - }); + enable: true, + include: [], + exclude: [] + }, + errors: [] as Diagnostic[] + }); + }); - // jsconfig.json - it("Convert jsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition( - { - typeAcquisition: - { - enable: false, - include: ["0.d.ts"], - exclude: ["0.js"] - } - }, "jsconfig.json", + // jsconfig.json + it("Convert jsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition( + { + typeAcquisition: + { + enable: false, + include: ["0.d.ts"], + exclude: ["0.js"] + } + }, "jsconfig.json", + { + typeAcquisition: { - typeAcquisition: - { - enable: false, - include: ["0.d.ts"], - exclude: ["0.js"] - }, - errors: [] as Diagnostic[] - }); - }); + enable: false, + include: ["0.d.ts"], + exclude: ["0.js"] + }, + errors: [] as Diagnostic[] + }); + }); - it("Convert default jsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition({ }, "jsconfig.json", - { - typeAcquisition: - { - enable: true, - include: [], - exclude: [] - }, - errors: [] as Diagnostic[] - }); - }); + it("Convert default jsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition({ }, "jsconfig.json", + { + typeAcquisition: + { + enable: true, + include: [], + exclude: [] + }, + errors: [] as Diagnostic[] + }); + }); - it("Convert incorrect format jsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition( + it("Convert incorrect format jsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition( + { + typeAcquisition: { - typeAcquisition: - { - enableAutoDiscovy: true, - } - }, "jsconfig.json", + enableAutoDiscovy: true, + } + }, "jsconfig.json", + { + typeAcquisition: { - typeAcquisition: - { - enable: true, - include: [], - exclude: [] - }, - errors: [ - { - category: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.category, - code: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.code, - file: undefined, - start: 0, - length: 0, - messageText: undefined!, // TODO: GH#18217 - } - ] - }); - }); + enable: true, + include: [], + exclude: [] + }, + errors: [ + { + category: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.category, + code: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.code, + file: undefined, + start: 0, + length: 0, + messageText: undefined!, // TODO: GH#18217 + } + ] + }); + }); - it("Convert jsconfig.json with only enable property to typeAcquisition ", () => { - assertTypeAcquisition( + it("Convert jsconfig.json with only enable property to typeAcquisition ", () => { + assertTypeAcquisition( + { + typeAcquisition: { - typeAcquisition: - { - enable: false - } - }, "jsconfig.json", + enable: false + } + }, "jsconfig.json", + { + typeAcquisition: { - typeAcquisition: - { - enable: false, - include: [], - exclude: [] - }, - errors: [] as Diagnostic[] - }); - }); + enable: false, + include: [], + exclude: [] + }, + errors: [] as Diagnostic[] + }); }); +}); } diff --git a/src/testRunner/unittests/config/initializeTSConfig.ts b/src/testRunner/unittests/config/initializeTSConfig.ts index f7fb7d2a4398b..98624708c6da2 100644 --- a/src/testRunner/unittests/config/initializeTSConfig.ts +++ b/src/testRunner/unittests/config/initializeTSConfig.ts @@ -1,33 +1,33 @@ namespace ts { - describe("unittests:: config:: initTSConfig", () => { - function initTSConfigCorrectly(name: string, commandLinesArgs: string[]) { - describe(name, () => { - const commandLine = parseCommandLine(commandLinesArgs); - const initResult = generateTSConfig(commandLine.options, commandLine.fileNames, "\n"); - const outputFileName = `tsConfig/${name.replace(/[^a-z0-9\-. ]/ig, "")}/tsconfig.json`; - - it(`Correct output for ${outputFileName}`, () => { - Harness.Baseline.runBaseline(outputFileName, initResult); - }); +describe("unittests:: config:: initTSConfig", () => { + function initTSConfigCorrectly(name: string, commandLinesArgs: string[]) { + describe(name, () => { + const commandLine = parseCommandLine(commandLinesArgs); + const initResult = generateTSConfig(commandLine.options, commandLine.fileNames, "\n"); + const outputFileName = `tsConfig/${name.replace(/[^a-z0-9\-. ]/ig, "")}/tsconfig.json`; + + it(`Correct output for ${outputFileName}`, () => { + Harness.Baseline.runBaseline(outputFileName, initResult); }); - } + }); + } - initTSConfigCorrectly("Default initialized TSConfig", ["--init"]); + initTSConfigCorrectly("Default initialized TSConfig", ["--init"]); - initTSConfigCorrectly("Initialized TSConfig with files options", ["--init", "file0.st", "file1.ts", "file2.ts"]); + initTSConfigCorrectly("Initialized TSConfig with files options", ["--init", "file0.st", "file1.ts", "file2.ts"]); - initTSConfigCorrectly("Initialized TSConfig with boolean value compiler options", ["--init", "--noUnusedLocals"]); + initTSConfigCorrectly("Initialized TSConfig with boolean value compiler options", ["--init", "--noUnusedLocals"]); - initTSConfigCorrectly("Initialized TSConfig with enum value compiler options", ["--init", "--target", "es5", "--jsx", "react"]); + initTSConfigCorrectly("Initialized TSConfig with enum value compiler options", ["--init", "--target", "es5", "--jsx", "react"]); - initTSConfigCorrectly("Initialized TSConfig with list compiler options", ["--init", "--types", "jquery,mocha"]); + initTSConfigCorrectly("Initialized TSConfig with list compiler options", ["--init", "--types", "jquery,mocha"]); - initTSConfigCorrectly("Initialized TSConfig with list compiler options with enum value", ["--init", "--lib", "es5,es2015.core"]); + initTSConfigCorrectly("Initialized TSConfig with list compiler options with enum value", ["--init", "--lib", "es5,es2015.core"]); - initTSConfigCorrectly("Initialized TSConfig with incorrect compiler option", ["--init", "--someNonExistOption"]); + initTSConfigCorrectly("Initialized TSConfig with incorrect compiler option", ["--init", "--someNonExistOption"]); - initTSConfigCorrectly("Initialized TSConfig with incorrect compiler option value", ["--init", "--lib", "nonExistLib,es5,es2015.promise"]); + initTSConfigCorrectly("Initialized TSConfig with incorrect compiler option value", ["--init", "--lib", "nonExistLib,es5,es2015.promise"]); - initTSConfigCorrectly("Initialized TSConfig with advanced options", ["--init", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]); - }); + initTSConfigCorrectly("Initialized TSConfig with advanced options", ["--init", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]); +}); } diff --git a/src/testRunner/unittests/config/matchFiles.ts b/src/testRunner/unittests/config/matchFiles.ts index 951d87be80f24..1ab1695842fd4 100644 --- a/src/testRunner/unittests/config/matchFiles.ts +++ b/src/testRunner/unittests/config/matchFiles.ts @@ -1,483 +1,672 @@ namespace ts { - const caseInsensitiveBasePath = "c:/dev/"; - const caseInsensitiveTsconfigPath = "c:/dev/tsconfig.json"; - const caseInsensitiveHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { - "c:/dev/a.ts": "", - "c:/dev/a.d.ts": "", - "c:/dev/a.js": "", - "c:/dev/b.ts": "", - "c:/dev/b.js": "", - "c:/dev/c.d.ts": "", - "c:/dev/z/a.ts": "", - "c:/dev/z/abz.ts": "", - "c:/dev/z/aba.ts": "", - "c:/dev/z/b.ts": "", - "c:/dev/z/bbz.ts": "", - "c:/dev/z/bba.ts": "", - "c:/dev/x/a.ts": "", - "c:/dev/x/aa.ts": "", - "c:/dev/x/b.ts": "", - "c:/dev/x/y/a.ts": "", - "c:/dev/x/y/b.ts": "", - "c:/dev/js/a.js": "", - "c:/dev/js/b.js": "", - "c:/dev/js/d.min.js": "", - "c:/dev/js/ab.min.js": "", - "c:/ext/ext.ts": "", - "c:/ext/b/a..b.ts": "", - }})); +const caseInsensitiveBasePath = "c:/dev/"; +const caseInsensitiveTsconfigPath = "c:/dev/tsconfig.json"; +const caseInsensitiveHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + "c:/dev/a.ts": "", + "c:/dev/a.d.ts": "", + "c:/dev/a.js": "", + "c:/dev/b.ts": "", + "c:/dev/b.js": "", + "c:/dev/c.d.ts": "", + "c:/dev/z/a.ts": "", + "c:/dev/z/abz.ts": "", + "c:/dev/z/aba.ts": "", + "c:/dev/z/b.ts": "", + "c:/dev/z/bbz.ts": "", + "c:/dev/z/bba.ts": "", + "c:/dev/x/a.ts": "", + "c:/dev/x/aa.ts": "", + "c:/dev/x/b.ts": "", + "c:/dev/x/y/a.ts": "", + "c:/dev/x/y/b.ts": "", + "c:/dev/js/a.js": "", + "c:/dev/js/b.js": "", + "c:/dev/js/d.min.js": "", + "c:/dev/js/ab.min.js": "", + "c:/ext/ext.ts": "", + "c:/ext/b/a..b.ts": "", +}})); - const caseSensitiveBasePath = "/dev/"; - const caseSensitiveHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: { - "/dev/a.ts": "", - "/dev/a.d.ts": "", - "/dev/a.js": "", - "/dev/b.ts": "", - "/dev/b.js": "", - "/dev/A.ts": "", - "/dev/B.ts": "", - "/dev/c.d.ts": "", - "/dev/z/a.ts": "", - "/dev/z/abz.ts": "", - "/dev/z/aba.ts": "", - "/dev/z/b.ts": "", - "/dev/z/bbz.ts": "", - "/dev/z/bba.ts": "", - "/dev/x/a.ts": "", - "/dev/x/b.ts": "", - "/dev/x/y/a.ts": "", - "/dev/x/y/b.ts": "", - "/dev/q/a/c/b/d.ts": "", - "/dev/js/a.js": "", - "/dev/js/b.js": "", - }})); +const caseSensitiveBasePath = "/dev/"; +const caseSensitiveHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: { + "/dev/a.ts": "", + "/dev/a.d.ts": "", + "/dev/a.js": "", + "/dev/b.ts": "", + "/dev/b.js": "", + "/dev/A.ts": "", + "/dev/B.ts": "", + "/dev/c.d.ts": "", + "/dev/z/a.ts": "", + "/dev/z/abz.ts": "", + "/dev/z/aba.ts": "", + "/dev/z/b.ts": "", + "/dev/z/bbz.ts": "", + "/dev/z/bba.ts": "", + "/dev/x/a.ts": "", + "/dev/x/b.ts": "", + "/dev/x/y/a.ts": "", + "/dev/x/y/b.ts": "", + "/dev/q/a/c/b/d.ts": "", + "/dev/js/a.js": "", + "/dev/js/b.js": "", +}})); - const caseInsensitiveMixedExtensionHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { - "c:/dev/a.ts": "", - "c:/dev/a.d.ts": "", - "c:/dev/a.js": "", - "c:/dev/b.tsx": "", - "c:/dev/b.d.ts": "", - "c:/dev/b.jsx": "", - "c:/dev/c.tsx": "", - "c:/dev/c.js": "", - "c:/dev/d.js": "", - "c:/dev/e.jsx": "", - "c:/dev/f.other": "", - }})); +const caseInsensitiveMixedExtensionHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + "c:/dev/a.ts": "", + "c:/dev/a.d.ts": "", + "c:/dev/a.js": "", + "c:/dev/b.tsx": "", + "c:/dev/b.d.ts": "", + "c:/dev/b.jsx": "", + "c:/dev/c.tsx": "", + "c:/dev/c.js": "", + "c:/dev/d.js": "", + "c:/dev/e.jsx": "", + "c:/dev/f.other": "", +}})); - const caseInsensitiveCommonFoldersHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { - "c:/dev/a.ts": "", - "c:/dev/a.d.ts": "", - "c:/dev/a.js": "", - "c:/dev/b.ts": "", - "c:/dev/x/a.ts": "", - "c:/dev/node_modules/a.ts": "", - "c:/dev/bower_components/a.ts": "", - "c:/dev/jspm_packages/a.ts": "", - }})); +const caseInsensitiveCommonFoldersHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + "c:/dev/a.ts": "", + "c:/dev/a.d.ts": "", + "c:/dev/a.js": "", + "c:/dev/b.ts": "", + "c:/dev/x/a.ts": "", + "c:/dev/node_modules/a.ts": "", + "c:/dev/bower_components/a.ts": "", + "c:/dev/jspm_packages/a.ts": "", +}})); - const caseInsensitiveDottedFoldersHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { - "c:/dev/x/d.ts": "", - "c:/dev/x/y/d.ts": "", - "c:/dev/x/y/.e.ts": "", - "c:/dev/x/.y/a.ts": "", - "c:/dev/.z/.b.ts": "", - "c:/dev/.z/c.ts": "", - "c:/dev/w/.u/e.ts": "", - "c:/dev/g.min.js/.g/g.ts": "", - }})); +const caseInsensitiveDottedFoldersHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + "c:/dev/x/d.ts": "", + "c:/dev/x/y/d.ts": "", + "c:/dev/x/y/.e.ts": "", + "c:/dev/x/.y/a.ts": "", + "c:/dev/.z/.b.ts": "", + "c:/dev/.z/c.ts": "", + "c:/dev/w/.u/e.ts": "", + "c:/dev/g.min.js/.g/g.ts": "", +}})); - const caseInsensitiveOrderingDiffersWithCaseHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { - "c:/dev/xylophone.ts": "", - "c:/dev/Yosemite.ts": "", - "c:/dev/zebra.ts": "", - }})); +const caseInsensitiveOrderingDiffersWithCaseHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + "c:/dev/xylophone.ts": "", + "c:/dev/Yosemite.ts": "", + "c:/dev/zebra.ts": "", +}})); - const caseSensitiveOrderingDiffersWithCaseHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: { - "/dev/xylophone.ts": "", - "/dev/Yosemite.ts": "", - "/dev/zebra.ts": "", - }})); +const caseSensitiveOrderingDiffersWithCaseHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: { + "/dev/xylophone.ts": "", + "/dev/Yosemite.ts": "", + "/dev/zebra.ts": "", +}})); - function assertParsed(actual: ParsedCommandLine, expected: ParsedCommandLine): void { - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); - } +function assertParsed(actual: ParsedCommandLine, expected: ParsedCommandLine): void { + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); +} - function validateMatches(expected: ParsedCommandLine, json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[]) { - { - const jsonText = JSON.stringify(json); - const result = parseJsonText(caseInsensitiveTsconfigPath, jsonText); - const actual = parseJsonSourceFileConfigFileContent(result, host, basePath, existingOptions, configFileName, resolutionStack); - for (const error of expected.errors) { - if (error.file) { - error.file = result; - } +function validateMatches(expected: ParsedCommandLine, json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[]) { + { + const jsonText = JSON.stringify(json); + const result = parseJsonText(caseInsensitiveTsconfigPath, jsonText); + const actual = parseJsonSourceFileConfigFileContent(result, host, basePath, existingOptions, configFileName, resolutionStack); + for (const error of expected.errors) { + if (error.file) { + error.file = result; } - assertParsed(actual, expected); - } - { - const actual = parseJsonConfigFileContent(json, host, basePath, existingOptions, configFileName, resolutionStack); - expected.errors = expected.errors.map((error): Diagnostic => ({ - category: error.category, - code: error.code, - file: undefined, - length: undefined, - messageText: error.messageText, - start: undefined, - reportsUnnecessary: undefined, - reportsDeprecated: undefined - })); - assertParsed(actual, expected); } + assertParsed(actual, expected); } - - function createDiagnosticForConfigFile(json: any, start: number, length: number, diagnosticMessage: DiagnosticMessage, arg0: string) { - const text = JSON.stringify(json); - const file = { - fileName: caseInsensitiveTsconfigPath, - kind: SyntaxKind.SourceFile, - text - } as SourceFile; - return createFileDiagnostic(file, start, length, diagnosticMessage, arg0); + { + const actual = parseJsonConfigFileContent(json, host, basePath, existingOptions, configFileName, resolutionStack); + expected.errors = expected.errors.map((error): Diagnostic => ({ + category: error.category, + code: error.code, + file: undefined, + length: undefined, + messageText: error.messageText, + start: undefined, + reportsUnnecessary: undefined, + reportsDeprecated: undefined + })); + assertParsed(actual, expected); } +} + +function createDiagnosticForConfigFile(json: any, start: number, length: number, diagnosticMessage: DiagnosticMessage, arg0: string) { + const text = JSON.stringify(json); + const file = { + fileName: caseInsensitiveTsconfigPath, + kind: SyntaxKind.SourceFile, + text + } as SourceFile; + return createFileDiagnostic(file, start, length, diagnosticMessage, arg0); +} + +describe("unittests:: config:: matchFiles", () => { + it("with defaults", () => { + const json = {}; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts", + "c:/dev/x/a.ts" + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + }); - describe("unittests:: config:: matchFiles", () => { - it("with defaults", () => { - const json = {}; + describe("with literal file list", () => { + it("without exclusions", () => { + const json = { + files: [ + "a.ts", + "b.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("missing files are still present", () => { + const json = { + files: [ + "z.ts", + "x.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/z.ts", + "c:/dev/x.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("are not removed due to excludes", () => { + const json = { + files: [ + "a.ts", + "b.ts" + ], + exclude: [ + "b.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + }); + + describe("with literal include list", () => { + it("without exclusions", () => { + const json = { + include: [ + "a.ts", + "b.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with non .ts file extensions are excluded", () => { + const json = { + include: [ + "a.js", + "b.js" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [ + createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + ], + fileNames: [], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); + }); + it("with missing files are excluded", () => { + const json = { + include: [ + "z.ts", + "x.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [ + createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + ], + fileNames: [], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); + }); + it("with literal excludes", () => { + const json = { + include: [ + "a.ts", + "b.ts" + ], + exclude: [ + "b.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with wildcard excludes", () => { + const json = { + include: [ + "a.ts", + "b.ts", + "z/a.ts", + "z/abz.ts", + "z/aba.ts", + "x/b.ts" + ], + exclude: [ + "*.ts", + "z/??z.ts", + "*/b.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/z/a.ts", + "c:/dev/z/aba.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with recursive excludes", () => { + const json = { + include: [ + "a.ts", + "b.ts", + "x/a.ts", + "x/b.ts", + "x/y/a.ts", + "x/y/b.ts" + ], + exclude: [ + "**/b.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/x/a.ts", + "c:/dev/x/y/a.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with case sensitive exclude", () => { + const json = { + include: [ + "B.ts" + ], + exclude: [ + "**/b.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "/dev/B.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); + }); + it("with common package folders and no exclusions", () => { + const json = { + include: [ + "a.ts", + "b.ts", + "node_modules/a.ts", + "bower_components/a.ts", + "jspm_packages/a.ts" + ] + }; const expected: ParsedCommandLine = { options: {}, errors: [], fileNames: [ "c:/dev/a.ts", "c:/dev/b.ts", - "c:/dev/x/a.ts" + "c:/dev/node_modules/a.ts", + "c:/dev/bower_components/a.ts", + "c:/dev/jspm_packages/a.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + }); + it("with common package folders and exclusions", () => { + const json = { + include: [ + "a.ts", + "b.ts", + "node_modules/a.ts", + "bower_components/a.ts", + "jspm_packages/a.ts" + ], + exclude: [ + "a.ts", + "b.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/node_modules/a.ts", + "c:/dev/bower_components/a.ts", + "c:/dev/jspm_packages/a.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + }); + it("with common package folders and empty exclude", () => { + const json = { + include: [ + "a.ts", + "b.ts", + "node_modules/a.ts", + "bower_components/a.ts", + "jspm_packages/a.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts", + "c:/dev/node_modules/a.ts", + "c:/dev/bower_components/a.ts", + "c:/dev/jspm_packages/a.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + }); + }); + + describe("with wildcard include list", () => { + it("is sorted in include order, then in alphabetical order", () => { + const json = { + include: [ + "z/*.ts", + "x/*.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/z/a.ts", + "c:/dev/z/aba.ts", + "c:/dev/z/abz.ts", + "c:/dev/z/b.ts", + "c:/dev/z/bba.ts", + "c:/dev/z/bbz.ts", + "c:/dev/x/a.ts", + "c:/dev/x/aa.ts", + "c:/dev/x/b.ts" + ], + wildcardDirectories: { + "c:/dev/z": WatchDirectoryFlags.None, + "c:/dev/x": WatchDirectoryFlags.None + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("same named declarations are excluded", () => { + const json = { + include: [ + "*.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts", + "c:/dev/c.d.ts" + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.None + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("`*` matches only ts files", () => { + const json = { + include: [ + "*" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts", + "c:/dev/c.d.ts" + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.None + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("`?` matches only a single character", () => { + const json = { + include: [ + "x/?.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/x/a.ts", + "c:/dev/x/b.ts" + ], + wildcardDirectories: { + "c:/dev/x": WatchDirectoryFlags.None + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with recursive directory", () => { + const json = { + include: [ + "**/a.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/x/a.ts", + "c:/dev/x/y/a.ts", + "c:/dev/z/a.ts" ], wildcardDirectories: { "c:/dev": WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); - - describe("with literal file list", () => { - it("without exclusions", () => { - const json = { - files: [ - "a.ts", - "b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("missing files are still present", () => { - const json = { - files: [ - "z.ts", - "x.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/z.ts", - "c:/dev/x.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("are not removed due to excludes", () => { - const json = { - files: [ - "a.ts", - "b.ts" - ], - exclude: [ - "b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); + it("with multiple recursive directories", () => { + const json = { + include: [ + "x/y/**/a.ts", + "x/**/a.ts", + "z/**/a.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/x/y/a.ts", + "c:/dev/x/a.ts", + "c:/dev/z/a.ts" + ], + wildcardDirectories: { + "c:/dev/x": WatchDirectoryFlags.Recursive, + "c:/dev/z": WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); - - describe("with literal include list", () => { - it("without exclusions", () => { - const json = { - include: [ - "a.ts", - "b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("with non .ts file extensions are excluded", () => { - const json = { - include: [ - "a.js", - "b.js" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") - ], - fileNames: [], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - it("with missing files are excluded", () => { - const json = { - include: [ - "z.ts", - "x.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") - ], - fileNames: [], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - it("with literal excludes", () => { - const json = { - include: [ - "a.ts", - "b.ts" - ], - exclude: [ - "b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("with wildcard excludes", () => { - const json = { - include: [ - "a.ts", - "b.ts", - "z/a.ts", - "z/abz.ts", - "z/aba.ts", - "x/b.ts" - ], - exclude: [ - "*.ts", - "z/??z.ts", - "*/b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/z/a.ts", - "c:/dev/z/aba.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("with recursive excludes", () => { - const json = { - include: [ - "a.ts", - "b.ts", - "x/a.ts", - "x/b.ts", - "x/y/a.ts", - "x/y/b.ts" - ], - exclude: [ - "**/b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/x/a.ts", - "c:/dev/x/y/a.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("with case sensitive exclude", () => { - const json = { - include: [ - "B.ts" - ], - exclude: [ - "**/b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "/dev/B.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); - }); - it("with common package folders and no exclusions", () => { - const json = { - include: [ - "a.ts", - "b.ts", - "node_modules/a.ts", - "bower_components/a.ts", - "jspm_packages/a.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts", - "c:/dev/node_modules/a.ts", - "c:/dev/bower_components/a.ts", - "c:/dev/jspm_packages/a.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("with common package folders and exclusions", () => { - const json = { - include: [ - "a.ts", - "b.ts", - "node_modules/a.ts", - "bower_components/a.ts", - "jspm_packages/a.ts" - ], - exclude: [ - "a.ts", - "b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/node_modules/a.ts", - "c:/dev/bower_components/a.ts", - "c:/dev/jspm_packages/a.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("with common package folders and empty exclude", () => { - const json = { - include: [ - "a.ts", - "b.ts", - "node_modules/a.ts", - "bower_components/a.ts", - "jspm_packages/a.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts", - "c:/dev/node_modules/a.ts", - "c:/dev/bower_components/a.ts", - "c:/dev/jspm_packages/a.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); + it("case sensitive", () => { + const json = { + include: [ + "**/A.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "/dev/A.ts" + ], + wildcardDirectories: { + "/dev": WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); }); - - describe("with wildcard include list", () => { - it("is sorted in include order, then in alphabetical order", () => { - const json = { - include: [ - "z/*.ts", - "x/*.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/z/a.ts", - "c:/dev/z/aba.ts", - "c:/dev/z/abz.ts", - "c:/dev/z/b.ts", - "c:/dev/z/bba.ts", - "c:/dev/z/bbz.ts", - "c:/dev/x/a.ts", - "c:/dev/x/aa.ts", - "c:/dev/x/b.ts" - ], - wildcardDirectories: { - "c:/dev/z": WatchDirectoryFlags.None, - "c:/dev/x": WatchDirectoryFlags.None - }, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("same named declarations are excluded", () => { + it("with missing files are excluded", () => { + const json = { + include: [ + "*/z.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [ + createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + ], + fileNames: [], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); + }); + it("always include literal files", () => { + const json = { + files: [ + "a.ts" + ], + include: [ + "*/z.ts" + ], + exclude: [ + "**/a.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts" + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("exclude folders", () => { + const json = { + include: [ + "**/*" + ], + exclude: [ + "z", + "x" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts", + "c:/dev/c.d.ts" + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + describe("with common package folders", () => { + it("and no exclusions", () => { const json = { include: [ - "*.ts" + "**/a.ts" ] }; const expected: ParsedCommandLine = { @@ -485,1058 +674,869 @@ namespace ts { errors: [], fileNames: [ "c:/dev/a.ts", - "c:/dev/b.ts", - "c:/dev/c.d.ts" + "c:/dev/x/a.ts" ], wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.None + "c:/dev": WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - it("`*` matches only ts files", () => { + it("and exclusions", () => { const json = { include: [ - "*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts", - "c:/dev/c.d.ts" + "**/?.ts" ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.None - }, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("`?` matches only a single character", () => { - const json = { - include: [ - "x/?.ts" + exclude: [ + "a.ts" ] }; const expected: ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/dev/x/a.ts", - "c:/dev/x/b.ts" + "c:/dev/b.ts", + "c:/dev/x/a.ts" ], wildcardDirectories: { - "c:/dev/x": WatchDirectoryFlags.None + "c:/dev": WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - it("with recursive directory", () => { + it("and empty exclude", () => { const json = { include: [ "**/a.ts" - ] + ], + exclude: [] as string[] }; const expected: ParsedCommandLine = { options: {}, errors: [], fileNames: [ "c:/dev/a.ts", - "c:/dev/x/a.ts", - "c:/dev/x/y/a.ts", - "c:/dev/z/a.ts" + "c:/dev/x/a.ts" ], wildcardDirectories: { "c:/dev": WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - it("with multiple recursive directories", () => { + it("and explicit recursive include", () => { const json = { include: [ - "x/y/**/a.ts", - "x/**/a.ts", - "z/**/a.ts" + "**/a.ts", + "**/node_modules/a.ts" ] }; const expected: ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/dev/x/y/a.ts", + "c:/dev/a.ts", "c:/dev/x/a.ts", - "c:/dev/z/a.ts" - ], - wildcardDirectories: { - "c:/dev/x": WatchDirectoryFlags.Recursive, - "c:/dev/z": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("case sensitive", () => { - const json = { - include: [ - "**/A.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "/dev/A.ts" - ], - wildcardDirectories: { - "/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); - }); - it("with missing files are excluded", () => { - const json = { - include: [ - "*/z.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + "c:/dev/node_modules/a.ts" ], - fileNames: [], wildcardDirectories: { "c:/dev": WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - it("always include literal files", () => { + it("and wildcard include", () => { const json = { - files: [ - "a.ts" - ], include: [ - "*/z.ts" - ], - exclude: [ - "**/a.ts" + "*/a.ts" ] }; const expected: ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/dev/a.ts" + "c:/dev/x/a.ts" ], wildcardDirectories: { "c:/dev": WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - it("exclude folders", () => { + it("and explicit wildcard include", () => { const json = { include: [ - "**/*" - ], - exclude: [ - "z", - "x" + "*/a.ts", + "node_modules/a.ts" ] }; const expected: ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts", - "c:/dev/c.d.ts" + "c:/dev/x/a.ts", + "c:/dev/node_modules/a.ts" ], wildcardDirectories: { "c:/dev": WatchDirectoryFlags.Recursive - } + }, }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - describe("with common package folders", () => { - it("and no exclusions", () => { - const json = { - include: [ - "**/a.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/x/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("and exclusions", () => { - const json = { - include: [ - "**/?.ts" - ], - exclude: [ - "a.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/b.ts", - "c:/dev/x/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("and empty exclude", () => { - const json = { - include: [ - "**/a.ts" - ], - exclude: [] as string[] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/x/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("and explicit recursive include", () => { - const json = { - include: [ - "**/a.ts", - "**/node_modules/a.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/x/a.ts", - "c:/dev/node_modules/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("and wildcard include", () => { - const json = { - include: [ - "*/a.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/x/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("and explicit wildcard include", () => { - const json = { - include: [ - "*/a.ts", - "node_modules/a.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/x/a.ts", - "c:/dev/node_modules/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - it("exclude .js files when allowJs=false", () => { + }); + it("exclude .js files when allowJs=false", () => { + const json = { + compilerOptions: { + allowJs: false + }, + include: [ + "js/*" + ] + }; + const expected: ParsedCommandLine = { + options: { + allowJs: false + }, + errors: [ + createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + ], + fileNames: [], + wildcardDirectories: { + "c:/dev/js": WatchDirectoryFlags.None + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); + }); + it("include .js files when allowJs=true", () => { + const json = { + compilerOptions: { + allowJs: true + }, + include: [ + "js/*" + ] + }; + const expected: ParsedCommandLine = { + options: { + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/js/a.js", + "c:/dev/js/b.js" + ], + wildcardDirectories: { + "c:/dev/js": WatchDirectoryFlags.None + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("include explicitly listed .min.js files when allowJs=true", () => { + const json = { + compilerOptions: { + allowJs: true + }, + include: [ + "js/*.min.js" + ] + }; + const expected: ParsedCommandLine = { + options: { + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/js/ab.min.js", + "c:/dev/js/d.min.js" + ], + wildcardDirectories: { + "c:/dev/js": WatchDirectoryFlags.None + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("include paths outside of the project", () => { + const json = { + include: [ + "*", + "c:/ext/*" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts", + "c:/dev/c.d.ts", + "c:/ext/ext.ts" + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.None, + "c:/ext": WatchDirectoryFlags.None + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("include paths outside of the project using relative paths", () => { + const json = { + include: [ + "*", + "../ext/*" + ], + exclude: [ + "**" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/ext/ext.ts" + ], + wildcardDirectories: { + "c:/ext": WatchDirectoryFlags.None + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("exclude paths outside of the project using relative paths", () => { + const json = { + include: [ + "c:/**/*" + ], + exclude: [ + "../**" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [ + createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), JSON.stringify(json.exclude))] + , + fileNames: [], + wildcardDirectories: {} + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); + }); + it("include files with .. in their name", () => { + const json = { + include: [ + "c:/ext/b/a..b.ts" + ], + exclude: [ + "**" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/ext/b/a..b.ts" + ], + wildcardDirectories: {} + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("exclude files with .. in their name", () => { + const json = { + include: [ + "c:/ext/**/*" + ], + exclude: [ + "c:/ext/b/a..b.ts" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/ext/ext.ts", + ], + wildcardDirectories: { + "c:/ext": WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with jsx=none, allowJs=false", () => { + const json = { + compilerOptions: { + allowJs: false + } + }; + const expected: ParsedCommandLine = { + options: { + allowJs: false + }, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.tsx", + "c:/dev/c.tsx", + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + }); + it("with jsx=preserve, allowJs=false", () => { + const json = { + compilerOptions: { + jsx: "preserve", + allowJs: false + } + }; + const expected: ParsedCommandLine = { + options: { + jsx: JsxEmit.Preserve, + allowJs: false + }, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.tsx", + "c:/dev/c.tsx", + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + }); + it("with jsx=react-native, allowJs=false", () => { + const json = { + compilerOptions: { + jsx: "react-native", + allowJs: false + } + }; + const expected: ParsedCommandLine = { + options: { + jsx: JsxEmit.ReactNative, + allowJs: false + }, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.tsx", + "c:/dev/c.tsx", + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + }); + it("with jsx=none, allowJs=true", () => { + const json = { + compilerOptions: { + allowJs: true + } + }; + const expected: ParsedCommandLine = { + options: { + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.tsx", + "c:/dev/c.tsx", + "c:/dev/d.js", + "c:/dev/e.jsx", + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + }); + it("with jsx=preserve, allowJs=true", () => { + const json = { + compilerOptions: { + jsx: "preserve", + allowJs: true + } + }; + const expected: ParsedCommandLine = { + options: { + jsx: JsxEmit.Preserve, + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.tsx", + "c:/dev/c.tsx", + "c:/dev/d.js", + "c:/dev/e.jsx", + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + }); + it("with jsx=react-native, allowJs=true", () => { + const json = { + compilerOptions: { + jsx: "react-native", + allowJs: true + } + }; + const expected: ParsedCommandLine = { + options: { + jsx: JsxEmit.ReactNative, + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.tsx", + "c:/dev/c.tsx", + "c:/dev/d.js", + "c:/dev/e.jsx", + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + }); + it("exclude .min.js files using wildcards", () => { + const json = { + compilerOptions: { + allowJs: true + }, + include: [ + "js/*.min.js" + ], + exclude: [ + "js/a*" + ] + }; + const expected: ParsedCommandLine = { + options: { + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/js/d.min.js" + ], + wildcardDirectories: { + "c:/dev/js": WatchDirectoryFlags.None + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + + describe("with trailing recursive directory", () => { + it("in includes", () => { const json = { - compilerOptions: { - allowJs: false - }, include: [ - "js/*" + "**" ] }; const expected: ParsedCommandLine = { - options: { - allowJs: false - }, + options: {}, errors: [ + createDiagnosticForConfigFile(json, 12, 4, Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**"), createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") ], fileNames: [], - wildcardDirectories: { - "c:/dev/js": WatchDirectoryFlags.None - } + wildcardDirectories: {} }; validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); - it("include .js files when allowJs=true", () => { + it("in excludes", () => { const json = { - compilerOptions: { - allowJs: true - }, include: [ - "js/*" - ] - }; - const expected: ParsedCommandLine = { - options: { - allowJs: true - }, - errors: [], - fileNames: [ - "c:/dev/js/a.js", - "c:/dev/js/b.js" + "**/*" ], - wildcardDirectories: { - "c:/dev/js": WatchDirectoryFlags.None - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("include explicitly listed .min.js files when allowJs=true", () => { - const json = { - compilerOptions: { - allowJs: true - }, - include: [ - "js/*.min.js" + exclude: [ + "**" ] }; const expected: ParsedCommandLine = { - options: { - allowJs: true - }, - errors: [], - fileNames: [ - "c:/dev/js/ab.min.js", - "c:/dev/js/d.min.js" + options: {}, + errors: [ + createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), JSON.stringify(json.exclude)) ], - wildcardDirectories: { - "c:/dev/js": WatchDirectoryFlags.None - } + fileNames: [], + wildcardDirectories: {} }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); - it("include paths outside of the project", () => { + }); + describe("with multiple recursive directory patterns", () => { + it("in includes", () => { const json = { include: [ - "*", - "c:/ext/*" + "**/x/**/*" ] }; const expected: ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts", - "c:/dev/c.d.ts", - "c:/ext/ext.ts" + "c:/dev/x/a.ts", + "c:/dev/x/aa.ts", + "c:/dev/x/b.ts", + "c:/dev/x/y/a.ts", + "c:/dev/x/y/b.ts", ], wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.None, - "c:/ext": WatchDirectoryFlags.None + "c:/dev": WatchDirectoryFlags.Recursive } }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); - it("include paths outside of the project using relative paths", () => { + it("in excludes", () => { const json = { include: [ - "*", - "../ext/*" + "**/a.ts" ], exclude: [ - "**" + "**/x/**" ] }; const expected: ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/ext/ext.ts" + "c:/dev/a.ts", + "c:/dev/z/a.ts" ], wildcardDirectories: { - "c:/ext": WatchDirectoryFlags.None + "c:/dev": WatchDirectoryFlags.Recursive } }; validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); - it("exclude paths outside of the project using relative paths", () => { + }); + + describe("with parent directory symbols after a recursive directory pattern", () => { + it("in includes immediately after", () => { const json = { include: [ - "c:/**/*" - ], - exclude: [ - "../**" + "**/../*" ] }; const expected: ParsedCommandLine = { options: {}, errors: [ + createDiagnosticForConfigFile(json, 12, 9, Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/../*"), createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), JSON.stringify(json.exclude))] - , + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + ], fileNames: [], wildcardDirectories: {} }; validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); - it("include files with .. in their name", () => { + + it("in includes after a subdirectory", () => { const json = { include: [ - "c:/ext/b/a..b.ts" - ], - exclude: [ - "**" + "**/y/../*" ] }; const expected: ParsedCommandLine = { options: {}, - errors: [], - fileNames: [ - "c:/ext/b/a..b.ts" + errors: [ + createDiagnosticForConfigFile(json, 12, 11, Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/../*"), + createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") ], + fileNames: [], wildcardDirectories: {} }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); - it("exclude files with .. in their name", () => { + + it("in excludes immediately after", () => { const json = { include: [ - "c:/ext/**/*" + "**/a.ts" ], exclude: [ - "c:/ext/b/a..b.ts" + "**/.." ] }; const expected: ParsedCommandLine = { options: {}, - errors: [], - fileNames: [ - "c:/ext/ext.ts", - ], - wildcardDirectories: { - "c:/ext": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("with jsx=none, allowJs=false", () => { - const json = { - compilerOptions: { - allowJs: false - } - }; - const expected: ParsedCommandLine = { - options: { - allowJs: false - }, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.tsx", - "c:/dev/c.tsx", + errors: [ + createDiagnosticForConfigFile(json, 34, 7, Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/..") ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - }); - it("with jsx=preserve, allowJs=false", () => { - const json = { - compilerOptions: { - jsx: "preserve", - allowJs: false - } - }; - const expected: ParsedCommandLine = { - options: { - jsx: JsxEmit.Preserve, - allowJs: false - }, - errors: [], fileNames: [ "c:/dev/a.ts", - "c:/dev/b.tsx", - "c:/dev/c.tsx", + "c:/dev/x/a.ts", + "c:/dev/x/y/a.ts", + "c:/dev/z/a.ts" ], wildcardDirectories: { "c:/dev": WatchDirectoryFlags.Recursive } }; - validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); - it("with jsx=react-native, allowJs=false", () => { + + it("in excludes after a subdirectory", () => { const json = { - compilerOptions: { - jsx: "react-native", - allowJs: false - } - }; - const expected: ParsedCommandLine = { - options: { - jsx: JsxEmit.ReactNative, - allowJs: false - }, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.tsx", - "c:/dev/c.tsx", + include: [ + "**/a.ts" ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - }); - it("with jsx=none, allowJs=true", () => { - const json = { - compilerOptions: { - allowJs: true - } + exclude: [ + "**/y/.." + ] }; const expected: ParsedCommandLine = { - options: { - allowJs: true - }, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.tsx", - "c:/dev/c.tsx", - "c:/dev/d.js", - "c:/dev/e.jsx", + options: {}, + errors: [ + createDiagnosticForConfigFile(json, 34, 9, Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/..") ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - }); - it("with jsx=preserve, allowJs=true", () => { - const json = { - compilerOptions: { - jsx: "preserve", - allowJs: true - } - }; - const expected: ParsedCommandLine = { - options: { - jsx: JsxEmit.Preserve, - allowJs: true - }, - errors: [], fileNames: [ "c:/dev/a.ts", - "c:/dev/b.tsx", - "c:/dev/c.tsx", - "c:/dev/d.js", - "c:/dev/e.jsx", + "c:/dev/x/a.ts", + "c:/dev/x/y/a.ts", + "c:/dev/z/a.ts" ], wildcardDirectories: { "c:/dev": WatchDirectoryFlags.Recursive } }; - validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); - it("with jsx=react-native, allowJs=true", () => { + }); + + describe("with implicit globbification", () => { + it("Expands 'z' to 'z/**/*'", () => { const json = { - compilerOptions: { - jsx: "react-native", - allowJs: true - } + include: ["z"] }; const expected: ParsedCommandLine = { - options: { - jsx: JsxEmit.ReactNative, - allowJs: true - }, + options: {}, errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.tsx", - "c:/dev/c.tsx", - "c:/dev/d.js", - "c:/dev/e.jsx", - ], + fileNames: [ "a.ts", "aba.ts", "abz.ts", "b.ts", "bba.ts", "bbz.ts" ].map(x => `c:/dev/z/${x}`), wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive + "c:/dev/z": WatchDirectoryFlags.Recursive } }; - validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); - it("exclude .min.js files using wildcards", () => { + }); + }); + + describe("with files or folders that begin with a .", () => { + it("that are not explicitly included", () => { + const json = { + include: [ + "x/**/*", + "w/*/*" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/x/d.ts", + "c:/dev/x/y/d.ts", + ], + wildcardDirectories: { + "c:/dev/x": WatchDirectoryFlags.Recursive, + "c:/dev/w": WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); + }); + describe("that are explicitly included", () => { + it("without wildcards", () => { const json = { - compilerOptions: { - allowJs: true - }, include: [ - "js/*.min.js" - ], - exclude: [ - "js/a*" + "x/.y/a.ts", + "c:/dev/.z/.b.ts" ] }; const expected: ParsedCommandLine = { - options: { - allowJs: true - }, + options: {}, errors: [], fileNames: [ - "c:/dev/js/d.min.js" + "c:/dev/x/.y/a.ts", + "c:/dev/.z/.b.ts" ], - wildcardDirectories: { - "c:/dev/js": WatchDirectoryFlags.None - } + wildcardDirectories: {} }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - - describe("with trailing recursive directory", () => { - it("in includes", () => { - const json = { - include: [ - "**" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createDiagnosticForConfigFile(json, 12, 4, Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**"), - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") - ], - fileNames: [], - wildcardDirectories: {} - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - it("in excludes", () => { - const json = { - include: [ - "**/*" - ], - exclude: [ - "**" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), JSON.stringify(json.exclude)) - ], - fileNames: [], - wildcardDirectories: {} - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - }); - describe("with multiple recursive directory patterns", () => { - it("in includes", () => { - const json = { - include: [ - "**/x/**/*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/x/a.ts", - "c:/dev/x/aa.ts", - "c:/dev/x/b.ts", - "c:/dev/x/y/a.ts", - "c:/dev/x/y/b.ts", - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - it("in excludes", () => { - const json = { - include: [ - "**/a.ts" - ], - exclude: [ - "**/x/**" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/z/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - }); - - describe("with parent directory symbols after a recursive directory pattern", () => { - it("in includes immediately after", () => { - const json = { - include: [ - "**/../*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createDiagnosticForConfigFile(json, 12, 9, Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/../*"), - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") - ], - fileNames: [], - wildcardDirectories: {} - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - - it("in includes after a subdirectory", () => { - const json = { - include: [ - "**/y/../*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createDiagnosticForConfigFile(json, 12, 11, Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/../*"), - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") - ], - fileNames: [], - wildcardDirectories: {} - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - - it("in excludes immediately after", () => { - const json = { - include: [ - "**/a.ts" - ], - exclude: [ - "**/.." - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createDiagnosticForConfigFile(json, 34, 7, Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/..") - ], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/x/a.ts", - "c:/dev/x/y/a.ts", - "c:/dev/z/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - - it("in excludes after a subdirectory", () => { - const json = { - include: [ - "**/a.ts" - ], - exclude: [ - "**/y/.." - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createDiagnosticForConfigFile(json, 34, 9, Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/..") - ], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/x/a.ts", - "c:/dev/x/y/a.ts", - "c:/dev/z/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - }); - - describe("with implicit globbification", () => { - it("Expands 'z' to 'z/**/*'", () => { - const json = { - include: ["z"] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ "a.ts", "aba.ts", "abz.ts", "b.ts", "bba.ts", "bbz.ts" ].map(x => `c:/dev/z/${x}`), - wildcardDirectories: { - "c:/dev/z": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); }); - }); - - describe("with files or folders that begin with a .", () => { - it("that are not explicitly included", () => { + it("with recursive wildcards that match directories", () => { const json = { include: [ - "x/**/*", - "w/*/*" + "**/.*/*" ] }; const expected: ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/dev/x/d.ts", - "c:/dev/x/y/d.ts", + "c:/dev/.z/c.ts", + "c:/dev/g.min.js/.g/g.ts", + "c:/dev/w/.u/e.ts", + "c:/dev/x/.y/a.ts" ], wildcardDirectories: { - "c:/dev/x": WatchDirectoryFlags.Recursive, - "c:/dev/w": WatchDirectoryFlags.Recursive + "c:/dev": WatchDirectoryFlags.Recursive } }; validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); }); - describe("that are explicitly included", () => { - it("without wildcards", () => { - const json = { - include: [ - "x/.y/a.ts", - "c:/dev/.z/.b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/x/.y/a.ts", - "c:/dev/.z/.b.ts" - ], - wildcardDirectories: {} - }; - validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); - }); - it("with recursive wildcards that match directories", () => { - const json = { - include: [ - "**/.*/*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/.z/c.ts", - "c:/dev/g.min.js/.g/g.ts", - "c:/dev/w/.u/e.ts", - "c:/dev/x/.y/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); - }); - it("with recursive wildcards that match nothing", () => { - const json = { - include: [ - "x/**/.y/*", - ".z/**/.*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/x/.y/a.ts", - "c:/dev/.z/.b.ts" - ], - wildcardDirectories: { - "c:/dev/.z": WatchDirectoryFlags.Recursive, - "c:/dev/x": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); - }); - it("with wildcard excludes that implicitly exclude dotted files", () => { - const json = { - include: [ - "**/.*/*" - ], - exclude: [ - "**/*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), JSON.stringify(json.exclude)) - ], - fileNames: [], - wildcardDirectories: {} - }; - validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - }); - }); - - describe("exclude or include patterns which start with **", () => { - it("can exclude dirs whose pattern starts with **", () => { + it("with recursive wildcards that match nothing", () => { const json = { - exclude: [ - "**/x" + include: [ + "x/**/.y/*", + ".z/**/.*" ] }; const expected: ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "/dev/A.ts", - "/dev/B.ts", - "/dev/a.ts", - "/dev/b.ts", - "/dev/c.d.ts", - "/dev/q/a/c/b/d.ts", - "/dev/z/a.ts", - "/dev/z/aba.ts", - "/dev/z/abz.ts", - "/dev/z/b.ts", - "/dev/z/bba.ts", - "/dev/z/bbz.ts", + "c:/dev/x/.y/a.ts", + "c:/dev/.z/.b.ts" ], wildcardDirectories: { - "/dev": WatchDirectoryFlags.Recursive + "c:/dev/.z": WatchDirectoryFlags.Recursive, + "c:/dev/x": WatchDirectoryFlags.Recursive } }; - validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); }); - it("can include dirs whose pattern starts with **", () => { + it("with wildcard excludes that implicitly exclude dotted files", () => { const json = { include: [ - "**/x", - "**/a/**/b" + "**/.*/*" + ], + exclude: [ + "**/*" ] }; const expected: ParsedCommandLine = { options: {}, - errors: [], - fileNames: [ - "/dev/x/a.ts", - "/dev/x/b.ts", - "/dev/x/y/a.ts", - "/dev/x/y/b.ts", - "/dev/q/a/c/b/d.ts", + errors: [ + createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), JSON.stringify(json.exclude)) ], - wildcardDirectories: { - "/dev": WatchDirectoryFlags.Recursive - } + fileNames: [], + wildcardDirectories: {} }; - validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); }); + }); - it("can include files in the same order on multiple platforms", () => { - function getExpected(basePath: string): ParsedCommandLine { - return { - options: {}, - errors: [], - fileNames: [ - `${basePath}Yosemite.ts`, // capital always comes before lowercase letters - `${basePath}xylophone.ts`, - `${basePath}zebra.ts` - ], - wildcardDirectories: { - [basePath.slice(0, basePath.length - 1)]: WatchDirectoryFlags.Recursive - }, - }; - } - const json = {}; - validateMatches(getExpected(caseSensitiveBasePath), json, caseSensitiveOrderingDiffersWithCaseHost, caseSensitiveBasePath); - validateMatches(getExpected(caseInsensitiveBasePath), json, caseInsensitiveOrderingDiffersWithCaseHost, caseInsensitiveBasePath); - }); - - it("when recursive symlinked directories are present", () => { - const fs = new vfs.FileSystem(/*ignoreCase*/ true, { - cwd: caseInsensitiveBasePath, files: { - "c:/dev/index.ts": "" + describe("exclude or include patterns which start with **", () => { + it("can exclude dirs whose pattern starts with **", () => { + const json = { + exclude: [ + "**/x" + ] + }; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "/dev/A.ts", + "/dev/B.ts", + "/dev/a.ts", + "/dev/b.ts", + "/dev/c.d.ts", + "/dev/q/a/c/b/d.ts", + "/dev/z/a.ts", + "/dev/z/aba.ts", + "/dev/z/abz.ts", + "/dev/z/b.ts", + "/dev/z/bba.ts", + "/dev/z/bbz.ts", + ], + wildcardDirectories: { + "/dev": WatchDirectoryFlags.Recursive } - }); - fs.mkdirpSync("c:/dev/a/b/c"); - fs.symlinkSync("c:/dev/A", "c:/dev/a/self"); - fs.symlinkSync("c:/dev/a", "c:/dev/a/b/parent"); - fs.symlinkSync("c:/dev/a", "c:/dev/a/b/c/grandparent"); - const host = new fakes.ParseConfigHost(fs); - const json = {}; + }; + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); + }); + it("can include dirs whose pattern starts with **", () => { + const json = { + include: [ + "**/x", + "**/a/**/b" + ] + }; const expected: ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/dev/index.ts" + "/dev/x/a.ts", + "/dev/x/b.ts", + "/dev/x/y/a.ts", + "/dev/x/y/b.ts", + "/dev/q/a/c/b/d.ts", ], wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive + "/dev": WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); + }); + }); + + it("can include files in the same order on multiple platforms", () => { + function getExpected(basePath: string): ParsedCommandLine { + return { + options: {}, + errors: [], + fileNames: [ + `${basePath}Yosemite.ts`, // capital always comes before lowercase letters + `${basePath}xylophone.ts`, + `${basePath}zebra.ts` + ], + wildcardDirectories: { + [basePath.slice(0, basePath.length - 1)]: WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, host, caseInsensitiveBasePath); + } + const json = {}; + validateMatches(getExpected(caseSensitiveBasePath), json, caseSensitiveOrderingDiffersWithCaseHost, caseSensitiveBasePath); + validateMatches(getExpected(caseInsensitiveBasePath), json, caseInsensitiveOrderingDiffersWithCaseHost, caseInsensitiveBasePath); + }); + + it("when recursive symlinked directories are present", () => { + const fs = new vfs.FileSystem(/*ignoreCase*/ true, { + cwd: caseInsensitiveBasePath, files: { + "c:/dev/index.ts": "" + } }); + fs.mkdirpSync("c:/dev/a/b/c"); + fs.symlinkSync("c:/dev/A", "c:/dev/a/self"); + fs.symlinkSync("c:/dev/a", "c:/dev/a/b/parent"); + fs.symlinkSync("c:/dev/a", "c:/dev/a/b/c/grandparent"); + const host = new fakes.ParseConfigHost(fs); + const json = {}; + const expected: ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/index.ts" + ], + wildcardDirectories: { + "c:/dev": WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, host, caseInsensitiveBasePath); }); +}); } diff --git a/src/testRunner/unittests/config/projectReferences.ts b/src/testRunner/unittests/config/projectReferences.ts index a70329a8db8a2..65c1772aa4881 100644 --- a/src/testRunner/unittests/config/projectReferences.ts +++ b/src/testRunner/unittests/config/projectReferences.ts @@ -1,352 +1,352 @@ namespace ts { - interface TestProjectSpecification { - configFileName?: string; - references?: readonly (string | ProjectReference)[]; - files: { [fileName: string]: string }; - outputFiles?: { [fileName: string]: string }; - config?: object; - options?: Partial; - } - interface TestSpecification { - [path: string]: TestProjectSpecification; - } +interface TestProjectSpecification { + configFileName?: string; + references?: readonly (string | ProjectReference)[]; + files: { [fileName: string]: string }; + outputFiles?: { [fileName: string]: string }; + config?: object; + options?: Partial; +} +interface TestSpecification { + [path: string]: TestProjectSpecification; +} - function assertHasError(message: string, errors: readonly Diagnostic[], diag: DiagnosticMessage) { - if (!errors.some(e => e.code === diag.code)) { - const errorString = errors.map(e => ` ${e.file ? e.file.fileName : "[global]"}: ${e.messageText}`).join("\r\n"); - assert(false, `${message}: Did not find any diagnostic for ${diag.message} in:\r\n${errorString}`); - } +function assertHasError(message: string, errors: readonly Diagnostic[], diag: DiagnosticMessage) { + if (!errors.some(e => e.code === diag.code)) { + const errorString = errors.map(e => ` ${e.file ? e.file.fileName : "[global]"}: ${e.messageText}`).join("\r\n"); + assert(false, `${message}: Did not find any diagnostic for ${diag.message} in:\r\n${errorString}`); } +} - function assertNoErrors(message: string, errors: readonly Diagnostic[]) { - if (errors && errors.length > 0) { - assert(false, `${message}: Expected no errors, but found:\r\n${errors.map(e => ` ${e.messageText}`).join("\r\n")}`); - } +function assertNoErrors(message: string, errors: readonly Diagnostic[]) { + if (errors && errors.length > 0) { + assert(false, `${message}: Expected no errors, but found:\r\n${errors.map(e => ` ${e.messageText}`).join("\r\n")}`); } +} - function combineAllPaths(...paths: string[]) { - let result = paths[0]; - for (let i = 1; i < paths.length; i++) { - result = combinePaths(result, paths[i]); - } - return result; +function combineAllPaths(...paths: string[]) { + let result = paths[0]; + for (let i = 1; i < paths.length; i++) { + result = combinePaths(result, paths[i]); } + return result; +} - const emptyModule = "export { };"; +const emptyModule = "export { };"; - /** - * Produces the text of a source file which imports all of the - * specified module names - */ - function moduleImporting(...names: string[]) { - return names.map((n, i) => `import * as mod_${i} from ${n}`).join("\r\n"); - } +/** + * Produces the text of a source file which imports all of the + * specified module names + */ +function moduleImporting(...names: string[]) { + return names.map((n, i) => `import * as mod_${i} from ${n}`).join("\r\n"); +} - function testProjectReferences(spec: TestSpecification, entryPointConfigFileName: string, checkResult: (prog: Program, host: fakes.CompilerHost) => void) { - const files = new Map(); - for (const key in spec) { - const sp = spec[key]; - const configFileName = combineAllPaths("/", key, sp.configFileName || "tsconfig.json"); - const options = { - compilerOptions: { - composite: true, - outDir: "bin", - ...sp.options - }, - references: (sp.references || []).map(r => { - if (typeof r === "string") { - return { path: r }; - } - return r; - }), - ...sp.config - }; - const configContent = JSON.stringify(options); - const outDir = options.compilerOptions.outDir; - files.set(configFileName, configContent); - for (const sourceFile of Object.keys(sp.files)) { - files.set(sourceFile, sp.files[sourceFile]); - } - if (sp.outputFiles) { - for (const outFile of Object.keys(sp.outputFiles)) { - files.set(combineAllPaths("/", key, outDir, outFile), sp.outputFiles[outFile]); +function testProjectReferences(spec: TestSpecification, entryPointConfigFileName: string, checkResult: (prog: Program, host: fakes.CompilerHost) => void) { + const files = new Map(); + for (const key in spec) { + const sp = spec[key]; + const configFileName = combineAllPaths("/", key, sp.configFileName || "tsconfig.json"); + const options = { + compilerOptions: { + composite: true, + outDir: "bin", + ...sp.options + }, + references: (sp.references || []).map(r => { + if (typeof r === "string") { + return { path: r }; } + return r; + }), + ...sp.config + }; + const configContent = JSON.stringify(options); + const outDir = options.compilerOptions.outDir; + files.set(configFileName, configContent); + for (const sourceFile of Object.keys(sp.files)) { + files.set(sourceFile, sp.files[sourceFile]); + } + if (sp.outputFiles) { + for (const outFile of Object.keys(sp.outputFiles)) { + files.set(combineAllPaths("/", key, outDir, outFile), sp.outputFiles[outFile]); } } + } - const vfsys = new vfs.FileSystem(false, { files: { "/lib.d.ts": TestFSWithWatch.libFile.content } }); - files.forEach((v, k) => { - vfsys.mkdirpSync(getDirectoryPath(k)); - vfsys.writeFileSync(k, v); - }); - const host = new fakes.CompilerHost(new fakes.System(vfsys)); + const vfsys = new vfs.FileSystem(false, { files: { "/lib.d.ts": TestFSWithWatch.libFile.content } }); + files.forEach((v, k) => { + vfsys.mkdirpSync(getDirectoryPath(k)); + vfsys.writeFileSync(k, v); + }); + const host = new fakes.CompilerHost(new fakes.System(vfsys)); - const { config, error } = readConfigFile(entryPointConfigFileName, name => host.readFile(name)); + const { config, error } = readConfigFile(entryPointConfigFileName, name => host.readFile(name)); - // We shouldn't have any errors about invalid tsconfig files in these tests - assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n")); - const file = parseJsonConfigFileContent(config, parseConfigHostFromCompilerHostLike(host), getDirectoryPath(entryPointConfigFileName), {}, entryPointConfigFileName); - file.options.configFilePath = entryPointConfigFileName; - const prog = createProgram({ - rootNames: file.fileNames, - options: file.options, - host, - projectReferences: file.projectReferences - }); - checkResult(prog, host); - } + // We shouldn't have any errors about invalid tsconfig files in these tests + assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n")); + const file = parseJsonConfigFileContent(config, parseConfigHostFromCompilerHostLike(host), getDirectoryPath(entryPointConfigFileName), {}, entryPointConfigFileName); + file.options.configFilePath = entryPointConfigFileName; + const prog = createProgram({ + rootNames: file.fileNames, + options: file.options, + host, + projectReferences: file.projectReferences + }); + checkResult(prog, host); +} - describe("unittests:: config:: project-references meta check", () => { - it("default setup was created correctly", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": emptyModule }, - references: [] - }, - "/reference": { - files: { "/secondary/b.ts": moduleImporting("../primary/a") }, - references: ["../primary"] - } - }; - testProjectReferences(spec, "/primary/tsconfig.json", prog => { - assert.isTrue(!!prog, "Program should exist"); - assertNoErrors("Sanity check should not produce errors", prog.getOptionsDiagnostics()); - }); +describe("unittests:: config:: project-references meta check", () => { + it("default setup was created correctly", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": emptyModule }, + references: [] + }, + "/reference": { + files: { "/secondary/b.ts": moduleImporting("../primary/a") }, + references: ["../primary"] + } + }; + testProjectReferences(spec, "/primary/tsconfig.json", prog => { + assert.isTrue(!!prog, "Program should exist"); + assertNoErrors("Sanity check should not produce errors", prog.getOptionsDiagnostics()); }); }); +}); - /** - * Validate that we enforce the basic settings constraints for referenced projects - */ - describe("unittests:: config:: project-references constraint checking for settings", () => { - it("errors when declaration = false", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": emptyModule }, - references: [], - options: { - declaration: false - } +/** + * Validate that we enforce the basic settings constraints for referenced projects + */ +describe("unittests:: config:: project-references constraint checking for settings", () => { + it("errors when declaration = false", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": emptyModule }, + references: [], + options: { + declaration: false } - }; + } + }; - testProjectReferences(spec, "/primary/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); - assertHasError("Reports an error about the wrong decl setting", errs, Diagnostics.Composite_projects_may_not_disable_declaration_emit); - }); + testProjectReferences(spec, "/primary/tsconfig.json", program => { + const errs = program.getOptionsDiagnostics(); + assertHasError("Reports an error about the wrong decl setting", errs, Diagnostics.Composite_projects_may_not_disable_declaration_emit); }); + }); - it("errors when the referenced project doesn't have composite:true", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": emptyModule }, - references: [], - options: { - composite: false - } - }, - "/reference": { - files: { "/secondary/b.ts": moduleImporting("../primary/a") }, - references: ["../primary"], - config: { - files: ["b.ts"] - } + it("errors when the referenced project doesn't have composite:true", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": emptyModule }, + references: [], + options: { + composite: false } - }; - testProjectReferences(spec, "/reference/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); - assertHasError("Reports an error about 'composite' not being set", errs, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true); - }); + }, + "/reference": { + files: { "/secondary/b.ts": moduleImporting("../primary/a") }, + references: ["../primary"], + config: { + files: ["b.ts"] + } + } + }; + testProjectReferences(spec, "/reference/tsconfig.json", program => { + const errs = program.getOptionsDiagnostics(); + assertHasError("Reports an error about 'composite' not being set", errs, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true); }); + }); - it("does not error when the referenced project doesn't have composite:true if its a container project", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": emptyModule }, - references: [], - options: { - composite: false - } - }, - "/reference": { - files: { "/secondary/b.ts": moduleImporting("../primary/a") }, - references: ["../primary"], + it("does not error when the referenced project doesn't have composite:true if its a container project", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": emptyModule }, + references: [], + options: { + composite: false } - }; - testProjectReferences(spec, "/reference/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); - assertNoErrors("Reports an error about 'composite' not being set", errs); - }); + }, + "/reference": { + files: { "/secondary/b.ts": moduleImporting("../primary/a") }, + references: ["../primary"], + } + }; + testProjectReferences(spec, "/reference/tsconfig.json", program => { + const errs = program.getOptionsDiagnostics(); + assertNoErrors("Reports an error about 'composite' not being set", errs); }); + }); - it("errors when the file list is not exhaustive", () => { - const spec: TestSpecification = { - "/primary": { - files: { - "/primary/a.ts": "import * as b from './b'", - "/primary/b.ts": "export {}" - }, - config: { - files: ["a.ts"] - } + it("errors when the file list is not exhaustive", () => { + const spec: TestSpecification = { + "/primary": { + files: { + "/primary/a.ts": "import * as b from './b'", + "/primary/b.ts": "export {}" + }, + config: { + files: ["a.ts"] } - }; + } + }; - testProjectReferences(spec, "/primary/tsconfig.json", program => { - const errs = program.getSemanticDiagnostics(program.getSourceFile("/primary/a.ts")); - assertHasError("Reports an error about b.ts not being in the list", errs, Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern); - }); + testProjectReferences(spec, "/primary/tsconfig.json", program => { + const errs = program.getSemanticDiagnostics(program.getSourceFile("/primary/a.ts")); + assertHasError("Reports an error about b.ts not being in the list", errs, Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern); }); + }); - it("errors when the referenced project doesn't exist", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": emptyModule }, - references: ["../foo"] - } - }; - testProjectReferences(spec, "/primary/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); - assertHasError("Reports an error about a missing file", errs, Diagnostics.File_0_not_found); - }); + it("errors when the referenced project doesn't exist", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": emptyModule }, + references: ["../foo"] + } + }; + testProjectReferences(spec, "/primary/tsconfig.json", program => { + const errs = program.getOptionsDiagnostics(); + assertHasError("Reports an error about a missing file", errs, Diagnostics.File_0_not_found); }); + }); - it("errors when a prepended project reference doesn't set outFile", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": emptyModule }, - references: [{ path: "../someProj", prepend: true }] - }, - "/someProj": { - files: { "/someProj/b.ts": "const x = 100;" } - } - }; - testProjectReferences(spec, "/primary/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); - assertHasError("Reports an error about outFile not being set", errs, Diagnostics.Cannot_prepend_project_0_because_it_does_not_have_outFile_set); - }); + it("errors when a prepended project reference doesn't set outFile", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": emptyModule }, + references: [{ path: "../someProj", prepend: true }] + }, + "/someProj": { + files: { "/someProj/b.ts": "const x = 100;" } + } + }; + testProjectReferences(spec, "/primary/tsconfig.json", program => { + const errs = program.getOptionsDiagnostics(); + assertHasError("Reports an error about outFile not being set", errs, Diagnostics.Cannot_prepend_project_0_because_it_does_not_have_outFile_set); }); + }); - it("errors when a prepended project reference output doesn't exist", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": "const y = x;" }, - references: [{ path: "../someProj", prepend: true }] - }, - "/someProj": { - files: { "/someProj/b.ts": "const x = 100;" }, - options: { outFile: "foo.js" } - } - }; - testProjectReferences(spec, "/primary/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); - assertHasError("Reports an error about outFile being missing", errs, Diagnostics.Output_file_0_from_project_1_does_not_exist); - }); + it("errors when a prepended project reference output doesn't exist", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": "const y = x;" }, + references: [{ path: "../someProj", prepend: true }] + }, + "/someProj": { + files: { "/someProj/b.ts": "const x = 100;" }, + options: { outFile: "foo.js" } + } + }; + testProjectReferences(spec, "/primary/tsconfig.json", program => { + const errs = program.getOptionsDiagnostics(); + assertHasError("Reports an error about outFile being missing", errs, Diagnostics.Output_file_0_from_project_1_does_not_exist); }); }); +}); - /** - * Path mapping behavior - */ - describe("unittests:: config:: project-references path mapping", () => { - it("redirects to the output .d.ts file", () => { - const spec: TestSpecification = { - "/alpha": { - files: { "/alpha/a.ts": "export const m: number = 3;" }, - references: [], - outputFiles: { "a.d.ts": emptyModule } - }, - "/beta": { - files: { "/beta/b.ts": "import { m } from '../alpha/a'" }, - references: ["../alpha"] - } - }; - testProjectReferences(spec, "/beta/tsconfig.json", program => { - assertNoErrors("File setup should be correct", program.getOptionsDiagnostics()); - assertHasError("Found a type error", program.getSemanticDiagnostics(), Diagnostics.Module_0_has_no_exported_member_1); - }); +/** + * Path mapping behavior + */ +describe("unittests:: config:: project-references path mapping", () => { + it("redirects to the output .d.ts file", () => { + const spec: TestSpecification = { + "/alpha": { + files: { "/alpha/a.ts": "export const m: number = 3;" }, + references: [], + outputFiles: { "a.d.ts": emptyModule } + }, + "/beta": { + files: { "/beta/b.ts": "import { m } from '../alpha/a'" }, + references: ["../alpha"] + } + }; + testProjectReferences(spec, "/beta/tsconfig.json", program => { + assertNoErrors("File setup should be correct", program.getOptionsDiagnostics()); + assertHasError("Found a type error", program.getSemanticDiagnostics(), Diagnostics.Module_0_has_no_exported_member_1); }); }); +}); - describe("unittests:: config:: project-references nice-behavior", () => { - it("issues a nice error when the input file is missing", () => { - const spec: TestSpecification = { - "/alpha": { - files: { "/alpha/a.ts": "export const m: number = 3;" }, - references: [] - }, - "/beta": { - files: { "/beta/b.ts": "import { m } from '../alpha/a'" }, - references: ["../alpha"] - } - }; - testProjectReferences(spec, "/beta/tsconfig.json", program => { - assertHasError("Issues a useful error", program.getSemanticDiagnostics(), Diagnostics.Output_file_0_has_not_been_built_from_source_file_1); - }); +describe("unittests:: config:: project-references nice-behavior", () => { + it("issues a nice error when the input file is missing", () => { + const spec: TestSpecification = { + "/alpha": { + files: { "/alpha/a.ts": "export const m: number = 3;" }, + references: [] + }, + "/beta": { + files: { "/beta/b.ts": "import { m } from '../alpha/a'" }, + references: ["../alpha"] + } + }; + testProjectReferences(spec, "/beta/tsconfig.json", program => { + assertHasError("Issues a useful error", program.getSemanticDiagnostics(), Diagnostics.Output_file_0_has_not_been_built_from_source_file_1); }); + }); - it("issues a nice error when the input file is missing when module reference is not relative", () => { - const spec: TestSpecification = { - "/alpha": { - files: { "/alpha/a.ts": "export const m: number = 3;" }, - references: [] - }, - "/beta": { - files: { "/beta/b.ts": "import { m } from '@alpha/a'" }, - references: ["../alpha"], - options: { - baseUrl: "./", - paths: { - "@alpha/*": ["/alpha/*"] - } + it("issues a nice error when the input file is missing when module reference is not relative", () => { + const spec: TestSpecification = { + "/alpha": { + files: { "/alpha/a.ts": "export const m: number = 3;" }, + references: [] + }, + "/beta": { + files: { "/beta/b.ts": "import { m } from '@alpha/a'" }, + references: ["../alpha"], + options: { + baseUrl: "./", + paths: { + "@alpha/*": ["/alpha/*"] } } - }; - testProjectReferences(spec, "/beta/tsconfig.json", program => { - assertHasError("Issues a useful error", program.getSemanticDiagnostics(), Diagnostics.Output_file_0_has_not_been_built_from_source_file_1); - }); + } + }; + testProjectReferences(spec, "/beta/tsconfig.json", program => { + assertHasError("Issues a useful error", program.getSemanticDiagnostics(), Diagnostics.Output_file_0_has_not_been_built_from_source_file_1); }); }); +}); - /** - * 'composite' behavior - */ - describe("unittests:: config:: project-references behavior changes under composite: true", () => { - it("doesn't infer the rootDir from source paths", () => { - const spec: TestSpecification = { - "/alpha": { - files: { "/alpha/src/a.ts": "export const m: number = 3;" }, - options: { - declaration: true, - outDir: "bin" - }, - references: [] - } - }; - testProjectReferences(spec, "/alpha/tsconfig.json", (program, host) => { - program.emit(); - assert.deepEqual(host.outputs.map(e => e.file).sort(), ["/alpha/bin/src/a.d.ts", "/alpha/bin/src/a.js", "/alpha/bin/tsconfig.tsbuildinfo"]); - }); +/** + * 'composite' behavior + */ +describe("unittests:: config:: project-references behavior changes under composite: true", () => { + it("doesn't infer the rootDir from source paths", () => { + const spec: TestSpecification = { + "/alpha": { + files: { "/alpha/src/a.ts": "export const m: number = 3;" }, + options: { + declaration: true, + outDir: "bin" + }, + references: [] + } + }; + testProjectReferences(spec, "/alpha/tsconfig.json", (program, host) => { + program.emit(); + assert.deepEqual(host.outputs.map(e => e.file).sort(), ["/alpha/bin/src/a.d.ts", "/alpha/bin/src/a.js", "/alpha/bin/tsconfig.tsbuildinfo"]); }); }); +}); - describe("unittests:: config:: project-references errors when a file in a composite project occurs outside the root", () => { - it("Errors when a file is outside the rootdir", () => { - const spec: TestSpecification = { - "/alpha": { - files: { "/alpha/src/a.ts": "import * from '../../beta/b'", "/beta/b.ts": "export { }" }, - options: { - declaration: true, - outDir: "bin" - }, - references: [] - } - }; - testProjectReferences(spec, "/alpha/tsconfig.json", (program) => { - const semanticDiagnostics = program.getSemanticDiagnostics(program.getSourceFile("/alpha/src/a.ts")); - assertHasError("Issues an error about the rootDir", semanticDiagnostics, Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files); - assertHasError("Issues an error about the fileList", semanticDiagnostics, Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern); - }); +describe("unittests:: config:: project-references errors when a file in a composite project occurs outside the root", () => { + it("Errors when a file is outside the rootdir", () => { + const spec: TestSpecification = { + "/alpha": { + files: { "/alpha/src/a.ts": "import * from '../../beta/b'", "/beta/b.ts": "export { }" }, + options: { + declaration: true, + outDir: "bin" + }, + references: [] + } + }; + testProjectReferences(spec, "/alpha/tsconfig.json", (program) => { + const semanticDiagnostics = program.getSemanticDiagnostics(program.getSourceFile("/alpha/src/a.ts")); + assertHasError("Issues an error about the rootDir", semanticDiagnostics, Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files); + assertHasError("Issues an error about the fileList", semanticDiagnostics, Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern); }); }); +}); } diff --git a/src/testRunner/unittests/config/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts index f8905e3412677..89ccd126a538b 100644 --- a/src/testRunner/unittests/config/showConfig.ts +++ b/src/testRunner/unittests/config/showConfig.ts @@ -1,193 +1,193 @@ namespace ts { - describe("unittests:: config:: showConfig", () => { - function showTSConfigCorrectly(name: string, commandLinesArgs: string[], configJson?: object) { - describe(name, () => { - const outputFileName = `showConfig/${name.replace(/[^a-z0-9\-./ ]/ig, "")}/tsconfig.json`; - - it(`Correct output for ${outputFileName}`, () => { - const cwd = `/${name}`; - const configPath = combinePaths(cwd, "tsconfig.json"); - const configContents = configJson ? JSON.stringify(configJson) : undefined; - const configParseHost: ParseConfigFileHost = { - fileExists: path => - comparePaths(getNormalizedAbsolutePath(path, cwd), configPath) === Comparison.EqualTo ? true : false, - getCurrentDirectory() { return cwd; }, - useCaseSensitiveFileNames: true, - onUnRecoverableConfigFileDiagnostic: d => { - throw new Error(flattenDiagnosticMessageText(d.messageText, "\n")); - }, - readDirectory() { return []; }, - readFile: path => - comparePaths(getNormalizedAbsolutePath(path, cwd), configPath) === Comparison.EqualTo ? configContents : undefined, - }; - let commandLine = parseCommandLine(commandLinesArgs); - if (commandLine.options.project) { - const result = getParsedCommandLineOfConfigFile(commandLine.options.project, commandLine.options, configParseHost); - if (result) { - commandLine = result; - } +describe("unittests:: config:: showConfig", () => { + function showTSConfigCorrectly(name: string, commandLinesArgs: string[], configJson?: object) { + describe(name, () => { + const outputFileName = `showConfig/${name.replace(/[^a-z0-9\-./ ]/ig, "")}/tsconfig.json`; + + it(`Correct output for ${outputFileName}`, () => { + const cwd = `/${name}`; + const configPath = combinePaths(cwd, "tsconfig.json"); + const configContents = configJson ? JSON.stringify(configJson) : undefined; + const configParseHost: ParseConfigFileHost = { + fileExists: path => + comparePaths(getNormalizedAbsolutePath(path, cwd), configPath) === Comparison.EqualTo ? true : false, + getCurrentDirectory() { return cwd; }, + useCaseSensitiveFileNames: true, + onUnRecoverableConfigFileDiagnostic: d => { + throw new Error(flattenDiagnosticMessageText(d.messageText, "\n")); + }, + readDirectory() { return []; }, + readFile: path => + comparePaths(getNormalizedAbsolutePath(path, cwd), configPath) === Comparison.EqualTo ? configContents : undefined, + }; + let commandLine = parseCommandLine(commandLinesArgs); + if (commandLine.options.project) { + const result = getParsedCommandLineOfConfigFile(commandLine.options.project, commandLine.options, configParseHost); + if (result) { + commandLine = result; } - const initResult = convertToTSConfig(commandLine, configPath, configParseHost); + } + const initResult = convertToTSConfig(commandLine, configPath, configParseHost); - // eslint-disable-next-line no-null/no-null - Harness.Baseline.runBaseline(outputFileName, JSON.stringify(initResult, null, 4) + "\n"); - }); + // eslint-disable-next-line no-null/no-null + Harness.Baseline.runBaseline(outputFileName, JSON.stringify(initResult, null, 4) + "\n"); }); - } - - showTSConfigCorrectly("Default initialized TSConfig", ["--showConfig"]); + }); + } - showTSConfigCorrectly("Show TSConfig with files options", ["--showConfig", "file0.ts", "file1.ts", "file2.ts"]); + showTSConfigCorrectly("Default initialized TSConfig", ["--showConfig"]); - showTSConfigCorrectly("Show TSConfig with boolean value compiler options", ["--showConfig", "--noUnusedLocals"]); + showTSConfigCorrectly("Show TSConfig with files options", ["--showConfig", "file0.ts", "file1.ts", "file2.ts"]); - showTSConfigCorrectly("Show TSConfig with enum value compiler options", ["--showConfig", "--target", "es5", "--jsx", "react"]); + showTSConfigCorrectly("Show TSConfig with boolean value compiler options", ["--showConfig", "--noUnusedLocals"]); - showTSConfigCorrectly("Show TSConfig with list compiler options", ["--showConfig", "--types", "jquery,mocha"]); + showTSConfigCorrectly("Show TSConfig with enum value compiler options", ["--showConfig", "--target", "es5", "--jsx", "react"]); - showTSConfigCorrectly("Show TSConfig with list compiler options with enum value", ["--showConfig", "--lib", "es5,es2015.core"]); + showTSConfigCorrectly("Show TSConfig with list compiler options", ["--showConfig", "--types", "jquery,mocha"]); - showTSConfigCorrectly("Show TSConfig with incorrect compiler option", ["--showConfig", "--someNonExistOption"]); + showTSConfigCorrectly("Show TSConfig with list compiler options with enum value", ["--showConfig", "--lib", "es5,es2015.core"]); - showTSConfigCorrectly("Show TSConfig with incorrect compiler option value", ["--showConfig", "--lib", "nonExistLib,es5,es2015.promise"]); + showTSConfigCorrectly("Show TSConfig with incorrect compiler option", ["--showConfig", "--someNonExistOption"]); - showTSConfigCorrectly("Show TSConfig with advanced options", ["--showConfig", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]); + showTSConfigCorrectly("Show TSConfig with incorrect compiler option value", ["--showConfig", "--lib", "nonExistLib,es5,es2015.promise"]); - showTSConfigCorrectly("Show TSConfig with compileOnSave and more", ["-p", "tsconfig.json"], { - compilerOptions: { - esModuleInterop: true, - target: "es5", - module: "commonjs", - strict: true, - }, - compileOnSave: true, - exclude: [ - "dist" - ], - files: [], - include: [ - "src/*" - ], - references: [ - { path: "./test" } - ], - }); + showTSConfigCorrectly("Show TSConfig with advanced options", ["--showConfig", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]); - // Regression test for https://github.com/Microsoft/TypeScript/issues/28836 - showTSConfigCorrectly("Show TSConfig with paths and more", ["-p", "tsconfig.json"], { - compilerOptions: { - allowJs: true, - outDir: "./lib", - esModuleInterop: true, - module: "commonjs", - moduleResolution: "node", - target: "ES2017", - sourceMap: true, - baseUrl: ".", - paths: { - "@root/*": ["./*"], - "@configs/*": ["src/configs/*"], - "@common/*": ["src/common/*"], - "*": [ - "node_modules/*", - "src/types/*" - ] - }, - experimentalDecorators: true, - emitDecoratorMetadata: true, - resolveJsonModule: true - }, - include: [ - "./src/**/*" - ] - }); + showTSConfigCorrectly("Show TSConfig with compileOnSave and more", ["-p", "tsconfig.json"], { + compilerOptions: { + esModuleInterop: true, + target: "es5", + module: "commonjs", + strict: true, + }, + compileOnSave: true, + exclude: [ + "dist" + ], + files: [], + include: [ + "src/*" + ], + references: [ + { path: "./test" } + ], + }); - showTSConfigCorrectly("Show TSConfig with watch options", ["-p", "tsconfig.json"], { - watchOptions: { - watchFile: "DynamicPriorityPolling" + // Regression test for https://github.com/Microsoft/TypeScript/issues/28836 + showTSConfigCorrectly("Show TSConfig with paths and more", ["-p", "tsconfig.json"], { + compilerOptions: { + allowJs: true, + outDir: "./lib", + esModuleInterop: true, + module: "commonjs", + moduleResolution: "node", + target: "ES2017", + sourceMap: true, + baseUrl: ".", + paths: { + "@root/*": ["./*"], + "@configs/*": ["src/configs/*"], + "@common/*": ["src/common/*"], + "*": [ + "node_modules/*", + "src/types/*" + ] }, - include: [ - "./src/**/*" - ] - }); - - // Bulk validation of all option declarations - for (const option of optionDeclarations) { - baselineOption(option, /*isCompilerOptions*/ true); - } + experimentalDecorators: true, + emitDecoratorMetadata: true, + resolveJsonModule: true + }, + include: [ + "./src/**/*" + ] + }); - for (const option of optionsForWatch) { - baselineOption(option, /*isCompilerOptions*/ false); - } + showTSConfigCorrectly("Show TSConfig with watch options", ["-p", "tsconfig.json"], { + watchOptions: { + watchFile: "DynamicPriorityPolling" + }, + include: [ + "./src/**/*" + ] + }); - function baselineOption(option: CommandLineOption, isCompilerOptions: boolean) { - if (option.name === "project") return; - let args: string[]; - let optionValue: object | undefined; - switch (option.type) { - case "boolean": { - if (option.isTSConfigOnly) { - args = ["-p", "tsconfig.json"]; - optionValue = { [option.name]: true }; - } - else { - args = [`--${option.name}`]; - } - break; + // Bulk validation of all option declarations + for (const option of optionDeclarations) { + baselineOption(option, /*isCompilerOptions*/ true); + } + + for (const option of optionsForWatch) { + baselineOption(option, /*isCompilerOptions*/ false); + } + + function baselineOption(option: CommandLineOption, isCompilerOptions: boolean) { + if (option.name === "project") return; + let args: string[]; + let optionValue: object | undefined; + switch (option.type) { + case "boolean": { + if (option.isTSConfigOnly) { + args = ["-p", "tsconfig.json"]; + optionValue = { [option.name]: true }; } - case "list": { - if (option.isTSConfigOnly) { - args = ["-p", "tsconfig.json"]; - optionValue = { [option.name]: [] }; - } - else { - args = [`--${option.name}`]; - } - break; + else { + args = [`--${option.name}`]; } - case "string": { - if (option.isTSConfigOnly) { - args = ["-p", "tsconfig.json"]; - optionValue = { [option.name]: "someString" }; - } - else { - args = [`--${option.name}`, "someString"]; - } - break; + break; + } + case "list": { + if (option.isTSConfigOnly) { + args = ["-p", "tsconfig.json"]; + optionValue = { [option.name]: [] }; } - case "number": { - if (option.isTSConfigOnly) { - args = ["-p", "tsconfig.json"]; - optionValue = { [option.name]: 0 }; - } - else { - args = [`--${option.name}`, "0"]; - } - break; + else { + args = [`--${option.name}`]; } - case "object": { + break; + } + case "string": { + if (option.isTSConfigOnly) { args = ["-p", "tsconfig.json"]; - optionValue = { [option.name]: {} }; - break; + optionValue = { [option.name]: "someString" }; } - default: { - const iterResult = option.type.keys().next(); - if (iterResult.done) return Debug.fail("Expected 'option.type' to have entries"); - const val = iterResult.value; - if (option.isTSConfigOnly) { - args = ["-p", "tsconfig.json"]; - optionValue = { [option.name]: val }; - } - else { - args = [`--${option.name}`, val]; - } - break; + else { + args = [`--${option.name}`, "someString"]; } + break; + } + case "number": { + if (option.isTSConfigOnly) { + args = ["-p", "tsconfig.json"]; + optionValue = { [option.name]: 0 }; + } + else { + args = [`--${option.name}`, "0"]; + } + break; + } + case "object": { + args = ["-p", "tsconfig.json"]; + optionValue = { [option.name]: {} }; + break; + } + default: { + const iterResult = option.type.keys().next(); + if (iterResult.done) return Debug.fail("Expected 'option.type' to have entries"); + const val = iterResult.value; + if (option.isTSConfigOnly) { + args = ["-p", "tsconfig.json"]; + optionValue = { [option.name]: val }; + } + else { + args = [`--${option.name}`, val]; + } + break; } - - const configObject = optionValue && - (isCompilerOptions ? { compilerOptions: optionValue } : { watchOptions: optionValue }); - showTSConfigCorrectly(`Shows tsconfig for single option/${option.name}`, args, configObject); } - }); + + const configObject = optionValue && + (isCompilerOptions ? { compilerOptions: optionValue } : { watchOptions: optionValue }); + showTSConfigCorrectly(`Shows tsconfig for single option/${option.name}`, args, configObject); + } +}); } diff --git a/src/testRunner/unittests/config/tsconfigParsing.ts b/src/testRunner/unittests/config/tsconfigParsing.ts index 3c6dfb6a53636..5770ef573c31b 100644 --- a/src/testRunner/unittests/config/tsconfigParsing.ts +++ b/src/testRunner/unittests/config/tsconfigParsing.ts @@ -1,104 +1,104 @@ namespace ts { - describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", () => { - function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic[] }) { +describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", () => { + function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic[] }) { + const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); + assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject)); + } + + function assertParseErrorWithExcludesKeyword(jsonText: string) { + { const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); - assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject)); + const parsedCommand = parseJsonConfigFileContent(parsed.config, sys, "tests/cases/unittests"); + assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && + parsedCommand.errors[0].code === Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); } - - function assertParseErrorWithExcludesKeyword(jsonText: string) { - { - const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); - const parsedCommand = parseJsonConfigFileContent(parsed.config, sys, "tests/cases/unittests"); - assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && - parsedCommand.errors[0].code === Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); - } - { - const parsed = parseJsonText("/apath/tsconfig.json", jsonText); - const parsedCommand = parseJsonSourceFileConfigFileContent(parsed, sys, "tests/cases/unittests"); - assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && - parsedCommand.errors[0].code === Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); - } + { + const parsed = parseJsonText("/apath/tsconfig.json", jsonText); + const parsedCommand = parseJsonSourceFileConfigFileContent(parsed, sys, "tests/cases/unittests"); + assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && + parsedCommand.errors[0].code === Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); } - - function getParsedCommandJson(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { - const parsed = parseConfigFileTextToJson(configFileName, jsonText); - const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet); - const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); - return parseJsonConfigFileContent(parsed.config, host, basePath, /*existingOptions*/ undefined, configFileName); + } + + function getParsedCommandJson(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { + const parsed = parseConfigFileTextToJson(configFileName, jsonText); + const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet); + const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); + return parseJsonConfigFileContent(parsed.config, host, basePath, /*existingOptions*/ undefined, configFileName); + } + + function getParsedCommandJsonNode(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { + const parsed = parseJsonText(configFileName, jsonText); + const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet); + const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); + return parseJsonSourceFileConfigFileContent(parsed, host, basePath, /*existingOptions*/ undefined, configFileName); + } + + function assertParseFileList(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedFileList: string[]) { + { + const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); + assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); } - - function getParsedCommandJsonNode(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { - const parsed = parseJsonText(configFileName, jsonText); - const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet); - const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); - return parseJsonSourceFileConfigFileContent(parsed, host, basePath, /*existingOptions*/ undefined, configFileName); + { + const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); + assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); } + } - function assertParseFileList(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedFileList: string[]) { - { - const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); - assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); - } - { - const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); - assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); - } + function assertParseFileDiagnostics(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedDiagnosticCode: number, noLocation?: boolean) { + { + const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); + assert.isTrue(parsed.errors.length >= 0); + assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); } - - function assertParseFileDiagnostics(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedDiagnosticCode: number, noLocation?: boolean) { - { - const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); - assert.isTrue(parsed.errors.length >= 0); - assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); - } - { - const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); - assert.isTrue(parsed.errors.length >= 0); - assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); - if (!noLocation) { - assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode && e.file && e.start && e.length).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)} with location information`); - } + { + const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); + assert.isTrue(parsed.errors.length >= 0); + assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); + if (!noLocation) { + assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode && e.file && e.start && e.length).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)} with location information`); } } + } - function assertParseFileDiagnosticsExclusion(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedExcludedDiagnosticCode: number) { - { - const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); - assert.isTrue(parsed.errors.length >= 0); - assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`); - } - { - const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); - assert.isTrue(parsed.errors.length >= 0); - assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`); - } + function assertParseFileDiagnosticsExclusion(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedExcludedDiagnosticCode: number) { + { + const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); + assert.isTrue(parsed.errors.length >= 0); + assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`); } + { + const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); + assert.isTrue(parsed.errors.length >= 0); + assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`); + } + } - it("returns empty config for file with only whitespaces", () => { - assertParseResult("", { config : {} }); - assertParseResult(" ", { config : {} }); - }); + it("returns empty config for file with only whitespaces", () => { + assertParseResult("", { config : {} }); + assertParseResult(" ", { config : {} }); + }); - it("returns empty config for file with comments only", () => { - assertParseResult("// Comment", { config: {} }); - assertParseResult("/* Comment*/", { config: {} }); - }); + it("returns empty config for file with comments only", () => { + assertParseResult("// Comment", { config: {} }); + assertParseResult("/* Comment*/", { config: {} }); + }); - it("returns empty config when config is empty object", () => { - assertParseResult("{}", { config: {} }); - }); + it("returns empty config when config is empty object", () => { + assertParseResult("{}", { config: {} }); + }); - it("returns config object without comments", () => { - assertParseResult( - `{ // Excluded files + it("returns config object without comments", () => { + assertParseResult( + `{ // Excluded files "exclude": [ // Exclude d.ts "file.d.ts" ] }`, { config: { exclude: ["file.d.ts"] } }); - assertParseResult( - `{ + assertParseResult( + `{ /* Excluded Files */ @@ -106,74 +106,74 @@ namespace ts { /* multiline comments can be in the middle of a line */"file.d.ts" ] }`, { config: { exclude: ["file.d.ts"] } }); - }); + }); - it("keeps string content untouched", () => { - assertParseResult( - `{ + it("keeps string content untouched", () => { + assertParseResult( + `{ "exclude": [ "xx//file.d.ts" ] }`, { config: { exclude: ["xx//file.d.ts"] } }); - assertParseResult( - `{ + assertParseResult( + `{ "exclude": [ "xx/*file.d.ts*/" ] }`, { config: { exclude: ["xx/*file.d.ts*/"] } }); - }); + }); - it("handles escaped characters in strings correctly", () => { - assertParseResult( - `{ + it("handles escaped characters in strings correctly", () => { + assertParseResult( + `{ "exclude": [ "xx\\"//files" ] }`, { config: { exclude: ["xx\"//files"] } }); - assertParseResult( - `{ + assertParseResult( + `{ "exclude": [ "xx\\\\" // end of line comment ] }`, { config: { exclude: ["xx\\"] } }); - }); - - it("returns object with error when json is invalid", () => { - const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", "invalid"); - assert.deepEqual(parsed.config, {}); - const expected = createCompilerDiagnostic(Diagnostics._0_expected, "{"); - const error = parsed.error!; - assert.equal(error.messageText, expected.messageText); - assert.equal(error.category, expected.category); - assert.equal(error.code, expected.code); - assert.equal(error.start, 0); - assert.equal(error.length, "invalid".length); - }); - - it("returns object when users correctly specify library", () => { - assertParseResult( - `{ + }); + + it("returns object with error when json is invalid", () => { + const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", "invalid"); + assert.deepEqual(parsed.config, {}); + const expected = createCompilerDiagnostic(Diagnostics._0_expected, "{"); + const error = parsed.error!; + assert.equal(error.messageText, expected.messageText); + assert.equal(error.category, expected.category); + assert.equal(error.code, expected.code); + assert.equal(error.start, 0); + assert.equal(error.length, "invalid".length); + }); + + it("returns object when users correctly specify library", () => { + assertParseResult( + `{ "compilerOptions": { "lib": ["es5"] } }`, { - config: { compilerOptions: { lib: ["es5"] } } - }); + config: { compilerOptions: { lib: ["es5"] } } + }); - assertParseResult( - `{ + assertParseResult( + `{ "compilerOptions": { "lib": ["es5", "es6"] } }`, { - config: { compilerOptions: { lib: ["es5", "es6"] } } - }); - }); + config: { compilerOptions: { lib: ["es5", "es6"] } } + }); + }); - it("returns error when tsconfig have excludes", () => { - assertParseErrorWithExcludesKeyword( - `{ + it("returns error when tsconfig have excludes", () => { + assertParseErrorWithExcludesKeyword( + `{ "compilerOptions": { "lib": ["es5"] }, @@ -181,86 +181,86 @@ namespace ts { "foge.ts" ] }`); - }); - - it("ignore dotted files and folders", () => { - assertParseFileList( - `{}`, - "tsconfig.json", - "/apath", - ["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"], - ["/apath/test.ts"] - ); - }); - - it("allow dotted files and folders when explicitly requested", () => { - assertParseFileList( - `{ + }); + + it("ignore dotted files and folders", () => { + assertParseFileList( + `{}`, + "tsconfig.json", + "/apath", + ["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"], + ["/apath/test.ts"] + ); + }); + + it("allow dotted files and folders when explicitly requested", () => { + assertParseFileList( + `{ "files": ["/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"] }`, - "tsconfig.json", - "/apath", - ["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"], - ["/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"] - ); - }); - - it("exclude outDir unless overridden", () => { - const tsconfigWithoutExclude = - `{ + "tsconfig.json", + "/apath", + ["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"], + ["/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"] + ); + }); + + it("exclude outDir unless overridden", () => { + const tsconfigWithoutExclude = + `{ "compilerOptions": { "outDir": "bin" } }`; - const tsconfigWithExclude = - `{ + const tsconfigWithExclude = + `{ "compilerOptions": { "outDir": "bin" }, "exclude": [ "obj" ] }`; - const rootDir = "/"; - const allFiles = ["/bin/a.ts", "/b.ts"]; - const expectedFiles = ["/b.ts"]; - assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles); - assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles); - }); - - it("exclude declarationDir unless overridden", () => { - const tsconfigWithoutExclude = - `{ + const rootDir = "/"; + const allFiles = ["/bin/a.ts", "/b.ts"]; + const expectedFiles = ["/b.ts"]; + assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles); + assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles); + }); + + it("exclude declarationDir unless overridden", () => { + const tsconfigWithoutExclude = + `{ "compilerOptions": { "declarationDir": "declarations" } }`; - const tsconfigWithExclude = - `{ + const tsconfigWithExclude = + `{ "compilerOptions": { "declarationDir": "declarations" }, "exclude": [ "types" ] }`; - const rootDir = "/"; - const allFiles = ["/declarations/a.d.ts", "/a.ts"]; - const expectedFiles = ["/a.ts"]; - - assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles); - assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles); - }); - - it("implicitly exclude common package folders", () => { - assertParseFileList( - `{}`, - "tsconfig.json", - "/", - ["/node_modules/a.ts", "/bower_components/b.ts", "/jspm_packages/c.ts", "/d.ts", "/folder/e.ts"], - ["/d.ts", "/folder/e.ts"] - ); - }); - - it("parse and re-emit tsconfig.json file with diagnostics", () => { - const content = `{ + const rootDir = "/"; + const allFiles = ["/declarations/a.d.ts", "/a.ts"]; + const expectedFiles = ["/a.ts"]; + + assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles); + assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles); + }); + + it("implicitly exclude common package folders", () => { + assertParseFileList( + `{}`, + "tsconfig.json", + "/", + ["/node_modules/a.ts", "/bower_components/b.ts", "/jspm_packages/c.ts", "/d.ts", "/folder/e.ts"], + ["/d.ts", "/folder/e.ts"] + ); + }); + + it("parse and re-emit tsconfig.json file with diagnostics", () => { + const content = `{ "compilerOptions": { "allowJs": true // Some comments @@ -268,149 +268,149 @@ namespace ts { } "files": ["file1.ts"] }`; - const result = parseJsonText("config.json", content); - const diagnostics = result.parseDiagnostics; - const configJsonObject = convertToObject(result, diagnostics); - const expectedResult = { - compilerOptions: { - allowJs: true, - outDir: "bin" - }, - files: ["file1.ts"] - }; - assert.isTrue(diagnostics.length === 2); - assert.equal(JSON.stringify(configJsonObject), JSON.stringify(expectedResult)); - }); - - it("generates errors for empty files list", () => { - const content = `{ + const result = parseJsonText("config.json", content); + const diagnostics = result.parseDiagnostics; + const configJsonObject = convertToObject(result, diagnostics); + const expectedResult = { + compilerOptions: { + allowJs: true, + outDir: "bin" + }, + files: ["file1.ts"] + }; + assert.isTrue(diagnostics.length === 2); + assert.equal(JSON.stringify(configJsonObject), JSON.stringify(expectedResult)); + }); + + it("generates errors for empty files list", () => { + const content = `{ "files": [] }`; - assertParseFileDiagnostics(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.The_files_list_in_config_file_0_is_empty.code); - }); - - it("generates errors for empty files list when no references are provided", () => { - const content = `{ + assertParseFileDiagnostics(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + Diagnostics.The_files_list_in_config_file_0_is_empty.code); + }); + + it("generates errors for empty files list when no references are provided", () => { + const content = `{ "files": [], "references": [] }`; - assertParseFileDiagnostics(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.The_files_list_in_config_file_0_is_empty.code); - }); - - it("does not generate errors for empty files list when one or more references are provided", () => { - const content = `{ + assertParseFileDiagnostics(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + Diagnostics.The_files_list_in_config_file_0_is_empty.code); + }); + + it("does not generate errors for empty files list when one or more references are provided", () => { + const content = `{ "files": [], "references": [{ "path": "/apath" }] }`; - assertParseFileDiagnosticsExclusion(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.The_files_list_in_config_file_0_is_empty.code); - }); - - it("generates errors for directory with no .ts files", () => { - const content = `{ + assertParseFileDiagnosticsExclusion(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + Diagnostics.The_files_list_in_config_file_0_is_empty.code); + }); + + it("generates errors for directory with no .ts files", () => { + const content = `{ }`; - assertParseFileDiagnostics(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.js"], - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, - /*noLocation*/ true); - }); - - it("generates errors for empty directory", () => { - const content = `{ + assertParseFileDiagnostics(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.js"], + Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, + /*noLocation*/ true); + }); + + it("generates errors for empty directory", () => { + const content = `{ "compilerOptions": { "allowJs": true } }`; - assertParseFileDiagnostics(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - [], - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, - /*noLocation*/ true); - }); - - it("generates errors for empty include", () => { - const content = `{ + assertParseFileDiagnostics(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + [], + Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, + /*noLocation*/ true); + }); + + it("generates errors for empty include", () => { + const content = `{ "include": [] }`; - assertParseFileDiagnostics(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, - /*noLocation*/ true); - }); - - it("generates errors for includes with outDir", () => { - const content = `{ + assertParseFileDiagnostics(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, + /*noLocation*/ true); + }); + + it("generates errors for includes with outDir", () => { + const content = `{ "compilerOptions": { "outDir": "./" }, "include": ["**/*"] }`; - assertParseFileDiagnostics(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, - /*noLocation*/ true); - }); + assertParseFileDiagnostics(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, + /*noLocation*/ true); + }); - it("generates errors for when invalid comment type present in tsconfig", () => { - const jsonText = `{ + it("generates errors for when invalid comment type present in tsconfig", () => { + const jsonText = `{ "compilerOptions": { ## this comment does cause issues "types" : [ ] } }`; - const parsed = getParsedCommandJsonNode(jsonText, "/apath/tsconfig.json", "tests/cases/unittests", ["/apath/a.ts"]); - assert.isTrue(parsed.errors.length >= 0); - }); - - it("generates errors when files is not string", () => { - assertParseFileDiagnostics( - JSON.stringify({ - files: [{ - compilerOptions: { - experimentalDecorators: true, - allowJs: true - } - }] - }), - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code, - /*noLocation*/ true); - }); - - it("generates errors when include is not string", () => { - assertParseFileDiagnostics( - JSON.stringify({ - include: [ - ["./**/*.ts"] - ] - }), - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code, - /*noLocation*/ true); - }); + const parsed = getParsedCommandJsonNode(jsonText, "/apath/tsconfig.json", "tests/cases/unittests", ["/apath/a.ts"]); + assert.isTrue(parsed.errors.length >= 0); + }); + + it("generates errors when files is not string", () => { + assertParseFileDiagnostics( + JSON.stringify({ + files: [{ + compilerOptions: { + experimentalDecorators: true, + allowJs: true + } + }] + }), + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code, + /*noLocation*/ true); + }); + + it("generates errors when include is not string", () => { + assertParseFileDiagnostics( + JSON.stringify({ + include: [ + ["./**/*.ts"] + ] + }), + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code, + /*noLocation*/ true); }); +}); } diff --git a/src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts b/src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts index 120dd42d2c542..1517bbdbc91f1 100644 --- a/src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts +++ b/src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts @@ -1,231 +1,231 @@ namespace ts { - describe("unittests:: config:: tsconfigParsingWatchOptions:: parseConfigFileTextToJson", () => { - function createParseConfigHost(additionalFiles?: vfs.FileSet) { - return new fakes.ParseConfigHost( - new vfs.FileSystem( - /*ignoreCase*/ false, - { - cwd: "/", - files: { "/": {}, "/a.ts": "", ...additionalFiles } - } - ) - ); - } - function getParsedCommandJson(json: object, additionalFiles?: vfs.FileSet, existingWatchOptions?: WatchOptions) { - return parseJsonConfigFileContent( - json, - createParseConfigHost(additionalFiles), - "/", - /*existingOptions*/ undefined, - "tsconfig.json", - /*resolutionStack*/ undefined, - /*extraFileExtensions*/ undefined, - /*extendedConfigCache*/ undefined, - existingWatchOptions, - ); - } +describe("unittests:: config:: tsconfigParsingWatchOptions:: parseConfigFileTextToJson", () => { + function createParseConfigHost(additionalFiles?: vfs.FileSet) { + return new fakes.ParseConfigHost( + new vfs.FileSystem( + /*ignoreCase*/ false, + { + cwd: "/", + files: { "/": {}, "/a.ts": "", ...additionalFiles } + } + ) + ); + } + function getParsedCommandJson(json: object, additionalFiles?: vfs.FileSet, existingWatchOptions?: WatchOptions) { + return parseJsonConfigFileContent( + json, + createParseConfigHost(additionalFiles), + "/", + /*existingOptions*/ undefined, + "tsconfig.json", + /*resolutionStack*/ undefined, + /*extraFileExtensions*/ undefined, + /*extendedConfigCache*/ undefined, + existingWatchOptions, + ); + } - function getParsedCommandJsonNode(json: object, additionalFiles?: vfs.FileSet, existingWatchOptions?: WatchOptions) { - const parsed = parseJsonText("tsconfig.json", JSON.stringify(json)); - return parseJsonSourceFileConfigFileContent( - parsed, - createParseConfigHost(additionalFiles), - "/", - /*existingOptions*/ undefined, - "tsconfig.json", - /*resolutionStack*/ undefined, - /*extraFileExtensions*/ undefined, - /*extendedConfigCache*/ undefined, - existingWatchOptions, - ); - } + function getParsedCommandJsonNode(json: object, additionalFiles?: vfs.FileSet, existingWatchOptions?: WatchOptions) { + const parsed = parseJsonText("tsconfig.json", JSON.stringify(json)); + return parseJsonSourceFileConfigFileContent( + parsed, + createParseConfigHost(additionalFiles), + "/", + /*existingOptions*/ undefined, + "tsconfig.json", + /*resolutionStack*/ undefined, + /*extraFileExtensions*/ undefined, + /*extendedConfigCache*/ undefined, + existingWatchOptions, + ); + } - interface VerifyWatchOptions { - json: object; - expectedOptions: WatchOptions | undefined; - additionalFiles?: vfs.FileSet; - existingWatchOptions?: WatchOptions | undefined; - expectedErrors?: (sourceFile?: SourceFile) => Diagnostic[]; - } + interface VerifyWatchOptions { + json: object; + expectedOptions: WatchOptions | undefined; + additionalFiles?: vfs.FileSet; + existingWatchOptions?: WatchOptions | undefined; + expectedErrors?: (sourceFile?: SourceFile) => Diagnostic[]; + } - function verifyWatchOptions(scenario: () => VerifyWatchOptions[]) { - it("with json api", () => { - for (const { json, expectedOptions, additionalFiles, existingWatchOptions, expectedErrors } of scenario()) { - const parsed = getParsedCommandJson(json, additionalFiles, existingWatchOptions); - assert.deepEqual(parsed.watchOptions, expectedOptions, `With ${JSON.stringify(json)}`); - if (length(parsed.errors)) { - assert.deepEqual(parsed.errors, expectedErrors?.()); - } - else { - assert.equal(0, length(expectedErrors?.()), `Expected no errors`); - } + function verifyWatchOptions(scenario: () => VerifyWatchOptions[]) { + it("with json api", () => { + for (const { json, expectedOptions, additionalFiles, existingWatchOptions, expectedErrors } of scenario()) { + const parsed = getParsedCommandJson(json, additionalFiles, existingWatchOptions); + assert.deepEqual(parsed.watchOptions, expectedOptions, `With ${JSON.stringify(json)}`); + if (length(parsed.errors)) { + assert.deepEqual(parsed.errors, expectedErrors?.()); } - }); - - it("with json source file api", () => { - for (const { json, expectedOptions, additionalFiles, existingWatchOptions, expectedErrors } of scenario()) { - const parsed = getParsedCommandJsonNode(json, additionalFiles, existingWatchOptions); - assert.deepEqual(parsed.watchOptions, expectedOptions); - if (length(parsed.errors)) { - assert.deepEqual(parsed.errors, expectedErrors?.(parsed.options.configFile)); - } - else { - assert.equal(0, length(expectedErrors?.(parsed.options.configFile)), `Expected no errors`); - } + else { + assert.equal(0, length(expectedErrors?.()), `Expected no errors`); } - }); - } - - describe("no watchOptions specified option", () => { - verifyWatchOptions(() => [{ - json: {}, - expectedOptions: undefined - }]); + } }); - describe("empty watchOptions specified option", () => { - verifyWatchOptions(() => [{ - json: { watchOptions: {} }, - expectedOptions: undefined - }]); + it("with json source file api", () => { + for (const { json, expectedOptions, additionalFiles, existingWatchOptions, expectedErrors } of scenario()) { + const parsed = getParsedCommandJsonNode(json, additionalFiles, existingWatchOptions); + assert.deepEqual(parsed.watchOptions, expectedOptions); + if (length(parsed.errors)) { + assert.deepEqual(parsed.errors, expectedErrors?.(parsed.options.configFile)); + } + else { + assert.equal(0, length(expectedErrors?.(parsed.options.configFile)), `Expected no errors`); + } + } }); + } - describe("extending config file", () => { - describe("when extending config file without watchOptions", () => { - verifyWatchOptions(() => [ - { - json: { - extends: "./base.json", - watchOptions: { watchFile: "UseFsEvents" } - }, - expectedOptions: { watchFile: WatchFileKind.UseFsEvents }, - additionalFiles: { "/base.json": "{}" } - }, - { - json: { extends: "./base.json", }, - expectedOptions: undefined, - additionalFiles: { "/base.json": "{}" } - } - ]); - }); + describe("no watchOptions specified option", () => { + verifyWatchOptions(() => [{ + json: {}, + expectedOptions: undefined + }]); + }); - describe("when extending config file with watchOptions", () => { - verifyWatchOptions(() => [ - { - json: { - extends: "./base.json", - watchOptions: { - watchFile: "UseFsEvents", - } - }, - expectedOptions: { - watchFile: WatchFileKind.UseFsEvents, - watchDirectory: WatchDirectoryKind.FixedPollingInterval - }, - additionalFiles: { - "/base.json": JSON.stringify({ - watchOptions: { - watchFile: "UseFsEventsOnParentDirectory", - watchDirectory: "FixedPollingInterval" - } - }) - } - }, - { - json: { - extends: "./base.json", - }, - expectedOptions: { - watchFile: WatchFileKind.UseFsEventsOnParentDirectory, - watchDirectory: WatchDirectoryKind.FixedPollingInterval - }, - additionalFiles: { - "/base.json": JSON.stringify({ - watchOptions: { - watchFile: "UseFsEventsOnParentDirectory", - watchDirectory: "FixedPollingInterval" - } - }) - } - } - ]); - }); - }); + describe("empty watchOptions specified option", () => { + verifyWatchOptions(() => [{ + json: { watchOptions: {} }, + expectedOptions: undefined + }]); + }); - describe("different options", () => { + describe("extending config file", () => { + describe("when extending config file without watchOptions", () => { verifyWatchOptions(() => [ { - json: { watchOptions: { watchFile: "UseFsEvents" } }, - expectedOptions: { watchFile: WatchFileKind.UseFsEvents } - }, - { - json: { watchOptions: { watchDirectory: "UseFsEvents" } }, - expectedOptions: { watchDirectory: WatchDirectoryKind.UseFsEvents } - }, - { - json: { watchOptions: { fallbackPolling: "DynamicPriority" } }, - expectedOptions: { fallbackPolling: PollingWatchKind.DynamicPriority } - }, - { - json: { watchOptions: { synchronousWatchDirectory: true } }, - expectedOptions: { synchronousWatchDirectory: true } - }, - { - json: { watchOptions: { excludeDirectories: ["**/temp"] } }, - expectedOptions: { excludeDirectories: ["/**/temp"] } - }, - { - json: { watchOptions: { excludeFiles: ["**/temp/*.ts"] } }, - expectedOptions: { excludeFiles: ["/**/temp/*.ts"] } - }, - { - json: { watchOptions: { excludeDirectories: ["**/../*"] } }, - expectedOptions: { excludeDirectories: [] }, - expectedErrors: sourceFile => [ - { - messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, - category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, - code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, - file: sourceFile, - start: sourceFile && sourceFile.text.indexOf(`"**/../*"`), - length: sourceFile && `"**/../*"`.length, - reportsDeprecated: undefined, - reportsUnnecessary: undefined - } - ] + json: { + extends: "./base.json", + watchOptions: { watchFile: "UseFsEvents" } + }, + expectedOptions: { watchFile: WatchFileKind.UseFsEvents }, + additionalFiles: { "/base.json": "{}" } }, { - json: { watchOptions: { excludeFiles: ["**/../*"] } }, - expectedOptions: { excludeFiles: [] }, - expectedErrors: sourceFile => [ - { - messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, - category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, - code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, - file: sourceFile, - start: sourceFile && sourceFile.text.indexOf(`"**/../*"`), - length: sourceFile && `"**/../*"`.length, - reportsDeprecated: undefined, - reportsUnnecessary: undefined - } - ] - }, + json: { extends: "./base.json", }, + expectedOptions: undefined, + additionalFiles: { "/base.json": "{}" } + } ]); }); - describe("watch options extending passed in watch options", () => { + describe("when extending config file with watchOptions", () => { verifyWatchOptions(() => [ { - json: { watchOptions: { watchFile: "UseFsEvents" } }, - expectedOptions: { watchFile: WatchFileKind.UseFsEvents, watchDirectory: WatchDirectoryKind.FixedPollingInterval }, - existingWatchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval } + json: { + extends: "./base.json", + watchOptions: { + watchFile: "UseFsEvents", + } + }, + expectedOptions: { + watchFile: WatchFileKind.UseFsEvents, + watchDirectory: WatchDirectoryKind.FixedPollingInterval + }, + additionalFiles: { + "/base.json": JSON.stringify({ + watchOptions: { + watchFile: "UseFsEventsOnParentDirectory", + watchDirectory: "FixedPollingInterval" + } + }) + } }, { - json: {}, - expectedOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval }, - existingWatchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval } - }, + json: { + extends: "./base.json", + }, + expectedOptions: { + watchFile: WatchFileKind.UseFsEventsOnParentDirectory, + watchDirectory: WatchDirectoryKind.FixedPollingInterval + }, + additionalFiles: { + "/base.json": JSON.stringify({ + watchOptions: { + watchFile: "UseFsEventsOnParentDirectory", + watchDirectory: "FixedPollingInterval" + } + }) + } + } ]); }); }); + + describe("different options", () => { + verifyWatchOptions(() => [ + { + json: { watchOptions: { watchFile: "UseFsEvents" } }, + expectedOptions: { watchFile: WatchFileKind.UseFsEvents } + }, + { + json: { watchOptions: { watchDirectory: "UseFsEvents" } }, + expectedOptions: { watchDirectory: WatchDirectoryKind.UseFsEvents } + }, + { + json: { watchOptions: { fallbackPolling: "DynamicPriority" } }, + expectedOptions: { fallbackPolling: PollingWatchKind.DynamicPriority } + }, + { + json: { watchOptions: { synchronousWatchDirectory: true } }, + expectedOptions: { synchronousWatchDirectory: true } + }, + { + json: { watchOptions: { excludeDirectories: ["**/temp"] } }, + expectedOptions: { excludeDirectories: ["/**/temp"] } + }, + { + json: { watchOptions: { excludeFiles: ["**/temp/*.ts"] } }, + expectedOptions: { excludeFiles: ["/**/temp/*.ts"] } + }, + { + json: { watchOptions: { excludeDirectories: ["**/../*"] } }, + expectedOptions: { excludeDirectories: [] }, + expectedErrors: sourceFile => [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: sourceFile, + start: sourceFile && sourceFile.text.indexOf(`"**/../*"`), + length: sourceFile && `"**/../*"`.length, + reportsDeprecated: undefined, + reportsUnnecessary: undefined + } + ] + }, + { + json: { watchOptions: { excludeFiles: ["**/../*"] } }, + expectedOptions: { excludeFiles: [] }, + expectedErrors: sourceFile => [ + { + messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, + category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, + code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, + file: sourceFile, + start: sourceFile && sourceFile.text.indexOf(`"**/../*"`), + length: sourceFile && `"**/../*"`.length, + reportsDeprecated: undefined, + reportsUnnecessary: undefined + } + ] + }, + ]); + }); + + describe("watch options extending passed in watch options", () => { + verifyWatchOptions(() => [ + { + json: { watchOptions: { watchFile: "UseFsEvents" } }, + expectedOptions: { watchFile: WatchFileKind.UseFsEvents, watchDirectory: WatchDirectoryKind.FixedPollingInterval }, + existingWatchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval } + }, + { + json: {}, + expectedOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval }, + existingWatchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval } + }, + ]); + }); +}); } diff --git a/src/testRunner/unittests/convertToBase64.ts b/src/testRunner/unittests/convertToBase64.ts index 37e38da8da7dc..86ac424e94f0c 100644 --- a/src/testRunner/unittests/convertToBase64.ts +++ b/src/testRunner/unittests/convertToBase64.ts @@ -1,33 +1,33 @@ namespace ts { - describe("unittests:: convertToBase64", () => { - function runTest(input: string): void { - const actual = convertToBase64(input); - const expected = sys.base64encode!(input); - assert.equal(actual, expected, "Encoded string using convertToBase64 does not match buffer.toString('base64')"); - } +describe("unittests:: convertToBase64", () => { + function runTest(input: string): void { + const actual = convertToBase64(input); + const expected = sys.base64encode!(input); + assert.equal(actual, expected, "Encoded string using convertToBase64 does not match buffer.toString('base64')"); + } - if (Buffer) { - it("Converts ASCII charaters correctly", () => { - runTest(" !\"#$ %&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"); - }); + if (Buffer) { + it("Converts ASCII charaters correctly", () => { + runTest(" !\"#$ %&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"); + }); - it("Converts escape sequences correctly", () => { - runTest("\t\n\r\\\"\'\u0062"); - }); + it("Converts escape sequences correctly", () => { + runTest("\t\n\r\\\"\'\u0062"); + }); - it("Converts simple unicode characters correctly", () => { - runTest("ΠΣ ٵپ औठ ⺐⺠"); - }); + it("Converts simple unicode characters correctly", () => { + runTest("ΠΣ ٵپ औठ ⺐⺠"); + }); - it("Converts simple code snippet correctly", () => { - runTest(`/// + it("Converts simple code snippet correctly", () => { + runTest(`/// var x: string = "string"; console.log(x);`); - }); + }); - it("Converts simple code snippet with unicode characters correctly", () => { - runTest(`var Π = 3.1415; console.log(Π);`); - }); - } - }); + it("Converts simple code snippet with unicode characters correctly", () => { + runTest(`var Π = 3.1415; console.log(Π);`); + }); + } +}); } diff --git a/src/testRunner/unittests/createMapShim.ts b/src/testRunner/unittests/createMapShim.ts index d96524057a4c1..ec9cb55d7c071 100644 --- a/src/testRunner/unittests/createMapShim.ts +++ b/src/testRunner/unittests/createMapShim.ts @@ -1,326 +1,326 @@ namespace ts { - describe("unittests:: createMapShim", () => { - - const stringKeys = [ - "1", - "3", - "2", - "4", - "0", - "999", - "A", - "B", - "C", - "Z", - "X", - "X1", - "X2", - "Y" - ]; - - const mixedKeys = [ - true, - 3, - { toString() { return "2"; } }, - "4", - false, - null, // eslint-disable-line no-null/no-null - undefined, - "B", - { toString() { return "C"; } }, - "Z", - "X", - { toString() { return "X1"; } }, - "X2", - "Y" - ]; - - function testMapIterationAddedValues(keys: K[], map: ESMap, useForEach: boolean): string { - let resultString = ""; - - map.set(keys[0], "1"); - map.set(keys[1], "3"); - map.set(keys[2], "2"); - map.set(keys[3], "4"); - - let addedThree = false; - const doForEach = (value: string, key: K) => { - resultString += `${key}:${value};`; - - // Add a new key ("0") - the map should provide this - // one in the next iteration. - if (key === keys[0]) { - map.set(keys[0], "X1"); - map.set(keys[4], "X0"); - map.set(keys[3], "X4"); - } - else if (key === keys[1]) { - if (!addedThree) { - addedThree = true; - - // Remove and re-add key "3"; the map should - // visit it after "0". - map.delete(keys[1]); - map.set(keys[1], "Y3"); - - // Change the value of "2"; the map should provide - // it when visiting the key. - map.set(keys[2], "Y2"); - } - else { - // Check that an entry added when we visit the - // currently last entry will still be visited. - map.set(keys[5], "999"); - } - } - else if (key === keys[5]) { - // Ensure that clear() behaves correctly same as removing all keys. - map.set(keys[6], "A"); - map.set(keys[7], "B"); - map.set(keys[8], "C"); - } - else if (key === keys[6]) { - map.clear(); - map.set(keys[9], "Z"); +describe("unittests:: createMapShim", () => { + + const stringKeys = [ + "1", + "3", + "2", + "4", + "0", + "999", + "A", + "B", + "C", + "Z", + "X", + "X1", + "X2", + "Y" + ]; + + const mixedKeys = [ + true, + 3, + { toString() { return "2"; } }, + "4", + false, + null, // eslint-disable-line no-null/no-null + undefined, + "B", + { toString() { return "C"; } }, + "Z", + "X", + { toString() { return "X1"; } }, + "X2", + "Y" + ]; + + function testMapIterationAddedValues(keys: K[], map: ESMap, useForEach: boolean): string { + let resultString = ""; + + map.set(keys[0], "1"); + map.set(keys[1], "3"); + map.set(keys[2], "2"); + map.set(keys[3], "4"); + + let addedThree = false; + const doForEach = (value: string, key: K) => { + resultString += `${key}:${value};`; + + // Add a new key ("0") - the map should provide this + // one in the next iteration. + if (key === keys[0]) { + map.set(keys[0], "X1"); + map.set(keys[4], "X0"); + map.set(keys[3], "X4"); + } + else if (key === keys[1]) { + if (!addedThree) { + addedThree = true; + + // Remove and re-add key "3"; the map should + // visit it after "0". + map.delete(keys[1]); + map.set(keys[1], "Y3"); + + // Change the value of "2"; the map should provide + // it when visiting the key. + map.set(keys[2], "Y2"); } - else if (key === keys[9]) { - // Check that the map behaves correctly when two items are - // added and removed immediately. - map.set(keys[10], "X"); - map.set(keys[11], "X1"); - map.set(keys[12], "X2"); - map.delete(keys[11]); - map.delete(keys[12]); - map.set(keys[13], "Y"); + else { + // Check that an entry added when we visit the + // currently last entry will still be visited. + map.set(keys[5], "999"); } - }; - - if (useForEach) { - map.forEach(doForEach); } - else { - // Use an iterator. - const iterator = map.entries(); - while (true) { - const iterResult = iterator.next(); - if (iterResult.done) { - break; - } - - const [key, value] = iterResult.value; - doForEach(value, key); - } + else if (key === keys[5]) { + // Ensure that clear() behaves correctly same as removing all keys. + map.set(keys[6], "A"); + map.set(keys[7], "B"); + map.set(keys[8], "C"); + } + else if (key === keys[6]) { + map.clear(); + map.set(keys[9], "Z"); } + else if (key === keys[9]) { + // Check that the map behaves correctly when two items are + // added and removed immediately. + map.set(keys[10], "X"); + map.set(keys[11], "X1"); + map.set(keys[12], "X2"); + map.delete(keys[11]); + map.delete(keys[12]); + map.set(keys[13], "Y"); + } + }; - return resultString; + if (useForEach) { + map.forEach(doForEach); } + else { + // Use an iterator. + const iterator = map.entries(); + while (true) { + const iterResult = iterator.next(); + if (iterResult.done) { + break; + } - let MapShim!: MapConstructor; - beforeEach(() => { - function getIterator | ReadonlyESMap | undefined>(iterable: I): Iterator< - I extends ReadonlyESMap ? [K, V] : - I extends ReadonlySet ? T : - I extends readonly (infer T)[] ? T : - I extends undefined ? undefined : - never>; - function getIterator(iterable: readonly any[] | ReadonlySet | ReadonlyESMap | undefined): Iterator | undefined { - // override `ts.getIterator` with a version that allows us to iterate over a `MapShim` in an environment with a native `Map`. - if (iterable instanceof MapShim) return iterable.entries(); - return ts.getIterator(iterable); + const [key, value] = iterResult.value; + doForEach(value, key); } + } - MapShim = ShimCollections.createMapShim(getIterator); - afterEach(() => { - MapShim = undefined!; - }); + return resultString; + } + + let MapShim!: MapConstructor; + beforeEach(() => { + function getIterator | ReadonlyESMap | undefined>(iterable: I): Iterator< + I extends ReadonlyESMap ? [K, V] : + I extends ReadonlySet ? T : + I extends readonly (infer T)[] ? T : + I extends undefined ? undefined : + never>; + function getIterator(iterable: readonly any[] | ReadonlySet | ReadonlyESMap | undefined): Iterator | undefined { + // override `ts.getIterator` with a version that allows us to iterate over a `MapShim` in an environment with a native `Map`. + if (iterable instanceof MapShim) return iterable.entries(); + return ts.getIterator(iterable); + } + + MapShim = ShimCollections.createMapShim(getIterator); + afterEach(() => { + MapShim = undefined!; }); + }); - it("iterates values in insertion order and handles changes with string keys", () => { - const expectedResult = "1:1;3:3;2:Y2;4:X4;0:X0;3:Y3;999:999;A:A;Z:Z;X:X;Y:Y;"; + it("iterates values in insertion order and handles changes with string keys", () => { + const expectedResult = "1:1;3:3;2:Y2;4:X4;0:X0;3:Y3;999:999;A:A;Z:Z;X:X;Y:Y;"; - // First, ensure the test actually has the same behavior as a native Map. - let nativeMap = new Map(); - const nativeMapForEachResult = testMapIterationAddedValues(stringKeys, nativeMap, /* useForEach */ true); - assert.equal(nativeMapForEachResult, expectedResult, "nativeMap-forEach"); + // First, ensure the test actually has the same behavior as a native Map. + let nativeMap = new Map(); + const nativeMapForEachResult = testMapIterationAddedValues(stringKeys, nativeMap, /* useForEach */ true); + assert.equal(nativeMapForEachResult, expectedResult, "nativeMap-forEach"); - nativeMap = new Map(); - const nativeMapIteratorResult = testMapIterationAddedValues(stringKeys, nativeMap, /* useForEach */ false); - assert.equal(nativeMapIteratorResult, expectedResult, "nativeMap-iterator"); + nativeMap = new Map(); + const nativeMapIteratorResult = testMapIterationAddedValues(stringKeys, nativeMap, /* useForEach */ false); + assert.equal(nativeMapIteratorResult, expectedResult, "nativeMap-iterator"); - // Then, test the map shim. - let localShimMap = new MapShim(); - const shimMapForEachResult = testMapIterationAddedValues(stringKeys, localShimMap, /* useForEach */ true); - assert.equal(shimMapForEachResult, expectedResult, "shimMap-forEach"); + // Then, test the map shim. + let localShimMap = new MapShim(); + const shimMapForEachResult = testMapIterationAddedValues(stringKeys, localShimMap, /* useForEach */ true); + assert.equal(shimMapForEachResult, expectedResult, "shimMap-forEach"); - localShimMap = new MapShim(); - const shimMapIteratorResult = testMapIterationAddedValues(stringKeys, localShimMap, /* useForEach */ false); - assert.equal(shimMapIteratorResult, expectedResult, "shimMap-iterator"); - }); + localShimMap = new MapShim(); + const shimMapIteratorResult = testMapIterationAddedValues(stringKeys, localShimMap, /* useForEach */ false); + assert.equal(shimMapIteratorResult, expectedResult, "shimMap-iterator"); + }); - it("iterates values in insertion order and handles changes with mixed-type keys", () => { - const expectedResult = "true:1;3:3;2:Y2;4:X4;false:X0;3:Y3;null:999;undefined:A;Z:Z;X:X;Y:Y;"; + it("iterates values in insertion order and handles changes with mixed-type keys", () => { + const expectedResult = "true:1;3:3;2:Y2;4:X4;false:X0;3:Y3;null:999;undefined:A;Z:Z;X:X;Y:Y;"; - // First, ensure the test actually has the same behavior as a native Map. - let nativeMap = new Map(); - const nativeMapForEachResult = testMapIterationAddedValues(mixedKeys, nativeMap, /* useForEach */ true); - assert.equal(nativeMapForEachResult, expectedResult, "nativeMap-forEach"); + // First, ensure the test actually has the same behavior as a native Map. + let nativeMap = new Map(); + const nativeMapForEachResult = testMapIterationAddedValues(mixedKeys, nativeMap, /* useForEach */ true); + assert.equal(nativeMapForEachResult, expectedResult, "nativeMap-forEach"); - nativeMap = new Map(); - const nativeMapIteratorResult = testMapIterationAddedValues(mixedKeys, nativeMap, /* useForEach */ false); - assert.equal(nativeMapIteratorResult, expectedResult, "nativeMap-iterator"); + nativeMap = new Map(); + const nativeMapIteratorResult = testMapIterationAddedValues(mixedKeys, nativeMap, /* useForEach */ false); + assert.equal(nativeMapIteratorResult, expectedResult, "nativeMap-iterator"); - // Then, test the map shim. - let localShimMap = new MapShim(); - const shimMapForEachResult = testMapIterationAddedValues(mixedKeys, localShimMap, /* useForEach */ true); - assert.equal(shimMapForEachResult, expectedResult, "shimMap-forEach"); + // Then, test the map shim. + let localShimMap = new MapShim(); + const shimMapForEachResult = testMapIterationAddedValues(mixedKeys, localShimMap, /* useForEach */ true); + assert.equal(shimMapForEachResult, expectedResult, "shimMap-forEach"); - localShimMap = new MapShim(); - const shimMapIteratorResult = testMapIterationAddedValues(mixedKeys, localShimMap, /* useForEach */ false); - assert.equal(shimMapIteratorResult, expectedResult, "shimMap-iterator"); - }); + localShimMap = new MapShim(); + const shimMapIteratorResult = testMapIterationAddedValues(mixedKeys, localShimMap, /* useForEach */ false); + assert.equal(shimMapIteratorResult, expectedResult, "shimMap-iterator"); + }); - it("create from Array", () => { - const map = new MapShim([["a", "b"]]); - assert.equal(map.size, 1); - assert.isTrue(map.has("a")); - assert.equal(map.get("a"), "b"); - }); + it("create from Array", () => { + const map = new MapShim([["a", "b"]]); + assert.equal(map.size, 1); + assert.isTrue(map.has("a")); + assert.equal(map.get("a"), "b"); + }); - it("create from Map", () => { - const map1 = new MapShim([["a", "b"]]); - const map2 = new MapShim(map1); - assert.equal(map1.size, 1); - assert.equal(map2.size, 1); - assert.isTrue(map2.has("a")); - assert.equal(map2.get("a"), "b"); - }); + it("create from Map", () => { + const map1 = new MapShim([["a", "b"]]); + const map2 = new MapShim(map1); + assert.equal(map1.size, 1); + assert.equal(map2.size, 1); + assert.isTrue(map2.has("a")); + assert.equal(map2.get("a"), "b"); + }); - it("set when not present", () => { - const map = new MapShim(); - const result = map.set("a", "b"); - assert.equal(map.size, 1); - assert.strictEqual(result, map); - assert.isTrue(map.has("a")); - assert.equal(map.get("a"), "b"); - }); + it("set when not present", () => { + const map = new MapShim(); + const result = map.set("a", "b"); + assert.equal(map.size, 1); + assert.strictEqual(result, map); + assert.isTrue(map.has("a")); + assert.equal(map.get("a"), "b"); + }); - it("set when present", () => { - const map = new MapShim(); - map.set("a", "z"); - const result = map.set("a", "b"); - assert.equal(map.size, 1); - assert.strictEqual(result, map); - assert.isTrue(map.has("a")); - assert.equal(map.get("a"), "b"); - }); + it("set when present", () => { + const map = new MapShim(); + map.set("a", "z"); + const result = map.set("a", "b"); + assert.equal(map.size, 1); + assert.strictEqual(result, map); + assert.isTrue(map.has("a")); + assert.equal(map.get("a"), "b"); + }); - it("has when not present", () => { - const map = new MapShim(); - assert.isFalse(map.has("a")); - }); + it("has when not present", () => { + const map = new MapShim(); + assert.isFalse(map.has("a")); + }); - it("has when present", () => { - const map = new MapShim(); - map.set("a", "b"); - assert.isTrue(map.has("a")); - }); + it("has when present", () => { + const map = new MapShim(); + map.set("a", "b"); + assert.isTrue(map.has("a")); + }); - it("get when not present", () => { - const map = new MapShim(); - assert.isUndefined(map.get("a")); - }); + it("get when not present", () => { + const map = new MapShim(); + assert.isUndefined(map.get("a")); + }); - it("get when present", () => { - const map = new MapShim(); - map.set("a", "b"); - assert.equal(map.get("a"), "b"); - }); + it("get when present", () => { + const map = new MapShim(); + map.set("a", "b"); + assert.equal(map.get("a"), "b"); + }); - it("delete when not present", () => { - const map = new MapShim(); - assert.isFalse(map.delete("a")); - }); + it("delete when not present", () => { + const map = new MapShim(); + assert.isFalse(map.delete("a")); + }); - it("delete when present", () => { - const map = new MapShim(); - map.set("a", "b"); - assert.isTrue(map.delete("a")); - }); + it("delete when present", () => { + const map = new MapShim(); + map.set("a", "b"); + assert.isTrue(map.delete("a")); + }); - it("delete twice when present", () => { - const map = new MapShim(); - map.set("a", "b"); - assert.isTrue(map.delete("a")); - assert.isFalse(map.delete("a")); - }); + it("delete twice when present", () => { + const map = new MapShim(); + map.set("a", "b"); + assert.isTrue(map.delete("a")); + assert.isFalse(map.delete("a")); + }); - it("remove only item and iterate", () => { - const map = new MapShim(); - map.set("a", "b"); - map.delete("a"); - const actual = arrayFrom(map.keys()); - assert.deepEqual(actual, []); - }); + it("remove only item and iterate", () => { + const map = new MapShim(); + map.set("a", "b"); + map.delete("a"); + const actual = arrayFrom(map.keys()); + assert.deepEqual(actual, []); + }); - it("remove first item and iterate", () => { - const map = new MapShim(); - map.set("a", "b"); - map.set("c", "d"); - map.delete("a"); - assert.deepEqual(arrayFrom(map.keys()), ["c"]); - assert.deepEqual(arrayFrom(map.values()), ["d"]); - assert.deepEqual(arrayFrom(map.entries()), [["c", "d"]]); - }); + it("remove first item and iterate", () => { + const map = new MapShim(); + map.set("a", "b"); + map.set("c", "d"); + map.delete("a"); + assert.deepEqual(arrayFrom(map.keys()), ["c"]); + assert.deepEqual(arrayFrom(map.values()), ["d"]); + assert.deepEqual(arrayFrom(map.entries()), [["c", "d"]]); + }); - it("remove last item and iterate", () => { - const map = new MapShim(); - map.set("a", "b"); - map.set("c", "d"); - map.delete("c"); - assert.deepEqual(arrayFrom(map.keys()), ["a"]); - assert.deepEqual(arrayFrom(map.values()), ["b"]); - assert.deepEqual(arrayFrom(map.entries()), [["a", "b"]]); - }); + it("remove last item and iterate", () => { + const map = new MapShim(); + map.set("a", "b"); + map.set("c", "d"); + map.delete("c"); + assert.deepEqual(arrayFrom(map.keys()), ["a"]); + assert.deepEqual(arrayFrom(map.values()), ["b"]); + assert.deepEqual(arrayFrom(map.entries()), [["a", "b"]]); + }); - it("remove middle item and iterate", () => { - const map = new MapShim(); - map.set("a", "b"); - map.set("c", "d"); - map.set("e", "f"); - map.delete("c"); - assert.deepEqual(arrayFrom(map.keys()), ["a", "e"]); - assert.deepEqual(arrayFrom(map.values()), ["b", "f"]); - assert.deepEqual(arrayFrom(map.entries()), [["a", "b"], ["e", "f"]]); - }); + it("remove middle item and iterate", () => { + const map = new MapShim(); + map.set("a", "b"); + map.set("c", "d"); + map.set("e", "f"); + map.delete("c"); + assert.deepEqual(arrayFrom(map.keys()), ["a", "e"]); + assert.deepEqual(arrayFrom(map.values()), ["b", "f"]); + assert.deepEqual(arrayFrom(map.entries()), [["a", "b"], ["e", "f"]]); + }); - it("keys", () => { - const map = new MapShim(); - map.set("c", "d"); - map.set("a", "b"); - assert.deepEqual(arrayFrom(map.keys()), ["c", "a"]); - }); + it("keys", () => { + const map = new MapShim(); + map.set("c", "d"); + map.set("a", "b"); + assert.deepEqual(arrayFrom(map.keys()), ["c", "a"]); + }); - it("values", () => { - const map = new MapShim(); - map.set("c", "d"); - map.set("a", "b"); - assert.deepEqual(arrayFrom(map.values()), ["d", "b"]); - }); + it("values", () => { + const map = new MapShim(); + map.set("c", "d"); + map.set("a", "b"); + assert.deepEqual(arrayFrom(map.values()), ["d", "b"]); + }); - it("entries", () => { - const map = new MapShim(); - map.set("c", "d"); - map.set("a", "b"); - assert.deepEqual(arrayFrom(map.entries()), [["c", "d"], ["a", "b"]]); - }); + it("entries", () => { + const map = new MapShim(); + map.set("c", "d"); + map.set("a", "b"); + assert.deepEqual(arrayFrom(map.entries()), [["c", "d"], ["a", "b"]]); + }); - it("forEach", () => { - const map = new MapShim(); - map.set("c", "d"); - map.set("a", "b"); - const actual: [string, string][] = []; - map.forEach((value, key) => actual.push([key, value])); - assert.deepEqual(actual, [["c", "d"], ["a", "b"]]); - }); + it("forEach", () => { + const map = new MapShim(); + map.set("c", "d"); + map.set("a", "b"); + const actual: [string, string][] = []; + map.forEach((value, key) => actual.push([key, value])); + assert.deepEqual(actual, [["c", "d"], ["a", "b"]]); }); +}); } diff --git a/src/testRunner/unittests/createSetShim.ts b/src/testRunner/unittests/createSetShim.ts index 28a6998e0eb07..673c4fd5260c1 100644 --- a/src/testRunner/unittests/createSetShim.ts +++ b/src/testRunner/unittests/createSetShim.ts @@ -1,309 +1,309 @@ namespace ts { - describe("unittests:: createSetShim", () => { - const stringKeys = [ - "1", - "3", - "2", - "4", - "0", - "999", - "A", - "B", - "C", - "Z", - "X", - "X1", - "X2", - "Y" - ]; - - const mixedKeys = [ - true, - 3, - { toString() { return "2"; } }, - "4", - false, - null, // eslint-disable-line no-null/no-null - undefined, - "B", - { toString() { return "C"; } }, - "Z", - "X", - { toString() { return "X1"; } }, - "X2", - "Y" - ]; - - function testSetIterationAddedValues(keys: K[], set: Set, useForEach: boolean): string { - let resultString = ""; - - set.add(keys[0]); - set.add(keys[1]); - set.add(keys[2]); - set.add(keys[3]); - - let addedThree = false; - const doForEach = (key: K) => { - resultString += `${key};`; - - // Add a new key ("0") - the set should provide this - // one in the next iteration. - if (key === keys[0]) { - set.add(keys[0]); - set.add(keys[4]); - set.add(keys[3]); - } - else if (key === keys[1]) { - if (!addedThree) { - addedThree = true; - - // Remove and re-add key "3"; the set should - // visit it after "0". - set.delete(keys[1]); - set.add(keys[1]); - - // Change the value of "2"; the set should provide - // it when visiting the key. - set.add(keys[2]); - } - else { - // Check that an entry added when we visit the - // currently last entry will still be visited. - set.add(keys[5]); - } - } - else if (key === keys[5]) { - // Ensure that clear() behaves correctly same as removing all keys. - set.add(keys[6]); - set.add(keys[7]); - set.add(keys[8]); - } - else if (key === keys[6]) { - set.clear(); - set.add(keys[9]); +describe("unittests:: createSetShim", () => { + const stringKeys = [ + "1", + "3", + "2", + "4", + "0", + "999", + "A", + "B", + "C", + "Z", + "X", + "X1", + "X2", + "Y" + ]; + + const mixedKeys = [ + true, + 3, + { toString() { return "2"; } }, + "4", + false, + null, // eslint-disable-line no-null/no-null + undefined, + "B", + { toString() { return "C"; } }, + "Z", + "X", + { toString() { return "X1"; } }, + "X2", + "Y" + ]; + + function testSetIterationAddedValues(keys: K[], set: Set, useForEach: boolean): string { + let resultString = ""; + + set.add(keys[0]); + set.add(keys[1]); + set.add(keys[2]); + set.add(keys[3]); + + let addedThree = false; + const doForEach = (key: K) => { + resultString += `${key};`; + + // Add a new key ("0") - the set should provide this + // one in the next iteration. + if (key === keys[0]) { + set.add(keys[0]); + set.add(keys[4]); + set.add(keys[3]); + } + else if (key === keys[1]) { + if (!addedThree) { + addedThree = true; + + // Remove and re-add key "3"; the set should + // visit it after "0". + set.delete(keys[1]); + set.add(keys[1]); + + // Change the value of "2"; the set should provide + // it when visiting the key. + set.add(keys[2]); } - else if (key === keys[9]) { - // Check that the set behaves correctly when two items are - // added and removed immediately. - set.add(keys[10]); - set.add(keys[11]); - set.add(keys[12]); - set.delete(keys[11]); - set.delete(keys[12]); - set.add(keys[13]); + else { + // Check that an entry added when we visit the + // currently last entry will still be visited. + set.add(keys[5]); } - }; - - if (useForEach) { - set.forEach(doForEach); } - else { - // Use an iterator. - const iterator = set.values(); - while (true) { - const iterResult = iterator.next(); - if (iterResult.done) { - break; - } - - doForEach(iterResult.value); - } + else if (key === keys[5]) { + // Ensure that clear() behaves correctly same as removing all keys. + set.add(keys[6]); + set.add(keys[7]); + set.add(keys[8]); + } + else if (key === keys[6]) { + set.clear(); + set.add(keys[9]); } + else if (key === keys[9]) { + // Check that the set behaves correctly when two items are + // added and removed immediately. + set.add(keys[10]); + set.add(keys[11]); + set.add(keys[12]); + set.delete(keys[11]); + set.delete(keys[12]); + set.add(keys[13]); + } + }; - return resultString; + if (useForEach) { + set.forEach(doForEach); } + else { + // Use an iterator. + const iterator = set.values(); + while (true) { + const iterResult = iterator.next(); + if (iterResult.done) { + break; + } - let SetShim!: SetConstructor; - beforeEach(() => { - function getIterator | ReadonlyESMap | undefined>(iterable: I): Iterator< - I extends ReadonlyESMap ? [K, V] : - I extends ReadonlySet ? T : - I extends readonly (infer T)[] ? T : - I extends undefined ? undefined : - never>; - function getIterator(iterable: readonly any[] | ReadonlySet | ReadonlyESMap | undefined): Iterator | undefined { - // override `ts.getIterator` with a version that allows us to iterate over a `SetShim` in an environment with a native `Set`. - if (iterable instanceof SetShim) return iterable.values(); - return ts.getIterator(iterable); + doForEach(iterResult.value); } + } - SetShim = ShimCollections.createSetShim(getIterator); - afterEach(() => { - SetShim = undefined!; - }); + return resultString; + } + + let SetShim!: SetConstructor; + beforeEach(() => { + function getIterator | ReadonlyESMap | undefined>(iterable: I): Iterator< + I extends ReadonlyESMap ? [K, V] : + I extends ReadonlySet ? T : + I extends readonly (infer T)[] ? T : + I extends undefined ? undefined : + never>; + function getIterator(iterable: readonly any[] | ReadonlySet | ReadonlyESMap | undefined): Iterator | undefined { + // override `ts.getIterator` with a version that allows us to iterate over a `SetShim` in an environment with a native `Set`. + if (iterable instanceof SetShim) return iterable.values(); + return ts.getIterator(iterable); + } + + SetShim = ShimCollections.createSetShim(getIterator); + afterEach(() => { + SetShim = undefined!; }); + }); - it("iterates values in insertion order and handles changes with string keys", () => { - const expectedResult = "1;3;2;4;0;3;999;A;Z;X;Y;"; + it("iterates values in insertion order and handles changes with string keys", () => { + const expectedResult = "1;3;2;4;0;3;999;A;Z;X;Y;"; - // First, ensure the test actually has the same behavior as a native Set. - let nativeSet = new Set(); - const nativeSetForEachResult = testSetIterationAddedValues(stringKeys, nativeSet, /* useForEach */ true); - assert.equal(nativeSetForEachResult, expectedResult, "nativeSet-forEach"); + // First, ensure the test actually has the same behavior as a native Set. + let nativeSet = new Set(); + const nativeSetForEachResult = testSetIterationAddedValues(stringKeys, nativeSet, /* useForEach */ true); + assert.equal(nativeSetForEachResult, expectedResult, "nativeSet-forEach"); - nativeSet = new Set(); - const nativeSetIteratorResult = testSetIterationAddedValues(stringKeys, nativeSet, /* useForEach */ false); - assert.equal(nativeSetIteratorResult, expectedResult, "nativeSet-iterator"); + nativeSet = new Set(); + const nativeSetIteratorResult = testSetIterationAddedValues(stringKeys, nativeSet, /* useForEach */ false); + assert.equal(nativeSetIteratorResult, expectedResult, "nativeSet-iterator"); - // Then, test the set shim. - let localShimSet = new SetShim(); - const shimSetForEachResult = testSetIterationAddedValues(stringKeys, localShimSet, /* useForEach */ true); - assert.equal(shimSetForEachResult, expectedResult, "shimSet-forEach"); + // Then, test the set shim. + let localShimSet = new SetShim(); + const shimSetForEachResult = testSetIterationAddedValues(stringKeys, localShimSet, /* useForEach */ true); + assert.equal(shimSetForEachResult, expectedResult, "shimSet-forEach"); - localShimSet = new SetShim(); - const shimSetIteratorResult = testSetIterationAddedValues(stringKeys, localShimSet, /* useForEach */ false); - assert.equal(shimSetIteratorResult, expectedResult, "shimSet-iterator"); - }); + localShimSet = new SetShim(); + const shimSetIteratorResult = testSetIterationAddedValues(stringKeys, localShimSet, /* useForEach */ false); + assert.equal(shimSetIteratorResult, expectedResult, "shimSet-iterator"); + }); - it("iterates values in insertion order and handles changes with mixed-type keys", () => { - const expectedResult = "true;3;2;4;false;3;null;undefined;Z;X;Y;"; + it("iterates values in insertion order and handles changes with mixed-type keys", () => { + const expectedResult = "true;3;2;4;false;3;null;undefined;Z;X;Y;"; - // First, ensure the test actually has the same behavior as a native Set. - let nativeSet = new Set(); - const nativeSetForEachResult = testSetIterationAddedValues(mixedKeys, nativeSet, /* useForEach */ true); - assert.equal(nativeSetForEachResult, expectedResult, "nativeSet-forEach"); + // First, ensure the test actually has the same behavior as a native Set. + let nativeSet = new Set(); + const nativeSetForEachResult = testSetIterationAddedValues(mixedKeys, nativeSet, /* useForEach */ true); + assert.equal(nativeSetForEachResult, expectedResult, "nativeSet-forEach"); - nativeSet = new Set(); - const nativeSetIteratorResult = testSetIterationAddedValues(mixedKeys, nativeSet, /* useForEach */ false); - assert.equal(nativeSetIteratorResult, expectedResult, "nativeSet-iterator"); + nativeSet = new Set(); + const nativeSetIteratorResult = testSetIterationAddedValues(mixedKeys, nativeSet, /* useForEach */ false); + assert.equal(nativeSetIteratorResult, expectedResult, "nativeSet-iterator"); - // Then, test the set shim. - let localshimSet = new SetShim(); - const shimSetForEachResult = testSetIterationAddedValues(mixedKeys, localshimSet, /* useForEach */ true); - assert.equal(shimSetForEachResult, expectedResult, "shimSet-forEach"); + // Then, test the set shim. + let localshimSet = new SetShim(); + const shimSetForEachResult = testSetIterationAddedValues(mixedKeys, localshimSet, /* useForEach */ true); + assert.equal(shimSetForEachResult, expectedResult, "shimSet-forEach"); - localshimSet = new SetShim(); - const shimSetIteratorResult = testSetIterationAddedValues(mixedKeys, localshimSet, /* useForEach */ false); - assert.equal(shimSetIteratorResult, expectedResult, "shimSet-iterator"); - }); + localshimSet = new SetShim(); + const shimSetIteratorResult = testSetIterationAddedValues(mixedKeys, localshimSet, /* useForEach */ false); + assert.equal(shimSetIteratorResult, expectedResult, "shimSet-iterator"); + }); - it("create from Array", () => { - const set = new SetShim(["a"]); - assert.equal(set.size, 1); - assert.isTrue(set.has("a")); - }); + it("create from Array", () => { + const set = new SetShim(["a"]); + assert.equal(set.size, 1); + assert.isTrue(set.has("a")); + }); - it("create from set", () => { - const set1 = new SetShim(["a"]); - const set2 = new SetShim(set1); - assert.equal(set1.size, 1); - assert.equal(set2.size, 1); - assert.isTrue(set2.has("a")); - }); + it("create from set", () => { + const set1 = new SetShim(["a"]); + const set2 = new SetShim(set1); + assert.equal(set1.size, 1); + assert.equal(set2.size, 1); + assert.isTrue(set2.has("a")); + }); - it("add when not present", () => { - const set = new SetShim(); - const result = set.add("a"); - assert.equal(set.size, 1); - assert.strictEqual(result, set); - assert.isTrue(set.has("a")); - }); + it("add when not present", () => { + const set = new SetShim(); + const result = set.add("a"); + assert.equal(set.size, 1); + assert.strictEqual(result, set); + assert.isTrue(set.has("a")); + }); - it("add when present", () => { - const set = new SetShim(); - set.add("a"); - const result = set.add("a"); - assert.equal(set.size, 1); - assert.strictEqual(result, set); - assert.isTrue(set.has("a")); - }); + it("add when present", () => { + const set = new SetShim(); + set.add("a"); + const result = set.add("a"); + assert.equal(set.size, 1); + assert.strictEqual(result, set); + assert.isTrue(set.has("a")); + }); - it("has when not present", () => { - const set = new SetShim(); - assert.isFalse(set.has("a")); - }); + it("has when not present", () => { + const set = new SetShim(); + assert.isFalse(set.has("a")); + }); - it("has when present", () => { - const set = new SetShim(); - set.add("a"); - assert.isTrue(set.has("a")); - }); + it("has when present", () => { + const set = new SetShim(); + set.add("a"); + assert.isTrue(set.has("a")); + }); - it("delete when not present", () => { - const set = new SetShim(); - assert.isFalse(set.delete("a")); - }); + it("delete when not present", () => { + const set = new SetShim(); + assert.isFalse(set.delete("a")); + }); - it("delete when present", () => { - const set = new SetShim(); - set.add("a"); - assert.isTrue(set.delete("a")); - }); + it("delete when present", () => { + const set = new SetShim(); + set.add("a"); + assert.isTrue(set.delete("a")); + }); - it("delete twice when present", () => { - const set = new SetShim(); - set.add("a"); - assert.isTrue(set.delete("a")); - assert.isFalse(set.delete("a")); - }); + it("delete twice when present", () => { + const set = new SetShim(); + set.add("a"); + assert.isTrue(set.delete("a")); + assert.isFalse(set.delete("a")); + }); - it("remove only item and iterate", () => { - const set = new SetShim(); - set.add("a"); - set.delete("a"); - const actual = arrayFrom(set.keys()); - assert.deepEqual(actual, []); - }); + it("remove only item and iterate", () => { + const set = new SetShim(); + set.add("a"); + set.delete("a"); + const actual = arrayFrom(set.keys()); + assert.deepEqual(actual, []); + }); - it("remove first item and iterate", () => { - const set = new SetShim(); - set.add("a"); - set.add("c"); - set.delete("a"); - assert.deepEqual(arrayFrom(set.keys()), ["c"]); - assert.deepEqual(arrayFrom(set.values()), ["c"]); - assert.deepEqual(arrayFrom(set.entries()), [["c", "c"]]); - }); + it("remove first item and iterate", () => { + const set = new SetShim(); + set.add("a"); + set.add("c"); + set.delete("a"); + assert.deepEqual(arrayFrom(set.keys()), ["c"]); + assert.deepEqual(arrayFrom(set.values()), ["c"]); + assert.deepEqual(arrayFrom(set.entries()), [["c", "c"]]); + }); - it("remove last item and iterate", () => { - const set = new SetShim(); - set.add("a"); - set.add("c"); - set.delete("c"); - assert.deepEqual(arrayFrom(set.keys()), ["a"]); - assert.deepEqual(arrayFrom(set.values()), ["a"]); - assert.deepEqual(arrayFrom(set.entries()), [["a", "a"]]); - }); + it("remove last item and iterate", () => { + const set = new SetShim(); + set.add("a"); + set.add("c"); + set.delete("c"); + assert.deepEqual(arrayFrom(set.keys()), ["a"]); + assert.deepEqual(arrayFrom(set.values()), ["a"]); + assert.deepEqual(arrayFrom(set.entries()), [["a", "a"]]); + }); - it("remove middle item and iterate", () => { - const set = new SetShim(); - set.add("a"); - set.add("c"); - set.add("e"); - set.delete("c"); - assert.deepEqual(arrayFrom(set.keys()), ["a", "e"]); - assert.deepEqual(arrayFrom(set.values()), ["a", "e"]); - assert.deepEqual(arrayFrom(set.entries()), [["a", "a"], ["e", "e"]]); - }); + it("remove middle item and iterate", () => { + const set = new SetShim(); + set.add("a"); + set.add("c"); + set.add("e"); + set.delete("c"); + assert.deepEqual(arrayFrom(set.keys()), ["a", "e"]); + assert.deepEqual(arrayFrom(set.values()), ["a", "e"]); + assert.deepEqual(arrayFrom(set.entries()), [["a", "a"], ["e", "e"]]); + }); - it("keys", () => { - const set = new SetShim(); - set.add("c"); - set.add("a"); - assert.deepEqual(arrayFrom(set.keys()), ["c", "a"]); - }); + it("keys", () => { + const set = new SetShim(); + set.add("c"); + set.add("a"); + assert.deepEqual(arrayFrom(set.keys()), ["c", "a"]); + }); - it("values", () => { - const set = new SetShim(); - set.add("c"); - set.add("a"); - assert.deepEqual(arrayFrom(set.values()), ["c", "a"]); - }); + it("values", () => { + const set = new SetShim(); + set.add("c"); + set.add("a"); + assert.deepEqual(arrayFrom(set.values()), ["c", "a"]); + }); - it("entries", () => { - const set = new SetShim(); - set.add("c"); - set.add("a"); - assert.deepEqual(arrayFrom(set.entries()), [["c", "c"], ["a", "a"]]); - }); + it("entries", () => { + const set = new SetShim(); + set.add("c"); + set.add("a"); + assert.deepEqual(arrayFrom(set.entries()), [["c", "c"], ["a", "a"]]); + }); - it("forEach", () => { - const set = new SetShim(); - set.add("c"); - set.add("a"); - const actual: [string, string][] = []; - set.forEach((value, key) => actual.push([key, value])); - assert.deepEqual(actual, [["c", "c"], ["a", "a"]]); - }); + it("forEach", () => { + const set = new SetShim(); + set.add("c"); + set.add("a"); + const actual: [string, string][] = []; + set.forEach((value, key) => actual.push([key, value])); + assert.deepEqual(actual, [["c", "c"], ["a", "a"]]); }); +}); } diff --git a/src/testRunner/unittests/customTransforms.ts b/src/testRunner/unittests/customTransforms.ts index 0f65aba6e3408..06228f7d2b7d1 100644 --- a/src/testRunner/unittests/customTransforms.ts +++ b/src/testRunner/unittests/customTransforms.ts @@ -1,168 +1,168 @@ namespace ts { - describe("unittests:: customTransforms", () => { - function emitsCorrectly(name: string, sources: { file: string, text: string }[], customTransformers: CustomTransformers, options: CompilerOptions = {}) { - it(name, () => { - const roots = sources.map(source => createSourceFile(source.file, source.text, ScriptTarget.ES2015)); - const fileMap = arrayToMap(roots, file => file.fileName); - const outputs = new Map(); - const host: CompilerHost = { - getSourceFile: (fileName) => fileMap.get(fileName), - getDefaultLibFileName: () => "lib.d.ts", - getCurrentDirectory: () => "", - getDirectories: () => [], - getCanonicalFileName: (fileName) => fileName, - useCaseSensitiveFileNames: () => true, - getNewLine: () => "\n", - fileExists: (fileName) => fileMap.has(fileName), - readFile: (fileName) => fileMap.has(fileName) ? fileMap.get(fileName)!.text : undefined, - writeFile: (fileName, text) => outputs.set(fileName, text), - }; +describe("unittests:: customTransforms", () => { + function emitsCorrectly(name: string, sources: { file: string, text: string }[], customTransformers: CustomTransformers, options: CompilerOptions = {}) { + it(name, () => { + const roots = sources.map(source => createSourceFile(source.file, source.text, ScriptTarget.ES2015)); + const fileMap = arrayToMap(roots, file => file.fileName); + const outputs = new Map(); + const host: CompilerHost = { + getSourceFile: (fileName) => fileMap.get(fileName), + getDefaultLibFileName: () => "lib.d.ts", + getCurrentDirectory: () => "", + getDirectories: () => [], + getCanonicalFileName: (fileName) => fileName, + useCaseSensitiveFileNames: () => true, + getNewLine: () => "\n", + fileExists: (fileName) => fileMap.has(fileName), + readFile: (fileName) => fileMap.has(fileName) ? fileMap.get(fileName)!.text : undefined, + writeFile: (fileName, text) => outputs.set(fileName, text), + }; - const program = createProgram(arrayFrom(fileMap.keys()), { newLine: NewLineKind.LineFeed, ...options }, host); - program.emit(/*targetSourceFile*/ undefined, host.writeFile, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ false, customTransformers); - let content = ""; - for (const [file, text] of arrayFrom(outputs.entries())) { - if (content) content += "\n\n"; - content += `// [${file}]\n`; - content += text; - } - Harness.Baseline.runBaseline(`customTransforms/${name}.js`, content); - }); - } + const program = createProgram(arrayFrom(fileMap.keys()), { newLine: NewLineKind.LineFeed, ...options }, host); + program.emit(/*targetSourceFile*/ undefined, host.writeFile, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ false, customTransformers); + let content = ""; + for (const [file, text] of arrayFrom(outputs.entries())) { + if (content) content += "\n\n"; + content += `// [${file}]\n`; + content += text; + } + Harness.Baseline.runBaseline(`customTransforms/${name}.js`, content); + }); + } - const sources = [{ - file: "source.ts", - text: ` + const sources = [{ + file: "source.ts", + text: ` function f1() { } class c() { } enum e { } // leading function f2() { } // trailing ` - }]; + }]; - const before: TransformerFactory = context => { - return file => visitEachChild(file, visit, context); - function visit(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - return visitFunction(node as FunctionDeclaration); - default: - return visitEachChild(node, visit, context); - } - } - function visitFunction(node: FunctionDeclaration) { - addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, "@before", /*hasTrailingNewLine*/ true); - return node; + const before: TransformerFactory = context => { + return file => visitEachChild(file, visit, context); + function visit(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return visitFunction(node as FunctionDeclaration); + default: + return visitEachChild(node, visit, context); } - }; + } + function visitFunction(node: FunctionDeclaration) { + addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, "@before", /*hasTrailingNewLine*/ true); + return node; + } + }; - const after: TransformerFactory = context => { - return file => visitEachChild(file, visit, context); - function visit(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.VariableStatement: - return visitVariableStatement(node as VariableStatement); - default: - return visitEachChild(node, visit, context); - } + const after: TransformerFactory = context => { + return file => visitEachChild(file, visit, context); + function visit(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.VariableStatement: + return visitVariableStatement(node as VariableStatement); + default: + return visitEachChild(node, visit, context); } - function visitVariableStatement(node: VariableStatement) { - addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, "@after"); - return node; - } - }; + } + function visitVariableStatement(node: VariableStatement) { + addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, "@after"); + return node; + } + }; - emitsCorrectly("before", sources, { before: [before] }); - emitsCorrectly("after", sources, { after: [after] }); - emitsCorrectly("both", sources, { before: [before], after: [after] }); + emitsCorrectly("before", sources, { before: [before] }); + emitsCorrectly("after", sources, { after: [after] }); + emitsCorrectly("both", sources, { before: [before], after: [after] }); - emitsCorrectly("before+decorators", [{ - file: "source.ts", - text: ` + emitsCorrectly("before+decorators", [{ + file: "source.ts", + text: ` declare const dec: any; class B {} @dec export class C { constructor(b: B) { } } 'change' ` - }], {before: [ - context => node => visitNode(node, function visitor(node: Node): Node { - if (isStringLiteral(node) && node.text === "change") return factory.createStringLiteral("changed"); - return visitEachChild(node, visitor, context); - }) - ]}, { - target: ScriptTarget.ES5, - module: ModuleKind.ES2015, - emitDecoratorMetadata: true, - experimentalDecorators: true - }); + }], {before: [ + context => node => visitNode(node, function visitor(node: Node): Node { + if (isStringLiteral(node) && node.text === "change") return factory.createStringLiteral("changed"); + return visitEachChild(node, visitor, context); + }) + ]}, { + target: ScriptTarget.ES5, + module: ModuleKind.ES2015, + emitDecoratorMetadata: true, + experimentalDecorators: true + }); - emitsCorrectly("sourceMapExternalSourceFiles", - [ - { - file: "source.ts", - // The text of length 'changed' is made to be on two lines so we know the line map change - text: `\`multi + emitsCorrectly("sourceMapExternalSourceFiles", + [ + { + file: "source.ts", + // The text of length 'changed' is made to be on two lines so we know the line map change + text: `\`multi line\` 'change'` - }, - ], - { - before: [ - context => node => visitNode(node, function visitor(node: Node): Node { - if (isStringLiteral(node) && node.text === "change") { - const text = "'changed'"; - const lineMap = computeLineStarts(text); - setSourceMapRange(node, { - pos: 0, end: text.length, source: { - text, - fileName: "another.html", - lineMap, - getLineAndCharacterOfPosition: pos => computeLineAndCharacterOfPosition(lineMap, pos) - } - }); - return node; - } - return visitEachChild(node, visitor, context); - }) - ] }, - { sourceMap: true } - ); - - emitsCorrectly("skipTriviaExternalSourceFiles", - [ - { - file: "source.ts", - // The source file contains preceding trivia (e.g. whitespace) to try to confuse the `skipSourceTrivia` function. - text: " original;" - }, - ], - { - before: [ - context => { - const transformSourceFile: Transformer = node => visitNode(node, function visitor(node: Node): Node { - if (isIdentifier(node) && node.text === "original") { - const newNode = factory.createIdentifier("changed"); - setSourceMapRange(newNode, { - pos: 0, - end: 7, - // Do not provide a custom skipTrivia function for `source`. - source: createSourceMapSource("another.html", "changed;") - }); - return newNode; + ], + { + before: [ + context => node => visitNode(node, function visitor(node: Node): Node { + if (isStringLiteral(node) && node.text === "change") { + const text = "'changed'"; + const lineMap = computeLineStarts(text); + setSourceMapRange(node, { + pos: 0, end: text.length, source: { + text, + fileName: "another.html", + lineMap, + getLineAndCharacterOfPosition: pos => computeLineAndCharacterOfPosition(lineMap, pos) } - return visitEachChild(node, visitor, context); }); - return { - transformSourceFile, - transformBundle: node => factory.createBundle(map(node.sourceFiles, transformSourceFile), node.prepends), - }; + return node; } - ] + return visitEachChild(node, visitor, context); + }) + ] + }, + { sourceMap: true } + ); + + emitsCorrectly("skipTriviaExternalSourceFiles", + [ + { + file: "source.ts", + // The source file contains preceding trivia (e.g. whitespace) to try to confuse the `skipSourceTrivia` function. + text: " original;" }, - { sourceMap: true, outFile: "source.js" } - ); + ], + { + before: [ + context => { + const transformSourceFile: Transformer = node => visitNode(node, function visitor(node: Node): Node { + if (isIdentifier(node) && node.text === "original") { + const newNode = factory.createIdentifier("changed"); + setSourceMapRange(newNode, { + pos: 0, + end: 7, + // Do not provide a custom skipTrivia function for `source`. + source: createSourceMapSource("another.html", "changed;") + }); + return newNode; + } + return visitEachChild(node, visitor, context); + }); + return { + transformSourceFile, + transformBundle: node => factory.createBundle(map(node.sourceFiles, transformSourceFile), node.prepends), + }; + } + ] + }, + { sourceMap: true, outFile: "source.js" } + ); - }); +}); } diff --git a/src/testRunner/unittests/debugDeprecation.ts b/src/testRunner/unittests/debugDeprecation.ts index 86d4490c4c2c2..744add29ffb06 100644 --- a/src/testRunner/unittests/debugDeprecation.ts +++ b/src/testRunner/unittests/debugDeprecation.ts @@ -1,95 +1,95 @@ namespace ts { - describe("unittests:: debugDeprecation", () => { - beforeEach(() => { - const loggingHost = Debug.loggingHost; - afterEach(() => { - Debug.loggingHost = loggingHost; - }); +describe("unittests:: debugDeprecation", () => { + beforeEach(() => { + const loggingHost = Debug.loggingHost; + afterEach(() => { + Debug.loggingHost = loggingHost; }); - describe("deprecateFunction", () => { - it("silent deprecation", () => { - const deprecation = Debug.deprecate(noop, { - warnAfter: "3.9", - typeScriptVersion: "3.8" - }); - let logWritten = false; - Debug.loggingHost = { - log() { - logWritten = true; - } - }; - deprecation(); - assert.isFalse(logWritten); + }); + describe("deprecateFunction", () => { + it("silent deprecation", () => { + const deprecation = Debug.deprecate(noop, { + warnAfter: "3.9", + typeScriptVersion: "3.8" }); - it("warning deprecation with warnAfter", () => { - const deprecation = Debug.deprecate(noop, { - warnAfter: "3.9", - typeScriptVersion: "3.9" - }); - let logWritten = false; - Debug.loggingHost = { - log() { - logWritten = true; - } - }; - deprecation(); - assert.isTrue(logWritten); + let logWritten = false; + Debug.loggingHost = { + log() { + logWritten = true; + } + }; + deprecation(); + assert.isFalse(logWritten); + }); + it("warning deprecation with warnAfter", () => { + const deprecation = Debug.deprecate(noop, { + warnAfter: "3.9", + typeScriptVersion: "3.9" }); - it("warning deprecation without warnAfter", () => { - const deprecation = Debug.deprecate(noop, { - typeScriptVersion: "3.9" - }); - let logWritten = false; - Debug.loggingHost = { - log() { - logWritten = true; - } - }; - deprecation(); - assert.isTrue(logWritten); + let logWritten = false; + Debug.loggingHost = { + log() { + logWritten = true; + } + }; + deprecation(); + assert.isTrue(logWritten); + }); + it("warning deprecation without warnAfter", () => { + const deprecation = Debug.deprecate(noop, { + typeScriptVersion: "3.9" }); - it("warning deprecation writes once", () => { - const deprecation = Debug.deprecate(noop, { - typeScriptVersion: "3.9" - }); - let logWrites = 0; - Debug.loggingHost = { - log() { - logWrites++; - } - }; - deprecation(); - deprecation(); - assert.equal(logWrites, 1); + let logWritten = false; + Debug.loggingHost = { + log() { + logWritten = true; + } + }; + deprecation(); + assert.isTrue(logWritten); + }); + it("warning deprecation writes once", () => { + const deprecation = Debug.deprecate(noop, { + typeScriptVersion: "3.9" }); - it("error deprecation with errorAfter", () => { - const deprecation = Debug.deprecate(noop, { - warnAfter: "3.8", - errorAfter: "3.9", - typeScriptVersion: "3.9" - }); - let logWritten = false; - Debug.loggingHost = { - log() { - logWritten = true; - } - }; - expect(deprecation).throws(); - assert.isFalse(logWritten); + let logWrites = 0; + Debug.loggingHost = { + log() { + logWrites++; + } + }; + deprecation(); + deprecation(); + assert.equal(logWrites, 1); + }); + it("error deprecation with errorAfter", () => { + const deprecation = Debug.deprecate(noop, { + warnAfter: "3.8", + errorAfter: "3.9", + typeScriptVersion: "3.9" }); - it("error deprecation with error", () => { - const deprecation = Debug.deprecate(noop, { - error: true, - }); - let logWritten = false; - Debug.loggingHost = { - log() { - logWritten = true; - } - }; - expect(deprecation).throws(); - assert.isFalse(logWritten); + let logWritten = false; + Debug.loggingHost = { + log() { + logWritten = true; + } + }; + expect(deprecation).throws(); + assert.isFalse(logWritten); + }); + it("error deprecation with error", () => { + const deprecation = Debug.deprecate(noop, { + error: true, }); + let logWritten = false; + Debug.loggingHost = { + log() { + logWritten = true; + } + }; + expect(deprecation).throws(); + assert.isFalse(logWritten); }); }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/evaluation/externalModules.ts b/src/testRunner/unittests/evaluation/externalModules.ts index 18bb0fa043f80..8febc2a6704a4 100644 --- a/src/testRunner/unittests/evaluation/externalModules.ts +++ b/src/testRunner/unittests/evaluation/externalModules.ts @@ -81,4 +81,4 @@ describe("unittests:: evaluation:: externalModules", () => { assert.equal(result.output[2], true); // `f.call(obj, obj)`. Behavior of `.call` (or `.apply`, etc.) should not be affected. assert.equal(result.output[3], true); // `other.f(other)`. `this` is still namespace because it is left of `.`. }); -}); \ No newline at end of file +}); diff --git a/src/testRunner/unittests/evaluation/forOf.ts b/src/testRunner/unittests/evaluation/forOf.ts index 9efdf16d351cd..3df4c762da2b9 100644 --- a/src/testRunner/unittests/evaluation/forOf.ts +++ b/src/testRunner/unittests/evaluation/forOf.ts @@ -116,4 +116,4 @@ describe("unittests:: evaluation:: forOfEvaluation", () => { result.main(); }); -}); \ No newline at end of file +}); diff --git a/src/testRunner/unittests/evaluation/superInStaticInitializer.ts b/src/testRunner/unittests/evaluation/superInStaticInitializer.ts index da1dc5fe888d9..73e591e78e553 100644 --- a/src/testRunner/unittests/evaluation/superInStaticInitializer.ts +++ b/src/testRunner/unittests/evaluation/superInStaticInitializer.ts @@ -188,4 +188,4 @@ describe("unittests:: evaluation:: superInStaticInitializer", () => { const [Derived] = result.main(); assert.strictEqual(Derived.y, 2); }); -}); \ No newline at end of file +}); diff --git a/src/testRunner/unittests/evaluation/updateExpressionInModule.ts b/src/testRunner/unittests/evaluation/updateExpressionInModule.ts index 443a1fb71a11b..8cbe776ab69c5 100644 --- a/src/testRunner/unittests/evaluation/updateExpressionInModule.ts +++ b/src/testRunner/unittests/evaluation/updateExpressionInModule.ts @@ -122,4 +122,4 @@ describe("unittests:: evaluation:: updateExpressionInModule", () => { assert.equal(result.a, BigInt(2)); assert.equal(result.b, BigInt(1)); }); -}); \ No newline at end of file +}); diff --git a/src/testRunner/unittests/factory.ts b/src/testRunner/unittests/factory.ts index a413881c07d3e..2c27bd0e0e1ab 100644 --- a/src/testRunner/unittests/factory.ts +++ b/src/testRunner/unittests/factory.ts @@ -1,86 +1,86 @@ namespace ts { - describe("unittests:: FactoryAPI", () => { - function assertSyntaxKind(node: Node, expected: SyntaxKind) { - assert.strictEqual(node.kind, expected, `Actual: ${Debug.formatSyntaxKind(node.kind)} Expected: ${Debug.formatSyntaxKind(expected)}`); - } - describe("factory.createExportAssignment", () => { - it("parenthesizes default export if necessary", () => { - function checkExpression(expression: Expression) { - const node = factory.createExportAssignment( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*isExportEquals*/ false, - expression, - ); - assertSyntaxKind(node.expression, SyntaxKind.ParenthesizedExpression); - } - - const clazz = factory.createClassExpression(/*decorators*/ undefined, /*modifiers*/ undefined, "C", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ - factory.createPropertyDeclaration(/*decorators*/ undefined, [factory.createToken(SyntaxKind.StaticKeyword)], "prop", /*questionOrExclamationToken*/ undefined, /*type*/ undefined, factory.createStringLiteral("1")), - ]); - checkExpression(clazz); - checkExpression(factory.createPropertyAccessExpression(clazz, "prop")); - - const func = factory.createFunctionExpression(/*modifiers*/ undefined, /*asteriskToken*/ undefined, "fn", /*typeParameters*/ undefined, /*parameters*/ undefined, /*type*/ undefined, factory.createBlock([])); - checkExpression(func); - checkExpression(factory.createCallExpression(func, /*typeArguments*/ undefined, /*argumentsArray*/ undefined)); - checkExpression(factory.createTaggedTemplateExpression(func, /*typeArguments*/ undefined, factory.createNoSubstitutionTemplateLiteral(""))); +describe("unittests:: FactoryAPI", () => { + function assertSyntaxKind(node: Node, expected: SyntaxKind) { + assert.strictEqual(node.kind, expected, `Actual: ${Debug.formatSyntaxKind(node.kind)} Expected: ${Debug.formatSyntaxKind(expected)}`); + } + describe("factory.createExportAssignment", () => { + it("parenthesizes default export if necessary", () => { + function checkExpression(expression: Expression) { + const node = factory.createExportAssignment( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*isExportEquals*/ false, + expression, + ); + assertSyntaxKind(node.expression, SyntaxKind.ParenthesizedExpression); + } - checkExpression(factory.createBinaryExpression(factory.createStringLiteral("a"), SyntaxKind.CommaToken, factory.createStringLiteral("b"))); - checkExpression(factory.createCommaListExpression([factory.createStringLiteral("a"), factory.createStringLiteral("b")])); - }); - }); + const clazz = factory.createClassExpression(/*decorators*/ undefined, /*modifiers*/ undefined, "C", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ + factory.createPropertyDeclaration(/*decorators*/ undefined, [factory.createToken(SyntaxKind.StaticKeyword)], "prop", /*questionOrExclamationToken*/ undefined, /*type*/ undefined, factory.createStringLiteral("1")), + ]); + checkExpression(clazz); + checkExpression(factory.createPropertyAccessExpression(clazz, "prop")); - describe("factory.createArrowFunction", () => { - it("parenthesizes concise body if necessary", () => { - function checkBody(body: ConciseBody) { - const node = factory.createArrowFunction( - /*modifiers*/ undefined, - /*typeParameters*/ undefined, - [], - /*type*/ undefined, - /*equalsGreaterThanToken*/ undefined, - body, - ); - assertSyntaxKind(node.body, SyntaxKind.ParenthesizedExpression); - } + const func = factory.createFunctionExpression(/*modifiers*/ undefined, /*asteriskToken*/ undefined, "fn", /*typeParameters*/ undefined, /*parameters*/ undefined, /*type*/ undefined, factory.createBlock([])); + checkExpression(func); + checkExpression(factory.createCallExpression(func, /*typeArguments*/ undefined, /*argumentsArray*/ undefined)); + checkExpression(factory.createTaggedTemplateExpression(func, /*typeArguments*/ undefined, factory.createNoSubstitutionTemplateLiteral(""))); - checkBody(factory.createObjectLiteralExpression()); - checkBody(factory.createPropertyAccessExpression(factory.createObjectLiteralExpression(), "prop")); - checkBody(factory.createAsExpression(factory.createPropertyAccessExpression(factory.createObjectLiteralExpression(), "prop"), factory.createTypeReferenceNode("T", /*typeArguments*/ undefined))); - checkBody(factory.createNonNullExpression(factory.createPropertyAccessExpression(factory.createObjectLiteralExpression(), "prop"))); - checkBody(factory.createCommaListExpression([factory.createStringLiteral("a"), factory.createStringLiteral("b")])); - checkBody(factory.createBinaryExpression(factory.createStringLiteral("a"), SyntaxKind.CommaToken, factory.createStringLiteral("b"))); - }); + checkExpression(factory.createBinaryExpression(factory.createStringLiteral("a"), SyntaxKind.CommaToken, factory.createStringLiteral("b"))); + checkExpression(factory.createCommaListExpression([factory.createStringLiteral("a"), factory.createStringLiteral("b")])); }); + }); - describe("createBinaryExpression", () => { - it("parenthesizes arrow function in RHS if necessary", () => { - const lhs = factory.createIdentifier("foo"); - const rhs = factory.createArrowFunction( + describe("factory.createArrowFunction", () => { + it("parenthesizes concise body if necessary", () => { + function checkBody(body: ConciseBody) { + const node = factory.createArrowFunction( /*modifiers*/ undefined, /*typeParameters*/ undefined, [], /*type*/ undefined, /*equalsGreaterThanToken*/ undefined, - factory.createBlock([]), + body, ); - function checkRhs(operator: BinaryOperator, expectParens: boolean) { - const node = factory.createBinaryExpression(lhs, operator, rhs); - assertSyntaxKind(node.right, expectParens ? SyntaxKind.ParenthesizedExpression : SyntaxKind.ArrowFunction); - } + assertSyntaxKind(node.body, SyntaxKind.ParenthesizedExpression); + } + + checkBody(factory.createObjectLiteralExpression()); + checkBody(factory.createPropertyAccessExpression(factory.createObjectLiteralExpression(), "prop")); + checkBody(factory.createAsExpression(factory.createPropertyAccessExpression(factory.createObjectLiteralExpression(), "prop"), factory.createTypeReferenceNode("T", /*typeArguments*/ undefined))); + checkBody(factory.createNonNullExpression(factory.createPropertyAccessExpression(factory.createObjectLiteralExpression(), "prop"))); + checkBody(factory.createCommaListExpression([factory.createStringLiteral("a"), factory.createStringLiteral("b")])); + checkBody(factory.createBinaryExpression(factory.createStringLiteral("a"), SyntaxKind.CommaToken, factory.createStringLiteral("b"))); + }); + }); + + describe("createBinaryExpression", () => { + it("parenthesizes arrow function in RHS if necessary", () => { + const lhs = factory.createIdentifier("foo"); + const rhs = factory.createArrowFunction( + /*modifiers*/ undefined, + /*typeParameters*/ undefined, + [], + /*type*/ undefined, + /*equalsGreaterThanToken*/ undefined, + factory.createBlock([]), + ); + function checkRhs(operator: BinaryOperator, expectParens: boolean) { + const node = factory.createBinaryExpression(lhs, operator, rhs); + assertSyntaxKind(node.right, expectParens ? SyntaxKind.ParenthesizedExpression : SyntaxKind.ArrowFunction); + } - checkRhs(SyntaxKind.CommaToken, /*expectParens*/ false); - checkRhs(SyntaxKind.EqualsToken, /*expectParens*/ false); - checkRhs(SyntaxKind.PlusEqualsToken, /*expectParens*/ false); - checkRhs(SyntaxKind.BarBarToken, /*expectParens*/ true); - checkRhs(SyntaxKind.AmpersandAmpersandToken, /*expectParens*/ true); - checkRhs(SyntaxKind.QuestionQuestionToken, /*expectParens*/ true); - checkRhs(SyntaxKind.EqualsEqualsToken, /*expectParens*/ true); - checkRhs(SyntaxKind.BarBarEqualsToken, /*expectParens*/ false); - checkRhs(SyntaxKind.AmpersandAmpersandEqualsToken, /*expectParens*/ false); - checkRhs(SyntaxKind.QuestionQuestionEqualsToken, /*expectParens*/ false); - }); + checkRhs(SyntaxKind.CommaToken, /*expectParens*/ false); + checkRhs(SyntaxKind.EqualsToken, /*expectParens*/ false); + checkRhs(SyntaxKind.PlusEqualsToken, /*expectParens*/ false); + checkRhs(SyntaxKind.BarBarToken, /*expectParens*/ true); + checkRhs(SyntaxKind.AmpersandAmpersandToken, /*expectParens*/ true); + checkRhs(SyntaxKind.QuestionQuestionToken, /*expectParens*/ true); + checkRhs(SyntaxKind.EqualsEqualsToken, /*expectParens*/ true); + checkRhs(SyntaxKind.BarBarEqualsToken, /*expectParens*/ false); + checkRhs(SyntaxKind.AmpersandAmpersandEqualsToken, /*expectParens*/ false); + checkRhs(SyntaxKind.QuestionQuestionEqualsToken, /*expectParens*/ false); }); }); +}); } diff --git a/src/testRunner/unittests/incrementalParser.ts b/src/testRunner/unittests/incrementalParser.ts index 07be24b4b5b4a..0641efeadc2b7 100644 --- a/src/testRunner/unittests/incrementalParser.ts +++ b/src/testRunner/unittests/incrementalParser.ts @@ -1,844 +1,844 @@ namespace ts { - function withChange(text: IScriptSnapshot, start: number, length: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { - const contents = getSnapshotText(text); - const newContents = contents.substr(0, start) + newText + contents.substring(start + length); +function withChange(text: IScriptSnapshot, start: number, length: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { + const contents = getSnapshotText(text); + const newContents = contents.substr(0, start) + newText + contents.substring(start + length); - return { text: ScriptSnapshot.fromString(newContents), textChangeRange: createTextChangeRange(createTextSpan(start, length), newText.length) }; - } + return { text: ScriptSnapshot.fromString(newContents), textChangeRange: createTextChangeRange(createTextSpan(start, length), newText.length) }; +} - function withInsert(text: IScriptSnapshot, start: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { - return withChange(text, start, 0, newText); - } +function withInsert(text: IScriptSnapshot, start: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { + return withChange(text, start, 0, newText); +} - function withDelete(text: IScriptSnapshot, start: number, length: number): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { - return withChange(text, start, length, ""); - } +function withDelete(text: IScriptSnapshot, start: number, length: number): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { + return withChange(text, start, length, ""); +} - function createTree(text: IScriptSnapshot, version: string) { - return createLanguageServiceSourceFile(/*fileName:*/ "", text, ScriptTarget.Latest, version, /*setNodeParents:*/ true); - } +function createTree(text: IScriptSnapshot, version: string) { + return createLanguageServiceSourceFile(/*fileName:*/ "", text, ScriptTarget.Latest, version, /*setNodeParents:*/ true); +} - function assertSameDiagnostics(file1: SourceFile, file2: SourceFile) { - const diagnostics1 = file1.parseDiagnostics; - const diagnostics2 = file2.parseDiagnostics; - - assert.equal(diagnostics1.length, diagnostics2.length, "diagnostics1.length !== diagnostics2.length"); - for (let i = 0; i < diagnostics1.length; i++) { - const d1 = diagnostics1[i]; - const d2 = diagnostics2[i]; - - assert.equal(d1.file, file1, "d1.file !== file1"); - assert.equal(d2.file, file2, "d2.file !== file2"); - assert.equal(d1.start, d2.start, "d1.start !== d2.start"); - assert.equal(d1.length, d2.length, "d1.length !== d2.length"); - assert.equal(d1.messageText, d2.messageText, "d1.messageText !== d2.messageText"); - assert.equal(d1.category, d2.category, "d1.category !== d2.category"); - assert.equal(d1.code, d2.code, "d1.code !== d2.code"); - } +function assertSameDiagnostics(file1: SourceFile, file2: SourceFile) { + const diagnostics1 = file1.parseDiagnostics; + const diagnostics2 = file2.parseDiagnostics; + + assert.equal(diagnostics1.length, diagnostics2.length, "diagnostics1.length !== diagnostics2.length"); + for (let i = 0; i < diagnostics1.length; i++) { + const d1 = diagnostics1[i]; + const d2 = diagnostics2[i]; + + assert.equal(d1.file, file1, "d1.file !== file1"); + assert.equal(d2.file, file2, "d2.file !== file2"); + assert.equal(d1.start, d2.start, "d1.start !== d2.start"); + assert.equal(d1.length, d2.length, "d1.length !== d2.length"); + assert.equal(d1.messageText, d2.messageText, "d1.messageText !== d2.messageText"); + assert.equal(d1.category, d2.category, "d1.category !== d2.category"); + assert.equal(d1.code, d2.code, "d1.code !== d2.code"); } +} - // NOTE: 'reusedElements' is the expected count of elements reused from the old tree to the new - // tree. It may change as we tweak the parser. If the count increases then that should always - // be a good thing. If it decreases, that's not great (less reusability), but that may be - // unavoidable. If it does decrease an investigation should be done to make sure that things - // are still ok and we're still appropriately reusing most of the tree. - function compareTrees(oldText: IScriptSnapshot, newText: IScriptSnapshot, textChangeRange: TextChangeRange, expectedReusedElements: number, oldTree?: SourceFile) { - oldTree = oldTree || createTree(oldText, /*version:*/ "."); - Utils.assertInvariants(oldTree, /*parent:*/ undefined); - - // Create a tree for the new text, in a non-incremental fashion. - const newTree = createTree(newText, oldTree.version + "."); - Utils.assertInvariants(newTree, /*parent:*/ undefined); +// NOTE: 'reusedElements' is the expected count of elements reused from the old tree to the new +// tree. It may change as we tweak the parser. If the count increases then that should always +// be a good thing. If it decreases, that's not great (less reusability), but that may be +// unavoidable. If it does decrease an investigation should be done to make sure that things +// are still ok and we're still appropriately reusing most of the tree. +function compareTrees(oldText: IScriptSnapshot, newText: IScriptSnapshot, textChangeRange: TextChangeRange, expectedReusedElements: number, oldTree?: SourceFile) { + oldTree = oldTree || createTree(oldText, /*version:*/ "."); + Utils.assertInvariants(oldTree, /*parent:*/ undefined); - // Create a tree for the new text, in an incremental fashion. - const incrementalNewTree = updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", textChangeRange); - Utils.assertInvariants(incrementalNewTree, /*parent:*/ undefined); + // Create a tree for the new text, in a non-incremental fashion. + const newTree = createTree(newText, oldTree.version + "."); + Utils.assertInvariants(newTree, /*parent:*/ undefined); - // We should get the same tree when doign a full or incremental parse. - Utils.assertStructuralEquals(newTree, incrementalNewTree); + // Create a tree for the new text, in an incremental fashion. + const incrementalNewTree = updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", textChangeRange); + Utils.assertInvariants(incrementalNewTree, /*parent:*/ undefined); - // We should also get the exact same set of diagnostics. - assertSameDiagnostics(newTree, incrementalNewTree); + // We should get the same tree when doign a full or incremental parse. + Utils.assertStructuralEquals(newTree, incrementalNewTree); - // There should be no reused nodes between two trees that are fully parsed. - assert.isTrue(reusedElements(oldTree, newTree) === 0); + // We should also get the exact same set of diagnostics. + assertSameDiagnostics(newTree, incrementalNewTree); - assert.equal(newTree.fileName, incrementalNewTree.fileName, "newTree.fileName !== incrementalNewTree.fileName"); - assert.equal(newTree.text, incrementalNewTree.text, "newTree.text !== incrementalNewTree.text"); + // There should be no reused nodes between two trees that are fully parsed. + assert.isTrue(reusedElements(oldTree, newTree) === 0); - if (expectedReusedElements !== -1) { - const actualReusedCount = reusedElements(oldTree, incrementalNewTree); - assert.equal(actualReusedCount, expectedReusedElements, actualReusedCount + " !== " + expectedReusedElements); - } + assert.equal(newTree.fileName, incrementalNewTree.fileName, "newTree.fileName !== incrementalNewTree.fileName"); + assert.equal(newTree.text, incrementalNewTree.text, "newTree.text !== incrementalNewTree.text"); - return { oldTree, newTree, incrementalNewTree }; + if (expectedReusedElements !== -1) { + const actualReusedCount = reusedElements(oldTree, incrementalNewTree); + assert.equal(actualReusedCount, expectedReusedElements, actualReusedCount + " !== " + expectedReusedElements); } - function reusedElements(oldNode: SourceFile, newNode: SourceFile): number { - const allOldElements = collectElements(oldNode); - const allNewElements = collectElements(newNode); + return { oldTree, newTree, incrementalNewTree }; +} + +function reusedElements(oldNode: SourceFile, newNode: SourceFile): number { + const allOldElements = collectElements(oldNode); + const allNewElements = collectElements(newNode); - return filter(allOldElements, v => contains(allNewElements, v)).length; - } + return filter(allOldElements, v => contains(allNewElements, v)).length; +} - function collectElements(node: Node) { - const result: Node[] = []; - visit(node); - return result; +function collectElements(node: Node) { + const result: Node[] = []; + visit(node); + return result; - function visit(node: Node) { - result.push(node); - forEachChild(node, visit); - } + function visit(node: Node) { + result.push(node); + forEachChild(node, visit); } +} + +function deleteCode(source: string, index: number, toDelete: string) { + const repeat = toDelete.length; + let oldTree = createTree(ScriptSnapshot.fromString(source), /*version:*/ "."); + for (let i = 0; i < repeat; i++) { + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index, 1); + const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree; - function deleteCode(source: string, index: number, toDelete: string) { - const repeat = toDelete.length; - let oldTree = createTree(ScriptSnapshot.fromString(source), /*version:*/ "."); - for (let i = 0; i < repeat; i++) { - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index, 1); - const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree; - - source = getSnapshotText(newTextAndChange.text); - oldTree = newTree; - } + source = getSnapshotText(newTextAndChange.text); + oldTree = newTree; } +} - function insertCode(source: string, index: number, toInsert: string) { - const repeat = toInsert.length; - let oldTree = createTree(ScriptSnapshot.fromString(source), /*version:*/ "."); - for (let i = 0; i < repeat; i++) { - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + i, toInsert.charAt(i)); - const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree; - - source = getSnapshotText(newTextAndChange.text); - oldTree = newTree; - } +function insertCode(source: string, index: number, toInsert: string) { + const repeat = toInsert.length; + let oldTree = createTree(ScriptSnapshot.fromString(source), /*version:*/ "."); + for (let i = 0; i < repeat; i++) { + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + i, toInsert.charAt(i)); + const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree; + + source = getSnapshotText(newTextAndChange.text); + oldTree = newTree; } +} - describe("unittests:: Incremental Parser", () => { - it("Inserting into method", () => { - const source = "class C {\r\n" + - " public foo1() { }\r\n" + - " public foo2() {\r\n" + - " return 1;\r\n" + - " }\r\n" + - " public foo3() { }\r\n" + - "}"; - - const oldText = ScriptSnapshot.fromString(source); - const semicolonIndex = source.indexOf(";"); - const newTextAndChange = withInsert(oldText, semicolonIndex, " + 1"); - - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); - }); +describe("unittests:: Incremental Parser", () => { + it("Inserting into method", () => { + const source = "class C {\r\n" + + " public foo1() { }\r\n" + + " public foo2() {\r\n" + + " return 1;\r\n" + + " }\r\n" + + " public foo3() { }\r\n" + + "}"; + + const oldText = ScriptSnapshot.fromString(source); + const semicolonIndex = source.indexOf(";"); + const newTextAndChange = withInsert(oldText, semicolonIndex, " + 1"); + + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); + }); - it("Deleting from method", () => { - const source = "class C {\r\n" + - " public foo1() { }\r\n" + - " public foo2() {\r\n" + - " return 1 + 1;\r\n" + - " }\r\n" + - " public foo3() { }\r\n" + - "}"; + it("Deleting from method", () => { + const source = "class C {\r\n" + + " public foo1() { }\r\n" + + " public foo2() {\r\n" + + " return 1 + 1;\r\n" + + " }\r\n" + + " public foo3() { }\r\n" + + "}"; - const index = source.indexOf("+ 1"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index, "+ 1".length); + const index = source.indexOf("+ 1"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index, "+ 1".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); + }); - it("Regular expression 1", () => { - const source = "class C { public foo1() { /; } public foo2() { return 1;} public foo3() { } }"; + it("Regular expression 1", () => { + const source = "class C { public foo1() { /; } public foo2() { return 1;} public foo3() { } }"; - const semicolonIndex = source.indexOf(";}"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); + const semicolonIndex = source.indexOf(";}"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Regular expression 2", () => { - const source = "class C { public foo1() { ; } public foo2() { return 1/;} public foo3() { } }"; + it("Regular expression 2", () => { + const source = "class C { public foo1() { ; } public foo2() { return 1/;} public foo3() { } }"; - const semicolonIndex = source.indexOf(";"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); + const semicolonIndex = source.indexOf(";"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); + }); - it("Comment 1", () => { - const source = "class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }"; + it("Comment 1", () => { + const source = "class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }"; - const semicolonIndex = source.indexOf(";"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); + const semicolonIndex = source.indexOf(";"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Comment 2", () => { - const source = "class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }"; + it("Comment 2", () => { + const source = "class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, 0, "//"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, 0, "//"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Comment 3", () => { - const source = "//class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }"; + it("Comment 3", () => { + const source = "//class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, 0, 2); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, 0, 2); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Comment 4", () => { - const source = "class C { public foo1() { /; } public foo2() { */ return 1; } public foo3() { } }"; + it("Comment 4", () => { + const source = "class C { public foo1() { /; } public foo2() { */ return 1; } public foo3() { } }"; - const index = source.indexOf(";"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index, "*"); + const index = source.indexOf(";"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index, "*"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); + }); - it("Parameter 1", () => { - // Should be able to reuse all the parameters. - const source = "class C {\r\n" + - " public foo2(a, b, c, d) {\r\n" + - " return 1;\r\n" + - " }\r\n" + - "}"; + it("Parameter 1", () => { + // Should be able to reuse all the parameters. + const source = "class C {\r\n" + + " public foo2(a, b, c, d) {\r\n" + + " return 1;\r\n" + + " }\r\n" + + "}"; - const semicolonIndex = source.indexOf(";"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, semicolonIndex, " + 1"); + const semicolonIndex = source.indexOf(";"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, semicolonIndex, " + 1"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); + }); - it("Type member 1", () => { - // Should be able to reuse most of the type members. - const source = "interface I { a: number; b: string; (c): d; new (e): f; g(): h }"; + it("Type member 1", () => { + // Should be able to reuse most of the type members. + const source = "interface I { a: number; b: string; (c): d; new (e): f; g(): h }"; - const index = source.indexOf(": string"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index, "?"); + const index = source.indexOf(": string"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index, "?"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); + }); - it("Enum element 1", () => { - // Should be able to reuse most of the enum elements. - const source = "enum E { a = 1, b = 1 << 1, c = 3, e = 4, f = 5, g = 7, h = 8, i = 9, j = 10 }"; + it("Enum element 1", () => { + // Should be able to reuse most of the enum elements. + const source = "enum E { a = 1, b = 1 << 1, c = 3, e = 4, f = 5, g = 7, h = 8, i = 9, j = 10 }"; - const index = source.indexOf("<<"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, 2, "+"); + const index = source.indexOf("<<"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, index, 2, "+"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 24); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 24); + }); - it("Strict mode 1", () => { - const source = "foo1();\r\nfoo1();\r\nfoo1();\r\package();"; + it("Strict mode 1", () => { + const source = "foo1();\r\nfoo1();\r\nfoo1();\r\package();"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, 0, "'strict';\r\n"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, 0, "'strict';\r\n"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); + }); - it("Strict mode 2", () => { - const source = "foo1();\r\nfoo1();\r\nfoo1();\r\package();"; + it("Strict mode 2", () => { + const source = "foo1();\r\nfoo1();\r\nfoo1();\r\package();"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, 0, "'use strict';\r\n"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, 0, "'use strict';\r\n"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); + }); - it("Strict mode 3", () => { - const source = "'strict';\r\nfoo1();\r\nfoo1();\r\nfoo1();\r\npackage();"; + it("Strict mode 3", () => { + const source = "'strict';\r\nfoo1();\r\nfoo1();\r\nfoo1();\r\npackage();"; - const index = source.indexOf("f"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, 0, index); + const index = source.indexOf("f"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, 0, index); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); + }); - it("Strict mode 4", () => { - const source = "'use strict';\r\nfoo1();\r\nfoo1();\r\nfoo1();\r\npackage();"; + it("Strict mode 4", () => { + const source = "'use strict';\r\nfoo1();\r\nfoo1();\r\nfoo1();\r\npackage();"; - const index = source.indexOf("f"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, 0, index); + const index = source.indexOf("f"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, 0, index); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); + }); - it("Strict mode 5", () => { - const source = "'use blahhh';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n"; + it("Strict mode 5", () => { + const source = "'use blahhh';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n"; - const index = source.indexOf("b"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, 6, "strict"); + const index = source.indexOf("b"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, index, 6, "strict"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 27); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 27); + }); - it("Strict mode 6", () => { - const source = "'use strict';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n"; + it("Strict mode 6", () => { + const source = "'use strict';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n"; - const index = source.indexOf("s"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, 6, "blahhh"); + const index = source.indexOf("s"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, index, 6, "blahhh"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 27); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 27); + }); - it("Strict mode 7", () => { - const source = "'use blahhh';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n"; + it("Strict mode 7", () => { + const source = "'use blahhh';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n"; - const index = source.indexOf("f"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, 0, index); + const index = source.indexOf("f"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, 0, index); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 24); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 24); + }); - it("Parenthesized expression to arrow function 1", () => { - const source = "var v = (a, b, c, d, e)"; + it("Parenthesized expression to arrow function 1", () => { + const source = "var v = (a, b, c, d, e)"; - const index = source.indexOf("a"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + 1, ":"); + const index = source.indexOf("a"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + 1, ":"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Parenthesized expression to arrow function 2", () => { - const source = "var v = (a, b) = c"; + it("Parenthesized expression to arrow function 2", () => { + const source = "var v = (a, b) = c"; - const index = source.indexOf("= c") + 1; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index, ">"); + const index = source.indexOf("= c") + 1; + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index, ">"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Arrow function to parenthesized expression 1", () => { - const source = "var v = (a:, b, c, d, e)"; + it("Arrow function to parenthesized expression 1", () => { + const source = "var v = (a:, b, c, d, e)"; - const index = source.indexOf(":"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index, 1); + const index = source.indexOf(":"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index, 1); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Arrow function to parenthesized expression 2", () => { - const source = "var v = (a, b) => c"; + it("Arrow function to parenthesized expression 2", () => { + const source = "var v = (a, b) => c"; - const index = source.indexOf(">"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index, 1); + const index = source.indexOf(">"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index, 1); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Speculative generic lookahead 1", () => { - const source = "var v = Fe"; + it("Speculative generic lookahead 1", () => { + const source = "var v = Fe"; - const index = source.indexOf("b"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + 1, ",x"); + const index = source.indexOf("b"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + 1, ",x"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + }); - it("Speculative generic lookahead 2", () => { - const source = "var v = Fe"; + it("Speculative generic lookahead 2", () => { + const source = "var v = Fe"; - const index = source.indexOf("b"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + 1, ",x"); + const index = source.indexOf("b"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + 1, ",x"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + }); - it("Speculative generic lookahead 3", () => { - const source = "var v = Fe"; + it("Speculative generic lookahead 3", () => { + const source = "var v = Fe"; - const index = source.indexOf("b"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + 1, ",x"); + const index = source.indexOf("b"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + 1, ",x"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + }); - it("Speculative generic lookahead 4", () => { - const source = "var v = Fe"; + it("Speculative generic lookahead 4", () => { + const source = "var v = Fe"; - const index = source.indexOf("b"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + 1, ",x"); + const index = source.indexOf("b"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + 1, ",x"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + }); - it("Assertion to arrow function", () => { - const source = "var v = (a);"; + it("Assertion to arrow function", () => { + const source = "var v = (a);"; - const index = source.indexOf(";"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index, " => 1"); + const index = source.indexOf(";"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index, " => 1"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Arrow function to assertion", () => { - const source = "var v = (a) => 1;"; + it("Arrow function to assertion", () => { + const source = "var v = (a) => 1;"; - const index = source.indexOf(" =>"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index, " => 1".length); + const index = source.indexOf(" =>"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index, " => 1".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Contextual shift to shift-equals", () => { - const source = "var v = 1 >> = 2"; + it("Contextual shift to shift-equals", () => { + const source = "var v = 1 >> = 2"; - const index = source.indexOf(">> ="); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index + 2, 1); + const index = source.indexOf(">> ="); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index + 2, 1); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Contextual shift-equals to shift", () => { - const source = "var v = 1 >>= 2"; + it("Contextual shift-equals to shift", () => { + const source = "var v = 1 >>= 2"; - const index = source.indexOf(">>="); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + 2, " "); + const index = source.indexOf(">>="); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + 2, " "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Contextual shift to generic invocation", () => { - const source = "var v = T>>(2)"; + it("Contextual shift to generic invocation", () => { + const source = "var v = T>>(2)"; - const index = source.indexOf("T"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index, "Foo { - const source = "var v = Foo>(2)"; + it("Test generic invocation to contextual shift", () => { + const source = "var v = Foo>(2)"; - const index = source.indexOf("Foo { - const source = "var v = T>>=2;"; + it("Contextual shift to generic type and initializer", () => { + const source = "var v = T>>=2;"; - const index = source.indexOf("="); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, "= ".length, ": Foo { - const source = "var v : Foo>=2;"; + it("Generic type and initializer to contextual shift", () => { + const source = "var v : Foo>=2;"; - const index = source.indexOf(":"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, ": Foo { - const source = "var v = new Dictionary0"; + it("Arithmetic operator to type argument list", () => { + const source = "var v = new Dictionary0"; - const index = source.indexOf("0"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, 1, "()"); + const index = source.indexOf("0"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, index, 1, "()"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Type argument list to arithmetic operator", () => { - const source = "var v = new Dictionary()"; + it("Type argument list to arithmetic operator", () => { + const source = "var v = new Dictionary()"; - const index = source.indexOf("()"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index, 2); + const index = source.indexOf("()"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index, 2); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Yield context 1", () => { - // We're changing from a non-generator to a genarator. We can't reuse statement nodes. - const source = "function foo() {\r\nyield(foo1);\r\n}"; + it("Yield context 1", () => { + // We're changing from a non-generator to a genarator. We can't reuse statement nodes. + const source = "function foo() {\r\nyield(foo1);\r\n}"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("foo"); - const newTextAndChange = withInsert(oldText, index, "*"); + const oldText = ScriptSnapshot.fromString(source); + const index = source.indexOf("foo"); + const newTextAndChange = withInsert(oldText, index, "*"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Yield context 2", () => { - // We're changing from a generator to a non-genarator. We can't reuse statement nodes. - const source = "function *foo() {\r\nyield(foo1);\r\n}"; + it("Yield context 2", () => { + // We're changing from a generator to a non-genarator. We can't reuse statement nodes. + const source = "function *foo() {\r\nyield(foo1);\r\n}"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("*"); - const newTextAndChange = withDelete(oldText, index, "*".length); + const oldText = ScriptSnapshot.fromString(source); + const index = source.indexOf("*"); + const newTextAndChange = withDelete(oldText, index, "*".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Delete semicolon", () => { - const source = "export class Foo {\r\n}\r\n\r\nexport var foo = new Foo();\r\n\r\n export function test(foo: Foo) {\r\n return true;\r\n }\r\n"; + it("Delete semicolon", () => { + const source = "export class Foo {\r\n}\r\n\r\nexport var foo = new Foo();\r\n\r\n export function test(foo: Foo) {\r\n return true;\r\n }\r\n"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.lastIndexOf(";"); - const newTextAndChange = withDelete(oldText, index, 1); + const oldText = ScriptSnapshot.fromString(source); + const index = source.lastIndexOf(";"); + const newTextAndChange = withDelete(oldText, index, 1); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); + }); - it("Edit after empty type parameter list", () => { - const source = "class Dictionary<> { }\r\nvar y;\r\n"; + it("Edit after empty type parameter list", () => { + const source = "class Dictionary<> { }\r\nvar y;\r\n"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.length; - const newTextAndChange = withInsert(oldText, index, "var x;"); + const oldText = ScriptSnapshot.fromString(source); + const index = source.length; + const newTextAndChange = withInsert(oldText, index, "var x;"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 2); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 2); + }); - it("Delete parameter after comment", () => { - const source = "function fn(/* comment! */ a: number, c) { }"; + it("Delete parameter after comment", () => { + const source = "function fn(/* comment! */ a: number, c) { }"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("a:"); - const newTextAndChange = withDelete(oldText, index, "a: number,".length); + const oldText = ScriptSnapshot.fromString(source); + const index = source.indexOf("a:"); + const newTextAndChange = withDelete(oldText, index, "a: number,".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Modifier added to accessor", () => { - const source = - "class C {\ + it("Modifier added to accessor", () => { + const source = + "class C {\ set Bar(bar:string) {}\ }\ var o2 = { set Foo(val:number) { } };"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("set"); - const newTextAndChange = withInsert(oldText, index, "public "); + const oldText = ScriptSnapshot.fromString(source); + const index = source.indexOf("set"); + const newTextAndChange = withInsert(oldText, index, "public "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); + }); - it("Insert parameter ahead of parameter", () => { - const source = - "alert(100);\ + it("Insert parameter ahead of parameter", () => { + const source = + "alert(100);\ \ class OverloadedMonster {\ constructor();\ constructor(name) { }\ }"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("100"); - const newTextAndChange = withInsert(oldText, index, "'1', "); + const oldText = ScriptSnapshot.fromString(source); + const index = source.indexOf("100"); + const newTextAndChange = withInsert(oldText, index, "'1', "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 7); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 7); + }); - it("Insert declare modifier before module", () => { - const source = - "module mAmbient {\ + it("Insert declare modifier before module", () => { + const source = + "module mAmbient {\ module m3 { }\ }"; - const oldText = ScriptSnapshot.fromString(source); - const index = 0; - const newTextAndChange = withInsert(oldText, index, "declare "); + const oldText = ScriptSnapshot.fromString(source); + const index = 0; + const newTextAndChange = withInsert(oldText, index, "declare "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Insert function above arrow function with comment", () => { - const source = - "\ + it("Insert function above arrow function with comment", () => { + const source = + "\ () =>\ // do something\ 0;"; - const oldText = ScriptSnapshot.fromString(source); - const index = 0; - const newTextAndChange = withInsert(oldText, index, "function Foo() { }"); + const oldText = ScriptSnapshot.fromString(source); + const index = 0; + const newTextAndChange = withInsert(oldText, index, "function Foo() { }"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Finish incomplete regular expression", () => { - const source = "while (true) /3; return;"; + it("Finish incomplete regular expression", () => { + const source = "while (true) /3; return;"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.length - 1; - const newTextAndChange = withInsert(oldText, index, "/"); + const oldText = ScriptSnapshot.fromString(source); + const index = source.length - 1; + const newTextAndChange = withInsert(oldText, index, "/"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Regular expression to divide operation", () => { - const source = "return;\r\nwhile (true) /3/g;"; + it("Regular expression to divide operation", () => { + const source = "return;\r\nwhile (true) /3/g;"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("while"); - const newTextAndChange = withDelete(oldText, index, "while ".length); + const oldText = ScriptSnapshot.fromString(source); + const index = source.indexOf("while"); + const newTextAndChange = withDelete(oldText, index, "while ".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Divide operation to regular expression", () => { - const source = "return;\r\n(true) /3/g;"; + it("Divide operation to regular expression", () => { + const source = "return;\r\n(true) /3/g;"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("("); - const newTextAndChange = withInsert(oldText, index, "while "); + const oldText = ScriptSnapshot.fromString(source); + const index = source.indexOf("("); + const newTextAndChange = withInsert(oldText, index, "while "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Unterminated comment after keyword converted to identifier", () => { - // 'public' as a keyword should be incrementally unusable (because it has an - // unterminated comment). When we convert it to an identifier, that shouldn't - // change anything, and we should still get the same errors. - const source = "return; a.public /*"; + it("Unterminated comment after keyword converted to identifier", () => { + // 'public' as a keyword should be incrementally unusable (because it has an + // unterminated comment). When we convert it to an identifier, that shouldn't + // change anything, and we should still get the same errors. + const source = "return; a.public /*"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, 0, ""); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, 0, ""); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 7); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 7); + }); - it("Class to interface", () => { - const source = "class A { public M1() { } public M2() { } public M3() { } p1 = 0; p2 = 0; p3 = 0 }"; + it("Class to interface", () => { + const source = "class A { public M1() { } public M2() { } public M3() { } p1 = 0; p2 = 0; p3 = 0 }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "class".length, "interface"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "class".length, "interface"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Interface to class", () => { - const source = "interface A { M1?(); M2?(); M3?(); p1?; p2?; p3? }"; + it("Interface to class", () => { + const source = "interface A { M1?(); M2?(); M3?(); p1?; p2?; p3? }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "interface".length, "class"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "interface".length, "class"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Surrounding function declarations with block", () => { - const source = "declare function F1() { } export function F2() { } declare export function F3() { }"; + it("Surrounding function declarations with block", () => { + const source = "declare function F1() { } export function F2() { } declare export function F3() { }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, 0, "{"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, 0, "{"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); + }); - it("Removing block around function declarations", () => { - const source = "{ declare function F1() { } export function F2() { } declare export function F3() { }"; + it("Removing block around function declarations", () => { + const source = "{ declare function F1() { } export function F2() { } declare export function F3() { }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, 0, "{".length); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, 0, "{".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); + }); - it("Moving methods from class to object literal", () => { - const source = "class C { public A() { } public B() { } public C() { } }"; + it("Moving methods from class to object literal", () => { + const source = "class C { public A() { } public B() { } public C() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Moving methods from object literal to class", () => { - const source = "var v = { public A() { } public B() { } public C() { } }"; + it("Moving methods from object literal to class", () => { + const source = "var v = { public A() { } public B() { } public C() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); + }); - it("Moving methods from object literal to class in strict mode", () => { - const source = "\"use strict\"; var v = { public A() { } public B() { } public C() { } }"; + it("Moving methods from object literal to class in strict mode", () => { + const source = "\"use strict\"; var v = { public A() { } public B() { } public C() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 14, "var v =".length, "class C"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 14, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); + }); - it("Do not move constructors from class to object-literal.", () => { - const source = "class C { public constructor() { } public constructor() { } public constructor() { } }"; + it("Do not move constructors from class to object-literal.", () => { + const source = "class C { public constructor() { } public constructor() { } public constructor() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Do not move methods called \"constructor\" from object literal to class", () => { - const source = "var v = { public constructor() { } public constructor() { } public constructor() { } }"; + it("Do not move methods called \"constructor\" from object literal to class", () => { + const source = "var v = { public constructor() { } public constructor() { } public constructor() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Moving index signatures from class to interface", () => { - const source = "class C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; + it("Moving index signatures from class to interface", () => { + const source = "class C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "class".length, "interface"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "class".length, "interface"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); + }); - it("Moving index signatures from class to interface in strict mode", () => { - const source = "\"use strict\"; class C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; + it("Moving index signatures from class to interface in strict mode", () => { + const source = "\"use strict\"; class C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 14, "class".length, "interface"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 14, "class".length, "interface"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); + }); - it("Moving index signatures from interface to class", () => { - const source = "interface C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; + it("Moving index signatures from interface to class", () => { + const source = "interface C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "interface".length, "class"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "interface".length, "class"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); + }); - it("Moving index signatures from interface to class in strict mode", () => { - const source = "\"use strict\"; interface C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; + it("Moving index signatures from interface to class in strict mode", () => { + const source = "\"use strict\"; interface C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 14, "interface".length, "class"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 14, "interface".length, "class"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); + }); - it("Moving accessors from class to object literal", () => { - const source = "class C { public get A() { } public get B() { } public get C() { } }"; + it("Moving accessors from class to object literal", () => { + const source = "class C { public get A() { } public get B() { } public get C() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Moving accessors from object literal to class", () => { - const source = "var v = { public get A() { } public get B() { } public get C() { } }"; + it("Moving accessors from object literal to class", () => { + const source = "var v = { public get A() { } public get B() { } public get C() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); + }); - it("Moving accessors from object literal to class in strict mode", () => { - const source = "\"use strict\"; var v = { public get A() { } public get B() { } public get C() { } }"; + it("Moving accessors from object literal to class in strict mode", () => { + const source = "\"use strict\"; var v = { public get A() { } public get B() { } public get C() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 14, "var v =".length, "class C"); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 14, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); + }); - it("Reuse transformFlags of subtree during bind", () => { - const source = `class Greeter { constructor(element: HTMLElement) { } }`; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 15, 0, "\n"); - const { oldTree, incrementalNewTree } = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); - bindSourceFile(oldTree, {}); - bindSourceFile(incrementalNewTree, {}); - assert.equal(oldTree.transformFlags, incrementalNewTree.transformFlags); - }); + it("Reuse transformFlags of subtree during bind", () => { + const source = `class Greeter { constructor(element: HTMLElement) { } }`; + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 15, 0, "\n"); + const { oldTree, incrementalNewTree } = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + bindSourceFile(oldTree, {}); + bindSourceFile(incrementalNewTree, {}); + assert.equal(oldTree.transformFlags, incrementalNewTree.transformFlags); + }); - // Simulated typing tests. + // Simulated typing tests. - it("Type extends clause 1", () => { - const source = "interface IFoo { }\r\ninterface Array extends IFoo { }"; + it("Type extends clause 1", () => { + const source = "interface IFoo { }\r\ninterface Array extends IFoo { }"; - const index = source.indexOf("extends"); - deleteCode(source, index, "extends IFoo"); - }); + const index = source.indexOf("extends"); + deleteCode(source, index, "extends IFoo"); + }); - it("Type after incomplete enum 1", () => { - const source = "function foo() {\r\n" + - " function getOccurrencesAtPosition() {\r\n" + - " switch (node) {\r\n" + - " enum \r\n" + - " }\r\n" + - " \r\n" + - " return undefined;\r\n" + - " \r\n" + - " function keywordToReferenceEntry() {\r\n" + - " }\r\n" + - " }\r\n" + - " \r\n" + - " return {\r\n" + - " getEmitOutput: (fileName): Bar => null,\r\n" + - " };\r\n" + - " }"; - - const index = source.indexOf("enum ") + "enum ".length; - insertCode(source, index, "Fo"); - }); + it("Type after incomplete enum 1", () => { + const source = "function foo() {\r\n" + + " function getOccurrencesAtPosition() {\r\n" + + " switch (node) {\r\n" + + " enum \r\n" + + " }\r\n" + + " \r\n" + + " return undefined;\r\n" + + " \r\n" + + " function keywordToReferenceEntry() {\r\n" + + " }\r\n" + + " }\r\n" + + " \r\n" + + " return {\r\n" + + " getEmitOutput: (fileName): Bar => null,\r\n" + + " };\r\n" + + " }"; + + const index = source.indexOf("enum ") + "enum ".length; + insertCode(source, index, "Fo"); + }); - for (const tsIgnoreComment of [ - "// @ts-ignore", - "/* @ts-ignore */", - "/*\n @ts-ignore */" - ]) { - describe(`${tsIgnoreComment} comment directives`, () => { - const textWithIgnoreComment = `const x = 10; + for (const tsIgnoreComment of [ + "// @ts-ignore", + "/* @ts-ignore */", + "/*\n @ts-ignore */" + ]) { + describe(`${tsIgnoreComment} comment directives`, () => { + const textWithIgnoreComment = `const x = 10; function foo() { ${tsIgnoreComment} let y: string = x; @@ -857,103 +857,103 @@ module m3 { }\ foo(); bar(); bar3();`; - verifyScenario("when deleting ts-ignore comment", verifyDelete); - verifyScenario("when inserting ts-ignore comment", verifyInsert); - verifyScenario("when changing ts-ignore comment to blah", verifyChangeToBlah); - verifyScenario("when changing blah comment to ts-ignore", verifyChangeBackToDirective); - verifyScenario("when deleting blah comment", verifyDeletingBlah); - verifyScenario("when changing text that adds another comment", verifyChangeDirectiveType); - verifyScenario("when changing text that keeps the comment but adds more nodes", verifyReuseChange); - - function verifyCommentDirectives(oldText: IScriptSnapshot, newTextAndChange: { text: IScriptSnapshot; textChangeRange: TextChangeRange; }) { - const { incrementalNewTree, newTree } = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); - assert.deepEqual(incrementalNewTree.commentDirectives, newTree.commentDirectives); - } - - function verifyScenario(scenario: string, verifyChange: (atIndex: number, singleIgnore?: true) => void) { - it(`${scenario} - 0`, () => { - verifyChange(0); - }); - it(`${scenario} - 1`, () => { - verifyChange(1); - }); - it(`${scenario} - 2`, () => { - verifyChange(2); - }); - it(`${scenario} - with single ts-ignore`, () => { - verifyChange(0, /*singleIgnore*/ true); - }); - } - - function getIndexOfTsIgnoreComment(atIndex: number) { - let index = 0; - for (let i = 0; i <= atIndex; i++) { - index = textWithIgnoreComment.indexOf(tsIgnoreComment, index); - } - return index; - } - - function textWithIgnoreCommentFrom(text: string, singleIgnore: true | undefined) { - if (!singleIgnore) return text; - const splits = text.split(tsIgnoreComment); - if (splits.length > 2) { - const tail = splits[splits.length - 2] + splits[splits.length - 1]; - splits.length = splits.length - 2; - return splits.join(tsIgnoreComment) + tail; - } - else { - return splits.join(tsIgnoreComment); - } - } - - function verifyDelete(atIndex: number, singleIgnore?: true) { - const index = getIndexOfTsIgnoreComment(atIndex); - const oldText = ScriptSnapshot.fromString(textWithIgnoreCommentFrom(textWithIgnoreComment, singleIgnore)); - const newTextAndChange = withDelete(oldText, index, tsIgnoreComment.length); - verifyCommentDirectives(oldText, newTextAndChange); - } - - function verifyInsert(atIndex: number, singleIgnore?: true) { - const index = getIndexOfTsIgnoreComment(atIndex); - const source = textWithIgnoreCommentFrom(textWithIgnoreComment.slice(0, index) + textWithIgnoreComment.slice(index + tsIgnoreComment.length), singleIgnore); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index, tsIgnoreComment); - verifyCommentDirectives(oldText, newTextAndChange); + verifyScenario("when deleting ts-ignore comment", verifyDelete); + verifyScenario("when inserting ts-ignore comment", verifyInsert); + verifyScenario("when changing ts-ignore comment to blah", verifyChangeToBlah); + verifyScenario("when changing blah comment to ts-ignore", verifyChangeBackToDirective); + verifyScenario("when deleting blah comment", verifyDeletingBlah); + verifyScenario("when changing text that adds another comment", verifyChangeDirectiveType); + verifyScenario("when changing text that keeps the comment but adds more nodes", verifyReuseChange); + + function verifyCommentDirectives(oldText: IScriptSnapshot, newTextAndChange: { text: IScriptSnapshot; textChangeRange: TextChangeRange; }) { + const { incrementalNewTree, newTree } = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + assert.deepEqual(incrementalNewTree.commentDirectives, newTree.commentDirectives); + } + + function verifyScenario(scenario: string, verifyChange: (atIndex: number, singleIgnore?: true) => void) { + it(`${scenario} - 0`, () => { + verifyChange(0); + }); + it(`${scenario} - 1`, () => { + verifyChange(1); + }); + it(`${scenario} - 2`, () => { + verifyChange(2); + }); + it(`${scenario} - with single ts-ignore`, () => { + verifyChange(0, /*singleIgnore*/ true); + }); + } + + function getIndexOfTsIgnoreComment(atIndex: number) { + let index = 0; + for (let i = 0; i <= atIndex; i++) { + index = textWithIgnoreComment.indexOf(tsIgnoreComment, index); } - - function verifyChangeToBlah(atIndex: number, singleIgnore?: true) { - const index = getIndexOfTsIgnoreComment(atIndex) + tsIgnoreComment.indexOf("@"); - const oldText = ScriptSnapshot.fromString(textWithIgnoreCommentFrom(textWithIgnoreComment, singleIgnore)); - const newTextAndChange = withChange(oldText, index, 1, "blah "); - verifyCommentDirectives(oldText, newTextAndChange); - } - - function verifyChangeBackToDirective(atIndex: number, singleIgnore?: true) { - const index = getIndexOfTsIgnoreComment(atIndex) + tsIgnoreComment.indexOf("@"); - const source = textWithIgnoreCommentFrom(textWithIgnoreComment.slice(0, index) + "blah " + textWithIgnoreComment.slice(index + 1), singleIgnore); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, "blah ".length, "@"); - verifyCommentDirectives(oldText, newTextAndChange); + return index; + } + + function textWithIgnoreCommentFrom(text: string, singleIgnore: true | undefined) { + if (!singleIgnore) return text; + const splits = text.split(tsIgnoreComment); + if (splits.length > 2) { + const tail = splits[splits.length - 2] + splits[splits.length - 1]; + splits.length = splits.length - 2; + return splits.join(tsIgnoreComment) + tail; } - - function verifyDeletingBlah(atIndex: number, singleIgnore?: true) { - const tsIgnoreIndex = getIndexOfTsIgnoreComment(atIndex); - const index = tsIgnoreIndex + tsIgnoreComment.indexOf("@"); - const source = textWithIgnoreCommentFrom(textWithIgnoreComment.slice(0, index) + "blah " + textWithIgnoreComment.slice(index + 1), singleIgnore); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, tsIgnoreIndex, tsIgnoreComment.length + "blah".length); - verifyCommentDirectives(oldText, newTextAndChange); - } - - function verifyChangeDirectiveType(atIndex: number, singleIgnore?: true) { - const index = getIndexOfTsIgnoreComment(atIndex) + tsIgnoreComment.indexOf("ignore"); - const oldText = ScriptSnapshot.fromString(textWithIgnoreCommentFrom(textWithIgnoreComment, singleIgnore)); - const newTextAndChange = withChange(oldText, index, "ignore".length, "expect-error"); - verifyCommentDirectives(oldText, newTextAndChange); + else { + return splits.join(tsIgnoreComment); } - - function verifyReuseChange(atIndex: number, singleIgnore?: true) { - const source = `const x = 10; + } + + function verifyDelete(atIndex: number, singleIgnore?: true) { + const index = getIndexOfTsIgnoreComment(atIndex); + const oldText = ScriptSnapshot.fromString(textWithIgnoreCommentFrom(textWithIgnoreComment, singleIgnore)); + const newTextAndChange = withDelete(oldText, index, tsIgnoreComment.length); + verifyCommentDirectives(oldText, newTextAndChange); + } + + function verifyInsert(atIndex: number, singleIgnore?: true) { + const index = getIndexOfTsIgnoreComment(atIndex); + const source = textWithIgnoreCommentFrom(textWithIgnoreComment.slice(0, index) + textWithIgnoreComment.slice(index + tsIgnoreComment.length), singleIgnore); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index, tsIgnoreComment); + verifyCommentDirectives(oldText, newTextAndChange); + } + + function verifyChangeToBlah(atIndex: number, singleIgnore?: true) { + const index = getIndexOfTsIgnoreComment(atIndex) + tsIgnoreComment.indexOf("@"); + const oldText = ScriptSnapshot.fromString(textWithIgnoreCommentFrom(textWithIgnoreComment, singleIgnore)); + const newTextAndChange = withChange(oldText, index, 1, "blah "); + verifyCommentDirectives(oldText, newTextAndChange); + } + + function verifyChangeBackToDirective(atIndex: number, singleIgnore?: true) { + const index = getIndexOfTsIgnoreComment(atIndex) + tsIgnoreComment.indexOf("@"); + const source = textWithIgnoreCommentFrom(textWithIgnoreComment.slice(0, index) + "blah " + textWithIgnoreComment.slice(index + 1), singleIgnore); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, index, "blah ".length, "@"); + verifyCommentDirectives(oldText, newTextAndChange); + } + + function verifyDeletingBlah(atIndex: number, singleIgnore?: true) { + const tsIgnoreIndex = getIndexOfTsIgnoreComment(atIndex); + const index = tsIgnoreIndex + tsIgnoreComment.indexOf("@"); + const source = textWithIgnoreCommentFrom(textWithIgnoreComment.slice(0, index) + "blah " + textWithIgnoreComment.slice(index + 1), singleIgnore); + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, tsIgnoreIndex, tsIgnoreComment.length + "blah".length); + verifyCommentDirectives(oldText, newTextAndChange); + } + + function verifyChangeDirectiveType(atIndex: number, singleIgnore?: true) { + const index = getIndexOfTsIgnoreComment(atIndex) + tsIgnoreComment.indexOf("ignore"); + const oldText = ScriptSnapshot.fromString(textWithIgnoreCommentFrom(textWithIgnoreComment, singleIgnore)); + const newTextAndChange = withChange(oldText, index, "ignore".length, "expect-error"); + verifyCommentDirectives(oldText, newTextAndChange); + } + + function verifyReuseChange(atIndex: number, singleIgnore?: true) { + const source = `const x = 10; function foo1() { const x1 = 10; ${tsIgnoreComment} @@ -978,16 +978,16 @@ module m3 { }\ foo1(); foo2(); foo3();`; - const oldText = ScriptSnapshot.fromString(textWithIgnoreCommentFrom(source, singleIgnore)); - const start = source.indexOf(`const x${atIndex + 1}`); - const letStr = `let y${atIndex + 1}: string = x;`; - const end = source.indexOf(letStr) + letStr.length; - const oldSubStr = source.slice(start, end); - const newText = oldSubStr.replace(letStr, `let yn : string = x;`); - const newTextAndChange = withChange(oldText, start, end - start, newText); - verifyCommentDirectives(oldText, newTextAndChange); - } - }); - } - }); + const oldText = ScriptSnapshot.fromString(textWithIgnoreCommentFrom(source, singleIgnore)); + const start = source.indexOf(`const x${atIndex + 1}`); + const letStr = `let y${atIndex + 1}: string = x;`; + const end = source.indexOf(letStr) + letStr.length; + const oldSubStr = source.slice(start, end); + const newText = oldSubStr.replace(letStr, `let yn : string = x;`); + const newTextAndChange = withChange(oldText, start, end - start, newText); + verifyCommentDirectives(oldText, newTextAndChange); + } + }); + } +}); } diff --git a/src/testRunner/unittests/jsDocParsing.ts b/src/testRunner/unittests/jsDocParsing.ts index e0aa87f2a4cd2..30901716b66c2 100644 --- a/src/testRunner/unittests/jsDocParsing.ts +++ b/src/testRunner/unittests/jsDocParsing.ts @@ -1,317 +1,317 @@ namespace ts { - describe("unittests:: JSDocParsing", () => { - describe("TypeExpressions", () => { - function parsesCorrectly(name: string, content: string) { - it(name, () => { - const typeAndDiagnostics = parseJSDocTypeExpressionForTests(content); - assert.isTrue(typeAndDiagnostics && typeAndDiagnostics.diagnostics.length === 0, "no errors issued"); - - Harness.Baseline.runBaseline("JSDocParsing/TypeExpressions.parsesCorrectly." + name + ".json", - Utils.sourceFileToJSON(typeAndDiagnostics!.jsDocTypeExpression.type)); - }); - } - - function parsesIncorrectly(name: string, content: string) { - it(name, () => { - const type = parseJSDocTypeExpressionForTests(content); - assert.isTrue(!type || type.diagnostics.length > 0); - }); - } - - describe("parseCorrectly", () => { - parsesCorrectly("unknownType", "{?}"); - parsesCorrectly("allType", "{*}"); - parsesCorrectly("nullableType", "{?number}"); - parsesCorrectly("nullableType2", "{number?}"); - parsesCorrectly("nonNullableType", "{!number}"); - parsesCorrectly("nonNullableType2", "{number!}"); - parsesCorrectly("recordType1", "{{}}"); - parsesCorrectly("recordType2", "{{foo}}"); - parsesCorrectly("recordType3", "{{foo: number}}"); - parsesCorrectly("recordType4", "{{foo, bar}}"); - parsesCorrectly("recordType5", "{{foo: number, bar}}"); - parsesCorrectly("recordType6", "{{foo, bar: number}}"); - parsesCorrectly("recordType7", "{{foo: number, bar: number}}"); - parsesCorrectly("recordType8", "{{function}}"); - parsesCorrectly("trailingCommaInRecordType", "{{a,}}"); - parsesCorrectly("callSignatureInRecordType", "{{(): number}}"); - parsesCorrectly("methodInRecordType", "{{foo(): number}}"); - parsesCorrectly("unionType", "{(number|string)}"); - parsesCorrectly("unionTypeWithLeadingOperator", "{( | number | string )}"); - parsesCorrectly("unionTypeWithOneElementAndLeadingOperator", "{( | number )}"); - parsesCorrectly("topLevelNoParenUnionType", "{number|string}"); - parsesCorrectly("functionType1", "{function()}"); - parsesCorrectly("functionType2", "{function(string, boolean)}"); - parsesCorrectly("functionReturnType1", "{function(string, boolean)}"); - parsesCorrectly("thisType1", "{function(this:a.b)}"); - parsesCorrectly("newType1", "{function(new:a.b)}"); - parsesCorrectly("variadicType", "{...number}"); - parsesCorrectly("optionalType", "{number=}"); - parsesCorrectly("optionalNullable", "{?=}"); - parsesCorrectly("typeReference1", "{a.}"); - parsesCorrectly("typeReference2", "{a.}"); - parsesCorrectly("typeReference3", "{a.function}"); - parsesCorrectly("arrayType1", "{a[]}"); - parsesCorrectly("arrayType2", "{a[][]}"); - parsesCorrectly("arrayType3", "{(a[][])=}"); - parsesCorrectly("keyword1", "{var}"); - parsesCorrectly("keyword2", "{null}"); - parsesCorrectly("keyword3", "{undefined}"); - parsesCorrectly("tupleType0", "{[]}"); - parsesCorrectly("tupleType1", "{[number]}"); - parsesCorrectly("tupleType2", "{[number,string]}"); - parsesCorrectly("tupleType3", "{[number,string,boolean]}"); - parsesCorrectly("tupleTypeWithTrailingComma", "{[number,]}"); - parsesCorrectly("typeOfType", "{typeof M}"); - parsesCorrectly("tsConstructorType", "{new () => string}"); - parsesCorrectly("tsFunctionType", "{() => string}"); - parsesCorrectly("typeArgumentsNotFollowingDot", "{a<>}"); - parsesCorrectly("functionTypeWithTrailingComma", "{function(a,)}"); +describe("unittests:: JSDocParsing", () => { + describe("TypeExpressions", () => { + function parsesCorrectly(name: string, content: string) { + it(name, () => { + const typeAndDiagnostics = parseJSDocTypeExpressionForTests(content); + assert.isTrue(typeAndDiagnostics && typeAndDiagnostics.diagnostics.length === 0, "no errors issued"); + + Harness.Baseline.runBaseline("JSDocParsing/TypeExpressions.parsesCorrectly." + name + ".json", + Utils.sourceFileToJSON(typeAndDiagnostics!.jsDocTypeExpression.type)); }); + } - describe("parsesIncorrectly", () => { - parsesIncorrectly("emptyType", "{}"); - parsesIncorrectly("unionTypeWithTrailingBar", "{(a|)}"); - parsesIncorrectly("unionTypeWithoutTypes", "{()}"); - parsesIncorrectly("nullableTypeWithoutType", "{!}"); - parsesIncorrectly("thisWithoutType", "{this:}"); - parsesIncorrectly("newWithoutType", "{new:}"); - parsesIncorrectly("variadicWithoutType", "{...}"); - parsesIncorrectly("optionalWithoutType", "{=}"); - parsesIncorrectly("allWithType", "{*foo}"); - parsesIncorrectly("namedParameter", "{function(a: number)}"); - parsesIncorrectly("tupleTypeWithComma", "{[,]}"); - parsesIncorrectly("tupleTypeWithLeadingComma", "{[,number]}"); + function parsesIncorrectly(name: string, content: string) { + it(name, () => { + const type = parseJSDocTypeExpressionForTests(content); + assert.isTrue(!type || type.diagnostics.length > 0); }); + } + + describe("parseCorrectly", () => { + parsesCorrectly("unknownType", "{?}"); + parsesCorrectly("allType", "{*}"); + parsesCorrectly("nullableType", "{?number}"); + parsesCorrectly("nullableType2", "{number?}"); + parsesCorrectly("nonNullableType", "{!number}"); + parsesCorrectly("nonNullableType2", "{number!}"); + parsesCorrectly("recordType1", "{{}}"); + parsesCorrectly("recordType2", "{{foo}}"); + parsesCorrectly("recordType3", "{{foo: number}}"); + parsesCorrectly("recordType4", "{{foo, bar}}"); + parsesCorrectly("recordType5", "{{foo: number, bar}}"); + parsesCorrectly("recordType6", "{{foo, bar: number}}"); + parsesCorrectly("recordType7", "{{foo: number, bar: number}}"); + parsesCorrectly("recordType8", "{{function}}"); + parsesCorrectly("trailingCommaInRecordType", "{{a,}}"); + parsesCorrectly("callSignatureInRecordType", "{{(): number}}"); + parsesCorrectly("methodInRecordType", "{{foo(): number}}"); + parsesCorrectly("unionType", "{(number|string)}"); + parsesCorrectly("unionTypeWithLeadingOperator", "{( | number | string )}"); + parsesCorrectly("unionTypeWithOneElementAndLeadingOperator", "{( | number )}"); + parsesCorrectly("topLevelNoParenUnionType", "{number|string}"); + parsesCorrectly("functionType1", "{function()}"); + parsesCorrectly("functionType2", "{function(string, boolean)}"); + parsesCorrectly("functionReturnType1", "{function(string, boolean)}"); + parsesCorrectly("thisType1", "{function(this:a.b)}"); + parsesCorrectly("newType1", "{function(new:a.b)}"); + parsesCorrectly("variadicType", "{...number}"); + parsesCorrectly("optionalType", "{number=}"); + parsesCorrectly("optionalNullable", "{?=}"); + parsesCorrectly("typeReference1", "{a.}"); + parsesCorrectly("typeReference2", "{a.}"); + parsesCorrectly("typeReference3", "{a.function}"); + parsesCorrectly("arrayType1", "{a[]}"); + parsesCorrectly("arrayType2", "{a[][]}"); + parsesCorrectly("arrayType3", "{(a[][])=}"); + parsesCorrectly("keyword1", "{var}"); + parsesCorrectly("keyword2", "{null}"); + parsesCorrectly("keyword3", "{undefined}"); + parsesCorrectly("tupleType0", "{[]}"); + parsesCorrectly("tupleType1", "{[number]}"); + parsesCorrectly("tupleType2", "{[number,string]}"); + parsesCorrectly("tupleType3", "{[number,string,boolean]}"); + parsesCorrectly("tupleTypeWithTrailingComma", "{[number,]}"); + parsesCorrectly("typeOfType", "{typeof M}"); + parsesCorrectly("tsConstructorType", "{new () => string}"); + parsesCorrectly("tsFunctionType", "{() => string}"); + parsesCorrectly("typeArgumentsNotFollowingDot", "{a<>}"); + parsesCorrectly("functionTypeWithTrailingComma", "{function(a,)}"); }); - describe("DocComments", () => { - function parsesCorrectly(name: string, content: string) { - it(name, () => { - const comment = parseIsolatedJSDocComment(content)!; - if (!comment) { - Debug.fail("Comment failed to parse entirely"); - } - if (comment.diagnostics.length > 0) { - Debug.fail("Comment has at least one diagnostic: " + comment.diagnostics[0].messageText); - } - - Harness.Baseline.runBaseline("JSDocParsing/DocComments.parsesCorrectly." + name + ".json", - JSON.stringify(comment.jsDoc, - (_, v) => v && v.pos !== undefined ? JSON.parse(Utils.sourceFileToJSON(v)) : v, 4)); - }); - } - - function parsesIncorrectly(name: string, content: string) { - it(name, () => { - const type = parseIsolatedJSDocComment(content); - assert.isTrue(!type || type.diagnostics.length > 0); - }); - } - - describe("parsesIncorrectly", () => { - parsesIncorrectly("multipleTypes", - `/** + describe("parsesIncorrectly", () => { + parsesIncorrectly("emptyType", "{}"); + parsesIncorrectly("unionTypeWithTrailingBar", "{(a|)}"); + parsesIncorrectly("unionTypeWithoutTypes", "{()}"); + parsesIncorrectly("nullableTypeWithoutType", "{!}"); + parsesIncorrectly("thisWithoutType", "{this:}"); + parsesIncorrectly("newWithoutType", "{new:}"); + parsesIncorrectly("variadicWithoutType", "{...}"); + parsesIncorrectly("optionalWithoutType", "{=}"); + parsesIncorrectly("allWithType", "{*foo}"); + parsesIncorrectly("namedParameter", "{function(a: number)}"); + parsesIncorrectly("tupleTypeWithComma", "{[,]}"); + parsesIncorrectly("tupleTypeWithLeadingComma", "{[,number]}"); + }); + }); + + describe("DocComments", () => { + function parsesCorrectly(name: string, content: string) { + it(name, () => { + const comment = parseIsolatedJSDocComment(content)!; + if (!comment) { + Debug.fail("Comment failed to parse entirely"); + } + if (comment.diagnostics.length > 0) { + Debug.fail("Comment has at least one diagnostic: " + comment.diagnostics[0].messageText); + } + + Harness.Baseline.runBaseline("JSDocParsing/DocComments.parsesCorrectly." + name + ".json", + JSON.stringify(comment.jsDoc, + (_, v) => v && v.pos !== undefined ? JSON.parse(Utils.sourceFileToJSON(v)) : v, 4)); + }); + } + + function parsesIncorrectly(name: string, content: string) { + it(name, () => { + const type = parseIsolatedJSDocComment(content); + assert.isTrue(!type || type.diagnostics.length > 0); + }); + } + + describe("parsesIncorrectly", () => { + parsesIncorrectly("multipleTypes", + `/** * @type {number} * @type {string} */`); - parsesIncorrectly("multipleReturnTypes", - `/** + parsesIncorrectly("multipleReturnTypes", + `/** * @return {number} * @return {string} */`); - parsesIncorrectly("noTypeParameters", - `/** + parsesIncorrectly("noTypeParameters", + `/** * @template */`); - parsesIncorrectly("trailingTypeParameterComma", - `/** + parsesIncorrectly("trailingTypeParameterComma", + `/** * @template T, */`); - parsesIncorrectly("paramWithoutName", - `/** + parsesIncorrectly("paramWithoutName", + `/** * @param {number} */`); - parsesIncorrectly("paramWithoutTypeOrName", - `/** + parsesIncorrectly("paramWithoutTypeOrName", + `/** * @param */`); - parsesIncorrectly("noType", - `/** + parsesIncorrectly("noType", + `/** * @type */`); - parsesIncorrectly("@augments with no type", - `/** + parsesIncorrectly("@augments with no type", + `/** * @augments */`); - }); + }); - describe("parsesCorrectly", () => { - parsesCorrectly("threeAsterisks", "/*** */"); - parsesCorrectly("emptyComment", "/***/"); - parsesCorrectly("noLeadingAsterisk", - `/** + describe("parsesCorrectly", () => { + parsesCorrectly("threeAsterisks", "/*** */"); + parsesCorrectly("emptyComment", "/***/"); + parsesCorrectly("noLeadingAsterisk", + `/** @type {number} */`); - parsesCorrectly("noReturnType", - `/** + parsesCorrectly("noReturnType", + `/** * @return */`); - parsesCorrectly("leadingAsterisk", - `/** + parsesCorrectly("leadingAsterisk", + `/** * @type {number} */`); - parsesCorrectly("asteriskAfterPreamble", "/** * @type {number} */"); + parsesCorrectly("asteriskAfterPreamble", "/** * @type {number} */"); - parsesCorrectly("typeTag", - `/** + parsesCorrectly("typeTag", + `/** * @type {number} */`); - parsesCorrectly("returnTag1", - `/** + parsesCorrectly("returnTag1", + `/** * @return {number} */`); - parsesCorrectly("returnTag2", - `/** + parsesCorrectly("returnTag2", + `/** * @return {number} Description text follows */`); - parsesCorrectly("returnsTag1", - `/** + parsesCorrectly("returnsTag1", + `/** * @returns {number} */`); - parsesCorrectly("oneParamTag", - `/** + parsesCorrectly("oneParamTag", + `/** * @param {number} name1 */`); - parsesCorrectly("twoParamTag2", - `/** + parsesCorrectly("twoParamTag2", + `/** * @param {number} name1 * @param {number} name2 */`); - parsesCorrectly("paramTag1", - `/** + parsesCorrectly("paramTag1", + `/** * @param {number} name1 Description text follows */`); - parsesCorrectly("paramTagBracketedName1", - `/** + parsesCorrectly("paramTagBracketedName1", + `/** * @param {number} [name1] Description text follows */`); - parsesCorrectly("paramTagBracketedName2", - `/** + parsesCorrectly("paramTagBracketedName2", + `/** * @param {number} [ name1 = 1] Description text follows */`); - parsesCorrectly("twoParamTagOnSameLine", - `/** + parsesCorrectly("twoParamTagOnSameLine", + `/** * @param {number} name1 @param {number} name2 */`); - parsesCorrectly("paramTagNameThenType1", - `/** + parsesCorrectly("paramTagNameThenType1", + `/** * @param name1 {number} */`); - parsesCorrectly("paramTagNameThenType2", - `/** + parsesCorrectly("paramTagNameThenType2", + `/** * @param name1 {number} Description */`); - parsesCorrectly("argSynonymForParamTag", - `/** + parsesCorrectly("argSynonymForParamTag", + `/** * @arg {number} name1 Description */`); - parsesCorrectly("argumentSynonymForParamTag", - `/** + parsesCorrectly("argumentSynonymForParamTag", + `/** * @argument {number} name1 Description */`); - parsesCorrectly("templateTag", - `/** + parsesCorrectly("templateTag", + `/** * @template T */`); - parsesCorrectly("templateTag2", - `/** + parsesCorrectly("templateTag2", + `/** * @template K,V */`); - parsesCorrectly("templateTag3", - `/** + parsesCorrectly("templateTag3", + `/** * @template K ,V */`); - parsesCorrectly("templateTag4", - `/** + parsesCorrectly("templateTag4", + `/** * @template K, V */`); - parsesCorrectly("templateTag5", - `/** + parsesCorrectly("templateTag5", + `/** * @template K , V */`); - parsesCorrectly("templateTag6", - `/** + parsesCorrectly("templateTag6", + `/** * @template K , V Description of type parameters. */`); - parsesCorrectly("paramWithoutType", - `/** + parsesCorrectly("paramWithoutType", + `/** * @param foo */`); - parsesCorrectly("typedefTagWithChildrenTags", - `/** + parsesCorrectly("typedefTagWithChildrenTags", + `/** * @typedef People * @type {Object} * @property {number} age * @property {string} name */`); - parsesCorrectly("less-than and greater-than characters", - `/** + parsesCorrectly("less-than and greater-than characters", + `/** * @param x hi < > still part of the previous comment */`); - parsesCorrectly("Nested @param tags", - `/** + parsesCorrectly("Nested @param tags", + `/** * @param {object} o Doc doc * @param {string} o.f Doc for f */`); - parsesCorrectly("@link tags", - `/** + parsesCorrectly("@link tags", + `/** * {@link first } * Inside {@link link text} thing * @param foo See also {@link A.Reference} @@ -330,8 +330,8 @@ oh.no * }, because of the intermediate asterisks. * @author Alfa Romero See my home page: {@link https://example.com} */`); - parsesCorrectly("authorTag", - `/** + parsesCorrectly("authorTag", + `/** * @author John Doe * @author John Doe unexpected comment * @author 108 <108@actionbutton.net> Video Games Forever @@ -351,55 +351,55 @@ oh.no * want to keep commenting down here, I dunno. */`); - parsesCorrectly("consecutive newline tokens", - `/** + parsesCorrectly("consecutive newline tokens", + `/** * @example * Some\n\n * text\r\n * with newlines. */`); - parsesCorrectly("Chained tags, no leading whitespace", `/**@a @b @c@d*/`); - parsesCorrectly("Initial star is not a tag", `/***@a*/`); - parsesCorrectly("Initial star space is not a tag", `/*** @a*/`); - parsesCorrectly("Initial email address is not a tag", `/**bill@example.com*/`); - parsesCorrectly("no space before @ is not a new tag", - `/** + parsesCorrectly("Chained tags, no leading whitespace", `/**@a @b @c@d*/`); + parsesCorrectly("Initial star is not a tag", `/***@a*/`); + parsesCorrectly("Initial star space is not a tag", `/*** @a*/`); + parsesCorrectly("Initial email address is not a tag", `/**bill@example.com*/`); + parsesCorrectly("no space before @ is not a new tag", + `/** * @param this (@is@) * @param fine its@fine @zerowidth *@singlestar **@doublestar */`); - parsesCorrectly("@@ does not start a new tag", - `/** + parsesCorrectly("@@ does not start a new tag", + `/** * @param this is (@@fine@@and) is one comment */`); - }); - }); - describe("getFirstToken", () => { - it("gets jsdoc", () => { - const root = createSourceFile("foo.ts", "/** comment */var a = true;", ScriptTarget.ES5, /*setParentNodes*/ true); - assert.isDefined(root); - assert.equal(root.kind, SyntaxKind.SourceFile); - const first = root.getFirstToken(); - assert.isDefined(first); - assert.equal(first!.kind, SyntaxKind.VarKeyword); - }); - }); - describe("getLastToken", () => { - it("gets jsdoc", () => { - const root = createSourceFile("foo.ts", "var a = true;/** comment */", ScriptTarget.ES5, /*setParentNodes*/ true); - assert.isDefined(root); - const last = root.getLastToken(); - assert.isDefined(last); - assert.equal(last!.kind, SyntaxKind.EndOfFileToken); - }); }); - describe("getStart of node with JSDoc but no parent pointers", () => { - const root = createSourceFile("foo.ts", "/** */var a = true;", ScriptTarget.ES5, /*setParentNodes*/ false); - root.statements[0].getStart(root, /*includeJsdocComment*/ true); + }); + describe("getFirstToken", () => { + it("gets jsdoc", () => { + const root = createSourceFile("foo.ts", "/** comment */var a = true;", ScriptTarget.ES5, /*setParentNodes*/ true); + assert.isDefined(root); + assert.equal(root.kind, SyntaxKind.SourceFile); + const first = root.getFirstToken(); + assert.isDefined(first); + assert.equal(first!.kind, SyntaxKind.VarKeyword); }); - describe("missing type parameter in jsDoc doesn't create a 1-element array", () => { - const doc = parseIsolatedJSDocComment("/**\n @template\n*/"); - assert.equal((doc?.jsDoc.tags?.[0] as JSDocTemplateTag).typeParameters.length, 0); + }); + describe("getLastToken", () => { + it("gets jsdoc", () => { + const root = createSourceFile("foo.ts", "var a = true;/** comment */", ScriptTarget.ES5, /*setParentNodes*/ true); + assert.isDefined(root); + const last = root.getLastToken(); + assert.isDefined(last); + assert.equal(last!.kind, SyntaxKind.EndOfFileToken); }); }); + describe("getStart of node with JSDoc but no parent pointers", () => { + const root = createSourceFile("foo.ts", "/** */var a = true;", ScriptTarget.ES5, /*setParentNodes*/ false); + root.statements[0].getStart(root, /*includeJsdocComment*/ true); + }); + describe("missing type parameter in jsDoc doesn't create a 1-element array", () => { + const doc = parseIsolatedJSDocComment("/**\n @template\n*/"); + assert.equal((doc?.jsDoc.tags?.[0] as JSDocTemplateTag).typeParameters.length, 0); + }); +}); } diff --git a/src/testRunner/unittests/jsonParserRecovery.ts b/src/testRunner/unittests/jsonParserRecovery.ts index 43007a687d349..e7c4be9e63c90 100644 --- a/src/testRunner/unittests/jsonParserRecovery.ts +++ b/src/testRunner/unittests/jsonParserRecovery.ts @@ -1,26 +1,26 @@ namespace ts { - describe("unittests:: jsonParserRecovery", () => { - function parsesToValidSourceFileWithErrors(name: string, text: string) { - it(name, () => { - const file = parseJsonText(name, text); - assert(file.parseDiagnostics.length, "Should have parse errors"); - Harness.Baseline.runBaseline( - `jsonParserRecovery/${name.replace(/[^a-z0-9_-]/ig, "_")}.errors.txt`, - Harness.Compiler.getErrorBaseline([{ - content: text, - unitName: name - }], file.parseDiagnostics)); +describe("unittests:: jsonParserRecovery", () => { + function parsesToValidSourceFileWithErrors(name: string, text: string) { + it(name, () => { + const file = parseJsonText(name, text); + assert(file.parseDiagnostics.length, "Should have parse errors"); + Harness.Baseline.runBaseline( + `jsonParserRecovery/${name.replace(/[^a-z0-9_-]/ig, "_")}.errors.txt`, + Harness.Compiler.getErrorBaseline([{ + content: text, + unitName: name + }], file.parseDiagnostics)); - // Will throw if parse tree does not cover full input text - file.getChildren(); - }); - } + // Will throw if parse tree does not cover full input text + file.getChildren(); + }); + } - parsesToValidSourceFileWithErrors("trailing identifier", "{} blah"); - parsesToValidSourceFileWithErrors("TypeScript code", "interface Foo {} blah"); - parsesToValidSourceFileWithErrors("Two comma-separated objects", "{}, {}"); - parsesToValidSourceFileWithErrors("Two objects", "{} {}"); - parsesToValidSourceFileWithErrors("JSX", ` + parsesToValidSourceFileWithErrors("trailing identifier", "{} blah"); + parsesToValidSourceFileWithErrors("TypeScript code", "interface Foo {} blah"); + parsesToValidSourceFileWithErrors("Two comma-separated objects", "{}, {}"); + parsesToValidSourceFileWithErrors("Two objects", "{} {}"); + parsesToValidSourceFileWithErrors("JSX", ` interface Test {} const Header = () => ( @@ -35,5 +35,5 @@ namespace ts {
)`); - }); +}); } diff --git a/src/testRunner/unittests/moduleResolution.ts b/src/testRunner/unittests/moduleResolution.ts index 87c3587ec2fed..9d4d0b0149290 100644 --- a/src/testRunner/unittests/moduleResolution.ts +++ b/src/testRunner/unittests/moduleResolution.ts @@ -1,765 +1,864 @@ namespace ts { - export function checkResolvedModule(actual: ResolvedModuleFull | undefined, expected: ResolvedModuleFull | undefined): boolean { - if (!expected) { - if (actual) { - assert.fail(actual, expected, "expected resolved module to be undefined"); - return false; - } - return true; - } - else if (!actual) { - assert.fail(actual, expected, "expected resolved module to be defined"); +export function checkResolvedModule(actual: ResolvedModuleFull | undefined, expected: ResolvedModuleFull | undefined): boolean { + if (!expected) { + if (actual) { + assert.fail(actual, expected, "expected resolved module to be undefined"); return false; } - - assert.isTrue(actual.resolvedFileName === expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`); - assert.isTrue(actual.extension === expected.extension, `'ext': expected '${actual.extension}' to be equal to '${expected.extension}'`); - assert.isTrue(actual.isExternalLibraryImport === expected.isExternalLibraryImport, `'isExternalLibraryImport': expected '${actual.isExternalLibraryImport}' to be equal to '${expected.isExternalLibraryImport}'`); return true; } - - export function checkResolvedModuleWithFailedLookupLocations(actual: ResolvedModuleWithFailedLookupLocations, expectedResolvedModule: ResolvedModuleFull, expectedFailedLookupLocations: string[]): void { - assert.isTrue(actual.resolvedModule !== undefined, "module should be resolved"); - checkResolvedModule(actual.resolvedModule, expectedResolvedModule); - assert.deepEqual(actual.failedLookupLocations, expectedFailedLookupLocations, `Failed lookup locations should match - expected has ${expectedFailedLookupLocations.length}, actual has ${actual.failedLookupLocations.length}`); + else if (!actual) { + assert.fail(actual, expected, "expected resolved module to be defined"); + return false; } - export function createResolvedModule(resolvedFileName: string, isExternalLibraryImport = false): ResolvedModuleFull { - return { resolvedFileName, extension: extensionFromPath(resolvedFileName), isExternalLibraryImport }; - } + assert.isTrue(actual.resolvedFileName === expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`); + assert.isTrue(actual.extension === expected.extension, `'ext': expected '${actual.extension}' to be equal to '${expected.extension}'`); + assert.isTrue(actual.isExternalLibraryImport === expected.isExternalLibraryImport, `'isExternalLibraryImport': expected '${actual.isExternalLibraryImport}' to be equal to '${expected.isExternalLibraryImport}'`); + return true; +} - interface File { - name: string; - content?: string; - symlinks?: string[]; - } +export function checkResolvedModuleWithFailedLookupLocations(actual: ResolvedModuleWithFailedLookupLocations, expectedResolvedModule: ResolvedModuleFull, expectedFailedLookupLocations: string[]): void { + assert.isTrue(actual.resolvedModule !== undefined, "module should be resolved"); + checkResolvedModule(actual.resolvedModule, expectedResolvedModule); + assert.deepEqual(actual.failedLookupLocations, expectedFailedLookupLocations, `Failed lookup locations should match - expected has ${expectedFailedLookupLocations.length}, actual has ${actual.failedLookupLocations.length}`); +} - function createModuleResolutionHost(hasDirectoryExists: boolean, ...files: File[]): ModuleResolutionHost { - const map = new Map(); - for (const file of files) { - map.set(file.name, file); - if (file.symlinks) { - for (const symlink of file.symlinks) { - map.set(symlink, file); - } +export function createResolvedModule(resolvedFileName: string, isExternalLibraryImport = false): ResolvedModuleFull { + return { resolvedFileName, extension: extensionFromPath(resolvedFileName), isExternalLibraryImport }; +} + +interface File { + name: string; + content?: string; + symlinks?: string[]; +} + +function createModuleResolutionHost(hasDirectoryExists: boolean, ...files: File[]): ModuleResolutionHost { + const map = new Map(); + for (const file of files) { + map.set(file.name, file); + if (file.symlinks) { + for (const symlink of file.symlinks) { + map.set(symlink, file); } } + } - if (hasDirectoryExists) { - const directories = new Map(); - for (const f of files) { - let name = getDirectoryPath(f.name); - while (true) { - directories.set(name, name); - const baseName = getDirectoryPath(name); - if (baseName === name) { - break; - } - name = baseName; + if (hasDirectoryExists) { + const directories = new Map(); + for (const f of files) { + let name = getDirectoryPath(f.name); + while (true) { + directories.set(name, name); + const baseName = getDirectoryPath(name); + if (baseName === name) { + break; } + name = baseName; } - return { - readFile, - realpath, - directoryExists: path => directories.has(path), - fileExists: path => { - assert.isTrue(directories.has(getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`); - return map.has(path); - }, - useCaseSensitiveFileNames: true - }; - } - else { - return { readFile, realpath, fileExists: path => map.has(path), useCaseSensitiveFileNames: true }; - } - function readFile(path: string): string | undefined { - const file = map.get(path); - return file && file.content; - } - function realpath(path: string): string { - return map.get(path)!.name; } + return { + readFile, + realpath, + directoryExists: path => directories.has(path), + fileExists: path => { + assert.isTrue(directories.has(getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`); + return map.has(path); + }, + useCaseSensitiveFileNames: true + }; } + else { + return { readFile, realpath, fileExists: path => map.has(path), useCaseSensitiveFileNames: true }; + } + function readFile(path: string): string | undefined { + const file = map.get(path); + return file && file.content; + } + function realpath(path: string): string { + return map.get(path)!.name; + } +} - describe("unittests:: moduleResolution:: Node module resolution - relative paths", () => { - // node module resolution does _not_ implicitly append these extensions to an extensionless path (though will still attempt to load them if explicitly) - const nonImplicitExtensions = [Extension.Mts, Extension.Dmts, Extension.Mjs, Extension.Cts, Extension.Dcts, Extension.Cjs]; - const autoExtensions = filter(supportedTSExtensionsFlat, e => nonImplicitExtensions.indexOf(e) === -1); - function testLoadAsFile(containingFileName: string, moduleFileNameNoExt: string, moduleName: string): void { - for (const ext of autoExtensions) { - test(ext, /*hasDirectoryExists*/ false); - test(ext, /*hasDirectoryExists*/ true); - } +describe("unittests:: moduleResolution:: Node module resolution - relative paths", () => { + // node module resolution does _not_ implicitly append these extensions to an extensionless path (though will still attempt to load them if explicitly) + const nonImplicitExtensions = [Extension.Mts, Extension.Dmts, Extension.Mjs, Extension.Cts, Extension.Dcts, Extension.Cjs]; + const autoExtensions = filter(supportedTSExtensionsFlat, e => nonImplicitExtensions.indexOf(e) === -1); + function testLoadAsFile(containingFileName: string, moduleFileNameNoExt: string, moduleName: string): void { + for (const ext of autoExtensions) { + test(ext, /*hasDirectoryExists*/ false); + test(ext, /*hasDirectoryExists*/ true); + } - function test(ext: string, hasDirectoryExists: boolean) { - const containingFile = { name: containingFileName }; - const moduleFile = { name: moduleFileNameNoExt + ext }; - const resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); - checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name)); - - const failedLookupLocations: string[] = []; - const dir = getDirectoryPath(containingFileName); - for (const e of autoExtensions) { - if (e === ext) { - break; - } - else { - failedLookupLocations.push(normalizePath(getRootLength(moduleName) === 0 ? combinePaths(dir, moduleName) : moduleName) + e); - } + function test(ext: string, hasDirectoryExists: boolean) { + const containingFile = { name: containingFileName }; + const moduleFile = { name: moduleFileNameNoExt + ext }; + const resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); + checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name)); + + const failedLookupLocations: string[] = []; + const dir = getDirectoryPath(containingFileName); + for (const e of autoExtensions) { + if (e === ext) { + break; } + else { + failedLookupLocations.push(normalizePath(getRootLength(moduleName) === 0 ? combinePaths(dir, moduleName) : moduleName) + e); + } + } - assert.deepEqual(resolution.failedLookupLocations, failedLookupLocations); + assert.deepEqual(resolution.failedLookupLocations, failedLookupLocations); - } } + } - it("module name that starts with './' resolved as relative file name", () => { - testLoadAsFile("/foo/bar/baz.ts", "/foo/bar/foo", "./foo"); - }); + it("module name that starts with './' resolved as relative file name", () => { + testLoadAsFile("/foo/bar/baz.ts", "/foo/bar/foo", "./foo"); + }); - it("module name that starts with '../' resolved as relative file name", () => { - testLoadAsFile("/foo/bar/baz.ts", "/foo/foo", "../foo"); - }); + it("module name that starts with '../' resolved as relative file name", () => { + testLoadAsFile("/foo/bar/baz.ts", "/foo/foo", "../foo"); + }); - it("module name that starts with '/' script extension resolved as relative file name", () => { - testLoadAsFile("/foo/bar/baz.ts", "/foo", "/foo"); - }); + it("module name that starts with '/' script extension resolved as relative file name", () => { + testLoadAsFile("/foo/bar/baz.ts", "/foo", "/foo"); + }); - it("module name that starts with 'c:/' script extension resolved as relative file name", () => { - testLoadAsFile("c:/foo/bar/baz.ts", "c:/foo", "c:/foo"); - }); + it("module name that starts with 'c:/' script extension resolved as relative file name", () => { + testLoadAsFile("c:/foo/bar/baz.ts", "c:/foo", "c:/foo"); + }); - function testLoadingFromPackageJson(containingFileName: string, packageJsonFileName: string, fieldRef: string, moduleFileName: string, moduleName: string): void { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const containingFile = { name: containingFileName }; - const packageJson = { name: packageJsonFileName, content: JSON.stringify({ typings: fieldRef }) }; - const moduleFile = { name: moduleFileName }; - const resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile)); - checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name)); - // expect three failed lookup location - attempt to load module as file with all supported extensions - assert.equal(resolution.failedLookupLocations.length, supportedTSExtensions[0].length); - } + function testLoadingFromPackageJson(containingFileName: string, packageJsonFileName: string, fieldRef: string, moduleFileName: string, moduleName: string): void { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const containingFile = { name: containingFileName }; + const packageJson = { name: packageJsonFileName, content: JSON.stringify({ typings: fieldRef }) }; + const moduleFile = { name: moduleFileName }; + const resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile)); + checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name)); + // expect three failed lookup location - attempt to load module as file with all supported extensions + assert.equal(resolution.failedLookupLocations.length, supportedTSExtensions[0].length); } + } - it("module name as directory - load from 'typings'", () => { - testLoadingFromPackageJson("/a/b/c/d.ts", "/a/b/c/bar/package.json", "c/d/e.d.ts", "/a/b/c/bar/c/d/e.d.ts", "./bar"); - testLoadingFromPackageJson("/a/b/c/d.ts", "/a/bar/package.json", "e.d.ts", "/a/bar/e.d.ts", "../../bar"); - testLoadingFromPackageJson("/a/b/c/d.ts", "/bar/package.json", "e.d.ts", "/bar/e.d.ts", "/bar"); - testLoadingFromPackageJson("c:/a/b/c/d.ts", "c:/bar/package.json", "e.d.ts", "c:/bar/e.d.ts", "c:/bar"); - }); + it("module name as directory - load from 'typings'", () => { + testLoadingFromPackageJson("/a/b/c/d.ts", "/a/b/c/bar/package.json", "c/d/e.d.ts", "/a/b/c/bar/c/d/e.d.ts", "./bar"); + testLoadingFromPackageJson("/a/b/c/d.ts", "/a/bar/package.json", "e.d.ts", "/a/bar/e.d.ts", "../../bar"); + testLoadingFromPackageJson("/a/b/c/d.ts", "/bar/package.json", "e.d.ts", "/bar/e.d.ts", "/bar"); + testLoadingFromPackageJson("c:/a/b/c/d.ts", "c:/bar/package.json", "e.d.ts", "c:/bar/e.d.ts", "c:/bar"); + }); - function testTypingsIgnored(typings: any): void { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); + function testTypingsIgnored(typings: any): void { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - function test(hasDirectoryExists: boolean) { - const containingFile = { name: "/a/b.ts" }; - const packageJson = { name: "/node_modules/b/package.json", content: JSON.stringify({ typings }) }; - const moduleFile = { name: "/a/b.d.ts" }; + function test(hasDirectoryExists: boolean) { + const containingFile = { name: "/a/b.ts" }; + const packageJson = { name: "/node_modules/b/package.json", content: JSON.stringify({ typings }) }; + const moduleFile = { name: "/a/b.d.ts" }; - const indexPath = "/node_modules/b/index.d.ts"; - const indexFile = { name: indexPath }; + const indexPath = "/node_modules/b/index.d.ts"; + const indexFile = { name: indexPath }; - const resolution = nodeModuleNameResolver("b", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile, indexFile)); + const resolution = nodeModuleNameResolver("b", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile, indexFile)); - checkResolvedModule(resolution.resolvedModule, createResolvedModule(indexPath, /*isExternalLibraryImport*/ true)); - } + checkResolvedModule(resolution.resolvedModule, createResolvedModule(indexPath, /*isExternalLibraryImport*/ true)); } + } - it("module name as directory - handle invalid 'typings'", () => { - testTypingsIgnored(["a", "b"]); - testTypingsIgnored({ a: "b" }); - testTypingsIgnored(/*typings*/ true); - testTypingsIgnored(/*typings*/ null); // eslint-disable-line no-null/no-null - testTypingsIgnored(/*typings*/ undefined); + it("module name as directory - handle invalid 'typings'", () => { + testTypingsIgnored(["a", "b"]); + testTypingsIgnored({ a: "b" }); + testTypingsIgnored(/*typings*/ true); + testTypingsIgnored(/*typings*/ null); // eslint-disable-line no-null/no-null + testTypingsIgnored(/*typings*/ undefined); + }); + it("module name as directory - load index.d.ts", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const containingFile = { name: "/a/b/c.ts" }; + const packageJson = { name: "/a/b/foo/package.json", content: JSON.stringify({ main: "/c/d" }) }; + const indexFile = { name: "/a/b/foo/index.d.ts" }; + const resolution = nodeModuleNameResolver("./foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, indexFile)); + checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(indexFile.name), [ + "/a/b/foo.ts", + "/a/b/foo.tsx", + "/a/b/foo.d.ts", + "/c/d", + "/c/d.ts", + "/c/d.tsx", + "/c/d.d.ts", + "/c/d/index.ts", + "/c/d/index.tsx", + "/c/d/index.d.ts", + "/a/b/foo/index.ts", + "/a/b/foo/index.tsx", + ]); + } + }); +}); + +describe("unittests:: moduleResolution:: Node module resolution - non-relative paths", () => { + it("computes correct commonPrefix for moduleName cache", () => { + const resolutionCache = createModuleResolutionCache("/", (f) => f); + let cache = resolutionCache.getOrCreateCacheForModuleName("a", /*mode*/ undefined); + cache.set("/sub", { + resolvedModule: { + originalPath: undefined, + resolvedFileName: "/sub/node_modules/a/index.ts", + isExternalLibraryImport: true, + extension: Extension.Ts, + }, + failedLookupLocations: [], }); - it("module name as directory - load index.d.ts", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const containingFile = { name: "/a/b/c.ts" }; - const packageJson = { name: "/a/b/foo/package.json", content: JSON.stringify({ main: "/c/d" }) }; - const indexFile = { name: "/a/b/foo/index.d.ts" }; - const resolution = nodeModuleNameResolver("./foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, indexFile)); - checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(indexFile.name), [ - "/a/b/foo.ts", - "/a/b/foo.tsx", - "/a/b/foo.d.ts", - "/c/d", - "/c/d.ts", - "/c/d.tsx", - "/c/d.d.ts", - "/c/d/index.ts", - "/c/d/index.tsx", - "/c/d/index.d.ts", - "/a/b/foo/index.ts", - "/a/b/foo/index.tsx", - ]); - } + assert.isDefined(cache.get("/sub")); + assert.isUndefined(cache.get("/")); + + cache = resolutionCache.getOrCreateCacheForModuleName("b", /*mode*/ undefined); + cache.set("/sub/dir/foo", { + resolvedModule: { + originalPath: undefined, + resolvedFileName: "/sub/directory/node_modules/b/index.ts", + isExternalLibraryImport: true, + extension: Extension.Ts, + }, + failedLookupLocations: [], }); - }); - - describe("unittests:: moduleResolution:: Node module resolution - non-relative paths", () => { - it("computes correct commonPrefix for moduleName cache", () => { - const resolutionCache = createModuleResolutionCache("/", (f) => f); - let cache = resolutionCache.getOrCreateCacheForModuleName("a", /*mode*/ undefined); - cache.set("/sub", { - resolvedModule: { - originalPath: undefined, - resolvedFileName: "/sub/node_modules/a/index.ts", - isExternalLibraryImport: true, - extension: Extension.Ts, - }, - failedLookupLocations: [], - }); - assert.isDefined(cache.get("/sub")); - assert.isUndefined(cache.get("/")); - - cache = resolutionCache.getOrCreateCacheForModuleName("b", /*mode*/ undefined); - cache.set("/sub/dir/foo", { - resolvedModule: { - originalPath: undefined, - resolvedFileName: "/sub/directory/node_modules/b/index.ts", - isExternalLibraryImport: true, - extension: Extension.Ts, - }, - failedLookupLocations: [], - }); - assert.isDefined(cache.get("/sub/dir/foo")); - assert.isDefined(cache.get("/sub/dir")); - assert.isDefined(cache.get("/sub")); - assert.isUndefined(cache.get("/")); - - cache = resolutionCache.getOrCreateCacheForModuleName("c", /*mode*/ undefined); - cache.set("/foo/bar", { - resolvedModule: { - originalPath: undefined, - resolvedFileName: "/bar/node_modules/c/index.ts", - isExternalLibraryImport: true, - extension: Extension.Ts, - }, - failedLookupLocations: [], - }); - assert.isDefined(cache.get("/foo/bar")); - assert.isDefined(cache.get("/foo")); - assert.isDefined(cache.get("/")); - - cache = resolutionCache.getOrCreateCacheForModuleName("d", /*mode*/ undefined); - cache.set("/foo", { - resolvedModule: { - originalPath: undefined, - resolvedFileName: "/foo/index.ts", - isExternalLibraryImport: true, - extension: Extension.Ts, - }, - failedLookupLocations: [], - }); - assert.isDefined(cache.get("/foo")); - assert.isUndefined(cache.get("/")); - - cache = resolutionCache.getOrCreateCacheForModuleName("e", /*mode*/ undefined); - cache.set("c:/foo", { - resolvedModule: { - originalPath: undefined, - resolvedFileName: "d:/bar/node_modules/e/index.ts", - isExternalLibraryImport: true, - extension: Extension.Ts, - }, - failedLookupLocations: [], - }); - assert.isDefined(cache.get("c:/foo")); - assert.isDefined(cache.get("c:/")); - assert.isUndefined(cache.get("d:/")); - - cache = resolutionCache.getOrCreateCacheForModuleName("f", /*mode*/ undefined); - cache.set("/foo/bar/baz", { - resolvedModule: undefined, - failedLookupLocations: [], - }); - assert.isDefined(cache.get("/foo/bar/baz")); - assert.isDefined(cache.get("/foo/bar")); - assert.isDefined(cache.get("/foo")); - assert.isDefined(cache.get("/")); + assert.isDefined(cache.get("/sub/dir/foo")); + assert.isDefined(cache.get("/sub/dir")); + assert.isDefined(cache.get("/sub")); + assert.isUndefined(cache.get("/")); + + cache = resolutionCache.getOrCreateCacheForModuleName("c", /*mode*/ undefined); + cache.set("/foo/bar", { + resolvedModule: { + originalPath: undefined, + resolvedFileName: "/bar/node_modules/c/index.ts", + isExternalLibraryImport: true, + extension: Extension.Ts, + }, + failedLookupLocations: [], }); + assert.isDefined(cache.get("/foo/bar")); + assert.isDefined(cache.get("/foo")); + assert.isDefined(cache.get("/")); + + cache = resolutionCache.getOrCreateCacheForModuleName("d", /*mode*/ undefined); + cache.set("/foo", { + resolvedModule: { + originalPath: undefined, + resolvedFileName: "/foo/index.ts", + isExternalLibraryImport: true, + extension: Extension.Ts, + }, + failedLookupLocations: [], + }); + assert.isDefined(cache.get("/foo")); + assert.isUndefined(cache.get("/")); + + cache = resolutionCache.getOrCreateCacheForModuleName("e", /*mode*/ undefined); + cache.set("c:/foo", { + resolvedModule: { + originalPath: undefined, + resolvedFileName: "d:/bar/node_modules/e/index.ts", + isExternalLibraryImport: true, + extension: Extension.Ts, + }, + failedLookupLocations: [], + }); + assert.isDefined(cache.get("c:/foo")); + assert.isDefined(cache.get("c:/")); + assert.isUndefined(cache.get("d:/")); + + cache = resolutionCache.getOrCreateCacheForModuleName("f", /*mode*/ undefined); + cache.set("/foo/bar/baz", { + resolvedModule: undefined, + failedLookupLocations: [], + }); + assert.isDefined(cache.get("/foo/bar/baz")); + assert.isDefined(cache.get("/foo/bar")); + assert.isDefined(cache.get("/foo")); + assert.isDefined(cache.get("/")); + }); - it("load module as file - ts files not loaded", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); + it("load module as file - ts files not loaded", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - function test(hasDirectoryExists: boolean) { - const containingFile = { name: "/a/b/c/d/e.ts" }; - const moduleFile = { name: "/a/b/node_modules/foo.ts" }; - const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); - checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [ - "/a/b/c/d/node_modules/foo/package.json", - "/a/b/c/d/node_modules/foo.ts", - "/a/b/c/d/node_modules/foo.tsx", - "/a/b/c/d/node_modules/foo.d.ts", + function test(hasDirectoryExists: boolean) { + const containingFile = { name: "/a/b/c/d/e.ts" }; + const moduleFile = { name: "/a/b/node_modules/foo.ts" }; + const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); + checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [ + "/a/b/c/d/node_modules/foo/package.json", + "/a/b/c/d/node_modules/foo.ts", + "/a/b/c/d/node_modules/foo.tsx", + "/a/b/c/d/node_modules/foo.d.ts", - "/a/b/c/d/node_modules/foo/index.ts", - "/a/b/c/d/node_modules/foo/index.tsx", - "/a/b/c/d/node_modules/foo/index.d.ts", + "/a/b/c/d/node_modules/foo/index.ts", + "/a/b/c/d/node_modules/foo/index.tsx", + "/a/b/c/d/node_modules/foo/index.d.ts", - "/a/b/c/d/node_modules/@types/foo/package.json", - "/a/b/c/d/node_modules/@types/foo.d.ts", + "/a/b/c/d/node_modules/@types/foo/package.json", + "/a/b/c/d/node_modules/@types/foo.d.ts", - "/a/b/c/d/node_modules/@types/foo/index.d.ts", + "/a/b/c/d/node_modules/@types/foo/index.d.ts", - "/a/b/c/node_modules/foo/package.json", - "/a/b/c/node_modules/foo.ts", - "/a/b/c/node_modules/foo.tsx", - "/a/b/c/node_modules/foo.d.ts", + "/a/b/c/node_modules/foo/package.json", + "/a/b/c/node_modules/foo.ts", + "/a/b/c/node_modules/foo.tsx", + "/a/b/c/node_modules/foo.d.ts", - "/a/b/c/node_modules/foo/index.ts", - "/a/b/c/node_modules/foo/index.tsx", - "/a/b/c/node_modules/foo/index.d.ts", + "/a/b/c/node_modules/foo/index.ts", + "/a/b/c/node_modules/foo/index.tsx", + "/a/b/c/node_modules/foo/index.d.ts", - "/a/b/c/node_modules/@types/foo/package.json", - "/a/b/c/node_modules/@types/foo.d.ts", + "/a/b/c/node_modules/@types/foo/package.json", + "/a/b/c/node_modules/@types/foo.d.ts", - "/a/b/c/node_modules/@types/foo/index.d.ts", - "/a/b/node_modules/foo/package.json", - ]); - } - }); + "/a/b/c/node_modules/@types/foo/index.d.ts", + "/a/b/node_modules/foo/package.json", + ]); + } + }); - it("load module as file", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); + it("load module as file", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - function test(hasDirectoryExists: boolean) { - const containingFile = { name: "/a/b/c/d/e.ts" }; - const moduleFile = { name: "/a/b/node_modules/foo.d.ts" }; - const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); - checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true)); - } - }); - - it("load module as directory", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); + function test(hasDirectoryExists: boolean) { + const containingFile = { name: "/a/b/c/d/e.ts" }; + const moduleFile = { name: "/a/b/node_modules/foo.d.ts" }; + const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); + checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true)); + } + }); - function test(hasDirectoryExists: boolean) { - const containingFile: File = { name: "/a/node_modules/b/c/node_modules/d/e.ts" }; - const moduleFile: File = { name: "/a/node_modules/foo/index.d.ts" }; - const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); - checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [ - "/a/node_modules/b/c/node_modules/d/node_modules/foo/package.json", - "/a/node_modules/b/c/node_modules/d/node_modules/foo.ts", - "/a/node_modules/b/c/node_modules/d/node_modules/foo.tsx", - "/a/node_modules/b/c/node_modules/d/node_modules/foo.d.ts", + it("load module as directory", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.ts", - "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.tsx", - "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.d.ts", + function test(hasDirectoryExists: boolean) { + const containingFile: File = { name: "/a/node_modules/b/c/node_modules/d/e.ts" }; + const moduleFile: File = { name: "/a/node_modules/foo/index.d.ts" }; + const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); + checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [ + "/a/node_modules/b/c/node_modules/d/node_modules/foo/package.json", + "/a/node_modules/b/c/node_modules/d/node_modules/foo.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/foo.tsx", + "/a/node_modules/b/c/node_modules/d/node_modules/foo.d.ts", - "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo/package.json", - "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo.d.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.tsx", + "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.d.ts", - "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo/index.d.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo/package.json", + "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo.d.ts", - "/a/node_modules/b/c/node_modules/foo/package.json", - "/a/node_modules/b/c/node_modules/foo.ts", - "/a/node_modules/b/c/node_modules/foo.tsx", - "/a/node_modules/b/c/node_modules/foo.d.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo/index.d.ts", - "/a/node_modules/b/c/node_modules/foo/index.ts", - "/a/node_modules/b/c/node_modules/foo/index.tsx", - "/a/node_modules/b/c/node_modules/foo/index.d.ts", + "/a/node_modules/b/c/node_modules/foo/package.json", + "/a/node_modules/b/c/node_modules/foo.ts", + "/a/node_modules/b/c/node_modules/foo.tsx", + "/a/node_modules/b/c/node_modules/foo.d.ts", - "/a/node_modules/b/c/node_modules/@types/foo/package.json", - "/a/node_modules/b/c/node_modules/@types/foo.d.ts", + "/a/node_modules/b/c/node_modules/foo/index.ts", + "/a/node_modules/b/c/node_modules/foo/index.tsx", + "/a/node_modules/b/c/node_modules/foo/index.d.ts", - "/a/node_modules/b/c/node_modules/@types/foo/index.d.ts", + "/a/node_modules/b/c/node_modules/@types/foo/package.json", + "/a/node_modules/b/c/node_modules/@types/foo.d.ts", - "/a/node_modules/b/node_modules/foo/package.json", - "/a/node_modules/b/node_modules/foo.ts", - "/a/node_modules/b/node_modules/foo.tsx", - "/a/node_modules/b/node_modules/foo.d.ts", + "/a/node_modules/b/c/node_modules/@types/foo/index.d.ts", - "/a/node_modules/b/node_modules/foo/index.ts", - "/a/node_modules/b/node_modules/foo/index.tsx", - "/a/node_modules/b/node_modules/foo/index.d.ts", + "/a/node_modules/b/node_modules/foo/package.json", + "/a/node_modules/b/node_modules/foo.ts", + "/a/node_modules/b/node_modules/foo.tsx", + "/a/node_modules/b/node_modules/foo.d.ts", - "/a/node_modules/b/node_modules/@types/foo/package.json", - "/a/node_modules/b/node_modules/@types/foo.d.ts", + "/a/node_modules/b/node_modules/foo/index.ts", + "/a/node_modules/b/node_modules/foo/index.tsx", + "/a/node_modules/b/node_modules/foo/index.d.ts", - "/a/node_modules/b/node_modules/@types/foo/index.d.ts", + "/a/node_modules/b/node_modules/@types/foo/package.json", + "/a/node_modules/b/node_modules/@types/foo.d.ts", - "/a/node_modules/foo/package.json", - "/a/node_modules/foo.ts", - "/a/node_modules/foo.tsx", - "/a/node_modules/foo.d.ts", + "/a/node_modules/b/node_modules/@types/foo/index.d.ts", - "/a/node_modules/foo/index.ts", - "/a/node_modules/foo/index.tsx" - ]); - } - }); + "/a/node_modules/foo/package.json", + "/a/node_modules/foo.ts", + "/a/node_modules/foo.tsx", + "/a/node_modules/foo.d.ts", - testPreserveSymlinks(/*preserveSymlinks*/ false); - testPreserveSymlinks(/*preserveSymlinks*/ true); - function testPreserveSymlinks(preserveSymlinks: boolean) { - it(`preserveSymlinks: ${preserveSymlinks}`, () => { - const realFileName = "/linked/index.d.ts"; - const symlinkFileName = "/app/node_modules/linked/index.d.ts"; - const host = createModuleResolutionHost( - /*hasDirectoryExists*/ true, - { name: realFileName, symlinks: [symlinkFileName] }, - { name: "/app/node_modules/linked/package.json", content: '{"version": "0.0.0", "main": "./index"}' }, - ); - const resolution = nodeModuleNameResolver("linked", "/app/app.ts", { preserveSymlinks }, host); - const resolvedFileName = preserveSymlinks ? symlinkFileName : realFileName; - checkResolvedModule(resolution.resolvedModule, createResolvedModule(resolvedFileName, /*isExternalLibraryImport*/ true)); - }); + "/a/node_modules/foo/index.ts", + "/a/node_modules/foo/index.tsx" + ]); } + }); - it("uses originalPath for caching", () => { - const host = createModuleResolutionHost( - /*hasDirectoryExists*/ true, - { - name: "/modules/a.ts", - symlinks: ["/sub/node_modules/a/index.ts"], - }, - { - name: "/sub/node_modules/a/package.json", - content: '{"version": "0.0.0", "main": "./index"}' - } - ); - const compilerOptions: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs }; - const cache = createModuleResolutionCache("/", (f) => f); - let resolution = resolveModuleName("a", "/sub/dir/foo.ts", compilerOptions, host, cache); - checkResolvedModule(resolution.resolvedModule, createResolvedModule("/modules/a.ts", /*isExternalLibraryImport*/ true)); - - resolution = resolveModuleName("a", "/sub/foo.ts", compilerOptions, host, cache); - checkResolvedModule(resolution.resolvedModule, createResolvedModule("/modules/a.ts", /*isExternalLibraryImport*/ true)); - - resolution = resolveModuleName("a", "/foo.ts", compilerOptions, host, cache); - assert.isUndefined(resolution.resolvedModule, "lookup in parent directory doesn't hit the cache"); - }); - - it("preserves originalPath on cache hit", () => { + testPreserveSymlinks(/*preserveSymlinks*/ false); + testPreserveSymlinks(/*preserveSymlinks*/ true); + function testPreserveSymlinks(preserveSymlinks: boolean) { + it(`preserveSymlinks: ${preserveSymlinks}`, () => { + const realFileName = "/linked/index.d.ts"; + const symlinkFileName = "/app/node_modules/linked/index.d.ts"; const host = createModuleResolutionHost( /*hasDirectoryExists*/ true, - { name: "/linked/index.d.ts", symlinks: ["/app/node_modules/linked/index.d.ts"] }, + { name: realFileName, symlinks: [symlinkFileName] }, { name: "/app/node_modules/linked/package.json", content: '{"version": "0.0.0", "main": "./index"}' }, ); - const cache = createModuleResolutionCache("/", (f) => f); - const compilerOptions: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs }; - checkResolution(resolveModuleName("linked", "/app/src/app.ts", compilerOptions, host, cache)); - checkResolution(resolveModuleName("linked", "/app/lib/main.ts", compilerOptions, host, cache)); - - function checkResolution(resolution: ResolvedModuleWithFailedLookupLocations) { - checkResolvedModule(resolution.resolvedModule, createResolvedModule("/linked/index.d.ts", /*isExternalLibraryImport*/ true)); - assert.strictEqual(resolution.resolvedModule!.originalPath, "/app/node_modules/linked/index.d.ts"); - } + const resolution = nodeModuleNameResolver("linked", "/app/app.ts", { preserveSymlinks }, host); + const resolvedFileName = preserveSymlinks ? symlinkFileName : realFileName; + checkResolvedModule(resolution.resolvedModule, createResolvedModule(resolvedFileName, /*isExternalLibraryImport*/ true)); }); - }); + } - describe("unittests:: moduleResolution:: Relative imports", () => { - function test(files: ESMap, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) { - const options: CompilerOptions = { module: ModuleKind.CommonJS }; - const host: CompilerHost = { - getSourceFile: (fileName: string, languageVersion: ScriptTarget) => { - const path = normalizePath(combinePaths(currentDirectory, fileName)); - const file = files.get(path); - return file ? createSourceFile(fileName, file, languageVersion) : undefined; - }, - getDefaultLibFileName: () => "lib.d.ts", - writeFile: notImplemented, - getCurrentDirectory: () => currentDirectory, - getDirectories: () => [], - getCanonicalFileName: fileName => fileName.toLowerCase(), - getNewLine: () => "\r\n", - useCaseSensitiveFileNames: () => false, - fileExists: fileName => { - const path = normalizePath(combinePaths(currentDirectory, fileName)); - return files.has(path); - }, - readFile: notImplemented, - }; + it("uses originalPath for caching", () => { + const host = createModuleResolutionHost( + /*hasDirectoryExists*/ true, + { + name: "/modules/a.ts", + symlinks: ["/sub/node_modules/a/index.ts"], + }, + { + name: "/sub/node_modules/a/package.json", + content: '{"version": "0.0.0", "main": "./index"}' + } + ); + const compilerOptions: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs }; + const cache = createModuleResolutionCache("/", (f) => f); + let resolution = resolveModuleName("a", "/sub/dir/foo.ts", compilerOptions, host, cache); + checkResolvedModule(resolution.resolvedModule, createResolvedModule("/modules/a.ts", /*isExternalLibraryImport*/ true)); - const program = createProgram(rootFiles, options, host); + resolution = resolveModuleName("a", "/sub/foo.ts", compilerOptions, host, cache); + checkResolvedModule(resolution.resolvedModule, createResolvedModule("/modules/a.ts", /*isExternalLibraryImport*/ true)); - assert.equal(program.getSourceFiles().length, expectedFilesCount); - const syntacticDiagnostics = program.getSyntacticDiagnostics(); - assert.equal(syntacticDiagnostics.length, 0, `expect no syntactic diagnostics, got: ${JSON.stringify(Harness.Compiler.minimalDiagnosticsToString(syntacticDiagnostics))}`); - const semanticDiagnostics = program.getSemanticDiagnostics(); - assert.equal(semanticDiagnostics.length, 0, `expect no semantic diagnostics, got: ${JSON.stringify(Harness.Compiler.minimalDiagnosticsToString(semanticDiagnostics))}`); + resolution = resolveModuleName("a", "/foo.ts", compilerOptions, host, cache); + assert.isUndefined(resolution.resolvedModule, "lookup in parent directory doesn't hit the cache"); + }); - // try to get file using a relative name - for (const relativeFileName of relativeNamesToCheck) { - assert.isTrue(program.getSourceFile(relativeFileName) !== undefined, `expected to get file by relative name, got undefined`); - } + it("preserves originalPath on cache hit", () => { + const host = createModuleResolutionHost( + /*hasDirectoryExists*/ true, + { name: "/linked/index.d.ts", symlinks: ["/app/node_modules/linked/index.d.ts"] }, + { name: "/app/node_modules/linked/package.json", content: '{"version": "0.0.0", "main": "./index"}' }, + ); + const cache = createModuleResolutionCache("/", (f) => f); + const compilerOptions: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs }; + checkResolution(resolveModuleName("linked", "/app/src/app.ts", compilerOptions, host, cache)); + checkResolution(resolveModuleName("linked", "/app/lib/main.ts", compilerOptions, host, cache)); + + function checkResolution(resolution: ResolvedModuleWithFailedLookupLocations) { + checkResolvedModule(resolution.resolvedModule, createResolvedModule("/linked/index.d.ts", /*isExternalLibraryImport*/ true)); + assert.strictEqual(resolution.resolvedModule!.originalPath, "/app/node_modules/linked/index.d.ts"); + } + }); +}); + +describe("unittests:: moduleResolution:: Relative imports", () => { + function test(files: ESMap, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) { + const options: CompilerOptions = { module: ModuleKind.CommonJS }; + const host: CompilerHost = { + getSourceFile: (fileName: string, languageVersion: ScriptTarget) => { + const path = normalizePath(combinePaths(currentDirectory, fileName)); + const file = files.get(path); + return file ? createSourceFile(fileName, file, languageVersion) : undefined; + }, + getDefaultLibFileName: () => "lib.d.ts", + writeFile: notImplemented, + getCurrentDirectory: () => currentDirectory, + getDirectories: () => [], + getCanonicalFileName: fileName => fileName.toLowerCase(), + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => false, + fileExists: fileName => { + const path = normalizePath(combinePaths(currentDirectory, fileName)); + return files.has(path); + }, + readFile: notImplemented, + }; + + const program = createProgram(rootFiles, options, host); + + assert.equal(program.getSourceFiles().length, expectedFilesCount); + const syntacticDiagnostics = program.getSyntacticDiagnostics(); + assert.equal(syntacticDiagnostics.length, 0, `expect no syntactic diagnostics, got: ${JSON.stringify(Harness.Compiler.minimalDiagnosticsToString(syntacticDiagnostics))}`); + const semanticDiagnostics = program.getSemanticDiagnostics(); + assert.equal(semanticDiagnostics.length, 0, `expect no semantic diagnostics, got: ${JSON.stringify(Harness.Compiler.minimalDiagnosticsToString(semanticDiagnostics))}`); + + // try to get file using a relative name + for (const relativeFileName of relativeNamesToCheck) { + assert.isTrue(program.getSourceFile(relativeFileName) !== undefined, `expected to get file by relative name, got undefined`); } + } - it("should find all modules", () => { - const files = new Map(getEntries({ - "/a/b/c/first/shared.ts": ` + it("should find all modules", () => { + const files = new Map(getEntries({ + "/a/b/c/first/shared.ts": ` class A {} export = A`, - "/a/b/c/first/second/class_a.ts": ` + "/a/b/c/first/second/class_a.ts": ` import Shared = require('../shared'); import C = require('../../third/class_c'); class B {} export = B;`, - "/a/b/c/third/class_c.ts": ` + "/a/b/c/third/class_c.ts": ` import Shared = require('../first/shared'); class C {} export = C; ` - })); - test(files, "/a/b/c/first/second", ["class_a.ts"], 3, ["../../../c/third/class_c.ts"]); - }); - - it("should find modules in node_modules", () => { - const files = new Map(getEntries({ - "/parent/node_modules/mod/index.d.ts": "export var x", - "/parent/app/myapp.ts": `import {x} from "mod"` - })); - test(files, "/parent/app", ["myapp.ts"], 2, []); - }); + })); + test(files, "/a/b/c/first/second", ["class_a.ts"], 3, ["../../../c/third/class_c.ts"]); + }); - it("should find file referenced via absolute and relative names", () => { - const files = new Map(getEntries({ - "/a/b/c.ts": `/// `, - "/a/b/b.ts": "var x" - })); - test(files, "/a/b", ["c.ts", "/a/b/b.ts"], 2, []); - }); + it("should find modules in node_modules", () => { + const files = new Map(getEntries({ + "/parent/node_modules/mod/index.d.ts": "export var x", + "/parent/app/myapp.ts": `import {x} from "mod"` + })); + test(files, "/parent/app", ["myapp.ts"], 2, []); }); - describe("unittests:: moduleResolution:: Files with different casing with forceConsistentCasingInFileNames", () => { - let library: SourceFile; - function test( - files: ESMap, - options: CompilerOptions, - currentDirectory: string, - useCaseSensitiveFileNames: boolean, - rootFiles: string[], - expectedDiagnostics: (program: Program) => readonly Diagnostic[] - ): void { - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - if (!useCaseSensitiveFileNames) { - const oldFiles = files; - files = new Map(); - oldFiles.forEach((file, fileName) => { - files.set(getCanonicalFileName(fileName), file); - }); - } + it("should find file referenced via absolute and relative names", () => { + const files = new Map(getEntries({ + "/a/b/c.ts": `/// `, + "/a/b/b.ts": "var x" + })); + test(files, "/a/b", ["c.ts", "/a/b/b.ts"], 2, []); + }); +}); + +describe("unittests:: moduleResolution:: Files with different casing with forceConsistentCasingInFileNames", () => { + let library: SourceFile; + function test( + files: ESMap, + options: CompilerOptions, + currentDirectory: string, + useCaseSensitiveFileNames: boolean, + rootFiles: string[], + expectedDiagnostics: (program: Program) => readonly Diagnostic[] + ): void { + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + if (!useCaseSensitiveFileNames) { + const oldFiles = files; + files = new Map(); + oldFiles.forEach((file, fileName) => { + files.set(getCanonicalFileName(fileName), file); + }); + } - const host: CompilerHost = { - getSourceFile: (fileName: string, languageVersion: ScriptTarget) => { - if (fileName === "lib.d.ts") { - if (!library) { - library = createSourceFile("lib.d.ts", "", ScriptTarget.ES5); - } - return library; + const host: CompilerHost = { + getSourceFile: (fileName: string, languageVersion: ScriptTarget) => { + if (fileName === "lib.d.ts") { + if (!library) { + library = createSourceFile("lib.d.ts", "", ScriptTarget.ES5); } - const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); - const file = files.get(path); - return file ? createSourceFile(fileName, file, languageVersion) : undefined; - }, - getDefaultLibFileName: () => "lib.d.ts", - writeFile: notImplemented, - getCurrentDirectory: () => currentDirectory, - getDirectories: () => [], - getCanonicalFileName, - getNewLine: () => "\r\n", - useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, - fileExists: fileName => { - const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); - return files.has(path); - }, - readFile: notImplemented, - }; - const program = createProgram(rootFiles, options, host); - const diagnostics = sortAndDeduplicateDiagnostics([...program.getSemanticDiagnostics(), ...program.getOptionsDiagnostics()]); - assert.deepEqual(diagnostics, sortAndDeduplicateDiagnostics(expectedDiagnostics(program))); - } + return library; + } + const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); + const file = files.get(path); + return file ? createSourceFile(fileName, file, languageVersion) : undefined; + }, + getDefaultLibFileName: () => "lib.d.ts", + writeFile: notImplemented, + getCurrentDirectory: () => currentDirectory, + getDirectories: () => [], + getCanonicalFileName, + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, + fileExists: fileName => { + const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); + return files.has(path); + }, + readFile: notImplemented, + }; + const program = createProgram(rootFiles, options, host); + const diagnostics = sortAndDeduplicateDiagnostics([...program.getSemanticDiagnostics(), ...program.getOptionsDiagnostics()]); + assert.deepEqual(diagnostics, sortAndDeduplicateDiagnostics(expectedDiagnostics(program))); + } - it("should succeed when the same file is referenced using absolute and relative names", () => { - const files = new Map(getEntries({ - "/a/b/c.ts": `/// `, - "/a/b/d.ts": "var x" - })); - test( - files, - { module: ModuleKind.AMD }, - "/a/b", - /*useCaseSensitiveFileNames*/ false, - ["c.ts", "/a/b/d.ts"], - () => emptyArray - ); - }); + it("should succeed when the same file is referenced using absolute and relative names", () => { + const files = new Map(getEntries({ + "/a/b/c.ts": `/// `, + "/a/b/d.ts": "var x" + })); + test( + files, + { module: ModuleKind.AMD }, + "/a/b", + /*useCaseSensitiveFileNames*/ false, + ["c.ts", "/a/b/d.ts"], + () => emptyArray + ); + }); - it("should fail when two files used in program differ only in casing (tripleslash references)", () => { - const files = new Map(getEntries({ - "/a/b/c.ts": `/// `, - "/a/b/d.ts": "var x" - })); - test( - files, - { module: ModuleKind.AMD, forceConsistentCasingInFileNames: true }, - "/a/b", - /*useCaseSensitiveFileNames*/ false, - ["c.ts", "d.ts"], - program => [{ - ...tscWatch.getDiagnosticOfFileFromProgram( - program, - "c.ts", - `/// `.indexOf(`D.ts`), - "D.ts".length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, - ["D.ts", "d.ts"], - [ - tscWatch.getDiagnosticMessageChain( - Diagnostics.The_file_is_in_the_program_because_Colon, - emptyArray, - [ - tscWatch.getDiagnosticMessageChain(Diagnostics.Referenced_via_0_from_file_1, ["D.ts", "c.ts"]), - tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation) - ] - ) - ], - ) - ), - relatedInformation: undefined, - }] - ); - }); + it("should fail when two files used in program differ only in casing (tripleslash references)", () => { + const files = new Map(getEntries({ + "/a/b/c.ts": `/// `, + "/a/b/d.ts": "var x" + })); + test( + files, + { module: ModuleKind.AMD, forceConsistentCasingInFileNames: true }, + "/a/b", + /*useCaseSensitiveFileNames*/ false, + ["c.ts", "d.ts"], + program => [{ + ...tscWatch.getDiagnosticOfFileFromProgram( + program, + "c.ts", + `/// `.indexOf(`D.ts`), + "D.ts".length, + tscWatch.getDiagnosticMessageChain( + Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, + ["D.ts", "d.ts"], + [ + tscWatch.getDiagnosticMessageChain( + Diagnostics.The_file_is_in_the_program_because_Colon, + emptyArray, + [ + tscWatch.getDiagnosticMessageChain(Diagnostics.Referenced_via_0_from_file_1, ["D.ts", "c.ts"]), + tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation) + ] + ) + ], + ) + ), + relatedInformation: undefined, + }] + ); + }); - it("should fail when two files used in program differ only in casing (imports)", () => { - const files = new Map(getEntries({ - "/a/b/c.ts": `import {x} from "D"`, - "/a/b/d.ts": "export var x" - })); - test( - files, - { module: ModuleKind.AMD, forceConsistentCasingInFileNames: true }, - "/a/b", - /*useCaseSensitiveFileNames*/ false, - ["c.ts", "d.ts"], - program => [{ - ...tscWatch.getDiagnosticOfFileFromProgram( - program, - "c.ts", - `import {x} from "D"`.indexOf(`"D"`), - `"D"`.length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, - ["/a/b/D.ts", "d.ts"], - [ - tscWatch.getDiagnosticMessageChain( - Diagnostics.The_file_is_in_the_program_because_Colon, - emptyArray, - [ - tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]), - tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation) - ] - ) - ], - ) - ), - relatedInformation: undefined, - }] - ); - }); + it("should fail when two files used in program differ only in casing (imports)", () => { + const files = new Map(getEntries({ + "/a/b/c.ts": `import {x} from "D"`, + "/a/b/d.ts": "export var x" + })); + test( + files, + { module: ModuleKind.AMD, forceConsistentCasingInFileNames: true }, + "/a/b", + /*useCaseSensitiveFileNames*/ false, + ["c.ts", "d.ts"], + program => [{ + ...tscWatch.getDiagnosticOfFileFromProgram( + program, + "c.ts", + `import {x} from "D"`.indexOf(`"D"`), + `"D"`.length, + tscWatch.getDiagnosticMessageChain( + Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, + ["/a/b/D.ts", "d.ts"], + [ + tscWatch.getDiagnosticMessageChain( + Diagnostics.The_file_is_in_the_program_because_Colon, + emptyArray, + [ + tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]), + tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation) + ] + ) + ], + ) + ), + relatedInformation: undefined, + }] + ); + }); + + it("should fail when two files used in program differ only in casing (imports, relative module names)", () => { + const files = new Map(getEntries({ + "moduleA.ts": `import {x} from "./ModuleB"`, + "moduleB.ts": "export var x" + })); + test( + files, + { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, + "", + /*useCaseSensitiveFileNames*/ false, + ["moduleA.ts", "moduleB.ts"], + program => [{ + ...tscWatch.getDiagnosticOfFileFromProgram( + program, + "moduleA.ts", + `import {x} from "./ModuleB"`.indexOf(`"./ModuleB"`), + `"./ModuleB"`.length, + tscWatch.getDiagnosticMessageChain( + Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, + ["ModuleB.ts", "moduleB.ts"], + [ + tscWatch.getDiagnosticMessageChain( + Diagnostics.The_file_is_in_the_program_because_Colon, + emptyArray, + [ + tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./ModuleB"`, "moduleA.ts"]), + tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation) + ] + ) + ], + ) + ), + relatedInformation: undefined + }] + ); + }); - it("should fail when two files used in program differ only in casing (imports, relative module names)", () => { - const files = new Map(getEntries({ - "moduleA.ts": `import {x} from "./ModuleB"`, - "moduleB.ts": "export var x" - })); - test( - files, - { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, - "", - /*useCaseSensitiveFileNames*/ false, - ["moduleA.ts", "moduleB.ts"], - program => [{ + it("should fail when two files exist on disk that differs only in casing", () => { + const files = new Map(getEntries({ + "/a/b/c.ts": `import {x} from "D"`, + "/a/b/D.ts": "export var x", + "/a/b/d.ts": "export var y" + })); + test( + files, + { module: ModuleKind.AMD }, + "/a/b", + /*useCaseSensitiveFileNames*/ true, + ["c.ts", "d.ts"], + program => [{ + ...tscWatch.getDiagnosticOfFileFromProgram( + program, + "c.ts", + `import {x} from "D"`.indexOf(`"D"`), + `"D"`.length, + tscWatch.getDiagnosticMessageChain( + Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, + ["/a/b/D.ts", "d.ts"], + [ + tscWatch.getDiagnosticMessageChain( + Diagnostics.The_file_is_in_the_program_because_Colon, + emptyArray, + [ + tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]), + tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation) + ] + ) + ], + ) + ), + relatedInformation: undefined + }] + ); + }); + + it("should fail when module name in 'require' calls has inconsistent casing", () => { + const files = new Map(getEntries({ + "moduleA.ts": `import a = require("./ModuleC")`, + "moduleB.ts": `import a = require("./moduleC")`, + "moduleC.ts": "export var x" + })); + test( + files, + { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, + "", + /*useCaseSensitiveFileNames*/ false, + ["moduleA.ts", "moduleB.ts", "moduleC.ts"], + program => { + const importInA = { ...tscWatch.getDiagnosticOfFileFromProgram( program, "moduleA.ts", - `import {x} from "./ModuleB"`.indexOf(`"./ModuleB"`), - `"./ModuleB"`.length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, - ["ModuleB.ts", "moduleB.ts"], - [ - tscWatch.getDiagnosticMessageChain( - Diagnostics.The_file_is_in_the_program_because_Colon, - emptyArray, - [ - tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./ModuleB"`, "moduleA.ts"]), - tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation) - ] - ) - ], - ) + `import a = require("./ModuleC")`.indexOf(`"./ModuleC"`), + `"./ModuleC"`.length, + Diagnostics.File_is_included_via_import_here, ), - relatedInformation: undefined - }] - ); - }); - - it("should fail when two files exist on disk that differs only in casing", () => { - const files = new Map(getEntries({ - "/a/b/c.ts": `import {x} from "D"`, - "/a/b/D.ts": "export var x", - "/a/b/d.ts": "export var y" - })); - test( - files, - { module: ModuleKind.AMD }, - "/a/b", - /*useCaseSensitiveFileNames*/ true, - ["c.ts", "d.ts"], - program => [{ + reportsUnnecessary: undefined, + reportsDeprecated: undefined + }; + const importInB = { ...tscWatch.getDiagnosticOfFileFromProgram( program, - "c.ts", - `import {x} from "D"`.indexOf(`"D"`), - `"D"`.length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, - ["/a/b/D.ts", "d.ts"], - [ - tscWatch.getDiagnosticMessageChain( - Diagnostics.The_file_is_in_the_program_because_Colon, - emptyArray, - [ - tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]), - tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation) - ] - ) - ], - ) + "moduleB.ts", + `import a = require("./moduleC")`.indexOf(`"./moduleC"`), + `"./moduleC"`.length, + Diagnostics.File_is_included_via_import_here, ), - relatedInformation: undefined - }] - ); - }); + reportsUnnecessary: undefined, + reportsDeprecated: undefined + }; + const importHereInA = tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "moduleA.ts"]); + const importHereInB = tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "moduleB.ts"]); + const details = [tscWatch.getDiagnosticMessageChain( + Diagnostics.The_file_is_in_the_program_because_Colon, + emptyArray, + [importHereInA, importHereInB, tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation)] + )]; + return [ + { + ...tscWatch.getDiagnosticOfFileFrom( + importInA.file, + importInA.start, + importInA.length, + tscWatch.getDiagnosticMessageChain( + Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, + ["ModuleC.ts", "moduleC.ts" ], + details, + ) + ), + relatedInformation: [importInB] + }, + { + ...tscWatch.getDiagnosticOfFileFrom( + importInB.file, + importInB.start, + importInB.length, + tscWatch.getDiagnosticMessageChain( + Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, + ["moduleC.ts", "ModuleC.ts"], + details, + ) + ), + relatedInformation: [importInA] + } + ]; + } + ); + }); - it("should fail when module name in 'require' calls has inconsistent casing", () => { - const files = new Map(getEntries({ - "moduleA.ts": `import a = require("./ModuleC")`, - "moduleB.ts": `import a = require("./moduleC")`, - "moduleC.ts": "export var x" - })); - test( - files, - { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, - "", - /*useCaseSensitiveFileNames*/ false, - ["moduleA.ts", "moduleB.ts", "moduleC.ts"], - program => { - const importInA = { + it("should fail when module names in 'require' calls has inconsistent casing and current directory has uppercase chars", () => { + const files = new Map(getEntries({ + "/a/B/c/moduleA.ts": `import a = require("./ModuleC")`, + "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, + "/a/B/c/moduleC.ts": "export var x", + "/a/B/c/moduleD.ts": ` +import a = require("./moduleA"); +import b = require("./moduleB"); + ` + })); + test( + files, + { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, + "/a/B/c", + /*useCaseSensitiveFileNames*/ false, + ["moduleD.ts"], + program => [{ + ...tscWatch.getDiagnosticOfFileFromProgram( + program, + "moduleB.ts", + `import a = require("./moduleC")`.indexOf(`"./moduleC"`), + `"./moduleC"`.length, + tscWatch.getDiagnosticMessageChain( + Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, + ["/a/B/c/moduleC.ts", "/a/B/c/ModuleC.ts"], + [ + tscWatch.getDiagnosticMessageChain( + Diagnostics.The_file_is_in_the_program_because_Colon, + emptyArray, + [ + tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "/a/B/c/moduleA.ts"]), + tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "/a/B/c/moduleB.ts"]) + ] + ) + ], + ) + ), + relatedInformation: [ + { ...tscWatch.getDiagnosticOfFileFromProgram( program, "moduleA.ts", @@ -769,731 +868,632 @@ export = C; ), reportsUnnecessary: undefined, reportsDeprecated: undefined - }; - const importInB = { - ...tscWatch.getDiagnosticOfFileFromProgram( - program, - "moduleB.ts", - `import a = require("./moduleC")`.indexOf(`"./moduleC"`), - `"./moduleC"`.length, - Diagnostics.File_is_included_via_import_here, - ), - reportsUnnecessary: undefined, - reportsDeprecated: undefined - }; - const importHereInA = tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "moduleA.ts"]); - const importHereInB = tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "moduleB.ts"]); - const details = [tscWatch.getDiagnosticMessageChain( - Diagnostics.The_file_is_in_the_program_because_Colon, - emptyArray, - [importHereInA, importHereInB, tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation)] - )]; - return [ - { - ...tscWatch.getDiagnosticOfFileFrom( - importInA.file, - importInA.start, - importInA.length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, - ["ModuleC.ts", "moduleC.ts" ], - details, - ) - ), - relatedInformation: [importInB] - }, - { - ...tscWatch.getDiagnosticOfFileFrom( - importInB.file, - importInB.start, - importInB.length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, - ["moduleC.ts", "ModuleC.ts"], - details, - ) - ), - relatedInformation: [importInA] - } - ]; - } - ); - }); - - it("should fail when module names in 'require' calls has inconsistent casing and current directory has uppercase chars", () => { - const files = new Map(getEntries({ - "/a/B/c/moduleA.ts": `import a = require("./ModuleC")`, - "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, - "/a/B/c/moduleC.ts": "export var x", - "/a/B/c/moduleD.ts": ` -import a = require("./moduleA"); -import b = require("./moduleB"); - ` - })); - test( - files, - { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, - "/a/B/c", - /*useCaseSensitiveFileNames*/ false, - ["moduleD.ts"], - program => [{ - ...tscWatch.getDiagnosticOfFileFromProgram( - program, - "moduleB.ts", - `import a = require("./moduleC")`.indexOf(`"./moduleC"`), - `"./moduleC"`.length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, - ["/a/B/c/moduleC.ts", "/a/B/c/ModuleC.ts"], - [ - tscWatch.getDiagnosticMessageChain( - Diagnostics.The_file_is_in_the_program_because_Colon, - emptyArray, - [ - tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "/a/B/c/moduleA.ts"]), - tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "/a/B/c/moduleB.ts"]) - ] - ) - ], - ) - ), - relatedInformation: [ - { - ...tscWatch.getDiagnosticOfFileFromProgram( - program, - "moduleA.ts", - `import a = require("./ModuleC")`.indexOf(`"./ModuleC"`), - `"./ModuleC"`.length, - Diagnostics.File_is_included_via_import_here, - ), - reportsUnnecessary: undefined, - reportsDeprecated: undefined - } - ] - }] - ); - }); - it("should not fail when module names in 'require' calls has consistent casing and current directory has uppercase chars", () => { - const files = new Map(getEntries({ - "/a/B/c/moduleA.ts": `import a = require("./moduleC")`, - "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, - "/a/B/c/moduleC.ts": "export var x", - "/a/B/c/moduleD.ts": ` + } + ] + }] + ); + }); + it("should not fail when module names in 'require' calls has consistent casing and current directory has uppercase chars", () => { + const files = new Map(getEntries({ + "/a/B/c/moduleA.ts": `import a = require("./moduleC")`, + "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, + "/a/B/c/moduleC.ts": "export var x", + "/a/B/c/moduleD.ts": ` import a = require("./moduleA"); import b = require("./moduleB"); ` - })); - test( - files, - { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, - "/a/B/c", - /*useCaseSensitiveFileNames*/ false, - ["moduleD.ts"], - () => emptyArray - ); - }); + })); + test( + files, + { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, + "/a/B/c", + /*useCaseSensitiveFileNames*/ false, + ["moduleD.ts"], + () => emptyArray + ); + }); - it("should succeed when the two files in program differ only in drive letter in their names", () => { - const files = new Map(getEntries({ - "d:/someFolder/moduleA.ts": `import a = require("D:/someFolder/moduleC")`, - "d:/someFolder/moduleB.ts": `import a = require("./moduleC")`, - "D:/someFolder/moduleC.ts": "export const x = 10", - })); - test( - files, - { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, - "d:/someFolder", - /*useCaseSensitiveFileNames*/ false, - ["d:/someFolder/moduleA.ts", "d:/someFolder/moduleB.ts"], - () => emptyArray - ); - }); + it("should succeed when the two files in program differ only in drive letter in their names", () => { + const files = new Map(getEntries({ + "d:/someFolder/moduleA.ts": `import a = require("D:/someFolder/moduleC")`, + "d:/someFolder/moduleB.ts": `import a = require("./moduleC")`, + "D:/someFolder/moduleC.ts": "export const x = 10", + })); + test( + files, + { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, + "d:/someFolder", + /*useCaseSensitiveFileNames*/ false, + ["d:/someFolder/moduleA.ts", "d:/someFolder/moduleB.ts"], + () => emptyArray + ); }); +}); - describe("unittests:: moduleResolution:: baseUrl augmented module resolution", () => { +describe("unittests:: moduleResolution:: baseUrl augmented module resolution", () => { - it("module resolution without path mappings/rootDirs", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); + it("module resolution without path mappings/rootDirs", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - function test(hasDirectoryExists: boolean) { - const file1: File = { name: "/root/folder1/file1.ts" }; - const file2: File = { name: "/root/folder2/file2.ts" }; - const file3: File = { name: "/root/folder2/file3.ts" }; - const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3); - for (const moduleResolution of [ ModuleResolutionKind.NodeJs, ModuleResolutionKind.Classic ]) { - const options: CompilerOptions = { moduleResolution, baseUrl: "/root" }; - { - const result = resolveModuleName("folder2/file2", file1.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file2.name), []); - } - { - const result = resolveModuleName("./file3", file2.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file3.name), []); - } - { - const result = resolveModuleName("/root/folder1/file1", file2.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file1.name), []); - } + function test(hasDirectoryExists: boolean) { + const file1: File = { name: "/root/folder1/file1.ts" }; + const file2: File = { name: "/root/folder2/file2.ts" }; + const file3: File = { name: "/root/folder2/file3.ts" }; + const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3); + for (const moduleResolution of [ ModuleResolutionKind.NodeJs, ModuleResolutionKind.Classic ]) { + const options: CompilerOptions = { moduleResolution, baseUrl: "/root" }; + { + const result = resolveModuleName("folder2/file2", file1.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file2.name), []); } - } - // add failure tests - }); - - it("node + baseUrl", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const main: File = { name: "/root/a/b/main.ts" }; - const m1: File = { name: "/root/m1.ts" }; // load file as module - const m2: File = { name: "/root/m2/index.d.ts" }; // load folder as module - const m3: File = { name: "/root/m3/package.json", content: JSON.stringify({ typings: "dist/typings.d.ts" }) }; - const m3Typings: File = { name: "/root/m3/dist/typings.d.ts" }; - const m4: File = { name: "/root/node_modules/m4.ts" }; // fallback to node - - const options: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs, baseUrl: "/root" }; - const host = createModuleResolutionHost(hasDirectoryExists, main, m1, m2, m3, m3Typings, m4); - - check("m1", main, m1); - check("m2", main, m2); - check("m3", main, m3Typings); - check("m4", main, m4, /*isExternalLibraryImport*/ true); - - function check(name: string, caller: File, expected: File, isExternalLibraryImport = false) { - const result = resolveModuleName(name, caller.name, options, host); - checkResolvedModule(result.resolvedModule, createResolvedModule(expected.name, isExternalLibraryImport)); + { + const result = resolveModuleName("./file3", file2.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file3.name), []); + } + { + const result = resolveModuleName("/root/folder1/file1", file2.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file1.name), []); } } - }); - - it("classic + baseUrl", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); + } + // add failure tests + }); - function test(hasDirectoryExists: boolean) { - const main: File = { name: "/root/a/b/main.ts" }; - const m1: File = { name: "/root/x/m1.ts" }; // load from base url - const m2: File = { name: "/m2.ts" }; // fallback to classic + it("node + baseUrl", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const main: File = { name: "/root/a/b/main.ts" }; + const m1: File = { name: "/root/m1.ts" }; // load file as module + const m2: File = { name: "/root/m2/index.d.ts" }; // load folder as module + const m3: File = { name: "/root/m3/package.json", content: JSON.stringify({ typings: "dist/typings.d.ts" }) }; + const m3Typings: File = { name: "/root/m3/dist/typings.d.ts" }; + const m4: File = { name: "/root/node_modules/m4.ts" }; // fallback to node + + const options: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs, baseUrl: "/root" }; + const host = createModuleResolutionHost(hasDirectoryExists, main, m1, m2, m3, m3Typings, m4); + + check("m1", main, m1); + check("m2", main, m2); + check("m3", main, m3Typings); + check("m4", main, m4, /*isExternalLibraryImport*/ true); + + function check(name: string, caller: File, expected: File, isExternalLibraryImport = false) { + const result = resolveModuleName(name, caller.name, options, host); + checkResolvedModule(result.resolvedModule, createResolvedModule(expected.name, isExternalLibraryImport)); + } + } + }); - const options: CompilerOptions = { moduleResolution: ModuleResolutionKind.Classic, baseUrl: "/root/x", jsx: JsxEmit.React }; - const host = createModuleResolutionHost(hasDirectoryExists, main, m1, m2); + it("classic + baseUrl", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - check("m1", main, m1); - check("m2", main, m2); + function test(hasDirectoryExists: boolean) { + const main: File = { name: "/root/a/b/main.ts" }; + const m1: File = { name: "/root/x/m1.ts" }; // load from base url + const m2: File = { name: "/m2.ts" }; // fallback to classic - function check(name: string, caller: File, expected: File) { - const result = resolveModuleName(name, caller.name, options, host); - checkResolvedModule(result.resolvedModule, createResolvedModule(expected.name)); - } - } - }); + const options: CompilerOptions = { moduleResolution: ModuleResolutionKind.Classic, baseUrl: "/root/x", jsx: JsxEmit.React }; + const host = createModuleResolutionHost(hasDirectoryExists, main, m1, m2); - it("node + baseUrl + path mappings", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const main: File = { name: "/root/folder1/main.ts" }; - - const file1: File = { name: "/root/folder1/file1.ts" }; - const file2: File = { name: "/root/generated/folder1/file2.ts" }; // load remapped file as module - const file3: File = { name: "/root/generated/folder2/file3/index.d.ts" }; // load folder a module - const file4Typings: File = { name: "/root/generated/folder2/file4/package.json", content: JSON.stringify({ typings: "dist/types.d.ts" }) }; - const file4: File = { name: "/root/generated/folder2/file4/dist/types.d.ts" }; // load file pointed by typings - const file5: File = { name: "/root/someanotherfolder/file5/index.d.ts" }; // load remapped module from folder - const file6: File = { name: "/root/node_modules/file6.ts" }; // fallback to node - const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3, file4, file4Typings, file5, file6); - - const options: CompilerOptions = { - moduleResolution: ModuleResolutionKind.NodeJs, - baseUrl: "/root", - jsx: JsxEmit.React, - paths: { - "*": [ - "*", - "generated/*" - ], - "somefolder/*": [ - "someanotherfolder/*" - ], - "/rooted/*": [ - "generated/*" - ] - } - }; - check("folder1/file1", file1, []); - check("folder1/file2", file2, [ - // first try the '*' - "/root/folder1/file2.ts", - "/root/folder1/file2.tsx", - "/root/folder1/file2.d.ts", - "/root/folder1/file2/package.json", - - "/root/folder1/file2/index.ts", - "/root/folder1/file2/index.tsx", - "/root/folder1/file2/index.d.ts", - // then first attempt on 'generated/*' was successful - ]); - check("/rooted/folder1/file2", file2, []); - check("folder2/file3", file3, [ - // first try '*' - "/root/folder2/file3.ts", - "/root/folder2/file3.tsx", - "/root/folder2/file3.d.ts", - "/root/folder2/file3/package.json", - - "/root/folder2/file3/index.ts", - "/root/folder2/file3/index.tsx", - "/root/folder2/file3/index.d.ts", - - // then use remapped location - "/root/generated/folder2/file3.ts", - "/root/generated/folder2/file3.tsx", - "/root/generated/folder2/file3.d.ts", - "/root/generated/folder2/file3/package.json", - - "/root/generated/folder2/file3/index.ts", - "/root/generated/folder2/file3/index.tsx", - // success on index.d.ts - ]); - check("folder2/file4", file4, [ - // first try '*' - "/root/folder2/file4.ts", - "/root/folder2/file4.tsx", - "/root/folder2/file4.d.ts", - "/root/folder2/file4/package.json", - - "/root/folder2/file4/index.ts", - "/root/folder2/file4/index.tsx", - "/root/folder2/file4/index.d.ts", - - // try to load from file from remapped location - "/root/generated/folder2/file4.ts", - "/root/generated/folder2/file4.tsx", - "/root/generated/folder2/file4.d.ts", - // success on loading as from folder - ]); - check("somefolder/file5", file5, [ - // load from remapped location - // first load from fle - "/root/someanotherfolder/file5.ts", - "/root/someanotherfolder/file5.tsx", - "/root/someanotherfolder/file5.d.ts", - - // load from folder - "/root/someanotherfolder/file5/package.json", - "/root/someanotherfolder/file5/index.ts", - "/root/someanotherfolder/file5/index.tsx", - // success on index.d.ts - ]); - check("file6", file6, [ - // first try * - // load from file - "/root/file6.ts", - "/root/file6.tsx", - "/root/file6.d.ts", - - // load from folder - "/root/file6/package.json", - "/root/file6/index.ts", - "/root/file6/index.tsx", - "/root/file6/index.d.ts", - - // then try 'generated/*' - // load from file - "/root/generated/file6.ts", - "/root/generated/file6.tsx", - "/root/generated/file6.d.ts", - - // load from folder - "/root/generated/file6/package.json", - "/root/generated/file6/index.ts", - "/root/generated/file6/index.tsx", - "/root/generated/file6/index.d.ts", - - // fallback to standard node behavior - "/root/folder1/node_modules/file6/package.json", - - // load from file - "/root/folder1/node_modules/file6.ts", - "/root/folder1/node_modules/file6.tsx", - "/root/folder1/node_modules/file6.d.ts", - - // load from folder - "/root/folder1/node_modules/file6/index.ts", - "/root/folder1/node_modules/file6/index.tsx", - "/root/folder1/node_modules/file6/index.d.ts", - - "/root/folder1/node_modules/@types/file6/package.json", - "/root/folder1/node_modules/@types/file6.d.ts", - "/root/folder1/node_modules/@types/file6/index.d.ts", - - "/root/node_modules/file6/package.json", - // success on /root/node_modules/file6.ts - ], /*isExternalLibraryImport*/ true); - - function check(name: string, expected: File, expectedFailedLookups: string[], isExternalLibraryImport = false) { - const result = resolveModuleName(name, main.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name, isExternalLibraryImport), expectedFailedLookups); - } - } - }); + check("m1", main, m1); + check("m2", main, m2); - it ("classic + baseUrl + path mappings", () => { - // classic mode does not use directoryExists - test(/*hasDirectoryExists*/ false); - - function test(hasDirectoryExists: boolean) { - const main: File = { name: "/root/folder1/main.ts" }; - - const file1: File = { name: "/root/folder1/file1.ts" }; - const file2: File = { name: "/root/generated/folder1/file2.ts" }; - const file3: File = { name: "/folder1/file3.ts" }; // fallback to classic - const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3); - - const options: CompilerOptions = { - moduleResolution: ModuleResolutionKind.Classic, - baseUrl: "/root", - jsx: JsxEmit.React, - paths: { - "*": [ - "*", - "generated/*" - ], - "somefolder/*": [ - "someanotherfolder/*" - ], - "/rooted/*": [ - "generated/*" - ] - } - }; - check("folder1/file1", file1, []); - check("folder1/file2", file2, [ - // first try '*' - "/root/folder1/file2.ts", - "/root/folder1/file2.tsx", - "/root/folder1/file2.d.ts", - // success when using 'generated/*' - ]); - check("/rooted/folder1/file2", file2, []); - check("folder1/file3", file3, [ - // first try '*' - "/root/folder1/file3.ts", - "/root/folder1/file3.tsx", - "/root/folder1/file3.d.ts", - // then try 'generated/*' - "/root/generated/folder1/file3.ts", - "/root/generated/folder1/file3.tsx", - "/root/generated/folder1/file3.d.ts", - // fallback to classic - "/root/folder1/folder1/file3.ts", - "/root/folder1/folder1/file3.tsx", - "/root/folder1/folder1/file3.d.ts", - "/root/folder1/file3.ts", - "/root/folder1/file3.tsx", - "/root/folder1/file3.d.ts", - ]); - - function check(name: string, expected: File, expectedFailedLookups: string[]) { - const result = resolveModuleName(name, main.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups); - } + function check(name: string, caller: File, expected: File) { + const result = resolveModuleName(name, caller.name, options, host); + checkResolvedModule(result.resolvedModule, createResolvedModule(expected.name)); } - }); + } + }); - it ("node + rootDirs", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const file1: File = { name: "/root/folder1/file1.ts" }; - const file1_1: File = { name: "/root/folder1/file1_1/index.d.ts" }; // eslint-disable-line @typescript-eslint/naming-convention - const file2: File = { name: "/root/generated/folder1/file2.ts" }; - const file3: File = { name: "/root/generated/folder2/file3.ts" }; - const host = createModuleResolutionHost(hasDirectoryExists, file1, file1_1, file2, file3); - const options: CompilerOptions = { - moduleResolution: ModuleResolutionKind.NodeJs, - rootDirs: [ - "/root", - "/root/generated/" + it("node + baseUrl + path mappings", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const main: File = { name: "/root/folder1/main.ts" }; + + const file1: File = { name: "/root/folder1/file1.ts" }; + const file2: File = { name: "/root/generated/folder1/file2.ts" }; // load remapped file as module + const file3: File = { name: "/root/generated/folder2/file3/index.d.ts" }; // load folder a module + const file4Typings: File = { name: "/root/generated/folder2/file4/package.json", content: JSON.stringify({ typings: "dist/types.d.ts" }) }; + const file4: File = { name: "/root/generated/folder2/file4/dist/types.d.ts" }; // load file pointed by typings + const file5: File = { name: "/root/someanotherfolder/file5/index.d.ts" }; // load remapped module from folder + const file6: File = { name: "/root/node_modules/file6.ts" }; // fallback to node + const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3, file4, file4Typings, file5, file6); + + const options: CompilerOptions = { + moduleResolution: ModuleResolutionKind.NodeJs, + baseUrl: "/root", + jsx: JsxEmit.React, + paths: { + "*": [ + "*", + "generated/*" + ], + "somefolder/*": [ + "someanotherfolder/*" + ], + "/rooted/*": [ + "generated/*" ] - }; - check("./file2", file1, file2, [ - // first try current location - // load from file - "/root/folder1/file2.ts", - "/root/folder1/file2.tsx", - "/root/folder1/file2.d.ts", - // load from folder - "/root/folder1/file2/package.json", - "/root/folder1/file2/index.ts", - "/root/folder1/file2/index.tsx", - "/root/folder1/file2/index.d.ts", - // success after using alternative rootDir entry - ]); - check("../folder1/file1", file3, file1, [ - // first try current location - // load from file - "/root/generated/folder1/file1.ts", - "/root/generated/folder1/file1.tsx", - "/root/generated/folder1/file1.d.ts", - // load from module - "/root/generated/folder1/file1/package.json", - "/root/generated/folder1/file1/index.ts", - "/root/generated/folder1/file1/index.tsx", - "/root/generated/folder1/file1/index.d.ts", - // success after using alternative rootDir entry - ]); - check("../folder1/file1_1", file3, file1_1, [ - // first try current location - // load from file - "/root/generated/folder1/file1_1.ts", - "/root/generated/folder1/file1_1.tsx", - "/root/generated/folder1/file1_1.d.ts", - // load from folder - "/root/generated/folder1/file1_1/package.json", - "/root/generated/folder1/file1_1/index.ts", - "/root/generated/folder1/file1_1/index.tsx", - "/root/generated/folder1/file1_1/index.d.ts", - // try alternative rootDir entry - // load from file - "/root/folder1/file1_1.ts", - "/root/folder1/file1_1.tsx", - "/root/folder1/file1_1.d.ts", - // load from directory - "/root/folder1/file1_1/package.json", - "/root/folder1/file1_1/index.ts", - "/root/folder1/file1_1/index.tsx", - // success on loading '/root/folder1/file1_1/index.d.ts' - ]); - - function check(name: string, container: File, expected: File, expectedFailedLookups: string[]) { - const result = resolveModuleName(name, container.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups); } + }; + check("folder1/file1", file1, []); + check("folder1/file2", file2, [ + // first try the '*' + "/root/folder1/file2.ts", + "/root/folder1/file2.tsx", + "/root/folder1/file2.d.ts", + "/root/folder1/file2/package.json", + + "/root/folder1/file2/index.ts", + "/root/folder1/file2/index.tsx", + "/root/folder1/file2/index.d.ts", + // then first attempt on 'generated/*' was successful + ]); + check("/rooted/folder1/file2", file2, []); + check("folder2/file3", file3, [ + // first try '*' + "/root/folder2/file3.ts", + "/root/folder2/file3.tsx", + "/root/folder2/file3.d.ts", + "/root/folder2/file3/package.json", + + "/root/folder2/file3/index.ts", + "/root/folder2/file3/index.tsx", + "/root/folder2/file3/index.d.ts", + + // then use remapped location + "/root/generated/folder2/file3.ts", + "/root/generated/folder2/file3.tsx", + "/root/generated/folder2/file3.d.ts", + "/root/generated/folder2/file3/package.json", + + "/root/generated/folder2/file3/index.ts", + "/root/generated/folder2/file3/index.tsx", + // success on index.d.ts + ]); + check("folder2/file4", file4, [ + // first try '*' + "/root/folder2/file4.ts", + "/root/folder2/file4.tsx", + "/root/folder2/file4.d.ts", + "/root/folder2/file4/package.json", + + "/root/folder2/file4/index.ts", + "/root/folder2/file4/index.tsx", + "/root/folder2/file4/index.d.ts", + + // try to load from file from remapped location + "/root/generated/folder2/file4.ts", + "/root/generated/folder2/file4.tsx", + "/root/generated/folder2/file4.d.ts", + // success on loading as from folder + ]); + check("somefolder/file5", file5, [ + // load from remapped location + // first load from fle + "/root/someanotherfolder/file5.ts", + "/root/someanotherfolder/file5.tsx", + "/root/someanotherfolder/file5.d.ts", + + // load from folder + "/root/someanotherfolder/file5/package.json", + "/root/someanotherfolder/file5/index.ts", + "/root/someanotherfolder/file5/index.tsx", + // success on index.d.ts + ]); + check("file6", file6, [ + // first try * + // load from file + "/root/file6.ts", + "/root/file6.tsx", + "/root/file6.d.ts", + + // load from folder + "/root/file6/package.json", + "/root/file6/index.ts", + "/root/file6/index.tsx", + "/root/file6/index.d.ts", + + // then try 'generated/*' + // load from file + "/root/generated/file6.ts", + "/root/generated/file6.tsx", + "/root/generated/file6.d.ts", + + // load from folder + "/root/generated/file6/package.json", + "/root/generated/file6/index.ts", + "/root/generated/file6/index.tsx", + "/root/generated/file6/index.d.ts", + + // fallback to standard node behavior + "/root/folder1/node_modules/file6/package.json", + + // load from file + "/root/folder1/node_modules/file6.ts", + "/root/folder1/node_modules/file6.tsx", + "/root/folder1/node_modules/file6.d.ts", + + // load from folder + "/root/folder1/node_modules/file6/index.ts", + "/root/folder1/node_modules/file6/index.tsx", + "/root/folder1/node_modules/file6/index.d.ts", + + "/root/folder1/node_modules/@types/file6/package.json", + "/root/folder1/node_modules/@types/file6.d.ts", + "/root/folder1/node_modules/@types/file6/index.d.ts", + + "/root/node_modules/file6/package.json", + // success on /root/node_modules/file6.ts + ], /*isExternalLibraryImport*/ true); + + function check(name: string, expected: File, expectedFailedLookups: string[], isExternalLibraryImport = false) { + const result = resolveModuleName(name, main.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name, isExternalLibraryImport), expectedFailedLookups); } - }); + } + }); - it ("classic + rootDirs", () => { - test(/*hasDirectoryExists*/ false); - - function test(hasDirectoryExists: boolean) { - const file1: File = { name: "/root/folder1/file1.ts" }; - const file2: File = { name: "/root/generated/folder1/file2.ts" }; - const file3: File = { name: "/root/generated/folder2/file3.ts" }; - const file4: File = { name: "/folder1/file1_1.ts" }; - const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3, file4); - const options: CompilerOptions = { - moduleResolution: ModuleResolutionKind.Classic, - jsx: JsxEmit.React, - rootDirs: [ - "/root", - "/root/generated/" + it ("classic + baseUrl + path mappings", () => { + // classic mode does not use directoryExists + test(/*hasDirectoryExists*/ false); + + function test(hasDirectoryExists: boolean) { + const main: File = { name: "/root/folder1/main.ts" }; + + const file1: File = { name: "/root/folder1/file1.ts" }; + const file2: File = { name: "/root/generated/folder1/file2.ts" }; + const file3: File = { name: "/folder1/file3.ts" }; // fallback to classic + const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3); + + const options: CompilerOptions = { + moduleResolution: ModuleResolutionKind.Classic, + baseUrl: "/root", + jsx: JsxEmit.React, + paths: { + "*": [ + "*", + "generated/*" + ], + "somefolder/*": [ + "someanotherfolder/*" + ], + "/rooted/*": [ + "generated/*" ] - }; - check("./file2", file1, file2, [ - // first load from current location - "/root/folder1/file2.ts", - "/root/folder1/file2.tsx", - "/root/folder1/file2.d.ts", - // then try alternative rootDir entry - ]); - check("../folder1/file1", file3, file1, [ - // first load from current location - "/root/generated/folder1/file1.ts", - "/root/generated/folder1/file1.tsx", - "/root/generated/folder1/file1.d.ts", - // then try alternative rootDir entry - ]); - check("folder1/file1_1", file3, file4, [ - // current location - "/root/generated/folder2/folder1/file1_1.ts", - "/root/generated/folder2/folder1/file1_1.tsx", - "/root/generated/folder2/folder1/file1_1.d.ts", - // other entry in rootDirs - "/root/generated/folder1/file1_1.ts", - "/root/generated/folder1/file1_1.tsx", - "/root/generated/folder1/file1_1.d.ts", - // fallback - "/root/folder1/file1_1.ts", - "/root/folder1/file1_1.tsx", - "/root/folder1/file1_1.d.ts", - // found one - ]); - - function check(name: string, container: File, expected: File, expectedFailedLookups: string[]) { - const result = resolveModuleName(name, container.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups); } + }; + check("folder1/file1", file1, []); + check("folder1/file2", file2, [ + // first try '*' + "/root/folder1/file2.ts", + "/root/folder1/file2.tsx", + "/root/folder1/file2.d.ts", + // success when using 'generated/*' + ]); + check("/rooted/folder1/file2", file2, []); + check("folder1/file3", file3, [ + // first try '*' + "/root/folder1/file3.ts", + "/root/folder1/file3.tsx", + "/root/folder1/file3.d.ts", + // then try 'generated/*' + "/root/generated/folder1/file3.ts", + "/root/generated/folder1/file3.tsx", + "/root/generated/folder1/file3.d.ts", + // fallback to classic + "/root/folder1/folder1/file3.ts", + "/root/folder1/folder1/file3.tsx", + "/root/folder1/folder1/file3.d.ts", + "/root/folder1/file3.ts", + "/root/folder1/file3.tsx", + "/root/folder1/file3.d.ts", + ]); + + function check(name: string, expected: File, expectedFailedLookups: string[]) { + const result = resolveModuleName(name, main.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups); } - }); + } + }); - it ("nested node module", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const app: File = { name: "/root/src/app.ts" }; - const libsPackage: File = { name: "/root/src/libs/guid/package.json", content: JSON.stringify({ typings: "dist/guid.d.ts" }) }; - const libsTypings: File = { name: "/root/src/libs/guid/dist/guid.d.ts" }; - const host = createModuleResolutionHost(hasDirectoryExists, app, libsPackage, libsTypings); - const options: CompilerOptions = { - moduleResolution: ModuleResolutionKind.NodeJs, - baseUrl: "/root", - paths: { - "libs/guid": [ "src/libs/guid" ] - } - }; - const result = resolveModuleName("libs/guid", app.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(libsTypings.name), [ - // first try to load module as file - "/root/src/libs/guid.ts", - "/root/src/libs/guid.tsx", - "/root/src/libs/guid.d.ts", - ]); + it ("node + rootDirs", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const file1: File = { name: "/root/folder1/file1.ts" }; + const file1_1: File = { name: "/root/folder1/file1_1/index.d.ts" }; // eslint-disable-line @typescript-eslint/naming-convention + const file2: File = { name: "/root/generated/folder1/file2.ts" }; + const file3: File = { name: "/root/generated/folder2/file3.ts" }; + const host = createModuleResolutionHost(hasDirectoryExists, file1, file1_1, file2, file3); + const options: CompilerOptions = { + moduleResolution: ModuleResolutionKind.NodeJs, + rootDirs: [ + "/root", + "/root/generated/" + ] + }; + check("./file2", file1, file2, [ + // first try current location + // load from file + "/root/folder1/file2.ts", + "/root/folder1/file2.tsx", + "/root/folder1/file2.d.ts", + // load from folder + "/root/folder1/file2/package.json", + "/root/folder1/file2/index.ts", + "/root/folder1/file2/index.tsx", + "/root/folder1/file2/index.d.ts", + // success after using alternative rootDir entry + ]); + check("../folder1/file1", file3, file1, [ + // first try current location + // load from file + "/root/generated/folder1/file1.ts", + "/root/generated/folder1/file1.tsx", + "/root/generated/folder1/file1.d.ts", + // load from module + "/root/generated/folder1/file1/package.json", + "/root/generated/folder1/file1/index.ts", + "/root/generated/folder1/file1/index.tsx", + "/root/generated/folder1/file1/index.d.ts", + // success after using alternative rootDir entry + ]); + check("../folder1/file1_1", file3, file1_1, [ + // first try current location + // load from file + "/root/generated/folder1/file1_1.ts", + "/root/generated/folder1/file1_1.tsx", + "/root/generated/folder1/file1_1.d.ts", + // load from folder + "/root/generated/folder1/file1_1/package.json", + "/root/generated/folder1/file1_1/index.ts", + "/root/generated/folder1/file1_1/index.tsx", + "/root/generated/folder1/file1_1/index.d.ts", + // try alternative rootDir entry + // load from file + "/root/folder1/file1_1.ts", + "/root/folder1/file1_1.tsx", + "/root/folder1/file1_1.d.ts", + // load from directory + "/root/folder1/file1_1/package.json", + "/root/folder1/file1_1/index.ts", + "/root/folder1/file1_1/index.tsx", + // success on loading '/root/folder1/file1_1/index.d.ts' + ]); + + function check(name: string, container: File, expected: File, expectedFailedLookups: string[]) { + const result = resolveModuleName(name, container.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups); } - }); + } }); - describe("unittests:: moduleResolution:: ModuleResolutionHost.directoryExists", () => { - it("No 'fileExists' calls if containing directory is missing", () => { - const host: ModuleResolutionHost = { - readFile: notImplemented, - fileExists: notImplemented, - directoryExists: _ => false + it ("classic + rootDirs", () => { + test(/*hasDirectoryExists*/ false); + + function test(hasDirectoryExists: boolean) { + const file1: File = { name: "/root/folder1/file1.ts" }; + const file2: File = { name: "/root/generated/folder1/file2.ts" }; + const file3: File = { name: "/root/generated/folder2/file3.ts" }; + const file4: File = { name: "/folder1/file1_1.ts" }; + const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3, file4); + const options: CompilerOptions = { + moduleResolution: ModuleResolutionKind.Classic, + jsx: JsxEmit.React, + rootDirs: [ + "/root", + "/root/generated/" + ] }; - - const result = resolveModuleName("someName", "/a/b/c/d", { moduleResolution: ModuleResolutionKind.NodeJs }, host); - assert(!result.resolvedModule); - }); + check("./file2", file1, file2, [ + // first load from current location + "/root/folder1/file2.ts", + "/root/folder1/file2.tsx", + "/root/folder1/file2.d.ts", + // then try alternative rootDir entry + ]); + check("../folder1/file1", file3, file1, [ + // first load from current location + "/root/generated/folder1/file1.ts", + "/root/generated/folder1/file1.tsx", + "/root/generated/folder1/file1.d.ts", + // then try alternative rootDir entry + ]); + check("folder1/file1_1", file3, file4, [ + // current location + "/root/generated/folder2/folder1/file1_1.ts", + "/root/generated/folder2/folder1/file1_1.tsx", + "/root/generated/folder2/folder1/file1_1.d.ts", + // other entry in rootDirs + "/root/generated/folder1/file1_1.ts", + "/root/generated/folder1/file1_1.tsx", + "/root/generated/folder1/file1_1.d.ts", + // fallback + "/root/folder1/file1_1.ts", + "/root/folder1/file1_1.tsx", + "/root/folder1/file1_1.d.ts", + // found one + ]); + + function check(name: string, container: File, expected: File, expectedFailedLookups: string[]) { + const result = resolveModuleName(name, container.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups); + } + } }); - describe("unittests:: moduleResolution:: Type reference directive resolution: ", () => { - function testWorker(hasDirectoryExists: boolean, typesRoot: string | undefined, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) { - const host = createModuleResolutionHost(hasDirectoryExists, ...[initialFile, targetFile].concat(...otherFiles)); - const result = resolveTypeReferenceDirective(typeDirective, initialFile.name, typesRoot ? { typeRoots: [typesRoot] } : {}, host); - assert(result.resolvedTypeReferenceDirective!.resolvedFileName !== undefined, "expected type directive to be resolved"); - assert.equal(result.resolvedTypeReferenceDirective!.resolvedFileName, targetFile.name, "unexpected result of type reference resolution"); - assert.equal(result.resolvedTypeReferenceDirective!.primary, primary, "unexpected 'primary' value"); + it ("nested node module", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const app: File = { name: "/root/src/app.ts" }; + const libsPackage: File = { name: "/root/src/libs/guid/package.json", content: JSON.stringify({ typings: "dist/guid.d.ts" }) }; + const libsTypings: File = { name: "/root/src/libs/guid/dist/guid.d.ts" }; + const host = createModuleResolutionHost(hasDirectoryExists, app, libsPackage, libsTypings); + const options: CompilerOptions = { + moduleResolution: ModuleResolutionKind.NodeJs, + baseUrl: "/root", + paths: { + "libs/guid": [ "src/libs/guid" ] + } + }; + const result = resolveModuleName("libs/guid", app.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(libsTypings.name), [ + // first try to load module as file + "/root/src/libs/guid.ts", + "/root/src/libs/guid.tsx", + "/root/src/libs/guid.d.ts", + ]); } + }); +}); + +describe("unittests:: moduleResolution:: ModuleResolutionHost.directoryExists", () => { + it("No 'fileExists' calls if containing directory is missing", () => { + const host: ModuleResolutionHost = { + readFile: notImplemented, + fileExists: notImplemented, + directoryExists: _ => false + }; + + const result = resolveModuleName("someName", "/a/b/c/d", { moduleResolution: ModuleResolutionKind.NodeJs }, host); + assert(!result.resolvedModule); + }); +}); + +describe("unittests:: moduleResolution:: Type reference directive resolution: ", () => { + function testWorker(hasDirectoryExists: boolean, typesRoot: string | undefined, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) { + const host = createModuleResolutionHost(hasDirectoryExists, ...[initialFile, targetFile].concat(...otherFiles)); + const result = resolveTypeReferenceDirective(typeDirective, initialFile.name, typesRoot ? { typeRoots: [typesRoot] } : {}, host); + assert(result.resolvedTypeReferenceDirective!.resolvedFileName !== undefined, "expected type directive to be resolved"); + assert.equal(result.resolvedTypeReferenceDirective!.resolvedFileName, targetFile.name, "unexpected result of type reference resolution"); + assert.equal(result.resolvedTypeReferenceDirective!.primary, primary, "unexpected 'primary' value"); + } - function test(typesRoot: string, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) { - testWorker(/*hasDirectoryExists*/ false, typesRoot, typeDirective, primary, initialFile, targetFile, ...otherFiles); - } + function test(typesRoot: string, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) { + testWorker(/*hasDirectoryExists*/ false, typesRoot, typeDirective, primary, initialFile, targetFile, ...otherFiles); + } - it("Can be resolved from primary location", () => { - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/src/types/lib/index.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/src/types/lib/typings/lib.d.ts" }; - const packageFile = { name: "/root/src/types/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2, packageFile); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/src/node_modules/lib/index.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/src/node_modules/lib/typings/lib.d.ts" }; - const packageFile = { name: "/root/src/node_modules/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/src/node_modules/@types/lib/index.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/src/node_modules/@types/lib/typings/lib.d.ts" }; - const packageFile = { name: "/root/src/node_modules/@types/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); - } - }); - it("Can be resolved from secondary location", () => { - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/node_modules/lib.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/node_modules/lib/index.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/node_modules/lib/typings/lib.d.ts" }; - const packageFile = { name: "/root/node_modules/lib/package.json", content: JSON.stringify({ typings: "typings/lib.d.ts" }) }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/node_modules/@types/lib/index.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/node_modules/@types/lib/typings/lib.d.ts" }; - const packageFile = { name: "/root/node_modules/@types/lib/package.json", content: JSON.stringify({ typings: "typings/lib.d.ts" }) }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); - } - }); - it("Primary resolution overrides secondary resolutions", () => { - { - const f1 = { name: "/root/src/a/b/c/app.ts" }; - const f2 = { name: "/root/src/types/lib/index.d.ts" }; - const f3 = { name: "/root/src/a/b/node_modules/lib.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2, f3); - } - }); - it("Reused program keeps errors", () => { - const f1 = { name: "/root/src/a/b/c/d/e/app.ts", content: `/// ` }; - const f2 = { name: "/root/src/a/b/c/d/node_modules/lib/index.d.ts", content: `declare var x: number;` }; - const f3 = { name: "/root/src/a/b/c/d/f/g/app.ts", content: `/// ` }; - const f4 = { name: "/root/src/a/b/c/d/f/node_modules/lib/index.d.ts", content: `declare var x: number;` }; - const files = [f1, f2, f3, f4]; - - const names = map(files, f => f.name); - const sourceFiles = arrayToMap(map(files, f => createSourceFile(f.name, f.content, ScriptTarget.ES2015)), f => f.fileName); - const compilerHost: CompilerHost = { - fileExists: fileName => sourceFiles.has(fileName), - getSourceFile: fileName => sourceFiles.get(fileName), - getDefaultLibFileName: () => "lib.d.ts", - writeFile: notImplemented, - getCurrentDirectory: () => "/", - getDirectories: () => [], - getCanonicalFileName: f => f.toLowerCase(), - getNewLine: () => "\r\n", - useCaseSensitiveFileNames: () => false, - readFile: fileName => { - const file = sourceFiles.get(fileName); - return file && file.text; - }, - }; - const program1 = createProgram(names, {}, compilerHost); - const diagnostics1 = program1.getOptionsDiagnostics(); - assert.equal(diagnostics1.length, 1, "expected one diagnostic"); - - const program2 = createProgram(names, {}, compilerHost, program1); - assert.isTrue(program2.structureIsReused === StructureIsReused.Completely); - const diagnostics2 = program2.getOptionsDiagnostics(); - assert.equal(diagnostics2.length, 1, "expected one diagnostic"); - assert.deepEqual(diagnostics1[0].messageText, diagnostics2[0].messageText, "expected one diagnostic"); - }); + it("Can be resolved from primary location", () => { + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/src/types/lib/index.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/src/types/lib/typings/lib.d.ts" }; + const packageFile = { name: "/root/src/types/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2, packageFile); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/src/node_modules/lib/index.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/src/node_modules/lib/typings/lib.d.ts" }; + const packageFile = { name: "/root/src/node_modules/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/src/node_modules/@types/lib/index.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/src/node_modules/@types/lib/typings/lib.d.ts" }; + const packageFile = { name: "/root/src/node_modules/@types/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); + } + }); + it("Can be resolved from secondary location", () => { + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/node_modules/lib.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/node_modules/lib/index.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/node_modules/lib/typings/lib.d.ts" }; + const packageFile = { name: "/root/node_modules/lib/package.json", content: JSON.stringify({ typings: "typings/lib.d.ts" }) }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/node_modules/@types/lib/index.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/node_modules/@types/lib/typings/lib.d.ts" }; + const packageFile = { name: "/root/node_modules/@types/lib/package.json", content: JSON.stringify({ typings: "typings/lib.d.ts" }) }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); + } + }); + it("Primary resolution overrides secondary resolutions", () => { + { + const f1 = { name: "/root/src/a/b/c/app.ts" }; + const f2 = { name: "/root/src/types/lib/index.d.ts" }; + const f3 = { name: "/root/src/a/b/node_modules/lib.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2, f3); + } + }); + it("Reused program keeps errors", () => { + const f1 = { name: "/root/src/a/b/c/d/e/app.ts", content: `/// ` }; + const f2 = { name: "/root/src/a/b/c/d/node_modules/lib/index.d.ts", content: `declare var x: number;` }; + const f3 = { name: "/root/src/a/b/c/d/f/g/app.ts", content: `/// ` }; + const f4 = { name: "/root/src/a/b/c/d/f/node_modules/lib/index.d.ts", content: `declare var x: number;` }; + const files = [f1, f2, f3, f4]; + + const names = map(files, f => f.name); + const sourceFiles = arrayToMap(map(files, f => createSourceFile(f.name, f.content, ScriptTarget.ES2015)), f => f.fileName); + const compilerHost: CompilerHost = { + fileExists: fileName => sourceFiles.has(fileName), + getSourceFile: fileName => sourceFiles.get(fileName), + getDefaultLibFileName: () => "lib.d.ts", + writeFile: notImplemented, + getCurrentDirectory: () => "/", + getDirectories: () => [], + getCanonicalFileName: f => f.toLowerCase(), + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => false, + readFile: fileName => { + const file = sourceFiles.get(fileName); + return file && file.text; + }, + }; + const program1 = createProgram(names, {}, compilerHost); + const diagnostics1 = program1.getOptionsDiagnostics(); + assert.equal(diagnostics1.length, 1, "expected one diagnostic"); + + const program2 = createProgram(names, {}, compilerHost, program1); + assert.isTrue(program2.structureIsReused === StructureIsReused.Completely); + const diagnostics2 = program2.getOptionsDiagnostics(); + assert.equal(diagnostics2.length, 1, "expected one diagnostic"); + assert.deepEqual(diagnostics1[0].messageText, diagnostics2[0].messageText, "expected one diagnostic"); + }); - it("Modules in the same .d.ts file are preferred to external files", () => { - const f = { - name: "/a/b/c/c/app.d.ts", - content: ` + it("Modules in the same .d.ts file are preferred to external files", () => { + const f = { + name: "/a/b/c/c/app.d.ts", + content: ` declare module "fs" { export interface Stat { id: number } } @@ -1501,28 +1501,28 @@ import b = require("./moduleB"); import { Stat } from "fs"; export function foo(): Stat; }` - }; - const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015); - const compilerHost: CompilerHost = { - fileExists: fileName => fileName === file.fileName, - getSourceFile: fileName => fileName === file.fileName ? file : undefined, - getDefaultLibFileName: () => "lib.d.ts", - writeFile: notImplemented, - getCurrentDirectory: () => "/", - getDirectories: () => [], - getCanonicalFileName: f => f.toLowerCase(), - getNewLine: () => "\r\n", - useCaseSensitiveFileNames: () => false, - readFile: fileName => fileName === file.fileName ? file.text : undefined, - resolveModuleNames: notImplemented, - }; - createProgram([f.name], {}, compilerHost); - }); + }; + const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015); + const compilerHost: CompilerHost = { + fileExists: fileName => fileName === file.fileName, + getSourceFile: fileName => fileName === file.fileName ? file : undefined, + getDefaultLibFileName: () => "lib.d.ts", + writeFile: notImplemented, + getCurrentDirectory: () => "/", + getDirectories: () => [], + getCanonicalFileName: f => f.toLowerCase(), + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => false, + readFile: fileName => fileName === file.fileName ? file.text : undefined, + resolveModuleNames: notImplemented, + }; + createProgram([f.name], {}, compilerHost); + }); - it("Modules in .ts file are not checked in the same file", () => { - const f = { - name: "/a/b/c/c/app.ts", - content: ` + it("Modules in .ts file are not checked in the same file", () => { + const f = { + name: "/a/b/c/c/app.ts", + content: ` declare module "fs" { export interface Stat { id: number } } @@ -1530,35 +1530,35 @@ import b = require("./moduleB"); import { Stat } from "fs"; export function foo(): Stat; }` - }; - const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015); - const compilerHost: CompilerHost = { - fileExists: fileName => fileName === file.fileName, - getSourceFile: fileName => fileName === file.fileName ? file : undefined, - getDefaultLibFileName: () => "lib.d.ts", - writeFile: notImplemented, - getCurrentDirectory: () => "/", - getDirectories: () => [], - getCanonicalFileName: f => f.toLowerCase(), - getNewLine: () => "\r\n", - useCaseSensitiveFileNames: () => false, - readFile: fileName => fileName === file.fileName ? file.text : undefined, - resolveModuleNames(moduleNames: string[], _containingFile: string) { - assert.deepEqual(moduleNames, ["fs"]); - return [undefined!]; // TODO: GH#18217 - } - }; - createProgram([f.name], {}, compilerHost); + }; + const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015); + const compilerHost: CompilerHost = { + fileExists: fileName => fileName === file.fileName, + getSourceFile: fileName => fileName === file.fileName ? file : undefined, + getDefaultLibFileName: () => "lib.d.ts", + writeFile: notImplemented, + getCurrentDirectory: () => "/", + getDirectories: () => [], + getCanonicalFileName: f => f.toLowerCase(), + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => false, + readFile: fileName => fileName === file.fileName ? file.text : undefined, + resolveModuleNames(moduleNames: string[], _containingFile: string) { + assert.deepEqual(moduleNames, ["fs"]); + return [undefined!]; // TODO: GH#18217 + } + }; + createProgram([f.name], {}, compilerHost); + }); + describe("can be resolved when typeReferenceDirective is relative and in a sibling folder", () => { + const initialFile = { name: "/root/src/background/app.ts" }; + const targetFile = { name: "/root/src/typedefs/filesystem.d.ts" }; + it("when host doesnt have directoryExists", () => { + testWorker(/*hasDirectoryExists*/ false, /*typesRoot*/ undefined, /*typeDirective*/ "../typedefs/filesystem", /*primary*/ false, initialFile, targetFile); }); - describe("can be resolved when typeReferenceDirective is relative and in a sibling folder", () => { - const initialFile = { name: "/root/src/background/app.ts" }; - const targetFile = { name: "/root/src/typedefs/filesystem.d.ts" }; - it("when host doesnt have directoryExists", () => { - testWorker(/*hasDirectoryExists*/ false, /*typesRoot*/ undefined, /*typeDirective*/ "../typedefs/filesystem", /*primary*/ false, initialFile, targetFile); - }); - it("when host has directoryExists", () => { - testWorker(/*hasDirectoryExists*/ true, /*typesRoot*/ undefined, /*typeDirective*/ "../typedefs/filesystem", /*primary*/ false, initialFile, targetFile); - }); + it("when host has directoryExists", () => { + testWorker(/*hasDirectoryExists*/ true, /*typesRoot*/ undefined, /*typeDirective*/ "../typedefs/filesystem", /*primary*/ false, initialFile, targetFile); }); }); +}); } diff --git a/src/testRunner/unittests/parsePseudoBigInt.ts b/src/testRunner/unittests/parsePseudoBigInt.ts index db1a841dc2b74..5459637d42902 100644 --- a/src/testRunner/unittests/parsePseudoBigInt.ts +++ b/src/testRunner/unittests/parsePseudoBigInt.ts @@ -1,71 +1,71 @@ namespace ts { - describe("unittests:: BigInt literal base conversions", () => { - describe("parsePseudoBigInt", () => { - const testNumbers: number[] = []; - for (let i = 0; i < 1e3; i++) testNumbers.push(i); - for (let bits = 0; bits <= 52; bits++) { - testNumbers.push(2 ** bits, 2 ** bits - 1); - } - it("can strip base-10 strings", () => { - for (const testNumber of testNumbers) { - for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { - assert.equal( - parsePseudoBigInt("0".repeat(leadingZeros) + testNumber + "n"), - String(testNumber) - ); - } +describe("unittests:: BigInt literal base conversions", () => { + describe("parsePseudoBigInt", () => { + const testNumbers: number[] = []; + for (let i = 0; i < 1e3; i++) testNumbers.push(i); + for (let bits = 0; bits <= 52; bits++) { + testNumbers.push(2 ** bits, 2 ** bits - 1); + } + it("can strip base-10 strings", () => { + for (const testNumber of testNumbers) { + for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { + assert.equal( + parsePseudoBigInt("0".repeat(leadingZeros) + testNumber + "n"), + String(testNumber) + ); } - }); - it("can parse binary literals", () => { - for (const testNumber of testNumbers) { - for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { - const binary = "0".repeat(leadingZeros) + testNumber.toString(2) + "n"; - for (const prefix of ["0b", "0B"]) { - assert.equal(parsePseudoBigInt(prefix + binary), String(testNumber)); - } + } + }); + it("can parse binary literals", () => { + for (const testNumber of testNumbers) { + for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { + const binary = "0".repeat(leadingZeros) + testNumber.toString(2) + "n"; + for (const prefix of ["0b", "0B"]) { + assert.equal(parsePseudoBigInt(prefix + binary), String(testNumber)); } } - }); - it("can parse octal literals", () => { - for (const testNumber of testNumbers) { - for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { - const octal = "0".repeat(leadingZeros) + testNumber.toString(8) + "n"; - for (const prefix of ["0o", "0O"]) { - assert.equal(parsePseudoBigInt(prefix + octal), String(testNumber)); - } + } + }); + it("can parse octal literals", () => { + for (const testNumber of testNumbers) { + for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { + const octal = "0".repeat(leadingZeros) + testNumber.toString(8) + "n"; + for (const prefix of ["0o", "0O"]) { + assert.equal(parsePseudoBigInt(prefix + octal), String(testNumber)); } } - }); - it("can parse hex literals", () => { - for (const testNumber of testNumbers) { - for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { - const hex = "0".repeat(leadingZeros) + testNumber.toString(16) + "n"; - for (const prefix of ["0x", "0X"]) { - for (const hexCase of [hex.toLowerCase(), hex.toUpperCase()]) { - assert.equal(parsePseudoBigInt(prefix + hexCase), String(testNumber)); - } + } + }); + it("can parse hex literals", () => { + for (const testNumber of testNumbers) { + for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { + const hex = "0".repeat(leadingZeros) + testNumber.toString(16) + "n"; + for (const prefix of ["0x", "0X"]) { + for (const hexCase of [hex.toLowerCase(), hex.toUpperCase()]) { + assert.equal(parsePseudoBigInt(prefix + hexCase), String(testNumber)); } } } - }); - it("can parse large literals", () => { - assert.equal( - parsePseudoBigInt("123456789012345678901234567890n"), - "123456789012345678901234567890" - ); - assert.equal( - parsePseudoBigInt("0b1100011101110100100001111111101101100001101110011111000001110111001001110001111110000101011010010n"), - "123456789012345678901234567890" - ); - assert.equal( - parsePseudoBigInt("0o143564417755415637016711617605322n"), - "123456789012345678901234567890" - ); - assert.equal( - parsePseudoBigInt("0x18ee90ff6c373e0ee4e3f0ad2n"), - "123456789012345678901234567890" - ); - }); + } + }); + it("can parse large literals", () => { + assert.equal( + parsePseudoBigInt("123456789012345678901234567890n"), + "123456789012345678901234567890" + ); + assert.equal( + parsePseudoBigInt("0b1100011101110100100001111111101101100001101110011111000001110111001001110001111110000101011010010n"), + "123456789012345678901234567890" + ); + assert.equal( + parsePseudoBigInt("0o143564417755415637016711617605322n"), + "123456789012345678901234567890" + ); + assert.equal( + parsePseudoBigInt("0x18ee90ff6c373e0ee4e3f0ad2n"), + "123456789012345678901234567890" + ); }); }); +}); } diff --git a/src/testRunner/unittests/printer.ts b/src/testRunner/unittests/printer.ts index 50c776c0f9f6e..10d91d785697a 100644 --- a/src/testRunner/unittests/printer.ts +++ b/src/testRunner/unittests/printer.ts @@ -1,21 +1,21 @@ namespace ts { - describe("unittests:: PrinterAPI", () => { - function makePrintsCorrectly(prefix: string) { - return function printsCorrectly(name: string, options: PrinterOptions, printCallback: (printer: Printer) => string) { - it(name, () => { - Harness.Baseline.runBaseline(`printerApi/${prefix}.${name}.js`, - printCallback(createPrinter({ newLine: NewLineKind.CarriageReturnLineFeed, ...options }))); - }); - }; - } +describe("unittests:: PrinterAPI", () => { + function makePrintsCorrectly(prefix: string) { + return function printsCorrectly(name: string, options: PrinterOptions, printCallback: (printer: Printer) => string) { + it(name, () => { + Harness.Baseline.runBaseline(`printerApi/${prefix}.${name}.js`, + printCallback(createPrinter({ newLine: NewLineKind.CarriageReturnLineFeed, ...options }))); + }); + }; + } - describe("printFile", () => { - const printsCorrectly = makePrintsCorrectly("printsFileCorrectly"); - describe("comment handling", () => { - // Avoid eagerly creating the sourceFile so that `createSourceFile` doesn't run unless one of these tests is run. - let sourceFile: SourceFile; - before(() => { - sourceFile = createSourceFile("source.ts", ` + describe("printFile", () => { + const printsCorrectly = makePrintsCorrectly("printsFileCorrectly"); + describe("comment handling", () => { + // Avoid eagerly creating the sourceFile so that `createSourceFile` doesn't run unless one of these tests is run. + let sourceFile: SourceFile; + before(() => { + sourceFile = createSourceFile("source.ts", ` interface A { // comment1 readonly prop?: T; @@ -50,278 +50,278 @@ namespace ts { // comment10 function functionWithDefaultArgValue(argument: string = "defaultValue"): void { } `, ScriptTarget.ES2015); - }); - printsCorrectly("default", {}, printer => printer.printFile(sourceFile)); - printsCorrectly("removeComments", { removeComments: true }, printer => printer.printFile(sourceFile)); }); + printsCorrectly("default", {}, printer => printer.printFile(sourceFile)); + printsCorrectly("removeComments", { removeComments: true }, printer => printer.printFile(sourceFile)); + }); - // https://github.com/microsoft/TypeScript/issues/14948 - // eslint-disable-next-line no-template-curly-in-string - printsCorrectly("templateLiteral", {}, printer => printer.printFile(createSourceFile("source.ts", "let greeting = `Hi ${name}, how are you?`;", ScriptTarget.ES2017))); + // https://github.com/microsoft/TypeScript/issues/14948 + // eslint-disable-next-line no-template-curly-in-string + printsCorrectly("templateLiteral", {}, printer => printer.printFile(createSourceFile("source.ts", "let greeting = `Hi ${name}, how are you?`;", ScriptTarget.ES2017))); - // https://github.com/microsoft/TypeScript/issues/18071 - printsCorrectly("regularExpressionLiteral", {}, printer => printer.printFile(createSourceFile("source.ts", "let regex = /abc/;", ScriptTarget.ES2017))); + // https://github.com/microsoft/TypeScript/issues/18071 + printsCorrectly("regularExpressionLiteral", {}, printer => printer.printFile(createSourceFile("source.ts", "let regex = /abc/;", ScriptTarget.ES2017))); - // https://github.com/microsoft/TypeScript/issues/22239 - printsCorrectly("importStatementRemoveComments", { removeComments: true }, printer => printer.printFile(createSourceFile("source.ts", "import {foo} from 'foo';", ScriptTarget.ESNext))); - printsCorrectly("classHeritageClauses", {}, printer => printer.printFile(createSourceFile( - "source.ts", - `class A extends B implements C implements D {}`, - ScriptTarget.ES2017 - ))); + // https://github.com/microsoft/TypeScript/issues/22239 + printsCorrectly("importStatementRemoveComments", { removeComments: true }, printer => printer.printFile(createSourceFile("source.ts", "import {foo} from 'foo';", ScriptTarget.ESNext))); + printsCorrectly("classHeritageClauses", {}, printer => printer.printFile(createSourceFile( + "source.ts", + `class A extends B implements C implements D {}`, + ScriptTarget.ES2017 + ))); - // https://github.com/microsoft/TypeScript/issues/35093 - printsCorrectly("definiteAssignmentAssertions", {}, printer => printer.printFile(createSourceFile( - "source.ts", - `class A { + // https://github.com/microsoft/TypeScript/issues/35093 + printsCorrectly("definiteAssignmentAssertions", {}, printer => printer.printFile(createSourceFile( + "source.ts", + `class A { prop!: string; } let x!: string;`, - ScriptTarget.ES2017 - ))); + ScriptTarget.ES2017 + ))); - // https://github.com/microsoft/TypeScript/issues/35054 - printsCorrectly("jsx attribute escaping", {}, printer => { - return printer.printFile(createSourceFile( - "source.ts", - String.raw``, - ScriptTarget.ESNext, - /*setParentNodes*/ undefined, - ScriptKind.TSX - )); - }); + // https://github.com/microsoft/TypeScript/issues/35054 + printsCorrectly("jsx attribute escaping", {}, printer => { + return printer.printFile(createSourceFile( + "source.ts", + String.raw``, + ScriptTarget.ESNext, + /*setParentNodes*/ undefined, + ScriptKind.TSX + )); }); + }); - describe("No duplicate ref directives when emiting .d.ts->.d.ts", () => { - it("without statements", () => { - const host = new fakes.CompilerHost(new vfs.FileSystem(true, { - files: { - "/test.d.ts": `/// \n/// { - const host = new fakes.CompilerHost(new vfs.FileSystem(true, { - files: { - "/test.d.ts": `/// \n/// .d.ts", () => { + it("without statements", () => { + const host = new fakes.CompilerHost(new vfs.FileSystem(true, { + files: { + "/test.d.ts": `/// \n/// { + const host = new fakes.CompilerHost(new vfs.FileSystem(true, { + files: { + "/test.d.ts": `/// \n/// { - const printsCorrectly = makePrintsCorrectly("printsBundleCorrectly"); - let bundle: Bundle; - before(() => { - bundle = factory.createBundle([ - createSourceFile("a.ts", ` + describe("printBundle", () => { + const printsCorrectly = makePrintsCorrectly("printsBundleCorrectly"); + let bundle: Bundle; + before(() => { + bundle = factory.createBundle([ + createSourceFile("a.ts", ` /*! [a.ts] */ // comment0 const a = 1; `, ScriptTarget.ES2015), - createSourceFile("b.ts", ` + createSourceFile("b.ts", ` /*! [b.ts] */ // comment1 const b = 2; `, ScriptTarget.ES2015) - ]); - }); - printsCorrectly("default", {}, printer => printer.printBundle(bundle)); - printsCorrectly("removeComments", { removeComments: true }, printer => printer.printBundle(bundle)); + ]); }); + printsCorrectly("default", {}, printer => printer.printBundle(bundle)); + printsCorrectly("removeComments", { removeComments: true }, printer => printer.printBundle(bundle)); + }); - describe("printNode", () => { - const printsCorrectly = makePrintsCorrectly("printsNodeCorrectly"); - printsCorrectly("class", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createClassDeclaration( + describe("printNode", () => { + const printsCorrectly = makePrintsCorrectly("printsNodeCorrectly"); + printsCorrectly("class", {}, printer => printer.printNode( + EmitHint.Unspecified, + factory.createClassDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*name*/ factory.createIdentifier("C"), + /*typeParameters*/ undefined, + /*heritageClauses*/ undefined, + [factory.createPropertyDeclaration( /*decorators*/ undefined, - /*modifiers*/ undefined, - /*name*/ factory.createIdentifier("C"), - /*typeParameters*/ undefined, - /*heritageClauses*/ undefined, - [factory.createPropertyDeclaration( - /*decorators*/ undefined, - factory.createNodeArray([factory.createToken(SyntaxKind.PublicKeyword)]), - factory.createIdentifier("prop"), - /*questionToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined - )] - ), - createSourceFile("source.ts", "", ScriptTarget.ES2015) - )); + factory.createNodeArray([factory.createToken(SyntaxKind.PublicKeyword)]), + factory.createIdentifier("prop"), + /*questionToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + )] + ), + createSourceFile("source.ts", "", ScriptTarget.ES2015) + )); - printsCorrectly("namespaceExportDeclaration", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createNamespaceExportDeclaration("B"), - createSourceFile("source.ts", "", ScriptTarget.ES2015) - )); + printsCorrectly("namespaceExportDeclaration", {}, printer => printer.printNode( + EmitHint.Unspecified, + factory.createNamespaceExportDeclaration("B"), + createSourceFile("source.ts", "", ScriptTarget.ES2015) + )); - printsCorrectly("newExpressionWithPropertyAccessOnCallExpression", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createNewExpression( - factory.createPropertyAccessExpression( - factory.createCallExpression(factory.createIdentifier("f"), /*typeArguments*/ undefined, /*argumentsArray*/ undefined), - "x"), - /*typeArguments*/ undefined, - /*argumentsArray*/ undefined - ), - createSourceFile("source.ts", "", ScriptTarget.ESNext)) - ); + printsCorrectly("newExpressionWithPropertyAccessOnCallExpression", {}, printer => printer.printNode( + EmitHint.Unspecified, + factory.createNewExpression( + factory.createPropertyAccessExpression( + factory.createCallExpression(factory.createIdentifier("f"), /*typeArguments*/ undefined, /*argumentsArray*/ undefined), + "x"), + /*typeArguments*/ undefined, + /*argumentsArray*/ undefined + ), + createSourceFile("source.ts", "", ScriptTarget.ESNext)) + ); - printsCorrectly("newExpressionOnConditionalExpression", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createNewExpression( - factory.createConditionalExpression( - factory.createIdentifier("x"), factory.createToken(SyntaxKind.QuestionToken), - factory.createIdentifier("y"), factory.createToken(SyntaxKind.ColonToken), - factory.createIdentifier("z")), - /*typeArguments*/ undefined, - /*argumentsArray*/ undefined - ), - createSourceFile("source.ts", "", ScriptTarget.ESNext)) - ); + printsCorrectly("newExpressionOnConditionalExpression", {}, printer => printer.printNode( + EmitHint.Unspecified, + factory.createNewExpression( + factory.createConditionalExpression( + factory.createIdentifier("x"), factory.createToken(SyntaxKind.QuestionToken), + factory.createIdentifier("y"), factory.createToken(SyntaxKind.ColonToken), + factory.createIdentifier("z")), + /*typeArguments*/ undefined, + /*argumentsArray*/ undefined + ), + createSourceFile("source.ts", "", ScriptTarget.ESNext)) + ); - printsCorrectly("emptyGlobalAugmentation", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createModuleDeclaration( - /*decorators*/ undefined, - /*modifiers*/ [factory.createToken(SyntaxKind.DeclareKeyword)], - factory.createIdentifier("global"), - factory.createModuleBlock(emptyArray), - NodeFlags.GlobalAugmentation), - createSourceFile("source.ts", "", ScriptTarget.ES2015) - )); + printsCorrectly("emptyGlobalAugmentation", {}, printer => printer.printNode( + EmitHint.Unspecified, + factory.createModuleDeclaration( + /*decorators*/ undefined, + /*modifiers*/ [factory.createToken(SyntaxKind.DeclareKeyword)], + factory.createIdentifier("global"), + factory.createModuleBlock(emptyArray), + NodeFlags.GlobalAugmentation), + createSourceFile("source.ts", "", ScriptTarget.ES2015) + )); - printsCorrectly("emptyGlobalAugmentationWithNoDeclareKeyword", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createModuleDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - factory.createIdentifier("global"), - factory.createModuleBlock(emptyArray), - NodeFlags.GlobalAugmentation), - createSourceFile("source.ts", "", ScriptTarget.ES2015) - )); + printsCorrectly("emptyGlobalAugmentationWithNoDeclareKeyword", {}, printer => printer.printNode( + EmitHint.Unspecified, + factory.createModuleDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createIdentifier("global"), + factory.createModuleBlock(emptyArray), + NodeFlags.GlobalAugmentation), + createSourceFile("source.ts", "", ScriptTarget.ES2015) + )); - // https://github.com/Microsoft/TypeScript/issues/15971 - printsCorrectly("classWithOptionalMethodAndProperty", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createClassDeclaration( - /*decorators*/ undefined, - /*modifiers*/ [factory.createToken(SyntaxKind.DeclareKeyword)], - /*name*/ factory.createIdentifier("X"), - /*typeParameters*/ undefined, - /*heritageClauses*/ undefined, - [ - factory.createMethodDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - /*name*/ factory.createIdentifier("method"), - /*questionToken*/ factory.createToken(SyntaxKind.QuestionToken), - /*typeParameters*/ undefined, - [], - /*type*/ factory.createKeywordTypeNode(SyntaxKind.VoidKeyword), - /*body*/ undefined - ), - factory.createPropertyDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*name*/ factory.createIdentifier("property"), - /*questionToken*/ factory.createToken(SyntaxKind.QuestionToken), - /*type*/ factory.createKeywordTypeNode(SyntaxKind.StringKeyword), - /*initializer*/ undefined - ), - ] - ), - createSourceFile("source.ts", "", ScriptTarget.ES2015) - )); - - // https://github.com/Microsoft/TypeScript/issues/15651 - printsCorrectly("functionTypes", {}, printer => printer.printNode( - EmitHint.Unspecified, - setEmitFlags(factory.createTupleTypeNode([ - factory.createFunctionTypeNode( - /*typeArguments*/ undefined, - [factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - factory.createIdentifier("args") - )], - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ), - factory.createFunctionTypeNode( - [factory.createTypeParameterDeclaration("T")], - [factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - factory.createIdentifier("args") - )], - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ), - factory.createFunctionTypeNode( - /*typeArguments*/ undefined, - [factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - factory.createToken(SyntaxKind.DotDotDotToken), - factory.createIdentifier("args") - )], - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ), - factory.createFunctionTypeNode( - /*typeArguments*/ undefined, - [factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - factory.createIdentifier("args"), - factory.createToken(SyntaxKind.QuestionToken) - )], - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + // https://github.com/Microsoft/TypeScript/issues/15971 + printsCorrectly("classWithOptionalMethodAndProperty", {}, printer => printer.printNode( + EmitHint.Unspecified, + factory.createClassDeclaration( + /*decorators*/ undefined, + /*modifiers*/ [factory.createToken(SyntaxKind.DeclareKeyword)], + /*name*/ factory.createIdentifier("X"), + /*typeParameters*/ undefined, + /*heritageClauses*/ undefined, + [ + factory.createMethodDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + /*name*/ factory.createIdentifier("method"), + /*questionToken*/ factory.createToken(SyntaxKind.QuestionToken), + /*typeParameters*/ undefined, + [], + /*type*/ factory.createKeywordTypeNode(SyntaxKind.VoidKeyword), + /*body*/ undefined ), - factory.createFunctionTypeNode( - /*typeArguments*/ undefined, - [factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - factory.createIdentifier("args"), - /*questionToken*/ undefined, - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - )], - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + factory.createPropertyDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*name*/ factory.createIdentifier("property"), + /*questionToken*/ factory.createToken(SyntaxKind.QuestionToken), + /*type*/ factory.createKeywordTypeNode(SyntaxKind.StringKeyword), + /*initializer*/ undefined ), - factory.createFunctionTypeNode( - /*typeArguments*/ undefined, - [factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - factory.createObjectBindingPattern([]) - )], + ] + ), + createSourceFile("source.ts", "", ScriptTarget.ES2015) + )); + + // https://github.com/Microsoft/TypeScript/issues/15651 + printsCorrectly("functionTypes", {}, printer => printer.printNode( + EmitHint.Unspecified, + setEmitFlags(factory.createTupleTypeNode([ + factory.createFunctionTypeNode( + /*typeArguments*/ undefined, + [factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + factory.createIdentifier("args") + )], + factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ), + factory.createFunctionTypeNode( + [factory.createTypeParameterDeclaration("T")], + [factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + factory.createIdentifier("args") + )], + factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ), + factory.createFunctionTypeNode( + /*typeArguments*/ undefined, + [factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createToken(SyntaxKind.DotDotDotToken), + factory.createIdentifier("args") + )], + factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ), + factory.createFunctionTypeNode( + /*typeArguments*/ undefined, + [factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + factory.createIdentifier("args"), + factory.createToken(SyntaxKind.QuestionToken) + )], + factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ), + factory.createFunctionTypeNode( + /*typeArguments*/ undefined, + [factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + factory.createIdentifier("args"), + /*questionToken*/ undefined, factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ), - ]), EmitFlags.SingleLine), - createSourceFile("source.ts", "", ScriptTarget.ES2015) - )); - }); + )], + factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ), + factory.createFunctionTypeNode( + /*typeArguments*/ undefined, + [factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + factory.createObjectBindingPattern([]) + )], + factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ), + ]), EmitFlags.SingleLine), + createSourceFile("source.ts", "", ScriptTarget.ES2015) + )); }); +}); } diff --git a/src/testRunner/unittests/programApi.ts b/src/testRunner/unittests/programApi.ts index 359dff1c03434..bd942768e92cd 100644 --- a/src/testRunner/unittests/programApi.ts +++ b/src/testRunner/unittests/programApi.ts @@ -1,223 +1,223 @@ namespace ts { - function verifyMissingFilePaths(missingPaths: readonly Path[], expected: readonly string[]) { - assert.isDefined(missingPaths); - const map = new Set(expected); - for (const missing of missingPaths) { - const value = map.has(missing); - assert.isTrue(value, `${missing} to be ${value === undefined ? "not present" : "present only once"}, in actual: ${missingPaths} expected: ${expected}`); - map.delete(missing); - } - const notFound = arrayFrom(mapDefinedIterator(map.keys(), k => map.has(k) ? k : undefined)); - assert.equal(notFound.length, 0, `Not found ${notFound} in actual: ${missingPaths} expected: ${expected}`); +function verifyMissingFilePaths(missingPaths: readonly Path[], expected: readonly string[]) { + assert.isDefined(missingPaths); + const map = new Set(expected); + for (const missing of missingPaths) { + const value = map.has(missing); + assert.isTrue(value, `${missing} to be ${value === undefined ? "not present" : "present only once"}, in actual: ${missingPaths} expected: ${expected}`); + map.delete(missing); } + const notFound = arrayFrom(mapDefinedIterator(map.keys(), k => map.has(k) ? k : undefined)); + assert.equal(notFound.length, 0, `Not found ${notFound} in actual: ${missingPaths} expected: ${expected}`); +} - describe("unittests:: programApi:: Program.getMissingFilePaths", () => { +describe("unittests:: programApi:: Program.getMissingFilePaths", () => { - const options: CompilerOptions = { - noLib: true, - }; + const options: CompilerOptions = { + noLib: true, + }; + + const emptyFileName = "empty.ts"; + const emptyFileRelativePath = "./" + emptyFileName; + + const emptyFile = new documents.TextDocument(emptyFileName, ""); + + const referenceFileName = "reference.ts"; + const referenceFileRelativePath = "./" + referenceFileName; + + const referenceFile = new documents.TextDocument(referenceFileName, + "/// \n" + // Absolute + "/// \n" + // Relative + "/// \n" + // Unqualified + "/// \n" // No extension + ); + + const testCompilerHost = new fakes.CompilerHost( + vfs.createFromFileSystem( + Harness.IO, + /*ignoreCase*/ true, + { documents: [emptyFile, referenceFile], cwd: "d:\\pretend\\" }), + { newLine: NewLineKind.LineFeed }); + + it("handles no missing root files", () => { + const program = createProgram([emptyFileRelativePath], options, testCompilerHost); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, []); + }); + + it("handles missing root file", () => { + const program = createProgram(["./nonexistent.ts"], options, testCompilerHost); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path + }); + + it("handles multiple missing root files", () => { + const program = createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); + }); + + it("handles a mix of present and missing root files", () => { + const program = createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); + }); + + it("handles repeatedly specified root files", () => { + const program = createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); + }); + + it("normalizes file paths", () => { + const program0 = createProgram(["./nonexistent.ts", "./NONEXISTENT.ts"], options, testCompilerHost); + const program1 = createProgram(["./NONEXISTENT.ts", "./nonexistent.ts"], options, testCompilerHost); + const missing0 = program0.getMissingFilePaths(); + const missing1 = program1.getMissingFilePaths(); + assert.equal(missing0.length, 1); + assert.deepEqual(missing0, missing1); + }); + + it("handles missing triple slash references", () => { + const program = createProgram([referenceFileRelativePath], options, testCompilerHost); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, [ + // From absolute reference + "d:/imaginary/nonexistent1.ts", + + // From relative reference + "d:/pretend/nonexistent2.ts", - const emptyFileName = "empty.ts"; - const emptyFileRelativePath = "./" + emptyFileName; - - const emptyFile = new documents.TextDocument(emptyFileName, ""); - - const referenceFileName = "reference.ts"; - const referenceFileRelativePath = "./" + referenceFileName; - - const referenceFile = new documents.TextDocument(referenceFileName, - "/// \n" + // Absolute - "/// \n" + // Relative - "/// \n" + // Unqualified - "/// \n" // No extension - ); - - const testCompilerHost = new fakes.CompilerHost( - vfs.createFromFileSystem( - Harness.IO, - /*ignoreCase*/ true, - { documents: [emptyFile, referenceFile], cwd: "d:\\pretend\\" }), - { newLine: NewLineKind.LineFeed }); - - it("handles no missing root files", () => { - const program = createProgram([emptyFileRelativePath], options, testCompilerHost); - const missing = program.getMissingFilePaths(); - verifyMissingFilePaths(missing, []); - }); - - it("handles missing root file", () => { - const program = createProgram(["./nonexistent.ts"], options, testCompilerHost); - const missing = program.getMissingFilePaths(); - verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path - }); - - it("handles multiple missing root files", () => { - const program = createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost); - const missing = program.getMissingFilePaths(); - verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); - }); - - it("handles a mix of present and missing root files", () => { - const program = createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost); - const missing = program.getMissingFilePaths(); - verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); - }); - - it("handles repeatedly specified root files", () => { - const program = createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost); - const missing = program.getMissingFilePaths(); - verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); - }); - - it("normalizes file paths", () => { - const program0 = createProgram(["./nonexistent.ts", "./NONEXISTENT.ts"], options, testCompilerHost); - const program1 = createProgram(["./NONEXISTENT.ts", "./nonexistent.ts"], options, testCompilerHost); - const missing0 = program0.getMissingFilePaths(); - const missing1 = program1.getMissingFilePaths(); - assert.equal(missing0.length, 1); - assert.deepEqual(missing0, missing1); - }); - - it("handles missing triple slash references", () => { - const program = createProgram([referenceFileRelativePath], options, testCompilerHost); - const missing = program.getMissingFilePaths(); - verifyMissingFilePaths(missing, [ - // From absolute reference - "d:/imaginary/nonexistent1.ts", - - // From relative reference - "d:/pretend/nonexistent2.ts", - - // From unqualified reference - "d:/pretend/nonexistent3.ts", - - // From no-extension reference - "d:/pretend/nonexistent4.d.ts", - "d:/pretend/nonexistent4.ts", - "d:/pretend/nonexistent4.tsx" - ]); - }); - - it("should not have missing file paths", () => { - const testSource = ` + // From unqualified reference + "d:/pretend/nonexistent3.ts", + + // From no-extension reference + "d:/pretend/nonexistent4.d.ts", + "d:/pretend/nonexistent4.ts", + "d:/pretend/nonexistent4.tsx" + ]); + }); + + it("should not have missing file paths", () => { + const testSource = ` class Foo extends HTMLElement { bar: string = 'baz'; }`; - const host: CompilerHost = { - getSourceFile: (fileName: string, languageVersion: ScriptTarget, _onError?: (message: string) => void) => { - return fileName === "test.ts" ? createSourceFile(fileName, testSource, languageVersion) : undefined; - }, - getDefaultLibFileName: () => "", - writeFile: (_fileName, _content) => { throw new Error("unsupported"); }, - getCurrentDirectory: () => sys.getCurrentDirectory(), - getCanonicalFileName: fileName => sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(), - getNewLine: () => sys.newLine, - useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, - fileExists: fileName => fileName === "test.ts", - readFile: fileName => fileName === "test.ts" ? testSource : undefined, - resolveModuleNames: (_moduleNames: string[], _containingFile: string) => { throw new Error("unsupported"); }, - getDirectories: _path => { throw new Error("unsupported"); }, - }; - - const program = createProgram(["test.ts"], { module: ModuleKind.ES2015 }, host); - assert(program.getSourceFiles().length === 1, "expected 'getSourceFiles' length to be 1"); - assert(program.getMissingFilePaths().length === 0, "expected 'getMissingFilePaths' length to be 0"); - assert((program.getFileProcessingDiagnostics()?.length || 0) === 0, "expected 'getFileProcessingDiagnostics' length to be 0"); - }); + const host: CompilerHost = { + getSourceFile: (fileName: string, languageVersion: ScriptTarget, _onError?: (message: string) => void) => { + return fileName === "test.ts" ? createSourceFile(fileName, testSource, languageVersion) : undefined; + }, + getDefaultLibFileName: () => "", + writeFile: (_fileName, _content) => { throw new Error("unsupported"); }, + getCurrentDirectory: () => sys.getCurrentDirectory(), + getCanonicalFileName: fileName => sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(), + getNewLine: () => sys.newLine, + useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, + fileExists: fileName => fileName === "test.ts", + readFile: fileName => fileName === "test.ts" ? testSource : undefined, + resolveModuleNames: (_moduleNames: string[], _containingFile: string) => { throw new Error("unsupported"); }, + getDirectories: _path => { throw new Error("unsupported"); }, + }; + + const program = createProgram(["test.ts"], { module: ModuleKind.ES2015 }, host); + assert(program.getSourceFiles().length === 1, "expected 'getSourceFiles' length to be 1"); + assert(program.getMissingFilePaths().length === 0, "expected 'getMissingFilePaths' length to be 0"); + assert((program.getFileProcessingDiagnostics()?.length || 0) === 0, "expected 'getFileProcessingDiagnostics' length to be 0"); + }); +}); + +describe("unittests:: Program.isSourceFileFromExternalLibrary", () => { + it("works on redirect files", () => { + // In this example '/node_modules/foo/index.d.ts' will redirect to '/node_modules/bar/node_modules/foo/index.d.ts'. + const a = new documents.TextDocument("/a.ts", 'import * as bar from "bar"; import * as foo from "foo";'); + const bar = new documents.TextDocument("/node_modules/bar/index.d.ts", 'import * as foo from "foo";'); + const fooPackageJsonText = '{ "name": "foo", "version": "1.2.3" }'; + const fooIndexText = "export const x: number;"; + const barFooPackage = new documents.TextDocument("/node_modules/bar/node_modules/foo/package.json", fooPackageJsonText); + const barFooIndex = new documents.TextDocument("/node_modules/bar/node_modules/foo/index.d.ts", fooIndexText); + const fooPackage = new documents.TextDocument("/node_modules/foo/package.json", fooPackageJsonText); + const fooIndex = new documents.TextDocument("/node_modules/foo/index.d.ts", fooIndexText); + + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [a, bar, barFooPackage, barFooIndex, fooPackage, fooIndex], cwd: "/" }); + const program = createProgram(["/a.ts"], emptyOptions, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); + assertIsExternal(program, [a, bar, barFooIndex, fooIndex], f => f !== a); }); - describe("unittests:: Program.isSourceFileFromExternalLibrary", () => { - it("works on redirect files", () => { - // In this example '/node_modules/foo/index.d.ts' will redirect to '/node_modules/bar/node_modules/foo/index.d.ts'. - const a = new documents.TextDocument("/a.ts", 'import * as bar from "bar"; import * as foo from "foo";'); - const bar = new documents.TextDocument("/node_modules/bar/index.d.ts", 'import * as foo from "foo";'); - const fooPackageJsonText = '{ "name": "foo", "version": "1.2.3" }'; - const fooIndexText = "export const x: number;"; - const barFooPackage = new documents.TextDocument("/node_modules/bar/node_modules/foo/package.json", fooPackageJsonText); - const barFooIndex = new documents.TextDocument("/node_modules/bar/node_modules/foo/index.d.ts", fooIndexText); - const fooPackage = new documents.TextDocument("/node_modules/foo/package.json", fooPackageJsonText); - const fooIndex = new documents.TextDocument("/node_modules/foo/index.d.ts", fooIndexText); - - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [a, bar, barFooPackage, barFooIndex, fooPackage, fooIndex], cwd: "/" }); - const program = createProgram(["/a.ts"], emptyOptions, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); - assertIsExternal(program, [a, bar, barFooIndex, fooIndex], f => f !== a); - }); - - it('works on `/// `', () => { - const a = new documents.TextDocument("/a.ts", '/// '); - const fooIndex = new documents.TextDocument("/node_modules/foo/index.d.ts", "declare const foo: number;"); - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [a, fooIndex], cwd: "/" }); - const program = createProgram(["/a.ts"], emptyOptions, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); - assertIsExternal(program, [a, fooIndex], f => f !== a); - }); - - function assertIsExternal(program: Program, files: readonly documents.TextDocument[], isExternalExpected: (file: documents.TextDocument) => boolean): void { - for (const file of files) { - const actual = program.isSourceFileFromExternalLibrary(program.getSourceFile(file.file)!); - const expected = isExternalExpected(file); - assert.equal(actual, expected, `Expected ${file.file} isSourceFileFromExternalLibrary to be ${expected}, got ${actual}`); - } - } + it('works on `/// `', () => { + const a = new documents.TextDocument("/a.ts", '/// '); + const fooIndex = new documents.TextDocument("/node_modules/foo/index.d.ts", "declare const foo: number;"); + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [a, fooIndex], cwd: "/" }); + const program = createProgram(["/a.ts"], emptyOptions, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); + assertIsExternal(program, [a, fooIndex], f => f !== a); }); - describe("unittests:: Program.getNodeCount / Program.getIdentifierCount", () => { - it("works on projects that have .json files", () => { - const main = new documents.TextDocument("/main.ts", 'export { version } from "./package.json";'); - const pkg = new documents.TextDocument("/package.json", '{"version": "1.0.0"}'); + function assertIsExternal(program: Program, files: readonly documents.TextDocument[], isExternalExpected: (file: documents.TextDocument) => boolean): void { + for (const file of files) { + const actual = program.isSourceFileFromExternalLibrary(program.getSourceFile(file.file)!); + const expected = isExternalExpected(file); + assert.equal(actual, expected, `Expected ${file.file} isSourceFileFromExternalLibrary to be ${expected}, got ${actual}`); + } + } +}); + +describe("unittests:: Program.getNodeCount / Program.getIdentifierCount", () => { + it("works on projects that have .json files", () => { + const main = new documents.TextDocument("/main.ts", 'export { version } from "./package.json";'); + const pkg = new documents.TextDocument("/package.json", '{"version": "1.0.0"}'); - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, pkg], cwd: "/" }); - const program = createProgram(["/main.ts"], { resolveJsonModule: true }, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, pkg], cwd: "/" }); + const program = createProgram(["/main.ts"], { resolveJsonModule: true }, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); - const json = program.getSourceFile("/package.json")!; - assert.equal(json.scriptKind, ScriptKind.JSON); - assert.isNumber(json.nodeCount); - assert.isNumber(json.identifierCount); + const json = program.getSourceFile("/package.json")!; + assert.equal(json.scriptKind, ScriptKind.JSON); + assert.isNumber(json.nodeCount); + assert.isNumber(json.identifierCount); - assert.isNotNaN(program.getNodeCount()); - assert.isNotNaN(program.getIdentifierCount()); - }); + assert.isNotNaN(program.getNodeCount()); + assert.isNotNaN(program.getIdentifierCount()); }); +}); + +describe("unittests:: programApi:: Program.getDiagnosticsProducingTypeChecker / Program.getSemanticDiagnostics", () => { + it("does not produce errors on `as const` it would not normally produce on the command line", () => { + const main = new documents.TextDocument("/main.ts", "0 as const"); + + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main], cwd: "/" }); + const program = createProgram(["/main.ts"], {}, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); + const typeChecker = program.getDiagnosticsProducingTypeChecker(); + const sourceFile = program.getSourceFile("main.ts")!; + typeChecker.getTypeAtLocation(((sourceFile.statements[0] as ExpressionStatement).expression as AsExpression).type); + const diag = program.getSemanticDiagnostics(); + assert.isEmpty(diag); + }); + it("getSymbolAtLocation does not cause additional error to be added on module resolution failure", () => { + const main = new documents.TextDocument("/main.ts", "import \"./module\";"); + const mod = new documents.TextDocument("/module.d.ts", "declare const foo: any;"); + + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" }); + const program = createProgram(["/main.ts"], {}, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); - describe("unittests:: programApi:: Program.getDiagnosticsProducingTypeChecker / Program.getSemanticDiagnostics", () => { - it("does not produce errors on `as const` it would not normally produce on the command line", () => { - const main = new documents.TextDocument("/main.ts", "0 as const"); - - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main], cwd: "/" }); - const program = createProgram(["/main.ts"], {}, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); - const typeChecker = program.getDiagnosticsProducingTypeChecker(); - const sourceFile = program.getSourceFile("main.ts")!; - typeChecker.getTypeAtLocation(((sourceFile.statements[0] as ExpressionStatement).expression as AsExpression).type); - const diag = program.getSemanticDiagnostics(); - assert.isEmpty(diag); - }); - it("getSymbolAtLocation does not cause additional error to be added on module resolution failure", () => { - const main = new documents.TextDocument("/main.ts", "import \"./module\";"); - const mod = new documents.TextDocument("/module.d.ts", "declare const foo: any;"); - - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" }); - const program = createProgram(["/main.ts"], {}, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); - - const sourceFile = program.getSourceFile("main.ts")!; - const typeChecker = program.getDiagnosticsProducingTypeChecker(); - typeChecker.getSymbolAtLocation((sourceFile.statements[0] as ImportDeclaration).moduleSpecifier); - assert.isEmpty(program.getSemanticDiagnostics()); - }); + const sourceFile = program.getSourceFile("main.ts")!; + const typeChecker = program.getDiagnosticsProducingTypeChecker(); + typeChecker.getSymbolAtLocation((sourceFile.statements[0] as ImportDeclaration).moduleSpecifier); + assert.isEmpty(program.getSemanticDiagnostics()); }); +}); - describe("unittests:: programApi:: CompilerOptions relative paths", () => { - it("resolves relative paths by getCurrentDirectory", () => { - const main = new documents.TextDocument("/main.ts", "import \"module\";"); - const mod = new documents.TextDocument("/lib/module.ts", "declare const foo: any;"); +describe("unittests:: programApi:: CompilerOptions relative paths", () => { + it("resolves relative paths by getCurrentDirectory", () => { + const main = new documents.TextDocument("/main.ts", "import \"module\";"); + const mod = new documents.TextDocument("/lib/module.ts", "declare const foo: any;"); - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" }); - const program = createProgram(["./main.ts"], { - paths: { "*": ["./lib/*"] } - }, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" }); + const program = createProgram(["./main.ts"], { + paths: { "*": ["./lib/*"] } + }, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); - assert.isEmpty(program.getConfigFileParsingDiagnostics()); - assert.isEmpty(program.getGlobalDiagnostics()); - assert.isEmpty(program.getSemanticDiagnostics()); - }); + assert.isEmpty(program.getConfigFileParsingDiagnostics()); + assert.isEmpty(program.getGlobalDiagnostics()); + assert.isEmpty(program.getSemanticDiagnostics()); }); +}); } diff --git a/src/testRunner/unittests/reuseProgramStructure.ts b/src/testRunner/unittests/reuseProgramStructure.ts index 6dc13de241d47..f7b78aee143d7 100644 --- a/src/testRunner/unittests/reuseProgramStructure.ts +++ b/src/testRunner/unittests/reuseProgramStructure.ts @@ -1,669 +1,630 @@ namespace ts { - const enum ChangedPart { - references = 1 << 0, - importsAndExports = 1 << 1, - program = 1 << 2 - } +const enum ChangedPart { + references = 1 << 0, + importsAndExports = 1 << 1, + program = 1 << 2 +} - const newLine = "\r\n"; +const newLine = "\r\n"; - interface SourceFileWithText extends SourceFile { - sourceText?: SourceText; - } - - export interface NamedSourceText { - name: string; - text: SourceText; - } +interface SourceFileWithText extends SourceFile { + sourceText?: SourceText; +} - export interface ProgramWithSourceTexts extends Program { - sourceTexts?: readonly NamedSourceText[]; - host: TestCompilerHost; - } +export interface NamedSourceText { + name: string; + text: SourceText; +} - interface TestCompilerHost extends CompilerHost { - getTrace(): string[]; - } +export interface ProgramWithSourceTexts extends Program { + sourceTexts?: readonly NamedSourceText[]; + host: TestCompilerHost; +} - export class SourceText implements IScriptSnapshot { - private fullText: string | undefined; +interface TestCompilerHost extends CompilerHost { + getTrace(): string[]; +} - constructor(private references: string, - private importsAndExports: string, - private program: string, - private changedPart: ChangedPart = 0, - private version = 0) { - } +export class SourceText implements IScriptSnapshot { + private fullText: string | undefined; - static New(references: string, importsAndExports: string, program: string): SourceText { - Debug.assert(references !== undefined); - Debug.assert(importsAndExports !== undefined); - Debug.assert(program !== undefined); - return new SourceText(references + newLine, importsAndExports + newLine, program || ""); - } + constructor(private references: string, + private importsAndExports: string, + private program: string, + private changedPart: ChangedPart = 0, + private version = 0) { + } - public getVersion(): number { - return this.version; - } + static New(references: string, importsAndExports: string, program: string): SourceText { + Debug.assert(references !== undefined); + Debug.assert(importsAndExports !== undefined); + Debug.assert(program !== undefined); + return new SourceText(references + newLine, importsAndExports + newLine, program || ""); + } - public updateReferences(newReferences: string): SourceText { - Debug.assert(newReferences !== undefined); - return new SourceText(newReferences + newLine, this.importsAndExports, this.program, this.changedPart | ChangedPart.references, this.version + 1); - } - public updateImportsAndExports(newImportsAndExports: string): SourceText { - Debug.assert(newImportsAndExports !== undefined); - return new SourceText(this.references, newImportsAndExports + newLine, this.program, this.changedPart | ChangedPart.importsAndExports, this.version + 1); - } - public updateProgram(newProgram: string): SourceText { - Debug.assert(newProgram !== undefined); - return new SourceText(this.references, this.importsAndExports, newProgram, this.changedPart | ChangedPart.program, this.version + 1); - } + public getVersion(): number { + return this.version; + } - public getFullText() { - return this.fullText || (this.fullText = this.references + this.importsAndExports + this.program); - } + public updateReferences(newReferences: string): SourceText { + Debug.assert(newReferences !== undefined); + return new SourceText(newReferences + newLine, this.importsAndExports, this.program, this.changedPart | ChangedPart.references, this.version + 1); + } + public updateImportsAndExports(newImportsAndExports: string): SourceText { + Debug.assert(newImportsAndExports !== undefined); + return new SourceText(this.references, newImportsAndExports + newLine, this.program, this.changedPart | ChangedPart.importsAndExports, this.version + 1); + } + public updateProgram(newProgram: string): SourceText { + Debug.assert(newProgram !== undefined); + return new SourceText(this.references, this.importsAndExports, newProgram, this.changedPart | ChangedPart.program, this.version + 1); + } - public getText(start: number, end: number): string { - return this.getFullText().substring(start, end); - } + public getFullText() { + return this.fullText || (this.fullText = this.references + this.importsAndExports + this.program); + } - getLength(): number { - return this.getFullText().length; - } + public getText(start: number, end: number): string { + return this.getFullText().substring(start, end); + } - getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange { - const oldText = oldSnapshot as SourceText; - let oldSpan: TextSpan; - let newLength: number; - switch (oldText.changedPart ^ this.changedPart) { - case ChangedPart.references: - oldSpan = createTextSpan(0, oldText.references.length); - newLength = this.references.length; - break; - case ChangedPart.importsAndExports: - oldSpan = createTextSpan(oldText.references.length, oldText.importsAndExports.length); - newLength = this.importsAndExports.length; - break; - case ChangedPart.program: - oldSpan = createTextSpan(oldText.references.length + oldText.importsAndExports.length, oldText.program.length); - newLength = this.program.length; - break; - default: - return Debug.fail("Unexpected change"); - } + getLength(): number { + return this.getFullText().length; + } - return createTextChangeRange(oldSpan, newLength); + getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange { + const oldText = oldSnapshot as SourceText; + let oldSpan: TextSpan; + let newLength: number; + switch (oldText.changedPart ^ this.changedPart) { + case ChangedPart.references: + oldSpan = createTextSpan(0, oldText.references.length); + newLength = this.references.length; + break; + case ChangedPart.importsAndExports: + oldSpan = createTextSpan(oldText.references.length, oldText.importsAndExports.length); + newLength = this.importsAndExports.length; + break; + case ChangedPart.program: + oldSpan = createTextSpan(oldText.references.length + oldText.importsAndExports.length, oldText.program.length); + newLength = this.program.length; + break; + default: + return Debug.fail("Unexpected change"); } - } - function createSourceFileWithText(fileName: string, sourceText: SourceText, target: ScriptTarget) { - const file = createSourceFile(fileName, sourceText.getFullText(), target) as SourceFileWithText; - file.sourceText = sourceText; - file.version = "" + sourceText.getVersion(); - return file; + return createTextChangeRange(oldSpan, newLength); } +} - export function createTestCompilerHost(texts: readonly NamedSourceText[], target: ScriptTarget, oldProgram?: ProgramWithSourceTexts, useGetSourceFileByPath?: boolean) { - const files = arrayToMap(texts, t => t.name, t => { - if (oldProgram) { - let oldFile = oldProgram.getSourceFile(t.name) as SourceFileWithText; - if (oldFile && oldFile.redirectInfo) { - oldFile = oldFile.redirectInfo.unredirected; - } - if (oldFile && oldFile.sourceText!.getVersion() === t.text.getVersion()) { - return oldFile; - } +function createSourceFileWithText(fileName: string, sourceText: SourceText, target: ScriptTarget) { + const file = createSourceFile(fileName, sourceText.getFullText(), target) as SourceFileWithText; + file.sourceText = sourceText; + file.version = "" + sourceText.getVersion(); + return file; +} + +export function createTestCompilerHost(texts: readonly NamedSourceText[], target: ScriptTarget, oldProgram?: ProgramWithSourceTexts, useGetSourceFileByPath?: boolean) { + const files = arrayToMap(texts, t => t.name, t => { + if (oldProgram) { + let oldFile = oldProgram.getSourceFile(t.name) as SourceFileWithText; + if (oldFile && oldFile.redirectInfo) { + oldFile = oldFile.redirectInfo.unredirected; + } + if (oldFile && oldFile.sourceText!.getVersion() === t.text.getVersion()) { + return oldFile; } - return createSourceFileWithText(t.name, t.text, target); - }); - const useCaseSensitiveFileNames = sys && sys.useCaseSensitiveFileNames; - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - const trace: string[] = []; - const result: TestCompilerHost = { - trace: s => trace.push(s), - getTrace: () => trace, - getSourceFile: fileName => files.get(fileName), - getDefaultLibFileName: () => "lib.d.ts", - writeFile: notImplemented, - getCurrentDirectory: () => "", - getDirectories: () => [], - getCanonicalFileName, - useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, - getNewLine: () => sys ? sys.newLine : newLine, - fileExists: fileName => files.has(fileName), - readFile: fileName => { - const file = files.get(fileName); - return file && file.text; - }, - }; - if (useGetSourceFileByPath) { - const filesByPath = mapEntries(files, (fileName, file) => [toPath(fileName, "", getCanonicalFileName), file]); - result.getSourceFileByPath = (_fileName, path) => filesByPath.get(path); } - return result; + return createSourceFileWithText(t.name, t.text, target); + }); + const useCaseSensitiveFileNames = sys && sys.useCaseSensitiveFileNames; + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + const trace: string[] = []; + const result: TestCompilerHost = { + trace: s => trace.push(s), + getTrace: () => trace, + getSourceFile: fileName => files.get(fileName), + getDefaultLibFileName: () => "lib.d.ts", + writeFile: notImplemented, + getCurrentDirectory: () => "", + getDirectories: () => [], + getCanonicalFileName, + useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, + getNewLine: () => sys ? sys.newLine : newLine, + fileExists: fileName => files.has(fileName), + readFile: fileName => { + const file = files.get(fileName); + return file && file.text; + }, + }; + if (useGetSourceFileByPath) { + const filesByPath = mapEntries(files, (fileName, file) => [toPath(fileName, "", getCanonicalFileName), file]); + result.getSourceFileByPath = (_fileName, path) => filesByPath.get(path); } + return result; +} - export function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions, useGetSourceFileByPath?: boolean): ProgramWithSourceTexts { - const host = createTestCompilerHost(texts, options.target!, /*oldProgram*/ undefined, useGetSourceFileByPath); - const program = createProgram(rootNames, options, host) as ProgramWithSourceTexts; - program.sourceTexts = texts; - program.host = host; - return program; - } +export function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions, useGetSourceFileByPath?: boolean): ProgramWithSourceTexts { + const host = createTestCompilerHost(texts, options.target!, /*oldProgram*/ undefined, useGetSourceFileByPath); + const program = createProgram(rootNames, options, host) as ProgramWithSourceTexts; + program.sourceTexts = texts; + program.host = host; + return program; +} - export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: readonly string[], options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[], useGetSourceFileByPath?: boolean) { - if (!newTexts) { - newTexts = oldProgram.sourceTexts!.slice(0); - } - updater(newTexts); - const host = createTestCompilerHost(newTexts, options.target!, oldProgram, useGetSourceFileByPath); - const program = createProgram(rootNames, options, host, oldProgram) as ProgramWithSourceTexts; - program.sourceTexts = newTexts; - program.host = host; - return program; +export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: readonly string[], options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[], useGetSourceFileByPath?: boolean) { + if (!newTexts) { + newTexts = oldProgram.sourceTexts!.slice(0); } + updater(newTexts); + const host = createTestCompilerHost(newTexts, options.target!, oldProgram, useGetSourceFileByPath); + const program = createProgram(rootNames, options, host, oldProgram) as ProgramWithSourceTexts; + program.sourceTexts = newTexts; + program.host = host; + return program; +} - export function updateProgramText(files: readonly NamedSourceText[], fileName: string, newProgramText: string) { - const file = find(files, f => f.name === fileName)!; - file.text = file.text.updateProgram(newProgramText); - } +export function updateProgramText(files: readonly NamedSourceText[], fileName: string, newProgramText: string) { + const file = find(files, f => f.name === fileName)!; + file.text = file.text.updateProgram(newProgramText); +} - function checkResolvedTypeDirective(actual: ResolvedTypeReferenceDirective, expected: ResolvedTypeReferenceDirective) { - assert.equal(actual.resolvedFileName, expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`); - assert.equal(actual.primary, expected.primary, `'primary': expected '${actual.primary}' to be equal to '${expected.primary}'`); - return true; - } +function checkResolvedTypeDirective(actual: ResolvedTypeReferenceDirective, expected: ResolvedTypeReferenceDirective) { + assert.equal(actual.resolvedFileName, expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`); + assert.equal(actual.primary, expected.primary, `'primary': expected '${actual.primary}' to be equal to '${expected.primary}'`); + return true; +} - function checkCache(caption: string, program: Program, fileName: string, expectedContent: ESMap | undefined, getCache: (f: SourceFile) => ModeAwareCache | undefined, entryChecker: (expected: T, original: T) => boolean): void { - const file = program.getSourceFile(fileName); - assert.isTrue(file !== undefined, `cannot find file ${fileName}`); - const cache = getCache(file!); - if (expectedContent === undefined) { - assert.isTrue(cache === undefined, `expected ${caption} to be undefined`); - } - else { - assert.isTrue(cache !== undefined, `expected ${caption} to be set`); - assert.isTrue(mapEqualToCache(expectedContent, cache!, entryChecker), `contents of ${caption} did not match the expected contents.`); - } +function checkCache(caption: string, program: Program, fileName: string, expectedContent: ESMap | undefined, getCache: (f: SourceFile) => ModeAwareCache | undefined, entryChecker: (expected: T, original: T) => boolean): void { + const file = program.getSourceFile(fileName); + assert.isTrue(file !== undefined, `cannot find file ${fileName}`); + const cache = getCache(file!); + if (expectedContent === undefined) { + assert.isTrue(cache === undefined, `expected ${caption} to be undefined`); } - - /** True if the maps have the same keys and values. */ - function mapEqualToCache(left: ESMap, right: ModeAwareCache, valuesAreEqual?: (left: T, right: T) => boolean): boolean { - if (left as any === right) return true; // given the type mismatch (the tests never pass a cache), this'll never be true - if (!left || !right) return false; - const someInLeftHasNoMatch = forEachEntry(left, (leftValue, leftKey) => { - if (!right.has(leftKey, /*mode*/ undefined)) return true; - const rightValue = right.get(leftKey, /*mode*/ undefined)!; - return !(valuesAreEqual ? valuesAreEqual(leftValue, rightValue) : leftValue === rightValue); - }); - if (someInLeftHasNoMatch) return false; - let someInRightHasNoMatch = false; - right.forEach((_, rightKey) => someInRightHasNoMatch = someInRightHasNoMatch || !left.has(rightKey)); - return !someInRightHasNoMatch; + else { + assert.isTrue(cache !== undefined, `expected ${caption} to be set`); + assert.isTrue(mapEqualToCache(expectedContent, cache!, entryChecker), `contents of ${caption} did not match the expected contents.`); } +} - function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: ESMap | undefined): void { - checkCache("resolved modules", program, fileName, expectedContent, f => f.resolvedModules, checkResolvedModule); - } +/** True if the maps have the same keys and values. */ +function mapEqualToCache(left: ESMap, right: ModeAwareCache, valuesAreEqual?: (left: T, right: T) => boolean): boolean { + if (left as any === right) return true; // given the type mismatch (the tests never pass a cache), this'll never be true + if (!left || !right) return false; + const someInLeftHasNoMatch = forEachEntry(left, (leftValue, leftKey) => { + if (!right.has(leftKey, /*mode*/ undefined)) return true; + const rightValue = right.get(leftKey, /*mode*/ undefined)!; + return !(valuesAreEqual ? valuesAreEqual(leftValue, rightValue) : leftValue === rightValue); + }); + if (someInLeftHasNoMatch) return false; + let someInRightHasNoMatch = false; + right.forEach((_, rightKey) => someInRightHasNoMatch = someInRightHasNoMatch || !left.has(rightKey)); + return !someInRightHasNoMatch; +} - function checkResolvedTypeDirectivesCache(program: Program, fileName: string, expectedContent: ESMap | undefined): void { - checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective); - } +function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: ESMap | undefined): void { + checkCache("resolved modules", program, fileName, expectedContent, f => f.resolvedModules, checkResolvedModule); +} - describe("unittests:: Reuse program structure:: General", () => { - const target = ScriptTarget.Latest; - const files: NamedSourceText[] = [ - { - name: "a.ts", text: SourceText.New( - ` +function checkResolvedTypeDirectivesCache(program: Program, fileName: string, expectedContent: ESMap | undefined): void { + checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective); +} + +describe("unittests:: Reuse program structure:: General", () => { + const target = ScriptTarget.Latest; + const files: NamedSourceText[] = [ + { + name: "a.ts", text: SourceText.New( + ` /// /// /// `, "", `var x = 1`) - }, - { name: "b.ts", text: SourceText.New(`/// `, "", `var y = 2`) }, - { name: "c.ts", text: SourceText.New("", "", `var z = 1;`) }, - { name: "types/typerefs/index.d.ts", text: SourceText.New("", "", `declare let z: number;`) }, - ]; - - it("successful if change does not affect imports", () => { - const program1 = newProgram(files, ["a.ts"], { target }); - const program2 = updateProgram(program1, ["a.ts"], { target }, files => { - files[0].text = files[0].text.updateProgram("var x = 100"); - }); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); - const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); - const program2Diagnostics = program2.getSemanticDiagnostics(program1.getSourceFile("a.ts")); - assert.equal(program1Diagnostics.length, program2Diagnostics.length); + }, + { name: "b.ts", text: SourceText.New(`/// `, "", `var y = 2`) }, + { name: "c.ts", text: SourceText.New("", "", `var z = 1;`) }, + { name: "types/typerefs/index.d.ts", text: SourceText.New("", "", `declare let z: number;`) }, + ]; + + it("successful if change does not affect imports", () => { + const program1 = newProgram(files, ["a.ts"], { target }); + const program2 = updateProgram(program1, ["a.ts"], { target }, files => { + files[0].text = files[0].text.updateProgram("var x = 100"); }); + assert.equal(program2.structureIsReused, StructureIsReused.Completely); + const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); + const program2Diagnostics = program2.getSemanticDiagnostics(program1.getSourceFile("a.ts")); + assert.equal(program1Diagnostics.length, program2Diagnostics.length); + }); - it("successful if change does not affect type reference directives", () => { - const program1 = newProgram(files, ["a.ts"], { target }); - const program2 = updateProgram(program1, ["a.ts"], { target }, files => { - files[0].text = files[0].text.updateProgram("var x = 100"); - }); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); - const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); - const program2Diagnostics = program2.getSemanticDiagnostics(program1.getSourceFile("a.ts")); - assert.equal(program1Diagnostics.length, program2Diagnostics.length); + it("successful if change does not affect type reference directives", () => { + const program1 = newProgram(files, ["a.ts"], { target }); + const program2 = updateProgram(program1, ["a.ts"], { target }, files => { + files[0].text = files[0].text.updateProgram("var x = 100"); }); + assert.equal(program2.structureIsReused, StructureIsReused.Completely); + const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); + const program2Diagnostics = program2.getSemanticDiagnostics(program1.getSourceFile("a.ts")); + assert.equal(program1Diagnostics.length, program2Diagnostics.length); + }); - it("fails if change affects tripleslash references", () => { - const program1 = newProgram(files, ["a.ts"], { target }); - const program2 = updateProgram(program1, ["a.ts"], { target }, files => { - const newReferences = `/// + it("fails if change affects tripleslash references", () => { + const program1 = newProgram(files, ["a.ts"], { target }); + const program2 = updateProgram(program1, ["a.ts"], { target }, files => { + const newReferences = `/// /// `; - files[0].text = files[0].text.updateReferences(newReferences); - }); - assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); + files[0].text = files[0].text.updateReferences(newReferences); }); + assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); + }); - it("fails if change affects type references", () => { - const program1 = newProgram(files, ["a.ts"], { types: ["a"] }); - const program2 = updateProgram(program1, ["a.ts"], { types: ["b"] }, noop); - assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); - }); + it("fails if change affects type references", () => { + const program1 = newProgram(files, ["a.ts"], { types: ["a"] }); + const program2 = updateProgram(program1, ["a.ts"], { types: ["b"] }, noop); + assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); + }); - it("succeeds if change doesn't affect type references", () => { - const program1 = newProgram(files, ["a.ts"], { types: ["a"] }); - const program2 = updateProgram(program1, ["a.ts"], { types: ["a"] }, noop); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); - }); + it("succeeds if change doesn't affect type references", () => { + const program1 = newProgram(files, ["a.ts"], { types: ["a"] }); + const program2 = updateProgram(program1, ["a.ts"], { types: ["a"] }, noop); + assert.equal(program2.structureIsReused, StructureIsReused.Completely); + }); - it("fails if change affects imports", () => { - const program1 = newProgram(files, ["a.ts"], { target }); - const program2 = updateProgram(program1, ["a.ts"], { target }, files => { - files[2].text = files[2].text.updateImportsAndExports("import x from 'b'"); - }); - assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); + it("fails if change affects imports", () => { + const program1 = newProgram(files, ["a.ts"], { target }); + const program2 = updateProgram(program1, ["a.ts"], { target }, files => { + files[2].text = files[2].text.updateImportsAndExports("import x from 'b'"); }); + assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); + }); - it("fails if change affects type directives", () => { - const program1 = newProgram(files, ["a.ts"], { target }); - const program2 = updateProgram(program1, ["a.ts"], { target }, files => { - const newReferences = ` + it("fails if change affects type directives", () => { + const program1 = newProgram(files, ["a.ts"], { target }); + const program2 = updateProgram(program1, ["a.ts"], { target }, files => { + const newReferences = ` /// /// /// `; - files[0].text = files[0].text.updateReferences(newReferences); - }); - assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); + files[0].text = files[0].text.updateReferences(newReferences); }); + assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); + }); - it("fails if module kind changes", () => { - const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS }); - const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.AMD }, noop); - assert.equal(program2.structureIsReused, StructureIsReused.Not); - }); + it("fails if module kind changes", () => { + const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS }); + const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.AMD }, noop); + assert.equal(program2.structureIsReused, StructureIsReused.Not); + }); - it("succeeds if rootdir changes", () => { - const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/b" }); - const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/c" }, noop); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); - }); + it("succeeds if rootdir changes", () => { + const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/b" }); + const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/c" }, noop); + assert.equal(program2.structureIsReused, StructureIsReused.Completely); + }); - it("fails if config path changes", () => { - const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, configFilePath: "/a/b/tsconfig.json" }); - const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.CommonJS, configFilePath: "/a/c/tsconfig.json" }, noop); - assert.equal(program2.structureIsReused, StructureIsReused.Not); - }); + it("fails if config path changes", () => { + const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, configFilePath: "/a/b/tsconfig.json" }); + const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.CommonJS, configFilePath: "/a/c/tsconfig.json" }, noop); + assert.equal(program2.structureIsReused, StructureIsReused.Not); + }); - it("succeeds if missing files remain missing", () => { - const options: CompilerOptions = { target, noLib: true }; + it("succeeds if missing files remain missing", () => { + const options: CompilerOptions = { target, noLib: true }; - const program1 = newProgram(files, ["a.ts"], options); - assert.notDeepEqual(emptyArray, program1.getMissingFilePaths()); + const program1 = newProgram(files, ["a.ts"], options); + assert.notDeepEqual(emptyArray, program1.getMissingFilePaths()); - const program2 = updateProgram(program1, ["a.ts"], options, noop); - assert.deepEqual(program1.getMissingFilePaths(), program2.getMissingFilePaths()); + const program2 = updateProgram(program1, ["a.ts"], options, noop); + assert.deepEqual(program1.getMissingFilePaths(), program2.getMissingFilePaths()); - assert.equal(program2.structureIsReused, StructureIsReused.Completely,); - }); + assert.equal(program2.structureIsReused, StructureIsReused.Completely,); + }); - it("fails if missing file is created", () => { - const options: CompilerOptions = { target, noLib: true }; + it("fails if missing file is created", () => { + const options: CompilerOptions = { target, noLib: true }; - const program1 = newProgram(files, ["a.ts"], options); - assert.notDeepEqual(emptyArray, program1.getMissingFilePaths()); + const program1 = newProgram(files, ["a.ts"], options); + assert.notDeepEqual(emptyArray, program1.getMissingFilePaths()); - const newTexts: NamedSourceText[] = files.concat([{ name: "non-existing-file.ts", text: SourceText.New("", "", `var x = 1`) }]); - const program2 = updateProgram(program1, ["a.ts"], options, noop, newTexts); - assert.lengthOf(program2.getMissingFilePaths(), 0); + const newTexts: NamedSourceText[] = files.concat([{ name: "non-existing-file.ts", text: SourceText.New("", "", `var x = 1`) }]); + const program2 = updateProgram(program1, ["a.ts"], options, noop, newTexts); + assert.lengthOf(program2.getMissingFilePaths(), 0); - assert.equal(program2.structureIsReused, StructureIsReused.Not); - }); + assert.equal(program2.structureIsReused, StructureIsReused.Not); + }); - it("resolution cache follows imports", () => { - (Error as any).stackTraceLimit = Infinity; + it("resolution cache follows imports", () => { + (Error as any).stackTraceLimit = Infinity; - const files = [ - { name: "a.ts", text: SourceText.New("", "import {_} from 'b'", "var x = 1") }, - { name: "b.ts", text: SourceText.New("", "", "var y = 2") }, - ]; - const options: CompilerOptions = { target }; + const files = [ + { name: "a.ts", text: SourceText.New("", "import {_} from 'b'", "var x = 1") }, + { name: "b.ts", text: SourceText.New("", "", "var y = 2") }, + ]; + const options: CompilerOptions = { target }; - const program1 = newProgram(files, ["a.ts"], options); - checkResolvedModulesCache(program1, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts") }))); - checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined); + const program1 = newProgram(files, ["a.ts"], options); + checkResolvedModulesCache(program1, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts") }))); + checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined); - const program2 = updateProgram(program1, ["a.ts"], options, files => { - files[0].text = files[0].text.updateProgram("var x = 2"); - }); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); + const program2 = updateProgram(program1, ["a.ts"], options, files => { + files[0].text = files[0].text.updateProgram("var x = 2"); + }); + assert.equal(program2.structureIsReused, StructureIsReused.Completely); - // content of resolution cache should not change - checkResolvedModulesCache(program1, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts") }))); - checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined); + // content of resolution cache should not change + checkResolvedModulesCache(program1, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts") }))); + checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined); - // imports has changed - program is not reused - const program3 = updateProgram(program2, ["a.ts"], options, files => { - files[0].text = files[0].text.updateImportsAndExports(""); - }); - assert.equal(program3.structureIsReused, StructureIsReused.SafeModules); - checkResolvedModulesCache(program3, "a.ts", /*expectedContent*/ undefined); + // imports has changed - program is not reused + const program3 = updateProgram(program2, ["a.ts"], options, files => { + files[0].text = files[0].text.updateImportsAndExports(""); + }); + assert.equal(program3.structureIsReused, StructureIsReused.SafeModules); + checkResolvedModulesCache(program3, "a.ts", /*expectedContent*/ undefined); - const program4 = updateProgram(program3, ["a.ts"], options, files => { - const newImports = `import x from 'b' + const program4 = updateProgram(program3, ["a.ts"], options, files => { + const newImports = `import x from 'b' import y from 'c' `; - files[0].text = files[0].text.updateImportsAndExports(newImports); - }); - assert.equal(program4.structureIsReused, StructureIsReused.SafeModules); - checkResolvedModulesCache(program4, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts"), c: undefined }))); + files[0].text = files[0].text.updateImportsAndExports(newImports); }); + assert.equal(program4.structureIsReused, StructureIsReused.SafeModules); + checkResolvedModulesCache(program4, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts"), c: undefined }))); + }); - it("set the resolvedImports after re-using an ambient external module declaration", () => { - const files = [ - { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";') }, - { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') }, - ]; - const options: CompilerOptions = { target, typeRoots: ["/types"] }; - const program1 = newProgram(files, ["/a.ts"], options); - const program2 = updateProgram(program1, ["/a.ts"], options, files => { - files[0].text = files[0].text.updateProgram('import * as aa from "a";'); - }); - assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a", /*mode*/ undefined), "'a' is not an unresolved module after re-use"); + it("set the resolvedImports after re-using an ambient external module declaration", () => { + const files = [ + { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";') }, + { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') }, + ]; + const options: CompilerOptions = { target, typeRoots: ["/types"] }; + const program1 = newProgram(files, ["/a.ts"], options); + const program2 = updateProgram(program1, ["/a.ts"], options, files => { + files[0].text = files[0].text.updateProgram('import * as aa from "a";'); }); + assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a", /*mode*/ undefined), "'a' is not an unresolved module after re-use"); + }); - it("works with updated SourceFiles", () => { - // adapted repro from https://github.com/Microsoft/TypeScript/issues/26166 - const files = [ - { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";a;') }, - { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') }, - ]; - const host = createTestCompilerHost(files, target); - const options: CompilerOptions = { target, typeRoots: ["/types"] }; - const program1 = createProgram(["/a.ts"], options, host); - let sourceFile = program1.getSourceFile("/a.ts")!; - assert.isDefined(sourceFile, "'/a.ts' is included in the program"); - sourceFile = updateSourceFile(sourceFile, "'use strict';" + sourceFile.text, { newLength: "'use strict';".length, span: { start: 0, length: 0 } }); - assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are updated"); - const updateHost: TestCompilerHost = { - ...host, - getSourceFile(fileName) { - return fileName === sourceFile.fileName ? sourceFile : program1.getSourceFile(fileName); - } - }; - const program2 = createProgram(["/a.ts"], options, updateHost, program1); - assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a", /*mode*/ undefined), "'a' is not an unresolved module after re-use"); - assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are not altered"); - }); + it("works with updated SourceFiles", () => { + // adapted repro from https://github.com/Microsoft/TypeScript/issues/26166 + const files = [ + { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";a;') }, + { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') }, + ]; + const host = createTestCompilerHost(files, target); + const options: CompilerOptions = { target, typeRoots: ["/types"] }; + const program1 = createProgram(["/a.ts"], options, host); + let sourceFile = program1.getSourceFile("/a.ts")!; + assert.isDefined(sourceFile, "'/a.ts' is included in the program"); + sourceFile = updateSourceFile(sourceFile, "'use strict';" + sourceFile.text, { newLength: "'use strict';".length, span: { start: 0, length: 0 } }); + assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are updated"); + const updateHost: TestCompilerHost = { + ...host, + getSourceFile(fileName) { + return fileName === sourceFile.fileName ? sourceFile : program1.getSourceFile(fileName); + } + }; + const program2 = createProgram(["/a.ts"], options, updateHost, program1); + assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a", /*mode*/ undefined), "'a' is not an unresolved module after re-use"); + assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are not altered"); + }); - it("resolved type directives cache follows type directives", () => { - const files = [ - { name: "/a.ts", text: SourceText.New("/// ", "", "var x = $") }, - { name: "/types/typedefs/index.d.ts", text: SourceText.New("", "", "declare var $: number") }, - ]; - const options: CompilerOptions = { target, typeRoots: ["/types"] }; + it("resolved type directives cache follows type directives", () => { + const files = [ + { name: "/a.ts", text: SourceText.New("/// ", "", "var x = $") }, + { name: "/types/typedefs/index.d.ts", text: SourceText.New("", "", "declare var $: number") }, + ]; + const options: CompilerOptions = { target, typeRoots: ["/types"] }; - const program1 = newProgram(files, ["/a.ts"], options); - checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); - checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined); + const program1 = newProgram(files, ["/a.ts"], options); + checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); + checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined); - const program2 = updateProgram(program1, ["/a.ts"], options, files => { - files[0].text = files[0].text.updateProgram("var x = 2"); - }); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); + const program2 = updateProgram(program1, ["/a.ts"], options, files => { + files[0].text = files[0].text.updateProgram("var x = 2"); + }); + assert.equal(program2.structureIsReused, StructureIsReused.Completely); - // content of resolution cache should not change - checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); - checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined); + // content of resolution cache should not change + checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); + checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined); - // type reference directives has changed - program is not reused - const program3 = updateProgram(program2, ["/a.ts"], options, files => { - files[0].text = files[0].text.updateReferences(""); - }); + // type reference directives has changed - program is not reused + const program3 = updateProgram(program2, ["/a.ts"], options, files => { + files[0].text = files[0].text.updateReferences(""); + }); - assert.equal(program3.structureIsReused, StructureIsReused.SafeModules); - checkResolvedTypeDirectivesCache(program3, "/a.ts", /*expectedContent*/ undefined); + assert.equal(program3.structureIsReused, StructureIsReused.SafeModules); + checkResolvedTypeDirectivesCache(program3, "/a.ts", /*expectedContent*/ undefined); - const program4 = updateProgram(program3, ["/a.ts"], options, files => { - const newReferences = `/// + const program4 = updateProgram(program3, ["/a.ts"], options, files => { + const newReferences = `/// /// `; - files[0].text = files[0].text.updateReferences(newReferences); - }); - assert.equal(program4.structureIsReused, StructureIsReused.SafeModules); - checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); - }); - - it("fetches imports after npm install", () => { - const file1Ts = { name: "file1.ts", text: SourceText.New("", `import * as a from "a";`, "const myX: number = a.x;") }; - const file2Ts = { name: "file2.ts", text: SourceText.New("", "", "") }; - const indexDTS = { name: "node_modules/a/index.d.ts", text: SourceText.New("", "export declare let x: number;", "") }; - const options: CompilerOptions = { target: ScriptTarget.ES2015, traceResolution: true, moduleResolution: ModuleResolutionKind.NodeJs }; - const rootFiles = [file1Ts, file2Ts]; - const filesAfterNpmInstall = [file1Ts, file2Ts, indexDTS]; - - const initialProgram = newProgram(rootFiles, rootFiles.map(f => f.name), options); - { - assert.deepEqual(initialProgram.host.getTrace(), - [ - "======== Resolving module 'a' from 'file1.ts'. ========", - "Explicitly specified module resolution kind: 'NodeJs'.", - "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.", - "File 'node_modules/a/package.json' does not exist.", - "File 'node_modules/a.ts' does not exist.", - "File 'node_modules/a.tsx' does not exist.", - "File 'node_modules/a.d.ts' does not exist.", - "File 'node_modules/a/index.ts' does not exist.", - "File 'node_modules/a/index.tsx' does not exist.", - "File 'node_modules/a/index.d.ts' does not exist.", - "File 'node_modules/@types/a/package.json' does not exist.", - "File 'node_modules/@types/a.d.ts' does not exist.", - "File 'node_modules/@types/a/index.d.ts' does not exist.", - "Loading module 'a' from 'node_modules' folder, target file type 'JavaScript'.", - "File 'node_modules/a/package.json' does not exist according to earlier cached lookups.", - "File 'node_modules/a.js' does not exist.", - "File 'node_modules/a.jsx' does not exist.", - "File 'node_modules/a/index.js' does not exist.", - "File 'node_modules/a/index.jsx' does not exist.", - "======== Module name 'a' was not resolved. ========" - ], - "initialProgram: execute module resolution normally."); - - const initialProgramDiagnostics = initialProgram.getSemanticDiagnostics(initialProgram.getSourceFile("file1.ts")); - assert.lengthOf(initialProgramDiagnostics, 1, `initialProgram: import should fail.`); - } - - const afterNpmInstallProgram = updateProgram(initialProgram, rootFiles.map(f => f.name), options, f => { - f[1].text = f[1].text.updateReferences(`/// `); - }, filesAfterNpmInstall); - { - assert.deepEqual(afterNpmInstallProgram.host.getTrace(), - [ - "======== Resolving module 'a' from 'file1.ts'. ========", - "Explicitly specified module resolution kind: 'NodeJs'.", - "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.", - "File 'node_modules/a/package.json' does not exist.", - "File 'node_modules/a.ts' does not exist.", - "File 'node_modules/a.tsx' does not exist.", - "File 'node_modules/a.d.ts' does not exist.", - "File 'node_modules/a/index.ts' does not exist.", - "File 'node_modules/a/index.tsx' does not exist.", - "File 'node_modules/a/index.d.ts' exist - use it as a name resolution result.", - "======== Module name 'a' was successfully resolved to 'node_modules/a/index.d.ts'. ========" - ], - "afterNpmInstallProgram: execute module resolution normally."); - - const afterNpmInstallProgramDiagnostics = afterNpmInstallProgram.getSemanticDiagnostics(afterNpmInstallProgram.getSourceFile("file1.ts")); - assert.lengthOf(afterNpmInstallProgramDiagnostics, 0, `afterNpmInstallProgram: program is well-formed with import.`); - } + files[0].text = files[0].text.updateReferences(newReferences); }); + assert.equal(program4.structureIsReused, StructureIsReused.SafeModules); + checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); + }); - it("can reuse ambient module declarations from non-modified files", () => { - const files = [ - { name: "/a/b/app.ts", text: SourceText.New("", "import * as fs from 'fs'", "") }, - { name: "/a/b/node.d.ts", text: SourceText.New("", "", "declare module 'fs' {}") } - ]; - const options = { target: ScriptTarget.ES2015, traceResolution: true }; - const program = newProgram(files, files.map(f => f.name), options); - assert.deepEqual(program.host.getTrace(), + it("fetches imports after npm install", () => { + const file1Ts = { name: "file1.ts", text: SourceText.New("", `import * as a from "a";`, "const myX: number = a.x;") }; + const file2Ts = { name: "file2.ts", text: SourceText.New("", "", "") }; + const indexDTS = { name: "node_modules/a/index.d.ts", text: SourceText.New("", "export declare let x: number;", "") }; + const options: CompilerOptions = { target: ScriptTarget.ES2015, traceResolution: true, moduleResolution: ModuleResolutionKind.NodeJs }; + const rootFiles = [file1Ts, file2Ts]; + const filesAfterNpmInstall = [file1Ts, file2Ts, indexDTS]; + + const initialProgram = newProgram(rootFiles, rootFiles.map(f => f.name), options); + { + assert.deepEqual(initialProgram.host.getTrace(), [ - "======== Resolving module 'fs' from '/a/b/app.ts'. ========", - "Module resolution kind is not specified, using 'Classic'.", - "File '/a/b/fs.ts' does not exist.", - "File '/a/b/fs.tsx' does not exist.", - "File '/a/b/fs.d.ts' does not exist.", - "File '/a/fs.ts' does not exist.", - "File '/a/fs.tsx' does not exist.", - "File '/a/fs.d.ts' does not exist.", - "File '/fs.ts' does not exist.", - "File '/fs.tsx' does not exist.", - "File '/fs.d.ts' does not exist.", - "File '/a/b/node_modules/@types/fs/package.json' does not exist.", - "File '/a/b/node_modules/@types/fs.d.ts' does not exist.", - "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.", - "File '/a/node_modules/@types/fs/package.json' does not exist.", - "File '/a/node_modules/@types/fs.d.ts' does not exist.", - "File '/a/node_modules/@types/fs/index.d.ts' does not exist.", - "File '/node_modules/@types/fs/package.json' does not exist.", - "File '/node_modules/@types/fs.d.ts' does not exist.", - "File '/node_modules/@types/fs/index.d.ts' does not exist.", - "File '/a/b/fs.js' does not exist.", - "File '/a/b/fs.jsx' does not exist.", - "File '/a/fs.js' does not exist.", - "File '/a/fs.jsx' does not exist.", - "File '/fs.js' does not exist.", - "File '/fs.jsx' does not exist.", - "======== Module name 'fs' was not resolved. ========", - ], "should look for 'fs'"); - - const program2 = updateProgram(program, program.getRootFileNames(), options, f => { - f[0].text = f[0].text.updateProgram("var x = 1;"); - }); - assert.deepEqual(program2.host.getTrace(), [ - "Module 'fs' was resolved as ambient module declared in '/a/b/node.d.ts' since this file was not modified." - ], "should reuse 'fs' since node.d.ts was not changed"); + "======== Resolving module 'a' from 'file1.ts'. ========", + "Explicitly specified module resolution kind: 'NodeJs'.", + "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.", + "File 'node_modules/a/package.json' does not exist.", + "File 'node_modules/a.ts' does not exist.", + "File 'node_modules/a.tsx' does not exist.", + "File 'node_modules/a.d.ts' does not exist.", + "File 'node_modules/a/index.ts' does not exist.", + "File 'node_modules/a/index.tsx' does not exist.", + "File 'node_modules/a/index.d.ts' does not exist.", + "File 'node_modules/@types/a/package.json' does not exist.", + "File 'node_modules/@types/a.d.ts' does not exist.", + "File 'node_modules/@types/a/index.d.ts' does not exist.", + "Loading module 'a' from 'node_modules' folder, target file type 'JavaScript'.", + "File 'node_modules/a/package.json' does not exist according to earlier cached lookups.", + "File 'node_modules/a.js' does not exist.", + "File 'node_modules/a.jsx' does not exist.", + "File 'node_modules/a/index.js' does not exist.", + "File 'node_modules/a/index.jsx' does not exist.", + "======== Module name 'a' was not resolved. ========" + ], + "initialProgram: execute module resolution normally."); + + const initialProgramDiagnostics = initialProgram.getSemanticDiagnostics(initialProgram.getSourceFile("file1.ts")); + assert.lengthOf(initialProgramDiagnostics, 1, `initialProgram: import should fail.`); + } - const program3 = updateProgram(program2, program2.getRootFileNames(), options, f => { - f[0].text = f[0].text.updateProgram("var y = 1;"); - f[1].text = f[1].text.updateProgram("declare var process: any"); - }); - assert.deepEqual(program3.host.getTrace(), + const afterNpmInstallProgram = updateProgram(initialProgram, rootFiles.map(f => f.name), options, f => { + f[1].text = f[1].text.updateReferences(`/// `); + }, filesAfterNpmInstall); + { + assert.deepEqual(afterNpmInstallProgram.host.getTrace(), [ - "======== Resolving module 'fs' from '/a/b/app.ts'. ========", - "Module resolution kind is not specified, using 'Classic'.", - "File '/a/b/fs.ts' does not exist.", - "File '/a/b/fs.tsx' does not exist.", - "File '/a/b/fs.d.ts' does not exist.", - "File '/a/fs.ts' does not exist.", - "File '/a/fs.tsx' does not exist.", - "File '/a/fs.d.ts' does not exist.", - "File '/fs.ts' does not exist.", - "File '/fs.tsx' does not exist.", - "File '/fs.d.ts' does not exist.", - "File '/a/b/node_modules/@types/fs/package.json' does not exist.", - "File '/a/b/node_modules/@types/fs.d.ts' does not exist.", - "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.", - "File '/a/node_modules/@types/fs/package.json' does not exist.", - "File '/a/node_modules/@types/fs.d.ts' does not exist.", - "File '/a/node_modules/@types/fs/index.d.ts' does not exist.", - "File '/node_modules/@types/fs/package.json' does not exist.", - "File '/node_modules/@types/fs.d.ts' does not exist.", - "File '/node_modules/@types/fs/index.d.ts' does not exist.", - "File '/a/b/fs.js' does not exist.", - "File '/a/b/fs.jsx' does not exist.", - "File '/a/fs.js' does not exist.", - "File '/a/fs.jsx' does not exist.", - "File '/fs.js' does not exist.", - "File '/fs.jsx' does not exist.", - "======== Module name 'fs' was not resolved. ========", - ], "should look for 'fs' again since node.d.ts was changed"); + "======== Resolving module 'a' from 'file1.ts'. ========", + "Explicitly specified module resolution kind: 'NodeJs'.", + "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.", + "File 'node_modules/a/package.json' does not exist.", + "File 'node_modules/a.ts' does not exist.", + "File 'node_modules/a.tsx' does not exist.", + "File 'node_modules/a.d.ts' does not exist.", + "File 'node_modules/a/index.ts' does not exist.", + "File 'node_modules/a/index.tsx' does not exist.", + "File 'node_modules/a/index.d.ts' exist - use it as a name resolution result.", + "======== Module name 'a' was successfully resolved to 'node_modules/a/index.d.ts'. ========" + ], + "afterNpmInstallProgram: execute module resolution normally."); + + const afterNpmInstallProgramDiagnostics = afterNpmInstallProgram.getSemanticDiagnostics(afterNpmInstallProgram.getSourceFile("file1.ts")); + assert.lengthOf(afterNpmInstallProgramDiagnostics, 0, `afterNpmInstallProgram: program is well-formed with import.`); + } + }); + + it("can reuse ambient module declarations from non-modified files", () => { + const files = [ + { name: "/a/b/app.ts", text: SourceText.New("", "import * as fs from 'fs'", "") }, + { name: "/a/b/node.d.ts", text: SourceText.New("", "", "declare module 'fs' {}") } + ]; + const options = { target: ScriptTarget.ES2015, traceResolution: true }; + const program = newProgram(files, files.map(f => f.name), options); + assert.deepEqual(program.host.getTrace(), + [ + "======== Resolving module 'fs' from '/a/b/app.ts'. ========", + "Module resolution kind is not specified, using 'Classic'.", + "File '/a/b/fs.ts' does not exist.", + "File '/a/b/fs.tsx' does not exist.", + "File '/a/b/fs.d.ts' does not exist.", + "File '/a/fs.ts' does not exist.", + "File '/a/fs.tsx' does not exist.", + "File '/a/fs.d.ts' does not exist.", + "File '/fs.ts' does not exist.", + "File '/fs.tsx' does not exist.", + "File '/fs.d.ts' does not exist.", + "File '/a/b/node_modules/@types/fs/package.json' does not exist.", + "File '/a/b/node_modules/@types/fs.d.ts' does not exist.", + "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/a/node_modules/@types/fs/package.json' does not exist.", + "File '/a/node_modules/@types/fs.d.ts' does not exist.", + "File '/a/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/node_modules/@types/fs/package.json' does not exist.", + "File '/node_modules/@types/fs.d.ts' does not exist.", + "File '/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/a/b/fs.js' does not exist.", + "File '/a/b/fs.jsx' does not exist.", + "File '/a/fs.js' does not exist.", + "File '/a/fs.jsx' does not exist.", + "File '/fs.js' does not exist.", + "File '/fs.jsx' does not exist.", + "======== Module name 'fs' was not resolved. ========", + ], "should look for 'fs'"); + + const program2 = updateProgram(program, program.getRootFileNames(), options, f => { + f[0].text = f[0].text.updateProgram("var x = 1;"); }); + assert.deepEqual(program2.host.getTrace(), [ + "Module 'fs' was resolved as ambient module declared in '/a/b/node.d.ts' since this file was not modified." + ], "should reuse 'fs' since node.d.ts was not changed"); - it("can reuse module resolutions from non-modified files", () => { - const files = [ - { name: "a1.ts", text: SourceText.New("", "", "let x = 1;") }, - { name: "a2.ts", text: SourceText.New("", "", "let x = 1;") }, - { name: "b1.ts", text: SourceText.New("", "export class B { x: number; }", "") }, - { name: "b2.ts", text: SourceText.New("", "export class B { x: number; }", "") }, - { name: "node_modules/@types/typerefs1/index.d.ts", text: SourceText.New("", "", "declare let z: string;") }, - { name: "node_modules/@types/typerefs2/index.d.ts", text: SourceText.New("", "", "declare let z: string;") }, - { - name: "f1.ts", - text: - SourceText.New( - `/// ${newLine}/// ${newLine}/// `, - `import { B } from './b1';${newLine}export let BB = B;`, - "declare module './b1' { interface B { y: string; } }") - }, - { - name: "f2.ts", - text: SourceText.New( - `/// ${newLine}/// `, - `import { B } from './b2';${newLine}import { BB } from './f1';`, - "(new BB).x; (new BB).y;") - }, - ]; + const program3 = updateProgram(program2, program2.getRootFileNames(), options, f => { + f[0].text = f[0].text.updateProgram("var y = 1;"); + f[1].text = f[1].text.updateProgram("declare var process: any"); + }); + assert.deepEqual(program3.host.getTrace(), + [ + "======== Resolving module 'fs' from '/a/b/app.ts'. ========", + "Module resolution kind is not specified, using 'Classic'.", + "File '/a/b/fs.ts' does not exist.", + "File '/a/b/fs.tsx' does not exist.", + "File '/a/b/fs.d.ts' does not exist.", + "File '/a/fs.ts' does not exist.", + "File '/a/fs.tsx' does not exist.", + "File '/a/fs.d.ts' does not exist.", + "File '/fs.ts' does not exist.", + "File '/fs.tsx' does not exist.", + "File '/fs.d.ts' does not exist.", + "File '/a/b/node_modules/@types/fs/package.json' does not exist.", + "File '/a/b/node_modules/@types/fs.d.ts' does not exist.", + "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/a/node_modules/@types/fs/package.json' does not exist.", + "File '/a/node_modules/@types/fs.d.ts' does not exist.", + "File '/a/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/node_modules/@types/fs/package.json' does not exist.", + "File '/node_modules/@types/fs.d.ts' does not exist.", + "File '/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/a/b/fs.js' does not exist.", + "File '/a/b/fs.jsx' does not exist.", + "File '/a/fs.js' does not exist.", + "File '/a/fs.jsx' does not exist.", + "File '/fs.js' does not exist.", + "File '/fs.jsx' does not exist.", + "======== Module name 'fs' was not resolved. ========", + ], "should look for 'fs' again since node.d.ts was changed"); + }); - const options: CompilerOptions = { target: ScriptTarget.ES2015, traceResolution: true, moduleResolution: ModuleResolutionKind.Classic }; - const program1 = newProgram(files, files.map(f => f.name), options); - let expectedErrors = 0; + it("can reuse module resolutions from non-modified files", () => { + const files = [ + { name: "a1.ts", text: SourceText.New("", "", "let x = 1;") }, + { name: "a2.ts", text: SourceText.New("", "", "let x = 1;") }, + { name: "b1.ts", text: SourceText.New("", "export class B { x: number; }", "") }, + { name: "b2.ts", text: SourceText.New("", "export class B { x: number; }", "") }, + { name: "node_modules/@types/typerefs1/index.d.ts", text: SourceText.New("", "", "declare let z: string;") }, + { name: "node_modules/@types/typerefs2/index.d.ts", text: SourceText.New("", "", "declare let z: string;") }, { - assert.deepEqual(program1.host.getTrace(), - [ - "======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========", - "Resolving with primary search path 'node_modules/@types'.", - "File 'node_modules/@types/typerefs1/package.json' does not exist.", - "File 'node_modules/@types/typerefs1/index.d.ts' exist - use it as a name resolution result.", - "======== Type reference directive 'typerefs1' was successfully resolved to 'node_modules/@types/typerefs1/index.d.ts', primary: true. ========", - "======== Resolving module './b1' from 'f1.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'b1.ts' exist - use it as a name resolution result.", - "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", - "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", - "Resolving with primary search path 'node_modules/@types'.", - "File 'node_modules/@types/typerefs2/package.json' does not exist.", - "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", - "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", - "======== Resolving module './b2' from 'f2.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'b2.ts' exist - use it as a name resolution result.", - "======== Module name './b2' was successfully resolved to 'b2.ts'. ========", - "======== Resolving module './f1' from 'f2.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'f1.ts' exist - use it as a name resolution result.", - "======== Module name './f1' was successfully resolved to 'f1.ts'. ========" - ], - "program1: execute module resolution normally."); - - const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("f2.ts")); - assert.lengthOf(program1Diagnostics, expectedErrors, `initial program should be well-formed`); - } - const indexOfF1 = 6; - const program2 = updateProgram(program1, program1.getRootFileNames(), options, f => { - const newSourceText = f[indexOfF1].text.updateReferences(`/// ${newLine}/// `); - f[indexOfF1] = { name: "f1.ts", text: newSourceText }; - }); - + name: "f1.ts", + text: + SourceText.New( + `/// ${newLine}/// ${newLine}/// `, + `import { B } from './b1';${newLine}export let BB = B;`, + "declare module './b1' { interface B { y: string; } }") + }, { - const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("f2.ts")); - assert.lengthOf(program2Diagnostics, expectedErrors, `removing no-default-lib shouldn't affect any types used.`); + name: "f2.ts", + text: SourceText.New( + `/// ${newLine}/// `, + `import { B } from './b2';${newLine}import { BB } from './f1';`, + "(new BB).x; (new BB).y;") + }, + ]; - assert.deepEqual(program2.host.getTrace(), [ + const options: CompilerOptions = { target: ScriptTarget.ES2015, traceResolution: true, moduleResolution: ModuleResolutionKind.Classic }; + const program1 = newProgram(files, files.map(f => f.name), options); + let expectedErrors = 0; + { + assert.deepEqual(program1.host.getTrace(), + [ "======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========", "Resolving with primary search path 'node_modules/@types'.", "File 'node_modules/@types/typerefs1/package.json' does not exist.", @@ -678,488 +639,527 @@ namespace ts { "File 'node_modules/@types/typerefs2/package.json' does not exist.", "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", - "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", - "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'." - ], "program2: reuse module resolutions in f2 since it is unchanged"); - } + "======== Resolving module './b2' from 'f2.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'b2.ts' exist - use it as a name resolution result.", + "======== Module name './b2' was successfully resolved to 'b2.ts'. ========", + "======== Resolving module './f1' from 'f2.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'f1.ts' exist - use it as a name resolution result.", + "======== Module name './f1' was successfully resolved to 'f1.ts'. ========" + ], + "program1: execute module resolution normally."); - const program3 = updateProgram(program2, program2.getRootFileNames(), options, f => { - const newSourceText = f[indexOfF1].text.updateReferences(`/// `); - f[indexOfF1] = { name: "f1.ts", text: newSourceText }; - }); + const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("f2.ts")); + assert.lengthOf(program1Diagnostics, expectedErrors, `initial program should be well-formed`); + } + const indexOfF1 = 6; + const program2 = updateProgram(program1, program1.getRootFileNames(), options, f => { + const newSourceText = f[indexOfF1].text.updateReferences(`/// ${newLine}/// `); + f[indexOfF1] = { name: "f1.ts", text: newSourceText }; + }); - { - const program3Diagnostics = program3.getSemanticDiagnostics(program3.getSourceFile("f2.ts")); - assert.lengthOf(program3Diagnostics, expectedErrors, `typerefs2 was unused, so diagnostics should be unaffected.`); + { + const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("f2.ts")); + assert.lengthOf(program2Diagnostics, expectedErrors, `removing no-default-lib shouldn't affect any types used.`); - assert.deepEqual(program3.host.getTrace(), [ - "======== Resolving module './b1' from 'f1.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'b1.ts' exist - use it as a name resolution result.", - "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", - "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", - "Resolving with primary search path 'node_modules/@types'.", - "File 'node_modules/@types/typerefs2/package.json' does not exist.", - "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", - "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", - "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", - "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'." - ], "program3: reuse module resolutions in f2 since it is unchanged"); - } + assert.deepEqual(program2.host.getTrace(), [ + "======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs1/package.json' does not exist.", + "File 'node_modules/@types/typerefs1/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs1' was successfully resolved to 'node_modules/@types/typerefs1/index.d.ts', primary: true. ========", + "======== Resolving module './b1' from 'f1.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'b1.ts' exist - use it as a name resolution result.", + "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", + "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs2/package.json' does not exist.", + "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", + "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", + "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'." + ], "program2: reuse module resolutions in f2 since it is unchanged"); + } + const program3 = updateProgram(program2, program2.getRootFileNames(), options, f => { + const newSourceText = f[indexOfF1].text.updateReferences(`/// `); + f[indexOfF1] = { name: "f1.ts", text: newSourceText }; + }); - const program4 = updateProgram(program3, program3.getRootFileNames(), options, f => { - const newSourceText = f[indexOfF1].text.updateReferences(""); - f[indexOfF1] = { name: "f1.ts", text: newSourceText }; - }); + { + const program3Diagnostics = program3.getSemanticDiagnostics(program3.getSourceFile("f2.ts")); + assert.lengthOf(program3Diagnostics, expectedErrors, `typerefs2 was unused, so diagnostics should be unaffected.`); + + assert.deepEqual(program3.host.getTrace(), [ + "======== Resolving module './b1' from 'f1.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'b1.ts' exist - use it as a name resolution result.", + "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", + "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs2/package.json' does not exist.", + "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", + "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", + "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'." + ], "program3: reuse module resolutions in f2 since it is unchanged"); + } - { - const program4Diagnostics = program4.getSemanticDiagnostics(program4.getSourceFile("f2.ts")); - assert.lengthOf(program4Diagnostics, expectedErrors, `a1.ts was unused, so diagnostics should be unaffected.`); - assert.deepEqual(program4.host.getTrace(), [ - "======== Resolving module './b1' from 'f1.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'b1.ts' exist - use it as a name resolution result.", - "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", - "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", - "Resolving with primary search path 'node_modules/@types'.", - "File 'node_modules/@types/typerefs2/package.json' does not exist.", - "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", - "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", - "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", - "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", - ], "program_4: reuse module resolutions in f2 since it is unchanged"); - } + const program4 = updateProgram(program3, program3.getRootFileNames(), options, f => { + const newSourceText = f[indexOfF1].text.updateReferences(""); + f[indexOfF1] = { name: "f1.ts", text: newSourceText }; + }); - const program5 = updateProgram(program4, program4.getRootFileNames(), options, f => { - const newSourceText = f[indexOfF1].text.updateImportsAndExports(`import { B } from './b1';`); - f[indexOfF1] = { name: "f1.ts", text: newSourceText }; - }); + { + const program4Diagnostics = program4.getSemanticDiagnostics(program4.getSourceFile("f2.ts")); + assert.lengthOf(program4Diagnostics, expectedErrors, `a1.ts was unused, so diagnostics should be unaffected.`); + + assert.deepEqual(program4.host.getTrace(), [ + "======== Resolving module './b1' from 'f1.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'b1.ts' exist - use it as a name resolution result.", + "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", + "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs2/package.json' does not exist.", + "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", + "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", + "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", + ], "program_4: reuse module resolutions in f2 since it is unchanged"); + } - { - const program5Diagnostics = program5.getSemanticDiagnostics(program5.getSourceFile("f2.ts")); - assert.lengthOf(program5Diagnostics, ++expectedErrors, `import of BB in f1 fails. BB is of type any. Add one error`); + const program5 = updateProgram(program4, program4.getRootFileNames(), options, f => { + const newSourceText = f[indexOfF1].text.updateImportsAndExports(`import { B } from './b1';`); + f[indexOfF1] = { name: "f1.ts", text: newSourceText }; + }); - assert.deepEqual(program5.host.getTrace(), [ - "======== Resolving module './b1' from 'f1.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'b1.ts' exist - use it as a name resolution result.", - "======== Module name './b1' was successfully resolved to 'b1.ts'. ========" - ], "program_5: exports do not affect program structure, so f2's resolutions are silently reused."); - } + { + const program5Diagnostics = program5.getSemanticDiagnostics(program5.getSourceFile("f2.ts")); + assert.lengthOf(program5Diagnostics, ++expectedErrors, `import of BB in f1 fails. BB is of type any. Add one error`); - const program6 = updateProgram(program5, program5.getRootFileNames(), options, f => { - const newSourceText = f[indexOfF1].text.updateProgram(""); - f[indexOfF1] = { name: "f1.ts", text: newSourceText }; - }); + assert.deepEqual(program5.host.getTrace(), [ + "======== Resolving module './b1' from 'f1.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'b1.ts' exist - use it as a name resolution result.", + "======== Module name './b1' was successfully resolved to 'b1.ts'. ========" + ], "program_5: exports do not affect program structure, so f2's resolutions are silently reused."); + } - { - const program6Diagnostics = program6.getSemanticDiagnostics(program6.getSourceFile("f2.ts")); - assert.lengthOf(program6Diagnostics, expectedErrors, `import of BB in f1 fails.`); + const program6 = updateProgram(program5, program5.getRootFileNames(), options, f => { + const newSourceText = f[indexOfF1].text.updateProgram(""); + f[indexOfF1] = { name: "f1.ts", text: newSourceText }; + }); - assert.deepEqual(program6.host.getTrace(), [ - "======== Resolving module './b1' from 'f1.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'b1.ts' exist - use it as a name resolution result.", - "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", - "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", - "Resolving with primary search path 'node_modules/@types'.", - "File 'node_modules/@types/typerefs2/package.json' does not exist.", - "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", - "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", - "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", - "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", - ], "program_6: reuse module resolutions in f2 since it is unchanged"); - } + { + const program6Diagnostics = program6.getSemanticDiagnostics(program6.getSourceFile("f2.ts")); + assert.lengthOf(program6Diagnostics, expectedErrors, `import of BB in f1 fails.`); + + assert.deepEqual(program6.host.getTrace(), [ + "======== Resolving module './b1' from 'f1.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'b1.ts' exist - use it as a name resolution result.", + "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", + "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs2/package.json' does not exist.", + "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", + "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", + "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", + ], "program_6: reuse module resolutions in f2 since it is unchanged"); + } - const program7 = updateProgram(program6, program6.getRootFileNames(), options, f => { - const newSourceText = f[indexOfF1].text.updateImportsAndExports(""); - f[indexOfF1] = { name: "f1.ts", text: newSourceText }; - }); + const program7 = updateProgram(program6, program6.getRootFileNames(), options, f => { + const newSourceText = f[indexOfF1].text.updateImportsAndExports(""); + f[indexOfF1] = { name: "f1.ts", text: newSourceText }; + }); - { - const program7Diagnostics = program7.getSemanticDiagnostics(program7.getSourceFile("f2.ts")); - assert.lengthOf(program7Diagnostics, expectedErrors, `removing import is noop with respect to program, so no change in diagnostics.`); + { + const program7Diagnostics = program7.getSemanticDiagnostics(program7.getSourceFile("f2.ts")); + assert.lengthOf(program7Diagnostics, expectedErrors, `removing import is noop with respect to program, so no change in diagnostics.`); + + assert.deepEqual(program7.host.getTrace(), [ + "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs2/package.json' does not exist.", + "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", + "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", + "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", + ], "program_7 should reuse module resolutions in f2 since it is unchanged"); + } + }); - assert.deepEqual(program7.host.getTrace(), [ - "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", - "Resolving with primary search path 'node_modules/@types'.", - "File 'node_modules/@types/typerefs2/package.json' does not exist.", - "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", - "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", - "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", - "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", - ], "program_7 should reuse module resolutions in f2 since it is unchanged"); - } - }); + describe("redirects", () => { + const axIndex = "/node_modules/a/node_modules/x/index.d.ts"; + const axPackage = "/node_modules/a/node_modules/x/package.json"; + const bxIndex = "/node_modules/b/node_modules/x/index.d.ts"; + const bxPackage = "/node_modules/b/node_modules/x/package.json"; + const root = "/a.ts"; + const compilerOptions = { target, moduleResolution: ModuleResolutionKind.NodeJs }; - describe("redirects", () => { - const axIndex = "/node_modules/a/node_modules/x/index.d.ts"; - const axPackage = "/node_modules/a/node_modules/x/package.json"; - const bxIndex = "/node_modules/b/node_modules/x/index.d.ts"; - const bxPackage = "/node_modules/b/node_modules/x/package.json"; - const root = "/a.ts"; - const compilerOptions = { target, moduleResolution: ModuleResolutionKind.NodeJs }; - - function createRedirectProgram(useGetSourceFileByPath: boolean, options?: { bText: string, bVersion: string }): ProgramWithSourceTexts { - const files: NamedSourceText[] = [ - { - name: "/node_modules/a/index.d.ts", - text: SourceText.New("", 'import X from "x";', "export function a(x: X): void;"), - }, - { - name: axIndex, - text: SourceText.New("", "", "export default class X { private x: number; }"), - }, - { - name: axPackage, - text: SourceText.New("", "", JSON.stringify({ name: "x", version: "1.2.3" })), - }, - { - name: "/node_modules/b/index.d.ts", - text: SourceText.New("", 'import X from "x";', "export const b: X;"), - }, - { - name: bxIndex, - text: SourceText.New("", "", options ? options.bText : "export default class X { private x: number; }"), - }, - { - name: bxPackage, - text: SourceText.New("", "", JSON.stringify({ name: "x", version: options ? options.bVersion : "1.2.3" })), - }, - { - name: root, - text: SourceText.New("", 'import { a } from "a"; import { b } from "b";', "a(b)"), - }, - ]; - - return newProgram(files, [root], compilerOptions, useGetSourceFileByPath); - } + function createRedirectProgram(useGetSourceFileByPath: boolean, options?: { bText: string, bVersion: string }): ProgramWithSourceTexts { + const files: NamedSourceText[] = [ + { + name: "/node_modules/a/index.d.ts", + text: SourceText.New("", 'import X from "x";', "export function a(x: X): void;"), + }, + { + name: axIndex, + text: SourceText.New("", "", "export default class X { private x: number; }"), + }, + { + name: axPackage, + text: SourceText.New("", "", JSON.stringify({ name: "x", version: "1.2.3" })), + }, + { + name: "/node_modules/b/index.d.ts", + text: SourceText.New("", 'import X from "x";', "export const b: X;"), + }, + { + name: bxIndex, + text: SourceText.New("", "", options ? options.bText : "export default class X { private x: number; }"), + }, + { + name: bxPackage, + text: SourceText.New("", "", JSON.stringify({ name: "x", version: options ? options.bVersion : "1.2.3" })), + }, + { + name: root, + text: SourceText.New("", 'import { a } from "a"; import { b } from "b";', "a(b)"), + }, + ]; - function updateRedirectProgram(program: ProgramWithSourceTexts, updater: (files: NamedSourceText[]) => void, useGetSourceFileByPath: boolean): ProgramWithSourceTexts { - return updateProgram(program, [root], compilerOptions, updater, /*newTexts*/ undefined, useGetSourceFileByPath); - } + return newProgram(files, [root], compilerOptions, useGetSourceFileByPath); + } - function verifyRedirects(useGetSourceFileByPath: boolean) { - it("No changes -> redirect not broken", () => { - const program1 = createRedirectProgram(useGetSourceFileByPath); - - const program2 = updateRedirectProgram(program1, files => { - updateProgramText(files, root, "const x = 1;"); - }, useGetSourceFileByPath); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); - assert.lengthOf(program2.getSemanticDiagnostics(), 0); - }); - - it("Target changes -> redirect broken", () => { - const program1 = createRedirectProgram(useGetSourceFileByPath); - assert.lengthOf(program1.getSemanticDiagnostics(), 0); - - const program2 = updateRedirectProgram(program1, files => { - updateProgramText(files, axIndex, "export default class X { private x: number; private y: number; }"); - updateProgramText(files, axPackage, JSON.stringify('{ name: "x", version: "1.2.4" }')); - }, useGetSourceFileByPath); - assert.equal(program2.structureIsReused, StructureIsReused.Not); - assert.lengthOf(program2.getSemanticDiagnostics(), 1); - }); - - it("Underlying changes -> redirect broken", () => { - const program1 = createRedirectProgram(useGetSourceFileByPath); - - const program2 = updateRedirectProgram(program1, files => { - updateProgramText(files, bxIndex, "export default class X { private x: number; private y: number; }"); - updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.4" })); - }, useGetSourceFileByPath); - assert.equal(program2.structureIsReused, StructureIsReused.Not); - assert.lengthOf(program2.getSemanticDiagnostics(), 1); - }); - - it("Previously duplicate packages -> program structure not reused", () => { - const program1 = createRedirectProgram(useGetSourceFileByPath, { bVersion: "1.2.4", bText: "export = class X { private x: number; }" }); - - const program2 = updateRedirectProgram(program1, files => { - updateProgramText(files, bxIndex, "export default class X { private x: number; }"); - updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.3" })); - }, useGetSourceFileByPath); - assert.equal(program2.structureIsReused, StructureIsReused.Not); - assert.deepEqual(program2.getSemanticDiagnostics(), []); - }); - } + function updateRedirectProgram(program: ProgramWithSourceTexts, updater: (files: NamedSourceText[]) => void, useGetSourceFileByPath: boolean): ProgramWithSourceTexts { + return updateProgram(program, [root], compilerOptions, updater, /*newTexts*/ undefined, useGetSourceFileByPath); + } + + function verifyRedirects(useGetSourceFileByPath: boolean) { + it("No changes -> redirect not broken", () => { + const program1 = createRedirectProgram(useGetSourceFileByPath); + + const program2 = updateRedirectProgram(program1, files => { + updateProgramText(files, root, "const x = 1;"); + }, useGetSourceFileByPath); + assert.equal(program2.structureIsReused, StructureIsReused.Completely); + assert.lengthOf(program2.getSemanticDiagnostics(), 0); + }); + + it("Target changes -> redirect broken", () => { + const program1 = createRedirectProgram(useGetSourceFileByPath); + assert.lengthOf(program1.getSemanticDiagnostics(), 0); + + const program2 = updateRedirectProgram(program1, files => { + updateProgramText(files, axIndex, "export default class X { private x: number; private y: number; }"); + updateProgramText(files, axPackage, JSON.stringify('{ name: "x", version: "1.2.4" }')); + }, useGetSourceFileByPath); + assert.equal(program2.structureIsReused, StructureIsReused.Not); + assert.lengthOf(program2.getSemanticDiagnostics(), 1); + }); + + it("Underlying changes -> redirect broken", () => { + const program1 = createRedirectProgram(useGetSourceFileByPath); - describe("when host implements getSourceFile", () => { - verifyRedirects(/*useGetSourceFileByPath*/ false); + const program2 = updateRedirectProgram(program1, files => { + updateProgramText(files, bxIndex, "export default class X { private x: number; private y: number; }"); + updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.4" })); + }, useGetSourceFileByPath); + assert.equal(program2.structureIsReused, StructureIsReused.Not); + assert.lengthOf(program2.getSemanticDiagnostics(), 1); }); - describe("when host implements getSourceFileByPath", () => { - verifyRedirects(/*useGetSourceFileByPath*/ true); + + it("Previously duplicate packages -> program structure not reused", () => { + const program1 = createRedirectProgram(useGetSourceFileByPath, { bVersion: "1.2.4", bText: "export = class X { private x: number; }" }); + + const program2 = updateRedirectProgram(program1, files => { + updateProgramText(files, bxIndex, "export default class X { private x: number; }"); + updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.3" })); + }, useGetSourceFileByPath); + assert.equal(program2.structureIsReused, StructureIsReused.Not); + assert.deepEqual(program2.getSemanticDiagnostics(), []); }); + } + + describe("when host implements getSourceFile", () => { + verifyRedirects(/*useGetSourceFileByPath*/ false); + }); + describe("when host implements getSourceFileByPath", () => { + verifyRedirects(/*useGetSourceFileByPath*/ true); }); }); +}); - describe("unittests:: Reuse program structure:: host is optional", () => { - it("should work if host is not provided", () => { - createProgram([], {}); - }); +describe("unittests:: Reuse program structure:: host is optional", () => { + it("should work if host is not provided", () => { + createProgram([], {}); }); +}); + +type File = TestFSWithWatch.File; +import createTestSystem = TestFSWithWatch.createWatchedSystem; +import libFile = TestFSWithWatch.libFile; + +describe("unittests:: Reuse program structure:: isProgramUptoDate", () => { + function getWhetherProgramIsUptoDate( + program: Program, + newRootFileNames: string[], + newOptions: CompilerOptions + ) { + return isProgramUptoDate( + program, newRootFileNames, newOptions, + path => program.getSourceFileByPath(path)!.version, /*fileExists*/ returnFalse, + /*hasInvalidatedResolution*/ returnFalse, + /*hasChangedAutomaticTypeDirectiveNames*/ undefined, + /*getParsedCommandLine*/ returnUndefined, + /*projectReferences*/ undefined + ); + } - type File = TestFSWithWatch.File; - import createTestSystem = TestFSWithWatch.createWatchedSystem; - import libFile = TestFSWithWatch.libFile; + function duplicate(options: CompilerOptions): CompilerOptions; + function duplicate(fileNames: string[]): string[]; + function duplicate(filesOrOptions: CompilerOptions | string[]) { + return JSON.parse(JSON.stringify(filesOrOptions)); + } - describe("unittests:: Reuse program structure:: isProgramUptoDate", () => { - function getWhetherProgramIsUptoDate( + describe("should return true when there is no change in compiler options and", () => { + function verifyProgramIsUptoDate( program: Program, newRootFileNames: string[], newOptions: CompilerOptions ) { - return isProgramUptoDate( - program, newRootFileNames, newOptions, - path => program.getSourceFileByPath(path)!.version, /*fileExists*/ returnFalse, - /*hasInvalidatedResolution*/ returnFalse, - /*hasChangedAutomaticTypeDirectiveNames*/ undefined, - /*getParsedCommandLine*/ returnUndefined, - /*projectReferences*/ undefined - ); + const actual = getWhetherProgramIsUptoDate(program, newRootFileNames, newOptions); + assert.isTrue(actual); } - function duplicate(options: CompilerOptions): CompilerOptions; - function duplicate(fileNames: string[]): string[]; - function duplicate(filesOrOptions: CompilerOptions | string[]) { - return JSON.parse(JSON.stringify(filesOrOptions)); + function verifyProgramWithoutConfigFile(system: System, rootFiles: string[], options: CompilerOptions) { + const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ + rootFiles, + options, + watchOptions: undefined, + system + })).getCurrentProgram().getProgram(); + verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); } - describe("should return true when there is no change in compiler options and", () => { - function verifyProgramIsUptoDate( - program: Program, - newRootFileNames: string[], - newOptions: CompilerOptions - ) { - const actual = getWhetherProgramIsUptoDate(program, newRootFileNames, newOptions); - assert.isTrue(actual); - } - - function verifyProgramWithoutConfigFile(system: System, rootFiles: string[], options: CompilerOptions) { - const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ - rootFiles, - options, - watchOptions: undefined, - system - })).getCurrentProgram().getProgram(); - verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); - } + function verifyProgramWithConfigFile(system: System, configFileName: string) { + const program = createWatchProgram(createWatchCompilerHostOfConfigFile({ + configFileName, + system + })).getCurrentProgram().getProgram(); + const { fileNames, options } = parseConfigFileWithSystem(configFileName, {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, notImplemented)!; // TODO: GH#18217 + verifyProgramIsUptoDate(program, fileNames, options); + } - function verifyProgramWithConfigFile(system: System, configFileName: string) { - const program = createWatchProgram(createWatchCompilerHostOfConfigFile({ - configFileName, - system - })).getCurrentProgram().getProgram(); - const { fileNames, options } = parseConfigFileWithSystem(configFileName, {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, notImplemented)!; // TODO: GH#18217 - verifyProgramIsUptoDate(program, fileNames, options); - } + function verifyProgram(files: File[], rootFiles: string[], options: CompilerOptions, configFile: string) { + const system = createTestSystem(files); + verifyProgramWithoutConfigFile(system, rootFiles, options); + verifyProgramWithConfigFile(system, configFile); + } - function verifyProgram(files: File[], rootFiles: string[], options: CompilerOptions, configFile: string) { - const system = createTestSystem(files); - verifyProgramWithoutConfigFile(system, rootFiles, options); - verifyProgramWithConfigFile(system, configFile); - } + it("has empty options", () => { + const file1: File = { + path: "/a/b/file1.ts", + content: "let x = 1" + }; + const file2: File = { + path: "/a/b/file2.ts", + content: "let y = 1" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + verifyProgram([file1, file2, libFile, configFile], [file1.path, file2.path], {}, configFile.path); + }); - it("has empty options", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: "let x = 1" - }; - const file2: File = { - path: "/a/b/file2.ts", - content: "let y = 1" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: "{}" - }; - verifyProgram([file1, file2, libFile, configFile], [file1.path, file2.path], {}, configFile.path); - }); + it("has lib specified in the options", () => { + const compilerOptions: CompilerOptions = { lib: ["es5", "es2015.promise"] }; + const app: File = { + path: "/src/app.ts", + content: "var x: Promise;" + }; + const configFile: File = { + path: "/src/tsconfig.json", + content: JSON.stringify({ compilerOptions }) + }; + const es5Lib: File = { + path: "/compiler/lib.es5.d.ts", + content: "declare const eval: any" + }; + const es2015Promise: File = { + path: "/compiler/lib.es2015.promise.d.ts", + content: "declare class Promise {}" + }; - it("has lib specified in the options", () => { - const compilerOptions: CompilerOptions = { lib: ["es5", "es2015.promise"] }; - const app: File = { - path: "/src/app.ts", - content: "var x: Promise;" - }; - const configFile: File = { - path: "/src/tsconfig.json", - content: JSON.stringify({ compilerOptions }) - }; - const es5Lib: File = { - path: "/compiler/lib.es5.d.ts", - content: "declare const eval: any" - }; - const es2015Promise: File = { - path: "/compiler/lib.es2015.promise.d.ts", - content: "declare class Promise {}" - }; - - verifyProgram([app, configFile, es5Lib, es2015Promise], [app.path], compilerOptions, configFile.path); - }); + verifyProgram([app, configFile, es5Lib, es2015Promise], [app.path], compilerOptions, configFile.path); + }); - it("has paths specified in the options", () => { - const compilerOptions: CompilerOptions = { - baseUrl: ".", - paths: { - "*": [ - "packages/mail/data/*", - "packages/styles/*", - "*" - ] - } - }; - const app: File = { - path: "/src/packages/framework/app.ts", - content: 'import classc from "module1/lib/file1";\ + it("has paths specified in the options", () => { + const compilerOptions: CompilerOptions = { + baseUrl: ".", + paths: { + "*": [ + "packages/mail/data/*", + "packages/styles/*", + "*" + ] + } + }; + const app: File = { + path: "/src/packages/framework/app.ts", + content: 'import classc from "module1/lib/file1";\ import classD from "module3/file3";\ let x = new classc();\ let y = new classD();' - }; - const module1: File = { - path: "/src/packages/mail/data/module1/lib/file1.ts", - content: 'import classc from "module2/file2";export default classc;', - }; - const module2: File = { - path: "/src/packages/mail/data/module1/lib/module2/file2.ts", - content: 'class classc { method2() { return "hello"; } }\nexport default classc', - }; - const module3: File = { - path: "/src/packages/styles/module3/file3.ts", - content: "class classD { method() { return 10; } }\nexport default classD;" - }; - const configFile: File = { - path: "/src/tsconfig.json", - content: JSON.stringify({ compilerOptions }) - }; - - verifyProgram([app, module1, module2, module3, libFile, configFile], [app.path], compilerOptions, configFile.path); - }); + }; + const module1: File = { + path: "/src/packages/mail/data/module1/lib/file1.ts", + content: 'import classc from "module2/file2";export default classc;', + }; + const module2: File = { + path: "/src/packages/mail/data/module1/lib/module2/file2.ts", + content: 'class classc { method2() { return "hello"; } }\nexport default classc', + }; + const module3: File = { + path: "/src/packages/styles/module3/file3.ts", + content: "class classD { method() { return 10; } }\nexport default classD;" + }; + const configFile: File = { + path: "/src/tsconfig.json", + content: JSON.stringify({ compilerOptions }) + }; - it("has include paths specified in tsconfig file", () => { - const compilerOptions: CompilerOptions = { - baseUrl: ".", - paths: { - "*": [ - "packages/mail/data/*", - "packages/styles/*", - "*" - ] - } - }; - const app: File = { - path: "/src/packages/framework/app.ts", - content: 'import classc from "module1/lib/file1";\ + verifyProgram([app, module1, module2, module3, libFile, configFile], [app.path], compilerOptions, configFile.path); + }); + + it("has include paths specified in tsconfig file", () => { + const compilerOptions: CompilerOptions = { + baseUrl: ".", + paths: { + "*": [ + "packages/mail/data/*", + "packages/styles/*", + "*" + ] + } + }; + const app: File = { + path: "/src/packages/framework/app.ts", + content: 'import classc from "module1/lib/file1";\ import classD from "module3/file3";\ let x = new classc();\ let y = new classD();' - }; - const module1: File = { - path: "/src/packages/mail/data/module1/lib/file1.ts", - content: 'import classc from "module2/file2";export default classc;', - }; - const module2: File = { - path: "/src/packages/mail/data/module1/lib/module2/file2.ts", - content: 'class classc { method2() { return "hello"; } }\nexport default classc', - }; - const module3: File = { - path: "/src/packages/styles/module3/file3.ts", - content: "class classD { method() { return 10; } }\nexport default classD;" - }; - const configFile: File = { - path: "/src/tsconfig.json", - content: JSON.stringify({ compilerOptions, include: ["packages/**/*.ts"] }) - }; - verifyProgramWithConfigFile(createTestSystem([app, module1, module2, module3, libFile, configFile]), configFile.path); - }); - it("has the same root file names", () => { - const module1: File = { - path: "/src/packages/mail/data/module1/lib/file1.ts", - content: 'import classc from "module2/file2";export default classc;', - }; - const module2: File = { - path: "/src/packages/mail/data/module1/lib/module2/file2.ts", - content: 'class classc { method2() { return "hello"; } }\nexport default classc', - }; - const module3: File = { - path: "/src/packages/styles/module3/file3.ts", - content: "class classD { method() { return 10; } }\nexport default classD;" - }; - const rootFiles = [module1.path, module2.path, module3.path]; - const system = createTestSystem([module1, module2, module3]); - const options = {}; - const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ - rootFiles, - options, - watchOptions: undefined, - system - })).getCurrentProgram().getProgram(); - verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); - }); + }; + const module1: File = { + path: "/src/packages/mail/data/module1/lib/file1.ts", + content: 'import classc from "module2/file2";export default classc;', + }; + const module2: File = { + path: "/src/packages/mail/data/module1/lib/module2/file2.ts", + content: 'class classc { method2() { return "hello"; } }\nexport default classc', + }; + const module3: File = { + path: "/src/packages/styles/module3/file3.ts", + content: "class classD { method() { return 10; } }\nexport default classD;" + }; + const configFile: File = { + path: "/src/tsconfig.json", + content: JSON.stringify({ compilerOptions, include: ["packages/**/*.ts"] }) + }; + verifyProgramWithConfigFile(createTestSystem([app, module1, module2, module3, libFile, configFile]), configFile.path); + }); + it("has the same root file names", () => { + const module1: File = { + path: "/src/packages/mail/data/module1/lib/file1.ts", + content: 'import classc from "module2/file2";export default classc;', + }; + const module2: File = { + path: "/src/packages/mail/data/module1/lib/module2/file2.ts", + content: 'class classc { method2() { return "hello"; } }\nexport default classc', + }; + const module3: File = { + path: "/src/packages/styles/module3/file3.ts", + content: "class classD { method() { return 10; } }\nexport default classD;" + }; + const rootFiles = [module1.path, module2.path, module3.path]; + const system = createTestSystem([module1, module2, module3]); + const options = {}; + const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ + rootFiles, + options, + watchOptions: undefined, + system + })).getCurrentProgram().getProgram(); + verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); + }); + }); + describe("should return false when there is no change in compiler options but", () => { + function verifyProgramIsNotUptoDate( + program: Program, + newRootFileNames: string[], + newOptions: CompilerOptions + ) { + const actual = getWhetherProgramIsUptoDate(program, newRootFileNames, newOptions); + assert.isFalse(actual); + } + it("has more root file names", () => { + const module1: File = { + path: "/src/packages/mail/data/module1/lib/file1.ts", + content: 'import classc from "module2/file2";export default classc;', + }; + const module2: File = { + path: "/src/packages/mail/data/module1/lib/module2/file2.ts", + content: 'class classc { method2() { return "hello"; } }\nexport default classc', + }; + const module3: File = { + path: "/src/packages/styles/module3/file3.ts", + content: "class classD { method() { return 10; } }\nexport default classD;" + }; + const rootFiles = [module1.path, module2.path]; + const newRootFiles = [module1.path, module2.path, module3.path]; + const system = createTestSystem([module1, module2, module3]); + const options = {}; + const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ + rootFiles, + options, + watchOptions: undefined, + system + })).getCurrentProgram().getProgram(); + verifyProgramIsNotUptoDate(program, duplicate(newRootFiles), duplicate(options)); }); - describe("should return false when there is no change in compiler options but", () => { - function verifyProgramIsNotUptoDate( - program: Program, - newRootFileNames: string[], - newOptions: CompilerOptions - ) { - const actual = getWhetherProgramIsUptoDate(program, newRootFileNames, newOptions); - assert.isFalse(actual); - } - it("has more root file names", () => { - const module1: File = { - path: "/src/packages/mail/data/module1/lib/file1.ts", - content: 'import classc from "module2/file2";export default classc;', - }; - const module2: File = { - path: "/src/packages/mail/data/module1/lib/module2/file2.ts", - content: 'class classc { method2() { return "hello"; } }\nexport default classc', - }; - const module3: File = { - path: "/src/packages/styles/module3/file3.ts", - content: "class classD { method() { return 10; } }\nexport default classD;" - }; - const rootFiles = [module1.path, module2.path]; - const newRootFiles = [module1.path, module2.path, module3.path]; - const system = createTestSystem([module1, module2, module3]); - const options = {}; - const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ - rootFiles, - options, - watchOptions: undefined, - system - })).getCurrentProgram().getProgram(); - verifyProgramIsNotUptoDate(program, duplicate(newRootFiles), duplicate(options)); - }); - it("has one root file replaced by another", () => { - const module1: File = { - path: "/src/packages/mail/data/module1/lib/file1.ts", - content: 'import classc from "module2/file2";export default classc;', - }; - const module2: File = { - path: "/src/packages/mail/data/module1/lib/module2/file2.ts", - content: 'class classc { method2() { return "hello"; } }\nexport default classc', - }; - const module3: File = { - path: "/src/packages/styles/module3/file3.ts", - content: "class classD { method() { return 10; } }\nexport default classD;" - }; - const rootFiles = [module1.path, module2.path]; - const newRootFiles = [module2.path, module3.path]; - const system = createTestSystem([module1, module2, module3]); - const options = {}; - const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ - rootFiles, - options, - watchOptions: undefined, - system - })).getCurrentProgram().getProgram(); - verifyProgramIsNotUptoDate(program, duplicate(newRootFiles), duplicate(options)); - }); + it("has one root file replaced by another", () => { + const module1: File = { + path: "/src/packages/mail/data/module1/lib/file1.ts", + content: 'import classc from "module2/file2";export default classc;', + }; + const module2: File = { + path: "/src/packages/mail/data/module1/lib/module2/file2.ts", + content: 'class classc { method2() { return "hello"; } }\nexport default classc', + }; + const module3: File = { + path: "/src/packages/styles/module3/file3.ts", + content: "class classD { method() { return 10; } }\nexport default classD;" + }; + const rootFiles = [module1.path, module2.path]; + const newRootFiles = [module2.path, module3.path]; + const system = createTestSystem([module1, module2, module3]); + const options = {}; + const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ + rootFiles, + options, + watchOptions: undefined, + system + })).getCurrentProgram().getProgram(); + verifyProgramIsNotUptoDate(program, duplicate(newRootFiles), duplicate(options)); }); }); +}); } diff --git a/src/testRunner/unittests/semver.ts b/src/testRunner/unittests/semver.ts index 2e6a61fbad87d..5ef770269888d 100644 --- a/src/testRunner/unittests/semver.ts +++ b/src/testRunner/unittests/semver.ts @@ -1,251 +1,251 @@ namespace ts { - import theory = Utils.theory; - describe("unittests:: semver", () => { - describe("VersionRange", () => { - function assertVersionRange(version: string, good: string[], bad: string[]): () => void { - return () => { - const range = VersionRange.tryParse(version)!; - assert(range); - for (const g of good) { - assert.isTrue(range.test(g), g); - } - for (const b of bad) { - assert.isFalse(range.test(b), b); - } - }; - } - it("< works", assertVersionRange("<3.8.0", ["3.6", "3.7"], ["3.8", "3.9", "4.0"])); - it("<= works", assertVersionRange("<=3.8.0", ["3.6", "3.7", "3.8"], ["3.9", "4.0"])); - it("> works", assertVersionRange(">3.8.0", ["3.9", "4.0"], ["3.6", "3.7", "3.8"])); - it(">= works", assertVersionRange(">=3.8.0", ["3.8", "3.9", "4.0"], ["3.6", "3.7"])); +import theory = Utils.theory; +describe("unittests:: semver", () => { + describe("VersionRange", () => { + function assertVersionRange(version: string, good: string[], bad: string[]): () => void { + return () => { + const range = VersionRange.tryParse(version)!; + assert(range); + for (const g of good) { + assert.isTrue(range.test(g), g); + } + for (const b of bad) { + assert.isFalse(range.test(b), b); + } + }; + } + it("< works", assertVersionRange("<3.8.0", ["3.6", "3.7"], ["3.8", "3.9", "4.0"])); + it("<= works", assertVersionRange("<=3.8.0", ["3.6", "3.7", "3.8"], ["3.9", "4.0"])); + it("> works", assertVersionRange(">3.8.0", ["3.9", "4.0"], ["3.6", "3.7", "3.8"])); + it(">= works", assertVersionRange(">=3.8.0", ["3.8", "3.9", "4.0"], ["3.6", "3.7"])); - it("< works with prerelease", assertVersionRange("<3.8.0-0", ["3.6", "3.7"], ["3.8", "3.9", "4.0"])); - it("<= works with prerelease", assertVersionRange("<=3.8.0-0", ["3.6", "3.7"], ["3.8", "3.9", "4.0"])); - it("> works with prerelease", assertVersionRange(">3.8.0-0", ["3.8", "3.9", "4.0"], ["3.6", "3.7"])); - it(">= works with prerelease", assertVersionRange(">=3.8.0-0", ["3.8", "3.9", "4.0"], ["3.6", "3.7"])); - }); - describe("Version", () => { - function assertVersion(version: Version, [major, minor, patch, prerelease, build]: [number, number, number, string[]?, string[]?]) { - assert.strictEqual(version.major, major); - assert.strictEqual(version.minor, minor); - assert.strictEqual(version.patch, patch); - assert.deepEqual(version.prerelease, prerelease || emptyArray); - assert.deepEqual(version.build, build || emptyArray); - } - describe("new", () => { - it("text", () => { - assertVersion(new Version("1.2.3-pre.4+build.5"), [1, 2, 3, ["pre", "4"], ["build", "5"]]); - }); - it("parts", () => { - assertVersion(new Version(1, 2, 3, "pre.4", "build.5"), [1, 2, 3, ["pre", "4"], ["build", "5"]]); - assertVersion(new Version(1, 2, 3), [1, 2, 3]); - assertVersion(new Version(1, 2), [1, 2, 0]); - assertVersion(new Version(1), [1, 0, 0]); - }); + it("< works with prerelease", assertVersionRange("<3.8.0-0", ["3.6", "3.7"], ["3.8", "3.9", "4.0"])); + it("<= works with prerelease", assertVersionRange("<=3.8.0-0", ["3.6", "3.7"], ["3.8", "3.9", "4.0"])); + it("> works with prerelease", assertVersionRange(">3.8.0-0", ["3.8", "3.9", "4.0"], ["3.6", "3.7"])); + it(">= works with prerelease", assertVersionRange(">=3.8.0-0", ["3.8", "3.9", "4.0"], ["3.6", "3.7"])); + }); + describe("Version", () => { + function assertVersion(version: Version, [major, minor, patch, prerelease, build]: [number, number, number, string[]?, string[]?]) { + assert.strictEqual(version.major, major); + assert.strictEqual(version.minor, minor); + assert.strictEqual(version.patch, patch); + assert.deepEqual(version.prerelease, prerelease || emptyArray); + assert.deepEqual(version.build, build || emptyArray); + } + describe("new", () => { + it("text", () => { + assertVersion(new Version("1.2.3-pre.4+build.5"), [1, 2, 3, ["pre", "4"], ["build", "5"]]); }); - it("toString", () => { - assert.strictEqual(new Version(1, 2, 3, "pre.4", "build.5").toString(), "1.2.3-pre.4+build.5"); - assert.strictEqual(new Version(1, 2, 3, "pre.4").toString(), "1.2.3-pre.4"); - assert.strictEqual(new Version(1, 2, 3, /*prerelease*/ undefined, "build.5").toString(), "1.2.3+build.5"); - assert.strictEqual(new Version(1, 2, 3).toString(), "1.2.3"); - assert.strictEqual(new Version(1, 2).toString(), "1.2.0"); - assert.strictEqual(new Version(1).toString(), "1.0.0"); + it("parts", () => { + assertVersion(new Version(1, 2, 3, "pre.4", "build.5"), [1, 2, 3, ["pre", "4"], ["build", "5"]]); + assertVersion(new Version(1, 2, 3), [1, 2, 3]); + assertVersion(new Version(1, 2), [1, 2, 0]); + assertVersion(new Version(1), [1, 0, 0]); }); - it("compareTo", () => { - // https://semver.org/#spec-item-11 - // > Precedence is determined by the first difference when comparing each of these - // > identifiers from left to right as follows: Major, minor, and patch versions are - // > always compared numerically. - assert.strictEqual(new Version("1.0.0").compareTo(new Version("2.0.0")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.1.0")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.1")), Comparison.LessThan); - assert.strictEqual(new Version("2.0.0").compareTo(new Version("1.0.0")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.1.0").compareTo(new Version("1.0.0")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.1").compareTo(new Version("1.0.0")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.0")), Comparison.EqualTo); + }); + it("toString", () => { + assert.strictEqual(new Version(1, 2, 3, "pre.4", "build.5").toString(), "1.2.3-pre.4+build.5"); + assert.strictEqual(new Version(1, 2, 3, "pre.4").toString(), "1.2.3-pre.4"); + assert.strictEqual(new Version(1, 2, 3, /*prerelease*/ undefined, "build.5").toString(), "1.2.3+build.5"); + assert.strictEqual(new Version(1, 2, 3).toString(), "1.2.3"); + assert.strictEqual(new Version(1, 2).toString(), "1.2.0"); + assert.strictEqual(new Version(1).toString(), "1.0.0"); + }); + it("compareTo", () => { + // https://semver.org/#spec-item-11 + // > Precedence is determined by the first difference when comparing each of these + // > identifiers from left to right as follows: Major, minor, and patch versions are + // > always compared numerically. + assert.strictEqual(new Version("1.0.0").compareTo(new Version("2.0.0")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.1.0")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.1")), Comparison.LessThan); + assert.strictEqual(new Version("2.0.0").compareTo(new Version("1.0.0")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.1.0").compareTo(new Version("1.0.0")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.1").compareTo(new Version("1.0.0")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.0")), Comparison.EqualTo); - // https://semver.org/#spec-item-11 - // > When major, minor, and patch are equal, a pre-release version has lower - // > precedence than a normal version. - assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.0-pre")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.1-pre").compareTo(new Version("1.0.0")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-pre").compareTo(new Version("1.0.0")), Comparison.LessThan); + // https://semver.org/#spec-item-11 + // > When major, minor, and patch are equal, a pre-release version has lower + // > precedence than a normal version. + assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.0-pre")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.1-pre").compareTo(new Version("1.0.0")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-pre").compareTo(new Version("1.0.0")), Comparison.LessThan); - // https://semver.org/#spec-item-11 - // > identifiers consisting of only digits are compared numerically - assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-1")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-1").compareTo(new Version("1.0.0-0")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-2").compareTo(new Version("1.0.0-10")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-10").compareTo(new Version("1.0.0-2")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-0")), Comparison.EqualTo); + // https://semver.org/#spec-item-11 + // > identifiers consisting of only digits are compared numerically + assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-1")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-1").compareTo(new Version("1.0.0-0")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-2").compareTo(new Version("1.0.0-10")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-10").compareTo(new Version("1.0.0-2")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-0")), Comparison.EqualTo); - // https://semver.org/#spec-item-11 - // > identifiers with letters or hyphens are compared lexically in ASCII sort order. - assert.strictEqual(new Version("1.0.0-a").compareTo(new Version("1.0.0-b")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-a-2").compareTo(new Version("1.0.0-a-10")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-b").compareTo(new Version("1.0.0-a")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-a").compareTo(new Version("1.0.0-a")), Comparison.EqualTo); - assert.strictEqual(new Version("1.0.0-A").compareTo(new Version("1.0.0-a")), Comparison.LessThan); + // https://semver.org/#spec-item-11 + // > identifiers with letters or hyphens are compared lexically in ASCII sort order. + assert.strictEqual(new Version("1.0.0-a").compareTo(new Version("1.0.0-b")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-a-2").compareTo(new Version("1.0.0-a-10")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-b").compareTo(new Version("1.0.0-a")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-a").compareTo(new Version("1.0.0-a")), Comparison.EqualTo); + assert.strictEqual(new Version("1.0.0-A").compareTo(new Version("1.0.0-a")), Comparison.LessThan); - // https://semver.org/#spec-item-11 - // > Numeric identifiers always have lower precedence than non-numeric identifiers. - assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-alpha")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-0")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-0")), Comparison.EqualTo); - assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-alpha")), Comparison.EqualTo); + // https://semver.org/#spec-item-11 + // > Numeric identifiers always have lower precedence than non-numeric identifiers. + assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-alpha")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-0")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-0")), Comparison.EqualTo); + assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-alpha")), Comparison.EqualTo); - // https://semver.org/#spec-item-11 - // > A larger set of pre-release fields has a higher precedence than a smaller set, if all - // > of the preceding identifiers are equal. - assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-alpha.0")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-alpha.0").compareTo(new Version("1.0.0-alpha")), Comparison.GreaterThan); + // https://semver.org/#spec-item-11 + // > A larger set of pre-release fields has a higher precedence than a smaller set, if all + // > of the preceding identifiers are equal. + assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-alpha.0")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-alpha.0").compareTo(new Version("1.0.0-alpha")), Comparison.GreaterThan); - // https://semver.org/#spec-item-11 - // > Precedence for two pre-release versions with the same major, minor, and patch version - // > MUST be determined by comparing each dot separated identifier from left to right until - // > a difference is found [...] - assert.strictEqual(new Version("1.0.0-a.0.b.1").compareTo(new Version("1.0.0-a.0.b.2")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-a.0.b.1").compareTo(new Version("1.0.0-b.0.a.1")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-a.0.b.2").compareTo(new Version("1.0.0-a.0.b.1")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-b.0.a.1").compareTo(new Version("1.0.0-a.0.b.1")), Comparison.GreaterThan); + // https://semver.org/#spec-item-11 + // > Precedence for two pre-release versions with the same major, minor, and patch version + // > MUST be determined by comparing each dot separated identifier from left to right until + // > a difference is found [...] + assert.strictEqual(new Version("1.0.0-a.0.b.1").compareTo(new Version("1.0.0-a.0.b.2")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-a.0.b.1").compareTo(new Version("1.0.0-b.0.a.1")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-a.0.b.2").compareTo(new Version("1.0.0-a.0.b.1")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-b.0.a.1").compareTo(new Version("1.0.0-a.0.b.1")), Comparison.GreaterThan); - // https://semver.org/#spec-item-11 - // > Build metadata does not figure into precedence - assert.strictEqual(new Version("1.0.0+build").compareTo(new Version("1.0.0")), Comparison.EqualTo); - }); - it("increment", () => { - assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("major"), [2, 0, 0]); - assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("minor"), [1, 3, 0]); - assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("patch"), [1, 2, 4]); - }); + // https://semver.org/#spec-item-11 + // > Build metadata does not figure into precedence + assert.strictEqual(new Version("1.0.0+build").compareTo(new Version("1.0.0")), Comparison.EqualTo); }); - describe("VersionRange", () => { - function assertRange(rangeText: string, versionText: string, inRange = true) { - const range = new VersionRange(rangeText); - const version = new Version(versionText); - assert.strictEqual(range.test(version), inRange, `Expected version '${version}' ${inRange ? `to be` : `to not be`} in range '${rangeText}' (${range})`); - } - theory("comparators", assertRange, [ - ["", "1.0.0"], - ["*", "1.0.0"], - ["1", "1.0.0"], - ["1", "2.0.0", false], - ["1.0", "1.0.0"], - ["1.0", "1.1.0", false], - ["1.0.0", "1.0.0"], - ["1.0.0", "1.0.1", false], - ["1.*", "1.0.0"], - ["1.*", "2.0.0", false], - ["1.x", "1.0.0"], - ["1.x", "2.0.0", false], - ["=1", "1.0.0"], - ["=1", "1.1.0"], - ["=1", "1.0.1"], - ["=1.0", "1.0.0"], - ["=1.0", "1.0.1"], - ["=1.0.0", "1.0.0"], - ["=*", "0.0.0"], - ["=*", "1.0.0"], - [">1", "2"], - [">1.0", "1.1"], - [">1.0.0", "1.0.1"], - [">1.0.0", "1.0.1-pre"], - [">*", "0.0.0", false], - [">*", "1.0.0", false], - [">=1", "1.0.0"], - [">=1.0", "1.0.0"], - [">=1.0.0", "1.0.0"], - [">=1.0.0", "1.0.1-pre"], - [">=*", "0.0.0"], - [">=*", "1.0.0"], - ["<2", "1.0.0"], - ["<2.1", "2.0.0"], - ["<2.0.1", "2.0.0"], - ["<2.0.0", "2.0.0-pre"], - ["<*", "0.0.0", false], - ["<*", "1.0.0", false], - ["<=2", "2.0.0"], - ["<=2.1", "2.1.0"], - ["<=2.0.1", "2.0.1"], - ["<=*", "0.0.0"], - ["<=*", "1.0.0"], - ]); - theory("conjunctions", assertRange, [ - [">1.0.0 <2.0.0", "1.0.1"], - [">1.0.0 <2.0.0", "2.0.0", false], - [">1.0.0 <2.0.0", "1.0.0", false], - [">1 >2", "3.0.0"], - ]); - theory("disjunctions", assertRange, [ - [">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "1.0.0"], - [">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "2.0.0", false], - [">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "3.0.0"], - ]); - theory("hyphen", assertRange, [ - ["1.0.0 - 2.0.0", "1.0.0"], - ["1.0.0 - 2.0.0", "2.0.0"], - ["1.0.0 - 2.0.0", "3.0.0", false], - ]); - theory("tilde", assertRange, [ - ["~0", "0.0.0"], - ["~0", "0.1.0"], - ["~0", "0.1.2"], - ["~0", "0.1.9"], - ["~0", "1.0.0", false], - ["~0.1", "0.1.0"], - ["~0.1", "0.1.2"], - ["~0.1", "0.1.9"], - ["~0.1", "0.2.0", false], - ["~0.1.2", "0.1.2"], - ["~0.1.2", "0.1.9"], - ["~0.1.2", "0.2.0", false], - ["~1", "1.0.0"], - ["~1", "1.2.0"], - ["~1", "1.2.3"], - ["~1", "1.2.0"], - ["~1", "1.2.3"], - ["~1", "0.0.0", false], - ["~1", "2.0.0", false], - ["~1.2", "1.2.0"], - ["~1.2", "1.2.3"], - ["~1.2", "1.1.0", false], - ["~1.2", "1.3.0", false], - ["~1.2.3", "1.2.3"], - ["~1.2.3", "1.2.9"], - ["~1.2.3", "1.1.0", false], - ["~1.2.3", "1.3.0", false], - ]); - theory("caret", assertRange, [ - ["^0", "0.0.0"], - ["^0", "0.1.0"], - ["^0", "0.9.0"], - ["^0", "0.1.2"], - ["^0", "0.1.9"], - ["^0", "1.0.0", false], - ["^0.1", "0.1.0"], - ["^0.1", "0.1.2"], - ["^0.1", "0.1.9"], - ["^0.1.2", "0.1.2"], - ["^0.1.2", "0.1.9"], - ["^0.1.2", "0.0.0", false], - ["^0.1.2", "0.2.0", false], - ["^0.1.2", "1.0.0", false], - ["^1", "1.0.0"], - ["^1", "1.2.0"], - ["^1", "1.2.3"], - ["^1", "1.9.0"], - ["^1", "0.0.0", false], - ["^1", "2.0.0", false], - ["^1.2", "1.2.0"], - ["^1.2", "1.2.3"], - ["^1.2", "1.9.0"], - ["^1.2", "1.1.0", false], - ["^1.2", "2.0.0", false], - ["^1.2.3", "1.2.3"], - ["^1.2.3", "1.9.0"], - ["^1.2.3", "1.2.2", false], - ["^1.2.3", "2.0.0", false], - ]); + it("increment", () => { + assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("major"), [2, 0, 0]); + assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("minor"), [1, 3, 0]); + assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("patch"), [1, 2, 4]); }); }); + describe("VersionRange", () => { + function assertRange(rangeText: string, versionText: string, inRange = true) { + const range = new VersionRange(rangeText); + const version = new Version(versionText); + assert.strictEqual(range.test(version), inRange, `Expected version '${version}' ${inRange ? `to be` : `to not be`} in range '${rangeText}' (${range})`); + } + theory("comparators", assertRange, [ + ["", "1.0.0"], + ["*", "1.0.0"], + ["1", "1.0.0"], + ["1", "2.0.0", false], + ["1.0", "1.0.0"], + ["1.0", "1.1.0", false], + ["1.0.0", "1.0.0"], + ["1.0.0", "1.0.1", false], + ["1.*", "1.0.0"], + ["1.*", "2.0.0", false], + ["1.x", "1.0.0"], + ["1.x", "2.0.0", false], + ["=1", "1.0.0"], + ["=1", "1.1.0"], + ["=1", "1.0.1"], + ["=1.0", "1.0.0"], + ["=1.0", "1.0.1"], + ["=1.0.0", "1.0.0"], + ["=*", "0.0.0"], + ["=*", "1.0.0"], + [">1", "2"], + [">1.0", "1.1"], + [">1.0.0", "1.0.1"], + [">1.0.0", "1.0.1-pre"], + [">*", "0.0.0", false], + [">*", "1.0.0", false], + [">=1", "1.0.0"], + [">=1.0", "1.0.0"], + [">=1.0.0", "1.0.0"], + [">=1.0.0", "1.0.1-pre"], + [">=*", "0.0.0"], + [">=*", "1.0.0"], + ["<2", "1.0.0"], + ["<2.1", "2.0.0"], + ["<2.0.1", "2.0.0"], + ["<2.0.0", "2.0.0-pre"], + ["<*", "0.0.0", false], + ["<*", "1.0.0", false], + ["<=2", "2.0.0"], + ["<=2.1", "2.1.0"], + ["<=2.0.1", "2.0.1"], + ["<=*", "0.0.0"], + ["<=*", "1.0.0"], + ]); + theory("conjunctions", assertRange, [ + [">1.0.0 <2.0.0", "1.0.1"], + [">1.0.0 <2.0.0", "2.0.0", false], + [">1.0.0 <2.0.0", "1.0.0", false], + [">1 >2", "3.0.0"], + ]); + theory("disjunctions", assertRange, [ + [">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "1.0.0"], + [">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "2.0.0", false], + [">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "3.0.0"], + ]); + theory("hyphen", assertRange, [ + ["1.0.0 - 2.0.0", "1.0.0"], + ["1.0.0 - 2.0.0", "2.0.0"], + ["1.0.0 - 2.0.0", "3.0.0", false], + ]); + theory("tilde", assertRange, [ + ["~0", "0.0.0"], + ["~0", "0.1.0"], + ["~0", "0.1.2"], + ["~0", "0.1.9"], + ["~0", "1.0.0", false], + ["~0.1", "0.1.0"], + ["~0.1", "0.1.2"], + ["~0.1", "0.1.9"], + ["~0.1", "0.2.0", false], + ["~0.1.2", "0.1.2"], + ["~0.1.2", "0.1.9"], + ["~0.1.2", "0.2.0", false], + ["~1", "1.0.0"], + ["~1", "1.2.0"], + ["~1", "1.2.3"], + ["~1", "1.2.0"], + ["~1", "1.2.3"], + ["~1", "0.0.0", false], + ["~1", "2.0.0", false], + ["~1.2", "1.2.0"], + ["~1.2", "1.2.3"], + ["~1.2", "1.1.0", false], + ["~1.2", "1.3.0", false], + ["~1.2.3", "1.2.3"], + ["~1.2.3", "1.2.9"], + ["~1.2.3", "1.1.0", false], + ["~1.2.3", "1.3.0", false], + ]); + theory("caret", assertRange, [ + ["^0", "0.0.0"], + ["^0", "0.1.0"], + ["^0", "0.9.0"], + ["^0", "0.1.2"], + ["^0", "0.1.9"], + ["^0", "1.0.0", false], + ["^0.1", "0.1.0"], + ["^0.1", "0.1.2"], + ["^0.1", "0.1.9"], + ["^0.1.2", "0.1.2"], + ["^0.1.2", "0.1.9"], + ["^0.1.2", "0.0.0", false], + ["^0.1.2", "0.2.0", false], + ["^0.1.2", "1.0.0", false], + ["^1", "1.0.0"], + ["^1", "1.2.0"], + ["^1", "1.2.3"], + ["^1", "1.9.0"], + ["^1", "0.0.0", false], + ["^1", "2.0.0", false], + ["^1.2", "1.2.0"], + ["^1.2", "1.2.3"], + ["^1.2", "1.9.0"], + ["^1.2", "1.1.0", false], + ["^1.2", "2.0.0", false], + ["^1.2.3", "1.2.3"], + ["^1.2.3", "1.9.0"], + ["^1.2.3", "1.2.2", false], + ["^1.2.3", "2.0.0", false], + ]); + }); +}); } diff --git a/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts b/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts index 26ad16d8ebcef..d7c82bbe62361 100644 --- a/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts +++ b/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts @@ -1,95 +1,95 @@ namespace ts { - describe("unittests:: services:: cancellableLanguageServiceOperations", () => { - const file = ` +describe("unittests:: services:: cancellableLanguageServiceOperations", () => { + const file = ` function foo(): void; function foo(x: T): T; function foo(x?: T): T | void {} foo(f); `; - it("can cancel signature help mid-request", () => { - verifyOperationCancelledAfter(file, 4, service => // Two calls are top-level in services, one is the root type, and the second should be for the parameter type - service.getSignatureHelpItems("file.ts", file.lastIndexOf("f"), emptyOptions)!, r => assert.exists(r.items[0]) - ); - }); + it("can cancel signature help mid-request", () => { + verifyOperationCancelledAfter(file, 4, service => // Two calls are top-level in services, one is the root type, and the second should be for the parameter type + service.getSignatureHelpItems("file.ts", file.lastIndexOf("f"), emptyOptions)!, r => assert.exists(r.items[0]) + ); + }); - it("can cancel find all references mid-request", () => { - verifyOperationCancelledAfter(file, 3, service => // Two calls are top-level in services, one is the root type - service.findReferences("file.ts", file.lastIndexOf("o"))!, r => assert.exists(r[0].definition) - ); - }); + it("can cancel find all references mid-request", () => { + verifyOperationCancelledAfter(file, 3, service => // Two calls are top-level in services, one is the root type + service.findReferences("file.ts", file.lastIndexOf("o"))!, r => assert.exists(r[0].definition) + ); + }); - it("can cancel quick info mid-request", () => { - verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for quickinfo, so the first check is within the checker - service.getQuickInfoAtPosition("file.ts", file.lastIndexOf("o"))!, r => assert.exists(r.displayParts) - ); - }); + it("can cancel quick info mid-request", () => { + verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for quickinfo, so the first check is within the checker + service.getQuickInfoAtPosition("file.ts", file.lastIndexOf("o"))!, r => assert.exists(r.displayParts) + ); + }); - it("can cancel completion entry details mid-request", () => { - const options: FormatCodeSettings = { - indentSize: 4, - tabSize: 4, - newLineCharacter: "\n", - convertTabsToSpaces: true, - indentStyle: IndentStyle.Smart, - insertSpaceAfterConstructor: false, - insertSpaceAfterCommaDelimiter: true, - insertSpaceAfterSemicolonInForStatements: true, - insertSpaceBeforeAndAfterBinaryOperators: true, - insertSpaceAfterKeywordsInControlFlowStatements: true, - insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, - insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, - insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, - insertSpaceBeforeFunctionParenthesis: false, - placeOpenBraceOnNewLineForFunctions: false, - placeOpenBraceOnNewLineForControlBlocks: false, - }; - verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for completion entry details, so the first check is within the checker - service.getCompletionEntryDetails("file.ts", file.lastIndexOf("f"), "foo", options, /*source*/ undefined, {}, /*data*/ undefined)!, r => assert.exists(r.displayParts) - ); - }); + it("can cancel completion entry details mid-request", () => { + const options: FormatCodeSettings = { + indentSize: 4, + tabSize: 4, + newLineCharacter: "\n", + convertTabsToSpaces: true, + indentStyle: IndentStyle.Smart, + insertSpaceAfterConstructor: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, + insertSpaceBeforeFunctionParenthesis: false, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, + }; + verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for completion entry details, so the first check is within the checker + service.getCompletionEntryDetails("file.ts", file.lastIndexOf("f"), "foo", options, /*source*/ undefined, {}, /*data*/ undefined)!, r => assert.exists(r.displayParts) + ); + }); - it("can cancel suggestion diagnostics mid-request", () => { - verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for suggestion diagnostics, so the first check is within the checker - service.getSuggestionDiagnostics("file.js"), r => assert.notEqual(r.length, 0), "file.js", "function foo() { let a = 10; }", { allowJs: true } - ); - }); + it("can cancel suggestion diagnostics mid-request", () => { + verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for suggestion diagnostics, so the first check is within the checker + service.getSuggestionDiagnostics("file.js"), r => assert.notEqual(r.length, 0), "file.js", "function foo() { let a = 10; }", { allowJs: true } + ); }); +}); - function verifyOperationCancelledAfter(content: string, cancelAfter: number, operation: (service: LanguageService) => T, validator: (arg: T) => void, fileName?: string, fileContent?: string, options?: CompilerOptions) { - let checks = 0; - const token: HostCancellationToken = { - isCancellationRequested() { - checks++; - const result = checks >= cancelAfter; - if (result) { - checks = -Infinity; // Cancel just once, then disable cancellation, effectively - } - return result; +function verifyOperationCancelledAfter(content: string, cancelAfter: number, operation: (service: LanguageService) => T, validator: (arg: T) => void, fileName?: string, fileContent?: string, options?: CompilerOptions) { + let checks = 0; + const token: HostCancellationToken = { + isCancellationRequested() { + checks++; + const result = checks >= cancelAfter; + if (result) { + checks = -Infinity; // Cancel just once, then disable cancellation, effectively } - }; - const adapter = new Harness.LanguageService.NativeLanguageServiceAdapter(token, options); - const host = adapter.getHost(); - host.addScript(fileName || "file.ts", fileContent || content, /*isRootFile*/ true); - const service = adapter.getLanguageService(); - assertCancelled(() => operation(service)); - validator(operation(service)); - } - - /** - * We don't just use `assert.throws` because it doesn't validate instances for thrown objects which do not inherit from `Error` - */ - function assertCancelled(cb: () => void) { - let caught: any; - try { - cb(); - } - catch (e) { - caught = e; + return result; } - assert.exists(caught, "Expected operation to be cancelled, but was not"); - assert.instanceOf(caught, OperationCanceledException); + }; + const adapter = new Harness.LanguageService.NativeLanguageServiceAdapter(token, options); + const host = adapter.getHost(); + host.addScript(fileName || "file.ts", fileContent || content, /*isRootFile*/ true); + const service = adapter.getLanguageService(); + assertCancelled(() => operation(service)); + validator(operation(service)); +} + +/** + * We don't just use `assert.throws` because it doesn't validate instances for thrown objects which do not inherit from `Error` + */ +function assertCancelled(cb: () => void) { + let caught: any; + try { + cb(); + } + catch (e) { + caught = e; } + assert.exists(caught, "Expected operation to be cancelled, but was not"); + assert.instanceOf(caught, OperationCanceledException); +} } diff --git a/src/testRunner/unittests/services/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts index a8bd6e6d520c0..64c04735f94f2 100644 --- a/src/testRunner/unittests/services/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/services/convertToAsyncFunction.ts @@ -1,7 +1,7 @@ namespace ts { - const libFile: TestFSWithWatch.File = { - path: "/a/lib/lib.d.ts", - content: `/// +const libFile: TestFSWithWatch.File = { + path: "/a/lib/lib.d.ts", + content: `/// interface Boolean {} interface Function {} interface IArguments {} @@ -260,207 +260,207 @@ declare var Promise: PromiseConstructor; interface RegExp {} interface String { charAt: any; } interface Array {}` - }; +}; - const moduleFile: TestFSWithWatch.File = { - path: "/module.ts", - content: +const moduleFile: TestFSWithWatch.File = { + path: "/module.ts", + content: `export function fn(res: any): any { return res; }` - }; - - type WithSkipAndOnly = ((...args: T) => void) & { - skip: (...args: T) => void; - only: (...args: T) => void; - }; - - function createTestWrapper(fn: (it: Mocha.PendingTestFunction, ...args: T) => void): WithSkipAndOnly { - wrapped.skip = (...args: T) => fn(it.skip, ...args); - wrapped.only = (...args: T) => fn(it.only, ...args); - return wrapped; - function wrapped(...args: T) { - return fn(it, ...args); - } +}; + +type WithSkipAndOnly = ((...args: T) => void) & { + skip: (...args: T) => void; + only: (...args: T) => void; +}; + +function createTestWrapper(fn: (it: Mocha.PendingTestFunction, ...args: T) => void): WithSkipAndOnly { + wrapped.skip = (...args: T) => fn(it.skip, ...args); + wrapped.only = (...args: T) => fn(it.only, ...args); + return wrapped; + function wrapped(...args: T) { + return fn(it, ...args); } +} - const enum ConvertToAsyncTestFlags { - None, - IncludeLib = 1 << 0, - IncludeModule = 1 << 1, - ExpectSuggestionDiagnostic = 1 << 2, - ExpectNoSuggestionDiagnostic = 1 << 3, - ExpectAction = 1 << 4, - ExpectNoAction = 1 << 5, - - ExpectSuccess = ExpectSuggestionDiagnostic | ExpectAction, - ExpectFailed = ExpectNoSuggestionDiagnostic | ExpectNoAction, +const enum ConvertToAsyncTestFlags { + None, + IncludeLib = 1 << 0, + IncludeModule = 1 << 1, + ExpectSuggestionDiagnostic = 1 << 2, + ExpectNoSuggestionDiagnostic = 1 << 3, + ExpectAction = 1 << 4, + ExpectNoAction = 1 << 5, + + ExpectSuccess = ExpectSuggestionDiagnostic | ExpectAction, + ExpectFailed = ExpectNoSuggestionDiagnostic | ExpectNoAction, +} + +function testConvertToAsyncFunction(it: Mocha.PendingTestFunction, caption: string, text: string, baselineFolder: string, flags: ConvertToAsyncTestFlags) { + const includeLib = !!(flags & ConvertToAsyncTestFlags.IncludeLib); + const includeModule = !!(flags & ConvertToAsyncTestFlags.IncludeModule); + const expectSuggestionDiagnostic = !!(flags & ConvertToAsyncTestFlags.ExpectSuggestionDiagnostic); + const expectNoSuggestionDiagnostic = !!(flags & ConvertToAsyncTestFlags.ExpectNoSuggestionDiagnostic); + const expectAction = !!(flags & ConvertToAsyncTestFlags.ExpectAction); + const expectNoAction = !!(flags & ConvertToAsyncTestFlags.ExpectNoAction); + const expectFailure = expectNoSuggestionDiagnostic || expectNoAction; + Debug.assert(!(expectSuggestionDiagnostic && expectNoSuggestionDiagnostic), "Cannot combine both 'ExpectSuggestionDiagnostic' and 'ExpectNoSuggestionDiagnostic'"); + Debug.assert(!(expectAction && expectNoAction), "Cannot combine both 'ExpectAction' and 'ExpectNoAction'"); + + const t = extractTest(text); + const selectionRange = t.ranges.get("selection")!; + if (!selectionRange) { + throw new Error(`Test ${caption} does not specify selection range`); } - function testConvertToAsyncFunction(it: Mocha.PendingTestFunction, caption: string, text: string, baselineFolder: string, flags: ConvertToAsyncTestFlags) { - const includeLib = !!(flags & ConvertToAsyncTestFlags.IncludeLib); - const includeModule = !!(flags & ConvertToAsyncTestFlags.IncludeModule); - const expectSuggestionDiagnostic = !!(flags & ConvertToAsyncTestFlags.ExpectSuggestionDiagnostic); - const expectNoSuggestionDiagnostic = !!(flags & ConvertToAsyncTestFlags.ExpectNoSuggestionDiagnostic); - const expectAction = !!(flags & ConvertToAsyncTestFlags.ExpectAction); - const expectNoAction = !!(flags & ConvertToAsyncTestFlags.ExpectNoAction); - const expectFailure = expectNoSuggestionDiagnostic || expectNoAction; - Debug.assert(!(expectSuggestionDiagnostic && expectNoSuggestionDiagnostic), "Cannot combine both 'ExpectSuggestionDiagnostic' and 'ExpectNoSuggestionDiagnostic'"); - Debug.assert(!(expectAction && expectNoAction), "Cannot combine both 'ExpectAction' and 'ExpectNoAction'"); - - const t = extractTest(text); - const selectionRange = t.ranges.get("selection")!; - if (!selectionRange) { - throw new Error(`Test ${caption} does not specify selection range`); + const extensions = expectFailure ? [Extension.Ts] : [Extension.Ts, Extension.Js]; + + extensions.forEach(extension => + it(`${caption} [${extension}]`, () => runBaseline(extension))); + + function runBaseline(extension: Extension) { + const path = "/a" + extension; + const languageService = makeLanguageService({ path, content: t.source }, includeLib, includeModule); + const program = languageService.getProgram()!; + + if (hasSyntacticDiagnostics(program)) { + // Don't bother generating JS baselines for inputs that aren't valid JS. + assert.equal(Extension.Js, extension, "Syntactic diagnostics found in non-JS file"); + return; + } + + const f = { + path, + content: t.source + }; + + const sourceFile = program.getSourceFile(path)!; + const context: CodeFixContext = { + errorCode: 80006, + span: { start: selectionRange.pos, length: selectionRange.end - selectionRange.pos }, + sourceFile, + program, + cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, + preferences: emptyOptions, + host: notImplementedHost, + formatContext: formatting.getFormatContext(testFormatSettings, notImplementedHost) + }; + + const diagnostics = languageService.getSuggestionDiagnostics(f.path); + const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === Diagnostics.This_may_be_converted_to_an_async_function.message && + diagnostic.start === context.span.start && diagnostic.length === context.span.length); + const actions = codefix.getFixes(context); + const action = find(actions, action => action.description === Diagnostics.Convert_to_async_function.message); + + let outputText: string | null; + if (action?.changes.length) { + const data: string[] = []; + data.push(`// ==ORIGINAL==`); + data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/")); + const changes = action.changes; + assert.lengthOf(changes, 1); + + data.push(`// ==ASYNC FUNCTION::${action.description}==`); + const newText = textChanges.applyChanges(sourceFile.text, changes[0].textChanges); + data.push(newText); + + const diagProgram = makeLanguageService({ path, content: newText }, includeLib, includeModule).getProgram()!; + assert.isFalse(hasSyntacticDiagnostics(diagProgram)); + outputText = data.join(newLineCharacter); + } + else { + // eslint-disable-next-line no-null/no-null + outputText = null; } - const extensions = expectFailure ? [Extension.Ts] : [Extension.Ts, Extension.Js]; - - extensions.forEach(extension => - it(`${caption} [${extension}]`, () => runBaseline(extension))); - - function runBaseline(extension: Extension) { - const path = "/a" + extension; - const languageService = makeLanguageService({ path, content: t.source }, includeLib, includeModule); - const program = languageService.getProgram()!; - - if (hasSyntacticDiagnostics(program)) { - // Don't bother generating JS baselines for inputs that aren't valid JS. - assert.equal(Extension.Js, extension, "Syntactic diagnostics found in non-JS file"); - return; - } - - const f = { - path, - content: t.source - }; - - const sourceFile = program.getSourceFile(path)!; - const context: CodeFixContext = { - errorCode: 80006, - span: { start: selectionRange.pos, length: selectionRange.end - selectionRange.pos }, - sourceFile, - program, - cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, - preferences: emptyOptions, - host: notImplementedHost, - formatContext: formatting.getFormatContext(testFormatSettings, notImplementedHost) - }; - - const diagnostics = languageService.getSuggestionDiagnostics(f.path); - const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === Diagnostics.This_may_be_converted_to_an_async_function.message && - diagnostic.start === context.span.start && diagnostic.length === context.span.length); - const actions = codefix.getFixes(context); - const action = find(actions, action => action.description === Diagnostics.Convert_to_async_function.message); - - let outputText: string | null; - if (action?.changes.length) { - const data: string[] = []; - data.push(`// ==ORIGINAL==`); - data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/")); - const changes = action.changes; - assert.lengthOf(changes, 1); - - data.push(`// ==ASYNC FUNCTION::${action.description}==`); - const newText = textChanges.applyChanges(sourceFile.text, changes[0].textChanges); - data.push(newText); - - const diagProgram = makeLanguageService({ path, content: newText }, includeLib, includeModule).getProgram()!; - assert.isFalse(hasSyntacticDiagnostics(diagProgram)); - outputText = data.join(newLineCharacter); - } - else { - // eslint-disable-next-line no-null/no-null - outputText = null; - } - - Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, outputText); - - if (expectNoSuggestionDiagnostic) { - assert.isUndefined(diagnostic, "Expected code fix to not provide a suggestion diagnostic"); - } - else if (expectSuggestionDiagnostic) { - assert.exists(diagnostic, "Expected code fix to provide a suggestion diagnostic"); - } - - if (expectNoAction) { - assert.isNotTrue(!!action?.changes.length, "Expected code fix to not provide an action"); - assert.isNotTrue(typeof outputText === "string", "Expected code fix to not apply changes"); - } - else if (expectAction) { - assert.isTrue(!!action?.changes.length, "Expected code fix to provide an action"); - assert.isTrue(typeof outputText === "string", "Expected code fix to apply changes"); - } + Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, outputText); + + if (expectNoSuggestionDiagnostic) { + assert.isUndefined(diagnostic, "Expected code fix to not provide a suggestion diagnostic"); + } + else if (expectSuggestionDiagnostic) { + assert.exists(diagnostic, "Expected code fix to provide a suggestion diagnostic"); } - function makeLanguageService(file: TestFSWithWatch.File, includeLib?: boolean, includeModule?: boolean) { - const files = [file]; - if (includeLib) { - files.push(libFile); // libFile is expensive to parse repeatedly - only test when required - } - if (includeModule) { - files.push(moduleFile); - } - const host = projectSystem.createServerHost(files); - const projectService = projectSystem.createProjectService(host); - projectService.openClientFile(file.path); - return first(projectService.inferredProjects).getLanguageService(); + if (expectNoAction) { + assert.isNotTrue(!!action?.changes.length, "Expected code fix to not provide an action"); + assert.isNotTrue(typeof outputText === "string", "Expected code fix to not apply changes"); } + else if (expectAction) { + assert.isTrue(!!action?.changes.length, "Expected code fix to provide an action"); + assert.isTrue(typeof outputText === "string", "Expected code fix to apply changes"); + } + } - function hasSyntacticDiagnostics(program: Program) { - const diags = program.getSyntacticDiagnostics(); - return length(diags) > 0; + function makeLanguageService(file: TestFSWithWatch.File, includeLib?: boolean, includeModule?: boolean) { + const files = [file]; + if (includeLib) { + files.push(libFile); // libFile is expensive to parse repeatedly - only test when required } + if (includeModule) { + files.push(moduleFile); + } + const host = projectSystem.createServerHost(files); + const projectService = projectSystem.createProjectService(host); + projectService.openClientFile(file.path); + return first(projectService.inferredProjects).getLanguageService(); } - const _testConvertToAsyncFunction = createTestWrapper((it, caption: string, text: string) => { - testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectSuccess); - }); + function hasSyntacticDiagnostics(program: Program) { + const diags = program.getSyntacticDiagnostics(); + return length(diags) > 0; + } +} - const _testConvertToAsyncFunctionFailed = createTestWrapper((it, caption: string, text: string) => { - testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectFailed); - }); +const _testConvertToAsyncFunction = createTestWrapper((it, caption: string, text: string) => { + testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectSuccess); +}); - const _testConvertToAsyncFunctionFailedSuggestion = createTestWrapper((it, caption: string, text: string) => { - testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectNoSuggestionDiagnostic | ConvertToAsyncTestFlags.ExpectAction); - }); +const _testConvertToAsyncFunctionFailed = createTestWrapper((it, caption: string, text: string) => { + testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectFailed); +}); - const _testConvertToAsyncFunctionFailedAction = createTestWrapper((it, caption: string, text: string) => { - testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectSuggestionDiagnostic | ConvertToAsyncTestFlags.ExpectNoAction); - }); +const _testConvertToAsyncFunctionFailedSuggestion = createTestWrapper((it, caption: string, text: string) => { + testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectNoSuggestionDiagnostic | ConvertToAsyncTestFlags.ExpectAction); +}); - const _testConvertToAsyncFunctionWithModule = createTestWrapper((it, caption: string, text: string) => { - testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.IncludeModule | ConvertToAsyncTestFlags.ExpectSuccess); - }); +const _testConvertToAsyncFunctionFailedAction = createTestWrapper((it, caption: string, text: string) => { + testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectSuggestionDiagnostic | ConvertToAsyncTestFlags.ExpectNoAction); +}); + +const _testConvertToAsyncFunctionWithModule = createTestWrapper((it, caption: string, text: string) => { + testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.IncludeModule | ConvertToAsyncTestFlags.ExpectSuccess); +}); - describe("unittests:: services:: convertToAsyncFunction", () => { - _testConvertToAsyncFunction("convertToAsyncFunction_basic", ` +describe("unittests:: services:: convertToAsyncFunction", () => { + _testConvertToAsyncFunction("convertToAsyncFunction_basic", ` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(result => { console.log(result) }); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_arrayBindingPattern", ` + _testConvertToAsyncFunction("convertToAsyncFunction_arrayBindingPattern", ` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(([result]) => { console.log(result) }); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_objectBindingPattern", ` + _testConvertToAsyncFunction("convertToAsyncFunction_objectBindingPattern", ` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(({ result }) => { console.log(result) }); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_arrayBindingPatternRename", ` + _testConvertToAsyncFunction("convertToAsyncFunction_arrayBindingPatternRename", ` function [#|f|](): Promise{ const result = getResult(); return fetch('https://typescriptlang.org').then(([result]) => { console.log(result) }); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_objectBindingPatternRename", ` + _testConvertToAsyncFunction("convertToAsyncFunction_objectBindingPatternRename", ` function [#|f|](): Promise{ const result = getResult(); return fetch('https://typescriptlang.org').then(({ result }) => { console.log(result) }); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_basicNoReturnTypeAnnotation", ` + _testConvertToAsyncFunction("convertToAsyncFunction_basicNoReturnTypeAnnotation", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(result => { console.log(result) }); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_basicWithComments", ` + _testConvertToAsyncFunction("convertToAsyncFunction_basicWithComments", ` function [#|f|](): Promise{ /* Note - some of these comments are removed during the refactor. This is not ideal. */ @@ -469,23 +469,23 @@ function [#|f|](): Promise{ // m }`); - _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunction", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunction", ` [#|():Promise => {|] return fetch('https://typescriptlang.org').then(result => console.log(result)); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunctionNoAnnotation", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunctionNoAnnotation", ` [#|() => {|] return fetch('https://typescriptlang.org').then(result => console.log(result)); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_Catch", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Catch", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => { console.log(result); }).catch(err => { console.log(err); }); }`); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchAndRej", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchAndRej", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }).catch(err => { console.log(err) }); }`); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchAndRejRef", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchAndRejRef", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res, rej).catch(catch_err) } @@ -498,7 +498,7 @@ function rej(rejection){ function catch_err(err){ console.log(err); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchRef", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchRef", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res).catch(catch_err) } @@ -509,48 +509,48 @@ function catch_err(err){ console.log(err); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchNoBrackets", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchNoBrackets", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => console.log(result)).catch(err => console.log(err)); }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs1", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs1", ` function [#|f|](): Promise { return fetch('https://typescriptlang.org').then( _ => { console.log("done"); }); }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs2", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs2", ` function [#|f|](): Promise { return fetch('https://typescriptlang.org').then( () => console.log("done") ); }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs3", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs3", ` function [#|f|](): Promise { return fetch('https://typescriptlang.org').then( () => console.log("almost done") ).then( () => console.log("done") ); }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs4", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs4", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(res); } function res(){ console.log("done"); }` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Method", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Method", ` class Parser { [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => console.log(result)); } }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_MultipleCatches", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleCatches", ` function [#|f|](): Promise { return fetch('https://typescriptlang.org').then(res => console.log(res)).catch(err => console.log("err", err)).catch(err2 => console.log("err2", err2)); }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_MultipleThens", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleThens", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res).then(res2); } @@ -560,8 +560,8 @@ function res(result){ function res2(result2){ console.log(result2); }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_MultipleThensSameVarName", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleThensSameVarName", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res).then(res2); } @@ -572,64 +572,64 @@ function res2(result){ return result.bodyUsed; } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_NoRes", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_NoRes", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(null, rejection => console.log("rejected:", rejection)); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_NoRes2", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_NoRes2", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(undefined).catch(rej => console.log(rej)); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_NoRes3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_NoRes3", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').catch(rej => console.log(rej)); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_NoRes4", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_NoRes4", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(undefined, rejection => console.log("rejected:", rejection)); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_NoCatchHandler", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_NoCatchHandler", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(x => x.statusText).catch(undefined); } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestion", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestion", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org'); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseDotAll", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseDotAll", ` function [#|f|]():Promise{ return Promise.all([fetch('https://typescriptlang.org'), fetch('https://microsoft.com'), fetch('https://youtube.com')]).then(function(vals){ vals.forEach(console.log); }); } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionNoPromise", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionNoPromise", ` function [#|f|]():void{ } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Rej", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Rej", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }); } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_RejRef", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_RejRef", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res, rej); } @@ -640,15 +640,15 @@ function rej(err){ console.log(err); } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_RejNoBrackets", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_RejNoBrackets", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => console.log(result), rejection => console.log("rejected:", rejection)); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_ResRef", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ResRef", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res); } @@ -656,9 +656,9 @@ function res(result){ return result.ok; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_ResRef1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ResRef1", ` class Foo { public [#|method|](): Promise { return fetch('a').then(this.foo); @@ -670,7 +670,7 @@ class Foo { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_ResRef2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ResRef2", ` class Foo { public [#|method|](): Promise { return fetch('a').then(this.foo); @@ -680,7 +680,7 @@ class Foo { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_ResRef3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ResRef3", ` const res = (result) => { return result.ok; } @@ -688,17 +688,17 @@ function [#|f|](): Promise { return fetch('https://typescriptlang.org').then(res); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef1", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef1", ` const res = 1; function [#|f|]() { return fetch('https://typescriptlang.org').then(res); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef2", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef2", ` class Foo { private foo = 1; public [#|method|](): Promise { @@ -706,17 +706,17 @@ class Foo { } } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef3", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef3", ` const res = undefined; function [#|f|]() { return fetch('https://typescriptlang.org').then(res); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef4", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef4", ` class Foo { private foo = undefined; public [#|method|](): Promise { @@ -724,9 +724,9 @@ class Foo { } } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_ResRefNoReturnVal", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ResRefNoReturnVal", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res); } @@ -734,9 +734,9 @@ function res(result){ console.log(result); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_ResRefNoReturnVal1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ResRefNoReturnVal1", ` class Foo { public [#|method|](): Promise { return fetch('a').then(this.foo); @@ -748,33 +748,33 @@ class Foo { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_NoBrackets", ` + _testConvertToAsyncFunction("convertToAsyncFunction_NoBrackets", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => console.log(result)); } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally1", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally1", ` function [#|finallyTest|](): Promise { return fetch("https://typescriptlang.org").then(res => console.log(res)).catch(rej => console.log("error", rej)).finally(console.log("finally!")); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally2", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally2", ` function [#|finallyTest|](): Promise { return fetch("https://typescriptlang.org").then(res => console.log(res)).finally(console.log("finally!")); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally3", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally3", ` function [#|finallyTest|](): Promise { return fetch("https://typescriptlang.org").finally(console.log("finally!")); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromise", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromise", ` function [#|innerPromise|](): Promise { return fetch("https://typescriptlang.org").then(resp => { var blob2 = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); @@ -784,8 +784,8 @@ function [#|innerPromise|](): Promise { }); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRet", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRet", ` function [#|innerPromise|](): Promise { return fetch("https://typescriptlang.org").then(resp => { return resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); @@ -794,9 +794,9 @@ function [#|innerPromise|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding1", ` function [#|innerPromise|](): Promise { return fetch("https://typescriptlang.org").then(resp => { return resp.blob().then(({ blob }) => blob.byteOffset).catch(({ message }) => 'Error ' + message); @@ -805,9 +805,9 @@ function [#|innerPromise|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding2", ` function [#|innerPromise|](): Promise { return fetch("https://typescriptlang.org").then(resp => { return resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); @@ -816,9 +816,9 @@ function [#|innerPromise|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding3", ` function [#|innerPromise|](): Promise { return fetch("https://typescriptlang.org").then(resp => { return resp.blob().then(({ blob }) => blob.byteOffset).catch(({ message }) => 'Error ' + message); @@ -827,9 +827,9 @@ function [#|innerPromise|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding4", ` + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding4", ` function [#|innerPromise|](): Promise { return fetch("https://typescriptlang.org").then(resp => { return resp.blob().then(({ blob }: { blob: { byteOffset: number } }) => [0, blob.byteOffset]).catch(({ message }: Error) => ['Error ', message]); @@ -838,24 +838,24 @@ function [#|innerPromise|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn01", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn01", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp)); return blob; } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn02", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn02", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); blob.then(resp => console.log(resp)); return blob; } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn03", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn03", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org") let blob2 = blob.then(resp => console.log(resp)); @@ -867,8 +867,8 @@ function err (rej) { console.log(rej) } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn04", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn04", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org").then(res => console.log(res)), blob2 = fetch("https://microsoft.com").then(res => res.ok).catch(err); return blob; @@ -877,26 +877,26 @@ function err (rej) { console.log(rej) } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn05", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn05", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org").then(res => console.log(res)); blob.then(x => x); return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn06", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn06", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org"); return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn07", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn07", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); let blob2 = fetch("https://microsoft.com"); @@ -905,9 +905,9 @@ function [#|f|]() { return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn08", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn08", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); if (!blob.ok){ @@ -917,9 +917,9 @@ function [#|f|]() { return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn09", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn09", ` function [#|f|]() { let blob3; let blob = fetch("https://typescriptlang.org"); @@ -930,10 +930,10 @@ function [#|f|]() { return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn10", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn10", ` function [#|f|]() { let blob3; let blob = fetch("https://typescriptlang.org"); @@ -945,19 +945,19 @@ function [#|f|]() { return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn11", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn11", ` function [#|f|]() { let blob; return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Param1", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Param1", ` function [#|f|]() { return my_print(fetch("https://typescriptlang.org").then(res => console.log(res))); } @@ -969,9 +969,9 @@ function my_print (resp) { } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Param2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Param2", ` function [#|f|]() { return my_print(fetch("https://typescriptlang.org").then(res => console.log(res))).catch(err => console.log("Error!", err)); } @@ -984,9 +984,9 @@ function my_print (resp): Promise { ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns1", ` function [#|f|](): Promise { let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); if (x.ok) { @@ -997,9 +997,9 @@ function [#|f|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns2", ` function [#|f|](): Promise { let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); if (x.ok) { @@ -1011,10 +1011,10 @@ function [#|f|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_SeperateLines", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_SeperateLines", ` function [#|f|](): Promise { var blob = fetch("https://typescriptlang.org") blob.then(resp => { @@ -1027,10 +1027,10 @@ function [#|f|](): Promise { return blob; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerVarNameConflict", ` + _testConvertToAsyncFunction("convertToAsyncFunction_InnerVarNameConflict", ` function [#|f|](): Promise { return fetch("https://typescriptlang.org").then(resp => { var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); @@ -1039,8 +1039,8 @@ function [#|f|](): Promise { }); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseSimple", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseSimple", ` function [#|f|](): Promise { return fetch("https://typescriptlang.org").then(resp => { return resp.blob().then(blob => blob.byteOffset); @@ -1049,8 +1049,8 @@ function [#|f|](): Promise { }); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen1", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen1", ` function [#|f|]() { return Promise.resolve().then(function () { return Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { @@ -1059,9 +1059,9 @@ function [#|f|]() { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen2", ` function [#|f|]() { return Promise.resolve().then(function () { return Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { @@ -1070,9 +1070,9 @@ function [#|f|]() { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen3", ` function [#|f|]() { return Promise.resolve().then(() => Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { @@ -1080,9 +1080,9 @@ function [#|f|]() { }).then(res => res.toString())])); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen4", ` + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen4", ` function [#|f|]() { return Promise.resolve().then(() => Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { @@ -1090,8 +1090,8 @@ function [#|f|]() { })]).then(res => res.toString())); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_Scope1", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_Scope1", ` function [#|f|]() { var var1: Response, var2; return fetch('https://typescriptlang.org').then( _ => @@ -1108,7 +1108,7 @@ function [#|f|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_Conditionals", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Conditionals", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res => { if (res.ok) { @@ -1125,9 +1125,9 @@ function [#|f|](){ }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThen", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThen", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1140,9 +1140,9 @@ function rej(reject){ return reject; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes01", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes01", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1155,9 +1155,9 @@ function rej(reject): number { return 3; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1170,10 +1170,10 @@ function rej(reject){ return 3; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes02", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes02", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res => 0).catch(rej => 1).then(res); } @@ -1182,9 +1182,9 @@ function res(result): number { return 5; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res => 0).catch(rej => 1).then(res); } @@ -1193,9 +1193,9 @@ function res(result){ return 5; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes01", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes01", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1208,9 +1208,9 @@ function rej(reject){ return "Error"; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes02", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes02", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1223,9 +1223,9 @@ function rej(reject): Response{ return reject; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1238,10 +1238,10 @@ function rej(reject){ return reject; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes03", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes03", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1254,9 +1254,9 @@ function rej(reject){ return Promise.resolve(1); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes04", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes04", ` interface a { name: string; age: number; @@ -1279,9 +1279,9 @@ function rej(reject): a{ return {name: "myName", age: 27}; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_ParameterNameCollision", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ParameterNameCollision", ` async function foo(x: T): Promise { return x; } @@ -1290,44 +1290,44 @@ function [#|bar|](x: T): Promise { return foo(x).then(foo) } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Return1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Return1", ` function [#|f|](p: Promise) { return p.catch((error: Error) => { return Promise.reject(error); }); }` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Return2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Return2", ` function [#|f|](p: Promise) { return p.catch((error: Error) => Promise.reject(error)); }` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Return3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Return3", ` function [#|f|](p: Promise) { return p.catch(function (error: Error) { return Promise.reject(error); }); }` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_LocalReturn", ` + _testConvertToAsyncFunction("convertToAsyncFunction_LocalReturn", ` function [#|f|]() { let x = fetch("https://typescriptlang.org").then(res => console.log(res)); return x.catch(err => console.log("Error!", err)); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseCallInner", ` + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseCallInner", ` function [#|f|]() { return fetch(Promise.resolve(1).then(res => "https://typescriptlang.org")).catch(err => console.log(err)); } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchFollowedByCall", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchFollowedByCall", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).toString(); } @@ -1340,26 +1340,26 @@ function rej(reject){ return reject; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Scope2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Scope2", ` function [#|f|](){ var i:number; return fetch("https://typescriptlang.org").then(i => i.ok).then(res => i+1).catch(err => i-1) } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Loop", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Loop", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res => { for(let i=0; i<10; i++){ console.log(res); }}) } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Conditional2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Conditional2", ` function [#|f|](){ var res = 100; if (res > 50) { @@ -1374,9 +1374,9 @@ function res_func(result){ console.log(result); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Scope3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Scope3", ` function [#|f|]() { var obj; return fetch("https://typescriptlang.org").then(function (res) { @@ -1388,9 +1388,9 @@ function [#|f|]() { }); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NestedFunctionWrongLocation", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NestedFunctionWrongLocation", ` function [#|f|]() { function fn2(){ function fn3(){ @@ -1402,7 +1402,7 @@ function [#|f|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_NestedFunctionRightLocation", ` + _testConvertToAsyncFunction("convertToAsyncFunction_NestedFunctionRightLocation", ` function f() { function fn2(){ function [#|fn3|](){ @@ -1414,56 +1414,56 @@ function f() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_UntypedFunction", ` + _testConvertToAsyncFunction("convertToAsyncFunction_UntypedFunction", ` function [#|f|]() { return Promise.resolve().then(res => console.log(res)); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_TernaryConditional", ` + _testConvertToAsyncFunction("convertToAsyncFunction_TernaryConditional", ` function [#|f|]() { let i; return Promise.resolve().then(res => res ? i = res : i = 100); } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_ResRejNoArgsArrow", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_ResRejNoArgsArrow", ` function [#|f|]() { return Promise.resolve().then(() => 1, () => "a"); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpression", ` + _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpression", ` const [#|foo|] = function () { return fetch('https://typescriptlang.org').then(result => { console.log(result) }); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpressionWithName", ` + _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpressionWithName", ` const foo = function [#|f|]() { return fetch('https://typescriptlang.org').then(result => { console.log(result) }); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpressionAssignedToBindingPattern", ` + _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpressionAssignedToBindingPattern", ` const { length } = [#|function|] () { return fetch('https://typescriptlang.org').then(result => { console.log(result) }); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParams", ` + _testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParams", ` function [#|f|]() { return Promise.resolve().then(x => 1).catch(x => "a").then(x => !!x); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParamsBindingPattern", ` + _testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParamsBindingPattern", ` function [#|f|]() { return Promise.resolve().then(() => ({ x: 3 })).catch(() => ({ x: "a" })).then(({ x }) => !!x); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_bindingPattern", ` + _testConvertToAsyncFunction("convertToAsyncFunction_bindingPattern", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(res); } @@ -1472,7 +1472,7 @@ function res({ status, trailer }){ } `); - _testConvertToAsyncFunction("convertToAsyncFunction_bindingPatternNameCollision", ` + _testConvertToAsyncFunction("convertToAsyncFunction_bindingPatternNameCollision", ` function [#|f|]() { const result = 'https://typescriptlang.org'; return fetch(result).then(res); @@ -1482,19 +1482,19 @@ function res({ status, trailer }){ } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_thenArgumentNotFunction", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_thenArgumentNotFunction", ` function [#|f|]() { return Promise.resolve().then(f ? (x => x) : (y => y)); } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_thenArgumentNotFunctionNotLastInChain", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_thenArgumentNotFunctionNotLastInChain", ` function [#|f|]() { return Promise.resolve().then(f ? (x => x) : (y => y)).then(q => q); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_runEffectfulContinuation", ` + _testConvertToAsyncFunction("convertToAsyncFunction_runEffectfulContinuation", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(res).then(_ => console.log("done")); } @@ -1503,31 +1503,31 @@ function res(result) { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromise", ` + _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromise", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(s => Promise.resolve(s.statusText.length)).then(x => console.log(x + 5)); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromiseInBlock", ` + _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromiseInBlock", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(s => { return Promise.resolve(s.statusText.length) }).then(x => x + 5); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsFixablePromise", ` + _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsFixablePromise", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(s => Promise.resolve(s.statusText).then(st => st.length)).then(x => console.log(x + 5)); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromiseLastInChain", ` + _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromiseLastInChain", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(s => Promise.resolve(s.statusText.length)); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsRejectedPromiseInTryBlock", ` + _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsRejectedPromiseInTryBlock", ` function [#|f|]() { return Promise.resolve(1) .then(x => Promise.reject(x)) @@ -1535,13 +1535,13 @@ function [#|f|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_nestedPromises", ` + _testConvertToAsyncFunction("convertToAsyncFunction_nestedPromises", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(x => Promise.resolve(3).then(y => Promise.resolve(x.statusText.length + y))); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_noArgs1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_noArgs1", ` function delay(millis: number): Promise { throw "no" } @@ -1555,7 +1555,7 @@ function [#|main2|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_noArgs2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_noArgs2", ` function delay(millis: number): Promise { throw "no" } @@ -1569,20 +1569,20 @@ function [#|main2|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_exportModifier", ` + _testConvertToAsyncFunction("convertToAsyncFunction_exportModifier", ` export function [#|foo|]() { return fetch('https://typescriptlang.org').then(s => console.log(s)); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_OutermostOnlySuccess", ` + _testConvertToAsyncFunction("convertToAsyncFunction_OutermostOnlySuccess", ` function [#|foo|]() { return fetch('a').then(() => { return fetch('b').then(() => 'c'); }) } `); - _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethod", ` + _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethod", ` function decorator() { return (target: any, key: any, descriptor: PropertyDescriptor) => descriptor; } @@ -1594,7 +1594,7 @@ class Foo { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethodWithSingleLineComment", ` + _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethodWithSingleLineComment", ` function decorator() { return (target: any, key: any, descriptor: PropertyDescriptor) => descriptor; } @@ -1607,7 +1607,7 @@ class Foo { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethodWithMultipleLineComment", ` + _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethodWithMultipleLineComment", ` function decorator() { return (target: any, key: any, descriptor: PropertyDescriptor) => descriptor; } @@ -1622,7 +1622,7 @@ class Foo { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethodWithModifier", ` + _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethodWithModifier", ` function decorator() { return (target: any, key: any, descriptor: PropertyDescriptor) => descriptor; } @@ -1634,7 +1634,7 @@ class Foo { } `); - _testConvertToAsyncFunctionFailedSuggestion("convertToAsyncFunction_OutermostOnlyFailure", ` + _testConvertToAsyncFunctionFailedSuggestion("convertToAsyncFunction_OutermostOnlyFailure", ` function foo() { return fetch('a').then([#|() => {|] return fetch('b').then(() => 'c'); @@ -1642,7 +1642,7 @@ function foo() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_thenTypeArgument1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_thenTypeArgument1", ` type APIResponse = { success: true, data: T } | { success: false }; function wrapResponse(response: T): APIResponse { @@ -1654,7 +1654,7 @@ function [#|get|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_thenTypeArgument2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_thenTypeArgument2", ` type APIResponse = { success: true, data: T } | { success: false }; function wrapResponse(response: T): APIResponse { @@ -1666,7 +1666,7 @@ function [#|get|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_thenTypeArgument3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_thenTypeArgument3", ` type APIResponse = { success: true, data: T } | { success: false }; function wrapResponse(response: T): APIResponse { @@ -1681,7 +1681,7 @@ function [#|get|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_catchTypeArgument1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_catchTypeArgument1", ` type APIResponse = { success: true, data: T } | { success: false }; function [#|get|]() { @@ -1691,12 +1691,12 @@ function [#|get|]() { } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_threeArguments", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_threeArguments", ` function [#|f|]() { return Promise.resolve().then(undefined, undefined, () => 1); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_callbackArgument", ` + _testConvertToAsyncFunction("convertToAsyncFunction_callbackArgument", ` function foo(props: any): void { return props; } @@ -1709,26 +1709,26 @@ function [#|f|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_emptyCatch1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_emptyCatch1", ` function [#|f|]() { return Promise.resolve().catch(); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_emptyCatch2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_emptyCatch2", ` function [#|f|]() { return Promise.resolve(0).then(x => x).catch(); } `); - _testConvertToAsyncFunctionWithModule("convertToAsyncFunction_importedFunction", ` + _testConvertToAsyncFunctionWithModule("convertToAsyncFunction_importedFunction", ` import { fn } from "./module"; function [#|f|]() { return Promise.resolve(0).then(fn); } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionInFunctionsWithNonFixableReturnStatements1", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionInFunctionsWithNonFixableReturnStatements1", ` function f(x: number): Promise; function f(): void; function [#|f|](x?: number): Promise | void { @@ -1737,7 +1737,7 @@ function [#|f|](x?: number): Promise | void { } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionInFunctionsWithNonFixableReturnStatements2", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionInFunctionsWithNonFixableReturnStatements2", ` function f(x: number): Promise; function f(): number; function [#|f|](x?: number): Promise | number { @@ -1746,7 +1746,7 @@ function [#|f|](x?: number): Promise | number { } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionInGetters", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionInGetters", ` class Foo { get [#|m|](): Promise { return Promise.resolve(1).then(n => n); @@ -1754,7 +1754,7 @@ class Foo { } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionForGeneratorCallbacks", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionForGeneratorCallbacks", ` function [#|foo|](p: Promise) { return p.then(function* (strings) { for (const s of strings) { @@ -1764,52 +1764,52 @@ function [#|foo|](p: Promise) { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_thenNoArguments", ` + _testConvertToAsyncFunction("convertToAsyncFunction_thenNoArguments", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().then(); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_catchNoArguments", ` + _testConvertToAsyncFunction("convertToAsyncFunction_catchNoArguments", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().catch(); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_chainedThenCatchThen", ` + _testConvertToAsyncFunction("convertToAsyncFunction_chainedThenCatchThen", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().then(x => Promise.resolve(x + 1)).catch(() => 1).then(y => y + 2); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_finally", ` + _testConvertToAsyncFunction("convertToAsyncFunction_finally", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().finally(() => console.log("done")); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_finallyNoArguments", ` + _testConvertToAsyncFunction("convertToAsyncFunction_finallyNoArguments", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().finally(); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_finallyNull", ` + _testConvertToAsyncFunction("convertToAsyncFunction_finallyNull", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().finally(null); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_finallyUndefined", ` + _testConvertToAsyncFunction("convertToAsyncFunction_finallyUndefined", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().finally(undefined); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_thenFinally", ` + _testConvertToAsyncFunction("convertToAsyncFunction_thenFinally", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().then(x => x + 1).finally(() => console.log("done")); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_thenFinallyThen", ` + _testConvertToAsyncFunction("convertToAsyncFunction_thenFinallyThen", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().then(x => Promise.resolve(x + 1)).finally(() => console.log("done")).then(y => y + 2); }`); - _testConvertToAsyncFunctionFailedAction("convertToAsyncFunction_returnInBranch", ` + _testConvertToAsyncFunctionFailedAction("convertToAsyncFunction_returnInBranch", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().then(() => { @@ -1822,7 +1822,7 @@ function [#|f|](): Promise { }); } `); - _testConvertToAsyncFunctionFailedAction("convertToAsyncFunction_partialReturnInBranch", ` + _testConvertToAsyncFunctionFailedAction("convertToAsyncFunction_partialReturnInBranch", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().then(() => { @@ -1835,5 +1835,5 @@ function [#|f|](): Promise { }); } `); - }); +}); } diff --git a/src/testRunner/unittests/services/extract/constants.ts b/src/testRunner/unittests/services/extract/constants.ts index d9cd5010d5b09..14b4cb7dad203 100644 --- a/src/testRunner/unittests/services/extract/constants.ts +++ b/src/testRunner/unittests/services/extract/constants.ts @@ -1,59 +1,59 @@ namespace ts { - describe("unittests:: services:: extract:: extractConstants", () => { - testExtractConstant("extractConstant_TopLevel", - `let x = [#|1|];`); +describe("unittests:: services:: extract:: extractConstants", () => { + testExtractConstant("extractConstant_TopLevel", + `let x = [#|1|];`); - testExtractConstant("extractConstant_Namespace", - `namespace N { + testExtractConstant("extractConstant_Namespace", + `namespace N { let x = [#|1|]; }`); - testExtractConstant("extractConstant_Class", - `class C { + testExtractConstant("extractConstant_Class", + `class C { x = [#|1|]; }`); - testExtractConstant("extractConstant_Method", - `class C { + testExtractConstant("extractConstant_Method", + `class C { M() { let x = [#|1|]; } }`); - testExtractConstant("extractConstant_Function", - `function F() { + testExtractConstant("extractConstant_Function", + `function F() { let x = [#|1|]; }`); - testExtractConstant("extractConstant_ExpressionStatement", - `[#|"hello";|]`); + testExtractConstant("extractConstant_ExpressionStatement", + `[#|"hello";|]`); - testExtractConstant("extractConstant_ExpressionStatementExpression", - `[#|"hello"|];`); + testExtractConstant("extractConstant_ExpressionStatementExpression", + `[#|"hello"|];`); - testExtractConstant("extractConstant_ExpressionStatementInNestedScope", ` + testExtractConstant("extractConstant_ExpressionStatementInNestedScope", ` let i = 0; function F() { [#|i++|]; } `); - testExtractConstant("extractConstant_ExpressionStatementConsumesLocal", ` + testExtractConstant("extractConstant_ExpressionStatementConsumesLocal", ` function F() { let i = 0; [#|i++|]; } `); - testExtractConstant("extractConstant_BlockScopes_NoDependencies", - `for (let i = 0; i < 10; i++) { + testExtractConstant("extractConstant_BlockScopes_NoDependencies", + `for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { let x = [#|1|]; } }`); - testExtractConstant("extractConstant_ClassInsertionPosition1", - `class C { + testExtractConstant("extractConstant_ClassInsertionPosition1", + `class C { a = 1; b = 2; M1() { } @@ -63,8 +63,8 @@ function F() { } }`); - testExtractConstant("extractConstant_ClassInsertionPosition2", - `class C { + testExtractConstant("extractConstant_ClassInsertionPosition2", + `class C { a = 1; M1() { } b = 2; @@ -74,8 +74,8 @@ function F() { } }`); - testExtractConstant("extractConstant_ClassInsertionPosition3", - `class C { + testExtractConstant("extractConstant_ClassInsertionPosition3", + `class C { M1() { } a = 1; b = 2; @@ -85,36 +85,36 @@ function F() { } }`); - testExtractConstant("extractConstant_Parameters", - `function F() { + testExtractConstant("extractConstant_Parameters", + `function F() { let w = 1; let x = [#|w + 1|]; }`); - testExtractConstant("extractConstant_TypeParameters", - `function F(t: T) { + testExtractConstant("extractConstant_TypeParameters", + `function F(t: T) { let x = [#|t + 1|]; }`); - testExtractConstant("extractConstant_RepeatedSubstitution", - `namespace X { + testExtractConstant("extractConstant_RepeatedSubstitution", + `namespace X { export const j = 10; export const y = [#|j * j|]; }`); - testExtractConstant("extractConstant_VariableList_const", - `const a = 1, b = [#|a + 1|];`); + testExtractConstant("extractConstant_VariableList_const", + `const a = 1, b = [#|a + 1|];`); - // NOTE: this test isn't normative - it just documents our sub-optimal behavior. - testExtractConstant("extractConstant_VariableList_let", - `let a = 1, b = [#|a + 1|];`); + // NOTE: this test isn't normative - it just documents our sub-optimal behavior. + testExtractConstant("extractConstant_VariableList_let", + `let a = 1, b = [#|a + 1|];`); - // NOTE: this test isn't normative - it just documents our sub-optimal behavior. - testExtractConstant("extractConstant_VariableList_MultipleLines", - `const /*About A*/a = 1, + // NOTE: this test isn't normative - it just documents our sub-optimal behavior. + testExtractConstant("extractConstant_VariableList_MultipleLines", + `const /*About A*/a = 1, /*About B*/b = [#|a + 1|];`); - testExtractConstant("extractConstant_BlockScopeMismatch", ` + testExtractConstant("extractConstant_BlockScopeMismatch", ` for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { const x = [#|i + 1|]; @@ -122,14 +122,14 @@ for (let i = 0; i < 10; i++) { } `); - testExtractConstant("extractConstant_StatementInsertionPosition1", ` + testExtractConstant("extractConstant_StatementInsertionPosition1", ` const i = 0; for (let j = 0; j < 10; j++) { const x = [#|i + 1|]; } `); - testExtractConstant("extractConstant_StatementInsertionPosition2", ` + testExtractConstant("extractConstant_StatementInsertionPosition2", ` const i = 0; function F() { for (let j = 0; j < 10; j++) { @@ -138,13 +138,13 @@ function F() { } `); - testExtractConstant("extractConstant_StatementInsertionPosition3", ` + testExtractConstant("extractConstant_StatementInsertionPosition3", ` for (let j = 0; j < 10; j++) { const x = [#|2 + 1|]; } `); - testExtractConstant("extractConstant_StatementInsertionPosition4", ` + testExtractConstant("extractConstant_StatementInsertionPosition4", ` function F() { for (let j = 0; j < 10; j++) { const x = [#|2 + 1|]; @@ -152,7 +152,7 @@ function F() { } `); - testExtractConstant("extractConstant_StatementInsertionPosition5", ` + testExtractConstant("extractConstant_StatementInsertionPosition5", ` function F0() { function F1() { function F2(x = [#|2 + 1|]) { @@ -161,13 +161,13 @@ function F0() { } `); - testExtractConstant("extractConstant_StatementInsertionPosition6", ` + testExtractConstant("extractConstant_StatementInsertionPosition6", ` class C { x = [#|2 + 1|]; } `); - testExtractConstant("extractConstant_StatementInsertionPosition7", ` + testExtractConstant("extractConstant_StatementInsertionPosition7", ` const i = 0; class C { M() { @@ -178,25 +178,25 @@ class C { } `); - testExtractConstant("extractConstant_TripleSlash", ` + testExtractConstant("extractConstant_TripleSlash", ` /// const x = [#|2 + 1|]; `); - testExtractConstant("extractConstant_PinnedComment", ` + testExtractConstant("extractConstant_PinnedComment", ` /*! Copyright */ const x = [#|2 + 1|]; `); - testExtractConstant("extractConstant_Directive", ` + testExtractConstant("extractConstant_Directive", ` "strict"; const x = [#|2 + 1|]; `); - testExtractConstant("extractConstant_MultipleHeaders", ` + testExtractConstant("extractConstant_MultipleHeaders", ` /*! Copyright */ /// @@ -206,22 +206,22 @@ const x = [#|2 + 1|]; const x = [#|2 + 1|]; `); - testExtractConstant("extractConstant_PinnedCommentAndDocComment", ` + testExtractConstant("extractConstant_PinnedCommentAndDocComment", ` /*! Copyright */ /* About x */ const x = [#|2 + 1|]; `); - testExtractConstant("extractConstant_ArrowFunction_Block", ` + testExtractConstant("extractConstant_ArrowFunction_Block", ` const f = () => { return [#|2 + 1|]; };`); - testExtractConstant("extractConstant_ArrowFunction_Expression", - `const f = () => [#|2 + 1|];`); + testExtractConstant("extractConstant_ArrowFunction_Expression", + `const f = () => [#|2 + 1|];`); - testExtractConstant("extractConstant_PreserveTrivia", ` + testExtractConstant("extractConstant_PreserveTrivia", ` // a var q = /*b*/ //c /*d*/ [#|1 /*e*/ //f @@ -229,15 +229,15 @@ var q = /*b*/ //c /*j*/ 2|] /*k*/ //l /*m*/; /*n*/ //o`); - testExtractConstantFailed("extractConstant_Void", ` + testExtractConstantFailed("extractConstant_Void", ` function f(): void { } [#|f();|]`); - testExtractConstantFailed("extractConstant_Never", ` + testExtractConstantFailed("extractConstant_Never", ` function f(): never { } [#|f();|]`); - testExtractConstant("extractConstant_This_Constructor", ` + testExtractConstant("extractConstant_This_Constructor", ` class C { constructor() { [#|this.m2()|]; @@ -245,7 +245,7 @@ class C { m2() { return 1; } }`); - testExtractConstant("extractConstant_This_Method", ` + testExtractConstant("extractConstant_This_Method", ` class C { m1() { [#|this.m2()|]; @@ -253,7 +253,7 @@ class C { m2() { return 1; } }`); - testExtractConstant("extractConstant_This_Property", ` + testExtractConstant("extractConstant_This_Property", ` namespace N { // Force this test to be TS-only class C { x = 1; @@ -261,31 +261,31 @@ namespace N { // Force this test to be TS-only } }`); - // TODO (https://github.com/Microsoft/TypeScript/issues/20727): the extracted constant should have a type annotation. - testExtractConstant("extractConstant_ContextualType", ` + // TODO (https://github.com/Microsoft/TypeScript/issues/20727): the extracted constant should have a type annotation. + testExtractConstant("extractConstant_ContextualType", ` interface I { a: 1 | 2 | 3 } let i: I = [#|{ a: 1 }|]; `); - testExtractConstant("extractConstant_ContextualType_Lambda", ` + testExtractConstant("extractConstant_ContextualType_Lambda", ` const myObj: { member(x: number, y: string): void } = { member: [#|(x, y) => x + y|], } `); - testExtractConstant("extractConstant_CaseClauseExpression", ` + testExtractConstant("extractConstant_CaseClauseExpression", ` switch (1) { case [#|1|]: break; } `); - }); +}); - function testExtractConstant(caption: string, text: string) { - testExtractSymbol(caption, text, "extractConstant", Diagnostics.Extract_constant); - } +function testExtractConstant(caption: string, text: string) { + testExtractSymbol(caption, text, "extractConstant", Diagnostics.Extract_constant); +} - function testExtractConstantFailed(caption: string, text: string) { - testExtractSymbolFailed(caption, text, Diagnostics.Extract_constant); - } +function testExtractConstantFailed(caption: string, text: string) { + testExtractSymbolFailed(caption, text, Diagnostics.Extract_constant); +} } diff --git a/src/testRunner/unittests/services/extract/functions.ts b/src/testRunner/unittests/services/extract/functions.ts index c0e0a4b21f5ec..0f3e96a322722 100644 --- a/src/testRunner/unittests/services/extract/functions.ts +++ b/src/testRunner/unittests/services/extract/functions.ts @@ -1,7 +1,7 @@ namespace ts { - describe("unittests:: services:: extract:: extractFunctions", () => { - testExtractFunction("extractFunction1", - `namespace A { +describe("unittests:: services:: extract:: extractFunctions", () => { + testExtractFunction("extractFunction1", + `namespace A { let x = 1; function foo() { } @@ -16,8 +16,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction2", - `namespace A { + testExtractFunction("extractFunction2", + `namespace A { let x = 1; function foo() { } @@ -30,8 +30,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction3", - `namespace A { + testExtractFunction("extractFunction3", + `namespace A { function foo() { } namespace B { @@ -43,8 +43,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction4", - `namespace A { + testExtractFunction("extractFunction4", + `namespace A { function foo() { } namespace B { @@ -58,8 +58,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction5", - `namespace A { + testExtractFunction("extractFunction5", + `namespace A { let x = 1; export function foo() { } @@ -74,8 +74,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction6", - `namespace A { + testExtractFunction("extractFunction6", + `namespace A { let x = 1; export function foo() { } @@ -90,8 +90,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction7", - `namespace A { + testExtractFunction("extractFunction7", + `namespace A { let x = 1; export namespace C { export function foo() { @@ -108,8 +108,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction9", - `namespace A { + testExtractFunction("extractFunction9", + `namespace A { export interface I { x: number }; namespace B { function a() { @@ -118,8 +118,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction10", - `namespace A { + testExtractFunction("extractFunction10", + `namespace A { export interface I { x: number }; class C { a() { @@ -129,8 +129,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction11", - `namespace A { + testExtractFunction("extractFunction11", + `namespace A { let y = 1; class C { a() { @@ -142,8 +142,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction12", - `namespace A { + testExtractFunction("extractFunction12", + `namespace A { let y = 1; class C { b() {} @@ -157,13 +157,13 @@ namespace ts { } } }`); - // The "b" type parameters aren't used and shouldn't be passed to the extracted function. - // Type parameters should be in syntactic order (i.e. in order or character offset from BOF). - // In all cases, we could use type inference, rather than passing explicit type arguments. - // Note the inclusion of arrow functions to ensure that some type parameters are not from - // targetable scopes. - testExtractFunction("extractFunction13", - `(u1a: U1a, u1b: U1b) => { + // The "b" type parameters aren't used and shouldn't be passed to the extracted function. + // Type parameters should be in syntactic order (i.e. in order or character offset from BOF). + // In all cases, we could use type inference, rather than passing explicit type arguments. + // Note the inclusion of arrow functions to ensure that some type parameters are not from + // targetable scopes. + testExtractFunction("extractFunction13", + `(u1a: U1a, u1b: U1b) => { function F1(t1a: T1a, t1b: T1b) { (u2a: U2a, u2b: U2b) => { function F2(t2a: T2a, t2b: T2b) { @@ -178,107 +178,107 @@ namespace ts { } } }`); - // This test is descriptive, rather than normative. The current implementation - // doesn't handle type parameter shadowing. - testExtractFunction("extractFunction14", - `function F(t1: T) { + // This test is descriptive, rather than normative. The current implementation + // doesn't handle type parameter shadowing. + testExtractFunction("extractFunction14", + `function F(t1: T) { function G(t2: T) { [#|t1.toString(); t2.toString();|] } }`); - // Confirm that the constraint is preserved. - testExtractFunction("extractFunction15", - `function F(t1: T) { + // Confirm that the constraint is preserved. + testExtractFunction("extractFunction15", + `function F(t1: T) { function G(t2: U) { [#|t2.toString();|] } }`, /*includeLib*/ true); - // Confirm that the contextual type of an extracted expression counts as a use. - testExtractFunction("extractFunction16", - `function F() { + // Confirm that the contextual type of an extracted expression counts as a use. + testExtractFunction("extractFunction16", + `function F() { const array: T[] = [#|[]|]; }`, /*includeLib*/ true); - // Class type parameter - testExtractFunction("extractFunction17", - `class C { + // Class type parameter + testExtractFunction("extractFunction17", + `class C { M(t1: T1, t2: T2) { [#|t1.toString()|]; } }`); - // Function type parameter - testExtractFunction("extractFunction18", - `class C { + // Function type parameter + testExtractFunction("extractFunction18", + `class C { M(t1: T1, t2: T2) { [#|t1.toString()|]; } }`); - // Coupled constraints - testExtractFunction("extractFunction19", - `function F(v: V) { + // Coupled constraints + testExtractFunction("extractFunction19", + `function F(v: V) { [#|v.toString()|]; }`, /*includeLib*/ true); - testExtractFunction("extractFunction20", - `const _ = class { + testExtractFunction("extractFunction20", + `const _ = class { a() { [#|let a1 = { x: 1 }; return a1.x + 10;|] } }`); - // Write + void return - testExtractFunction("extractFunction21", - `function foo() { + // Write + void return + testExtractFunction("extractFunction21", + `function foo() { let x = 10; [#|x++; return;|] }`); - // Return in finally block - testExtractFunction("extractFunction22", - `function test() { + // Return in finally block + testExtractFunction("extractFunction22", + `function test() { try { } finally { [#|return 1;|] } }`); - // Extraction position - namespace - testExtractFunction("extractFunction23", - `namespace NS { + // Extraction position - namespace + testExtractFunction("extractFunction23", + `namespace NS { function M1() { } function M2() { [#|return 1;|] } function M3() { } }`); - // Extraction position - function - testExtractFunction("extractFunction24", - `function Outer() { + // Extraction position - function + testExtractFunction("extractFunction24", + `function Outer() { function M1() { } function M2() { [#|return 1;|] } function M3() { } }`); - // Extraction position - file - testExtractFunction("extractFunction25", - `function M1() { } + // Extraction position - file + testExtractFunction("extractFunction25", + `function M1() { } function M2() { [#|return 1;|] } function M3() { }`); - // Extraction position - class without ctor - testExtractFunction("extractFunction26", - `class C { + // Extraction position - class without ctor + testExtractFunction("extractFunction26", + `class C { M1() { } M2() { [#|return 1;|] } M3() { } }`); - // Extraction position - class with ctor in middle - testExtractFunction("extractFunction27", - `class C { + // Extraction position - class with ctor in middle + testExtractFunction("extractFunction27", + `class C { M1() { } M2() { [#|return 1;|] @@ -286,9 +286,9 @@ function M3() { }`); constructor() { } M3() { } }`); - // Extraction position - class with ctor at end - testExtractFunction("extractFunction28", - `class C { + // Extraction position - class with ctor at end + testExtractFunction("extractFunction28", + `class C { M1() { } M2() { [#|return 1;|] @@ -296,9 +296,9 @@ function M3() { }`); M3() { } constructor() { } }`); - // Shorthand property names - testExtractFunction("extractFunction29", - `interface UnaryExpression { + // Shorthand property names + testExtractFunction("extractFunction29", + `interface UnaryExpression { kind: "Unary"; operator: string; operand: any; @@ -315,14 +315,14 @@ function parseUnaryExpression(operator: string): UnaryExpression { function parsePrimaryExpression(): any { throw "Not implemented"; }`); - // Type parameter as declared type - testExtractFunction("extractFunction30", - `function F() { + // Type parameter as declared type + testExtractFunction("extractFunction30", + `function F() { [#|let t: T;|] }`); - // Return in nested function - testExtractFunction("extractFunction31", - `namespace N { + // Return in nested function + testExtractFunction("extractFunction31", + `namespace N { export const value = 1; @@ -333,9 +333,9 @@ function parsePrimaryExpression(): any { }|] } }`); - // Return in nested class - testExtractFunction("extractFunction32", - `namespace N { + // Return in nested class + testExtractFunction("extractFunction32", + `namespace N { export const value = 1; @@ -347,83 +347,83 @@ function parsePrimaryExpression(): any { }|] } }`); - // Selection excludes leading trivia of declaration - testExtractFunction("extractFunction33", - `function F() { + // Selection excludes leading trivia of declaration + testExtractFunction("extractFunction33", + `function F() { [#|function G() { }|] }`); - // Arrow function - testExtractFunction("extractFunction34", - `const F = () => { + // Arrow function + testExtractFunction("extractFunction34", + `const F = () => { [#|function G() { }|] };`); - testExtractFunction("extractFunction_RepeatedSubstitution", - `namespace X { + testExtractFunction("extractFunction_RepeatedSubstitution", + `namespace X { export const j = 10; export const y = [#|j * j|]; }`); - testExtractFunction("extractFunction_VariableDeclaration_Var", ` + testExtractFunction("extractFunction_VariableDeclaration_Var", ` [#|var x = 1; "hello"|] x; `); - testExtractFunction("extractFunction_VariableDeclaration_Let_Type", ` + testExtractFunction("extractFunction_VariableDeclaration_Let_Type", ` [#|let x: number = 1; "hello";|] x; `); - testExtractFunction("extractFunction_VariableDeclaration_Let_NoType", ` + testExtractFunction("extractFunction_VariableDeclaration_Let_NoType", ` [#|let x = 1; "hello";|] x; `); - testExtractFunction("extractFunction_VariableDeclaration_Const_Type", ` + testExtractFunction("extractFunction_VariableDeclaration_Const_Type", ` [#|const x: number = 1; "hello";|] x; `); - testExtractFunction("extractFunction_VariableDeclaration_Const_NoType", ` + testExtractFunction("extractFunction_VariableDeclaration_Const_NoType", ` [#|const x = 1; "hello";|] x; `); - testExtractFunction("extractFunction_VariableDeclaration_Multiple1", ` + testExtractFunction("extractFunction_VariableDeclaration_Multiple1", ` [#|const x = 1, y: string = "a";|] x; y; `); - testExtractFunction("extractFunction_VariableDeclaration_Multiple2", ` + testExtractFunction("extractFunction_VariableDeclaration_Multiple2", ` [#|const x = 1, y = "a"; const z = 3;|] x; y; z; `); - testExtractFunction("extractFunction_VariableDeclaration_Multiple3", ` + testExtractFunction("extractFunction_VariableDeclaration_Multiple3", ` [#|const x = 1, y: string = "a"; let z = 3;|] x; y; z; `); - testExtractFunction("extractFunction_VariableDeclaration_ConsumedTwice", ` + testExtractFunction("extractFunction_VariableDeclaration_ConsumedTwice", ` [#|const x: number = 1; "hello";|] x; x; `); - testExtractFunction("extractFunction_VariableDeclaration_DeclaredTwice", ` + testExtractFunction("extractFunction_VariableDeclaration_DeclaredTwice", ` [#|var x = 1; var x = 2;|] x; `); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Var", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Var", ` function f() { let a = 1; [#|var x = 1; @@ -431,7 +431,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_NoType", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_NoType", ` function f() { let a = 1; [#|let x = 1; @@ -439,7 +439,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_Type", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_Type", ` function f() { let a = 1; [#|let x: number = 1; @@ -447,9 +447,9 @@ function f() { a; x; }`); - // We propagate numericLiteralFlags, but it's not consumed by the emitter, - // so everything comes out decimal. It would be nice to improve this. - testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType1", ` + // We propagate numericLiteralFlags, but it's not consumed by the emitter, + // so everything comes out decimal. It would be nice to improve this. + testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType1", ` function f() { let a = 1; [#|let x: 0o10 | 10 | 0b10 = 10; @@ -457,7 +457,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType2", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType2", ` function f() { let a = 1; [#|let x: "a" | 'b' = 'a'; @@ -465,9 +465,9 @@ function f() { a; x; }`); - // We propagate numericLiteralFlags, but it's not consumed by the emitter, - // so everything comes out decimal. It would be nice to improve this. - testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType1", ` + // We propagate numericLiteralFlags, but it's not consumed by the emitter, + // so everything comes out decimal. It would be nice to improve this. + testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType1", ` function f() { let a = 1; [#|let x: 0o10 | 10 | 0b10 = 10; @@ -475,7 +475,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_TypeWithComments", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_TypeWithComments", ` function f() { let a = 1; [#|let x: /*A*/ "a" /*B*/ | /*C*/ 'b' /*D*/ = 'a'; @@ -483,7 +483,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Const_NoType", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Const_NoType", ` function f() { let a = 1; [#|const x = 1; @@ -491,7 +491,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Const_Type", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Const_Type", ` function f() { let a = 1; [#|const x: number = 1; @@ -499,7 +499,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed1", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed1", ` function f() { let a = 1; [#|const x = 1; @@ -508,7 +508,7 @@ function f() { a; x; y; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed2", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed2", ` function f() { let a = 1; [#|var x = 1; @@ -517,7 +517,7 @@ function f() { a; x; y; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed3", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed3", ` function f() { let a = 1; [#|let x: number = 1; @@ -526,7 +526,7 @@ function f() { a; x; y; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_UnionUndefined", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_UnionUndefined", ` function f() { let a = 1; [#|let x: number | undefined = 1; @@ -536,13 +536,13 @@ function f() { a; x; y; z; }`); - testExtractFunction("extractFunction_VariableDeclaration_ShorthandProperty", ` + testExtractFunction("extractFunction_VariableDeclaration_ShorthandProperty", ` function f() { [#|let x;|] return { x }; }`); - testExtractFunction("extractFunction_PreserveTrivia", ` + testExtractFunction("extractFunction_PreserveTrivia", ` // a var q = /*b*/ //c /*d*/ [#|1 /*e*/ //f @@ -550,20 +550,20 @@ var q = /*b*/ //c /*j*/ 2|] /*k*/ //l /*m*/; /*n*/ //o`); - testExtractFunction("extractFunction_NamelessClass", ` + testExtractFunction("extractFunction_NamelessClass", ` export default class { M() { [#|1 + 1|]; } }`); - testExtractFunction("extractFunction_NoDeclarations", ` + testExtractFunction("extractFunction_NoDeclarations", ` function F() { [#|arguments.length|]; // arguments has no declaration }`); - }); +}); - function testExtractFunction(caption: string, text: string, includeLib?: boolean) { - testExtractSymbol(caption, text, "extractFunction", Diagnostics.Extract_function, includeLib); - } +function testExtractFunction(caption: string, text: string, includeLib?: boolean) { + testExtractSymbol(caption, text, "extractFunction", Diagnostics.Extract_function, includeLib); +} } diff --git a/src/testRunner/unittests/services/extract/helpers.ts b/src/testRunner/unittests/services/extract/helpers.ts index 9e86a96c617f2..e5cccd232e8b4 100644 --- a/src/testRunner/unittests/services/extract/helpers.ts +++ b/src/testRunner/unittests/services/extract/helpers.ts @@ -1,177 +1,177 @@ namespace ts { - interface Range { - pos: number; - end: number; - name: string; - } +interface Range { + pos: number; + end: number; + name: string; +} - interface Test { - source: string; - ranges: ESMap; - } +interface Test { + source: string; + ranges: ESMap; +} - export function extractTest(source: string): Test { - const activeRanges: Range[] = []; - let text = ""; - let lastPos = 0; - let pos = 0; - const ranges = new Map(); - - while (pos < source.length) { - if (source.charCodeAt(pos) === CharacterCodes.openBracket && - (source.charCodeAt(pos + 1) === CharacterCodes.hash || source.charCodeAt(pos + 1) === CharacterCodes.$)) { - const saved = pos; - pos += 2; - const s = pos; - consumeIdentifier(); - const e = pos; - if (source.charCodeAt(pos) === CharacterCodes.bar) { - pos++; - text += source.substring(lastPos, saved); - const name = s === e - ? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted" - : source.substring(s, e); - activeRanges.push({ name, pos: text.length, end: undefined! }); // TODO: GH#18217 - lastPos = pos; - continue; - } - else { - pos = saved; - } - } - else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) { - text += source.substring(lastPos, pos); - activeRanges[activeRanges.length - 1].end = text.length; - const range = activeRanges.pop()!; - if (hasProperty(ranges, range.name)) { - throw new Error(`Duplicate name of range ${range.name}`); - } - ranges.set(range.name, range); - pos += 2; +export function extractTest(source: string): Test { + const activeRanges: Range[] = []; + let text = ""; + let lastPos = 0; + let pos = 0; + const ranges = new Map(); + + while (pos < source.length) { + if (source.charCodeAt(pos) === CharacterCodes.openBracket && + (source.charCodeAt(pos + 1) === CharacterCodes.hash || source.charCodeAt(pos + 1) === CharacterCodes.$)) { + const saved = pos; + pos += 2; + const s = pos; + consumeIdentifier(); + const e = pos; + if (source.charCodeAt(pos) === CharacterCodes.bar) { + pos++; + text += source.substring(lastPos, saved); + const name = s === e + ? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted" + : source.substring(s, e); + activeRanges.push({ name, pos: text.length, end: undefined! }); // TODO: GH#18217 lastPos = pos; continue; } - pos++; + else { + pos = saved; + } } - text += source.substring(lastPos, pos); - - function consumeIdentifier() { - while (isIdentifierPart(source.charCodeAt(pos), ScriptTarget.Latest)) { - pos++; + else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) { + text += source.substring(lastPos, pos); + activeRanges[activeRanges.length - 1].end = text.length; + const range = activeRanges.pop()!; + if (hasProperty(ranges, range.name)) { + throw new Error(`Duplicate name of range ${range.name}`); } + ranges.set(range.name, range); + pos += 2; + lastPos = pos; + continue; } - return { source: text, ranges }; + pos++; } + text += source.substring(lastPos, pos); - export const newLineCharacter = "\n"; - - export const notImplementedHost: LanguageServiceHost = { - getCompilationSettings: notImplemented, - getScriptFileNames: notImplemented, - getScriptVersion: notImplemented, - getScriptSnapshot: notImplemented, - getDefaultLibFileName: notImplemented, - getCurrentDirectory: notImplemented, - }; - - export function testExtractSymbol(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage, includeLib?: boolean) { - const t = extractTest(text); - const selectionRange = t.ranges.get("selection")!; - if (!selectionRange) { - throw new Error(`Test ${caption} does not specify selection range`); + function consumeIdentifier() { + while (isIdentifierPart(source.charCodeAt(pos), ScriptTarget.Latest)) { + pos++; } + } + return { source: text, ranges }; +} - [Extension.Ts, Extension.Js].forEach(extension => - it(`${caption} [${extension}]`, () => runBaseline(extension))); +export const newLineCharacter = "\n"; + +export const notImplementedHost: LanguageServiceHost = { + getCompilationSettings: notImplemented, + getScriptFileNames: notImplemented, + getScriptVersion: notImplemented, + getScriptSnapshot: notImplemented, + getDefaultLibFileName: notImplemented, + getCurrentDirectory: notImplemented, +}; + +export function testExtractSymbol(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage, includeLib?: boolean) { + const t = extractTest(text); + const selectionRange = t.ranges.get("selection")!; + if (!selectionRange) { + throw new Error(`Test ${caption} does not specify selection range`); + } - function runBaseline(extension: Extension) { - const path = "/a" + extension; - const { program } = makeProgram({ path, content: t.source }, includeLib); + [Extension.Ts, Extension.Js].forEach(extension => + it(`${caption} [${extension}]`, () => runBaseline(extension))); - if (hasSyntacticDiagnostics(program)) { - // Don't bother generating JS baselines for inputs that aren't valid JS. - assert.equal(Extension.Js, extension, "Syntactic diagnostics found in non-JS file"); - return; - } + function runBaseline(extension: Extension) { + const path = "/a" + extension; + const { program } = makeProgram({ path, content: t.source }, includeLib); - const sourceFile = program.getSourceFile(path)!; - const context: RefactorContext = { - cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, - program, - file: sourceFile, - startPosition: selectionRange.pos, - endPosition: selectionRange.end, - host: notImplementedHost, - formatContext: formatting.getFormatContext(testFormatSettings, notImplementedHost), - preferences: emptyOptions, - }; - const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange)); - assert.equal(rangeToExtract.errors, undefined, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText); - const infos = refactor.extractSymbol.getAvailableActions(context); - const actions = find(infos, info => info.description === description.message)!.actions; - - const data: string[] = []; - data.push(`// ==ORIGINAL==`); - data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/")); - for (const action of actions) { - const { renameLocation, edits } = refactor.extractSymbol.getEditsForAction(context, action.name)!; - assert.lengthOf(edits, 1); - data.push(`// ==SCOPE::${action.description}==`); - const newText = textChanges.applyChanges(sourceFile.text, edits[0].textChanges); - const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation); - data.push(newTextWithRename); - - const { program: diagProgram } = makeProgram({ path, content: newText }, includeLib); - assert.isFalse(hasSyntacticDiagnostics(diagProgram)); - } - Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, data.join(newLineCharacter)); + if (hasSyntacticDiagnostics(program)) { + // Don't bother generating JS baselines for inputs that aren't valid JS. + assert.equal(Extension.Js, extension, "Syntactic diagnostics found in non-JS file"); + return; } - function makeProgram(f: {path: string, content: string }, includeLib?: boolean) { - const host = projectSystem.createServerHost(includeLib ? [f, projectSystem.libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required - const projectService = projectSystem.createProjectService(host); - projectService.openClientFile(f.path); - const program = projectService.inferredProjects[0].getLanguageService().getProgram()!; - const autoImportProvider = projectService.inferredProjects[0].getLanguageService().getAutoImportProvider(); - return { program, autoImportProvider }; + const sourceFile = program.getSourceFile(path)!; + const context: RefactorContext = { + cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, + program, + file: sourceFile, + startPosition: selectionRange.pos, + endPosition: selectionRange.end, + host: notImplementedHost, + formatContext: formatting.getFormatContext(testFormatSettings, notImplementedHost), + preferences: emptyOptions, + }; + const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange)); + assert.equal(rangeToExtract.errors, undefined, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText); + const infos = refactor.extractSymbol.getAvailableActions(context); + const actions = find(infos, info => info.description === description.message)!.actions; + + const data: string[] = []; + data.push(`// ==ORIGINAL==`); + data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/")); + for (const action of actions) { + const { renameLocation, edits } = refactor.extractSymbol.getEditsForAction(context, action.name)!; + assert.lengthOf(edits, 1); + data.push(`// ==SCOPE::${action.description}==`); + const newText = textChanges.applyChanges(sourceFile.text, edits[0].textChanges); + const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation); + data.push(newTextWithRename); + + const { program: diagProgram } = makeProgram({ path, content: newText }, includeLib); + assert.isFalse(hasSyntacticDiagnostics(diagProgram)); } + Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, data.join(newLineCharacter)); + } - function hasSyntacticDiagnostics(program: Program) { - const diags = program.getSyntacticDiagnostics(); - return length(diags) > 0; - } + function makeProgram(f: {path: string, content: string }, includeLib?: boolean) { + const host = projectSystem.createServerHost(includeLib ? [f, projectSystem.libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required + const projectService = projectSystem.createProjectService(host); + projectService.openClientFile(f.path); + const program = projectService.inferredProjects[0].getLanguageService().getProgram()!; + const autoImportProvider = projectService.inferredProjects[0].getLanguageService().getAutoImportProvider(); + return { program, autoImportProvider }; } - export function testExtractSymbolFailed(caption: string, text: string, description: DiagnosticMessage) { - it(caption, () => { - const t = extractTest(text); - const selectionRange = t.ranges.get("selection"); - if (!selectionRange) { - throw new Error(`Test ${caption} does not specify selection range`); - } - const f = { - path: "/a.ts", - content: t.source - }; - const host = projectSystem.createServerHost([f, projectSystem.libFile]); - const projectService = projectSystem.createProjectService(host); - projectService.openClientFile(f.path); - const program = projectService.inferredProjects[0].getLanguageService().getProgram()!; - const sourceFile = program.getSourceFile(f.path)!; - const context: RefactorContext = { - cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, - program, - file: sourceFile, - startPosition: selectionRange.pos, - endPosition: selectionRange.end, - host: notImplementedHost, - formatContext: formatting.getFormatContext(testFormatSettings, notImplementedHost), - preferences: emptyOptions, - }; - const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange)); - assert.isUndefined(rangeToExtract.errors, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText); - const infos = refactor.extractSymbol.getAvailableActions(context); - assert.isUndefined(find(infos, info => info.description === description.message)); - }); + function hasSyntacticDiagnostics(program: Program) { + const diags = program.getSyntacticDiagnostics(); + return length(diags) > 0; } } + +export function testExtractSymbolFailed(caption: string, text: string, description: DiagnosticMessage) { + it(caption, () => { + const t = extractTest(text); + const selectionRange = t.ranges.get("selection"); + if (!selectionRange) { + throw new Error(`Test ${caption} does not specify selection range`); + } + const f = { + path: "/a.ts", + content: t.source + }; + const host = projectSystem.createServerHost([f, projectSystem.libFile]); + const projectService = projectSystem.createProjectService(host); + projectService.openClientFile(f.path); + const program = projectService.inferredProjects[0].getLanguageService().getProgram()!; + const sourceFile = program.getSourceFile(f.path)!; + const context: RefactorContext = { + cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, + program, + file: sourceFile, + startPosition: selectionRange.pos, + endPosition: selectionRange.end, + host: notImplementedHost, + formatContext: formatting.getFormatContext(testFormatSettings, notImplementedHost), + preferences: emptyOptions, + }; + const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange)); + assert.isUndefined(rangeToExtract.errors, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText); + const infos = refactor.extractSymbol.getAvailableActions(context); + assert.isUndefined(find(infos, info => info.description === description.message)); + }); +} +} diff --git a/src/testRunner/unittests/services/extract/ranges.ts b/src/testRunner/unittests/services/extract/ranges.ts index 34f379df26d3c..d0d4c44bf6dd9 100644 --- a/src/testRunner/unittests/services/extract/ranges.ts +++ b/src/testRunner/unittests/services/extract/ranges.ts @@ -1,113 +1,113 @@ namespace ts { - function testExtractRangeFailed(caption: string, s: string, expectedErrors: string[]) { - return it(caption, () => { - const t = extractTest(s); - const file = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true); - const selectionRange = t.ranges.get("selection"); - if (!selectionRange) { - throw new Error(`Test ${s} does not specify selection range`); - } - const result = refactor.extractSymbol.getRangeToExtract(file, createTextSpanFromRange(selectionRange), /*userRequested*/ false); - assert(result.targetRange === undefined, "failure expected"); - const sortedErrors = result.errors!.map(e => e.messageText as string).sort(); - assert.deepEqual(sortedErrors, expectedErrors.sort(), "unexpected errors"); - }); - } +function testExtractRangeFailed(caption: string, s: string, expectedErrors: string[]) { + return it(caption, () => { + const t = extractTest(s); + const file = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true); + const selectionRange = t.ranges.get("selection"); + if (!selectionRange) { + throw new Error(`Test ${s} does not specify selection range`); + } + const result = refactor.extractSymbol.getRangeToExtract(file, createTextSpanFromRange(selectionRange), /*userRequested*/ false); + assert(result.targetRange === undefined, "failure expected"); + const sortedErrors = result.errors!.map(e => e.messageText as string).sort(); + assert.deepEqual(sortedErrors, expectedErrors.sort(), "unexpected errors"); + }); +} - function testExtractRange(caption: string, s: string) { - return it(caption, () => { - const t = extractTest(s); - const f = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true); - const selectionRange = t.ranges.get("selection"); - if (!selectionRange) { - throw new Error(`Test ${s} does not specify selection range`); - } - const result = refactor.extractSymbol.getRangeToExtract(f, createTextSpanFromRange(selectionRange)); - const expectedRange = t.ranges.get("extracted"); - if (expectedRange) { - let pos: number, end: number; - const targetRange = result.targetRange!; - if (isArray(targetRange.range)) { - pos = targetRange.range[0].getStart(f); - end = last(targetRange.range).getEnd(); - } - else { - pos = targetRange.range.getStart(f); - end = targetRange.range.getEnd(); - } - assert.equal(pos, expectedRange.pos, "incorrect pos of range"); - assert.equal(end, expectedRange.end, "incorrect end of range"); +function testExtractRange(caption: string, s: string) { + return it(caption, () => { + const t = extractTest(s); + const f = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true); + const selectionRange = t.ranges.get("selection"); + if (!selectionRange) { + throw new Error(`Test ${s} does not specify selection range`); + } + const result = refactor.extractSymbol.getRangeToExtract(f, createTextSpanFromRange(selectionRange)); + const expectedRange = t.ranges.get("extracted"); + if (expectedRange) { + let pos: number, end: number; + const targetRange = result.targetRange!; + if (isArray(targetRange.range)) { + pos = targetRange.range[0].getStart(f); + end = last(targetRange.range).getEnd(); } else { - assert.isTrue(!result.targetRange, `expected range to extract to be undefined`); + pos = targetRange.range.getStart(f); + end = targetRange.range.getEnd(); } - }); - } + assert.equal(pos, expectedRange.pos, "incorrect pos of range"); + assert.equal(end, expectedRange.end, "incorrect end of range"); + } + else { + assert.isTrue(!result.targetRange, `expected range to extract to be undefined`); + } + }); +} - describe("unittests:: services:: extract:: extractRanges", () => { - describe("get extract range from selection", () => { - testExtractRange("extractRange1", ` +describe("unittests:: services:: extract:: extractRanges", () => { + describe("get extract range from selection", () => { + testExtractRange("extractRange1", ` [#| [$|var x = 1; var y = 2;|]|] `); - testExtractRange("extractRange2", ` + testExtractRange("extractRange2", ` [$|[#|var x = 1; var y = 2|];|] `); - testExtractRange("extractRange3", ` + testExtractRange("extractRange3", ` [#|var x = [$|1|]|]; var y = 2; `); - testExtractRange("extractRange4", ` + testExtractRange("extractRange4", ` var x = [$|10[#|00|]|]; `); - testExtractRange("extractRange5", ` + testExtractRange("extractRange5", ` [$|va[#|r foo = 1; var y = 200|]0;|] `); - testExtractRange("extractRange6", ` + testExtractRange("extractRange6", ` var x = [$|fo[#|o.bar.baz()|]|]; `); - testExtractRange("extractRange7", ` + testExtractRange("extractRange7", ` if ([#|[#extracted|a && b && c && d|]|]) { } `); - testExtractRange("extractRange8", ` + testExtractRange("extractRange8", ` if [#|(a && b && c && d|]) { } `); - testExtractRange("extractRange9", ` + testExtractRange("extractRange9", ` if ([$|a[#|a && b && c && d|]d|]) { } `); - testExtractRange("extractRange10", ` + testExtractRange("extractRange10", ` if (a && b && c && d) { [#| [$|var x = 1; console.log(x);|] |] } `); - testExtractRange("extractRange11", ` + testExtractRange("extractRange11", ` [#| if (a) { return 100; } |] `); - testExtractRange("extractRange12", ` + testExtractRange("extractRange12", ` function foo() { [#| [$|if (a) { } return 100|] |] } `); - testExtractRange("extractRange13", ` + testExtractRange("extractRange13", ` [#| [$|l1: if (x) { break l1; }|]|] `); - testExtractRange("extractRange14", ` + testExtractRange("extractRange14", ` [#| [$|l2: { @@ -116,21 +116,21 @@ namespace ts { break l2; }|]|] `); - testExtractRange("extractRange15", ` + testExtractRange("extractRange15", ` while (true) { [#| if(x) { } break; |] } `); - testExtractRange("extractRange16", ` + testExtractRange("extractRange16", ` while (true) { [#| if(x) { } continue; |] } `); - testExtractRange("extractRange17", ` + testExtractRange("extractRange17", ` l3: { [#| @@ -139,7 +139,7 @@ namespace ts { break l3; |] } `); - testExtractRange("extractRange18", ` + testExtractRange("extractRange18", ` function f() { while (true) { [#| @@ -149,7 +149,7 @@ namespace ts { } } `); - testExtractRange("extractRange19", ` + testExtractRange("extractRange19", ` function f() { while (true) { [#| @@ -160,13 +160,13 @@ namespace ts { } } `); - testExtractRange("extractRange20", ` + testExtractRange("extractRange20", ` function f() { return [#| [$|1 + 2|] |]+ 3; } } `); - testExtractRange("extractRange21", ` + testExtractRange("extractRange21", ` function f(x: number) { [#|[$|try { x++; @@ -177,26 +177,26 @@ namespace ts { } `); - // Variable statements - testExtractRange("extractRange22", `[#|let x = [$|1|];|]`); - testExtractRange("extractRange23", `[#|let x = [$|1|], y;|]`); - testExtractRange("extractRange24", `[#|[$|let x = 1, y = 1;|]|]`); + // Variable statements + testExtractRange("extractRange22", `[#|let x = [$|1|];|]`); + testExtractRange("extractRange23", `[#|let x = [$|1|], y;|]`); + testExtractRange("extractRange24", `[#|[$|let x = 1, y = 1;|]|]`); - // Variable declarations - testExtractRange("extractRange25", `let [#|x = [$|1|]|];`); - testExtractRange("extractRange26", `let [#|x = [$|1|]|], y = 2;`); - testExtractRange("extractRange27", `let x = 1, [#|y = [$|2|]|];`); + // Variable declarations + testExtractRange("extractRange25", `let [#|x = [$|1|]|];`); + testExtractRange("extractRange26", `let [#|x = [$|1|]|], y = 2;`); + testExtractRange("extractRange27", `let x = 1, [#|y = [$|2|]|];`); - // Return statements - testExtractRange("extractRange28", `[#|return [$|1|];|]`); + // Return statements + testExtractRange("extractRange28", `[#|return [$|1|];|]`); - // For statements - testExtractRange("extractRange29", `for ([#|var i = [$|1|]|]; i < 2; i++) {}`); - testExtractRange("extractRange30", `for (var i = [#|[$|1|]|]; i < 2; i++) {}`); - }); + // For statements + testExtractRange("extractRange29", `for ([#|var i = [$|1|]|]; i < 2; i++) {}`); + testExtractRange("extractRange30", `for (var i = [#|[$|1|]|]; i < 2; i++) {}`); + }); - testExtractRangeFailed("extractRangeFailed1", - ` + testExtractRangeFailed("extractRangeFailed1", + ` namespace A { function f() { [#| @@ -208,10 +208,10 @@ function f() { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]); + [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]); - testExtractRangeFailed("extractRangeFailed2", - ` + testExtractRangeFailed("extractRangeFailed2", + ` namespace A { function f() { while (true) { @@ -225,10 +225,10 @@ function f() { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); + [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); - testExtractRangeFailed("extractRangeFailed3", - ` + testExtractRangeFailed("extractRangeFailed3", + ` namespace A { function f() { while (true) { @@ -242,10 +242,10 @@ function f() { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); + [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); - testExtractRangeFailed("extractRangeFailed4", - ` + testExtractRangeFailed("extractRangeFailed4", + ` namespace A { function f() { l1: { @@ -259,10 +259,10 @@ function f() { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange.message]); + [refactor.extractSymbol.Messages.cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange.message]); - testExtractRangeFailed("extractRangeFailed5", - ` + testExtractRangeFailed("extractRangeFailed5", + ` namespace A { function f() { [#| @@ -278,10 +278,10 @@ function f2() { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]); + [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]); - testExtractRangeFailed("extractRangeFailed6", - ` + testExtractRangeFailed("extractRangeFailed6", + ` namespace A { function f() { [#| @@ -297,10 +297,10 @@ function f2() { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]); + [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]); - testExtractRangeFailed("extractRangeFailed7", - ` + testExtractRangeFailed("extractRangeFailed7", + ` function test(x: number) { while (x) { x--; @@ -308,10 +308,10 @@ while (x) { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); + [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); - testExtractRangeFailed("extractRangeFailed8", - ` + testExtractRangeFailed("extractRangeFailed8", + ` function test(x: number) { switch (x) { case 1: @@ -319,23 +319,23 @@ switch (x) { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); + [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); - testExtractRangeFailed("extractRangeFailed9", - `var x = ([#||]1 + 2);`, - [refactor.extractSymbol.Messages.cannotExtractEmpty.message]); + testExtractRangeFailed("extractRangeFailed9", + `var x = ([#||]1 + 2);`, + [refactor.extractSymbol.Messages.cannotExtractEmpty.message]); - testExtractRangeFailed("extractRangeFailed10", - ` + testExtractRangeFailed("extractRangeFailed10", + ` function f() { return 1 + [#|2 + 3|]; } } `, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + [refactor.extractSymbol.Messages.cannotExtractRange.message]); - testExtractRangeFailed("extractRangeFailed11", - ` + testExtractRangeFailed("extractRangeFailed11", + ` function f(x: number) { while (true) { [#|try { @@ -347,62 +347,62 @@ switch (x) { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); + [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); - testExtractRangeFailed("extractRangeFailed12", - `let [#|x|];`, - [refactor.extractSymbol.Messages.statementOrExpressionExpected.message]); + testExtractRangeFailed("extractRangeFailed12", + `let [#|x|];`, + [refactor.extractSymbol.Messages.statementOrExpressionExpected.message]); - testExtractRangeFailed("extractRangeFailed13", - `[#|return;|]`, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + testExtractRangeFailed("extractRangeFailed13", + `[#|return;|]`, + [refactor.extractSymbol.Messages.cannotExtractRange.message]); - testExtractRangeFailed("extractRangeFailed14", - ` + testExtractRangeFailed("extractRangeFailed14", + ` switch(1) { case [#|1: break;|] } `, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + [refactor.extractSymbol.Messages.cannotExtractRange.message]); - testExtractRangeFailed("extractRangeFailed15", - ` + testExtractRangeFailed("extractRangeFailed15", + ` switch(1) { case [#|1: break|]; } `, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + [refactor.extractSymbol.Messages.cannotExtractRange.message]); - // Documentation only - it would be nice if the result were [$|1|] - testExtractRangeFailed("extractRangeFailed16", - ` + // Documentation only - it would be nice if the result were [$|1|] + testExtractRangeFailed("extractRangeFailed16", + ` switch(1) { [#|case 1|]: break; } `, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + [refactor.extractSymbol.Messages.cannotExtractRange.message]); - // Documentation only - it would be nice if the result were [$|1|] - testExtractRangeFailed("extractRangeFailed17", - ` + // Documentation only - it would be nice if the result were [$|1|] + testExtractRangeFailed("extractRangeFailed17", + ` switch(1) { [#|case 1:|] break; } `, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + [refactor.extractSymbol.Messages.cannotExtractRange.message]); - testExtractRangeFailed("extractRangeFailed18", - `[#|{ 1;|] }`, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + testExtractRangeFailed("extractRangeFailed18", + `[#|{ 1;|] }`, + [refactor.extractSymbol.Messages.cannotExtractRange.message]); - testExtractRangeFailed("extractRangeFailed19", - `[#|/** @type {number} */|] const foo = 1;`, - [refactor.extractSymbol.Messages.cannotExtractJSDoc.message]); + testExtractRangeFailed("extractRangeFailed19", + `[#|/** @type {number} */|] const foo = 1;`, + [refactor.extractSymbol.Messages.cannotExtractJSDoc.message]); - testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.cannotExtractIdentifier.message]); - }); + testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.cannotExtractIdentifier.message]); +}); } diff --git a/src/testRunner/unittests/services/extract/symbolWalker.ts b/src/testRunner/unittests/services/extract/symbolWalker.ts index 58d9dcb577f39..9d090ef45a610 100644 --- a/src/testRunner/unittests/services/extract/symbolWalker.ts +++ b/src/testRunner/unittests/services/extract/symbolWalker.ts @@ -1,45 +1,45 @@ namespace ts { - describe("unittests:: services:: extract:: Symbol Walker", () => { - function test(description: string, source: string, verifier: (file: SourceFile, checker: TypeChecker) => void) { - it(description, () => { - const result = Harness.Compiler.compileFiles([{ - unitName: "main.ts", - content: source - }], [], {}, {}, "/"); - const file = result.program!.getSourceFile("main.ts")!; - const checker = result.program!.getTypeChecker(); - verifier(file, checker); - }); - } +describe("unittests:: services:: extract:: Symbol Walker", () => { + function test(description: string, source: string, verifier: (file: SourceFile, checker: TypeChecker) => void) { + it(description, () => { + const result = Harness.Compiler.compileFiles([{ + unitName: "main.ts", + content: source + }], [], {}, {}, "/"); + const file = result.program!.getSourceFile("main.ts")!; + const checker = result.program!.getTypeChecker(); + verifier(file, checker); + }); + } - test("can be created", ` + test("can be created", ` interface Bar { x: number; y: number; history: Bar[]; } export default function foo(a: number, b: Bar): void {}`, (file, checker) => { - let foundCount = 0; - let stdLibRefSymbols = 0; - const expectedSymbols = ["default", "a", "b", "Bar", "x", "y", "history"]; - const walker = checker.getSymbolWalker(symbol => { - const isStdLibSymbol = forEach(symbol.declarations, d => { - return getSourceFileOfNode(d).hasNoDefaultLib; - }); - if (isStdLibSymbol) { - stdLibRefSymbols++; - return false; // Don't traverse into the stdlib. That's unnecessary for this test. - } - assert.equal(symbol.name, expectedSymbols[foundCount]); - foundCount++; - return true; + let foundCount = 0; + let stdLibRefSymbols = 0; + const expectedSymbols = ["default", "a", "b", "Bar", "x", "y", "history"]; + const walker = checker.getSymbolWalker(symbol => { + const isStdLibSymbol = forEach(symbol.declarations, d => { + return getSourceFileOfNode(d).hasNoDefaultLib; }); - const symbols = checker.getExportsOfModule(file.symbol); - for (const symbol of symbols) { - walker.walkSymbol(symbol); + if (isStdLibSymbol) { + stdLibRefSymbols++; + return false; // Don't traverse into the stdlib. That's unnecessary for this test. } - assert.equal(foundCount, expectedSymbols.length); - assert.equal(stdLibRefSymbols, 1); // Expect 1 stdlib entry symbol - the implicit Array referenced by Bar.history + assert.equal(symbol.name, expectedSymbols[foundCount]); + foundCount++; + return true; }); + const symbols = checker.getExportsOfModule(file.symbol); + for (const symbol of symbols) { + walker.walkSymbol(symbol); + } + assert.equal(foundCount, expectedSymbols.length); + assert.equal(stdLibRefSymbols, 1); // Expect 1 stdlib entry symbol - the implicit Array referenced by Bar.history }); +}); } diff --git a/src/testRunner/unittests/services/hostNewLineSupport.ts b/src/testRunner/unittests/services/hostNewLineSupport.ts index 057cb60602a4b..39cc01e124045 100644 --- a/src/testRunner/unittests/services/hostNewLineSupport.ts +++ b/src/testRunner/unittests/services/hostNewLineSupport.ts @@ -1,71 +1,71 @@ namespace ts { - describe("unittests:: services:: hostNewLineSupport", () => { - function testLSWithFiles(settings: CompilerOptions, files: Harness.Compiler.TestFile[]) { - function snapFor(path: string): IScriptSnapshot | undefined { - if (path === "lib.d.ts") { - return ScriptSnapshot.fromString(""); - } - const result = find(files, f => f.unitName === path); - return result && ScriptSnapshot.fromString(result.content); +describe("unittests:: services:: hostNewLineSupport", () => { + function testLSWithFiles(settings: CompilerOptions, files: Harness.Compiler.TestFile[]) { + function snapFor(path: string): IScriptSnapshot | undefined { + if (path === "lib.d.ts") { + return ScriptSnapshot.fromString(""); } - const lshost: LanguageServiceHost = { - getCompilationSettings: () => settings, - getScriptFileNames: () => map(files, f => f.unitName), - getScriptVersion: () => "1", - getScriptSnapshot: name => snapFor(name), - getDefaultLibFileName: () => "lib.d.ts", - getCurrentDirectory: () => "", - }; - return createLanguageService(lshost); + const result = find(files, f => f.unitName === path); + return result && ScriptSnapshot.fromString(result.content); } + const lshost: LanguageServiceHost = { + getCompilationSettings: () => settings, + getScriptFileNames: () => map(files, f => f.unitName), + getScriptVersion: () => "1", + getScriptSnapshot: name => snapFor(name), + getDefaultLibFileName: () => "lib.d.ts", + getCurrentDirectory: () => "", + }; + return createLanguageService(lshost); + } - function verifyNewLines(content: string, options: CompilerOptions) { - const ls = testLSWithFiles(options, [{ - content, - fileOptions: {}, - unitName: "input.ts" - }]); - const result = ls.getEmitOutput("input.ts"); - assert(!result.emitSkipped, "emit was skipped"); - assert(result.outputFiles.length === 1, "a number of files other than 1 was output"); - assert(result.outputFiles[0].name === "input.js", `Expected output file name input.js, but got ${result.outputFiles[0].name}`); - assert(result.outputFiles[0].text.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /\r\n/ : /[^\r]\n/), "expected to find appropriate newlines"); - assert(!result.outputFiles[0].text.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /[^\r]\n/ : /\r\n/), "expected not to find inappropriate newlines"); - } + function verifyNewLines(content: string, options: CompilerOptions) { + const ls = testLSWithFiles(options, [{ + content, + fileOptions: {}, + unitName: "input.ts" + }]); + const result = ls.getEmitOutput("input.ts"); + assert(!result.emitSkipped, "emit was skipped"); + assert(result.outputFiles.length === 1, "a number of files other than 1 was output"); + assert(result.outputFiles[0].name === "input.js", `Expected output file name input.js, but got ${result.outputFiles[0].name}`); + assert(result.outputFiles[0].text.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /\r\n/ : /[^\r]\n/), "expected to find appropriate newlines"); + assert(!result.outputFiles[0].text.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /[^\r]\n/ : /\r\n/), "expected not to find inappropriate newlines"); + } - function verifyBothNewLines(content: string) { - verifyNewLines(content, { newLine: NewLineKind.CarriageReturnLineFeed }); - verifyNewLines(content, { newLine: NewLineKind.LineFeed }); - } + function verifyBothNewLines(content: string) { + verifyNewLines(content, { newLine: NewLineKind.CarriageReturnLineFeed }); + verifyNewLines(content, { newLine: NewLineKind.LineFeed }); + } - function verifyOutliningSpanNewLines(content: string, options: CompilerOptions) { - const ls = testLSWithFiles(options, [{ - content, - fileOptions: {}, - unitName: "input.ts" - }]); - const span = ls.getOutliningSpans("input.ts")[0]; - const textAfterSpanCollapse = content.substring(span.textSpan.start + span.textSpan.length); - assert(textAfterSpanCollapse.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /\r\n/ : /[^\r]\n/), "expected to find appropriate newlines"); - assert(!textAfterSpanCollapse.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /[^\r]\n/ : /\r\n/), "expected not to find inappropriate newlines"); - } + function verifyOutliningSpanNewLines(content: string, options: CompilerOptions) { + const ls = testLSWithFiles(options, [{ + content, + fileOptions: {}, + unitName: "input.ts" + }]); + const span = ls.getOutliningSpans("input.ts")[0]; + const textAfterSpanCollapse = content.substring(span.textSpan.start + span.textSpan.length); + assert(textAfterSpanCollapse.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /\r\n/ : /[^\r]\n/), "expected to find appropriate newlines"); + assert(!textAfterSpanCollapse.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /[^\r]\n/ : /\r\n/), "expected not to find inappropriate newlines"); + } - it("should exist and respect provided compiler options", () => { - verifyBothNewLines(` + it("should exist and respect provided compiler options", () => { + verifyBothNewLines(` function foo() { return 2 + 2; } `); - }); + }); - it("should respect CRLF line endings around outlining spans", () => { - verifyOutliningSpanNewLines("// comment not included\r\n// #region name\r\nlet x: string = \"x\";\r\n// #endregion name\r\n", - { newLine: NewLineKind.CarriageReturnLineFeed }); - }); + it("should respect CRLF line endings around outlining spans", () => { + verifyOutliningSpanNewLines("// comment not included\r\n// #region name\r\nlet x: string = \"x\";\r\n// #endregion name\r\n", + { newLine: NewLineKind.CarriageReturnLineFeed }); + }); - it("should respect LF line endings around outlining spans", () => { - verifyOutliningSpanNewLines("// comment not included\n// #region name\nlet x: string = \"x\";\n// #endregion name\n\n", - { newLine: NewLineKind.LineFeed }); - }); + it("should respect LF line endings around outlining spans", () => { + verifyOutliningSpanNewLines("// comment not included\n// #region name\nlet x: string = \"x\";\n// #endregion name\n\n", + { newLine: NewLineKind.LineFeed }); }); +}); } diff --git a/src/testRunner/unittests/services/languageService.ts b/src/testRunner/unittests/services/languageService.ts index a1f7e5844b84d..3bf6f437d67aa 100644 --- a/src/testRunner/unittests/services/languageService.ts +++ b/src/testRunner/unittests/services/languageService.ts @@ -1,9 +1,9 @@ namespace ts { - const _chai: typeof import("chai") = require("chai"); - const expect: typeof _chai.expect = _chai.expect; - describe("unittests:: services:: languageService", () => { - const files: {[index: string]: string} = { - "foo.ts": `import Vue from "./vue"; +const _chai: typeof import("chai") = require("chai"); +const expect: typeof _chai.expect = _chai.expect; +describe("unittests:: services:: languageService", () => { + const files: {[index: string]: string} = { + "foo.ts": `import Vue from "./vue"; import Component from "./vue-class-component"; import { vueTemplateHtml } from "./variables"; @@ -12,261 +12,261 @@ import { vueTemplateHtml } from "./variables"; }) class Carousel extends Vue { }`, - "variables.ts": `export const vueTemplateHtml = \`
\`;`, - "vue.d.ts": `export namespace Vue { export type Config = { template: string }; }`, - "vue-class-component.d.ts": `import Vue from "./vue"; + "variables.ts": `export const vueTemplateHtml = \`
\`;`, + "vue.d.ts": `export namespace Vue { export type Config = { template: string }; }`, + "vue-class-component.d.ts": `import Vue from "./vue"; export function Component(x: Config): any;` - }; + }; - function createLanguageService() { - return ts.createLanguageService({ - getCompilationSettings() { - return {}; - }, - getScriptFileNames() { - return ["foo.ts", "variables.ts", "vue.d.ts", "vue-class-component.d.ts"]; - }, - getScriptVersion(_fileName) { - return ""; - }, - getScriptSnapshot(fileName) { - if (fileName === ".ts") { - return ScriptSnapshot.fromString(""); - } - return ScriptSnapshot.fromString(files[fileName] || ""); - }, - getCurrentDirectory: () => ".", - getDefaultLibFileName(options) { - return getDefaultLibFilePath(options); - }, - }); - } - // Regression test for GH #18245 - bug in single line comment writer caused a debug assertion when attempting - // to write an alias to a module's default export was referrenced across files and had no default export - it("should be able to create a language service which can respond to deinition requests without throwing", () => { - const languageService = createLanguageService(); - const definitions = languageService.getDefinitionAtPosition("foo.ts", 160); // 160 is the latter `vueTemplateHtml` position - expect(definitions).to.exist; // eslint-disable-line @typescript-eslint/no-unused-expressions + function createLanguageService() { + return ts.createLanguageService({ + getCompilationSettings() { + return {}; + }, + getScriptFileNames() { + return ["foo.ts", "variables.ts", "vue.d.ts", "vue-class-component.d.ts"]; + }, + getScriptVersion(_fileName) { + return ""; + }, + getScriptSnapshot(fileName) { + if (fileName === ".ts") { + return ScriptSnapshot.fromString(""); + } + return ScriptSnapshot.fromString(files[fileName] || ""); + }, + getCurrentDirectory: () => ".", + getDefaultLibFileName(options) { + return getDefaultLibFilePath(options); + }, }); + } + // Regression test for GH #18245 - bug in single line comment writer caused a debug assertion when attempting + // to write an alias to a module's default export was referrenced across files and had no default export + it("should be able to create a language service which can respond to deinition requests without throwing", () => { + const languageService = createLanguageService(); + const definitions = languageService.getDefinitionAtPosition("foo.ts", 160); // 160 is the latter `vueTemplateHtml` position + expect(definitions).to.exist; // eslint-disable-line @typescript-eslint/no-unused-expressions + }); - it("getEmitOutput on language service has way to force dts emit", () => { - const languageService = createLanguageService(); - assert.deepEqual( - languageService.getEmitOutput( - "foo.ts", - /*emitOnlyDtsFiles*/ true - ), - { - emitSkipped: true, - diagnostics: emptyArray, - outputFiles: emptyArray, - exportedModulesFromDeclarationEmit: undefined - } - ); + it("getEmitOutput on language service has way to force dts emit", () => { + const languageService = createLanguageService(); + assert.deepEqual( + languageService.getEmitOutput( + "foo.ts", + /*emitOnlyDtsFiles*/ true + ), + { + emitSkipped: true, + diagnostics: emptyArray, + outputFiles: emptyArray, + exportedModulesFromDeclarationEmit: undefined + } + ); - assert.deepEqual( - languageService.getEmitOutput( - "foo.ts", - /*emitOnlyDtsFiles*/ true, - /*forceDtsEmit*/ true - ), - { - emitSkipped: false, - diagnostics: emptyArray, - outputFiles: [{ - name: "foo.d.ts", - text: "export {};\r\n", - writeByteOrderMark: false - }], - exportedModulesFromDeclarationEmit: undefined - } - ); - }); + assert.deepEqual( + languageService.getEmitOutput( + "foo.ts", + /*emitOnlyDtsFiles*/ true, + /*forceDtsEmit*/ true + ), + { + emitSkipped: false, + diagnostics: emptyArray, + outputFiles: [{ + name: "foo.d.ts", + text: "export {};\r\n", + writeByteOrderMark: false + }], + exportedModulesFromDeclarationEmit: undefined + } + ); + }); - describe("detects program upto date correctly", () => { - function verifyProgramUptoDate(useProjectVersion: boolean) { - let projectVersion = "1"; - const files = new Map(); - files.set("/project/root.ts", { version: "1", text: `import { foo } from "./other"` }); - files.set("/project/other.ts", { version: "1", text: `export function foo() { }` }); - files.set("/lib/lib.d.ts", { version: "1", text: projectSystem.libFile.content }); - const host: LanguageServiceHost = { - useCaseSensitiveFileNames: returnTrue, - getCompilationSettings: getDefaultCompilerOptions, - fileExists: path => files.has(path), - getProjectVersion: !useProjectVersion ? undefined : () => projectVersion, - getScriptFileNames: () => ["/project/root.ts"], - getScriptVersion: path => files.get(path)?.version || "", - getScriptSnapshot: path => { - const text = files.get(path)?.text; - return text ? ScriptSnapshot.fromString(text) : undefined; - }, - getCurrentDirectory: () => "/project", - getDefaultLibFileName: () => "/lib/lib.d.ts" - }; - const ls = ts.createLanguageService(host); - const program1 = ls.getProgram()!; - const program2 = ls.getProgram()!; - assert.strictEqual(program1, program2); - verifyProgramFiles(program1); + describe("detects program upto date correctly", () => { + function verifyProgramUptoDate(useProjectVersion: boolean) { + let projectVersion = "1"; + const files = new Map(); + files.set("/project/root.ts", { version: "1", text: `import { foo } from "./other"` }); + files.set("/project/other.ts", { version: "1", text: `export function foo() { }` }); + files.set("/lib/lib.d.ts", { version: "1", text: projectSystem.libFile.content }); + const host: LanguageServiceHost = { + useCaseSensitiveFileNames: returnTrue, + getCompilationSettings: getDefaultCompilerOptions, + fileExists: path => files.has(path), + getProjectVersion: !useProjectVersion ? undefined : () => projectVersion, + getScriptFileNames: () => ["/project/root.ts"], + getScriptVersion: path => files.get(path)?.version || "", + getScriptSnapshot: path => { + const text = files.get(path)?.text; + return text ? ScriptSnapshot.fromString(text) : undefined; + }, + getCurrentDirectory: () => "/project", + getDefaultLibFileName: () => "/lib/lib.d.ts" + }; + const ls = ts.createLanguageService(host); + const program1 = ls.getProgram()!; + const program2 = ls.getProgram()!; + assert.strictEqual(program1, program2); + verifyProgramFiles(program1); - // Change other - projectVersion = "2"; - files.set("/project/other.ts", { version: "2", text: `export function foo() { } export function bar() { }` }); - const program3 = ls.getProgram()!; - assert.notStrictEqual(program2, program3); - verifyProgramFiles(program3); + // Change other + projectVersion = "2"; + files.set("/project/other.ts", { version: "2", text: `export function foo() { } export function bar() { }` }); + const program3 = ls.getProgram()!; + assert.notStrictEqual(program2, program3); + verifyProgramFiles(program3); - // change root - projectVersion = "3"; - files.set("/project/root.ts", { version: "2", text: `import { foo, bar } from "./other"` }); - const program4 = ls.getProgram()!; - assert.notStrictEqual(program3, program4); - verifyProgramFiles(program4); + // change root + projectVersion = "3"; + files.set("/project/root.ts", { version: "2", text: `import { foo, bar } from "./other"` }); + const program4 = ls.getProgram()!; + assert.notStrictEqual(program3, program4); + verifyProgramFiles(program4); - function verifyProgramFiles(program: Program) { - assert.deepEqual( - program.getSourceFiles().map(f => f.fileName), - ["/lib/lib.d.ts", "/project/other.ts", "/project/root.ts"] - ); - } + function verifyProgramFiles(program: Program) { + assert.deepEqual( + program.getSourceFiles().map(f => f.fileName), + ["/lib/lib.d.ts", "/project/other.ts", "/project/root.ts"] + ); } - it("when host implements getProjectVersion", () => { - verifyProgramUptoDate(/*useProjectVersion*/ true); - }); - it("when host does not implement getProjectVersion", () => { - verifyProgramUptoDate(/*useProjectVersion*/ false); - }); + } + it("when host implements getProjectVersion", () => { + verifyProgramUptoDate(/*useProjectVersion*/ true); + }); + it("when host does not implement getProjectVersion", () => { + verifyProgramUptoDate(/*useProjectVersion*/ false); }); + }); - describe("detects program upto date when new file is added to the referenced project", () => { - function setup(useSourceOfProjectReferenceRedirect: (() => boolean) | undefined) { - const config1: TestFSWithWatch.File = { - path: `${tscWatch.projectRoot}/projects/project1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - exclude: ["temp"] - }) - }; - const class1: TestFSWithWatch.File = { - path: `${tscWatch.projectRoot}/projects/project1/class1.ts`, - content: `class class1 {}` - }; - const class1Dts: TestFSWithWatch.File = { - path: `${tscWatch.projectRoot}/projects/project1/class1.d.ts`, - content: `declare class class1 {}` - }; - const config2: TestFSWithWatch.File = { - path: `${tscWatch.projectRoot}/projects/project2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - references: [ - { path: "../project1" } - ] - }) - }; - const class2: TestFSWithWatch.File = { - path: `${tscWatch.projectRoot}/projects/project2/class2.ts`, - content: `class class2 {}` - }; - const system = projectSystem.createServerHost([config1, class1, class1Dts, config2, class2, projectSystem.libFile]); - const result = getParsedCommandLineOfConfigFile(`${tscWatch.projectRoot}/projects/project2/tsconfig.json`, /*optionsToExtend*/ undefined, { - useCaseSensitiveFileNames: true, - fileExists: path => system.fileExists(path), - readFile: path => system.readFile(path), - getCurrentDirectory: () => system.getCurrentDirectory(), - readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth), - onUnRecoverableConfigFileDiagnostic: noop, - })!; - const host: LanguageServiceHost = { - useCaseSensitiveFileNames: returnTrue, - useSourceOfProjectReferenceRedirect, - getCompilationSettings: () => result.options, - fileExists: path => system.fileExists(path), - getScriptFileNames: () => result.fileNames, - getScriptVersion: path => { - const text = system.readFile(path); - return text !== undefined ? system.createHash(path) : ""; + describe("detects program upto date when new file is added to the referenced project", () => { + function setup(useSourceOfProjectReferenceRedirect: (() => boolean) | undefined) { + const config1: TestFSWithWatch.File = { + path: `${tscWatch.projectRoot}/projects/project1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true }, - getScriptSnapshot: path => { - const text = system.readFile(path); - return text ? ScriptSnapshot.fromString(text) : undefined; + exclude: ["temp"] + }) + }; + const class1: TestFSWithWatch.File = { + path: `${tscWatch.projectRoot}/projects/project1/class1.ts`, + content: `class class1 {}` + }; + const class1Dts: TestFSWithWatch.File = { + path: `${tscWatch.projectRoot}/projects/project1/class1.d.ts`, + content: `declare class class1 {}` + }; + const config2: TestFSWithWatch.File = { + path: `${tscWatch.projectRoot}/projects/project2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true }, - readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth), - getCurrentDirectory: () => system.getCurrentDirectory(), - getDefaultLibFileName: () => projectSystem.libFile.path, - getProjectReferences: () => result.projectReferences, - }; - const ls = ts.createLanguageService(host); - return { system, ls, class1, class1Dts, class2 }; - } - it("detects program upto date when new file is added to the referenced project", () => { - const { ls, system, class1, class2 } = setup(returnTrue); - assert.deepEqual( - ls.getProgram()!.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1.path, class2.path] - ); - // Add new file to referenced project - const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; - system.writeFile(class3, `class class3 {}`); - const program = ls.getProgram()!; - assert.deepEqual( - program.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1.path, class3, class2.path] - ); - // Add excluded file to referenced project - system.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); - assert.strictEqual(ls.getProgram(), program); - // Add output from new class to referenced project - system.writeFile(`${tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`); - assert.strictEqual(ls.getProgram(), program); - }); + references: [ + { path: "../project1" } + ] + }) + }; + const class2: TestFSWithWatch.File = { + path: `${tscWatch.projectRoot}/projects/project2/class2.ts`, + content: `class class2 {}` + }; + const system = projectSystem.createServerHost([config1, class1, class1Dts, config2, class2, projectSystem.libFile]); + const result = getParsedCommandLineOfConfigFile(`${tscWatch.projectRoot}/projects/project2/tsconfig.json`, /*optionsToExtend*/ undefined, { + useCaseSensitiveFileNames: true, + fileExists: path => system.fileExists(path), + readFile: path => system.readFile(path), + getCurrentDirectory: () => system.getCurrentDirectory(), + readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth), + onUnRecoverableConfigFileDiagnostic: noop, + })!; + const host: LanguageServiceHost = { + useCaseSensitiveFileNames: returnTrue, + useSourceOfProjectReferenceRedirect, + getCompilationSettings: () => result.options, + fileExists: path => system.fileExists(path), + getScriptFileNames: () => result.fileNames, + getScriptVersion: path => { + const text = system.readFile(path); + return text !== undefined ? system.createHash(path) : ""; + }, + getScriptSnapshot: path => { + const text = system.readFile(path); + return text ? ScriptSnapshot.fromString(text) : undefined; + }, + readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth), + getCurrentDirectory: () => system.getCurrentDirectory(), + getDefaultLibFileName: () => projectSystem.libFile.path, + getProjectReferences: () => result.projectReferences, + }; + const ls = ts.createLanguageService(host); + return { system, ls, class1, class1Dts, class2 }; + } + it("detects program upto date when new file is added to the referenced project", () => { + const { ls, system, class1, class2 } = setup(returnTrue); + assert.deepEqual( + ls.getProgram()!.getSourceFiles().map(f => f.fileName), + [projectSystem.libFile.path, class1.path, class2.path] + ); + // Add new file to referenced project + const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; + system.writeFile(class3, `class class3 {}`); + const program = ls.getProgram()!; + assert.deepEqual( + program.getSourceFiles().map(f => f.fileName), + [projectSystem.libFile.path, class1.path, class3, class2.path] + ); + // Add excluded file to referenced project + system.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); + assert.strictEqual(ls.getProgram(), program); + // Add output from new class to referenced project + system.writeFile(`${tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`); + assert.strictEqual(ls.getProgram(), program); + }); - it("detects program upto date when new file is added to the referenced project without useSourceOfProjectReferenceRedirect", () => { - const { ls, system, class1Dts, class2 } = setup(/*useSourceOfProjectReferenceRedirect*/ undefined); - const program1 = ls.getProgram()!; - assert.deepEqual( - program1.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1Dts.path, class2.path] - ); - // Add new file to referenced project - const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; - system.writeFile(class3, `class class3 {}`); - assert.notStrictEqual(ls.getProgram(), program1); - assert.deepEqual( - ls.getProgram()!.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1Dts.path, class2.path] - ); - // Add class3 output - const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; - system.writeFile(class3Dts, `declare class class3 {}`); - const program2 = ls.getProgram()!; - assert.deepEqual( - program2.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1Dts.path, class3Dts, class2.path] - ); - // Add excluded file to referenced project - system.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); - assert.strictEqual(ls.getProgram(), program2); - // Delete output from new class to referenced project - system.deleteFile(class3Dts); - assert.deepEqual( - ls.getProgram()!.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1Dts.path, class2.path] - ); - // Write output again - system.writeFile(class3Dts, `declare class class3 {}`); - assert.deepEqual( - ls.getProgram()!.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1Dts.path, class3Dts, class2.path] - ); - }); + it("detects program upto date when new file is added to the referenced project without useSourceOfProjectReferenceRedirect", () => { + const { ls, system, class1Dts, class2 } = setup(/*useSourceOfProjectReferenceRedirect*/ undefined); + const program1 = ls.getProgram()!; + assert.deepEqual( + program1.getSourceFiles().map(f => f.fileName), + [projectSystem.libFile.path, class1Dts.path, class2.path] + ); + // Add new file to referenced project + const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; + system.writeFile(class3, `class class3 {}`); + assert.notStrictEqual(ls.getProgram(), program1); + assert.deepEqual( + ls.getProgram()!.getSourceFiles().map(f => f.fileName), + [projectSystem.libFile.path, class1Dts.path, class2.path] + ); + // Add class3 output + const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; + system.writeFile(class3Dts, `declare class class3 {}`); + const program2 = ls.getProgram()!; + assert.deepEqual( + program2.getSourceFiles().map(f => f.fileName), + [projectSystem.libFile.path, class1Dts.path, class3Dts, class2.path] + ); + // Add excluded file to referenced project + system.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); + assert.strictEqual(ls.getProgram(), program2); + // Delete output from new class to referenced project + system.deleteFile(class3Dts); + assert.deepEqual( + ls.getProgram()!.getSourceFiles().map(f => f.fileName), + [projectSystem.libFile.path, class1Dts.path, class2.path] + ); + // Write output again + system.writeFile(class3Dts, `declare class class3 {}`); + assert.deepEqual( + ls.getProgram()!.getSourceFiles().map(f => f.fileName), + [projectSystem.libFile.path, class1Dts.path, class3Dts, class2.path] + ); }); }); +}); } diff --git a/src/testRunner/unittests/services/organizeImports.ts b/src/testRunner/unittests/services/organizeImports.ts index f3b222bb76791..c60b7114643b1 100644 --- a/src/testRunner/unittests/services/organizeImports.ts +++ b/src/testRunner/unittests/services/organizeImports.ts @@ -1,366 +1,366 @@ namespace ts { - describe("unittests:: services:: organizeImports", () => { - describe("Sort imports", () => { - it("Sort - non-relative vs non-relative", () => { - assertSortsBefore( - `import y from "lib1";`, - `import x from "lib2";`); - }); - - it("Sort - relative vs relative", () => { - assertSortsBefore( - `import y from "./lib1";`, - `import x from "./lib2";`); - }); +describe("unittests:: services:: organizeImports", () => { + describe("Sort imports", () => { + it("Sort - non-relative vs non-relative", () => { + assertSortsBefore( + `import y from "lib1";`, + `import x from "lib2";`); + }); - it("Sort - relative vs non-relative", () => { - assertSortsBefore( - `import y from "lib";`, - `import x from "./lib";`); - }); + it("Sort - relative vs relative", () => { + assertSortsBefore( + `import y from "./lib1";`, + `import x from "./lib2";`); + }); - it("Sort - case-insensitive", () => { - assertSortsBefore( - `import y from "a";`, - `import x from "Z";`); - assertSortsBefore( - `import y from "A";`, - `import x from "z";`); - }); + it("Sort - relative vs non-relative", () => { + assertSortsBefore( + `import y from "lib";`, + `import x from "./lib";`); + }); - function assertSortsBefore(importString1: string, importString2: string) { - const [{moduleSpecifier: moduleSpecifier1}, {moduleSpecifier: moduleSpecifier2}] = parseImports(importString1, importString2); - assert.equal(OrganizeImports.compareModuleSpecifiers(moduleSpecifier1, moduleSpecifier2), Comparison.LessThan); - assert.equal(OrganizeImports.compareModuleSpecifiers(moduleSpecifier2, moduleSpecifier1), Comparison.GreaterThan); - } + it("Sort - case-insensitive", () => { + assertSortsBefore( + `import y from "a";`, + `import x from "Z";`); + assertSortsBefore( + `import y from "A";`, + `import x from "z";`); }); - describe("Coalesce imports", () => { - it("No imports", () => { - assert.isEmpty(OrganizeImports.coalesceImports([])); - }); + function assertSortsBefore(importString1: string, importString2: string) { + const [{moduleSpecifier: moduleSpecifier1}, {moduleSpecifier: moduleSpecifier2}] = parseImports(importString1, importString2); + assert.equal(OrganizeImports.compareModuleSpecifiers(moduleSpecifier1, moduleSpecifier2), Comparison.LessThan); + assert.equal(OrganizeImports.compareModuleSpecifiers(moduleSpecifier2, moduleSpecifier1), Comparison.GreaterThan); + } + }); - it("Sort specifiers - case-insensitive", () => { - const sortedImports = parseImports(`import { default as M, a as n, B, y, Z as O } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports(`import { a as n, B, default as M, y, Z as O } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + describe("Coalesce imports", () => { + it("No imports", () => { + assert.isEmpty(OrganizeImports.coalesceImports([])); + }); - it("Combine side-effect-only imports", () => { - const sortedImports = parseImports( - `import "lib";`, - `import "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports(`import "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Sort specifiers - case-insensitive", () => { + const sortedImports = parseImports(`import { default as M, a as n, B, y, Z as O } from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports(`import { a as n, B, default as M, y, Z as O } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine namespace imports", () => { - const sortedImports = parseImports( - `import * as x from "lib";`, - `import * as y from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = sortedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine side-effect-only imports", () => { + const sortedImports = parseImports( + `import "lib";`, + `import "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports(`import "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine default imports", () => { - const sortedImports = parseImports( - `import x from "lib";`, - `import y from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports(`import { default as x, default as y } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine namespace imports", () => { + const sortedImports = parseImports( + `import * as x from "lib";`, + `import * as y from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = sortedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine property imports", () => { - const sortedImports = parseImports( - `import { x } from "lib";`, - `import { y as z } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports(`import { x, y as z } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine default imports", () => { + const sortedImports = parseImports( + `import x from "lib";`, + `import y from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports(`import { default as x, default as y } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine side-effect-only import with namespace import", () => { - const sortedImports = parseImports( - `import "lib";`, - `import * as x from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = sortedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine property imports", () => { + const sortedImports = parseImports( + `import { x } from "lib";`, + `import { y as z } from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports(`import { x, y as z } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine side-effect-only import with default import", () => { - const sortedImports = parseImports( - `import "lib";`, - `import x from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = sortedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine side-effect-only import with namespace import", () => { + const sortedImports = parseImports( + `import "lib";`, + `import * as x from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = sortedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine side-effect-only import with property import", () => { - const sortedImports = parseImports( - `import "lib";`, - `import { x } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = sortedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine side-effect-only import with default import", () => { + const sortedImports = parseImports( + `import "lib";`, + `import x from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = sortedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine namespace import with default import", () => { - const sortedImports = parseImports( - `import * as x from "lib";`, - `import y from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports( - `import y, * as x from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine side-effect-only import with property import", () => { + const sortedImports = parseImports( + `import "lib";`, + `import { x } from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = sortedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine namespace import with property import", () => { - const sortedImports = parseImports( - `import * as x from "lib";`, - `import { y } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = sortedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine namespace import with default import", () => { + const sortedImports = parseImports( + `import * as x from "lib";`, + `import y from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports( + `import y, * as x from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine default import with property import", () => { - const sortedImports = parseImports( - `import x from "lib";`, - `import { y } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports( - `import x, { y } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine namespace import with property import", () => { + const sortedImports = parseImports( + `import * as x from "lib";`, + `import { y } from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = sortedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine many imports", () => { - const sortedImports = parseImports( - `import "lib";`, - `import * as y from "lib";`, - `import w from "lib";`, - `import { b } from "lib";`, - `import "lib";`, - `import * as x from "lib";`, - `import z from "lib";`, - `import { a } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports( - `import "lib";`, - `import * as x from "lib";`, - `import * as y from "lib";`, - `import { a, b, default as w, default as z } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine default import with property import", () => { + const sortedImports = parseImports( + `import x from "lib";`, + `import { y } from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports( + `import x, { y } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - // This is descriptive, rather than normative - it("Combine two namespace imports with one default import", () => { - const sortedImports = parseImports( - `import * as x from "lib";`, - `import * as y from "lib";`, - `import z from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = sortedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine many imports", () => { + const sortedImports = parseImports( + `import "lib";`, + `import * as y from "lib";`, + `import w from "lib";`, + `import { b } from "lib";`, + `import "lib";`, + `import * as x from "lib";`, + `import z from "lib";`, + `import { a } from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports( + `import "lib";`, + `import * as x from "lib";`, + `import * as y from "lib";`, + `import { a, b, default as w, default as z } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine type-only imports separately from other imports", () => { - const sortedImports = parseImports( - `import type { x } from "lib";`, - `import type { y } from "lib";`, - `import { z } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports( - `import { z } from "lib";`, - `import type { x, y } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + // This is descriptive, rather than normative + it("Combine two namespace imports with one default import", () => { + const sortedImports = parseImports( + `import * as x from "lib";`, + `import * as y from "lib";`, + `import z from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = sortedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Do not combine type-only default, namespace, or named imports with each other", () => { - const sortedImports = parseImports( - `import type { x } from "lib";`, - `import type * as y from "lib";`, - `import type z from "lib";`); - // Default import could be rewritten as a named import to combine with `x`, - // but seems of debatable merit. - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = actualCoalescedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine type-only imports separately from other imports", () => { + const sortedImports = parseImports( + `import type { x } from "lib";`, + `import type { y } from "lib";`, + `import { z } from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports( + `import { z } from "lib";`, + `import type { x, y } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); }); - describe("Coalesce exports", () => { - it("No exports", () => { - assert.isEmpty(OrganizeImports.coalesceExports([])); - }); + it("Do not combine type-only default, namespace, or named imports with each other", () => { + const sortedImports = parseImports( + `import type { x } from "lib";`, + `import type * as y from "lib";`, + `import type z from "lib";`); + // Default import could be rewritten as a named import to combine with `x`, + // but seems of debatable merit. + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = actualCoalescedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); + }); - it("Sort specifiers - case-insensitive", () => { - const sortedExports = parseExports(`export { default as M, a as n, B, y, Z as O } from "lib";`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports(`export { a as n, B, default as M, y, Z as O } from "lib";`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + describe("Coalesce exports", () => { + it("No exports", () => { + assert.isEmpty(OrganizeImports.coalesceExports([])); + }); - it("Sort specifiers - type-only", () => { - const sortedImports = parseImports(`import { type z, y, type x, c, type b, a } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports(`import { a, c, y, type b, type x, type z } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Sort specifiers - case-insensitive", () => { + const sortedExports = parseExports(`export { default as M, a as n, B, y, Z as O } from "lib";`); + const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports(`export { a as n, B, default as M, y, Z as O } from "lib";`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Combine namespace re-exports", () => { - const sortedExports = parseExports( - `export * from "lib";`, - `export * from "lib";`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports(`export * from "lib";`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Sort specifiers - type-only", () => { + const sortedImports = parseImports(`import { type z, y, type x, c, type b, a } from "lib";`); + const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports(`import { a, c, y, type b, type x, type z } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine property exports", () => { - const sortedExports = parseExports( - `export { x };`, - `export { y as z };`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports(`export { x, y as z };`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Combine namespace re-exports", () => { + const sortedExports = parseExports( + `export * from "lib";`, + `export * from "lib";`); + const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports(`export * from "lib";`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Combine property re-exports", () => { - const sortedExports = parseExports( - `export { x } from "lib";`, - `export { y as z } from "lib";`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports(`export { x, y as z } from "lib";`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Combine property exports", () => { + const sortedExports = parseExports( + `export { x };`, + `export { y as z };`); + const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports(`export { x, y as z };`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Combine namespace re-export with property re-export", () => { - const sortedExports = parseExports( - `export * from "lib";`, - `export { y } from "lib";`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = sortedExports; - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Combine property re-exports", () => { + const sortedExports = parseExports( + `export { x } from "lib";`, + `export { y as z } from "lib";`); + const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports(`export { x, y as z } from "lib";`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Combine many exports", () => { - const sortedExports = parseExports( - `export { x };`, - `export { y as w, z as default };`, - `export { w as q };`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports( - `export { w as q, x, y as w, z as default };`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Combine namespace re-export with property re-export", () => { + const sortedExports = parseExports( + `export * from "lib";`, + `export { y } from "lib";`); + const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = sortedExports; + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Combine many re-exports", () => { - const sortedExports = parseExports( - `export { x as a, y } from "lib";`, - `export * from "lib";`, - `export { z as b } from "lib";`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports( - `export * from "lib";`, - `export { x as a, y, z as b } from "lib";`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Combine many exports", () => { + const sortedExports = parseExports( + `export { x };`, + `export { y as w, z as default };`, + `export { w as q };`); + const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports( + `export { w as q, x, y as w, z as default };`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Keep type-only exports separate", () => { - const sortedExports = parseExports( - `export { x };`, - `export type { y };`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = sortedExports; - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Combine many re-exports", () => { + const sortedExports = parseExports( + `export { x as a, y } from "lib";`, + `export * from "lib";`, + `export { z as b } from "lib";`); + const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports( + `export * from "lib";`, + `export { x as a, y, z as b } from "lib";`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Combine type-only exports", () => { - const sortedExports = parseExports( - `export type { x };`, - `export type { y };`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports( - `export type { x, y };`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Keep type-only exports separate", () => { + const sortedExports = parseExports( + `export { x };`, + `export type { y };`); + const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = sortedExports; + assertListEqual(actualCoalescedExports, expectedCoalescedExports); }); + it("Combine type-only exports", () => { + const sortedExports = parseExports( + `export type { x };`, + `export type { y };`); + const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports( + `export type { x, y };`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); + }); - describe("Baselines", () => { - const libFile = { - path: "/lib.ts", - content: ` + describe("Baselines", () => { + + const libFile = { + path: "/lib.ts", + content: ` export function F1(); export default function F2(); `, - }; + }; - const reactLibFile = { - path: "/react.ts", - content: ` + const reactLibFile = { + path: "/react.ts", + content: ` export const React = { createElement: (_type, _props, _children) => {}, }; export const Other = 1; `, - }; + }; - // Don't bother to actually emit a baseline for this. - it("NoImports", () => { - const testFile = { - path: "/a.ts", - content: "function F() { }", - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); - }); + // Don't bother to actually emit a baseline for this. + it("NoImports", () => { + const testFile = { + path: "/a.ts", + content: "function F() { }", + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); + assert.isEmpty(changes); + }); - it("doesn't crash on shorthand ambient module", () => { - const testFile = { - path: "/a.ts", - content: "declare module '*';", - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); - }); + it("doesn't crash on shorthand ambient module", () => { + const testFile = { + path: "/a.ts", + content: "declare module '*';", + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); + assert.isEmpty(changes); + }); - it("doesn't return any changes when the text would be identical", () => { - const testFile = { - path: "/a.ts", - content: `import { f } from 'foo';\nf();` - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); - }); + it("doesn't return any changes when the text would be identical", () => { + const testFile = { + path: "/a.ts", + content: `import { f } from 'foo';\nf();` + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); + assert.isEmpty(changes); + }); - testOrganizeImports("Renamed_used", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("Renamed_used", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1 as EffOne, F2 as EffTwo } from "lib"; EffOne(); `, - }, - libFile); + }, + libFile); - testOrganizeImports("Simple", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("Simple", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; import * as NS from "lib"; import D from "lib"; @@ -370,29 +370,29 @@ D(); F1(); F2(); `, - }, - libFile); + }, + libFile); - testOrganizeImports("Unused_Some", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("Unused_Some", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; import * as NS from "lib"; import D from "lib"; D(); `, - }, - libFile); + }, + libFile); - describe("skipDestructiveCodeActions=true", () => { - testOrganizeImports("Syntax_Error_Body_skipDestructiveCodeActions", - /*skipDestructiveCodeActions*/ true, - { - path: "/test.ts", - content: ` + describe("skipDestructiveCodeActions=true", () => { + testOrganizeImports("Syntax_Error_Body_skipDestructiveCodeActions", + /*skipDestructiveCodeActions*/ true, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; import * as NS from "lib"; import D from "lib"; @@ -400,15 +400,15 @@ import D from "lib"; class class class; D; `, - }, - libFile); - }); - - testOrganizeImports("Syntax_Error_Imports_skipDestructiveCodeActions", - /*skipDestructiveCodeActions*/ true, - { - path: "/test.ts", - content: ` + }, + libFile); + }); + + testOrganizeImports("Syntax_Error_Imports_skipDestructiveCodeActions", + /*skipDestructiveCodeActions*/ true, + { + path: "/test.ts", + content: ` import { F1, F2 class class class; } from "lib"; import * as NS from "lib"; class class class; @@ -416,15 +416,15 @@ import D from "lib"; D; `, - }, - libFile); - - describe("skipDestructiveCodeActions=false", () => { - testOrganizeImports("Syntax_Error_Body", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + libFile); + + describe("skipDestructiveCodeActions=false", () => { + testOrganizeImports("Syntax_Error_Body", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; import * as NS from "lib"; import D from "lib"; @@ -432,14 +432,14 @@ import D from "lib"; class class class; D; `, - }, - libFile); - - testOrganizeImports("Syntax_Error_Imports", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + libFile); + + testOrganizeImports("Syntax_Error_Imports", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 class class class; } from "lib"; import * as NS from "lib"; class class class; @@ -447,49 +447,49 @@ import D from "lib"; D; `, - }, - libFile); - }); - - it("doesn't return any changes when the text would be identical", () => { - const testFile = { - path: "/a.ts", - content: `import { f } from 'foo';\nf();` - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); + }, + libFile); }); - testOrganizeImports("Unused_All", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + it("doesn't return any changes when the text would be identical", () => { + const testFile = { + path: "/a.ts", + content: `import { f } from 'foo';\nf();` + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); + assert.isEmpty(changes); + }); + + testOrganizeImports("Unused_All", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; import * as NS from "lib"; import D from "lib"; `, - }, - libFile); + }, + libFile); - it("Unused_Empty", () => { - const testFile = { - path: "/test.ts", - content: ` + it("Unused_Empty", () => { + const testFile = { + path: "/test.ts", + content: ` import { } from "lib"; `, - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); - }); + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); + assert.isEmpty(changes); + }); - testOrganizeImports("Unused_false_positive_module_augmentation", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.d.ts", - content: ` + testOrganizeImports("Unused_false_positive_module_augmentation", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.d.ts", + content: ` import foo from 'foo'; import { Caseless } from 'caseless'; @@ -499,13 +499,13 @@ declare module 'caseless' { test(name: KeyType): boolean; } }` - }); + }); - testOrganizeImports("Unused_preserve_imports_for_module_augmentation_in_non_declaration_file", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("Unused_preserve_imports_for_module_augmentation_in_non_declaration_file", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import foo from 'foo'; import { Caseless } from 'caseless'; @@ -515,39 +515,39 @@ declare module 'caseless' { test(name: KeyType): boolean; } }` - }); + }); - it("Unused_false_positive_shorthand_assignment", () => { - const testFile = { - path: "/test.ts", - content: ` + it("Unused_false_positive_shorthand_assignment", () => { + const testFile = { + path: "/test.ts", + content: ` import { x } from "a"; const o = { x }; ` - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); - }); + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); + assert.isEmpty(changes); + }); - it("Unused_false_positive_export_shorthand", () => { - const testFile = { - path: "/test.ts", - content: ` + it("Unused_false_positive_export_shorthand", () => { + const testFile = { + path: "/test.ts", + content: ` import { x } from "a"; export { x }; ` - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); - }); + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); + assert.isEmpty(changes); + }); - testOrganizeImports("MoveToTop", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("MoveToTop", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; F1(); F2(); @@ -556,15 +556,15 @@ NS.F1(); import D from "lib"; D(); `, - }, - libFile); - - /* eslint-disable no-template-curly-in-string */ - testOrganizeImports("MoveToTop_Invalid", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + libFile); + + /* eslint-disable no-template-curly-in-string */ + testOrganizeImports("MoveToTop_Invalid", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; F1(); F2(); @@ -575,115 +575,115 @@ import a from ${"`${'lib'}`"}; import D from "lib"; D(); `, - }, - libFile); - /* eslint-enable no-template-curly-in-string */ - - testOrganizeImports("TypeOnly", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + libFile); + /* eslint-enable no-template-curly-in-string */ + + testOrganizeImports("TypeOnly", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { X } from "lib"; import type Y from "lib"; import { Z } from "lib"; import type { A, B } from "lib"; export { A, B, X, Y, Z };` - }); + }); - testOrganizeImports("CoalesceMultipleModules", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("CoalesceMultipleModules", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { d } from "lib1"; import { b } from "lib1"; import { c } from "lib2"; import { a } from "lib2"; a + b + c + d; `, - }, - { path: "/lib1.ts", content: "export const b = 1, d = 2;" }, - { path: "/lib2.ts", content: "export const a = 3, c = 4;" }); - - testOrganizeImports("CoalesceTrivia", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + { path: "/lib1.ts", content: "export const b = 1, d = 2;" }, + { path: "/lib2.ts", content: "export const a = 3, c = 4;" }); + + testOrganizeImports("CoalesceTrivia", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` /*A*/import /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I /*J*/import /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R F1(); F2(); `, - }, - libFile); + }, + libFile); - testOrganizeImports("SortTrivia", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("SortTrivia", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` /*A*/import /*B*/ "lib2" /*C*/;/*D*/ //E /*F*/import /*G*/ "lib1" /*H*/;/*I*/ //J `, - }, - { path: "/lib1.ts", content: "" }, - { path: "/lib2.ts", content: "" }); - - testOrganizeImports("UnusedTrivia1", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + { path: "/lib1.ts", content: "" }, + { path: "/lib2.ts", content: "" }); + + testOrganizeImports("UnusedTrivia1", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` /*A*/import /*B*/ { /*C*/ F1 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I `, - }, - libFile); + }, + libFile); - testOrganizeImports("UnusedTrivia2", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("UnusedTrivia2", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` /*A*/import /*B*/ { /*C*/ F1 /*D*/, /*E*/ F2 /*F*/ } /*G*/ from /*H*/ "lib" /*I*/;/*J*/ //K F1(); `, - }, - libFile); + }, + libFile); - testOrganizeImports("UnusedHeaderComment", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("UnusedHeaderComment", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` // Header import { F1 } from "lib"; `, - }, - libFile); + }, + libFile); - testOrganizeImports("SortHeaderComment", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("SortHeaderComment", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` // Header import "lib2"; import "lib1"; `, - }, - { path: "/lib1.ts", content: "" }, - { path: "/lib2.ts", content: "" }); - - testOrganizeImports("SortComments", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + { path: "/lib1.ts", content: "" }, + { path: "/lib2.ts", content: "" }); + + testOrganizeImports("SortComments", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` // Header import "lib3"; // Comment2 @@ -691,16 +691,16 @@ import "lib2"; // Comment1 import "lib1"; `, - }, - { path: "/lib1.ts", content: "" }, - { path: "/lib2.ts", content: "" }, - { path: "/lib3.ts", content: "" }); - - testOrganizeImports("AmbientModule", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + { path: "/lib1.ts", content: "" }, + { path: "/lib2.ts", content: "" }, + { path: "/lib3.ts", content: "" }); + + testOrganizeImports("AmbientModule", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` declare module "mod" { import { F1 } from "lib"; import * as NS from "lib"; @@ -709,14 +709,14 @@ declare module "mod" { function F(f1: {} = F1, f2: {} = F2) {} } `, - }, - libFile); + }, + libFile); - testOrganizeImports("TopLevelAndAmbientModule", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("TopLevelAndAmbientModule", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import D from "lib"; declare module "mod" { @@ -732,168 +732,168 @@ import "lib"; D(); `, - }, - libFile); + }, + libFile); - testOrganizeImports("JsxFactoryUsedJsx", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.jsx", - content: ` + testOrganizeImports("JsxFactoryUsedJsx", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.jsx", + content: ` import { React, Other } from "react";
; `, - }, - reactLibFile); + }, + reactLibFile); - testOrganizeImports("JsxFactoryUsedJs", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.js", - content: ` + testOrganizeImports("JsxFactoryUsedJs", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.js", + content: ` import { React, Other } from "react";
; `, - }, - reactLibFile); + }, + reactLibFile); - testOrganizeImports("JsxFactoryUsedTsx", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.tsx", - content: ` + testOrganizeImports("JsxFactoryUsedTsx", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.tsx", + content: ` import { React, Other } from "react";
; `, - }, - reactLibFile); - - // TS files are not JSX contexts, so the parser does not treat - // `
` as a JSX element. - testOrganizeImports("JsxFactoryUsedTs", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + reactLibFile); + + // TS files are not JSX contexts, so the parser does not treat + // `
` as a JSX element. + testOrganizeImports("JsxFactoryUsedTs", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { React, Other } from "react";
; `, - }, - reactLibFile); + }, + reactLibFile); - testOrganizeImports("JsxFactoryUnusedJsx", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.jsx", - content: ` + testOrganizeImports("JsxFactoryUnusedJsx", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.jsx", + content: ` import { React, Other } from "react"; `, - }, - reactLibFile); - - // Note: Since the file extension does not end with "x", the jsx compiler option - // will not be enabled. The import should be retained regardless. - testOrganizeImports("JsxFactoryUnusedJs", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.js", - content: ` + }, + reactLibFile); + + // Note: Since the file extension does not end with "x", the jsx compiler option + // will not be enabled. The import should be retained regardless. + testOrganizeImports("JsxFactoryUnusedJs", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.js", + content: ` import { React, Other } from "react"; `, - }, - reactLibFile); + }, + reactLibFile); - testOrganizeImports("JsxFactoryUnusedTsx", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.tsx", - content: ` + testOrganizeImports("JsxFactoryUnusedTsx", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.tsx", + content: ` import { React, Other } from "react"; `, - }, - reactLibFile); + }, + reactLibFile); - testOrganizeImports("JsxFactoryUnusedTs", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("JsxFactoryUnusedTs", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { React, Other } from "react"; `, - }, - reactLibFile); + }, + reactLibFile); - testOrganizeImports("JsxPragmaTsx", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.tsx", - content: `/** @jsx jsx */ + testOrganizeImports("JsxPragmaTsx", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.tsx", + content: `/** @jsx jsx */ import { Global, jsx } from '@emotion/core'; import * as React from 'react'; export const App: React.FunctionComponent = _ =>

Hello!

`, - }, - { - path: "/@emotion/core/index.d.ts", - content: `import { createElement } from 'react' + }, + { + path: "/@emotion/core/index.d.ts", + content: `import { createElement } from 'react' export const jsx: typeof createElement; export function Global(props: any): ReactElement;` - }, - { - path: reactLibFile.path, - content: `${reactLibFile.content} + }, + { + path: reactLibFile.path, + content: `${reactLibFile.content} export namespace React { interface FunctionComponent { } } ` - } - ); + } + ); - testOrganizeImports("JsxFragmentPragmaTsx", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.tsx", - content: `/** @jsx h */ + testOrganizeImports("JsxFragmentPragmaTsx", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.tsx", + content: `/** @jsx h */ /** @jsxFrag frag */ import { h, frag } from "@foo/core"; const elem = <>
Foo
; `, - }, - { - path: "/@foo/core/index.d.ts", - content: `export function h(): void; + }, + { + path: "/@foo/core/index.d.ts", + content: `export function h(): void; export function frag(): void; ` - } - ); + } + ); - describe("Exports", () => { + describe("Exports", () => { - testOrganizeExports("MoveToTop", - { - path: "/test.ts", - content: ` + testOrganizeExports("MoveToTop", + { + path: "/test.ts", + content: ` export { F1, F2 } from "lib"; 1; export * from "lib"; 2; `, - }, - libFile); + }, + libFile); - /* eslint-disable no-template-curly-in-string */ - testOrganizeExports("MoveToTop_Invalid", - { - path: "/test.ts", - content: ` + /* eslint-disable no-template-curly-in-string */ + testOrganizeExports("MoveToTop_Invalid", + { + path: "/test.ts", + content: ` export { F1, F2 } from "lib"; 1; export * from "lib"; @@ -903,14 +903,14 @@ export { a } from ${"`${'lib'}`"}; export { D } from "lib"; 3; `, - }, - libFile); - /* eslint-enable no-template-curly-in-string */ + }, + libFile); + /* eslint-enable no-template-curly-in-string */ - testOrganizeExports("MoveToTop_WithImportsFirst", - { - path: "/test.ts", - content: ` + testOrganizeExports("MoveToTop_WithImportsFirst", + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; 1; export { F1, F2 } from "lib"; @@ -921,13 +921,13 @@ export * from "lib"; 4; F1(); F2(); NS.F1(); `, - }, - libFile); + }, + libFile); - testOrganizeExports("MoveToTop_WithExportsFirst", - { - path: "/test.ts", - content: ` + testOrganizeExports("MoveToTop_WithExportsFirst", + { + path: "/test.ts", + content: ` export { F1, F2 } from "lib"; 1; import { F1, F2 } from "lib"; @@ -938,72 +938,72 @@ import * as NS from "lib"; 4; F1(); F2(); NS.F1(); `, - }, - libFile); + }, + libFile); - testOrganizeExports("CoalesceMultipleModules", - { - path: "/test.ts", - content: ` + testOrganizeExports("CoalesceMultipleModules", + { + path: "/test.ts", + content: ` export { d } from "lib1"; export { b } from "lib1"; export { c } from "lib2"; export { a } from "lib2"; `, - }, - { path: "/lib1.ts", content: "export const b = 1, d = 2;" }, - { path: "/lib2.ts", content: "export const a = 3, c = 4;" }); + }, + { path: "/lib1.ts", content: "export const b = 1, d = 2;" }, + { path: "/lib2.ts", content: "export const a = 3, c = 4;" }); - testOrganizeExports("CoalesceTrivia", - { - path: "/test.ts", - content: ` + testOrganizeExports("CoalesceTrivia", + { + path: "/test.ts", + content: ` /*A*/export /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I /*J*/export /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R `, - }, - libFile); + }, + libFile); - testOrganizeExports("SortTrivia", - { - path: "/test.ts", - content: ` + testOrganizeExports("SortTrivia", + { + path: "/test.ts", + content: ` /*A*/export /*B*/ * /*C*/ from /*D*/ "lib2" /*E*/;/*F*/ //G /*H*/export /*I*/ * /*J*/ from /*K*/ "lib1" /*L*/;/*M*/ //N `, - }, - { path: "/lib1.ts", content: "" }, - { path: "/lib2.ts", content: "" }); + }, + { path: "/lib1.ts", content: "" }, + { path: "/lib2.ts", content: "" }); - testOrganizeExports("SortHeaderComment", - { - path: "/test.ts", - content: ` + testOrganizeExports("SortHeaderComment", + { + path: "/test.ts", + content: ` // Header export * from "lib2"; export * from "lib1"; `, - }, - { path: "/lib1.ts", content: "" }, - { path: "/lib2.ts", content: "" }); + }, + { path: "/lib1.ts", content: "" }, + { path: "/lib2.ts", content: "" }); - testOrganizeExports("AmbientModule", - { - path: "/test.ts", - content: ` + testOrganizeExports("AmbientModule", + { + path: "/test.ts", + content: ` declare module "mod" { export { F1 } from "lib"; export * from "lib"; export { F2 } from "lib"; } `, - }, - libFile); + }, + libFile); - testOrganizeExports("TopLevelAndAmbientModule", - { - path: "/test.ts", - content: ` + testOrganizeExports("TopLevelAndAmbientModule", + { + path: "/test.ts", + content: ` export { D } from "lib"; declare module "mod" { @@ -1015,143 +1015,143 @@ declare module "mod" { export { E } from "lib"; export * from "lib"; `, - }, - libFile); - }); - - function testOrganizeExports(testName: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { - testOrganizeImports(`${testName}.exports`, /*skipDestructiveCodeActions*/ true, testFile, ...otherFiles); - } - - function testOrganizeImports(testName: string, skipDestructiveCodeActions: boolean, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { - it(testName, () => runBaseline(`organizeImports/${testName}.ts`, skipDestructiveCodeActions, testFile, ...otherFiles)); - } + }, + libFile); + }); - function runBaseline(baselinePath: string, skipDestructiveCodeActions: boolean, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { - const { path: testPath, content: testContent } = testFile; - const languageService = makeLanguageService(testFile, ...otherFiles); - const changes = languageService.organizeImports({ skipDestructiveCodeActions, type: "file", fileName: testPath }, testFormatSettings, emptyOptions); - assert.equal(changes.length, 1); - assert.equal(changes[0].fileName, testPath); - - const newText = textChanges.applyChanges(testContent, changes[0].textChanges); - Harness.Baseline.runBaseline(baselinePath, [ - "// ==ORIGINAL==", - testContent, - "// ==ORGANIZED==", - newText, - ].join(newLineCharacter)); - } + function testOrganizeExports(testName: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { + testOrganizeImports(`${testName}.exports`, /*skipDestructiveCodeActions*/ true, testFile, ...otherFiles); + } - function makeLanguageService(...files: TestFSWithWatch.File[]) { - const host = projectSystem.createServerHost(files); - const projectService = projectSystem.createProjectService(host, { useSingleInferredProject: true }); - projectService.setCompilerOptionsForInferredProjects({ jsx: files.some(f => f.path.endsWith("x")) ? JsxEmit.React : JsxEmit.None }); - files.forEach(f => projectService.openClientFile(f.path)); - return projectService.inferredProjects[0].getLanguageService(); - } - }); + function testOrganizeImports(testName: string, skipDestructiveCodeActions: boolean, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { + it(testName, () => runBaseline(`organizeImports/${testName}.ts`, skipDestructiveCodeActions, testFile, ...otherFiles)); + } - function parseImports(...importStrings: string[]): readonly ImportDeclaration[] { - const sourceFile = createSourceFile("a.ts", importStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS); - const imports = filter(sourceFile.statements, isImportDeclaration); - assert.equal(imports.length, importStrings.length); - return imports; + function runBaseline(baselinePath: string, skipDestructiveCodeActions: boolean, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { + const { path: testPath, content: testContent } = testFile; + const languageService = makeLanguageService(testFile, ...otherFiles); + const changes = languageService.organizeImports({ skipDestructiveCodeActions, type: "file", fileName: testPath }, testFormatSettings, emptyOptions); + assert.equal(changes.length, 1); + assert.equal(changes[0].fileName, testPath); + + const newText = textChanges.applyChanges(testContent, changes[0].textChanges); + Harness.Baseline.runBaseline(baselinePath, [ + "// ==ORIGINAL==", + testContent, + "// ==ORGANIZED==", + newText, + ].join(newLineCharacter)); } - function parseExports(...exportStrings: string[]): readonly ExportDeclaration[] { - const sourceFile = createSourceFile("a.ts", exportStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS); - const exports = filter(sourceFile.statements, isExportDeclaration); - assert.equal(exports.length, exportStrings.length); - return exports; + function makeLanguageService(...files: TestFSWithWatch.File[]) { + const host = projectSystem.createServerHost(files); + const projectService = projectSystem.createProjectService(host, { useSingleInferredProject: true }); + projectService.setCompilerOptionsForInferredProjects({ jsx: files.some(f => f.path.endsWith("x")) ? JsxEmit.React : JsxEmit.None }); + files.forEach(f => projectService.openClientFile(f.path)); + return projectService.inferredProjects[0].getLanguageService(); } + }); - function assertEqual(node1?: Node, node2?: Node) { - if (node1 === undefined) { - assert.isUndefined(node2); - return; - } - else if (node2 === undefined) { - assert.isUndefined(node1); // Guaranteed to fail - return; - } + function parseImports(...importStrings: string[]): readonly ImportDeclaration[] { + const sourceFile = createSourceFile("a.ts", importStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS); + const imports = filter(sourceFile.statements, isImportDeclaration); + assert.equal(imports.length, importStrings.length); + return imports; + } - assert.equal(node1.kind, node2.kind); - - switch (node1.kind) { - case SyntaxKind.ImportDeclaration: - const decl1 = node1 as ImportDeclaration; - const decl2 = node2 as ImportDeclaration; - assertEqual(decl1.importClause, decl2.importClause); - assertEqual(decl1.moduleSpecifier, decl2.moduleSpecifier); - break; - case SyntaxKind.ImportClause: - const clause1 = node1 as ImportClause; - const clause2 = node2 as ImportClause; - assertEqual(clause1.name, clause2.name); - assertEqual(clause1.namedBindings, clause2.namedBindings); - break; - case SyntaxKind.NamespaceImport: - const nsi1 = node1 as NamespaceImport; - const nsi2 = node2 as NamespaceImport; - assertEqual(nsi1.name, nsi2.name); - break; - case SyntaxKind.NamedImports: - const ni1 = node1 as NamedImports; - const ni2 = node2 as NamedImports; - assertListEqual(ni1.elements, ni2.elements); - break; - case SyntaxKind.ImportSpecifier: - const is1 = node1 as ImportSpecifier; - const is2 = node2 as ImportSpecifier; - assertEqual(is1.name, is2.name); - assertEqual(is1.propertyName, is2.propertyName); - break; - case SyntaxKind.ExportDeclaration: - const ed1 = node1 as ExportDeclaration; - const ed2 = node2 as ExportDeclaration; - assertEqual(ed1.exportClause, ed2.exportClause); - assertEqual(ed1.moduleSpecifier, ed2.moduleSpecifier); - break; - case SyntaxKind.NamedExports: - const ne1 = node1 as NamedExports; - const ne2 = node2 as NamedExports; - assertListEqual(ne1.elements, ne2.elements); - break; - case SyntaxKind.ExportSpecifier: - const es1 = node1 as ExportSpecifier; - const es2 = node2 as ExportSpecifier; - assertEqual(es1.name, es2.name); - assertEqual(es1.propertyName, es2.propertyName); - break; - case SyntaxKind.Identifier: - const id1 = node1 as Identifier; - const id2 = node2 as Identifier; - assert.equal(id1.text, id2.text); - break; - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - const sl1 = node1 as LiteralLikeNode; - const sl2 = node2 as LiteralLikeNode; - assert.equal(sl1.text, sl2.text); - break; - default: - assert.equal(node1.getText(), node2.getText()); - break; - } + function parseExports(...exportStrings: string[]): readonly ExportDeclaration[] { + const sourceFile = createSourceFile("a.ts", exportStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS); + const exports = filter(sourceFile.statements, isExportDeclaration); + assert.equal(exports.length, exportStrings.length); + return exports; + } + + function assertEqual(node1?: Node, node2?: Node) { + if (node1 === undefined) { + assert.isUndefined(node2); + return; + } + else if (node2 === undefined) { + assert.isUndefined(node1); // Guaranteed to fail + return; } - function assertListEqual(list1: readonly Node[], list2: readonly Node[]) { - if (list1 === undefined || list2 === undefined) { - assert.isUndefined(list1); - assert.isUndefined(list2); - return; - } + assert.equal(node1.kind, node2.kind); + + switch (node1.kind) { + case SyntaxKind.ImportDeclaration: + const decl1 = node1 as ImportDeclaration; + const decl2 = node2 as ImportDeclaration; + assertEqual(decl1.importClause, decl2.importClause); + assertEqual(decl1.moduleSpecifier, decl2.moduleSpecifier); + break; + case SyntaxKind.ImportClause: + const clause1 = node1 as ImportClause; + const clause2 = node2 as ImportClause; + assertEqual(clause1.name, clause2.name); + assertEqual(clause1.namedBindings, clause2.namedBindings); + break; + case SyntaxKind.NamespaceImport: + const nsi1 = node1 as NamespaceImport; + const nsi2 = node2 as NamespaceImport; + assertEqual(nsi1.name, nsi2.name); + break; + case SyntaxKind.NamedImports: + const ni1 = node1 as NamedImports; + const ni2 = node2 as NamedImports; + assertListEqual(ni1.elements, ni2.elements); + break; + case SyntaxKind.ImportSpecifier: + const is1 = node1 as ImportSpecifier; + const is2 = node2 as ImportSpecifier; + assertEqual(is1.name, is2.name); + assertEqual(is1.propertyName, is2.propertyName); + break; + case SyntaxKind.ExportDeclaration: + const ed1 = node1 as ExportDeclaration; + const ed2 = node2 as ExportDeclaration; + assertEqual(ed1.exportClause, ed2.exportClause); + assertEqual(ed1.moduleSpecifier, ed2.moduleSpecifier); + break; + case SyntaxKind.NamedExports: + const ne1 = node1 as NamedExports; + const ne2 = node2 as NamedExports; + assertListEqual(ne1.elements, ne2.elements); + break; + case SyntaxKind.ExportSpecifier: + const es1 = node1 as ExportSpecifier; + const es2 = node2 as ExportSpecifier; + assertEqual(es1.name, es2.name); + assertEqual(es1.propertyName, es2.propertyName); + break; + case SyntaxKind.Identifier: + const id1 = node1 as Identifier; + const id2 = node2 as Identifier; + assert.equal(id1.text, id2.text); + break; + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + const sl1 = node1 as LiteralLikeNode; + const sl2 = node2 as LiteralLikeNode; + assert.equal(sl1.text, sl2.text); + break; + default: + assert.equal(node1.getText(), node2.getText()); + break; + } + } - assert.equal(list1.length, list2.length); - for (let i = 0; i < list1.length; i++) { - assertEqual(list1[i], list2[i]); - } + function assertListEqual(list1: readonly Node[], list2: readonly Node[]) { + if (list1 === undefined || list2 === undefined) { + assert.isUndefined(list1); + assert.isUndefined(list2); + return; } - }); + + assert.equal(list1.length, list2.length); + for (let i = 0; i < list1.length; i++) { + assertEqual(list1[i], list2[i]); + } + } +}); } diff --git a/src/testRunner/unittests/services/textChanges.ts b/src/testRunner/unittests/services/textChanges.ts index 534d57ebf8f08..e745df385caa6 100644 --- a/src/testRunner/unittests/services/textChanges.ts +++ b/src/testRunner/unittests/services/textChanges.ts @@ -1,65 +1,65 @@ // Some tests have trailing whitespace namespace ts { - describe("unittests:: services:: textChanges", () => { - function findChild(name: string, n: Node) { - return find(n)!; +describe("unittests:: services:: textChanges", () => { + function findChild(name: string, n: Node) { + return find(n)!; - function find(node: Node): Node | undefined { - if (isDeclaration(node) && node.name && isIdentifier(node.name) && node.name.escapedText === name) { - return node; - } - else { - return forEachChild(node, find); - } + function find(node: Node): Node | undefined { + if (isDeclaration(node) && node.name && isIdentifier(node.name) && node.name.escapedText === name) { + return node; + } + else { + return forEachChild(node, find); } } + } - const printerOptions = { newLine: NewLineKind.LineFeed }; - const newLineCharacter = getNewLineCharacter(printerOptions); + const printerOptions = { newLine: NewLineKind.LineFeed }; + const newLineCharacter = getNewLineCharacter(printerOptions); - function getRuleProvider(placeOpenBraceOnNewLineForFunctions: boolean): formatting.FormatContext { - return formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...testFormatSettings, placeOpenBraceOnNewLineForFunctions: true } : testFormatSettings, notImplementedHost); - } + function getRuleProvider(placeOpenBraceOnNewLineForFunctions: boolean): formatting.FormatContext { + return formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...testFormatSettings, placeOpenBraceOnNewLineForFunctions: true } : testFormatSettings, notImplementedHost); + } - // validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed. - function verifyPositions(node: Node, text: string): void { - const nodeList = flattenNodes(node); - const sourceFile = createSourceFile("f.ts", text, ScriptTarget.ES2015); - const parsedNodeList = flattenNodes(sourceFile.statements[0]); - zipWith(nodeList, parsedNodeList, (left, right) => { - Debug.assert(left.pos === right.pos); - Debug.assert(left.end === right.end); - }); + // validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed. + function verifyPositions(node: Node, text: string): void { + const nodeList = flattenNodes(node); + const sourceFile = createSourceFile("f.ts", text, ScriptTarget.ES2015); + const parsedNodeList = flattenNodes(sourceFile.statements[0]); + zipWith(nodeList, parsedNodeList, (left, right) => { + Debug.assert(left.pos === right.pos); + Debug.assert(left.end === right.end); + }); - function flattenNodes(n: Node) { - const data: (Node | NodeArray)[] = []; - walk(n); - return data; + function flattenNodes(n: Node) { + const data: (Node | NodeArray)[] = []; + walk(n); + return data; - function walk(n: Node | NodeArray): void { - data.push(n); - return isArray(n) ? forEach(n, walk) : forEachChild(n, walk, walk); - } + function walk(n: Node | NodeArray): void { + data.push(n); + return isArray(n) ? forEach(n, walk) : forEachChild(n, walk, walk); } } + } - function runSingleFileTest(caption: string, placeOpenBraceOnNewLineForFunctions: boolean, text: string, validateNodes: boolean, testBlock: (sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker) => void) { - it(caption, () => { - const sourceFile = createSourceFile("source.ts", text, ScriptTarget.ES2015, /*setParentNodes*/ true); - const rulesProvider = getRuleProvider(placeOpenBraceOnNewLineForFunctions); - const changeTracker = new textChanges.ChangeTracker(newLineCharacter, rulesProvider); - testBlock(sourceFile, changeTracker); - const changes = changeTracker.getChanges(validateNodes ? verifyPositions : undefined); - assert.equal(changes.length, 1); - assert.equal(changes[0].fileName, sourceFile.fileName); - const modified = textChanges.applyChanges(sourceFile.text, changes[0].textChanges); - Harness.Baseline.runBaseline(`textChanges/${caption}.js`, `===ORIGINAL===${newLineCharacter}${text}${newLineCharacter}===MODIFIED===${newLineCharacter}${modified}`); - }); - } + function runSingleFileTest(caption: string, placeOpenBraceOnNewLineForFunctions: boolean, text: string, validateNodes: boolean, testBlock: (sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker) => void) { + it(caption, () => { + const sourceFile = createSourceFile("source.ts", text, ScriptTarget.ES2015, /*setParentNodes*/ true); + const rulesProvider = getRuleProvider(placeOpenBraceOnNewLineForFunctions); + const changeTracker = new textChanges.ChangeTracker(newLineCharacter, rulesProvider); + testBlock(sourceFile, changeTracker); + const changes = changeTracker.getChanges(validateNodes ? verifyPositions : undefined); + assert.equal(changes.length, 1); + assert.equal(changes[0].fileName, sourceFile.fileName); + const modified = textChanges.applyChanges(sourceFile.text, changes[0].textChanges); + Harness.Baseline.runBaseline(`textChanges/${caption}.js`, `===ORIGINAL===${newLineCharacter}${text}${newLineCharacter}===MODIFIED===${newLineCharacter}${modified}`); + }); + } - { - const text = ` + { + const text = ` namespace M { namespace M2 @@ -80,33 +80,33 @@ namespace M } } }`; - runSingleFileTest("extractMethodLike", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - const statements = (findChild("foo", sourceFile) as FunctionDeclaration).body!.statements.slice(1); - const newFunction = factory.createFunctionDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - /*name*/ "bar", - /*typeParameters*/ undefined, - /*parameters*/ emptyArray, - /*type*/ factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), - /*body */ factory.createBlock(statements) - ); + runSingleFileTest("extractMethodLike", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + const statements = (findChild("foo", sourceFile) as FunctionDeclaration).body!.statements.slice(1); + const newFunction = factory.createFunctionDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + /*name*/ "bar", + /*typeParameters*/ undefined, + /*parameters*/ emptyArray, + /*type*/ factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), + /*body */ factory.createBlock(statements) + ); - changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction); + changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction); - // replace statements with return statement - const newStatement = factory.createReturnStatement( - factory.createCallExpression( - /*expression*/ newFunction.name!, - /*typeArguments*/ undefined, - /*argumentsArray*/ emptyArray - )); - changeTracker.replaceNodeRange(sourceFile, statements[0], last(statements), newStatement, { suffix: newLineCharacter }); - }); - } - { - const text = ` + // replace statements with return statement + const newStatement = factory.createReturnStatement( + factory.createCallExpression( + /*expression*/ newFunction.name!, + /*typeArguments*/ undefined, + /*argumentsArray*/ emptyArray + )); + changeTracker.replaceNodeRange(sourceFile, statements[0], last(statements), newStatement, { suffix: newLineCharacter }); + }); + } + { + const text = ` function foo() { return 1; } @@ -115,19 +115,19 @@ function bar() { return 2; } `; - runSingleFileTest("deleteRange1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.deleteRange(sourceFile, { pos: text.indexOf("function foo"), end: text.indexOf("function bar") }); - }); - } - function findVariableStatementContaining(name: string, sourceFile: SourceFile): VariableStatement { - return cast(findVariableDeclarationContaining(name, sourceFile).parent.parent, isVariableStatement); - } - function findVariableDeclarationContaining(name: string, sourceFile: SourceFile): VariableDeclaration { - return cast(findChild(name, sourceFile), isVariableDeclaration); - } - const { deleteNode } = textChanges; - { - const text = ` + runSingleFileTest("deleteRange1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteRange(sourceFile, { pos: text.indexOf("function foo"), end: text.indexOf("function bar") }); + }); + } + function findVariableStatementContaining(name: string, sourceFile: SourceFile): VariableStatement { + return cast(findVariableDeclarationContaining(name, sourceFile).parent.parent, isVariableStatement); + } + function findVariableDeclarationContaining(name: string, sourceFile: SourceFile): VariableDeclaration { + return cast(findChild(name, sourceFile), isVariableDeclaration); + } + const { deleteNode } = textChanges; + { + const text = ` var x = 1; // some comment - 1 /** * comment 2 @@ -135,24 +135,24 @@ var x = 1; // some comment - 1 var y = 2; // comment 3 var z = 3; // comment 4 `; - runSingleFileTest("deleteNode1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile)); - }); - runSingleFileTest("deleteNode2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude }); - }); - runSingleFileTest("deleteNode3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - runSingleFileTest("deleteNode4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - runSingleFileTest("deleteNode5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("x", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNode1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile)); + }); + runSingleFileTest("deleteNode2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude }); + }); + runSingleFileTest("deleteNode3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); + }); + runSingleFileTest("deleteNode4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); + }); + runSingleFileTest("deleteNode5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("x", sourceFile)); + }); + } + { + const text = ` // comment 1 var x = 1; // comment 2 // comment 3 @@ -161,55 +161,55 @@ var z = 3; // comment 5 // comment 6 var a = 4; // comment 7 `; - runSingleFileTest("deleteNodeRange1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile)); - }); - runSingleFileTest("deleteNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), - { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude }); - }); - runSingleFileTest("deleteNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), - { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - runSingleFileTest("deleteNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), - { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - } - function createTestVariableDeclaration(name: string) { - return factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, /*type*/ undefined, factory.createObjectLiteralExpression([factory.createPropertyAssignment("p1", factory.createNumericLiteral(1))], /*multiline*/ true)); - } - function createTestClass() { - return factory.createClassDeclaration( - /*decorators*/ undefined, - [ - factory.createToken(SyntaxKind.PublicKeyword) - ], - "class1", - /*typeParameters*/ undefined, - [ - factory.createHeritageClause( - SyntaxKind.ImplementsKeyword, - [ - factory.createExpressionWithTypeArguments(factory.createIdentifier("interface1"), /*typeArguments*/ undefined) - ] - ) - ], - [ - factory.createPropertyDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - "property1", - /*questionToken*/ undefined, - factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword), - /*initializer*/ undefined - ) - ] - ); - } - { - const text = ` + runSingleFileTest("deleteNodeRange1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile)); + }); + runSingleFileTest("deleteNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), + { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude }); + }); + runSingleFileTest("deleteNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), + { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); + }); + runSingleFileTest("deleteNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), + { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); + }); + } + function createTestVariableDeclaration(name: string) { + return factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, /*type*/ undefined, factory.createObjectLiteralExpression([factory.createPropertyAssignment("p1", factory.createNumericLiteral(1))], /*multiline*/ true)); + } + function createTestClass() { + return factory.createClassDeclaration( + /*decorators*/ undefined, + [ + factory.createToken(SyntaxKind.PublicKeyword) + ], + "class1", + /*typeParameters*/ undefined, + [ + factory.createHeritageClause( + SyntaxKind.ImplementsKeyword, + [ + factory.createExpressionWithTypeArguments(factory.createIdentifier("interface1"), /*typeArguments*/ undefined) + ] + ) + ], + [ + factory.createPropertyDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + "property1", + /*questionToken*/ undefined, + factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword), + /*initializer*/ undefined + ) + ] + ); + } + { + const text = ` // comment 1 var x = 1; // comment 2 // comment 3 @@ -217,31 +217,31 @@ var y = 2; // comment 4 var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; - runSingleFileTest("replaceRange", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter }); - }); - runSingleFileTest("replaceRangeWithForcedIndentation", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter, indentation: 8, delta: 0 }); - }); + runSingleFileTest("replaceRange", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter }); + }); + runSingleFileTest("replaceRangeWithForcedIndentation", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter, indentation: 8, delta: 0 }); + }); - runSingleFileTest("replaceRangeNoLineBreakBefore", /*placeOpenBraceOnNewLineForFunctions*/ true, `const x = 1, y = "2";`, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = createTestVariableDeclaration("z1"); - changeTracker.replaceRange(sourceFile, { pos: sourceFile.text.indexOf("y"), end: sourceFile.text.indexOf(";") }, newNode); - }); - } - { - const text = ` + runSingleFileTest("replaceRangeNoLineBreakBefore", /*placeOpenBraceOnNewLineForFunctions*/ true, `const x = 1, y = "2";`, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = createTestVariableDeclaration("z1"); + changeTracker.replaceRange(sourceFile, { pos: sourceFile.text.indexOf("y"), end: sourceFile.text.indexOf(";") }, newNode); + }); + } + { + const text = ` namespace A { const x = 1, y = "2"; } `; - runSingleFileTest("replaceNode1NoLineBreakBefore", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = createTestVariableDeclaration("z1"); - changeTracker.replaceNode(sourceFile, findChild("y", sourceFile), newNode); - }); - } - { - const text = ` + runSingleFileTest("replaceNode1NoLineBreakBefore", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = createTestVariableDeclaration("z1"); + changeTracker.replaceNode(sourceFile, findChild("y", sourceFile), newNode); + }); + } + { + const text = ` // comment 1 var x = 1; // comment 2 // comment 3 @@ -249,24 +249,24 @@ var y = 2; // comment 4 var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; - runSingleFileTest("replaceNode1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); - }); - runSingleFileTest("replaceNode2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, suffix: newLineCharacter, prefix: newLineCharacter }); - }); - runSingleFileTest("replaceNode3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude, suffix: newLineCharacter }); - }); - runSingleFileTest("replaceNode4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - runSingleFileTest("replaceNode5", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("x", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - } - { - const text = ` + runSingleFileTest("replaceNode1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); + }); + runSingleFileTest("replaceNode2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, suffix: newLineCharacter, prefix: newLineCharacter }); + }); + runSingleFileTest("replaceNode3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude, suffix: newLineCharacter }); + }); + runSingleFileTest("replaceNode4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); + }); + runSingleFileTest("replaceNode5", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("x", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); + }); + } + { + const text = ` // comment 1 var x = 1; // comment 2 // comment 3 @@ -274,21 +274,21 @@ var y = 2; // comment 4 var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; - runSingleFileTest("replaceNodeRange1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { suffix: newLineCharacter }); - }); - runSingleFileTest("replaceNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, suffix: newLineCharacter, prefix: newLineCharacter }); - }); - runSingleFileTest("replaceNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude, suffix: newLineCharacter }); - }); - runSingleFileTest("replaceNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - } - { - const text = ` + runSingleFileTest("replaceNodeRange1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { suffix: newLineCharacter }); + }); + runSingleFileTest("replaceNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, suffix: newLineCharacter, prefix: newLineCharacter }); + }); + runSingleFileTest("replaceNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude, suffix: newLineCharacter }); + }); + runSingleFileTest("replaceNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); + }); + } + { + const text = ` // comment 1 var x = 1; // comment 2 // comment 3 @@ -296,15 +296,15 @@ var y; // comment 4 var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; - runSingleFileTest("insertNodeBefore3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); - }); - runSingleFileTest("insertNodeAfterVariableDeclaration", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findVariableDeclarationContaining("y", sourceFile), createTestVariableDeclaration("z1")); - }); - } - { - const text = ` + runSingleFileTest("insertNodeBefore3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); + }); + runSingleFileTest("insertNodeAfterVariableDeclaration", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findVariableDeclarationContaining("y", sourceFile), createTestVariableDeclaration("z1")); + }); + } + { + const text = ` namespace M { // comment 1 var x = 1; // comment 2 @@ -314,107 +314,107 @@ namespace M { // comment 6 var a = 4; // comment 7 }`; - runSingleFileTest("insertNodeBefore1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); - }); - runSingleFileTest("insertNodeBefore2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass()); - }); - runSingleFileTest("insertNodeAfter1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); - }); - runSingleFileTest("insertNodeAfter2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass()); - }); - } + runSingleFileTest("insertNodeBefore1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); + }); + runSingleFileTest("insertNodeBefore2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass()); + }); + runSingleFileTest("insertNodeAfter1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); + }); + runSingleFileTest("insertNodeAfter2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass()); + }); + } - function findConstructor(sourceFile: SourceFile): ConstructorDeclaration { - const classDecl = sourceFile.statements[0] as ClassDeclaration; - return find(classDecl.members, (m): m is ConstructorDeclaration => isConstructorDeclaration(m) && !!m.body)!; - } - function createTestSuperCall() { - const superCall = factory.createCallExpression( - factory.createSuper(), - /*typeArguments*/ undefined, - /*argumentsArray*/ emptyArray - ); - return factory.createExpressionStatement(superCall); - } + function findConstructor(sourceFile: SourceFile): ConstructorDeclaration { + const classDecl = sourceFile.statements[0] as ClassDeclaration; + return find(classDecl.members, (m): m is ConstructorDeclaration => isConstructorDeclaration(m) && !!m.body)!; + } + function createTestSuperCall() { + const superCall = factory.createCallExpression( + factory.createSuper(), + /*typeArguments*/ undefined, + /*argumentsArray*/ emptyArray + ); + return factory.createExpressionStatement(superCall); + } - { - const text1 = ` + { + const text1 = ` class A { constructor() { } } `; - runSingleFileTest("insertNodeAtConstructorStart", /*placeOpenBraceOnNewLineForFunctions*/ false, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAtConstructorStart(sourceFile, findConstructor(sourceFile), createTestSuperCall()); - }); - const text2 = ` + runSingleFileTest("insertNodeAtConstructorStart", /*placeOpenBraceOnNewLineForFunctions*/ false, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAtConstructorStart(sourceFile, findConstructor(sourceFile), createTestSuperCall()); + }); + const text2 = ` class A { constructor() { var x = 1; } } `; - runSingleFileTest("insertNodeAfter4", /*placeOpenBraceOnNewLineForFunctions*/ false, text2, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall()); - }); - const text3 = ` + runSingleFileTest("insertNodeAfter4", /*placeOpenBraceOnNewLineForFunctions*/ false, text2, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall()); + }); + const text3 = ` class A { constructor() { } } `; - runSingleFileTest("insertNodeAtConstructorStart-block with newline", /*placeOpenBraceOnNewLineForFunctions*/ false, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAtConstructorStart(sourceFile, findConstructor(sourceFile), createTestSuperCall()); - }); - } - { - const text = `var a = 1, b = 2, c = 3;`; - runSingleFileTest("deleteNodeInList1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = `var a = 1,b = 2,c = 3;`; - runSingleFileTest("deleteNodeInList1_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList2_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList3_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("insertNodeAtConstructorStart-block with newline", /*placeOpenBraceOnNewLineForFunctions*/ false, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAtConstructorStart(sourceFile, findConstructor(sourceFile), createTestSuperCall()); + }); + } + { + const text = `var a = 1, b = 2, c = 3;`; + runSingleFileTest("deleteNodeInList1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = `var a = 1,b = 2,c = 3;`; + runSingleFileTest("deleteNodeInList1_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList2_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList3_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` namespace M { var a = 1, b = 2, c = 3; }`; - runSingleFileTest("deleteNodeInList4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList6", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeInList4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList6", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` namespace M { var a = 1, // comment 1 // comment 2 @@ -422,356 +422,356 @@ namespace M { // comment 4 c = 3; // comment 5 }`; - runSingleFileTest("deleteNodeInList4_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList5_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList6_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeInList4_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList5_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList6_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` function foo(a: number, b: string, c = true) { return 1; }`; - runSingleFileTest("deleteNodeInList7", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList8", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList9", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeInList7", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList8", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList9", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` function foo(a: number,b: string,c = true) { return 1; }`; - runSingleFileTest("deleteNodeInList10", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList11", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList12", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeInList10", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList11", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList12", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` function foo( a: number, b: string, c = true) { return 1; }`; - runSingleFileTest("deleteNodeInList13", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList14", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList15", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeInList13", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList14", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList15", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` const x = 1, y = 2;`; - runSingleFileTest("insertNodeInListAfter1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - runSingleFileTest("insertNodeInListAfter2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); + }); + } + { + const text = ` const /*x*/ x = 1, /*y*/ y = 2;`; - runSingleFileTest("insertNodeInListAfter3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - runSingleFileTest("insertNodeInListAfter4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); + }); + } + { + const text = ` const x = 1;`; - runSingleFileTest("insertNodeInListAfter5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); + }); + } + { + const text = ` const x = 1, y = 2;`; - runSingleFileTest("insertNodeInListAfter6", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - runSingleFileTest("insertNodeInListAfter7", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter6", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter7", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); + }); + } + { + const text = ` const /*x*/ x = 1, /*y*/ y = 2;`; - runSingleFileTest("insertNodeInListAfter8", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - runSingleFileTest("insertNodeInListAfter9", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter8", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter9", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); + }); + } + { + const text = ` import { x } from "bar"`; - runSingleFileTest("insertNodeInListAfter10", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter10", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x // this is x } from "bar"`; - runSingleFileTest("insertNodeInListAfter11", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter11", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x } from "bar"`; - runSingleFileTest("insertNodeInListAfter12", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - // eslint-disable-next-line boolean-trivia - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter12", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line boolean-trivia + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x // this is x } from "bar"`; - runSingleFileTest("insertNodeInListAfter13", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - // eslint-disable-next-line boolean-trivia - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter13", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line boolean-trivia + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x0, x } from "bar"`; - runSingleFileTest("insertNodeInListAfter14", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter14", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x0, x // this is x } from "bar"`; - runSingleFileTest("insertNodeInListAfter15", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter15", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x0, x } from "bar"`; - runSingleFileTest("insertNodeInListAfter16", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - // eslint-disable-next-line boolean-trivia - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter16", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line boolean-trivia + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x0, x // this is x } from "bar"`; - runSingleFileTest("insertNodeInListAfter17", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - // eslint-disable-next-line boolean-trivia - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter17", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line boolean-trivia + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x0, x } from "bar"`; - runSingleFileTest("insertNodeInListAfter18", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + runSingleFileTest("insertNodeInListAfter18", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line boolean-trivia + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); + }); + } + { + const runTest = (name: string, text: string) => runSingleFileTest(name, /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + for (const specifier of ["x3", "x4", "x5"]) { // eslint-disable-next-line boolean-trivia - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); - }); - } - { - const runTest = (name: string, text: string) => runSingleFileTest(name, /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - for (const specifier of ["x3", "x4", "x5"]) { - // eslint-disable-next-line boolean-trivia - changeTracker.insertNodeInListAfter(sourceFile, findChild("x2", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier(specifier))); - } - }); + changeTracker.insertNodeInListAfter(sourceFile, findChild("x2", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier(specifier))); + } + }); - const crlfText = "import {\r\nx1,\r\nx2\r\n} from \"bar\";"; - runTest("insertNodeInListAfter19", crlfText); + const crlfText = "import {\r\nx1,\r\nx2\r\n} from \"bar\";"; + runTest("insertNodeInListAfter19", crlfText); - const lfText = "import {\nx1,\nx2\n} from \"bar\";"; - runTest("insertNodeInListAfter20", lfText); - } - { - const text = ` + const lfText = "import {\nx1,\nx2\n} from \"bar\";"; + runTest("insertNodeInListAfter20", lfText); + } + { + const text = ` class A { x; }`; - runSingleFileTest("insertNodeAfterMultipleNodes", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNodes = []; - for (let i = 0; i < 11 /*error doesn't occur with fewer nodes*/; ++i) { - newNodes.push( - // eslint-disable-next-line boolean-trivia - factory.createPropertyDeclaration(undefined, undefined, i + "", undefined, undefined, undefined)); - } - const insertAfter = findChild("x", sourceFile); - for (const newNode of newNodes) { - changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode); - } - }); - } - { - const text = ` + runSingleFileTest("insertNodeAfterMultipleNodes", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNodes = []; + for (let i = 0; i < 11 /*error doesn't occur with fewer nodes*/; ++i) { + newNodes.push( + // eslint-disable-next-line boolean-trivia + factory.createPropertyDeclaration(undefined, undefined, i + "", undefined, undefined, undefined)); + } + const insertAfter = findChild("x", sourceFile); + for (const newNode of newNodes) { + changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode); + } + }); + } + { + const text = ` class A { x } `; - runSingleFileTest("insertNodeAfterInClass1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - // eslint-disable-next-line boolean-trivia - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), factory.createPropertyDeclaration(undefined, undefined, "a", undefined, factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined)); - }); - } - { - const text = ` + runSingleFileTest("insertNodeAfterInClass1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line boolean-trivia + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), factory.createPropertyDeclaration(undefined, undefined, "a", undefined, factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined)); + }); + } + { + const text = ` class A { x; } `; - runSingleFileTest("insertNodeAfterInClass2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - // eslint-disable-next-line boolean-trivia - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), factory.createPropertyDeclaration(undefined, undefined, "a", undefined, factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined)); - }); - } - { - const text = ` + runSingleFileTest("insertNodeAfterInClass2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line boolean-trivia + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), factory.createPropertyDeclaration(undefined, undefined, "a", undefined, factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined)); + }); + } + { + const text = ` class A { x; y = 1; } `; - runSingleFileTest("deleteNodeAfterInClass1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findChild("x", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeAfterInClass1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findChild("x", sourceFile)); + }); + } + { + const text = ` class A { x y = 1; } `; - runSingleFileTest("deleteNodeAfterInClass2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findChild("x", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeAfterInClass2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findChild("x", sourceFile)); + }); + } + { + const text = ` class A { x = foo } `; - runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = factory.createPropertyDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - factory.createComputedPropertyName(factory.createNumericLiteral(1)), - /*questionToken*/ undefined, - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), - /*initializer*/ undefined); - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = factory.createPropertyDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createComputedPropertyName(factory.createNumericLiteral(1)), + /*questionToken*/ undefined, + factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), + /*initializer*/ undefined); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); + }); + } + { + const text = ` class A { x() { } } `; - runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = factory.createPropertyDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - factory.createComputedPropertyName(factory.createNumericLiteral(1)), - /*questionToken*/ undefined, - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), - /*initializer*/ undefined); - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = factory.createPropertyDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createComputedPropertyName(factory.createNumericLiteral(1)), + /*questionToken*/ undefined, + factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), + /*initializer*/ undefined); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); + }); + } + { + const text = ` interface A { x } `; - runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = factory.createPropertyDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - factory.createComputedPropertyName(factory.createNumericLiteral(1)), - /*questionToken*/ undefined, - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), - /*initializer*/ undefined); - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = factory.createPropertyDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createComputedPropertyName(factory.createNumericLiteral(1)), + /*questionToken*/ undefined, + factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), + /*initializer*/ undefined); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); + }); + } + { + const text = ` interface A { x() } `; - runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = factory.createPropertyDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - factory.createComputedPropertyName(factory.createNumericLiteral(1)), - /*questionToken*/ undefined, - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), - /*initializer*/ undefined); - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = factory.createPropertyDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createComputedPropertyName(factory.createNumericLiteral(1)), + /*questionToken*/ undefined, + factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), + /*initializer*/ undefined); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); + }); + } + { + const text = ` let x = foo `; - runSingleFileTest("insertNodeInStatementListAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = factory.createExpressionStatement(factory.createParenthesizedExpression(factory.createNumericLiteral(1))); - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), newNode); - }); - } - }); + runSingleFileTest("insertNodeInStatementListAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = factory.createExpressionStatement(factory.createParenthesizedExpression(factory.createNumericLiteral(1))); + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), newNode); + }); + } +}); } diff --git a/src/testRunner/unittests/services/transpile.ts b/src/testRunner/unittests/services/transpile.ts index 2e0c8ea24547a..11d6b822d4e76 100644 --- a/src/testRunner/unittests/services/transpile.ts +++ b/src/testRunner/unittests/services/transpile.ts @@ -1,489 +1,489 @@ namespace ts { - describe("unittests:: services:: Transpile", () => { - - interface TranspileTestSettings { - options?: TranspileOptions; - noSetFileName?: boolean; - } - - function transpilesCorrectly(name: string, input: string, testSettings: TranspileTestSettings) { - describe(name, () => { - let transpileResult: TranspileOutput; - let oldTranspileResult: string; - let oldTranspileDiagnostics: Diagnostic[]; - - const transpileOptions: TranspileOptions = testSettings.options || {}; - if (!transpileOptions.compilerOptions) { - transpileOptions.compilerOptions = { }; - } - if (transpileOptions.compilerOptions.target === undefined) { - transpileOptions.compilerOptions.target = ScriptTarget.ES3; - } +describe("unittests:: services:: Transpile", () => { + + interface TranspileTestSettings { + options?: TranspileOptions; + noSetFileName?: boolean; + } + + function transpilesCorrectly(name: string, input: string, testSettings: TranspileTestSettings) { + describe(name, () => { + let transpileResult: TranspileOutput; + let oldTranspileResult: string; + let oldTranspileDiagnostics: Diagnostic[]; + + const transpileOptions: TranspileOptions = testSettings.options || {}; + if (!transpileOptions.compilerOptions) { + transpileOptions.compilerOptions = { }; + } + if (transpileOptions.compilerOptions.target === undefined) { + transpileOptions.compilerOptions.target = ScriptTarget.ES3; + } - if (transpileOptions.compilerOptions.newLine === undefined) { - // use \r\n as default new line - transpileOptions.compilerOptions.newLine = NewLineKind.CarriageReturnLineFeed; - } + if (transpileOptions.compilerOptions.newLine === undefined) { + // use \r\n as default new line + transpileOptions.compilerOptions.newLine = NewLineKind.CarriageReturnLineFeed; + } - transpileOptions.compilerOptions.sourceMap = true; + transpileOptions.compilerOptions.sourceMap = true; - let unitName = transpileOptions.fileName; - if (!unitName) { - unitName = transpileOptions.compilerOptions.jsx ? "file.tsx" : "file.ts"; - if (!testSettings.noSetFileName) { - transpileOptions.fileName = unitName; - } + let unitName = transpileOptions.fileName; + if (!unitName) { + unitName = transpileOptions.compilerOptions.jsx ? "file.tsx" : "file.ts"; + if (!testSettings.noSetFileName) { + transpileOptions.fileName = unitName; } + } - transpileOptions.reportDiagnostics = true; - - const justName = "transpile/" + name.replace(/[^a-z0-9\-. ]/ig, "") + (transpileOptions.compilerOptions.jsx ? Extension.Tsx : Extension.Ts); - const toBeCompiled = [{ - unitName, - content: input - }]; - const canUseOldTranspile = !transpileOptions.renamedDependencies; - - before(() => { - transpileResult = transpileModule(input, transpileOptions); - - if (canUseOldTranspile) { - oldTranspileDiagnostics = []; - oldTranspileResult = transpile(input, transpileOptions.compilerOptions, transpileOptions.fileName, oldTranspileDiagnostics, transpileOptions.moduleName); - } - }); + transpileOptions.reportDiagnostics = true; - after(() => { - transpileResult = undefined!; - oldTranspileResult = undefined!; - oldTranspileDiagnostics = undefined!; - }); + const justName = "transpile/" + name.replace(/[^a-z0-9\-. ]/ig, "") + (transpileOptions.compilerOptions.jsx ? Extension.Tsx : Extension.Ts); + const toBeCompiled = [{ + unitName, + content: input + }]; + const canUseOldTranspile = !transpileOptions.renamedDependencies; - /* eslint-disable no-null/no-null */ - it("Correct errors for " + justName, () => { - Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".errors.txt"), - transpileResult.diagnostics!.length === 0 ? null : Harness.Compiler.getErrorBaseline(toBeCompiled, transpileResult.diagnostics!)); - }); + before(() => { + transpileResult = transpileModule(input, transpileOptions); if (canUseOldTranspile) { - it("Correct errors (old transpile) for " + justName, () => { - Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.errors.txt"), - oldTranspileDiagnostics.length === 0 ? null : Harness.Compiler.getErrorBaseline(toBeCompiled, oldTranspileDiagnostics)); - }); + oldTranspileDiagnostics = []; + oldTranspileResult = transpile(input, transpileOptions.compilerOptions, transpileOptions.fileName, oldTranspileDiagnostics, transpileOptions.moduleName); } - /* eslint-enable no-null/no-null */ + }); - it("Correct output for " + justName, () => { - Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, Extension.Js), transpileResult.outputText); - }); + after(() => { + transpileResult = undefined!; + oldTranspileResult = undefined!; + oldTranspileDiagnostics = undefined!; + }); - if (canUseOldTranspile) { - it("Correct output (old transpile) for " + justName, () => { - Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.js"), oldTranspileResult); - }); - } + /* eslint-disable no-null/no-null */ + it("Correct errors for " + justName, () => { + Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".errors.txt"), + transpileResult.diagnostics!.length === 0 ? null : Harness.Compiler.getErrorBaseline(toBeCompiled, transpileResult.diagnostics!)); }); - } - transpilesCorrectly("Generates no diagnostics with valid inputs", `var x = 0;`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - }); + if (canUseOldTranspile) { + it("Correct errors (old transpile) for " + justName, () => { + Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.errors.txt"), + oldTranspileDiagnostics.length === 0 ? null : Harness.Compiler.getErrorBaseline(toBeCompiled, oldTranspileDiagnostics)); + }); + } + /* eslint-enable no-null/no-null */ - transpilesCorrectly("Generates no diagnostics for missing file references", `/// -var x = 0;`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - }); + it("Correct output for " + justName, () => { + Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, Extension.Js), transpileResult.outputText); + }); - transpilesCorrectly("Generates no diagnostics for missing module imports", `import {a} from "module2";`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } + if (canUseOldTranspile) { + it("Correct output (old transpile) for " + justName, () => { + Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.js"), oldTranspileResult); + }); + } }); + } - transpilesCorrectly("Generates expected syntactic diagnostics", `a b`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - }); + transpilesCorrectly("Generates no diagnostics with valid inputs", `var x = 0;`, { + options: { compilerOptions: { module: ModuleKind.CommonJS } } + }); - transpilesCorrectly("Does not generate semantic diagnostics", `var x: string = 0;`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - }); + transpilesCorrectly("Generates no diagnostics for missing file references", `/// +var x = 0;`, { + options: { compilerOptions: { module: ModuleKind.CommonJS } } + }); - transpilesCorrectly("Generates module output", `var x = 0;`, { - options: { compilerOptions: { module: ModuleKind.AMD } } - }); + transpilesCorrectly("Generates no diagnostics for missing module imports", `import {a} from "module2";`, { + options: { compilerOptions: { module: ModuleKind.CommonJS } } + }); - transpilesCorrectly("Uses correct newLine character", `var x = 0;`, { - options: { compilerOptions: { module: ModuleKind.CommonJS, newLine: NewLineKind.LineFeed } } - }); + transpilesCorrectly("Generates expected syntactic diagnostics", `a b`, { + options: { compilerOptions: { module: ModuleKind.CommonJS } } + }); - transpilesCorrectly("Sets module name", "var x = 1;", { - options: { compilerOptions: { module: ModuleKind.System, newLine: NewLineKind.LineFeed }, moduleName: "NamedModule" } - }); + transpilesCorrectly("Does not generate semantic diagnostics", `var x: string = 0;`, { + options: { compilerOptions: { module: ModuleKind.CommonJS } } + }); - transpilesCorrectly("No extra errors for file without extension", `"use strict";\r\nvar x = 0;`, { - options: { compilerOptions: { module: ModuleKind.CommonJS }, fileName: "file" } - }); + transpilesCorrectly("Generates module output", `var x = 0;`, { + options: { compilerOptions: { module: ModuleKind.AMD } } + }); - transpilesCorrectly("Rename dependencies - System", - `import {foo} from "SomeName";\n` + - `declare function use(a: any);\n` + - `use(foo);`, { - options: { compilerOptions: { module: ModuleKind.System, newLine: NewLineKind.LineFeed }, renamedDependencies: { SomeName: "SomeOtherName" } } - }); + transpilesCorrectly("Uses correct newLine character", `var x = 0;`, { + options: { compilerOptions: { module: ModuleKind.CommonJS, newLine: NewLineKind.LineFeed } } + }); - transpilesCorrectly("Rename dependencies - AMD", - `import {foo} from "SomeName";\n` + - `declare function use(a: any);\n` + - `use(foo);`, { - options: { compilerOptions: { module: ModuleKind.AMD, newLine: NewLineKind.LineFeed }, renamedDependencies: { SomeName: "SomeOtherName" } } - }); + transpilesCorrectly("Sets module name", "var x = 1;", { + options: { compilerOptions: { module: ModuleKind.System, newLine: NewLineKind.LineFeed }, moduleName: "NamedModule" } + }); - transpilesCorrectly("Rename dependencies - UMD", - `import {foo} from "SomeName";\n` + - `declare function use(a: any);\n` + - `use(foo);`, { - options: { compilerOptions: { module: ModuleKind.UMD, newLine: NewLineKind.LineFeed }, renamedDependencies: { SomeName: "SomeOtherName" } } - }); + transpilesCorrectly("No extra errors for file without extension", `"use strict";\r\nvar x = 0;`, { + options: { compilerOptions: { module: ModuleKind.CommonJS }, fileName: "file" } + }); - transpilesCorrectly("Transpile with emit decorators and emit metadata", - `import {db} from './db';\n` + - `function someDecorator(target) {\n` + - ` return target;\n` + - `} \n` + - `@someDecorator\n` + - `class MyClass {\n` + - ` db: db;\n` + - ` constructor(db: db) {\n` + - ` this.db = db;\n` + - ` this.db.doSomething(); \n` + - ` }\n` + - `}\n` + - `export {MyClass}; \n`, { - options: { - compilerOptions: { - module: ModuleKind.CommonJS, - newLine: NewLineKind.LineFeed, - noEmitHelpers: true, - emitDecoratorMetadata: true, - experimentalDecorators: true, - target: ScriptTarget.ES5, - } + transpilesCorrectly("Rename dependencies - System", + `import {foo} from "SomeName";\n` + + `declare function use(a: any);\n` + + `use(foo);`, { + options: { compilerOptions: { module: ModuleKind.System, newLine: NewLineKind.LineFeed }, renamedDependencies: { SomeName: "SomeOtherName" } } + }); + + transpilesCorrectly("Rename dependencies - AMD", + `import {foo} from "SomeName";\n` + + `declare function use(a: any);\n` + + `use(foo);`, { + options: { compilerOptions: { module: ModuleKind.AMD, newLine: NewLineKind.LineFeed }, renamedDependencies: { SomeName: "SomeOtherName" } } + }); + + transpilesCorrectly("Rename dependencies - UMD", + `import {foo} from "SomeName";\n` + + `declare function use(a: any);\n` + + `use(foo);`, { + options: { compilerOptions: { module: ModuleKind.UMD, newLine: NewLineKind.LineFeed }, renamedDependencies: { SomeName: "SomeOtherName" } } + }); + + transpilesCorrectly("Transpile with emit decorators and emit metadata", + `import {db} from './db';\n` + + `function someDecorator(target) {\n` + + ` return target;\n` + + `} \n` + + `@someDecorator\n` + + `class MyClass {\n` + + ` db: db;\n` + + ` constructor(db: db) {\n` + + ` this.db = db;\n` + + ` this.db.doSomething(); \n` + + ` }\n` + + `}\n` + + `export {MyClass}; \n`, { + options: { + compilerOptions: { + module: ModuleKind.CommonJS, + newLine: NewLineKind.LineFeed, + noEmitHelpers: true, + emitDecoratorMetadata: true, + experimentalDecorators: true, + target: ScriptTarget.ES5, } - }); - - transpilesCorrectly("Supports backslashes in file name", "var x", { - options: { fileName: "a\\b.ts" } + } }); - transpilesCorrectly("transpile file as 'tsx' if 'jsx' is specified", `var x =
`, { - options: { compilerOptions: { jsx: JsxEmit.React, newLine: NewLineKind.LineFeed } } - }); + transpilesCorrectly("Supports backslashes in file name", "var x", { + options: { fileName: "a\\b.ts" } + }); - transpilesCorrectly("transpile .js files", "const a = 10;", { - options: { compilerOptions: { newLine: NewLineKind.LineFeed, module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("transpile file as 'tsx' if 'jsx' is specified", `var x =
`, { + options: { compilerOptions: { jsx: JsxEmit.React, newLine: NewLineKind.LineFeed } } + }); - transpilesCorrectly("Supports urls in file name", "var x", { - options: { fileName: "http://somewhere/directory//directory2/file.ts" } - }); + transpilesCorrectly("transpile .js files", "const a = 10;", { + options: { compilerOptions: { newLine: NewLineKind.LineFeed, module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Accepts string as enum values for compile-options", "export const x = 0", { - options: { - compilerOptions: { - module: "es6" as any as ModuleKind, - // Capitalization and spaces ignored - target: " Es6 " as any as ScriptTarget - } + transpilesCorrectly("Supports urls in file name", "var x", { + options: { fileName: "http://somewhere/directory//directory2/file.ts" } + }); + + transpilesCorrectly("Accepts string as enum values for compile-options", "export const x = 0", { + options: { + compilerOptions: { + module: "es6" as any as ModuleKind, + // Capitalization and spaces ignored + target: " Es6 " as any as ScriptTarget } - }); + } + }); - transpilesCorrectly("Report an error when compiler-options module-kind is out-of-range", "", { - options: { compilerOptions: { module: 123 as any as ModuleKind } } - }); + transpilesCorrectly("Report an error when compiler-options module-kind is out-of-range", "", { + options: { compilerOptions: { module: 123 as any as ModuleKind } } + }); - transpilesCorrectly("Report an error when compiler-options target-script is out-of-range", "", { - options: { compilerOptions: { module: 123 as any as ModuleKind } } - }); + transpilesCorrectly("Report an error when compiler-options target-script is out-of-range", "", { + options: { compilerOptions: { module: 123 as any as ModuleKind } } + }); - transpilesCorrectly("Support options with lib values", "const a = 10;", { - options: { compilerOptions: { lib: ["es6", "dom"], module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Support options with lib values", "const a = 10;", { + options: { compilerOptions: { lib: ["es6", "dom"], module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Support options with types values", "const a = 10;", { - options: { compilerOptions: { types: ["jquery", "typescript"], module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Support options with types values", "const a = 10;", { + options: { compilerOptions: { types: ["jquery", "typescript"], module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'allowJs'", "x;", { - options: { compilerOptions: { allowJs: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'allowJs'", "x;", { + options: { compilerOptions: { allowJs: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'allowSyntheticDefaultImports'", "x;", { - options: { compilerOptions: { allowSyntheticDefaultImports: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'allowSyntheticDefaultImports'", "x;", { + options: { compilerOptions: { allowSyntheticDefaultImports: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'allowUnreachableCode'", "x;", { - options: { compilerOptions: { allowUnreachableCode: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'allowUnreachableCode'", "x;", { + options: { compilerOptions: { allowUnreachableCode: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'allowUnusedLabels'", "x;", { - options: { compilerOptions: { allowUnusedLabels: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'allowUnusedLabels'", "x;", { + options: { compilerOptions: { allowUnusedLabels: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'alwaysStrict'", "x;", { - options: { compilerOptions: { alwaysStrict: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'alwaysStrict'", "x;", { + options: { compilerOptions: { alwaysStrict: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'baseUrl'", "x;", { - options: { compilerOptions: { baseUrl: "./folder/baseUrl" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'baseUrl'", "x;", { + options: { compilerOptions: { baseUrl: "./folder/baseUrl" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'charset'", "x;", { - options: { compilerOptions: { charset: "en-us" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'charset'", "x;", { + options: { compilerOptions: { charset: "en-us" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'declaration'", "x;", { - options: { compilerOptions: { declaration: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'declaration'", "x;", { + options: { compilerOptions: { declaration: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'declarationDir'", "x;", { - options: { compilerOptions: { declarationDir: "out/declarations" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'declarationDir'", "x;", { + options: { compilerOptions: { declarationDir: "out/declarations" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'emitBOM'", "x;", { - options: { compilerOptions: { emitBOM: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'emitBOM'", "x;", { + options: { compilerOptions: { emitBOM: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'emitDecoratorMetadata'", "x;", { - options: { compilerOptions: { emitDecoratorMetadata: true, experimentalDecorators: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'emitDecoratorMetadata'", "x;", { + options: { compilerOptions: { emitDecoratorMetadata: true, experimentalDecorators: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'experimentalDecorators'", "x;", { - options: { compilerOptions: { experimentalDecorators: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'experimentalDecorators'", "x;", { + options: { compilerOptions: { experimentalDecorators: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'forceConsistentCasingInFileNames'", "x;", { - options: { compilerOptions: { forceConsistentCasingInFileNames: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'forceConsistentCasingInFileNames'", "x;", { + options: { compilerOptions: { forceConsistentCasingInFileNames: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'isolatedModules'", "x;", { - options: { compilerOptions: { isolatedModules: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'isolatedModules'", "x;", { + options: { compilerOptions: { isolatedModules: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'jsx'", "x;", { - options: { compilerOptions: { jsx: 1 }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'jsx'", "x;", { + options: { compilerOptions: { jsx: 1 }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'lib'", "x;", { - options: { compilerOptions: { lib: ["es2015", "dom"] }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'lib'", "x;", { + options: { compilerOptions: { lib: ["es2015", "dom"] }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'locale'", "x;", { - options: { compilerOptions: { locale: "en-us" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'locale'", "x;", { + options: { compilerOptions: { locale: "en-us" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'module'", "x;", { - options: { compilerOptions: { module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'module'", "x;", { + options: { compilerOptions: { module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'moduleResolution'", "x;", { - options: { compilerOptions: { moduleResolution: ModuleResolutionKind.NodeJs }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'moduleResolution'", "x;", { + options: { compilerOptions: { moduleResolution: ModuleResolutionKind.NodeJs }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'newLine'", "x;", { - options: { compilerOptions: { newLine: NewLineKind.CarriageReturnLineFeed }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'newLine'", "x;", { + options: { compilerOptions: { newLine: NewLineKind.CarriageReturnLineFeed }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noEmit'", "x;", { - options: { compilerOptions: { noEmit: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noEmit'", "x;", { + options: { compilerOptions: { noEmit: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noEmitHelpers'", "x;", { - options: { compilerOptions: { noEmitHelpers: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noEmitHelpers'", "x;", { + options: { compilerOptions: { noEmitHelpers: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noEmitOnError'", "x;", { - options: { compilerOptions: { noEmitOnError: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noEmitOnError'", "x;", { + options: { compilerOptions: { noEmitOnError: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noErrorTruncation'", "x;", { - options: { compilerOptions: { noErrorTruncation: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noErrorTruncation'", "x;", { + options: { compilerOptions: { noErrorTruncation: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noFallthroughCasesInSwitch'", "x;", { - options: { compilerOptions: { noFallthroughCasesInSwitch: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noFallthroughCasesInSwitch'", "x;", { + options: { compilerOptions: { noFallthroughCasesInSwitch: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noImplicitAny'", "x;", { - options: { compilerOptions: { noImplicitAny: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noImplicitAny'", "x;", { + options: { compilerOptions: { noImplicitAny: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noImplicitReturns'", "x;", { - options: { compilerOptions: { noImplicitReturns: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noImplicitReturns'", "x;", { + options: { compilerOptions: { noImplicitReturns: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noImplicitThis'", "x;", { - options: { compilerOptions: { noImplicitThis: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noImplicitThis'", "x;", { + options: { compilerOptions: { noImplicitThis: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noImplicitUseStrict'", "x;", { - options: { compilerOptions: { noImplicitUseStrict: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noImplicitUseStrict'", "x;", { + options: { compilerOptions: { noImplicitUseStrict: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noLib'", "x;", { - options: { compilerOptions: { noLib: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noLib'", "x;", { + options: { compilerOptions: { noLib: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noResolve'", "x;", { - options: { compilerOptions: { noResolve: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noResolve'", "x;", { + options: { compilerOptions: { noResolve: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'out'", "x;", { - options: { compilerOptions: { out: "./out" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'out'", "x;", { + options: { compilerOptions: { out: "./out" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'outDir'", "x;", { - options: { compilerOptions: { outDir: "./outDir" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'outDir'", "x;", { + options: { compilerOptions: { outDir: "./outDir" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'outFile'", "x;", { - options: { compilerOptions: { outFile: "./outFile" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'outFile'", "x;", { + options: { compilerOptions: { outFile: "./outFile" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'paths'", "x;", { - options: { compilerOptions: { paths: { "*": ["./generated*"] } }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'paths'", "x;", { + options: { compilerOptions: { paths: { "*": ["./generated*"] } }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'preserveConstEnums'", "x;", { - options: { compilerOptions: { preserveConstEnums: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'preserveConstEnums'", "x;", { + options: { compilerOptions: { preserveConstEnums: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'reactNamespace'", "x;", { - options: { compilerOptions: { reactNamespace: "react" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'reactNamespace'", "x;", { + options: { compilerOptions: { reactNamespace: "react" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'jsxFactory'", "x;", { - options: { compilerOptions: { jsxFactory: "createElement" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'jsxFactory'", "x;", { + options: { compilerOptions: { jsxFactory: "createElement" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'jsxFragmentFactory'", "x;", { - options: { compilerOptions: { jsxFactory: "x", jsxFragmentFactory: "frag" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'jsxFragmentFactory'", "x;", { + options: { compilerOptions: { jsxFactory: "x", jsxFragmentFactory: "frag" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'removeComments'", "x;", { - options: { compilerOptions: { removeComments: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'removeComments'", "x;", { + options: { compilerOptions: { removeComments: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'rootDir'", "x;", { - options: { compilerOptions: { rootDir: "./rootDir" }, fileName: "./rootDir/input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'rootDir'", "x;", { + options: { compilerOptions: { rootDir: "./rootDir" }, fileName: "./rootDir/input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'rootDirs'", "x;", { - options: { compilerOptions: { rootDirs: ["./a", "./b"] }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'rootDirs'", "x;", { + options: { compilerOptions: { rootDirs: ["./a", "./b"] }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'skipLibCheck'", "x;", { - options: { compilerOptions: { skipLibCheck: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'skipLibCheck'", "x;", { + options: { compilerOptions: { skipLibCheck: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'skipDefaultLibCheck'", "x;", { - options: { compilerOptions: { skipDefaultLibCheck: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'skipDefaultLibCheck'", "x;", { + options: { compilerOptions: { skipDefaultLibCheck: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'strictNullChecks'", "x;", { - options: { compilerOptions: { strictNullChecks: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'strictNullChecks'", "x;", { + options: { compilerOptions: { strictNullChecks: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'stripInternal'", "x;", { - options: { compilerOptions: { stripInternal: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'stripInternal'", "x;", { + options: { compilerOptions: { stripInternal: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'suppressExcessPropertyErrors'", "x;", { - options: { compilerOptions: { suppressExcessPropertyErrors: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'suppressExcessPropertyErrors'", "x;", { + options: { compilerOptions: { suppressExcessPropertyErrors: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'suppressImplicitAnyIndexErrors'", "x;", { - options: { compilerOptions: { suppressImplicitAnyIndexErrors: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'suppressImplicitAnyIndexErrors'", "x;", { + options: { compilerOptions: { suppressImplicitAnyIndexErrors: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'target'", "x;", { - options: { compilerOptions: { target: 2 }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'target'", "x;", { + options: { compilerOptions: { target: 2 }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'types'", "x;", { - options: { compilerOptions: { types: ["jquery", "jasmine"] }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'types'", "x;", { + options: { compilerOptions: { types: ["jquery", "jasmine"] }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'typeRoots'", "x;", { - options: { compilerOptions: { typeRoots: ["./folder"] }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'typeRoots'", "x;", { + options: { compilerOptions: { typeRoots: ["./folder"] }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'incremental'", "x;", { - options: { compilerOptions: { incremental: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'incremental'", "x;", { + options: { compilerOptions: { incremental: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'composite'", "x;", { - options: { compilerOptions: { composite: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'composite'", "x;", { + options: { compilerOptions: { composite: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'tsbuildinfo'", "x;", { - options: { compilerOptions: { incremental: true, tsBuildInfoFile: "./folder/config.tsbuildinfo" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'tsbuildinfo'", "x;", { + options: { compilerOptions: { incremental: true, tsBuildInfoFile: "./folder/config.tsbuildinfo" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Correctly serialize metadata when transpile with CommonJS option", - `import * as ng from "angular2/core";` + - `declare function foo(...args: any[]);` + - `@foo` + - `export class MyClass1 {` + - ` constructor(private _elementRef: ng.ElementRef){}` + - `}`, { - options: { - compilerOptions: { - target: ScriptTarget.ES5, - module: ModuleKind.CommonJS, - moduleResolution: ModuleResolutionKind.NodeJs, - emitDecoratorMetadata: true, - experimentalDecorators: true, - isolatedModules: true, - } + transpilesCorrectly("Correctly serialize metadata when transpile with CommonJS option", + `import * as ng from "angular2/core";` + + `declare function foo(...args: any[]);` + + `@foo` + + `export class MyClass1 {` + + ` constructor(private _elementRef: ng.ElementRef){}` + + `}`, { + options: { + compilerOptions: { + target: ScriptTarget.ES5, + module: ModuleKind.CommonJS, + moduleResolution: ModuleResolutionKind.NodeJs, + emitDecoratorMetadata: true, + experimentalDecorators: true, + isolatedModules: true, } } - ); - - transpilesCorrectly("Correctly serialize metadata when transpile with System option", - `import * as ng from "angular2/core";` + - `declare function foo(...args: any[]);` + - `@foo` + - `export class MyClass1 {` + - ` constructor(private _elementRef: ng.ElementRef){}` + - `}`, { - options: { - compilerOptions: { - target: ScriptTarget.ES5, - module: ModuleKind.System, - moduleResolution: ModuleResolutionKind.NodeJs, - emitDecoratorMetadata: true, - experimentalDecorators: true, - isolatedModules: true, - } + } + ); + + transpilesCorrectly("Correctly serialize metadata when transpile with System option", + `import * as ng from "angular2/core";` + + `declare function foo(...args: any[]);` + + `@foo` + + `export class MyClass1 {` + + ` constructor(private _elementRef: ng.ElementRef){}` + + `}`, { + options: { + compilerOptions: { + target: ScriptTarget.ES5, + module: ModuleKind.System, + moduleResolution: ModuleResolutionKind.NodeJs, + emitDecoratorMetadata: true, + experimentalDecorators: true, + isolatedModules: true, } } - ); + } + ); - transpilesCorrectly("Supports readonly keyword for arrays", "let x: readonly string[];", { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - }); + transpilesCorrectly("Supports readonly keyword for arrays", "let x: readonly string[];", { + options: { compilerOptions: { module: ModuleKind.CommonJS } } + }); - transpilesCorrectly("Supports 'as const' arrays", `([] as const).forEach(k => console.log(k));`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - }); + transpilesCorrectly("Supports 'as const' arrays", `([] as const).forEach(k => console.log(k));`, { + options: { compilerOptions: { module: ModuleKind.CommonJS } } + }); - transpilesCorrectly("Infer correct file extension", `const fn = (a: T) => a`, { - noSetFileName: true - }); + transpilesCorrectly("Infer correct file extension", `const fn = (a: T) => a`, { + noSetFileName: true + }); - transpilesCorrectly("Export star as ns conflict does not crash", ` + transpilesCorrectly("Export star as ns conflict does not crash", ` var a; export { a as alias }; export * as alias from './file';`, { - noSetFileName: true - }); + noSetFileName: true }); +}); } diff --git a/src/testRunner/unittests/transform.ts b/src/testRunner/unittests/transform.ts index 476dc6540d502..510dd34c355d7 100644 --- a/src/testRunner/unittests/transform.ts +++ b/src/testRunner/unittests/transform.ts @@ -1,449 +1,449 @@ namespace ts { - describe("unittests:: TransformAPI", () => { - function replaceUndefinedWithVoid0(context: TransformationContext) { - const previousOnSubstituteNode = context.onSubstituteNode; - context.enableSubstitution(SyntaxKind.Identifier); - context.onSubstituteNode = (hint, node) => { - node = previousOnSubstituteNode(hint, node); - if (hint === EmitHint.Expression && isIdentifier(node) && node.escapedText === "undefined") { - node = factory.createPartiallyEmittedExpression( - addSyntheticTrailingComment( - setTextRange( - factory.createVoidZero(), - node), - SyntaxKind.MultiLineCommentTrivia, "undefined")); - } - return node; - }; - return (file: SourceFile) => file; - } - function replaceNumberWith2(context: TransformationContext) { - function visitor(node: Node): Node { - if (isNumericLiteral(node)) { - return factory.createNumericLiteral("2"); - } - return visitEachChild(node, visitor, context); +describe("unittests:: TransformAPI", () => { + function replaceUndefinedWithVoid0(context: TransformationContext) { + const previousOnSubstituteNode = context.onSubstituteNode; + context.enableSubstitution(SyntaxKind.Identifier); + context.onSubstituteNode = (hint, node) => { + node = previousOnSubstituteNode(hint, node); + if (hint === EmitHint.Expression && isIdentifier(node) && node.escapedText === "undefined") { + node = factory.createPartiallyEmittedExpression( + addSyntheticTrailingComment( + setTextRange( + factory.createVoidZero(), + node), + SyntaxKind.MultiLineCommentTrivia, "undefined")); } - return (file: SourceFile) => visitNode(file, visitor); - } - - function replaceIdentifiersNamedOldNameWithNewName(context: TransformationContext) { - const previousOnSubstituteNode = context.onSubstituteNode; - context.enableSubstitution(SyntaxKind.Identifier); - context.onSubstituteNode = (hint, node) => { - node = previousOnSubstituteNode(hint, node); - if (isIdentifier(node) && node.escapedText === "oldName") { - node = setTextRange(factory.createIdentifier("newName"), node); - } - return node; - }; - return (file: SourceFile) => file; - } - - function replaceIdentifiersNamedOldNameWithNewName2(context: TransformationContext) { - const visitor: Visitor = (node) => { - if (isIdentifier(node) && node.text === "oldName") { - return factory.createIdentifier("newName"); - } - return visitEachChild(node, visitor, context); - }; - return (node: SourceFile) => visitNode(node, visitor); - } - - function createTaggedTemplateLiteral(): Transformer { - return sourceFile => factory.updateSourceFile(sourceFile, [ - factory.createExpressionStatement( - factory.createTaggedTemplateExpression( - factory.createIdentifier("$tpl"), - /*typeArguments*/ undefined, - factory.createNoSubstitutionTemplateLiteral("foo", "foo"))) - ]); + return node; + }; + return (file: SourceFile) => file; + } + function replaceNumberWith2(context: TransformationContext) { + function visitor(node: Node): Node { + if (isNumericLiteral(node)) { + return factory.createNumericLiteral("2"); + } + return visitEachChild(node, visitor, context); } + return (file: SourceFile) => visitNode(file, visitor); + } + + function replaceIdentifiersNamedOldNameWithNewName(context: TransformationContext) { + const previousOnSubstituteNode = context.onSubstituteNode; + context.enableSubstitution(SyntaxKind.Identifier); + context.onSubstituteNode = (hint, node) => { + node = previousOnSubstituteNode(hint, node); + if (isIdentifier(node) && node.escapedText === "oldName") { + node = setTextRange(factory.createIdentifier("newName"), node); + } + return node; + }; + return (file: SourceFile) => file; + } + + function replaceIdentifiersNamedOldNameWithNewName2(context: TransformationContext) { + const visitor: Visitor = (node) => { + if (isIdentifier(node) && node.text === "oldName") { + return factory.createIdentifier("newName"); + } + return visitEachChild(node, visitor, context); + }; + return (node: SourceFile) => visitNode(node, visitor); + } + + function createTaggedTemplateLiteral(): Transformer { + return sourceFile => factory.updateSourceFile(sourceFile, [ + factory.createExpressionStatement( + factory.createTaggedTemplateExpression( + factory.createIdentifier("$tpl"), + /*typeArguments*/ undefined, + factory.createNoSubstitutionTemplateLiteral("foo", "foo"))) + ]); + } + + function transformSourceFile(sourceText: string, transformers: TransformerFactory[]) { + const transformed = transform(createSourceFile("source.ts", sourceText, ScriptTarget.ES2015), transformers); + const printer = createPrinter({ newLine: NewLineKind.CarriageReturnLineFeed }, { + onEmitNode: transformed.emitNodeWithNotification, + substituteNode: transformed.substituteNode + }); + const result = printer.printBundle(factory.createBundle(transformed.transformed)); + transformed.dispose(); + return result; + } + + function testBaseline(testName: string, test: () => string) { + it(testName, () => { + Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${testName}.js`, test()); + }); + } - function transformSourceFile(sourceText: string, transformers: TransformerFactory[]) { - const transformed = transform(createSourceFile("source.ts", sourceText, ScriptTarget.ES2015), transformers); - const printer = createPrinter({ newLine: NewLineKind.CarriageReturnLineFeed }, { - onEmitNode: transformed.emitNodeWithNotification, - substituteNode: transformed.substituteNode + function testBaselineAndEvaluate(testName: string, test: () => string, onEvaluate: (exports: any) => void) { + describe(testName, () => { + let sourceText!: string; + before(() => { + sourceText = test(); }); - const result = printer.printBundle(factory.createBundle(transformed.transformed)); - transformed.dispose(); - return result; - } - - function testBaseline(testName: string, test: () => string) { - it(testName, () => { - Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${testName}.js`, test()); + after(() => { + sourceText = undefined!; }); - } - - function testBaselineAndEvaluate(testName: string, test: () => string, onEvaluate: (exports: any) => void) { - describe(testName, () => { - let sourceText!: string; - before(() => { - sourceText = test(); - }); - after(() => { - sourceText = undefined!; - }); - it("compare baselines", () => { - Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${testName}.js`, sourceText); - }); - it("evaluate", () => { - onEvaluate(evaluator.evaluateJavaScript(sourceText)); - }); + it("compare baselines", () => { + Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${testName}.js`, sourceText); + }); + it("evaluate", () => { + onEvaluate(evaluator.evaluateJavaScript(sourceText)); }); - } - - testBaseline("substitution", () => { - return transformSourceFile(`var a = undefined;`, [replaceUndefinedWithVoid0]); }); + } - testBaseline("types", () => { - return transformSourceFile(`let a: () => void`, [ - context => file => visitNode(file, function visitor(node: Node): VisitResult { - return visitEachChild(node, visitor, context); - }) - ]); - }); + testBaseline("substitution", () => { + return transformSourceFile(`var a = undefined;`, [replaceUndefinedWithVoid0]); + }); - testBaseline("transformDefiniteAssignmentAssertions", () => { - return transformSourceFile(`let a!: () => void`, [ - context => file => visitNode(file, function visitor(node: Node): VisitResult { - if (node.kind === SyntaxKind.VoidKeyword) { - return factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword); - } - return visitEachChild(node, visitor, context); - }) - ]); - }); + testBaseline("types", () => { + return transformSourceFile(`let a: () => void`, [ + context => file => visitNode(file, function visitor(node: Node): VisitResult { + return visitEachChild(node, visitor, context); + }) + ]); + }); - testBaseline("fromTranspileModule", () => { - return transpileModule(`var oldName = undefined;`, { - transformers: { - before: [replaceUndefinedWithVoid0], - after: [replaceIdentifiersNamedOldNameWithNewName] - }, - compilerOptions: { - newLine: NewLineKind.CarriageReturnLineFeed + testBaseline("transformDefiniteAssignmentAssertions", () => { + return transformSourceFile(`let a!: () => void`, [ + context => file => visitNode(file, function visitor(node: Node): VisitResult { + if (node.kind === SyntaxKind.VoidKeyword) { + return factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword); } - }).outputText; - }); + return visitEachChild(node, visitor, context); + }) + ]); + }); - testBaseline("transformTaggedTemplateLiteral", () => { - return transpileModule("", { - transformers: { - before: [createTaggedTemplateLiteral], - }, - compilerOptions: { - target: ScriptTarget.ES5, - newLine: NewLineKind.CarriageReturnLineFeed - } - }).outputText; - }); + testBaseline("fromTranspileModule", () => { + return transpileModule(`var oldName = undefined;`, { + transformers: { + before: [replaceUndefinedWithVoid0], + after: [replaceIdentifiersNamedOldNameWithNewName] + }, + compilerOptions: { + newLine: NewLineKind.CarriageReturnLineFeed + } + }).outputText; + }); - testBaseline("issue27854", () => { - return transpileModule(`oldName<{ a: string; }>\` ... \`;`, { - transformers: { - before: [replaceIdentifiersNamedOldNameWithNewName2] - }, - compilerOptions: { - newLine: NewLineKind.CarriageReturnLineFeed, - target: ScriptTarget.Latest - } - }).outputText; - }); + testBaseline("transformTaggedTemplateLiteral", () => { + return transpileModule("", { + transformers: { + before: [createTaggedTemplateLiteral], + }, + compilerOptions: { + target: ScriptTarget.ES5, + newLine: NewLineKind.CarriageReturnLineFeed + } + }).outputText; + }); + + testBaseline("issue27854", () => { + return transpileModule(`oldName<{ a: string; }>\` ... \`;`, { + transformers: { + before: [replaceIdentifiersNamedOldNameWithNewName2] + }, + compilerOptions: { + newLine: NewLineKind.CarriageReturnLineFeed, + target: ScriptTarget.Latest + } + }).outputText; + }); - testBaseline("issue44068", () => { - return transformSourceFile(` + testBaseline("issue44068", () => { + return transformSourceFile(` const FirstVar = null; const SecondVar = null; `, [ - context => file => { - const firstVarName = (file.statements[0] as VariableStatement) - .declarationList.declarations[0].name as Identifier; - const secondVarName = (file.statements[0] as VariableStatement) - .declarationList.declarations[0].name as Identifier; - - return context.factory.updateSourceFile(file, file.statements.concat([ - context.factory.createExpressionStatement( - context.factory.createArrayLiteralExpression([firstVarName, secondVarName]) - ), - ])); - } - ]); - }); + context => file => { + const firstVarName = (file.statements[0] as VariableStatement) + .declarationList.declarations[0].name as Identifier; + const secondVarName = (file.statements[0] as VariableStatement) + .declarationList.declarations[0].name as Identifier; + + return context.factory.updateSourceFile(file, file.statements.concat([ + context.factory.createExpressionStatement( + context.factory.createArrayLiteralExpression([firstVarName, secondVarName]) + ), + ])); + } + ]); + }); - testBaseline("rewrittenNamespace", () => { - return transpileModule(`namespace Reflect { const x = 1; }`, { - transformers: { - before: [forceNamespaceRewrite], - }, - compilerOptions: { - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + testBaseline("rewrittenNamespace", () => { + return transpileModule(`namespace Reflect { const x = 1; }`, { + transformers: { + before: [forceNamespaceRewrite], + }, + compilerOptions: { + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - testBaseline("rewrittenNamespaceFollowingClass", () => { - return transpileModule(` + testBaseline("rewrittenNamespaceFollowingClass", () => { + return transpileModule(` class C { foo = 10; static bar = 20 } namespace C { export let x = 10; } `, { - transformers: { - before: [forceNamespaceRewrite], - }, - compilerOptions: { - target: ScriptTarget.ESNext, - newLine: NewLineKind.CarriageReturnLineFeed, - useDefineForClassFields: false, - } - }).outputText; - }); + transformers: { + before: [forceNamespaceRewrite], + }, + compilerOptions: { + target: ScriptTarget.ESNext, + newLine: NewLineKind.CarriageReturnLineFeed, + useDefineForClassFields: false, + } + }).outputText; + }); - testBaseline("transformTypesInExportDefault", () => { - return transpileModule(` + testBaseline("transformTypesInExportDefault", () => { + return transpileModule(` export default (foo: string) => { return 1; } `, { - transformers: { - before: [replaceNumberWith2], - }, - compilerOptions: { - target: ScriptTarget.ESNext, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [replaceNumberWith2], + }, + compilerOptions: { + target: ScriptTarget.ESNext, + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); + + testBaseline("synthesizedClassAndNamespaceCombination", () => { + return transpileModule("", { + transformers: { + before: [replaceWithClassAndNamespace], + }, + compilerOptions: { + target: ScriptTarget.ESNext, + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + + function replaceWithClassAndNamespace() { + return (sourceFile: SourceFile) => { + // TODO(rbuckton): Does this need to be parented? + const result = factory.updateSourceFile( + sourceFile, + factory.createNodeArray([ + factory.createClassDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, /*members*/ undefined!), // TODO: GH#18217 + factory.createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createIdentifier("Foo"), factory.createModuleBlock([factory.createEmptyStatement()])) + ]) + ); + return result; + }; + } + }); + + function forceNamespaceRewrite(context: TransformationContext) { + return (sourceFile: SourceFile): SourceFile => { + return visitNode(sourceFile); - testBaseline("synthesizedClassAndNamespaceCombination", () => { - return transpileModule("", { - transformers: { - before: [replaceWithClassAndNamespace], - }, - compilerOptions: { - target: ScriptTarget.ESNext, - newLine: NewLineKind.CarriageReturnLineFeed, + function visitNode(node: T): T { + if (node.kind === SyntaxKind.ModuleBlock) { + const block = node as T & ModuleBlock; + const statements = factory.createNodeArray([...block.statements]); + return factory.updateModuleBlock(block, statements) as typeof block; } - }).outputText; - - function replaceWithClassAndNamespace() { - return (sourceFile: SourceFile) => { - // TODO(rbuckton): Does this need to be parented? - const result = factory.updateSourceFile( - sourceFile, - factory.createNodeArray([ - factory.createClassDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, /*members*/ undefined!), // TODO: GH#18217 - factory.createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createIdentifier("Foo"), factory.createModuleBlock([factory.createEmptyStatement()])) - ]) - ); - return result; - }; + return visitEachChild(node, visitNode, context); } - }); + }; + } + + testBaseline("transformAwayExportStar", () => { + return transpileModule("export * from './helper';", { + transformers: { + before: [expandExportStar], + }, + compilerOptions: { + target: ScriptTarget.ESNext, + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; - function forceNamespaceRewrite(context: TransformationContext) { + function expandExportStar(context: TransformationContext) { return (sourceFile: SourceFile): SourceFile => { return visitNode(sourceFile); function visitNode(node: T): T { - if (node.kind === SyntaxKind.ModuleBlock) { - const block = node as T & ModuleBlock; - const statements = factory.createNodeArray([...block.statements]); - return factory.updateModuleBlock(block, statements) as typeof block; + if (node.kind === SyntaxKind.ExportDeclaration) { + const ed = node as Node as ExportDeclaration; + const exports = [{ name: "x" }]; + const exportSpecifiers = exports.map(e => factory.createExportSpecifier(/*isTypeOnly*/ false, e.name, e.name)); + const exportClause = factory.createNamedExports(exportSpecifiers); + const newEd = factory.updateExportDeclaration(ed, ed.decorators, ed.modifiers, ed.isTypeOnly, exportClause, ed.moduleSpecifier, ed.assertClause); + + return newEd as Node as T; } return visitEachChild(node, visitNode, context); } }; } + }); - testBaseline("transformAwayExportStar", () => { - return transpileModule("export * from './helper';", { - transformers: { - before: [expandExportStar], - }, - compilerOptions: { - target: ScriptTarget.ESNext, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - - function expandExportStar(context: TransformationContext) { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile); - - function visitNode(node: T): T { - if (node.kind === SyntaxKind.ExportDeclaration) { - const ed = node as Node as ExportDeclaration; - const exports = [{ name: "x" }]; - const exportSpecifiers = exports.map(e => factory.createExportSpecifier(/*isTypeOnly*/ false, e.name, e.name)); - const exportClause = factory.createNamedExports(exportSpecifiers); - const newEd = factory.updateExportDeclaration(ed, ed.decorators, ed.modifiers, ed.isTypeOnly, exportClause, ed.moduleSpecifier, ed.assertClause); - - return newEd as Node as T; - } - return visitEachChild(node, visitNode, context); - } - }; + // https://github.com/Microsoft/TypeScript/issues/19618 + testBaseline("transformAddImportStar", () => { + return transpileModule("", { + transformers: { + before: [transformAddImportStar], + }, + compilerOptions: { + target: ScriptTarget.ES5, + module: ModuleKind.System, + newLine: NewLineKind.CarriageReturnLineFeed, } - }); - - // https://github.com/Microsoft/TypeScript/issues/19618 - testBaseline("transformAddImportStar", () => { - return transpileModule("", { - transformers: { - before: [transformAddImportStar], - }, - compilerOptions: { - target: ScriptTarget.ES5, - module: ModuleKind.System, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; + }).outputText; - function transformAddImportStar(_context: TransformationContext) { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile); - }; - function visitNode(sf: SourceFile) { - // produce `import * as i0 from './comp'; - const importStar = factory.createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*importClause*/ factory.createImportClause( - /*isTypeOnly*/ false, - /*name*/ undefined, - factory.createNamespaceImport(factory.createIdentifier("i0")) - ), - /*moduleSpecifier*/ factory.createStringLiteral("./comp1"), - /*assertClause*/ undefined); - return factory.updateSourceFile(sf, [importStar]); - } + function transformAddImportStar(_context: TransformationContext) { + return (sourceFile: SourceFile): SourceFile => { + return visitNode(sourceFile); + }; + function visitNode(sf: SourceFile) { + // produce `import * as i0 from './comp'; + const importStar = factory.createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*importClause*/ factory.createImportClause( + /*isTypeOnly*/ false, + /*name*/ undefined, + factory.createNamespaceImport(factory.createIdentifier("i0")) + ), + /*moduleSpecifier*/ factory.createStringLiteral("./comp1"), + /*assertClause*/ undefined); + return factory.updateSourceFile(sf, [importStar]); } - }); + } + }); - // https://github.com/Microsoft/TypeScript/issues/17384 - testBaseline("transformAddDecoratedNode", () => { - return transpileModule("", { - transformers: { - before: [transformAddDecoratedNode], - }, - compilerOptions: { - target: ScriptTarget.ES5, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; + // https://github.com/Microsoft/TypeScript/issues/17384 + testBaseline("transformAddDecoratedNode", () => { + return transpileModule("", { + transformers: { + before: [transformAddDecoratedNode], + }, + compilerOptions: { + target: ScriptTarget.ES5, + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; - function transformAddDecoratedNode(_context: TransformationContext) { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile); - }; - function visitNode(sf: SourceFile) { - // produce `class Foo { @Bar baz() {} }`; - const classDecl = factory.createClassDeclaration([], [], "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ - factory.createMethodDeclaration([factory.createDecorator(factory.createIdentifier("Bar"))], [], /**/ undefined, "baz", /**/ undefined, /**/ undefined, [], /**/ undefined, factory.createBlock([])) - ]); - return factory.updateSourceFile(sf, [classDecl]); - } + function transformAddDecoratedNode(_context: TransformationContext) { + return (sourceFile: SourceFile): SourceFile => { + return visitNode(sourceFile); + }; + function visitNode(sf: SourceFile) { + // produce `class Foo { @Bar baz() {} }`; + const classDecl = factory.createClassDeclaration([], [], "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ + factory.createMethodDeclaration([factory.createDecorator(factory.createIdentifier("Bar"))], [], /**/ undefined, "baz", /**/ undefined, /**/ undefined, [], /**/ undefined, factory.createBlock([])) + ]); + return factory.updateSourceFile(sf, [classDecl]); } - }); + } + }); - testBaseline("transformDeclarationFile", () => { - return baselineDeclarationTransform(`var oldName = undefined;`, { - transformers: { - afterDeclarations: [replaceIdentifiersNamedOldNameWithNewName] - }, - compilerOptions: { - newLine: NewLineKind.CarriageReturnLineFeed, - declaration: true - } - }); + testBaseline("transformDeclarationFile", () => { + return baselineDeclarationTransform(`var oldName = undefined;`, { + transformers: { + afterDeclarations: [replaceIdentifiersNamedOldNameWithNewName] + }, + compilerOptions: { + newLine: NewLineKind.CarriageReturnLineFeed, + declaration: true + } }); + }); - // https://github.com/microsoft/TypeScript/issues/33295 - testBaseline("transformParameterProperty", () => { - return transpileModule("", { - transformers: { - before: [transformAddParameterProperty], - }, - compilerOptions: { - target: ScriptTarget.ES5, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - - function transformAddParameterProperty(_context: TransformationContext) { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile); - }; - function visitNode(sf: SourceFile) { - // produce `class Foo { constructor(@Dec private x) {} }`; - // The decorator is required to trigger ts.ts transformations. - const classDecl = factory.createClassDeclaration([], [], "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ - factory.createConstructorDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, [ - factory.createParameterDeclaration(/*decorators*/ [factory.createDecorator(factory.createIdentifier("Dec"))], /*modifiers*/ [factory.createModifier(SyntaxKind.PrivateKeyword)], /*dotDotDotToken*/ undefined, "x")], factory.createBlock([])) - ]); - return factory.updateSourceFile(sf, [classDecl]); - } + // https://github.com/microsoft/TypeScript/issues/33295 + testBaseline("transformParameterProperty", () => { + return transpileModule("", { + transformers: { + before: [transformAddParameterProperty], + }, + compilerOptions: { + target: ScriptTarget.ES5, + newLine: NewLineKind.CarriageReturnLineFeed, } - }); + }).outputText; - function baselineDeclarationTransform(text: string, opts: TranspileOptions) { - const fs = vfs.createFromFileSystem(Harness.IO, /*caseSensitive*/ true, { documents: [new documents.TextDocument("/.src/index.ts", text)] }); - const host = new fakes.CompilerHost(fs, opts.compilerOptions); - const program = createProgram(["/.src/index.ts"], opts.compilerOptions!, host); - program.emit(program.getSourceFile("/.src/index.ts"), (p, s, bom) => host.writeFile(p, s, bom), /*cancellationToken*/ undefined, /*onlyDts*/ true, opts.transformers); - return fs.readFileSync("/.src/index.d.ts").toString(); + function transformAddParameterProperty(_context: TransformationContext) { + return (sourceFile: SourceFile): SourceFile => { + return visitNode(sourceFile); + }; + function visitNode(sf: SourceFile) { + // produce `class Foo { constructor(@Dec private x) {} }`; + // The decorator is required to trigger ts.ts transformations. + const classDecl = factory.createClassDeclaration([], [], "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ + factory.createConstructorDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, [ + factory.createParameterDeclaration(/*decorators*/ [factory.createDecorator(factory.createIdentifier("Dec"))], /*modifiers*/ [factory.createModifier(SyntaxKind.PrivateKeyword)], /*dotDotDotToken*/ undefined, "x")], factory.createBlock([])) + ]); + return factory.updateSourceFile(sf, [classDecl]); + } } + }); - function addSyntheticComment(nodeFilter: (node: Node) => boolean) { - return (context: TransformationContext) => { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile, rootTransform, isSourceFile); - }; - function rootTransform(node: T): VisitResult { - if (nodeFilter(node)) { - setEmitFlags(node, EmitFlags.NoLeadingComments); - setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "comment", pos: -1, end: -1, hasTrailingNewLine: true }]); - } - return visitEachChild(node, rootTransform, context); - } + function baselineDeclarationTransform(text: string, opts: TranspileOptions) { + const fs = vfs.createFromFileSystem(Harness.IO, /*caseSensitive*/ true, { documents: [new documents.TextDocument("/.src/index.ts", text)] }); + const host = new fakes.CompilerHost(fs, opts.compilerOptions); + const program = createProgram(["/.src/index.ts"], opts.compilerOptions!, host); + program.emit(program.getSourceFile("/.src/index.ts"), (p, s, bom) => host.writeFile(p, s, bom), /*cancellationToken*/ undefined, /*onlyDts*/ true, opts.transformers); + return fs.readFileSync("/.src/index.d.ts").toString(); + } + + function addSyntheticComment(nodeFilter: (node: Node) => boolean) { + return (context: TransformationContext) => { + return (sourceFile: SourceFile): SourceFile => { + return visitNode(sourceFile, rootTransform, isSourceFile); }; - } + function rootTransform(node: T): VisitResult { + if (nodeFilter(node)) { + setEmitFlags(node, EmitFlags.NoLeadingComments); + setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "comment", pos: -1, end: -1, hasTrailingNewLine: true }]); + } + return visitEachChild(node, rootTransform, context); + } + }; + } - // https://github.com/Microsoft/TypeScript/issues/24096 - testBaseline("transformAddCommentToArrowReturnValue", () => { - return transpileModule(`const foo = () => + // https://github.com/Microsoft/TypeScript/issues/24096 + testBaseline("transformAddCommentToArrowReturnValue", () => { + return transpileModule(`const foo = () => void 0 `, { - transformers: { - before: [addSyntheticComment(isVoidExpression)], - }, - compilerOptions: { - target: ScriptTarget.ES5, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [addSyntheticComment(isVoidExpression)], + }, + compilerOptions: { + target: ScriptTarget.ES5, + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - // https://github.com/Microsoft/TypeScript/issues/17594 - testBaseline("transformAddCommentToExportedVar", () => { - return transpileModule(`export const exportedDirectly = 1; + // https://github.com/Microsoft/TypeScript/issues/17594 + testBaseline("transformAddCommentToExportedVar", () => { + return transpileModule(`export const exportedDirectly = 1; const exportedSeparately = 2; export {exportedSeparately}; `, { - transformers: { - before: [addSyntheticComment(isVariableStatement)], - }, - compilerOptions: { - target: ScriptTarget.ES5, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [addSyntheticComment(isVariableStatement)], + }, + compilerOptions: { + target: ScriptTarget.ES5, + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - // https://github.com/Microsoft/TypeScript/issues/17594 - testBaseline("transformAddCommentToImport", () => { - return transpileModule(` + // https://github.com/Microsoft/TypeScript/issues/17594 + testBaseline("transformAddCommentToImport", () => { + return transpileModule(` // Previous comment on import. import {Value} from 'somewhere'; import * as X from 'somewhere'; @@ -452,19 +452,19 @@ export { /* specifier comment */ X, Y} from 'somewhere'; export * from 'somewhere'; export {Value}; `, { - transformers: { - before: [addSyntheticComment(n => isImportDeclaration(n) || isExportDeclaration(n) || isImportSpecifier(n) || isExportSpecifier(n))], - }, - compilerOptions: { - target: ScriptTarget.ES5, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [addSyntheticComment(n => isImportDeclaration(n) || isExportDeclaration(n) || isImportSpecifier(n) || isExportSpecifier(n))], + }, + compilerOptions: { + target: ScriptTarget.ES5, + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - // https://github.com/Microsoft/TypeScript/issues/17594 - testBaseline("transformAddCommentToProperties", () => { - return transpileModule(` + // https://github.com/Microsoft/TypeScript/issues/17594 + testBaseline("transformAddCommentToProperties", () => { + return transpileModule(` // class comment. class Clazz { // original comment 1. @@ -475,18 +475,18 @@ class Clazz { constructor(readonly field = 1) {} } `, { - transformers: { - before: [addSyntheticComment(n => isPropertyDeclaration(n) || isParameterPropertyDeclaration(n, n.parent) || isClassDeclaration(n) || isConstructorDeclaration(n))], - }, - compilerOptions: { - target: ScriptTarget.ES2015, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [addSyntheticComment(n => isPropertyDeclaration(n) || isParameterPropertyDeclaration(n, n.parent) || isClassDeclaration(n) || isConstructorDeclaration(n))], + }, + compilerOptions: { + target: ScriptTarget.ES2015, + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - testBaseline("transformAddCommentToNamespace", () => { - return transpileModule(` + testBaseline("transformAddCommentToNamespace", () => { + return transpileModule(` // namespace comment. namespace Foo { export const x = 1; @@ -496,109 +496,109 @@ namespace Foo { export const y = 1; } `, { - transformers: { - before: [addSyntheticComment(n => isModuleDeclaration(n))], - }, - compilerOptions: { - target: ScriptTarget.ES2015, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [addSyntheticComment(n => isModuleDeclaration(n))], + }, + compilerOptions: { + target: ScriptTarget.ES2015, + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - testBaseline("transformUpdateModuleMember", () => { - return transpileModule(` + testBaseline("transformUpdateModuleMember", () => { + return transpileModule(` module MyModule { const myVariable = 1; function foo(param: string) {} } `, { - transformers: { - before: [renameVariable], - }, - compilerOptions: { - target: ScriptTarget.ES2015, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - - function renameVariable(context: TransformationContext) { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile, rootTransform, isSourceFile); - }; - function rootTransform(node: T): Node { - if (isVariableDeclaration(node)) { - return factory.updateVariableDeclaration(node, factory.createIdentifier("newName"), /*exclamationToken*/ undefined, /*type*/ undefined, node.initializer); - } - return visitEachChild(node, rootTransform, context); - } + transformers: { + before: [renameVariable], + }, + compilerOptions: { + target: ScriptTarget.ES2015, + newLine: NewLineKind.CarriageReturnLineFeed, } - }); + }).outputText; - // https://github.com/Microsoft/TypeScript/issues/24709 - testBaseline("issue24709", () => { - const fs = vfs.createFromFileSystem(Harness.IO, /*caseSensitive*/ true); - const transformed = transform(createSourceFile("source.ts", "class X { echo(x: string) { return x; } }", ScriptTarget.ES3), [transformSourceFile]); - const transformedSourceFile = transformed.transformed[0]; - transformed.dispose(); - const host = new fakes.CompilerHost(fs); - host.getSourceFile = () => transformedSourceFile; - const program = createProgram(["source.ts"], { - target: ScriptTarget.ES3, - module: ModuleKind.None, - noLib: true - }, host); - program.emit(transformedSourceFile, (_p, s, b) => host.writeFile("source.js", s, b)); - return host.readFile("source.js")!.toString(); - - function transformSourceFile(context: TransformationContext) { - const visitor: Visitor = (node) => { - if (isMethodDeclaration(node)) { - return factory.updateMethodDeclaration( - node, - node.decorators, - node.modifiers, - node.asteriskToken, - factory.createIdentifier("foobar"), - node.questionToken, - node.typeParameters, - node.parameters, - node.type, - node.body, - ); - } - return visitEachChild(node, visitor, context); + function renameVariable(context: TransformationContext) { + return (sourceFile: SourceFile): SourceFile => { + return visitNode(sourceFile, rootTransform, isSourceFile); }; - return (node: SourceFile) => visitNode(node, visitor); - } - - }); + function rootTransform(node: T): Node { + if (isVariableDeclaration(node)) { + return factory.updateVariableDeclaration(node, factory.createIdentifier("newName"), /*exclamationToken*/ undefined, /*type*/ undefined, node.initializer); + } + return visitEachChild(node, rootTransform, context); + } + } + }); - testBaselineAndEvaluate("templateSpans", () => { - return transpileModule("const x = String.raw`\n\nhello`; exports.stringLength = x.trim().length;", { - compilerOptions: { - target: ScriptTarget.ESNext, - newLine: NewLineKind.CarriageReturnLineFeed, - }, - transformers: { - before: [transformSourceFile] + // https://github.com/Microsoft/TypeScript/issues/24709 + testBaseline("issue24709", () => { + const fs = vfs.createFromFileSystem(Harness.IO, /*caseSensitive*/ true); + const transformed = transform(createSourceFile("source.ts", "class X { echo(x: string) { return x; } }", ScriptTarget.ES3), [transformSourceFile]); + const transformedSourceFile = transformed.transformed[0]; + transformed.dispose(); + const host = new fakes.CompilerHost(fs); + host.getSourceFile = () => transformedSourceFile; + const program = createProgram(["source.ts"], { + target: ScriptTarget.ES3, + module: ModuleKind.None, + noLib: true + }, host); + program.emit(transformedSourceFile, (_p, s, b) => host.writeFile("source.js", s, b)); + return host.readFile("source.js")!.toString(); + + function transformSourceFile(context: TransformationContext) { + const visitor: Visitor = (node) => { + if (isMethodDeclaration(node)) { + return factory.updateMethodDeclaration( + node, + node.decorators, + node.modifiers, + node.asteriskToken, + factory.createIdentifier("foobar"), + node.questionToken, + node.typeParameters, + node.parameters, + node.type, + node.body, + ); } - }).outputText; + return visitEachChild(node, visitor, context); + }; + return (node: SourceFile) => visitNode(node, visitor); + } - function transformSourceFile(context: TransformationContext): Transformer { - function visitor(node: Node): VisitResult { - if (isNoSubstitutionTemplateLiteral(node)) { - return factory.createNoSubstitutionTemplateLiteral(node.text, node.rawText); - } - else { - return visitEachChild(node, visitor, context); - } + }); + + testBaselineAndEvaluate("templateSpans", () => { + return transpileModule("const x = String.raw`\n\nhello`; exports.stringLength = x.trim().length;", { + compilerOptions: { + target: ScriptTarget.ESNext, + newLine: NewLineKind.CarriageReturnLineFeed, + }, + transformers: { + before: [transformSourceFile] + } + }).outputText; + + function transformSourceFile(context: TransformationContext): Transformer { + function visitor(node: Node): VisitResult { + if (isNoSubstitutionTemplateLiteral(node)) { + return factory.createNoSubstitutionTemplateLiteral(node.text, node.rawText); + } + else { + return visitEachChild(node, visitor, context); } - return sourceFile => visitNode(sourceFile, visitor, isSourceFile); } - }, exports => { - assert.equal(exports.stringLength, 5); - }); + return sourceFile => visitNode(sourceFile, visitor, isSourceFile); + } + }, exports => { + assert.equal(exports.stringLength, 5); }); +}); } diff --git a/src/testRunner/unittests/tsbuild/amdModulesWithOut.ts b/src/testRunner/unittests/tsbuild/amdModulesWithOut.ts index a08365887de0a..0c681a01e65f8 100644 --- a/src/testRunner/unittests/tsbuild/amdModulesWithOut.ts +++ b/src/testRunner/unittests/tsbuild/amdModulesWithOut.ts @@ -1,136 +1,136 @@ namespace ts { - describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => { - let outFileFs: vfs.FileSystem; - const enum Project { lib, app } - function relName(path: string) { - return path.slice(1); - } - type Sources = [string, readonly string[]]; - const enum Source { config, ts } - const sources: [Sources, Sources] = [ +describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => { + let outFileFs: vfs.FileSystem; + const enum Project { lib, app } + function relName(path: string) { + return path.slice(1); + } + type Sources = [string, readonly string[]]; + const enum Source { config, ts } + const sources: [Sources, Sources] = [ + [ + "/src/lib/tsconfig.json", [ - "/src/lib/tsconfig.json", - [ - "/src/lib/file0.ts", - "/src/lib/file1.ts", - "/src/lib/file2.ts", - "/src/lib/global.ts", - ] - ], + "/src/lib/file0.ts", + "/src/lib/file1.ts", + "/src/lib/file2.ts", + "/src/lib/global.ts", + ] + ], + [ + "/src/app/tsconfig.json", [ - "/src/app/tsconfig.json", - [ - "/src/app/file3.ts", - "/src/app/file4.ts" - ] + "/src/app/file3.ts", + "/src/app/file4.ts" ] - ]; - before(() => { - outFileFs = loadProjectFromDisk("tests/projects/amdModulesWithOut"); - }); - after(() => { - outFileFs = undefined!; - }); + ] + ]; + before(() => { + outFileFs = loadProjectFromDisk("tests/projects/amdModulesWithOut"); + }); + after(() => { + outFileFs = undefined!; + }); - interface VerifyOutFileScenarioInput { - subScenario: string; - modifyFs?: (fs: vfs.FileSystem) => void; - modifyAgainFs?: (fs: vfs.FileSystem) => void; - } + interface VerifyOutFileScenarioInput { + subScenario: string; + modifyFs?: (fs: vfs.FileSystem) => void; + modifyAgainFs?: (fs: vfs.FileSystem) => void; + } - function verifyOutFileScenario({ + function verifyOutFileScenario({ + subScenario, + modifyFs, + modifyAgainFs + }: VerifyOutFileScenarioInput) { + verifyTscIncrementalEdits({ + scenario: "amdModulesWithOut", subScenario, + fs: () => outFileFs, + commandLineArgs: ["--b", "/src/app", "--verbose"], + baselineSourceMap: true, modifyFs, - modifyAgainFs - }: VerifyOutFileScenarioInput) { - verifyTscIncrementalEdits({ - scenario: "amdModulesWithOut", - subScenario, - fs: () => outFileFs, - commandLineArgs: ["--b", "/src/app", "--verbose"], - baselineSourceMap: true, - modifyFs, - incrementalScenarios: [ - { - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => appendText(fs, relName(sources[Project.lib][Source.ts][1]), "console.log(x);") - }, - ...(modifyAgainFs ? [{ - buildKind: BuildKind.IncrementalHeadersChange, - modifyFs: modifyAgainFs - }] : emptyArray), - ] - }); - } + incrementalScenarios: [ + { + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => appendText(fs, relName(sources[Project.lib][Source.ts][1]), "console.log(x);") + }, + ...(modifyAgainFs ? [{ + buildKind: BuildKind.IncrementalHeadersChange, + modifyFs: modifyAgainFs + }] : emptyArray), + ] + }); + } - describe("Prepend output with .tsbuildinfo", () => { - verifyOutFileScenario({ - subScenario: "modules and globals mixed in amd", - }); + describe("Prepend output with .tsbuildinfo", () => { + verifyOutFileScenario({ + subScenario: "modules and globals mixed in amd", + }); - // Prologues - describe("Prologues", () => { - verifyOutFileScenario({ - subScenario: "multiple prologues in all projects", - modifyFs: fs => { - enableStrict(fs, sources[Project.lib][Source.config]); - addTestPrologue(fs, sources[Project.lib][Source.ts][0], `"myPrologue"`); - addTestPrologue(fs, sources[Project.lib][Source.ts][2], `"myPrologueFile"`); - addTestPrologue(fs, sources[Project.lib][Source.ts][3], `"myPrologue3"`); - enableStrict(fs, sources[Project.app][Source.config]); - addTestPrologue(fs, sources[Project.app][Source.ts][0], `"myPrologue"`); - addTestPrologue(fs, sources[Project.app][Source.ts][1], `"myPrologue2";`); - }, - modifyAgainFs: fs => addTestPrologue(fs, relName(sources[Project.lib][Source.ts][1]), `"myPrologue5"`) - }); + // Prologues + describe("Prologues", () => { + verifyOutFileScenario({ + subScenario: "multiple prologues in all projects", + modifyFs: fs => { + enableStrict(fs, sources[Project.lib][Source.config]); + addTestPrologue(fs, sources[Project.lib][Source.ts][0], `"myPrologue"`); + addTestPrologue(fs, sources[Project.lib][Source.ts][2], `"myPrologueFile"`); + addTestPrologue(fs, sources[Project.lib][Source.ts][3], `"myPrologue3"`); + enableStrict(fs, sources[Project.app][Source.config]); + addTestPrologue(fs, sources[Project.app][Source.ts][0], `"myPrologue"`); + addTestPrologue(fs, sources[Project.app][Source.ts][1], `"myPrologue2";`); + }, + modifyAgainFs: fs => addTestPrologue(fs, relName(sources[Project.lib][Source.ts][1]), `"myPrologue5"`) }); + }); - // Shebang - describe("Shebang", () => { - // changes declaration because its emitted in .d.ts file - verifyOutFileScenario({ - subScenario: "shebang in all projects", - modifyFs: fs => { - addShebang(fs, "lib", "file0"); - addShebang(fs, "lib", "file1"); - addShebang(fs, "app", "file3"); - }, - }); + // Shebang + describe("Shebang", () => { + // changes declaration because its emitted in .d.ts file + verifyOutFileScenario({ + subScenario: "shebang in all projects", + modifyFs: fs => { + addShebang(fs, "lib", "file0"); + addShebang(fs, "lib", "file1"); + addShebang(fs, "app", "file3"); + }, }); + }); - // emitHelpers - describe("emitHelpers", () => { - verifyOutFileScenario({ - subScenario: "multiple emitHelpers in all projects", - modifyFs: fs => { - addSpread(fs, "lib", "file0"); - addRest(fs, "lib", "file1"); - addRest(fs, "app", "file3"); - addSpread(fs, "app", "file4"); - }, - modifyAgainFs: fs => removeRest(fs, "lib", "file1") - }); + // emitHelpers + describe("emitHelpers", () => { + verifyOutFileScenario({ + subScenario: "multiple emitHelpers in all projects", + modifyFs: fs => { + addSpread(fs, "lib", "file0"); + addRest(fs, "lib", "file1"); + addRest(fs, "app", "file3"); + addSpread(fs, "app", "file4"); + }, + modifyAgainFs: fs => removeRest(fs, "lib", "file1") }); + }); - // triple slash refs - describe("triple slash refs", () => { - // changes declaration because its emitted in .d.ts file - verifyOutFileScenario({ - subScenario: "triple slash refs in all projects", - modifyFs: fs => { - addTripleSlashRef(fs, "lib", "file0"); - addTripleSlashRef(fs, "app", "file4"); - } - }); + // triple slash refs + describe("triple slash refs", () => { + // changes declaration because its emitted in .d.ts file + verifyOutFileScenario({ + subScenario: "triple slash refs in all projects", + modifyFs: fs => { + addTripleSlashRef(fs, "lib", "file0"); + addTripleSlashRef(fs, "app", "file4"); + } }); + }); - describe("stripInternal", () => { - function stripInternalScenario(fs: vfs.FileSystem) { - const internal = "/*@internal*/"; - replaceText(fs, sources[Project.app][Source.config], `"composite": true,`, `"composite": true, + describe("stripInternal", () => { + function stripInternalScenario(fs: vfs.FileSystem) { + const internal = "/*@internal*/"; + replaceText(fs, sources[Project.app][Source.config], `"composite": true,`, `"composite": true, "stripInternal": true,`); - replaceText(fs, sources[Project.lib][Source.ts][0], "const", `${internal} const`); - appendText(fs, sources[Project.lib][Source.ts][1], ` + replaceText(fs, sources[Project.lib][Source.ts][0], "const", `${internal} const`); + appendText(fs, sources[Project.lib][Source.ts][1], ` export class normalC { ${internal} constructor() { } ${internal} prop: string; @@ -156,33 +156,33 @@ ${internal} export import internalImport = internalNamespace.someClass; ${internal} export type internalType = internalC; ${internal} export const internalConst = 10; ${internal} export enum internalEnum { a, b, c }`); - } + } - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "stripInternal", - modifyFs: stripInternalScenario, - modifyAgainFs: fs => replaceText(fs, sources[Project.lib][Source.ts][1], `export const`, `/*@internal*/ export const`), - }); + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "stripInternal", + modifyFs: stripInternalScenario, + modifyAgainFs: fs => replaceText(fs, sources[Project.lib][Source.ts][1], `export const`, `/*@internal*/ export const`), }); + }); - describe("when the module resolution finds original source file", () => { - function modifyFs(fs: vfs.FileSystem) { - // Make lib to output to parent dir - replaceText(fs, sources[Project.lib][Source.config], `"outFile": "module.js"`, `"outFile": "../module.js", "rootDir": "../"`); - // Change reference to file1 module to resolve to lib/file1 - replaceText(fs, sources[Project.app][Source.ts][0], "file1", "lib/file1"); - } + describe("when the module resolution finds original source file", () => { + function modifyFs(fs: vfs.FileSystem) { + // Make lib to output to parent dir + replaceText(fs, sources[Project.lib][Source.config], `"outFile": "module.js"`, `"outFile": "../module.js", "rootDir": "../"`); + // Change reference to file1 module to resolve to lib/file1 + replaceText(fs, sources[Project.app][Source.ts][0], "file1", "lib/file1"); + } - verifyTsc({ - scenario: "amdModulesWithOut", - subScenario: "when the module resolution finds original source file", - fs: () => outFileFs, - commandLineArgs: ["-b", "/src/app", "--verbose"], - modifyFs, - baselineSourceMap: true, - }); + verifyTsc({ + scenario: "amdModulesWithOut", + subScenario: "when the module resolution finds original source file", + fs: () => outFileFs, + commandLineArgs: ["-b", "/src/app", "--verbose"], + modifyFs, + baselineSourceMap: true, }); }); }); +}); } diff --git a/src/testRunner/unittests/tsbuild/clean.ts b/src/testRunner/unittests/tsbuild/clean.ts index 90bb298529028..0be3e3c5a2703 100644 --- a/src/testRunner/unittests/tsbuild/clean.ts +++ b/src/testRunner/unittests/tsbuild/clean.ts @@ -1,16 +1,16 @@ namespace ts { - describe("unittests:: tsbuild - clean", () => { - verifyTsc({ - scenario: "clean", - subScenario: `file name and output name clashing`, - commandLineArgs: ["--b", "/src/tsconfig.json", "-clean"], - fs: () => loadProjectFromFiles({ - "/src/index.js": "", - "/src/bar.ts": "", - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { allowJs: true }, - }), +describe("unittests:: tsbuild - clean", () => { + verifyTsc({ + scenario: "clean", + subScenario: `file name and output name clashing`, + commandLineArgs: ["--b", "/src/tsconfig.json", "-clean"], + fs: () => loadProjectFromFiles({ + "/src/index.js": "", + "/src/bar.ts": "", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { allowJs: true }, }), - }); + }), }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/tsbuild/configFileErrors.ts b/src/testRunner/unittests/tsbuild/configFileErrors.ts index 5d2229588709b..fa5e3c034805a 100644 --- a/src/testRunner/unittests/tsbuild/configFileErrors.ts +++ b/src/testRunner/unittests/tsbuild/configFileErrors.ts @@ -1,21 +1,21 @@ namespace ts { - describe("unittests:: tsbuild:: configFileErrors:: when tsconfig extends the missing file", () => { - verifyTsc({ - scenario: "configFileErrors", - subScenario: "when tsconfig extends the missing file", - fs: () => loadProjectFromDisk("tests/projects/missingExtendedConfig"), - commandLineArgs: ["--b", "/src/tsconfig.json"], - }); +describe("unittests:: tsbuild:: configFileErrors:: when tsconfig extends the missing file", () => { + verifyTsc({ + scenario: "configFileErrors", + subScenario: "when tsconfig extends the missing file", + fs: () => loadProjectFromDisk("tests/projects/missingExtendedConfig"), + commandLineArgs: ["--b", "/src/tsconfig.json"], }); +}); - describe("unittests:: tsbuild:: configFileErrors:: reports syntax errors in config file", () => { - verifyTscIncrementalEdits({ - scenario: "configFileErrors", - subScenario: "reports syntax errors in config file", - fs: () => loadProjectFromFiles({ - "/src/a.ts": "export function foo() { }", - "/src/b.ts": "export function bar() { }", - "/src/tsconfig.json": Utils.dedent` +describe("unittests:: tsbuild:: configFileErrors:: reports syntax errors in config file", () => { + verifyTscIncrementalEdits({ + scenario: "configFileErrors", + subScenario: "reports syntax errors in config file", + fs: () => loadProjectFromFiles({ + "/src/a.ts": "export function foo() { }", + "/src/b.ts": "export function bar() { }", + "/src/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true, @@ -25,33 +25,33 @@ namespace ts { "b.ts" ] }` - }), - commandLineArgs: ["--b", "/src/tsconfig.json"], - incrementalScenarios: [ - { - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => replaceText(fs, "/src/tsconfig.json", ",", `, + }), + commandLineArgs: ["--b", "/src/tsconfig.json"], + incrementalScenarios: [ + { + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => replaceText(fs, "/src/tsconfig.json", ",", `, "declaration": true,`), - subScenario: "reports syntax errors after change to config file" - }, - { - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => appendText(fs, "/src/a.ts", "export function fooBar() { }"), - subScenario: "reports syntax errors after change to ts file" - }, - noChangeRun, - { - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => fs.writeFileSync( - "/src/tsconfig.json", - JSON.stringify({ - compilerOptions: { composite: true, declaration: true }, - files: ["a.ts", "b.ts"] - }) - ), - subScenario: "builds after fixing config file errors" - }, - ] - }); + subScenario: "reports syntax errors after change to config file" + }, + { + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => appendText(fs, "/src/a.ts", "export function fooBar() { }"), + subScenario: "reports syntax errors after change to ts file" + }, + noChangeRun, + { + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => fs.writeFileSync( + "/src/tsconfig.json", + JSON.stringify({ + compilerOptions: { composite: true, declaration: true }, + files: ["a.ts", "b.ts"] + }) + ), + subScenario: "builds after fixing config file errors" + }, + ] }); +}); } diff --git a/src/testRunner/unittests/tsbuild/configFileExtends.ts b/src/testRunner/unittests/tsbuild/configFileExtends.ts index b00a213b9d3fc..f7c8029380975 100644 --- a/src/testRunner/unittests/tsbuild/configFileExtends.ts +++ b/src/testRunner/unittests/tsbuild/configFileExtends.ts @@ -1,52 +1,52 @@ namespace ts { - describe("unittests:: tsbuild:: configFileExtends:: when tsconfig extends another config", () => { - function getConfigExtendsWithIncludeFs() { - return loadProjectFromFiles({ - "/src/tsconfig.json": JSON.stringify({ - references: [ - { path: "./shared/tsconfig.json" }, - { path: "./webpack/tsconfig.json" } - ], - files: [] - }), - "/src/shared/tsconfig-base.json": JSON.stringify({ - include: ["./typings-base/"] - }), - "/src/shared/typings-base/globals.d.ts": `type Unrestricted = any;`, - "/src/shared/tsconfig.json": JSON.stringify({ - extends: "./tsconfig-base.json", - compilerOptions: { - composite: true, - outDir: "../target-tsc-build/", - rootDir: ".." - }, - files: ["./index.ts"] - }), - "/src/shared/index.ts": `export const a: Unrestricted = 1;`, - "/src/webpack/tsconfig.json": JSON.stringify({ - extends: "../shared/tsconfig-base.json", - compilerOptions: { - composite: true, - outDir: "../target-tsc-build/", - rootDir: ".." - }, - files: ["./index.ts"], - references: [{ path: "../shared/tsconfig.json" }] - }), - "/src/webpack/index.ts": `export const b: Unrestricted = 1;`, - }); - } - verifyTsc({ - scenario: "configFileExtends", - subScenario: "when building solution with projects extends config with include", - fs: getConfigExtendsWithIncludeFs, - commandLineArgs: ["--b", "/src/tsconfig.json", "--v", "--listFiles"], - }); - verifyTsc({ - scenario: "configFileExtends", - subScenario: "when building project uses reference and both extend config with include", - fs: getConfigExtendsWithIncludeFs, - commandLineArgs: ["--b", "/src/webpack/tsconfig.json", "--v", "--listFiles"], +describe("unittests:: tsbuild:: configFileExtends:: when tsconfig extends another config", () => { + function getConfigExtendsWithIncludeFs() { + return loadProjectFromFiles({ + "/src/tsconfig.json": JSON.stringify({ + references: [ + { path: "./shared/tsconfig.json" }, + { path: "./webpack/tsconfig.json" } + ], + files: [] + }), + "/src/shared/tsconfig-base.json": JSON.stringify({ + include: ["./typings-base/"] + }), + "/src/shared/typings-base/globals.d.ts": `type Unrestricted = any;`, + "/src/shared/tsconfig.json": JSON.stringify({ + extends: "./tsconfig-base.json", + compilerOptions: { + composite: true, + outDir: "../target-tsc-build/", + rootDir: ".." + }, + files: ["./index.ts"] + }), + "/src/shared/index.ts": `export const a: Unrestricted = 1;`, + "/src/webpack/tsconfig.json": JSON.stringify({ + extends: "../shared/tsconfig-base.json", + compilerOptions: { + composite: true, + outDir: "../target-tsc-build/", + rootDir: ".." + }, + files: ["./index.ts"], + references: [{ path: "../shared/tsconfig.json" }] + }), + "/src/webpack/index.ts": `export const b: Unrestricted = 1;`, }); + } + verifyTsc({ + scenario: "configFileExtends", + subScenario: "when building solution with projects extends config with include", + fs: getConfigExtendsWithIncludeFs, + commandLineArgs: ["--b", "/src/tsconfig.json", "--v", "--listFiles"], + }); + verifyTsc({ + scenario: "configFileExtends", + subScenario: "when building project uses reference and both extend config with include", + fs: getConfigExtendsWithIncludeFs, + commandLineArgs: ["--b", "/src/webpack/tsconfig.json", "--v", "--listFiles"], }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/tsbuild/containerOnlyReferenced.ts b/src/testRunner/unittests/tsbuild/containerOnlyReferenced.ts index 8ccfbe5fe4742..e13c53bc6a7a2 100644 --- a/src/testRunner/unittests/tsbuild/containerOnlyReferenced.ts +++ b/src/testRunner/unittests/tsbuild/containerOnlyReferenced.ts @@ -1,11 +1,11 @@ namespace ts { - describe("unittests:: tsbuild:: when containerOnly project is referenced", () => { - verifyTscSerializedIncrementalEdits({ - scenario: "containerOnlyReferenced", - subScenario: "verify that subsequent builds after initial build doesnt build anything", - fs: () => loadProjectFromDisk("tests/projects/containerOnlyReferenced"), - commandLineArgs: ["--b", "/src", "--verbose"], - incrementalScenarios: noChangeOnlyRuns - }); +describe("unittests:: tsbuild:: when containerOnly project is referenced", () => { + verifyTscSerializedIncrementalEdits({ + scenario: "containerOnlyReferenced", + subScenario: "verify that subsequent builds after initial build doesnt build anything", + fs: () => loadProjectFromDisk("tests/projects/containerOnlyReferenced"), + commandLineArgs: ["--b", "/src", "--verbose"], + incrementalScenarios: noChangeOnlyRuns }); +}); } diff --git a/src/testRunner/unittests/tsbuild/declarationEmit.ts b/src/testRunner/unittests/tsbuild/declarationEmit.ts index 122912e026d8c..32f2ded89084a 100644 --- a/src/testRunner/unittests/tsbuild/declarationEmit.ts +++ b/src/testRunner/unittests/tsbuild/declarationEmit.ts @@ -1,39 +1,39 @@ namespace ts { - describe("unittests:: tsbuild:: declarationEmit", () => { - function getFiles(): vfs.FileSet { - return { - "/src/solution/tsconfig.base.json": JSON.stringify({ - compilerOptions: { - rootDir: "./", - outDir: "lib" - } - }), - "/src/solution/tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true }, - references: [{ path: "./src" }], - include: [] - }), - "/src/solution/src/tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true }, - references: [{ path: "./subProject" }, { path: "./subProject2" }], - include: [] - }), - "/src/solution/src/subProject/tsconfig.json": JSON.stringify({ - extends: "../../tsconfig.base.json", - compilerOptions: { composite: true }, - references: [{ path: "../common" }], - include: ["./index.ts"] - }), - "/src/solution/src/subProject/index.ts": Utils.dedent` +describe("unittests:: tsbuild:: declarationEmit", () => { + function getFiles(): vfs.FileSet { + return { + "/src/solution/tsconfig.base.json": JSON.stringify({ + compilerOptions: { + rootDir: "./", + outDir: "lib" + } + }), + "/src/solution/tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true }, + references: [{ path: "./src" }], + include: [] + }), + "/src/solution/src/tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true }, + references: [{ path: "./subProject" }, { path: "./subProject2" }], + include: [] + }), + "/src/solution/src/subProject/tsconfig.json": JSON.stringify({ + extends: "../../tsconfig.base.json", + compilerOptions: { composite: true }, + references: [{ path: "../common" }], + include: ["./index.ts"] + }), + "/src/solution/src/subProject/index.ts": Utils.dedent` import { Nominal } from '../common/nominal'; export type MyNominal = Nominal;`, - "/src/solution/src/subProject2/tsconfig.json": JSON.stringify({ - extends: "../../tsconfig.base.json", - compilerOptions: { composite: true }, - references: [{ path: "../subProject" }], - include: ["./index.ts"] - }), - "/src/solution/src/subProject2/index.ts": Utils.dedent` + "/src/solution/src/subProject2/tsconfig.json": JSON.stringify({ + extends: "../../tsconfig.base.json", + compilerOptions: { composite: true }, + references: [{ path: "../subProject" }], + include: ["./index.ts"] + }), + "/src/solution/src/subProject2/index.ts": Utils.dedent` import { MyNominal } from '../subProject/index'; const variable = { key: 'value' as MyNominal, @@ -41,78 +41,78 @@ const variable = { export function getVar(): keyof typeof variable { return 'key'; }`, - "/src/solution/src/common/tsconfig.json": JSON.stringify({ - extends: "../../tsconfig.base.json", - compilerOptions: { composite: true }, - include: ["./nominal.ts"] - }), - "/src/solution/src/common/nominal.ts": Utils.dedent` + "/src/solution/src/common/tsconfig.json": JSON.stringify({ + extends: "../../tsconfig.base.json", + compilerOptions: { composite: true }, + include: ["./nominal.ts"] + }), + "/src/solution/src/common/nominal.ts": Utils.dedent` /// export declare type Nominal = MyNominal;`, - "/src/solution/src/common/types.d.ts": Utils.dedent` + "/src/solution/src/common/types.d.ts": Utils.dedent` declare type MyNominal = T & { specialKey: Name; };`, - }; - } - verifyTsc({ - scenario: "declarationEmit", - subScenario: "when declaration file is referenced through triple slash", - fs: () => loadProjectFromFiles(getFiles()), - commandLineArgs: ["--b", "/src/solution/tsconfig.json", "--verbose"] - }); + }; + } + verifyTsc({ + scenario: "declarationEmit", + subScenario: "when declaration file is referenced through triple slash", + fs: () => loadProjectFromFiles(getFiles()), + commandLineArgs: ["--b", "/src/solution/tsconfig.json", "--verbose"] + }); - verifyTsc({ - scenario: "declarationEmit", - subScenario: "when declaration file is referenced through triple slash but uses no references", - fs: () => loadProjectFromFiles({ - ...getFiles(), - "/src/solution/tsconfig.json": JSON.stringify({ - extends: "./tsconfig.base.json", - compilerOptions: { composite: true }, - include: ["./src/**/*.ts"] - }), + verifyTsc({ + scenario: "declarationEmit", + subScenario: "when declaration file is referenced through triple slash but uses no references", + fs: () => loadProjectFromFiles({ + ...getFiles(), + "/src/solution/tsconfig.json": JSON.stringify({ + extends: "./tsconfig.base.json", + compilerOptions: { composite: true }, + include: ["./src/**/*.ts"] }), - commandLineArgs: ["--b", "/src/solution/tsconfig.json", "--verbose"] - }); + }), + commandLineArgs: ["--b", "/src/solution/tsconfig.json", "--verbose"] + }); - verifyTsc({ - scenario: "declarationEmit", - subScenario: "when declaration file used inferred type from referenced project", - fs: () => loadProjectFromFiles({ - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { - composite: true, - baseUrl: ".", - paths: { "@fluentui/*": ["packages/*/src"] } - } - }), - "/src/packages/pkg1/src/index.ts": Utils.dedent` + verifyTsc({ + scenario: "declarationEmit", + subScenario: "when declaration file used inferred type from referenced project", + fs: () => loadProjectFromFiles({ + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + composite: true, + baseUrl: ".", + paths: { "@fluentui/*": ["packages/*/src"] } + } + }), + "/src/packages/pkg1/src/index.ts": Utils.dedent` export interface IThing { a: string; } export interface IThings { thing1: IThing; }`, - "/src/packages/pkg1/tsconfig.json": JSON.stringify({ - extends: "../../tsconfig", - compilerOptions: { outDir: "lib" }, - include: ["src"] - }), - "/src/packages/pkg2/src/index.ts": Utils.dedent` + "/src/packages/pkg1/tsconfig.json": JSON.stringify({ + extends: "../../tsconfig", + compilerOptions: { outDir: "lib" }, + include: ["src"] + }), + "/src/packages/pkg2/src/index.ts": Utils.dedent` import { IThings } from '@fluentui/pkg1'; export function fn4() { const a: IThings = { thing1: { a: 'b' } }; return a.thing1; }`, - "/src/packages/pkg2/tsconfig.json": JSON.stringify({ - extends: "../../tsconfig", - compilerOptions: { outDir: "lib" }, - include: ["src"], - references: [{ path: "../pkg1" }] - }), + "/src/packages/pkg2/tsconfig.json": JSON.stringify({ + extends: "../../tsconfig", + compilerOptions: { outDir: "lib" }, + include: ["src"], + references: [{ path: "../pkg1" }] }), - commandLineArgs: ["--b", "/src/packages/pkg2/tsconfig.json", "--verbose"] - }); + }), + commandLineArgs: ["--b", "/src/packages/pkg2/tsconfig.json", "--verbose"] }); +}); } diff --git a/src/testRunner/unittests/tsbuild/demo.ts b/src/testRunner/unittests/tsbuild/demo.ts index 18bc1eea89a3b..a53c1935b213a 100644 --- a/src/testRunner/unittests/tsbuild/demo.ts +++ b/src/testRunner/unittests/tsbuild/demo.ts @@ -1,49 +1,49 @@ namespace ts { - describe("unittests:: tsbuild:: on demo project", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/demo"); - }); +describe("unittests:: tsbuild:: on demo project", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = loadProjectFromDisk("tests/projects/demo"); + }); - after(() => { - projFs = undefined!; // Release the contents - }); + after(() => { + projFs = undefined!; // Release the contents + }); - verifyTsc({ - scenario: "demo", - subScenario: "in master branch with everything setup correctly and reports no error", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"] - }); + verifyTsc({ + scenario: "demo", + subScenario: "in master branch with everything setup correctly and reports no error", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"] + }); - verifyTsc({ - scenario: "demo", - subScenario: "in circular branch reports the error about it by stopping build", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"], - modifyFs: fs => replaceText( - fs, - "/src/core/tsconfig.json", - "}", - `}, + verifyTsc({ + scenario: "demo", + subScenario: "in circular branch reports the error about it by stopping build", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"], + modifyFs: fs => replaceText( + fs, + "/src/core/tsconfig.json", + "}", + `}, "references": [ { "path": "../zoo" } ]` - ) - }); - verifyTsc({ - scenario: "demo", - subScenario: "in bad-ref branch reports the error about files not in rootDir at the import location", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"], - modifyFs: fs => prependText( - fs, - "/src/core/utilities.ts", - `import * as A from '../animals'; + ) + }); + verifyTsc({ + scenario: "demo", + subScenario: "in bad-ref branch reports the error about files not in rootDir at the import location", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"], + modifyFs: fs => prependText( + fs, + "/src/core/utilities.ts", + `import * as A from '../animals'; ` - ) - }); + ) }); +}); } diff --git a/src/testRunner/unittests/tsbuild/emitDeclarationOnly.ts b/src/testRunner/unittests/tsbuild/emitDeclarationOnly.ts index c673cd11bcd6a..4cf1b12e14124 100644 --- a/src/testRunner/unittests/tsbuild/emitDeclarationOnly.ts +++ b/src/testRunner/unittests/tsbuild/emitDeclarationOnly.ts @@ -1,53 +1,53 @@ namespace ts { - describe("unittests:: tsbuild:: on project with emitDeclarationOnly set to true", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/emitDeclarationOnly"); - }); - after(() => { - projFs = undefined!; - }); - - function verifyEmitDeclarationOnly(disableMap?: true) { - verifyTscSerializedIncrementalEdits({ - subScenario: `only dts output in circular import project with emitDeclarationOnly${disableMap ? "" : " and declarationMap"}`, - fs: () => projFs, - scenario: "emitDeclarationOnly", - commandLineArgs: ["--b", "/src", "--verbose"], - modifyFs: disableMap ? - (fs => replaceText(fs, "/src/tsconfig.json", `"declarationMap": true,`, "")) : - undefined, - incrementalScenarios: [{ - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"), - }], - }); - } - verifyEmitDeclarationOnly(); - verifyEmitDeclarationOnly(/*disableMap*/ true); +describe("unittests:: tsbuild:: on project with emitDeclarationOnly set to true", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = loadProjectFromDisk("tests/projects/emitDeclarationOnly"); + }); + after(() => { + projFs = undefined!; + }); + function verifyEmitDeclarationOnly(disableMap?: true) { verifyTscSerializedIncrementalEdits({ - subScenario: `only dts output in non circular imports project with emitDeclarationOnly`, + subScenario: `only dts output in circular import project with emitDeclarationOnly${disableMap ? "" : " and declarationMap"}`, fs: () => projFs, scenario: "emitDeclarationOnly", commandLineArgs: ["--b", "/src", "--verbose"], - modifyFs: fs => { - fs.rimrafSync("/src/src/index.ts"); - replaceText(fs, "/src/src/a.ts", `import { B } from "./b";`, `export class B { prop = "hello"; }`); - }, - incrementalScenarios: [ - { - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => replaceText(fs, "/src/src/a.ts", "export interface A {", `class C { } + modifyFs: disableMap ? + (fs => replaceText(fs, "/src/tsconfig.json", `"declarationMap": true,`, "")) : + undefined, + incrementalScenarios: [{ + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"), + }], + }); + } + verifyEmitDeclarationOnly(); + verifyEmitDeclarationOnly(/*disableMap*/ true); + + verifyTscSerializedIncrementalEdits({ + subScenario: `only dts output in non circular imports project with emitDeclarationOnly`, + fs: () => projFs, + scenario: "emitDeclarationOnly", + commandLineArgs: ["--b", "/src", "--verbose"], + modifyFs: fs => { + fs.rimrafSync("/src/src/index.ts"); + replaceText(fs, "/src/src/a.ts", `import { B } from "./b";`, `export class B { prop = "hello"; }`); + }, + incrementalScenarios: [ + { + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => replaceText(fs, "/src/src/a.ts", "export interface A {", `class C { } export interface A {`), - }, - { - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"), + }, + { + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"), - }, - ], - }); + }, + ], }); +}); } diff --git a/src/testRunner/unittests/tsbuild/emptyFiles.ts b/src/testRunner/unittests/tsbuild/emptyFiles.ts index ffd9429a34411..43b2967648b5a 100644 --- a/src/testRunner/unittests/tsbuild/emptyFiles.ts +++ b/src/testRunner/unittests/tsbuild/emptyFiles.ts @@ -1,25 +1,25 @@ namespace ts { - describe("unittests:: tsbuild - empty files option in tsconfig", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/empty-files"); - }); - after(() => { - projFs = undefined!; - }); +describe("unittests:: tsbuild - empty files option in tsconfig", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = loadProjectFromDisk("tests/projects/empty-files"); + }); + after(() => { + projFs = undefined!; + }); - verifyTsc({ - scenario: "emptyFiles", - subScenario: "has empty files diagnostic when files is empty and no references are provided", - fs: () => projFs, - commandLineArgs: ["--b", "/src/no-references"], - }); + verifyTsc({ + scenario: "emptyFiles", + subScenario: "has empty files diagnostic when files is empty and no references are provided", + fs: () => projFs, + commandLineArgs: ["--b", "/src/no-references"], + }); - verifyTsc({ - scenario: "emptyFiles", - subScenario: "does not have empty files diagnostic when files is empty and references are provided", - fs: () => projFs, - commandLineArgs: ["--b", "/src/with-references"], - }); + verifyTsc({ + scenario: "emptyFiles", + subScenario: "does not have empty files diagnostic when files is empty and references are provided", + fs: () => projFs, + commandLineArgs: ["--b", "/src/with-references"], }); +}); } diff --git a/src/testRunner/unittests/tsbuild/exitCodeOnBogusFile.ts b/src/testRunner/unittests/tsbuild/exitCodeOnBogusFile.ts index 487671dfae147..b4132ca0ab620 100644 --- a/src/testRunner/unittests/tsbuild/exitCodeOnBogusFile.ts +++ b/src/testRunner/unittests/tsbuild/exitCodeOnBogusFile.ts @@ -1,11 +1,11 @@ namespace ts { - // https://github.com/microsoft/TypeScript/issues/33849 - describe("unittests:: tsbuild:: exitCodeOnBogusFile:: test exit code", () => { - verifyTsc({ - scenario: "exitCodeOnBogusFile", - subScenario: `test exit code`, - fs: () => loadProjectFromFiles({}), - commandLineArgs: ["-b", "bogus.json"] - }); +// https://github.com/microsoft/TypeScript/issues/33849 +describe("unittests:: tsbuild:: exitCodeOnBogusFile:: test exit code", () => { + verifyTsc({ + scenario: "exitCodeOnBogusFile", + subScenario: `test exit code`, + fs: () => loadProjectFromFiles({}), + commandLineArgs: ["-b", "bogus.json"] }); +}); } diff --git a/src/testRunner/unittests/tsbuild/graphOrdering.ts b/src/testRunner/unittests/tsbuild/graphOrdering.ts index 79ddd80d67bde..17ab9d05680d2 100644 --- a/src/testRunner/unittests/tsbuild/graphOrdering.ts +++ b/src/testRunner/unittests/tsbuild/graphOrdering.ts @@ -1,91 +1,91 @@ namespace ts { - describe("unittests:: tsbuild - graph-ordering", () => { - let host: fakes.SolutionBuilderHost | undefined; - const deps: [string, string][] = [ - ["A", "B"], - ["B", "C"], - ["A", "C"], - ["B", "D"], - ["C", "D"], - ["C", "E"], - ["F", "E"], - ["H", "I"], - ["I", "J"], - ["J", "H"], - ["J", "E"] - ]; +describe("unittests:: tsbuild - graph-ordering", () => { + let host: fakes.SolutionBuilderHost | undefined; + const deps: [string, string][] = [ + ["A", "B"], + ["B", "C"], + ["A", "C"], + ["B", "D"], + ["C", "D"], + ["C", "E"], + ["F", "E"], + ["H", "I"], + ["I", "J"], + ["J", "H"], + ["J", "E"] + ]; - before(() => { - const fs = new vfs.FileSystem(false); - host = fakes.SolutionBuilderHost.create(fs); - writeProjects(fs, ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"], deps); - }); + before(() => { + const fs = new vfs.FileSystem(false); + host = fakes.SolutionBuilderHost.create(fs); + writeProjects(fs, ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"], deps); + }); - after(() => { - host = undefined; - }); + after(() => { + host = undefined; + }); - it("orders the graph correctly - specify two roots", () => { - checkGraphOrdering(["A", "G"], ["D", "E", "C", "B", "A", "G"]); - }); + it("orders the graph correctly - specify two roots", () => { + checkGraphOrdering(["A", "G"], ["D", "E", "C", "B", "A", "G"]); + }); - it("orders the graph correctly - multiple parts of the same graph in various orders", () => { - checkGraphOrdering(["A"], ["D", "E", "C", "B", "A"]); - checkGraphOrdering(["A", "C", "D"], ["D", "E", "C", "B", "A"]); - checkGraphOrdering(["D", "C", "A"], ["D", "E", "C", "B", "A"]); - }); + it("orders the graph correctly - multiple parts of the same graph in various orders", () => { + checkGraphOrdering(["A"], ["D", "E", "C", "B", "A"]); + checkGraphOrdering(["A", "C", "D"], ["D", "E", "C", "B", "A"]); + checkGraphOrdering(["D", "C", "A"], ["D", "E", "C", "B", "A"]); + }); - it("orders the graph correctly - other orderings", () => { - checkGraphOrdering(["F"], ["E", "F"]); - checkGraphOrdering(["E"], ["E"]); - checkGraphOrdering(["F", "C", "A"], ["E", "F", "D", "C", "B", "A"]); - }); + it("orders the graph correctly - other orderings", () => { + checkGraphOrdering(["F"], ["E", "F"]); + checkGraphOrdering(["E"], ["E"]); + checkGraphOrdering(["F", "C", "A"], ["E", "F", "D", "C", "B", "A"]); + }); - it("returns circular order", () => { - checkGraphOrdering(["H"], ["E", "J", "I", "H"], /*circular*/ true); - checkGraphOrdering(["A", "H"], ["D", "E", "C", "B", "A", "J", "I", "H"], /*circular*/ true); - }); + it("returns circular order", () => { + checkGraphOrdering(["H"], ["E", "J", "I", "H"], /*circular*/ true); + checkGraphOrdering(["A", "H"], ["D", "E", "C", "B", "A", "J", "I", "H"], /*circular*/ true); + }); - function checkGraphOrdering(rootNames: string[], expectedBuildSet: string[], circular?: true) { - const builder = createSolutionBuilder(host!, rootNames.map(getProjectFileName), { dry: true, force: false, verbose: false }); - const buildOrder = builder.getBuildOrder(); - assert.equal(isCircularBuildOrder(buildOrder), !!circular); - const buildQueue = getBuildOrderFromAnyBuildOrder(buildOrder); - assert.deepEqual(buildQueue, expectedBuildSet.map(getProjectFileName)); + function checkGraphOrdering(rootNames: string[], expectedBuildSet: string[], circular?: true) { + const builder = createSolutionBuilder(host!, rootNames.map(getProjectFileName), { dry: true, force: false, verbose: false }); + const buildOrder = builder.getBuildOrder(); + assert.equal(isCircularBuildOrder(buildOrder), !!circular); + const buildQueue = getBuildOrderFromAnyBuildOrder(buildOrder); + assert.deepEqual(buildQueue, expectedBuildSet.map(getProjectFileName)); - if (!circular) { - for (const dep of deps) { - const child = getProjectFileName(dep[0]); - if (buildQueue.indexOf(child) < 0) continue; - const parent = getProjectFileName(dep[1]); - assert.isAbove(buildQueue.indexOf(child), buildQueue.indexOf(parent), `Expecting child ${child} to be built after parent ${parent}`); - } + if (!circular) { + for (const dep of deps) { + const child = getProjectFileName(dep[0]); + if (buildQueue.indexOf(child) < 0) continue; + const parent = getProjectFileName(dep[1]); + assert.isAbove(buildQueue.indexOf(child), buildQueue.indexOf(parent), `Expecting child ${child} to be built after parent ${parent}`); } } + } - function getProjectFileName(proj: string) { - return `/project/${proj}/tsconfig.json` as ResolvedConfigFileName; - } + function getProjectFileName(proj: string) { + return `/project/${proj}/tsconfig.json` as ResolvedConfigFileName; + } - function writeProjects(fileSystem: vfs.FileSystem, projectNames: string[], deps: [string, string][]): string[] { - const projFileNames: string[] = []; - for (const dep of deps) { - if (projectNames.indexOf(dep[0]) < 0) throw new Error(`Invalid dependency - project ${dep[0]} does not exist`); - if (projectNames.indexOf(dep[1]) < 0) throw new Error(`Invalid dependency - project ${dep[1]} does not exist`); - } - for (const proj of projectNames) { - fileSystem.mkdirpSync(`/project/${proj}`); - fileSystem.writeFileSync(`/project/${proj}/${proj}.ts`, "export {}"); - const configFileName = getProjectFileName(proj); - const configContent = JSON.stringify({ - compilerOptions: { composite: true }, - files: [`./${proj}.ts`], - references: deps.filter(d => d[0] === proj).map(d => ({ path: `../${d[1]}` })) - }, undefined, 2); - fileSystem.writeFileSync(configFileName, configContent); - projFileNames.push(configFileName); - } - return projFileNames; + function writeProjects(fileSystem: vfs.FileSystem, projectNames: string[], deps: [string, string][]): string[] { + const projFileNames: string[] = []; + for (const dep of deps) { + if (projectNames.indexOf(dep[0]) < 0) throw new Error(`Invalid dependency - project ${dep[0]} does not exist`); + if (projectNames.indexOf(dep[1]) < 0) throw new Error(`Invalid dependency - project ${dep[1]} does not exist`); } - }); + for (const proj of projectNames) { + fileSystem.mkdirpSync(`/project/${proj}`); + fileSystem.writeFileSync(`/project/${proj}/${proj}.ts`, "export {}"); + const configFileName = getProjectFileName(proj); + const configContent = JSON.stringify({ + compilerOptions: { composite: true }, + files: [`./${proj}.ts`], + references: deps.filter(d => d[0] === proj).map(d => ({ path: `../${d[1]}` })) + }, undefined, 2); + fileSystem.writeFileSync(configFileName, configContent); + projFileNames.push(configFileName); + } + return projFileNames; + } +}); } diff --git a/src/testRunner/unittests/tsbuild/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index 05800deaaac4f..8477ad646643e 100644 --- a/src/testRunner/unittests/tsbuild/helpers.ts +++ b/src/testRunner/unittests/tsbuild/helpers.ts @@ -1,108 +1,108 @@ namespace ts { - export function errorDiagnostic(message: fakes.ExpectedDiagnosticMessage): fakes.ExpectedErrorDiagnostic { - return { message }; - } +export function errorDiagnostic(message: fakes.ExpectedDiagnosticMessage): fakes.ExpectedErrorDiagnostic { + return { message }; +} - export function getExpectedDiagnosticForProjectsInBuild(...projects: string[]): fakes.ExpectedDiagnostic { - return [Diagnostics.Projects_in_this_build_Colon_0, projects.map(p => "\r\n * " + p).join("")]; - } +export function getExpectedDiagnosticForProjectsInBuild(...projects: string[]): fakes.ExpectedDiagnostic { + return [Diagnostics.Projects_in_this_build_Colon_0, projects.map(p => "\r\n * " + p).join("")]; +} - export function changeCompilerVersion(host: fakes.SolutionBuilderHost) { - const originalReadFile = host.readFile; - host.readFile = path => { - const value = originalReadFile.call(host, path); - if (!value || !isBuildInfoFile(path)) return value; - const buildInfo = getBuildInfo(value); - buildInfo.version = fakes.version; - return getBuildInfoText(buildInfo); - }; - } +export function changeCompilerVersion(host: fakes.SolutionBuilderHost) { + const originalReadFile = host.readFile; + host.readFile = path => { + const value = originalReadFile.call(host, path); + if (!value || !isBuildInfoFile(path)) return value; + const buildInfo = getBuildInfo(value); + buildInfo.version = fakes.version; + return getBuildInfoText(buildInfo); + }; +} - export function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) { - if (!fs.statSync(path).isFile()) { - throw new Error(`File ${path} does not exist`); - } - const old = fs.readFileSync(path, "utf-8"); - if (old.indexOf(oldText) < 0) { - throw new Error(`Text "${oldText}" does not exist in file ${path}`); - } - const newContent = old.replace(oldText, newText); - fs.writeFileSync(path, newContent, "utf-8"); +export function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); } - - export function prependText(fs: vfs.FileSystem, path: string, additionalContent: string) { - if (!fs.statSync(path).isFile()) { - throw new Error(`File ${path} does not exist`); - } - const old = fs.readFileSync(path, "utf-8"); - fs.writeFileSync(path, `${additionalContent}${old}`, "utf-8"); + const old = fs.readFileSync(path, "utf-8"); + if (old.indexOf(oldText) < 0) { + throw new Error(`Text "${oldText}" does not exist in file ${path}`); } + const newContent = old.replace(oldText, newText); + fs.writeFileSync(path, newContent, "utf-8"); +} - export function appendText(fs: vfs.FileSystem, path: string, additionalContent: string) { - if (!fs.statSync(path).isFile()) { - throw new Error(`File ${path} does not exist`); - } - const old = fs.readFileSync(path, "utf-8"); - fs.writeFileSync(path, `${old}${additionalContent}`); +export function prependText(fs: vfs.FileSystem, path: string, additionalContent: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); } + const old = fs.readFileSync(path, "utf-8"); + fs.writeFileSync(path, `${additionalContent}${old}`, "utf-8"); +} - export function indexOf(fs: vfs.FileSystem, path: string, searchStr: string) { - if (!fs.statSync(path).isFile()) { - throw new Error(`File ${path} does not exist`); - } - const content = fs.readFileSync(path, "utf-8"); - return content.indexOf(searchStr); +export function appendText(fs: vfs.FileSystem, path: string, additionalContent: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); } + const old = fs.readFileSync(path, "utf-8"); + fs.writeFileSync(path, `${old}${additionalContent}`); +} - export function lastIndexOf(fs: vfs.FileSystem, path: string, searchStr: string) { - if (!fs.statSync(path).isFile()) { - throw new Error(`File ${path} does not exist`); - } - const content = fs.readFileSync(path, "utf-8"); - return content.lastIndexOf(searchStr); +export function indexOf(fs: vfs.FileSystem, path: string, searchStr: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); } + const content = fs.readFileSync(path, "utf-8"); + return content.indexOf(searchStr); +} - export function expectedLocationIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation { - return { - file, - start: indexOf(fs, file, searchStr), - length: searchStr.length - }; +export function lastIndexOf(fs: vfs.FileSystem, path: string, searchStr: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); } + const content = fs.readFileSync(path, "utf-8"); + return content.lastIndexOf(searchStr); +} - export function expectedLocationLastIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation { - return { - file, - start: lastIndexOf(fs, file, searchStr), - length: searchStr.length - }; - } +export function expectedLocationIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation { + return { + file, + start: indexOf(fs, file, searchStr), + length: searchStr.length + }; +} - export function getTime() { - let currentTime = 100; - return { tick, time, touch }; +export function expectedLocationLastIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation { + return { + file, + start: lastIndexOf(fs, file, searchStr), + length: searchStr.length + }; +} - function tick() { - currentTime += 60_000; - } +export function getTime() { + let currentTime = 100; + return { tick, time, touch }; - function time() { - return currentTime; - } + function tick() { + currentTime += 60_000; + } - function touch(fs: vfs.FileSystem, path: string) { - if (!fs.statSync(path).isFile()) { - throw new Error(`File ${path} does not exist`); - } - fs.utimesSync(path, new Date(time()), new Date(time())); + function time() { + return currentTime; + } + + function touch(fs: vfs.FileSystem, path: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); } + fs.utimesSync(path, new Date(time()), new Date(time())); } +} - export const libContent = `${TestFSWithWatch.libFile.content} +export const libContent = `${TestFSWithWatch.libFile.content} interface ReadonlyArray {} declare const console: { log(msg: any): void; };`; - export const symbolLibContent = ` +export const symbolLibContent = ` interface SymbolConstructor { readonly species: symbol; readonly toStringTag: symbol; @@ -113,659 +113,659 @@ interface Symbol { } `; - /** - * Load project from disk into /src folder - */ - export function loadProjectFromDisk( - root: string, - libContentToAppend?: string - ): vfs.FileSystem { - const resolver = vfs.createResolver(Harness.IO); - const fs = new vfs.FileSystem(/*ignoreCase*/ true, { - files: { - ["/src"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), root), resolver) - }, - cwd: "/", - meta: { defaultLibLocation: "/lib" }, - }); - addLibAndMakeReadonly(fs, libContentToAppend); - return fs; - } +/** + * Load project from disk into /src folder + */ +export function loadProjectFromDisk( + root: string, + libContentToAppend?: string +): vfs.FileSystem { + const resolver = vfs.createResolver(Harness.IO); + const fs = new vfs.FileSystem(/*ignoreCase*/ true, { + files: { + ["/src"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), root), resolver) + }, + cwd: "/", + meta: { defaultLibLocation: "/lib" }, + }); + addLibAndMakeReadonly(fs, libContentToAppend); + return fs; +} - /** - * All the files must be in /src - */ - export function loadProjectFromFiles( - files: vfs.FileSet, - libContentToAppend?: string - ): vfs.FileSystem { - const fs = new vfs.FileSystem(/*ignoreCase*/ true, { - files, - cwd: "/", - meta: { defaultLibLocation: "/lib" }, - }); - addLibAndMakeReadonly(fs, libContentToAppend); - return fs; - } +/** + * All the files must be in /src + */ +export function loadProjectFromFiles( + files: vfs.FileSet, + libContentToAppend?: string +): vfs.FileSystem { + const fs = new vfs.FileSystem(/*ignoreCase*/ true, { + files, + cwd: "/", + meta: { defaultLibLocation: "/lib" }, + }); + addLibAndMakeReadonly(fs, libContentToAppend); + return fs; +} - function addLibAndMakeReadonly(fs: vfs.FileSystem, libContentToAppend?: string) { - fs.mkdirSync("/lib"); - fs.writeFileSync("/lib/lib.d.ts", libContentToAppend ? `${libContent}${libContentToAppend}` : libContent); - fs.makeReadonly(); - } +function addLibAndMakeReadonly(fs: vfs.FileSystem, libContentToAppend?: string) { + fs.mkdirSync("/lib"); + fs.writeFileSync("/lib/lib.d.ts", libContentToAppend ? `${libContent}${libContentToAppend}` : libContent); + fs.makeReadonly(); +} - /** - * Gets the FS mountuing existing fs's /src and /lib folder - */ - export function getFsWithTime(baseFs: vfs.FileSystem) { - const { time, tick } = getTime(); - const host = new fakes.System(baseFs) as any as vfs.FileSystemResolverHost; - host.getWorkspaceRoot = notImplemented; - const resolver = vfs.createResolver(host); - const fs = new vfs.FileSystem(/*ignoreCase*/ true, { - files: { - ["/src"]: new vfs.Mount("/src", resolver), - ["/lib"]: new vfs.Mount("/lib", resolver) - }, - cwd: "/", - meta: { defaultLibLocation: "/lib" }, - time - }); - return { fs, time, tick }; - } +/** + * Gets the FS mountuing existing fs's /src and /lib folder + */ +export function getFsWithTime(baseFs: vfs.FileSystem) { + const { time, tick } = getTime(); + const host = new fakes.System(baseFs) as any as vfs.FileSystemResolverHost; + host.getWorkspaceRoot = notImplemented; + const resolver = vfs.createResolver(host); + const fs = new vfs.FileSystem(/*ignoreCase*/ true, { + files: { + ["/src"]: new vfs.Mount("/src", resolver), + ["/lib"]: new vfs.Mount("/lib", resolver) + }, + cwd: "/", + meta: { defaultLibLocation: "/lib" }, + time + }); + return { fs, time, tick }; +} - export function verifyOutputsPresent(fs: vfs.FileSystem, outputs: readonly string[]) { - for (const output of outputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } +export function verifyOutputsPresent(fs: vfs.FileSystem, outputs: readonly string[]) { + for (const output of outputs) { + assert(fs.existsSync(output), `Expect file ${output} to exist`); } +} - export function verifyOutputsAbsent(fs: vfs.FileSystem, outputs: readonly string[]) { - for (const output of outputs) { - assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`); - } +export function verifyOutputsAbsent(fs: vfs.FileSystem, outputs: readonly string[]) { + for (const output of outputs) { + assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`); } +} - export function generateSourceMapBaselineFiles(sys: System & { writtenFiles: ReadonlyCollection; }) { - const mapFileNames = mapDefinedIterator(sys.writtenFiles.keys(), f => f.endsWith(".map") ? f : undefined); - while (true) { - const result = mapFileNames.next(); - if (result.done) break; - const mapFile = result.value; - const text = Harness.SourceMapRecorder.getSourceMapRecordWithSystem(sys, mapFile); - sys.writeFile(`${mapFile}.baseline.txt`, text); - } +export function generateSourceMapBaselineFiles(sys: System & { writtenFiles: ReadonlyCollection; }) { + const mapFileNames = mapDefinedIterator(sys.writtenFiles.keys(), f => f.endsWith(".map") ? f : undefined); + while (true) { + const result = mapFileNames.next(); + if (result.done) break; + const mapFile = result.value; + const text = Harness.SourceMapRecorder.getSourceMapRecordWithSystem(sys, mapFile); + sys.writeFile(`${mapFile}.baseline.txt`, text); } +} - function generateBundleFileSectionInfo(sys: System, originalReadCall: System["readFile"], baselineRecorder: Harness.Compiler.WriterAggregator, bundleFileInfo: BundleFileInfo | undefined, outFile: string | undefined) { - if (!length(bundleFileInfo && bundleFileInfo.sections) && !outFile) return; // Nothing to baseline - - const content = outFile && sys.fileExists(outFile) ? originalReadCall.call(sys, outFile, "utf8")! : ""; - baselineRecorder.WriteLine("======================================================================"); - baselineRecorder.WriteLine(`File:: ${outFile}`); - for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) { - baselineRecorder.WriteLine("----------------------------------------------------------------------"); - writeSectionHeader(section); - if (section.kind !== BundleFileSectionKind.Prepend) { - writeTextOfSection(section.pos, section.end); - } - else if (section.texts.length > 0) { - Debug.assert(section.pos === first(section.texts).pos); - Debug.assert(section.end === last(section.texts).end); - for (const text of section.texts) { - baselineRecorder.WriteLine(">>--------------------------------------------------------------------"); - writeSectionHeader(text); - writeTextOfSection(text.pos, text.end); - } - } - else { - Debug.assert(section.pos === section.end); - } +function generateBundleFileSectionInfo(sys: System, originalReadCall: System["readFile"], baselineRecorder: Harness.Compiler.WriterAggregator, bundleFileInfo: BundleFileInfo | undefined, outFile: string | undefined) { + if (!length(bundleFileInfo && bundleFileInfo.sections) && !outFile) return; // Nothing to baseline + + const content = outFile && sys.fileExists(outFile) ? originalReadCall.call(sys, outFile, "utf8")! : ""; + baselineRecorder.WriteLine("======================================================================"); + baselineRecorder.WriteLine(`File:: ${outFile}`); + for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) { + baselineRecorder.WriteLine("----------------------------------------------------------------------"); + writeSectionHeader(section); + if (section.kind !== BundleFileSectionKind.Prepend) { + writeTextOfSection(section.pos, section.end); } - baselineRecorder.WriteLine("======================================================================"); - - function writeTextOfSection(pos: number, end: number) { - const textLines = content.substring(pos, end).split(/\r?\n/); - for (const line of textLines) { - baselineRecorder.WriteLine(line); + else if (section.texts.length > 0) { + Debug.assert(section.pos === first(section.texts).pos); + Debug.assert(section.end === last(section.texts).end); + for (const text of section.texts) { + baselineRecorder.WriteLine(">>--------------------------------------------------------------------"); + writeSectionHeader(text); + writeTextOfSection(text.pos, text.end); } } - - function writeSectionHeader(section: BundleFileSection) { - baselineRecorder.WriteLine(`${section.kind}: (${section.pos}-${section.end})${section.data ? ":: " + section.data : ""}${section.kind === BundleFileSectionKind.Prepend ? " texts:: " + section.texts.length : ""}`); + else { + Debug.assert(section.pos === section.end); } } + baselineRecorder.WriteLine("======================================================================"); - type ReadableProgramBuildInfoDiagnostic = string | [string, readonly ReusableDiagnostic[]]; - type ReadableProgramBuilderInfoFilePendingEmit = [string, "DtsOnly" | "Full"]; - interface ReadableProgramBuildInfo { - fileNames: readonly string[]; - fileNamesList: readonly (readonly string[])[] | undefined; - fileInfos: MapLike; - options: CompilerOptions | undefined; - referencedMap?: MapLike; - exportedModulesMap?: MapLike; - semanticDiagnosticsPerFile?: readonly ReadableProgramBuildInfoDiagnostic[]; - affectedFilesPendingEmit?: readonly ReadableProgramBuilderInfoFilePendingEmit[]; - } - type ReadableBuildInfo = Omit & { program: ReadableProgramBuildInfo | undefined; size: number; }; - function generateBuildInfoProgramBaseline(sys: System, originalWriteFile: System["writeFile"], buildInfoPath: string, buildInfo: BuildInfo) { - const fileInfos: ReadableProgramBuildInfo["fileInfos"] = {}; - buildInfo.program?.fileInfos.forEach((fileInfo, index) => fileInfos[toFileName(index + 1 as ProgramBuildInfoFileId)] = toBuilderStateFileInfo(fileInfo)); - const fileNamesList = buildInfo.program?.fileIdsList?.map(fileIdsListId => fileIdsListId.map(toFileName)); - const program: ReadableProgramBuildInfo | undefined = buildInfo.program && { - fileNames: buildInfo.program.fileNames, - fileNamesList, - fileInfos, - options: buildInfo.program.options, - referencedMap: toMapOfReferencedSet(buildInfo.program.referencedMap), - exportedModulesMap: toMapOfReferencedSet(buildInfo.program.exportedModulesMap), - semanticDiagnosticsPerFile: buildInfo.program.semanticDiagnosticsPerFile?.map(d => - isNumber(d) ? - toFileName(d) : - [toFileName(d[0]), d[1]] - ), - affectedFilesPendingEmit: buildInfo.program.affectedFilesPendingEmit?.map(([fileId, emitKind]) => [ - toFileName(fileId), - emitKind === BuilderFileEmit.DtsOnly ? "DtsOnly" : - emitKind === BuilderFileEmit.Full ? "Full" : - Debug.assertNever(emitKind) - ]), - }; - const version = buildInfo.version === ts.version ? fakes.version : buildInfo.version; - const result: ReadableBuildInfo = { - bundle: buildInfo.bundle, - program, - version, - size: getBuildInfoText({ ...buildInfo, version }).length, - }; - // For now its just JSON.stringify - originalWriteFile.call(sys, `${buildInfoPath}.readable.baseline.txt`, JSON.stringify(result, /*replacer*/ undefined, 2)); - - function toFileName(fileId: ProgramBuildInfoFileId) { - return buildInfo.program!.fileNames[fileId - 1]; + function writeTextOfSection(pos: number, end: number) { + const textLines = content.substring(pos, end).split(/\r?\n/); + for (const line of textLines) { + baselineRecorder.WriteLine(line); } + } - function toFileNames(fileIdsListId: ProgramBuildInfoFileIdListId) { - return fileNamesList![fileIdsListId - 1]; - } + function writeSectionHeader(section: BundleFileSection) { + baselineRecorder.WriteLine(`${section.kind}: (${section.pos}-${section.end})${section.data ? ":: " + section.data : ""}${section.kind === BundleFileSectionKind.Prepend ? " texts:: " + section.texts.length : ""}`); + } +} - function toMapOfReferencedSet(referenceMap: ProgramBuildInfoReferencedMap | undefined): MapLike | undefined { - if (!referenceMap) return undefined; - const result: MapLike = {}; - for (const [fileNamesKey, fileNamesListKey] of referenceMap) { - result[toFileName(fileNamesKey)] = toFileNames(fileNamesListKey); - } - return result; +type ReadableProgramBuildInfoDiagnostic = string | [string, readonly ReusableDiagnostic[]]; +type ReadableProgramBuilderInfoFilePendingEmit = [string, "DtsOnly" | "Full"]; +interface ReadableProgramBuildInfo { + fileNames: readonly string[]; + fileNamesList: readonly (readonly string[])[] | undefined; + fileInfos: MapLike; + options: CompilerOptions | undefined; + referencedMap?: MapLike; + exportedModulesMap?: MapLike; + semanticDiagnosticsPerFile?: readonly ReadableProgramBuildInfoDiagnostic[]; + affectedFilesPendingEmit?: readonly ReadableProgramBuilderInfoFilePendingEmit[]; +} +type ReadableBuildInfo = Omit & { program: ReadableProgramBuildInfo | undefined; size: number; }; +function generateBuildInfoProgramBaseline(sys: System, originalWriteFile: System["writeFile"], buildInfoPath: string, buildInfo: BuildInfo) { + const fileInfos: ReadableProgramBuildInfo["fileInfos"] = {}; + buildInfo.program?.fileInfos.forEach((fileInfo, index) => fileInfos[toFileName(index + 1 as ProgramBuildInfoFileId)] = toBuilderStateFileInfo(fileInfo)); + const fileNamesList = buildInfo.program?.fileIdsList?.map(fileIdsListId => fileIdsListId.map(toFileName)); + const program: ReadableProgramBuildInfo | undefined = buildInfo.program && { + fileNames: buildInfo.program.fileNames, + fileNamesList, + fileInfos, + options: buildInfo.program.options, + referencedMap: toMapOfReferencedSet(buildInfo.program.referencedMap), + exportedModulesMap: toMapOfReferencedSet(buildInfo.program.exportedModulesMap), + semanticDiagnosticsPerFile: buildInfo.program.semanticDiagnosticsPerFile?.map(d => + isNumber(d) ? + toFileName(d) : + [toFileName(d[0]), d[1]] + ), + affectedFilesPendingEmit: buildInfo.program.affectedFilesPendingEmit?.map(([fileId, emitKind]) => [ + toFileName(fileId), + emitKind === BuilderFileEmit.DtsOnly ? "DtsOnly" : + emitKind === BuilderFileEmit.Full ? "Full" : + Debug.assertNever(emitKind) + ]), + }; + const version = buildInfo.version === ts.version ? fakes.version : buildInfo.version; + const result: ReadableBuildInfo = { + bundle: buildInfo.bundle, + program, + version, + size: getBuildInfoText({ ...buildInfo, version }).length, + }; + // For now its just JSON.stringify + originalWriteFile.call(sys, `${buildInfoPath}.readable.baseline.txt`, JSON.stringify(result, /*replacer*/ undefined, 2)); + + function toFileName(fileId: ProgramBuildInfoFileId) { + return buildInfo.program!.fileNames[fileId - 1]; + } + + function toFileNames(fileIdsListId: ProgramBuildInfoFileIdListId) { + return fileNamesList![fileIdsListId - 1]; + } + + function toMapOfReferencedSet(referenceMap: ProgramBuildInfoReferencedMap | undefined): MapLike | undefined { + if (!referenceMap) return undefined; + const result: MapLike = {}; + for (const [fileNamesKey, fileNamesListKey] of referenceMap) { + result[toFileName(fileNamesKey)] = toFileNames(fileNamesListKey); } + return result; } +} - export function toPathWithSystem(sys: System, fileName: string): Path { - return toPath(fileName, sys.getCurrentDirectory(), createGetCanonicalFileName(sys.useCaseSensitiveFileNames)); - } +export function toPathWithSystem(sys: System, fileName: string): Path { + return toPath(fileName, sys.getCurrentDirectory(), createGetCanonicalFileName(sys.useCaseSensitiveFileNames)); +} - export function baselineBuildInfo( - options: CompilerOptions, - sys: System & { writtenFiles: ReadonlyCollection; }, - originalReadCall?: System["readFile"], - originalWriteFile?: System["writeFile"], - ) { - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); - if (!buildInfoPath || !sys.writtenFiles.has(toPathWithSystem(sys, buildInfoPath))) return; - if (!sys.fileExists(buildInfoPath)) return; - - const buildInfo = getBuildInfo((originalReadCall || sys.readFile).call(sys, buildInfoPath, "utf8")!); - generateBuildInfoProgramBaseline(sys, originalWriteFile || sys.writeFile, buildInfoPath, buildInfo); - - if (!outFile(options)) return; - const { jsFilePath, declarationFilePath } = getOutputPathsForBundle(options, /*forceDts*/ false); - const bundle = buildInfo.bundle; - if (!bundle || (!length(bundle.js && bundle.js.sections) && !length(bundle.dts && bundle.dts.sections))) return; - - // Write the baselines: - const baselineRecorder = new Harness.Compiler.WriterAggregator(); - generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.js, jsFilePath); - generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.dts, declarationFilePath); - baselineRecorder.Close(); - const text = baselineRecorder.lines.join("\r\n"); - (originalWriteFile || sys.writeFile).call(sys, `${buildInfoPath}.baseline.txt`, text); - } +export function baselineBuildInfo( + options: CompilerOptions, + sys: System & { writtenFiles: ReadonlyCollection; }, + originalReadCall?: System["readFile"], + originalWriteFile?: System["writeFile"], +) { + const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); + if (!buildInfoPath || !sys.writtenFiles.has(toPathWithSystem(sys, buildInfoPath))) return; + if (!sys.fileExists(buildInfoPath)) return; + + const buildInfo = getBuildInfo((originalReadCall || sys.readFile).call(sys, buildInfoPath, "utf8")!); + generateBuildInfoProgramBaseline(sys, originalWriteFile || sys.writeFile, buildInfoPath, buildInfo); + + if (!outFile(options)) return; + const { jsFilePath, declarationFilePath } = getOutputPathsForBundle(options, /*forceDts*/ false); + const bundle = buildInfo.bundle; + if (!bundle || (!length(bundle.js && bundle.js.sections) && !length(bundle.dts && bundle.dts.sections))) return; + + // Write the baselines: + const baselineRecorder = new Harness.Compiler.WriterAggregator(); + generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.js, jsFilePath); + generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.dts, declarationFilePath); + baselineRecorder.Close(); + const text = baselineRecorder.lines.join("\r\n"); + (originalWriteFile || sys.writeFile).call(sys, `${buildInfoPath}.baseline.txt`, text); +} - interface VerifyIncrementalCorrectness { - scenario: TscCompile["scenario"]; - commandLineArgs: TscCompile["commandLineArgs"]; - modifyFs: TscCompile["modifyFs"]; - incrementalModifyFs: TscIncremental["modifyFs"]; - tick: () => void; - baseFs: vfs.FileSystem; - newSys: TscCompileSystem; - cleanBuildDiscrepancies: TscIncremental["cleanBuildDiscrepancies"]; - } - function verifyIncrementalCorrectness(input: () => VerifyIncrementalCorrectness, index: number, subScenario: TscCompile["subScenario"]) { - it(`Verify emit output file text is same when built clean for incremental scenario at:: ${index} ${subScenario}`, () => { - const { - scenario, commandLineArgs, cleanBuildDiscrepancies, - modifyFs, incrementalModifyFs, - tick, baseFs, newSys - } = input(); - const sys = tscCompile({ - scenario, - subScenario, - fs: () => baseFs.makeReadonly(), - commandLineArgs, - modifyFs: fs => { - tick(); - if (modifyFs) modifyFs(fs); - incrementalModifyFs(fs); - }, - disableUseFileVersionAsSignature: true, - }); - const discrepancies = cleanBuildDiscrepancies?.(); - for (const outputFile of arrayFrom(sys.writtenFiles.keys())) { - const cleanBuildText = sys.readFile(outputFile); - const incrementalBuildText = newSys.readFile(outputFile); - const descrepancyInClean = discrepancies?.get(outputFile); - if (isBuildInfoFile(outputFile)) { - // Check only presence and absence and not text as we will do that for readable baseline - assert.isTrue(sys.fileExists(`${outputFile}.readable.baseline.txt`), `Readable baseline should be present in clean build:: File:: ${outputFile}`); - assert.isTrue(newSys.fileExists(`${outputFile}.readable.baseline.txt`), `Readable baseline should be present in incremental build:: File:: ${outputFile}`); - if (descrepancyInClean === undefined) { - verifyPresenceAbsence(incrementalBuildText, cleanBuildText, `Incremental and clean tsbuildinfo file presence should match:: File:: ${outputFile}`); - } - else { - verifyTextEqual(incrementalBuildText, cleanBuildText, descrepancyInClean, `File: ${outputFile}`); - } +interface VerifyIncrementalCorrectness { + scenario: TscCompile["scenario"]; + commandLineArgs: TscCompile["commandLineArgs"]; + modifyFs: TscCompile["modifyFs"]; + incrementalModifyFs: TscIncremental["modifyFs"]; + tick: () => void; + baseFs: vfs.FileSystem; + newSys: TscCompileSystem; + cleanBuildDiscrepancies: TscIncremental["cleanBuildDiscrepancies"]; +} +function verifyIncrementalCorrectness(input: () => VerifyIncrementalCorrectness, index: number, subScenario: TscCompile["subScenario"]) { + it(`Verify emit output file text is same when built clean for incremental scenario at:: ${index} ${subScenario}`, () => { + const { + scenario, commandLineArgs, cleanBuildDiscrepancies, + modifyFs, incrementalModifyFs, + tick, baseFs, newSys + } = input(); + const sys = tscCompile({ + scenario, + subScenario, + fs: () => baseFs.makeReadonly(), + commandLineArgs, + modifyFs: fs => { + tick(); + if (modifyFs) modifyFs(fs); + incrementalModifyFs(fs); + }, + disableUseFileVersionAsSignature: true, + }); + const discrepancies = cleanBuildDiscrepancies?.(); + for (const outputFile of arrayFrom(sys.writtenFiles.keys())) { + const cleanBuildText = sys.readFile(outputFile); + const incrementalBuildText = newSys.readFile(outputFile); + const descrepancyInClean = discrepancies?.get(outputFile); + if (isBuildInfoFile(outputFile)) { + // Check only presence and absence and not text as we will do that for readable baseline + assert.isTrue(sys.fileExists(`${outputFile}.readable.baseline.txt`), `Readable baseline should be present in clean build:: File:: ${outputFile}`); + assert.isTrue(newSys.fileExists(`${outputFile}.readable.baseline.txt`), `Readable baseline should be present in incremental build:: File:: ${outputFile}`); + if (descrepancyInClean === undefined) { + verifyPresenceAbsence(incrementalBuildText, cleanBuildText, `Incremental and clean tsbuildinfo file presence should match:: File:: ${outputFile}`); } - else if (!fileExtensionIs(outputFile, ".tsbuildinfo.readable.baseline.txt")) { + else { verifyTextEqual(incrementalBuildText, cleanBuildText, descrepancyInClean, `File: ${outputFile}`); } - else if (incrementalBuildText !== cleanBuildText) { - // Verify build info without affectedFilesPendingEmit - const { buildInfo: incrementalBuildInfo, readableBuildInfo: incrementalReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText); - const { buildInfo: cleanBuildInfo, readableBuildInfo: cleanReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText); - verifyTextEqual(incrementalBuildInfo, cleanBuildInfo, descrepancyInClean, `TsBuild info text without affectedFilesPendingEmit ${subScenario}:: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`); - if (descrepancyInClean === undefined) { - // Verify file info sigantures - verifyMapLike( - incrementalReadableBuildInfo?.program?.fileInfos, - cleanReadableBuildInfo?.program?.fileInfos, - (key, incrementalFileInfo, cleanFileInfo) => { - if (incrementalFileInfo.signature !== cleanFileInfo.signature && incrementalFileInfo.signature !== incrementalFileInfo.version) { - assert.fail(`Incremental signature should either be dts signature or file version for File:: ${key}:: Incremental:: ${JSON.stringify(incrementalFileInfo)}, Clean:: ${JSON.stringify(cleanFileInfo)}}`); - } - }, - `FileInfos:: File:: ${outputFile}` - ); - // Verify exportedModulesMap - verifyMapLike( - incrementalReadableBuildInfo?.program?.exportedModulesMap, - cleanReadableBuildInfo?.program?.exportedModulesMap, - (key, incrementalReferenceSet, cleanReferenceSet) => { - if (!arrayIsEqualTo(incrementalReferenceSet, cleanReferenceSet) && !arrayIsEqualTo(incrementalReferenceSet, incrementalReadableBuildInfo!.program!.referencedMap![key])) { - assert.fail(`Incremental Reference set should either be from dts or files reference map for File:: ${key}:: Incremental:: ${JSON.stringify(incrementalReferenceSet)}, Clean:: ${JSON.stringify(cleanReferenceSet)}, referenceMap:: ${JSON.stringify(incrementalReadableBuildInfo!.program!.referencedMap![key])}}`); - } - }, - `exportedModulesMap:: File:: ${outputFile}` - ); - // Verify that incrementally pending affected file emit are in clean build since clean build can contain more files compared to incremental depending of noEmitOnError option - if (incrementalReadableBuildInfo?.program?.affectedFilesPendingEmit) { - assert.isDefined(cleanReadableBuildInfo?.program?.affectedFilesPendingEmit, `Incremental build contains affectedFilesPendingEmit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`); - let expectedIndex = 0; - incrementalReadableBuildInfo.program.affectedFilesPendingEmit.forEach(([actualFile]) => { - expectedIndex = findIndex(cleanReadableBuildInfo!.program!.affectedFilesPendingEmit!, ([expectedFile]) => actualFile === expectedFile, expectedIndex); - assert.notEqual(expectedIndex, -1, `Incremental build contains ${actualFile} file as pending emit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`); - expectedIndex++; - }); - } + } + else if (!fileExtensionIs(outputFile, ".tsbuildinfo.readable.baseline.txt")) { + verifyTextEqual(incrementalBuildText, cleanBuildText, descrepancyInClean, `File: ${outputFile}`); + } + else if (incrementalBuildText !== cleanBuildText) { + // Verify build info without affectedFilesPendingEmit + const { buildInfo: incrementalBuildInfo, readableBuildInfo: incrementalReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText); + const { buildInfo: cleanBuildInfo, readableBuildInfo: cleanReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText); + verifyTextEqual(incrementalBuildInfo, cleanBuildInfo, descrepancyInClean, `TsBuild info text without affectedFilesPendingEmit ${subScenario}:: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`); + if (descrepancyInClean === undefined) { + // Verify file info sigantures + verifyMapLike( + incrementalReadableBuildInfo?.program?.fileInfos, + cleanReadableBuildInfo?.program?.fileInfos, + (key, incrementalFileInfo, cleanFileInfo) => { + if (incrementalFileInfo.signature !== cleanFileInfo.signature && incrementalFileInfo.signature !== incrementalFileInfo.version) { + assert.fail(`Incremental signature should either be dts signature or file version for File:: ${key}:: Incremental:: ${JSON.stringify(incrementalFileInfo)}, Clean:: ${JSON.stringify(cleanFileInfo)}}`); + } + }, + `FileInfos:: File:: ${outputFile}` + ); + // Verify exportedModulesMap + verifyMapLike( + incrementalReadableBuildInfo?.program?.exportedModulesMap, + cleanReadableBuildInfo?.program?.exportedModulesMap, + (key, incrementalReferenceSet, cleanReferenceSet) => { + if (!arrayIsEqualTo(incrementalReferenceSet, cleanReferenceSet) && !arrayIsEqualTo(incrementalReferenceSet, incrementalReadableBuildInfo!.program!.referencedMap![key])) { + assert.fail(`Incremental Reference set should either be from dts or files reference map for File:: ${key}:: Incremental:: ${JSON.stringify(incrementalReferenceSet)}, Clean:: ${JSON.stringify(cleanReferenceSet)}, referenceMap:: ${JSON.stringify(incrementalReadableBuildInfo!.program!.referencedMap![key])}}`); + } + }, + `exportedModulesMap:: File:: ${outputFile}` + ); + // Verify that incrementally pending affected file emit are in clean build since clean build can contain more files compared to incremental depending of noEmitOnError option + if (incrementalReadableBuildInfo?.program?.affectedFilesPendingEmit) { + assert.isDefined(cleanReadableBuildInfo?.program?.affectedFilesPendingEmit, `Incremental build contains affectedFilesPendingEmit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`); + let expectedIndex = 0; + incrementalReadableBuildInfo.program.affectedFilesPendingEmit.forEach(([actualFile]) => { + expectedIndex = findIndex(cleanReadableBuildInfo!.program!.affectedFilesPendingEmit!, ([expectedFile]) => actualFile === expectedFile, expectedIndex); + assert.notEqual(expectedIndex, -1, `Incremental build contains ${actualFile} file as pending emit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`); + expectedIndex++; + }); } } } + } - function verifyTextEqual(incrementalText: string | undefined, cleanText: string | undefined, descrepancyInClean: CleanBuildDescrepancy | undefined, message: string) { - if (descrepancyInClean === undefined) { - assert.equal(incrementalText, cleanText, message); + function verifyTextEqual(incrementalText: string | undefined, cleanText: string | undefined, descrepancyInClean: CleanBuildDescrepancy | undefined, message: string) { + if (descrepancyInClean === undefined) { + assert.equal(incrementalText, cleanText, message); + return; + } + switch (descrepancyInClean) { + case CleanBuildDescrepancy.CleanFileTextDifferent: + assert.isDefined(incrementalText, `Incremental file should be present:: ${message}`); + assert.isDefined(cleanText, `Clean file should be present present:: ${message}`); + assert.notEqual(incrementalText, cleanText, message); return; - } - switch (descrepancyInClean) { - case CleanBuildDescrepancy.CleanFileTextDifferent: - assert.isDefined(incrementalText, `Incremental file should be present:: ${message}`); - assert.isDefined(cleanText, `Clean file should be present present:: ${message}`); - assert.notEqual(incrementalText, cleanText, message); - return; - case CleanBuildDescrepancy.CleanFilePresent: - assert.isUndefined(incrementalText, `Incremental file should be absent:: ${message}`); - assert.isDefined(cleanText, `Clean file should be present:: ${message}`); - return; - default: - Debug.assertNever(descrepancyInClean); - } + case CleanBuildDescrepancy.CleanFilePresent: + assert.isUndefined(incrementalText, `Incremental file should be absent:: ${message}`); + assert.isDefined(cleanText, `Clean file should be present:: ${message}`); + return; + default: + Debug.assertNever(descrepancyInClean); } + } - function verifyMapLike(incremental: MapLike | undefined, clean: MapLike | undefined, verifyValue: (key: string, incrementalValue: T, cleanValue: T) => void, message: string) { - verifyPresenceAbsence(incremental, clean, `Incremental and clean presence should match:: ${message}`); - if (!incremental) return; - const incrementalMap = new Map(getEntries(incremental)); - const cleanMap = new Map(getEntries(clean!)); - assert.equal(incrementalMap.size, cleanMap.size, `Incremental and clean size of map should match:: ${message}, Incremental keys: ${arrayFrom(incrementalMap.keys())} Clean: ${arrayFrom(cleanMap.keys())}${TestFSWithWatch.getDiffInKeys(incrementalMap, arrayFrom(cleanMap.keys()))}`); - cleanMap.forEach((cleanValue, key) => { - assert.isTrue(incrementalMap.has(key), `Expected to contain ${key} in incremental map:: ${message}, Incremental keys: ${arrayFrom(incrementalMap.keys())}`); - verifyValue(key, incrementalMap.get(key)!, cleanValue); - }); - } - }); - } + function verifyMapLike(incremental: MapLike | undefined, clean: MapLike | undefined, verifyValue: (key: string, incrementalValue: T, cleanValue: T) => void, message: string) { + verifyPresenceAbsence(incremental, clean, `Incremental and clean presence should match:: ${message}`); + if (!incremental) return; + const incrementalMap = new Map(getEntries(incremental)); + const cleanMap = new Map(getEntries(clean!)); + assert.equal(incrementalMap.size, cleanMap.size, `Incremental and clean size of map should match:: ${message}, Incremental keys: ${arrayFrom(incrementalMap.keys())} Clean: ${arrayFrom(cleanMap.keys())}${TestFSWithWatch.getDiffInKeys(incrementalMap, arrayFrom(cleanMap.keys()))}`); + cleanMap.forEach((cleanValue, key) => { + assert.isTrue(incrementalMap.has(key), `Expected to contain ${key} in incremental map:: ${message}, Incremental keys: ${arrayFrom(incrementalMap.keys())}`); + verifyValue(key, incrementalMap.get(key)!, cleanValue); + }); + } + }); +} - function verifyPresenceAbsence(actual: T | undefined, expected: T | undefined, message: string) { - (expected !== undefined ? assert.isDefined : assert.isUndefined)(actual, message); - } +function verifyPresenceAbsence(actual: T | undefined, expected: T | undefined, message: string) { + (expected !== undefined ? assert.isDefined : assert.isUndefined)(actual, message); +} - function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): { - buildInfo: string | undefined; - readableBuildInfo?: ReadableBuildInfo; - } { - if (!text) return { buildInfo: text }; - const readableBuildInfo = JSON.parse(text) as ReadableBuildInfo; - let sanitizedFileInfos: MapLike | undefined; - if (readableBuildInfo.program) { - sanitizedFileInfos = {}; - for (const id in readableBuildInfo.program.fileInfos) { - if (hasProperty(readableBuildInfo.program.fileInfos, id)) { - sanitizedFileInfos[id] = { ...readableBuildInfo.program.fileInfos[id], signature: undefined }; - } +function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): { + buildInfo: string | undefined; + readableBuildInfo?: ReadableBuildInfo; +} { + if (!text) return { buildInfo: text }; + const readableBuildInfo = JSON.parse(text) as ReadableBuildInfo; + let sanitizedFileInfos: MapLike | undefined; + if (readableBuildInfo.program) { + sanitizedFileInfos = {}; + for (const id in readableBuildInfo.program.fileInfos) { + if (hasProperty(readableBuildInfo.program.fileInfos, id)) { + sanitizedFileInfos[id] = { ...readableBuildInfo.program.fileInfos[id], signature: undefined }; } } - return { - buildInfo: JSON.stringify({ - ...readableBuildInfo, - program: readableBuildInfo.program && { - ...readableBuildInfo.program, - fileNames: undefined, - fileNamesList: undefined, - fileInfos: sanitizedFileInfos, - // Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter - options: { ...readableBuildInfo.program.options, noEmit: undefined }, - exportedModulesMap: undefined, - affectedFilesPendingEmit: undefined, - }, - size: undefined, // Size doesnt need to be equal - }, /*replacer*/ undefined, 2), - readableBuildInfo, - }; - } - - export enum CleanBuildDescrepancy { - CleanFileTextDifferent, - CleanFilePresent, - } - - export interface TscIncremental { - buildKind: BuildKind; - modifyFs: (fs: vfs.FileSystem) => void; - subScenario?: string; - commandLineArgs?: readonly string[]; - cleanBuildDiscrepancies?: () => ESMap; } + return { + buildInfo: JSON.stringify({ + ...readableBuildInfo, + program: readableBuildInfo.program && { + ...readableBuildInfo.program, + fileNames: undefined, + fileNamesList: undefined, + fileInfos: sanitizedFileInfos, + // Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter + options: { ...readableBuildInfo.program.options, noEmit: undefined }, + exportedModulesMap: undefined, + affectedFilesPendingEmit: undefined, + }, + size: undefined, // Size doesnt need to be equal + }, /*replacer*/ undefined, 2), + readableBuildInfo, + }; +} - export interface VerifyTsBuildInput extends VerifyTsBuildInputWorker { - baselineIncremental?: boolean; - } +export enum CleanBuildDescrepancy { + CleanFileTextDifferent, + CleanFilePresent, +} - export function verifyTscIncrementalEdits(input: VerifyTsBuildInput) { - verifyTscIncrementalEditsWorker(input); - if (input.baselineIncremental) { - verifyTscIncrementalEditsWorker({ - ...input, - subScenario: `${input.subScenario} with incremental`, - commandLineArgs: [...input.commandLineArgs, "--incremental"], - }); - } - } +export interface TscIncremental { + buildKind: BuildKind; + modifyFs: (fs: vfs.FileSystem) => void; + subScenario?: string; + commandLineArgs?: readonly string[]; + cleanBuildDiscrepancies?: () => ESMap; +} - export interface VerifyTsBuildInputWorker extends TscCompile { - incrementalScenarios: TscIncremental[]; - } - function verifyTscIncrementalEditsWorker({ - subScenario, fs, scenario, commandLineArgs, - baselineSourceMap, modifyFs, baselineReadFileCalls, baselinePrograms, - incrementalScenarios - }: VerifyTsBuildInputWorker) { - describe(`tsc ${commandLineArgs.join(" ")} ${scenario}:: ${subScenario}`, () => { - let tick: () => void; - let sys: TscCompileSystem; - let baseFs: vfs.FileSystem; - before(() => { - ({ fs: baseFs, tick } = getFsWithTime(fs())); - sys = tscCompile({ - scenario, - subScenario, - fs: () => baseFs.makeReadonly(), - commandLineArgs, - modifyFs: fs => { - if (modifyFs) modifyFs(fs); - tick(); - }, - baselineSourceMap, - baselineReadFileCalls, - baselinePrograms - }); - Debug.assert(!!incrementalScenarios.length, `${scenario}/${subScenario}:: No incremental scenarios, you probably want to use verifyTsc instead.`); - }); - after(() => { - baseFs = undefined!; - sys = undefined!; - tick = undefined!; - }); - describe("initialBuild", () => { - verifyTscBaseline(() => sys); - }); +export interface VerifyTsBuildInput extends VerifyTsBuildInputWorker { + baselineIncremental?: boolean; +} - incrementalScenarios.forEach(({ - buildKind, - modifyFs: incrementalModifyFs, - subScenario: incrementalSubScenario, - commandLineArgs: incrementalCommandLineArgs, - cleanBuildDiscrepancies, - }, index) => { - describe(incrementalSubScenario || buildKind, () => { - let newSys: TscCompileSystem; - before(() => { - Debug.assert(buildKind !== BuildKind.Initial, "Incremental edit cannot be initial compilation"); - tick(); - newSys = tscCompile({ - scenario, - subScenario: incrementalSubScenario || subScenario, - buildKind, - fs: () => sys.vfs, - commandLineArgs: incrementalCommandLineArgs || commandLineArgs, - modifyFs: fs => { - tick(); - incrementalModifyFs(fs); - tick(); - }, - baselineSourceMap, - baselineReadFileCalls, - baselinePrograms - }); - }); - after(() => { - newSys = undefined!; - }); - verifyTscBaseline(() => newSys); - verifyIncrementalCorrectness(() => ({ - scenario, - baseFs, - newSys, - commandLineArgs: incrementalCommandLineArgs || commandLineArgs, - cleanBuildDiscrepancies, - incrementalModifyFs, - modifyFs, - tick - }), index, incrementalSubScenario || subScenario); - }); - }); +export function verifyTscIncrementalEdits(input: VerifyTsBuildInput) { + verifyTscIncrementalEditsWorker(input); + if (input.baselineIncremental) { + verifyTscIncrementalEditsWorker({ + ...input, + subScenario: `${input.subScenario} with incremental`, + commandLineArgs: [...input.commandLineArgs, "--incremental"], }); } +} - export function verifyTscSerializedIncrementalEdits(input: VerifyTsBuildInput) { - verifyTscSerializedIncrementalEditsWorker(input); - if (input.baselineIncremental) { - verifyTscSerializedIncrementalEditsWorker({ - ...input, - subScenario: `${input.subScenario} with incremental`, - commandLineArgs: [...input.commandLineArgs, "--incremental"], +export interface VerifyTsBuildInputWorker extends TscCompile { + incrementalScenarios: TscIncremental[]; +} +function verifyTscIncrementalEditsWorker({ + subScenario, fs, scenario, commandLineArgs, + baselineSourceMap, modifyFs, baselineReadFileCalls, baselinePrograms, + incrementalScenarios +}: VerifyTsBuildInputWorker) { + describe(`tsc ${commandLineArgs.join(" ")} ${scenario}:: ${subScenario}`, () => { + let tick: () => void; + let sys: TscCompileSystem; + let baseFs: vfs.FileSystem; + before(() => { + ({ fs: baseFs, tick } = getFsWithTime(fs())); + sys = tscCompile({ + scenario, + subScenario, + fs: () => baseFs.makeReadonly(), + commandLineArgs, + modifyFs: fs => { + if (modifyFs) modifyFs(fs); + tick(); + }, + baselineSourceMap, + baselineReadFileCalls, + baselinePrograms }); - } - } - function verifyTscSerializedIncrementalEditsWorker({ - subScenario, fs, scenario, commandLineArgs, - baselineSourceMap, modifyFs, baselineReadFileCalls, baselinePrograms, - incrementalScenarios - }: VerifyTsBuildInputWorker) { - describe(`tsc ${commandLineArgs.join(" ")} ${scenario}:: ${subScenario} serializedEdits`, () => { Debug.assert(!!incrementalScenarios.length, `${scenario}/${subScenario}:: No incremental scenarios, you probably want to use verifyTsc instead.`); - let tick: () => void; - let sys: TscCompileSystem; - let baseFs: vfs.FileSystem; - let incrementalSys: TscCompileSystem[]; - before(() => { - ({ fs: baseFs, tick } = getFsWithTime(fs())); - sys = tscCompile({ - scenario, - subScenario, - fs: () => baseFs.makeReadonly(), - commandLineArgs, - modifyFs: fs => { - if (modifyFs) modifyFs(fs); - tick(); - }, - baselineSourceMap, - baselineReadFileCalls, - baselinePrograms - }); - incrementalScenarios.forEach(( - { buildKind, modifyFs, subScenario: incrementalSubScenario, commandLineArgs: incrementalCommandLineArgs }, - index - ) => { + }); + after(() => { + baseFs = undefined!; + sys = undefined!; + tick = undefined!; + }); + describe("initialBuild", () => { + verifyTscBaseline(() => sys); + }); + + incrementalScenarios.forEach(({ + buildKind, + modifyFs: incrementalModifyFs, + subScenario: incrementalSubScenario, + commandLineArgs: incrementalCommandLineArgs, + cleanBuildDiscrepancies, + }, index) => { + describe(incrementalSubScenario || buildKind, () => { + let newSys: TscCompileSystem; + before(() => { Debug.assert(buildKind !== BuildKind.Initial, "Incremental edit cannot be initial compilation"); tick(); - (incrementalSys || (incrementalSys = [])).push(tscCompile({ + newSys = tscCompile({ scenario, subScenario: incrementalSubScenario || subScenario, buildKind, - fs: () => index === 0 ? sys.vfs : incrementalSys[index - 1].vfs, + fs: () => sys.vfs, commandLineArgs: incrementalCommandLineArgs || commandLineArgs, modifyFs: fs => { tick(); - modifyFs(fs); + incrementalModifyFs(fs); tick(); }, baselineSourceMap, baselineReadFileCalls, baselinePrograms - })); + }); }); - }); - after(() => { - baseFs = undefined!; - sys = undefined!; - tick = undefined!; - incrementalSys = undefined!; - }); - describe("serializedBuild", () => { - - verifyTscBaseline(() => ({ - baseLine: () => { - const { file, text } = sys.baseLine(); - const texts: string[] = [text]; - incrementalSys.forEach((sys, index) => { - const incrementalScenario = incrementalScenarios[index]; - texts.push(""); - texts.push(`Change:: ${incrementalScenario.subScenario || incrementalScenario.buildKind}`); - texts.push(sys.baseLine().text); - }); - return { file, text: texts.join("\r\n") }; - } - })); - }); - describe("incremental correctness", () => { - incrementalScenarios.forEach(({ commandLineArgs: incrementalCommandLineArgs, subScenario, buildKind, cleanBuildDiscrepancies }, index) => verifyIncrementalCorrectness(() => ({ + after(() => { + newSys = undefined!; + }); + verifyTscBaseline(() => newSys); + verifyIncrementalCorrectness(() => ({ scenario, baseFs, - newSys: incrementalSys[index], + newSys, commandLineArgs: incrementalCommandLineArgs || commandLineArgs, cleanBuildDiscrepancies, - incrementalModifyFs: fs => { - for (let i = 0; i <= index; i++) { - incrementalScenarios[i].modifyFs(fs); - } - }, + incrementalModifyFs, modifyFs, tick - }), index, subScenario || buildKind)); + }), index, incrementalSubScenario || subScenario); }); }); - } + }); +} - export function enableStrict(fs: vfs.FileSystem, path: string) { - replaceText(fs, path, `"strict": false`, `"strict": true`); +export function verifyTscSerializedIncrementalEdits(input: VerifyTsBuildInput) { + verifyTscSerializedIncrementalEditsWorker(input); + if (input.baselineIncremental) { + verifyTscSerializedIncrementalEditsWorker({ + ...input, + subScenario: `${input.subScenario} with incremental`, + commandLineArgs: [...input.commandLineArgs, "--incremental"], + }); } +} +function verifyTscSerializedIncrementalEditsWorker({ + subScenario, fs, scenario, commandLineArgs, + baselineSourceMap, modifyFs, baselineReadFileCalls, baselinePrograms, + incrementalScenarios +}: VerifyTsBuildInputWorker) { + describe(`tsc ${commandLineArgs.join(" ")} ${scenario}:: ${subScenario} serializedEdits`, () => { + Debug.assert(!!incrementalScenarios.length, `${scenario}/${subScenario}:: No incremental scenarios, you probably want to use verifyTsc instead.`); + let tick: () => void; + let sys: TscCompileSystem; + let baseFs: vfs.FileSystem; + let incrementalSys: TscCompileSystem[]; + before(() => { + ({ fs: baseFs, tick } = getFsWithTime(fs())); + sys = tscCompile({ + scenario, + subScenario, + fs: () => baseFs.makeReadonly(), + commandLineArgs, + modifyFs: fs => { + if (modifyFs) modifyFs(fs); + tick(); + }, + baselineSourceMap, + baselineReadFileCalls, + baselinePrograms + }); + incrementalScenarios.forEach(( + { buildKind, modifyFs, subScenario: incrementalSubScenario, commandLineArgs: incrementalCommandLineArgs }, + index + ) => { + Debug.assert(buildKind !== BuildKind.Initial, "Incremental edit cannot be initial compilation"); + tick(); + (incrementalSys || (incrementalSys = [])).push(tscCompile({ + scenario, + subScenario: incrementalSubScenario || subScenario, + buildKind, + fs: () => index === 0 ? sys.vfs : incrementalSys[index - 1].vfs, + commandLineArgs: incrementalCommandLineArgs || commandLineArgs, + modifyFs: fs => { + tick(); + modifyFs(fs); + tick(); + }, + baselineSourceMap, + baselineReadFileCalls, + baselinePrograms + })); + }); + }); + after(() => { + baseFs = undefined!; + sys = undefined!; + tick = undefined!; + incrementalSys = undefined!; + }); + describe("serializedBuild", () => { + + verifyTscBaseline(() => ({ + baseLine: () => { + const { file, text } = sys.baseLine(); + const texts: string[] = [text]; + incrementalSys.forEach((sys, index) => { + const incrementalScenario = incrementalScenarios[index]; + texts.push(""); + texts.push(`Change:: ${incrementalScenario.subScenario || incrementalScenario.buildKind}`); + texts.push(sys.baseLine().text); + }); + return { file, text: texts.join("\r\n") }; + } + })); + }); + describe("incremental correctness", () => { + incrementalScenarios.forEach(({ commandLineArgs: incrementalCommandLineArgs, subScenario, buildKind, cleanBuildDiscrepancies }, index) => verifyIncrementalCorrectness(() => ({ + scenario, + baseFs, + newSys: incrementalSys[index], + commandLineArgs: incrementalCommandLineArgs || commandLineArgs, + cleanBuildDiscrepancies, + incrementalModifyFs: fs => { + for (let i = 0; i <= index; i++) { + incrementalScenarios[i].modifyFs(fs); + } + }, + modifyFs, + tick + }), index, subScenario || buildKind)); + }); + }); +} + +export function enableStrict(fs: vfs.FileSystem, path: string) { + replaceText(fs, path, `"strict": false`, `"strict": true`); +} - export function addTestPrologue(fs: vfs.FileSystem, path: string, prologue: string) { - prependText(fs, path, `${prologue} +export function addTestPrologue(fs: vfs.FileSystem, path: string, prologue: string) { + prependText(fs, path, `${prologue} `); - } +} - export function addShebang(fs: vfs.FileSystem, project: string, file: string) { - prependText(fs, `src/${project}/${file}.ts`, `#!someshebang ${project} ${file} +export function addShebang(fs: vfs.FileSystem, project: string, file: string) { + prependText(fs, `src/${project}/${file}.ts`, `#!someshebang ${project} ${file} `); - } +} - export function restContent(project: string, file: string) { - return `function for${project}${file}Rest() { +export function restContent(project: string, file: string) { + return `function for${project}${file}Rest() { const { b, ...rest } = { a: 10, b: 30, yy: 30 }; }`; - } +} - function nonrestContent(project: string, file: string) { - return `function for${project}${file}Rest() { }`; - } +function nonrestContent(project: string, file: string) { + return `function for${project}${file}Rest() { }`; +} - export function addRest(fs: vfs.FileSystem, project: string, file: string) { - appendText(fs, `src/${project}/${file}.ts`, restContent(project, file)); - } +export function addRest(fs: vfs.FileSystem, project: string, file: string) { + appendText(fs, `src/${project}/${file}.ts`, restContent(project, file)); +} - export function removeRest(fs: vfs.FileSystem, project: string, file: string) { - replaceText(fs, `src/${project}/${file}.ts`, restContent(project, file), nonrestContent(project, file)); - } +export function removeRest(fs: vfs.FileSystem, project: string, file: string) { + replaceText(fs, `src/${project}/${file}.ts`, restContent(project, file), nonrestContent(project, file)); +} - export function addStubFoo(fs: vfs.FileSystem, project: string, file: string) { - appendText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file)); - } +export function addStubFoo(fs: vfs.FileSystem, project: string, file: string) { + appendText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file)); +} - export function changeStubToRest(fs: vfs.FileSystem, project: string, file: string) { - replaceText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file), restContent(project, file)); - } +export function changeStubToRest(fs: vfs.FileSystem, project: string, file: string) { + replaceText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file), restContent(project, file)); +} - export function addSpread(fs: vfs.FileSystem, project: string, file: string) { - const path = `src/${project}/${file}.ts`; - const content = fs.readFileSync(path, "utf8"); - fs.writeFileSync(path, `${content} +export function addSpread(fs: vfs.FileSystem, project: string, file: string) { + const path = `src/${project}/${file}.ts`; + const content = fs.readFileSync(path, "utf8"); + fs.writeFileSync(path, `${content} function ${project}${file}Spread(...b: number[]) { } const ${project}${file}_ar = [20, 30]; ${project}${file}Spread(10, ...${project}${file}_ar);`); - replaceText(fs, `src/${project}/tsconfig.json`, `"strict": false,`, `"strict": false, + replaceText(fs, `src/${project}/tsconfig.json`, `"strict": false,`, `"strict": false, "downlevelIteration": true,`); - } +} - export function getTripleSlashRef(project: string) { - return `/src/${project}/tripleRef.d.ts`; - } +export function getTripleSlashRef(project: string) { + return `/src/${project}/tripleRef.d.ts`; +} - export function addTripleSlashRef(fs: vfs.FileSystem, project: string, file: string) { - fs.writeFileSync(getTripleSlashRef(project), `declare class ${project}${file} { }`); - prependText(fs, `src/${project}/${file}.ts`, `/// +export function addTripleSlashRef(fs: vfs.FileSystem, project: string, file: string) { + fs.writeFileSync(getTripleSlashRef(project), `declare class ${project}${file} { }`); + prependText(fs, `src/${project}/${file}.ts`, `/// const ${file}Const = new ${project}${file}(); `); - } +} } diff --git a/src/testRunner/unittests/tsbuild/inferredTypeFromTransitiveModule.ts b/src/testRunner/unittests/tsbuild/inferredTypeFromTransitiveModule.ts index e2d27de684a0a..7e1c7a49aa7f7 100644 --- a/src/testRunner/unittests/tsbuild/inferredTypeFromTransitiveModule.ts +++ b/src/testRunner/unittests/tsbuild/inferredTypeFromTransitiveModule.ts @@ -1,90 +1,90 @@ namespace ts { - describe("unittests:: tsbuild:: inferredTypeFromTransitiveModule::", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/inferredTypeFromTransitiveModule"); - }); - after(() => { - projFs = undefined!; - }); +describe("unittests:: tsbuild:: inferredTypeFromTransitiveModule::", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = loadProjectFromDisk("tests/projects/inferredTypeFromTransitiveModule"); + }); + after(() => { + projFs = undefined!; + }); - verifyTscSerializedIncrementalEdits({ - scenario: "inferredTypeFromTransitiveModule", - subScenario: "inferred type from transitive module", - fs: () => projFs, - commandLineArgs: ["--b", "/src", "--verbose"], - incrementalScenarios: [ - { - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: changeBarParam, - }, - { - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: changeBarParamBack, - }, - ], - }); + verifyTscSerializedIncrementalEdits({ + scenario: "inferredTypeFromTransitiveModule", + subScenario: "inferred type from transitive module", + fs: () => projFs, + commandLineArgs: ["--b", "/src", "--verbose"], + incrementalScenarios: [ + { + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: changeBarParam, + }, + { + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: changeBarParamBack, + }, + ], + }); - verifyTscSerializedIncrementalEdits({ - subScenario: "inferred type from transitive module with isolatedModules", - fs: () => projFs, - scenario: "inferredTypeFromTransitiveModule", - commandLineArgs: ["--b", "/src", "--verbose"], - modifyFs: changeToIsolatedModules, - incrementalScenarios: [ - { - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: changeBarParam - }, - { - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: changeBarParamBack, - }, - ] - }); + verifyTscSerializedIncrementalEdits({ + subScenario: "inferred type from transitive module with isolatedModules", + fs: () => projFs, + scenario: "inferredTypeFromTransitiveModule", + commandLineArgs: ["--b", "/src", "--verbose"], + modifyFs: changeToIsolatedModules, + incrementalScenarios: [ + { + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: changeBarParam + }, + { + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: changeBarParamBack, + }, + ] + }); - verifyTscSerializedIncrementalEdits({ - scenario: "inferredTypeFromTransitiveModule", - subScenario: "reports errors in files affected by change in signature with isolatedModules", - fs: () => projFs, - commandLineArgs: ["--b", "/src", "--verbose"], - modifyFs: fs => { - changeToIsolatedModules(fs); - appendText(fs, "/src/lazyIndex.ts", ` + verifyTscSerializedIncrementalEdits({ + scenario: "inferredTypeFromTransitiveModule", + subScenario: "reports errors in files affected by change in signature with isolatedModules", + fs: () => projFs, + commandLineArgs: ["--b", "/src", "--verbose"], + modifyFs: fs => { + changeToIsolatedModules(fs); + appendText(fs, "/src/lazyIndex.ts", ` import { default as bar } from './bar'; bar("hello");`); + }, + incrementalScenarios: [ + { + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: changeBarParam + }, + { + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: changeBarParamBack, + }, + { + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: changeBarParam + }, + { + subScenario: "Fix Error", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => replaceText(fs, "/src/lazyIndex.ts", `bar("hello")`, "bar()") }, - incrementalScenarios: [ - { - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: changeBarParam - }, - { - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: changeBarParamBack, - }, - { - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: changeBarParam - }, - { - subScenario: "Fix Error", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => replaceText(fs, "/src/lazyIndex.ts", `bar("hello")`, "bar()") - }, - ] - }); + ] }); +}); - function changeToIsolatedModules(fs: vfs.FileSystem) { - replaceText(fs, "/src/tsconfig.json", `"incremental": true`, `"incremental": true, "isolatedModules": true`); - } +function changeToIsolatedModules(fs: vfs.FileSystem) { + replaceText(fs, "/src/tsconfig.json", `"incremental": true`, `"incremental": true, "isolatedModules": true`); +} - function changeBarParam(fs: vfs.FileSystem) { - replaceText(fs, "/src/bar.ts", "param: string", ""); - } +function changeBarParam(fs: vfs.FileSystem) { + replaceText(fs, "/src/bar.ts", "param: string", ""); +} - function changeBarParamBack(fs: vfs.FileSystem) { - replaceText(fs, "/src/bar.ts", "foobar()", "foobar(param: string)"); - } +function changeBarParamBack(fs: vfs.FileSystem) { + replaceText(fs, "/src/bar.ts", "foobar()", "foobar(param: string)"); +} } diff --git a/src/testRunner/unittests/tsbuild/javascriptProjectEmit.ts b/src/testRunner/unittests/tsbuild/javascriptProjectEmit.ts index 6786205bb20a1..3b6279836a320 100644 --- a/src/testRunner/unittests/tsbuild/javascriptProjectEmit.ts +++ b/src/testRunner/unittests/tsbuild/javascriptProjectEmit.ts @@ -1,17 +1,17 @@ namespace ts { - describe("unittests:: tsbuild:: javascriptProjectEmit::", () => { - verifyTsc({ - scenario: "javascriptProjectEmit", - subScenario: `loads js-based projects and emits them correctly`, - fs: () => loadProjectFromFiles({ - "/src/common/nominal.js": Utils.dedent` +describe("unittests:: tsbuild:: javascriptProjectEmit::", () => { + verifyTsc({ + scenario: "javascriptProjectEmit", + subScenario: `loads js-based projects and emits them correctly`, + fs: () => loadProjectFromFiles({ + "/src/common/nominal.js": Utils.dedent` /** * @template T, Name * @typedef {T & {[Symbol.species]: Name}} Nominal */ module.exports = {}; `, - "/src/common/tsconfig.json": Utils.dedent` + "/src/common/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -19,14 +19,14 @@ namespace ts { }, "include": ["nominal.js"] }`, - "/src/sub-project/index.js": Utils.dedent` + "/src/sub-project/index.js": Utils.dedent` import { Nominal } from '../common/nominal'; /** * @typedef {Nominal} MyNominal */ `, - "/src/sub-project/tsconfig.json": Utils.dedent` + "/src/sub-project/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -37,7 +37,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/sub-project-2/index.js": Utils.dedent` + "/src/sub-project-2/index.js": Utils.dedent` import { MyNominal } from '../sub-project/index'; const variable = { @@ -51,7 +51,7 @@ namespace ts { return 'key'; } `, - "/src/sub-project-2/tsconfig.json": Utils.dedent` + "/src/sub-project-2/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -62,7 +62,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/tsconfig.json": Utils.dedent` + "/src/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true @@ -73,7 +73,7 @@ namespace ts { ], "include": [] }`, - "/src/tsconfig.base.json": Utils.dedent` + "/src/tsconfig.base.json": Utils.dedent` { "compilerOptions": { "skipLibCheck": true, @@ -84,21 +84,21 @@ namespace ts { "declaration": true } }`, - }, symbolLibContent), - commandLineArgs: ["-b", "/src"] - }); + }, symbolLibContent), + commandLineArgs: ["-b", "/src"] + }); - verifyTscSerializedIncrementalEdits({ - scenario: "javascriptProjectEmit", - subScenario: `modifies outfile js projects and concatenates them correctly`, - fs: () => loadProjectFromFiles({ - "/src/common/nominal.js": Utils.dedent` + verifyTscSerializedIncrementalEdits({ + scenario: "javascriptProjectEmit", + subScenario: `modifies outfile js projects and concatenates them correctly`, + fs: () => loadProjectFromFiles({ + "/src/common/nominal.js": Utils.dedent` /** * @template T, Name * @typedef {T & {[Symbol.species]: Name}} Nominal */ `, - "/src/common/tsconfig.json": Utils.dedent` + "/src/common/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -108,13 +108,13 @@ namespace ts { }, "include": ["nominal.js"] }`, - "/src/sub-project/index.js": Utils.dedent` + "/src/sub-project/index.js": Utils.dedent` /** * @typedef {Nominal} MyNominal */ const c = /** @type {*} */(null); `, - "/src/sub-project/tsconfig.json": Utils.dedent` + "/src/sub-project/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -127,7 +127,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/sub-project-2/index.js": Utils.dedent` + "/src/sub-project-2/index.js": Utils.dedent` const variable = { key: /** @type {MyNominal} */('value'), }; @@ -139,7 +139,7 @@ namespace ts { return 'key'; } `, - "/src/sub-project-2/tsconfig.json": Utils.dedent` + "/src/sub-project-2/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -152,7 +152,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/tsconfig.json": Utils.dedent` + "/src/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true, @@ -164,7 +164,7 @@ namespace ts { ], "include": [] }`, - "/src/tsconfig.base.json": Utils.dedent` + "/src/tsconfig.base.json": Utils.dedent` { "compilerOptions": { "skipLibCheck": true, @@ -174,27 +174,27 @@ namespace ts { "declaration": true } }`, - }, symbolLibContent), - commandLineArgs: ["-b", "/src"], - incrementalScenarios: [{ - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => replaceText(fs, "/src/sub-project/index.js", "null", "undefined") - }] - }); + }, symbolLibContent), + commandLineArgs: ["-b", "/src"], + incrementalScenarios: [{ + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => replaceText(fs, "/src/sub-project/index.js", "null", "undefined") + }] + }); - verifyTsc({ - scenario: "javascriptProjectEmit", - subScenario: `loads js-based projects with non-moved json files and emits them correctly`, - fs: () => loadProjectFromFiles({ - "/src/common/obj.json": Utils.dedent` + verifyTsc({ + scenario: "javascriptProjectEmit", + subScenario: `loads js-based projects with non-moved json files and emits them correctly`, + fs: () => loadProjectFromFiles({ + "/src/common/obj.json": Utils.dedent` { "val": 42 }`, - "/src/common/index.ts": Utils.dedent` + "/src/common/index.ts": Utils.dedent` import x = require("./obj.json"); export = x; `, - "/src/common/tsconfig.json": Utils.dedent` + "/src/common/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -203,12 +203,12 @@ namespace ts { }, "include": ["index.ts", "obj.json"] }`, - "/src/sub-project/index.js": Utils.dedent` + "/src/sub-project/index.js": Utils.dedent` import mod from '../common'; export const m = mod; `, - "/src/sub-project/tsconfig.json": Utils.dedent` + "/src/sub-project/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -219,7 +219,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/sub-project-2/index.js": Utils.dedent` + "/src/sub-project-2/index.js": Utils.dedent` import { m } from '../sub-project/index'; const variable = { @@ -230,7 +230,7 @@ namespace ts { return variable; } `, - "/src/sub-project-2/tsconfig.json": Utils.dedent` + "/src/sub-project-2/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -241,7 +241,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/tsconfig.json": Utils.dedent` + "/src/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true @@ -252,7 +252,7 @@ namespace ts { ], "include": [] }`, - "/src/tsconfig.base.json": Utils.dedent` + "/src/tsconfig.base.json": Utils.dedent` { "compilerOptions": { "skipLibCheck": true, @@ -265,8 +265,8 @@ namespace ts { "declaration": true } }`, - }, symbolLibContent), - commandLineArgs: ["-b", "/src"] - }); + }, symbolLibContent), + commandLineArgs: ["-b", "/src"] }); +}); } diff --git a/src/testRunner/unittests/tsbuild/lateBoundSymbol.ts b/src/testRunner/unittests/tsbuild/lateBoundSymbol.ts index 953e7f7f045a3..aae47abceb68e 100644 --- a/src/testRunner/unittests/tsbuild/lateBoundSymbol.ts +++ b/src/testRunner/unittests/tsbuild/lateBoundSymbol.ts @@ -1,20 +1,20 @@ namespace ts { - describe("unittests:: tsbuild:: lateBoundSymbol:: interface is merged and contains late bound member", () => { - verifyTscSerializedIncrementalEdits({ - subScenario: "interface is merged and contains late bound member", - fs: () => loadProjectFromDisk("tests/projects/lateBoundSymbol"), - scenario: "lateBoundSymbol", - commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"], - incrementalScenarios: [ - { - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => replaceText(fs, "/src/src/main.ts", "const x = 10;", ""), - }, - { - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => appendText(fs, "/src/src/main.ts", "const x = 10;"), - }, - ] - }); +describe("unittests:: tsbuild:: lateBoundSymbol:: interface is merged and contains late bound member", () => { + verifyTscSerializedIncrementalEdits({ + subScenario: "interface is merged and contains late bound member", + fs: () => loadProjectFromDisk("tests/projects/lateBoundSymbol"), + scenario: "lateBoundSymbol", + commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"], + incrementalScenarios: [ + { + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => replaceText(fs, "/src/src/main.ts", "const x = 10;", ""), + }, + { + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => appendText(fs, "/src/src/main.ts", "const x = 10;"), + }, + ] }); +}); } diff --git a/src/testRunner/unittests/tsbuild/moduleResolution.ts b/src/testRunner/unittests/tsbuild/moduleResolution.ts index 0ac722293626f..7797baa59e1dc 100644 --- a/src/testRunner/unittests/tsbuild/moduleResolution.ts +++ b/src/testRunner/unittests/tsbuild/moduleResolution.ts @@ -1,325 +1,325 @@ namespace ts.tscWatch { - describe("unittests:: tsbuild:: moduleResolution:: handles the modules and options from referenced project correctly", () => { - function sys(optionsToExtend?: CompilerOptions) { - return createWatchedSystem([ - { - path: `${projectRoot}/packages/pkg1/index.ts`, - content: Utils.dedent` +describe("unittests:: tsbuild:: moduleResolution:: handles the modules and options from referenced project correctly", () => { + function sys(optionsToExtend?: CompilerOptions) { + return createWatchedSystem([ + { + path: `${projectRoot}/packages/pkg1/index.ts`, + content: Utils.dedent` import type { TheNum } from 'pkg2' export const theNum: TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { outDir: "build", ...optionsToExtend }, - references: [{ path: "../pkg2" }] - }) - }, - { - path: `${projectRoot}/packages/pkg2/const.ts`, - content: `export type TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg2/index.ts`, - content: `export type { TheNum } from 'const';` - }, - { - path: `${projectRoot}/packages/pkg2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "build", - baseUrl: ".", - ...optionsToExtend - } - }) - }, - { - path: `${projectRoot}/packages/pkg2/package.json`, - content: JSON.stringify({ - name: "pkg2", - version: "1.0.0", - main: "build/index.js", - }) - }, - { - path: `${projectRoot}/node_modules/pkg2`, - symLink: `${projectRoot}/packages/pkg2`, - }, - libFile - ], { currentDirectory: projectRoot }); - } + }, + { + path: `${projectRoot}/packages/pkg1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { outDir: "build", ...optionsToExtend }, + references: [{ path: "../pkg2" }] + }) + }, + { + path: `${projectRoot}/packages/pkg2/const.ts`, + content: `export type TheNum = 42;` + }, + { + path: `${projectRoot}/packages/pkg2/index.ts`, + content: `export type { TheNum } from 'const';` + }, + { + path: `${projectRoot}/packages/pkg2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "build", + baseUrl: ".", + ...optionsToExtend + } + }) + }, + { + path: `${projectRoot}/packages/pkg2/package.json`, + content: JSON.stringify({ + name: "pkg2", + version: "1.0.0", + main: "build/index.js", + }) + }, + { + path: `${projectRoot}/node_modules/pkg2`, + symLink: `${projectRoot}/packages/pkg2`, + }, + libFile + ], { currentDirectory: projectRoot }); + } - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: `resolves specifier in output declaration file from referenced project correctly`, - sys, - commandLineArgs: ["-b", "packages/pkg1", "--verbose", "--traceResolution"], - changes: emptyArray - }); + verifyTscWatch({ + scenario: "moduleResolution", + subScenario: `resolves specifier in output declaration file from referenced project correctly`, + sys, + commandLineArgs: ["-b", "packages/pkg1", "--verbose", "--traceResolution"], + changes: emptyArray + }); - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: `resolves specifier in output declaration file from referenced project correctly with preserveSymlinks`, - sys: () => sys({ preserveSymlinks: true }), - commandLineArgs: ["-b", "packages/pkg1", "--verbose", "--traceResolution"], - changes: emptyArray - }); + verifyTscWatch({ + scenario: "moduleResolution", + subScenario: `resolves specifier in output declaration file from referenced project correctly with preserveSymlinks`, + sys: () => sys({ preserveSymlinks: true }), + commandLineArgs: ["-b", "packages/pkg1", "--verbose", "--traceResolution"], + changes: emptyArray + }); - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: `resolves specifier in output declaration file from referenced project correctly with cts and mts extensions`, - sys: () => createWatchedSystem([ - { - path: `${projectRoot}/packages/pkg1/package.json`, - content: JSON.stringify({ - name: "pkg1", - version: "1.0.0", - main: "build/index.js", - type: "module" - }) - }, - { - path: `${projectRoot}/packages/pkg1/index.ts`, - content: Utils.dedent` + verifyTscWatch({ + scenario: "moduleResolution", + subScenario: `resolves specifier in output declaration file from referenced project correctly with cts and mts extensions`, + sys: () => createWatchedSystem([ + { + path: `${projectRoot}/packages/pkg1/package.json`, + content: JSON.stringify({ + name: "pkg1", + version: "1.0.0", + main: "build/index.js", + type: "module" + }) + }, + { + path: `${projectRoot}/packages/pkg1/index.ts`, + content: Utils.dedent` import type { TheNum } from 'pkg2' export const theNum: TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - outDir: "build", - module: "node12", - }, - references: [{ path: "../pkg2" }] - }) - }, - { - path: `${projectRoot}/packages/pkg2/const.cts`, - content: `export type TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg2/index.ts`, - content: `export type { TheNum } from './const.cjs';` - }, - { - path: `${projectRoot}/packages/pkg2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "build", - module: "node12", - } - }) - }, - { - path: `${projectRoot}/packages/pkg2/package.json`, - content: JSON.stringify({ - name: "pkg2", - version: "1.0.0", - main: "build/index.js", - type: "module" - }) - }, - { - path: `${projectRoot}/node_modules/pkg2`, - symLink: `${projectRoot}/packages/pkg2`, - }, - { ...libFile, path: `/a/lib/lib.es2020.full.d.ts` } - ], { currentDirectory: projectRoot }), - commandLineArgs: ["-b", "packages/pkg1", "-w", "--verbose", "--traceResolution"], - changes: [ - { - caption: "reports import errors after change to package file", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "removes those errors when a package file is changed back", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"commonjs"`, `"module"`), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "reports import errors after change to package file", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "removes those errors when a package file is changed to cjs extensions", - change: sys => { - replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `"build/index.js"`, `"build/index.cjs"`); - sys.renameFile(`${projectRoot}/packages/pkg2/index.ts`, `${projectRoot}/packages/pkg2/index.cts`); + }, + { + path: `${projectRoot}/packages/pkg1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + outDir: "build", + module: "node12", }, - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + references: [{ path: "../pkg2" }] + }) + }, + { + path: `${projectRoot}/packages/pkg2/const.cts`, + content: `export type TheNum = 42;` + }, + { + path: `${projectRoot}/packages/pkg2/index.ts`, + content: `export type { TheNum } from './const.cjs';` + }, + { + path: `${projectRoot}/packages/pkg2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "build", + module: "node12", + } + }) + }, + { + path: `${projectRoot}/packages/pkg2/package.json`, + content: JSON.stringify({ + name: "pkg2", + version: "1.0.0", + main: "build/index.js", + type: "module" + }) + }, + { + path: `${projectRoot}/node_modules/pkg2`, + symLink: `${projectRoot}/packages/pkg2`, + }, + { ...libFile, path: `/a/lib/lib.es2020.full.d.ts` } + ], { currentDirectory: projectRoot }), + commandLineArgs: ["-b", "packages/pkg1", "-w", "--verbose", "--traceResolution"], + changes: [ + { + caption: "reports import errors after change to package file", + change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "removes those errors when a package file is changed back", + change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"commonjs"`, `"module"`), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "reports import errors after change to package file", + change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "removes those errors when a package file is changed to cjs extensions", + change: sys => { + replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `"build/index.js"`, `"build/index.cjs"`); + sys.renameFile(`${projectRoot}/packages/pkg2/index.ts`, `${projectRoot}/packages/pkg2/index.cts`); + }, + timeouts: runQueuedTimeoutCallbacks, + }, + ] + }); - verifyTsc({ - scenario: "moduleResolution", - subScenario: `type reference resolution uses correct options for different resolution options referenced project`, - fs: () => loadProjectFromFiles({ - "/src/packages/pkg1_index.ts": `export const theNum: TheNum = "type1";`, - "/src/packages/pkg1.tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true, typeRoots: ["./typeroot1"] }, - files: ["./pkg1_index.ts"] - }), - "/src/packages/typeroot1/sometype/index.d.ts": Utils.dedent`declare type TheNum = "type1";`, - "/src/packages/pkg2_index.ts": `export const theNum: TheNum2 = "type2";`, - "/src/packages/pkg2.tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true, typeRoots: ["./typeroot2"] }, - files: ["./pkg2_index.ts"] - }), - "/src/packages/typeroot2/sometype/index.d.ts": Utils.dedent`declare type TheNum2 = "type2";`, + verifyTsc({ + scenario: "moduleResolution", + subScenario: `type reference resolution uses correct options for different resolution options referenced project`, + fs: () => loadProjectFromFiles({ + "/src/packages/pkg1_index.ts": `export const theNum: TheNum = "type1";`, + "/src/packages/pkg1.tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true, typeRoots: ["./typeroot1"] }, + files: ["./pkg1_index.ts"] + }), + "/src/packages/typeroot1/sometype/index.d.ts": Utils.dedent`declare type TheNum = "type1";`, + "/src/packages/pkg2_index.ts": `export const theNum: TheNum2 = "type2";`, + "/src/packages/pkg2.tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true, typeRoots: ["./typeroot2"] }, + files: ["./pkg2_index.ts"] }), - commandLineArgs: ["-b", "/src/packages/pkg1.tsconfig.json", "/src/packages/pkg2.tsconfig.json", "--verbose", "--traceResolution"], - }); + "/src/packages/typeroot2/sometype/index.d.ts": Utils.dedent`declare type TheNum2 = "type2";`, + }), + commandLineArgs: ["-b", "/src/packages/pkg1.tsconfig.json", "/src/packages/pkg2.tsconfig.json", "--verbose", "--traceResolution"], + }); - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: `watches for changes to package-json main fields`, - sys: () => createWatchedSystem([ - { - path: `${projectRoot}/packages/pkg1/package.json`, - content: JSON.stringify({ - name: "pkg1", - version: "1.0.0", - main: "build/index.js", - }) - }, - { - path: `${projectRoot}/packages/pkg1/index.ts`, - content: Utils.dedent` + verifyTscWatch({ + scenario: "moduleResolution", + subScenario: `watches for changes to package-json main fields`, + sys: () => createWatchedSystem([ + { + path: `${projectRoot}/packages/pkg1/package.json`, + content: JSON.stringify({ + name: "pkg1", + version: "1.0.0", + main: "build/index.js", + }) + }, + { + path: `${projectRoot}/packages/pkg1/index.ts`, + content: Utils.dedent` import type { TheNum } from 'pkg2' export const theNum: TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - outDir: "build", - }, - }) - }, - { - path: `${projectRoot}/packages/pkg2/build/const.d.ts`, - content: `export type TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg2/build/index.d.ts`, - content: `export type { TheNum } from './const.js';` - }, - { - path: `${projectRoot}/packages/pkg2/build/other.d.ts`, - content: `export type TheStr = string;` - }, - { - path: `${projectRoot}/packages/pkg2/package.json`, - content: JSON.stringify({ - name: "pkg2", - version: "1.0.0", - main: "build/index.js", - }) - }, - { - path: `${projectRoot}/node_modules/pkg2`, - symLink: `${projectRoot}/packages/pkg2`, - }, - libFile - ], { currentDirectory: projectRoot }), - commandLineArgs: ["--project", "./packages/pkg1/tsconfig.json", "-w", "--traceResolution"], - changes: [ - { - caption: "reports import errors after change to package file", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "removes those errors when a package file is changed back", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + }, + { + path: `${projectRoot}/packages/pkg1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + outDir: "build", + }, + }) + }, + { + path: `${projectRoot}/packages/pkg2/build/const.d.ts`, + content: `export type TheNum = 42;` + }, + { + path: `${projectRoot}/packages/pkg2/build/index.d.ts`, + content: `export type { TheNum } from './const.js';` + }, + { + path: `${projectRoot}/packages/pkg2/build/other.d.ts`, + content: `export type TheStr = string;` + }, + { + path: `${projectRoot}/packages/pkg2/package.json`, + content: JSON.stringify({ + name: "pkg2", + version: "1.0.0", + main: "build/index.js", + }) + }, + { + path: `${projectRoot}/node_modules/pkg2`, + symLink: `${projectRoot}/packages/pkg2`, + }, + libFile + ], { currentDirectory: projectRoot }), + commandLineArgs: ["--project", "./packages/pkg1/tsconfig.json", "-w", "--traceResolution"], + changes: [ + { + caption: "reports import errors after change to package file", + change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "removes those errors when a package file is changed back", + change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`), + timeouts: runQueuedTimeoutCallbacks, + }, + ] + }); - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: `build mode watches for changes to package-json main fields`, - sys: () => createWatchedSystem([ - { - path: `${projectRoot}/packages/pkg1/package.json`, - content: JSON.stringify({ - name: "pkg1", - version: "1.0.0", - main: "build/index.js", - }) - }, - { - path: `${projectRoot}/packages/pkg1/index.ts`, - content: Utils.dedent` + verifyTscWatch({ + scenario: "moduleResolution", + subScenario: `build mode watches for changes to package-json main fields`, + sys: () => createWatchedSystem([ + { + path: `${projectRoot}/packages/pkg1/package.json`, + content: JSON.stringify({ + name: "pkg1", + version: "1.0.0", + main: "build/index.js", + }) + }, + { + path: `${projectRoot}/packages/pkg1/index.ts`, + content: Utils.dedent` import type { TheNum } from 'pkg2' export const theNum: TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - outDir: "build", - }, - references: [{ path: "../pkg2" }] - }) - }, - { - path: `${projectRoot}/packages/pkg2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "build", - baseUrl: ".", - } - }) - }, - { - path: `${projectRoot}/packages/pkg2/const.ts`, - content: `export type TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg2/index.ts`, - content: `export type { TheNum } from './const.js';` - }, - { - path: `${projectRoot}/packages/pkg2/other.ts`, - content: `export type TheStr = string;` - }, - { - path: `${projectRoot}/packages/pkg2/package.json`, - content: JSON.stringify({ - name: "pkg2", - version: "1.0.0", - main: "build/index.js", - }) - }, - { - path: `${projectRoot}/node_modules/pkg2`, - symLink: `${projectRoot}/packages/pkg2`, - }, - libFile - ], { currentDirectory: projectRoot }), - commandLineArgs: ["-b", "packages/pkg1", "--verbose", "-w", "--traceResolution"], - changes: [ - { - caption: "reports import errors after change to package file", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "removes those errors when a package file is changed back", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + }, + { + path: `${projectRoot}/packages/pkg1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + outDir: "build", + }, + references: [{ path: "../pkg2" }] + }) + }, + { + path: `${projectRoot}/packages/pkg2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "build", + baseUrl: ".", + } + }) + }, + { + path: `${projectRoot}/packages/pkg2/const.ts`, + content: `export type TheNum = 42;` + }, + { + path: `${projectRoot}/packages/pkg2/index.ts`, + content: `export type { TheNum } from './const.js';` + }, + { + path: `${projectRoot}/packages/pkg2/other.ts`, + content: `export type TheStr = string;` + }, + { + path: `${projectRoot}/packages/pkg2/package.json`, + content: JSON.stringify({ + name: "pkg2", + version: "1.0.0", + main: "build/index.js", + }) + }, + { + path: `${projectRoot}/node_modules/pkg2`, + symLink: `${projectRoot}/packages/pkg2`, + }, + libFile + ], { currentDirectory: projectRoot }), + commandLineArgs: ["-b", "packages/pkg1", "--verbose", "-w", "--traceResolution"], + changes: [ + { + caption: "reports import errors after change to package file", + change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "removes those errors when a package file is changed back", + change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`), + timeouts: runQueuedTimeoutCallbacks, + }, + ] }); +}); } diff --git a/src/testRunner/unittests/tsbuild/moduleSpecifiers.ts b/src/testRunner/unittests/tsbuild/moduleSpecifiers.ts index 9c560a1bd6f4e..6cc86dcade741 100644 --- a/src/testRunner/unittests/tsbuild/moduleSpecifiers.ts +++ b/src/testRunner/unittests/tsbuild/moduleSpecifiers.ts @@ -1,16 +1,16 @@ namespace ts { - // https://github.com/microsoft/TypeScript/issues/31696 - describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers to referenced projects resolve correctly", () => { - verifyTsc({ - scenario: "moduleSpecifiers", - subScenario: `synthesized module specifiers resolve correctly`, - fs: () => loadProjectFromFiles({ - "/src/solution/common/nominal.ts": Utils.dedent` +// https://github.com/microsoft/TypeScript/issues/31696 +describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers to referenced projects resolve correctly", () => { + verifyTsc({ + scenario: "moduleSpecifiers", + subScenario: `synthesized module specifiers resolve correctly`, + fs: () => loadProjectFromFiles({ + "/src/solution/common/nominal.ts": Utils.dedent` export declare type Nominal = T & { [Symbol.species]: Name; }; `, - "/src/solution/common/tsconfig.json": Utils.dedent` + "/src/solution/common/tsconfig.json": Utils.dedent` { "extends": "../../tsconfig.base.json", "compilerOptions": { @@ -18,12 +18,12 @@ namespace ts { }, "include": ["nominal.ts"] }`, - "/src/solution/sub-project/index.ts": Utils.dedent` + "/src/solution/sub-project/index.ts": Utils.dedent` import { Nominal } from '../common/nominal'; export type MyNominal = Nominal; `, - "/src/solution/sub-project/tsconfig.json": Utils.dedent` + "/src/solution/sub-project/tsconfig.json": Utils.dedent` { "extends": "../../tsconfig.base.json", "compilerOptions": { @@ -34,7 +34,7 @@ namespace ts { ], "include": ["./index.ts"] }`, - "/src/solution/sub-project-2/index.ts": Utils.dedent` + "/src/solution/sub-project-2/index.ts": Utils.dedent` import { MyNominal } from '../sub-project/index'; const variable = { @@ -45,7 +45,7 @@ namespace ts { return 'key'; } `, - "/src/solution/sub-project-2/tsconfig.json": Utils.dedent` + "/src/solution/sub-project-2/tsconfig.json": Utils.dedent` { "extends": "../../tsconfig.base.json", "compilerOptions": { @@ -56,7 +56,7 @@ namespace ts { ], "include": ["./index.ts"] }`, - "/src/solution/tsconfig.json": Utils.dedent` + "/src/solution/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true @@ -67,7 +67,7 @@ namespace ts { ], "include": [] }`, - "/src/tsconfig.base.json": Utils.dedent` + "/src/tsconfig.base.json": Utils.dedent` { "compilerOptions": { "skipLibCheck": true, @@ -75,7 +75,7 @@ namespace ts { "outDir": "lib", } }`, - "/src/tsconfig.json": Utils.dedent`{ + "/src/tsconfig.json": Utils.dedent`{ "compilerOptions": { "composite": true }, @@ -84,35 +84,35 @@ namespace ts { ], "include": [] }` - }, symbolLibContent), - commandLineArgs: ["-b", "/src", "--verbose"] - }); + }, symbolLibContent), + commandLineArgs: ["-b", "/src", "--verbose"] }); +}); - // https://github.com/microsoft/TypeScript/issues/44434 but with `module: node12`, some `exports` maps blocking direct access, and no `baseUrl` - describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers across referenced projects resolve correctly", () => { - verifyTsc({ - scenario: "moduleSpecifiers", - subScenario: `synthesized module specifiers across projects resolve correctly`, - fs: () => loadProjectFromFiles({ - "/src/src-types/index.ts": Utils.dedent` +// https://github.com/microsoft/TypeScript/issues/44434 but with `module: node12`, some `exports` maps blocking direct access, and no `baseUrl` +describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers across referenced projects resolve correctly", () => { + verifyTsc({ + scenario: "moduleSpecifiers", + subScenario: `synthesized module specifiers across projects resolve correctly`, + fs: () => loadProjectFromFiles({ + "/src/src-types/index.ts": Utils.dedent` export * from './dogconfig.js';`, - "/src/src-types/dogconfig.ts": Utils.dedent` + "/src/src-types/dogconfig.ts": Utils.dedent` export interface DogConfig { name: string; }`, - "/src/src-dogs/index.ts": Utils.dedent` + "/src/src-dogs/index.ts": Utils.dedent` export * from 'src-types'; export * from './lassie/lassiedog.js'; `, - "/src/src-dogs/dogconfig.ts": Utils.dedent` + "/src/src-dogs/dogconfig.ts": Utils.dedent` import { DogConfig } from 'src-types'; export const DOG_CONFIG: DogConfig = { name: 'Default dog', }; `, - "/src/src-dogs/dog.ts": Utils.dedent` + "/src/src-dogs/dog.ts": Utils.dedent` import { DogConfig } from 'src-types'; import { DOG_CONFIG } from './dogconfig.js'; @@ -123,7 +123,7 @@ namespace ts { } } `, - "/src/src-dogs/lassie/lassiedog.ts": Utils.dedent` + "/src/src-dogs/lassie/lassiedog.ts": Utils.dedent` import { Dog } from '../dog.js'; import { LASSIE_CONFIG } from './lassieconfig.js'; @@ -131,29 +131,29 @@ namespace ts { protected static getDogConfig = () => LASSIE_CONFIG; } `, - "/src/src-dogs/lassie/lassieconfig.ts": Utils.dedent` + "/src/src-dogs/lassie/lassieconfig.ts": Utils.dedent` import { DogConfig } from 'src-types'; export const LASSIE_CONFIG: DogConfig = { name: 'Lassie' }; `, - "/src/tsconfig-base.json": Utils.dedent` + "/src/tsconfig-base.json": Utils.dedent` { "compilerOptions": { "declaration": true, "module": "node12" } }`, - "/src/src-types/package.json": Utils.dedent` + "/src/src-types/package.json": Utils.dedent` { "type": "module", "exports": "./index.js" }`, - "/src/src-dogs/package.json": Utils.dedent` + "/src/src-dogs/package.json": Utils.dedent` { "type": "module", "exports": "./index.js" }`, - "/src/src-types/tsconfig.json": Utils.dedent` + "/src/src-types/tsconfig.json": Utils.dedent` { "extends": "../tsconfig-base.json", "compilerOptions": { @@ -163,7 +163,7 @@ namespace ts { "**/*" ] }`, - "/src/src-dogs/tsconfig.json": Utils.dedent` + "/src/src-dogs/tsconfig.json": Utils.dedent` { "extends": "../tsconfig-base.json", "compilerOptions": { @@ -176,13 +176,13 @@ namespace ts { "**/*" ] }`, - }, ""), - modifyFs: fs => { - fs.writeFileSync("/lib/lib.es2020.full.d.ts", tscWatch.libFile.content); - fs.symlinkSync("/src", "/src/src-types/node_modules"); - fs.symlinkSync("/src", "/src/src-dogs/node_modules"); - }, - commandLineArgs: ["-b", "src/src-types", "src/src-dogs", "--verbose"] - }); + }, ""), + modifyFs: fs => { + fs.writeFileSync("/lib/lib.es2020.full.d.ts", tscWatch.libFile.content); + fs.symlinkSync("/src", "/src/src-types/node_modules"); + fs.symlinkSync("/src", "/src/src-dogs/node_modules"); + }, + commandLineArgs: ["-b", "src/src-types", "src/src-dogs", "--verbose"] }); +}); } diff --git a/src/testRunner/unittests/tsbuild/noEmitOnError.ts b/src/testRunner/unittests/tsbuild/noEmitOnError.ts index 75e17f66203e9..2b2f8008e54ef 100644 --- a/src/testRunner/unittests/tsbuild/noEmitOnError.ts +++ b/src/testRunner/unittests/tsbuild/noEmitOnError.ts @@ -1,48 +1,48 @@ namespace ts { - describe("unittests:: tsbuild - with noEmitOnError", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/noEmitOnError"); - }); - after(() => { - projFs = undefined!; - }); +describe("unittests:: tsbuild - with noEmitOnError", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = loadProjectFromDisk("tests/projects/noEmitOnError"); + }); + after(() => { + projFs = undefined!; + }); - function verifyNoEmitOnError(subScenario: string, fixModifyFs: TscIncremental["modifyFs"], modifyFs?: TscIncremental["modifyFs"]) { - verifyTscSerializedIncrementalEdits({ - scenario: "noEmitOnError", - subScenario, - fs: () => projFs, - modifyFs, - commandLineArgs: ["--b", "/src/tsconfig.json"], - incrementalScenarios: [ - noChangeRun, - { - subScenario: "Fix error", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fixModifyFs, - }, - noChangeRun, - ], - baselinePrograms: true, - baselineIncremental: true - }); - } + function verifyNoEmitOnError(subScenario: string, fixModifyFs: TscIncremental["modifyFs"], modifyFs?: TscIncremental["modifyFs"]) { + verifyTscSerializedIncrementalEdits({ + scenario: "noEmitOnError", + subScenario, + fs: () => projFs, + modifyFs, + commandLineArgs: ["--b", "/src/tsconfig.json"], + incrementalScenarios: [ + noChangeRun, + { + subScenario: "Fix error", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fixModifyFs, + }, + noChangeRun, + ], + baselinePrograms: true, + baselineIncremental: true + }); + } - verifyNoEmitOnError( - "syntax errors", - fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + verifyNoEmitOnError( + "syntax errors", + fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a = { lastName: 'sdsd' };`, "utf-8") - ); + ); - verifyNoEmitOnError( - "semantic errors", - fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + verifyNoEmitOnError( + "semantic errors", + fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a: string = "hello";`, "utf-8"), - fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a: string = 10;`, "utf-8") - ); - }); + ); +}); } diff --git a/src/testRunner/unittests/tsbuild/outFile.ts b/src/testRunner/unittests/tsbuild/outFile.ts index 33f7705085258..fd8e83d808f39 100644 --- a/src/testRunner/unittests/tsbuild/outFile.ts +++ b/src/testRunner/unittests/tsbuild/outFile.ts @@ -1,479 +1,479 @@ namespace ts { - describe("unittests:: tsbuild:: outFile::", () => { - let outFileFs: vfs.FileSystem; - const enum Ext { js, jsmap, dts, dtsmap, buildinfo } - const enum Project { first, second, third } - type OutputFile = [string, string, string, string, string]; - function relName(path: string) { - return path.slice(1); - } - const outputFiles: [OutputFile, OutputFile, OutputFile] = [ - [ - "/src/first/bin/first-output.js", - "/src/first/bin/first-output.js.map", - "/src/first/bin/first-output.d.ts", - "/src/first/bin/first-output.d.ts.map", - "/src/first/bin/first-output.tsbuildinfo" - ], - [ - "/src/2/second-output.js", - "/src/2/second-output.js.map", - "/src/2/second-output.d.ts", - "/src/2/second-output.d.ts.map", - "/src/2/second-output.tsbuildinfo" - ], +describe("unittests:: tsbuild:: outFile::", () => { + let outFileFs: vfs.FileSystem; + const enum Ext { js, jsmap, dts, dtsmap, buildinfo } + const enum Project { first, second, third } + type OutputFile = [string, string, string, string, string]; + function relName(path: string) { + return path.slice(1); + } + const outputFiles: [OutputFile, OutputFile, OutputFile] = [ + [ + "/src/first/bin/first-output.js", + "/src/first/bin/first-output.js.map", + "/src/first/bin/first-output.d.ts", + "/src/first/bin/first-output.d.ts.map", + "/src/first/bin/first-output.tsbuildinfo" + ], + [ + "/src/2/second-output.js", + "/src/2/second-output.js.map", + "/src/2/second-output.d.ts", + "/src/2/second-output.d.ts.map", + "/src/2/second-output.tsbuildinfo" + ], + [ + "/src/third/thirdjs/output/third-output.js", + "/src/third/thirdjs/output/third-output.js.map", + "/src/third/thirdjs/output/third-output.d.ts", + "/src/third/thirdjs/output/third-output.d.ts.map", + "/src/third/thirdjs/output/third-output.tsbuildinfo" + ] + ]; + const relOutputFiles = outputFiles.map(v => v.map(relName)) as [OutputFile, OutputFile, OutputFile]; + type Sources = [string, readonly string[]]; + const enum Source { config, ts } + const enum Part { one, two, three } + const sources: [Sources, Sources, Sources] = [ + [ + "/src/first/tsconfig.json", [ - "/src/third/thirdjs/output/third-output.js", - "/src/third/thirdjs/output/third-output.js.map", - "/src/third/thirdjs/output/third-output.d.ts", - "/src/third/thirdjs/output/third-output.d.ts.map", - "/src/third/thirdjs/output/third-output.tsbuildinfo" + "/src/first/first_PART1.ts", + "/src/first/first_part2.ts", + "/src/first/first_part3.ts" ] - ]; - const relOutputFiles = outputFiles.map(v => v.map(relName)) as [OutputFile, OutputFile, OutputFile]; - type Sources = [string, readonly string[]]; - const enum Source { config, ts } - const enum Part { one, two, three } - const sources: [Sources, Sources, Sources] = [ - [ - "/src/first/tsconfig.json", - [ - "/src/first/first_PART1.ts", - "/src/first/first_part2.ts", - "/src/first/first_part3.ts" - ] - ], + ], + [ + "/src/second/tsconfig.json", [ - "/src/second/tsconfig.json", - [ - "/src/second/second_part1.ts", - "/src/second/second_part2.ts" - ] - ], + "/src/second/second_part1.ts", + "/src/second/second_part2.ts" + ] + ], + [ + "/src/third/tsconfig.json", [ - "/src/third/tsconfig.json", - [ - "/src/third/third_part1.ts" - ] + "/src/third/third_part1.ts" ] - ]; - const relSources = sources.map(([config, sources]) => [relName(config), sources.map(relName)]) as any as [Sources, Sources, Sources]; - let initialExpectedDiagnostics: readonly fakes.ExpectedDiagnostic[] = [ - getExpectedDiagnosticForProjectsInBuild(relSources[Project.first][Source.config], relSources[Project.second][Source.config], relSources[Project.third][Source.config]), - [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[Project.first][Source.config], relOutputFiles[Project.first][Ext.js]], - [Diagnostics.Building_project_0, sources[Project.first][Source.config]], - [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[Project.second][Source.config], relOutputFiles[Project.second][Ext.js]], - [Diagnostics.Building_project_0, sources[Project.second][Source.config]], - [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[Project.third][Source.config], relOutputFiles[Project.third][Ext.js]], - [Diagnostics.Building_project_0, sources[Project.third][Source.config]] - ]; - before(() => { - outFileFs = loadProjectFromDisk("tests/projects/outfile-concat"); - }); - after(() => { - outFileFs = undefined!; - initialExpectedDiagnostics = undefined!; - }); + ] + ]; + const relSources = sources.map(([config, sources]) => [relName(config), sources.map(relName)]) as any as [Sources, Sources, Sources]; + let initialExpectedDiagnostics: readonly fakes.ExpectedDiagnostic[] = [ + getExpectedDiagnosticForProjectsInBuild(relSources[Project.first][Source.config], relSources[Project.second][Source.config], relSources[Project.third][Source.config]), + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[Project.first][Source.config], relOutputFiles[Project.first][Ext.js]], + [Diagnostics.Building_project_0, sources[Project.first][Source.config]], + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[Project.second][Source.config], relOutputFiles[Project.second][Ext.js]], + [Diagnostics.Building_project_0, sources[Project.second][Source.config]], + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[Project.third][Source.config], relOutputFiles[Project.third][Ext.js]], + [Diagnostics.Building_project_0, sources[Project.third][Source.config]] + ]; + before(() => { + outFileFs = loadProjectFromDisk("tests/projects/outfile-concat"); + }); + after(() => { + outFileFs = undefined!; + initialExpectedDiagnostics = undefined!; + }); - function createSolutionBuilder(host: fakes.SolutionBuilderHost, baseOptions?: BuildOptions) { - return ts.createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: true, ...(baseOptions || {}) }); - } + function createSolutionBuilder(host: fakes.SolutionBuilderHost, baseOptions?: BuildOptions) { + return ts.createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: true, ...(baseOptions || {}) }); + } - interface VerifyOutFileScenarioInput { - subScenario: string; - modifyFs?: (fs: vfs.FileSystem) => void; - modifyAgainFs?: (fs: vfs.FileSystem) => void; - ignoreDtsChanged?: true; - ignoreDtsUnchanged?: true; - baselineOnly?: true; - additionalCommandLineArgs?: string[]; - } + interface VerifyOutFileScenarioInput { + subScenario: string; + modifyFs?: (fs: vfs.FileSystem) => void; + modifyAgainFs?: (fs: vfs.FileSystem) => void; + ignoreDtsChanged?: true; + ignoreDtsUnchanged?: true; + baselineOnly?: true; + additionalCommandLineArgs?: string[]; + } - function verifyOutFileScenario({ + function verifyOutFileScenario({ + subScenario, + modifyFs, + modifyAgainFs, + ignoreDtsChanged, + ignoreDtsUnchanged, + baselineOnly, + additionalCommandLineArgs, + }: VerifyOutFileScenarioInput) { + const incrementalScenarios: TscIncremental[] = []; + if (!ignoreDtsChanged) { + incrementalScenarios.push({ + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => replaceText(fs, relSources[Project.first][Source.ts][Part.one], "Hello", "Hola"), + }); + } + if (!ignoreDtsUnchanged) { + incrementalScenarios.push({ + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => appendText(fs, relSources[Project.first][Source.ts][Part.one], "console.log(s);"), + }); + } + if (modifyAgainFs) { + incrementalScenarios.push({ + buildKind: BuildKind.IncrementalHeadersChange, + modifyFs: modifyAgainFs + }); + } + const input: VerifyTsBuildInput = { subScenario, + fs: () => outFileFs, + scenario: "outfile-concat", + commandLineArgs: ["--b", "/src/third", "--verbose", ...(additionalCommandLineArgs || [])], + baselineSourceMap: true, modifyFs, - modifyAgainFs, - ignoreDtsChanged, - ignoreDtsUnchanged, - baselineOnly, - additionalCommandLineArgs, - }: VerifyOutFileScenarioInput) { - const incrementalScenarios: TscIncremental[] = []; - if (!ignoreDtsChanged) { - incrementalScenarios.push({ - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => replaceText(fs, relSources[Project.first][Source.ts][Part.one], "Hello", "Hola"), - }); - } - if (!ignoreDtsUnchanged) { - incrementalScenarios.push({ - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => appendText(fs, relSources[Project.first][Source.ts][Part.one], "console.log(s);"), - }); - } - if (modifyAgainFs) { - incrementalScenarios.push({ - buildKind: BuildKind.IncrementalHeadersChange, - modifyFs: modifyAgainFs - }); - } - const input: VerifyTsBuildInput = { - subScenario, - fs: () => outFileFs, - scenario: "outfile-concat", - commandLineArgs: ["--b", "/src/third", "--verbose", ...(additionalCommandLineArgs || [])], - baselineSourceMap: true, - modifyFs, - baselineReadFileCalls: !baselineOnly, - incrementalScenarios, - }; - return incrementalScenarios.length ? - verifyTscIncrementalEdits(input) : - verifyTsc(input); - } + baselineReadFileCalls: !baselineOnly, + incrementalScenarios, + }; + return incrementalScenarios.length ? + verifyTscIncrementalEdits(input) : + verifyTsc(input); + } - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "baseline sectioned sourcemaps", - }); + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "baseline sectioned sourcemaps", + }); - verifyOutFileScenario({ - subScenario: "explainFiles", - additionalCommandLineArgs: ["--explainFiles"], - baselineOnly: true - }); + verifyOutFileScenario({ + subScenario: "explainFiles", + additionalCommandLineArgs: ["--explainFiles"], + baselineOnly: true + }); - // Verify baseline with build info + dts unChanged - verifyOutFileScenario({ - subScenario: "when final project is not composite but uses project references", - modifyFs: fs => replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, ""), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify baseline with build info + dts unChanged + verifyOutFileScenario({ + subScenario: "when final project is not composite but uses project references", + modifyFs: fs => replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, ""), + ignoreDtsChanged: true, + baselineOnly: true + }); - // Verify baseline with build info - verifyOutFileScenario({ - subScenario: "when final project is not composite but incremental", - modifyFs: fs => replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, `"incremental": true,`), - ignoreDtsChanged: true, - ignoreDtsUnchanged: true, - baselineOnly: true - }); + // Verify baseline with build info + verifyOutFileScenario({ + subScenario: "when final project is not composite but incremental", + modifyFs: fs => replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, `"incremental": true,`), + ignoreDtsChanged: true, + ignoreDtsUnchanged: true, + baselineOnly: true + }); - // Verify baseline with build info - verifyOutFileScenario({ - subScenario: "when final project specifies tsBuildInfoFile", - modifyFs: fs => replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, `"composite": true, + // Verify baseline with build info + verifyOutFileScenario({ + subScenario: "when final project specifies tsBuildInfoFile", + modifyFs: fs => replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, `"composite": true, "tsBuildInfoFile": "./thirdjs/output/third.tsbuildinfo",`), - ignoreDtsChanged: true, - ignoreDtsUnchanged: true, - baselineOnly: true - }); + ignoreDtsChanged: true, + ignoreDtsUnchanged: true, + baselineOnly: true + }); - function getOutFileFsAfterBuild() { - const fs = outFileFs.shadow(); - const host = fakes.SolutionBuilderHost.create(fs); - const builder = createSolutionBuilder(host); - builder.build(); - fs.makeReadonly(); - return fs; - } + function getOutFileFsAfterBuild() { + const fs = outFileFs.shadow(); + const host = fakes.SolutionBuilderHost.create(fs); + const builder = createSolutionBuilder(host); + builder.build(); + fs.makeReadonly(); + return fs; + } - verifyTscSerializedIncrementalEdits({ - scenario: "outFile", - subScenario: "clean projects", - fs: getOutFileFsAfterBuild, - commandLineArgs: ["--b", "/src/third", "--clean"], - incrementalScenarios: noChangeOnlyRuns - }); + verifyTscSerializedIncrementalEdits({ + scenario: "outFile", + subScenario: "clean projects", + fs: getOutFileFsAfterBuild, + commandLineArgs: ["--b", "/src/third", "--clean"], + incrementalScenarios: noChangeOnlyRuns + }); - verifyTsc({ - scenario: "outFile", - subScenario: "verify buildInfo absence results in new build", - fs: getOutFileFsAfterBuild, - commandLineArgs: ["--b", "/src/third", "--verbose"], - modifyFs: fs => fs.unlinkSync(outputFiles[Project.first][Ext.buildinfo]), - }); + verifyTsc({ + scenario: "outFile", + subScenario: "verify buildInfo absence results in new build", + fs: getOutFileFsAfterBuild, + commandLineArgs: ["--b", "/src/third", "--verbose"], + modifyFs: fs => fs.unlinkSync(outputFiles[Project.first][Ext.buildinfo]), + }); - verifyTsc({ - scenario: "outFile", - subScenario: "tsbuildinfo is not generated when incremental is set to false", - fs: () => outFileFs, - commandLineArgs: ["--b", "/src/third", "--verbose"], - modifyFs: fs => replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, ""), - }); + verifyTsc({ + scenario: "outFile", + subScenario: "tsbuildinfo is not generated when incremental is set to false", + fs: () => outFileFs, + commandLineArgs: ["--b", "/src/third", "--verbose"], + modifyFs: fs => replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, ""), + }); - it("rebuilds completely when version in tsbuildinfo doesnt match ts version", () => { - const { fs, tick } = getFsWithTime(outFileFs); - const host = fakes.SolutionBuilderHost.create(fs); - let builder = createSolutionBuilder(host); - builder.build(); - host.assertDiagnosticMessages(...initialExpectedDiagnostics); - host.clearDiagnostics(); - tick(); - builder = createSolutionBuilder(host); - changeCompilerVersion(host); - tick(); - builder.build(); - host.assertDiagnosticMessages( - getExpectedDiagnosticForProjectsInBuild(relSources[Project.first][Source.config], relSources[Project.second][Source.config], relSources[Project.third][Source.config]), - [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[Project.first][Source.config], fakes.version, version], - [Diagnostics.Building_project_0, sources[Project.first][Source.config]], - [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[Project.second][Source.config], fakes.version, version], - [Diagnostics.Building_project_0, sources[Project.second][Source.config]], - [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[Project.third][Source.config], fakes.version, version], - [Diagnostics.Building_project_0, sources[Project.third][Source.config]], - ); - }); + it("rebuilds completely when version in tsbuildinfo doesnt match ts version", () => { + const { fs, tick } = getFsWithTime(outFileFs); + const host = fakes.SolutionBuilderHost.create(fs); + let builder = createSolutionBuilder(host); + builder.build(); + host.assertDiagnosticMessages(...initialExpectedDiagnostics); + host.clearDiagnostics(); + tick(); + builder = createSolutionBuilder(host); + changeCompilerVersion(host); + tick(); + builder.build(); + host.assertDiagnosticMessages( + getExpectedDiagnosticForProjectsInBuild(relSources[Project.first][Source.config], relSources[Project.second][Source.config], relSources[Project.third][Source.config]), + [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[Project.first][Source.config], fakes.version, version], + [Diagnostics.Building_project_0, sources[Project.first][Source.config]], + [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[Project.second][Source.config], fakes.version, version], + [Diagnostics.Building_project_0, sources[Project.second][Source.config]], + [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[Project.third][Source.config], fakes.version, version], + [Diagnostics.Building_project_0, sources[Project.third][Source.config]], + ); + }); - it("rebuilds completely when command line incremental flag changes between non dts changes", () => { - const { fs, tick } = getFsWithTime(outFileFs); - // Make non composite third project - replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, ""); - - // Build with command line incremental - const host = fakes.SolutionBuilderHost.create(fs); - let builder = createSolutionBuilder(host, { incremental: true }); - builder.build(); - host.assertDiagnosticMessages(...initialExpectedDiagnostics); - host.clearDiagnostics(); - tick(); - - // Make non incremental build with change in file that doesnt affect dts - appendText(fs, relSources[Project.first][Source.ts][Part.one], "console.log(s);"); - builder = createSolutionBuilder(host, { verbose: true }); - builder.build(); - host.assertDiagnosticMessages(getExpectedDiagnosticForProjectsInBuild(relSources[Project.first][Source.config], relSources[Project.second][Source.config], relSources[Project.third][Source.config]), - [Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[Project.first][Source.config], relOutputFiles[Project.first][Ext.js], relSources[Project.first][Source.ts][Part.one]], - [Diagnostics.Building_project_0, sources[Project.first][Source.config]], - [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, relSources[Project.second][Source.config], relSources[Project.second][Source.ts][Part.one], relOutputFiles[Project.second][Ext.js]], - [Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, relSources[Project.third][Source.config], "src/first"], - [Diagnostics.Building_project_0, sources[Project.third][Source.config]] - ); - host.clearDiagnostics(); - tick(); - - // Make incremental build with change in file that doesnt affect dts - appendText(fs, relSources[Project.first][Source.ts][Part.one], "console.log(s);"); - builder = createSolutionBuilder(host, { verbose: true, incremental: true }); - builder.build(); - // Builds completely because tsbuildinfo is old. - host.assertDiagnosticMessages( - getExpectedDiagnosticForProjectsInBuild(relSources[Project.first][Source.config], relSources[Project.second][Source.config], relSources[Project.third][Source.config]), - [Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[Project.first][Source.config], relOutputFiles[Project.first][Ext.js], relSources[Project.first][Source.ts][Part.one]], - [Diagnostics.Building_project_0, sources[Project.first][Source.config]], - [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, relSources[Project.second][Source.config], relSources[Project.second][Source.ts][Part.one], relOutputFiles[Project.second][Ext.js]], - [Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[Project.third][Source.config], relOutputFiles[Project.third][Ext.buildinfo], "src/first"], - [Diagnostics.Building_project_0, sources[Project.third][Source.config]] - ); - host.clearDiagnostics(); - }); + it("rebuilds completely when command line incremental flag changes between non dts changes", () => { + const { fs, tick } = getFsWithTime(outFileFs); + // Make non composite third project + replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, ""); + + // Build with command line incremental + const host = fakes.SolutionBuilderHost.create(fs); + let builder = createSolutionBuilder(host, { incremental: true }); + builder.build(); + host.assertDiagnosticMessages(...initialExpectedDiagnostics); + host.clearDiagnostics(); + tick(); + + // Make non incremental build with change in file that doesnt affect dts + appendText(fs, relSources[Project.first][Source.ts][Part.one], "console.log(s);"); + builder = createSolutionBuilder(host, { verbose: true }); + builder.build(); + host.assertDiagnosticMessages(getExpectedDiagnosticForProjectsInBuild(relSources[Project.first][Source.config], relSources[Project.second][Source.config], relSources[Project.third][Source.config]), + [Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[Project.first][Source.config], relOutputFiles[Project.first][Ext.js], relSources[Project.first][Source.ts][Part.one]], + [Diagnostics.Building_project_0, sources[Project.first][Source.config]], + [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, relSources[Project.second][Source.config], relSources[Project.second][Source.ts][Part.one], relOutputFiles[Project.second][Ext.js]], + [Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, relSources[Project.third][Source.config], "src/first"], + [Diagnostics.Building_project_0, sources[Project.third][Source.config]] + ); + host.clearDiagnostics(); + tick(); + + // Make incremental build with change in file that doesnt affect dts + appendText(fs, relSources[Project.first][Source.ts][Part.one], "console.log(s);"); + builder = createSolutionBuilder(host, { verbose: true, incremental: true }); + builder.build(); + // Builds completely because tsbuildinfo is old. + host.assertDiagnosticMessages( + getExpectedDiagnosticForProjectsInBuild(relSources[Project.first][Source.config], relSources[Project.second][Source.config], relSources[Project.third][Source.config]), + [Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[Project.first][Source.config], relOutputFiles[Project.first][Ext.js], relSources[Project.first][Source.ts][Part.one]], + [Diagnostics.Building_project_0, sources[Project.first][Source.config]], + [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, relSources[Project.second][Source.config], relSources[Project.second][Source.ts][Part.one], relOutputFiles[Project.second][Ext.js]], + [Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[Project.third][Source.config], relOutputFiles[Project.third][Ext.buildinfo], "src/first"], + [Diagnostics.Building_project_0, sources[Project.third][Source.config]] + ); + host.clearDiagnostics(); + }); - it("builds till project specified", () => { - const fs = outFileFs.shadow(); - const host = fakes.SolutionBuilderHost.create(fs); - const builder = createSolutionBuilder(host, { verbose: false }); - const result = builder.build(sources[Project.second][Source.config]); - host.assertDiagnosticMessages(/*empty*/); - // First and Third is not built - verifyOutputsAbsent(fs, [...outputFiles[Project.first], ...outputFiles[Project.third]]); - // second is built - verifyOutputsPresent(fs, outputFiles[Project.second]); - assert.equal(result, ExitStatus.Success); - }); + it("builds till project specified", () => { + const fs = outFileFs.shadow(); + const host = fakes.SolutionBuilderHost.create(fs); + const builder = createSolutionBuilder(host, { verbose: false }); + const result = builder.build(sources[Project.second][Source.config]); + host.assertDiagnosticMessages(/*empty*/); + // First and Third is not built + verifyOutputsAbsent(fs, [...outputFiles[Project.first], ...outputFiles[Project.third]]); + // second is built + verifyOutputsPresent(fs, outputFiles[Project.second]); + assert.equal(result, ExitStatus.Success); + }); - it("cleans till project specified", () => { - const fs = outFileFs.shadow(); - const host = fakes.SolutionBuilderHost.create(fs); - const builder = createSolutionBuilder(host, { verbose: false }); - builder.build(); - const result = builder.clean(sources[Project.second][Source.config]); - host.assertDiagnosticMessages(/*empty*/); - // First and Third output for present - verifyOutputsPresent(fs, [...outputFiles[Project.first], ...outputFiles[Project.third]]); - // second is cleaned - verifyOutputsAbsent(fs, outputFiles[Project.second]); - assert.equal(result, ExitStatus.Success); - }); + it("cleans till project specified", () => { + const fs = outFileFs.shadow(); + const host = fakes.SolutionBuilderHost.create(fs); + const builder = createSolutionBuilder(host, { verbose: false }); + builder.build(); + const result = builder.clean(sources[Project.second][Source.config]); + host.assertDiagnosticMessages(/*empty*/); + // First and Third output for present + verifyOutputsPresent(fs, [...outputFiles[Project.first], ...outputFiles[Project.third]]); + // second is cleaned + verifyOutputsAbsent(fs, outputFiles[Project.second]); + assert.equal(result, ExitStatus.Success); + }); - describe("Prepend output with .tsbuildinfo", () => { - // Prologues - describe("Prologues", () => { - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "strict in all projects", - modifyFs: fs => { - enableStrict(fs, sources[Project.first][Source.config]); - enableStrict(fs, sources[Project.second][Source.config]); - enableStrict(fs, sources[Project.third][Source.config]); - }, - modifyAgainFs: fs => addTestPrologue(fs, relSources[Project.first][Source.ts][Part.one], `"myPrologue"`) - }); + describe("Prepend output with .tsbuildinfo", () => { + // Prologues + describe("Prologues", () => { + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "strict in all projects", + modifyFs: fs => { + enableStrict(fs, sources[Project.first][Source.config]); + enableStrict(fs, sources[Project.second][Source.config]); + enableStrict(fs, sources[Project.third][Source.config]); + }, + modifyAgainFs: fs => addTestPrologue(fs, relSources[Project.first][Source.ts][Part.one], `"myPrologue"`) + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "strict in one dependency", - modifyFs: fs => enableStrict(fs, sources[Project.second][Source.config]), - modifyAgainFs: fs => addTestPrologue(fs, "src/first/first_PART1.ts", `"myPrologue"`), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "strict in one dependency", + modifyFs: fs => enableStrict(fs, sources[Project.second][Source.config]), + modifyAgainFs: fs => addTestPrologue(fs, "src/first/first_PART1.ts", `"myPrologue"`), + ignoreDtsChanged: true, + baselineOnly: true + }); - // Verify initial + incremental edits - sourcemap verification - verifyOutFileScenario({ - subScenario: "multiple prologues in all projects", - modifyFs: fs => { - enableStrict(fs, sources[Project.first][Source.config]); - addTestPrologue(fs, sources[Project.first][Source.ts][Part.one], `"myPrologue"`); - enableStrict(fs, sources[Project.second][Source.config]); - addTestPrologue(fs, sources[Project.second][Source.ts][Part.one], `"myPrologue"`); - addTestPrologue(fs, sources[Project.second][Source.ts][Part.two], `"myPrologue2";`); - enableStrict(fs, sources[Project.third][Source.config]); - addTestPrologue(fs, sources[Project.third][Source.ts][Part.one], `"myPrologue";`); - addTestPrologue(fs, sources[Project.third][Source.ts][Part.one], `"myPrologue3";`); - }, - modifyAgainFs: fs => addTestPrologue(fs, relSources[Project.first][Source.ts][Part.one], `"myPrologue5"`) - }); + // Verify initial + incremental edits - sourcemap verification + verifyOutFileScenario({ + subScenario: "multiple prologues in all projects", + modifyFs: fs => { + enableStrict(fs, sources[Project.first][Source.config]); + addTestPrologue(fs, sources[Project.first][Source.ts][Part.one], `"myPrologue"`); + enableStrict(fs, sources[Project.second][Source.config]); + addTestPrologue(fs, sources[Project.second][Source.ts][Part.one], `"myPrologue"`); + addTestPrologue(fs, sources[Project.second][Source.ts][Part.two], `"myPrologue2";`); + enableStrict(fs, sources[Project.third][Source.config]); + addTestPrologue(fs, sources[Project.third][Source.ts][Part.one], `"myPrologue";`); + addTestPrologue(fs, sources[Project.third][Source.ts][Part.one], `"myPrologue3";`); + }, + modifyAgainFs: fs => addTestPrologue(fs, relSources[Project.first][Source.ts][Part.one], `"myPrologue5"`) + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "multiple prologues in different projects", - modifyFs: fs => { - enableStrict(fs, sources[Project.first][Source.config]); - addTestPrologue(fs, sources[Project.second][Source.ts][Part.one], `"myPrologue"`); - addTestPrologue(fs, sources[Project.second][Source.ts][Part.two], `"myPrologue2";`); - enableStrict(fs, sources[Project.third][Source.config]); - }, - modifyAgainFs: fs => addTestPrologue(fs, sources[Project.first][Source.ts][Part.one], `"myPrologue5"`), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "multiple prologues in different projects", + modifyFs: fs => { + enableStrict(fs, sources[Project.first][Source.config]); + addTestPrologue(fs, sources[Project.second][Source.ts][Part.one], `"myPrologue"`); + addTestPrologue(fs, sources[Project.second][Source.ts][Part.two], `"myPrologue2";`); + enableStrict(fs, sources[Project.third][Source.config]); + }, + modifyAgainFs: fs => addTestPrologue(fs, sources[Project.first][Source.ts][Part.one], `"myPrologue5"`), + ignoreDtsChanged: true, + baselineOnly: true }); + }); - // Shebang - describe("Shebang", () => { - // changes declaration because its emitted in .d.ts file - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "shebang in all projects", - modifyFs: fs => { - addShebang(fs, "first", "first_PART1"); - addShebang(fs, "first", "first_part2"); - addShebang(fs, "second", "second_part1"); - addShebang(fs, "third", "third_part1"); - }, - }); + // Shebang + describe("Shebang", () => { + // changes declaration because its emitted in .d.ts file + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "shebang in all projects", + modifyFs: fs => { + addShebang(fs, "first", "first_PART1"); + addShebang(fs, "first", "first_part2"); + addShebang(fs, "second", "second_part1"); + addShebang(fs, "third", "third_part1"); + }, + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "shebang in only one dependency project", - modifyFs: fs => addShebang(fs, "second", "second_part1"), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "shebang in only one dependency project", + modifyFs: fs => addShebang(fs, "second", "second_part1"), + ignoreDtsChanged: true, + baselineOnly: true }); + }); - // emitHelpers - describe("emitHelpers", () => { - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "emitHelpers in all projects", - modifyFs: fs => { - addRest(fs, "first", "first_PART1"); - addRest(fs, "second", "second_part1"); - addRest(fs, "third", "third_part1"); - }, - modifyAgainFs: fs => removeRest(fs, "first", "first_PART1") - }); + // emitHelpers + describe("emitHelpers", () => { + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "emitHelpers in all projects", + modifyFs: fs => { + addRest(fs, "first", "first_PART1"); + addRest(fs, "second", "second_part1"); + addRest(fs, "third", "third_part1"); + }, + modifyAgainFs: fs => removeRest(fs, "first", "first_PART1") + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "emitHelpers in only one dependency project", - modifyFs: fs => { - addStubFoo(fs, "first", "first_PART1"); - addRest(fs, "second", "second_part1"); - }, - modifyAgainFs: fs => changeStubToRest(fs, "first", "first_PART1"), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "emitHelpers in only one dependency project", + modifyFs: fs => { + addStubFoo(fs, "first", "first_PART1"); + addRest(fs, "second", "second_part1"); + }, + modifyAgainFs: fs => changeStubToRest(fs, "first", "first_PART1"), + ignoreDtsChanged: true, + baselineOnly: true + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "multiple emitHelpers in all projects", - modifyFs: fs => { - addRest(fs, "first", "first_PART1"); - addSpread(fs, "first", "first_part3"); - addRest(fs, "second", "second_part1"); - addSpread(fs, "second", "second_part2"); - addRest(fs, "third", "third_part1"); - addSpread(fs, "third", "third_part1"); - }, - modifyAgainFs: fs => removeRest(fs, "first", "first_PART1"), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "multiple emitHelpers in all projects", + modifyFs: fs => { + addRest(fs, "first", "first_PART1"); + addSpread(fs, "first", "first_part3"); + addRest(fs, "second", "second_part1"); + addSpread(fs, "second", "second_part2"); + addRest(fs, "third", "third_part1"); + addSpread(fs, "third", "third_part1"); + }, + modifyAgainFs: fs => removeRest(fs, "first", "first_PART1"), + ignoreDtsChanged: true, + baselineOnly: true + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "multiple emitHelpers in different projects", - modifyFs: fs => { - addRest(fs, "first", "first_PART1"); - addSpread(fs, "second", "second_part1"); - addRest(fs, "third", "third_part1"); - }, - modifyAgainFs: fs => removeRest(fs, "first", "first_PART1"), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "multiple emitHelpers in different projects", + modifyFs: fs => { + addRest(fs, "first", "first_PART1"); + addSpread(fs, "second", "second_part1"); + addRest(fs, "third", "third_part1"); + }, + modifyAgainFs: fs => removeRest(fs, "first", "first_PART1"), + ignoreDtsChanged: true, + baselineOnly: true }); + }); - // triple slash refs - describe("triple slash refs", () => { - // changes declaration because its emitted in .d.ts file - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "triple slash refs in all projects", - modifyFs: fs => { - addTripleSlashRef(fs, "first", "first_part2"); - addTripleSlashRef(fs, "second", "second_part1"); - addTripleSlashRef(fs, "third", "third_part1"); - } - }); + // triple slash refs + describe("triple slash refs", () => { + // changes declaration because its emitted in .d.ts file + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "triple slash refs in all projects", + modifyFs: fs => { + addTripleSlashRef(fs, "first", "first_part2"); + addTripleSlashRef(fs, "second", "second_part1"); + addTripleSlashRef(fs, "third", "third_part1"); + } + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "triple slash refs in one project", - modifyFs: fs => addTripleSlashRef(fs, "second", "second_part1"), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "triple slash refs in one project", + modifyFs: fs => addTripleSlashRef(fs, "second", "second_part1"), + ignoreDtsChanged: true, + baselineOnly: true }); + }); - describe("stripInternal", () => { - function disableRemoveComments(fs: vfs.FileSystem, file: string) { - replaceText(fs, file, `"removeComments": true`, `"removeComments": false`); - } + describe("stripInternal", () => { + function disableRemoveComments(fs: vfs.FileSystem, file: string) { + replaceText(fs, file, `"removeComments": true`, `"removeComments": false`); + } - function diableRemoveCommentsInAll(fs: vfs.FileSystem) { - disableRemoveComments(fs, sources[Project.first][Source.config]); - disableRemoveComments(fs, sources[Project.second][Source.config]); - disableRemoveComments(fs, sources[Project.third][Source.config]); - } + function diableRemoveCommentsInAll(fs: vfs.FileSystem) { + disableRemoveComments(fs, sources[Project.first][Source.config]); + disableRemoveComments(fs, sources[Project.second][Source.config]); + disableRemoveComments(fs, sources[Project.third][Source.config]); + } - function stripInternalOfThird(fs: vfs.FileSystem) { - replaceText(fs, sources[Project.third][Source.config], `"declaration": true,`, `"declaration": true, + function stripInternalOfThird(fs: vfs.FileSystem) { + replaceText(fs, sources[Project.third][Source.config], `"declaration": true,`, `"declaration": true, "stripInternal": true,`); - } + } - function stripInternalScenario(fs: vfs.FileSystem, removeCommentsDisabled?: boolean, jsDocStyle?: boolean) { - const internal: string = jsDocStyle ? `/**@internal*/` : `/*@internal*/`; - if (removeCommentsDisabled) { - diableRemoveCommentsInAll(fs); - } - stripInternalOfThird(fs); - replaceText(fs, sources[Project.first][Source.ts][Part.one], "interface", `${internal} interface`); - appendText(fs, sources[Project.second][Source.ts][Part.one], ` + function stripInternalScenario(fs: vfs.FileSystem, removeCommentsDisabled?: boolean, jsDocStyle?: boolean) { + const internal: string = jsDocStyle ? `/**@internal*/` : `/*@internal*/`; + if (removeCommentsDisabled) { + diableRemoveCommentsInAll(fs); + } + stripInternalOfThird(fs); + replaceText(fs, sources[Project.first][Source.ts][Part.one], "interface", `${internal} interface`); + appendText(fs, sources[Project.second][Source.ts][Part.one], ` class normalC { ${internal} constructor() { } ${internal} prop: string; @@ -499,19 +499,64 @@ ${internal} import internalImport = internalNamespace.someClass; ${internal} type internalType = internalC; ${internal} const internalConst = 10; ${internal} enum internalEnum { a, b, c }`); + } + + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "stripInternal", + modifyFs: stripInternalScenario, + modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/*@internal*/ interface`, "interface"), + }); + + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "stripInternal with comments emit enabled", + modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ true), + modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/*@internal*/ interface`, "interface"), + ignoreDtsChanged: true, + baselineOnly: true + }); + + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "stripInternal jsdoc style comment", + modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true), + modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/**@internal*/ interface`, "interface"), + ignoreDtsChanged: true, + baselineOnly: true + }); + + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "stripInternal jsdoc style with comments emit enabled", + modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ true, /*jsDocStyle*/ true), + ignoreDtsChanged: true, + baselineOnly: true + }); + + describe("with three levels of project dependency", () => { + function makeOneTwoThreeDependOrder(fs: vfs.FileSystem) { + replaceText(fs, sources[Project.second][Source.config], "[", `[ + { "path": "../first", "prepend": true }`); + replaceText(fs, sources[Project.third][Source.config], `{ "path": "../first", "prepend": true },`, ""); + } + + function stripInternalWithDependentOrder(fs: vfs.FileSystem, removeCommentsDisabled?: boolean, jsDocStyle?: boolean) { + stripInternalScenario(fs, removeCommentsDisabled, jsDocStyle); + makeOneTwoThreeDependOrder(fs); } // Verify initial + incremental edits verifyOutFileScenario({ - subScenario: "stripInternal", - modifyFs: stripInternalScenario, + subScenario: "stripInternal when one-two-three are prepended in order", + modifyFs: stripInternalWithDependentOrder, modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/*@internal*/ interface`, "interface"), }); // Verify ignore dtsChanged verifyOutFileScenario({ - subScenario: "stripInternal with comments emit enabled", - modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ true), + subScenario: "stripInternal with comments emit enabled when one-two-three are prepended in order", + modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ true), modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/*@internal*/ interface`, "interface"), ignoreDtsChanged: true, baselineOnly: true @@ -519,8 +564,8 @@ ${internal} enum internalEnum { a, b, c }`); // Verify ignore dtsChanged verifyOutFileScenario({ - subScenario: "stripInternal jsdoc style comment", - modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true), + subScenario: "stripInternal jsdoc style comment when one-two-three are prepended in order", + modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true), modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/**@internal*/ interface`, "interface"), ignoreDtsChanged: true, baselineOnly: true @@ -528,64 +573,19 @@ ${internal} enum internalEnum { a, b, c }`); // Verify ignore dtsChanged verifyOutFileScenario({ - subScenario: "stripInternal jsdoc style with comments emit enabled", - modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ true, /*jsDocStyle*/ true), + subScenario: "stripInternal jsdoc style with comments emit enabled when one-two-three are prepended in order", + modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ true, /*jsDocStyle*/ true), ignoreDtsChanged: true, baselineOnly: true }); + }); - describe("with three levels of project dependency", () => { - function makeOneTwoThreeDependOrder(fs: vfs.FileSystem) { - replaceText(fs, sources[Project.second][Source.config], "[", `[ - { "path": "../first", "prepend": true }`); - replaceText(fs, sources[Project.third][Source.config], `{ "path": "../first", "prepend": true },`, ""); - } - - function stripInternalWithDependentOrder(fs: vfs.FileSystem, removeCommentsDisabled?: boolean, jsDocStyle?: boolean) { - stripInternalScenario(fs, removeCommentsDisabled, jsDocStyle); - makeOneTwoThreeDependOrder(fs); - } - - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "stripInternal when one-two-three are prepended in order", - modifyFs: stripInternalWithDependentOrder, - modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/*@internal*/ interface`, "interface"), - }); - - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "stripInternal with comments emit enabled when one-two-three are prepended in order", - modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ true), - modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/*@internal*/ interface`, "interface"), - ignoreDtsChanged: true, - baselineOnly: true - }); - - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "stripInternal jsdoc style comment when one-two-three are prepended in order", - modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true), - modifyAgainFs: fs => replaceText(fs, sources[Project.first][Source.ts][Part.one], `/**@internal*/ interface`, "interface"), - ignoreDtsChanged: true, - baselineOnly: true - }); - - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "stripInternal jsdoc style with comments emit enabled when one-two-three are prepended in order", - modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ true, /*jsDocStyle*/ true), - ignoreDtsChanged: true, - baselineOnly: true - }); - }); - - // only baseline - verifyOutFileScenario({ - subScenario: "stripInternal baseline when internal is inside another internal", - modifyFs: fs => { - stripInternalOfThird(fs); - prependText(fs, sources[Project.first][Source.ts][Part.one], `namespace ts { + // only baseline + verifyOutFileScenario({ + subScenario: "stripInternal baseline when internal is inside another internal", + modifyFs: fs => { + stripInternalOfThird(fs); + prependText(fs, sources[Project.first][Source.ts][Part.one], `namespace ts { /* @internal */ /** * Subset of properties from SourceFile that are used in multiple utility functions @@ -613,18 +613,18 @@ ${internal} enum internalEnum { a, b, c }`); someProp: string; } }`); - }, - ignoreDtsChanged: true, - ignoreDtsUnchanged: true, - baselineOnly: true - }); + }, + ignoreDtsChanged: true, + ignoreDtsUnchanged: true, + baselineOnly: true + }); - // only baseline - verifyOutFileScenario({ - subScenario: "stripInternal when few members of enum are internal", - modifyFs: fs => { - stripInternalOfThird(fs); - prependText(fs, sources[Project.first][Source.ts][Part.one], `enum TokenFlags { + // only baseline + verifyOutFileScenario({ + subScenario: "stripInternal when few members of enum are internal", + modifyFs: fs => { + stripInternalOfThird(fs); + prependText(fs, sources[Project.first][Source.ts][Part.one], `enum TokenFlags { None = 0, /* @internal */ PrecedingLineBreak = 1 << 0, @@ -647,96 +647,96 @@ ${internal} enum internalEnum { a, b, c }`); NumericLiteralFlags = Scientific | Octal | HexSpecifier | BinaryOrOctalSpecifier | ContainsSeparator } `); - }, - ignoreDtsChanged: true, - ignoreDtsUnchanged: true, - baselineOnly: true - }); - - verifyOutFileScenario({ - subScenario: "stripInternal when prepend is completely internal", - baselineOnly: true, - ignoreDtsChanged: true, - ignoreDtsUnchanged: true, - modifyFs: fs => { - fs.writeFileSync(sources[Project.first][Source.ts][Part.one], "/* @internal */ const A = 1;"); - fs.writeFileSync(sources[Project.third][Source.ts][Part.one], "const B = 2;"); - fs.writeFileSync(sources[Project.first][Source.config], JSON.stringify({ - compilerOptions: { - composite: true, - declaration: true, - declarationMap: true, - skipDefaultLibCheck: true, - sourceMap: true, - outFile: "./bin/first-output.js" - }, - files: [sources[Project.first][Source.ts][Part.one]] - })); - fs.writeFileSync(sources[Project.third][Source.config], JSON.stringify({ - compilerOptions: { - composite: true, - declaration: true, - declarationMap: false, - stripInternal: true, - sourceMap: true, - outFile: "./thirdjs/output/third-output.js", - }, - references: [{ path: "../first", prepend: true }], - files: [sources[Project.third][Source.ts][Part.one]] - })); - } - }); + }, + ignoreDtsChanged: true, + ignoreDtsUnchanged: true, + baselineOnly: true }); - describe("empty source files", () => { - function makeThirdEmptySourceFile(fs: vfs.FileSystem) { - fs.writeFileSync(sources[Project.third][Source.ts][Part.one], "", "utf8"); + verifyOutFileScenario({ + subScenario: "stripInternal when prepend is completely internal", + baselineOnly: true, + ignoreDtsChanged: true, + ignoreDtsUnchanged: true, + modifyFs: fs => { + fs.writeFileSync(sources[Project.first][Source.ts][Part.one], "/* @internal */ const A = 1;"); + fs.writeFileSync(sources[Project.third][Source.ts][Part.one], "const B = 2;"); + fs.writeFileSync(sources[Project.first][Source.config], JSON.stringify({ + compilerOptions: { + composite: true, + declaration: true, + declarationMap: true, + skipDefaultLibCheck: true, + sourceMap: true, + outFile: "./bin/first-output.js" + }, + files: [sources[Project.first][Source.ts][Part.one]] + })); + fs.writeFileSync(sources[Project.third][Source.config], JSON.stringify({ + compilerOptions: { + composite: true, + declaration: true, + declarationMap: false, + stripInternal: true, + sourceMap: true, + outFile: "./thirdjs/output/third-output.js", + }, + references: [{ path: "../first", prepend: true }], + files: [sources[Project.third][Source.ts][Part.one]] + })); } + }); + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "when source files are empty in the own file", - modifyFs: makeThirdEmptySourceFile, - ignoreDtsChanged: true, - baselineOnly: true - }); + describe("empty source files", () => { + function makeThirdEmptySourceFile(fs: vfs.FileSystem) { + fs.writeFileSync(sources[Project.third][Source.ts][Part.one], "", "utf8"); + } - // only baseline - verifyOutFileScenario({ - subScenario: "declarationMap and sourceMap disabled", - modifyFs: fs => { - makeThirdEmptySourceFile(fs); - replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, ""); - replaceText(fs, sources[Project.third][Source.config], `"sourceMap": true,`, ""); - replaceText(fs, sources[Project.third][Source.config], `"declarationMap": true,`, ""); - }, - ignoreDtsChanged: true, - ignoreDtsUnchanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "when source files are empty in the own file", + modifyFs: makeThirdEmptySourceFile, + ignoreDtsChanged: true, + baselineOnly: true }); - }); - verifyTsc({ - scenario: "outFile", - subScenario: "non module projects without prepend", - fs: () => outFileFs, - commandLineArgs: ["--b", "/src/third", "--verbose"], - modifyFs: fs => { - // No prepend - replaceText(fs, sources[Project.third][Source.config], `{ "path": "../first", "prepend": true }`, `{ "path": "../first" }`); - replaceText(fs, sources[Project.third][Source.config], `{ "path": "../second", "prepend": true }`, `{ "path": "../second" }`); - - // Non Modules - replaceText(fs, sources[Project.first][Source.config], `"composite": true,`, `"composite": true, "module": "none",`); - replaceText(fs, sources[Project.second][Source.config], `"composite": true,`, `"composite": true, "module": "none",`); - replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, `"composite": true, "module": "none",`); - - // Own file emit - replaceText(fs, sources[Project.first][Source.config], `"outFile": "./bin/first-output.js",`, ""); - replaceText(fs, sources[Project.second][Source.config], `"outFile": "../2/second-output.js",`, ""); - replaceText(fs, sources[Project.third][Source.config], `"outFile": "./thirdjs/output/third-output.js",`, ""); - }, + // only baseline + verifyOutFileScenario({ + subScenario: "declarationMap and sourceMap disabled", + modifyFs: fs => { + makeThirdEmptySourceFile(fs); + replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, ""); + replaceText(fs, sources[Project.third][Source.config], `"sourceMap": true,`, ""); + replaceText(fs, sources[Project.third][Source.config], `"declarationMap": true,`, ""); + }, + ignoreDtsChanged: true, + ignoreDtsUnchanged: true, + baselineOnly: true + }); }); }); + + verifyTsc({ + scenario: "outFile", + subScenario: "non module projects without prepend", + fs: () => outFileFs, + commandLineArgs: ["--b", "/src/third", "--verbose"], + modifyFs: fs => { + // No prepend + replaceText(fs, sources[Project.third][Source.config], `{ "path": "../first", "prepend": true }`, `{ "path": "../first" }`); + replaceText(fs, sources[Project.third][Source.config], `{ "path": "../second", "prepend": true }`, `{ "path": "../second" }`); + + // Non Modules + replaceText(fs, sources[Project.first][Source.config], `"composite": true,`, `"composite": true, "module": "none",`); + replaceText(fs, sources[Project.second][Source.config], `"composite": true,`, `"composite": true, "module": "none",`); + replaceText(fs, sources[Project.third][Source.config], `"composite": true,`, `"composite": true, "module": "none",`); + + // Own file emit + replaceText(fs, sources[Project.first][Source.config], `"outFile": "./bin/first-output.js",`, ""); + replaceText(fs, sources[Project.second][Source.config], `"outFile": "../2/second-output.js",`, ""); + replaceText(fs, sources[Project.third][Source.config], `"outFile": "./thirdjs/output/third-output.js",`, ""); + }, + }); +}); } diff --git a/src/testRunner/unittests/tsbuild/outputPaths.ts b/src/testRunner/unittests/tsbuild/outputPaths.ts index e2a9ac549bf0a..ee9af2c6c89da 100644 --- a/src/testRunner/unittests/tsbuild/outputPaths.ts +++ b/src/testRunner/unittests/tsbuild/outputPaths.ts @@ -1,116 +1,116 @@ namespace ts { - describe("unittests:: tsbuild - output file paths", () => { - const noChangeProject: TscIncremental = { - buildKind: BuildKind.NoChangeRun, - modifyFs: noop, - subScenario: "Normal build without change, that does not block emit on error to show files that get emitted", - commandLineArgs: ["-p", "/src/tsconfig.json"], - }; - const incrementalScenarios: TscIncremental[] = [ - noChangeRun, - noChangeProject, - ]; +describe("unittests:: tsbuild - output file paths", () => { + const noChangeProject: TscIncremental = { + buildKind: BuildKind.NoChangeRun, + modifyFs: noop, + subScenario: "Normal build without change, that does not block emit on error to show files that get emitted", + commandLineArgs: ["-p", "/src/tsconfig.json"], + }; + const incrementalScenarios: TscIncremental[] = [ + noChangeRun, + noChangeProject, + ]; - function verify(input: Pick, expectedOuptutNames: readonly string[]) { - verifyTscSerializedIncrementalEdits({ - scenario: "outputPaths", - commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], - ...input - }); + function verify(input: Pick, expectedOuptutNames: readonly string[]) { + verifyTscSerializedIncrementalEdits({ + scenario: "outputPaths", + commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], + ...input + }); - it("verify getOutputFileNames", () => { - const sys = new fakes.System(input.fs().makeReadonly(), { executingFilePath: "/lib/tsc" }) as TscCompileSystem; - ; - assert.deepEqual( - getOutputFileNames( - parseConfigFileWithSystem("/src/tsconfig.json", {}, /*extendedConfigCache*/ undefined, {}, sys, noop)!, - "/src/src/index.ts", - /*ignoreCase*/ false - ), - expectedOuptutNames - ); - }); - } + it("verify getOutputFileNames", () => { + const sys = new fakes.System(input.fs().makeReadonly(), { executingFilePath: "/lib/tsc" }) as TscCompileSystem; + ; + assert.deepEqual( + getOutputFileNames( + parseConfigFileWithSystem("/src/tsconfig.json", {}, /*extendedConfigCache*/ undefined, {}, sys, noop)!, + "/src/src/index.ts", + /*ignoreCase*/ false + ), + expectedOuptutNames + ); + }); + } - verify({ - subScenario: "when rootDir is not specified", - fs: () => loadProjectFromFiles({ - "/src/src/index.ts": "export const x = 10;", - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { - outDir: "dist" - } - }) - }), - incrementalScenarios, - }, ["/src/dist/index.js"]); + verify({ + subScenario: "when rootDir is not specified", + fs: () => loadProjectFromFiles({ + "/src/src/index.ts": "export const x = 10;", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + outDir: "dist" + } + }) + }), + incrementalScenarios, + }, ["/src/dist/index.js"]); - verify({ - subScenario: "when rootDir is not specified and is composite", - fs: () => loadProjectFromFiles({ - "/src/src/index.ts": "export const x = 10;", - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { - outDir: "dist", - composite: true - } - }) - }), - incrementalScenarios: [ - noChangeRun, - { - ...noChangeProject, - cleanBuildDiscrepancies: () => new Map([ - ["/src/dist/tsconfig.tsbuildinfo", CleanBuildDescrepancy.CleanFileTextDifferent], // tsbuildinfo will have -p setting when built using -p vs no build happens incrementally because of no change. - ["/src/dist/tsconfig.tsbuildinfo.readable.baseline.txt", CleanBuildDescrepancy.CleanFileTextDifferent] // tsbuildinfo will have -p setting when built using -p vs no build happens incrementally because of no change. - ]), + verify({ + subScenario: "when rootDir is not specified and is composite", + fs: () => loadProjectFromFiles({ + "/src/src/index.ts": "export const x = 10;", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + outDir: "dist", + composite: true } - ], - }, ["/src/dist/src/index.js", "/src/dist/src/index.d.ts"]); + }) + }), + incrementalScenarios: [ + noChangeRun, + { + ...noChangeProject, + cleanBuildDiscrepancies: () => new Map([ + ["/src/dist/tsconfig.tsbuildinfo", CleanBuildDescrepancy.CleanFileTextDifferent], // tsbuildinfo will have -p setting when built using -p vs no build happens incrementally because of no change. + ["/src/dist/tsconfig.tsbuildinfo.readable.baseline.txt", CleanBuildDescrepancy.CleanFileTextDifferent] // tsbuildinfo will have -p setting when built using -p vs no build happens incrementally because of no change. + ]), + } + ], + }, ["/src/dist/src/index.js", "/src/dist/src/index.d.ts"]); - verify({ - subScenario: "when rootDir is specified", - fs: () => loadProjectFromFiles({ - "/src/src/index.ts": "export const x = 10;", - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { - outDir: "dist", - rootDir: "src" - } - }) - }), - incrementalScenarios, - }, ["/src/dist/index.js"]); + verify({ + subScenario: "when rootDir is specified", + fs: () => loadProjectFromFiles({ + "/src/src/index.ts": "export const x = 10;", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + outDir: "dist", + rootDir: "src" + } + }) + }), + incrementalScenarios, + }, ["/src/dist/index.js"]); - verify({ - subScenario: "when rootDir is specified but not all files belong to rootDir", - fs: () => loadProjectFromFiles({ - "/src/src/index.ts": "export const x = 10;", - "/src/types/type.ts": "export type t = string;", - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { - outDir: "dist", - rootDir: "src" - } - }) - }), - incrementalScenarios, - }, ["/src/dist/index.js"]); + verify({ + subScenario: "when rootDir is specified but not all files belong to rootDir", + fs: () => loadProjectFromFiles({ + "/src/src/index.ts": "export const x = 10;", + "/src/types/type.ts": "export type t = string;", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + outDir: "dist", + rootDir: "src" + } + }) + }), + incrementalScenarios, + }, ["/src/dist/index.js"]); - verify({ - subScenario: "when rootDir is specified but not all files belong to rootDir and is composite", - fs: () => loadProjectFromFiles({ - "/src/src/index.ts": "export const x = 10;", - "/src/types/type.ts": "export type t = string;", - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { - outDir: "dist", - rootDir: "src", - composite: true - } - }) - }), - incrementalScenarios, - }, ["/src/dist/index.js", "/src/dist/index.d.ts"]); - }); + verify({ + subScenario: "when rootDir is specified but not all files belong to rootDir and is composite", + fs: () => loadProjectFromFiles({ + "/src/src/index.ts": "export const x = 10;", + "/src/types/type.ts": "export type t = string;", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + outDir: "dist", + rootDir: "src", + composite: true + } + }) + }), + incrementalScenarios, + }, ["/src/dist/index.js", "/src/dist/index.d.ts"]); +}); } diff --git a/src/testRunner/unittests/tsbuild/publicApi.ts b/src/testRunner/unittests/tsbuild/publicApi.ts index d3c39188b421a..daee64e066039 100644 --- a/src/testRunner/unittests/tsbuild/publicApi.ts +++ b/src/testRunner/unittests/tsbuild/publicApi.ts @@ -1,124 +1,124 @@ namespace ts { - describe("unittests:: tsbuild:: Public API with custom transformers when passed to build", () => { - let sys: TscCompileSystem; - before(() => { - const initialFs = getFsWithTime(loadProjectFromFiles({ - "/src/tsconfig.json": JSON.stringify({ - references: [ - { path: "./shared/tsconfig.json" }, - { path: "./webpack/tsconfig.json" } - ], - files: [] - }), - "/src/shared/tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true }, - }), - "/src/shared/index.ts": `export function f1() { } +describe("unittests:: tsbuild:: Public API with custom transformers when passed to build", () => { + let sys: TscCompileSystem; + before(() => { + const initialFs = getFsWithTime(loadProjectFromFiles({ + "/src/tsconfig.json": JSON.stringify({ + references: [ + { path: "./shared/tsconfig.json" }, + { path: "./webpack/tsconfig.json" } + ], + files: [] + }), + "/src/shared/tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true }, + }), + "/src/shared/index.ts": `export function f1() { } export class c { } export enum e { } // leading export function f2() { } // trailing`, - "/src/webpack/tsconfig.json": JSON.stringify({ - compilerOptions: { - composite: true, - }, - references: [{ path: "../shared/tsconfig.json" }] - }), - "/src/webpack/index.ts": `export function f2() { } + "/src/webpack/tsconfig.json": JSON.stringify({ + compilerOptions: { + composite: true, + }, + references: [{ path: "../shared/tsconfig.json" }] + }), + "/src/webpack/index.ts": `export function f2() { } export class c2 { } export enum e2 { } // leading export function f22() { } // trailing`, - })).fs.makeReadonly(); - const inputFs = initialFs.shadow(); - inputFs.makeReadonly(); - const fs = inputFs.shadow(); + })).fs.makeReadonly(); + const inputFs = initialFs.shadow(); + inputFs.makeReadonly(); + const fs = inputFs.shadow(); - // Create system - sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" }) as TscCompileSystem; - fakes.patchHostForBuildInfoReadWrite(sys); - const commandLineArgs = ["--b", "/src/tsconfig.json"]; - sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`); - sys.exit = exitCode => sys.exitCode = exitCode; - const writtenFiles = sys.writtenFiles = new Set(); - const originalWriteFile = sys.writeFile; - sys.writeFile = (fileName, content, writeByteOrderMark) => { - const path = toPathWithSystem(sys, fileName); - assert.isFalse(writtenFiles.has(path)); - writtenFiles.add(path); - return originalWriteFile.call(sys, fileName, content, writeByteOrderMark); - }; - const { cb, getPrograms } = commandLineCallbacks(sys, /*originalReadCall*/ undefined, originalWriteFile); - const buildHost = createSolutionBuilderHost( - sys, - /*createProgram*/ undefined, - createDiagnosticReporter(sys, /*pretty*/ true), - createBuilderStatusReporter(sys, /*pretty*/ true), - errorCount => sys.write(getErrorSummaryText(errorCount, sys.newLine)) - ); - buildHost.afterProgramEmitAndDiagnostics = cb; - buildHost.afterEmitBundle = cb; - const builder = createSolutionBuilder(buildHost, [commandLineArgs[1]], { verbose: true }); - const exitStatus = builder.build(/*project*/ undefined, /*cancellationToken*/ undefined, /*writeFile*/ undefined, getCustomTransformers); - sys.exit(exitStatus); - sys.write(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}\n`); - const baseline: string[] = []; - tscWatch.baselinePrograms(baseline, getPrograms, emptyArray, /*baselineDependencies*/ false); - sys.write(baseline.join("\n")); - fs.makeReadonly(); - sys.baseLine = () => { - const baseFsPatch = inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true }); - const patch = fs.diff(inputFs, { includeChangedFileWithSameContent: true }); - return { - file: `tsbuild/$publicAPI/${BuildKind.Initial}/${"build with custom transformers".split(" ").join("-")}.js`, - text: `Input:: + // Create system + sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" }) as TscCompileSystem; + fakes.patchHostForBuildInfoReadWrite(sys); + const commandLineArgs = ["--b", "/src/tsconfig.json"]; + sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`); + sys.exit = exitCode => sys.exitCode = exitCode; + const writtenFiles = sys.writtenFiles = new Set(); + const originalWriteFile = sys.writeFile; + sys.writeFile = (fileName, content, writeByteOrderMark) => { + const path = toPathWithSystem(sys, fileName); + assert.isFalse(writtenFiles.has(path)); + writtenFiles.add(path); + return originalWriteFile.call(sys, fileName, content, writeByteOrderMark); + }; + const { cb, getPrograms } = commandLineCallbacks(sys, /*originalReadCall*/ undefined, originalWriteFile); + const buildHost = createSolutionBuilderHost( + sys, + /*createProgram*/ undefined, + createDiagnosticReporter(sys, /*pretty*/ true), + createBuilderStatusReporter(sys, /*pretty*/ true), + errorCount => sys.write(getErrorSummaryText(errorCount, sys.newLine)) + ); + buildHost.afterProgramEmitAndDiagnostics = cb; + buildHost.afterEmitBundle = cb; + const builder = createSolutionBuilder(buildHost, [commandLineArgs[1]], { verbose: true }); + const exitStatus = builder.build(/*project*/ undefined, /*cancellationToken*/ undefined, /*writeFile*/ undefined, getCustomTransformers); + sys.exit(exitStatus); + sys.write(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}\n`); + const baseline: string[] = []; + tscWatch.baselinePrograms(baseline, getPrograms, emptyArray, /*baselineDependencies*/ false); + sys.write(baseline.join("\n")); + fs.makeReadonly(); + sys.baseLine = () => { + const baseFsPatch = inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true }); + const patch = fs.diff(inputFs, { includeChangedFileWithSameContent: true }); + return { + file: `tsbuild/$publicAPI/${BuildKind.Initial}/${"build with custom transformers".split(" ").join("-")}.js`, + text: `Input:: ${baseFsPatch ? vfs.formatPatch(baseFsPatch) : ""} Output:: ${sys.output.join("")} ${patch ? vfs.formatPatch(patch) : ""}` - }; }; + }; - function getCustomTransformers(project: string): CustomTransformers { - const before: TransformerFactory = context => { - return file => visitEachChild(file, visit, context); - function visit(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - return visitFunction(node as FunctionDeclaration); - default: - return visitEachChild(node, visit, context); - } - } - function visitFunction(node: FunctionDeclaration) { - addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, `@before${project}`, /*hasTrailingNewLine*/ true); - return node; + function getCustomTransformers(project: string): CustomTransformers { + const before: TransformerFactory = context => { + return file => visitEachChild(file, visit, context); + function visit(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return visitFunction(node as FunctionDeclaration); + default: + return visitEachChild(node, visit, context); } - }; + } + function visitFunction(node: FunctionDeclaration) { + addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, `@before${project}`, /*hasTrailingNewLine*/ true); + return node; + } + }; - const after: TransformerFactory = context => { - return file => visitEachChild(file, visit, context); - function visit(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.VariableStatement: - return visitVariableStatement(node as VariableStatement); - default: - return visitEachChild(node, visit, context); - } + const after: TransformerFactory = context => { + return file => visitEachChild(file, visit, context); + function visit(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.VariableStatement: + return visitVariableStatement(node as VariableStatement); + default: + return visitEachChild(node, visit, context); } - function visitVariableStatement(node: VariableStatement) { - addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, `@after${project}`); - return node; - } - }; - return { before: [before], after: [after] }; - } - }); - after(() => { - sys = undefined!; - }); - verifyTscBaseline(() => sys); + } + function visitVariableStatement(node: VariableStatement) { + addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, `@after${project}`); + return node; + } + }; + return { before: [before], after: [after] }; + } + }); + after(() => { + sys = undefined!; }); -} \ No newline at end of file + verifyTscBaseline(() => sys); +}); +} diff --git a/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts b/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts index bd78954780aab..b18ee13e6aa6f 100644 --- a/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts +++ b/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts @@ -1,61 +1,61 @@ namespace ts { - describe("unittests:: tsbuild:: with rootDir of project reference in parentDirectory", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/projectReferenceWithRootDirInParent"); - }); +describe("unittests:: tsbuild:: with rootDir of project reference in parentDirectory", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = loadProjectFromDisk("tests/projects/projectReferenceWithRootDirInParent"); + }); - after(() => { - projFs = undefined!; // Release the contents - }); + after(() => { + projFs = undefined!; // Release the contents + }); - verifyTsc({ - scenario: "projectReferenceWithRootDirInParent", - subScenario: "builds correctly", - fs: () => projFs, - commandLineArgs: ["--b", "/src/src/main", "/src/src/other"], - }); + verifyTsc({ + scenario: "projectReferenceWithRootDirInParent", + subScenario: "builds correctly", + fs: () => projFs, + commandLineArgs: ["--b", "/src/src/main", "/src/src/other"], + }); - verifyTsc({ - scenario: "projectReferenceWithRootDirInParent", - subScenario: "reports error for same tsbuildinfo file because no rootDir in the base", - fs: () => projFs, - commandLineArgs: ["--b", "/src/src/main", "--verbose"], - modifyFs: fs => replaceText(fs, "/src/tsconfig.base.json", `"rootDir": "./src/",`, ""), - }); + verifyTsc({ + scenario: "projectReferenceWithRootDirInParent", + subScenario: "reports error for same tsbuildinfo file because no rootDir in the base", + fs: () => projFs, + commandLineArgs: ["--b", "/src/src/main", "--verbose"], + modifyFs: fs => replaceText(fs, "/src/tsconfig.base.json", `"rootDir": "./src/",`, ""), + }); - verifyTsc({ - scenario: "projectReferenceWithRootDirInParent", - subScenario: "reports error for same tsbuildinfo file", - fs: () => projFs, - commandLineArgs: ["--b", "/src/src/main", "--verbose"], - modifyFs: fs => { - fs.writeFileSync("/src/src/main/tsconfig.json", JSON.stringify({ - compilerOptions: { composite: true, outDir: "../../dist/" }, - references: [{ path: "../other" }] - })); - fs.writeFileSync("/src/src/other/tsconfig.json", JSON.stringify({ - compilerOptions: { composite: true, outDir: "../../dist/" }, - })); - }, - }); + verifyTsc({ + scenario: "projectReferenceWithRootDirInParent", + subScenario: "reports error for same tsbuildinfo file", + fs: () => projFs, + commandLineArgs: ["--b", "/src/src/main", "--verbose"], + modifyFs: fs => { + fs.writeFileSync("/src/src/main/tsconfig.json", JSON.stringify({ + compilerOptions: { composite: true, outDir: "../../dist/" }, + references: [{ path: "../other" }] + })); + fs.writeFileSync("/src/src/other/tsconfig.json", JSON.stringify({ + compilerOptions: { composite: true, outDir: "../../dist/" }, + })); + }, + }); - verifyTsc({ - scenario: "projectReferenceWithRootDirInParent", - subScenario: "reports no error when tsbuildinfo differ", - fs: () => projFs, - commandLineArgs: ["--b", "/src/src/main/tsconfig.main.json", "--verbose"], - modifyFs: fs => { - fs.renameSync("/src/src/main/tsconfig.json", "/src/src/main/tsconfig.main.json"); - fs.renameSync("/src/src/other/tsconfig.json", "/src/src/other/tsconfig.other.json"); - fs.writeFileSync("/src/src/main/tsconfig.main.json", JSON.stringify({ - compilerOptions: { composite: true, outDir: "../../dist/" }, - references: [{ path: "../other/tsconfig.other.json" }] - })); - fs.writeFileSync("/src/src/other/tsconfig.other.json", JSON.stringify({ - compilerOptions: { composite: true, outDir: "../../dist/" }, - })); - }, - }); + verifyTsc({ + scenario: "projectReferenceWithRootDirInParent", + subScenario: "reports no error when tsbuildinfo differ", + fs: () => projFs, + commandLineArgs: ["--b", "/src/src/main/tsconfig.main.json", "--verbose"], + modifyFs: fs => { + fs.renameSync("/src/src/main/tsconfig.json", "/src/src/main/tsconfig.main.json"); + fs.renameSync("/src/src/other/tsconfig.json", "/src/src/other/tsconfig.other.json"); + fs.writeFileSync("/src/src/main/tsconfig.main.json", JSON.stringify({ + compilerOptions: { composite: true, outDir: "../../dist/" }, + references: [{ path: "../other/tsconfig.other.json" }] + })); + fs.writeFileSync("/src/src/other/tsconfig.other.json", JSON.stringify({ + compilerOptions: { composite: true, outDir: "../../dist/" }, + })); + }, }); +}); } diff --git a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts index 733f61efe3f3f..e8d0f2ccc8c8e 100644 --- a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts +++ b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts @@ -1,82 +1,82 @@ namespace ts { - describe("unittests:: tsbuild:: with resolveJsonModule option on project resolveJsonModuleAndComposite", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite"); - }); +describe("unittests:: tsbuild:: with resolveJsonModule option on project resolveJsonModuleAndComposite", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite"); + }); - after(() => { - projFs = undefined!; // Release the contents - }); + after(() => { + projFs = undefined!; // Release the contents + }); - verifyTsc({ - scenario: "resolveJsonModule", - subScenario: "include only", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig_withInclude.json", "--v", "--explainFiles"], - }); + verifyTsc({ + scenario: "resolveJsonModule", + subScenario: "include only", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig_withInclude.json", "--v", "--explainFiles"], + }); - verifyTsc({ - scenario: "resolveJsonModule", - subScenario: "include of json along with other include", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig_withIncludeOfJson.json", "--v", "--explainFiles"], - }); + verifyTsc({ + scenario: "resolveJsonModule", + subScenario: "include of json along with other include", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig_withIncludeOfJson.json", "--v", "--explainFiles"], + }); - verifyTsc({ - scenario: "resolveJsonModule", - subScenario: "include of json along with other include and file name matches ts file", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig_withIncludeOfJson.json", "--v", "--explainFiles"], - modifyFs: fs => { - fs.rimrafSync("/src/src/hello.json"); - fs.writeFileSync("/src/src/index.json", JSON.stringify({ hello: "world" })); - fs.writeFileSync("/src/src/index.ts", `import hello from "./index.json" + verifyTsc({ + scenario: "resolveJsonModule", + subScenario: "include of json along with other include and file name matches ts file", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig_withIncludeOfJson.json", "--v", "--explainFiles"], + modifyFs: fs => { + fs.rimrafSync("/src/src/hello.json"); + fs.writeFileSync("/src/src/index.json", JSON.stringify({ hello: "world" })); + fs.writeFileSync("/src/src/index.ts", `import hello from "./index.json" export default hello.hello`); - }, - }); + }, + }); - verifyTsc({ - scenario: "resolveJsonModule", - subScenario: "files containing json file", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig_withFiles.json", "--v", "--explainFiles"], - }); + verifyTsc({ + scenario: "resolveJsonModule", + subScenario: "files containing json file", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig_withFiles.json", "--v", "--explainFiles"], + }); - verifyTsc({ - scenario: "resolveJsonModule", - subScenario: "include and files", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig_withIncludeAndFiles.json", "--v", "--explainFiles"], - }); + verifyTsc({ + scenario: "resolveJsonModule", + subScenario: "include and files", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig_withIncludeAndFiles.json", "--v", "--explainFiles"], + }); - verifyTscSerializedIncrementalEdits({ - scenario: "resolveJsonModule", - subScenario: "sourcemap", - fs: () => projFs, - commandLineArgs: ["--b", "src/tsconfig_withFiles.json", "--verbose", "--explainFiles"], - modifyFs: fs => replaceText(fs, "src/tsconfig_withFiles.json", `"composite": true,`, `"composite": true, "sourceMap": true,`), - incrementalScenarios: noChangeOnlyRuns - }); + verifyTscSerializedIncrementalEdits({ + scenario: "resolveJsonModule", + subScenario: "sourcemap", + fs: () => projFs, + commandLineArgs: ["--b", "src/tsconfig_withFiles.json", "--verbose", "--explainFiles"], + modifyFs: fs => replaceText(fs, "src/tsconfig_withFiles.json", `"composite": true,`, `"composite": true, "sourceMap": true,`), + incrementalScenarios: noChangeOnlyRuns + }); - verifyTscSerializedIncrementalEdits({ - scenario: "resolveJsonModule", - subScenario: "without outDir", - fs: () => projFs, - commandLineArgs: ["--b", "src/tsconfig_withFiles.json", "--verbose"], - modifyFs: fs => replaceText(fs, "src/tsconfig_withFiles.json", `"outDir": "dist",`, ""), - incrementalScenarios: noChangeOnlyRuns - }); + verifyTscSerializedIncrementalEdits({ + scenario: "resolveJsonModule", + subScenario: "without outDir", + fs: () => projFs, + commandLineArgs: ["--b", "src/tsconfig_withFiles.json", "--verbose"], + modifyFs: fs => replaceText(fs, "src/tsconfig_withFiles.json", `"outDir": "dist",`, ""), + incrementalScenarios: noChangeOnlyRuns }); +}); - describe("unittests:: tsbuild:: with resolveJsonModule option on project importJsonFromProjectReference", () => { - verifyTscSerializedIncrementalEdits({ - scenario: "resolveJsonModule", - subScenario: "importing json module from project reference", - fs: () => loadProjectFromDisk("tests/projects/importJsonFromProjectReference"), - commandLineArgs: ["--b", "src/tsconfig.json", "--verbose", "--explainFiles"], - incrementalScenarios: noChangeOnlyRuns - }); +describe("unittests:: tsbuild:: with resolveJsonModule option on project importJsonFromProjectReference", () => { + verifyTscSerializedIncrementalEdits({ + scenario: "resolveJsonModule", + subScenario: "importing json module from project reference", + fs: () => loadProjectFromDisk("tests/projects/importJsonFromProjectReference"), + commandLineArgs: ["--b", "src/tsconfig.json", "--verbose", "--explainFiles"], + incrementalScenarios: noChangeOnlyRuns }); +}); } diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index d666370d156f6..39745a3b6d70d 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -1,460 +1,460 @@ namespace ts { - describe("unittests:: tsbuild:: on 'sample1' project", () => { - let projFs: vfs.FileSystem; - const testsOutputs = ["/src/tests/index.js", "/src/tests/index.d.ts", "/src/tests/tsconfig.tsbuildinfo"]; - const logicOutputs = ["/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts", "/src/logic/tsconfig.tsbuildinfo"]; - const coreOutputs = ["/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map", "/src/core/tsconfig.tsbuildinfo"]; - const allExpectedOutputs = [...testsOutputs, ...logicOutputs, ...coreOutputs]; - - before(() => { - projFs = loadProjectFromDisk("tests/projects/sample1"); +describe("unittests:: tsbuild:: on 'sample1' project", () => { + let projFs: vfs.FileSystem; + const testsOutputs = ["/src/tests/index.js", "/src/tests/index.d.ts", "/src/tests/tsconfig.tsbuildinfo"]; + const logicOutputs = ["/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts", "/src/logic/tsconfig.tsbuildinfo"]; + const coreOutputs = ["/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map", "/src/core/tsconfig.tsbuildinfo"]; + const allExpectedOutputs = [...testsOutputs, ...logicOutputs, ...coreOutputs]; + + before(() => { + projFs = loadProjectFromDisk("tests/projects/sample1"); + }); + + after(() => { + projFs = undefined!; // Release the contents + }); + + function getSampleFsAfterBuild() { + const fs = projFs.shadow(); + const host = fakes.SolutionBuilderHost.create(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], {}); + builder.build(); + fs.makeReadonly(); + return fs; + } + + describe("sanity check of clean build of 'sample1' project", () => { + verifyTsc({ + scenario: "sample1", + subScenario: "builds correctly when outDir is specified", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests"], + modifyFs: fs => fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({ + compilerOptions: { composite: true, declaration: true, sourceMap: true, outDir: "outDir" }, + references: [{ path: "../core" }] + })), + }); + + verifyTsc({ + scenario: "sample1", + subScenario: "builds correctly when declarationDir is specified", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests"], + modifyFs: fs => fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({ + compilerOptions: { composite: true, declaration: true, sourceMap: true, declarationDir: "out/decls" }, + references: [{ path: "../core" }] + })), + }); + + verifyTsc({ + scenario: "sample1", + subScenario: "builds correctly when project is not composite or doesnt have any references", + fs: () => projFs, + commandLineArgs: ["--b", "/src/core", "--verbose"], + modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"composite": true,`, ""), }); + }); + + describe("dry builds", () => { + verifyTsc({ + scenario: "sample1", + subScenario: "does not write any files in a dry build", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--dry"], + }); + }); - after(() => { - projFs = undefined!; // Release the contents + describe("clean builds", () => { + verifyTscSerializedIncrementalEdits({ + scenario: "sample1", + subScenario: "removes all files it built", + fs: getSampleFsAfterBuild, + commandLineArgs: ["--b", "/src/tests", "--clean"], + incrementalScenarios: noChangeOnlyRuns }); - function getSampleFsAfterBuild() { + it("cleans till project specified", () => { const fs = projFs.shadow(); const host = fakes.SolutionBuilderHost.create(fs); const builder = createSolutionBuilder(host, ["/src/tests"], {}); builder.build(); - fs.makeReadonly(); - return fs; + const result = builder.clean("/src/logic"); + host.assertDiagnosticMessages(/*empty*/); + verifyOutputsPresent(fs, testsOutputs); + verifyOutputsAbsent(fs, [...logicOutputs, ...coreOutputs]); + assert.equal(result, ExitStatus.Success); + }); + + it("cleaning project in not build order doesnt throw error", () => { + const fs = projFs.shadow(); + const host = fakes.SolutionBuilderHost.create(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], {}); + builder.build(); + const result = builder.clean("/src/logic2"); + host.assertDiagnosticMessages(/*empty*/); + verifyOutputsPresent(fs, allExpectedOutputs); + assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped); + }); + }); + + describe("force builds", () => { + verifyTscSerializedIncrementalEdits({ + scenario: "sample1", + subScenario: "always builds under with force option", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--force"], + incrementalScenarios: noChangeOnlyRuns + }); + }); + + describe("can detect when and what to rebuild", () => { + function initializeWithBuild(opts?: BuildOptions) { + const { fs, tick } = getFsWithTime(projFs); + const host = fakes.SolutionBuilderHost.create(fs); + let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); + builder.build(); + host.clearDiagnostics(); + tick(); + builder = createSolutionBuilder(host, ["/src/tests"], { ...(opts || {}), verbose: true }); + return { fs, host, builder }; } - describe("sanity check of clean build of 'sample1' project", () => { - verifyTsc({ - scenario: "sample1", - subScenario: "builds correctly when outDir is specified", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests"], - modifyFs: fs => fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({ - compilerOptions: { composite: true, declaration: true, sourceMap: true, outDir: "outDir" }, - references: [{ path: "../core" }] - })), - }); - - verifyTsc({ - scenario: "sample1", - subScenario: "builds correctly when declarationDir is specified", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests"], - modifyFs: fs => fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({ - compilerOptions: { composite: true, declaration: true, sourceMap: true, declarationDir: "out/decls" }, - references: [{ path: "../core" }] - })), - }); - - verifyTsc({ - scenario: "sample1", - subScenario: "builds correctly when project is not composite or doesnt have any references", - fs: () => projFs, - commandLineArgs: ["--b", "/src/core", "--verbose"], - modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"composite": true,`, ""), - }); + verifyTscIncrementalEdits({ + scenario: "sample1", + subScenario: "can detect when and what to rebuild", + fs: getSampleFsAfterBuild, + commandLineArgs: ["--b", "/src/tests", "--verbose"], + incrementalScenarios: [ + // Update a file in the leaf node (tests), only it should rebuild the last one + { + subScenario: "Only builds the leaf node project", + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => fs.writeFileSync("/src/tests/index.ts", "const m = 10;"), + }, + // Update a file in the parent (without affecting types), should get fast downstream builds + { + subScenario: "Detects type-only changes in upstream projects", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET"), + }, + { + subScenario: "indicates that it would skip builds during a dry build", + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: noop, + commandLineArgs: ["--b", "/src/tests", "--dry"], + }, + { + subScenario: "rebuilds from start if force option is set", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: noop, + commandLineArgs: ["--b", "/src/tests", "--verbose", "--force"], + }, + { + subScenario: "rebuilds when tsconfig changes", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => replaceText(fs, "/src/tests/tsconfig.json", `"composite": true`, `"composite": true, "target": "es3"`), + }, + ] }); - describe("dry builds", () => { - verifyTsc({ - scenario: "sample1", - subScenario: "does not write any files in a dry build", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--dry"], - }); + it("rebuilds completely when version in tsbuildinfo doesnt match ts version", () => { + const { host, builder } = initializeWithBuild(); + changeCompilerVersion(host); + builder.build(); + host.assertDiagnosticMessages( + getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), + [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/core/tsconfig.json", fakes.version, version], + [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], + [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/logic/tsconfig.json", fakes.version, version], + [Diagnostics.Building_project_0, "/src/logic/tsconfig.json"], + [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/tests/tsconfig.json", fakes.version, version], + [Diagnostics.Building_project_0, "/src/tests/tsconfig.json"], + ); }); - describe("clean builds", () => { - verifyTscSerializedIncrementalEdits({ - scenario: "sample1", - subScenario: "removes all files it built", - fs: getSampleFsAfterBuild, - commandLineArgs: ["--b", "/src/tests", "--clean"], - incrementalScenarios: noChangeOnlyRuns - }); - - it("cleans till project specified", () => { - const fs = projFs.shadow(); - const host = fakes.SolutionBuilderHost.create(fs); - const builder = createSolutionBuilder(host, ["/src/tests"], {}); - builder.build(); - const result = builder.clean("/src/logic"); - host.assertDiagnosticMessages(/*empty*/); - verifyOutputsPresent(fs, testsOutputs); - verifyOutputsAbsent(fs, [...logicOutputs, ...coreOutputs]); - assert.equal(result, ExitStatus.Success); - }); - - it("cleaning project in not build order doesnt throw error", () => { - const fs = projFs.shadow(); - const host = fakes.SolutionBuilderHost.create(fs); - const builder = createSolutionBuilder(host, ["/src/tests"], {}); - builder.build(); - const result = builder.clean("/src/logic2"); - host.assertDiagnosticMessages(/*empty*/); - verifyOutputsPresent(fs, allExpectedOutputs); - assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped); - }); + it("does not rebuild if there is no program and bundle in the ts build info event if version doesnt match ts version", () => { + const { fs, tick } = getFsWithTime(projFs); + const host = fakes.SolutionBuilderHost.create(fs, /*options*/ undefined, /*setParentNodes*/ undefined, createAbstractBuilder); + let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); + builder.build(); + host.assertDiagnosticMessages( + getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], + [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"], + [Diagnostics.Building_project_0, "/src/logic/tsconfig.json"], + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tests/tsconfig.json", "src/tests/index.js"], + [Diagnostics.Building_project_0, "/src/tests/tsconfig.json"] + ); + verifyOutputsPresent(fs, allExpectedOutputs); + + host.clearDiagnostics(); + tick(); + builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); + changeCompilerVersion(host); + builder.build(); + host.assertDiagnosticMessages( + getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), + [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"], + [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/logic/tsconfig.json", "src/logic/index.ts", "src/logic/index.js"], + [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/tests/tsconfig.json", "src/tests/index.ts", "src/tests/index.js"] + ); }); - describe("force builds", () => { - verifyTscSerializedIncrementalEdits({ - scenario: "sample1", - subScenario: "always builds under with force option", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--force"], - incrementalScenarios: noChangeOnlyRuns - }); + verifyTscSerializedIncrementalEdits({ + scenario: "sample1", + subScenario: "rebuilds when extended config file changes", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--verbose"], + modifyFs: fs => { + fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: { target: "es3" } })); + replaceText(fs, "/src/tests/tsconfig.json", `"references": [`, `"extends": "./tsconfig.base.json", "references": [`); + }, + incrementalScenarios: [{ + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: {} })) + }] }); - describe("can detect when and what to rebuild", () => { - function initializeWithBuild(opts?: BuildOptions) { - const { fs, tick } = getFsWithTime(projFs); - const host = fakes.SolutionBuilderHost.create(fs); - let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); - builder.build(); - host.clearDiagnostics(); - tick(); - builder = createSolutionBuilder(host, ["/src/tests"], { ...(opts || {}), verbose: true }); - return { fs, host, builder }; - } + it("builds till project specified", () => { + const fs = projFs.shadow(); + const host = fakes.SolutionBuilderHost.create(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], {}); + const result = builder.build("/src/logic"); + host.assertDiagnosticMessages(/*empty*/); + verifyOutputsAbsent(fs, testsOutputs); + verifyOutputsPresent(fs, [...logicOutputs, ...coreOutputs]); + assert.equal(result, ExitStatus.Success); + }); - verifyTscIncrementalEdits({ - scenario: "sample1", - subScenario: "can detect when and what to rebuild", - fs: getSampleFsAfterBuild, - commandLineArgs: ["--b", "/src/tests", "--verbose"], - incrementalScenarios: [ - // Update a file in the leaf node (tests), only it should rebuild the last one - { - subScenario: "Only builds the leaf node project", - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => fs.writeFileSync("/src/tests/index.ts", "const m = 10;"), - }, - // Update a file in the parent (without affecting types), should get fast downstream builds - { - subScenario: "Detects type-only changes in upstream projects", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET"), - }, - { - subScenario: "indicates that it would skip builds during a dry build", - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: noop, - commandLineArgs: ["--b", "/src/tests", "--dry"], - }, - { - subScenario: "rebuilds from start if force option is set", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: noop, - commandLineArgs: ["--b", "/src/tests", "--verbose", "--force"], - }, - { - subScenario: "rebuilds when tsconfig changes", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => replaceText(fs, "/src/tests/tsconfig.json", `"composite": true`, `"composite": true, "target": "es3"`), - }, - ] - }); - - it("rebuilds completely when version in tsbuildinfo doesnt match ts version", () => { - const { host, builder } = initializeWithBuild(); - changeCompilerVersion(host); - builder.build(); - host.assertDiagnosticMessages( - getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), - [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/core/tsconfig.json", fakes.version, version], - [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], - [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/logic/tsconfig.json", fakes.version, version], - [Diagnostics.Building_project_0, "/src/logic/tsconfig.json"], - [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/tests/tsconfig.json", fakes.version, version], - [Diagnostics.Building_project_0, "/src/tests/tsconfig.json"], - ); - }); - - it("does not rebuild if there is no program and bundle in the ts build info event if version doesnt match ts version", () => { - const { fs, tick } = getFsWithTime(projFs); - const host = fakes.SolutionBuilderHost.create(fs, /*options*/ undefined, /*setParentNodes*/ undefined, createAbstractBuilder); - let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); - builder.build(); - host.assertDiagnosticMessages( - getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), - [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], - [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], - [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"], - [Diagnostics.Building_project_0, "/src/logic/tsconfig.json"], - [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tests/tsconfig.json", "src/tests/index.js"], - [Diagnostics.Building_project_0, "/src/tests/tsconfig.json"] - ); - verifyOutputsPresent(fs, allExpectedOutputs); - - host.clearDiagnostics(); - tick(); - builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); - changeCompilerVersion(host); - builder.build(); - host.assertDiagnosticMessages( - getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), - [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"], - [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/logic/tsconfig.json", "src/logic/index.ts", "src/logic/index.js"], - [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/tests/tsconfig.json", "src/tests/index.ts", "src/tests/index.js"] - ); - }); - - verifyTscSerializedIncrementalEdits({ - scenario: "sample1", - subScenario: "rebuilds when extended config file changes", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--verbose"], - modifyFs: fs => { - fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: { target: "es3" } })); - replaceText(fs, "/src/tests/tsconfig.json", `"references": [`, `"extends": "./tsconfig.base.json", "references": [`); - }, - incrementalScenarios: [{ - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: {} })) - }] - }); - - it("builds till project specified", () => { - const fs = projFs.shadow(); - const host = fakes.SolutionBuilderHost.create(fs); - const builder = createSolutionBuilder(host, ["/src/tests"], {}); - const result = builder.build("/src/logic"); - host.assertDiagnosticMessages(/*empty*/); - verifyOutputsAbsent(fs, testsOutputs); - verifyOutputsPresent(fs, [...logicOutputs, ...coreOutputs]); - assert.equal(result, ExitStatus.Success); - }); - - it("building project in not build order doesnt throw error", () => { - const fs = projFs.shadow(); - const host = fakes.SolutionBuilderHost.create(fs); - const builder = createSolutionBuilder(host, ["/src/tests"], {}); - const result = builder.build("/src/logic2"); - host.assertDiagnosticMessages(/*empty*/); - verifyOutputsAbsent(fs, allExpectedOutputs); - assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped); - }); - - it("building using getNextInvalidatedProject", () => { - interface SolutionBuilderResult { - project: ResolvedConfigFileName; - result: T; - } + it("building project in not build order doesnt throw error", () => { + const fs = projFs.shadow(); + const host = fakes.SolutionBuilderHost.create(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], {}); + const result = builder.build("/src/logic2"); + host.assertDiagnosticMessages(/*empty*/); + verifyOutputsAbsent(fs, allExpectedOutputs); + assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped); + }); - const fs = projFs.shadow(); - const host = fakes.SolutionBuilderHost.create(fs); - const builder = createSolutionBuilder(host, ["/src/tests"], {}); - verifyBuildNextResult({ - project: "/src/core/tsconfig.json" as ResolvedConfigFileName, - result: ExitStatus.Success - }, coreOutputs, [...logicOutputs, ...testsOutputs]); - - verifyBuildNextResult({ - project: "/src/logic/tsconfig.json" as ResolvedConfigFileName, - result: ExitStatus.Success - }, [...coreOutputs, ...logicOutputs], testsOutputs); - - verifyBuildNextResult({ - project: "/src/tests/tsconfig.json" as ResolvedConfigFileName, - result: ExitStatus.Success - }, allExpectedOutputs, emptyArray); - - verifyBuildNextResult(/*expected*/ undefined, allExpectedOutputs, emptyArray); - - function verifyBuildNextResult( - expected: SolutionBuilderResult | undefined, - presentOutputs: readonly string[], - absentOutputs: readonly string[] - ) { - const project = builder.getNextInvalidatedProject(); - const result = project && project.done(); - assert.deepEqual(project && { project: project.project, result }, expected); - verifyOutputsPresent(fs, presentOutputs); - verifyOutputsAbsent(fs, absentOutputs); - } - }); - - it("building using buildReferencedProject", () => { - const fs = projFs.shadow(); - const host = fakes.SolutionBuilderHost.create(fs); - const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); - builder.buildReferences("/src/tests"); - host.assertDiagnosticMessages( - getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json"), - [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], - [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], - [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"], - [Diagnostics.Building_project_0, "/src/logic/tsconfig.json"], - ); - verifyOutputsPresent(fs, [...coreOutputs, ...logicOutputs]); - verifyOutputsAbsent(fs, testsOutputs); - }); + it("building using getNextInvalidatedProject", () => { + interface SolutionBuilderResult { + project: ResolvedConfigFileName; + result: T; + } + + const fs = projFs.shadow(); + const host = fakes.SolutionBuilderHost.create(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], {}); + verifyBuildNextResult({ + project: "/src/core/tsconfig.json" as ResolvedConfigFileName, + result: ExitStatus.Success + }, coreOutputs, [...logicOutputs, ...testsOutputs]); + + verifyBuildNextResult({ + project: "/src/logic/tsconfig.json" as ResolvedConfigFileName, + result: ExitStatus.Success + }, [...coreOutputs, ...logicOutputs], testsOutputs); + + verifyBuildNextResult({ + project: "/src/tests/tsconfig.json" as ResolvedConfigFileName, + result: ExitStatus.Success + }, allExpectedOutputs, emptyArray); + + verifyBuildNextResult(/*expected*/ undefined, allExpectedOutputs, emptyArray); + + function verifyBuildNextResult( + expected: SolutionBuilderResult | undefined, + presentOutputs: readonly string[], + absentOutputs: readonly string[] + ) { + const project = builder.getNextInvalidatedProject(); + const result = project && project.done(); + assert.deepEqual(project && { project: project.project, result }, expected); + verifyOutputsPresent(fs, presentOutputs); + verifyOutputsAbsent(fs, absentOutputs); + } }); - describe("downstream-blocked compilations", () => { - verifyTsc({ - scenario: "sample1", - subScenario: "does not build downstream projects if upstream projects have errors", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--verbose"], - modifyFs: fs => replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`) - }); + it("building using buildReferencedProject", () => { + const fs = projFs.shadow(); + const host = fakes.SolutionBuilderHost.create(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); + builder.buildReferences("/src/tests"); + host.assertDiagnosticMessages( + getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json"), + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], + [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"], + [Diagnostics.Building_project_0, "/src/logic/tsconfig.json"], + ); + verifyOutputsPresent(fs, [...coreOutputs, ...logicOutputs]); + verifyOutputsAbsent(fs, testsOutputs); }); + }); - describe("project invalidation", () => { - it("invalidates projects correctly", () => { - const { fs, time, tick } = getFsWithTime(projFs); - const host = fakes.SolutionBuilderHost.create(fs); - const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false }); + describe("downstream-blocked compilations", () => { + verifyTsc({ + scenario: "sample1", + subScenario: "does not build downstream projects if upstream projects have errors", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--verbose"], + modifyFs: fs => replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`) + }); + }); - builder.build(); - host.assertDiagnosticMessages(/*empty*/); + describe("project invalidation", () => { + it("invalidates projects correctly", () => { + const { fs, time, tick } = getFsWithTime(projFs); + const host = fakes.SolutionBuilderHost.create(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false }); - // Update a timestamp in the middle project - tick(); - appendText(fs, "/src/logic/index.ts", "function foo() {}"); - const originalWriteFile = fs.writeFileSync; - const writtenFiles = new Map(); - fs.writeFileSync = (path, data, encoding) => { - writtenFiles.set(path, true); - originalWriteFile.call(fs, path, data, encoding); - }; - // Because we haven't reset the build context, the builder should assume there's nothing to do right now - const status = builder.getUpToDateStatusOfProject("/src/logic"); - assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date"); - verifyInvalidation(/*expectedToWriteTests*/ false); + builder.build(); + host.assertDiagnosticMessages(/*empty*/); + + // Update a timestamp in the middle project + tick(); + appendText(fs, "/src/logic/index.ts", "function foo() {}"); + const originalWriteFile = fs.writeFileSync; + const writtenFiles = new Map(); + fs.writeFileSync = (path, data, encoding) => { + writtenFiles.set(path, true); + originalWriteFile.call(fs, path, data, encoding); + }; + // Because we haven't reset the build context, the builder should assume there's nothing to do right now + const status = builder.getUpToDateStatusOfProject("/src/logic"); + assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date"); + verifyInvalidation(/*expectedToWriteTests*/ false); + + // Rebuild this project + fs.writeFileSync("/src/logic/index.ts", `${fs.readFileSync("/src/logic/index.ts")} +export class cNew {}`); + verifyInvalidation(/*expectedToWriteTests*/ true); + function verifyInvalidation(expectedToWriteTests: boolean) { // Rebuild this project - fs.writeFileSync("/src/logic/index.ts", `${fs.readFileSync("/src/logic/index.ts")} -export class cNew {}`); - verifyInvalidation(/*expectedToWriteTests*/ true); - - function verifyInvalidation(expectedToWriteTests: boolean) { - // Rebuild this project - tick(); - builder.invalidateProject("/src/logic/tsconfig.json" as ResolvedConfigFilePath); - builder.buildNextInvalidatedProject(); - // The file should be updated - assert.isTrue(writtenFiles.has("/src/logic/index.js"), "JS file should have been rebuilt"); - assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt"); - assert.isFalse(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should *not* have been rebuilt"); - assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt"); - writtenFiles.clear(); - - // Build downstream projects should update 'tests', but not 'core' - tick(); - builder.buildNextInvalidatedProject(); - if (expectedToWriteTests) { - assert.isTrue(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should have been rebuilt"); - } - else { - assert.equal(writtenFiles.size, 0, "Should not write any new files"); - } - assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have new timestamp"); - assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt"); + tick(); + builder.invalidateProject("/src/logic/tsconfig.json" as ResolvedConfigFilePath); + builder.buildNextInvalidatedProject(); + // The file should be updated + assert.isTrue(writtenFiles.has("/src/logic/index.js"), "JS file should have been rebuilt"); + assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt"); + assert.isFalse(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should *not* have been rebuilt"); + assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt"); + writtenFiles.clear(); + + // Build downstream projects should update 'tests', but not 'core' + tick(); + builder.buildNextInvalidatedProject(); + if (expectedToWriteTests) { + assert.isTrue(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should have been rebuilt"); } - }); + else { + assert.equal(writtenFiles.size, 0, "Should not write any new files"); + } + assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have new timestamp"); + assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt"); + } }); + }); - const coreChanges: TscIncremental[] = [ - { - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => appendText(fs, "/src/core/index.ts", ` + const coreChanges: TscIncremental[] = [ + { + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => appendText(fs, "/src/core/index.ts", ` export class someClass { }`), - }, - { - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => appendText(fs, "/src/core/index.ts", ` + }, + { + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => appendText(fs, "/src/core/index.ts", ` class someClass2 { }`), - } - ]; - - describe("lists files", () => { - verifyTscSerializedIncrementalEdits({ - scenario: "sample1", - subScenario: "listFiles", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--listFiles"], - incrementalScenarios: coreChanges - }); - verifyTscSerializedIncrementalEdits({ - scenario: "sample1", - subScenario: "listEmittedFiles", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--listEmittedFiles"], - incrementalScenarios: coreChanges - }); - verifyTscSerializedIncrementalEdits({ - scenario: "sample1", - subScenario: "explainFiles", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--explainFiles", "--v"], - incrementalScenarios: coreChanges - }); + } + ]; + + describe("lists files", () => { + verifyTscSerializedIncrementalEdits({ + scenario: "sample1", + subScenario: "listFiles", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--listFiles"], + incrementalScenarios: coreChanges + }); + verifyTscSerializedIncrementalEdits({ + scenario: "sample1", + subScenario: "listEmittedFiles", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--listEmittedFiles"], + incrementalScenarios: coreChanges }); + verifyTscSerializedIncrementalEdits({ + scenario: "sample1", + subScenario: "explainFiles", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--explainFiles", "--v"], + incrementalScenarios: coreChanges + }); + }); - describe("emit output", () => { - verifyTscSerializedIncrementalEdits({ - subScenario: "sample", - fs: () => projFs, - scenario: "sample1", - commandLineArgs: ["--b", "/src/tests", "--verbose"], - baselineSourceMap: true, - baselineReadFileCalls: true, - incrementalScenarios: [ - ...coreChanges, - { - subScenario: "when logic config changes declaration dir", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"declaration": true,`, `"declaration": true, + describe("emit output", () => { + verifyTscSerializedIncrementalEdits({ + subScenario: "sample", + fs: () => projFs, + scenario: "sample1", + commandLineArgs: ["--b", "/src/tests", "--verbose"], + baselineSourceMap: true, + baselineReadFileCalls: true, + incrementalScenarios: [ + ...coreChanges, + { + subScenario: "when logic config changes declaration dir", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"declaration": true,`, `"declaration": true, "declarationDir": "decls",`), - }, - noChangeRun, - ], - }); - - verifyTsc({ - scenario: "sample1", - subScenario: "when logic specifies tsBuildInfoFile", - fs: () => projFs, - modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"composite": true,`, `"composite": true, + }, + noChangeRun, + ], + }); + + verifyTsc({ + scenario: "sample1", + subScenario: "when logic specifies tsBuildInfoFile", + fs: () => projFs, + modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"composite": true,`, `"composite": true, "tsBuildInfoFile": "ownFile.tsbuildinfo",`), - commandLineArgs: ["--b", "/src/tests", "--verbose"], - baselineSourceMap: true, - baselineReadFileCalls: true - }); - - verifyTscSerializedIncrementalEdits({ - subScenario: "when declaration option changes", - fs: () => projFs, - scenario: "sample1", - commandLineArgs: ["--b", "/src/core", "--verbose"], - modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{ + commandLineArgs: ["--b", "/src/tests", "--verbose"], + baselineSourceMap: true, + baselineReadFileCalls: true + }); + + verifyTscSerializedIncrementalEdits({ + subScenario: "when declaration option changes", + fs: () => projFs, + scenario: "sample1", + commandLineArgs: ["--b", "/src/core", "--verbose"], + modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{ "compilerOptions": { "incremental": true, "skipDefaultLibCheck": true } }`), - incrementalScenarios: [{ - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"incremental": true,`, `"incremental": true, "declaration": true,`), - }], - }); - - verifyTscSerializedIncrementalEdits({ - subScenario: "when target option changes", - fs: () => projFs, - scenario: "sample1", - commandLineArgs: ["--b", "/src/core", "--verbose"], - modifyFs: fs => { - fs.writeFileSync("/lib/lib.esnext.full.d.ts", `/// + incrementalScenarios: [{ + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"incremental": true,`, `"incremental": true, "declaration": true,`), + }], + }); + + verifyTscSerializedIncrementalEdits({ + subScenario: "when target option changes", + fs: () => projFs, + scenario: "sample1", + commandLineArgs: ["--b", "/src/core", "--verbose"], + modifyFs: fs => { + fs.writeFileSync("/lib/lib.esnext.full.d.ts", `/// /// `); - fs.writeFileSync("/lib/lib.esnext.d.ts", libContent); - fs.writeFileSync("/lib/lib.d.ts", `/// + fs.writeFileSync("/lib/lib.esnext.d.ts", libContent); + fs.writeFileSync("/lib/lib.d.ts", `/// /// `); - fs.writeFileSync("/src/core/tsconfig.json", `{ + fs.writeFileSync("/src/core/tsconfig.json", `{ "compilerOptions": { "incremental": true, "listFiles": true, @@ -462,36 +462,36 @@ class someClass2 { }`), "target": "esnext", } }`); - }, - incrementalScenarios: [{ - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", "esnext", "es5"), - }], - }); - - verifyTscSerializedIncrementalEdits({ - subScenario: "when module option changes", - fs: () => projFs, - scenario: "sample1", - commandLineArgs: ["--b", "/src/core", "--verbose"], - modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{ + }, + incrementalScenarios: [{ + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", "esnext", "es5"), + }], + }); + + verifyTscSerializedIncrementalEdits({ + subScenario: "when module option changes", + fs: () => projFs, + scenario: "sample1", + commandLineArgs: ["--b", "/src/core", "--verbose"], + modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{ "compilerOptions": { "incremental": true, "module": "commonjs" } }`), - incrementalScenarios: [{ - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"module": "commonjs"`, `"module": "amd"`), - }], - }); - - verifyTscSerializedIncrementalEdits({ - subScenario: "when esModuleInterop option changes", - fs: () => projFs, - scenario: "sample1", - commandLineArgs: ["--b", "/src/tests", "--verbose"], - modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.json", `{ + incrementalScenarios: [{ + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"module": "commonjs"`, `"module": "amd"`), + }], + }); + + verifyTscSerializedIncrementalEdits({ + subScenario: "when esModuleInterop option changes", + fs: () => projFs, + scenario: "sample1", + commandLineArgs: ["--b", "/src/tests", "--verbose"], + modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.json", `{ "references": [ { "path": "../core" }, { "path": "../logic" } @@ -505,11 +505,11 @@ class someClass2 { }`), "esModuleInterop": false } }`), - incrementalScenarios: [{ - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => replaceText(fs, "/src/tests/tsconfig.json", `"esModuleInterop": false`, `"esModuleInterop": true`), - }], - }); + incrementalScenarios: [{ + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => replaceText(fs, "/src/tests/tsconfig.json", `"esModuleInterop": false`, `"esModuleInterop": true`), + }], }); }); +}); } diff --git a/src/testRunner/unittests/tsbuild/transitiveReferences.ts b/src/testRunner/unittests/tsbuild/transitiveReferences.ts index 735c13f5976fb..49b1e76334af6 100644 --- a/src/testRunner/unittests/tsbuild/transitiveReferences.ts +++ b/src/testRunner/unittests/tsbuild/transitiveReferences.ts @@ -1,47 +1,47 @@ namespace ts { - describe("unittests:: tsbuild:: when project reference is referenced transitively", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/transitiveReferences"); - }); - after(() => { - projFs = undefined!; // Release the contents - }); +describe("unittests:: tsbuild:: when project reference is referenced transitively", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = loadProjectFromDisk("tests/projects/transitiveReferences"); + }); + after(() => { + projFs = undefined!; // Release the contents + }); - function modifyFsBTsToNonRelativeImport(fs: vfs.FileSystem, moduleResolution: "node" | "classic") { - fs.writeFileSync("/src/b.ts", `import {A} from 'a'; + function modifyFsBTsToNonRelativeImport(fs: vfs.FileSystem, moduleResolution: "node" | "classic") { + fs.writeFileSync("/src/b.ts", `import {A} from 'a'; export const b = new A();`); - fs.writeFileSync("/src/tsconfig.b.json", JSON.stringify({ - compilerOptions: { - composite: true, - moduleResolution - }, - files: ["b.ts"], - references: [{ path: "tsconfig.a.json" }] - })); - } + fs.writeFileSync("/src/tsconfig.b.json", JSON.stringify({ + compilerOptions: { + composite: true, + moduleResolution + }, + files: ["b.ts"], + references: [{ path: "tsconfig.a.json" }] + })); + } - verifyTsc({ - scenario: "transitiveReferences", - subScenario: "builds correctly", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"], - }); + verifyTsc({ + scenario: "transitiveReferences", + subScenario: "builds correctly", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"], + }); - verifyTsc({ - scenario: "transitiveReferences", - subScenario: "builds correctly when the referenced project uses different module resolution", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"], - modifyFs: fs => modifyFsBTsToNonRelativeImport(fs, "classic"), - }); + verifyTsc({ + scenario: "transitiveReferences", + subScenario: "builds correctly when the referenced project uses different module resolution", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"], + modifyFs: fs => modifyFsBTsToNonRelativeImport(fs, "classic"), + }); - verifyTsc({ - scenario: "transitiveReferences", - subScenario: "reports error about module not found with node resolution with external module name", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"], - modifyFs: fs => modifyFsBTsToNonRelativeImport(fs, "node"), - }); + verifyTsc({ + scenario: "transitiveReferences", + subScenario: "reports error about module not found with node resolution with external module name", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"], + modifyFs: fs => modifyFsBTsToNonRelativeImport(fs, "node"), }); +}); } diff --git a/src/testRunner/unittests/tsbuildWatch/configFileErrors.ts b/src/testRunner/unittests/tsbuildWatch/configFileErrors.ts index 2d0bb90c4cb12..af619dcf50a6b 100644 --- a/src/testRunner/unittests/tsbuildWatch/configFileErrors.ts +++ b/src/testRunner/unittests/tsbuildWatch/configFileErrors.ts @@ -1,19 +1,19 @@ namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: configFileErrors:: reports syntax errors in config file", () => { - function build(sys: WatchedSystem) { - sys.checkTimeoutQueueLengthAndRun(1); // build the project - sys.checkTimeoutQueueLength(0); - } - verifyTscWatch({ - scenario: "configFileErrors", - subScenario: "reports syntax errors in config file", - sys: () => createWatchedSystem( - [ - { path: `${projectRoot}/a.ts`, content: "export function foo() { }" }, - { path: `${projectRoot}/b.ts`, content: "export function bar() { }" }, - { - path: `${projectRoot}/tsconfig.json`, - content: Utils.dedent` +describe("unittests:: tsbuildWatch:: watchMode:: configFileErrors:: reports syntax errors in config file", () => { + function build(sys: WatchedSystem) { + sys.checkTimeoutQueueLengthAndRun(1); // build the project + sys.checkTimeoutQueueLength(0); + } + verifyTscWatch({ + scenario: "configFileErrors", + subScenario: "reports syntax errors in config file", + sys: () => createWatchedSystem( + [ + { path: `${projectRoot}/a.ts`, content: "export function foo() { }" }, + { path: `${projectRoot}/b.ts`, content: "export function bar() { }" }, + { + path: `${projectRoot}/tsconfig.json`, + content: Utils.dedent` { "compilerOptions": { "composite": true, @@ -23,38 +23,38 @@ namespace ts.tscWatch { "b.ts" ] }` - }, - libFile - ], - { currentDirectory: projectRoot } - ), - commandLineArgs: ["--b", "-w"], - changes: [ - { - caption: "reports syntax errors after change to config file", - change: sys => replaceFileText(sys, `${projectRoot}/tsconfig.json`, ",", `, - "declaration": true,`), - timeouts: build, - }, - { - caption: "reports syntax errors after change to ts file", - change: sys => replaceFileText(sys, `${projectRoot}/a.ts`, "foo", "fooBar"), - timeouts: build, }, - { - caption: "reports error when there is no change to tsconfig file", - change: sys => replaceFileText(sys, `${projectRoot}/tsconfig.json`, "", ""), - timeouts: build, - }, - { - caption: "builds after fixing config file errors", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ - compilerOptions: { composite: true, declaration: true }, - files: ["a.ts", "b.ts"] - })), - timeouts: build, - } - ] - }); + libFile + ], + { currentDirectory: projectRoot } + ), + commandLineArgs: ["--b", "-w"], + changes: [ + { + caption: "reports syntax errors after change to config file", + change: sys => replaceFileText(sys, `${projectRoot}/tsconfig.json`, ",", `, + "declaration": true,`), + timeouts: build, + }, + { + caption: "reports syntax errors after change to ts file", + change: sys => replaceFileText(sys, `${projectRoot}/a.ts`, "foo", "fooBar"), + timeouts: build, + }, + { + caption: "reports error when there is no change to tsconfig file", + change: sys => replaceFileText(sys, `${projectRoot}/tsconfig.json`, "", ""), + timeouts: build, + }, + { + caption: "builds after fixing config file errors", + change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ + compilerOptions: { composite: true, declaration: true }, + files: ["a.ts", "b.ts"] + })), + timeouts: build, + } + ] }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/tsbuildWatch/demo.ts b/src/testRunner/unittests/tsbuildWatch/demo.ts index a648f084cd07e..8912be54d9a7d 100644 --- a/src/testRunner/unittests/tsbuildWatch/demo.ts +++ b/src/testRunner/unittests/tsbuildWatch/demo.ts @@ -1,90 +1,90 @@ namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: with demo project", () => { - const projectLocation = `${TestFSWithWatch.tsbuildProjectsLocation}/demo`; - let coreFiles: File[]; - let animalFiles: File[]; - let zooFiles: File[]; - let solutionFile: File; - let baseConfig: File; - let allFiles: File[]; - before(() => { - coreFiles = subProjectFiles("core", ["tsconfig.json", "utilities.ts"]); - animalFiles = subProjectFiles("animals", ["tsconfig.json", "animal.ts", "dog.ts", "index.ts"]); - zooFiles = subProjectFiles("zoo", ["tsconfig.json", "zoo.ts"]); - solutionFile = projectFile("tsconfig.json"); - baseConfig = projectFile("tsconfig-base.json"); - allFiles = [...coreFiles, ...animalFiles, ...zooFiles, solutionFile, baseConfig, { path: libFile.path, content: libContent }]; - }); +describe("unittests:: tsbuildWatch:: watchMode:: with demo project", () => { + const projectLocation = `${TestFSWithWatch.tsbuildProjectsLocation}/demo`; + let coreFiles: File[]; + let animalFiles: File[]; + let zooFiles: File[]; + let solutionFile: File; + let baseConfig: File; + let allFiles: File[]; + before(() => { + coreFiles = subProjectFiles("core", ["tsconfig.json", "utilities.ts"]); + animalFiles = subProjectFiles("animals", ["tsconfig.json", "animal.ts", "dog.ts", "index.ts"]); + zooFiles = subProjectFiles("zoo", ["tsconfig.json", "zoo.ts"]); + solutionFile = projectFile("tsconfig.json"); + baseConfig = projectFile("tsconfig-base.json"); + allFiles = [...coreFiles, ...animalFiles, ...zooFiles, solutionFile, baseConfig, { path: libFile.path, content: libContent }]; + }); - after(() => { - coreFiles = undefined!; - animalFiles = undefined!; - zooFiles = undefined!; - solutionFile = undefined!; - baseConfig = undefined!; - allFiles = undefined!; - }); + after(() => { + coreFiles = undefined!; + animalFiles = undefined!; + zooFiles = undefined!; + solutionFile = undefined!; + baseConfig = undefined!; + allFiles = undefined!; + }); - verifyTscWatch({ - scenario: "demo", - subScenario: "updates with circular reference", - commandLineArgs: ["-b", "-w", "-verbose"], - sys: () => { - const sys = createWatchedSystem(allFiles, { currentDirectory: projectLocation }); - sys.writeFile(coreFiles[0].path, coreFiles[0].content.replace( - "}", - `}, + verifyTscWatch({ + scenario: "demo", + subScenario: "updates with circular reference", + commandLineArgs: ["-b", "-w", "-verbose"], + sys: () => { + const sys = createWatchedSystem(allFiles, { currentDirectory: projectLocation }); + sys.writeFile(coreFiles[0].path, coreFiles[0].content.replace( + "}", + `}, "references": [ { "path": "../zoo" } ]` - )); - return sys; - }, - changes: [ - { - caption: "Fix error", - change: sys => sys.writeFile(coreFiles[0].path, coreFiles[0].content), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // build core - sys.checkTimeoutQueueLengthAndRun(1); // build animals - sys.checkTimeoutQueueLengthAndRun(1); // build zoo - sys.checkTimeoutQueueLengthAndRun(1); // build solution - sys.checkTimeoutQueueLength(0); - }, - } - ] - }); + )); + return sys; + }, + changes: [ + { + caption: "Fix error", + change: sys => sys.writeFile(coreFiles[0].path, coreFiles[0].content), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // build core + sys.checkTimeoutQueueLengthAndRun(1); // build animals + sys.checkTimeoutQueueLengthAndRun(1); // build zoo + sys.checkTimeoutQueueLengthAndRun(1); // build solution + sys.checkTimeoutQueueLength(0); + }, + } + ] + }); - verifyTscWatch({ - scenario: "demo", - subScenario: "updates with bad reference", - commandLineArgs: ["-b", "-w", "-verbose"], - sys: () => { - const sys = createWatchedSystem(allFiles, { currentDirectory: projectLocation }); - sys.writeFile(coreFiles[1].path, `import * as A from '../animals'; + verifyTscWatch({ + scenario: "demo", + subScenario: "updates with bad reference", + commandLineArgs: ["-b", "-w", "-verbose"], + sys: () => { + const sys = createWatchedSystem(allFiles, { currentDirectory: projectLocation }); + sys.writeFile(coreFiles[1].path, `import * as A from '../animals'; ${coreFiles[1].content}`); - return sys; - }, - changes: [ - { - caption: "Prepend a line", - change: sys => sys.writeFile(coreFiles[1].path, ` + return sys; + }, + changes: [ + { + caption: "Prepend a line", + change: sys => sys.writeFile(coreFiles[1].path, ` import * as A from '../animals'; ${coreFiles[1].content}`), - // build core - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - } - ] - }); + // build core + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + } + ] + }); - function subProjectFiles(subProject: string, fileNames: readonly string[]): File[] { - return fileNames.map(file => projectFile(`${subProject}/${file}`)); - } + function subProjectFiles(subProject: string, fileNames: readonly string[]): File[] { + return fileNames.map(file => projectFile(`${subProject}/${file}`)); + } - function projectFile(fileName: string): File { - return TestFSWithWatch.getTsBuildProjectFile("demo", fileName); - } - }); -} \ No newline at end of file + function projectFile(fileName: string): File { + return TestFSWithWatch.getTsBuildProjectFile("demo", fileName); + } +}); +} diff --git a/src/testRunner/unittests/tsbuildWatch/moduleResolution.ts b/src/testRunner/unittests/tsbuildWatch/moduleResolution.ts index d68e63a5c4e8b..2ff00f4f57b71 100644 --- a/src/testRunner/unittests/tsbuildWatch/moduleResolution.ts +++ b/src/testRunner/unittests/tsbuildWatch/moduleResolution.ts @@ -1,56 +1,56 @@ namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: module resolution different in referenced project", () => { - verifyTscWatch({ - scenario: "moduleResolutionCache", - subScenario: "handles the cache correctly when two projects use different module resolution settings", - sys: () => createWatchedSystem( - [ - { path: `${projectRoot}/project1/index.ts`, content: `import { foo } from "file";` }, - { path: `${projectRoot}/project1/node_modules/file/index.d.ts`, content: "export const foo = 10;" }, - { - path: `${projectRoot}/project1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, types: ["foo", "bar"] }, - files: ["index.ts"] - }) - }, - { path: `${projectRoot}/project2/index.ts`, content: `import { foo } from "file";` }, - { path: `${projectRoot}/project2/file.d.ts`, content: "export const foo = 10;" }, - { - path: `${projectRoot}/project2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, types: ["foo"], moduleResolution: "classic" }, - files: ["index.ts"] - }) - }, - { path: `${projectRoot}/node_modules/@types/foo/index.d.ts`, content: "export const foo = 10;" }, - { path: `${projectRoot}/node_modules/@types/bar/index.d.ts`, content: "export const bar = 10;" }, - { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - files: [], - references: [ - { path: "./project1" }, - { path: "./project2" } - ] - }) - }, - libFile - ], - { currentDirectory: projectRoot } - ), - commandLineArgs: ["--b", "-w", "-v"], - changes: [ +describe("unittests:: tsbuildWatch:: watchMode:: module resolution different in referenced project", () => { + verifyTscWatch({ + scenario: "moduleResolutionCache", + subScenario: "handles the cache correctly when two projects use different module resolution settings", + sys: () => createWatchedSystem( + [ + { path: `${projectRoot}/project1/index.ts`, content: `import { foo } from "file";` }, + { path: `${projectRoot}/project1/node_modules/file/index.d.ts`, content: "export const foo = 10;" }, { - caption: "Append text", - change: sys => sys.appendFile(`${projectRoot}/project1/index.ts`, "const bar = 10;"), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // build project1 - sys.checkTimeoutQueueLengthAndRun(1); // Solution - sys.checkTimeoutQueueLength(0); - } + path: `${projectRoot}/project1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, types: ["foo", "bar"] }, + files: ["index.ts"] + }) }, - ] - }); + { path: `${projectRoot}/project2/index.ts`, content: `import { foo } from "file";` }, + { path: `${projectRoot}/project2/file.d.ts`, content: "export const foo = 10;" }, + { + path: `${projectRoot}/project2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, types: ["foo"], moduleResolution: "classic" }, + files: ["index.ts"] + }) + }, + { path: `${projectRoot}/node_modules/@types/foo/index.d.ts`, content: "export const foo = 10;" }, + { path: `${projectRoot}/node_modules/@types/bar/index.d.ts`, content: "export const bar = 10;" }, + { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + files: [], + references: [ + { path: "./project1" }, + { path: "./project2" } + ] + }) + }, + libFile + ], + { currentDirectory: projectRoot } + ), + commandLineArgs: ["--b", "-w", "-v"], + changes: [ + { + caption: "Append text", + change: sys => sys.appendFile(`${projectRoot}/project1/index.ts`, "const bar = 10;"), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // build project1 + sys.checkTimeoutQueueLengthAndRun(1); // Solution + sys.checkTimeoutQueueLength(0); + } + }, + ] }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/tsbuildWatch/noEmit.ts b/src/testRunner/unittests/tsbuildWatch/noEmit.ts index ddcf1dd0b2893..33461910b20a4 100644 --- a/src/testRunner/unittests/tsbuildWatch/noEmit.ts +++ b/src/testRunner/unittests/tsbuildWatch/noEmit.ts @@ -1,34 +1,34 @@ namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: with noEmit", () => { - verifyTscWatch({ - scenario: "noEmit", - subScenario: "does not go in loop when watching when no files are emitted", - commandLineArgs: ["-b", "-w", "-verbose"], - sys: () => createWatchedSystem( - [ - libFile, - { path: `${projectRoot}/a.js`, content: "" }, - { path: `${projectRoot}/b.ts`, content: "" }, - { path: `${projectRoot}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { allowJs: true, noEmit: true } }) }, - { path: libFile.path, content: libContent } - ], - { currentDirectory: projectRoot } - ), - changes: [ - { - caption: "No change", - change: sys => sys.writeFile(`${projectRoot}/a.js`, sys.readFile(`${projectRoot}/a.js`)!), - // build project - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - }, - { - caption: "change", - change: sys => sys.writeFile(`${projectRoot}/a.js`, "const x = 10;"), - // build project - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - }, +describe("unittests:: tsbuildWatch:: watchMode:: with noEmit", () => { + verifyTscWatch({ + scenario: "noEmit", + subScenario: "does not go in loop when watching when no files are emitted", + commandLineArgs: ["-b", "-w", "-verbose"], + sys: () => createWatchedSystem( + [ + libFile, + { path: `${projectRoot}/a.js`, content: "" }, + { path: `${projectRoot}/b.ts`, content: "" }, + { path: `${projectRoot}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { allowJs: true, noEmit: true } }) }, + { path: libFile.path, content: libContent } ], - baselineIncremental: true - }); + { currentDirectory: projectRoot } + ), + changes: [ + { + caption: "No change", + change: sys => sys.writeFile(`${projectRoot}/a.js`, sys.readFile(`${projectRoot}/a.js`)!), + // build project + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + }, + { + caption: "change", + change: sys => sys.writeFile(`${projectRoot}/a.js`, "const x = 10;"), + // build project + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + }, + ], + baselineIncremental: true }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/tsbuildWatch/noEmitOnError.ts b/src/testRunner/unittests/tsbuildWatch/noEmitOnError.ts index 34ae0e8bcdf0f..5a8d152ace589 100644 --- a/src/testRunner/unittests/tsbuildWatch/noEmitOnError.ts +++ b/src/testRunner/unittests/tsbuildWatch/noEmitOnError.ts @@ -1,46 +1,46 @@ namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: with noEmitOnError", () => { - function change(caption: string, content: string): TscWatchCompileChange { - return { - caption, - change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, content), - // build project - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - }; - } - - const noChange: TscWatchCompileChange = { - caption: "No change", - change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`)!), +describe("unittests:: tsbuildWatch:: watchMode:: with noEmitOnError", () => { + function change(caption: string, content: string): TscWatchCompileChange { + return { + caption, + change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, content), // build project timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, }; - verifyTscWatch({ - scenario: "noEmitOnError", - subScenario: "does not emit any files on error", - commandLineArgs: ["-b", "-w", "-verbose"], - sys: () => createWatchedSystem( - [ - ...["tsconfig.json", "shared/types/db.ts", "src/main.ts", "src/other.ts"] - .map(f => TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", f)), - { path: libFile.path, content: libContent } - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError` } - ), - changes: [ - noChange, - change("Fix Syntax error", `import { A } from "../shared/types/db"; + } + + const noChange: TscWatchCompileChange = { + caption: "No change", + change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`)!), + // build project + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + }; + verifyTscWatch({ + scenario: "noEmitOnError", + subScenario: "does not emit any files on error", + commandLineArgs: ["-b", "-w", "-verbose"], + sys: () => createWatchedSystem( + [ + ...["tsconfig.json", "shared/types/db.ts", "src/main.ts", "src/other.ts"] + .map(f => TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", f)), + { path: libFile.path, content: libContent } + ], + { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError` } + ), + changes: [ + noChange, + change("Fix Syntax error", `import { A } from "../shared/types/db"; const a = { lastName: 'sdsd' };`), - change("Semantic Error", `import { A } from "../shared/types/db"; + change("Semantic Error", `import { A } from "../shared/types/db"; const a: string = 10;`), - noChange, - change("Fix Semantic Error", `import { A } from "../shared/types/db"; + noChange, + change("Fix Semantic Error", `import { A } from "../shared/types/db"; const a: string = "hello";`), - noChange, - ], - baselineIncremental: true - }); + noChange, + ], + baselineIncremental: true }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts index 5d63b9b47e157..4d0bef1b19169 100644 --- a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts +++ b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts @@ -1,341 +1,341 @@ namespace ts.tscWatch { - import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation; - describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => { - type TsBuildWatchSystem = TestFSWithWatch.TestServerHostTrackingWrittenFiles; - - function createTsBuildWatchSystem(fileOrFolderList: readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) { - return TestFSWithWatch.changeToHostTrackingWrittenFiles( - createWatchedSystem(fileOrFolderList, params) - ); - } - - type OutputFileStamp = [string, Date | undefined, boolean]; - function transformOutputToOutputFileStamp(f: string, host: TsBuildWatchSystem): OutputFileStamp { - return [f, host.getModifiedTime(f), host.writtenFiles.has(host.toFullPath(f))] as OutputFileStamp; - } - - const scenario = "programUpdates"; - const project = "sample1"; - const enum SubProject { - core = "core", - logic = "logic", - tests = "tests", - ui = "ui" +import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation; +describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => { + type TsBuildWatchSystem = TestFSWithWatch.TestServerHostTrackingWrittenFiles; + + function createTsBuildWatchSystem(fileOrFolderList: readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) { + return TestFSWithWatch.changeToHostTrackingWrittenFiles( + createWatchedSystem(fileOrFolderList, params) + ); + } + + type OutputFileStamp = [string, Date | undefined, boolean]; + function transformOutputToOutputFileStamp(f: string, host: TsBuildWatchSystem): OutputFileStamp { + return [f, host.getModifiedTime(f), host.writtenFiles.has(host.toFullPath(f))] as OutputFileStamp; + } + + const scenario = "programUpdates"; + const project = "sample1"; + const enum SubProject { + core = "core", + logic = "logic", + tests = "tests", + ui = "ui" + } + type ReadonlyFile = Readonly; + /** [tsconfig, index] | [tsconfig, index, anotherModule, someDecl] */ + type SubProjectFiles = [ReadonlyFile, ReadonlyFile] | [ReadonlyFile, ReadonlyFile, ReadonlyFile, ReadonlyFile]; + function projectPath(subProject: SubProject) { + return TestFSWithWatch.getTsBuildProjectFilePath(project, subProject); + } + + function projectFilePath(subProject: SubProject, baseFileName: string) { + return `${projectPath(subProject)}/${baseFileName.toLowerCase()}`; + } + + function projectFile(subProject: SubProject, baseFileName: string): File { + return TestFSWithWatch.getTsBuildProjectFile(project, `${subProject}/${baseFileName}`); + } + + function subProjectFiles(subProject: SubProject, anotherModuleAndSomeDecl?: true): SubProjectFiles { + const tsconfig = projectFile(subProject, "tsconfig.json"); + const index = projectFile(subProject, "index.ts"); + if (!anotherModuleAndSomeDecl) { + return [tsconfig, index]; } - type ReadonlyFile = Readonly; - /** [tsconfig, index] | [tsconfig, index, anotherModule, someDecl] */ - type SubProjectFiles = [ReadonlyFile, ReadonlyFile] | [ReadonlyFile, ReadonlyFile, ReadonlyFile, ReadonlyFile]; - function projectPath(subProject: SubProject) { - return TestFSWithWatch.getTsBuildProjectFilePath(project, subProject); - } - - function projectFilePath(subProject: SubProject, baseFileName: string) { - return `${projectPath(subProject)}/${baseFileName.toLowerCase()}`; - } - - function projectFile(subProject: SubProject, baseFileName: string): File { - return TestFSWithWatch.getTsBuildProjectFile(project, `${subProject}/${baseFileName}`); - } - - function subProjectFiles(subProject: SubProject, anotherModuleAndSomeDecl?: true): SubProjectFiles { - const tsconfig = projectFile(subProject, "tsconfig.json"); - const index = projectFile(subProject, "index.ts"); - if (!anotherModuleAndSomeDecl) { - return [tsconfig, index]; - } - const anotherModule = projectFile(SubProject.core, "anotherModule.ts"); - const someDecl = projectFile(SubProject.core, "some_decl.ts"); - return [tsconfig, index, anotherModule, someDecl]; - } - - function getOutputFileNames(subProject: SubProject, baseFileNameWithoutExtension: string) { - const file = projectFilePath(subProject, baseFileNameWithoutExtension); - return [`${file}.js`, `${file}.d.ts`]; + const anotherModule = projectFile(SubProject.core, "anotherModule.ts"); + const someDecl = projectFile(SubProject.core, "some_decl.ts"); + return [tsconfig, index, anotherModule, someDecl]; + } + + function getOutputFileNames(subProject: SubProject, baseFileNameWithoutExtension: string) { + const file = projectFilePath(subProject, baseFileNameWithoutExtension); + return [`${file}.js`, `${file}.d.ts`]; + } + + function getOutputStamps(host: TsBuildWatchSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] { + return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => transformOutputToOutputFileStamp(f, host)); + } + + function getOutputFileStamps(host: TsBuildWatchSystem, additionalFiles?: readonly [SubProject, string][]): OutputFileStamp[] { + const result = [ + ...getOutputStamps(host, SubProject.core, "anotherModule"), + ...getOutputStamps(host, SubProject.core, "index"), + ...getOutputStamps(host, SubProject.logic, "index"), + ...getOutputStamps(host, SubProject.tests, "index"), + ]; + if (additionalFiles) { + additionalFiles.forEach(([subProject, baseFileNameWithoutExtension]) => result.push(...getOutputStamps(host, subProject, baseFileNameWithoutExtension))); } + host.writtenFiles.clear(); + return result; + } + + function changeFile(fileName: string | (() => string), content: string | (() => string), caption: string): TscWatchCompileChange { + return { + caption, + change: sys => sys.writeFile(isString(fileName) ? fileName : fileName(), isString(content) ? content : content()), + timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds core + }; + } + + function changeCore(content: () => string, caption: string) { + return changeFile(() => core[1].path, content, caption); + } + + let core: SubProjectFiles; + let logic: SubProjectFiles; + let tests: SubProjectFiles; + let ui: SubProjectFiles; + let allFiles: readonly File[]; + let testProjectExpectedWatchedFiles: string[]; + let testProjectExpectedWatchedDirectoriesRecursive: string[]; + + before(() => { + core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true); + logic = subProjectFiles(SubProject.logic); + tests = subProjectFiles(SubProject.tests); + ui = subProjectFiles(SubProject.ui); + allFiles = [libFile, ...core, ...logic, ...tests, ...ui]; + testProjectExpectedWatchedFiles = [core[0], core[1], core[2]!, ...logic, ...tests].map(f => f.path.toLowerCase()); + testProjectExpectedWatchedDirectoriesRecursive = [projectPath(SubProject.core), projectPath(SubProject.logic)]; + }); - function getOutputStamps(host: TsBuildWatchSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] { - return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => transformOutputToOutputFileStamp(f, host)); - } + after(() => { + core = undefined!; + logic = undefined!; + tests = undefined!; + ui = undefined!; + allFiles = undefined!; + testProjectExpectedWatchedFiles = undefined!; + testProjectExpectedWatchedDirectoriesRecursive = undefined!; + }); - function getOutputFileStamps(host: TsBuildWatchSystem, additionalFiles?: readonly [SubProject, string][]): OutputFileStamp[] { - const result = [ - ...getOutputStamps(host, SubProject.core, "anotherModule"), - ...getOutputStamps(host, SubProject.core, "index"), - ...getOutputStamps(host, SubProject.logic, "index"), - ...getOutputStamps(host, SubProject.tests, "index"), - ]; - if (additionalFiles) { - additionalFiles.forEach(([subProject, baseFileNameWithoutExtension]) => result.push(...getOutputStamps(host, subProject, baseFileNameWithoutExtension))); - } - host.writtenFiles.clear(); - return result; - } + verifyTscWatch({ + scenario, + subScenario: "creates solution in watch mode", + commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`], + sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }), + changes: emptyArray + }); - function changeFile(fileName: string | (() => string), content: string | (() => string), caption: string): TscWatchCompileChange { - return { - caption, - change: sys => sys.writeFile(isString(fileName) ? fileName : fileName(), isString(content) ? content : content()), - timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds core - }; + it("verify building references watches only those projects", () => { + const system = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation }); + const host = createSolutionBuilderWithWatchHost(system); + const solutionBuilder = createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`], { watch: true }); + solutionBuilder.buildReferences(`${project}/${SubProject.tests}`); + + checkWatchedFiles(system, testProjectExpectedWatchedFiles.slice(0, testProjectExpectedWatchedFiles.length - tests.length)); + checkWatchedDirectories(system, emptyArray, /*recursive*/ false); + checkWatchedDirectories(system, testProjectExpectedWatchedDirectoriesRecursive, /*recursive*/ true); + + checkOutputErrorsInitial(system, emptyArray); + const testOutput = getOutputStamps(system, SubProject.tests, "index"); + const outputFileStamps = getOutputFileStamps(system); + for (const stamp of outputFileStamps.slice(0, outputFileStamps.length - testOutput.length)) { + assert.isDefined(stamp[1], `${stamp[0]} expected to be present`); } - - function changeCore(content: () => string, caption: string) { - return changeFile(() => core[1].path, content, caption); + for (const stamp of testOutput) { + assert.isUndefined(stamp[1], `${stamp[0]} expected to be missing`); } + return system; + }); - let core: SubProjectFiles; - let logic: SubProjectFiles; - let tests: SubProjectFiles; - let ui: SubProjectFiles; - let allFiles: readonly File[]; - let testProjectExpectedWatchedFiles: string[]; - let testProjectExpectedWatchedDirectoriesRecursive: string[]; - - before(() => { - core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true); - logic = subProjectFiles(SubProject.logic); - tests = subProjectFiles(SubProject.tests); - ui = subProjectFiles(SubProject.ui); - allFiles = [libFile, ...core, ...logic, ...tests, ...ui]; - testProjectExpectedWatchedFiles = [core[0], core[1], core[2]!, ...logic, ...tests].map(f => f.path.toLowerCase()); - testProjectExpectedWatchedDirectoriesRecursive = [projectPath(SubProject.core), projectPath(SubProject.logic)]; - }); - - after(() => { - core = undefined!; - logic = undefined!; - tests = undefined!; - ui = undefined!; - allFiles = undefined!; - testProjectExpectedWatchedFiles = undefined!; - testProjectExpectedWatchedDirectoriesRecursive = undefined!; - }); - - verifyTscWatch({ - scenario, - subScenario: "creates solution in watch mode", - commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`], - sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }), - changes: emptyArray - }); - - it("verify building references watches only those projects", () => { - const system = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation }); - const host = createSolutionBuilderWithWatchHost(system); - const solutionBuilder = createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`], { watch: true }); - solutionBuilder.buildReferences(`${project}/${SubProject.tests}`); - - checkWatchedFiles(system, testProjectExpectedWatchedFiles.slice(0, testProjectExpectedWatchedFiles.length - tests.length)); - checkWatchedDirectories(system, emptyArray, /*recursive*/ false); - checkWatchedDirectories(system, testProjectExpectedWatchedDirectoriesRecursive, /*recursive*/ true); - - checkOutputErrorsInitial(system, emptyArray); - const testOutput = getOutputStamps(system, SubProject.tests, "index"); - const outputFileStamps = getOutputFileStamps(system); - for (const stamp of outputFileStamps.slice(0, outputFileStamps.length - testOutput.length)) { - assert.isDefined(stamp[1], `${stamp[0]} expected to be present`); - } - for (const stamp of testOutput) { - assert.isUndefined(stamp[1], `${stamp[0]} expected to be missing`); - } - return system; - }); + const buildTests: TscWatchCompileChange = { + caption: "Build Tests", + change: noop, + // Build tests + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + }; - const buildTests: TscWatchCompileChange = { - caption: "Build Tests", - change: noop, - // Build tests - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + describe("validates the changes and watched files", () => { + const newFileWithoutExtension = "newFile"; + const newFile: File = { + path: projectFilePath(SubProject.core, `${newFileWithoutExtension}.ts`), + content: `export const newFileConst = 30;` }; - describe("validates the changes and watched files", () => { - const newFileWithoutExtension = "newFile"; - const newFile: File = { - path: projectFilePath(SubProject.core, `${newFileWithoutExtension}.ts`), - content: `export const newFileConst = 30;` + function verifyProjectChanges(subScenario: string, allFilesGetter: () => readonly File[]) { + const buildLogicOrUpdateTimeStamps: TscWatchCompileChange = { + caption: "Build logic or update time stamps", + change: noop, + timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds logic or updates timestamps }; - function verifyProjectChanges(subScenario: string, allFilesGetter: () => readonly File[]) { - const buildLogicOrUpdateTimeStamps: TscWatchCompileChange = { - caption: "Build logic or update time stamps", - change: noop, - timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds logic or updates timestamps - }; - - verifyTscWatch({ - scenario, - subScenario: `${subScenario}/change builds changes and reports found errors message`, - commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`], - sys: () => createWatchedSystem( - allFilesGetter(), - { currentDirectory: projectsLocation } - ), - changes: [ - changeCore(() => `${core[1].content} + verifyTscWatch({ + scenario, + subScenario: `${subScenario}/change builds changes and reports found errors message`, + commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`], + sys: () => createWatchedSystem( + allFilesGetter(), + { currentDirectory: projectsLocation } + ), + changes: [ + changeCore(() => `${core[1].content} export class someClass { }`, "Make change to core"), - buildLogicOrUpdateTimeStamps, - buildTests, - // Another change requeues and builds it - changeCore(() => core[1].content, "Revert core file"), - buildLogicOrUpdateTimeStamps, - buildTests, - { - caption: "Make two changes", - change: sys => { - const change1 = `${core[1].content} + buildLogicOrUpdateTimeStamps, + buildTests, + // Another change requeues and builds it + changeCore(() => core[1].content, "Revert core file"), + buildLogicOrUpdateTimeStamps, + buildTests, + { + caption: "Make two changes", + change: sys => { + const change1 = `${core[1].content} export class someClass { }`; - sys.writeFile(core[1].path, change1); - assert.equal(sys.writtenFiles.size, 1); - sys.writtenFiles.clear(); - sys.writeFile(core[1].path, `${change1} + sys.writeFile(core[1].path, change1); + assert.equal(sys.writtenFiles.size, 1); + sys.writtenFiles.clear(); + sys.writeFile(core[1].path, `${change1} export class someClass2 { }`); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds core }, - buildLogicOrUpdateTimeStamps, - buildTests, - ] - }); + timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds core + }, + buildLogicOrUpdateTimeStamps, + buildTests, + ] + }); - verifyTscWatch({ - scenario, - subScenario: `${subScenario}/non local change does not start build of referencing projects`, - commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`], - sys: () => createWatchedSystem( - allFilesGetter(), - { currentDirectory: projectsLocation } - ), - changes: [ - changeCore(() => `${core[1].content} + verifyTscWatch({ + scenario, + subScenario: `${subScenario}/non local change does not start build of referencing projects`, + commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`], + sys: () => createWatchedSystem( + allFilesGetter(), + { currentDirectory: projectsLocation } + ), + changes: [ + changeCore(() => `${core[1].content} function foo() { }`, "Make local change to core"), - buildLogicOrUpdateTimeStamps, - buildTests - ] - }); - - function changeNewFile(newFileContent: string) { - return changeFile(newFile.path, newFileContent, "Change to new File and build core"); - } - verifyTscWatch({ - scenario, - subScenario: `${subScenario}/builds when new file is added, and its subsequent updates`, - commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`], - sys: () => createWatchedSystem( - allFilesGetter(), - { currentDirectory: projectsLocation } - ), - changes: [ - changeNewFile(newFile.content), - buildLogicOrUpdateTimeStamps, - buildTests, - changeNewFile(`${newFile.content} -export class someClass2 { }`), - buildLogicOrUpdateTimeStamps, - buildTests - ] - }); - } - - describe("with simple project reference graph", () => { - verifyProjectChanges( - "with simple project reference graph", - () => allFiles - ); + buildLogicOrUpdateTimeStamps, + buildTests + ] }); - describe("with circular project reference", () => { - verifyProjectChanges( - "with circular project reference", - () => { - const [coreTsconfig, ...otherCoreFiles] = core; - const circularCoreConfig: File = { - path: coreTsconfig.path, - content: JSON.stringify({ - compilerOptions: { composite: true, declaration: true }, - references: [{ path: "../tests", circular: true }] - }) - }; - return [libFile, circularCoreConfig, ...otherCoreFiles, ...logic, ...tests]; - } - ); + function changeNewFile(newFileContent: string) { + return changeFile(newFile.path, newFileContent, "Change to new File and build core"); + } + verifyTscWatch({ + scenario, + subScenario: `${subScenario}/builds when new file is added, and its subsequent updates`, + commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`], + sys: () => createWatchedSystem( + allFilesGetter(), + { currentDirectory: projectsLocation } + ), + changes: [ + changeNewFile(newFile.content), + buildLogicOrUpdateTimeStamps, + buildTests, + changeNewFile(`${newFile.content} +export class someClass2 { }`), + buildLogicOrUpdateTimeStamps, + buildTests + ] }); - }); + } - verifyTscWatch({ - scenario, - subScenario: "watches config files that are not present", - commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`], - sys: () => createWatchedSystem( - [libFile, ...core, logic[1], ...tests], - { currentDirectory: projectsLocation } - ), - changes: [ - { - caption: "Write logic tsconfig and build logic", - change: sys => sys.writeFile(logic[0].path, logic[0].content), - timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds logic - }, - buildTests - ] + describe("with simple project reference graph", () => { + verifyProjectChanges( + "with simple project reference graph", + () => allFiles + ); }); - describe("when referenced using prepend, builds referencing project even for non local change", () => { - let coreIndex: File; - before(() => { - coreIndex = { - path: core[1].path, - content: `function foo() { return 10; }` - }; - }); - after(() => { - coreIndex = undefined!; - }); - const buildLogic: TscWatchCompileChange = { - caption: "Build logic", - change: noop, - // Builds logic - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - }; - verifyTscWatch({ - scenario, - subScenario: "when referenced using prepend builds referencing project even for non local change", - commandLineArgs: ["-b", "-w", `${project}/${SubProject.logic}`], - sys: () => { - const coreTsConfig: File = { - path: core[0].path, - content: JSON.stringify({ - compilerOptions: { composite: true, declaration: true, outFile: "index.js" } - }) - }; - const logicTsConfig: File = { - path: logic[0].path, + describe("with circular project reference", () => { + verifyProjectChanges( + "with circular project reference", + () => { + const [coreTsconfig, ...otherCoreFiles] = core; + const circularCoreConfig: File = { + path: coreTsconfig.path, content: JSON.stringify({ - compilerOptions: { composite: true, declaration: true, outFile: "index.js" }, - references: [{ path: "../core", prepend: true }] + compilerOptions: { composite: true, declaration: true }, + references: [{ path: "../tests", circular: true }] }) }; - const logicIndex: File = { - path: logic[1].path, - content: `function bar() { return foo() + 1 };` - }; - return createWatchedSystem([libFile, coreTsConfig, coreIndex, logicTsConfig, logicIndex], { currentDirectory: projectsLocation }); - }, - changes: [ - changeCore(() => `${coreIndex.content} + return [libFile, circularCoreConfig, ...otherCoreFiles, ...logic, ...tests]; + } + ); + }); + }); + + verifyTscWatch({ + scenario, + subScenario: "watches config files that are not present", + commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`], + sys: () => createWatchedSystem( + [libFile, ...core, logic[1], ...tests], + { currentDirectory: projectsLocation } + ), + changes: [ + { + caption: "Write logic tsconfig and build logic", + change: sys => sys.writeFile(logic[0].path, logic[0].content), + timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds logic + }, + buildTests + ] + }); + + describe("when referenced using prepend, builds referencing project even for non local change", () => { + let coreIndex: File; + before(() => { + coreIndex = { + path: core[1].path, + content: `function foo() { return 10; }` + }; + }); + after(() => { + coreIndex = undefined!; + }); + const buildLogic: TscWatchCompileChange = { + caption: "Build logic", + change: noop, + // Builds logic + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + }; + verifyTscWatch({ + scenario, + subScenario: "when referenced using prepend builds referencing project even for non local change", + commandLineArgs: ["-b", "-w", `${project}/${SubProject.logic}`], + sys: () => { + const coreTsConfig: File = { + path: core[0].path, + content: JSON.stringify({ + compilerOptions: { composite: true, declaration: true, outFile: "index.js" } + }) + }; + const logicTsConfig: File = { + path: logic[0].path, + content: JSON.stringify({ + compilerOptions: { composite: true, declaration: true, outFile: "index.js" }, + references: [{ path: "../core", prepend: true }] + }) + }; + const logicIndex: File = { + path: logic[1].path, + content: `function bar() { return foo() + 1 };` + }; + return createWatchedSystem([libFile, coreTsConfig, coreIndex, logicTsConfig, logicIndex], { currentDirectory: projectsLocation }); + }, + changes: [ + changeCore(() => `${coreIndex.content} function myFunc() { return 10; }`, "Make non local change and build core"), - buildLogic, - changeCore(() => `${coreIndex.content} + buildLogic, + changeCore(() => `${coreIndex.content} function myFunc() { return 100; }`, "Make local change and build core"), - buildLogic, - ] - }); + buildLogic, + ] }); + }); - describe("when referenced project change introduces error in the down stream project and then fixes it", () => { - const subProjectLibrary = `${projectsLocation}/${project}/Library`; - const libraryTs: File = { - path: `${subProjectLibrary}/library.ts`, - content: ` + describe("when referenced project change introduces error in the down stream project and then fixes it", () => { + const subProjectLibrary = `${projectsLocation}/${project}/Library`; + const libraryTs: File = { + path: `${subProjectLibrary}/library.ts`, + content: ` interface SomeObject { message: string; @@ -347,448 +347,448 @@ export function createSomeObject(): SomeObject message: "new Object" }; }` - }; - verifyTscWatch({ - scenario, - subScenario: "when referenced project change introduces error in the down stream project and then fixes it", - commandLineArgs: ["-b", "-w", "App"], - sys: () => { - const libraryTsconfig: File = { - path: `${subProjectLibrary}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true } }) - }; - const subProjectApp = `${projectsLocation}/${project}/App`; - const appTs: File = { - path: `${subProjectApp}/app.ts`, - content: `import { createSomeObject } from "../Library/library"; + }; + verifyTscWatch({ + scenario, + subScenario: "when referenced project change introduces error in the down stream project and then fixes it", + commandLineArgs: ["-b", "-w", "App"], + sys: () => { + const libraryTsconfig: File = { + path: `${subProjectLibrary}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true } }) + }; + const subProjectApp = `${projectsLocation}/${project}/App`; + const appTs: File = { + path: `${subProjectApp}/app.ts`, + content: `import { createSomeObject } from "../Library/library"; createSomeObject().message;` - }; - const appTsconfig: File = { - path: `${subProjectApp}/tsconfig.json`, - content: JSON.stringify({ references: [{ path: "../Library" }] }) - }; + }; + const appTsconfig: File = { + path: `${subProjectApp}/tsconfig.json`, + content: JSON.stringify({ references: [{ path: "../Library" }] }) + }; - const files = [libFile, libraryTs, libraryTsconfig, appTs, appTsconfig]; - return createWatchedSystem(files, { currentDirectory: `${projectsLocation}/${project}` }); + const files = [libFile, libraryTs, libraryTsconfig, appTs, appTsconfig]; + return createWatchedSystem(files, { currentDirectory: `${projectsLocation}/${project}` }); + }, + changes: [ + { + caption: "Introduce error", + // Change message in library to message2 + change: sys => sys.writeFile(libraryTs.path, libraryTs.content.replace(/message/g, "message2")), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // Build library + sys.checkTimeoutQueueLengthAndRun(1); // Build App + }, }, + { + caption: "Fix error", + // Revert library changes + change: sys => sys.writeFile(libraryTs.path, libraryTs.content), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // Build library + sys.checkTimeoutQueueLengthAndRun(1); // Build App + }, + }, + ] + }); + + }); + + describe("reports errors in all projects on incremental compile", () => { + function verifyIncrementalErrors(subScenario: string, buildOptions: readonly string[]) { + verifyTscWatch({ + scenario, + subScenario: `reportErrors/${subScenario}`, + commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`, ...buildOptions], + sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }), changes: [ { - caption: "Introduce error", - // Change message in library to message2 - change: sys => sys.writeFile(libraryTs.path, libraryTs.content.replace(/message/g, "message2")), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // Build library - sys.checkTimeoutQueueLengthAndRun(1); // Build App - }, + caption: "change logic", + change: sys => sys.writeFile(logic[1].path, `${logic[1].content} +let y: string = 10;`), + // Builds logic + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, }, { - caption: "Fix error", - // Revert library changes - change: sys => sys.writeFile(libraryTs.path, libraryTs.content), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // Build library - sys.checkTimeoutQueueLengthAndRun(1); // Build App - }, - }, + caption: "change core", + change: sys => sys.writeFile(core[1].path, `${core[1].content} +let x: string = 10;`), + // Builds core + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + } ] }); - - }); - - describe("reports errors in all projects on incremental compile", () => { - function verifyIncrementalErrors(subScenario: string, buildOptions: readonly string[]) { - verifyTscWatch({ - scenario, - subScenario: `reportErrors/${subScenario}`, - commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`, ...buildOptions], - sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }), - changes: [ - { - caption: "change logic", - change: sys => sys.writeFile(logic[1].path, `${logic[1].content} -let y: string = 10;`), - // Builds logic - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - }, - { - caption: "change core", - change: sys => sys.writeFile(core[1].path, `${core[1].content} -let x: string = 10;`), - // Builds core - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - } - ] - }); - } - verifyIncrementalErrors("when preserveWatchOutput is not used", emptyArray); - verifyIncrementalErrors("when preserveWatchOutput is passed on command line", ["--preserveWatchOutput"]); - - describe("when declaration emit errors are present", () => { - const solution = "solution"; - const subProject = "app"; - const subProjectLocation = `${projectsLocation}/${solution}/${subProject}`; - const fileWithError: File = { - path: `${subProjectLocation}/fileWithError.ts`, - content: `export var myClassWithError = class { + } + verifyIncrementalErrors("when preserveWatchOutput is not used", emptyArray); + verifyIncrementalErrors("when preserveWatchOutput is passed on command line", ["--preserveWatchOutput"]); + + describe("when declaration emit errors are present", () => { + const solution = "solution"; + const subProject = "app"; + const subProjectLocation = `${projectsLocation}/${solution}/${subProject}`; + const fileWithError: File = { + path: `${subProjectLocation}/fileWithError.ts`, + content: `export var myClassWithError = class { tags() { } private p = 12 };` - }; - const fileWithFixedError: File = { - path: fileWithError.path, - content: fileWithError.content.replace("private p = 12", "") - }; - const fileWithoutError: File = { - path: `${subProjectLocation}/fileWithoutError.ts`, - content: `export class myClass { }` - }; - const tsconfig: File = { - path: `${subProjectLocation}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true } }) - }; + }; + const fileWithFixedError: File = { + path: fileWithError.path, + content: fileWithError.content.replace("private p = 12", "") + }; + const fileWithoutError: File = { + path: `${subProjectLocation}/fileWithoutError.ts`, + content: `export class myClass { }` + }; + const tsconfig: File = { + path: `${subProjectLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true } }) + }; - function incrementalBuild(sys: WatchedSystem) { - sys.checkTimeoutQueueLengthAndRun(1); // Build the app - sys.checkTimeoutQueueLength(0); - } + function incrementalBuild(sys: WatchedSystem) { + sys.checkTimeoutQueueLengthAndRun(1); // Build the app + sys.checkTimeoutQueueLength(0); + } - const fixError: TscWatchCompileChange = { - caption: "Fix error in fileWithError", - // Fix error - change: sys => sys.writeFile(fileWithError.path, fileWithFixedError.content), - timeouts: incrementalBuild - }; + const fixError: TscWatchCompileChange = { + caption: "Fix error in fileWithError", + // Fix error + change: sys => sys.writeFile(fileWithError.path, fileWithFixedError.content), + timeouts: incrementalBuild + }; + + const changeFileWithoutError: TscWatchCompileChange = { + caption: "Change fileWithoutError", + change: sys => sys.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2")), + timeouts: incrementalBuild + }; + + verifyTscWatch({ + scenario, + subScenario: "reportErrors/declarationEmitErrors/when fixing error files all files are emitted", + commandLineArgs: ["-b", "-w", subProject], + sys: () => createWatchedSystem( + [libFile, fileWithError, fileWithoutError, tsconfig], + { currentDirectory: `${projectsLocation}/${solution}` } + ), + changes: [ + fixError + ] + }); - const changeFileWithoutError: TscWatchCompileChange = { - caption: "Change fileWithoutError", - change: sys => sys.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2")), - timeouts: incrementalBuild + verifyTscWatch({ + scenario, + subScenario: "reportErrors/declarationEmitErrors/when file with no error changes", + commandLineArgs: ["-b", "-w", subProject], + sys: () => createWatchedSystem( + [libFile, fileWithError, fileWithoutError, tsconfig], + { currentDirectory: `${projectsLocation}/${solution}` } + ), + changes: [ + changeFileWithoutError + ] + }); + + describe("when reporting errors on introducing error", () => { + const introduceError: TscWatchCompileChange = { + caption: "Introduce error", + change: sys => sys.writeFile(fileWithError.path, fileWithError.content), + timeouts: incrementalBuild, }; verifyTscWatch({ scenario, - subScenario: "reportErrors/declarationEmitErrors/when fixing error files all files are emitted", + subScenario: "reportErrors/declarationEmitErrors/introduceError/when fixing errors only changed file is emitted", commandLineArgs: ["-b", "-w", subProject], sys: () => createWatchedSystem( - [libFile, fileWithError, fileWithoutError, tsconfig], + [libFile, fileWithFixedError, fileWithoutError, tsconfig], { currentDirectory: `${projectsLocation}/${solution}` } ), changes: [ + introduceError, fixError ] }); verifyTscWatch({ scenario, - subScenario: "reportErrors/declarationEmitErrors/when file with no error changes", + subScenario: "reportErrors/declarationEmitErrors/introduceError/when file with no error changes", commandLineArgs: ["-b", "-w", subProject], sys: () => createWatchedSystem( - [libFile, fileWithError, fileWithoutError, tsconfig], + [libFile, fileWithFixedError, fileWithoutError, tsconfig], { currentDirectory: `${projectsLocation}/${solution}` } ), changes: [ + introduceError, changeFileWithoutError ] }); - - describe("when reporting errors on introducing error", () => { - const introduceError: TscWatchCompileChange = { - caption: "Introduce error", - change: sys => sys.writeFile(fileWithError.path, fileWithError.content), - timeouts: incrementalBuild, - }; - - verifyTscWatch({ - scenario, - subScenario: "reportErrors/declarationEmitErrors/introduceError/when fixing errors only changed file is emitted", - commandLineArgs: ["-b", "-w", subProject], - sys: () => createWatchedSystem( - [libFile, fileWithFixedError, fileWithoutError, tsconfig], - { currentDirectory: `${projectsLocation}/${solution}` } - ), - changes: [ - introduceError, - fixError - ] - }); - - verifyTscWatch({ - scenario, - subScenario: "reportErrors/declarationEmitErrors/introduceError/when file with no error changes", - commandLineArgs: ["-b", "-w", subProject], - sys: () => createWatchedSystem( - [libFile, fileWithFixedError, fileWithoutError, tsconfig], - { currentDirectory: `${projectsLocation}/${solution}` } - ), - changes: [ - introduceError, - changeFileWithoutError - ] - }); - }); }); }); + }); - verifyTscWatch({ - scenario, - subScenario: "incremental updates in verbose mode", - commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`, "-verbose"], - sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }), - changes: [ - { - caption: "Make non dts change", - change: sys => sys.writeFile(logic[1].path, `${logic[1].content} + verifyTscWatch({ + scenario, + subScenario: "incremental updates in verbose mode", + commandLineArgs: ["-b", "-w", `${project}/${SubProject.tests}`, "-verbose"], + sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }), + changes: [ + { + caption: "Make non dts change", + change: sys => sys.writeFile(logic[1].path, `${logic[1].content} function someFn() { }`), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // build logic - sys.checkTimeoutQueueLengthAndRun(1); // build tests - }, + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // build logic + sys.checkTimeoutQueueLengthAndRun(1); // build tests }, - { - caption: "Make dts change", - change: sys => sys.writeFile(logic[1].path, `${logic[1].content} + }, + { + caption: "Make dts change", + change: sys => sys.writeFile(logic[1].path, `${logic[1].content} export function someFn() { }`), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // build logic - sys.checkTimeoutQueueLengthAndRun(1); // build tests - }, - } - ], - }); + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // build logic + sys.checkTimeoutQueueLengthAndRun(1); // build tests + }, + } + ], + }); - verifyTscWatch({ - scenario, - subScenario: "works when noUnusedParameters changes to false", - commandLineArgs: ["-b", "-w"], - sys: () => { - const index: File = { - path: `${projectRoot}/index.ts`, - content: `const fn = (a: string, b: string) => b;` - }; - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - noUnusedParameters: true - } - }) - }; - return createWatchedSystem([index, configFile, libFile], { currentDirectory: projectRoot }); + verifyTscWatch({ + scenario, + subScenario: "works when noUnusedParameters changes to false", + commandLineArgs: ["-b", "-w"], + sys: () => { + const index: File = { + path: `${projectRoot}/index.ts`, + content: `const fn = (a: string, b: string) => b;` + }; + const configFile: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + noUnusedParameters: true + } + }) + }; + return createWatchedSystem([index, configFile, libFile], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "Change tsconfig to set noUnusedParameters to false", + change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ + compilerOptions: { + noUnusedParameters: false + } + })), + timeouts: runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Change tsconfig to set noUnusedParameters to false", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ - compilerOptions: { - noUnusedParameters: false - } - })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "should not trigger recompilation because of program emit", - commandLineArgs: ["-b", "-w", `${project}/${SubProject.core}`, "-verbose"], - sys: () => createWatchedSystem([libFile, ...core], { currentDirectory: projectsLocation }), - changes: [ - noopChange, - { - caption: "Add new file", - change: sys => sys.writeFile(`${project}/${SubProject.core}/file3.ts`, `export const y = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - noopChange, - ] - }); + verifyTscWatch({ + scenario, + subScenario: "should not trigger recompilation because of program emit", + commandLineArgs: ["-b", "-w", `${project}/${SubProject.core}`, "-verbose"], + sys: () => createWatchedSystem([libFile, ...core], { currentDirectory: projectsLocation }), + changes: [ + noopChange, + { + caption: "Add new file", + change: sys => sys.writeFile(`${project}/${SubProject.core}/file3.ts`, `export const y = 10;`), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + noopChange, + ] + }); - verifyTscWatch({ - scenario, - subScenario: "should not trigger recompilation because of program emit with outDir specified", - commandLineArgs: ["-b", "-w", `${project}/${SubProject.core}`, "-verbose"], - sys: () => { - const [coreConfig, ...rest] = core; - const newCoreConfig: File = { path: coreConfig.path, content: JSON.stringify({ compilerOptions: { composite: true, outDir: "outDir" } }) }; - return createWatchedSystem([libFile, newCoreConfig, ...rest], { currentDirectory: projectsLocation }); + verifyTscWatch({ + scenario, + subScenario: "should not trigger recompilation because of program emit with outDir specified", + commandLineArgs: ["-b", "-w", `${project}/${SubProject.core}`, "-verbose"], + sys: () => { + const [coreConfig, ...rest] = core; + const newCoreConfig: File = { path: coreConfig.path, content: JSON.stringify({ compilerOptions: { composite: true, outDir: "outDir" } }) }; + return createWatchedSystem([libFile, newCoreConfig, ...rest], { currentDirectory: projectsLocation }); + }, + changes: [ + noopChange, + { + caption: "Add new file", + change: sys => sys.writeFile(`${project}/${SubProject.core}/file3.ts`, `export const y = 10;`), + timeouts: checkSingleTimeoutQueueLengthAndRun }, - changes: [ - noopChange, - { - caption: "Add new file", - change: sys => sys.writeFile(`${project}/${SubProject.core}/file3.ts`, `export const y = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - noopChange - ] - }); + noopChange + ] + }); - verifyTscWatch({ - scenario, - subScenario: "works with extended source files", - commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json"], - sys: () => { - const alphaExtendedConfigFile: File = { - path: "/a/b/alpha.tsconfig.json", - content: "{}" - }; - const project1Config: File = { - path: "/a/b/project1.tsconfig.json", - content: JSON.stringify({ - extends: "./alpha.tsconfig.json", - compilerOptions: { - composite: true, - }, - files: [commonFile1.path, commonFile2.path] - }) - }; - const bravoExtendedConfigFile: File = { - path: "/a/b/bravo.tsconfig.json", - content: JSON.stringify({ - extends: "./alpha.tsconfig.json" - }) - }; - const otherFile: File = { - path: "/a/b/other.ts", - content: "let z = 0;", - }; - const project2Config: File = { - path: "/a/b/project2.tsconfig.json", - content: JSON.stringify({ - extends: "./bravo.tsconfig.json", - compilerOptions: { - composite: true, - }, - files: [otherFile.path] - }) - }; - return createWatchedSystem([ - libFile, - alphaExtendedConfigFile, project1Config, commonFile1, commonFile2, - bravoExtendedConfigFile, project2Config, otherFile - ], { currentDirectory: "/a/b" }); + verifyTscWatch({ + scenario, + subScenario: "works with extended source files", + commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json"], + sys: () => { + const alphaExtendedConfigFile: File = { + path: "/a/b/alpha.tsconfig.json", + content: "{}" + }; + const project1Config: File = { + path: "/a/b/project1.tsconfig.json", + content: JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { + composite: true, + }, + files: [commonFile1.path, commonFile2.path] + }) + }; + const bravoExtendedConfigFile: File = { + path: "/a/b/bravo.tsconfig.json", + content: JSON.stringify({ + extends: "./alpha.tsconfig.json" + }) + }; + const otherFile: File = { + path: "/a/b/other.ts", + content: "let z = 0;", + }; + const project2Config: File = { + path: "/a/b/project2.tsconfig.json", + content: JSON.stringify({ + extends: "./bravo.tsconfig.json", + compilerOptions: { + composite: true, + }, + files: [otherFile.path] + }) + }; + return createWatchedSystem([ + libFile, + alphaExtendedConfigFile, project1Config, commonFile1, commonFile2, + bravoExtendedConfigFile, project2Config, otherFile + ], { currentDirectory: "/a/b" }); + }, + changes: [ + { + caption: "Modify alpha config", + change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", JSON.stringify({ + compilerOptions: { strict: true } + })), + timeouts: checkSingleTimeoutQueueLengthAndRun // Build project1 }, - changes: [ - { - caption: "Modify alpha config", - change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", JSON.stringify({ - compilerOptions: { strict: true } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun // Build project1 - }, - { - caption: "Build project 2", - change: noop, - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 - }, - { - caption: "change bravo config", - change: sys => sys.writeFile("/a/b/bravo.tsconfig.json", JSON.stringify({ - extends: "./alpha.tsconfig.json", - compilerOptions: { strict: false } - })), - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 - }, - { - caption: "project 2 extends alpha", - change: sys => sys.writeFile("/a/b/project2.tsconfig.json", JSON.stringify({ - extends: "./alpha.tsconfig.json", - })), - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 - }, - { - caption: "update aplha config", - change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", "{}"), - timeouts: checkSingleTimeoutQueueLengthAndRun, // build project1 - }, - { - caption: "Build project 2", - change: noop, - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 - }, - ] - }); + { + caption: "Build project 2", + change: noop, + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 + }, + { + caption: "change bravo config", + change: sys => sys.writeFile("/a/b/bravo.tsconfig.json", JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { strict: false } + })), + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 + }, + { + caption: "project 2 extends alpha", + change: sys => sys.writeFile("/a/b/project2.tsconfig.json", JSON.stringify({ + extends: "./alpha.tsconfig.json", + })), + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 + }, + { + caption: "update aplha config", + change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", "{}"), + timeouts: checkSingleTimeoutQueueLengthAndRun, // build project1 + }, + { + caption: "Build project 2", + change: noop, + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 + }, + ] + }); - verifyTscWatch({ - scenario, - subScenario: "works correctly when project with extended config is removed", - commandLineArgs: ["-b", "-w", "-v"], - sys: () => { - const alphaExtendedConfigFile: File = { - path: "/a/b/alpha.tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - strict: true - } - }) - }; - const project1Config: File = { - path: "/a/b/project1.tsconfig.json", - content: JSON.stringify({ - extends: "./alpha.tsconfig.json", - compilerOptions: { - composite: true, + verifyTscWatch({ + scenario, + subScenario: "works correctly when project with extended config is removed", + commandLineArgs: ["-b", "-w", "-v"], + sys: () => { + const alphaExtendedConfigFile: File = { + path: "/a/b/alpha.tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + strict: true + } + }) + }; + const project1Config: File = { + path: "/a/b/project1.tsconfig.json", + content: JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { + composite: true, + }, + files: [commonFile1.path, commonFile2.path] + }) + }; + const bravoExtendedConfigFile: File = { + path: "/a/b/bravo.tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + strict: true + } + }) + }; + const otherFile: File = { + path: "/a/b/other.ts", + content: "let z = 0;", + }; + const project2Config: File = { + path: "/a/b/project2.tsconfig.json", + content: JSON.stringify({ + extends: "./bravo.tsconfig.json", + compilerOptions: { + composite: true, + }, + files: [otherFile.path] + }) + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + references: [ + { + path: "./project1.tsconfig.json", }, - files: [commonFile1.path, commonFile2.path] - }) - }; - const bravoExtendedConfigFile: File = { - path: "/a/b/bravo.tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - strict: true - } - }) - }; - const otherFile: File = { - path: "/a/b/other.ts", - content: "let z = 0;", - }; - const project2Config: File = { - path: "/a/b/project2.tsconfig.json", - content: JSON.stringify({ - extends: "./bravo.tsconfig.json", - compilerOptions: { - composite: true, + { + path: "./project2.tsconfig.json", }, - files: [otherFile.path] - }) - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - references: [ - { - path: "./project1.tsconfig.json", - }, - { - path: "./project2.tsconfig.json", - }, - ], - files: [], - }) - }; - return createWatchedSystem([ - libFile, configFile, - alphaExtendedConfigFile, project1Config, commonFile1, commonFile2, - bravoExtendedConfigFile, project2Config, otherFile - ], { currentDirectory: "/a/b" }); - }, - changes: [ - { - caption: "Remove project2 from base config", - change: sys => sys.modifyFile("/a/b/tsconfig.json", JSON.stringify({ - references: [ - { - path: "./project1.tsconfig.json", - }, - ], - files: [], - })), - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - } - ] - }); + ], + files: [], + }) + }; + return createWatchedSystem([ + libFile, configFile, + alphaExtendedConfigFile, project1Config, commonFile1, commonFile2, + bravoExtendedConfigFile, project2Config, otherFile + ], { currentDirectory: "/a/b" }); + }, + changes: [ + { + caption: "Remove project2 from base config", + change: sys => sys.modifyFile("/a/b/tsconfig.json", JSON.stringify({ + references: [ + { + path: "./project1.tsconfig.json", + }, + ], + files: [], + })), + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + } + ] }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/tsbuildWatch/publicApi.ts b/src/testRunner/unittests/tsbuildWatch/publicApi.ts index bca1472e0d893..861bf023684b1 100644 --- a/src/testRunner/unittests/tsbuildWatch/publicApi.ts +++ b/src/testRunner/unittests/tsbuildWatch/publicApi.ts @@ -1,115 +1,115 @@ namespace ts.tscWatch { - it("unittests:: tsbuildWatch:: watchMode:: Public API with custom transformers", () => { - const solution: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - references: [ - { path: "./shared/tsconfig.json" }, - { path: "./webpack/tsconfig.json" } - ], - files: [] - }) - }; - const sharedConfig: File = { - path: `${projectRoot}/shared/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true }, - }) - }; - const sharedIndex: File = { - path: `${projectRoot}/shared/index.ts`, - content: `export function f1() { } +it("unittests:: tsbuildWatch:: watchMode:: Public API with custom transformers", () => { + const solution: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + references: [ + { path: "./shared/tsconfig.json" }, + { path: "./webpack/tsconfig.json" } + ], + files: [] + }) + }; + const sharedConfig: File = { + path: `${projectRoot}/shared/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true }, + }) + }; + const sharedIndex: File = { + path: `${projectRoot}/shared/index.ts`, + content: `export function f1() { } export class c { } export enum e { } // leading export function f2() { } // trailing` - }; - const webpackConfig: File = { - path: `${projectRoot}/webpack/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, }, - references: [{ path: "../shared/tsconfig.json" }] - }) - }; - const webpackIndex: File = { - path: `${projectRoot}/webpack/index.ts`, - content: `export function f2() { } + }; + const webpackConfig: File = { + path: `${projectRoot}/webpack/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, }, + references: [{ path: "../shared/tsconfig.json" }] + }) + }; + const webpackIndex: File = { + path: `${projectRoot}/webpack/index.ts`, + content: `export function f2() { } export class c2 { } export enum e2 { } // leading export function f22() { } // trailing` - }; - const commandLineArgs = ["--b", "--w"]; - const { sys, baseline, oldSnap } = createBaseline(createWatchedSystem([libFile, solution, sharedConfig, sharedIndex, webpackConfig, webpackIndex], { currentDirectory: projectRoot })); - const { cb, getPrograms } = commandLineCallbacks(sys); - const buildHost = createSolutionBuilderWithWatchHost( - sys, - /*createProgram*/ undefined, - createDiagnosticReporter(sys, /*pretty*/ true), - createBuilderStatusReporter(sys, /*pretty*/ true), - createWatchStatusReporter(sys, /*pretty*/ true) - ); - buildHost.afterProgramEmitAndDiagnostics = cb; - buildHost.afterEmitBundle = cb; - buildHost.getCustomTransformers = getCustomTransformers; - const builder = createSolutionBuilderWithWatch(buildHost, [solution.path], { verbose: true }); - builder.build(); - runWatchBaseline({ - scenario: "publicApi", - subScenario: "with custom transformers", - commandLineArgs, - sys, - baseline, - oldSnap, - getPrograms, - changes: [ - { - caption: "change to shared", - change: sys => sys.prependFile(sharedIndex.path, "export function fooBar() {}"), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // Shared - sys.checkTimeoutQueueLengthAndRun(1); // webpack - sys.checkTimeoutQueueLengthAndRun(1); // solution - sys.checkTimeoutQueueLength(0); - } + }; + const commandLineArgs = ["--b", "--w"]; + const { sys, baseline, oldSnap } = createBaseline(createWatchedSystem([libFile, solution, sharedConfig, sharedIndex, webpackConfig, webpackIndex], { currentDirectory: projectRoot })); + const { cb, getPrograms } = commandLineCallbacks(sys); + const buildHost = createSolutionBuilderWithWatchHost( + sys, + /*createProgram*/ undefined, + createDiagnosticReporter(sys, /*pretty*/ true), + createBuilderStatusReporter(sys, /*pretty*/ true), + createWatchStatusReporter(sys, /*pretty*/ true) + ); + buildHost.afterProgramEmitAndDiagnostics = cb; + buildHost.afterEmitBundle = cb; + buildHost.getCustomTransformers = getCustomTransformers; + const builder = createSolutionBuilderWithWatch(buildHost, [solution.path], { verbose: true }); + builder.build(); + runWatchBaseline({ + scenario: "publicApi", + subScenario: "with custom transformers", + commandLineArgs, + sys, + baseline, + oldSnap, + getPrograms, + changes: [ + { + caption: "change to shared", + change: sys => sys.prependFile(sharedIndex.path, "export function fooBar() {}"), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // Shared + sys.checkTimeoutQueueLengthAndRun(1); // webpack + sys.checkTimeoutQueueLengthAndRun(1); // solution + sys.checkTimeoutQueueLength(0); } - ], - watchOrSolution: builder - }); + } + ], + watchOrSolution: builder + }); - function getCustomTransformers(project: string): CustomTransformers { - const before: TransformerFactory = context => { - return file => visitEachChild(file, visit, context); - function visit(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - return visitFunction(node as FunctionDeclaration); - default: - return visitEachChild(node, visit, context); - } - } - function visitFunction(node: FunctionDeclaration) { - addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, `@before${project}`, /*hasTrailingNewLine*/ true); - return node; + function getCustomTransformers(project: string): CustomTransformers { + const before: TransformerFactory = context => { + return file => visitEachChild(file, visit, context); + function visit(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return visitFunction(node as FunctionDeclaration); + default: + return visitEachChild(node, visit, context); } - }; + } + function visitFunction(node: FunctionDeclaration) { + addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, `@before${project}`, /*hasTrailingNewLine*/ true); + return node; + } + }; - const after: TransformerFactory = context => { - return file => visitEachChild(file, visit, context); - function visit(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.VariableStatement: - return visitVariableStatement(node as VariableStatement); - default: - return visitEachChild(node, visit, context); - } + const after: TransformerFactory = context => { + return file => visitEachChild(file, visit, context); + function visit(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.VariableStatement: + return visitVariableStatement(node as VariableStatement); + default: + return visitEachChild(node, visit, context); } - function visitVariableStatement(node: VariableStatement) { - addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, `@after${project}`); - return node; - } - }; - return { before: [before], after: [after] }; - } - }); -} \ No newline at end of file + } + function visitVariableStatement(node: VariableStatement) { + addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, `@after${project}`); + return node; + } + }; + return { before: [before], after: [after] }; + } +}); +} diff --git a/src/testRunner/unittests/tsbuildWatch/reexport.ts b/src/testRunner/unittests/tsbuildWatch/reexport.ts index d105bd0fcf0ba..db93b5ba0c06f 100644 --- a/src/testRunner/unittests/tsbuildWatch/reexport.ts +++ b/src/testRunner/unittests/tsbuildWatch/reexport.ts @@ -1,39 +1,39 @@ namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: with reexport when referenced project reexports definitions from another file", () => { - function build(sys: WatchedSystem) { - sys.checkTimeoutQueueLengthAndRun(1); // build src/pure - sys.checkTimeoutQueueLengthAndRun(1); // build src/main - sys.checkTimeoutQueueLengthAndRun(1); // build src - sys.checkTimeoutQueueLength(0); - } - verifyTscWatch({ - scenario: "reexport", - subScenario: "Reports errors correctly", - commandLineArgs: ["-b", "-w", "-verbose", "src"], - sys: () => createWatchedSystem( - [ - ...[ - "src/tsconfig.json", - "src/main/tsconfig.json", "src/main/index.ts", - "src/pure/tsconfig.json", "src/pure/index.ts", "src/pure/session.ts" - ] - .map(f => TestFSWithWatch.getTsBuildProjectFile("reexport", f)), - { path: libFile.path, content: libContent } - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/reexport` } - ), - changes: [ - { - caption: "Introduce error", - change: sys => replaceFileText(sys, `${TestFSWithWatch.tsbuildProjectsLocation}/reexport/src/pure/session.ts`, "// ", ""), - timeouts: build, - }, - { - caption: "Fix error", - change: sys => replaceFileText(sys, `${TestFSWithWatch.tsbuildProjectsLocation}/reexport/src/pure/session.ts`, "bar: ", "// bar: "), - timeouts: build - } - ] - }); +describe("unittests:: tsbuildWatch:: watchMode:: with reexport when referenced project reexports definitions from another file", () => { + function build(sys: WatchedSystem) { + sys.checkTimeoutQueueLengthAndRun(1); // build src/pure + sys.checkTimeoutQueueLengthAndRun(1); // build src/main + sys.checkTimeoutQueueLengthAndRun(1); // build src + sys.checkTimeoutQueueLength(0); + } + verifyTscWatch({ + scenario: "reexport", + subScenario: "Reports errors correctly", + commandLineArgs: ["-b", "-w", "-verbose", "src"], + sys: () => createWatchedSystem( + [ + ...[ + "src/tsconfig.json", + "src/main/tsconfig.json", "src/main/index.ts", + "src/pure/tsconfig.json", "src/pure/index.ts", "src/pure/session.ts" + ] + .map(f => TestFSWithWatch.getTsBuildProjectFile("reexport", f)), + { path: libFile.path, content: libContent } + ], + { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/reexport` } + ), + changes: [ + { + caption: "Introduce error", + change: sys => replaceFileText(sys, `${TestFSWithWatch.tsbuildProjectsLocation}/reexport/src/pure/session.ts`, "// ", ""), + timeouts: build, + }, + { + caption: "Fix error", + change: sys => replaceFileText(sys, `${TestFSWithWatch.tsbuildProjectsLocation}/reexport/src/pure/session.ts`, "bar: ", "// bar: "), + timeouts: build + } + ] }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/tsbuildWatch/watchEnvironment.ts b/src/testRunner/unittests/tsbuildWatch/watchEnvironment.ts index 599b5790f85b9..1174210436305 100644 --- a/src/testRunner/unittests/tsbuildWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tsbuildWatch/watchEnvironment.ts @@ -1,124 +1,124 @@ namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: with different watch environments", () => { - describe("when watchFile can create multiple watchers per file", () => { - verifyWatchFileOnMultipleProjects(/*singleWatchPerFile*/ false); - }); +describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: with different watch environments", () => { + describe("when watchFile can create multiple watchers per file", () => { + verifyWatchFileOnMultipleProjects(/*singleWatchPerFile*/ false); + }); - describe("when watchFile is single watcher per file", () => { - verifyWatchFileOnMultipleProjects( - /*singleWatchPerFile*/ true, - arrayToMap(["TSC_WATCHFILE"], identity, () => TestFSWithWatch.Tsc_WatchFile.SingleFileWatcherPerName) - ); - }); + describe("when watchFile is single watcher per file", () => { + verifyWatchFileOnMultipleProjects( + /*singleWatchPerFile*/ true, + arrayToMap(["TSC_WATCHFILE"], identity, () => TestFSWithWatch.Tsc_WatchFile.SingleFileWatcherPerName) + ); + }); - function verifyWatchFileOnMultipleProjects(singleWatchPerFile: boolean, environmentVariables?: ESMap) { - it("watchFile on same file multiple times because file is part of multiple projects", () => { - const project = `${TestFSWithWatch.tsbuildProjectsLocation}/myproject`; - let maxPkgs = 4; - const configPath = `${project}/tsconfig.json`; - const typing: File = { - path: `${project}/typings/xterm.d.ts`, - content: "export const typing = 10;" - }; + function verifyWatchFileOnMultipleProjects(singleWatchPerFile: boolean, environmentVariables?: ESMap) { + it("watchFile on same file multiple times because file is part of multiple projects", () => { + const project = `${TestFSWithWatch.tsbuildProjectsLocation}/myproject`; + let maxPkgs = 4; + const configPath = `${project}/tsconfig.json`; + const typing: File = { + path: `${project}/typings/xterm.d.ts`, + content: "export const typing = 10;" + }; - const allPkgFiles = pkgs(pkgFiles); - const system = createWatchedSystem([libFile, typing, ...flatArray(allPkgFiles)], { currentDirectory: project, environmentVariables }); - writePkgReferences(); - const host = createSolutionBuilderWithWatchHost(system); - const solutionBuilder = createSolutionBuilderWithWatch(host, ["tsconfig.json"], { watch: true, verbose: true }); - solutionBuilder.build(); - checkOutputErrorsInitial(system, emptyArray, /*disableConsoleClears*/ undefined, [ - `Projects in this build: \r\n${ - concatenate( - pkgs(index => ` * pkg${index}/tsconfig.json`), - [" * tsconfig.json"] - ).join("\r\n")}\n\n`, - ...flatArray(pkgs(index => [ - `Project 'pkg${index}/tsconfig.json' is out of date because output file 'pkg${index}/index.js' does not exist\n\n`, - `Building project '${project}/pkg${index}/tsconfig.json'...\n\n` - ])) - ]); + const allPkgFiles = pkgs(pkgFiles); + const system = createWatchedSystem([libFile, typing, ...flatArray(allPkgFiles)], { currentDirectory: project, environmentVariables }); + writePkgReferences(); + const host = createSolutionBuilderWithWatchHost(system); + const solutionBuilder = createSolutionBuilderWithWatch(host, ["tsconfig.json"], { watch: true, verbose: true }); + solutionBuilder.build(); + checkOutputErrorsInitial(system, emptyArray, /*disableConsoleClears*/ undefined, [ + `Projects in this build: \r\n${ + concatenate( + pkgs(index => ` * pkg${index}/tsconfig.json`), + [" * tsconfig.json"] + ).join("\r\n")}\n\n`, + ...flatArray(pkgs(index => [ + `Project 'pkg${index}/tsconfig.json' is out of date because output file 'pkg${index}/index.js' does not exist\n\n`, + `Building project '${project}/pkg${index}/tsconfig.json'...\n\n` + ])) + ]); - const watchFilesDetailed = arrayToMap(flatArray(allPkgFiles), f => f.path, () => 1); - watchFilesDetailed.set(configPath, 1); - watchFilesDetailed.set(typing.path, singleWatchPerFile ? 1 : maxPkgs); - checkWatchedFilesDetailed(system, watchFilesDetailed); - system.writeFile(typing.path, `${typing.content}export const typing1 = 10;`); - verifyInvoke(); + const watchFilesDetailed = arrayToMap(flatArray(allPkgFiles), f => f.path, () => 1); + watchFilesDetailed.set(configPath, 1); + watchFilesDetailed.set(typing.path, singleWatchPerFile ? 1 : maxPkgs); + checkWatchedFilesDetailed(system, watchFilesDetailed); + system.writeFile(typing.path, `${typing.content}export const typing1 = 10;`); + verifyInvoke(); - // Make change - maxPkgs--; - writePkgReferences(); - system.checkTimeoutQueueLengthAndRun(1); - checkOutputErrorsIncremental(system, emptyArray); - const lastFiles = last(allPkgFiles); - lastFiles.forEach(f => watchFilesDetailed.delete(f.path)); - watchFilesDetailed.set(typing.path, singleWatchPerFile ? 1 : maxPkgs); - checkWatchedFilesDetailed(system, watchFilesDetailed); - system.writeFile(typing.path, typing.content); - verifyInvoke(); + // Make change + maxPkgs--; + writePkgReferences(); + system.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(system, emptyArray); + const lastFiles = last(allPkgFiles); + lastFiles.forEach(f => watchFilesDetailed.delete(f.path)); + watchFilesDetailed.set(typing.path, singleWatchPerFile ? 1 : maxPkgs); + checkWatchedFilesDetailed(system, watchFilesDetailed); + system.writeFile(typing.path, typing.content); + verifyInvoke(); - // Make change to remove all the watches - maxPkgs = 0; - writePkgReferences(); - system.checkTimeoutQueueLengthAndRun(1); - checkOutputErrorsIncremental(system, [ - `tsconfig.json(1,10): error TS18002: The 'files' list in config file '${configPath}' is empty.\n` - ]); - checkWatchedFilesDetailed(system, [configPath], 1); + // Make change to remove all the watches + maxPkgs = 0; + writePkgReferences(); + system.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(system, [ + `tsconfig.json(1,10): error TS18002: The 'files' list in config file '${configPath}' is empty.\n` + ]); + checkWatchedFilesDetailed(system, [configPath], 1); - system.writeFile(typing.path, `${typing.content}export const typing1 = 10;`); - system.checkTimeoutQueueLength(0); + system.writeFile(typing.path, `${typing.content}export const typing1 = 10;`); + system.checkTimeoutQueueLength(0); - function flatArray(arr: T[][]): readonly T[] { - return flatMap(arr, identity); + function flatArray(arr: T[][]): readonly T[] { + return flatMap(arr, identity); + } + function pkgs(cb: (index: number) => T): T[] { + const result: T[] = []; + for (let index = 0; index < maxPkgs; index++) { + result.push(cb(index)); } - function pkgs(cb: (index: number) => T): T[] { - const result: T[] = []; - for (let index = 0; index < maxPkgs; index++) { - result.push(cb(index)); + return result; + } + function createPkgReference(index: number) { + return { path: `./pkg${index}` }; + } + function pkgFiles(index: number): File[] { + return [ + { + path: `${project}/pkg${index}/index.ts`, + content: `export const pkg${index} = ${index};` + }, + { + path: `${project}/pkg${index}/tsconfig.json`, + content: JSON.stringify({ + complerOptions: { composite: true }, + include: [ + "**/*.ts", + "../typings/xterm.d.ts" + ] + }) } - return result; - } - function createPkgReference(index: number) { - return { path: `./pkg${index}` }; - } - function pkgFiles(index: number): File[] { - return [ - { - path: `${project}/pkg${index}/index.ts`, - content: `export const pkg${index} = ${index};` - }, - { - path: `${project}/pkg${index}/tsconfig.json`, - content: JSON.stringify({ - complerOptions: { composite: true }, - include: [ - "**/*.ts", - "../typings/xterm.d.ts" - ] - }) - } - ]; - } - function writePkgReferences() { - system.writeFile(configPath, JSON.stringify({ - files: [], - include: [], - references: pkgs(createPkgReference) - })); - } - function verifyInvoke() { - pkgs(() => system.checkTimeoutQueueLengthAndRun(1)); - checkOutputErrorsIncremental(system, emptyArray, /*disableConsoleClears*/ undefined, /*logsBeforeWatchDiagnostics*/ undefined, [ - ...flatArray(pkgs(index => [ - `Project 'pkg${index}/tsconfig.json' is out of date because oldest output 'pkg${index}/index.js' is older than newest input 'typings/xterm.d.ts'\n\n`, - `Building project '${project}/pkg${index}/tsconfig.json'...\n\n`, - `Updating unchanged output timestamps of project '${project}/pkg${index}/tsconfig.json'...\n\n` - ])) - ]); - } - }); - } - }); + ]; + } + function writePkgReferences() { + system.writeFile(configPath, JSON.stringify({ + files: [], + include: [], + references: pkgs(createPkgReference) + })); + } + function verifyInvoke() { + pkgs(() => system.checkTimeoutQueueLengthAndRun(1)); + checkOutputErrorsIncremental(system, emptyArray, /*disableConsoleClears*/ undefined, /*logsBeforeWatchDiagnostics*/ undefined, [ + ...flatArray(pkgs(index => [ + `Project 'pkg${index}/tsconfig.json' is out of date because oldest output 'pkg${index}/index.js' is older than newest input 'typings/xterm.d.ts'\n\n`, + `Building project '${project}/pkg${index}/tsconfig.json'...\n\n`, + `Updating unchanged output timestamps of project '${project}/pkg${index}/tsconfig.json'...\n\n` + ])) + ]); + } + }); + } +}); } diff --git a/src/testRunner/unittests/tsc/composite.ts b/src/testRunner/unittests/tsc/composite.ts index 8eb0ea3bd8595..e031cf134e5fd 100644 --- a/src/testRunner/unittests/tsc/composite.ts +++ b/src/testRunner/unittests/tsc/composite.ts @@ -1,11 +1,11 @@ namespace ts { - describe("unittests:: tsc:: composite::", () => { - verifyTsc({ - scenario: "composite", - subScenario: "when setting composite false on command line", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` +describe("unittests:: tsc:: composite::", () => { + verifyTsc({ + scenario: "composite", + subScenario: "when setting composite false on command line", + fs: () => loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "es5", @@ -16,16 +16,16 @@ namespace ts { "src/**/*.ts" ] }`, - }), - commandLineArgs: ["--composite", "false", "--p", "src/project"], - }); + }), + commandLineArgs: ["--composite", "false", "--p", "src/project"], + }); - verifyTsc({ - scenario: "composite", - subScenario: "when setting composite null on command line", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` + verifyTsc({ + scenario: "composite", + subScenario: "when setting composite null on command line", + fs: () => loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "es5", @@ -36,16 +36,16 @@ namespace ts { "src/**/*.ts" ] }`, - }), - commandLineArgs: ["--composite", "null", "--p", "src/project"], - }); + }), + commandLineArgs: ["--composite", "null", "--p", "src/project"], + }); - verifyTsc({ - scenario: "composite", - subScenario: "when setting composite false on command line but has tsbuild info in config", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` + verifyTsc({ + scenario: "composite", + subScenario: "when setting composite false on command line but has tsbuild info in config", + fs: () => loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "es5", @@ -57,16 +57,16 @@ namespace ts { "src/**/*.ts" ] }`, - }), - commandLineArgs: ["--composite", "false", "--p", "src/project"], - }); + }), + commandLineArgs: ["--composite", "false", "--p", "src/project"], + }); - verifyTsc({ - scenario: "composite", - subScenario: "when setting composite false and tsbuildinfo as null on command line but has tsbuild info in config", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` + verifyTsc({ + scenario: "composite", + subScenario: "when setting composite false and tsbuildinfo as null on command line but has tsbuild info in config", + fs: () => loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "es5", @@ -78,8 +78,8 @@ namespace ts { "src/**/*.ts" ] }`, - }), - commandLineArgs: ["--composite", "false", "--p", "src/project", "--tsBuildInfoFile", "null"], - }); + }), + commandLineArgs: ["--composite", "false", "--p", "src/project", "--tsBuildInfoFile", "null"], }); +}); } diff --git a/src/testRunner/unittests/tsc/declarationEmit.ts b/src/testRunner/unittests/tsc/declarationEmit.ts index f345d4a3c8f61..b3292869d3932 100644 --- a/src/testRunner/unittests/tsc/declarationEmit.ts +++ b/src/testRunner/unittests/tsc/declarationEmit.ts @@ -1,66 +1,66 @@ namespace ts { - describe("unittests:: tsc:: declarationEmit::", () => { - interface VerifyDeclarationEmitInput { - subScenario: string; - files: TestFSWithWatch.FileOrFolderOrSymLink[]; - rootProject: string; - changeCaseFileTestPath: (path: string) => boolean; - } +describe("unittests:: tsc:: declarationEmit::", () => { + interface VerifyDeclarationEmitInput { + subScenario: string; + files: TestFSWithWatch.FileOrFolderOrSymLink[]; + rootProject: string; + changeCaseFileTestPath: (path: string) => boolean; + } - function changeCaseFile(file: TestFSWithWatch.FileOrFolderOrSymLink, testPath: (path: string) => boolean, replacePath: (path: string) => string): TestFSWithWatch.FileOrFolderOrSymLink { - return !TestFSWithWatch.isSymLink(file) || !testPath(file.symLink) ? - testPath(file.path) ? { ...file, path: replacePath(file.path) } : file : - { path: testPath(file.path) ? replacePath(file.path) : file.path, symLink: replacePath(file.symLink) }; - } + function changeCaseFile(file: TestFSWithWatch.FileOrFolderOrSymLink, testPath: (path: string) => boolean, replacePath: (path: string) => string): TestFSWithWatch.FileOrFolderOrSymLink { + return !TestFSWithWatch.isSymLink(file) || !testPath(file.symLink) ? + testPath(file.path) ? { ...file, path: replacePath(file.path) } : file : + { path: testPath(file.path) ? replacePath(file.path) : file.path, symLink: replacePath(file.symLink) }; + } - function verifyDeclarationEmit({ subScenario, files, rootProject, changeCaseFileTestPath }: VerifyDeclarationEmitInput) { - describe(subScenario, () => { - tscWatch.verifyTscWatch({ - scenario: "declarationEmit", - subScenario, - sys: () => tscWatch.createWatchedSystem(files, { currentDirectory: tscWatch.projectRoot }), - commandLineArgs: ["-p", rootProject, "--explainFiles"], - changes: emptyArray - }); + function verifyDeclarationEmit({ subScenario, files, rootProject, changeCaseFileTestPath }: VerifyDeclarationEmitInput) { + describe(subScenario, () => { + tscWatch.verifyTscWatch({ + scenario: "declarationEmit", + subScenario, + sys: () => tscWatch.createWatchedSystem(files, { currentDirectory: tscWatch.projectRoot }), + commandLineArgs: ["-p", rootProject, "--explainFiles"], + changes: emptyArray }); + }); - const caseChangeScenario = `${subScenario} moduleCaseChange`; - describe(caseChangeScenario, () => { - tscWatch.verifyTscWatch({ - scenario: "declarationEmit", - subScenario: caseChangeScenario, - sys: () => tscWatch.createWatchedSystem( - files.map(f => changeCaseFile(f, changeCaseFileTestPath, str => str.replace("myproject", "myProject"))), - { currentDirectory: tscWatch.projectRoot } - ), - commandLineArgs: ["-p", rootProject, "--explainFiles"], - changes: emptyArray - }); + const caseChangeScenario = `${subScenario} moduleCaseChange`; + describe(caseChangeScenario, () => { + tscWatch.verifyTscWatch({ + scenario: "declarationEmit", + subScenario: caseChangeScenario, + sys: () => tscWatch.createWatchedSystem( + files.map(f => changeCaseFile(f, changeCaseFileTestPath, str => str.replace("myproject", "myProject"))), + { currentDirectory: tscWatch.projectRoot } + ), + commandLineArgs: ["-p", rootProject, "--explainFiles"], + changes: emptyArray }); - } + }); + } - describe("with symlinks in sibling folders and common package referenced from both folders", () => { - function pluginOneConfig() { - return JSON.stringify({ - compilerOptions: { - target: "es5", - declaration: true, - traceResolution: true - }, - }); - } - function pluginOneIndex() { - return `import pluginTwo from "plugin-two"; // include this to add reference to symlink`; - } - function pluginOneAction() { - return Utils.dedent` + describe("with symlinks in sibling folders and common package referenced from both folders", () => { + function pluginOneConfig() { + return JSON.stringify({ + compilerOptions: { + target: "es5", + declaration: true, + traceResolution: true + }, + }); + } + function pluginOneIndex() { + return `import pluginTwo from "plugin-two"; // include this to add reference to symlink`; + } + function pluginOneAction() { + return Utils.dedent` import { actionCreatorFactory } from "typescript-fsa"; // Include version of shared lib const action = actionCreatorFactory("somekey"); const featureOne = action<{ route: string }>("feature-one"); export const actions = { featureOne };`; - } - function pluginTwoDts() { - return Utils.dedent` + } + function pluginTwoDts() { + return Utils.dedent` declare const _default: { features: { featureOne: { @@ -82,15 +82,15 @@ namespace ts { }; }; export default _default;`; - } - function fsaPackageJson() { - return JSON.stringify({ - name: "typescript-fsa", - version: "3.0.0-beta-2" - }); - } - function fsaIndex() { - return Utils.dedent` + } + function fsaPackageJson() { + return JSON.stringify({ + name: "typescript-fsa", + version: "3.0.0-beta-2" + }); + } + function fsaIndex() { + return Utils.dedent` export interface Action { type: string; payload: Payload; @@ -104,69 +104,69 @@ namespace ts { } export declare function actionCreatorFactory(prefix?: string | null): ActionCreatorFactory; export default actionCreatorFactory;`; - } - - verifyDeclarationEmit({ - subScenario: "when same version is referenced through source and another symlinked package", - rootProject: "plugin-one", - files: [ - { path: `${tscWatch.projectRoot}/plugin-two/index.d.ts`, content: pluginTwoDts() }, - { path: `${tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, - { path: `${tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, - { path: `${tscWatch.projectRoot}/plugin-one/tsconfig.json`, content: pluginOneConfig() }, - { path: `${tscWatch.projectRoot}/plugin-one/index.ts`, content: pluginOneIndex() }, - { path: `${tscWatch.projectRoot}/plugin-one/action.ts`, content: pluginOneAction() }, - { path: `${tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, - { path: `${tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, - { path: `${tscWatch.projectRoot}/plugin-one/node_modules/plugin-two`, symLink: `${tscWatch.projectRoot}/plugin-two` }, - tscWatch.libFile - ], - changeCaseFileTestPath: str => stringContains(str, "/plugin-two"), - }); + } - verifyDeclarationEmit({ - subScenario: "when same version is referenced through source and another symlinked package with indirect link", - rootProject: "plugin-one", - files: [ - { - path: `${tscWatch.projectRoot}/plugin-two/package.json`, - content: JSON.stringify({ - name: "plugin-two", - version: "0.1.3", - main: "dist/commonjs/index.js" - }) - }, - { path: `${tscWatch.projectRoot}/plugin-two/dist/commonjs/index.d.ts`, content: pluginTwoDts() }, - { path: `${tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, - { path: `${tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, - { path: `${tscWatch.projectRoot}/plugin-one/tsconfig.json`, content: pluginOneConfig() }, - { - path: `${tscWatch.projectRoot}/plugin-one/index.ts`, - content: `${pluginOneIndex()} -${pluginOneAction()}` - }, - { path: `${tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, - { path: `${tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, - { path: `/temp/yarn/data/link/plugin-two`, symLink: `${tscWatch.projectRoot}/plugin-two` }, - { path: `${tscWatch.projectRoot}/plugin-one/node_modules/plugin-two`, symLink: `/temp/yarn/data/link/plugin-two` }, - tscWatch.libFile - ], - changeCaseFileTestPath: str => stringContains(str, "/plugin-two"), - }); + verifyDeclarationEmit({ + subScenario: "when same version is referenced through source and another symlinked package", + rootProject: "plugin-one", + files: [ + { path: `${tscWatch.projectRoot}/plugin-two/index.d.ts`, content: pluginTwoDts() }, + { path: `${tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, + { path: `${tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, + { path: `${tscWatch.projectRoot}/plugin-one/tsconfig.json`, content: pluginOneConfig() }, + { path: `${tscWatch.projectRoot}/plugin-one/index.ts`, content: pluginOneIndex() }, + { path: `${tscWatch.projectRoot}/plugin-one/action.ts`, content: pluginOneAction() }, + { path: `${tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, + { path: `${tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, + { path: `${tscWatch.projectRoot}/plugin-one/node_modules/plugin-two`, symLink: `${tscWatch.projectRoot}/plugin-two` }, + tscWatch.libFile + ], + changeCaseFileTestPath: str => stringContains(str, "/plugin-two"), }); verifyDeclarationEmit({ - subScenario: "when pkg references sibling package through indirect symlink", - rootProject: "pkg3", + subScenario: "when same version is referenced through source and another symlinked package with indirect link", + rootProject: "plugin-one", files: [ { - path: `${tscWatch.projectRoot}/pkg1/dist/index.d.ts`, - content: Utils.dedent` - export * from './types';` + path: `${tscWatch.projectRoot}/plugin-two/package.json`, + content: JSON.stringify({ + name: "plugin-two", + version: "0.1.3", + main: "dist/commonjs/index.js" + }) }, + { path: `${tscWatch.projectRoot}/plugin-two/dist/commonjs/index.d.ts`, content: pluginTwoDts() }, + { path: `${tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, + { path: `${tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, + { path: `${tscWatch.projectRoot}/plugin-one/tsconfig.json`, content: pluginOneConfig() }, { - path: `${tscWatch.projectRoot}/pkg1/dist/types.d.ts`, - content: Utils.dedent` + path: `${tscWatch.projectRoot}/plugin-one/index.ts`, + content: `${pluginOneIndex()} +${pluginOneAction()}` + }, + { path: `${tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, + { path: `${tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, + { path: `/temp/yarn/data/link/plugin-two`, symLink: `${tscWatch.projectRoot}/plugin-two` }, + { path: `${tscWatch.projectRoot}/plugin-one/node_modules/plugin-two`, symLink: `/temp/yarn/data/link/plugin-two` }, + tscWatch.libFile + ], + changeCaseFileTestPath: str => stringContains(str, "/plugin-two"), + }); + }); + + verifyDeclarationEmit({ + subScenario: "when pkg references sibling package through indirect symlink", + rootProject: "pkg3", + files: [ + { + path: `${tscWatch.projectRoot}/pkg1/dist/index.d.ts`, + content: Utils.dedent` + export * from './types';` + }, + { + path: `${tscWatch.projectRoot}/pkg1/dist/types.d.ts`, + content: Utils.dedent` export declare type A = { id: string; }; @@ -180,71 +180,71 @@ ${pluginOneAction()}` toString(): string; static create(key: string): MetadataAccessor; }` - }, - { - path: `${tscWatch.projectRoot}/pkg1/package.json`, - content: JSON.stringify({ - name: "@raymondfeng/pkg1", - version: "1.0.0", - main: "dist/index.js", - typings: "dist/index.d.ts" - }) - }, - { - path: `${tscWatch.projectRoot}/pkg2/dist/index.d.ts`, - content: Utils.dedent` + }, + { + path: `${tscWatch.projectRoot}/pkg1/package.json`, + content: JSON.stringify({ + name: "@raymondfeng/pkg1", + version: "1.0.0", + main: "dist/index.js", + typings: "dist/index.d.ts" + }) + }, + { + path: `${tscWatch.projectRoot}/pkg2/dist/index.d.ts`, + content: Utils.dedent` export * from './types';` - }, - { - path: `${tscWatch.projectRoot}/pkg2/dist/types.d.ts`, - content: Utils.dedent` + }, + { + path: `${tscWatch.projectRoot}/pkg2/dist/types.d.ts`, + content: Utils.dedent` export {MetadataAccessor} from '@raymondfeng/pkg1';` - }, - { - path: `${tscWatch.projectRoot}/pkg2/package.json`, - content: JSON.stringify({ - name: "@raymondfeng/pkg2", - version: "1.0.0", - main: "dist/index.js", - typings: "dist/index.d.ts" - }) - }, - { - path: `${tscWatch.projectRoot}/pkg3/src/index.ts`, - content: Utils.dedent` + }, + { + path: `${tscWatch.projectRoot}/pkg2/package.json`, + content: JSON.stringify({ + name: "@raymondfeng/pkg2", + version: "1.0.0", + main: "dist/index.js", + typings: "dist/index.d.ts" + }) + }, + { + path: `${tscWatch.projectRoot}/pkg3/src/index.ts`, + content: Utils.dedent` export * from './keys';` - }, - { - path: `${tscWatch.projectRoot}/pkg3/src/keys.ts`, - content: Utils.dedent` + }, + { + path: `${tscWatch.projectRoot}/pkg3/src/keys.ts`, + content: Utils.dedent` import {MetadataAccessor} from "@raymondfeng/pkg2"; export const ADMIN = MetadataAccessor.create('1');` - }, - { - path: `${tscWatch.projectRoot}/pkg3/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - outDir: "dist", - rootDir: "src", - target: "es5", - module: "commonjs", - strict: true, - esModuleInterop: true, - declaration: true - } - }) - }, - { - path: `${tscWatch.projectRoot}/pkg2/node_modules/@raymondfeng/pkg1`, - symLink: `${tscWatch.projectRoot}/pkg1` - }, - { - path: `${tscWatch.projectRoot}/pkg3/node_modules/@raymondfeng/pkg2`, - symLink: `${tscWatch.projectRoot}/pkg2` - }, - tscWatch.libFile - ], - changeCaseFileTestPath: str => stringContains(str, "/pkg1"), - }); + }, + { + path: `${tscWatch.projectRoot}/pkg3/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + outDir: "dist", + rootDir: "src", + target: "es5", + module: "commonjs", + strict: true, + esModuleInterop: true, + declaration: true + } + }) + }, + { + path: `${tscWatch.projectRoot}/pkg2/node_modules/@raymondfeng/pkg1`, + symLink: `${tscWatch.projectRoot}/pkg1` + }, + { + path: `${tscWatch.projectRoot}/pkg3/node_modules/@raymondfeng/pkg2`, + symLink: `${tscWatch.projectRoot}/pkg2` + }, + tscWatch.libFile + ], + changeCaseFileTestPath: str => stringContains(str, "/pkg1"), }); +}); } diff --git a/src/testRunner/unittests/tsc/helpers.ts b/src/testRunner/unittests/tsc/helpers.ts index 14c4bc0df5127..1f15e5444bf80 100644 --- a/src/testRunner/unittests/tsc/helpers.ts +++ b/src/testRunner/unittests/tsc/helpers.ts @@ -1,176 +1,176 @@ namespace ts { - export type TscCompileSystem = fakes.System & { - writtenFiles: Set; - baseLine(): { file: string; text: string; }; - disableUseFileVersionAsSignature?: boolean; - }; +export type TscCompileSystem = fakes.System & { + writtenFiles: Set; + baseLine(): { file: string; text: string; }; + disableUseFileVersionAsSignature?: boolean; +}; + +export enum BuildKind { + Initial = "initial-build", + IncrementalDtsChange = "incremental-declaration-changes", + IncrementalDtsUnchanged = "incremental-declaration-doesnt-change", + IncrementalHeadersChange = "incremental-headers-change-without-dts-changes", + NoChangeRun = "no-change-run" +} - export enum BuildKind { - Initial = "initial-build", - IncrementalDtsChange = "incremental-declaration-changes", - IncrementalDtsUnchanged = "incremental-declaration-doesnt-change", - IncrementalHeadersChange = "incremental-headers-change-without-dts-changes", - NoChangeRun = "no-change-run" - } +export const noChangeRun: TscIncremental = { + buildKind: BuildKind.NoChangeRun, + modifyFs: noop +}; +export const noChangeOnlyRuns = [noChangeRun]; + +export interface TscCompile { + scenario: string; + subScenario: string; + buildKind?: BuildKind; // Should be defined for tsc --b + fs: () => vfs.FileSystem; + commandLineArgs: readonly string[]; + + modifyFs?: (fs: vfs.FileSystem) => void; + baselineSourceMap?: boolean; + baselineReadFileCalls?: boolean; + baselinePrograms?: boolean; + baselineDependencies?: boolean; + disableUseFileVersionAsSignature?: boolean; + environmentVariables?: Record; +} - export const noChangeRun: TscIncremental = { - buildKind: BuildKind.NoChangeRun, - modifyFs: noop +export type CommandLineProgram = [Program, EmitAndSemanticDiagnosticsBuilderProgram?]; +export interface CommandLineCallbacks { + cb: ExecuteCommandLineCallbacks; + getPrograms: () => readonly CommandLineProgram[]; +} + +function isAnyProgram(program: Program | EmitAndSemanticDiagnosticsBuilderProgram | ParsedCommandLine): program is Program | EmitAndSemanticDiagnosticsBuilderProgram { + return !!(program as Program | EmitAndSemanticDiagnosticsBuilderProgram).getCompilerOptions; +} +export function commandLineCallbacks( + sys: System & { writtenFiles: ReadonlyCollection; }, + originalReadCall?: System["readFile"], + originalWriteFile?: System["writeFile"], +): CommandLineCallbacks { + let programs: CommandLineProgram[] | undefined; + + return { + cb: program => { + if (isAnyProgram(program)) { + baselineBuildInfo(program.getCompilerOptions(), sys, originalReadCall, originalWriteFile); + (programs || (programs = [])).push(isBuilderProgram(program) ? + [program.getProgram(), program] : + [program] + ); + } + else { + baselineBuildInfo(program.options, sys, originalReadCall, originalWriteFile); + } + }, + getPrograms: () => { + const result = programs || emptyArray; + programs = undefined; + return result; + } }; - export const noChangeOnlyRuns = [noChangeRun]; - - export interface TscCompile { - scenario: string; - subScenario: string; - buildKind?: BuildKind; // Should be defined for tsc --b - fs: () => vfs.FileSystem; - commandLineArgs: readonly string[]; - - modifyFs?: (fs: vfs.FileSystem) => void; - baselineSourceMap?: boolean; - baselineReadFileCalls?: boolean; - baselinePrograms?: boolean; - baselineDependencies?: boolean; - disableUseFileVersionAsSignature?: boolean; - environmentVariables?: Record; - } +} - export type CommandLineProgram = [Program, EmitAndSemanticDiagnosticsBuilderProgram?]; - export interface CommandLineCallbacks { - cb: ExecuteCommandLineCallbacks; - getPrograms: () => readonly CommandLineProgram[]; - } +export function tscCompile(input: TscCompile) { + const initialFs = input.fs(); + const inputFs = initialFs.shadow(); + const { + scenario, subScenario, buildKind, + commandLineArgs, modifyFs, + baselineSourceMap, baselineReadFileCalls, baselinePrograms, baselineDependencies, + environmentVariables + } = input; + if (modifyFs) modifyFs(inputFs); + inputFs.makeReadonly(); + const fs = inputFs.shadow(); + + // Create system + const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc", env: environmentVariables }) as TscCompileSystem; + if (input.disableUseFileVersionAsSignature) sys.disableUseFileVersionAsSignature = true; + fakes.patchHostForBuildInfoReadWrite(sys); + const writtenFiles = sys.writtenFiles = new Set(); + const originalWriteFile = sys.writeFile; + sys.writeFile = (fileName, content, writeByteOrderMark) => { + const path = toPathWithSystem(sys, fileName); + assert.isFalse(writtenFiles.has(path)); + writtenFiles.add(path); + return originalWriteFile.call(sys, fileName, content, writeByteOrderMark); + }; + const actualReadFileMap: MapLike = {}; + const originalReadFile = sys.readFile; + sys.readFile = path => { + // Dont record libs + if (path.startsWith("/src/")) { + actualReadFileMap[path] = (getProperty(actualReadFileMap, path) || 0) + 1; + } + return originalReadFile.call(sys, path); + }; - function isAnyProgram(program: Program | EmitAndSemanticDiagnosticsBuilderProgram | ParsedCommandLine): program is Program | EmitAndSemanticDiagnosticsBuilderProgram { - return !!(program as Program | EmitAndSemanticDiagnosticsBuilderProgram).getCompilerOptions; + sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`); + sys.exit = exitCode => sys.exitCode = exitCode; + const { cb, getPrograms } = commandLineCallbacks(sys, originalReadFile, originalWriteFile); + executeCommandLine( + sys, + cb, + commandLineArgs, + ); + sys.write(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}\n`); + if (baselinePrograms) { + const baseline: string[] = []; + tscWatch.baselinePrograms(baseline, getPrograms, emptyArray, baselineDependencies); + sys.write(baseline.join("\n")); } - export function commandLineCallbacks( - sys: System & { writtenFiles: ReadonlyCollection; }, - originalReadCall?: System["readFile"], - originalWriteFile?: System["writeFile"], - ): CommandLineCallbacks { - let programs: CommandLineProgram[] | undefined; - - return { - cb: program => { - if (isAnyProgram(program)) { - baselineBuildInfo(program.getCompilerOptions(), sys, originalReadCall, originalWriteFile); - (programs || (programs = [])).push(isBuilderProgram(program) ? - [program.getProgram(), program] : - [program] - ); - } - else { - baselineBuildInfo(program.options, sys, originalReadCall, originalWriteFile); - } - }, - getPrograms: () => { - const result = programs || emptyArray; - programs = undefined; - return result; - } - }; + if (baselineReadFileCalls) { + sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `); } + if (baselineSourceMap) generateSourceMapBaselineFiles(sys); - export function tscCompile(input: TscCompile) { - const initialFs = input.fs(); - const inputFs = initialFs.shadow(); - const { - scenario, subScenario, buildKind, - commandLineArgs, modifyFs, - baselineSourceMap, baselineReadFileCalls, baselinePrograms, baselineDependencies, - environmentVariables - } = input; - if (modifyFs) modifyFs(inputFs); - inputFs.makeReadonly(); - const fs = inputFs.shadow(); - - // Create system - const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc", env: environmentVariables }) as TscCompileSystem; - if (input.disableUseFileVersionAsSignature) sys.disableUseFileVersionAsSignature = true; - fakes.patchHostForBuildInfoReadWrite(sys); - const writtenFiles = sys.writtenFiles = new Set(); - const originalWriteFile = sys.writeFile; - sys.writeFile = (fileName, content, writeByteOrderMark) => { - const path = toPathWithSystem(sys, fileName); - assert.isFalse(writtenFiles.has(path)); - writtenFiles.add(path); - return originalWriteFile.call(sys, fileName, content, writeByteOrderMark); - }; - const actualReadFileMap: MapLike = {}; - const originalReadFile = sys.readFile; - sys.readFile = path => { - // Dont record libs - if (path.startsWith("/src/")) { - actualReadFileMap[path] = (getProperty(actualReadFileMap, path) || 0) + 1; - } - return originalReadFile.call(sys, path); - }; + fs.makeReadonly(); - sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`); - sys.exit = exitCode => sys.exitCode = exitCode; - const { cb, getPrograms } = commandLineCallbacks(sys, originalReadFile, originalWriteFile); - executeCommandLine( - sys, - cb, - commandLineArgs, - ); - sys.write(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}\n`); - if (baselinePrograms) { - const baseline: string[] = []; - tscWatch.baselinePrograms(baseline, getPrograms, emptyArray, baselineDependencies); - sys.write(baseline.join("\n")); - } - if (baselineReadFileCalls) { - sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `); - } - if (baselineSourceMap) generateSourceMapBaselineFiles(sys); - - fs.makeReadonly(); - - sys.baseLine = () => { - const baseFsPatch = !buildKind || buildKind === BuildKind.Initial ? - inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true }) : - inputFs.diff(initialFs, { includeChangedFileWithSameContent: true }); - const patch = fs.diff(inputFs, { includeChangedFileWithSameContent: true }); - return { - file: `${isBuild(commandLineArgs) ? "tsbuild" : "tsc"}/${scenario}/${buildKind || BuildKind.Initial}/${subScenario.split(" ").join("-")}.js`, - text: `Input:: + sys.baseLine = () => { + const baseFsPatch = !buildKind || buildKind === BuildKind.Initial ? + inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true }) : + inputFs.diff(initialFs, { includeChangedFileWithSameContent: true }); + const patch = fs.diff(inputFs, { includeChangedFileWithSameContent: true }); + return { + file: `${isBuild(commandLineArgs) ? "tsbuild" : "tsc"}/${scenario}/${buildKind || BuildKind.Initial}/${subScenario.split(" ").join("-")}.js`, + text: `Input:: ${baseFsPatch ? vfs.formatPatch(baseFsPatch) : ""} Output:: ${sys.output.join("")} ${patch ? vfs.formatPatch(patch) : ""}` - }; }; - return sys; - } + }; + return sys; +} - export function verifyTscBaseline(sys: () => { baseLine: TscCompileSystem["baseLine"]; }) { - it(`Generates files matching the baseline`, () => { - const { file, text } = sys().baseLine(); - Harness.Baseline.runBaseline(file, text); - }); - } +export function verifyTscBaseline(sys: () => { baseLine: TscCompileSystem["baseLine"]; }) { + it(`Generates files matching the baseline`, () => { + const { file, text } = sys().baseLine(); + Harness.Baseline.runBaseline(file, text); + }); +} - export function verifyTsc(input: TscCompile) { - describe(`tsc ${input.commandLineArgs.join(" ")} ${input.scenario}:: ${input.subScenario}`, () => { - describe(input.scenario, () => { - describe(input.subScenario, () => { - let sys: TscCompileSystem; - before(() => { - sys = tscCompile({ - ...input, - fs: () => getFsWithTime(input.fs()).fs.makeReadonly() - }); +export function verifyTsc(input: TscCompile) { + describe(`tsc ${input.commandLineArgs.join(" ")} ${input.scenario}:: ${input.subScenario}`, () => { + describe(input.scenario, () => { + describe(input.subScenario, () => { + let sys: TscCompileSystem; + before(() => { + sys = tscCompile({ + ...input, + fs: () => getFsWithTime(input.fs()).fs.makeReadonly() }); - after(() => { - sys = undefined!; - }); - verifyTscBaseline(() => sys); }); + after(() => { + sys = undefined!; + }); + verifyTscBaseline(() => sys); }); }); - } + }); +} } diff --git a/src/testRunner/unittests/tsc/incremental.ts b/src/testRunner/unittests/tsc/incremental.ts index a8fe334de7c7f..5317c6db406be 100644 --- a/src/testRunner/unittests/tsc/incremental.ts +++ b/src/testRunner/unittests/tsc/incremental.ts @@ -1,11 +1,11 @@ namespace ts { - describe("unittests:: tsc:: incremental::", () => { - verifyTscSerializedIncrementalEdits({ - scenario: "incremental", - subScenario: "when passing filename for buildinfo on commandline", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` +describe("unittests:: tsc:: incremental::", () => { + verifyTscSerializedIncrementalEdits({ + scenario: "incremental", + subScenario: "when passing filename for buildinfo on commandline", + fs: () => loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "es5", @@ -15,52 +15,52 @@ namespace ts { "src/**/*.ts" ] }`, - }), - commandLineArgs: ["--incremental", "--p", "src/project", "--tsBuildInfoFile", "src/project/.tsbuildinfo", "--explainFiles"], - incrementalScenarios: noChangeOnlyRuns - }); + }), + commandLineArgs: ["--incremental", "--p", "src/project", "--tsBuildInfoFile", "src/project/.tsbuildinfo", "--explainFiles"], + incrementalScenarios: noChangeOnlyRuns + }); - verifyTscSerializedIncrementalEdits({ - scenario: "incremental", - subScenario: "when passing rootDir from commandline", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` + verifyTscSerializedIncrementalEdits({ + scenario: "incremental", + subScenario: "when passing rootDir from commandline", + fs: () => loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "incremental": true, "outDir": "dist", }, }`, - }), - commandLineArgs: ["--p", "src/project", "--rootDir", "src/project/src"], - incrementalScenarios: noChangeOnlyRuns - }); + }), + commandLineArgs: ["--p", "src/project", "--rootDir", "src/project/src"], + incrementalScenarios: noChangeOnlyRuns + }); - verifyTscSerializedIncrementalEdits({ - scenario: "incremental", - subScenario: "with only dts files", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.d.ts": "export const x = 10;", - "/src/project/src/another.d.ts": "export const y = 10;", - "/src/project/tsconfig.json": "{}", - }), - commandLineArgs: ["--incremental", "--p", "src/project"], - incrementalScenarios: [ - noChangeRun, - { - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => appendText(fs, "/src/project/src/main.d.ts", "export const xy = 100;") - } - ] - }); + verifyTscSerializedIncrementalEdits({ + scenario: "incremental", + subScenario: "with only dts files", + fs: () => loadProjectFromFiles({ + "/src/project/src/main.d.ts": "export const x = 10;", + "/src/project/src/another.d.ts": "export const y = 10;", + "/src/project/tsconfig.json": "{}", + }), + commandLineArgs: ["--incremental", "--p", "src/project"], + incrementalScenarios: [ + noChangeRun, + { + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => appendText(fs, "/src/project/src/main.d.ts", "export const xy = 100;") + } + ] + }); - verifyTscSerializedIncrementalEdits({ - scenario: "incremental", - subScenario: "when passing rootDir is in the tsconfig", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` + verifyTscSerializedIncrementalEdits({ + scenario: "incremental", + subScenario: "when passing rootDir is in the tsconfig", + fs: () => loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "incremental": true, @@ -68,234 +68,234 @@ namespace ts { "rootDir": "./" }, }`, - }), - commandLineArgs: ["--p", "src/project"], - incrementalScenarios: noChangeOnlyRuns + }), + commandLineArgs: ["--p", "src/project"], + incrementalScenarios: noChangeOnlyRuns + }); + + describe("with noEmitOnError", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = loadProjectFromDisk("tests/projects/noEmitOnError"); + }); + after(() => { + projFs = undefined!; }); - describe("with noEmitOnError", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/noEmitOnError"); - }); - after(() => { - projFs = undefined!; + function verifyNoEmitOnError(subScenario: string, fixModifyFs: TscIncremental["modifyFs"], modifyFs?: TscIncremental["modifyFs"]) { + verifyTscSerializedIncrementalEdits({ + scenario: "incremental", + subScenario, + fs: () => projFs, + commandLineArgs: ["--incremental", "-p", "src"], + modifyFs, + incrementalScenarios: [ + noChangeRun, + { + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fixModifyFs + }, + noChangeRun, + ], + baselinePrograms: true }); - - function verifyNoEmitOnError(subScenario: string, fixModifyFs: TscIncremental["modifyFs"], modifyFs?: TscIncremental["modifyFs"]) { - verifyTscSerializedIncrementalEdits({ - scenario: "incremental", - subScenario, - fs: () => projFs, - commandLineArgs: ["--incremental", "-p", "src"], - modifyFs, - incrementalScenarios: [ - noChangeRun, - { - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fixModifyFs - }, - noChangeRun, - ], - baselinePrograms: true - }); - } - verifyNoEmitOnError( - "with noEmitOnError syntax errors", - fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + } + verifyNoEmitOnError( + "with noEmitOnError syntax errors", + fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a = { lastName: 'sdsd' };`, "utf-8") - ); + ); - verifyNoEmitOnError( - "with noEmitOnError semantic errors", - fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + verifyNoEmitOnError( + "with noEmitOnError semantic errors", + fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a: string = "hello";`, "utf-8"), - fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a: string = 10;`, "utf-8"), - ); - }); + ); + }); - describe("when noEmit changes between compilation", () => { - verifyNoEmitChanges({ incremental: true }); - verifyNoEmitChanges({ incremental: true, declaration: true }); - verifyNoEmitChanges({ composite: true }); + describe("when noEmit changes between compilation", () => { + verifyNoEmitChanges({ incremental: true }); + verifyNoEmitChanges({ incremental: true, declaration: true }); + verifyNoEmitChanges({ composite: true }); - function verifyNoEmitChanges(compilerOptions: CompilerOptions) { - const noChangeRunWithNoEmit: TscIncremental = { - subScenario: "No Change run with noEmit", - commandLineArgs: ["--p", "src/project", "--noEmit"], - ...noChangeRun, - }; - const noChangeRunWithEmit: TscIncremental = { - subScenario: "No Change run with emit", - commandLineArgs: ["--p", "src/project"], - ...noChangeRun, - }; - let optionsString = ""; - for (const key in compilerOptions) { - if (hasProperty(compilerOptions, key)) { - optionsString += ` ${key}`; - } + function verifyNoEmitChanges(compilerOptions: CompilerOptions) { + const noChangeRunWithNoEmit: TscIncremental = { + subScenario: "No Change run with noEmit", + commandLineArgs: ["--p", "src/project", "--noEmit"], + ...noChangeRun, + }; + const noChangeRunWithEmit: TscIncremental = { + subScenario: "No Change run with emit", + commandLineArgs: ["--p", "src/project"], + ...noChangeRun, + }; + let optionsString = ""; + for (const key in compilerOptions) { + if (hasProperty(compilerOptions, key)) { + optionsString += ` ${key}`; } + } - verifyTscSerializedIncrementalEdits({ - scenario: "incremental", - subScenario: `noEmit changes${optionsString}`, - commandLineArgs: ["--p", "src/project"], - fs, - incrementalScenarios: [ - noChangeRunWithNoEmit, - noChangeRunWithNoEmit, - { - subScenario: "Introduce error but still noEmit", - commandLineArgs: ["--p", "src/project", "--noEmit"], - modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"), - buildKind: BuildKind.IncrementalDtsChange - }, - { - subScenario: "Fix error and emit", - modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"), - buildKind: BuildKind.IncrementalDtsChange - }, - noChangeRunWithEmit, - noChangeRunWithNoEmit, - noChangeRunWithNoEmit, - noChangeRunWithEmit, - { - subScenario: "Introduce error and emit", - modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"), - buildKind: BuildKind.IncrementalDtsChange - }, - noChangeRunWithEmit, - noChangeRunWithNoEmit, - noChangeRunWithNoEmit, - noChangeRunWithEmit, - { - subScenario: "Fix error and no emit", - commandLineArgs: ["--p", "src/project", "--noEmit"], - modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"), - buildKind: BuildKind.IncrementalDtsChange - }, - noChangeRunWithEmit, - noChangeRunWithNoEmit, - noChangeRunWithNoEmit, - noChangeRunWithEmit, - ], - }); + verifyTscSerializedIncrementalEdits({ + scenario: "incremental", + subScenario: `noEmit changes${optionsString}`, + commandLineArgs: ["--p", "src/project"], + fs, + incrementalScenarios: [ + noChangeRunWithNoEmit, + noChangeRunWithNoEmit, + { + subScenario: "Introduce error but still noEmit", + commandLineArgs: ["--p", "src/project", "--noEmit"], + modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"), + buildKind: BuildKind.IncrementalDtsChange + }, + { + subScenario: "Fix error and emit", + modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"), + buildKind: BuildKind.IncrementalDtsChange + }, + noChangeRunWithEmit, + noChangeRunWithNoEmit, + noChangeRunWithNoEmit, + noChangeRunWithEmit, + { + subScenario: "Introduce error and emit", + modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"), + buildKind: BuildKind.IncrementalDtsChange + }, + noChangeRunWithEmit, + noChangeRunWithNoEmit, + noChangeRunWithNoEmit, + noChangeRunWithEmit, + { + subScenario: "Fix error and no emit", + commandLineArgs: ["--p", "src/project", "--noEmit"], + modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"), + buildKind: BuildKind.IncrementalDtsChange + }, + noChangeRunWithEmit, + noChangeRunWithNoEmit, + noChangeRunWithNoEmit, + noChangeRunWithEmit, + ], + }); - verifyTscSerializedIncrementalEdits({ - scenario: "incremental", - subScenario: `noEmit changes with initial noEmit${optionsString}`, - commandLineArgs: ["--p", "src/project", "--noEmit"], - fs, - incrementalScenarios: [ - noChangeRunWithEmit, - { - subScenario: "Introduce error with emit", - commandLineArgs: ["--p", "src/project"], - modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"), - buildKind: BuildKind.IncrementalDtsChange - }, - { - subScenario: "Fix error and no emit", - modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"), - buildKind: BuildKind.IncrementalDtsChange - }, - noChangeRunWithEmit, - ], - }); + verifyTscSerializedIncrementalEdits({ + scenario: "incremental", + subScenario: `noEmit changes with initial noEmit${optionsString}`, + commandLineArgs: ["--p", "src/project", "--noEmit"], + fs, + incrementalScenarios: [ + noChangeRunWithEmit, + { + subScenario: "Introduce error with emit", + commandLineArgs: ["--p", "src/project"], + modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"), + buildKind: BuildKind.IncrementalDtsChange + }, + { + subScenario: "Fix error and no emit", + modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"), + buildKind: BuildKind.IncrementalDtsChange + }, + noChangeRunWithEmit, + ], + }); - function fs() { - return loadProjectFromFiles({ - "/src/project/src/class.ts": Utils.dedent` + function fs() { + return loadProjectFromFiles({ + "/src/project/src/class.ts": Utils.dedent` export class classC { prop = 1; }`, - "/src/project/src/indirectClass.ts": Utils.dedent` + "/src/project/src/indirectClass.ts": Utils.dedent` import { classC } from './class'; export class indirectClass { classC = new classC(); }`, - "/src/project/src/directUse.ts": Utils.dedent` + "/src/project/src/directUse.ts": Utils.dedent` import { indirectClass } from './indirectClass'; new indirectClass().classC.prop;`, - "/src/project/src/indirectUse.ts": Utils.dedent` + "/src/project/src/indirectUse.ts": Utils.dedent` import { indirectClass } from './indirectClass'; new indirectClass().classC.prop;`, - "/src/project/src/noChangeFile.ts": Utils.dedent` + "/src/project/src/noChangeFile.ts": Utils.dedent` export function writeLog(s: string) { }`, - "/src/project/src/noChangeFileWithEmitSpecificError.ts": Utils.dedent` + "/src/project/src/noChangeFileWithEmitSpecificError.ts": Utils.dedent` function someFunc(arguments: boolean, ...rest: any[]) { }`, - "/src/project/tsconfig.json": JSON.stringify({ compilerOptions }), - }); - } + "/src/project/tsconfig.json": JSON.stringify({ compilerOptions }), + }); } - }); + } + }); - verifyTscSerializedIncrementalEdits({ - scenario: "incremental", - subScenario: `when global file is added, the signatures are updated`, - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": Utils.dedent` + verifyTscSerializedIncrementalEdits({ + scenario: "incremental", + subScenario: `when global file is added, the signatures are updated`, + fs: () => loadProjectFromFiles({ + "/src/project/src/main.ts": Utils.dedent` /// /// function main() { } `, - "/src/project/src/anotherFileWithSameReferenes.ts": Utils.dedent` + "/src/project/src/anotherFileWithSameReferenes.ts": Utils.dedent` /// /// function anotherFileWithSameReferenes() { } `, - "/src/project/src/filePresent.ts": `function something() { return 10; }`, - "/src/project/tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true, }, - include: ["src/**/*.ts"] - }), + "/src/project/src/filePresent.ts": `function something() { return 10; }`, + "/src/project/tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true, }, + include: ["src/**/*.ts"] }), - commandLineArgs: ["--p", "src/project"], - incrementalScenarios: [ - noChangeRun, - { - subScenario: "Modify main file", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`), - }, - { - subScenario: "Modify main file again", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`), - }, - { - subScenario: "Add new file and update main file", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => { - fs.writeFileSync(`/src/project/src/newFile.ts`, "function foo() { return 20; }"); - prependText(fs, `/src/project/src/main.ts`, `/// + }), + commandLineArgs: ["--p", "src/project"], + incrementalScenarios: [ + noChangeRun, + { + subScenario: "Modify main file", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`), + }, + { + subScenario: "Modify main file again", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`), + }, + { + subScenario: "Add new file and update main file", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => { + fs.writeFileSync(`/src/project/src/newFile.ts`, "function foo() { return 20; }"); + prependText(fs, `/src/project/src/main.ts`, `/// `); - appendText(fs, `/src/project/src/main.ts`, `foo();`); - }, + appendText(fs, `/src/project/src/main.ts`, `foo();`); }, - { - subScenario: "Write file that could not be resolved", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => fs.writeFileSync(`/src/project/src/fileNotFound.ts`, "function something2() { return 20; }"), - }, - { - subScenario: "Modify main file", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`), - }, - ], - baselinePrograms: true, - }); + }, + { + subScenario: "Write file that could not be resolved", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => fs.writeFileSync(`/src/project/src/fileNotFound.ts`, "function something2() { return 20; }"), + }, + { + subScenario: "Modify main file", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`), + }, + ], + baselinePrograms: true, + }); - describe("when synthesized imports are added to files", () => { - function getJsxLibraryContent() { - return ` + describe("when synthesized imports are added to files", () => { + function getJsxLibraryContent() { + return ` export {}; declare global { namespace JSX { @@ -307,116 +307,116 @@ declare global { } } }`; - } - - verifyTsc({ - scenario: "react-jsx-emit-mode", - subScenario: "with no backing types found doesn't crash", - fs: () => loadProjectFromFiles({ - "/src/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result - "/src/project/node_modules/@types/react/index.d.ts": getJsxLibraryContent(), // doesn't contain a jsx-runtime definition - "/src/project/src/index.tsx": `export const App = () =>
;`, - "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" } }) - }), - commandLineArgs: ["--p", "src/project"] - }); + } - verifyTsc({ - scenario: "react-jsx-emit-mode", - subScenario: "with no backing types found doesn't crash under --strict", - fs: () => loadProjectFromFiles({ - "/src/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result - "/src/project/node_modules/@types/react/index.d.ts": getJsxLibraryContent(), // doesn't contain a jsx-runtime definition - "/src/project/src/index.tsx": `export const App = () =>
;`, - "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" } }) - }), - commandLineArgs: ["--p", "src/project", "--strict"] - }); + verifyTsc({ + scenario: "react-jsx-emit-mode", + subScenario: "with no backing types found doesn't crash", + fs: () => loadProjectFromFiles({ + "/src/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result + "/src/project/node_modules/@types/react/index.d.ts": getJsxLibraryContent(), // doesn't contain a jsx-runtime definition + "/src/project/src/index.tsx": `export const App = () =>
;`, + "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" } }) + }), + commandLineArgs: ["--p", "src/project"] }); - verifyTscSerializedIncrementalEdits({ - scenario: "incremental", - subScenario: "when new file is added to the referenced project", - commandLineArgs: ["-i", "-p", `src/projects/project2`], + verifyTsc({ + scenario: "react-jsx-emit-mode", + subScenario: "with no backing types found doesn't crash under --strict", fs: () => loadProjectFromFiles({ - "/src/projects/project1/tsconfig.json": JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - exclude: ["temp"] - }), - "/src/projects/project1/class1.ts": `class class1 {}`, - "/src/projects/project1/class1.d.ts": `declare class class1 {}`, - "/src/projects/project2/tsconfig.json": JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - references: [ - { path: "../project1" } - ] - }), - "/src/projects/project2/class2.ts": `class class2 {}`, + "/src/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result + "/src/project/node_modules/@types/react/index.d.ts": getJsxLibraryContent(), // doesn't contain a jsx-runtime definition + "/src/project/src/index.tsx": `export const App = () =>
;`, + "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" } }) }), - incrementalScenarios: [ - { - subScenario: "Add class3 to project1 and build it", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.ts", `class class3 {}`, "utf-8"), - cleanBuildDiscrepancies: () => new Map([ - // Ts buildinfo will not be updated in incremental build so it will have semantic diagnostics cached from previous build - // But in clean build because of global diagnostics, semantic diagnostics are not queried so not cached in tsbuildinfo - ["/src/projects/project2/tsconfig.tsbuildinfo", CleanBuildDescrepancy.CleanFileTextDifferent] - ]), - }, - { - subScenario: "Add output of class3", - buildKind: BuildKind.IncrementalDtsChange, - modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.d.ts", `declare class class3 {}`, "utf-8"), - }, - { - subScenario: "Add excluded file to project1", - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => { - fs.mkdirSync("/src/projects/project1/temp"); - fs.writeFileSync("/src/projects/project1/temp/file.d.ts", `declare class file {}`, "utf-8"); - }, + commandLineArgs: ["--p", "src/project", "--strict"] + }); + }); + + verifyTscSerializedIncrementalEdits({ + scenario: "incremental", + subScenario: "when new file is added to the referenced project", + commandLineArgs: ["-i", "-p", `src/projects/project2`], + fs: () => loadProjectFromFiles({ + "/src/projects/project1/tsconfig.json": JSON.stringify({ + compilerOptions: { + module: "none", + composite: true }, - { - subScenario: "Delete output for class3", - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => fs.unlinkSync("/src/projects/project1/class3.d.ts"), - cleanBuildDiscrepancies: () => new Map([ - // Ts buildinfo willbe updated but will retain lib file errors from previous build and not others because they are emitted because of change which results in clearing their semantic diagnostics cache - // But in clean build because of global diagnostics, semantic diagnostics are not queried so not cached in tsbuildinfo - ["/src/projects/project2/tsconfig.tsbuildinfo", CleanBuildDescrepancy.CleanFileTextDifferent] - ]), + exclude: ["temp"] + }), + "/src/projects/project1/class1.ts": `class class1 {}`, + "/src/projects/project1/class1.d.ts": `declare class class1 {}`, + "/src/projects/project2/tsconfig.json": JSON.stringify({ + compilerOptions: { + module: "none", + composite: true }, - { - subScenario: "Create output for class3", - buildKind: BuildKind.IncrementalDtsUnchanged, - modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.d.ts", `declare class class3 {}`, "utf-8"), + references: [ + { path: "../project1" } + ] + }), + "/src/projects/project2/class2.ts": `class class2 {}`, + }), + incrementalScenarios: [ + { + subScenario: "Add class3 to project1 and build it", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.ts", `class class3 {}`, "utf-8"), + cleanBuildDiscrepancies: () => new Map([ + // Ts buildinfo will not be updated in incremental build so it will have semantic diagnostics cached from previous build + // But in clean build because of global diagnostics, semantic diagnostics are not queried so not cached in tsbuildinfo + ["/src/projects/project2/tsconfig.tsbuildinfo", CleanBuildDescrepancy.CleanFileTextDifferent] + ]), + }, + { + subScenario: "Add output of class3", + buildKind: BuildKind.IncrementalDtsChange, + modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.d.ts", `declare class class3 {}`, "utf-8"), + }, + { + subScenario: "Add excluded file to project1", + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => { + fs.mkdirSync("/src/projects/project1/temp"); + fs.writeFileSync("/src/projects/project1/temp/file.d.ts", `declare class file {}`, "utf-8"); }, - ] - }); + }, + { + subScenario: "Delete output for class3", + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => fs.unlinkSync("/src/projects/project1/class3.d.ts"), + cleanBuildDiscrepancies: () => new Map([ + // Ts buildinfo willbe updated but will retain lib file errors from previous build and not others because they are emitted because of change which results in clearing their semantic diagnostics cache + // But in clean build because of global diagnostics, semantic diagnostics are not queried so not cached in tsbuildinfo + ["/src/projects/project2/tsconfig.tsbuildinfo", CleanBuildDescrepancy.CleanFileTextDifferent] + ]), + }, + { + subScenario: "Create output for class3", + buildKind: BuildKind.IncrementalDtsUnchanged, + modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.d.ts", `declare class class3 {}`, "utf-8"), + }, + ] + }); - verifyTscSerializedIncrementalEdits({ - scenario: "incremental", - subScenario: "when project has strict true", - commandLineArgs: ["-noEmit", "-p", `src/project`], - fs: () => loadProjectFromFiles({ - "/src/project/tsconfig.json": JSON.stringify({ - compilerOptions: { - incremental: true, - strict: true, - }, - }), - "/src/project/class1.ts": `export class class1 {}`, + verifyTscSerializedIncrementalEdits({ + scenario: "incremental", + subScenario: "when project has strict true", + commandLineArgs: ["-noEmit", "-p", `src/project`], + fs: () => loadProjectFromFiles({ + "/src/project/tsconfig.json": JSON.stringify({ + compilerOptions: { + incremental: true, + strict: true, + }, }), - incrementalScenarios: noChangeOnlyRuns, - baselinePrograms: true - }); + "/src/project/class1.ts": `export class class1 {}`, + }), + incrementalScenarios: noChangeOnlyRuns, + baselinePrograms: true }); +}); } diff --git a/src/testRunner/unittests/tsc/listFilesOnly.ts b/src/testRunner/unittests/tsc/listFilesOnly.ts index 97c9bd7d5e43c..7dee8b4e7638a 100644 --- a/src/testRunner/unittests/tsc/listFilesOnly.ts +++ b/src/testRunner/unittests/tsc/listFilesOnly.ts @@ -1,23 +1,23 @@ namespace ts { - describe("unittests:: tsc:: listFilesOnly::", () => { - verifyTsc({ - scenario: "listFilesOnly", - subScenario: "combined with watch", - fs: () => loadProjectFromFiles({ - "/src/test.ts": Utils.dedent` +describe("unittests:: tsc:: listFilesOnly::", () => { + verifyTsc({ + scenario: "listFilesOnly", + subScenario: "combined with watch", + fs: () => loadProjectFromFiles({ + "/src/test.ts": Utils.dedent` export const x = 1;`, - }), - commandLineArgs: ["/src/test.ts", "--watch", "--listFilesOnly"] - }); + }), + commandLineArgs: ["/src/test.ts", "--watch", "--listFilesOnly"] + }); - verifyTsc({ - scenario: "listFilesOnly", - subScenario: "loose file", - fs: () => loadProjectFromFiles({ - "/src/test.ts": Utils.dedent` + verifyTsc({ + scenario: "listFilesOnly", + subScenario: "loose file", + fs: () => loadProjectFromFiles({ + "/src/test.ts": Utils.dedent` export const x = 1;`, - }), - commandLineArgs: ["/src/test.ts", "--listFilesOnly"] - }); + }), + commandLineArgs: ["/src/test.ts", "--listFilesOnly"] }); +}); } diff --git a/src/testRunner/unittests/tsc/projectReferences.ts b/src/testRunner/unittests/tsc/projectReferences.ts index 765bdfdb53cf1..2023a8a47086a 100644 --- a/src/testRunner/unittests/tsc/projectReferences.ts +++ b/src/testRunner/unittests/tsc/projectReferences.ts @@ -1,42 +1,42 @@ namespace ts { - describe("unittests:: tsc:: projectReferences::", () => { - verifyTsc({ - scenario: "projectReferences", - subScenario: "when project contains invalid project reference", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": JSON.stringify({ - compilerOptions: { - module: "amd", - outFile: "theApp.js" - }, - references: [ - { path: "../Util/Dates" } - ] - }), +describe("unittests:: tsc:: projectReferences::", () => { + verifyTsc({ + scenario: "projectReferences", + subScenario: "when project contains invalid project reference", + fs: () => loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": JSON.stringify({ + compilerOptions: { + module: "amd", + outFile: "theApp.js" + }, + references: [ + { path: "../Util/Dates" } + ] }), - commandLineArgs: ["--p", "src/project"], - }); + }), + commandLineArgs: ["--p", "src/project"], + }); - verifyTsc({ - scenario: "projectReferences", - subScenario: "when project references composite project with noEmit", - fs: () => loadProjectFromFiles({ - "/src/utils/index.ts": "export const x = 10;", - "/src/utils/tsconfig.json": JSON.stringify({ - compilerOptions: { - composite: true, - noEmit: true, - } - }), - "/src/project/index.ts": `import { x } from "../utils";`, - "/src/project/tsconfig.json": JSON.stringify({ - references: [ - { path: "../utils" } - ] - }), + verifyTsc({ + scenario: "projectReferences", + subScenario: "when project references composite project with noEmit", + fs: () => loadProjectFromFiles({ + "/src/utils/index.ts": "export const x = 10;", + "/src/utils/tsconfig.json": JSON.stringify({ + compilerOptions: { + composite: true, + noEmit: true, + } + }), + "/src/project/index.ts": `import { x } from "../utils";`, + "/src/project/tsconfig.json": JSON.stringify({ + references: [ + { path: "../utils" } + ] }), - commandLineArgs: ["--p", "src/project"] - }); + }), + commandLineArgs: ["--p", "src/project"] }); +}); } diff --git a/src/testRunner/unittests/tsc/runWithoutArgs.ts b/src/testRunner/unittests/tsc/runWithoutArgs.ts index 1c4a3c9a0a4f4..bf9f0ee0a025e 100644 --- a/src/testRunner/unittests/tsc/runWithoutArgs.ts +++ b/src/testRunner/unittests/tsc/runWithoutArgs.ts @@ -1,27 +1,27 @@ namespace ts { - describe("unittests:: tsc:: runWithoutArgs::", () => { - verifyTsc({ - scenario: "runWithoutArgs", - subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped", - fs: () => loadProjectFromFiles({}), - commandLineArgs: [], - environmentVariables: { TS_TEST_TERMINAL_WIDTH: "120" } - }); - - verifyTsc({ - scenario: "runWithoutArgs", - subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped when host can't provide terminal width", - fs: () => loadProjectFromFiles({}), - commandLineArgs: [], - }); +describe("unittests:: tsc:: runWithoutArgs::", () => { + verifyTsc({ + scenario: "runWithoutArgs", + subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped", + fs: () => loadProjectFromFiles({}), + commandLineArgs: [], + environmentVariables: { TS_TEST_TERMINAL_WIDTH: "120" } + }); - verifyTsc({ - scenario: "runWithoutArgs", - subScenario: "does not add color when NO_COLOR is set", - fs: () => loadProjectFromFiles({}), - commandLineArgs: [], - environmentVariables: { NO_COLOR: "true" } - }); + verifyTsc({ + scenario: "runWithoutArgs", + subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped when host can't provide terminal width", + fs: () => loadProjectFromFiles({}), + commandLineArgs: [], + }); + verifyTsc({ + scenario: "runWithoutArgs", + subScenario: "does not add color when NO_COLOR is set", + fs: () => loadProjectFromFiles({}), + commandLineArgs: [], + environmentVariables: { NO_COLOR: "true" } }); + +}); } diff --git a/src/testRunner/unittests/tscWatch/consoleClearing.ts b/src/testRunner/unittests/tscWatch/consoleClearing.ts index c874c32e04b31..f2bb6d8db03c9 100644 --- a/src/testRunner/unittests/tscWatch/consoleClearing.ts +++ b/src/testRunner/unittests/tscWatch/consoleClearing.ts @@ -1,62 +1,62 @@ namespace ts.tscWatch { - describe("unittests:: tsc-watch:: console clearing", () => { - const scenario = "consoleClearing"; - const file: File = { - path: "/f.ts", - content: "" - }; +describe("unittests:: tsc-watch:: console clearing", () => { + const scenario = "consoleClearing"; + const file: File = { + path: "/f.ts", + content: "" + }; - const makeChangeToFile: TscWatchCompileChange[] = [{ - caption: "Comment added to file f", - change: sys => sys.modifyFile(file.path, "//"), - timeouts: runQueuedTimeoutCallbacks, - }]; + const makeChangeToFile: TscWatchCompileChange[] = [{ + caption: "Comment added to file f", + change: sys => sys.modifyFile(file.path, "//"), + timeouts: runQueuedTimeoutCallbacks, + }]; - function checkConsoleClearingUsingCommandLineOptions(subScenario: string, commandLineOptions?: string[]) { - verifyTscWatch({ - scenario, - subScenario, - commandLineArgs: ["--w", file.path, ...commandLineOptions || emptyArray], - sys: () => createWatchedSystem([file, libFile]), - changes: makeChangeToFile, - }); - } + function checkConsoleClearingUsingCommandLineOptions(subScenario: string, commandLineOptions?: string[]) { + verifyTscWatch({ + scenario, + subScenario, + commandLineArgs: ["--w", file.path, ...commandLineOptions || emptyArray], + sys: () => createWatchedSystem([file, libFile]), + changes: makeChangeToFile, + }); + } - checkConsoleClearingUsingCommandLineOptions("without --diagnostics or --extendedDiagnostics"); - checkConsoleClearingUsingCommandLineOptions("with --diagnostics", ["--diagnostics"]); - checkConsoleClearingUsingCommandLineOptions("with --extendedDiagnostics", ["--extendedDiagnostics"]); - checkConsoleClearingUsingCommandLineOptions("with --preserveWatchOutput", ["--preserveWatchOutput"]); + checkConsoleClearingUsingCommandLineOptions("without --diagnostics or --extendedDiagnostics"); + checkConsoleClearingUsingCommandLineOptions("with --diagnostics", ["--diagnostics"]); + checkConsoleClearingUsingCommandLineOptions("with --extendedDiagnostics", ["--extendedDiagnostics"]); + checkConsoleClearingUsingCommandLineOptions("with --preserveWatchOutput", ["--preserveWatchOutput"]); - describe("when preserveWatchOutput is true in config file", () => { - const compilerOptions: CompilerOptions = { - preserveWatchOutput: true - }; - const configFile: File = { - path: "/tsconfig.json", - content: JSON.stringify({ compilerOptions }) - }; - const files = [file, configFile, libFile]; - it("using createWatchOfConfigFile ", () => { - const baseline = createBaseline(createWatchedSystem(files)); - const watch = createWatchOfConfigFile(configFile.path, baseline.sys); - // Initially console is cleared if --preserveOutput is not provided since the config file is yet to be parsed - runWatchBaseline({ - scenario, - subScenario: "when preserveWatchOutput is true in config file/createWatchOfConfigFile", - commandLineArgs: ["--w", "-p", configFile.path], - ...baseline, - getPrograms: () => [[watch.getCurrentProgram().getProgram(), watch.getCurrentProgram()]], - changes: makeChangeToFile, - watchOrSolution: watch - }); - }); - verifyTscWatch({ + describe("when preserveWatchOutput is true in config file", () => { + const compilerOptions: CompilerOptions = { + preserveWatchOutput: true + }; + const configFile: File = { + path: "/tsconfig.json", + content: JSON.stringify({ compilerOptions }) + }; + const files = [file, configFile, libFile]; + it("using createWatchOfConfigFile ", () => { + const baseline = createBaseline(createWatchedSystem(files)); + const watch = createWatchOfConfigFile(configFile.path, baseline.sys); + // Initially console is cleared if --preserveOutput is not provided since the config file is yet to be parsed + runWatchBaseline({ scenario, - subScenario: "when preserveWatchOutput is true in config file/when createWatchProgram is invoked with configFileParseResult on WatchCompilerHostOfConfigFile", + subScenario: "when preserveWatchOutput is true in config file/createWatchOfConfigFile", commandLineArgs: ["--w", "-p", configFile.path], - sys: () => createWatchedSystem(files), + ...baseline, + getPrograms: () => [[watch.getCurrentProgram().getProgram(), watch.getCurrentProgram()]], changes: makeChangeToFile, + watchOrSolution: watch }); }); + verifyTscWatch({ + scenario, + subScenario: "when preserveWatchOutput is true in config file/when createWatchProgram is invoked with configFileParseResult on WatchCompilerHostOfConfigFile", + commandLineArgs: ["--w", "-p", configFile.path], + sys: () => createWatchedSystem(files), + changes: makeChangeToFile, + }); }); +}); } diff --git a/src/testRunner/unittests/tscWatch/emit.ts b/src/testRunner/unittests/tscWatch/emit.ts index 217d4c059a5ba..3244858b23476 100644 --- a/src/testRunner/unittests/tscWatch/emit.ts +++ b/src/testRunner/unittests/tscWatch/emit.ts @@ -1,529 +1,529 @@ namespace ts.tscWatch { - const scenario = "emit"; - describe("unittests:: tsc-watch:: emit with outFile or out setting", () => { - function verifyOutAndOutFileSetting(subScenario: string, out?: string, outFile?: string) { - verifyTscWatch({ - scenario, - subScenario: `emit with outFile or out setting/${subScenario}`, - commandLineArgs: ["--w", "-p", "/a/tsconfig.json"], - sys: () => { - const config: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ compilerOptions: { out, outFile } }) - }; - const f1: File = { - path: "/a/a.ts", - content: "let x = 1" - }; - const f2: File = { - path: "/a/b.ts", - content: "let y = 1" - }; - return createWatchedSystem([f1, f2, config, libFile]); - }, - changes: [ - { - caption: "Make change in the file", - change: sys => sys.writeFile("/a/a.ts", "let x = 11"), - timeouts: runQueuedTimeoutCallbacks - }, - { - caption: "Make change in the file again", - change: sys => sys.writeFile("/a/a.ts", "let xy = 11"), - timeouts: runQueuedTimeoutCallbacks - } - ] - }); - } - verifyOutAndOutFileSetting("config does not have out or outFile"); - verifyOutAndOutFileSetting("config has out", "/a/out.js"); - verifyOutAndOutFileSetting("config has outFile", /*out*/ undefined, "/a/out.js"); - - function verifyFilesEmittedOnce(subScenario: string, useOutFile: boolean) { - verifyTscWatch({ - scenario, - subScenario: `emit with outFile or out setting/${subScenario}`, - commandLineArgs: ["--w", "-p", "/a/b/project/tsconfig.json"], - sys: () => { - const file1: File = { - path: "/a/b/output/AnotherDependency/file1.d.ts", - content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }" - }; - const file2: File = { - path: "/a/b/dependencies/file2.d.ts", - content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }" - }; - const file3: File = { - path: "/a/b/project/src/main.ts", - content: "namespace Main { export function fooBar() {} }" - }; - const file4: File = { - path: "/a/b/project/src/main2.ts", - content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }" - }; - const configFile: File = { - path: "/a/b/project/tsconfig.json", - content: JSON.stringify({ - compilerOptions: useOutFile ? - { outFile: "../output/common.js", target: "es5" } : - { outDir: "../output", target: "es5" }, - files: [file1.path, file2.path, file3.path, file4.path] - }) - }; - return createWatchedSystem([file1, file2, file3, file4, libFile, configFile]); - }, - changes: emptyArray - }); - } - verifyFilesEmittedOnce("with --outFile and multiple declaration files in the program", /*useOutFile*/ true); - verifyFilesEmittedOnce("without --outFile and multiple declaration files in the program", /*useOutFile*/ false); - }); - - describe("unittests:: tsc-watch:: emit for configured projects", () => { - const file1Consumer1Path = "/a/b/file1Consumer1.ts"; - const file1Consumer2Path = "/a/b/file1Consumer2.ts"; - const moduleFile1Path = "/a/b/moduleFile1.ts"; - const moduleFile2Path = "/a/b/moduleFile2.ts"; - const globalFilePath = "/a/b/globalFile3.ts"; - const configFilePath = "/a/b/tsconfig.json"; - interface VerifyTscWatchEmit { - subScenario: string; - /** custom config file options */ - configObj?: any; - /** Additional files and folders to add */ - getAdditionalFileOrFolder?: () => File[]; - /** initial list of files to emit if not the default list */ - firstReloadFileList?: string[]; - changes: TscWatchCompileChange[] - } - function verifyTscWatchEmit({ - subScenario, - configObj, - getAdditionalFileOrFolder, - firstReloadFileList, - changes - }: VerifyTscWatchEmit) { - verifyTscWatch({ - scenario, - subScenario: `emit for configured projects/${subScenario}`, - commandLineArgs: ["--w", "-p", configFilePath], - sys: () => { - const moduleFile1: File = { - path: moduleFile1Path, - content: "export function Foo() { };", - }; - - const file1Consumer1: File = { - path: file1Consumer1Path, - content: `import {Foo} from "./moduleFile1"; export var y = 10;`, - }; - - const file1Consumer2: File = { - path: file1Consumer2Path, - content: `import {Foo} from "./moduleFile1"; let z = 10;`, - }; - - const moduleFile2: File = { - path: moduleFile2Path, - content: `export var Foo4 = 10;`, - }; - - const globalFile3: File = { - path: globalFilePath, - content: `interface GlobalFoo { age: number }` - }; - const configFile: File = { - path: configFilePath, - content: JSON.stringify(configObj || {}) - }; - const additionalFiles = getAdditionalFileOrFolder?.() || emptyArray; - const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles]; - return createWatchedSystem(firstReloadFileList ? - map(firstReloadFileList, fileName => find(files, file => file.path === fileName)!) : - files - ); - }, - changes - }); - } - - function modifyModuleFile1Shape(sys: WatchedSystem) { - sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { };`); - } - const changeModuleFile1Shape: TscWatchCompileChange = { - caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { };`", - change: modifyModuleFile1Shape, - timeouts: checkSingleTimeoutQueueLengthAndRun, - }; - - verifyTscWatchEmit({ - subScenario: "should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", - changes: [ - changeModuleFile1Shape, - { - caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };`", - change: sys => sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { console.log('hi'); };`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatchEmit({ - subScenario: "should be up-to-date with the reference map changes", - changes: [ - { - caption: "Change file1Consumer1 content to `export let y = Foo();`", - change: sys => sys.writeFile(file1Consumer1Path, `export let y = Foo();`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - changeModuleFile1Shape, - { - caption: "Add the import statements back to file1Consumer1", - change: sys => sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };`", - change: sys => sys.writeFile(moduleFile1Path, `export let y = Foo();`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Multiple file edits in one go", - // Change file1Consumer1 content to `export let y = Foo();` - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - change: sys => { - sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`); - modifyModuleFile1Shape(sys); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatchEmit({ - subScenario: "should be up-to-date with deleted files", - changes: [ - { - caption: "change moduleFile1 shape and delete file1Consumer2", - change: sys => { - modifyModuleFile1Shape(sys); - sys.deleteFile(file1Consumer2Path); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatchEmit({ - subScenario: "should be up-to-date with newly created files", - changes: [ - { - caption: "change moduleFile1 shape and create file1Consumer3", - change: sys => { - sys.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`); - modifyModuleFile1Shape(sys); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatchEmit({ - subScenario: "should detect changes in non-root files", - configObj: { files: [file1Consumer1Path] }, - changes: [ - changeModuleFile1Shape, - { - caption: "change file1 internal, and verify only file1 is affected", - change: sys => sys.appendFile(moduleFile1Path, "var T1: number;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatchEmit({ - subScenario: "should return all files if a global file changed shape", - changes: [ - { - caption: "change shape of global file", - change: sys => sys.appendFile(globalFilePath, "var T2: string;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatchEmit({ - subScenario: "should always return the file itself if '--isolatedModules' is specified", - configObj: { compilerOptions: { isolatedModules: true } }, - changes: [ - changeModuleFile1Shape - ] - }); - - verifyTscWatchEmit({ - subScenario: "should always return the file itself if '--out' or '--outFile' is specified", - configObj: { compilerOptions: { module: "system", outFile: "/a/b/out.js" } }, - changes: [ - changeModuleFile1Shape - ] - }); - - verifyTscWatchEmit({ - subScenario: "should return cascaded affected file list", - getAdditionalFileOrFolder: () => [{ - path: "/a/b/file1Consumer1Consumer1.ts", - content: `import {y} from "./file1Consumer1";` - }], - changes: [ - { - caption: "change file1Consumer1", - change: sys => sys.appendFile(file1Consumer1Path, "export var T: number;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - changeModuleFile1Shape, - { - caption: "change file1Consumer1 and moduleFile1", - change: sys => { - sys.appendFile(file1Consumer1Path, "export var T2: number;"); - sys.writeFile(moduleFile1Path, `export var T2: number;export function Foo() { };`); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatchEmit({ - subScenario: "should work fine for files with circular references", - getAdditionalFileOrFolder: () => [ - { - path: "/a/b/file1.ts", - content: `/// -export var t1 = 10;` - }, - { - path: "/a/b/file2.ts", - content: `/// -export var t2 = 10;` - } - ], - firstReloadFileList: [libFile.path, "/a/b/file1.ts", "/a/b/file2.ts", configFilePath], - changes: [ - { - caption: "change file1", - change: sys => sys.appendFile("/a/b/file1.ts", "export var t3 = 10;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatchEmit({ - subScenario: "should detect removed code file", - getAdditionalFileOrFolder: () => [{ - path: "/a/b/referenceFile1.ts", - content: `/// -export var x = Foo();` - }], - firstReloadFileList: [libFile.path, "/a/b/referenceFile1.ts", moduleFile1Path, configFilePath], - changes: [ - { - caption: "delete moduleFile1", - change: sys => sys.deleteFile(moduleFile1Path), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatchEmit({ - subScenario: "should detect non existing code file", - getAdditionalFileOrFolder: () => [{ - path: "/a/b/referenceFile1.ts", - content: `/// -export var x = Foo();` - }], - firstReloadFileList: [libFile.path, "/a/b/referenceFile1.ts", configFilePath], - changes: [ - { - caption: "edit refereceFile1", - change: sys => sys.appendFile("/a/b/referenceFile1.ts", "export var yy = Foo();"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "create moduleFile2", - change: sys => sys.writeFile(moduleFile2Path, "export var Foo4 = 10;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - }); - - describe("unittests:: tsc-watch:: emit file content", () => { - function verifyNewLine(subScenario: string, newLine: string) { - verifyTscWatch({ - scenario, - subScenario: `emit file content/${subScenario}`, - commandLineArgs: ["--w", "/a/app.ts"], - sys: () => createWatchedSystem( - [ - { - path: "/a/app.ts", - content: ["var x = 1;", "var y = 2;"].join(newLine) - }, - libFile - ], - { newLine } - ), - changes: [ - { - caption: "Append a line", - change: sys => sys.appendFile("/a/app.ts", newLine + "var z = 3;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ], - }); - } - verifyNewLine("handles new lines lineFeed", "\n"); - verifyNewLine("handles new lines carriageReturn lineFeed", "\r\n"); - +const scenario = "emit"; +describe("unittests:: tsc-watch:: emit with outFile or out setting", () => { + function verifyOutAndOutFileSetting(subScenario: string, out?: string, outFile?: string) { verifyTscWatch({ scenario, - subScenario: "emit file content/should emit specified file", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + subScenario: `emit with outFile or out setting/${subScenario}`, + commandLineArgs: ["--w", "-p", "/a/tsconfig.json"], sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export function Foo() { return 10; }` + const config: File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ compilerOptions: { out, outFile } }) }; - - const file2 = { - path: "/a/b/f2.ts", - content: `import {Foo} from "./f1"; export let y = Foo();` + const f1: File = { + path: "/a/a.ts", + content: "let x = 1" }; - - const file3 = { - path: "/a/b/f3.ts", - content: `import {y} from "./f2"; let x = y;` - }; - - const configFile = { - path: "/a/b/tsconfig.json", - content: "{}" + const f2: File = { + path: "/a/b.ts", + content: "let y = 1" }; - return createWatchedSystem([file1, file2, file3, configFile, libFile]); + return createWatchedSystem([f1, f2, config, libFile]); }, changes: [ { - caption: "Append content to f1", - change: sys => sys.appendFile("/a/b/f1.ts", "export function foo2() { return 2; }"), - timeouts: checkSingleTimeoutQueueLengthAndRun, + caption: "Make change in the file", + change: sys => sys.writeFile("/a/a.ts", "let x = 11"), + timeouts: runQueuedTimeoutCallbacks }, { - caption: "Again Append content to f1", - change: sys => sys.appendFile("/a/b/f1.ts", "export function fooN() { return 2; }"), - timeouts: checkSingleTimeoutQueueLengthAndRun, + caption: "Make change in the file again", + change: sys => sys.writeFile("/a/a.ts", "let xy = 11"), + timeouts: runQueuedTimeoutCallbacks } - ], + ] }); + } + verifyOutAndOutFileSetting("config does not have out or outFile"); + verifyOutAndOutFileSetting("config has out", "/a/out.js"); + verifyOutAndOutFileSetting("config has outFile", /*out*/ undefined, "/a/out.js"); + function verifyFilesEmittedOnce(subScenario: string, useOutFile: boolean) { verifyTscWatch({ scenario, - subScenario: "emit file content/elides const enums correctly in incremental compilation", - commandLineArgs: ["-w", "/user/someone/projects/myproject/file3.ts"], + subScenario: `emit with outFile or out setting/${subScenario}`, + commandLineArgs: ["--w", "-p", "/a/b/project/tsconfig.json"], sys: () => { - const currentDirectory = "/user/someone/projects/myproject"; const file1: File = { - path: `${currentDirectory}/file1.ts`, - content: "export const enum E1 { V = 1 }" + path: "/a/b/output/AnotherDependency/file1.d.ts", + content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }" }; const file2: File = { - path: `${currentDirectory}/file2.ts`, - content: `import { E1 } from "./file1"; export const enum E2 { V = E1.V }` + path: "/a/b/dependencies/file2.d.ts", + content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }" }; const file3: File = { - path: `${currentDirectory}/file3.ts`, - content: `import { E2 } from "./file2"; const v: E2 = E2.V;` + path: "/a/b/project/src/main.ts", + content: "namespace Main { export function fooBar() {} }" }; - return createWatchedSystem([file1, file2, file3, libFile]); - }, - changes: [ - { - caption: "Append content to file3", - change: sys => sys.appendFile("/user/someone/projects/myproject/file3.ts", "function foo2() { return 2; }"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ], - }); - - verifyTscWatch({ - scenario, - subScenario: "emit file content/file is deleted and created as part of change", - commandLineArgs: ["-w"], - sys: () => { - const projectLocation = "/home/username/project"; - const file: File = { - path: `${projectLocation}/app/file.ts`, - content: "var a = 10;" + const file4: File = { + path: "/a/b/project/src/main2.ts", + content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }" }; const configFile: File = { - path: `${projectLocation}/tsconfig.json`, + path: "/a/b/project/tsconfig.json", content: JSON.stringify({ - include: [ - "app/**/*.ts" - ] + compilerOptions: useOutFile ? + { outFile: "../output/common.js", target: "es5" } : + { outDir: "../output", target: "es5" }, + files: [file1.path, file2.path, file3.path, file4.path] }) }; - const files = [file, configFile, libFile]; - return createWatchedSystem(files, { currentDirectory: projectLocation, useCaseSensitiveFileNames: true }); + return createWatchedSystem([file1, file2, file3, file4, libFile, configFile]); }, - changes: [ - { - caption: "file is deleted and then created to modify content", - change: sys => sys.appendFile("/home/username/project/app/file.ts", "\nvar b = 10;", { invokeFileDeleteCreateAsPartInsteadOfChange: true }), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] + changes: emptyArray }); - }); + } + verifyFilesEmittedOnce("with --outFile and multiple declaration files in the program", /*useOutFile*/ true); + verifyFilesEmittedOnce("without --outFile and multiple declaration files in the program", /*useOutFile*/ false); +}); - describe("unittests:: tsc-watch:: emit with when module emit is specified as node", () => { +describe("unittests:: tsc-watch:: emit for configured projects", () => { + const file1Consumer1Path = "/a/b/file1Consumer1.ts"; + const file1Consumer2Path = "/a/b/file1Consumer2.ts"; + const moduleFile1Path = "/a/b/moduleFile1.ts"; + const moduleFile2Path = "/a/b/moduleFile2.ts"; + const globalFilePath = "/a/b/globalFile3.ts"; + const configFilePath = "/a/b/tsconfig.json"; + interface VerifyTscWatchEmit { + subScenario: string; + /** custom config file options */ + configObj?: any; + /** Additional files and folders to add */ + getAdditionalFileOrFolder?: () => File[]; + /** initial list of files to emit if not the default list */ + firstReloadFileList?: string[]; + changes: TscWatchCompileChange[] + } + function verifyTscWatchEmit({ + subScenario, + configObj, + getAdditionalFileOrFolder, + firstReloadFileList, + changes + }: VerifyTscWatchEmit) { verifyTscWatch({ scenario, - subScenario: "when module emit is specified as node/when instead of filechanged recursive directory watcher is invoked", - commandLineArgs: ["--w", "--p", "/a/rootFolder/project/tsconfig.json"], + subScenario: `emit for configured projects/${subScenario}`, + commandLineArgs: ["--w", "-p", configFilePath], sys: () => { - const configFile: File = { - path: "/a/rootFolder/project/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - module: "none", - allowJs: true, - outDir: "Static/scripts/" - }, - include: [ - "Scripts/**/*" - ], - }) + const moduleFile1: File = { + path: moduleFile1Path, + content: "export function Foo() { };", }; - const file1: File = { - path: "/a/rootFolder/project/Scripts/TypeScript.ts", - content: "var z = 10;" + + const file1Consumer1: File = { + path: file1Consumer1Path, + content: `import {Foo} from "./moduleFile1"; export var y = 10;`, }; - const file2: File = { - path: "/a/rootFolder/project/Scripts/Javascript.js", - content: "var zz = 10;" + + const file1Consumer2: File = { + path: file1Consumer2Path, + content: `import {Foo} from "./moduleFile1"; let z = 10;`, + }; + + const moduleFile2: File = { + path: moduleFile2Path, + content: `export var Foo4 = 10;`, + }; + + const globalFile3: File = { + path: globalFilePath, + content: `interface GlobalFoo { age: number }` }; - return createWatchedSystem([configFile, file1, file2, libFile]); + const configFile: File = { + path: configFilePath, + content: JSON.stringify(configObj || {}) + }; + const additionalFiles = getAdditionalFileOrFolder?.() || emptyArray; + const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles]; + return createWatchedSystem(firstReloadFileList ? + map(firstReloadFileList, fileName => find(files, file => file.path === fileName)!) : + files + ); + }, + changes + }); + } + + function modifyModuleFile1Shape(sys: WatchedSystem) { + sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { };`); + } + const changeModuleFile1Shape: TscWatchCompileChange = { + caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { };`", + change: modifyModuleFile1Shape, + timeouts: checkSingleTimeoutQueueLengthAndRun, + }; + + verifyTscWatchEmit({ + subScenario: "should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", + changes: [ + changeModuleFile1Shape, + { + caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };`", + change: sys => sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { console.log('hi'); };`), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + verifyTscWatchEmit({ + subScenario: "should be up-to-date with the reference map changes", + changes: [ + { + caption: "Change file1Consumer1 content to `export let y = Foo();`", + change: sys => sys.writeFile(file1Consumer1Path, `export let y = Foo();`), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + changeModuleFile1Shape, + { + caption: "Add the import statements back to file1Consumer1", + change: sys => sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };`", + change: sys => sys.writeFile(moduleFile1Path, `export let y = Foo();`), + timeouts: checkSingleTimeoutQueueLengthAndRun, }, + { + caption: "Multiple file edits in one go", + // Change file1Consumer1 content to `export let y = Foo();` + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + change: sys => { + sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`); + modifyModuleFile1Shape(sys); + }, + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + verifyTscWatchEmit({ + subScenario: "should be up-to-date with deleted files", + changes: [ + { + caption: "change moduleFile1 shape and delete file1Consumer2", + change: sys => { + modifyModuleFile1Shape(sys); + sys.deleteFile(file1Consumer2Path); + }, + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + verifyTscWatchEmit({ + subScenario: "should be up-to-date with newly created files", + changes: [ + { + caption: "change moduleFile1 shape and create file1Consumer3", + change: sys => { + sys.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`); + modifyModuleFile1Shape(sys); + }, + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + verifyTscWatchEmit({ + subScenario: "should detect changes in non-root files", + configObj: { files: [file1Consumer1Path] }, + changes: [ + changeModuleFile1Shape, + { + caption: "change file1 internal, and verify only file1 is affected", + change: sys => sys.appendFile(moduleFile1Path, "var T1: number;"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + verifyTscWatchEmit({ + subScenario: "should return all files if a global file changed shape", + changes: [ + { + caption: "change shape of global file", + change: sys => sys.appendFile(globalFilePath, "var T2: string;"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + verifyTscWatchEmit({ + subScenario: "should always return the file itself if '--isolatedModules' is specified", + configObj: { compilerOptions: { isolatedModules: true } }, + changes: [ + changeModuleFile1Shape + ] + }); + + verifyTscWatchEmit({ + subScenario: "should always return the file itself if '--out' or '--outFile' is specified", + configObj: { compilerOptions: { module: "system", outFile: "/a/b/out.js" } }, + changes: [ + changeModuleFile1Shape + ] + }); + + verifyTscWatchEmit({ + subScenario: "should return cascaded affected file list", + getAdditionalFileOrFolder: () => [{ + path: "/a/b/file1Consumer1Consumer1.ts", + content: `import {y} from "./file1Consumer1";` + }], + changes: [ + { + caption: "change file1Consumer1", + change: sys => sys.appendFile(file1Consumer1Path, "export var T: number;"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + changeModuleFile1Shape, + { + caption: "change file1Consumer1 and moduleFile1", + change: sys => { + sys.appendFile(file1Consumer1Path, "export var T2: number;"); + sys.writeFile(moduleFile1Path, `export var T2: number;export function Foo() { };`); + }, + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + verifyTscWatchEmit({ + subScenario: "should work fine for files with circular references", + getAdditionalFileOrFolder: () => [ + { + path: "/a/b/file1.ts", + content: `/// +export var t1 = 10;` + }, + { + path: "/a/b/file2.ts", + content: `/// +export var t2 = 10;` + } + ], + firstReloadFileList: [libFile.path, "/a/b/file1.ts", "/a/b/file2.ts", configFilePath], + changes: [ + { + caption: "change file1", + change: sys => sys.appendFile("/a/b/file1.ts", "export var t3 = 10;"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + verifyTscWatchEmit({ + subScenario: "should detect removed code file", + getAdditionalFileOrFolder: () => [{ + path: "/a/b/referenceFile1.ts", + content: `/// +export var x = Foo();` + }], + firstReloadFileList: [libFile.path, "/a/b/referenceFile1.ts", moduleFile1Path, configFilePath], + changes: [ + { + caption: "delete moduleFile1", + change: sys => sys.deleteFile(moduleFile1Path), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + verifyTscWatchEmit({ + subScenario: "should detect non existing code file", + getAdditionalFileOrFolder: () => [{ + path: "/a/b/referenceFile1.ts", + content: `/// +export var x = Foo();` + }], + firstReloadFileList: [libFile.path, "/a/b/referenceFile1.ts", configFilePath], + changes: [ + { + caption: "edit refereceFile1", + change: sys => sys.appendFile("/a/b/referenceFile1.ts", "export var yy = Foo();"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "create moduleFile2", + change: sys => sys.writeFile(moduleFile2Path, "export var Foo4 = 10;"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); +}); + +describe("unittests:: tsc-watch:: emit file content", () => { + function verifyNewLine(subScenario: string, newLine: string) { + verifyTscWatch({ + scenario, + subScenario: `emit file content/${subScenario}`, + commandLineArgs: ["--w", "/a/app.ts"], + sys: () => createWatchedSystem( + [ + { + path: "/a/app.ts", + content: ["var x = 1;", "var y = 2;"].join(newLine) + }, + libFile + ], + { newLine } + ), changes: [ { - caption: "Modify typescript file", - change: sys => sys.modifyFile( - "/a/rootFolder/project/Scripts/TypeScript.ts", - "var zz30 = 100;", - { invokeDirectoryWatcherInsteadOfFileChanged: true }, - ), - timeouts: runQueuedTimeoutCallbacks, + caption: "Append a line", + change: sys => sys.appendFile("/a/app.ts", newLine + "var z = 3;"), + timeouts: checkSingleTimeoutQueueLengthAndRun, } ], }); + } + verifyNewLine("handles new lines lineFeed", "\n"); + verifyNewLine("handles new lines carriageReturn lineFeed", "\r\n"); + + verifyTscWatch({ + scenario, + subScenario: "emit file content/should emit specified file", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export function Foo() { return 10; }` + }; + + const file2 = { + path: "/a/b/f2.ts", + content: `import {Foo} from "./f1"; export let y = Foo();` + }; + + const file3 = { + path: "/a/b/f3.ts", + content: `import {y} from "./f2"; let x = y;` + }; + + const configFile = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + return createWatchedSystem([file1, file2, file3, configFile, libFile]); + }, + changes: [ + { + caption: "Append content to f1", + change: sys => sys.appendFile("/a/b/f1.ts", "export function foo2() { return 2; }"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Again Append content to f1", + change: sys => sys.appendFile("/a/b/f1.ts", "export function fooN() { return 2; }"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ], + }); + + verifyTscWatch({ + scenario, + subScenario: "emit file content/elides const enums correctly in incremental compilation", + commandLineArgs: ["-w", "/user/someone/projects/myproject/file3.ts"], + sys: () => { + const currentDirectory = "/user/someone/projects/myproject"; + const file1: File = { + path: `${currentDirectory}/file1.ts`, + content: "export const enum E1 { V = 1 }" + }; + const file2: File = { + path: `${currentDirectory}/file2.ts`, + content: `import { E1 } from "./file1"; export const enum E2 { V = E1.V }` + }; + const file3: File = { + path: `${currentDirectory}/file3.ts`, + content: `import { E2 } from "./file2"; const v: E2 = E2.V;` + }; + return createWatchedSystem([file1, file2, file3, libFile]); + }, + changes: [ + { + caption: "Append content to file3", + change: sys => sys.appendFile("/user/someone/projects/myproject/file3.ts", "function foo2() { return 2; }"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ], + }); + + verifyTscWatch({ + scenario, + subScenario: "emit file content/file is deleted and created as part of change", + commandLineArgs: ["-w"], + sys: () => { + const projectLocation = "/home/username/project"; + const file: File = { + path: `${projectLocation}/app/file.ts`, + content: "var a = 10;" + }; + const configFile: File = { + path: `${projectLocation}/tsconfig.json`, + content: JSON.stringify({ + include: [ + "app/**/*.ts" + ] + }) + }; + const files = [file, configFile, libFile]; + return createWatchedSystem(files, { currentDirectory: projectLocation, useCaseSensitiveFileNames: true }); + }, + changes: [ + { + caption: "file is deleted and then created to modify content", + change: sys => sys.appendFile("/home/username/project/app/file.ts", "\nvar b = 10;", { invokeFileDeleteCreateAsPartInsteadOfChange: true }), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); +}); + +describe("unittests:: tsc-watch:: emit with when module emit is specified as node", () => { + verifyTscWatch({ + scenario, + subScenario: "when module emit is specified as node/when instead of filechanged recursive directory watcher is invoked", + commandLineArgs: ["--w", "--p", "/a/rootFolder/project/tsconfig.json"], + sys: () => { + const configFile: File = { + path: "/a/rootFolder/project/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + module: "none", + allowJs: true, + outDir: "Static/scripts/" + }, + include: [ + "Scripts/**/*" + ], + }) + }; + const file1: File = { + path: "/a/rootFolder/project/Scripts/TypeScript.ts", + content: "var z = 10;" + }; + const file2: File = { + path: "/a/rootFolder/project/Scripts/Javascript.js", + content: "var zz = 10;" + }; + return createWatchedSystem([configFile, file1, file2, libFile]); + }, + changes: [ + { + caption: "Modify typescript file", + change: sys => sys.modifyFile( + "/a/rootFolder/project/Scripts/TypeScript.ts", + "var zz30 = 100;", + { invokeDirectoryWatcherInsteadOfFileChanged: true }, + ), + timeouts: runQueuedTimeoutCallbacks, + } + ], }); +}); } diff --git a/src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts b/src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts index 598b422d5bab7..30c157130af83 100644 --- a/src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts +++ b/src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts @@ -1,181 +1,181 @@ namespace ts.tscWatch { - describe("unittests:: tsc-watch:: Emit times and Error updates in builder after program changes", () => { - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: `{}` - }; - interface VerifyEmitAndErrorUpdatesWorker extends VerifyEmitAndErrorUpdates { - configFile: () => File; - } - function verifyEmitAndErrorUpdatesWorker({ +describe("unittests:: tsc-watch:: Emit times and Error updates in builder after program changes", () => { + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: `{}` + }; + interface VerifyEmitAndErrorUpdatesWorker extends VerifyEmitAndErrorUpdates { + configFile: () => File; + } + function verifyEmitAndErrorUpdatesWorker({ + subScenario, + files, + currentDirectory, + lib, + configFile, + changes, + baselineIncremental + }: VerifyEmitAndErrorUpdatesWorker) { + verifyTscWatch({ + scenario: "emitAndErrorUpdates", subScenario, - files, - currentDirectory, - lib, - configFile, + commandLineArgs: ["--w"], + sys: () => createWatchedSystem( + [...files(), configFile(), lib?.() || libFile], + { currentDirectory: currentDirectory || projectRoot } + ), changes, baselineIncremental - }: VerifyEmitAndErrorUpdatesWorker) { - verifyTscWatch({ - scenario: "emitAndErrorUpdates", - subScenario, - commandLineArgs: ["--w"], - sys: () => createWatchedSystem( - [...files(), configFile(), lib?.() || libFile], - { currentDirectory: currentDirectory || projectRoot } - ), - changes, - baselineIncremental - }); - verifyTscWatch({ - scenario: "emitAndErrorUpdates", - subScenario: `incremental/${subScenario}`, - commandLineArgs: ["--w", "--i"], - sys: () => createWatchedSystem( - [...files(), configFile(), lib?.() || libFile], - { currentDirectory: currentDirectory || projectRoot } - ), - changes, - baselineIncremental - }); - } + }); + verifyTscWatch({ + scenario: "emitAndErrorUpdates", + subScenario: `incremental/${subScenario}`, + commandLineArgs: ["--w", "--i"], + sys: () => createWatchedSystem( + [...files(), configFile(), lib?.() || libFile], + { currentDirectory: currentDirectory || projectRoot } + ), + changes, + baselineIncremental + }); + } - function changeCompilerOptions(input: VerifyEmitAndErrorUpdates, additionalOptions: CompilerOptions): File { - const configFile = input.configFile?.() || config; - const content = JSON.parse(configFile.content); - content.compilerOptions = { ...content.compilerOptions, ...additionalOptions }; - return { path: configFile.path, content: JSON.stringify(content) }; - } + function changeCompilerOptions(input: VerifyEmitAndErrorUpdates, additionalOptions: CompilerOptions): File { + const configFile = input.configFile?.() || config; + const content = JSON.parse(configFile.content); + content.compilerOptions = { ...content.compilerOptions, ...additionalOptions }; + return { path: configFile.path, content: JSON.stringify(content) }; + } - interface VerifyEmitAndErrorUpdates { - subScenario: string - files: () => File[]; - currentDirectory?: string; - lib?: () => File; - changes: TscWatchCompileChange[]; - configFile?: () => File; - baselineIncremental?: boolean - } - function verifyEmitAndErrorUpdates(input: VerifyEmitAndErrorUpdates) { - verifyEmitAndErrorUpdatesWorker({ - ...input, - subScenario: `default/${input.subScenario}`, - configFile: () => input.configFile?.() || config - }); + interface VerifyEmitAndErrorUpdates { + subScenario: string + files: () => File[]; + currentDirectory?: string; + lib?: () => File; + changes: TscWatchCompileChange[]; + configFile?: () => File; + baselineIncremental?: boolean + } + function verifyEmitAndErrorUpdates(input: VerifyEmitAndErrorUpdates) { + verifyEmitAndErrorUpdatesWorker({ + ...input, + subScenario: `default/${input.subScenario}`, + configFile: () => input.configFile?.() || config + }); - verifyEmitAndErrorUpdatesWorker({ - ...input, - subScenario: `defaultAndD/${input.subScenario}`, - configFile: () => changeCompilerOptions(input, { declaration: true }) - }); + verifyEmitAndErrorUpdatesWorker({ + ...input, + subScenario: `defaultAndD/${input.subScenario}`, + configFile: () => changeCompilerOptions(input, { declaration: true }) + }); - verifyEmitAndErrorUpdatesWorker({ - ...input, - subScenario: `isolatedModules/${input.subScenario}`, - configFile: () => changeCompilerOptions(input, { isolatedModules: true }) - }); + verifyEmitAndErrorUpdatesWorker({ + ...input, + subScenario: `isolatedModules/${input.subScenario}`, + configFile: () => changeCompilerOptions(input, { isolatedModules: true }) + }); - verifyEmitAndErrorUpdatesWorker({ - ...input, - subScenario: `isolatedModulesAndD/${input.subScenario}`, - configFile: () => changeCompilerOptions(input, { isolatedModules: true, declaration: true }) - }); + verifyEmitAndErrorUpdatesWorker({ + ...input, + subScenario: `isolatedModulesAndD/${input.subScenario}`, + configFile: () => changeCompilerOptions(input, { isolatedModules: true, declaration: true }) + }); - verifyEmitAndErrorUpdatesWorker({ - ...input, - subScenario: `assumeChangesOnlyAffectDirectDependencies/${input.subScenario}`, - configFile: () => changeCompilerOptions(input, { assumeChangesOnlyAffectDirectDependencies: true }) - }); + verifyEmitAndErrorUpdatesWorker({ + ...input, + subScenario: `assumeChangesOnlyAffectDirectDependencies/${input.subScenario}`, + configFile: () => changeCompilerOptions(input, { assumeChangesOnlyAffectDirectDependencies: true }) + }); - verifyEmitAndErrorUpdatesWorker({ - ...input, - subScenario: `assumeChangesOnlyAffectDirectDependenciesAndD/${input.subScenario}`, - configFile: () => changeCompilerOptions(input, { assumeChangesOnlyAffectDirectDependencies: true, declaration: true }) - }); - } + verifyEmitAndErrorUpdatesWorker({ + ...input, + subScenario: `assumeChangesOnlyAffectDirectDependenciesAndD/${input.subScenario}`, + configFile: () => changeCompilerOptions(input, { assumeChangesOnlyAffectDirectDependencies: true, declaration: true }) + }); + } - describe("deep import changes", () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `import {B} from './b'; + describe("deep import changes", () => { + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `import {B} from './b'; declare var console: any; let b = new B(); console.log(b.c.d);` - }; + }; - function verifyDeepImportChange(subScenario: string, bFile: File, cFile: File) { - verifyEmitAndErrorUpdates({ - subScenario: `deepImportChanges/${subScenario}`, - files: () => [aFile, bFile, cFile], - changes: [ - { - caption: "Rename property d to d2 of class C to initialize signatures", - change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rename property d2 to d of class C to revert back to original text", - change: sys => sys.writeFile(cFile.path, cFile.content.replace("d2", "d")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rename property d to d2 of class C", - change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")), - timeouts: runQueuedTimeoutCallbacks, - } - ], - }); - } - describe("updates errors when deep import file changes", () => { - const bFile: File = { - path: `${projectRoot}/b.ts`, - content: `import {C} from './c'; + function verifyDeepImportChange(subScenario: string, bFile: File, cFile: File) { + verifyEmitAndErrorUpdates({ + subScenario: `deepImportChanges/${subScenario}`, + files: () => [aFile, bFile, cFile], + changes: [ + { + caption: "Rename property d to d2 of class C to initialize signatures", + change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "Rename property d2 to d of class C to revert back to original text", + change: sys => sys.writeFile(cFile.path, cFile.content.replace("d2", "d")), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "Rename property d to d2 of class C", + change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")), + timeouts: runQueuedTimeoutCallbacks, + } + ], + }); + } + describe("updates errors when deep import file changes", () => { + const bFile: File = { + path: `${projectRoot}/b.ts`, + content: `import {C} from './c'; export class B { c = new C(); }` - }; - const cFile: File = { - path: `${projectRoot}/c.ts`, - content: `export class C + }; + const cFile: File = { + path: `${projectRoot}/c.ts`, + content: `export class C { d = 1; }` - }; - verifyDeepImportChange( - "errors for .ts change", - bFile, - cFile - ); - }); - describe("updates errors when deep import through declaration file changes", () => { - const bFile: File = { - path: `${projectRoot}/b.d.ts`, - content: `import {C} from './c'; + }; + verifyDeepImportChange( + "errors for .ts change", + bFile, + cFile + ); + }); + describe("updates errors when deep import through declaration file changes", () => { + const bFile: File = { + path: `${projectRoot}/b.d.ts`, + content: `import {C} from './c'; export class B { c: C; }` - }; - const cFile: File = { - path: `${projectRoot}/c.d.ts`, - content: `export class C + }; + const cFile: File = { + path: `${projectRoot}/c.d.ts`, + content: `export class C { d: number; }` - }; - verifyDeepImportChange( - "errors for .d.ts change", - bFile, - cFile - ); - }); + }; + verifyDeepImportChange( + "errors for .d.ts change", + bFile, + cFile + ); }); + }); - describe("updates errors in file not exporting a deep multilevel import that changes", () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `export interface Point { + describe("updates errors in file not exporting a deep multilevel import that changes", () => { + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `export interface Point { name: string; c: Coords; } @@ -183,16 +183,16 @@ export interface Coords { x2: number; y: number; }` - }; - const bFile: File = { - path: `${projectRoot}/b.ts`, - content: `import { Point } from "./a"; + }; + const bFile: File = { + path: `${projectRoot}/b.ts`, + content: `import { Point } from "./a"; export interface PointWrapper extends Point { }` - }; - const cFile: File = { - path: `${projectRoot}/c.ts`, - content: `import { PointWrapper } from "./b"; + }; + const cFile: File = { + path: `${projectRoot}/c.ts`, + content: `import { PointWrapper } from "./b"; export function getPoint(): PointWrapper { return { name: "test", @@ -202,62 +202,62 @@ export function getPoint(): PointWrapper { } } };` - }; - const dFile: File = { - path: `${projectRoot}/d.ts`, - content: `import { getPoint } from "./c"; + }; + const dFile: File = { + path: `${projectRoot}/d.ts`, + content: `import { getPoint } from "./c"; getPoint().c.x;` - }; - const eFile: File = { - path: `${projectRoot}/e.ts`, - content: `import "./d";` - }; - verifyEmitAndErrorUpdates({ - subScenario: "file not exporting a deep multilevel import that changes", - files: () => [aFile, bFile, cFile, dFile, eFile], - changes: [ - { - caption: "Rename property x2 to x of interface Coords to initialize signatures", - change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rename property x to x2 of interface Coords to revert back to original text", - change: sys => sys.writeFile(aFile.path, aFile.content.replace("x: number", "x2: number")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rename property x2 to x of interface Coords", - change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + }; + const eFile: File = { + path: `${projectRoot}/e.ts`, + content: `import "./d";` + }; + verifyEmitAndErrorUpdates({ + subScenario: "file not exporting a deep multilevel import that changes", + files: () => [aFile, bFile, cFile, dFile, eFile], + changes: [ + { + caption: "Rename property x2 to x of interface Coords to initialize signatures", + change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "Rename property x to x2 of interface Coords to revert back to original text", + change: sys => sys.writeFile(aFile.path, aFile.content.replace("x: number", "x2: number")), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "Rename property x2 to x of interface Coords", + change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")), + timeouts: runQueuedTimeoutCallbacks, + }, + ] }); - describe("updates errors when file transitively exported file changes", () => { - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - files: ["app.ts"], - compilerOptions: { baseUrl: "." } - }) - }; - const app: File = { - path: `${projectRoot}/app.ts`, - content: `import { Data } from "lib2/public"; + }); + describe("updates errors when file transitively exported file changes", () => { + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + files: ["app.ts"], + compilerOptions: { baseUrl: "." } + }) + }; + const app: File = { + path: `${projectRoot}/app.ts`, + content: `import { Data } from "lib2/public"; export class App { public constructor() { new Data().test(); } }` - }; - const lib2Public: File = { - path: `${projectRoot}/lib2/public.ts`, - content: `export * from "./data";` - }; - const lib2Data: File = { - path: `${projectRoot}/lib2/data.ts`, - content: `import { ITest } from "lib1/public"; + }; + const lib2Public: File = { + path: `${projectRoot}/lib2/public.ts`, + content: `export * from "./data";` + }; + const lib2Data: File = { + path: `${projectRoot}/lib2/data.ts`, + content: `import { ITest } from "lib1/public"; export class Data { public test() { const result: ITest = { @@ -266,56 +266,56 @@ export class Data { return result; } }` - }; - const lib1Public: File = { - path: `${projectRoot}/lib1/public.ts`, - content: `export * from "./tools/public";` - }; - const lib1ToolsPublic: File = { - path: `${projectRoot}/lib1/tools/public.ts`, - content: `export * from "./tools.interface";` - }; - const lib1ToolsInterface: File = { - path: `${projectRoot}/lib1/tools/tools.interface.ts`, - content: `export interface ITest { + }; + const lib1Public: File = { + path: `${projectRoot}/lib1/public.ts`, + content: `export * from "./tools/public";` + }; + const lib1ToolsPublic: File = { + path: `${projectRoot}/lib1/tools/public.ts`, + content: `export * from "./tools.interface";` + }; + const lib1ToolsInterface: File = { + path: `${projectRoot}/lib1/tools/tools.interface.ts`, + content: `export interface ITest { title: string; }` - }; + }; - function verifyTransitiveExports(subScenario: string, files: readonly File[]) { - verifyEmitAndErrorUpdates({ - subScenario: `transitive exports/${subScenario}`, - files: () => [lib1ToolsInterface, lib1ToolsPublic, app, lib2Public, lib1Public, ...files], - configFile: () => config, - changes: [ - { - caption: "Rename property title to title2 of interface ITest to initialize signatures", - change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rename property title2 to title of interface ITest to revert back to original text", - change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title2", "title")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rename property title to title2 of interface ITest", - change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); - } - describe("when there are no circular import and exports", () => { - verifyTransitiveExports( - "no circular import/export", - [lib2Data] - ); + function verifyTransitiveExports(subScenario: string, files: readonly File[]) { + verifyEmitAndErrorUpdates({ + subScenario: `transitive exports/${subScenario}`, + files: () => [lib1ToolsInterface, lib1ToolsPublic, app, lib2Public, lib1Public, ...files], + configFile: () => config, + changes: [ + { + caption: "Rename property title to title2 of interface ITest to initialize signatures", + change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "Rename property title2 to title of interface ITest to revert back to original text", + change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title2", "title")), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "Rename property title to title2 of interface ITest", + change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")), + timeouts: runQueuedTimeoutCallbacks, + } + ] }); - describe("when there are circular import and exports", () => { - const lib2Data: File = { - path: `${projectRoot}/lib2/data.ts`, - content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2"; + } + describe("when there are no circular import and exports", () => { + verifyTransitiveExports( + "no circular import/export", + [lib2Data] + ); + }); + describe("when there are circular import and exports", () => { + const lib2Data: File = { + path: `${projectRoot}/lib2/data.ts`, + content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2"; export class Data { public dat?: Data2; public test() { const result: ITest = { @@ -324,58 +324,58 @@ export class Data { return result; } }` - }; - const lib2Data2: File = { - path: `${projectRoot}/lib2/data2.ts`, - content: `import { Data } from "./data"; + }; + const lib2Data2: File = { + path: `${projectRoot}/lib2/data2.ts`, + content: `import { Data } from "./data"; export class Data2 { public dat?: Data; }` - }; - verifyTransitiveExports( - "yes circular import/exports", - [lib2Data, lib2Data2] - ); - }); + }; + verifyTransitiveExports( + "yes circular import/exports", + [lib2Data, lib2Data2] + ); }); + }); - describe("with noEmitOnError", () => { - function change(caption: string, content: string): TscWatchCompileChange { - return { - caption, - change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, content), - // build project - timeouts: checkSingleTimeoutQueueLengthAndRun - }; - } - const noChange: TscWatchCompileChange = { - caption: "No change", - change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`)!), + describe("with noEmitOnError", () => { + function change(caption: string, content: string): TscWatchCompileChange { + return { + caption, + change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, content), // build project - timeouts: checkSingleTimeoutQueueLengthAndRun, + timeouts: checkSingleTimeoutQueueLengthAndRun }; - verifyEmitAndErrorUpdates({ - subScenario: "with noEmitOnError", - currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError`, - files: () => ["shared/types/db.ts", "src/main.ts", "src/other.ts"] - .map(f => TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", f)), - lib: () => ({ path: libFile.path, content: libContent }), - configFile: () => TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", "tsconfig.json"), - changes: [ - noChange, - change("Fix Syntax error", `import { A } from "../shared/types/db"; + } + const noChange: TscWatchCompileChange = { + caption: "No change", + change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`)!), + // build project + timeouts: checkSingleTimeoutQueueLengthAndRun, + }; + verifyEmitAndErrorUpdates({ + subScenario: "with noEmitOnError", + currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError`, + files: () => ["shared/types/db.ts", "src/main.ts", "src/other.ts"] + .map(f => TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", f)), + lib: () => ({ path: libFile.path, content: libContent }), + configFile: () => TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", "tsconfig.json"), + changes: [ + noChange, + change("Fix Syntax error", `import { A } from "../shared/types/db"; const a = { lastName: 'sdsd' };`), - change("Semantic Error", `import { A } from "../shared/types/db"; + change("Semantic Error", `import { A } from "../shared/types/db"; const a: string = 10;`), - noChange, - change("Fix Semantic Error", `import { A } from "../shared/types/db"; + noChange, + change("Fix Semantic Error", `import { A } from "../shared/types/db"; const a: string = "hello";`), - noChange, - ], - baselineIncremental: true - }); + noChange, + ], + baselineIncremental: true }); }); +}); } diff --git a/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts b/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts index 5199b780b06e3..b21e3fed5df5b 100644 --- a/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts +++ b/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts @@ -1,94 +1,94 @@ namespace ts.tscWatch { - describe("unittests:: tsc-watch:: forceConsistentCasingInFileNames", () => { - const loggerFile: File = { - path: `${projectRoot}/logger.ts`, - content: `export class logger { }` - }; - const anotherFile: File = { - path: `${projectRoot}/another.ts`, - content: `import { logger } from "./logger"; new logger();` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { forceConsistentCasingInFileNames: true } - }) - }; - - function verifyConsistentFileNames({ subScenario, changes }: { subScenario: string; changes: TscWatchCompileChange[]; }) { - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario, - commandLineArgs: ["--w", "--p", tsconfig.path], - sys: () => createWatchedSystem([loggerFile, anotherFile, tsconfig, libFile, tsconfig]), - changes - }); - } +describe("unittests:: tsc-watch:: forceConsistentCasingInFileNames", () => { + const loggerFile: File = { + path: `${projectRoot}/logger.ts`, + content: `export class logger { }` + }; + const anotherFile: File = { + path: `${projectRoot}/another.ts`, + content: `import { logger } from "./logger"; new logger();` + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { forceConsistentCasingInFileNames: true } + }) + }; - verifyConsistentFileNames({ - subScenario: "when changing module name with different casing", - changes: [ - { - caption: "Change module name from logger to Logger", - change: sys => sys.writeFile(anotherFile.path, anotherFile.content.replace("./logger", "./Logger")), - timeouts: runQueuedTimeoutCallbacks, - } - ] + function verifyConsistentFileNames({ subScenario, changes }: { subScenario: string; changes: TscWatchCompileChange[]; }) { + verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario, + commandLineArgs: ["--w", "--p", tsconfig.path], + sys: () => createWatchedSystem([loggerFile, anotherFile, tsconfig, libFile, tsconfig]), + changes }); + } - verifyConsistentFileNames({ - subScenario: "when renaming file with different casing", - changes: [ - { - caption: "Change name of file from logger to Logger", - change: sys => sys.renameFile(loggerFile.path, `${projectRoot}/Logger.ts`), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + verifyConsistentFileNames({ + subScenario: "when changing module name with different casing", + changes: [ + { + caption: "Change module name from logger to Logger", + change: sys => sys.writeFile(anotherFile.path, anotherFile.content.replace("./logger", "./Logger")), + timeouts: runQueuedTimeoutCallbacks, + } + ] + }); - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario: "when relative information file location changes", - commandLineArgs: ["--w", "--p", ".", "--explainFiles"], - sys: () => { - const moduleA: File = { - path: `${projectRoot}/moduleA.ts`, - content: `import a = require("./ModuleC")` - }; - const moduleB: File = { - path: `${projectRoot}/moduleB.ts`, - content: `import a = require("./moduleC")` - }; - const moduleC: File = { - path: `${projectRoot}/moduleC.ts`, - content: `export const x = 10;` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) - }; - return createWatchedSystem([moduleA, moduleB, moduleC, libFile, tsconfig], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "Prepend a line to moduleA", - change: sys => sys.prependFile(`${projectRoot}/moduleA.ts`, `// some comment + verifyConsistentFileNames({ + subScenario: "when renaming file with different casing", + changes: [ + { + caption: "Change name of file from logger to Logger", + change: sys => sys.renameFile(loggerFile.path, `${projectRoot}/Logger.ts`), + timeouts: runQueuedTimeoutCallbacks, + } + ] + }); + + verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario: "when relative information file location changes", + commandLineArgs: ["--w", "--p", ".", "--explainFiles"], + sys: () => { + const moduleA: File = { + path: `${projectRoot}/moduleA.ts`, + content: `import a = require("./ModuleC")` + }; + const moduleB: File = { + path: `${projectRoot}/moduleB.ts`, + content: `import a = require("./moduleC")` + }; + const moduleC: File = { + path: `${projectRoot}/moduleC.ts`, + content: `export const x = 10;` + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) + }; + return createWatchedSystem([moduleA, moduleB, moduleC, libFile, tsconfig], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "Prepend a line to moduleA", + change: sys => sys.prependFile(`${projectRoot}/moduleA.ts`, `// some comment `), - timeouts: runQueuedTimeoutCallbacks, - } - ], - }); + timeouts: runQueuedTimeoutCallbacks, + } + ], + }); - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario: "jsxImportSource option changed", - commandLineArgs: ["--w", "--p", ".", "--explainFiles"], - sys: () => createWatchedSystem([ - libFile, - { - path: `${projectRoot}/node_modules/react/Jsx-runtime/index.d.ts`, - content: `export namespace JSX { + verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario: "jsxImportSource option changed", + commandLineArgs: ["--w", "--p", ".", "--explainFiles"], + sys: () => createWatchedSystem([ + libFile, + { + path: `${projectRoot}/node_modules/react/Jsx-runtime/index.d.ts`, + content: `export namespace JSX { interface Element {} interface IntrinsicElements { div: { @@ -100,167 +100,167 @@ export function jsx(...args: any[]): void; export function jsxs(...args: any[]): void; export const Fragment: unique symbol; `, - }, - { - path: `${projectRoot}/node_modules/react/package.json`, - content: JSON.stringify({ name: "react", version: "0.0.1" }) - }, - { - path: `${projectRoot}/index.tsx`, - content: `export const App = () =>
;` - }, - { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { jsx: "react-jsx", jsxImportSource: "react", forceConsistentCasingInFileNames: true }, - files: ["node_modules/react/jsx-Runtime/index.d.ts", "index.tsx"] // NB: casing does not match disk - }) - } - ], { currentDirectory: projectRoot }), - changes: emptyArray, - }); + }, + { + path: `${projectRoot}/node_modules/react/package.json`, + content: JSON.stringify({ name: "react", version: "0.0.1" }) + }, + { + path: `${projectRoot}/index.tsx`, + content: `export const App = () =>
;` + }, + { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { jsx: "react-jsx", jsxImportSource: "react", forceConsistentCasingInFileNames: true }, + files: ["node_modules/react/jsx-Runtime/index.d.ts", "index.tsx"] // NB: casing does not match disk + }) + } + ], { currentDirectory: projectRoot }), + changes: emptyArray, + }); - function verifyWindowsStyleRoot(subScenario: string, windowsStyleRoot: string, projectRootRelative: string) { - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario, - commandLineArgs: ["--w", "--p", `${windowsStyleRoot}/${projectRootRelative}`, "--explainFiles"], - sys: () => { - const moduleA: File = { - path: `${windowsStyleRoot}/${projectRootRelative}/a.ts`, - content: ` + function verifyWindowsStyleRoot(subScenario: string, windowsStyleRoot: string, projectRootRelative: string) { + verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario, + commandLineArgs: ["--w", "--p", `${windowsStyleRoot}/${projectRootRelative}`, "--explainFiles"], + sys: () => { + const moduleA: File = { + path: `${windowsStyleRoot}/${projectRootRelative}/a.ts`, + content: ` export const a = 1; export const b = 2; ` - }; - const moduleB: File = { - path: `${windowsStyleRoot}/${projectRootRelative}/b.ts`, - content: ` + }; + const moduleB: File = { + path: `${windowsStyleRoot}/${projectRootRelative}/b.ts`, + content: ` import { a } from "${windowsStyleRoot.toLocaleUpperCase()}/${projectRootRelative}/a" import { b } from "${windowsStyleRoot.toLocaleLowerCase()}/${projectRootRelative}/a" a;b; ` - }; - const tsconfig: File = { - path: `${windowsStyleRoot}/${projectRootRelative}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) - }; - return createWatchedSystem([moduleA, moduleB, libFile, tsconfig], { windowsStyleRoot, useCaseSensitiveFileNames: false }); - }, - changes: [ - { - caption: "Prepend a line to moduleA", - change: sys => sys.prependFile(`${windowsStyleRoot}/${projectRootRelative}/a.ts`, `// some comment + }; + const tsconfig: File = { + path: `${windowsStyleRoot}/${projectRootRelative}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) + }; + return createWatchedSystem([moduleA, moduleB, libFile, tsconfig], { windowsStyleRoot, useCaseSensitiveFileNames: false }); + }, + changes: [ + { + caption: "Prepend a line to moduleA", + change: sys => sys.prependFile(`${windowsStyleRoot}/${projectRootRelative}/a.ts`, `// some comment `), - timeouts: runQueuedTimeoutCallbacks, - } - ], - }); - } + timeouts: runQueuedTimeoutCallbacks, + } + ], + }); + } - verifyWindowsStyleRoot("when Windows-style drive root is lowercase", "c:/", "project"); - verifyWindowsStyleRoot("when Windows-style drive root is uppercase", "C:/", "project"); + verifyWindowsStyleRoot("when Windows-style drive root is lowercase", "c:/", "project"); + verifyWindowsStyleRoot("when Windows-style drive root is uppercase", "C:/", "project"); - function verifyFileSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) { - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario, - commandLineArgs: ["--w", "--p", ".", "--explainFiles"], - sys: () => { - const moduleA: File = { + function verifyFileSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) { + verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario, + commandLineArgs: ["--w", "--p", ".", "--explainFiles"], + sys: () => { + const moduleA: File = { - path: diskPath, - content: ` + path: diskPath, + content: ` export const a = 1; export const b = 2; ` - }; - const symlinkA: SymLink = { - path: `${projectRoot}/link.ts`, - symLink: targetPath, - }; - const moduleB: File = { - path: `${projectRoot}/b.ts`, - content: ` + }; + const symlinkA: SymLink = { + path: `${projectRoot}/link.ts`, + symLink: targetPath, + }; + const moduleB: File = { + path: `${projectRoot}/b.ts`, + content: ` import { a } from "${importedPath}"; import { b } from "./link"; a;b; ` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) - }; - return createWatchedSystem([moduleA, symlinkA, moduleB, libFile, tsconfig], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "Prepend a line to moduleA", - change: sys => sys.prependFile(diskPath, `// some comment + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) + }; + return createWatchedSystem([moduleA, symlinkA, moduleB, libFile, tsconfig], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "Prepend a line to moduleA", + change: sys => sys.prependFile(diskPath, `// some comment `), - timeouts: runQueuedTimeoutCallbacks, - } - ], - }); - } + timeouts: runQueuedTimeoutCallbacks, + } + ], + }); + } - verifyFileSymlink("when both file symlink target and import match disk", `${projectRoot}/XY.ts`, `${projectRoot}/XY.ts`, `./XY`); - verifyFileSymlink("when file symlink target matches disk but import does not", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./XY`); - verifyFileSymlink("when import matches disk but file symlink target does not", `${projectRoot}/XY.ts`, `${projectRoot}/XY.ts`, `./Xy`); - verifyFileSymlink("when import and file symlink target agree but do not match disk", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./Xy`); - verifyFileSymlink("when import, file symlink target, and disk are all different", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./yX`); + verifyFileSymlink("when both file symlink target and import match disk", `${projectRoot}/XY.ts`, `${projectRoot}/XY.ts`, `./XY`); + verifyFileSymlink("when file symlink target matches disk but import does not", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./XY`); + verifyFileSymlink("when import matches disk but file symlink target does not", `${projectRoot}/XY.ts`, `${projectRoot}/XY.ts`, `./Xy`); + verifyFileSymlink("when import and file symlink target agree but do not match disk", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./Xy`); + verifyFileSymlink("when import, file symlink target, and disk are all different", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./yX`); - function verifyDirSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) { - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario, - commandLineArgs: ["--w", "--p", ".", "--explainFiles"], - sys: () => { - const moduleA: File = { + function verifyDirSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) { + verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario, + commandLineArgs: ["--w", "--p", ".", "--explainFiles"], + sys: () => { + const moduleA: File = { - path: `${diskPath}/a.ts`, - content: ` + path: `${diskPath}/a.ts`, + content: ` export const a = 1; export const b = 2; ` - }; - const symlinkA: SymLink = { - path: `${projectRoot}/link`, - symLink: targetPath, - }; - const moduleB: File = { - path: `${projectRoot}/b.ts`, - content: ` + }; + const symlinkA: SymLink = { + path: `${projectRoot}/link`, + symLink: targetPath, + }; + const moduleB: File = { + path: `${projectRoot}/b.ts`, + content: ` import { a } from "${importedPath}/a"; import { b } from "./link/a"; a;b; ` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - // Use outFile because otherwise the real and linked files will have the same output path - content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true, outFile: "out.js", module: "system" } }) - }; - return createWatchedSystem([moduleA, symlinkA, moduleB, libFile, tsconfig], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "Prepend a line to moduleA", - change: sys => sys.prependFile(`${diskPath}/a.ts`, `// some comment + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + // Use outFile because otherwise the real and linked files will have the same output path + content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true, outFile: "out.js", module: "system" } }) + }; + return createWatchedSystem([moduleA, symlinkA, moduleB, libFile, tsconfig], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "Prepend a line to moduleA", + change: sys => sys.prependFile(`${diskPath}/a.ts`, `// some comment `), - timeouts: runQueuedTimeoutCallbacks, - } - ], - }); - } + timeouts: runQueuedTimeoutCallbacks, + } + ], + }); + } - verifyDirSymlink("when both directory symlink target and import match disk", `${projectRoot}/XY`, `${projectRoot}/XY`, `./XY`); - verifyDirSymlink("when directory symlink target matches disk but import does not", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./XY`); - verifyDirSymlink("when import matches disk but directory symlink target does not", `${projectRoot}/XY`, `${projectRoot}/XY`, `./Xy`); - verifyDirSymlink("when import and directory symlink target agree but do not match disk", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./Xy`); - verifyDirSymlink("when import, directory symlink target, and disk are all different", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./yX`); - }); + verifyDirSymlink("when both directory symlink target and import match disk", `${projectRoot}/XY`, `${projectRoot}/XY`, `./XY`); + verifyDirSymlink("when directory symlink target matches disk but import does not", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./XY`); + verifyDirSymlink("when import matches disk but directory symlink target does not", `${projectRoot}/XY`, `${projectRoot}/XY`, `./Xy`); + verifyDirSymlink("when import and directory symlink target agree but do not match disk", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./Xy`); + verifyDirSymlink("when import, directory symlink target, and disk are all different", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./yX`); +}); } diff --git a/src/testRunner/unittests/tscWatch/helpers.ts b/src/testRunner/unittests/tscWatch/helpers.ts index 813615fb869a2..0f2dc42c0ab63 100644 --- a/src/testRunner/unittests/tscWatch/helpers.ts +++ b/src/testRunner/unittests/tscWatch/helpers.ts @@ -1,568 +1,568 @@ namespace ts.tscWatch { - export const projects = `/user/username/projects`; - export const projectRoot = `${projects}/myproject`; - export import WatchedSystem = TestFSWithWatch.TestServerHost; - export type File = TestFSWithWatch.File; - export type SymLink = TestFSWithWatch.SymLink; - export import libFile = TestFSWithWatch.libFile; - export import createWatchedSystem = TestFSWithWatch.createWatchedSystem; - export import checkArray = TestFSWithWatch.checkArray; - export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles; - export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed; - export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; - export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; - export import checkOutputContains = TestFSWithWatch.checkOutputContains; - export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain; - - export const commonFile1: File = { - path: "/a/b/commonFile1.ts", - content: "let x = 1" - }; - export const commonFile2: File = { - path: "/a/b/commonFile2.ts", - content: "let y = 1" - }; - - export function checkProgramActualFiles(program: Program, expectedFiles: readonly string[]) { - checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles); - } - - export function checkProgramRootFiles(program: Program, expectedFiles: readonly string[]) { - checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles); - } +export const projects = `/user/username/projects`; +export const projectRoot = `${projects}/myproject`; +export import WatchedSystem = TestFSWithWatch.TestServerHost; +export type File = TestFSWithWatch.File; +export type SymLink = TestFSWithWatch.SymLink; +export import libFile = TestFSWithWatch.libFile; +export import createWatchedSystem = TestFSWithWatch.createWatchedSystem; +export import checkArray = TestFSWithWatch.checkArray; +export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles; +export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed; +export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; +export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; +export import checkOutputContains = TestFSWithWatch.checkOutputContains; +export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain; + +export const commonFile1: File = { + path: "/a/b/commonFile1.ts", + content: "let x = 1" +}; +export const commonFile2: File = { + path: "/a/b/commonFile2.ts", + content: "let y = 1" +}; + +export function checkProgramActualFiles(program: Program, expectedFiles: readonly string[]) { + checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles); +} - export type Watch = WatchOfConfigFile | WatchOfFilesAndCompilerOptions; +export function checkProgramRootFiles(program: Program, expectedFiles: readonly string[]) { + checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles); +} - export function createWatchOfConfigFile(configFileName: string, system: WatchedSystem, optionsToExtend?: CompilerOptions, watchOptionsToExtend?: WatchOptions) { - const compilerHost = createWatchCompilerHostOfConfigFile({ configFileName, optionsToExtend, watchOptionsToExtend, system }); - return createWatchProgram(compilerHost); - } +export type Watch = WatchOfConfigFile | WatchOfFilesAndCompilerOptions; - export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], system: WatchedSystem, options: CompilerOptions = {}, watchOptions?: WatchOptions) { - const compilerHost = createWatchCompilerHostOfFilesAndCompilerOptions({ rootFiles, options, watchOptions, system }); - return createWatchProgram(compilerHost); - } +export function createWatchOfConfigFile(configFileName: string, system: WatchedSystem, optionsToExtend?: CompilerOptions, watchOptionsToExtend?: WatchOptions) { + const compilerHost = createWatchCompilerHostOfConfigFile({ configFileName, optionsToExtend, watchOptionsToExtend, system }); + return createWatchProgram(compilerHost); +} - const elapsedRegex = /^Elapsed:: \d+(?:\.\d+)?ms/; - const buildVerboseLogRegEx = /^.+ \- /; - export enum HostOutputKind { - Log, - Diagnostic, - WatchDiagnostic - } +export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], system: WatchedSystem, options: CompilerOptions = {}, watchOptions?: WatchOptions) { + const compilerHost = createWatchCompilerHostOfFilesAndCompilerOptions({ rootFiles, options, watchOptions, system }); + return createWatchProgram(compilerHost); +} - export interface HostOutputLog { - kind: HostOutputKind.Log; - expected: string; - caption?: string; - } +const elapsedRegex = /^Elapsed:: \d+(?:\.\d+)?ms/; +const buildVerboseLogRegEx = /^.+ \- /; +export enum HostOutputKind { + Log, + Diagnostic, + WatchDiagnostic +} - export interface HostOutputDiagnostic { - kind: HostOutputKind.Diagnostic; - diagnostic: Diagnostic | string; - } +export interface HostOutputLog { + kind: HostOutputKind.Log; + expected: string; + caption?: string; +} - export interface HostOutputWatchDiagnostic { - kind: HostOutputKind.WatchDiagnostic; - diagnostic: Diagnostic | string; - } +export interface HostOutputDiagnostic { + kind: HostOutputKind.Diagnostic; + diagnostic: Diagnostic | string; +} - export type HostOutput = HostOutputLog | HostOutputDiagnostic | HostOutputWatchDiagnostic; - - export function checkOutputErrors( - host: WatchedSystem, - expected: readonly HostOutput[], - disableConsoleClears?: boolean | undefined - ) { - let screenClears = 0; - const outputs = host.getOutput(); - assert.equal(outputs.length, expected.length, JSON.stringify(outputs)); - let index = 0; - forEach(expected, expected => { - switch (expected.kind) { - case HostOutputKind.Log: - return assertLog(expected); - case HostOutputKind.Diagnostic: - return assertDiagnostic(expected); - case HostOutputKind.WatchDiagnostic: - return assertWatchDiagnostic(expected); - default: - return Debug.assertNever(expected); - } - }); - assert.equal(host.screenClears.length, screenClears, "Expected number of screen clears"); - host.clearOutput(); +export interface HostOutputWatchDiagnostic { + kind: HostOutputKind.WatchDiagnostic; + diagnostic: Diagnostic | string; +} - function isDiagnostic(diagnostic: Diagnostic | string): diagnostic is Diagnostic { - return !!(diagnostic as Diagnostic).messageText; +export type HostOutput = HostOutputLog | HostOutputDiagnostic | HostOutputWatchDiagnostic; + +export function checkOutputErrors( + host: WatchedSystem, + expected: readonly HostOutput[], + disableConsoleClears?: boolean | undefined +) { + let screenClears = 0; + const outputs = host.getOutput(); + assert.equal(outputs.length, expected.length, JSON.stringify(outputs)); + let index = 0; + forEach(expected, expected => { + switch (expected.kind) { + case HostOutputKind.Log: + return assertLog(expected); + case HostOutputKind.Diagnostic: + return assertDiagnostic(expected); + case HostOutputKind.WatchDiagnostic: + return assertWatchDiagnostic(expected); + default: + return Debug.assertNever(expected); } + }); + assert.equal(host.screenClears.length, screenClears, "Expected number of screen clears"); + host.clearOutput(); - function assertDiagnostic({ diagnostic }: HostOutputDiagnostic) { - const expected = isDiagnostic(diagnostic) ? formatDiagnostic(diagnostic, host) : diagnostic; - assert.equal(outputs[index], expected, getOutputAtFailedMessage("Diagnostic", expected)); - index++; - } + function isDiagnostic(diagnostic: Diagnostic | string): diagnostic is Diagnostic { + return !!(diagnostic as Diagnostic).messageText; + } - function getCleanLogString(log: string) { - return log.replace(elapsedRegex, "").replace(buildVerboseLogRegEx, ""); - } + function assertDiagnostic({ diagnostic }: HostOutputDiagnostic) { + const expected = isDiagnostic(diagnostic) ? formatDiagnostic(diagnostic, host) : diagnostic; + assert.equal(outputs[index], expected, getOutputAtFailedMessage("Diagnostic", expected)); + index++; + } - function assertLog({ caption, expected }: HostOutputLog) { - const actual = outputs[index]; - assert.equal(getCleanLogString(actual), getCleanLogString(expected), getOutputAtFailedMessage(caption || "Log", expected)); - index++; - } + function getCleanLogString(log: string) { + return log.replace(elapsedRegex, "").replace(buildVerboseLogRegEx, ""); + } - function assertWatchDiagnostic({ diagnostic }: HostOutputWatchDiagnostic) { - if (isString(diagnostic)) { - assert.equal(outputs[index], diagnostic, getOutputAtFailedMessage("Diagnostic", diagnostic)); - } - else { - const expected = getWatchDiagnosticWithoutDate(diagnostic); - if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) { - assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`); - screenClears++; - } - assert.isTrue(endsWith(outputs[index], expected), getOutputAtFailedMessage("Watch diagnostic", expected)); - } - index++; - } + function assertLog({ caption, expected }: HostOutputLog) { + const actual = outputs[index]; + assert.equal(getCleanLogString(actual), getCleanLogString(expected), getOutputAtFailedMessage(caption || "Log", expected)); + index++; + } - function getOutputAtFailedMessage(caption: string, expectedOutput: string) { - return `Expected ${caption}: ${JSON.stringify(expectedOutput)} at ${index} in ${JSON.stringify(outputs)}`; + function assertWatchDiagnostic({ diagnostic }: HostOutputWatchDiagnostic) { + if (isString(diagnostic)) { + assert.equal(outputs[index], diagnostic, getOutputAtFailedMessage("Diagnostic", diagnostic)); } - - function getWatchDiagnosticWithoutDate(diagnostic: Diagnostic) { - const newLines = contains(screenStartingMessageCodes, diagnostic.code) - ? `${host.newLine}${host.newLine}` - : host.newLine; - return ` - ${flattenDiagnosticMessageText(diagnostic.messageText, host.newLine)}${newLines}`; + else { + const expected = getWatchDiagnosticWithoutDate(diagnostic); + if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) { + assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`); + screenClears++; + } + assert.isTrue(endsWith(outputs[index], expected), getOutputAtFailedMessage("Watch diagnostic", expected)); } + index++; } - export function hostOutputLog(expected: string, caption?: string): HostOutputLog { - return { kind: HostOutputKind.Log, expected, caption }; - } - export function hostOutputDiagnostic(diagnostic: Diagnostic | string): HostOutputDiagnostic { - return { kind: HostOutputKind.Diagnostic, diagnostic }; - } - export function hostOutputWatchDiagnostic(diagnostic: Diagnostic | string): HostOutputWatchDiagnostic { - return { kind: HostOutputKind.WatchDiagnostic, diagnostic }; + function getOutputAtFailedMessage(caption: string, expectedOutput: string) { + return `Expected ${caption}: ${JSON.stringify(expectedOutput)} at ${index} in ${JSON.stringify(outputs)}`; } - export function startingCompilationInWatchMode() { - return hostOutputWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode)); - } - export function foundErrorsWatching(errors: readonly any[]) { - return hostOutputWatchDiagnostic(errors.length === 1 ? - createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes) : - createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length) - ); - } - export function fileChangeDetected() { - return hostOutputWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); + function getWatchDiagnosticWithoutDate(diagnostic: Diagnostic) { + const newLines = contains(screenStartingMessageCodes, diagnostic.code) + ? `${host.newLine}${host.newLine}` + : host.newLine; + return ` - ${flattenDiagnosticMessageText(diagnostic.messageText, host.newLine)}${newLines}`; } +} - export function checkOutputErrorsInitial(host: WatchedSystem, errors: readonly Diagnostic[] | readonly string[], disableConsoleClears?: boolean, logsBeforeErrors?: string[]) { - checkOutputErrors( - host, - [ - startingCompilationInWatchMode(), - ...map(logsBeforeErrors || emptyArray, expected => hostOutputLog(expected, "logBeforeError")), - ...map(errors, hostOutputDiagnostic), - foundErrorsWatching(errors) - ], - disableConsoleClears - ); - } +export function hostOutputLog(expected: string, caption?: string): HostOutputLog { + return { kind: HostOutputKind.Log, expected, caption }; +} +export function hostOutputDiagnostic(diagnostic: Diagnostic | string): HostOutputDiagnostic { + return { kind: HostOutputKind.Diagnostic, diagnostic }; +} +export function hostOutputWatchDiagnostic(diagnostic: Diagnostic | string): HostOutputWatchDiagnostic { + return { kind: HostOutputKind.WatchDiagnostic, diagnostic }; +} - export function checkOutputErrorsIncremental(host: WatchedSystem, errors: readonly Diagnostic[] | readonly string[], disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) { - checkOutputErrors( - host, - [ - ...map(logsBeforeWatchDiagnostic || emptyArray, expected => hostOutputLog(expected, "logsBeforeWatchDiagnostic")), - fileChangeDetected(), - ...map(logsBeforeErrors || emptyArray, expected => hostOutputLog(expected, "logBeforeError")), - ...map(errors, hostOutputDiagnostic), - foundErrorsWatching(errors) - ], - disableConsoleClears - ); - } +export function startingCompilationInWatchMode() { + return hostOutputWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode)); +} +export function foundErrorsWatching(errors: readonly any[]) { + return hostOutputWatchDiagnostic(errors.length === 1 ? + createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes) : + createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length) + ); +} +export function fileChangeDetected() { + return hostOutputWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); +} - export function checkOutputErrorsIncrementalWithExit(host: WatchedSystem, errors: readonly Diagnostic[] | readonly string[], expectedExitCode: ExitStatus, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) { - checkOutputErrors( - host, - [ - ...map(logsBeforeWatchDiagnostic || emptyArray, expected => hostOutputLog(expected, "logsBeforeWatchDiagnostic")), - fileChangeDetected(), - ...map(logsBeforeErrors || emptyArray, expected => hostOutputLog(expected, "logBeforeError")), - ...map(errors, hostOutputDiagnostic), - ], - disableConsoleClears - ); - assert.equal(host.exitCode, expectedExitCode); - } +export function checkOutputErrorsInitial(host: WatchedSystem, errors: readonly Diagnostic[] | readonly string[], disableConsoleClears?: boolean, logsBeforeErrors?: string[]) { + checkOutputErrors( + host, + [ + startingCompilationInWatchMode(), + ...map(logsBeforeErrors || emptyArray, expected => hostOutputLog(expected, "logBeforeError")), + ...map(errors, hostOutputDiagnostic), + foundErrorsWatching(errors) + ], + disableConsoleClears + ); +} - export function checkNormalBuildErrors(host: WatchedSystem, errors: readonly Diagnostic[] | readonly string[], reportErrorSummary?: boolean) { - checkOutputErrors( - host, - [ - ...map(errors, hostOutputDiagnostic), - ...reportErrorSummary ? - [hostOutputWatchDiagnostic(getErrorSummaryText(errors.length, host.newLine))] : - emptyArray - ] - ); - } +export function checkOutputErrorsIncremental(host: WatchedSystem, errors: readonly Diagnostic[] | readonly string[], disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) { + checkOutputErrors( + host, + [ + ...map(logsBeforeWatchDiagnostic || emptyArray, expected => hostOutputLog(expected, "logsBeforeWatchDiagnostic")), + fileChangeDetected(), + ...map(logsBeforeErrors || emptyArray, expected => hostOutputLog(expected, "logBeforeError")), + ...map(errors, hostOutputDiagnostic), + foundErrorsWatching(errors) + ], + disableConsoleClears + ); +} - export function getDiagnosticMessageChain(message: DiagnosticMessage, args?: (string | number)[], next?: DiagnosticMessageChain[]): DiagnosticMessageChain { - let text = getLocaleSpecificMessage(message); - if (args?.length) { - text = formatStringFromArgs(text, args); - } - return { - messageText: text, - category: message.category, - code: message.code, - next - }; - } +export function checkOutputErrorsIncrementalWithExit(host: WatchedSystem, errors: readonly Diagnostic[] | readonly string[], expectedExitCode: ExitStatus, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) { + checkOutputErrors( + host, + [ + ...map(logsBeforeWatchDiagnostic || emptyArray, expected => hostOutputLog(expected, "logsBeforeWatchDiagnostic")), + fileChangeDetected(), + ...map(logsBeforeErrors || emptyArray, expected => hostOutputLog(expected, "logBeforeError")), + ...map(errors, hostOutputDiagnostic), + ], + disableConsoleClears + ); + assert.equal(host.exitCode, expectedExitCode); +} - function isDiagnosticMessageChain(message: DiagnosticMessage | DiagnosticMessageChain): message is DiagnosticMessageChain { - return !!(message as DiagnosticMessageChain).messageText; - } +export function checkNormalBuildErrors(host: WatchedSystem, errors: readonly Diagnostic[] | readonly string[], reportErrorSummary?: boolean) { + checkOutputErrors( + host, + [ + ...map(errors, hostOutputDiagnostic), + ...reportErrorSummary ? + [hostOutputWatchDiagnostic(getErrorSummaryText(errors.length, host.newLine))] : + emptyArray + ] + ); +} - export function getDiagnosticOfFileFrom(file: SourceFile | undefined, start: number | undefined, length: number | undefined, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { - return { - file, - start, - length, - - messageText: isDiagnosticMessageChain(message) ? - message : - getDiagnosticMessageChain(message, args).messageText, - category: message.category, - code: message.code, - }; +export function getDiagnosticMessageChain(message: DiagnosticMessage, args?: (string | number)[], next?: DiagnosticMessageChain[]): DiagnosticMessageChain { + let text = getLocaleSpecificMessage(message); + if (args?.length) { + text = formatStringFromArgs(text, args); } + return { + messageText: text, + category: message.category, + code: message.code, + next + }; +} - export function getDiagnosticWithoutFile(message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { - return getDiagnosticOfFileFrom(/*file*/ undefined, /*start*/ undefined, /*length*/ undefined, message, ...args); - } +function isDiagnosticMessageChain(message: DiagnosticMessage | DiagnosticMessageChain): message is DiagnosticMessageChain { + return !!(message as DiagnosticMessageChain).messageText; +} - export function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { - return getDiagnosticOfFileFrom(file, start, length, message, ...args); - } +export function getDiagnosticOfFileFrom(file: SourceFile | undefined, start: number | undefined, length: number | undefined, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { + return { + file, + start, + length, + + messageText: isDiagnosticMessageChain(message) ? + message : + getDiagnosticMessageChain(message, args).messageText, + category: message.category, + code: message.code, + }; +} - export function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { - return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase())), - start, length, message, ...args); - } +export function getDiagnosticWithoutFile(message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { + return getDiagnosticOfFileFrom(/*file*/ undefined, /*start*/ undefined, /*length*/ undefined, message, ...args); +} - export function getUnknownCompilerOption(program: Program, configFile: File, option: string) { - const quotedOption = `"${option}"`; - return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option); - } +export function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { + return getDiagnosticOfFileFrom(file, start, length, message, ...args); +} - export function getUnknownDidYouMeanCompilerOption(program: Program, configFile: File, option: string, didYouMean: string) { - const quotedOption = `"${option}"`; - return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0_Did_you_mean_1, option, didYouMean); - } +export function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { + return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase())), + start, length, message, ...args); +} - export function getDiagnosticModuleNotFoundOfFile(program: Program, file: File, moduleName: string) { - const quotedModuleName = `"${moduleName}"`; - return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option, moduleName); - } +export function getUnknownCompilerOption(program: Program, configFile: File, option: string) { + const quotedOption = `"${option}"`; + return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option); +} - export function runQueuedTimeoutCallbacks(sys: WatchedSystem) { - sys.runQueuedTimeoutCallbacks(); - } +export function getUnknownDidYouMeanCompilerOption(program: Program, configFile: File, option: string, didYouMean: string) { + const quotedOption = `"${option}"`; + return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0_Did_you_mean_1, option, didYouMean); +} - export function checkSingleTimeoutQueueLengthAndRun(sys: WatchedSystem) { - sys.checkTimeoutQueueLengthAndRun(1); - } +export function getDiagnosticModuleNotFoundOfFile(program: Program, file: File, moduleName: string) { + const quotedModuleName = `"${moduleName}"`; + return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option, moduleName); +} - export function checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys: WatchedSystem) { - sys.checkTimeoutQueueLengthAndRun(1); - sys.checkTimeoutQueueLength(0); - } +export function runQueuedTimeoutCallbacks(sys: WatchedSystem) { + sys.runQueuedTimeoutCallbacks(); +} - export interface TscWatchCompileChange { - caption: string; - change: (sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles) => void; - timeouts: ( - sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles, - programs: readonly CommandLineProgram[], - watchOrSolution: ReturnType - ) => void; - } - export interface TscWatchCheckOptions { - baselineSourceMap?: boolean; - baselineDependencies?: boolean; - } - export interface TscWatchCompileBase extends TscWatchCheckOptions { - scenario: string; - subScenario: string; - commandLineArgs: readonly string[]; - changes: readonly TscWatchCompileChange[]; - } - export interface TscWatchCompile extends TscWatchCompileBase { - sys: () => WatchedSystem; - } +export function checkSingleTimeoutQueueLengthAndRun(sys: WatchedSystem) { + sys.checkTimeoutQueueLengthAndRun(1); +} - export const noopChange: TscWatchCompileChange = { - caption: "No change", - change: noop, - timeouts: sys => sys.checkTimeoutQueueLength(0), - }; +export function checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys: WatchedSystem) { + sys.checkTimeoutQueueLengthAndRun(1); + sys.checkTimeoutQueueLength(0); +} - export type SystemSnap = ReturnType; - function tscWatchCompile(input: TscWatchCompile) { - it("tsc-watch:: Generates files matching the baseline", () => { - const { sys, baseline, oldSnap } = createBaseline(input.sys()); - const { - scenario, subScenario, - commandLineArgs, changes, - baselineSourceMap, baselineDependencies - } = input; - - if (!isWatch(commandLineArgs)) sys.exit = exitCode => sys.exitCode = exitCode; - const { cb, getPrograms } = commandLineCallbacks(sys); - const watchOrSolution = executeCommandLine( - sys, - cb, - commandLineArgs, - ); - runWatchBaseline({ - scenario, - subScenario, - commandLineArgs, - sys, - baseline, - oldSnap, - getPrograms, - baselineSourceMap, - baselineDependencies, - changes, - watchOrSolution - }); +export interface TscWatchCompileChange { + caption: string; + change: (sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles) => void; + timeouts: ( + sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles, + programs: readonly CommandLineProgram[], + watchOrSolution: ReturnType + ) => void; +} +export interface TscWatchCheckOptions { + baselineSourceMap?: boolean; + baselineDependencies?: boolean; +} +export interface TscWatchCompileBase extends TscWatchCheckOptions { + scenario: string; + subScenario: string; + commandLineArgs: readonly string[]; + changes: readonly TscWatchCompileChange[]; +} +export interface TscWatchCompile extends TscWatchCompileBase { + sys: () => WatchedSystem; +} + +export const noopChange: TscWatchCompileChange = { + caption: "No change", + change: noop, + timeouts: sys => sys.checkTimeoutQueueLength(0), +}; + +export type SystemSnap = ReturnType; +function tscWatchCompile(input: TscWatchCompile) { + it("tsc-watch:: Generates files matching the baseline", () => { + const { sys, baseline, oldSnap } = createBaseline(input.sys()); + const { + scenario, subScenario, + commandLineArgs, changes, + baselineSourceMap, baselineDependencies + } = input; + + if (!isWatch(commandLineArgs)) sys.exit = exitCode => sys.exitCode = exitCode; + const { cb, getPrograms } = commandLineCallbacks(sys); + const watchOrSolution = executeCommandLine( + sys, + cb, + commandLineArgs, + ); + runWatchBaseline({ + scenario, + subScenario, + commandLineArgs, + sys, + baseline, + oldSnap, + getPrograms, + baselineSourceMap, + baselineDependencies, + changes, + watchOrSolution }); - } + }); +} - export interface Baseline { - baseline: string[]; - sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles; - oldSnap: SystemSnap; - } +export interface Baseline { + baseline: string[]; + sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles; + oldSnap: SystemSnap; +} - export function createBaseline(system: WatchedSystem): Baseline { - const sys = TestFSWithWatch.changeToHostTrackingWrittenFiles( - fakes.patchHostForBuildInfoReadWrite(system) - ); - const baseline: string[] = []; - baseline.push("Input::"); - sys.diff(baseline); - return { sys, baseline, oldSnap: sys.snap() }; - } +export function createBaseline(system: WatchedSystem): Baseline { + const sys = TestFSWithWatch.changeToHostTrackingWrittenFiles( + fakes.patchHostForBuildInfoReadWrite(system) + ); + const baseline: string[] = []; + baseline.push("Input::"); + sys.diff(baseline); + return { sys, baseline, oldSnap: sys.snap() }; +} - export function applyChange(sys: Baseline["sys"], baseline: Baseline["baseline"], change: TscWatchCompileChange["change"], caption?: TscWatchCompileChange["caption"]) { - const oldSnap = sys.snap(); - baseline.push(`Change::${caption ? " " + caption : ""}`, ""); - change(sys); - baseline.push("Input::"); - sys.diff(baseline, oldSnap); - return sys.snap(); - } +export function applyChange(sys: Baseline["sys"], baseline: Baseline["baseline"], change: TscWatchCompileChange["change"], caption?: TscWatchCompileChange["caption"]) { + const oldSnap = sys.snap(); + baseline.push(`Change::${caption ? " " + caption : ""}`, ""); + change(sys); + baseline.push("Input::"); + sys.diff(baseline, oldSnap); + return sys.snap(); +} - export interface RunWatchBaseline extends Baseline, TscWatchCompileBase { - sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles; - getPrograms: () => readonly CommandLineProgram[]; - watchOrSolution: ReturnType; - } - export function runWatchBaseline({ - scenario, subScenario, commandLineArgs, - getPrograms, sys, baseline, oldSnap, - baselineSourceMap, baselineDependencies, - changes, watchOrSolution - }: RunWatchBaseline) { - baseline.push(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}`); - let programs = watchBaseline({ +export interface RunWatchBaseline extends Baseline, TscWatchCompileBase { + sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles; + getPrograms: () => readonly CommandLineProgram[]; + watchOrSolution: ReturnType; +} +export function runWatchBaseline({ + scenario, subScenario, commandLineArgs, + getPrograms, sys, baseline, oldSnap, + baselineSourceMap, baselineDependencies, + changes, watchOrSolution +}: RunWatchBaseline) { + baseline.push(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}`); + let programs = watchBaseline({ + baseline, + getPrograms, + oldPrograms: emptyArray, + sys, + oldSnap, + baselineSourceMap, + baselineDependencies, + }); + + for (const { caption, change, timeouts } of changes) { + oldSnap = applyChange(sys, baseline, change, caption); + timeouts(sys, programs, watchOrSolution); + programs = watchBaseline({ baseline, getPrograms, - oldPrograms: emptyArray, + oldPrograms: programs, sys, oldSnap, baselineSourceMap, baselineDependencies, }); - - for (const { caption, change, timeouts } of changes) { - oldSnap = applyChange(sys, baseline, change, caption); - timeouts(sys, programs, watchOrSolution); - programs = watchBaseline({ - baseline, - getPrograms, - oldPrograms: programs, - sys, - oldSnap, - baselineSourceMap, - baselineDependencies, - }); - } - Harness.Baseline.runBaseline(`${isBuild(commandLineArgs) ? - isWatch(commandLineArgs) ? "tsbuild/watchMode" : "tsbuild" : - isWatch(commandLineArgs) ? "tscWatch" : "tsc"}/${scenario}/${subScenario.split(" ").join("-")}.js`, baseline.join("\r\n")); } + Harness.Baseline.runBaseline(`${isBuild(commandLineArgs) ? + isWatch(commandLineArgs) ? "tsbuild/watchMode" : "tsbuild" : + isWatch(commandLineArgs) ? "tscWatch" : "tsc"}/${scenario}/${subScenario.split(" ").join("-")}.js`, baseline.join("\r\n")); +} - function isWatch(commandLineArgs: readonly string[]) { - return forEach(commandLineArgs, arg => { - if (arg.charCodeAt(0) !== CharacterCodes.minus) return false; - const option = arg.slice(arg.charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase(); - return option === "watch" || option === "w"; - }); - } +function isWatch(commandLineArgs: readonly string[]) { + return forEach(commandLineArgs, arg => { + if (arg.charCodeAt(0) !== CharacterCodes.minus) return false; + const option = arg.slice(arg.charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase(); + return option === "watch" || option === "w"; + }); +} - export interface WatchBaseline extends Baseline, TscWatchCheckOptions { - oldPrograms: readonly (CommandLineProgram | undefined)[]; - getPrograms: () => readonly CommandLineProgram[]; - } - export function watchBaseline({ baseline, getPrograms, oldPrograms, sys, oldSnap, baselineSourceMap, baselineDependencies }: WatchBaseline) { - if (baselineSourceMap) generateSourceMapBaselineFiles(sys); - sys.serializeOutput(baseline); - const programs = baselinePrograms(baseline, getPrograms, oldPrograms, baselineDependencies); - sys.serializeWatches(baseline); - baseline.push(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}`, ""); - sys.diff(baseline, oldSnap); - sys.writtenFiles.forEach((value, key) => { - assert.equal(value, 1, `Expected to write file ${key} only once`); - }); - sys.writtenFiles.clear(); - return programs; +export interface WatchBaseline extends Baseline, TscWatchCheckOptions { + oldPrograms: readonly (CommandLineProgram | undefined)[]; + getPrograms: () => readonly CommandLineProgram[]; +} +export function watchBaseline({ baseline, getPrograms, oldPrograms, sys, oldSnap, baselineSourceMap, baselineDependencies }: WatchBaseline) { + if (baselineSourceMap) generateSourceMapBaselineFiles(sys); + sys.serializeOutput(baseline); + const programs = baselinePrograms(baseline, getPrograms, oldPrograms, baselineDependencies); + sys.serializeWatches(baseline); + baseline.push(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}`, ""); + sys.diff(baseline, oldSnap); + sys.writtenFiles.forEach((value, key) => { + assert.equal(value, 1, `Expected to write file ${key} only once`); + }); + sys.writtenFiles.clear(); + return programs; +} + +export function baselinePrograms(baseline: string[], getPrograms: () => readonly CommandLineProgram[], oldPrograms: readonly (CommandLineProgram | undefined)[], baselineDependencies: boolean | undefined) { + const programs = getPrograms(); + for (let i = 0; i < programs.length; i++) { + baselineProgram(baseline, programs[i], oldPrograms[i], baselineDependencies); } + return programs; +} - export function baselinePrograms(baseline: string[], getPrograms: () => readonly CommandLineProgram[], oldPrograms: readonly (CommandLineProgram | undefined)[], baselineDependencies: boolean | undefined) { - const programs = getPrograms(); - for (let i = 0; i < programs.length; i++) { - baselineProgram(baseline, programs[i], oldPrograms[i], baselineDependencies); +function baselineProgram(baseline: string[], [program, builderProgram]: CommandLineProgram, oldProgram: CommandLineProgram | undefined, baselineDependencies: boolean | undefined) { + if (program !== oldProgram?.[0]) { + const options = program.getCompilerOptions(); + baseline.push(`Program root files: ${JSON.stringify(program.getRootFileNames())}`); + baseline.push(`Program options: ${JSON.stringify(options)}`); + baseline.push(`Program structureReused: ${(ts as any).StructureIsReused[program.structureIsReused]}`); + baseline.push("Program files::"); + for (const file of program.getSourceFiles()) { + baseline.push(file.fileName); } - return programs; } + else { + baseline.push(`Program: Same as old program`); + } + baseline.push(""); - function baselineProgram(baseline: string[], [program, builderProgram]: CommandLineProgram, oldProgram: CommandLineProgram | undefined, baselineDependencies: boolean | undefined) { - if (program !== oldProgram?.[0]) { - const options = program.getCompilerOptions(); - baseline.push(`Program root files: ${JSON.stringify(program.getRootFileNames())}`); - baseline.push(`Program options: ${JSON.stringify(options)}`); - baseline.push(`Program structureReused: ${(ts as any).StructureIsReused[program.structureIsReused]}`); - baseline.push("Program files::"); + if (!builderProgram) return; + if (builderProgram !== oldProgram?.[1]) { + const state = builderProgram.getState(); + const internalState = state as unknown as BuilderState; + if (state.semanticDiagnosticsPerFile?.size) { + baseline.push("Semantic diagnostics in builder refreshed for::"); for (const file of program.getSourceFiles()) { - baseline.push(file.fileName); + if (!state.semanticDiagnosticsFromOldState || !state.semanticDiagnosticsFromOldState.has(file.resolvedPath)) { + baseline.push(file.fileName); + } } } else { - baseline.push(`Program: Same as old program`); + baseline.push("No cached semantic diagnostics in the builder::"); } - baseline.push(""); - - if (!builderProgram) return; - if (builderProgram !== oldProgram?.[1]) { - const state = builderProgram.getState(); - const internalState = state as unknown as BuilderState; - if (state.semanticDiagnosticsPerFile?.size) { - baseline.push("Semantic diagnostics in builder refreshed for::"); - for (const file of program.getSourceFiles()) { - if (!state.semanticDiagnosticsFromOldState || !state.semanticDiagnosticsFromOldState.has(file.resolvedPath)) { - baseline.push(file.fileName); + if (internalState) { + baseline.push(""); + if (internalState.hasCalledUpdateShapeSignature?.size) { + baseline.push("Shape signatures in builder refreshed for::"); + internalState.hasCalledUpdateShapeSignature.forEach((path: Path) => { + const info = state.fileInfos.get(path); + if(info?.version === info?.signature || !info?.signature) { + baseline.push(path + " (used version)"); } - } + else { + baseline.push(path + " (computed .d.ts)"); + } + }); } else { - baseline.push("No cached semantic diagnostics in the builder::"); - } - if (internalState) { - baseline.push(""); - if (internalState.hasCalledUpdateShapeSignature?.size) { - baseline.push("Shape signatures in builder refreshed for::"); - internalState.hasCalledUpdateShapeSignature.forEach((path: Path) => { - const info = state.fileInfos.get(path); - if(info?.version === info?.signature || !info?.signature) { - baseline.push(path + " (used version)"); - } - else { - baseline.push(path + " (computed .d.ts)"); - } - }); - } - else { - baseline.push("No shapes updated in the builder::"); - } - } - baseline.push(""); - if (!baselineDependencies) return; - baseline.push("Dependencies for::"); - for (const file of builderProgram.getSourceFiles()) { - baseline.push(`${file.fileName}:`); - for (const depenedency of builderProgram.getAllDependencies(file)) { - baseline.push(` ${depenedency}`); - } + baseline.push("No shapes updated in the builder::"); } } - else { - baseline.push(`BuilderProgram: Same as old builder program`); - } baseline.push(""); + if (!baselineDependencies) return; + baseline.push("Dependencies for::"); + for (const file of builderProgram.getSourceFiles()) { + baseline.push(`${file.fileName}:`); + for (const depenedency of builderProgram.getAllDependencies(file)) { + baseline.push(` ${depenedency}`); + } + } } - - export interface VerifyTscWatch extends TscWatchCompile { - baselineIncremental?: boolean; + else { + baseline.push(`BuilderProgram: Same as old builder program`); } - export function verifyTscWatch(input: VerifyTscWatch) { - describe(input.scenario, () => { - describe(input.subScenario, () => { - tscWatchCompile(input); - }); - if (input.baselineIncremental) { - describe(`${input.subScenario} with incremental`, () => { - tscWatchCompile({ - ...input, - subScenario: `${input.subScenario} with incremental`, - commandLineArgs: [...input.commandLineArgs, "--incremental"], - }); - }); - } + baseline.push(""); +} + +export interface VerifyTscWatch extends TscWatchCompile { + baselineIncremental?: boolean; +} +export function verifyTscWatch(input: VerifyTscWatch) { + describe(input.scenario, () => { + describe(input.subScenario, () => { + tscWatchCompile(input); }); - } + if (input.baselineIncremental) { + describe(`${input.subScenario} with incremental`, () => { + tscWatchCompile({ + ...input, + subScenario: `${input.subScenario} with incremental`, + commandLineArgs: [...input.commandLineArgs, "--incremental"], + }); + }); + } + }); +} - export function replaceFileText(sys: WatchedSystem, file: string, searchValue: string | RegExp, replaceValue: string) { - const content = Debug.checkDefined(sys.readFile(file)); - sys.writeFile(file, content.replace(searchValue, replaceValue)); - } +export function replaceFileText(sys: WatchedSystem, file: string, searchValue: string | RegExp, replaceValue: string) { + const content = Debug.checkDefined(sys.readFile(file)); + sys.writeFile(file, content.replace(searchValue, replaceValue)); +} - export function createSolutionBuilder(system: WatchedSystem, rootNames: readonly string[], defaultOptions?: BuildOptions) { - const host = createSolutionBuilderHost(system); - return ts.createSolutionBuilder(host, rootNames, defaultOptions || {}); - } +export function createSolutionBuilder(system: WatchedSystem, rootNames: readonly string[], defaultOptions?: BuildOptions) { + const host = createSolutionBuilderHost(system); + return ts.createSolutionBuilder(host, rootNames, defaultOptions || {}); +} - export function ensureErrorFreeBuild(host: WatchedSystem, rootNames: readonly string[]) { - // ts build should succeed - const solutionBuilder = createSolutionBuilder(host, rootNames, {}); - solutionBuilder.build(); - assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " ")); - } +export function ensureErrorFreeBuild(host: WatchedSystem, rootNames: readonly string[]) { + // ts build should succeed + const solutionBuilder = createSolutionBuilder(host, rootNames, {}); + solutionBuilder.build(); + assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " ")); +} - export function createSystemWithSolutionBuild(solutionRoots: readonly string[], files: readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) { - const sys = createWatchedSystem(files, params); - const originalReadFile = sys.readFile; - const originalWrite = sys.write; - const originalWriteFile = sys.writeFile; - const solutionBuilder = createSolutionBuilder(TestFSWithWatch.changeToHostTrackingWrittenFiles( - fakes.patchHostForBuildInfoReadWrite(sys) - ), solutionRoots, {}); - solutionBuilder.build(); - sys.readFile = originalReadFile; - sys.write = originalWrite; - sys.writeFile = originalWriteFile; - return sys; - } +export function createSystemWithSolutionBuild(solutionRoots: readonly string[], files: readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) { + const sys = createWatchedSystem(files, params); + const originalReadFile = sys.readFile; + const originalWrite = sys.write; + const originalWriteFile = sys.writeFile; + const solutionBuilder = createSolutionBuilder(TestFSWithWatch.changeToHostTrackingWrittenFiles( + fakes.patchHostForBuildInfoReadWrite(sys) + ), solutionRoots, {}); + solutionBuilder.build(); + sys.readFile = originalReadFile; + sys.write = originalWrite; + sys.writeFile = originalWriteFile; + return sys; +} } diff --git a/src/testRunner/unittests/tscWatch/incremental.ts b/src/testRunner/unittests/tscWatch/incremental.ts index 8da2d0c09c4ba..44ad4ca7bfbbf 100644 --- a/src/testRunner/unittests/tscWatch/incremental.ts +++ b/src/testRunner/unittests/tscWatch/incremental.ts @@ -1,290 +1,290 @@ namespace ts.tscWatch { - describe("unittests:: tsc-watch:: emit file --incremental", () => { - const project = "/users/username/projects/project"; +describe("unittests:: tsc-watch:: emit file --incremental", () => { + const project = "/users/username/projects/project"; - const configFile: File = { - path: `${project}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { incremental: true } }) - }; + const configFile: File = { + path: `${project}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { incremental: true } }) + }; - interface VerifyIncrementalWatchEmitInput { - subScenario: string; - files: () => readonly File[]; - optionsToExtend?: readonly string[]; - modifyFs?: (host: WatchedSystem) => void; - } - function verifyIncrementalWatchEmit(input: VerifyIncrementalWatchEmitInput) { - describe(input.subScenario, () => { - it("with tsc --w", () => { - verifyIncrementalWatchEmitWorker(input, /*incremental*/ false); - }); - it("with tsc", () => { - verifyIncrementalWatchEmitWorker(input, /*incremental*/ true); - }); + interface VerifyIncrementalWatchEmitInput { + subScenario: string; + files: () => readonly File[]; + optionsToExtend?: readonly string[]; + modifyFs?: (host: WatchedSystem) => void; + } + function verifyIncrementalWatchEmit(input: VerifyIncrementalWatchEmitInput) { + describe(input.subScenario, () => { + it("with tsc --w", () => { + verifyIncrementalWatchEmitWorker(input, /*incremental*/ false); }); - } + it("with tsc", () => { + verifyIncrementalWatchEmitWorker(input, /*incremental*/ true); + }); + }); + } + + function verifyIncrementalWatchEmitWorker( + { subScenario, files, optionsToExtend, modifyFs }: VerifyIncrementalWatchEmitInput, + incremental: boolean + ) { + const { sys, baseline, oldSnap } = createBaseline(createWatchedSystem(files(), { currentDirectory: project })); + if (incremental) sys.exit = exitCode => sys.exitCode = exitCode; + const argsToPass = [incremental ? "-i" : "-w", ...(optionsToExtend || emptyArray)]; + baseline.push(`${sys.getExecutingFilePath()} ${argsToPass.join(" ")}`); + let oldPrograms: readonly CommandLineProgram[] = emptyArray; + const { cb, getPrograms } = commandLineCallbacks(sys); + build(oldSnap); - function verifyIncrementalWatchEmitWorker( - { subScenario, files, optionsToExtend, modifyFs }: VerifyIncrementalWatchEmitInput, - incremental: boolean - ) { - const { sys, baseline, oldSnap } = createBaseline(createWatchedSystem(files(), { currentDirectory: project })); - if (incremental) sys.exit = exitCode => sys.exitCode = exitCode; - const argsToPass = [incremental ? "-i" : "-w", ...(optionsToExtend || emptyArray)]; - baseline.push(`${sys.getExecutingFilePath()} ${argsToPass.join(" ")}`); - let oldPrograms: readonly CommandLineProgram[] = emptyArray; - const { cb, getPrograms } = commandLineCallbacks(sys); + if (modifyFs) { + const oldSnap = applyChange(sys, baseline, modifyFs); build(oldSnap); + } - if (modifyFs) { - const oldSnap = applyChange(sys, baseline, modifyFs); - build(oldSnap); - } + Harness.Baseline.runBaseline(`${isBuild(argsToPass) ? "tsbuild/watchMode" : "tscWatch"}/incremental/${subScenario.split(" ").join("-")}-${incremental ? "incremental" : "watch"}.js`, baseline.join("\r\n")); - Harness.Baseline.runBaseline(`${isBuild(argsToPass) ? "tsbuild/watchMode" : "tscWatch"}/incremental/${subScenario.split(" ").join("-")}-${incremental ? "incremental" : "watch"}.js`, baseline.join("\r\n")); + function build(oldSnap: SystemSnap) { + const closer = executeCommandLine( + sys, + cb, + argsToPass, + ); + oldPrograms = watchBaseline({ + baseline, + getPrograms, + oldPrograms, + sys, + oldSnap + }); + if (closer) closer.close(); + } + } - function build(oldSnap: SystemSnap) { - const closer = executeCommandLine( - sys, - cb, - argsToPass, - ); - oldPrograms = watchBaseline({ - baseline, - getPrograms, - oldPrograms, - sys, - oldSnap + describe("non module compilation", () => { + const file1: File = { + path: `${project}/file1.ts`, + content: "const x = 10;" + }; + const file2: File = { + path: `${project}/file2.ts`, + content: "const y = 20;" + }; + describe("own file emit without errors", () => { + function verify(subScenario: string, optionsToExtend?: readonly string[]) { + const modifiedFile2Content = file2.content.replace("y", "z").replace("20", "10"); + verifyIncrementalWatchEmit({ + files: () => [libFile, file1, file2, configFile], + optionsToExtend, + subScenario: `own file emit without errors/${subScenario}`, + modifyFs: host => host.writeFile(file2.path, modifiedFile2Content), }); - if (closer) closer.close(); } - } + verify("without commandline options"); + verify("with commandline parameters that are not relative", ["-p", "tsconfig.json"]); + }); - describe("non module compilation", () => { - const file1: File = { - path: `${project}/file1.ts`, - content: "const x = 10;" - }; - const file2: File = { - path: `${project}/file2.ts`, - content: "const y = 20;" - }; - describe("own file emit without errors", () => { - function verify(subScenario: string, optionsToExtend?: readonly string[]) { - const modifiedFile2Content = file2.content.replace("y", "z").replace("20", "10"); - verifyIncrementalWatchEmit({ - files: () => [libFile, file1, file2, configFile], - optionsToExtend, - subScenario: `own file emit without errors/${subScenario}`, - modifyFs: host => host.writeFile(file2.path, modifiedFile2Content), - }); - } - verify("without commandline options"); - verify("with commandline parameters that are not relative", ["-p", "tsconfig.json"]); - }); + verifyIncrementalWatchEmit({ + files: () => [libFile, file1, configFile, { + path: file2.path, + content: `const y: string = 20;` + }], + subScenario: "own file emit with errors", + modifyFs: host => host.writeFile(file1.path, file1.content.replace("x", "z")), + }); - verifyIncrementalWatchEmit({ - files: () => [libFile, file1, configFile, { - path: file2.path, - content: `const y: string = 20;` - }], - subScenario: "own file emit with errors", - modifyFs: host => host.writeFile(file1.path, file1.content.replace("x", "z")), - }); + verifyIncrementalWatchEmit({ + files: () => [libFile, file1, file2, { + path: configFile.path, + content: JSON.stringify({ compilerOptions: { incremental: true, outFile: "out.js" } }) + }], + subScenario: "with --out", + }); + }); - verifyIncrementalWatchEmit({ - files: () => [libFile, file1, file2, { - path: configFile.path, - content: JSON.stringify({ compilerOptions: { incremental: true, outFile: "out.js" } }) - }], - subScenario: "with --out", - }); + describe("module compilation", () => { + const file1: File = { + path: `${project}/file1.ts`, + content: "export const x = 10;" + }; + const file2: File = { + path: `${project}/file2.ts`, + content: "export const y = 20;" + }; + const config: File = { + path: configFile.path, + content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd" } }) + }; + + verifyIncrementalWatchEmit({ + files: () => [libFile, file1, file2, config], + subScenario: "module compilation/own file emit without errors", + modifyFs: host => host.writeFile(file2.path, file2.content.replace("y", "z").replace("20", "10")), }); - describe("module compilation", () => { - const file1: File = { - path: `${project}/file1.ts`, - content: "export const x = 10;" - }; - const file2: File = { - path: `${project}/file2.ts`, - content: "export const y = 20;" - }; - const config: File = { - path: configFile.path, - content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd" } }) + describe("own file emit with errors", () => { + const fileModified: File = { + path: file2.path, + content: `export const y: string = 20;` }; verifyIncrementalWatchEmit({ - files: () => [libFile, file1, file2, config], - subScenario: "module compilation/own file emit without errors", - modifyFs: host => host.writeFile(file2.path, file2.content.replace("y", "z").replace("20", "10")), + files: () => [libFile, file1, fileModified, config], + subScenario: "module compilation/own file emit with errors", + modifyFs: host => host.writeFile(file1.path, file1.content.replace("x = 10", "z = 10")), }); - describe("own file emit with errors", () => { - const fileModified: File = { - path: file2.path, - content: `export const y: string = 20;` - }; - - verifyIncrementalWatchEmit({ - files: () => [libFile, file1, fileModified, config], - subScenario: "module compilation/own file emit with errors", - modifyFs: host => host.writeFile(file1.path, file1.content.replace("x = 10", "z = 10")), + it("verify that state is read correctly", () => { + const system = createWatchedSystem([libFile, file1, fileModified, config], { currentDirectory: project }); + const reportDiagnostic = createDiagnosticReporter(system); + const parsedConfig = parseConfigFileWithSystem("tsconfig.json", {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, reportDiagnostic)!; + performIncrementalCompilation({ + rootNames: parsedConfig.fileNames, + options: parsedConfig.options, + projectReferences: parsedConfig.projectReferences, + configFileParsingDiagnostics: getConfigFileParsingDiagnostics(parsedConfig), + reportDiagnostic, + system }); - it("verify that state is read correctly", () => { - const system = createWatchedSystem([libFile, file1, fileModified, config], { currentDirectory: project }); - const reportDiagnostic = createDiagnosticReporter(system); - const parsedConfig = parseConfigFileWithSystem("tsconfig.json", {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, reportDiagnostic)!; - performIncrementalCompilation({ - rootNames: parsedConfig.fileNames, - options: parsedConfig.options, - projectReferences: parsedConfig.projectReferences, - configFileParsingDiagnostics: getConfigFileParsingDiagnostics(parsedConfig), - reportDiagnostic, - system - }); - - const command = parseConfigFileWithSystem("tsconfig.json", {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, noop)!; - const builderProgram = createIncrementalProgram({ - rootNames: command.fileNames, - options: command.options, - projectReferences: command.projectReferences, - configFileParsingDiagnostics: getConfigFileParsingDiagnostics(command), - host: createIncrementalCompilerHost(command.options, system) - }); - - const state = builderProgram.getState(); - assert.equal(state.changedFilesSet!.size, 0, "changes"); - - assert.equal(state.fileInfos.size, 3, "FileInfo size"); - assert.deepEqual(state.fileInfos.get(libFile.path as Path), { - version: system.createHash(libFile.content), - signature: system.createHash(libFile.content), - affectsGlobalScope: true, - impliedFormat: undefined, - }); - assert.deepEqual(state.fileInfos.get(file1.path as Path), { - version: system.createHash(file1.content), - signature: system.createHash(file1.content), - affectsGlobalScope: undefined, - impliedFormat: undefined, - }); - assert.deepEqual(state.fileInfos.get(file2.path as Path), { - version: system.createHash(fileModified.content), - signature: system.createHash(fileModified.content), - affectsGlobalScope: undefined, - impliedFormat: undefined, - }); + const command = parseConfigFileWithSystem("tsconfig.json", {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, noop)!; + const builderProgram = createIncrementalProgram({ + rootNames: command.fileNames, + options: command.options, + projectReferences: command.projectReferences, + configFileParsingDiagnostics: getConfigFileParsingDiagnostics(command), + host: createIncrementalCompilerHost(command.options, system) + }); - assert.deepEqual(state.compilerOptions, { - incremental: true, - module: ModuleKind.AMD, - configFilePath: config.path - }); + const state = builderProgram.getState(); + assert.equal(state.changedFilesSet!.size, 0, "changes"); - assert.equal(arrayFrom(state.referencedMap!.keys()).length, 0); - assert.equal(arrayFrom(state.exportedModulesMap!.keys()).length, 0); + assert.equal(state.fileInfos.size, 3, "FileInfo size"); + assert.deepEqual(state.fileInfos.get(libFile.path as Path), { + version: system.createHash(libFile.content), + signature: system.createHash(libFile.content), + affectsGlobalScope: true, + impliedFormat: undefined, + }); + assert.deepEqual(state.fileInfos.get(file1.path as Path), { + version: system.createHash(file1.content), + signature: system.createHash(file1.content), + affectsGlobalScope: undefined, + impliedFormat: undefined, + }); + assert.deepEqual(state.fileInfos.get(file2.path as Path), { + version: system.createHash(fileModified.content), + signature: system.createHash(fileModified.content), + affectsGlobalScope: undefined, + impliedFormat: undefined, + }); - assert.equal(state.semanticDiagnosticsPerFile!.size, 3); - assert.deepEqual(state.semanticDiagnosticsPerFile!.get(libFile.path as Path), emptyArray); - assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file1.path as Path), emptyArray); - assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file2.path as Path), [{ - file: state.program!.getSourceFileByPath(file2.path as Path)!, - start: 13, - length: 1, - code: Diagnostics.Type_0_is_not_assignable_to_type_1.code, - category: Diagnostics.Type_0_is_not_assignable_to_type_1.category, - messageText: "Type 'number' is not assignable to type 'string'.", - relatedInformation: undefined, - reportsUnnecessary: undefined, - reportsDeprecated: undefined, - source: undefined, - skippedOn: undefined, - }]); + assert.deepEqual(state.compilerOptions, { + incremental: true, + module: ModuleKind.AMD, + configFilePath: config.path }); - }); - verifyIncrementalWatchEmit({ - files: () => [libFile, file1, file2, { - path: configFile.path, - content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd", outFile: "out.js" } }) - }], - subScenario: "module compilation/with --out", + assert.equal(arrayFrom(state.referencedMap!.keys()).length, 0); + assert.equal(arrayFrom(state.exportedModulesMap!.keys()).length, 0); + + assert.equal(state.semanticDiagnosticsPerFile!.size, 3); + assert.deepEqual(state.semanticDiagnosticsPerFile!.get(libFile.path as Path), emptyArray); + assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file1.path as Path), emptyArray); + assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file2.path as Path), [{ + file: state.program!.getSourceFileByPath(file2.path as Path)!, + start: 13, + length: 1, + code: Diagnostics.Type_0_is_not_assignable_to_type_1.code, + category: Diagnostics.Type_0_is_not_assignable_to_type_1.category, + messageText: "Type 'number' is not assignable to type 'string'.", + relatedInformation: undefined, + reportsUnnecessary: undefined, + reportsDeprecated: undefined, + source: undefined, + skippedOn: undefined, + }]); }); }); verifyIncrementalWatchEmit({ - files: () => { - const config: File = { - path: configFile.path, - content: JSON.stringify({ - compilerOptions: { - incremental: true, - target: "es5", - module: "commonjs", - declaration: true, - emitDeclarationOnly: true - } - }) - }; - const aTs: File = { - path: `${project}/a.ts`, - content: `import { B } from "./b"; + files: () => [libFile, file1, file2, { + path: configFile.path, + content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd", outFile: "out.js" } }) + }], + subScenario: "module compilation/with --out", + }); + }); + + verifyIncrementalWatchEmit({ + files: () => { + const config: File = { + path: configFile.path, + content: JSON.stringify({ + compilerOptions: { + incremental: true, + target: "es5", + module: "commonjs", + declaration: true, + emitDeclarationOnly: true + } + }) + }; + const aTs: File = { + path: `${project}/a.ts`, + content: `import { B } from "./b"; export interface A { b: B; } ` - }; - const bTs: File = { - path: `${project}/b.ts`, - content: `import { C } from "./c"; + }; + const bTs: File = { + path: `${project}/b.ts`, + content: `import { C } from "./c"; export interface B { b: C; } ` - }; - const cTs: File = { - path: `${project}/c.ts`, - content: `import { A } from "./a"; + }; + const cTs: File = { + path: `${project}/c.ts`, + content: `import { A } from "./a"; export interface C { a: A; } ` - }; - const indexTs: File = { - path: `${project}/index.ts`, - content: `export { A } from "./a"; + }; + const indexTs: File = { + path: `${project}/index.ts`, + content: `export { A } from "./a"; export { B } from "./b"; export { C } from "./c"; ` - }; - return [libFile, aTs, bTs, cTs, indexTs, config]; - }, - subScenario: "incremental with circular references", - modifyFs: host => host.writeFile(`${project}/a.ts`, `import { B } from "./b"; + }; + return [libFile, aTs, bTs, cTs, indexTs, config]; + }, + subScenario: "incremental with circular references", + modifyFs: host => host.writeFile(`${project}/a.ts`, `import { B } from "./b"; export interface A { b: B; foo: any; } `) - }); + }); - verifyIncrementalWatchEmit({ - subScenario: "when file with ambient global declaration file is deleted", - files: () => [ - { path: libFile.path, content: libContent }, - { path: `${project}/globals.d.ts`, content: `declare namespace Config { const value: string;} ` }, - { path: `${project}/index.ts`, content: `console.log(Config.value);` }, - { path: configFile.path, content: JSON.stringify({ compilerOptions: { incremental: true, } }) } - ], - modifyFs: host => host.deleteFile(`${project}/globals.d.ts`) - }); + verifyIncrementalWatchEmit({ + subScenario: "when file with ambient global declaration file is deleted", + files: () => [ + { path: libFile.path, content: libContent }, + { path: `${project}/globals.d.ts`, content: `declare namespace Config { const value: string;} ` }, + { path: `${project}/index.ts`, content: `console.log(Config.value);` }, + { path: configFile.path, content: JSON.stringify({ compilerOptions: { incremental: true, } }) } + ], + modifyFs: host => host.deleteFile(`${project}/globals.d.ts`) + }); - describe("with option jsxImportSource", () => { - const jsxImportSourceOptions = { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" }; - const jsxLibraryContent = `export namespace JSX { + describe("with option jsxImportSource", () => { + const jsxImportSourceOptions = { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" }; + const jsxLibraryContent = `export namespace JSX { interface Element {} interface IntrinsicElements { div: { @@ -297,83 +297,83 @@ export function jsxs(...args: any[]): void; export const Fragment: unique symbol; `; - verifyIncrementalWatchEmit({ - subScenario: "jsxImportSource option changed", - files: () => [ - { path: libFile.path, content: libContent }, - { path: `${project}/node_modules/react/jsx-runtime/index.d.ts`, content: jsxLibraryContent }, - { path: `${project}/node_modules/react/package.json`, content: JSON.stringify({ name: "react", version: "0.0.1" }) }, - { path: `${project}/node_modules/preact/jsx-runtime/index.d.ts`, content: jsxLibraryContent.replace("propA", "propB") }, - { path: `${project}/node_modules/preact/package.json`, content: JSON.stringify({ name: "preact", version: "0.0.1" }) }, - { path: `${project}/index.tsx`, content: `export const App = () =>
;` }, - { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) } - ], - modifyFs: host => host.writeFile(configFile.path, JSON.stringify({ compilerOptions: { ...jsxImportSourceOptions, jsxImportSource: "preact" } })), - optionsToExtend: ["--explainFiles"] - }); + verifyIncrementalWatchEmit({ + subScenario: "jsxImportSource option changed", + files: () => [ + { path: libFile.path, content: libContent }, + { path: `${project}/node_modules/react/jsx-runtime/index.d.ts`, content: jsxLibraryContent }, + { path: `${project}/node_modules/react/package.json`, content: JSON.stringify({ name: "react", version: "0.0.1" }) }, + { path: `${project}/node_modules/preact/jsx-runtime/index.d.ts`, content: jsxLibraryContent.replace("propA", "propB") }, + { path: `${project}/node_modules/preact/package.json`, content: JSON.stringify({ name: "preact", version: "0.0.1" }) }, + { path: `${project}/index.tsx`, content: `export const App = () =>
;` }, + { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) } + ], + modifyFs: host => host.writeFile(configFile.path, JSON.stringify({ compilerOptions: { ...jsxImportSourceOptions, jsxImportSource: "preact" } })), + optionsToExtend: ["--explainFiles"] + }); - verifyIncrementalWatchEmit({ - subScenario: "jsxImportSource backing types added", - files: () => [ - { path: libFile.path, content: libContent }, - { path: `${project}/index.tsx`, content: `export const App = () =>
;` }, - { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) } - ], - modifyFs: host => { - host.createDirectory(`${project}/node_modules`); - host.createDirectory(`${project}/node_modules/react`); - host.createDirectory(`${project}/node_modules/react/jsx-runtime`); - host.writeFile(`${project}/node_modules/react/jsx-runtime/index.d.ts`, jsxLibraryContent); - host.writeFile(`${project}/node_modules/react/package.json`, JSON.stringify({ name: "react", version: "0.0.1" })); - } - }); + verifyIncrementalWatchEmit({ + subScenario: "jsxImportSource backing types added", + files: () => [ + { path: libFile.path, content: libContent }, + { path: `${project}/index.tsx`, content: `export const App = () =>
;` }, + { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) } + ], + modifyFs: host => { + host.createDirectory(`${project}/node_modules`); + host.createDirectory(`${project}/node_modules/react`); + host.createDirectory(`${project}/node_modules/react/jsx-runtime`); + host.writeFile(`${project}/node_modules/react/jsx-runtime/index.d.ts`, jsxLibraryContent); + host.writeFile(`${project}/node_modules/react/package.json`, JSON.stringify({ name: "react", version: "0.0.1" })); + } + }); - verifyIncrementalWatchEmit({ - subScenario: "jsxImportSource backing types removed", - files: () => [ - { path: libFile.path, content: libContent }, - { path: `${project}/node_modules/react/jsx-runtime/index.d.ts`, content: jsxLibraryContent }, - { path: `${project}/node_modules/react/package.json`, content: JSON.stringify({ name: "react", version: "0.0.1" }) }, - { path: `${project}/index.tsx`, content: `export const App = () =>
;` }, - { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) } - ], - modifyFs: host => { - host.deleteFile(`${project}/node_modules/react/jsx-runtime/index.d.ts`); - host.deleteFile(`${project}/node_modules/react/package.json`); - } - }); + verifyIncrementalWatchEmit({ + subScenario: "jsxImportSource backing types removed", + files: () => [ + { path: libFile.path, content: libContent }, + { path: `${project}/node_modules/react/jsx-runtime/index.d.ts`, content: jsxLibraryContent }, + { path: `${project}/node_modules/react/package.json`, content: JSON.stringify({ name: "react", version: "0.0.1" }) }, + { path: `${project}/index.tsx`, content: `export const App = () =>
;` }, + { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) } + ], + modifyFs: host => { + host.deleteFile(`${project}/node_modules/react/jsx-runtime/index.d.ts`); + host.deleteFile(`${project}/node_modules/react/package.json`); + } + }); - verifyIncrementalWatchEmit({ - subScenario: "importHelpers backing types removed", - files: () => [ - { path: libFile.path, content: libContent }, - { path: `${project}/node_modules/tslib/index.d.ts`, content: "export function __assign(...args: any[]): any;" }, - { path: `${project}/node_modules/tslib/package.json`, content: JSON.stringify({ name: "tslib", version: "0.0.1" }) }, - { path: `${project}/index.tsx`, content: `export const x = {...{}};` }, - { path: configFile.path, content: JSON.stringify({ compilerOptions: { importHelpers: true } }) } - ], - modifyFs: host => { - host.deleteFile(`${project}/node_modules/tslib/index.d.ts`); - host.deleteFile(`${project}/node_modules/tslib/package.json`); - } - }); + verifyIncrementalWatchEmit({ + subScenario: "importHelpers backing types removed", + files: () => [ + { path: libFile.path, content: libContent }, + { path: `${project}/node_modules/tslib/index.d.ts`, content: "export function __assign(...args: any[]): any;" }, + { path: `${project}/node_modules/tslib/package.json`, content: JSON.stringify({ name: "tslib", version: "0.0.1" }) }, + { path: `${project}/index.tsx`, content: `export const x = {...{}};` }, + { path: configFile.path, content: JSON.stringify({ compilerOptions: { importHelpers: true } }) } + ], + modifyFs: host => { + host.deleteFile(`${project}/node_modules/tslib/index.d.ts`); + host.deleteFile(`${project}/node_modules/tslib/package.json`); + } }); + }); - describe("editing module augmentation", () => { - verifyIncrementalWatchEmit({ - subScenario: "editing module augmentation", - files: () => [ - { path: libFile.path, content: libContent }, - { path: `${project}/node_modules/classnames/index.d.ts`, content: `export interface Result {} export default function classNames(): Result;` }, - { path: `${project}/src/types/classnames.d.ts`, content: `export {}; declare module "classnames" { interface Result { foo } }` }, - { path: `${project}/src/index.ts`, content: `import classNames from "classnames"; classNames().foo;` }, - { path: configFile.path, content: JSON.stringify({ compilerOptions: { module: "commonjs", incremental: true } }) }, - ], - modifyFs: host => { - // delete 'foo' - host.writeFile(`${project}/src/types/classnames.d.ts`, `export {}; declare module "classnames" { interface Result {} }`); - }, - }); + describe("editing module augmentation", () => { + verifyIncrementalWatchEmit({ + subScenario: "editing module augmentation", + files: () => [ + { path: libFile.path, content: libContent }, + { path: `${project}/node_modules/classnames/index.d.ts`, content: `export interface Result {} export default function classNames(): Result;` }, + { path: `${project}/src/types/classnames.d.ts`, content: `export {}; declare module "classnames" { interface Result { foo } }` }, + { path: `${project}/src/index.ts`, content: `import classNames from "classnames"; classNames().foo;` }, + { path: configFile.path, content: JSON.stringify({ compilerOptions: { module: "commonjs", incremental: true } }) }, + ], + modifyFs: host => { + // delete 'foo' + host.writeFile(`${project}/src/types/classnames.d.ts`, `export {}; declare module "classnames" { interface Result {} }`); + }, }); }); +}); } diff --git a/src/testRunner/unittests/tscWatch/nodeNextWatch.ts b/src/testRunner/unittests/tscWatch/nodeNextWatch.ts index a7cb1d4ed949a..c040c781326c0 100644 --- a/src/testRunner/unittests/tscWatch/nodeNextWatch.ts +++ b/src/testRunner/unittests/tscWatch/nodeNextWatch.ts @@ -1,58 +1,58 @@ namespace ts.tscWatch { - describe("unittests:: tsc-watch:: nodeNextWatch:: emit when module emit is specified as nodenext", () => { - verifyTscWatch({ - scenario: "nodenext watch emit", - subScenario: "esm-mode file is edited", - commandLineArgs: ["--w", "--p", "/project/tsconfig.json"], - sys: () => { - const configFile: File = { - path: "/project/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - strict: true, - target: "es2020", - module: "nodenext", - moduleResolution: "nodenext", - outDir: "../dist" - } - }) - }; - const packageFile: File = { - path: "/project/package.json", - content: JSON.stringify({ - name: "some-proj", - version: "1.0.0", - description: "", - type: "module", - main: "index.js", - }) - }; - const file1: File = { - path: "/project/src/index.ts", - content: Utils.dedent` +describe("unittests:: tsc-watch:: nodeNextWatch:: emit when module emit is specified as nodenext", () => { + verifyTscWatch({ + scenario: "nodenext watch emit", + subScenario: "esm-mode file is edited", + commandLineArgs: ["--w", "--p", "/project/tsconfig.json"], + sys: () => { + const configFile: File = { + path: "/project/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + strict: true, + target: "es2020", + module: "nodenext", + moduleResolution: "nodenext", + outDir: "../dist" + } + }) + }; + const packageFile: File = { + path: "/project/package.json", + content: JSON.stringify({ + name: "some-proj", + version: "1.0.0", + description: "", + type: "module", + main: "index.js", + }) + }; + const file1: File = { + path: "/project/src/index.ts", + content: Utils.dedent` import * as Thing from "thing"; Thing.fn();` - }; - const declFile: File = { - path: "/project/src/deps.d.ts", - content: `declare module "thing";` - }; - return createWatchedSystem([configFile, file1, declFile, packageFile, { ...libFile, path: "/a/lib/lib.es2020.full.d.ts" }]); - }, - changes: [ - { - caption: "Modify typescript file", - change: sys => sys.modifyFile( - "/project/src/index.ts", - Utils.dedent` + }; + const declFile: File = { + path: "/project/src/deps.d.ts", + content: `declare module "thing";` + }; + return createWatchedSystem([configFile, file1, declFile, packageFile, { ...libFile, path: "/a/lib/lib.es2020.full.d.ts" }]); + }, + changes: [ + { + caption: "Modify typescript file", + change: sys => sys.modifyFile( + "/project/src/index.ts", + Utils.dedent` import * as Thing from "thing"; Thing.fn();`, - {}, - ), - timeouts: runQueuedTimeoutCallbacks, - } - ], - }); + {}, + ), + timeouts: runQueuedTimeoutCallbacks, + } + ], }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/tscWatch/programUpdates.ts b/src/testRunner/unittests/tscWatch/programUpdates.ts index c7d2e1dec26f7..d826d44e2c4da 100644 --- a/src/testRunner/unittests/tscWatch/programUpdates.ts +++ b/src/testRunner/unittests/tscWatch/programUpdates.ts @@ -1,1019 +1,1019 @@ namespace ts.tscWatch { - describe("unittests:: tsc-watch:: program updates", () => { - const scenario = "programUpdates"; - const configFilePath = "/a/b/tsconfig.json"; - const configFile: File = { - path: configFilePath, - content: `{}` - }; - verifyTscWatch({ - scenario, - subScenario: "create watch without config file", - commandLineArgs: ["-w", "/a/b/c/app.ts"], - sys: () => { - const appFile: File = { - path: "/a/b/c/app.ts", - content: ` +describe("unittests:: tsc-watch:: program updates", () => { + const scenario = "programUpdates"; + const configFilePath = "/a/b/tsconfig.json"; + const configFile: File = { + path: configFilePath, + content: `{}` + }; + verifyTscWatch({ + scenario, + subScenario: "create watch without config file", + commandLineArgs: ["-w", "/a/b/c/app.ts"], + sys: () => { + const appFile: File = { + path: "/a/b/c/app.ts", + content: ` import {f} from "./module" console.log(f) ` - }; + }; - const moduleFile: File = { - path: "/a/b/c/module.d.ts", - content: `export let x: number` - }; - return createWatchedSystem([appFile, moduleFile, libFile]); - }, - changes: emptyArray - }); + const moduleFile: File = { + path: "/a/b/c/module.d.ts", + content: `export let x: number` + }; + return createWatchedSystem([appFile, moduleFile, libFile]); + }, + changes: emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "can handle tsconfig file name with difference casing", - commandLineArgs: ["-w", "-p", "/A/B/tsconfig.json"], - sys: () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: configFilePath, - content: JSON.stringify({ - include: ["app.ts"] - }) - }; - return createWatchedSystem([f1, libFile, config], { useCaseSensitiveFileNames: false }); - }, - changes: emptyArray - }); + verifyTscWatch({ + scenario, + subScenario: "can handle tsconfig file name with difference casing", + commandLineArgs: ["-w", "-p", "/A/B/tsconfig.json"], + sys: () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: configFilePath, + content: JSON.stringify({ + include: ["app.ts"] + }) + }; + return createWatchedSystem([f1, libFile, config], { useCaseSensitiveFileNames: false }); + }, + changes: emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "create configured project without file list", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const configFile: File = { - path: configFilePath, - content: ` + verifyTscWatch({ + scenario, + subScenario: "create configured project without file list", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const configFile: File = { + path: configFilePath, + content: ` { "compilerOptions": {}, "exclude": [ "e" ] }` - }; - const file1: File = { - path: "/a/b/c/f1.ts", - content: "let x = 1" - }; - const file2: File = { - path: "/a/b/d/f2.ts", - content: "let y = 1" - }; - const file3: File = { - path: "/a/b/e/f3.ts", - content: "let z = 1" - }; - return createWatchedSystem([configFile, libFile, file1, file2, file3]); - }, - changes: emptyArray - }); + }; + const file1: File = { + path: "/a/b/c/f1.ts", + content: "let x = 1" + }; + const file2: File = { + path: "/a/b/d/f2.ts", + content: "let y = 1" + }; + const file3: File = { + path: "/a/b/e/f3.ts", + content: "let z = 1" + }; + return createWatchedSystem([configFile, libFile, file1, file2, file3]); + }, + changes: emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "add new files to a configured program without file list", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => createWatchedSystem([commonFile1, libFile, configFile]), - changes: [ - { - caption: "Create commonFile2", - change: sys => sys.writeFile(commonFile2.path, commonFile2.content), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + verifyTscWatch({ + scenario, + subScenario: "add new files to a configured program without file list", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => createWatchedSystem([commonFile1, libFile, configFile]), + changes: [ + { + caption: "Create commonFile2", + change: sys => sys.writeFile(commonFile2.path, commonFile2.content), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "should ignore non-existing files specified in the config file", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const configFile: File = { - path: configFilePath, - content: `{ + verifyTscWatch({ + scenario, + subScenario: "should ignore non-existing files specified in the config file", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const configFile: File = { + path: configFilePath, + content: `{ "compilerOptions": {}, "files": [ "commonFile1.ts", "commonFile3.ts" ] }` - }; - return createWatchedSystem([commonFile1, commonFile2, libFile, configFile]); - }, - changes: emptyArray - }); + }; + return createWatchedSystem([commonFile1, commonFile2, libFile, configFile]); + }, + changes: emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "handle recreated files correctly", - commandLineArgs: ["-w", "-p", configFilePath, "--explainFiles"], - sys: () => { - return createWatchedSystem([libFile, commonFile1, commonFile2, configFile]); + verifyTscWatch({ + scenario, + subScenario: "handle recreated files correctly", + commandLineArgs: ["-w", "-p", configFilePath, "--explainFiles"], + sys: () => { + return createWatchedSystem([libFile, commonFile1, commonFile2, configFile]); + }, + changes: [ + { + caption: "change file to ensure signatures are updated", + change: sys => sys.appendFile(commonFile2.path, ";let xy = 10;"), + timeouts: checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "change file to ensure signatures are updated", - change: sys => sys.appendFile(commonFile2.path, ";let xy = 10;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "delete file2", - change: sys => sys.deleteFile(commonFile2.path), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "recreate file2", - change: sys => sys.writeFile(commonFile2.path, commonFile2.content), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + { + caption: "delete file2", + change: sys => sys.deleteFile(commonFile2.path), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "recreate file2", + change: sys => sys.writeFile(commonFile2.path, commonFile2.content), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "handles the missing files - that were added to program because they were added with tripleSlashRefs", - commandLineArgs: ["-w", "/a/b/commonFile1.ts"], - sys: () => { - const file1: File = { - path: commonFile1.path, - content: `/// + verifyTscWatch({ + scenario, + subScenario: "handles the missing files - that were added to program because they were added with tripleSlashRefs", + commandLineArgs: ["-w", "/a/b/commonFile1.ts"], + sys: () => { + const file1: File = { + path: commonFile1.path, + content: `/// let x = y` - }; - return createWatchedSystem([file1, libFile]); - }, - changes: [ - { - caption: "create file2", - change: sys => sys.writeFile(commonFile2.path, commonFile2.content), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + }; + return createWatchedSystem([file1, libFile]); + }, + changes: [ + { + caption: "create file2", + change: sys => sys.writeFile(commonFile2.path, commonFile2.content), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "should reflect change in config file", - commandLineArgs: ["-w", "-p", configFilePath, "--explainFiles"], - sys: () => { - const configFile: File = { - path: configFilePath, - content: `{ + verifyTscWatch({ + scenario, + subScenario: "should reflect change in config file", + commandLineArgs: ["-w", "-p", configFilePath, "--explainFiles"], + sys: () => { + const configFile: File = { + path: configFilePath, + content: `{ "compilerOptions": {}, "files": ["${commonFile1.path}", "${commonFile2.path}"] }` - }; - return createWatchedSystem([libFile, commonFile1, commonFile2, configFile]); + }; + return createWatchedSystem([libFile, commonFile1, commonFile2, configFile]); + }, + changes: [ + { + caption: "change file to ensure signatures are updated", + change: sys => sys.appendFile(commonFile2.path, ";let xy = 10;"), + timeouts: checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "change file to ensure signatures are updated", - change: sys => sys.appendFile(commonFile2.path, ";let xy = 10;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Change config", - change: sys => sys.writeFile(configFilePath, `{ + { + caption: "Change config", + change: sys => sys.writeFile(configFilePath, `{ "compilerOptions": {}, "files": ["${commonFile1.path}"] }`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "works correctly when config file is changed but its content havent", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const configFile: File = { - path: configFilePath, - content: `{ + verifyTscWatch({ + scenario, + subScenario: "works correctly when config file is changed but its content havent", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const configFile: File = { + path: configFilePath, + content: `{ "compilerOptions": {}, "files": ["${commonFile1.path}", "${commonFile2.path}"] }` - }; - return createWatchedSystem([libFile, commonFile1, commonFile2, configFile]); - }, - changes: [ - { - caption: "Modify config without changing content", - change: sys => sys.modifyFile(configFilePath, `{ + }; + return createWatchedSystem([libFile, commonFile1, commonFile2, configFile]); + }, + changes: [ + { + caption: "Modify config without changing content", + change: sys => sys.modifyFile(configFilePath, `{ "compilerOptions": {}, "files": ["${commonFile1.path}", "${commonFile2.path}"] }`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "Updates diagnostics when '--noUnusedLabels' changes", - commandLineArgs: ["-w", "-p", "/tsconfig.json"], - sys: () => { - const aTs: File = { - path: "/a.ts", - content: "label: while (1) {}" - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { allowUnusedLabels: true } - }) - }; - return createWatchedSystem([libFile, aTs, tsconfig]); + verifyTscWatch({ + scenario, + subScenario: "Updates diagnostics when '--noUnusedLabels' changes", + commandLineArgs: ["-w", "-p", "/tsconfig.json"], + sys: () => { + const aTs: File = { + path: "/a.ts", + content: "label: while (1) {}" + }; + const tsconfig: File = { + path: "/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { allowUnusedLabels: true } + }) + }; + return createWatchedSystem([libFile, aTs, tsconfig]); + }, + changes: [ + { + caption: "Disable allowUnsusedLabels", + change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ + compilerOptions: { allowUnusedLabels: false } + })), + timeouts: checkSingleTimeoutQueueLengthAndRun }, - changes: [ - { - caption: "Disable allowUnsusedLabels", - change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ - compilerOptions: { allowUnusedLabels: false } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - { - caption: "Enable allowUnsusedLabels", - change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ - compilerOptions: { allowUnusedLabels: true } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + { + caption: "Enable allowUnsusedLabels", + change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ + compilerOptions: { allowUnusedLabels: true } + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates diagnostics and emit for decorators", - commandLineArgs: ["-w"], - sys: () => { - const aTs: File = { - path: "/a.ts", - content: `import {B} from './b' + verifyTscWatch({ + scenario, + subScenario: "updates diagnostics and emit for decorators", + commandLineArgs: ["-w"], + sys: () => { + const aTs: File = { + path: "/a.ts", + content: `import {B} from './b' @((_) => {}) export class A { constructor(p: B) {} }`, - }; - const bTs: File = { - path: "/b.ts", - content: `export class B {}`, - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { target: "es6", importsNotUsedAsValues: "error" } - }) - }; - return createWatchedSystem([libFile, aTs, bTs, tsconfig]); - }, - changes: [ - { - caption: "Enable experimentalDecorators", - change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ - compilerOptions: { target: "es6", importsNotUsedAsValues: "error", experimentalDecorators: true } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, + }; + const bTs: File = { + path: "/b.ts", + content: `export class B {}`, + }; + const tsconfig: File = { + path: "/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { target: "es6", importsNotUsedAsValues: "error" } + }) + }; + return createWatchedSystem([libFile, aTs, bTs, tsconfig]); + }, + changes: [ + { + caption: "Enable experimentalDecorators", + change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ + compilerOptions: { target: "es6", importsNotUsedAsValues: "error", experimentalDecorators: true } + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Enable emitDecoratorMetadata", - change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ - compilerOptions: { target: "es6", importsNotUsedAsValues: "error", experimentalDecorators: true, emitDecoratorMetadata: true } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + }, + { + caption: "Enable emitDecoratorMetadata", + change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ + compilerOptions: { target: "es6", importsNotUsedAsValues: "error", experimentalDecorators: true, emitDecoratorMetadata: true } + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "files explicitly excluded in config file", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const configFile: File = { - path: configFilePath, - content: `{ + verifyTscWatch({ + scenario, + subScenario: "files explicitly excluded in config file", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const configFile: File = { + path: configFilePath, + content: `{ "compilerOptions": {}, "exclude": ["/a/c"] }` - }; - const excludedFile1: File = { - path: "/a/c/excluedFile1.ts", - content: `let t = 1;` - }; - return createWatchedSystem([libFile, commonFile1, commonFile2, excludedFile1, configFile]); - }, - changes: emptyArray - }); + }; + const excludedFile1: File = { + path: "/a/c/excluedFile1.ts", + content: `let t = 1;` + }; + return createWatchedSystem([libFile, commonFile1, commonFile2, excludedFile1, configFile]); + }, + changes: emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "should properly handle module resolution changes in config file", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1: File = { - path: "/a/b/file1.ts", - content: `import { T } from "module1";` - }; - const nodeModuleFile: File = { - path: "/a/b/node_modules/module1.ts", - content: `export interface T {}` - }; - const classicModuleFile: File = { - path: "/a/module1.ts", - content: `export interface T {}` - }; - const configFile: File = { - path: configFilePath, - content: `{ + verifyTscWatch({ + scenario, + subScenario: "should properly handle module resolution changes in config file", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1: File = { + path: "/a/b/file1.ts", + content: `import { T } from "module1";` + }; + const nodeModuleFile: File = { + path: "/a/b/node_modules/module1.ts", + content: `export interface T {}` + }; + const classicModuleFile: File = { + path: "/a/module1.ts", + content: `export interface T {}` + }; + const configFile: File = { + path: configFilePath, + content: `{ "compilerOptions": { "moduleResolution": "node" }, "files": ["${file1.path}"] }` - }; - return createWatchedSystem([libFile, file1, nodeModuleFile, classicModuleFile, configFile]); - }, - changes: [ - { - caption: "Change module resolution to classic", - change: sys => sys.writeFile(configFile.path, `{ + }; + return createWatchedSystem([libFile, file1, nodeModuleFile, classicModuleFile, configFile]); + }, + changes: [ + { + caption: "Change module resolution to classic", + change: sys => sys.writeFile(configFile.path, `{ "compilerOptions": { "moduleResolution": "classic" }, "files": ["/a/b/file1.ts"] }`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "should tolerate config file errors and still try to build a project", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const configFile: File = { - path: configFilePath, - content: `{ + verifyTscWatch({ + scenario, + subScenario: "should tolerate config file errors and still try to build a project", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const configFile: File = { + path: configFilePath, + content: `{ "compilerOptions": { "module": "none", "allowAnything": true }, "someOtherProperty": {} }` - }; - return createWatchedSystem([commonFile1, commonFile2, libFile, configFile]); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "changes in files are reflected in project structure", - commandLineArgs: ["-w", "/a/b/f1.ts", "--explainFiles"], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "./f2"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: `export let x = 1` - }; - const file3 = { - path: "/a/c/f3.ts", - content: `export let y = 1;` - }; - return createWatchedSystem([file1, file2, file3, libFile]); - }, - changes: [ - { - caption: "Modify f2 to include f3", - // now inferred project should inclule file3 - change: sys => sys.modifyFile("/a/b/f2.ts", `export * from "../c/f3"`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatch({ - scenario, - subScenario: "deleted files affect project structure", - commandLineArgs: ["-w", "/a/b/f1.ts", "--noImplicitAny"], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "./f2"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: `export * from "../c/f3"` - }; - const file3 = { - path: "/a/c/f3.ts", - content: `export let y = 1;` - }; - return createWatchedSystem([file1, file2, file3, libFile]); - }, - changes: [ - { - caption: "Delete f2", - change: sys => sys.deleteFile("/a/b/f2.ts"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + }; + return createWatchedSystem([commonFile1, commonFile2, libFile, configFile]); + }, + changes: emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "deleted files affect project structure-2", - commandLineArgs: ["-w", "/a/b/f1.ts", "/a/c/f3.ts", "--noImplicitAny"], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "./f2"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: `export * from "../c/f3"` - }; - const file3 = { - path: "/a/c/f3.ts", - content: `export let y = 1;` - }; - return createWatchedSystem([file1, file2, file3, libFile]); - }, - changes: [ - { - caption: "Delete f2", - change: sys => sys.deleteFile("/a/b/f2.ts"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + verifyTscWatch({ + scenario, + subScenario: "changes in files are reflected in project structure", + commandLineArgs: ["-w", "/a/b/f1.ts", "--explainFiles"], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export let x = 1` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + return createWatchedSystem([file1, file2, file3, libFile]); + }, + changes: [ + { + caption: "Modify f2 to include f3", + // now inferred project should inclule file3 + change: sys => sys.modifyFile("/a/b/f2.ts", `export * from "../c/f3"`), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "config file includes the file", - commandLineArgs: ["-w", "-p", "/a/c/tsconfig.json"], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "export let x = 5" - }; - const file2 = { - path: "/a/c/f2.ts", - content: `import {x} from "../b/f1"` - }; - const file3 = { - path: "/a/c/f3.ts", - content: "export let y = 1" - }; - const configFile = { - path: "/a/c/tsconfig.json", - content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) - }; - return createWatchedSystem([file1, file2, file3, libFile, configFile]); - }, - changes: emptyArray - }); + verifyTscWatch({ + scenario, + subScenario: "deleted files affect project structure", + commandLineArgs: ["-w", "/a/b/f1.ts", "--noImplicitAny"], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export * from "../c/f3"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + return createWatchedSystem([file1, file2, file3, libFile]); + }, + changes: [ + { + caption: "Delete f2", + change: sys => sys.deleteFile("/a/b/f2.ts"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "change module to none", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "export {}\ndeclare global {}" - }; - return createWatchedSystem([file1, libFile, configFile]); - }, - changes: [{ - caption: "change `module` to 'none'", + verifyTscWatch({ + scenario, + subScenario: "deleted files affect project structure-2", + commandLineArgs: ["-w", "/a/b/f1.ts", "/a/c/f3.ts", "--noImplicitAny"], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export * from "../c/f3"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + return createWatchedSystem([file1, file2, file3, libFile]); + }, + changes: [ + { + caption: "Delete f2", + change: sys => sys.deleteFile("/a/b/f2.ts"), timeouts: checkSingleTimeoutQueueLengthAndRun, - change: sys => { - sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: { module: "none" } })); - } - }] - }); + } + ] + }); - it("correctly migrate files between projects", () => { + verifyTscWatch({ + scenario, + subScenario: "config file includes the file", + commandLineArgs: ["-w", "-p", "/a/c/tsconfig.json"], + sys: () => { const file1 = { path: "/a/b/f1.ts", - content: ` - export * from "../c/f2"; - export * from "../d/f3";` + content: "export let x = 5" }; const file2 = { path: "/a/c/f2.ts", - content: "export let x = 1;" + content: `import {x} from "../b/f1"` }; const file3 = { - path: "/a/d/f3.ts", - content: "export let y = 1;" + path: "/a/c/f3.ts", + content: "export let y = 1" }; - const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchOfFilesAndCompilerOptions([file2.path, file3.path], host); - checkProgramActualFiles(watch.getCurrentProgram().getProgram(), [file2.path, file3.path]); - - const watch2 = createWatchOfFilesAndCompilerOptions([file1.path], host); - checkProgramActualFiles(watch2.getCurrentProgram().getProgram(), [file1.path, file2.path, file3.path]); - - // Previous program shouldnt be updated - checkProgramActualFiles(watch.getCurrentProgram().getProgram(), [file2.path, file3.path]); - host.checkTimeoutQueueLength(0); - }); - - verifyTscWatch({ - scenario, - subScenario: "can correctly update configured project when set of root files has changed (new file on disk)", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - return createWatchedSystem([file1, libFile, configFile]); - }, - changes: [ - { - caption: "Write f2", - change: sys => sys.writeFile("/a/b/f2.ts", "let y = 1"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatch({ - scenario, - subScenario: "can correctly update configured project when set of root files has changed (new file in list of files)", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: configFilePath, - content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) - }; - return createWatchedSystem([file1, file2, libFile, configFile]); - }, - changes: [ - { - caption: "Modify config to make f2 as root too", - change: sys => sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + const configFile = { + path: "/a/c/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) + }; + return createWatchedSystem([file1, file2, file3, libFile, configFile]); + }, + changes: emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "can correctly update configured project when set of root files has changed through include", - commandLineArgs: ["-w", "-p", "."], - sys: () => { - const file1 = { - path: `${projectRoot}/Project/file1.ts`, - content: "export const x = 10;" - }; - const configFile = { - path: `${projectRoot}/Project/tsconfig.json`, - content: JSON.stringify({ include: [".", "./**/*.json"] }) - }; - return createWatchedSystem([file1, libFile, configFile], { currentDirectory: `${projectRoot}/Project` }); - }, - changes: [ - { - caption: "Write file2", - change: sys => sys.writeFile(`${projectRoot}/Project/file2.ts`, "export const y = 10;"), - timeouts: checkSingleTimeoutQueueLengthAndRun - } - ] - }); + verifyTscWatch({ + scenario, + subScenario: "change module to none", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: "export {}\ndeclare global {}" + }; + return createWatchedSystem([file1, libFile, configFile]); + }, + changes: [{ + caption: "change `module` to 'none'", + timeouts: checkSingleTimeoutQueueLengthAndRun, + change: sys => { + sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: { module: "none" } })); + } + }] + }); - verifyTscWatch({ - scenario, - subScenario: "can update configured project when set of root files was not changed", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: configFilePath, - content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) - }; - return createWatchedSystem([file1, file2, libFile, configFile]); - }, - changes: [ - { - caption: "Modify config to set outFile option", - change: sys => sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + it("correctly migrate files between projects", () => { + const file1 = { + path: "/a/b/f1.ts", + content: ` + export * from "../c/f2"; + export * from "../d/f3";` + }; + const file2 = { + path: "/a/c/f2.ts", + content: "export let x = 1;" + }; + const file3 = { + path: "/a/d/f3.ts", + content: "export let y = 1;" + }; + const host = createWatchedSystem([file1, file2, file3]); + const watch = createWatchOfFilesAndCompilerOptions([file2.path, file3.path], host); + checkProgramActualFiles(watch.getCurrentProgram().getProgram(), [file2.path, file3.path]); - verifyTscWatch({ - scenario, - subScenario: "config file is deleted", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1;" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 2;" - }; - return createWatchedSystem([file1, file2, libFile, configFile]); - }, - changes: [ - { - caption: "Delete config file", - change: sys => sys.deleteFile(configFilePath), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + const watch2 = createWatchOfFilesAndCompilerOptions([file1.path], host); + checkProgramActualFiles(watch2.getCurrentProgram().getProgram(), [file1.path, file2.path, file3.path]); - verifyTscWatch({ - scenario, - subScenario: "Proper errors document is not contained in project", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/app.ts", - content: "" - }; - const corruptedConfig = { - path: configFilePath, - content: "{" - }; - return createWatchedSystem([file1, libFile, corruptedConfig]); - }, - changes: emptyArray - }); + // Previous program shouldnt be updated + checkProgramActualFiles(watch.getCurrentProgram().getProgram(), [file2.path, file3.path]); + host.checkTimeoutQueueLength(0); + }); - verifyTscWatch({ - scenario, - subScenario: "correctly handles changes in lib section of config file", - commandLineArgs: ["-w", "-p", "/src/tsconfig.json"], - sys: () => { - const libES5 = { - path: "/compiler/lib.es5.d.ts", - content: `${libFile.content} -declare const eval: any` - }; - const libES2015Promise = { - path: "/compiler/lib.es2015.promise.d.ts", - content: `declare class Promise {}` - }; - const app = { - path: "/src/app.ts", - content: "var x: Promise;" - }; - const config1 = { - path: "/src/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: true, - sourceMap: false, - lib: [ - "es5" - ] - } - }) - }; - return createWatchedSystem([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); - }, - changes: [ - { - caption: "Change the lib in config", - change: sys => sys.writeFile("/src/tsconfig.json", JSON.stringify( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: true, - sourceMap: false, - lib: [ - "es5", - "es2015.promise" - ] - } - }) - ), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + verifyTscWatch({ + scenario, + subScenario: "can correctly update configured project when set of root files has changed (new file on disk)", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + return createWatchedSystem([file1, libFile, configFile]); + }, + changes: [ + { + caption: "Write f2", + change: sys => sys.writeFile("/a/b/f2.ts", "let y = 1"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "should handle non-existing directories in config file", - commandLineArgs: ["-w", "-p", "/a/tsconfig.json"], - sys: () => { - const f = { - path: "/a/src/app.ts", - content: "let x = 1;" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {}, - include: [ - "src/**/*", - "notexistingfolder/*" - ] - }) - }; - return createWatchedSystem([f, config, libFile]); - }, - changes: emptyArray - }); + verifyTscWatch({ + scenario, + subScenario: "can correctly update configured project when set of root files has changed (new file in list of files)", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: configFilePath, + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) + }; + return createWatchedSystem([file1, file2, libFile, configFile]); + }, + changes: [ + { + caption: "Modify config to make f2 as root too", + change: sys => sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - function runQueuedTimeoutCallbacksTwice(sys: WatchedSystem) { - sys.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions - sys.runQueuedTimeoutCallbacks(); // Actual update - } + verifyTscWatch({ + scenario, + subScenario: "can correctly update configured project when set of root files has changed through include", + commandLineArgs: ["-w", "-p", "."], + sys: () => { + const file1 = { + path: `${projectRoot}/Project/file1.ts`, + content: "export const x = 10;" + }; + const configFile = { + path: `${projectRoot}/Project/tsconfig.json`, + content: JSON.stringify({ include: [".", "./**/*.json"] }) + }; + return createWatchedSystem([file1, libFile, configFile], { currentDirectory: `${projectRoot}/Project` }); + }, + changes: [ + { + caption: "Write file2", + change: sys => sys.writeFile(`${projectRoot}/Project/file2.ts`, "export const y = 10;"), + timeouts: checkSingleTimeoutQueueLengthAndRun + } + ] + }); - const changeModuleFileToModuleFile1: TscWatchCompileChange = { - caption: "Rename moduleFile to moduleFile1", - change: sys => { - sys.renameFile("/a/b/moduleFile.ts", "/a/b/moduleFile1.ts"); - sys.deleteFile("/a/b/moduleFile.js"); - }, - timeouts: runQueuedTimeoutCallbacksTwice - }; - const changeModuleFile1ToModuleFile: TscWatchCompileChange = { - caption: "Rename moduleFile1 back to moduleFile", - change: sys => sys.renameFile("/a/b/moduleFile1.ts", "/a/b/moduleFile.ts"), - timeouts: runQueuedTimeoutCallbacksTwice, - }; + verifyTscWatch({ + scenario, + subScenario: "can update configured project when set of root files was not changed", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: configFilePath, + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) + }; + return createWatchedSystem([file1, file2, libFile, configFile]); + }, + changes: [ + { + caption: "Modify config to set outFile option", + change: sys => sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "rename a module file and rename back should restore the states for inferred projects", - commandLineArgs: ["-w", "/a/b/file1.ts"], - sys: () => { - const moduleFile = { - path: "/a/b/moduleFile.ts", - content: "export function bar() { };" - }; - const file1 = { - path: "/a/b/file1.ts", - content: 'import * as T from "./moduleFile"; T.bar();' - }; - return createWatchedSystem([moduleFile, file1, libFile]); - }, - changes: [ - changeModuleFileToModuleFile1, - changeModuleFile1ToModuleFile - ] - }); + verifyTscWatch({ + scenario, + subScenario: "config file is deleted", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1;" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 2;" + }; + return createWatchedSystem([file1, file2, libFile, configFile]); + }, + changes: [ + { + caption: "Delete config file", + change: sys => sys.deleteFile(configFilePath), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "rename a module file and rename back should restore the states for configured projects", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const moduleFile = { - path: "/a/b/moduleFile.ts", - content: "export function bar() { };" - }; - const file1 = { - path: "/a/b/file1.ts", - content: 'import * as T from "./moduleFile"; T.bar();' - }; - return createWatchedSystem([moduleFile, file1, configFile, libFile]); - }, - changes: [ - changeModuleFileToModuleFile1, - changeModuleFile1ToModuleFile - ] - }); + verifyTscWatch({ + scenario, + subScenario: "Proper errors document is not contained in project", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const corruptedConfig = { + path: configFilePath, + content: "{" + }; + return createWatchedSystem([file1, libFile, corruptedConfig]); + }, + changes: emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "types should load from config file path if config exists", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: configFilePath, - content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } }) - }; - const node = { - path: "/a/b/node_modules/@types/node/index.d.ts", - content: "declare var process: any" - }; - const cwd = { - path: "/a/c" - }; - return createWatchedSystem([f1, config, node, cwd, libFile], { currentDirectory: cwd.path }); - }, - changes: emptyArray - }); + verifyTscWatch({ + scenario, + subScenario: "correctly handles changes in lib section of config file", + commandLineArgs: ["-w", "-p", "/src/tsconfig.json"], + sys: () => { + const libES5 = { + path: "/compiler/lib.es5.d.ts", + content: `${libFile.content} +declare const eval: any` + }; + const libES2015Promise = { + path: "/compiler/lib.es2015.promise.d.ts", + content: `declare class Promise {}` + }; + const app = { + path: "/src/app.ts", + content: "var x: Promise;" + }; + const config1 = { + path: "/src/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: true, + sourceMap: false, + lib: [ + "es5" + ] + } + }) + }; + return createWatchedSystem([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); + }, + changes: [ + { + caption: "Change the lib in config", + change: sys => sys.writeFile("/src/tsconfig.json", JSON.stringify( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: true, + sourceMap: false, + lib: [ + "es5", + "es2015.promise" + ] + } + }) + ), + timeouts: checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "add the missing module file for inferred project-should remove the module not found error", - commandLineArgs: ["-w", "/a/b/file1.ts"], - sys: () => { - const file1 = { - path: "/a/b/file1.ts", - content: 'import * as T from "./moduleFile"; T.bar();' - }; - return createWatchedSystem([file1, libFile]); - }, - changes: [ - { - caption: "Create module file", - change: sys => sys.writeFile("/a/b/moduleFile.ts", "export function bar() { }"), - timeouts: runQueuedTimeoutCallbacksTwice, - } - ] - }); + verifyTscWatch({ + scenario, + subScenario: "should handle non-existing directories in config file", + commandLineArgs: ["-w", "-p", "/a/tsconfig.json"], + sys: () => { + const f = { + path: "/a/src/app.ts", + content: "let x = 1;" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {}, + include: [ + "src/**/*", + "notexistingfolder/*" + ] + }) + }; + return createWatchedSystem([f, config, libFile]); + }, + changes: emptyArray + }); + + function runQueuedTimeoutCallbacksTwice(sys: WatchedSystem) { + sys.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions + sys.runQueuedTimeoutCallbacks(); // Actual update + } + + const changeModuleFileToModuleFile1: TscWatchCompileChange = { + caption: "Rename moduleFile to moduleFile1", + change: sys => { + sys.renameFile("/a/b/moduleFile.ts", "/a/b/moduleFile1.ts"); + sys.deleteFile("/a/b/moduleFile.js"); + }, + timeouts: runQueuedTimeoutCallbacksTwice + }; + const changeModuleFile1ToModuleFile: TscWatchCompileChange = { + caption: "Rename moduleFile1 back to moduleFile", + change: sys => sys.renameFile("/a/b/moduleFile1.ts", "/a/b/moduleFile.ts"), + timeouts: runQueuedTimeoutCallbacksTwice, + }; + + verifyTscWatch({ + scenario, + subScenario: "rename a module file and rename back should restore the states for inferred projects", + commandLineArgs: ["-w", "/a/b/file1.ts"], + sys: () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: 'import * as T from "./moduleFile"; T.bar();' + }; + return createWatchedSystem([moduleFile, file1, libFile]); + }, + changes: [ + changeModuleFileToModuleFile1, + changeModuleFile1ToModuleFile + ] + }); - verifyTscWatch({ - scenario, - subScenario: "Configure file diagnostics events are generated when the config file has errors", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile = { - path: configFilePath, - content: `{ + verifyTscWatch({ + scenario, + subScenario: "rename a module file and rename back should restore the states for configured projects", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: 'import * as T from "./moduleFile"; T.bar();' + }; + return createWatchedSystem([moduleFile, file1, configFile, libFile]); + }, + changes: [ + changeModuleFileToModuleFile1, + changeModuleFile1ToModuleFile + ] + }); + + verifyTscWatch({ + scenario, + subScenario: "types should load from config file path if config exists", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: configFilePath, + content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } }) + }; + const node = { + path: "/a/b/node_modules/@types/node/index.d.ts", + content: "declare var process: any" + }; + const cwd = { + path: "/a/c" + }; + return createWatchedSystem([f1, config, node, cwd, libFile], { currentDirectory: cwd.path }); + }, + changes: emptyArray + }); + + verifyTscWatch({ + scenario, + subScenario: "add the missing module file for inferred project-should remove the module not found error", + commandLineArgs: ["-w", "/a/b/file1.ts"], + sys: () => { + const file1 = { + path: "/a/b/file1.ts", + content: 'import * as T from "./moduleFile"; T.bar();' + }; + return createWatchedSystem([file1, libFile]); + }, + changes: [ + { + caption: "Create module file", + change: sys => sys.writeFile("/a/b/moduleFile.ts", "export function bar() { }"), + timeouts: runQueuedTimeoutCallbacksTwice, + } + ] + }); + + verifyTscWatch({ + scenario, + subScenario: "Configure file diagnostics events are generated when the config file has errors", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile = { + path: configFilePath, + content: `{ "compilerOptions": { "foo": "bar", "allowJS": true } }` - }; - return createWatchedSystem([file, configFile, libFile]); - }, - changes: emptyArray - }); + }; + return createWatchedSystem([file, configFile, libFile]); + }, + changes: emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "if config file doesnt have errors, they are not reported", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile = { - path: configFilePath, - content: `{ + verifyTscWatch({ + scenario, + subScenario: "if config file doesnt have errors, they are not reported", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile = { + path: configFilePath, + content: `{ "compilerOptions": {} }` - }; - return createWatchedSystem([file, configFile, libFile]); - }, - changes: emptyArray - }); + }; + return createWatchedSystem([file, configFile, libFile]); + }, + changes: emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "Reports errors when the config file changes", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - return createWatchedSystem([file, configFile, libFile]); - }, - changes: [ - { - caption: "change config file to add error", - change: sys => sys.writeFile(configFilePath, `{ + verifyTscWatch({ + scenario, + subScenario: "Reports errors when the config file changes", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + return createWatchedSystem([file, configFile, libFile]); + }, + changes: [ + { + caption: "change config file to add error", + change: sys => sys.writeFile(configFilePath, `{ "compilerOptions": { "haha": 123 } }`), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "change config file to remove error", - change: sys => sys.writeFile(configFilePath, `{ + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "change config file to remove error", + change: sys => sys.writeFile(configFilePath, `{ "compilerOptions": { } }`), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + timeouts: runQueuedTimeoutCallbacks, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "non-existing directories listed in config file input array should be tolerated without crashing the server", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const configFile = { - path: configFilePath, - content: `{ + verifyTscWatch({ + scenario, + subScenario: "non-existing directories listed in config file input array should be tolerated without crashing the server", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const configFile = { + path: configFilePath, + content: `{ "compilerOptions": {}, "include": ["app/*", "test/**/*", "something"] }` - }; - const file1 = { - path: "/a/b/file1.ts", - content: "let t = 10;" - }; - return createWatchedSystem([file1, configFile, libFile]); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "non-existing directories listed in config file input array should be able to handle @types if input file list is empty", - commandLineArgs: ["-w", "-p", "/a/tsconfig.json"], - sys: () => { - const f = { - path: "/a/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compiler: {}, - files: [] - }) - }; - const t1 = { - path: "/a/node_modules/@types/typings/index.d.ts", - content: `export * from "./lib"` - }; - const t2 = { - path: "/a/node_modules/@types/typings/lib.d.ts", - content: `export const x: number` - }; - return createWatchedSystem([f, config, t1, t2, libFile], { currentDirectory: getDirectoryPath(f.path) }); - }, - changes: emptyArray - }); + }; + const file1 = { + path: "/a/b/file1.ts", + content: "let t = 10;" + }; + return createWatchedSystem([file1, configFile, libFile]); + }, + changes: emptyArray + }); - it("should support files without extensions", () => { + verifyTscWatch({ + scenario, + subScenario: "non-existing directories listed in config file input array should be able to handle @types if input file list is empty", + commandLineArgs: ["-w", "-p", "/a/tsconfig.json"], + sys: () => { const f = { - path: "/a/compile", + path: "/a/app.ts", content: "let x = 1" }; - const host = createWatchedSystem([f, libFile]); - const watch = createWatchOfFilesAndCompilerOptions([f.path], host, { allowNonTsExtensions: true }); - checkProgramActualFiles(watch.getCurrentProgram().getProgram(), [f.path, libFile.path]); - }); + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compiler: {}, + files: [] + }) + }; + const t1 = { + path: "/a/node_modules/@types/typings/index.d.ts", + content: `export * from "./lib"` + }; + const t2 = { + path: "/a/node_modules/@types/typings/lib.d.ts", + content: `export const x: number` + }; + return createWatchedSystem([f, config, t1, t2, libFile], { currentDirectory: getDirectoryPath(f.path) }); + }, + changes: emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "Options Diagnostic locations reported correctly with changes in configFile contents when options change", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile = { - path: configFilePath, - content: ` + it("should support files without extensions", () => { + const f = { + path: "/a/compile", + content: "let x = 1" + }; + const host = createWatchedSystem([f, libFile]); + const watch = createWatchOfFilesAndCompilerOptions([f.path], host, { allowNonTsExtensions: true }); + checkProgramActualFiles(watch.getCurrentProgram().getProgram(), [f.path, libFile.path]); + }); + + verifyTscWatch({ + scenario, + subScenario: "Options Diagnostic locations reported correctly with changes in configFile contents when options change", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile = { + path: configFilePath, + content: ` { // comment // More comment @@ -1022,232 +1022,232 @@ declare const eval: any` "mapRoot": "./" } }` - }; - return createWatchedSystem([file, libFile, configFile]); - }, - changes: [ - { - caption: "Remove the comment from config file", - change: sys => sys.writeFile(configFilePath, ` + }; + return createWatchedSystem([file, libFile, configFile]); + }, + changes: [ + { + caption: "Remove the comment from config file", + change: sys => sys.writeFile(configFilePath, ` { "compilerOptions": { "inlineSourceMap": true, "mapRoot": "./" } }`), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + timeouts: runQueuedTimeoutCallbacks, + } + ] + }); - describe("should not trigger recompilation because of program emit", () => { - function verifyWithOptions(subScenario: string, options: CompilerOptions) { - verifyTscWatch({ - scenario, - subScenario: `should not trigger recompilation because of program emit/${subScenario}`, - commandLineArgs: ["-w", "-p", `${projectRoot}/tsconfig.json`], - sys: () => { - const file1: File = { - path: `${projectRoot}/file1.ts`, - content: "export const c = 30;" - }; - const file2: File = { - path: `${projectRoot}/src/file2.ts`, - content: `import {c} from "file1"; export const d = 30;` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: generateTSConfig(options, emptyArray, "\n") - }; - return createWatchedSystem([file1, file2, libFile, tsconfig], { currentDirectory: projectRoot }); + describe("should not trigger recompilation because of program emit", () => { + function verifyWithOptions(subScenario: string, options: CompilerOptions) { + verifyTscWatch({ + scenario, + subScenario: `should not trigger recompilation because of program emit/${subScenario}`, + commandLineArgs: ["-w", "-p", `${projectRoot}/tsconfig.json`], + sys: () => { + const file1: File = { + path: `${projectRoot}/file1.ts`, + content: "export const c = 30;" + }; + const file2: File = { + path: `${projectRoot}/src/file2.ts`, + content: `import {c} from "file1"; export const d = 30;` + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: generateTSConfig(options, emptyArray, "\n") + }; + return createWatchedSystem([file1, file2, libFile, tsconfig], { currentDirectory: projectRoot }); + }, + changes: [ + noopChange, + { + caption: "Add new file", + change: sys => sys.writeFile(`${projectRoot}/src/file3.ts`, `export const y = 10;`), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), // To update program and failed lookups }, - changes: [ - noopChange, - { - caption: "Add new file", - change: sys => sys.writeFile(`${projectRoot}/src/file3.ts`, `export const y = 10;`), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), // To update program and failed lookups - }, - noopChange, - ] - }); - } + noopChange, + ] + }); + } - verifyWithOptions( - "without outDir or outFile is specified", - { module: ModuleKind.AMD } - ); - - verifyWithOptions( - "with outFile", - { module: ModuleKind.AMD, outFile: "build/outFile.js" } - ); - - verifyWithOptions( - "when outDir is specified", - { module: ModuleKind.AMD, outDir: "build" } - ); - - verifyWithOptions( - "without outDir or outFile is specified with declaration enabled", - { module: ModuleKind.AMD, declaration: true } - ); - - verifyWithOptions( - "when outDir and declarationDir is specified", - { module: ModuleKind.AMD, outDir: "build", declaration: true, declarationDir: "decls" } - ); - - verifyWithOptions( - "declarationDir is specified", - { module: ModuleKind.AMD, declaration: true, declarationDir: "decls" } - ); - }); + verifyWithOptions( + "without outDir or outFile is specified", + { module: ModuleKind.AMD } + ); + + verifyWithOptions( + "with outFile", + { module: ModuleKind.AMD, outFile: "build/outFile.js" } + ); + + verifyWithOptions( + "when outDir is specified", + { module: ModuleKind.AMD, outDir: "build" } + ); + + verifyWithOptions( + "without outDir or outFile is specified with declaration enabled", + { module: ModuleKind.AMD, declaration: true } + ); + + verifyWithOptions( + "when outDir and declarationDir is specified", + { module: ModuleKind.AMD, outDir: "build", declaration: true, declarationDir: "decls" } + ); + + verifyWithOptions( + "declarationDir is specified", + { module: ModuleKind.AMD, declaration: true, declarationDir: "decls" } + ); + }); - verifyTscWatch({ - scenario, - subScenario: "shouldnt report error about unused function incorrectly when file changes from global to module", - commandLineArgs: ["-w", "/a/b/file.ts", "--noUnusedLocals"], - sys: () => { - const file: File = { - path: "/a/b/file.ts", - content: `function one() {} + verifyTscWatch({ + scenario, + subScenario: "shouldnt report error about unused function incorrectly when file changes from global to module", + commandLineArgs: ["-w", "/a/b/file.ts", "--noUnusedLocals"], + sys: () => { + const file: File = { + path: "/a/b/file.ts", + content: `function one() {} function two() { return function three() { one(); } }` - }; - return createWatchedSystem([file, libFile]); - }, - changes: [ - { - caption: "Change file to module", - change: sys => sys.writeFile("/a/b/file.ts", `function one() {} + }; + return createWatchedSystem([file, libFile]); + }, + changes: [ + { + caption: "Change file to module", + change: sys => sys.writeFile("/a/b/file.ts", `function one() {} export function two() { return function three() { one(); } }`), - timeouts: runQueuedTimeoutCallbacks, - - } - ] - }); + timeouts: runQueuedTimeoutCallbacks, - verifyTscWatch({ - scenario, - subScenario: "watched files when file is deleted and new file is added as part of change", - commandLineArgs: ["-w", "-p", "/home/username/project/tsconfig.json"], - sys: () => { - const projectLocation = "/home/username/project"; - const file: File = { - path: `${projectLocation}/src/file1.ts`, - content: "var a = 10;" - }; - const configFile: File = { - path: `${projectLocation}/tsconfig.json`, - content: "{}" - }; - return createWatchedSystem([file, libFile, configFile]); - }, - changes: [ - { - caption: "Rename file1 to file2", - change: sys => sys.renameFile("/home/username/project/src/file1.ts", "/home/username/project/src/file2.ts"), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + } + ] + }); - function changeParameterTypeOfBFile(parameterName: string, toType: string): TscWatchCompileChange { - return { - caption: `Changed ${parameterName} type to ${toType}`, - change: sys => replaceFileText(sys, `${projectRoot}/b.ts`, new RegExp(`${parameterName}\: [a-z]*`), `${parameterName}: ${toType}`), - timeouts: runQueuedTimeoutCallbacks, + verifyTscWatch({ + scenario, + subScenario: "watched files when file is deleted and new file is added as part of change", + commandLineArgs: ["-w", "-p", "/home/username/project/tsconfig.json"], + sys: () => { + const projectLocation = "/home/username/project"; + const file: File = { + path: `${projectLocation}/src/file1.ts`, + content: "var a = 10;" }; - } + const configFile: File = { + path: `${projectLocation}/tsconfig.json`, + content: "{}" + }; + return createWatchedSystem([file, libFile, configFile]); + }, + changes: [ + { + caption: "Rename file1 to file2", + change: sys => sys.renameFile("/home/username/project/src/file1.ts", "/home/username/project/src/file2.ts"), + timeouts: runQueuedTimeoutCallbacks, + } + ] + }); + + function changeParameterTypeOfBFile(parameterName: string, toType: string): TscWatchCompileChange { + return { + caption: `Changed ${parameterName} type to ${toType}`, + change: sys => replaceFileText(sys, `${projectRoot}/b.ts`, new RegExp(`${parameterName}\: [a-z]*`), `${parameterName}: ${toType}`), + timeouts: runQueuedTimeoutCallbacks, + }; + } - verifyTscWatch({ - scenario, - subScenario: "updates errors correctly when declaration emit is disabled in compiler options", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `import test from './b'; + verifyTscWatch({ + scenario, + subScenario: "updates errors correctly when declaration emit is disabled in compiler options", + commandLineArgs: ["-w"], + sys: () => { + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `import test from './b'; test(4, 5);` - }; - const bFile: File = { - path: `${projectRoot}/b.ts`, - content: `function test(x: number, y: number) { + }; + const bFile: File = { + path: `${projectRoot}/b.ts`, + content: `function test(x: number, y: number) { return x + y / 5; } export default test;` - }; - const tsconfigFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "commonjs", - noEmit: true, - strict: true, - } - }) - }; - return createWatchedSystem([aFile, bFile, libFile, tsconfigFile], { currentDirectory: projectRoot }); - }, - changes: [ - changeParameterTypeOfBFile("x", "string"), - changeParameterTypeOfBFile("x", "number"), - changeParameterTypeOfBFile("y", "string"), - changeParameterTypeOfBFile("y", "number"), - ] - }); + }; + const tsconfigFile: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "commonjs", + noEmit: true, + strict: true, + } + }) + }; + return createWatchedSystem([aFile, bFile, libFile, tsconfigFile], { currentDirectory: projectRoot }); + }, + changes: [ + changeParameterTypeOfBFile("x", "string"), + changeParameterTypeOfBFile("x", "number"), + changeParameterTypeOfBFile("y", "string"), + changeParameterTypeOfBFile("y", "number"), + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates errors when strictNullChecks changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `declare function foo(): null | { hello: any }; + verifyTscWatch({ + scenario, + subScenario: "updates errors when strictNullChecks changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `declare function foo(): null | { hello: any }; foo().hello` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: {} }) - }; - return createWatchedSystem([aFile, config, libFile], { currentDirectory: projectRoot }); + }; + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: {} }) + }; + return createWatchedSystem([aFile, config, libFile], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "Enable strict null checks", + change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { strictNullChecks: true } })), + timeouts: runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Enable strict null checks", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { strictNullChecks: true } })), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Set always strict false", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { strict: true, alwaysStrict: false } })), // Avoid changing 'alwaysStrict' or must re-bind - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Disable strict", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: {} })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + { + caption: "Set always strict false", + change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { strict: true, alwaysStrict: false } })), // Avoid changing 'alwaysStrict' or must re-bind + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "Disable strict", + change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: {} })), + timeouts: runQueuedTimeoutCallbacks, + }, + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates errors when noErrorTruncation changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `declare var v: { + verifyTscWatch({ + scenario, + subScenario: "updates errors when noErrorTruncation changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `declare var v: { reallyLongPropertyName1: string | number | boolean | object | symbol | bigint; reallyLongPropertyName2: string | number | boolean | object | symbol | bigint; reallyLongPropertyName3: string | number | boolean | object | symbol | bigint; @@ -1257,563 +1257,563 @@ foo().hello` reallyLongPropertyName7: string | number | boolean | object | symbol | bigint; }; v === 'foo';` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: {} }) - }; - return createWatchedSystem([aFile, config, libFile], { currentDirectory: projectRoot }); + }; + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: {} }) + }; + return createWatchedSystem([aFile, config, libFile], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "Enable noErrorTruncation", + change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { noErrorTruncation: true } })), + timeouts: runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Enable noErrorTruncation", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { noErrorTruncation: true } })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates diagnostics and emit when useDefineForClassFields changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `/a.ts`, - content: `class C { get prop() { return 1; } } + verifyTscWatch({ + scenario, + subScenario: "updates diagnostics and emit when useDefineForClassFields changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: File = { + path: `/a.ts`, + content: `class C { get prop() { return 1; } } class D extends C { prop = 1; }` - }; - const config: File = { - path: `/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { target: "es6" } }) - }; - return createWatchedSystem([aFile, config, libFile]); + }; + const config: File = { + path: `/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { target: "es6" } }) + }; + return createWatchedSystem([aFile, config, libFile]); + }, + changes: [ + { + caption: "Enable useDefineForClassFields", + change: sys => sys.writeFile(`/tsconfig.json`, JSON.stringify({ compilerOptions: { target: "es6", useDefineForClassFields: true } })), + timeouts: runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Enable useDefineForClassFields", - change: sys => sys.writeFile(`/tsconfig.json`, JSON.stringify({ compilerOptions: { target: "es6", useDefineForClassFields: true } })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates errors and emit when importsNotUsedAsValues changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `export class C {}` - }; - const bFile: File = { - path: `${projectRoot}/b.ts`, - content: `import {C} from './a'; + verifyTscWatch({ + scenario, + subScenario: "updates errors and emit when importsNotUsedAsValues changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `export class C {}` + }; + const bFile: File = { + path: `${projectRoot}/b.ts`, + content: `import {C} from './a'; export function f(p: C) { return p; }` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: {} }) - }; - return createWatchedSystem([aFile, bFile, config, libFile], { currentDirectory: projectRoot }); + }; + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: {} }) + }; + return createWatchedSystem([aFile, bFile, config, libFile], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: 'Set to "remove"', + change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "remove" } })), + timeouts: runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: 'Set to "remove"', - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "remove" } })), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: 'Set to "error"', - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "error" } })), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: 'Set to "preserve"', - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "preserve" } })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + { + caption: 'Set to "error"', + change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "error" } })), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: 'Set to "preserve"', + change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "preserve" } })), + timeouts: runQueuedTimeoutCallbacks, + }, + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates errors when forceConsistentCasingInFileNames changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `/a.ts`, - content: `export class C {}` - }; - const bFile: File = { - path: `/b.ts`, - content: `import {C} from './a'; import * as A from './A';` - }; - const config: File = { - path: `/tsconfig.json`, - content: JSON.stringify({ compilerOptions: {} }) - }; - return createWatchedSystem([aFile, bFile, config, libFile], { useCaseSensitiveFileNames: false }); + verifyTscWatch({ + scenario, + subScenario: "updates errors when forceConsistentCasingInFileNames changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: File = { + path: `/a.ts`, + content: `export class C {}` + }; + const bFile: File = { + path: `/b.ts`, + content: `import {C} from './a'; import * as A from './A';` + }; + const config: File = { + path: `/tsconfig.json`, + content: JSON.stringify({ compilerOptions: {} }) + }; + return createWatchedSystem([aFile, bFile, config, libFile], { useCaseSensitiveFileNames: false }); + }, + changes: [ + { + caption: "Enable forceConsistentCasingInFileNames", + change: sys => sys.writeFile(`/tsconfig.json`, JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } })), + timeouts: runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Enable forceConsistentCasingInFileNames", - change: sys => sys.writeFile(`/tsconfig.json`, JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates moduleResolution when resolveJsonModule changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `import * as data from './data.json'` - }; - const jsonFile: File = { - path: `${projectRoot}/data.json`, - content: `{ "foo": 1 }` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { moduleResolution: "node" } }) - }; - return createWatchedSystem([aFile, jsonFile, config, libFile], { currentDirectory: projectRoot }); + verifyTscWatch({ + scenario, + subScenario: "updates moduleResolution when resolveJsonModule changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `import * as data from './data.json'` + }; + const jsonFile: File = { + path: `${projectRoot}/data.json`, + content: `{ "foo": 1 }` + }; + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { moduleResolution: "node" } }) + }; + return createWatchedSystem([aFile, jsonFile, config, libFile], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "Enable resolveJsonModule", + change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { moduleResolution: "node", resolveJsonModule: true } })), + timeouts: runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Enable resolveJsonModule", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { moduleResolution: "node", resolveJsonModule: true } })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates errors when ambient modules of program changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `declare module 'a' { + verifyTscWatch({ + scenario, + subScenario: "updates errors when ambient modules of program changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `declare module 'a' { type foo = number; }` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - return createWatchedSystem([aFile, config, libFile], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "Create b.ts with same content", - // Create bts with same file contents - change: sys => sys.writeFile(`${projectRoot}/b.ts`, `declare module 'a' { + }; + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: "{}" + }; + return createWatchedSystem([aFile, config, libFile], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "Create b.ts with same content", + // Create bts with same file contents + change: sys => sys.writeFile(`${projectRoot}/b.ts`, `declare module 'a' { type foo = number; }`), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Delete b.ts", - change: sys => sys.deleteFile(`${projectRoot}/b.ts`), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "Delete b.ts", + change: sys => sys.deleteFile(`${projectRoot}/b.ts`), + timeouts: runQueuedTimeoutCallbacks, + }, + ] + }); - describe("updates errors in lib file", () => { - const field = "fullscreen"; - const fieldWithoutReadonly = `interface Document { + describe("updates errors in lib file", () => { + const field = "fullscreen"; + const fieldWithoutReadonly = `interface Document { ${field}: boolean; }`; - const libFileWithDocument: File = { - path: libFile.path, - content: `${libFile.content} + const libFileWithDocument: File = { + path: libFile.path, + content: `${libFile.content} interface Document { readonly ${field}: boolean; }` - }; + }; - function verifyLibFileErrorsWith(subScenario: string, aFile: File) { - function verifyLibErrors(subScenario: string, commandLineOptions: readonly string[]) { - verifyTscWatch({ - scenario, - subScenario: `updates errors in lib file/${subScenario}`, - commandLineArgs: ["-w", aFile.path, ...commandLineOptions], - sys: () => createWatchedSystem([aFile, libFileWithDocument], { currentDirectory: projectRoot }), - changes: [ - { - caption: "Remove document declaration from file", - change: sys => sys.writeFile(aFile.path, aFile.content.replace(fieldWithoutReadonly, "var x: string;")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rever the file to contain document declaration", - change: sys => sys.writeFile(aFile.path, aFile.content), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); - } - - verifyLibErrors(`${subScenario}/with default options`, emptyArray); - verifyLibErrors(`${subScenario}/with skipLibCheck`, ["--skipLibCheck"]); - verifyLibErrors(`${subScenario}/with skipDefaultLibCheck`, ["--skipDefaultLibCheck"]); + function verifyLibFileErrorsWith(subScenario: string, aFile: File) { + function verifyLibErrors(subScenario: string, commandLineOptions: readonly string[]) { + verifyTscWatch({ + scenario, + subScenario: `updates errors in lib file/${subScenario}`, + commandLineArgs: ["-w", aFile.path, ...commandLineOptions], + sys: () => createWatchedSystem([aFile, libFileWithDocument], { currentDirectory: projectRoot }), + changes: [ + { + caption: "Remove document declaration from file", + change: sys => sys.writeFile(aFile.path, aFile.content.replace(fieldWithoutReadonly, "var x: string;")), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: "Rever the file to contain document declaration", + change: sys => sys.writeFile(aFile.path, aFile.content), + timeouts: runQueuedTimeoutCallbacks, + }, + ] + }); } - describe("when non module file changes", () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `${fieldWithoutReadonly} + verifyLibErrors(`${subScenario}/with default options`, emptyArray); + verifyLibErrors(`${subScenario}/with skipLibCheck`, ["--skipLibCheck"]); + verifyLibErrors(`${subScenario}/with skipDefaultLibCheck`, ["--skipDefaultLibCheck"]); + } + + describe("when non module file changes", () => { + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `${fieldWithoutReadonly} var y: number;` - }; - verifyLibFileErrorsWith("when non module file changes", aFile); - }); + }; + verifyLibFileErrorsWith("when non module file changes", aFile); + }); - describe("when module file with global definitions changes", () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `export {} + describe("when module file with global definitions changes", () => { + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `export {} declare global { ${fieldWithoutReadonly} var y: number; }` - }; - verifyLibFileErrorsWith("when module file with global definitions changes", aFile); - }); + }; + verifyLibFileErrorsWith("when module file with global definitions changes", aFile); }); + }); - function changeWhenLibCheckChanges(compilerOptions: CompilerOptions): TscWatchCompileChange { - const configFileContent = JSON.stringify({ compilerOptions }); - return { - caption: `Changing config to ${configFileContent}`, - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, configFileContent), - timeouts: runQueuedTimeoutCallbacks, - }; - } + function changeWhenLibCheckChanges(compilerOptions: CompilerOptions): TscWatchCompileChange { + const configFileContent = JSON.stringify({ compilerOptions }); + return { + caption: `Changing config to ${configFileContent}`, + change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, configFileContent), + timeouts: runQueuedTimeoutCallbacks, + }; + } - verifyTscWatch({ - scenario, - subScenario: "when skipLibCheck and skipDefaultLibCheck changes", - commandLineArgs: ["-w"], - sys: () => { - const field = "fullscreen"; - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `interface Document { + verifyTscWatch({ + scenario, + subScenario: "when skipLibCheck and skipDefaultLibCheck changes", + commandLineArgs: ["-w"], + sys: () => { + const field = "fullscreen"; + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `interface Document { ${field}: boolean; }` - }; - const bFile: File = { - path: `${projectRoot}/b.d.ts`, - content: `interface Document { + }; + const bFile: File = { + path: `${projectRoot}/b.d.ts`, + content: `interface Document { ${field}: boolean; }` - }; - const libFileWithDocument: File = { - path: libFile.path, - content: `${libFile.content} + }; + const libFileWithDocument: File = { + path: libFile.path, + content: `${libFile.content} interface Document { readonly ${field}: boolean; }` - }; - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - return createWatchedSystem([aFile, bFile, configFile, libFileWithDocument], { currentDirectory: projectRoot }); - }, - changes: [ - changeWhenLibCheckChanges({ skipLibCheck: true }), - changeWhenLibCheckChanges({ skipDefaultLibCheck: true }), - changeWhenLibCheckChanges({}), - changeWhenLibCheckChanges({ skipDefaultLibCheck: true }), - changeWhenLibCheckChanges({ skipLibCheck: true }), - changeWhenLibCheckChanges({}), - ] - }); + }; + const configFile: File = { + path: `${projectRoot}/tsconfig.json`, + content: "{}" + }; + return createWatchedSystem([aFile, bFile, configFile, libFileWithDocument], { currentDirectory: projectRoot }); + }, + changes: [ + changeWhenLibCheckChanges({ skipLibCheck: true }), + changeWhenLibCheckChanges({ skipDefaultLibCheck: true }), + changeWhenLibCheckChanges({}), + changeWhenLibCheckChanges({ skipDefaultLibCheck: true }), + changeWhenLibCheckChanges({ skipLibCheck: true }), + changeWhenLibCheckChanges({}), + ] + }); - verifyTscWatch({ - scenario, - subScenario: "reports errors correctly with isolatedModules", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `export const a: string = "";` - }; - const bFile: File = { - path: `${projectRoot}/b.ts`, - content: `import { a } from "./a"; + verifyTscWatch({ + scenario, + subScenario: "reports errors correctly with isolatedModules", + commandLineArgs: ["-w"], + sys: () => { + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `export const a: string = "";` + }; + const bFile: File = { + path: `${projectRoot}/b.ts`, + content: `import { a } from "./a"; const b: string = a;` - }; - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - isolatedModules: true - } - }) - }; - return createWatchedSystem([aFile, bFile, configFile, libFile], { currentDirectory: projectRoot }); + }; + const configFile: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + isolatedModules: true + } + }) + }; + return createWatchedSystem([aFile, bFile, configFile, libFile], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "Change shape of a", + change: sys => sys.writeFile(`${projectRoot}/a.ts`, `export const a: number = 1`), + timeouts: runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Change shape of a", - change: sys => sys.writeFile(`${projectRoot}/a.ts`, `export const a: number = 1`), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "reports errors correctly with file not in rootDir", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `import { x } from "../b";` - }; - const bFile: File = { - path: `/user/username/projects/b.ts`, - content: `export const x = 10;` - }; - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - rootDir: ".", - outDir: "lib" - } - }) - }; - return createWatchedSystem([aFile, bFile, configFile, libFile], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "Make changes to file a", - change: sys => sys.writeFile(`${projectRoot}/a.ts`, ` + verifyTscWatch({ + scenario, + subScenario: "reports errors correctly with file not in rootDir", + commandLineArgs: ["-w"], + sys: () => { + const aFile: File = { + path: `${projectRoot}/a.ts`, + content: `import { x } from "../b";` + }; + const bFile: File = { + path: `/user/username/projects/b.ts`, + content: `export const x = 10;` + }; + const configFile: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + rootDir: ".", + outDir: "lib" + } + }) + }; + return createWatchedSystem([aFile, bFile, configFile, libFile], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "Make changes to file a", + change: sys => sys.writeFile(`${projectRoot}/a.ts`, ` import { x } from "../b";`), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + timeouts: runQueuedTimeoutCallbacks, + }, + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates emit on jsx option change", - commandLineArgs: ["-w"], - sys: () => { - const index: File = { - path: `${projectRoot}/index.tsx`, - content: `declare var React: any;\nconst d =
;` - }; - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - jsx: "preserve" - } - }) - }; - return createWatchedSystem([index, configFile, libFile], { currentDirectory: projectRoot }); + verifyTscWatch({ + scenario, + subScenario: "updates emit on jsx option change", + commandLineArgs: ["-w"], + sys: () => { + const index: File = { + path: `${projectRoot}/index.tsx`, + content: `declare var React: any;\nconst d =
;` + }; + const configFile: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + jsx: "preserve" + } + }) + }; + return createWatchedSystem([index, configFile, libFile], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "Update 'jsx' to 'react'", + change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, '{ "compilerOptions": { "jsx": "react" } }'), + timeouts: runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Update 'jsx' to 'react'", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, '{ "compilerOptions": { "jsx": "react" } }'), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "extended source files are watched", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const firstExtendedConfigFile: File = { - path: "/a/b/first.tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - strict: true - } - }) - }; - const secondExtendedConfigFile: File = { - path: "/a/b/second.tsconfig.json", - content: JSON.stringify({ - extends: "./first.tsconfig.json" - }) - }; - const configFile: File = { - path: configFilePath, - content: JSON.stringify({ - compilerOptions: {}, - files: [commonFile1.path, commonFile2.path] - }) - }; - return createWatchedSystem([ - libFile, commonFile1, commonFile2, configFile, firstExtendedConfigFile, secondExtendedConfigFile - ]); + verifyTscWatch({ + scenario, + subScenario: "extended source files are watched", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const firstExtendedConfigFile: File = { + path: "/a/b/first.tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + strict: true + } + }) + }; + const secondExtendedConfigFile: File = { + path: "/a/b/second.tsconfig.json", + content: JSON.stringify({ + extends: "./first.tsconfig.json" + }) + }; + const configFile: File = { + path: configFilePath, + content: JSON.stringify({ + compilerOptions: {}, + files: [commonFile1.path, commonFile2.path] + }) + }; + return createWatchedSystem([ + libFile, commonFile1, commonFile2, configFile, firstExtendedConfigFile, secondExtendedConfigFile + ]); + }, + changes: [ + { + caption: "Change config to extend another config", + change: sys => sys.modifyFile(configFilePath, JSON.stringify({ + extends: "./second.tsconfig.json", + compilerOptions: {}, + files: [commonFile1.path, commonFile2.path] + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "Change config to extend another config", - change: sys => sys.modifyFile(configFilePath, JSON.stringify({ - extends: "./second.tsconfig.json", - compilerOptions: {}, - files: [commonFile1.path, commonFile2.path] - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Change first extended config", - change: sys => sys.modifyFile("/a/b/first.tsconfig.json", JSON.stringify({ - compilerOptions: { - strict: false, - } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Change second extended config", - change: sys => sys.modifyFile("/a/b/second.tsconfig.json", JSON.stringify({ - extends: "./first.tsconfig.json", - compilerOptions: { - strictNullChecks: true, - } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Change config to stop extending another config", - change: sys => sys.modifyFile(configFilePath, JSON.stringify({ - compilerOptions: {}, - files: [commonFile1.path, commonFile2.path] - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - ] - }); + { + caption: "Change first extended config", + change: sys => sys.modifyFile("/a/b/first.tsconfig.json", JSON.stringify({ + compilerOptions: { + strict: false, + } + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Change second extended config", + change: sys => sys.modifyFile("/a/b/second.tsconfig.json", JSON.stringify({ + extends: "./first.tsconfig.json", + compilerOptions: { + strictNullChecks: true, + } + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Change config to stop extending another config", + change: sys => sys.modifyFile(configFilePath, JSON.stringify({ + compilerOptions: {}, + files: [commonFile1.path, commonFile2.path] + })), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + ] + }); - verifyTscWatch({ - scenario, - subScenario: "when creating new file in symlinked folder", - commandLineArgs: ["-w", "-p", ".", "--extendedDiagnostics"], - sys: () => { - const module1: File = { - path: `${projectRoot}/client/folder1/module1.ts`, - content: `export class Module1Class { }` - }; - const module2: File = { - path: `${projectRoot}/folder2/module2.ts`, - content: `import * as M from "folder1/module1";` - }; - const symlink: SymLink = { - path: `${projectRoot}/client/linktofolder2`, - symLink: `${projectRoot}/folder2`, - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - baseUrl: "client", - paths: { "*": ["*"] }, - }, - include: ["client/**/*", "folder2"] - }) - }; - return createWatchedSystem([module1, module2, symlink, config, libFile], { currentDirectory: projectRoot }); + verifyTscWatch({ + scenario, + subScenario: "when creating new file in symlinked folder", + commandLineArgs: ["-w", "-p", ".", "--extendedDiagnostics"], + sys: () => { + const module1: File = { + path: `${projectRoot}/client/folder1/module1.ts`, + content: `export class Module1Class { }` + }; + const module2: File = { + path: `${projectRoot}/folder2/module2.ts`, + content: `import * as M from "folder1/module1";` + }; + const symlink: SymLink = { + path: `${projectRoot}/client/linktofolder2`, + symLink: `${projectRoot}/folder2`, + }; + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + baseUrl: "client", + paths: { "*": ["*"] }, + }, + include: ["client/**/*", "folder2"] + }) + }; + return createWatchedSystem([module1, module2, symlink, config, libFile], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "Add module3 to folder2", + change: sys => sys.writeFile(`${projectRoot}/client/linktofolder2/module3.ts`, `import * as M from "folder1/module1";`), + timeouts: checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "Add module3 to folder2", - change: sys => sys.writeFile(`${projectRoot}/client/linktofolder2/module3.ts`, `import * as M from "folder1/module1";`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "when new file is added to the referenced project", - commandLineArgs: ["-w", "-p", `${projectRoot}/projects/project2/tsconfig.json`, "--extendedDiagnostics"], - sys: () => { - const config1: File = { - path: `${projectRoot}/projects/project1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - exclude: ["temp"] - }) - }; - const class1: File = { - path: `${projectRoot}/projects/project1/class1.ts`, - content: `class class1 {}` - }; - // Built file - const class1Dt: File = { - path: `${projectRoot}/projects/project1/class1.d.ts`, - content: `declare class class1 {}` - }; - const config2: File = { - path: `${projectRoot}/projects/project2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - references: [ - { path: "../project1" } - ] - }) - }; - const class2: File = { - path: `${projectRoot}/projects/project2/class2.ts`, - content: `class class2 {}` - }; - return createWatchedSystem([config1, class1, config2, class2, libFile, class1Dt]); + verifyTscWatch({ + scenario, + subScenario: "when new file is added to the referenced project", + commandLineArgs: ["-w", "-p", `${projectRoot}/projects/project2/tsconfig.json`, "--extendedDiagnostics"], + sys: () => { + const config1: File = { + path: `${projectRoot}/projects/project1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true + }, + exclude: ["temp"] + }) + }; + const class1: File = { + path: `${projectRoot}/projects/project1/class1.ts`, + content: `class class1 {}` + }; + // Built file + const class1Dt: File = { + path: `${projectRoot}/projects/project1/class1.d.ts`, + content: `declare class class1 {}` + }; + const config2: File = { + path: `${projectRoot}/projects/project2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true + }, + references: [ + { path: "../project1" } + ] + }) + }; + const class2: File = { + path: `${projectRoot}/projects/project2/class2.ts`, + content: `class class2 {}` + }; + return createWatchedSystem([config1, class1, config2, class2, libFile, class1Dt]); + }, + changes: [ + { + caption: "Add class3 to project1", + change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.ts`, `class class3 {}`), + timeouts: checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "Add class3 to project1", - change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.ts`, `class class3 {}`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Add output of class3", - change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Add excluded file to project1", - change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), - timeouts: sys => sys.checkTimeoutQueueLength(0), - }, - { - caption: "Delete output of class3", - change: sys => sys.deleteFile(`${projectRoot}/projects/project1/class3.d.ts`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Add output of class3", - change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - ] - }); + { + caption: "Add output of class3", + change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Add excluded file to project1", + change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), + timeouts: sys => sys.checkTimeoutQueueLength(0), + }, + { + caption: "Delete output of class3", + change: sys => sys.deleteFile(`${projectRoot}/projects/project1/class3.d.ts`), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Add output of class3", + change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + ] }); +}); } diff --git a/src/testRunner/unittests/tscWatch/projectsWithReferences.ts b/src/testRunner/unittests/tscWatch/projectsWithReferences.ts index 83b65de1bda2d..f2a60e61f6e4f 100644 --- a/src/testRunner/unittests/tscWatch/projectsWithReferences.ts +++ b/src/testRunner/unittests/tscWatch/projectsWithReferences.ts @@ -1,414 +1,414 @@ namespace ts.tscWatch { - describe("unittests:: tsc-watch:: projects with references: invoking when references are already built", () => { - verifyTscWatch({ - scenario: "projectsWithReferences", - subScenario: "on sample project", - sys: () => createSystemWithSolutionBuild( - ["tests"], - [ - libFile, - TestFSWithWatch.getTsBuildProjectFile("sample1", "core/tsconfig.json"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "core/index.ts"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "core/anotherModule.ts"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "core/some_decl.d.ts"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/tsconfig.json"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/index.ts"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/tsconfig.json"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/index.ts"), - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/sample1` } - ), - commandLineArgs: ["-w", "-p", "tests"], - changes: [ - { - caption: "local edit in logic ts, and build logic", - change: sys => { - sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/index.ts"), `function foo() { }`); - const solutionBuilder = createSolutionBuilder(sys, ["logic"]); - solutionBuilder.build(); - }, - // not ideal, but currently because of d.ts but no new file is written - // There will be timeout queued even though file contents are same - timeouts: checkSingleTimeoutQueueLengthAndRun +describe("unittests:: tsc-watch:: projects with references: invoking when references are already built", () => { + verifyTscWatch({ + scenario: "projectsWithReferences", + subScenario: "on sample project", + sys: () => createSystemWithSolutionBuild( + ["tests"], + [ + libFile, + TestFSWithWatch.getTsBuildProjectFile("sample1", "core/tsconfig.json"), + TestFSWithWatch.getTsBuildProjectFile("sample1", "core/index.ts"), + TestFSWithWatch.getTsBuildProjectFile("sample1", "core/anotherModule.ts"), + TestFSWithWatch.getTsBuildProjectFile("sample1", "core/some_decl.d.ts"), + TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/tsconfig.json"), + TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/index.ts"), + TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/tsconfig.json"), + TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/index.ts"), + ], + { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/sample1` } + ), + commandLineArgs: ["-w", "-p", "tests"], + changes: [ + { + caption: "local edit in logic ts, and build logic", + change: sys => { + sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/index.ts"), `function foo() { }`); + const solutionBuilder = createSolutionBuilder(sys, ["logic"]); + solutionBuilder.build(); }, - { - caption: "non local edit in logic ts, and build logic", - change: sys => { - sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/index.ts"), `export function gfoo() { }`); - const solutionBuilder = createSolutionBuilder(sys, ["logic"]); - solutionBuilder.build(); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun + // not ideal, but currently because of d.ts but no new file is written + // There will be timeout queued even though file contents are same + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "non local edit in logic ts, and build logic", + change: sys => { + sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/index.ts"), `export function gfoo() { }`); + const solutionBuilder = createSolutionBuilder(sys, ["logic"]); + solutionBuilder.build(); }, - { - caption: "change in project reference config file builds correctly", - change: sys => { - sys.writeFile(TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/tsconfig.json"), JSON.stringify({ - compilerOptions: { composite: true, declaration: true, declarationDir: "decls" }, - references: [{ path: "../core" }] - })); - const solutionBuilder = createSolutionBuilder(sys, ["logic"]); - solutionBuilder.build(); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "change in project reference config file builds correctly", + change: sys => { + sys.writeFile(TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/tsconfig.json"), JSON.stringify({ + compilerOptions: { composite: true, declaration: true, declarationDir: "decls" }, + references: [{ path: "../core" }] + })); + const solutionBuilder = createSolutionBuilder(sys, ["logic"]); + solutionBuilder.build(); }, - ], - baselineDependencies: true - }); + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + ], + baselineDependencies: true + }); - function changeCompilerOpitonsPaths(sys: WatchedSystem, config: string, newPaths: object) { - const configJson = JSON.parse(sys.readFile(config)!); - configJson.compilerOptions.paths = newPaths; - sys.writeFile(config, JSON.stringify(configJson)); - } + function changeCompilerOpitonsPaths(sys: WatchedSystem, config: string, newPaths: object) { + const configJson = JSON.parse(sys.readFile(config)!); + configJson.compilerOptions.paths = newPaths; + sys.writeFile(config, JSON.stringify(configJson)); + } - verifyTscWatch({ - scenario: "projectsWithReferences", - subScenario: "on transitive references", - sys: () => createSystemWithSolutionBuild( - ["tsconfig.c.json"], - [ - libFile, - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "a.ts"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "b.ts"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "c.ts"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } - ), - commandLineArgs: ["-w", "-p", "tsconfig.c.json"], - changes: [ - { - caption: "non local edit b ts, and build b", - change: sys => { - sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b.ts"), `export function gfoo() { }`); - const solutionBuilder = createSolutionBuilder(sys, ["tsconfig.b.json"]); - solutionBuilder.build(); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun + verifyTscWatch({ + scenario: "projectsWithReferences", + subScenario: "on transitive references", + sys: () => createSystemWithSolutionBuild( + ["tsconfig.c.json"], + [ + libFile, + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"), + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json"), + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"), + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "a.ts"), + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "b.ts"), + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "c.ts"), + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), + ], + { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } + ), + commandLineArgs: ["-w", "-p", "tsconfig.c.json"], + changes: [ + { + caption: "non local edit b ts, and build b", + change: sys => { + sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b.ts"), `export function gfoo() { }`); + const solutionBuilder = createSolutionBuilder(sys, ["tsconfig.b.json"]); + solutionBuilder.build(); }, - { - caption: "edit on config file", - change: sys => { - sys.ensureFileOrFolder({ - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"), - content: sys.readFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))! - }); - changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./nrefs/*"] }); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "edit on config file", + change: sys => { + sys.ensureFileOrFolder({ + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"), + content: sys.readFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))! + }); + changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./nrefs/*"] }); }, + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert config file edit", + change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./refs/*"] }), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "edit in referenced config file", + change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./nrefs/*"] }), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert referenced config file edit", + change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./refs/*"] }), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "deleting referenced config file", + change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json")), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert deleting referenced config file", + change: sys => sys.ensureFileOrFolder(TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json")), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "deleting transitively referenced config file", + change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.a.json")), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert deleting transitively referenced config file", + change: sys => sys.ensureFileOrFolder(TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json")), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + ], + baselineDependencies: true, + }); + + verifyTscWatch({ + scenario: "projectsWithReferences", + subScenario: "when referenced project uses different module resolution", + sys: () => createSystemWithSolutionBuild( + ["tsconfig.c.json"], + [ + libFile, + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"), { - caption: "Revert config file edit", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./refs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), + content: JSON.stringify({ + compilerOptions: { composite: true, moduleResolution: "classic" }, + files: ["b.ts"], + references: [{ path: "tsconfig.a.json" }] + }) }, + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"), + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "a.ts"), { - caption: "edit in referenced config file", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./nrefs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b.ts"), + content: `import {A} from "a";export const b = new A();` }, + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "c.ts"), + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), + ], + { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } + ), + commandLineArgs: ["-w", "-p", "tsconfig.c.json"], + changes: emptyArray, + baselineDependencies: true, + }); + + verifyTscWatch({ + scenario: "projectsWithReferences", + subScenario: "on transitive references in different folders", + sys: () => createSystemWithSolutionBuild( + ["c"], + [ + libFile, { - caption: "Revert referenced config file edit", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./refs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), + content: JSON.stringify({ + compilerOptions: { composite: true }, + files: ["index.ts"] + }), }, { - caption: "deleting referenced config file", - change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json")), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), + content: JSON.stringify({ + compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, + files: ["index.ts"], + references: [{ path: `../a` }] + }), }, { - caption: "Revert deleting referenced config file", - change: sys => sys.ensureFileOrFolder(TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json")), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), + content: JSON.stringify({ + compilerOptions: { baseUrl: "./", paths: { "@ref/*": ["../refs/*"] } }, + files: ["index.ts"], + references: [{ path: `../b` }] + }), }, { - caption: "deleting transitively referenced config file", - change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.a.json")), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"), + content: `export class A {}`, }, { - caption: "Revert deleting transitively referenced config file", - change: sys => sys.ensureFileOrFolder(TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json")), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - ], - baselineDependencies: true, - }); - - verifyTscWatch({ - scenario: "projectsWithReferences", - subScenario: "when referenced project uses different module resolution", - sys: () => createSystemWithSolutionBuild( - ["tsconfig.c.json"], - [ - libFile, - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"), - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), - content: JSON.stringify({ - compilerOptions: { composite: true, moduleResolution: "classic" }, - files: ["b.ts"], - references: [{ path: "tsconfig.a.json" }] - }) - }, - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "a.ts"), - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b.ts"), - content: `import {A} from "a";export const b = new A();` - }, - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "c.ts"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } - ), - commandLineArgs: ["-w", "-p", "tsconfig.c.json"], - changes: emptyArray, - baselineDependencies: true, - }); - - verifyTscWatch({ - scenario: "projectsWithReferences", - subScenario: "on transitive references in different folders", - sys: () => createSystemWithSolutionBuild( - ["c"], - [ - libFile, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), - content: JSON.stringify({ - compilerOptions: { composite: true }, - files: ["index.ts"] - }), - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), - content: JSON.stringify({ - compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, - files: ["index.ts"], - references: [{ path: `../a` }] - }), - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), - content: JSON.stringify({ - compilerOptions: { baseUrl: "./", paths: { "@ref/*": ["../refs/*"] } }, - files: ["index.ts"], - references: [{ path: `../b` }] - }), - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"), - content: `export class A {}`, - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), - content: `import {A} from '@ref/a'; + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), + content: `import {A} from '@ref/a'; export const b = new A();`, - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"), - content: `import {b} from '../b'; + }, + { + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"), + content: `import {b} from '../b'; import {X} from "@ref/a"; b; X;`, - }, - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } - ), - commandLineArgs: ["-w", "-p", "c"], - changes: [ - { - caption: "non local edit b ts, and build b", - change: sys => { - sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`); - const solutionBuilder = createSolutionBuilder(sys, ["b"]); - solutionBuilder.build(); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun }, - { - caption: "edit on config file", - change: sys => { - sys.ensureFileOrFolder({ - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"), - content: sys.readFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))! - }); - changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), + ], + { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } + ), + commandLineArgs: ["-w", "-p", "c"], + changes: [ + { + caption: "non local edit b ts, and build b", + change: sys => { + sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`); + const solutionBuilder = createSolutionBuilder(sys, ["b"]); + solutionBuilder.build(); }, - { - caption: "Revert config file edit", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "edit on config file", + change: sys => { + sys.ensureFileOrFolder({ + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"), + content: sys.readFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))! + }); + changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }); }, + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert config file edit", + change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "edit in referenced config file", + change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert referenced config file edit", + change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "deleting referenced config file", + change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + { + caption: "Revert deleting referenced config file", + change: sys => sys.writeFile( + TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), + JSON.stringify({ + compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, + files: ["index.ts"], + references: [{ path: `../a` }] + }) + ), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + { + caption: "deleting transitively referenced config file", + change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + { + caption: "Revert deleting transitively referenced config file", + change: sys => sys.writeFile( + TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), + JSON.stringify({ + compilerOptions: { composite: true }, + files: ["index.ts"] + }), + ), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + ], + baselineDependencies: true, + }); + + verifyTscWatch({ + scenario: "projectsWithReferences", + subScenario: "on transitive references in different folders with no files clause", + sys: () => createSystemWithSolutionBuild( + ["c"], + [ + libFile, { - caption: "edit in referenced config file", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), + content: JSON.stringify({ compilerOptions: { composite: true } }), }, { - caption: "Revert referenced config file edit", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), + content: JSON.stringify({ + compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, + references: [{ path: `../a` }] + }), }, { - caption: "deleting referenced config file", - change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), + content: JSON.stringify({ + compilerOptions: { baseUrl: "./", paths: { "@ref/*": ["../refs/*"] } }, + references: [{ path: `../b` }] + }), }, { - caption: "Revert deleting referenced config file", - change: sys => sys.writeFile( - TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), - JSON.stringify({ - compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, - files: ["index.ts"], - references: [{ path: `../a` }] - }) - ), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"), + content: `export class A {}`, }, { - caption: "deleting transitively referenced config file", - change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), + content: `import {A} from '@ref/a'; +export const b = new A();`, }, { - caption: "Revert deleting transitively referenced config file", - change: sys => sys.writeFile( - TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), - JSON.stringify({ - compilerOptions: { composite: true }, - files: ["index.ts"] - }), - ), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) - }, - ], - baselineDependencies: true, - }); - - verifyTscWatch({ - scenario: "projectsWithReferences", - subScenario: "on transitive references in different folders with no files clause", - sys: () => createSystemWithSolutionBuild( - ["c"], - [ - libFile, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), - content: JSON.stringify({ compilerOptions: { composite: true } }), - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), - content: JSON.stringify({ - compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, - references: [{ path: `../a` }] - }), - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), - content: JSON.stringify({ - compilerOptions: { baseUrl: "./", paths: { "@ref/*": ["../refs/*"] } }, - references: [{ path: `../b` }] - }), - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"), - content: `export class A {}`, - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), - content: `import {A} from '@ref/a'; -export const b = new A();`, - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"), - content: `import {b} from '../b'; + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"), + content: `import {b} from '../b'; import {X} from "@ref/a"; b; X;`, - }, - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } - ), - commandLineArgs: ["-w", "-p", "c"], - changes: [ - { - caption: "non local edit b ts, and build b", - change: sys => { - sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`); - const solutionBuilder = createSolutionBuilder(sys, ["b"]); - solutionBuilder.build(); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - { - caption: "edit on config file", - change: sys => { - sys.ensureFileOrFolder({ - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"), - content: sys.readFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))! - }); - changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - { - caption: "Revert config file edit", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun }, - { - caption: "edit in referenced config file", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - { - caption: "Revert referenced config file edit", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - { - caption: "deleting referenced config file", - change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) - }, - { - caption: "Revert deleting referenced config file", - change: sys => sys.writeFile( - TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), - JSON.stringify({ - compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, - references: [{ path: `../a` }] - }) - ), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) - }, - { - caption: "deleting transitively referenced config file", - change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), + ], + { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } + ), + commandLineArgs: ["-w", "-p", "c"], + changes: [ + { + caption: "non local edit b ts, and build b", + change: sys => { + sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`); + const solutionBuilder = createSolutionBuilder(sys, ["b"]); + solutionBuilder.build(); }, - { - caption: "Revert deleting transitively referenced config file", - change: sys => sys.writeFile( - TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), - JSON.stringify({ compilerOptions: { composite: true } }), - ), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "edit on config file", + change: sys => { + sys.ensureFileOrFolder({ + path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"), + content: sys.readFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))! + }); + changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }); }, - ], - baselineDependencies: true, - }); + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert config file edit", + change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "edit in referenced config file", + change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert referenced config file edit", + change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }), + timeouts: checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "deleting referenced config file", + change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + { + caption: "Revert deleting referenced config file", + change: sys => sys.writeFile( + TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), + JSON.stringify({ + compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, + references: [{ path: `../a` }] + }) + ), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + { + caption: "deleting transitively referenced config file", + change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + { + caption: "Revert deleting transitively referenced config file", + change: sys => sys.writeFile( + TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), + JSON.stringify({ compilerOptions: { composite: true } }), + ), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + ], + baselineDependencies: true, }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/tscWatch/resolutionCache.ts b/src/testRunner/unittests/tscWatch/resolutionCache.ts index b8f065ae41b82..72b6d995a4f19 100644 --- a/src/testRunner/unittests/tscWatch/resolutionCache.ts +++ b/src/testRunner/unittests/tscWatch/resolutionCache.ts @@ -1,532 +1,532 @@ namespace ts.tscWatch { - describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution caching", () => { - const scenario = "resolutionCache"; - it("works", () => { - const root = { - path: "/a/d/f0.ts", - content: `import {x} from "f1"` - }; - const imported = { - path: "/a/f1.ts", - content: `foo()` - }; - - const files = [root, imported, libFile]; - const host = createWatchedSystem(files); - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); - - const f1IsNotModule = getDiagnosticOfFileFromProgram(watch.getCurrentProgram().getProgram(), root.path, root.content.indexOf('"f1"'), '"f1"'.length, Diagnostics.File_0_is_not_a_module, imported.path); - const cannotFindFoo = getDiagnosticOfFileFromProgram(watch.getCurrentProgram().getProgram(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo"); - - // ensure that imported file was found - checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]); - - const originalFileExists = host.fileExists; - { - const newContent = `import {x} from "f1" +describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution caching", () => { + const scenario = "resolutionCache"; + it("works", () => { + const root = { + path: "/a/d/f0.ts", + content: `import {x} from "f1"` + }; + const imported = { + path: "/a/f1.ts", + content: `foo()` + }; + + const files = [root, imported, libFile]; + const host = createWatchedSystem(files); + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); + + const f1IsNotModule = getDiagnosticOfFileFromProgram(watch.getCurrentProgram().getProgram(), root.path, root.content.indexOf('"f1"'), '"f1"'.length, Diagnostics.File_0_is_not_a_module, imported.path); + const cannotFindFoo = getDiagnosticOfFileFromProgram(watch.getCurrentProgram().getProgram(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo"); + + // ensure that imported file was found + checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]); + + const originalFileExists = host.fileExists; + { + const newContent = `import {x} from "f1" var x: string = 1;`; - root.content = newContent; - host.writeFile(root.path, root.content); - - // patch fileExists to make sure that disk is not touched - host.fileExists = notImplemented; - - // trigger synchronization to make sure that import will be fetched from the cache - host.runQueuedTimeoutCallbacks(); - - // ensure file has correct number of errors after edit - checkOutputErrorsIncremental(host, [ - f1IsNotModule, - getDiagnosticOfFileFromProgram(watch.getCurrentProgram().getProgram(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, "number", "string"), - cannotFindFoo - ]); - } - { - let fileExistsIsCalled = false; - host.fileExists = (fileName): boolean => { - if (fileName === "lib.d.ts") { - return false; - } - fileExistsIsCalled = true; - assert.isTrue(fileName.indexOf("/f2.") !== -1); - return originalFileExists.call(host, fileName); - }; - - root.content = `import {x} from "f2"`; - host.writeFile(root.path, root.content); - - // trigger synchronization to make sure that system will try to find 'f2' module on disk - host.runQueuedTimeoutCallbacks(); - - // ensure file has correct number of errors after edit - checkOutputErrorsIncremental(host, [ - getDiagnosticModuleNotFoundOfFile(watch.getCurrentProgram().getProgram(), root, "f2") - ]); - - assert.isTrue(fileExistsIsCalled); - } - { - let fileExistsCalled = false; - host.fileExists = (fileName): boolean => { - if (fileName === "lib.d.ts") { - return false; - } - fileExistsCalled = true; - assert.isTrue(fileName.indexOf("/f1.") !== -1); - return originalFileExists.call(host, fileName); - }; - - const newContent = `import {x} from "f1"`; - root.content = newContent; - - host.writeFile(root.path, root.content); - host.runQueuedTimeoutCallbacks(); - - checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]); - assert.isTrue(fileExistsCalled); - } - }); + root.content = newContent; + host.writeFile(root.path, root.content); - it("loads missing files from disk", () => { - const root = { - path: `/a/foo.ts`, - content: `import {x} from "bar"` - }; + // patch fileExists to make sure that disk is not touched + host.fileExists = notImplemented; - const imported = { - path: `/a/bar.d.ts`, - content: `export const y = 1;` - }; - - const files = [root, libFile]; - const host = createWatchedSystem(files); - const originalFileExists = host.fileExists; + // trigger synchronization to make sure that import will be fetched from the cache + host.runQueuedTimeoutCallbacks(); - let fileExistsCalledForBar = false; - host.fileExists = fileName => { + // ensure file has correct number of errors after edit + checkOutputErrorsIncremental(host, [ + f1IsNotModule, + getDiagnosticOfFileFromProgram(watch.getCurrentProgram().getProgram(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, "number", "string"), + cannotFindFoo + ]); + } + { + let fileExistsIsCalled = false; + host.fileExists = (fileName): boolean => { if (fileName === "lib.d.ts") { return false; } - if (!fileExistsCalledForBar) { - fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; - } - + fileExistsIsCalled = true; + assert.isTrue(fileName.indexOf("/f2.") !== -1); return originalFileExists.call(host, fileName); }; - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); - - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); - checkOutputErrorsInitial(host, [ - getDiagnosticModuleNotFoundOfFile(watch.getCurrentProgram().getProgram(), root, "bar") - ]); - - fileExistsCalledForBar = false; - root.content = `import {y} from "bar"`; + root.content = `import {x} from "f2"`; host.writeFile(root.path, root.content); - host.writeFile(imported.path, imported.content); + // trigger synchronization to make sure that system will try to find 'f2' module on disk host.runQueuedTimeoutCallbacks(); - checkOutputErrorsIncremental(host, emptyArray); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - }); - - it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => { - const root = { - path: `/a/foo.ts`, - content: `import {x} from "bar"` - }; - const imported = { - path: `/a/bar.d.ts`, - content: `export const y = 1;export const x = 10;` - }; + // ensure file has correct number of errors after edit + checkOutputErrorsIncremental(host, [ + getDiagnosticModuleNotFoundOfFile(watch.getCurrentProgram().getProgram(), root, "f2") + ]); - const host = createWatchedSystem([root, libFile, imported]); - const originalFileExists = host.fileExists; - let fileExistsCalledForBar = false; - host.fileExists = fileName => { + assert.isTrue(fileExistsIsCalled); + } + { + let fileExistsCalled = false; + host.fileExists = (fileName): boolean => { if (fileName === "lib.d.ts") { return false; } - if (!fileExistsCalledForBar) { - fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; - } + fileExistsCalled = true; + assert.isTrue(fileName.indexOf("/f1.") !== -1); return originalFileExists.call(host, fileName); }; - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); - - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); - checkOutputErrorsInitial(host, emptyArray); + const newContent = `import {x} from "f1"`; + root.content = newContent; - fileExistsCalledForBar = false; - host.deleteFile(imported.path); + host.writeFile(root.path, root.content); host.runQueuedTimeoutCallbacks(); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - checkOutputErrorsIncremental(host, [ - getDiagnosticModuleNotFoundOfFile(watch.getCurrentProgram().getProgram(), root, "bar") - ]); - fileExistsCalledForBar = false; - host.writeFile(imported.path, imported.content); - host.checkTimeoutQueueLengthAndRun(1); // Scheduled invalidation of resolutions - host.checkTimeoutQueueLengthAndRun(1); // Actual update - checkOutputErrorsIncremental(host, emptyArray); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - }); + checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]); + assert.isTrue(fileExistsCalled); + } + }); - verifyTscWatch({ - scenario, - subScenario: "works when module resolution changes to ambient module", - commandLineArgs: ["-w", "/a/b/foo.ts"], - sys: () => createWatchedSystem([{ - path: "/a/b/foo.ts", - content: `import * as fs from "fs";` - }, libFile], { currentDirectory: "/a/b" }), - changes: [ - { - caption: "npm install node types", - change: sys => { - sys.ensureFileOrFolder({ - path: "/a/b/node_modules/@types/node/package.json", - content: ` + it("loads missing files from disk", () => { + const root = { + path: `/a/foo.ts`, + content: `import {x} from "bar"` + }; + + const imported = { + path: `/a/bar.d.ts`, + content: `export const y = 1;` + }; + + const files = [root, libFile]; + const host = createWatchedSystem(files); + const originalFileExists = host.fileExists; + + let fileExistsCalledForBar = false; + host.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + + return originalFileExists.call(host, fileName); + }; + + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); + + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + checkOutputErrorsInitial(host, [ + getDiagnosticModuleNotFoundOfFile(watch.getCurrentProgram().getProgram(), root, "bar") + ]); + + fileExistsCalledForBar = false; + root.content = `import {y} from "bar"`; + host.writeFile(root.path, root.content); + host.writeFile(imported.path, imported.content); + + host.runQueuedTimeoutCallbacks(); + checkOutputErrorsIncremental(host, emptyArray); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + }); + + it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => { + const root = { + path: `/a/foo.ts`, + content: `import {x} from "bar"` + }; + + const imported = { + path: `/a/bar.d.ts`, + content: `export const y = 1;export const x = 10;` + }; + + const host = createWatchedSystem([root, libFile, imported]); + const originalFileExists = host.fileExists; + let fileExistsCalledForBar = false; + host.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + return originalFileExists.call(host, fileName); + }; + + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); + + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + checkOutputErrorsInitial(host, emptyArray); + + fileExistsCalledForBar = false; + host.deleteFile(imported.path); + host.runQueuedTimeoutCallbacks(); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + checkOutputErrorsIncremental(host, [ + getDiagnosticModuleNotFoundOfFile(watch.getCurrentProgram().getProgram(), root, "bar") + ]); + + fileExistsCalledForBar = false; + host.writeFile(imported.path, imported.content); + host.checkTimeoutQueueLengthAndRun(1); // Scheduled invalidation of resolutions + host.checkTimeoutQueueLengthAndRun(1); // Actual update + checkOutputErrorsIncremental(host, emptyArray); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + }); + + verifyTscWatch({ + scenario, + subScenario: "works when module resolution changes to ambient module", + commandLineArgs: ["-w", "/a/b/foo.ts"], + sys: () => createWatchedSystem([{ + path: "/a/b/foo.ts", + content: `import * as fs from "fs";` + }, libFile], { currentDirectory: "/a/b" }), + changes: [ + { + caption: "npm install node types", + change: sys => { + sys.ensureFileOrFolder({ + path: "/a/b/node_modules/@types/node/package.json", + content: ` { "main": "" } ` - }); - sys.ensureFileOrFolder({ - path: "/a/b/node_modules/@types/node/index.d.ts", - content: ` + }); + sys.ensureFileOrFolder({ + path: "/a/b/node_modules/@types/node/index.d.ts", + content: ` declare module "fs" { export interface Stats { isFile(): boolean; } }` - }); - }, - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + }); + }, + timeouts: runQueuedTimeoutCallbacks, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "works when included file with ambient module changes", - commandLineArgs: ["--w", "/a/b/foo.ts", "/a/b/bar.d.ts"], - sys: () => { - const root = { - path: "/a/b/foo.ts", - content: ` + verifyTscWatch({ + scenario, + subScenario: "works when included file with ambient module changes", + commandLineArgs: ["--w", "/a/b/foo.ts", "/a/b/bar.d.ts"], + sys: () => { + const root = { + path: "/a/b/foo.ts", + content: ` import * as fs from "fs"; import * as u from "url"; ` - }; + }; - const file = { - path: "/a/b/bar.d.ts", - content: ` + const file = { + path: "/a/b/bar.d.ts", + content: ` declare module "url" { export interface Url { href?: string; } } ` - }; - return createWatchedSystem([root, file, libFile], { currentDirectory: "/a/b" }); - }, - changes: [ - { - caption: "Add fs definition", - change: sys => sys.appendFile("/a/b/bar.d.ts", ` + }; + return createWatchedSystem([root, file, libFile], { currentDirectory: "/a/b" }); + }, + changes: [ + { + caption: "Add fs definition", + change: sys => sys.appendFile("/a/b/bar.d.ts", ` declare module "fs" { export interface Stats { isFile(): boolean; } } `), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + timeouts: runQueuedTimeoutCallbacks, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "works when reusing program with files from external library", - commandLineArgs: ["--w", "-p", "/a/b/projects/myProject/src"], - sys: () => { - const configDir = "/a/b/projects/myProject/src/"; - const file1: File = { - path: configDir + "file1.ts", - content: 'import module1 = require("module1");\nmodule1("hello");' - }; - const file2: File = { - path: configDir + "file2.ts", - content: 'import module11 = require("module1");\nmodule11("hello");' - }; - const module1: File = { - path: "/a/b/projects/myProject/node_modules/module1/index.js", - content: "module.exports = options => { return options.toString(); }" - }; - const configFile: File = { - path: configDir + "tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - allowJs: true, - rootDir: ".", - outDir: "../dist", - moduleResolution: "node", - maxNodeModuleJsDepth: 1 - } - }) - }; - return createWatchedSystem([file1, file2, module1, libFile, configFile], { currentDirectory: "/a/b/projects/myProject/" }); - }, - changes: [ - { - caption: "Add new line to file1", - change: sys => sys.appendFile("/a/b/projects/myProject/src/file1.ts", "\n;"), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + verifyTscWatch({ + scenario, + subScenario: "works when reusing program with files from external library", + commandLineArgs: ["--w", "-p", "/a/b/projects/myProject/src"], + sys: () => { + const configDir = "/a/b/projects/myProject/src/"; + const file1: File = { + path: configDir + "file1.ts", + content: 'import module1 = require("module1");\nmodule1("hello");' + }; + const file2: File = { + path: configDir + "file2.ts", + content: 'import module11 = require("module1");\nmodule11("hello");' + }; + const module1: File = { + path: "/a/b/projects/myProject/node_modules/module1/index.js", + content: "module.exports = options => { return options.toString(); }" + }; + const configFile: File = { + path: configDir + "tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + allowJs: true, + rootDir: ".", + outDir: "../dist", + moduleResolution: "node", + maxNodeModuleJsDepth: 1 + } + }) + }; + return createWatchedSystem([file1, file2, module1, libFile, configFile], { currentDirectory: "/a/b/projects/myProject/" }); + }, + changes: [ + { + caption: "Add new line to file1", + change: sys => sys.appendFile("/a/b/projects/myProject/src/file1.ts", "\n;"), + timeouts: runQueuedTimeoutCallbacks, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "works when renaming node_modules folder that already contains @types folder", - commandLineArgs: ["--w", `${projectRoot}/a.ts`], - sys: () => { - const file: File = { - path: `${projectRoot}/a.ts`, - content: `import * as q from "qqq";` - }; - const module: File = { - path: `${projectRoot}/node_modules2/@types/qqq/index.d.ts`, - content: "export {}" - }; - return createWatchedSystem([file, libFile, module], { currentDirectory: projectRoot }); + verifyTscWatch({ + scenario, + subScenario: "works when renaming node_modules folder that already contains @types folder", + commandLineArgs: ["--w", `${projectRoot}/a.ts`], + sys: () => { + const file: File = { + path: `${projectRoot}/a.ts`, + content: `import * as q from "qqq";` + }; + const module: File = { + path: `${projectRoot}/node_modules2/@types/qqq/index.d.ts`, + content: "export {}" + }; + return createWatchedSystem([file, libFile, module], { currentDirectory: projectRoot }); + }, + changes: [ + { + caption: "npm install", + change: sys => sys.renameFolder(`${projectRoot}/node_modules2`, `${projectRoot}/node_modules`), + timeouts: runQueuedTimeoutCallbacks, + } + ] + }); + + describe("ignores files/folder changes in node_modules that start with '.'", () => { + function verifyIgnore(subScenario: string, commandLineArgs: readonly string[]) { + verifyTscWatch({ + scenario, + subScenario: `ignores changes in node_modules that start with dot/${subScenario}`, + commandLineArgs, + sys: () => { + const file1: File = { + path: `${projectRoot}/test.ts`, + content: `import { x } from "somemodule";` + }; + const file2: File = { + path: `${projectRoot}/node_modules/somemodule/index.d.ts`, + content: `export const x = 10;` + }; + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: "{}" + }; + return createWatchedSystem([libFile, file1, file2, config]); + }, + changes: [ + { + caption: "npm install file and folder that start with '.'", + change: sys => sys.ensureFileOrFolder({ + path: `${projectRoot}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, + content: JSON.stringify({ something: 10 }) + }), + timeouts: sys => sys.checkTimeoutQueueLength(0), + } + ] + }); + } + verifyIgnore("watch without configFile", ["--w", `${projectRoot}/test.ts`]); + verifyIgnore("watch with configFile", ["--w", "-p", `${projectRoot}/tsconfig.json`]); + }); + + verifyTscWatch({ + scenario, + subScenario: "when types in compiler option are global and installed at later point", + commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], + sys: () => { + const app: File = { + path: `${projectRoot}/lib/app.ts`, + content: `myapp.component("hello");` + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + types: ["@myapp/ts-types"] + } + }) + }; + return createWatchedSystem([app, tsconfig, libFile]); + }, + changes: [ + { + caption: "npm install ts-types", + change: sys => { + sys.ensureFileOrFolder({ + path: `${projectRoot}/node_modules/@myapp/ts-types/package.json`, + content: JSON.stringify({ + version: "1.65.1", + types: "types/somefile.define.d.ts" + }) + }); + sys.ensureFileOrFolder({ + path: `${projectRoot}/node_modules/@myapp/ts-types/types/somefile.define.d.ts`, + content: ` +declare namespace myapp { + function component(str: string): number; +}` + }); + }, + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(2); // Scheduled invalidation of resolutions, update that gets cancelled and rescheduled by actual invalidation of resolution + sys.checkTimeoutQueueLengthAndRun(1); // Actual update + }, }, - changes: [ - { - caption: "npm install", - change: sys => sys.renameFolder(`${projectRoot}/node_modules2`, `${projectRoot}/node_modules`), - timeouts: runQueuedTimeoutCallbacks, + { + caption: "No change, just check program", + change: noop, + timeouts: (sys, [[oldProgram, oldBuilderProgram]], watchorSolution) => { + sys.checkTimeoutQueueLength(0); + const newProgram = (watchorSolution as Watch).getProgram(); + assert.strictEqual(newProgram, oldBuilderProgram, "No change so builder program should be same"); + assert.strictEqual(newProgram.getProgram(), oldProgram, "No change so program should be same"); } - ] - }); - - describe("ignores files/folder changes in node_modules that start with '.'", () => { - function verifyIgnore(subScenario: string, commandLineArgs: readonly string[]) { - verifyTscWatch({ - scenario, - subScenario: `ignores changes in node_modules that start with dot/${subScenario}`, - commandLineArgs, - sys: () => { - const file1: File = { - path: `${projectRoot}/test.ts`, - content: `import { x } from "somemodule";` - }; - const file2: File = { - path: `${projectRoot}/node_modules/somemodule/index.d.ts`, - content: `export const x = 10;` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - return createWatchedSystem([libFile, file1, file2, config]); - }, - changes: [ - { - caption: "npm install file and folder that start with '.'", - change: sys => sys.ensureFileOrFolder({ - path: `${projectRoot}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, - content: JSON.stringify({ something: 10 }) - }), - timeouts: sys => sys.checkTimeoutQueueLength(0), - } - ] - }); } - verifyIgnore("watch without configFile", ["--w", `${projectRoot}/test.ts`]); - verifyIgnore("watch with configFile", ["--w", "-p", `${projectRoot}/tsconfig.json`]); - }); + ] + }); + verifyTscWatch({ + scenario, + subScenario: "with modules linked to sibling folder", + commandLineArgs: ["-w"], + sys: () => { + const mainPackageRoot = `${projectRoot}/main`; + const linkedPackageRoot = `${projectRoot}/linked-package`; + const mainFile: File = { + path: `${mainPackageRoot}/index.ts`, + content: "import { Foo } from '@scoped/linked-package'" + }; + const config: File = { + path: `${mainPackageRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." }, + files: ["index.ts"] + }) + }; + const linkedPackageInMain: SymLink = { + path: `${mainPackageRoot}/node_modules/@scoped/linked-package`, + symLink: `${linkedPackageRoot}` + }; + const linkedPackageJson: File = { + path: `${linkedPackageRoot}/package.json`, + content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" }) + }; + const linkedPackageIndex: File = { + path: `${linkedPackageRoot}/dist/index.d.ts`, + content: "export * from './other';" + }; + const linkedPackageOther: File = { + path: `${linkedPackageRoot}/dist/other.d.ts`, + content: 'export declare const Foo = "BAR";' + }; + const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther]; + return createWatchedSystem(files, { currentDirectory: mainPackageRoot }); + }, + changes: emptyArray + }); + + describe("works when installing something in node_modules or @types when there is no notification from fs for index file", () => { + function getNodeAtTypes() { + const nodeAtTypesIndex: File = { + path: `${projectRoot}/node_modules/@types/node/index.d.ts`, + content: `/// ` + }; + const nodeAtTypesBase: File = { + path: `${projectRoot}/node_modules/@types/node/base.d.ts`, + content: `// Base definitions for all NodeJS modules that are not specific to any version of TypeScript: +/// ` + }; + const nodeAtTypes36Base: File = { + path: `${projectRoot}/node_modules/@types/node/ts3.6/base.d.ts`, + content: `/// ` + }; + const nodeAtTypesGlobals: File = { + path: `${projectRoot}/node_modules/@types/node/globals.d.ts`, + content: `declare var process: NodeJS.Process; +declare namespace NodeJS { + interface Process { + on(msg: string): void; + } +}` + }; + return { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals }; + } verifyTscWatch({ scenario, - subScenario: "when types in compiler option are global and installed at later point", - commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], + subScenario: "works when installing something in node_modules or @types when there is no notification from fs for index file", + commandLineArgs: ["--w", `--extendedDiagnostics`], sys: () => { - const app: File = { - path: `${projectRoot}/lib/app.ts`, - content: `myapp.component("hello");` + const file: File = { + path: `${projectRoot}/worker.ts`, + content: `process.on("uncaughtException");` }; const tsconfig: File = { path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - types: ["@myapp/ts-types"] - } - }) + content: "{}" }; - return createWatchedSystem([app, tsconfig, libFile]); + const { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals } = getNodeAtTypes(); + return createWatchedSystem([file, libFile, tsconfig, nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals], { currentDirectory: projectRoot }); }, changes: [ { - caption: "npm install ts-types", + caption: "npm ci step one: remove all node_modules files", + change: sys => sys.deleteFolder(`${projectRoot}/node_modules/@types`, /*recursive*/ true), + timeouts: runQueuedTimeoutCallbacks, + }, + { + caption: `npm ci step two: create atTypes but something else in the @types folder`, + change: sys => sys.ensureFileOrFolder({ + path: `${projectRoot}/node_modules/@types/mocha/index.d.ts`, + content: `export const foo = 10;` + }), + timeouts: runQueuedTimeoutCallbacks + }, + { + caption: `npm ci step three: create atTypes node folder`, + change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/node_modules/@types/node` }), + timeouts: runQueuedTimeoutCallbacks + }, + { + caption: `npm ci step four: create atTypes write all the files but dont invoke watcher for index.d.ts`, change: sys => { - sys.ensureFileOrFolder({ - path: `${projectRoot}/node_modules/@myapp/ts-types/package.json`, - content: JSON.stringify({ - version: "1.65.1", - types: "types/somefile.define.d.ts" - }) - }); - sys.ensureFileOrFolder({ - path: `${projectRoot}/node_modules/@myapp/ts-types/types/somefile.define.d.ts`, - content: ` -declare namespace myapp { - function component(str: string): number; -}` - }); + const { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals } = getNodeAtTypes(); + sys.ensureFileOrFolder(nodeAtTypesBase); + sys.ensureFileOrFolder(nodeAtTypesIndex, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); + sys.ensureFileOrFolder(nodeAtTypes36Base, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); + sys.ensureFileOrFolder(nodeAtTypesGlobals, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); }, timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(2); // Scheduled invalidation of resolutions, update that gets cancelled and rescheduled by actual invalidation of resolution - sys.checkTimeoutQueueLengthAndRun(1); // Actual update + sys.runQueuedTimeoutCallbacks(); // update failed lookups + sys.runQueuedTimeoutCallbacks(); // actual program update }, }, - { - caption: "No change, just check program", - change: noop, - timeouts: (sys, [[oldProgram, oldBuilderProgram]], watchorSolution) => { - sys.checkTimeoutQueueLength(0); - const newProgram = (watchorSolution as Watch).getProgram(); - assert.strictEqual(newProgram, oldBuilderProgram, "No change so builder program should be same"); - assert.strictEqual(newProgram.getProgram(), oldProgram, "No change so program should be same"); - } - } ] }); - - verifyTscWatch({ - scenario, - subScenario: "with modules linked to sibling folder", - commandLineArgs: ["-w"], - sys: () => { - const mainPackageRoot = `${projectRoot}/main`; - const linkedPackageRoot = `${projectRoot}/linked-package`; - const mainFile: File = { - path: `${mainPackageRoot}/index.ts`, - content: "import { Foo } from '@scoped/linked-package'" - }; - const config: File = { - path: `${mainPackageRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." }, - files: ["index.ts"] - }) - }; - const linkedPackageInMain: SymLink = { - path: `${mainPackageRoot}/node_modules/@scoped/linked-package`, - symLink: `${linkedPackageRoot}` - }; - const linkedPackageJson: File = { - path: `${linkedPackageRoot}/package.json`, - content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" }) - }; - const linkedPackageIndex: File = { - path: `${linkedPackageRoot}/dist/index.d.ts`, - content: "export * from './other';" - }; - const linkedPackageOther: File = { - path: `${linkedPackageRoot}/dist/other.d.ts`, - content: 'export declare const Foo = "BAR";' - }; - const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther]; - return createWatchedSystem(files, { currentDirectory: mainPackageRoot }); - }, - changes: emptyArray - }); - - describe("works when installing something in node_modules or @types when there is no notification from fs for index file", () => { - function getNodeAtTypes() { - const nodeAtTypesIndex: File = { - path: `${projectRoot}/node_modules/@types/node/index.d.ts`, - content: `/// ` - }; - const nodeAtTypesBase: File = { - path: `${projectRoot}/node_modules/@types/node/base.d.ts`, - content: `// Base definitions for all NodeJS modules that are not specific to any version of TypeScript: -/// ` - }; - const nodeAtTypes36Base: File = { - path: `${projectRoot}/node_modules/@types/node/ts3.6/base.d.ts`, - content: `/// ` - }; - const nodeAtTypesGlobals: File = { - path: `${projectRoot}/node_modules/@types/node/globals.d.ts`, - content: `declare var process: NodeJS.Process; -declare namespace NodeJS { - interface Process { - on(msg: string): void; - } -}` - }; - return { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals }; - } - verifyTscWatch({ - scenario, - subScenario: "works when installing something in node_modules or @types when there is no notification from fs for index file", - commandLineArgs: ["--w", `--extendedDiagnostics`], - sys: () => { - const file: File = { - path: `${projectRoot}/worker.ts`, - content: `process.on("uncaughtException");` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - const { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals } = getNodeAtTypes(); - return createWatchedSystem([file, libFile, tsconfig, nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "npm ci step one: remove all node_modules files", - change: sys => sys.deleteFolder(`${projectRoot}/node_modules/@types`, /*recursive*/ true), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: `npm ci step two: create atTypes but something else in the @types folder`, - change: sys => sys.ensureFileOrFolder({ - path: `${projectRoot}/node_modules/@types/mocha/index.d.ts`, - content: `export const foo = 10;` - }), - timeouts: runQueuedTimeoutCallbacks - }, - { - caption: `npm ci step three: create atTypes node folder`, - change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/node_modules/@types/node` }), - timeouts: runQueuedTimeoutCallbacks - }, - { - caption: `npm ci step four: create atTypes write all the files but dont invoke watcher for index.d.ts`, - change: sys => { - const { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals } = getNodeAtTypes(); - sys.ensureFileOrFolder(nodeAtTypesBase); - sys.ensureFileOrFolder(nodeAtTypesIndex, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); - sys.ensureFileOrFolder(nodeAtTypes36Base, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); - sys.ensureFileOrFolder(nodeAtTypesGlobals, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); - }, - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); // update failed lookups - sys.runQueuedTimeoutCallbacks(); // actual program update - }, - }, - ] - }); - }); }); +}); } diff --git a/src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts b/src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts index 4a831626e4e22..684a9de042ba2 100644 --- a/src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts +++ b/src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts @@ -1,161 +1,161 @@ namespace ts.tscWatch { - import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile; - describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedirect", () => { - interface VerifyWatchInput { - files: readonly TestFSWithWatch.FileOrFolderOrSymLink[]; - config: string; - expectedProgramFiles: readonly string[]; - } - function verifyWatch( - { files, config, expectedProgramFiles }: VerifyWatchInput, - alreadyBuilt: boolean - ) { - const sys = createWatchedSystem(files); - if (alreadyBuilt) { - const solutionBuilder = createSolutionBuilder(sys, [config], {}); - solutionBuilder.build(); - solutionBuilder.close(); - sys.clearOutput(); - } - const host = createWatchCompilerHostOfConfigFile({ - configFileName: config, - system: sys - }); - host.useSourceOfProjectReferenceRedirect = returnTrue; - const watch = createWatchProgram(host); - checkProgramActualFiles(watch.getCurrentProgram().getProgram(), expectedProgramFiles); +import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile; +describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedirect", () => { + interface VerifyWatchInput { + files: readonly TestFSWithWatch.FileOrFolderOrSymLink[]; + config: string; + expectedProgramFiles: readonly string[]; + } + function verifyWatch( + { files, config, expectedProgramFiles }: VerifyWatchInput, + alreadyBuilt: boolean + ) { + const sys = createWatchedSystem(files); + if (alreadyBuilt) { + const solutionBuilder = createSolutionBuilder(sys, [config], {}); + solutionBuilder.build(); + solutionBuilder.close(); + sys.clearOutput(); } + const host = createWatchCompilerHostOfConfigFile({ + configFileName: config, + system: sys + }); + host.useSourceOfProjectReferenceRedirect = returnTrue; + const watch = createWatchProgram(host); + checkProgramActualFiles(watch.getCurrentProgram().getProgram(), expectedProgramFiles); + } - function verifyScenario(input: () => VerifyWatchInput) { - it("when solution is not built", () => { - verifyWatch(input(), /*alreadyBuilt*/ false); - }); + function verifyScenario(input: () => VerifyWatchInput) { + it("when solution is not built", () => { + verifyWatch(input(), /*alreadyBuilt*/ false); + }); + + it("when solution is already built", () => { + verifyWatch(input(), /*alreadyBuilt*/ true); + }); + } - it("when solution is already built", () => { - verifyWatch(input(), /*alreadyBuilt*/ true); + describe("with simple project", () => { + verifyScenario(() => { + const baseConfig = getFileFromProject("demo", "tsconfig-base.json"); + const coreTs = getFileFromProject("demo", "core/utilities.ts"); + const coreConfig = getFileFromProject("demo", "core/tsconfig.json"); + const animalTs = getFileFromProject("demo", "animals/animal.ts"); + const dogTs = getFileFromProject("demo", "animals/dog.ts"); + const indexTs = getFileFromProject("demo", "animals/index.ts"); + const animalsConfig = getFileFromProject("demo", "animals/tsconfig.json"); + return { + files: [{ path: libFile.path, content: libContent }, baseConfig, coreTs, coreConfig, animalTs, dogTs, indexTs, animalsConfig], + config: animalsConfig.path, + expectedProgramFiles: [libFile.path, indexTs.path, dogTs.path, animalTs.path, coreTs.path] + }; + }); + }); + + describe("when references are monorepo like with symlinks", () => { + interface Packages { + bPackageJson: File; + aTest: File; + bFoo: File; + bBar: File; + bSymlink: SymLink; + } + function verifySymlinkScenario(packages: () => Packages) { + describe("when preserveSymlinks is turned off", () => { + verifySymlinkScenarioWorker(packages, {}); + }); + describe("when preserveSymlinks is turned on", () => { + verifySymlinkScenarioWorker(packages, { preserveSymlinks: true }); }); } - describe("with simple project", () => { + function verifySymlinkScenarioWorker(packages: () => Packages, extraOptions: CompilerOptions) { verifyScenario(() => { - const baseConfig = getFileFromProject("demo", "tsconfig-base.json"); - const coreTs = getFileFromProject("demo", "core/utilities.ts"); - const coreConfig = getFileFromProject("demo", "core/tsconfig.json"); - const animalTs = getFileFromProject("demo", "animals/animal.ts"); - const dogTs = getFileFromProject("demo", "animals/dog.ts"); - const indexTs = getFileFromProject("demo", "animals/index.ts"); - const animalsConfig = getFileFromProject("demo", "animals/tsconfig.json"); + const { bPackageJson, aTest, bFoo, bBar, bSymlink } = packages(); + const aConfig = config("A", extraOptions, ["../B"]); + const bConfig = config("B", extraOptions); return { - files: [{ path: libFile.path, content: libContent }, baseConfig, coreTs, coreConfig, animalTs, dogTs, indexTs, animalsConfig], - config: animalsConfig.path, - expectedProgramFiles: [libFile.path, indexTs.path, dogTs.path, animalTs.path, coreTs.path] + files: [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink], + config: aConfig.path, + expectedProgramFiles: [libFile.path, aTest.path, bFoo.path, bBar.path] }; }); - }); - - describe("when references are monorepo like with symlinks", () => { - interface Packages { - bPackageJson: File; - aTest: File; - bFoo: File; - bBar: File; - bSymlink: SymLink; - } - function verifySymlinkScenario(packages: () => Packages) { - describe("when preserveSymlinks is turned off", () => { - verifySymlinkScenarioWorker(packages, {}); - }); - describe("when preserveSymlinks is turned on", () => { - verifySymlinkScenarioWorker(packages, { preserveSymlinks: true }); - }); - } - - function verifySymlinkScenarioWorker(packages: () => Packages, extraOptions: CompilerOptions) { - verifyScenario(() => { - const { bPackageJson, aTest, bFoo, bBar, bSymlink } = packages(); - const aConfig = config("A", extraOptions, ["../B"]); - const bConfig = config("B", extraOptions); - return { - files: [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink], - config: aConfig.path, - expectedProgramFiles: [libFile.path, aTest.path, bFoo.path, bBar.path] - }; - }); - } + } - function config(packageName: string, extraOptions: CompilerOptions, references?: string[]): File { - return { - path: `${projectRoot}/packages/${packageName}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - outDir: "lib", - rootDir: "src", - composite: true, - ...extraOptions - }, - include: ["src"], - ...(references ? { references: references.map(path => ({ path })) } : {}) - }) - }; - } + function config(packageName: string, extraOptions: CompilerOptions, references?: string[]): File { + return { + path: `${projectRoot}/packages/${packageName}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + outDir: "lib", + rootDir: "src", + composite: true, + ...extraOptions + }, + include: ["src"], + ...(references ? { references: references.map(path => ({ path })) } : {}) + }) + }; + } - function file(packageName: string, fileName: string, content: string): File { - return { - path: `${projectRoot}/packages/${packageName}/src/${fileName}`, - content - }; - } + function file(packageName: string, fileName: string, content: string): File { + return { + path: `${projectRoot}/packages/${packageName}/src/${fileName}`, + content + }; + } - function verifyMonoRepoLike(scope = "") { - describe("when packageJson has types field", () => { - verifySymlinkScenario(() => ({ - bPackageJson: { - path: `${projectRoot}/packages/B/package.json`, - content: JSON.stringify({ - main: "lib/index.js", - types: "lib/index.d.ts" - }) - }, - aTest: file("A", "index.ts", `import { foo } from '${scope}b'; + function verifyMonoRepoLike(scope = "") { + describe("when packageJson has types field", () => { + verifySymlinkScenario(() => ({ + bPackageJson: { + path: `${projectRoot}/packages/B/package.json`, + content: JSON.stringify({ + main: "lib/index.js", + types: "lib/index.d.ts" + }) + }, + aTest: file("A", "index.ts", `import { foo } from '${scope}b'; import { bar } from '${scope}b/lib/bar'; foo(); bar(); `), - bFoo: file("B", "index.ts", `export function foo() { }`), - bBar: file("B", "bar.ts", `export function bar() { }`), - bSymlink: { - path: `${projectRoot}/node_modules/${scope}b`, - symLink: `${projectRoot}/packages/B` - } - })); - }); + bFoo: file("B", "index.ts", `export function foo() { }`), + bBar: file("B", "bar.ts", `export function bar() { }`), + bSymlink: { + path: `${projectRoot}/node_modules/${scope}b`, + symLink: `${projectRoot}/packages/B` + } + })); + }); - describe("when referencing file from subFolder", () => { - verifySymlinkScenario(() => ({ - bPackageJson: { - path: `${projectRoot}/packages/B/package.json`, - content: "{}" - }, - aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo'; + describe("when referencing file from subFolder", () => { + verifySymlinkScenario(() => ({ + bPackageJson: { + path: `${projectRoot}/packages/B/package.json`, + content: "{}" + }, + aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo'; import { bar } from '${scope}b/lib/bar/foo'; foo(); bar(); `), - bFoo: file("B", "foo.ts", `export function foo() { }`), - bBar: file("B", "bar/foo.ts", `export function bar() { }`), - bSymlink: { - path: `${projectRoot}/node_modules/${scope}b`, - symLink: `${projectRoot}/packages/B` - } - })); - }); - } - describe("when package is not scoped", () => { - verifyMonoRepoLike(); - }); - describe("when package is scoped", () => { - verifyMonoRepoLike("@issue/"); + bFoo: file("B", "foo.ts", `export function foo() { }`), + bBar: file("B", "bar/foo.ts", `export function bar() { }`), + bSymlink: { + path: `${projectRoot}/node_modules/${scope}b`, + symLink: `${projectRoot}/packages/B` + } + })); }); + } + describe("when package is not scoped", () => { + verifyMonoRepoLike(); + }); + describe("when package is scoped", () => { + verifyMonoRepoLike("@issue/"); }); }); +}); } diff --git a/src/testRunner/unittests/tscWatch/watchApi.ts b/src/testRunner/unittests/tscWatch/watchApi.ts index d71318d2de868..3060a029a1199 100644 --- a/src/testRunner/unittests/tscWatch/watchApi.ts +++ b/src/testRunner/unittests/tscWatch/watchApi.ts @@ -1,382 +1,382 @@ namespace ts.tscWatch { - describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolution", () => { - const configFileJson: any = { - compilerOptions: { module: "commonjs", resolveJsonModule: true }, - files: ["index.ts"] - }; - const mainFile: File = { - path: `${projectRoot}/index.ts`, - content: "import settings from './settings.json';" - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify(configFileJson) - }; - const settingsJson: File = { - path: `${projectRoot}/settings.json`, - content: JSON.stringify({ content: "Print this" }) - }; +describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolution", () => { + const configFileJson: any = { + compilerOptions: { module: "commonjs", resolveJsonModule: true }, + files: ["index.ts"] + }; + const mainFile: File = { + path: `${projectRoot}/index.ts`, + content: "import settings from './settings.json';" + }; + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify(configFileJson) + }; + const settingsJson: File = { + path: `${projectRoot}/settings.json`, + content: JSON.stringify({ content: "Print this" }) + }; - it("verify that module resolution with json extension works when returned without extension", () => { - const files = [libFile, mainFile, config, settingsJson]; - const host = createWatchedSystem(files, { currentDirectory: projectRoot }); - const compilerHost = createWatchCompilerHostOfConfigFile({ - configFileName: config.path, - system: host - }); - const parsedCommandResult = parseJsonConfigFileContent(configFileJson, host, config.path); - compilerHost.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => { - const result = resolveModuleName(m, containingFile, parsedCommandResult.options, compilerHost); - const resolvedModule = result.resolvedModule!; - return { - resolvedFileName: resolvedModule.resolvedFileName, - isExternalLibraryImport: resolvedModule.isExternalLibraryImport, - originalFileName: resolvedModule.originalPath, - }; - }); - const watch = createWatchProgram(compilerHost); - const program = watch.getCurrentProgram().getProgram(); - checkProgramActualFiles(program, [mainFile.path, libFile.path, settingsJson.path]); + it("verify that module resolution with json extension works when returned without extension", () => { + const files = [libFile, mainFile, config, settingsJson]; + const host = createWatchedSystem(files, { currentDirectory: projectRoot }); + const compilerHost = createWatchCompilerHostOfConfigFile({ + configFileName: config.path, + system: host + }); + const parsedCommandResult = parseJsonConfigFileContent(configFileJson, host, config.path); + compilerHost.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => { + const result = resolveModuleName(m, containingFile, parsedCommandResult.options, compilerHost); + const resolvedModule = result.resolvedModule!; + return { + resolvedFileName: resolvedModule.resolvedFileName, + isExternalLibraryImport: resolvedModule.isExternalLibraryImport, + originalFileName: resolvedModule.originalPath, + }; }); + const watch = createWatchProgram(compilerHost); + const program = watch.getCurrentProgram().getProgram(); + checkProgramActualFiles(program, [mainFile.path, libFile.path, settingsJson.path]); }); +}); - describe("unittests:: tsc-watch:: watchAPI:: tsc-watch expose error count to watch status reporter", () => { - const configFileJson: any = { - compilerOptions: { module: "commonjs" }, - files: ["index.ts"] +describe("unittests:: tsc-watch:: watchAPI:: tsc-watch expose error count to watch status reporter", () => { + const configFileJson: any = { + compilerOptions: { module: "commonjs" }, + files: ["index.ts"] + }; + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify(configFileJson) + }; + const mainFile: File = { + path: `${projectRoot}/index.ts`, + content: "let compiler = new Compiler(); for (let i = 0; j < 5; i++) {}" + }; + + it("verify that the error count is correctly passed down to the watch status reporter", () => { + const files = [libFile, mainFile, config]; + const host = createWatchedSystem(files, { currentDirectory: projectRoot }); + let watchedErrorCount; + const reportWatchStatus: WatchStatusReporter = (_, __, ___, errorCount) => { + watchedErrorCount = errorCount; }; + const compilerHost = createWatchCompilerHostOfConfigFile({ + configFileName: config.path, + system: host, + reportWatchStatus + }); + createWatchProgram(compilerHost); + assert.equal(watchedErrorCount, 2, "The error count was expected to be 2 for the file change"); + }); +}); + +describe("unittests:: tsc-watch:: watchAPI:: when watchHost does not implement setTimeout or clearTimeout", () => { + it("verifies that getProgram gets updated program if new file is added to the program", () => { const config: File = { path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify(configFileJson) + content: "{}" }; const mainFile: File = { - path: `${projectRoot}/index.ts`, - content: "let compiler = new Compiler(); for (let i = 0; j < 5; i++) {}" + path: `${projectRoot}/main.ts`, + content: "const x = 10;" }; - - it("verify that the error count is correctly passed down to the watch status reporter", () => { - const files = [libFile, mainFile, config]; - const host = createWatchedSystem(files, { currentDirectory: projectRoot }); - let watchedErrorCount; - const reportWatchStatus: WatchStatusReporter = (_, __, ___, errorCount) => { - watchedErrorCount = errorCount; - }; - const compilerHost = createWatchCompilerHostOfConfigFile({ - configFileName: config.path, - system: host, - reportWatchStatus - }); - createWatchProgram(compilerHost); - assert.equal(watchedErrorCount, 2, "The error count was expected to be 2 for the file change"); - }); + const sys = createWatchedSystem([config, mainFile, libFile]); + const watchCompilerHost = createWatchCompilerHost(config.path, {}, sys); + watchCompilerHost.setTimeout = undefined; + watchCompilerHost.clearTimeout = undefined; + const watch = createWatchProgram(watchCompilerHost); + checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, libFile.path]); + // Write new file + const barPath = `${projectRoot}/bar.ts`; + sys.writeFile(barPath, "const y =10;"); + checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, barPath, libFile.path]); }); +}); - describe("unittests:: tsc-watch:: watchAPI:: when watchHost does not implement setTimeout or clearTimeout", () => { - it("verifies that getProgram gets updated program if new file is added to the program", () => { - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - const mainFile: File = { - path: `${projectRoot}/main.ts`, - content: "const x = 10;" - }; - const sys = createWatchedSystem([config, mainFile, libFile]); - const watchCompilerHost = createWatchCompilerHost(config.path, {}, sys); - watchCompilerHost.setTimeout = undefined; - watchCompilerHost.clearTimeout = undefined; - const watch = createWatchProgram(watchCompilerHost); - checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, libFile.path]); - // Write new file - const barPath = `${projectRoot}/bar.ts`; - sys.writeFile(barPath, "const y =10;"); - checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, barPath, libFile.path]); - }); - }); - - describe("unittests:: tsc-watch:: watchAPI:: when watchHost can add extraFileExtensions to process", () => { - it("verifies that extraFileExtensions are supported to get the program with other extensions", () => { - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - const mainFile: File = { - path: `${projectRoot}/main.ts`, - content: "const x = 10;" - }; - const otherFile: File = { - path: `${projectRoot}/other.vue`, - content: "" - }; - const sys = createWatchedSystem([config, mainFile, otherFile, libFile]); - const watchCompilerHost = createWatchCompilerHost( - config.path, - { allowNonTsExtensions: true }, - sys, - /*createProgram*/ undefined, - /*reportDiagnostics*/ undefined, - /*reportWatchStatus*/ undefined, - /*watchOptionsToExtend*/ undefined, - [{ extension: ".vue", isMixedContent: true, scriptKind: ScriptKind.Deferred }] - ); - const watch = createWatchProgram(watchCompilerHost); - checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]); +describe("unittests:: tsc-watch:: watchAPI:: when watchHost can add extraFileExtensions to process", () => { + it("verifies that extraFileExtensions are supported to get the program with other extensions", () => { + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: "{}" + }; + const mainFile: File = { + path: `${projectRoot}/main.ts`, + content: "const x = 10;" + }; + const otherFile: File = { + path: `${projectRoot}/other.vue`, + content: "" + }; + const sys = createWatchedSystem([config, mainFile, otherFile, libFile]); + const watchCompilerHost = createWatchCompilerHost( + config.path, + { allowNonTsExtensions: true }, + sys, + /*createProgram*/ undefined, + /*reportDiagnostics*/ undefined, + /*reportWatchStatus*/ undefined, + /*watchOptionsToExtend*/ undefined, + [{ extension: ".vue", isMixedContent: true, scriptKind: ScriptKind.Deferred }] + ); + const watch = createWatchProgram(watchCompilerHost); + checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]); - const other2 = `${projectRoot}/other2.vue`; - sys.writeFile(other2, otherFile.content); - checkSingleTimeoutQueueLengthAndRun(sys); - checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path, other2]); - }); + const other2 = `${projectRoot}/other2.vue`; + sys.writeFile(other2, otherFile.content); + checkSingleTimeoutQueueLengthAndRun(sys); + checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path, other2]); }); +}); - describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticDiagnosticsBuilderProgram", () => { - function getWatch(config: File, optionsToExtend: CompilerOptions | undefined, sys: System, createProgram: CreateProgram) { - const watchCompilerHost = createWatchCompilerHost(config.path, optionsToExtend, sys, createProgram); - return createWatchProgram(watchCompilerHost); - } +describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticDiagnosticsBuilderProgram", () => { + function getWatch(config: File, optionsToExtend: CompilerOptions | undefined, sys: System, createProgram: CreateProgram) { + const watchCompilerHost = createWatchCompilerHost(config.path, optionsToExtend, sys, createProgram); + return createWatchProgram(watchCompilerHost); + } - function setup(createProgram: CreateProgram, configText: string) { - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: configText - }; - const mainFile: File = { - path: `${projectRoot}/main.ts`, - content: "export const x = 10;" - }; - const otherFile: File = { - path: `${projectRoot}/other.ts`, - content: "export const y = 10;" - }; - const sys = createWatchedSystem([config, mainFile, otherFile, libFile]); - const watch = getWatch(config, { noEmit: true }, sys, createProgram); - return { sys, watch, mainFile, otherFile, config }; - } + function setup(createProgram: CreateProgram, configText: string) { + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: configText + }; + const mainFile: File = { + path: `${projectRoot}/main.ts`, + content: "export const x = 10;" + }; + const otherFile: File = { + path: `${projectRoot}/other.ts`, + content: "export const y = 10;" + }; + const sys = createWatchedSystem([config, mainFile, otherFile, libFile]); + const watch = getWatch(config, { noEmit: true }, sys, createProgram); + return { sys, watch, mainFile, otherFile, config }; + } - function verifyOutputs(sys: System, emitSys: System) { - for (const output of [`${projectRoot}/main.js`, `${projectRoot}/main.d.ts`, `${projectRoot}/other.js`, `${projectRoot}/other.d.ts`, `${projectRoot}/tsconfig.tsbuildinfo`]) { - assert.strictEqual(sys.readFile(output), emitSys.readFile(output), `Output file text for ${output}`); - } + function verifyOutputs(sys: System, emitSys: System) { + for (const output of [`${projectRoot}/main.js`, `${projectRoot}/main.d.ts`, `${projectRoot}/other.js`, `${projectRoot}/other.d.ts`, `${projectRoot}/tsconfig.tsbuildinfo`]) { + assert.strictEqual(sys.readFile(output), emitSys.readFile(output), `Output file text for ${output}`); } + } - function verifyBuilder(config: File, sys: System, emitSys: System, createProgram: CreateProgram, createEmitProgram: CreateProgram, optionsToExtend?: CompilerOptions) { - const watch = getWatch(config, /*optionsToExtend*/ optionsToExtend, sys, createProgram); - const emitWatch = getWatch(config, /*optionsToExtend*/ optionsToExtend, emitSys, createEmitProgram); - verifyOutputs(sys, emitSys); - watch.close(); - emitWatch.close(); - } + function verifyBuilder(config: File, sys: System, emitSys: System, createProgram: CreateProgram, createEmitProgram: CreateProgram, optionsToExtend?: CompilerOptions) { + const watch = getWatch(config, /*optionsToExtend*/ optionsToExtend, sys, createProgram); + const emitWatch = getWatch(config, /*optionsToExtend*/ optionsToExtend, emitSys, createEmitProgram); + verifyOutputs(sys, emitSys); + watch.close(); + emitWatch.close(); + } - it("verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram and typechecking happens only on affected files", () => { - const { sys, watch, mainFile, otherFile } = setup(createSemanticDiagnosticsBuilderProgram, "{}"); - checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]); - sys.appendFile(mainFile.path, "\n// SomeComment"); - sys.runQueuedTimeoutCallbacks(); - const program = watch.getProgram().getProgram(); - assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(mainFile.path)), []); - // Should not retrieve diagnostics for other file thats not changed - assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(otherFile.path)), /*expected*/ undefined); - }); + it("verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram and typechecking happens only on affected files", () => { + const { sys, watch, mainFile, otherFile } = setup(createSemanticDiagnosticsBuilderProgram, "{}"); + checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]); + sys.appendFile(mainFile.path, "\n// SomeComment"); + sys.runQueuedTimeoutCallbacks(); + const program = watch.getProgram().getProgram(); + assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(mainFile.path)), []); + // Should not retrieve diagnostics for other file thats not changed + assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(otherFile.path)), /*expected*/ undefined); + }); - it("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => { - const configText = JSON.stringify({ compilerOptions: { composite: true } }); - const { sys, watch, config, mainFile } = setup(createSemanticDiagnosticsBuilderProgram, configText); - const { sys: emitSys, watch: emitWatch } = setup(createEmitAndSemanticDiagnosticsBuilderProgram, configText); - verifyOutputs(sys, emitSys); + it("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => { + const configText = JSON.stringify({ compilerOptions: { composite: true } }); + const { sys, watch, config, mainFile } = setup(createSemanticDiagnosticsBuilderProgram, configText); + const { sys: emitSys, watch: emitWatch } = setup(createEmitAndSemanticDiagnosticsBuilderProgram, configText); + verifyOutputs(sys, emitSys); - watch.close(); - emitWatch.close(); + watch.close(); + emitWatch.close(); - // Emit on both sys should result in same output - verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram); + // Emit on both sys should result in same output + verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram); - // Change file - sys.appendFile(mainFile.path, "\n// SomeComment"); - emitSys.appendFile(mainFile.path, "\n// SomeComment"); + // Change file + sys.appendFile(mainFile.path, "\n// SomeComment"); + emitSys.appendFile(mainFile.path, "\n// SomeComment"); - // Verify noEmit results in same output - verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmit: true }); + // Verify noEmit results in same output + verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmit: true }); - // Emit on both sys should result in same output - verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram); + // Emit on both sys should result in same output + verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram); - // Change file - sys.appendFile(mainFile.path, "\n// SomeComment"); - emitSys.appendFile(mainFile.path, "\n// SomeComment"); + // Change file + sys.appendFile(mainFile.path, "\n// SomeComment"); + emitSys.appendFile(mainFile.path, "\n// SomeComment"); - // Emit on both the builders should result in same files - verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram); - }); + // Emit on both the builders should result in same files + verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram); + }); - it("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => { - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true } }) - }; - const mainFile: File = { - path: `${projectRoot}/main.ts`, - content: "export const x: string = 10;" - }; - const otherFile: File = { - path: `${projectRoot}/other.ts`, - content: "export const y = 10;" - }; - const sys = createWatchedSystem([config, mainFile, otherFile, libFile]); - const emitSys = createWatchedSystem([config, mainFile, otherFile, libFile]); + it("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => { + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true } }) + }; + const mainFile: File = { + path: `${projectRoot}/main.ts`, + content: "export const x: string = 10;" + }; + const otherFile: File = { + path: `${projectRoot}/other.ts`, + content: "export const y = 10;" + }; + const sys = createWatchedSystem([config, mainFile, otherFile, libFile]); + const emitSys = createWatchedSystem([config, mainFile, otherFile, libFile]); - // Verify noEmit results in same output - verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true }); + // Verify noEmit results in same output + verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true }); - // Change file - sys.appendFile(mainFile.path, "\n// SomeComment"); - emitSys.appendFile(mainFile.path, "\n// SomeComment"); + // Change file + sys.appendFile(mainFile.path, "\n// SomeComment"); + emitSys.appendFile(mainFile.path, "\n// SomeComment"); - // Verify noEmit results in same output - verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true }); + // Verify noEmit results in same output + verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true }); - // Fix error - const fixed = "export const x = 10;"; - sys.appendFile(mainFile.path, fixed); - emitSys.appendFile(mainFile.path, fixed); + // Fix error + const fixed = "export const x = 10;"; + sys.appendFile(mainFile.path, fixed); + emitSys.appendFile(mainFile.path, fixed); - // Emit on both the builders should result in same files - verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true }); - }); + // Emit on both the builders should result in same files + verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true }); }); +}); - describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implemented", () => { - function setup(useSourceOfProjectReferenceRedirect?: () => boolean) { - const config1: File = { - path: `${projectRoot}/projects/project1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - exclude: ["temp"] - }) - }; - const class1: File = { - path: `${projectRoot}/projects/project1/class1.ts`, - content: `class class1 {}` - }; - const class1Dts: File = { - path: `${projectRoot}/projects/project1/class1.d.ts`, - content: `declare class class1 {}` - }; - const config2: File = { - path: `${projectRoot}/projects/project2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - references: [ - { path: "../project1" } - ] - }) - }; - const class2: File = { - path: `${projectRoot}/projects/project2/class2.ts`, - content: `class class2 {}` - }; - const system = createWatchedSystem([config1, class1, class1Dts, config2, class2, libFile]); - const baseline = createBaseline(system); - const compilerHost = createWatchCompilerHostOfConfigFile({ - configFileName: config2.path, - system, - optionsToExtend: { extendedDiagnostics: true } +describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implemented", () => { + function setup(useSourceOfProjectReferenceRedirect?: () => boolean) { + const config1: File = { + path: `${projectRoot}/projects/project1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true + }, + exclude: ["temp"] + }) + }; + const class1: File = { + path: `${projectRoot}/projects/project1/class1.ts`, + content: `class class1 {}` + }; + const class1Dts: File = { + path: `${projectRoot}/projects/project1/class1.d.ts`, + content: `declare class class1 {}` + }; + const config2: File = { + path: `${projectRoot}/projects/project2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true + }, + references: [ + { path: "../project1" } + ] + }) + }; + const class2: File = { + path: `${projectRoot}/projects/project2/class2.ts`, + content: `class class2 {}` + }; + const system = createWatchedSystem([config1, class1, class1Dts, config2, class2, libFile]); + const baseline = createBaseline(system); + const compilerHost = createWatchCompilerHostOfConfigFile({ + configFileName: config2.path, + system, + optionsToExtend: { extendedDiagnostics: true } + }); + compilerHost.useSourceOfProjectReferenceRedirect = useSourceOfProjectReferenceRedirect; + const calledGetParsedCommandLine = new Set(); + compilerHost.getParsedCommandLine = fileName => { + assert.isFalse(calledGetParsedCommandLine.has(fileName), `Already called on ${fileName}`); + calledGetParsedCommandLine.add(fileName); + return getParsedCommandLineOfConfigFile(fileName, /*optionsToExtend*/ undefined, { + useCaseSensitiveFileNames: true, + fileExists: path => system.fileExists(path), + readFile: path => system.readFile(path), + getCurrentDirectory: () => system.getCurrentDirectory(), + readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth), + onUnRecoverableConfigFileDiagnostic: noop, }); - compilerHost.useSourceOfProjectReferenceRedirect = useSourceOfProjectReferenceRedirect; - const calledGetParsedCommandLine = new Set(); - compilerHost.getParsedCommandLine = fileName => { - assert.isFalse(calledGetParsedCommandLine.has(fileName), `Already called on ${fileName}`); - calledGetParsedCommandLine.add(fileName); - return getParsedCommandLineOfConfigFile(fileName, /*optionsToExtend*/ undefined, { - useCaseSensitiveFileNames: true, - fileExists: path => system.fileExists(path), - readFile: path => system.readFile(path), - getCurrentDirectory: () => system.getCurrentDirectory(), - readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth), - onUnRecoverableConfigFileDiagnostic: noop, - }); - }; - const watch = createWatchProgram(compilerHost); - return { watch, baseline, config2, calledGetParsedCommandLine }; - } + }; + const watch = createWatchProgram(compilerHost); + return { watch, baseline, config2, calledGetParsedCommandLine }; + } - it("when new file is added to the referenced project with host implementing getParsedCommandLine", () => { - const { watch, baseline, config2, calledGetParsedCommandLine } = setup(returnTrue); - runWatchBaseline({ - scenario: "watchApi", - subScenario: "when new file is added to the referenced project with host implementing getParsedCommandLine", - commandLineArgs: ["--w", "-p", config2.path, "--extendedDiagnostics"], - ...baseline, - getPrograms: () => [[watch.getCurrentProgram().getProgram(), watch.getCurrentProgram()]], - changes: [ - { - caption: "Add class3 to project1", - change: sys => { - calledGetParsedCommandLine.clear(); - sys.writeFile(`${projectRoot}/projects/project1/class3.ts`, `class class3 {}`); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Add excluded file to project1", - change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), - timeouts: sys => sys.checkTimeoutQueueLength(0), - }, - { - caption: "Add output of class3", - change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), - timeouts: sys => sys.checkTimeoutQueueLength(0), + it("when new file is added to the referenced project with host implementing getParsedCommandLine", () => { + const { watch, baseline, config2, calledGetParsedCommandLine } = setup(returnTrue); + runWatchBaseline({ + scenario: "watchApi", + subScenario: "when new file is added to the referenced project with host implementing getParsedCommandLine", + commandLineArgs: ["--w", "-p", config2.path, "--extendedDiagnostics"], + ...baseline, + getPrograms: () => [[watch.getCurrentProgram().getProgram(), watch.getCurrentProgram()]], + changes: [ + { + caption: "Add class3 to project1", + change: sys => { + calledGetParsedCommandLine.clear(); + sys.writeFile(`${projectRoot}/projects/project1/class3.ts`, `class class3 {}`); }, - ], - watchOrSolution: watch - }); + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Add excluded file to project1", + change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), + timeouts: sys => sys.checkTimeoutQueueLength(0), + }, + { + caption: "Add output of class3", + change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), + timeouts: sys => sys.checkTimeoutQueueLength(0), + }, + ], + watchOrSolution: watch }); + }); - it("when new file is added to the referenced project with host implementing getParsedCommandLine without implementing useSourceOfProjectReferenceRedirect", () => { - const { watch, baseline, config2, calledGetParsedCommandLine } = setup(); - runWatchBaseline({ - scenario: "watchApi", - subScenario: "when new file is added to the referenced project with host implementing getParsedCommandLine without implementing useSourceOfProjectReferenceRedirect", - commandLineArgs: ["--w", "-p", config2.path, "--extendedDiagnostics"], - ...baseline, - getPrograms: () => [[watch.getCurrentProgram().getProgram(), watch.getCurrentProgram()]], - changes: [ - { - caption: "Add class3 to project1", - change: sys => { - calledGetParsedCommandLine.clear(); - sys.writeFile(`${projectRoot}/projects/project1/class3.ts`, `class class3 {}`); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, + it("when new file is added to the referenced project with host implementing getParsedCommandLine without implementing useSourceOfProjectReferenceRedirect", () => { + const { watch, baseline, config2, calledGetParsedCommandLine } = setup(); + runWatchBaseline({ + scenario: "watchApi", + subScenario: "when new file is added to the referenced project with host implementing getParsedCommandLine without implementing useSourceOfProjectReferenceRedirect", + commandLineArgs: ["--w", "-p", config2.path, "--extendedDiagnostics"], + ...baseline, + getPrograms: () => [[watch.getCurrentProgram().getProgram(), watch.getCurrentProgram()]], + changes: [ + { + caption: "Add class3 to project1", + change: sys => { + calledGetParsedCommandLine.clear(); + sys.writeFile(`${projectRoot}/projects/project1/class3.ts`, `class class3 {}`); }, - { - caption: "Add class3 output to project1", - change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Add excluded file to project1", - change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), - timeouts: sys => sys.checkTimeoutQueueLength(0), - }, - { - caption: "Delete output of class3", - change: sys => sys.deleteFile(`${projectRoot}/projects/project1/class3.d.ts`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Add output of class3", - change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - ], - watchOrSolution: watch - }); + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Add class3 output to project1", + change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Add excluded file to project1", + change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), + timeouts: sys => sys.checkTimeoutQueueLength(0), + }, + { + caption: "Delete output of class3", + change: sys => sys.deleteFile(`${projectRoot}/projects/project1/class3.d.ts`), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Add output of class3", + change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + ], + watchOrSolution: watch }); }); +}); } diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts index d02afa3137714..19f59a67c094f 100644 --- a/src/testRunner/unittests/tscWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -1,585 +1,585 @@ namespace ts.tscWatch { - import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; - describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different polling/non polling options", () => { - const scenario = "watchEnvironment"; +import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; +describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different polling/non polling options", () => { + const scenario = "watchEnvironment"; + verifyTscWatch({ + scenario, + subScenario: "watchFile/using dynamic priority polling", + commandLineArgs: ["--w", `/a/username/project/typescript.ts`], + sys: () => { + const projectFolder = "/a/username/project"; + const file1: File = { + path: `${projectFolder}/typescript.ts`, + content: "var z = 10;" + }; + const environmentVariables = new Map(); + environmentVariables.set("TSC_WATCHFILE", TestFSWithWatch.Tsc_WatchFile.DynamicPolling); + return createWatchedSystem([file1, libFile], { environmentVariables }); + }, + changes: [ + { + caption: "Time spent to Transition libFile and file1 to low priority queue", + change: noop, + timeouts: (sys, programs) => { + const initialProgram = programs[0][0]; + const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; + for (let index = 0; index < mediumPollingIntervalThreshold; index++) { + // Transition libFile and file1 to low priority queue + sys.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(programs[0][0], initialProgram); + } + return; + }, + }, + { + caption: "Make change to file", + // Make a change to file + change: sys => sys.writeFile("/a/username/project/typescript.ts", "var zz30 = 100;"), + // During this timeout the file would be detected as unchanged + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Callbacks: medium priority + high priority queue and scheduled program update", + change: noop, + // Callbacks: medium priority + high priority queue and scheduled program update + // This should detect change in the file + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(3), + }, + { + caption: "Polling queues polled and everything is in the high polling queue", + change: noop, + timeouts: (sys, programs) => { + const initialProgram = programs[0][0]; + const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; + const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold; + for (let fileUnchangeDetected = 1; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) { + // For high + Medium/low polling interval + sys.checkTimeoutQueueLengthAndRun(2); + assert.deepEqual(programs[0][0], initialProgram); + } + + // Everything goes in high polling interval queue + sys.checkTimeoutQueueLengthAndRun(1); + return; + }, + } + ] + }); + + verifyTscWatch({ + scenario, + subScenario: "watchFile/using fixed chunk size polling", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + watchOptions: { + watchFile: "FixedChunkSizePolling" + } + }) + }; + const files = [libFile, commonFile1, commonFile2, configFile]; + return createWatchedSystem(files); + }, + changes: [ + { + caption: "The timeout is to check the status of all files", + change: noop, + timeouts: (sys, programs) => { + // On each timeout file does not change + const initialProgram = programs[0][0]; + for (let index = 0; index < 4; index++) { + sys.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(programs[0][0], initialProgram); + } + }, + }, + { + caption: "Make change to file but should detect as changed and schedule program update", + // Make a change to file + change: sys => sys.writeFile(commonFile1.path, "var zz30 = 100;"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Callbacks: queue and scheduled program update", + change: noop, + // Callbacks: scheduled program update and queue for the polling + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), + }, + { + caption: "The timeout is to check the status of all files", + change: noop, + timeouts: (sys, programs) => { + // On each timeout file does not change + const initialProgram = programs[0][0]; + sys.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(programs[0][0], initialProgram); + }, + }, + ] + }); + + verifyTscWatch({ + scenario, + subScenario: "watchFile/setting default as fixed chunk size watch file works", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + const files = [libFile, commonFile1, commonFile2, configFile]; + const sys = createWatchedSystem(files); + sys.defaultWatchFileKind = () => WatchFileKind.FixedChunkSizePolling; + return sys; + }, + changes: [ + { + caption: "Make change to file but should detect as changed and schedule program update", + // Make a change to file + change: sys => sys.writeFile(commonFile1.path, "var zz30 = 100;"), + timeouts: checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Callbacks: queue and scheduled program update", + change: noop, + // Callbacks: scheduled program update and queue for the polling + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), + }, + ] + }); + + describe("tsc-watch when watchDirectories implementation", () => { + function verifyRenamingFileInSubFolder(subScenario: string, tscWatchDirectory: Tsc_WatchDirectory) { + const projectFolder = "/a/username/project"; + const projectSrcFolder = `${projectFolder}/src`; + const configFile: File = { + path: `${projectFolder}/tsconfig.json`, + content: JSON.stringify({ + watchOptions: { + synchronousWatchDirectory: true + } + }) + }; + const file: File = { + path: `${projectSrcFolder}/file1.ts`, + content: "" + }; + verifyTscWatch({ + scenario, + subScenario: `watchDirectories/${subScenario}`, + commandLineArgs: ["--w", "-p", configFile.path], + sys: () => { + const files = [file, configFile, libFile]; + const environmentVariables = new Map(); + environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); + return createWatchedSystem(files, { environmentVariables }); + }, + changes: [ + { + caption: "Rename file1 to file2", + // Rename the file: + change: sys => sys.renameFile(file.path, file.path.replace("file1.ts", "file2.ts")), + timeouts: sys => { + if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { + // With dynamic polling the fs change would be detected only by running timeouts + sys.runQueuedTimeoutCallbacks(); + } + // Delayed update program + sys.runQueuedTimeoutCallbacks(); + return; + }, + }, + ], + }); + } + + verifyRenamingFileInSubFolder("uses watchFile when renaming file in subfolder", Tsc_WatchDirectory.WatchFile); + + verifyRenamingFileInSubFolder("uses non recursive watchDirectory when renaming file in subfolder", Tsc_WatchDirectory.NonRecursiveWatchDirectory); + + verifyRenamingFileInSubFolder("uses non recursive dynamic polling when renaming file in subfolder", Tsc_WatchDirectory.DynamicPolling); + verifyTscWatch({ scenario, - subScenario: "watchFile/using dynamic priority polling", - commandLineArgs: ["--w", `/a/username/project/typescript.ts`], + subScenario: "watchDirectories/when there are symlinks to folders in recursive folders", + commandLineArgs: ["--w"], sys: () => { - const projectFolder = "/a/username/project"; + const cwd = "/home/user/projects/myproject"; const file1: File = { - path: `${projectFolder}/typescript.ts`, - content: "var z = 10;" + path: `${cwd}/src/file.ts`, + content: `import * as a from "a"` + }; + const tsconfig: File = { + path: `${cwd}/tsconfig.json`, + content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}` + }; + const realA: File = { + path: `${cwd}/node_modules/reala/index.d.ts`, + content: `export {}` + }; + const realB: File = { + path: `${cwd}/node_modules/realb/index.d.ts`, + content: `export {}` + }; + const symLinkA: SymLink = { + path: `${cwd}/node_modules/a`, + symLink: `${cwd}/node_modules/reala` }; + const symLinkB: SymLink = { + path: `${cwd}/node_modules/b`, + symLink: `${cwd}/node_modules/realb` + }; + const symLinkBInA: SymLink = { + path: `${cwd}/node_modules/reala/node_modules/b`, + symLink: `${cwd}/node_modules/b` + }; + const symLinkAInB: SymLink = { + path: `${cwd}/node_modules/realb/node_modules/a`, + symLink: `${cwd}/node_modules/a` + }; + const files = [libFile, file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; const environmentVariables = new Map(); - environmentVariables.set("TSC_WATCHFILE", TestFSWithWatch.Tsc_WatchFile.DynamicPolling); - return createWatchedSystem([file1, libFile], { environmentVariables }); + environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); + return createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); + }, + changes: emptyArray + }); + + verifyTscWatch({ + scenario, + subScenario: "watchDirectories/with non synchronous watch directory", + commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], + sys: () => { + const configFile: File = { + path: `${projectRoot}/tsconfig.json`, + content: "{}" + }; + const file1: File = { + path: `${projectRoot}/src/file1.ts`, + content: `import { x } from "file2";` + }; + const file2: File = { + path: `${projectRoot}/node_modules/file2/index.d.ts`, + content: `export const x = 10;` + }; + const files = [libFile, file1, file2, configFile]; + return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); }, changes: [ { - caption: "Time spent to Transition libFile and file1 to low priority queue", + caption: "Directory watch updates because of file1.js creation", change: noop, - timeouts: (sys, programs) => { - const initialProgram = programs[0][0]; - const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; - for (let index = 0; index < mediumPollingIntervalThreshold; index++) { - // Transition libFile and file1 to low priority queue - sys.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(programs[0][0], initialProgram); - } - return; + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for file1.js output + sys.checkTimeoutQueueLength(0); }, }, { - caption: "Make change to file", - // Make a change to file - change: sys => sys.writeFile("/a/username/project/typescript.ts", "var zz30 = 100;"), - // During this timeout the file would be detected as unchanged - timeouts: checkSingleTimeoutQueueLengthAndRun, + caption: "Remove directory node_modules", + // Remove directory node_modules + change: sys => sys.deleteFolder(`${projectRoot}/node_modules`, /*recursive*/ true), + timeouts: sys => { + sys.checkTimeoutQueueLength(3); // 1. Failed lookup invalidation 2. For updating program and 3. for updating child watches + sys.runQueuedTimeoutCallbacks(sys.getNextTimeoutId() - 2); // Update program + }, }, { - caption: "Callbacks: medium priority + high priority queue and scheduled program update", + caption: "Pending directory watchers and program update", change: noop, - // Callbacks: medium priority + high priority queue and scheduled program update - // This should detect change in the file - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(3), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // To update directory watchers + sys.checkTimeoutQueueLengthAndRun(2); // To Update program and failed lookup update + sys.checkTimeoutQueueLengthAndRun(1); // Actual program update + sys.checkTimeoutQueueLength(0); + }, }, { - caption: "Polling queues polled and everything is in the high polling queue", + caption: "Start npm install", + // npm install + change: sys => sys.createDirectory(`${projectRoot}/node_modules`), + timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure + }, + { + caption: "npm install folder creation of file2", + change: sys => sys.createDirectory(`${projectRoot}/node_modules/file2`), + timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure + }, + { + caption: "npm install index file in file2", + change: sys => sys.writeFile(`${projectRoot}/node_modules/file2/index.d.ts`, `export const x = 10;`), + timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure + }, + { + caption: "Updates the program", change: noop, - timeouts: (sys, programs) => { - const initialProgram = programs[0][0]; - const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; - const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold; - for (let fileUnchangeDetected = 1; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) { - // For high + Medium/low polling interval - sys.checkTimeoutQueueLengthAndRun(2); - assert.deepEqual(programs[0][0], initialProgram); - } - - // Everything goes in high polling interval queue - sys.checkTimeoutQueueLengthAndRun(1); - return; + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); + sys.checkTimeoutQueueLength(2); // To Update program and failed lookup update + }, + }, + { + caption: "Invalidates module resolution cache", + change: noop, + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); + sys.checkTimeoutQueueLength(1); // To Update program + }, + }, + { + caption: "Pending updates", + change: noop, + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); + sys.checkTimeoutQueueLength(0); }, - } - ] + }, + ], }); verifyTscWatch({ scenario, - subScenario: "watchFile/using fixed chunk size polling", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + subScenario: "watchDirectories/with non synchronous watch directory with outDir and declaration enabled", + commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], sys: () => { const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - watchOptions: { - watchFile: "FixedChunkSizePolling" - } - }) + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { outDir: "dist", declaration: true } }) }; - const files = [libFile, commonFile1, commonFile2, configFile]; - return createWatchedSystem(files); + const file1: File = { + path: `${projectRoot}/src/file1.ts`, + content: `import { x } from "file2";` + }; + const file2: File = { + path: `${projectRoot}/node_modules/file2/index.d.ts`, + content: `export const x = 10;` + }; + const files = [libFile, file1, file2, configFile]; + return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); }, changes: [ + noopChange, { - caption: "The timeout is to check the status of all files", - change: noop, - timeouts: (sys, programs) => { - // On each timeout file does not change - const initialProgram = programs[0][0]; - for (let index = 0; index < 4; index++) { - sys.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(programs[0][0], initialProgram); - } - }, + caption: "Add new file, should schedule and run timeout to update directory watcher", + change: sys => sys.writeFile(`${projectRoot}/src/file3.ts`, `export const y = 10;`), + timeouts: checkSingleTimeoutQueueLengthAndRun, // Update the child watch }, { - caption: "Make change to file but should detect as changed and schedule program update", - // Make a change to file - change: sys => sys.writeFile(commonFile1.path, "var zz30 = 100;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Callbacks: queue and scheduled program update", + caption: "Actual program update to include new file", change: noop, - // Callbacks: scheduled program update and queue for the polling - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), // Scheduling failed lookup update and program update }, { - caption: "The timeout is to check the status of all files", + caption: "After program emit with new file, should schedule and run timeout to update directory watcher", change: noop, - timeouts: (sys, programs) => { - // On each timeout file does not change - const initialProgram = programs[0][0]; - sys.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(programs[0][0], initialProgram); - }, + timeouts: checkSingleTimeoutQueueLengthAndRun, // Update the child watch }, - ] + noopChange, + ], }); verifyTscWatch({ scenario, - subScenario: "watchFile/setting default as fixed chunk size watch file works", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + subScenario: "watchDirectories/with non synchronous watch directory renaming a file", + commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], sys: () => { const configFile: File = { - path: "/a/b/tsconfig.json", - content: "{}" + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { outDir: "dist" } }) }; - const files = [libFile, commonFile1, commonFile2, configFile]; - const sys = createWatchedSystem(files); - sys.defaultWatchFileKind = () => WatchFileKind.FixedChunkSizePolling; - return sys; + const file1: File = { + path: `${projectRoot}/src/file1.ts`, + content: `import { x } from "./file2";` + }; + const file2: File = { + path: `${projectRoot}/src/file2.ts`, + content: `export const x = 10;` + }; + const files = [libFile, file1, file2, configFile]; + return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); }, changes: [ + noopChange, { - caption: "Make change to file but should detect as changed and schedule program update", - // Make a change to file - change: sys => sys.writeFile(commonFile1.path, "var zz30 = 100;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, + caption: "rename the file", + change: sys => sys.renameFile(`${projectRoot}/src/file2.ts`, `${projectRoot}/src/renamed.ts`), + timeouts: sys => { + sys.checkTimeoutQueueLength(2); // 1. For updating program and 2. for updating child watches + sys.runQueuedTimeoutCallbacks(1); // Update program + }, }, { - caption: "Callbacks: queue and scheduled program update", + caption: "Pending directory watchers and program update", change: noop, - // Callbacks: scheduled program update and queue for the polling - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // To update directory watchers + sys.checkTimeoutQueueLengthAndRun(2); // To Update program and failed lookup update + sys.checkTimeoutQueueLengthAndRun(1); // Actual program update + sys.checkTimeoutQueueLength(0); + }, }, - ] + ], }); + }); - describe("tsc-watch when watchDirectories implementation", () => { - function verifyRenamingFileInSubFolder(subScenario: string, tscWatchDirectory: Tsc_WatchDirectory) { - const projectFolder = "/a/username/project"; - const projectSrcFolder = `${projectFolder}/src`; + describe("handles watch compiler options", () => { + verifyTscWatch({ + scenario, + subScenario: "watchOptions/with watchFile option", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + watchOptions: { + watchFile: "UseFsEvents" + } + }) + }; + const files = [libFile, commonFile1, commonFile2, configFile]; + return createWatchedSystem(files); + }, + changes: emptyArray + }); + + verifyTscWatch({ + scenario, + subScenario: "watchOptions/with watchDirectory option", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + watchOptions: { + watchDirectory: "UseFsEvents" + } + }) + }; + const files = [libFile, commonFile1, commonFile2, configFile]; + return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); + }, + changes: emptyArray + }); + + verifyTscWatch({ + scenario, + subScenario: "watchOptions/with fallbackPolling option", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { const configFile: File = { - path: `${projectFolder}/tsconfig.json`, + path: "/a/b/tsconfig.json", content: JSON.stringify({ watchOptions: { - synchronousWatchDirectory: true + fallbackPolling: "PriorityInterval" } }) }; - const file: File = { - path: `${projectSrcFolder}/file1.ts`, - content: "" + const files = [libFile, commonFile1, commonFile2, configFile]; + return createWatchedSystem(files, { runWithoutRecursiveWatches: true, runWithFallbackPolling: true }); + }, + changes: emptyArray + }); + + verifyTscWatch({ + scenario, + subScenario: "watchOptions/with watchFile as watch options to extend", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json", "--watchFile", "UseFsEvents"], + sys: () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + const files = [libFile, commonFile1, commonFile2, configFile]; + return createWatchedSystem(files); + }, + changes: emptyArray + }); + + describe("exclude options", () => { + function sys(watchOptions: WatchOptions, runWithoutRecursiveWatches?: boolean): WatchedSystem { + const configFile: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ exclude: ["node_modules"], watchOptions }) + }; + const main: File = { + path: `${projectRoot}/src/main.ts`, + content: `import { foo } from "bar"; foo();` + }; + const bar: File = { + path: `${projectRoot}/node_modules/bar/index.d.ts`, + content: `export { foo } from "./foo";` }; + const foo: File = { + path: `${projectRoot}/node_modules/bar/foo.d.ts`, + content: `export function foo(): string;` + }; + const fooBar: File = { + path: `${projectRoot}/node_modules/bar/fooBar.d.ts`, + content: `export function fooBar(): string;` + }; + const temp: File = { + path: `${projectRoot}/node_modules/bar/temp/index.d.ts`, + content: "export function temp(): string;" + }; + const files = [libFile, main, bar, foo, fooBar, temp, configFile]; + return createWatchedSystem(files, { currentDirectory: projectRoot, runWithoutRecursiveWatches }); + } + + function verifyWorker(...additionalFlags: string[]) { verifyTscWatch({ scenario, - subScenario: `watchDirectories/${subScenario}`, - commandLineArgs: ["--w", "-p", configFile.path], - sys: () => { - const files = [file, configFile, libFile]; - const environmentVariables = new Map(); - environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); - return createWatchedSystem(files, { environmentVariables }); - }, + subScenario: `watchOptions/with excludeFiles option${additionalFlags.join("")}`, + commandLineArgs: ["-w", ...additionalFlags], + sys: () => sys({ excludeFiles: ["node_modules/*"] }), + changes: [ + { + caption: "Change foo", + change: sys => replaceFileText(sys, `${projectRoot}/node_modules/bar/foo.d.ts`, "foo", "fooBar"), + timeouts: sys => sys.checkTimeoutQueueLength(0), + } + ] + }); + + verifyTscWatch({ + scenario, + subScenario: `watchOptions/with excludeDirectories option${additionalFlags.join("")}`, + commandLineArgs: ["-w", ...additionalFlags], + sys: () => sys({ excludeDirectories: ["node_modules"] }), changes: [ { - caption: "Rename file1 to file2", - // Rename the file: - change: sys => sys.renameFile(file.path, file.path.replace("file1.ts", "file2.ts")), + caption: "delete fooBar", + change: sys => sys.deleteFile(`${projectRoot}/node_modules/bar/fooBar.d.ts`), + timeouts: sys => sys.checkTimeoutQueueLength(0), } + ] + }); + + verifyTscWatch({ + scenario, + subScenario: `watchOptions/with excludeDirectories option with recursive directory watching${additionalFlags.join("")}`, + commandLineArgs: ["-w", ...additionalFlags], + sys: () => sys({ excludeDirectories: ["**/temp"] }, /*runWithoutRecursiveWatches*/ true), + changes: [ + { + caption: "Directory watch updates because of main.js creation", + change: noop, timeouts: sys => { - if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { - // With dynamic polling the fs change would be detected only by running timeouts - sys.runQueuedTimeoutCallbacks(); - } - // Delayed update program - sys.runQueuedTimeoutCallbacks(); - return; + sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for main.js output + sys.checkTimeoutQueueLength(0); }, }, - ], + { + caption: "add new folder to temp", + change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/node_modules/bar/temp/fooBar/index.d.ts`, content: "export function temp(): string;" }), + timeouts: sys => sys.checkTimeoutQueueLength(0), + } + ] }); } - verifyRenamingFileInSubFolder("uses watchFile when renaming file in subfolder", Tsc_WatchDirectory.WatchFile); - - verifyRenamingFileInSubFolder("uses non recursive watchDirectory when renaming file in subfolder", Tsc_WatchDirectory.NonRecursiveWatchDirectory); - - verifyRenamingFileInSubFolder("uses non recursive dynamic polling when renaming file in subfolder", Tsc_WatchDirectory.DynamicPolling); - - verifyTscWatch({ - scenario, - subScenario: "watchDirectories/when there are symlinks to folders in recursive folders", - commandLineArgs: ["--w"], - sys: () => { - const cwd = "/home/user/projects/myproject"; - const file1: File = { - path: `${cwd}/src/file.ts`, - content: `import * as a from "a"` - }; - const tsconfig: File = { - path: `${cwd}/tsconfig.json`, - content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}` - }; - const realA: File = { - path: `${cwd}/node_modules/reala/index.d.ts`, - content: `export {}` - }; - const realB: File = { - path: `${cwd}/node_modules/realb/index.d.ts`, - content: `export {}` - }; - const symLinkA: SymLink = { - path: `${cwd}/node_modules/a`, - symLink: `${cwd}/node_modules/reala` - }; - const symLinkB: SymLink = { - path: `${cwd}/node_modules/b`, - symLink: `${cwd}/node_modules/realb` - }; - const symLinkBInA: SymLink = { - path: `${cwd}/node_modules/reala/node_modules/b`, - symLink: `${cwd}/node_modules/b` - }; - const symLinkAInB: SymLink = { - path: `${cwd}/node_modules/realb/node_modules/a`, - symLink: `${cwd}/node_modules/a` - }; - const files = [libFile, file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; - const environmentVariables = new Map(); - environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); - return createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "watchDirectories/with non synchronous watch directory", - commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], - sys: () => { - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - const file1: File = { - path: `${projectRoot}/src/file1.ts`, - content: `import { x } from "file2";` - }; - const file2: File = { - path: `${projectRoot}/node_modules/file2/index.d.ts`, - content: `export const x = 10;` - }; - const files = [libFile, file1, file2, configFile]; - return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); - }, - changes: [ - { - caption: "Directory watch updates because of file1.js creation", - change: noop, - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for file1.js output - sys.checkTimeoutQueueLength(0); - }, - }, - { - caption: "Remove directory node_modules", - // Remove directory node_modules - change: sys => sys.deleteFolder(`${projectRoot}/node_modules`, /*recursive*/ true), - timeouts: sys => { - sys.checkTimeoutQueueLength(3); // 1. Failed lookup invalidation 2. For updating program and 3. for updating child watches - sys.runQueuedTimeoutCallbacks(sys.getNextTimeoutId() - 2); // Update program - }, - }, - { - caption: "Pending directory watchers and program update", - change: noop, - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // To update directory watchers - sys.checkTimeoutQueueLengthAndRun(2); // To Update program and failed lookup update - sys.checkTimeoutQueueLengthAndRun(1); // Actual program update - sys.checkTimeoutQueueLength(0); - }, - }, - { - caption: "Start npm install", - // npm install - change: sys => sys.createDirectory(`${projectRoot}/node_modules`), - timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure - }, - { - caption: "npm install folder creation of file2", - change: sys => sys.createDirectory(`${projectRoot}/node_modules/file2`), - timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure - }, - { - caption: "npm install index file in file2", - change: sys => sys.writeFile(`${projectRoot}/node_modules/file2/index.d.ts`, `export const x = 10;`), - timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure - }, - { - caption: "Updates the program", - change: noop, - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); - sys.checkTimeoutQueueLength(2); // To Update program and failed lookup update - }, - }, - { - caption: "Invalidates module resolution cache", - change: noop, - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); - sys.checkTimeoutQueueLength(1); // To Update program - }, - }, - { - caption: "Pending updates", - change: noop, - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); - sys.checkTimeoutQueueLength(0); - }, - }, - ], - }); - - verifyTscWatch({ - scenario, - subScenario: "watchDirectories/with non synchronous watch directory with outDir and declaration enabled", - commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], - sys: () => { - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { outDir: "dist", declaration: true } }) - }; - const file1: File = { - path: `${projectRoot}/src/file1.ts`, - content: `import { x } from "file2";` - }; - const file2: File = { - path: `${projectRoot}/node_modules/file2/index.d.ts`, - content: `export const x = 10;` - }; - const files = [libFile, file1, file2, configFile]; - return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); - }, - changes: [ - noopChange, - { - caption: "Add new file, should schedule and run timeout to update directory watcher", - change: sys => sys.writeFile(`${projectRoot}/src/file3.ts`, `export const y = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun, // Update the child watch - }, - { - caption: "Actual program update to include new file", - change: noop, - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), // Scheduling failed lookup update and program update - }, - { - caption: "After program emit with new file, should schedule and run timeout to update directory watcher", - change: noop, - timeouts: checkSingleTimeoutQueueLengthAndRun, // Update the child watch - }, - noopChange, - ], - }); - - verifyTscWatch({ - scenario, - subScenario: "watchDirectories/with non synchronous watch directory renaming a file", - commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], - sys: () => { - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { outDir: "dist" } }) - }; - const file1: File = { - path: `${projectRoot}/src/file1.ts`, - content: `import { x } from "./file2";` - }; - const file2: File = { - path: `${projectRoot}/src/file2.ts`, - content: `export const x = 10;` - }; - const files = [libFile, file1, file2, configFile]; - return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); - }, - changes: [ - noopChange, - { - caption: "rename the file", - change: sys => sys.renameFile(`${projectRoot}/src/file2.ts`, `${projectRoot}/src/renamed.ts`), - timeouts: sys => { - sys.checkTimeoutQueueLength(2); // 1. For updating program and 2. for updating child watches - sys.runQueuedTimeoutCallbacks(1); // Update program - }, - }, - { - caption: "Pending directory watchers and program update", - change: noop, - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // To update directory watchers - sys.checkTimeoutQueueLengthAndRun(2); // To Update program and failed lookup update - sys.checkTimeoutQueueLengthAndRun(1); // Actual program update - sys.checkTimeoutQueueLength(0); - }, - }, - ], - }); - }); - - describe("handles watch compiler options", () => { - verifyTscWatch({ - scenario, - subScenario: "watchOptions/with watchFile option", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], - sys: () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - watchOptions: { - watchFile: "UseFsEvents" - } - }) - }; - const files = [libFile, commonFile1, commonFile2, configFile]; - return createWatchedSystem(files); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "watchOptions/with watchDirectory option", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], - sys: () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - watchOptions: { - watchDirectory: "UseFsEvents" - } - }) - }; - const files = [libFile, commonFile1, commonFile2, configFile]; - return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "watchOptions/with fallbackPolling option", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], - sys: () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - watchOptions: { - fallbackPolling: "PriorityInterval" - } - }) - }; - const files = [libFile, commonFile1, commonFile2, configFile]; - return createWatchedSystem(files, { runWithoutRecursiveWatches: true, runWithFallbackPolling: true }); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "watchOptions/with watchFile as watch options to extend", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json", "--watchFile", "UseFsEvents"], - sys: () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: "{}" - }; - const files = [libFile, commonFile1, commonFile2, configFile]; - return createWatchedSystem(files); - }, - changes: emptyArray - }); - - describe("exclude options", () => { - function sys(watchOptions: WatchOptions, runWithoutRecursiveWatches?: boolean): WatchedSystem { - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ exclude: ["node_modules"], watchOptions }) - }; - const main: File = { - path: `${projectRoot}/src/main.ts`, - content: `import { foo } from "bar"; foo();` - }; - const bar: File = { - path: `${projectRoot}/node_modules/bar/index.d.ts`, - content: `export { foo } from "./foo";` - }; - const foo: File = { - path: `${projectRoot}/node_modules/bar/foo.d.ts`, - content: `export function foo(): string;` - }; - const fooBar: File = { - path: `${projectRoot}/node_modules/bar/fooBar.d.ts`, - content: `export function fooBar(): string;` - }; - const temp: File = { - path: `${projectRoot}/node_modules/bar/temp/index.d.ts`, - content: "export function temp(): string;" - }; - const files = [libFile, main, bar, foo, fooBar, temp, configFile]; - return createWatchedSystem(files, { currentDirectory: projectRoot, runWithoutRecursiveWatches }); - } - - function verifyWorker(...additionalFlags: string[]) { - verifyTscWatch({ - scenario, - subScenario: `watchOptions/with excludeFiles option${additionalFlags.join("")}`, - commandLineArgs: ["-w", ...additionalFlags], - sys: () => sys({ excludeFiles: ["node_modules/*"] }), - changes: [ - { - caption: "Change foo", - change: sys => replaceFileText(sys, `${projectRoot}/node_modules/bar/foo.d.ts`, "foo", "fooBar"), - timeouts: sys => sys.checkTimeoutQueueLength(0), - } - ] - }); - - verifyTscWatch({ - scenario, - subScenario: `watchOptions/with excludeDirectories option${additionalFlags.join("")}`, - commandLineArgs: ["-w", ...additionalFlags], - sys: () => sys({ excludeDirectories: ["node_modules"] }), - changes: [ - { - caption: "delete fooBar", - change: sys => sys.deleteFile(`${projectRoot}/node_modules/bar/fooBar.d.ts`), - timeouts: sys => sys.checkTimeoutQueueLength(0), } - ] - }); - - verifyTscWatch({ - scenario, - subScenario: `watchOptions/with excludeDirectories option with recursive directory watching${additionalFlags.join("")}`, - commandLineArgs: ["-w", ...additionalFlags], - sys: () => sys({ excludeDirectories: ["**/temp"] }, /*runWithoutRecursiveWatches*/ true), - changes: [ - { - caption: "Directory watch updates because of main.js creation", - change: noop, - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for main.js output - sys.checkTimeoutQueueLength(0); - }, - }, - { - caption: "add new folder to temp", - change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/node_modules/bar/temp/fooBar/index.d.ts`, content: "export function temp(): string;" }), - timeouts: sys => sys.checkTimeoutQueueLength(0), - } - ] - }); - } - - verifyWorker(); - verifyWorker("-extendedDiagnostics"); - }); + verifyWorker(); + verifyWorker("-extendedDiagnostics"); }); }); +}); } diff --git a/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts b/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts index 6d5ee91d68d67..2352bbd1bf24f 100644 --- a/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts +++ b/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts @@ -1,181 +1,181 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: applyChangesToOpenFiles", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: "{}" - }; - const file3: File = { - path: "/a/b/file3.ts", - content: "let xyz = 1;" - }; - const app: File = { - path: "/a/b/app.ts", - content: "let z = 1;" - }; +describe("unittests:: tsserver:: applyChangesToOpenFiles", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + const file3: File = { + path: "/a/b/file3.ts", + content: "let xyz = 1;" + }; + const app: File = { + path: "/a/b/app.ts", + content: "let z = 1;" + }; - function fileContentWithComment(file: File) { - return `// some copy right notice + function fileContentWithComment(file: File) { + return `// some copy right notice ${file.content}`; - } + } - function verifyText(service: server.ProjectService, file: string, expected: string) { - const info = service.getScriptInfo(file)!; - const snap = info.getSnapshot(); - // Verified applied in reverse order - assert.equal(snap.getText(0, snap.getLength()), expected, `Text of changed file: ${file}`); - } + function verifyText(service: server.ProjectService, file: string, expected: string) { + const info = service.getScriptInfo(file)!; + const snap = info.getSnapshot(); + // Verified applied in reverse order + assert.equal(snap.getText(0, snap.getLength()), expected, `Text of changed file: ${file}`); + } - function verifyProjectVersion(project: server.Project, expected: number) { - assert.equal(Number(project.getProjectVersion()), expected); - } + function verifyProjectVersion(project: server.Project, expected: number) { + assert.equal(Number(project.getProjectVersion()), expected); + } - interface Verify { - applyChangesToOpen: (session: TestSession) => void; - openFile1Again: (session: TestSession) => void; - } - function verify({ applyChangesToOpen, openFile1Again }: Verify) { - const host = createServerHost([app, file3, commonFile1, commonFile2, libFile, configFile]); - const session = createSession(host); - session.executeCommandSeq({ - command: protocol.CommandTypes.Open, - arguments: { file: app.path } - }); - const service = session.getProjectService(); - const project = service.configuredProjects.get(configFile.path)!; - assert.isDefined(project); - verifyProjectVersion(project, 1); - session.executeCommandSeq({ - command: protocol.CommandTypes.Open, - arguments: { - file: file3.path, - fileContent: fileContentWithComment(file3) - } - }); - verifyProjectVersion(project, 2); + interface Verify { + applyChangesToOpen: (session: TestSession) => void; + openFile1Again: (session: TestSession) => void; + } + function verify({ applyChangesToOpen, openFile1Again }: Verify) { + const host = createServerHost([app, file3, commonFile1, commonFile2, libFile, configFile]); + const session = createSession(host); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { file: app.path } + }); + const service = session.getProjectService(); + const project = service.configuredProjects.get(configFile.path)!; + assert.isDefined(project); + verifyProjectVersion(project, 1); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: file3.path, + fileContent: fileContentWithComment(file3) + } + }); + verifyProjectVersion(project, 2); - // Verify Texts - verifyText(service, commonFile1.path, commonFile1.content); - verifyText(service, commonFile2.path, commonFile2.content); - verifyText(service, app.path, app.content); - verifyText(service, file3.path, fileContentWithComment(file3)); + // Verify Texts + verifyText(service, commonFile1.path, commonFile1.content); + verifyText(service, commonFile2.path, commonFile2.content); + verifyText(service, app.path, app.content); + verifyText(service, file3.path, fileContentWithComment(file3)); - // Apply changes - applyChangesToOpen(session); + // Apply changes + applyChangesToOpen(session); - // Verify again - verifyProjectVersion(project, 3); - // Open file contents - verifyText(service, commonFile1.path, fileContentWithComment(commonFile1)); - verifyText(service, commonFile2.path, fileContentWithComment(commonFile2)); - verifyText(service, app.path, "let zzz = 10;let zz = 10;let z = 1;"); - verifyText(service, file3.path, file3.content); + // Verify again + verifyProjectVersion(project, 3); + // Open file contents + verifyText(service, commonFile1.path, fileContentWithComment(commonFile1)); + verifyText(service, commonFile2.path, fileContentWithComment(commonFile2)); + verifyText(service, app.path, "let zzz = 10;let zz = 10;let z = 1;"); + verifyText(service, file3.path, file3.content); - // Open file1 again - openFile1Again(session); - assert.isTrue(service.getScriptInfo(commonFile1.path)!.isScriptOpen()); + // Open file1 again + openFile1Again(session); + assert.isTrue(service.getScriptInfo(commonFile1.path)!.isScriptOpen()); - // Verify that file1 contents are changed - verifyProjectVersion(project, 4); - verifyText(service, commonFile1.path, commonFile1.content); - verifyText(service, commonFile2.path, fileContentWithComment(commonFile2)); - verifyText(service, app.path, "let zzz = 10;let zz = 10;let z = 1;"); - verifyText(service, file3.path, file3.content); - } + // Verify that file1 contents are changed + verifyProjectVersion(project, 4); + verifyText(service, commonFile1.path, commonFile1.content); + verifyText(service, commonFile2.path, fileContentWithComment(commonFile2)); + verifyText(service, app.path, "let zzz = 10;let zz = 10;let z = 1;"); + verifyText(service, file3.path, file3.content); + } - it("with applyChangedToOpenFiles request", () => { - verify({ - applyChangesToOpen: session => session.executeCommandSeq({ - command: protocol.CommandTypes.ApplyChangedToOpenFiles, - arguments: { - openFiles: [ - { - fileName: commonFile1.path, - content: fileContentWithComment(commonFile1) - }, - { - fileName: commonFile2.path, - content: fileContentWithComment(commonFile2) - } - ], - changedFiles: [ - { - fileName: app.path, - changes: [ - { - span: { start: 0, length: 0 }, - newText: "let zzz = 10;" - }, - { - span: { start: 0, length: 0 }, - newText: "let zz = 10;" - } - ] - } - ], - closedFiles: [ - file3.path - ] - } - }), - openFile1Again: session => session.executeCommandSeq({ - command: protocol.CommandTypes.ApplyChangedToOpenFiles, - arguments: { - openFiles: [{ + it("with applyChangedToOpenFiles request", () => { + verify({ + applyChangesToOpen: session => session.executeCommandSeq({ + command: protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + openFiles: [ + { fileName: commonFile1.path, - content: commonFile1.content - }] - } - }), - }); + content: fileContentWithComment(commonFile1) + }, + { + fileName: commonFile2.path, + content: fileContentWithComment(commonFile2) + } + ], + changedFiles: [ + { + fileName: app.path, + changes: [ + { + span: { start: 0, length: 0 }, + newText: "let zzz = 10;" + }, + { + span: { start: 0, length: 0 }, + newText: "let zz = 10;" + } + ] + } + ], + closedFiles: [ + file3.path + ] + } + }), + openFile1Again: session => session.executeCommandSeq({ + command: protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + openFiles: [{ + fileName: commonFile1.path, + content: commonFile1.content + }] + } + }), }); + }); - it("with updateOpen request", () => { - verify({ - applyChangesToOpen: session => session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - openFiles: [ - { - file: commonFile1.path, - fileContent: fileContentWithComment(commonFile1) - }, - { - file: commonFile2.path, - fileContent: fileContentWithComment(commonFile2) - } - ], - changedFiles: [ - { - fileName: app.path, - textChanges: [ - { - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - newText: "let zzz = 10;", - }, - { - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - newText: "let zz = 10;", - } - ] - } - ], - closedFiles: [ - file3.path - ] - } - }), - openFile1Again: session => session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - openFiles: [{ + it("with updateOpen request", () => { + verify({ + applyChangesToOpen: session => session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + openFiles: [ + { file: commonFile1.path, - fileContent: commonFile1.content - }] - } - }), - }); + fileContent: fileContentWithComment(commonFile1) + }, + { + file: commonFile2.path, + fileContent: fileContentWithComment(commonFile2) + } + ], + changedFiles: [ + { + fileName: app.path, + textChanges: [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: "let zzz = 10;", + }, + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: "let zz = 10;", + } + ] + } + ], + closedFiles: [ + file3.path + ] + } + }), + openFile1Again: session => session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + openFiles: [{ + file: commonFile1.path, + fileContent: commonFile1.content + }] + } + }), }); }); +}); } diff --git a/src/testRunner/unittests/tsserver/autoImportProvider.ts b/src/testRunner/unittests/tsserver/autoImportProvider.ts index f05d06f484237..27f20261845c6 100644 --- a/src/testRunner/unittests/tsserver/autoImportProvider.ts +++ b/src/testRunner/unittests/tsserver/autoImportProvider.ts @@ -1,353 +1,353 @@ namespace ts.projectSystem { - const angularFormsDts: File = { - path: "/node_modules/@angular/forms/forms.d.ts", - content: "export declare class PatternValidator {}", - }; - const angularFormsPackageJson: File = { - path: "/node_modules/@angular/forms/package.json", - content: `{ "name": "@angular/forms", "typings": "./forms.d.ts" }`, - }; - const angularCoreDts: File = { - path: "/node_modules/@angular/core/core.d.ts", - content: "", - }; - const angularCorePackageJson: File = { - path: "/node_modules/@angular/core/package.json", - content: `{ "name": "@angular/core", "typings": "./core.d.ts" }`, - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: `{ "compilerOptions": { "module": "commonjs" } }`, - }; - const packageJson: File = { - path: "/package.json", - content: `{ "dependencies": { "@angular/forms": "*", "@angular/core": "*" } }` - }; - const indexTs: File = { - path: "/index.ts", - content: "" - }; +const angularFormsDts: File = { + path: "/node_modules/@angular/forms/forms.d.ts", + content: "export declare class PatternValidator {}", +}; +const angularFormsPackageJson: File = { + path: "/node_modules/@angular/forms/package.json", + content: `{ "name": "@angular/forms", "typings": "./forms.d.ts" }`, +}; +const angularCoreDts: File = { + path: "/node_modules/@angular/core/core.d.ts", + content: "", +}; +const angularCorePackageJson: File = { + path: "/node_modules/@angular/core/package.json", + content: `{ "name": "@angular/core", "typings": "./core.d.ts" }`, +}; +const tsconfig: File = { + path: "/tsconfig.json", + content: `{ "compilerOptions": { "module": "commonjs" } }`, +}; +const packageJson: File = { + path: "/package.json", + content: `{ "dependencies": { "@angular/forms": "*", "@angular/core": "*" } }` +}; +const indexTs: File = { + path: "/index.ts", + content: "" +}; + +describe("unittests:: tsserver:: autoImportProvider", () => { + it("Auto import provider program is not created without dependencies listed in package.json", () => { + const { projectService, session } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + { path: packageJson.path, content: `{ "dependencies": {} }` }, + indexTs + ]); + openFilesForSession([indexTs], session); + assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + }); - describe("unittests:: tsserver:: autoImportProvider", () => { - it("Auto import provider program is not created without dependencies listed in package.json", () => { - const { projectService, session } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - { path: packageJson.path, content: `{ "dependencies": {} }` }, - indexTs - ]); - openFilesForSession([indexTs], session); - assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - }); + it("Auto import provider program is not created if dependencies are already in main program", () => { + const { projectService, session } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + packageJson, + { path: indexTs.path, content: "import '@angular/forms';" } + ]); + openFilesForSession([indexTs], session); + assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + }); - it("Auto import provider program is not created if dependencies are already in main program", () => { - const { projectService, session } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - packageJson, - { path: indexTs.path, content: "import '@angular/forms';" } - ]); - openFilesForSession([indexTs], session); - assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - }); + it("Auto-import program is not created for projects already inside node_modules", () => { + // Simulate browsing typings files inside node_modules: no point creating auto import program + // for the InferredProject that gets created in there. + const { projectService, session } = setup([ + angularFormsDts, + { path: angularFormsPackageJson.path, content: `{ "dependencies": { "@angular/core": "*" } }` }, + { path: "/node_modules/@angular/core/package.json", content: `{ "typings": "./core.d.ts" }` }, + { path: "/node_modules/@angular/core/core.d.ts", content: `export namespace angular {};` }, + ]); + + openFilesForSession([angularFormsDts], session); + checkNumberOfInferredProjects(projectService, 1); + checkNumberOfConfiguredProjects(projectService, 0); + assert.isUndefined(projectService + .getDefaultProjectForFile(angularFormsDts.path as server.NormalizedPath, /*ensureProject*/ true)! + .getLanguageService() + .getAutoImportProvider()); + }); - it("Auto-import program is not created for projects already inside node_modules", () => { - // Simulate browsing typings files inside node_modules: no point creating auto import program - // for the InferredProject that gets created in there. - const { projectService, session } = setup([ - angularFormsDts, - { path: angularFormsPackageJson.path, content: `{ "dependencies": { "@angular/core": "*" } }` }, - { path: "/node_modules/@angular/core/package.json", content: `{ "typings": "./core.d.ts" }` }, - { path: "/node_modules/@angular/core/core.d.ts", content: `export namespace angular {};` }, - ]); - - openFilesForSession([angularFormsDts], session); - checkNumberOfInferredProjects(projectService, 1); - checkNumberOfConfiguredProjects(projectService, 0); - assert.isUndefined(projectService - .getDefaultProjectForFile(angularFormsDts.path as server.NormalizedPath, /*ensureProject*/ true)! - .getLanguageService() - .getAutoImportProvider()); - }); + it("Auto-importable file is in inferred project until imported", () => { + const { projectService, session, updateFile } = setup([angularFormsDts, angularFormsPackageJson, tsconfig, packageJson, indexTs]); + checkNumberOfInferredProjects(projectService, 0); + openFilesForSession([angularFormsDts], session); + checkNumberOfInferredProjects(projectService, 1); + assert.equal( + projectService.getDefaultProjectForFile(angularFormsDts.path as server.NormalizedPath, /*ensureProject*/ true)?.projectKind, + server.ProjectKind.Inferred); + + updateFile(indexTs.path, "import '@angular/forms'"); + assert.equal( + projectService.getDefaultProjectForFile(angularFormsDts.path as server.NormalizedPath, /*ensureProject*/ true)?.projectKind, + server.ProjectKind.Configured); + + assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + }); - it("Auto-importable file is in inferred project until imported", () => { - const { projectService, session, updateFile } = setup([angularFormsDts, angularFormsPackageJson, tsconfig, packageJson, indexTs]); - checkNumberOfInferredProjects(projectService, 0); - openFilesForSession([angularFormsDts], session); - checkNumberOfInferredProjects(projectService, 1); - assert.equal( - projectService.getDefaultProjectForFile(angularFormsDts.path as server.NormalizedPath, /*ensureProject*/ true)?.projectKind, - server.ProjectKind.Inferred); - - updateFile(indexTs.path, "import '@angular/forms'"); - assert.equal( - projectService.getDefaultProjectForFile(angularFormsDts.path as server.NormalizedPath, /*ensureProject*/ true)?.projectKind, - server.ProjectKind.Configured); - - assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - }); + it("Responds to package.json changes", () => { + const { projectService, session, host } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + { path: "/package.json", content: "{}" }, + indexTs + ]); - it("Responds to package.json changes", () => { - const { projectService, session, host } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - { path: "/package.json", content: "{}" }, - indexTs - ]); + openFilesForSession([indexTs], session); + assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - openFilesForSession([indexTs], session); - assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + host.writeFile(packageJson.path, packageJson.content); + assert.ok(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + }); - host.writeFile(packageJson.path, packageJson.content); - assert.ok(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - }); + it("Reuses autoImportProvider when program structure is unchanged", () => { + const { projectService, session, updateFile } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + packageJson, + indexTs + ]); + + openFilesForSession([indexTs], session); + const autoImportProvider = projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider(); + assert.ok(autoImportProvider); + + updateFile(indexTs.path, "console.log(0)"); + assert.strictEqual( + projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider(), + autoImportProvider); + }); - it("Reuses autoImportProvider when program structure is unchanged", () => { - const { projectService, session, updateFile } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - packageJson, - indexTs - ]); - - openFilesForSession([indexTs], session); - const autoImportProvider = projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider(); - assert.ok(autoImportProvider); - - updateFile(indexTs.path, "console.log(0)"); - assert.strictEqual( - projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider(), - autoImportProvider); - }); + it("Closes AutoImportProviderProject when host project closes", () => { + const { projectService, session } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + packageJson, + indexTs + ]); + + openFilesForSession([indexTs], session); + const hostProject = projectService.configuredProjects.get(tsconfig.path)!; + hostProject.getPackageJsonAutoImportProvider(); + const autoImportProviderProject = hostProject.autoImportProviderHost; + assert.ok(autoImportProviderProject); + + hostProject.close(); + assert.ok(autoImportProviderProject && autoImportProviderProject.isClosed()); + assert.isUndefined(hostProject.autoImportProviderHost); + }); - it("Closes AutoImportProviderProject when host project closes", () => { - const { projectService, session } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - packageJson, - indexTs - ]); - - openFilesForSession([indexTs], session); - const hostProject = projectService.configuredProjects.get(tsconfig.path)!; - hostProject.getPackageJsonAutoImportProvider(); - const autoImportProviderProject = hostProject.autoImportProviderHost; - assert.ok(autoImportProviderProject); - - hostProject.close(); - assert.ok(autoImportProviderProject && autoImportProviderProject.isClosed()); - assert.isUndefined(hostProject.autoImportProviderHost); - }); + it("Does not schedule ensureProjectForOpenFiles on AutoImportProviderProject creation", () => { + const { projectService, session, host } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + indexTs + ]); + + // Create configured project only, ensure !projectService.pendingEnsureProjectForOpenFiles + openFilesForSession([indexTs], session); + const hostProject = projectService.configuredProjects.get(tsconfig.path)!; + projectService.delayEnsureProjectForOpenFiles(); + host.runQueuedTimeoutCallbacks(); + assert.isFalse(projectService.pendingEnsureProjectForOpenFiles); + + // Create auto import provider project, ensure still !projectService.pendingEnsureProjectForOpenFiles + host.writeFile(packageJson.path, packageJson.content); + hostProject.getPackageJsonAutoImportProvider(); + assert.isFalse(projectService.pendingEnsureProjectForOpenFiles); + }); - it("Does not schedule ensureProjectForOpenFiles on AutoImportProviderProject creation", () => { - const { projectService, session, host } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - indexTs - ]); - - // Create configured project only, ensure !projectService.pendingEnsureProjectForOpenFiles - openFilesForSession([indexTs], session); - const hostProject = projectService.configuredProjects.get(tsconfig.path)!; - projectService.delayEnsureProjectForOpenFiles(); - host.runQueuedTimeoutCallbacks(); - assert.isFalse(projectService.pendingEnsureProjectForOpenFiles); - - // Create auto import provider project, ensure still !projectService.pendingEnsureProjectForOpenFiles - host.writeFile(packageJson.path, packageJson.content); - hostProject.getPackageJsonAutoImportProvider(); - assert.isFalse(projectService.pendingEnsureProjectForOpenFiles); - }); + it("Responds to automatic changes in node_modules", () => { + const { projectService, session, host } = setup([ + angularFormsDts, + angularFormsPackageJson, + angularCoreDts, + angularCorePackageJson, + tsconfig, + packageJson, + indexTs + ]); + + openFilesForSession([indexTs], session); + const project = projectService.configuredProjects.get(tsconfig.path)!; + const completionsBefore = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); + assert.isTrue(completionsBefore?.entries.some(c => c.name === "PatternValidator")); + + // Directory watchers only fire for add/remove, not change. + // This is ok since a real `npm install` will always trigger add/remove events. + host.deleteFile(angularFormsDts.path); + host.writeFile(angularFormsDts.path, ""); + + const autoImportProvider = project.getLanguageService().getAutoImportProvider(); + const completionsAfter = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); + assert.equal(autoImportProvider!.getSourceFile(angularFormsDts.path)!.getText(), ""); + assert.isFalse(completionsAfter?.entries.some(c => c.name === "PatternValidator")); + }); - it("Responds to automatic changes in node_modules", () => { - const { projectService, session, host } = setup([ - angularFormsDts, - angularFormsPackageJson, - angularCoreDts, - angularCorePackageJson, - tsconfig, - packageJson, - indexTs - ]); - - openFilesForSession([indexTs], session); - const project = projectService.configuredProjects.get(tsconfig.path)!; - const completionsBefore = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); - assert.isTrue(completionsBefore?.entries.some(c => c.name === "PatternValidator")); - - // Directory watchers only fire for add/remove, not change. - // This is ok since a real `npm install` will always trigger add/remove events. - host.deleteFile(angularFormsDts.path); - host.writeFile(angularFormsDts.path, ""); - - const autoImportProvider = project.getLanguageService().getAutoImportProvider(); - const completionsAfter = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); - assert.equal(autoImportProvider!.getSourceFile(angularFormsDts.path)!.getText(), ""); - assert.isFalse(completionsAfter?.entries.some(c => c.name === "PatternValidator")); - }); + it("Responds to manual changes in node_modules", () => { + const { projectService, session, updateFile } = setup([ + angularFormsDts, + angularFormsPackageJson, + angularCoreDts, + angularCorePackageJson, + tsconfig, + packageJson, + indexTs + ]); + + openFilesForSession([indexTs, angularFormsDts], session); + const project = projectService.configuredProjects.get(tsconfig.path)!; + const completionsBefore = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); + assert.isTrue(completionsBefore?.entries.some(c => c.name === "PatternValidator")); + + updateFile(angularFormsDts.path, "export class ValidatorPattern {}"); + const completionsAfter = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); + assert.isFalse(completionsAfter?.entries.some(c => c.name === "PatternValidator")); + assert.isTrue(completionsAfter?.entries.some(c => c.name === "ValidatorPattern")); + }); - it("Responds to manual changes in node_modules", () => { - const { projectService, session, updateFile } = setup([ - angularFormsDts, - angularFormsPackageJson, - angularCoreDts, - angularCorePackageJson, - tsconfig, - packageJson, - indexTs - ]); - - openFilesForSession([indexTs, angularFormsDts], session); - const project = projectService.configuredProjects.get(tsconfig.path)!; - const completionsBefore = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); - assert.isTrue(completionsBefore?.entries.some(c => c.name === "PatternValidator")); - - updateFile(angularFormsDts.path, "export class ValidatorPattern {}"); - const completionsAfter = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); - assert.isFalse(completionsAfter?.entries.some(c => c.name === "PatternValidator")); - assert.isTrue(completionsAfter?.entries.some(c => c.name === "ValidatorPattern")); - }); + it("Recovers from an unparseable package.json", () => { + const { projectService, session, host } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + { path: packageJson.path, content: "{" }, + indexTs + ]); - it("Recovers from an unparseable package.json", () => { - const { projectService, session, host } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - { path: packageJson.path, content: "{" }, - indexTs - ]); + openFilesForSession([indexTs], session); + assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - openFilesForSession([indexTs], session); - assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + host.writeFile(packageJson.path, packageJson.content); + assert.ok(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + }); - host.writeFile(packageJson.path, packageJson.content); - assert.ok(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - }); + it("Does not create an auto import provider if there are too many dependencies", () => { + const createPackage = (i: number): File[] => ([ + { path: `/node_modules/package${i}/package.json`, content: `{ "name": "package${i}" }` }, + { path: `/node_modules/package${i}/index.d.ts`, content: `` } + ]); - it("Does not create an auto import provider if there are too many dependencies", () => { - const createPackage = (i: number): File[] => ([ - { path: `/node_modules/package${i}/package.json`, content: `{ "name": "package${i}" }` }, - { path: `/node_modules/package${i}/index.d.ts`, content: `` } - ]); + const packages = []; + for (let i = 0; i < 11; i++) { + packages.push(createPackage(i)); + } - const packages = []; - for (let i = 0; i < 11; i++) { - packages.push(createPackage(i)); - } + const dependencies = packages.reduce((hash, p) => ({ ...hash, [JSON.parse(p[0].content).name]: "*" }), {}); + const packageJson: File = { path: "/package.json", content: JSON.stringify(dependencies) }; + const { projectService, session } = setup([ ...flatten(packages), indexTs, tsconfig, packageJson ]); - const dependencies = packages.reduce((hash, p) => ({ ...hash, [JSON.parse(p[0].content).name]: "*" }), {}); - const packageJson: File = { path: "/package.json", content: JSON.stringify(dependencies) }; - const { projectService, session } = setup([ ...flatten(packages), indexTs, tsconfig, packageJson ]); + openFilesForSession([indexTs], session); + const project = projectService.configuredProjects.get(tsconfig.path)!; + assert.isUndefined(project.getPackageJsonAutoImportProvider()); + }); +}); + +describe("unittests:: tsserver:: autoImportProvider - monorepo", () => { + it("Does not create auto import providers upon opening projects for find-all-references", () => { + const files = [ + // node_modules + angularFormsDts, + angularFormsPackageJson, + + // root + { path: tsconfig.path, content: `{ "references": [{ "path": "packages/a" }, { "path": "packages/b" }] }` }, + { path: packageJson.path, content: `{ "private": true }` }, + + // packages/a + { path: "/packages/a/package.json", content: packageJson.content }, + { path: "/packages/a/tsconfig.json", content: `{ "compilerOptions": { "composite": true }, "references": [{ "path": "../b" }] }` }, + { path: "/packages/a/index.ts", content: "import { B } from '../b';" }, + + // packages/b + { path: "/packages/b/package.json", content: packageJson.content }, + { path: "/packages/b/tsconfig.json", content: `{ "compilerOptions": { "composite": true } }` }, + { path: "/packages/b/index.ts", content: `export class B {}` } + ]; + + const { projectService, session, findAllReferences } = setup(files); + + openFilesForSession([files.find(f => f.path === "/packages/b/index.ts")!], session); + checkNumberOfConfiguredProjects(projectService, 2); // Solution (no files), B + findAllReferences("/packages/b/index.ts", 1, "export class B".length - 1); + checkNumberOfConfiguredProjects(projectService, 3); // Solution (no files), A, B + + // Project for A is created - ensure it doesn't have an autoImportProvider + assert.isUndefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getLanguageService().getAutoImportProvider()); + }); - openFilesForSession([indexTs], session); - const project = projectService.configuredProjects.get(tsconfig.path)!; - assert.isUndefined(project.getPackageJsonAutoImportProvider()); - }); + it("Does not close when root files are redirects that don't actually exist", () => { + const files = [ + // packages/a + { path: "/packages/a/package.json", content: `{ "dependencies": { "b": "*" } }` }, + { path: "/packages/a/tsconfig.json", content: `{ "compilerOptions": { "composite": true }, "references": [{ "path": "./node_modules/b" }] }` }, + { path: "/packages/a/index.ts", content: "" }, + + // packages/b + { path: "/packages/a/node_modules/b/package.json", content: `{ "types": "dist/index.d.ts" }` }, + { path: "/packages/a/node_modules/b/tsconfig.json", content: `{ "compilerOptions": { "composite": true, "outDir": "dist" } }` }, + { path: "/packages/a/node_modules/b/index.ts", content: `export class B {}` } + ]; + + const { projectService, session } = setup(files); + openFilesForSession([files[2]], session); + assert.isDefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getPackageJsonAutoImportProvider()); + assert.isDefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getPackageJsonAutoImportProvider()); }); - describe("unittests:: tsserver:: autoImportProvider - monorepo", () => { - it("Does not create auto import providers upon opening projects for find-all-references", () => { - const files = [ - // node_modules - angularFormsDts, - angularFormsPackageJson, - - // root - { path: tsconfig.path, content: `{ "references": [{ "path": "packages/a" }, { "path": "packages/b" }] }` }, - { path: packageJson.path, content: `{ "private": true }` }, - - // packages/a - { path: "/packages/a/package.json", content: packageJson.content }, - { path: "/packages/a/tsconfig.json", content: `{ "compilerOptions": { "composite": true }, "references": [{ "path": "../b" }] }` }, - { path: "/packages/a/index.ts", content: "import { B } from '../b';" }, - - // packages/b - { path: "/packages/b/package.json", content: packageJson.content }, - { path: "/packages/b/tsconfig.json", content: `{ "compilerOptions": { "composite": true } }` }, - { path: "/packages/b/index.ts", content: `export class B {}` } - ]; - - const { projectService, session, findAllReferences } = setup(files); - - openFilesForSession([files.find(f => f.path === "/packages/b/index.ts")!], session); - checkNumberOfConfiguredProjects(projectService, 2); // Solution (no files), B - findAllReferences("/packages/b/index.ts", 1, "export class B".length - 1); - checkNumberOfConfiguredProjects(projectService, 3); // Solution (no files), A, B - - // Project for A is created - ensure it doesn't have an autoImportProvider - assert.isUndefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getLanguageService().getAutoImportProvider()); - }); + it("Can use the same document registry bucket key as main program", () => { + for (const option of sourceFileAffectingCompilerOptions) { + assert( + !hasProperty(server.AutoImportProviderProject.compilerOptionsOverrides, option.name), + `'${option.name}' may cause AutoImportProviderProject not to share source files with main program` + ); + } + }); +}); + +function setup(files: File[]) { + const host = createServerHost(files); + const session = createSession(host); + const projectService = session.getProjectService(); + return { + host, + projectService, + session, + updateFile, + findAllReferences + }; - it("Does not close when root files are redirects that don't actually exist", () => { - const files = [ - // packages/a - { path: "/packages/a/package.json", content: `{ "dependencies": { "b": "*" } }` }, - { path: "/packages/a/tsconfig.json", content: `{ "compilerOptions": { "composite": true }, "references": [{ "path": "./node_modules/b" }] }` }, - { path: "/packages/a/index.ts", content: "" }, - - // packages/b - { path: "/packages/a/node_modules/b/package.json", content: `{ "types": "dist/index.d.ts" }` }, - { path: "/packages/a/node_modules/b/tsconfig.json", content: `{ "compilerOptions": { "composite": true, "outDir": "dist" } }` }, - { path: "/packages/a/node_modules/b/index.ts", content: `export class B {}` } - ]; - - const { projectService, session } = setup(files); - openFilesForSession([files[2]], session); - assert.isDefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getPackageJsonAutoImportProvider()); - assert.isDefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getPackageJsonAutoImportProvider()); + function updateFile(path: string, newText: string) { + Debug.assertIsDefined(files.find(f => f.path === path)); + session.executeCommandSeq({ + command: protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + openFiles: [{ + fileName: path, + content: newText + }] + } }); + } - it("Can use the same document registry bucket key as main program", () => { - for (const option of sourceFileAffectingCompilerOptions) { - assert( - !hasProperty(server.AutoImportProviderProject.compilerOptionsOverrides, option.name), - `'${option.name}' may cause AutoImportProviderProject not to share source files with main program` - ); + function findAllReferences(file: string, line: number, offset: number) { + Debug.assertIsDefined(files.find(f => f.path === file)); + session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: { + file, + line, + offset } }); - }); - - function setup(files: File[]) { - const host = createServerHost(files); - const session = createSession(host); - const projectService = session.getProjectService(); - return { - host, - projectService, - session, - updateFile, - findAllReferences - }; - - function updateFile(path: string, newText: string) { - Debug.assertIsDefined(files.find(f => f.path === path)); - session.executeCommandSeq({ - command: protocol.CommandTypes.ApplyChangedToOpenFiles, - arguments: { - openFiles: [{ - fileName: path, - content: newText - }] - } - }); - } - - function findAllReferences(file: string, line: number, offset: number) { - Debug.assertIsDefined(files.find(f => f.path === file)); - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: { - file, - line, - offset - } - }); - } } } +} diff --git a/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts index 34863897e8abb..5ddfd9e7cea5a 100644 --- a/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts +++ b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts @@ -1,521 +1,521 @@ namespace ts.projectSystem { - function getNumberOfWatchesInvokedForRecursiveWatches(recursiveWatchedDirs: string[], file: string) { - return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator); - } +function getNumberOfWatchesInvokedForRecursiveWatches(recursiveWatchedDirs: string[], file: string) { + return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator); +} - describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => { - enum CalledMapsWithSingleArg { - fileExists = "fileExists", - directoryExists = "directoryExists", - getDirectories = "getDirectories", - readFile = "readFile" - } - enum CalledMapsWithFiveArgs { - readDirectory = "readDirectory" - } - type CalledMaps = CalledMapsWithSingleArg | CalledMapsWithFiveArgs; - type CalledWithFiveArgs = [readonly string[], readonly string[], readonly string[], number]; - function createCallsTrackingHost(host: TestServerHost) { - const calledMaps: Record> & Record> = { - fileExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.fileExists), - directoryExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.directoryExists), - getDirectories: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.getDirectories), - readFile: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.readFile), - readDirectory: setCallsTrackingWithFiveArgFn(CalledMapsWithFiveArgs.readDirectory) +describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => { + enum CalledMapsWithSingleArg { + fileExists = "fileExists", + directoryExists = "directoryExists", + getDirectories = "getDirectories", + readFile = "readFile" + } + enum CalledMapsWithFiveArgs { + readDirectory = "readDirectory" + } + type CalledMaps = CalledMapsWithSingleArg | CalledMapsWithFiveArgs; + type CalledWithFiveArgs = [readonly string[], readonly string[], readonly string[], number]; + function createCallsTrackingHost(host: TestServerHost) { + const calledMaps: Record> & Record> = { + fileExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.fileExists), + directoryExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.directoryExists), + getDirectories: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.getDirectories), + readFile: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.readFile), + readDirectory: setCallsTrackingWithFiveArgFn(CalledMapsWithFiveArgs.readDirectory) + }; + + return { + verifyNoCall, + verifyCalledOnEachEntryNTimes, + verifyCalledOnEachEntry, + verifyNoHostCalls, + verifyNoHostCallsExceptFileExistsOnce, + verifyCalledOn, + clear + }; + + function setCallsTrackingWithSingleArgFn(prop: CalledMapsWithSingleArg) { + const calledMap = createMultiMap(); + const cb = (host as any)[prop].bind(host); + (host as any)[prop] = (f: string) => { + calledMap.add(f, /*value*/ true); + return cb(f); }; + return calledMap; + } - return { - verifyNoCall, - verifyCalledOnEachEntryNTimes, - verifyCalledOnEachEntry, - verifyNoHostCalls, - verifyNoHostCallsExceptFileExistsOnce, - verifyCalledOn, - clear + function setCallsTrackingWithFiveArgFn(prop: CalledMapsWithFiveArgs) { + const calledMap = createMultiMap<[U, V, W, X]>(); + const cb = (host as any)[prop].bind(host); + (host as any)[prop] = (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => { + calledMap.add(f, [arg1!, arg2!, arg3!, arg4!]); // TODO: GH#18217 + return cb(f, arg1, arg2, arg3, arg4); }; + return calledMap; + } - function setCallsTrackingWithSingleArgFn(prop: CalledMapsWithSingleArg) { - const calledMap = createMultiMap(); - const cb = (host as any)[prop].bind(host); - (host as any)[prop] = (f: string) => { - calledMap.add(f, /*value*/ true); - return cb(f); - }; - return calledMap; - } - - function setCallsTrackingWithFiveArgFn(prop: CalledMapsWithFiveArgs) { - const calledMap = createMultiMap<[U, V, W, X]>(); - const cb = (host as any)[prop].bind(host); - (host as any)[prop] = (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => { - calledMap.add(f, [arg1!, arg2!, arg3!, arg4!]); // TODO: GH#18217 - return cb(f, arg1, arg2, arg3, arg4); - }; - return calledMap; - } + function verifyCalledOn(callback: CalledMaps, name: string) { + const calledMap = calledMaps[callback]; + const result = calledMap.get(name); + assert.isTrue(result && !!result.length, `${callback} should be called with name: ${name}: ${arrayFrom(calledMap.keys())}`); + } - function verifyCalledOn(callback: CalledMaps, name: string) { - const calledMap = calledMaps[callback]; - const result = calledMap.get(name); - assert.isTrue(result && !!result.length, `${callback} should be called with name: ${name}: ${arrayFrom(calledMap.keys())}`); - } + function verifyNoCall(callback: CalledMaps) { + const calledMap = calledMaps[callback]; + assert.equal(calledMap.size, 0, `${callback} shouldn't be called: ${arrayFrom(calledMap.keys())}`); + } - function verifyNoCall(callback: CalledMaps) { - const calledMap = calledMaps[callback]; - assert.equal(calledMap.size, 0, `${callback} shouldn't be called: ${arrayFrom(calledMap.keys())}`); - } + function verifyCalledOnEachEntry(callback: CalledMaps, expectedKeys: ESMap) { + TestFSWithWatch.checkMap(callback, calledMaps[callback], expectedKeys); + } - function verifyCalledOnEachEntry(callback: CalledMaps, expectedKeys: ESMap) { - TestFSWithWatch.checkMap(callback, calledMaps[callback], expectedKeys); - } + function verifyCalledOnEachEntryNTimes(callback: CalledMaps, expectedKeys: readonly string[], nTimes: number) { + TestFSWithWatch.checkMap(callback, calledMaps[callback], expectedKeys, nTimes); + } - function verifyCalledOnEachEntryNTimes(callback: CalledMaps, expectedKeys: readonly string[], nTimes: number) { - TestFSWithWatch.checkMap(callback, calledMaps[callback], expectedKeys, nTimes); - } + function verifyNoHostCalls() { + iterateOnCalledMaps(key => verifyNoCall(key)); + } - function verifyNoHostCalls() { - iterateOnCalledMaps(key => verifyNoCall(key)); - } + function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: readonly string[]) { + verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, expectedKeys, 1); + verifyNoCall(CalledMapsWithSingleArg.directoryExists); + verifyNoCall(CalledMapsWithSingleArg.getDirectories); + verifyNoCall(CalledMapsWithSingleArg.readFile); + verifyNoCall(CalledMapsWithFiveArgs.readDirectory); + } - function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: readonly string[]) { - verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, expectedKeys, 1); - verifyNoCall(CalledMapsWithSingleArg.directoryExists); - verifyNoCall(CalledMapsWithSingleArg.getDirectories); - verifyNoCall(CalledMapsWithSingleArg.readFile); - verifyNoCall(CalledMapsWithFiveArgs.readDirectory); - } + function clear() { + iterateOnCalledMaps(key => calledMaps[key].clear()); + } - function clear() { - iterateOnCalledMaps(key => calledMaps[key].clear()); + function iterateOnCalledMaps(cb: (key: CalledMaps) => void) { + for (const key in CalledMapsWithSingleArg) { + cb(key as CalledMapsWithSingleArg); } - - function iterateOnCalledMaps(cb: (key: CalledMaps) => void) { - for (const key in CalledMapsWithSingleArg) { - cb(key as CalledMapsWithSingleArg); - } - for (const key in CalledMapsWithFiveArgs) { - cb(key as CalledMapsWithFiveArgs); - } + for (const key in CalledMapsWithFiveArgs) { + cb(key as CalledMapsWithFiveArgs); } } + } - it("works using legacy resolution logic", () => { - let rootContent = `import {x} from "f1"`; - const root: File = { - path: "/c/d/f0.ts", - content: rootContent - }; - - const imported: File = { - path: "/c/f1.ts", - content: `foo()` - }; - - const host = createServerHost([root, imported]); - const projectService = createProjectService(host); - projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true }); - projectService.openClientFile(root.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const project = projectService.inferredProjects[0]; - const rootScriptInfo = project.getRootScriptInfos()[0]; - assert.equal(rootScriptInfo.fileName, root.path); - - // ensure that imported file was found - verifyImportedDiagnostics(); - - const callsTrackingHost = createCallsTrackingHost(host); - - // trigger synchronization to make sure that import will be fetched from the cache - // ensure file has correct number of errors after edit - editContent(`import {x} from "f1"; + it("works using legacy resolution logic", () => { + let rootContent = `import {x} from "f1"`; + const root: File = { + path: "/c/d/f0.ts", + content: rootContent + }; + + const imported: File = { + path: "/c/f1.ts", + content: `foo()` + }; + + const host = createServerHost([root, imported]); + const projectService = createProjectService(host); + projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true }); + projectService.openClientFile(root.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + const rootScriptInfo = project.getRootScriptInfos()[0]; + assert.equal(rootScriptInfo.fileName, root.path); + + // ensure that imported file was found + verifyImportedDiagnostics(); + + const callsTrackingHost = createCallsTrackingHost(host); + + // trigger synchronization to make sure that import will be fetched from the cache + // ensure file has correct number of errors after edit + editContent(`import {x} from "f1"; var x: string = 1;`); - verifyImportedDiagnostics(); - callsTrackingHost.verifyNoHostCalls(); + verifyImportedDiagnostics(); + callsTrackingHost.verifyNoHostCalls(); + // trigger synchronization to make sure that the host will try to find 'f2' module on disk + editContent(`import {x} from "f2"`); + try { // trigger synchronization to make sure that the host will try to find 'f2' module on disk - editContent(`import {x} from "f2"`); - try { - // trigger synchronization to make sure that the host will try to find 'f2' module on disk - verifyImportedDiagnostics(); - assert.isTrue(false, `should not find file '${imported.path}'`); - } - catch (e) { - assert.isTrue(e.message.indexOf(`Could not find source file: '${imported.path}'.`) === 0, `Actual: ${e.message}`); - } - const f2Lookups = getLocationsForModuleLookup("f2"); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f2Lookups, 1); - const f2DirLookups = getLocationsForDirectoryLookup(); - callsTrackingHost.verifyCalledOnEachEntry(CalledMapsWithSingleArg.directoryExists, f2DirLookups); + verifyImportedDiagnostics(); + assert.isTrue(false, `should not find file '${imported.path}'`); + } + catch (e) { + assert.isTrue(e.message.indexOf(`Could not find source file: '${imported.path}'.`) === 0, `Actual: ${e.message}`); + } + const f2Lookups = getLocationsForModuleLookup("f2"); + callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f2Lookups, 1); + const f2DirLookups = getLocationsForDirectoryLookup(); + callsTrackingHost.verifyCalledOnEachEntry(CalledMapsWithSingleArg.directoryExists, f2DirLookups); + callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); + callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.readFile); + callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); + + editContent(`import {x} from "f1"`); + verifyImportedDiagnostics(); + const f1Lookups = f2Lookups.map(s => s.replace("f2", "f1")); + f1Lookups.length = f1Lookups.indexOf(imported.path) + 1; + const f1DirLookups = ["/c/d", "/c", ...mapCombinedPathsInAncestor(getDirectoryPath(root.path), nodeModulesAtTypes, returnTrue)]; + vertifyF1Lookups(); + + // setting compiler options discards module resolution cache + callsTrackingHost.clear(); + projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true, target: ScriptTarget.ES5 }); + verifyImportedDiagnostics(); + vertifyF1Lookups(); + + function vertifyF1Lookups() { + callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f1Lookups, 1); + callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.directoryExists, f1DirLookups, 1); callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.readFile); callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); + } - editContent(`import {x} from "f1"`); - verifyImportedDiagnostics(); - const f1Lookups = f2Lookups.map(s => s.replace("f2", "f1")); - f1Lookups.length = f1Lookups.indexOf(imported.path) + 1; - const f1DirLookups = ["/c/d", "/c", ...mapCombinedPathsInAncestor(getDirectoryPath(root.path), nodeModulesAtTypes, returnTrue)]; - vertifyF1Lookups(); - - // setting compiler options discards module resolution cache + function editContent(newContent: string) { callsTrackingHost.clear(); - projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true, target: ScriptTarget.ES5 }); - verifyImportedDiagnostics(); - vertifyF1Lookups(); - - function vertifyF1Lookups() { - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f1Lookups, 1); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.directoryExists, f1DirLookups, 1); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.readFile); - callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); - } - - function editContent(newContent: string) { - callsTrackingHost.clear(); - rootScriptInfo.editContent(0, rootContent.length, newContent); - rootContent = newContent; - } - - function verifyImportedDiagnostics() { - const diags = project.getLanguageService().getSemanticDiagnostics(imported.path); - assert.equal(diags.length, 1); - const diag = diags[0]; - assert.equal(diag.code, Diagnostics.Cannot_find_name_0.code); - assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find name 'foo'."); - } - - function getLocationsForModuleLookup(module: string) { - const locations: string[] = []; - forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { - locations.push( - combinePaths(ancestor, `${module}.ts`), - combinePaths(ancestor, `${module}.tsx`), - combinePaths(ancestor, `${module}.d.ts`) - ); - }); - forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { - locations.push( - combinePaths(ancestor, `${module}.js`), - combinePaths(ancestor, `${module}.jsx`) - ); - }); - return locations; - } - - function getLocationsForDirectoryLookup() { - const result = new Map(); - forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { - // To resolve modules - result.set(ancestor, 2); - // for type roots - result.set(combinePaths(ancestor, nodeModules), 1); - result.set(combinePaths(ancestor, nodeModulesAtTypes), 1); - }); - return result; - } - }); - - it("loads missing files from disk", () => { - const root: File = { - path: "/c/foo.ts", - content: `import {y} from "bar"` - }; - - const imported: File = { - path: "/c/bar.d.ts", - content: `export var y = 1` - }; - - const host = createServerHost([root]); - const projectService = createProjectService(host); - projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true }); - const callsTrackingHost = createCallsTrackingHost(host); - projectService.openClientFile(root.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const project = projectService.inferredProjects[0]; - const rootScriptInfo = project.getRootScriptInfos()[0]; - assert.equal(rootScriptInfo.fileName, root.path); + rootScriptInfo.editContent(0, rootContent.length, newContent); + rootContent = newContent; + } - let diags = project.getLanguageService().getSemanticDiagnostics(root.path); + function verifyImportedDiagnostics() { + const diags = project.getLanguageService().getSemanticDiagnostics(imported.path); assert.equal(diags.length, 1); const diag = diags[0]; - assert.equal(diag.code, Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option.code); - assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find module 'bar'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option?"); - callsTrackingHost.verifyCalledOn(CalledMapsWithSingleArg.fileExists, imported.path); + assert.equal(diag.code, Diagnostics.Cannot_find_name_0.code); + assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find name 'foo'."); + } + function getLocationsForModuleLookup(module: string) { + const locations: string[] = []; + forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { + locations.push( + combinePaths(ancestor, `${module}.ts`), + combinePaths(ancestor, `${module}.tsx`), + combinePaths(ancestor, `${module}.d.ts`) + ); + }); + forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { + locations.push( + combinePaths(ancestor, `${module}.js`), + combinePaths(ancestor, `${module}.jsx`) + ); + }); + return locations; + } - callsTrackingHost.clear(); - host.writeFile(imported.path, imported.content); - host.runQueuedTimeoutCallbacks(); - diags = project.getLanguageService().getSemanticDiagnostics(root.path); - assert.equal(diags.length, 0); - callsTrackingHost.verifyCalledOn(CalledMapsWithSingleArg.fileExists, imported.path); - }); + function getLocationsForDirectoryLookup() { + const result = new Map(); + forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { + // To resolve modules + result.set(ancestor, 2); + // for type roots + result.set(combinePaths(ancestor, nodeModules), 1); + result.set(combinePaths(ancestor, nodeModulesAtTypes), 1); + }); + return result; + } + }); - it("when calling goto definition of module", () => { - const clientFile: File = { - path: "/a/b/controllers/vessels/client.ts", - content: ` + it("loads missing files from disk", () => { + const root: File = { + path: "/c/foo.ts", + content: `import {y} from "bar"` + }; + + const imported: File = { + path: "/c/bar.d.ts", + content: `export var y = 1` + }; + + const host = createServerHost([root]); + const projectService = createProjectService(host); + projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true }); + const callsTrackingHost = createCallsTrackingHost(host); + projectService.openClientFile(root.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + const rootScriptInfo = project.getRootScriptInfos()[0]; + assert.equal(rootScriptInfo.fileName, root.path); + + let diags = project.getLanguageService().getSemanticDiagnostics(root.path); + assert.equal(diags.length, 1); + const diag = diags[0]; + assert.equal(diag.code, Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option.code); + assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find module 'bar'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option?"); + callsTrackingHost.verifyCalledOn(CalledMapsWithSingleArg.fileExists, imported.path); + + + callsTrackingHost.clear(); + host.writeFile(imported.path, imported.content); + host.runQueuedTimeoutCallbacks(); + diags = project.getLanguageService().getSemanticDiagnostics(root.path); + assert.equal(diags.length, 0); + callsTrackingHost.verifyCalledOn(CalledMapsWithSingleArg.fileExists, imported.path); + }); + + it("when calling goto definition of module", () => { + const clientFile: File = { + path: "/a/b/controllers/vessels/client.ts", + content: ` import { Vessel } from '~/models/vessel'; const v = new Vessel(); ` - }; - const anotherModuleFile: File = { - path: "/a/b/utils/db.ts", - content: "export class Bookshelf { }" - }; - const moduleFile: File = { - path: "/a/b/models/vessel.ts", - content: ` + }; + const anotherModuleFile: File = { + path: "/a/b/utils/db.ts", + content: "export class Bookshelf { }" + }; + const moduleFile: File = { + path: "/a/b/models/vessel.ts", + content: ` import { Bookshelf } from '~/utils/db'; export class Vessel extends Bookshelf {} ` + }; + const tsconfigFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + target: "es6", + module: "es6", + baseUrl: "./", // all paths are relative to the baseUrl + paths: { + "~/*": ["*"] // resolve any `~/foo/bar` to `/foo/bar` + } + }, + exclude: [ + "api", + "build", + "node_modules", + "public", + "seeds", + "sql_updates", + "tests.build" + ] + }) + }; + const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile]; + const host = createServerHost(projectFiles); + const session = createSession(host); + const projectService = session.getProjectService(); + const { configFileName } = projectService.openClientFile(clientFile.path); + + assert.isDefined(configFileName, `should find config`); + checkNumberOfConfiguredProjects(projectService, 1); + + const project = projectService.configuredProjects.get(tsconfigFile.path)!; + checkProjectActualFiles(project, map(projectFiles, f => f.path)); + + const callsTrackingHost = createCallsTrackingHost(host); + + // Get definitions shouldnt make host requests + const getDefinitionRequest = makeSessionRequest(protocol.CommandTypes.Definition, { + file: clientFile.path, + position: clientFile.content.indexOf("/vessel") + 1, + line: undefined!, // TODO: GH#18217 + offset: undefined! // TODO: GH#18217 + }); + const response = session.executeCommand(getDefinitionRequest).response as server.protocol.FileSpan[]; + assert.equal(response[0].file, moduleFile.path, "Should go to definition of vessel: response: " + JSON.stringify(response)); + callsTrackingHost.verifyNoHostCalls(); + + // Open the file should call only file exists on module directory and use cached value for parental directory + const { configFileName: config2 } = projectService.openClientFile(moduleFile.path); + assert.equal(config2, configFileName); + callsTrackingHost.verifyNoHostCallsExceptFileExistsOnce(["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]); + + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(tsconfigFile.path), project); + }); + + describe("WatchDirectories for config file with", () => { + function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { + const frontendDir = "/Users/someuser/work/applications/frontend"; + const toCanonical: (s: string) => Path = useCaseSensitiveFileNames ? s => s as Path : s => s.toLowerCase() as Path; + const canonicalFrontendDir = toCanonical(frontendDir); + const file1: File = { + path: `${frontendDir}/src/app/utils/Analytic.ts`, + content: "export class SomeClass { };" + }; + const file2: File = { + path: `${frontendDir}/src/app/redux/configureStore.ts`, + content: "export class configureStore { }" + }; + const file3: File = { + path: `${frontendDir}/src/app/utils/Cookie.ts`, + content: "export class Cookie { }" }; + const es2016LibFile: File = { + path: "/a/lib/lib.es2016.full.d.ts", + content: libFile.content + }; + const typeRoots = ["types", "node_modules/@types"]; + const types = ["node", "jest"]; const tsconfigFile: File = { - path: "/a/b/tsconfig.json", + path: `${frontendDir}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { - target: "es6", - module: "es6", - baseUrl: "./", // all paths are relative to the baseUrl + strict: true, + strictNullChecks: true, + target: "es2016", + module: "commonjs", + moduleResolution: "node", + sourceMap: true, + noEmitOnError: true, + experimentalDecorators: true, + emitDecoratorMetadata: true, + types, + noUnusedLocals: true, + outDir: "./compiled", + typeRoots, + baseUrl: ".", paths: { - "~/*": ["*"] // resolve any `~/foo/bar` to `/foo/bar` + "*": [ + "types/*" + ] } }, + include: [ + "src/**/*" + ], exclude: [ - "api", - "build", "node_modules", - "public", - "seeds", - "sql_updates", - "tests.build" + "compiled" ] }) }; - const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile]; - const host = createServerHost(projectFiles); - const session = createSession(host); - const projectService = session.getProjectService(); - const { configFileName } = projectService.openClientFile(clientFile.path); - - assert.isDefined(configFileName, `should find config`); + const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; + const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); + const projectService = createProjectService(host); + const canonicalConfigPath = toCanonical(tsconfigFile.path); + const { configFileName } = projectService.openClientFile(file1.path); + assert.equal(configFileName, tsconfigFile.path as server.NormalizedPath, `should find config`); checkNumberOfConfiguredProjects(projectService, 1); + const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, `${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(canonicalFrontendDir))); - const project = projectService.configuredProjects.get(tsconfigFile.path)!; - checkProjectActualFiles(project, map(projectFiles, f => f.path)); + const project = projectService.configuredProjects.get(canonicalConfigPath)!; + verifyProjectAndWatchedDirectories(); const callsTrackingHost = createCallsTrackingHost(host); - // Get definitions shouldnt make host requests - const getDefinitionRequest = makeSessionRequest(protocol.CommandTypes.Definition, { - file: clientFile.path, - position: clientFile.content.indexOf("/vessel") + 1, - line: undefined!, // TODO: GH#18217 - offset: undefined! // TODO: GH#18217 - }); - const response = session.executeCommand(getDefinitionRequest).response as server.protocol.FileSpan[]; - assert.equal(response[0].file, moduleFile.path, "Should go to definition of vessel: response: " + JSON.stringify(response)); - callsTrackingHost.verifyNoHostCalls(); + // Create file cookie.ts + projectFiles.push(file3); + host.writeFile(file3.path, file3.content); + host.runQueuedTimeoutCallbacks(); - // Open the file should call only file exists on module directory and use cached value for parental directory - const { configFileName: config2 } = projectService.openClientFile(moduleFile.path); - assert.equal(config2, configFileName); - callsTrackingHost.verifyNoHostCallsExceptFileExistsOnce(["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]); + const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase(); + const numberOfTimesWatchInvoked = getNumberOfWatchesInvokedForRecursiveWatches(watchingRecursiveDirectories, canonicalFile3Path); + callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, [canonicalFile3Path], numberOfTimesWatchInvoked); + callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.directoryExists, [canonicalFile3Path], numberOfTimesWatchInvoked); + callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); + callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.readFile, [file3.path], 1); + callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(tsconfigFile.path), project); - }); + assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); + verifyProjectAndWatchedDirectories(); - describe("WatchDirectories for config file with", () => { - function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { - const frontendDir = "/Users/someuser/work/applications/frontend"; - const toCanonical: (s: string) => Path = useCaseSensitiveFileNames ? s => s as Path : s => s.toLowerCase() as Path; - const canonicalFrontendDir = toCanonical(frontendDir); - const file1: File = { - path: `${frontendDir}/src/app/utils/Analytic.ts`, - content: "export class SomeClass { };" - }; - const file2: File = { - path: `${frontendDir}/src/app/redux/configureStore.ts`, - content: "export class configureStore { }" - }; - const file3: File = { - path: `${frontendDir}/src/app/utils/Cookie.ts`, - content: "export class Cookie { }" - }; - const es2016LibFile: File = { - path: "/a/lib/lib.es2016.full.d.ts", - content: libFile.content - }; - const typeRoots = ["types", "node_modules/@types"]; - const types = ["node", "jest"]; - const tsconfigFile: File = { - path: `${frontendDir}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - strict: true, - strictNullChecks: true, - target: "es2016", - module: "commonjs", - moduleResolution: "node", - sourceMap: true, - noEmitOnError: true, - experimentalDecorators: true, - emitDecoratorMetadata: true, - types, - noUnusedLocals: true, - outDir: "./compiled", - typeRoots, - baseUrl: ".", - paths: { - "*": [ - "types/*" - ] - } - }, - include: [ - "src/**/*" - ], - exclude: [ - "node_modules", - "compiled" - ] - }) - }; - const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; - const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); - const projectService = createProjectService(host); - const canonicalConfigPath = toCanonical(tsconfigFile.path); - const { configFileName } = projectService.openClientFile(file1.path); - assert.equal(configFileName, tsconfigFile.path as server.NormalizedPath, `should find config`); - checkNumberOfConfiguredProjects(projectService, 1); - const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, `${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(canonicalFrontendDir))); + callsTrackingHost.clear(); - const project = projectService.configuredProjects.get(canonicalConfigPath)!; - verifyProjectAndWatchedDirectories(); + const { configFileName: configFile2 } = projectService.openClientFile(file3.path); + assert.equal(configFile2, configFileName); - const callsTrackingHost = createCallsTrackingHost(host); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); + verifyProjectAndWatchedDirectories(); + callsTrackingHost.verifyNoHostCalls(); - // Create file cookie.ts - projectFiles.push(file3); - host.writeFile(file3.path, file3.content); - host.runQueuedTimeoutCallbacks(); + function getFilePathIfNotOpen(f: File) { + const path = toCanonical(f.path); + const info = projectService.getScriptInfoForPath(toCanonical(f.path)); + return info && info.isScriptOpen() ? undefined : path; + } - const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase(); - const numberOfTimesWatchInvoked = getNumberOfWatchesInvokedForRecursiveWatches(watchingRecursiveDirectories, canonicalFile3Path); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, [canonicalFile3Path], numberOfTimesWatchInvoked); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.directoryExists, [canonicalFile3Path], numberOfTimesWatchInvoked); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.readFile, [file3.path], 1); - callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); + function verifyProjectAndWatchedDirectories() { + checkProjectActualFiles(project, map(projectFiles, f => f.path)); + checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfNotOpen)); + checkWatchedDirectories(host, watchingRecursiveDirectories, /*recursive*/ true); + checkWatchedDirectories(host, [], /*recursive*/ false); + } + } - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); - verifyProjectAndWatchedDirectories(); + it("case insensitive file system", () => { + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); + }); - callsTrackingHost.clear(); + it("case sensitive file system", () => { + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true); + }); + }); - const { configFileName: configFile2 } = projectService.openClientFile(file3.path); - assert.equal(configFile2, configFileName); + describe("Subfolder invalidations correctly include parent folder failed lookup locations", () => { + function runFailedLookupTest(resolution: "Node" | "Classic") { + const projectLocation = "/proj"; + const file1: File = { + path: `${projectLocation}/foo/boo/app.ts`, + content: `import * as debug from "debug"` + }; + const file2: File = { + path: `${projectLocation}/foo/boo/moo/app.ts`, + content: `import * as debug from "debug"` + }; + const tsconfig: File = { + path: `${projectLocation}/tsconfig.json`, + content: JSON.stringify({ + files: ["foo/boo/app.ts", "foo/boo/moo/app.ts"], + moduleResolution: resolution + }) + }; - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); - verifyProjectAndWatchedDirectories(); - callsTrackingHost.verifyNoHostCalls(); - - function getFilePathIfNotOpen(f: File) { - const path = toCanonical(f.path); - const info = projectService.getScriptInfoForPath(toCanonical(f.path)); - return info && info.isScriptOpen() ? undefined : path; - } + const files = [file1, file2, tsconfig, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(file1.path); - function verifyProjectAndWatchedDirectories() { - checkProjectActualFiles(project, map(projectFiles, f => f.path)); - checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfNotOpen)); - checkWatchedDirectories(host, watchingRecursiveDirectories, /*recursive*/ true); - checkWatchedDirectories(host, [], /*recursive*/ false); - } - } + const project = service.configuredProjects.get(tsconfig.path)!; + checkProjectActualFiles(project, files.map(f => f.path)); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]); - it("case insensitive file system", () => { - verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); - }); + const debugTypesFile: File = { + path: `${projectLocation}/node_modules/debug/index.d.ts`, + content: "export {}" + }; + files.push(debugTypesFile); + host.writeFile(debugTypesFile.path, debugTypesFile.content); + host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions + host.runQueuedTimeoutCallbacks(); // Actual update + checkProjectActualFiles(project, files.map(f => f.path)); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), []); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), []); + } - it("case sensitive file system", () => { - verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true); - }); + it("Includes the parent folder FLLs in node module resolution mode", () => { + runFailedLookupTest("Node"); }); + it("Includes the parent folder FLLs in classic module resolution mode", () => { + runFailedLookupTest("Classic"); + }); + }); - describe("Subfolder invalidations correctly include parent folder failed lookup locations", () => { - function runFailedLookupTest(resolution: "Node" | "Classic") { - const projectLocation = "/proj"; - const file1: File = { - path: `${projectLocation}/foo/boo/app.ts`, - content: `import * as debug from "debug"` - }; - const file2: File = { - path: `${projectLocation}/foo/boo/moo/app.ts`, - content: `import * as debug from "debug"` - }; - const tsconfig: File = { - path: `${projectLocation}/tsconfig.json`, - content: JSON.stringify({ - files: ["foo/boo/app.ts", "foo/boo/moo/app.ts"], - moduleResolution: resolution - }) - }; - - const files = [file1, file2, tsconfig, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(file1.path); - - const project = service.configuredProjects.get(tsconfig.path)!; - checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]); - - const debugTypesFile: File = { - path: `${projectLocation}/node_modules/debug/index.d.ts`, - content: "export {}" - }; - files.push(debugTypesFile); - host.writeFile(debugTypesFile.path, debugTypesFile.content); - host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions - host.runQueuedTimeoutCallbacks(); // Actual update - checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), []); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), []); - } - - it("Includes the parent folder FLLs in node module resolution mode", () => { - runFailedLookupTest("Node"); + describe("Verify npm install in directory with tsconfig file works when", () => { + function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) { + const root = "/user/username/rootfolder/otherfolder"; + const getRootedFileOrFolder = (fileOrFolder: File) => { + fileOrFolder.path = root + fileOrFolder.path; + return fileOrFolder; + }; + const app: File = getRootedFileOrFolder({ + path: "/a/b/app.ts", + content: "import _ from 'lodash';" }); - it("Includes the parent folder FLLs in classic module resolution mode", () => { - runFailedLookupTest("Classic"); + const tsconfigJson: File = getRootedFileOrFolder({ + path: "/a/b/tsconfig.json", + content: '{ "compilerOptions": { } }' }); - }); - - describe("Verify npm install in directory with tsconfig file works when", () => { - function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) { - const root = "/user/username/rootfolder/otherfolder"; - const getRootedFileOrFolder = (fileOrFolder: File) => { - fileOrFolder.path = root + fileOrFolder.path; - return fileOrFolder; - }; - const app: File = getRootedFileOrFolder({ - path: "/a/b/app.ts", - content: "import _ from 'lodash';" - }); - const tsconfigJson: File = getRootedFileOrFolder({ - path: "/a/b/tsconfig.json", - content: '{ "compilerOptions": { } }' - }); - const packageJson: File = getRootedFileOrFolder({ - path: "/a/b/package.json", - content: ` + const packageJson: File = getRootedFileOrFolder({ + path: "/a/b/package.json", + content: ` { "name": "test", "version": "1.0.0", @@ -537,216 +537,216 @@ namespace ts.projectSystem { "license": "ISC" } ` - }); - const appFolder = getDirectoryPath(app.path); - const projectFiles = [app, libFile, tsconfigJson]; - const typeRootDirectories = getTypeRootsFromLocation(getDirectoryPath(tsconfigJson.path)); - const otherFiles = [packageJson]; - const host = createServerHost(projectFiles.concat(otherFiles)); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ preferences: { includePackageJsonAutoImports: "off" } }); - const { configFileName } = projectService.openClientFile(app.path); - assert.equal(configFileName, tsconfigJson.path as server.NormalizedPath, `should find config`); // TODO: GH#18217 - const recursiveWatchedDirectories: string[] = [`${appFolder}`, `${appFolder}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(appFolder))); - verifyProject(); + }); + const appFolder = getDirectoryPath(app.path); + const projectFiles = [app, libFile, tsconfigJson]; + const typeRootDirectories = getTypeRootsFromLocation(getDirectoryPath(tsconfigJson.path)); + const otherFiles = [packageJson]; + const host = createServerHost(projectFiles.concat(otherFiles)); + const projectService = createProjectService(host); + projectService.setHostConfiguration({ preferences: { includePackageJsonAutoImports: "off" } }); + const { configFileName } = projectService.openClientFile(app.path); + assert.equal(configFileName, tsconfigJson.path as server.NormalizedPath, `should find config`); // TODO: GH#18217 + const recursiveWatchedDirectories: string[] = [`${appFolder}`, `${appFolder}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(appFolder))); + verifyProject(); + + let npmInstallComplete = false; + + // Simulate npm install + const filesAndFoldersToAdd: File[] = [ + { path: "/a/b/node_modules" }, + { path: "/a/b/node_modules/.staging/@types" }, + { path: "/a/b/node_modules/.staging/lodash-b0733faa" }, + { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61" }, + { path: "/a/b/node_modules/.staging/typescript-8493ea5d" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/package.json", content: "{\n \"name\": \"symbol-observable\",\n \"version\": \"1.0.4\",\n \"description\": \"Symbol.observable ponyfill\",\n \"license\": \"MIT\",\n \"repository\": \"blesh/symbol-observable\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"engines\": {\n \"node\": \">=0.10.0\"\n },\n \"scripts\": {\n \"test\": \"npm run build && mocha && tsc ./ts-test/test.ts && node ./ts-test/test.js && check-es3-syntax -p lib/ --kill\",\n \"build\": \"babel es --out-dir lib\",\n \"prepublish\": \"npm test\"\n },\n \"files\": [\n \"" }, + { path: "/a/b/node_modules/.staging/lodash-b0733faa/package.json", content: "{\n \"name\": \"lodash\",\n \"version\": \"4.17.4\",\n \"description\": \"Lodash modular utilities.\",\n \"keywords\": \"modules, stdlib, util\",\n \"homepage\": \"https://lodash.com/\",\n \"repository\": \"lodash/lodash\",\n \"icon\": \"https://lodash.com/icon.svg\",\n \"license\": \"MIT\",\n \"main\": \"lodash.js\",\n \"author\": \"John-David Dalton (http://allyoucanleet.com/)\",\n \"contributors\": [\n \"John-David Dalton (http://allyoucanleet.com/)\",\n \"Mathias Bynens \",\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"license\": \"Apache-2.0\",\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"eslint\": \"^4.4.2\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"typings\": \"Rx.d.ts\",\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n }\n}" }, + { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json", content: "{\n \"name\": \"typescript\",\n \"author\": \"Microsoft Corp.\",\n \"homepage\": \"http://typescriptlang.org/\",\n \"version\": \"2.4.2\",\n \"license\": \"Apache-2.0\",\n \"description\": \"TypeScript is a language for application scale JavaScript development\",\n \"keywords\": [\n \"TypeScript\",\n \"Microsoft\",\n \"compiler\",\n \"language\",\n \"javascript\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/Microsoft/TypeScript/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Microsoft/TypeScript.git\"\n },\n \"main\": \"./lib/typescript.js\",\n \"typings\": \"./lib/typescript.d.ts\",\n \"bin\": {\n \"tsc\": \"./bin/tsc\",\n \"tsserver\": \"./bin/tsserver\"\n },\n \"engines\": {\n \"node\": \">=4.2.0\"\n },\n \"devDependencies\": {\n \"@types/browserify\": \"latest\",\n \"@types/chai\": \"latest\",\n \"@types/convert-source-map\": \"latest\",\n \"@types/del\": \"latest\",\n \"@types/glob\": \"latest\",\n \"@types/gulp\": \"latest\",\n \"@types/gulp-concat\": \"latest\",\n \"@types/gulp-help\": \"latest\",\n \"@types/gulp-newer\": \"latest\",\n \"@types/gulp-sourcemaps\": \"latest\",\n \"@types/merge2\": \"latest\",\n \"@types/minimatch\": \"latest\",\n \"@types/minimist\": \"latest\",\n \"@types/mkdirp\": \"latest\",\n \"@types/mocha\": \"latest\",\n \"@types/node\": \"latest\",\n \"@types/q\": \"latest\",\n \"@types/run-sequence\": \"latest\",\n \"@types/through2\": \"latest\",\n \"browserify\": \"latest\",\n \"chai\": \"latest\",\n \"convert-source-map\": \"latest\",\n \"del\": \"latest\",\n \"gulp\": \"latest\",\n \"gulp-clone\": \"latest\",\n \"gulp-concat\": \"latest\",\n \"gulp-help\": \"latest\",\n \"gulp-insert\": \"latest\",\n \"gulp-newer\": \"latest\",\n \"gulp-sourcemaps\": \"latest\",\n \"gulp-typescript\": \"latest\",\n \"into-stream\": \"latest\",\n \"istanbul\": \"latest\",\n \"jake\": \"latest\",\n \"merge2\": \"latest\",\n \"minimist\": \"latest\",\n \"mkdirp\": \"latest\",\n \"mocha\": \"latest\",\n \"mocha-fivemat-progress-reporter\": \"latest\",\n \"q\": \"latest\",\n \"run-sequence\": \"latest\",\n \"sorcery\": \"latest\",\n \"through2\": \"latest\",\n \"travis-fold\": \"latest\",\n \"ts-node\": \"latest\",\n \"eslint\": \"5.16.0\",\n \"typescript\": \"^2.4\"\n },\n \"scripts\": {\n \"pretest\": \"jake tests\",\n \"test\": \"jake runtests-parallel\",\n \"build\": \"npm run build:compiler && npm run build:tests\",\n \"build:compiler\": \"jake local\",\n \"build:tests\": \"jake tests\",\n \"start\": \"node lib/tsc\",\n \"clean\": \"jake clean\",\n \"gulp\": \"gulp\",\n \"jake\": \"jake\",\n \"lint\": \"jake lint\",\n \"setup-hooks\": \"node scripts/link-hooks.js\"\n },\n \"browser\": {\n \"buffer\": false,\n \"fs\": false,\n \"os\": false,\n \"path\": false\n }\n}" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.js", content: "module.exports = require('./lib/index');\n" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.d.ts", content: "declare const observableSymbol: symbol;\nexport default observableSymbol;\n" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib/index.js", content: "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _ponyfill = require('./ponyfill');\n\nvar _ponyfill2 = _interopRequireDefault(_ponyfill);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar root; /* global window */\n\n\nif (typeof self !== 'undefined') {\n root = self;\n} else if (typeof window !== 'undefined') {\n root = window;\n} else if (typeof global !== 'undefined') {\n root = global;\n} else if (typeof module !== 'undefined') {\n root = module;\n} else {\n root = Function('return this')();\n}\n\nvar result = (0, _ponyfill2['default'])(root);\nexports['default'] = result;" }, + ].map(getRootedFileOrFolder); + verifyAfterPartialOrCompleteNpmInstall(2); + + filesAndFoldersToAdd.push(...[ + { path: "/a/b/node_modules/.staging/typescript-8493ea5d/lib" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/add/operator" }, + { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/package.json", content: "{\n \"name\": \"@types/lodash\",\n \"version\": \"4.14.74\",\n \"description\": \"TypeScript definitions for Lo-Dash\",\n \"license\": \"MIT\",\n \"contributors\": [\n {\n \"name\": \"Brian Zengel\",\n \"url\": \"https://github.com/bczengel\"\n },\n {\n \"name\": \"Ilya Mochalov\",\n \"url\": \"https://github.com/chrootsu\"\n },\n {\n \"name\": \"Stepan Mikhaylyuk\",\n \"url\": \"https://github.com/stepancar\"\n },\n {\n \"name\": \"Eric L Anderson\",\n \"url\": \"https://github.com/ericanderson\"\n },\n {\n \"name\": \"AJ Richardson\",\n \"url\": \"https://github.com/aj-r\"\n },\n {\n \"name\": \"Junyoung Clare Jang\",\n \"url\": \"https://github.com/ailrun\"\n }\n ],\n \"main\": \"\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://www.github.com/DefinitelyTyped/DefinitelyTyped.git\"\n },\n \"scripts\": {},\n \"dependencies\": {},\n \"typesPublisherContentHash\": \"12af578ffaf8d86d2df37e591857906a86b983fa9258414326544a0fe6af0de8\",\n \"typeScriptVersion\": \"2.2\"\n}" }, + { path: "/a/b/node_modules/.staging/lodash-b0733faa/index.js", content: "module.exports = require('./lodash');" }, + { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594", content: "" } + ].map(getRootedFileOrFolder)); + // Since we added/removed in .staging no timeout + verifyAfterPartialOrCompleteNpmInstall(0); + + // Remove file "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594" + host.deleteFile(last(filesAndFoldersToAdd).path); + filesAndFoldersToAdd.length--; + verifyAfterPartialOrCompleteNpmInstall(0); + + filesAndFoldersToAdd.push(...[ + { path: "/a/b/node_modules/.staging/rxjs-22375c61/bundles" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/operator" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/add/observable/dom" }, + { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/index.d.ts", content: "\n// Stub for lodash\nexport = _;\nexport as namespace _;\ndeclare var _: _.LoDashStatic;\ndeclare namespace _ {\n interface LoDashStatic {\n someProp: string;\n }\n class SomeClass {\n someMethod(): void;\n }\n}" } + ].map(getRootedFileOrFolder)); + verifyAfterPartialOrCompleteNpmInstall(0); + + filesAndFoldersToAdd.push(...[ + { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/scheduler" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/util" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/symbol" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/testing" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041", content: "{\n \"_args\": [\n [\n {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\"\n ]\n ],\n \"_from\": \"rxjs@>=5.4.2 <6.0.0\",\n \"_id\": \"rxjs@5.4.3\",\n \"_inCache\": true,\n \"_location\": \"/rxjs\",\n \"_nodeVersion\": \"7.7.2\",\n \"_npmOperationalInternal\": {\n \"host\": \"s3://npm-registry-packages\",\n \"tmp\": \"tmp/rxjs-5.4.3.tgz_1502407898166_0.6800217325799167\"\n },\n \"_npmUser\": {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"_npmVersion\": \"5.3.0\",\n \"_phantomChildren\": {},\n \"_requested\": {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"_requiredBy\": [\n \"/\"\n ],\n \"_resolved\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\",\n \"_shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"_shrinkwrap\": null,\n \"_spec\": \"rxjs@^5.4.2\",\n \"_where\": \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"config\": {\n \"commitizen\": {\n \"path\": \"cz-conventional-changelog\"\n }\n },\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n },\n \"description\": \"Reactive Extensions for modern JavaScript\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"eslint\": \"^5.16.0\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"directories\": {},\n \"dist\": {\n \"integrity\": \"sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==\",\n \"shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"tarball\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"keywords\": [\n \"Rx\",\n \"RxJS\",\n \"ReactiveX\",\n \"ReactiveExtensions\",\n \"Streams\",\n \"Observables\",\n \"Observable\",\n \"Stream\",\n \"ES6\",\n \"ES2015\"\n ],\n \"license\": \"Apache-2.0\",\n \"lint-staged\": {\n \"*.@(js)\": [\n \"eslint --fix\",\n \"git add\"\n ],\n \"*.@(ts)\": [\n \"eslint -c .eslintrc --ext .ts . --fix\",\n \"git add\"\n ]\n },\n \"main\": \"Rx.js\",\n \"maintainers\": [\n {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n }\n ],\n \"name\": \"rxjs\",\n \"optionalDependencies\": {},\n \"readme\": \"ERROR: No README data found!\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+ssh://git@github.com/ReactiveX/RxJS.git\"\n },\n \"scripts-info\": {\n \"info\": \"List available script\",\n \"build_all\": \"Build all packages (ES6, CJS, UMD) and generate packages\",\n \"build_cjs\": \"Build CJS package with clean up existing build, copy source into dist\",\n \"build_es6\": \"Build ES6 package with clean up existing build, copy source into dist\",\n \"build_closure_core\": \"Minify Global core build using closure compiler\",\n \"build_global\": \"Build Global package, then minify build\",\n \"build_perf\": \"Build CJS & Global build, run macro performance test\",\n \"build_test\": \"Build CJS package & test spec, execute mocha test runner\",\n \"build_cover\": \"Run lint to current code, build CJS & test spec, execute test coverage\",\n \"build_docs\": \"Build ES6 & global package, create documentation using it\",\n \"build_spec\": \"Build test specs\",\n \"check_circular_dependencies\": \"Check codebase has circular dependencies\",\n \"clean_spec\": \"Clean up existing test spec build output\",\n \"clean_dist_cjs\": \"Clean up existing CJS package output\",\n \"clean_dist_es6\": \"Clean up existing ES6 package output\",\n \"clean_dist_global\": \"Clean up existing Global package output\",\n \"commit\": \"Run git commit wizard\",\n \"compile_dist_cjs\": \"Compile codebase into CJS module\",\n \"compile_module_es6\": \"Compile codebase into ES6\",\n \"cover\": \"Execute test coverage\",\n \"lint_perf\": \"Run lint against performance test suite\",\n \"lint_spec\": \"Run lint against test spec\",\n \"lint_src\": \"Run lint against source\",\n \"lint\": \"Run lint against everything\",\n \"perf\": \"Run macro performance benchmark\",\n \"perf_micro\": \"Run micro performance benchmark\",\n \"test_mocha\": \"Execute mocha test runner against existing test spec build\",\n \"test_browser\": \"Execute mocha test runner on browser against existing test spec build\",\n \"test\": \"Clean up existing test spec build, build test spec and execute mocha test runner\",\n \"tests2png\": \"Generate marble diagram image from test spec\",\n \"watch\": \"Watch codebase, trigger compile when source code changes\"\n },\n \"typings\": \"Rx.d.ts\",\n \"version\": \"5.4.3\"\n}\n" } + ].map(getRootedFileOrFolder)); + verifyAfterPartialOrCompleteNpmInstall(0); + + // remove /a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041 + host.deleteFile(last(filesAndFoldersToAdd).path); + filesAndFoldersToAdd.length--; + // and add few more folders/files + filesAndFoldersToAdd.push(...[ + { path: "/a/b/node_modules/symbol-observable" }, + { path: "/a/b/node_modules/@types" }, + { path: "/a/b/node_modules/@types/lodash" }, + { path: "/a/b/node_modules/lodash" }, + { path: "/a/b/node_modules/rxjs" }, + { path: "/a/b/node_modules/typescript" }, + { path: "/a/b/node_modules/.bin" } + ].map(getRootedFileOrFolder)); + // From the type root update + verifyAfterPartialOrCompleteNpmInstall(2); + + forEach(filesAndFoldersToAdd, f => { + f.path = f.path + .replace("/a/b/node_modules/.staging", "/a/b/node_modules") + .replace(/[\-\.][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w]/g, ""); + }); - let npmInstallComplete = false; - - // Simulate npm install - const filesAndFoldersToAdd: File[] = [ - { path: "/a/b/node_modules" }, - { path: "/a/b/node_modules/.staging/@types" }, - { path: "/a/b/node_modules/.staging/lodash-b0733faa" }, - { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61" }, - { path: "/a/b/node_modules/.staging/typescript-8493ea5d" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/package.json", content: "{\n \"name\": \"symbol-observable\",\n \"version\": \"1.0.4\",\n \"description\": \"Symbol.observable ponyfill\",\n \"license\": \"MIT\",\n \"repository\": \"blesh/symbol-observable\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"engines\": {\n \"node\": \">=0.10.0\"\n },\n \"scripts\": {\n \"test\": \"npm run build && mocha && tsc ./ts-test/test.ts && node ./ts-test/test.js && check-es3-syntax -p lib/ --kill\",\n \"build\": \"babel es --out-dir lib\",\n \"prepublish\": \"npm test\"\n },\n \"files\": [\n \"" }, - { path: "/a/b/node_modules/.staging/lodash-b0733faa/package.json", content: "{\n \"name\": \"lodash\",\n \"version\": \"4.17.4\",\n \"description\": \"Lodash modular utilities.\",\n \"keywords\": \"modules, stdlib, util\",\n \"homepage\": \"https://lodash.com/\",\n \"repository\": \"lodash/lodash\",\n \"icon\": \"https://lodash.com/icon.svg\",\n \"license\": \"MIT\",\n \"main\": \"lodash.js\",\n \"author\": \"John-David Dalton (http://allyoucanleet.com/)\",\n \"contributors\": [\n \"John-David Dalton (http://allyoucanleet.com/)\",\n \"Mathias Bynens \",\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"license\": \"Apache-2.0\",\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"eslint\": \"^4.4.2\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"typings\": \"Rx.d.ts\",\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n }\n}" }, - { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json", content: "{\n \"name\": \"typescript\",\n \"author\": \"Microsoft Corp.\",\n \"homepage\": \"http://typescriptlang.org/\",\n \"version\": \"2.4.2\",\n \"license\": \"Apache-2.0\",\n \"description\": \"TypeScript is a language for application scale JavaScript development\",\n \"keywords\": [\n \"TypeScript\",\n \"Microsoft\",\n \"compiler\",\n \"language\",\n \"javascript\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/Microsoft/TypeScript/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Microsoft/TypeScript.git\"\n },\n \"main\": \"./lib/typescript.js\",\n \"typings\": \"./lib/typescript.d.ts\",\n \"bin\": {\n \"tsc\": \"./bin/tsc\",\n \"tsserver\": \"./bin/tsserver\"\n },\n \"engines\": {\n \"node\": \">=4.2.0\"\n },\n \"devDependencies\": {\n \"@types/browserify\": \"latest\",\n \"@types/chai\": \"latest\",\n \"@types/convert-source-map\": \"latest\",\n \"@types/del\": \"latest\",\n \"@types/glob\": \"latest\",\n \"@types/gulp\": \"latest\",\n \"@types/gulp-concat\": \"latest\",\n \"@types/gulp-help\": \"latest\",\n \"@types/gulp-newer\": \"latest\",\n \"@types/gulp-sourcemaps\": \"latest\",\n \"@types/merge2\": \"latest\",\n \"@types/minimatch\": \"latest\",\n \"@types/minimist\": \"latest\",\n \"@types/mkdirp\": \"latest\",\n \"@types/mocha\": \"latest\",\n \"@types/node\": \"latest\",\n \"@types/q\": \"latest\",\n \"@types/run-sequence\": \"latest\",\n \"@types/through2\": \"latest\",\n \"browserify\": \"latest\",\n \"chai\": \"latest\",\n \"convert-source-map\": \"latest\",\n \"del\": \"latest\",\n \"gulp\": \"latest\",\n \"gulp-clone\": \"latest\",\n \"gulp-concat\": \"latest\",\n \"gulp-help\": \"latest\",\n \"gulp-insert\": \"latest\",\n \"gulp-newer\": \"latest\",\n \"gulp-sourcemaps\": \"latest\",\n \"gulp-typescript\": \"latest\",\n \"into-stream\": \"latest\",\n \"istanbul\": \"latest\",\n \"jake\": \"latest\",\n \"merge2\": \"latest\",\n \"minimist\": \"latest\",\n \"mkdirp\": \"latest\",\n \"mocha\": \"latest\",\n \"mocha-fivemat-progress-reporter\": \"latest\",\n \"q\": \"latest\",\n \"run-sequence\": \"latest\",\n \"sorcery\": \"latest\",\n \"through2\": \"latest\",\n \"travis-fold\": \"latest\",\n \"ts-node\": \"latest\",\n \"eslint\": \"5.16.0\",\n \"typescript\": \"^2.4\"\n },\n \"scripts\": {\n \"pretest\": \"jake tests\",\n \"test\": \"jake runtests-parallel\",\n \"build\": \"npm run build:compiler && npm run build:tests\",\n \"build:compiler\": \"jake local\",\n \"build:tests\": \"jake tests\",\n \"start\": \"node lib/tsc\",\n \"clean\": \"jake clean\",\n \"gulp\": \"gulp\",\n \"jake\": \"jake\",\n \"lint\": \"jake lint\",\n \"setup-hooks\": \"node scripts/link-hooks.js\"\n },\n \"browser\": {\n \"buffer\": false,\n \"fs\": false,\n \"os\": false,\n \"path\": false\n }\n}" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.js", content: "module.exports = require('./lib/index');\n" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.d.ts", content: "declare const observableSymbol: symbol;\nexport default observableSymbol;\n" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib/index.js", content: "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _ponyfill = require('./ponyfill');\n\nvar _ponyfill2 = _interopRequireDefault(_ponyfill);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar root; /* global window */\n\n\nif (typeof self !== 'undefined') {\n root = self;\n} else if (typeof window !== 'undefined') {\n root = window;\n} else if (typeof global !== 'undefined') {\n root = global;\n} else if (typeof module !== 'undefined') {\n root = module;\n} else {\n root = Function('return this')();\n}\n\nvar result = (0, _ponyfill2['default'])(root);\nexports['default'] = result;" }, - ].map(getRootedFileOrFolder); - verifyAfterPartialOrCompleteNpmInstall(2); - - filesAndFoldersToAdd.push(...[ - { path: "/a/b/node_modules/.staging/typescript-8493ea5d/lib" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/add/operator" }, - { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/package.json", content: "{\n \"name\": \"@types/lodash\",\n \"version\": \"4.14.74\",\n \"description\": \"TypeScript definitions for Lo-Dash\",\n \"license\": \"MIT\",\n \"contributors\": [\n {\n \"name\": \"Brian Zengel\",\n \"url\": \"https://github.com/bczengel\"\n },\n {\n \"name\": \"Ilya Mochalov\",\n \"url\": \"https://github.com/chrootsu\"\n },\n {\n \"name\": \"Stepan Mikhaylyuk\",\n \"url\": \"https://github.com/stepancar\"\n },\n {\n \"name\": \"Eric L Anderson\",\n \"url\": \"https://github.com/ericanderson\"\n },\n {\n \"name\": \"AJ Richardson\",\n \"url\": \"https://github.com/aj-r\"\n },\n {\n \"name\": \"Junyoung Clare Jang\",\n \"url\": \"https://github.com/ailrun\"\n }\n ],\n \"main\": \"\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://www.github.com/DefinitelyTyped/DefinitelyTyped.git\"\n },\n \"scripts\": {},\n \"dependencies\": {},\n \"typesPublisherContentHash\": \"12af578ffaf8d86d2df37e591857906a86b983fa9258414326544a0fe6af0de8\",\n \"typeScriptVersion\": \"2.2\"\n}" }, - { path: "/a/b/node_modules/.staging/lodash-b0733faa/index.js", content: "module.exports = require('./lodash');" }, - { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594", content: "" } - ].map(getRootedFileOrFolder)); - // Since we added/removed in .staging no timeout - verifyAfterPartialOrCompleteNpmInstall(0); - - // Remove file "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594" - host.deleteFile(last(filesAndFoldersToAdd).path); - filesAndFoldersToAdd.length--; - verifyAfterPartialOrCompleteNpmInstall(0); - - filesAndFoldersToAdd.push(...[ - { path: "/a/b/node_modules/.staging/rxjs-22375c61/bundles" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/operator" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/add/observable/dom" }, - { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/index.d.ts", content: "\n// Stub for lodash\nexport = _;\nexport as namespace _;\ndeclare var _: _.LoDashStatic;\ndeclare namespace _ {\n interface LoDashStatic {\n someProp: string;\n }\n class SomeClass {\n someMethod(): void;\n }\n}" } - ].map(getRootedFileOrFolder)); - verifyAfterPartialOrCompleteNpmInstall(0); - - filesAndFoldersToAdd.push(...[ - { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/scheduler" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/util" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/symbol" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/testing" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041", content: "{\n \"_args\": [\n [\n {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\"\n ]\n ],\n \"_from\": \"rxjs@>=5.4.2 <6.0.0\",\n \"_id\": \"rxjs@5.4.3\",\n \"_inCache\": true,\n \"_location\": \"/rxjs\",\n \"_nodeVersion\": \"7.7.2\",\n \"_npmOperationalInternal\": {\n \"host\": \"s3://npm-registry-packages\",\n \"tmp\": \"tmp/rxjs-5.4.3.tgz_1502407898166_0.6800217325799167\"\n },\n \"_npmUser\": {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"_npmVersion\": \"5.3.0\",\n \"_phantomChildren\": {},\n \"_requested\": {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"_requiredBy\": [\n \"/\"\n ],\n \"_resolved\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\",\n \"_shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"_shrinkwrap\": null,\n \"_spec\": \"rxjs@^5.4.2\",\n \"_where\": \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"config\": {\n \"commitizen\": {\n \"path\": \"cz-conventional-changelog\"\n }\n },\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n },\n \"description\": \"Reactive Extensions for modern JavaScript\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"eslint\": \"^5.16.0\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"directories\": {},\n \"dist\": {\n \"integrity\": \"sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==\",\n \"shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"tarball\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"keywords\": [\n \"Rx\",\n \"RxJS\",\n \"ReactiveX\",\n \"ReactiveExtensions\",\n \"Streams\",\n \"Observables\",\n \"Observable\",\n \"Stream\",\n \"ES6\",\n \"ES2015\"\n ],\n \"license\": \"Apache-2.0\",\n \"lint-staged\": {\n \"*.@(js)\": [\n \"eslint --fix\",\n \"git add\"\n ],\n \"*.@(ts)\": [\n \"eslint -c .eslintrc --ext .ts . --fix\",\n \"git add\"\n ]\n },\n \"main\": \"Rx.js\",\n \"maintainers\": [\n {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n }\n ],\n \"name\": \"rxjs\",\n \"optionalDependencies\": {},\n \"readme\": \"ERROR: No README data found!\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+ssh://git@github.com/ReactiveX/RxJS.git\"\n },\n \"scripts-info\": {\n \"info\": \"List available script\",\n \"build_all\": \"Build all packages (ES6, CJS, UMD) and generate packages\",\n \"build_cjs\": \"Build CJS package with clean up existing build, copy source into dist\",\n \"build_es6\": \"Build ES6 package with clean up existing build, copy source into dist\",\n \"build_closure_core\": \"Minify Global core build using closure compiler\",\n \"build_global\": \"Build Global package, then minify build\",\n \"build_perf\": \"Build CJS & Global build, run macro performance test\",\n \"build_test\": \"Build CJS package & test spec, execute mocha test runner\",\n \"build_cover\": \"Run lint to current code, build CJS & test spec, execute test coverage\",\n \"build_docs\": \"Build ES6 & global package, create documentation using it\",\n \"build_spec\": \"Build test specs\",\n \"check_circular_dependencies\": \"Check codebase has circular dependencies\",\n \"clean_spec\": \"Clean up existing test spec build output\",\n \"clean_dist_cjs\": \"Clean up existing CJS package output\",\n \"clean_dist_es6\": \"Clean up existing ES6 package output\",\n \"clean_dist_global\": \"Clean up existing Global package output\",\n \"commit\": \"Run git commit wizard\",\n \"compile_dist_cjs\": \"Compile codebase into CJS module\",\n \"compile_module_es6\": \"Compile codebase into ES6\",\n \"cover\": \"Execute test coverage\",\n \"lint_perf\": \"Run lint against performance test suite\",\n \"lint_spec\": \"Run lint against test spec\",\n \"lint_src\": \"Run lint against source\",\n \"lint\": \"Run lint against everything\",\n \"perf\": \"Run macro performance benchmark\",\n \"perf_micro\": \"Run micro performance benchmark\",\n \"test_mocha\": \"Execute mocha test runner against existing test spec build\",\n \"test_browser\": \"Execute mocha test runner on browser against existing test spec build\",\n \"test\": \"Clean up existing test spec build, build test spec and execute mocha test runner\",\n \"tests2png\": \"Generate marble diagram image from test spec\",\n \"watch\": \"Watch codebase, trigger compile when source code changes\"\n },\n \"typings\": \"Rx.d.ts\",\n \"version\": \"5.4.3\"\n}\n" } - ].map(getRootedFileOrFolder)); - verifyAfterPartialOrCompleteNpmInstall(0); - - // remove /a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041 - host.deleteFile(last(filesAndFoldersToAdd).path); - filesAndFoldersToAdd.length--; - // and add few more folders/files - filesAndFoldersToAdd.push(...[ - { path: "/a/b/node_modules/symbol-observable" }, - { path: "/a/b/node_modules/@types" }, - { path: "/a/b/node_modules/@types/lodash" }, - { path: "/a/b/node_modules/lodash" }, - { path: "/a/b/node_modules/rxjs" }, - { path: "/a/b/node_modules/typescript" }, - { path: "/a/b/node_modules/.bin" } - ].map(getRootedFileOrFolder)); - // From the type root update - verifyAfterPartialOrCompleteNpmInstall(2); - - forEach(filesAndFoldersToAdd, f => { - f.path = f.path - .replace("/a/b/node_modules/.staging", "/a/b/node_modules") - .replace(/[\-\.][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w]/g, ""); - }); - - host.deleteFolder(root + "/a/b/node_modules/.staging", /*recursive*/ true); - const lodashIndexPath = root + "/a/b/node_modules/@types/lodash/index.d.ts"; - projectFiles.push(find(filesAndFoldersToAdd, f => f.path === lodashIndexPath)!); - // we would now not have failed lookup in the parent of appFolder since lodash is available - recursiveWatchedDirectories.length = 2; - // npm installation complete, timeout after reload fs - npmInstallComplete = true; - verifyAfterPartialOrCompleteNpmInstall(2); - - function verifyAfterPartialOrCompleteNpmInstall(timeoutQueueLengthWhenRunningTimeouts: number) { - filesAndFoldersToAdd.forEach(f => host.ensureFileOrFolder(f)); - if (npmInstallComplete || timeoutDuringPartialInstallation) { - if (timeoutQueueLengthWhenRunningTimeouts) { - // Expected project update - host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts + 1); // Scheduled invalidation of resolutions - host.runQueuedTimeoutCallbacks(); // Actual update - } - else { - host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts); - } + host.deleteFolder(root + "/a/b/node_modules/.staging", /*recursive*/ true); + const lodashIndexPath = root + "/a/b/node_modules/@types/lodash/index.d.ts"; + projectFiles.push(find(filesAndFoldersToAdd, f => f.path === lodashIndexPath)!); + // we would now not have failed lookup in the parent of appFolder since lodash is available + recursiveWatchedDirectories.length = 2; + // npm installation complete, timeout after reload fs + npmInstallComplete = true; + verifyAfterPartialOrCompleteNpmInstall(2); + + function verifyAfterPartialOrCompleteNpmInstall(timeoutQueueLengthWhenRunningTimeouts: number) { + filesAndFoldersToAdd.forEach(f => host.ensureFileOrFolder(f)); + if (npmInstallComplete || timeoutDuringPartialInstallation) { + if (timeoutQueueLengthWhenRunningTimeouts) { + // Expected project update + host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts + 1); // Scheduled invalidation of resolutions + host.runQueuedTimeoutCallbacks(); // Actual update } else { - host.checkTimeoutQueueLength(3); + host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts); } - verifyProject(); } - - function verifyProject() { - checkNumberOfConfiguredProjects(projectService, 1); - - const project = projectService.configuredProjects.get(tsconfigJson.path)!; - const projectFilePaths = map(projectFiles, f => f.path); - checkProjectActualFiles(project, projectFilePaths); - - const filesWatched = filter(projectFilePaths, p => p !== app.path && p.indexOf("/a/b/node_modules") === -1); - checkWatchedFiles(host, filesWatched); - checkWatchedDirectories(host, typeRootDirectories.concat(recursiveWatchedDirectories), /*recursive*/ true); - checkWatchedDirectories(host, [], /*recursive*/ false); + else { + host.checkTimeoutQueueLength(3); } + verifyProject(); } - it("timeouts occur inbetween installation", () => { - verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true); - }); - - it("timeout occurs after installation", () => { - verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false); - }); - }); - - it("when node_modules dont receive event for the @types file addition", () => { - const projectLocation = "/user/username/folder/myproject"; - const app: File = { - path: `${projectLocation}/app.ts`, - content: `import * as debug from "debug"` - }; - const tsconfig: File = { - path: `${projectLocation}/tsconfig.json`, - content: "" - }; + function verifyProject() { + checkNumberOfConfiguredProjects(projectService, 1); - const files = [app, tsconfig, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(app.path); + const project = projectService.configuredProjects.get(tsconfigJson.path)!; + const projectFilePaths = map(projectFiles, f => f.path); + checkProjectActualFiles(project, projectFilePaths); - const project = service.configuredProjects.get(tsconfig.path)!; - checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]); + const filesWatched = filter(projectFilePaths, p => p !== app.path && p.indexOf("/a/b/node_modules") === -1); + checkWatchedFiles(host, filesWatched); + checkWatchedDirectories(host, typeRootDirectories.concat(recursiveWatchedDirectories), /*recursive*/ true); + checkWatchedDirectories(host, [], /*recursive*/ false); + } + } - const debugTypesFile: File = { - path: `${projectLocation}/node_modules/@types/debug/index.d.ts`, - content: "export {}" - }; - files.push(debugTypesFile); - // Do not invoke recursive directory watcher for anything other than node_module/@types - const invoker = host.invokeFsWatchesRecursiveCallbacks; - host.invokeFsWatchesRecursiveCallbacks = (fullPath, eventName, entryFullPath) => { - if (fullPath.endsWith("@types")) { - invoker.call(host, fullPath, eventName, entryFullPath); - } - }; - host.writeFile(debugTypesFile.path, debugTypesFile.content); - host.runQueuedTimeoutCallbacks(); - checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), []); + it("timeouts occur inbetween installation", () => { + verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true); }); - it("when creating new file in symlinked folder", () => { - const module1: File = { - path: `${tscWatch.projectRoot}/client/folder1/module1.ts`, - content: `export class Module1Class { }` - }; - const module2: File = { - path: `${tscWatch.projectRoot}/folder2/module2.ts`, - content: `import * as M from "folder1/module1";` - }; - const symlink: SymLink = { - path: `${tscWatch.projectRoot}/client/linktofolder2`, - symLink: `${tscWatch.projectRoot}/folder2`, - }; - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - baseUrl: "client", - paths: { "*": ["*"] }, - }, - include: ["client/**/*", "folder2"] - }) - }; - const host = createServerHost([module1, module2, symlink, config, libFile]); - const service = createProjectService(host); - service.openClientFile(`${symlink.path}/module2.ts`); - checkNumberOfProjects(service, { configuredProjects: 1 }); - const project = Debug.checkDefined(service.configuredProjects.get(config.path)); - checkProjectActualFiles(project, [module1.path, `${symlink.path}/module2.ts`, config.path, libFile.path]); - host.writeFile(`${symlink.path}/module3.ts`, `import * as M from "folder1/module1";`); - host.runQueuedTimeoutCallbacks(); - checkNumberOfProjects(service, { configuredProjects: 1 }); - checkProjectActualFiles(project, [module1.path, `${symlink.path}/module2.ts`, config.path, libFile.path, `${symlink.path}/module3.ts`]); + it("timeout occurs after installation", () => { + verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false); }); }); + + it("when node_modules dont receive event for the @types file addition", () => { + const projectLocation = "/user/username/folder/myproject"; + const app: File = { + path: `${projectLocation}/app.ts`, + content: `import * as debug from "debug"` + }; + const tsconfig: File = { + path: `${projectLocation}/tsconfig.json`, + content: "" + }; + + const files = [app, tsconfig, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(app.path); + + const project = service.configuredProjects.get(tsconfig.path)!; + checkProjectActualFiles(project, files.map(f => f.path)); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]); + + const debugTypesFile: File = { + path: `${projectLocation}/node_modules/@types/debug/index.d.ts`, + content: "export {}" + }; + files.push(debugTypesFile); + // Do not invoke recursive directory watcher for anything other than node_module/@types + const invoker = host.invokeFsWatchesRecursiveCallbacks; + host.invokeFsWatchesRecursiveCallbacks = (fullPath, eventName, entryFullPath) => { + if (fullPath.endsWith("@types")) { + invoker.call(host, fullPath, eventName, entryFullPath); + } + }; + host.writeFile(debugTypesFile.path, debugTypesFile.content); + host.runQueuedTimeoutCallbacks(); + checkProjectActualFiles(project, files.map(f => f.path)); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), []); + }); + + it("when creating new file in symlinked folder", () => { + const module1: File = { + path: `${tscWatch.projectRoot}/client/folder1/module1.ts`, + content: `export class Module1Class { }` + }; + const module2: File = { + path: `${tscWatch.projectRoot}/folder2/module2.ts`, + content: `import * as M from "folder1/module1";` + }; + const symlink: SymLink = { + path: `${tscWatch.projectRoot}/client/linktofolder2`, + symLink: `${tscWatch.projectRoot}/folder2`, + }; + const config: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + baseUrl: "client", + paths: { "*": ["*"] }, + }, + include: ["client/**/*", "folder2"] + }) + }; + const host = createServerHost([module1, module2, symlink, config, libFile]); + const service = createProjectService(host); + service.openClientFile(`${symlink.path}/module2.ts`); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const project = Debug.checkDefined(service.configuredProjects.get(config.path)); + checkProjectActualFiles(project, [module1.path, `${symlink.path}/module2.ts`, config.path, libFile.path]); + host.writeFile(`${symlink.path}/module3.ts`, `import * as M from "folder1/module1";`); + host.runQueuedTimeoutCallbacks(); + checkNumberOfProjects(service, { configuredProjects: 1 }); + checkProjectActualFiles(project, [module1.path, `${symlink.path}/module2.ts`, config.path, libFile.path, `${symlink.path}/module3.ts`]); + }); +}); } diff --git a/src/testRunner/unittests/tsserver/cancellationToken.ts b/src/testRunner/unittests/tsserver/cancellationToken.ts index f727263357ec0..02a45d6cc23fb 100644 --- a/src/testRunner/unittests/tsserver/cancellationToken.ts +++ b/src/testRunner/unittests/tsserver/cancellationToken.ts @@ -1,274 +1,274 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: cancellationToken", () => { - // Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test - let oldPrepare: AnyFunction; - before(() => { - oldPrepare = (Error as any).prepareStackTrace; - delete (Error as any).prepareStackTrace; - }); +describe("unittests:: tsserver:: cancellationToken", () => { + // Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test + let oldPrepare: AnyFunction; + before(() => { + oldPrepare = (Error as any).prepareStackTrace; + delete (Error as any).prepareStackTrace; + }); - after(() => { - (Error as any).prepareStackTrace = oldPrepare; - }); + after(() => { + (Error as any).prepareStackTrace = oldPrepare; + }); + + it("is attached to request", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let xyz = 1;" + }; + const host = createServerHost([f1]); + let expectedRequestId: number; + const cancellationToken: server.ServerCancellationToken = { + isCancellationRequested: () => false, + setRequest: requestId => { + if (expectedRequestId === undefined) { + assert.isTrue(false, "unexpected call"); + } + assert.equal(requestId, expectedRequestId); + }, + resetRequest: noop + }; + + const session = createSession(host, { cancellationToken }); + + expectedRequestId = session.getNextSeq(); + session.executeCommandSeq({ + command: "open", + arguments: { file: f1.path } + } as server.protocol.OpenRequest); - it("is attached to request", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let xyz = 1;" - }; - const host = createServerHost([f1]); - let expectedRequestId: number; - const cancellationToken: server.ServerCancellationToken = { - isCancellationRequested: () => false, - setRequest: requestId => { - if (expectedRequestId === undefined) { - assert.isTrue(false, "unexpected call"); - } - assert.equal(requestId, expectedRequestId); - }, - resetRequest: noop - }; + expectedRequestId = session.getNextSeq(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + } as server.protocol.GeterrRequest); - const session = createSession(host, { cancellationToken }); + expectedRequestId = session.getNextSeq(); + session.executeCommandSeq({ + command: "occurrences", + arguments: { file: f1.path, line: 1, offset: 6 } + } as server.protocol.OccurrencesRequest); - expectedRequestId = session.getNextSeq(); + expectedRequestId = 2; + host.runQueuedImmediateCallbacks(); + expectedRequestId = 2; + host.runQueuedImmediateCallbacks(); + }); + + it("Geterr is cancellable", () => { + const f1 = { + path: "/a/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {} + }) + }; + + const cancellationToken = new TestServerCancellationToken(); + const host = createServerHost([f1, config]); + const session = createSession(host, { + canUseEvents: true, + eventHandler: noop, + cancellationToken + }); + { session.executeCommandSeq({ command: "open", arguments: { file: f1.path } - } as server.protocol.OpenRequest); - - expectedRequestId = session.getNextSeq(); + } as protocol.OpenRequest); + // send geterr for missing file session.executeCommandSeq({ command: "geterr", - arguments: { files: [f1.path] } - } as server.protocol.GeterrRequest); - - expectedRequestId = session.getNextSeq(); + arguments: { files: ["/a/missing"] } + } as protocol.GeterrRequest); + // Queued files + assert.equal(host.getOutput().length, 0, "expected 0 message"); + host.checkTimeoutQueueLengthAndRun(1); + // Completed event since file is missing + assert.equal(host.getOutput().length, 1, "expect 1 message"); + verifyRequestCompleted(session.getSeq(), 0); + } + { + const getErrId = session.getNextSeq(); + // send geterr for a valid file session.executeCommandSeq({ - command: "occurrences", - arguments: { file: f1.path, line: 1, offset: 6 } - } as server.protocol.OccurrencesRequest); - - expectedRequestId = 2; - host.runQueuedImmediateCallbacks(); - expectedRequestId = 2; - host.runQueuedImmediateCallbacks(); - }); - - it("Geterr is cancellable", () => { - const f1 = { - path: "/a/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {} - }) - }; + command: "geterr", + arguments: { files: [f1.path] } + } as protocol.GeterrRequest); - const cancellationToken = new TestServerCancellationToken(); - const host = createServerHost([f1, config]); - const session = createSession(host, { - canUseEvents: true, - eventHandler: noop, - cancellationToken - }); - { - session.executeCommandSeq({ - command: "open", - arguments: { file: f1.path } - } as protocol.OpenRequest); - // send geterr for missing file - session.executeCommandSeq({ - command: "geterr", - arguments: { files: ["/a/missing"] } - } as protocol.GeterrRequest); - // Queued files - assert.equal(host.getOutput().length, 0, "expected 0 message"); - host.checkTimeoutQueueLengthAndRun(1); - // Completed event since file is missing - assert.equal(host.getOutput().length, 1, "expect 1 message"); - verifyRequestCompleted(session.getSeq(), 0); - } - { - const getErrId = session.getNextSeq(); - // send geterr for a valid file - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - } as protocol.GeterrRequest); + assert.equal(host.getOutput().length, 0, "expect 0 messages"); - assert.equal(host.getOutput().length, 0, "expect 0 messages"); + // run new request + session.executeCommandSeq({ + command: "projectInfo", + arguments: { file: f1.path } + } as protocol.ProjectInfoRequest); + session.clearMessages(); - // run new request - session.executeCommandSeq({ - command: "projectInfo", - arguments: { file: f1.path } - } as protocol.ProjectInfoRequest); - session.clearMessages(); + // cancel previously issued Geterr + cancellationToken.setRequestToCancel(getErrId); + host.runQueuedTimeoutCallbacks(); - // cancel previously issued Geterr - cancellationToken.setRequestToCancel(getErrId); - host.runQueuedTimeoutCallbacks(); + assert.equal(host.getOutput().length, 1, "expect 1 message"); + verifyRequestCompleted(getErrId, 0); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - verifyRequestCompleted(getErrId, 0); + cancellationToken.resetToken(); + } + { + const getErrId = session.getNextSeq(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + } as protocol.GeterrRequest); + assert.equal(host.getOutput().length, 0, "expect 0 messages"); - cancellationToken.resetToken(); - } - { - const getErrId = session.getNextSeq(); - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - } as protocol.GeterrRequest); - assert.equal(host.getOutput().length, 0, "expect 0 messages"); + // run first step + host.runQueuedTimeoutCallbacks(); + assert.equal(host.getOutput().length, 1, "expect 1 message"); + const e1 = getMessage(0) as protocol.Event; + assert.equal(e1.event, "syntaxDiag"); + session.clearMessages(); - // run first step - host.runQueuedTimeoutCallbacks(); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - const e1 = getMessage(0) as protocol.Event; - assert.equal(e1.event, "syntaxDiag"); - session.clearMessages(); + cancellationToken.setRequestToCancel(getErrId); + host.runQueuedImmediateCallbacks(); + assert.equal(host.getOutput().length, 1, "expect 1 message"); + verifyRequestCompleted(getErrId, 0); - cancellationToken.setRequestToCancel(getErrId); - host.runQueuedImmediateCallbacks(); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - verifyRequestCompleted(getErrId, 0); + cancellationToken.resetToken(); + } + { + const getErrId = session.getNextSeq(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + } as protocol.GeterrRequest); + assert.equal(host.getOutput().length, 0, "expect 0 messages"); - cancellationToken.resetToken(); - } - { - const getErrId = session.getNextSeq(); - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - } as protocol.GeterrRequest); - assert.equal(host.getOutput().length, 0, "expect 0 messages"); + // run first step + host.runQueuedTimeoutCallbacks(); + assert.equal(host.getOutput().length, 1, "expect 1 message"); + const e1 = getMessage(0) as protocol.Event; + assert.equal(e1.event, "syntaxDiag"); + session.clearMessages(); - // run first step - host.runQueuedTimeoutCallbacks(); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - const e1 = getMessage(0) as protocol.Event; - assert.equal(e1.event, "syntaxDiag"); - session.clearMessages(); + // the semanticDiag message + host.runQueuedImmediateCallbacks(); + assert.equal(host.getOutput().length, 1); + const e2 = getMessage(0) as protocol.Event; + assert.equal(e2.event, "semanticDiag"); + session.clearMessages(); - // the semanticDiag message - host.runQueuedImmediateCallbacks(); - assert.equal(host.getOutput().length, 1); - const e2 = getMessage(0) as protocol.Event; - assert.equal(e2.event, "semanticDiag"); - session.clearMessages(); + host.runQueuedImmediateCallbacks(1); + assert.equal(host.getOutput().length, 2); + const e3 = getMessage(0) as protocol.Event; + assert.equal(e3.event, "suggestionDiag"); + verifyRequestCompleted(getErrId, 1); - host.runQueuedImmediateCallbacks(1); - assert.equal(host.getOutput().length, 2); - const e3 = getMessage(0) as protocol.Event; - assert.equal(e3.event, "suggestionDiag"); - verifyRequestCompleted(getErrId, 1); + cancellationToken.resetToken(); + } + { + const getErr1 = session.getNextSeq(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + } as protocol.GeterrRequest); + assert.equal(host.getOutput().length, 0, "expect 0 messages"); + // run first step + host.runQueuedTimeoutCallbacks(); + assert.equal(host.getOutput().length, 1, "expect 1 message"); + const e1 = getMessage(0) as protocol.Event; + assert.equal(e1.event, "syntaxDiag"); + session.clearMessages(); - cancellationToken.resetToken(); - } - { - const getErr1 = session.getNextSeq(); - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - } as protocol.GeterrRequest); - assert.equal(host.getOutput().length, 0, "expect 0 messages"); - // run first step - host.runQueuedTimeoutCallbacks(); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - const e1 = getMessage(0) as protocol.Event; - assert.equal(e1.event, "syntaxDiag"); - session.clearMessages(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + } as protocol.GeterrRequest); + // make sure that getErr1 is completed + verifyRequestCompleted(getErr1, 0); + } - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - } as protocol.GeterrRequest); - // make sure that getErr1 is completed - verifyRequestCompleted(getErr1, 0); - } + function verifyRequestCompleted(expectedSeq: number, n: number) { + const event = getMessage(n) as protocol.RequestCompletedEvent; + assert.equal(event.event, "requestCompleted"); + assert.equal(event.body.request_seq, expectedSeq, "expectedSeq"); + session.clearMessages(); + } - function verifyRequestCompleted(expectedSeq: number, n: number) { - const event = getMessage(n) as protocol.RequestCompletedEvent; - assert.equal(event.event, "requestCompleted"); - assert.equal(event.body.request_seq, expectedSeq, "expectedSeq"); - session.clearMessages(); - } + function getMessage(n: number) { + return JSON.parse(server.extractMessage(host.getOutput()[n])); + } + }); - function getMessage(n: number) { - return JSON.parse(server.extractMessage(host.getOutput()[n])); - } + it("Lower priority tasks are cancellable", () => { + const f1 = { + path: "/a/app.ts", + content: `{ let x = 1; } var foo = "foo"; var bar = "bar"; var fooBar = "fooBar";` + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {} + }) + }; + const cancellationToken = new TestServerCancellationToken(/*cancelAfterRequest*/ 3); + const host = createServerHost([f1, config]); + const session = createSession(host, { + canUseEvents: true, + eventHandler: noop, + cancellationToken, + throttleWaitMilliseconds: 0 }); + { + session.executeCommandSeq({ + command: "open", + arguments: { file: f1.path } + } as protocol.OpenRequest); - it("Lower priority tasks are cancellable", () => { - const f1 = { - path: "/a/app.ts", - content: `{ let x = 1; } var foo = "foo"; var bar = "bar"; var fooBar = "fooBar";` - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {} - }) - }; - const cancellationToken = new TestServerCancellationToken(/*cancelAfterRequest*/ 3); - const host = createServerHost([f1, config]); - const session = createSession(host, { - canUseEvents: true, - eventHandler: noop, - cancellationToken, - throttleWaitMilliseconds: 0 - }); - { - session.executeCommandSeq({ - command: "open", - arguments: { file: f1.path } - } as protocol.OpenRequest); - - // send navbar request (normal priority) - session.executeCommandSeq({ - command: "navbar", - arguments: { file: f1.path } - } as protocol.NavBarRequest); + // send navbar request (normal priority) + session.executeCommandSeq({ + command: "navbar", + arguments: { file: f1.path } + } as protocol.NavBarRequest); - // ensure the nav bar request can be canceled - verifyExecuteCommandSeqIsCancellable({ - command: "navbar", - arguments: { file: f1.path } - } as protocol.NavBarRequest); + // ensure the nav bar request can be canceled + verifyExecuteCommandSeqIsCancellable({ + command: "navbar", + arguments: { file: f1.path } + } as protocol.NavBarRequest); - // send outlining spans request (normal priority) - session.executeCommandSeq({ - command: "outliningSpans", - arguments: { file: f1.path } - } as protocol.OutliningSpansRequestFull); + // send outlining spans request (normal priority) + session.executeCommandSeq({ + command: "outliningSpans", + arguments: { file: f1.path } + } as protocol.OutliningSpansRequestFull); - // ensure the outlining spans request can be canceled - verifyExecuteCommandSeqIsCancellable({ - command: "outliningSpans", - arguments: { file: f1.path } - } as protocol.OutliningSpansRequestFull); - } + // ensure the outlining spans request can be canceled + verifyExecuteCommandSeqIsCancellable({ + command: "outliningSpans", + arguments: { file: f1.path } + } as protocol.OutliningSpansRequestFull); + } - function verifyExecuteCommandSeqIsCancellable(request: Partial) { - // Set the next request to be cancellable - // The cancellation token will cancel the request the third time - // isCancellationRequested() is called. - cancellationToken.setRequestToCancel(session.getNextSeq()); - let operationCanceledExceptionThrown = false; + function verifyExecuteCommandSeqIsCancellable(request: Partial) { + // Set the next request to be cancellable + // The cancellation token will cancel the request the third time + // isCancellationRequested() is called. + cancellationToken.setRequestToCancel(session.getNextSeq()); + let operationCanceledExceptionThrown = false; - try { - session.executeCommandSeq(request); - } - catch (e) { - assert(e instanceof OperationCanceledException); - operationCanceledExceptionThrown = true; - } - assert(operationCanceledExceptionThrown, "Operation Canceled Exception not thrown for request: " + JSON.stringify(request)); + try { + session.executeCommandSeq(request); } - }); + catch (e) { + assert(e instanceof OperationCanceledException); + operationCanceledExceptionThrown = true; + } + assert(operationCanceledExceptionThrown, "Operation Canceled Exception not thrown for request: " + JSON.stringify(request)); + } }); +}); } diff --git a/src/testRunner/unittests/tsserver/compileOnSave.ts b/src/testRunner/unittests/tsserver/compileOnSave.ts index d8ea2df4008d9..1a77272b1679a 100644 --- a/src/testRunner/unittests/tsserver/compileOnSave.ts +++ b/src/testRunner/unittests/tsserver/compileOnSave.ts @@ -1,1087 +1,1087 @@ namespace ts.projectSystem { - import CommandNames = server.CommandNames; - function createTestTypingsInstaller(host: server.ServerHost) { - return new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host); - } +import CommandNames = server.CommandNames; +function createTestTypingsInstaller(host: server.ServerHost) { + return new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host); +} - describe("unittests:: tsserver:: compileOnSave:: affected list", () => { - function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: File[] }[]) { - const response = session.executeCommand(request).response as server.protocol.CompileOnSaveAffectedFileListSingleProject[]; - const actualResult = response.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName)); - expectedFileList = expectedFileList.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName)); +describe("unittests:: tsserver:: compileOnSave:: affected list", () => { + function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: File[] }[]) { + const response = session.executeCommand(request).response as server.protocol.CompileOnSaveAffectedFileListSingleProject[]; + const actualResult = response.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName)); + expectedFileList = expectedFileList.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName)); - assert.equal(actualResult.length, expectedFileList.length, `Actual result project number is different from the expected project number`); + assert.equal(actualResult.length, expectedFileList.length, `Actual result project number is different from the expected project number`); - for (let i = 0; i < actualResult.length; i++) { - const actualResultSingleProject = actualResult[i]; - const expectedResultSingleProject = expectedFileList[i]; - assert.equal(actualResultSingleProject.projectFileName, expectedResultSingleProject.projectFileName, `Actual result contains different projects than the expected result`); + for (let i = 0; i < actualResult.length; i++) { + const actualResultSingleProject = actualResult[i]; + const expectedResultSingleProject = expectedFileList[i]; + assert.equal(actualResultSingleProject.projectFileName, expectedResultSingleProject.projectFileName, `Actual result contains different projects than the expected result`); - const actualResultSingleProjectFileNameList = actualResultSingleProject.fileNames.sort(); - const expectedResultSingleProjectFileNameList = map(expectedResultSingleProject.files, f => f.path).sort(); - assert.isTrue( - arrayIsEqualTo(actualResultSingleProjectFileNameList, expectedResultSingleProjectFileNameList), - `For project ${actualResultSingleProject.projectFileName}, the actual result is ${actualResultSingleProjectFileNameList}, while expected ${expectedResultSingleProjectFileNameList}`); - } + const actualResultSingleProjectFileNameList = actualResultSingleProject.fileNames.sort(); + const expectedResultSingleProjectFileNameList = map(expectedResultSingleProject.files, f => f.path).sort(); + assert.isTrue( + arrayIsEqualTo(actualResultSingleProjectFileNameList, expectedResultSingleProjectFileNameList), + `For project ${actualResultSingleProject.projectFileName}, the actual result is ${actualResultSingleProjectFileNameList}, while expected ${expectedResultSingleProjectFileNameList}`); } + } + + describe("for configured projects", () => { + let moduleFile1: File; + let file1Consumer1: File; + let file1Consumer2: File; + let moduleFile2: File; + let globalFile3: File; + let configFile: File; + let changeModuleFile1ShapeRequest1: server.protocol.Request; + let changeModuleFile1InternalRequest1: server.protocol.Request; + // A compile on save affected file request using file1 + let moduleFile1FileListRequest: server.protocol.Request; + + beforeEach(() => { + moduleFile1 = { + path: "/a/b/moduleFile1.ts", + content: "export function Foo() { };" + }; + + file1Consumer1 = { + path: "/a/b/file1Consumer1.ts", + content: `import {Foo} from "./moduleFile1"; export var y = 10;` + }; + + file1Consumer2 = { + path: "/a/b/file1Consumer2.ts", + content: `import {Foo} from "./moduleFile1"; let z = 10;` + }; - describe("for configured projects", () => { - let moduleFile1: File; - let file1Consumer1: File; - let file1Consumer2: File; - let moduleFile2: File; - let globalFile3: File; - let configFile: File; - let changeModuleFile1ShapeRequest1: server.protocol.Request; - let changeModuleFile1InternalRequest1: server.protocol.Request; - // A compile on save affected file request using file1 - let moduleFile1FileListRequest: server.protocol.Request; - - beforeEach(() => { - moduleFile1 = { - path: "/a/b/moduleFile1.ts", - content: "export function Foo() { };" - }; - - file1Consumer1 = { - path: "/a/b/file1Consumer1.ts", - content: `import {Foo} from "./moduleFile1"; export var y = 10;` - }; - - file1Consumer2 = { - path: "/a/b/file1Consumer2.ts", - content: `import {Foo} from "./moduleFile1"; let z = 10;` - }; - - moduleFile2 = { - path: "/a/b/moduleFile2.ts", - content: `export var Foo4 = 10;` - }; - - globalFile3 = { - path: "/a/b/globalFile3.ts", - content: `interface GlobalFoo { age: number }` - }; - - configFile = { - path: "/a/b/tsconfig.json", - content: `{ + moduleFile2 = { + path: "/a/b/moduleFile2.ts", + content: `export var Foo4 = 10;` + }; + + globalFile3 = { + path: "/a/b/globalFile3.ts", + content: `interface GlobalFoo { age: number }` + }; + + configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compileOnSave": true }` - }; - - // Change the content of file1 to `export var T: number;export function Foo() { };` - changeModuleFile1ShapeRequest1 = makeSessionRequest(CommandNames.Change, { - file: moduleFile1.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 1, - insertString: `export var T: number;` - }); + }; - // Change the content of file1 to `export var T: number;export function Foo() { };` - changeModuleFile1InternalRequest1 = makeSessionRequest(CommandNames.Change, { - file: moduleFile1.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 1, - insertString: `var T1: number;` - }); + // Change the content of file1 to `export var T: number;export function Foo() { };` + changeModuleFile1ShapeRequest1 = makeSessionRequest(CommandNames.Change, { + file: moduleFile1.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: `export var T: number;` + }); - moduleFile1FileListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path, projectFileName: configFile.path }); + // Change the content of file1 to `export var T: number;export function Foo() { };` + changeModuleFile1InternalRequest1 = makeSessionRequest(CommandNames.Change, { + file: moduleFile1.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: `var T1: number;` }); - it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - - openFilesForSession([moduleFile1, file1Consumer1], session); - - // Send an initial compileOnSave request - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - - // Change the content of file1 to `export var T: number;export function Foo() { console.log('hi'); };` - const changeFile1InternalRequest = makeSessionRequest(CommandNames.Change, { - file: moduleFile1.path, - line: 1, - offset: 46, - endLine: 1, - endOffset: 46, - insertString: `console.log('hi');` - }); - session.executeCommand(changeFile1InternalRequest); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + moduleFile1FileListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path, projectFileName: configFile.path }); + }); + + it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); + + openFilesForSession([moduleFile1, file1Consumer1], session); + + // Send an initial compileOnSave request + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + session.executeCommand(changeModuleFile1ShapeRequest1); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + + // Change the content of file1 to `export var T: number;export function Foo() { console.log('hi'); };` + const changeFile1InternalRequest = makeSessionRequest(CommandNames.Change, { + file: moduleFile1.path, + line: 1, + offset: 46, + endLine: 1, + endOffset: 46, + insertString: `console.log('hi');` }); + session.executeCommand(changeFile1InternalRequest); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + }); - it("should be up-to-date with the reference map changes", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + it("should be up-to-date with the reference map changes", () => { + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1, file1Consumer1], session); + openFilesForSession([moduleFile1, file1Consumer1], session); - // Send an initial compileOnSave request - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + // Send an initial compileOnSave request + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - // Change file2 content to `let y = Foo();` - const removeFile1Consumer1ImportRequest = makeSessionRequest(CommandNames.Change, { - file: file1Consumer1.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 28, - insertString: "" - }); - session.executeCommand(removeFile1Consumer1ImportRequest); - session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]); - - // Add the import statements back to file2 - const addFile2ImportRequest = makeSessionRequest(CommandNames.Change, { - file: file1Consumer1.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 1, - insertString: `import {Foo} from "./moduleFile1";` - }); - session.executeCommand(addFile2ImportRequest); - - // Change the content of file1 to `export var T2: string;export var T: number;export function Foo() { };` - const changeModuleFile1ShapeRequest2 = makeSessionRequest(CommandNames.Change, { - file: moduleFile1.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 1, - insertString: `export var T2: string;` - }); - session.executeCommand(changeModuleFile1ShapeRequest2); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + // Change file2 content to `let y = Foo();` + const removeFile1Consumer1ImportRequest = makeSessionRequest(CommandNames.Change, { + file: file1Consumer1.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 28, + insertString: "" + }); + session.executeCommand(removeFile1Consumer1ImportRequest); + session.executeCommand(changeModuleFile1ShapeRequest1); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]); + + // Add the import statements back to file2 + const addFile2ImportRequest = makeSessionRequest(CommandNames.Change, { + file: file1Consumer1.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: `import {Foo} from "./moduleFile1";` + }); + session.executeCommand(addFile2ImportRequest); + + // Change the content of file1 to `export var T2: string;export var T: number;export function Foo() { };` + const changeModuleFile1ShapeRequest2 = makeSessionRequest(CommandNames.Change, { + file: moduleFile1.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: `export var T2: string;` }); + session.executeCommand(changeModuleFile1ShapeRequest2); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + }); - it("should be up-to-date with changes made in non-open files", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + it("should be up-to-date with changes made in non-open files", () => { + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); + openFilesForSession([moduleFile1], session); - // Send an initial compileOnSave request - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + // Send an initial compileOnSave request + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - host.writeFile(file1Consumer1.path, `let y = 10;`); + host.writeFile(file1Consumer1.path, `let y = 10;`); - session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]); - }); + session.executeCommand(changeModuleFile1ShapeRequest1); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]); + }); - it("should be up-to-date with deleted files", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + it("should be up-to-date with deleted files", () => { + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + openFilesForSession([moduleFile1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - session.executeCommand(changeModuleFile1ShapeRequest1); - // Delete file1Consumer2 - host.deleteFile(file1Consumer2.path); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); - }); + session.executeCommand(changeModuleFile1ShapeRequest1); + // Delete file1Consumer2 + host.deleteFile(file1Consumer2.path); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); + }); - it("should be up-to-date with newly created files", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - - openFilesForSession([moduleFile1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - - const file1Consumer3: File = { - path: "/a/b/file1Consumer3.ts", - content: `import {Foo} from "./moduleFile1"; let y = Foo();` - }; - host.writeFile(file1Consumer3.path, file1Consumer3.content); - host.runQueuedTimeoutCallbacks(); - session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3] }]); - }); + it("should be up-to-date with newly created files", () => { + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); - it("should detect changes in non-root files", () => { - moduleFile1 = { - path: "/a/b/moduleFile1.ts", - content: "export function Foo() { };" - }; + openFilesForSession([moduleFile1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + + const file1Consumer3: File = { + path: "/a/b/file1Consumer3.ts", + content: `import {Foo} from "./moduleFile1"; let y = Foo();` + }; + host.writeFile(file1Consumer3.path, file1Consumer3.content); + host.runQueuedTimeoutCallbacks(); + session.executeCommand(changeModuleFile1ShapeRequest1); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3] }]); + }); + + it("should detect changes in non-root files", () => { + moduleFile1 = { + path: "/a/b/moduleFile1.ts", + content: "export function Foo() { };" + }; - file1Consumer1 = { - path: "/a/b/file1Consumer1.ts", - content: `import {Foo} from "./moduleFile1"; let y = Foo();` - }; + file1Consumer1 = { + path: "/a/b/file1Consumer1.ts", + content: `import {Foo} from "./moduleFile1"; let y = Foo();` + }; - configFile = { - path: "/a/b/tsconfig.json", - content: `{ + configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compileOnSave": true, "files": ["${file1Consumer1.path}"] }` - }; + }; - const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1, file1Consumer1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); + openFilesForSession([moduleFile1, file1Consumer1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); - // change file1 shape now, and verify both files are affected - session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); + // change file1 shape now, and verify both files are affected + session.executeCommand(changeModuleFile1ShapeRequest1); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); - // change file1 internal, and verify only file1 is affected - session.executeCommand(changeModuleFile1InternalRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); - }); + // change file1 internal, and verify only file1 is affected + session.executeCommand(changeModuleFile1InternalRequest1); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + }); - it("should return all files if a global file changed shape", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - - openFilesForSession([globalFile3], session); - const changeGlobalFile3ShapeRequest = makeSessionRequest(CommandNames.Change, { - file: globalFile3.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 1, - insertString: `var T2: string;` - }); + it("should return all files if a global file changed shape", () => { + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); - // check after file1 shape changes - session.executeCommand(changeGlobalFile3ShapeRequest); - const globalFile3FileListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: globalFile3.path }); - sendAffectedFileRequestAndCheckResult(session, globalFile3FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2] }]); + openFilesForSession([globalFile3], session); + const changeGlobalFile3ShapeRequest = makeSessionRequest(CommandNames.Change, { + file: globalFile3.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: `var T2: string;` }); - it("should return empty array if CompileOnSave is not enabled", () => { - configFile = { - path: "/a/b/tsconfig.json", - content: `{}` - }; - - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []); - }); + // check after file1 shape changes + session.executeCommand(changeGlobalFile3ShapeRequest); + const globalFile3FileListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: globalFile3.path }); + sendAffectedFileRequestAndCheckResult(session, globalFile3FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2] }]); + }); + + it("should return empty array if CompileOnSave is not enabled", () => { + configFile = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); + openFilesForSession([moduleFile1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []); + }); - it("should return empty array if noEmit is set", () => { - configFile = { - path: "/a/b/tsconfig.json", - content: `{ + it("should return empty array if noEmit is set", () => { + configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compileOnSave": true, "compilerOptions": { "noEmit": true } }` - }; + }; - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []); - }); + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); + openFilesForSession([moduleFile1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []); + }); - it("should save when compileOnSave is enabled in base tsconfig.json", () => { - configFile = { - path: "/a/b/tsconfig.json", - content: `{ + it("should save when compileOnSave is enabled in base tsconfig.json", () => { + configFile = { + path: "/a/b/tsconfig.json", + content: `{ "extends": "/a/tsconfig.json" }` - }; + }; - const configFile2: File = { - path: "/a/tsconfig.json", - content: `{ + const configFile2: File = { + path: "/a/tsconfig.json", + content: `{ "compileOnSave": true }` - }; + }; - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1, file1Consumer1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - }); + openFilesForSession([moduleFile1, file1Consumer1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + }); - it("should always return the file itself if '--isolatedModules' is specified", () => { - configFile = { - path: "/a/b/tsconfig.json", - content: `{ + it("should always return the file itself if '--isolatedModules' is specified", () => { + configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compileOnSave": true, "compilerOptions": { "isolatedModules": true } }` - }; - - const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); - - const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { - file: moduleFile1.path, - line: 1, - offset: 27, - endLine: 1, - endOffset: 27, - insertString: `Point,` - }); - session.executeCommand(file1ChangeShapeRequest); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + }; + + const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); + openFilesForSession([moduleFile1], session); + + const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { + file: moduleFile1.path, + line: 1, + offset: 27, + endLine: 1, + endOffset: 27, + insertString: `Point,` }); + session.executeCommand(file1ChangeShapeRequest); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + }); - it("should always return the file itself if '--out' or '--outFile' is specified", () => { - configFile = { - path: "/a/b/tsconfig.json", - content: `{ + it("should always return the file itself if '--out' or '--outFile' is specified", () => { + configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compileOnSave": true, "compilerOptions": { "module": "system", "outFile": "/a/b/out.js" } }` - }; - - const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); - - const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { - file: moduleFile1.path, - line: 1, - offset: 27, - endLine: 1, - endOffset: 27, - insertString: `Point,` - }); - session.executeCommand(file1ChangeShapeRequest); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + }; + + const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); + openFilesForSession([moduleFile1], session); + + const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { + file: moduleFile1.path, + line: 1, + offset: 27, + endLine: 1, + endOffset: 27, + insertString: `Point,` }); + session.executeCommand(file1ChangeShapeRequest); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + }); - it("should return cascaded affected file list", () => { - const file1Consumer1Consumer1: File = { - path: "/a/b/file1Consumer1Consumer1.ts", - content: `import {y} from "./file1Consumer1";` - }; - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - - openFilesForSession([moduleFile1, file1Consumer1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); - - const changeFile1Consumer1ShapeRequest = makeSessionRequest(CommandNames.Change, { - file: file1Consumer1.path, - line: 2, - offset: 1, - endLine: 2, - endOffset: 1, - insertString: `export var T: number;` - }); - session.executeCommand(changeModuleFile1ShapeRequest1); - session.executeCommand(changeFile1Consumer1ShapeRequest); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); + it("should return cascaded affected file list", () => { + const file1Consumer1Consumer1: File = { + path: "/a/b/file1Consumer1Consumer1.ts", + content: `import {y} from "./file1Consumer1";` + }; + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); + + openFilesForSession([moduleFile1, file1Consumer1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); + + const changeFile1Consumer1ShapeRequest = makeSessionRequest(CommandNames.Change, { + file: file1Consumer1.path, + line: 2, + offset: 1, + endLine: 2, + endOffset: 1, + insertString: `export var T: number;` }); + session.executeCommand(changeModuleFile1ShapeRequest1); + session.executeCommand(changeFile1Consumer1ShapeRequest); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); + }); - it("should work fine for files with circular references", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: ` + it("should work fine for files with circular references", () => { + const file1: File = { + path: "/a/b/file1.ts", + content: ` /// export var t1 = 10;` - }; - const file2: File = { - path: "/a/b/file2.ts", - content: ` + }; + const file2: File = { + path: "/a/b/file2.ts", + content: ` /// export var t2 = 10;` - }; - const host = createServerHost([file1, file2, configFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - - openFilesForSession([file1, file2], session); - const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); - sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [{ projectFileName: configFile.path, files: [file1, file2] }]); - }); + }; + const host = createServerHost([file1, file2, configFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); - it("should return results for all projects if not specifying projectFileName", () => { - const file1: File = { path: "/a/b/file1.ts", content: "export var t = 10;" }; - const file2: File = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` }; - const file3: File = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` }; - const configFile1: File = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` }; - const configFile2: File = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` }; + openFilesForSession([file1, file2], session); + const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); + sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [{ projectFileName: configFile.path, files: [file1, file2] }]); + }); - const host = createServerHost([file1, file2, file3, configFile1, configFile2]); - const session = createSession(host); + it("should return results for all projects if not specifying projectFileName", () => { + const file1: File = { path: "/a/b/file1.ts", content: "export var t = 10;" }; + const file2: File = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` }; + const file3: File = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` }; + const configFile1: File = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` }; + const configFile2: File = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` }; - openFilesForSession([file1, file2, file3], session); - const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); + const host = createServerHost([file1, file2, file3, configFile1, configFile2]); + const session = createSession(host); - sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [ - { projectFileName: configFile1.path, files: [file1, file2] }, - { projectFileName: configFile2.path, files: [file1, file3] } - ]); - }); + openFilesForSession([file1, file2, file3], session); + const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); - it("should detect removed code file", () => { - const referenceFile1: File = { - path: "/a/b/referenceFile1.ts", - content: ` + sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [ + { projectFileName: configFile1.path, files: [file1, file2] }, + { projectFileName: configFile2.path, files: [file1, file3] } + ]); + }); + + it("should detect removed code file", () => { + const referenceFile1: File = { + path: "/a/b/referenceFile1.ts", + content: ` /// export var x = Foo();` - }; - const host = createServerHost([moduleFile1, referenceFile1, configFile]); - const session = createSession(host); + }; + const host = createServerHost([moduleFile1, referenceFile1, configFile]); + const session = createSession(host); - openFilesForSession([referenceFile1], session); - host.deleteFile(moduleFile1.path); + openFilesForSession([referenceFile1], session); + host.deleteFile(moduleFile1.path); - const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); - sendAffectedFileRequestAndCheckResult(session, request, [ - { projectFileName: configFile.path, files: [referenceFile1] } - ]); - const requestForMissingFile = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path }); - sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []); - }); + const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); + sendAffectedFileRequestAndCheckResult(session, request, [ + { projectFileName: configFile.path, files: [referenceFile1] } + ]); + const requestForMissingFile = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path }); + sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []); + }); - it("should detect non-existing code file", () => { - const referenceFile1: File = { - path: "/a/b/referenceFile1.ts", - content: ` + it("should detect non-existing code file", () => { + const referenceFile1: File = { + path: "/a/b/referenceFile1.ts", + content: ` /// export var x = Foo();` - }; - const host = createServerHost([referenceFile1, configFile]); - const session = createSession(host); - - openFilesForSession([referenceFile1], session); - const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); - sendAffectedFileRequestAndCheckResult(session, request, [ - { projectFileName: configFile.path, files: [referenceFile1] } - ]); - }); - }); - - describe("for changes in declaration files", () => { - function testDTS(dtsFileContents: string, tsFileContents: string, opts: CompilerOptions, expectDTSEmit: boolean) { - const dtsFile = { - path: "/a/runtime/a.d.ts", - content: dtsFileContents - }; - const f2 = { - path: "/a/b.ts", - content: tsFileContents - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: opts, - compileOnSave: true - }) - }; - const host = createServerHost([dtsFile, f2, config]); - const session = createSession(host); - session.executeCommand({ - seq: 1, - type: "request", - command: "open", - arguments: { file: dtsFile.path } - } as protocol.OpenRequest); - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects.get(config.path)!; - checkProjectRootFiles(project, [dtsFile.path, f2.path]); - session.executeCommand({ - seq: 2, - type: "request", - command: "open", - arguments: { file: f2.path } - } as protocol.OpenRequest); - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); - const { response } = session.executeCommand({ - seq: 3, - type: "request", - command: "compileOnSaveAffectedFileList", - arguments: { file: dtsFile.path } - } as protocol.CompileOnSaveAffectedFileListRequest); - if (expectDTSEmit) { - assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output from 1 project"); - assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[])[0].fileNames.length, 2, "expected to affect 2 files"); - } - else { - assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 0, "expected no output"); - } + }; + const host = createServerHost([referenceFile1, configFile]); + const session = createSession(host); + openFilesForSession([referenceFile1], session); + const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); + sendAffectedFileRequestAndCheckResult(session, request, [ + { projectFileName: configFile.path, files: [referenceFile1] } + ]); + }); + }); - const { response: response2 } = session.executeCommand({ - seq: 4, - type: "request", - command: "compileOnSaveAffectedFileList", - arguments: { file: f2.path } - } as protocol.CompileOnSaveAffectedFileListRequest); - assert.equal((response2 as protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output from 1 project"); + describe("for changes in declaration files", () => { + function testDTS(dtsFileContents: string, tsFileContents: string, opts: CompilerOptions, expectDTSEmit: boolean) { + const dtsFile = { + path: "/a/runtime/a.d.ts", + content: dtsFileContents + }; + const f2 = { + path: "/a/b.ts", + content: tsFileContents + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: opts, + compileOnSave: true + }) + }; + const host = createServerHost([dtsFile, f2, config]); + const session = createSession(host); + session.executeCommand({ + seq: 1, + type: "request", + command: "open", + arguments: { file: dtsFile.path } + } as protocol.OpenRequest); + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(config.path)!; + checkProjectRootFiles(project, [dtsFile.path, f2.path]); + session.executeCommand({ + seq: 2, + type: "request", + command: "open", + arguments: { file: f2.path } + } as protocol.OpenRequest); + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); + const { response } = session.executeCommand({ + seq: 3, + type: "request", + command: "compileOnSaveAffectedFileList", + arguments: { file: dtsFile.path } + } as protocol.CompileOnSaveAffectedFileListRequest); + if (expectDTSEmit) { + assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output from 1 project"); + assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[])[0].fileNames.length, 2, "expected to affect 2 files"); + } + else { + assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 0, "expected no output"); } - it("should return empty array if change is made in a global declaration file", () => { - testDTS( - /*dtsFileContents*/ "declare const x: string;", - /*tsFileContents*/ "var y = 1;", - /*opts*/ {}, - /*expectDTSEmit*/ false - ); - }); - - it("should return empty array if change is made in a module declaration file", () => { - testDTS( - /*dtsFileContents*/ "export const x: string;", - /*tsFileContents*/ "import { x } from './runtime/a;", - /*opts*/ {}, - /*expectDTSEmit*/ false - ); - }); - it("should return results if change is made in a global declaration file with declaration emit", () => { - testDTS( - /*dtsFileContents*/ "declare const x: string;", - /*tsFileContents*/ "var y = 1;", - /*opts*/ { declaration: true }, - /*expectDTSEmit*/ true - ); - }); + const { response: response2 } = session.executeCommand({ + seq: 4, + type: "request", + command: "compileOnSaveAffectedFileList", + arguments: { file: f2.path } + } as protocol.CompileOnSaveAffectedFileListRequest); + assert.equal((response2 as protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output from 1 project"); + } - it("should return results if change is made in a global declaration file with composite enabled", () => { - testDTS( - /*dtsFileContents*/ "declare const x: string;", - /*tsFileContents*/ "var y = 1;", - /*opts*/ { composite: true }, - /*expectDTSEmit*/ true - ); - }); + it("should return empty array if change is made in a global declaration file", () => { + testDTS( + /*dtsFileContents*/ "declare const x: string;", + /*tsFileContents*/ "var y = 1;", + /*opts*/ {}, + /*expectDTSEmit*/ false + ); + }); - it("should return results if change is made in a global declaration file with decorator emit enabled", () => { - testDTS( - /*dtsFileContents*/ "declare const x: string;", - /*tsFileContents*/ "var y = 1;", - /*opts*/ { experimentalDecorators: true, emitDecoratorMetadata: true }, - /*expectDTSEmit*/ true - ); - }); + it("should return empty array if change is made in a module declaration file", () => { + testDTS( + /*dtsFileContents*/ "export const x: string;", + /*tsFileContents*/ "import { x } from './runtime/a;", + /*opts*/ {}, + /*expectDTSEmit*/ false + ); }); - describe("tsserverProjectSystem emit with outFile or out setting", () => { - function test(opts: CompilerOptions, expectedUsesOutFile: boolean) { - const f1 = { - path: "/a/a.ts", - content: "let x = 1" - }; - const f2 = { - path: "/a/b.ts", - content: "let y = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: opts, - compileOnSave: true - }) - }; - const host = createServerHost([f1, f2, config]); - const session = createSession(host); - session.executeCommand({ - seq: 1, - type: "request", - command: "open", - arguments: { file: f1.path } - } as protocol.OpenRequest); - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); - const { response } = session.executeCommand({ - seq: 2, - type: "request", - command: "compileOnSaveAffectedFileList", - arguments: { file: f1.path } - } as protocol.CompileOnSaveAffectedFileListRequest); - assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output for 1 project"); - assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[])[0].fileNames.length, 2, "expected output for 1 project"); - assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[])[0].projectUsesOutFile, expectedUsesOutFile, "usesOutFile"); - } + it("should return results if change is made in a global declaration file with declaration emit", () => { + testDTS( + /*dtsFileContents*/ "declare const x: string;", + /*tsFileContents*/ "var y = 1;", + /*opts*/ { declaration: true }, + /*expectDTSEmit*/ true + ); + }); - it("projectUsesOutFile should not be returned if not set", () => { - test({}, /*expectedUsesOutFile*/ false); - }); - it("projectUsesOutFile should be true if outFile is set", () => { - test({ outFile: "/a/out.js" }, /*expectedUsesOutFile*/ true); - }); - it("projectUsesOutFile should be true if out is set", () => { - test({ out: "/a/out.js" }, /*expectedUsesOutFile*/ true); - }); + it("should return results if change is made in a global declaration file with composite enabled", () => { + testDTS( + /*dtsFileContents*/ "declare const x: string;", + /*tsFileContents*/ "var y = 1;", + /*opts*/ { composite: true }, + /*expectDTSEmit*/ true + ); }); - }); - describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { - it("should respect line endings", () => { - test("\n"); - test("\r\n"); - - function test(newLine: string) { - const lines = ["var x = 1;", "var y = 2;"]; - const path = "/a/app"; - const f = { - path: path + Extension.Ts, - content: lines.join(newLine) - }; - const host = createServerHost([f], { newLine }); - const session = createSession(host); - const openRequest: server.protocol.OpenRequest = { - seq: 1, - type: "request", - command: server.protocol.CommandTypes.Open, - arguments: { file: f.path } - }; - session.executeCommand(openRequest); - const emitFileRequest: server.protocol.CompileOnSaveEmitFileRequest = { - seq: 2, - type: "request", - command: server.protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: f.path } - }; - session.executeCommand(emitFileRequest); - const emitOutput = host.readFile(path + Extension.Js); - assert.equal(emitOutput, f.content + newLine, "content of emit output should be identical with the input + newline"); - } + it("should return results if change is made in a global declaration file with decorator emit enabled", () => { + testDTS( + /*dtsFileContents*/ "declare const x: string;", + /*tsFileContents*/ "var y = 1;", + /*opts*/ { experimentalDecorators: true, emitDecoratorMetadata: true }, + /*expectDTSEmit*/ true + ); }); + }); - it("should emit specified file", () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export function Foo() { return 10; }` + describe("tsserverProjectSystem emit with outFile or out setting", () => { + function test(opts: CompilerOptions, expectedUsesOutFile: boolean) { + const f1 = { + path: "/a/a.ts", + content: "let x = 1" }; - const file2 = { - path: "/a/b/f2.ts", - content: `import {Foo} from "./f1"; let y = Foo();` + const f2 = { + path: "/a/b.ts", + content: "let y = 1" }; - const configFile = { - path: "/a/b/tsconfig.json", - content: `{}` + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: opts, + compileOnSave: true + }) }; - const host = createServerHost([file1, file2, configFile, libFile], { newLine: "\r\n" }); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - - openFilesForSession([file1, file2], session); - const compileFileRequest = makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: configFile.path }); - session.executeCommand(compileFileRequest); + const host = createServerHost([f1, f2, config]); + const session = createSession(host); + session.executeCommand({ + seq: 1, + type: "request", + command: "open", + arguments: { file: f1.path } + } as protocol.OpenRequest); + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); + const { response } = session.executeCommand({ + seq: 2, + type: "request", + command: "compileOnSaveAffectedFileList", + arguments: { file: f1.path } + } as protocol.CompileOnSaveAffectedFileListRequest); + assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output for 1 project"); + assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[])[0].fileNames.length, 2, "expected output for 1 project"); + assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[])[0].projectUsesOutFile, expectedUsesOutFile, "usesOutFile"); + } - const expectedEmittedFileName = "/a/b/f1.js"; - assert.isTrue(host.fileExists(expectedEmittedFileName)); - assert.equal(host.readFile(expectedEmittedFileName), `"use strict";\r\nexports.__esModule = true;\r\nexports.Foo = void 0;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`); + it("projectUsesOutFile should not be returned if not set", () => { + test({}, /*expectedUsesOutFile*/ false); }); - - it("shoud not emit js files in external projects", () => { - const file1 = { - path: "/a/b/file1.ts", - content: "consonle.log('file1');" + it("projectUsesOutFile should be true if outFile is set", () => { + test({ outFile: "/a/out.js" }, /*expectedUsesOutFile*/ true); + }); + it("projectUsesOutFile should be true if out is set", () => { + test({ out: "/a/out.js" }, /*expectedUsesOutFile*/ true); + }); + }); +}); + +describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { + it("should respect line endings", () => { + test("\n"); + test("\r\n"); + + function test(newLine: string) { + const lines = ["var x = 1;", "var y = 2;"]; + const path = "/a/app"; + const f = { + path: path + Extension.Ts, + content: lines.join(newLine) }; - // file2 has errors. The emitting should not be blocked. - const file2 = { - path: "/a/b/file2.js", - content: "console.log'file2');" + const host = createServerHost([f], { newLine }); + const session = createSession(host); + const openRequest: server.protocol.OpenRequest = { + seq: 1, + type: "request", + command: server.protocol.CommandTypes.Open, + arguments: { file: f.path } }; - const file3 = { - path: "/a/b/file3.js", - content: "console.log('file3');" + session.executeCommand(openRequest); + const emitFileRequest: server.protocol.CompileOnSaveEmitFileRequest = { + seq: 2, + type: "request", + command: server.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: f.path } }; - const externalProjectName = "/a/b/externalproject"; - const host = createServerHost([file1, file2, file3, libFile]); - const session = createSession(host); - const projectService = session.getProjectService(); + session.executeCommand(emitFileRequest); + const emitOutput = host.readFile(path + Extension.Js); + assert.equal(emitOutput, f.content + newLine, "content of emit output should be identical with the input + newline"); + } + }); - projectService.openExternalProject({ - rootFiles: toExternalFiles([file1.path, file2.path]), - options: { - allowJs: true, - outFile: "dist.js", - compileOnSave: true - }, - projectFileName: externalProjectName - }); + it("should emit specified file", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export function Foo() { return 10; }` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `import {Foo} from "./f1"; let y = Foo();` + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createServerHost([file1, file2, configFile, libFile], { newLine: "\r\n" }); + const typingsInstaller = createTestTypingsInstaller(host); + const session = createSession(host, { typingsInstaller }); + + openFilesForSession([file1, file2], session); + const compileFileRequest = makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: configFile.path }); + session.executeCommand(compileFileRequest); - const emitRequest = makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path }); - session.executeCommand(emitRequest); + const expectedEmittedFileName = "/a/b/f1.js"; + assert.isTrue(host.fileExists(expectedEmittedFileName)); + assert.equal(host.readFile(expectedEmittedFileName), `"use strict";\r\nexports.__esModule = true;\r\nexports.Foo = void 0;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`); + }); - const expectedOutFileName = "/a/b/dist.js"; - assert.isTrue(host.fileExists(expectedOutFileName)); - const outFileContent = host.readFile(expectedOutFileName)!; - assert.isTrue(outFileContent.indexOf(file1.content) !== -1); - assert.isTrue(outFileContent.indexOf(file2.content) === -1); - assert.isTrue(outFileContent.indexOf(file3.content) === -1); + it("shoud not emit js files in external projects", () => { + const file1 = { + path: "/a/b/file1.ts", + content: "consonle.log('file1');" + }; + // file2 has errors. The emitting should not be blocked. + const file2 = { + path: "/a/b/file2.js", + content: "console.log'file2');" + }; + const file3 = { + path: "/a/b/file3.js", + content: "console.log('file3');" + }; + const externalProjectName = "/a/b/externalproject"; + const host = createServerHost([file1, file2, file3, libFile]); + const session = createSession(host); + const projectService = session.getProjectService(); + + projectService.openExternalProject({ + rootFiles: toExternalFiles([file1.path, file2.path]), + options: { + allowJs: true, + outFile: "dist.js", + compileOnSave: true + }, + projectFileName: externalProjectName }); - it("should use project root as current directory so that compile on save results in correct file mapping", () => { - const inputFileName = "Foo.ts"; - const file1 = { - path: `/root/TypeScriptProject3/TypeScriptProject3/${inputFileName}`, - content: "consonle.log('file1');" - }; - const externalProjectName = "/root/TypeScriptProject3/TypeScriptProject3/TypeScriptProject3.csproj"; - const host = createServerHost([file1, libFile]); - const session = createSession(host); - const projectService = session.getProjectService(); + const emitRequest = makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path }); + session.executeCommand(emitRequest); - const outFileName = "bar.js"; - projectService.openExternalProject({ - rootFiles: toExternalFiles([file1.path]), - options: { - outFile: outFileName, - sourceMap: true, - compileOnSave: true - }, - projectFileName: externalProjectName - }); - - const emitRequest = makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path }); - session.executeCommand(emitRequest); + const expectedOutFileName = "/a/b/dist.js"; + assert.isTrue(host.fileExists(expectedOutFileName)); + const outFileContent = host.readFile(expectedOutFileName)!; + assert.isTrue(outFileContent.indexOf(file1.content) !== -1); + assert.isTrue(outFileContent.indexOf(file2.content) === -1); + assert.isTrue(outFileContent.indexOf(file3.content) === -1); + }); - // Verify js file - const expectedOutFileName = "/root/TypeScriptProject3/TypeScriptProject3/" + outFileName; - assert.isTrue(host.fileExists(expectedOutFileName)); - const outFileContent = host.readFile(expectedOutFileName)!; - verifyContentHasString(outFileContent, file1.content); - verifyContentHasString(outFileContent, `//# ${"sourceMappingURL"}=${outFileName}.map`); // Sometimes tools can sometimes see this line as a source mapping url comment, so we obfuscate it a little + it("should use project root as current directory so that compile on save results in correct file mapping", () => { + const inputFileName = "Foo.ts"; + const file1 = { + path: `/root/TypeScriptProject3/TypeScriptProject3/${inputFileName}`, + content: "consonle.log('file1');" + }; + const externalProjectName = "/root/TypeScriptProject3/TypeScriptProject3/TypeScriptProject3.csproj"; + const host = createServerHost([file1, libFile]); + const session = createSession(host); + const projectService = session.getProjectService(); + + const outFileName = "bar.js"; + projectService.openExternalProject({ + rootFiles: toExternalFiles([file1.path]), + options: { + outFile: outFileName, + sourceMap: true, + compileOnSave: true + }, + projectFileName: externalProjectName + }); - // Verify map file - const expectedMapFileName = expectedOutFileName + ".map"; - assert.isTrue(host.fileExists(expectedMapFileName)); - const mapFileContent = host.readFile(expectedMapFileName)!; - verifyContentHasString(mapFileContent, `"sources":["${inputFileName}"]`); + const emitRequest = makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path }); + session.executeCommand(emitRequest); - function verifyContentHasString(content: string, str: string) { - assert.isTrue(stringContains(content, str), `Expected "${content}" to have "${str}"`); - } - }); + // Verify js file + const expectedOutFileName = "/root/TypeScriptProject3/TypeScriptProject3/" + outFileName; + assert.isTrue(host.fileExists(expectedOutFileName)); + const outFileContent = host.readFile(expectedOutFileName)!; + verifyContentHasString(outFileContent, file1.content); + verifyContentHasString(outFileContent, `//# ${"sourceMappingURL"}=${outFileName}.map`); // Sometimes tools can sometimes see this line as a source mapping url comment, so we obfuscate it a little - describe("compile on save emit with and without richResponse", () => { - it("without rich Response", () => { - verify(/*richRepsonse*/ undefined); - }); - it("with rich Response set to false", () => { - verify(/*richRepsonse*/ false); - }); - it("with rich Repsonse", () => { - verify(/*richRepsonse*/ true); - }); + // Verify map file + const expectedMapFileName = expectedOutFileName + ".map"; + assert.isTrue(host.fileExists(expectedMapFileName)); + const mapFileContent = host.readFile(expectedMapFileName)!; + verifyContentHasString(mapFileContent, `"sources":["${inputFileName}"]`); - function verify(richResponse: boolean | undefined) { - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compileOnSave: true, - compilerOptions: { - outDir: "test", - noEmitOnError: true, - declaration: true, - }, - exclude: ["node_modules"] - }) - }; - const file1: File = { - path: `${tscWatch.projectRoot}/file1.ts`, - content: "const x = 1;" - }; - const file2: File = { - path: `${tscWatch.projectRoot}/file2.ts`, - content: "const y = 2;" - }; - const host = createServerHost([file1, file2, config, libFile]); - const session = createSession(host); - openFilesForSession([file1], session); + function verifyContentHasString(content: string, str: string) { + assert.isTrue(stringContains(content, str), `Expected "${content}" to have "${str}"`); + } + }); - const affectedFileResponse = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: file1.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(affectedFileResponse, [ - { fileNames: [file1.path, file2.path], projectFileName: config.path, projectUsesOutFile: false } - ]); - const file1SaveResponse = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: file1.path, richResponse } - }).response; - if (richResponse) { - assert.deepEqual(file1SaveResponse, { emitSkipped: false, diagnostics: emptyArray }); - } - else { - assert.isTrue(file1SaveResponse); - } - assert.strictEqual(host.readFile(`${tscWatch.projectRoot}/test/file1.d.ts`), "declare const x = 1;\n"); - const file2SaveResponse = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: file2.path, richResponse } - }).response; - if (richResponse) { - assert.deepEqual(file2SaveResponse, { - emitSkipped: true, - diagnostics: [{ - start: undefined, - end: undefined, - fileName: undefined, - text: formatStringFromArgs(Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file.message, [`${tscWatch.projectRoot}/test/file1.d.ts`]), - code: Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file.code, - category: diagnosticCategoryName(Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file), - reportsUnnecessary: undefined, - reportsDeprecated: undefined, - relatedInformation: undefined, - source: undefined - }] - }); - } - else { - assert.isFalse(file2SaveResponse); - } - assert.isFalse(host.fileExists(`${tscWatch.projectRoot}/test/file2.d.ts`)); - } + describe("compile on save emit with and without richResponse", () => { + it("without rich Response", () => { + verify(/*richRepsonse*/ undefined); + }); + it("with rich Response set to false", () => { + verify(/*richRepsonse*/ false); + }); + it("with rich Repsonse", () => { + verify(/*richRepsonse*/ true); }); - describe("compile on save in global files", () => { - describe("when program contains module", () => { - it("when d.ts emit is enabled", () => { - verifyGlobalSave(/*declaration*/ true, /*hasModule*/ true); - }); - it("when d.ts emit is not enabled", () => { - verifyGlobalSave(/*declaration*/ false, /*hasModule*/ true); + function verify(richResponse: boolean | undefined) { + const config: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compileOnSave: true, + compilerOptions: { + outDir: "test", + noEmitOnError: true, + declaration: true, + }, + exclude: ["node_modules"] + }) + }; + const file1: File = { + path: `${tscWatch.projectRoot}/file1.ts`, + content: "const x = 1;" + }; + const file2: File = { + path: `${tscWatch.projectRoot}/file2.ts`, + content: "const y = 2;" + }; + const host = createServerHost([file1, file2, config, libFile]); + const session = createSession(host); + openFilesForSession([file1], session); + + const affectedFileResponse = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: file1.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(affectedFileResponse, [ + { fileNames: [file1.path, file2.path], projectFileName: config.path, projectUsesOutFile: false } + ]); + const file1SaveResponse = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: file1.path, richResponse } + }).response; + if (richResponse) { + assert.deepEqual(file1SaveResponse, { emitSkipped: false, diagnostics: emptyArray }); + } + else { + assert.isTrue(file1SaveResponse); + } + assert.strictEqual(host.readFile(`${tscWatch.projectRoot}/test/file1.d.ts`), "declare const x = 1;\n"); + const file2SaveResponse = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: file2.path, richResponse } + }).response; + if (richResponse) { + assert.deepEqual(file2SaveResponse, { + emitSkipped: true, + diagnostics: [{ + start: undefined, + end: undefined, + fileName: undefined, + text: formatStringFromArgs(Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file.message, [`${tscWatch.projectRoot}/test/file1.d.ts`]), + code: Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file.code, + category: diagnosticCategoryName(Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file), + reportsUnnecessary: undefined, + reportsDeprecated: undefined, + relatedInformation: undefined, + source: undefined + }] }); + } + else { + assert.isFalse(file2SaveResponse); + } + assert.isFalse(host.fileExists(`${tscWatch.projectRoot}/test/file2.d.ts`)); + } + }); + + describe("compile on save in global files", () => { + describe("when program contains module", () => { + it("when d.ts emit is enabled", () => { + verifyGlobalSave(/*declaration*/ true, /*hasModule*/ true); }); - describe("when program doesnt have module", () => { - it("when d.ts emit is enabled", () => { - verifyGlobalSave(/*declaration*/ true, /*hasModule*/ false); - }); - it("when d.ts emit is not enabled", () => { - verifyGlobalSave(/*declaration*/ false, /*hasModule*/ false); - }); + it("when d.ts emit is not enabled", () => { + verifyGlobalSave(/*declaration*/ false, /*hasModule*/ true); + }); + }); + describe("when program doesnt have module", () => { + it("when d.ts emit is enabled", () => { + verifyGlobalSave(/*declaration*/ true, /*hasModule*/ false); + }); + it("when d.ts emit is not enabled", () => { + verifyGlobalSave(/*declaration*/ false, /*hasModule*/ false); }); - function verifyGlobalSave(declaration: boolean,hasModule: boolean) { - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compileOnSave: true, - compilerOptions: { - declaration, - module: hasModule ? undefined : "none" - }, - }) - }; - const file1: File = { - path: `${tscWatch.projectRoot}/file1.ts`, - content: `const x = 1; + }); + function verifyGlobalSave(declaration: boolean,hasModule: boolean) { + const config: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compileOnSave: true, + compilerOptions: { + declaration, + module: hasModule ? undefined : "none" + }, + }) + }; + const file1: File = { + path: `${tscWatch.projectRoot}/file1.ts`, + content: `const x = 1; function foo() { return "hello"; }` - }; - const file2: File = { - path: `${tscWatch.projectRoot}/file2.ts`, - content: `const y = 2; + }; + const file2: File = { + path: `${tscWatch.projectRoot}/file2.ts`, + content: `const y = 2; function bar() { return "world"; }` - }; - const file3: File = { - path: `${tscWatch.projectRoot}/file3.ts`, - content: "const xy = 3;" - }; - const module: File = { - path: `${tscWatch.projectRoot}/module.ts`, - content: "export const xyz = 4;" - }; - const files = [file1, file2, file3, ...(hasModule ? [module] : emptyArray)]; - const host = createServerHost([...files, config, libFile]); - const session = createSession(host); - openFilesForSession([file1, file2], session); + }; + const file3: File = { + path: `${tscWatch.projectRoot}/file3.ts`, + content: "const xy = 3;" + }; + const module: File = { + path: `${tscWatch.projectRoot}/module.ts`, + content: "export const xyz = 4;" + }; + const files = [file1, file2, file3, ...(hasModule ? [module] : emptyArray)]; + const host = createServerHost([...files, config, libFile]); + const session = createSession(host); + openFilesForSession([file1, file2], session); - const affectedFileResponse = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: file1.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(affectedFileResponse, [ - { fileNames: files.map(f => f.path), projectFileName: config.path, projectUsesOutFile: false } - ]); - verifyFileSave(file1); - verifyFileSave(file2); - verifyFileSave(file3); - if (hasModule) { - verifyFileSave(module); - } + const affectedFileResponse = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: file1.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(affectedFileResponse, [ + { fileNames: files.map(f => f.path), projectFileName: config.path, projectUsesOutFile: false } + ]); + verifyFileSave(file1); + verifyFileSave(file2); + verifyFileSave(file3); + if (hasModule) { + verifyFileSave(module); + } - // Change file1 get affected file list - verifyLocalEdit(file1, "hello", "world"); + // Change file1 get affected file list + verifyLocalEdit(file1, "hello", "world"); - // Change file2 get affected file list = will return only file2 if --declaration otherwise all files - verifyLocalEdit(file2, "world", "hello", /*returnsAllFilesAsAffected*/ !declaration); + // Change file2 get affected file list = will return only file2 if --declaration otherwise all files + verifyLocalEdit(file2, "world", "hello", /*returnsAllFilesAsAffected*/ !declaration); - function verifyFileSave(file: File) { - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: file.path } - }).response; - assert.isTrue(response); + function verifyFileSave(file: File) { + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: file.path } + }).response; + assert.isTrue(response); + assert.strictEqual( + host.readFile(changeExtension(file.path, ".js")), + file === module ? + `"use strict";\nexports.__esModule = true;\nexports.xyz = void 0;\nexports.xyz = 4;\n` : + `${file.content.replace("const", "var")}\n` + ); + if (declaration) { assert.strictEqual( - host.readFile(changeExtension(file.path, ".js")), - file === module ? - `"use strict";\nexports.__esModule = true;\nexports.xyz = void 0;\nexports.xyz = 4;\n` : - `${file.content.replace("const", "var")}\n` + host.readFile(changeExtension(file.path, ".d.ts")), + (file.content.substr(0, file.content.indexOf(" {") === -1 ? file.content.length : file.content.indexOf(" {")) + .replace("const ", "declare const ") + .replace("function ", "declare function ") + .replace(")", "): string;")) + "\n" ); - if (declaration) { - assert.strictEqual( - host.readFile(changeExtension(file.path, ".d.ts")), - (file.content.substr(0, file.content.indexOf(" {") === -1 ? file.content.length : file.content.indexOf(" {")) - .replace("const ", "declare const ") - .replace("function ", "declare function ") - .replace(")", "): string;")) + "\n" - ); - } } + } - function verifyLocalEdit(file: File, oldText: string, newText: string, returnsAllFilesAsAffected?: boolean) { - // Change file1 get affected file list - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: file.path, - textChanges: [{ - newText, - ...protocolTextSpanFromSubstring(file.content, oldText) - }] + function verifyLocalEdit(file: File, oldText: string, newText: string, returnsAllFilesAsAffected?: boolean) { + // Change file1 get affected file list + session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: file.path, + textChanges: [{ + newText, + ...protocolTextSpanFromSubstring(file.content, oldText) }] - } - }); - const affectedFileResponse = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: file.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(affectedFileResponse, [ - { fileNames: [file.path, ...(returnsAllFilesAsAffected ? files.filter(f => f !== file).map(f => f.path) : emptyArray)], projectFileName: config.path, projectUsesOutFile: false } - ]); - file.content = file.content.replace(oldText, newText); - verifyFileSave(file); - } + }] + } + }); + const affectedFileResponse = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: file.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(affectedFileResponse, [ + { fileNames: [file.path, ...(returnsAllFilesAsAffected ? files.filter(f => f !== file).map(f => f.path) : emptyArray)], projectFileName: config.path, projectUsesOutFile: false } + ]); + file.content = file.content.replace(oldText, newText); + verifyFileSave(file); } - }); - }); - - describe("unittests:: tsserver:: compileOnSave:: CompileOnSaveAffectedFileListRequest with and without projectFileName in request", () => { - const core: File = { - path: `${tscWatch.projectRoot}/core/core.ts`, - content: "let z = 10;" - }; - const app1: File = { - path: `${tscWatch.projectRoot}/app1/app.ts`, - content: "let x = 10;" - }; - const app2: File = { - path: `${tscWatch.projectRoot}/app2/app.ts`, - content: "let y = 10;" - }; - const app1Config: File = { - path: `${tscWatch.projectRoot}/app1/tsconfig.json`, - content: JSON.stringify({ - files: ["app.ts", "../core/core.ts"], - compilerOptions: { outFile: "build/output.js" }, - compileOnSave: true - }) - }; - const app2Config: File = { - path: `${tscWatch.projectRoot}/app2/tsconfig.json`, - content: JSON.stringify({ - files: ["app.ts", "../core/core.ts"], - compilerOptions: { outFile: "build/output.js" }, - compileOnSave: true - }) - }; - const files = [libFile, core, app1, app2, app1Config, app2Config]; - - function insertString(session: TestSession, file: File) { - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: file.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 1, - insertString: "let k = 1" - } - }); } + }); +}); + +describe("unittests:: tsserver:: compileOnSave:: CompileOnSaveAffectedFileListRequest with and without projectFileName in request", () => { + const core: File = { + path: `${tscWatch.projectRoot}/core/core.ts`, + content: "let z = 10;" + }; + const app1: File = { + path: `${tscWatch.projectRoot}/app1/app.ts`, + content: "let x = 10;" + }; + const app2: File = { + path: `${tscWatch.projectRoot}/app2/app.ts`, + content: "let y = 10;" + }; + const app1Config: File = { + path: `${tscWatch.projectRoot}/app1/tsconfig.json`, + content: JSON.stringify({ + files: ["app.ts", "../core/core.ts"], + compilerOptions: { outFile: "build/output.js" }, + compileOnSave: true + }) + }; + const app2Config: File = { + path: `${tscWatch.projectRoot}/app2/tsconfig.json`, + content: JSON.stringify({ + files: ["app.ts", "../core/core.ts"], + compilerOptions: { outFile: "build/output.js" }, + compileOnSave: true + }) + }; + const files = [libFile, core, app1, app2, app1Config, app2Config]; + + function insertString(session: TestSession, file: File) { + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: file.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: "let k = 1" + } + }); + } - function getSession() { - const host = createServerHost(files); - const session = createSession(host); - openFilesForSession([app1, app2, core], session); - const service = session.getProjectService(); - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - const project1 = service.configuredProjects.get(app1Config.path)!; - const project2 = service.configuredProjects.get(app2Config.path)!; - checkProjectActualFiles(project1, [libFile.path, app1.path, core.path, app1Config.path]); - checkProjectActualFiles(project2, [libFile.path, app2.path, core.path, app2Config.path]); - insertString(session, app1); - insertString(session, app2); - assert.equal(project1.dirty, true); - assert.equal(project2.dirty, true); - return session; - } + function getSession() { + const host = createServerHost(files); + const session = createSession(host); + openFilesForSession([app1, app2, core], session); + const service = session.getProjectService(); + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + const project1 = service.configuredProjects.get(app1Config.path)!; + const project2 = service.configuredProjects.get(app2Config.path)!; + checkProjectActualFiles(project1, [libFile.path, app1.path, core.path, app1Config.path]); + checkProjectActualFiles(project2, [libFile.path, app2.path, core.path, app2Config.path]); + insertString(session, app1); + insertString(session, app2); + assert.equal(project1.dirty, true); + assert.equal(project2.dirty, true); + return session; + } - it("when projectFile is specified", () => { - const session = getSession(); - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { - file: core.path, - projectFileName: app1Config.path - } - }).response; - assert.deepEqual(response, [ - { projectFileName: app1Config.path, fileNames: [core.path, app1.path], projectUsesOutFile: true } - ]); - assert.equal(session.getProjectService().configuredProjects.get(app1Config.path)!.dirty, false); - assert.equal(session.getProjectService().configuredProjects.get(app2Config.path)!.dirty, true); - }); + it("when projectFile is specified", () => { + const session = getSession(); + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { + file: core.path, + projectFileName: app1Config.path + } + }).response; + assert.deepEqual(response, [ + { projectFileName: app1Config.path, fileNames: [core.path, app1.path], projectUsesOutFile: true } + ]); + assert.equal(session.getProjectService().configuredProjects.get(app1Config.path)!.dirty, false); + assert.equal(session.getProjectService().configuredProjects.get(app2Config.path)!.dirty, true); + }); - it("when projectFile is not specified", () => { - const session = getSession(); - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { - file: core.path - } - }).response; - assert.deepEqual(response, [ - { projectFileName: app1Config.path, fileNames: [core.path, app1.path], projectUsesOutFile: true }, - { projectFileName: app2Config.path, fileNames: [core.path, app2.path], projectUsesOutFile: true } - ]); - assert.equal(session.getProjectService().configuredProjects.get(app1Config.path)!.dirty, false); - assert.equal(session.getProjectService().configuredProjects.get(app2Config.path)!.dirty, false); - }); + it("when projectFile is not specified", () => { + const session = getSession(); + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { + file: core.path + } + }).response; + assert.deepEqual(response, [ + { projectFileName: app1Config.path, fileNames: [core.path, app1.path], projectUsesOutFile: true }, + { projectFileName: app2Config.path, fileNames: [core.path, app2.path], projectUsesOutFile: true } + ]); + assert.equal(session.getProjectService().configuredProjects.get(app1Config.path)!.dirty, false); + assert.equal(session.getProjectService().configuredProjects.get(app2Config.path)!.dirty, false); }); +}); } diff --git a/src/testRunner/unittests/tsserver/completions.ts b/src/testRunner/unittests/tsserver/completions.ts index 4dff530a75b89..0b067ce63f058 100644 --- a/src/testRunner/unittests/tsserver/completions.ts +++ b/src/testRunner/unittests/tsserver/completions.ts @@ -1,265 +1,265 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: completions", () => { - it("works", () => { - const aTs: File = { - path: "/a.ts", - content: "export const foo = 0;", - }; - const bTs: File = { - path: "/b.ts", - content: "foo", - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", - }; +describe("unittests:: tsserver:: completions", () => { + it("works", () => { + const aTs: File = { + path: "/a.ts", + content: "export const foo = 0;", + }; + const bTs: File = { + path: "/b.ts", + content: "foo", + }; + const tsconfig: File = { + path: "/tsconfig.json", + content: "{}", + }; - const session = createSession(createServerHost([aTs, bTs, tsconfig])); - openFilesForSession([aTs, bTs], session); + const session = createSession(createServerHost([aTs, bTs, tsconfig])); + openFilesForSession([aTs, bTs], session); - const requestLocation: protocol.FileLocationRequestArgs = { - file: bTs.path, - line: 1, - offset: 3, - }; + const requestLocation: protocol.FileLocationRequestArgs = { + file: bTs.path, + line: 1, + offset: 3, + }; - const response = executeSessionRequest(session, protocol.CommandTypes.CompletionInfo, { - ...requestLocation, - includeExternalModuleExports: true, - prefix: "foo", - }); - const entry: protocol.CompletionEntry = { - hasAction: true, - insertText: undefined, - isRecommended: undefined, - kind: ScriptElementKind.constElement, - kindModifiers: ScriptElementKindModifier.exportedModifier, - name: "foo", - replacementSpan: undefined, - isPackageJsonImport: undefined, - isImportStatementCompletion: undefined, - sortText: Completions.SortText.AutoImportSuggestions, - source: "/a", - sourceDisplay: undefined, - isSnippet: undefined, - data: { exportName: "foo", fileName: "/a.ts", ambientModuleName: undefined, isPackageJsonImport: undefined } - }; + const response = executeSessionRequest(session, protocol.CommandTypes.CompletionInfo, { + ...requestLocation, + includeExternalModuleExports: true, + prefix: "foo", + }); + const entry: protocol.CompletionEntry = { + hasAction: true, + insertText: undefined, + isRecommended: undefined, + kind: ScriptElementKind.constElement, + kindModifiers: ScriptElementKindModifier.exportedModifier, + name: "foo", + replacementSpan: undefined, + isPackageJsonImport: undefined, + isImportStatementCompletion: undefined, + sortText: Completions.SortText.AutoImportSuggestions, + source: "/a", + sourceDisplay: undefined, + isSnippet: undefined, + data: { exportName: "foo", fileName: "/a.ts", ambientModuleName: undefined, isPackageJsonImport: undefined } + }; - // `data.exportMapKey` contains a SymbolId so should not be mocked up with an expected value here. - // Just assert that it's a string and then delete it so we can compare everything else with `deepEqual`. - const exportMapKey = (response?.entries[0].data as any)?.exportMapKey; - assert.isString(exportMapKey); - delete (response?.entries[0].data as any).exportMapKey; - assert.deepEqual(response, { - isGlobalCompletion: true, - isIncomplete: undefined, - isMemberCompletion: false, - isNewIdentifierLocation: false, - optionalReplacementSpan: { start: { line: 1, offset: 1 }, end: { line: 1, offset: 4 } }, - entries: [entry], - }); + // `data.exportMapKey` contains a SymbolId so should not be mocked up with an expected value here. + // Just assert that it's a string and then delete it so we can compare everything else with `deepEqual`. + const exportMapKey = (response?.entries[0].data as any)?.exportMapKey; + assert.isString(exportMapKey); + delete (response?.entries[0].data as any).exportMapKey; + assert.deepEqual(response, { + isGlobalCompletion: true, + isIncomplete: undefined, + isMemberCompletion: false, + isNewIdentifierLocation: false, + optionalReplacementSpan: { start: { line: 1, offset: 1 }, end: { line: 1, offset: 4 } }, + entries: [entry], + }); - const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = { - ...requestLocation, - entryNames: [{ name: "foo", source: "/a", data: { exportName: "foo", fileName: "/a.ts", exportMapKey } }], - }; + const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = { + ...requestLocation, + entryNames: [{ name: "foo", source: "/a", data: { exportName: "foo", fileName: "/a.ts", exportMapKey } }], + }; - const detailsResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs); - const detailsCommon: protocol.CompletionEntryDetails & CompletionEntryDetails = { - displayParts: [ - keywordPart(SyntaxKind.ConstKeyword), - spacePart(), - displayPart("foo", SymbolDisplayPartKind.localName), - punctuationPart(SyntaxKind.ColonToken), - spacePart(), - displayPart("0", SymbolDisplayPartKind.stringLiteral), + const detailsResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs); + const detailsCommon: protocol.CompletionEntryDetails & CompletionEntryDetails = { + displayParts: [ + keywordPart(SyntaxKind.ConstKeyword), + spacePart(), + displayPart("foo", SymbolDisplayPartKind.localName), + punctuationPart(SyntaxKind.ColonToken), + spacePart(), + displayPart("0", SymbolDisplayPartKind.stringLiteral), + ], + documentation: emptyArray, + kind: ScriptElementKind.constElement, + kindModifiers: ScriptElementKindModifier.exportedModifier, + name: "foo", + source: [{ text: "./a", kind: "text" }], + sourceDisplay: [{ text: "./a", kind: "text" }], + }; + assert.deepEqual(detailsResponse, [ + { + codeActions: [ + { + description: `Import 'foo' from module "./a"`, + changes: [ + { + fileName: "/b.ts", + textChanges: [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: 'import { foo } from "./a";\n\n', + }, + ], + }, + ], + commands: undefined, + }, ], - documentation: emptyArray, - kind: ScriptElementKind.constElement, - kindModifiers: ScriptElementKindModifier.exportedModifier, - name: "foo", - source: [{ text: "./a", kind: "text" }], - sourceDisplay: [{ text: "./a", kind: "text" }], - }; - assert.deepEqual(detailsResponse, [ - { - codeActions: [ - { - description: `Import 'foo' from module "./a"`, - changes: [ - { - fileName: "/b.ts", - textChanges: [ - { - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - newText: 'import { foo } from "./a";\n\n', - }, - ], - }, - ], - commands: undefined, - }, - ], - tags: [], - ...detailsCommon, - }, - ]); + tags: [], + ...detailsCommon, + }, + ]); - interface CompletionDetailsFullRequest extends protocol.FileLocationRequest { - readonly command: protocol.CommandTypes.CompletionDetailsFull; - readonly arguments: protocol.CompletionDetailsRequestArgs; - } - interface CompletionDetailsFullResponse extends protocol.Response { - readonly body?: readonly CompletionEntryDetails[]; + interface CompletionDetailsFullRequest extends protocol.FileLocationRequest { + readonly command: protocol.CommandTypes.CompletionDetailsFull; + readonly arguments: protocol.CompletionDetailsRequestArgs; + } + interface CompletionDetailsFullResponse extends protocol.Response { + readonly body?: readonly CompletionEntryDetails[]; + } + const detailsFullResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs); + assert.deepEqual(detailsFullResponse, [ + { + codeActions: [ + { + description: `Import 'foo' from module "./a"`, + changes: [ + { + fileName: "/b.ts", + textChanges: [createTextChange(createTextSpan(0, 0), 'import { foo } from "./a";\n\n')], + }, + ], + commands: undefined, + } + ], + tags: [], + ...detailsCommon, } - const detailsFullResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs); - assert.deepEqual(detailsFullResponse, [ - { - codeActions: [ - { - description: `Import 'foo' from module "./a"`, - changes: [ - { - fileName: "/b.ts", - textChanges: [createTextChange(createTextSpan(0, 0), 'import { foo } from "./a";\n\n')], - }, - ], - commands: undefined, - } - ], - tags: [], - ...detailsCommon, - } - ]); - }); + ]); + }); - it("works when files are included from two different drives of windows", () => { - const projectRoot = "e:/myproject"; - const appPackage: File = { - path: `${projectRoot}/package.json`, - content: JSON.stringify({ - name: "test", - version: "0.1.0", - dependencies: { - "react": "^16.12.0", - "react-router-dom": "^5.1.2", - } - }) - }; - const appFile: File = { - path: `${projectRoot}/src/app.js`, - content: `import React from 'react'; + it("works when files are included from two different drives of windows", () => { + const projectRoot = "e:/myproject"; + const appPackage: File = { + path: `${projectRoot}/package.json`, + content: JSON.stringify({ + name: "test", + version: "0.1.0", + dependencies: { + "react": "^16.12.0", + "react-router-dom": "^5.1.2", + } + }) + }; + const appFile: File = { + path: `${projectRoot}/src/app.js`, + content: `import React from 'react'; import { BrowserRouter as Router, } from "react-router-dom"; ` - }; - const localNodeModules = `${projectRoot}/node_modules`; - const localAtTypes = `${localNodeModules}/@types`; - const localReactPackage: File = { - path: `${localAtTypes}/react/package.json`, - content: JSON.stringify({ - name: "@types/react", - version: "16.9.14", - }) - }; - const localReact: File = { - path: `${localAtTypes}/react/index.d.ts`, - content: `import * as PropTypes from 'prop-types'; + }; + const localNodeModules = `${projectRoot}/node_modules`; + const localAtTypes = `${localNodeModules}/@types`; + const localReactPackage: File = { + path: `${localAtTypes}/react/package.json`, + content: JSON.stringify({ + name: "@types/react", + version: "16.9.14", + }) + }; + const localReact: File = { + path: `${localAtTypes}/react/index.d.ts`, + content: `import * as PropTypes from 'prop-types'; ` - }; - const localReactRouterDomPackage: File = { - path: `${localNodeModules}/react-router-dom/package.json`, - content: JSON.stringify({ - name: "react-router-dom", - version: "5.1.2", - }) - }; - const localReactRouterDom: File = { - path: `${localNodeModules}/react-router-dom/index.js`, - content: `export function foo() {}` - }; - const localPropTypesPackage: File = { - path: `${localAtTypes}/prop-types/package.json`, - content: JSON.stringify({ - name: "@types/prop-types", - version: "15.7.3", - }) - }; - const localPropTypes: File = { - path: `${localAtTypes}/prop-types/index.d.ts`, - content: `export type ReactComponentLike = + }; + const localReactRouterDomPackage: File = { + path: `${localNodeModules}/react-router-dom/package.json`, + content: JSON.stringify({ + name: "react-router-dom", + version: "5.1.2", + }) + }; + const localReactRouterDom: File = { + path: `${localNodeModules}/react-router-dom/index.js`, + content: `export function foo() {}` + }; + const localPropTypesPackage: File = { + path: `${localAtTypes}/prop-types/package.json`, + content: JSON.stringify({ + name: "@types/prop-types", + version: "15.7.3", + }) + }; + const localPropTypes: File = { + path: `${localAtTypes}/prop-types/index.d.ts`, + content: `export type ReactComponentLike = | string | ((props: any, context?: any) => any) | (new (props: any, context?: any) => any); ` - }; + }; - const globalCacheLocation = `c:/typescript`; - const globalAtTypes = `${globalCacheLocation}/node_modules/@types`; - const globalReactRouterDomPackage: File = { - path: `${globalAtTypes}/react-router-dom/package.json`, - content: JSON.stringify({ - name: "@types/react-router-dom", - version: "5.1.2", - }) - }; - const globalReactRouterDom: File = { - path: `${globalAtTypes}/react-router-dom/index.d.ts`, - content: `import * as React from 'react'; + const globalCacheLocation = `c:/typescript`; + const globalAtTypes = `${globalCacheLocation}/node_modules/@types`; + const globalReactRouterDomPackage: File = { + path: `${globalAtTypes}/react-router-dom/package.json`, + content: JSON.stringify({ + name: "@types/react-router-dom", + version: "5.1.2", + }) + }; + const globalReactRouterDom: File = { + path: `${globalAtTypes}/react-router-dom/index.d.ts`, + content: `import * as React from 'react'; export interface BrowserRouterProps { basename?: string; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void); forceRefresh?: boolean; keyLength?: number; }` - }; - const globalReactPackage: File = { - path: `${globalAtTypes}/react/package.json`, - content: localReactPackage.content - }; - const globalReact: File = { - path: `${globalAtTypes}/react/index.d.ts`, - content: localReact.content - }; + }; + const globalReactPackage: File = { + path: `${globalAtTypes}/react/package.json`, + content: localReactPackage.content + }; + const globalReact: File = { + path: `${globalAtTypes}/react/index.d.ts`, + content: localReact.content + }; - const filesInProject = [ - appFile, - localReact, - localPropTypes, - globalReactRouterDom, - globalReact, - ]; - const files = [ - ...filesInProject, - appPackage, libFile, - localReactPackage, - localReactRouterDomPackage, localReactRouterDom, - localPropTypesPackage, - globalReactRouterDomPackage, - globalReactPackage, - ]; + const filesInProject = [ + appFile, + localReact, + localPropTypes, + globalReactRouterDom, + globalReact, + ]; + const files = [ + ...filesInProject, + appPackage, libFile, + localReactPackage, + localReactRouterDomPackage, localReactRouterDom, + localPropTypesPackage, + globalReactRouterDomPackage, + globalReactPackage, + ]; - const host = createServerHost(files, { windowsStyleRoot: "c:/" }); - const session = createSession(host, { - typingsInstaller: new TestTypingsInstaller(globalCacheLocation, /*throttleLimit*/ 5, host), - }); - const service = session.getProjectService(); - openFilesForSession([appFile], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const windowsStyleLibFilePath = "c:/" + libFile.path.substring(1); - checkProjectActualFiles(service.inferredProjects[0], filesInProject.map(f => f.path).concat(windowsStyleLibFilePath)); - session.executeCommandSeq({ - command: protocol.CommandTypes.CompletionInfo, - arguments: { - file: appFile.path, - line: 5, - offset: 1, - includeExternalModuleExports: true, - includeInsertTextCompletions: true - } - }); + const host = createServerHost(files, { windowsStyleRoot: "c:/" }); + const session = createSession(host, { + typingsInstaller: new TestTypingsInstaller(globalCacheLocation, /*throttleLimit*/ 5, host), + }); + const service = session.getProjectService(); + openFilesForSession([appFile], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + const windowsStyleLibFilePath = "c:/" + libFile.path.substring(1); + checkProjectActualFiles(service.inferredProjects[0], filesInProject.map(f => f.path).concat(windowsStyleLibFilePath)); + session.executeCommandSeq({ + command: protocol.CommandTypes.CompletionInfo, + arguments: { + file: appFile.path, + line: 5, + offset: 1, + includeExternalModuleExports: true, + includeInsertTextCompletions: true + } }); }); +}); } diff --git a/src/testRunner/unittests/tsserver/completionsIncomplete.ts b/src/testRunner/unittests/tsserver/completionsIncomplete.ts index ae9c53cbc9289..3a6d0f0d5e946 100644 --- a/src/testRunner/unittests/tsserver/completionsIncomplete.ts +++ b/src/testRunner/unittests/tsserver/completionsIncomplete.ts @@ -1,262 +1,262 @@ namespace ts.projectSystem { - function createExportingModuleFile(path: string, exportPrefix: string, exportCount: number): File { - return { - path, - content: fill(exportCount, i => `export const ${exportPrefix}_${i} = ${i};`).join("\n"), - }; - } +function createExportingModuleFile(path: string, exportPrefix: string, exportCount: number): File { + return { + path, + content: fill(exportCount, i => `export const ${exportPrefix}_${i} = ${i};`).join("\n"), + }; +} - function createExportingModuleFiles(pathPrefix: string, fileCount: number, exportCount: number, getExportPrefix: (fileIndex: number) => string): File[] { - return fill(fileCount, fileIndex => createExportingModuleFile( - `${pathPrefix}_${fileIndex}.ts`, - getExportPrefix(fileIndex), - exportCount)); - } +function createExportingModuleFiles(pathPrefix: string, fileCount: number, exportCount: number, getExportPrefix: (fileIndex: number) => string): File[] { + return fill(fileCount, fileIndex => createExportingModuleFile( + `${pathPrefix}_${fileIndex}.ts`, + getExportPrefix(fileIndex), + exportCount)); +} - function createNodeModulesPackage(packageName: string, fileCount: number, exportCount: number, getExportPrefix: (fileIndex: number) => string): File[] { - const exportingFiles = createExportingModuleFiles(`/node_modules/${packageName}/file`, fileCount, exportCount, getExportPrefix); - return [ - { - path: `/node_modules/${packageName}/package.json`, - content: `{ "types": "index.d.ts" }`, - }, - { - path: `/node_modules/${packageName}/index.d.ts`, - content: exportingFiles - .map(f => `export * from "./${removeFileExtension(convertToRelativePath(f.path, `/node_modules/${packageName}/`, identity))}";`) - .join("\n") + `\nexport default function main(): void;`, - }, - ...exportingFiles, - ]; - } +function createNodeModulesPackage(packageName: string, fileCount: number, exportCount: number, getExportPrefix: (fileIndex: number) => string): File[] { + const exportingFiles = createExportingModuleFiles(`/node_modules/${packageName}/file`, fileCount, exportCount, getExportPrefix); + return [ + { + path: `/node_modules/${packageName}/package.json`, + content: `{ "types": "index.d.ts" }`, + }, + { + path: `/node_modules/${packageName}/index.d.ts`, + content: exportingFiles + .map(f => `export * from "./${removeFileExtension(convertToRelativePath(f.path, `/node_modules/${packageName}/`, identity))}";`) + .join("\n") + `\nexport default function main(): void;`, + }, + ...exportingFiles, + ]; +} - const indexFile: File = { - path: "/index.ts", - content: "" - }; +const indexFile: File = { + path: "/index.ts", + content: "" +}; - const tsconfigFile: File = { - path: "/tsconfig.json", - content: `{ "compilerOptions": { "module": "commonjs" } }` - }; +const tsconfigFile: File = { + path: "/tsconfig.json", + content: `{ "compilerOptions": { "module": "commonjs" } }` +}; - const packageJsonFile: File = { - path: "/package.json", - content: `{ "dependencies": { "dep-a": "*" } }`, - }; +const packageJsonFile: File = { + path: "/package.json", + content: `{ "dependencies": { "dep-a": "*" } }`, +}; - describe("unittests:: tsserver:: completionsIncomplete", () => { - it("works", () => { - const excessFileCount = Completions.moduleSpecifierResolutionLimit + 50; - const exportingFiles = createExportingModuleFiles(`/lib/a`, Completions.moduleSpecifierResolutionLimit + excessFileCount, 1, i => `aa_${i}_`); - const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]); - openFilesForSession([indexFile], session); +describe("unittests:: tsserver:: completionsIncomplete", () => { + it("works", () => { + const excessFileCount = Completions.moduleSpecifierResolutionLimit + 50; + const exportingFiles = createExportingModuleFiles(`/lib/a`, Completions.moduleSpecifierResolutionLimit + excessFileCount, 1, i => `aa_${i}_`); + const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]); + openFilesForSession([indexFile], session); - typeToTriggerCompletions(indexFile.path, "a", completions => { - assert(completions.isIncomplete); - assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), Completions.moduleSpecifierResolutionLimit); - assert.lengthOf(completions.entries.filter(entry => entry.source && !(entry.data as any)?.moduleSpecifier), excessFileCount); - }) - .continueTyping("a", completions => { - assert(completions.isIncomplete); - assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), Completions.moduleSpecifierResolutionLimit * 2); - }) - .continueTyping("_", completions => { - assert(!completions.isIncomplete); - assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), exportingFiles.length); - }); + typeToTriggerCompletions(indexFile.path, "a", completions => { + assert(completions.isIncomplete); + assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), Completions.moduleSpecifierResolutionLimit); + assert.lengthOf(completions.entries.filter(entry => entry.source && !(entry.data as any)?.moduleSpecifier), excessFileCount); + }) + .continueTyping("a", completions => { + assert(completions.isIncomplete); + assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), Completions.moduleSpecifierResolutionLimit * 2); + }) + .continueTyping("_", completions => { + assert(!completions.isIncomplete); + assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), exportingFiles.length); }); + }); - it("resolves more when available from module specifier cache (1)", () => { - const exportingFiles = createExportingModuleFiles(`/lib/a`, 50, 50, i => `aa_${i}_`); - const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]); - openFilesForSession([indexFile], session); + it("resolves more when available from module specifier cache (1)", () => { + const exportingFiles = createExportingModuleFiles(`/lib/a`, 50, 50, i => `aa_${i}_`); + const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]); + openFilesForSession([indexFile], session); - typeToTriggerCompletions(indexFile.path, "a", completions => { - assert(!completions.isIncomplete); - }); + typeToTriggerCompletions(indexFile.path, "a", completions => { + assert(!completions.isIncomplete); }); + }); - it("resolves more when available from module specifier cache (2)", () => { - const excessFileCount = 50; - const exportingFiles = createExportingModuleFiles(`/lib/a`, Completions.moduleSpecifierResolutionLimit + excessFileCount, 1, i => `aa_${i}_`); - const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]); - openFilesForSession([indexFile], session); + it("resolves more when available from module specifier cache (2)", () => { + const excessFileCount = 50; + const exportingFiles = createExportingModuleFiles(`/lib/a`, Completions.moduleSpecifierResolutionLimit + excessFileCount, 1, i => `aa_${i}_`); + const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]); + openFilesForSession([indexFile], session); - typeToTriggerCompletions(indexFile.path, "a", completions => assert(completions.isIncomplete)) - .backspace() - .type("a", completions => assert(!completions.isIncomplete)); - }); + typeToTriggerCompletions(indexFile.path, "a", completions => assert(completions.isIncomplete)) + .backspace() + .type("a", completions => assert(!completions.isIncomplete)); + }); - it("ambient module specifier resolutions do not count against the resolution limit", () => { - const ambientFiles = fill(100, (i): File => ({ - path: `/lib/ambient_${i}.ts`, - content: `declare module "ambient_${i}" { export const aa_${i} = ${i}; }`, - })); + it("ambient module specifier resolutions do not count against the resolution limit", () => { + const ambientFiles = fill(100, (i): File => ({ + path: `/lib/ambient_${i}.ts`, + content: `declare module "ambient_${i}" { export const aa_${i} = ${i}; }`, + })); - const exportingFiles = createExportingModuleFiles(`/lib/a`, Completions.moduleSpecifierResolutionLimit, 5, i => `aa_${i}_`); - const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...ambientFiles, ...exportingFiles]); - openFilesForSession([indexFile], session); + const exportingFiles = createExportingModuleFiles(`/lib/a`, Completions.moduleSpecifierResolutionLimit, 5, i => `aa_${i}_`); + const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...ambientFiles, ...exportingFiles]); + openFilesForSession([indexFile], session); - typeToTriggerCompletions(indexFile.path, "a", completions => { - assert(!completions.isIncomplete); - assert.lengthOf(completions.entries.filter(e => (e.data as any)?.moduleSpecifier), ambientFiles.length * 5 + exportingFiles.length); - }); + typeToTriggerCompletions(indexFile.path, "a", completions => { + assert(!completions.isIncomplete); + assert.lengthOf(completions.entries.filter(e => (e.data as any)?.moduleSpecifier), ambientFiles.length * 5 + exportingFiles.length); }); + }); - it("works with PackageJsonAutoImportProvider", () => { - const exportingFiles = createExportingModuleFiles(`/lib/a`, Completions.moduleSpecifierResolutionLimit, 1, i => `aa_${i}_`); - const nodeModulesPackage = createNodeModulesPackage("dep-a", 50, 1, i => `depA_${i}_`); - const { typeToTriggerCompletions, assertCompletionDetailsOk, session } = setup([tsconfigFile, packageJsonFile, indexFile, ...exportingFiles, ...nodeModulesPackage]); - openFilesForSession([indexFile], session); + it("works with PackageJsonAutoImportProvider", () => { + const exportingFiles = createExportingModuleFiles(`/lib/a`, Completions.moduleSpecifierResolutionLimit, 1, i => `aa_${i}_`); + const nodeModulesPackage = createNodeModulesPackage("dep-a", 50, 1, i => `depA_${i}_`); + const { typeToTriggerCompletions, assertCompletionDetailsOk, session } = setup([tsconfigFile, packageJsonFile, indexFile, ...exportingFiles, ...nodeModulesPackage]); + openFilesForSession([indexFile], session); - typeToTriggerCompletions(indexFile.path, "a", completions => assert(completions.isIncomplete)) - .continueTyping("_", completions => { - assert(!completions.isIncomplete); - assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier?.startsWith("dep-a")), 50); - assertCompletionDetailsOk( - indexFile.path, - completions.entries.find(entry => (entry.data as any)?.moduleSpecifier?.startsWith("dep-a"))!); - }); - }); + typeToTriggerCompletions(indexFile.path, "a", completions => assert(completions.isIncomplete)) + .continueTyping("_", completions => { + assert(!completions.isIncomplete); + assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier?.startsWith("dep-a")), 50); + assertCompletionDetailsOk( + indexFile.path, + completions.entries.find(entry => (entry.data as any)?.moduleSpecifier?.startsWith("dep-a"))!); + }); + }); - it("works for transient symbols between requests", () => { - const constantsDts: File = { - path: "/lib/foo/constants.d.ts", - content: ` + it("works for transient symbols between requests", () => { + const constantsDts: File = { + path: "/lib/foo/constants.d.ts", + content: ` type Signals = "SIGINT" | "SIGABRT"; declare const exp: {} & { [K in Signals]: K }; export = exp;`, - }; - const exportingFiles = createExportingModuleFiles("/lib/a", Completions.moduleSpecifierResolutionLimit, 1, i => `S${i}`); - const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles, constantsDts]); - openFilesForSession([indexFile], session); + }; + const exportingFiles = createExportingModuleFiles("/lib/a", Completions.moduleSpecifierResolutionLimit, 1, i => `S${i}`); + const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles, constantsDts]); + openFilesForSession([indexFile], session); - typeToTriggerCompletions(indexFile.path, "s", completions => { - const sigint = completions.entries.find(e => e.name === "SIGINT"); - assert(sigint); - assert(!(sigint!.data as any).moduleSpecifier); - }) - .continueTyping("i", completions => { - const sigint = completions.entries.find(e => e.name === "SIGINT"); - assert((sigint!.data as any).moduleSpecifier); - }); + typeToTriggerCompletions(indexFile.path, "s", completions => { + const sigint = completions.entries.find(e => e.name === "SIGINT"); + assert(sigint); + assert(!(sigint!.data as any).moduleSpecifier); + }) + .continueTyping("i", completions => { + const sigint = completions.entries.find(e => e.name === "SIGINT"); + assert((sigint!.data as any).moduleSpecifier); }); }); +}); - function setup(files: File[]) { - const host = createServerHost(files); - const session = createSession(host); - const projectService = session.getProjectService(); - session.executeCommandSeq({ - command: protocol.CommandTypes.Configure, - arguments: { - preferences: { - allowIncompleteCompletions: true, - includeCompletionsForModuleExports: true, - includeCompletionsWithInsertText: true, - includeCompletionsForImportStatements: true, - includePackageJsonAutoImports: "auto", - } +function setup(files: File[]) { + const host = createServerHost(files); + const session = createSession(host); + const projectService = session.getProjectService(); + session.executeCommandSeq({ + command: protocol.CommandTypes.Configure, + arguments: { + preferences: { + allowIncompleteCompletions: true, + includeCompletionsForModuleExports: true, + includeCompletionsWithInsertText: true, + includeCompletionsForImportStatements: true, + includePackageJsonAutoImports: "auto", } - }); + } + }); - return { host, session, projectService, typeToTriggerCompletions, assertCompletionDetailsOk }; + return { host, session, projectService, typeToTriggerCompletions, assertCompletionDetailsOk }; - function typeToTriggerCompletions(fileName: string, typedCharacters: string, cb?: (completions: protocol.CompletionInfo) => void) { - const project = projectService.getDefaultProjectForFile(server.toNormalizedPath(fileName), /*ensureProject*/ true)!; - return type(typedCharacters, cb, /*isIncompleteContinuation*/ false); + function typeToTriggerCompletions(fileName: string, typedCharacters: string, cb?: (completions: protocol.CompletionInfo) => void) { + const project = projectService.getDefaultProjectForFile(server.toNormalizedPath(fileName), /*ensureProject*/ true)!; + return type(typedCharacters, cb, /*isIncompleteContinuation*/ false); - function type(typedCharacters: string, cb: ((completions: protocol.CompletionInfo) => void) | undefined, isIncompleteContinuation: boolean) { - const file = Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName)); - const { line, character } = getLineAndCharacterOfPosition(file, file.text.length); - const oneBasedEditPosition = { line: line + 1, offset: character + 1 }; - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName, - textChanges: [{ - newText: typedCharacters, - start: oneBasedEditPosition, - end: oneBasedEditPosition, - }], + function type(typedCharacters: string, cb: ((completions: protocol.CompletionInfo) => void) | undefined, isIncompleteContinuation: boolean) { + const file = Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName)); + const { line, character } = getLineAndCharacterOfPosition(file, file.text.length); + const oneBasedEditPosition = { line: line + 1, offset: character + 1 }; + session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName, + textChanges: [{ + newText: typedCharacters, + start: oneBasedEditPosition, + end: oneBasedEditPosition, }], - }, - }); - - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.CompletionInfo, - arguments: { - file: fileName, - line: oneBasedEditPosition.line, - offset: oneBasedEditPosition.offset, - triggerKind: isIncompleteContinuation - ? protocol.CompletionTriggerKind.TriggerForIncompleteCompletions - : undefined, - } - }).response as protocol.CompletionInfo; - - cb?.(Debug.checkDefined(response)); - return { - backspace, - continueTyping: (typedCharacters: string, cb: (completions: protocol.CompletionInfo) => void) => { - return type(typedCharacters, cb, !!response.isIncomplete); - }, - }; - } + }], + }, + }); - function backspace(n = 1) { - const file = Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName)); - const startLineCharacter = getLineAndCharacterOfPosition(file, file.text.length - n); - const endLineCharacter = getLineAndCharacterOfPosition(file, file.text.length); - const oneBasedStartPosition = { line: startLineCharacter.line + 1, offset: startLineCharacter.character + 1 }; - const oneBasedEndPosition = { line: endLineCharacter.line + 1, offset: endLineCharacter.character + 1 }; - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName, - textChanges: [{ - newText: "", - start: oneBasedStartPosition, - end: oneBasedEndPosition, - }], - }], - }, - }); + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.CompletionInfo, + arguments: { + file: fileName, + line: oneBasedEditPosition.line, + offset: oneBasedEditPosition.offset, + triggerKind: isIncompleteContinuation + ? protocol.CompletionTriggerKind.TriggerForIncompleteCompletions + : undefined, + } + }).response as protocol.CompletionInfo; - return { - backspace, - type: (typedCharacters: string, cb: (completions: protocol.CompletionInfo) => void) => { - return type(typedCharacters, cb, /*isIncompleteContinuation*/ false); - }, - }; - } + cb?.(Debug.checkDefined(response)); + return { + backspace, + continueTyping: (typedCharacters: string, cb: (completions: protocol.CompletionInfo) => void) => { + return type(typedCharacters, cb, !!response.isIncomplete); + }, + }; } - function assertCompletionDetailsOk(fileName: string, entry: protocol.CompletionEntry) { - const project = projectService.getDefaultProjectForFile(server.toNormalizedPath(fileName), /*ensureProject*/ true)!; + function backspace(n = 1) { const file = Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName)); - const { line, character } = getLineAndCharacterOfPosition(file, file.text.length - 1); - const details = session.executeCommandSeq({ - command: protocol.CommandTypes.CompletionDetails, + const startLineCharacter = getLineAndCharacterOfPosition(file, file.text.length - n); + const endLineCharacter = getLineAndCharacterOfPosition(file, file.text.length); + const oneBasedStartPosition = { line: startLineCharacter.line + 1, offset: startLineCharacter.character + 1 }; + const oneBasedEndPosition = { line: endLineCharacter.line + 1, offset: endLineCharacter.character + 1 }; + session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, arguments: { - file: fileName, - line: line + 1, - offset: character + 1, - entryNames: [{ - name: entry.name, - source: entry.source, - data: entry.data, - }] - } - }).response as protocol.CompletionEntryDetails[]; + changedFiles: [{ + fileName, + textChanges: [{ + newText: "", + start: oneBasedStartPosition, + end: oneBasedEndPosition, + }], + }], + }, + }); - assert(details[0]); - assert(details[0].codeActions); - assert(details[0].codeActions![0].changes[0].textChanges[0].newText.includes(`"${(entry.data as any).moduleSpecifier}"`)); - return details; + return { + backspace, + type: (typedCharacters: string, cb: (completions: protocol.CompletionInfo) => void) => { + return type(typedCharacters, cb, /*isIncompleteContinuation*/ false); + }, + }; } } + + function assertCompletionDetailsOk(fileName: string, entry: protocol.CompletionEntry) { + const project = projectService.getDefaultProjectForFile(server.toNormalizedPath(fileName), /*ensureProject*/ true)!; + const file = Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName)); + const { line, character } = getLineAndCharacterOfPosition(file, file.text.length - 1); + const details = session.executeCommandSeq({ + command: protocol.CommandTypes.CompletionDetails, + arguments: { + file: fileName, + line: line + 1, + offset: character + 1, + entryNames: [{ + name: entry.name, + source: entry.source, + data: entry.data, + }] + } + }).response as protocol.CompletionEntryDetails[]; + + assert(details[0]); + assert(details[0].codeActions); + assert(details[0].codeActions![0].changes[0].textChanges[0].newText.includes(`"${(entry.data as any).moduleSpecifier}"`)); + return details; + } +} } diff --git a/src/testRunner/unittests/tsserver/configFileSearch.ts b/src/testRunner/unittests/tsserver/configFileSearch.ts index 152184e9ae5d3..890caa39a2788 100644 --- a/src/testRunner/unittests/tsserver/configFileSearch.ts +++ b/src/testRunner/unittests/tsserver/configFileSearch.ts @@ -1,188 +1,188 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: searching for config file", () => { - it("should stop at projectRootPath if given", () => { - const f1 = { - path: "/a/file1.ts", - content: "" - }; - const configFile = { - path: "/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, configFile]); - const service = createProjectService(host); - service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a"); - - checkNumberOfConfiguredProjects(service, 0); - checkNumberOfInferredProjects(service, 1); - - service.closeClientFile(f1.path); - service.openClientFile(f1.path); - checkNumberOfConfiguredProjects(service, 1); - checkNumberOfInferredProjects(service, 0); - }); +describe("unittests:: tsserver:: searching for config file", () => { + it("should stop at projectRootPath if given", () => { + const f1 = { + path: "/a/file1.ts", + content: "" + }; + const configFile = { + path: "/tsconfig.json", + content: "{}" + }; + const host = createServerHost([f1, configFile]); + const service = createProjectService(host); + service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a"); + + checkNumberOfConfiguredProjects(service, 0); + checkNumberOfInferredProjects(service, 1); + + service.closeClientFile(f1.path); + service.openClientFile(f1.path); + checkNumberOfConfiguredProjects(service, 1); + checkNumberOfInferredProjects(service, 0); + }); - it("should use projectRootPath when searching for inferred project again", () => { - const projectDir = "/a/b/projects/project"; - const configFileLocation = `${projectDir}/src`; - const f1 = { - path: `${configFileLocation}/file1.ts`, - content: "" - }; - const configFile = { - path: `${configFileLocation}/tsconfig.json`, - content: "{}" - }; - const configFile2 = { - path: "/a/b/projects/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, libFile, configFile, configFile2]); - const service = createProjectService(host); - service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); - checkNumberOfProjects(service, { configuredProjects: 1 }); - assert.isDefined(service.configuredProjects.get(configFile.path)); - checkWatchedFiles(host, [libFile.path, configFile.path]); - checkWatchedDirectories(host, [], /*recursive*/ false); - const typeRootLocations = getTypeRootsFromLocation(configFileLocation); - checkWatchedDirectories(host, typeRootLocations.concat(configFileLocation), /*recursive*/ true); - - // Delete config file - should create inferred project and not configured project - host.deleteFile(configFile.path); - host.runQueuedTimeoutCallbacks(); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, typeRootLocations, /*recursive*/ true); - }); + it("should use projectRootPath when searching for inferred project again", () => { + const projectDir = "/a/b/projects/project"; + const configFileLocation = `${projectDir}/src`; + const f1 = { + path: `${configFileLocation}/file1.ts`, + content: "" + }; + const configFile = { + path: `${configFileLocation}/tsconfig.json`, + content: "{}" + }; + const configFile2 = { + path: "/a/b/projects/tsconfig.json", + content: "{}" + }; + const host = createServerHost([f1, libFile, configFile, configFile2]); + const service = createProjectService(host); + service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); + checkNumberOfProjects(service, { configuredProjects: 1 }); + assert.isDefined(service.configuredProjects.get(configFile.path)); + checkWatchedFiles(host, [libFile.path, configFile.path]); + checkWatchedDirectories(host, [], /*recursive*/ false); + const typeRootLocations = getTypeRootsFromLocation(configFileLocation); + checkWatchedDirectories(host, typeRootLocations.concat(configFileLocation), /*recursive*/ true); + + // Delete config file - should create inferred project and not configured project + host.deleteFile(configFile.path); + host.runQueuedTimeoutCallbacks(); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, typeRootLocations, /*recursive*/ true); + }); - it("should use projectRootPath when searching for inferred project again 2", () => { - const projectDir = "/a/b/projects/project"; - const configFileLocation = `${projectDir}/src`; - const f1 = { - path: `${configFileLocation}/file1.ts`, - content: "" - }; - const configFile = { - path: `${configFileLocation}/tsconfig.json`, - content: "{}" - }; - const configFile2 = { - path: "/a/b/projects/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, libFile, configFile, configFile2]); - const service = createProjectService(host, { useSingleInferredProject: true, useInferredProjectPerProjectRoot: true }); - service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); - checkNumberOfProjects(service, { configuredProjects: 1 }); - assert.isDefined(service.configuredProjects.get(configFile.path)); - checkWatchedFiles(host, [libFile.path, configFile.path]); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, getTypeRootsFromLocation(configFileLocation).concat(configFileLocation), /*recursive*/ true); - - // Delete config file - should create inferred project with project root path set - host.deleteFile(configFile.path); - host.runQueuedTimeoutCallbacks(); - checkNumberOfProjects(service, { inferredProjects: 1 }); - assert.equal(service.inferredProjects[0].projectRootPath, projectDir); - checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, getTypeRootsFromLocation(projectDir), /*recursive*/ true); - }); + it("should use projectRootPath when searching for inferred project again 2", () => { + const projectDir = "/a/b/projects/project"; + const configFileLocation = `${projectDir}/src`; + const f1 = { + path: `${configFileLocation}/file1.ts`, + content: "" + }; + const configFile = { + path: `${configFileLocation}/tsconfig.json`, + content: "{}" + }; + const configFile2 = { + path: "/a/b/projects/tsconfig.json", + content: "{}" + }; + const host = createServerHost([f1, libFile, configFile, configFile2]); + const service = createProjectService(host, { useSingleInferredProject: true, useInferredProjectPerProjectRoot: true }); + service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); + checkNumberOfProjects(service, { configuredProjects: 1 }); + assert.isDefined(service.configuredProjects.get(configFile.path)); + checkWatchedFiles(host, [libFile.path, configFile.path]); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, getTypeRootsFromLocation(configFileLocation).concat(configFileLocation), /*recursive*/ true); + + // Delete config file - should create inferred project with project root path set + host.deleteFile(configFile.path); + host.runQueuedTimeoutCallbacks(); + checkNumberOfProjects(service, { inferredProjects: 1 }); + assert.equal(service.inferredProjects[0].projectRootPath, projectDir); + checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, getTypeRootsFromLocation(projectDir), /*recursive*/ true); + }); - describe("when the opened file is not from project root", () => { - const projectRoot = "/a/b/projects/project"; - const file: File = { - path: `${projectRoot}/src/index.ts`, - content: "let y = 10" - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - const dirOfFile = getDirectoryPath(file.path); - - function openClientFile(files: File[]) { - const host = createServerHost(files); - const projectService = createProjectService(host); - - projectService.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a/b/projects/proj"); - return { host, projectService }; + describe("when the opened file is not from project root", () => { + const projectRoot = "/a/b/projects/project"; + const file: File = { + path: `${projectRoot}/src/index.ts`, + content: "let y = 10" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: "{}" + }; + const dirOfFile = getDirectoryPath(file.path); + + function openClientFile(files: File[]) { + const host = createServerHost(files); + const projectService = createProjectService(host); + + projectService.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a/b/projects/proj"); + return { host, projectService }; + } + + function verifyConfiguredProject(host: TestServerHost, projectService: TestProjectService, orphanInferredProject?: boolean) { + projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: orphanInferredProject ? 1 : 0 }); + const project = Debug.checkDefined(projectService.configuredProjects.get(tsconfig.path)); + + if (orphanInferredProject) { + const inferredProject = projectService.inferredProjects[0]; + assert.isTrue(inferredProject.isOrphan()); } - function verifyConfiguredProject(host: TestServerHost, projectService: TestProjectService, orphanInferredProject?: boolean) { - projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: orphanInferredProject ? 1 : 0 }); - const project = Debug.checkDefined(projectService.configuredProjects.get(tsconfig.path)); + checkProjectActualFiles(project, [file.path, libFile.path, tsconfig.path]); + checkWatchedFiles(host, [libFile.path, tsconfig.path]); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectories(host, (orphanInferredProject ? [projectRoot, `${dirOfFile}/node_modules/@types`] : [projectRoot]).concat(getTypeRootsFromLocation(projectRoot)), /*recursive*/ true); + } - if (orphanInferredProject) { - const inferredProject = projectService.inferredProjects[0]; - assert.isTrue(inferredProject.isOrphan()); - } + function verifyInferredProject(host: TestServerHost, projectService: TestProjectService) { + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + assert.isDefined(project); - checkProjectActualFiles(project, [file.path, libFile.path, tsconfig.path]); - checkWatchedFiles(host, [libFile.path, tsconfig.path]); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectories(host, (orphanInferredProject ? [projectRoot, `${dirOfFile}/node_modules/@types`] : [projectRoot]).concat(getTypeRootsFromLocation(projectRoot)), /*recursive*/ true); - } - - function verifyInferredProject(host: TestServerHost, projectService: TestProjectService) { - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - const project = projectService.inferredProjects[0]; - assert.isDefined(project); + const filesToWatch = [libFile.path, ...getConfigFilesToWatch(dirOfFile)]; - const filesToWatch = [libFile.path, ...getConfigFilesToWatch(dirOfFile)]; - - checkProjectActualFiles(project, [file.path, libFile.path]); - checkWatchedFiles(host, filesToWatch); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectories(host, getTypeRootsFromLocation(dirOfFile), /*recursive*/ true); - } + checkProjectActualFiles(project, [file.path, libFile.path]); + checkWatchedFiles(host, filesToWatch); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectories(host, getTypeRootsFromLocation(dirOfFile), /*recursive*/ true); + } - it("tsconfig for the file exists", () => { - const { host, projectService } = openClientFile([file, libFile, tsconfig]); - verifyConfiguredProject(host, projectService); + it("tsconfig for the file exists", () => { + const { host, projectService } = openClientFile([file, libFile, tsconfig]); + verifyConfiguredProject(host, projectService); - host.deleteFile(tsconfig.path); - host.runQueuedTimeoutCallbacks(); - verifyInferredProject(host, projectService); + host.deleteFile(tsconfig.path); + host.runQueuedTimeoutCallbacks(); + verifyInferredProject(host, projectService); - host.writeFile(tsconfig.path, tsconfig.content); - host.runQueuedTimeoutCallbacks(); - verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true); - }); + host.writeFile(tsconfig.path, tsconfig.content); + host.runQueuedTimeoutCallbacks(); + verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true); + }); - it("tsconfig for the file does not exist", () => { - const { host, projectService } = openClientFile([file, libFile]); - verifyInferredProject(host, projectService); + it("tsconfig for the file does not exist", () => { + const { host, projectService } = openClientFile([file, libFile]); + verifyInferredProject(host, projectService); - host.writeFile(tsconfig.path, tsconfig.content); - host.runQueuedTimeoutCallbacks(); - verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true); + host.writeFile(tsconfig.path, tsconfig.content); + host.runQueuedTimeoutCallbacks(); + verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true); - host.deleteFile(tsconfig.path); - host.runQueuedTimeoutCallbacks(); - verifyInferredProject(host, projectService); - }); + host.deleteFile(tsconfig.path); + host.runQueuedTimeoutCallbacks(); + verifyInferredProject(host, projectService); }); + }); - describe("should not search and watch config files from directories that cannot be watched", () => { - const root = "/root/teams/VSCode68/Shared Documents/General/jt-ts-test-workspace"; - function verifyConfigFileWatch(projectRootPath: string | undefined) { - const path = `${root}/x.js`; - const host = createServerHost([libFile, { path, content: "const x = 10" }], { useCaseSensitiveFileNames: true }); - const service = createProjectService(host); - service.openClientFile(path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRootPath); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(service.inferredProjects[0], [path, libFile.path]); - checkWatchedFilesDetailed(host, [libFile.path, ...getConfigFilesToWatch(root)], 1); - } + describe("should not search and watch config files from directories that cannot be watched", () => { + const root = "/root/teams/VSCode68/Shared Documents/General/jt-ts-test-workspace"; + function verifyConfigFileWatch(projectRootPath: string | undefined) { + const path = `${root}/x.js`; + const host = createServerHost([libFile, { path, content: "const x = 10" }], { useCaseSensitiveFileNames: true }); + const service = createProjectService(host); + service.openClientFile(path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRootPath); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkProjectActualFiles(service.inferredProjects[0], [path, libFile.path]); + checkWatchedFilesDetailed(host, [libFile.path, ...getConfigFilesToWatch(root)], 1); + } - it("when projectRootPath is not present", () => { - verifyConfigFileWatch(/*projectRootPath*/ undefined); - }); - it("when projectRootPath is present but file is not from project root", () => { - verifyConfigFileWatch("/a/b"); - }); + it("when projectRootPath is not present", () => { + verifyConfigFileWatch(/*projectRootPath*/ undefined); + }); + it("when projectRootPath is present but file is not from project root", () => { + verifyConfigFileWatch("/a/b"); }); }); +}); } diff --git a/src/testRunner/unittests/tsserver/configuredProjects.ts b/src/testRunner/unittests/tsserver/configuredProjects.ts index cd5bd4d81f306..20e7aafb4fd1f 100644 --- a/src/testRunner/unittests/tsserver/configuredProjects.ts +++ b/src/testRunner/unittests/tsserver/configuredProjects.ts @@ -1,1405 +1,1405 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: ConfiguredProjects", () => { - it("create configured project without file list", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: ` +describe("unittests:: tsserver:: ConfiguredProjects", () => { + it("create configured project without file list", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: ` { "compilerOptions": {}, "exclude": [ "e" ] }` - }; - const file1: File = { - path: "/a/b/c/f1.ts", - content: "let x = 1" - }; - const file2: File = { - path: "/a/b/d/f2.ts", - content: "let y = 1" - }; - const file3: File = { - path: "/a/b/e/f3.ts", - content: "let z = 1" - }; - - const host = createServerHost([configFile, libFile, file1, file2, file3]); - const projectService = createProjectService(host); - const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); - - assert(configFileName, "should find config file"); - assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); - checkNumberOfInferredProjects(projectService, 0); - checkNumberOfConfiguredProjects(projectService, 1); - - const project = configuredProjectAt(projectService, 0); - checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); - checkProjectRootFiles(project, [file1.path, file2.path]); - // watching all files except one that was open - checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); - const configFileDirectory = getDirectoryPath(configFile.path); - checkWatchedDirectories(host, [configFileDirectory, combinePaths(configFileDirectory, nodeModulesAtTypes)], /*recursive*/ true); - }); + }; + const file1: File = { + path: "/a/b/c/f1.ts", + content: "let x = 1" + }; + const file2: File = { + path: "/a/b/d/f2.ts", + content: "let y = 1" + }; + const file3: File = { + path: "/a/b/e/f3.ts", + content: "let z = 1" + }; + + const host = createServerHost([configFile, libFile, file1, file2, file3]); + const projectService = createProjectService(host); + const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); + + assert(configFileName, "should find config file"); + assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); + checkNumberOfInferredProjects(projectService, 0); + checkNumberOfConfiguredProjects(projectService, 1); + + const project = configuredProjectAt(projectService, 0); + checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); + checkProjectRootFiles(project, [file1.path, file2.path]); + // watching all files except one that was open + checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); + const configFileDirectory = getDirectoryPath(configFile.path); + checkWatchedDirectories(host, [configFileDirectory, combinePaths(configFileDirectory, nodeModulesAtTypes)], /*recursive*/ true); + }); - it("create configured project with the file list", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: ` + it("create configured project with the file list", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: ` { "compilerOptions": {}, "include": ["*.ts"] }` - }; - const file1: File = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2: File = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const file3: File = { - path: "/a/b/c/f3.ts", - content: "let z = 1" - }; - - const host = createServerHost([configFile, libFile, file1, file2, file3]); - const projectService = createProjectService(host); - const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); - - assert(configFileName, "should find config file"); - assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); - checkNumberOfInferredProjects(projectService, 0); - checkNumberOfConfiguredProjects(projectService, 1); - - const project = configuredProjectAt(projectService, 0); - checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); - checkProjectRootFiles(project, [file1.path, file2.path]); - // watching all files except one that was open - checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); - checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ false); - }); + }; + const file1: File = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2: File = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const file3: File = { + path: "/a/b/c/f3.ts", + content: "let z = 1" + }; + + const host = createServerHost([configFile, libFile, file1, file2, file3]); + const projectService = createProjectService(host); + const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); + + assert(configFileName, "should find config file"); + assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); + checkNumberOfInferredProjects(projectService, 0); + checkNumberOfConfiguredProjects(projectService, 1); + + const project = configuredProjectAt(projectService, 0); + checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); + checkProjectRootFiles(project, [file1.path, file2.path]); + // watching all files except one that was open + checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); + checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ false); + }); - it("add and then remove a config file in a folder with loose files", () => { - const configFile: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: `{ + it("add and then remove a config file in a folder with loose files", () => { + const configFile: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: `{ "files": ["commonFile1.ts"] }` - }; - const commonFile1: File = { - path: `${tscWatch.projectRoot}/commonFile1.ts`, - content: "let x = 1" - }; - const commonFile2: File = { - path: `${tscWatch.projectRoot}/commonFile2.ts`, - content: "let y = 1" - }; - - const host = createServerHost([libFile, commonFile1, commonFile2]); + }; + const commonFile1: File = { + path: `${tscWatch.projectRoot}/commonFile1.ts`, + content: "let x = 1" + }; + const commonFile2: File = { + path: `${tscWatch.projectRoot}/commonFile2.ts`, + content: "let y = 1" + }; - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - projectService.openClientFile(commonFile2.path); - - projectService.checkNumberOfProjects({ inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [commonFile1.path, libFile.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); + const host = createServerHost([libFile, commonFile1, commonFile2]); - const watchedFiles = getConfigFilesToWatch(tscWatch.projectRoot).concat(libFile.path); - checkWatchedFiles(host, watchedFiles); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + projectService.openClientFile(commonFile2.path); - // Add a tsconfig file - host.writeFile(configFile.path, configFile.content); - host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles + projectService.checkNumberOfProjects({ inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [commonFile1.path, libFile.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); - projectService.checkNumberOfProjects({ inferredProjects: 2, configuredProjects: 1 }); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); - checkProjectActualFiles(projectService.configuredProjects.get(configFile.path)!, [libFile.path, commonFile1.path, configFile.path]); + const watchedFiles = getConfigFilesToWatch(tscWatch.projectRoot).concat(libFile.path); + checkWatchedFiles(host, watchedFiles); - checkWatchedFiles(host, watchedFiles); + // Add a tsconfig file + host.writeFile(configFile.path, configFile.content); + host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles - // remove the tsconfig file - host.deleteFile(configFile.path); + projectService.checkNumberOfProjects({ inferredProjects: 2, configuredProjects: 1 }); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); + checkProjectActualFiles(projectService.configuredProjects.get(configFile.path)!, [libFile.path, commonFile1.path, configFile.path]); - projectService.checkNumberOfProjects({ inferredProjects: 2 }); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); + checkWatchedFiles(host, watchedFiles); - host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects + // remove the tsconfig file + host.deleteFile(configFile.path); - projectService.checkNumberOfProjects({ inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [commonFile1.path, libFile.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); - checkWatchedFiles(host, watchedFiles); - }); + projectService.checkNumberOfProjects({ inferredProjects: 2 }); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); - it("add new files to a configured project without file list", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{}` - }; - const host = createServerHost([commonFile1, libFile, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - const configFileDir = getDirectoryPath(configFile.path); - checkWatchedDirectories(host, [configFileDir, combinePaths(configFileDir, nodeModulesAtTypes)], /*recursive*/ true); - checkNumberOfConfiguredProjects(projectService, 1); + host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path]); + projectService.checkNumberOfProjects({ inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [commonFile1.path, libFile.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); + checkWatchedFiles(host, watchedFiles); + }); - // add a new ts file - host.writeFile(commonFile2.path, commonFile2.content); - host.checkTimeoutQueueLengthAndRun(2); - // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer. - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - }); + it("add new files to a configured project without file list", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createServerHost([commonFile1, libFile, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + const configFileDir = getDirectoryPath(configFile.path); + checkWatchedDirectories(host, [configFileDir, combinePaths(configFileDir, nodeModulesAtTypes)], /*recursive*/ true); + checkNumberOfConfiguredProjects(projectService, 1); + + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path]); + + // add a new ts file + host.writeFile(commonFile2.path, commonFile2.content); + host.checkTimeoutQueueLengthAndRun(2); + // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer. + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + }); - it("should ignore non-existing files specified in the config file", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should ignore non-existing files specified in the config file", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {}, "files": [ "commonFile1.ts", "commonFile3.ts" ] }` - }; - const host = createServerHost([commonFile1, commonFile2, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - projectService.openClientFile(commonFile2.path); - - checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path]); - checkNumberOfInferredProjects(projectService, 1); - }); - - it("handle recreated files correctly", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{}` - }; - const host = createServerHost([commonFile1, commonFile2, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - - checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - - // delete commonFile2 - host.deleteFile(commonFile2.path); - host.checkTimeoutQueueLengthAndRun(2); - checkProjectRootFiles(project, [commonFile1.path]); + }; + const host = createServerHost([commonFile1, commonFile2, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + projectService.openClientFile(commonFile2.path); + + checkNumberOfConfiguredProjects(projectService, 1); + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path]); + checkNumberOfInferredProjects(projectService, 1); + }); - // re-add commonFile2 - host.writeFile(commonFile2.path, commonFile2.content); - host.checkTimeoutQueueLengthAndRun(2); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - }); + it("handle recreated files correctly", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createServerHost([commonFile1, commonFile2, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + + checkNumberOfConfiguredProjects(projectService, 1); + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + + // delete commonFile2 + host.deleteFile(commonFile2.path); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectRootFiles(project, [commonFile1.path]); + + // re-add commonFile2 + host.writeFile(commonFile2.path, commonFile2.content); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + }); - it("files explicitly excluded in config file", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("files explicitly excluded in config file", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {}, "exclude": ["/a/c"] }` - }; - const excludedFile1: File = { - path: "/a/c/excluedFile1.ts", - content: `let t = 1;` - }; - - const host = createServerHost([commonFile1, commonFile2, excludedFile1, configFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(commonFile1.path); - checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - projectService.openClientFile(excludedFile1.path); - checkNumberOfInferredProjects(projectService, 1); - }); + }; + const excludedFile1: File = { + path: "/a/c/excluedFile1.ts", + content: `let t = 1;` + }; + + const host = createServerHost([commonFile1, commonFile2, excludedFile1, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(commonFile1.path); + checkNumberOfConfiguredProjects(projectService, 1); + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + projectService.openClientFile(excludedFile1.path); + checkNumberOfInferredProjects(projectService, 1); + }); - it("should properly handle module resolution changes in config file", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: `import { T } from "module1";` - }; - const nodeModuleFile: File = { - path: "/a/b/node_modules/module1.ts", - content: `export interface T {}` - }; - const classicModuleFile: File = { - path: "/a/module1.ts", - content: `export interface T {}` - }; - const randomFile: File = { - path: "/a/file1.ts", - content: `export interface T {}` - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should properly handle module resolution changes in config file", () => { + const file1: File = { + path: "/a/b/file1.ts", + content: `import { T } from "module1";` + }; + const nodeModuleFile: File = { + path: "/a/b/node_modules/module1.ts", + content: `export interface T {}` + }; + const classicModuleFile: File = { + path: "/a/module1.ts", + content: `export interface T {}` + }; + const randomFile: File = { + path: "/a/file1.ts", + content: `export interface T {}` + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "moduleResolution": "node" }, "files": ["${file1.path}"] }` - }; - const files = [file1, nodeModuleFile, classicModuleFile, configFile, randomFile]; - const host = createServerHost(files); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - projectService.openClientFile(nodeModuleFile.path); - projectService.openClientFile(classicModuleFile.path); - - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - const project = configuredProjectAt(projectService, 0); - const inferredProject0 = projectService.inferredProjects[0]; - checkProjectActualFiles(project, [file1.path, nodeModuleFile.path, configFile.path]); - checkProjectActualFiles(projectService.inferredProjects[0], [classicModuleFile.path]); - - host.writeFile(configFile.path, `{ + }; + const files = [file1, nodeModuleFile, classicModuleFile, configFile, randomFile]; + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + projectService.openClientFile(nodeModuleFile.path); + projectService.openClientFile(classicModuleFile.path); + + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + const project = configuredProjectAt(projectService, 0); + const inferredProject0 = projectService.inferredProjects[0]; + checkProjectActualFiles(project, [file1.path, nodeModuleFile.path, configFile.path]); + checkProjectActualFiles(projectService.inferredProjects[0], [classicModuleFile.path]); + + host.writeFile(configFile.path, `{ "compilerOptions": { "moduleResolution": "classic" }, "files": ["${file1.path}"] }`); - host.checkTimeoutQueueLengthAndRun(2); - - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); // will not remove project 1 - checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); - assert.strictEqual(projectService.inferredProjects[0], inferredProject0); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - const inferredProject1 = projectService.inferredProjects[1]; - checkProjectActualFiles(projectService.inferredProjects[1], [nodeModuleFile.path]); - - // Open random file and it will reuse first inferred project - projectService.openClientFile(randomFile.path); - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); - checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); - assert.strictEqual(projectService.inferredProjects[0], inferredProject0); - checkProjectActualFiles(projectService.inferredProjects[0], [randomFile.path]); // Reuses first inferred project - assert.strictEqual(projectService.inferredProjects[1], inferredProject1); - checkProjectActualFiles(projectService.inferredProjects[1], [nodeModuleFile.path]); - }); + host.checkTimeoutQueueLengthAndRun(2); + + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); // will not remove project 1 + checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); + assert.strictEqual(projectService.inferredProjects[0], inferredProject0); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + const inferredProject1 = projectService.inferredProjects[1]; + checkProjectActualFiles(projectService.inferredProjects[1], [nodeModuleFile.path]); + + // Open random file and it will reuse first inferred project + projectService.openClientFile(randomFile.path); + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); + assert.strictEqual(projectService.inferredProjects[0], inferredProject0); + checkProjectActualFiles(projectService.inferredProjects[0], [randomFile.path]); // Reuses first inferred project + assert.strictEqual(projectService.inferredProjects[1], inferredProject1); + checkProjectActualFiles(projectService.inferredProjects[1], [nodeModuleFile.path]); + }); - it("should keep the configured project when the opened file is referenced by the project but not its root", () => { - const file1: File = { - path: "/a/b/main.ts", - content: "import { objA } from './obj-a';" - }; - const file2: File = { - path: "/a/b/obj-a.ts", - content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should keep the configured project when the opened file is referenced by the project but not its root", () => { + const file1: File = { + path: "/a/b/main.ts", + content: "import { objA } from './obj-a';" + }; + const file2: File = { + path: "/a/b/obj-a.ts", + content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts" ] }` - }; - const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - projectService.closeClientFile(file1.path); - projectService.openClientFile(file2.path); - checkNumberOfConfiguredProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); - }); + }; + const host = createServerHost([file1, file2, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + projectService.closeClientFile(file1.path); + projectService.openClientFile(file2.path); + checkNumberOfConfiguredProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + }); - it("should keep the configured project when the opened file is referenced by the project but not its root", () => { - const file1: File = { - path: "/a/b/main.ts", - content: "import { objA } from './obj-a';" - }; - const file2: File = { - path: "/a/b/obj-a.ts", - content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should keep the configured project when the opened file is referenced by the project but not its root", () => { + const file1: File = { + path: "/a/b/main.ts", + content: "import { objA } from './obj-a';" + }; + const file2: File = { + path: "/a/b/obj-a.ts", + content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts" ] }` - }; - const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - projectService.closeClientFile(file1.path); - projectService.openClientFile(file2.path); - checkNumberOfConfiguredProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); - }); + }; + const host = createServerHost([file1, file2, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + projectService.closeClientFile(file1.path); + projectService.openClientFile(file2.path); + checkNumberOfConfiguredProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + }); - it("should tolerate config file errors and still try to build a project", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should tolerate config file errors and still try to build a project", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6", "allowAnything": true }, "someOtherProperty": {} }` - }; - const host = createServerHost([commonFile1, commonFile2, libFile, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - checkNumberOfConfiguredProjects(projectService, 1); - checkProjectRootFiles(configuredProjectAt(projectService, 0), [commonFile1.path, commonFile2.path]); - }); + }; + const host = createServerHost([commonFile1, commonFile2, libFile, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + checkNumberOfConfiguredProjects(projectService, 1); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [commonFile1.path, commonFile2.path]); + }); - it("should reuse same project if file is opened from the configured project that has no open files", () => { - const file1 = { - path: "/a/b/main.ts", - content: "let x =1;" - }; - const file2 = { - path: "/a/b/main2.ts", - content: "let y =1;" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should reuse same project if file is opened from the configured project that has no open files", () => { + const file1 = { + path: "/a/b/main.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/b/main2.ts", + content: "let y =1;" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts", "main2.ts" ] }` - }; - const host = createServerHost([file1, file2, configFile, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true }); - projectService.openClientFile(file1.path); - checkNumberOfConfiguredProjects(projectService, 1); - const project = projectService.configuredProjects.get(configFile.path)!; - assert.isTrue(project.hasOpenRef()); // file1 - - projectService.closeClientFile(file1.path); - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - assert.isFalse(project.hasOpenRef()); // No open files - assert.isFalse(project.isClosed()); - - projectService.openClientFile(file2.path); - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - assert.isTrue(project.hasOpenRef()); // file2 - assert.isFalse(project.isClosed()); - }); + }; + const host = createServerHost([file1, file2, configFile, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: true }); + projectService.openClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isTrue(project.hasOpenRef()); // file1 + + projectService.closeClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isFalse(project.hasOpenRef()); // No open files + assert.isFalse(project.isClosed()); + + projectService.openClientFile(file2.path); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isTrue(project.hasOpenRef()); // file2 + assert.isFalse(project.isClosed()); + }); - it("should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", () => { - const file1 = { - path: "/a/b/main.ts", - content: "let x =1;" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", () => { + const file1 = { + path: "/a/b/main.ts", + content: "let x =1;" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts" ] }` - }; - const host = createServerHost([file1, configFile, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true }); - projectService.openClientFile(file1.path); - checkNumberOfConfiguredProjects(projectService, 1); - const project = projectService.configuredProjects.get(configFile.path)!; - assert.isTrue(project.hasOpenRef()); // file1 - - projectService.closeClientFile(file1.path); - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - assert.isFalse(project.hasOpenRef()); // No files - assert.isFalse(project.isClosed()); - - projectService.openClientFile(libFile.path); - checkNumberOfConfiguredProjects(projectService, 0); - assert.isFalse(project.hasOpenRef()); // No files + project closed - assert.isTrue(project.isClosed()); - }); - - it("open file become a part of configured project if it is referenced from root file", () => { - const file1 = { - path: `${tscWatch.projectRoot}/a/b/f1.ts`, - content: "export let x = 5" - }; - const file2 = { - path: `${tscWatch.projectRoot}/a/c/f2.ts`, - content: `import {x} from "../b/f1"` - }; - const file3 = { - path: `${tscWatch.projectRoot}/a/c/f3.ts`, - content: "export let y = 1" - }; - const configFile = { - path: `${tscWatch.projectRoot}/a/c/tsconfig.json`, - content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) - }; - - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); - - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); - - host.writeFile(configFile.path, configFile.content); - host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, file3.path, configFile.path]); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - assert.isTrue(projectService.inferredProjects[1].isOrphan()); - }); - - it("can correctly update configured project when set of root files has changed (new file on disk)", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {} }) - }; - - const host = createServerHost([file1, configFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); - - host.writeFile(file2.path, file2.content); - - host.checkTimeoutQueueLengthAndRun(2); - - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); - }); - - it("can correctly update configured project when set of root files has changed (new file in list of files)", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) - }; - - const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); - - host.writeFile(configFile.path, JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })); - - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - host.checkTimeoutQueueLengthAndRun(2); - checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); - }); - - it("can update configured project when set of root files was not changed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) - }; - - const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, configFile.path]); - - host.writeFile(configFile.path, JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })); - - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); - }); + }; + const host = createServerHost([file1, configFile, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: true }); + projectService.openClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isTrue(project.hasOpenRef()); // file1 + + projectService.closeClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isFalse(project.hasOpenRef()); // No files + assert.isFalse(project.isClosed()); + + projectService.openClientFile(libFile.path); + checkNumberOfConfiguredProjects(projectService, 0); + assert.isFalse(project.hasOpenRef()); // No files + project closed + assert.isTrue(project.isClosed()); + }); - it("Open ref of configured project when open file gets added to the project as part of configured file update", () => { - const file1: File = { - path: "/a/b/src/file1.ts", - content: "let x = 1;" - }; - const file2: File = { - path: "/a/b/src/file2.ts", - content: "let y = 1;" - }; - const file3: File = { - path: "/a/b/file3.ts", - content: "let z = 1;" - }; - const file4: File = { - path: "/a/file4.ts", - content: "let z = 1;" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] }) - }; + it("open file become a part of configured project if it is referenced from root file", () => { + const file1 = { + path: `${tscWatch.projectRoot}/a/b/f1.ts`, + content: "export let x = 5" + }; + const file2 = { + path: `${tscWatch.projectRoot}/a/c/f2.ts`, + content: `import {x} from "../b/f1"` + }; + const file3 = { + path: `${tscWatch.projectRoot}/a/c/f3.ts`, + content: "export let y = 1" + }; + const configFile = { + path: `${tscWatch.projectRoot}/a/c/tsconfig.json`, + content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) + }; + + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + + host.writeFile(configFile.path, configFile.content); + host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, file3.path, configFile.path]); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + assert.isTrue(projectService.inferredProjects[1].isOrphan()); + }); - const files = [file1, file2, file3, file4]; - const host = createServerHost(files.concat(configFile)); - const projectService = createProjectService(host); + it("can correctly update configured project when set of root files has changed (new file on disk)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + + const host = createServerHost([file1, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); + + host.writeFile(file2.path, file2.content); + + host.checkTimeoutQueueLengthAndRun(2); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); + }); - projectService.openClientFile(file1.path); - projectService.openClientFile(file2.path); - projectService.openClientFile(file3.path); - projectService.openClientFile(file4.path); - - const infos = files.map(file => projectService.getScriptInfoForPath(file.path as Path)!); - checkOpenFiles(projectService, files); - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); - const configProject1 = projectService.configuredProjects.get(configFile.path)!; - assert.isTrue(configProject1.hasOpenRef()); // file1 and file3 - checkProjectActualFiles(configProject1, [file1.path, file3.path, configFile.path]); - const inferredProject1 = projectService.inferredProjects[0]; - checkProjectActualFiles(inferredProject1, [file2.path]); - const inferredProject2 = projectService.inferredProjects[1]; - checkProjectActualFiles(inferredProject2, [file4.path]); - - host.writeFile(configFile.path, "{}"); - host.runQueuedTimeoutCallbacks(); - - verifyScriptInfos(); - checkOpenFiles(projectService, files); - verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true, 2); // file1, file2, file3 - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - const inferredProject3 = projectService.inferredProjects[1]; - checkProjectActualFiles(inferredProject3, [file4.path]); - assert.strictEqual(inferredProject3, inferredProject2); - - projectService.closeClientFile(file1.path); - projectService.closeClientFile(file2.path); - projectService.closeClientFile(file4.path); - - verifyScriptInfos(); - checkOpenFiles(projectService, [file3]); - verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true, 2); // file3 - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - assert.isTrue(projectService.inferredProjects[1].isOrphan()); - - projectService.openClientFile(file4.path); - verifyScriptInfos(); - checkOpenFiles(projectService, [file3, file4]); - verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true, 1); // file3 - const inferredProject4 = projectService.inferredProjects[0]; - checkProjectActualFiles(inferredProject4, [file4.path]); - - projectService.closeClientFile(file3.path); - verifyScriptInfos(); - checkOpenFiles(projectService, [file4]); - verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ false, 1); // No open files - const inferredProject5 = projectService.inferredProjects[0]; - checkProjectActualFiles(inferredProject4, [file4.path]); - assert.strictEqual(inferredProject5, inferredProject4); - - const file5: File = { - path: "/file5.ts", - content: "let zz = 1;" - }; - host.writeFile(file5.path, file5.content); - projectService.openClientFile(file5.path); - verifyScriptInfosAreUndefined([file1, file2, file3]); - assert.strictEqual(projectService.getScriptInfoForPath(file4.path as Path), find(infos, info => info.path === file4.path)); - assert.isDefined(projectService.getScriptInfoForPath(file5.path as Path)); - checkOpenFiles(projectService, [file4, file5]); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file5.path]); - - function verifyScriptInfos() { - infos.forEach(info => assert.strictEqual(projectService.getScriptInfoForPath(info.path), info)); - } + it("can correctly update configured project when set of root files has changed (new file in list of files)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) + }; + + const host = createServerHost([file1, file2, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); + + host.writeFile(configFile.path, JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); + }); - function verifyScriptInfosAreUndefined(files: File[]) { - for (const file of files) { - assert.isUndefined(projectService.getScriptInfoForPath(file.path as Path)); - } - } + it("can update configured project when set of root files was not changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) + }; + + const host = createServerHost([file1, file2, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, configFile.path]); + + host.writeFile(configFile.path, JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); + }); - function verifyConfiguredProjectStateAfterUpdate(hasOpenRef: boolean, inferredProjects: number) { - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects }); - const configProject2 = projectService.configuredProjects.get(configFile.path)!; - assert.strictEqual(configProject2, configProject1); - checkProjectActualFiles(configProject2, [file1.path, file2.path, file3.path, configFile.path]); - assert.equal(configProject2.hasOpenRef(), hasOpenRef); + it("Open ref of configured project when open file gets added to the project as part of configured file update", () => { + const file1: File = { + path: "/a/b/src/file1.ts", + content: "let x = 1;" + }; + const file2: File = { + path: "/a/b/src/file2.ts", + content: "let y = 1;" + }; + const file3: File = { + path: "/a/b/file3.ts", + content: "let z = 1;" + }; + const file4: File = { + path: "/a/file4.ts", + content: "let z = 1;" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] }) + }; + + const files = [file1, file2, file3, file4]; + const host = createServerHost(files.concat(configFile)); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + projectService.openClientFile(file3.path); + projectService.openClientFile(file4.path); + + const infos = files.map(file => projectService.getScriptInfoForPath(file.path as Path)!); + checkOpenFiles(projectService, files); + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + const configProject1 = projectService.configuredProjects.get(configFile.path)!; + assert.isTrue(configProject1.hasOpenRef()); // file1 and file3 + checkProjectActualFiles(configProject1, [file1.path, file3.path, configFile.path]); + const inferredProject1 = projectService.inferredProjects[0]; + checkProjectActualFiles(inferredProject1, [file2.path]); + const inferredProject2 = projectService.inferredProjects[1]; + checkProjectActualFiles(inferredProject2, [file4.path]); + + host.writeFile(configFile.path, "{}"); + host.runQueuedTimeoutCallbacks(); + + verifyScriptInfos(); + checkOpenFiles(projectService, files); + verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true, 2); // file1, file2, file3 + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + const inferredProject3 = projectService.inferredProjects[1]; + checkProjectActualFiles(inferredProject3, [file4.path]); + assert.strictEqual(inferredProject3, inferredProject2); + + projectService.closeClientFile(file1.path); + projectService.closeClientFile(file2.path); + projectService.closeClientFile(file4.path); + + verifyScriptInfos(); + checkOpenFiles(projectService, [file3]); + verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true, 2); // file3 + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + assert.isTrue(projectService.inferredProjects[1].isOrphan()); + + projectService.openClientFile(file4.path); + verifyScriptInfos(); + checkOpenFiles(projectService, [file3, file4]); + verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true, 1); // file3 + const inferredProject4 = projectService.inferredProjects[0]; + checkProjectActualFiles(inferredProject4, [file4.path]); + + projectService.closeClientFile(file3.path); + verifyScriptInfos(); + checkOpenFiles(projectService, [file4]); + verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ false, 1); // No open files + const inferredProject5 = projectService.inferredProjects[0]; + checkProjectActualFiles(inferredProject4, [file4.path]); + assert.strictEqual(inferredProject5, inferredProject4); + + const file5: File = { + path: "/file5.ts", + content: "let zz = 1;" + }; + host.writeFile(file5.path, file5.content); + projectService.openClientFile(file5.path); + verifyScriptInfosAreUndefined([file1, file2, file3]); + assert.strictEqual(projectService.getScriptInfoForPath(file4.path as Path), find(infos, info => info.path === file4.path)); + assert.isDefined(projectService.getScriptInfoForPath(file5.path as Path)); + checkOpenFiles(projectService, [file4, file5]); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file5.path]); + + function verifyScriptInfos() { + infos.forEach(info => assert.strictEqual(projectService.getScriptInfoForPath(info.path), info)); + } + + function verifyScriptInfosAreUndefined(files: File[]) { + for (const file of files) { + assert.isUndefined(projectService.getScriptInfoForPath(file.path as Path)); } - }); - - it("Open ref of configured project when open file gets added to the project as part of configured file update buts its open file references are all closed when the update happens", () => { - const file1: File = { - path: "/a/b/src/file1.ts", - content: "let x = 1;" - }; - const file2: File = { - path: "/a/b/src/file2.ts", - content: "let y = 1;" - }; - const file3: File = { - path: "/a/b/file3.ts", - content: "let z = 1;" - }; - const file4: File = { - path: "/a/file4.ts", - content: "let z = 1;" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] }) - }; - - const files = [file1, file2, file3]; - const hostFiles = files.concat(file4, configFile); - const host = createServerHost(hostFiles); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - projectService.openClientFile(file2.path); - projectService.openClientFile(file3.path); - - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - const configuredProject = projectService.configuredProjects.get(configFile.path)!; - assert.isTrue(configuredProject.hasOpenRef()); // file1 and file3 - checkProjectActualFiles(configuredProject, [file1.path, file3.path, configFile.path]); - const inferredProject1 = projectService.inferredProjects[0]; - checkProjectActualFiles(inferredProject1, [file2.path]); - - projectService.closeClientFile(file1.path); - projectService.closeClientFile(file3.path); - assert.isFalse(configuredProject.hasOpenRef()); // No files - - host.writeFile(configFile.path, "{}"); - // Time out is not yet run so there is project update pending - assert.isTrue(configuredProject.hasOpenRef()); // Pending update and file2 might get into the project - - projectService.openClientFile(file4.path); - - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); - assert.isTrue(configuredProject.hasOpenRef()); // Pending update and F2 might get into the project - assert.strictEqual(projectService.inferredProjects[0], inferredProject1); - const inferredProject2 = projectService.inferredProjects[1]; - checkProjectActualFiles(inferredProject2, [file4.path]); - - host.runQueuedTimeoutCallbacks(); - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); - assert.isTrue(configuredProject.hasOpenRef()); // file2 - checkProjectActualFiles(configuredProject, [file1.path, file2.path, file3.path, configFile.path]); - assert.strictEqual(projectService.inferredProjects[0], inferredProject1); - assert.isTrue(inferredProject1.isOrphan()); - assert.strictEqual(projectService.inferredProjects[1], inferredProject2); - checkProjectActualFiles(inferredProject2, [file4.path]); - }); - - it("files are properly detached when language service is disabled", () => { - const f1 = { - path: "/a/app.js", - content: "var x = 1" - }; - const f2 = { - path: "/a/largefile.js", - content: "" - }; - const f3 = { - path: "/a/lib.js", - content: "var x = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true } }) - }; - const host = createServerHost([f1, f2, f3, config]); - const originalGetFileSize = host.getFileSize; - host.getFileSize = (filePath: string) => - filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); + } + + function verifyConfiguredProjectStateAfterUpdate(hasOpenRef: boolean, inferredProjects: number) { + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects }); + const configProject2 = projectService.configuredProjects.get(configFile.path)!; + assert.strictEqual(configProject2, configProject1); + checkProjectActualFiles(configProject2, [file1.path, file2.path, file3.path, configFile.path]); + assert.equal(configProject2.hasOpenRef(), hasOpenRef); + } + }); - const projectService = createProjectService(host); - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const project = projectService.configuredProjects.get(config.path)!; - assert.isTrue(project.hasOpenRef()); // f1 - assert.isFalse(project.isClosed()); - - projectService.closeClientFile(f1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config.path), project); - assert.isFalse(project.hasOpenRef()); // No files - assert.isFalse(project.isClosed()); - - for (const f of [f1, f2, f3]) { - // All the script infos should be present and contain the project since it is still alive. - const scriptInfo = projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path))!; - assert.equal(scriptInfo.containingProjects.length, 1, `expect 1 containing projects for '${f.path}'`); - assert.equal(scriptInfo.containingProjects[0], project, `expect configured project to be the only containing project for '${f.path}'`); - } + it("Open ref of configured project when open file gets added to the project as part of configured file update buts its open file references are all closed when the update happens", () => { + const file1: File = { + path: "/a/b/src/file1.ts", + content: "let x = 1;" + }; + const file2: File = { + path: "/a/b/src/file2.ts", + content: "let y = 1;" + }; + const file3: File = { + path: "/a/b/file3.ts", + content: "let z = 1;" + }; + const file4: File = { + path: "/a/file4.ts", + content: "let z = 1;" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] }) + }; + + const files = [file1, file2, file3]; + const hostFiles = files.concat(file4, configFile); + const host = createServerHost(hostFiles); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + projectService.openClientFile(file3.path); + + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + const configuredProject = projectService.configuredProjects.get(configFile.path)!; + assert.isTrue(configuredProject.hasOpenRef()); // file1 and file3 + checkProjectActualFiles(configuredProject, [file1.path, file3.path, configFile.path]); + const inferredProject1 = projectService.inferredProjects[0]; + checkProjectActualFiles(inferredProject1, [file2.path]); + + projectService.closeClientFile(file1.path); + projectService.closeClientFile(file3.path); + assert.isFalse(configuredProject.hasOpenRef()); // No files + + host.writeFile(configFile.path, "{}"); + // Time out is not yet run so there is project update pending + assert.isTrue(configuredProject.hasOpenRef()); // Pending update and file2 might get into the project + + projectService.openClientFile(file4.path); + + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); + assert.isTrue(configuredProject.hasOpenRef()); // Pending update and F2 might get into the project + assert.strictEqual(projectService.inferredProjects[0], inferredProject1); + const inferredProject2 = projectService.inferredProjects[1]; + checkProjectActualFiles(inferredProject2, [file4.path]); + + host.runQueuedTimeoutCallbacks(); + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); + assert.isTrue(configuredProject.hasOpenRef()); // file2 + checkProjectActualFiles(configuredProject, [file1.path, file2.path, file3.path, configFile.path]); + assert.strictEqual(projectService.inferredProjects[0], inferredProject1); + assert.isTrue(inferredProject1.isOrphan()); + assert.strictEqual(projectService.inferredProjects[1], inferredProject2); + checkProjectActualFiles(inferredProject2, [file4.path]); + }); - const f4 = { - path: "/aa.js", - content: "var x = 1" - }; - host.writeFile(f4.path, f4.content); - projectService.openClientFile(f4.path); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - assert.isFalse(project.hasOpenRef()); // No files - assert.isTrue(project.isClosed()); - - for (const f of [f1, f2, f3]) { - // All the script infos should not be present since the project is closed and orphan script infos are collected - assert.isUndefined(projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path))); - } - }); + it("files are properly detached when language service is disabled", () => { + const f1 = { + path: "/a/app.js", + content: "var x = 1" + }; + const f2 = { + path: "/a/largefile.js", + content: "" + }; + const f3 = { + path: "/a/lib.js", + content: "var x = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true } }) + }; + const host = createServerHost([f1, f2, f3, config]); + const originalGetFileSize = host.getFileSize; + host.getFileSize = (filePath: string) => + filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); + + const projectService = createProjectService(host); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const project = projectService.configuredProjects.get(config.path)!; + assert.isTrue(project.hasOpenRef()); // f1 + assert.isFalse(project.isClosed()); + + projectService.closeClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.isFalse(project.hasOpenRef()); // No files + assert.isFalse(project.isClosed()); + + for (const f of [f1, f2, f3]) { + // All the script infos should be present and contain the project since it is still alive. + const scriptInfo = projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path))!; + assert.equal(scriptInfo.containingProjects.length, 1, `expect 1 containing projects for '${f.path}'`); + assert.equal(scriptInfo.containingProjects[0], project, `expect configured project to be the only containing project for '${f.path}'`); + } + + const f4 = { + path: "/aa.js", + content: "var x = 1" + }; + host.writeFile(f4.path, f4.content); + projectService.openClientFile(f4.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + assert.isFalse(project.hasOpenRef()); // No files + assert.isTrue(project.isClosed()); + + for (const f of [f1, f2, f3]) { + // All the script infos should not be present since the project is closed and orphan script infos are collected + assert.isUndefined(projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path))); + } + }); - it("syntactic features work even if language service is disabled", () => { - const f1 = { - path: "/a/app.js", - content: "let x = 1;" - }; - const f2 = { - path: "/a/largefile.js", - content: "" - }; - const config = { - path: "/a/jsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, f2, config]); - const originalGetFileSize = host.getFileSize; - host.getFileSize = (filePath: string) => - filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); - const { session, events } = createSessionWithEventTracking(host, server.ProjectLanguageServiceStateEvent); - session.executeCommand({ - seq: 0, - type: "request", - command: "open", - arguments: { file: f1.path } - } as protocol.OpenRequest); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = configuredProjectAt(projectService, 0); - assert.isFalse(project.languageServiceEnabled, "Language service enabled"); - assert.equal(events.length, 1, "should receive event"); - assert.equal(events[0].data.project, project, "project name"); - assert.isFalse(events[0].data.languageServiceEnabled, "Language service state"); - - const options = projectService.getFormatCodeOptions(f1.path as server.NormalizedPath); - const edits = project.getLanguageService().getFormattingEditsForDocument(f1.path, options); - assert.deepEqual(edits, [{ span: createTextSpan(/*start*/ 7, /*length*/ 3), newText: " " }]); - }); + it("syntactic features work even if language service is disabled", () => { + const f1 = { + path: "/a/app.js", + content: "let x = 1;" + }; + const f2 = { + path: "/a/largefile.js", + content: "" + }; + const config = { + path: "/a/jsconfig.json", + content: "{}" + }; + const host = createServerHost([f1, f2, config]); + const originalGetFileSize = host.getFileSize; + host.getFileSize = (filePath: string) => + filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); + const { session, events } = createSessionWithEventTracking(host, server.ProjectLanguageServiceStateEvent); + session.executeCommand({ + seq: 0, + type: "request", + command: "open", + arguments: { file: f1.path } + } as protocol.OpenRequest); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = configuredProjectAt(projectService, 0); + assert.isFalse(project.languageServiceEnabled, "Language service enabled"); + assert.equal(events.length, 1, "should receive event"); + assert.equal(events[0].data.project, project, "project name"); + assert.isFalse(events[0].data.languageServiceEnabled, "Language service state"); + + const options = projectService.getFormatCodeOptions(f1.path as server.NormalizedPath); + const edits = project.getLanguageService().getFormattingEditsForDocument(f1.path, options); + assert.deepEqual(edits, [{ span: createTextSpan(/*start*/ 7, /*length*/ 3), newText: " " }]); + }); - it("when multiple projects are open, detects correct default project", () => { - const barConfig: File = { - path: `${tscWatch.projectRoot}/bar/tsconfig.json`, - content: JSON.stringify({ - include: ["index.ts"], - compilerOptions: { - lib: ["dom", "es2017"] - } - }) - }; - const barIndex: File = { - path: `${tscWatch.projectRoot}/bar/index.ts`, - content: ` + it("when multiple projects are open, detects correct default project", () => { + const barConfig: File = { + path: `${tscWatch.projectRoot}/bar/tsconfig.json`, + content: JSON.stringify({ + include: ["index.ts"], + compilerOptions: { + lib: ["dom", "es2017"] + } + }) + }; + const barIndex: File = { + path: `${tscWatch.projectRoot}/bar/index.ts`, + content: ` export function bar() { console.log("hello world"); }` - }; - const fooConfig: File = { - path: `${tscWatch.projectRoot}/foo/tsconfig.json`, - content: JSON.stringify({ - include: ["index.ts"], - compilerOptions: { - lib: ["es2017"] - } - }) - }; - const fooIndex: File = { - path: `${tscWatch.projectRoot}/foo/index.ts`, - content: ` + }; + const fooConfig: File = { + path: `${tscWatch.projectRoot}/foo/tsconfig.json`, + content: JSON.stringify({ + include: ["index.ts"], + compilerOptions: { + lib: ["es2017"] + } + }) + }; + const fooIndex: File = { + path: `${tscWatch.projectRoot}/foo/index.ts`, + content: ` import { bar } from "bar"; bar();` - }; - const barSymLink: SymLink = { - path: `${tscWatch.projectRoot}/foo/node_modules/bar`, - symLink: `${tscWatch.projectRoot}/bar` - }; - - const lib2017: File = { - path: `${getDirectoryPath(libFile.path)}/lib.es2017.d.ts`, - content: libFile.content - }; - const libDom: File = { - path: `${getDirectoryPath(libFile.path)}/lib.dom.d.ts`, - content: ` + }; + const barSymLink: SymLink = { + path: `${tscWatch.projectRoot}/foo/node_modules/bar`, + symLink: `${tscWatch.projectRoot}/bar` + }; + + const lib2017: File = { + path: `${getDirectoryPath(libFile.path)}/lib.es2017.d.ts`, + content: libFile.content + }; + const libDom: File = { + path: `${getDirectoryPath(libFile.path)}/lib.dom.d.ts`, + content: ` declare var console: { log(...args: any[]): void; };` - }; - const host = createServerHost([barConfig, barIndex, fooConfig, fooIndex, barSymLink, lib2017, libDom]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([fooIndex, barIndex], session); - verifyGetErrRequest({ session, host, files: [barIndex, fooIndex] }); - baselineTsserverLogs("configuredProjects", "when multiple projects are open detects correct default project", session); - }); + }; + const host = createServerHost([barConfig, barIndex, fooConfig, fooIndex, barSymLink, lib2017, libDom]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([fooIndex, barIndex], session); + verifyGetErrRequest({ session, host, files: [barIndex, fooIndex] }); + baselineTsserverLogs("configuredProjects", "when multiple projects are open detects correct default project", session); + }); - it("when file name starts with ^", () => { - const file: File = { - path: `${tscWatch.projectRoot}/file.ts`, - content: "const x = 10;" - }; - const app: File = { - path: `${tscWatch.projectRoot}/^app.ts`, - content: "const y = 10;" - }; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const host = createServerHost([file, app, tsconfig, libFile]); - const service = createProjectService(host); - service.openClientFile(file.path); - }); + it("when file name starts with ^", () => { + const file: File = { + path: `${tscWatch.projectRoot}/file.ts`, + content: "const x = 10;" + }; + const app: File = { + path: `${tscWatch.projectRoot}/^app.ts`, + content: "const y = 10;" + }; + const tsconfig: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const host = createServerHost([file, app, tsconfig, libFile]); + const service = createProjectService(host); + service.openClientFile(file.path); + }); - describe("when creating new file", () => { - const foo: File = { - path: `${tscWatch.projectRoot}/src/foo.ts`, - content: "export function foo() { }" - }; - const bar: File = { - path: `${tscWatch.projectRoot}/src/bar.ts`, - content: "export function bar() { }" - }; - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - include: ["./src"] - }) - }; - const fooBar: File = { - path: `${tscWatch.projectRoot}/src/sub/fooBar.ts`, - content: "export function fooBar() { }" - }; - function verifySessionWorker({ withExclude, openFileBeforeCreating }: VerifySession, errorOnNewFileBeforeOldFile: boolean) { - const host = createServerHost([ - foo, bar, libFile, { path: `${tscWatch.projectRoot}/src/sub` }, - withExclude ? - { - path: config.path, - content: JSON.stringify({ - include: ["./src"], - exclude: ["./src/sub"] - }) - } : - config - ]); - const session = createSession(host, { - canUseEvents: true, - logger: createLoggerWithInMemoryLogs(), - }); - session.executeCommandSeq({ - command: protocol.CommandTypes.Open, - arguments: { - file: foo.path, - fileContent: foo.content, - projectRootPath: tscWatch.projectRoot - } - }); - if (!openFileBeforeCreating) { - host.writeFile(fooBar.path, fooBar.content); - } - session.executeCommandSeq({ - command: protocol.CommandTypes.Open, - arguments: { - file: fooBar.path, - fileContent: fooBar.content, - projectRootPath: tscWatch.projectRoot - } - }); - if (openFileBeforeCreating) { - host.writeFile(fooBar.path, fooBar.content); + describe("when creating new file", () => { + const foo: File = { + path: `${tscWatch.projectRoot}/src/foo.ts`, + content: "export function foo() { }" + }; + const bar: File = { + path: `${tscWatch.projectRoot}/src/bar.ts`, + content: "export function bar() { }" + }; + const config: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + include: ["./src"] + }) + }; + const fooBar: File = { + path: `${tscWatch.projectRoot}/src/sub/fooBar.ts`, + content: "export function fooBar() { }" + }; + function verifySessionWorker({ withExclude, openFileBeforeCreating }: VerifySession, errorOnNewFileBeforeOldFile: boolean) { + const host = createServerHost([ + foo, bar, libFile, { path: `${tscWatch.projectRoot}/src/sub` }, + withExclude ? + { + path: config.path, + content: JSON.stringify({ + include: ["./src"], + exclude: ["./src/sub"] + }) + } : + config + ]); + const session = createSession(host, { + canUseEvents: true, + logger: createLoggerWithInMemoryLogs(), + }); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: foo.path, + fileContent: foo.content, + projectRootPath: tscWatch.projectRoot } - verifyGetErrRequest({ - session, - host, - files: errorOnNewFileBeforeOldFile ? - [fooBar, foo] : - [foo, fooBar], - existingTimeouts: withExclude ? 0 : 2 - }); - baselineTsserverLogs("configuredProjects", `creating new file and then open it ${openFileBeforeCreating ? "before" : "after"} watcher is invoked, ask errors on it ${errorOnNewFileBeforeOldFile ? "before" : "after"} old one${withExclude ? " without file being in config" : ""}`, session); + }); + if (!openFileBeforeCreating) { + host.writeFile(fooBar.path, fooBar.content); } - interface VerifySession { - withExclude?: boolean; - openFileBeforeCreating: boolean; + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: fooBar.path, + fileContent: fooBar.content, + projectRootPath: tscWatch.projectRoot + } + }); + if (openFileBeforeCreating) { + host.writeFile(fooBar.path, fooBar.content); } - function verifySession(input: VerifySession) { - it("when error on new file are asked before old one", () => { - verifySessionWorker(input, /*errorOnNewFileBeforeOldFile*/ true); - }); + verifyGetErrRequest({ + session, + host, + files: errorOnNewFileBeforeOldFile ? + [fooBar, foo] : + [foo, fooBar], + existingTimeouts: withExclude ? 0 : 2 + }); + baselineTsserverLogs("configuredProjects", `creating new file and then open it ${openFileBeforeCreating ? "before" : "after"} watcher is invoked, ask errors on it ${errorOnNewFileBeforeOldFile ? "before" : "after"} old one${withExclude ? " without file being in config" : ""}`, session); + } + interface VerifySession { + withExclude?: boolean; + openFileBeforeCreating: boolean; + } + function verifySession(input: VerifySession) { + it("when error on new file are asked before old one", () => { + verifySessionWorker(input, /*errorOnNewFileBeforeOldFile*/ true); + }); - it("when error on new file are asked after old one", () => { - verifySessionWorker(input, /*errorOnNewFileBeforeOldFile*/ false); - }); - } - describe("when new file creation directory watcher is invoked before file is opened in editor", () => { + it("when error on new file are asked after old one", () => { + verifySessionWorker(input, /*errorOnNewFileBeforeOldFile*/ false); + }); + } + describe("when new file creation directory watcher is invoked before file is opened in editor", () => { + verifySession({ + openFileBeforeCreating: false, + }); + describe("when new file is excluded from config", () => { verifySession({ + withExclude: true, openFileBeforeCreating: false, }); - describe("when new file is excluded from config", () => { - verifySession({ - withExclude: true, - openFileBeforeCreating: false, - }); - }); }); + }); - describe("when new file creation directory watcher is invoked after file is opened in editor", () => { + describe("when new file creation directory watcher is invoked after file is opened in editor", () => { + verifySession({ + openFileBeforeCreating: true, + }); + describe("when new file is excluded from config", () => { verifySession({ + withExclude: true, openFileBeforeCreating: true, }); - describe("when new file is excluded from config", () => { - verifySession({ - withExclude: true, - openFileBeforeCreating: true, - }); - }); }); }); + }); - it("when default configured project does not contain the file", () => { - const barConfig: File = { - path: `${tscWatch.projectRoot}/bar/tsconfig.json`, + it("when default configured project does not contain the file", () => { + const barConfig: File = { + path: `${tscWatch.projectRoot}/bar/tsconfig.json`, + content: "{}" + }; + const barIndex: File = { + path: `${tscWatch.projectRoot}/bar/index.ts`, + content: `import {foo} from "../foo/lib"; +foo();` + }; + const fooBarConfig: File = { + path: `${tscWatch.projectRoot}/foobar/tsconfig.json`, + content: barConfig.path + }; + const fooBarIndex: File = { + path: `${tscWatch.projectRoot}/foobar/index.ts`, + content: barIndex.content + }; + const fooConfig: File = { + path: `${tscWatch.projectRoot}/foo/tsconfig.json`, + content: JSON.stringify({ + include: ["index.ts"], + compilerOptions: { + declaration: true, + outDir: "lib" + } + }) + }; + const fooIndex: File = { + path: `${tscWatch.projectRoot}/foo/index.ts`, + content: `export function foo() {}` + }; + const host = createServerHost([barConfig, barIndex, fooBarConfig, fooBarIndex, fooConfig, fooIndex, libFile]); + tscWatch.ensureErrorFreeBuild(host, [fooConfig.path]); + const fooDts = `${tscWatch.projectRoot}/foo/lib/index.d.ts`; + assert.isTrue(host.fileExists(fooDts)); + const session = createSession(host); + const service = session.getProjectService(); + service.openClientFile(barIndex.path); + checkProjectActualFiles(service.configuredProjects.get(barConfig.path)!, [barIndex.path, fooDts, libFile.path, barConfig.path]); + service.openClientFile(fooBarIndex.path); + checkProjectActualFiles(service.configuredProjects.get(fooBarConfig.path)!, [fooBarIndex.path, fooDts, libFile.path, fooBarConfig.path]); + service.openClientFile(fooIndex.path); + checkProjectActualFiles(service.configuredProjects.get(fooConfig.path)!, [fooIndex.path, libFile.path, fooConfig.path]); + service.openClientFile(fooDts); + session.executeCommandSeq({ + command: protocol.CommandTypes.GetApplicableRefactors, + arguments: { + file: fooDts, + startLine: 1, + startOffset: 1, + endLine: 1, + endOffset: 1 + } + }); + assert.equal(service.tryGetDefaultProjectForFile(server.toNormalizedPath(fooDts)), service.configuredProjects.get(barConfig.path)); + }); + + describe("watches extended config files", () => { + function getService(additionalFiles?: File[]) { + const alphaExtendedConfig: File = { + path: `${tscWatch.projectRoot}/extended/alpha.tsconfig.json`, content: "{}" }; - const barIndex: File = { - path: `${tscWatch.projectRoot}/bar/index.ts`, - content: `import {foo} from "../foo/lib"; -foo();` + const bravoExtendedConfig: File = { + path: `${tscWatch.projectRoot}/extended/bravo.tsconfig.json`, + content: JSON.stringify({ + extends: "./alpha.tsconfig.json" + }) }; - const fooBarConfig: File = { - path: `${tscWatch.projectRoot}/foobar/tsconfig.json`, - content: barConfig.path + const aConfig: File = { + path: `${tscWatch.projectRoot}/a/tsconfig.json`, + content: JSON.stringify({ + extends: "../extended/alpha.tsconfig.json", + files: ["a.ts"] + }) }; - const fooBarIndex: File = { - path: `${tscWatch.projectRoot}/foobar/index.ts`, - content: barIndex.content + const aFile: File = { + path: `${tscWatch.projectRoot}/a/a.ts`, + content: `let a = 1;` }; - const fooConfig: File = { - path: `${tscWatch.projectRoot}/foo/tsconfig.json`, + const bConfig: File = { + path: `${tscWatch.projectRoot}/b/tsconfig.json`, content: JSON.stringify({ - include: ["index.ts"], - compilerOptions: { - declaration: true, - outDir: "lib" - } + extends: "../extended/bravo.tsconfig.json", + files: ["b.ts"] }) }; - const fooIndex: File = { - path: `${tscWatch.projectRoot}/foo/index.ts`, - content: `export function foo() {}` + const bFile: File = { + path: `${tscWatch.projectRoot}/b/b.ts`, + content: `let b = 1;` }; - const host = createServerHost([barConfig, barIndex, fooBarConfig, fooBarIndex, fooConfig, fooIndex, libFile]); - tscWatch.ensureErrorFreeBuild(host, [fooConfig.path]); - const fooDts = `${tscWatch.projectRoot}/foo/lib/index.d.ts`; - assert.isTrue(host.fileExists(fooDts)); - const session = createSession(host); - const service = session.getProjectService(); - service.openClientFile(barIndex.path); - checkProjectActualFiles(service.configuredProjects.get(barConfig.path)!, [barIndex.path, fooDts, libFile.path, barConfig.path]); - service.openClientFile(fooBarIndex.path); - checkProjectActualFiles(service.configuredProjects.get(fooBarConfig.path)!, [fooBarIndex.path, fooDts, libFile.path, fooBarConfig.path]); - service.openClientFile(fooIndex.path); - checkProjectActualFiles(service.configuredProjects.get(fooConfig.path)!, [fooIndex.path, libFile.path, fooConfig.path]); - service.openClientFile(fooDts); - session.executeCommandSeq({ - command: protocol.CommandTypes.GetApplicableRefactors, - arguments: { - file: fooDts, - startLine: 1, - startOffset: 1, - endLine: 1, - endOffset: 1 + + const host = createServerHost([alphaExtendedConfig, aConfig, aFile, bravoExtendedConfig, bConfig, bFile, ...(additionalFiles || emptyArray)]); + const projectService = createProjectService(host); + return { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig }; + } + + it("should watch the extended configs of multiple projects", () => { + const { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService(); + + projectService.openClientFile(aFile.path); + projectService.openClientFile(bFile.path); + checkNumberOfConfiguredProjects(projectService, 2); + const aProject = projectService.configuredProjects.get(aConfig.path)!; + const bProject = projectService.configuredProjects.get(bConfig.path)!; + checkProjectActualFiles(aProject, [aFile.path, aConfig.path, alphaExtendedConfig.path]); + checkProjectActualFiles(bProject, [bFile.path, bConfig.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + assert.isUndefined(aProject.getCompilerOptions().strict); + assert.isUndefined(bProject.getCompilerOptions().strict); + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + + host.writeFile(alphaExtendedConfig.path, JSON.stringify({ + compilerOptions: { + strict: true } - }); - assert.equal(service.tryGetDefaultProjectForFile(server.toNormalizedPath(fooDts)), service.configuredProjects.get(barConfig.path)); + })); + assert.isTrue(projectService.hasPendingProjectUpdate(aProject)); + assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); + host.checkTimeoutQueueLengthAndRun(3); + assert.isTrue(aProject.getCompilerOptions().strict); + assert.isTrue(bProject.getCompilerOptions().strict); + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + + host.writeFile(bravoExtendedConfig.path, JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { + strict: false + } + })); + assert.isFalse(projectService.hasPendingProjectUpdate(aProject)); + assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); + host.checkTimeoutQueueLengthAndRun(2); + assert.isTrue(aProject.getCompilerOptions().strict); + assert.isFalse(bProject.getCompilerOptions().strict); + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); + + host.writeFile(bConfig.path, JSON.stringify({ + extends: "../extended/alpha.tsconfig.json", + })); + assert.isFalse(projectService.hasPendingProjectUpdate(aProject)); + assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); + host.checkTimeoutQueueLengthAndRun(2); + assert.isTrue(aProject.getCompilerOptions().strict); + assert.isTrue(bProject.getCompilerOptions().strict); + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, alphaExtendedConfig.path]); + + host.writeFile(alphaExtendedConfig.path, "{}"); + assert.isTrue(projectService.hasPendingProjectUpdate(aProject)); + assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); + host.checkTimeoutQueueLengthAndRun(3); + assert.isUndefined(aProject.getCompilerOptions().strict); + assert.isUndefined(bProject.getCompilerOptions().strict); + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, alphaExtendedConfig.path]); }); - describe("watches extended config files", () => { - function getService(additionalFiles?: File[]) { - const alphaExtendedConfig: File = { - path: `${tscWatch.projectRoot}/extended/alpha.tsconfig.json`, - content: "{}" - }; - const bravoExtendedConfig: File = { - path: `${tscWatch.projectRoot}/extended/bravo.tsconfig.json`, - content: JSON.stringify({ - extends: "./alpha.tsconfig.json" - }) - }; - const aConfig: File = { - path: `${tscWatch.projectRoot}/a/tsconfig.json`, - content: JSON.stringify({ - extends: "../extended/alpha.tsconfig.json", - files: ["a.ts"] - }) - }; - const aFile: File = { - path: `${tscWatch.projectRoot}/a/a.ts`, - content: `let a = 1;` - }; - const bConfig: File = { - path: `${tscWatch.projectRoot}/b/tsconfig.json`, - content: JSON.stringify({ - extends: "../extended/bravo.tsconfig.json", - files: ["b.ts"] - }) - }; - const bFile: File = { - path: `${tscWatch.projectRoot}/b/b.ts`, - content: `let b = 1;` - }; - - const host = createServerHost([alphaExtendedConfig, aConfig, aFile, bravoExtendedConfig, bConfig, bFile, ...(additionalFiles || emptyArray)]); - const projectService = createProjectService(host); - return { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig }; - } + it("should stop watching the extended configs of closed projects", () => { + const dummy: File = { + path: `${tscWatch.projectRoot}/dummy/dummy.ts`, + content: `let dummy = 1;` + }; + const dummyConfig: File = { + path: `${tscWatch.projectRoot}/dummy/tsconfig.json`, + content: "{}" + }; + const { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService([dummy, dummyConfig]); - it("should watch the extended configs of multiple projects", () => { - const { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService(); - - projectService.openClientFile(aFile.path); - projectService.openClientFile(bFile.path); - checkNumberOfConfiguredProjects(projectService, 2); - const aProject = projectService.configuredProjects.get(aConfig.path)!; - const bProject = projectService.configuredProjects.get(bConfig.path)!; - checkProjectActualFiles(aProject, [aFile.path, aConfig.path, alphaExtendedConfig.path]); - checkProjectActualFiles(bProject, [bFile.path, bConfig.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); - assert.isUndefined(aProject.getCompilerOptions().strict); - assert.isUndefined(bProject.getCompilerOptions().strict); - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); - - host.writeFile(alphaExtendedConfig.path, JSON.stringify({ - compilerOptions: { - strict: true - } - })); - assert.isTrue(projectService.hasPendingProjectUpdate(aProject)); - assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); - host.checkTimeoutQueueLengthAndRun(3); - assert.isTrue(aProject.getCompilerOptions().strict); - assert.isTrue(bProject.getCompilerOptions().strict); - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); - - host.writeFile(bravoExtendedConfig.path, JSON.stringify({ - extends: "./alpha.tsconfig.json", - compilerOptions: { - strict: false - } - })); - assert.isFalse(projectService.hasPendingProjectUpdate(aProject)); - assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); - host.checkTimeoutQueueLengthAndRun(2); - assert.isTrue(aProject.getCompilerOptions().strict); - assert.isFalse(bProject.getCompilerOptions().strict); - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path]); - - host.writeFile(bConfig.path, JSON.stringify({ - extends: "../extended/alpha.tsconfig.json", - })); - assert.isFalse(projectService.hasPendingProjectUpdate(aProject)); - assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); - host.checkTimeoutQueueLengthAndRun(2); - assert.isTrue(aProject.getCompilerOptions().strict); - assert.isTrue(bProject.getCompilerOptions().strict); - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, alphaExtendedConfig.path]); - - host.writeFile(alphaExtendedConfig.path, "{}"); - assert.isTrue(projectService.hasPendingProjectUpdate(aProject)); - assert.isTrue(projectService.hasPendingProjectUpdate(bProject)); - host.checkTimeoutQueueLengthAndRun(3); - assert.isUndefined(aProject.getCompilerOptions().strict); - assert.isUndefined(bProject.getCompilerOptions().strict); - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, alphaExtendedConfig.path]); - }); + projectService.openClientFile(aFile.path); + projectService.openClientFile(bFile.path); + projectService.openClientFile(dummy.path); + checkNumberOfConfiguredProjects(projectService, 3); + checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path, dummyConfig.path]); - it("should stop watching the extended configs of closed projects", () => { - const dummy: File = { - path: `${tscWatch.projectRoot}/dummy/dummy.ts`, - content: `let dummy = 1;` - }; - const dummyConfig: File = { - path: `${tscWatch.projectRoot}/dummy/tsconfig.json`, - content: "{}" - }; - const { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService([dummy, dummyConfig]); - - projectService.openClientFile(aFile.path); - projectService.openClientFile(bFile.path); - projectService.openClientFile(dummy.path); - checkNumberOfConfiguredProjects(projectService, 3); - checkWatchedFiles(host, [aConfig.path, bConfig.path, libFile.path, bravoExtendedConfig.path, alphaExtendedConfig.path, dummyConfig.path]); - - projectService.closeClientFile(bFile.path); - projectService.closeClientFile(dummy.path); - projectService.openClientFile(dummy.path); - - checkNumberOfConfiguredProjects(projectService, 2); - checkWatchedFiles(host, [aConfig.path, libFile.path, alphaExtendedConfig.path, dummyConfig.path]); - - projectService.closeClientFile(aFile.path); - projectService.closeClientFile(dummy.path); - projectService.openClientFile(dummy.path); - - checkNumberOfConfiguredProjects(projectService, 1); - checkWatchedFiles(host, [libFile.path, dummyConfig.path]); - }); + projectService.closeClientFile(bFile.path); + projectService.closeClientFile(dummy.path); + projectService.openClientFile(dummy.path); + + checkNumberOfConfiguredProjects(projectService, 2); + checkWatchedFiles(host, [aConfig.path, libFile.path, alphaExtendedConfig.path, dummyConfig.path]); + + projectService.closeClientFile(aFile.path); + projectService.closeClientFile(dummy.path); + projectService.openClientFile(dummy.path); + + checkNumberOfConfiguredProjects(projectService, 1); + checkWatchedFiles(host, [libFile.path, dummyConfig.path]); }); }); +}); - describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories listed in config file input array", () => { - it("should be tolerated without crashing the server", () => { - const configFile = { - path: "/a/b/tsconfig.json", - content: `{ +describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories listed in config file input array", () => { + it("should be tolerated without crashing the server", () => { + const configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {}, "include": ["app/*", "test/**/*", "something"] }` - }; - const file1 = { - path: "/a/b/file1.ts", - content: "let t = 10;" - }; - - const host = createServerHost([file1, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - host.runQueuedTimeoutCallbacks(); - - // Since file1 refers to config file as the default project, it needs to be kept alive - checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); - const inferredProject = projectService.inferredProjects[0]; - assert.isTrue(inferredProject.containsFile(file1.path as server.NormalizedPath)); - assert.isFalse(projectService.configuredProjects.get(configFile.path)!.containsFile(file1.path as server.NormalizedPath)); - }); - - it("should be able to handle @types if input file list is empty", () => { - const f = { - path: "/a/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compiler: {}, - files: [] - }) - }; - const t1 = { - path: "/a/node_modules/@types/typings/index.d.ts", - content: `export * from "./lib"` - }; - const t2 = { - path: "/a/node_modules/@types/typings/lib.d.ts", - content: `export const x: number` - }; - const host = createServerHost([f, config, t1, t2], { currentDirectory: getDirectoryPath(f.path) }); - const projectService = createProjectService(host); - - projectService.openClientFile(f.path); - // Since f refers to config file as the default project, it needs to be kept alive - projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); - }); + }; + const file1 = { + path: "/a/b/file1.ts", + content: "let t = 10;" + }; + + const host = createServerHost([file1, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + host.runQueuedTimeoutCallbacks(); + + // Since file1 refers to config file as the default project, it needs to be kept alive + checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); + const inferredProject = projectService.inferredProjects[0]; + assert.isTrue(inferredProject.containsFile(file1.path as server.NormalizedPath)); + assert.isFalse(projectService.configuredProjects.get(configFile.path)!.containsFile(file1.path as server.NormalizedPath)); + }); - it("should tolerate invalid include files that start in subDirectory", () => { - const f = { - path: `${tscWatch.projectRoot}/src/server/index.ts`, - content: "let x = 1" - }; - const config = { - path: `${tscWatch.projectRoot}/src/server/tsconfig.json`, - content: JSON.stringify({ - compiler: { - module: "commonjs", - outDir: "../../build" - }, - include: [ - "../src/**/*.ts" - ] - }) - }; - const host = createServerHost([f, config, libFile], { useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host); + it("should be able to handle @types if input file list is empty", () => { + const f = { + path: "/a/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compiler: {}, + files: [] + }) + }; + const t1 = { + path: "/a/node_modules/@types/typings/index.d.ts", + content: `export * from "./lib"` + }; + const t2 = { + path: "/a/node_modules/@types/typings/lib.d.ts", + content: `export const x: number` + }; + const host = createServerHost([f, config, t1, t2], { currentDirectory: getDirectoryPath(f.path) }); + const projectService = createProjectService(host); + + projectService.openClientFile(f.path); + // Since f refers to config file as the default project, it needs to be kept alive + projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); + }); - projectService.openClientFile(f.path); - // Since f refers to config file as the default project, it needs to be kept alive - projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); - }); + it("should tolerate invalid include files that start in subDirectory", () => { + const f = { + path: `${tscWatch.projectRoot}/src/server/index.ts`, + content: "let x = 1" + }; + const config = { + path: `${tscWatch.projectRoot}/src/server/tsconfig.json`, + content: JSON.stringify({ + compiler: { + module: "commonjs", + outDir: "../../build" + }, + include: [ + "../src/**/*.ts" + ] + }) + }; + const host = createServerHost([f, config, libFile], { useCaseSensitiveFileNames: true }); + const projectService = createProjectService(host); + + projectService.openClientFile(f.path); + // Since f refers to config file as the default project, it needs to be kept alive + projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); + }); - it("Changed module resolution reflected when specifying files list", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: 'import classc from "file2"' - }; - const file2a: File = { - path: "/a/file2.ts", - content: "export classc { method2a() { return 10; } }" - }; - const file2: File = { - path: "/a/b/file2.ts", - content: "export classc { method2() { return 10; } }" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ files: [file1.path], compilerOptions: { module: "amd" } }) - }; - const files = [file1, file2a, configFile, libFile]; - const host = createServerHost(files); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects.get(configFile.path)!; - assert.isDefined(project); - checkProjectActualFiles(project, map(files, file => file.path)); - checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path)); - checkWatchedDirectoriesDetailed(host, ["/a/b"], 1, /*recursive*/ false); - checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true); - - files.push(file2); - host.writeFile(file2.path, file2.content); - host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions - host.runQueuedTimeoutCallbacks(); // Actual update - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path)); - checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path)); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true); - - // On next file open the files file2a should be closed and not watched any more - projectService.openClientFile(file2.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path)); - checkWatchedFiles(host, [libFile.path, configFile.path]); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true); - }); + it("Changed module resolution reflected when specifying files list", () => { + const file1: File = { + path: "/a/b/file1.ts", + content: 'import classc from "file2"' + }; + const file2a: File = { + path: "/a/file2.ts", + content: "export classc { method2a() { return 10; } }" + }; + const file2: File = { + path: "/a/b/file2.ts", + content: "export classc { method2() { return 10; } }" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1.path], compilerOptions: { module: "amd" } }) + }; + const files = [file1, file2a, configFile, libFile]; + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isDefined(project); + checkProjectActualFiles(project, map(files, file => file.path)); + checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path)); + checkWatchedDirectoriesDetailed(host, ["/a/b"], 1, /*recursive*/ false); + checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true); + + files.push(file2); + host.writeFile(file2.path, file2.content); + host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions + host.runQueuedTimeoutCallbacks(); // Actual update + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path)); + checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path)); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true); + + // On next file open the files file2a should be closed and not watched any more + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path)); + checkWatchedFiles(host, [libFile.path, configFile.path]); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true); + }); - it("Failed lookup locations uses parent most node_modules directory", () => { - const root = "/user/username/rootfolder"; - const file1: File = { - path: "/a/b/src/file1.ts", - content: 'import { classc } from "module1"' - }; - const module1: File = { - path: "/a/b/node_modules/module1/index.d.ts", - content: `import { class2 } from "module2"; + it("Failed lookup locations uses parent most node_modules directory", () => { + const root = "/user/username/rootfolder"; + const file1: File = { + path: "/a/b/src/file1.ts", + content: 'import { classc } from "module1"' + }; + const module1: File = { + path: "/a/b/node_modules/module1/index.d.ts", + content: `import { class2 } from "module2"; export classc { method2a(): class2; }` - }; - const module2: File = { - path: "/a/b/node_modules/module2/index.d.ts", - content: "export class2 { method2() { return 10; } }" - }; - const module3: File = { - path: "/a/b/node_modules/module/node_modules/module3/index.d.ts", - content: "export class3 { method2() { return 10; } }" - }; - const configFile: File = { - path: "/a/b/src/tsconfig.json", - content: JSON.stringify({ files: ["file1.ts"] }) - }; - const nonLibFiles = [file1, module1, module2, module3, configFile]; - nonLibFiles.forEach(f => f.path = root + f.path); - const files = nonLibFiles.concat(libFile); - const host = createServerHost(files); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects.get(configFile.path)!; - assert.isDefined(project); - checkProjectActualFiles(project, [file1.path, libFile.path, module1.path, module2.path, configFile.path]); - checkWatchedFiles(host, [libFile.path, configFile.path]); - checkWatchedDirectories(host, [], /*recursive*/ false); - const watchedRecursiveDirectories = getTypeRootsFromLocation(root + "/a/b/src"); - watchedRecursiveDirectories.push(`${root}/a/b/src/node_modules`, `${root}/a/b/node_modules`); - checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); - }); + }; + const module2: File = { + path: "/a/b/node_modules/module2/index.d.ts", + content: "export class2 { method2() { return 10; } }" + }; + const module3: File = { + path: "/a/b/node_modules/module/node_modules/module3/index.d.ts", + content: "export class3 { method2() { return 10; } }" + }; + const configFile: File = { + path: "/a/b/src/tsconfig.json", + content: JSON.stringify({ files: ["file1.ts"] }) + }; + const nonLibFiles = [file1, module1, module2, module3, configFile]; + nonLibFiles.forEach(f => f.path = root + f.path); + const files = nonLibFiles.concat(libFile); + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isDefined(project); + checkProjectActualFiles(project, [file1.path, libFile.path, module1.path, module2.path, configFile.path]); + checkWatchedFiles(host, [libFile.path, configFile.path]); + checkWatchedDirectories(host, [], /*recursive*/ false); + const watchedRecursiveDirectories = getTypeRootsFromLocation(root + "/a/b/src"); + watchedRecursiveDirectories.push(`${root}/a/b/src/node_modules`, `${root}/a/b/node_modules`); + checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); }); - - describe("unittests:: tsserver:: ConfiguredProjects:: when reading tsconfig file fails", () => { - it("should be tolerated without crashing the server", () => { - const configFile = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "" - }; - const file1 = { - path: `${tscWatch.projectRoot}/file1.ts`, - content: "let t = 10;" - }; - - const host = createServerHost([file1, libFile, configFile]); - const { session, events } = createSessionWithEventTracking(host, server.ConfigFileDiagEvent); - const originalReadFile = host.readFile; - host.readFile = f => { - return f === configFile.path ? - undefined : - originalReadFile.call(host, f); - }; - openFilesForSession([file1], session); - - assert.deepEqual(events, [{ - eventName: server.ConfigFileDiagEvent, - data: { - triggerFile: file1.path, - configFileName: configFile.path, - diagnostics: [ - createCompilerDiagnostic(Diagnostics.Cannot_read_file_0, configFile.path) - ] - } - }]); - }); +}); + +describe("unittests:: tsserver:: ConfiguredProjects:: when reading tsconfig file fails", () => { + it("should be tolerated without crashing the server", () => { + const configFile = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: "" + }; + const file1 = { + path: `${tscWatch.projectRoot}/file1.ts`, + content: "let t = 10;" + }; + + const host = createServerHost([file1, libFile, configFile]); + const { session, events } = createSessionWithEventTracking(host, server.ConfigFileDiagEvent); + const originalReadFile = host.readFile; + host.readFile = f => { + return f === configFile.path ? + undefined : + originalReadFile.call(host, f); + }; + openFilesForSession([file1], session); + + assert.deepEqual(events, [{ + eventName: server.ConfigFileDiagEvent, + data: { + triggerFile: file1.path, + configFileName: configFile.path, + diagnostics: [ + createCompilerDiagnostic(Diagnostics.Cannot_read_file_0, configFile.path) + ] + } + }]); }); +}); } diff --git a/src/testRunner/unittests/tsserver/declarationFileMaps.ts b/src/testRunner/unittests/tsserver/declarationFileMaps.ts index c52e2ad807a74..fb68abda4ac65 100644 --- a/src/testRunner/unittests/tsserver/declarationFileMaps.ts +++ b/src/testRunner/unittests/tsserver/declarationFileMaps.ts @@ -1,776 +1,776 @@ namespace ts.projectSystem { - function documentSpanFromSubstring({ file, text, contextText, options, contextOptions }: DocumentSpanFromSubstring): DocumentSpan { - const contextSpan = contextText !== undefined ? documentSpanFromSubstring({ file, text: contextText, options: contextOptions }) : undefined; - return { - fileName: file.path, - textSpan: textSpanFromSubstring(file.content, text, options), - ...contextSpan && { contextSpan: contextSpan.textSpan } - }; - } - - function renameLocation(input: DocumentSpanFromSubstring): RenameLocation { - return documentSpanFromSubstring(input); - } - - interface MakeReferenceEntry extends DocumentSpanFromSubstring { - isDefinition: boolean; - } - function makeReferenceEntry({ isDefinition, ...rest }: MakeReferenceEntry): ReferenceEntry { - return { - ...documentSpanFromSubstring(rest), - isDefinition, - isWriteAccess: isDefinition, - isInString: undefined, - }; - } - - function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: readonly File[]): void { - openFilesForSession([file], session); - const project = Debug.checkDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false)); - const program = project.getCurrentProgram()!; - const output = getFileEmitOutput(program, Debug.checkDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true); - closeFilesForSession([file], session); - - Debug.assert(!output.emitSkipped); - assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false }))); - } - - describe("unittests:: tsserver:: with declaration file maps:: project references", () => { - const aTs: File = { - path: "/a/a.ts", - content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};", - }; - const compilerOptions: CompilerOptions = { - outDir: "bin", - declaration: true, - declarationMap: true, - composite: true, - }; - const configContent = JSON.stringify({ compilerOptions }); - const aTsconfig: File = { path: "/a/tsconfig.json", content: configContent }; - - const aDtsMapContent: RawSourceMap = { - version: 3, - file: "a.d.ts", - sourceRoot: "", - sources: ["../a.ts"], - names: [], - mappings: "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC" - }; - const aDtsMap: File = { - path: "/a/bin/a.d.ts.map", - content: JSON.stringify(aDtsMapContent), - }; - const aDts: File = { - path: "/a/bin/a.d.ts", - // ${""} is needed to mangle the sourceMappingURL part or it breaks the build - content: `export declare function fnA(): void;\nexport interface IfaceA {\n}\nexport declare const instanceA: IfaceA;\n//# source${""}MappingURL=a.d.ts.map`, - }; - - const bTs: File = { - path: "/b/b.ts", - content: "export function fnB() {}", - }; - const bTsconfig: File = { path: "/b/tsconfig.json", content: configContent }; - - const bDtsMapContent: RawSourceMap = { - version: 3, - file: "b.d.ts", - sourceRoot: "", - sources: ["../b.ts"], - names: [], - mappings: "AAAA,wBAAgB,GAAG,SAAK", - }; - const bDtsMap: File = { - path: "/b/bin/b.d.ts.map", - content: JSON.stringify(bDtsMapContent), - }; - const bDts: File = { - // ${""} is need to mangle the sourceMappingURL part so it doesn't break the build - path: "/b/bin/b.d.ts", - content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`, - }; - - const dummyFile: File = { - path: "/dummy/dummy.ts", - content: "let a = 10;" - }; +function documentSpanFromSubstring({ file, text, contextText, options, contextOptions }: DocumentSpanFromSubstring): DocumentSpan { + const contextSpan = contextText !== undefined ? documentSpanFromSubstring({ file, text: contextText, options: contextOptions }) : undefined; + return { + fileName: file.path, + textSpan: textSpanFromSubstring(file.content, text, options), + ...contextSpan && { contextSpan: contextSpan.textSpan } + }; +} - const userTs: File = { - path: "/user/user.ts", - content: 'import * as a from "../a/bin/a";\nimport * as b from "../b/bin/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', - }; +function renameLocation(input: DocumentSpanFromSubstring): RenameLocation { + return documentSpanFromSubstring(input); +} - const userTsForConfigProject: File = { - path: "/user/user.ts", - content: 'import * as a from "../a/a";\nimport * as b from "../b/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', - }; +interface MakeReferenceEntry extends DocumentSpanFromSubstring { + isDefinition: boolean; +} +function makeReferenceEntry({ isDefinition, ...rest }: MakeReferenceEntry): ReferenceEntry { + return { + ...documentSpanFromSubstring(rest), + isDefinition, + isWriteAccess: isDefinition, + isInString: undefined, + }; +} - const userTsconfig: File = { - path: "/user/tsconfig.json", - content: JSON.stringify({ - file: ["user.ts"], - references: [{ path: "../a" }, { path: "../b" }] - }) - }; +function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: readonly File[]): void { + openFilesForSession([file], session); + const project = Debug.checkDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false)); + const program = project.getCurrentProgram()!; + const output = getFileEmitOutput(program, Debug.checkDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true); + closeFilesForSession([file], session); - function makeSampleProjects(addUserTsConfig?: boolean, keepAllFiles?: boolean) { - const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]); - const session = createSession(host); + Debug.assert(!output.emitSkipped); + assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false }))); +} - checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); - checkDeclarationFiles(bTs, session, [bDtsMap, bDts]); +describe("unittests:: tsserver:: with declaration file maps:: project references", () => { + const aTs: File = { + path: "/a/a.ts", + content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};", + }; + const compilerOptions: CompilerOptions = { + outDir: "bin", + declaration: true, + declarationMap: true, + composite: true, + }; + const configContent = JSON.stringify({ compilerOptions }); + const aTsconfig: File = { path: "/a/tsconfig.json", content: configContent }; + + const aDtsMapContent: RawSourceMap = { + version: 3, + file: "a.d.ts", + sourceRoot: "", + sources: ["../a.ts"], + names: [], + mappings: "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC" + }; + const aDtsMap: File = { + path: "/a/bin/a.d.ts.map", + content: JSON.stringify(aDtsMapContent), + }; + const aDts: File = { + path: "/a/bin/a.d.ts", + // ${""} is needed to mangle the sourceMappingURL part or it breaks the build + content: `export declare function fnA(): void;\nexport interface IfaceA {\n}\nexport declare const instanceA: IfaceA;\n//# source${""}MappingURL=a.d.ts.map`, + }; + + const bTs: File = { + path: "/b/b.ts", + content: "export function fnB() {}", + }; + const bTsconfig: File = { path: "/b/tsconfig.json", content: configContent }; + + const bDtsMapContent: RawSourceMap = { + version: 3, + file: "b.d.ts", + sourceRoot: "", + sources: ["../b.ts"], + names: [], + mappings: "AAAA,wBAAgB,GAAG,SAAK", + }; + const bDtsMap: File = { + path: "/b/bin/b.d.ts.map", + content: JSON.stringify(bDtsMapContent), + }; + const bDts: File = { + // ${""} is need to mangle the sourceMappingURL part so it doesn't break the build + path: "/b/bin/b.d.ts", + content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`, + }; + + const dummyFile: File = { + path: "/dummy/dummy.ts", + content: "let a = 10;" + }; + + const userTs: File = { + path: "/user/user.ts", + content: 'import * as a from "../a/bin/a";\nimport * as b from "../b/bin/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', + }; + + const userTsForConfigProject: File = { + path: "/user/user.ts", + content: 'import * as a from "../a/a";\nimport * as b from "../b/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', + }; + + const userTsconfig: File = { + path: "/user/tsconfig.json", + content: JSON.stringify({ + file: ["user.ts"], + references: [{ path: "../a" }, { path: "../b" }] + }) + }; + + function makeSampleProjects(addUserTsConfig?: boolean, keepAllFiles?: boolean) { + const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]); + const session = createSession(host); + + checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); + checkDeclarationFiles(bTs, session, [bDtsMap, bDts]); + + // Testing what happens if we delete the original sources. + if (!keepAllFiles) { + host.deleteFile(bTs.path); + } - // Testing what happens if we delete the original sources. - if (!keepAllFiles) { - host.deleteFile(bTs.path); - } + openFilesForSession([userTs], session); + const service = session.getProjectService(); + // If config file then userConfig project and bConfig project since it is referenced + checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 2 } : { inferredProjects: 1 }); + return session; + } - openFilesForSession([userTs], session); - const service = session.getProjectService(); - // If config file then userConfig project and bConfig project since it is referenced - checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 2 } : { inferredProjects: 1 }); - return session; - } + function verifyInferredProjectUnchanged(session: TestSession) { + checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]); + } - function verifyInferredProjectUnchanged(session: TestSession) { - checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]); - } + function verifyDummyProject(session: TestSession) { + checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]); + } - function verifyDummyProject(session: TestSession) { - checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]); - } + function verifyOnlyOrphanInferredProject(session: TestSession) { + openFilesForSession([dummyFile], session); + checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); + verifyDummyProject(session); + } - function verifyOnlyOrphanInferredProject(session: TestSession) { - openFilesForSession([dummyFile], session); - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); - verifyDummyProject(session); - } + function verifySingleInferredProject(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); + verifyInferredProjectUnchanged(session); - function verifySingleInferredProject(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); - verifyInferredProjectUnchanged(session); + // Close user file should close all the projects after opening dummy file + closeFilesForSession([userTs], session); + verifyOnlyOrphanInferredProject(session); + } - // Close user file should close all the projects after opening dummy file - closeFilesForSession([userTs], session); - verifyOnlyOrphanInferredProject(session); - } + function verifyATsConfigProject(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]); + } - function verifyATsConfigProject(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]); - } + function verifyATsConfigOriginalProject(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); + verifyInferredProjectUnchanged(session); + verifyATsConfigProject(session); + // Close user file should close all the projects + closeFilesForSession([userTs], session); + verifyOnlyOrphanInferredProject(session); + } - function verifyATsConfigOriginalProject(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); - verifyInferredProjectUnchanged(session); - verifyATsConfigProject(session); - // Close user file should close all the projects - closeFilesForSession([userTs], session); - verifyOnlyOrphanInferredProject(session); - } + function verifyATsConfigWhenOpened(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); + verifyInferredProjectUnchanged(session); + verifyATsConfigProject(session); - function verifyATsConfigWhenOpened(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); - verifyInferredProjectUnchanged(session); - verifyATsConfigProject(session); + closeFilesForSession([userTs], session); + openFilesForSession([dummyFile], session); + checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); + verifyDummyProject(session); + verifyATsConfigProject(session); // ATsConfig should still be alive + } - closeFilesForSession([userTs], session); - openFilesForSession([dummyFile], session); - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); - verifyDummyProject(session); - verifyATsConfigProject(session); // ATsConfig should still be alive - } + function verifyUserTsConfigProject(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aTs.path, userTsconfig.path]); + } - function verifyUserTsConfigProject(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aTs.path, userTsconfig.path]); - } + it("goToDefinition", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.Definition, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, [ + protocolFileSpanWithContextFromSubstring({ + file: aTs, + text: "fnA", + contextText: "export function fnA() {}" + }) + ]); + verifySingleInferredProject(session); + }); - it("goToDefinition", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.Definition, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, [ + it("getDefinitionAndBoundSpan", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), + definitions: [ protocolFileSpanWithContextFromSubstring({ file: aTs, text: "fnA", contextText: "export function fnA() {}" }) - ]); - verifySingleInferredProject(session); - }); - - it("getDefinitionAndBoundSpan", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, { - textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), - definitions: [ - protocolFileSpanWithContextFromSubstring({ - file: aTs, - text: "fnA", - contextText: "export function fnA() {}" - }) - ], - }); - verifySingleInferredProject(session); - }); - - it("getDefinitionAndBoundSpan with file navigation", () => { - const session = makeSampleProjects(/*addUserTsConfig*/ true); - const response = executeSessionRequest(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, { - textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), - definitions: [ - protocolFileSpanWithContextFromSubstring({ - file: aTs, - text: "fnA", - contextText: "export function fnA() {}" - }) - ], - }); - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - verifyUserTsConfigProject(session); - - // Navigate to the definition - closeFilesForSession([userTs], session); - openFilesForSession([aTs], session); - - // UserTs configured project should be alive - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); - verifyUserTsConfigProject(session); - verifyATsConfigProject(session); - - closeFilesForSession([aTs], session); - verifyOnlyOrphanInferredProject(session); - }); - - it("goToType", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.TypeDefinition, protocolFileLocationFromSubstring(userTs, "instanceA")); - assert.deepEqual(response, [ - protocolFileSpanWithContextFromSubstring({ - file: aTs, - text: "IfaceA", - contextText: "export interface IfaceA {}" - }) - ]); - verifySingleInferredProject(session); + ], }); + verifySingleInferredProject(session); + }); - it("goToImplementation", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.Implementation, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, [ + it("getDefinitionAndBoundSpan with file navigation", () => { + const session = makeSampleProjects(/*addUserTsConfig*/ true); + const response = executeSessionRequest(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), + definitions: [ protocolFileSpanWithContextFromSubstring({ file: aTs, text: "fnA", contextText: "export function fnA() {}" - })]); - verifySingleInferredProject(session); - }); - - it("goToDefinition -- target does not exist", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, CommandNames.Definition, protocolFileLocationFromSubstring(userTs, "fnB()")); - // bTs does not exist, so stick with bDts - assert.deepEqual(response, [ - protocolFileSpanWithContextFromSubstring({ - file: bDts, - text: "fnB", - contextText: "export declare function fnB(): void;" }) - ]); - verifySingleInferredProject(session); + ], }); + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + verifyUserTsConfigProject(session); - it("navigateTo", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, CommandNames.Navto, { file: userTs.path, searchValue: "fn" }); - assert.deepEqual(response, [ - { - ...protocolFileSpanFromSubstring({ - file: bDts, - text: "export declare function fnB(): void;" - }), - name: "fnB", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export,declare", - }, - { - ...protocolFileSpanFromSubstring({ - file: userTs, - text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" - }), - name: "fnUser", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - }, - { - ...protocolFileSpanFromSubstring({ - file: aTs, - text: "export function fnA() {}" - }), - name: "fnA", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - }, - ]); + // Navigate to the definition + closeFilesForSession([userTs], session); + openFilesForSession([aTs], session); - verifyATsConfigOriginalProject(session); - }); + // UserTs configured project should be alive + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); + verifyUserTsConfigProject(session); + verifyATsConfigProject(session); - it("navigateToAll -- when neither file nor project is specified", () => { - const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true); - const response = executeSessionRequest(session, CommandNames.Navto, { file: undefined, searchValue: "fn" }); - assert.deepEqual(response, [ - { - ...protocolFileSpanFromSubstring({ - file: bTs, - text: "export function fnB() {}" - }), - name: "fnB", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - }, - { - ...protocolFileSpanFromSubstring({ - file: aTs, - text: "export function fnA() {}" - }), - name: "fnA", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - }, - { - ...protocolFileSpanFromSubstring({ - file: userTs, - text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" - }), - name: "fnUser", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - } - ]); - }); + closeFilesForSession([aTs], session); + verifyOnlyOrphanInferredProject(session); + }); - it("navigateToAll -- when file is not specified but project is", () => { - const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true); - const response = executeSessionRequest(session, CommandNames.Navto, { projectFileName: bTsconfig.path, file: undefined, searchValue: "fn" }); - assert.deepEqual(response, [ - { - ...protocolFileSpanFromSubstring({ - file: bTs, - text: "export function fnB() {}" - }), - name: "fnB", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - } - ]); - }); + it("goToType", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.TypeDefinition, protocolFileLocationFromSubstring(userTs, "instanceA")); + assert.deepEqual(response, [ + protocolFileSpanWithContextFromSubstring({ + file: aTs, + text: "IfaceA", + contextText: "export interface IfaceA {}" + }) + ]); + verifySingleInferredProject(session); + }); - const referenceATs = (aTs: File): protocol.ReferencesResponseItem => makeReferenceItem({ - file: aTs, - isDefinition: true, - text: "fnA", - contextText: "export function fnA() {}", - lineText: "export function fnA() {}" - }); - const referencesUserTs = (userTs: File): readonly protocol.ReferencesResponseItem[] => [ - makeReferenceItem({ - file: userTs, - isDefinition: false, + it("goToImplementation", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.Implementation, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, [ + protocolFileSpanWithContextFromSubstring({ + file: aTs, text: "fnA", - lineText: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" - }), - ]; + contextText: "export function fnA() {}" + })]); + verifySingleInferredProject(session); + }); - it("findAllReferences", () => { - const session = makeSampleProjects(); + it("goToDefinition -- target does not exist", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, CommandNames.Definition, protocolFileLocationFromSubstring(userTs, "fnB()")); + // bTs does not exist, so stick with bDts + assert.deepEqual(response, [ + protocolFileSpanWithContextFromSubstring({ + file: bDts, + text: "fnB", + contextText: "export declare function fnB(): void;" + }) + ]); + verifySingleInferredProject(session); + }); - const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, { - refs: [...referencesUserTs(userTs), referenceATs(aTs)], - symbolName: "fnA", - symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnA()").offset, - symbolDisplayString: "function fnA(): void", - }); + it("navigateTo", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, CommandNames.Navto, { file: userTs.path, searchValue: "fn" }); + assert.deepEqual(response, [ + { + ...protocolFileSpanFromSubstring({ + file: bDts, + text: "export declare function fnB(): void;" + }), + name: "fnB", + matchKind: "prefix", + isCaseSensitive: true, + kind: ScriptElementKind.functionElement, + kindModifiers: "export,declare", + }, + { + ...protocolFileSpanFromSubstring({ + file: userTs, + text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" + }), + name: "fnUser", + matchKind: "prefix", + isCaseSensitive: true, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + }, + { + ...protocolFileSpanFromSubstring({ + file: aTs, + text: "export function fnA() {}" + }), + name: "fnA", + matchKind: "prefix", + isCaseSensitive: true, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + }, + ]); + + verifyATsConfigOriginalProject(session); + }); + + it("navigateToAll -- when neither file nor project is specified", () => { + const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true); + const response = executeSessionRequest(session, CommandNames.Navto, { file: undefined, searchValue: "fn" }); + assert.deepEqual(response, [ + { + ...protocolFileSpanFromSubstring({ + file: bTs, + text: "export function fnB() {}" + }), + name: "fnB", + matchKind: "prefix", + isCaseSensitive: true, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + }, + { + ...protocolFileSpanFromSubstring({ + file: aTs, + text: "export function fnA() {}" + }), + name: "fnA", + matchKind: "prefix", + isCaseSensitive: true, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + }, + { + ...protocolFileSpanFromSubstring({ + file: userTs, + text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" + }), + name: "fnUser", + matchKind: "prefix", + isCaseSensitive: true, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + } + ]); + }); - verifyATsConfigOriginalProject(session); + it("navigateToAll -- when file is not specified but project is", () => { + const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true); + const response = executeSessionRequest(session, CommandNames.Navto, { projectFileName: bTsconfig.path, file: undefined, searchValue: "fn" }); + assert.deepEqual(response, [ + { + ...protocolFileSpanFromSubstring({ + file: bTs, + text: "export function fnB() {}" + }), + name: "fnB", + matchKind: "prefix", + isCaseSensitive: true, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + } + ]); + }); + + const referenceATs = (aTs: File): protocol.ReferencesResponseItem => makeReferenceItem({ + file: aTs, + isDefinition: true, + text: "fnA", + contextText: "export function fnA() {}", + lineText: "export function fnA() {}" + }); + const referencesUserTs = (userTs: File): readonly protocol.ReferencesResponseItem[] => [ + makeReferenceItem({ + file: userTs, + isDefinition: false, + text: "fnA", + lineText: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" + }), + ]; + + it("findAllReferences", () => { + const session = makeSampleProjects(); + + const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + refs: [...referencesUserTs(userTs), referenceATs(aTs)], + symbolName: "fnA", + symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnA()").offset, + symbolDisplayString: "function fnA(): void", }); - it("findAllReferences -- starting at definition", () => { - const session = makeSampleProjects(); - openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. - const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(aTs, "fnA")); - assert.deepEqual(response, { - refs: [referenceATs(aTs), ...referencesUserTs(userTs)], - symbolName: "fnA", - symbolStartOffset: protocolLocationFromSubstring(aTs.content, "fnA").offset, - symbolDisplayString: "function fnA(): void", - }); - verifyATsConfigWhenOpened(session); + verifyATsConfigOriginalProject(session); + }); + + it("findAllReferences -- starting at definition", () => { + const session = makeSampleProjects(); + openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. + const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(aTs, "fnA")); + assert.deepEqual(response, { + refs: [referenceATs(aTs), ...referencesUserTs(userTs)], + symbolName: "fnA", + symbolStartOffset: protocolLocationFromSubstring(aTs.content, "fnA").offset, + symbolDisplayString: "function fnA(): void", }); + verifyATsConfigWhenOpened(session); + }); - interface ReferencesFullRequest extends protocol.FileLocationRequest { readonly command: protocol.CommandTypes.ReferencesFull; } - interface ReferencesFullResponse extends protocol.Response { readonly body: readonly ReferencedSymbol[]; } + interface ReferencesFullRequest extends protocol.FileLocationRequest { readonly command: protocol.CommandTypes.ReferencesFull; } + interface ReferencesFullResponse extends protocol.Response { readonly body: readonly ReferencedSymbol[]; } - it("findAllReferencesFull", () => { - const session = makeSampleProjects(); + it("findAllReferencesFull", () => { + const session = makeSampleProjects(); - const responseFull = executeSessionRequest(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(userTs, "fnA()")); + const responseFull = executeSessionRequest(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(responseFull, [ - { - definition: { - ...documentSpanFromSubstring({ - file: aTs, - text: "fnA", - contextText: "export function fnA() {}" - }), - kind: ScriptElementKind.functionElement, - name: "function fnA(): void", - containerKind: ScriptElementKind.unknown, - containerName: "", - displayParts: [ - keywordPart(SyntaxKind.FunctionKeyword), - spacePart(), - displayPart("fnA", SymbolDisplayPartKind.functionName), - punctuationPart(SyntaxKind.OpenParenToken), - punctuationPart(SyntaxKind.CloseParenToken), - punctuationPart(SyntaxKind.ColonToken), - spacePart(), - keywordPart(SyntaxKind.VoidKeyword), - ], - }, - references: [ - makeReferenceEntry({ file: userTs, /*isDefinition*/ isDefinition: false, text: "fnA" }), - makeReferenceEntry({ file: aTs, /*isDefinition*/ isDefinition: true, text: "fnA", contextText: "export function fnA() {}" }), + assert.deepEqual(responseFull, [ + { + definition: { + ...documentSpanFromSubstring({ + file: aTs, + text: "fnA", + contextText: "export function fnA() {}" + }), + kind: ScriptElementKind.functionElement, + name: "function fnA(): void", + containerKind: ScriptElementKind.unknown, + containerName: "", + displayParts: [ + keywordPart(SyntaxKind.FunctionKeyword), + spacePart(), + displayPart("fnA", SymbolDisplayPartKind.functionName), + punctuationPart(SyntaxKind.OpenParenToken), + punctuationPart(SyntaxKind.CloseParenToken), + punctuationPart(SyntaxKind.ColonToken), + spacePart(), + keywordPart(SyntaxKind.VoidKeyword), ], }, - ]); - verifyATsConfigOriginalProject(session); - }); + references: [ + makeReferenceEntry({ file: userTs, /*isDefinition*/ isDefinition: false, text: "fnA" }), + makeReferenceEntry({ file: aTs, /*isDefinition*/ isDefinition: true, text: "fnA", contextText: "export function fnA() {}" }), + ], + }, + ]); + verifyATsConfigOriginalProject(session); + }); - it("findAllReferencesFull definition is in mapped file", () => { - const aTs: File = { path: "/a/a.ts", content: `function f() {}` }; - const aTsconfig: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ compilerOptions: { declaration: true, declarationMap: true, outFile: "../bin/a.js" } }), - }; - const bTs: File = { path: "/b/b.ts", content: `f();` }; - const bTsconfig: File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) }; - const aDts: File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` }; - const aDtsMap: File = { - path: "/bin/a.d.ts.map", - content: JSON.stringify({ version: 3, file: "a.d.ts", sourceRoot: "", sources: ["../a/a.ts"], names: [], mappings: "AAAA,iBAAS,CAAC,SAAK" }), - }; - - const session = createSession(createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap])); - checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); - openFilesForSession([bTs], session); - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); // configured project of b is alive since a references b - - const responseFull = executeSessionRequest(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(bTs, "f()")); - - assert.deepEqual(responseFull, [ - { - definition: { - ...documentSpanFromSubstring({ - file: aTs, - text: "f", - options: { index: 1 }, - contextText: "function f() {}" - }), - containerKind: ScriptElementKind.unknown, - containerName: "", - displayParts: [ - keywordPart(SyntaxKind.FunctionKeyword), - spacePart(), - displayPart("f", SymbolDisplayPartKind.functionName), - punctuationPart(SyntaxKind.OpenParenToken), - punctuationPart(SyntaxKind.CloseParenToken), - punctuationPart(SyntaxKind.ColonToken), - spacePart(), - keywordPart(SyntaxKind.VoidKeyword), - ], - kind: ScriptElementKind.functionElement, - name: "function f(): void", - }, - references: [ - makeReferenceEntry({ - file: aTs, - text: "f", - options: { index: 1 }, - contextText: "function f() {}", - isDefinition: true - }), - { - fileName: bTs.path, - isDefinition: false, - isInString: undefined, - isWriteAccess: false, - textSpan: { start: 0, length: 1 }, - }, - ], - } - ]); - }); + it("findAllReferencesFull definition is in mapped file", () => { + const aTs: File = { path: "/a/a.ts", content: `function f() {}` }; + const aTsconfig: File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ compilerOptions: { declaration: true, declarationMap: true, outFile: "../bin/a.js" } }), + }; + const bTs: File = { path: "/b/b.ts", content: `f();` }; + const bTsconfig: File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) }; + const aDts: File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` }; + const aDtsMap: File = { + path: "/bin/a.d.ts.map", + content: JSON.stringify({ version: 3, file: "a.d.ts", sourceRoot: "", sources: ["../a/a.ts"], names: [], mappings: "AAAA,iBAAS,CAAC,SAAK" }), + }; - it("findAllReferences -- target does not exist", () => { - const session = makeSampleProjects(); + const session = createSession(createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap])); + checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); + openFilesForSession([bTs], session); + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); // configured project of b is alive since a references b - const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnB()")); - assert.deepEqual(response, { - refs: [ - makeReferenceItem({ - file: bDts, - isDefinition: true, - text: "fnB", - contextText: "export declare function fnB(): void;", - lineText: "export declare function fnB(): void;" + const responseFull = executeSessionRequest(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(bTs, "f()")); + + assert.deepEqual(responseFull, [ + { + definition: { + ...documentSpanFromSubstring({ + file: aTs, + text: "f", + options: { index: 1 }, + contextText: "function f() {}" }), - makeReferenceItem({ - file: userTs, - isDefinition: false, - text: "fnB", - lineText: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" + containerKind: ScriptElementKind.unknown, + containerName: "", + displayParts: [ + keywordPart(SyntaxKind.FunctionKeyword), + spacePart(), + displayPart("f", SymbolDisplayPartKind.functionName), + punctuationPart(SyntaxKind.OpenParenToken), + punctuationPart(SyntaxKind.CloseParenToken), + punctuationPart(SyntaxKind.ColonToken), + spacePart(), + keywordPart(SyntaxKind.VoidKeyword), + ], + kind: ScriptElementKind.functionElement, + name: "function f(): void", + }, + references: [ + makeReferenceEntry({ + file: aTs, + text: "f", + options: { index: 1 }, + contextText: "function f() {}", + isDefinition: true }), + { + fileName: bTs.path, + isDefinition: false, + isInString: undefined, + isWriteAccess: false, + textSpan: { start: 0, length: 1 }, + }, ], - symbolName: "fnB", - symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnB()").offset, - symbolDisplayString: "function fnB(): void", - }); - verifySingleInferredProject(session); - }); + } + ]); + }); - const renameATs = (aTs: File): protocol.SpanGroup => ({ - file: aTs.path, - locs: [ - protocolRenameSpanFromSubstring({ - fileText: aTs.content, - text: "fnA", - contextText: "export function fnA() {}" - }) - ], - }); - const renameUserTs = (userTs: File): protocol.SpanGroup => ({ - file: userTs.path, - locs: [ - protocolRenameSpanFromSubstring({ - fileText: userTs.content, - text: "fnA" - }) + it("findAllReferences -- target does not exist", () => { + const session = makeSampleProjects(); + + const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnB()")); + assert.deepEqual(response, { + refs: [ + makeReferenceItem({ + file: bDts, + isDefinition: true, + text: "fnB", + contextText: "export declare function fnB(): void;", + lineText: "export declare function fnB(): void;" + }), + makeReferenceItem({ + file: userTs, + isDefinition: false, + text: "fnB", + lineText: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" + }), ], + symbolName: "fnB", + symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnB()").offset, + symbolDisplayString: "function fnB(): void", }); + verifySingleInferredProject(session); + }); - it("renameLocations", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, { - info: { - canRename: true, - displayName: "fnA", - fileToRename: undefined, - fullDisplayName: '"/a/bin/a".fnA', // Ideally this would use the original source's path instead of the declaration file's path. - kind: ScriptElementKind.functionElement, - kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","), - triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), - }, - locs: [renameUserTs(userTs), renameATs(aTs)], - }); - verifyATsConfigOriginalProject(session); - }); + const renameATs = (aTs: File): protocol.SpanGroup => ({ + file: aTs.path, + locs: [ + protocolRenameSpanFromSubstring({ + fileText: aTs.content, + text: "fnA", + contextText: "export function fnA() {}" + }) + ], + }); + const renameUserTs = (userTs: File): protocol.SpanGroup => ({ + file: userTs.path, + locs: [ + protocolRenameSpanFromSubstring({ + fileText: userTs.content, + text: "fnA" + }) + ], + }); - it("renameLocations -- starting at definition", () => { - const session = makeSampleProjects(); - openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. - const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "fnA")); - assert.deepEqual(response, { - info: { - canRename: true, - displayName: "fnA", - fileToRename: undefined, - fullDisplayName: '"/a/a".fnA', - kind: ScriptElementKind.functionElement, - kindModifiers: ScriptElementKindModifier.exportedModifier, - triggerSpan: protocolTextSpanFromSubstring(aTs.content, "fnA"), - }, - locs: [renameATs(aTs), renameUserTs(userTs)], - }); - verifyATsConfigWhenOpened(session); + it("renameLocations", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + info: { + canRename: true, + displayName: "fnA", + fileToRename: undefined, + fullDisplayName: '"/a/bin/a".fnA', // Ideally this would use the original source's path instead of the declaration file's path. + kind: ScriptElementKind.functionElement, + kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","), + triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), + }, + locs: [renameUserTs(userTs), renameATs(aTs)], }); + verifyATsConfigOriginalProject(session); + }); - it("renameLocationsFull", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.RenameLocationsFull, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, [ - renameLocation({ file: userTs, text: "fnA" }), - renameLocation({ file: aTs, text: "fnA", contextText: "export function fnA() {}" }), - ]); - verifyATsConfigOriginalProject(session); + it("renameLocations -- starting at definition", () => { + const session = makeSampleProjects(); + openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. + const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "fnA")); + assert.deepEqual(response, { + info: { + canRename: true, + displayName: "fnA", + fileToRename: undefined, + fullDisplayName: '"/a/a".fnA', + kind: ScriptElementKind.functionElement, + kindModifiers: ScriptElementKindModifier.exportedModifier, + triggerSpan: protocolTextSpanFromSubstring(aTs.content, "fnA"), + }, + locs: [renameATs(aTs), renameUserTs(userTs)], }); + verifyATsConfigWhenOpened(session); + }); - it("renameLocations -- target does not exist", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnB()")); - assert.deepEqual(response, { - info: { - canRename: true, - displayName: "fnB", - fileToRename: undefined, - fullDisplayName: '"/b/bin/b".fnB', - kind: ScriptElementKind.functionElement, - kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","), - triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"), - }, - locs: [ - { - file: bDts.path, - locs: [ - protocolRenameSpanFromSubstring({ - fileText: bDts.content, - text: "fnB", - contextText: "export declare function fnB(): void;" - }) - ], - }, - { - file: userTs.path, - locs: [ - protocolRenameSpanFromSubstring({ - fileText: userTs.content, - text: "fnB" - }) - ], - }, - ], - }); - verifySingleInferredProject(session); - }); + it("renameLocationsFull", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.RenameLocationsFull, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, [ + renameLocation({ file: userTs, text: "fnA" }), + renameLocation({ file: aTs, text: "fnA", contextText: "export function fnA() {}" }), + ]); + verifyATsConfigOriginalProject(session); + }); - it("getEditsForFileRename", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.GetEditsForFileRename, { - oldFilePath: aTs.path, - newFilePath: "/a/aNew.ts", - }); - assert.deepEqual(response, [ + it("renameLocations -- target does not exist", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnB()")); + assert.deepEqual(response, { + info: { + canRename: true, + displayName: "fnB", + fileToRename: undefined, + fullDisplayName: '"/b/bin/b".fnB', + kind: ScriptElementKind.functionElement, + kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","), + triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"), + }, + locs: [ { - fileName: userTs.path, - textChanges: [ - { ...protocolTextSpanFromSubstring(userTs.content, "../a/bin/a"), newText: "../a/bin/aNew" }, + file: bDts.path, + locs: [ + protocolRenameSpanFromSubstring({ + fileText: bDts.content, + text: "fnB", + contextText: "export declare function fnB(): void;" + }) ], }, - ]); - verifySingleInferredProject(session); + { + file: userTs.path, + locs: [ + protocolRenameSpanFromSubstring({ + fileText: userTs.content, + text: "fnB" + }) + ], + }, + ], }); + verifySingleInferredProject(session); + }); - it("getEditsForFileRename when referencing project doesnt include file and its renamed", () => { - const aTs: File = { path: "/a/src/a.ts", content: "" }; - const aTsconfig: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - composite: true, - declaration: true, - declarationMap: true, - outDir: "./build", - } - }), - }; - const bTs: File = { path: "/b/src/b.ts", content: "" }; - const bTsconfig: File = { - path: "/b/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "./build", - }, - include: ["./src"], - references: [{ path: "../a" }], - }), - }; - - const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig]); - const session = createSession(host); - openFilesForSession([aTs, bTs], session); - const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { - oldFilePath: aTs.path, - newFilePath: "/a/src/a1.ts", - }); - assert.deepEqual(response, []); // Should not change anything + it("getEditsForFileRename", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.GetEditsForFileRename, { + oldFilePath: aTs.path, + newFilePath: "/a/aNew.ts", }); + assert.deepEqual(response, [ + { + fileName: userTs.path, + textChanges: [ + { ...protocolTextSpanFromSubstring(userTs.content, "../a/bin/a"), newText: "../a/bin/aNew" }, + ], + }, + ]); + verifySingleInferredProject(session); + }); - it("does not jump to source if inlined sources", () => { - const aDtsInlinedSources: RawSourceMap = { - ...aDtsMapContent, - sourcesContent: [aTs.content] - }; - const aDtsMapInlinedSources: File = { - path: aDtsMap.path, - content: JSON.stringify(aDtsInlinedSources) - }; - const host = createServerHost([aTs, aDtsMapInlinedSources, aDts, bTs, bDtsMap, bDts, userTs, dummyFile]); - const session = createSession(host); - - openFilesForSession([userTs], session); - const service = session.getProjectService(); - // If config file then userConfig project and bConfig project since it is referenced - checkNumberOfProjects(service, { inferredProjects: 1 }); - - // Inlined so does not jump to aTs - assert.deepEqual( - executeSessionRequest( - session, - protocol.CommandTypes.DefinitionAndBoundSpan, - protocolFileLocationFromSubstring(userTs, "fnA()") - ), - { - textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), - definitions: [ - protocolFileSpanWithContextFromSubstring({ - file: aDts, - text: "fnA", - contextText: "export declare function fnA(): void;" - }) - ], - } - ); - - // Not inlined, jumps to bTs - assert.deepEqual( - executeSessionRequest( - session, - protocol.CommandTypes.DefinitionAndBoundSpan, - protocolFileLocationFromSubstring(userTs, "fnB()") - ), - { - textSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"), - definitions: [ - protocolFileSpanWithContextFromSubstring({ - file: bTs, - text: "fnB", - contextText: "export function fnB() {}" - }) - ], + it("getEditsForFileRename when referencing project doesnt include file and its renamed", () => { + const aTs: File = { path: "/a/src/a.ts", content: "" }; + const aTsconfig: File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + composite: true, + declaration: true, + declarationMap: true, + outDir: "./build", } - ); + }), + }; + const bTs: File = { path: "/b/src/b.ts", content: "" }; + const bTsconfig: File = { + path: "/b/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "./build", + }, + include: ["./src"], + references: [{ path: "../a" }], + }), + }; - verifySingleInferredProject(session); + const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig]); + const session = createSession(host); + openFilesForSession([aTs, bTs], session); + const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { + oldFilePath: aTs.path, + newFilePath: "/a/src/a1.ts", }); + assert.deepEqual(response, []); // Should not change anything + }); + + it("does not jump to source if inlined sources", () => { + const aDtsInlinedSources: RawSourceMap = { + ...aDtsMapContent, + sourcesContent: [aTs.content] + }; + const aDtsMapInlinedSources: File = { + path: aDtsMap.path, + content: JSON.stringify(aDtsInlinedSources) + }; + const host = createServerHost([aTs, aDtsMapInlinedSources, aDts, bTs, bDtsMap, bDts, userTs, dummyFile]); + const session = createSession(host); + + openFilesForSession([userTs], session); + const service = session.getProjectService(); + // If config file then userConfig project and bConfig project since it is referenced + checkNumberOfProjects(service, { inferredProjects: 1 }); + + // Inlined so does not jump to aTs + assert.deepEqual( + executeSessionRequest( + session, + protocol.CommandTypes.DefinitionAndBoundSpan, + protocolFileLocationFromSubstring(userTs, "fnA()") + ), + { + textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), + definitions: [ + protocolFileSpanWithContextFromSubstring({ + file: aDts, + text: "fnA", + contextText: "export declare function fnA(): void;" + }) + ], + } + ); + + // Not inlined, jumps to bTs + assert.deepEqual( + executeSessionRequest( + session, + protocol.CommandTypes.DefinitionAndBoundSpan, + protocolFileLocationFromSubstring(userTs, "fnB()") + ), + { + textSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"), + definitions: [ + protocolFileSpanWithContextFromSubstring({ + file: bTs, + text: "fnB", + contextText: "export function fnB() {}" + }) + ], + } + ); + + verifySingleInferredProject(session); }); +}); } diff --git a/src/testRunner/unittests/tsserver/documentRegistry.ts b/src/testRunner/unittests/tsserver/documentRegistry.ts index 8c29af51a27a0..c0eb6ba2ee375 100644 --- a/src/testRunner/unittests/tsserver/documentRegistry.ts +++ b/src/testRunner/unittests/tsserver/documentRegistry.ts @@ -1,93 +1,93 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: document registry in project service", () => { - const importModuleContent = `import {a} from "./module1"`; - const file: File = { - path: `${tscWatch.projectRoot}/index.ts`, - content: importModuleContent - }; - const moduleFile: File = { - path: `${tscWatch.projectRoot}/module1.d.ts`, - content: "export const a: number;" - }; - const configFile: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ files: ["index.ts"] }) - }; +describe("unittests:: tsserver:: document registry in project service", () => { + const importModuleContent = `import {a} from "./module1"`; + const file: File = { + path: `${tscWatch.projectRoot}/index.ts`, + content: importModuleContent + }; + const moduleFile: File = { + path: `${tscWatch.projectRoot}/module1.d.ts`, + content: "export const a: number;" + }; + const configFile: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ files: ["index.ts"] }) + }; - function getProject(service: TestProjectService) { - return service.configuredProjects.get(configFile.path)!; - } + function getProject(service: TestProjectService) { + return service.configuredProjects.get(configFile.path)!; + } - function checkProject(service: TestProjectService, moduleIsOrphan: boolean) { - // Update the project - const project = getProject(service); - project.getLanguageService(); - checkProjectActualFiles(project, [file.path, libFile.path, configFile.path, ...(moduleIsOrphan ? [] : [moduleFile.path])]); - const moduleInfo = service.getScriptInfo(moduleFile.path)!; - assert.isDefined(moduleInfo); - assert.equal(moduleInfo.isOrphan(), moduleIsOrphan); - const key = service.documentRegistry.getKeyForCompilationSettings(project.getCompilationSettings()); - assert.deepEqual(service.documentRegistry.getLanguageServiceRefCounts(moduleInfo.path, moduleInfo.scriptKind), [[key, moduleIsOrphan ? undefined : 1]]); - } + function checkProject(service: TestProjectService, moduleIsOrphan: boolean) { + // Update the project + const project = getProject(service); + project.getLanguageService(); + checkProjectActualFiles(project, [file.path, libFile.path, configFile.path, ...(moduleIsOrphan ? [] : [moduleFile.path])]); + const moduleInfo = service.getScriptInfo(moduleFile.path)!; + assert.isDefined(moduleInfo); + assert.equal(moduleInfo.isOrphan(), moduleIsOrphan); + const key = service.documentRegistry.getKeyForCompilationSettings(project.getCompilationSettings()); + assert.deepEqual(service.documentRegistry.getLanguageServiceRefCounts(moduleInfo.path, moduleInfo.scriptKind), [[key, moduleIsOrphan ? undefined : 1]]); + } - function createServiceAndHost() { - const host = createServerHost([file, moduleFile, libFile, configFile]); - const service = createProjectService(host); - service.openClientFile(file.path); - checkProject(service, /*moduleIsOrphan*/ false); - return { host, service }; - } + function createServiceAndHost() { + const host = createServerHost([file, moduleFile, libFile, configFile]); + const service = createProjectService(host); + service.openClientFile(file.path); + checkProject(service, /*moduleIsOrphan*/ false); + return { host, service }; + } - function changeFileToNotImportModule(service: TestProjectService) { - const info = service.getScriptInfo(file.path)!; - service.applyChangesToFile(info, singleIterator({ span: { start: 0, length: importModuleContent.length }, newText: "" })); - checkProject(service, /*moduleIsOrphan*/ true); - } + function changeFileToNotImportModule(service: TestProjectService) { + const info = service.getScriptInfo(file.path)!; + service.applyChangesToFile(info, singleIterator({ span: { start: 0, length: importModuleContent.length }, newText: "" })); + checkProject(service, /*moduleIsOrphan*/ true); + } - function changeFileToImportModule(service: TestProjectService) { - const info = service.getScriptInfo(file.path)!; - service.applyChangesToFile(info, singleIterator({ span: { start: 0, length: 0 }, newText: importModuleContent })); - checkProject(service, /*moduleIsOrphan*/ false); - } + function changeFileToImportModule(service: TestProjectService) { + const info = service.getScriptInfo(file.path)!; + service.applyChangesToFile(info, singleIterator({ span: { start: 0, length: 0 }, newText: importModuleContent })); + checkProject(service, /*moduleIsOrphan*/ false); + } - it("Caches the source file if script info is orphan", () => { - const { service } = createServiceAndHost(); - const project = getProject(service); + it("Caches the source file if script info is orphan", () => { + const { service } = createServiceAndHost(); + const project = getProject(service); - const moduleInfo = service.getScriptInfo(moduleFile.path)!; - const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; - assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); + const moduleInfo = service.getScriptInfo(moduleFile.path)!; + const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; + assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); - // edit file - changeFileToNotImportModule(service); - assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); + // edit file + changeFileToNotImportModule(service); + assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); - // write content back - changeFileToImportModule(service); - assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); - assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); - }); + // write content back + changeFileToImportModule(service); + assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); + assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); + }); - it("Caches the source file if script info is orphan, and orphan script info changes", () => { - const { host, service } = createServiceAndHost(); - const project = getProject(service); + it("Caches the source file if script info is orphan, and orphan script info changes", () => { + const { host, service } = createServiceAndHost(); + const project = getProject(service); - const moduleInfo = service.getScriptInfo(moduleFile.path)!; - const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; - assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); + const moduleInfo = service.getScriptInfo(moduleFile.path)!; + const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; + assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); - // edit file - changeFileToNotImportModule(service); - assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); + // edit file + changeFileToNotImportModule(service); + assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); - const updatedModuleContent = moduleFile.content + "\nexport const b: number;"; - host.writeFile(moduleFile.path, updatedModuleContent); + const updatedModuleContent = moduleFile.content + "\nexport const b: number;"; + host.writeFile(moduleFile.path, updatedModuleContent); - // write content back - changeFileToImportModule(service); - assert.notEqual(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); - assert.equal(project.getSourceFile(moduleInfo.path), moduleInfo.cacheSourceFile!.sourceFile); - assert.equal(moduleInfo.cacheSourceFile!.sourceFile.text, updatedModuleContent); - }); + // write content back + changeFileToImportModule(service); + assert.notEqual(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); + assert.equal(project.getSourceFile(moduleInfo.path), moduleInfo.cacheSourceFile!.sourceFile); + assert.equal(moduleInfo.cacheSourceFile!.sourceFile.text, updatedModuleContent); }); +}); } diff --git a/src/testRunner/unittests/tsserver/duplicatePackages.ts b/src/testRunner/unittests/tsserver/duplicatePackages.ts index c6e4868ba4ab7..3e5cb239c853f 100644 --- a/src/testRunner/unittests/tsserver/duplicatePackages.ts +++ b/src/testRunner/unittests/tsserver/duplicatePackages.ts @@ -1,54 +1,54 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: duplicate packages", () => { - // Tests that 'moduleSpecifiers.ts' will import from the redirecting file, and not from the file it redirects to, if that can provide a global module specifier. - it("works with import fixes", () => { - const packageContent = "export const foo: number;"; - const packageJsonContent = JSON.stringify({ name: "foo", version: "1.2.3" }); - const aFooIndex: File = { path: "/a/node_modules/foo/index.d.ts", content: packageContent }; - const aFooPackage: File = { path: "/a/node_modules/foo/package.json", content: packageJsonContent }; - const bFooIndex: File = { path: "/b/node_modules/foo/index.d.ts", content: packageContent }; - const bFooPackage: File = { path: "/b/node_modules/foo/package.json", content: packageJsonContent }; +describe("unittests:: tsserver:: duplicate packages", () => { + // Tests that 'moduleSpecifiers.ts' will import from the redirecting file, and not from the file it redirects to, if that can provide a global module specifier. + it("works with import fixes", () => { + const packageContent = "export const foo: number;"; + const packageJsonContent = JSON.stringify({ name: "foo", version: "1.2.3" }); + const aFooIndex: File = { path: "/a/node_modules/foo/index.d.ts", content: packageContent }; + const aFooPackage: File = { path: "/a/node_modules/foo/package.json", content: packageJsonContent }; + const bFooIndex: File = { path: "/b/node_modules/foo/index.d.ts", content: packageContent }; + const bFooPackage: File = { path: "/b/node_modules/foo/package.json", content: packageJsonContent }; - const userContent = 'import("foo");\nfoo'; - const aUser: File = { path: "/a/user.ts", content: userContent }; - const bUser: File = { path: "/b/user.ts", content: userContent }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", - }; + const userContent = 'import("foo");\nfoo'; + const aUser: File = { path: "/a/user.ts", content: userContent }; + const bUser: File = { path: "/b/user.ts", content: userContent }; + const tsconfig: File = { + path: "/tsconfig.json", + content: "{}", + }; - const host = createServerHost([aFooIndex, aFooPackage, bFooIndex, bFooPackage, aUser, bUser, tsconfig]); - const session = createSession(host); + const host = createServerHost([aFooIndex, aFooPackage, bFooIndex, bFooPackage, aUser, bUser, tsconfig]); + const session = createSession(host); - openFilesForSession([aUser, bUser], session); + openFilesForSession([aUser, bUser], session); - for (const user of [aUser, bUser]) { - const response = executeSessionRequest(session, protocol.CommandTypes.GetCodeFixes, { - file: user.path, - startLine: 2, - startOffset: 1, - endLine: 2, - endOffset: 4, - errorCodes: [Diagnostics.Cannot_find_name_0.code], - }); - assert.deepEqual(response, [ - { - description: `Import 'foo' from module "foo"`, - fixName: "import", - changes: [{ - fileName: user.path, - textChanges: [{ - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - newText: 'import { foo } from "foo";\n\n', - }], + for (const user of [aUser, bUser]) { + const response = executeSessionRequest(session, protocol.CommandTypes.GetCodeFixes, { + file: user.path, + startLine: 2, + startOffset: 1, + endLine: 2, + endOffset: 4, + errorCodes: [Diagnostics.Cannot_find_name_0.code], + }); + assert.deepEqual(response, [ + { + description: `Import 'foo' from module "foo"`, + fixName: "import", + changes: [{ + fileName: user.path, + textChanges: [{ + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: 'import { foo } from "foo";\n\n', }], - commands: undefined, - fixId: undefined, - fixAllDescription: undefined - }, - ]); - } - }); + }], + commands: undefined, + fixId: undefined, + fixAllDescription: undefined + }, + ]); + } }); +}); } diff --git a/src/testRunner/unittests/tsserver/dynamicFiles.ts b/src/testRunner/unittests/tsserver/dynamicFiles.ts index 554c4c8c67477..8f94bf2700d77 100644 --- a/src/testRunner/unittests/tsserver/dynamicFiles.ts +++ b/src/testRunner/unittests/tsserver/dynamicFiles.ts @@ -1,260 +1,260 @@ namespace ts.projectSystem { - export function verifyDynamic(service: server.ProjectService, path: string) { - const info = Debug.checkDefined(service.filenameToScriptInfo.get(path), `Expected ${path} in :: ${JSON.stringify(arrayFrom(service.filenameToScriptInfo.entries(), ([key, f]) => ({ key, fileName: f.fileName, path: f.path })))}`); - assert.isTrue(info.isDynamic); - } +export function verifyDynamic(service: server.ProjectService, path: string) { + const info = Debug.checkDefined(service.filenameToScriptInfo.get(path), `Expected ${path} in :: ${JSON.stringify(arrayFrom(service.filenameToScriptInfo.entries(), ([key, f]) => ({ key, fileName: f.fileName, path: f.path })))}`); + assert.isTrue(info.isDynamic); +} - function verifyPathRecognizedAsDynamic(path: string) { - const file: File = { - path, - content: `/// +function verifyPathRecognizedAsDynamic(path: string) { + const file: File = { + path, + content: `/// /// var x = 10;` - }; - const host = createServerHost([libFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file.path, file.content); - verifyDynamic(projectService, projectService.toPath(file.path)); + }; + const host = createServerHost([libFile]); + const projectService = createProjectService(host); + projectService.openClientFile(file.path, file.content); + verifyDynamic(projectService, projectService.toPath(file.path)); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - const project = projectService.inferredProjects[0]; - checkProjectRootFiles(project, [file.path]); - checkProjectActualFiles(project, [file.path, libFile.path]); - } + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + checkProjectRootFiles(project, [file.path]); + checkProjectActualFiles(project, [file.path, libFile.path]); +} - describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => { - const untitledFile = "untitled:^Untitled-1"; - it("Can convert positions to locations", () => { - const aTs: File = { path: "/proj/a.ts", content: "" }; - const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" }; - const session = createSession(createServerHost([aTs, tsconfig]), { useInferredProjectPerProjectRoot: true }); +describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => { + const untitledFile = "untitled:^Untitled-1"; + it("Can convert positions to locations", () => { + const aTs: File = { path: "/proj/a.ts", content: "" }; + const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" }; + const session = createSession(createServerHost([aTs, tsconfig]), { useInferredProjectPerProjectRoot: true }); - openFilesForSession([aTs], session); + openFilesForSession([aTs], session); - executeSessionRequestNoResponse(session, protocol.CommandTypes.Open, { - file: untitledFile, - fileContent: `/// \nlet foo = 1;\nfooo/**/`, - scriptKindName: "TS", - projectRootPath: "/proj", - }); - verifyDynamic(session.getProjectService(), `/proj/untitled:^untitled-1`); - const response = executeSessionRequest(session, protocol.CommandTypes.GetCodeFixes, { - file: untitledFile, - startLine: 3, - startOffset: 1, - endLine: 3, - endOffset: 5, - errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_1.code], - }); - assert.deepEqual(response, [ - { - description: "Change spelling to 'foo'", - fixName: "spelling", - changes: [{ - fileName: untitledFile, - textChanges: [{ - start: { line: 3, offset: 1 }, - end: { line: 3, offset: 5 }, - newText: "foo", - }], - }], - commands: undefined, - fixId: undefined, - fixAllDescription: undefined - }, - ]); + executeSessionRequestNoResponse(session, protocol.CommandTypes.Open, { + file: untitledFile, + fileContent: `/// \nlet foo = 1;\nfooo/**/`, + scriptKindName: "TS", + projectRootPath: "/proj", }); + verifyDynamic(session.getProjectService(), `/proj/untitled:^untitled-1`); + const response = executeSessionRequest(session, protocol.CommandTypes.GetCodeFixes, { + file: untitledFile, + startLine: 3, + startOffset: 1, + endLine: 3, + endOffset: 5, + errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_1.code], + }); + assert.deepEqual(response, [ + { + description: "Change spelling to 'foo'", + fixName: "spelling", + changes: [{ + fileName: untitledFile, + textChanges: [{ + start: { line: 3, offset: 1 }, + end: { line: 3, offset: 5 }, + newText: "foo", + }], + }], + commands: undefined, + fixId: undefined, + fixAllDescription: undefined + }, + ]); + }); - it("opening untitled files", () => { - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const host = createServerHost([config, libFile], { useCaseSensitiveFileNames: true, currentDirectory: tscWatch.projectRoot }); - const service = createProjectService(host); - service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); - verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`); + it("opening untitled files", () => { + const config: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const host = createServerHost([config, libFile], { useCaseSensitiveFileNames: true, currentDirectory: tscWatch.projectRoot }); + const service = createProjectService(host); + service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); + verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`); - const untitled: File = { - path: `${tscWatch.projectRoot}/Untitled-1.ts`, - content: "const x = 10;" - }; - host.writeFile(untitled.path, untitled.content); - host.checkTimeoutQueueLength(0); - service.openClientFile(untitled.path, untitled.content, /*scriptKind*/ undefined, tscWatch.projectRoot); - checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 }); - checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); + const untitled: File = { + path: `${tscWatch.projectRoot}/Untitled-1.ts`, + content: "const x = 10;" + }; + host.writeFile(untitled.path, untitled.content); + host.checkTimeoutQueueLength(0); + service.openClientFile(untitled.path, untitled.content, /*scriptKind*/ undefined, tscWatch.projectRoot); + checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 }); + checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]); + checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); - service.closeClientFile(untitledFile); - checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); + service.closeClientFile(untitledFile); + checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]); + checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); - service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot); - verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`); - checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); - }); + service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot); + verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`); + checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]); + checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); + }); - it("opening and closing untitled files when projectRootPath is different from currentDirectory", () => { - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const file: File = { - path: `${tscWatch.projectRoot}/file.ts`, - content: "const y = 10" - }; - const host = createServerHost([config, file, libFile], { useCaseSensitiveFileNames: true }); - const service = createProjectService(host, { useInferredProjectPerProjectRoot: true }); - service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); - verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`); + it("opening and closing untitled files when projectRootPath is different from currentDirectory", () => { + const config: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const file: File = { + path: `${tscWatch.projectRoot}/file.ts`, + content: "const y = 10" + }; + const host = createServerHost([config, file, libFile], { useCaseSensitiveFileNames: true }); + const service = createProjectService(host, { useInferredProjectPerProjectRoot: true }); + service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); + verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`); - // Close untitled file - service.closeClientFile(untitledFile); + // Close untitled file + service.closeClientFile(untitledFile); - // Open file from configured project which should collect inferredProject - service.openClientFile(file.path); - checkNumberOfProjects(service, { configuredProjects: 1 }); - }); + // Open file from configured project which should collect inferredProject + service.openClientFile(file.path); + checkNumberOfProjects(service, { configuredProjects: 1 }); + }); - it("when changing scriptKind of the untitled files", () => { - const host = createServerHost([libFile], { useCaseSensitiveFileNames: true }); - const service = createProjectService(host, { useInferredProjectPerProjectRoot: true }); - service.openClientFile(untitledFile, "const x = 10;", ScriptKind.TS, tscWatch.projectRoot); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); - const program = service.inferredProjects[0].getCurrentProgram()!; - const sourceFile = program.getSourceFile(untitledFile)!; + it("when changing scriptKind of the untitled files", () => { + const host = createServerHost([libFile], { useCaseSensitiveFileNames: true }); + const service = createProjectService(host, { useInferredProjectPerProjectRoot: true }); + service.openClientFile(untitledFile, "const x = 10;", ScriptKind.TS, tscWatch.projectRoot); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); + const program = service.inferredProjects[0].getCurrentProgram()!; + const sourceFile = program.getSourceFile(untitledFile)!; - // Close untitled file - service.closeClientFile(untitledFile); + // Close untitled file + service.closeClientFile(untitledFile); - // Open untitled file with different mode - service.openClientFile(untitledFile, "const x = 10;", ScriptKind.TSX, tscWatch.projectRoot); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); - const newProgram = service.inferredProjects[0].getCurrentProgram()!; - const newSourceFile = newProgram.getSourceFile(untitledFile)!; - assert.notStrictEqual(newProgram, program); - assert.notStrictEqual(newSourceFile, sourceFile); - }); + // Open untitled file with different mode + service.openClientFile(untitledFile, "const x = 10;", ScriptKind.TSX, tscWatch.projectRoot); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); + const newProgram = service.inferredProjects[0].getCurrentProgram()!; + const newSourceFile = newProgram.getSourceFile(untitledFile)!; + assert.notStrictEqual(newProgram, program); + assert.notStrictEqual(newSourceFile, sourceFile); }); +}); - describe("unittests:: tsserver:: dynamicFiles:: ", () => { - it("dynamic file without external project", () => { - const file: File = { - path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", - content: "var x = 10;" - }; - const host = createServerHost([libFile], { useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host); - projectService.setCompilerOptionsForInferredProjects({ - module: ModuleKind.CommonJS, - allowJs: true, - allowSyntheticDefaultImports: true, - allowNonTsExtensions: true - }); - projectService.openClientFile(file.path, "var x = 10;"); - - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - const project = projectService.inferredProjects[0]; - checkProjectRootFiles(project, [file.path]); - checkProjectActualFiles(project, [file.path, libFile.path]); - verifyDynamic(projectService, `/${file.path}`); - - assert.strictEqual(projectService.ensureDefaultProjectForFile(server.toNormalizedPath(file.path)), project); - const indexOfX = file.content.indexOf("x"); - assert.deepEqual(project.getLanguageService(/*ensureSynchronized*/ true).getQuickInfoAtPosition(file.path, indexOfX), { - kind: ScriptElementKind.variableElement, - kindModifiers: "", - textSpan: { start: indexOfX, length: 1 }, - displayParts: [ - { text: "var", kind: "keyword" }, - { text: " ", kind: "space" }, - { text: "x", kind: "localName" }, - { text: ":", kind: "punctuation" }, - { text: " ", kind: "space" }, - { text: "number", kind: "keyword" } - ], - documentation: [], - tags: undefined, - }); +describe("unittests:: tsserver:: dynamicFiles:: ", () => { + it("dynamic file without external project", () => { + const file: File = { + path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", + content: "var x = 10;" + }; + const host = createServerHost([libFile], { useCaseSensitiveFileNames: true }); + const projectService = createProjectService(host); + projectService.setCompilerOptionsForInferredProjects({ + module: ModuleKind.CommonJS, + allowJs: true, + allowSyntheticDefaultImports: true, + allowNonTsExtensions: true }); + projectService.openClientFile(file.path, "var x = 10;"); - it("dynamic file with reference paths without external project", () => { - verifyPathRecognizedAsDynamic("^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js"); - }); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + checkProjectRootFiles(project, [file.path]); + checkProjectActualFiles(project, [file.path, libFile.path]); + verifyDynamic(projectService, `/${file.path}`); - describe("dynamic file with projectRootPath", () => { - const file: File = { - path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", - content: "var x = 10;" - }; - const configFile: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const configProjectFile: File = { - path: `${tscWatch.projectRoot}/a.ts`, - content: "let y = 10;" - }; - it("with useInferredProjectPerProjectRoot", () => { - const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); - const session = createSession(host, { useInferredProjectPerProjectRoot: true }); - openFilesForSession([{ file: file.path, projectRootPath: tscWatch.projectRoot }], session); + assert.strictEqual(projectService.ensureDefaultProjectForFile(server.toNormalizedPath(file.path)), project); + const indexOfX = file.content.indexOf("x"); + assert.deepEqual(project.getLanguageService(/*ensureSynchronized*/ true).getQuickInfoAtPosition(file.path, indexOfX), { + kind: ScriptElementKind.variableElement, + kindModifiers: "", + textSpan: { start: indexOfX, length: 1 }, + displayParts: [ + { text: "var", kind: "keyword" }, + { text: " ", kind: "space" }, + { text: "x", kind: "localName" }, + { text: ":", kind: "punctuation" }, + { text: " ", kind: "space" }, + { text: "number", kind: "keyword" } + ], + documentation: [], + tags: undefined, + }); + }); - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]); - verifyDynamic(projectService, `${tscWatch.projectRoot}/${file.path}`); + it("dynamic file with reference paths without external project", () => { + verifyPathRecognizedAsDynamic("^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js"); + }); - session.executeCommandSeq({ - command: protocol.CommandTypes.GetOutliningSpans, - arguments: { - file: file.path - } - }); + describe("dynamic file with projectRootPath", () => { + const file: File = { + path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", + content: "var x = 10;" + }; + const configFile: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const configProjectFile: File = { + path: `${tscWatch.projectRoot}/a.ts`, + content: "let y = 10;" + }; + it("with useInferredProjectPerProjectRoot", () => { + const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); + const session = createSession(host, { useInferredProjectPerProjectRoot: true }); + openFilesForSession([{ file: file.path, projectRootPath: tscWatch.projectRoot }], session); - // Without project root - const file2Path = file.path.replace("#1", "#2"); - projectService.openClientFile(file2Path, file.content); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file2Path, libFile.path]); - }); + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]); + verifyDynamic(projectService, `${tscWatch.projectRoot}/${file.path}`); - it("fails when useInferredProjectPerProjectRoot is false", () => { - const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host); - try { - projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, tscWatch.projectRoot); + session.executeCommandSeq({ + command: protocol.CommandTypes.GetOutliningSpans, + arguments: { + file: file.path } - catch (e) { - assert.strictEqual( - e.message.replace(/\r?\n/, "\n"), - `Debug Failure. False expression.\nVerbose Debug Information: {"fileName":"^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js","currentDirectory":"/user/username/projects/myproject","hostCurrentDirectory":"/","openKeys":[]}\nDynamic files must always be opened with service's current directory or service should support inferred project per projectRootPath.` - ); - } - const file2Path = file.path.replace("#1", "#2"); - projectService.openClientFile(file2Path, file.content); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file2Path, libFile.path]); }); + + // Without project root + const file2Path = file.path.replace("#1", "#2"); + projectService.openClientFile(file2Path, file.content); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file2Path, libFile.path]); }); - describe("verify accepts known schemas as dynamic file", () => { - it("walkThroughSnippet", () => { - verifyPathRecognizedAsDynamic("walkThroughSnippet:/usr/share/code/resources/app/out/vs/workbench/contrib/welcome/walkThrough/browser/editor/^vs_code_editor_walkthrough.md#1.ts"); - }); + it("fails when useInferredProjectPerProjectRoot is false", () => { + const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); + const projectService = createProjectService(host); + try { + projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, tscWatch.projectRoot); + } + catch (e) { + assert.strictEqual( + e.message.replace(/\r?\n/, "\n"), + `Debug Failure. False expression.\nVerbose Debug Information: {"fileName":"^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js","currentDirectory":"/user/username/projects/myproject","hostCurrentDirectory":"/","openKeys":[]}\nDynamic files must always be opened with service's current directory or service should support inferred project per projectRootPath.` + ); + } + const file2Path = file.path.replace("#1", "#2"); + projectService.openClientFile(file2Path, file.content); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file2Path, libFile.path]); + }); + }); - it("untitled", () => { - verifyPathRecognizedAsDynamic("untitled:/Users/matb/projects/san/^newFile.ts"); - }); + describe("verify accepts known schemas as dynamic file", () => { + it("walkThroughSnippet", () => { + verifyPathRecognizedAsDynamic("walkThroughSnippet:/usr/share/code/resources/app/out/vs/workbench/contrib/welcome/walkThrough/browser/editor/^vs_code_editor_walkthrough.md#1.ts"); + }); + + it("untitled", () => { + verifyPathRecognizedAsDynamic("untitled:/Users/matb/projects/san/^newFile.ts"); }); }); +}); } diff --git a/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts index 1807f104ee2d6..9bdd602f7433b 100644 --- a/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts +++ b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts @@ -1,75 +1,75 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large file", () => { +describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large file", () => { - function getLargeFile(useLargeTsFile: boolean) { - return `src/large.${useLargeTsFile ? "ts" : "js"}`; - } - - function createSessionWithEventHandler(files: File[], useLargeTsFile: boolean) { - const largeFile: File = { - path: `${tscWatch.projectRoot}/${getLargeFile(useLargeTsFile)}`, - content: "export var x = 10;", - fileSize: server.maxFileSize + 1 - }; - files.push(largeFile); - const host = createServerHost(files); - const { session, events: largeFileReferencedEvents } = createSessionWithEventTracking(host, server.LargeFileReferencedEvent); + function getLargeFile(useLargeTsFile: boolean) { + return `src/large.${useLargeTsFile ? "ts" : "js"}`; + } - return { session, verifyLargeFile }; + function createSessionWithEventHandler(files: File[], useLargeTsFile: boolean) { + const largeFile: File = { + path: `${tscWatch.projectRoot}/${getLargeFile(useLargeTsFile)}`, + content: "export var x = 10;", + fileSize: server.maxFileSize + 1 + }; + files.push(largeFile); + const host = createServerHost(files); + const { session, events: largeFileReferencedEvents } = createSessionWithEventTracking(host, server.LargeFileReferencedEvent); - function verifyLargeFile(project: server.Project) { - checkProjectActualFiles(project, files.map(f => f.path)); + return { session, verifyLargeFile }; - // large file for non ts file should be empty and for ts file should have content - const service = session.getProjectService(); - const info = service.getScriptInfo(largeFile.path)!; - assert.equal(info.cacheSourceFile!.sourceFile.text, useLargeTsFile ? largeFile.content : ""); + function verifyLargeFile(project: server.Project) { + checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(largeFileReferencedEvents, useLargeTsFile ? emptyArray : [{ - eventName: server.LargeFileReferencedEvent, - data: { file: largeFile.path, fileSize: largeFile.fileSize, maxFileSize: server.maxFileSize } - }]); - } - } - - function verifyLargeFile(useLargeTsFile: boolean) { - it("when large file is included by tsconfig", () => { - const file: File = { - path: `${tscWatch.projectRoot}/src/file.ts`, - content: "export var y = 10;" - }; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ files: ["src/file.ts", getLargeFile(useLargeTsFile)], compilerOptions: { target: 1, allowJs: true } }) - }; - const files = [file, libFile, tsconfig]; - const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile); - const service = session.getProjectService(); - openFilesForSession([file], session); - checkNumberOfProjects(service, { configuredProjects: 1 }); - verifyLargeFile(service.configuredProjects.get(tsconfig.path)!); - }); + // large file for non ts file should be empty and for ts file should have content + const service = session.getProjectService(); + const info = service.getScriptInfo(largeFile.path)!; + assert.equal(info.cacheSourceFile!.sourceFile.text, useLargeTsFile ? largeFile.content : ""); - it("when large file is included by module resolution", () => { - const file: File = { - path: `${tscWatch.projectRoot}/src/file.ts`, - content: `export var y = 10;import {x} from "./large"` - }; - const files = [file, libFile]; - const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile); - const service = session.getProjectService(); - openFilesForSession([file], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - verifyLargeFile(service.inferredProjects[0]); - }); + assert.deepEqual(largeFileReferencedEvents, useLargeTsFile ? emptyArray : [{ + eventName: server.LargeFileReferencedEvent, + data: { file: largeFile.path, fileSize: largeFile.fileSize, maxFileSize: server.maxFileSize } + }]); } + } - describe("large file is ts file", () => { - verifyLargeFile(/*useLargeTsFile*/ true); + function verifyLargeFile(useLargeTsFile: boolean) { + it("when large file is included by tsconfig", () => { + const file: File = { + path: `${tscWatch.projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const tsconfig: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ files: ["src/file.ts", getLargeFile(useLargeTsFile)], compilerOptions: { target: 1, allowJs: true } }) + }; + const files = [file, libFile, tsconfig]; + const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile); + const service = session.getProjectService(); + openFilesForSession([file], session); + checkNumberOfProjects(service, { configuredProjects: 1 }); + verifyLargeFile(service.configuredProjects.get(tsconfig.path)!); }); - describe("large file is js file", () => { - verifyLargeFile(/*useLargeTsFile*/ false); + it("when large file is included by module resolution", () => { + const file: File = { + path: `${tscWatch.projectRoot}/src/file.ts`, + content: `export var y = 10;import {x} from "./large"` + }; + const files = [file, libFile]; + const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile); + const service = session.getProjectService(); + openFilesForSession([file], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + verifyLargeFile(service.inferredProjects[0]); }); + } + + describe("large file is ts file", () => { + verifyLargeFile(/*useLargeTsFile*/ true); + }); + + describe("large file is js file", () => { + verifyLargeFile(/*useLargeTsFile*/ false); }); +}); } diff --git a/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts b/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts index 08d66db76a16c..06cbb94d9726c 100644 --- a/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts +++ b/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts @@ -1,79 +1,79 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", () => { - it("language service disabled events are triggered", () => { - const f1 = { - path: "/a/app.js", - content: "let x = 1;" - }; - const f2 = { - path: "/a/largefile.js", - content: "", - }; - const config = { - path: "/a/jsconfig.json", - content: "{}" - }; - const configWithExclude = { - path: config.path, - content: JSON.stringify({ exclude: ["largefile.js"] }) - }; - const host = createServerHost([f1, f2, config]); - const originalGetFileSize = host.getFileSize; - host.getFileSize = (filePath: string) => - filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); +describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", () => { + it("language service disabled events are triggered", () => { + const f1 = { + path: "/a/app.js", + content: "let x = 1;" + }; + const f2 = { + path: "/a/largefile.js", + content: "", + }; + const config = { + path: "/a/jsconfig.json", + content: "{}" + }; + const configWithExclude = { + path: config.path, + content: JSON.stringify({ exclude: ["largefile.js"] }) + }; + const host = createServerHost([f1, f2, config]); + const originalGetFileSize = host.getFileSize; + host.getFileSize = (filePath: string) => + filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); - const { session, events } = createSessionWithEventTracking(host, server.ProjectLanguageServiceStateEvent); - session.executeCommand({ - seq: 0, - type: "request", - command: "open", - arguments: { file: f1.path } - } as protocol.OpenRequest); - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = configuredProjectAt(projectService, 0); - assert.isFalse(project.languageServiceEnabled, "Language service enabled"); - assert.equal(events.length, 1, "should receive event"); - assert.equal(events[0].data.project, project, "project name"); - assert.equal(events[0].data.project.getProjectName(), config.path, "config path"); - assert.isFalse(events[0].data.languageServiceEnabled, "Language service state"); + const { session, events } = createSessionWithEventTracking(host, server.ProjectLanguageServiceStateEvent); + session.executeCommand({ + seq: 0, + type: "request", + command: "open", + arguments: { file: f1.path } + } as protocol.OpenRequest); + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = configuredProjectAt(projectService, 0); + assert.isFalse(project.languageServiceEnabled, "Language service enabled"); + assert.equal(events.length, 1, "should receive event"); + assert.equal(events[0].data.project, project, "project name"); + assert.equal(events[0].data.project.getProjectName(), config.path, "config path"); + assert.isFalse(events[0].data.languageServiceEnabled, "Language service state"); - host.writeFile(configWithExclude.path, configWithExclude.content); - host.checkTimeoutQueueLengthAndRun(2); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.isTrue(project.languageServiceEnabled, "Language service enabled"); - assert.equal(events.length, 2, "should receive event"); - assert.equal(events[1].data.project, project, "project"); - assert.equal(events[1].data.project.getProjectName(), config.path, "config path"); - assert.isTrue(events[1].data.languageServiceEnabled, "Language service state"); - }); + host.writeFile(configWithExclude.path, configWithExclude.content); + host.checkTimeoutQueueLengthAndRun(2); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.isTrue(project.languageServiceEnabled, "Language service enabled"); + assert.equal(events.length, 2, "should receive event"); + assert.equal(events[1].data.project, project, "project"); + assert.equal(events[1].data.project.getProjectName(), config.path, "config path"); + assert.isTrue(events[1].data.languageServiceEnabled, "Language service state"); + }); - it("Large file size is determined correctly", () => { - const f1: File = { - path: "/a/app.js", - content: "let x = 1;" - }; - const f2: File = { - path: "/a/largefile.js", - content: "", - fileSize: server.maxProgramSizeForNonTsFiles + 1 - }; - const f3: File = { - path: "/a/extremlylarge.d.ts", - content: "", - fileSize: server.maxProgramSizeForNonTsFiles + 100 - }; - const config = { - path: "/a/jsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, f2, f3, libFile, config]); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); - service.openClientFile(f1.path); - const project = service.configuredProjects.get(config.path)!; - service.logger.logs.push(`languageServiceEnabled: ${project.languageServiceEnabled}`); - service.logger.logs.push(`lastFileExceededProgramSize: ${project.lastFileExceededProgramSize}`); - baselineTsserverLogs("projectLanguageServiceStateEvent", "large file size is determined correctly", service); - }); + it("Large file size is determined correctly", () => { + const f1: File = { + path: "/a/app.js", + content: "let x = 1;" + }; + const f2: File = { + path: "/a/largefile.js", + content: "", + fileSize: server.maxProgramSizeForNonTsFiles + 1 + }; + const f3: File = { + path: "/a/extremlylarge.d.ts", + content: "", + fileSize: server.maxProgramSizeForNonTsFiles + 100 + }; + const config = { + path: "/a/jsconfig.json", + content: "{}" + }; + const host = createServerHost([f1, f2, f3, libFile, config]); + const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs() }); + service.openClientFile(f1.path); + const project = service.configuredProjects.get(config.path)!; + service.logger.logs.push(`languageServiceEnabled: ${project.languageServiceEnabled}`); + service.logger.logs.push(`lastFileExceededProgramSize: ${project.lastFileExceededProgramSize}`); + baselineTsserverLogs("projectLanguageServiceStateEvent", "large file size is determined correctly", service); }); +}); } diff --git a/src/testRunner/unittests/tsserver/events/projectLoading.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts index 150f7c1c724e3..c6bb22bca01b5 100644 --- a/src/testRunner/unittests/tsserver/events/projectLoading.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -1,232 +1,232 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoadingFinish events", () => { - const aTs: File = { - path: `${tscWatch.projects}/a/a.ts`, - content: "export class A { }" - }; - const configA: File = { - path: `${tscWatch.projects}/a/tsconfig.json`, - content: "{}" - }; - const bTsPath = `${tscWatch.projects}/b/b.ts`; - const configBPath = `${tscWatch.projects}/b/tsconfig.json`; - const files = [libFile, aTs, configA]; - - function verifyProjectLoadingStartAndFinish(createSession: (host: TestServerHost) => { - session: TestSession; - getNumberOfEvents: () => number; - clearEvents: () => void; - verifyProjectLoadEvents: (expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) => void; - }) { - function createSessionToVerifyEvent(files: readonly File[]) { - const host = createServerHost(files); - const originalReadFile = host.readFile; - const { session, getNumberOfEvents, clearEvents, verifyProjectLoadEvents } = createSession(host); - host.readFile = file => { - if (file === configA.path || file === configBPath) { - assert.equal(getNumberOfEvents(), 1, "Event for loading is sent before reading config file"); - } - return originalReadFile.call(host, file); - }; - const service = session.getProjectService(); - return { host, session, verifyEvent, verifyEventWithOpenTs, service, getNumberOfEvents }; - - function verifyEvent(project: server.Project, reason: string) { - verifyProjectLoadEvents([ - { eventName: server.ProjectLoadingStartEvent, data: { project, reason } }, - { eventName: server.ProjectLoadingFinishEvent, data: { project } } - ]); - clearEvents(); +describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoadingFinish events", () => { + const aTs: File = { + path: `${tscWatch.projects}/a/a.ts`, + content: "export class A { }" + }; + const configA: File = { + path: `${tscWatch.projects}/a/tsconfig.json`, + content: "{}" + }; + const bTsPath = `${tscWatch.projects}/b/b.ts`; + const configBPath = `${tscWatch.projects}/b/tsconfig.json`; + const files = [libFile, aTs, configA]; + + function verifyProjectLoadingStartAndFinish(createSession: (host: TestServerHost) => { + session: TestSession; + getNumberOfEvents: () => number; + clearEvents: () => void; + verifyProjectLoadEvents: (expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) => void; + }) { + function createSessionToVerifyEvent(files: readonly File[]) { + const host = createServerHost(files); + const originalReadFile = host.readFile; + const { session, getNumberOfEvents, clearEvents, verifyProjectLoadEvents } = createSession(host); + host.readFile = file => { + if (file === configA.path || file === configBPath) { + assert.equal(getNumberOfEvents(), 1, "Event for loading is sent before reading config file"); } + return originalReadFile.call(host, file); + }; + const service = session.getProjectService(); + return { host, session, verifyEvent, verifyEventWithOpenTs, service, getNumberOfEvents }; + + function verifyEvent(project: server.Project, reason: string) { + verifyProjectLoadEvents([ + { eventName: server.ProjectLoadingStartEvent, data: { project, reason } }, + { eventName: server.ProjectLoadingFinishEvent, data: { project } } + ]); + clearEvents(); + } - function verifyEventWithOpenTs(file: File, configPath: string, configuredProjects: number) { - openFilesForSession([file], session); - checkNumberOfProjects(service, { configuredProjects }); - const project = service.configuredProjects.get(configPath)!; - assert.isDefined(project); - verifyEvent(project, `Creating possible configured project for ${file.path} to open`); - } + function verifyEventWithOpenTs(file: File, configPath: string, configuredProjects: number) { + openFilesForSession([file], session); + checkNumberOfProjects(service, { configuredProjects }); + const project = service.configuredProjects.get(configPath)!; + assert.isDefined(project); + verifyEvent(project, `Creating possible configured project for ${file.path} to open`); } + } - it("when project is created by open file", () => { - const bTs: File = { - path: bTsPath, - content: "export class B {}" - }; - const configB: File = { - path: configBPath, - content: "{}" - }; - const { verifyEventWithOpenTs } = createSessionToVerifyEvent(files.concat(bTs, configB)); - verifyEventWithOpenTs(aTs, configA.path, 1); - verifyEventWithOpenTs(bTs, configB.path, 2); - }); + it("when project is created by open file", () => { + const bTs: File = { + path: bTsPath, + content: "export class B {}" + }; + const configB: File = { + path: configBPath, + content: "{}" + }; + const { verifyEventWithOpenTs } = createSessionToVerifyEvent(files.concat(bTs, configB)); + verifyEventWithOpenTs(aTs, configA.path, 1); + verifyEventWithOpenTs(bTs, configB.path, 2); + }); - it("when change is detected in the config file", () => { - const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files); - verifyEventWithOpenTs(aTs, configA.path, 1); + it("when change is detected in the config file", () => { + const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files); + verifyEventWithOpenTs(aTs, configA.path, 1); - host.writeFile(configA.path, configA.content); - host.checkTimeoutQueueLengthAndRun(2); - const project = service.configuredProjects.get(configA.path)!; - verifyEvent(project, `Change in config file detected`); + host.writeFile(configA.path, configA.content); + host.checkTimeoutQueueLengthAndRun(2); + const project = service.configuredProjects.get(configA.path)!; + verifyEvent(project, `Change in config file detected`); + }); + + it("when change is detected in an extended config file", () => { + const bTs: File = { + path: bTsPath, + content: "export class B {}" + }; + const configB: File = { + path: configBPath, + content: JSON.stringify({ + extends: "../a/tsconfig.json", + }) + }; + const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files.concat(bTs, configB)); + verifyEventWithOpenTs(bTs, configB.path, 1); + + host.writeFile(configA.path, configA.content); + host.checkTimeoutQueueLengthAndRun(2); + const project = service.configuredProjects.get(configB.path)!; + verifyEvent(project, `Change in extended config file ${configA.path} detected`); + }); + + describe("when opening original location project", () => { + it("with project references", () => { + verify(); }); - it("when change is detected in an extended config file", () => { + it("when disableSourceOfProjectReferenceRedirect is true", () => { + verify(/*disableSourceOfProjectReferenceRedirect*/ true); + }); + + function verify(disableSourceOfProjectReferenceRedirect?: true) { + const aDTs: File = { + path: `${tscWatch.projects}/a/a.d.ts`, + content: `export declare class A { +} +//# sourceMappingURL=a.d.ts.map +` + }; + const aDTsMap: File = { + path: `${tscWatch.projects}/a/a.d.ts.map`, + content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}` + }; const bTs: File = { path: bTsPath, - content: "export class B {}" + content: `import {A} from "../a/a"; new A();` }; const configB: File = { path: configBPath, content: JSON.stringify({ - extends: "../a/tsconfig.json", + ...(disableSourceOfProjectReferenceRedirect && { + compilerOptions: { + disableSourceOfProjectReferenceRedirect + } + }), + references: [{ path: "../a" }] }) }; - const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files.concat(bTs, configB)); - verifyEventWithOpenTs(bTs, configB.path, 1); - host.writeFile(configA.path, configA.content); - host.checkTimeoutQueueLengthAndRun(2); - const project = service.configuredProjects.get(configB.path)!; - verifyEvent(project, `Change in extended config file ${configA.path} detected`); - }); - - describe("when opening original location project", () => { - it("with project references", () => { - verify(); - }); + const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB)); + verifyEventWithOpenTs(bTs, configB.path, 1); - it("when disableSourceOfProjectReferenceRedirect is true", () => { - verify(/*disableSourceOfProjectReferenceRedirect*/ true); + session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: { + file: bTs.path, + ...protocolLocationFromSubstring(bTs.content, "A()") + } }); - function verify(disableSourceOfProjectReferenceRedirect?: true) { - const aDTs: File = { - path: `${tscWatch.projects}/a/a.d.ts`, - content: `export declare class A { -} -//# sourceMappingURL=a.d.ts.map -` - }; - const aDTsMap: File = { - path: `${tscWatch.projects}/a/a.d.ts.map`, - content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}` - }; - const bTs: File = { - path: bTsPath, - content: `import {A} from "../a/a"; new A();` - }; - const configB: File = { - path: configBPath, - content: JSON.stringify({ - ...(disableSourceOfProjectReferenceRedirect && { - compilerOptions: { - disableSourceOfProjectReferenceRedirect - } - }), - references: [{ path: "../a" }] - }) - }; - - const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB)); - verifyEventWithOpenTs(bTs, configB.path, 1); - - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: { - file: bTs.path, - ...protocolLocationFromSubstring(bTs.content, "A()") - } - }); - - checkNumberOfProjects(service, { configuredProjects: 2 }); - const project = service.configuredProjects.get(configA.path)!; - assert.isDefined(project); - verifyEvent( - project, - disableSourceOfProjectReferenceRedirect ? - `Creating project for original file: ${aTs.path} for location: ${aDTs.path}` : - `Creating project for original file: ${aTs.path}` - ); - } - }); + checkNumberOfProjects(service, { configuredProjects: 2 }); + const project = service.configuredProjects.get(configA.path)!; + assert.isDefined(project); + verifyEvent( + project, + disableSourceOfProjectReferenceRedirect ? + `Creating project for original file: ${aTs.path} for location: ${aDTs.path}` : + `Creating project for original file: ${aTs.path}` + ); + } + }); - describe("with external projects and config files ", () => { - const projectFileName = `${tscWatch.projects}/a/project.csproj`; - - function createSession(lazyConfiguredProjectsFromExternalProject: boolean) { - const { session, service, verifyEvent: verifyEventWorker, getNumberOfEvents } = createSessionToVerifyEvent(files); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([aTs.path, configA.path]), - options: {} - } as protocol.ExternalProject); - checkNumberOfProjects(service, { configuredProjects: 1 }); - return { session, service, verifyEvent, getNumberOfEvents }; - - function verifyEvent() { - const projectA = service.configuredProjects.get(configA.path)!; - assert.isDefined(projectA); - verifyEventWorker(projectA, `Creating configured project in external project: ${projectFileName}`); - } + describe("with external projects and config files ", () => { + const projectFileName = `${tscWatch.projects}/a/project.csproj`; + + function createSession(lazyConfiguredProjectsFromExternalProject: boolean) { + const { session, service, verifyEvent: verifyEventWorker, getNumberOfEvents } = createSessionToVerifyEvent(files); + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([aTs.path, configA.path]), + options: {} + } as protocol.ExternalProject); + checkNumberOfProjects(service, { configuredProjects: 1 }); + return { session, service, verifyEvent, getNumberOfEvents }; + + function verifyEvent() { + const projectA = service.configuredProjects.get(configA.path)!; + assert.isDefined(projectA); + verifyEventWorker(projectA, `Creating configured project in external project: ${projectFileName}`); } + } - it("when lazyConfiguredProjectsFromExternalProject is false", () => { - const { verifyEvent } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ false); - verifyEvent(); - }); - - it("when lazyConfiguredProjectsFromExternalProject is true and file is opened", () => { - const { verifyEvent, getNumberOfEvents, session } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); - assert.equal(getNumberOfEvents(), 0); - - openFilesForSession([aTs], session); - verifyEvent(); - }); + it("when lazyConfiguredProjectsFromExternalProject is false", () => { + const { verifyEvent } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ false); + verifyEvent(); + }); - it("when lazyConfiguredProjectsFromExternalProject is disabled", () => { - const { verifyEvent, getNumberOfEvents, service } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); - assert.equal(getNumberOfEvents(), 0); + it("when lazyConfiguredProjectsFromExternalProject is true and file is opened", () => { + const { verifyEvent, getNumberOfEvents, session } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); + assert.equal(getNumberOfEvents(), 0); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); - verifyEvent(); - }); + openFilesForSession([aTs], session); + verifyEvent(); }); - } - describe("when using event handler", () => { - verifyProjectLoadingStartAndFinish(host => { - const { session, events } = createSessionWithEventTracking(host, server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent); - return { - session, - getNumberOfEvents: () => events.length, - clearEvents: () => events.length = 0, - verifyProjectLoadEvents: expected => assert.deepEqual(events, expected) - }; + it("when lazyConfiguredProjectsFromExternalProject is disabled", () => { + const { verifyEvent, getNumberOfEvents, service } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); + assert.equal(getNumberOfEvents(), 0); + + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); + verifyEvent(); }); }); + } + + describe("when using event handler", () => { + verifyProjectLoadingStartAndFinish(host => { + const { session, events } = createSessionWithEventTracking(host, server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent); + return { + session, + getNumberOfEvents: () => events.length, + clearEvents: () => events.length = 0, + verifyProjectLoadEvents: expected => assert.deepEqual(events, expected) + }; + }); + }); - describe("when using default event handler", () => { - verifyProjectLoadingStartAndFinish(host => { - const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]); - return { - session, - getNumberOfEvents: () => getEvents().length, - clearEvents, - verifyProjectLoadEvents - }; - - function verifyProjectLoadEvents(expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) { - const actual = getEvents().map(e => ({ eventName: e.event, data: e.body })); - const mappedExpected = expected.map(e => { - const { project, ...rest } = e.data; - return { eventName: e.eventName, data: { projectName: project.getProjectName(), ...rest } }; - }); - assert.deepEqual(actual, mappedExpected); - } - }); + describe("when using default event handler", () => { + verifyProjectLoadingStartAndFinish(host => { + const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]); + return { + session, + getNumberOfEvents: () => getEvents().length, + clearEvents, + verifyProjectLoadEvents + }; + + function verifyProjectLoadEvents(expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) { + const actual = getEvents().map(e => ({ eventName: e.event, data: e.body })); + const mappedExpected = expected.map(e => { + const { project, ...rest } = e.data; + return { eventName: e.eventName, data: { projectName: project.getProjectName(), ...rest } }; + }); + assert.deepEqual(actual, mappedExpected); + } }); }); +}); } diff --git a/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts index 34e8f7d8e0ad8..1d031442e3a19 100644 --- a/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts +++ b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts @@ -1,58 +1,102 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => { - function verifyFiles(caption: string, actual: readonly string[], expected: readonly string[]) { - assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); - const seen = new Map(); - forEach(actual, f => { - assert.isFalse(seen.has(f), `${caption}: Found duplicate ${f}. Actual: ${actual} Expected: ${expected}`); - seen.set(f, true); - assert.isTrue(contains(expected, f), `${caption}: Expected not to contain ${f}. Actual: ${actual} Expected: ${expected}`); - }); - } - - function createVerifyInitialOpen(session: TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: server.ProjectsUpdatedInBackgroundEvent[]) => void) { - return (file: File) => { - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { - file: file.path - } - } as protocol.OpenRequest); - verifyProjectsUpdatedInBackgroundEventHandler([]); +describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => { + function verifyFiles(caption: string, actual: readonly string[], expected: readonly string[]) { + assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); + const seen = new Map(); + forEach(actual, f => { + assert.isFalse(seen.has(f), `${caption}: Found duplicate ${f}. Actual: ${actual} Expected: ${expected}`); + seen.set(f, true); + assert.isTrue(contains(expected, f), `${caption}: Expected not to contain ${f}. Actual: ${actual} Expected: ${expected}`); + }); + } + + function createVerifyInitialOpen(session: TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: server.ProjectsUpdatedInBackgroundEvent[]) => void) { + return (file: File) => { + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { + file: file.path + } + } as protocol.OpenRequest); + verifyProjectsUpdatedInBackgroundEventHandler([]); + }; + } + + interface ProjectsUpdatedInBackgroundEventVerifier { + session: TestSession; + verifyProjectsUpdatedInBackgroundEventHandler(events: server.ProjectsUpdatedInBackgroundEvent[]): void; + verifyInitialOpen(file: File): void; + } + + function verifyProjectsUpdatedInBackgroundEvent(createSession: (host: TestServerHost) => ProjectsUpdatedInBackgroundEventVerifier) { + it("when adding new file", () => { + const commonFile1: File = { + path: "/a/b/file1.ts", + content: "export var x = 10;" }; - } - - interface ProjectsUpdatedInBackgroundEventVerifier { - session: TestSession; - verifyProjectsUpdatedInBackgroundEventHandler(events: server.ProjectsUpdatedInBackgroundEvent[]): void; - verifyInitialOpen(file: File): void; - } + const commonFile2: File = { + path: "/a/b/file2.ts", + content: "export var y = 10;" + }; + const commonFile3: File = { + path: "/a/b/file3.ts", + content: "export var z = 10;" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const openFiles = [commonFile1.path]; + const host = createServerHost([commonFile1, libFile, configFile]); + const { verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); + verifyInitialOpen(commonFile1); + + host.writeFile(commonFile2.path, commonFile2.content); + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); + + host.writeFile(commonFile3.path, commonFile3.content); + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); + }); - function verifyProjectsUpdatedInBackgroundEvent(createSession: (host: TestServerHost) => ProjectsUpdatedInBackgroundEventVerifier) { - it("when adding new file", () => { - const commonFile1: File = { - path: "/a/b/file1.ts", - content: "export var x = 10;" + describe("with --out or --outFile setting", () => { + function verifyEventWithOutSettings(compilerOptions: CompilerOptions = {}) { + const config: File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions + }) }; - const commonFile2: File = { - path: "/a/b/file2.ts", - content: "export var y = 10;" - }; - const commonFile3: File = { - path: "/a/b/file3.ts", - content: "export var z = 10;" + + const f1: File = { + path: "/a/a.ts", + content: "export let x = 1" }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{}` + const f2: File = { + path: "/a/b.ts", + content: "export let y = 1" }; - const openFiles = [commonFile1.path]; - const host = createServerHost([commonFile1, libFile, configFile]); - const { verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); - verifyInitialOpen(commonFile1); - host.writeFile(commonFile2.path, commonFile2.content); + const openFiles = [f1.path]; + const files = [f1, config, libFile]; + const host = createServerHost(files); + const { verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); + verifyInitialOpen(f1); + + host.writeFile(f2.path, f2.content); host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ eventName: server.ProjectsUpdatedInBackgroundEvent, data: { @@ -60,7 +104,7 @@ namespace ts.projectSystem { } }]); - host.writeFile(commonFile3.path, commonFile3.content); + host.writeFile(f2.path, "export let x = 11"); host.runQueuedTimeoutCallbacks(); verifyProjectsUpdatedInBackgroundEventHandler([{ eventName: server.ProjectsUpdatedInBackgroundEvent, @@ -68,511 +112,467 @@ namespace ts.projectSystem { openFiles } }]); - }); - - describe("with --out or --outFile setting", () => { - function verifyEventWithOutSettings(compilerOptions: CompilerOptions = {}) { - const config: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions - }) - }; - - const f1: File = { - path: "/a/a.ts", - content: "export let x = 1" - }; - const f2: File = { - path: "/a/b.ts", - content: "export let y = 1" - }; - - const openFiles = [f1.path]; - const files = [f1, config, libFile]; - const host = createServerHost(files); - const { verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); - verifyInitialOpen(f1); - - host.writeFile(f2.path, f2.content); - host.runQueuedTimeoutCallbacks(); - - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - - host.writeFile(f2.path, "export let x = 11"); - host.runQueuedTimeoutCallbacks(); - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - } + } - it("when both options are not set", () => { - verifyEventWithOutSettings(); - }); + it("when both options are not set", () => { + verifyEventWithOutSettings(); + }); - it("when --out is set", () => { - const outJs = "/a/out.js"; - verifyEventWithOutSettings({ out: outJs }); - }); + it("when --out is set", () => { + const outJs = "/a/out.js"; + verifyEventWithOutSettings({ out: outJs }); + }); - it("when --outFile is set", () => { - const outJs = "/a/out.js"; - verifyEventWithOutSettings({ outFile: outJs }); - }); + it("when --outFile is set", () => { + const outJs = "/a/out.js"; + verifyEventWithOutSettings({ outFile: outJs }); }); + }); - describe("with modules and configured project", () => { - const file1Consumer1Path = "/a/b/file1Consumer1.ts"; - const moduleFile1Path = "/a/b/moduleFile1.ts"; - const configFilePath = "/a/b/tsconfig.json"; - interface InitialStateParams { - /** custom config file options */ - configObj?: any; - /** Additional files and folders to add */ - getAdditionalFileOrFolder?(): File[]; - /** initial list of files to reload in fs and first file in this list being the file to open */ - firstReloadFileList?: string[]; - } - function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) { - const moduleFile1: File = { - path: moduleFile1Path, - content: "export function Foo() { };", - }; + describe("with modules and configured project", () => { + const file1Consumer1Path = "/a/b/file1Consumer1.ts"; + const moduleFile1Path = "/a/b/moduleFile1.ts"; + const configFilePath = "/a/b/tsconfig.json"; + interface InitialStateParams { + /** custom config file options */ + configObj?: any; + /** Additional files and folders to add */ + getAdditionalFileOrFolder?(): File[]; + /** initial list of files to reload in fs and first file in this list being the file to open */ + firstReloadFileList?: string[]; + } + function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) { + const moduleFile1: File = { + path: moduleFile1Path, + content: "export function Foo() { };", + }; - const file1Consumer1: File = { - path: file1Consumer1Path, - content: `import {Foo} from "./moduleFile1"; export var y = 10;`, - }; + const file1Consumer1: File = { + path: file1Consumer1Path, + content: `import {Foo} from "./moduleFile1"; export var y = 10;`, + }; - const file1Consumer2: File = { - path: "/a/b/file1Consumer2.ts", - content: `import {Foo} from "./moduleFile1"; let z = 10;`, - }; + const file1Consumer2: File = { + path: "/a/b/file1Consumer2.ts", + content: `import {Foo} from "./moduleFile1"; let z = 10;`, + }; - const moduleFile2: File = { - path: "/a/b/moduleFile2.ts", - content: `export var Foo4 = 10;`, - }; + const moduleFile2: File = { + path: "/a/b/moduleFile2.ts", + content: `export var Foo4 = 10;`, + }; - const globalFile3: File = { - path: "/a/b/globalFile3.ts", - content: `interface GlobalFoo { age: number }` - }; + const globalFile3: File = { + path: "/a/b/globalFile3.ts", + content: `interface GlobalFoo { age: number }` + }; - const additionalFiles = getAdditionalFileOrFolder ? getAdditionalFileOrFolder() : []; - const configFile = { - path: configFilePath, - content: JSON.stringify(configObj || { compilerOptions: {} }) - }; + const additionalFiles = getAdditionalFileOrFolder ? getAdditionalFileOrFolder() : []; + const configFile = { + path: configFilePath, + content: JSON.stringify(configObj || { compilerOptions: {} }) + }; - const files: File[] = [file1Consumer1, moduleFile1, file1Consumer2, moduleFile2, ...additionalFiles, globalFile3, libFile, configFile]; + const files: File[] = [file1Consumer1, moduleFile1, file1Consumer2, moduleFile2, ...additionalFiles, globalFile3, libFile, configFile]; - const filesToReload = firstReloadFileList && getFiles(firstReloadFileList) || files; - const host = createServerHost([filesToReload[0], configFile]); + const filesToReload = firstReloadFileList && getFiles(firstReloadFileList) || files; + const host = createServerHost([filesToReload[0], configFile]); - // Initial project creation - const { session, verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); - const openFiles = [filesToReload[0].path]; - verifyInitialOpen(filesToReload[0]); + // Initial project creation + const { session, verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); + const openFiles = [filesToReload[0].path]; + verifyInitialOpen(filesToReload[0]); - // Since this is first event, it will have all the files - filesToReload.forEach(f => host.ensureFileOrFolder(f)); - if (!firstReloadFileList) host.runQueuedTimeoutCallbacks(); // Invalidated module resolutions to schedule project update - verifyProjectsUpdatedInBackgroundEvent(); + // Since this is first event, it will have all the files + filesToReload.forEach(f => host.ensureFileOrFolder(f)); + if (!firstReloadFileList) host.runQueuedTimeoutCallbacks(); // Invalidated module resolutions to schedule project update + verifyProjectsUpdatedInBackgroundEvent(); - return { - host, - moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, - updateContentOfOpenFile, - verifyNoProjectsUpdatedInBackgroundEvent, - verifyProjectsUpdatedInBackgroundEvent - }; + return { + host, + moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, + updateContentOfOpenFile, + verifyNoProjectsUpdatedInBackgroundEvent, + verifyProjectsUpdatedInBackgroundEvent + }; - function getFiles(filelist: string[]) { - return map(filelist, getFile); - } + function getFiles(filelist: string[]) { + return map(filelist, getFile); + } - function getFile(fileName: string) { - return find(files, file => file.path === fileName)!; - } + function getFile(fileName: string) { + return find(files, file => file.path === fileName)!; + } - function verifyNoProjectsUpdatedInBackgroundEvent() { - host.runQueuedTimeoutCallbacks(); - verifyProjectsUpdatedInBackgroundEventHandler([]); - } + function verifyNoProjectsUpdatedInBackgroundEvent() { + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([]); + } - function verifyProjectsUpdatedInBackgroundEvent() { - host.runQueuedTimeoutCallbacks(); - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - } + function verifyProjectsUpdatedInBackgroundEvent() { + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); + } - function updateContentOfOpenFile(file: File, newContent: string) { - session.executeCommandSeq({ - command: server.CommandNames.Change, - arguments: { - file: file.path, - insertString: newContent, - endLine: 1, - endOffset: file.content.length, - line: 1, - offset: 1 - } - }); - file.content = newContent; - } + function updateContentOfOpenFile(file: File, newContent: string) { + session.executeCommandSeq({ + command: server.CommandNames.Change, + arguments: { + file: file.path, + insertString: newContent, + endLine: 1, + endOffset: file.content.length, + line: 1, + offset: 1 + } + }); + file.content = newContent; } + } - it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { - const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); + it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { + const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); - // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { console.log('hi'); };`); - verifyProjectsUpdatedInBackgroundEvent(); - }); + // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { console.log('hi'); };`); + verifyProjectsUpdatedInBackgroundEvent(); + }); - it("should be up-to-date with the reference map changes", () => { - const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyProjectsUpdatedInBackgroundEvent, verifyNoProjectsUpdatedInBackgroundEvent } = getInitialState(); + it("should be up-to-date with the reference map changes", () => { + const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyProjectsUpdatedInBackgroundEvent, verifyNoProjectsUpdatedInBackgroundEvent } = getInitialState(); - // Change file1Consumer1 content to `export let y = Foo();` - updateContentOfOpenFile(file1Consumer1, "export let y = Foo();"); - verifyNoProjectsUpdatedInBackgroundEvent(); + // Change file1Consumer1 content to `export let y = Foo();` + updateContentOfOpenFile(file1Consumer1, "export let y = Foo();"); + verifyNoProjectsUpdatedInBackgroundEvent(); - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); - // Add the import statements back to file1Consumer1 - updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`); - verifyNoProjectsUpdatedInBackgroundEvent(); + // Add the import statements back to file1Consumer1 + updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`); + verifyNoProjectsUpdatedInBackgroundEvent(); - // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` - host.writeFile(moduleFile1.path, `export var T: number;export var T2: string;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` + host.writeFile(moduleFile1.path, `export var T: number;export var T2: string;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); - // Multiple file edits in one go: + // Multiple file edits in one go: - // Change file1Consumer1 content to `export let y = Foo();` - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`); - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); - }); + // Change file1Consumer1 content to `export let y = Foo();` + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`); + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); + }); - it("should be up-to-date with deleted files", () => { - const { host, moduleFile1, file1Consumer2, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); + it("should be up-to-date with deleted files", () => { + const { host, moduleFile1, file1Consumer2, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - // Delete file1Consumer2 - host.deleteFile(file1Consumer2.path); - verifyProjectsUpdatedInBackgroundEvent(); - }); + // Delete file1Consumer2 + host.deleteFile(file1Consumer2.path); + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should be up-to-date with newly created files", () => { + const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent, } = getInitialState(); - it("should be up-to-date with newly created files", () => { - const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent, } = getInitialState(); + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + host.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`); + verifyProjectsUpdatedInBackgroundEvent(); + }); - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - host.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`); - verifyProjectsUpdatedInBackgroundEvent(); + it("should detect changes in non-root files", () => { + const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + configObj: { files: [file1Consumer1Path] }, }); - it("should detect changes in non-root files", () => { - const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - configObj: { files: [file1Consumer1Path] }, - }); + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + // change file1 internal, and verify only file1 is affected + host.writeFile(moduleFile1.path, moduleFile1.content + "var T1: number;"); + verifyProjectsUpdatedInBackgroundEvent(); + }); - // change file1 internal, and verify only file1 is affected - host.writeFile(moduleFile1.path, moduleFile1.content + "var T1: number;"); - verifyProjectsUpdatedInBackgroundEvent(); - }); + it("should return all files if a global file changed shape", () => { + const { host, globalFile3, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); - it("should return all files if a global file changed shape", () => { - const { host, globalFile3, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); + host.writeFile(globalFile3.path, globalFile3.content + "var T2: string;"); + verifyProjectsUpdatedInBackgroundEvent(); + }); - host.writeFile(globalFile3.path, globalFile3.content + "var T2: string;"); - verifyProjectsUpdatedInBackgroundEvent(); + it("should always return the file itself if '--isolatedModules' is specified", () => { + const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + configObj: { compilerOptions: { isolatedModules: true } } }); - it("should always return the file itself if '--isolatedModules' is specified", () => { - const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - configObj: { compilerOptions: { isolatedModules: true } } - }); + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); + }); - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + it("should always return the file itself if '--out' or '--outFile' is specified", () => { + const outFilePath = "/a/b/out.js"; + const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + configObj: { compilerOptions: { module: "system", outFile: outFilePath } } }); - it("should always return the file itself if '--out' or '--outFile' is specified", () => { - const outFilePath = "/a/b/out.js"; - const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - configObj: { compilerOptions: { module: "system", outFile: outFilePath } } - }); + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); + }); - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + it("should return cascaded affected file list", () => { + const file1Consumer1Consumer1: File = { + path: "/a/b/file1Consumer1Consumer1.ts", + content: `import {y} from "./file1Consumer1";` + }; + const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] }); - it("should return cascaded affected file list", () => { - const file1Consumer1Consumer1: File = { - path: "/a/b/file1Consumer1Consumer1.ts", - content: `import {y} from "./file1Consumer1";` - }; - const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] - }); - - updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;"); - verifyNoProjectsUpdatedInBackgroundEvent(); + updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;"); + verifyNoProjectsUpdatedInBackgroundEvent(); - // Doesnt change the shape of file1Consumer1 - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + // Doesnt change the shape of file1Consumer1 + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); - // Change both files before the timeout - updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;"); - host.writeFile(moduleFile1.path, `export var T2: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); - }); + // Change both files before the timeout + updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;"); + host.writeFile(moduleFile1.path, `export var T2: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); + }); - it("should work fine for files with circular references", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: ` + it("should work fine for files with circular references", () => { + const file1: File = { + path: "/a/b/file1.ts", + content: ` /// export var t1 = 10;` - }; - const file2: File = { - path: "/a/b/file2.ts", - content: ` + }; + const file2: File = { + path: "/a/b/file2.ts", + content: ` /// export var t2 = 10;` - }; - const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - getAdditionalFileOrFolder: () => [file1, file2], - firstReloadFileList: [file1.path, libFile.path, file2.path, configFilePath] - }); - - host.writeFile(file2.path, file2.content + "export var t3 = 10;"); - verifyProjectsUpdatedInBackgroundEvent(); + }; + const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [file1, file2], + firstReloadFileList: [file1.path, libFile.path, file2.path, configFilePath] }); - it("should detect removed code file", () => { - const referenceFile1: File = { - path: "/a/b/referenceFile1.ts", - content: ` + host.writeFile(file2.path, file2.content + "export var t3 = 10;"); + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should detect removed code file", () => { + const referenceFile1: File = { + path: "/a/b/referenceFile1.ts", + content: ` /// export var x = Foo();` - }; - const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - getAdditionalFileOrFolder: () => [referenceFile1], - firstReloadFileList: [referenceFile1.path, libFile.path, moduleFile1Path, configFilePath] - }); - - host.deleteFile(moduleFile1Path); - verifyProjectsUpdatedInBackgroundEvent(); + }; + const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [referenceFile1.path, libFile.path, moduleFile1Path, configFilePath] }); - it("should detect non-existing code file", () => { - const referenceFile1: File = { - path: "/a/b/referenceFile1.ts", - content: ` + host.deleteFile(moduleFile1Path); + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should detect non-existing code file", () => { + const referenceFile1: File = { + path: "/a/b/referenceFile1.ts", + content: ` /// export var x = Foo();` - }; - const { host, moduleFile2, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - getAdditionalFileOrFolder: () => [referenceFile1], - firstReloadFileList: [referenceFile1.path, libFile.path, configFilePath] - }); + }; + const { host, moduleFile2, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [referenceFile1.path, libFile.path, configFilePath] + }); - updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();"); - verifyNoProjectsUpdatedInBackgroundEvent(); + updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();"); + verifyNoProjectsUpdatedInBackgroundEvent(); - // Create module File2 and see both files are saved - host.writeFile(moduleFile2.path, moduleFile2.content); - verifyProjectsUpdatedInBackgroundEvent(); - }); + // Create module File2 and see both files are saved + host.writeFile(moduleFile2.path, moduleFile2.content); + verifyProjectsUpdatedInBackgroundEvent(); }); + }); - describe("resolution when resolution cache size", () => { - function verifyWithMaxCacheLimit(useSlashRootAsSomeNotRootFolderInUserDirectory: boolean) { - const rootFolder = useSlashRootAsSomeNotRootFolderInUserDirectory ? "/user/username/rootfolder/otherfolder/" : "/"; - const file1: File = { - path: rootFolder + "a/b/project/file1.ts", - content: 'import a from "file2"' - }; - const file2: File = { - path: rootFolder + "a/b/node_modules/file2.d.ts", - content: "export class a { }" - }; - const file3: File = { - path: rootFolder + "a/b/project/file3.ts", - content: "export class c { }" - }; - const configFile: File = { - path: rootFolder + "a/b/project/tsconfig.json", - content: JSON.stringify({ compilerOptions: { typeRoots: [] } }) - }; - - const projectFiles = [file1, file3, libFile, configFile]; - const openFiles = [file1.path]; - const watchedRecursiveDirectories = useSlashRootAsSomeNotRootFolderInUserDirectory ? - // Folders of node_modules lookup not in changedRoot - ["a/b/project", "a/b/project/node_modules", "a/b/node_modules", "a/node_modules", "node_modules"].map(v => rootFolder + v) : - // Folder of tsconfig - ["/a/b/project", "/a/b/project/node_modules"]; - const host = createServerHost(projectFiles); - const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); - const projectService = session.getProjectService(); - verifyInitialOpen(file1); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects.get(configFile.path)!; - verifyProject(); - - file3.content += "export class d {}"; - host.writeFile(file3.path, file3.content); - host.checkTimeoutQueueLengthAndRun(2); - - // Since this is first event - verifyProject(); - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); + describe("resolution when resolution cache size", () => { + function verifyWithMaxCacheLimit(useSlashRootAsSomeNotRootFolderInUserDirectory: boolean) { + const rootFolder = useSlashRootAsSomeNotRootFolderInUserDirectory ? "/user/username/rootfolder/otherfolder/" : "/"; + const file1: File = { + path: rootFolder + "a/b/project/file1.ts", + content: 'import a from "file2"' + }; + const file2: File = { + path: rootFolder + "a/b/node_modules/file2.d.ts", + content: "export class a { }" + }; + const file3: File = { + path: rootFolder + "a/b/project/file3.ts", + content: "export class c { }" + }; + const configFile: File = { + path: rootFolder + "a/b/project/tsconfig.json", + content: JSON.stringify({ compilerOptions: { typeRoots: [] } }) + }; - projectFiles.push(file2); - host.writeFile(file2.path, file2.content); - host.runQueuedTimeoutCallbacks(); // For invalidation - host.runQueuedTimeoutCallbacks(); // For actual update - if (useSlashRootAsSomeNotRootFolderInUserDirectory) { - watchedRecursiveDirectories.length = 3; - } - else { - // file2 addition wont be detected - projectFiles.pop(); - assert.isTrue(host.fileExists(file2.path)); + const projectFiles = [file1, file3, libFile, configFile]; + const openFiles = [file1.path]; + const watchedRecursiveDirectories = useSlashRootAsSomeNotRootFolderInUserDirectory ? + // Folders of node_modules lookup not in changedRoot + ["a/b/project", "a/b/project/node_modules", "a/b/node_modules", "a/node_modules", "node_modules"].map(v => rootFolder + v) : + // Folder of tsconfig + ["/a/b/project", "/a/b/project/node_modules"]; + const host = createServerHost(projectFiles); + const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); + const projectService = session.getProjectService(); + verifyInitialOpen(file1); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path)!; + verifyProject(); + + file3.content += "export class d {}"; + host.writeFile(file3.path, file3.content); + host.checkTimeoutQueueLengthAndRun(2); + + // Since this is first event + verifyProject(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles } - verifyProject(); + }]); - verifyProjectsUpdatedInBackgroundEventHandler(useSlashRootAsSomeNotRootFolderInUserDirectory ? [{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }] : []); + projectFiles.push(file2); + host.writeFile(file2.path, file2.content); + host.runQueuedTimeoutCallbacks(); // For invalidation + host.runQueuedTimeoutCallbacks(); // For actual update + if (useSlashRootAsSomeNotRootFolderInUserDirectory) { + watchedRecursiveDirectories.length = 3; + } + else { + // file2 addition wont be detected + projectFiles.pop(); + assert.isTrue(host.fileExists(file2.path)); + } + verifyProject(); - function verifyProject() { - checkProjectActualFiles(project, map(projectFiles, file => file.path)); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); + verifyProjectsUpdatedInBackgroundEventHandler(useSlashRootAsSomeNotRootFolderInUserDirectory ? [{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles } + }] : []); + + function verifyProject() { + checkProjectActualFiles(project, map(projectFiles, file => file.path)); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); } + } - it("project is not at root level", () => { - verifyWithMaxCacheLimit(/*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true); - }); + it("project is not at root level", () => { + verifyWithMaxCacheLimit(/*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true); + }); - it("project is at root level", () => { - verifyWithMaxCacheLimit(/*useSlashRootAsSomeNotRootFolderInUserDirectory*/ false); - }); + it("project is at root level", () => { + verifyWithMaxCacheLimit(/*useSlashRootAsSomeNotRootFolderInUserDirectory*/ false); }); - } + }); + } - describe("when event handler is set in the session", () => { - verifyProjectsUpdatedInBackgroundEvent(createSessionWithProjectChangedEventHandler); + describe("when event handler is set in the session", () => { + verifyProjectsUpdatedInBackgroundEvent(createSessionWithProjectChangedEventHandler); - function createSessionWithProjectChangedEventHandler(host: TestServerHost): ProjectsUpdatedInBackgroundEventVerifier { - const { session, events: projectChangedEvents } = createSessionWithEventTracking(host, server.ProjectsUpdatedInBackgroundEvent); - return { - session, - verifyProjectsUpdatedInBackgroundEventHandler, - verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) - }; + function createSessionWithProjectChangedEventHandler(host: TestServerHost): ProjectsUpdatedInBackgroundEventVerifier { + const { session, events: projectChangedEvents } = createSessionWithEventTracking(host, server.ProjectsUpdatedInBackgroundEvent); + return { + session, + verifyProjectsUpdatedInBackgroundEventHandler, + verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) + }; - function eventToString(event: server.ProjectsUpdatedInBackgroundEvent) { - return JSON.stringify(event && { eventName: event.eventName, data: event.data }); - } + function eventToString(event: server.ProjectsUpdatedInBackgroundEvent) { + return JSON.stringify(event && { eventName: event.eventName, data: event.data }); + } - function eventsToString(events: readonly server.ProjectsUpdatedInBackgroundEvent[]) { - return "[" + map(events, eventToString).join(",") + "]"; - } + function eventsToString(events: readonly server.ProjectsUpdatedInBackgroundEvent[]) { + return "[" + map(events, eventToString).join(",") + "]"; + } - function verifyProjectsUpdatedInBackgroundEventHandler(expectedEvents: readonly server.ProjectsUpdatedInBackgroundEvent[]) { - assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`); - forEach(projectChangedEvents, (actualEvent, i) => { - const expectedEvent = expectedEvents[i]; - assert.strictEqual(actualEvent.eventName, expectedEvent.eventName); - verifyFiles("openFiles", actualEvent.data.openFiles, expectedEvent.data.openFiles); - }); + function verifyProjectsUpdatedInBackgroundEventHandler(expectedEvents: readonly server.ProjectsUpdatedInBackgroundEvent[]) { + assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`); + forEach(projectChangedEvents, (actualEvent, i) => { + const expectedEvent = expectedEvents[i]; + assert.strictEqual(actualEvent.eventName, expectedEvent.eventName); + verifyFiles("openFiles", actualEvent.data.openFiles, expectedEvent.data.openFiles); + }); - // Verified the events, reset them - projectChangedEvents.length = 0; - } + // Verified the events, reset them + projectChangedEvents.length = 0; } - }); + } + }); - describe("when event handler is not set but session is created with canUseEvents = true", () => { - describe("without noGetErrOnBackgroundUpdate, diagnostics for open files are queued", () => { - verifyProjectsUpdatedInBackgroundEvent(createSessionThatUsesEvents); - }); + describe("when event handler is not set but session is created with canUseEvents = true", () => { + describe("without noGetErrOnBackgroundUpdate, diagnostics for open files are queued", () => { + verifyProjectsUpdatedInBackgroundEvent(createSessionThatUsesEvents); + }); - describe("with noGetErrOnBackgroundUpdate, diagnostics for open file are not queued", () => { - verifyProjectsUpdatedInBackgroundEvent(host => createSessionThatUsesEvents(host, /*noGetErrOnBackgroundUpdate*/ true)); - }); + describe("with noGetErrOnBackgroundUpdate, diagnostics for open file are not queued", () => { + verifyProjectsUpdatedInBackgroundEvent(host => createSessionThatUsesEvents(host, /*noGetErrOnBackgroundUpdate*/ true)); + }); - function createSessionThatUsesEvents(host: TestServerHost, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier { - const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, server.ProjectsUpdatedInBackgroundEvent, { noGetErrOnBackgroundUpdate }); + function createSessionThatUsesEvents(host: TestServerHost, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier { + const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, server.ProjectsUpdatedInBackgroundEvent, { noGetErrOnBackgroundUpdate }); - return { - session, - verifyProjectsUpdatedInBackgroundEventHandler, - verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) - }; + return { + session, + verifyProjectsUpdatedInBackgroundEventHandler, + verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) + }; - function verifyProjectsUpdatedInBackgroundEventHandler(expected: readonly server.ProjectsUpdatedInBackgroundEvent[]) { - const expectedEvents: protocol.ProjectsUpdatedInBackgroundEventBody[] = map(expected, e => { - return { - openFiles: e.data.openFiles - }; - }); - const events = getEvents(); - assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${map(events, e => e.body)} Expected: ${expectedEvents}`); - forEach(events, (actualEvent, i) => { - const expectedEvent = expectedEvents[i]; - verifyFiles("openFiles", actualEvent.body.openFiles, expectedEvent.openFiles); - }); + function verifyProjectsUpdatedInBackgroundEventHandler(expected: readonly server.ProjectsUpdatedInBackgroundEvent[]) { + const expectedEvents: protocol.ProjectsUpdatedInBackgroundEventBody[] = map(expected, e => { + return { + openFiles: e.data.openFiles + }; + }); + const events = getEvents(); + assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${map(events, e => e.body)} Expected: ${expectedEvents}`); + forEach(events, (actualEvent, i) => { + const expectedEvent = expectedEvents[i]; + verifyFiles("openFiles", actualEvent.body.openFiles, expectedEvent.openFiles); + }); - // Verified the events, reset them - clearEvents(); + // Verified the events, reset them + clearEvents(); - if (events.length) { - host.checkTimeoutQueueLength(noGetErrOnBackgroundUpdate ? 0 : 1); // Error checking queued only if not noGetErrOnBackgroundUpdate - } + if (events.length) { + host.checkTimeoutQueueLength(noGetErrOnBackgroundUpdate ? 0 : 1); // Error checking queued only if not noGetErrOnBackgroundUpdate } } - }); + } }); +}); } diff --git a/src/testRunner/unittests/tsserver/exportMapCache.ts b/src/testRunner/unittests/tsserver/exportMapCache.ts index 5eb69c514cd1b..2ceeaeb221649 100644 --- a/src/testRunner/unittests/tsserver/exportMapCache.ts +++ b/src/testRunner/unittests/tsserver/exportMapCache.ts @@ -1,143 +1,143 @@ namespace ts.projectSystem { - const packageJson: File = { - path: "/package.json", - content: `{ "dependencies": { "mobx": "*" } }` - }; - const aTs: File = { - path: "/a.ts", - content: "export const foo = 0;", - }; - const bTs: File = { - path: "/b.ts", - content: "foo", - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", - }; - const ambientDeclaration: File = { - path: "/ambient.d.ts", - content: "declare module 'ambient' {}" - }; - const mobxDts: File = { - path: "/node_modules/mobx/index.d.ts", - content: "export declare function observable(): unknown;" - }; - const exportEqualsMappedType: File = { - path: "/lib/foo/constants.d.ts", - content: ` +const packageJson: File = { + path: "/package.json", + content: `{ "dependencies": { "mobx": "*" } }` +}; +const aTs: File = { + path: "/a.ts", + content: "export const foo = 0;", +}; +const bTs: File = { + path: "/b.ts", + content: "foo", +}; +const tsconfig: File = { + path: "/tsconfig.json", + content: "{}", +}; +const ambientDeclaration: File = { + path: "/ambient.d.ts", + content: "declare module 'ambient' {}" +}; +const mobxDts: File = { + path: "/node_modules/mobx/index.d.ts", + content: "export declare function observable(): unknown;" +}; +const exportEqualsMappedType: File = { + path: "/lib/foo/constants.d.ts", + content: ` type Signals = "SIGINT" | "SIGABRT"; declare const exp: {} & { [K in Signals]: K }; export = exp;`, - }; +}; - describe("unittests:: tsserver:: exportMapCache", () => { - it("caches auto-imports in the same file", () => { - const { exportMapCache } = setup(); - assert.ok(exportMapCache.isUsableByFile(bTs.path as Path)); - assert.ok(!exportMapCache.isEmpty()); - }); +describe("unittests:: tsserver:: exportMapCache", () => { + it("caches auto-imports in the same file", () => { + const { exportMapCache } = setup(); + assert.ok(exportMapCache.isUsableByFile(bTs.path as Path)); + assert.ok(!exportMapCache.isEmpty()); + }); - it("invalidates the cache when new files are added", () => { - const { host, exportMapCache } = setup(); - host.writeFile("/src/a2.ts", aTs.content); - host.runQueuedTimeoutCallbacks(); - assert.ok(!exportMapCache.isUsableByFile(bTs.path as Path)); - assert.ok(exportMapCache.isEmpty()); - }); + it("invalidates the cache when new files are added", () => { + const { host, exportMapCache } = setup(); + host.writeFile("/src/a2.ts", aTs.content); + host.runQueuedTimeoutCallbacks(); + assert.ok(!exportMapCache.isUsableByFile(bTs.path as Path)); + assert.ok(exportMapCache.isEmpty()); + }); - it("invalidates the cache when files are deleted", () => { - const { host, projectService, exportMapCache } = setup(); - projectService.closeClientFile(aTs.path); - host.deleteFile(aTs.path); - host.runQueuedTimeoutCallbacks(); - assert.ok(!exportMapCache.isUsableByFile(bTs.path as Path)); - assert.ok(exportMapCache.isEmpty()); - }); + it("invalidates the cache when files are deleted", () => { + const { host, projectService, exportMapCache } = setup(); + projectService.closeClientFile(aTs.path); + host.deleteFile(aTs.path); + host.runQueuedTimeoutCallbacks(); + assert.ok(!exportMapCache.isUsableByFile(bTs.path as Path)); + assert.ok(exportMapCache.isEmpty()); + }); - it("does not invalidate the cache when package.json is changed inconsequentially", () => { - const { host, exportMapCache, project } = setup(); - host.writeFile("/package.json", `{ "name": "blah", "dependencies": { "mobx": "*" } }`); - host.runQueuedTimeoutCallbacks(); - project.getPackageJsonAutoImportProvider(); - assert.ok(exportMapCache.isUsableByFile(bTs.path as Path)); - assert.ok(!exportMapCache.isEmpty()); - }); + it("does not invalidate the cache when package.json is changed inconsequentially", () => { + const { host, exportMapCache, project } = setup(); + host.writeFile("/package.json", `{ "name": "blah", "dependencies": { "mobx": "*" } }`); + host.runQueuedTimeoutCallbacks(); + project.getPackageJsonAutoImportProvider(); + assert.ok(exportMapCache.isUsableByFile(bTs.path as Path)); + assert.ok(!exportMapCache.isEmpty()); + }); - it("invalidates the cache when package.json change results in AutoImportProvider change", () => { - const { host, exportMapCache, project } = setup(); - host.writeFile("/package.json", `{}`); - host.runQueuedTimeoutCallbacks(); - project.getPackageJsonAutoImportProvider(); - assert.ok(!exportMapCache.isUsableByFile(bTs.path as Path)); - assert.ok(exportMapCache.isEmpty()); - }); + it("invalidates the cache when package.json change results in AutoImportProvider change", () => { + const { host, exportMapCache, project } = setup(); + host.writeFile("/package.json", `{}`); + host.runQueuedTimeoutCallbacks(); + project.getPackageJsonAutoImportProvider(); + assert.ok(!exportMapCache.isUsableByFile(bTs.path as Path)); + assert.ok(exportMapCache.isEmpty()); + }); - it("does not store transient symbols through program updates", () => { - const { exportMapCache, project, session } = setup(); - // SIGINT, exported from /lib/foo/constants.d.ts, is a mapped type property, which will be a transient symbol. - // Transient symbols contain types, which retain the checkers they came from, so are not safe to cache. - // We clear symbols from the cache during updateGraph, leaving only the information about how to re-get them - // (see getters on `CachedSymbolExportInfo`). We can roughly test that this is working by ensuring that - // accessing a transient symbol with two different checkers results in different symbol identities, since - // transient symbols are recreated with every new checker. - const programBefore = project.getCurrentProgram()!; - let sigintPropBefore: readonly SymbolExportInfo[] | undefined; - exportMapCache.forEach(bTs.path as Path, (info, name) => { - if (name === "SIGINT") sigintPropBefore = info; - }); - assert.ok(sigintPropBefore); - assert.ok(sigintPropBefore![0].symbol.flags & SymbolFlags.Transient); - const symbolIdBefore = getSymbolId(sigintPropBefore![0].symbol); + it("does not store transient symbols through program updates", () => { + const { exportMapCache, project, session } = setup(); + // SIGINT, exported from /lib/foo/constants.d.ts, is a mapped type property, which will be a transient symbol. + // Transient symbols contain types, which retain the checkers they came from, so are not safe to cache. + // We clear symbols from the cache during updateGraph, leaving only the information about how to re-get them + // (see getters on `CachedSymbolExportInfo`). We can roughly test that this is working by ensuring that + // accessing a transient symbol with two different checkers results in different symbol identities, since + // transient symbols are recreated with every new checker. + const programBefore = project.getCurrentProgram()!; + let sigintPropBefore: readonly SymbolExportInfo[] | undefined; + exportMapCache.forEach(bTs.path as Path, (info, name) => { + if (name === "SIGINT") sigintPropBefore = info; + }); + assert.ok(sigintPropBefore); + assert.ok(sigintPropBefore![0].symbol.flags & SymbolFlags.Transient); + const symbolIdBefore = getSymbolId(sigintPropBefore![0].symbol); - // Update program without clearing cache - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: bTs.path, - textChanges: [{ - newText: " ", - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - }] + // Update program without clearing cache + session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: bTs.path, + textChanges: [{ + newText: " ", + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, }] - } - }); - project.getLanguageService(/*ensureSynchronized*/ true); - assert.notEqual(programBefore, project.getCurrentProgram()!); + }] + } + }); + project.getLanguageService(/*ensureSynchronized*/ true); + assert.notEqual(programBefore, project.getCurrentProgram()!); - // Get same info from cache again - let sigintPropAfter: readonly SymbolExportInfo[] | undefined; - exportMapCache.forEach(bTs.path as Path, (info, name) => { - if (name === "SIGINT") sigintPropAfter = info; - }); - assert.ok(sigintPropAfter); - assert.notEqual(symbolIdBefore, getSymbolId(sigintPropAfter![0].symbol)); + // Get same info from cache again + let sigintPropAfter: readonly SymbolExportInfo[] | undefined; + exportMapCache.forEach(bTs.path as Path, (info, name) => { + if (name === "SIGINT") sigintPropAfter = info; }); + assert.ok(sigintPropAfter); + assert.notEqual(symbolIdBefore, getSymbolId(sigintPropAfter![0].symbol)); }); +}); - function setup() { - const host = createServerHost([aTs, bTs, ambientDeclaration, tsconfig, packageJson, mobxDts, exportEqualsMappedType]); - const session = createSession(host); - openFilesForSession([aTs, bTs], session); - const projectService = session.getProjectService(); - const project = configuredProjectAt(projectService, 0); - triggerCompletions(); - const checker = project.getLanguageService().getProgram()!.getTypeChecker(); - return { host, project, projectService, session, exportMapCache: project.getCachedExportInfoMap(), checker, triggerCompletions }; +function setup() { + const host = createServerHost([aTs, bTs, ambientDeclaration, tsconfig, packageJson, mobxDts, exportEqualsMappedType]); + const session = createSession(host); + openFilesForSession([aTs, bTs], session); + const projectService = session.getProjectService(); + const project = configuredProjectAt(projectService, 0); + triggerCompletions(); + const checker = project.getLanguageService().getProgram()!.getTypeChecker(); + return { host, project, projectService, session, exportMapCache: project.getCachedExportInfoMap(), checker, triggerCompletions }; - function triggerCompletions() { - const requestLocation: protocol.FileLocationRequestArgs = { - file: bTs.path, - line: 1, - offset: 3, - }; - executeSessionRequest(session, protocol.CommandTypes.CompletionInfo, { - ...requestLocation, - includeExternalModuleExports: true, - prefix: "foo", - }); - } + function triggerCompletions() { + const requestLocation: protocol.FileLocationRequestArgs = { + file: bTs.path, + line: 1, + offset: 3, + }; + executeSessionRequest(session, protocol.CommandTypes.CompletionInfo, { + ...requestLocation, + includeExternalModuleExports: true, + prefix: "foo", + }); } } +} diff --git a/src/testRunner/unittests/tsserver/externalProjects.ts b/src/testRunner/unittests/tsserver/externalProjects.ts index 3bcb5789daaea..54cb3ed567c35 100644 --- a/src/testRunner/unittests/tsserver/externalProjects.ts +++ b/src/testRunner/unittests/tsserver/externalProjects.ts @@ -1,941 +1,941 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: ExternalProjects", () => { - describe("can handle tsconfig file name with difference casing", () => { - function verifyConfigFileCasing(lazyConfiguredProjectsFromExternalProject: boolean) { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - include: [] - }) - }; - - const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false }); - const service = createProjectService(host); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); - service.openExternalProject({ - projectFileName: "/a/b/project.csproj", - rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]), - options: {} - } as protocol.ExternalProject); - service.checkNumberOfProjects({ configuredProjects: 1 }); - const project = service.configuredProjects.get(config.path)!; - if (lazyConfiguredProjectsFromExternalProject) { - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded - checkProjectActualFiles(project, emptyArray); - } - else { - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded - checkProjectActualFiles(project, [upperCaseConfigFilePath]); - } - - service.openClientFile(f1.path); - service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); +describe("unittests:: tsserver:: ExternalProjects", () => { + describe("can handle tsconfig file name with difference casing", () => { + function verifyConfigFileCasing(lazyConfiguredProjectsFromExternalProject: boolean) { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + include: [] + }) + }; - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated + const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false }); + const service = createProjectService(host); + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); + service.openExternalProject({ + projectFileName: "/a/b/project.csproj", + rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]), + options: {} + } as protocol.ExternalProject); + service.checkNumberOfProjects({ configuredProjects: 1 }); + const project = service.configuredProjects.get(config.path)!; + if (lazyConfiguredProjectsFromExternalProject) { + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded + checkProjectActualFiles(project, emptyArray); + } + else { + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded checkProjectActualFiles(project, [upperCaseConfigFilePath]); - checkProjectActualFiles(service.inferredProjects[0], [f1.path]); } - it("when lazyConfiguredProjectsFromExternalProject not set", () => { - verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ false); - }); + service.openClientFile(f1.path); + service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); - it("when lazyConfiguredProjectsFromExternalProject is set", () => { - verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ true); - }); - }); + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated + checkProjectActualFiles(project, [upperCaseConfigFilePath]); + checkProjectActualFiles(service.inferredProjects[0], [f1.path]); + } - it("load global plugins", () => { - const f1 = { - path: "/a/file1.ts", - content: "let x = [1, 2];" - }; - const p1 = { projectFileName: "/a/proj1.csproj", rootFiles: [toExternalFile(f1.path)], options: {} }; - - const host = createServerHost([f1]); - host.require = (_initialPath, moduleName) => { - assert.equal(moduleName, "myplugin"); - return { - module: () => ({ - create(info: server.PluginCreateInfo) { - const proxy = Harness.LanguageService.makeDefaultProxy(info); - proxy.getSemanticDiagnostics = filename => { - const prev = info.languageService.getSemanticDiagnostics(filename); - const sourceFile: SourceFile = info.project.getSourceFile(toPath(filename, /*basePath*/ undefined, createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!; - prev.push({ - category: DiagnosticCategory.Warning, - file: sourceFile, - code: 9999, - length: 3, - messageText: `Plugin diagnostic`, - start: 0 - }); - return prev; - }; - return proxy; - } - }), - error: undefined - }; - }; - const session = createSession(host, { globalPlugins: ["myplugin"] }); - - session.executeCommand({ - seq: 1, - type: "request", - command: "openExternalProjects", - arguments: { projects: [p1] } - } as protocol.OpenExternalProjectsRequest); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); - - const handlerResponse = session.executeCommand({ - seq: 2, - type: "request", - command: "semanticDiagnosticsSync", - arguments: { - file: f1.path, - projectFileName: p1.projectFileName - } - } as protocol.SemanticDiagnosticsSyncRequest); + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ false); + }); - assert.isDefined(handlerResponse.response); - const response = handlerResponse.response as protocol.Diagnostic[]; - assert.equal(response.length, 1); - assert.equal(response[0].text, "Plugin diagnostic"); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ true); }); + }); - it("remove not-listed external projects", () => { - const f1 = { - path: "/a/app.ts", - content: "let x = 1" - }; - const f2 = { - path: "/b/app.ts", - content: "let x = 1" - }; - const f3 = { - path: "/c/app.ts", - content: "let x = 1" - }; - const makeProject = (f: File) => ({ projectFileName: f.path + ".csproj", rootFiles: [toExternalFile(f.path)], options: {} }); - const p1 = makeProject(f1); - const p2 = makeProject(f2); - const p3 = makeProject(f3); - - const host = createServerHost([f1, f2, f3]); - const session = createSession(host); - - session.executeCommand({ - seq: 1, - type: "request", - command: "openExternalProjects", - arguments: { projects: [p1, p2] } - } as protocol.OpenExternalProjectsRequest); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { externalProjects: 2 }); - assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); - assert.equal(projectService.externalProjects[1].getProjectName(), p2.projectFileName); - - session.executeCommand({ - seq: 2, - type: "request", - command: "openExternalProjects", - arguments: { projects: [p1, p3] } - } as protocol.OpenExternalProjectsRequest); - checkNumberOfProjects(projectService, { externalProjects: 2 }); - assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); - assert.equal(projectService.externalProjects[1].getProjectName(), p3.projectFileName); - - session.executeCommand({ - seq: 3, - type: "request", - command: "openExternalProjects", - arguments: { projects: [] } - } as protocol.OpenExternalProjectsRequest); - checkNumberOfProjects(projectService, { externalProjects: 0 }); - - session.executeCommand({ - seq: 3, - type: "request", - command: "openExternalProjects", - arguments: { projects: [p2] } - } as protocol.OpenExternalProjectsRequest); - assert.equal(projectService.externalProjects[0].getProjectName(), p2.projectFileName); + it("load global plugins", () => { + const f1 = { + path: "/a/file1.ts", + content: "let x = [1, 2];" + }; + const p1 = { projectFileName: "/a/proj1.csproj", rootFiles: [toExternalFile(f1.path)], options: {} }; + + const host = createServerHost([f1]); + host.require = (_initialPath, moduleName) => { + assert.equal(moduleName, "myplugin"); + return { + module: () => ({ + create(info: server.PluginCreateInfo) { + const proxy = Harness.LanguageService.makeDefaultProxy(info); + proxy.getSemanticDiagnostics = filename => { + const prev = info.languageService.getSemanticDiagnostics(filename); + const sourceFile: SourceFile = info.project.getSourceFile(toPath(filename, /*basePath*/ undefined, createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!; + prev.push({ + category: DiagnosticCategory.Warning, + file: sourceFile, + code: 9999, + length: 3, + messageText: `Plugin diagnostic`, + start: 0 + }); + return prev; + }; + return proxy; + } + }), + error: undefined + }; + }; + const session = createSession(host, { globalPlugins: ["myplugin"] }); + + session.executeCommand({ + seq: 1, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p1] } + } as protocol.OpenExternalProjectsRequest); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); + + const handlerResponse = session.executeCommand({ + seq: 2, + type: "request", + command: "semanticDiagnosticsSync", + arguments: { + file: f1.path, + projectFileName: p1.projectFileName + } + } as protocol.SemanticDiagnosticsSyncRequest); + + assert.isDefined(handlerResponse.response); + const response = handlerResponse.response as protocol.Diagnostic[]; + assert.equal(response.length, 1); + assert.equal(response[0].text, "Plugin diagnostic"); + }); + + it("remove not-listed external projects", () => { + const f1 = { + path: "/a/app.ts", + content: "let x = 1" + }; + const f2 = { + path: "/b/app.ts", + content: "let x = 1" + }; + const f3 = { + path: "/c/app.ts", + content: "let x = 1" + }; + const makeProject = (f: File) => ({ projectFileName: f.path + ".csproj", rootFiles: [toExternalFile(f.path)], options: {} }); + const p1 = makeProject(f1); + const p2 = makeProject(f2); + const p3 = makeProject(f3); + + const host = createServerHost([f1, f2, f3]); + const session = createSession(host); + + session.executeCommand({ + seq: 1, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p1, p2] } + } as protocol.OpenExternalProjectsRequest); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { externalProjects: 2 }); + assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); + assert.equal(projectService.externalProjects[1].getProjectName(), p2.projectFileName); + + session.executeCommand({ + seq: 2, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p1, p3] } + } as protocol.OpenExternalProjectsRequest); + checkNumberOfProjects(projectService, { externalProjects: 2 }); + assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); + assert.equal(projectService.externalProjects[1].getProjectName(), p3.projectFileName); + + session.executeCommand({ + seq: 3, + type: "request", + command: "openExternalProjects", + arguments: { projects: [] } + } as protocol.OpenExternalProjectsRequest); + checkNumberOfProjects(projectService, { externalProjects: 0 }); + + session.executeCommand({ + seq: 3, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p2] } + } as protocol.OpenExternalProjectsRequest); + assert.equal(projectService.externalProjects[0].getProjectName(), p2.projectFileName); + }); + + it("should not close external project with no open files", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y =1;" + }; + const externalProjectName = "externalproject"; + const host = createServerHost([file1, file2]); + const projectService = createProjectService(host); + projectService.openExternalProject({ + rootFiles: toExternalFiles([file1.path, file2.path]), + options: {}, + projectFileName: externalProjectName }); - it("should not close external project with no open files", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y =1;" - }; - const externalProjectName = "externalproject"; - const host = createServerHost([file1, file2]); - const projectService = createProjectService(host); - projectService.openExternalProject({ - rootFiles: toExternalFiles([file1.path, file2.path]), - options: {}, - projectFileName: externalProjectName - }); + checkNumberOfExternalProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); - checkNumberOfExternalProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); + // open client file - should not lead to creation of inferred project + projectService.openClientFile(file1.path, file1.content); + checkNumberOfExternalProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); - // open client file - should not lead to creation of inferred project - projectService.openClientFile(file1.path, file1.content); - checkNumberOfExternalProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); + // close client file - external project should still exists + projectService.closeClientFile(file1.path); + checkNumberOfExternalProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); - // close client file - external project should still exists - projectService.closeClientFile(file1.path); - checkNumberOfExternalProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); + projectService.closeExternalProject(externalProjectName); + checkNumberOfExternalProjects(projectService, 0); + checkNumberOfInferredProjects(projectService, 0); + }); - projectService.closeExternalProject(externalProjectName); - checkNumberOfExternalProjects(projectService, 0); - checkNumberOfInferredProjects(projectService, 0); + it("external project for dynamic file", () => { + const externalProjectName = "^ScriptDocument1 file1.ts"; + const externalFiles = toExternalFiles(["^ScriptDocument1 file1.ts"]); + const host = createServerHost([]); + const projectService = createProjectService(host); + projectService.openExternalProject({ + rootFiles: externalFiles, + options: {}, + projectFileName: externalProjectName }); - it("external project for dynamic file", () => { - const externalProjectName = "^ScriptDocument1 file1.ts"; - const externalFiles = toExternalFiles(["^ScriptDocument1 file1.ts"]); - const host = createServerHost([]); - const projectService = createProjectService(host); - projectService.openExternalProject({ - rootFiles: externalFiles, - options: {}, - projectFileName: externalProjectName - }); + checkNumberOfExternalProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + verifyDynamic(projectService, "/^scriptdocument1 file1.ts"); - checkNumberOfExternalProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); - verifyDynamic(projectService, "/^scriptdocument1 file1.ts"); + externalFiles[0].content = "let x =1;"; + projectService.applyChangesInOpenFiles(arrayIterator(externalFiles)); + }); - externalFiles[0].content = "let x =1;"; - projectService.applyChangesInOpenFiles(arrayIterator(externalFiles)); - }); + it("when file name starts with ^", () => { + const file: File = { + path: `${tscWatch.projectRoot}/file.ts`, + content: "const x = 10;" + }; + const app: File = { + path: `${tscWatch.projectRoot}/^app.ts`, + content: "const y = 10;" + }; + const host = createServerHost([file, app, libFile]); + const service = createProjectService(host); + service.openExternalProjects([{ + projectFileName: `${tscWatch.projectRoot}/myproject.njsproj`, + rootFiles: [ + toExternalFile(file.path), + toExternalFile(app.path) + ], + options: { }, + }]); + }); - it("when file name starts with ^", () => { - const file: File = { - path: `${tscWatch.projectRoot}/file.ts`, - content: "const x = 10;" - }; - const app: File = { - path: `${tscWatch.projectRoot}/^app.ts`, - content: "const y = 10;" - }; - const host = createServerHost([file, app, libFile]); - const service = createProjectService(host); - service.openExternalProjects([{ - projectFileName: `${tscWatch.projectRoot}/myproject.njsproj`, - rootFiles: [ - toExternalFile(file.path), - toExternalFile(app.path) - ], - options: { }, - }]); + it("external project that included config files", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) + }; + const file2 = { + path: "/a/c/f2.ts", + content: "let y =1;" + }; + const config2 = { + path: "/a/c/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f2.ts"] + } + ) + }; + const file3 = { + path: "/a/d/f3.ts", + content: "let z =1;" + }; + const externalProjectName = "externalproject"; + const host = createServerHost([file1, file2, file3, config1, config2]); + const projectService = createProjectService(host); + projectService.openExternalProject({ + rootFiles: toExternalFiles([config1.path, config2.path, file3.path]), + options: {}, + projectFileName: externalProjectName }); - it("external project that included config files", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; - const config1 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: {}, - files: ["f1.ts"] - } - ) - }; - const file2 = { - path: "/a/c/f2.ts", - content: "let y =1;" - }; - const config2 = { - path: "/a/c/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: {}, - files: ["f2.ts"] - } - ) - }; - const file3 = { - path: "/a/d/f3.ts", - content: "let z =1;" - }; - const externalProjectName = "externalproject"; - const host = createServerHost([file1, file2, file3, config1, config2]); - const projectService = createProjectService(host); - projectService.openExternalProject({ - rootFiles: toExternalFiles([config1.path, config2.path, file3.path]), - options: {}, - projectFileName: externalProjectName - }); + checkNumberOfProjects(projectService, { configuredProjects: 2 }); + const proj1 = projectService.configuredProjects.get(config1.path); + const proj2 = projectService.configuredProjects.get(config2.path); + assert.isDefined(proj1); + assert.isDefined(proj2); + + // open client file - should not lead to creation of inferred project + projectService.openClientFile(file1.path, file1.content); + checkNumberOfProjects(projectService, { configuredProjects: 2 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); + + projectService.openClientFile(file3.path, file3.content); + checkNumberOfProjects(projectService, { configuredProjects: 2, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); + + projectService.closeExternalProject(externalProjectName); + // open file 'file1' from configured project keeps project alive + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); + + projectService.closeClientFile(file3.path); + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + + projectService.closeClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + + projectService.openClientFile(file2.path, file2.content); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(config1.path)); + assert.isDefined(projectService.configuredProjects.get(config2.path)); + }); - checkNumberOfProjects(projectService, { configuredProjects: 2 }); - const proj1 = projectService.configuredProjects.get(config1.path); - const proj2 = projectService.configuredProjects.get(config2.path); - assert.isDefined(proj1); - assert.isDefined(proj2); - - // open client file - should not lead to creation of inferred project - projectService.openClientFile(file1.path, file1.content); - checkNumberOfProjects(projectService, { configuredProjects: 2 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); - - projectService.openClientFile(file3.path, file3.content); - checkNumberOfProjects(projectService, { configuredProjects: 2, inferredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); - - projectService.closeExternalProject(externalProjectName); - // open file 'file1' from configured project keeps project alive - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.isUndefined(projectService.configuredProjects.get(config2.path)); - - projectService.closeClientFile(file3.path); - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.isUndefined(projectService.configuredProjects.get(config2.path)); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - - projectService.closeClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.isUndefined(projectService.configuredProjects.get(config2.path)); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - - projectService.openClientFile(file2.path, file2.content); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.isUndefined(projectService.configuredProjects.get(config1.path)); - assert.isDefined(projectService.configuredProjects.get(config2.path)); + it("external project with included config file opened after configured project", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const externalProjectName = "externalproject"; + const host = createServerHost([file1, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + projectService.openExternalProject({ + rootFiles: toExternalFiles([configFile.path]), + options: {}, + projectFileName: externalProjectName }); - it("external project with included config file opened after configured project", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {} }) - }; - const externalProjectName = "externalproject"; - const host = createServerHost([file1, configFile]); - const projectService = createProjectService(host); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + projectService.closeClientFile(file1.path); + // configured project is alive since it is opened as part of external project + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - projectService.openExternalProject({ - rootFiles: toExternalFiles([configFile.path]), - options: {}, - projectFileName: externalProjectName - }); + projectService.closeExternalProject(externalProjectName); + checkNumberOfProjects(projectService, { configuredProjects: 0 }); + }); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + it("external project with included config file opened after configured project and then closed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/f2.ts", + content: "let x = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const externalProjectName = "externalproject"; + const host = createServerHost([file1, file2, libFile, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path); + + projectService.openExternalProject({ + rootFiles: toExternalFiles([configFile.path]), + options: {}, + projectFileName: externalProjectName + }); - projectService.closeClientFile(file1.path); - // configured project is alive since it is opened as part of external project - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - projectService.closeExternalProject(externalProjectName); - checkNumberOfProjects(projectService, { configuredProjects: 0 }); - }); + projectService.closeExternalProject(externalProjectName); + // configured project is alive since file is still open + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - it("external project with included config file opened after configured project and then closed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/f2.ts", - content: "let x = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {} }) - }; - const externalProjectName = "externalproject"; - const host = createServerHost([file1, file2, libFile, configFile]); - const projectService = createProjectService(host); + projectService.closeClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects.get(configFile.path); + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(configFile.path)); + }); - projectService.openExternalProject({ - rootFiles: toExternalFiles([configFile.path]), - options: {}, - projectFileName: externalProjectName - }); + it("can correctly update external project when set of root files has changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const host = createServerHost([file1, file2]); + const projectService = createProjectService(host); + + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path]) }); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [file1.path]); + + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]) }); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + }); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + it("can update external project when set of root files was not changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "m"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: "export let y = 1" + }; + const file3 = { + path: "/a/m.ts", + content: "export let y = 1" + }; + + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.NodeJs }, rootFiles: toExternalFiles([file1.path, file2.path]) }); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]); + + projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.Classic }, rootFiles: toExternalFiles([file1.path, file2.path]) }); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path, file3.path]); + }); - projectService.closeExternalProject(externalProjectName); - // configured project is alive since file is still open - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + it("language service disabled state is updated in external projects", () => { + const f1 = { + path: "/a/app.js", + content: "var x = 1" + }; + const f2 = { + path: "/a/largefile.js", + content: "" + }; + const host = createServerHost([f1, f2]); + const originalGetFileSize = host.getFileSize; + host.getFileSize = (filePath: string) => + filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); + + const service = createProjectService(host); + const projectFileName = "/a/proj.csproj"; + + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([f1.path, f2.path]), + options: {} + }); + service.checkNumberOfProjects({ externalProjects: 1 }); + assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 1"); - projectService.closeClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([f1.path]), + options: {} + }); + service.checkNumberOfProjects({ externalProjects: 1 }); + assert.isTrue(service.externalProjects[0].languageServiceEnabled, "language service should be enabled"); - projectService.openClientFile(file2.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - assert.isUndefined(projectService.configuredProjects.get(configFile.path)); + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([f1.path, f2.path]), + options: {} }); + service.checkNumberOfProjects({ externalProjects: 1 }); + assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 2"); + }); - it("can correctly update external project when set of root files has changed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" + describe("deleting config file opened from the external project works", () => { + function verifyDeletingConfigFile(lazyConfiguredProjectsFromExternalProject: boolean) { + const site = { + path: "/user/someuser/project/js/site.js", + content: "" }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" + const configFile = { + path: "/user/someuser/project/tsconfig.json", + content: "{}" }; - const host = createServerHost([file1, file2]); + const projectFileName = "/user/someuser/project/WebApplication6.csproj"; + const host = createServerHost([libFile, site, configFile]); const projectService = createProjectService(host); + projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + + const externalProject: protocol.ExternalProject = { + projectFileName, + rootFiles: [toExternalFile(site.path), toExternalFile(configFile.path)], + options: { allowJs: false }, + typeAcquisition: { include: [] } + }; - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path]) }); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [file1.path]); + projectService.openExternalProjects([externalProject]); - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]) }); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); - }); + let knownProjects = projectService.synchronizeProjectList([]); + checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 }); - it("can update external project when set of root files was not changed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "m"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: "export let y = 1" - }; - const file3 = { - path: "/a/m.ts", - content: "export let y = 1" - }; + const configProject = configuredProjectAt(projectService, 0); + checkProjectActualFiles(configProject, lazyConfiguredProjectsFromExternalProject ? + emptyArray : // Since no files opened from this project, its not loaded + [configFile.path]); - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); + host.deleteFile(configFile.path); + + knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039 + checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 }); - projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.NodeJs }, rootFiles: toExternalFiles([file1.path, file2.path]) }); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); - checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]); + externalProject.rootFiles.length = 1; + projectService.openExternalProjects([externalProject]); - projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.Classic }, rootFiles: toExternalFiles([file1.path, file2.path]) }); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); - checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path, file3.path]); + checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); + checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]); + } + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ false); }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ true); + }); + }); - it("language service disabled state is updated in external projects", () => { + describe("correctly handling add/remove tsconfig - 1", () => { + function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { const f1 = { - path: "/a/app.js", - content: "var x = 1" + path: "/a/b/app.ts", + content: "let x = 1;" }; const f2 = { - path: "/a/largefile.js", + path: "/a/b/lib.ts", + content: "" + }; + const tsconfig = { + path: "/a/b/tsconfig.json", content: "" }; const host = createServerHost([f1, f2]); - const originalGetFileSize = host.getFileSize; - host.getFileSize = (filePath: string) => - filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); - - const service = createProjectService(host); - const projectFileName = "/a/proj.csproj"; + const projectService = createProjectService(host); + projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - service.openExternalProject({ - projectFileName, + // open external project + const projectName = "/a/b/proj1"; + projectService.openExternalProject({ + projectFileName: projectName, rootFiles: toExternalFiles([f1.path, f2.path]), options: {} }); - service.checkNumberOfProjects({ externalProjects: 1 }); - assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 1"); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([f1.path]), + // rename lib.ts to tsconfig.json + host.renameFile(f2.path, tsconfig.path); + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, tsconfig.path]), options: {} }); - service.checkNumberOfProjects({ externalProjects: 1 }); - assert.isTrue(service.externalProjects[0].languageServiceEnabled, "language service should be enabled"); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + if (lazyConfiguredProjectsFromExternalProject) { + checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); - service.openExternalProject({ - projectFileName, + // rename tsconfig.json back to lib.ts + host.renameFile(tsconfig.path, f2.path); + projectService.openExternalProject({ + projectFileName: projectName, rootFiles: toExternalFiles([f1.path, f2.path]), options: {} }); - service.checkNumberOfProjects({ externalProjects: 1 }); - assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 2"); - }); - describe("deleting config file opened from the external project works", () => { - function verifyDeletingConfigFile(lazyConfiguredProjectsFromExternalProject: boolean) { - const site = { - path: "/user/someuser/project/js/site.js", - content: "" - }; - const configFile = { - path: "/user/someuser/project/tsconfig.json", - content: "{}" - }; - const projectFileName = "/user/someuser/project/WebApplication6.csproj"; - const host = createServerHost([libFile, site, configFile]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - - const externalProject: protocol.ExternalProject = { - projectFileName, - rootFiles: [toExternalFile(site.path), toExternalFile(configFile.path)], - options: { allowJs: false }, - typeAcquisition: { include: [] } - }; - - projectService.openExternalProjects([externalProject]); - - let knownProjects = projectService.synchronizeProjectList([]); - checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 }); - - const configProject = configuredProjectAt(projectService, 0); - checkProjectActualFiles(configProject, lazyConfiguredProjectsFromExternalProject ? - emptyArray : // Since no files opened from this project, its not loaded - [configFile.path]); - - host.deleteFile(configFile.path); - - knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039 - checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 }); - - externalProject.rootFiles.length = 1; - projectService.openExternalProjects([externalProject]); - - checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); - checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]); - } - it("when lazyConfiguredProjectsFromExternalProject not set", () => { - verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ false); - }); - it("when lazyConfiguredProjectsFromExternalProject is set", () => { - verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ true); - }); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); + } + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); }); - - describe("correctly handling add/remove tsconfig - 1", () => { - function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const f2 = { - path: "/a/b/lib.ts", - content: "" - }; - const tsconfig = { - path: "/a/b/tsconfig.json", - content: "" - }; - const host = createServerHost([f1, f2]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - - // open external project - const projectName = "/a/b/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, f2.path]), - options: {} - }); - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); - - // rename lib.ts to tsconfig.json - host.renameFile(f2.path, tsconfig.path); - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, tsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - if (lazyConfiguredProjectsFromExternalProject) { - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - } - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); - - // rename tsconfig.json back to lib.ts - host.renameFile(tsconfig.path, f2.path); - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, f2.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); - } - it("when lazyConfiguredProjectsFromExternalProject not set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); - }); - it("when lazyConfiguredProjectsFromExternalProject is set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); - }); - }); - - describe("correctly handling add/remove tsconfig - 2", () => { - function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const cLib = { - path: "/a/b/c/lib.ts", - content: "" - }; - const cTsconfig = { - path: "/a/b/c/tsconfig.json", - content: "{}" - }; - const dLib = { - path: "/a/b/d/lib.ts", - content: "" - }; - const dTsconfig = { - path: "/a/b/d/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - - // open external project - const projectName = "/a/b/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - - // add two config file as root files - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 2 }); - if (lazyConfiguredProjectsFromExternalProject) { - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - } - checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); - checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); - - // remove one config file - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, dTsconfig.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]); - - // remove second config file - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - - // open two config files - // add two config file as root files - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 2 }); - if (lazyConfiguredProjectsFromExternalProject) { - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - } - checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); - checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); - - // close all projects - no projects should be opened - projectService.closeExternalProject(projectName); - projectService.checkNumberOfProjects({}); - } - - it("when lazyConfiguredProjectsFromExternalProject not set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); - }); - it("when lazyConfiguredProjectsFromExternalProject is set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); - }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); }); + }); - it("correctly handles changes in lib section of config file", () => { - const libES5 = { - path: "/compiler/lib.es5.d.ts", - content: "declare const eval: any" + describe("correctly handling add/remove tsconfig - 2", () => { + function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1;" }; - const libES2015Promise = { - path: "/compiler/lib.es2015.promise.d.ts", - content: "declare class Promise {}" + const cLib = { + path: "/a/b/c/lib.ts", + content: "" }; - const app = { - path: "/src/app.ts", - content: "var x: Promise;" + const cTsconfig = { + path: "/a/b/c/tsconfig.json", + content: "{}" }; - const config1 = { - path: "/src/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: true, - sourceMap: false, - lib: [ - "es5" - ] - } - }) + const dLib = { + path: "/a/b/d/lib.ts", + content: "" }; - const config2 = { - path: config1.path, - content: JSON.stringify( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: true, - sourceMap: false, - lib: [ - "es5", - "es2015.promise" - ] - } - }) + const dTsconfig = { + path: "/a/b/d/tsconfig.json", + content: "{}" }; - const host = createServerHost([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); + const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]); const projectService = createProjectService(host); - projectService.openClientFile(app.path); + projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, app.path, config1.path]); - - host.writeFile(config2.path, config2.content); - host.checkTimeoutQueueLengthAndRun(2); + // open external project + const projectName = "/a/b/proj1"; + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path]), + options: {} + }); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, libES2015Promise.path, app.path, config2.path]); - }); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - it("should handle non-existing directories in config file", () => { - const f = { - path: "/a/src/app.ts", - content: "let x = 1;" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {}, - include: [ - "src/**/*", - "notexistingfolder/*" - ] - }) - }; - const host = createServerHost([f, config]); - const projectService = createProjectService(host); - projectService.openClientFile(f.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const project = projectService.configuredProjects.get(config.path)!; - assert.isTrue(project.hasOpenRef()); // f + // add two config file as root files + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), + options: {} + }); + projectService.checkNumberOfProjects({ configuredProjects: 2 }); + if (lazyConfiguredProjectsFromExternalProject) { + checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed + checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); - projectService.closeClientFile(f.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config.path), project); - assert.isFalse(project.hasOpenRef()); // No files - assert.isFalse(project.isClosed()); + // remove one config file + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, dTsconfig.path]), + options: {} + }); - projectService.openClientFile(f.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config.path), project); - assert.isTrue(project.hasOpenRef()); // f - assert.isFalse(project.isClosed()); - }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]); - it("handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({}) - }; - const projectFileName = "/a/b/project.csproj"; - const host = createServerHost([f1, config]); - const service = createProjectService(host); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: true } }); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([f1.path, config.path]), + // remove second config file + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path]), options: {} - } as protocol.ExternalProject); - service.checkNumberOfProjects({ configuredProjects: 1 }); - const project = service.configuredProjects.get(config.path)!; - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded - checkProjectActualFiles(project, emptyArray); - - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded - checkProjectActualFiles(project, [config.path, f1.path]); + }); - service.closeExternalProject(projectFileName); - service.checkNumberOfProjects({}); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([f1.path, config.path]), + // open two config files + // add two config file as root files + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), options: {} - } as protocol.ExternalProject); - service.checkNumberOfProjects({ configuredProjects: 1 }); - const project2 = service.configuredProjects.get(config.path)!; - assert.equal(project2.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded - checkProjectActualFiles(project2, [config.path, f1.path]); + }); + projectService.checkNumberOfProjects({ configuredProjects: 2 }); + if (lazyConfiguredProjectsFromExternalProject) { + checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed + checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); + + // close all projects - no projects should be opened + projectService.closeExternalProject(projectName); + projectService.checkNumberOfProjects({}); + } + + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); + }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); }); + }); - it("handles creation of external project with jsconfig before jsconfig creation watcher is invoked", () => { - const projectFileName = `${tscWatch.projectRoot}/WebApplication36.csproj`; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const files = [libFile, tsconfig]; - const host = createServerHost(files); - const service = createProjectService(host); + it("correctly handles changes in lib section of config file", () => { + const libES5 = { + path: "/compiler/lib.es5.d.ts", + content: "declare const eval: any" + }; + const libES2015Promise = { + path: "/compiler/lib.es2015.promise.d.ts", + content: "declare class Promise {}" + }; + const app = { + path: "/src/app.ts", + content: "var x: Promise;" + }; + const config1 = { + path: "/src/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: true, + sourceMap: false, + lib: [ + "es5" + ] + } + }) + }; + const config2 = { + path: config1.path, + content: JSON.stringify( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: true, + sourceMap: false, + lib: [ + "es5", + "es2015.promise" + ] + } + }) + }; + const host = createServerHost([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); + const projectService = createProjectService(host); + projectService.openClientFile(app.path); - // Create external project - service.openExternalProjects([{ - projectFileName, - rootFiles: [{ fileName: tsconfig.path }], - options: { allowJs: false } - }]); - checkNumberOfProjects(service, { configuredProjects: 1 }); - const configProject = service.configuredProjects.get(tsconfig.path.toLowerCase())!; - checkProjectActualFiles(configProject, [tsconfig.path]); - - // write js file, open external project and open it for edit - const jsFilePath = `${tscWatch.projectRoot}/javascript.js`; - host.writeFile(jsFilePath, ""); - service.openExternalProjects([{ - projectFileName, - rootFiles: [{ fileName: tsconfig.path }, { fileName: jsFilePath }], - options: { allowJs: false } - }]); - service.applyChangesInOpenFiles(singleIterator({ fileName: jsFilePath, scriptKind: ScriptKind.JS, content: "" })); - checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 }); - checkProjectActualFiles(configProject, [tsconfig.path]); - const inferredProject = service.inferredProjects[0]; - checkProjectActualFiles(inferredProject, [libFile.path, jsFilePath]); - - // write jsconfig file - const jsConfig: File = { - path: `${tscWatch.projectRoot}/jsconfig.json`, - content: "{}" - }; - // Dont invoke file creation watchers as the repro suggests - host.ensureFileOrFolder(jsConfig, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, app.path, config1.path]); - // Open external project - service.openExternalProjects([{ - projectFileName, - rootFiles: [{ fileName: jsConfig.path }, { fileName: tsconfig.path }, { fileName: jsFilePath }], - options: { allowJs: false } - }]); - checkNumberOfProjects(service, { configuredProjects: 2, inferredProjects: 1 }); - checkProjectActualFiles(configProject, [tsconfig.path]); - assert.isTrue(inferredProject.isOrphan()); - const jsConfigProject = service.configuredProjects.get(jsConfig.path.toLowerCase())!; - checkProjectActualFiles(jsConfigProject, [jsConfig.path, jsFilePath, libFile.path]); - }); + host.writeFile(config2.path, config2.content); + host.checkTimeoutQueueLengthAndRun(2); - it("does not crash if external file does not exist", () => { - const f1 = { - path: "/a/file1.ts", - content: "let x = [1, 2];", - }; - const p1 = { - projectFileName: "/a/proj1.csproj", - rootFiles: [toExternalFile(f1.path)], - options: {}, - }; + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, libES2015Promise.path, app.path, config2.path]); + }); - const host = createServerHost([f1]); - host.require = (_initialPath, moduleName) => { - assert.equal(moduleName, "myplugin"); - return { - module: () => ({ - create(info: server.PluginCreateInfo) { - return Harness.LanguageService.makeDefaultProxy(info); - }, - getExternalFiles() { - return ["/does/not/exist"]; - }, - }), - error: undefined, - }; - }; - const session = createSession(host, { - globalPlugins: ["myplugin"], - }); - const projectService = session.getProjectService(); - // When the external project is opened, the graph will be updated, - // and in the process getExternalFiles() above will be called. - // Since the external file does not exist, there will not be a script - // info for it. If tsserver does not handle this case, the following - // method call will crash. - projectService.openExternalProject(p1); - checkNumberOfProjects(projectService, { externalProjects: 1 }); + it("should handle non-existing directories in config file", () => { + const f = { + path: "/a/src/app.ts", + content: "let x = 1;" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {}, + include: [ + "src/**/*", + "notexistingfolder/*" + ] + }) + }; + const host = createServerHost([f, config]); + const projectService = createProjectService(host); + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const project = projectService.configuredProjects.get(config.path)!; + assert.isTrue(project.hasOpenRef()); // f + + projectService.closeClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.isFalse(project.hasOpenRef()); // No files + assert.isFalse(project.isClosed()); + + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.isTrue(project.hasOpenRef()); // f + assert.isFalse(project.isClosed()); + }); + + it("handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({}) + }; + const projectFileName = "/a/b/project.csproj"; + const host = createServerHost([f1, config]); + const service = createProjectService(host); + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: true } }); + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([f1.path, config.path]), + options: {} + } as protocol.ExternalProject); + service.checkNumberOfProjects({ configuredProjects: 1 }); + const project = service.configuredProjects.get(config.path)!; + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded + checkProjectActualFiles(project, emptyArray); + + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded + checkProjectActualFiles(project, [config.path, f1.path]); + + service.closeExternalProject(projectFileName); + service.checkNumberOfProjects({}); + + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([f1.path, config.path]), + options: {} + } as protocol.ExternalProject); + service.checkNumberOfProjects({ configuredProjects: 1 }); + const project2 = service.configuredProjects.get(config.path)!; + assert.equal(project2.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded + checkProjectActualFiles(project2, [config.path, f1.path]); + }); + + it("handles creation of external project with jsconfig before jsconfig creation watcher is invoked", () => { + const projectFileName = `${tscWatch.projectRoot}/WebApplication36.csproj`; + const tsconfig: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const files = [libFile, tsconfig]; + const host = createServerHost(files); + const service = createProjectService(host); + + // Create external project + service.openExternalProjects([{ + projectFileName, + rootFiles: [{ fileName: tsconfig.path }], + options: { allowJs: false } + }]); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const configProject = service.configuredProjects.get(tsconfig.path.toLowerCase())!; + checkProjectActualFiles(configProject, [tsconfig.path]); + + // write js file, open external project and open it for edit + const jsFilePath = `${tscWatch.projectRoot}/javascript.js`; + host.writeFile(jsFilePath, ""); + service.openExternalProjects([{ + projectFileName, + rootFiles: [{ fileName: tsconfig.path }, { fileName: jsFilePath }], + options: { allowJs: false } + }]); + service.applyChangesInOpenFiles(singleIterator({ fileName: jsFilePath, scriptKind: ScriptKind.JS, content: "" })); + checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 }); + checkProjectActualFiles(configProject, [tsconfig.path]); + const inferredProject = service.inferredProjects[0]; + checkProjectActualFiles(inferredProject, [libFile.path, jsFilePath]); + + // write jsconfig file + const jsConfig: File = { + path: `${tscWatch.projectRoot}/jsconfig.json`, + content: "{}" + }; + // Dont invoke file creation watchers as the repro suggests + host.ensureFileOrFolder(jsConfig, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); + + // Open external project + service.openExternalProjects([{ + projectFileName, + rootFiles: [{ fileName: jsConfig.path }, { fileName: tsconfig.path }, { fileName: jsFilePath }], + options: { allowJs: false } + }]); + checkNumberOfProjects(service, { configuredProjects: 2, inferredProjects: 1 }); + checkProjectActualFiles(configProject, [tsconfig.path]); + assert.isTrue(inferredProject.isOrphan()); + const jsConfigProject = service.configuredProjects.get(jsConfig.path.toLowerCase())!; + checkProjectActualFiles(jsConfigProject, [jsConfig.path, jsFilePath, libFile.path]); + }); + + it("does not crash if external file does not exist", () => { + const f1 = { + path: "/a/file1.ts", + content: "let x = [1, 2];", + }; + const p1 = { + projectFileName: "/a/proj1.csproj", + rootFiles: [toExternalFile(f1.path)], + options: {}, + }; + + const host = createServerHost([f1]); + host.require = (_initialPath, moduleName) => { + assert.equal(moduleName, "myplugin"); + return { + module: () => ({ + create(info: server.PluginCreateInfo) { + return Harness.LanguageService.makeDefaultProxy(info); + }, + getExternalFiles() { + return ["/does/not/exist"]; + }, + }), + error: undefined, + }; + }; + const session = createSession(host, { + globalPlugins: ["myplugin"], }); + const projectService = session.getProjectService(); + // When the external project is opened, the graph will be updated, + // and in the process getExternalFiles() above will be called. + // Since the external file does not exist, there will not be a script + // info for it. If tsserver does not handle this case, the following + // method call will crash. + projectService.openExternalProject(p1); + checkNumberOfProjects(projectService, { externalProjects: 1 }); }); +}); } diff --git a/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts b/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts index 674556e8cf135..6025682391076 100644 --- a/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts +++ b/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts @@ -1,136 +1,136 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => { - it("works when extends is specified with a case insensitive file system", () => { - const rootPath = "/Users/username/dev/project"; - const file1: File = { - path: `${rootPath}/index.ts`, - content: 'import {x} from "file2";', - }; - const file2: File = { - path: `${rootPath}/file2.js`, - content: "", - }; - const file2Dts: File = { - path: `${rootPath}/types/file2/index.d.ts`, - content: "export declare const x: string;", - }; - const tsconfigAll: File = { - path: `${rootPath}/tsconfig.all.json`, - content: JSON.stringify({ - compilerOptions: { - baseUrl: ".", - paths: { file2: ["./file2.js"] }, - typeRoots: ["./types"], - forceConsistentCasingInFileNames: true, - }, - }), - }; - const tsconfig: File = { - path: `${rootPath}/tsconfig.json`, - content: JSON.stringify({ extends: "./tsconfig.all.json" }), - }; +describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => { + it("works when extends is specified with a case insensitive file system", () => { + const rootPath = "/Users/username/dev/project"; + const file1: File = { + path: `${rootPath}/index.ts`, + content: 'import {x} from "file2";', + }; + const file2: File = { + path: `${rootPath}/file2.js`, + content: "", + }; + const file2Dts: File = { + path: `${rootPath}/types/file2/index.d.ts`, + content: "export declare const x: string;", + }; + const tsconfigAll: File = { + path: `${rootPath}/tsconfig.all.json`, + content: JSON.stringify({ + compilerOptions: { + baseUrl: ".", + paths: { file2: ["./file2.js"] }, + typeRoots: ["./types"], + forceConsistentCasingInFileNames: true, + }, + }), + }; + const tsconfig: File = { + path: `${rootPath}/tsconfig.json`, + content: JSON.stringify({ extends: "./tsconfig.all.json" }), + }; - const host = createServerHost([file1, file2, file2Dts, libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false }); - const session = createSession(host); + const host = createServerHost([file1, file2, file2Dts, libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false }); + const session = createSession(host); - openFilesForSession([file1], session); - const projectService = session.getProjectService(); + openFilesForSession([file1], session); + const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); - }); + const diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); + }); - it("works when renaming file with different casing", () => { - const loggerFile: File = { - path: `${tscWatch.projectRoot}/Logger.ts`, - content: `export class logger { }` - }; - const anotherFile: File = { - path: `${tscWatch.projectRoot}/another.ts`, - content: `import { logger } from "./Logger"; new logger();` - }; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { forceConsistentCasingInFileNames: true } - }) - }; + it("works when renaming file with different casing", () => { + const loggerFile: File = { + path: `${tscWatch.projectRoot}/Logger.ts`, + content: `export class logger { }` + }; + const anotherFile: File = { + path: `${tscWatch.projectRoot}/another.ts`, + content: `import { logger } from "./Logger"; new logger();` + }; + const tsconfig: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { forceConsistentCasingInFileNames: true } + }) + }; - const host = createServerHost([loggerFile, anotherFile, tsconfig, libFile, tsconfig]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([{ file: loggerFile, projectRootPath: tscWatch.projectRoot }], session); - verifyGetErrRequest({ session, host, files: [loggerFile] }); + const host = createServerHost([loggerFile, anotherFile, tsconfig, libFile, tsconfig]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([{ file: loggerFile, projectRootPath: tscWatch.projectRoot }], session); + verifyGetErrRequest({ session, host, files: [loggerFile] }); - const newLoggerPath = loggerFile.path.toLowerCase(); - host.renameFile(loggerFile.path, newLoggerPath); - closeFilesForSession([loggerFile], session); - openFilesForSession([{ file: newLoggerPath, content: loggerFile.content, projectRootPath: tscWatch.projectRoot }], session); + const newLoggerPath = loggerFile.path.toLowerCase(); + host.renameFile(loggerFile.path, newLoggerPath); + closeFilesForSession([loggerFile], session); + openFilesForSession([{ file: newLoggerPath, content: loggerFile.content, projectRootPath: tscWatch.projectRoot }], session); - // Apply edits for rename - openFilesForSession([{ file: anotherFile, projectRootPath: tscWatch.projectRoot }], session); - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: anotherFile.path, - textChanges: [{ - newText: "./logger", - ...protocolTextSpanFromSubstring( - anotherFile.content, - "./Logger" - ) - }] + // Apply edits for rename + openFilesForSession([{ file: anotherFile, projectRootPath: tscWatch.projectRoot }], session); + session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: anotherFile.path, + textChanges: [{ + newText: "./logger", + ...protocolTextSpanFromSubstring( + anotherFile.content, + "./Logger" + ) }] - } - }); - - // Check errors in both files - verifyGetErrRequest({ session, host, files: [newLoggerPath, anotherFile] }); - baselineTsserverLogs("forceConsistentCasingInFileNames", "works when renaming file with different casing", session); + }] + } }); - it("when changing module name with different casing", () => { - const loggerFile: File = { - path: `${tscWatch.projectRoot}/Logger.ts`, - content: `export class logger { }` - }; - const anotherFile: File = { - path: `${tscWatch.projectRoot}/another.ts`, - content: `import { logger } from "./Logger"; new logger();` - }; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { forceConsistentCasingInFileNames: true } - }) - }; + // Check errors in both files + verifyGetErrRequest({ session, host, files: [newLoggerPath, anotherFile] }); + baselineTsserverLogs("forceConsistentCasingInFileNames", "works when renaming file with different casing", session); + }); - const host = createServerHost([loggerFile, anotherFile, tsconfig, libFile, tsconfig]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([{ file: anotherFile, projectRootPath: tscWatch.projectRoot }], session); - verifyGetErrRequest({ session, host, files: [anotherFile] }); + it("when changing module name with different casing", () => { + const loggerFile: File = { + path: `${tscWatch.projectRoot}/Logger.ts`, + content: `export class logger { }` + }; + const anotherFile: File = { + path: `${tscWatch.projectRoot}/another.ts`, + content: `import { logger } from "./Logger"; new logger();` + }; + const tsconfig: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { forceConsistentCasingInFileNames: true } + }) + }; - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: anotherFile.path, - textChanges: [{ - newText: "./logger", - ...protocolTextSpanFromSubstring( - anotherFile.content, - "./Logger" - ) - }] - }] - } - }); + const host = createServerHost([loggerFile, anotherFile, tsconfig, libFile, tsconfig]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([{ file: anotherFile, projectRootPath: tscWatch.projectRoot }], session); + verifyGetErrRequest({ session, host, files: [anotherFile] }); - // Check errors in both files - verifyGetErrRequest({ host, session, files: [anotherFile] }); - baselineTsserverLogs("forceConsistentCasingInFileNames", "when changing module name with different casing", session); + session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: anotherFile.path, + textChanges: [{ + newText: "./logger", + ...protocolTextSpanFromSubstring( + anotherFile.content, + "./Logger" + ) + }] + }] + } }); + + // Check errors in both files + verifyGetErrRequest({ host, session, files: [anotherFile] }); + baselineTsserverLogs("forceConsistentCasingInFileNames", "when changing module name with different casing", session); }); +}); } diff --git a/src/testRunner/unittests/tsserver/formatSettings.ts b/src/testRunner/unittests/tsserver/formatSettings.ts index 2d09ed8a8a0eb..4261e7859e5c9 100644 --- a/src/testRunner/unittests/tsserver/formatSettings.ts +++ b/src/testRunner/unittests/tsserver/formatSettings.ts @@ -1,39 +1,39 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: format settings", () => { - it("can be set globally", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x;" - }; - const host = createServerHost([f1]); - const projectService = createProjectService(host); - projectService.openClientFile(f1.path); +describe("unittests:: tsserver:: format settings", () => { + it("can be set globally", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x;" + }; + const host = createServerHost([f1]); + const projectService = createProjectService(host); + projectService.openClientFile(f1.path); - const defaultSettings = projectService.getFormatCodeOptions(f1.path as server.NormalizedPath); + const defaultSettings = projectService.getFormatCodeOptions(f1.path as server.NormalizedPath); - // set global settings - const newGlobalSettings1 = { ...defaultSettings, placeOpenBraceOnNewLineForControlBlocks: !defaultSettings.placeOpenBraceOnNewLineForControlBlocks }; - projectService.setHostConfiguration({ formatOptions: newGlobalSettings1 }); + // set global settings + const newGlobalSettings1 = { ...defaultSettings, placeOpenBraceOnNewLineForControlBlocks: !defaultSettings.placeOpenBraceOnNewLineForControlBlocks }; + projectService.setHostConfiguration({ formatOptions: newGlobalSettings1 }); - // get format options for file - should be equal to new global settings - const s1 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); - assert.deepEqual(s1, newGlobalSettings1, "file settings should be the same with global settings"); + // get format options for file - should be equal to new global settings + const s1 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); + assert.deepEqual(s1, newGlobalSettings1, "file settings should be the same with global settings"); - // set per file format options - const newPerFileSettings = { ...defaultSettings, insertSpaceAfterCommaDelimiter: !defaultSettings.insertSpaceAfterCommaDelimiter }; - projectService.setHostConfiguration({ formatOptions: newPerFileSettings, file: f1.path }); + // set per file format options + const newPerFileSettings = { ...defaultSettings, insertSpaceAfterCommaDelimiter: !defaultSettings.insertSpaceAfterCommaDelimiter }; + projectService.setHostConfiguration({ formatOptions: newPerFileSettings, file: f1.path }); - // get format options for file - should be equal to new per-file settings - const s2 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); - assert.deepEqual(s2, newPerFileSettings, "file settings should be the same with per-file settings"); + // get format options for file - should be equal to new per-file settings + const s2 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); + assert.deepEqual(s2, newPerFileSettings, "file settings should be the same with per-file settings"); - // set new global settings - they should not affect ones that were set per-file - const newGlobalSettings2 = { ...defaultSettings, insertSpaceAfterSemicolonInForStatements: !defaultSettings.insertSpaceAfterSemicolonInForStatements }; - projectService.setHostConfiguration({ formatOptions: newGlobalSettings2 }); + // set new global settings - they should not affect ones that were set per-file + const newGlobalSettings2 = { ...defaultSettings, insertSpaceAfterSemicolonInForStatements: !defaultSettings.insertSpaceAfterSemicolonInForStatements }; + projectService.setHostConfiguration({ formatOptions: newGlobalSettings2 }); - // get format options for file - should be equal to new per-file settings - const s3 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); - assert.deepEqual(s3, newPerFileSettings, "file settings should still be the same with per-file settings"); - }); + // get format options for file - should be equal to new per-file settings + const s3 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); + assert.deepEqual(s3, newPerFileSettings, "file settings should still be the same with per-file settings"); }); +}); } diff --git a/src/testRunner/unittests/tsserver/getApplicableRefactors.ts b/src/testRunner/unittests/tsserver/getApplicableRefactors.ts index bec4a65456051..265618a9a1beb 100644 --- a/src/testRunner/unittests/tsserver/getApplicableRefactors.ts +++ b/src/testRunner/unittests/tsserver/getApplicableRefactors.ts @@ -1,12 +1,12 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: getApplicableRefactors", () => { - it("works when taking position", () => { - const aTs: File = { path: "/a.ts", content: "" }; - const session = createSession(createServerHost([aTs])); - openFilesForSession([aTs], session); - const response = executeSessionRequest( - session, protocol.CommandTypes.GetApplicableRefactors, { file: aTs.path, line: 1, offset: 1 }); - assert.deepEqual(response, []); - }); +describe("unittests:: tsserver:: getApplicableRefactors", () => { + it("works when taking position", () => { + const aTs: File = { path: "/a.ts", content: "" }; + const session = createSession(createServerHost([aTs])); + openFilesForSession([aTs], session); + const response = executeSessionRequest( + session, protocol.CommandTypes.GetApplicableRefactors, { file: aTs.path, line: 1, offset: 1 }); + assert.deepEqual(response, []); }); +}); } diff --git a/src/testRunner/unittests/tsserver/getEditsForFileRename.ts b/src/testRunner/unittests/tsserver/getEditsForFileRename.ts index 5dcd3e96fdb1d..bbf3e9023b041 100644 --- a/src/testRunner/unittests/tsserver/getEditsForFileRename.ts +++ b/src/testRunner/unittests/tsserver/getEditsForFileRename.ts @@ -1,105 +1,105 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: getEditsForFileRename", () => { - it("works for host implementing 'resolveModuleNames' and 'getResolvedModuleWithFailedLookupLocationsFromCache'", () => { - const userTs: File = { - path: "/user.ts", - content: 'import { x } from "./old";', - }; - const newTs: File = { - path: "/new.ts", - content: "export const x = 0;", - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", - }; +describe("unittests:: tsserver:: getEditsForFileRename", () => { + it("works for host implementing 'resolveModuleNames' and 'getResolvedModuleWithFailedLookupLocationsFromCache'", () => { + const userTs: File = { + path: "/user.ts", + content: 'import { x } from "./old";', + }; + const newTs: File = { + path: "/new.ts", + content: "export const x = 0;", + }; + const tsconfig: File = { + path: "/tsconfig.json", + content: "{}", + }; - const host = createServerHost([userTs, newTs, tsconfig]); - const projectService = createProjectService(host); - projectService.openClientFile(userTs.path); - const project = projectService.configuredProjects.get(tsconfig.path)!; + const host = createServerHost([userTs, newTs, tsconfig]); + const projectService = createProjectService(host); + projectService.openClientFile(userTs.path); + const project = projectService.configuredProjects.get(tsconfig.path)!; - Debug.assert(!!project.resolveModuleNames); + Debug.assert(!!project.resolveModuleNames); - const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", testFormatSettings, emptyOptions); - assert.deepEqual(edits, [{ - fileName: "/user.ts", - textChanges: [{ - span: textSpanFromSubstring(userTs.content, "./old"), - newText: "./new", - }], - }]); - }); + const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", testFormatSettings, emptyOptions); + assert.deepEqual(edits, [{ + fileName: "/user.ts", + textChanges: [{ + span: textSpanFromSubstring(userTs.content, "./old"), + newText: "./new", + }], + }]); + }); - it("works with multiple projects", () => { - const aUserTs: File = { - path: "/a/user.ts", - content: 'import { x } from "./old";', - }; - const aOldTs: File = { - path: "/a/old.ts", - content: "export const x = 0;", - }; - const aTsconfig: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ files: ["./old.ts", "./user.ts"] }), - }; - const bUserTs: File = { - path: "/b/user.ts", - content: 'import { x } from "../a/old";', - }; - const bTsconfig: File = { - path: "/b/tsconfig.json", - content: "{}", - }; + it("works with multiple projects", () => { + const aUserTs: File = { + path: "/a/user.ts", + content: 'import { x } from "./old";', + }; + const aOldTs: File = { + path: "/a/old.ts", + content: "export const x = 0;", + }; + const aTsconfig: File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ files: ["./old.ts", "./user.ts"] }), + }; + const bUserTs: File = { + path: "/b/user.ts", + content: 'import { x } from "../a/old";', + }; + const bTsconfig: File = { + path: "/b/tsconfig.json", + content: "{}", + }; - const host = createServerHost([aUserTs, aOldTs, aTsconfig, bUserTs, bTsconfig]); - const session = createSession(host); - openFilesForSession([aUserTs, bUserTs], session); + const host = createServerHost([aUserTs, aOldTs, aTsconfig, bUserTs, bTsconfig]); + const session = createSession(host); + openFilesForSession([aUserTs, bUserTs], session); - const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { - oldFilePath: aOldTs.path, - newFilePath: "/a/new.ts", - }); - assert.deepEqual(response, [ - { - fileName: aTsconfig.path, - textChanges: [{ ...protocolTextSpanFromSubstring(aTsconfig.content, "./old.ts"), newText: "new.ts" }], - }, - { - fileName: aUserTs.path, - textChanges: [{ ...protocolTextSpanFromSubstring(aUserTs.content, "./old"), newText: "./new" }], - }, - { - fileName: bUserTs.path, - textChanges: [{ ...protocolTextSpanFromSubstring(bUserTs.content, "../a/old"), newText: "../a/new" }], - }, - ]); + const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { + oldFilePath: aOldTs.path, + newFilePath: "/a/new.ts", }); + assert.deepEqual(response, [ + { + fileName: aTsconfig.path, + textChanges: [{ ...protocolTextSpanFromSubstring(aTsconfig.content, "./old.ts"), newText: "new.ts" }], + }, + { + fileName: aUserTs.path, + textChanges: [{ ...protocolTextSpanFromSubstring(aUserTs.content, "./old"), newText: "./new" }], + }, + { + fileName: bUserTs.path, + textChanges: [{ ...protocolTextSpanFromSubstring(bUserTs.content, "../a/old"), newText: "../a/new" }], + }, + ]); + }); - it("works with file moved to inferred project", () => { - const aTs: File = { path: "/a.ts", content: 'import {} from "./b";' }; - const cTs: File = { path: "/c.ts", content: "export {};" }; - const tsconfig: File = { path: "/tsconfig.json", content: JSON.stringify({ files: ["./a.ts", "./b.ts"] }) }; + it("works with file moved to inferred project", () => { + const aTs: File = { path: "/a.ts", content: 'import {} from "./b";' }; + const cTs: File = { path: "/c.ts", content: "export {};" }; + const tsconfig: File = { path: "/tsconfig.json", content: JSON.stringify({ files: ["./a.ts", "./b.ts"] }) }; - const host = createServerHost([aTs, cTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs, cTs], session); + const host = createServerHost([aTs, cTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs, cTs], session); - const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { - oldFilePath: "/b.ts", - newFilePath: cTs.path, - }); - assert.deepEqual(response, [ - { - fileName: "/tsconfig.json", - textChanges: [{ ...protocolTextSpanFromSubstring(tsconfig.content, "./b.ts"), newText: "c.ts" }], - }, - { - fileName: "/a.ts", - textChanges: [{ ...protocolTextSpanFromSubstring(aTs.content, "./b"), newText: "./c" }], - }, - ]); + const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { + oldFilePath: "/b.ts", + newFilePath: cTs.path, }); + assert.deepEqual(response, [ + { + fileName: "/tsconfig.json", + textChanges: [{ ...protocolTextSpanFromSubstring(tsconfig.content, "./b.ts"), newText: "c.ts" }], + }, + { + fileName: "/a.ts", + textChanges: [{ ...protocolTextSpanFromSubstring(aTs.content, "./b"), newText: "./c" }], + }, + ]); }); +}); } diff --git a/src/testRunner/unittests/tsserver/getExportReferences.ts b/src/testRunner/unittests/tsserver/getExportReferences.ts index 2312c7b823d07..f3b7d8bfef8af 100644 --- a/src/testRunner/unittests/tsserver/getExportReferences.ts +++ b/src/testRunner/unittests/tsserver/getExportReferences.ts @@ -1,193 +1,193 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: getExportReferences", () => { - const exportVariable = "export const value = 0;"; - const exportArrayDestructured = "export const [valueA, valueB] = [0, 1];"; - const exportObjectDestructured = "export const { valueC, valueD: renamedD } = { valueC: 0, valueD: 1 };"; - const exportNestedObject = "export const { nest: [valueE, { valueF }] } = { nest: [0, { valueF: 1 }] };"; - - const mainTs: File = { - path: "/main.ts", - content: 'import { value, valueA, valueB, valueC, renamedD, valueE, valueF } from "./mod";', - }; - const modTs: File = { - path: "/mod.ts", - content: `${exportVariable} +describe("unittests:: tsserver:: getExportReferences", () => { + const exportVariable = "export const value = 0;"; + const exportArrayDestructured = "export const [valueA, valueB] = [0, 1];"; + const exportObjectDestructured = "export const { valueC, valueD: renamedD } = { valueC: 0, valueD: 1 };"; + const exportNestedObject = "export const { nest: [valueE, { valueF }] } = { nest: [0, { valueF: 1 }] };"; + + const mainTs: File = { + path: "/main.ts", + content: 'import { value, valueA, valueB, valueC, renamedD, valueE, valueF } from "./mod";', + }; + const modTs: File = { + path: "/mod.ts", + content: `${exportVariable} ${exportArrayDestructured} ${exportObjectDestructured} ${exportNestedObject} `, + }; + const tsconfig: File = { + path: "/tsconfig.json", + content: "{}", + }; + + function makeSampleSession() { + const host = createServerHost([mainTs, modTs, tsconfig]); + const session = createSession(host); + openFilesForSession([mainTs, modTs], session); + return session; + } + + const referenceMainTs = (mainTs: File, text: string): protocol.ReferencesResponseItem => + makeReferenceItem({ + file: mainTs, + isDefinition: false, + isWriteAccess: true, + lineText: mainTs.content, + contextText: mainTs.content, + text, + }); + + const referenceModTs = ( + texts: { text: string; lineText: string; contextText?: string }, + override: Partial = {}, + ): protocol.ReferencesResponseItem => + makeReferenceItem({ + file: modTs, + isDefinition: true, + ...texts, + ...override, + }); + + it("should get const variable declaration references", () => { + const session = makeSampleSession(); + + const response = executeSessionRequest( + session, + protocol.CommandTypes.References, + protocolFileLocationFromSubstring(modTs, "value"), + ); + + const expectResponse = { + refs: [ + referenceModTs({ text: "value", lineText: exportVariable, contextText: exportVariable }), + referenceMainTs(mainTs, "value"), + ], + symbolDisplayString: "const value: 0", + symbolName: "value", + symbolStartOffset: protocolLocationFromSubstring(modTs.content, "value").offset, }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", + + assert.deepEqual(response, expectResponse); + }); + + it("should get array destructuring declaration references", () => { + const session = makeSampleSession(); + const response = executeSessionRequest( + session, + protocol.CommandTypes.References, + protocolFileLocationFromSubstring(modTs, "valueA"), + ); + + const expectResponse = { + refs: [ + referenceModTs({ + text: "valueA", + lineText: exportArrayDestructured, + contextText: exportArrayDestructured, + }), + referenceMainTs(mainTs, "valueA"), + ], + symbolDisplayString: "const valueA: number", + symbolName: "valueA", + symbolStartOffset: protocolLocationFromSubstring(modTs.content, "valueA").offset, }; - function makeSampleSession() { - const host = createServerHost([mainTs, modTs, tsconfig]); - const session = createSession(host); - openFilesForSession([mainTs, modTs], session); - return session; - } - - const referenceMainTs = (mainTs: File, text: string): protocol.ReferencesResponseItem => - makeReferenceItem({ - file: mainTs, - isDefinition: false, - isWriteAccess: true, - lineText: mainTs.content, - contextText: mainTs.content, - text, - }); - - const referenceModTs = ( - texts: { text: string; lineText: string; contextText?: string }, - override: Partial = {}, - ): protocol.ReferencesResponseItem => - makeReferenceItem({ - file: modTs, - isDefinition: true, - ...texts, - ...override, - }); - - it("should get const variable declaration references", () => { - const session = makeSampleSession(); - - const response = executeSessionRequest( - session, - protocol.CommandTypes.References, - protocolFileLocationFromSubstring(modTs, "value"), - ); - - const expectResponse = { - refs: [ - referenceModTs({ text: "value", lineText: exportVariable, contextText: exportVariable }), - referenceMainTs(mainTs, "value"), - ], - symbolDisplayString: "const value: 0", - symbolName: "value", - symbolStartOffset: protocolLocationFromSubstring(modTs.content, "value").offset, - }; - - assert.deepEqual(response, expectResponse); - }); + assert.deepEqual(response, expectResponse); + }); - it("should get array destructuring declaration references", () => { - const session = makeSampleSession(); - const response = executeSessionRequest( - session, - protocol.CommandTypes.References, - protocolFileLocationFromSubstring(modTs, "valueA"), - ); - - const expectResponse = { - refs: [ - referenceModTs({ - text: "valueA", - lineText: exportArrayDestructured, - contextText: exportArrayDestructured, + it("should get object destructuring declaration references", () => { + const session = makeSampleSession(); + const response = executeSessionRequest( + session, + protocol.CommandTypes.References, + protocolFileLocationFromSubstring(modTs, "valueC"), + ); + const expectResponse = { + refs: [ + referenceModTs({ + text: "valueC", + lineText: exportObjectDestructured, + contextText: exportObjectDestructured, + }), + referenceMainTs(mainTs, "valueC"), + referenceModTs( + { text: "valueC", lineText: exportObjectDestructured, contextText: "valueC: 0" }, + { + options: { index: 1 }, + isDefinition: false, + isWriteAccess: true, }), - referenceMainTs(mainTs, "valueA"), - ], - symbolDisplayString: "const valueA: number", - symbolName: "valueA", - symbolStartOffset: protocolLocationFromSubstring(modTs.content, "valueA").offset, - }; - - assert.deepEqual(response, expectResponse); - }); + ], + symbolDisplayString: "const valueC: number", + symbolName: "valueC", + symbolStartOffset: protocolLocationFromSubstring(modTs.content, "valueC").offset, + }; - it("should get object destructuring declaration references", () => { - const session = makeSampleSession(); - const response = executeSessionRequest( - session, - protocol.CommandTypes.References, - protocolFileLocationFromSubstring(modTs, "valueC"), - ); - const expectResponse = { - refs: [ - referenceModTs({ - text: "valueC", - lineText: exportObjectDestructured, - contextText: exportObjectDestructured, - }), - referenceMainTs(mainTs, "valueC"), - referenceModTs( - { text: "valueC", lineText: exportObjectDestructured, contextText: "valueC: 0" }, - { - options: { index: 1 }, - isDefinition: false, - isWriteAccess: true, - }), - ], - symbolDisplayString: "const valueC: number", - symbolName: "valueC", - symbolStartOffset: protocolLocationFromSubstring(modTs.content, "valueC").offset, - }; - - assert.deepEqual(response, expectResponse); - }); + assert.deepEqual(response, expectResponse); + }); - it("should get object declaration references that renames destructured property", () => { - const session = makeSampleSession(); - const response = executeSessionRequest( - session, - protocol.CommandTypes.References, - protocolFileLocationFromSubstring(modTs, "renamedD"), - ); - - const expectResponse = { - refs: [ - referenceModTs({ - text: "renamedD", - lineText: exportObjectDestructured, - contextText: exportObjectDestructured, - }), - referenceMainTs(mainTs, "renamedD"), - ], - symbolDisplayString: "const renamedD: number", - symbolName: "renamedD", - symbolStartOffset: protocolLocationFromSubstring(modTs.content, "renamedD").offset, - }; - - assert.deepEqual(response, expectResponse); - }); + it("should get object declaration references that renames destructured property", () => { + const session = makeSampleSession(); + const response = executeSessionRequest( + session, + protocol.CommandTypes.References, + protocolFileLocationFromSubstring(modTs, "renamedD"), + ); + + const expectResponse = { + refs: [ + referenceModTs({ + text: "renamedD", + lineText: exportObjectDestructured, + contextText: exportObjectDestructured, + }), + referenceMainTs(mainTs, "renamedD"), + ], + symbolDisplayString: "const renamedD: number", + symbolName: "renamedD", + symbolStartOffset: protocolLocationFromSubstring(modTs.content, "renamedD").offset, + }; - it("should get nested object declaration references", () => { - const session = makeSampleSession(); - const response = executeSessionRequest( - session, - protocol.CommandTypes.References, - protocolFileLocationFromSubstring(modTs, "valueF"), - ); - - const expectResponse = { - refs: [ - referenceModTs({ + assert.deepEqual(response, expectResponse); + }); + + it("should get nested object declaration references", () => { + const session = makeSampleSession(); + const response = executeSessionRequest( + session, + protocol.CommandTypes.References, + protocolFileLocationFromSubstring(modTs, "valueF"), + ); + + const expectResponse = { + refs: [ + referenceModTs({ + text: "valueF", + lineText: exportNestedObject, + contextText: exportNestedObject, + }), + referenceMainTs(mainTs, "valueF"), + referenceModTs( + { text: "valueF", lineText: exportNestedObject, - contextText: exportNestedObject, - }), - referenceMainTs(mainTs, "valueF"), - referenceModTs( - { - text: "valueF", - lineText: exportNestedObject, - contextText: "valueF: 1", - }, - { - options: { index: 1 }, - isDefinition: false, - isWriteAccess: true, - }, - ), - ], - symbolDisplayString: "const valueF: number", - symbolName: "valueF", - symbolStartOffset: protocolLocationFromSubstring(modTs.content, "valueF").offset, - }; - - assert.deepEqual(response, expectResponse); - }); + contextText: "valueF: 1", + }, + { + options: { index: 1 }, + isDefinition: false, + isWriteAccess: true, + }, + ), + ], + symbolDisplayString: "const valueF: number", + symbolName: "valueF", + symbolStartOffset: protocolLocationFromSubstring(modTs.content, "valueF").offset, + }; + + assert.deepEqual(response, expectResponse); }); +}); } diff --git a/src/testRunner/unittests/tsserver/getFileReferences.ts b/src/testRunner/unittests/tsserver/getFileReferences.ts index f9d049f8ead3f..1da3f6258f43c 100644 --- a/src/testRunner/unittests/tsserver/getFileReferences.ts +++ b/src/testRunner/unittests/tsserver/getFileReferences.ts @@ -1,58 +1,58 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: getFileReferences", () => { - const importA = `import "./a";`; - const importCurlyFromA = `import {} from "./a";`; - const importAFromA = `import { a } from "/project/a";`; - const typeofImportA = `type T = typeof import("./a").a;`; +describe("unittests:: tsserver:: getFileReferences", () => { + const importA = `import "./a";`; + const importCurlyFromA = `import {} from "./a";`; + const importAFromA = `import { a } from "/project/a";`; + const typeofImportA = `type T = typeof import("./a").a;`; - const aTs: File = { - path: "/project/a.ts", - content: "export const a = {};", - }; - const bTs: File = { - path: "/project/b.ts", - content: importA, - }; - const cTs: File = { - path: "/project/c.ts", - content: importCurlyFromA - }; - const dTs: File = { - path: "/project/d.ts", - content: [importAFromA, typeofImportA].join("\n") - }; - const tsconfig: File = { - path: "/project/tsconfig.json", - content: "{}", - }; + const aTs: File = { + path: "/project/a.ts", + content: "export const a = {};", + }; + const bTs: File = { + path: "/project/b.ts", + content: importA, + }; + const cTs: File = { + path: "/project/c.ts", + content: importCurlyFromA + }; + const dTs: File = { + path: "/project/d.ts", + content: [importAFromA, typeofImportA].join("\n") + }; + const tsconfig: File = { + path: "/project/tsconfig.json", + content: "{}", + }; - function makeSampleSession() { - const host = createServerHost([aTs, bTs, cTs, dTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs, bTs, cTs, dTs], session); - return session; - } + function makeSampleSession() { + const host = createServerHost([aTs, bTs, cTs, dTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs, bTs, cTs, dTs], session); + return session; + } - it("should get file references", () => { - const session = makeSampleSession(); + it("should get file references", () => { + const session = makeSampleSession(); - const response = executeSessionRequest( - session, - protocol.CommandTypes.FileReferences, - { file: aTs.path }, - ); + const response = executeSessionRequest( + session, + protocol.CommandTypes.FileReferences, + { file: aTs.path }, + ); - const expectResponse: protocol.FileReferencesResponseBody = { - refs: [ - makeReferenceItem({ file: bTs, text: "./a", lineText: importA, contextText: importA, isDefinition: false, isWriteAccess: false }), - makeReferenceItem({ file: cTs, text: "./a", lineText: importCurlyFromA, contextText: importCurlyFromA, isDefinition: false, isWriteAccess: false }), - makeReferenceItem({ file: dTs, text: "/project/a", lineText: importAFromA, contextText: importAFromA, isDefinition: false, isWriteAccess: false }), - makeReferenceItem({ file: dTs, text: "./a", lineText: typeofImportA, contextText: typeofImportA, isDefinition: false, isWriteAccess: false }), - ], - symbolName: `"${aTs.path}"`, - }; + const expectResponse: protocol.FileReferencesResponseBody = { + refs: [ + makeReferenceItem({ file: bTs, text: "./a", lineText: importA, contextText: importA, isDefinition: false, isWriteAccess: false }), + makeReferenceItem({ file: cTs, text: "./a", lineText: importCurlyFromA, contextText: importCurlyFromA, isDefinition: false, isWriteAccess: false }), + makeReferenceItem({ file: dTs, text: "/project/a", lineText: importAFromA, contextText: importAFromA, isDefinition: false, isWriteAccess: false }), + makeReferenceItem({ file: dTs, text: "./a", lineText: typeofImportA, contextText: typeofImportA, isDefinition: false, isWriteAccess: false }), + ], + symbolName: `"${aTs.path}"`, + }; - assert.deepEqual(response, expectResponse); - }); + assert.deepEqual(response, expectResponse); }); +}); } diff --git a/src/testRunner/unittests/tsserver/helpers.ts b/src/testRunner/unittests/tsserver/helpers.ts index a5327f297c75b..727824b97fa9c 100644 --- a/src/testRunner/unittests/tsserver/helpers.ts +++ b/src/testRunner/unittests/tsserver/helpers.ts @@ -1,34 +1,34 @@ namespace ts.projectSystem { - export import TI = server.typingsInstaller; - export import protocol = server.protocol; - export import CommandNames = server.CommandNames; - - export import TestServerHost = TestFSWithWatch.TestServerHost; - export type File = TestFSWithWatch.File; - export type SymLink = TestFSWithWatch.SymLink; - export type Folder = TestFSWithWatch.Folder; - export import createServerHost = TestFSWithWatch.createServerHost; - export import checkArray = TestFSWithWatch.checkArray; - export import libFile = TestFSWithWatch.libFile; - export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles; - export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed; - export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; - export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; - - export import commonFile1 = tscWatch.commonFile1; - export import commonFile2 = tscWatch.commonFile2; - - const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; - export function mapOutputToJson(s: string) { - return convertToObject( - parseJsonText("json.json", s.replace(outputEventRegex, "")), - [] - ); - } - - export const customTypesMap = { - path: "/typesMap.json" as Path, - content: `{ +export import TI = server.typingsInstaller; +export import protocol = server.protocol; +export import CommandNames = server.CommandNames; + +export import TestServerHost = TestFSWithWatch.TestServerHost; +export type File = TestFSWithWatch.File; +export type SymLink = TestFSWithWatch.SymLink; +export type Folder = TestFSWithWatch.Folder; +export import createServerHost = TestFSWithWatch.createServerHost; +export import checkArray = TestFSWithWatch.checkArray; +export import libFile = TestFSWithWatch.libFile; +export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles; +export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed; +export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; +export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; + +export import commonFile1 = tscWatch.commonFile1; +export import commonFile2 = tscWatch.commonFile2; + +const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; +export function mapOutputToJson(s: string) { + return convertToObject( + parseJsonText("json.json", s.replace(outputEventRegex, "")), + [] + ); +} + +export const customTypesMap = { + path: "/typesMap.json" as Path, + content: `{ "typesMap": { "jquery": { "match": "jquery(-(\\\\.?\\\\d+)+)?(\\\\.intellisense)?(\\\\.min)?\\\\.js$", @@ -48,800 +48,800 @@ namespace ts.projectSystem { "lodash": "lodash" } }` - }; +}; - export interface PostExecAction { - readonly success: boolean; - readonly callback: TI.RequestCompletedAction; - } +export interface PostExecAction { + readonly success: boolean; + readonly callback: TI.RequestCompletedAction; +} - export interface Logger extends server.Logger { - logs: string[]; - } +export interface Logger extends server.Logger { + logs: string[]; +} - export function nullLogger(): Logger { - return { - close: noop, - hasLevel: returnFalse, - loggingEnabled: returnFalse, - perftrc: noop, - info: noop, - msg: noop, - startGroup: noop, - endGroup: noop, - getLogFileName: returnUndefined, - logs: [], - }; - } +export function nullLogger(): Logger { + return { + close: noop, + hasLevel: returnFalse, + loggingEnabled: returnFalse, + perftrc: noop, + info: noop, + msg: noop, + startGroup: noop, + endGroup: noop, + getLogFileName: returnUndefined, + logs: [], + }; +} - export function createHasErrorMessageLogger(): Logger { - return { - ...nullLogger(), - msg: (s, type) => Debug.fail(`Error: ${s}, type: ${type}`), - }; - } +export function createHasErrorMessageLogger(): Logger { + return { + ...nullLogger(), + msg: (s, type) => Debug.fail(`Error: ${s}, type: ${type}`), + }; +} - export function createLoggerWritingToConsole(): Logger { - return { - ...nullLogger(), - hasLevel: returnTrue, - loggingEnabled: returnTrue, - perftrc: s => console.log(s), - info: s => console.log(s), - msg: (s, type) => console.log(`${type}:: ${s}`), - }; - } +export function createLoggerWritingToConsole(): Logger { + return { + ...nullLogger(), + hasLevel: returnTrue, + loggingEnabled: returnTrue, + perftrc: s => console.log(s), + info: s => console.log(s), + msg: (s, type) => console.log(`${type}:: ${s}`), + }; +} - export function createLoggerWithInMemoryLogs(): Logger { - const logger = createHasErrorMessageLogger(); - return { - ...logger, - hasLevel: returnTrue, - loggingEnabled: returnTrue, - info: s => logger.logs.push( - s.replace(/Elapsed::?\s*\d+(?:\.\d+)?ms/g, "Elapsed:: *ms") - .replace(/\"updateGraphDurationMs\"\:\d+(?:\.\d+)?/g, `"updateGraphDurationMs":*`) - .replace(/\"createAutoImportProviderProgramDurationMs\"\:\d+(?:\.\d+)?/g, `"createAutoImportProviderProgramDurationMs":*`) - .replace(`"version":"${version}"`, `"version":"FakeVersion"`) - ) - }; - } +export function createLoggerWithInMemoryLogs(): Logger { + const logger = createHasErrorMessageLogger(); + return { + ...logger, + hasLevel: returnTrue, + loggingEnabled: returnTrue, + info: s => logger.logs.push( + s.replace(/Elapsed::?\s*\d+(?:\.\d+)?ms/g, "Elapsed:: *ms") + .replace(/\"updateGraphDurationMs\"\:\d+(?:\.\d+)?/g, `"updateGraphDurationMs":*`) + .replace(/\"createAutoImportProviderProgramDurationMs\"\:\d+(?:\.\d+)?/g, `"createAutoImportProviderProgramDurationMs":*`) + .replace(`"version":"${version}"`, `"version":"FakeVersion"`) + ) + }; +} - export function baselineTsserverLogs(scenario: string, subScenario: string, sessionOrService: TestSession | TestProjectService) { - Debug.assert(sessionOrService.logger.logs.length); // Ensure caller used in memory logger - Harness.Baseline.runBaseline(`tsserver/${scenario}/${subScenario.split(" ").join("-")}.js`, sessionOrService.logger.logs.join("\r\n")); - } +export function baselineTsserverLogs(scenario: string, subScenario: string, sessionOrService: TestSession | TestProjectService) { + Debug.assert(sessionOrService.logger.logs.length); // Ensure caller used in memory logger + Harness.Baseline.runBaseline(`tsserver/${scenario}/${subScenario.split(" ").join("-")}.js`, sessionOrService.logger.logs.join("\r\n")); +} - export function appendAllScriptInfos(service: server.ProjectService, logs: string[]) { - logs.push(""); - logs.push(`ScriptInfos:`); - service.filenameToScriptInfo.forEach(info => logs.push(`path: ${info.path} fileName: ${info.fileName}`)); - logs.push(""); - } +export function appendAllScriptInfos(service: server.ProjectService, logs: string[]) { + logs.push(""); + logs.push(`ScriptInfos:`); + service.filenameToScriptInfo.forEach(info => logs.push(`path: ${info.path} fileName: ${info.fileName}`)); + logs.push(""); +} - export function appendProjectFileText(project: server.Project, logs: string[]) { - logs.push(""); - logs.push(`Project: ${project.getProjectName()}`); - project.getCurrentProgram()?.getSourceFiles().forEach(f => { - logs.push(JSON.stringify({ fileName: f.fileName, version: f.version })); - logs.push(f.text); - logs.push(""); - }); +export function appendProjectFileText(project: server.Project, logs: string[]) { + logs.push(""); + logs.push(`Project: ${project.getProjectName()}`); + project.getCurrentProgram()?.getSourceFiles().forEach(f => { + logs.push(JSON.stringify({ fileName: f.fileName, version: f.version })); + logs.push(f.text); logs.push(""); - } - - export class TestTypingsInstaller extends TI.TypingsInstaller implements server.ITypingsInstaller { - protected projectService!: server.ProjectService; - constructor( - readonly globalTypingsCacheLocation: string, - throttleLimit: number, - installTypingHost: server.ServerHost, - readonly typesRegistry = new Map>(), - log?: TI.Log) { - super(installTypingHost, globalTypingsCacheLocation, TestFSWithWatch.safeList.path, customTypesMap.path, throttleLimit, log); - } - - protected postExecActions: PostExecAction[] = []; - - isKnownTypesPackageName = notImplemented; - installPackage = notImplemented; - inspectValue = notImplemented; - - executePendingCommands() { - const actionsToRun = this.postExecActions; - this.postExecActions = []; - for (const action of actionsToRun) { - action.callback(action.success); - } - } + }); + logs.push(""); +} - checkPendingCommands(expectedCount: number) { - assert.equal(this.postExecActions.length, expectedCount, `Expected ${expectedCount} post install actions`); - } +export class TestTypingsInstaller extends TI.TypingsInstaller implements server.ITypingsInstaller { + protected projectService!: server.ProjectService; + constructor( + readonly globalTypingsCacheLocation: string, + throttleLimit: number, + installTypingHost: server.ServerHost, + readonly typesRegistry = new Map>(), + log?: TI.Log) { + super(installTypingHost, globalTypingsCacheLocation, TestFSWithWatch.safeList.path, customTypesMap.path, throttleLimit, log); + } - onProjectClosed = noop; + protected postExecActions: PostExecAction[] = []; - attach(projectService: server.ProjectService) { - this.projectService = projectService; - } + isKnownTypesPackageName = notImplemented; + installPackage = notImplemented; + inspectValue = notImplemented; - getInstallTypingHost() { - return this.installTypingHost; - } - - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { - this.addPostExecAction("success", cb); + executePendingCommands() { + const actionsToRun = this.postExecActions; + this.postExecActions = []; + for (const action of actionsToRun) { + action.callback(action.success); } + } - sendResponse(response: server.SetTypings | server.InvalidateCachedTypings) { - this.projectService.updateTypingsForProject(response); - } + checkPendingCommands(expectedCount: number) { + assert.equal(this.postExecActions.length, expectedCount, `Expected ${expectedCount} post install actions`); + } - enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray) { - const request = server.createInstallTypingsRequest(project, typeAcquisition, unresolvedImports, this.globalTypingsCacheLocation); - this.install(request); - } + onProjectClosed = noop; - addPostExecAction(stdout: string | string[], cb: TI.RequestCompletedAction) { - const out = isString(stdout) ? stdout : createNpmPackageJsonString(stdout); - const action: PostExecAction = { - success: !!out, - callback: cb - }; - this.postExecActions.push(action); - } + attach(projectService: server.ProjectService) { + this.projectService = projectService; } - function createNpmPackageJsonString(installedTypings: string[]): string { - const dependencies: MapLike = {}; - for (const typing of installedTypings) { - dependencies[typing] = "1.0.0"; - } - return JSON.stringify({ dependencies }); - } - - export function createTypesRegistry(...list: string[]): ESMap> { - const versionMap = { - "latest": "1.3.0", - "ts2.0": "1.0.0", - "ts2.1": "1.0.0", - "ts2.2": "1.2.0", - "ts2.3": "1.3.0", - "ts2.4": "1.3.0", - "ts2.5": "1.3.0", - "ts2.6": "1.3.0", - "ts2.7": "1.3.0" - }; - const map = new Map>(); - for (const l of list) { - map.set(l, versionMap); - } - return map; + getInstallTypingHost() { + return this.installTypingHost; } - export function toExternalFile(fileName: string): protocol.ExternalFile { - return { fileName }; + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + this.addPostExecAction("success", cb); } - export function toExternalFiles(fileNames: string[]) { - return map(fileNames, toExternalFile); + sendResponse(response: server.SetTypings | server.InvalidateCachedTypings) { + this.projectService.updateTypingsForProject(response); } - export function fileStats(nonZeroStats: Partial): server.FileStats { - return { ts: 0, tsSize: 0, tsx: 0, tsxSize: 0, dts: 0, dtsSize: 0, js: 0, jsSize: 0, jsx: 0, jsxSize: 0, deferred: 0, deferredSize: 0, ...nonZeroStats }; + enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray) { + const request = server.createInstallTypingsRequest(project, typeAcquisition, unresolvedImports, this.globalTypingsCacheLocation); + this.install(request); } - export class TestServerEventManager { - private events: server.ProjectServiceEvent[] = []; - readonly session: TestSession; - readonly service: server.ProjectService; - readonly host: TestServerHost; - constructor(files: File[], suppressDiagnosticEvents?: boolean) { - this.host = createServerHost(files); - this.session = createSession(this.host, { - canUseEvents: true, - eventHandler: event => this.events.push(event), - suppressDiagnosticEvents, - }); - this.service = this.session.getProjectService(); - } - - getEvents(): readonly server.ProjectServiceEvent[] { - const events = this.events; - this.events = []; - return events; - } - - getEvent(eventName: T["eventName"]): T["data"] { - let eventData: T["data"] | undefined; - filterMutate(this.events, e => { - if (e.eventName === eventName) { - if (eventData !== undefined) { - assert(false, "more than one event found"); - } - eventData = e.data; - return false; - } - return true; - }); - return Debug.checkDefined(eventData); - } - - hasZeroEvent(eventName: T["eventName"]) { - this.events.forEach(event => assert.notEqual(event.eventName, eventName)); - } - - assertProjectInfoTelemetryEvent(partial: Partial, configFile = "/tsconfig.json"): void { - assert.deepEqual(this.getEvent(server.ProjectInfoTelemetryEvent), { - projectId: sys.createSHA256Hash!(configFile), - fileStats: fileStats({ ts: 1 }), - compilerOptions: {}, - extends: false, - files: false, - include: false, - exclude: false, - compileOnSave: false, - typeAcquisition: { - enable: false, - exclude: false, - include: false, - }, - configFileName: "tsconfig.json", - projectType: "configured", - languageServiceEnabled: true, - version: ts.version, // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier - ...partial, - }); - } - - assertOpenFileTelemetryEvent(info: server.OpenFileInfo): void { - assert.deepEqual(this.getEvent(server.OpenFileInfoTelemetryEvent), { info }); - } - assertNoOpenFilesTelemetryEvent(): void { - this.hasZeroEvent(server.OpenFileInfoTelemetryEvent); - } + addPostExecAction(stdout: string | string[], cb: TI.RequestCompletedAction) { + const out = isString(stdout) ? stdout : createNpmPackageJsonString(stdout); + const action: PostExecAction = { + success: !!out, + callback: cb + }; + this.postExecActions.push(action); } +} - export interface TestSessionOptions extends server.SessionOptions { - logger: Logger; +function createNpmPackageJsonString(installedTypings: string[]): string { + const dependencies: MapLike = {}; + for (const typing of installedTypings) { + dependencies[typing] = "1.0.0"; } + return JSON.stringify({ dependencies }); +} - export class TestSession extends server.Session { - private seq = 0; - public events: protocol.Event[] = []; - public testhost: TestServerHost = this.host as TestServerHost; - public logger: Logger; - - constructor(opts: TestSessionOptions) { - super(opts); - this.logger = opts.logger; - } - - getProjectService() { - return this.projectService; - } - - public getSeq() { - return this.seq; - } - - public getNextSeq() { - return this.seq + 1; - } +export function createTypesRegistry(...list: string[]): ESMap> { + const versionMap = { + "latest": "1.3.0", + "ts2.0": "1.0.0", + "ts2.1": "1.0.0", + "ts2.2": "1.2.0", + "ts2.3": "1.3.0", + "ts2.4": "1.3.0", + "ts2.5": "1.3.0", + "ts2.6": "1.3.0", + "ts2.7": "1.3.0" + }; + const map = new Map>(); + for (const l of list) { + map.set(l, versionMap); + } + return map; +} - public executeCommand(request: protocol.Request) { - const verboseLogging = this.logger.hasLevel(server.LogLevel.verbose); - if (verboseLogging) this.logger.info(`request:${JSON.stringify(request)}`); - const result = super.executeCommand(request); - if (verboseLogging) this.logger.info(`response:${JSON.stringify(result)}`); - return result; - } +export function toExternalFile(fileName: string): protocol.ExternalFile { + return { fileName }; +} - public executeCommandSeq(request: Partial) { - this.seq++; - request.seq = this.seq; - request.type = "request"; - return this.executeCommand(request as T); - } +export function toExternalFiles(fileNames: string[]) { + return map(fileNames, toExternalFile); +} - public event(body: T, eventName: string) { - this.events.push(server.toEvent(eventName, body)); - super.event(body, eventName); - } +export function fileStats(nonZeroStats: Partial): server.FileStats { + return { ts: 0, tsSize: 0, tsx: 0, tsxSize: 0, dts: 0, dtsSize: 0, js: 0, jsSize: 0, jsx: 0, jsxSize: 0, deferred: 0, deferredSize: 0, ...nonZeroStats }; +} - public clearMessages() { - clear(this.events); - this.testhost.clearOutput(); - } +export class TestServerEventManager { + private events: server.ProjectServiceEvent[] = []; + readonly session: TestSession; + readonly service: server.ProjectService; + readonly host: TestServerHost; + constructor(files: File[], suppressDiagnosticEvents?: boolean) { + this.host = createServerHost(files); + this.session = createSession(this.host, { + canUseEvents: true, + eventHandler: event => this.events.push(event), + suppressDiagnosticEvents, + }); + this.service = this.session.getProjectService(); } - export function createSession(host: server.ServerHost, opts: Partial = {}) { - if (opts.typingsInstaller === undefined) { - opts.typingsInstaller = new TestTypingsInstaller("/a/data/", /*throttleLimit*/ 5, host); - } - - if (opts.eventHandler !== undefined) { - opts.canUseEvents = true; - } - - const sessionOptions: TestSessionOptions = { - host, - cancellationToken: server.nullCancellationToken, - useSingleInferredProject: false, - useInferredProjectPerProjectRoot: false, - typingsInstaller: undefined!, // TODO: GH#18217 - byteLength: Utils.byteLength, - hrtime: process.hrtime, - logger: opts.logger || createHasErrorMessageLogger(), - canUseEvents: false - }; - - return new TestSession({ ...sessionOptions, ...opts }); + getEvents(): readonly server.ProjectServiceEvent[] { + const events = this.events; + this.events = []; + return events; } - export function createSessionWithEventTracking(host: server.ServerHost, eventName: T["eventName"], ...eventNames: T["eventName"][]) { - const events: T[] = []; - const session = createSession(host, { - eventHandler: e => { - if (e.eventName === eventName || eventNames.some(eventName => e.eventName === eventName)) { - events.push(e as T); + getEvent(eventName: T["eventName"]): T["data"] { + let eventData: T["data"] | undefined; + filterMutate(this.events, e => { + if (e.eventName === eventName) { + if (eventData !== undefined) { + assert(false, "more than one event found"); } + eventData = e.data; + return false; } + return true; }); - - return { session, events }; + return Debug.checkDefined(eventData); } - export function createSessionWithDefaultEventHandler(host: TestServerHost, eventNames: T["event"] | T["event"][], opts: Partial = {}) { - const session = createSession(host, { canUseEvents: true, ...opts }); - - return { - session, - getEvents, - clearEvents - }; - - function getEvents() { - return mapDefined(host.getOutput(), s => { - const e = mapOutputToJson(s); - return (isArray(eventNames) ? eventNames.some(eventName => e.event === eventName) : e.event === eventNames) ? e as T : undefined; - }); - } + hasZeroEvent(eventName: T["eventName"]) { + this.events.forEach(event => assert.notEqual(event.eventName, eventName)); + } - function clearEvents() { - session.clearMessages(); - } + assertProjectInfoTelemetryEvent(partial: Partial, configFile = "/tsconfig.json"): void { + assert.deepEqual(this.getEvent(server.ProjectInfoTelemetryEvent), { + projectId: sys.createSHA256Hash!(configFile), + fileStats: fileStats({ ts: 1 }), + compilerOptions: {}, + extends: false, + files: false, + include: false, + exclude: false, + compileOnSave: false, + typeAcquisition: { + enable: false, + exclude: false, + include: false, + }, + configFileName: "tsconfig.json", + projectType: "configured", + languageServiceEnabled: true, + version: ts.version, // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier + ...partial, + }); } - export interface TestProjectServiceOptions extends server.ProjectServiceOptions { - logger: Logger; + assertOpenFileTelemetryEvent(info: server.OpenFileInfo): void { + assert.deepEqual(this.getEvent(server.OpenFileInfoTelemetryEvent), { info }); + } + assertNoOpenFilesTelemetryEvent(): void { + this.hasZeroEvent(server.OpenFileInfoTelemetryEvent); } +} - export class TestProjectService extends server.ProjectService { - constructor(host: server.ServerHost, public logger: Logger, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, - typingsInstaller: server.ITypingsInstaller, opts: Partial = {}) { - super({ - host, - logger, - session: undefined, - cancellationToken, - useSingleInferredProject, - useInferredProjectPerProjectRoot: false, - typingsInstaller, - typesMapLocation: customTypesMap.path, - ...opts - }); - } +export interface TestSessionOptions extends server.SessionOptions { + logger: Logger; +} - checkNumberOfProjects(count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { - checkNumberOfProjects(this, count); - } +export class TestSession extends server.Session { + private seq = 0; + public events: protocol.Event[] = []; + public testhost: TestServerHost = this.host as TestServerHost; + public logger: Logger; + + constructor(opts: TestSessionOptions) { + super(opts); + this.logger = opts.logger; } - export function createProjectService(host: server.ServerHost, options?: Partial) { - const cancellationToken = options?.cancellationToken || server.nullCancellationToken; - const logger = options?.logger || createHasErrorMessageLogger(); - const useSingleInferredProject = options?.useSingleInferredProject !== undefined ? options.useSingleInferredProject : false; - return new TestProjectService(host, logger, cancellationToken, useSingleInferredProject, options?.typingsInstaller || server.nullTypingsInstaller, options); + getProjectService() { + return this.projectService; } - export function checkNumberOfConfiguredProjects(projectService: server.ProjectService, expected: number) { - assert.equal(projectService.configuredProjects.size, expected, `expected ${expected} configured project(s)`); + public getSeq() { + return this.seq; } - export function checkNumberOfExternalProjects(projectService: server.ProjectService, expected: number) { - assert.equal(projectService.externalProjects.length, expected, `expected ${expected} external project(s)`); + public getNextSeq() { + return this.seq + 1; } - export function checkNumberOfInferredProjects(projectService: server.ProjectService, expected: number) { - assert.equal(projectService.inferredProjects.length, expected, `expected ${expected} inferred project(s)`); + public executeCommand(request: protocol.Request) { + const verboseLogging = this.logger.hasLevel(server.LogLevel.verbose); + if (verboseLogging) this.logger.info(`request:${JSON.stringify(request)}`); + const result = super.executeCommand(request); + if (verboseLogging) this.logger.info(`response:${JSON.stringify(result)}`); + return result; } - export function checkNumberOfProjects(projectService: server.ProjectService, count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { - checkNumberOfConfiguredProjects(projectService, count.configuredProjects || 0); - checkNumberOfExternalProjects(projectService, count.externalProjects || 0); - checkNumberOfInferredProjects(projectService, count.inferredProjects || 0); + public executeCommandSeq(request: Partial) { + this.seq++; + request.seq = this.seq; + request.type = "request"; + return this.executeCommand(request as T); } - export function configuredProjectAt(projectService: server.ProjectService, index: number) { - const values = projectService.configuredProjects.values(); - while (index > 0) { - const iterResult = values.next(); - if (iterResult.done) return Debug.fail("Expected a result."); - index--; - } - const iterResult = values.next(); - if (iterResult.done) return Debug.fail("Expected a result."); - return iterResult.value; + public event(body: T, eventName: string) { + this.events.push(server.toEvent(eventName, body)); + super.event(body, eventName); } - export function checkOrphanScriptInfos(service: server.ProjectService, expectedFiles: readonly string[]) { - checkArray("Orphan ScriptInfos:", arrayFrom(mapDefinedIterator( - service.filenameToScriptInfo.values(), - v => v.containingProjects.length === 0 ? v.fileName : undefined - )), expectedFiles); + public clearMessages() { + clear(this.events); + this.testhost.clearOutput(); } +} - export function checkProjectActualFiles(project: server.Project, expectedFiles: readonly string[]) { - checkArray(`${server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}:: actual files`, project.getFileNames(), expectedFiles); +export function createSession(host: server.ServerHost, opts: Partial = {}) { + if (opts.typingsInstaller === undefined) { + opts.typingsInstaller = new TestTypingsInstaller("/a/data/", /*throttleLimit*/ 5, host); } - export function checkProjectRootFiles(project: server.Project, expectedFiles: readonly string[]) { - checkArray(`${server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}::, rootFileNames`, project.getRootFiles(), expectedFiles); + if (opts.eventHandler !== undefined) { + opts.canUseEvents = true; } - export function mapCombinedPathsInAncestor(dir: string, path2: string, mapAncestor: (ancestor: string) => boolean) { - dir = normalizePath(dir); - const result: string[] = []; - forEachAncestorDirectory(dir, ancestor => { - if (mapAncestor(ancestor)) { - result.push(combinePaths(ancestor, path2)); + const sessionOptions: TestSessionOptions = { + host, + cancellationToken: server.nullCancellationToken, + useSingleInferredProject: false, + useInferredProjectPerProjectRoot: false, + typingsInstaller: undefined!, // TODO: GH#18217 + byteLength: Utils.byteLength, + hrtime: process.hrtime, + logger: opts.logger || createHasErrorMessageLogger(), + canUseEvents: false + }; + + return new TestSession({ ...sessionOptions, ...opts }); +} + +export function createSessionWithEventTracking(host: server.ServerHost, eventName: T["eventName"], ...eventNames: T["eventName"][]) { + const events: T[] = []; + const session = createSession(host, { + eventHandler: e => { + if (e.eventName === eventName || eventNames.some(eventName => e.eventName === eventName)) { + events.push(e as T); } + } + }); + + return { session, events }; +} + +export function createSessionWithDefaultEventHandler(host: TestServerHost, eventNames: T["event"] | T["event"][], opts: Partial = {}) { + const session = createSession(host, { canUseEvents: true, ...opts }); + + return { + session, + getEvents, + clearEvents + }; + + function getEvents() { + return mapDefined(host.getOutput(), s => { + const e = mapOutputToJson(s); + return (isArray(eventNames) ? eventNames.some(eventName => e.event === eventName) : e.event === eventNames) ? e as T : undefined; }); - return result; } - export function getRootsToWatchWithAncestorDirectory(dir: string, path2: string) { - return mapCombinedPathsInAncestor(dir, path2, ancestor => ancestor.split(directorySeparator).length > 4); + function clearEvents() { + session.clearMessages(); } +} - export const nodeModules = "node_modules"; - export function getNodeModuleDirectories(dir: string) { - return getRootsToWatchWithAncestorDirectory(dir, nodeModules); - } +export interface TestProjectServiceOptions extends server.ProjectServiceOptions { + logger: Logger; +} - export const nodeModulesAtTypes = "node_modules/@types"; - export function getTypeRootsFromLocation(currentDirectory: string) { - return getRootsToWatchWithAncestorDirectory(currentDirectory, nodeModulesAtTypes); +export class TestProjectService extends server.ProjectService { + constructor(host: server.ServerHost, public logger: Logger, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, + typingsInstaller: server.ITypingsInstaller, opts: Partial = {}) { + super({ + host, + logger, + session: undefined, + cancellationToken, + useSingleInferredProject, + useInferredProjectPerProjectRoot: false, + typingsInstaller, + typesMapLocation: customTypesMap.path, + ...opts + }); } - export function getConfigFilesToWatch(folder: string) { - return [ - ...getRootsToWatchWithAncestorDirectory(folder, "tsconfig.json"), - ...getRootsToWatchWithAncestorDirectory(folder, "jsconfig.json") - ]; + checkNumberOfProjects(count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { + checkNumberOfProjects(this, count); } +} - export function checkOpenFiles(projectService: server.ProjectService, expectedFiles: File[]) { - checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path)); - } +export function createProjectService(host: server.ServerHost, options?: Partial) { + const cancellationToken = options?.cancellationToken || server.nullCancellationToken; + const logger = options?.logger || createHasErrorMessageLogger(); + const useSingleInferredProject = options?.useSingleInferredProject !== undefined ? options.useSingleInferredProject : false; + return new TestProjectService(host, logger, cancellationToken, useSingleInferredProject, options?.typingsInstaller || server.nullTypingsInstaller, options); +} - export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: readonly string[], additionInfo?: string) { - checkArray(`ScriptInfos files: ${additionInfo || ""}`, arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles); - } +export function checkNumberOfConfiguredProjects(projectService: server.ProjectService, expected: number) { + assert.equal(projectService.configuredProjects.size, expected, `expected ${expected} configured project(s)`); +} - export function protocolLocationFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.Location { - const start = nthIndexOf(str, substring, options ? options.index : 0); - Debug.assert(start !== -1); - return protocolToLocation(str)(start); - } +export function checkNumberOfExternalProjects(projectService: server.ProjectService, expected: number) { + assert.equal(projectService.externalProjects.length, expected, `expected ${expected} external project(s)`); +} - export function protocolToLocation(text: string): (pos: number) => protocol.Location { - const lineStarts = computeLineStarts(text); - return pos => { - const x = computeLineAndCharacterOfPosition(lineStarts, pos); - return { line: x.line + 1, offset: x.character + 1 }; - }; - } +export function checkNumberOfInferredProjects(projectService: server.ProjectService, expected: number) { + assert.equal(projectService.inferredProjects.length, expected, `expected ${expected} inferred project(s)`); +} - export function protocolTextSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.TextSpan { - const span = textSpanFromSubstring(str, substring, options); - const toLocation = protocolToLocation(str); - return { start: toLocation(span.start), end: toLocation(textSpanEnd(span)) }; - } - - export interface DocumentSpanFromSubstring { - file: File; - text: string; - options?: SpanFromSubstringOptions; - contextText?: string; - contextOptions?: SpanFromSubstringOptions; - } - export function protocolFileSpanFromSubstring({ file, text, options }: DocumentSpanFromSubstring): protocol.FileSpan { - return { file: file.path, ...protocolTextSpanFromSubstring(file.content, text, options) }; - } - - interface FileSpanWithContextFromSubString { - file: File; - text: string; - options?: SpanFromSubstringOptions; - contextText?: string; - contextOptions?: SpanFromSubstringOptions; - } - export function protocolFileSpanWithContextFromSubstring({ contextText, contextOptions, ...rest }: FileSpanWithContextFromSubString): protocol.FileSpanWithContext { - const result = protocolFileSpanFromSubstring(rest); - const contextSpan = contextText !== undefined ? - protocolFileSpanFromSubstring({ file: rest.file, text: contextText, options: contextOptions }) : - undefined; - return contextSpan ? - { - ...result, - contextStart: contextSpan.start, - contextEnd: contextSpan.end - } : - result; - } - - export interface ProtocolTextSpanWithContextFromString { - fileText: string; - text: string; - options?: SpanFromSubstringOptions; - contextText?: string; - contextOptions?: SpanFromSubstringOptions; - } - export function protocolTextSpanWithContextFromSubstring({ fileText, text, options, contextText, contextOptions }: ProtocolTextSpanWithContextFromString): protocol.TextSpanWithContext { - const span = textSpanFromSubstring(fileText, text, options); - const toLocation = protocolToLocation(fileText); - const contextSpan = contextText !== undefined ? textSpanFromSubstring(fileText, contextText, contextOptions) : undefined; - return { - start: toLocation(span.start), - end: toLocation(textSpanEnd(span)), - ...contextSpan && { - contextStart: toLocation(contextSpan.start), - contextEnd: toLocation(textSpanEnd(contextSpan)) - } - }; - } +export function checkNumberOfProjects(projectService: server.ProjectService, count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { + checkNumberOfConfiguredProjects(projectService, count.configuredProjects || 0); + checkNumberOfExternalProjects(projectService, count.externalProjects || 0); + checkNumberOfInferredProjects(projectService, count.inferredProjects || 0); +} - export interface ProtocolRenameSpanFromSubstring extends ProtocolTextSpanWithContextFromString { - prefixSuffixText?: { - readonly prefixText?: string; - readonly suffixText?: string; - }; - } - export function protocolRenameSpanFromSubstring({ prefixSuffixText, ...rest }: ProtocolRenameSpanFromSubstring): protocol.RenameTextSpan { - return { - ...protocolTextSpanWithContextFromSubstring(rest), - ...prefixSuffixText - }; +export function configuredProjectAt(projectService: server.ProjectService, index: number) { + const values = projectService.configuredProjects.values(); + while (index > 0) { + const iterResult = values.next(); + if (iterResult.done) return Debug.fail("Expected a result."); + index--; } + const iterResult = values.next(); + if (iterResult.done) return Debug.fail("Expected a result."); + return iterResult.value; +} - export function textSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): TextSpan { - const start = nthIndexOf(str, substring, options ? options.index : 0); - Debug.assert(start !== -1); - return createTextSpan(start, substring.length); - } +export function checkOrphanScriptInfos(service: server.ProjectService, expectedFiles: readonly string[]) { + checkArray("Orphan ScriptInfos:", arrayFrom(mapDefinedIterator( + service.filenameToScriptInfo.values(), + v => v.containingProjects.length === 0 ? v.fileName : undefined + )), expectedFiles); +} - export function protocolFileLocationFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileLocationRequestArgs { - return { file: file.path, ...protocolLocationFromSubstring(file.content, substring, options) }; - } +export function checkProjectActualFiles(project: server.Project, expectedFiles: readonly string[]) { + checkArray(`${server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}:: actual files`, project.getFileNames(), expectedFiles); +} - export interface SpanFromSubstringOptions { - readonly index: number; - } +export function checkProjectRootFiles(project: server.Project, expectedFiles: readonly string[]) { + checkArray(`${server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}::, rootFileNames`, project.getRootFiles(), expectedFiles); +} - function nthIndexOf(str: string, substr: string, n: number): number { - let index = -1; - for (; n >= 0; n--) { - index = str.indexOf(substr, index + 1); - if (index === -1) return -1; +export function mapCombinedPathsInAncestor(dir: string, path2: string, mapAncestor: (ancestor: string) => boolean) { + dir = normalizePath(dir); + const result: string[] = []; + forEachAncestorDirectory(dir, ancestor => { + if (mapAncestor(ancestor)) { + result.push(combinePaths(ancestor, path2)); } - return index; - } + }); + return result; +} - /** - * Test server cancellation token used to mock host token cancellation requests. - * The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls - * should be made before canceling the token. The id of the request to cancel should be set with - * setRequestToCancel(); - */ - export class TestServerCancellationToken implements server.ServerCancellationToken { - private currentId: number | undefined = -1; - private requestToCancel = -1; - private isCancellationRequestedCount = 0; +export function getRootsToWatchWithAncestorDirectory(dir: string, path2: string) { + return mapCombinedPathsInAncestor(dir, path2, ancestor => ancestor.split(directorySeparator).length > 4); +} - constructor(private cancelAfterRequest = 0) { - } +export const nodeModules = "node_modules"; +export function getNodeModuleDirectories(dir: string) { + return getRootsToWatchWithAncestorDirectory(dir, nodeModules); +} - setRequest(requestId: number) { - this.currentId = requestId; - } +export const nodeModulesAtTypes = "node_modules/@types"; +export function getTypeRootsFromLocation(currentDirectory: string) { + return getRootsToWatchWithAncestorDirectory(currentDirectory, nodeModulesAtTypes); +} - setRequestToCancel(requestId: number) { - this.resetToken(); - this.requestToCancel = requestId; - } +export function getConfigFilesToWatch(folder: string) { + return [ + ...getRootsToWatchWithAncestorDirectory(folder, "tsconfig.json"), + ...getRootsToWatchWithAncestorDirectory(folder, "jsconfig.json") + ]; +} - resetRequest(requestId: number) { - assert.equal(requestId, this.currentId, "unexpected request id in cancellation"); - this.currentId = undefined; - } +export function checkOpenFiles(projectService: server.ProjectService, expectedFiles: File[]) { + checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path)); +} - isCancellationRequested() { - this.isCancellationRequestedCount++; - // If the request id is the request to cancel and isCancellationRequestedCount - // has been met then cancel the request. Ex: cancel the request if it is a - // nav bar request & isCancellationRequested() has already been called three times. - return this.requestToCancel === this.currentId && this.isCancellationRequestedCount >= this.cancelAfterRequest; - } +export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: readonly string[], additionInfo?: string) { + checkArray(`ScriptInfos files: ${additionInfo || ""}`, arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles); +} + +export function protocolLocationFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.Location { + const start = nthIndexOf(str, substring, options ? options.index : 0); + Debug.assert(start !== -1); + return protocolToLocation(str)(start); +} + +export function protocolToLocation(text: string): (pos: number) => protocol.Location { + const lineStarts = computeLineStarts(text); + return pos => { + const x = computeLineAndCharacterOfPosition(lineStarts, pos); + return { line: x.line + 1, offset: x.character + 1 }; + }; +} + +export function protocolTextSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.TextSpan { + const span = textSpanFromSubstring(str, substring, options); + const toLocation = protocolToLocation(str); + return { start: toLocation(span.start), end: toLocation(textSpanEnd(span)) }; +} + +export interface DocumentSpanFromSubstring { + file: File; + text: string; + options?: SpanFromSubstringOptions; + contextText?: string; + contextOptions?: SpanFromSubstringOptions; +} +export function protocolFileSpanFromSubstring({ file, text, options }: DocumentSpanFromSubstring): protocol.FileSpan { + return { file: file.path, ...protocolTextSpanFromSubstring(file.content, text, options) }; +} + +interface FileSpanWithContextFromSubString { + file: File; + text: string; + options?: SpanFromSubstringOptions; + contextText?: string; + contextOptions?: SpanFromSubstringOptions; +} +export function protocolFileSpanWithContextFromSubstring({ contextText, contextOptions, ...rest }: FileSpanWithContextFromSubString): protocol.FileSpanWithContext { + const result = protocolFileSpanFromSubstring(rest); + const contextSpan = contextText !== undefined ? + protocolFileSpanFromSubstring({ file: rest.file, text: contextText, options: contextOptions }) : + undefined; + return contextSpan ? + { + ...result, + contextStart: contextSpan.start, + contextEnd: contextSpan.end + } : + result; +} - resetToken() { - this.currentId = -1; - this.isCancellationRequestedCount = 0; - this.requestToCancel = -1; +export interface ProtocolTextSpanWithContextFromString { + fileText: string; + text: string; + options?: SpanFromSubstringOptions; + contextText?: string; + contextOptions?: SpanFromSubstringOptions; +} +export function protocolTextSpanWithContextFromSubstring({ fileText, text, options, contextText, contextOptions }: ProtocolTextSpanWithContextFromString): protocol.TextSpanWithContext { + const span = textSpanFromSubstring(fileText, text, options); + const toLocation = protocolToLocation(fileText); + const contextSpan = contextText !== undefined ? textSpanFromSubstring(fileText, contextText, contextOptions) : undefined; + return { + start: toLocation(span.start), + end: toLocation(textSpanEnd(span)), + ...contextSpan && { + contextStart: toLocation(contextSpan.start), + contextEnd: toLocation(textSpanEnd(contextSpan)) } - } + }; +} - export function makeSessionRequest(command: string, args: T): protocol.Request { - return { - seq: 0, - type: "request", - command, - arguments: args - }; - } +export interface ProtocolRenameSpanFromSubstring extends ProtocolTextSpanWithContextFromString { + prefixSuffixText?: { + readonly prefixText?: string; + readonly suffixText?: string; + }; +} +export function protocolRenameSpanFromSubstring({ prefixSuffixText, ...rest }: ProtocolRenameSpanFromSubstring): protocol.RenameTextSpan { + return { + ...protocolTextSpanWithContextFromSubstring(rest), + ...prefixSuffixText + }; +} - export function executeSessionRequest(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): TResponse["body"] { - return session.executeCommand(makeSessionRequest(command, args)).response as TResponse["body"]; - } +export function textSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): TextSpan { + const start = nthIndexOf(str, substring, options ? options.index : 0); + Debug.assert(start !== -1); + return createTextSpan(start, substring.length); +} - export function executeSessionRequestNoResponse(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): void { - session.executeCommand(makeSessionRequest(command, args)); +export function protocolFileLocationFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileLocationRequestArgs { + return { file: file.path, ...protocolLocationFromSubstring(file.content, substring, options) }; +} + +export interface SpanFromSubstringOptions { + readonly index: number; +} + +function nthIndexOf(str: string, substr: string, n: number): number { + let index = -1; + for (; n >= 0; n--) { + index = str.indexOf(substr, index + 1); + if (index === -1) return -1; } + return index; +} - export function openFilesForSession(files: readonly (File | { readonly file: File | string, readonly projectRootPath: string, content?: string })[], session: server.Session): void { - for (const file of files) { - session.executeCommand(makeSessionRequest(CommandNames.Open, - "projectRootPath" in file ? { file: typeof file.file === "string" ? file.file : file.file.path, projectRootPath: file.projectRootPath } : { file: file.path })); // eslint-disable-line no-in-operator - } +/** + * Test server cancellation token used to mock host token cancellation requests. + * The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls + * should be made before canceling the token. The id of the request to cancel should be set with + * setRequestToCancel(); + */ +export class TestServerCancellationToken implements server.ServerCancellationToken { + private currentId: number | undefined = -1; + private requestToCancel = -1; + private isCancellationRequestedCount = 0; + + constructor(private cancelAfterRequest = 0) { } - export function closeFilesForSession(files: readonly File[], session: server.Session): void { - for (const file of files) { - session.executeCommand(makeSessionRequest(CommandNames.Close, { file: file.path })); - } + setRequest(requestId: number) { + this.currentId = requestId; } - export interface MakeReferenceItem extends DocumentSpanFromSubstring { - isDefinition: boolean; - isWriteAccess?: boolean; - lineText: string; + setRequestToCancel(requestId: number) { + this.resetToken(); + this.requestToCancel = requestId; } - export function makeReferenceItem({ isDefinition, isWriteAccess, lineText, ...rest }: MakeReferenceItem): protocol.ReferencesResponseItem { - return { - ...protocolFileSpanWithContextFromSubstring(rest), - isDefinition, - isWriteAccess: isWriteAccess === undefined ? isDefinition : isWriteAccess, - lineText, - }; + resetRequest(requestId: number) { + assert.equal(requestId, this.currentId, "unexpected request id in cancellation"); + this.currentId = undefined; } - export interface VerifyGetErrRequestBase { - session: TestSession; - host: TestServerHost; - existingTimeouts?: number; + isCancellationRequested() { + this.isCancellationRequestedCount++; + // If the request id is the request to cancel and isCancellationRequestedCount + // has been met then cancel the request. Ex: cancel the request if it is a + // nav bar request & isCancellationRequested() has already been called three times. + return this.requestToCancel === this.currentId && this.isCancellationRequestedCount >= this.cancelAfterRequest; } - export interface VerifyGetErrRequest extends VerifyGetErrRequestBase { - files: readonly (string | File)[]; - skip?: CheckAllErrors["skip"]; + + resetToken() { + this.currentId = -1; + this.isCancellationRequestedCount = 0; + this.requestToCancel = -1; } - export function verifyGetErrRequest(request: VerifyGetErrRequest) { - const { session, files } = request; - session.executeCommandSeq({ - command: protocol.CommandTypes.Geterr, - arguments: { delay: 0, files: files.map(filePath) } - }); - checkAllErrors(request); +} + +export function makeSessionRequest(command: string, args: T): protocol.Request { + return { + seq: 0, + type: "request", + command, + arguments: args + }; +} + +export function executeSessionRequest(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): TResponse["body"] { + return session.executeCommand(makeSessionRequest(command, args)).response as TResponse["body"]; +} + +export function executeSessionRequestNoResponse(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): void { + session.executeCommand(makeSessionRequest(command, args)); +} + +export function openFilesForSession(files: readonly (File | { readonly file: File | string, readonly projectRootPath: string, content?: string })[], session: server.Session): void { + for (const file of files) { + session.executeCommand(makeSessionRequest(CommandNames.Open, + "projectRootPath" in file ? { file: typeof file.file === "string" ? file.file : file.file.path, projectRootPath: file.projectRootPath } : { file: file.path })); // eslint-disable-line no-in-operator } +} - interface SkipErrors { semantic?: true; suggestion?: true }; - export interface CheckAllErrors extends VerifyGetErrRequestBase { - files: readonly any[]; - skip?: readonly (SkipErrors | undefined)[]; +export function closeFilesForSession(files: readonly File[], session: server.Session): void { + for (const file of files) { + session.executeCommand(makeSessionRequest(CommandNames.Close, { file: file.path })); } - function checkAllErrors({ session, host, existingTimeouts, files, skip }: CheckAllErrors) { - Debug.assert(session.logger.logs.length); - for (let i = 0; i < files.length; i++) { - if (existingTimeouts !== undefined) { - host.checkTimeoutQueueLength(existingTimeouts + 1); - host.runQueuedTimeoutCallbacks(host.getNextTimeoutId() - 1); - } - else { - host.checkTimeoutQueueLengthAndRun(1); - } - if (!skip?.[i]?.semantic) host.runQueuedImmediateCallbacks(1); - if (!skip?.[i]?.suggestion) host.runQueuedImmediateCallbacks(1); +} + +export interface MakeReferenceItem extends DocumentSpanFromSubstring { + isDefinition: boolean; + isWriteAccess?: boolean; + lineText: string; +} + +export function makeReferenceItem({ isDefinition, isWriteAccess, lineText, ...rest }: MakeReferenceItem): protocol.ReferencesResponseItem { + return { + ...protocolFileSpanWithContextFromSubstring(rest), + isDefinition, + isWriteAccess: isWriteAccess === undefined ? isDefinition : isWriteAccess, + lineText, + }; +} + +export interface VerifyGetErrRequestBase { + session: TestSession; + host: TestServerHost; + existingTimeouts?: number; +} +export interface VerifyGetErrRequest extends VerifyGetErrRequestBase { + files: readonly (string | File)[]; + skip?: CheckAllErrors["skip"]; +} +export function verifyGetErrRequest(request: VerifyGetErrRequest) { + const { session, files } = request; + session.executeCommandSeq({ + command: protocol.CommandTypes.Geterr, + arguments: { delay: 0, files: files.map(filePath) } + }); + checkAllErrors(request); +} + +interface SkipErrors { semantic?: true; suggestion?: true }; +export interface CheckAllErrors extends VerifyGetErrRequestBase { + files: readonly any[]; + skip?: readonly (SkipErrors | undefined)[]; +} +function checkAllErrors({ session, host, existingTimeouts, files, skip }: CheckAllErrors) { + Debug.assert(session.logger.logs.length); + for (let i = 0; i < files.length; i++) { + if (existingTimeouts !== undefined) { + host.checkTimeoutQueueLength(existingTimeouts + 1); + host.runQueuedTimeoutCallbacks(host.getNextTimeoutId() - 1); } + else { + host.checkTimeoutQueueLengthAndRun(1); + } + if (!skip?.[i]?.semantic) host.runQueuedImmediateCallbacks(1); + if (!skip?.[i]?.suggestion) host.runQueuedImmediateCallbacks(1); } +} - function filePath(file: string | File) { - return isString(file) ? file : file.path; - } +function filePath(file: string | File) { + return isString(file) ? file : file.path; +} - function verifyErrorsUsingGeterr({scenario, subScenario, allFiles, openFiles, getErrRequest }: VerifyGetErrScenario) { - it("verifies the errors in open file", () => { - const host = createServerHost([...allFiles(), libFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession(openFiles(), session); +function verifyErrorsUsingGeterr({scenario, subScenario, allFiles, openFiles, getErrRequest }: VerifyGetErrScenario) { + it("verifies the errors in open file", () => { + const host = createServerHost([...allFiles(), libFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession(openFiles(), session); - verifyGetErrRequest({ session, host, files: getErrRequest() }); - baselineTsserverLogs(scenario, `${subScenario} getErr`, session); - }); - } + verifyGetErrRequest({ session, host, files: getErrRequest() }); + baselineTsserverLogs(scenario, `${subScenario} getErr`, session); + }); +} - function verifyErrorsUsingGeterrForProject({ scenario, subScenario, allFiles, openFiles, getErrForProjectRequest }: VerifyGetErrScenario) { - it("verifies the errors in projects", () => { - const host = createServerHost([...allFiles(), libFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession(openFiles(), session); +function verifyErrorsUsingGeterrForProject({ scenario, subScenario, allFiles, openFiles, getErrForProjectRequest }: VerifyGetErrScenario) { + it("verifies the errors in projects", () => { + const host = createServerHost([...allFiles(), libFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession(openFiles(), session); - for (const expected of getErrForProjectRequest()) { - session.executeCommandSeq({ - command: protocol.CommandTypes.GeterrForProject, - arguments: { delay: 0, file: filePath(expected.project) } - }); - checkAllErrors({ session, host, files: expected.files }); - } - baselineTsserverLogs(scenario, `${subScenario} geterrForProject`, session); - }); - } + for (const expected of getErrForProjectRequest()) { + session.executeCommandSeq({ + command: protocol.CommandTypes.GeterrForProject, + arguments: { delay: 0, file: filePath(expected.project) } + }); + checkAllErrors({ session, host, files: expected.files }); + } + baselineTsserverLogs(scenario, `${subScenario} geterrForProject`, session); + }); +} - function verifyErrorsUsingSyncMethods({ scenario, subScenario, allFiles, openFiles, syncDiagnostics }: VerifyGetErrScenario) { - it("verifies the errors using sync commands", () => { - const host = createServerHost([...allFiles(), libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession(openFiles(), session); - for (const { file, project } of syncDiagnostics()) { - const reqArgs = { file: filePath(file), projectFileName: project && filePath(project) }; - session.executeCommandSeq({ - command: protocol.CommandTypes.SyntacticDiagnosticsSync, - arguments: reqArgs - }); - session.executeCommandSeq({ - command: protocol.CommandTypes.SemanticDiagnosticsSync, - arguments: reqArgs - }); - session.executeCommandSeq({ - command: protocol.CommandTypes.SuggestionDiagnosticsSync, - arguments: reqArgs - }); - } - baselineTsserverLogs(scenario, `${subScenario} gerErr with sync commands`, session); - }); - } +function verifyErrorsUsingSyncMethods({ scenario, subScenario, allFiles, openFiles, syncDiagnostics }: VerifyGetErrScenario) { + it("verifies the errors using sync commands", () => { + const host = createServerHost([...allFiles(), libFile]); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession(openFiles(), session); + for (const { file, project } of syncDiagnostics()) { + const reqArgs = { file: filePath(file), projectFileName: project && filePath(project) }; + session.executeCommandSeq({ + command: protocol.CommandTypes.SyntacticDiagnosticsSync, + arguments: reqArgs + }); + session.executeCommandSeq({ + command: protocol.CommandTypes.SemanticDiagnosticsSync, + arguments: reqArgs + }); + session.executeCommandSeq({ + command: protocol.CommandTypes.SuggestionDiagnosticsSync, + arguments: reqArgs + }); + } + baselineTsserverLogs(scenario, `${subScenario} gerErr with sync commands`, session); + }); +} - export interface GetErrForProjectDiagnostics { - project: string | File; - files: readonly (string | File)[]; - skip?: CheckAllErrors["skip"]; - } - export interface SyncDiagnostics { - file: string | File; - project?: string | File; - } - export interface VerifyGetErrScenario { - scenario: string; - subScenario: string; - allFiles: () => readonly File[]; - openFiles: () => readonly File[]; - getErrRequest: () => VerifyGetErrRequest["files"]; - getErrForProjectRequest: () => readonly GetErrForProjectDiagnostics[]; - syncDiagnostics: () => readonly SyncDiagnostics[]; - } - export function verifyGetErrScenario(scenario: VerifyGetErrScenario) { - verifyErrorsUsingGeterr(scenario); - verifyErrorsUsingGeterrForProject(scenario); - verifyErrorsUsingSyncMethods(scenario); - } +export interface GetErrForProjectDiagnostics { + project: string | File; + files: readonly (string | File)[]; + skip?: CheckAllErrors["skip"]; +} +export interface SyncDiagnostics { + file: string | File; + project?: string | File; +} +export interface VerifyGetErrScenario { + scenario: string; + subScenario: string; + allFiles: () => readonly File[]; + openFiles: () => readonly File[]; + getErrRequest: () => VerifyGetErrRequest["files"]; + getErrForProjectRequest: () => readonly GetErrForProjectDiagnostics[]; + syncDiagnostics: () => readonly SyncDiagnostics[]; +} +export function verifyGetErrScenario(scenario: VerifyGetErrScenario) { + verifyErrorsUsingGeterr(scenario); + verifyErrorsUsingGeterrForProject(scenario); + verifyErrorsUsingSyncMethods(scenario); +} } diff --git a/src/testRunner/unittests/tsserver/importHelpers.ts b/src/testRunner/unittests/tsserver/importHelpers.ts index b1ec5955eaae4..3edcb87884ca6 100644 --- a/src/testRunner/unittests/tsserver/importHelpers.ts +++ b/src/testRunner/unittests/tsserver/importHelpers.ts @@ -1,18 +1,18 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: import helpers", () => { - it("should not crash in tsserver", () => { - const f1 = { - path: "/a/app.ts", - content: "export async function foo() { return 100; }" - }; - const tslib = { - path: "/a/node_modules/tslib/index.d.ts", - content: "" - }; - const host = createServerHost([f1, tslib]); - const service = createProjectService(host); - service.openExternalProject({ projectFileName: "p", rootFiles: [toExternalFile(f1.path)], options: { importHelpers: true } }); - service.checkNumberOfProjects({ externalProjects: 1 }); - }); +describe("unittests:: tsserver:: import helpers", () => { + it("should not crash in tsserver", () => { + const f1 = { + path: "/a/app.ts", + content: "export async function foo() { return 100; }" + }; + const tslib = { + path: "/a/node_modules/tslib/index.d.ts", + content: "" + }; + const host = createServerHost([f1, tslib]); + const service = createProjectService(host); + service.openExternalProject({ projectFileName: "p", rootFiles: [toExternalFile(f1.path)], options: { importHelpers: true } }); + service.checkNumberOfProjects({ externalProjects: 1 }); }); +}); } diff --git a/src/testRunner/unittests/tsserver/inferredProjects.ts b/src/testRunner/unittests/tsserver/inferredProjects.ts index be7238323fe58..02f45cb1dee36 100644 --- a/src/testRunner/unittests/tsserver/inferredProjects.ts +++ b/src/testRunner/unittests/tsserver/inferredProjects.ts @@ -1,464 +1,464 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: Inferred projects", () => { - it("create inferred project", () => { - const appFile: File = { - path: `${tscWatch.projectRoot}/app.ts`, - content: ` +describe("unittests:: tsserver:: Inferred projects", () => { + it("create inferred project", () => { + const appFile: File = { + path: `${tscWatch.projectRoot}/app.ts`, + content: ` import {f} from "./module" console.log(f) ` - }; - - const moduleFile: File = { - path: `${tscWatch.projectRoot}/module.d.ts`, - content: `export let x: number` - }; - const host = createServerHost([appFile, moduleFile, libFile]); - const projectService = createProjectService(host); - const { configFileName } = projectService.openClientFile(appFile.path); - - assert(!configFileName, `should not find config, got: '${configFileName}`); - checkNumberOfConfiguredProjects(projectService, 0); - checkNumberOfInferredProjects(projectService, 1); - - const project = projectService.inferredProjects[0]; - - checkArray("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); - checkWatchedFiles(host, getConfigFilesToWatch(tscWatch.projectRoot).concat(libFile.path, moduleFile.path)); - checkWatchedDirectories(host, [tscWatch.projectRoot], /*recursive*/ false); - checkWatchedDirectories(host, [combinePaths(tscWatch.projectRoot, nodeModulesAtTypes)], /*recursive*/ true); - }); + }; + + const moduleFile: File = { + path: `${tscWatch.projectRoot}/module.d.ts`, + content: `export let x: number` + }; + const host = createServerHost([appFile, moduleFile, libFile]); + const projectService = createProjectService(host); + const { configFileName } = projectService.openClientFile(appFile.path); + + assert(!configFileName, `should not find config, got: '${configFileName}`); + checkNumberOfConfiguredProjects(projectService, 0); + checkNumberOfInferredProjects(projectService, 1); + + const project = projectService.inferredProjects[0]; + + checkArray("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); + checkWatchedFiles(host, getConfigFilesToWatch(tscWatch.projectRoot).concat(libFile.path, moduleFile.path)); + checkWatchedDirectories(host, [tscWatch.projectRoot], /*recursive*/ false); + checkWatchedDirectories(host, [combinePaths(tscWatch.projectRoot, nodeModulesAtTypes)], /*recursive*/ true); + }); - it("should use only one inferred project if 'useOneInferredProject' is set", () => { - const file1 = { - path: `${tscWatch.projectRoot}/a/b/main.ts`, - content: "let x =1;" - }; - const configFile: File = { - path: `${tscWatch.projectRoot}/a/b/tsconfig.json`, - content: `{ + it("should use only one inferred project if 'useOneInferredProject' is set", () => { + const file1 = { + path: `${tscWatch.projectRoot}/a/b/main.ts`, + content: "let x =1;" + }; + const configFile: File = { + path: `${tscWatch.projectRoot}/a/b/tsconfig.json`, + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts" ] }` - }; - const file2 = { - path: `${tscWatch.projectRoot}/a/c/main.ts`, - content: "let x =1;" - }; - - const file3 = { - path: `${tscWatch.projectRoot}/a/d/main.ts`, - content: "let x =1;" - }; - - const host = createServerHost([file1, file2, file3, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true }); - projectService.openClientFile(file1.path); - projectService.openClientFile(file2.path); - projectService.openClientFile(file3.path); - - checkNumberOfConfiguredProjects(projectService, 0); - checkNumberOfInferredProjects(projectService, 1); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path, libFile.path]); - - - host.writeFile(configFile.path, configFile.content); - host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles - checkNumberOfConfiguredProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 1); - checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]); - }); + }; + const file2 = { + path: `${tscWatch.projectRoot}/a/c/main.ts`, + content: "let x =1;" + }; + + const file3 = { + path: `${tscWatch.projectRoot}/a/d/main.ts`, + content: "let x =1;" + }; + + const host = createServerHost([file1, file2, file3, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: true }); + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + projectService.openClientFile(file3.path); + + checkNumberOfConfiguredProjects(projectService, 0); + checkNumberOfInferredProjects(projectService, 1); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path, libFile.path]); + + + host.writeFile(configFile.path, configFile.content); + host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles + checkNumberOfConfiguredProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 1); + checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]); + }); - it("disable inferred project", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; + it("disable inferred project", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; - const host = createServerHost([file1]); - const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); + const host = createServerHost([file1]); + const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); - projectService.openClientFile(file1.path, file1.content); + projectService.openClientFile(file1.path, file1.content); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const proj = projectService.inferredProjects[0]; - assert.isDefined(proj); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + assert.isDefined(proj); - assert.isFalse(proj.languageServiceEnabled); - }); + assert.isFalse(proj.languageServiceEnabled); + }); - it("project settings for inferred projects", () => { - const file1 = { - path: "/a/b/app.ts", - content: `import {x} from "mod"` - }; - const modFile = { - path: "/a/mod.ts", - content: "export let x: number" - }; - const host = createServerHost([file1, modFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - projectService.openClientFile(modFile.path); - - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - const inferredProjects = projectService.inferredProjects.slice(); - checkProjectActualFiles(inferredProjects[0], [file1.path]); - checkProjectActualFiles(inferredProjects[1], [modFile.path]); - - projectService.setCompilerOptionsForInferredProjects({ moduleResolution: ModuleResolutionKind.Classic }); - host.checkTimeoutQueueLengthAndRun(3); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); - assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); - checkProjectActualFiles(inferredProjects[0], [file1.path, modFile.path]); - assert.isTrue(inferredProjects[1].isOrphan()); - }); + it("project settings for inferred projects", () => { + const file1 = { + path: "/a/b/app.ts", + content: `import {x} from "mod"` + }; + const modFile = { + path: "/a/mod.ts", + content: "export let x: number" + }; + const host = createServerHost([file1, modFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.openClientFile(modFile.path); + + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + const inferredProjects = projectService.inferredProjects.slice(); + checkProjectActualFiles(inferredProjects[0], [file1.path]); + checkProjectActualFiles(inferredProjects[1], [modFile.path]); + + projectService.setCompilerOptionsForInferredProjects({ moduleResolution: ModuleResolutionKind.Classic }); + host.checkTimeoutQueueLengthAndRun(3); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); + assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); + checkProjectActualFiles(inferredProjects[0], [file1.path, modFile.path]); + assert.isTrue(inferredProjects[1].isOrphan()); + }); - it("should support files without extensions", () => { - const f = { - path: "/a/compile", - content: "let x = 1" - }; - const host = createServerHost([f]); - const session = createSession(host); - session.executeCommand({ - seq: 1, - type: "request", - command: "compilerOptionsForInferredProjects", - arguments: { - options: { - allowJs: true - } - } - } as server.protocol.SetCompilerOptionsForInferredProjectsRequest); - session.executeCommand({ - seq: 2, - type: "request", - command: "open", - arguments: { - file: f.path, - fileContent: f.content, - scriptKindName: "JS" + it("should support files without extensions", () => { + const f = { + path: "/a/compile", + content: "let x = 1" + }; + const host = createServerHost([f]); + const session = createSession(host); + session.executeCommand({ + seq: 1, + type: "request", + command: "compilerOptionsForInferredProjects", + arguments: { + options: { + allowJs: true } - } as server.protocol.OpenRequest); - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [f.path]); - }); + } + } as server.protocol.SetCompilerOptionsForInferredProjectsRequest); + session.executeCommand({ + seq: 2, + type: "request", + command: "open", + arguments: { + file: f.path, + fileContent: f.content, + scriptKindName: "JS" + } + } as server.protocol.OpenRequest); + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [f.path]); + }); - it("inferred projects per project root", () => { - const file1 = { path: "/a/file1.ts", content: "let x = 1;", projectRootPath: "/a" }; - const file2 = { path: "/a/file2.ts", content: "let y = 2;", projectRootPath: "/a" }; - const file3 = { path: "/b/file2.ts", content: "let x = 3;", projectRootPath: "/b" }; - const file4 = { path: "/c/file3.ts", content: "let z = 4;" }; - const host = createServerHost([file1, file2, file3, file4]); - const session = createSession(host, { - useSingleInferredProject: true, - useInferredProjectPerProjectRoot: true - }); - session.executeCommand({ - seq: 1, - type: "request", - command: CommandNames.CompilerOptionsForInferredProjects, - arguments: { - options: { - allowJs: true, - target: ScriptTarget.ESNext - } - } - } as server.protocol.SetCompilerOptionsForInferredProjectsRequest); - session.executeCommand({ - seq: 2, - type: "request", - command: CommandNames.CompilerOptionsForInferredProjects, - arguments: { - options: { - allowJs: true, - target: ScriptTarget.ES2015 - }, - projectRootPath: "/b" - } - } as server.protocol.SetCompilerOptionsForInferredProjectsRequest); - session.executeCommand({ - seq: 3, - type: "request", - command: CommandNames.Open, - arguments: { - file: file1.path, - fileContent: file1.content, - scriptKindName: "JS", - projectRootPath: file1.projectRootPath - } - } as server.protocol.OpenRequest); - session.executeCommand({ - seq: 4, - type: "request", - command: CommandNames.Open, - arguments: { - file: file2.path, - fileContent: file2.content, - scriptKindName: "JS", - projectRootPath: file2.projectRootPath - } - } as server.protocol.OpenRequest); - session.executeCommand({ - seq: 5, - type: "request", - command: CommandNames.Open, - arguments: { - file: file3.path, - fileContent: file3.content, - scriptKindName: "JS", - projectRootPath: file3.projectRootPath - } - } as server.protocol.OpenRequest); - session.executeCommand({ - seq: 6, - type: "request", - command: CommandNames.Open, - arguments: { - file: file4.path, - fileContent: file4.content, - scriptKindName: "JS" - } - } as server.protocol.OpenRequest); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 3 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file1.path, file2.path]); - checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); - assert.equal(projectService.inferredProjects[0].getCompilationSettings().target, ScriptTarget.ESNext); - assert.equal(projectService.inferredProjects[1].getCompilationSettings().target, ScriptTarget.ESNext); - assert.equal(projectService.inferredProjects[2].getCompilationSettings().target, ScriptTarget.ES2015); + it("inferred projects per project root", () => { + const file1 = { path: "/a/file1.ts", content: "let x = 1;", projectRootPath: "/a" }; + const file2 = { path: "/a/file2.ts", content: "let y = 2;", projectRootPath: "/a" }; + const file3 = { path: "/b/file2.ts", content: "let x = 3;", projectRootPath: "/b" }; + const file4 = { path: "/c/file3.ts", content: "let z = 4;" }; + const host = createServerHost([file1, file2, file3, file4]); + const session = createSession(host, { + useSingleInferredProject: true, + useInferredProjectPerProjectRoot: true }); + session.executeCommand({ + seq: 1, + type: "request", + command: CommandNames.CompilerOptionsForInferredProjects, + arguments: { + options: { + allowJs: true, + target: ScriptTarget.ESNext + } + } + } as server.protocol.SetCompilerOptionsForInferredProjectsRequest); + session.executeCommand({ + seq: 2, + type: "request", + command: CommandNames.CompilerOptionsForInferredProjects, + arguments: { + options: { + allowJs: true, + target: ScriptTarget.ES2015 + }, + projectRootPath: "/b" + } + } as server.protocol.SetCompilerOptionsForInferredProjectsRequest); + session.executeCommand({ + seq: 3, + type: "request", + command: CommandNames.Open, + arguments: { + file: file1.path, + fileContent: file1.content, + scriptKindName: "JS", + projectRootPath: file1.projectRootPath + } + } as server.protocol.OpenRequest); + session.executeCommand({ + seq: 4, + type: "request", + command: CommandNames.Open, + arguments: { + file: file2.path, + fileContent: file2.content, + scriptKindName: "JS", + projectRootPath: file2.projectRootPath + } + } as server.protocol.OpenRequest); + session.executeCommand({ + seq: 5, + type: "request", + command: CommandNames.Open, + arguments: { + file: file3.path, + fileContent: file3.content, + scriptKindName: "JS", + projectRootPath: file3.projectRootPath + } + } as server.protocol.OpenRequest); + session.executeCommand({ + seq: 6, + type: "request", + command: CommandNames.Open, + arguments: { + file: file4.path, + fileContent: file4.content, + scriptKindName: "JS" + } + } as server.protocol.OpenRequest); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { inferredProjects: 3 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file1.path, file2.path]); + checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); + assert.equal(projectService.inferredProjects[0].getCompilationSettings().target, ScriptTarget.ESNext); + assert.equal(projectService.inferredProjects[1].getCompilationSettings().target, ScriptTarget.ESNext); + assert.equal(projectService.inferredProjects[2].getCompilationSettings().target, ScriptTarget.ES2015); + }); - function checkInferredProject(inferredProject: server.InferredProject, actualFiles: File[], target: ScriptTarget) { - checkProjectActualFiles(inferredProject, actualFiles.map(f => f.path)); - assert.equal(inferredProject.getCompilationSettings().target, target); + function checkInferredProject(inferredProject: server.InferredProject, actualFiles: File[], target: ScriptTarget) { + checkProjectActualFiles(inferredProject, actualFiles.map(f => f.path)); + assert.equal(inferredProject.getCompilationSettings().target, target); + } + + function verifyProjectRootWithCaseSensitivity(useCaseSensitiveFileNames: boolean) { + const files: [File, File, File, File] = [ + { path: "/a/file1.ts", content: "let x = 1;" }, + { path: "/A/file2.ts", content: "let y = 2;" }, + { path: "/b/file2.ts", content: "let x = 3;" }, + { path: "/c/file3.ts", content: "let z = 4;" } + ]; + const host = createServerHost(files, { useCaseSensitiveFileNames }); + const projectService = createProjectService(host, { useSingleInferredProject: true, useInferredProjectPerProjectRoot: true }); + projectService.setCompilerOptionsForInferredProjects({ + allowJs: true, + target: ScriptTarget.ESNext + }); + projectService.setCompilerOptionsForInferredProjects({ + allowJs: true, + target: ScriptTarget.ES2015 + }, "/a"); + + openClientFiles(["/a", "/a", "/b", undefined]); + verifyInferredProjectsState([ + [[files[3]], ScriptTarget.ESNext], + [[files[0], files[1]], ScriptTarget.ES2015], + [[files[2]], ScriptTarget.ESNext] + ]); + closeClientFiles(); + + openClientFiles(["/a", "/A", "/b", undefined]); + if (useCaseSensitiveFileNames) { + verifyInferredProjectsState([ + [[files[3]], ScriptTarget.ESNext], + [[files[0]], ScriptTarget.ES2015], + [[files[1]], ScriptTarget.ESNext], + [[files[2]], ScriptTarget.ESNext] + ]); } - - function verifyProjectRootWithCaseSensitivity(useCaseSensitiveFileNames: boolean) { - const files: [File, File, File, File] = [ - { path: "/a/file1.ts", content: "let x = 1;" }, - { path: "/A/file2.ts", content: "let y = 2;" }, - { path: "/b/file2.ts", content: "let x = 3;" }, - { path: "/c/file3.ts", content: "let z = 4;" } - ]; - const host = createServerHost(files, { useCaseSensitiveFileNames }); - const projectService = createProjectService(host, { useSingleInferredProject: true, useInferredProjectPerProjectRoot: true }); - projectService.setCompilerOptionsForInferredProjects({ - allowJs: true, - target: ScriptTarget.ESNext - }); - projectService.setCompilerOptionsForInferredProjects({ - allowJs: true, - target: ScriptTarget.ES2015 - }, "/a"); - - openClientFiles(["/a", "/a", "/b", undefined]); + else { verifyInferredProjectsState([ [[files[3]], ScriptTarget.ESNext], [[files[0], files[1]], ScriptTarget.ES2015], [[files[2]], ScriptTarget.ESNext] ]); - closeClientFiles(); - - openClientFiles(["/a", "/A", "/b", undefined]); - if (useCaseSensitiveFileNames) { - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0]], ScriptTarget.ES2015], - [[files[1]], ScriptTarget.ESNext], - [[files[2]], ScriptTarget.ESNext] - ]); - } - else { - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0], files[1]], ScriptTarget.ES2015], - [[files[2]], ScriptTarget.ESNext] - ]); - } - closeClientFiles(); - - projectService.setCompilerOptionsForInferredProjects({ - allowJs: true, - target: ScriptTarget.ES2017 - }, "/A"); - - openClientFiles(["/a", "/a", "/b", undefined]); + } + closeClientFiles(); + + projectService.setCompilerOptionsForInferredProjects({ + allowJs: true, + target: ScriptTarget.ES2017 + }, "/A"); + + openClientFiles(["/a", "/a", "/b", undefined]); + verifyInferredProjectsState([ + [[files[3]], ScriptTarget.ESNext], + [[files[0], files[1]], useCaseSensitiveFileNames ? ScriptTarget.ES2015 : ScriptTarget.ES2017], + [[files[2]], ScriptTarget.ESNext] + ]); + closeClientFiles(); + + openClientFiles(["/a", "/A", "/b", undefined]); + if (useCaseSensitiveFileNames) { verifyInferredProjectsState([ [[files[3]], ScriptTarget.ESNext], - [[files[0], files[1]], useCaseSensitiveFileNames ? ScriptTarget.ES2015 : ScriptTarget.ES2017], + [[files[0]], ScriptTarget.ES2015], + [[files[1]], ScriptTarget.ES2017], [[files[2]], ScriptTarget.ESNext] ]); - closeClientFiles(); - - openClientFiles(["/a", "/A", "/b", undefined]); - if (useCaseSensitiveFileNames) { - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0]], ScriptTarget.ES2015], - [[files[1]], ScriptTarget.ES2017], - [[files[2]], ScriptTarget.ESNext] - ]); - } - else { - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0], files[1]], ScriptTarget.ES2017], - [[files[2]], ScriptTarget.ESNext] - ]); - } - closeClientFiles(); + } + else { + verifyInferredProjectsState([ + [[files[3]], ScriptTarget.ESNext], + [[files[0], files[1]], ScriptTarget.ES2017], + [[files[2]], ScriptTarget.ESNext] + ]); + } + closeClientFiles(); - function openClientFiles(projectRoots: [string | undefined, string | undefined, string | undefined, string | undefined]) { - files.forEach((file, index) => { - projectService.openClientFile(file.path, file.content, ScriptKind.JS, projectRoots[index]); - }); - } + function openClientFiles(projectRoots: [string | undefined, string | undefined, string | undefined, string | undefined]) { + files.forEach((file, index) => { + projectService.openClientFile(file.path, file.content, ScriptKind.JS, projectRoots[index]); + }); + } - function closeClientFiles() { - files.forEach(file => projectService.closeClientFile(file.path)); - } + function closeClientFiles() { + files.forEach(file => projectService.closeClientFile(file.path)); + } - function verifyInferredProjectsState(expected: [File[], ScriptTarget][]) { - checkNumberOfProjects(projectService, { inferredProjects: expected.length }); - projectService.inferredProjects.forEach((p, index) => { - const [actualFiles, target] = expected[index]; - checkInferredProject(p, actualFiles, target); - }); - } + function verifyInferredProjectsState(expected: [File[], ScriptTarget][]) { + checkNumberOfProjects(projectService, { inferredProjects: expected.length }); + projectService.inferredProjects.forEach((p, index) => { + const [actualFiles, target] = expected[index]; + checkInferredProject(p, actualFiles, target); + }); } + } - it("inferred projects per project root with case sensitive system", () => { - verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ true); - }); + it("inferred projects per project root with case sensitive system", () => { + verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ true); + }); - it("inferred projects per project root with case insensitive system", () => { - verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ false); - }); + it("inferred projects per project root with case insensitive system", () => { + verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ false); + }); - it("should still retain configured project created while opening the file", () => { - const appFile: File = { - path: `${tscWatch.projectRoot}/app.ts`, - content: `const app = 20;` - }; - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const jsFile1: File = { - path: `${tscWatch.projectRoot}/jsFile1.js`, - content: `const jsFile1 = 10;` - }; - const jsFile2: File = { - path: `${tscWatch.projectRoot}/jsFile2.js`, - content: `const jsFile2 = 10;` - }; - const host = createServerHost([appFile, libFile, config, jsFile1, jsFile2]); - const projectService = createProjectService(host); - const originalSet = projectService.configuredProjects.set; - const originalDelete = projectService.configuredProjects.delete; - const configuredCreated = new Map(); - const configuredRemoved = new Map(); - projectService.configuredProjects.set = (key, value) => { - assert.isFalse(configuredCreated.has(key)); - configuredCreated.set(key, true); - return originalSet.call(projectService.configuredProjects, key, value); - }; - projectService.configuredProjects.delete = key => { - assert.isFalse(configuredRemoved.has(key)); - configuredRemoved.set(key, true); - return originalDelete.call(projectService.configuredProjects, key); - }; - - // Do not remove config project when opening jsFile that is not present as part of config project - projectService.openClientFile(jsFile1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [jsFile1.path, libFile.path]); - const project = projectService.configuredProjects.get(config.path)!; - checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]); - checkConfiguredProjectCreatedAndNotDeleted(); - - // Do not remove config project when opening jsFile that is not present as part of config project - projectService.closeClientFile(jsFile1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); - projectService.openClientFile(jsFile2.path); - checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]); - checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]); - checkConfiguredProjectNotCreatedAndNotDeleted(); - - // Do not remove config project when opening jsFile that is not present as part of config project - projectService.openClientFile(jsFile1.path); - checkNumberOfProjects(projectService, { inferredProjects: 2, configuredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [jsFile1.path, libFile.path]); - checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]); - checkConfiguredProjectNotCreatedAndNotDeleted(); - - // When opening file that doesnt fall back to the config file, we remove the config project - projectService.openClientFile(libFile.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [jsFile1.path, libFile.path]); - checkConfiguredProjectNotCreatedButDeleted(); - - function checkConfiguredProjectCreatedAndNotDeleted() { - assert.equal(configuredCreated.size, 1); - assert.isTrue(configuredCreated.has(config.path)); - assert.equal(configuredRemoved.size, 0); - configuredCreated.clear(); - } + it("should still retain configured project created while opening the file", () => { + const appFile: File = { + path: `${tscWatch.projectRoot}/app.ts`, + content: `const app = 20;` + }; + const config: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const jsFile1: File = { + path: `${tscWatch.projectRoot}/jsFile1.js`, + content: `const jsFile1 = 10;` + }; + const jsFile2: File = { + path: `${tscWatch.projectRoot}/jsFile2.js`, + content: `const jsFile2 = 10;` + }; + const host = createServerHost([appFile, libFile, config, jsFile1, jsFile2]); + const projectService = createProjectService(host); + const originalSet = projectService.configuredProjects.set; + const originalDelete = projectService.configuredProjects.delete; + const configuredCreated = new Map(); + const configuredRemoved = new Map(); + projectService.configuredProjects.set = (key, value) => { + assert.isFalse(configuredCreated.has(key)); + configuredCreated.set(key, true); + return originalSet.call(projectService.configuredProjects, key, value); + }; + projectService.configuredProjects.delete = key => { + assert.isFalse(configuredRemoved.has(key)); + configuredRemoved.set(key, true); + return originalDelete.call(projectService.configuredProjects, key); + }; + + // Do not remove config project when opening jsFile that is not present as part of config project + projectService.openClientFile(jsFile1.path); + checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [jsFile1.path, libFile.path]); + const project = projectService.configuredProjects.get(config.path)!; + checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]); + checkConfiguredProjectCreatedAndNotDeleted(); + + // Do not remove config project when opening jsFile that is not present as part of config project + projectService.closeClientFile(jsFile1.path); + checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); + projectService.openClientFile(jsFile2.path); + checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]); + checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]); + checkConfiguredProjectNotCreatedAndNotDeleted(); + + // Do not remove config project when opening jsFile that is not present as part of config project + projectService.openClientFile(jsFile1.path); + checkNumberOfProjects(projectService, { inferredProjects: 2, configuredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [jsFile1.path, libFile.path]); + checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]); + checkConfiguredProjectNotCreatedAndNotDeleted(); + + // When opening file that doesnt fall back to the config file, we remove the config project + projectService.openClientFile(libFile.path); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [jsFile1.path, libFile.path]); + checkConfiguredProjectNotCreatedButDeleted(); + + function checkConfiguredProjectCreatedAndNotDeleted() { + assert.equal(configuredCreated.size, 1); + assert.isTrue(configuredCreated.has(config.path)); + assert.equal(configuredRemoved.size, 0); + configuredCreated.clear(); + } - function checkConfiguredProjectNotCreatedAndNotDeleted() { - assert.equal(configuredCreated.size, 0); - assert.equal(configuredRemoved.size, 0); - } + function checkConfiguredProjectNotCreatedAndNotDeleted() { + assert.equal(configuredCreated.size, 0); + assert.equal(configuredRemoved.size, 0); + } - function checkConfiguredProjectNotCreatedButDeleted() { - assert.equal(configuredCreated.size, 0); - assert.equal(configuredRemoved.size, 1); - assert.isTrue(configuredRemoved.has(config.path)); - configuredRemoved.clear(); - } - }); + function checkConfiguredProjectNotCreatedButDeleted() { + assert.equal(configuredCreated.size, 0); + assert.equal(configuredRemoved.size, 1); + assert.isTrue(configuredRemoved.has(config.path)); + configuredRemoved.clear(); + } + }); - it("regression test - should infer typeAcquisition for inferred projects when set undefined", () => { - const file1 = { path: "/a/file1.js", content: "" }; - const host = createServerHost([file1]); + it("regression test - should infer typeAcquisition for inferred projects when set undefined", () => { + const file1 = { path: "/a/file1.js", content: "" }; + const host = createServerHost([file1]); - const projectService = createProjectService(host); + const projectService = createProjectService(host); - projectService.openClientFile(file1.path); + projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const inferredProject = projectService.inferredProjects[0]; - checkProjectActualFiles(inferredProject, [file1.path]); - inferredProject.setTypeAcquisition(undefined); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const inferredProject = projectService.inferredProjects[0]; + checkProjectActualFiles(inferredProject, [file1.path]); + inferredProject.setTypeAcquisition(undefined); - const expected = { - enable: true, - include: [], - exclude: [] - }; - assert.deepEqual(inferredProject.getTypeAcquisition(), expected, "typeAcquisition should be inferred for inferred projects"); - }); + const expected = { + enable: true, + include: [], + exclude: [] + }; + assert.deepEqual(inferredProject.getTypeAcquisition(), expected, "typeAcquisition should be inferred for inferred projects"); + }); - it("Setting compiler options for inferred projects when there are no open files should not schedule any refresh", () => { - const host = createServerHost([commonFile1, libFile]); - const projectService = createProjectService(host); - projectService.setCompilerOptionsForInferredProjects({ - allowJs: true, - target: ScriptTarget.ES2015 - }); - host.checkTimeoutQueueLength(0); + it("Setting compiler options for inferred projects when there are no open files should not schedule any refresh", () => { + const host = createServerHost([commonFile1, libFile]); + const projectService = createProjectService(host); + projectService.setCompilerOptionsForInferredProjects({ + allowJs: true, + target: ScriptTarget.ES2015 }); + host.checkTimeoutQueueLength(0); }); +}); } diff --git a/src/testRunner/unittests/tsserver/inlayHints.ts b/src/testRunner/unittests/tsserver/inlayHints.ts index 837bc9c452e6a..c749786f1bbe0 100644 --- a/src/testRunner/unittests/tsserver/inlayHints.ts +++ b/src/testRunner/unittests/tsserver/inlayHints.ts @@ -1,63 +1,63 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: inlayHints", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: "{}" - }; - const app: File = { - path: "/a/b/app.ts", - content: "declare function foo(param: any): void;\nfoo(12);" - }; +describe("unittests:: tsserver:: inlayHints", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + const app: File = { + path: "/a/b/app.ts", + content: "declare function foo(param: any): void;\nfoo(12);" + }; - it("with updateOpen request does not corrupt documents", () => { - const host = createServerHost([app, commonFile1, commonFile2, libFile, configFile]); - const session = createSession(host); - session.executeCommandSeq({ - command: protocol.CommandTypes.Open, - arguments: { file: app.path } - }); - session.executeCommandSeq({ - command: protocol.CommandTypes.Configure, - arguments: { - preferences: { - includeInlayParameterNameHints: "all" - } as UserPreferences - } - }); - verifyInlayHintResponse(session); - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ fileName: app.path, textChanges: [{ start: { line: 1, offset: 39 }, end: { line: 1, offset: 39 }, newText: "//" }] }] - } - }); - verifyInlayHintResponse(session); - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ fileName: app.path, textChanges: [{ start: { line: 1, offset: 41 }, end: { line: 1, offset: 41 }, newText: "c" }] }] - } - }); - verifyInlayHintResponse(session); - - function verifyInlayHintResponse(session: TestSession) { - verifyParamInlayHint(session.executeCommandSeq({ - command: protocol.CommandTypes.ProvideInlayHints, - arguments: { - file: app.path, - start: 0, - length: app.content.length, - } - }).response as protocol.InlayHintItem[] | undefined); + it("with updateOpen request does not corrupt documents", () => { + const host = createServerHost([app, commonFile1, commonFile2, libFile, configFile]); + const session = createSession(host); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { file: app.path } + }); + session.executeCommandSeq({ + command: protocol.CommandTypes.Configure, + arguments: { + preferences: { + includeInlayParameterNameHints: "all" + } as UserPreferences } - - function verifyParamInlayHint(response: protocol.InlayHintItem[] | undefined) { - Debug.assert(response); - Debug.assert(response[0]); - Debug.assertEqual(response[0].text, "param:"); - Debug.assertEqual(response[0].position.line, 2); - Debug.assertEqual(response[0].position.offset, 5); + }); + verifyInlayHintResponse(session); + session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ fileName: app.path, textChanges: [{ start: { line: 1, offset: 39 }, end: { line: 1, offset: 39 }, newText: "//" }] }] + } + }); + verifyInlayHintResponse(session); + session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ fileName: app.path, textChanges: [{ start: { line: 1, offset: 41 }, end: { line: 1, offset: 41 }, newText: "c" }] }] } }); + verifyInlayHintResponse(session); + + function verifyInlayHintResponse(session: TestSession) { + verifyParamInlayHint(session.executeCommandSeq({ + command: protocol.CommandTypes.ProvideInlayHints, + arguments: { + file: app.path, + start: 0, + length: app.content.length, + } + }).response as protocol.InlayHintItem[] | undefined); + } + + function verifyParamInlayHint(response: protocol.InlayHintItem[] | undefined) { + Debug.assert(response); + Debug.assert(response[0]); + Debug.assertEqual(response[0].text, "param:"); + Debug.assertEqual(response[0].position.line, 2); + Debug.assertEqual(response[0].position.offset, 5); + } }); +}); } diff --git a/src/testRunner/unittests/tsserver/jsdocTag.ts b/src/testRunner/unittests/tsserver/jsdocTag.ts index 7699c107586e2..249eca937f3c1 100644 --- a/src/testRunner/unittests/tsserver/jsdocTag.ts +++ b/src/testRunner/unittests/tsserver/jsdocTag.ts @@ -1,8 +1,8 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: jsdoc @link ", () => { - const config: File = { - path: "/a/tsconfig.json", - content: `{ +describe("unittests:: tsserver:: jsdoc @link ", () => { + const config: File = { + path: "/a/tsconfig.json", + content: `{ "compilerOptions": { "checkJs": true, "noEmit": true @@ -10,134 +10,92 @@ namespace ts.projectSystem { "files": ["someFile1.js"] } ` - }; - function assertQuickInfoJSDoc(file: File, options: { - displayPartsForJSDoc: boolean, - command: protocol.CommandTypes, - tags: string | unknown[] | undefined, - documentation: string | unknown[] - }) { + }; + function assertQuickInfoJSDoc(file: File, options: { + displayPartsForJSDoc: boolean, + command: protocol.CommandTypes, + tags: string | unknown[] | undefined, + documentation: string | unknown[] + }) { - const { command, displayPartsForJSDoc, tags, documentation } = options; - const session = createSession(createServerHost([file, config])); - session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } }); - openFilesForSession([file], session); - const indexOfX = file.content.indexOf("x"); - const quickInfo = session.executeCommandSeq({ - command: command as protocol.CommandTypes.Quickinfo, - arguments: { - file: file.path, - position: indexOfX, - } as protocol.FileLocationRequestArgs - }).response; - const summaryAndLocation = command === protocol.CommandTypes.Quickinfo ? { - displayString: "var x: number", - start: { - line: 3, - offset: 5, - }, - end: { - line: 3, - offset: 6, - } - } : { - displayParts: [{ - kind: "keyword", - text: "var", - }, { - kind: "space", - text: " ", - }, { - kind: "localName", - text: "x", - }, { - kind: "punctuation", - text: ":", - }, { - kind: "space", - text: " ", - }, { - kind: "keyword", - text: "number", - }], - textSpan: { - length: 1, - start: 38, - } - }; - assert.deepEqual(quickInfo, { - kind: "var", - kindModifiers: "", - ...summaryAndLocation, - documentation, - tags - }); - } + const { command, displayPartsForJSDoc, tags, documentation } = options; + const session = createSession(createServerHost([file, config])); + session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } }); + openFilesForSession([file], session); + const indexOfX = file.content.indexOf("x"); + const quickInfo = session.executeCommandSeq({ + command: command as protocol.CommandTypes.Quickinfo, + arguments: { + file: file.path, + position: indexOfX, + } as protocol.FileLocationRequestArgs + }).response; + const summaryAndLocation = command === protocol.CommandTypes.Quickinfo ? { + displayString: "var x: number", + start: { + line: 3, + offset: 5, + }, + end: { + line: 3, + offset: 6, + } + } : { + displayParts: [{ + kind: "keyword", + text: "var", + }, { + kind: "space", + text: " ", + }, { + kind: "localName", + text: "x", + }, { + kind: "punctuation", + text: ":", + }, { + kind: "space", + text: " ", + }, { + kind: "keyword", + text: "number", + }], + textSpan: { + length: 1, + start: 38, + } + }; + assert.deepEqual(quickInfo, { + kind: "var", + kindModifiers: "", + ...summaryAndLocation, + documentation, + tags + }); + } - const linkInTag: File = { - path: "/a/someFile1.js", - content: `class C { } + const linkInTag: File = { + path: "/a/someFile1.js", + content: `class C { } /** @wat {@link C} */ var x = 1` - }; - const linkInComment: File = { - path: "/a/someFile1.js", - content: `class C { } + }; + const linkInComment: File = { + path: "/a/someFile1.js", + content: `class C { } /** {@link C} */ var x = 1 ;` - }; + }; - it("for quickinfo, should provide display parts plus a span for a working link in a tag", () => { - assertQuickInfoJSDoc(linkInTag, { - command: protocol.CommandTypes.Quickinfo, - displayPartsForJSDoc: true, - documentation: [], - tags: [{ - name: "wat", - text: [{ - kind: "text", - text: "", - }, { - kind: "link", - text: "{@link ", - }, { - kind: "linkName", - target: { - end: { - line: 1, - offset: 12, - }, - file: "/a/someFile1.js", - start: { - line: 1, - offset: 1, - }, - }, - text: "C", - }, { - kind: "link", - text: "}", - }] - }], - }); - }); - it("for quickinfo, should provide a string for a working link in a tag", () => { - assertQuickInfoJSDoc(linkInTag, { - command: protocol.CommandTypes.Quickinfo, - displayPartsForJSDoc: false, - documentation: "", - tags: [{ - name: "wat", - text: "{@link C}" - }], - }); - }); - it("for quickinfo, should provide display parts for a working link in a comment", () => { - assertQuickInfoJSDoc(linkInComment, { - command: protocol.CommandTypes.Quickinfo, - displayPartsForJSDoc: true, - documentation: [{ + it("for quickinfo, should provide display parts plus a span for a working link in a tag", () => { + assertQuickInfoJSDoc(linkInTag, { + command: protocol.CommandTypes.Quickinfo, + displayPartsForJSDoc: true, + documentation: [], + tags: [{ + name: "wat", + text: [{ kind: "text", text: "", }, { @@ -160,65 +118,69 @@ var x = 1 }, { kind: "link", text: "}", - }], - tags: [], - }); + }] + }], }); - it("for quickinfo, should provide a string for a working link in a comment", () => { - assertQuickInfoJSDoc(linkInComment, { - command: protocol.CommandTypes.Quickinfo, - displayPartsForJSDoc: false, - documentation: "{@link C}", - tags: [], - }); + }); + it("for quickinfo, should provide a string for a working link in a tag", () => { + assertQuickInfoJSDoc(linkInTag, { + command: protocol.CommandTypes.Quickinfo, + displayPartsForJSDoc: false, + documentation: "", + tags: [{ + name: "wat", + text: "{@link C}" + }], }); - - it("for quickinfo-full, should provide display parts plus a span for a working link in a tag", () => { - assertQuickInfoJSDoc(linkInTag, { - command: protocol.CommandTypes.QuickinfoFull, - displayPartsForJSDoc: true, - documentation: [], - tags: [{ - name: "wat", - text: [{ - kind: "text", - text: "", - }, { - kind: "link", - text: "{@link ", - }, { - kind: "linkName", - target: { - fileName: "/a/someFile1.js", - textSpan: { - length: 11, - start: 0 - }, - }, - text: "C", - }, { - kind: "link", - text: "}", - }] - }], - }); + }); + it("for quickinfo, should provide display parts for a working link in a comment", () => { + assertQuickInfoJSDoc(linkInComment, { + command: protocol.CommandTypes.Quickinfo, + displayPartsForJSDoc: true, + documentation: [{ + kind: "text", + text: "", + }, { + kind: "link", + text: "{@link ", + }, { + kind: "linkName", + target: { + end: { + line: 1, + offset: 12, + }, + file: "/a/someFile1.js", + start: { + line: 1, + offset: 1, + }, + }, + text: "C", + }, { + kind: "link", + text: "}", + }], + tags: [], }); - it("for quickinfo-full, should provide a string for a working link in a tag", () => { - assertQuickInfoJSDoc(linkInTag, { - command: protocol.CommandTypes.QuickinfoFull, - displayPartsForJSDoc: false, - documentation: [], - tags: [{ - name: "wat", - text: "{@link C}" - }], - }); + }); + it("for quickinfo, should provide a string for a working link in a comment", () => { + assertQuickInfoJSDoc(linkInComment, { + command: protocol.CommandTypes.Quickinfo, + displayPartsForJSDoc: false, + documentation: "{@link C}", + tags: [], }); - it("for quickinfo-full, should provide display parts plus a span for a working link in a comment", () => { - assertQuickInfoJSDoc(linkInComment, { - command: protocol.CommandTypes.QuickinfoFull, - displayPartsForJSDoc: true, - documentation: [{ + }); + + it("for quickinfo-full, should provide display parts plus a span for a working link in a tag", () => { + assertQuickInfoJSDoc(linkInTag, { + command: protocol.CommandTypes.QuickinfoFull, + displayPartsForJSDoc: true, + documentation: [], + tags: [{ + name: "wat", + text: [{ kind: "text", text: "", }, { @@ -237,195 +199,418 @@ var x = 1 }, { kind: "link", text: "}", - }], - tags: undefined, - }); + }] + }], }); - it("for quickinfo-full, should provide a string for a working link in a comment", () => { - assertQuickInfoJSDoc(linkInComment, { - command: protocol.CommandTypes.QuickinfoFull, - displayPartsForJSDoc: false, - documentation: [{ - kind: "text", - text: "", - }, { - kind: "link", - text: "{@link ", - }, { - kind: "linkName", - target: { - fileName: "/a/someFile1.js", - textSpan: { - length: 11, - start: 0 - }, - }, - text: "C", - }, { - kind: "link", - text: "}", - }], - tags: [], - }); + }); + it("for quickinfo-full, should provide a string for a working link in a tag", () => { + assertQuickInfoJSDoc(linkInTag, { + command: protocol.CommandTypes.QuickinfoFull, + displayPartsForJSDoc: false, + documentation: [], + tags: [{ + name: "wat", + text: "{@link C}" + }], }); - - function assertSignatureHelpJSDoc(options: { - displayPartsForJSDoc: boolean, - command: protocol.CommandTypes, - documentation: string | unknown[], - tags: unknown[] - }) { - const linkInParamTag: File = { - path: "/a/someFile1.js", - content: `class C { } -/** @param y - {@link C} */ -function x(y) { } -x(1)` - }; - - const { command, displayPartsForJSDoc, documentation, tags } = options; - const session = createSession(createServerHost([linkInParamTag, config])); - session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } }); - openFilesForSession([linkInParamTag], session); - const indexOfX = linkInParamTag.content.lastIndexOf("1"); - const signatureHelp = session.executeCommandSeq({ - command: command as protocol.CommandTypes.SignatureHelp, - arguments: { - triggerReason: { - kind: "invoked" + }); + it("for quickinfo-full, should provide display parts plus a span for a working link in a comment", () => { + assertQuickInfoJSDoc(linkInComment, { + command: protocol.CommandTypes.QuickinfoFull, + displayPartsForJSDoc: true, + documentation: [{ + kind: "text", + text: "", + }, { + kind: "link", + text: "{@link ", + }, { + kind: "linkName", + target: { + fileName: "/a/someFile1.js", + textSpan: { + length: 11, + start: 0 }, - file: linkInParamTag.path, - position: indexOfX, - } as protocol.SignatureHelpRequestArgs - }).response; - const applicableSpan = command === protocol.CommandTypes.SignatureHelp ? { - end: { - line: 4, - offset: 4 }, - start: { - line: 4, - offset: 3 - } - } : { - length: 1, - start: 60 - }; - assert.deepEqual(signatureHelp, { - applicableSpan, - argumentCount: 1, - argumentIndex: 0, - selectedItemIndex: 0, - items: [{ - documentation: [], - isVariadic: false, - parameters: [{ - displayParts: [{ - kind: "parameterName", - text: "y" - }, { - kind: "punctuation", - text: ":" - }, { - kind: "space", - text: " " - }, { - kind: "keyword", - text: "any" - }], - documentation, - isOptional: false, - isRest: false, - name: "y" - }], - prefixDisplayParts: [ - { - kind: "functionName", - text: "x", - }, - { - kind: "punctuation", - text: "(", - }, - ], - separatorDisplayParts: [ - { - kind: "punctuation", - text: ",", - }, - { - kind: "space", - text: " ", - }, - ], - suffixDisplayParts: [ - { - kind: "punctuation", - text: ")", - }, - { - kind: "punctuation", - text: ":", - }, - { - kind: "space", - text: " ", - }, - { - kind: "keyword", - text: "void", - } - ], - tags, - }], - }); - } - it("for signature help, should provide a string for a working link in a comment", () => { - assertSignatureHelpJSDoc({ - command: protocol.CommandTypes.SignatureHelp, - displayPartsForJSDoc: false, - tags: [{ - name: "param", - text: "y - {@link C}" - }], - documentation: [{ + text: "C", + }, { + kind: "link", + text: "}", + }], + tags: undefined, + }); + }); + it("for quickinfo-full, should provide a string for a working link in a comment", () => { + assertQuickInfoJSDoc(linkInComment, { + command: protocol.CommandTypes.QuickinfoFull, + displayPartsForJSDoc: false, + documentation: [{ kind: "text", - text: "- " + text: "", }, { kind: "link", - text: "{@link " + text: "{@link ", }, { kind: "linkName", target: { - file: "/a/someFile1.js", - start: { - line: 1, - offset: 1 + fileName: "/a/someFile1.js", + textSpan: { + length: 11, + start: 0 }, - end: { - line: 1, - offset: 12 - } }, - text: "C" + text: "C", }, { kind: "link", - text: "}" + text: "}", }], - }); + tags: [], }); - it("for signature help, should provide display parts for a working link in a comment", () => { - const tags = [{ + }); + + function assertSignatureHelpJSDoc(options: { + displayPartsForJSDoc: boolean, + command: protocol.CommandTypes, + documentation: string | unknown[], + tags: unknown[] + }) { + const linkInParamTag: File = { + path: "/a/someFile1.js", + content: `class C { } +/** @param y - {@link C} */ +function x(y) { } +x(1)` + }; + + const { command, displayPartsForJSDoc, documentation, tags } = options; + const session = createSession(createServerHost([linkInParamTag, config])); + session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } }); + openFilesForSession([linkInParamTag], session); + const indexOfX = linkInParamTag.content.lastIndexOf("1"); + const signatureHelp = session.executeCommandSeq({ + command: command as protocol.CommandTypes.SignatureHelp, + arguments: { + triggerReason: { + kind: "invoked" + }, + file: linkInParamTag.path, + position: indexOfX, + } as protocol.SignatureHelpRequestArgs + }).response; + const applicableSpan = command === protocol.CommandTypes.SignatureHelp ? { + end: { + line: 4, + offset: 4 + }, + start: { + line: 4, + offset: 3 + } + } : { + length: 1, + start: 60 + }; + assert.deepEqual(signatureHelp, { + applicableSpan, + argumentCount: 1, + argumentIndex: 0, + selectedItemIndex: 0, + items: [{ + documentation: [], + isVariadic: false, + parameters: [{ + displayParts: [{ + kind: "parameterName", + text: "y" + }, { + kind: "punctuation", + text: ":" + }, { + kind: "space", + text: " " + }, { + kind: "keyword", + text: "any" + }], + documentation, + isOptional: false, + isRest: false, + name: "y" + }], + prefixDisplayParts: [ + { + kind: "functionName", + text: "x", + }, + { + kind: "punctuation", + text: "(", + }, + ], + separatorDisplayParts: [ + { + kind: "punctuation", + text: ",", + }, + { + kind: "space", + text: " ", + }, + ], + suffixDisplayParts: [ + { + kind: "punctuation", + text: ")", + }, + { + kind: "punctuation", + text: ":", + }, + { + kind: "space", + text: " ", + }, + { + kind: "keyword", + text: "void", + } + ], + tags, + }], + }); + } + it("for signature help, should provide a string for a working link in a comment", () => { + assertSignatureHelpJSDoc({ + command: protocol.CommandTypes.SignatureHelp, + displayPartsForJSDoc: false, + tags: [{ + name: "param", + text: "y - {@link C}" + }], + documentation: [{ + kind: "text", + text: "- " + }, { + kind: "link", + text: "{@link " + }, { + kind: "linkName", + target: { + file: "/a/someFile1.js", + start: { + line: 1, + offset: 1 + }, + end: { + line: 1, + offset: 12 + } + }, + text: "C" + }, { + kind: "link", + text: "}" + }], + }); + }); + it("for signature help, should provide display parts for a working link in a comment", () => { + const tags = [{ + name: "param", + text: [{ + kind: "parameterName", + text: "y" + }, { + kind: "space", + text: " " + }, { + kind: "text", + text: "- " + }, { + kind: "link", + text: "{@link " + }, { + kind: "linkName", + target: { + file: "/a/someFile1.js", + start: { + line: 1, + offset: 1 + }, + end: { + line: 1, + offset: 12 + } + }, + text: "C" + }, { + kind: "link", + text: "}" + }] + }]; + assertSignatureHelpJSDoc({ + command: protocol.CommandTypes.SignatureHelp, + displayPartsForJSDoc: true, + tags, + documentation: tags[0].text.slice(2) + }); + }); + it("for signature help-full, should provide a string for a working link in a comment", () => { + assertSignatureHelpJSDoc({ + command: protocol.CommandTypes.SignatureHelpFull, + displayPartsForJSDoc: false, + tags: [{ + name: "param", + text: "y - {@link C}" + }], + documentation: [{ + kind: "text", + text: "- " + }, { + kind: "link", + text: "{@link " + }, { + kind: "linkName", + target: { + fileName: "/a/someFile1.js", + textSpan: { + length: 11, + start: 0 + } + }, + text: "C" + }, { + kind: "link", + text: "}" + }], + }); + }); + it("for signature help-full, should provide display parts for a working link in a comment", () => { + const tags = [{ + name: "param", + text: [{ + kind: "parameterName", + text: "y" + }, { + kind: "space", + text: " " + }, { + kind: "text", + text: "- " + }, { + kind: "link", + text: "{@link " + }, { + kind: "linkName", + target: { + fileName: "/a/someFile1.js", + textSpan: { + length: 11, + start: 0 + } + }, + text: "C" + }, { + kind: "link", + text: "}" + }] + }]; + assertSignatureHelpJSDoc({ + command: protocol.CommandTypes.SignatureHelpFull, + displayPartsForJSDoc: true, + tags, + documentation: tags[0].text.slice(2), + }); + }); + + function assertCompletionsJSDoc(options: { + displayPartsForJSDoc: boolean, + command: protocol.CommandTypes, + tags: unknown[] + }) { + const linkInParamJSDoc: File = { + path: "/a/someFile1.js", + content: `class C { } +/** @param x - see {@link C} */ +function foo (x) { } +foo` + }; + const { command, displayPartsForJSDoc, tags } = options; + const session = createSession(createServerHost([linkInParamJSDoc, config])); + session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } }); + openFilesForSession([linkInParamJSDoc], session); + const indexOfFoo = linkInParamJSDoc.content.lastIndexOf("fo"); + const completions = session.executeCommandSeq({ + command: command as protocol.CommandTypes.CompletionDetails, + arguments: { + entryNames: ["foo"], + file: linkInParamJSDoc.path, + position: indexOfFoo, + } as protocol.CompletionDetailsRequestArgs + }).response; + assert.deepEqual(completions, [{ + codeActions: undefined, + displayParts: [{ + kind: "keyword", + text: "function", + }, { + kind: "space", + text: " ", + }, { + kind: "functionName", + text: "foo", + }, { + kind: "punctuation", + text: "(", + }, { + kind: "parameterName", + text: "x", + }, { + kind: "punctuation", + text: ":", + }, { + kind: "space", + text: " ", + }, { + kind: "keyword", + text: "any", + }, { + kind: "punctuation", + text: ")", + }, { + kind: "punctuation", + text: ":", + }, { + kind: "space", + text: " ", + }, { + kind: "keyword", + text: "void", + }], + documentation: [], + kind: "function", + kindModifiers: "", + name: "foo", + source: undefined, + sourceDisplay: undefined, + tags, + }]); + } + it("for completions, should provide display parts for a working link in a comment", () => { + assertCompletionsJSDoc({ + command: protocol.CommandTypes.CompletionDetails, + displayPartsForJSDoc: true, + tags: [{ name: "param", text: [{ kind: "parameterName", - text: "y" + text: "x" }, { kind: "space", text: " " }, { kind: "text", - text: "- " + text: "- see " }, { kind: "link", text: "{@link " @@ -433,49 +618,13 @@ x(1)` kind: "linkName", target: { file: "/a/someFile1.js", - start: { + end: { line: 1, - offset: 1 + offset: 12, }, - end: { + start: { line: 1, - offset: 12 - } - }, - text: "C" - }, { - kind: "link", - text: "}" - }] - }]; - assertSignatureHelpJSDoc({ - command: protocol.CommandTypes.SignatureHelp, - displayPartsForJSDoc: true, - tags, - documentation: tags[0].text.slice(2) - }); - }); - it("for signature help-full, should provide a string for a working link in a comment", () => { - assertSignatureHelpJSDoc({ - command: protocol.CommandTypes.SignatureHelpFull, - displayPartsForJSDoc: false, - tags: [{ - name: "param", - text: "y - {@link C}" - }], - documentation: [{ - kind: "text", - text: "- " - }, { - kind: "link", - text: "{@link " - }, { - kind: "linkName", - target: { - fileName: "/a/someFile1.js", - textSpan: { - length: 11, - start: 0 + offset: 1, } }, text: "C" @@ -483,20 +632,34 @@ x(1)` kind: "link", text: "}" }], - }); + }], + }); + }); + it("for completions, should provide a string for a working link in a comment", () => { + assertCompletionsJSDoc({ + command: protocol.CommandTypes.CompletionDetails, + displayPartsForJSDoc: false, + tags: [{ + name: "param", + text: "x - see {@link C}", + }], }); - it("for signature help-full, should provide display parts for a working link in a comment", () => { - const tags = [{ + }); + it("for completions-full, should provide display parts for a working link in a comment", () => { + assertCompletionsJSDoc({ + command: protocol.CommandTypes.CompletionDetailsFull, + displayPartsForJSDoc: true, + tags: [{ name: "param", text: [{ kind: "parameterName", - text: "y" + text: "x" }, { kind: "space", text: " " }, { kind: "text", - text: "- " + text: "- see " }, { kind: "link", text: "{@link " @@ -513,182 +676,19 @@ x(1)` }, { kind: "link", text: "}" - }] - }]; - assertSignatureHelpJSDoc({ - command: protocol.CommandTypes.SignatureHelpFull, - displayPartsForJSDoc: true, - tags, - documentation: tags[0].text.slice(2), - }); - }); - - function assertCompletionsJSDoc(options: { - displayPartsForJSDoc: boolean, - command: protocol.CommandTypes, - tags: unknown[] - }) { - const linkInParamJSDoc: File = { - path: "/a/someFile1.js", - content: `class C { } -/** @param x - see {@link C} */ -function foo (x) { } -foo` - }; - const { command, displayPartsForJSDoc, tags } = options; - const session = createSession(createServerHost([linkInParamJSDoc, config])); - session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } }); - openFilesForSession([linkInParamJSDoc], session); - const indexOfFoo = linkInParamJSDoc.content.lastIndexOf("fo"); - const completions = session.executeCommandSeq({ - command: command as protocol.CommandTypes.CompletionDetails, - arguments: { - entryNames: ["foo"], - file: linkInParamJSDoc.path, - position: indexOfFoo, - } as protocol.CompletionDetailsRequestArgs - }).response; - assert.deepEqual(completions, [{ - codeActions: undefined, - displayParts: [{ - kind: "keyword", - text: "function", - }, { - kind: "space", - text: " ", - }, { - kind: "functionName", - text: "foo", - }, { - kind: "punctuation", - text: "(", - }, { - kind: "parameterName", - text: "x", - }, { - kind: "punctuation", - text: ":", - }, { - kind: "space", - text: " ", - }, { - kind: "keyword", - text: "any", - }, { - kind: "punctuation", - text: ")", - }, { - kind: "punctuation", - text: ":", - }, { - kind: "space", - text: " ", - }, { - kind: "keyword", - text: "void", }], - documentation: [], - kind: "function", - kindModifiers: "", - name: "foo", - source: undefined, - sourceDisplay: undefined, - tags, - }]); - } - it("for completions, should provide display parts for a working link in a comment", () => { - assertCompletionsJSDoc({ - command: protocol.CommandTypes.CompletionDetails, - displayPartsForJSDoc: true, - tags: [{ - name: "param", - text: [{ - kind: "parameterName", - text: "x" - }, { - kind: "space", - text: " " - }, { - kind: "text", - text: "- see " - }, { - kind: "link", - text: "{@link " - }, { - kind: "linkName", - target: { - file: "/a/someFile1.js", - end: { - line: 1, - offset: 12, - }, - start: { - line: 1, - offset: 1, - } - }, - text: "C" - }, { - kind: "link", - text: "}" - }], - }], - }); - }); - it("for completions, should provide a string for a working link in a comment", () => { - assertCompletionsJSDoc({ - command: protocol.CommandTypes.CompletionDetails, - displayPartsForJSDoc: false, - tags: [{ - name: "param", - text: "x - see {@link C}", - }], - }); - }); - it("for completions-full, should provide display parts for a working link in a comment", () => { - assertCompletionsJSDoc({ - command: protocol.CommandTypes.CompletionDetailsFull, - displayPartsForJSDoc: true, - tags: [{ - name: "param", - text: [{ - kind: "parameterName", - text: "x" - }, { - kind: "space", - text: " " - }, { - kind: "text", - text: "- see " - }, { - kind: "link", - text: "{@link " - }, { - kind: "linkName", - target: { - fileName: "/a/someFile1.js", - textSpan: { - length: 11, - start: 0 - } - }, - text: "C" - }, { - kind: "link", - text: "}" - }], - }], - }); + }], }); - it("for completions-full, should provide a string for a working link in a comment", () => { - assertCompletionsJSDoc({ - command: protocol.CommandTypes.CompletionDetailsFull, - displayPartsForJSDoc: false, - tags: [{ - name: "param", - text: "x - see {@link C}", - }], - }); + }); + it("for completions-full, should provide a string for a working link in a comment", () => { + assertCompletionsJSDoc({ + command: protocol.CommandTypes.CompletionDetailsFull, + displayPartsForJSDoc: false, + tags: [{ + name: "param", + text: "x - see {@link C}", + }], }); }); +}); } diff --git a/src/testRunner/unittests/tsserver/languageService.ts b/src/testRunner/unittests/tsserver/languageService.ts index 86d426664df7c..f6318b57c648a 100644 --- a/src/testRunner/unittests/tsserver/languageService.ts +++ b/src/testRunner/unittests/tsserver/languageService.ts @@ -1,19 +1,19 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: Language service", () => { - it("should work correctly on case-sensitive file systems", () => { - const lib = { - path: "/a/Lib/lib.d.ts", - content: "let x: number" - }; - const f = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const host = createServerHost([lib, f], { executingFilePath: "/a/Lib/tsc.js", useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host); - projectService.openClientFile(f.path); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - projectService.inferredProjects[0].getLanguageService().getProgram(); - }); +describe("unittests:: tsserver:: Language service", () => { + it("should work correctly on case-sensitive file systems", () => { + const lib = { + path: "/a/Lib/lib.d.ts", + content: "let x: number" + }; + const f = { + path: "/a/b/app.ts", + content: "let x = 1;" + }; + const host = createServerHost([lib, f], { executingFilePath: "/a/Lib/tsc.js", useCaseSensitiveFileNames: true }); + const projectService = createProjectService(host); + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + projectService.inferredProjects[0].getLanguageService().getProgram(); }); +}); } diff --git a/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts b/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts index eb254789d671e..c0810c2984c86 100644 --- a/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts +++ b/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts @@ -1,55 +1,55 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: maxNodeModuleJsDepth for inferred projects", () => { - it("should be set to 2 if the project has js root files", () => { - const file1: File = { - path: "/a/b/file1.js", - content: `var t = require("test"); t.` - }; - const moduleFile: File = { - path: "/a/b/node_modules/test/index.js", - content: `var v = 10; module.exports = v;` - }; - - const host = createServerHost([file1, moduleFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - - let project = projectService.inferredProjects[0]; - let options = project.getCompilationSettings(); - assert.isTrue(options.maxNodeModuleJsDepth === 2); - - // Assert the option sticks - projectService.setCompilerOptionsForInferredProjects({ target: ScriptTarget.ES2016 }); - project = projectService.inferredProjects[0]; - options = project.getCompilationSettings(); - assert.isTrue(options.maxNodeModuleJsDepth === 2); - }); - - it("should return to normal state when all js root files are removed from project", () => { - const file1 = { - path: "/a/file1.ts", - content: "let x =1;" - }; - const file2 = { - path: "/a/file2.js", - content: "let x =1;" - }; - - const host = createServerHost([file1, file2, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true }); - - projectService.openClientFile(file1.path); - checkNumberOfInferredProjects(projectService, 1); - let project = projectService.inferredProjects[0]; - assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); - - projectService.openClientFile(file2.path); - project = projectService.inferredProjects[0]; - assert.isTrue(project.getCompilationSettings().maxNodeModuleJsDepth === 2); - - projectService.closeClientFile(file2.path); - project = projectService.inferredProjects[0]; - assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); - }); +describe("unittests:: tsserver:: maxNodeModuleJsDepth for inferred projects", () => { + it("should be set to 2 if the project has js root files", () => { + const file1: File = { + path: "/a/b/file1.js", + content: `var t = require("test"); t.` + }; + const moduleFile: File = { + path: "/a/b/node_modules/test/index.js", + content: `var v = 10; module.exports = v;` + }; + + const host = createServerHost([file1, moduleFile]); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + + let project = projectService.inferredProjects[0]; + let options = project.getCompilationSettings(); + assert.isTrue(options.maxNodeModuleJsDepth === 2); + + // Assert the option sticks + projectService.setCompilerOptionsForInferredProjects({ target: ScriptTarget.ES2016 }); + project = projectService.inferredProjects[0]; + options = project.getCompilationSettings(); + assert.isTrue(options.maxNodeModuleJsDepth === 2); }); + + it("should return to normal state when all js root files are removed from project", () => { + const file1 = { + path: "/a/file1.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/file2.js", + content: "let x =1;" + }; + + const host = createServerHost([file1, file2, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: true }); + + projectService.openClientFile(file1.path); + checkNumberOfInferredProjects(projectService, 1); + let project = projectService.inferredProjects[0]; + assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); + + projectService.openClientFile(file2.path); + project = projectService.inferredProjects[0]; + assert.isTrue(project.getCompilationSettings().maxNodeModuleJsDepth === 2); + + projectService.closeClientFile(file2.path); + project = projectService.inferredProjects[0]; + assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); + }); +}); } diff --git a/src/testRunner/unittests/tsserver/metadataInResponse.ts b/src/testRunner/unittests/tsserver/metadataInResponse.ts index 2ca20ff4046a9..707a8dc2fa124 100644 --- a/src/testRunner/unittests/tsserver/metadataInResponse.ts +++ b/src/testRunner/unittests/tsserver/metadataInResponse.ts @@ -1,103 +1,103 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: with metadata in response", () => { - const metadata = "Extra Info"; - function verifyOutput(host: TestServerHost, expectedResponse: protocol.Response) { - const output = host.getOutput().map(mapOutputToJson); - assert.deepEqual(output, [expectedResponse]); - host.clearOutput(); - } +describe("unittests:: tsserver:: with metadata in response", () => { + const metadata = "Extra Info"; + function verifyOutput(host: TestServerHost, expectedResponse: protocol.Response) { + const output = host.getOutput().map(mapOutputToJson); + assert.deepEqual(output, [expectedResponse]); + host.clearOutput(); + } - function verifyCommandWithMetadata(session: TestSession, host: TestServerHost, command: Partial, expectedResponseBody: U) { - command.seq = session.getSeq(); - command.type = "request"; - session.onMessage(JSON.stringify(command)); - verifyOutput(host, expectedResponseBody ? - { seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: true, body: expectedResponseBody, metadata } : - { seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: false, message: "No content available." } - ); - } + function verifyCommandWithMetadata(session: TestSession, host: TestServerHost, command: Partial, expectedResponseBody: U) { + command.seq = session.getSeq(); + command.type = "request"; + session.onMessage(JSON.stringify(command)); + verifyOutput(host, expectedResponseBody ? + { seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: true, body: expectedResponseBody, metadata } : + { seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: false, message: "No content available." } + ); + } - const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; - const tsconfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { plugins: [{ name: "myplugin" }] } - }) - }; - function createHostWithPlugin(files: readonly File[]) { - const host = createServerHost(files); - host.require = (_initialPath, moduleName) => { - assert.equal(moduleName, "myplugin"); - return { - module: () => ({ - create(info: server.PluginCreateInfo) { - const proxy = Harness.LanguageService.makeDefaultProxy(info); - proxy.getCompletionsAtPosition = (filename, position, options) => { - const result = info.languageService.getCompletionsAtPosition(filename, position, options); - if (result) { - result.metadata = metadata; - } - return result; - }; - return proxy; - } - }), - error: undefined - }; + const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; + const tsconfig: File = { + path: "/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { plugins: [{ name: "myplugin" }] } + }) + }; + function createHostWithPlugin(files: readonly File[]) { + const host = createServerHost(files); + host.require = (_initialPath, moduleName) => { + assert.equal(moduleName, "myplugin"); + return { + module: () => ({ + create(info: server.PluginCreateInfo) { + const proxy = Harness.LanguageService.makeDefaultProxy(info); + proxy.getCompletionsAtPosition = (filename, position, options) => { + const result = info.languageService.getCompletionsAtPosition(filename, position, options); + if (result) { + result.metadata = metadata; + } + return result; + }; + return proxy; + } + }), + error: undefined }; - return host; - } + }; + return host; + } - describe("With completion requests", () => { - const completionRequestArgs: protocol.CompletionsRequestArgs = { - file: aTs.path, - line: 1, - offset: aTs.content.indexOf("this.") + 1 + "this.".length - }; - const expectedCompletionEntries: readonly protocol.CompletionEntry[] = [ - { name: "foo", kind: ScriptElementKind.memberFunctionElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority }, - { name: "prop", kind: ScriptElementKind.memberVariableElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority } - ]; + describe("With completion requests", () => { + const completionRequestArgs: protocol.CompletionsRequestArgs = { + file: aTs.path, + line: 1, + offset: aTs.content.indexOf("this.") + 1 + "this.".length + }; + const expectedCompletionEntries: readonly protocol.CompletionEntry[] = [ + { name: "foo", kind: ScriptElementKind.memberFunctionElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority }, + { name: "prop", kind: ScriptElementKind.memberVariableElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority } + ]; - it("can pass through metadata when the command returns array", () => { - const host = createHostWithPlugin([aTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs], session); - verifyCommandWithMetadata(session, host, { - command: protocol.CommandTypes.Completions, - arguments: completionRequestArgs - }, expectedCompletionEntries); - }); + it("can pass through metadata when the command returns array", () => { + const host = createHostWithPlugin([aTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs], session); + verifyCommandWithMetadata(session, host, { + command: protocol.CommandTypes.Completions, + arguments: completionRequestArgs + }, expectedCompletionEntries); + }); - it("can pass through metadata when the command returns object", () => { - const host = createHostWithPlugin([aTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs], session); - verifyCommandWithMetadata(session, host, { - command: protocol.CommandTypes.CompletionInfo, - arguments: completionRequestArgs - }, { - isGlobalCompletion: false, - isMemberCompletion: true, - isNewIdentifierLocation: false, - optionalReplacementSpan: { - start: { line: 1, offset: aTs.content.indexOf("prop;") + 1 }, - end: { line: 1, offset: aTs.content.indexOf("prop;") + 1 + "prop".length } - }, - entries: expectedCompletionEntries - }); + it("can pass through metadata when the command returns object", () => { + const host = createHostWithPlugin([aTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs], session); + verifyCommandWithMetadata(session, host, { + command: protocol.CommandTypes.CompletionInfo, + arguments: completionRequestArgs + }, { + isGlobalCompletion: false, + isMemberCompletion: true, + isNewIdentifierLocation: false, + optionalReplacementSpan: { + start: { line: 1, offset: aTs.content.indexOf("prop;") + 1 }, + end: { line: 1, offset: aTs.content.indexOf("prop;") + 1 + "prop".length } + }, + entries: expectedCompletionEntries }); + }); - it("returns undefined correctly", () => { - const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { const x = 0; } }` }; - const host = createHostWithPlugin([aTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs], session); - verifyCommandWithMetadata(session, host, { - command: protocol.CommandTypes.Completions, - arguments: { file: aTs.path, line: 1, offset: aTs.content.indexOf("x") + 1 } - }, /*expectedResponseBody*/ undefined); - }); + it("returns undefined correctly", () => { + const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { const x = 0; } }` }; + const host = createHostWithPlugin([aTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs], session); + verifyCommandWithMetadata(session, host, { + command: protocol.CommandTypes.Completions, + arguments: { file: aTs.path, line: 1, offset: aTs.content.indexOf("x") + 1 } + }, /*expectedResponseBody*/ undefined); }); }); +}); } diff --git a/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts b/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts index fd57438a2691e..52fd39ac823e3 100644 --- a/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts +++ b/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts @@ -1,146 +1,146 @@ namespace ts.projectSystem { - const packageJson: File = { - path: "/package.json", - content: `{ "dependencies": { "mobx": "*" } }` - }; - const aTs: File = { - path: "/src/a.ts", - content: "export const foo = 0;", - }; - const bTs: File = { - path: "/src/b.ts", - content: "foo", - }; - const cTs: File = { - path: "/src/c.ts", - content: "import ", - }; - const bSymlink: SymLink = { - path: "/src/b-link.ts", - symLink: "./b.ts", - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: `{ "include": ["src"] }`, - }; - const ambientDeclaration: File = { - path: "/src/ambient.d.ts", - content: "declare module 'ambient' {}" - }; - const mobxDts: File = { - path: "/node_modules/mobx/index.d.ts", - content: "export declare function observable(): unknown;" - }; +const packageJson: File = { + path: "/package.json", + content: `{ "dependencies": { "mobx": "*" } }` +}; +const aTs: File = { + path: "/src/a.ts", + content: "export const foo = 0;", +}; +const bTs: File = { + path: "/src/b.ts", + content: "foo", +}; +const cTs: File = { + path: "/src/c.ts", + content: "import ", +}; +const bSymlink: SymLink = { + path: "/src/b-link.ts", + symLink: "./b.ts", +}; +const tsconfig: File = { + path: "/tsconfig.json", + content: `{ "include": ["src"] }`, +}; +const ambientDeclaration: File = { + path: "/src/ambient.d.ts", + content: "declare module 'ambient' {}" +}; +const mobxDts: File = { + path: "/node_modules/mobx/index.d.ts", + content: "export declare function observable(): unknown;" +}; - describe("unittests:: tsserver:: moduleSpecifierCache", () => { - it("caches importability within a file", () => { - const { moduleSpecifierCache } = setup(); - assert.isTrue(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {})?.isAutoImportable); - }); - - it("caches module specifiers within a file", () => { - const { moduleSpecifierCache, triggerCompletions } = setup(); - // Completion at an import statement will calculate and cache module specifiers - triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 }); - const mobxCache = moduleSpecifierCache.get(cTs.path as Path, mobxDts.path as Path, {}); - assert.deepEqual(mobxCache, { - modulePaths: [{ - path: mobxDts.path, - isInNodeModules: true, - isRedirect: false - }], - moduleSpecifiers: ["mobx"], - isAutoImportable: true, - }); - }); - - it("invalidates module specifiers when changes happen in contained node_modules directories", () => { - const { host, moduleSpecifierCache, triggerCompletions } = setup(); - // Completion at an import statement will calculate and cache module specifiers - triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 }); - checkWatchedDirectories(host, ["/src", "/node_modules"], /*recursive*/ true); - host.writeFile("/node_modules/.staging/mobx-12345678/package.json", "{}"); - host.runQueuedTimeoutCallbacks(); - assert.equal(moduleSpecifierCache.count(), 0); - }); +describe("unittests:: tsserver:: moduleSpecifierCache", () => { + it("caches importability within a file", () => { + const { moduleSpecifierCache } = setup(); + assert.isTrue(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {})?.isAutoImportable); + }); - it("does not invalidate the cache when new files are added", () => { - const { host, moduleSpecifierCache } = setup(); - host.writeFile("/src/a2.ts", aTs.content); - host.runQueuedTimeoutCallbacks(); - assert.isTrue(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {})?.isAutoImportable); + it("caches module specifiers within a file", () => { + const { moduleSpecifierCache, triggerCompletions } = setup(); + // Completion at an import statement will calculate and cache module specifiers + triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 }); + const mobxCache = moduleSpecifierCache.get(cTs.path as Path, mobxDts.path as Path, {}); + assert.deepEqual(mobxCache, { + modulePaths: [{ + path: mobxDts.path, + isInNodeModules: true, + isRedirect: false + }], + moduleSpecifiers: ["mobx"], + isAutoImportable: true, }); + }); - it("invalidates the cache when symlinks are added or removed", () => { - const { host, moduleSpecifierCache } = setup(); - host.renameFile(bSymlink.path, "/src/b-link2.ts"); - host.runQueuedTimeoutCallbacks(); - assert.equal(moduleSpecifierCache.count(), 0); - }); + it("invalidates module specifiers when changes happen in contained node_modules directories", () => { + const { host, moduleSpecifierCache, triggerCompletions } = setup(); + // Completion at an import statement will calculate and cache module specifiers + triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 }); + checkWatchedDirectories(host, ["/src", "/node_modules"], /*recursive*/ true); + host.writeFile("/node_modules/.staging/mobx-12345678/package.json", "{}"); + host.runQueuedTimeoutCallbacks(); + assert.equal(moduleSpecifierCache.count(), 0); + }); - it("invalidates the cache when local package.json changes", () => { - const { host, moduleSpecifierCache } = setup(); - host.writeFile("/package.json", `{}`); - host.runQueuedTimeoutCallbacks(); - assert.equal(moduleSpecifierCache.count(), 0); - }); + it("does not invalidate the cache when new files are added", () => { + const { host, moduleSpecifierCache } = setup(); + host.writeFile("/src/a2.ts", aTs.content); + host.runQueuedTimeoutCallbacks(); + assert.isTrue(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {})?.isAutoImportable); + }); - it("invalidates the cache when module resolution settings change", () => { - const { host, moduleSpecifierCache } = setup(); - host.writeFile(tsconfig.path, `{ "compilerOptions": { "moduleResolution": "classic" }, "include": ["src"] }`); - host.runQueuedTimeoutCallbacks(); - assert.equal(moduleSpecifierCache.count(), 0); - }); + it("invalidates the cache when symlinks are added or removed", () => { + const { host, moduleSpecifierCache } = setup(); + host.renameFile(bSymlink.path, "/src/b-link2.ts"); + host.runQueuedTimeoutCallbacks(); + assert.equal(moduleSpecifierCache.count(), 0); + }); - it("invalidates the cache when user preferences change", () => { - const { moduleSpecifierCache, session, triggerCompletions } = setup(); - const preferences: UserPreferences = { importModuleSpecifierPreference: "project-relative" }; + it("invalidates the cache when local package.json changes", () => { + const { host, moduleSpecifierCache } = setup(); + host.writeFile("/package.json", `{}`); + host.runQueuedTimeoutCallbacks(); + assert.equal(moduleSpecifierCache.count(), 0); + }); - assert.ok(getWithPreferences({})); - executeSessionRequest(session, protocol.CommandTypes.Configure, { preferences }); - // Nothing changes yet - assert.ok(getWithPreferences({})); - assert.isUndefined(getWithPreferences(preferences)); - // Completions will request (getting nothing) and set the cache with new preferences - triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); - assert.isUndefined(getWithPreferences({})); - assert.ok(getWithPreferences(preferences)); + it("invalidates the cache when module resolution settings change", () => { + const { host, moduleSpecifierCache } = setup(); + host.writeFile(tsconfig.path, `{ "compilerOptions": { "moduleResolution": "classic" }, "include": ["src"] }`); + host.runQueuedTimeoutCallbacks(); + assert.equal(moduleSpecifierCache.count(), 0); + }); - // Test other affecting preference - executeSessionRequest(session, protocol.CommandTypes.Configure, { - preferences: { importModuleSpecifierEnding: "js" }, - }); - triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); - assert.isUndefined(getWithPreferences(preferences)); + it("invalidates the cache when user preferences change", () => { + const { moduleSpecifierCache, session, triggerCompletions } = setup(); + const preferences: UserPreferences = { importModuleSpecifierPreference: "project-relative" }; - function getWithPreferences(preferences: UserPreferences) { - return moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, preferences); - } - }); - }); + assert.ok(getWithPreferences({})); + executeSessionRequest(session, protocol.CommandTypes.Configure, { preferences }); + // Nothing changes yet + assert.ok(getWithPreferences({})); + assert.isUndefined(getWithPreferences(preferences)); + // Completions will request (getting nothing) and set the cache with new preferences + triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); + assert.isUndefined(getWithPreferences({})); + assert.ok(getWithPreferences(preferences)); - function setup() { - const host = createServerHost([aTs, bTs, cTs, bSymlink, ambientDeclaration, tsconfig, packageJson, mobxDts]); - const session = createSession(host); - openFilesForSession([aTs, bTs, cTs], session); - const projectService = session.getProjectService(); - const project = configuredProjectAt(projectService, 0); + // Test other affecting preference executeSessionRequest(session, protocol.CommandTypes.Configure, { - preferences: { - includeCompletionsForImportStatements: true, - includeCompletionsForModuleExports: true, - includeCompletionsWithInsertText: true, - includeCompletionsWithSnippetText: true, - }, + preferences: { importModuleSpecifierEnding: "js" }, }); triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); + assert.isUndefined(getWithPreferences(preferences)); - return { host, project, projectService, session, moduleSpecifierCache: project.getModuleSpecifierCache(), triggerCompletions }; - - function triggerCompletions(requestLocation: protocol.FileLocationRequestArgs) { - executeSessionRequest(session, protocol.CommandTypes.CompletionInfo, { - ...requestLocation, - }); + function getWithPreferences(preferences: UserPreferences) { + return moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, preferences); } + }); +}); + +function setup() { + const host = createServerHost([aTs, bTs, cTs, bSymlink, ambientDeclaration, tsconfig, packageJson, mobxDts]); + const session = createSession(host); + openFilesForSession([aTs, bTs, cTs], session); + const projectService = session.getProjectService(); + const project = configuredProjectAt(projectService, 0); + executeSessionRequest(session, protocol.CommandTypes.Configure, { + preferences: { + includeCompletionsForImportStatements: true, + includeCompletionsForModuleExports: true, + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: true, + }, + }); + triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); + + return { host, project, projectService, session, moduleSpecifierCache: project.getModuleSpecifierCache(), triggerCompletions }; + + function triggerCompletions(requestLocation: protocol.FileLocationRequestArgs) { + executeSessionRequest(session, protocol.CommandTypes.CompletionInfo, { + ...requestLocation, + }); } } +} diff --git a/src/testRunner/unittests/tsserver/navTo.ts b/src/testRunner/unittests/tsserver/navTo.ts index eeebca75263fd..50d6c5dde9d2d 100644 --- a/src/testRunner/unittests/tsserver/navTo.ts +++ b/src/testRunner/unittests/tsserver/navTo.ts @@ -1,52 +1,52 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: navigate-to for javascript project", () => { - function findNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) { - return find(items, item => item.name === itemName && item.kind === itemKind); - } +describe("unittests:: tsserver:: navigate-to for javascript project", () => { + function findNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) { + return find(items, item => item.name === itemName && item.kind === itemKind); + } - function containsNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) { - return findNavToItem(items, itemName, itemKind) !== undefined; - } + function containsNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) { + return findNavToItem(items, itemName, itemKind) !== undefined; + } - it("should not include type symbols", () => { - const file1: File = { - path: "/a/b/file1.js", - content: "function foo() {}" - }; - const configFile: File = { - path: "/a/b/jsconfig.json", - content: "{}" - }; - const host = createServerHost([file1, configFile, libFile]); - const session = createSession(host); - openFilesForSession([file1], session); + it("should not include type symbols", () => { + const file1: File = { + path: "/a/b/file1.js", + content: "function foo() {}" + }; + const configFile: File = { + path: "/a/b/jsconfig.json", + content: "{}" + }; + const host = createServerHost([file1, configFile, libFile]); + const session = createSession(host); + openFilesForSession([file1], session); - // Try to find some interface type defined in lib.d.ts - const libTypeNavToRequest = makeSessionRequest(CommandNames.Navto, { searchValue: "Document", file: file1.path, projectFileName: configFile.path }); - const items = session.executeCommand(libTypeNavToRequest).response as protocol.NavtoItem[]; - assert.isFalse(containsNavToItem(items, "Document", "interface"), `Found lib.d.ts symbol in JavaScript project nav to request result.`); + // Try to find some interface type defined in lib.d.ts + const libTypeNavToRequest = makeSessionRequest(CommandNames.Navto, { searchValue: "Document", file: file1.path, projectFileName: configFile.path }); + const items = session.executeCommand(libTypeNavToRequest).response as protocol.NavtoItem[]; + assert.isFalse(containsNavToItem(items, "Document", "interface"), `Found lib.d.ts symbol in JavaScript project nav to request result.`); - const localFunctionNavToRequst = makeSessionRequest(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); - const items2 = session.executeCommand(localFunctionNavToRequst).response as protocol.NavtoItem[]; - assert.isTrue(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`); - }); + const localFunctionNavToRequst = makeSessionRequest(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); + const items2 = session.executeCommand(localFunctionNavToRequst).response as protocol.NavtoItem[]; + assert.isTrue(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`); + }); - it("should de-duplicate symbols", () => { - const configFile1: File = { - path: "/a/tsconfig.json", - content: `{ + it("should de-duplicate symbols", () => { + const configFile1: File = { + path: "/a/tsconfig.json", + content: `{ "compilerOptions": { "composite": true } }` - }; - const file1: File = { - path: "/a/index.ts", - content: "export const abcdef = 1;" - }; - const configFile2: File = { - path: "/b/tsconfig.json", - content: `{ + }; + const file1: File = { + path: "/a/index.ts", + content: "export const abcdef = 1;" + }; + const configFile2: File = { + path: "/b/tsconfig.json", + content: `{ "compilerOptions": { "composite": true }, @@ -54,45 +54,45 @@ namespace ts.projectSystem { { "path": "../a" } ] }` - }; - const file2: File = { - path: "/b/index.ts", - content: `import a = require("../a"); + }; + const file2: File = { + path: "/b/index.ts", + content: `import a = require("../a"); export const ghijkl = a.abcdef;` - }; - const host = createServerHost([configFile1, file1, configFile2, file2]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([file1, file2], session); + }; + const host = createServerHost([configFile1, file1, configFile2, file2]); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([file1, file2], session); - const request = makeSessionRequest(CommandNames.Navto, { searchValue: "abcdef", file: file1.path }); - session.executeCommand(request).response as protocol.NavtoItem[]; + const request = makeSessionRequest(CommandNames.Navto, { searchValue: "abcdef", file: file1.path }); + session.executeCommand(request).response as protocol.NavtoItem[]; - baselineTsserverLogs("navTo", "should de-duplicate symbols", session); - }); + baselineTsserverLogs("navTo", "should de-duplicate symbols", session); + }); - it("should de-duplicate symbols when searching all projects", () => { - const solutionConfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - references: [{ path: "./a" }, { path: "./b" }], - files: [], - }) - }; - const configFile1: File = { - path: "/a/tsconfig.json", - content: `{ + it("should de-duplicate symbols when searching all projects", () => { + const solutionConfig: File = { + path: "/tsconfig.json", + content: JSON.stringify({ + references: [{ path: "./a" }, { path: "./b" }], + files: [], + }) + }; + const configFile1: File = { + path: "/a/tsconfig.json", + content: `{ "compilerOptions": { "composite": true } }` - }; - const file1: File = { - path: "/a/index.ts", - content: "export const abcdef = 1;" - }; - const configFile2: File = { - path: "/b/tsconfig.json", - content: `{ + }; + const file1: File = { + path: "/a/index.ts", + content: "export const abcdef = 1;" + }; + const configFile2: File = { + path: "/b/tsconfig.json", + content: `{ "compilerOptions": { "composite": true }, @@ -100,40 +100,40 @@ export const ghijkl = a.abcdef;` { "path": "../a" } ] }` - }; - const file2: File = { - path: "/b/index.ts", - content: `import a = require("../a"); + }; + const file2: File = { + path: "/b/index.ts", + content: `import a = require("../a"); export const ghijkl = a.abcdef;` - }; - const host = createServerHost([configFile1, file1, configFile2, file2, solutionConfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([file1], session); + }; + const host = createServerHost([configFile1, file1, configFile2, file2, solutionConfig]); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([file1], session); - const request = makeSessionRequest(CommandNames.Navto, { searchValue: "abcdef" }); - session.executeCommand(request).response as protocol.NavtoItem[]; - baselineTsserverLogs("navTo", "should de-duplicate symbols when searching all projects", session); - }); + const request = makeSessionRequest(CommandNames.Navto, { searchValue: "abcdef" }); + session.executeCommand(request).response as protocol.NavtoItem[]; + baselineTsserverLogs("navTo", "should de-duplicate symbols when searching all projects", session); + }); - it("should work with Deprecated", () => { - const file1: File = { - path: "/a/b/file1.js", - content: "/** @deprecated */\nfunction foo () {}" - }; - const configFile: File = { - path: "/a/b/jsconfig.json", - content: "{}" - }; - const host = createServerHost([file1, configFile, libFile]); - const session = createSession(host); - openFilesForSession([file1], session); + it("should work with Deprecated", () => { + const file1: File = { + path: "/a/b/file1.js", + content: "/** @deprecated */\nfunction foo () {}" + }; + const configFile: File = { + path: "/a/b/jsconfig.json", + content: "{}" + }; + const host = createServerHost([file1, configFile, libFile]); + const session = createSession(host); + openFilesForSession([file1], session); - // Try to find some interface type defined in lib.d.ts - const libTypeNavToRequest = makeSessionRequest(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); - const items = session.executeCommand(libTypeNavToRequest).response as protocol.NavtoItem[]; - const fooItem = findNavToItem(items, "foo", "function"); - assert.isNotNull(fooItem, `Cannot find function symbol "foo".`); - assert.isTrue(fooItem?.kindModifiers?.includes("deprecated")); - }); + // Try to find some interface type defined in lib.d.ts + const libTypeNavToRequest = makeSessionRequest(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); + const items = session.executeCommand(libTypeNavToRequest).response as protocol.NavtoItem[]; + const fooItem = findNavToItem(items, "foo", "function"); + assert.isNotNull(fooItem, `Cannot find function symbol "foo".`); + assert.isTrue(fooItem?.kindModifiers?.includes("deprecated")); }); +}); } diff --git a/src/testRunner/unittests/tsserver/occurences.ts b/src/testRunner/unittests/tsserver/occurences.ts index 25dfb3c9e90f2..ef9bf02eeea08 100644 --- a/src/testRunner/unittests/tsserver/occurences.ts +++ b/src/testRunner/unittests/tsserver/occurences.ts @@ -1,47 +1,47 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: occurrence highlight on string", () => { - it("should be marked if only on string values", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: `let t1 = "div";\nlet t2 = "div";\nlet t3 = { "div": 123 };\nlet t4 = t3["div"];` - }; +describe("unittests:: tsserver:: occurrence highlight on string", () => { + it("should be marked if only on string values", () => { + const file1: File = { + path: "/a/b/file1.ts", + content: `let t1 = "div";\nlet t2 = "div";\nlet t3 = { "div": 123 };\nlet t4 = t3["div"];` + }; - const host = createServerHost([file1]); - const session = createSession(host); - const projectService = session.getProjectService(); + const host = createServerHost([file1]); + const session = createSession(host); + const projectService = session.getProjectService(); - projectService.openClientFile(file1.path); - { - const highlightRequest = makeSessionRequest( - CommandNames.Occurrences, - { file: file1.path, line: 1, offset: 11 } - ); - const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; - const firstOccurence = highlightResponse[0]; - assert.isTrue(firstOccurence.isInString, "Highlights should be marked with isInString"); - } + projectService.openClientFile(file1.path); + { + const highlightRequest = makeSessionRequest( + CommandNames.Occurrences, + { file: file1.path, line: 1, offset: 11 } + ); + const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; + const firstOccurence = highlightResponse[0]; + assert.isTrue(firstOccurence.isInString, "Highlights should be marked with isInString"); + } - { - const highlightRequest = makeSessionRequest( - CommandNames.Occurrences, - { file: file1.path, line: 3, offset: 13 } - ); - const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; - assert.isTrue(highlightResponse.length === 2); - const firstOccurence = highlightResponse[0]; - assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on property name"); - } + { + const highlightRequest = makeSessionRequest( + CommandNames.Occurrences, + { file: file1.path, line: 3, offset: 13 } + ); + const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; + assert.isTrue(highlightResponse.length === 2); + const firstOccurence = highlightResponse[0]; + assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on property name"); + } - { - const highlightRequest = makeSessionRequest( - CommandNames.Occurrences, - { file: file1.path, line: 4, offset: 14 } - ); - const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; - assert.isTrue(highlightResponse.length === 2); - const firstOccurence = highlightResponse[0]; - assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on indexer"); - } - }); + { + const highlightRequest = makeSessionRequest( + CommandNames.Occurrences, + { file: file1.path, line: 4, offset: 14 } + ); + const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; + assert.isTrue(highlightResponse.length === 2); + const firstOccurence = highlightResponse[0]; + assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on indexer"); + } }); +}); } diff --git a/src/testRunner/unittests/tsserver/openFile.ts b/src/testRunner/unittests/tsserver/openFile.ts index 362c6ae16f005..69804687696b5 100644 --- a/src/testRunner/unittests/tsserver/openFile.ts +++ b/src/testRunner/unittests/tsserver/openFile.ts @@ -1,138 +1,138 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: Open-file", () => { - it("can be reloaded with empty content", () => { - const f = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const projectFileName = "externalProject"; - const host = createServerHost([f]); - const projectService = createProjectService(host); - // create a project - projectService.openExternalProject({ projectFileName, rootFiles: [toExternalFile(f.path)], options: {} }); - projectService.checkNumberOfProjects({ externalProjects: 1 }); +describe("unittests:: tsserver:: Open-file", () => { + it("can be reloaded with empty content", () => { + const f = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const projectFileName = "externalProject"; + const host = createServerHost([f]); + const projectService = createProjectService(host); + // create a project + projectService.openExternalProject({ projectFileName, rootFiles: [toExternalFile(f.path)], options: {} }); + projectService.checkNumberOfProjects({ externalProjects: 1 }); - const p = projectService.externalProjects[0]; - // force to load the content of the file - p.updateGraph(); + const p = projectService.externalProjects[0]; + // force to load the content of the file + p.updateGraph(); - const scriptInfo = p.getScriptInfo(f.path)!; - checkSnapLength(scriptInfo.getSnapshot(), f.content.length); + const scriptInfo = p.getScriptInfo(f.path)!; + checkSnapLength(scriptInfo.getSnapshot(), f.content.length); - // open project and replace its content with empty string - projectService.openClientFile(f.path, ""); - checkSnapLength(scriptInfo.getSnapshot(), 0); - }); - function checkSnapLength(snap: IScriptSnapshot, expectedLength: number) { - assert.equal(snap.getLength(), expectedLength, "Incorrect snapshot size"); - } + // open project and replace its content with empty string + projectService.openClientFile(f.path, ""); + checkSnapLength(scriptInfo.getSnapshot(), 0); + }); + function checkSnapLength(snap: IScriptSnapshot, expectedLength: number) { + assert.equal(snap.getLength(), expectedLength, "Incorrect snapshot size"); + } - function verifyOpenFileWorks(useCaseSensitiveFileNames: boolean) { - const file1: File = { - path: "/a/b/src/app.ts", - content: "let x = 10;" - }; - const file2: File = { - path: "/a/B/lib/module2.ts", - content: "let z = 10;" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: "" - }; - const configFile2: File = { - path: "/a/tsconfig.json", - content: "" - }; - const host = createServerHost([file1, file2, configFile, configFile2], { - useCaseSensitiveFileNames - }); - const service = createProjectService(host); + function verifyOpenFileWorks(useCaseSensitiveFileNames: boolean) { + const file1: File = { + path: "/a/b/src/app.ts", + content: "let x = 10;" + }; + const file2: File = { + path: "/a/B/lib/module2.ts", + content: "let z = 10;" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: "" + }; + const configFile2: File = { + path: "/a/tsconfig.json", + content: "" + }; + const host = createServerHost([file1, file2, configFile, configFile2], { + useCaseSensitiveFileNames + }); + const service = createProjectService(host); - // Open file1 -> configFile - verifyConfigFileName(file1, "/a", configFile); - verifyConfigFileName(file1, "/a/b", configFile); - verifyConfigFileName(file1, "/a/B", configFile); + // Open file1 -> configFile + verifyConfigFileName(file1, "/a", configFile); + verifyConfigFileName(file1, "/a/b", configFile); + verifyConfigFileName(file1, "/a/B", configFile); - // Open file2 use root "/a/b" - verifyConfigFileName(file2, "/a", useCaseSensitiveFileNames ? configFile2 : configFile); - verifyConfigFileName(file2, "/a/b", useCaseSensitiveFileNames ? configFile2 : configFile); - verifyConfigFileName(file2, "/a/B", useCaseSensitiveFileNames ? undefined : configFile); + // Open file2 use root "/a/b" + verifyConfigFileName(file2, "/a", useCaseSensitiveFileNames ? configFile2 : configFile); + verifyConfigFileName(file2, "/a/b", useCaseSensitiveFileNames ? configFile2 : configFile); + verifyConfigFileName(file2, "/a/B", useCaseSensitiveFileNames ? undefined : configFile); - function verifyConfigFileName(file: File, projectRoot: string, expectedConfigFile: File | undefined) { - const { configFileName } = service.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRoot); - assert.equal(configFileName, expectedConfigFile && expectedConfigFile.path); - service.closeClientFile(file.path); - } + function verifyConfigFileName(file: File, projectRoot: string, expectedConfigFile: File | undefined) { + const { configFileName } = service.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRoot); + assert.equal(configFileName, expectedConfigFile && expectedConfigFile.path); + service.closeClientFile(file.path); } - it("works when project root is used with case-sensitive system", () => { - verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ true); - }); + } + it("works when project root is used with case-sensitive system", () => { + verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ true); + }); - it("works when project root is used with case-insensitive system", () => { - verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ false); - }); + it("works when project root is used with case-insensitive system", () => { + verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ false); + }); - it("uses existing project even if project refresh is pending", () => { - const projectFolder = "/user/someuser/projects/myproject"; - const aFile: File = { - path: `${projectFolder}/src/a.ts`, - content: "export const x = 0;" - }; - const configFile: File = { - path: `${projectFolder}/tsconfig.json`, - content: "{}" - }; - const files = [aFile, configFile, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(aFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder); - verifyProject(); + it("uses existing project even if project refresh is pending", () => { + const projectFolder = "/user/someuser/projects/myproject"; + const aFile: File = { + path: `${projectFolder}/src/a.ts`, + content: "export const x = 0;" + }; + const configFile: File = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const files = [aFile, configFile, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(aFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder); + verifyProject(); - const bFile: File = { - path: `${projectFolder}/src/b.ts`, - content: `export {}; declare module "./a" { export const y: number; }` - }; - files.push(bFile); - host.writeFile(bFile.path, bFile.content); - service.openClientFile(bFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder); - verifyProject(); + const bFile: File = { + path: `${projectFolder}/src/b.ts`, + content: `export {}; declare module "./a" { export const y: number; }` + }; + files.push(bFile); + host.writeFile(bFile.path, bFile.content); + service.openClientFile(bFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder); + verifyProject(); - function verifyProject() { - assert.isDefined(service.configuredProjects.get(configFile.path)); - const project = service.configuredProjects.get(configFile.path)!; - checkProjectActualFiles(project, files.map(f => f.path)); - } - }); + function verifyProject() { + assert.isDefined(service.configuredProjects.get(configFile.path)); + const project = service.configuredProjects.get(configFile.path)!; + checkProjectActualFiles(project, files.map(f => f.path)); + } + }); - it("can open same file again", () => { - const projectFolder = "/user/someuser/projects/myproject"; - const aFile: File = { - path: `${projectFolder}/src/a.ts`, - content: "export const x = 0;" - }; - const configFile: File = { - path: `${projectFolder}/tsconfig.json`, - content: "{}" - }; - const files = [aFile, configFile, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - verifyProject(aFile.content); - verifyProject(`${aFile.content}export const y = 10;`); + it("can open same file again", () => { + const projectFolder = "/user/someuser/projects/myproject"; + const aFile: File = { + path: `${projectFolder}/src/a.ts`, + content: "export const x = 0;" + }; + const configFile: File = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const files = [aFile, configFile, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + verifyProject(aFile.content); + verifyProject(`${aFile.content}export const y = 10;`); - function verifyProject(aFileContent: string) { - service.openClientFile(aFile.path, aFileContent, ScriptKind.TS, projectFolder); - const project = service.configuredProjects.get(configFile.path)!; - checkProjectActualFiles(project, files.map(f => f.path)); - assert.equal(project.getCurrentProgram()?.getSourceFile(aFile.path)!.text, aFileContent); - } - }); + function verifyProject(aFileContent: string) { + service.openClientFile(aFile.path, aFileContent, ScriptKind.TS, projectFolder); + const project = service.configuredProjects.get(configFile.path)!; + checkProjectActualFiles(project, files.map(f => f.path)); + assert.equal(project.getCurrentProgram()?.getSourceFile(aFile.path)!.text, aFileContent); + } + }); - it("when file makes edits to add/remove comment directives, they are handled correcrly", () => { - const file: File = { - path: `${tscWatch.projectRoot}/file.ts`, - content: `const x = 10; + it("when file makes edits to add/remove comment directives, they are handled correcrly", () => { + const file: File = { + path: `${tscWatch.projectRoot}/file.ts`, + content: `const x = 10; function foo() { // @ts-ignore let y: string = x; @@ -145,43 +145,43 @@ function bar() { } foo(); bar();` - }; - const host = createServerHost([file, libFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([file], session); - verifyGetErrRequest({ session, host, files: [file] }); + }; + const host = createServerHost([file, libFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([file], session); + verifyGetErrRequest({ session, host, files: [file] }); - // Remove first ts-ignore and check only first error is reported - const tsIgnoreComment = `// @ts-ignore`; - const locationOfTsIgnore = protocolTextSpanFromSubstring(file.content, tsIgnoreComment); - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: file.path, - textChanges: [{ - newText: " ", - ...locationOfTsIgnore - }] + // Remove first ts-ignore and check only first error is reported + const tsIgnoreComment = `// @ts-ignore`; + const locationOfTsIgnore = protocolTextSpanFromSubstring(file.content, tsIgnoreComment); + session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: file.path, + textChanges: [{ + newText: " ", + ...locationOfTsIgnore }] - } - }); - verifyGetErrRequest({ session, host, files: [file] }); - // Revert the change and no errors should be reported - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: file.path, - textChanges: [{ - newText: tsIgnoreComment, - ...locationOfTsIgnore - }] + }] + } + }); + verifyGetErrRequest({ session, host, files: [file] }); + // Revert the change and no errors should be reported + session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: file.path, + textChanges: [{ + newText: tsIgnoreComment, + ...locationOfTsIgnore }] - } - }); - verifyGetErrRequest({ session, host, files: [file] }); - baselineTsserverLogs("openfile", "when file makes edits to add/remove comment directives, they are handled correcrly", session); + }] + } }); + verifyGetErrRequest({ session, host, files: [file] }); + baselineTsserverLogs("openfile", "when file makes edits to add/remove comment directives, they are handled correcrly", session); }); +}); } diff --git a/src/testRunner/unittests/tsserver/packageJsonInfo.ts b/src/testRunner/unittests/tsserver/packageJsonInfo.ts index b931d984c1275..f9211bc117daa 100644 --- a/src/testRunner/unittests/tsserver/packageJsonInfo.ts +++ b/src/testRunner/unittests/tsserver/packageJsonInfo.ts @@ -1,112 +1,112 @@ namespace ts.projectSystem { - const tsConfig: File = { - path: "/tsconfig.json", - content: "{}" - }; - const packageJsonContent = { - dependencies: { - redux: "*" - }, - peerDependencies: { - react: "*" - }, - optionalDependencies: { - typescript: "*" - }, - devDependencies: { - webpack: "*" - } - }; - const packageJson: File = { - path: "/package.json", - content: JSON.stringify(packageJsonContent, undefined, 2) - }; +const tsConfig: File = { + path: "/tsconfig.json", + content: "{}" +}; +const packageJsonContent = { + dependencies: { + redux: "*" + }, + peerDependencies: { + react: "*" + }, + optionalDependencies: { + typescript: "*" + }, + devDependencies: { + webpack: "*" + } +}; +const packageJson: File = { + path: "/package.json", + content: JSON.stringify(packageJsonContent, undefined, 2) +}; - describe("unittests:: tsserver:: packageJsonInfo", () => { - it("detects new package.json files that are added, caches them, and watches them", () => { - // Initialize project without package.json - const { projectService, host } = setup([tsConfig]); - assert.isUndefined(projectService.packageJsonCache.getInDirectory("/" as Path)); +describe("unittests:: tsserver:: packageJsonInfo", () => { + it("detects new package.json files that are added, caches them, and watches them", () => { + // Initialize project without package.json + const { projectService, host } = setup([tsConfig]); + assert.isUndefined(projectService.packageJsonCache.getInDirectory("/" as Path)); - // Add package.json - host.writeFile(packageJson.path, packageJson.content); - let packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as Path)!; - assert.ok(packageJsonInfo); - assert.ok(packageJsonInfo.dependencies); - assert.ok(packageJsonInfo.devDependencies); - assert.ok(packageJsonInfo.peerDependencies); - assert.ok(packageJsonInfo.optionalDependencies); + // Add package.json + host.writeFile(packageJson.path, packageJson.content); + let packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as Path)!; + assert.ok(packageJsonInfo); + assert.ok(packageJsonInfo.dependencies); + assert.ok(packageJsonInfo.devDependencies); + assert.ok(packageJsonInfo.peerDependencies); + assert.ok(packageJsonInfo.optionalDependencies); - // Edit package.json - host.writeFile(packageJson.path, JSON.stringify({ - ...packageJsonContent, - dependencies: undefined - })); - packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as Path)!; - assert.isUndefined(packageJsonInfo.dependencies); - }); + // Edit package.json + host.writeFile(packageJson.path, JSON.stringify({ + ...packageJsonContent, + dependencies: undefined + })); + packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as Path)!; + assert.isUndefined(packageJsonInfo.dependencies); + }); - it("finds package.json on demand, watches for deletion, and removes them from cache", () => { - // Initialize project with package.json - const { projectService, host } = setup(); - projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); - assert.ok(projectService.packageJsonCache.getInDirectory("/" as Path)); + it("finds package.json on demand, watches for deletion, and removes them from cache", () => { + // Initialize project with package.json + const { projectService, host } = setup(); + projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); + assert.ok(projectService.packageJsonCache.getInDirectory("/" as Path)); - // Delete package.json - host.deleteFile(packageJson.path); - assert.isUndefined(projectService.packageJsonCache.getInDirectory("/" as Path)); - }); + // Delete package.json + host.deleteFile(packageJson.path); + assert.isUndefined(projectService.packageJsonCache.getInDirectory("/" as Path)); + }); - it("finds multiple package.json files when present", () => { - // Initialize project with package.json at root - const { projectService, host } = setup(); - // Add package.json in /src - host.writeFile("/src/package.json", packageJson.content); - assert.lengthOf(projectService.getPackageJsonsVisibleToFile("/a.ts" as Path), 1); - assert.lengthOf(projectService.getPackageJsonsVisibleToFile("/src/b.ts" as Path), 2); - }); + it("finds multiple package.json files when present", () => { + // Initialize project with package.json at root + const { projectService, host } = setup(); + // Add package.json in /src + host.writeFile("/src/package.json", packageJson.content); + assert.lengthOf(projectService.getPackageJsonsVisibleToFile("/a.ts" as Path), 1); + assert.lengthOf(projectService.getPackageJsonsVisibleToFile("/src/b.ts" as Path), 2); + }); - it("handles errors in json parsing of package.json", () => { - const packageJsonContent = `{ "mod" }`; - const { projectService, host } = setup([tsConfig, { path: packageJson.path, content: packageJsonContent }]); - projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); - const packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as Path)!; - assert.isFalse(packageJsonInfo.parseable); + it("handles errors in json parsing of package.json", () => { + const packageJsonContent = `{ "mod" }`; + const { projectService, host } = setup([tsConfig, { path: packageJson.path, content: packageJsonContent }]); + projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); + const packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as Path)!; + assert.isFalse(packageJsonInfo.parseable); - host.writeFile(packageJson.path, packageJson.content); - projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); - const packageJsonInfo2 = projectService.packageJsonCache.getInDirectory("/" as Path)!; - assert.ok(packageJsonInfo2); - assert.ok(packageJsonInfo2.dependencies); - assert.ok(packageJsonInfo2.devDependencies); - assert.ok(packageJsonInfo2.peerDependencies); - assert.ok(packageJsonInfo2.optionalDependencies); - }); + host.writeFile(packageJson.path, packageJson.content); + projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); + const packageJsonInfo2 = projectService.packageJsonCache.getInDirectory("/" as Path)!; + assert.ok(packageJsonInfo2); + assert.ok(packageJsonInfo2.dependencies); + assert.ok(packageJsonInfo2.devDependencies); + assert.ok(packageJsonInfo2.peerDependencies); + assert.ok(packageJsonInfo2.optionalDependencies); + }); - it("handles empty package.json", () => { - const packageJsonContent = ""; - const { projectService, host } = setup([tsConfig, { path: packageJson.path, content: packageJsonContent }]); - projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); - const packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as Path)!; - assert.isFalse(packageJsonInfo.parseable); + it("handles empty package.json", () => { + const packageJsonContent = ""; + const { projectService, host } = setup([tsConfig, { path: packageJson.path, content: packageJsonContent }]); + projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); + const packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as Path)!; + assert.isFalse(packageJsonInfo.parseable); - host.writeFile(packageJson.path, packageJson.content); - projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); - const packageJsonInfo2 = projectService.packageJsonCache.getInDirectory("/" as Path)!; - assert.ok(packageJsonInfo2); - assert.ok(packageJsonInfo2.dependencies); - assert.ok(packageJsonInfo2.devDependencies); - assert.ok(packageJsonInfo2.peerDependencies); - assert.ok(packageJsonInfo2.optionalDependencies); - }); + host.writeFile(packageJson.path, packageJson.content); + projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); + const packageJsonInfo2 = projectService.packageJsonCache.getInDirectory("/" as Path)!; + assert.ok(packageJsonInfo2); + assert.ok(packageJsonInfo2.dependencies); + assert.ok(packageJsonInfo2.devDependencies); + assert.ok(packageJsonInfo2.peerDependencies); + assert.ok(packageJsonInfo2.optionalDependencies); }); +}); - function setup(files: readonly File[] = [tsConfig, packageJson]) { - const host = createServerHost(files); - const session = createSession(host); - const projectService = session.getProjectService(); - projectService.openClientFile(files[0].path); - const project = configuredProjectAt(projectService, 0); - return { host, session, project, projectService }; - } +function setup(files: readonly File[] = [tsConfig, packageJson]) { + const host = createServerHost(files); + const session = createSession(host); + const projectService = session.getProjectService(); + projectService.openClientFile(files[0].path); + const project = configuredProjectAt(projectService, 0); + return { host, session, project, projectService }; +} } diff --git a/src/testRunner/unittests/tsserver/partialSemanticServer.ts b/src/testRunner/unittests/tsserver/partialSemanticServer.ts index 290cd1cade215..02f2ee74abd2a 100644 --- a/src/testRunner/unittests/tsserver/partialSemanticServer.ts +++ b/src/testRunner/unittests/tsserver/partialSemanticServer.ts @@ -1,266 +1,266 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: Semantic operations on partialSemanticServer", () => { - function setup() { - const file1: File = { - path: `${tscWatch.projectRoot}/a.ts`, - content: `import { y, cc } from "./b"; +describe("unittests:: tsserver:: Semantic operations on partialSemanticServer", () => { + function setup() { + const file1: File = { + path: `${tscWatch.projectRoot}/a.ts`, + content: `import { y, cc } from "./b"; import { something } from "something"; class c { prop = "hello"; foo() { return this.prop; } }` - }; - const file2: File = { - path: `${tscWatch.projectRoot}/b.ts`, - content: `export { cc } from "./c"; + }; + const file2: File = { + path: `${tscWatch.projectRoot}/b.ts`, + content: `export { cc } from "./c"; import { something } from "something"; export const y = 10;` - }; - const file3: File = { - path: `${tscWatch.projectRoot}/c.ts`, - content: `export const cc = 10;` - }; - const something: File = { - path: `${tscWatch.projectRoot}/node_modules/something/index.d.ts`, - content: "export const something = 10;" - }; - const configFile: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const host = createServerHost([file1, file2, file3, something, libFile, configFile]); - const session = createSession(host, { serverMode: LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); - return { host, session, file1, file2, file3, something, configFile }; - } + }; + const file3: File = { + path: `${tscWatch.projectRoot}/c.ts`, + content: `export const cc = 10;` + }; + const something: File = { + path: `${tscWatch.projectRoot}/node_modules/something/index.d.ts`, + content: "export const something = 10;" + }; + const configFile: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const host = createServerHost([file1, file2, file3, something, libFile, configFile]); + const session = createSession(host, { serverMode: LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); + return { host, session, file1, file2, file3, something, configFile }; + } - it("open files are added to inferred project even if config file is present and semantic operations succeed", () => { - const { host, session, file1, file2 } = setup(); - const service = session.getProjectService(); - openFilesForSession([file1], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [libFile.path, file1.path]); // no imports are resolved - verifyCompletions(); + it("open files are added to inferred project even if config file is present and semantic operations succeed", () => { + const { host, session, file1, file2 } = setup(); + const service = session.getProjectService(); + openFilesForSession([file1], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + const project = service.inferredProjects[0]; + checkProjectActualFiles(project, [libFile.path, file1.path]); // no imports are resolved + verifyCompletions(); - openFilesForSession([file2], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); - verifyCompletions(); + openFilesForSession([file2], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); + verifyCompletions(); - function verifyCompletions() { - assert.isTrue(project.languageServiceEnabled); - checkWatchedFiles(host, emptyArray); - checkWatchedDirectories(host, emptyArray, /*recursive*/ true); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.Completions, - arguments: protocolFileLocationFromSubstring(file1, "prop", { index: 1 }) - }).response as protocol.CompletionEntry[]; - assert.deepEqual(response, [ - completionEntry("foo", ScriptElementKind.memberFunctionElement), - completionEntry("prop", ScriptElementKind.memberVariableElement), - ]); - } - - function completionEntry(name: string, kind: ScriptElementKind): protocol.CompletionEntry { - return { - name, - kind, - kindModifiers: "", - sortText: Completions.SortText.LocationPriority, - hasAction: undefined, - insertText: undefined, - isPackageJsonImport: undefined, - isImportStatementCompletion: undefined, - isRecommended: undefined, - replacementSpan: undefined, - source: undefined, - data: undefined, - sourceDisplay: undefined, - isSnippet: undefined, - }; - } - }); + function verifyCompletions() { + assert.isTrue(project.languageServiceEnabled); + checkWatchedFiles(host, emptyArray); + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.Completions, + arguments: protocolFileLocationFromSubstring(file1, "prop", { index: 1 }) + }).response as protocol.CompletionEntry[]; + assert.deepEqual(response, [ + completionEntry("foo", ScriptElementKind.memberFunctionElement), + completionEntry("prop", ScriptElementKind.memberVariableElement), + ]); + } - it("throws on unsupported commands", () => { - const { session, file1 } = setup(); - const service = session.getProjectService(); - openFilesForSession([file1], session); - let hasException = false; - const request: protocol.SemanticDiagnosticsSyncRequest = { - type: "request", - seq: 1, - command: protocol.CommandTypes.SemanticDiagnosticsSync, - arguments: { file: file1.path } + function completionEntry(name: string, kind: ScriptElementKind): protocol.CompletionEntry { + return { + name, + kind, + kindModifiers: "", + sortText: Completions.SortText.LocationPriority, + hasAction: undefined, + insertText: undefined, + isPackageJsonImport: undefined, + isImportStatementCompletion: undefined, + isRecommended: undefined, + replacementSpan: undefined, + source: undefined, + data: undefined, + sourceDisplay: undefined, + isSnippet: undefined, }; - try { - session.executeCommand(request); - } - catch (e) { - assert.equal(e.message, `Request: semanticDiagnosticsSync not allowed in LanguageServiceMode.PartialSemantic`); - hasException = true; - } - assert.isTrue(hasException); + } + }); - hasException = false; - const project = service.inferredProjects[0]; - try { - project.getLanguageService().getSemanticDiagnostics(file1.path); - } - catch (e) { - assert.equal(e.message, `LanguageService Operation: getSemanticDiagnostics not allowed in LanguageServiceMode.PartialSemantic`); - hasException = true; - } - assert.isTrue(hasException); - }); + it("throws on unsupported commands", () => { + const { session, file1 } = setup(); + const service = session.getProjectService(); + openFilesForSession([file1], session); + let hasException = false; + const request: protocol.SemanticDiagnosticsSyncRequest = { + type: "request", + seq: 1, + command: protocol.CommandTypes.SemanticDiagnosticsSync, + arguments: { file: file1.path } + }; + try { + session.executeCommand(request); + } + catch (e) { + assert.equal(e.message, `Request: semanticDiagnosticsSync not allowed in LanguageServiceMode.PartialSemantic`); + hasException = true; + } + assert.isTrue(hasException); - it("allows syntactic diagnostic commands", () => { - const file1: File = { - path: `${tscWatch.projectRoot}/a.ts`, - content: `if (a < (b + c) { }` - }; - const configFile: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: `{}` - }; - const expectedErrorMessage = "')' expected."; + hasException = false; + const project = service.inferredProjects[0]; + try { + project.getLanguageService().getSemanticDiagnostics(file1.path); + } + catch (e) { + assert.equal(e.message, `LanguageService Operation: getSemanticDiagnostics not allowed in LanguageServiceMode.PartialSemantic`); + hasException = true; + } + assert.isTrue(hasException); + }); - const host = createServerHost([file1, libFile, configFile]); - const session = createSession(host, { - serverMode: LanguageServiceMode.PartialSemantic, - useSingleInferredProject: true, - logger: createLoggerWithInMemoryLogs() - }); + it("allows syntactic diagnostic commands", () => { + const file1: File = { + path: `${tscWatch.projectRoot}/a.ts`, + content: `if (a < (b + c) { }` + }; + const configFile: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: `{}` + }; + const expectedErrorMessage = "')' expected."; - const service = session.getProjectService(); - openFilesForSession([file1], session); - const request: protocol.SyntacticDiagnosticsSyncRequest = { - type: "request", - seq: 1, - command: protocol.CommandTypes.SyntacticDiagnosticsSync, - arguments: { file: file1.path } - }; - const response = session.executeCommandSeq(request).response as protocol.SyntacticDiagnosticsSyncResponse["body"]; - assert.isDefined(response); - assert.equal(response!.length, 1); - assert.equal((response![0] as protocol.Diagnostic).text, expectedErrorMessage); + const host = createServerHost([file1, libFile, configFile]); + const session = createSession(host, { + serverMode: LanguageServiceMode.PartialSemantic, + useSingleInferredProject: true, + logger: createLoggerWithInMemoryLogs() + }); - const project = service.inferredProjects[0]; - const diagnostics = project.getLanguageService().getSyntacticDiagnostics(file1.path); - assert.isTrue(diagnostics.length === 1); - assert.equal(diagnostics[0].messageText, expectedErrorMessage); + const service = session.getProjectService(); + openFilesForSession([file1], session); + const request: protocol.SyntacticDiagnosticsSyncRequest = { + type: "request", + seq: 1, + command: protocol.CommandTypes.SyntacticDiagnosticsSync, + arguments: { file: file1.path } + }; + const response = session.executeCommandSeq(request).response as protocol.SyntacticDiagnosticsSyncResponse["body"]; + assert.isDefined(response); + assert.equal(response!.length, 1); + assert.equal((response![0] as protocol.Diagnostic).text, expectedErrorMessage); - verifyGetErrRequest({ session, host, files: [file1], skip: [{ semantic: true, suggestion: true }] }); - baselineTsserverLogs("partialSemanticServer", "syntactic diagnostics are returned with no error", session); - }); + const project = service.inferredProjects[0]; + const diagnostics = project.getLanguageService().getSyntacticDiagnostics(file1.path); + assert.isTrue(diagnostics.length === 1); + assert.equal(diagnostics[0].messageText, expectedErrorMessage); - it("should not include auto type reference directives", () => { - const { host, session, file1 } = setup(); - const atTypes: File = { - path: `/node_modules/@types/somemodule/index.d.ts`, - content: "export const something = 10;" - }; - host.ensureFileOrFolder(atTypes); - const service = session.getProjectService(); - openFilesForSession([file1], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [libFile.path, file1.path]); // Should not contain atTypes - }); + verifyGetErrRequest({ session, host, files: [file1], skip: [{ semantic: true, suggestion: true }] }); + baselineTsserverLogs("partialSemanticServer", "syntactic diagnostics are returned with no error", session); + }); - it("should not include referenced files from unopened files", () => { - const file1: File = { - path: `${tscWatch.projectRoot}/a.ts`, - content: `/// + it("should not include auto type reference directives", () => { + const { host, session, file1 } = setup(); + const atTypes: File = { + path: `/node_modules/@types/somemodule/index.d.ts`, + content: "export const something = 10;" + }; + host.ensureFileOrFolder(atTypes); + const service = session.getProjectService(); + openFilesForSession([file1], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + const project = service.inferredProjects[0]; + checkProjectActualFiles(project, [libFile.path, file1.path]); // Should not contain atTypes + }); + + it("should not include referenced files from unopened files", () => { + const file1: File = { + path: `${tscWatch.projectRoot}/a.ts`, + content: `/// /// function fooA() { }` - }; - const file2: File = { - path: `${tscWatch.projectRoot}/b.ts`, - content: `/// + }; + const file2: File = { + path: `${tscWatch.projectRoot}/b.ts`, + content: `/// /// function fooB() { }` - }; - const file3: File = { - path: `${tscWatch.projectRoot}/c.ts`, - content: `function fooC() { }` - }; - const something: File = { - path: `${tscWatch.projectRoot}/node_modules/something/index.d.ts`, - content: "function something() {}" - }; - const configFile: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const host = createServerHost([file1, file2, file3, something, libFile, configFile]); - const session = createSession(host, { serverMode: LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); - const service = session.getProjectService(); - openFilesForSession([file1], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [libFile.path, file1.path]); // no resolve - }); + }; + const file3: File = { + path: `${tscWatch.projectRoot}/c.ts`, + content: `function fooC() { }` + }; + const something: File = { + path: `${tscWatch.projectRoot}/node_modules/something/index.d.ts`, + content: "function something() {}" + }; + const configFile: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const host = createServerHost([file1, file2, file3, something, libFile, configFile]); + const session = createSession(host, { serverMode: LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); + const service = session.getProjectService(); + openFilesForSession([file1], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + const project = service.inferredProjects[0]; + checkProjectActualFiles(project, [libFile.path, file1.path]); // no resolve + }); - it("should not crash when external module name resolution is reused", () => { - const { session, file1, file2, file3 } = setup(); - const service = session.getProjectService(); - openFilesForSession([file1], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [libFile.path, file1.path]); + it("should not crash when external module name resolution is reused", () => { + const { session, file1, file2, file3 } = setup(); + const service = session.getProjectService(); + openFilesForSession([file1], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + const project = service.inferredProjects[0]; + checkProjectActualFiles(project, [libFile.path, file1.path]); - // Close the file that contains non relative external module name and open some file that doesnt have non relative external module import - closeFilesForSession([file1], session); - openFilesForSession([file3], session); - checkProjectActualFiles(project, [libFile.path, file3.path]); + // Close the file that contains non relative external module name and open some file that doesnt have non relative external module import + closeFilesForSession([file1], session); + openFilesForSession([file3], session); + checkProjectActualFiles(project, [libFile.path, file3.path]); - // Open file with non relative external module name - openFilesForSession([file2], session); - checkProjectActualFiles(project, [libFile.path, file2.path, file3.path]); - }); + // Open file with non relative external module name + openFilesForSession([file2], session); + checkProjectActualFiles(project, [libFile.path, file2.path, file3.path]); + }); - it("should not create autoImportProvider or handle package jsons", () => { - const angularFormsDts: File = { - path: "/node_modules/@angular/forms/forms.d.ts", - content: "export declare class PatternValidator {}", - }; - const angularFormsPackageJson: File = { - path: "/node_modules/@angular/forms/package.json", - content: `{ "name": "@angular/forms", "typings": "./forms.d.ts" }`, - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: `{ "compilerOptions": { "module": "commonjs" } }`, - }; - const packageJson: File = { - path: "/package.json", - content: `{ "dependencies": { "@angular/forms": "*", "@angular/core": "*" } }` - }; - const indexTs: File = { - path: "/index.ts", - content: "" - }; - const host = createServerHost([angularFormsDts, angularFormsPackageJson, tsconfig, packageJson, indexTs, libFile]); - const session = createSession(host, { serverMode: LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); - const service = session.getProjectService(); - openFilesForSession([indexTs], session); - const project = service.inferredProjects[0]; - assert.isFalse(project.autoImportProviderHost); - assert.isUndefined(project.getPackageJsonAutoImportProvider()); - assert.deepEqual(project.getPackageJsonsForAutoImport(), emptyArray); - }); + it("should not create autoImportProvider or handle package jsons", () => { + const angularFormsDts: File = { + path: "/node_modules/@angular/forms/forms.d.ts", + content: "export declare class PatternValidator {}", + }; + const angularFormsPackageJson: File = { + path: "/node_modules/@angular/forms/package.json", + content: `{ "name": "@angular/forms", "typings": "./forms.d.ts" }`, + }; + const tsconfig: File = { + path: "/tsconfig.json", + content: `{ "compilerOptions": { "module": "commonjs" } }`, + }; + const packageJson: File = { + path: "/package.json", + content: `{ "dependencies": { "@angular/forms": "*", "@angular/core": "*" } }` + }; + const indexTs: File = { + path: "/index.ts", + content: "" + }; + const host = createServerHost([angularFormsDts, angularFormsPackageJson, tsconfig, packageJson, indexTs, libFile]); + const session = createSession(host, { serverMode: LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); + const service = session.getProjectService(); + openFilesForSession([indexTs], session); + const project = service.inferredProjects[0]; + assert.isFalse(project.autoImportProviderHost); + assert.isUndefined(project.getPackageJsonAutoImportProvider()); + assert.deepEqual(project.getPackageJsonsForAutoImport(), emptyArray); + }); - it("should support go-to-definition on module specifiers", () => { - const { session, file1, file2 } = setup(); - openFilesForSession([file1], session); - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: protocolFileLocationFromSubstring(file1, `"./b"`) - }).response as protocol.DefinitionInfoAndBoundSpan; - assert.isDefined(response); - assert.deepEqual(response.definitions, [{ - file: file2.path, - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - }]); - }); + it("should support go-to-definition on module specifiers", () => { + const { session, file1, file2 } = setup(); + openFilesForSession([file1], session); + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: protocolFileLocationFromSubstring(file1, `"./b"`) + }).response as protocol.DefinitionInfoAndBoundSpan; + assert.isDefined(response); + assert.deepEqual(response.definitions, [{ + file: file2.path, + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + }]); }); +}); } diff --git a/src/testRunner/unittests/tsserver/plugins.ts b/src/testRunner/unittests/tsserver/plugins.ts index 5d46dd203f9db..8c6b094a963ab 100644 --- a/src/testRunner/unittests/tsserver/plugins.ts +++ b/src/testRunner/unittests/tsserver/plugins.ts @@ -1,104 +1,104 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: plugins loading", () => { - const testProtocolCommand = "testProtocolCommand"; - const testProtocolCommandRequest = "testProtocolCommandRequest"; - const testProtocolCommandResponse = "testProtocolCommandResponse"; +describe("unittests:: tsserver:: plugins loading", () => { + const testProtocolCommand = "testProtocolCommand"; + const testProtocolCommandRequest = "testProtocolCommandRequest"; + const testProtocolCommandResponse = "testProtocolCommandResponse"; - function createHostWithPlugin(files: readonly File[]) { - const host = createServerHost(files); - const pluginsLoaded: string[] = []; - const protocolHandlerRequests: [string, string][] = []; - host.require = (_initialPath, moduleName) => { - pluginsLoaded.push(moduleName); - return { - module: () => ({ - create(info: server.PluginCreateInfo) { - info.session?.addProtocolHandler(testProtocolCommand, request => { - protocolHandlerRequests.push([request.command, request.arguments]); - return { - response: testProtocolCommandResponse - }; - }); - return Harness.LanguageService.makeDefaultProxy(info); - } - }), - error: undefined - }; - }; - return { host, pluginsLoaded, protocolHandlerRequests }; - } - - it("With local plugins", () => { - const expectedToLoad = ["@myscoped/plugin", "unscopedPlugin"]; - const notToLoad = ["../myPlugin", "myPlugin/../malicious"]; - const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; - const tsconfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - plugins: [ - ...[...expectedToLoad, ...notToLoad].map(name => ({ name })), - { transform: "some-transform" } - ] + function createHostWithPlugin(files: readonly File[]) { + const host = createServerHost(files); + const pluginsLoaded: string[] = []; + const protocolHandlerRequests: [string, string][] = []; + host.require = (_initialPath, moduleName) => { + pluginsLoaded.push(moduleName); + return { + module: () => ({ + create(info: server.PluginCreateInfo) { + info.session?.addProtocolHandler(testProtocolCommand, request => { + protocolHandlerRequests.push([request.command, request.arguments]); + return { + response: testProtocolCommandResponse + }; + }); + return Harness.LanguageService.makeDefaultProxy(info); } - }) + }), + error: undefined }; - const { host, pluginsLoaded } = createHostWithPlugin([aTs, tsconfig, libFile]); - const service = createProjectService(host); - service.openClientFile(aTs.path); - assert.deepEqual(pluginsLoaded, expectedToLoad); - }); + }; + return { host, pluginsLoaded, protocolHandlerRequests }; + } - it("With global plugins", () => { - const expectedToLoad = ["@myscoped/plugin", "unscopedPlugin"]; - const notToLoad = ["../myPlugin", "myPlugin/../malicious"]; - const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}" - }; - const { host, pluginsLoaded } = createHostWithPlugin([aTs, tsconfig, libFile]); - const service = createProjectService(host, { globalPlugins: [...expectedToLoad, ...notToLoad] }); - service.openClientFile(aTs.path); - assert.deepEqual(pluginsLoaded, expectedToLoad); - }); - - it("With session and custom protocol message", () => { - const pluginName = "some-plugin"; - const expectedToLoad = [pluginName]; - const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; - const tsconfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - plugins: [ - { name: pluginName } - ] - } - }) - }; + it("With local plugins", () => { + const expectedToLoad = ["@myscoped/plugin", "unscopedPlugin"]; + const notToLoad = ["../myPlugin", "myPlugin/../malicious"]; + const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; + const tsconfig: File = { + path: "/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + plugins: [ + ...[...expectedToLoad, ...notToLoad].map(name => ({ name })), + { transform: "some-transform" } + ] + } + }) + }; + const { host, pluginsLoaded } = createHostWithPlugin([aTs, tsconfig, libFile]); + const service = createProjectService(host); + service.openClientFile(aTs.path); + assert.deepEqual(pluginsLoaded, expectedToLoad); + }); - const { host, pluginsLoaded, protocolHandlerRequests } = createHostWithPlugin([aTs, tsconfig, libFile]); - const session = createSession(host); + it("With global plugins", () => { + const expectedToLoad = ["@myscoped/plugin", "unscopedPlugin"]; + const notToLoad = ["../myPlugin", "myPlugin/../malicious"]; + const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; + const tsconfig: File = { + path: "/tsconfig.json", + content: "{}" + }; + const { host, pluginsLoaded } = createHostWithPlugin([aTs, tsconfig, libFile]); + const service = createProjectService(host, { globalPlugins: [...expectedToLoad, ...notToLoad] }); + service.openClientFile(aTs.path); + assert.deepEqual(pluginsLoaded, expectedToLoad); + }); - const service = createProjectService(host, { session }); - service.openClientFile(aTs.path); - assert.deepEqual(pluginsLoaded, expectedToLoad); + it("With session and custom protocol message", () => { + const pluginName = "some-plugin"; + const expectedToLoad = [pluginName]; + const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; + const tsconfig: File = { + path: "/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + plugins: [ + { name: pluginName } + ] + } + }) + }; - const resp = session.executeCommandSeq({ - command: testProtocolCommand, - arguments: testProtocolCommandRequest - }); + const { host, pluginsLoaded, protocolHandlerRequests } = createHostWithPlugin([aTs, tsconfig, libFile]); + const session = createSession(host); - assert.strictEqual(protocolHandlerRequests.length, 1); - const [command, args] = protocolHandlerRequests[0]; - assert.strictEqual(command, testProtocolCommand); - assert.strictEqual(args, testProtocolCommandRequest); + const service = createProjectService(host, { session }); + service.openClientFile(aTs.path); + assert.deepEqual(pluginsLoaded, expectedToLoad); - const expectedResp: server.HandlerResponse = { - response: testProtocolCommandResponse - }; - assert.deepEqual(resp, expectedResp); + const resp = session.executeCommandSeq({ + command: testProtocolCommand, + arguments: testProtocolCommandRequest }); + + assert.strictEqual(protocolHandlerRequests.length, 1); + const [command, args] = protocolHandlerRequests[0]; + assert.strictEqual(command, testProtocolCommand); + assert.strictEqual(args, testProtocolCommandRequest); + + const expectedResp: server.HandlerResponse = { + response: testProtocolCommandResponse + }; + assert.deepEqual(resp, expectedResp); }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/tsserver/projectErrors.ts b/src/testRunner/unittests/tsserver/projectErrors.ts index f721a27cf316f..5ec758b964d5f 100644 --- a/src/testRunner/unittests/tsserver/projectErrors.ts +++ b/src/testRunner/unittests/tsserver/projectErrors.ts @@ -1,891 +1,891 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: Project Errors", () => { - function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: readonly string[]): void { - assert.isTrue(projectFiles !== undefined, "missing project files"); - checkProjectErrorsWorker(projectFiles.projectErrors, expectedErrors); - } - - function checkProjectErrorsWorker(errors: readonly Diagnostic[], expectedErrors: readonly string[]): void { - assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); - if (expectedErrors.length) { - for (let i = 0; i < errors.length; i++) { - const actualMessage = flattenDiagnosticMessageText(errors[i].messageText, "\n"); - const expectedMessage = expectedErrors[i]; - assert.isTrue(actualMessage.indexOf(expectedMessage) === 0, `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); - } +describe("unittests:: tsserver:: Project Errors", () => { + function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: readonly string[]): void { + assert.isTrue(projectFiles !== undefined, "missing project files"); + checkProjectErrorsWorker(projectFiles.projectErrors, expectedErrors); + } + + function checkProjectErrorsWorker(errors: readonly Diagnostic[], expectedErrors: readonly string[]): void { + assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); + if (expectedErrors.length) { + for (let i = 0; i < errors.length; i++) { + const actualMessage = flattenDiagnosticMessageText(errors[i].messageText, "\n"); + const expectedMessage = expectedErrors[i]; + assert.isTrue(actualMessage.indexOf(expectedMessage) === 0, `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); } } + } - function checkDiagnosticsWithLinePos(errors: server.protocol.DiagnosticWithLinePosition[], expectedErrors: string[]) { - assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); - if (expectedErrors.length) { - zipWith(errors, expectedErrors, ({ message: actualMessage }, expectedMessage) => { - assert.isTrue(startsWith(actualMessage, actualMessage), `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); - }); - } + function checkDiagnosticsWithLinePos(errors: server.protocol.DiagnosticWithLinePosition[], expectedErrors: string[]) { + assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); + if (expectedErrors.length) { + zipWith(errors, expectedErrors, ({ message: actualMessage }, expectedMessage) => { + assert.isTrue(startsWith(actualMessage, actualMessage), `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); + }); } + } + + it("external project - diagnostics for missing files", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const file2 = { + path: "/a/b/applib.ts", + content: "" + }; + const host = createServerHost([file1, libFile]); + const session = createSession(host); + const projectService = session.getProjectService(); + const projectFileName = "/a/b/test.csproj"; + const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = { + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName } + }; + + { + projectService.openExternalProject({ + projectFileName, + options: {}, + rootFiles: toExternalFiles([file1.path, file2.path]) + }); - it("external project - diagnostics for missing files", () => { - const file1 = { - path: "/a/b/app.ts", - content: "" - }; - const file2 = { - path: "/a/b/applib.ts", - content: "" - }; - const host = createServerHost([file1, libFile]); - const session = createSession(host); - const projectService = session.getProjectService(); - const projectFileName = "/a/b/test.csproj"; - const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = { - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 2, - arguments: { projectFileName } - }; - - { - projectService.openExternalProject({ - projectFileName, - options: {}, - rootFiles: toExternalFiles([file1.path, file2.path]) - }); - - checkNumberOfProjects(projectService, { externalProjects: 1 }); - const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; - // only file1 exists - expect error - checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]); - } - host.renameFile(file1.path, file2.path); - { - // only file2 exists - expect error - checkNumberOfProjects(projectService, { externalProjects: 1 }); - const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; - checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]); - } - - host.writeFile(file1.path, file1.content); - { - // both files exist - expect no errors - checkNumberOfProjects(projectService, { externalProjects: 1 }); - const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; - checkDiagnosticsWithLinePos(diags, []); - } - }); - - it("configured projects - diagnostics for missing files", () => { - const file1 = { - path: "/a/b/app.ts", - content: "" - }; - const file2 = { - path: "/a/b/applib.ts", - content: "" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) - }; - const host = createServerHost([file1, config, libFile]); - const session = createSession(host); - const projectService = session.getProjectService(); - openFilesForSession([file1], session); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = configuredProjectAt(projectService, 0); - const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = { - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 2, - arguments: { projectFileName: project.getProjectName() } - }; - let diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; + checkNumberOfProjects(projectService, { externalProjects: 1 }); + const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; + // only file1 exists - expect error checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]); + } + host.renameFile(file1.path, file2.path); + { + // only file2 exists - expect error + checkNumberOfProjects(projectService, { externalProjects: 1 }); + const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; + checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]); + } - host.writeFile(file2.path, file2.content); - - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; + host.writeFile(file1.path, file1.content); + { + // both files exist - expect no errors + checkNumberOfProjects(projectService, { externalProjects: 1 }); + const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; checkDiagnosticsWithLinePos(diags, []); - }); - - it("configured projects - diagnostics for corrupted config 1", () => { - const file1 = { - path: "/a/b/app.ts", - content: "" - }; - const file2 = { - path: "/a/b/lib.ts", - content: "" - }; - const correctConfig = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) - }; - const corruptedConfig = { - path: correctConfig.path, - content: correctConfig.content.substr(1) - }; - const host = createServerHost([file1, file2, corruptedConfig]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - { - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; - assert.isTrue(configuredProject !== undefined, "should find configured project"); - checkProjectErrors(configuredProject, []); - const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); - checkProjectErrorsWorker(projectErrors, [ - "'{' expected." - ]); - assert.isNotNull(projectErrors[0].file); - assert.equal(projectErrors[0].file!.fileName, corruptedConfig.path); - } - // fix config and trigger watcher - host.writeFile(correctConfig.path, correctConfig.content); - { - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; - assert.isTrue(configuredProject !== undefined, "should find configured project"); - checkProjectErrors(configuredProject, []); - const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); - checkProjectErrorsWorker(projectErrors, []); - } - }); - - it("configured projects - diagnostics for corrupted config 2", () => { - const file1 = { - path: "/a/b/app.ts", - content: "" - }; - const file2 = { - path: "/a/b/lib.ts", - content: "" - }; - const correctConfig = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) - }; - const corruptedConfig = { - path: correctConfig.path, - content: correctConfig.content.substr(1) - }; - const host = createServerHost([file1, file2, correctConfig]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - { - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; - assert.isTrue(configuredProject !== undefined, "should find configured project"); - checkProjectErrors(configuredProject, []); - const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); - checkProjectErrorsWorker(projectErrors, []); - } - // break config and trigger watcher - host.writeFile(corruptedConfig.path, corruptedConfig.content); - { - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; - assert.isTrue(configuredProject !== undefined, "should find configured project"); - checkProjectErrors(configuredProject, []); - const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); - checkProjectErrorsWorker(projectErrors, [ - "'{' expected." - ]); - assert.isNotNull(projectErrors[0].file); - assert.equal(projectErrors[0].file!.fileName, corruptedConfig.path); - } - }); + } }); - describe("unittests:: tsserver:: Project Errors are reported as appropriate", () => { - it("document is not contained in project", () => { - const file1 = { - path: "/a/b/app.ts", - content: "" - }; - const corruptedConfig = { - path: "/a/b/tsconfig.json", - content: "{" - }; - const host = createServerHost([file1, corruptedConfig]); - const projectService = createProjectService(host); + it("configured projects - diagnostics for missing files", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const file2 = { + path: "/a/b/applib.ts", + content: "" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) + }; + const host = createServerHost([file1, config, libFile]); + const session = createSession(host); + const projectService = session.getProjectService(); + openFilesForSession([file1], session); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = configuredProjectAt(projectService, 0); + const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = { + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName: project.getProjectName() } + }; + let diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; + checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]); + + host.writeFile(file2.path, file2.content); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; + checkDiagnosticsWithLinePos(diags, []); + }); - projectService.openClientFile(file1.path); + it("configured projects - diagnostics for corrupted config 1", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const file2 = { + path: "/a/b/lib.ts", + content: "" + }; + const correctConfig = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) + }; + const corruptedConfig = { + path: correctConfig.path, + content: correctConfig.content.substr(1) + }; + const host = createServerHost([file1, file2, corruptedConfig]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + { projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; + assert.isTrue(configuredProject !== undefined, "should find configured project"); + checkProjectErrors(configuredProject, []); + const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); + checkProjectErrorsWorker(projectErrors, [ + "'{' expected." + ]); + assert.isNotNull(projectErrors[0].file); + assert.equal(projectErrors[0].file!.fileName, corruptedConfig.path); + } + // fix config and trigger watcher + host.writeFile(correctConfig.path, correctConfig.content); + { + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; + assert.isTrue(configuredProject !== undefined, "should find configured project"); + checkProjectErrors(configuredProject, []); + const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); + checkProjectErrorsWorker(projectErrors, []); + } + }); - const project = projectService.findProject(corruptedConfig.path)!; - checkProjectRootFiles(project, [file1.path]); - }); - - describe("when opening new file that doesnt exist on disk yet", () => { - function verifyNonExistentFile(useProjectRoot: boolean) { - const folderPath = "/user/someuser/projects/someFolder"; - const fileInRoot: File = { - path: `/src/somefile.d.ts`, - content: "class c { }" - }; - const fileInProjectRoot: File = { - path: `${folderPath}/src/somefile.d.ts`, - content: "class c { }" - }; - const host = createServerHost([libFile, fileInRoot, fileInProjectRoot]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(), useInferredProjectPerProjectRoot: true }); - - const untitledFile = "untitled:Untitled-1"; - const refPathNotFound1 = "../../../../../../typings/@epic/Core.d.ts"; - const refPathNotFound2 = "./src/somefile.d.ts"; - const fileContent = `/// -/// `; - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { - file: untitledFile, - fileContent, - scriptKindName: "TS", - projectRootPath: useProjectRoot ? folderPath : undefined - } - }); - appendAllScriptInfos(session.getProjectService(), session.logger.logs); - - // Since this is not js project so no typings are queued - host.checkTimeoutQueueLength(0); - verifyGetErrRequest({ session, host, files: [untitledFile] }); - baselineTsserverLogs("projectErrors", `when opening new file that doesnt exist on disk yet ${useProjectRoot ? "with projectRoot" : "without projectRoot"}`, session); - } - - it("has projectRoot", () => { - verifyNonExistentFile(/*useProjectRoot*/ true); - }); - - it("does not have projectRoot", () => { - verifyNonExistentFile(/*useProjectRoot*/ false); - }); - }); + it("configured projects - diagnostics for corrupted config 2", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const file2 = { + path: "/a/b/lib.ts", + content: "" + }; + const correctConfig = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) + }; + const corruptedConfig = { + path: correctConfig.path, + content: correctConfig.content.substr(1) + }; + const host = createServerHost([file1, file2, correctConfig]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + { + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; + assert.isTrue(configuredProject !== undefined, "should find configured project"); + checkProjectErrors(configuredProject, []); + const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); + checkProjectErrorsWorker(projectErrors, []); + } + // break config and trigger watcher + host.writeFile(corruptedConfig.path, corruptedConfig.content); + { + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; + assert.isTrue(configuredProject !== undefined, "should find configured project"); + checkProjectErrors(configuredProject, []); + const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); + checkProjectErrorsWorker(projectErrors, [ + "'{' expected." + ]); + assert.isNotNull(projectErrors[0].file); + assert.equal(projectErrors[0].file!.fileName, corruptedConfig.path); + } + }); +}); + +describe("unittests:: tsserver:: Project Errors are reported as appropriate", () => { + it("document is not contained in project", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const corruptedConfig = { + path: "/a/b/tsconfig.json", + content: "{" + }; + const host = createServerHost([file1, corruptedConfig]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + + const project = projectService.findProject(corruptedConfig.path)!; + checkProjectRootFiles(project, [file1.path]); + }); - it("folder rename updates project structure and reports no errors", () => { - const projectDir = "/a/b/projects/myproject"; - const app: File = { - path: `${projectDir}/bar/app.ts`, - content: "class Bar implements foo.Foo { getFoo() { return ''; } get2() { return 1; } }" - }; - const foo: File = { - path: `${projectDir}/foo/foo.ts`, - content: "declare namespace foo { interface Foo { get2(): number; getFoo(): string; } }" + describe("when opening new file that doesnt exist on disk yet", () => { + function verifyNonExistentFile(useProjectRoot: boolean) { + const folderPath = "/user/someuser/projects/someFolder"; + const fileInRoot: File = { + path: `/src/somefile.d.ts`, + content: "class c { }" }; - const configFile: File = { - path: `${projectDir}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { module: "none", targer: "es5" }, exclude: ["node_modules"] }) + const fileInProjectRoot: File = { + path: `${folderPath}/src/somefile.d.ts`, + content: "class c { }" }; - const host = createServerHost([app, foo, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + const host = createServerHost([libFile, fileInRoot, fileInProjectRoot]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(), useInferredProjectPerProjectRoot: true }); + const untitledFile = "untitled:Untitled-1"; + const refPathNotFound1 = "../../../../../../typings/@epic/Core.d.ts"; + const refPathNotFound2 = "./src/somefile.d.ts"; + const fileContent = `/// +/// `; session.executeCommandSeq({ command: server.CommandNames.Open, - arguments: { file: app.path, } + arguments: { + file: untitledFile, + fileContent, + scriptKindName: "TS", + projectRootPath: useProjectRoot ? folderPath : undefined + } }); - verifyGetErrRequest({ session, host, files: [app] }); + appendAllScriptInfos(session.getProjectService(), session.logger.logs); - host.renameFolder(`${projectDir}/foo`, `${projectDir}/foo2`); - host.runQueuedTimeoutCallbacks(); - host.runQueuedTimeoutCallbacks(); - verifyGetErrRequest({ session, host, files: [app] }); - baselineTsserverLogs("projectErrors", `folder rename updates project structure and reports no errors`, session); + // Since this is not js project so no typings are queued + host.checkTimeoutQueueLength(0); + verifyGetErrRequest({ session, host, files: [untitledFile] }); + baselineTsserverLogs("projectErrors", `when opening new file that doesnt exist on disk yet ${useProjectRoot ? "with projectRoot" : "without projectRoot"}`, session); + } + + it("has projectRoot", () => { + verifyNonExistentFile(/*useProjectRoot*/ true); }); - it("Getting errors before opening file", () => { - const file: File = { - path: "/a/b/project/file.ts", - content: "let x: number = false;" - }; - const host = createServerHost([file, libFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [file.path] - } - }); + it("does not have projectRoot", () => { + verifyNonExistentFile(/*useProjectRoot*/ false); + }); + }); - host.checkTimeoutQueueLengthAndRun(1); - baselineTsserverLogs("projectErrors", "getting errors before opening file", session); + it("folder rename updates project structure and reports no errors", () => { + const projectDir = "/a/b/projects/myproject"; + const app: File = { + path: `${projectDir}/bar/app.ts`, + content: "class Bar implements foo.Foo { getFoo() { return ''; } get2() { return 1; } }" + }; + const foo: File = { + path: `${projectDir}/foo/foo.ts`, + content: "declare namespace foo { interface Foo { get2(): number; getFoo(): string; } }" + }; + const configFile: File = { + path: `${projectDir}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { module: "none", targer: "es5" }, exclude: ["node_modules"] }) + }; + const host = createServerHost([app, foo, configFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { file: app.path, } }); + verifyGetErrRequest({ session, host, files: [app] }); - it("Reports errors correctly when file referenced by inferred project root, is opened right after closing the root file", () => { - const app: File = { - path: `${tscWatch.projectRoot}/src/client/app.js`, - content: "" - }; - const serverUtilities: File = { - path: `${tscWatch.projectRoot}/src/server/utilities.js`, - content: `function getHostName() { return "hello"; } export { getHostName };` - }; - const backendTest: File = { - path: `${tscWatch.projectRoot}/test/backend/index.js`, - content: `import { getHostName } from '../../src/server/utilities';export default getHostName;` - }; - const files = [libFile, app, serverUtilities, backendTest]; - const host = createServerHost(files); - const session = createSession(host, { useInferredProjectPerProjectRoot: true, canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([{ file: app, projectRootPath: tscWatch.projectRoot }], session); - openFilesForSession([{ file: backendTest, projectRootPath: tscWatch.projectRoot }], session); - verifyGetErrRequest({ session, host, files: [backendTest.path, app.path] }); - closeFilesForSession([backendTest], session); - openFilesForSession([{ file: serverUtilities.path, projectRootPath: tscWatch.projectRoot }], session); - verifyGetErrRequest({ session, host, files: [serverUtilities.path, app.path] }); - baselineTsserverLogs("projectErrors", `reports errors correctly when file referenced by inferred project root, is opened right after closing the root file`, session); + host.renameFolder(`${projectDir}/foo`, `${projectDir}/foo2`); + host.runQueuedTimeoutCallbacks(); + host.runQueuedTimeoutCallbacks(); + verifyGetErrRequest({ session, host, files: [app] }); + baselineTsserverLogs("projectErrors", `folder rename updates project structure and reports no errors`, session); + }); + + it("Getting errors before opening file", () => { + const file: File = { + path: "/a/b/project/file.ts", + content: "let x: number = false;" + }; + const host = createServerHost([file, libFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [file.path] + } }); - it("Correct errors when resolution resolves to file that has same ambient module and is also module", () => { - const projectRootPath = "/users/username/projects/myproject"; - const aFile: File = { - path: `${projectRootPath}/src/a.ts`, - content: `import * as myModule from "@custom/plugin"; + host.checkTimeoutQueueLengthAndRun(1); + baselineTsserverLogs("projectErrors", "getting errors before opening file", session); + }); + + it("Reports errors correctly when file referenced by inferred project root, is opened right after closing the root file", () => { + const app: File = { + path: `${tscWatch.projectRoot}/src/client/app.js`, + content: "" + }; + const serverUtilities: File = { + path: `${tscWatch.projectRoot}/src/server/utilities.js`, + content: `function getHostName() { return "hello"; } export { getHostName };` + }; + const backendTest: File = { + path: `${tscWatch.projectRoot}/test/backend/index.js`, + content: `import { getHostName } from '../../src/server/utilities';export default getHostName;` + }; + const files = [libFile, app, serverUtilities, backendTest]; + const host = createServerHost(files); + const session = createSession(host, { useInferredProjectPerProjectRoot: true, canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([{ file: app, projectRootPath: tscWatch.projectRoot }], session); + openFilesForSession([{ file: backendTest, projectRootPath: tscWatch.projectRoot }], session); + verifyGetErrRequest({ session, host, files: [backendTest.path, app.path] }); + closeFilesForSession([backendTest], session); + openFilesForSession([{ file: serverUtilities.path, projectRootPath: tscWatch.projectRoot }], session); + verifyGetErrRequest({ session, host, files: [serverUtilities.path, app.path] }); + baselineTsserverLogs("projectErrors", `reports errors correctly when file referenced by inferred project root, is opened right after closing the root file`, session); + }); + + it("Correct errors when resolution resolves to file that has same ambient module and is also module", () => { + const projectRootPath = "/users/username/projects/myproject"; + const aFile: File = { + path: `${projectRootPath}/src/a.ts`, + content: `import * as myModule from "@custom/plugin"; function foo() { // hello }` - }; - const config: File = { - path: `${projectRootPath}/tsconfig.json`, - content: JSON.stringify({ include: ["src"] }) - }; - const plugin: File = { - path: `${projectRootPath}/node_modules/@custom/plugin/index.d.ts`, - content: `import './proposed'; + }; + const config: File = { + path: `${projectRootPath}/tsconfig.json`, + content: JSON.stringify({ include: ["src"] }) + }; + const plugin: File = { + path: `${projectRootPath}/node_modules/@custom/plugin/index.d.ts`, + content: `import './proposed'; declare module '@custom/plugin' { export const version: string; }` - }; - const pluginProposed: File = { - path: `${projectRootPath}/node_modules/@custom/plugin/proposed.d.ts`, - content: `declare module '@custom/plugin' { + }; + const pluginProposed: File = { + path: `${projectRootPath}/node_modules/@custom/plugin/proposed.d.ts`, + content: `declare module '@custom/plugin' { export const bar = 10; }` - }; - const files = [libFile, aFile, config, plugin, pluginProposed]; - const host = createServerHost(files); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([aFile], session); - - checkErrors(); - - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: aFile.path, - line: 3, - offset: 8, - endLine: 3, - endOffset: 8, - insertString: "o" - } - }); - checkErrors(); - baselineTsserverLogs("projectErrors", `correct errors when resolution resolves to file that has same ambient module and is also module`, session); - - function checkErrors() { - host.checkTimeoutQueueLength(0); - verifyGetErrRequest({ session, host, files: [aFile] }); + }; + const files = [libFile, aFile, config, plugin, pluginProposed]; + const host = createServerHost(files); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([aFile], session); + + checkErrors(); + + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: aFile.path, + line: 3, + offset: 8, + endLine: 3, + endOffset: 8, + insertString: "o" } }); + checkErrors(); + baselineTsserverLogs("projectErrors", `correct errors when resolution resolves to file that has same ambient module and is also module`, session); - describe("when semantic error returns includes global error", () => { - const file: File = { - path: `${tscWatch.projectRoot}/ui.ts`, - content: `const x = async (_action: string) => { + function checkErrors() { + host.checkTimeoutQueueLength(0); + verifyGetErrRequest({ session, host, files: [aFile] }); + } + }); + + describe("when semantic error returns includes global error", () => { + const file: File = { + path: `${tscWatch.projectRoot}/ui.ts`, + content: `const x = async (_action: string) => { };` - }; - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - verifyGetErrScenario({ - scenario: "projectErrors", - subScenario: "when semantic error returns includes global error", - allFiles: () => [libFile, file, config], - openFiles: () => [file], - getErrRequest: () => [file], - getErrForProjectRequest: () => [{ project: file, files: [file] }], - syncDiagnostics: () => [{ file, project: config }], - }); + }; + const config: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + verifyGetErrScenario({ + scenario: "projectErrors", + subScenario: "when semantic error returns includes global error", + allFiles: () => [libFile, file, config], + openFiles: () => [file], + getErrRequest: () => [file], + getErrForProjectRequest: () => [{ project: file, files: [file] }], + syncDiagnostics: () => [{ file, project: config }], }); }); - - describe("unittests:: tsserver:: Project Errors for Configure file diagnostics events", () => { - it("are generated when the config file has errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ +}); + +describe("unittests:: tsserver:: Project Errors for Configure file diagnostics events", () => { + it("are generated when the config file has errors", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "foo": "bar", "allowJS": true } }` - }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([file], session); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file has errors", session); - }); + }; + const host = createServerHost([file, libFile, configFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([file], session); + baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file has errors", session); + }); - it("are generated when the config file doesn't have errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("are generated when the config file doesn't have errors", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {} }` - }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([file], session); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file doesnt have errors", session); - }); + }; + const host = createServerHost([file, libFile, configFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([file], session); + baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file doesnt have errors", session); + }); - it("are generated when the config file changes", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: `{ + it("are generated when the config file changes", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {} }` - }; + }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([file], session); + const host = createServerHost([file, libFile, configFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([file], session); - configFile.content = `{ + configFile.content = `{ "compilerOptions": { "haha": 123 } }`; - host.writeFile(configFile.path, configFile.content); - host.runQueuedTimeoutCallbacks(); + host.writeFile(configFile.path, configFile.content); + host.runQueuedTimeoutCallbacks(); - configFile.content = `{ + configFile.content = `{ "compilerOptions": {} }`; - host.writeFile(configFile.path, configFile.content); - host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file changes", session); - }); + host.writeFile(configFile.path, configFile.content); + host.runQueuedTimeoutCallbacks(); + baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file changes", session); + }); - it("are not generated when the config file does not include file opened and config file has errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const file2: File = { - path: "/a/b/test.ts", - content: "let x = 10" - }; - const file3: File = { - path: "/a/b/test2.ts", - content: "let xy = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("are not generated when the config file does not include file opened and config file has errors", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const file2: File = { + path: "/a/b/test.ts", + content: "let x = 10" + }; + const file3: File = { + path: "/a/b/test2.ts", + content: "let xy = 10" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "foo": "bar", "allowJS": true }, "files": ["app.ts"] }` - }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([file2], session); - openFilesForSession([file], session); - // We generate only if project is created when opening file from the project - openFilesForSession([file3], session); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file does not include file opened and config file has errors", session); - }); + }; + const host = createServerHost([file, libFile, configFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([file2], session); + openFilesForSession([file], session); + // We generate only if project is created when opening file from the project + openFilesForSession([file3], session); + baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file does not include file opened and config file has errors", session); + }); - it("are not generated when the config file has errors but suppressDiagnosticEvents is true", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("are not generated when the config file has errors but suppressDiagnosticEvents is true", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "foo": "bar", "allowJS": true } }` - }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, suppressDiagnosticEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([file], session); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file has errors but suppressDiagnosticEvents is true", session); - }); + }; + const host = createServerHost([file, libFile, configFile]); + const session = createSession(host, { canUseEvents: true, suppressDiagnosticEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([file], session); + baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file has errors but suppressDiagnosticEvents is true", session); + }); - it("are not generated when the config file does not include file opened and doesnt contain any errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const file2: File = { - path: "/a/b/test.ts", - content: "let x = 10" - }; - const file3: File = { - path: "/a/b/test2.ts", - content: "let xy = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("are not generated when the config file does not include file opened and doesnt contain any errors", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const file2: File = { + path: "/a/b/test.ts", + content: "let x = 10" + }; + const file3: File = { + path: "/a/b/test2.ts", + content: "let xy = 10" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "files": ["app.ts"] }` - }; - - const host = createServerHost([file, file2, file3, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([file2], session); - openFilesForSession([file], session); - // We generate only if project is created when opening file from the project - openFilesForSession([file3], session); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file does not include file opened and doesnt contain any errors", session); - }); + }; + + const host = createServerHost([file, file2, file3, libFile, configFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([file2], session); + openFilesForSession([file], session); + // We generate only if project is created when opening file from the project + openFilesForSession([file3], session); + baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file does not include file opened and doesnt contain any errors", session); + }); - it("contains the project reference errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const noSuchTsconfig = "no-such-tsconfig.json"; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("contains the project reference errors", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const noSuchTsconfig = "no-such-tsconfig.json"; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "files": ["app.ts"], "references": [{"path":"./${noSuchTsconfig}"}] }` - }; + }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([file], session); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events contains the project reference errors", session); - }); + const host = createServerHost([file, libFile, configFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([file], session); + baselineTsserverLogs("projectErrors", "configFileDiagnostic events contains the project reference errors", session); + }); +}); + +describe("unittests:: tsserver:: Project Errors dont include overwrite emit error", () => { + it("for inferred project", () => { + const f1 = { + path: "/a/b/f1.js", + content: "function test1() { }" + }; + const host = createServerHost([f1, libFile]); + const session = createSession(host); + openFilesForSession([f1], session); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const projectName = projectService.inferredProjects[0].getProjectName(); + + const diags = session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName: projectName } + } as server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly protocol.DiagnosticWithLinePosition[]; + assert.isTrue(diags.length === 0); + + session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsForInferredProjects, + seq: 3, + arguments: { options: { module: ModuleKind.CommonJS } } + } as server.protocol.SetCompilerOptionsForInferredProjectsRequest); + const diagsAfterUpdate = session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 4, + arguments: { projectFileName: projectName } + } as server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly protocol.DiagnosticWithLinePosition[]; + assert.isTrue(diagsAfterUpdate.length === 0); }); - describe("unittests:: tsserver:: Project Errors dont include overwrite emit error", () => { - it("for inferred project", () => { - const f1 = { - path: "/a/b/f1.js", - content: "function test1() { }" - }; - const host = createServerHost([f1, libFile]); - const session = createSession(host); - openFilesForSession([f1], session); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const projectName = projectService.inferredProjects[0].getProjectName(); - - const diags = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 2, - arguments: { projectFileName: projectName } - } as server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly protocol.DiagnosticWithLinePosition[]; - assert.isTrue(diags.length === 0); - - session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsForInferredProjects, - seq: 3, - arguments: { options: { module: ModuleKind.CommonJS } } - } as server.protocol.SetCompilerOptionsForInferredProjectsRequest); - const diagsAfterUpdate = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 4, - arguments: { projectFileName: projectName } - } as server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly protocol.DiagnosticWithLinePosition[]; - assert.isTrue(diagsAfterUpdate.length === 0); - }); - - it("for external project", () => { - const f1 = { - path: "/a/b/f1.js", - content: "function test1() { }" - }; - const host = createServerHost([f1, libFile]); - const session = createSession(host); - const projectService = session.getProjectService(); - const projectFileName = "/a/b/project.csproj"; - const externalFiles = toExternalFiles([f1.path]); - projectService.openExternalProject({ + it("for external project", () => { + const f1 = { + path: "/a/b/f1.js", + content: "function test1() { }" + }; + const host = createServerHost([f1, libFile]); + const session = createSession(host); + const projectService = session.getProjectService(); + const projectFileName = "/a/b/project.csproj"; + const externalFiles = toExternalFiles([f1.path]); + projectService.openExternalProject({ + projectFileName, + rootFiles: externalFiles, + options: {} + } as protocol.ExternalProject); + + checkNumberOfProjects(projectService, { externalProjects: 1 }); + + const diags = session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName } + } as server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly server.protocol.DiagnosticWithLinePosition[]; + assert.isTrue(diags.length === 0); + + session.executeCommand({ + type: "request", + command: server.CommandNames.OpenExternalProject, + seq: 3, + arguments: { projectFileName, rootFiles: externalFiles, - options: {} - } as protocol.ExternalProject); - - checkNumberOfProjects(projectService, { externalProjects: 1 }); - - const diags = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 2, - arguments: { projectFileName } - } as server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly server.protocol.DiagnosticWithLinePosition[]; - assert.isTrue(diags.length === 0); - - session.executeCommand({ - type: "request", - command: server.CommandNames.OpenExternalProject, - seq: 3, - arguments: { - projectFileName, - rootFiles: externalFiles, - options: { module: ModuleKind.CommonJS } - } - } as server.protocol.OpenExternalProjectRequest); - const diagsAfterUpdate = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 4, - arguments: { projectFileName } - } as server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly server.protocol.DiagnosticWithLinePosition[]; - assert.isTrue(diagsAfterUpdate.length === 0); - }); + options: { module: ModuleKind.CommonJS } + } + } as server.protocol.OpenExternalProjectRequest); + const diagsAfterUpdate = session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 4, + arguments: { projectFileName } + } as server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly server.protocol.DiagnosticWithLinePosition[]; + assert.isTrue(diagsAfterUpdate.length === 0); }); - - describe("unittests:: tsserver:: Project Errors reports Options Diagnostic locations correctly with changes in configFile contents", () => { - it("when options change", () => { - const file = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFileContentBeforeComment = `{`; - const configFileContentComment = ` +}); + +describe("unittests:: tsserver:: Project Errors reports Options Diagnostic locations correctly with changes in configFile contents", () => { + it("when options change", () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFileContentBeforeComment = `{`; + const configFileContentComment = ` // comment`; - const configFileContentAfterComment = ` + const configFileContentAfterComment = ` "compilerOptions": { "inlineSourceMap": true, "mapRoot": "./" } }`; - const configFileContentWithComment = configFileContentBeforeComment + configFileContentComment + configFileContentAfterComment; - const configFileContentWithoutCommentLine = configFileContentBeforeComment + configFileContentAfterComment; - - const configFile = { - path: "/a/b/tsconfig.json", - content: configFileContentWithComment - }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host); - openFilesForSession([file], session); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const projectName = configuredProjectAt(projectService, 0).getProjectName(); - - const diags = session.executeCommand({ - type: "request", - command: server.CommandNames.SemanticDiagnosticsSync, - seq: 2, - arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } - } as server.protocol.SemanticDiagnosticsSyncRequest).response as readonly server.protocol.DiagnosticWithLinePosition[]; - assert.isTrue(diags.length === 3); - - configFile.content = configFileContentWithoutCommentLine; - host.writeFile(configFile.path, configFile.content); - - const diagsAfterEdit = session.executeCommand({ - type: "request", - command: server.CommandNames.SemanticDiagnosticsSync, - seq: 2, - arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } - } as server.protocol.SemanticDiagnosticsSyncRequest).response as readonly server.protocol.DiagnosticWithLinePosition[]; - assert.isTrue(diagsAfterEdit.length === 3); - - verifyDiagnostic(diags[0], diagsAfterEdit[0]); - verifyDiagnostic(diags[1], diagsAfterEdit[1]); - verifyDiagnostic(diags[2], diagsAfterEdit[2]); - - function verifyDiagnostic(beforeEditDiag: server.protocol.DiagnosticWithLinePosition, afterEditDiag: server.protocol.DiagnosticWithLinePosition) { - assert.equal(beforeEditDiag.message, afterEditDiag.message); - assert.equal(beforeEditDiag.code, afterEditDiag.code); - assert.equal(beforeEditDiag.category, afterEditDiag.category); - assert.equal(beforeEditDiag.startLocation.line, afterEditDiag.startLocation.line + 1); - assert.equal(beforeEditDiag.startLocation.offset, afterEditDiag.startLocation.offset); - assert.equal(beforeEditDiag.endLocation.line, afterEditDiag.endLocation.line + 1); - assert.equal(beforeEditDiag.endLocation.offset, afterEditDiag.endLocation.offset); - } - }); + const configFileContentWithComment = configFileContentBeforeComment + configFileContentComment + configFileContentAfterComment; + const configFileContentWithoutCommentLine = configFileContentBeforeComment + configFileContentAfterComment; + + const configFile = { + path: "/a/b/tsconfig.json", + content: configFileContentWithComment + }; + const host = createServerHost([file, libFile, configFile]); + const session = createSession(host); + openFilesForSession([file], session); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const projectName = configuredProjectAt(projectService, 0).getProjectName(); + + const diags = session.executeCommand({ + type: "request", + command: server.CommandNames.SemanticDiagnosticsSync, + seq: 2, + arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } + } as server.protocol.SemanticDiagnosticsSyncRequest).response as readonly server.protocol.DiagnosticWithLinePosition[]; + assert.isTrue(diags.length === 3); + + configFile.content = configFileContentWithoutCommentLine; + host.writeFile(configFile.path, configFile.content); + + const diagsAfterEdit = session.executeCommand({ + type: "request", + command: server.CommandNames.SemanticDiagnosticsSync, + seq: 2, + arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } + } as server.protocol.SemanticDiagnosticsSyncRequest).response as readonly server.protocol.DiagnosticWithLinePosition[]; + assert.isTrue(diagsAfterEdit.length === 3); + + verifyDiagnostic(diags[0], diagsAfterEdit[0]); + verifyDiagnostic(diags[1], diagsAfterEdit[1]); + verifyDiagnostic(diags[2], diagsAfterEdit[2]); + + function verifyDiagnostic(beforeEditDiag: server.protocol.DiagnosticWithLinePosition, afterEditDiag: server.protocol.DiagnosticWithLinePosition) { + assert.equal(beforeEditDiag.message, afterEditDiag.message); + assert.equal(beforeEditDiag.code, afterEditDiag.code); + assert.equal(beforeEditDiag.category, afterEditDiag.category); + assert.equal(beforeEditDiag.startLocation.line, afterEditDiag.startLocation.line + 1); + assert.equal(beforeEditDiag.startLocation.offset, afterEditDiag.startLocation.offset); + assert.equal(beforeEditDiag.endLocation.line, afterEditDiag.endLocation.line + 1); + assert.equal(beforeEditDiag.endLocation.offset, afterEditDiag.endLocation.offset); + } }); +}); - describe("unittests:: tsserver:: Project Errors with config file change", () => { - it("Updates diagnostics when '--noUnusedLabels' changes", () => { - const aTs: File = { path: "/a.ts", content: "label: while (1) {}" }; - const options = (allowUnusedLabels: boolean) => `{ "compilerOptions": { "allowUnusedLabels": ${allowUnusedLabels} } }`; - const tsconfig: File = { path: "/tsconfig.json", content: options(/*allowUnusedLabels*/ true) }; - - const host = createServerHost([aTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs], session); - - host.modifyFile(tsconfig.path, options(/*allowUnusedLabels*/ false)); - host.runQueuedTimeoutCallbacks(); - - const response = executeSessionRequest(session, protocol.CommandTypes.SemanticDiagnosticsSync, { file: aTs.path }) as protocol.Diagnostic[] | undefined; - assert.deepEqual(response, [ - { - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 + "label".length }, - text: "Unused label.", - category: "error", - code: Diagnostics.Unused_label.code, - relatedInformation: undefined, - reportsUnnecessary: true, - reportsDeprecated: undefined, - source: undefined, - }, - ]); - }); +describe("unittests:: tsserver:: Project Errors with config file change", () => { + it("Updates diagnostics when '--noUnusedLabels' changes", () => { + const aTs: File = { path: "/a.ts", content: "label: while (1) {}" }; + const options = (allowUnusedLabels: boolean) => `{ "compilerOptions": { "allowUnusedLabels": ${allowUnusedLabels} } }`; + const tsconfig: File = { path: "/tsconfig.json", content: options(/*allowUnusedLabels*/ true) }; + + const host = createServerHost([aTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs], session); + + host.modifyFile(tsconfig.path, options(/*allowUnusedLabels*/ false)); + host.runQueuedTimeoutCallbacks(); + + const response = executeSessionRequest(session, protocol.CommandTypes.SemanticDiagnosticsSync, { file: aTs.path }) as protocol.Diagnostic[] | undefined; + assert.deepEqual(response, [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 + "label".length }, + text: "Unused label.", + category: "error", + code: Diagnostics.Unused_label.code, + relatedInformation: undefined, + reportsUnnecessary: true, + reportsDeprecated: undefined, + source: undefined, + }, + ]); }); +}); - describe("unittests:: tsserver:: Project Errors with resolveJsonModule", () => { - function createSessionForTest({ include }: { include: readonly string[]; }) { - const test: File = { - path: `${tscWatch.projectRoot}/src/test.ts`, - content: `import * as blabla from "./blabla.json"; +describe("unittests:: tsserver:: Project Errors with resolveJsonModule", () => { + function createSessionForTest({ include }: { include: readonly string[]; }) { + const test: File = { + path: `${tscWatch.projectRoot}/src/test.ts`, + content: `import * as blabla from "./blabla.json"; declare var console: any; console.log(blabla);` - }; - const blabla: File = { - path: `${tscWatch.projectRoot}/src/blabla.json`, - content: "{}" - }; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - resolveJsonModule: true, - composite: true - }, - include - }) - }; - - const host = createServerHost([test, blabla, libFile, tsconfig]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([test], session); - return { host, session, test, blabla, tsconfig }; - } - - it("should not report incorrect error when json is root file found by tsconfig", () => { - const { host, session, test } = createSessionForTest({ - include: ["./src/*.ts", "./src/*.json"] - }); - verifyGetErrRequest({ session, host, files: [test] }); - baselineTsserverLogs("projectErrors", `should not report incorrect error when json is root file found by tsconfig`, session); + }; + const blabla: File = { + path: `${tscWatch.projectRoot}/src/blabla.json`, + content: "{}" + }; + const tsconfig: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + resolveJsonModule: true, + composite: true + }, + include + }) + }; + + const host = createServerHost([test, blabla, libFile, tsconfig]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([test], session); + return { host, session, test, blabla, tsconfig }; + } + + it("should not report incorrect error when json is root file found by tsconfig", () => { + const { host, session, test } = createSessionForTest({ + include: ["./src/*.ts", "./src/*.json"] }); + verifyGetErrRequest({ session, host, files: [test] }); + baselineTsserverLogs("projectErrors", `should not report incorrect error when json is root file found by tsconfig`, session); + }); - it("should report error when json is not root file found by tsconfig", () => { - const { host, session, test } = createSessionForTest({ - include: ["./src/*.ts"] - }); - verifyGetErrRequest({ session, host, files: [test] }); - baselineTsserverLogs("projectErrors", `should report error when json is not root file found by tsconfig`, session); + it("should report error when json is not root file found by tsconfig", () => { + const { host, session, test } = createSessionForTest({ + include: ["./src/*.ts"] }); + verifyGetErrRequest({ session, host, files: [test] }); + baselineTsserverLogs("projectErrors", `should report error when json is not root file found by tsconfig`, session); }); - - describe("unittests:: tsserver:: Project Errors with npm install when", () => { - function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) { - const main: File = { - path: `${tscWatch.projectRoot}/src/main.ts`, - content: "import * as _a from '@angular/core';" - }; - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - // Move things from staging to node_modules without triggering watch - const moduleFile: File = { - path: `${tscWatch.projectRoot}/node_modules/@angular/core/index.d.ts`, - content: `export const y = 10;` - }; - const projectFiles = [main, libFile, config]; - const host = createServerHost(projectFiles); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([{ file: main, projectRootPath: tscWatch.projectRoot }], session); - verifyGetErrRequest({ session, host, files: [main] }); - - let npmInstallComplete = false; - - // Simulate npm install - let filesAndFoldersToAdd: (File | Folder)[] = [ - { path: `${tscWatch.projectRoot}/node_modules` }, // This should queue update - { path: `${tscWatch.projectRoot}/node_modules/.staging` }, - { path: `${tscWatch.projectRoot}/node_modules/.staging/@babel` }, - { path: `${tscWatch.projectRoot}/node_modules/.staging/@babel/helper-plugin-utils-a06c629f` }, - { path: `${tscWatch.projectRoot}/node_modules/.staging/core-js-db53158d` }, - ]; - verifyWhileNpmInstall(3); - - filesAndFoldersToAdd = [ - { path: `${tscWatch.projectRoot}/node_modules/.staging/@angular/platform-browser-dynamic-5efaaa1a` }, - { path: `${tscWatch.projectRoot}/node_modules/.staging/@angular/cli-c1e44b05/models/analytics.d.ts`, content: `export const x = 10;` }, - { path: `${tscWatch.projectRoot}/node_modules/.staging/@angular/core-0963aebf/index.d.ts`, content: `export const y = 10;` }, - ]; - // Since we added/removed in .staging no timeout - verifyWhileNpmInstall(0); - - filesAndFoldersToAdd = []; - host.ensureFileOrFolder(moduleFile, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true, /*ignoreParentWatch*/ true); - // Since we added/removed in .staging no timeout - verifyWhileNpmInstall(0); - - // Remove staging folder to remove errors - host.deleteFolder(`${tscWatch.projectRoot}/node_modules/.staging`, /*recursive*/ true); - npmInstallComplete = true; - projectFiles.push(moduleFile); - // Additional watch for watching script infos from node_modules - verifyWhileNpmInstall(3); - - baselineTsserverLogs("projectErrors", `npm install when timeout occurs ${timeoutDuringPartialInstallation ? "inbetween" : "after"} installation`, session); - - function verifyWhileNpmInstall(timeouts: number) { - filesAndFoldersToAdd.forEach(f => host.ensureFileOrFolder(f)); - if (npmInstallComplete || timeoutDuringPartialInstallation) { - host.checkTimeoutQueueLengthAndRun(timeouts); // Invalidation of failed lookups - if (timeouts) { - host.checkTimeoutQueueLengthAndRun(timeouts - 1); // Actual update - } - } - else { - host.checkTimeoutQueueLength(timeouts ? 3 : 2); +}); + +describe("unittests:: tsserver:: Project Errors with npm install when", () => { + function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) { + const main: File = { + path: `${tscWatch.projectRoot}/src/main.ts`, + content: "import * as _a from '@angular/core';" + }; + const config: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + // Move things from staging to node_modules without triggering watch + const moduleFile: File = { + path: `${tscWatch.projectRoot}/node_modules/@angular/core/index.d.ts`, + content: `export const y = 10;` + }; + const projectFiles = [main, libFile, config]; + const host = createServerHost(projectFiles); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([{ file: main, projectRootPath: tscWatch.projectRoot }], session); + verifyGetErrRequest({ session, host, files: [main] }); + + let npmInstallComplete = false; + + // Simulate npm install + let filesAndFoldersToAdd: (File | Folder)[] = [ + { path: `${tscWatch.projectRoot}/node_modules` }, // This should queue update + { path: `${tscWatch.projectRoot}/node_modules/.staging` }, + { path: `${tscWatch.projectRoot}/node_modules/.staging/@babel` }, + { path: `${tscWatch.projectRoot}/node_modules/.staging/@babel/helper-plugin-utils-a06c629f` }, + { path: `${tscWatch.projectRoot}/node_modules/.staging/core-js-db53158d` }, + ]; + verifyWhileNpmInstall(3); + + filesAndFoldersToAdd = [ + { path: `${tscWatch.projectRoot}/node_modules/.staging/@angular/platform-browser-dynamic-5efaaa1a` }, + { path: `${tscWatch.projectRoot}/node_modules/.staging/@angular/cli-c1e44b05/models/analytics.d.ts`, content: `export const x = 10;` }, + { path: `${tscWatch.projectRoot}/node_modules/.staging/@angular/core-0963aebf/index.d.ts`, content: `export const y = 10;` }, + ]; + // Since we added/removed in .staging no timeout + verifyWhileNpmInstall(0); + + filesAndFoldersToAdd = []; + host.ensureFileOrFolder(moduleFile, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true, /*ignoreParentWatch*/ true); + // Since we added/removed in .staging no timeout + verifyWhileNpmInstall(0); + + // Remove staging folder to remove errors + host.deleteFolder(`${tscWatch.projectRoot}/node_modules/.staging`, /*recursive*/ true); + npmInstallComplete = true; + projectFiles.push(moduleFile); + // Additional watch for watching script infos from node_modules + verifyWhileNpmInstall(3); + + baselineTsserverLogs("projectErrors", `npm install when timeout occurs ${timeoutDuringPartialInstallation ? "inbetween" : "after"} installation`, session); + + function verifyWhileNpmInstall(timeouts: number) { + filesAndFoldersToAdd.forEach(f => host.ensureFileOrFolder(f)); + if (npmInstallComplete || timeoutDuringPartialInstallation) { + host.checkTimeoutQueueLengthAndRun(timeouts); // Invalidation of failed lookups + if (timeouts) { + host.checkTimeoutQueueLengthAndRun(timeouts - 1); // Actual update } - verifyGetErrRequest({ session, host, files: [main], existingTimeouts: !npmInstallComplete && !timeoutDuringPartialInstallation ? timeouts ? 3 : 2 : undefined }); } + else { + host.checkTimeoutQueueLength(timeouts ? 3 : 2); + } + verifyGetErrRequest({ session, host, files: [main], existingTimeouts: !npmInstallComplete && !timeoutDuringPartialInstallation ? timeouts ? 3 : 2 : undefined }); } + } - it("timeouts occur inbetween installation", () => { - verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true); - }); + it("timeouts occur inbetween installation", () => { + verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true); + }); - it("timeout occurs after installation", () => { - verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false); - }); + it("timeout occurs after installation", () => { + verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false); }); +}); } diff --git a/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts b/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts index 97132be83b75d..8ce9e258dd68e 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts @@ -1,95 +1,95 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: with project references and compile on save", () => { - const dependecyLocation = `${tscWatch.projectRoot}/dependency`; - const usageLocation = `${tscWatch.projectRoot}/usage`; - const dependencyTs: File = { - path: `${dependecyLocation}/fns.ts`, - content: `export function fn1() { } +describe("unittests:: tsserver:: with project references and compile on save", () => { + const dependecyLocation = `${tscWatch.projectRoot}/dependency`; + const usageLocation = `${tscWatch.projectRoot}/usage`; + const dependencyTs: File = { + path: `${dependecyLocation}/fns.ts`, + content: `export function fn1() { } export function fn2() { } ` - }; - const dependencyConfig: File = { - path: `${dependecyLocation}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, declarationDir: "../decls" }, - compileOnSave: true - }) - }; - const usageTs: File = { - path: `${usageLocation}/usage.ts`, - content: `import { + }; + const dependencyConfig: File = { + path: `${dependecyLocation}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, declarationDir: "../decls" }, + compileOnSave: true + }) + }; + const usageTs: File = { + path: `${usageLocation}/usage.ts`, + content: `import { fn1, fn2, } from '../decls/fns' fn1(); fn2(); ` - }; - const usageConfig: File = { - path: `${usageLocation}/tsconfig.json`, - content: JSON.stringify({ - compileOnSave: true, - references: [{ path: "../dependency" }] - }) - }; - - const localChange = "function fn3() { }"; - const change = `export ${localChange}`; - const changeJs = `function fn3() { } + }; + const usageConfig: File = { + path: `${usageLocation}/tsconfig.json`, + content: JSON.stringify({ + compileOnSave: true, + references: [{ path: "../dependency" }] + }) + }; + + const localChange = "function fn3() { }"; + const change = `export ${localChange}`; + const changeJs = `function fn3() { } exports.fn3 = fn3;`; - const changeDts = "export declare function fn3(): void;"; - - function expectedAffectedFiles(config: File, fileNames: readonly File[]): protocol.CompileOnSaveAffectedFileListSingleProject { - return { - projectFileName: config.path, - fileNames: fileNames.map(f => f.path), - projectUsesOutFile: false - }; - } + const changeDts = "export declare function fn3(): void;"; + + function expectedAffectedFiles(config: File, fileNames: readonly File[]): protocol.CompileOnSaveAffectedFileListSingleProject { + return { + projectFileName: config.path, + fileNames: fileNames.map(f => f.path), + projectUsesOutFile: false + }; + } - function expectedUsageEmitFiles(appendJsText?: string): readonly File[] { - const appendJs = appendJsText ? `${appendJsText} + function expectedUsageEmitFiles(appendJsText?: string): readonly File[] { + const appendJs = appendJsText ? `${appendJsText} ` : ""; - return [{ - path: `${usageLocation}/usage.js`, - content: `"use strict"; + return [{ + path: `${usageLocation}/usage.js`, + content: `"use strict"; exports.__esModule = true;${appendJsText === changeJs ? "\nexports.fn3 = void 0;" : ""} var fns_1 = require("../decls/fns"); (0, fns_1.fn1)(); (0, fns_1.fn2)(); ${appendJs}` - }]; - } + }]; + } - function expectedEmitOutput(expectedFiles: readonly File[]): EmitOutput { - return { - outputFiles: expectedFiles.map(({ path, content }) => ({ - name: path, - text: content, - writeByteOrderMark: false - })), - emitSkipped: false, - diagnostics: emptyArray - }; - } + function expectedEmitOutput(expectedFiles: readonly File[]): EmitOutput { + return { + outputFiles: expectedFiles.map(({ path, content }) => ({ + name: path, + text: content, + writeByteOrderMark: false + })), + emitSkipped: false, + diagnostics: emptyArray + }; + } - function noEmitOutput(): EmitOutput { - return { - emitSkipped: true, - outputFiles: [], - diagnostics: emptyArray - }; - } + function noEmitOutput(): EmitOutput { + return { + emitSkipped: true, + outputFiles: [], + diagnostics: emptyArray + }; + } - function expectedDependencyEmitFiles(appendJsText?: string, appendDtsText?: string): readonly File[] { - const appendJs = appendJsText ? `${appendJsText} + function expectedDependencyEmitFiles(appendJsText?: string, appendDtsText?: string): readonly File[] { + const appendJs = appendJsText ? `${appendJsText} ` : ""; - const appendDts = appendDtsText ? `${appendDtsText} + const appendDts = appendDtsText ? `${appendDtsText} ` : ""; - return [ - { - path: `${dependecyLocation}/fns.js`, - content: `"use strict"; + return [ + { + path: `${dependecyLocation}/fns.js`, + content: `"use strict"; exports.__esModule = true; ${appendJsText === changeJs ? "exports.fn3 = " : ""}exports.fn2 = exports.fn1 = void 0; function fn1() { } @@ -97,2220 +97,2220 @@ exports.fn1 = fn1; function fn2() { } exports.fn2 = fn2; ${appendJs}` - }, - { - path: `${tscWatch.projectRoot}/decls/fns.d.ts`, - content: `export declare function fn1(): void; + }, + { + path: `${tscWatch.projectRoot}/decls/fns.d.ts`, + content: `export declare function fn1(): void; export declare function fn2(): void; ${appendDts}` + } + ]; + } + + describe("when dependency project is not open", () => { + describe("Of usageTs", () => { + it("with initial file open, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); } - ]; - } - describe("when dependency project is not open", () => { - describe("Of usageTs", () => { - it("with initial file open, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); - } + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with initial file open, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with initial file open, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); - } + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with local change to dependency, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with local change to dependency, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); - } + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with local change to dependency, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with local change to dependency, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with local change to usage, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(localChange); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with local change to usage, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(localChange); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with local change to usage, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(localChange); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with local change to usage, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(localChange); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); - } + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with change to dependency, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with change to dependency, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); - } + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with change to dependency, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with change to dependency, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with change to usage, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(changeJs); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with change to usage, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(changeJs); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with change to usage, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(changeJs); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with change to usage, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(changeJs); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); - } + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + }); - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + describe("Of dependencyTs in usage project", () => { + it("with initial file open, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + }); + it("with initial file open, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + }); + it("with local change to dependency, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, emptyArray) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); }); - - describe("Of dependencyTs in usage project", () => { - it("with initial file open, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with initial file open, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with local change to dependency, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, emptyArray) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with local change to dependency, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, emptyArray) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with local change to usage, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, emptyArray) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with local change to usage, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, emptyArray) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with change to dependency, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with change to dependency, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with change to usage, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, emptyArray) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with change to usage, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, emptyArray) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + it("with local change to dependency, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, emptyArray) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + }); + it("with local change to usage, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, emptyArray) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + }); + it("with local change to usage, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, emptyArray) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + }); + it("with change to dependency, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + }); + it("with change to dependency, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + }); + it("with change to usage, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, emptyArray) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + }); + it("with change to usage, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, emptyArray) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); }); }); + }); - describe("when the depedency file is open", () => { - describe("Of usageTs", () => { - it("with initial file open, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); - } + describe("when the depedency file is open", () => { + describe("Of usageTs", () => { + it("with initial file open, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with initial file open, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); - } + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with initial file open, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with local change to dependency, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with local change to dependency, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with local change to dependency, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with local change to dependency, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with local change to usage, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(localChange); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with local change to usage, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(localChange); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with local change to usage, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(localChange); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with local change to usage, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(localChange); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with change to dependency, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with change to dependency, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with change to dependency, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with change to dependency, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with change to usage, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(changeJs); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with change to usage, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(changeJs); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with change to usage, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedUsageEmitFiles(changeJs); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with change to usage, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change } - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedUsageEmitFiles(changeJs); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); }); + }); - describe("Of dependencyTs in usage project", () => { - it("with initial file open, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with local change to dependency, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, emptyArray) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with local change to usage, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, emptyArray) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with change to dependency, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); - }); - it("with change to usage, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, emptyArray) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response; - assert.isFalse(actualEmit, "Emit files"); - assert.equal(host.writtenFiles.size, 0); - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + describe("Of dependencyTs in usage project", () => { + it("with initial file open, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + }); + it("with local change to dependency, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } }); + const toLocation = protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, emptyArray) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); }); - - describe("Of dependencyTs", () => { - it("with initial file open, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]), - expectedAffectedFiles(dependencyConfig, [dependencyTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedDependencyEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + it("with local change to usage, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange } - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with initial file open, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(dependencyConfig, [dependencyTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedDependencyEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, emptyArray) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + }); + it("with change to dependency, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change } - - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with local change to dependency, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, emptyArray), - expectedAffectedFiles(dependencyConfig, [dependencyTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedDependencyEmitFiles(localChange); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + }); + it("with change to usage, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, emptyArray) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response; + assert.isFalse(actualEmit, "Emit files"); + assert.equal(host.writtenFiles.size, 0); + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, noEmitOutput(), "Emit output"); + }); + }); + + describe("Of dependencyTs", () => { + it("with initial file open, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]), + expectedAffectedFiles(dependencyConfig, [dependencyTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedDependencyEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with initial file open, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(dependencyConfig, [dependencyTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedDependencyEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with local change to dependency, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(dependencyConfig, [dependencyTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedDependencyEmitFiles(localChange); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with local change to dependency, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, emptyArray), + expectedAffectedFiles(dependencyConfig, [dependencyTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedDependencyEmitFiles(localChange); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with local change to usage, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, emptyArray), - expectedAffectedFiles(dependencyConfig, [dependencyTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedDependencyEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with local change to dependency, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(dependencyConfig, [dependencyTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedDependencyEmitFiles(localChange); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with local change to usage, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(dependencyConfig, [dependencyTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedDependencyEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with local change to usage, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, emptyArray), + expectedAffectedFiles(dependencyConfig, [dependencyTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedDependencyEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with change to dependency, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, [usageTs]), - expectedAffectedFiles(dependencyConfig, [dependencyTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedDependencyEmitFiles(changeJs, changeDts); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with local change to usage, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(dependencyConfig, [dependencyTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedDependencyEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with change to dependency, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(dependencyConfig, [dependencyTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedDependencyEmitFiles(changeJs, changeDts); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with change to dependency, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, [usageTs]), + expectedAffectedFiles(dependencyConfig, [dependencyTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedDependencyEmitFiles(changeJs, changeDts); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with change to usage, without specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(usageConfig, emptyArray), - expectedAffectedFiles(dependencyConfig, [dependencyTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedDependencyEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with change to dependency, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(dependencyConfig, [dependencyTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedDependencyEmitFiles(changeJs, changeDts); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); - }); - it("with change to usage, with specifying project file", () => { - const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( - createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) - ); - const session = createSession(host); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - host.writtenFiles.clear(); - - // Verify CompileOnSaveAffectedFileList - const actualAffectedFiles = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(actualAffectedFiles, [ - expectedAffectedFiles(dependencyConfig, [dependencyTs]) - ], "Affected files"); - - // Verify CompileOnSaveEmit - const actualEmit = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response; - assert.isTrue(actualEmit, "Emit files"); - const expectedFiles = expectedDependencyEmitFiles(); - assert.equal(host.writtenFiles.size, expectedFiles.length); - for (const file of expectedFiles) { - assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); - assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with change to usage, without specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change } + }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(usageConfig, emptyArray), + expectedAffectedFiles(dependencyConfig, [dependencyTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedDependencyEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } - // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }).response as EmitOutput; - assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); + }); + it("with change to usage, with specifying project file", () => { + const host = TestFSWithWatch.changeToHostTrackingWrittenFiles( + createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]) + ); + const session = createSession(host); + openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } }); + host.writtenFiles.clear(); + + // Verify CompileOnSaveAffectedFileList + const actualAffectedFiles = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(actualAffectedFiles, [ + expectedAffectedFiles(dependencyConfig, [dependencyTs]) + ], "Affected files"); + + // Verify CompileOnSaveEmit + const actualEmit = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response; + assert.isTrue(actualEmit, "Emit files"); + const expectedFiles = expectedDependencyEmitFiles(); + assert.equal(host.writtenFiles.size, expectedFiles.length); + for (const file of expectedFiles) { + assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`); + assert.isTrue(host.writtenFiles.has(file.path as Path), `${file.path} is newly written`); + } + + // Verify EmitOutput + const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + command: protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }).response as EmitOutput; + assert.deepEqual(actualEmitOutput, expectedEmitOutput(expectedFiles), "Emit output"); }); }); }); +}); - describe("unittests:: tsserver:: with project references and compile on save with external projects", () => { - it("compile on save emits same output as project build", () => { - const tsbaseJson: File = { - path: `${tscWatch.projectRoot}/tsbase.json`, - content: JSON.stringify({ - compileOnSave: true, - compilerOptions: { - module: "none", - composite: true - } - }) - }; - const buttonClass = `${tscWatch.projectRoot}/buttonClass`; - const buttonConfig: File = { - path: `${buttonClass}/tsconfig.json`, - content: JSON.stringify({ - extends: "../tsbase.json", - compilerOptions: { - outFile: "Source.js" - }, - files: ["Source.ts"] - }) - }; - const buttonSource: File = { - path: `${buttonClass}/Source.ts`, - content: `module Hmi { +describe("unittests:: tsserver:: with project references and compile on save with external projects", () => { + it("compile on save emits same output as project build", () => { + const tsbaseJson: File = { + path: `${tscWatch.projectRoot}/tsbase.json`, + content: JSON.stringify({ + compileOnSave: true, + compilerOptions: { + module: "none", + composite: true + } + }) + }; + const buttonClass = `${tscWatch.projectRoot}/buttonClass`; + const buttonConfig: File = { + path: `${buttonClass}/tsconfig.json`, + content: JSON.stringify({ + extends: "../tsbase.json", + compilerOptions: { + outFile: "Source.js" + }, + files: ["Source.ts"] + }) + }; + const buttonSource: File = { + path: `${buttonClass}/Source.ts`, + content: `module Hmi { export class Button { public static myStaticFunction() { } } }` - }; - - const siblingClass = `${tscWatch.projectRoot}/SiblingClass`; - const siblingConfig: File = { - path: `${siblingClass}/tsconfig.json`, - content: JSON.stringify({ - extends: "../tsbase.json", - references: [{ - path: "../buttonClass/" - }], - compilerOptions: { - outFile: "Source.js" - }, - files: ["Source.ts"] - }) - }; - const siblingSource: File = { - path: `${siblingClass}/Source.ts`, - content: `module Hmi { + }; + + const siblingClass = `${tscWatch.projectRoot}/SiblingClass`; + const siblingConfig: File = { + path: `${siblingClass}/tsconfig.json`, + content: JSON.stringify({ + extends: "../tsbase.json", + references: [{ + path: "../buttonClass/" + }], + compilerOptions: { + outFile: "Source.js" + }, + files: ["Source.ts"] + }) + }; + const siblingSource: File = { + path: `${siblingClass}/Source.ts`, + content: `module Hmi { export class Sibling { public mySiblingFunction() { } } }` - }; - const host = createServerHost([libFile, tsbaseJson, buttonConfig, buttonSource, siblingConfig, siblingSource], { useCaseSensitiveFileNames: true }); - - // ts build should succeed - tscWatch.ensureErrorFreeBuild(host, [siblingConfig.path]); - const sourceJs = changeExtension(siblingSource.path, ".js"); - const expectedSiblingJs = host.readFile(sourceJs); - - const session = createSession(host); - openFilesForSession([siblingSource], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { - file: siblingSource.path, - projectFileName: siblingConfig.path - } - }); - assert.equal(host.readFile(sourceJs), expectedSiblingJs); + }; + const host = createServerHost([libFile, tsbaseJson, buttonConfig, buttonSource, siblingConfig, siblingSource], { useCaseSensitiveFileNames: true }); + + // ts build should succeed + tscWatch.ensureErrorFreeBuild(host, [siblingConfig.path]); + const sourceJs = changeExtension(siblingSource.path, ".js"); + const expectedSiblingJs = host.readFile(sourceJs); + + const session = createSession(host); + openFilesForSession([siblingSource], session); + + session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { + file: siblingSource.path, + projectFileName: siblingConfig.path + } }); + assert.equal(host.readFile(sourceJs), expectedSiblingJs); }); -} \ No newline at end of file +}); +} diff --git a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts index 7368f54b9f2ad..4b329f6a98bf2 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts @@ -1,83 +1,83 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: with project references and error reporting", () => { - const dependecyLocation = `${tscWatch.projectRoot}/dependency`; - const usageLocation = `${tscWatch.projectRoot}/usage`; +describe("unittests:: tsserver:: with project references and error reporting", () => { + const dependecyLocation = `${tscWatch.projectRoot}/dependency`; + const usageLocation = `${tscWatch.projectRoot}/usage`; - function verifyUsageAndDependency(scenario: string, dependencyTs: File, dependencyConfig: File, usageTs: File, usageConfig: File) { - function usageProjectDiagnostics(): GetErrForProjectDiagnostics { - return { project: usageTs, files: [usageTs, dependencyTs] }; - } + function verifyUsageAndDependency(scenario: string, dependencyTs: File, dependencyConfig: File, usageTs: File, usageConfig: File) { + function usageProjectDiagnostics(): GetErrForProjectDiagnostics { + return { project: usageTs, files: [usageTs, dependencyTs] }; + } - function dependencyProjectDiagnostics(): GetErrForProjectDiagnostics { - return { project: dependencyTs, files: [dependencyTs] }; - } + function dependencyProjectDiagnostics(): GetErrForProjectDiagnostics { + return { project: dependencyTs, files: [dependencyTs] }; + } - describe("when dependency project is not open", () => { - verifyGetErrScenario({ - scenario: "projectReferenceErrors", - subScenario: `${scenario} when dependency project is not open`, - allFiles: () => [dependencyTs, dependencyConfig, usageTs, usageConfig], - openFiles: () => [usageTs], - getErrRequest: () => [usageTs], - getErrForProjectRequest: () => [ - usageProjectDiagnostics(), - { - project: dependencyTs, - files: [dependencyTs, usageTs] - } - ], - syncDiagnostics: () => [ - // Without project - { file: usageTs }, - { file: dependencyTs }, - // With project - { file: usageTs, project: usageConfig }, - { file: dependencyTs, project: usageConfig }, - ], - }); + describe("when dependency project is not open", () => { + verifyGetErrScenario({ + scenario: "projectReferenceErrors", + subScenario: `${scenario} when dependency project is not open`, + allFiles: () => [dependencyTs, dependencyConfig, usageTs, usageConfig], + openFiles: () => [usageTs], + getErrRequest: () => [usageTs], + getErrForProjectRequest: () => [ + usageProjectDiagnostics(), + { + project: dependencyTs, + files: [dependencyTs, usageTs] + } + ], + syncDiagnostics: () => [ + // Without project + { file: usageTs }, + { file: dependencyTs }, + // With project + { file: usageTs, project: usageConfig }, + { file: dependencyTs, project: usageConfig }, + ], }); + }); - describe("when the depedency file is open", () => { - verifyGetErrScenario({ - scenario: "projectReferenceErrors", - subScenario: `${scenario} when the depedency file is open`, - allFiles: () => [dependencyTs, dependencyConfig, usageTs, usageConfig], - openFiles: () => [usageTs, dependencyTs], - getErrRequest: () => [usageTs, dependencyTs], - getErrForProjectRequest: () => [ - usageProjectDiagnostics(), - dependencyProjectDiagnostics() - ], - syncDiagnostics: () => [ - // Without project - { file: usageTs }, - { file: dependencyTs }, - // With project - { file: usageTs, project: usageConfig }, - { file: dependencyTs, project: usageConfig }, - { file: dependencyTs, project: dependencyConfig }, - ], - }); + describe("when the depedency file is open", () => { + verifyGetErrScenario({ + scenario: "projectReferenceErrors", + subScenario: `${scenario} when the depedency file is open`, + allFiles: () => [dependencyTs, dependencyConfig, usageTs, usageConfig], + openFiles: () => [usageTs, dependencyTs], + getErrRequest: () => [usageTs, dependencyTs], + getErrForProjectRequest: () => [ + usageProjectDiagnostics(), + dependencyProjectDiagnostics() + ], + syncDiagnostics: () => [ + // Without project + { file: usageTs }, + { file: dependencyTs }, + // With project + { file: usageTs, project: usageConfig }, + { file: dependencyTs, project: usageConfig }, + { file: dependencyTs, project: dependencyConfig }, + ], }); - } + }); + } - describe("with module scenario", () => { - const dependencyTs: File = { - path: `${dependecyLocation}/fns.ts`, - content: `export function fn1() { } + describe("with module scenario", () => { + const dependencyTs: File = { + path: `${dependecyLocation}/fns.ts`, + content: `export function fn1() { } export function fn2() { } // Introduce error for fnErr import in main // export function fnErr() { } // Error in dependency ts file export let x: string = 10;` - }; - const dependencyConfig: File = { - path: `${dependecyLocation}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true, declarationDir: "../decls" } }) - }; - const usageTs: File = { - path: `${usageLocation}/usage.ts`, - content: `import { + }; + const dependencyConfig: File = { + path: `${dependecyLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true, declarationDir: "../decls" } }) + }; + const usageTs: File = { + path: `${usageLocation}/usage.ts`, + content: `import { fn1, fn2, fnErr @@ -86,46 +86,46 @@ fn1(); fn2(); fnErr(); ` - }; - const usageConfig: File = { - path: `${usageLocation}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true }, - references: [{ path: "../dependency" }] - }) - }; - verifyUsageAndDependency("with module scenario", dependencyTs, dependencyConfig, usageTs, usageConfig); - }); + }; + const usageConfig: File = { + path: `${usageLocation}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true }, + references: [{ path: "../dependency" }] + }) + }; + verifyUsageAndDependency("with module scenario", dependencyTs, dependencyConfig, usageTs, usageConfig); + }); - describe("with non module --out", () => { - const dependencyTs: File = { - path: `${dependecyLocation}/fns.ts`, - content: `function fn1() { } + describe("with non module --out", () => { + const dependencyTs: File = { + path: `${dependecyLocation}/fns.ts`, + content: `function fn1() { } function fn2() { } // Introduce error for fnErr import in main // function fnErr() { } // Error in dependency ts file let x: string = 10;` - }; - const dependencyConfig: File = { - path: `${dependecyLocation}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true, outFile: "../dependency.js" } }) - }; - const usageTs: File = { - path: `${usageLocation}/usage.ts`, - content: `fn1(); + }; + const dependencyConfig: File = { + path: `${dependecyLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true, outFile: "../dependency.js" } }) + }; + const usageTs: File = { + path: `${usageLocation}/usage.ts`, + content: `fn1(); fn2(); fnErr(); ` - }; - const usageConfig: File = { - path: `${usageLocation}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, outFile: "../usage.js" }, - references: [{ path: "../dependency" }] - }) - }; - verifyUsageAndDependency("with non module", dependencyTs, dependencyConfig, usageTs, usageConfig); - }); + }; + const usageConfig: File = { + path: `${usageLocation}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, outFile: "../usage.js" }, + references: [{ path: "../dependency" }] + }) + }; + verifyUsageAndDependency("with non module", dependencyTs, dependencyConfig, usageTs, usageConfig); }); +}); } diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index bae075912be5f..050122d2942da 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1,471 +1,471 @@ namespace ts.projectSystem { - export function createHostWithSolutionBuild(files: readonly TestFSWithWatch.FileOrFolderOrSymLink[], rootNames: readonly string[]) { - const host = createServerHost(files); - // ts build should succeed - tscWatch.ensureErrorFreeBuild(host, rootNames); - return host; - } - - describe("unittests:: tsserver:: with project references and tsbuild", () => { - describe("with container project", () => { - function getProjectFiles(project: string): [File, File] { - return [ - TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"), - TestFSWithWatch.getTsBuildProjectFile(project, "index.ts"), - ]; - } - - const project = "container"; - const containerLib = getProjectFiles("container/lib"); - const containerExec = getProjectFiles("container/exec"); - const containerCompositeExec = getProjectFiles("container/compositeExec"); - const containerConfig = TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"); - const files = [libFile, ...containerLib, ...containerExec, ...containerCompositeExec, containerConfig]; - - it("does not error on container only project", () => { - const host = createHostWithSolutionBuild(files, [containerConfig.path]); +export function createHostWithSolutionBuild(files: readonly TestFSWithWatch.FileOrFolderOrSymLink[], rootNames: readonly string[]) { + const host = createServerHost(files); + // ts build should succeed + tscWatch.ensureErrorFreeBuild(host, rootNames); + return host; +} - // Open external project for the folder - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - const service = session.getProjectService(); - service.openExternalProjects([{ - projectFileName: TestFSWithWatch.getTsBuildProjectFilePath(project, project), - rootFiles: files.map(f => ({ fileName: f.path })), - options: {} - }]); - files.forEach(f => { - const args: protocol.FileRequestArgs = { - file: f.path, - projectFileName: endsWith(f.path, "tsconfig.json") ? f.path : undefined - }; - session.executeCommandSeq({ - command: protocol.CommandTypes.SyntacticDiagnosticsSync, - arguments: args - }); - session.executeCommandSeq({ - command: protocol.CommandTypes.SemanticDiagnosticsSync, - arguments: args - }); +describe("unittests:: tsserver:: with project references and tsbuild", () => { + describe("with container project", () => { + function getProjectFiles(project: string): [File, File] { + return [ + TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"), + TestFSWithWatch.getTsBuildProjectFile(project, "index.ts"), + ]; + } + + const project = "container"; + const containerLib = getProjectFiles("container/lib"); + const containerExec = getProjectFiles("container/exec"); + const containerCompositeExec = getProjectFiles("container/compositeExec"); + const containerConfig = TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"); + const files = [libFile, ...containerLib, ...containerExec, ...containerCompositeExec, containerConfig]; + + it("does not error on container only project", () => { + const host = createHostWithSolutionBuild(files, [containerConfig.path]); + + // Open external project for the folder + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + const service = session.getProjectService(); + service.openExternalProjects([{ + projectFileName: TestFSWithWatch.getTsBuildProjectFilePath(project, project), + rootFiles: files.map(f => ({ fileName: f.path })), + options: {} + }]); + files.forEach(f => { + const args: protocol.FileRequestArgs = { + file: f.path, + projectFileName: endsWith(f.path, "tsconfig.json") ? f.path : undefined + }; + session.executeCommandSeq({ + command: protocol.CommandTypes.SyntacticDiagnosticsSync, + arguments: args }); - const containerProject = service.configuredProjects.get(containerConfig.path)!; - session.executeCommandSeq({ - command: protocol.CommandTypes.CompilerOptionsDiagnosticsFull, - arguments: { projectFileName: containerProject.projectName } + session.executeCommandSeq({ + command: protocol.CommandTypes.SemanticDiagnosticsSync, + arguments: args }); - baselineTsserverLogs("projectReferences", `does not error on container only project`, session); }); - - it("can successfully find references with --out options", () => { - const host = createHostWithSolutionBuild(files, [containerConfig.path]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([containerCompositeExec[1]], session); - const myConstStart = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); - session.executeCommandSeq({ - command: protocol.CommandTypes.Rename, - arguments: { file: containerCompositeExec[1].path, ...myConstStart } - }); - - baselineTsserverLogs("projectReferences", `can successfully find references with out option`, session); + const containerProject = service.configuredProjects.get(containerConfig.path)!; + session.executeCommandSeq({ + command: protocol.CommandTypes.CompilerOptionsDiagnosticsFull, + arguments: { projectFileName: containerProject.projectName } }); + baselineTsserverLogs("projectReferences", `does not error on container only project`, session); + }); - it("ancestor and project ref management", () => { - const tempFile: File = { - path: `/user/username/projects/temp/temp.ts`, - content: "let x = 10" - }; - const host = createHostWithSolutionBuild(files.concat([tempFile]), [containerConfig.path]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([containerCompositeExec[1]], session); - const service = session.getProjectService(); - - // Open temp file and verify all projects alive - openFilesForSession([tempFile], session); - - // Ref projects are loaded after as part of this command - const locationOfMyConst = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); - session.executeCommandSeq({ - command: protocol.CommandTypes.Rename, - arguments: { - file: containerCompositeExec[1].path, - ...locationOfMyConst - } - }); - - // Open temp file and verify all projects alive - service.closeClientFile(tempFile.path); - openFilesForSession([tempFile], session); - - // Close all files and open temp file, only inferred project should be alive - service.closeClientFile(containerCompositeExec[1].path); - service.closeClientFile(tempFile.path); - openFilesForSession([tempFile], session); - baselineTsserverLogs("projectReferences", `ancestor and project ref management`, session); + it("can successfully find references with --out options", () => { + const host = createHostWithSolutionBuild(files, [containerConfig.path]); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([containerCompositeExec[1]], session); + const myConstStart = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); + session.executeCommandSeq({ + command: protocol.CommandTypes.Rename, + arguments: { file: containerCompositeExec[1].path, ...myConstStart } }); + + baselineTsserverLogs("projectReferences", `can successfully find references with out option`, session); }); - describe("when root file is file from referenced project", () => { - function verify(disableSourceOfProjectReferenceRedirect: boolean) { - const projectLocation = `/user/username/projects/project`; - const commonConfig: File = { - path: `${projectLocation}/src/common/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - declarationMap: true, - outDir: "../../out", - baseUrl: "..", - disableSourceOfProjectReferenceRedirect - }, - include: ["./**/*"] - }) - }; - const keyboardTs: File = { - path: `${projectLocation}/src/common/input/keyboard.ts`, - content: `function bar() { return "just a random function so .d.ts location doesnt match"; } -export function evaluateKeyboardEvent() { }` - }; - const keyboardTestTs: File = { - path: `${projectLocation}/src/common/input/keyboard.test.ts`, - content: `import { evaluateKeyboardEvent } from 'common/input/keyboard'; -function testEvaluateKeyboardEvent() { - return evaluateKeyboardEvent(); -} -` - }; - const srcConfig: File = { - path: `${projectLocation}/src/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - declarationMap: true, - outDir: "../out", - baseUrl: ".", - paths: { - "common/*": ["./common/*"], - }, - tsBuildInfoFile: "../out/src.tsconfig.tsbuildinfo", - disableSourceOfProjectReferenceRedirect - }, - include: ["./**/*"], - references: [ - { path: "./common" } - ] - }) - }; - const terminalTs: File = { - path: `${projectLocation}/src/terminal.ts`, - content: `import { evaluateKeyboardEvent } from 'common/input/keyboard'; -function foo() { - return evaluateKeyboardEvent(); -} -` - }; - const host = createHostWithSolutionBuild( - [commonConfig, keyboardTs, keyboardTestTs, srcConfig, terminalTs, libFile], - [srcConfig.path] - ); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([keyboardTs, terminalTs], session); + it("ancestor and project ref management", () => { + const tempFile: File = { + path: `/user/username/projects/temp/temp.ts`, + content: "let x = 10" + }; + const host = createHostWithSolutionBuild(files.concat([tempFile]), [containerConfig.path]); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([containerCompositeExec[1]], session); + const service = session.getProjectService(); + + // Open temp file and verify all projects alive + openFilesForSession([tempFile], session); + + // Ref projects are loaded after as part of this command + const locationOfMyConst = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); + session.executeCommandSeq({ + command: protocol.CommandTypes.Rename, + arguments: { + file: containerCompositeExec[1].path, + ...locationOfMyConst + } + }); - const searchStr = "evaluateKeyboardEvent"; - const importStr = `import { evaluateKeyboardEvent } from 'common/input/keyboard';`; - const result = session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(keyboardTs, searchStr) - }).response as protocol.ReferencesResponseBody; - assert.deepEqual(result, { - refs: [ - makeReferenceItem({ - file: keyboardTs, - text: searchStr, - contextText: `export function evaluateKeyboardEvent() { }`, - isDefinition: true, - lineText: `export function evaluateKeyboardEvent() { }` - }), - makeReferenceItem({ - file: keyboardTestTs, - text: searchStr, - contextText: importStr, - isDefinition: false, - isWriteAccess: true, - lineText: importStr - }), - makeReferenceItem({ - file: keyboardTestTs, - text: searchStr, - options: { index: 1 }, - isDefinition: false, - lineText: ` return evaluateKeyboardEvent();` - }), - makeReferenceItem({ - file: terminalTs, - text: searchStr, - contextText: importStr, - isDefinition: false, - isWriteAccess: true, - lineText: importStr - }), - makeReferenceItem({ - file: terminalTs, - text: searchStr, - options: { index: 1 }, - isDefinition: false, - lineText: ` return evaluateKeyboardEvent();` - }), - ], - symbolName: searchStr, - symbolStartOffset: protocolLocationFromSubstring(keyboardTs.content, searchStr).offset, - symbolDisplayString: "function evaluateKeyboardEvent(): void" - }); - baselineTsserverLogs("projectReferences", `root file is file from referenced project${disableSourceOfProjectReferenceRedirect ? " and using declaration maps" : ""}`, session); - } + // Open temp file and verify all projects alive + service.closeClientFile(tempFile.path); + openFilesForSession([tempFile], session); - it(`when using declaration file maps to navigate between projects`, () => { - verify(/*disableSourceOfProjectReferenceRedirect*/ true); - }); - it(`when using original source files in the project`, () => { - verify(/*disableSourceOfProjectReferenceRedirect*/ false); - }); + // Close all files and open temp file, only inferred project should be alive + service.closeClientFile(containerCompositeExec[1].path); + service.closeClientFile(tempFile.path); + openFilesForSession([tempFile], session); + baselineTsserverLogs("projectReferences", `ancestor and project ref management`, session); }); + }); - it("reusing d.ts files from composite and non composite projects", () => { - const configA: File = { - path: `${tscWatch.projectRoot}/compositea/tsconfig.json`, + describe("when root file is file from referenced project", () => { + function verify(disableSourceOfProjectReferenceRedirect: boolean) { + const projectLocation = `/user/username/projects/project`; + const commonConfig: File = { + path: `${projectLocation}/src/common/tsconfig.json`, content: JSON.stringify({ compilerOptions: { composite: true, - outDir: "../dist/", - rootDir: "../", - baseUrl: "../", - paths: { "@ref/*": ["./dist/*"] } - } + declarationMap: true, + outDir: "../../out", + baseUrl: "..", + disableSourceOfProjectReferenceRedirect + }, + include: ["./**/*"] }) }; - const aTs: File = { - path: `${tscWatch.projectRoot}/compositea/a.ts`, - content: `import { b } from "@ref/compositeb/b";` - }; - const a2Ts: File = { - path: `${tscWatch.projectRoot}/compositea/a2.ts`, - content: `export const x = 10;` - }; - const configB: File = { - path: `${tscWatch.projectRoot}/compositeb/tsconfig.json`, - content: configA.content - }; - const bTs: File = { - path: `${tscWatch.projectRoot}/compositeb/b.ts`, - content: "export function b() {}" + const keyboardTs: File = { + path: `${projectLocation}/src/common/input/keyboard.ts`, + content: `function bar() { return "just a random function so .d.ts location doesnt match"; } +export function evaluateKeyboardEvent() { }` }; - const bDts: File = { - path: `${tscWatch.projectRoot}/dist/compositeb/b.d.ts`, - content: "export declare function b(): void;" + const keyboardTestTs: File = { + path: `${projectLocation}/src/common/input/keyboard.test.ts`, + content: `import { evaluateKeyboardEvent } from 'common/input/keyboard'; +function testEvaluateKeyboardEvent() { + return evaluateKeyboardEvent(); +} +` }; - const configC: File = { - path: `${tscWatch.projectRoot}/compositec/tsconfig.json`, + const srcConfig: File = { + path: `${projectLocation}/src/tsconfig.json`, content: JSON.stringify({ compilerOptions: { composite: true, - outDir: "../dist/", - rootDir: "../", - baseUrl: "../", - paths: { "@ref/*": ["./*"] } + declarationMap: true, + outDir: "../out", + baseUrl: ".", + paths: { + "common/*": ["./common/*"], + }, + tsBuildInfoFile: "../out/src.tsconfig.tsbuildinfo", + disableSourceOfProjectReferenceRedirect }, - references: [{ path: "../compositeb" }] + include: ["./**/*"], + references: [ + { path: "./common" } + ] }) }; - const cTs: File = { - path: `${tscWatch.projectRoot}/compositec/c.ts`, - content: aTs.content + const terminalTs: File = { + path: `${projectLocation}/src/terminal.ts`, + content: `import { evaluateKeyboardEvent } from 'common/input/keyboard'; +function foo() { + return evaluateKeyboardEvent(); +} +` }; - const files = [libFile, aTs, a2Ts, configA, bDts, bTs, configB, cTs, configC]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(aTs.path); - service.checkNumberOfProjects({ configuredProjects: 1 }); - - // project A referencing b.d.ts without project reference - const projectA = service.configuredProjects.get(configA.path)!; - assert.isDefined(projectA); - checkProjectActualFiles(projectA, [aTs.path, a2Ts.path, bDts.path, libFile.path, configA.path]); - - // reuses b.d.ts but sets the path and resolved path since projectC has project references - // as the real resolution was to b.ts - service.openClientFile(cTs.path); - service.checkNumberOfProjects({ configuredProjects: 2 }); - const projectC = service.configuredProjects.get(configC.path)!; - checkProjectActualFiles(projectC, [cTs.path, bTs.path, libFile.path, configC.path]); - - // Now new project for project A tries to reuse b but there is no filesByName mapping for b's source location - host.writeFile(a2Ts.path, `${a2Ts.content}export const y = 30;`); - assert.isTrue(projectA.dirty); - projectA.updateGraph(); + const host = createHostWithSolutionBuild( + [commonConfig, keyboardTs, keyboardTestTs, srcConfig, terminalTs, libFile], + [srcConfig.path] + ); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([keyboardTs, terminalTs], session); + + const searchStr = "evaluateKeyboardEvent"; + const importStr = `import { evaluateKeyboardEvent } from 'common/input/keyboard';`; + const result = session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: protocolFileLocationFromSubstring(keyboardTs, searchStr) + }).response as protocol.ReferencesResponseBody; + assert.deepEqual(result, { + refs: [ + makeReferenceItem({ + file: keyboardTs, + text: searchStr, + contextText: `export function evaluateKeyboardEvent() { }`, + isDefinition: true, + lineText: `export function evaluateKeyboardEvent() { }` + }), + makeReferenceItem({ + file: keyboardTestTs, + text: searchStr, + contextText: importStr, + isDefinition: false, + isWriteAccess: true, + lineText: importStr + }), + makeReferenceItem({ + file: keyboardTestTs, + text: searchStr, + options: { index: 1 }, + isDefinition: false, + lineText: ` return evaluateKeyboardEvent();` + }), + makeReferenceItem({ + file: terminalTs, + text: searchStr, + contextText: importStr, + isDefinition: false, + isWriteAccess: true, + lineText: importStr + }), + makeReferenceItem({ + file: terminalTs, + text: searchStr, + options: { index: 1 }, + isDefinition: false, + lineText: ` return evaluateKeyboardEvent();` + }), + ], + symbolName: searchStr, + symbolStartOffset: protocolLocationFromSubstring(keyboardTs.content, searchStr).offset, + symbolDisplayString: "function evaluateKeyboardEvent(): void" + }); + baselineTsserverLogs("projectReferences", `root file is file from referenced project${disableSourceOfProjectReferenceRedirect ? " and using declaration maps" : ""}`, session); + } + + it(`when using declaration file maps to navigate between projects`, () => { + verify(/*disableSourceOfProjectReferenceRedirect*/ true); }); + it(`when using original source files in the project`, () => { + verify(/*disableSourceOfProjectReferenceRedirect*/ false); + }); + }); - describe("when references are monorepo like with symlinks", () => { - interface Packages { - bPackageJson: File; - aTest: File; - bFoo: File; - bBar: File; - bSymlink: SymLink; - } - function verifySymlinkScenario(scenario: string, packages: () => Packages) { - describe(`${scenario}: when solution is not built`, () => { - it("with preserveSymlinks turned off", () => { - verifySession(scenario, packages(), /*alreadyBuilt*/ false, {}); - }); - - it("with preserveSymlinks turned on", () => { - verifySession(scenario, packages(), /*alreadyBuilt*/ false, { preserveSymlinks: true }); - }); + it("reusing d.ts files from composite and non composite projects", () => { + const configA: File = { + path: `${tscWatch.projectRoot}/compositea/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "../dist/", + rootDir: "../", + baseUrl: "../", + paths: { "@ref/*": ["./dist/*"] } + } + }) + }; + const aTs: File = { + path: `${tscWatch.projectRoot}/compositea/a.ts`, + content: `import { b } from "@ref/compositeb/b";` + }; + const a2Ts: File = { + path: `${tscWatch.projectRoot}/compositea/a2.ts`, + content: `export const x = 10;` + }; + const configB: File = { + path: `${tscWatch.projectRoot}/compositeb/tsconfig.json`, + content: configA.content + }; + const bTs: File = { + path: `${tscWatch.projectRoot}/compositeb/b.ts`, + content: "export function b() {}" + }; + const bDts: File = { + path: `${tscWatch.projectRoot}/dist/compositeb/b.d.ts`, + content: "export declare function b(): void;" + }; + const configC: File = { + path: `${tscWatch.projectRoot}/compositec/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "../dist/", + rootDir: "../", + baseUrl: "../", + paths: { "@ref/*": ["./*"] } + }, + references: [{ path: "../compositeb" }] + }) + }; + const cTs: File = { + path: `${tscWatch.projectRoot}/compositec/c.ts`, + content: aTs.content + }; + const files = [libFile, aTs, a2Ts, configA, bDts, bTs, configB, cTs, configC]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(aTs.path); + service.checkNumberOfProjects({ configuredProjects: 1 }); + + // project A referencing b.d.ts without project reference + const projectA = service.configuredProjects.get(configA.path)!; + assert.isDefined(projectA); + checkProjectActualFiles(projectA, [aTs.path, a2Ts.path, bDts.path, libFile.path, configA.path]); + + // reuses b.d.ts but sets the path and resolved path since projectC has project references + // as the real resolution was to b.ts + service.openClientFile(cTs.path); + service.checkNumberOfProjects({ configuredProjects: 2 }); + const projectC = service.configuredProjects.get(configC.path)!; + checkProjectActualFiles(projectC, [cTs.path, bTs.path, libFile.path, configC.path]); + + // Now new project for project A tries to reuse b but there is no filesByName mapping for b's source location + host.writeFile(a2Ts.path, `${a2Ts.content}export const y = 30;`); + assert.isTrue(projectA.dirty); + projectA.updateGraph(); + }); + + describe("when references are monorepo like with symlinks", () => { + interface Packages { + bPackageJson: File; + aTest: File; + bFoo: File; + bBar: File; + bSymlink: SymLink; + } + function verifySymlinkScenario(scenario: string, packages: () => Packages) { + describe(`${scenario}: when solution is not built`, () => { + it("with preserveSymlinks turned off", () => { + verifySession(scenario, packages(), /*alreadyBuilt*/ false, {}); }); - describe(`${scenario}: when solution is already built`, () => { - it("with preserveSymlinks turned off", () => { - verifySession(scenario, packages(), /*alreadyBuilt*/ true, {}); - }); + it("with preserveSymlinks turned on", () => { + verifySession(scenario, packages(), /*alreadyBuilt*/ false, { preserveSymlinks: true }); + }); + }); - it("with preserveSymlinks turned on", () => { - verifySession(scenario, packages(), /*alreadyBuilt*/ true, { preserveSymlinks: true }); - }); + describe(`${scenario}: when solution is already built`, () => { + it("with preserveSymlinks turned off", () => { + verifySession(scenario, packages(), /*alreadyBuilt*/ true, {}); }); - } - function verifySession(scenario: string, { bPackageJson, aTest, bFoo, bBar, bSymlink }: Packages, alreadyBuilt: boolean, extraOptions: CompilerOptions) { - const aConfig = config("A", extraOptions, ["../B"]); - const bConfig = config("B", extraOptions); - const files = [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink]; - const host = alreadyBuilt ? - createHostWithSolutionBuild(files, [aConfig.path]) : - createServerHost(files); - - // Create symlink in node module - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([aTest], session); - verifyGetErrRequest({ session, host, files: [aTest] }); - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: aTest.path, - textChanges: [{ - newText: "\n", - start: { line: 5, offset: 1 }, - end: { line: 5, offset: 1 } - }] - }] - } + it("with preserveSymlinks turned on", () => { + verifySession(scenario, packages(), /*alreadyBuilt*/ true, { preserveSymlinks: true }); }); - verifyGetErrRequest({ session, host, files: [aTest] }); - baselineTsserverLogs("projectReferences", `monorepo like with symlinks ${scenario} and solution is ${alreadyBuilt ? "built" : "not built"}${extraOptions.preserveSymlinks ? " with preserveSymlinks" : ""}`, session); - } + }); + } - function config(packageName: string, extraOptions: CompilerOptions, references?: string[]): File { - return { - path: `${tscWatch.projectRoot}/packages/${packageName}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - outDir: "lib", - rootDir: "src", - composite: true, - ...extraOptions - }, - include: ["src"], - ...(references ? { references: references.map(path => ({ path })) } : {}) - }) - }; - } + function verifySession(scenario: string, { bPackageJson, aTest, bFoo, bBar, bSymlink }: Packages, alreadyBuilt: boolean, extraOptions: CompilerOptions) { + const aConfig = config("A", extraOptions, ["../B"]); + const bConfig = config("B", extraOptions); + const files = [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink]; + const host = alreadyBuilt ? + createHostWithSolutionBuild(files, [aConfig.path]) : + createServerHost(files); - function file(packageName: string, fileName: string, content: string): File { - return { - path: `${tscWatch.projectRoot}/packages/${packageName}/src/${fileName}`, - content - }; - } + // Create symlink in node module + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([aTest], session); + verifyGetErrRequest({ session, host, files: [aTest] }); + session.executeCommandSeq({ + command: protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: aTest.path, + textChanges: [{ + newText: "\n", + start: { line: 5, offset: 1 }, + end: { line: 5, offset: 1 } + }] + }] + } + }); + verifyGetErrRequest({ session, host, files: [aTest] }); + baselineTsserverLogs("projectReferences", `monorepo like with symlinks ${scenario} and solution is ${alreadyBuilt ? "built" : "not built"}${extraOptions.preserveSymlinks ? " with preserveSymlinks" : ""}`, session); + } - function verifyMonoRepoLike(scope = "") { - verifySymlinkScenario(`when packageJson has types field and has index.ts${scope ? " with scoped package" : ""}`, () => ({ - bPackageJson: { - path: `${tscWatch.projectRoot}/packages/B/package.json`, - content: JSON.stringify({ - main: "lib/index.js", - types: "lib/index.d.ts" - }) + function config(packageName: string, extraOptions: CompilerOptions, references?: string[]): File { + return { + path: `${tscWatch.projectRoot}/packages/${packageName}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + outDir: "lib", + rootDir: "src", + composite: true, + ...extraOptions }, - aTest: file("A", "index.ts", `import { foo } from '${scope}b'; + include: ["src"], + ...(references ? { references: references.map(path => ({ path })) } : {}) + }) + }; + } + + function file(packageName: string, fileName: string, content: string): File { + return { + path: `${tscWatch.projectRoot}/packages/${packageName}/src/${fileName}`, + content + }; + } + + function verifyMonoRepoLike(scope = "") { + verifySymlinkScenario(`when packageJson has types field and has index.ts${scope ? " with scoped package" : ""}`, () => ({ + bPackageJson: { + path: `${tscWatch.projectRoot}/packages/B/package.json`, + content: JSON.stringify({ + main: "lib/index.js", + types: "lib/index.d.ts" + }) + }, + aTest: file("A", "index.ts", `import { foo } from '${scope}b'; import { bar } from '${scope}b/lib/bar'; foo(); bar(); `), - bFoo: file("B", "index.ts", `export function foo() { }`), - bBar: file("B", "bar.ts", `export function bar() { }`), - bSymlink: { - path: `${tscWatch.projectRoot}/node_modules/${scope}b`, - symLink: `${tscWatch.projectRoot}/packages/B` - } - })); - - verifySymlinkScenario(`when referencing file from subFolder${scope ? " with scoped package" : ""}`, () => ({ - bPackageJson: { - path: `${tscWatch.projectRoot}/packages/B/package.json`, - content: "{}" - }, - aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo'; + bFoo: file("B", "index.ts", `export function foo() { }`), + bBar: file("B", "bar.ts", `export function bar() { }`), + bSymlink: { + path: `${tscWatch.projectRoot}/node_modules/${scope}b`, + symLink: `${tscWatch.projectRoot}/packages/B` + } + })); + + verifySymlinkScenario(`when referencing file from subFolder${scope ? " with scoped package" : ""}`, () => ({ + bPackageJson: { + path: `${tscWatch.projectRoot}/packages/B/package.json`, + content: "{}" + }, + aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo'; import { bar } from '${scope}b/lib/bar/foo'; foo(); bar(); `), - bFoo: file("B", "foo.ts", `export function foo() { }`), - bBar: file("B", "bar/foo.ts", `export function bar() { }`), - bSymlink: { - path: `${tscWatch.projectRoot}/node_modules/${scope}b`, - symLink: `${tscWatch.projectRoot}/packages/B` - } - })); - } + bFoo: file("B", "foo.ts", `export function foo() { }`), + bBar: file("B", "bar/foo.ts", `export function bar() { }`), + bSymlink: { + path: `${tscWatch.projectRoot}/node_modules/${scope}b`, + symLink: `${tscWatch.projectRoot}/packages/B` + } + })); + } - describe("when package is not scoped", () => { - verifyMonoRepoLike(); - }); - describe("when package is scoped", () => { - verifyMonoRepoLike("@issue/"); - }); + describe("when package is not scoped", () => { + verifyMonoRepoLike(); }); + describe("when package is scoped", () => { + verifyMonoRepoLike("@issue/"); + }); + }); - it("when the referenced projects have allowJs and emitDeclarationOnly", () => { - const compositeConfig: File = { - path: `${tscWatch.projectRoot}/packages/emit-composite/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - allowJs: true, - emitDeclarationOnly: true, - outDir: "lib", - rootDir: "src" - }, - include: ["src"] - }) - }; - const compositePackageJson: File = { - path: `${tscWatch.projectRoot}/packages/emit-composite/package.json`, - content: JSON.stringify({ - name: "emit-composite", - version: "1.0.0", - main: "src/index.js", - typings: "lib/index.d.ts" - }) - }; - const compositeIndex: File = { - path: `${tscWatch.projectRoot}/packages/emit-composite/src/index.js`, - content: `const testModule = require('./testModule'); + it("when the referenced projects have allowJs and emitDeclarationOnly", () => { + const compositeConfig: File = { + path: `${tscWatch.projectRoot}/packages/emit-composite/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + allowJs: true, + emitDeclarationOnly: true, + outDir: "lib", + rootDir: "src" + }, + include: ["src"] + }) + }; + const compositePackageJson: File = { + path: `${tscWatch.projectRoot}/packages/emit-composite/package.json`, + content: JSON.stringify({ + name: "emit-composite", + version: "1.0.0", + main: "src/index.js", + typings: "lib/index.d.ts" + }) + }; + const compositeIndex: File = { + path: `${tscWatch.projectRoot}/packages/emit-composite/src/index.js`, + content: `const testModule = require('./testModule'); module.exports = { ...testModule }` - }; - const compositeTestModule: File = { - path: `${tscWatch.projectRoot}/packages/emit-composite/src/testModule.js`, - content: `/** + }; + const compositeTestModule: File = { + path: `${tscWatch.projectRoot}/packages/emit-composite/src/testModule.js`, + content: `/** * @param {string} arg */ const testCompositeFunction = (arg) => { @@ -473,1002 +473,1002 @@ module.exports = { module.exports = { testCompositeFunction }` - }; - const consumerConfig: File = { - path: `${tscWatch.projectRoot}/packages/consumer/tsconfig.json`, - content: JSON.stringify({ - include: ["src"], - references: [{ path: "../emit-composite" }] - }) - }; - const consumerIndex: File = { - path: `${tscWatch.projectRoot}/packages/consumer/src/index.ts`, - content: `import { testCompositeFunction } from 'emit-composite'; + }; + const consumerConfig: File = { + path: `${tscWatch.projectRoot}/packages/consumer/tsconfig.json`, + content: JSON.stringify({ + include: ["src"], + references: [{ path: "../emit-composite" }] + }) + }; + const consumerIndex: File = { + path: `${tscWatch.projectRoot}/packages/consumer/src/index.ts`, + content: `import { testCompositeFunction } from 'emit-composite'; testCompositeFunction('why hello there'); testCompositeFunction('why hello there', 42);` - }; - const symlink: SymLink = { - path: `${tscWatch.projectRoot}/node_modules/emit-composite`, - symLink: `${tscWatch.projectRoot}/packages/emit-composite` - }; - const host = createServerHost([libFile, compositeConfig, compositePackageJson, compositeIndex, compositeTestModule, consumerConfig, consumerIndex, symlink], { useCaseSensitiveFileNames: true }); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([consumerIndex], session); - verifyGetErrRequest({ host, session, files: [consumerIndex] }); - baselineTsserverLogs("projectReferences", `when the referenced projects have allowJs and emitDeclarationOnly`, session); - }); + }; + const symlink: SymLink = { + path: `${tscWatch.projectRoot}/node_modules/emit-composite`, + symLink: `${tscWatch.projectRoot}/packages/emit-composite` + }; + const host = createServerHost([libFile, compositeConfig, compositePackageJson, compositeIndex, compositeTestModule, consumerConfig, consumerIndex, symlink], { useCaseSensitiveFileNames: true }); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([consumerIndex], session); + verifyGetErrRequest({ host, session, files: [consumerIndex] }); + baselineTsserverLogs("projectReferences", `when the referenced projects have allowJs and emitDeclarationOnly`, session); + }); - it("when finding local reference doesnt load ancestor/sibling projects", () => { - const solutionLocation = "/user/username/projects/solution"; - const solution: File = { - path: `${solutionLocation}/tsconfig.json`, - content: JSON.stringify({ - files: [], - include: [], - references: [ - { path: "./compiler" }, - { path: "./services" }, - ] - }) - }; - const compilerConfig: File = { - path: `${solutionLocation}/compiler/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - module: "none" - }, - files: ["./types.ts", "./program.ts"] - }) - }; - const typesFile: File = { - path: `${solutionLocation}/compiler/types.ts`, - content: ` + it("when finding local reference doesnt load ancestor/sibling projects", () => { + const solutionLocation = "/user/username/projects/solution"; + const solution: File = { + path: `${solutionLocation}/tsconfig.json`, + content: JSON.stringify({ + files: [], + include: [], + references: [ + { path: "./compiler" }, + { path: "./services" }, + ] + }) + }; + const compilerConfig: File = { + path: `${solutionLocation}/compiler/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + module: "none" + }, + files: ["./types.ts", "./program.ts"] + }) + }; + const typesFile: File = { + path: `${solutionLocation}/compiler/types.ts`, + content: ` namespace ts { export interface Program { getSourceFiles(): string[]; } }` - }; - const programFile: File = { - path: `${solutionLocation}/compiler/program.ts`, - content: ` + }; + const programFile: File = { + path: `${solutionLocation}/compiler/program.ts`, + content: ` namespace ts { export const program: Program = { getSourceFiles: () => [getSourceFile()] }; function getSourceFile() { return "something"; } }` - }; - const servicesConfig: File = { - path: `${solutionLocation}/services/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true - }, - files: ["./services.ts"], - references: [ - { path: "../compiler" } - ] - }) - }; - const servicesFile: File = { - path: `${solutionLocation}/services/services.ts`, - content: ` + }; + const servicesConfig: File = { + path: `${solutionLocation}/services/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true + }, + files: ["./services.ts"], + references: [ + { path: "../compiler" } + ] + }) + }; + const servicesFile: File = { + path: `${solutionLocation}/services/services.ts`, + content: ` namespace ts { const result = program.getSourceFiles(); }` - }; + }; - const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile]; - const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([programFile], session); - - // Find all references for getSourceFile - // Shouldnt load more projects - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(programFile, "getSourceFile", { index: 1 }) - }); + const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile]; + const host = createServerHost(files); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([programFile], session); + + // Find all references for getSourceFile + // Shouldnt load more projects + session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: protocolFileLocationFromSubstring(programFile, "getSourceFile", { index: 1 }) + }); - // Find all references for getSourceFiles - // Should load more projects - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(programFile, "getSourceFiles") - }); - baselineTsserverLogs("projectReferences", `finding local reference doesnt load ancestor/sibling projects`, session); + // Find all references for getSourceFiles + // Should load more projects + session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: protocolFileLocationFromSubstring(programFile, "getSourceFiles") }); + baselineTsserverLogs("projectReferences", `finding local reference doesnt load ancestor/sibling projects`, session); + }); - describe("special handling of localness of the definitions for findAllRefs", () => { - function verify(scenario: string, definition: string, usage: string, referenceTerm: string) { - it(scenario, () => { - const solutionLocation = "/user/username/projects/solution"; - const solution: File = { - path: `${solutionLocation}/tsconfig.json`, - content: JSON.stringify({ - files: [], - references: [ - { path: "./api" }, - { path: "./app" }, - ] - }) - }; - const apiConfig: File = { - path: `${solutionLocation}/api/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "dist", - rootDir: "src", - }, - include: ["src"], - references: [{ path: "../shared" }] - }) - }; - const apiFile: File = { - path: `${solutionLocation}/api/src/server.ts`, - content: `import * as shared from "../../shared/dist"; + describe("special handling of localness of the definitions for findAllRefs", () => { + function verify(scenario: string, definition: string, usage: string, referenceTerm: string) { + it(scenario, () => { + const solutionLocation = "/user/username/projects/solution"; + const solution: File = { + path: `${solutionLocation}/tsconfig.json`, + content: JSON.stringify({ + files: [], + references: [ + { path: "./api" }, + { path: "./app" }, + ] + }) + }; + const apiConfig: File = { + path: `${solutionLocation}/api/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "dist", + rootDir: "src", + }, + include: ["src"], + references: [{ path: "../shared" }] + }) + }; + const apiFile: File = { + path: `${solutionLocation}/api/src/server.ts`, + content: `import * as shared from "../../shared/dist"; ${usage}` - }; - const appConfig: File = { - path: `${solutionLocation}/app/tsconfig.json`, - content: apiConfig.content - }; - const appFile: File = { - path: `${solutionLocation}/app/src/app.ts`, - content: apiFile.content - }; - const sharedConfig: File = { - path: `${solutionLocation}/shared/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "dist", - rootDir: "src", - }, - include: ["src"] - }) - }; - const sharedFile: File = { - path: `${solutionLocation}/shared/src/index.ts`, - content: definition - }; - const host = createServerHost([libFile, solution, libFile, apiConfig, apiFile, appConfig, appFile, sharedConfig, sharedFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([apiFile], session); - - // Find all references - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(apiFile, referenceTerm) - }); + }; + const appConfig: File = { + path: `${solutionLocation}/app/tsconfig.json`, + content: apiConfig.content + }; + const appFile: File = { + path: `${solutionLocation}/app/src/app.ts`, + content: apiFile.content + }; + const sharedConfig: File = { + path: `${solutionLocation}/shared/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "dist", + rootDir: "src", + }, + include: ["src"] + }) + }; + const sharedFile: File = { + path: `${solutionLocation}/shared/src/index.ts`, + content: definition + }; + const host = createServerHost([libFile, solution, libFile, apiConfig, apiFile, appConfig, appFile, sharedConfig, sharedFile]); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([apiFile], session); - baselineTsserverLogs("projectReferences", `special handling of localness ${scenario}`, session); + // Find all references + session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: protocolFileLocationFromSubstring(apiFile, referenceTerm) }); - } - - verify( - "when using arrow function assignment", - `export const dog = () => { };`, - `shared.dog();`, - "dog" - ); - - verify( - "when using arrow function as object literal property types", - `export const foo = { bar: () => { } };`, - `shared.foo.bar();`, - "bar" - ); - - verify( - "when using object literal property", - `export const foo = { baz: "BAZ" };`, - `shared.foo.baz;`, - "baz" - ); - verify( - "when using method of class expression", - `export const foo = class { fly() {} };`, - `const instance = new shared.foo(); + baselineTsserverLogs("projectReferences", `special handling of localness ${scenario}`, session); + }); + } + + verify( + "when using arrow function assignment", + `export const dog = () => { };`, + `shared.dog();`, + "dog" + ); + + verify( + "when using arrow function as object literal property types", + `export const foo = { bar: () => { } };`, + `shared.foo.bar();`, + "bar" + ); + + verify( + "when using object literal property", + `export const foo = { baz: "BAZ" };`, + `shared.foo.baz;`, + "baz" + ); + + verify( + "when using method of class expression", + `export const foo = class { fly() {} };`, + `const instance = new shared.foo(); instance.fly();`, - "fly" - ); + "fly" + ); - verify( - // when using arrow function as object literal property is loaded through indirect assignment with original declaration local to project is treated as local - "when using arrow function as object literal property", - `const local = { bar: () => { } }; + verify( + // when using arrow function as object literal property is loaded through indirect assignment with original declaration local to project is treated as local + "when using arrow function as object literal property", + `const local = { bar: () => { } }; export const foo = local;`, - `shared.foo.bar();`, - "bar" - ); - }); + `shared.foo.bar();`, + "bar" + ); + }); - it("when disableSolutionSearching is true, solution and siblings are not loaded", () => { - const solutionLocation = "/user/username/projects/solution"; - const solution: File = { - path: `${solutionLocation}/tsconfig.json`, - content: JSON.stringify({ - files: [], - include: [], - references: [ - { path: "./compiler" }, - { path: "./services" }, - ] - }) - }; - const compilerConfig: File = { - path: `${solutionLocation}/compiler/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - module: "none", - disableSolutionSearching: true - }, - files: ["./types.ts", "./program.ts"] - }) - }; - const typesFile: File = { - path: `${solutionLocation}/compiler/types.ts`, - content: ` + it("when disableSolutionSearching is true, solution and siblings are not loaded", () => { + const solutionLocation = "/user/username/projects/solution"; + const solution: File = { + path: `${solutionLocation}/tsconfig.json`, + content: JSON.stringify({ + files: [], + include: [], + references: [ + { path: "./compiler" }, + { path: "./services" }, + ] + }) + }; + const compilerConfig: File = { + path: `${solutionLocation}/compiler/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + module: "none", + disableSolutionSearching: true + }, + files: ["./types.ts", "./program.ts"] + }) + }; + const typesFile: File = { + path: `${solutionLocation}/compiler/types.ts`, + content: ` namespace ts { export interface Program { getSourceFiles(): string[]; } }` - }; - const programFile: File = { - path: `${solutionLocation}/compiler/program.ts`, - content: ` + }; + const programFile: File = { + path: `${solutionLocation}/compiler/program.ts`, + content: ` namespace ts { export const program: Program = { getSourceFiles: () => [getSourceFile()] }; function getSourceFile() { return "something"; } }` - }; - const servicesConfig: File = { - path: `${solutionLocation}/services/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true - }, - files: ["./services.ts"], - references: [ - { path: "../compiler" } - ] - }) - }; - const servicesFile: File = { - path: `${solutionLocation}/services/services.ts`, - content: ` + }; + const servicesConfig: File = { + path: `${solutionLocation}/services/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true + }, + files: ["./services.ts"], + references: [ + { path: "../compiler" } + ] + }) + }; + const servicesFile: File = { + path: `${solutionLocation}/services/services.ts`, + content: ` namespace ts { const result = program.getSourceFiles(); }` - }; + }; - const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile]; - const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([programFile], session); - - // Find all references - // No new solutions/projects loaded - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(programFile, "getSourceFiles") - }); - baselineTsserverLogs("projectReferences", `with disableSolutionSearching solution and siblings are not loaded`, session); + const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile]; + const host = createServerHost(files); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([programFile], session); + + // Find all references + // No new solutions/projects loaded + session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: protocolFileLocationFromSubstring(programFile, "getSourceFiles") }); + baselineTsserverLogs("projectReferences", `with disableSolutionSearching solution and siblings are not loaded`, session); + }); - describe("when default project is solution project", () => { - interface Setup { - scenario: string; - solutionOptions?: CompilerOptions; - solutionFiles?: string[]; - configRefs: string[]; - additionalFiles: readonly File[]; - } - const main: File = { - path: `${tscWatch.projectRoot}/src/main.ts`, - content: `import { foo } from 'helpers/functions'; + describe("when default project is solution project", () => { + interface Setup { + scenario: string; + solutionOptions?: CompilerOptions; + solutionFiles?: string[]; + configRefs: string[]; + additionalFiles: readonly File[]; + } + const main: File = { + path: `${tscWatch.projectRoot}/src/main.ts`, + content: `import { foo } from 'helpers/functions'; export { foo };` - }; - const helper: File = { - path: `${tscWatch.projectRoot}/src/helpers/functions.ts`, - content: `export const foo = 1;` - }; - const mainDts: File = { - path: `${tscWatch.projectRoot}/target/src/main.d.ts`, - content: `import { foo } from 'helpers/functions'; + }; + const helper: File = { + path: `${tscWatch.projectRoot}/src/helpers/functions.ts`, + content: `export const foo = 1;` + }; + const mainDts: File = { + path: `${tscWatch.projectRoot}/target/src/main.d.ts`, + content: `import { foo } from 'helpers/functions'; export { foo }; //# sourceMappingURL=main.d.ts.map` - }; - const mainDtsMap: File = { - path: `${tscWatch.projectRoot}/target/src/main.d.ts.map`, - content: `{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,OAAO,EAAC,GAAG,EAAC,CAAC"}` - }; - const helperDts: File = { - path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts`, - content: `export declare const foo = 1; + }; + const mainDtsMap: File = { + path: `${tscWatch.projectRoot}/target/src/main.d.ts.map`, + content: `{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,OAAO,EAAC,GAAG,EAAC,CAAC"}` + }; + const helperDts: File = { + path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts`, + content: `export declare const foo = 1; //# sourceMappingURL=functions.d.ts.map` - }; - const helperDtsMap: File = { - path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts.map`, - content: `{"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../src/helpers/functions.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,GAAG,IAAI,CAAC"}` - }; - const tsconfigIndirect3: File = { - path: `${tscWatch.projectRoot}/indirect3/tsconfig.json`, + }; + const helperDtsMap: File = { + path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts.map`, + content: `{"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../src/helpers/functions.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,GAAG,IAAI,CAAC"}` + }; + const tsconfigIndirect3: File = { + path: `${tscWatch.projectRoot}/indirect3/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + baseUrl: "../target/src/" + }, + }) + }; + const fileResolvingToMainDts: File = { + path: `${tscWatch.projectRoot}/indirect3/main.ts`, + content: `import { foo } from 'main'; +foo; +export function bar() {}` + }; + const tsconfigSrcPath = `${tscWatch.projectRoot}/tsconfig-src.json`; + const tsconfigPath = `${tscWatch.projectRoot}/tsconfig.json`; + const dummyFilePath = "/dummy/dummy.ts"; + function setup({ solutionFiles, solutionOptions, configRefs, additionalFiles }: Setup) { + const tsconfigSrc: File = { + path: tsconfigSrcPath, content: JSON.stringify({ compilerOptions: { - baseUrl: "../target/src/" + composite: true, + outDir: "./target/", + baseUrl: "./src/" }, + include: ["./src/**/*"] }) }; - const fileResolvingToMainDts: File = { - path: `${tscWatch.projectRoot}/indirect3/main.ts`, - content: `import { foo } from 'main'; -foo; -export function bar() {}` + const tsconfig: File = { + path: tsconfigPath, + content: JSON.stringify({ + ... (solutionOptions ? { compilerOptions: solutionOptions } : {}), + references: configRefs.map(path => ({ path })), + files: solutionFiles || [] + }) }; - const tsconfigSrcPath = `${tscWatch.projectRoot}/tsconfig-src.json`; - const tsconfigPath = `${tscWatch.projectRoot}/tsconfig.json`; - const dummyFilePath = "/dummy/dummy.ts"; - function setup({ solutionFiles, solutionOptions, configRefs, additionalFiles }: Setup) { - const tsconfigSrc: File = { - path: tsconfigSrcPath, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "./target/", - baseUrl: "./src/" - }, - include: ["./src/**/*"] - }) - }; - const tsconfig: File = { - path: tsconfigPath, - content: JSON.stringify({ - ... (solutionOptions ? { compilerOptions: solutionOptions } : {}), - references: configRefs.map(path => ({ path })), - files: solutionFiles || [] - }) - }; - const dummyFile: File = { - path: dummyFilePath, - content: "let a = 10;" - }; - const host = createServerHost([ - tsconfigSrc, tsconfig, main, helper, - libFile, dummyFile, - mainDts, mainDtsMap, helperDts, helperDtsMap, - tsconfigIndirect3, fileResolvingToMainDts, - ...additionalFiles]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); - const service = session.getProjectService(); - service.openClientFile(main.path); - return { session, service, host }; - } + const dummyFile: File = { + path: dummyFilePath, + content: "let a = 10;" + }; + const host = createServerHost([ + tsconfigSrc, tsconfig, main, helper, + libFile, dummyFile, + mainDts, mainDtsMap, helperDts, helperDtsMap, + tsconfigIndirect3, fileResolvingToMainDts, + ...additionalFiles]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs() }); + const service = session.getProjectService(); + service.openClientFile(main.path); + return { session, service, host }; + } - function verifySolutionScenario(input: Setup) { - const { session, service, host } = setup(input); + function verifySolutionScenario(input: Setup) { + const { session, service, host } = setup(input); - const info = service.getScriptInfoForPath(main.path as Path)!; - session.logger.logs.push(""); - session.logger.logs.push(`getDefaultProject for ${main.path}: ${info.getDefaultProject().projectName}`); - session.logger.logs.push(`findDefaultConfiguredProject for ${main.path}: ${service.findDefaultConfiguredProject(info)!.projectName}`); - session.logger.logs.push(""); + const info = service.getScriptInfoForPath(main.path as Path)!; + session.logger.logs.push(""); + session.logger.logs.push(`getDefaultProject for ${main.path}: ${info.getDefaultProject().projectName}`); + session.logger.logs.push(`findDefaultConfiguredProject for ${main.path}: ${service.findDefaultConfiguredProject(info)!.projectName}`); + session.logger.logs.push(""); - // Verify errors - verifyGetErrRequest({ session, host, files: [main] }); + // Verify errors + verifyGetErrRequest({ session, host, files: [main] }); - // Verify collection of script infos - service.openClientFile(dummyFilePath); + // Verify collection of script infos + service.openClientFile(dummyFilePath); - service.closeClientFile(main.path); - service.closeClientFile(dummyFilePath); - service.openClientFile(dummyFilePath); + service.closeClientFile(main.path); + service.closeClientFile(dummyFilePath); + service.openClientFile(dummyFilePath); - service.openClientFile(main.path); - service.closeClientFile(dummyFilePath); - service.openClientFile(dummyFilePath); + service.openClientFile(main.path); + service.closeClientFile(dummyFilePath); + service.openClientFile(dummyFilePath); - // Verify Reload projects - service.reloadProjects(); + // Verify Reload projects + service.reloadProjects(); - // Find all refs - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(main, "foo", { index: 1 }) - }).response as protocol.ReferencesResponseBody; + // Find all refs + session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: protocolFileLocationFromSubstring(main, "foo", { index: 1 }) + }).response as protocol.ReferencesResponseBody; - service.closeClientFile(main.path); - service.closeClientFile(dummyFilePath); + service.closeClientFile(main.path); + service.closeClientFile(dummyFilePath); - // Verify when declaration map references the file - service.openClientFile(fileResolvingToMainDts.path); + // Verify when declaration map references the file + service.openClientFile(fileResolvingToMainDts.path); - // Find all refs from dts include - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(fileResolvingToMainDts, "foo") - }).response as protocol.ReferencesResponseBody; - baselineTsserverLogs("projectReferences", input.scenario, session); - } + // Find all refs from dts include + session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: protocolFileLocationFromSubstring(fileResolvingToMainDts, "foo") + }).response as protocol.ReferencesResponseBody; + baselineTsserverLogs("projectReferences", input.scenario, session); + } + + function getIndirectProject(postfix: string, optionsToExtend?: CompilerOptions) { + const tsconfigIndirect: File = { + path: `${tscWatch.projectRoot}/tsconfig-indirect${postfix}.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "./target/", + baseUrl: "./src/", + ...optionsToExtend + }, + files: [`./indirect${postfix}/main.ts`], + references: [{ path: "./tsconfig-src.json" }] + }) + }; + const indirect: File = { + path: `${tscWatch.projectRoot}/indirect${postfix}/main.ts`, + content: fileResolvingToMainDts.content + }; + return { tsconfigIndirect, indirect }; + } - function getIndirectProject(postfix: string, optionsToExtend?: CompilerOptions) { - const tsconfigIndirect: File = { - path: `${tscWatch.projectRoot}/tsconfig-indirect${postfix}.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "./target/", - baseUrl: "./src/", - ...optionsToExtend - }, - files: [`./indirect${postfix}/main.ts`], - references: [{ path: "./tsconfig-src.json" }] - }) - }; - const indirect: File = { - path: `${tscWatch.projectRoot}/indirect${postfix}/main.ts`, - content: fileResolvingToMainDts.content - }; - return { tsconfigIndirect, indirect }; - } + function verifyDisableReferencedProjectLoad(input: Setup) { + const { session, service } = setup(input); - function verifyDisableReferencedProjectLoad(input: Setup) { - const { session, service } = setup(input); + const info = service.getScriptInfoForPath(main.path as Path)!; + session.logger.logs.push(""); + session.logger.logs.push(`getDefaultProject for ${main.path}: ${info.getDefaultProject().projectName}`); + session.logger.logs.push(`findDefaultConfiguredProject for ${main.path}: ${service.findDefaultConfiguredProject(info)?.projectName}`); + session.logger.logs.push(""); - const info = service.getScriptInfoForPath(main.path as Path)!; - session.logger.logs.push(""); - session.logger.logs.push(`getDefaultProject for ${main.path}: ${info.getDefaultProject().projectName}`); - session.logger.logs.push(`findDefaultConfiguredProject for ${main.path}: ${service.findDefaultConfiguredProject(info)?.projectName}`); - session.logger.logs.push(""); + // Verify collection of script infos + service.openClientFile(dummyFilePath); - // Verify collection of script infos - service.openClientFile(dummyFilePath); + service.closeClientFile(main.path); + service.closeClientFile(dummyFilePath); + service.openClientFile(dummyFilePath); - service.closeClientFile(main.path); - service.closeClientFile(dummyFilePath); - service.openClientFile(dummyFilePath); + service.openClientFile(main.path); - service.openClientFile(main.path); + // Verify Reload projects + service.reloadProjects(); + baselineTsserverLogs("projectReferences", input.scenario, session); + } - // Verify Reload projects - service.reloadProjects(); - baselineTsserverLogs("projectReferences", input.scenario, session); - } + it("when project is directly referenced by solution", () => { + verifySolutionScenario({ + scenario: "project is directly referenced by solution", + configRefs: ["./tsconfig-src.json"], + additionalFiles: emptyArray, + }); + }); + + it("when project is indirectly referenced by solution", () => { + const { tsconfigIndirect, indirect } = getIndirectProject("1"); + const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); + verifySolutionScenario({ + scenario: "project is indirectly referenced by solution", + configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], + additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], + }); + }); + + it("disables looking into the child project if disableReferencedProjectLoad is set", () => { + verifyDisableReferencedProjectLoad({ + scenario: "disables looking into the child project if disableReferencedProjectLoad is set", + solutionOptions: { disableReferencedProjectLoad: true }, + configRefs: ["./tsconfig-src.json"], + additionalFiles: emptyArray, + }); + }); + + it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => { + const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); + verifyDisableReferencedProjectLoad({ + scenario: "disables looking into the child project if disableReferencedProjectLoad is set in indirect project", + configRefs: ["./tsconfig-indirect1.json"], + additionalFiles: [tsconfigIndirect, indirect], + }); + }); - it("when project is directly referenced by solution", () => { + it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => { + const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); + const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); + verifyDisableReferencedProjectLoad({ + scenario: "disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", + configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], + additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], + }); + }); + + describe("when solution is project that contains its own files", () => { + it("when the project found is not solution but references open file through project reference", () => { + const ownMain: File = { + path: `${tscWatch.projectRoot}/own/main.ts`, + content: fileResolvingToMainDts.content + }; verifySolutionScenario({ - scenario: "project is directly referenced by solution", + scenario: "solution with its own files and project found is not solution but references open file through project reference", + solutionFiles: [`./own/main.ts`], + solutionOptions: { + outDir: "./target/", + baseUrl: "./src/" + }, configRefs: ["./tsconfig-src.json"], - additionalFiles: emptyArray, + additionalFiles: [ownMain], }); }); it("when project is indirectly referenced by solution", () => { + const ownMain: File = { + path: `${tscWatch.projectRoot}/own/main.ts`, + content: `import { bar } from 'main'; +bar;` + }; const { tsconfigIndirect, indirect } = getIndirectProject("1"); const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); verifySolutionScenario({ - scenario: "project is indirectly referenced by solution", + scenario: "solution with its own files and project is indirectly referenced by solution", + solutionFiles: [`./own/main.ts`], + solutionOptions: { + outDir: "./target/", + baseUrl: "./indirect1/" + }, configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], - additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], + additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain], }); }); it("disables looking into the child project if disableReferencedProjectLoad is set", () => { + const ownMain: File = { + path: `${tscWatch.projectRoot}/own/main.ts`, + content: fileResolvingToMainDts.content + }; verifyDisableReferencedProjectLoad({ - scenario: "disables looking into the child project if disableReferencedProjectLoad is set", - solutionOptions: { disableReferencedProjectLoad: true }, + scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set", + solutionFiles: [`./own/main.ts`], + solutionOptions: { + outDir: "./target/", + baseUrl: "./src/", + disableReferencedProjectLoad: true + }, configRefs: ["./tsconfig-src.json"], - additionalFiles: emptyArray, + additionalFiles: [ownMain], }); }); it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => { + const ownMain: File = { + path: `${tscWatch.projectRoot}/own/main.ts`, + content: `import { bar } from 'main'; +bar;` + }; const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); verifyDisableReferencedProjectLoad({ - scenario: "disables looking into the child project if disableReferencedProjectLoad is set in indirect project", + scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set in indirect project", + solutionFiles: [`./own/main.ts`], + solutionOptions: { + outDir: "./target/", + baseUrl: "./indirect1/", + }, configRefs: ["./tsconfig-indirect1.json"], - additionalFiles: [tsconfigIndirect, indirect], + additionalFiles: [tsconfigIndirect, indirect, ownMain], }); }); it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => { + const ownMain: File = { + path: `${tscWatch.projectRoot}/own/main.ts`, + content: `import { bar } from 'main'; +bar;` + }; const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); verifyDisableReferencedProjectLoad({ - scenario: "disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", + scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", + solutionFiles: [`./own/main.ts`], + solutionOptions: { + outDir: "./target/", + baseUrl: "./indirect1/", + }, configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], - additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], - }); - }); - - describe("when solution is project that contains its own files", () => { - it("when the project found is not solution but references open file through project reference", () => { - const ownMain: File = { - path: `${tscWatch.projectRoot}/own/main.ts`, - content: fileResolvingToMainDts.content - }; - verifySolutionScenario({ - scenario: "solution with its own files and project found is not solution but references open file through project reference", - solutionFiles: [`./own/main.ts`], - solutionOptions: { - outDir: "./target/", - baseUrl: "./src/" - }, - configRefs: ["./tsconfig-src.json"], - additionalFiles: [ownMain], - }); - }); - - it("when project is indirectly referenced by solution", () => { - const ownMain: File = { - path: `${tscWatch.projectRoot}/own/main.ts`, - content: `import { bar } from 'main'; -bar;` - }; - const { tsconfigIndirect, indirect } = getIndirectProject("1"); - const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); - verifySolutionScenario({ - scenario: "solution with its own files and project is indirectly referenced by solution", - solutionFiles: [`./own/main.ts`], - solutionOptions: { - outDir: "./target/", - baseUrl: "./indirect1/" - }, - configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], - additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain], - }); - }); - - it("disables looking into the child project if disableReferencedProjectLoad is set", () => { - const ownMain: File = { - path: `${tscWatch.projectRoot}/own/main.ts`, - content: fileResolvingToMainDts.content - }; - verifyDisableReferencedProjectLoad({ - scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set", - solutionFiles: [`./own/main.ts`], - solutionOptions: { - outDir: "./target/", - baseUrl: "./src/", - disableReferencedProjectLoad: true - }, - configRefs: ["./tsconfig-src.json"], - additionalFiles: [ownMain], - }); - }); - - it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => { - const ownMain: File = { - path: `${tscWatch.projectRoot}/own/main.ts`, - content: `import { bar } from 'main'; -bar;` - }; - const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); - verifyDisableReferencedProjectLoad({ - scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set in indirect project", - solutionFiles: [`./own/main.ts`], - solutionOptions: { - outDir: "./target/", - baseUrl: "./indirect1/", - }, - configRefs: ["./tsconfig-indirect1.json"], - additionalFiles: [tsconfigIndirect, indirect, ownMain], - }); - }); - - it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => { - const ownMain: File = { - path: `${tscWatch.projectRoot}/own/main.ts`, - content: `import { bar } from 'main'; -bar;` - }; - const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); - const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); - verifyDisableReferencedProjectLoad({ - scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", - solutionFiles: [`./own/main.ts`], - solutionOptions: { - outDir: "./target/", - baseUrl: "./indirect1/", - }, - configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], - additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain], - }); + additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain], }); }); }); + }); - describe("when new file is added to the referenced project", () => { - function setup(extendOptionsProject2?: CompilerOptions) { - const config1: File = { - path: `${tscWatch.projectRoot}/projects/project1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - exclude: ["temp"] - }) - }; - const class1: File = { - path: `${tscWatch.projectRoot}/projects/project1/class1.ts`, - content: `class class1 {}` - }; - const class1Dts: File = { - path: `${tscWatch.projectRoot}/projects/project1/class1.d.ts`, - content: `declare class class1 {}` - }; - const config2: File = { - path: `${tscWatch.projectRoot}/projects/project2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true, - ...(extendOptionsProject2 || {}) - }, - references: [ - { path: "../project1" } - ] - }) - }; - const class2: File = { - path: `${tscWatch.projectRoot}/projects/project2/class2.ts`, - content: `class class2 {}` - }; - const host = createServerHost([config1, class1, class1Dts, config2, class2, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([class2], session); - return { host, session, class1 }; - } - - it("when referenced project is not open", () => { - const { host, session } = setup(); - - // Add new class to referenced project - const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; - host.writeFile(class3, `class class3 {}`); - host.checkTimeoutQueueLengthAndRun(2); - - // Add excluded file to referenced project - host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); - host.checkTimeoutQueueLengthAndRun(0); - - // Add output from new class to referenced project - const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; - host.writeFile(class3Dts, `declare class class3 {}`); - host.checkTimeoutQueueLengthAndRun(0); - baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is not open`, session); - }); + describe("when new file is added to the referenced project", () => { + function setup(extendOptionsProject2?: CompilerOptions) { + const config1: File = { + path: `${tscWatch.projectRoot}/projects/project1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true + }, + exclude: ["temp"] + }) + }; + const class1: File = { + path: `${tscWatch.projectRoot}/projects/project1/class1.ts`, + content: `class class1 {}` + }; + const class1Dts: File = { + path: `${tscWatch.projectRoot}/projects/project1/class1.d.ts`, + content: `declare class class1 {}` + }; + const config2: File = { + path: `${tscWatch.projectRoot}/projects/project2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true, + ...(extendOptionsProject2 || {}) + }, + references: [ + { path: "../project1" } + ] + }) + }; + const class2: File = { + path: `${tscWatch.projectRoot}/projects/project2/class2.ts`, + content: `class class2 {}` + }; + const host = createServerHost([config1, class1, class1Dts, config2, class2, libFile]); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([class2], session); + return { host, session, class1 }; + } + + it("when referenced project is not open", () => { + const { host, session } = setup(); + + // Add new class to referenced project + const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; + host.writeFile(class3, `class class3 {}`); + host.checkTimeoutQueueLengthAndRun(2); + + // Add excluded file to referenced project + host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); + host.checkTimeoutQueueLengthAndRun(0); + + // Add output from new class to referenced project + const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; + host.writeFile(class3Dts, `declare class class3 {}`); + host.checkTimeoutQueueLengthAndRun(0); + baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is not open`, session); + }); - it("when referenced project is open", () => { - const { host, session, class1 } = setup(); - openFilesForSession([class1], session); - - // Add new class to referenced project - const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; - host.writeFile(class3, `class class3 {}`); - host.checkTimeoutQueueLengthAndRun(3); - // Add excluded file to referenced project - host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); - host.checkTimeoutQueueLengthAndRun(0); - // Add output from new class to referenced project - const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; - host.writeFile(class3Dts, `declare class class3 {}`); - host.checkTimeoutQueueLengthAndRun(0); - baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is open`, session); - }); + it("when referenced project is open", () => { + const { host, session, class1 } = setup(); + openFilesForSession([class1], session); + + // Add new class to referenced project + const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; + host.writeFile(class3, `class class3 {}`); + host.checkTimeoutQueueLengthAndRun(3); + // Add excluded file to referenced project + host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); + host.checkTimeoutQueueLengthAndRun(0); + // Add output from new class to referenced project + const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; + host.writeFile(class3Dts, `declare class class3 {}`); + host.checkTimeoutQueueLengthAndRun(0); + baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is open`, session); + }); - it("when referenced project is not open with disableSourceOfProjectReferenceRedirect", () => { - const { host, session } = setup({ disableSourceOfProjectReferenceRedirect: true }); - - // Add new class to referenced project - const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; - host.writeFile(class3, `class class3 {}`); - host.checkTimeoutQueueLengthAndRun(2); - // Add output of new class to referenced project - const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; - host.writeFile(class3Dts, `declare class class3 {}`); - host.checkTimeoutQueueLengthAndRun(2); - // Add excluded file to referenced project - host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); - host.checkTimeoutQueueLengthAndRun(0); - // Delete output from new class to referenced project - host.deleteFile(class3Dts); - host.checkTimeoutQueueLengthAndRun(2); - // Write back output of new class to referenced project - host.writeFile(class3Dts, `declare class class3 {}`); - host.checkTimeoutQueueLengthAndRun(2); - baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is not open with disableSourceOfProjectReferenceRedirect`, session); - }); + it("when referenced project is not open with disableSourceOfProjectReferenceRedirect", () => { + const { host, session } = setup({ disableSourceOfProjectReferenceRedirect: true }); + + // Add new class to referenced project + const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; + host.writeFile(class3, `class class3 {}`); + host.checkTimeoutQueueLengthAndRun(2); + // Add output of new class to referenced project + const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; + host.writeFile(class3Dts, `declare class class3 {}`); + host.checkTimeoutQueueLengthAndRun(2); + // Add excluded file to referenced project + host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); + host.checkTimeoutQueueLengthAndRun(0); + // Delete output from new class to referenced project + host.deleteFile(class3Dts); + host.checkTimeoutQueueLengthAndRun(2); + // Write back output of new class to referenced project + host.writeFile(class3Dts, `declare class class3 {}`); + host.checkTimeoutQueueLengthAndRun(2); + baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is not open with disableSourceOfProjectReferenceRedirect`, session); + }); - it("when referenced project is open with disableSourceOfProjectReferenceRedirect", () => { - const { host, session, class1 } = setup({ disableSourceOfProjectReferenceRedirect: true }); - openFilesForSession([class1], session); - - // Add new class to referenced project - const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; - host.writeFile(class3, `class class3 {}`); - host.checkTimeoutQueueLengthAndRun(3); - // Add output of new class to referenced project - const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; - host.writeFile(class3Dts, `declare class class3 {}`); - host.checkTimeoutQueueLengthAndRun(2); - // Add excluded file to referenced project - host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); - host.checkTimeoutQueueLengthAndRun(0); - // Delete output from new class to referenced project - host.deleteFile(class3Dts); - host.checkTimeoutQueueLengthAndRun(2); - // Write back output of new class to referenced project - host.writeFile(class3Dts, `declare class class3 {}`); - host.checkTimeoutQueueLengthAndRun(2); - baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is open with disableSourceOfProjectReferenceRedirect`, session); - }); + it("when referenced project is open with disableSourceOfProjectReferenceRedirect", () => { + const { host, session, class1 } = setup({ disableSourceOfProjectReferenceRedirect: true }); + openFilesForSession([class1], session); + + // Add new class to referenced project + const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; + host.writeFile(class3, `class class3 {}`); + host.checkTimeoutQueueLengthAndRun(3); + // Add output of new class to referenced project + const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; + host.writeFile(class3Dts, `declare class class3 {}`); + host.checkTimeoutQueueLengthAndRun(2); + // Add excluded file to referenced project + host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); + host.checkTimeoutQueueLengthAndRun(0); + // Delete output from new class to referenced project + host.deleteFile(class3Dts); + host.checkTimeoutQueueLengthAndRun(2); + // Write back output of new class to referenced project + host.writeFile(class3Dts, `declare class class3 {}`); + host.checkTimeoutQueueLengthAndRun(2); + baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is open with disableSourceOfProjectReferenceRedirect`, session); }); + }); - describe("auto import with referenced project", () => { - function verifyAutoImport(built: boolean, disableSourceOfProjectReferenceRedirect?: boolean) { - const solnConfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - files: [], - references: [ - { path: "shared/src/library" }, - { path: "app/src/program" } - ] - }) - }; - const sharedConfig: File = { - path: `${tscWatch.projectRoot}/shared/src/library/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "../../bld/library" - } - }) - }; - const sharedIndex: File = { - path: `${tscWatch.projectRoot}/shared/src/library/index.ts`, - content: `export function foo() {}` - }; - const sharedPackage: File = { - path: `${tscWatch.projectRoot}/shared/package.json`, - content: JSON.stringify({ - name: "shared", - version: "1.0.0", - main: "bld/library/index.js", - types: "bld/library/index.d.ts" - }) - }; - const appConfig: File = { - path: `${tscWatch.projectRoot}/app/src/program/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "../../bld/program", - disableSourceOfProjectReferenceRedirect - }, - references: [ - { path: "../../../shared/src/library" } - ] - }) - }; - const appBar: File = { - path: `${tscWatch.projectRoot}/app/src/program/bar.ts`, - content: `import {foo} from "shared";` - }; - const appIndex: File = { - path: `${tscWatch.projectRoot}/app/src/program/index.ts`, - content: `foo` - }; - const sharedSymlink: SymLink = { - path: `${tscWatch.projectRoot}/node_modules/shared`, - symLink: `${tscWatch.projectRoot}/shared` - }; - const files = [solnConfig, sharedConfig, sharedIndex, sharedPackage, appConfig, appBar, appIndex, sharedSymlink, libFile]; - const host = createServerHost(files); - if (built) { - const solutionBuilder = tscWatch.createSolutionBuilder(host, [solnConfig.path], {}); - solutionBuilder.build(); - host.clearOutput(); - } - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([appIndex], session); - session.executeCommandSeq({ - command: protocol.CommandTypes.GetCodeFixes, - arguments: { - file: appIndex.path, - startLine: 1, - startOffset: 1, - endLine: 1, - endOffset: 4, - errorCodes: [Diagnostics.Cannot_find_name_0.code], + describe("auto import with referenced project", () => { + function verifyAutoImport(built: boolean, disableSourceOfProjectReferenceRedirect?: boolean) { + const solnConfig: File = { + path: `${tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + files: [], + references: [ + { path: "shared/src/library" }, + { path: "app/src/program" } + ] + }) + }; + const sharedConfig: File = { + path: `${tscWatch.projectRoot}/shared/src/library/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "../../bld/library" } - }); - baselineTsserverLogs("projectReferences", `auto import with referenced project${built ? " when built" : ""}${disableSourceOfProjectReferenceRedirect ? " with disableSourceOfProjectReferenceRedirect": ""}`, session); + }) + }; + const sharedIndex: File = { + path: `${tscWatch.projectRoot}/shared/src/library/index.ts`, + content: `export function foo() {}` + }; + const sharedPackage: File = { + path: `${tscWatch.projectRoot}/shared/package.json`, + content: JSON.stringify({ + name: "shared", + version: "1.0.0", + main: "bld/library/index.js", + types: "bld/library/index.d.ts" + }) + }; + const appConfig: File = { + path: `${tscWatch.projectRoot}/app/src/program/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "../../bld/program", + disableSourceOfProjectReferenceRedirect + }, + references: [ + { path: "../../../shared/src/library" } + ] + }) + }; + const appBar: File = { + path: `${tscWatch.projectRoot}/app/src/program/bar.ts`, + content: `import {foo} from "shared";` + }; + const appIndex: File = { + path: `${tscWatch.projectRoot}/app/src/program/index.ts`, + content: `foo` + }; + const sharedSymlink: SymLink = { + path: `${tscWatch.projectRoot}/node_modules/shared`, + symLink: `${tscWatch.projectRoot}/shared` + }; + const files = [solnConfig, sharedConfig, sharedIndex, sharedPackage, appConfig, appBar, appIndex, sharedSymlink, libFile]; + const host = createServerHost(files); + if (built) { + const solutionBuilder = tscWatch.createSolutionBuilder(host, [solnConfig.path], {}); + solutionBuilder.build(); + host.clearOutput(); } - - it("when project is built", () => { - verifyAutoImport(/*built*/ true); - }); - it("when project is not built", () => { - verifyAutoImport(/*built*/ false); - }); - it("when disableSourceOfProjectReferenceRedirect is true", () => { - verifyAutoImport(/*built*/ true, /*disableSourceOfProjectReferenceRedirect*/ true); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([appIndex], session); + session.executeCommandSeq({ + command: protocol.CommandTypes.GetCodeFixes, + arguments: { + file: appIndex.path, + startLine: 1, + startOffset: 1, + endLine: 1, + endOffset: 4, + errorCodes: [Diagnostics.Cannot_find_name_0.code], + } }); - }); - - it("when files from two projects are open and one project references", () => { - function getPackageAndFile(packageName: string, references?: string[], optionsToExtend?: CompilerOptions): [file: File, config: File] { - const file: File = { - path: `${tscWatch.projectRoot}/${packageName}/src/file1.ts`, - content: `export const ${packageName}Const = 10;` - }; - const config: File = { - path: `${tscWatch.projectRoot}/${packageName}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, ...optionsToExtend || {} }, - references: references?.map(path => ({ path: `../${path}` })) - }) - }; - return [file, config]; - } - const [mainFile, mainConfig] = getPackageAndFile("main", ["core", "indirect", "noCoreRef1", "indirectDisabledChildLoad1", "indirectDisabledChildLoad2", "refToCoreRef3", "indirectNoCoreRef"]); - const [coreFile, coreConfig] = getPackageAndFile("core"); - const [noCoreRef1File, noCoreRef1Config] = getPackageAndFile("noCoreRef1"); - const [indirectFile, indirectConfig] = getPackageAndFile("indirect", ["coreRef1"]); - const [coreRef1File, coreRef1Config] = getPackageAndFile("coreRef1", ["core"]); - const [indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config] = getPackageAndFile("indirectDisabledChildLoad1", ["coreRef2"], { disableReferencedProjectLoad: true }); - const [coreRef2File, coreRef2Config] = getPackageAndFile("coreRef2", ["core"]); - const [indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config] = getPackageAndFile("indirectDisabledChildLoad2", ["coreRef3"], { disableReferencedProjectLoad: true }); - const [coreRef3File, coreRef3Config] = getPackageAndFile("coreRef3", ["core"]); - const [refToCoreRef3File, refToCoreRef3Config] = getPackageAndFile("refToCoreRef3", ["coreRef3"]); - const [indirectNoCoreRefFile, indirectNoCoreRefConfig] = getPackageAndFile("indirectNoCoreRef", ["noCoreRef2"]); - const [noCoreRef2File, noCoreRef2Config] = getPackageAndFile("noCoreRef2"); + baselineTsserverLogs("projectReferences", `auto import with referenced project${built ? " when built" : ""}${disableSourceOfProjectReferenceRedirect ? " with disableSourceOfProjectReferenceRedirect": ""}`, session); + } - const host = createServerHost([ - libFile, mainFile, mainConfig, coreFile, coreConfig, noCoreRef1File, noCoreRef1Config, - indirectFile, indirectConfig, coreRef1File, coreRef1Config, - indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config, coreRef2File, coreRef2Config, - indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config, coreRef3File, coreRef3Config, - refToCoreRef3File, refToCoreRef3Config, - indirectNoCoreRefFile, indirectNoCoreRefConfig, noCoreRef2File, noCoreRef2Config - ], { useCaseSensitiveFileNames: true }); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([mainFile, coreFile], session); + it("when project is built", () => { + verifyAutoImport(/*built*/ true); + }); + it("when project is not built", () => { + verifyAutoImport(/*built*/ false); + }); + it("when disableSourceOfProjectReferenceRedirect is true", () => { + verifyAutoImport(/*built*/ true, /*disableSourceOfProjectReferenceRedirect*/ true); + }); + }); - // Find all refs in coreFile - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(coreFile, `coreConst`) - }); - baselineTsserverLogs("projectReferences", `when files from two projects are open and one project references`, session); + it("when files from two projects are open and one project references", () => { + function getPackageAndFile(packageName: string, references?: string[], optionsToExtend?: CompilerOptions): [file: File, config: File] { + const file: File = { + path: `${tscWatch.projectRoot}/${packageName}/src/file1.ts`, + content: `export const ${packageName}Const = 10;` + }; + const config: File = { + path: `${tscWatch.projectRoot}/${packageName}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, ...optionsToExtend || {} }, + references: references?.map(path => ({ path: `../${path}` })) + }) + }; + return [file, config]; + } + const [mainFile, mainConfig] = getPackageAndFile("main", ["core", "indirect", "noCoreRef1", "indirectDisabledChildLoad1", "indirectDisabledChildLoad2", "refToCoreRef3", "indirectNoCoreRef"]); + const [coreFile, coreConfig] = getPackageAndFile("core"); + const [noCoreRef1File, noCoreRef1Config] = getPackageAndFile("noCoreRef1"); + const [indirectFile, indirectConfig] = getPackageAndFile("indirect", ["coreRef1"]); + const [coreRef1File, coreRef1Config] = getPackageAndFile("coreRef1", ["core"]); + const [indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config] = getPackageAndFile("indirectDisabledChildLoad1", ["coreRef2"], { disableReferencedProjectLoad: true }); + const [coreRef2File, coreRef2Config] = getPackageAndFile("coreRef2", ["core"]); + const [indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config] = getPackageAndFile("indirectDisabledChildLoad2", ["coreRef3"], { disableReferencedProjectLoad: true }); + const [coreRef3File, coreRef3Config] = getPackageAndFile("coreRef3", ["core"]); + const [refToCoreRef3File, refToCoreRef3Config] = getPackageAndFile("refToCoreRef3", ["coreRef3"]); + const [indirectNoCoreRefFile, indirectNoCoreRefConfig] = getPackageAndFile("indirectNoCoreRef", ["noCoreRef2"]); + const [noCoreRef2File, noCoreRef2Config] = getPackageAndFile("noCoreRef2"); + + const host = createServerHost([ + libFile, mainFile, mainConfig, coreFile, coreConfig, noCoreRef1File, noCoreRef1Config, + indirectFile, indirectConfig, coreRef1File, coreRef1Config, + indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config, coreRef2File, coreRef2Config, + indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config, coreRef3File, coreRef3Config, + refToCoreRef3File, refToCoreRef3Config, + indirectNoCoreRefFile, indirectNoCoreRefConfig, noCoreRef2File, noCoreRef2Config + ], { useCaseSensitiveFileNames: true }); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([mainFile, coreFile], session); + + // Find all refs in coreFile + session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: protocolFileLocationFromSubstring(coreFile, `coreConst`) }); + baselineTsserverLogs("projectReferences", `when files from two projects are open and one project references`, session); + }); - describe("find refs to decl in other proj", () => { - const indexA: File = { - path: `${tscWatch.projectRoot}/a/index.ts`, - content: `import { B } from "../b/lib"; + describe("find refs to decl in other proj", () => { + const indexA: File = { + path: `${tscWatch.projectRoot}/a/index.ts`, + content: `import { B } from "../b/lib"; const b: B = new B();` - }; + }; - const configB: File = { - path: `${tscWatch.projectRoot}/b/tsconfig.json`, - content: `{ + const configB: File = { + path: `${tscWatch.projectRoot}/b/tsconfig.json`, + content: `{ "compilerOptions": { "declarationMap": true, "outDir": "lib", "composite": true } }` - }; + }; - const indexB: File = { - path: `${tscWatch.projectRoot}/b/index.ts`, - content: `export class B { + const indexB: File = { + path: `${tscWatch.projectRoot}/b/index.ts`, + content: `export class B { M() {} }` - }; + }; - const helperB: File = { - path: `${tscWatch.projectRoot}/b/helper.ts`, - content: `import { B } from "."; + const helperB: File = { + path: `${tscWatch.projectRoot}/b/helper.ts`, + content: `import { B } from "."; const b: B = new B();` - }; + }; - const dtsB: File = { - path: `${tscWatch.projectRoot}/b/lib/index.d.ts`, - content: `export declare class B { + const dtsB: File = { + path: `${tscWatch.projectRoot}/b/lib/index.d.ts`, + content: `export declare class B { M(): void; } //# sourceMappingURL=index.d.ts.map` + }; + + const dtsMapB: File = { + path: `${tscWatch.projectRoot}/b/lib/index.d.ts.map`, + content: `{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;IACV,CAAC;CACJ"}` + }; + + function baselineDisableReferencedProjectLoad( + projectAlreadyLoaded: boolean, + disableReferencedProjectLoad: boolean, + disableSourceOfProjectReferenceRedirect: boolean, + dtsMapPresent: boolean) { + + // Mangled to stay under windows path length limit + const subScenario = + `when proj ${projectAlreadyLoaded ? "is" : "is not"} loaded` + + ` and refd proj loading is ${disableReferencedProjectLoad ? "disabled" : "enabled"}` + + ` and proj ref redirects are ${disableSourceOfProjectReferenceRedirect ? "disabled" : "enabled"}` + + ` and a decl map is ${dtsMapPresent ? "present" : "missing"}`; + const compilerOptions: CompilerOptions = { + disableReferencedProjectLoad, + disableSourceOfProjectReferenceRedirect, + composite: true }; - const dtsMapB: File = { - path: `${tscWatch.projectRoot}/b/lib/index.d.ts.map`, - content: `{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;IACV,CAAC;CACJ"}` - }; - - function baselineDisableReferencedProjectLoad( - projectAlreadyLoaded: boolean, - disableReferencedProjectLoad: boolean, - disableSourceOfProjectReferenceRedirect: boolean, - dtsMapPresent: boolean) { - - // Mangled to stay under windows path length limit - const subScenario = - `when proj ${projectAlreadyLoaded ? "is" : "is not"} loaded` + - ` and refd proj loading is ${disableReferencedProjectLoad ? "disabled" : "enabled"}` + - ` and proj ref redirects are ${disableSourceOfProjectReferenceRedirect ? "disabled" : "enabled"}` + - ` and a decl map is ${dtsMapPresent ? "present" : "missing"}`; - const compilerOptions: CompilerOptions = { - disableReferencedProjectLoad, - disableSourceOfProjectReferenceRedirect, - composite: true - }; - - it(subScenario, () => { - const configA: File = { - path: `${tscWatch.projectRoot}/a/tsconfig.json`, - content: `{ + it(subScenario, () => { + const configA: File = { + path: `${tscWatch.projectRoot}/a/tsconfig.json`, + content: `{ "compilerOptions": ${JSON.stringify(compilerOptions)}, "references": [{ "path": "../b" }] }` - }; + }; - const host = createServerHost([configA, indexA, configB, indexB, helperB, dtsB, ...(dtsMapPresent ? [dtsMapB] : [])]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([indexA, ...(projectAlreadyLoaded ? [helperB] : [])], session); + const host = createServerHost([configA, indexA, configB, indexB, helperB, dtsB, ...(dtsMapPresent ? [dtsMapB] : [])]); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([indexA, ...(projectAlreadyLoaded ? [helperB] : [])], session); - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(indexA, `B`, { index: 1 }) - }); - baselineTsserverLogs("projectReferences", `find refs to decl in other proj ${subScenario}`, session); + session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: protocolFileLocationFromSubstring(indexA, `B`, { index: 1 }) }); - } - - /* eslint-disable boolean-trivia */ - - // Pre-loaded = A file from project B is already open when FAR is invoked - // dRPL = Project A has disableReferencedProjectLoad - // dSOPRR = Project A has disableSourceOfProjectReferenceRedirect - // Map = The declaration map file b/lib/index.d.ts.map exists - // B refs = files under directory b in which references are found (all scenarios find all references in a/index.ts) - - // Pre-loaded | dRPL | dSOPRR | Map | B state | Notes | B refs | Notes - // -----------+--------+--------+----------+------------+--------------+---------------------+--------------------------------------------------- - baselineDisableReferencedProjectLoad(true, true, true, true); // Pre-loaded | | index.ts, helper.ts | Via map and pre-loaded project - baselineDisableReferencedProjectLoad(true, true, true, false); // Pre-loaded | | lib/index.d.ts | Even though project is loaded - baselineDisableReferencedProjectLoad(true, true, false, true); // Pre-loaded | | index.ts, helper.ts | - baselineDisableReferencedProjectLoad(true, true, false, false); // Pre-loaded | | index.ts, helper.ts | - baselineDisableReferencedProjectLoad(true, false, true, true); // Pre-loaded | | index.ts, helper.ts | Via map and pre-loaded project - baselineDisableReferencedProjectLoad(true, false, true, false); // Pre-loaded | | lib/index.d.ts | Even though project is loaded - baselineDisableReferencedProjectLoad(true, false, false, true); // Pre-loaded | | index.ts, helper.ts | - baselineDisableReferencedProjectLoad(true, false, false, false); // Pre-loaded | | index.ts, helper.ts | - baselineDisableReferencedProjectLoad(false, true, true, true); // Not loaded | | lib/index.d.ts | Even though map is present - baselineDisableReferencedProjectLoad(false, true, true, false); // Not loaded | | lib/index.d.ts | - baselineDisableReferencedProjectLoad(false, true, false, true); // Not loaded | | index.ts | But not helper.ts, which is not referenced from a - baselineDisableReferencedProjectLoad(false, true, false, false); // Not loaded | | index.ts | But not helper.ts, which is not referenced from a - baselineDisableReferencedProjectLoad(false, false, true, true); // Loaded | Via map | index.ts, helper.ts | Via map and newly loaded project - baselineDisableReferencedProjectLoad(false, false, true, false); // Not loaded | | lib/index.d.ts | - baselineDisableReferencedProjectLoad(false, false, false, true); // Loaded | Via redirect | index.ts, helper.ts | - baselineDisableReferencedProjectLoad(false, false, false, false); // Loaded | Via redirect | index.ts, helper.ts | - - /* eslint-enable boolean-trivia */ - }); + baselineTsserverLogs("projectReferences", `find refs to decl in other proj ${subScenario}`, session); + }); + } + + /* eslint-disable boolean-trivia */ + + // Pre-loaded = A file from project B is already open when FAR is invoked + // dRPL = Project A has disableReferencedProjectLoad + // dSOPRR = Project A has disableSourceOfProjectReferenceRedirect + // Map = The declaration map file b/lib/index.d.ts.map exists + // B refs = files under directory b in which references are found (all scenarios find all references in a/index.ts) + + // Pre-loaded | dRPL | dSOPRR | Map | B state | Notes | B refs | Notes + // -----------+--------+--------+----------+------------+--------------+---------------------+--------------------------------------------------- + baselineDisableReferencedProjectLoad(true, true, true, true); // Pre-loaded | | index.ts, helper.ts | Via map and pre-loaded project + baselineDisableReferencedProjectLoad(true, true, true, false); // Pre-loaded | | lib/index.d.ts | Even though project is loaded + baselineDisableReferencedProjectLoad(true, true, false, true); // Pre-loaded | | index.ts, helper.ts | + baselineDisableReferencedProjectLoad(true, true, false, false); // Pre-loaded | | index.ts, helper.ts | + baselineDisableReferencedProjectLoad(true, false, true, true); // Pre-loaded | | index.ts, helper.ts | Via map and pre-loaded project + baselineDisableReferencedProjectLoad(true, false, true, false); // Pre-loaded | | lib/index.d.ts | Even though project is loaded + baselineDisableReferencedProjectLoad(true, false, false, true); // Pre-loaded | | index.ts, helper.ts | + baselineDisableReferencedProjectLoad(true, false, false, false); // Pre-loaded | | index.ts, helper.ts | + baselineDisableReferencedProjectLoad(false, true, true, true); // Not loaded | | lib/index.d.ts | Even though map is present + baselineDisableReferencedProjectLoad(false, true, true, false); // Not loaded | | lib/index.d.ts | + baselineDisableReferencedProjectLoad(false, true, false, true); // Not loaded | | index.ts | But not helper.ts, which is not referenced from a + baselineDisableReferencedProjectLoad(false, true, false, false); // Not loaded | | index.ts | But not helper.ts, which is not referenced from a + baselineDisableReferencedProjectLoad(false, false, true, true); // Loaded | Via map | index.ts, helper.ts | Via map and newly loaded project + baselineDisableReferencedProjectLoad(false, false, true, false); // Not loaded | | lib/index.d.ts | + baselineDisableReferencedProjectLoad(false, false, false, true); // Loaded | Via redirect | index.ts, helper.ts | + baselineDisableReferencedProjectLoad(false, false, false, false); // Loaded | Via redirect | index.ts, helper.ts | + + /* eslint-enable boolean-trivia */ }); +}); } diff --git a/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts b/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts index d50f879a60dc5..88a99061e6ee7 100644 --- a/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts +++ b/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts @@ -1,26 +1,26 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: with project references and tsbuild source map", () => { - const dependecyLocation = `${tscWatch.projectRoot}/dependency`; - const dependecyDeclsLocation = `${tscWatch.projectRoot}/decls`; - const mainLocation = `${tscWatch.projectRoot}/main`; - const dependencyTs: File = { - path: `${dependecyLocation}/FnS.ts`, - content: `export function fn1() { } +describe("unittests:: tsserver:: with project references and tsbuild source map", () => { + const dependecyLocation = `${tscWatch.projectRoot}/dependency`; + const dependecyDeclsLocation = `${tscWatch.projectRoot}/decls`; + const mainLocation = `${tscWatch.projectRoot}/main`; + const dependencyTs: File = { + path: `${dependecyLocation}/FnS.ts`, + content: `export function fn1() { } export function fn2() { } export function fn3() { } export function fn4() { } export function fn5() { } ` - }; - const dependencyTsPath = dependencyTs.path.toLowerCase(); - const dependencyConfig: File = { - path: `${dependecyLocation}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true, declarationDir: "../decls" } }) - }; - - const mainTs: File = { - path: `${mainLocation}/main.ts`, - content: `import { + }; + const dependencyTsPath = dependencyTs.path.toLowerCase(); + const dependencyConfig: File = { + path: `${dependecyLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true, declarationDir: "../decls" } }) + }; + + const mainTs: File = { + path: `${mainLocation}/main.ts`, + content: `import { fn1, fn2, fn3, @@ -34,4406 +34,4406 @@ fn3(); fn4(); fn5(); ` + }; + const mainConfig: File = { + path: `${mainLocation}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, declarationMap: true }, + references: [{ path: "../dependency" }] + }) + }; + + const randomFile: File = { + path: `${tscWatch.projectRoot}/random/random.ts`, + content: "let a = 10;" + }; + const randomConfig: File = { + path: `${tscWatch.projectRoot}/random/tsconfig.json`, + content: "{}" + }; + const dtsLocation = `${dependecyDeclsLocation}/FnS.d.ts`; + const dtsPath = dtsLocation.toLowerCase() as Path; + const dtsMapLocation = `${dependecyDeclsLocation}/FnS.d.ts.map`; + const dtsMapPath = dtsMapLocation.toLowerCase() as Path; + + const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; + + function changeDtsFile(host: TestServerHost) { + host.writeFile( + dtsLocation, + host.readFile(dtsLocation)!.replace( + "//# sourceMappingURL=FnS.d.ts.map", + `export declare function fn6(): void; +//# sourceMappingURL=FnS.d.ts.map` + ) + ); + } + + function changeDtsMapFile(host: TestServerHost) { + host.writeFile( + dtsMapLocation, + `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` + ); + } + + function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: readonly string[], closedInfos: readonly string[], otherWatchedFiles: readonly string[], additionalInfo?: string) { + checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos), additionalInfo); + checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase()), additionalInfo); + } + + function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) { + verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path], "Random"); + } + + function declarationSpan(fn: number): protocol.TextSpanWithContext { + return { + start: { line: fn, offset: 17 }, + end: { line: fn, offset: 20 }, + contextStart: { line: fn, offset: 1 }, + contextEnd: { line: fn, offset: 26 } }; - const mainConfig: File = { - path: `${mainLocation}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, declarationMap: true }, - references: [{ path: "../dependency" }] - }) + } + function importSpan(fn: number): protocol.TextSpanWithContext { + return { + start: { line: fn + 1, offset: 5 }, + end: { line: fn + 1, offset: 8 }, + contextStart: { line: 1, offset: 1 }, + contextEnd: { line: 7, offset: 22 } }; - - const randomFile: File = { - path: `${tscWatch.projectRoot}/random/random.ts`, - content: "let a = 10;" + } + function usageSpan(fn: number): protocol.TextSpan { + return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } }; + } + + function goToDefFromMainTs(fn: number): Action { + const textSpan = usageSpan(fn); + const definition: protocol.FileSpan = { file: dependencyTs.path, ...declarationSpan(fn) }; + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To dependency + definitions: [definition], + textSpan + } }; - const randomConfig: File = { - path: `${tscWatch.projectRoot}/random/tsconfig.json`, - content: "{}" + } + + function goToDefFromMainTsWithNoMap(fn: number): Action { + const textSpan = usageSpan(fn); + const definition = declarationSpan(fn); + const declareSpaceLength = "declare ".length; + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To the dts + definitions: [{ + file: dtsPath, + start: { line: fn, offset: definition.start.offset + declareSpaceLength }, + end: { line: fn, offset: definition.end.offset + declareSpaceLength }, + contextStart: { line: fn, offset: 1 }, + contextEnd: { line: fn, offset: 37 } + }], + textSpan + } }; - const dtsLocation = `${dependecyDeclsLocation}/FnS.d.ts`; - const dtsPath = dtsLocation.toLowerCase() as Path; - const dtsMapLocation = `${dependecyDeclsLocation}/FnS.d.ts.map`; - const dtsMapPath = dtsMapLocation.toLowerCase() as Path; - - const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; - - function changeDtsFile(host: TestServerHost) { - host.writeFile( - dtsLocation, - host.readFile(dtsLocation)!.replace( - "//# sourceMappingURL=FnS.d.ts.map", - `export declare function fn6(): void; -//# sourceMappingURL=FnS.d.ts.map` - ) - ); - } - - function changeDtsMapFile(host: TestServerHost) { - host.writeFile( - dtsMapLocation, - `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` - ); - } - - function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: readonly string[], closedInfos: readonly string[], otherWatchedFiles: readonly string[], additionalInfo?: string) { - checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos), additionalInfo); - checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase()), additionalInfo); - } - - function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) { - verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path], "Random"); - } - - function declarationSpan(fn: number): protocol.TextSpanWithContext { - return { - start: { line: fn, offset: 17 }, - end: { line: fn, offset: 20 }, - contextStart: { line: fn, offset: 1 }, - contextEnd: { line: fn, offset: 26 } - }; - } - function importSpan(fn: number): protocol.TextSpanWithContext { - return { - start: { line: fn + 1, offset: 5 }, - end: { line: fn + 1, offset: 8 }, - contextStart: { line: 1, offset: 1 }, - contextEnd: { line: 7, offset: 22 } - }; - } - function usageSpan(fn: number): protocol.TextSpan { - return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } }; - } - - function goToDefFromMainTs(fn: number): Action { - const textSpan = usageSpan(fn); - const definition: protocol.FileSpan = { file: dependencyTs.path, ...declarationSpan(fn) }; - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } + } + + function goToDefFromMainTsWithNoDts(fn: number): Action { + const textSpan = usageSpan(fn); + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To import declaration + definitions: [{ file: mainTs.path, ...importSpan(fn) }], + textSpan + } + }; + } + + function goToDefFromMainTsWithDependencyChange(fn: number): Action { + const textSpan = usageSpan(fn); + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // Definition on fn + 1 line + definitions: [{ file: dependencyTs.path, ...declarationSpan(fn + 1) }], + textSpan + } + }; + } + + function renameFromDependencyTs(fn: number): Action { + const defSpan = declarationSpan(fn); + const { contextStart: _, contextEnd: _1, ...triggerSpan } = defSpan; + return { + reqName: "rename", + request: { + command: protocol.CommandTypes.Rename, + arguments: { file: dependencyTs.path, ...triggerSpan.start } + }, + expectedResponse: { + info: { + canRename: true, + fileToRename: undefined, + displayName: `fn${fn}`, + fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + triggerSpan }, - expectedResponse: { - // To dependency - definitions: [definition], - textSpan - } - }; - } - - function goToDefFromMainTsWithNoMap(fn: number): Action { - const textSpan = usageSpan(fn); - const definition = declarationSpan(fn); - const declareSpaceLength = "declare ".length; - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } + locs: [ + { file: dependencyTs.path, locs: [defSpan] } + ] + } + }; + } + + function renameFromDependencyTsWithDependencyChange(fn: number): Action { + const { expectedResponse: { info, locs }, ...rest } = renameFromDependencyTs(fn + 1); + + return { + ...rest, + expectedResponse: { + info: { + ...info as protocol.RenameInfoSuccess, + displayName: `fn${fn}`, + fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, }, - expectedResponse: { - // To the dts - definitions: [{ - file: dtsPath, - start: { line: fn, offset: definition.start.offset + declareSpaceLength }, - end: { line: fn, offset: definition.end.offset + declareSpaceLength }, - contextStart: { line: fn, offset: 1 }, - contextEnd: { line: fn, offset: 37 } - }], - textSpan - } - }; - } + locs + } + }; + } + + function renameFromDependencyTsWithBothProjectsOpen(fn: number): Action { + const { reqName, request, expectedResponse } = renameFromDependencyTs(fn); + const { info, locs } = expectedResponse; + return { + reqName, + request, + expectedResponse: { + info, + locs: [ + locs[0], + { + file: mainTs.path, + locs: [ + importSpan(fn), + usageSpan(fn) + ] + } + ] + } + }; + } + + function renameFromDependencyTsWithBothProjectsOpenWithDependencyChange(fn: number): Action { + const { reqName, request, expectedResponse, } = renameFromDependencyTsWithDependencyChange(fn); + const { info, locs } = expectedResponse; + return { + reqName, + request, + expectedResponse: { + info, + locs: [ + locs[0], + { + file: mainTs.path, + locs: [ + importSpan(fn), + usageSpan(fn) + ] + } + ] + } + }; + } - function goToDefFromMainTsWithNoDts(fn: number): Action { - const textSpan = usageSpan(fn); - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // To import declaration - definitions: [{ file: mainTs.path, ...importSpan(fn) }], - textSpan - } - }; + function removePath(array: readonly string[], ...delPaths: string[]) { + return array.filter(a => { + const aLower = a.toLowerCase(); + return delPaths.every(dPath => dPath !== aLower); + }); + } + + interface Action { + reqName: string; + request: Partial; + expectedResponse: Response; + } + + function verifyAction(session: TestSession, { reqName, request, expectedResponse }: Action) { + const { response } = session.executeCommandSeq(request); + assert.deepEqual(response, expectedResponse, `Failed Request: ${reqName}`); + } + + function verifyDocumentPositionMapper( + session: TestSession, + dependencyMap: server.ScriptInfo | undefined, + documentPositionMapper: server.ScriptInfo["documentPositionMapper"], + equal: boolean, + debugInfo?: string, + ) { + assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap, `${debugInfo} dependencyMap`); + if (dependencyMap) { + verifyEquality(dependencyMap.documentPositionMapper, documentPositionMapper, equal, `${debugInfo} DocumentPositionMapper`); } - - function goToDefFromMainTsWithDependencyChange(fn: number): Action { - const textSpan = usageSpan(fn); - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // Definition on fn + 1 line - definitions: [{ file: dependencyTs.path, ...declarationSpan(fn + 1) }], - textSpan - } - }; + } + + function verifyDocumentPositionMapperEqual( + session: TestSession, + dependencyMap: server.ScriptInfo | undefined, + documentPositionMapper: server.ScriptInfo["documentPositionMapper"], + debugInfo?: string, + ) { + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, /*equal*/ true, debugInfo); + } + + function verifyEquality(actual: T, expected: T, equal: boolean, debugInfo?: string) { + if (equal) { + assert.strictEqual(actual, expected, debugInfo); } - - function renameFromDependencyTs(fn: number): Action { - const defSpan = declarationSpan(fn); - const { contextStart: _, contextEnd: _1, ...triggerSpan } = defSpan; - return { - reqName: "rename", - request: { - command: protocol.CommandTypes.Rename, - arguments: { file: dependencyTs.path, ...triggerSpan.start } - }, - expectedResponse: { - info: { - canRename: true, - fileToRename: undefined, - displayName: `fn${fn}`, - fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - triggerSpan - }, - locs: [ - { file: dependencyTs.path, locs: [defSpan] } - ] - } - }; - } - - function renameFromDependencyTsWithDependencyChange(fn: number): Action { - const { expectedResponse: { info, locs }, ...rest } = renameFromDependencyTs(fn + 1); - - return { - ...rest, - expectedResponse: { - info: { - ...info as protocol.RenameInfoSuccess, - displayName: `fn${fn}`, - fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, - }, - locs - } - }; + else { + assert.notStrictEqual(actual, expected, debugInfo); } - - function renameFromDependencyTsWithBothProjectsOpen(fn: number): Action { - const { reqName, request, expectedResponse } = renameFromDependencyTs(fn); - const { info, locs } = expectedResponse; - return { - reqName, - request, - expectedResponse: { - info, - locs: [ - locs[0], - { - file: mainTs.path, - locs: [ - importSpan(fn), - usageSpan(fn) - ] + } + + function verifyAllFnAction( + session: TestSession, + host: TestServerHost, + action: (fn: number) => Action, + expectedInfos: readonly string[], + expectedWatchedFiles: readonly string[], + existingDependencyMap: server.ScriptInfo | undefined, + existingDocumentPositionMapper: server.ScriptInfo["documentPositionMapper"], + existingMapEqual: boolean, + existingDocumentPositionMapperEqual: boolean, + skipMapPathInDtsInfo?: boolean + ) { + let sourceMapPath: server.ScriptInfo["sourceMapFilePath"] | undefined; + let dependencyMap: server.ScriptInfo | undefined; + let documentPositionMapper: server.ScriptInfo["documentPositionMapper"]; + for (let fn = 1; fn <= 5; fn++) { + const fnAction = action(fn); + verifyAction(session, fnAction); + const debugInfo = `${fnAction.reqName}:: ${fn}`; + checkScriptInfos(session.getProjectService(), expectedInfos, debugInfo); + checkWatchedFiles(host, expectedWatchedFiles, debugInfo); + const dtsInfo = session.getProjectService().getScriptInfoForPath(dtsPath); + const dtsMapInfo = session.getProjectService().getScriptInfoForPath(dtsMapPath); + + if (fn === 1) { + if (dtsInfo) { + if (!skipMapPathInDtsInfo) { + if (dtsMapInfo) { + assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath, `${debugInfo} sourceMapFilePath`); } - ] - } - }; - } - - function renameFromDependencyTsWithBothProjectsOpenWithDependencyChange(fn: number): Action { - const { reqName, request, expectedResponse, } = renameFromDependencyTsWithDependencyChange(fn); - const { info, locs } = expectedResponse; - return { - reqName, - request, - expectedResponse: { - info, - locs: [ - locs[0], - { - file: mainTs.path, - locs: [ - importSpan(fn), - usageSpan(fn) - ] + else { + assert.isNotString(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); + assert.isNotFalse(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); + assert.isDefined(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); } - ] + } } - }; - } - - function removePath(array: readonly string[], ...delPaths: string[]) { - return array.filter(a => { - const aLower = a.toLowerCase(); - return delPaths.every(dPath => dPath !== aLower); - }); - } - - interface Action { - reqName: string; - request: Partial; - expectedResponse: Response; - } - - function verifyAction(session: TestSession, { reqName, request, expectedResponse }: Action) { - const { response } = session.executeCommandSeq(request); - assert.deepEqual(response, expectedResponse, `Failed Request: ${reqName}`); - } - - function verifyDocumentPositionMapper( - session: TestSession, - dependencyMap: server.ScriptInfo | undefined, - documentPositionMapper: server.ScriptInfo["documentPositionMapper"], - equal: boolean, - debugInfo?: string, - ) { - assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap, `${debugInfo} dependencyMap`); - if (dependencyMap) { - verifyEquality(dependencyMap.documentPositionMapper, documentPositionMapper, equal, `${debugInfo} DocumentPositionMapper`); + verifyEquality(dtsMapInfo, existingDependencyMap, existingMapEqual, `${debugInfo} dependencyMap`); + verifyEquality(existingDocumentPositionMapper, dtsMapInfo?.documentPositionMapper, existingDocumentPositionMapperEqual, `${debugInfo} DocumentPositionMapper`); + sourceMapPath = dtsInfo?.sourceMapFilePath; + dependencyMap = dtsMapInfo; + documentPositionMapper = dependencyMap?.documentPositionMapper; + } + else { + assert.equal(dtsInfo?.sourceMapFilePath, sourceMapPath, `${debugInfo} sourceMapFilePath`); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper, debugInfo); } } - - function verifyDocumentPositionMapperEqual( - session: TestSession, - dependencyMap: server.ScriptInfo | undefined, - documentPositionMapper: server.ScriptInfo["documentPositionMapper"], - debugInfo?: string, - ) { - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, /*equal*/ true, debugInfo); + } + + function verifyScriptInfoCollectionWith( + session: TestSession, + host: TestServerHost, + openFiles: readonly File[], + expectedInfos: readonly string[], + expectedWatchedFiles: readonly string[], + ) { + const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); + + // Collecting at this point retains dependency.d.ts and map + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + + checkScriptInfos(session.getProjectService(), expectedInfos); + checkWatchedFiles(host, expectedWatchedFiles); + // If map is not collected, document position mapper shouldnt change + if (session.getProjectService().filenameToScriptInfo.has(dtsMapPath)) { + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); } - function verifyEquality(actual: T, expected: T, equal: boolean, debugInfo?: string) { - if (equal) { - assert.strictEqual(actual, expected, debugInfo); + // Closing open file, removes dependencies too + closeFilesForSession([...openFiles, randomFile], session); + openFilesForSession([randomFile], session); + verifyOnlyRandomInfos(session, host); + } + + type OnHostCreate = (host: TestServerHost) => void; + type CreateSessionFn = (onHostCreate?: OnHostCreate) => { host: TestServerHost; session: TestSession; }; + function setupWith(createSession: CreateSessionFn, openFiles: readonly File[], onHostCreate: OnHostCreate | undefined) { + const result = createSession(onHostCreate); + openFilesForSession(openFiles, result.session); + return result; + } + + function setupWithMainTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { + return setupWith(createSession, [mainTs, randomFile], onHostCreate); + } + + function setupWithDependencyTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { + return setupWith(createSession, [dependencyTs, randomFile], onHostCreate); + } + + function setupWithMainTsAndDependencyTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { + return setupWith(createSession, [mainTs, dependencyTs, randomFile], onHostCreate); + } + + function createSessionWithoutProjectReferences(onHostCreate?: OnHostCreate) { + const host = createHostWithSolutionBuild(files, [mainConfig.path]); + // Erase project reference + host.writeFile(mainConfig.path, JSON.stringify({ + compilerOptions: { composite: true, declarationMap: true } + })); + onHostCreate?.(host); + const session = createSession(host); + return { host, session }; + } + + function createSessionWithProjectReferences(onHostCreate?: OnHostCreate) { + const host = createHostWithSolutionBuild(files, [mainConfig.path]); + onHostCreate?.(host); + const session = createSession(host); + return { host, session }; + } + + function createSessionWithDisabledProjectReferences(onHostCreate?: OnHostCreate) { + const host = createHostWithSolutionBuild(files, [mainConfig.path]); + // Erase project reference + host.writeFile(mainConfig.path, JSON.stringify({ + compilerOptions: { + composite: true, + declarationMap: true, + disableSourceOfProjectReferenceRedirect: true + }, + references: [{ path: "../dependency" }] + })); + onHostCreate?.(host); + const session = createSession(host); + return { host, session }; + } + + function getDocumentPositionMapper(session: TestSession) { + const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); + const documentPositionMapper = dependencyMap?.documentPositionMapper; + return { dependencyMap, documentPositionMapper }; + } + + function checkMainProjectWithoutProjectReferences(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, dtsPath]); + } + + function checkMainProjectWithoutProjectReferencesWithoutDts(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path]); + } + + function checkMainProjectWithProjectReferences(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path]); + } + + function checkMainProjectWithDisabledProjectReferences(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, dtsPath]); + } + + function checkMainProjectWithDisabledProjectReferencesWithoutDts(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path]); + } + + function checkDependencyProjectWith(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(dependencyConfig.path)!, [dependencyTs.path, libFile.path, dependencyConfig.path]); + } + + function makeChangeToMainTs(session: TestSession) { + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: mainTs.path, + line: 14, + offset: 1, + endLine: 14, + endOffset: 1, + insertString: "const x = 10;" } - else { - assert.notStrictEqual(actual, expected, debugInfo); + }); + } + + function makeChangeToDependencyTs(session: TestSession) { + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + line: 6, + offset: 1, + endLine: 6, + endOffset: 1, + insertString: "const x = 10;" } + }); + } + + describe("from project that uses dependency: goToDef", () => { + function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { + const result = setup(onHostCreate); + result.session.executeCommandSeq(goToDefFromMainTs(1).request); + return { ...result, ...getDocumentPositionMapper(result.session) }; } - function verifyAllFnAction( + function verifyScriptInfoCollection( session: TestSession, host: TestServerHost, - action: (fn: number) => Action, expectedInfos: readonly string[], expectedWatchedFiles: readonly string[], - existingDependencyMap: server.ScriptInfo | undefined, - existingDocumentPositionMapper: server.ScriptInfo["documentPositionMapper"], - existingMapEqual: boolean, - existingDocumentPositionMapperEqual: boolean, - skipMapPathInDtsInfo?: boolean ) { - let sourceMapPath: server.ScriptInfo["sourceMapFilePath"] | undefined; - let dependencyMap: server.ScriptInfo | undefined; - let documentPositionMapper: server.ScriptInfo["documentPositionMapper"]; - for (let fn = 1; fn <= 5; fn++) { - const fnAction = action(fn); - verifyAction(session, fnAction); - const debugInfo = `${fnAction.reqName}:: ${fn}`; - checkScriptInfos(session.getProjectService(), expectedInfos, debugInfo); - checkWatchedFiles(host, expectedWatchedFiles, debugInfo); - const dtsInfo = session.getProjectService().getScriptInfoForPath(dtsPath); - const dtsMapInfo = session.getProjectService().getScriptInfoForPath(dtsMapPath); - - if (fn === 1) { - if (dtsInfo) { - if (!skipMapPathInDtsInfo) { - if (dtsMapInfo) { - assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath, `${debugInfo} sourceMapFilePath`); - } - else { - assert.isNotString(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); - assert.isNotFalse(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); - assert.isDefined(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); - } - } - } - verifyEquality(dtsMapInfo, existingDependencyMap, existingMapEqual, `${debugInfo} dependencyMap`); - verifyEquality(existingDocumentPositionMapper, dtsMapInfo?.documentPositionMapper, existingDocumentPositionMapperEqual, `${debugInfo} DocumentPositionMapper`); - sourceMapPath = dtsInfo?.sourceMapFilePath; - dependencyMap = dtsMapInfo; - documentPositionMapper = dependencyMap?.documentPositionMapper; - } - else { - assert.equal(dtsInfo?.sourceMapFilePath, sourceMapPath, `${debugInfo} sourceMapFilePath`); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper, debugInfo); - } - } + return verifyScriptInfoCollectionWith(session, host, [mainTs], expectedInfos, expectedWatchedFiles); } - function verifyScriptInfoCollectionWith( - session: TestSession, - host: TestServerHost, - openFiles: readonly File[], - expectedInfos: readonly string[], - expectedWatchedFiles: readonly string[], - ) { - const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); + describe("when main tsconfig doesnt have project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTs(createSessionWithoutProjectReferences, onHostCreate); + } - // Collecting at this point retains dependency.d.ts and map - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - checkScriptInfos(session.getProjectService(), expectedInfos); - checkWatchedFiles(host, expectedWatchedFiles); - // If map is not collected, document position mapper shouldnt change - if (session.getProjectService().filenameToScriptInfo.has(dtsMapPath)) { - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + checkMainProjectWithoutProjectReferences(session); } - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); - } + function checkProjectsWithoutDts(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + checkMainProjectWithoutProjectReferencesWithoutDts(session); + } - type OnHostCreate = (host: TestServerHost) => void; - type CreateSessionFn = (onHostCreate?: OnHostCreate) => { host: TestServerHost; session: TestSession; }; - function setupWith(createSession: CreateSessionFn, openFiles: readonly File[], onHostCreate: OnHostCreate | undefined) { - const result = createSession(onHostCreate); - openFilesForSession(openFiles, result.session); - return result; - } + function expectedScriptInfosWhenMapped() { + return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; + } - function setupWithMainTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { - return setupWith(createSession, [mainTs, randomFile], onHostCreate); - } + function expectedWatchedFilesWhenMapped() { + return [dependencyTsPath, libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path]; + } - function setupWithDependencyTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { - return setupWith(createSession, [dependencyTs, randomFile], onHostCreate); - } + function expectedScriptInfosWhenNoMap() { + // Because map is deleted, map and dependency are released + return removePath(expectedScriptInfosWhenMapped(), dtsMapPath, dependencyTsPath); + } - function setupWithMainTsAndDependencyTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { - return setupWith(createSession, [mainTs, dependencyTs, randomFile], onHostCreate); - } + function expectedWatchedFilesWhenNoMap() { + // Watches deleted file + return removePath(expectedWatchedFilesWhenMapped(), dependencyTsPath); + } - function createSessionWithoutProjectReferences(onHostCreate?: OnHostCreate) { - const host = createHostWithSolutionBuild(files, [mainConfig.path]); - // Erase project reference - host.writeFile(mainConfig.path, JSON.stringify({ - compilerOptions: { composite: true, declarationMap: true } - })); - onHostCreate?.(host); - const session = createSession(host); - return { host, session }; - } + function expectedScriptInfosWhenNoDts() { + // No dts, no map, no dependency + return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); + } - function createSessionWithProjectReferences(onHostCreate?: OnHostCreate) { - const host = createHostWithSolutionBuild(files, [mainConfig.path]); - onHostCreate?.(host); - const session = createSession(host); - return { host, session }; - } + function expectedWatchedFilesWhenNoDts() { + return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); + } - function createSessionWithDisabledProjectReferences(onHostCreate?: OnHostCreate) { - const host = createHostWithSolutionBuild(files, [mainConfig.path]); - // Erase project reference - host.writeFile(mainConfig.path, JSON.stringify({ - compilerOptions: { - composite: true, - declarationMap: true, - disableSourceOfProjectReferenceRedirect: true - }, - references: [{ path: "../dependency" }] - })); - onHostCreate?.(host); - const session = createSession(host); - return { host, session }; - } + it("can go to definition correctly", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + ); + }); - function getDocumentPositionMapper(session: TestSession) { - const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); - const documentPositionMapper = dependencyMap?.documentPositionMapper; - return { dependencyMap, documentPositionMapper }; - } + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - function checkMainProjectWithoutProjectReferences(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, dtsPath]); - } + // change + makeChangeToMainTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - function checkMainProjectWithoutProjectReferencesWithoutDts(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path]); - } + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - function checkMainProjectWithProjectReferences(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path]); - } + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - function checkMainProjectWithDisabledProjectReferences(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, dtsPath]); - } + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - function checkMainProjectWithDisabledProjectReferencesWithoutDts(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path]); - } + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - function checkDependencyProjectWith(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(dependencyConfig.path)!, [dependencyTs.path, libFile.path, dependencyConfig.path]); - } + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - function makeChangeToMainTs(session: TestSession) { - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: mainTs.path, - line: 14, - offset: 1, - endLine: 14, - endOffset: 1, - insertString: "const x = 10;" - } + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); }); - } - function makeChangeToDependencyTs(session: TestSession) { - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - line: 6, - offset: 1, - endLine: 6, - endOffset: 1, - insertString: "const x = 10;" - } + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); }); - } + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); + }); - describe("from project that uses dependency: goToDef", () => { - function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { - const result = setup(onHostCreate); - result.session.executeCommandSeq(goToDefFromMainTs(1).request); - return { ...result, ...getDocumentPositionMapper(result.session) }; - } + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + // The script info for map is collected only after file open + expectedScriptInfosWhenNoMap().concat(dependencyTs.path), + expectedWatchedFilesWhenNoMap().concat(dependencyTsPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); - function verifyScriptInfoCollection( - session: TestSession, - host: TestServerHost, - expectedInfos: readonly string[], - expectedWatchedFiles: readonly string[], - ) { - return verifyScriptInfoCollectionWith(session, host, [mainTs], expectedInfos, expectedWatchedFiles); - } + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjectsWithoutDts(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; + host.deleteFile(dtsLocation); + }); - describe("when main tsconfig doesnt have project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithMainTs(createSessionWithoutProjectReferences, onHostCreate); - } + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + // The script info for map is collected only after file open + expectedScriptInfosWhenNoDts().concat(dependencyTs.path, dtsMapLocation), + expectedWatchedFilesWhenNoDts().concat(dependencyTsPath, dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + }); + describe("when main tsconfig has project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTs(createSessionWithProjectReferences, onHostCreate); + } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - checkMainProjectWithoutProjectReferences(session); - } + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + checkMainProjectWithProjectReferences(session); + } - function checkProjectsWithoutDts(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - checkMainProjectWithoutProjectReferencesWithoutDts(session); - } + function expectedScriptInfos() { + return [dependencyTs.path, libFile.path, mainTs.path, randomFile.path]; + } - function expectedScriptInfosWhenMapped() { - return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; - } + function expectedWatchedFiles() { + return [dependencyTsPath, dependencyConfig.path, libFile.path, mainConfig.path, randomConfig.path]; + } - function expectedWatchedFilesWhenMapped() { - return [dependencyTsPath, libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path]; - } + it("can go to definition correctly", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); - function expectedScriptInfosWhenNoMap() { - // Because map is deleted, map and dependency are released - return removePath(expectedScriptInfosWhenMapped(), dtsMapPath, dependencyTsPath); - } + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - function expectedWatchedFilesWhenNoMap() { - // Watches deleted file - return removePath(expectedWatchedFilesWhenMapped(), dependencyTsPath); - } + // change + makeChangeToMainTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - function expectedScriptInfosWhenNoDts() { - // No dts, no map, no dependency - return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); - } + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - function expectedWatchedFilesWhenNoDts() { - return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); - } + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - it("can go to definition correctly", () => { - const { host, session } = setup(); - checkProjects(session); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - ); - }); + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - }); + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoMap, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoMap, - // The script info for map is collected only after file open - expectedScriptInfosWhenNoMap().concat(dependencyTs.path), - expectedWatchedFilesWhenNoMap().concat(dependencyTsPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjectsWithoutDts(session); - - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoDts, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjectsWithoutDts(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoDts, - // The script info for map is collected only after file open - expectedScriptInfosWhenNoDts().concat(dependencyTs.path, dtsMapLocation), - expectedWatchedFilesWhenNoDts().concat(dependencyTsPath, dtsMapPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjectsWithoutDts(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); }); - describe("when main tsconfig has project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithMainTs(createSessionWithProjectReferences, onHostCreate); - } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + it(`when defining project source changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - checkMainProjectWithProjectReferences(session); - } - - function expectedScriptInfos() { - return [dependencyTs.path, libFile.path, mainTs.path, randomFile.path]; - } + // change + // Make change, without rebuild of solution + host.writeFile(dependencyTs.path, `function fooBar() { } +${dependencyTs.content}`); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - function expectedWatchedFiles() { - return [dependencyTsPath, dependencyConfig.path, libFile.path, mainConfig.path, randomConfig.path]; - } + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithDependencyChange, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when defining project source changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session } = setupWithAction(); - it("can go to definition correctly", () => { - const { host, session } = setup(); - checkProjects(session); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); - }); + // change + // Make change, without rebuild of solution + host.writeFile(dependencyTs.path, `function fooBar() { } +${dependencyTs.content}`); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithDependencyChange, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + it("when projects are not built", () => { + const host = createServerHost(files); + const session = createSession(host); + openFilesForSession([mainTs, randomFile], session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); + }); + describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTs(createSessionWithDisabledProjectReferences, onHostCreate); + } - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + checkMainProjectWithDisabledProjectReferences(session); + } - // The dependency file is deleted when orphan files are collected - host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); - }); + function checkProjectsWithoutDts(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + checkMainProjectWithDisabledProjectReferencesWithoutDts(session); + } - it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + function expectedScriptInfosWhenMapped() { + return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; + } - // The dependency file is deleted when orphan files are collected - host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); - }); + function expectedWatchedFilesWhenMapped() { + return [dependencyTsPath, libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path, dependencyConfig.path]; + } - it(`when defining project source changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + function expectedScriptInfosWhenNoMap() { + // Because map is deleted, map and dependency are released + return removePath(expectedScriptInfosWhenMapped(), dtsMapPath, dependencyTsPath); + } - // change - // Make change, without rebuild of solution - host.writeFile(dependencyTs.path, `function fooBar() { } -${dependencyTs.content}`); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithDependencyChange, - expectedScriptInfos(), - expectedWatchedFiles(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when defining project source changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session } = setupWithAction(); + function expectedWatchedFilesWhenNoMap() { + // Watches deleted file + return removePath(expectedWatchedFilesWhenMapped(), dependencyTsPath); + } - // change - // Make change, without rebuild of solution - host.writeFile(dependencyTs.path, `function fooBar() { } -${dependencyTs.content}`); + function expectedScriptInfosWhenNoDts() { + // No dts, no map, no dependency + return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); + } - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithDependencyChange, - expectedScriptInfos(), - expectedWatchedFiles(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + function expectedWatchedFilesWhenNoDts() { + return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); + } - it("when projects are not built", () => { - const host = createServerHost(files); - const session = createSession(host); - openFilesForSession([mainTs, randomFile], session); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfos(), - expectedWatchedFiles(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); - }); + it("can go to definition correctly", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + ); }); - describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithMainTs(createSessionWithDisabledProjectReferences, onHostCreate); - } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - checkMainProjectWithDisabledProjectReferences(session); - } + // change + makeChangeToMainTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - function checkProjectsWithoutDts(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - checkMainProjectWithDisabledProjectReferencesWithoutDts(session); - } + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - function expectedScriptInfosWhenMapped() { - return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; - } + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - function expectedWatchedFilesWhenMapped() { - return [dependencyTsPath, libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path, dependencyConfig.path]; - } + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - function expectedScriptInfosWhenNoMap() { - // Because map is deleted, map and dependency are released - return removePath(expectedScriptInfosWhenMapped(), dtsMapPath, dependencyTsPath); - } + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - function expectedWatchedFilesWhenNoMap() { - // Watches deleted file - return removePath(expectedWatchedFilesWhenMapped(), dependencyTsPath); - } + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - function expectedScriptInfosWhenNoDts() { - // No dts, no map, no dependency - return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); - } + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - function expectedWatchedFilesWhenNoDts() { - return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); - } + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); - it("can go to definition correctly", () => { - const { host, session } = setup(); - checkProjects(session); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - ); + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); }); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + // The script info for map is collected only after file open + expectedScriptInfosWhenNoMap().concat(dependencyTs.path), + expectedWatchedFilesWhenNoMap().concat(dependencyTsPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjectsWithoutDts(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; + host.deleteFile(dtsLocation); }); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - }); + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + // The script info for map is collected only after file open + expectedScriptInfosWhenNoDts().concat(dependencyTs.path, dtsMapLocation), + expectedWatchedFilesWhenNoDts().concat(dependencyTsPath, dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + }); + }); - it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoMap, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + describe("from defining project: rename", () => { + function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { + const result = setup(onHostCreate); + result.session.executeCommandSeq(renameFromDependencyTs(1).request); + return { ...result, ...getDocumentPositionMapper(result.session) }; + } - // The dependency file is deleted when orphan files are collected - host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoMap, - // The script info for map is collected only after file open - expectedScriptInfosWhenNoMap().concat(dependencyTs.path), - expectedWatchedFilesWhenNoMap().concat(dependencyTsPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); - }); + function verifyScriptInfoCollection( + session: TestSession, + host: TestServerHost, + expectedInfos: readonly string[], + expectedWatchedFiles: readonly string[], + ) { + return verifyScriptInfoCollectionWith(session, host, [dependencyTs], expectedInfos, expectedWatchedFiles); + } - it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjectsWithoutDts(session); - - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoDts, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjectsWithoutDts(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + checkDependencyProjectWith(session); + } - // The dependency file is deleted when orphan files are collected - host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoDts, - // The script info for map is collected only after file open - expectedScriptInfosWhenNoDts().concat(dependencyTs.path, dtsMapLocation), - expectedWatchedFilesWhenNoDts().concat(dependencyTsPath, dtsMapPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjectsWithoutDts(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); - }); - }); - }); + function expectedScriptInfos() { + return [libFile.path, dtsLocation, dtsMapLocation, dependencyTs.path, randomFile.path]; + } - describe("from defining project: rename", () => { - function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { - const result = setup(onHostCreate); - result.session.executeCommandSeq(renameFromDependencyTs(1).request); - return { ...result, ...getDocumentPositionMapper(result.session) }; - } + function expectedWatchedFiles() { + return [libFile.path, dtsPath, dtsMapPath, dependencyConfig.path, randomConfig.path]; + } - function verifyScriptInfoCollection( - session: TestSession, - host: TestServerHost, - expectedInfos: readonly string[], - expectedWatchedFiles: readonly string[], - ) { - return verifyScriptInfoCollectionWith(session, host, [dependencyTs], expectedInfos, expectedWatchedFiles); - } + function expectedScriptInfosWhenNoMap() { + // No map + return removePath(expectedScriptInfos(), dtsMapPath); + } - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - checkDependencyProjectWith(session); - } + function expectedWatchedFilesWhenNoMap() { + // Watches deleted file = map file + return expectedWatchedFiles(); + } - function expectedScriptInfos() { - return [libFile.path, dtsLocation, dtsMapLocation, dependencyTs.path, randomFile.path]; - } + function expectedScriptInfosWhenNoDts() { + // no dts or map since dts itself doesnt exist + return removePath(expectedScriptInfos(), dtsPath, dtsMapPath); + } - function expectedWatchedFiles() { - return [libFile.path, dtsPath, dtsMapPath, dependencyConfig.path, randomConfig.path]; - } + function expectedWatchedFilesWhenNoDts() { + // watch deleted file + return removePath(expectedWatchedFiles(), dtsMapPath); + } - function expectedScriptInfosWhenNoMap() { - // No map - return removePath(expectedScriptInfos(), dtsMapPath); + describe("when main tsconfig doesnt have project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithDependencyTs(createSessionWithoutProjectReferences, onHostCreate); } - function expectedWatchedFilesWhenNoMap() { - // Watches deleted file = map file - return expectedWatchedFiles(); + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); } - function expectedScriptInfosWhenNoDts() { - // no dts or map since dts itself doesnt exist - return removePath(expectedScriptInfos(), dtsPath, dtsMapPath); - } + it("rename locations from dependency", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); - function expectedWatchedFilesWhenNoDts() { - // watch deleted file - return removePath(expectedWatchedFiles(), dtsMapPath); - } + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - describe("when main tsconfig doesnt have project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithDependencyTs(createSessionWithoutProjectReferences, onHostCreate); - } + // change + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - it("rename locations from dependency", () => { - const { host, session } = setup(); - checkProjects(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); - }); + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToDependencyTs(session); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToDependencyTs(session); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - }); + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // The dependency file is deleted when orphan files are collected + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - // Map is collected after file open - expectedScriptInfosWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesWhenNoDts().concat(dtsPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); }); - describe("when main tsconfig has project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithDependencyTs(createSessionWithProjectReferences, onHostCreate); - } + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + // Map is collected after file open + expectedScriptInfosWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesWhenNoDts().concat(dtsPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + }); + describe("when main tsconfig has project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithDependencyTs(createSessionWithProjectReferences, onHostCreate); + } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - it("rename locations from dependency", () => { - const { host, session } = setup(); - checkProjects(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); - }); + it("rename locations from dependency", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToDependencyTs(session); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToDependencyTs(session); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - }); + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - // Map is collected after file open - expectedScriptInfosWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesWhenNoDts().concat(dtsPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); }); - it(`when defining project source changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + // Map is collected after file open + expectedScriptInfosWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesWhenNoDts().concat(dtsPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + + it(`when defining project source changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // change - // Make change, without rebuild of solution - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } + // change + // Make change, without rebuild of solution + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } `} - }); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithDependencyChange, - expectedScriptInfos(), - expectedWatchedFiles(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); }); - it(`when defining project source changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session } = setupWithAction(); - - // change - // Make change, without rebuild of solution - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithDependencyChange, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when defining project source changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session } = setupWithAction(); + + // change + // Make change, without rebuild of solution + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } `} - }); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithDependencyChange, - expectedScriptInfos(), - expectedWatchedFiles(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); }); - it("when projects are not built", () => { - const host = createServerHost(files); - const session = createSession(host); - openFilesForSession([dependencyTs, randomFile], session); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); - }); + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithDependencyChange, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); }); - describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithDependencyTs(createSessionWithDisabledProjectReferences, onHostCreate); - } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + it("when projects are not built", () => { + const host = createServerHost(files); + const session = createSession(host); + openFilesForSession([dependencyTs, randomFile], session); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + }); + describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithDependencyTs(createSessionWithDisabledProjectReferences, onHostCreate); + } - it("rename locations from dependency", () => { - const { host, session } = setup(); - checkProjects(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles(), - ); - }); + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToDependencyTs(session); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToDependencyTs(session); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + it("rename locations from dependency", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - }); + // change + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // The dependency file is deleted when orphan files are collected + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfos(), - expectedWatchedFiles(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfos(), - expectedWatchedFiles() - ); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - // Map is collected after file open - expectedScriptInfosWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesWhenNoDts().concat(dtsPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoDts(), - expectedWatchedFilesWhenNoDts(), - ); }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + // Map is collected after file open + expectedScriptInfosWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesWhenNoDts().concat(dtsPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); }); }); + }); - describe("when opening depedency and usage project: goToDef and rename", () => { - function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { - const result = setup(onHostCreate); - result.session.executeCommandSeq(goToDefFromMainTs(1).request); - result.session.executeCommandSeq(renameFromDependencyTsWithBothProjectsOpen(1).request); - return { ...result, ...getDocumentPositionMapper(result.session) }; + describe("when opening depedency and usage project: goToDef and rename", () => { + function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { + const result = setup(onHostCreate); + result.session.executeCommandSeq(goToDefFromMainTs(1).request); + result.session.executeCommandSeq(renameFromDependencyTsWithBothProjectsOpen(1).request); + return { ...result, ...getDocumentPositionMapper(result.session) }; + } + + function verifyScriptInfoCollection( + session: TestSession, + host: TestServerHost, + expectedInfos: readonly string[], + expectedWatchedFiles: readonly string[], + ) { + return verifyScriptInfoCollectionWith(session, host, [mainTs, dependencyTs], expectedInfos, expectedWatchedFiles); + } + + describe("when main tsconfig doesnt have project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTsAndDependencyTs(createSessionWithoutProjectReferences, onHostCreate); } - function verifyScriptInfoCollection( - session: TestSession, - host: TestServerHost, - expectedInfos: readonly string[], - expectedWatchedFiles: readonly string[], - ) { - return verifyScriptInfoCollectionWith(session, host, [mainTs, dependencyTs], expectedInfos, expectedWatchedFiles); + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); } - describe("when main tsconfig doesnt have project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithMainTsAndDependencyTs(createSessionWithoutProjectReferences, onHostCreate); - } + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); + checkMainProjectWithoutProjectReferences(session); + checkDependencyProjectWith(session); + } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + function checkProjectsWithoutDts(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); + checkMainProjectWithoutProjectReferencesWithoutDts(session); + checkDependencyProjectWith(session); + } - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); - checkMainProjectWithoutProjectReferences(session); - checkDependencyProjectWith(session); - } + function expectedScriptInfosWhenMapped() { + return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; + } - function checkProjectsWithoutDts(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); - checkMainProjectWithoutProjectReferencesWithoutDts(session); - checkDependencyProjectWith(session); - } + function expectedWatchedFilesWhenMapped() { + return [libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path, dependencyConfig.path]; + } - function expectedScriptInfosWhenMapped() { - return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; - } + function expectedScriptInfosWhenNoMap() { + // Because map is deleted, map and dependency are released + return removePath(expectedScriptInfosWhenMapped(), dtsMapPath); + } - function expectedWatchedFilesWhenMapped() { - return [libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path, dependencyConfig.path]; - } + function expectedWatchedFilesWhenNoMap() { + // Watches deleted file + return expectedWatchedFilesWhenMapped(); + } - function expectedScriptInfosWhenNoMap() { - // Because map is deleted, map and dependency are released - return removePath(expectedScriptInfosWhenMapped(), dtsMapPath); - } + function expectedScriptInfosAfterGotoDefWhenNoDts() { + // No dts, no map + return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); + } - function expectedWatchedFilesWhenNoMap() { - // Watches deleted file - return expectedWatchedFilesWhenMapped(); - } + function expectedWatchedFilesAfterGotoDefWhenNoDts() { + return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath); + } - function expectedScriptInfosAfterGotoDefWhenNoDts() { - // No dts, no map - return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); - } + function expectedScriptInfosAfterRenameWhenNoDts() { + // No dts, no map + return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); + } - function expectedWatchedFilesAfterGotoDefWhenNoDts() { - return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath); - } + function expectedWatchedFilesAfterRenameWhenNoDts() { + // Watches dts file but not map file + return removePath(expectedWatchedFilesWhenMapped(), dtsMapPath); + } - function expectedScriptInfosAfterRenameWhenNoDts() { - // No dts, no map - return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); - } + it("goto Definition in usage and rename locations from defining project", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + ); + }); - function expectedWatchedFilesAfterRenameWhenNoDts() { - // Watches dts file but not map file - return removePath(expectedWatchedFilesWhenMapped(), dtsMapPath); - } + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - it("goto Definition in usage and rename locations from defining project", () => { - const { host, session } = setup(); - checkProjects(session); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - ); - }); + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - makeChangeToDependencyTs(session); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - makeChangeToDependencyTs(session); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoMap, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - newDependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoMap, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - newDependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjectsWithoutDts(session); - - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoDts, - expectedScriptInfosAfterGotoDefWhenNoDts(), - expectedWatchedFilesAfterGotoDefWhenNoDts(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjectsWithoutDts(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - newDependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjectsWithoutDts(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + expectedScriptInfosAfterGotoDefWhenNoDts(), + expectedWatchedFilesAfterGotoDefWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoDts, - // The script info for map is collected only after file open - expectedScriptInfosAfterGotoDefWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesAfterGotoDefWhenNoDts().concat(dtsMapPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - // The script info for map is collected only after file open - expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesAfterRenameWhenNoDts().concat(dtsMapPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjectsWithoutDts(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); }); - describe("when main tsconfig has project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithMainTsAndDependencyTs(createSessionWithProjectReferences, onHostCreate); - } + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + // The script info for map is collected only after file open + expectedScriptInfosAfterGotoDefWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesAfterGotoDefWhenNoDts().concat(dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + // The script info for map is collected only after file open + expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesAfterRenameWhenNoDts().concat(dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + }); + describe("when main tsconfig has project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTsAndDependencyTs(createSessionWithProjectReferences, onHostCreate); + } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); - checkMainProjectWithProjectReferences(session); - checkDependencyProjectWith(session); - } + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); + checkMainProjectWithProjectReferences(session); + checkDependencyProjectWith(session); + } - function expectedScriptInfosAfterGotoDef() { - return [dependencyTs.path, libFile.path, mainTs.path, randomFile.path]; - } + function expectedScriptInfosAfterGotoDef() { + return [dependencyTs.path, libFile.path, mainTs.path, randomFile.path]; + } - function expectedWatchedFilesAfterGotoDef() { - return [dependencyConfig.path, libFile.path, mainConfig.path, randomConfig.path]; - } + function expectedWatchedFilesAfterGotoDef() { + return [dependencyConfig.path, libFile.path, mainConfig.path, randomConfig.path]; + } - function expectedScriptInfosAfterRenameWhenMapped() { - return expectedScriptInfosAfterGotoDef().concat(dtsLocation, dtsMapLocation); - } + function expectedScriptInfosAfterRenameWhenMapped() { + return expectedScriptInfosAfterGotoDef().concat(dtsLocation, dtsMapLocation); + } - function expectedWatchedFilesAfterRenameWhenMapped() { - return expectedWatchedFilesAfterGotoDef().concat(dtsPath, dtsMapPath); - } + function expectedWatchedFilesAfterRenameWhenMapped() { + return expectedWatchedFilesAfterGotoDef().concat(dtsPath, dtsMapPath); + } - function expectedScriptInfosAfterRenameWhenNoMap() { - // Map file is not present - return removePath(expectedScriptInfosAfterRenameWhenMapped(), dtsMapPath); - } + function expectedScriptInfosAfterRenameWhenNoMap() { + // Map file is not present + return removePath(expectedScriptInfosAfterRenameWhenMapped(), dtsMapPath); + } - function expectedWatchedFilesAfterRenameWhenNoMap() { - // Watches map file - return expectedWatchedFilesAfterRenameWhenMapped(); - } + function expectedWatchedFilesAfterRenameWhenNoMap() { + // Watches map file + return expectedWatchedFilesAfterRenameWhenMapped(); + } - function expectedScriptInfosAfterRenameWhenNoDts() { - // map and dts not present - return removePath(expectedScriptInfosAfterRenameWhenMapped(), dtsPath, dtsMapPath); - } + function expectedScriptInfosAfterRenameWhenNoDts() { + // map and dts not present + return removePath(expectedScriptInfosAfterRenameWhenMapped(), dtsPath, dtsMapPath); + } - function expectedWatchedFilesAfterRenameWhenNoDts() { - // Watches dts file but not map - return removePath(expectedWatchedFilesAfterRenameWhenMapped(), dtsMapPath); - } + function expectedWatchedFilesAfterRenameWhenNoDts() { + // Watches dts file but not map + return removePath(expectedWatchedFilesAfterRenameWhenMapped(), dtsMapPath); + } - it("goto Definition in usage and rename locations from defining project", () => { - const { host, session } = setup(); - checkProjects(session); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterGotoDef(), - expectedWatchedFilesAfterGotoDef(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - ); - }); + it("goto Definition in usage and rename locations from defining project", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterGotoDef(), + expectedWatchedFilesAfterGotoDef(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + ); + }); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - makeChangeToDependencyTs(session); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - makeChangeToDependencyTs(session); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - }); + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterGotoDef(), - expectedWatchedFilesAfterGotoDef(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenNoMap(), - expectedWatchedFilesAfterRenameWhenNoMap(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoMap(), - expectedWatchedFilesAfterRenameWhenNoMap(), - ); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenNoMap(), - // Map file is reset so its not watched any more, as this action doesnt need map - removePath(expectedWatchedFilesAfterRenameWhenNoMap(), dtsMapPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true, - /*skipMapPathInDtsInfo*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - ); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // The dependency file is deleted when orphan files are collected + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterGotoDef(), + expectedWatchedFilesAfterGotoDef(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenNoMap(), + expectedWatchedFilesAfterRenameWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoMap(), + expectedWatchedFilesAfterRenameWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterRenameWhenNoMap(), - // Map file is reset so its not watched any more, as this action doesnt need map - removePath(expectedWatchedFilesAfterRenameWhenNoMap(), dtsMapPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false, - /*skipMapPathInDtsInfo*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenNoMap(), - expectedWatchedFilesAfterRenameWhenNoMap(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoMap(), - expectedWatchedFilesAfterRenameWhenNoMap(), - ); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterGotoDef(), - expectedWatchedFilesAfterGotoDef(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterGotoDef(), - // Since the project for dependency is not updated, the watcher from rename for dts still there - expectedWatchedFilesAfterGotoDef().concat(dtsPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - ); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenNoMap(), + // Map file is reset so its not watched any more, as this action doesnt need map + removePath(expectedWatchedFilesAfterRenameWhenNoMap(), dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true, + /*skipMapPathInDtsInfo*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenNoMap(), + // Map file is reset so its not watched any more, as this action doesnt need map + removePath(expectedWatchedFilesAfterRenameWhenNoMap(), dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false, + /*skipMapPathInDtsInfo*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenNoMap(), + expectedWatchedFilesAfterRenameWhenNoMap(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoMap(), + expectedWatchedFilesAfterRenameWhenNoMap(), + ); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterGotoDef(), + expectedWatchedFilesAfterGotoDef(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - // Map collection after file open - expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), - // not watching dts since this operation doesnt need it - removePath(expectedWatchedFilesAfterRenameWhenNoDts(), dtsPath).concat(dtsMapPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - // Map collection after file open - expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesAfterRenameWhenNoDts().concat(dtsMapPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); }); - it(`when defining project source changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterGotoDef(), + // Since the project for dependency is not updated, the watcher from rename for dts still there + expectedWatchedFilesAfterGotoDef().concat(dtsPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + // Map collection after file open + expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), + // not watching dts since this operation doesnt need it + removePath(expectedWatchedFilesAfterRenameWhenNoDts(), dtsPath).concat(dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + // Map collection after file open + expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesAfterRenameWhenNoDts().concat(dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + + it(`when defining project source changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // change - // Make change, without rebuild of solution - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } + // change + // Make change, without rebuild of solution + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } `} - }); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithDependencyChange, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpenWithDependencyChange, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); }); - it(`when defining project source changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - // Make change, without rebuild of solution - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithDependencyChange, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpenWithDependencyChange, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when defining project source changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + // Make change, without rebuild of solution + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } `} - }); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithDependencyChange, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpenWithDependencyChange, - expectedScriptInfosAfterRenameWhenMapped(), - expectedWatchedFilesAfterRenameWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); }); - it("when projects are not built", () => { - const host = createServerHost(files); - const session = createSession(host); - openFilesForSession([mainTs, dependencyTs, randomFile], session); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosAfterGotoDef(), - expectedWatchedFilesAfterGotoDef(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); - }); + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithDependencyChange, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpenWithDependencyChange, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); }); - describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithMainTsAndDependencyTs(createSessionWithDisabledProjectReferences, onHostCreate); - } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + it("when projects are not built", () => { + const host = createServerHost(files); + const session = createSession(host); + openFilesForSession([mainTs, dependencyTs, randomFile], session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterGotoDef(), + expectedWatchedFilesAfterGotoDef(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + }); + describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTsAndDependencyTs(createSessionWithDisabledProjectReferences, onHostCreate); + } - function checkProjects(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); - checkMainProjectWithDisabledProjectReferences(session); - checkDependencyProjectWith(session); - } + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - function checkProjectsWithoutDts(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); - checkMainProjectWithDisabledProjectReferencesWithoutDts(session); - checkDependencyProjectWith(session); - } + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); + checkMainProjectWithDisabledProjectReferences(session); + checkDependencyProjectWith(session); + } - function expectedScriptInfosWhenMapped() { - return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; - } + function checkProjectsWithoutDts(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); + checkMainProjectWithDisabledProjectReferencesWithoutDts(session); + checkDependencyProjectWith(session); + } - function expectedWatchedFilesWhenMapped() { - return [libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path, dependencyConfig.path]; - } + function expectedScriptInfosWhenMapped() { + return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; + } - function expectedScriptInfosWhenNoMap() { - // Because map is deleted, map and dependency are released - return removePath(expectedScriptInfosWhenMapped(), dtsMapPath); - } + function expectedWatchedFilesWhenMapped() { + return [libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path, dependencyConfig.path]; + } - function expectedWatchedFilesWhenNoMap() { - // Watches deleted file - return expectedWatchedFilesWhenMapped(); - } + function expectedScriptInfosWhenNoMap() { + // Because map is deleted, map and dependency are released + return removePath(expectedScriptInfosWhenMapped(), dtsMapPath); + } - function expectedScriptInfosAfterGotoDefWhenNoDts() { - // No dts, no map - return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); - } + function expectedWatchedFilesWhenNoMap() { + // Watches deleted file + return expectedWatchedFilesWhenMapped(); + } - function expectedWatchedFilesAfterGotoDefWhenNoDts() { - return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath); - } + function expectedScriptInfosAfterGotoDefWhenNoDts() { + // No dts, no map + return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); + } - function expectedScriptInfosAfterRenameWhenNoDts() { - // No dts, no map - return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); - } + function expectedWatchedFilesAfterGotoDefWhenNoDts() { + return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath); + } - function expectedWatchedFilesAfterRenameWhenNoDts() { - // Watches dts file but not map file - return removePath(expectedWatchedFilesWhenMapped(), dtsMapPath); - } + function expectedScriptInfosAfterRenameWhenNoDts() { + // No dts, no map + return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); + } - it("goto Definition in usage and rename locations from defining project", () => { - const { host, session } = setup(); - checkProjects(session); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - ); - }); + function expectedWatchedFilesAfterRenameWhenNoDts() { + // Watches dts file but not map file + return removePath(expectedWatchedFilesWhenMapped(), dtsMapPath); + } - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - makeChangeToDependencyTs(session); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - makeChangeToDependencyTs(session); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + it("goto Definition in usage and rename locations from defining project", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + ); + }); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - checkProjects(session); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - }); + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - it(`with depedency files map file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); - checkProjects(session); - - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoMap, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - newDependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoMap, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - newDependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenNoMap(), - expectedWatchedFilesWhenNoMap(), - ); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { host, session } = setup(host => host.deleteFile(dtsLocation)); - checkProjectsWithoutDts(session); - - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoDts, - expectedScriptInfosAfterGotoDefWhenNoDts(), - expectedWatchedFilesAfterGotoDefWhenNoDts(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjectsWithoutDts(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - host, - goToDefFromMainTs, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - host, - renameFromDependencyTsWithBothProjectsOpen, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped(), - newDependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjects(session); - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosWhenMapped(), - expectedWatchedFilesWhenMapped() - ); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjectsWithoutDts(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + expectedScriptInfosAfterGotoDefWhenNoDts(), + expectedWatchedFilesAfterGotoDefWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - host, - goToDefFromMainTsWithNoDts, - // The script info for map is collected only after file open - expectedScriptInfosAfterGotoDefWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesAfterGotoDefWhenNoDts().concat(dtsMapPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - host, - renameFromDependencyTs, - // The script info for map is collected only after file open - expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), - expectedWatchedFilesAfterRenameWhenNoDts().concat(dtsMapPath), - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - checkProjectsWithoutDts(session); - - // Script info collection should behave as "noDts" - verifyScriptInfoCollection( - session, - host, - expectedScriptInfosAfterRenameWhenNoDts(), - expectedWatchedFilesAfterRenameWhenNoDts(), - ); }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + // The script info for map is collected only after file open + expectedScriptInfosAfterGotoDefWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesAfterGotoDefWhenNoDts().concat(dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + // The script info for map is collected only after file open + expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesAfterRenameWhenNoDts().concat(dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); }); }); }); +}); } diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index cd9ba6c92bf0c..08c79916831a9 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -1,1324 +1,1324 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: Projects", () => { - it("handles the missing files - that were added to program because they were added with /// { - const file1: File = { - path: "/a/b/commonFile1.ts", - content: `/// +describe("unittests:: tsserver:: Projects", () => { + it("handles the missing files - that were added to program because they were added with /// { + const file1: File = { + path: "/a/b/commonFile1.ts", + content: `/// let x = y` - }; - const host = createServerHost([file1, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); - openFilesForSession([file1], session); - - const getErrRequest = makeSessionRequest( - server.CommandNames.SemanticDiagnosticsSync, - { file: file1.path } - ); - - // Two errors: CommonFile2 not found and cannot find name y - session.executeCommand(getErrRequest); - - host.writeFile(commonFile2.path, commonFile2.content); - host.runQueuedTimeoutCallbacks(); - session.executeCommand(getErrRequest); - baselineTsserverLogs("projects", "handles the missing files added with tripleslash ref", session); - }); + }; + const host = createServerHost([file1, libFile]); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs() }); + openFilesForSession([file1], session); + + const getErrRequest = makeSessionRequest( + server.CommandNames.SemanticDiagnosticsSync, + { file: file1.path } + ); + + // Two errors: CommonFile2 not found and cannot find name y + session.executeCommand(getErrRequest); + + host.writeFile(commonFile2.path, commonFile2.content); + host.runQueuedTimeoutCallbacks(); + session.executeCommand(getErrRequest); + baselineTsserverLogs("projects", "handles the missing files added with tripleslash ref", session); + }); - it("should create new inferred projects for files excluded from a configured project", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should create new inferred projects for files excluded from a configured project", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {}, "files": ["${commonFile1.path}", "${commonFile2.path}"] }` - }; - const files = [commonFile1, commonFile2, configFile]; - const host = createServerHost(files); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - configFile.content = `{ + }; + const files = [commonFile1, commonFile2, configFile]; + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + configFile.content = `{ "compilerOptions": {}, "files": ["${commonFile1.path}"] }`; - host.writeFile(configFile.path, configFile.content); + host.writeFile(configFile.path, configFile.content); - checkNumberOfConfiguredProjects(projectService, 1); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - host.checkTimeoutQueueLengthAndRun(2); // Update the configured project + refresh inferred projects - checkNumberOfConfiguredProjects(projectService, 1); - checkProjectRootFiles(project, [commonFile1.path]); + checkNumberOfConfiguredProjects(projectService, 1); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + host.checkTimeoutQueueLengthAndRun(2); // Update the configured project + refresh inferred projects + checkNumberOfConfiguredProjects(projectService, 1); + checkProjectRootFiles(project, [commonFile1.path]); - projectService.openClientFile(commonFile2.path); - checkNumberOfInferredProjects(projectService, 1); - }); + projectService.openClientFile(commonFile2.path); + checkNumberOfInferredProjects(projectService, 1); + }); - it("should disable features when the files are too large", () => { - const file1 = { - path: "/a/b/f1.js", - content: "let x =1;", - fileSize: 10 * 1024 * 1024 - }; - const file2 = { - path: "/a/b/f2.js", - content: "let y =1;", - fileSize: 6 * 1024 * 1024 - }; - const file3 = { - path: "/a/b/f3.js", - content: "let y =1;", - fileSize: 6 * 1024 * 1024 - }; + it("should disable features when the files are too large", () => { + const file1 = { + path: "/a/b/f1.js", + content: "let x =1;", + fileSize: 10 * 1024 * 1024 + }; + const file2 = { + path: "/a/b/f2.js", + content: "let y =1;", + fileSize: 6 * 1024 * 1024 + }; + const file3 = { + path: "/a/b/f3.js", + content: "let y =1;", + fileSize: 6 * 1024 * 1024 + }; + + const proj1name = "proj1", proj2name = "proj2", proj3name = "proj3"; + + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openExternalProject({ rootFiles: toExternalFiles([file1.path]), options: {}, projectFileName: proj1name }); + const proj1 = projectService.findProject(proj1name)!; + assert.isTrue(proj1.languageServiceEnabled); + + projectService.openExternalProject({ rootFiles: toExternalFiles([file2.path]), options: {}, projectFileName: proj2name }); + const proj2 = projectService.findProject(proj2name)!; + assert.isTrue(proj2.languageServiceEnabled); + + projectService.openExternalProject({ rootFiles: toExternalFiles([file3.path]), options: {}, projectFileName: proj3name }); + const proj3 = projectService.findProject(proj3name)!; + assert.isFalse(proj3.languageServiceEnabled); + }); - const proj1name = "proj1", proj2name = "proj2", proj3name = "proj3"; + it("should not crash when opening a file in a project with a disabled language service", () => { + const file1 = { + path: "/a/b/f1.js", + content: "let x =1;", + fileSize: 50 * 1024 * 1024 + }; + const file2 = { + path: "/a/b/f2.js", + content: "let x =1;", + fileSize: 100 + }; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); + const projName = "proj1"; - projectService.openExternalProject({ rootFiles: toExternalFiles([file1.path]), options: {}, projectFileName: proj1name }); - const proj1 = projectService.findProject(proj1name)!; - assert.isTrue(proj1.languageServiceEnabled); + const host = createServerHost([file1, file2]); + const projectService = createProjectService(host, { useSingleInferredProject: true, eventHandler: noop }); - projectService.openExternalProject({ rootFiles: toExternalFiles([file2.path]), options: {}, projectFileName: proj2name }); - const proj2 = projectService.findProject(proj2name)!; - assert.isTrue(proj2.languageServiceEnabled); + projectService.openExternalProject({ rootFiles: toExternalFiles([file1.path, file2.path]), options: {}, projectFileName: projName }); + const proj1 = projectService.findProject(projName)!; + assert.isFalse(proj1.languageServiceEnabled); - projectService.openExternalProject({ rootFiles: toExternalFiles([file3.path]), options: {}, projectFileName: proj3name }); - const proj3 = projectService.findProject(proj3name)!; - assert.isFalse(proj3.languageServiceEnabled); - }); + assert.doesNotThrow(() => projectService.openClientFile(file2.path)); + }); - it("should not crash when opening a file in a project with a disabled language service", () => { + describe("ignoreConfigFiles", () => { + it("external project including config file", () => { const file1 = { - path: "/a/b/f1.js", - content: "let x =1;", - fileSize: 50 * 1024 * 1024 + path: "/a/b/f1.ts", + content: "let x =1;" }; - const file2 = { - path: "/a/b/f2.js", - content: "let x =1;", - fileSize: 100 + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) }; - const projName = "proj1"; - - const host = createServerHost([file1, file2]); - const projectService = createProjectService(host, { useSingleInferredProject: true, eventHandler: noop }); - - projectService.openExternalProject({ rootFiles: toExternalFiles([file1.path, file2.path]), options: {}, projectFileName: projName }); - const proj1 = projectService.findProject(projName)!; - assert.isFalse(proj1.languageServiceEnabled); - - assert.doesNotThrow(() => projectService.openClientFile(file2.path)); - }); - - describe("ignoreConfigFiles", () => { - it("external project including config file", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; - const config1 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: {}, - files: ["f1.ts"] - } - ) - }; - - const externalProjectName = "externalproject"; - const host = createServerHost([file1, config1]); - const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); - projectService.openExternalProject({ - rootFiles: toExternalFiles([file1.path, config1.path]), - options: {}, - projectFileName: externalProjectName - }); - - checkNumberOfProjects(projectService, { externalProjects: 1 }); - const proj = projectService.externalProjects[0]; - assert.isDefined(proj); - - assert.isTrue(proj.fileExists(file1.path)); - }); - - it("loose file included in config file (openClientFile)", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; - const config1 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: {}, - files: ["f1.ts"] - } - ) - }; - - const host = createServerHost([file1, config1]); - const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); - projectService.openClientFile(file1.path, file1.content); - - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const proj = projectService.inferredProjects[0]; - assert.isDefined(proj); - - assert.isTrue(proj.fileExists(file1.path)); - }); - - it("loose file included in config file (applyCodeChanges)", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; - const config1 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: {}, - files: ["f1.ts"] - } - ) - }; - - const host = createServerHost([file1, config1]); - const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); - projectService.applyChangesInOpenFiles(singleIterator({ fileName: file1.path, content: file1.content })); - - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const proj = projectService.inferredProjects[0]; - assert.isDefined(proj); - - assert.isTrue(proj.fileExists(file1.path)); + const externalProjectName = "externalproject"; + const host = createServerHost([file1, config1]); + const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); + projectService.openExternalProject({ + rootFiles: toExternalFiles([file1.path, config1.path]), + options: {}, + projectFileName: externalProjectName }); - }); - - it("reload regular file after closing", () => { - const f1 = { - path: "/a/b/app.ts", - content: "x." - }; - const f2 = { - path: "/a/b/lib.ts", - content: "let x: number;" - }; - - const host = createServerHost([f1, f2, libFile]); - const service = createProjectService(host); - service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: toExternalFiles([f1.path, f2.path]), options: {} }); - - service.openClientFile(f1.path); - service.openClientFile(f2.path, "let x: string"); - - service.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); - - const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, emptyOptions)!; - // should contain completions for string - assert.isTrue(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'"); - assert.isFalse(completions1.entries.some(e => e.name === "toExponential"), "should not contain 'toExponential'"); - - service.closeClientFile(f2.path); - const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, emptyOptions)!; - // should contain completions for string - assert.isFalse(completions2.entries.some(e => e.name === "charAt"), "should not contain 'charAt'"); - assert.isTrue(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'"); - }); - - it("clear mixed content file after closing", () => { - const f1 = { - path: "/a/b/app.ts", - content: " " - }; - const f2 = { - path: "/a/b/lib.html", - content: "" - }; - - const host = createServerHost([f1, f2, libFile]); - const service = createProjectService(host); - service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], options: {} }); - - service.openClientFile(f1.path); - service.openClientFile(f2.path, "let somelongname: string"); - service.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); - - const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, emptyOptions)!; - assert.isTrue(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'"); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + const proj = projectService.externalProjects[0]; + assert.isDefined(proj); - service.closeClientFile(f2.path); - const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, emptyOptions)!; - assert.isFalse(completions2.entries.some(e => e.name === "somelongname"), "should not contain 'somelongname'"); - const sf2 = service.externalProjects[0].getLanguageService().getProgram()!.getSourceFile(f2.path)!; - assert.equal(sf2.text, ""); + assert.isTrue(proj.fileExists(file1.path)); }); - it("changes in closed files are reflected in project structure", () => { + it("loose file included in config file (openClientFile)", () => { const file1 = { path: "/a/b/f1.ts", - content: `export * from "./f2"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: `export let x = 1` + content: "let x =1;" }; - const file3 = { - path: "/a/c/f3.ts", - content: `export let y = 1;` + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) }; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); + const host = createServerHost([file1, config1]); + const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); + projectService.openClientFile(file1.path, file1.content); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const inferredProject0 = projectService.inferredProjects[0]; - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]); - - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProject0); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]); - const inferredProject1 = projectService.inferredProjects[1]; - checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); - - host.writeFile(file2.path, `export * from "../c/f3"`); // now inferred project should inclule file3 - host.checkTimeoutQueueLengthAndRun(2); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProject0); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); - assert.strictEqual(projectService.inferredProjects[1], inferredProject1); - assert.isTrue(inferredProject1.isOrphan()); + const proj = projectService.inferredProjects[0]; + assert.isDefined(proj); + + assert.isTrue(proj.fileExists(file1.path)); }); - it("deleted files affect project structure", () => { + it("loose file included in config file (applyCodeChanges)", () => { const file1 = { path: "/a/b/f1.ts", - content: `export * from "./f2"` + content: "let x =1;" }; - const file2 = { - path: "/a/b/f2.ts", - content: `export * from "../c/f3"` - }; - const file3 = { - path: "/a/c/f3.ts", - content: `export let y = 1;` + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) }; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); + const host = createServerHost([file1, config1]); + const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); + projectService.applyChangesInOpenFiles(singleIterator({ fileName: file1.path, content: file1.content })); checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + assert.isDefined(proj); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); + assert.isTrue(proj.fileExists(file1.path)); + }); + }); - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); + it("reload regular file after closing", () => { + const f1 = { + path: "/a/b/app.ts", + content: "x." + }; + const f2 = { + path: "/a/b/lib.ts", + content: "let x: number;" + }; + + const host = createServerHost([f1, f2, libFile]); + const service = createProjectService(host); + service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: toExternalFiles([f1.path, f2.path]), options: {} }); + + service.openClientFile(f1.path); + service.openClientFile(f2.path, "let x: string"); + + service.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); + + const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, emptyOptions)!; + // should contain completions for string + assert.isTrue(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'"); + assert.isFalse(completions1.entries.some(e => e.name === "toExponential"), "should not contain 'toExponential'"); + + service.closeClientFile(f2.path); + const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, emptyOptions)!; + // should contain completions for string + assert.isFalse(completions2.entries.some(e => e.name === "charAt"), "should not contain 'charAt'"); + assert.isTrue(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'"); + }); - host.deleteFile(file2.path); - host.checkTimeoutQueueLengthAndRun(2); + it("clear mixed content file after closing", () => { + const f1 = { + path: "/a/b/app.ts", + content: " " + }; + const f2 = { + path: "/a/b/lib.html", + content: "" + }; + + const host = createServerHost([f1, f2, libFile]); + const service = createProjectService(host); + service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], options: {} }); + + service.openClientFile(f1.path); + service.openClientFile(f2.path, "let somelongname: string"); + + service.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); + + const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, emptyOptions)!; + assert.isTrue(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'"); + + service.closeClientFile(f2.path); + const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, emptyOptions)!; + assert.isFalse(completions2.entries.some(e => e.name === "somelongname"), "should not contain 'somelongname'"); + const sf2 = service.externalProjects[0].getLanguageService().getProgram()!.getSourceFile(f2.path)!; + assert.equal(sf2.text, ""); + }); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); + it("changes in closed files are reflected in project structure", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export let x = 1` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const inferredProject0 = projectService.inferredProjects[0]; + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]); + + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProject0); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]); + const inferredProject1 = projectService.inferredProjects[1]; + checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + + host.writeFile(file2.path, `export * from "../c/f3"`); // now inferred project should inclule file3 + host.checkTimeoutQueueLengthAndRun(2); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProject0); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); + assert.strictEqual(projectService.inferredProjects[1], inferredProject1); + assert.isTrue(inferredProject1.isOrphan()); + }); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); - }); + it("deleted files affect project structure", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export * from "../c/f3"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); - it("ignores files excluded by a custom safe type list", () => { - const file1 = { - path: "/a/b/f1.js", - content: "export let x = 5" - }; - const office = { - path: "/lib/duckquack-3.min.js", - content: "whoa do @@ not parse me ok thanks!!!" - }; - const host = createServerHost([file1, office, customTypesMap]); - const projectService = createProjectService(host); - try { - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, office.path]) }); - const proj = projectService.externalProjects[0]; - assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); - assert.deepEqual(proj.getTypeAcquisition().include, ["duck-types"]); - } - finally { - projectService.resetSafeList(); - } - }); + projectService.openClientFile(file1.path); - it("file with name constructor.js doesnt cause issue with typeAcquisition when safe type list", () => { - const file1 = { - path: "/a/b/f1.js", - content: `export let x = 5; import { s } from "s"` - }; - const constructorFile = { - path: "/a/b/constructor.js", - content: "const x = 10;" - }; - const bliss = { - path: "/a/b/bliss.js", - content: "export function is() { return true; }" - }; - const host = createServerHost([file1, libFile, constructorFile, bliss, customTypesMap]); - let request: string | undefined; - const cachePath = "/a/data"; - const typingsInstaller: server.ITypingsInstaller = { - isKnownTypesPackageName: returnFalse, - installPackage: notImplemented, - enqueueInstallTypingsRequest: (proj, typeAcquisition, unresolvedImports) => { - assert.isUndefined(request); - request = JSON.stringify(server.createInstallTypingsRequest(proj, typeAcquisition, unresolvedImports || server.emptyArray, cachePath)); - }, - attach: noop, - onProjectClosed: noop, - globalTypingsCacheLocation: cachePath - }; + checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const projectName = "project"; - const projectService = createProjectService(host, { typingsInstaller }); - projectService.openExternalProject({ projectFileName: projectName, options: {}, rootFiles: toExternalFiles([file1.path, constructorFile.path, bliss.path]) }); - assert.equal(request, JSON.stringify({ - projectName, - fileNames: [libFile.path, file1.path, constructorFile.path, bliss.path], - compilerOptions: { allowNonTsExtensions: true, noEmitForJsFiles: true }, - typeAcquisition: { include: ["blissfuljs"], exclude: [], enable: true }, - unresolvedImports: ["s"], - projectRootPath: "/", - cachePath, - kind: "discover" - })); - const response = JSON.parse(request!); - request = undefined; - projectService.updateTypingsForProject({ - kind: "action::set", - projectName: response.projectName, - typeAcquisition: response.typeAcquisition, - compilerOptions: response.compilerOptions, - typings: emptyArray, - unresolvedImports: response.unresolvedImports, - }); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); - host.checkTimeoutQueueLength(0); - assert.isUndefined(request); - }); + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); - it("ignores files excluded by the default type list", () => { - const file1 = { - path: "/a/b/f1.js", - content: "export let x = 5" - }; - const minFile = { - path: "/c/moment.min.js", - content: "unspecified" - }; - const kendoFile1 = { - path: "/q/lib/kendo/kendo.all.min.js", - content: "unspecified" - }; - const kendoFile2 = { - path: "/q/lib/kendo/kendo.ui.min.js", - content: "unspecified" - }; - const kendoFile3 = { - path: "/q/lib/kendo-ui/kendo.all.js", - content: "unspecified" - }; - const officeFile1 = { - path: "/scripts/Office/1/excel-15.debug.js", - content: "unspecified" - }; - const officeFile2 = { - path: "/scripts/Office/1/powerpoint.js", - content: "unspecified" - }; - const files = [file1, minFile, kendoFile1, kendoFile2, kendoFile3, officeFile1, officeFile2]; - const host = createServerHost(files); - const projectService = createProjectService(host); - try { - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles(files.map(f => f.path)) }); - const proj = projectService.externalProjects[0]; - assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); - assert.deepEqual(proj.getTypeAcquisition().include, ["kendo-ui", "office"]); - } - finally { - projectService.resetSafeList(); - } - }); + host.deleteFile(file2.path); + host.checkTimeoutQueueLengthAndRun(2); - it("removes version numbers correctly", () => { - const testData: [string, string][] = [ - ["jquery-max", "jquery-max"], - ["jquery.min", "jquery"], - ["jquery-min.4.2.3", "jquery"], - ["jquery.min.4.2.1", "jquery"], - ["minimum", "minimum"], - ["min", "min"], - ["min.3.2", "min"], - ["jquery", "jquery"] - ]; - for (const t of testData) { - assert.equal(removeMinAndVersionNumbers(t[0]), t[1], t[0]); - } - }); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); - it("ignores files excluded by a legacy safe type list", () => { - const file1 = { - path: "/a/b/bliss.js", - content: "let x = 5" - }; - const file2 = { - path: "/a/b/foo.js", - content: "" - }; - const file3 = { - path: "/a/b/Bacon.js", - content: "let y = 5" - }; - const host = createServerHost([file1, file2, file3, customTypesMap]); - const projectService = createProjectService(host); - try { - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]), typeAcquisition: { enable: true } }); - const proj = projectService.externalProjects[0]; - assert.deepEqual(proj.getFileNames(), [file2.path]); - } - finally { - projectService.resetSafeList(); - } - }); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + }); - it("correctly migrate files between projects", () => { - const file1 = { - path: "/a/b/f1.ts", - content: ` - export * from "../c/f2"; - export * from "../d/f3";` - }; - const file2 = { - path: "/a/c/f2.ts", - content: "export let x = 1;" - }; - const file3 = { - path: "/a/d/f3.ts", - content: "export let y = 1;" - }; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); + it("ignores files excluded by a custom safe type list", () => { + const file1 = { + path: "/a/b/f1.js", + content: "export let x = 5" + }; + const office = { + path: "/lib/duckquack-3.min.js", + content: "whoa do @@ not parse me ok thanks!!!" + }; + const host = createServerHost([file1, office, customTypesMap]); + const projectService = createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, office.path]) }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); + assert.deepEqual(proj.getTypeAcquisition().include, ["duck-types"]); + } + finally { + projectService.resetSafeList(); + } + }); - projectService.openClientFile(file2.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); - let inferredProjects = projectService.inferredProjects.slice(); + it("file with name constructor.js doesnt cause issue with typeAcquisition when safe type list", () => { + const file1 = { + path: "/a/b/f1.js", + content: `export let x = 5; import { s } from "s"` + }; + const constructorFile = { + path: "/a/b/constructor.js", + content: "const x = 10;" + }; + const bliss = { + path: "/a/b/bliss.js", + content: "export function is() { return true; }" + }; + const host = createServerHost([file1, libFile, constructorFile, bliss, customTypesMap]); + let request: string | undefined; + const cachePath = "/a/data"; + const typingsInstaller: server.ITypingsInstaller = { + isKnownTypesPackageName: returnFalse, + installPackage: notImplemented, + enqueueInstallTypingsRequest: (proj, typeAcquisition, unresolvedImports) => { + assert.isUndefined(request); + request = JSON.stringify(server.createInstallTypingsRequest(proj, typeAcquisition, unresolvedImports || server.emptyArray, cachePath)); + }, + attach: noop, + onProjectClosed: noop, + globalTypingsCacheLocation: cachePath + }; + + const projectName = "project"; + const projectService = createProjectService(host, { typingsInstaller }); + projectService.openExternalProject({ projectFileName: projectName, options: {}, rootFiles: toExternalFiles([file1.path, constructorFile.path, bliss.path]) }); + assert.equal(request, JSON.stringify({ + projectName, + fileNames: [libFile.path, file1.path, constructorFile.path, bliss.path], + compilerOptions: { allowNonTsExtensions: true, noEmitForJsFiles: true }, + typeAcquisition: { include: ["blissfuljs"], exclude: [], enable: true }, + unresolvedImports: ["s"], + projectRootPath: "/", + cachePath, + kind: "discover" + })); + const response = JSON.parse(request!); + request = undefined; + projectService.updateTypingsForProject({ + kind: "action::set", + projectName: response.projectName, + typeAcquisition: response.typeAcquisition, + compilerOptions: response.compilerOptions, + typings: emptyArray, + unresolvedImports: response.unresolvedImports, + }); - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); - checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); - inferredProjects = projectService.inferredProjects.slice(); + host.checkTimeoutQueueLength(0); + assert.isUndefined(request); + }); - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - assert.notStrictEqual(projectService.inferredProjects[0], inferredProjects[0]); - assert.notStrictEqual(projectService.inferredProjects[0], inferredProjects[1]); - checkProjectRootFiles(projectService.inferredProjects[0], [file1.path]); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); - inferredProjects = projectService.inferredProjects.slice(); - - projectService.closeClientFile(file1.path); - checkNumberOfProjects(projectService, { inferredProjects: 3 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); - checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); - inferredProjects = projectService.inferredProjects.slice(); - - projectService.closeClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 3 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); - assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); - assert.strictEqual(projectService.inferredProjects[2], inferredProjects[2]); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); - assert.isTrue(projectService.inferredProjects[2].isOrphan()); - - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProjects[2]); - assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); - checkProjectActualFiles(projectService.inferredProjects[0], [file3.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); - }); + it("ignores files excluded by the default type list", () => { + const file1 = { + path: "/a/b/f1.js", + content: "export let x = 5" + }; + const minFile = { + path: "/c/moment.min.js", + content: "unspecified" + }; + const kendoFile1 = { + path: "/q/lib/kendo/kendo.all.min.js", + content: "unspecified" + }; + const kendoFile2 = { + path: "/q/lib/kendo/kendo.ui.min.js", + content: "unspecified" + }; + const kendoFile3 = { + path: "/q/lib/kendo-ui/kendo.all.js", + content: "unspecified" + }; + const officeFile1 = { + path: "/scripts/Office/1/excel-15.debug.js", + content: "unspecified" + }; + const officeFile2 = { + path: "/scripts/Office/1/powerpoint.js", + content: "unspecified" + }; + const files = [file1, minFile, kendoFile1, kendoFile2, kendoFile3, officeFile1, officeFile2]; + const host = createServerHost(files); + const projectService = createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles(files.map(f => f.path)) }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); + assert.deepEqual(proj.getTypeAcquisition().include, ["kendo-ui", "office"]); + } + finally { + projectService.resetSafeList(); + } + }); - it("regression test for crash in acquireOrUpdateDocument", () => { - const tsFile = { - fileName: "/a/b/file1.ts", - path: "/a/b/file1.ts", - content: "" - }; - const jsFile = { - path: "/a/b/file1.js", - content: "var x = 10;", - fileName: "/a/b/file1.js", - scriptKind: "JS" as "JS" - }; + it("removes version numbers correctly", () => { + const testData: [string, string][] = [ + ["jquery-max", "jquery-max"], + ["jquery.min", "jquery"], + ["jquery-min.4.2.3", "jquery"], + ["jquery.min.4.2.1", "jquery"], + ["minimum", "minimum"], + ["min", "min"], + ["min.3.2", "min"], + ["jquery", "jquery"] + ]; + for (const t of testData) { + assert.equal(removeMinAndVersionNumbers(t[0]), t[1], t[0]); + } + }); - const host = createServerHost([]); - const projectService = createProjectService(host); - projectService.applyChangesInOpenFiles(singleIterator(tsFile)); - const projs = projectService.synchronizeProjectList([]); - projectService.findProject(projs[0].info!.projectName)!.getLanguageService().getNavigationBarItems(tsFile.fileName); - projectService.synchronizeProjectList([projs[0].info!]); - projectService.applyChangesInOpenFiles(singleIterator(jsFile)); - }); + it("ignores files excluded by a legacy safe type list", () => { + const file1 = { + path: "/a/b/bliss.js", + content: "let x = 5" + }; + const file2 = { + path: "/a/b/foo.js", + content: "" + }; + const file3 = { + path: "/a/b/Bacon.js", + content: "let y = 5" + }; + const host = createServerHost([file1, file2, file3, customTypesMap]); + const projectService = createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]), typeAcquisition: { enable: true } }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(), [file2.path]); + } + finally { + projectService.resetSafeList(); + } + }); - it("config file is deleted", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1;" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 2;" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {} }) - }; - const host = createServerHost([file1, file2, config]); - const projectService = createProjectService(host); + it("correctly migrate files between projects", () => { + const file1 = { + path: "/a/b/f1.ts", + content: ` + export * from "../c/f2"; + export * from "../d/f3";` + }; + const file2 = { + path: "/a/c/f2.ts", + content: "export let x = 1;" + }; + const file3 = { + path: "/a/d/f3.ts", + content: "export let y = 1;" + }; + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); + let inferredProjects = projectService.inferredProjects.slice(); + + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); + checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + inferredProjects = projectService.inferredProjects.slice(); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + assert.notStrictEqual(projectService.inferredProjects[0], inferredProjects[0]); + assert.notStrictEqual(projectService.inferredProjects[0], inferredProjects[1]); + checkProjectRootFiles(projectService.inferredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); + inferredProjects = projectService.inferredProjects.slice(); + + projectService.closeClientFile(file1.path); + checkNumberOfProjects(projectService, { inferredProjects: 3 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); + inferredProjects = projectService.inferredProjects.slice(); + + projectService.closeClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 3 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); + assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); + assert.strictEqual(projectService.inferredProjects[2], inferredProjects[2]); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + assert.isTrue(projectService.inferredProjects[2].isOrphan()); + + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[2]); + assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); + checkProjectActualFiles(projectService.inferredProjects[0], [file3.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + }); - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + it("regression test for crash in acquireOrUpdateDocument", () => { + const tsFile = { + fileName: "/a/b/file1.ts", + path: "/a/b/file1.ts", + content: "" + }; + const jsFile = { + path: "/a/b/file1.js", + content: "var x = 10;", + fileName: "/a/b/file1.js", + scriptKind: "JS" as "JS" + }; + + const host = createServerHost([]); + const projectService = createProjectService(host); + projectService.applyChangesInOpenFiles(singleIterator(tsFile)); + const projs = projectService.synchronizeProjectList([]); + projectService.findProject(projs[0].info!.projectName)!.getLanguageService().getNavigationBarItems(tsFile.fileName); + projectService.synchronizeProjectList([projs[0].info!]); + projectService.applyChangesInOpenFiles(singleIterator(jsFile)); + }); - projectService.openClientFile(file2.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + it("config file is deleted", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1;" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 2;" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const host = createServerHost([file1, file2, config]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + host.deleteFile(config.path); + host.checkTimeoutQueueLengthAndRun(1); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + }); - host.deleteFile(config.path); - host.checkTimeoutQueueLengthAndRun(1); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + it("loading files with correct priority", () => { + const f1 = { + path: "/a/main.ts", + content: "let x = 1" + }; + const f2 = { + path: "/a/main.js", + content: "var y = 1" + }; + const f3 = { + path: "/main.js", + content: "var y = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { allowJs: true } + }) + }; + const host = createServerHost([f1, f2, f3, config]); + const projectService = createProjectService(host); + projectService.setHostConfiguration({ + extraFileExtensions: [ + { extension: ".js", isMixedContent: false }, + { extension: ".html", isMixedContent: true } + ] }); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path]); + + // Since f2 refers to config file as the default project, it needs to be kept alive + projectService.closeClientFile(f1.path); + projectService.openClientFile(f2.path); + projectService.checkNumberOfProjects({ inferredProjects: 1, configuredProjects: 1 }); + assert.isDefined(projectService.configuredProjects.get(config.path)); + checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]); + + // Should close configured project with next file open + projectService.closeClientFile(f2.path); + projectService.openClientFile(f3.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(config.path)); + checkProjectActualFiles(projectService.inferredProjects[0], [f3.path]); + }); - it("loading files with correct priority", () => { - const f1 = { - path: "/a/main.ts", - content: "let x = 1" - }; - const f2 = { - path: "/a/main.js", - content: "var y = 1" - }; - const f3 = { - path: "/main.js", - content: "var y = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { allowJs: true } - }) - }; - const host = createServerHost([f1, f2, f3, config]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ - extraFileExtensions: [ - { extension: ".js", isMixedContent: false }, - { extension: ".html", isMixedContent: true } - ] - }); - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path]); - - // Since f2 refers to config file as the default project, it needs to be kept alive - projectService.closeClientFile(f1.path); - projectService.openClientFile(f2.path); - projectService.checkNumberOfProjects({ inferredProjects: 1, configuredProjects: 1 }); - assert.isDefined(projectService.configuredProjects.get(config.path)); - checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]); - - // Should close configured project with next file open - projectService.closeClientFile(f2.path); - projectService.openClientFile(f3.path); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - assert.isUndefined(projectService.configuredProjects.get(config.path)); - checkProjectActualFiles(projectService.inferredProjects[0], [f3.path]); - }); + it("tsconfig script block support", () => { + const file1 = { + path: "/a/b/f1.ts", + content: ` ` + }; + const file2 = { + path: "/a/b/f2.html", + content: `var hello = "hello";` + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true } }) + }; + const host = createServerHost([file1, file2, config]); + const session = createSession(host); + openFilesForSession([file1], session); + const projectService = session.getProjectService(); + + // HTML file will not be included in any projects yet + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const configuredProj = configuredProjectAt(projectService, 0); + checkProjectActualFiles(configuredProj, [file1.path, config.path]); + + // Specify .html extension as mixed content + const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; + const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); + session.executeCommand(configureHostRequest); + + // The configured project should now be updated to include html file + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(configuredProjectAt(projectService, 0), configuredProj, "Same configured project should be updated"); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + // Open HTML file + projectService.applyChangesInOpenFiles(singleIterator({ + fileName: file2.path, + hasMixedContent: true, + scriptKind: ScriptKind.JS, + content: `var hello = "hello";` + })); + // Now HTML file is included in the project + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + // Check identifiers defined in HTML content are available in .ts file + const project = configuredProjectAt(projectService, 0); + let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, emptyOptions); + assert(completions && completions.entries[1].name === "hello", `expected entry hello to be in completion list`); + assert(completions && completions.entries[0].name === "globalThis", `first entry should be globalThis (not strictly relevant for this test).`); + + // Close HTML file + projectService.applyChangesInOpenFiles( + /*openFiles*/ undefined, + /*changedFiles*/ undefined, + /*closedFiles*/[file2.path]); + + // HTML file is still included in project + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + // Check identifiers defined in HTML content are not available in .ts file + completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, emptyOptions); + assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`); + }); - it("tsconfig script block support", () => { - const file1 = { - path: "/a/b/f1.ts", - content: ` ` - }; - const file2 = { - path: "/a/b/f2.html", - content: `var hello = "hello";` - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true } }) - }; - const host = createServerHost([file1, file2, config]); - const session = createSession(host); - openFilesForSession([file1], session); - const projectService = session.getProjectService(); + it("no tsconfig script block diagnostic errors", () => { - // HTML file will not be included in any projects yet - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const configuredProj = configuredProjectAt(projectService, 0); - checkProjectActualFiles(configuredProj, [file1.path, config.path]); + // #1. Ensure no diagnostic errors when allowJs is true + const file1 = { + path: "/a/b/f1.ts", + content: ` ` + }; + const file2 = { + path: "/a/b/f2.html", + content: `var hello = "hello";` + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true } }) + }; - // Specify .html extension as mixed content - const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; - const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); - session.executeCommand(configureHostRequest); + let host = createServerHost([file1, file2, config1, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + let session = createSession(host); - // The configured project should now be updated to include html file - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(configuredProjectAt(projectService, 0), configuredProj, "Same configured project should be updated"); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); - - // Open HTML file - projectService.applyChangesInOpenFiles(singleIterator({ - fileName: file2.path, - hasMixedContent: true, - scriptKind: ScriptKind.JS, - content: `var hello = "hello";` - })); - // Now HTML file is included in the project - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + // Specify .html extension as mixed content in a configure host request + const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; + const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); + session.executeCommand(configureHostRequest); - // Check identifiers defined in HTML content are available in .ts file - const project = configuredProjectAt(projectService, 0); - let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, emptyOptions); - assert(completions && completions.entries[1].name === "hello", `expected entry hello to be in completion list`); - assert(completions && completions.entries[0].name === "globalThis", `first entry should be globalThis (not strictly relevant for this test).`); + openFilesForSession([file1], session); + let projectService = session.getProjectService(); - // Close HTML file - projectService.applyChangesInOpenFiles( - /*openFiles*/ undefined, - /*changedFiles*/ undefined, - /*closedFiles*/[file2.path]); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - // HTML file is still included in project - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + let diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); - // Check identifiers defined in HTML content are not available in .ts file - completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, emptyOptions); - assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`); - }); + // #2. Ensure no errors when allowJs is false + const config2 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: false } }) + }; - it("no tsconfig script block diagnostic errors", () => { + host = createServerHost([file1, file2, config2, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + session = createSession(host); - // #1. Ensure no diagnostic errors when allowJs is true - const file1 = { - path: "/a/b/f1.ts", - content: ` ` - }; - const file2 = { - path: "/a/b/f2.html", - content: `var hello = "hello";` - }; - const config1 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true } }) - }; + session.executeCommand(configureHostRequest); - let host = createServerHost([file1, file2, config1, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - let session = createSession(host); + openFilesForSession([file1], session); + projectService = session.getProjectService(); - // Specify .html extension as mixed content in a configure host request - const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; - const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); - session.executeCommand(configureHostRequest); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - openFilesForSession([file1], session); - let projectService = session.getProjectService(); + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + // #3. Ensure no errors when compiler options aren't specified + const config3 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({}) + }; - let diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); + host = createServerHost([file1, file2, config3, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + session = createSession(host); - // #2. Ensure no errors when allowJs is false - const config2 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: false } }) - }; + session.executeCommand(configureHostRequest); - host = createServerHost([file1, file2, config2, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - session = createSession(host); + openFilesForSession([file1], session); + projectService = session.getProjectService(); - session.executeCommand(configureHostRequest); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - openFilesForSession([file1], session); - projectService = session.getProjectService(); + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + // #4. Ensure no errors when files are explicitly specified in tsconfig + const config4 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true }, files: [file1.path, file2.path] }) + }; - diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); + host = createServerHost([file1, file2, config4, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + session = createSession(host); - // #3. Ensure no errors when compiler options aren't specified - const config3 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({}) - }; + session.executeCommand(configureHostRequest); - host = createServerHost([file1, file2, config3, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - session = createSession(host); + openFilesForSession([file1], session); + projectService = session.getProjectService(); - session.executeCommand(configureHostRequest); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - openFilesForSession([file1], session); - projectService = session.getProjectService(); + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + // #4. Ensure no errors when files are explicitly excluded in tsconfig + const config5 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true }, exclude: [file2.path] }) + }; - diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); + host = createServerHost([file1, file2, config5, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + session = createSession(host); - // #4. Ensure no errors when files are explicitly specified in tsconfig - const config4 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true }, files: [file1.path, file2.path] }) - }; + session.executeCommand(configureHostRequest); - host = createServerHost([file1, file2, config4, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - session = createSession(host); + openFilesForSession([file1], session); + projectService = session.getProjectService(); - session.executeCommand(configureHostRequest); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - openFilesForSession([file1], session); - projectService = session.getProjectService(); + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); + }); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + it("project structure update is deferred if files are not added\removed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `import {x} from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: "export let x = 1" + }; + const host = createServerHost([file1, file2]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + projectService.applyChangesInOpenFiles( + /*openFiles*/ undefined, + /*changedFiles*/singleIterator({ fileName: file1.path, changes: singleIterator({ span: createTextSpan(0, file1.path.length), newText: "let y = 1" }) }), + /*closedFiles*/ undefined); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + projectService.ensureInferredProjectsUpToDate_TestOnly(); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + }); - diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); + it("files with mixed content are handled correctly", () => { + const file1 = { + path: "/a/b/f1.html", + content: `